video: tegra: dc: load video mode during vblank
Jon Mayo [Fri, 16 Mar 2012 19:50:59 +0000 (12:50 -0700)]
Handle mode set for FBIOPUT_VSCREENINFO at the end of a frame (during
vblank). This elimiates the work around that requires disabling then
enabling display to change modes.
Adds a spinlock to protect irq code from updates to tegra_dc_mode structure.

Bug 560152

Change-Id: I5d2175f01a177a32d685b46e5af4f78efeec0786
Signed-off-by: Jon Mayo <jmayo@nvidia.com>
Reviewed-on: http://git-master/r/90688
Reviewed-by: Simone Willett <swillett@nvidia.com>
Tested-by: Simone Willett <swillett@nvidia.com>

arch/arm/mach-tegra/include/mach/dc.h
drivers/video/tegra/dc/dc.c
drivers/video/tegra/dc/dc_priv.h
drivers/video/tegra/fb.c

index a5f7210..d4f63af 100644 (file)
@@ -6,7 +6,7 @@
  * Author:
  *     Erik Gilling <konkers@google.com>
  *
- * Copyright (C) 2010-2011 NVIDIA Corporation
+ * Copyright (C) 2010-2012 NVIDIA Corporation
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -523,6 +523,8 @@ int tegra_dc_sync_windows(struct tegra_dc_win *windows[], int n);
 
 int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode);
 struct fb_videomode;
+int tegra_dc_to_fb_videomode(struct fb_videomode *fbmode,
+       const struct tegra_dc_mode *mode);
 int tegra_dc_set_fb_mode(struct tegra_dc *dc, const struct fb_videomode *fbmode,
        bool stereo_mode);
 
index 20f80f9..51af1e0 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/errno.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
+#include <linux/spinlock.h>
 #include <linux/io.h>
 #include <linux/clk.h>
 #include <linux/mutex.h>
@@ -63,6 +64,8 @@
 #define ALL_UF_INT (0)
 #endif
 
+static int calc_refresh(const struct tegra_dc_mode *m);
+
 static int no_vsync;
 
 static void _tegra_dc_controller_disable(struct tegra_dc *dc);
@@ -1611,7 +1614,6 @@ static bool check_ref_to_sync(struct tegra_dc_mode *mode)
        return true;
 }
 
-#ifdef DEBUG
 /* return in 1000ths of a Hertz */
 static int calc_refresh(const struct tegra_dc_mode *m)
 {
@@ -1626,6 +1628,7 @@ static int calc_refresh(const struct tegra_dc_mode *m)
        return refresh;
 }
 
+#ifdef DEBUG
 static void print_mode(struct tegra_dc *dc,
                        const struct tegra_dc_mode *mode, const char *note)
 {
@@ -1736,7 +1739,12 @@ static int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode
 
 int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
 {
+       unsigned long flags;
+
+       spin_lock_irqsave(&dc->mode_lock, flags);
        memcpy(&dc->mode, mode, sizeof(dc->mode));
+       dc->mode_dirty = true;
+       spin_unlock_irqrestore(&dc->mode_lock, flags);
 
        print_mode(dc, mode, __func__);
 
@@ -1744,6 +1752,46 @@ int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
 }
 EXPORT_SYMBOL(tegra_dc_set_mode);
 
+/* convert tegra_dc_mode to fb_videomode
+ * return non-zero on error. */
+int tegra_dc_to_fb_videomode(struct fb_videomode *fbmode,
+       const struct tegra_dc_mode *mode)
+{
+       if (!fbmode || !mode || !mode->pclk)
+               return -EINVAL;
+
+       memset(fbmode, 0, sizeof(*fbmode));
+
+       if (mode->rated_pclk >= 1000)
+               fbmode->pixclock = KHZ2PICOS(mode->rated_pclk / 1000);
+       else if (mode->pclk >= 1000)
+               fbmode->pixclock = KHZ2PICOS(mode->pclk / 1000);
+       fbmode->hsync_len = mode->h_sync_width;
+       fbmode->vsync_len = mode->v_sync_width;
+       fbmode->left_margin = mode->h_back_porch;
+       fbmode->upper_margin = mode->v_back_porch;
+       fbmode->xres = mode->h_active;
+       fbmode->yres = mode->v_active;
+       fbmode->right_margin = mode->h_front_porch;
+       fbmode->lower_margin = mode->v_front_porch;
+       fbmode->vmode = FB_VMODE_NONINTERLACED;
+       fbmode->vmode |= mode->stereo_mode ?
+#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
+               FB_VMODE_STEREO_FRAME_PACK
+#else
+               FB_VMODE_STEREO_LEFT_RIGHT
+#endif
+               : 0;
+       fbmode->sync |= (mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC) ?
+               0 : FB_SYNC_HOR_HIGH_ACT;
+       fbmode->sync |= (mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC) ?
+               0 : FB_SYNC_VERT_HIGH_ACT;
+       fbmode->refresh = (calc_refresh(mode) + 500) / 1000;
+
+       return 0;
+}
+EXPORT_SYMBOL(tegra_dc_to_fb_videomode);
+
 int tegra_dc_set_fb_mode(struct tegra_dc *dc,
                const struct fb_videomode *fbmode, bool stereo_mode)
 {
@@ -2132,6 +2180,18 @@ static bool tegra_dc_windows_are_dirty(struct tegra_dc *dc)
        return false;
 }
 
+static void tegra_dc_update_mode(struct tegra_dc *dc)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dc->mode_lock, flags);
+       if (dc->mode_dirty) {
+               tegra_dc_program_mode(dc, &dc->mode);
+               dc->mode_dirty = false;
+       }
+       spin_unlock_irqrestore(&dc->mode_lock, flags);
+}
+
 static void tegra_dc_trigger_windows(struct tegra_dc *dc)
 {
        u32 val, i;
@@ -2191,6 +2251,9 @@ static void tegra_dc_one_shot_irq(struct tegra_dc *dc, unsigned long status)
                /* Mark the frame_end as complete. */
                if (!completion_done(&dc->frame_end_complete))
                        complete(&dc->frame_end_complete);
+
+               /* handle a mode change, if it was scheduled */
+               tegra_dc_update_mode(dc);
        }
 }
 
