1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2019-2022 Bootlin |
4 | * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> |
5 | */ |
6 | |
7 | #include <linux/bitfield.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/mfd/syscon.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_address.h> |
13 | #include <linux/of_reserved_mem.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/types.h> |
17 | |
18 | #include <drm/drm_atomic_helper.h> |
19 | #include <drm/drm_drv.h> |
20 | #include <drm/drm_fbdev_dma.h> |
21 | #include <drm/drm_gem_dma_helper.h> |
22 | #include <drm/drm_print.h> |
23 | |
24 | #include "logicvc_crtc.h" |
25 | #include "logicvc_drm.h" |
26 | #include "logicvc_interface.h" |
27 | #include "logicvc_mode.h" |
28 | #include "logicvc_layer.h" |
29 | #include "logicvc_of.h" |
30 | #include "logicvc_regs.h" |
31 | |
32 | DEFINE_DRM_GEM_DMA_FOPS(logicvc_drm_fops); |
33 | |
34 | static int logicvc_drm_gem_dma_dumb_create(struct drm_file *file_priv, |
35 | struct drm_device *drm_dev, |
36 | struct drm_mode_create_dumb *args) |
37 | { |
38 | struct logicvc_drm *logicvc = logicvc_drm(drm_dev); |
39 | |
40 | /* Stride is always fixed to its configuration value. */ |
41 | args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8); |
42 | |
43 | return drm_gem_dma_dumb_create_internal(file_priv, drm: drm_dev, args); |
44 | } |
45 | |
46 | static struct drm_driver logicvc_drm_driver = { |
47 | .driver_features = DRIVER_GEM | DRIVER_MODESET | |
48 | DRIVER_ATOMIC, |
49 | |
50 | .fops = &logicvc_drm_fops, |
51 | .name = "logicvc-drm" , |
52 | .desc = "Xylon LogiCVC DRM driver" , |
53 | .date = "20200403" , |
54 | .major = 1, |
55 | .minor = 0, |
56 | |
57 | DRM_GEM_DMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_dma_dumb_create), |
58 | }; |
59 | |
60 | static struct regmap_config logicvc_drm_regmap_config = { |
61 | .reg_bits = 32, |
62 | .val_bits = 32, |
63 | .reg_stride = 4, |
64 | .name = "logicvc-drm" , |
65 | }; |
66 | |
67 | static irqreturn_t logicvc_drm_irq_handler(int irq, void *data) |
68 | { |
69 | struct logicvc_drm *logicvc = data; |
70 | irqreturn_t ret = IRQ_NONE; |
71 | u32 stat = 0; |
72 | |
73 | /* Get pending interrupt sources. */ |
74 | regmap_read(map: logicvc->regmap, LOGICVC_INT_STAT_REG, val: &stat); |
75 | |
76 | /* Clear all pending interrupt sources. */ |
77 | regmap_write(map: logicvc->regmap, LOGICVC_INT_STAT_REG, val: stat); |
78 | |
79 | if (stat & LOGICVC_INT_STAT_V_SYNC) { |
80 | logicvc_crtc_vblank_handler(logicvc); |
81 | ret = IRQ_HANDLED; |
82 | } |
83 | |
84 | return ret; |
85 | } |
86 | |
87 | static int logicvc_drm_config_parse(struct logicvc_drm *logicvc) |
88 | { |
89 | struct drm_device *drm_dev = &logicvc->drm_dev; |
90 | struct device *dev = drm_dev->dev; |
91 | struct device_node *of_node = dev->of_node; |
92 | struct logicvc_drm_config *config = &logicvc->config; |
93 | struct device_node *layers_node; |
94 | int ret; |
95 | |
96 | logicvc_of_property_parse_bool(of_node, index: LOGICVC_OF_PROPERTY_DITHERING, |
97 | target: &config->dithering); |
98 | logicvc_of_property_parse_bool(of_node, |
99 | index: LOGICVC_OF_PROPERTY_BACKGROUND_LAYER, |
100 | target: &config->background_layer); |
101 | logicvc_of_property_parse_bool(of_node, |
102 | index: LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE, |
103 | target: &config->layers_configurable); |
104 | |
105 | ret = logicvc_of_property_parse_u32(of_node, |
106 | index: LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE, |
107 | target: &config->display_interface); |
108 | if (ret) |
109 | return ret; |
110 | |
111 | ret = logicvc_of_property_parse_u32(of_node, |
112 | index: LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE, |
113 | target: &config->display_colorspace); |
114 | if (ret) |
115 | return ret; |
116 | |
117 | ret = logicvc_of_property_parse_u32(of_node, |
118 | index: LOGICVC_OF_PROPERTY_DISPLAY_DEPTH, |
119 | target: &config->display_depth); |
120 | if (ret) |
121 | return ret; |
122 | |
123 | ret = logicvc_of_property_parse_u32(of_node, |
124 | index: LOGICVC_OF_PROPERTY_ROW_STRIDE, |
125 | target: &config->row_stride); |
126 | if (ret) |
127 | return ret; |
128 | |
129 | layers_node = of_get_child_by_name(node: of_node, name: "layers" ); |
130 | if (!layers_node) { |
131 | drm_err(drm_dev, "Missing non-optional layers node\n" ); |
132 | return -EINVAL; |
133 | } |
134 | |
135 | config->layers_count = of_get_child_count(np: layers_node); |
136 | if (!config->layers_count) { |
137 | drm_err(drm_dev, |
138 | "Missing a non-optional layers children node\n" ); |
139 | return -EINVAL; |
140 | } |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static int logicvc_clocks_prepare(struct logicvc_drm *logicvc) |
146 | { |
147 | struct drm_device *drm_dev = &logicvc->drm_dev; |
148 | struct device *dev = drm_dev->dev; |
149 | |
150 | struct { |
151 | struct clk **clk; |
152 | char *name; |
153 | bool optional; |
154 | } clocks_map[] = { |
155 | { |
156 | .clk = &logicvc->vclk, |
157 | .name = "vclk" , |
158 | .optional = false, |
159 | }, |
160 | { |
161 | .clk = &logicvc->vclk2, |
162 | .name = "vclk2" , |
163 | .optional = true, |
164 | }, |
165 | { |
166 | .clk = &logicvc->lvdsclk, |
167 | .name = "lvdsclk" , |
168 | .optional = true, |
169 | }, |
170 | { |
171 | .clk = &logicvc->lvdsclkn, |
172 | .name = "lvdsclkn" , |
173 | .optional = true, |
174 | }, |
175 | }; |
176 | unsigned int i; |
177 | int ret; |
178 | |
179 | for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { |
180 | struct clk *clk; |
181 | |
182 | clk = devm_clk_get(dev, id: clocks_map[i].name); |
183 | if (IS_ERR(ptr: clk)) { |
184 | if (PTR_ERR(ptr: clk) == -ENOENT && clocks_map[i].optional) |
185 | continue; |
186 | |
187 | drm_err(drm_dev, "Missing non-optional clock %s\n" , |
188 | clocks_map[i].name); |
189 | |
190 | ret = PTR_ERR(ptr: clk); |
191 | goto error; |
192 | } |
193 | |
194 | ret = clk_prepare_enable(clk); |
195 | if (ret) { |
196 | drm_err(drm_dev, |
197 | "Failed to prepare and enable clock %s\n" , |
198 | clocks_map[i].name); |
199 | goto error; |
200 | } |
201 | |
202 | *clocks_map[i].clk = clk; |
203 | } |
204 | |
205 | return 0; |
206 | |
207 | error: |
208 | for (i = 0; i < ARRAY_SIZE(clocks_map); i++) { |
209 | if (!*clocks_map[i].clk) |
210 | continue; |
211 | |
212 | clk_disable_unprepare(clk: *clocks_map[i].clk); |
213 | *clocks_map[i].clk = NULL; |
214 | } |
215 | |
216 | return ret; |
217 | } |
218 | |
219 | static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc) |
220 | { |
221 | struct clk **clocks[] = { |
222 | &logicvc->vclk, |
223 | &logicvc->vclk2, |
224 | &logicvc->lvdsclk, |
225 | &logicvc->lvdsclkn, |
226 | }; |
227 | unsigned int i; |
228 | |
229 | for (i = 0; i < ARRAY_SIZE(clocks); i++) { |
230 | if (!*clocks[i]) |
231 | continue; |
232 | |
233 | clk_disable_unprepare(clk: *clocks[i]); |
234 | *clocks[i] = NULL; |
235 | } |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static const struct logicvc_drm_caps logicvc_drm_caps[] = { |
241 | { |
242 | .major = 3, |
243 | .layer_address = false, |
244 | }, |
245 | { |
246 | .major = 4, |
247 | .layer_address = true, |
248 | }, |
249 | { |
250 | .major = 5, |
251 | .layer_address = true, |
252 | }, |
253 | }; |
254 | |
255 | static const struct logicvc_drm_caps * |
256 | logicvc_drm_caps_match(struct logicvc_drm *logicvc) |
257 | { |
258 | struct drm_device *drm_dev = &logicvc->drm_dev; |
259 | const struct logicvc_drm_caps *caps = NULL; |
260 | unsigned int major, minor; |
261 | char level; |
262 | unsigned int i; |
263 | u32 version; |
264 | |
265 | regmap_read(map: logicvc->regmap, LOGICVC_IP_VERSION_REG, val: &version); |
266 | |
267 | major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version); |
268 | minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version); |
269 | level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a'; |
270 | |
271 | for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) { |
272 | if (logicvc_drm_caps[i].major && |
273 | logicvc_drm_caps[i].major != major) |
274 | continue; |
275 | |
276 | if (logicvc_drm_caps[i].minor && |
277 | logicvc_drm_caps[i].minor != minor) |
278 | continue; |
279 | |
280 | if (logicvc_drm_caps[i].level && |
281 | logicvc_drm_caps[i].level != level) |
282 | continue; |
283 | |
284 | caps = &logicvc_drm_caps[i]; |
285 | } |
286 | |
287 | drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n" , major, minor, level); |
288 | |
289 | return caps; |
290 | } |
291 | |
292 | static int logicvc_drm_probe(struct platform_device *pdev) |
293 | { |
294 | struct device_node *of_node = pdev->dev.of_node; |
295 | struct device_node *reserved_mem_node; |
296 | struct reserved_mem *reserved_mem = NULL; |
297 | const struct logicvc_drm_caps *caps; |
298 | struct logicvc_drm *logicvc; |
299 | struct device *dev = &pdev->dev; |
300 | struct drm_device *drm_dev; |
301 | struct regmap *regmap = NULL; |
302 | struct resource res; |
303 | void __iomem *base; |
304 | unsigned int preferred_bpp; |
305 | int irq; |
306 | int ret; |
307 | |
308 | ret = of_reserved_mem_device_init(dev); |
309 | if (ret && ret != -ENODEV) { |
310 | dev_err(dev, "Failed to init memory region\n" ); |
311 | goto error_early; |
312 | } |
313 | |
314 | reserved_mem_node = of_parse_phandle(np: of_node, phandle_name: "memory-region" , index: 0); |
315 | if (reserved_mem_node) { |
316 | reserved_mem = of_reserved_mem_lookup(np: reserved_mem_node); |
317 | of_node_put(node: reserved_mem_node); |
318 | } |
319 | |
320 | /* Get regmap from parent if available. */ |
321 | if (of_node->parent) |
322 | regmap = syscon_node_to_regmap(np: of_node->parent); |
323 | |
324 | /* Register our own regmap otherwise. */ |
325 | if (IS_ERR_OR_NULL(ptr: regmap)) { |
326 | ret = of_address_to_resource(dev: of_node, index: 0, r: &res); |
327 | if (ret) { |
328 | dev_err(dev, "Failed to get resource from address\n" ); |
329 | goto error_reserved_mem; |
330 | } |
331 | |
332 | base = devm_ioremap_resource(dev, res: &res); |
333 | if (IS_ERR(ptr: base)) { |
334 | dev_err(dev, "Failed to map I/O base\n" ); |
335 | ret = PTR_ERR(ptr: base); |
336 | goto error_reserved_mem; |
337 | } |
338 | |
339 | logicvc_drm_regmap_config.max_register = resource_size(res: &res) - |
340 | 4; |
341 | |
342 | regmap = devm_regmap_init_mmio(dev, base, |
343 | &logicvc_drm_regmap_config); |
344 | if (IS_ERR(ptr: regmap)) { |
345 | dev_err(dev, "Failed to create regmap for I/O\n" ); |
346 | ret = PTR_ERR(ptr: regmap); |
347 | goto error_reserved_mem; |
348 | } |
349 | } |
350 | |
351 | irq = platform_get_irq(pdev, 0); |
352 | if (irq < 0) { |
353 | ret = -ENODEV; |
354 | goto error_reserved_mem; |
355 | } |
356 | |
357 | logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver, |
358 | struct logicvc_drm, drm_dev); |
359 | if (IS_ERR(ptr: logicvc)) { |
360 | ret = PTR_ERR(ptr: logicvc); |
361 | goto error_reserved_mem; |
362 | } |
363 | |
364 | platform_set_drvdata(pdev, data: logicvc); |
365 | drm_dev = &logicvc->drm_dev; |
366 | |
367 | logicvc->regmap = regmap; |
368 | INIT_LIST_HEAD(list: &logicvc->layers_list); |
369 | |
370 | caps = logicvc_drm_caps_match(logicvc); |
371 | if (!caps) { |
372 | ret = -EINVAL; |
373 | goto error_reserved_mem; |
374 | } |
375 | |
376 | logicvc->caps = caps; |
377 | |
378 | if (reserved_mem) |
379 | logicvc->reserved_mem_base = reserved_mem->base; |
380 | |
381 | ret = logicvc_clocks_prepare(logicvc); |
382 | if (ret) { |
383 | drm_err(drm_dev, "Failed to prepare clocks\n" ); |
384 | goto error_reserved_mem; |
385 | } |
386 | |
387 | ret = devm_request_irq(dev, irq, handler: logicvc_drm_irq_handler, irqflags: 0, |
388 | devname: dev_name(dev), dev_id: logicvc); |
389 | if (ret) { |
390 | drm_err(drm_dev, "Failed to request IRQ\n" ); |
391 | goto error_clocks; |
392 | } |
393 | |
394 | ret = logicvc_drm_config_parse(logicvc); |
395 | if (ret && ret != -ENODEV) { |
396 | drm_err(drm_dev, "Failed to parse config\n" ); |
397 | goto error_clocks; |
398 | } |
399 | |
400 | ret = drmm_mode_config_init(dev: drm_dev); |
401 | if (ret) { |
402 | drm_err(drm_dev, "Failed to init mode config\n" ); |
403 | goto error_clocks; |
404 | } |
405 | |
406 | ret = logicvc_layers_init(logicvc); |
407 | if (ret) { |
408 | drm_err(drm_dev, "Failed to initialize layers\n" ); |
409 | goto error_clocks; |
410 | } |
411 | |
412 | ret = logicvc_crtc_init(logicvc); |
413 | if (ret) { |
414 | drm_err(drm_dev, "Failed to initialize CRTC\n" ); |
415 | goto error_clocks; |
416 | } |
417 | |
418 | logicvc_layers_attach_crtc(logicvc); |
419 | |
420 | ret = logicvc_interface_init(logicvc); |
421 | if (ret) { |
422 | if (ret != -EPROBE_DEFER) |
423 | drm_err(drm_dev, "Failed to initialize interface\n" ); |
424 | |
425 | goto error_clocks; |
426 | } |
427 | |
428 | logicvc_interface_attach_crtc(logicvc); |
429 | |
430 | ret = logicvc_mode_init(logicvc); |
431 | if (ret) { |
432 | drm_err(drm_dev, "Failed to initialize KMS\n" ); |
433 | goto error_clocks; |
434 | } |
435 | |
436 | ret = drm_dev_register(dev: drm_dev, flags: 0); |
437 | if (ret) { |
438 | drm_err(drm_dev, "Failed to register DRM device\n" ); |
439 | goto error_mode; |
440 | } |
441 | |
442 | switch (drm_dev->mode_config.preferred_depth) { |
443 | case 16: |
444 | preferred_bpp = 16; |
445 | break; |
446 | case 24: |
447 | case 32: |
448 | default: |
449 | preferred_bpp = 32; |
450 | break; |
451 | } |
452 | drm_fbdev_dma_setup(dev: drm_dev, preferred_bpp); |
453 | |
454 | return 0; |
455 | |
456 | error_mode: |
457 | logicvc_mode_fini(logicvc); |
458 | |
459 | error_clocks: |
460 | logicvc_clocks_unprepare(logicvc); |
461 | |
462 | error_reserved_mem: |
463 | of_reserved_mem_device_release(dev); |
464 | |
465 | error_early: |
466 | return ret; |
467 | } |
468 | |
469 | static void logicvc_drm_remove(struct platform_device *pdev) |
470 | { |
471 | struct logicvc_drm *logicvc = platform_get_drvdata(pdev); |
472 | struct device *dev = &pdev->dev; |
473 | struct drm_device *drm_dev = &logicvc->drm_dev; |
474 | |
475 | drm_dev_unregister(dev: drm_dev); |
476 | drm_atomic_helper_shutdown(dev: drm_dev); |
477 | |
478 | logicvc_mode_fini(logicvc); |
479 | |
480 | logicvc_clocks_unprepare(logicvc); |
481 | |
482 | of_reserved_mem_device_release(dev); |
483 | } |
484 | |
485 | static void logicvc_drm_shutdown(struct platform_device *pdev) |
486 | { |
487 | struct logicvc_drm *logicvc = platform_get_drvdata(pdev); |
488 | struct drm_device *drm_dev = &logicvc->drm_dev; |
489 | |
490 | drm_atomic_helper_shutdown(dev: drm_dev); |
491 | } |
492 | |
493 | static const struct of_device_id logicvc_drm_of_table[] = { |
494 | { .compatible = "xylon,logicvc-3.02.a-display" }, |
495 | { .compatible = "xylon,logicvc-4.01.a-display" }, |
496 | {}, |
497 | }; |
498 | MODULE_DEVICE_TABLE(of, logicvc_drm_of_table); |
499 | |
500 | static struct platform_driver logicvc_drm_platform_driver = { |
501 | .probe = logicvc_drm_probe, |
502 | .remove_new = logicvc_drm_remove, |
503 | .shutdown = logicvc_drm_shutdown, |
504 | .driver = { |
505 | .name = "logicvc-drm" , |
506 | .of_match_table = logicvc_drm_of_table, |
507 | }, |
508 | }; |
509 | |
510 | module_platform_driver(logicvc_drm_platform_driver); |
511 | |
512 | MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>" ); |
513 | MODULE_DESCRIPTION("Xylon LogiCVC DRM driver" ); |
514 | MODULE_LICENSE("GPL" ); |
515 | |