1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Rob Clark <rob@ti.com> |
5 | */ |
6 | |
7 | #include <linux/dma-mapping.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/of.h> |
10 | #include <linux/sort.h> |
11 | #include <linux/sys_soc.h> |
12 | |
13 | #include <drm/drm_atomic.h> |
14 | #include <drm/drm_atomic_helper.h> |
15 | #include <drm/drm_bridge.h> |
16 | #include <drm/drm_bridge_connector.h> |
17 | #include <drm/drm_drv.h> |
18 | #include <drm/drm_file.h> |
19 | #include <drm/drm_ioctl.h> |
20 | #include <drm/drm_panel.h> |
21 | #include <drm/drm_prime.h> |
22 | #include <drm/drm_probe_helper.h> |
23 | #include <drm/drm_vblank.h> |
24 | |
25 | #include "omap_dmm_tiler.h" |
26 | #include "omap_drv.h" |
27 | #include "omap_fbdev.h" |
28 | |
29 | #define DRIVER_NAME MODULE_NAME |
30 | #define DRIVER_DESC "OMAP DRM" |
31 | #define DRIVER_DATE "20110917" |
32 | #define DRIVER_MAJOR 1 |
33 | #define DRIVER_MINOR 0 |
34 | #define DRIVER_PATCHLEVEL 0 |
35 | |
36 | /* |
37 | * mode config funcs |
38 | */ |
39 | |
40 | /* Notes about mapping DSS and DRM entities: |
41 | * CRTC: overlay |
42 | * encoder: manager.. with some extension to allow one primary CRTC |
43 | * and zero or more video CRTC's to be mapped to one encoder? |
44 | * connector: dssdev.. manager can be attached/detached from different |
45 | * devices |
46 | */ |
47 | |
48 | static void omap_atomic_wait_for_completion(struct drm_device *dev, |
49 | struct drm_atomic_state *old_state) |
50 | { |
51 | struct drm_crtc_state *new_crtc_state; |
52 | struct drm_crtc *crtc; |
53 | unsigned int i; |
54 | int ret; |
55 | |
56 | for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) { |
57 | if (!new_crtc_state->active) |
58 | continue; |
59 | |
60 | ret = omap_crtc_wait_pending(crtc); |
61 | |
62 | if (!ret) |
63 | dev_warn(dev->dev, |
64 | "atomic complete timeout (pipe %u)!\n" , i); |
65 | } |
66 | } |
67 | |
68 | static void omap_atomic_commit_tail(struct drm_atomic_state *old_state) |
69 | { |
70 | struct drm_device *dev = old_state->dev; |
71 | struct omap_drm_private *priv = dev->dev_private; |
72 | |
73 | dispc_runtime_get(dispc: priv->dispc); |
74 | |
75 | /* Apply the atomic update. */ |
76 | drm_atomic_helper_commit_modeset_disables(dev, state: old_state); |
77 | |
78 | if (priv->omaprev != 0x3430) { |
79 | /* With the current dss dispc implementation we have to enable |
80 | * the new modeset before we can commit planes. The dispc ovl |
81 | * configuration relies on the video mode configuration been |
82 | * written into the HW when the ovl configuration is |
83 | * calculated. |
84 | * |
85 | * This approach is not ideal because after a mode change the |
86 | * plane update is executed only after the first vblank |
87 | * interrupt. The dispc implementation should be fixed so that |
88 | * it is able use uncommitted drm state information. |
89 | */ |
90 | drm_atomic_helper_commit_modeset_enables(dev, old_state); |
91 | omap_atomic_wait_for_completion(dev, old_state); |
92 | |
93 | drm_atomic_helper_commit_planes(dev, state: old_state, flags: 0); |
94 | |
95 | drm_atomic_helper_commit_hw_done(state: old_state); |
96 | } else { |
97 | /* |
98 | * OMAP3 DSS seems to have issues with the work-around above, |
99 | * resulting in endless sync losts if a crtc is enabled without |
100 | * a plane. For now, skip the WA for OMAP3. |
101 | */ |
102 | drm_atomic_helper_commit_planes(dev, state: old_state, flags: 0); |
103 | |
104 | drm_atomic_helper_commit_modeset_enables(dev, old_state); |
105 | |
106 | drm_atomic_helper_commit_hw_done(state: old_state); |
107 | } |
108 | |
109 | /* |
110 | * Wait for completion of the page flips to ensure that old buffers |
111 | * can't be touched by the hardware anymore before cleaning up planes. |
112 | */ |
113 | omap_atomic_wait_for_completion(dev, old_state); |
114 | |
115 | drm_atomic_helper_cleanup_planes(dev, old_state); |
116 | |
117 | dispc_runtime_put(dispc: priv->dispc); |
118 | } |
119 | |
120 | static int drm_atomic_state_normalized_zpos_cmp(const void *a, const void *b) |
121 | { |
122 | const struct drm_plane_state *sa = *(struct drm_plane_state **)a; |
123 | const struct drm_plane_state *sb = *(struct drm_plane_state **)b; |
124 | |
125 | if (sa->normalized_zpos != sb->normalized_zpos) |
126 | return sa->normalized_zpos - sb->normalized_zpos; |
127 | else |
128 | return sa->plane->base.id - sb->plane->base.id; |
129 | } |
130 | |
131 | /* |
132 | * This replaces the drm_atomic_normalize_zpos to handle the dual overlay case. |
133 | * |
134 | * Since both halves need to be 'appear' side by side the zpos is |
135 | * recalculated when dealing with dual overlay cases so that the other |
136 | * planes zpos is consistent. |
137 | */ |
138 | static int omap_atomic_update_normalize_zpos(struct drm_device *dev, |
139 | struct drm_atomic_state *state) |
140 | { |
141 | struct drm_crtc *crtc; |
142 | struct drm_crtc_state *old_state, *new_state; |
143 | struct drm_plane *plane; |
144 | int c, i, n, inc; |
145 | int total_planes = dev->mode_config.num_total_plane; |
146 | struct drm_plane_state **states; |
147 | int ret = 0; |
148 | |
149 | states = kmalloc_array(n: total_planes, size: sizeof(*states), GFP_KERNEL); |
150 | if (!states) |
151 | return -ENOMEM; |
152 | |
153 | for_each_oldnew_crtc_in_state(state, crtc, old_state, new_state, c) { |
154 | if (old_state->plane_mask == new_state->plane_mask && |
155 | !new_state->zpos_changed) |
156 | continue; |
157 | |
158 | /* Reset plane increment and index value for every crtc */ |
159 | n = 0; |
160 | |
161 | /* |
162 | * Normalization process might create new states for planes |
163 | * which normalized_zpos has to be recalculated. |
164 | */ |
165 | drm_for_each_plane_mask(plane, dev, new_state->plane_mask) { |
166 | struct drm_plane_state *plane_state = |
167 | drm_atomic_get_plane_state(state: new_state->state, |
168 | plane); |
169 | if (IS_ERR(ptr: plane_state)) { |
170 | ret = PTR_ERR(ptr: plane_state); |
171 | goto done; |
172 | } |
173 | states[n++] = plane_state; |
174 | } |
175 | |
176 | sort(base: states, num: n, size: sizeof(*states), |
177 | cmp_func: drm_atomic_state_normalized_zpos_cmp, NULL); |
178 | |
179 | for (i = 0, inc = 0; i < n; i++) { |
180 | plane = states[i]->plane; |
181 | |
182 | states[i]->normalized_zpos = i + inc; |
183 | DRM_DEBUG_ATOMIC("[PLANE:%d:%s] updated normalized zpos value %d\n" , |
184 | plane->base.id, plane->name, |
185 | states[i]->normalized_zpos); |
186 | |
187 | if (is_omap_plane_dual_overlay(state: states[i])) |
188 | inc++; |
189 | } |
190 | new_state->zpos_changed = true; |
191 | } |
192 | |
193 | done: |
194 | kfree(objp: states); |
195 | return ret; |
196 | } |
197 | |
198 | static int omap_atomic_check(struct drm_device *dev, |
199 | struct drm_atomic_state *state) |
200 | { |
201 | int ret; |
202 | |
203 | ret = drm_atomic_helper_check(dev, state); |
204 | if (ret) |
205 | return ret; |
206 | |
207 | if (dev->mode_config.normalize_zpos) { |
208 | ret = omap_atomic_update_normalize_zpos(dev, state); |
209 | if (ret) |
210 | return ret; |
211 | } |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static const struct drm_mode_config_helper_funcs omap_mode_config_helper_funcs = { |
217 | .atomic_commit_tail = omap_atomic_commit_tail, |
218 | }; |
219 | |
220 | static const struct drm_mode_config_funcs omap_mode_config_funcs = { |
221 | .fb_create = omap_framebuffer_create, |
222 | .atomic_check = omap_atomic_check, |
223 | .atomic_commit = drm_atomic_helper_commit, |
224 | }; |
225 | |
226 | /* Global/shared object state funcs */ |
227 | |
228 | /* |
229 | * This is a helper that returns the private state currently in operation. |
230 | * Note that this would return the "old_state" if called in the atomic check |
231 | * path, and the "new_state" after the atomic swap has been done. |
232 | */ |
233 | struct omap_global_state * |
234 | omap_get_existing_global_state(struct omap_drm_private *priv) |
235 | { |
236 | return to_omap_global_state(priv->glob_obj.state); |
237 | } |
238 | |
239 | /* |
240 | * This acquires the modeset lock set aside for global state, creates |
241 | * a new duplicated private object state. |
242 | */ |
243 | struct omap_global_state *__must_check |
244 | omap_get_global_state(struct drm_atomic_state *s) |
245 | { |
246 | struct omap_drm_private *priv = s->dev->dev_private; |
247 | struct drm_private_state *priv_state; |
248 | |
249 | priv_state = drm_atomic_get_private_obj_state(state: s, obj: &priv->glob_obj); |
250 | if (IS_ERR(ptr: priv_state)) |
251 | return ERR_CAST(ptr: priv_state); |
252 | |
253 | return to_omap_global_state(priv_state); |
254 | } |
255 | |
256 | static struct drm_private_state * |
257 | omap_global_duplicate_state(struct drm_private_obj *obj) |
258 | { |
259 | struct omap_global_state *state; |
260 | |
261 | state = kmemdup(p: obj->state, size: sizeof(*state), GFP_KERNEL); |
262 | if (!state) |
263 | return NULL; |
264 | |
265 | __drm_atomic_helper_private_obj_duplicate_state(obj, state: &state->base); |
266 | |
267 | return &state->base; |
268 | } |
269 | |
270 | static void omap_global_destroy_state(struct drm_private_obj *obj, |
271 | struct drm_private_state *state) |
272 | { |
273 | struct omap_global_state *omap_state = to_omap_global_state(state); |
274 | |
275 | kfree(objp: omap_state); |
276 | } |
277 | |
278 | static const struct drm_private_state_funcs omap_global_state_funcs = { |
279 | .atomic_duplicate_state = omap_global_duplicate_state, |
280 | .atomic_destroy_state = omap_global_destroy_state, |
281 | }; |
282 | |
283 | static int omap_global_obj_init(struct drm_device *dev) |
284 | { |
285 | struct omap_drm_private *priv = dev->dev_private; |
286 | struct omap_global_state *state; |
287 | |
288 | state = kzalloc(size: sizeof(*state), GFP_KERNEL); |
289 | if (!state) |
290 | return -ENOMEM; |
291 | |
292 | drm_atomic_private_obj_init(dev, obj: &priv->glob_obj, state: &state->base, |
293 | funcs: &omap_global_state_funcs); |
294 | return 0; |
295 | } |
296 | |
297 | static void omap_global_obj_fini(struct omap_drm_private *priv) |
298 | { |
299 | drm_atomic_private_obj_fini(obj: &priv->glob_obj); |
300 | } |
301 | |
302 | static void omap_disconnect_pipelines(struct drm_device *ddev) |
303 | { |
304 | struct omap_drm_private *priv = ddev->dev_private; |
305 | unsigned int i; |
306 | |
307 | for (i = 0; i < priv->num_pipes; i++) { |
308 | struct omap_drm_pipeline *pipe = &priv->pipes[i]; |
309 | |
310 | omapdss_device_disconnect(NULL, dst: pipe->output); |
311 | |
312 | omapdss_device_put(dssdev: pipe->output); |
313 | pipe->output = NULL; |
314 | } |
315 | |
316 | memset(&priv->channels, 0, sizeof(priv->channels)); |
317 | |
318 | priv->num_pipes = 0; |
319 | } |
320 | |
321 | static int omap_connect_pipelines(struct drm_device *ddev) |
322 | { |
323 | struct omap_drm_private *priv = ddev->dev_private; |
324 | struct omap_dss_device *output = NULL; |
325 | int r; |
326 | |
327 | for_each_dss_output(output) { |
328 | r = omapdss_device_connect(dss: priv->dss, NULL, dst: output); |
329 | if (r == -EPROBE_DEFER) { |
330 | omapdss_device_put(dssdev: output); |
331 | return r; |
332 | } else if (r) { |
333 | dev_warn(output->dev, "could not connect output %s\n" , |
334 | output->name); |
335 | } else { |
336 | struct omap_drm_pipeline *pipe; |
337 | |
338 | pipe = &priv->pipes[priv->num_pipes++]; |
339 | pipe->output = omapdss_device_get(dssdev: output); |
340 | |
341 | if (priv->num_pipes == ARRAY_SIZE(priv->pipes)) { |
342 | /* To balance the 'for_each_dss_output' loop */ |
343 | omapdss_device_put(dssdev: output); |
344 | break; |
345 | } |
346 | } |
347 | } |
348 | |
349 | return 0; |
350 | } |
351 | |
352 | static int omap_compare_pipelines(const void *a, const void *b) |
353 | { |
354 | const struct omap_drm_pipeline *pipe1 = a; |
355 | const struct omap_drm_pipeline *pipe2 = b; |
356 | |
357 | if (pipe1->alias_id > pipe2->alias_id) |
358 | return 1; |
359 | else if (pipe1->alias_id < pipe2->alias_id) |
360 | return -1; |
361 | return 0; |
362 | } |
363 | |
364 | static int omap_modeset_init_properties(struct drm_device *dev) |
365 | { |
366 | struct omap_drm_private *priv = dev->dev_private; |
367 | unsigned int num_planes = dispc_get_num_ovls(dispc: priv->dispc); |
368 | |
369 | priv->zorder_prop = drm_property_create_range(dev, flags: 0, name: "zorder" , min: 0, |
370 | max: num_planes - 1); |
371 | if (!priv->zorder_prop) |
372 | return -ENOMEM; |
373 | |
374 | return 0; |
375 | } |
376 | |
377 | static int omap_display_id(struct omap_dss_device *output) |
378 | { |
379 | struct device_node *node = NULL; |
380 | |
381 | if (output->bridge) { |
382 | struct drm_bridge *bridge = output->bridge; |
383 | |
384 | while (drm_bridge_get_next_bridge(bridge)) |
385 | bridge = drm_bridge_get_next_bridge(bridge); |
386 | |
387 | node = bridge->of_node; |
388 | } |
389 | |
390 | return node ? of_alias_get_id(np: node, stem: "display" ) : -ENODEV; |
391 | } |
392 | |
393 | static int omap_modeset_init(struct drm_device *dev) |
394 | { |
395 | struct omap_drm_private *priv = dev->dev_private; |
396 | int num_ovls = dispc_get_num_ovls(dispc: priv->dispc); |
397 | int num_mgrs = dispc_get_num_mgrs(dispc: priv->dispc); |
398 | unsigned int i; |
399 | int ret; |
400 | u32 plane_crtc_mask; |
401 | |
402 | if (!omapdss_stack_is_ready()) |
403 | return -EPROBE_DEFER; |
404 | |
405 | ret = omap_modeset_init_properties(dev); |
406 | if (ret < 0) |
407 | return ret; |
408 | |
409 | /* |
410 | * This function creates exactly one connector, encoder, crtc, |
411 | * and primary plane per each connected dss-device. Each |
412 | * connector->encoder->crtc chain is expected to be separate |
413 | * and each crtc is connect to a single dss-channel. If the |
414 | * configuration does not match the expectations or exceeds |
415 | * the available resources, the configuration is rejected. |
416 | */ |
417 | ret = omap_connect_pipelines(ddev: dev); |
418 | if (ret < 0) |
419 | return ret; |
420 | |
421 | if (priv->num_pipes > num_mgrs || priv->num_pipes > num_ovls) { |
422 | dev_err(dev->dev, "%s(): Too many connected displays\n" , |
423 | __func__); |
424 | return -EINVAL; |
425 | } |
426 | |
427 | /* Create all planes first. They can all be put to any CRTC. */ |
428 | plane_crtc_mask = (1 << priv->num_pipes) - 1; |
429 | |
430 | for (i = 0; i < num_ovls; i++) { |
431 | enum drm_plane_type type = i < priv->num_pipes |
432 | ? DRM_PLANE_TYPE_PRIMARY |
433 | : DRM_PLANE_TYPE_OVERLAY; |
434 | struct drm_plane *plane; |
435 | |
436 | if (WARN_ON(priv->num_planes >= ARRAY_SIZE(priv->planes))) |
437 | return -EINVAL; |
438 | |
439 | plane = omap_plane_init(dev, idx: i, type, possible_crtcs: plane_crtc_mask); |
440 | if (IS_ERR(ptr: plane)) |
441 | return PTR_ERR(ptr: plane); |
442 | |
443 | priv->planes[priv->num_planes++] = plane; |
444 | } |
445 | |
446 | /* |
447 | * Create the encoders, attach the bridges and get the pipeline alias |
448 | * IDs. |
449 | */ |
450 | for (i = 0; i < priv->num_pipes; i++) { |
451 | struct omap_drm_pipeline *pipe = &priv->pipes[i]; |
452 | int id; |
453 | |
454 | pipe->encoder = omap_encoder_init(dev, output: pipe->output); |
455 | if (!pipe->encoder) |
456 | return -ENOMEM; |
457 | |
458 | if (pipe->output->bridge) { |
459 | ret = drm_bridge_attach(encoder: pipe->encoder, |
460 | bridge: pipe->output->bridge, NULL, |
461 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
462 | if (ret < 0) |
463 | return ret; |
464 | } |
465 | |
466 | id = omap_display_id(output: pipe->output); |
467 | pipe->alias_id = id >= 0 ? id : i; |
468 | } |
469 | |
470 | /* Sort the pipelines by DT aliases. */ |
471 | sort(base: priv->pipes, num: priv->num_pipes, size: sizeof(priv->pipes[0]), |
472 | cmp_func: omap_compare_pipelines, NULL); |
473 | |
474 | /* |
475 | * Populate the pipeline lookup table by DISPC channel. Only one display |
476 | * is allowed per channel. |
477 | */ |
478 | for (i = 0; i < priv->num_pipes; ++i) { |
479 | struct omap_drm_pipeline *pipe = &priv->pipes[i]; |
480 | enum omap_channel channel = pipe->output->dispc_channel; |
481 | |
482 | if (WARN_ON(priv->channels[channel] != NULL)) |
483 | return -EINVAL; |
484 | |
485 | priv->channels[channel] = pipe; |
486 | } |
487 | |
488 | /* Create the connectors and CRTCs. */ |
489 | for (i = 0; i < priv->num_pipes; i++) { |
490 | struct omap_drm_pipeline *pipe = &priv->pipes[i]; |
491 | struct drm_encoder *encoder = pipe->encoder; |
492 | struct drm_crtc *crtc; |
493 | |
494 | pipe->connector = drm_bridge_connector_init(drm: dev, encoder); |
495 | if (IS_ERR(ptr: pipe->connector)) { |
496 | dev_err(priv->dev, |
497 | "unable to create bridge connector for %s\n" , |
498 | pipe->output->name); |
499 | return PTR_ERR(ptr: pipe->connector); |
500 | } |
501 | |
502 | drm_connector_attach_encoder(connector: pipe->connector, encoder); |
503 | |
504 | crtc = omap_crtc_init(dev, pipe, plane: priv->planes[i]); |
505 | if (IS_ERR(ptr: crtc)) |
506 | return PTR_ERR(ptr: crtc); |
507 | |
508 | encoder->possible_crtcs = 1 << i; |
509 | pipe->crtc = crtc; |
510 | } |
511 | |
512 | DBG("registered %u planes, %u crtcs/encoders/connectors\n" , |
513 | priv->num_planes, priv->num_pipes); |
514 | |
515 | dev->mode_config.min_width = 8; |
516 | dev->mode_config.min_height = 2; |
517 | |
518 | /* |
519 | * Note: these values are used for multiple independent things: |
520 | * connector mode filtering, buffer sizes, crtc sizes... |
521 | * Use big enough values here to cover all use cases, and do more |
522 | * specific checking in the respective code paths. |
523 | */ |
524 | dev->mode_config.max_width = 8192; |
525 | dev->mode_config.max_height = 8192; |
526 | |
527 | /* We want the zpos to be normalized */ |
528 | dev->mode_config.normalize_zpos = true; |
529 | |
530 | dev->mode_config.funcs = &omap_mode_config_funcs; |
531 | dev->mode_config.helper_private = &omap_mode_config_helper_funcs; |
532 | |
533 | drm_mode_config_reset(dev); |
534 | |
535 | omap_drm_irq_install(dev); |
536 | |
537 | return 0; |
538 | } |
539 | |
540 | static void omap_modeset_fini(struct drm_device *ddev) |
541 | { |
542 | omap_drm_irq_uninstall(dev: ddev); |
543 | |
544 | drm_mode_config_cleanup(dev: ddev); |
545 | } |
546 | |
547 | /* |
548 | * drm ioctl funcs |
549 | */ |
550 | |
551 | |
552 | static int ioctl_get_param(struct drm_device *dev, void *data, |
553 | struct drm_file *file_priv) |
554 | { |
555 | struct omap_drm_private *priv = dev->dev_private; |
556 | struct drm_omap_param *args = data; |
557 | |
558 | DBG("%p: param=%llu" , dev, args->param); |
559 | |
560 | switch (args->param) { |
561 | case OMAP_PARAM_CHIPSET_ID: |
562 | args->value = priv->omaprev; |
563 | break; |
564 | default: |
565 | DBG("unknown parameter %lld" , args->param); |
566 | return -EINVAL; |
567 | } |
568 | |
569 | return 0; |
570 | } |
571 | |
572 | #define OMAP_BO_USER_MASK 0x00ffffff /* flags settable by userspace */ |
573 | |
574 | static int ioctl_gem_new(struct drm_device *dev, void *data, |
575 | struct drm_file *file_priv) |
576 | { |
577 | struct drm_omap_gem_new *args = data; |
578 | u32 flags = args->flags & OMAP_BO_USER_MASK; |
579 | |
580 | VERB("%p:%p: size=0x%08x, flags=%08x" , dev, file_priv, |
581 | args->size.bytes, flags); |
582 | |
583 | return omap_gem_new_handle(dev, file: file_priv, gsize: args->size, flags, |
584 | handle: &args->handle); |
585 | } |
586 | |
587 | static int ioctl_gem_info(struct drm_device *dev, void *data, |
588 | struct drm_file *file_priv) |
589 | { |
590 | struct drm_omap_gem_info *args = data; |
591 | struct drm_gem_object *obj; |
592 | int ret = 0; |
593 | |
594 | VERB("%p:%p: handle=%d" , dev, file_priv, args->handle); |
595 | |
596 | obj = drm_gem_object_lookup(filp: file_priv, handle: args->handle); |
597 | if (!obj) |
598 | return -ENOENT; |
599 | |
600 | args->size = omap_gem_mmap_size(obj); |
601 | args->offset = omap_gem_mmap_offset(obj); |
602 | |
603 | drm_gem_object_put(obj); |
604 | |
605 | return ret; |
606 | } |
607 | |
608 | static const struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { |
609 | DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, |
610 | DRM_RENDER_ALLOW), |
611 | DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, drm_invalid_op, |
612 | DRM_AUTH | DRM_MASTER | DRM_ROOT_ONLY), |
613 | DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, |
614 | DRM_RENDER_ALLOW), |
615 | /* Deprecated, to be removed. */ |
616 | DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, drm_noop, |
617 | DRM_RENDER_ALLOW), |
618 | /* Deprecated, to be removed. */ |
619 | DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, drm_noop, |
620 | DRM_RENDER_ALLOW), |
621 | DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, |
622 | DRM_RENDER_ALLOW), |
623 | }; |
624 | |
625 | /* |
626 | * drm driver funcs |
627 | */ |
628 | |
629 | static int dev_open(struct drm_device *dev, struct drm_file *file) |
630 | { |
631 | file->driver_priv = NULL; |
632 | |
633 | DBG("open: dev=%p, file=%p" , dev, file); |
634 | |
635 | return 0; |
636 | } |
637 | |
638 | DEFINE_DRM_GEM_FOPS(omapdriver_fops); |
639 | |
640 | static const struct drm_driver omap_drm_driver = { |
641 | .driver_features = DRIVER_MODESET | DRIVER_GEM | |
642 | DRIVER_ATOMIC | DRIVER_RENDER, |
643 | .open = dev_open, |
644 | #ifdef CONFIG_DEBUG_FS |
645 | .debugfs_init = omap_debugfs_init, |
646 | #endif |
647 | .gem_prime_import = omap_gem_prime_import, |
648 | .dumb_create = omap_gem_dumb_create, |
649 | .dumb_map_offset = omap_gem_dumb_map_offset, |
650 | .ioctls = ioctls, |
651 | .num_ioctls = DRM_OMAP_NUM_IOCTLS, |
652 | .fops = &omapdriver_fops, |
653 | .name = DRIVER_NAME, |
654 | .desc = DRIVER_DESC, |
655 | .date = DRIVER_DATE, |
656 | .major = DRIVER_MAJOR, |
657 | .minor = DRIVER_MINOR, |
658 | .patchlevel = DRIVER_PATCHLEVEL, |
659 | }; |
660 | |
661 | static const struct soc_device_attribute omapdrm_soc_devices[] = { |
662 | { .family = "OMAP3" , .data = (void *)0x3430 }, |
663 | { .family = "OMAP4" , .data = (void *)0x4430 }, |
664 | { .family = "OMAP5" , .data = (void *)0x5430 }, |
665 | { .family = "DRA7" , .data = (void *)0x0752 }, |
666 | { /* sentinel */ } |
667 | }; |
668 | |
669 | static int omapdrm_init(struct omap_drm_private *priv, struct device *dev) |
670 | { |
671 | const struct soc_device_attribute *soc; |
672 | struct dss_pdata *pdata = dev->platform_data; |
673 | struct drm_device *ddev; |
674 | int ret; |
675 | |
676 | DBG("%s" , dev_name(dev)); |
677 | |
678 | if (drm_firmware_drivers_only()) |
679 | return -ENODEV; |
680 | |
681 | /* Allocate and initialize the DRM device. */ |
682 | ddev = drm_dev_alloc(driver: &omap_drm_driver, parent: dev); |
683 | if (IS_ERR(ptr: ddev)) |
684 | return PTR_ERR(ptr: ddev); |
685 | |
686 | priv->ddev = ddev; |
687 | ddev->dev_private = priv; |
688 | |
689 | priv->dev = dev; |
690 | priv->dss = pdata->dss; |
691 | priv->dispc = dispc_get_dispc(dss: priv->dss); |
692 | |
693 | priv->dss->mgr_ops_priv = priv; |
694 | |
695 | soc = soc_device_match(matches: omapdrm_soc_devices); |
696 | priv->omaprev = soc ? (uintptr_t)soc->data : 0; |
697 | priv->wq = alloc_ordered_workqueue("omapdrm" , 0); |
698 | |
699 | mutex_init(&priv->list_lock); |
700 | INIT_LIST_HEAD(list: &priv->obj_list); |
701 | |
702 | /* Get memory bandwidth limits */ |
703 | priv->max_bandwidth = dispc_get_memory_bandwidth_limit(dispc: priv->dispc); |
704 | |
705 | omap_gem_init(dev: ddev); |
706 | |
707 | drm_mode_config_init(dev: ddev); |
708 | |
709 | ret = omap_global_obj_init(dev: ddev); |
710 | if (ret) |
711 | goto err_gem_deinit; |
712 | |
713 | ret = omap_hwoverlays_init(priv); |
714 | if (ret) |
715 | goto err_free_priv_obj; |
716 | |
717 | ret = omap_modeset_init(dev: ddev); |
718 | if (ret) { |
719 | dev_err(priv->dev, "omap_modeset_init failed: ret=%d\n" , ret); |
720 | goto err_free_overlays; |
721 | } |
722 | |
723 | /* Initialize vblank handling, start with all CRTCs disabled. */ |
724 | ret = drm_vblank_init(dev: ddev, num_crtcs: priv->num_pipes); |
725 | if (ret) { |
726 | dev_err(priv->dev, "could not init vblank\n" ); |
727 | goto err_cleanup_modeset; |
728 | } |
729 | |
730 | drm_kms_helper_poll_init(dev: ddev); |
731 | |
732 | /* |
733 | * Register the DRM device with the core and the connectors with |
734 | * sysfs. |
735 | */ |
736 | ret = drm_dev_register(dev: ddev, flags: 0); |
737 | if (ret) |
738 | goto err_cleanup_helpers; |
739 | |
740 | omap_fbdev_setup(dev: ddev); |
741 | |
742 | return 0; |
743 | |
744 | err_cleanup_helpers: |
745 | drm_kms_helper_poll_fini(dev: ddev); |
746 | err_cleanup_modeset: |
747 | omap_modeset_fini(ddev); |
748 | err_free_overlays: |
749 | omap_hwoverlays_destroy(priv); |
750 | err_free_priv_obj: |
751 | omap_global_obj_fini(priv); |
752 | err_gem_deinit: |
753 | drm_mode_config_cleanup(dev: ddev); |
754 | omap_gem_deinit(dev: ddev); |
755 | destroy_workqueue(wq: priv->wq); |
756 | omap_disconnect_pipelines(ddev); |
757 | drm_dev_put(dev: ddev); |
758 | return ret; |
759 | } |
760 | |
761 | static void omapdrm_cleanup(struct omap_drm_private *priv) |
762 | { |
763 | struct drm_device *ddev = priv->ddev; |
764 | |
765 | DBG("" ); |
766 | |
767 | drm_dev_unregister(dev: ddev); |
768 | |
769 | drm_kms_helper_poll_fini(dev: ddev); |
770 | |
771 | drm_atomic_helper_shutdown(dev: ddev); |
772 | |
773 | omap_modeset_fini(ddev); |
774 | omap_hwoverlays_destroy(priv); |
775 | omap_global_obj_fini(priv); |
776 | drm_mode_config_cleanup(dev: ddev); |
777 | omap_gem_deinit(dev: ddev); |
778 | |
779 | destroy_workqueue(wq: priv->wq); |
780 | |
781 | omap_disconnect_pipelines(ddev); |
782 | |
783 | drm_dev_put(dev: ddev); |
784 | } |
785 | |
786 | static int pdev_probe(struct platform_device *pdev) |
787 | { |
788 | struct omap_drm_private *priv; |
789 | int ret; |
790 | |
791 | ret = dma_coerce_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(32)); |
792 | if (ret) { |
793 | dev_err(&pdev->dev, "Failed to set the DMA mask\n" ); |
794 | return ret; |
795 | } |
796 | |
797 | /* Allocate and initialize the driver private structure. */ |
798 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
799 | if (!priv) |
800 | return -ENOMEM; |
801 | |
802 | platform_set_drvdata(pdev, data: priv); |
803 | |
804 | ret = omapdrm_init(priv, dev: &pdev->dev); |
805 | if (ret < 0) |
806 | kfree(objp: priv); |
807 | |
808 | return ret; |
809 | } |
810 | |
811 | static void pdev_remove(struct platform_device *pdev) |
812 | { |
813 | struct omap_drm_private *priv = platform_get_drvdata(pdev); |
814 | |
815 | omapdrm_cleanup(priv); |
816 | kfree(objp: priv); |
817 | } |
818 | |
819 | static void pdev_shutdown(struct platform_device *pdev) |
820 | { |
821 | struct omap_drm_private *priv = platform_get_drvdata(pdev); |
822 | |
823 | drm_atomic_helper_shutdown(dev: priv->ddev); |
824 | } |
825 | |
826 | #ifdef CONFIG_PM_SLEEP |
827 | static int omap_drm_suspend(struct device *dev) |
828 | { |
829 | struct omap_drm_private *priv = dev_get_drvdata(dev); |
830 | struct drm_device *drm_dev = priv->ddev; |
831 | |
832 | return drm_mode_config_helper_suspend(dev: drm_dev); |
833 | } |
834 | |
835 | static int omap_drm_resume(struct device *dev) |
836 | { |
837 | struct omap_drm_private *priv = dev_get_drvdata(dev); |
838 | struct drm_device *drm_dev = priv->ddev; |
839 | |
840 | drm_mode_config_helper_resume(dev: drm_dev); |
841 | |
842 | return omap_gem_resume(dev: drm_dev); |
843 | } |
844 | #endif |
845 | |
846 | static SIMPLE_DEV_PM_OPS(omapdrm_pm_ops, omap_drm_suspend, omap_drm_resume); |
847 | |
848 | static struct platform_driver pdev = { |
849 | .driver = { |
850 | .name = "omapdrm" , |
851 | .pm = &omapdrm_pm_ops, |
852 | }, |
853 | .probe = pdev_probe, |
854 | .remove_new = pdev_remove, |
855 | .shutdown = pdev_shutdown, |
856 | }; |
857 | |
858 | static struct platform_driver * const drivers[] = { |
859 | &omap_dmm_driver, |
860 | &pdev, |
861 | }; |
862 | |
863 | static int __init omap_drm_init(void) |
864 | { |
865 | int r; |
866 | |
867 | DBG("init" ); |
868 | |
869 | r = omap_dss_init(); |
870 | if (r) |
871 | return r; |
872 | |
873 | r = platform_register_drivers(drivers, ARRAY_SIZE(drivers)); |
874 | if (r) { |
875 | omap_dss_exit(); |
876 | return r; |
877 | } |
878 | |
879 | return 0; |
880 | } |
881 | |
882 | static void __exit omap_drm_fini(void) |
883 | { |
884 | DBG("fini" ); |
885 | |
886 | platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); |
887 | |
888 | omap_dss_exit(); |
889 | } |
890 | |
891 | module_init(omap_drm_init); |
892 | module_exit(omap_drm_fini); |
893 | |
894 | MODULE_AUTHOR("Rob Clark <rob@ti.com>" ); |
895 | MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>" ); |
896 | MODULE_DESCRIPTION("OMAP DRM Display Driver" ); |
897 | MODULE_ALIAS("platform:" DRIVER_NAME); |
898 | MODULE_LICENSE("GPL v2" ); |
899 | |