1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Copyright 2020 NXP |
5 | */ |
6 | |
7 | #include <linux/bitfield.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | #include <linux/media-bus-format.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_graph.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_runtime.h> |
17 | |
18 | #include <drm/drm_atomic_state_helper.h> |
19 | #include <drm/drm_bridge.h> |
20 | #include <drm/drm_print.h> |
21 | |
22 | #define PC_CTRL_REG 0x0 |
23 | #define PC_COMBINE_ENABLE BIT(0) |
24 | #define PC_DISP_BYPASS(n) BIT(1 + 21 * (n)) |
25 | #define PC_DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n)) |
26 | #define PC_DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n) |
27 | #define PC_DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n)) |
28 | #define PC_DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n) |
29 | #define PC_DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n)) |
30 | #define PC_DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n) |
31 | #define PC_VSYNC_MASK_ENABLE BIT(5) |
32 | #define PC_SKIP_MODE BIT(6) |
33 | #define PC_SKIP_NUMBER_MASK GENMASK(12, 7) |
34 | #define PC_SKIP_NUMBER(n) FIELD_PREP(PC_SKIP_NUMBER_MASK, (n)) |
35 | #define PC_DISP0_PIX_DATA_FORMAT_MASK GENMASK(18, 16) |
36 | #define PC_DISP0_PIX_DATA_FORMAT(fmt) \ |
37 | FIELD_PREP(PC_DISP0_PIX_DATA_FORMAT_MASK, (fmt)) |
38 | #define PC_DISP1_PIX_DATA_FORMAT_MASK GENMASK(21, 19) |
39 | #define PC_DISP1_PIX_DATA_FORMAT(fmt) \ |
40 | FIELD_PREP(PC_DISP1_PIX_DATA_FORMAT_MASK, (fmt)) |
41 | |
42 | #define PC_SW_RESET_REG 0x20 |
43 | #define PC_SW_RESET_N BIT(0) |
44 | #define PC_DISP_SW_RESET_N(n) BIT(1 + (n)) |
45 | #define PC_FULL_RESET_N (PC_SW_RESET_N | \ |
46 | PC_DISP_SW_RESET_N(0) | \ |
47 | PC_DISP_SW_RESET_N(1)) |
48 | |
49 | #define PC_REG_SET 0x4 |
50 | #define PC_REG_CLR 0x8 |
51 | |
52 | #define DRIVER_NAME "imx8qxp-pixel-combiner" |
53 | |
54 | enum imx8qxp_pc_pix_data_format { |
55 | RGB, |
56 | YUV444, |
57 | YUV422, |
58 | SPLIT_RGB, |
59 | }; |
60 | |
61 | struct imx8qxp_pc_channel { |
62 | struct drm_bridge bridge; |
63 | struct drm_bridge *next_bridge; |
64 | struct imx8qxp_pc *pc; |
65 | unsigned int stream_id; |
66 | bool is_available; |
67 | }; |
68 | |
69 | struct imx8qxp_pc { |
70 | struct device *dev; |
71 | struct imx8qxp_pc_channel ch[2]; |
72 | struct clk *clk_apb; |
73 | void __iomem *base; |
74 | }; |
75 | |
76 | static inline u32 imx8qxp_pc_read(struct imx8qxp_pc *pc, unsigned int offset) |
77 | { |
78 | return readl(addr: pc->base + offset); |
79 | } |
80 | |
81 | static inline void |
82 | imx8qxp_pc_write(struct imx8qxp_pc *pc, unsigned int offset, u32 value) |
83 | { |
84 | writel(val: value, addr: pc->base + offset); |
85 | } |
86 | |
87 | static inline void |
88 | imx8qxp_pc_write_set(struct imx8qxp_pc *pc, unsigned int offset, u32 value) |
89 | { |
90 | imx8qxp_pc_write(pc, offset: offset + PC_REG_SET, value); |
91 | } |
92 | |
93 | static inline void |
94 | imx8qxp_pc_write_clr(struct imx8qxp_pc *pc, unsigned int offset, u32 value) |
95 | { |
96 | imx8qxp_pc_write(pc, offset: offset + PC_REG_CLR, value); |
97 | } |
98 | |
99 | static enum drm_mode_status |
100 | imx8qxp_pc_bridge_mode_valid(struct drm_bridge *bridge, |
101 | const struct drm_display_info *info, |
102 | const struct drm_display_mode *mode) |
103 | { |
104 | if (mode->hdisplay > 2560) |
105 | return MODE_BAD_HVALUE; |
106 | |
107 | return MODE_OK; |
108 | } |
109 | |
110 | static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge, |
111 | enum drm_bridge_attach_flags flags) |
112 | { |
113 | struct imx8qxp_pc_channel *ch = bridge->driver_private; |
114 | struct imx8qxp_pc *pc = ch->pc; |
115 | |
116 | if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { |
117 | DRM_DEV_ERROR(pc->dev, |
118 | "do not support creating a drm_connector\n" ); |
119 | return -EINVAL; |
120 | } |
121 | |
122 | if (!bridge->encoder) { |
123 | DRM_DEV_ERROR(pc->dev, "missing encoder\n" ); |
124 | return -ENODEV; |
125 | } |
126 | |
127 | return drm_bridge_attach(encoder: bridge->encoder, |
128 | bridge: ch->next_bridge, previous: bridge, |
129 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
130 | } |
131 | |
132 | static void |
133 | imx8qxp_pc_bridge_mode_set(struct drm_bridge *bridge, |
134 | const struct drm_display_mode *mode, |
135 | const struct drm_display_mode *adjusted_mode) |
136 | { |
137 | struct imx8qxp_pc_channel *ch = bridge->driver_private; |
138 | struct imx8qxp_pc *pc = ch->pc; |
139 | u32 val; |
140 | int ret; |
141 | |
142 | ret = pm_runtime_get_sync(dev: pc->dev); |
143 | if (ret < 0) |
144 | DRM_DEV_ERROR(pc->dev, |
145 | "failed to get runtime PM sync: %d\n" , ret); |
146 | |
147 | ret = clk_prepare_enable(clk: pc->clk_apb); |
148 | if (ret) |
149 | DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n" , |
150 | __func__, ret); |
151 | |
152 | /* HSYNC to pixel link is active low. */ |
153 | imx8qxp_pc_write_clr(pc, PC_CTRL_REG, |
154 | PC_DISP_HSYNC_POLARITY(ch->stream_id)); |
155 | |
156 | /* VSYNC to pixel link is active low. */ |
157 | imx8qxp_pc_write_clr(pc, PC_CTRL_REG, |
158 | PC_DISP_VSYNC_POLARITY(ch->stream_id)); |
159 | |
160 | /* Data enable to pixel link is active high. */ |
161 | imx8qxp_pc_write_set(pc, PC_CTRL_REG, |
162 | PC_DISP_DVALID_POLARITY(ch->stream_id)); |
163 | |
164 | /* Mask the first frame output which may be incomplete. */ |
165 | imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_VSYNC_MASK_ENABLE); |
166 | |
167 | /* Only support RGB currently. */ |
168 | val = imx8qxp_pc_read(pc, PC_CTRL_REG); |
169 | if (ch->stream_id == 0) { |
170 | val &= ~PC_DISP0_PIX_DATA_FORMAT_MASK; |
171 | val |= PC_DISP0_PIX_DATA_FORMAT(RGB); |
172 | } else { |
173 | val &= ~PC_DISP1_PIX_DATA_FORMAT_MASK; |
174 | val |= PC_DISP1_PIX_DATA_FORMAT(RGB); |
175 | } |
176 | imx8qxp_pc_write(pc, PC_CTRL_REG, value: val); |
177 | |
178 | /* Only support bypass mode currently. */ |
179 | imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_DISP_BYPASS(ch->stream_id)); |
180 | |
181 | clk_disable_unprepare(clk: pc->clk_apb); |
182 | } |
183 | |
184 | static void |
185 | imx8qxp_pc_bridge_atomic_disable(struct drm_bridge *bridge, |
186 | struct drm_bridge_state *old_bridge_state) |
187 | { |
188 | struct imx8qxp_pc_channel *ch = bridge->driver_private; |
189 | struct imx8qxp_pc *pc = ch->pc; |
190 | int ret; |
191 | |
192 | ret = pm_runtime_put(dev: pc->dev); |
193 | if (ret < 0) |
194 | DRM_DEV_ERROR(pc->dev, "failed to put runtime PM: %d\n" , ret); |
195 | } |
196 | |
197 | static const u32 imx8qxp_pc_bus_output_fmts[] = { |
198 | MEDIA_BUS_FMT_RGB888_1X36_CPADLO, |
199 | MEDIA_BUS_FMT_RGB666_1X36_CPADLO, |
200 | }; |
201 | |
202 | static bool imx8qxp_pc_bus_output_fmt_supported(u32 fmt) |
203 | { |
204 | int i; |
205 | |
206 | for (i = 0; i < ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); i++) { |
207 | if (imx8qxp_pc_bus_output_fmts[i] == fmt) |
208 | return true; |
209 | } |
210 | |
211 | return false; |
212 | } |
213 | |
214 | static u32 * |
215 | imx8qxp_pc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, |
216 | struct drm_bridge_state *bridge_state, |
217 | struct drm_crtc_state *crtc_state, |
218 | struct drm_connector_state *conn_state, |
219 | u32 output_fmt, |
220 | unsigned int *num_input_fmts) |
221 | { |
222 | u32 *input_fmts; |
223 | |
224 | if (!imx8qxp_pc_bus_output_fmt_supported(fmt: output_fmt)) |
225 | return NULL; |
226 | |
227 | *num_input_fmts = 1; |
228 | |
229 | input_fmts = kmalloc(size: sizeof(*input_fmts), GFP_KERNEL); |
230 | if (!input_fmts) |
231 | return NULL; |
232 | |
233 | switch (output_fmt) { |
234 | case MEDIA_BUS_FMT_RGB888_1X36_CPADLO: |
235 | input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X30_CPADLO; |
236 | break; |
237 | case MEDIA_BUS_FMT_RGB666_1X36_CPADLO: |
238 | input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X30_CPADLO; |
239 | break; |
240 | default: |
241 | kfree(objp: input_fmts); |
242 | input_fmts = NULL; |
243 | break; |
244 | } |
245 | |
246 | return input_fmts; |
247 | } |
248 | |
249 | static u32 * |
250 | imx8qxp_pc_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, |
251 | struct drm_bridge_state *bridge_state, |
252 | struct drm_crtc_state *crtc_state, |
253 | struct drm_connector_state *conn_state, |
254 | unsigned int *num_output_fmts) |
255 | { |
256 | *num_output_fmts = ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); |
257 | return kmemdup(p: imx8qxp_pc_bus_output_fmts, |
258 | size: sizeof(imx8qxp_pc_bus_output_fmts), GFP_KERNEL); |
259 | } |
260 | |
261 | static const struct drm_bridge_funcs imx8qxp_pc_bridge_funcs = { |
262 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
263 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
264 | .atomic_reset = drm_atomic_helper_bridge_reset, |
265 | .mode_valid = imx8qxp_pc_bridge_mode_valid, |
266 | .attach = imx8qxp_pc_bridge_attach, |
267 | .mode_set = imx8qxp_pc_bridge_mode_set, |
268 | .atomic_disable = imx8qxp_pc_bridge_atomic_disable, |
269 | .atomic_get_input_bus_fmts = |
270 | imx8qxp_pc_bridge_atomic_get_input_bus_fmts, |
271 | .atomic_get_output_bus_fmts = |
272 | imx8qxp_pc_bridge_atomic_get_output_bus_fmts, |
273 | }; |
274 | |
275 | static int imx8qxp_pc_bridge_probe(struct platform_device *pdev) |
276 | { |
277 | struct imx8qxp_pc *pc; |
278 | struct imx8qxp_pc_channel *ch; |
279 | struct device *dev = &pdev->dev; |
280 | struct device_node *np = dev->of_node; |
281 | struct device_node *child, *remote; |
282 | u32 i; |
283 | int ret; |
284 | |
285 | pc = devm_kzalloc(dev, size: sizeof(*pc), GFP_KERNEL); |
286 | if (!pc) |
287 | return -ENOMEM; |
288 | |
289 | pc->base = devm_platform_ioremap_resource(pdev, index: 0); |
290 | if (IS_ERR(ptr: pc->base)) |
291 | return PTR_ERR(ptr: pc->base); |
292 | |
293 | pc->dev = dev; |
294 | |
295 | pc->clk_apb = devm_clk_get(dev, id: "apb" ); |
296 | if (IS_ERR(ptr: pc->clk_apb)) { |
297 | ret = PTR_ERR(ptr: pc->clk_apb); |
298 | if (ret != -EPROBE_DEFER) |
299 | DRM_DEV_ERROR(dev, "failed to get apb clock: %d\n" , ret); |
300 | return ret; |
301 | } |
302 | |
303 | platform_set_drvdata(pdev, data: pc); |
304 | pm_runtime_enable(dev); |
305 | |
306 | for_each_available_child_of_node(np, child) { |
307 | ret = of_property_read_u32(np: child, propname: "reg" , out_value: &i); |
308 | if (ret || i > 1) { |
309 | ret = -EINVAL; |
310 | DRM_DEV_ERROR(dev, |
311 | "invalid channel(%u) node address\n" , i); |
312 | goto free_child; |
313 | } |
314 | |
315 | ch = &pc->ch[i]; |
316 | ch->pc = pc; |
317 | ch->stream_id = i; |
318 | |
319 | remote = of_graph_get_remote_node(node: child, port: 1, endpoint: 0); |
320 | if (!remote) { |
321 | ret = -ENODEV; |
322 | DRM_DEV_ERROR(dev, |
323 | "channel%u failed to get port1's remote node: %d\n" , |
324 | i, ret); |
325 | goto free_child; |
326 | } |
327 | |
328 | ch->next_bridge = of_drm_find_bridge(np: remote); |
329 | if (!ch->next_bridge) { |
330 | of_node_put(node: remote); |
331 | ret = -EPROBE_DEFER; |
332 | DRM_DEV_DEBUG_DRIVER(dev, |
333 | "channel%u failed to find next bridge: %d\n" , |
334 | i, ret); |
335 | goto free_child; |
336 | } |
337 | |
338 | of_node_put(node: remote); |
339 | |
340 | ch->bridge.driver_private = ch; |
341 | ch->bridge.funcs = &imx8qxp_pc_bridge_funcs; |
342 | ch->bridge.of_node = child; |
343 | ch->is_available = true; |
344 | |
345 | drm_bridge_add(bridge: &ch->bridge); |
346 | } |
347 | |
348 | return 0; |
349 | |
350 | free_child: |
351 | of_node_put(node: child); |
352 | |
353 | if (i == 1 && pc->ch[0].next_bridge) |
354 | drm_bridge_remove(bridge: &pc->ch[0].bridge); |
355 | |
356 | pm_runtime_disable(dev); |
357 | return ret; |
358 | } |
359 | |
360 | static void imx8qxp_pc_bridge_remove(struct platform_device *pdev) |
361 | { |
362 | struct imx8qxp_pc *pc = platform_get_drvdata(pdev); |
363 | struct imx8qxp_pc_channel *ch; |
364 | int i; |
365 | |
366 | for (i = 0; i < 2; i++) { |
367 | ch = &pc->ch[i]; |
368 | |
369 | if (!ch->is_available) |
370 | continue; |
371 | |
372 | drm_bridge_remove(bridge: &ch->bridge); |
373 | ch->is_available = false; |
374 | } |
375 | |
376 | pm_runtime_disable(dev: &pdev->dev); |
377 | } |
378 | |
379 | static int __maybe_unused imx8qxp_pc_runtime_suspend(struct device *dev) |
380 | { |
381 | struct platform_device *pdev = to_platform_device(dev); |
382 | struct imx8qxp_pc *pc = platform_get_drvdata(pdev); |
383 | int ret; |
384 | |
385 | ret = clk_prepare_enable(clk: pc->clk_apb); |
386 | if (ret) |
387 | DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n" , |
388 | __func__, ret); |
389 | |
390 | /* Disable pixel combiner by full reset. */ |
391 | imx8qxp_pc_write_clr(pc, PC_SW_RESET_REG, PC_FULL_RESET_N); |
392 | |
393 | clk_disable_unprepare(clk: pc->clk_apb); |
394 | |
395 | /* Ensure the reset takes effect. */ |
396 | usleep_range(min: 10, max: 20); |
397 | |
398 | return ret; |
399 | } |
400 | |
401 | static int __maybe_unused imx8qxp_pc_runtime_resume(struct device *dev) |
402 | { |
403 | struct platform_device *pdev = to_platform_device(dev); |
404 | struct imx8qxp_pc *pc = platform_get_drvdata(pdev); |
405 | int ret; |
406 | |
407 | ret = clk_prepare_enable(clk: pc->clk_apb); |
408 | if (ret) { |
409 | DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n" , |
410 | __func__, ret); |
411 | return ret; |
412 | } |
413 | |
414 | /* out of reset */ |
415 | imx8qxp_pc_write_set(pc, PC_SW_RESET_REG, PC_FULL_RESET_N); |
416 | |
417 | clk_disable_unprepare(clk: pc->clk_apb); |
418 | |
419 | return ret; |
420 | } |
421 | |
422 | static const struct dev_pm_ops imx8qxp_pc_pm_ops = { |
423 | SET_RUNTIME_PM_OPS(imx8qxp_pc_runtime_suspend, |
424 | imx8qxp_pc_runtime_resume, NULL) |
425 | }; |
426 | |
427 | static const struct of_device_id imx8qxp_pc_dt_ids[] = { |
428 | { .compatible = "fsl,imx8qm-pixel-combiner" , }, |
429 | { .compatible = "fsl,imx8qxp-pixel-combiner" , }, |
430 | { /* sentinel */ } |
431 | }; |
432 | MODULE_DEVICE_TABLE(of, imx8qxp_pc_dt_ids); |
433 | |
434 | static struct platform_driver imx8qxp_pc_bridge_driver = { |
435 | .probe = imx8qxp_pc_bridge_probe, |
436 | .remove_new = imx8qxp_pc_bridge_remove, |
437 | .driver = { |
438 | .pm = &imx8qxp_pc_pm_ops, |
439 | .name = DRIVER_NAME, |
440 | .of_match_table = imx8qxp_pc_dt_ids, |
441 | }, |
442 | }; |
443 | module_platform_driver(imx8qxp_pc_bridge_driver); |
444 | |
445 | MODULE_DESCRIPTION("i.MX8QM/QXP pixel combiner bridge driver" ); |
446 | MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>" ); |
447 | MODULE_LICENSE("GPL v2" ); |
448 | MODULE_ALIAS("platform:" DRIVER_NAME); |
449 | |