drm/msm/dsi-staging: add dsi phy driver
Add dsi phy driver. Configure clocks, regulators, gpios, lanes,
and other dsi phy related hardware.
Change-Id: Ic06c17f57496b22bcf820ea5c6929e334fa99a09
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_phy.c b/drivers/gpu/drm/msm/dsi-staging/dsi_phy.c
new file mode 100644
index 0000000..d53ef1140
--- /dev/null
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_phy.c
@@ -0,0 +1,859 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "msm-dsi-phy:[%s] " fmt, __func__
+
+#include <linux/of_device.h>
+#include <linux/err.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk.h>
+#include <linux/msm-bus.h>
+#include <linux/list.h>
+
+#include "msm_drv.h"
+#include "msm_kms.h"
+#include "msm_gpu.h"
+#include "dsi_phy.h"
+#include "dsi_phy_hw.h"
+#include "dsi_clk_pwr.h"
+#include "dsi_catalog.h"
+
+#define DSI_PHY_DEFAULT_LABEL "MDSS PHY CTRL"
+
+struct dsi_phy_list_item {
+ struct msm_dsi_phy *phy;
+ struct list_head list;
+};
+
+static LIST_HEAD(dsi_phy_list);
+static DEFINE_MUTEX(dsi_phy_list_lock);
+
+static const struct dsi_ver_spec_info dsi_phy_v1_0 = {
+ .version = DSI_PHY_VERSION_1_0,
+ .lane_cfg_count = 4,
+ .strength_cfg_count = 2,
+ .regulator_cfg_count = 1,
+ .timing_cfg_count = 8,
+};
+static const struct dsi_ver_spec_info dsi_phy_v2_0 = {
+ .version = DSI_PHY_VERSION_2_0,
+ .lane_cfg_count = 4,
+ .strength_cfg_count = 2,
+ .regulator_cfg_count = 1,
+ .timing_cfg_count = 8,
+};
+static const struct dsi_ver_spec_info dsi_phy_v3_0 = {
+ .version = DSI_PHY_VERSION_3_0,
+ .lane_cfg_count = 4,
+ .strength_cfg_count = 2,
+ .regulator_cfg_count = 1,
+ .timing_cfg_count = 8,
+};
+static const struct dsi_ver_spec_info dsi_phy_v4_0 = {
+ .version = DSI_PHY_VERSION_4_0,
+ .lane_cfg_count = 4,
+ .strength_cfg_count = 2,
+ .regulator_cfg_count = 1,
+ .timing_cfg_count = 8,
+};
+
+static const struct of_device_id msm_dsi_phy_of_match[] = {
+ { .compatible = "qcom,dsi-phy-v1.0",
+ .data = &dsi_phy_v1_0,},
+ { .compatible = "qcom,dsi-phy-v2.0",
+ .data = &dsi_phy_v2_0,},
+ { .compatible = "qcom,dsi-phy-v3.0",
+ .data = &dsi_phy_v3_0,},
+ { .compatible = "qcom,dsi-phy-v4.0",
+ .data = &dsi_phy_v4_0,},
+ {}
+};
+
+static int dsi_phy_regmap_init(struct platform_device *pdev,
+ struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+ void __iomem *ptr;
+
+ ptr = msm_ioremap(pdev, "dsi_phy", phy->name);
+ if (IS_ERR(ptr)) {
+ rc = PTR_ERR(ptr);
+ return rc;
+ }
+
+ phy->hw.base = ptr;
+
+ pr_debug("[%s] map dsi_phy registers to %p\n", phy->name, phy->hw.base);
+
+ return rc;
+}
+
+static int dsi_phy_regmap_deinit(struct msm_dsi_phy *phy)
+{
+ pr_debug("[%s] unmap registers\n", phy->name);
+ return 0;
+}
+
+static int dsi_phy_clocks_deinit(struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+ struct dsi_core_clk_info *core = &phy->clks.core_clks;
+
+ if (core->mdp_core_clk)
+ devm_clk_put(&phy->pdev->dev, core->mdp_core_clk);
+ if (core->iface_clk)
+ devm_clk_put(&phy->pdev->dev, core->iface_clk);
+ if (core->core_mmss_clk)
+ devm_clk_put(&phy->pdev->dev, core->core_mmss_clk);
+ if (core->bus_clk)
+ devm_clk_put(&phy->pdev->dev, core->bus_clk);
+
+ memset(core, 0x0, sizeof(*core));
+
+ return rc;
+}
+
+static int dsi_phy_clocks_init(struct platform_device *pdev,
+ struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+ struct dsi_core_clk_info *core = &phy->clks.core_clks;
+
+ core->mdp_core_clk = devm_clk_get(&pdev->dev, "mdp_core_clk");
+ if (IS_ERR(core->mdp_core_clk)) {
+ rc = PTR_ERR(core->mdp_core_clk);
+ pr_err("failed to get mdp_core_clk, rc=%d\n", rc);
+ goto fail;
+ }
+
+ core->iface_clk = devm_clk_get(&pdev->dev, "iface_clk");
+ if (IS_ERR(core->iface_clk)) {
+ rc = PTR_ERR(core->iface_clk);
+ pr_err("failed to get iface_clk, rc=%d\n", rc);
+ goto fail;
+ }
+
+ core->core_mmss_clk = devm_clk_get(&pdev->dev, "core_mmss_clk");
+ if (IS_ERR(core->core_mmss_clk)) {
+ rc = PTR_ERR(core->core_mmss_clk);
+ pr_err("failed to get core_mmss_clk, rc=%d\n", rc);
+ goto fail;
+ }
+
+ core->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
+ if (IS_ERR(core->bus_clk)) {
+ rc = PTR_ERR(core->bus_clk);
+ pr_err("failed to get bus_clk, rc=%d\n", rc);
+ goto fail;
+ }
+
+ return rc;
+fail:
+ dsi_phy_clocks_deinit(phy);
+ return rc;
+}
+
+static int dsi_phy_supplies_init(struct platform_device *pdev,
+ struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+ int i = 0;
+ struct dsi_regulator_info *regs;
+ struct regulator *vreg = NULL;
+
+ regs = &phy->pwr_info.digital;
+ regs->vregs = devm_kzalloc(&pdev->dev, sizeof(struct dsi_vreg),
+ GFP_KERNEL);
+ if (!regs->vregs)
+ goto error;
+
+ regs->count = 1;
+ snprintf(regs->vregs->vreg_name,
+ ARRAY_SIZE(regs->vregs[i].vreg_name),
+ "%s", "gdsc");
+
+ rc = dsi_clk_pwr_get_dt_vreg_data(&pdev->dev,
+ &phy->pwr_info.phy_pwr,
+ "qcom,phy-supply-entries");
+ if (rc) {
+ pr_err("failed to get host power supplies, rc = %d\n", rc);
+ goto error_digital;
+ }
+
+ regs = &phy->pwr_info.digital;
+ for (i = 0; i < regs->count; i++) {
+ vreg = devm_regulator_get(&pdev->dev, regs->vregs[i].vreg_name);
+ rc = PTR_RET(vreg);
+ if (rc) {
+ pr_err("failed to get %s regulator\n",
+ regs->vregs[i].vreg_name);
+ goto error_host_pwr;
+ }
+ regs->vregs[i].vreg = vreg;
+ }
+
+ regs = &phy->pwr_info.phy_pwr;
+ for (i = 0; i < regs->count; i++) {
+ vreg = devm_regulator_get(&pdev->dev, regs->vregs[i].vreg_name);
+ rc = PTR_RET(vreg);
+ if (rc) {
+ pr_err("failed to get %s regulator\n",
+ regs->vregs[i].vreg_name);
+ for (--i; i >= 0; i--)
+ devm_regulator_put(regs->vregs[i].vreg);
+ goto error_digital_put;
+ }
+ regs->vregs[i].vreg = vreg;
+ }
+
+ return rc;
+
+error_digital_put:
+ regs = &phy->pwr_info.digital;
+ for (i = 0; i < regs->count; i++)
+ devm_regulator_put(regs->vregs[i].vreg);
+error_host_pwr:
+ devm_kfree(&pdev->dev, phy->pwr_info.phy_pwr.vregs);
+ phy->pwr_info.phy_pwr.vregs = NULL;
+ phy->pwr_info.phy_pwr.count = 0;
+error_digital:
+ devm_kfree(&pdev->dev, phy->pwr_info.digital.vregs);
+ phy->pwr_info.digital.vregs = NULL;
+ phy->pwr_info.digital.count = 0;
+error:
+ return rc;
+}
+
+static int dsi_phy_supplies_deinit(struct msm_dsi_phy *phy)
+{
+ int i = 0;
+ int rc = 0;
+ struct dsi_regulator_info *regs;
+
+ regs = &phy->pwr_info.digital;
+ for (i = 0; i < regs->count; i++) {
+ if (!regs->vregs[i].vreg)
+ pr_err("vreg is NULL, should not reach here\n");
+ else
+ devm_regulator_put(regs->vregs[i].vreg);
+ }
+
+ regs = &phy->pwr_info.phy_pwr;
+ for (i = 0; i < regs->count; i++) {
+ if (!regs->vregs[i].vreg)
+ pr_err("vreg is NULL, should not reach here\n");
+ else
+ devm_regulator_put(regs->vregs[i].vreg);
+ }
+
+ if (phy->pwr_info.phy_pwr.vregs) {
+ devm_kfree(&phy->pdev->dev, phy->pwr_info.phy_pwr.vregs);
+ phy->pwr_info.phy_pwr.vregs = NULL;
+ phy->pwr_info.phy_pwr.count = 0;
+ }
+ if (phy->pwr_info.digital.vregs) {
+ devm_kfree(&phy->pdev->dev, phy->pwr_info.digital.vregs);
+ phy->pwr_info.digital.vregs = NULL;
+ phy->pwr_info.digital.count = 0;
+ }
+
+ return rc;
+}
+
+static int dsi_phy_parse_dt_per_lane_cfgs(struct platform_device *pdev,
+ struct dsi_phy_per_lane_cfgs *cfg,
+ char *property)
+{
+ int rc = 0, i = 0, j = 0;
+ const u8 *data;
+ u32 len = 0;
+
+ data = of_get_property(pdev->dev.of_node, property, &len);
+ if (!data) {
+ pr_err("Unable to read Phy %s settings\n", property);
+ return -EINVAL;
+ }
+
+ if (len != DSI_LANE_MAX * cfg->count_per_lane) {
+ pr_err("incorrect phy %s settings, exp=%d, act=%d\n",
+ property, (DSI_LANE_MAX * cfg->count_per_lane), len);
+ return -EINVAL;
+ }
+
+ for (i = DSI_LOGICAL_LANE_0; i < DSI_LANE_MAX; i++) {
+ for (j = 0; j < cfg->count_per_lane; j++) {
+ cfg->lane[i][j] = *data;
+ data++;
+ }
+ }
+
+ return rc;
+}
+
+static int dsi_phy_settings_init(struct platform_device *pdev,
+ struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+ struct dsi_phy_per_lane_cfgs *lane = &phy->cfg.lanecfg;
+ struct dsi_phy_per_lane_cfgs *strength = &phy->cfg.strength;
+ struct dsi_phy_per_lane_cfgs *timing = &phy->cfg.timing;
+ struct dsi_phy_per_lane_cfgs *regs = &phy->cfg.regulators;
+
+ lane->count_per_lane = phy->ver_info->lane_cfg_count;
+ rc = dsi_phy_parse_dt_per_lane_cfgs(pdev, lane,
+ "qcom,platform-lane-config");
+ if (rc) {
+ pr_err("failed to parse lane cfgs, rc=%d\n", rc);
+ goto err;
+ }
+
+ strength->count_per_lane = phy->ver_info->strength_cfg_count;
+ rc = dsi_phy_parse_dt_per_lane_cfgs(pdev, strength,
+ "qcom,platform-strength-ctrl");
+ if (rc) {
+ pr_err("failed to parse lane cfgs, rc=%d\n", rc);
+ goto err;
+ }
+
+ regs->count_per_lane = phy->ver_info->regulator_cfg_count;
+ rc = dsi_phy_parse_dt_per_lane_cfgs(pdev, regs,
+ "qcom,platform-regulator-settings");
+ if (rc) {
+ pr_err("failed to parse lane cfgs, rc=%d\n", rc);
+ goto err;
+ }
+
+ /* Actual timing values are dependent on panel */
+ timing->count_per_lane = phy->ver_info->timing_cfg_count;
+
+err:
+ lane->count_per_lane = 0;
+ strength->count_per_lane = 0;
+ regs->count_per_lane = 0;
+ timing->count_per_lane = 0;
+ return rc;
+}
+
+static int dsi_phy_settings_deinit(struct msm_dsi_phy *phy)
+{
+ memset(&phy->cfg.lanecfg, 0x0, sizeof(phy->cfg.lanecfg));
+ memset(&phy->cfg.strength, 0x0, sizeof(phy->cfg.strength));
+ memset(&phy->cfg.timing, 0x0, sizeof(phy->cfg.timing));
+ memset(&phy->cfg.regulators, 0x0, sizeof(phy->cfg.regulators));
+ return 0;
+}
+
+static int dsi_phy_driver_probe(struct platform_device *pdev)
+{
+ struct msm_dsi_phy *dsi_phy;
+ struct dsi_phy_list_item *item;
+ const struct of_device_id *id;
+ const struct dsi_ver_spec_info *ver_info;
+ int rc = 0;
+ u32 index = 0;
+
+ if (!pdev || !pdev->dev.of_node) {
+ pr_err("pdev not found\n");
+ return -ENODEV;
+ }
+
+ id = of_match_node(msm_dsi_phy_of_match, pdev->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ ver_info = id->data;
+
+ item = devm_kzalloc(&pdev->dev, sizeof(*item), GFP_KERNEL);
+ if (!item)
+ return -ENOMEM;
+
+
+ dsi_phy = devm_kzalloc(&pdev->dev, sizeof(*dsi_phy), GFP_KERNEL);
+ if (!dsi_phy) {
+ devm_kfree(&pdev->dev, item);
+ return -ENOMEM;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "cell-index", &index);
+ if (rc) {
+ pr_debug("cell index not set, default to 0\n");
+ index = 0;
+ }
+
+ dsi_phy->index = index;
+
+ dsi_phy->name = of_get_property(pdev->dev.of_node, "label", NULL);
+ if (!dsi_phy->name)
+ dsi_phy->name = DSI_PHY_DEFAULT_LABEL;
+
+ pr_debug("Probing %s device\n", dsi_phy->name);
+
+ rc = dsi_phy_regmap_init(pdev, dsi_phy);
+ if (rc) {
+ pr_err("Failed to parse register information, rc=%d\n", rc);
+ goto fail;
+ }
+
+ rc = dsi_phy_clocks_init(pdev, dsi_phy);
+ if (rc) {
+ pr_err("failed to parse clock information, rc = %d\n", rc);
+ goto fail_regmap;
+ }
+
+ rc = dsi_phy_supplies_init(pdev, dsi_phy);
+ if (rc) {
+ pr_err("failed to parse voltage supplies, rc = %d\n", rc);
+ goto fail_clks;
+ }
+
+ rc = dsi_catalog_phy_setup(&dsi_phy->hw, ver_info->version,
+ dsi_phy->index);
+ if (rc) {
+ pr_err("Catalog does not support version (%d)\n",
+ ver_info->version);
+ goto fail_supplies;
+ }
+
+ dsi_phy->ver_info = ver_info;
+ rc = dsi_phy_settings_init(pdev, dsi_phy);
+ if (rc) {
+ pr_err("Failed to parse phy setting, rc=%d\n", rc);
+ goto fail_supplies;
+ }
+
+ item->phy = dsi_phy;
+
+ mutex_lock(&dsi_phy_list_lock);
+ list_add(&item->list, &dsi_phy_list);
+ mutex_unlock(&dsi_phy_list_lock);
+
+ mutex_init(&dsi_phy->phy_lock);
+ /** TODO: initialize debugfs */
+ dsi_phy->pdev = pdev;
+ platform_set_drvdata(pdev, dsi_phy);
+ pr_debug("Probe successful for %s\n", dsi_phy->name);
+ return 0;
+
+fail_supplies:
+ (void)dsi_phy_supplies_deinit(dsi_phy);
+fail_clks:
+ (void)dsi_phy_clocks_deinit(dsi_phy);
+fail_regmap:
+ (void)dsi_phy_regmap_deinit(dsi_phy);
+fail:
+ devm_kfree(&pdev->dev, dsi_phy);
+ devm_kfree(&pdev->dev, item);
+ return rc;
+}
+
+static int dsi_phy_driver_remove(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct msm_dsi_phy *phy = platform_get_drvdata(pdev);
+ struct list_head *pos, *tmp;
+
+ if (!pdev || !phy) {
+ pr_err("Invalid device\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&dsi_phy_list_lock);
+ list_for_each_safe(pos, tmp, &dsi_phy_list) {
+ struct dsi_phy_list_item *n;
+
+ n = list_entry(pos, struct dsi_phy_list_item, list);
+ if (n->phy == phy) {
+ list_del(&n->list);
+ devm_kfree(&pdev->dev, n);
+ break;
+ }
+ }
+ mutex_unlock(&dsi_phy_list_lock);
+
+ mutex_lock(&phy->phy_lock);
+ rc = dsi_phy_settings_deinit(phy);
+ if (rc)
+ pr_err("failed to deinitialize phy settings, rc=%d\n", rc);
+
+ rc = dsi_phy_supplies_deinit(phy);
+ if (rc)
+ pr_err("failed to deinitialize voltage supplies, rc=%d\n", rc);
+
+ rc = dsi_phy_clocks_deinit(phy);
+ if (rc)
+ pr_err("failed to deinitialize clocks, rc=%d\n", rc);
+
+ rc = dsi_phy_regmap_deinit(phy);
+ if (rc)
+ pr_err("failed to deinitialize regmap, rc=%d\n", rc);
+ mutex_unlock(&phy->phy_lock);
+
+ mutex_destroy(&phy->phy_lock);
+ devm_kfree(&pdev->dev, phy);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver dsi_phy_platform_driver = {
+ .probe = dsi_phy_driver_probe,
+ .remove = dsi_phy_driver_remove,
+ .driver = {
+ .name = "msm_dsi_phy",
+ .of_match_table = msm_dsi_phy_of_match,
+ },
+};
+
+static void dsi_phy_enable_hw(struct msm_dsi_phy *phy)
+{
+ if (phy->hw.ops.regulator_enable)
+ phy->hw.ops.regulator_enable(&phy->hw, &phy->cfg.regulators);
+
+ if (phy->hw.ops.enable)
+ phy->hw.ops.enable(&phy->hw, &phy->cfg);
+}
+
+static void dsi_phy_disable_hw(struct msm_dsi_phy *phy)
+{
+ if (phy->hw.ops.disable)
+ phy->hw.ops.disable(&phy->hw);
+
+ if (phy->hw.ops.regulator_disable)
+ phy->hw.ops.regulator_disable(&phy->hw);
+}
+
+/**
+ * dsi_phy_get() - get a dsi phy handle from device node
+ * @of_node: device node for dsi phy controller
+ *
+ * Gets the DSI PHY handle for the corresponding of_node. The ref count is
+ * incremented to one all subsequents get will fail until the original client
+ * calls a put.
+ *
+ * Return: DSI PHY handle or an error code.
+ */
+struct msm_dsi_phy *dsi_phy_get(struct device_node *of_node)
+{
+ struct list_head *pos, *tmp;
+ struct msm_dsi_phy *phy = NULL;
+
+ mutex_lock(&dsi_phy_list_lock);
+ list_for_each_safe(pos, tmp, &dsi_phy_list) {
+ struct dsi_phy_list_item *n;
+
+ n = list_entry(pos, struct dsi_phy_list_item, list);
+ if (n->phy->pdev->dev.of_node == of_node) {
+ phy = n->phy;
+ break;
+ }
+ }
+ mutex_unlock(&dsi_phy_list_lock);
+
+ if (!phy) {
+ pr_err("Device with of node not found\n");
+ phy = ERR_PTR(-EPROBE_DEFER);
+ return phy;
+ }
+
+ mutex_lock(&phy->phy_lock);
+ if (phy->refcount > 0) {
+ pr_err("[PHY_%d] Device under use\n", phy->index);
+ phy = ERR_PTR(-EINVAL);
+ } else {
+ phy->refcount++;
+ }
+ mutex_unlock(&phy->phy_lock);
+ return phy;
+}
+
+/**
+ * dsi_phy_put() - release dsi phy handle
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Release the DSI PHY hardware. Driver will clean up all resources and puts
+ * back the DSI PHY into reset state.
+ */
+void dsi_phy_put(struct msm_dsi_phy *dsi_phy)
+{
+ mutex_lock(&dsi_phy->phy_lock);
+
+ if (dsi_phy->refcount == 0)
+ pr_err("Unbalanced dsi_phy_put call\n");
+ else
+ dsi_phy->refcount--;
+
+ mutex_unlock(&dsi_phy->phy_lock);
+}
+
+/**
+ * dsi_phy_drv_init() - initialize dsi phy driver
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Initializes DSI PHY driver. Should be called after dsi_phy_get().
+ *
+ * Return: error code.
+ */
+int dsi_phy_drv_init(struct msm_dsi_phy *dsi_phy)
+{
+ return 0;
+}
+
+/**
+ * dsi_phy_drv_deinit() - de-initialize dsi phy driver
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Release all resources acquired by dsi_phy_drv_init().
+ *
+ * Return: error code.
+ */
+int dsi_phy_drv_deinit(struct msm_dsi_phy *dsi_phy)
+{
+ return 0;
+}
+
+/**
+ * dsi_phy_validate_mode() - validate a display mode
+ * @dsi_phy: DSI PHY handle.
+ * @mode: Mode information.
+ *
+ * Validation will fail if the mode cannot be supported by the PHY driver or
+ * hardware.
+ *
+ * Return: error code.
+ */
+int dsi_phy_validate_mode(struct msm_dsi_phy *dsi_phy,
+ struct dsi_mode_info *mode)
+{
+ int rc = 0;
+
+ if (!dsi_phy || !mode) {
+ pr_err("Invalid params\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&dsi_phy->phy_lock);
+
+ pr_debug("[PHY_%d] Skipping validation\n", dsi_phy->index);
+
+ mutex_unlock(&dsi_phy->phy_lock);
+ return rc;
+}
+
+/**
+ * dsi_phy_set_power_state() - enable/disable dsi phy power supplies
+ * @dsi_phy: DSI PHY handle.
+ * @enable: Boolean flag to enable/disable.
+ *
+ * Return: error code.
+ */
+int dsi_phy_set_power_state(struct msm_dsi_phy *dsi_phy, bool enable)
+{
+ int rc = 0;
+
+ if (!dsi_phy) {
+ pr_err("Invalid params\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&dsi_phy->phy_lock);
+
+ if (enable == dsi_phy->power_state) {
+ pr_err("[PHY_%d] No state change\n", dsi_phy->index);
+ goto error;
+ }
+
+ if (enable) {
+ rc = dsi_pwr_enable_regulator(&dsi_phy->pwr_info.digital, true);
+ if (rc) {
+ pr_err("failed to enable digital regulator\n");
+ goto error;
+ }
+ rc = dsi_pwr_enable_regulator(&dsi_phy->pwr_info.phy_pwr, true);
+ if (rc) {
+ pr_err("failed to enable phy power\n");
+ (void)dsi_pwr_enable_regulator(
+ &dsi_phy->pwr_info.digital,
+ false
+ );
+ goto error;
+ }
+ } else {
+ rc = dsi_pwr_enable_regulator(&dsi_phy->pwr_info.phy_pwr,
+ false);
+ if (rc) {
+ pr_err("failed to enable digital regulator\n");
+ goto error;
+ }
+ rc = dsi_pwr_enable_regulator(&dsi_phy->pwr_info.digital,
+ false);
+ if (rc) {
+ pr_err("failed to enable phy power\n");
+ goto error;
+ }
+ }
+
+ dsi_phy->power_state = enable;
+error:
+ mutex_unlock(&dsi_phy->phy_lock);
+ return rc;
+}
+
+/**
+ * dsi_phy_enable() - enable DSI PHY hardware
+ * @dsi_phy: DSI PHY handle.
+ * @config: DSI host configuration.
+ * @pll_source: Source PLL for PHY clock.
+ * @skip_validation: Validation will not be performed on parameters.
+ *
+ * Validates and enables DSI PHY.
+ *
+ * Return: error code.
+ */
+int dsi_phy_enable(struct msm_dsi_phy *phy,
+ struct dsi_host_config *config,
+ enum dsi_phy_pll_source pll_source,
+ bool skip_validation)
+{
+ int rc = 0;
+
+ if (!phy || !config) {
+ pr_err("Invalid params\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&phy->phy_lock);
+
+ if (!skip_validation)
+ pr_debug("[PHY_%d] TODO: perform validation\n", phy->index);
+
+ rc = dsi_clk_enable_core_clks(&phy->clks.core_clks, true);
+ if (rc) {
+ pr_err("failed to enable core clocks, rc=%d\n", rc);
+ goto error;
+ }
+
+ memcpy(&phy->mode, &config->video_timing, sizeof(phy->mode));
+ phy->data_lanes = config->common_config.data_lanes;
+ phy->dst_format = config->common_config.dst_format;
+ phy->lane_map = config->lane_map;
+ phy->cfg.pll_source = pll_source;
+
+ rc = phy->hw.ops.calculate_timing_params(&phy->hw,
+ &phy->mode,
+ &config->common_config,
+ &phy->cfg.timing);
+ if (rc) {
+ pr_err("[%s] failed to set timing, rc=%d\n", phy->name, rc);
+ goto error_disable_clks;
+ }
+
+ dsi_phy_enable_hw(phy);
+
+error_disable_clks:
+ rc = dsi_clk_enable_core_clks(&phy->clks.core_clks, false);
+ if (rc) {
+ pr_err("failed to disable clocks, skip phy disable\n");
+ goto error;
+ }
+error:
+ mutex_unlock(&phy->phy_lock);
+ return rc;
+}
+
+/**
+ * dsi_phy_disable() - disable DSI PHY hardware.
+ * @phy: DSI PHY handle.
+ *
+ * Return: error code.
+ */
+int dsi_phy_disable(struct msm_dsi_phy *phy)
+{
+ int rc = 0;
+
+ if (!phy) {
+ pr_err("Invalid params\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&phy->phy_lock);
+
+ rc = dsi_clk_enable_core_clks(&phy->clks.core_clks, true);
+ if (rc) {
+ pr_err("failed to enable core clocks, rc=%d\n", rc);
+ goto error;
+ }
+
+ dsi_phy_disable_hw(phy);
+
+ rc = dsi_clk_enable_core_clks(&phy->clks.core_clks, false);
+ if (rc) {
+ pr_err("failed to disable core clocks, rc=%d\n", rc);
+ goto error;
+ }
+
+error:
+ mutex_unlock(&phy->phy_lock);
+ return rc;
+}
+
+/**
+ * dsi_phy_set_timing_params() - timing parameters for the panel
+ * @phy: DSI PHY handle
+ * @timing: array holding timing params.
+ * @size: size of the array.
+ *
+ * When PHY timing calculator is not implemented, this array will be used to
+ * pass PHY timing information.
+ *
+ * Return: error code.
+ */
+int dsi_phy_set_timing_params(struct msm_dsi_phy *phy,
+ u8 *timing, u32 size)
+{
+ int rc = 0;
+ int i, j;
+ struct dsi_phy_per_lane_cfgs *timing_cfg;
+
+ if (!phy || !timing || !size) {
+ pr_err("Invalid params\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&phy->phy_lock);
+
+ if (size != (DSI_LANE_MAX * phy->cfg.timing.count_per_lane)) {
+ pr_err("Unexpected timing array size %d\n", size);
+ rc = -EINVAL;
+ } else {
+ timing_cfg = &phy->cfg.timing;
+ for (i = DSI_LOGICAL_LANE_0; i < DSI_LANE_MAX; i++) {
+ for (j = 0; j < timing_cfg->count_per_lane; j++) {
+ timing_cfg->lane[i][j] = *timing;
+ timing++;
+ }
+ }
+ }
+ mutex_unlock(&phy->phy_lock);
+ return rc;
+}
+
+void __init dsi_phy_drv_register(void)
+{
+ platform_driver_register(&dsi_phy_platform_driver);
+}
+
+void __exit dsi_phy_drv_unregister(void)
+{
+ platform_driver_unregister(&dsi_phy_platform_driver);
+}
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_phy.h b/drivers/gpu/drm/msm/dsi-staging/dsi_phy.h
new file mode 100644
index 0000000..6c31bfa
--- /dev/null
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_phy.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DSI_PHY_H_
+#define _DSI_PHY_H_
+
+#include "dsi_defs.h"
+#include "dsi_clk_pwr.h"
+#include "dsi_phy_hw.h"
+
+struct dsi_ver_spec_info {
+ enum dsi_phy_version version;
+ u32 lane_cfg_count;
+ u32 strength_cfg_count;
+ u32 regulator_cfg_count;
+ u32 timing_cfg_count;
+};
+
+/**
+ * struct dsi_phy_clk_info - clock information for DSI controller
+ * @core_clks: Core clocks needed to access PHY registers.
+ */
+struct dsi_phy_clk_info {
+ struct dsi_core_clk_info core_clks;
+};
+
+/**
+ * struct dsi_phy_power_info - digital and analog power supplies for DSI PHY
+ * @digital: Digital power supply for DSI PHY.
+ * @phy_pwr: Analog power supplies for DSI PHY to work.
+ */
+struct dsi_phy_power_info {
+ struct dsi_regulator_info digital;
+ struct dsi_regulator_info phy_pwr;
+};
+
+/**
+ * struct msm_dsi_phy - DSI PHY object
+ * @pdev: Pointer to platform device.
+ * @index: Instance id.
+ * @name: Name of the PHY instance.
+ * @refcount: Reference count.
+ * @phy_lock: Mutex for hardware and object access.
+ * @ver_info: Version specific phy parameters.
+ * @hw: DSI PHY hardware object.
+ * @cfg: DSI phy configuration.
+ * @power_state: True if PHY is powered on.
+ * @mode: Current mode.
+ * @data_lanes: Number of data lanes used.
+ * @dst_format: Destination format.
+ * @lane_map: Map between logical and physical lanes.
+ */
+struct msm_dsi_phy {
+ struct platform_device *pdev;
+ int index;
+ const char *name;
+ u32 refcount;
+ struct mutex phy_lock;
+
+ const struct dsi_ver_spec_info *ver_info;
+ struct dsi_phy_hw hw;
+
+ struct dsi_phy_clk_info clks;
+ struct dsi_phy_power_info pwr_info;
+
+ struct dsi_phy_cfg cfg;
+
+ bool power_state;
+ struct dsi_mode_info mode;
+ enum dsi_data_lanes data_lanes;
+ enum dsi_pixel_format dst_format;
+ struct dsi_lane_mapping lane_map;
+};
+
+/**
+ * dsi_phy_get() - get a dsi phy handle from device node
+ * @of_node: device node for dsi phy controller
+ *
+ * Gets the DSI PHY handle for the corresponding of_node. The ref count is
+ * incremented to one all subsequents get will fail until the original client
+ * calls a put.
+ *
+ * Return: DSI PHY handle or an error code.
+ */
+struct msm_dsi_phy *dsi_phy_get(struct device_node *of_node);
+
+/**
+ * dsi_phy_put() - release dsi phy handle
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Release the DSI PHY hardware. Driver will clean up all resources and puts
+ * back the DSI PHY into reset state.
+ */
+void dsi_phy_put(struct msm_dsi_phy *dsi_phy);
+
+/**
+ * dsi_phy_drv_init() - initialize dsi phy driver
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Initializes DSI PHY driver. Should be called after dsi_phy_get().
+ *
+ * Return: error code.
+ */
+int dsi_phy_drv_init(struct msm_dsi_phy *dsi_phy);
+
+/**
+ * dsi_phy_drv_deinit() - de-initialize dsi phy driver
+ * @dsi_phy: DSI PHY handle.
+ *
+ * Release all resources acquired by dsi_phy_drv_init().
+ *
+ * Return: error code.
+ */
+int dsi_phy_drv_deinit(struct msm_dsi_phy *dsi_phy);
+
+/**
+ * dsi_phy_validate_mode() - validate a display mode
+ * @dsi_phy: DSI PHY handle.
+ * @mode: Mode information.
+ *
+ * Validation will fail if the mode cannot be supported by the PHY driver or
+ * hardware.
+ *
+ * Return: error code.
+ */
+int dsi_phy_validate_mode(struct msm_dsi_phy *dsi_phy,
+ struct dsi_mode_info *mode);
+
+/**
+ * dsi_phy_set_power_state() - enable/disable dsi phy power supplies
+ * @dsi_phy: DSI PHY handle.
+ * @enable: Boolean flag to enable/disable.
+ *
+ * Return: error code.
+ */
+int dsi_phy_set_power_state(struct msm_dsi_phy *dsi_phy, bool enable);
+
+/**
+ * dsi_phy_enable() - enable DSI PHY hardware
+ * @dsi_phy: DSI PHY handle.
+ * @config: DSI host configuration.
+ * @pll_source: Source PLL for PHY clock.
+ * @skip_validation: Validation will not be performed on parameters.
+ *
+ * Validates and enables DSI PHY.
+ *
+ * Return: error code.
+ */
+int dsi_phy_enable(struct msm_dsi_phy *dsi_phy,
+ struct dsi_host_config *config,
+ enum dsi_phy_pll_source pll_source,
+ bool skip_validation);
+
+/**
+ * dsi_phy_disable() - disable DSI PHY hardware.
+ * @phy: DSI PHY handle.
+ *
+ * Return: error code.
+ */
+int dsi_phy_disable(struct msm_dsi_phy *phy);
+
+/**
+ * dsi_phy_set_timing_params() - timing parameters for the panel
+ * @phy: DSI PHY handle
+ * @timing: array holding timing params.
+ * @size: size of the array.
+ *
+ * When PHY timing calculator is not implemented, this array will be used to
+ * pass PHY timing information.
+ *
+ * Return: error code.
+ */
+int dsi_phy_set_timing_params(struct msm_dsi_phy *phy,
+ u8 *timing, u32 size);
+
+/**
+ * dsi_phy_drv_register() - register platform driver for dsi phy
+ */
+void dsi_phy_drv_register(void);
+
+/**
+ * dsi_phy_drv_unregister() - unregister platform driver
+ */
+void dsi_phy_drv_unregister(void);
+
+#endif /* _DSI_PHY_H_ */