video: tegra: detect fbmem alignment on probe
[linux-2.6.git] / drivers / video / tegra / fb.c
index 7b01f1e..1193a2e 100644 (file)
 #include <mach/dc.h>
 #include <mach/fb.h>
 #include <linux/nvhost.h>
-#include <mach/nvmap.h>
+#include <linux/nvmap.h>
 
 #include "host/dev.h"
 #include "nvmap/nvmap.h"
 #include "dc/dc_priv.h"
 
 /* Pad pitch to 16-byte boundary. */
-#define TEGRA_LINEAR_PITCH_ALIGNMENT 16
+#define TEGRA_LINEAR_PITCH_ALIGNMENT 32
 
 struct tegra_fb_info {
        struct tegra_dc_win     *win;
@@ -64,11 +64,27 @@ static u32 pseudo_palette[16];
 static int tegra_fb_check_var(struct fb_var_screeninfo *var,
                              struct fb_info *info)
 {
+       struct tegra_fb_info *tegra_fb = info->par;
+       struct tegra_dc *dc = tegra_fb->win->dc;
+       struct tegra_dc_out_ops *ops = dc->out_ops;
+       struct fb_videomode mode;
+
        if ((var->yres * var->xres * var->bits_per_pixel / 8 * 2) >
            info->screen_size)
                return -EINVAL;
 
-       /* double yres_virtual to allow double buffering through pan_display */
+       /* Apply mode filter for HDMI only -LVDS supports only fix mode */
+       if (ops && ops->mode_filter) {
+
+               fb_var_to_videomode(&mode, var);
+               if (!ops->mode_filter(dc, &mode))
+                       return -EINVAL;
+
+               /* Mode filter may have modified the mode */
+               fb_videomode_to_var(var, &mode);
+       }
+
+       /* Double yres_virtual to allow double buffering through pan_display */
        var->yres_virtual = var->yres * 2;
 
        return 0;
@@ -134,7 +150,11 @@ static int tegra_fb_set_par(struct fb_info *info)
                 * client requests it
                 */
                stereo = !!(var->vmode & info->mode->vmode &
+#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
                                        FB_VMODE_STEREO_FRAME_PACK);
+#else
+                                       FB_VMODE_STEREO_LEFT_RIGHT);
+#endif
 
                tegra_dc_set_fb_mode(tegra_fb->win->dc, info->mode, stereo);
 
@@ -172,6 +192,104 @@ static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
        return 0;
 }
 
