1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright: 2017-2018 Cadence Design Systems, Inc. |
4 | */ |
5 | |
6 | #include <linux/bitfield.h> |
7 | #include <linux/bitops.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/io.h> |
10 | #include <linux/iopoll.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/reset.h> |
15 | |
16 | #include <linux/phy/phy.h> |
17 | #include <linux/phy/phy-mipi-dphy.h> |
18 | |
19 | #define REG_WAKEUP_TIME_NS 800 |
20 | #define DPHY_PLL_RATE_HZ 108000000 |
21 | #define POLL_TIMEOUT_US 1000 |
22 | |
23 | /* DPHY registers */ |
24 | #define DPHY_PMA_CMN(reg) (reg) |
25 | #define DPHY_PMA_LCLK(reg) (0x100 + (reg)) |
26 | #define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) |
27 | #define DPHY_PMA_RCLK(reg) (0x600 + (reg)) |
28 | #define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) |
29 | #define DPHY_PCS(reg) (0xb00 + (reg)) |
30 | |
31 | #define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) |
32 | #define DPHY_CMN_SSM_EN BIT(0) |
33 | #define DPHY_CMN_TX_MODE_EN BIT(9) |
34 | |
35 | #define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) |
36 | #define DPHY_CMN_PWM_DIV(x) ((x) << 20) |
37 | #define DPHY_CMN_PWM_LOW(x) ((x) << 10) |
38 | #define DPHY_CMN_PWM_HIGH(x) (x) |
39 | |
40 | #define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) |
41 | #define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) |
42 | #define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) |
43 | |
44 | #define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) |
45 | #define DPHY_CMN_IPDIV_FROM_REG BIT(0) |
46 | #define DPHY_CMN_IPDIV(x) ((x) << 1) |
47 | #define DPHY_CMN_OPDIV_FROM_REG BIT(6) |
48 | #define DPHY_CMN_OPDIV(x) ((x) << 7) |
49 | |
50 | #define DPHY_BAND_CFG DPHY_PCS(0x0) |
51 | #define DPHY_BAND_CFG_LEFT_BAND GENMASK(4, 0) |
52 | #define DPHY_BAND_CFG_RIGHT_BAND GENMASK(9, 5) |
53 | |
54 | #define DPHY_PSM_CFG DPHY_PCS(0x4) |
55 | #define DPHY_PSM_CFG_FROM_REG BIT(0) |
56 | #define DPHY_PSM_CLK_DIV(x) ((x) << 1) |
57 | |
58 | #define DSI_HBP_FRAME_OVERHEAD 12 |
59 | #define DSI_HSA_FRAME_OVERHEAD 14 |
60 | #define DSI_HFP_FRAME_OVERHEAD 6 |
61 | #define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 |
62 | #define DSI_BLANKING_FRAME_OVERHEAD 6 |
63 | #define DSI_NULL_FRAME_OVERHEAD 6 |
64 | #define DSI_EOT_PKT_SIZE 4 |
65 | |
66 | #define DPHY_TX_J721E_WIZ_PLL_CTRL 0xF04 |
67 | #define DPHY_TX_J721E_WIZ_STATUS 0xF08 |
68 | #define DPHY_TX_J721E_WIZ_RST_CTRL 0xF0C |
69 | #define DPHY_TX_J721E_WIZ_PSM_FREQ 0xF10 |
70 | |
71 | #define DPHY_TX_J721E_WIZ_IPDIV GENMASK(4, 0) |
72 | #define DPHY_TX_J721E_WIZ_OPDIV GENMASK(13, 8) |
73 | #define DPHY_TX_J721E_WIZ_FBDIV GENMASK(25, 16) |
74 | #define DPHY_TX_J721E_WIZ_LANE_RSTB BIT(31) |
75 | #define DPHY_TX_WIZ_PLL_LOCK BIT(31) |
76 | #define DPHY_TX_WIZ_O_CMN_READY BIT(31) |
77 | |
78 | struct cdns_dphy_cfg { |
79 | u8 pll_ipdiv; |
80 | u8 pll_opdiv; |
81 | u16 pll_fbdiv; |
82 | unsigned int nlanes; |
83 | }; |
84 | |
85 | enum cdns_dphy_clk_lane_cfg { |
86 | DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, |
87 | DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, |
88 | DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, |
89 | DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, |
90 | }; |
91 | |
92 | struct cdns_dphy; |
93 | struct cdns_dphy_ops { |
94 | int (*probe)(struct cdns_dphy *dphy); |
95 | void (*remove)(struct cdns_dphy *dphy); |
96 | void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); |
97 | void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, |
98 | enum cdns_dphy_clk_lane_cfg cfg); |
99 | void (*set_pll_cfg)(struct cdns_dphy *dphy, |
100 | const struct cdns_dphy_cfg *cfg); |
101 | unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); |
102 | }; |
103 | |
104 | struct cdns_dphy { |
105 | struct cdns_dphy_cfg cfg; |
106 | void __iomem *regs; |
107 | struct clk *psm_clk; |
108 | struct clk *pll_ref_clk; |
109 | const struct cdns_dphy_ops *ops; |
110 | struct phy *phy; |
111 | }; |
112 | |
113 | /* Order of bands is important since the index is the band number. */ |
114 | static const unsigned int tx_bands[] = { |
115 | 80, 100, 120, 160, 200, 240, 320, 390, 450, 510, 560, 640, 690, 770, |
116 | 870, 950, 1000, 1200, 1400, 1600, 1800, 2000, 2200, 2500 |
117 | }; |
118 | |
119 | static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, |
120 | struct cdns_dphy_cfg *cfg, |
121 | struct phy_configure_opts_mipi_dphy *opts, |
122 | unsigned int *dsi_hfp_ext) |
123 | { |
124 | unsigned long pll_ref_hz = clk_get_rate(clk: dphy->pll_ref_clk); |
125 | u64 dlane_bps; |
126 | |
127 | memset(cfg, 0, sizeof(*cfg)); |
128 | |
129 | if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) |
130 | return -EINVAL; |
131 | else if (pll_ref_hz < 19200000) |
132 | cfg->pll_ipdiv = 1; |
133 | else if (pll_ref_hz < 38400000) |
134 | cfg->pll_ipdiv = 2; |
135 | else if (pll_ref_hz < 76800000) |
136 | cfg->pll_ipdiv = 4; |
137 | else |
138 | cfg->pll_ipdiv = 8; |
139 | |
140 | dlane_bps = opts->hs_clk_rate; |
141 | |
142 | if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) |
143 | return -EINVAL; |
144 | else if (dlane_bps >= 1250000000) |
145 | cfg->pll_opdiv = 1; |
146 | else if (dlane_bps >= 630000000) |
147 | cfg->pll_opdiv = 2; |
148 | else if (dlane_bps >= 320000000) |
149 | cfg->pll_opdiv = 4; |
150 | else if (dlane_bps >= 160000000) |
151 | cfg->pll_opdiv = 8; |
152 | |
153 | cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * |
154 | cfg->pll_ipdiv, |
155 | pll_ref_hz); |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) |
161 | { |
162 | unsigned long psm_clk_hz = clk_get_rate(clk: dphy->psm_clk); |
163 | unsigned long psm_div; |
164 | |
165 | if (!psm_clk_hz || psm_clk_hz > 100000000) |
166 | return -EINVAL; |
167 | |
168 | psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); |
169 | if (dphy->ops->set_psm_div) |
170 | dphy->ops->set_psm_div(dphy, psm_div); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, |
176 | enum cdns_dphy_clk_lane_cfg cfg) |
177 | { |
178 | if (dphy->ops->set_clk_lane_cfg) |
179 | dphy->ops->set_clk_lane_cfg(dphy, cfg); |
180 | } |
181 | |
182 | static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, |
183 | const struct cdns_dphy_cfg *cfg) |
184 | { |
185 | if (dphy->ops->set_pll_cfg) |
186 | dphy->ops->set_pll_cfg(dphy, cfg); |
187 | } |
188 | |
189 | static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) |
190 | { |
191 | return dphy->ops->get_wakeup_time_ns(dphy); |
192 | } |
193 | |
194 | static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) |
195 | { |
196 | /* Default wakeup time is 800 ns (in a simulated environment). */ |
197 | return 800; |
198 | } |
199 | |
200 | static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, |
201 | const struct cdns_dphy_cfg *cfg) |
202 | { |
203 | u32 fbdiv_low, fbdiv_high; |
204 | |
205 | fbdiv_low = (cfg->pll_fbdiv / 4) - 2; |
206 | fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; |
207 | |
208 | writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | |
209 | DPHY_CMN_IPDIV(cfg->pll_ipdiv) | |
210 | DPHY_CMN_OPDIV(cfg->pll_opdiv), |
211 | addr: dphy->regs + DPHY_CMN_OPIPDIV); |
212 | writel(DPHY_CMN_FBDIV_FROM_REG | |
213 | DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), |
214 | addr: dphy->regs + DPHY_CMN_FBDIV); |
215 | writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | |
216 | DPHY_CMN_PWM_DIV(0x8), |
217 | addr: dphy->regs + DPHY_CMN_PWM); |
218 | } |
219 | |
220 | static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) |
221 | { |
222 | writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), |
223 | addr: dphy->regs + DPHY_PSM_CFG); |
224 | } |
225 | |
226 | static unsigned long cdns_dphy_j721e_get_wakeup_time_ns(struct cdns_dphy *dphy) |
227 | { |
228 | /* Minimum wakeup time as per MIPI D-PHY spec v1.2 */ |
229 | return 1000000; |
230 | } |
231 | |
232 | static void cdns_dphy_j721e_set_pll_cfg(struct cdns_dphy *dphy, |
233 | const struct cdns_dphy_cfg *cfg) |
234 | { |
235 | u32 status; |
236 | |
237 | /* |
238 | * set the PWM and PLL Byteclk divider settings to recommended values |
239 | * which is same as that of in ref ops |
240 | */ |
241 | writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | |
242 | DPHY_CMN_PWM_DIV(0x8), |
243 | addr: dphy->regs + DPHY_CMN_PWM); |
244 | |
245 | writel(val: (FIELD_PREP(DPHY_TX_J721E_WIZ_IPDIV, cfg->pll_ipdiv) | |
246 | FIELD_PREP(DPHY_TX_J721E_WIZ_OPDIV, cfg->pll_opdiv) | |
247 | FIELD_PREP(DPHY_TX_J721E_WIZ_FBDIV, cfg->pll_fbdiv)), |
248 | addr: dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL); |
249 | |
250 | writel(DPHY_TX_J721E_WIZ_LANE_RSTB, |
251 | addr: dphy->regs + DPHY_TX_J721E_WIZ_RST_CTRL); |
252 | |
253 | readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_PLL_CTRL, status, |
254 | (status & DPHY_TX_WIZ_PLL_LOCK), 0, POLL_TIMEOUT_US); |
255 | |
256 | readl_poll_timeout(dphy->regs + DPHY_TX_J721E_WIZ_STATUS, status, |
257 | (status & DPHY_TX_WIZ_O_CMN_READY), 0, |
258 | POLL_TIMEOUT_US); |
259 | } |
260 | |
261 | static void cdns_dphy_j721e_set_psm_div(struct cdns_dphy *dphy, u8 div) |
262 | { |
263 | writel(val: div, addr: dphy->regs + DPHY_TX_J721E_WIZ_PSM_FREQ); |
264 | } |
265 | |
266 | /* |
267 | * This is the reference implementation of DPHY hooks. Specific integration of |
268 | * this IP may have to re-implement some of them depending on how they decided |
269 | * to wire things in the SoC. |
270 | */ |
271 | static const struct cdns_dphy_ops ref_dphy_ops = { |
272 | .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, |
273 | .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, |
274 | .set_psm_div = cdns_dphy_ref_set_psm_div, |
275 | }; |
276 | |
277 | static const struct cdns_dphy_ops j721e_dphy_ops = { |
278 | .get_wakeup_time_ns = cdns_dphy_j721e_get_wakeup_time_ns, |
279 | .set_pll_cfg = cdns_dphy_j721e_set_pll_cfg, |
280 | .set_psm_div = cdns_dphy_j721e_set_psm_div, |
281 | }; |
282 | |
283 | static int cdns_dphy_config_from_opts(struct phy *phy, |
284 | struct phy_configure_opts_mipi_dphy *opts, |
285 | struct cdns_dphy_cfg *cfg) |
286 | { |
287 | struct cdns_dphy *dphy = phy_get_drvdata(phy); |
288 | unsigned int dsi_hfp_ext = 0; |
289 | int ret; |
290 | |
291 | ret = phy_mipi_dphy_config_validate(cfg: opts); |
292 | if (ret) |
293 | return ret; |
294 | |
295 | ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg, |
296 | opts, dsi_hfp_ext: &dsi_hfp_ext); |
297 | if (ret) |
298 | return ret; |
299 | |
300 | opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000; |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static int cdns_dphy_tx_get_band_ctrl(unsigned long hs_clk_rate) |
306 | { |
307 | unsigned int rate; |
308 | int i; |
309 | |
310 | rate = hs_clk_rate / 1000000UL; |
311 | |
312 | if (rate < tx_bands[0]) |
313 | return -EOPNOTSUPP; |
314 | |
315 | for (i = 0; i < ARRAY_SIZE(tx_bands) - 1; i++) { |
316 | if (rate >= tx_bands[i] && rate < tx_bands[i + 1]) |
317 | return i; |
318 | } |
319 | |
320 | return -EOPNOTSUPP; |
321 | } |
322 | |
323 | static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, |
324 | union phy_configure_opts *opts) |
325 | { |
326 | struct cdns_dphy_cfg cfg = { 0 }; |
327 | |
328 | if (mode != PHY_MODE_MIPI_DPHY) |
329 | return -EINVAL; |
330 | |
331 | return cdns_dphy_config_from_opts(phy, opts: &opts->mipi_dphy, cfg: &cfg); |
332 | } |
333 | |
334 | static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) |
335 | { |
336 | struct cdns_dphy *dphy = phy_get_drvdata(phy); |
337 | struct cdns_dphy_cfg cfg = { 0 }; |
338 | int ret, band_ctrl; |
339 | unsigned int reg; |
340 | |
341 | ret = cdns_dphy_config_from_opts(phy, opts: &opts->mipi_dphy, cfg: &cfg); |
342 | if (ret) |
343 | return ret; |
344 | |
345 | /* |
346 | * Configure the internal PSM clk divider so that the DPHY has a |
347 | * 1MHz clk (or something close). |
348 | */ |
349 | ret = cdns_dphy_setup_psm(dphy); |
350 | if (ret) |
351 | return ret; |
352 | |
353 | /* |
354 | * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes |
355 | * and 8 data lanes, each clk lane can be attache different set of |
356 | * data lanes. The 2 groups are named 'left' and 'right', so here we |
357 | * just say that we want the 'left' clk lane to drive the 'left' data |
358 | * lanes. |
359 | */ |
360 | cdns_dphy_set_clk_lane_cfg(dphy, cfg: DPHY_CLK_CFG_LEFT_DRIVES_LEFT); |
361 | |
362 | /* |
363 | * Configure the DPHY PLL that will be used to generate the TX byte |
364 | * clk. |
365 | */ |
366 | cdns_dphy_set_pll_cfg(dphy, cfg: &cfg); |
367 | |
368 | band_ctrl = cdns_dphy_tx_get_band_ctrl(hs_clk_rate: opts->mipi_dphy.hs_clk_rate); |
369 | if (band_ctrl < 0) |
370 | return band_ctrl; |
371 | |
372 | reg = FIELD_PREP(DPHY_BAND_CFG_LEFT_BAND, band_ctrl) | |
373 | FIELD_PREP(DPHY_BAND_CFG_RIGHT_BAND, band_ctrl); |
374 | writel(val: reg, addr: dphy->regs + DPHY_BAND_CFG); |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static int cdns_dphy_power_on(struct phy *phy) |
380 | { |
381 | struct cdns_dphy *dphy = phy_get_drvdata(phy); |
382 | |
383 | clk_prepare_enable(clk: dphy->psm_clk); |
384 | clk_prepare_enable(clk: dphy->pll_ref_clk); |
385 | |
386 | /* Start TX state machine. */ |
387 | writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, |
388 | addr: dphy->regs + DPHY_CMN_SSM); |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | static int cdns_dphy_power_off(struct phy *phy) |
394 | { |
395 | struct cdns_dphy *dphy = phy_get_drvdata(phy); |
396 | |
397 | clk_disable_unprepare(clk: dphy->pll_ref_clk); |
398 | clk_disable_unprepare(clk: dphy->psm_clk); |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | static const struct phy_ops cdns_dphy_ops = { |
404 | .configure = cdns_dphy_configure, |
405 | .validate = cdns_dphy_validate, |
406 | .power_on = cdns_dphy_power_on, |
407 | .power_off = cdns_dphy_power_off, |
408 | }; |
409 | |
410 | static int cdns_dphy_probe(struct platform_device *pdev) |
411 | { |
412 | struct phy_provider *phy_provider; |
413 | struct cdns_dphy *dphy; |
414 | int ret; |
415 | |
416 | dphy = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dphy), GFP_KERNEL); |
417 | if (!dphy) |
418 | return -ENOMEM; |
419 | dev_set_drvdata(dev: &pdev->dev, data: dphy); |
420 | |
421 | dphy->ops = of_device_get_match_data(dev: &pdev->dev); |
422 | if (!dphy->ops) |
423 | return -EINVAL; |
424 | |
425 | dphy->regs = devm_platform_ioremap_resource(pdev, index: 0); |
426 | if (IS_ERR(ptr: dphy->regs)) |
427 | return PTR_ERR(ptr: dphy->regs); |
428 | |
429 | dphy->psm_clk = devm_clk_get(dev: &pdev->dev, id: "psm" ); |
430 | if (IS_ERR(ptr: dphy->psm_clk)) |
431 | return PTR_ERR(ptr: dphy->psm_clk); |
432 | |
433 | dphy->pll_ref_clk = devm_clk_get(dev: &pdev->dev, id: "pll_ref" ); |
434 | if (IS_ERR(ptr: dphy->pll_ref_clk)) |
435 | return PTR_ERR(ptr: dphy->pll_ref_clk); |
436 | |
437 | if (dphy->ops->probe) { |
438 | ret = dphy->ops->probe(dphy); |
439 | if (ret) |
440 | return ret; |
441 | } |
442 | |
443 | dphy->phy = devm_phy_create(dev: &pdev->dev, NULL, ops: &cdns_dphy_ops); |
444 | if (IS_ERR(ptr: dphy->phy)) { |
445 | dev_err(&pdev->dev, "failed to create PHY\n" ); |
446 | if (dphy->ops->remove) |
447 | dphy->ops->remove(dphy); |
448 | return PTR_ERR(ptr: dphy->phy); |
449 | } |
450 | |
451 | phy_set_drvdata(phy: dphy->phy, data: dphy); |
452 | phy_provider = devm_of_phy_provider_register(&pdev->dev, |
453 | of_phy_simple_xlate); |
454 | |
455 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
456 | } |
457 | |
458 | static void cdns_dphy_remove(struct platform_device *pdev) |
459 | { |
460 | struct cdns_dphy *dphy = dev_get_drvdata(dev: &pdev->dev); |
461 | |
462 | if (dphy->ops->remove) |
463 | dphy->ops->remove(dphy); |
464 | } |
465 | |
466 | static const struct of_device_id cdns_dphy_of_match[] = { |
467 | { .compatible = "cdns,dphy" , .data = &ref_dphy_ops }, |
468 | { .compatible = "ti,j721e-dphy" , .data = &j721e_dphy_ops }, |
469 | { /* sentinel */ }, |
470 | }; |
471 | MODULE_DEVICE_TABLE(of, cdns_dphy_of_match); |
472 | |
473 | static struct platform_driver cdns_dphy_platform_driver = { |
474 | .probe = cdns_dphy_probe, |
475 | .remove_new = cdns_dphy_remove, |
476 | .driver = { |
477 | .name = "cdns-mipi-dphy" , |
478 | .of_match_table = cdns_dphy_of_match, |
479 | }, |
480 | }; |
481 | module_platform_driver(cdns_dphy_platform_driver); |
482 | |
483 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>" ); |
484 | MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver" ); |
485 | MODULE_LICENSE("GPL" ); |
486 | |