1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Copyright 2020,2022 NXP |
5 | */ |
6 | |
7 | #include <linux/firmware/imx/svc/misc.h> |
8 | #include <linux/media-bus-format.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_graph.h> |
12 | #include <linux/platform_device.h> |
13 | |
14 | #include <drm/drm_atomic_state_helper.h> |
15 | #include <drm/drm_bridge.h> |
16 | #include <drm/drm_print.h> |
17 | |
18 | #include <dt-bindings/firmware/imx/rsrc.h> |
19 | |
20 | #define DRIVER_NAME "imx8qxp-display-pixel-link" |
21 | #define PL_MAX_MST_ADDR 3 |
22 | #define PL_MAX_NEXT_BRIDGES 2 |
23 | |
24 | struct imx8qxp_pixel_link { |
25 | struct drm_bridge bridge; |
26 | struct drm_bridge *next_bridge; |
27 | struct device *dev; |
28 | struct imx_sc_ipc *ipc_handle; |
29 | u8 stream_id; |
30 | u8 dc_id; |
31 | u32 sink_rsc; |
32 | u32 mst_addr; |
33 | u8 mst_addr_ctrl; |
34 | u8 mst_en_ctrl; |
35 | u8 mst_vld_ctrl; |
36 | u8 sync_ctrl; |
37 | }; |
38 | |
39 | static void imx8qxp_pixel_link_enable_mst_en(struct imx8qxp_pixel_link *pl) |
40 | { |
41 | int ret; |
42 | |
43 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
44 | ctrl: pl->mst_en_ctrl, val: true); |
45 | if (ret) |
46 | DRM_DEV_ERROR(pl->dev, |
47 | "failed to enable DC%u stream%u pixel link mst_en: %d\n" , |
48 | pl->dc_id, pl->stream_id, ret); |
49 | } |
50 | |
51 | static void imx8qxp_pixel_link_enable_mst_vld(struct imx8qxp_pixel_link *pl) |
52 | { |
53 | int ret; |
54 | |
55 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
56 | ctrl: pl->mst_vld_ctrl, val: true); |
57 | if (ret) |
58 | DRM_DEV_ERROR(pl->dev, |
59 | "failed to enable DC%u stream%u pixel link mst_vld: %d\n" , |
60 | pl->dc_id, pl->stream_id, ret); |
61 | } |
62 | |
63 | static void imx8qxp_pixel_link_enable_sync(struct imx8qxp_pixel_link *pl) |
64 | { |
65 | int ret; |
66 | |
67 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
68 | ctrl: pl->sync_ctrl, val: true); |
69 | if (ret) |
70 | DRM_DEV_ERROR(pl->dev, |
71 | "failed to enable DC%u stream%u pixel link sync: %d\n" , |
72 | pl->dc_id, pl->stream_id, ret); |
73 | } |
74 | |
75 | static int imx8qxp_pixel_link_disable_mst_en(struct imx8qxp_pixel_link *pl) |
76 | { |
77 | int ret; |
78 | |
79 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
80 | ctrl: pl->mst_en_ctrl, val: false); |
81 | if (ret) |
82 | DRM_DEV_ERROR(pl->dev, |
83 | "failed to disable DC%u stream%u pixel link mst_en: %d\n" , |
84 | pl->dc_id, pl->stream_id, ret); |
85 | |
86 | return ret; |
87 | } |
88 | |
89 | static int imx8qxp_pixel_link_disable_mst_vld(struct imx8qxp_pixel_link *pl) |
90 | { |
91 | int ret; |
92 | |
93 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
94 | ctrl: pl->mst_vld_ctrl, val: false); |
95 | if (ret) |
96 | DRM_DEV_ERROR(pl->dev, |
97 | "failed to disable DC%u stream%u pixel link mst_vld: %d\n" , |
98 | pl->dc_id, pl->stream_id, ret); |
99 | |
100 | return ret; |
101 | } |
102 | |
103 | static int imx8qxp_pixel_link_disable_sync(struct imx8qxp_pixel_link *pl) |
104 | { |
105 | int ret; |
106 | |
107 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, resource: pl->sink_rsc, |
108 | ctrl: pl->sync_ctrl, val: false); |
109 | if (ret) |
110 | DRM_DEV_ERROR(pl->dev, |
111 | "failed to disable DC%u stream%u pixel link sync: %d\n" , |
112 | pl->dc_id, pl->stream_id, ret); |
113 | |
114 | return ret; |
115 | } |
116 | |
117 | static void imx8qxp_pixel_link_set_mst_addr(struct imx8qxp_pixel_link *pl) |
118 | { |
119 | int ret; |
120 | |
121 | ret = imx_sc_misc_set_control(ipc: pl->ipc_handle, |
122 | resource: pl->sink_rsc, ctrl: pl->mst_addr_ctrl, |
123 | val: pl->mst_addr); |
124 | if (ret) |
125 | DRM_DEV_ERROR(pl->dev, |
126 | "failed to set DC%u stream%u pixel link mst addr(%u): %d\n" , |
127 | pl->dc_id, pl->stream_id, pl->mst_addr, ret); |
128 | } |
129 | |
130 | static int imx8qxp_pixel_link_bridge_attach(struct drm_bridge *bridge, |
131 | enum drm_bridge_attach_flags flags) |
132 | { |
133 | struct imx8qxp_pixel_link *pl = bridge->driver_private; |
134 | |
135 | if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { |
136 | DRM_DEV_ERROR(pl->dev, |
137 | "do not support creating a drm_connector\n" ); |
138 | return -EINVAL; |
139 | } |
140 | |
141 | if (!bridge->encoder) { |
142 | DRM_DEV_ERROR(pl->dev, "missing encoder\n" ); |
143 | return -ENODEV; |
144 | } |
145 | |
146 | return drm_bridge_attach(encoder: bridge->encoder, |
147 | bridge: pl->next_bridge, previous: bridge, |
148 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
149 | } |
150 | |
151 | static void |
152 | imx8qxp_pixel_link_bridge_mode_set(struct drm_bridge *bridge, |
153 | const struct drm_display_mode *mode, |
154 | const struct drm_display_mode *adjusted_mode) |
155 | { |
156 | struct imx8qxp_pixel_link *pl = bridge->driver_private; |
157 | |
158 | imx8qxp_pixel_link_set_mst_addr(pl); |
159 | } |
160 | |
161 | static void |
162 | imx8qxp_pixel_link_bridge_atomic_enable(struct drm_bridge *bridge, |
163 | struct drm_bridge_state *old_bridge_state) |
164 | { |
165 | struct imx8qxp_pixel_link *pl = bridge->driver_private; |
166 | |
167 | imx8qxp_pixel_link_enable_mst_en(pl); |
168 | imx8qxp_pixel_link_enable_mst_vld(pl); |
169 | imx8qxp_pixel_link_enable_sync(pl); |
170 | } |
171 | |
172 | static void |
173 | imx8qxp_pixel_link_bridge_atomic_disable(struct drm_bridge *bridge, |
174 | struct drm_bridge_state *old_bridge_state) |
175 | { |
176 | struct imx8qxp_pixel_link *pl = bridge->driver_private; |
177 | |
178 | imx8qxp_pixel_link_disable_mst_en(pl); |
179 | imx8qxp_pixel_link_disable_mst_vld(pl); |
180 | imx8qxp_pixel_link_disable_sync(pl); |
181 | } |
182 | |
183 | static const u32 imx8qxp_pixel_link_bus_output_fmts[] = { |
184 | MEDIA_BUS_FMT_RGB888_1X36_CPADLO, |
185 | MEDIA_BUS_FMT_RGB666_1X36_CPADLO, |
186 | }; |
187 | |
188 | static bool imx8qxp_pixel_link_bus_output_fmt_supported(u32 fmt) |
189 | { |
190 | int i; |
191 | |
192 | for (i = 0; i < ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts); i++) { |
193 | if (imx8qxp_pixel_link_bus_output_fmts[i] == fmt) |
194 | return true; |
195 | } |
196 | |
197 | return false; |
198 | } |
199 | |
200 | static u32 * |
201 | imx8qxp_pixel_link_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, |
202 | struct drm_bridge_state *bridge_state, |
203 | struct drm_crtc_state *crtc_state, |
204 | struct drm_connector_state *conn_state, |
205 | u32 output_fmt, |
206 | unsigned int *num_input_fmts) |
207 | { |
208 | u32 *input_fmts; |
209 | |
210 | if (!imx8qxp_pixel_link_bus_output_fmt_supported(fmt: output_fmt)) |
211 | return NULL; |
212 | |
213 | *num_input_fmts = 1; |
214 | |
215 | input_fmts = kmalloc(size: sizeof(*input_fmts), GFP_KERNEL); |
216 | if (!input_fmts) |
217 | return NULL; |
218 | |
219 | input_fmts[0] = output_fmt; |
220 | |
221 | return input_fmts; |
222 | } |
223 | |
224 | static u32 * |
225 | imx8qxp_pixel_link_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, |
226 | struct drm_bridge_state *bridge_state, |
227 | struct drm_crtc_state *crtc_state, |
228 | struct drm_connector_state *conn_state, |
229 | unsigned int *num_output_fmts) |
230 | { |
231 | *num_output_fmts = ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts); |
232 | return kmemdup(p: imx8qxp_pixel_link_bus_output_fmts, |
233 | size: sizeof(imx8qxp_pixel_link_bus_output_fmts), GFP_KERNEL); |
234 | } |
235 | |
236 | static const struct drm_bridge_funcs imx8qxp_pixel_link_bridge_funcs = { |
237 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
238 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
239 | .atomic_reset = drm_atomic_helper_bridge_reset, |
240 | .attach = imx8qxp_pixel_link_bridge_attach, |
241 | .mode_set = imx8qxp_pixel_link_bridge_mode_set, |
242 | .atomic_enable = imx8qxp_pixel_link_bridge_atomic_enable, |
243 | .atomic_disable = imx8qxp_pixel_link_bridge_atomic_disable, |
244 | .atomic_get_input_bus_fmts = |
245 | imx8qxp_pixel_link_bridge_atomic_get_input_bus_fmts, |
246 | .atomic_get_output_bus_fmts = |
247 | imx8qxp_pixel_link_bridge_atomic_get_output_bus_fmts, |
248 | }; |
249 | |
250 | static int imx8qxp_pixel_link_disable_all_controls(struct imx8qxp_pixel_link *pl) |
251 | { |
252 | int ret; |
253 | |
254 | ret = imx8qxp_pixel_link_disable_mst_en(pl); |
255 | if (ret) |
256 | return ret; |
257 | |
258 | ret = imx8qxp_pixel_link_disable_mst_vld(pl); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | return imx8qxp_pixel_link_disable_sync(pl); |
263 | } |
264 | |
265 | static struct drm_bridge * |
266 | imx8qxp_pixel_link_find_next_bridge(struct imx8qxp_pixel_link *pl) |
267 | { |
268 | struct device_node *np = pl->dev->of_node; |
269 | struct device_node *port, *remote; |
270 | struct drm_bridge *next_bridge[PL_MAX_NEXT_BRIDGES]; |
271 | u32 port_id; |
272 | bool found_port = false; |
273 | int reg, ep_cnt = 0; |
274 | /* select the first next bridge by default */ |
275 | int bridge_sel = 0; |
276 | |
277 | for (port_id = 1; port_id <= PL_MAX_MST_ADDR + 1; port_id++) { |
278 | port = of_graph_get_port_by_id(node: np, id: port_id); |
279 | if (!port) |
280 | continue; |
281 | |
282 | if (of_device_is_available(device: port)) { |
283 | found_port = true; |
284 | of_node_put(node: port); |
285 | break; |
286 | } |
287 | |
288 | of_node_put(node: port); |
289 | } |
290 | |
291 | if (!found_port) { |
292 | DRM_DEV_ERROR(pl->dev, "no available output port\n" ); |
293 | return ERR_PTR(error: -ENODEV); |
294 | } |
295 | |
296 | for (reg = 0; reg < PL_MAX_NEXT_BRIDGES; reg++) { |
297 | remote = of_graph_get_remote_node(node: np, port: port_id, endpoint: reg); |
298 | if (!remote) |
299 | continue; |
300 | |
301 | if (!of_device_is_available(device: remote->parent)) { |
302 | DRM_DEV_DEBUG(pl->dev, |
303 | "port%u endpoint%u remote parent is not available\n" , |
304 | port_id, reg); |
305 | of_node_put(node: remote); |
306 | continue; |
307 | } |
308 | |
309 | next_bridge[ep_cnt] = of_drm_find_bridge(np: remote); |
310 | if (!next_bridge[ep_cnt]) { |
311 | of_node_put(node: remote); |
312 | return ERR_PTR(error: -EPROBE_DEFER); |
313 | } |
314 | |
315 | /* specially select the next bridge with companion PXL2DPI */ |
316 | if (of_property_present(np: remote, propname: "fsl,companion-pxl2dpi" )) |
317 | bridge_sel = ep_cnt; |
318 | |
319 | ep_cnt++; |
320 | |
321 | of_node_put(node: remote); |
322 | } |
323 | |
324 | pl->mst_addr = port_id - 1; |
325 | |
326 | return next_bridge[bridge_sel]; |
327 | } |
328 | |
329 | static int imx8qxp_pixel_link_bridge_probe(struct platform_device *pdev) |
330 | { |
331 | struct imx8qxp_pixel_link *pl; |
332 | struct device *dev = &pdev->dev; |
333 | struct device_node *np = dev->of_node; |
334 | int ret; |
335 | |
336 | pl = devm_kzalloc(dev, size: sizeof(*pl), GFP_KERNEL); |
337 | if (!pl) |
338 | return -ENOMEM; |
339 | |
340 | ret = imx_scu_get_handle(ipc: &pl->ipc_handle); |
341 | if (ret) { |
342 | if (ret != -EPROBE_DEFER) |
343 | DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n" , |
344 | ret); |
345 | return ret; |
346 | } |
347 | |
348 | ret = of_property_read_u8(np, propname: "fsl,dc-id" , out_value: &pl->dc_id); |
349 | if (ret) { |
350 | DRM_DEV_ERROR(dev, "failed to get DC index: %d\n" , ret); |
351 | return ret; |
352 | } |
353 | |
354 | ret = of_property_read_u8(np, propname: "fsl,dc-stream-id" , out_value: &pl->stream_id); |
355 | if (ret) { |
356 | DRM_DEV_ERROR(dev, "failed to get DC stream index: %d\n" , ret); |
357 | return ret; |
358 | } |
359 | |
360 | pl->dev = dev; |
361 | |
362 | pl->sink_rsc = pl->dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; |
363 | |
364 | if (pl->stream_id == 0) { |
365 | pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST1_ADDR; |
366 | pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST1_ENB; |
367 | pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST1_VLD; |
368 | pl->sync_ctrl = IMX_SC_C_SYNC_CTRL0; |
369 | } else { |
370 | pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST2_ADDR; |
371 | pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST2_ENB; |
372 | pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST2_VLD; |
373 | pl->sync_ctrl = IMX_SC_C_SYNC_CTRL1; |
374 | } |
375 | |
376 | /* disable all controls to POR default */ |
377 | ret = imx8qxp_pixel_link_disable_all_controls(pl); |
378 | if (ret) |
379 | return ret; |
380 | |
381 | pl->next_bridge = imx8qxp_pixel_link_find_next_bridge(pl); |
382 | if (IS_ERR(ptr: pl->next_bridge)) { |
383 | ret = PTR_ERR(ptr: pl->next_bridge); |
384 | if (ret != -EPROBE_DEFER) |
385 | DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n" , |
386 | ret); |
387 | return ret; |
388 | } |
389 | |
390 | platform_set_drvdata(pdev, data: pl); |
391 | |
392 | pl->bridge.driver_private = pl; |
393 | pl->bridge.funcs = &imx8qxp_pixel_link_bridge_funcs; |
394 | pl->bridge.of_node = np; |
395 | |
396 | drm_bridge_add(bridge: &pl->bridge); |
397 | |
398 | return ret; |
399 | } |
400 | |
401 | static void imx8qxp_pixel_link_bridge_remove(struct platform_device *pdev) |
402 | { |
403 | struct imx8qxp_pixel_link *pl = platform_get_drvdata(pdev); |
404 | |
405 | drm_bridge_remove(bridge: &pl->bridge); |
406 | } |
407 | |
408 | static const struct of_device_id imx8qxp_pixel_link_dt_ids[] = { |
409 | { .compatible = "fsl,imx8qm-dc-pixel-link" , }, |
410 | { .compatible = "fsl,imx8qxp-dc-pixel-link" , }, |
411 | { /* sentinel */ } |
412 | }; |
413 | MODULE_DEVICE_TABLE(of, imx8qxp_pixel_link_dt_ids); |
414 | |
415 | static struct platform_driver imx8qxp_pixel_link_bridge_driver = { |
416 | .probe = imx8qxp_pixel_link_bridge_probe, |
417 | .remove_new = imx8qxp_pixel_link_bridge_remove, |
418 | .driver = { |
419 | .of_match_table = imx8qxp_pixel_link_dt_ids, |
420 | .name = DRIVER_NAME, |
421 | }, |
422 | }; |
423 | module_platform_driver(imx8qxp_pixel_link_bridge_driver); |
424 | |
425 | MODULE_DESCRIPTION("i.MX8QXP/QM display pixel link bridge driver" ); |
426 | MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>" ); |
427 | MODULE_LICENSE("GPL v2" ); |
428 | MODULE_ALIAS("platform:" DRIVER_NAME); |
429 | |