video: tegra: detect fbmem alignment on probe
[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 <linux/nvhost.h>
40 #include <linux/nvmap.h>
41
42 #include "host/dev.h"
43 #include "nvmap/nvmap.h"
44 #include "dc/dc_priv.h"
45
46 /* Pad pitch to 16-byte boundary. */
47 #define TEGRA_LINEAR_PITCH_ALIGNMENT 32
48
49 struct tegra_fb_info {
50         struct tegra_dc_win     *win;
51         struct nvhost_device    *ndev;
52         struct fb_info          *info;
53         bool                    valid;
54
55         struct resource         *fb_mem;
56
57         int                     xres;
58         int                     yres;
59 };
60
61 /* palette array used by the fbcon */
62 static u32 pseudo_palette[16];
63
64 static int tegra_fb_check_var(struct fb_var_screeninfo *var,
65                               struct fb_info *info)
66 {
67         struct tegra_fb_info *tegra_fb = info->par;
68         struct tegra_dc *dc = tegra_fb->win->dc;
69         struct tegra_dc_out_ops *ops = dc->out_ops;
70         struct fb_videomode mode;
71
72         if ((var->yres * var->xres * var->bits_per_pixel / 8 * 2) >
73             info->screen_size)
74                 return -EINVAL;
75
76         /* Apply mode filter for HDMI only -LVDS supports only fix mode */
77         if (ops && ops->mode_filter) {
78
79                 fb_var_to_videomode(&mode, var);
80                 if (!ops->mode_filter(dc, &mode))
81                         return -EINVAL;
82
83                 /* Mode filter may have modified the mode */
84                 fb_videomode_to_var(var, &mode);
85         }
86
87         /* Double yres_virtual to allow double buffering through pan_display */
88         var->yres_virtual = var->yres * 2;
89
90         return 0;
91 }
92
93 static int tegra_fb_set_par(struct fb_info *info)
94 {
95         struct tegra_fb_info *tegra_fb = info->par;
96         struct fb_var_screeninfo *var = &info->var;
97
98         if (var->bits_per_pixel) {
99                 /* we only support RGB ordering for now */
100                 switch (var->bits_per_pixel) {
101                 case 32:
102                         var->red.offset = 0;
103                         var->red.length = 8;
104                         var->green.offset = 8;
105                         var->green.length = 8;
106                         var->blue.offset = 16;
107                         var->blue.length = 8;
108                         var->transp.offset = 24;
109                         var->transp.length = 8;
110                         tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8;
111                         break;
112                 case 16:
113                         var->red.offset = 11;
114                         var->red.length = 5;
115                         var->green.offset = 5;
116                         var->green.length = 6;
117                         var->blue.offset = 0;
118                         var->blue.length = 5;
119                         tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5;
120                         break;
121
122                 default:
123                         return -EINVAL;
124                 }
125                 info->fix.line_length = var->xres * var->bits_per_pixel / 8;
126                 /* Pad the stride to 16-byte boundary. */
127                 info->fix.line_length = round_up(info->fix.line_length,
128                                                 TEGRA_LINEAR_PITCH_ALIGNMENT);
129                 tegra_fb->win->stride = info->fix.line_length;
130                 tegra_fb->win->stride_uv = 0;
131                 tegra_fb->win->phys_addr_u = 0;
132                 tegra_fb->win->phys_addr_v = 0;
133         }
134
135         if (var->pixclock) {
136                 bool stereo;
137                 struct fb_videomode m;
138
139                 fb_var_to_videomode(&m, var);
140
141                 info->mode = (struct fb_videomode *)
142                         fb_find_nearest_mode(&m, &info->modelist);
143                 if (!info->mode) {
144                         dev_warn(&tegra_fb->ndev->dev, "can't match video mode\n");
145                         return -EINVAL;
146                 }
147
148                 /*
149                  * only enable stereo if the mode supports it and
150                  * client requests it
151                  */
152                 stereo = !!(var->vmode & info->mode->vmode &
153 #ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
154                                         FB_VMODE_STEREO_FRAME_PACK);
155 #else
156                                         FB_VMODE_STEREO_LEFT_RIGHT);
157 #endif
158
159                 tegra_dc_set_fb_mode(tegra_fb->win->dc, info->mode, stereo);
160
161                 tegra_fb->win->w.full = dfixed_const(info->mode->xres);
162                 tegra_fb->win->h.full = dfixed_const(info->mode->yres);
163                 tegra_fb->win->out_w = info->mode->xres;
164                 tegra_fb->win->out_h = info->mode->yres;
165         }
166         return 0;
167 }
168
169 static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
170         unsigned blue, unsigned transp, struct fb_info *info)
171 {
172         struct fb_var_screeninfo *var = &info->var;
173
174         if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
175             info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
176                 u32 v;
177
178                 if (regno >= 16)
179                         return -EINVAL;
180
181                 red = (red >> (16 - info->var.red.length));
182                 green = (green >> (16 - info->var.green.length));
183                 blue = (blue >> (16 - info->var.blue.length));
184
185                 v = (red << var->red.offset) |
186                         (green << var->green.offset) |
187                         (blue << var->blue.offset);
188
189                 ((u32 *)info->pseudo_palette)[regno] = v;
190         }
191
192         return 0;
193 }
194
195
196 static int tegra_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
197 {
198         struct tegra_fb_info *tegra_fb = info->par;
199         struct tegra_dc *dc = tegra_fb->win->dc;
200         int i;
201         u16 *red = cmap->red;
202         u16 *green = cmap->green;
203         u16 *blue = cmap->blue;
204         int start = cmap->start;
205
206         if (((unsigned)start > 255) || ((start + cmap->len) > 256))
207                 return -EINVAL;
208
209         if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
210                 info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
211                 /*
212                  * For now we are considering color schemes with
213                  * cmap->len <=16 as special case of basic color
214                  * scheme to support fbconsole.But for DirectColor
215                  * visuals(like the one we actually have, that include
216                  * a HW LUT),the way it's intended to work is that the
217                  * actual LUT HW is programmed to the intended values,
218                  * even for small color maps like those with 16 or fewer
219                  * entries. The pseudo_palette is then programmed to the
220                  * identity transform.
221                  */
222                 if (cmap->len <= 16) {
223                         /* Low-color schemes like fbconsole*/
224                         u16 *transp = cmap->transp;
225                         u_int vtransp = 0xffff;
226
227                         for (i = 0; i < cmap->len; i++) {
228                                 if (transp)
229                                         vtransp = *transp++;
230                                 if (tegra_fb_setcolreg(start++, *red++,
231                                         *green++, *blue++,
232                                         vtransp, info))
233                                                 return -EINVAL;
234                         }
235                 } else {
236                         /* High-color schemes*/
237                         for (i = 0; i < cmap->len; i++) {
238                                 dc->fb_lut.r[start+i] = *red++ >> 8;
239                                 dc->fb_lut.g[start+i] = *green++ >> 8;
240                                 dc->fb_lut.b[start+i] = *blue++ >> 8;
241                         }
242                         tegra_dc_update_lut(dc, -1, -1);
243                 }
244         }
245         return 0;
246 }
247
248 #if defined(CONFIG_FRAMEBUFFER_CONSOLE)
249 static void tegra_fb_flip_win(struct tegra_fb_info *tegra_fb)
250 {
251         struct tegra_dc_win *win = tegra_fb->win;
252         struct fb_info *info = tegra_fb->info;
253
254         win->x.full = dfixed_const(0);
255         win->y.full = dfixed_const(0);
256         win->w.full = dfixed_const(tegra_fb->xres);
257         win->h.full = dfixed_const(tegra_fb->yres);
258
259         /* TODO: set to output res dc */
260         win->out_x = 0;
261         win->out_y = 0;
262         win->out_w = tegra_fb->xres;
263         win->out_h = tegra_fb->yres;
264         win->z = 0;
265         win->phys_addr = info->fix.smem_start +
266                 (info->var.yoffset * info->fix.line_length) +
267                 (info->var.xoffset * (info->var.bits_per_pixel / 8));
268         win->virt_addr = info->screen_base;
269
270         win->phys_addr_u = 0;
271         win->phys_addr_v = 0;
272         win->stride = info->fix.line_length;
273         win->stride_uv = 0;
274
275         switch (info->var.bits_per_pixel) {
276         default:
277                 WARN_ON(1);
278                 /* fall through */
279         case 32:
280                 tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8;
281                 break;
282         case 16:
283                 tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5;
284                 break;
285         }
286         win->flags = TEGRA_WIN_FLAG_ENABLED;
287
288         tegra_dc_update_windows(&tegra_fb->win, 1);
289         tegra_dc_sync_windows(&tegra_fb->win, 1);
290 }
291 #endif
292
293 static int tegra_fb_blank(int blank, struct fb_info *info)
294 {
295         struct tegra_fb_info *tegra_fb = info->par;
296
297         switch (blank) {
298         case FB_BLANK_UNBLANK:
299                 dev_dbg(&tegra_fb->ndev->dev, "unblank\n");
300                 tegra_fb->win->flags = TEGRA_WIN_FLAG_ENABLED;
301                 tegra_dc_enable(tegra_fb->win->dc);
302                 return 0;
303
304         case FB_BLANK_NORMAL:
305                 dev_dbg(&tegra_fb->ndev->dev, "blank - normal\n");
306                 tegra_dc_blank(tegra_fb->win->dc);
307                 return 0;
308
309         case FB_BLANK_VSYNC_SUSPEND:
310         case FB_BLANK_HSYNC_SUSPEND:
311         case FB_BLANK_POWERDOWN:
312                 dev_dbg(&tegra_fb->ndev->dev, "blank - powerdown\n");
313                 tegra_dc_disable(tegra_fb->win->dc);
314                 return 0;
315
316         default:
317                 return -ENOTTY;
318         }
319 }
320
321 static int tegra_fb_pan_display(struct fb_var_screeninfo *var,
322                                 struct fb_info *info)
323 {
324         struct tegra_fb_info *tegra_fb = info->par;
325         char __iomem *flush_start;
326         char __iomem *flush_end;
327         u32 addr;
328
329         if (!tegra_fb->win->cur_handle) {
330                 flush_start = info->screen_base + (var->yoffset * info->fix.line_length);
331                 flush_end = flush_start + (var->yres * info->fix.line_length);
332
333                 info->var.xoffset = var->xoffset;
334                 info->var.yoffset = var->yoffset;
335
336                 addr = info->fix.smem_start + (var->yoffset * info->fix.line_length) +
337                         (var->xoffset * (var->bits_per_pixel/8));
338
339                 tegra_fb->win->phys_addr = addr;
340                 tegra_fb->win->flags = TEGRA_WIN_FLAG_ENABLED;
341                 tegra_fb->win->virt_addr = info->screen_base;
342
343                 tegra_dc_update_windows(&tegra_fb->win, 1);
344                 tegra_dc_sync_windows(&tegra_fb->win, 1);
345         }
346
347         return 0;
348 }
349
350 static void tegra_fb_fillrect(struct fb_info *info,
351                               const struct fb_fillrect *rect)
352 {
353         cfb_fillrect(info, rect);
354 }
355
356 static void tegra_fb_copyarea(struct fb_info *info,
357                               const struct fb_copyarea *region)
358 {
359         cfb_copyarea(info, region);
360 }
361
362 static void tegra_fb_imageblit(struct fb_info *info,
363                                const struct fb_image *image)
364 {
365         cfb_imageblit(info, image);
366 }
367
368 static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
369 {
370         struct tegra_fb_modedb modedb;
371         struct fb_modelist *modelist;
372         int i;
373
374         switch (cmd) {
375         case FBIO_TEGRA_GET_MODEDB:
376                 if (copy_from_user(&modedb, (void __user *)arg, sizeof(modedb)))
377                         return -EFAULT;
378
379                 i = 0;
380                 list_for_each_entry(modelist, &info->modelist, list) {
381                         struct fb_var_screeninfo var;
382
383                         if (i >= modedb.modedb_len)
384                                 break;
385
386                         /* fb_videomode_to_var doesn't fill out all the members
387                            of fb_var_screeninfo */
388                         memset(&var, 0x0, sizeof(var));
389
390                         fb_videomode_to_var(&var, &modelist->mode);
391
392                         if (copy_to_user((void __user *)&modedb.modedb[i],
393                                          &var, sizeof(var)))
394                                 return -EFAULT;
395                         i++;
396
397                         if (var.vmode & FB_VMODE_STEREO_MASK) {
398                                 if (i >= modedb.modedb_len)
399                                         break;
400                                 var.vmode &= ~FB_VMODE_STEREO_MASK;
401                                 if (copy_to_user(
402                                         (void __user *)&modedb.modedb[i],
403                                          &var, sizeof(var)))
404                                         return -EFAULT;
405                                 i++;
406                         }
407                 }
408                 modedb.modedb_len = i;
409
410                 if (copy_to_user((void __user *)arg, &modedb, sizeof(modedb)))
411                         return -EFAULT;
412                 break;
413
414         default:
415                 return -ENOTTY;
416         }
417
418         return 0;
419 }
420
421 int tegra_fb_get_mode(struct tegra_dc *dc) {
422         return dc->fb->info->mode->refresh;
423 }
424
425 int tegra_fb_set_mode(struct tegra_dc *dc, int fps) {
426         size_t stereo;
427         struct list_head *pos;
428         struct fb_videomode *best_mode = NULL;
429         int curr_diff = INT_MAX; /* difference of best_mode refresh rate */
430         struct fb_modelist *modelist;
431         struct fb_info *info = dc->fb->info;
432
433         list_for_each(pos, &info->modelist) {
434                 struct fb_videomode *mode;
435
436                 modelist = list_entry(pos, struct fb_modelist, list);
437                 mode = &modelist->mode;
438                 if (fps <= mode->refresh && curr_diff > (mode->refresh - fps)) {
439                         curr_diff = mode->refresh - fps;
440                         best_mode = mode;
441                 }
442         }
443         if (best_mode) {
444                 info->mode = best_mode;
445                 stereo = !!(info->var.vmode & info->mode->vmode &
446 #ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
447                                 FB_VMODE_STEREO_FRAME_PACK);
448 #else
449                                 FB_VMODE_STEREO_LEFT_RIGHT);
450 #endif
451                 return tegra_dc_set_fb_mode(dc, best_mode, stereo);
452         }
453         return -EIO;
454 }
455
456 static struct fb_ops tegra_fb_ops = {
457         .owner = THIS_MODULE,
458         .fb_check_var = tegra_fb_check_var,
459         .fb_set_par = tegra_fb_set_par,
460         .fb_setcmap = tegra_fb_setcmap,
461         .fb_blank = tegra_fb_blank,
462         .fb_pan_display = tegra_fb_pan_display,
463         .fb_fillrect = tegra_fb_fillrect,
464         .fb_copyarea = tegra_fb_copyarea,
465         .fb_imageblit = tegra_fb_imageblit,
466         .fb_ioctl = tegra_fb_ioctl,
467 };
468
469 void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
470                               struct fb_monspecs *specs,
471                               bool (*mode_filter)(const struct tegra_dc *dc,
472                                                   struct fb_videomode *mode))
473 {
474         struct fb_event event;
475         int i;
476
477         mutex_lock(&fb_info->info->lock);
478         fb_destroy_modedb(fb_info->info->monspecs.modedb);
479
480         fb_destroy_modelist(&fb_info->info->modelist);
481
482         if (specs == NULL) {
483                 struct tegra_dc_mode mode;
484                 memset(&fb_info->info->monspecs, 0x0,
485                        sizeof(fb_info->info->monspecs));
486                 memset(&mode, 0x0, sizeof(mode));
487
488                 /*
489                  * reset video mode properties to prevent garbage being displayed on 'mode' device.
490                  */
491                 fb_info->info->mode = (struct fb_videomode*) NULL;
492
493                 tegra_dc_set_mode(fb_info->win->dc, &mode);
494                 mutex_unlock(&fb_info->info->lock);
495                 return;
496         }
497
498         memcpy(&fb_info->info->monspecs, specs,
499                sizeof(fb_info->info->monspecs));
500         fb_info->info->mode = specs->modedb;
501
502         for (i = 0; i < specs->modedb_len; i++) {
503                 if (mode_filter) {
504                         if (mode_filter(fb_info->win->dc, &specs->modedb[i]))
505                                 fb_add_videomode(&specs->modedb[i],
506                                                  &fb_info->info->modelist);
507                 } else {
508                         fb_add_videomode(&specs->modedb[i],
509                                          &fb_info->info->modelist);
510                 }
511         }
512
513         event.info = fb_info->info;
514         fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event);
515         mutex_unlock(&fb_info->info->lock);
516 }
517
518 struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
519                                         struct tegra_dc *dc,
520                                         struct tegra_fb_data *fb_data,
521                                         struct resource *fb_mem)
522 {
523         struct tegra_dc_win *win;
524         struct fb_info *info;
525         struct tegra_fb_info *tegra_fb;
526         void __iomem *fb_base = NULL;
527         unsigned long fb_size = 0;
528         unsigned long fb_phys = 0;
529         int ret = 0;
530         unsigned stride;
531
532         win = tegra_dc_get_window(dc, fb_data->win);
533         if (!win) {
534                 dev_err(&ndev->dev, "dc does not have a window at index %d\n",
535                         fb_data->win);
536                 return ERR_PTR(-ENOENT);
537         }
538
539         info = framebuffer_alloc(sizeof(struct tegra_fb_info), &ndev->dev);
540         if (!info) {
541                 ret = -ENOMEM;
542                 goto err;
543         }
544
545         tegra_fb = info->par;
546         tegra_fb->win = win;
547         tegra_fb->ndev = ndev;
548         tegra_fb->fb_mem = fb_mem;
549         tegra_fb->xres = fb_data->xres;
550         tegra_fb->yres = fb_data->yres;
551
552         if (fb_mem) {
553                 fb_size = resource_size(fb_mem);
554                 fb_phys = fb_mem->start;
555                 fb_base = ioremap_nocache(fb_phys, fb_size);
556                 if (!fb_base) {
557                         dev_err(&ndev->dev, "fb can't be mapped\n");
558                         ret = -EBUSY;
559                         goto err_free;
560                 }
561                 tegra_fb->valid = true;
562         }
563
564         stride = tegra_dc_get_stride(dc, 0);
565         if (!stride) /* default to pad the stride to 16-byte boundary. */
566                 stride = round_up(info->fix.line_length,
567                         TEGRA_LINEAR_PITCH_ALIGNMENT);
568
569         info->fbops = &tegra_fb_ops;
570         info->pseudo_palette = pseudo_palette;
571         info->screen_base = fb_base;
572         info->screen_size = fb_size;
573
574         strlcpy(info->fix.id, "tegra_fb", sizeof(info->fix.id));
575         info->fix.type          = FB_TYPE_PACKED_PIXELS;
576         info->fix.visual        = FB_VISUAL_TRUECOLOR;
577         info->fix.xpanstep      = 1;
578         info->fix.ypanstep      = 1;
579         info->fix.accel         = FB_ACCEL_NONE;
580         info->fix.smem_start    = fb_phys;
581         info->fix.smem_len      = fb_size;
582         info->fix.line_length = fb_data->xres * fb_data->bits_per_pixel / 8;
583         info->fix.line_length = stride;
584
585         info->var.xres                  = fb_data->xres;
586         info->var.yres                  = fb_data->yres;
587         info->var.xres_virtual          = fb_data->xres;
588         info->var.yres_virtual          = fb_data->yres * 2;
589         info->var.bits_per_pixel        = fb_data->bits_per_pixel;
590         info->var.activate              = FB_ACTIVATE_VBL;
591         info->var.height                = tegra_dc_get_out_height(dc);
592         info->var.width                 = tegra_dc_get_out_width(dc);
593         info->var.pixclock              = 0;
594         info->var.left_margin           = 0;
595         info->var.right_margin          = 0;
596         info->var.upper_margin          = 0;
597         info->var.lower_margin          = 0;
598         info->var.hsync_len             = 0;
599         info->var.vsync_len             = 0;
600         info->var.vmode                 = FB_VMODE_NONINTERLACED;
601
602         win->x.full = dfixed_const(0);
603         win->y.full = dfixed_const(0);
604         win->w.full = dfixed_const(fb_data->xres);
605         win->h.full = dfixed_const(fb_data->yres);
606         /* TODO: set to output res dc */
607         win->out_x = 0;
608         win->out_y = 0;
609         win->out_w = fb_data->xres;
610         win->out_h = fb_data->yres;
611         win->z = 0;
612         win->phys_addr = fb_phys;
613         win->virt_addr = fb_base;
614         win->phys_addr_u = 0;
615         win->phys_addr_v = 0;
616         win->stride = info->fix.line_length;
617         win->stride_uv = 0;
618         win->flags = TEGRA_WIN_FLAG_ENABLED;
619
620         if (fb_mem)
621                 tegra_fb_set_par(info);
622
623         if (register_framebuffer(info)) {
624                 dev_err(&ndev->dev, "failed to register framebuffer\n");
625                 ret = -ENODEV;
626                 goto err_iounmap_fb;
627         }
628
629         tegra_fb->info = info;
630
631         dev_info(&ndev->dev, "probed\n");
632
633         if (fb_data->flags & TEGRA_FB_FLIP_ON_PROBE) {
634                 tegra_dc_update_windows(&tegra_fb->win, 1);
635                 tegra_dc_sync_windows(&tegra_fb->win, 1);
636         }
637
638         if (dc->mode.pclk > 1000) {
639                 struct tegra_dc_mode *mode = &dc->mode;
640
641                 if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
642                         info->var.pixclock = KHZ2PICOS(mode->rated_pclk / 1000);
643                 else
644                         info->var.pixclock = KHZ2PICOS(mode->pclk / 1000);
645                 info->var.left_margin = mode->h_back_porch;
646                 info->var.right_margin = mode->h_front_porch;
647                 info->var.upper_margin = mode->v_back_porch;
648                 info->var.lower_margin = mode->v_front_porch;
649                 info->var.hsync_len = mode->h_sync_width;
650                 info->var.vsync_len = mode->v_sync_width;
651         }
652
653         return tegra_fb;
654
655 err_iounmap_fb:
656         if (fb_base)
657                 iounmap(fb_base);
658 err_free:
659         framebuffer_release(info);
660 err:
661         return ERR_PTR(ret);
662 }
663
664 void tegra_fb_unregister(struct tegra_fb_info *fb_info)
665 {
666         struct fb_info *info = fb_info->info;
667
668         unregister_framebuffer(info);
669
670         iounmap(info->screen_base);
671         framebuffer_release(info);
672 }