+
+static int tegra_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+       struct tegra_fb_info *tegra_fb = info->par;
+       struct tegra_dc *dc = tegra_fb->win->dc;
+       int i;
+       u16 *red = cmap->red;
+       u16 *green = cmap->green;
+       u16 *blue = cmap->blue;
+       int start = cmap->start;
+
+       if (((unsigned)start > 255) || ((start + cmap->len) > 256))
+               return -EINVAL;
+
+       if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
+               info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
+               /*
+                * For now we are considering color schemes with
+                * cmap->len <=16 as special case of basic color
+                * scheme to support fbconsole.But for DirectColor
+                * visuals(like the one we actually have, that include
+                * a HW LUT),the way it's intended to work is that the
+                * actual LUT HW is programmed to the intended values,
+                * even for small color maps like those with 16 or fewer
+                * entries. The pseudo_palette is then programmed to the
+                * identity transform.
+                */
+               if (cmap->len <= 16) {
+                       /* Low-color schemes like fbconsole*/
+                       u16 *transp = cmap->transp;
+                       u_int vtransp = 0xffff;
+
+                       for (i = 0; i < cmap->len; i++) {
+                               if (transp)
+                                       vtransp = *transp++;
+                               if (tegra_fb_setcolreg(start++, *red++,
+                                       *green++, *blue++,
+                                       vtransp, info))
+                                               return -EINVAL;
+                       }
+               } else {
+                       /* High-color schemes*/
+                       for (i = 0; i < cmap->len; i++) {
+                               dc->fb_lut.r[start+i] = *red++ >> 8;
+                               dc->fb_lut.g[start+i] = *green++ >> 8;
+                               dc->fb_lut.b[start+i] = *blue++ >> 8;
+                       }
+                       tegra_dc_update_lut(dc, -1, -1);
+               }
+       }
+       return 0;
+}
+
+#if defined(CONFIG_FRAMEBUFFER_CONSOLE)
+static void tegra_fb_flip_win(struct tegra_fb_info *tegra_fb)
+{
+       struct tegra_dc_win *win = tegra_fb->win;
+       struct fb_info *info = tegra_fb->info;
+
+       win->x.full = dfixed_const(0);
+       win->y.full = dfixed_const(0);
+       win->w.full = dfixed_const(tegra_fb->xres);
+       win->h.full = dfixed_const(tegra_fb->yres);
+
+       /* TODO: set to output res dc */
+       win->out_x = 0;
+       win->out_y = 0;
+       win->out_w = tegra_fb->xres;
+       win->out_h = tegra_fb->yres;
+       win->z = 0;
+       win->phys_addr = info->fix.smem_start +
+               (info->var.yoffset * info->fix.line_length) +
+               (info->var.xoffset * (info->var.bits_per_pixel / 8));
+       win->virt_addr = info->screen_base;
+
+       win->phys_addr_u = 0;
+       win->phys_addr_v = 0;
+       win->stride = info->fix.line_length;
+       win->stride_uv = 0;
+
+       switch (info->var.bits_per_pixel) {
+       default:
+               WARN_ON(1);
+               /* fall through */
+       case 32:
+               tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8;
+               break;
+       case 16:
+               tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5;
+               break;
+       }
+       win->flags = TEGRA_WIN_FLAG_ENABLED;
+
+       tegra_dc_update_windows(&tegra_fb->win, 1);
+       tegra_dc_sync_windows(&tegra_fb->win, 1);
+}
+#endif
+
 static int tegra_fb_blank(int blank, struct fb_info *info)
 {
        struct tegra_fb_info *tegra_fb = info->par;
@@ -179,6 +297,7 @@ static int tegra_fb_blank(int blank, struct fb_info *info)
        switch (blank) {
        case FB_BLANK_UNBLANK:
                dev_dbg(&tegra_fb->ndev->dev, "unblank\n");
+               tegra_fb->win->flags = TEGRA_WIN_FLAG_ENABLED;
                tegra_dc_enable(tegra_fb->win->dc);
                return 0;
 
@@ -218,7 +337,8 @@ static int tegra_fb_pan_display(struct fb_var_screeninfo *var,
                        (var->xoffset * (var->bits_per_pixel/8));
 
                tegra_fb->win->phys_addr = addr;
-               /* TODO: update virt_addr */
+               tegra_fb->win->flags = TEGRA_WIN_FLAG_ENABLED;
+               tegra_fb->win->virt_addr = info->screen_base;
 
                tegra_dc_update_windows(&tegra_fb->win, 1);
                tegra_dc_sync_windows(&tegra_fb->win, 1);
@@ -298,11 +418,46 @@ static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long
        return 0;
 }
 
+int tegra_fb_get_mode(struct tegra_dc *dc) {
+       return dc->fb->info->mode->refresh;
+}
+
+int tegra_fb_set_mode(struct tegra_dc *dc, int fps) {
+       size_t stereo;
+       struct list_head *pos;
+       struct fb_videomode *best_mode = NULL;
+       int curr_diff = INT_MAX; /* difference of best_mode refresh rate */
+       struct fb_modelist *modelist;
+       struct fb_info *info = dc->fb->info;
+
+       list_for_each(pos, &info->modelist) {
+               struct fb_videomode *mode;
+
+               modelist = list_entry(pos, struct fb_modelist, list);
+               mode = &modelist->mode;
+               if (fps <= mode->refresh && curr_diff > (mode->refresh - fps)) {
+                       curr_diff = mode->refresh - fps;
+                       best_mode = mode;
+               }
+       }
+       if (best_mode) {
+               info->mode = best_mode;
+               stereo = !!(info->var.vmode & info->mode->vmode &
+#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
+                               FB_VMODE_STEREO_FRAME_PACK);
+#else
+                               FB_VMODE_STEREO_LEFT_RIGHT);
+#endif
+               return tegra_dc_set_fb_mode(dc, best_mode, stereo);
+       }
+       return -EIO;
+}
+
 static struct fb_ops tegra_fb_ops = {
        .owner = THIS_MODULE,
        .fb_check_var = tegra_fb_check_var,
        .fb_set_par = tegra_fb_set_par,
-       .fb_setcolreg = tegra_fb_setcolreg,
+       .fb_setcmap = tegra_fb_setcmap,
        .fb_blank = tegra_fb_blank,
        .fb_pan_display = tegra_fb_pan_display,
        .fb_fillrect = tegra_fb_fillrect,
@@ -329,6 +484,12 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
                memset(&fb_info->info->monspecs, 0x0,
                       sizeof(fb_info->info->monspecs));
                memset(&mode, 0x0, sizeof(mode));
+
+               /*
+                * reset video mode properties to prevent garbage being displayed on 'mode' device.
+                */
+               fb_info->info->mode = (struct fb_videomode*) NULL;
+
                tegra_dc_set_mode(fb_info->win->dc, &mode);
                mutex_unlock(&fb_info->info->lock);
                return;
@@ -336,6 +497,7 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
 
        memcpy(&fb_info->info->monspecs, specs,
               sizeof(fb_info->info->monspecs));
+       fb_info->info->mode = specs->modedb;
 
        for (i = 0; i < specs->modedb_len; i++) {
                if (mode_filter) {
@@ -365,6 +527,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
        unsigned long fb_size = 0;
        unsigned long fb_phys = 0;
        int ret = 0;
+       unsigned stride;
 
        win = tegra_dc_get_window(dc, fb_data->win);
        if (!win) {
@@ -398,6 +561,11 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
                tegra_fb->valid = true;
        }
 
+       stride = tegra_dc_get_stride(dc, 0);
+       if (!stride) /* default to pad the stride to 16-byte boundary. */
+               stride = round_up(info->fix.line_length,
+                       TEGRA_LINEAR_PITCH_ALIGNMENT);
+
        info->fbops = &tegra_fb_ops;
        info->pseudo_palette = pseudo_palette;
        info->screen_base = fb_base;
@@ -412,9 +580,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
        info->fix.smem_start    = fb_phys;
        info->fix.smem_len      = fb_size;
        info->fix.line_length = fb_data->xres * fb_data->bits_per_pixel / 8;
-       /* Pad the stride to 16-byte boundary. */
-       info->fix.line_length = round_up(info->fix.line_length,
-                                       TEGRA_LINEAR_PITCH_ALIGNMENT);
+       info->fix.line_length = stride;
 
        info->var.xres                  = fb_data->xres;
        info->var.yres                  = fb_data->yres;
@@ -469,6 +635,21 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
                tegra_dc_sync_windows(&tegra_fb->win, 1);
        }
 
+       if (dc->mode.pclk > 1000) {
+               struct tegra_dc_mode *mode = &dc->mode;
+
+               if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
+                       info->var.pixclock = KHZ2PICOS(mode->rated_pclk / 1000);
+               else
+                       info->var.pixclock = KHZ2PICOS(mode->pclk / 1000);
+               info->var.left_margin = mode->h_back_porch;
+               info->var.right_margin = mode->h_front_porch;
+               info->var.upper_margin = mode->v_back_porch;
+               info->var.lower_margin = mode->v_front_porch;
+               info->var.hsync_len = mode->h_sync_width;
+               info->var.vsync_len = mode->v_sync_width;
+       }
+
        return tegra_fb;
 
 err_iounmap_fb: