xref: /linux/drivers/phy/cadence/cdns-dphy.c (revision 6fdcba32711044c35c0e1b094cbd8f3f0b4472c9)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright: 2017-2018 Cadence Design Systems, Inc.
4  */
5 
6 #include <linux/bitops.h>
7 #include <linux/clk.h>
8 #include <linux/io.h>
9 #include <linux/module.h>
10 #include <linux/of_address.h>
11 #include <linux/of_device.h>
12 #include <linux/platform_device.h>
13 #include <linux/reset.h>
14 
15 #include <linux/phy/phy.h>
16 #include <linux/phy/phy-mipi-dphy.h>
17 
18 #define REG_WAKEUP_TIME_NS		800
19 #define DPHY_PLL_RATE_HZ		108000000
20 
21 /* DPHY registers */
22 #define DPHY_PMA_CMN(reg)		(reg)
23 #define DPHY_PMA_LCLK(reg)		(0x100 + (reg))
24 #define DPHY_PMA_LDATA(lane, reg)	(0x200 + ((lane) * 0x100) + (reg))
25 #define DPHY_PMA_RCLK(reg)		(0x600 + (reg))
26 #define DPHY_PMA_RDATA(lane, reg)	(0x700 + ((lane) * 0x100) + (reg))
27 #define DPHY_PCS(reg)			(0xb00 + (reg))
28 
29 #define DPHY_CMN_SSM			DPHY_PMA_CMN(0x20)
30 #define DPHY_CMN_SSM_EN			BIT(0)
31 #define DPHY_CMN_TX_MODE_EN		BIT(9)
32 
33 #define DPHY_CMN_PWM			DPHY_PMA_CMN(0x40)
34 #define DPHY_CMN_PWM_DIV(x)		((x) << 20)
35 #define DPHY_CMN_PWM_LOW(x)		((x) << 10)
36 #define DPHY_CMN_PWM_HIGH(x)		(x)
37 
38 #define DPHY_CMN_FBDIV			DPHY_PMA_CMN(0x4c)
39 #define DPHY_CMN_FBDIV_VAL(low, high)	(((high) << 11) | ((low) << 22))
40 #define DPHY_CMN_FBDIV_FROM_REG		(BIT(10) | BIT(21))
41 
42 #define DPHY_CMN_OPIPDIV		DPHY_PMA_CMN(0x50)
43 #define DPHY_CMN_IPDIV_FROM_REG		BIT(0)
44 #define DPHY_CMN_IPDIV(x)		((x) << 1)
45 #define DPHY_CMN_OPDIV_FROM_REG		BIT(6)
46 #define DPHY_CMN_OPDIV(x)		((x) << 7)
47 
48 #define DPHY_PSM_CFG			DPHY_PCS(0x4)
49 #define DPHY_PSM_CFG_FROM_REG		BIT(0)
50 #define DPHY_PSM_CLK_DIV(x)		((x) << 1)
51 
52 #define DSI_HBP_FRAME_OVERHEAD		12
53 #define DSI_HSA_FRAME_OVERHEAD		14
54 #define DSI_HFP_FRAME_OVERHEAD		6
55 #define DSI_HSS_VSS_VSE_FRAME_OVERHEAD	4
56 #define DSI_BLANKING_FRAME_OVERHEAD	6
57 #define DSI_NULL_FRAME_OVERHEAD		6
58 #define DSI_EOT_PKT_SIZE		4
59 
60 struct cdns_dphy_cfg {
61 	u8 pll_ipdiv;
62 	u8 pll_opdiv;
63 	u16 pll_fbdiv;
64 	unsigned int nlanes;
65 };
66 
67 enum cdns_dphy_clk_lane_cfg {
68 	DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
69 	DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
70 	DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
71 	DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
72 };
73 
74 struct cdns_dphy;
75 struct cdns_dphy_ops {
76 	int (*probe)(struct cdns_dphy *dphy);
77 	void (*remove)(struct cdns_dphy *dphy);
78 	void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
79 	void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
80 				 enum cdns_dphy_clk_lane_cfg cfg);
81 	void (*set_pll_cfg)(struct cdns_dphy *dphy,
82 			    const struct cdns_dphy_cfg *cfg);
83 	unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
84 };
85 
86 struct cdns_dphy {
87 	struct cdns_dphy_cfg cfg;
88 	void __iomem *regs;
89 	struct clk *psm_clk;
90 	struct clk *pll_ref_clk;
91 	const struct cdns_dphy_ops *ops;
92 	struct phy *phy;
93 };
94 
95 static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
96 				     struct cdns_dphy_cfg *cfg,
97 				     struct phy_configure_opts_mipi_dphy *opts,
98 				     unsigned int *dsi_hfp_ext)
99 {
100 	unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
101 	u64 dlane_bps;
102 
103 	memset(cfg, 0, sizeof(*cfg));
104 
105 	if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
106 		return -EINVAL;
107 	else if (pll_ref_hz < 19200000)
108 		cfg->pll_ipdiv = 1;
109 	else if (pll_ref_hz < 38400000)
110 		cfg->pll_ipdiv = 2;
111 	else if (pll_ref_hz < 76800000)
112 		cfg->pll_ipdiv = 4;
113 	else
114 		cfg->pll_ipdiv = 8;
115 
116 	dlane_bps = opts->hs_clk_rate;
117 
118 	if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
119 		return -EINVAL;
120 	else if (dlane_bps >= 1250000000)
121 		cfg->pll_opdiv = 1;
122 	else if (dlane_bps >= 630000000)
123 		cfg->pll_opdiv = 2;
124 	else if (dlane_bps >= 320000000)
125 		cfg->pll_opdiv = 4;
126 	else if (dlane_bps >= 160000000)
127 		cfg->pll_opdiv = 8;
128 
129 	cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
130 					  cfg->pll_ipdiv,
131 					  pll_ref_hz);
132 
133 	return 0;
134 }
135 
136 static int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
137 {
138 	unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
139 	unsigned long psm_div;
140 
141 	if (!psm_clk_hz || psm_clk_hz > 100000000)
142 		return -EINVAL;
143 
144 	psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
145 	if (dphy->ops->set_psm_div)
146 		dphy->ops->set_psm_div(dphy, psm_div);
147 
148 	return 0;
149 }
150 
151 static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
152 				       enum cdns_dphy_clk_lane_cfg cfg)
153 {
154 	if (dphy->ops->set_clk_lane_cfg)
155 		dphy->ops->set_clk_lane_cfg(dphy, cfg);
156 }
157 
158 static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
159 				  const struct cdns_dphy_cfg *cfg)
160 {
161 	if (dphy->ops->set_pll_cfg)
162 		dphy->ops->set_pll_cfg(dphy, cfg);
163 }
164 
165 static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
166 {
167 	return dphy->ops->get_wakeup_time_ns(dphy);
168 }
169 
170 static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
171 {
172 	/* Default wakeup time is 800 ns (in a simulated environment). */
173 	return 800;
174 }
175 
176 static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
177 				      const struct cdns_dphy_cfg *cfg)
178 {
179 	u32 fbdiv_low, fbdiv_high;
180 
181 	fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
182 	fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
183 
184 	writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
185 	       DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
186 	       DPHY_CMN_OPDIV(cfg->pll_opdiv),
187 	       dphy->regs + DPHY_CMN_OPIPDIV);
188 	writel(DPHY_CMN_FBDIV_FROM_REG |
189 	       DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
190 	       dphy->regs + DPHY_CMN_FBDIV);
191 	writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
192 	       DPHY_CMN_PWM_DIV(0x8),
193 	       dphy->regs + DPHY_CMN_PWM);
194 }
195 
196 static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
197 {
198 	writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
199 	       dphy->regs + DPHY_PSM_CFG);
200 }
201 
202 /*
203  * This is the reference implementation of DPHY hooks. Specific integration of
204  * this IP may have to re-implement some of them depending on how they decided
205  * to wire things in the SoC.
206  */
207 static const struct cdns_dphy_ops ref_dphy_ops = {
208 	.get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
209 	.set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
210 	.set_psm_div = cdns_dphy_ref_set_psm_div,
211 };
212 
213 static int cdns_dphy_config_from_opts(struct phy *phy,
214 				      struct phy_configure_opts_mipi_dphy *opts,
215 				      struct cdns_dphy_cfg *cfg)
216 {
217 	struct cdns_dphy *dphy = phy_get_drvdata(phy);
218 	unsigned int dsi_hfp_ext = 0;
219 	int ret;
220 
221 	ret = phy_mipi_dphy_config_validate(opts);
222 	if (ret)
223 		return ret;
224 
225 	ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
226 					opts, &dsi_hfp_ext);
227 	if (ret)
228 		return ret;
229 
230 	opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
231 
232 	return 0;
233 }
234 
235 static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
236 			      union phy_configure_opts *opts)
237 {
238 	struct cdns_dphy_cfg cfg = { 0 };
239 
240 	if (mode != PHY_MODE_MIPI_DPHY)
241 		return -EINVAL;
242 
243 	return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
244 }
245 
246 static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
247 {
248 	struct cdns_dphy *dphy = phy_get_drvdata(phy);
249 	struct cdns_dphy_cfg cfg = { 0 };
250 	int ret;
251 
252 	ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
253 	if (ret)
254 		return ret;
255 
256 	/*
257 	 * Configure the internal PSM clk divider so that the DPHY has a
258 	 * 1MHz clk (or something close).
259 	 */
260 	ret = cdns_dphy_setup_psm(dphy);
261 	if (ret)
262 		return ret;
263 
264 	/*
265 	 * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
266 	 * and 8 data lanes, each clk lane can be attache different set of
267 	 * data lanes. The 2 groups are named 'left' and 'right', so here we
268 	 * just say that we want the 'left' clk lane to drive the 'left' data
269 	 * lanes.
270 	 */
271 	cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
272 
273 	/*
274 	 * Configure the DPHY PLL that will be used to generate the TX byte
275 	 * clk.
276 	 */
277 	cdns_dphy_set_pll_cfg(dphy, &cfg);
278 
279 	return 0;
280 }
281 
282 static int cdns_dphy_power_on(struct phy *phy)
283 {
284 	struct cdns_dphy *dphy = phy_get_drvdata(phy);
285 
286 	clk_prepare_enable(dphy->psm_clk);
287 	clk_prepare_enable(dphy->pll_ref_clk);
288 
289 	/* Start TX state machine. */
290 	writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
291 	       dphy->regs + DPHY_CMN_SSM);
292 
293 	return 0;
294 }
295 
296 static int cdns_dphy_power_off(struct phy *phy)
297 {
298 	struct cdns_dphy *dphy = phy_get_drvdata(phy);
299 
300 	clk_disable_unprepare(dphy->pll_ref_clk);
301 	clk_disable_unprepare(dphy->psm_clk);
302 
303 	return 0;
304 }
305 
306 static const struct phy_ops cdns_dphy_ops = {
307 	.configure	= cdns_dphy_configure,
308 	.validate	= cdns_dphy_validate,
309 	.power_on	= cdns_dphy_power_on,
310 	.power_off	= cdns_dphy_power_off,
311 };
312 
313 static int cdns_dphy_probe(struct platform_device *pdev)
314 {
315 	struct phy_provider *phy_provider;
316 	struct cdns_dphy *dphy;
317 	struct resource *res;
318 	int ret;
319 
320 	dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
321 	if (!dphy)
322 		return -ENOMEM;
323 	dev_set_drvdata(&pdev->dev, dphy);
324 
325 	dphy->ops = of_device_get_match_data(&pdev->dev);
326 	if (!dphy->ops)
327 		return -EINVAL;
328 
329 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
330 	dphy->regs = devm_ioremap_resource(&pdev->dev, res);
331 	if (IS_ERR(dphy->regs))
332 		return PTR_ERR(dphy->regs);
333 
334 	dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
335 	if (IS_ERR(dphy->psm_clk))
336 		return PTR_ERR(dphy->psm_clk);
337 
338 	dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
339 	if (IS_ERR(dphy->pll_ref_clk))
340 		return PTR_ERR(dphy->pll_ref_clk);
341 
342 	if (dphy->ops->probe) {
343 		ret = dphy->ops->probe(dphy);
344 		if (ret)
345 			return ret;
346 	}
347 
348 	dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
349 	if (IS_ERR(dphy->phy)) {
350 		dev_err(&pdev->dev, "failed to create PHY\n");
351 		if (dphy->ops->remove)
352 			dphy->ops->remove(dphy);
353 		return PTR_ERR(dphy->phy);
354 	}
355 
356 	phy_set_drvdata(dphy->phy, dphy);
357 	phy_provider = devm_of_phy_provider_register(&pdev->dev,
358 						     of_phy_simple_xlate);
359 
360 	return PTR_ERR_OR_ZERO(phy_provider);
361 }
362 
363 static int cdns_dphy_remove(struct platform_device *pdev)
364 {
365 	struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
366 
367 	if (dphy->ops->remove)
368 		dphy->ops->remove(dphy);
369 
370 	return 0;
371 }
372 
373 static const struct of_device_id cdns_dphy_of_match[] = {
374 	{ .compatible = "cdns,dphy", .data = &ref_dphy_ops },
375 	{ /* sentinel */ },
376 };
377 MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
378 
379 static struct platform_driver cdns_dphy_platform_driver = {
380 	.probe		= cdns_dphy_probe,
381 	.remove		= cdns_dphy_remove,
382 	.driver		= {
383 		.name		= "cdns-mipi-dphy",
384 		.of_match_table	= cdns_dphy_of_match,
385 	},
386 };
387 module_platform_driver(cdns_dphy_platform_driver);
388 
389 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
390 MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
391 MODULE_LICENSE("GPL");
392