1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
4 */
5
6#include <linux/of.h>
7#include <linux/platform_device.h>
8
9#include "hdmi.h"
10
11static int msm_hdmi_phy_resource_init(struct hdmi_phy *phy)
12{
13	struct hdmi_phy_cfg *cfg = phy->cfg;
14	struct device *dev = &phy->pdev->dev;
15	int i, ret;
16
17	phy->regs = devm_kcalloc(dev, cfg->num_regs, sizeof(phy->regs[0]),
18				 GFP_KERNEL);
19	if (!phy->regs)
20		return -ENOMEM;
21
22	phy->clks = devm_kcalloc(dev, cfg->num_clks, sizeof(phy->clks[0]),
23				 GFP_KERNEL);
24	if (!phy->clks)
25		return -ENOMEM;
26
27	for (i = 0; i < cfg->num_regs; i++)
28		phy->regs[i].supply = cfg->reg_names[i];
29
30	ret = devm_regulator_bulk_get(dev, cfg->num_regs, phy->regs);
31	if (ret) {
32		if (ret != -EPROBE_DEFER)
33			DRM_DEV_ERROR(dev, "failed to get phy regulators: %d\n", ret);
34
35		return ret;
36	}
37
38	for (i = 0; i < cfg->num_clks; i++) {
39		struct clk *clk;
40
41		clk = msm_clk_get(phy->pdev, cfg->clk_names[i]);
42		if (IS_ERR(clk)) {
43			ret = PTR_ERR(clk);
44			DRM_DEV_ERROR(dev, "failed to get phy clock: %s (%d)\n",
45				cfg->clk_names[i], ret);
46			return ret;
47		}
48
49		phy->clks[i] = clk;
50	}
51
52	return 0;
53}
54
55int msm_hdmi_phy_resource_enable(struct hdmi_phy *phy)
56{
57	struct hdmi_phy_cfg *cfg = phy->cfg;
58	struct device *dev = &phy->pdev->dev;
59	int i, ret = 0;
60
61	pm_runtime_get_sync(dev);
62
63	ret = regulator_bulk_enable(cfg->num_regs, phy->regs);
64	if (ret) {
65		DRM_DEV_ERROR(dev, "failed to enable regulators: (%d)\n", ret);
66		return ret;
67	}
68
69	for (i = 0; i < cfg->num_clks; i++) {
70		ret = clk_prepare_enable(phy->clks[i]);
71		if (ret)
72			DRM_DEV_ERROR(dev, "failed to enable clock: %s (%d)\n",
73				cfg->clk_names[i], ret);
74	}
75
76	return ret;
77}
78
79void msm_hdmi_phy_resource_disable(struct hdmi_phy *phy)
80{
81	struct hdmi_phy_cfg *cfg = phy->cfg;
82	struct device *dev = &phy->pdev->dev;
83	int i;
84
85	for (i = cfg->num_clks - 1; i >= 0; i--)
86		clk_disable_unprepare(phy->clks[i]);
87
88	regulator_bulk_disable(cfg->num_regs, phy->regs);
89
90	pm_runtime_put_sync(dev);
91}
92
93void msm_hdmi_phy_powerup(struct hdmi_phy *phy, unsigned long int pixclock)
94{
95	if (!phy || !phy->cfg->powerup)
96		return;
97
98	phy->cfg->powerup(phy, pixclock);
99}
100
101void msm_hdmi_phy_powerdown(struct hdmi_phy *phy)
102{
103	if (!phy || !phy->cfg->powerdown)
104		return;
105
106	phy->cfg->powerdown(phy);
107}
108
109static int msm_hdmi_phy_pll_init(struct platform_device *pdev,
110			     enum hdmi_phy_type type)
111{
112	int ret;
113
114	switch (type) {
115	case MSM_HDMI_PHY_8960:
116		ret = msm_hdmi_pll_8960_init(pdev);
117		break;
118	case MSM_HDMI_PHY_8996:
119		ret = msm_hdmi_pll_8996_init(pdev);
120		break;
121	/*
122	 * we don't have PLL support for these, don't report an error for now
123	 */
124	case MSM_HDMI_PHY_8x60:
125	case MSM_HDMI_PHY_8x74:
126	default:
127		ret = 0;
128		break;
129	}
130
131	return ret;
132}
133
134static int msm_hdmi_phy_probe(struct platform_device *pdev)
135{
136	struct device *dev = &pdev->dev;
137	struct hdmi_phy *phy;
138	int ret;
139
140	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
141	if (!phy)
142		return -ENODEV;
143
144	phy->cfg = (struct hdmi_phy_cfg *)of_device_get_match_data(dev);
145	if (!phy->cfg)
146		return -ENODEV;
147
148	phy->mmio = msm_ioremap(pdev, "hdmi_phy");
149	if (IS_ERR(phy->mmio)) {
150		DRM_DEV_ERROR(dev, "%s: failed to map phy base\n", __func__);
151		return -ENOMEM;
152	}
153
154	phy->pdev = pdev;
155
156	ret = msm_hdmi_phy_resource_init(phy);
157	if (ret)
158		return ret;
159
160	pm_runtime_enable(&pdev->dev);
161
162	ret = msm_hdmi_phy_resource_enable(phy);
163	if (ret)
164		return ret;
165
166	ret = msm_hdmi_phy_pll_init(pdev, phy->cfg->type);
167	if (ret) {
168		DRM_DEV_ERROR(dev, "couldn't init PLL\n");
169		msm_hdmi_phy_resource_disable(phy);
170		return ret;
171	}
172
173	msm_hdmi_phy_resource_disable(phy);
174
175	platform_set_drvdata(pdev, phy);
176
177	return 0;
178}
179
180static void msm_hdmi_phy_remove(struct platform_device *pdev)
181{
182	pm_runtime_disable(&pdev->dev);
183}
184
185static const struct of_device_id msm_hdmi_phy_dt_match[] = {
186	{ .compatible = "qcom,hdmi-phy-8660",
187	  .data = &msm_hdmi_phy_8x60_cfg },
188	{ .compatible = "qcom,hdmi-phy-8960",
189	  .data = &msm_hdmi_phy_8960_cfg },
190	{ .compatible = "qcom,hdmi-phy-8974",
191	  .data = &msm_hdmi_phy_8x74_cfg },
192	{ .compatible = "qcom,hdmi-phy-8084",
193	  .data = &msm_hdmi_phy_8x74_cfg },
194	{ .compatible = "qcom,hdmi-phy-8996",
195	  .data = &msm_hdmi_phy_8996_cfg },
196	{}
197};
198
199static struct platform_driver msm_hdmi_phy_platform_driver = {
200	.probe      = msm_hdmi_phy_probe,
201	.remove_new = msm_hdmi_phy_remove,
202	.driver     = {
203		.name   = "msm_hdmi_phy",
204		.of_match_table = msm_hdmi_phy_dt_match,
205	},
206};
207
208void __init msm_hdmi_phy_driver_register(void)
209{
210	platform_driver_register(&msm_hdmi_phy_platform_driver);
211}
212
213void __exit msm_hdmi_phy_driver_unregister(void)
214{
215	platform_driver_unregister(&msm_hdmi_phy_platform_driver);
216}
217