1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Copyright 2020 NXP |
5 | */ |
6 | |
7 | #include <linux/firmware/imx/svc/misc.h> |
8 | #include <linux/media-bus-format.h> |
9 | #include <linux/mfd/syscon.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_device.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | #include <drm/drm_atomic_state_helper.h> |
19 | #include <drm/drm_bridge.h> |
20 | #include <drm/drm_of.h> |
21 | #include <drm/drm_print.h> |
22 | |
23 | #include <dt-bindings/firmware/imx/rsrc.h> |
24 | |
25 | #define PXL2DPI_CTRL 0x40 |
26 | #define CFG1_16BIT 0x0 |
27 | #define CFG2_16BIT 0x1 |
28 | #define CFG3_16BIT 0x2 |
29 | #define CFG1_18BIT 0x3 |
30 | #define CFG2_18BIT 0x4 |
31 | #define CFG_24BIT 0x5 |
32 | |
33 | #define DRIVER_NAME "imx8qxp-pxl2dpi" |
34 | |
35 | struct imx8qxp_pxl2dpi { |
36 | struct regmap *regmap; |
37 | struct drm_bridge bridge; |
38 | struct drm_bridge *next_bridge; |
39 | struct drm_bridge *companion; |
40 | struct device *dev; |
41 | struct imx_sc_ipc *ipc_handle; |
42 | u32 sc_resource; |
43 | u32 in_bus_format; |
44 | u32 out_bus_format; |
45 | u32 pl_sel; |
46 | }; |
47 | |
48 | #define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge) |
49 | |
50 | static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge, |
51 | enum drm_bridge_attach_flags flags) |
52 | { |
53 | struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; |
54 | |
55 | if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { |
56 | DRM_DEV_ERROR(p2d->dev, |
57 | "do not support creating a drm_connector\n" ); |
58 | return -EINVAL; |
59 | } |
60 | |
61 | if (!bridge->encoder) { |
62 | DRM_DEV_ERROR(p2d->dev, "missing encoder\n" ); |
63 | return -ENODEV; |
64 | } |
65 | |
66 | return drm_bridge_attach(encoder: bridge->encoder, |
67 | bridge: p2d->next_bridge, previous: bridge, |
68 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
69 | } |
70 | |
71 | static int |
72 | imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge, |
73 | struct drm_bridge_state *bridge_state, |
74 | struct drm_crtc_state *crtc_state, |
75 | struct drm_connector_state *conn_state) |
76 | { |
77 | struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; |
78 | |
79 | p2d->in_bus_format = bridge_state->input_bus_cfg.format; |
80 | p2d->out_bus_format = bridge_state->output_bus_cfg.format; |
81 | |
82 | return 0; |
83 | } |
84 | |
85 | static void |
86 | imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge, |
87 | const struct drm_display_mode *mode, |
88 | const struct drm_display_mode *adjusted_mode) |
89 | { |
90 | struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; |
91 | struct imx8qxp_pxl2dpi *companion_p2d; |
92 | int ret; |
93 | |
94 | ret = pm_runtime_get_sync(dev: p2d->dev); |
95 | if (ret < 0) |
96 | DRM_DEV_ERROR(p2d->dev, |
97 | "failed to get runtime PM sync: %d\n" , ret); |
98 | |
99 | ret = imx_sc_misc_set_control(ipc: p2d->ipc_handle, resource: p2d->sc_resource, |
100 | IMX_SC_C_PXL_LINK_SEL, val: p2d->pl_sel); |
101 | if (ret) |
102 | DRM_DEV_ERROR(p2d->dev, |
103 | "failed to set pixel link selection(%u): %d\n" , |
104 | p2d->pl_sel, ret); |
105 | |
106 | switch (p2d->out_bus_format) { |
107 | case MEDIA_BUS_FMT_RGB888_1X24: |
108 | regmap_write(map: p2d->regmap, PXL2DPI_CTRL, CFG_24BIT); |
109 | break; |
110 | case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: |
111 | regmap_write(map: p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT); |
112 | break; |
113 | default: |
114 | DRM_DEV_ERROR(p2d->dev, |
115 | "unsupported output bus format 0x%08x\n" , |
116 | p2d->out_bus_format); |
117 | } |
118 | |
119 | if (p2d->companion) { |
120 | companion_p2d = bridge_to_p2d(p2d->companion); |
121 | |
122 | companion_p2d->in_bus_format = p2d->in_bus_format; |
123 | companion_p2d->out_bus_format = p2d->out_bus_format; |
124 | |
125 | p2d->companion->funcs->mode_set(p2d->companion, mode, |
126 | adjusted_mode); |
127 | } |
128 | } |
129 | |
130 | static void |
131 | imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge, |
132 | struct drm_bridge_state *old_bridge_state) |
133 | { |
134 | struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; |
135 | int ret; |
136 | |
137 | ret = pm_runtime_put(dev: p2d->dev); |
138 | if (ret < 0) |
139 | DRM_DEV_ERROR(p2d->dev, "failed to put runtime PM: %d\n" , ret); |
140 | |
141 | if (p2d->companion) |
142 | p2d->companion->funcs->atomic_disable(p2d->companion, |
143 | old_bridge_state); |
144 | } |
145 | |
146 | static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = { |
147 | MEDIA_BUS_FMT_RGB888_1X24, |
148 | MEDIA_BUS_FMT_RGB666_1X24_CPADHI, |
149 | }; |
150 | |
151 | static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt) |
152 | { |
153 | int i; |
154 | |
155 | for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) { |
156 | if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt) |
157 | return true; |
158 | } |
159 | |
160 | return false; |
161 | } |
162 | |
163 | static u32 * |
164 | imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, |
165 | struct drm_bridge_state *bridge_state, |
166 | struct drm_crtc_state *crtc_state, |
167 | struct drm_connector_state *conn_state, |
168 | u32 output_fmt, |
169 | unsigned int *num_input_fmts) |
170 | { |
171 | u32 *input_fmts; |
172 | |
173 | if (!imx8qxp_pxl2dpi_bus_output_fmt_supported(fmt: output_fmt)) |
174 | return NULL; |
175 | |
176 | *num_input_fmts = 1; |
177 | |
178 | input_fmts = kmalloc(size: sizeof(*input_fmts), GFP_KERNEL); |
179 | if (!input_fmts) |
180 | return NULL; |
181 | |
182 | switch (output_fmt) { |
183 | case MEDIA_BUS_FMT_RGB888_1X24: |
184 | input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO; |
185 | break; |
186 | case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: |
187 | input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO; |
188 | break; |
189 | default: |
190 | kfree(objp: input_fmts); |
191 | input_fmts = NULL; |
192 | break; |
193 | } |
194 | |
195 | return input_fmts; |
196 | } |
197 | |
198 | static u32 * |
199 | imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, |
200 | struct drm_bridge_state *bridge_state, |
201 | struct drm_crtc_state *crtc_state, |
202 | struct drm_connector_state *conn_state, |
203 | unsigned int *num_output_fmts) |
204 | { |
205 | *num_output_fmts = ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); |
206 | return kmemdup(p: imx8qxp_pxl2dpi_bus_output_fmts, |
207 | size: sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL); |
208 | } |
209 | |
210 | static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = { |
211 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
212 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
213 | .atomic_reset = drm_atomic_helper_bridge_reset, |
214 | .attach = imx8qxp_pxl2dpi_bridge_attach, |
215 | .atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check, |
216 | .mode_set = imx8qxp_pxl2dpi_bridge_mode_set, |
217 | .atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable, |
218 | .atomic_get_input_bus_fmts = |
219 | imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts, |
220 | .atomic_get_output_bus_fmts = |
221 | imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts, |
222 | }; |
223 | |
224 | static struct device_node * |
225 | imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d, |
226 | u32 port_id) |
227 | { |
228 | struct device_node *port, *ep; |
229 | int ep_cnt; |
230 | |
231 | port = of_graph_get_port_by_id(node: p2d->dev->of_node, id: port_id); |
232 | if (!port) { |
233 | DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n" , port_id); |
234 | return ERR_PTR(error: -ENODEV); |
235 | } |
236 | |
237 | ep_cnt = of_get_available_child_count(np: port); |
238 | if (ep_cnt == 0) { |
239 | DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n" , |
240 | port_id); |
241 | ep = ERR_PTR(error: -ENODEV); |
242 | goto out; |
243 | } else if (ep_cnt > 1) { |
244 | DRM_DEV_ERROR(p2d->dev, |
245 | "invalid available endpoints of port@%u\n" , |
246 | port_id); |
247 | ep = ERR_PTR(error: -EINVAL); |
248 | goto out; |
249 | } |
250 | |
251 | ep = of_get_next_available_child(node: port, NULL); |
252 | if (!ep) { |
253 | DRM_DEV_ERROR(p2d->dev, |
254 | "failed to get available endpoint of port@%u\n" , |
255 | port_id); |
256 | ep = ERR_PTR(error: -ENODEV); |
257 | goto out; |
258 | } |
259 | out: |
260 | of_node_put(node: port); |
261 | return ep; |
262 | } |
263 | |
264 | static struct drm_bridge * |
265 | imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) |
266 | { |
267 | struct device_node *ep, *remote; |
268 | struct drm_bridge *next_bridge; |
269 | int ret; |
270 | |
271 | ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, port_id: 1); |
272 | if (IS_ERR(ptr: ep)) { |
273 | ret = PTR_ERR(ptr: ep); |
274 | return ERR_PTR(error: ret); |
275 | } |
276 | |
277 | remote = of_graph_get_remote_port_parent(node: ep); |
278 | if (!remote || !of_device_is_available(device: remote)) { |
279 | DRM_DEV_ERROR(p2d->dev, "no available remote\n" ); |
280 | next_bridge = ERR_PTR(error: -ENODEV); |
281 | goto out; |
282 | } else if (!of_device_is_available(device: remote->parent)) { |
283 | DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n" ); |
284 | next_bridge = ERR_PTR(error: -ENODEV); |
285 | goto out; |
286 | } |
287 | |
288 | next_bridge = of_drm_find_bridge(np: remote); |
289 | if (!next_bridge) { |
290 | next_bridge = ERR_PTR(error: -EPROBE_DEFER); |
291 | goto out; |
292 | } |
293 | out: |
294 | of_node_put(node: remote); |
295 | of_node_put(node: ep); |
296 | |
297 | return next_bridge; |
298 | } |
299 | |
300 | static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d) |
301 | { |
302 | struct device_node *ep; |
303 | struct of_endpoint endpoint; |
304 | int ret; |
305 | |
306 | ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, port_id: 0); |
307 | if (IS_ERR(ptr: ep)) |
308 | return PTR_ERR(ptr: ep); |
309 | |
310 | ret = of_graph_parse_endpoint(node: ep, endpoint: &endpoint); |
311 | if (ret) { |
312 | DRM_DEV_ERROR(p2d->dev, |
313 | "failed to parse endpoint of port@0: %d\n" , ret); |
314 | goto out; |
315 | } |
316 | |
317 | p2d->pl_sel = endpoint.id; |
318 | out: |
319 | of_node_put(node: ep); |
320 | |
321 | return ret; |
322 | } |
323 | |
324 | static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d) |
325 | { |
326 | struct imx8qxp_pxl2dpi *companion_p2d; |
327 | struct device *dev = p2d->dev; |
328 | struct device_node *companion; |
329 | struct device_node *port1, *port2; |
330 | const struct of_device_id *match; |
331 | int dual_link; |
332 | int ret = 0; |
333 | |
334 | /* Locate the companion PXL2DPI for dual-link operation, if any. */ |
335 | companion = of_parse_phandle(np: dev->of_node, phandle_name: "fsl,companion-pxl2dpi" , index: 0); |
336 | if (!companion) |
337 | return 0; |
338 | |
339 | if (!of_device_is_available(device: companion)) { |
340 | DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n" ); |
341 | ret = -ENODEV; |
342 | goto out; |
343 | } |
344 | |
345 | /* |
346 | * Sanity check: the companion bridge must have the same compatible |
347 | * string. |
348 | */ |
349 | match = of_match_device(matches: dev->driver->of_match_table, dev); |
350 | if (!of_device_is_compatible(device: companion, match->compatible)) { |
351 | DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n" ); |
352 | ret = -ENXIO; |
353 | goto out; |
354 | } |
355 | |
356 | p2d->companion = of_drm_find_bridge(np: companion); |
357 | if (!p2d->companion) { |
358 | ret = -EPROBE_DEFER; |
359 | DRM_DEV_DEBUG_DRIVER(p2d->dev, |
360 | "failed to find companion bridge: %d\n" , |
361 | ret); |
362 | goto out; |
363 | } |
364 | |
365 | companion_p2d = bridge_to_p2d(p2d->companion); |
366 | |
367 | /* |
368 | * We need to work out if the sink is expecting us to function in |
369 | * dual-link mode. We do this by looking at the DT port nodes that |
370 | * the next bridges are connected to. If they are marked as expecting |
371 | * even pixels and odd pixels than we need to use the companion PXL2DPI. |
372 | */ |
373 | port1 = of_graph_get_port_by_id(node: p2d->next_bridge->of_node, id: 1); |
374 | port2 = of_graph_get_port_by_id(node: companion_p2d->next_bridge->of_node, id: 1); |
375 | dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); |
376 | of_node_put(node: port1); |
377 | of_node_put(node: port2); |
378 | |
379 | if (dual_link < 0) { |
380 | ret = dual_link; |
381 | DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n" , |
382 | ret); |
383 | goto out; |
384 | } |
385 | |
386 | DRM_DEV_DEBUG_DRIVER(dev, |
387 | "dual-link configuration detected (companion bridge %pOF)\n" , |
388 | companion); |
389 | out: |
390 | of_node_put(node: companion); |
391 | return ret; |
392 | } |
393 | |
394 | static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev) |
395 | { |
396 | struct imx8qxp_pxl2dpi *p2d; |
397 | struct device *dev = &pdev->dev; |
398 | struct device_node *np = dev->of_node; |
399 | int ret; |
400 | |
401 | p2d = devm_kzalloc(dev, size: sizeof(*p2d), GFP_KERNEL); |
402 | if (!p2d) |
403 | return -ENOMEM; |
404 | |
405 | p2d->regmap = syscon_node_to_regmap(np: np->parent); |
406 | if (IS_ERR(ptr: p2d->regmap)) { |
407 | ret = PTR_ERR(ptr: p2d->regmap); |
408 | if (ret != -EPROBE_DEFER) |
409 | DRM_DEV_ERROR(dev, "failed to get regmap: %d\n" , ret); |
410 | return ret; |
411 | } |
412 | |
413 | ret = imx_scu_get_handle(ipc: &p2d->ipc_handle); |
414 | if (ret) { |
415 | if (ret != -EPROBE_DEFER) |
416 | DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n" , |
417 | ret); |
418 | return ret; |
419 | } |
420 | |
421 | p2d->dev = dev; |
422 | |
423 | ret = of_property_read_u32(np, propname: "fsl,sc-resource" , out_value: &p2d->sc_resource); |
424 | if (ret) { |
425 | DRM_DEV_ERROR(dev, "failed to get SC resource %d\n" , ret); |
426 | return ret; |
427 | } |
428 | |
429 | p2d->next_bridge = imx8qxp_pxl2dpi_find_next_bridge(p2d); |
430 | if (IS_ERR(ptr: p2d->next_bridge)) { |
431 | ret = PTR_ERR(ptr: p2d->next_bridge); |
432 | if (ret != -EPROBE_DEFER) |
433 | DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n" , |
434 | ret); |
435 | return ret; |
436 | } |
437 | |
438 | ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d); |
439 | if (ret) |
440 | return ret; |
441 | |
442 | ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d); |
443 | if (ret) |
444 | return ret; |
445 | |
446 | platform_set_drvdata(pdev, data: p2d); |
447 | pm_runtime_enable(dev); |
448 | |
449 | p2d->bridge.driver_private = p2d; |
450 | p2d->bridge.funcs = &imx8qxp_pxl2dpi_bridge_funcs; |
451 | p2d->bridge.of_node = np; |
452 | |
453 | drm_bridge_add(bridge: &p2d->bridge); |
454 | |
455 | return ret; |
456 | } |
457 | |
458 | static void imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev) |
459 | { |
460 | struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev); |
461 | |
462 | drm_bridge_remove(bridge: &p2d->bridge); |
463 | |
464 | pm_runtime_disable(dev: &pdev->dev); |
465 | } |
466 | |
467 | static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = { |
468 | { .compatible = "fsl,imx8qxp-pxl2dpi" , }, |
469 | { /* sentinel */ } |
470 | }; |
471 | MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids); |
472 | |
473 | static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = { |
474 | .probe = imx8qxp_pxl2dpi_bridge_probe, |
475 | .remove_new = imx8qxp_pxl2dpi_bridge_remove, |
476 | .driver = { |
477 | .of_match_table = imx8qxp_pxl2dpi_dt_ids, |
478 | .name = DRIVER_NAME, |
479 | }, |
480 | }; |
481 | module_platform_driver(imx8qxp_pxl2dpi_bridge_driver); |
482 | |
483 | MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver" ); |
484 | MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>" ); |
485 | MODULE_LICENSE("GPL v2" ); |
486 | MODULE_ALIAS("platform:" DRIVER_NAME); |
487 | |