video: tegra: add host1x support to driver
authorGary King <gking@nvidia.com>
Tue, 31 Aug 2010 23:45:55 +0000 (16:45 -0700)
committerRebecca Schultz Zavin <rebecca@android.com>
Fri, 8 Oct 2010 22:59:02 +0000 (15:59 -0700)
includes changes by:

Antti Hatala <ahatala@nvidia.com> (1)
Gary King <gking@nvidia.com> (1)
Erik Gilling <konkers@android.com> (2)

Antti Hatala <ahatala@nvidia.com>
video: tegra: dc: use nvhost driver for host1x power management

Gary King <gking@nvidia.com>
video: tegra: fb: add ioctl to flip dc windows to nvmap handles

Erik Gilling <konkers@android.com>
video: tegra: dc: increment syncpoints following window flips
video: tegra: drain syncpt waits on display disable

Original commit messages:
**
video: tegra: dc: use nvhost driver for host1x power management

Incrementing the frame done syncpoint value from the display interrupt
requires that the host module is powered on. As the syncpoint state is
saved and restored automatically by the host driver a cpu increment of
a syncpoint in the powered down state will be lost.

Also adds checks for host module being powered.
**
video: tegra: fb: add ioctl to flip dc windows to nvmap handles

tegra user-space graphics drivers may allocate framebuffers using
nvmap rather than rendering to the common framebuffer, this may
be done to support deeper buffer pipelining, color formats
and pixel layouts other than the initial bootup framebuffer,
etc.

to use this ioctl, a caller must first specify an nvmap fd
which is already open in the calling process so that the
subsequent flip ioctls may be properly validated. flips are performed
asynchronously, with flip completion notifications provided back to
the caller via the host1x syncpoint mechanism

based on earlier changes made by Antti Hatala <ahatala@nvidia.com>
and Erik Gilling <konkers@android.com>

Change-Id: I4e8a8bb92085a485d65fd87d89112b2969ee37ff
Signed-off-by: Gary King <gking@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
include/video/tegrafb.h [new file with mode: 0644]

index ce562e5634f76db9a511eef50552b99795656daa..49c09a177d00bf3702644181b9bdbb7af60c3950 100644 (file)
@@ -86,6 +86,7 @@ struct tegra_dc_out {
 #define TEGRA_DC_ORDER_BLUE_RED                1
 
 struct tegra_dc;
+struct nvmap_handle_ref;
 
 struct tegra_dc_win {
        u8                      idx;
@@ -108,6 +109,8 @@ struct tegra_dc_win {
 
        int                     dirty;
        struct tegra_dc         *dc;
+
+       struct nvmap_handle_ref *surface;
 };
 
 #define TEGRA_WIN_FLAG_ENABLED         (1 << 0)
@@ -165,6 +168,10 @@ struct tegra_dc_win *tegra_dc_get_window(struct tegra_dc *dc, unsigned win);
 void tegra_dc_enable(struct tegra_dc *dc);
 void tegra_dc_disable(struct tegra_dc *dc);
 
+u32 tegra_dc_get_syncpt_id(const struct tegra_dc *dc);
+u32 tegra_dc_incr_syncpt_max(struct tegra_dc *dc);
+void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, u32 val);
+
 /* tegra_dc_update_windows and tegra_dc_sync_windows do not support windows
  * with differenct dcs in one call
  */
index fadecf4796e1086b065a081c74c6eb02bf4bdd40..16e6a454336d86a2867b953d02ccb3064f194b94 100644 (file)
@@ -102,6 +102,8 @@ static void _dump_regs(struct tegra_dc *dc, void *data,
        int i;
        char buff[256];
 
+       tegra_dc_io_start(dc);
+
        DUMP_REG(DC_CMD_DISPLAY_COMMAND_OPTION0);
        DUMP_REG(DC_CMD_DISPLAY_COMMAND);
        DUMP_REG(DC_CMD_SIGNAL_RAISE);
@@ -225,6 +227,8 @@ static void _dump_regs(struct tegra_dc *dc, void *data,
                DUMP_REG(DC_WINBUF_ADDR_H_OFFSET);
                DUMP_REG(DC_WINBUF_ADDR_V_OFFSET);
        }
+
+       tegra_dc_io_end(dc);
 }
 
 #undef DUMP_REG
@@ -285,7 +289,6 @@ static void tegra_dc_dbg_add(struct tegra_dc *dc)
 
        snprintf(name, sizeof(name), "tegra_dc%d_regs", dc->ndev->id);
        (void) debugfs_create_file(name, S_IRUGO, NULL, dc, &dbg_fops);
-
 }
 #else
 static void tegra_dc_dbg_add(struct tegra_dc *dc) {}
