Commit | Line | Data |
---|---|---|
d8f4a9ed TR |
1 | /* |
2 | * Copyright (C) 2012 Avionic Design GmbH | |
3 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/module.h> | |
11 | #include <linux/of_address.h> | |
12 | #include <linux/of_platform.h> | |
13 | ||
d8f4a9ed TR |
14 | #include <linux/dma-mapping.h> |
15 | #include <asm/dma-iommu.h> | |
16 | ||
692e6d7b | 17 | #include "host1x_client.h" |
d8f4a9ed TR |
18 | #include "drm.h" |
19 | ||
20 | #define DRIVER_NAME "tegra" | |
21 | #define DRIVER_DESC "NVIDIA Tegra graphics" | |
22 | #define DRIVER_DATE "20120330" | |
23 | #define DRIVER_MAJOR 0 | |
24 | #define DRIVER_MINOR 0 | |
25 | #define DRIVER_PATCHLEVEL 0 | |
26 | ||
692e6d7b TB |
27 | struct host1x_drm_client { |
28 | struct host1x_client *client; | |
29 | struct device_node *np; | |
30 | struct list_head list; | |
31 | }; | |
32 | ||
33 | static int host1x_add_drm_client(struct host1x_drm *host1x, | |
34 | struct device_node *np) | |
35 | { | |
36 | struct host1x_drm_client *client; | |
37 | ||
38 | client = kzalloc(sizeof(*client), GFP_KERNEL); | |
39 | if (!client) | |
40 | return -ENOMEM; | |
41 | ||
42 | INIT_LIST_HEAD(&client->list); | |
43 | client->np = of_node_get(np); | |
44 | ||
45 | list_add_tail(&client->list, &host1x->drm_clients); | |
46 | ||
47 | return 0; | |
48 | } | |
49 | ||
50 | static int host1x_activate_drm_client(struct host1x_drm *host1x, | |
51 | struct host1x_drm_client *drm, | |
52 | struct host1x_client *client) | |
53 | { | |
54 | mutex_lock(&host1x->drm_clients_lock); | |
55 | list_del_init(&drm->list); | |
56 | list_add_tail(&drm->list, &host1x->drm_active); | |
57 | drm->client = client; | |
58 | mutex_unlock(&host1x->drm_clients_lock); | |
59 | ||
60 | return 0; | |
61 | } | |
62 | ||
63 | static int host1x_remove_drm_client(struct host1x_drm *host1x, | |
64 | struct host1x_drm_client *client) | |
65 | { | |
66 | mutex_lock(&host1x->drm_clients_lock); | |
67 | list_del_init(&client->list); | |
68 | mutex_unlock(&host1x->drm_clients_lock); | |
69 | ||
70 | of_node_put(client->np); | |
71 | kfree(client); | |
72 | ||
73 | return 0; | |
74 | } | |
75 | ||
76 | static int host1x_parse_dt(struct host1x_drm *host1x) | |
77 | { | |
78 | static const char * const compat[] = { | |
79 | "nvidia,tegra20-dc", | |
80 | "nvidia,tegra20-hdmi", | |
81 | "nvidia,tegra30-dc", | |
82 | "nvidia,tegra30-hdmi", | |
83 | }; | |
84 | unsigned int i; | |
85 | int err; | |
86 | ||
87 | for (i = 0; i < ARRAY_SIZE(compat); i++) { | |
88 | struct device_node *np; | |
89 | ||
90 | for_each_child_of_node(host1x->dev->of_node, np) { | |
91 | if (of_device_is_compatible(np, compat[i]) && | |
92 | of_device_is_available(np)) { | |
93 | err = host1x_add_drm_client(host1x, np); | |
94 | if (err < 0) | |
95 | return err; | |
96 | } | |
97 | } | |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
103 | int host1x_drm_alloc(struct platform_device *pdev) | |
104 | { | |
105 | struct host1x_drm *host1x; | |
106 | int err; | |
107 | ||
108 | host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL); | |
109 | if (!host1x) | |
110 | return -ENOMEM; | |
111 | ||
112 | mutex_init(&host1x->drm_clients_lock); | |
113 | INIT_LIST_HEAD(&host1x->drm_clients); | |
114 | INIT_LIST_HEAD(&host1x->drm_active); | |
115 | mutex_init(&host1x->clients_lock); | |
116 | INIT_LIST_HEAD(&host1x->clients); | |
117 | host1x->dev = &pdev->dev; | |
118 | ||
119 | err = host1x_parse_dt(host1x); | |
120 | if (err < 0) { | |
121 | dev_err(&pdev->dev, "failed to parse DT: %d\n", err); | |
122 | return err; | |
123 | } | |
124 | ||
125 | host1x_set_drm_data(&pdev->dev, host1x); | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm) | |
131 | { | |
132 | struct host1x_client *client; | |
133 | ||
134 | mutex_lock(&host1x->clients_lock); | |
135 | ||
136 | list_for_each_entry(client, &host1x->clients, list) { | |
137 | if (client->ops && client->ops->drm_init) { | |
138 | int err = client->ops->drm_init(client, drm); | |
139 | if (err < 0) { | |
140 | dev_err(host1x->dev, | |
141 | "DRM setup failed for %s: %d\n", | |
142 | dev_name(client->dev), err); | |
143 | return err; | |
144 | } | |
145 | } | |
146 | } | |
147 | ||
148 | mutex_unlock(&host1x->clients_lock); | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | int host1x_drm_exit(struct host1x_drm *host1x) | |
154 | { | |
155 | struct platform_device *pdev = to_platform_device(host1x->dev); | |
156 | struct host1x_client *client; | |
157 | ||
158 | if (!host1x->drm) | |
159 | return 0; | |
160 | ||
161 | mutex_lock(&host1x->clients_lock); | |
162 | ||
163 | list_for_each_entry_reverse(client, &host1x->clients, list) { | |
164 | if (client->ops && client->ops->drm_exit) { | |
165 | int err = client->ops->drm_exit(client); | |
166 | if (err < 0) { | |
167 | dev_err(host1x->dev, | |
168 | "DRM cleanup failed for %s: %d\n", | |
169 | dev_name(client->dev), err); | |
170 | return err; | |
171 | } | |
172 | } | |
173 | } | |
174 | ||
175 | mutex_unlock(&host1x->clients_lock); | |
176 | ||
177 | drm_platform_exit(&tegra_drm_driver, pdev); | |
178 | host1x->drm = NULL; | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
183 | int host1x_register_client(struct host1x_drm *host1x, | |
184 | struct host1x_client *client) | |
185 | { | |
186 | struct host1x_drm_client *drm, *tmp; | |
187 | int err; | |
188 | ||
189 | mutex_lock(&host1x->clients_lock); | |
190 | list_add_tail(&client->list, &host1x->clients); | |
191 | mutex_unlock(&host1x->clients_lock); | |
192 | ||
193 | list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list) | |
194 | if (drm->np == client->dev->of_node) | |
195 | host1x_activate_drm_client(host1x, drm, client); | |
196 | ||
197 | if (list_empty(&host1x->drm_clients)) { | |
198 | struct platform_device *pdev = to_platform_device(host1x->dev); | |
199 | ||
200 | err = drm_platform_init(&tegra_drm_driver, pdev); | |
201 | if (err < 0) { | |
202 | dev_err(host1x->dev, "drm_platform_init(): %d\n", err); | |
203 | return err; | |
204 | } | |
205 | } | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | int host1x_unregister_client(struct host1x_drm *host1x, | |
211 | struct host1x_client *client) | |
212 | { | |
213 | struct host1x_drm_client *drm, *tmp; | |
214 | int err; | |
215 | ||
216 | list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) { | |
217 | if (drm->client == client) { | |
218 | err = host1x_drm_exit(host1x); | |
219 | if (err < 0) { | |
220 | dev_err(host1x->dev, "host1x_drm_exit(): %d\n", | |
221 | err); | |
222 | return err; | |
223 | } | |
224 | ||
225 | host1x_remove_drm_client(host1x, drm); | |
226 | break; | |
227 | } | |
228 | } | |
229 | ||
230 | mutex_lock(&host1x->clients_lock); | |
231 | list_del_init(&client->list); | |
232 | mutex_unlock(&host1x->clients_lock); | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
d8f4a9ed TR |
237 | static int tegra_drm_load(struct drm_device *drm, unsigned long flags) |
238 | { | |
c89c0ea6 | 239 | struct host1x_drm *host1x; |
d8f4a9ed TR |
240 | int err; |
241 | ||
692e6d7b | 242 | host1x = host1x_get_drm_data(drm->dev); |
d8f4a9ed TR |
243 | drm->dev_private = host1x; |
244 | host1x->drm = drm; | |
245 | ||
246 | drm_mode_config_init(drm); | |
247 | ||
248 | err = host1x_drm_init(host1x, drm); | |
249 | if (err < 0) | |
250 | return err; | |
251 | ||
6e5ff998 TR |
252 | err = drm_vblank_init(drm, drm->mode_config.num_crtc); |
253 | if (err < 0) | |
254 | return err; | |
255 | ||
d8f4a9ed TR |
256 | err = tegra_drm_fb_init(drm); |
257 | if (err < 0) | |
258 | return err; | |
259 | ||
260 | drm_kms_helper_poll_init(drm); | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | static int tegra_drm_unload(struct drm_device *drm) | |
266 | { | |
267 | drm_kms_helper_poll_fini(drm); | |
268 | tegra_drm_fb_exit(drm); | |
269 | ||
270 | drm_mode_config_cleanup(drm); | |
271 | ||
272 | return 0; | |
273 | } | |
274 | ||
275 | static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp) | |
276 | { | |
277 | return 0; | |
278 | } | |
279 | ||
280 | static void tegra_drm_lastclose(struct drm_device *drm) | |
281 | { | |
c89c0ea6 | 282 | struct host1x_drm *host1x = drm->dev_private; |
d8f4a9ed TR |
283 | |
284 | drm_fbdev_cma_restore_mode(host1x->fbdev); | |
285 | } | |
286 | ||
287 | static struct drm_ioctl_desc tegra_drm_ioctls[] = { | |
288 | }; | |
289 | ||
290 | static const struct file_operations tegra_drm_fops = { | |
291 | .owner = THIS_MODULE, | |
292 | .open = drm_open, | |
293 | .release = drm_release, | |
294 | .unlocked_ioctl = drm_ioctl, | |
295 | .mmap = drm_gem_cma_mmap, | |
296 | .poll = drm_poll, | |
297 | .fasync = drm_fasync, | |
298 | .read = drm_read, | |
299 | #ifdef CONFIG_COMPAT | |
300 | .compat_ioctl = drm_compat_ioctl, | |
301 | #endif | |
302 | .llseek = noop_llseek, | |
303 | }; | |
304 | ||
6e5ff998 TR |
305 | static struct drm_crtc *tegra_crtc_from_pipe(struct drm_device *drm, int pipe) |
306 | { | |
307 | struct drm_crtc *crtc; | |
308 | ||
309 | list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) { | |
310 | struct tegra_dc *dc = to_tegra_dc(crtc); | |
311 | ||
312 | if (dc->pipe == pipe) | |
313 | return crtc; | |
314 | } | |
315 | ||
316 | return NULL; | |
317 | } | |
318 | ||
319 | static u32 tegra_drm_get_vblank_counter(struct drm_device *dev, int crtc) | |
320 | { | |
321 | /* TODO: implement real hardware counter using syncpoints */ | |
322 | return drm_vblank_count(dev, crtc); | |
323 | } | |
324 | ||
325 | static int tegra_drm_enable_vblank(struct drm_device *drm, int pipe) | |
326 | { | |
327 | struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); | |
328 | struct tegra_dc *dc = to_tegra_dc(crtc); | |
329 | ||
330 | if (!crtc) | |
331 | return -ENODEV; | |
332 | ||
333 | tegra_dc_enable_vblank(dc); | |
334 | ||
335 | return 0; | |
336 | } | |
337 | ||
338 | static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe) | |
339 | { | |
340 | struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); | |
341 | struct tegra_dc *dc = to_tegra_dc(crtc); | |
342 | ||
343 | if (crtc) | |
344 | tegra_dc_disable_vblank(dc); | |
345 | } | |
346 | ||
3c03c46a TR |
347 | static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) |
348 | { | |
349 | struct drm_crtc *crtc; | |
350 | ||
351 | list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) | |
352 | tegra_dc_cancel_page_flip(crtc, file); | |
353 | } | |
354 | ||
e450fcc6 TR |
355 | #ifdef CONFIG_DEBUG_FS |
356 | static int tegra_debugfs_framebuffers(struct seq_file *s, void *data) | |
357 | { | |
358 | struct drm_info_node *node = (struct drm_info_node *)s->private; | |
359 | struct drm_device *drm = node->minor->dev; | |
360 | struct drm_framebuffer *fb; | |
361 | ||
362 | mutex_lock(&drm->mode_config.fb_lock); | |
363 | ||
364 | list_for_each_entry(fb, &drm->mode_config.fb_list, head) { | |
365 | seq_printf(s, "%3d: user size: %d x %d, depth %d, %d bpp, refcount %d\n", | |
366 | fb->base.id, fb->width, fb->height, fb->depth, | |
367 | fb->bits_per_pixel, | |
368 | atomic_read(&fb->refcount.refcount)); | |
369 | } | |
370 | ||
371 | mutex_unlock(&drm->mode_config.fb_lock); | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | static struct drm_info_list tegra_debugfs_list[] = { | |
377 | { "framebuffers", tegra_debugfs_framebuffers, 0 }, | |
378 | }; | |
379 | ||
380 | static int tegra_debugfs_init(struct drm_minor *minor) | |
381 | { | |
382 | return drm_debugfs_create_files(tegra_debugfs_list, | |
383 | ARRAY_SIZE(tegra_debugfs_list), | |
384 | minor->debugfs_root, minor); | |
385 | } | |
386 | ||
387 | static void tegra_debugfs_cleanup(struct drm_minor *minor) | |
388 | { | |
389 | drm_debugfs_remove_files(tegra_debugfs_list, | |
390 | ARRAY_SIZE(tegra_debugfs_list), minor); | |
391 | } | |
392 | #endif | |
393 | ||
d8f4a9ed TR |
394 | struct drm_driver tegra_drm_driver = { |
395 | .driver_features = DRIVER_BUS_PLATFORM | DRIVER_MODESET | DRIVER_GEM, | |
396 | .load = tegra_drm_load, | |
397 | .unload = tegra_drm_unload, | |
398 | .open = tegra_drm_open, | |
3c03c46a | 399 | .preclose = tegra_drm_preclose, |
d8f4a9ed TR |
400 | .lastclose = tegra_drm_lastclose, |
401 | ||
6e5ff998 TR |
402 | .get_vblank_counter = tegra_drm_get_vblank_counter, |
403 | .enable_vblank = tegra_drm_enable_vblank, | |
404 | .disable_vblank = tegra_drm_disable_vblank, | |
405 | ||
e450fcc6 TR |
406 | #if defined(CONFIG_DEBUG_FS) |
407 | .debugfs_init = tegra_debugfs_init, | |
408 | .debugfs_cleanup = tegra_debugfs_cleanup, | |
409 | #endif | |
410 | ||
d8f4a9ed TR |
411 | .gem_free_object = drm_gem_cma_free_object, |
412 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
413 | .dumb_create = drm_gem_cma_dumb_create, | |
414 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, | |
415 | .dumb_destroy = drm_gem_cma_dumb_destroy, | |
416 | ||
417 | .ioctls = tegra_drm_ioctls, | |
418 | .num_ioctls = ARRAY_SIZE(tegra_drm_ioctls), | |
419 | .fops = &tegra_drm_fops, | |
420 | ||
421 | .name = DRIVER_NAME, | |
422 | .desc = DRIVER_DESC, | |
423 | .date = DRIVER_DATE, | |
424 | .major = DRIVER_MAJOR, | |
425 | .minor = DRIVER_MINOR, | |
426 | .patchlevel = DRIVER_PATCHLEVEL, | |
427 | }; |