1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2017-2020,2022 NXP |
4 | */ |
5 | |
6 | #include <linux/bitfield.h> |
7 | #include <linux/bits.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/mfd/syscon.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/pm_runtime.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/units.h> |
17 | |
18 | #define REG_SET 0x4 |
19 | #define REG_CLR 0x8 |
20 | |
21 | #define PHY_CTRL 0x0 |
22 | #define M_MASK GENMASK(18, 17) |
23 | #define M(n) FIELD_PREP(M_MASK, (n)) |
24 | #define CCM_MASK GENMASK(16, 14) |
25 | #define CCM(n) FIELD_PREP(CCM_MASK, (n)) |
26 | #define CA_MASK GENMASK(13, 11) |
27 | #define CA(n) FIELD_PREP(CA_MASK, (n)) |
28 | #define TST_MASK GENMASK(10, 5) |
29 | #define TST(n) FIELD_PREP(TST_MASK, (n)) |
30 | #define CH_EN(id) BIT(3 + (id)) |
31 | #define NB BIT(2) |
32 | #define RFB BIT(1) |
33 | #define PD BIT(0) |
34 | |
35 | /* Power On Reset(POR) value */ |
36 | #define CTRL_RESET_VAL (M(0x0) | CCM(0x4) | CA(0x4) | TST(0x25)) |
37 | |
38 | /* PHY initialization value and mask */ |
39 | #define CTRL_INIT_MASK (M_MASK | CCM_MASK | CA_MASK | TST_MASK | NB | RFB) |
40 | #define CTRL_INIT_VAL (M(0x0) | CCM(0x5) | CA(0x4) | TST(0x25) | RFB) |
41 | |
42 | #define PHY_STATUS 0x10 |
43 | #define LOCK BIT(0) |
44 | |
45 | #define PHY_NUM 2 |
46 | |
47 | #define MIN_CLKIN_FREQ (25 * MEGA) |
48 | #define MAX_CLKIN_FREQ (165 * MEGA) |
49 | |
50 | #define PLL_LOCK_SLEEP 10 |
51 | #define PLL_LOCK_TIMEOUT 1000 |
52 | |
53 | struct mixel_lvds_phy { |
54 | struct phy *phy; |
55 | struct phy_configure_opts_lvds cfg; |
56 | unsigned int id; |
57 | }; |
58 | |
59 | struct mixel_lvds_phy_priv { |
60 | struct regmap *regmap; |
61 | struct mutex lock; /* protect remap access and cfg of our own */ |
62 | struct clk *phy_ref_clk; |
63 | struct mixel_lvds_phy *phys[PHY_NUM]; |
64 | }; |
65 | |
66 | static int mixel_lvds_phy_init(struct phy *phy) |
67 | { |
68 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: phy->dev.parent); |
69 | |
70 | mutex_lock(&priv->lock); |
71 | regmap_update_bits(map: priv->regmap, |
72 | PHY_CTRL, CTRL_INIT_MASK, CTRL_INIT_VAL); |
73 | mutex_unlock(lock: &priv->lock); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int mixel_lvds_phy_power_on(struct phy *phy) |
79 | { |
80 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: phy->dev.parent); |
81 | struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy); |
82 | struct mixel_lvds_phy *companion = priv->phys[lvds_phy->id ^ 1]; |
83 | struct phy_configure_opts_lvds *cfg = &lvds_phy->cfg; |
84 | u32 val = 0; |
85 | u32 locked; |
86 | int ret; |
87 | |
88 | /* The master PHY would power on the slave PHY. */ |
89 | if (cfg->is_slave) |
90 | return 0; |
91 | |
92 | ret = clk_prepare_enable(clk: priv->phy_ref_clk); |
93 | if (ret < 0) { |
94 | dev_err(&phy->dev, |
95 | "failed to enable PHY reference clock: %d\n" , ret); |
96 | return ret; |
97 | } |
98 | |
99 | mutex_lock(&priv->lock); |
100 | if (cfg->bits_per_lane_and_dclk_cycle == 7) { |
101 | if (cfg->differential_clk_rate < 44000000) |
102 | val |= M(0x2); |
103 | else if (cfg->differential_clk_rate < 90000000) |
104 | val |= M(0x1); |
105 | else |
106 | val |= M(0x0); |
107 | } else { |
108 | val = NB; |
109 | |
110 | if (cfg->differential_clk_rate < 32000000) |
111 | val |= M(0x2); |
112 | else if (cfg->differential_clk_rate < 63000000) |
113 | val |= M(0x1); |
114 | else |
115 | val |= M(0x0); |
116 | } |
117 | regmap_update_bits(map: priv->regmap, PHY_CTRL, M_MASK | NB, val); |
118 | |
119 | /* |
120 | * Enable two channels synchronously, |
121 | * if the companion PHY is a slave PHY. |
122 | */ |
123 | if (companion->cfg.is_slave) |
124 | val = CH_EN(0) | CH_EN(1); |
125 | else |
126 | val = CH_EN(lvds_phy->id); |
127 | regmap_write(map: priv->regmap, PHY_CTRL + REG_SET, val); |
128 | |
129 | ret = regmap_read_poll_timeout(priv->regmap, PHY_STATUS, locked, |
130 | locked, PLL_LOCK_SLEEP, |
131 | PLL_LOCK_TIMEOUT); |
132 | if (ret < 0) { |
133 | dev_err(&phy->dev, "failed to get PHY lock: %d\n" , ret); |
134 | clk_disable_unprepare(clk: priv->phy_ref_clk); |
135 | } |
136 | mutex_unlock(lock: &priv->lock); |
137 | |
138 | return ret; |
139 | } |
140 | |
141 | static int mixel_lvds_phy_power_off(struct phy *phy) |
142 | { |
143 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: phy->dev.parent); |
144 | struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy); |
145 | struct mixel_lvds_phy *companion = priv->phys[lvds_phy->id ^ 1]; |
146 | struct phy_configure_opts_lvds *cfg = &lvds_phy->cfg; |
147 | |
148 | /* The master PHY would power off the slave PHY. */ |
149 | if (cfg->is_slave) |
150 | return 0; |
151 | |
152 | mutex_lock(&priv->lock); |
153 | if (companion->cfg.is_slave) |
154 | regmap_write(map: priv->regmap, PHY_CTRL + REG_CLR, |
155 | CH_EN(0) | CH_EN(1)); |
156 | else |
157 | regmap_write(map: priv->regmap, PHY_CTRL + REG_CLR, |
158 | CH_EN(lvds_phy->id)); |
159 | mutex_unlock(lock: &priv->lock); |
160 | |
161 | clk_disable_unprepare(clk: priv->phy_ref_clk); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static int mixel_lvds_phy_configure(struct phy *phy, |
167 | union phy_configure_opts *opts) |
168 | { |
169 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: phy->dev.parent); |
170 | struct phy_configure_opts_lvds *cfg = &opts->lvds; |
171 | int ret; |
172 | |
173 | ret = clk_set_rate(clk: priv->phy_ref_clk, rate: cfg->differential_clk_rate); |
174 | if (ret) |
175 | dev_err(&phy->dev, "failed to set PHY reference clock rate(%lu): %d\n" , |
176 | cfg->differential_clk_rate, ret); |
177 | |
178 | return ret; |
179 | } |
180 | |
181 | /* Assume the master PHY's configuration set is cached first. */ |
182 | static int mixel_lvds_phy_check_slave(struct phy *slave_phy) |
183 | { |
184 | struct device *dev = &slave_phy->dev; |
185 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: dev->parent); |
186 | struct mixel_lvds_phy *slv = phy_get_drvdata(phy: slave_phy); |
187 | struct mixel_lvds_phy *mst = priv->phys[slv->id ^ 1]; |
188 | struct phy_configure_opts_lvds *mst_cfg = &mst->cfg; |
189 | struct phy_configure_opts_lvds *slv_cfg = &slv->cfg; |
190 | |
191 | if (mst_cfg->bits_per_lane_and_dclk_cycle != |
192 | slv_cfg->bits_per_lane_and_dclk_cycle) { |
193 | dev_err(dev, "number bits mismatch(mst: %u vs slv: %u)\n" , |
194 | mst_cfg->bits_per_lane_and_dclk_cycle, |
195 | slv_cfg->bits_per_lane_and_dclk_cycle); |
196 | return -EINVAL; |
197 | } |
198 | |
199 | if (mst_cfg->differential_clk_rate != |
200 | slv_cfg->differential_clk_rate) { |
201 | dev_err(dev, "dclk rate mismatch(mst: %lu vs slv: %lu)\n" , |
202 | mst_cfg->differential_clk_rate, |
203 | slv_cfg->differential_clk_rate); |
204 | return -EINVAL; |
205 | } |
206 | |
207 | if (mst_cfg->lanes != slv_cfg->lanes) { |
208 | dev_err(dev, "lanes mismatch(mst: %u vs slv: %u)\n" , |
209 | mst_cfg->lanes, slv_cfg->lanes); |
210 | return -EINVAL; |
211 | } |
212 | |
213 | if (mst_cfg->is_slave == slv_cfg->is_slave) { |
214 | dev_err(dev, "master PHY is not found\n" ); |
215 | return -EINVAL; |
216 | } |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | static int mixel_lvds_phy_validate(struct phy *phy, enum phy_mode mode, |
222 | int submode, union phy_configure_opts *opts) |
223 | { |
224 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev: phy->dev.parent); |
225 | struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy); |
226 | struct phy_configure_opts_lvds *cfg = &opts->lvds; |
227 | int ret = 0; |
228 | |
229 | if (mode != PHY_MODE_LVDS) { |
230 | dev_err(&phy->dev, "invalid PHY mode(%d)\n" , mode); |
231 | return -EINVAL; |
232 | } |
233 | |
234 | if (cfg->bits_per_lane_and_dclk_cycle != 7 && |
235 | cfg->bits_per_lane_and_dclk_cycle != 10) { |
236 | dev_err(&phy->dev, "invalid bits per data lane(%u)\n" , |
237 | cfg->bits_per_lane_and_dclk_cycle); |
238 | return -EINVAL; |
239 | } |
240 | |
241 | if (cfg->lanes != 4 && cfg->lanes != 3) { |
242 | dev_err(&phy->dev, "invalid data lanes(%u)\n" , cfg->lanes); |
243 | return -EINVAL; |
244 | } |
245 | |
246 | if (cfg->differential_clk_rate < MIN_CLKIN_FREQ || |
247 | cfg->differential_clk_rate > MAX_CLKIN_FREQ) { |
248 | dev_err(&phy->dev, "invalid differential clock rate(%lu)\n" , |
249 | cfg->differential_clk_rate); |
250 | return -EINVAL; |
251 | } |
252 | |
253 | mutex_lock(&priv->lock); |
254 | /* cache configuration set of our own for check */ |
255 | memcpy(&lvds_phy->cfg, cfg, sizeof(*cfg)); |
256 | |
257 | if (cfg->is_slave) { |
258 | ret = mixel_lvds_phy_check_slave(slave_phy: phy); |
259 | if (ret) |
260 | dev_err(&phy->dev, "failed to check slave PHY: %d\n" , ret); |
261 | } |
262 | mutex_unlock(lock: &priv->lock); |
263 | |
264 | return ret; |
265 | } |
266 | |
267 | static const struct phy_ops mixel_lvds_phy_ops = { |
268 | .init = mixel_lvds_phy_init, |
269 | .power_on = mixel_lvds_phy_power_on, |
270 | .power_off = mixel_lvds_phy_power_off, |
271 | .configure = mixel_lvds_phy_configure, |
272 | .validate = mixel_lvds_phy_validate, |
273 | .owner = THIS_MODULE, |
274 | }; |
275 | |
276 | static int mixel_lvds_phy_reset(struct device *dev) |
277 | { |
278 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev); |
279 | int ret; |
280 | |
281 | ret = pm_runtime_resume_and_get(dev); |
282 | if (ret < 0) { |
283 | dev_err(dev, "failed to get PM runtime: %d\n" , ret); |
284 | return ret; |
285 | } |
286 | |
287 | regmap_write(map: priv->regmap, PHY_CTRL, CTRL_RESET_VAL); |
288 | |
289 | ret = pm_runtime_put(dev); |
290 | if (ret < 0) |
291 | dev_err(dev, "failed to put PM runtime: %d\n" , ret); |
292 | |
293 | return ret; |
294 | } |
295 | |
296 | static struct phy *mixel_lvds_phy_xlate(struct device *dev, |
297 | const struct of_phandle_args *args) |
298 | { |
299 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev); |
300 | unsigned int phy_id; |
301 | |
302 | if (args->args_count != 1) { |
303 | dev_err(dev, |
304 | "invalid argument number(%d) for 'phys' property\n" , |
305 | args->args_count); |
306 | return ERR_PTR(error: -EINVAL); |
307 | } |
308 | |
309 | phy_id = args->args[0]; |
310 | |
311 | if (phy_id >= PHY_NUM) { |
312 | dev_err(dev, "invalid PHY index(%d)\n" , phy_id); |
313 | return ERR_PTR(error: -ENODEV); |
314 | } |
315 | |
316 | return priv->phys[phy_id]->phy; |
317 | } |
318 | |
319 | static int mixel_lvds_phy_probe(struct platform_device *pdev) |
320 | { |
321 | struct device *dev = &pdev->dev; |
322 | struct phy_provider *phy_provider; |
323 | struct mixel_lvds_phy_priv *priv; |
324 | struct mixel_lvds_phy *lvds_phy; |
325 | struct phy *phy; |
326 | int i; |
327 | int ret; |
328 | |
329 | if (!dev->of_node) |
330 | return -ENODEV; |
331 | |
332 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
333 | if (!priv) |
334 | return -ENOMEM; |
335 | |
336 | priv->regmap = syscon_node_to_regmap(np: dev->of_node->parent); |
337 | if (IS_ERR(ptr: priv->regmap)) |
338 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->regmap), |
339 | fmt: "failed to get regmap\n" ); |
340 | |
341 | priv->phy_ref_clk = devm_clk_get(dev, NULL); |
342 | if (IS_ERR(ptr: priv->phy_ref_clk)) |
343 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->phy_ref_clk), |
344 | fmt: "failed to get PHY reference clock\n" ); |
345 | |
346 | mutex_init(&priv->lock); |
347 | |
348 | dev_set_drvdata(dev, data: priv); |
349 | |
350 | pm_runtime_enable(dev); |
351 | |
352 | ret = mixel_lvds_phy_reset(dev); |
353 | if (ret) { |
354 | dev_err(dev, "failed to do POR reset: %d\n" , ret); |
355 | return ret; |
356 | } |
357 | |
358 | for (i = 0; i < PHY_NUM; i++) { |
359 | lvds_phy = devm_kzalloc(dev, size: sizeof(*lvds_phy), GFP_KERNEL); |
360 | if (!lvds_phy) { |
361 | ret = -ENOMEM; |
362 | goto err; |
363 | } |
364 | |
365 | phy = devm_phy_create(dev, NULL, ops: &mixel_lvds_phy_ops); |
366 | if (IS_ERR(ptr: phy)) { |
367 | ret = PTR_ERR(ptr: phy); |
368 | dev_err(dev, "failed to create PHY for channel%d: %d\n" , |
369 | i, ret); |
370 | goto err; |
371 | } |
372 | |
373 | lvds_phy->phy = phy; |
374 | lvds_phy->id = i; |
375 | priv->phys[i] = lvds_phy; |
376 | |
377 | phy_set_drvdata(phy, data: lvds_phy); |
378 | } |
379 | |
380 | phy_provider = devm_of_phy_provider_register(dev, mixel_lvds_phy_xlate); |
381 | if (IS_ERR(ptr: phy_provider)) { |
382 | ret = PTR_ERR(ptr: phy_provider); |
383 | dev_err(dev, "failed to register PHY provider: %d\n" , ret); |
384 | goto err; |
385 | } |
386 | |
387 | return 0; |
388 | err: |
389 | pm_runtime_disable(dev); |
390 | |
391 | return ret; |
392 | } |
393 | |
394 | static void mixel_lvds_phy_remove(struct platform_device *pdev) |
395 | { |
396 | pm_runtime_disable(dev: &pdev->dev); |
397 | } |
398 | |
399 | static int __maybe_unused mixel_lvds_phy_runtime_suspend(struct device *dev) |
400 | { |
401 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev); |
402 | |
403 | /* power down */ |
404 | mutex_lock(&priv->lock); |
405 | regmap_write(map: priv->regmap, PHY_CTRL + REG_SET, PD); |
406 | mutex_unlock(lock: &priv->lock); |
407 | |
408 | return 0; |
409 | } |
410 | |
411 | static int __maybe_unused mixel_lvds_phy_runtime_resume(struct device *dev) |
412 | { |
413 | struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev); |
414 | |
415 | /* power up + control initialization */ |
416 | mutex_lock(&priv->lock); |
417 | regmap_update_bits(map: priv->regmap, PHY_CTRL, |
418 | CTRL_INIT_MASK | PD, CTRL_INIT_VAL); |
419 | mutex_unlock(lock: &priv->lock); |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | static const struct dev_pm_ops mixel_lvds_phy_pm_ops = { |
425 | SET_RUNTIME_PM_OPS(mixel_lvds_phy_runtime_suspend, |
426 | mixel_lvds_phy_runtime_resume, NULL) |
427 | }; |
428 | |
429 | static const struct of_device_id mixel_lvds_phy_of_match[] = { |
430 | { .compatible = "fsl,imx8qm-lvds-phy" }, |
431 | { /* sentinel */ } |
432 | }; |
433 | MODULE_DEVICE_TABLE(of, mixel_lvds_phy_of_match); |
434 | |
435 | static struct platform_driver mixel_lvds_phy_driver = { |
436 | .probe = mixel_lvds_phy_probe, |
437 | .remove_new = mixel_lvds_phy_remove, |
438 | .driver = { |
439 | .pm = &mixel_lvds_phy_pm_ops, |
440 | .name = "mixel-lvds-phy" , |
441 | .of_match_table = mixel_lvds_phy_of_match, |
442 | } |
443 | }; |
444 | module_platform_driver(mixel_lvds_phy_driver); |
445 | |
446 | MODULE_DESCRIPTION("Mixel LVDS PHY driver" ); |
447 | MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>" ); |
448 | MODULE_LICENSE("GPL" ); |
449 | |