@@ -533,13 +536,40 @@ int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n)
        }
 
        tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL);
-
        mutex_unlock(&dc->lock);
 
        return 0;
 }
 EXPORT_SYMBOL(tegra_dc_update_windows);
 
+u32 tegra_dc_get_syncpt_id(const struct tegra_dc *dc)
+{
+       return dc->syncpt_id;
+}
+EXPORT_SYMBOL(tegra_dc_get_syncpt_id);
+
+u32 tegra_dc_incr_syncpt_max(struct tegra_dc *dc)
+{
+       u32 max;
+
+       mutex_lock(&dc->lock);
+       max = nvhost_syncpt_incr_max(&dc->ndev->host->syncpt, dc->syncpt_id, 1);
+       dc->syncpt_max = max;
+       mutex_unlock(&dc->lock);
+
+       return max;
+}
+
+void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, u32 val)
+{
+       mutex_lock(&dc->lock);
+       while (dc->syncpt_min < val) {
+               dc->syncpt_min++;
+               nvhost_syncpt_cpu_incr(&dc->ndev->host->syncpt, dc->syncpt_id);
+       }
+       mutex_unlock(&dc->lock);
+}
+
 static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[],
                                             int n)
 {
@@ -769,11 +799,18 @@ static void tegra_dc_set_color_control(struct tegra_dc *dc)
 
 static void tegra_dc_init(struct tegra_dc *dc)
 {
+       u32 disp_syncpt;
+       u32 vblank_syncpt;
+
        tegra_dc_writel(dc, 0x00000100, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
-       if (dc->ndev->id == 0)
-               tegra_dc_writel(dc, 0x0000011a, DC_CMD_CONT_SYNCPT_VSYNC);
-       else
-               tegra_dc_writel(dc, 0x0000011b, DC_CMD_CONT_SYNCPT_VSYNC);
+       if (dc->ndev->id == 0) {
+               disp_syncpt = NVSYNCPT_DISP0;
+               vblank_syncpt = NVSYNCPT_VBLANK0;
+       } else if (dc->ndev->id == 1) {
+               disp_syncpt = NVSYNCPT_DISP1;
+               vblank_syncpt = NVSYNCPT_VBLANK1;
+       }
+       tegra_dc_writel(dc, 0x00000100 | vblank_syncpt, DC_CMD_CONT_SYNCPT_VSYNC);
        tegra_dc_writel(dc, 0x00004700, DC_CMD_INT_TYPE);
        tegra_dc_writel(dc, 0x0001c700, DC_CMD_INT_POLARITY);
        tegra_dc_writel(dc, 0x00000020, DC_DISP_MEM_HIGH_PRIORITY);
@@ -786,18 +823,24 @@ static void tegra_dc_init(struct tegra_dc *dc)
 
        tegra_dc_set_color_control(dc);
 
+       dc->syncpt_id = disp_syncpt;
+
+       dc->syncpt_min = dc->syncpt_max =
+               nvhost_syncpt_read(&dc->ndev->host->syncpt, disp_syncpt);
+
        if (dc->mode.pclk)
                tegra_dc_program_mode(dc, &dc->mode);
 }
 
 static void _tegra_dc_enable(struct tegra_dc *dc)
 {
+       tegra_dc_io_start(dc);
+
        if (dc->out && dc->out->enable)
                dc->out->enable();
 
        tegra_dc_setup_clk(dc, dc->clk);
 
-       clk_enable(dc->host1x_clk);
        clk_enable(dc->clk);
        tegra_periph_reset_deassert(dc->clk);
        enable_irq(dc->irq);
@@ -831,10 +874,17 @@ static void _tegra_dc_disable(struct tegra_dc *dc)
        disable_irq(dc->irq);
        tegra_periph_reset_assert(dc->clk);
        clk_disable(dc->clk);
-       clk_disable(dc->host1x_clk);
 
        if (dc->out && dc->out->disable)
                dc->out->disable();
+
+       /* flush any pending syncpt waits */
+       while (dc->syncpt_min < dc->syncpt_max) {
+               dc->syncpt_min++;
+               nvhost_syncpt_cpu_incr(&dc->ndev->host->syncpt, dc->syncpt_id);
+       }
+
+       tegra_dc_io_end(dc);
 }
 
 
@@ -854,7 +904,6 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
 {
        struct tegra_dc *dc;
        struct clk *clk;
-       struct clk *host1x_clk;
        struct resource *res;
        struct resource *base_res;
        struct resource *fb_mem = NULL;
@@ -904,23 +953,14 @@ static int tegra_dc_probe(struct nvhost_device *ndev)
 
        fb_mem = nvhost_get_resource_byname(ndev, IORESOURCE_MEM, "fbmem");
 
-       host1x_clk = clk_get(&ndev->dev, "host1x");
-       if (IS_ERR_OR_NULL(host1x_clk)) {
-               dev_err(&ndev->dev, "can't get host1x clock\n");
-               ret = -ENOENT;
-               goto err_iounmap_reg;
-       }
-
        clk = clk_get(&ndev->dev, NULL);
        if (IS_ERR_OR_NULL(clk)) {
                dev_err(&ndev->dev, "can't get clock\n");
                ret = -ENOENT;
-
-               goto err_put_host1x_clk;
+               goto err_iounmap_reg;
        }
 
        dc->clk = clk;
-       dc->host1x_clk = host1x_clk;
        dc->base_res = base_res;
        dc->base = base;
        dc->irq = irq;
@@ -984,8 +1024,6 @@ err_free_irq:
        free_irq(irq, dc);
 err_put_clk:
        clk_put(clk);
-err_put_host1x_clk:
-       clk_put(host1x_clk);
 err_iounmap_reg:
        iounmap(base);
        if (fb_mem)
@@ -1014,7 +1052,6 @@ static int tegra_dc_remove(struct nvhost_device *ndev)
 
        free_irq(dc->irq, dc);
        clk_put(dc->clk);
-       clk_put(dc->host1x_clk);
        iounmap(dc->base);
        if (dc->fb_mem)
                release_resource(dc->base_res);
index 2297be40f03014671092d77c5350645be262eb48..39a03e8fb9c0644603e28d7f4acd4eeefada786f 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/wait.h>
+#include "../host/dev.h"
 
 struct tegra_dc;
 
@@ -54,7 +55,6 @@ struct tegra_dc {
        int                             irq;
 
        struct clk                      *clk;
-       struct clk                      *host1x_clk;
 
        bool                            enabled;
 
@@ -74,17 +74,33 @@ struct tegra_dc {
 
        struct resource                 *fb_mem;
        struct tegra_fb_info            *fb;
+
+       u32                             syncpt_id;
+       u32                             syncpt_min;
+       u32                             syncpt_max;
 };
 
+static inline void tegra_dc_io_start(struct tegra_dc *dc)
+{
+       nvhost_module_busy(&dc->ndev->host->mod);
+}
+
+static inline void tegra_dc_io_end(struct tegra_dc *dc)
+{
+       nvhost_module_idle(&dc->ndev->host->mod);
+}
+
 static inline unsigned long tegra_dc_readl(struct tegra_dc *dc,
                                           unsigned long reg)
 {
+       BUG_ON(!nvhost_module_powered(&dc->ndev->host->mod));
        return readl(dc->base + reg * 4);
 }
 
 static inline void tegra_dc_writel(struct tegra_dc *dc, unsigned long val,
                                   unsigned long reg)
 {
+       BUG_ON(!nvhost_module_powered(&dc->ndev->host->mod));
        writel(val, dc->base + reg * 4);
 }
 
index 7e4ffa8ba0b97425847456ef57c7ddee3144ed5d..1656a6e492795a280a5c5567ab6aa2143cff2e6e 100644 (file)
 #include <linux/errno.h>
 #include <linux/string.h>
 #include <linux/mm.h>
+#include <linux/uaccess.h>
 #include <linux/slab.h>
+#include <linux/file.h>
+#include <linux/workqueue.h>
 
 #include <asm/atomic.h>
 
+#include <video/tegrafb.h>
+
 #include <mach/dc.h>
 #include <mach/fb.h>
 #include <mach/nvhost.h>
+#include <mach/nvmap.h>
+
+#include "host/dev.h"
+#include "nvmap/nvmap.h"
 
 struct tegra_fb_info {
        struct tegra_dc_win     *win;
@@ -43,6 +52,24 @@ struct tegra_fb_info {
        int                     yres;
 
        atomic_t                in_use;
+       struct nvmap_client     *user_nvmap;
+       struct nvmap_client     *fb_nvmap;
+
+       struct workqueue_struct *flip_wq;
+};
+
+struct tegra_fb_flip_win {
+       struct tegra_dc_win     win_data;
+       struct tegra_dc_win     *dc_win;
+       s32                     pre_syncpt_id;
+       u32                     pre_syncpt_val;
+};
+
+struct tegra_fb_flip_data {
+       struct work_struct      work;
+       struct tegra_fb_info    *fb;
+       struct tegra_fb_flip_win windows[TEGRA_FB_FLIP_N_WINDOWS];
+       u32                     syncpt_max;
 };
 
 /* palette array used by the fbcon */
@@ -55,6 +82,8 @@ static int tegra_fb_open(struct fb_info *info, int user)
        if (atomic_xchg(&tegra_fb->in_use, 1))
                return -EBUSY;
 
+       tegra_fb->user_nvmap = NULL;
+
        return 0;
 }
 
@@ -62,6 +91,13 @@ static int tegra_fb_release(struct fb_info *info, int user)
 {
        struct tegra_fb_info *tegra_fb = info->par;
 
+       flush_workqueue(tegra_fb->flip_wq);
+
+       if (tegra_fb->user_nvmap) {
+               nvmap_client_put(tegra_fb->user_nvmap);
+               tegra_fb->user_nvmap = NULL;
+       }
+
        WARN_ON(!atomic_xchg(&tegra_fb->in_use, 0));
 
        return 0;
@@ -215,6 +251,12 @@ static int tegra_fb_pan_display(struct fb_var_screeninfo *var,
        tegra_dc_update_windows(&tegra_fb->win, 1);
        tegra_dc_sync_windows(&tegra_fb->win, 1);
 
+       if (WARN_ON(tegra_fb->win->surface)) {
+               nvmap_unpin(tegra_fb->fb_nvmap, tegra_fb->win->surface);
+               nvmap_free(tegra_fb->fb_nvmap, tegra_fb->win->surface);
+               tegra_fb->win->surface = NULL;
+       }
+
        return 0;
 }
 
@@ -236,6 +278,255 @@ static void tegra_fb_imageblit(struct fb_info *info,
        cfb_imageblit(info, image);
 }
 
+/* TODO: implement ALLOC, FREE, BLANK ioctls */
+
+static int tegra_fb_set_nvmap_fd(struct tegra_fb_info *tegra_fb, int fd)
+{
+       struct nvmap_client *nvmap = NULL;
+
+       if (fd < 0)
+               return -EINVAL;
+
+       nvmap = nvmap_client_get_file(fd);
+       if (IS_ERR(nvmap))
+               return PTR_ERR(nvmap);
+
+       if (tegra_fb->user_nvmap)
+               nvmap_client_put(tegra_fb->user_nvmap);
+
+       tegra_fb->user_nvmap = nvmap;
+
+       return 0;
+}
+
+static int tegra_fb_set_windowattr(struct tegra_fb_info *tegra_fb,
+                                  struct tegra_dc_win *win,
+                                  const struct tegra_fb_windowattr *attr)
+{
+       struct nvmap_handle_ref *r_dupe;
+       struct nvmap_handle *h_win;
+
+       if (!attr->buff_id) {
+               win->flags = 0;
+               win->surface = NULL;
+               return 0;
+       }
+
+       h_win = nvmap_get_handle_id(tegra_fb->user_nvmap, attr->buff_id);
+       if (h_win == NULL) {
+               dev_err(&tegra_fb->ndev->dev, "%s: flip invalid "
+                       "handle %08x\n", current->comm, attr->buff_id);
+               return -EPERM;
+       }
+
+       /* duplicate the new framebuffer's handle into the fb driver's
+        * nvmap context, to ensure that the handle won't be freed as
+        * long as it is in-use by the fb driver */
+       r_dupe = nvmap_duplicate_handle_id(tegra_fb->fb_nvmap, attr->buff_id);
+       nvmap_handle_put(h_win);
+
+       if (IS_ERR(r_dupe)) {
+               dev_err(&tegra_fb->ndev->dev, "couldn't duplicate handle\n");
+               return PTR_ERR(r_dupe);
+       }
+
+       win->surface = r_dupe;
+
+       win->flags = TEGRA_WIN_FLAG_ENABLED;
+       if (attr->blend == TEGRA_FB_WIN_BLEND_PREMULT)
+               win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT;
+       else if (attr->blend == TEGRA_FB_WIN_BLEND_COVERAGE)
+               win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE;
+       win->fmt = attr->pixformat;
+       win->x = attr->x;
+       win->y = attr->y;
+       win->w = attr->w;
+       win->h = attr->h;
+       win->out_x = attr->out_x;
+       win->out_y = attr->out_y;
+       win->out_w = attr->out_w;
+       win->out_h = attr->out_h;
+       win->z = attr->z;
+
+       win->phys_addr = nvmap_pin(tegra_fb->fb_nvmap, r_dupe);
+       if (IS_ERR((void *)win->phys_addr)) {
+               dev_err(&tegra_fb->ndev->dev, "couldn't pin handle\n");
+               nvmap_free(tegra_fb->fb_nvmap, r_dupe);
+               return (int)win->phys_addr;
+       }
+       /* STOPSHIP verify that this won't read outside of the surface */
+       win->phys_addr += attr->offset;
+       win->stride = attr->stride;
+
+       return 0;
+}
+
+static void tegra_fb_flip_work(struct work_struct *work)
+{
+       struct tegra_fb_flip_data *data;
+       struct tegra_dc_win *wins[TEGRA_FB_FLIP_N_WINDOWS];
+       struct nvmap_handle_ref *surfs[TEGRA_FB_FLIP_N_WINDOWS];
+       int i, nr_win = 0, nr_unpin = 0;
+
+       data = container_of(work, struct tegra_fb_flip_data, work);
+
+       for (i = 0; i < TEGRA_FB_FLIP_N_WINDOWS; i++) {
+               struct tegra_fb_flip_win *flip_win = &data->windows[i];
+
+               if (!flip_win->dc_win)
+                       continue;
+
+               if (flip_win->dc_win->flags && flip_win->dc_win->surface)
+                       surfs[nr_unpin++] = flip_win->dc_win->surface;
+
+               wins[nr_win++] = flip_win->dc_win;
+
+               flip_win->dc_win->flags = flip_win->win_data.flags;
+               if (!flip_win->dc_win->flags)
+                       continue;
+
+               flip_win->dc_win->surface = flip_win->win_data.surface;
+               flip_win->dc_win->fmt = flip_win->win_data.fmt;
+               flip_win->dc_win->x = flip_win->win_data.x;
+               flip_win->dc_win->y = flip_win->win_data.y;
+               flip_win->dc_win->w = flip_win->win_data.w;
+               flip_win->dc_win->h = flip_win->win_data.h;
+               flip_win->dc_win->out_x = flip_win->win_data.out_x;
+               flip_win->dc_win->out_y = flip_win->win_data.out_y;
+               flip_win->dc_win->out_w = flip_win->win_data.out_w;
+               flip_win->dc_win->out_h = flip_win->win_data.out_h;
+               flip_win->dc_win->z = flip_win->win_data.z;
+               flip_win->dc_win->phys_addr = flip_win->win_data.phys_addr;
+               flip_win->dc_win->stride = flip_win->win_data.stride;
+
+               if (flip_win->pre_syncpt_id < 0)
+                       continue;
+
+               nvhost_syncpt_wait_timeout(&data->fb->ndev->host->syncpt,
+                                          flip_win->pre_syncpt_id,
+                                          flip_win->pre_syncpt_val,
+                                          msecs_to_jiffies(500));
+       }
+
+       if (!nr_win)
+               goto free_data;
+
+       tegra_dc_update_windows(wins, nr_win);
+       /* TODO: implement swapinterval here */
+       tegra_dc_sync_windows(wins, nr_win);
+
+       tegra_dc_incr_syncpt_min(data->fb->win->dc, data->syncpt_max);
+
+       /* unpin and deref previous front buffers */
+       for (i = 0; i < nr_unpin; i++) {
+               nvmap_unpin(data->fb->fb_nvmap, surfs[i]);
+               nvmap_free(data->fb->fb_nvmap, surfs[i]);
+       }
+
+free_data:
+       kfree(data);
+}
+
+static int tegra_fb_flip(struct tegra_fb_info *tegra_fb,
+                        struct tegra_fb_flip_args *args)
+{
+       struct tegra_fb_flip_data *data;
+       struct tegra_fb_flip_win *flip_win;
+       struct tegra_dc *dc = tegra_fb->win->dc;
+       u32 syncpt_max;
+       int i, err;
+
+       if (WARN_ON(!tegra_fb->user_nvmap))
+               return -EFAULT;
+
+       if (WARN_ON(!tegra_fb->ndev))
+               return -EFAULT;
+
+       data = kmalloc(sizeof(*data), GFP_KERNEL);
+       if (!data) {
+               dev_err(&tegra_fb->ndev->dev, "no memory for flip\n");
+               return -ENOMEM;
+       }
+
+       INIT_WORK(&data->work, tegra_fb_flip_work);
+       data->fb = tegra_fb;
+
+       for (i = 0; i < TEGRA_FB_FLIP_N_WINDOWS; i++) {
+
+               flip_win = &data->windows[i];
+               flip_win->dc_win = tegra_dc_get_window(dc, args->win[i].index);
+               flip_win->pre_syncpt_id = args->win[i].pre_syncpt_id;
+               flip_win->pre_syncpt_val = args->win[i].pre_syncpt_val;
+
+               if (!flip_win->dc_win)
+                       continue;
+
+               err = tegra_fb_set_windowattr(tegra_fb, &flip_win->win_data,
+                                             &args->win[i]);
+               if (err) {
+                       dev_err(&tegra_fb->ndev->dev, "error setting window "
+                               "attributes\n");
+                       goto surf_err;
+               }
+       }
+
+       syncpt_max = tegra_dc_incr_syncpt_max(dc);
+       data->syncpt_max = syncpt_max;
+
+       queue_work(tegra_fb->flip_wq, &data->work);
+
+       args->post_syncpt_val = syncpt_max;
+       args->post_syncpt_id = tegra_dc_get_syncpt_id(dc);
+
+       return 0;
+
+surf_err:
+       while (i--) {
+               if (data->windows[i].win_data.surface) {
+                       nvmap_unpin(tegra_fb->fb_nvmap,
+                                   data->windows[i].win_data.surface);
+                       nvmap_free(tegra_fb->fb_nvmap,
+                                  data->windows[i].win_data.surface);
+               }
+       }
+       kfree(data);
+       return err;
+}
+
+/* TODO: implement private window ioctls to set overlay x,y */
+
+static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+       struct tegra_fb_info *tegra_fb = info->par;
+       struct tegra_fb_flip_args flip_args;
+       int fd;
+       int ret;
+
+       switch (cmd) {
+       case FBIO_TEGRA_SET_NVMAP_FD:
+               if (copy_from_user(&fd, (void __user *)arg, sizeof(fd)))
+                       return -EFAULT;
+
+               return tegra_fb_set_nvmap_fd(tegra_fb, fd);
+
+       case FBIO_TEGRA_FLIP:
+               if (copy_from_user(&flip_args, (void __user *)arg, sizeof(flip_args)))
+                       return -EFAULT;
+
+               ret = tegra_fb_flip(tegra_fb, &flip_args);
+
+               if (copy_to_user((void __user *)arg, &flip_args, sizeof(flip_args)))
+                       return -EFAULT;
+
+               return ret;
+
+       default:
+               return -ENOTTY;
+       }
+
+       return 0;
+}
+
 static struct fb_ops tegra_fb_ops = {
        .owner = THIS_MODULE,
        .fb_open = tegra_fb_open,
@@ -248,6 +539,7 @@ static struct fb_ops tegra_fb_ops = {
        .fb_fillrect = tegra_fb_fillrect,
        .fb_copyarea = tegra_fb_copyarea,
        .fb_imageblit = tegra_fb_imageblit,
+       .fb_ioctl = tegra_fb_ioctl,
 };
 
 void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info,
@@ -316,8 +608,21 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
        tegra_fb->fb_mem = fb_mem;
        tegra_fb->xres = fb_data->xres;
        tegra_fb->yres = fb_data->yres;
+       tegra_fb->fb_nvmap = nvmap_create_client(nvmap_dev);
+       if (!tegra_fb->fb_nvmap) {
+               dev_err(&ndev->dev, "couldn't create nvmap client\n");
+               ret = -ENOMEM;
+               goto err_free;
+       }
        atomic_set(&tegra_fb->in_use, 0);
 
+       tegra_fb->flip_wq = create_singlethread_workqueue(dev_name(&ndev->dev));
+       if (!tegra_fb->flip_wq) {
+               dev_err(&ndev->dev, "couldn't create flip work-queue\n");
+               ret = -ENOMEM;
+               goto err_delete_wq;
+       }
+
        if (fb_mem) {
                fb_size = resource_size(fb_mem);
                fb_phys = fb_mem->start;
@@ -325,7 +630,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
                if (!fb_base) {
                        dev_err(&ndev->dev, "fb can't be mapped\n");
                        ret = -EBUSY;
-                       goto err_free;
+                       goto err_put_client;
                }
                tegra_fb->valid = true;
        }
@@ -396,6 +701,10 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev,
 
 err_iounmap_fb:
        iounmap(fb_base);
+err_put_client:
+       nvmap_client_put(tegra_fb->fb_nvmap);
+err_delete_wq:
+       destroy_workqueue(tegra_fb->flip_wq);
 err_free:
        framebuffer_release(info);
 err:
@@ -406,7 +715,19 @@ void tegra_fb_unregister(struct tegra_fb_info *fb_info)
 {
        struct fb_info *info = fb_info->info;
 
+       if (fb_info->win->surface) {
+               nvmap_unpin(fb_info->fb_nvmap, fb_info->win->surface);
+               nvmap_free(fb_info->fb_nvmap, fb_info->win->surface);
+       }
+
+       if (fb_info->fb_nvmap)
+               nvmap_client_put(fb_info->fb_nvmap);
+
        unregister_framebuffer(info);
+
+       flush_workqueue(fb_info->flip_wq);
+       destroy_workqueue(fb_info->flip_wq);
+
        iounmap(info->screen_base);
        framebuffer_release(info);
 }
diff --git a/include/video/tegrafb.h b/include/video/tegrafb.h
new file mode 100644 (file)
index 0000000..3d7a5a9
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * include/video/tegrafb.h
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Erik Gilling <konkers@android.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _LINUX_TEGRAFB_H_
+#define _LINUX_TEGRAFB_H_
+
+#include <linux/types.h>
+#include <asm/ioctl.h>
+
+#define TEGRA_FB_WIN_FMT_P1            0
+#define TEGRA_FB_WIN_FMT_P2            1
+#define TEGRA_FB_WIN_FMT_P4            2
+#define TEGRA_FB_WIN_FMT_P8            3
+#define TEGRA_FB_WIN_FMT_B4G4R4A4      4
+#define TEGRA_FB_WIN_FMT_B5G5R5A       5
+#define TEGRA_FB_WIN_FMT_B5G6R5                6
+#define TEGRA_FB_WIN_FMT_AB5G5R5       7
+#define TEGRA_FB_WIN_FMT_B8G8R8A8      12
+#define TEGRA_FB_WIN_FMT_R8G8B8A8      13
+#define TEGRA_FB_WIN_FMT_B6x2G6x2R6x2A8        14
+#define TEGRA_FB_WIN_FMT_R6x2G6x2B6x2A8        15
+#define TEGRA_FB_WIN_FMT_YCbCr422      16
+#define TEGRA_FB_WIN_FMT_YUV422                17
+#define TEGRA_FB_WIN_FMT_YCbCr420P     18
+#define TEGRA_FB_WIN_FMT_YUV420P       19
+#define TEGRA_FB_WIN_FMT_YCbCr422P     20
+#define TEGRA_FB_WIN_FMT_YUV422P       21
+#define TEGRA_FB_WIN_FMT_YCbCr422R     22
+#define TEGRA_FB_WIN_FMT_YUV422R       23
+#define TEGRA_FB_WIN_FMT_YCbCr422RA    24
+#define TEGRA_FB_WIN_FMT_YUV422RA      25
+
+#define TEGRA_FB_WIN_BLEND_NONE        0
+#define TEGRA_FB_WIN_BLEND_PREMULT 1
+#define TEGRA_FB_WIN_BLEND_COVERAGE 2
+
+/* set index to -1 to ignore window data */
+struct tegra_fb_windowattr {
+       __s32   index;
+       __u32   buff_id;
+       __u32   blend;
+       __u32   offset;
+       __u32   stride;
+       __u32   pixformat;
+       __u32   x;
+       __u32   y;
+       __u32   w;
+       __u32   h;
+       __u32   out_x;
+       __u32   out_y;
+       __u32   out_w;
+       __u32   out_h;
+       __u32   z;
+       __u32   pre_syncpt_id;
+       __u32   pre_syncpt_val;
+};
+
+#define TEGRA_FB_FLIP_N_WINDOWS                3
+
+struct tegra_fb_flip_args {
+       struct tegra_fb_windowattr win[TEGRA_FB_FLIP_N_WINDOWS];
+       __u32 post_syncpt_id;
+       __u32 post_syncpt_val;
+};
+
+#define FBIO_TEGRA_SET_NVMAP_FD        _IOW('F', 0x40, __u32)
+#define FBIO_TEGRA_FLIP                _IOW('F', 0x41, struct tegra_fb_flip_args)
+
+#endif