1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2015 Freescale Semiconductor, Inc. |
4 | * |
5 | * Freescale DCU drm device driver |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/console.h> |
11 | #include <linux/io.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/mm.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm.h> |
18 | #include <linux/pm_runtime.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | #include <drm/drm_atomic_helper.h> |
22 | #include <drm/drm_drv.h> |
23 | #include <drm/drm_fbdev_dma.h> |
24 | #include <drm/drm_gem_dma_helper.h> |
25 | #include <drm/drm_modeset_helper.h> |
26 | #include <drm/drm_module.h> |
27 | #include <drm/drm_probe_helper.h> |
28 | #include <drm/drm_vblank.h> |
29 | |
30 | #include "fsl_dcu_drm_crtc.h" |
31 | #include "fsl_dcu_drm_drv.h" |
32 | #include "fsl_tcon.h" |
33 | |
34 | static int legacyfb_depth = 24; |
35 | module_param(legacyfb_depth, int, 0444); |
36 | |
37 | static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg) |
38 | { |
39 | if (reg == DCU_INT_STATUS || reg == DCU_UPDATE_MODE) |
40 | return true; |
41 | |
42 | return false; |
43 | } |
44 | |
45 | static const struct regmap_config fsl_dcu_regmap_config = { |
46 | .reg_bits = 32, |
47 | .reg_stride = 4, |
48 | .val_bits = 32, |
49 | |
50 | .volatile_reg = fsl_dcu_drm_is_volatile_reg, |
51 | }; |
52 | |
53 | static void fsl_dcu_irq_reset(struct drm_device *dev) |
54 | { |
55 | struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; |
56 | |
57 | regmap_write(map: fsl_dev->regmap, DCU_INT_STATUS, val: ~0); |
58 | regmap_write(map: fsl_dev->regmap, DCU_INT_MASK, val: ~0); |
59 | } |
60 | |
61 | static irqreturn_t fsl_dcu_drm_irq(int irq, void *arg) |
62 | { |
63 | struct drm_device *dev = arg; |
64 | struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; |
65 | unsigned int int_status; |
66 | int ret; |
67 | |
68 | ret = regmap_read(map: fsl_dev->regmap, DCU_INT_STATUS, val: &int_status); |
69 | if (ret) { |
70 | dev_err(dev->dev, "read DCU_INT_STATUS failed\n" ); |
71 | return IRQ_NONE; |
72 | } |
73 | |
74 | if (int_status & DCU_INT_STATUS_VBLANK) |
75 | drm_handle_vblank(dev, pipe: 0); |
76 | |
77 | regmap_write(map: fsl_dev->regmap, DCU_INT_STATUS, val: int_status); |
78 | |
79 | return IRQ_HANDLED; |
80 | } |
81 | |
82 | static int fsl_dcu_irq_install(struct drm_device *dev, unsigned int irq) |
83 | { |
84 | if (irq == IRQ_NOTCONNECTED) |
85 | return -ENOTCONN; |
86 | |
87 | fsl_dcu_irq_reset(dev); |
88 | |
89 | return request_irq(irq, handler: fsl_dcu_drm_irq, flags: 0, name: dev->driver->name, dev); |
90 | } |
91 | |
92 | static void fsl_dcu_irq_uninstall(struct drm_device *dev) |
93 | { |
94 | struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; |
95 | |
96 | fsl_dcu_irq_reset(dev); |
97 | free_irq(fsl_dev->irq, dev); |
98 | } |
99 | |
100 | static int fsl_dcu_load(struct drm_device *dev, unsigned long flags) |
101 | { |
102 | struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; |
103 | int ret; |
104 | |
105 | ret = fsl_dcu_drm_modeset_init(fsl_dev); |
106 | if (ret < 0) { |
107 | dev_err(dev->dev, "failed to initialize mode setting\n" ); |
108 | return ret; |
109 | } |
110 | |
111 | ret = drm_vblank_init(dev, num_crtcs: dev->mode_config.num_crtc); |
112 | if (ret < 0) { |
113 | dev_err(dev->dev, "failed to initialize vblank\n" ); |
114 | goto done_vblank; |
115 | } |
116 | |
117 | ret = fsl_dcu_irq_install(dev, irq: fsl_dev->irq); |
118 | if (ret < 0) { |
119 | dev_err(dev->dev, "failed to install IRQ handler\n" ); |
120 | goto done_irq; |
121 | } |
122 | |
123 | if (legacyfb_depth != 16 && legacyfb_depth != 24 && |
124 | legacyfb_depth != 32) { |
125 | dev_warn(dev->dev, |
126 | "Invalid legacyfb_depth. Defaulting to 24bpp\n" ); |
127 | legacyfb_depth = 24; |
128 | } |
129 | |
130 | return 0; |
131 | done_irq: |
132 | drm_kms_helper_poll_fini(dev); |
133 | |
134 | drm_mode_config_cleanup(dev); |
135 | done_vblank: |
136 | dev->dev_private = NULL; |
137 | |
138 | return ret; |
139 | } |
140 | |
141 | static void fsl_dcu_unload(struct drm_device *dev) |
142 | { |
143 | drm_atomic_helper_shutdown(dev); |
144 | drm_kms_helper_poll_fini(dev); |
145 | |
146 | drm_mode_config_cleanup(dev); |
147 | fsl_dcu_irq_uninstall(dev); |
148 | |
149 | dev->dev_private = NULL; |
150 | } |
151 | |
152 | DEFINE_DRM_GEM_DMA_FOPS(fsl_dcu_drm_fops); |
153 | |
154 | static const struct drm_driver fsl_dcu_drm_driver = { |
155 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
156 | .load = fsl_dcu_load, |
157 | .unload = fsl_dcu_unload, |
158 | DRM_GEM_DMA_DRIVER_OPS, |
159 | .fops = &fsl_dcu_drm_fops, |
160 | .name = "fsl-dcu-drm" , |
161 | .desc = "Freescale DCU DRM" , |
162 | .date = "20160425" , |
163 | .major = 1, |
164 | .minor = 1, |
165 | }; |
166 | |
167 | #ifdef CONFIG_PM_SLEEP |
168 | static int fsl_dcu_drm_pm_suspend(struct device *dev) |
169 | { |
170 | struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev); |
171 | int ret; |
172 | |
173 | if (!fsl_dev) |
174 | return 0; |
175 | |
176 | disable_irq(irq: fsl_dev->irq); |
177 | |
178 | ret = drm_mode_config_helper_suspend(dev: fsl_dev->drm); |
179 | if (ret) { |
180 | enable_irq(irq: fsl_dev->irq); |
181 | return ret; |
182 | } |
183 | |
184 | clk_disable_unprepare(clk: fsl_dev->clk); |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static int fsl_dcu_drm_pm_resume(struct device *dev) |
190 | { |
191 | struct fsl_dcu_drm_device *fsl_dev = dev_get_drvdata(dev); |
192 | int ret; |
193 | |
194 | if (!fsl_dev) |
195 | return 0; |
196 | |
197 | ret = clk_prepare_enable(clk: fsl_dev->clk); |
198 | if (ret < 0) { |
199 | dev_err(dev, "failed to enable dcu clk\n" ); |
200 | return ret; |
201 | } |
202 | |
203 | if (fsl_dev->tcon) |
204 | fsl_tcon_bypass_enable(tcon: fsl_dev->tcon); |
205 | fsl_dcu_drm_init_planes(dev: fsl_dev->drm); |
206 | enable_irq(irq: fsl_dev->irq); |
207 | |
208 | drm_mode_config_helper_resume(dev: fsl_dev->drm); |
209 | |
210 | return 0; |
211 | } |
212 | #endif |
213 | |
214 | static const struct dev_pm_ops fsl_dcu_drm_pm_ops = { |
215 | SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_drm_pm_suspend, fsl_dcu_drm_pm_resume) |
216 | }; |
217 | |
218 | static const struct fsl_dcu_soc_data fsl_dcu_ls1021a_data = { |
219 | .name = "ls1021a" , |
220 | .total_layer = 16, |
221 | .max_layer = 4, |
222 | .layer_regs = LS1021A_LAYER_REG_NUM, |
223 | }; |
224 | |
225 | static const struct fsl_dcu_soc_data fsl_dcu_vf610_data = { |
226 | .name = "vf610" , |
227 | .total_layer = 64, |
228 | .max_layer = 6, |
229 | .layer_regs = VF610_LAYER_REG_NUM, |
230 | }; |
231 | |
232 | static const struct of_device_id fsl_dcu_of_match[] = { |
233 | { |
234 | .compatible = "fsl,ls1021a-dcu" , |
235 | .data = &fsl_dcu_ls1021a_data, |
236 | }, { |
237 | .compatible = "fsl,vf610-dcu" , |
238 | .data = &fsl_dcu_vf610_data, |
239 | }, { |
240 | }, |
241 | }; |
242 | MODULE_DEVICE_TABLE(of, fsl_dcu_of_match); |
243 | |
244 | static int fsl_dcu_drm_probe(struct platform_device *pdev) |
245 | { |
246 | struct fsl_dcu_drm_device *fsl_dev; |
247 | struct drm_device *drm; |
248 | struct device *dev = &pdev->dev; |
249 | struct resource *res; |
250 | void __iomem *base; |
251 | struct clk *pix_clk_in; |
252 | char pix_clk_name[32]; |
253 | const char *pix_clk_in_name; |
254 | const struct of_device_id *id; |
255 | int ret; |
256 | u8 div_ratio_shift = 0; |
257 | |
258 | fsl_dev = devm_kzalloc(dev, size: sizeof(*fsl_dev), GFP_KERNEL); |
259 | if (!fsl_dev) |
260 | return -ENOMEM; |
261 | |
262 | id = of_match_node(matches: fsl_dcu_of_match, node: pdev->dev.of_node); |
263 | if (!id) |
264 | return -ENODEV; |
265 | fsl_dev->soc = id->data; |
266 | |
267 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
268 | base = devm_ioremap_resource(dev, res); |
269 | if (IS_ERR(ptr: base)) { |
270 | ret = PTR_ERR(ptr: base); |
271 | return ret; |
272 | } |
273 | |
274 | fsl_dev->irq = platform_get_irq(pdev, 0); |
275 | if (fsl_dev->irq < 0) { |
276 | dev_err(dev, "failed to get irq\n" ); |
277 | return fsl_dev->irq; |
278 | } |
279 | |
280 | fsl_dev->regmap = devm_regmap_init_mmio(dev, base, |
281 | &fsl_dcu_regmap_config); |
282 | if (IS_ERR(ptr: fsl_dev->regmap)) { |
283 | dev_err(dev, "regmap init failed\n" ); |
284 | return PTR_ERR(ptr: fsl_dev->regmap); |
285 | } |
286 | |
287 | fsl_dev->clk = devm_clk_get(dev, id: "dcu" ); |
288 | if (IS_ERR(ptr: fsl_dev->clk)) { |
289 | dev_err(dev, "failed to get dcu clock\n" ); |
290 | return PTR_ERR(ptr: fsl_dev->clk); |
291 | } |
292 | ret = clk_prepare_enable(clk: fsl_dev->clk); |
293 | if (ret < 0) { |
294 | dev_err(dev, "failed to enable dcu clk\n" ); |
295 | return ret; |
296 | } |
297 | |
298 | pix_clk_in = devm_clk_get(dev, id: "pix" ); |
299 | if (IS_ERR(ptr: pix_clk_in)) { |
300 | /* legancy binding, use dcu clock as pixel clock input */ |
301 | pix_clk_in = fsl_dev->clk; |
302 | } |
303 | |
304 | if (of_property_read_bool(np: dev->of_node, propname: "big-endian" )) |
305 | div_ratio_shift = 24; |
306 | |
307 | pix_clk_in_name = __clk_get_name(clk: pix_clk_in); |
308 | snprintf(buf: pix_clk_name, size: sizeof(pix_clk_name), fmt: "%s_pix" , pix_clk_in_name); |
309 | fsl_dev->pix_clk = clk_register_divider(dev, pix_clk_name, |
310 | pix_clk_in_name, 0, base + DCU_DIV_RATIO, |
311 | div_ratio_shift, 8, CLK_DIVIDER_ROUND_CLOSEST, NULL); |
312 | if (IS_ERR(ptr: fsl_dev->pix_clk)) { |
313 | dev_err(dev, "failed to register pix clk\n" ); |
314 | ret = PTR_ERR(ptr: fsl_dev->pix_clk); |
315 | goto disable_clk; |
316 | } |
317 | |
318 | fsl_dev->tcon = fsl_tcon_init(dev); |
319 | |
320 | drm = drm_dev_alloc(driver: &fsl_dcu_drm_driver, parent: dev); |
321 | if (IS_ERR(ptr: drm)) { |
322 | ret = PTR_ERR(ptr: drm); |
323 | goto unregister_pix_clk; |
324 | } |
325 | |
326 | fsl_dev->dev = dev; |
327 | fsl_dev->drm = drm; |
328 | fsl_dev->np = dev->of_node; |
329 | drm->dev_private = fsl_dev; |
330 | dev_set_drvdata(dev, data: fsl_dev); |
331 | |
332 | ret = drm_dev_register(dev: drm, flags: 0); |
333 | if (ret < 0) |
334 | goto put; |
335 | |
336 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: legacyfb_depth); |
337 | |
338 | return 0; |
339 | |
340 | put: |
341 | drm_dev_put(dev: drm); |
342 | unregister_pix_clk: |
343 | clk_unregister(clk: fsl_dev->pix_clk); |
344 | disable_clk: |
345 | clk_disable_unprepare(clk: fsl_dev->clk); |
346 | return ret; |
347 | } |
348 | |
349 | static void fsl_dcu_drm_remove(struct platform_device *pdev) |
350 | { |
351 | struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev); |
352 | |
353 | drm_dev_unregister(dev: fsl_dev->drm); |
354 | drm_dev_put(dev: fsl_dev->drm); |
355 | clk_disable_unprepare(clk: fsl_dev->clk); |
356 | clk_unregister(clk: fsl_dev->pix_clk); |
357 | } |
358 | |
359 | static void fsl_dcu_drm_shutdown(struct platform_device *pdev) |
360 | { |
361 | struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev); |
362 | |
363 | drm_atomic_helper_shutdown(dev: fsl_dev->drm); |
364 | } |
365 | |
366 | static struct platform_driver fsl_dcu_drm_platform_driver = { |
367 | .probe = fsl_dcu_drm_probe, |
368 | .remove_new = fsl_dcu_drm_remove, |
369 | .shutdown = fsl_dcu_drm_shutdown, |
370 | .driver = { |
371 | .name = "fsl-dcu" , |
372 | .pm = &fsl_dcu_drm_pm_ops, |
373 | .of_match_table = fsl_dcu_of_match, |
374 | }, |
375 | }; |
376 | |
377 | drm_module_platform_driver(fsl_dcu_drm_platform_driver); |
378 | |
379 | MODULE_DESCRIPTION("Freescale DCU DRM Driver" ); |
380 | MODULE_LICENSE("GPL" ); |
381 | |