1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Linus Walleij <linus.walleij@linaro.org> |
4 | * Parts of this file were based on the MCDE driver by Marcus Lorentzon |
5 | * (C) ST-Ericsson SA 2013 |
6 | */ |
7 | |
8 | /** |
9 | * DOC: ST-Ericsson MCDE Driver |
10 | * |
11 | * The MCDE (short for multi-channel display engine) is a graphics |
12 | * controller found in the Ux500 chipsets, such as NovaThor U8500. |
13 | * It was initially conceptualized by ST Microelectronics for the |
14 | * successor of the Nomadik line, STn8500 but productified in the |
15 | * ST-Ericsson U8500 where is was used for mass-market deployments |
16 | * in Android phones from Samsung and Sony Ericsson. |
17 | * |
18 | * It can do 1080p30 on SDTV CCIR656, DPI-2, DBI-2 or DSI for |
19 | * panels with or without frame buffering and can convert most |
20 | * input formats including most variants of RGB and YUV. |
21 | * |
22 | * The hardware has four display pipes, and the layout is a little |
23 | * bit like this:: |
24 | * |
25 | * Memory -> Overlay -> Channel -> FIFO -> 8 formatters -> DSI/DPI |
26 | * External 0..5 0..3 A,B, 6 x DSI bridge |
27 | * source 0..9 C0,C1 2 x DPI |
28 | * |
29 | * FIFOs A and B are for LCD and HDMI while FIFO CO/C1 are for |
30 | * panels with embedded buffer. |
31 | * 6 of the formatters are for DSI, 3 pairs for VID/CMD respectively. |
32 | * 2 of the formatters are for DPI. |
33 | * |
34 | * Behind the formatters are the DSI or DPI ports that route to |
35 | * the external pins of the chip. As there are 3 DSI ports and one |
36 | * DPI port, it is possible to configure up to 4 display pipelines |
37 | * (effectively using channels 0..3) for concurrent use. |
38 | * |
39 | * In the current DRM/KMS setup, we use one external source, one overlay, |
40 | * one FIFO and one formatter which we connect to the simple DMA framebuffer |
41 | * helpers. We then provide a bridge to the DSI port, and on the DSI port |
42 | * bridge we connect hang a panel bridge or other bridge. This may be subject |
43 | * to change as we exploit more of the hardware capabilities. |
44 | * |
45 | * TODO: |
46 | * |
47 | * - Enabled damaged rectangles using drm_plane_enable_fb_damage_clips() |
48 | * so we can selectively just transmit the damaged area to a |
49 | * command-only display. |
50 | * - Enable mixing of more planes, possibly at the cost of moving away |
51 | * from using the simple framebuffer pipeline. |
52 | * - Enable output to bridges such as the AV8100 HDMI encoder from |
53 | * the DSI bridge. |
54 | */ |
55 | |
56 | #include <linux/clk.h> |
57 | #include <linux/component.h> |
58 | #include <linux/dma-buf.h> |
59 | #include <linux/irq.h> |
60 | #include <linux/io.h> |
61 | #include <linux/module.h> |
62 | #include <linux/of_platform.h> |
63 | #include <linux/platform_device.h> |
64 | #include <linux/regulator/consumer.h> |
65 | #include <linux/slab.h> |
66 | #include <linux/delay.h> |
67 | |
68 | #include <drm/drm_atomic_helper.h> |
69 | #include <drm/drm_bridge.h> |
70 | #include <drm/drm_drv.h> |
71 | #include <drm/drm_fb_dma_helper.h> |
72 | #include <drm/drm_fbdev_dma.h> |
73 | #include <drm/drm_gem.h> |
74 | #include <drm/drm_gem_dma_helper.h> |
75 | #include <drm/drm_gem_framebuffer_helper.h> |
76 | #include <drm/drm_managed.h> |
77 | #include <drm/drm_of.h> |
78 | #include <drm/drm_probe_helper.h> |
79 | #include <drm/drm_panel.h> |
80 | #include <drm/drm_vblank.h> |
81 | |
82 | #include "mcde_drm.h" |
83 | |
84 | #define DRIVER_DESC "DRM module for MCDE" |
85 | |
86 | #define MCDE_PID 0x000001FC |
87 | #define MCDE_PID_METALFIX_VERSION_SHIFT 0 |
88 | #define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF |
89 | #define MCDE_PID_DEVELOPMENT_VERSION_SHIFT 8 |
90 | #define MCDE_PID_DEVELOPMENT_VERSION_MASK 0x0000FF00 |
91 | #define MCDE_PID_MINOR_VERSION_SHIFT 16 |
92 | #define MCDE_PID_MINOR_VERSION_MASK 0x00FF0000 |
93 | #define MCDE_PID_MAJOR_VERSION_SHIFT 24 |
94 | #define MCDE_PID_MAJOR_VERSION_MASK 0xFF000000 |
95 | |
96 | static const struct drm_mode_config_funcs mcde_mode_config_funcs = { |
97 | .fb_create = drm_gem_fb_create, |
98 | .atomic_check = drm_atomic_helper_check, |
99 | .atomic_commit = drm_atomic_helper_commit, |
100 | }; |
101 | |
102 | static const struct drm_mode_config_helper_funcs mcde_mode_config_helpers = { |
103 | /* |
104 | * Using this function is necessary to commit atomic updates |
105 | * that need the CRTC to be enabled before a commit, as is |
106 | * the case with e.g. DSI displays. |
107 | */ |
108 | .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, |
109 | }; |
110 | |
111 | static irqreturn_t mcde_irq(int irq, void *data) |
112 | { |
113 | struct mcde *mcde = data; |
114 | u32 val; |
115 | |
116 | val = readl(addr: mcde->regs + MCDE_MISERR); |
117 | |
118 | mcde_display_irq(mcde); |
119 | |
120 | if (val) |
121 | dev_info(mcde->dev, "some error IRQ\n" ); |
122 | writel(val, addr: mcde->regs + MCDE_RISERR); |
123 | |
124 | return IRQ_HANDLED; |
125 | } |
126 | |
127 | static int mcde_modeset_init(struct drm_device *drm) |
128 | { |
129 | struct drm_mode_config *mode_config; |
130 | struct mcde *mcde = to_mcde(drm); |
131 | int ret; |
132 | |
133 | /* |
134 | * If no other bridge was found, check if we have a DPI panel or |
135 | * any other bridge connected directly to the MCDE DPI output. |
136 | * If a DSI bridge is found, DSI will take precedence. |
137 | * |
138 | * TODO: more elaborate bridge selection if we have more than one |
139 | * thing attached to the system. |
140 | */ |
141 | if (!mcde->bridge) { |
142 | struct drm_panel *panel; |
143 | struct drm_bridge *bridge; |
144 | |
145 | ret = drm_of_find_panel_or_bridge(np: drm->dev->of_node, |
146 | port: 0, endpoint: 0, panel: &panel, bridge: &bridge); |
147 | if (ret) { |
148 | dev_err(drm->dev, |
149 | "Could not locate any output bridge or panel\n" ); |
150 | return ret; |
151 | } |
152 | if (panel) { |
153 | bridge = drm_panel_bridge_add_typed(panel, |
154 | DRM_MODE_CONNECTOR_DPI); |
155 | if (IS_ERR(ptr: bridge)) { |
156 | dev_err(drm->dev, |
157 | "Could not connect panel bridge\n" ); |
158 | return PTR_ERR(ptr: bridge); |
159 | } |
160 | } |
161 | mcde->dpi_output = true; |
162 | mcde->bridge = bridge; |
163 | mcde->flow_mode = MCDE_DPI_FORMATTER_FLOW; |
164 | } |
165 | |
166 | mode_config = &drm->mode_config; |
167 | mode_config->funcs = &mcde_mode_config_funcs; |
168 | mode_config->helper_private = &mcde_mode_config_helpers; |
169 | /* This hardware can do 1080p */ |
170 | mode_config->min_width = 1; |
171 | mode_config->max_width = 1920; |
172 | mode_config->min_height = 1; |
173 | mode_config->max_height = 1080; |
174 | |
175 | ret = drm_vblank_init(dev: drm, num_crtcs: 1); |
176 | if (ret) { |
177 | dev_err(drm->dev, "failed to init vblank\n" ); |
178 | return ret; |
179 | } |
180 | |
181 | ret = mcde_display_init(drm); |
182 | if (ret) { |
183 | dev_err(drm->dev, "failed to init display\n" ); |
184 | return ret; |
185 | } |
186 | |
187 | /* Attach the bridge. */ |
188 | ret = drm_simple_display_pipe_attach_bridge(pipe: &mcde->pipe, |
189 | bridge: mcde->bridge); |
190 | if (ret) { |
191 | dev_err(drm->dev, "failed to attach display output bridge\n" ); |
192 | return ret; |
193 | } |
194 | |
195 | drm_mode_config_reset(dev: drm); |
196 | drm_kms_helper_poll_init(dev: drm); |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | DEFINE_DRM_GEM_DMA_FOPS(drm_fops); |
202 | |
203 | static const struct drm_driver mcde_drm_driver = { |
204 | .driver_features = |
205 | DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
206 | .ioctls = NULL, |
207 | .fops = &drm_fops, |
208 | .name = "mcde" , |
209 | .desc = DRIVER_DESC, |
210 | .date = "20180529" , |
211 | .major = 1, |
212 | .minor = 0, |
213 | .patchlevel = 0, |
214 | DRM_GEM_DMA_DRIVER_OPS, |
215 | }; |
216 | |
217 | static int mcde_drm_bind(struct device *dev) |
218 | { |
219 | struct drm_device *drm = dev_get_drvdata(dev); |
220 | int ret; |
221 | |
222 | ret = drmm_mode_config_init(dev: drm); |
223 | if (ret) |
224 | return ret; |
225 | |
226 | ret = component_bind_all(parent: drm->dev, data: drm); |
227 | if (ret) { |
228 | dev_err(dev, "can't bind component devices\n" ); |
229 | return ret; |
230 | } |
231 | |
232 | ret = mcde_modeset_init(drm); |
233 | if (ret) |
234 | goto unbind; |
235 | |
236 | ret = drm_dev_register(dev: drm, flags: 0); |
237 | if (ret < 0) |
238 | goto unbind; |
239 | |
240 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: 32); |
241 | |
242 | return 0; |
243 | |
244 | unbind: |
245 | component_unbind_all(parent: drm->dev, data: drm); |
246 | return ret; |
247 | } |
248 | |
249 | static void mcde_drm_unbind(struct device *dev) |
250 | { |
251 | struct drm_device *drm = dev_get_drvdata(dev); |
252 | |
253 | drm_dev_unregister(dev: drm); |
254 | drm_atomic_helper_shutdown(dev: drm); |
255 | component_unbind_all(parent: drm->dev, data: drm); |
256 | } |
257 | |
258 | static const struct component_master_ops mcde_drm_comp_ops = { |
259 | .bind = mcde_drm_bind, |
260 | .unbind = mcde_drm_unbind, |
261 | }; |
262 | |
263 | static struct platform_driver *const mcde_component_drivers[] = { |
264 | &mcde_dsi_driver, |
265 | }; |
266 | |
267 | static int mcde_probe(struct platform_device *pdev) |
268 | { |
269 | struct device *dev = &pdev->dev; |
270 | struct drm_device *drm; |
271 | struct mcde *mcde; |
272 | struct component_match *match = NULL; |
273 | u32 pid; |
274 | int irq; |
275 | int ret; |
276 | int i; |
277 | |
278 | mcde = devm_drm_dev_alloc(dev, &mcde_drm_driver, struct mcde, drm); |
279 | if (IS_ERR(ptr: mcde)) |
280 | return PTR_ERR(ptr: mcde); |
281 | drm = &mcde->drm; |
282 | mcde->dev = dev; |
283 | platform_set_drvdata(pdev, data: drm); |
284 | |
285 | /* First obtain and turn on the main power */ |
286 | mcde->epod = devm_regulator_get(dev, id: "epod" ); |
287 | if (IS_ERR(ptr: mcde->epod)) { |
288 | ret = PTR_ERR(ptr: mcde->epod); |
289 | dev_err(dev, "can't get EPOD regulator\n" ); |
290 | return ret; |
291 | } |
292 | ret = regulator_enable(regulator: mcde->epod); |
293 | if (ret) { |
294 | dev_err(dev, "can't enable EPOD regulator\n" ); |
295 | return ret; |
296 | } |
297 | mcde->vana = devm_regulator_get(dev, id: "vana" ); |
298 | if (IS_ERR(ptr: mcde->vana)) { |
299 | ret = PTR_ERR(ptr: mcde->vana); |
300 | dev_err(dev, "can't get VANA regulator\n" ); |
301 | goto regulator_epod_off; |
302 | } |
303 | ret = regulator_enable(regulator: mcde->vana); |
304 | if (ret) { |
305 | dev_err(dev, "can't enable VANA regulator\n" ); |
306 | goto regulator_epod_off; |
307 | } |
308 | /* |
309 | * The vendor code uses ESRAM (onchip RAM) and need to activate |
310 | * the v-esram34 regulator, but we don't use that yet |
311 | */ |
312 | |
313 | /* Clock the silicon so we can access the registers */ |
314 | mcde->mcde_clk = devm_clk_get(dev, id: "mcde" ); |
315 | if (IS_ERR(ptr: mcde->mcde_clk)) { |
316 | dev_err(dev, "unable to get MCDE main clock\n" ); |
317 | ret = PTR_ERR(ptr: mcde->mcde_clk); |
318 | goto regulator_off; |
319 | } |
320 | ret = clk_prepare_enable(clk: mcde->mcde_clk); |
321 | if (ret) { |
322 | dev_err(dev, "failed to enable MCDE main clock\n" ); |
323 | goto regulator_off; |
324 | } |
325 | dev_info(dev, "MCDE clk rate %lu Hz\n" , clk_get_rate(mcde->mcde_clk)); |
326 | |
327 | mcde->lcd_clk = devm_clk_get(dev, id: "lcd" ); |
328 | if (IS_ERR(ptr: mcde->lcd_clk)) { |
329 | dev_err(dev, "unable to get LCD clock\n" ); |
330 | ret = PTR_ERR(ptr: mcde->lcd_clk); |
331 | goto clk_disable; |
332 | } |
333 | mcde->hdmi_clk = devm_clk_get(dev, id: "hdmi" ); |
334 | if (IS_ERR(ptr: mcde->hdmi_clk)) { |
335 | dev_err(dev, "unable to get HDMI clock\n" ); |
336 | ret = PTR_ERR(ptr: mcde->hdmi_clk); |
337 | goto clk_disable; |
338 | } |
339 | |
340 | mcde->regs = devm_platform_ioremap_resource(pdev, index: 0); |
341 | if (IS_ERR(ptr: mcde->regs)) { |
342 | dev_err(dev, "no MCDE regs\n" ); |
343 | ret = -EINVAL; |
344 | goto clk_disable; |
345 | } |
346 | |
347 | irq = platform_get_irq(pdev, 0); |
348 | if (irq < 0) { |
349 | ret = irq; |
350 | goto clk_disable; |
351 | } |
352 | |
353 | ret = devm_request_irq(dev, irq, handler: mcde_irq, irqflags: 0, devname: "mcde" , dev_id: mcde); |
354 | if (ret) { |
355 | dev_err(dev, "failed to request irq %d\n" , ret); |
356 | goto clk_disable; |
357 | } |
358 | |
359 | /* |
360 | * Check hardware revision, we only support U8500v2 version |
361 | * as this was the only version used for mass market deployment, |
362 | * but surely you can add more versions if you have them and |
363 | * need them. |
364 | */ |
365 | pid = readl(addr: mcde->regs + MCDE_PID); |
366 | dev_info(dev, "found MCDE HW revision %d.%d (dev %d, metal fix %d)\n" , |
367 | (pid & MCDE_PID_MAJOR_VERSION_MASK) |
368 | >> MCDE_PID_MAJOR_VERSION_SHIFT, |
369 | (pid & MCDE_PID_MINOR_VERSION_MASK) |
370 | >> MCDE_PID_MINOR_VERSION_SHIFT, |
371 | (pid & MCDE_PID_DEVELOPMENT_VERSION_MASK) |
372 | >> MCDE_PID_DEVELOPMENT_VERSION_SHIFT, |
373 | (pid & MCDE_PID_METALFIX_VERSION_MASK) |
374 | >> MCDE_PID_METALFIX_VERSION_SHIFT); |
375 | if (pid != 0x03000800) { |
376 | dev_err(dev, "unsupported hardware revision\n" ); |
377 | ret = -ENODEV; |
378 | goto clk_disable; |
379 | } |
380 | |
381 | /* Disable and clear any pending interrupts */ |
382 | mcde_display_disable_irqs(mcde); |
383 | writel(val: 0, addr: mcde->regs + MCDE_IMSCERR); |
384 | writel(val: 0xFFFFFFFF, addr: mcde->regs + MCDE_RISERR); |
385 | |
386 | /* Spawn child devices for the DSI ports */ |
387 | devm_of_platform_populate(dev); |
388 | |
389 | /* Create something that will match the subdrivers when we bind */ |
390 | for (i = 0; i < ARRAY_SIZE(mcde_component_drivers); i++) { |
391 | struct device_driver *drv = &mcde_component_drivers[i]->driver; |
392 | struct device *p = NULL, *d; |
393 | |
394 | while ((d = platform_find_device_by_driver(start: p, drv))) { |
395 | put_device(dev: p); |
396 | component_match_add(parent: dev, matchptr: &match, compare: component_compare_dev, compare_data: d); |
397 | p = d; |
398 | } |
399 | put_device(dev: p); |
400 | } |
401 | if (!match) { |
402 | dev_err(dev, "no matching components\n" ); |
403 | ret = -ENODEV; |
404 | goto clk_disable; |
405 | } |
406 | if (IS_ERR(ptr: match)) { |
407 | dev_err(dev, "could not create component match\n" ); |
408 | ret = PTR_ERR(ptr: match); |
409 | goto clk_disable; |
410 | } |
411 | |
412 | /* |
413 | * Perform an invasive reset of the MCDE and all blocks by |
414 | * cutting the power to the subsystem, then bring it back up |
415 | * later when we enable the display as a result of |
416 | * component_master_add_with_match(). |
417 | */ |
418 | ret = regulator_disable(regulator: mcde->epod); |
419 | if (ret) { |
420 | dev_err(dev, "can't disable EPOD regulator\n" ); |
421 | return ret; |
422 | } |
423 | /* Wait 50 ms so we are sure we cut the power */ |
424 | usleep_range(min: 50000, max: 70000); |
425 | |
426 | ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops, |
427 | match); |
428 | if (ret) { |
429 | dev_err(dev, "failed to add component master\n" ); |
430 | /* |
431 | * The EPOD regulator is already disabled at this point so some |
432 | * special errorpath code is needed |
433 | */ |
434 | clk_disable_unprepare(clk: mcde->mcde_clk); |
435 | regulator_disable(regulator: mcde->vana); |
436 | return ret; |
437 | } |
438 | |
439 | return 0; |
440 | |
441 | clk_disable: |
442 | clk_disable_unprepare(clk: mcde->mcde_clk); |
443 | regulator_off: |
444 | regulator_disable(regulator: mcde->vana); |
445 | regulator_epod_off: |
446 | regulator_disable(regulator: mcde->epod); |
447 | return ret; |
448 | |
449 | } |
450 | |
451 | static void mcde_remove(struct platform_device *pdev) |
452 | { |
453 | struct drm_device *drm = platform_get_drvdata(pdev); |
454 | struct mcde *mcde = to_mcde(drm); |
455 | |
456 | component_master_del(&pdev->dev, &mcde_drm_comp_ops); |
457 | clk_disable_unprepare(clk: mcde->mcde_clk); |
458 | regulator_disable(regulator: mcde->vana); |
459 | regulator_disable(regulator: mcde->epod); |
460 | } |
461 | |
462 | static void mcde_shutdown(struct platform_device *pdev) |
463 | { |
464 | struct drm_device *drm = platform_get_drvdata(pdev); |
465 | |
466 | if (drm->registered) |
467 | drm_atomic_helper_shutdown(dev: drm); |
468 | } |
469 | |
470 | static const struct of_device_id mcde_of_match[] = { |
471 | { |
472 | .compatible = "ste,mcde" , |
473 | }, |
474 | {}, |
475 | }; |
476 | |
477 | static struct platform_driver mcde_driver = { |
478 | .driver = { |
479 | .name = "mcde" , |
480 | .of_match_table = mcde_of_match, |
481 | }, |
482 | .probe = mcde_probe, |
483 | .remove_new = mcde_remove, |
484 | .shutdown = mcde_shutdown, |
485 | }; |
486 | |
487 | static struct platform_driver *const component_drivers[] = { |
488 | &mcde_dsi_driver, |
489 | }; |
490 | |
491 | static int __init mcde_drm_register(void) |
492 | { |
493 | int ret; |
494 | |
495 | if (drm_firmware_drivers_only()) |
496 | return -ENODEV; |
497 | |
498 | ret = platform_register_drivers(component_drivers, |
499 | ARRAY_SIZE(component_drivers)); |
500 | if (ret) |
501 | return ret; |
502 | |
503 | return platform_driver_register(&mcde_driver); |
504 | } |
505 | |
506 | static void __exit mcde_drm_unregister(void) |
507 | { |
508 | platform_unregister_drivers(drivers: component_drivers, |
509 | ARRAY_SIZE(component_drivers)); |
510 | platform_driver_unregister(&mcde_driver); |
511 | } |
512 | |
513 | module_init(mcde_drm_register); |
514 | module_exit(mcde_drm_unregister); |
515 | |
516 | MODULE_ALIAS("platform:mcde-drm" ); |
517 | MODULE_DESCRIPTION(DRIVER_DESC); |
518 | MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>" ); |
519 | MODULE_LICENSE("GPL" ); |
520 | |