@@ -2215,6 +2278,8 @@ static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status)
                if (!completion_done(&dc->frame_end_complete))
                        complete(&dc->frame_end_complete);
 
+               /* handle a mode change, if it was scheduled */
+               tegra_dc_update_mode(dc);
                tegra_dc_trigger_windows(dc);
        }
 }
@@ -2867,6 +2932,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
 
        mutex_init(&dc->lock);
        mutex_init(&dc->one_shot_lock);
+       spin_lock_init(&dc->mode_lock);
        init_completion(&dc->frame_end_complete);
        init_waitqueue_head(&dc->wq);
 #ifdef CONFIG_ARCH_TEGRA_2x_SOC
@@ -2953,6 +3019,22 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
                        dc->fb = NULL;
        }
 
+       if (dc->out && dc->out->n_modes) {
+               struct fb_monspecs specs;
+               int i;
+
+               memset(&specs, 0, sizeof(specs));
+               specs.max_x = dc->mode.h_active * 1000;
+               specs.max_y = dc->mode.v_active * 1000;
+               specs.modedb_len = dc->out->n_modes;
+               specs.modedb = kzalloc(specs.modedb_len *
+                       sizeof(struct fb_videomode), GFP_KERNEL);
+               for (i = 0; i < dc->out->n_modes; i++)
+                       tegra_dc_to_fb_videomode(&specs.modedb[i],
+                               &dc->out->modes[i]);
+               tegra_fb_update_monspecs(dc->fb, &specs, NULL);
+       }
+
        if (dc->out && dc->out->hotplug_init)
                dc->out->hotplug_init();
 
index a10e648..5cba88f 100644 (file)
@@ -4,6 +4,8 @@
  * Copyright (C) 2010 Google, Inc.
  * Author: Erik Gilling <konkers@android.com>
  *
+ * Copyright (C) 2010-2012 NVIDIA Corporation
+ *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
  * may be copied, distributed, and modified under those terms.
@@ -93,6 +95,8 @@ struct tegra_dc {
        void                            *out_data;
 
        struct tegra_dc_mode            mode;
+       bool                            mode_dirty;
+       spinlock_t                      mode_lock;
 
        struct tegra_dc_win             windows[DC_N_WINDOWS];
        struct tegra_dc_blend           blend;
index 7bc3ab0..ac4c84b 100644 (file)
@@ -6,7 +6,7 @@
  *         Colin Cross <ccross@android.com>
  *         Travis Geiselbrecht <travis@palm.com>
  *
- * Copyright (C) 2010-2011 NVIDIA Corporation
+ * Copyright (C) 2010-2012 NVIDIA Corporation
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -79,6 +79,10 @@ static int tegra_fb_set_par(struct fb_info *info)
        struct tegra_fb_info *tegra_fb = info->par;
        struct fb_var_screeninfo *var = &info->var;
 
+       BUG_ON(info == NULL);
+       if (!info)
+               return -EINVAL;
+
        if (var->bits_per_pixel) {
                /* we only support RGB ordering for now */
                switch (var->bits_per_pixel) {
@@ -484,6 +488,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;
+       struct fb_videomode m;
 
        win = tegra_dc_get_window(dc, fb_data->win);
        if (!win) {
@@ -535,22 +540,15 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
        info->fix.line_length = round_up(info->fix.line_length,
                                        TEGRA_LINEAR_PITCH_ALIGNMENT);
 
-       info->var.xres                  = fb_data->xres;
-       info->var.yres                  = fb_data->yres;
+       INIT_LIST_HEAD(&info->modelist);
+       tegra_dc_to_fb_videomode(&m, &dc->mode);
+       fb_videomode_to_var(&info->var, &m);
        info->var.xres_virtual          = fb_data->xres;
        info->var.yres_virtual          = fb_data->yres * 2;
        info->var.bits_per_pixel        = fb_data->bits_per_pixel;
        info->var.activate              = FB_ACTIVATE_VBL;
        info->var.height                = tegra_dc_get_out_height(dc);
        info->var.width                 = tegra_dc_get_out_width(dc);
-       info->var.pixclock              = 0;
-       info->var.left_margin           = 0;
-       info->var.right_margin          = 0;
-       info->var.upper_margin          = 0;
-       info->var.lower_margin          = 0;
-       info->var.hsync_len             = 0;
-       info->var.vsync_len             = 0;
-       info->var.vmode                 = FB_VMODE_NONINTERLACED;
 
        win->x.full = dfixed_const(0);
        win->y.full = dfixed_const(0);