]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
9026e0d1 MR |
2 | /* |
3 | * Copyright (C) 2015 Free Electrons | |
4 | * Copyright (C) 2015 NextThing Co | |
5 | * | |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
9026e0d1 MR |
7 | */ |
8 | ||
9 | #include <linux/component.h> | |
8b11aafa | 10 | #include <linux/kfifo.h> |
9026e0d1 | 11 | #include <linux/of_graph.h> |
596afb6f | 12 | #include <linux/of_reserved_mem.h> |
9026e0d1 MR |
13 | |
14 | #include <drm/drmP.h> | |
71adf60f | 15 | #include <drm/drm_atomic_helper.h> |
9026e0d1 | 16 | #include <drm/drm_fb_cma_helper.h> |
44adece5 | 17 | #include <drm/drm_fb_helper.h> |
fcd70cd3 | 18 | #include <drm/drm_gem_cma_helper.h> |
97ac0e47 | 19 | #include <drm/drm_of.h> |
fcd70cd3 | 20 | #include <drm/drm_probe_helper.h> |
9026e0d1 | 21 | |
9026e0d1 | 22 | #include "sun4i_drv.h" |
dd0421f4 | 23 | #include "sun4i_frontend.h" |
9026e0d1 | 24 | #include "sun4i_framebuffer.h" |
b3f266e4 | 25 | #include "sun4i_tcon.h" |
ef0cf644 | 26 | #include "sun8i_tcon_top.h" |
9026e0d1 | 27 | |
31cf282a PK |
28 | static int drm_sun4i_gem_dumb_create(struct drm_file *file_priv, |
29 | struct drm_device *drm, | |
30 | struct drm_mode_create_dumb *args) | |
31 | { | |
32 | /* The hardware only allows even pitches for YUV buffers. */ | |
33 | args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 2); | |
34 | ||
35 | return drm_gem_cma_dumb_create_internal(file_priv, drm, args); | |
36 | } | |
37 | ||
d55f7e5d | 38 | DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops); |
9026e0d1 MR |
39 | |
40 | static struct drm_driver sun4i_drv_driver = { | |
41 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, | |
42 | ||
43 | /* Generic Operations */ | |
44 | .fops = &sun4i_drv_fops, | |
45 | .name = "sun4i-drm", | |
46 | .desc = "Allwinner sun4i Display Engine", | |
47 | .date = "20150629", | |
48 | .major = 1, | |
49 | .minor = 0, | |
50 | ||
51 | /* GEM Operations */ | |
31cf282a | 52 | .dumb_create = drm_sun4i_gem_dumb_create, |
ae2c6221 | 53 | .gem_free_object_unlocked = drm_gem_cma_free_object, |
9026e0d1 MR |
54 | .gem_vm_ops = &drm_gem_cma_vm_ops, |
55 | ||
56 | /* PRIME Operations */ | |
57 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
58 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
59 | .gem_prime_import = drm_gem_prime_import, | |
60 | .gem_prime_export = drm_gem_prime_export, | |
61 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
62 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
63 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
64 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
65 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
66 | ||
67 | /* Frame Buffer Operations */ | |
9026e0d1 MR |
68 | }; |
69 | ||
70 | static int sun4i_drv_bind(struct device *dev) | |
71 | { | |
72 | struct drm_device *drm; | |
73 | struct sun4i_drv *drv; | |
74 | int ret; | |
75 | ||
76 | drm = drm_dev_alloc(&sun4i_drv_driver, dev); | |
0f288605 TG |
77 | if (IS_ERR(drm)) |
78 | return PTR_ERR(drm); | |
9026e0d1 | 79 | |
9026e0d1 MR |
80 | drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); |
81 | if (!drv) { | |
82 | ret = -ENOMEM; | |
83 | goto free_drm; | |
84 | } | |
02b92adb PK |
85 | |
86 | dev_set_drvdata(dev, drm); | |
9026e0d1 | 87 | drm->dev_private = drv; |
dd0421f4 | 88 | INIT_LIST_HEAD(&drv->frontend_list); |
87969338 | 89 | INIT_LIST_HEAD(&drv->engine_list); |
80a58240 | 90 | INIT_LIST_HEAD(&drv->tcon_list); |
9026e0d1 | 91 | |
596afb6f MR |
92 | ret = of_reserved_mem_device_init(dev); |
93 | if (ret && ret != -ENODEV) { | |
94 | dev_err(drm->dev, "Couldn't claim our memory region\n"); | |
95 | goto free_drm; | |
96 | } | |
97 | ||
9026e0d1 | 98 | drm_mode_config_init(drm); |
9db9c0cf | 99 | drm->mode_config.allow_fb_modifiers = true; |
9026e0d1 MR |
100 | |
101 | ret = component_bind_all(drm->dev, drm); | |
102 | if (ret) { | |
103 | dev_err(drm->dev, "Couldn't bind all pipelines components\n"); | |
9d56defb | 104 | goto cleanup_mode_config; |
9026e0d1 MR |
105 | } |
106 | ||
070badfa CYT |
107 | /* drm_vblank_init calls kcalloc, which can fail */ |
108 | ret = drm_vblank_init(drm, drm->mode_config.num_crtc); | |
109 | if (ret) | |
9ca86149 | 110 | goto cleanup_mode_config; |
070badfa | 111 | |
9026e0d1 MR |
112 | drm->irq_enabled = true; |
113 | ||
3d6bd906 | 114 | /* Remove early framebuffers (ie. simplefb) */ |
a7e3fa76 | 115 | drm_fb_helper_remove_conflicting_framebuffers(NULL, "sun4i-drm-fb", false); |
3d6bd906 | 116 | |
94ebfc07 | 117 | sun4i_framebuffer_init(drm); |
9026e0d1 MR |
118 | |
119 | /* Enable connectors polling */ | |
120 | drm_kms_helper_poll_init(drm); | |
121 | ||
122 | ret = drm_dev_register(drm, 0); | |
123 | if (ret) | |
9d56defb | 124 | goto finish_poll; |
9026e0d1 | 125 | |
94ebfc07 NT |
126 | drm_fbdev_generic_setup(drm, 32); |
127 | ||
9026e0d1 MR |
128 | return 0; |
129 | ||
9d56defb CYT |
130 | finish_poll: |
131 | drm_kms_helper_poll_fini(drm); | |
9d56defb CYT |
132 | cleanup_mode_config: |
133 | drm_mode_config_cleanup(drm); | |
596afb6f | 134 | of_reserved_mem_device_release(dev); |
9026e0d1 | 135 | free_drm: |
4c2ae34f | 136 | drm_dev_put(drm); |
9026e0d1 MR |
137 | return ret; |
138 | } | |
139 | ||
140 | static void sun4i_drv_unbind(struct device *dev) | |
141 | { | |
142 | struct drm_device *drm = dev_get_drvdata(dev); | |
143 | ||
144 | drm_dev_unregister(drm); | |
145 | drm_kms_helper_poll_fini(drm); | |
71adf60f | 146 | drm_atomic_helper_shutdown(drm); |
92caf9be | 147 | drm_mode_config_cleanup(drm); |
f5a9ed86 PK |
148 | |
149 | component_unbind_all(dev, NULL); | |
e02bc29b PK |
150 | of_reserved_mem_device_release(dev); |
151 | ||
152 | drm_dev_put(drm); | |
9026e0d1 MR |
153 | } |
154 | ||
155 | static const struct component_master_ops sun4i_drv_master_ops = { | |
156 | .bind = sun4i_drv_bind, | |
157 | .unbind = sun4i_drv_unbind, | |
158 | }; | |
159 | ||
49baeb07 MR |
160 | static bool sun4i_drv_node_is_connector(struct device_node *node) |
161 | { | |
162 | return of_device_is_compatible(node, "hdmi-connector"); | |
163 | } | |
164 | ||
9026e0d1 MR |
165 | static bool sun4i_drv_node_is_frontend(struct device_node *node) |
166 | { | |
9a8187c0 CYT |
167 | return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || |
168 | of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || | |
49c440e8 | 169 | of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || |
aaddb6d2 | 170 | of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") || |
d0ec0a3e | 171 | of_device_is_compatible(node, "allwinner,sun8i-a23-display-frontend") || |
33478959 CYT |
172 | of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend") || |
173 | of_device_is_compatible(node, "allwinner,sun9i-a80-display-frontend"); | |
174 | } | |
175 | ||
176 | static bool sun4i_drv_node_is_deu(struct device_node *node) | |
177 | { | |
178 | return of_device_is_compatible(node, "allwinner,sun9i-a80-deu"); | |
9026e0d1 MR |
179 | } |
180 | ||
dd0421f4 MR |
181 | static bool sun4i_drv_node_is_supported_frontend(struct device_node *node) |
182 | { | |
183 | if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND)) | |
184 | return !!of_match_node(sun4i_frontend_of_table, node); | |
185 | ||
186 | return false; | |
187 | } | |
188 | ||
29e57fab MR |
189 | static bool sun4i_drv_node_is_tcon(struct device_node *node) |
190 | { | |
ff71c2cf | 191 | return !!of_match_node(sun4i_tcon_of_table, node); |
29e57fab MR |
192 | } |
193 | ||
c5cf04df JS |
194 | static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node) |
195 | { | |
196 | const struct of_device_id *match; | |
197 | ||
198 | match = of_match_node(sun4i_tcon_of_table, node); | |
199 | if (match) { | |
200 | struct sun4i_tcon_quirks *quirks; | |
201 | ||
202 | quirks = (struct sun4i_tcon_quirks *)match->data; | |
203 | ||
204 | return quirks->has_channel_0; | |
205 | } | |
206 | ||
207 | return false; | |
208 | } | |
209 | ||
ef0cf644 JS |
210 | static bool sun4i_drv_node_is_tcon_top(struct device_node *node) |
211 | { | |
58d4d298 AB |
212 | return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
213 | !!of_match_node(sun8i_tcon_top_of_table, node); | |
ef0cf644 JS |
214 | } |
215 | ||
9026e0d1 MR |
216 | static int compare_of(struct device *dev, void *data) |
217 | { | |
4bf99144 RH |
218 | DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n", |
219 | dev->of_node, | |
220 | data); | |
9026e0d1 MR |
221 | |
222 | return dev->of_node == data; | |
223 | } | |
224 | ||
da82b878 CYT |
225 | /* |
226 | * The encoder drivers use drm_of_find_possible_crtcs to get upstream | |
227 | * crtcs from the device tree using of_graph. For the results to be | |
228 | * correct, encoders must be probed/bound after _all_ crtcs have been | |
229 | * created. The existing code uses a depth first recursive traversal | |
230 | * of the of_graph, which means the encoders downstream of the TCON | |
231 | * get add right after the first TCON. The second TCON or CRTC will | |
232 | * never be properly associated with encoders connected to it. | |
233 | * | |
234 | * Also, in a dual display pipeline setup, both frontends can feed | |
235 | * either backend, and both backends can feed either TCON, we want | |
236 | * all components of the same type to be added before the next type | |
237 | * in the pipeline. Fortunately, the pipelines are perfectly symmetric, | |
238 | * i.e. components of the same type are at the same depth when counted | |
239 | * from the frontend. The only exception is the third pipeline in | |
240 | * the A80 SoC, which we do not support anyway. | |
241 | * | |
242 | * Hence we can use a breadth first search traversal order to add | |
243 | * components. We do not need to check for duplicates. The component | |
244 | * matching system handles this for us. | |
245 | */ | |
246 | struct endpoint_list { | |
8b11aafa | 247 | DECLARE_KFIFO(fifo, struct device_node *, 16); |
da82b878 CYT |
248 | }; |
249 | ||
71f4796a JS |
250 | static void sun4i_drv_traverse_endpoints(struct endpoint_list *list, |
251 | struct device_node *node, | |
252 | int port_id) | |
253 | { | |
254 | struct device_node *ep, *remote, *port; | |
255 | ||
256 | port = of_graph_get_port_by_id(node, port_id); | |
257 | if (!port) { | |
258 | DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id); | |
259 | return; | |
260 | } | |
261 | ||
262 | for_each_available_child_of_node(port, ep) { | |
263 | remote = of_graph_get_remote_port_parent(ep); | |
264 | if (!remote) { | |
265 | DRM_DEBUG_DRIVER("Error retrieving the output node\n"); | |
266 | continue; | |
267 | } | |
268 | ||
71f4796a | 269 | if (sun4i_drv_node_is_tcon(node)) { |
ef0cf644 JS |
270 | /* |
271 | * TCON TOP is always probed before TCON. However, TCON | |
272 | * points back to TCON TOP when it is source for HDMI. | |
273 | * We have to skip it here to prevent infinite looping | |
274 | * between TCON TOP and TCON. | |
275 | */ | |
276 | if (sun4i_drv_node_is_tcon_top(remote)) { | |
277 | DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n"); | |
278 | of_node_put(remote); | |
279 | continue; | |
280 | } | |
281 | ||
c5cf04df JS |
282 | /* |
283 | * If the node is our TCON with channel 0, the first | |
284 | * port is used for panel or bridges, and will not be | |
285 | * part of the component framework. | |
286 | */ | |
287 | if (sun4i_drv_node_is_tcon_with_ch0(node)) { | |
288 | struct of_endpoint endpoint; | |
289 | ||
290 | if (of_graph_parse_endpoint(ep, &endpoint)) { | |
291 | DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); | |
292 | of_node_put(remote); | |
293 | continue; | |
294 | } | |
295 | ||
296 | if (!endpoint.id) { | |
297 | DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); | |
298 | of_node_put(remote); | |
299 | continue; | |
300 | } | |
71f4796a JS |
301 | } |
302 | } | |
303 | ||
304 | kfifo_put(&list->fifo, remote); | |
305 | } | |
306 | } | |
307 | ||
9026e0d1 | 308 | static int sun4i_drv_add_endpoints(struct device *dev, |
8b11aafa | 309 | struct endpoint_list *list, |
9026e0d1 MR |
310 | struct component_match **match, |
311 | struct device_node *node) | |
312 | { | |
9026e0d1 MR |
313 | int count = 0; |
314 | ||
315 | /* | |
dd0421f4 MR |
316 | * The frontend has been disabled in some of our old device |
317 | * trees. If we find a node that is the frontend and is | |
318 | * disabled, we should just follow through and parse its | |
319 | * child, but without adding it to the component list. | |
320 | * Otherwise, we obviously want to add it to the list. | |
9026e0d1 MR |
321 | */ |
322 | if (!sun4i_drv_node_is_frontend(node) && | |
323 | !of_device_is_available(node)) | |
324 | return 0; | |
325 | ||
49baeb07 MR |
326 | /* |
327 | * The connectors will be the last nodes in our pipeline, we | |
328 | * can just bail out. | |
329 | */ | |
330 | if (sun4i_drv_node_is_connector(node)) | |
331 | return 0; | |
332 | ||
dd0421f4 MR |
333 | /* |
334 | * If the device is either just a regular device, or an | |
335 | * enabled frontend supported by the driver, we add it to our | |
336 | * component list. | |
337 | */ | |
33478959 CYT |
338 | if (!(sun4i_drv_node_is_frontend(node) || |
339 | sun4i_drv_node_is_deu(node)) || | |
dd0421f4 MR |
340 | (sun4i_drv_node_is_supported_frontend(node) && |
341 | of_device_is_available(node))) { | |
9026e0d1 | 342 | /* Add current component */ |
4bf99144 | 343 | DRM_DEBUG_DRIVER("Adding component %pOF\n", node); |
97ac0e47 | 344 | drm_of_component_match_add(dev, match, compare_of, node); |
9026e0d1 MR |
345 | count++; |
346 | } | |
347 | ||
71f4796a JS |
348 | /* each node has at least one output */ |
349 | sun4i_drv_traverse_endpoints(list, node, 1); | |
9026e0d1 | 350 | |
ef0cf644 JS |
351 | /* TCON TOP has second and third output */ |
352 | if (sun4i_drv_node_is_tcon_top(node)) { | |
353 | sun4i_drv_traverse_endpoints(list, node, 3); | |
354 | sun4i_drv_traverse_endpoints(list, node, 5); | |
355 | } | |
356 | ||
9026e0d1 MR |
357 | return count; |
358 | } | |
359 | ||
360 | static int sun4i_drv_probe(struct platform_device *pdev) | |
361 | { | |
362 | struct component_match *match = NULL; | |
8b11aafa MR |
363 | struct device_node *np = pdev->dev.of_node, *endpoint; |
364 | struct endpoint_list list; | |
da82b878 | 365 | int i, ret, count = 0; |
8b11aafa MR |
366 | |
367 | INIT_KFIFO(list.fifo); | |
9026e0d1 MR |
368 | |
369 | for (i = 0;; i++) { | |
370 | struct device_node *pipeline = of_parse_phandle(np, | |
371 | "allwinner,pipelines", | |
372 | i); | |
373 | if (!pipeline) | |
374 | break; | |
375 | ||
8b11aafa | 376 | kfifo_put(&list.fifo, pipeline); |
da82b878 CYT |
377 | } |
378 | ||
8b11aafa | 379 | while (kfifo_get(&list.fifo, &endpoint)) { |
da82b878 | 380 | /* process this endpoint */ |
8b11aafa MR |
381 | ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, |
382 | endpoint); | |
da82b878 CYT |
383 | |
384 | /* sun4i_drv_add_endpoints can fail to allocate memory */ | |
385 | if (ret < 0) | |
8b11aafa | 386 | return ret; |
da82b878 CYT |
387 | |
388 | count += ret; | |
9026e0d1 MR |
389 | } |
390 | ||
391 | if (count) | |
392 | return component_master_add_with_match(&pdev->dev, | |
393 | &sun4i_drv_master_ops, | |
394 | match); | |
395 | else | |
396 | return 0; | |
397 | } | |
398 | ||
399 | static int sun4i_drv_remove(struct platform_device *pdev) | |
400 | { | |
f5a9ed86 PK |
401 | component_master_del(&pdev->dev, &sun4i_drv_master_ops); |
402 | ||
9026e0d1 MR |
403 | return 0; |
404 | } | |
405 | ||
406 | static const struct of_device_id sun4i_drv_of_table[] = { | |
9a8187c0 | 407 | { .compatible = "allwinner,sun4i-a10-display-engine" }, |
110d33dd | 408 | { .compatible = "allwinner,sun5i-a10s-display-engine" }, |
9026e0d1 | 409 | { .compatible = "allwinner,sun5i-a13-display-engine" }, |
49c440e8 CYT |
410 | { .compatible = "allwinner,sun6i-a31-display-engine" }, |
411 | { .compatible = "allwinner,sun6i-a31s-display-engine" }, | |
aaddb6d2 | 412 | { .compatible = "allwinner,sun7i-a20-display-engine" }, |
d0ec0a3e | 413 | { .compatible = "allwinner,sun8i-a23-display-engine" }, |
4a408f1f | 414 | { .compatible = "allwinner,sun8i-a33-display-engine" }, |
2f0d7bb1 | 415 | { .compatible = "allwinner,sun8i-a83t-display-engine" }, |
1ceb5f1b | 416 | { .compatible = "allwinner,sun8i-h3-display-engine" }, |
3dcf0f30 | 417 | { .compatible = "allwinner,sun8i-r40-display-engine" }, |
9df90c25 | 418 | { .compatible = "allwinner,sun8i-v3s-display-engine" }, |
33478959 | 419 | { .compatible = "allwinner,sun9i-a80-display-engine" }, |
dd8bd547 | 420 | { .compatible = "allwinner,sun50i-a64-display-engine" }, |
97f2930f | 421 | { .compatible = "allwinner,sun50i-h6-display-engine" }, |
9026e0d1 MR |
422 | { } |
423 | }; | |
424 | MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); | |
425 | ||
426 | static struct platform_driver sun4i_drv_platform_driver = { | |
427 | .probe = sun4i_drv_probe, | |
428 | .remove = sun4i_drv_remove, | |
429 | .driver = { | |
430 | .name = "sun4i-drm", | |
431 | .of_match_table = sun4i_drv_of_table, | |
432 | }, | |
433 | }; | |
434 | module_platform_driver(sun4i_drv_platform_driver); | |
435 | ||
436 | MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); | |
437 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | |
438 | MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); | |
439 | MODULE_LICENSE("GPL"); |