diff options
Diffstat (limited to 'drivers/gpu/drm/msm/dsi/dsi_phy.c')
| -rw-r--r-- | drivers/gpu/drm/msm/dsi/dsi_phy.c | 315 | 
1 files changed, 285 insertions, 30 deletions
| diff --git a/drivers/gpu/drm/msm/dsi/dsi_phy.c b/drivers/gpu/drm/msm/dsi/dsi_phy.c index f0cea8927388..2d3b33ce1cc5 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_phy.c +++ b/drivers/gpu/drm/msm/dsi/dsi_phy.c @@ -11,12 +11,27 @@   * GNU General Public License for more details.   */ +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +  #include "dsi.h"  #include "dsi.xml.h"  #define dsi_phy_read(offset) msm_readl((offset))  #define dsi_phy_write(offset, data) msm_writel((data), (offset)) +struct dsi_phy_ops { +	int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, +		const unsigned long bit_rate, const unsigned long esc_rate); +	int (*disable)(struct msm_dsi_phy *phy); +}; + +struct dsi_phy_cfg { +	enum msm_dsi_phy_type type; +	struct dsi_reg_config reg_cfg; +	struct dsi_phy_ops ops; +}; +  struct dsi_dphy_timing {  	u32 clk_pre;  	u32 clk_post; @@ -34,15 +49,106 @@ struct dsi_dphy_timing {  };  struct msm_dsi_phy { +	struct platform_device *pdev;  	void __iomem *base;  	void __iomem *reg_base;  	int id; + +	struct clk *ahb_clk; +	struct regulator_bulk_data supplies[DSI_DEV_REGULATOR_MAX]; +  	struct dsi_dphy_timing timing; -	int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, -		const unsigned long bit_rate, const unsigned long esc_rate); -	int (*disable)(struct msm_dsi_phy *phy); +	const struct dsi_phy_cfg *cfg; + +	struct msm_dsi_pll *pll;  }; +static int dsi_phy_regulator_init(struct msm_dsi_phy *phy) +{ +	struct regulator_bulk_data *s = phy->supplies; +	const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; +	struct device *dev = &phy->pdev->dev; +	int num = phy->cfg->reg_cfg.num; +	int i, ret; + +	for (i = 0; i < num; i++) +		s[i].supply = regs[i].name; + +	ret = devm_regulator_bulk_get(&phy->pdev->dev, num, s); +	if (ret < 0) { +		dev_err(dev, "%s: failed to init regulator, ret=%d\n", +						__func__, ret); +		return ret; +	} + +	for (i = 0; i < num; i++) { +		if ((regs[i].min_voltage >= 0) && (regs[i].max_voltage >= 0)) { +			ret = regulator_set_voltage(s[i].consumer, +				regs[i].min_voltage, regs[i].max_voltage); +			if (ret < 0) { +				dev_err(dev, +					"regulator %d set voltage failed, %d\n", +					i, ret); +				return ret; +			} +		} +	} + +	return 0; +} + +static void dsi_phy_regulator_disable(struct msm_dsi_phy *phy) +{ +	struct regulator_bulk_data *s = phy->supplies; +	const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; +	int num = phy->cfg->reg_cfg.num; +	int i; + +	DBG(""); +	for (i = num - 1; i >= 0; i--) +		if (regs[i].disable_load >= 0) +			regulator_set_load(s[i].consumer, +						regs[i].disable_load); + +	regulator_bulk_disable(num, s); +} + +static int dsi_phy_regulator_enable(struct msm_dsi_phy *phy) +{ +	struct regulator_bulk_data *s = phy->supplies; +	const struct dsi_reg_entry *regs = phy->cfg->reg_cfg.regs; +	struct device *dev = &phy->pdev->dev; +	int num = phy->cfg->reg_cfg.num; +	int ret, i; + +	DBG(""); +	for (i = 0; i < num; i++) { +		if (regs[i].enable_load >= 0) { +			ret = regulator_set_load(s[i].consumer, +							regs[i].enable_load); +			if (ret < 0) { +				dev_err(dev, +					"regulator %d set op mode failed, %d\n", +					i, ret); +				goto fail; +			} +		} +	} + +	ret = regulator_bulk_enable(num, s); +	if (ret < 0) { +		dev_err(dev, "regulator enable failed, %d\n", ret); +		goto fail; +	} + +	return 0; + +fail: +	for (i--; i >= 0; i--) +		regulator_set_load(s[i].consumer, regs[i].disable_load); +	return ret; +} +  #define S_DIV_ROUND_UP(n, d)	\  	(((n) >= 0) ? (((n) + (d) - 1) / (d)) : (((n) - (d) + 1) / (d))) @@ -284,59 +390,200 @@ static int dsi_28nm_phy_disable(struct msm_dsi_phy *phy)  	return 0;  } -#define dsi_phy_func_init(name)	\ -	do {	\ -		phy->enable = dsi_##name##_phy_enable;	\ -		phy->disable = dsi_##name##_phy_disable;	\ -	} while (0) +static int dsi_phy_enable_resource(struct msm_dsi_phy *phy) +{ +	int ret; + +	pm_runtime_get_sync(&phy->pdev->dev); -struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev, -			enum msm_dsi_phy_type type, int id) +	ret = clk_prepare_enable(phy->ahb_clk); +	if (ret) { +		pr_err("%s: can't enable ahb clk, %d\n", __func__, ret); +		pm_runtime_put_sync(&phy->pdev->dev); +	} + +	return ret; +} + +static void dsi_phy_disable_resource(struct msm_dsi_phy *phy) +{ +	clk_disable_unprepare(phy->ahb_clk); +	pm_runtime_put_sync(&phy->pdev->dev); +} + +static const struct dsi_phy_cfg dsi_phy_cfgs[MSM_DSI_PHY_MAX] = { +	[MSM_DSI_PHY_28NM_HPM] = { +		.type = MSM_DSI_PHY_28NM_HPM, +		.reg_cfg = { +			.num = 1, +			.regs = { +				{"vddio", 1800000, 1800000, 100000, 100}, +			}, +		}, +		.ops = { +			.enable = dsi_28nm_phy_enable, +			.disable = dsi_28nm_phy_disable, +		} +	}, +	[MSM_DSI_PHY_28NM_LP] = { +		.type = MSM_DSI_PHY_28NM_LP, +		.reg_cfg = { +			.num = 1, +			.regs = { +				{"vddio", 1800000, 1800000, 100000, 100}, +			}, +		}, +		.ops = { +			.enable = dsi_28nm_phy_enable, +			.disable = dsi_28nm_phy_disable, +		} +	}, +}; + +static const struct of_device_id dsi_phy_dt_match[] = { +	{ .compatible = "qcom,dsi-phy-28nm-hpm", +	  .data = &dsi_phy_cfgs[MSM_DSI_PHY_28NM_HPM],}, +	{ .compatible = "qcom,dsi-phy-28nm-lp", +	  .data = &dsi_phy_cfgs[MSM_DSI_PHY_28NM_LP],}, +	{} +}; + +static int dsi_phy_driver_probe(struct platform_device *pdev)  {  	struct msm_dsi_phy *phy; +	const struct of_device_id *match; +	int ret;  	phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);  	if (!phy) -		return NULL; +		return -ENOMEM; + +	match = of_match_node(dsi_phy_dt_match, pdev->dev.of_node); +	if (!match) +		return -ENODEV; + +	phy->cfg = match->data; +	phy->pdev = pdev; + +	ret = of_property_read_u32(pdev->dev.of_node, +				"qcom,dsi-phy-index", &phy->id); +	if (ret) { +		dev_err(&pdev->dev, +			"%s: PHY index not specified, ret=%d\n", +			__func__, ret); +		goto fail; +	}  	phy->base = msm_ioremap(pdev, "dsi_phy", "DSI_PHY"); -	if (IS_ERR_OR_NULL(phy->base)) { -		pr_err("%s: failed to map phy base\n", __func__); -		return NULL; +	if (IS_ERR(phy->base)) { +		dev_err(&pdev->dev, "%s: failed to map phy base\n", __func__); +		ret = -ENOMEM; +		goto fail;  	}  	phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator", "DSI_PHY_REG"); -	if (IS_ERR_OR_NULL(phy->reg_base)) { -		pr_err("%s: failed to map phy regulator base\n", __func__); -		return NULL; +	if (IS_ERR(phy->reg_base)) { +		dev_err(&pdev->dev, +			"%s: failed to map phy regulator base\n", __func__); +		ret = -ENOMEM; +		goto fail;  	} -	switch (type) { -	case MSM_DSI_PHY_28NM: -		dsi_phy_func_init(28nm); -		break; -	default: -		pr_err("%s: unsupported type, %d\n", __func__, type); -		return NULL; +	ret = dsi_phy_regulator_init(phy); +	if (ret) { +		dev_err(&pdev->dev, "%s: failed to init regulator\n", __func__); +		goto fail; +	} + +	phy->ahb_clk = devm_clk_get(&pdev->dev, "iface_clk"); +	if (IS_ERR(phy->ahb_clk)) { +		pr_err("%s: Unable to get ahb clk\n", __func__); +		ret = PTR_ERR(phy->ahb_clk); +		goto fail;  	} -	phy->id = id; +	/* PLL init will call into clk_register which requires +	 * register access, so we need to enable power and ahb clock. +	 */ +	ret = dsi_phy_enable_resource(phy); +	if (ret) +		goto fail; + +	phy->pll = msm_dsi_pll_init(pdev, phy->cfg->type, phy->id); +	if (!phy->pll) +		dev_info(&pdev->dev, +			"%s: pll init failed, need separate pll clk driver\n", +			__func__); + +	dsi_phy_disable_resource(phy); + +	platform_set_drvdata(pdev, phy); + +	return 0; -	return phy; +fail: +	return ret; +} + +static int dsi_phy_driver_remove(struct platform_device *pdev) +{ +	struct msm_dsi_phy *phy = platform_get_drvdata(pdev); + +	if (phy && phy->pll) { +		msm_dsi_pll_destroy(phy->pll); +		phy->pll = NULL; +	} + +	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 = dsi_phy_dt_match, +	}, +}; + +void __init msm_dsi_phy_driver_register(void) +{ +	platform_driver_register(&dsi_phy_platform_driver); +} + +void __exit msm_dsi_phy_driver_unregister(void) +{ +	platform_driver_unregister(&dsi_phy_platform_driver);  }  int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel,  	const unsigned long bit_rate, const unsigned long esc_rate)  { -	if (!phy || !phy->enable) +	int ret; + +	if (!phy || !phy->cfg->ops.enable)  		return -EINVAL; -	return phy->enable(phy, is_dual_panel, bit_rate, esc_rate); + +	ret = dsi_phy_regulator_enable(phy); +	if (ret) { +		dev_err(&phy->pdev->dev, "%s: regulator enable failed, %d\n", +			__func__, ret); +		return ret; +	} + +	return phy->cfg->ops.enable(phy, is_dual_panel, bit_rate, esc_rate);  }  int msm_dsi_phy_disable(struct msm_dsi_phy *phy)  { -	if (!phy || !phy->disable) +	if (!phy || !phy->cfg->ops.disable)  		return -EINVAL; -	return phy->disable(phy); + +	phy->cfg->ops.disable(phy); +	dsi_phy_regulator_disable(phy); + +	return 0;  }  void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy, @@ -350,3 +597,11 @@ void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy,  		*clk_post = phy->timing.clk_post;  } +struct msm_dsi_pll *msm_dsi_phy_get_pll(struct msm_dsi_phy *phy) +{ +	if (!phy) +		return NULL; + +	return phy->pll; +} + | 
