aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/tegra/dc/ext/dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/tegra/dc/ext/dev.c')
-rw-r--r--drivers/video/tegra/dc/ext/dev.c975
1 files changed, 975 insertions, 0 deletions
diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c
new file mode 100644
index 00000000000..04553e77839
--- /dev/null
+++ b/drivers/video/tegra/dc/ext/dev.c
@@ -0,0 +1,975 @@
1/*
2 * drivers/video/tegra/dc/dev.c
3 *
4 * Copyright (C) 2011-2012, NVIDIA Corporation
5 *
6 * Author: Robert Morell <rmorell@nvidia.com>
7 * Some code based on fbdev extensions written by:
8 * Erik Gilling <konkers@android.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
19 */
20
21#include <linux/file.h>
22#include <linux/fs.h>
23#include <linux/uaccess.h>
24#include <linux/slab.h>
25#include <linux/workqueue.h>
26
27#include <video/tegra_dc_ext.h>
28
29#include <mach/dc.h>
30#include <mach/nvmap.h>
31#include <mach/tegra_dc_ext.h>
32
33/* XXX ew */
34#include "../dc_priv.h"
35/* XXX ew 2 */
36#include "../../host/dev.h"
37/* XXX ew 3 */
38#include "../../nvmap/nvmap.h"
39#include "tegra_dc_ext_priv.h"
40
41int tegra_dc_ext_devno;
42struct class *tegra_dc_ext_class;
43static int head_count;
44
45struct tegra_dc_ext_flip_win {
46 struct tegra_dc_ext_flip_windowattr attr;
47 struct nvmap_handle_ref *handle[TEGRA_DC_NUM_PLANES];
48 dma_addr_t phys_addr;
49 dma_addr_t phys_addr_u;
50 dma_addr_t phys_addr_v;
51 u32 syncpt_max;
52};
53
54struct tegra_dc_ext_flip_data {
55 struct tegra_dc_ext *ext;
56 struct work_struct work;
57 struct tegra_dc_ext_flip_win win[DC_N_WINDOWS];
58};
59
60int tegra_dc_ext_get_num_outputs(void)
61{
62 /* TODO: decouple output count from head count */
63 return head_count;
64}
65
66static int tegra_dc_ext_set_nvmap_fd(struct tegra_dc_ext_user *user,
67 int fd)
68{
69 struct nvmap_client *nvmap = NULL;
70
71 if (fd >= 0) {
72 nvmap = nvmap_client_get_file(fd);
73 if (IS_ERR(nvmap))
74 return PTR_ERR(nvmap);
75 }
76
77 if (user->nvmap)
78 nvmap_client_put(user->nvmap);
79
80 user->nvmap = nvmap;
81
82 return 0;
83}
84
85static int tegra_dc_ext_get_window(struct tegra_dc_ext_user *user,
86 unsigned int n)
87{
88 struct tegra_dc_ext *ext = user->ext;
89 struct tegra_dc_ext_win *win;
90 int ret = 0;
91
92 if (n >= DC_N_WINDOWS)
93 return -EINVAL;
94
95 win = &ext->win[n];
96
97 mutex_lock(&win->lock);
98
99 if (!win->user)
100 win->user = user;
101 else if (win->user != user)
102 ret = -EBUSY;
103
104 mutex_unlock(&win->lock);
105
106 return ret;
107}
108
109static int tegra_dc_ext_put_window(struct tegra_dc_ext_user *user,
110 unsigned int n)
111{
112 struct tegra_dc_ext *ext = user->ext;
113 struct tegra_dc_ext_win *win;
114 int ret = 0;
115
116 if (n >= DC_N_WINDOWS)
117 return -EINVAL;
118
119 win = &ext->win[n];
120
121 mutex_lock(&win->lock);
122
123 if (win->user == user) {
124 flush_workqueue(win->flip_wq);
125 win->user = 0;
126 } else {
127 ret = -EACCES;
128 }
129
130 mutex_unlock(&win->lock);
131
132 return ret;
133}
134
135static void set_enable(struct tegra_dc_ext *ext, bool en)
136{
137 int i;
138
139 /*
140 * Take all locks to make sure any flip requests or cursor moves are
141 * out of their critical sections
142 */
143 for (i = 0; i < ext->dc->n_windows; i++)
144 mutex_lock(&ext->win[i].lock);
145 mutex_lock(&ext->cursor.lock);
146
147 ext->enabled = en;
148
149 mutex_unlock(&ext->cursor.lock);
150 for (i = ext->dc->n_windows - 1; i >= 0 ; i--)
151 mutex_unlock(&ext->win[i].lock);
152}
153
154void tegra_dc_ext_enable(struct tegra_dc_ext *ext)
155{
156 set_enable(ext, true);
157}
158
159void tegra_dc_ext_disable(struct tegra_dc_ext *ext)
160{
161 int i;
162 set_enable(ext, false);
163
164 /*
165 * Flush the flip queue -- note that this must be called with dc->lock
166 * unlocked or else it will hang.
167 */
168 for (i = 0; i < ext->dc->n_windows; i++) {
169 struct tegra_dc_ext_win *win = &ext->win[i];
170
171 flush_workqueue(win->flip_wq);
172 }
173}
174
175static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext,
176 struct tegra_dc_win *win,
177 const struct tegra_dc_ext_flip_win *flip_win)
178{
179 struct tegra_dc_ext_win *ext_win = &ext->win[win->idx];
180
181 if (flip_win->handle[TEGRA_DC_Y] == NULL) {
182 win->flags = 0;
183 memset(ext_win->cur_handle, 0, sizeof(ext_win->cur_handle));
184 return 0;
185 }
186
187 win->flags = TEGRA_WIN_FLAG_ENABLED;
188 if (flip_win->attr.blend == TEGRA_DC_EXT_BLEND_PREMULT)
189 win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT;
190 else if (flip_win->attr.blend == TEGRA_DC_EXT_BLEND_COVERAGE)
191 win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE;
192 if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_TILED)
193 win->flags |= TEGRA_WIN_FLAG_TILED;
194 if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_H)
195 win->flags |= TEGRA_WIN_FLAG_INVERT_H;
196 if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_V)
197 win->flags |= TEGRA_WIN_FLAG_INVERT_V;
198 win->fmt = flip_win->attr.pixformat;
199 win->x.full = flip_win->attr.x;
200 win->y.full = flip_win->attr.y;
201 win->w.full = flip_win->attr.w;
202 win->h.full = flip_win->attr.h;
203 /* XXX verify that this doesn't go outside display's active region */
204 win->out_x = flip_win->attr.out_x;
205 win->out_y = flip_win->attr.out_y;
206 win->out_w = flip_win->attr.out_w;
207 win->out_h = flip_win->attr.out_h;
208 win->z = flip_win->attr.z;
209 memcpy(ext_win->cur_handle, flip_win->handle,
210 sizeof(ext_win->cur_handle));
211
212 /* XXX verify that this won't read outside of the surface */
213 win->phys_addr = flip_win->phys_addr + flip_win->attr.offset;
214
215 win->phys_addr_u = flip_win->handle[TEGRA_DC_U] ?
216 flip_win->phys_addr_u : flip_win->phys_addr;
217 win->phys_addr_u += flip_win->attr.offset_u;
218
219 win->phys_addr_v = flip_win->handle[TEGRA_DC_V] ?
220 flip_win->phys_addr_v : flip_win->phys_addr;
221 win->phys_addr_v += flip_win->attr.offset_v;
222
223 win->stride = flip_win->attr.stride;
224 win->stride_uv = flip_win->attr.stride_uv;
225
226 if ((s32)flip_win->attr.pre_syncpt_id >= 0) {
227 nvhost_syncpt_wait_timeout(
228 &nvhost_get_host(ext->dc->ndev)->syncpt,
229 flip_win->attr.pre_syncpt_id,
230 flip_win->attr.pre_syncpt_val,
231 msecs_to_jiffies(500), NULL);
232 }
233
234
235 return 0;
236}
237
238static void tegra_dc_ext_flip_worker(struct work_struct *work)
239{
240 struct tegra_dc_ext_flip_data *data =
241 container_of(work, struct tegra_dc_ext_flip_data, work);
242 struct tegra_dc_ext *ext = data->ext;
243 struct tegra_dc_win *wins[DC_N_WINDOWS];
244 struct nvmap_handle_ref *unpin_handles[DC_N_WINDOWS *
245 TEGRA_DC_NUM_PLANES];
246 struct nvmap_handle_ref *old_handle;
247 int i, nr_unpin = 0, nr_win = 0;
248 bool skip_flip = false;
249
250 for (i = 0; i < DC_N_WINDOWS; i++) {
251 struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
252 int index = flip_win->attr.index;
253 struct tegra_dc_win *win;
254 struct tegra_dc_ext_win *ext_win;
255
256 if (index < 0)
257 continue;
258
259 win = tegra_dc_get_window(ext->dc, index);
260 ext_win = &ext->win[index];
261
262 if (!(atomic_dec_and_test(&ext_win->nr_pending_flips)) &&
263 (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR))
264 skip_flip = true;
265
266 if (win->flags & TEGRA_WIN_FLAG_ENABLED) {
267 int j;
268 for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) {
269 if (skip_flip)
270 old_handle = flip_win->handle[j];
271 else
272 old_handle = ext_win->cur_handle[j];
273
274 if (!old_handle)
275 continue;
276
277 unpin_handles[nr_unpin++] = old_handle;
278 }
279 }
280
281 if (!skip_flip)
282 tegra_dc_ext_set_windowattr(ext, win, &data->win[i]);
283
284 wins[nr_win++] = win;
285 }
286
287 if (!skip_flip) {
288 tegra_dc_update_windows(wins, nr_win);
289 /* TODO: implement swapinterval here */
290 tegra_dc_sync_windows(wins, nr_win);
291 }
292
293 for (i = 0; i < DC_N_WINDOWS; i++) {
294 struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
295 int index = flip_win->attr.index;
296
297 if (index < 0)
298 continue;
299
300 tegra_dc_incr_syncpt_min(ext->dc, index,
301 flip_win->syncpt_max);
302 }
303
304 /* unpin and deref previous front buffers */
305 for (i = 0; i < nr_unpin; i++) {
306 nvmap_unpin(ext->nvmap, unpin_handles[i]);
307 nvmap_free(ext->nvmap, unpin_handles[i]);
308 }
309
310 kfree(data);
311}
312
313static int lock_windows_for_flip(struct tegra_dc_ext_user *user,
314 struct tegra_dc_ext_flip *args)
315{
316 struct tegra_dc_ext *ext = user->ext;
317 u8 idx_mask = 0;
318 int i;
319
320 for (i = 0; i < DC_N_WINDOWS; i++) {
321 int index = args->win[i].index;
322
323 if (index < 0)
324 continue;
325
326 idx_mask |= BIT(index);
327 }
328
329 for (i = 0; i < DC_N_WINDOWS; i++) {
330 struct tegra_dc_ext_win *win;
331
332 if (!(idx_mask & BIT(i)))
333 continue;
334
335 win = &ext->win[i];
336
337 mutex_lock(&win->lock);
338
339 if (win->user != user)
340 goto fail_unlock;
341 }
342
343 return 0;
344
345fail_unlock:
346 do {
347 if (!(idx_mask & BIT(i)))
348 continue;
349
350 mutex_unlock(&ext->win[i].lock);
351 } while (i--);
352
353 return -EACCES;
354}
355
356static void unlock_windows_for_flip(struct tegra_dc_ext_user *user,
357 struct tegra_dc_ext_flip *args)
358{
359 struct tegra_dc_ext *ext = user->ext;
360 u8 idx_mask = 0;
361 int i;
362
363 for (i = 0; i < DC_N_WINDOWS; i++) {
364 int index = args->win[i].index;
365
366 if (index < 0)
367 continue;
368
369 idx_mask |= BIT(index);
370 }
371
372 for (i = DC_N_WINDOWS - 1; i >= 0; i--) {
373 if (!(idx_mask & BIT(i)))
374 continue;
375
376 mutex_unlock(&ext->win[i].lock);
377 }
378}
379
380static int sanitize_flip_args(struct tegra_dc_ext_user *user,
381 struct tegra_dc_ext_flip *args)
382{
383 int i, used_windows = 0;
384
385 for (i = 0; i < DC_N_WINDOWS; i++) {
386 int index = args->win[i].index;
387
388 if (index < 0)
389 continue;
390
391 if (index >= DC_N_WINDOWS)
392 return -EINVAL;
393
394 if (used_windows & BIT(index))
395 return -EINVAL;
396
397 used_windows |= BIT(index);
398 }
399
400 if (!used_windows)
401 return -EINVAL;
402
403 return 0;
404}
405
406static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user,
407 struct tegra_dc_ext_flip *args)
408{
409 struct tegra_dc_ext *ext = user->ext;
410 struct tegra_dc_ext_flip_data *data;
411 int work_index;
412 int i, ret = 0;
413
414#ifdef CONFIG_ANDROID
415 int index_check[DC_N_WINDOWS] = {0, };
416 int zero_index_id = 0;
417#endif
418
419 if (!user->nvmap)
420 return -EFAULT;
421
422 ret = sanitize_flip_args(user, args);
423 if (ret)
424 return ret;
425
426 data = kzalloc(sizeof(*data), GFP_KERNEL);
427 if (!data)
428 return -ENOMEM;
429
430 INIT_WORK(&data->work, tegra_dc_ext_flip_worker);
431 data->ext = ext;
432
433#ifdef CONFIG_ANDROID
434 for (i = 0; i < DC_N_WINDOWS; i++) {
435 index_check[i] = args->win[i].index;
436 if (index_check[i] == 0)
437 zero_index_id = i;
438 }
439
440 if (index_check[DC_N_WINDOWS - 1] != 0) {
441 struct tegra_dc_ext_flip_windowattr win_temp;
442 win_temp = args->win[DC_N_WINDOWS - 1];
443 args->win[DC_N_WINDOWS - 1] = args->win[zero_index_id];
444 args->win[zero_index_id] = win_temp;
445 }
446#endif
447
448 for (i = 0; i < DC_N_WINDOWS; i++) {
449 struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
450 int index = args->win[i].index;
451
452 memcpy(&flip_win->attr, &args->win[i], sizeof(flip_win->attr));
453
454 if (index < 0)
455 continue;
456
457 ret = tegra_dc_ext_pin_window(user, flip_win->attr.buff_id,
458 &flip_win->handle[TEGRA_DC_Y],
459 &flip_win->phys_addr);
460 if (ret)
461 goto fail_pin;
462
463 if (flip_win->attr.buff_id_u) {
464 ret = tegra_dc_ext_pin_window(user,
465 flip_win->attr.buff_id_u,
466 &flip_win->handle[TEGRA_DC_U],
467 &flip_win->phys_addr_u);
468 if (ret)
469 goto fail_pin;
470 } else {
471 flip_win->handle[TEGRA_DC_U] = NULL;
472 flip_win->phys_addr_u = 0;
473 }
474
475 if (flip_win->attr.buff_id_v) {
476 ret = tegra_dc_ext_pin_window(user,
477 flip_win->attr.buff_id_v,
478 &flip_win->handle[TEGRA_DC_V],
479 &flip_win->phys_addr_v);
480 if (ret)
481 goto fail_pin;
482 } else {
483 flip_win->handle[TEGRA_DC_V] = NULL;
484 flip_win->phys_addr_v = 0;
485 }
486 }
487
488 ret = lock_windows_for_flip(user, args);
489 if (ret)
490 goto fail_pin;
491
492 if (!ext->enabled) {
493 ret = -ENXIO;
494 goto unlock;
495 }
496
497 for (i = 0; i < DC_N_WINDOWS; i++) {
498 u32 syncpt_max;
499 int index = args->win[i].index;
500 struct tegra_dc_win *win;
501 struct tegra_dc_ext_win *ext_win;
502
503 if (index < 0)
504 continue;
505
506 win = tegra_dc_get_window(ext->dc, index);
507 ext_win = &ext->win[index];
508
509 syncpt_max = tegra_dc_incr_syncpt_max(ext->dc, index);
510
511 data->win[i].syncpt_max = syncpt_max;
512
513 /*
514 * Any of these windows' syncpoints should be equivalent for
515 * the client, so we just send back an arbitrary one of them
516 */
517 args->post_syncpt_val = syncpt_max;
518 args->post_syncpt_id = tegra_dc_get_syncpt_id(ext->dc, index);
519 work_index = index;
520
521 atomic_inc(&ext->win[work_index].nr_pending_flips);
522 }
523 queue_work(ext->win[work_index].flip_wq, &data->work);
524
525 unlock_windows_for_flip(user, args);
526
527 return 0;
528
529unlock:
530 unlock_windows_for_flip(user, args);
531
532fail_pin:
533 for (i = 0; i < DC_N_WINDOWS; i++) {
534 int j;
535 for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) {
536 if (!data->win[i].handle[j])
537 continue;
538
539 nvmap_unpin(ext->nvmap, data->win[i].handle[j]);
540 nvmap_free(ext->nvmap, data->win[i].handle[j]);
541 }
542 }
543 kfree(data);
544
545 return ret;
546}
547
548static int tegra_dc_ext_set_csc(struct tegra_dc_ext_user *user,
549 struct tegra_dc_ext_csc *new_csc)
550{
551 unsigned int index = new_csc->win_index;
552 struct tegra_dc *dc = user->ext->dc;
553 struct tegra_dc_ext_win *ext_win;
554 struct tegra_dc_csc *csc;
555
556 if (index >= DC_N_WINDOWS)
557 return -EINVAL;
558
559 ext_win = &user->ext->win[index];
560 csc = &dc->windows[index].csc;
561
562 mutex_lock(&ext_win->lock);
563
564 if (ext_win->user != user) {
565 mutex_unlock(&ext_win->lock);
566 return -EACCES;
567 }
568
569 csc->yof = new_csc->yof;
570 csc->kyrgb = new_csc->kyrgb;
571 csc->kur = new_csc->kur;
572 csc->kvr = new_csc->kvr;
573 csc->kug = new_csc->kug;
574 csc->kvg = new_csc->kvg;
575 csc->kub = new_csc->kub;
576 csc->kvb = new_csc->kvb;
577
578 tegra_dc_update_csc(dc, index);
579
580 mutex_unlock(&ext_win->lock);
581
582 return 0;
583}
584
585static int set_lut_channel(u16 *channel_from_user,
586 u8 *channel_to,
587 u32 start,
588 u32 len)
589{
590 int i;
591 u16 lut16bpp[256];
592
593 if (channel_from_user) {
594 if (copy_from_user(lut16bpp, channel_from_user, len<<1))
595 return 1;
596
597 for (i = 0; i < len; i++)
598 channel_to[start+i] = lut16bpp[i]>>8;
599 } else {
600 for (i = 0; i < len; i++)
601 channel_to[start+i] = start+i;
602 }
603
604 return 0;
605}
606
607static int tegra_dc_ext_set_lut(struct tegra_dc_ext_user *user,
608 struct tegra_dc_ext_lut *new_lut)
609{
610 int err;
611 unsigned int index = new_lut->win_index;
612 u32 start = new_lut->start;
613 u32 len = new_lut->len;
614
615 struct tegra_dc *dc = user->ext->dc;
616 struct tegra_dc_ext_win *ext_win;
617 struct tegra_dc_lut *lut;
618
619 if (index >= DC_N_WINDOWS)
620 return -EINVAL;
621
622 if ((start >= 256) || (len > 256) || ((start + len) > 256))
623 return -EINVAL;
624
625 ext_win = &user->ext->win[index];
626 lut = &dc->windows[index].lut;
627
628 mutex_lock(&ext_win->lock);
629
630 if (ext_win->user != user) {
631 mutex_unlock(&ext_win->lock);
632 return -EACCES;
633 }
634
635 err = set_lut_channel(new_lut->r, lut->r, start, len) |
636 set_lut_channel(new_lut->g, lut->g, start, len) |
637 set_lut_channel(new_lut->b, lut->b, start, len);
638
639 if (err) {
640 mutex_unlock(&ext_win->lock);
641 return -EFAULT;
642 }
643
644 tegra_dc_update_lut(dc, index,
645 new_lut->flags & TEGRA_DC_EXT_LUT_FLAGS_FBOVERRIDE);
646
647 mutex_unlock(&ext_win->lock);
648
649 return 0;
650}
651
652static u32 tegra_dc_ext_get_vblank_syncpt(struct tegra_dc_ext_user *user)
653{
654 struct tegra_dc *dc = user->ext->dc;
655
656 return dc->vblank_syncpt;
657}
658
659static int tegra_dc_ext_get_status(struct tegra_dc_ext_user *user,
660 struct tegra_dc_ext_status *status)
661{
662 struct tegra_dc *dc = user->ext->dc;
663
664 memset(status, 0, sizeof(*status));
665
666 if (dc->enabled)
667 status->flags |= TEGRA_DC_EXT_FLAGS_ENABLED;
668
669 return 0;
670}
671
672static long tegra_dc_ioctl(struct file *filp, unsigned int cmd,
673 unsigned long arg)
674{
675 void __user *user_arg = (void __user *)arg;
676 struct tegra_dc_ext_user *user = filp->private_data;
677
678 switch (cmd) {
679 case TEGRA_DC_EXT_SET_NVMAP_FD:
680 return tegra_dc_ext_set_nvmap_fd(user, arg);
681
682 case TEGRA_DC_EXT_GET_WINDOW:
683 return tegra_dc_ext_get_window(user, arg);
684 case TEGRA_DC_EXT_PUT_WINDOW:
685 return tegra_dc_ext_put_window(user, arg);
686
687 case TEGRA_DC_EXT_FLIP:
688 {
689 struct tegra_dc_ext_flip args;
690 int ret;
691
692 if (copy_from_user(&args, user_arg, sizeof(args)))
693 return -EFAULT;
694
695 ret = tegra_dc_ext_flip(user, &args);
696
697 if (copy_to_user(user_arg, &args, sizeof(args)))
698 return -EFAULT;
699
700 return ret;
701 }
702
703 case TEGRA_DC_EXT_GET_CURSOR:
704 return tegra_dc_ext_get_cursor(user);
705 case TEGRA_DC_EXT_PUT_CURSOR:
706 return tegra_dc_ext_put_cursor(user);
707 case TEGRA_DC_EXT_SET_CURSOR_IMAGE:
708 {
709 struct tegra_dc_ext_cursor_image args;
710
711 if (copy_from_user(&args, user_arg, sizeof(args)))
712 return -EFAULT;
713
714 return tegra_dc_ext_set_cursor_image(user, &args);
715 }
716 case TEGRA_DC_EXT_SET_CURSOR:
717 {
718 struct tegra_dc_ext_cursor args;
719
720 if (copy_from_user(&args, user_arg, sizeof(args)))
721 return -EFAULT;
722
723 return tegra_dc_ext_set_cursor(user, &args);
724 }
725
726 case TEGRA_DC_EXT_SET_CSC:
727 {
728 struct tegra_dc_ext_csc args;
729
730 if (copy_from_user(&args, user_arg, sizeof(args)))
731 return -EFAULT;
732
733 return tegra_dc_ext_set_csc(user, &args);
734 }
735
736 case TEGRA_DC_EXT_GET_VBLANK_SYNCPT:
737 {
738 u32 syncpt = tegra_dc_ext_get_vblank_syncpt(user);
739
740 if (copy_to_user(user_arg, &syncpt, sizeof(syncpt)))
741 return -EFAULT;
742
743 return 0;
744 }
745
746 case TEGRA_DC_EXT_GET_STATUS:
747 {
748 struct tegra_dc_ext_status args;
749 int ret;
750
751 ret = tegra_dc_ext_get_status(user, &args);
752
753 if (copy_to_user(user_arg, &args, sizeof(args)))
754 return -EFAULT;
755
756 return ret;
757 }
758
759 case TEGRA_DC_EXT_SET_LUT:
760 {
761 struct tegra_dc_ext_lut args;
762
763 if (copy_from_user(&args, user_arg, sizeof(args)))
764 return -EFAULT;
765
766 return tegra_dc_ext_set_lut(user, &args);
767 }
768
769 default:
770 return -EINVAL;
771 }
772}
773
774static int tegra_dc_open(struct inode *inode, struct file *filp)
775{
776 struct tegra_dc_ext_user *user;
777 struct tegra_dc_ext *ext;
778
779 user = kzalloc(sizeof(*user), GFP_KERNEL);
780 if (!user)
781 return -ENOMEM;
782
783 ext = container_of(inode->i_cdev, struct tegra_dc_ext, cdev);
784 user->ext = ext;
785
786 filp->private_data = user;
787
788 return 0;
789}
790
791static int tegra_dc_release(struct inode *inode, struct file *filp)
792{
793 struct tegra_dc_ext_user *user = filp->private_data;
794 struct tegra_dc_ext *ext = user->ext;
795 unsigned int i;
796
797 for (i = 0; i < DC_N_WINDOWS; i++) {
798 if (ext->win[i].user == user)
799 tegra_dc_ext_put_window(user, i);
800 }
801 if (ext->cursor.user == user)
802 tegra_dc_ext_put_cursor(user);
803
804 if (user->nvmap)
805 nvmap_client_put(user->nvmap);
806
807 kfree(user);
808
809 return 0;
810}
811
812static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext)
813{
814 int i, ret;
815
816 for (i = 0; i < ext->dc->n_windows; i++) {
817 struct tegra_dc_ext_win *win = &ext->win[i];
818 char name[32];
819
820 win->ext = ext;
821 win->idx = i;
822
823 snprintf(name, sizeof(name), "tegradc.%d/%c",
824 ext->dc->ndev->id, 'a' + i);
825 win->flip_wq = create_singlethread_workqueue(name);
826 if (!win->flip_wq) {
827 ret = -ENOMEM;
828 goto cleanup;
829 }
830
831 mutex_init(&win->lock);
832 }
833
834 return 0;
835
836cleanup:
837 while (i--) {
838 struct tegra_dc_ext_win *win = &ext->win[i];
839 destroy_workqueue(win->flip_wq);
840 }
841
842 return ret;
843}
844
845static const struct file_operations tegra_dc_devops = {
846 .owner = THIS_MODULE,
847 .open = tegra_dc_open,
848 .release = tegra_dc_release,
849 .unlocked_ioctl = tegra_dc_ioctl,
850};
851
852struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
853 struct tegra_dc *dc)
854{
855 int ret;
856 struct tegra_dc_ext *ext;
857 int devno;
858
859 ext = kzalloc(sizeof(*ext), GFP_KERNEL);
860 if (!ext)
861 return ERR_PTR(-ENOMEM);
862
863 BUG_ON(!tegra_dc_ext_devno);
864 devno = tegra_dc_ext_devno + head_count + 1;
865
866 cdev_init(&ext->cdev, &tegra_dc_devops);
867 ext->cdev.owner = THIS_MODULE;
868 ret = cdev_add(&ext->cdev, devno, 1);
869 if (ret) {
870 dev_err(&ndev->dev, "Failed to create character device\n");
871 goto cleanup_alloc;
872 }
873
874 ext->dev = device_create(tegra_dc_ext_class,
875 &ndev->dev,
876 devno,
877 NULL,
878 "tegra_dc_%d",
879 ndev->id);
880
881 if (IS_ERR(ext->dev)) {
882 ret = PTR_ERR(ext->dev);
883 goto cleanup_cdev;
884 }
885
886 ext->dc = dc;
887
888 ext->nvmap = nvmap_create_client(nvmap_dev, "tegra_dc_ext");
889 if (!ext->nvmap) {
890 ret = -ENOMEM;
891 goto cleanup_device;
892 }
893
894 ret = tegra_dc_ext_setup_windows(ext);
895 if (ret)
896 goto cleanup_nvmap;
897
898 mutex_init(&ext->cursor.lock);
899
900 head_count++;
901
902 return ext;
903
904cleanup_nvmap:
905 nvmap_client_put(ext->nvmap);
906
907cleanup_device:
908 device_del(ext->dev);
909
910cleanup_cdev:
911 cdev_del(&ext->cdev);
912
913cleanup_alloc:
914 kfree(ext);
915
916 return ERR_PTR(ret);
917}
918
919void tegra_dc_ext_unregister(struct tegra_dc_ext *ext)
920{
921 int i;
922
923 for (i = 0; i < ext->dc->n_windows; i++) {
924 struct tegra_dc_ext_win *win = &ext->win[i];
925
926 flush_workqueue(win->flip_wq);
927 destroy_workqueue(win->flip_wq);
928 }
929
930 nvmap_client_put(ext->nvmap);
931 device_del(ext->dev);
932 cdev_del(&ext->cdev);
933
934 kfree(ext);
935
936 head_count--;
937}
938
939int __init tegra_dc_ext_module_init(void)
940{
941 int ret;
942
943 tegra_dc_ext_class = class_create(THIS_MODULE, "tegra_dc_ext");
944 if (!tegra_dc_ext_class) {
945 printk(KERN_ERR "tegra_dc_ext: failed to create class\n");
946 return -ENOMEM;
947 }
948
949 /* Reserve one character device per head, plus the control device */
950 ret = alloc_chrdev_region(&tegra_dc_ext_devno,
951 0, TEGRA_MAX_DC + 1,
952 "tegra_dc_ext");
953 if (ret)
954 goto cleanup_class;
955
956 ret = tegra_dc_ext_control_init();
957 if (ret)
958 goto cleanup_region;
959
960 return 0;
961
962cleanup_region:
963 unregister_chrdev_region(tegra_dc_ext_devno, TEGRA_MAX_DC);
964
965cleanup_class:
966 class_destroy(tegra_dc_ext_class);
967
968 return ret;
969}
970
971void __exit tegra_dc_ext_module_exit(void)
972{
973 unregister_chrdev_region(tegra_dc_ext_devno, TEGRA_MAX_DC);
974 class_destroy(tegra_dc_ext_class);
975}