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