1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2022 Marek Vasut <marex@denx.de> |
4 | * |
5 | * This code is based on drivers/gpu/drm/mxsfb/mxsfb* |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/dma-mapping.h> |
10 | #include <linux/io.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_runtime.h> |
16 | |
17 | #include <drm/drm_atomic_helper.h> |
18 | #include <drm/drm_bridge.h> |
19 | #include <drm/drm_drv.h> |
20 | #include <drm/drm_encoder.h> |
21 | #include <drm/drm_fbdev_dma.h> |
22 | #include <drm/drm_gem_dma_helper.h> |
23 | #include <drm/drm_gem_framebuffer_helper.h> |
24 | #include <drm/drm_mode_config.h> |
25 | #include <drm/drm_module.h> |
26 | #include <drm/drm_of.h> |
27 | #include <drm/drm_probe_helper.h> |
28 | #include <drm/drm_vblank.h> |
29 | |
30 | #include "lcdif_drv.h" |
31 | #include "lcdif_regs.h" |
32 | |
33 | static const struct drm_mode_config_funcs lcdif_mode_config_funcs = { |
34 | .fb_create = drm_gem_fb_create, |
35 | .atomic_check = drm_atomic_helper_check, |
36 | .atomic_commit = drm_atomic_helper_commit, |
37 | }; |
38 | |
39 | static const struct drm_mode_config_helper_funcs lcdif_mode_config_helpers = { |
40 | .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, |
41 | }; |
42 | |
43 | static const struct drm_encoder_funcs lcdif_encoder_funcs = { |
44 | .destroy = drm_encoder_cleanup, |
45 | }; |
46 | |
47 | static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif) |
48 | { |
49 | struct device *dev = lcdif->drm->dev; |
50 | struct device_node *ep; |
51 | struct drm_bridge *bridge; |
52 | int ret; |
53 | |
54 | for_each_endpoint_of_node(dev->of_node, ep) { |
55 | struct device_node *remote; |
56 | struct of_endpoint of_ep; |
57 | struct drm_encoder *encoder; |
58 | |
59 | remote = of_graph_get_remote_port_parent(node: ep); |
60 | if (!of_device_is_available(device: remote)) { |
61 | of_node_put(node: remote); |
62 | continue; |
63 | } |
64 | of_node_put(node: remote); |
65 | |
66 | ret = of_graph_parse_endpoint(node: ep, endpoint: &of_ep); |
67 | if (ret < 0) { |
68 | dev_err(dev, "Failed to parse endpoint %pOF\n" , ep); |
69 | of_node_put(node: ep); |
70 | return ret; |
71 | } |
72 | |
73 | bridge = devm_drm_of_get_bridge(dev, node: dev->of_node, port: 0, endpoint: of_ep.id); |
74 | if (IS_ERR(ptr: bridge)) { |
75 | of_node_put(node: ep); |
76 | return dev_err_probe(dev, err: PTR_ERR(ptr: bridge), |
77 | fmt: "Failed to get bridge for endpoint%u\n" , |
78 | of_ep.id); |
79 | } |
80 | |
81 | encoder = devm_kzalloc(dev, size: sizeof(*encoder), GFP_KERNEL); |
82 | if (!encoder) { |
83 | dev_err(dev, "Failed to allocate encoder for endpoint%u\n" , |
84 | of_ep.id); |
85 | of_node_put(node: ep); |
86 | return -ENOMEM; |
87 | } |
88 | |
89 | encoder->possible_crtcs = drm_crtc_mask(crtc: &lcdif->crtc); |
90 | ret = drm_encoder_init(dev: lcdif->drm, encoder, funcs: &lcdif_encoder_funcs, |
91 | DRM_MODE_ENCODER_NONE, NULL); |
92 | if (ret) { |
93 | dev_err(dev, "Failed to initialize encoder for endpoint%u: %d\n" , |
94 | of_ep.id, ret); |
95 | of_node_put(node: ep); |
96 | return ret; |
97 | } |
98 | |
99 | ret = drm_bridge_attach(encoder, bridge, NULL, flags: 0); |
100 | if (ret) { |
101 | of_node_put(node: ep); |
102 | return dev_err_probe(dev, err: ret, |
103 | fmt: "Failed to attach bridge for endpoint%u\n" , |
104 | of_ep.id); |
105 | } |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static irqreturn_t lcdif_irq_handler(int irq, void *data) |
112 | { |
113 | struct drm_device *drm = data; |
114 | struct lcdif_drm_private *lcdif = drm->dev_private; |
115 | u32 reg, stat; |
116 | |
117 | stat = readl(addr: lcdif->base + LCDC_V8_INT_STATUS_D0); |
118 | if (!stat) |
119 | return IRQ_NONE; |
120 | |
121 | if (stat & INT_STATUS_D0_VS_BLANK) { |
122 | reg = readl(addr: lcdif->base + LCDC_V8_CTRLDESCL0_5); |
123 | if (!(reg & CTRLDESCL0_5_SHADOW_LOAD_EN)) |
124 | drm_crtc_handle_vblank(crtc: &lcdif->crtc); |
125 | } |
126 | |
127 | writel(val: stat, addr: lcdif->base + LCDC_V8_INT_STATUS_D0); |
128 | |
129 | return IRQ_HANDLED; |
130 | } |
131 | |
132 | static int lcdif_load(struct drm_device *drm) |
133 | { |
134 | struct platform_device *pdev = to_platform_device(drm->dev); |
135 | struct lcdif_drm_private *lcdif; |
136 | struct resource *res; |
137 | int ret; |
138 | |
139 | lcdif = devm_kzalloc(dev: &pdev->dev, size: sizeof(*lcdif), GFP_KERNEL); |
140 | if (!lcdif) |
141 | return -ENOMEM; |
142 | |
143 | lcdif->drm = drm; |
144 | drm->dev_private = lcdif; |
145 | |
146 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
147 | lcdif->base = devm_ioremap_resource(dev: drm->dev, res); |
148 | if (IS_ERR(ptr: lcdif->base)) |
149 | return PTR_ERR(ptr: lcdif->base); |
150 | |
151 | lcdif->clk = devm_clk_get(dev: drm->dev, id: "pix" ); |
152 | if (IS_ERR(ptr: lcdif->clk)) |
153 | return PTR_ERR(ptr: lcdif->clk); |
154 | |
155 | lcdif->clk_axi = devm_clk_get(dev: drm->dev, id: "axi" ); |
156 | if (IS_ERR(ptr: lcdif->clk_axi)) |
157 | return PTR_ERR(ptr: lcdif->clk_axi); |
158 | |
159 | lcdif->clk_disp_axi = devm_clk_get(dev: drm->dev, id: "disp_axi" ); |
160 | if (IS_ERR(ptr: lcdif->clk_disp_axi)) |
161 | return PTR_ERR(ptr: lcdif->clk_disp_axi); |
162 | |
163 | platform_set_drvdata(pdev, data: drm); |
164 | |
165 | ret = dma_set_mask_and_coherent(dev: drm->dev, DMA_BIT_MASK(36)); |
166 | if (ret) |
167 | return ret; |
168 | |
169 | /* Modeset init */ |
170 | ret = drmm_mode_config_init(dev: drm); |
171 | if (ret) { |
172 | dev_err(drm->dev, "Failed to initialize mode config\n" ); |
173 | return ret; |
174 | } |
175 | |
176 | ret = lcdif_kms_init(lcdif); |
177 | if (ret < 0) { |
178 | dev_err(drm->dev, "Failed to initialize KMS pipeline\n" ); |
179 | return ret; |
180 | } |
181 | |
182 | ret = drm_vblank_init(dev: drm, num_crtcs: drm->mode_config.num_crtc); |
183 | if (ret < 0) { |
184 | dev_err(drm->dev, "Failed to initialise vblank\n" ); |
185 | return ret; |
186 | } |
187 | |
188 | /* Start with vertical blanking interrupt reporting disabled. */ |
189 | drm_crtc_vblank_off(crtc: &lcdif->crtc); |
190 | |
191 | ret = lcdif_attach_bridge(lcdif); |
192 | if (ret) |
193 | return dev_err_probe(dev: drm->dev, err: ret, fmt: "Cannot connect bridge\n" ); |
194 | |
195 | drm->mode_config.min_width = LCDIF_MIN_XRES; |
196 | drm->mode_config.min_height = LCDIF_MIN_YRES; |
197 | drm->mode_config.max_width = LCDIF_MAX_XRES; |
198 | drm->mode_config.max_height = LCDIF_MAX_YRES; |
199 | drm->mode_config.funcs = &lcdif_mode_config_funcs; |
200 | drm->mode_config.helper_private = &lcdif_mode_config_helpers; |
201 | |
202 | drm_mode_config_reset(dev: drm); |
203 | |
204 | ret = platform_get_irq(pdev, 0); |
205 | if (ret < 0) |
206 | return ret; |
207 | lcdif->irq = ret; |
208 | |
209 | ret = devm_request_irq(dev: drm->dev, irq: lcdif->irq, handler: lcdif_irq_handler, irqflags: 0, |
210 | devname: drm->driver->name, dev_id: drm); |
211 | if (ret < 0) { |
212 | dev_err(drm->dev, "Failed to install IRQ handler\n" ); |
213 | return ret; |
214 | } |
215 | |
216 | drm_kms_helper_poll_init(dev: drm); |
217 | |
218 | drm_helper_hpd_irq_event(dev: drm); |
219 | |
220 | pm_runtime_enable(dev: drm->dev); |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static void lcdif_unload(struct drm_device *drm) |
226 | { |
227 | struct lcdif_drm_private *lcdif = drm->dev_private; |
228 | |
229 | pm_runtime_get_sync(dev: drm->dev); |
230 | |
231 | drm_crtc_vblank_off(crtc: &lcdif->crtc); |
232 | |
233 | drm_kms_helper_poll_fini(dev: drm); |
234 | |
235 | pm_runtime_put_sync(dev: drm->dev); |
236 | pm_runtime_disable(dev: drm->dev); |
237 | |
238 | drm->dev_private = NULL; |
239 | } |
240 | |
241 | DEFINE_DRM_GEM_DMA_FOPS(fops); |
242 | |
243 | static const struct drm_driver lcdif_driver = { |
244 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
245 | DRM_GEM_DMA_DRIVER_OPS, |
246 | .fops = &fops, |
247 | .name = "imx-lcdif" , |
248 | .desc = "i.MX LCDIF Controller DRM" , |
249 | .date = "20220417" , |
250 | .major = 1, |
251 | .minor = 0, |
252 | }; |
253 | |
254 | static const struct of_device_id lcdif_dt_ids[] = { |
255 | { .compatible = "fsl,imx8mp-lcdif" }, |
256 | { .compatible = "fsl,imx93-lcdif" }, |
257 | { /* sentinel */ } |
258 | }; |
259 | MODULE_DEVICE_TABLE(of, lcdif_dt_ids); |
260 | |
261 | static int lcdif_probe(struct platform_device *pdev) |
262 | { |
263 | struct drm_device *drm; |
264 | int ret; |
265 | |
266 | drm = drm_dev_alloc(driver: &lcdif_driver, parent: &pdev->dev); |
267 | if (IS_ERR(ptr: drm)) |
268 | return PTR_ERR(ptr: drm); |
269 | |
270 | ret = lcdif_load(drm); |
271 | if (ret) |
272 | goto err_free; |
273 | |
274 | ret = drm_dev_register(dev: drm, flags: 0); |
275 | if (ret) |
276 | goto err_unload; |
277 | |
278 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: 32); |
279 | |
280 | return 0; |
281 | |
282 | err_unload: |
283 | lcdif_unload(drm); |
284 | err_free: |
285 | drm_dev_put(dev: drm); |
286 | |
287 | return ret; |
288 | } |
289 | |
290 | static void lcdif_remove(struct platform_device *pdev) |
291 | { |
292 | struct drm_device *drm = platform_get_drvdata(pdev); |
293 | |
294 | drm_dev_unregister(dev: drm); |
295 | drm_atomic_helper_shutdown(dev: drm); |
296 | lcdif_unload(drm); |
297 | drm_dev_put(dev: drm); |
298 | } |
299 | |
300 | static void lcdif_shutdown(struct platform_device *pdev) |
301 | { |
302 | struct drm_device *drm = platform_get_drvdata(pdev); |
303 | |
304 | drm_atomic_helper_shutdown(dev: drm); |
305 | } |
306 | |
307 | static int __maybe_unused lcdif_rpm_suspend(struct device *dev) |
308 | { |
309 | struct drm_device *drm = dev_get_drvdata(dev); |
310 | struct lcdif_drm_private *lcdif = drm->dev_private; |
311 | |
312 | /* These clock supply the DISPLAY CLOCK Domain */ |
313 | clk_disable_unprepare(clk: lcdif->clk); |
314 | /* These clock supply the System Bus, AXI, Write Path, LFIFO */ |
315 | clk_disable_unprepare(clk: lcdif->clk_disp_axi); |
316 | /* These clock supply the Control Bus, APB, APBH Ctrl Registers */ |
317 | clk_disable_unprepare(clk: lcdif->clk_axi); |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static int __maybe_unused lcdif_rpm_resume(struct device *dev) |
323 | { |
324 | struct drm_device *drm = dev_get_drvdata(dev); |
325 | struct lcdif_drm_private *lcdif = drm->dev_private; |
326 | |
327 | /* These clock supply the Control Bus, APB, APBH Ctrl Registers */ |
328 | clk_prepare_enable(clk: lcdif->clk_axi); |
329 | /* These clock supply the System Bus, AXI, Write Path, LFIFO */ |
330 | clk_prepare_enable(clk: lcdif->clk_disp_axi); |
331 | /* These clock supply the DISPLAY CLOCK Domain */ |
332 | clk_prepare_enable(clk: lcdif->clk); |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static int __maybe_unused lcdif_suspend(struct device *dev) |
338 | { |
339 | struct drm_device *drm = dev_get_drvdata(dev); |
340 | int ret; |
341 | |
342 | ret = drm_mode_config_helper_suspend(dev: drm); |
343 | if (ret) |
344 | return ret; |
345 | |
346 | return lcdif_rpm_suspend(dev); |
347 | } |
348 | |
349 | static int __maybe_unused lcdif_resume(struct device *dev) |
350 | { |
351 | struct drm_device *drm = dev_get_drvdata(dev); |
352 | |
353 | lcdif_rpm_resume(dev); |
354 | |
355 | return drm_mode_config_helper_resume(dev: drm); |
356 | } |
357 | |
358 | static const struct dev_pm_ops lcdif_pm_ops = { |
359 | SET_SYSTEM_SLEEP_PM_OPS(lcdif_suspend, lcdif_resume) |
360 | SET_RUNTIME_PM_OPS(lcdif_rpm_suspend, lcdif_rpm_resume, NULL) |
361 | }; |
362 | |
363 | static struct platform_driver lcdif_platform_driver = { |
364 | .probe = lcdif_probe, |
365 | .remove_new = lcdif_remove, |
366 | .shutdown = lcdif_shutdown, |
367 | .driver = { |
368 | .name = "imx-lcdif" , |
369 | .of_match_table = lcdif_dt_ids, |
370 | .pm = &lcdif_pm_ops, |
371 | }, |
372 | }; |
373 | |
374 | drm_module_platform_driver(lcdif_platform_driver); |
375 | |
376 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>" ); |
377 | MODULE_DESCRIPTION("Freescale LCDIF DRM/KMS driver" ); |
378 | MODULE_LICENSE("GPL" ); |
379 | |