video: tegra: fix max yres in stereo hdmi case
[linux-2.6.git] / drivers / video / tegra / fb.c
1 /*
2  * drivers/video/tegra/fb.c
3  *
4  * Copyright (C) 2010 Google, Inc.
5  * Author: Erik Gilling <konkers@android.com>
6  *         Colin Cross <ccross@android.com>
7  *         Travis Geiselbrecht <travis@palm.com>
8  *
9  * This software is licensed under the terms of the GNU General Public
10  * License version 2, as published by the Free Software Foundation, and
11  * may be copied, distributed, and modified under those terms.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  */
19
20 #include <linux/fb.h>
21 #include <linux/module.h>
22 #include <linux/kernel.h>
23 #include <linux/errno.h>
24 #include <linux/string.h>
25 #include <linux/mm.h>
26 #include <linux/uaccess.h>
27 #include <linux/slab.h>
28 #include <linux/file.h>
29 #include <linux/workqueue.h>
30
31 #include <asm/atomic.h>
32
33 #include <video/tegrafb.h>
34
35 #include <mach/dc.h>
36 #include <mach/fb.h>
37 #include <mach/nvhost.h>
38 #include <mach/nvmap.h>
39
40 #include "host/dev.h"
41 #include "nvmap/nvmap.h"
42 #include "dc/dc_priv.h"
43
44 struct tegra_fb_info {
45         struct tegra_dc_win     *win;
46         struct nvhost_device    *ndev;
47         struct fb_info          *info;
48         bool                    valid;
49
50         struct resource         *fb_mem;
51
52         int                     xres;
53         int                     yres;
54
55         atomic_t                in_use;
56         struct nvmap_client     *user_nvmap;
57         struct nvmap_client     *fb_nvmap;
58
59         struct workqueue_struct *flip_wq;
60 };
61
62 struct tegra_fb_flip_win {
63         struct tegra_fb_windowattr      attr;
64         struct nvmap_handle_ref         *handle;
65         dma_addr_t                      phys_addr;
66 };
67
68 struct tegra_fb_flip_data {
69         struct work_struct              work;
70         struct tegra_fb_info            *fb;
71         struct tegra_fb_flip_win        win[TEGRA_FB_FLIP_N_WINDOWS];
72         u32                             syncpt_max;
73 };
74
75 /* palette array used by the fbcon */
76 static u32 pseudo_palette[16];
77
78 static int tegra_fb_open(struct fb_info *info, int user)
79 {
80         struct tegra_fb_info *tegra_fb = info->par;
81
82         if (atomic_xchg(&tegra_fb->in_use, 1))
83                 return -EBUSY;
84
85         tegra_fb->user_nvmap = NULL;
86
87         return 0;
88 }
89
90 static int tegra_fb_release(struct fb_info *info, int user)
91 {
92         struct tegra_fb_info *tegra_fb = info->par;
93         struct fb_var_screeninfo *var = &info->var;
94
95         flush_workqueue(tegra_fb->flip_wq);
96
97         if (tegra_fb->win->cur_handle) {
98                 nvmap_unpin(tegra_fb->fb_nvmap, tegra_fb->win->cur_handle);
99                 nvmap_free(tegra_fb->fb_nvmap, tegra_fb->win->cur_handle);
100
101                 tegra_fb->win->cur_handle = NULL;
102
103                 tegra_fb->win->x = 0;
104                 tegra_fb->win->y = 0;
105                 tegra_fb->win->w = var->xres;
106                 tegra_fb->win->h = var->yres;
107                 tegra_fb->win->out_x = 0;
108                 tegra_fb->win->out_y = 0;
109                 tegra_fb->win->out_w = var->xres;
110                 tegra_fb->win->out_h = var->yres;
111                 tegra_fb->win->flags = TEGRA_WIN_FLAG_ENABLED;
112         }
113
114         if (tegra_fb->user_nvmap) {
115                 nvmap_client_put(tegra_fb->user_nvmap);
116                 tegra_fb->user_nvmap = NULL;
117         }
118
119         WARN_ON(!atomic_xchg(&tegra_fb->in_use, 0));
120
121         return 0;
122 }
123
124 static int tegra_fb_check_var(struct fb_var_screeninfo *var,
125                               struct fb_info *info)
126 {
127         if ((var->yres * var->xres * var->bits_per_pixel / 8 * 2) >
128             info->screen_size)
129                 return -EINVAL;
130
131         /* double yres_virtual to allow double buffering through pan_display */
132         var->yres_virtual = var->yres * 2;
133
134         return 0;
135 }
136
137 static int tegra_fb_set_par(struct fb_info *info)
138 {
139         struct tegra_fb_info *tegra_fb = info->par;
140         struct fb_var_screeninfo *var = &info->var;
141
142         if (var->bits_per_pixel) {
143                 /* we only support RGB ordering for now */
144                 switch (var->bits_per_pixel) {
145                 case 32:
146                         var->red.offset = 0;
147                         var->red.length = 8;
148                         var->green.offset = 8;
149                         var->green.length = 8;
150                         var->blue.offset = 16;
151                         var->blue.length = 8;
152                         var->transp.offset = 24;
153                         var->transp.length = 8;
154                         tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8;
155                         break;
156                 case 16:
157                         var->red.offset = 11;
158                         var->red.length = 5;
159                         var->green.offset = 5;
160                         var->green.length = 6;
161                         var->blue.offset = 0;
162                         var->blue.length = 5;
163                         tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5;
164                         break;
165
166                 default:
167                         return -EINVAL;
168                 }
169                 info->fix.line_length = var->xres * var->bits_per_pixel / 8;
170                 tegra_fb->win->stride = info->fix.line_length;
171                 tegra_fb->win->stride_uv = 0;
172                 tegra_fb->win->offset_u = 0;
173                 tegra_fb->win->offset_v = 0;
174         }
175
176         if (var->pixclock) {
177                 struct tegra_dc_mode mode;
178
179                 info->mode = (struct fb_videomode *)
180                         fb_find_best_mode(var, &info->modelist);
181                 if (!info->mode) {
182                         dev_warn(&tegra_fb->ndev->dev, "can't match video mode\n");
183                         return -EINVAL;
184                 }
185
186                 mode.pclk = PICOS2KHZ(info->mode->pixclock) * 1000;
187                 mode.h_ref_to_sync = 1;
188                 mode.v_ref_to_sync = 1;
189                 mode.h_sync_width = info->mode->hsync_len;
190                 mode.v_sync_width = info->mode->vsync_len;
191                 mode.h_back_porch = info->mode->left_margin;
192                 mode.v_back_porch = info->mode->upper_margin;
193                 mode.h_active = info->mode->xres;
194                 mode.v_active = info->mode->yres;
195                 mode.h_front_porch = info->mode->right_margin;
196                 mode.v_front_porch = info->mode->lower_margin;
197                 /*
198                  * only enable stereo if the mode supports it and
199                  * client requests it
200                  */
201                 mode.stereo_mode = !!(var->vmode & info->mode->vmode &
202                                         FB_VMODE_STEREO_FRAME_PACK);
203
204                 if (mode.stereo_mode) {
205                         mode.pclk *= 2;
206                         /* total v_active = yres*2 + activespace */
207                         mode.v_active = info->mode->yres*2 +
208                                         info->mode->vsync_len +
209                                         info->mode->upper_margin +
210                                         info->mode->lower_margin;
211                 }
212
213                 mode.flags = 0;
214
215                 if (!(info->mode->sync & FB_SYNC_HOR_HIGH_ACT))
216                         mode.flags |= TEGRA_DC_MODE_FLAG_NEG_H_SYNC;
217
218                 if (!(info->mode->sync & FB_SYNC_VERT_HIGH_ACT))
219                         mode.flags |= TEGRA_DC_MODE_FLAG_NEG_V_SYNC;
220
221                 tegra_dc_set_mode(tegra_fb->win->dc, &mode);
222
223                 tegra_fb->win->w = info->mode->xres;
224                 tegra_fb->win->h = info->mode->yres;
225                 tegra_fb->win->out_w = info->mode->xres;
226                 tegra_fb->win->out_h = info->mode->yres;
227         }
228         return 0;
229 }
230
231 static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
232         unsigned blue, unsigned transp, struct fb_info *info)
233 {
234         struct fb_var_screeninfo *var = &info->var;
235
236         if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
237             info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
238                 u32 v;
239
240                 if (regno >= 16)
241                         return -EINVAL;
242
243                 red = (red >> (16 - info->var.red.length));
244                 green = (green >> (16 - info->var.green.length));
245                 blue = (blue >> (16 - info->var.blue.length));
246
247                 v = (red << var->red.offset) |
248                         (green << var->green.offset) |
249                         (blue << var->blue.offset);
250
251                 ((u32 *)info->pseudo_palette)[regno] = v;
252         }
253
254         return 0;
255 }
256
257 static int tegra_fb_blank(int blank, struct fb_info *info)
258 {
259         struct tegra_fb_info *tegra_fb = info->par;
260
261         switch (blank) {
262         case FB_BLANK_UNBLANK:
263                 dev_dbg(&tegra_fb->ndev->dev, "unblank\n");
264                 tegra_dc_enable(tegra_fb->win->dc);
265                 return 0;
266
267         case FB_BLANK_POWERDOWN:
268                 dev_dbg(&tegra_fb->ndev->dev, "blank\n");
269                 flush_workqueue(tegra_fb->flip_wq);
270                 tegra_dc_disable(tegra_fb->win->dc);
271                 return 0;
272
273         default:
274                 return -ENOTTY;
275         }
276 }
277
278 void tegra_fb_suspend(struct tegra_fb_info *tegra_fb)
279 {
280         flush_workqueue(tegra_fb->flip_wq);
281 }
282
283
284 static int tegra_fb_pan_display(struct fb_var_screeninfo *var,
285                                 struct fb_info *info)
286 {
287         struct tegra_fb_info *tegra_fb = info->par;
288         char __iomem *flush_start;
289         char __iomem *flush_end;
290         u32 addr;
291
292         if (!tegra_fb->win->cur_handle) {
293                 flush_start = info->screen_base + (var->yoffset * info->fix.line_length);
294                 flush_end = flush_start + (var->yres * info->fix.line_length);
295
296                 info->var.xoffset = var->xoffset;
297                 info->var.yoffset = var->yoffset;
298
299                 addr = info->fix.smem_start + (var->yoffset * info->fix.line_length) +
300                         (var->xoffset * (var->bits_per_pixel/8));
301
302                 tegra_fb->win->phys_addr = addr;
303                 /* TODO: update virt_addr */
304
305                 tegra_dc_update_windows(&tegra_fb->win, 1);
306                 tegra_dc_sync_windows(&tegra_fb->win, 1);
307         }
308
309         return 0;
310 }
311
312 static void tegra_fb_fillrect(struct fb_info *info,
313                               const struct fb_fillrect *rect)
314 {
315         cfb_fillrect(info, rect);
316 }
317
318 static void tegra_fb_copyarea(struct fb_info *info,
319                               const struct fb_copyarea *region)
320 {
321         cfb_copyarea(info, region);
322 }
323
324 static void tegra_fb_imageblit(struct fb_info *info,
325                                const struct fb_image *image)
326 {
327         cfb_imageblit(info, image);
328 }
329
330 /* TODO: implement ALLOC, FREE, BLANK ioctls */
331
332 static int tegra_fb_set_nvmap_fd(struct tegra_fb_info *tegra_fb, int fd)
333 {
334         struct nvmap_client *nvmap = NULL;
335
336         if (fd < 0)
337                 return -EINVAL;
338
339         nvmap = nvmap_client_get_file(fd);
340         if (IS_ERR(nvmap))
341                 return PTR_ERR(nvmap);
342
343         if (tegra_fb->user_nvmap)
344                 nvmap_client_put(tegra_fb->user_nvmap);
345
346         tegra_fb->user_nvmap = nvmap;
347
348         return 0;
349 }
350
351 static int tegra_fb_pin_window(struct tegra_fb_info *tegra_fb,
352                                struct tegra_fb_flip_win *flip_win)
353 {
354         struct nvmap_handle_ref *win_dupe;
355         struct nvmap_handle *win_handle;
356         unsigned long buff_id = flip_win->attr.buff_id;
357
358         if (!buff_id)
359                 return 0;
360
361         win_handle = nvmap_get_handle_id(tegra_fb->user_nvmap, buff_id);
362         if (win_handle == NULL) {
363                 dev_err(&tegra_fb->ndev->dev, "%s: flip invalid "
364                         "handle %08lx\n", current->comm, buff_id);
365                 return -EPERM;
366         }
367
368         /* duplicate the new framebuffer's handle into the fb driver's
369          * nvmap context, to ensure that the handle won't be freed as
370          * long as it is in-use by the fb driver */
371         win_dupe = nvmap_duplicate_handle_id(tegra_fb->fb_nvmap, buff_id);
372         nvmap_handle_put(win_handle);
373
374         if (IS_ERR(win_dupe)) {
375                 dev_err(&tegra_fb->ndev->dev, "couldn't duplicate handle\n");
376                 return PTR_ERR(win_dupe);
377         }
378
379         flip_win->handle = win_dupe;
380
381         flip_win->phys_addr = nvmap_pin(tegra_fb->fb_nvmap, win_dupe);
382         if (IS_ERR((void *)flip_win->phys_addr)) {
383                 dev_err(&tegra_fb->ndev->dev, "couldn't pin handle\n");
384                 nvmap_free(tegra_fb->fb_nvmap, win_dupe);
385                 return PTR_ERR((void *)flip_win->phys_addr);
386         }
387
388         return 0;
389 }
390
391 static int tegra_fb_set_windowattr(struct tegra_fb_info *tegra_fb,
392                                    struct tegra_dc_win *win,
393                                    const struct tegra_fb_flip_win *flip_win)
394 {
395         int xres, yres;
396         if (flip_win->handle == NULL) {
397                 win->flags = 0;
398                 win->cur_handle = NULL;
399                 return 0;
400         }
401
402         xres = tegra_fb->win->dc->mode.h_active;
403         yres = tegra_fb->win->dc->mode.v_active;
404
405         win->flags = TEGRA_WIN_FLAG_ENABLED;
406         if (flip_win->attr.blend == TEGRA_FB_WIN_BLEND_PREMULT)
407                 win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT;
408         else if (flip_win->attr.blend == TEGRA_FB_WIN_BLEND_COVERAGE)
409                 win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE;
410         if (flip_win->attr.flags & TEGRA_FB_WIN_FLAG_INVERT_H)
411                 win->flags |= TEGRA_WIN_FLAG_INVERT_H;
412         if (flip_win->attr.flags & TEGRA_FB_WIN_FLAG_INVERT_V)
413                 win->flags |= TEGRA_WIN_FLAG_INVERT_V;
414         if (flip_win->attr.flags & TEGRA_FB_WIN_FLAG_TILED)
415                 win->flags |= TEGRA_WIN_FLAG_TILED;
416
417         win->fmt = flip_win->attr.pixformat;
418         win->x = flip_win->attr.x;
419         win->y = flip_win->attr.y;
420         win->w = flip_win->attr.w;
421         win->h = flip_win->attr.h;
422         win->out_x = flip_win->attr.out_x;
423         win->out_y = flip_win->attr.out_y;
424         win->out_w = flip_win->attr.out_w;
425         win->out_h = flip_win->attr.out_h;
426
427         if (((win->out_x + win->out_w) > xres) && (win->out_x < xres)) {
428                 win->out_w = xres - win->out_x;
429         }
430
431         if (((win->out_y + win->out_h) > yres) && (win->out_y < yres)) {
432                 win->out_h = yres - win->out_y;
433         }
434
435         win->z = flip_win->attr.z;
436         win->cur_handle = flip_win->handle;
437
438         /* STOPSHIP verify that this won't read outside of the surface */
439         win->phys_addr = flip_win->phys_addr + flip_win->attr.offset;
440         win->offset_u = flip_win->attr.offset_u + flip_win->attr.offset;
441         win->offset_v = flip_win->attr.offset_v + flip_win->attr.offset;
442         win->stride = flip_win->attr.stride;
443         win->stride_uv = flip_win->attr.stride_uv;
444
445         if ((s32)flip_win->attr.pre_syncpt_id >= 0) {
446                 nvhost_syncpt_wait_timeout(&tegra_fb->ndev->host->syncpt,
447                                            flip_win->attr.pre_syncpt_id,
448                                            flip_win->attr.pre_syncpt_val,
449                                            msecs_to_jiffies(500));
450         }
451
452
453         return 0;
454 }
455
456 static void tegra_fb_flip_worker(struct work_struct *work)
457 {
458         struct tegra_fb_flip_data *data =
459                 container_of(work, struct tegra_fb_flip_data, work);
460         struct tegra_fb_info *tegra_fb = data->fb;
461         struct tegra_dc_win *win;
462         struct tegra_dc_win *wins[TEGRA_FB_FLIP_N_WINDOWS];
463         struct nvmap_handle_ref *unpin_handles[TEGRA_FB_FLIP_N_WINDOWS];
464         int i, nr_win = 0, nr_unpin = 0;
465
466         data = container_of(work, struct tegra_fb_flip_data, work);
467
468         for (i = 0; i < TEGRA_FB_FLIP_N_WINDOWS; i++) {
469                 struct tegra_fb_flip_win *flip_win = &data->win[i];
470                 int idx = flip_win->attr.index;
471                 win = tegra_dc_get_window(tegra_fb->win->dc, idx);
472
473                 if (!win)
474                         continue;
475
476                 if (win->flags && win->cur_handle)
477                         unpin_handles[nr_unpin++] = win->cur_handle;
478
479                 tegra_fb_set_windowattr(tegra_fb, win, &data->win[i]);
480
481                 wins[nr_win++] = win;
482
483 #if 0
484                 if (flip_win->attr.pre_syncpt_id < 0)
485                         continue;
486                 printk("%08x %08x\n",
487                        flip_win->attr.pre_syncpt_id,
488                        flip_win->attr.pre_syncpt_val);
489
490                 nvhost_syncpt_wait_timeout(&tegra_fb->ndev->host->syncpt,
491                                            flip_win->attr.pre_syncpt_id,
492                                            flip_win->attr.pre_syncpt_val,
493                                            msecs_to_jiffies(500));
494 #endif
495         }
496
497         tegra_dc_update_windows(wins, nr_win);
498         /* TODO: implement swapinterval here */
499         tegra_dc_sync_windows(wins, nr_win);
500
501         tegra_dc_incr_syncpt_min(tegra_fb->win->dc, data->syncpt_max);
502
503         /* unpin and deref previous front buffers */
504         for (i = 0; i < nr_unpin; i++) {
505                 nvmap_unpin(tegra_fb->fb_nvmap, unpin_handles[i]);
506                 nvmap_free(tegra_fb->fb_nvmap, unpin_handles[i]);
507         }
508
509         kfree(data);
510 }
511
512 static int tegra_fb_flip(struct tegra_fb_info *tegra_fb,
513                          struct tegra_fb_flip_args *args)
514 {
515         struct tegra_fb_flip_data *data;
516         struct tegra_fb_flip_win *flip_win;
517         u32 syncpt_max;
518         int i, err;
519
520         if (WARN_ON(!tegra_fb->user_nvmap))
521                 return -EFAULT;
522
523         if (WARN_ON(!tegra_fb->ndev))
524                 return -EFAULT;
525
526         data = kzalloc(sizeof(*data), GFP_KERNEL);
527         if (data == NULL) {
528                 dev_err(&tegra_fb->ndev->dev,
529                         "can't allocate memory for flip\n");
530                 return -ENOMEM;
531         }
532
533         INIT_WORK(&data->work, tegra_fb_flip_worker);
534         data->fb = tegra_fb;
535
536         for (i = 0; i < TEGRA_FB_FLIP_N_WINDOWS; i++) {
537                 flip_win = &data->win[i];
538
539                 memcpy(&flip_win->attr, &args->win[i], sizeof(flip_win->attr));
540
541                 err = tegra_fb_pin_window(tegra_fb, flip_win);
542                 if (err < 0) {
543                         dev_err(&tegra_fb->ndev->dev,
544                                 "error setting window attributes\n");
545                         goto surf_err;
546                 }
547         }
548
549         syncpt_max = tegra_dc_incr_syncpt_max(tegra_fb->win->dc);
550         data->syncpt_max = syncpt_max;
551
552         queue_work(tegra_fb->flip_wq, &data->work);
553
554         args->post_syncpt_val = syncpt_max;
555         args->post_syncpt_id = tegra_dc_get_syncpt_id(tegra_fb->win->dc);
556
557         return 0;
558
559 surf_err:
560         while (i--) {
561                 if (data->win[i].handle) {
562                         nvmap_unpin(tegra_fb->fb_nvmap,
563                                     data->win[i].handle);
564                         nvmap_free(tegra_fb->fb_nvmap,
565                                    data->win[i].handle);
566                 }
567         }
568         kfree(data);
569         return err;
570 }
571
572 /* TODO: implement private window ioctls to set overlay x,y */
573
574 static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
575 {
576         struct tegra_fb_info *tegra_fb = info->par;
577         struct tegra_fb_flip_args flip_args;
578         struct tegra_fb_modedb modedb;
579         struct fb_modelist *modelist;
580         int i;
581         int fd;
582         int ret;
583
584         switch (cmd) {
585         case FBIO_TEGRA_SET_NVMAP_FD:
586                 if (copy_from_user(&fd, (void __user *)arg, sizeof(fd)))
587                         return -EFAULT;
588
589                 return tegra_fb_set_nvmap_fd(tegra_fb, fd);
590
591         case FBIO_TEGRA_FLIP:
592                 if (copy_from_user(&flip_args, (void __user *)arg, sizeof(flip_args)))
593                         return -EFAULT;
594
595                 ret = tegra_fb_flip(tegra_fb, &flip_args);
596
597                 if (copy_to_user((void __user *)arg, &flip_args, sizeof(flip_args)))
598                         return -EFAULT;
599
600                 return ret;
601
602         case FBIO_TEGRA_GET_MODEDB:
603                 if (copy_from_user(&modedb, (void __user *)arg, sizeof(modedb)))
604                         return -EFAULT;
605
606                 i = 0;
607                 list_for_each_entry(modelist, &info->modelist, list) {
608                         struct fb_var_screeninfo var;
609
610                         if (i >= modedb.modedb_len)
611                                 break;
612                         memset(&var, 0x0, sizeof(var));
613                         fb_videomode_to_var(&var, &modelist->mode);
614
615                         if (copy_to_user((void __user *)&modedb.modedb[i],
616                                          &var, sizeof(var)))
617                                 return -EFAULT;
618                         i++;
619
620                         if (var.vmode & FB_VMODE_STEREO_MASK) {
621                                 if (i >= modedb.modedb_len)
622                                         break;
623                                 var.vmode &= ~FB_VMODE_STEREO_MASK;
624                                 if (copy_to_user(
625                                         (void __user *)&modedb.modedb[i],
626                                          &var, sizeof(var)))
627                                         return -EFAULT;
628                                 i++;
629                         }
630                 }
631                 modedb.modedb_len = i;
632
633                 if (copy_to_user((void __user *)arg, &modedb, sizeof(modedb)))
634                         return -EFAULT;
635                 break;
636
637         default:
638                 return -ENOTTY;
639         }
640
641         return 0;
642 }
643
644 static struct fb_ops tegra_fb_ops = {
645         .owner = THIS_MODULE,
646         .fb_open = tegra_fb_open,
647         .fb_release = tegra_fb_release,
648         .fb_check_var = tegra_fb_check_var,
649         .fb_set_par = tegra_fb_set_par,
650         .fb_setcolreg = tegra_fb_setcolreg,
651         .fb_blank = tegra_fb_blank,
652         .fb_pan_display = tegra_fb_pan_display,
653         .fb_fillrect = tegra_fb_fillrect,
654         .fb_copyarea = tegra_fb_copyarea,
655         .fb_imageblit = tegra_fb_imageblit,
656         .fb_ioctl = tegra_fb_ioctl,
657 };
658
659 void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
660                               struct fb_monspecs *specs,
661                               bool (*mode_filter)(struct fb_videomode *mode))
662 {
663         struct fb_event event;
664         struct fb_modelist *m;
665         int i;
666
667         mutex_lock(&fb_info->info->lock);
668         fb_destroy_modedb(fb_info->info->monspecs.modedb);
669
670         fb_destroy_modelist(&fb_info->info->modelist);
671
672         if (specs == NULL) {
673                 struct tegra_dc_mode mode;
674                 memset(&fb_info->info->monspecs, 0x0,
675                        sizeof(fb_info->info->monspecs));
676                 memset(&mode, 0x0, sizeof(mode));
677                 tegra_dc_set_mode(fb_info->win->dc, &mode);
678                 mutex_unlock(&fb_info->info->lock);
679                 return;
680         }
681
682         memcpy(&fb_info->info->monspecs, specs,
683                sizeof(fb_info->info->monspecs));
684
685         for (i = 0; i < specs->modedb_len; i++) {
686                 if (mode_filter) {
687                         if (mode_filter(&specs->modedb[i]))
688                                 fb_add_videomode(&specs->modedb[i],
689                                                  &fb_info->info->modelist);
690                 } else {
691                         fb_add_videomode(&specs->modedb[i],
692                                          &fb_info->info->modelist);
693                 }
694         }
695
696         if (list_empty(&fb_info->info->modelist)) {
697                 struct tegra_dc_mode mode;
698                 memset(&fb_info->info->var, 0x0, sizeof(fb_info->info->var));
699                 memset(&mode, 0x0, sizeof(mode));
700                 tegra_dc_set_mode(fb_info->win->dc, &mode);
701         } else {
702                 /* in case the first mode was not matched */
703                 m = list_first_entry(&fb_info->info->modelist, struct fb_modelist, list);
704                 m->mode.flag |= FB_MODE_IS_FIRST;
705                 fb_info->info->mode = (struct fb_videomode *)
706                         fb_find_best_display(specs, &fb_info->info->modelist);
707
708                 memset(&fb_info->info->var, 0x0,
709                        sizeof(fb_info->info->var));
710                 fb_videomode_to_var(&fb_info->info->var, fb_info->info->mode);
711                 tegra_fb_set_par(fb_info->info);
712         }
713
714         event.info = fb_info->info;
715         fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event);
716         mutex_unlock(&fb_info->info->lock);
717 }
718
719 struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
720                                         struct tegra_dc *dc,
721                                         struct tegra_fb_data *fb_data,
722                                         struct resource *fb_mem)
723 {
724         struct tegra_dc_win *win;
725         struct fb_info *info;
726         struct tegra_fb_info *tegra_fb;
727         void __iomem *fb_base = NULL;
728         unsigned long fb_size = 0;
729         unsigned long fb_phys = 0;
730         int ret = 0;
731
732         win = tegra_dc_get_window(dc, fb_data->win);
733         if (!win) {
734                 dev_err(&ndev->dev, "dc does not have a window at index %d\n",
735                         fb_data->win);
736                 return ERR_PTR(-ENOENT);
737         }
738
739         info = framebuffer_alloc(sizeof(struct tegra_fb_info), &ndev->dev);
740         if (!info) {
741                 ret = -ENOMEM;
742                 goto err;
743         }
744
745         tegra_fb = info->par;
746         tegra_fb->win = win;
747         tegra_fb->ndev = ndev;
748         tegra_fb->fb_mem = fb_mem;
749         tegra_fb->xres = fb_data->xres;
750         tegra_fb->yres = fb_data->yres;
751         tegra_fb->fb_nvmap = nvmap_create_client(nvmap_dev, "tegra-fb");
752         if (!tegra_fb->fb_nvmap) {
753                 dev_err(&ndev->dev, "couldn't create nvmap client\n");
754                 ret = -ENOMEM;
755                 goto err_free;
756         }
757         atomic_set(&tegra_fb->in_use, 0);
758
759         tegra_fb->flip_wq = create_singlethread_workqueue(dev_name(&ndev->dev));
760         if (!tegra_fb->flip_wq) {
761                 dev_err(&ndev->dev, "couldn't create flip work-queue\n");
762                 ret = -ENOMEM;
763                 goto err_delete_wq;
764         }
765
766         if (fb_mem) {
767                 fb_size = resource_size(fb_mem);
768                 fb_phys = fb_mem->start;
769                 fb_base = ioremap_nocache(fb_phys, fb_size);
770                 if (!fb_base) {
771                         dev_err(&ndev->dev, "fb can't be mapped\n");
772                         ret = -EBUSY;
773                         goto err_put_client;
774                 }
775                 tegra_fb->valid = true;
776         }
777
778         info->fbops = &tegra_fb_ops;
779         info->pseudo_palette = pseudo_palette;
780         info->screen_base = fb_base;
781         info->screen_size = fb_size;
782
783         strlcpy(info->fix.id, "tegra_fb", sizeof(info->fix.id));
784         info->fix.type          = FB_TYPE_PACKED_PIXELS;
785         info->fix.visual        = FB_VISUAL_TRUECOLOR;
786         info->fix.xpanstep      = 1;
787         info->fix.ypanstep      = 1;
788         info->fix.accel         = FB_ACCEL_NONE;
789         info->fix.smem_start    = fb_phys;
790         info->fix.smem_len      = fb_size;
791
792         info->var.xres                  = fb_data->xres;
793         info->var.yres                  = fb_data->yres;
794         info->var.xres_virtual          = fb_data->xres;
795         info->var.yres_virtual          = fb_data->yres * 2;
796         info->var.bits_per_pixel        = fb_data->bits_per_pixel;
797         info->var.activate              = FB_ACTIVATE_VBL;
798         info->var.height                = tegra_dc_get_out_height(dc);
799         info->var.width                 = tegra_dc_get_out_width(dc);
800         info->var.pixclock              = 0;
801         info->var.left_margin           = 0;
802         info->var.right_margin          = 0;
803         info->var.upper_margin          = 0;
804         info->var.lower_margin          = 0;
805         info->var.hsync_len             = 0;
806         info->var.vsync_len             = 0;
807         info->var.vmode                 = FB_VMODE_NONINTERLACED;
808
809         win->x = 0;
810         win->y = 0;
811         win->w = fb_data->xres;
812         win->h = fb_data->yres;
813         /* TODO: set to output res dc */
814         win->out_x = 0;
815         win->out_y = 0;
816         win->out_w = fb_data->xres;
817         win->out_h = fb_data->yres;
818         win->z = 0;
819         win->phys_addr = fb_phys;
820         win->virt_addr = fb_base;
821         win->offset_u = 0;
822         win->offset_v = 0;
823         win->stride = fb_data->xres * fb_data->bits_per_pixel / 8;
824         win->stride_uv = 0;
825         win->flags = TEGRA_WIN_FLAG_ENABLED;
826
827         if (fb_mem)
828                 tegra_fb_set_par(info);
829
830         if (register_framebuffer(info)) {
831                 dev_err(&ndev->dev, "failed to register framebuffer\n");
832                 ret = -ENODEV;
833                 goto err_iounmap_fb;
834         }
835
836         tegra_fb->info = info;
837
838         dev_info(&ndev->dev, "probed\n");
839
840         if (fb_data->flags & TEGRA_FB_FLIP_ON_PROBE) {
841                 tegra_dc_update_windows(&tegra_fb->win, 1);
842                 tegra_dc_sync_windows(&tegra_fb->win, 1);
843         }
844
845         return tegra_fb;
846
847 err_iounmap_fb:
848         iounmap(fb_base);
849 err_put_client:
850         nvmap_client_put(tegra_fb->fb_nvmap);
851 err_delete_wq:
852         destroy_workqueue(tegra_fb->flip_wq);
853 err_free:
854         framebuffer_release(info);
855 err:
856         return ERR_PTR(ret);
857 }
858
859 void tegra_fb_unregister(struct tegra_fb_info *fb_info)
860 {
861         struct fb_info *info = fb_info->info;
862
863         if (fb_info->win->cur_handle) {
864                 nvmap_unpin(fb_info->fb_nvmap, fb_info->win->cur_handle);
865                 nvmap_free(fb_info->fb_nvmap, fb_info->win->cur_handle);
866         }
867
868         if (fb_info->fb_nvmap)
869                 nvmap_client_put(fb_info->fb_nvmap);
870
871         unregister_framebuffer(info);
872
873         flush_workqueue(fb_info->flip_wq);
874         destroy_workqueue(fb_info->flip_wq);
875
876         iounmap(info->screen_base);
877         framebuffer_release(info);
878 }