video: tegra: Implement FLIP dc extension ioctl.
Robert Morell [Wed, 16 Feb 2011 18:45:53 +0000 (10:45 -0800)]
This is very similar to the tegra_fb FLIP ioctl.

bug 818525

Original-Change-Id: Iba32ab5bf730b575477c62a8ae4394f1779ef65e
Signed-off-by: Robert Morell <rmorell@nvidia.com>
Reviewed-on: http://git-master/r/40514
Reviewed-by: Jonathan Mayo <jmayo@nvidia.com>

Rebase-Id: R9a79363b09d2df38bec4b8a8666f97b1feff76ff

arch/arm/mach-tegra/include/mach/tegra_dc_ext.h
drivers/video/tegra/dc/dc.c
drivers/video/tegra/dc/ext/dev.c
drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
include/video/tegra_dc_ext.h

index 54680a5..7156695 100644 (file)
@@ -31,6 +31,9 @@ struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
                                           struct tegra_dc *dc);
 void tegra_dc_ext_unregister(struct tegra_dc_ext *dc_ext);
 
+/* called by display controller on suspend */
+void tegra_dc_ext_suspend(struct tegra_dc_ext *dc_ext);
+
 #else /* CONFIG_TEGRA_DC_EXTENSIONS */
 
 static inline
@@ -53,6 +56,10 @@ static inline
 void tegra_dc_ext_unregister(struct tegra_dc_ext *dc_ext)
 {
 }
+static inline
+void tegra_dc_ext_suspend(struct tegra_dc_ext *dc_ext)
+{
+}
 #endif /* CONFIG_TEGRA_DC_EXTENSIONS */
 
 #endif /* __MACH_TEGRA_DC_EXT_H */
index a0e8c45..9f190cc 100644 (file)
@@ -2453,6 +2453,7 @@ static int tegra_dc_suspend(struct nvhost_device *ndev, pm_message_t state)
 
        if (dc->enabled) {
                tegra_fb_suspend(dc->fb);
+               tegra_dc_ext_suspend(dc->ext);
                _tegra_dc_disable(dc);
 
                dc->suspended = true;
index e5f8299..8905402 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/fs.h>
 #include <linux/uaccess.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
 
 #include <video/tegra_dc_ext.h>
 
 
 /* XXX ew */
 #include "../dc_priv.h"
+/* XXX ew 2 */
+#include "../../host/dev.h"
+/* XXX ew 3 */
+#include "../../nvmap/nvmap.h"
 #include "tegra_dc_ext_priv.h"
 
 static int tegra_dc_ext_devno;
 static struct class *tegra_dc_ext_class;
 
+struct tegra_dc_ext_flip_win {
+       struct tegra_dc_ext_flip_windowattr     attr;
+       struct nvmap_handle_ref                 *handle;
+       /* ugh. is this really necessary */
+       dma_addr_t                              phys_addr;
+};
+
+struct tegra_dc_ext_flip_data {
+       struct tegra_dc_ext             *ext;
+       struct work_struct              work;
+       struct tegra_dc_ext_flip_win    win[DC_N_WINDOWS];
+       u32                             syncpt_max;
+};
+
 static int tegra_dc_ext_set_nvmap_fd(struct tegra_dc_ext_user *user,
                                     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 (fd >= 0) {
+               nvmap = nvmap_client_get_file(fd);
+               if (IS_ERR(nvmap))
+                       return PTR_ERR(nvmap);
+       }
 
        if (user->nvmap)
                nvmap_client_put(user->nvmap);
@@ -104,14 +122,293 @@ static int tegra_dc_ext_put_window(struct tegra_dc_ext_user *user,
        return ret;
 }
 
+void tegra_dc_ext_suspend(struct tegra_dc_ext *ext)
+{
+       flush_workqueue(ext->flip_wq);
+}
+
+static int tegra_dc_ext_set_windowattr(struct tegra_dc_ext *ext,
+                              struct tegra_dc_win *win,
+                              const struct tegra_dc_ext_flip_win *flip_win)
+{
+       struct tegra_dc_ext_win *ext_win = &ext->win[win->idx];
+
+       if (flip_win->handle == NULL) {
+               win->flags = 0;
+               ext_win->cur_handle = NULL;
+               return 0;
+       }
+
+       win->flags = TEGRA_WIN_FLAG_ENABLED;
+       if (flip_win->attr.blend == TEGRA_DC_EXT_BLEND_PREMULT)
+               win->flags |= TEGRA_WIN_FLAG_BLEND_PREMULT;
+       else if (flip_win->attr.blend == TEGRA_DC_EXT_BLEND_COVERAGE)
+               win->flags |= TEGRA_WIN_FLAG_BLEND_COVERAGE;
+       win->fmt = flip_win->attr.pixformat;
+       win->x = flip_win->attr.x;
+       win->y = flip_win->attr.y;
+       win->w = flip_win->attr.w;
+       win->h = flip_win->attr.h;
+       /* XXX verify that this doesn't go outside display's active region */
+       win->out_x = flip_win->attr.out_x;
+       win->out_y = flip_win->attr.out_y;
+       win->out_w = flip_win->attr.out_w;
+       win->out_h = flip_win->attr.out_h;
+       win->z = flip_win->attr.z;
+       ext_win->cur_handle = flip_win->handle;
+
+       /* XXX verify that this won't read outside of the surface */
+       win->phys_addr = flip_win->phys_addr + flip_win->attr.offset;
+       win->offset_u = flip_win->attr.offset_u + flip_win->attr.offset;
+       win->offset_v = flip_win->attr.offset_v + flip_win->attr.offset;
+       win->stride = flip_win->attr.stride;
+       win->stride_uv = flip_win->attr.stride_uv;
+
+       if ((s32)flip_win->attr.pre_syncpt_id >= 0) {
+               nvhost_syncpt_wait_timeout(&ext->dc->ndev->host->syncpt,
+                                          flip_win->attr.pre_syncpt_id,
+                                          flip_win->attr.pre_syncpt_val,
+                                          msecs_to_jiffies(500), NULL);
+       }
+
+
+       return 0;
+}
+
+static void tegra_dc_ext_flip_worker(struct work_struct *work)
+{
+       struct tegra_dc_ext_flip_data *data =
+               container_of(work, struct tegra_dc_ext_flip_data, work);
+       struct tegra_dc_ext *ext = data->ext;
+       struct tegra_dc_win *wins[DC_N_WINDOWS];
+       struct nvmap_handle_ref *unpin_handles[DC_N_WINDOWS];
+       int i, nr_unpin = 0, nr_win = 0;
+
+       for (i = 0; i < DC_N_WINDOWS; i++) {
+               struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
+               int index = flip_win->attr.index;
+               struct tegra_dc_win *win;
+               struct tegra_dc_ext_win *ext_win;
+
+               if (index < 0)
+                       continue;
+
+               win = tegra_dc_get_window(ext->dc, index);
+               ext_win = &ext->win[index];
+
+               if ((win->flags & TEGRA_WIN_FLAG_ENABLED) &&
+                   ext_win->cur_handle)
+                       unpin_handles[nr_unpin++] = ext_win->cur_handle;
+
+               tegra_dc_ext_set_windowattr(ext, win, &data->win[i]);
+
+               wins[nr_win++] = win;
+       }
+
+       tegra_dc_update_windows(wins, nr_win);
+       /* TODO: implement swapinterval here */
+       tegra_dc_sync_windows(wins, nr_win);
+
+       tegra_dc_incr_syncpt_min(ext->dc, data->syncpt_max);
+
+       /* unpin and deref previous front buffers */
+       for (i = 0; i < nr_unpin; i++) {
+               nvmap_unpin(ext->nvmap, unpin_handles[i]);
+               nvmap_free(ext->nvmap, unpin_handles[i]);
+       }
+
+       kfree(data);
+}
+
+static int tegra_dc_ext_pin_window(struct tegra_dc_ext_user *user,
+                                  struct tegra_dc_ext_flip_win *flip_win)
+{
+       struct tegra_dc_ext *ext = user->ext;
+       struct nvmap_handle_ref *win_dup;
+       struct nvmap_handle *win_handle;
+       u32 id = flip_win->attr.buff_id;
+
+       if (!id) {
+               flip_win->handle = NULL;
+               flip_win->phys_addr = -1;
+
+               return 0;
+       }
+
+       /*
+        * Take a reference to the buffer using the user's nvmap context, to
+        * make sure they have permissions to access it.
+        */
+       win_handle = nvmap_get_handle_id(user->nvmap, id);
+       if (!win_handle)
+               return -EACCES;
+
+       /*
+        * Duplicate the buffer's handle into the dc_ext driver's nvmap
+        * context, to ensure that the handle won't be freed as long as it is
+        * in use by display.
+        */
+       win_dup = nvmap_duplicate_handle_id(ext->nvmap, id);
+
+       /* Release the reference we took in the user's context above */
+       nvmap_handle_put(win_handle);
+
+       if (IS_ERR(win_dup))
+               return PTR_ERR(win_dup);
+
+       flip_win->handle = win_dup;
+
+       flip_win->phys_addr = nvmap_pin(ext->nvmap, win_dup);
+       /* XXX this isn't correct for non-pointers... */
+       if (IS_ERR((void *)flip_win->phys_addr)) {
+               nvmap_free(ext->nvmap, win_dup);
+               return PTR_ERR((void *)flip_win->phys_addr);
+       }
+
+       return 0;
+}
+
+static int lock_windows_for_flip(struct tegra_dc_ext_user *user,
+                                struct tegra_dc_ext_flip *args)
+{
+       struct tegra_dc_ext *ext = user->ext;
+       int i;
+
+       for (i = 0; i < DC_N_WINDOWS; i++) {
+               int index = args->win[i].index;
+               struct tegra_dc_ext_win *win;
+
+               if (index < 0)
+                       continue;
+
+               win = &ext->win[index];
+
+               mutex_lock(&win->lock);
+
+               if (win->user != user)
+                       goto fail_unlock;
+       }
+
+       return 0;
+
+fail_unlock:
+       do {
+               int index = args->win[i].index;
+
+               if (index < 0)
+                       continue;
+
+               mutex_unlock(&ext->win[index].lock);
+       } while (i--);
+
+       return -EACCES;
+}
+
+static void unlock_windows_for_flip(struct tegra_dc_ext_user *user,
+                                   struct tegra_dc_ext_flip *args)
+{
+       struct tegra_dc_ext *ext = user->ext;
+       int i;
+
+       for (i = 0; i < DC_N_WINDOWS; i++) {
+               int index = args->win[i].index;
+
+               if (index < 0)
+                       continue;
+
+               mutex_unlock(&ext->win[index].lock);
+       }
+}
+
+static int sanitize_flip_args(struct tegra_dc_ext_user *user,
+                             struct tegra_dc_ext_flip *args)
+{
+       int i, used_windows = 0;
+
+       for (i = 0; i < DC_N_WINDOWS; i++) {
+               int index = args->win[i].index;
+
+               if (index < 0)
+                       continue;
+
+               if (index >= DC_N_WINDOWS)
+                       return -EINVAL;
+
+               if (used_windows & BIT(index))
+                       return -EINVAL;
+
+               used_windows |= BIT(index);
+       }
+
+       if (!used_windows)
+               return -EINVAL;
+
+       return 0;
+}
+
 static int tegra_dc_ext_flip(struct tegra_dc_ext_user *user,
                             struct tegra_dc_ext_flip *args)
 {
+       struct tegra_dc_ext *ext = user->ext;
+       struct tegra_dc_ext_flip_data *data;
+       u32 syncpt_max;
+       int i, ret = 0;
+
        if (!user->nvmap)
-               return -EINVAL;
+               return -EFAULT;
+
+       ret = sanitize_flip_args(user, args);
+       if (ret)
+               return ret;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       INIT_WORK(&data->work, tegra_dc_ext_flip_worker);
+       data->ext = ext;
+
+       for (i = 0; i < DC_N_WINDOWS; i++) {
+               struct tegra_dc_ext_flip_win *flip_win = &data->win[i];
+               int index = args->win[i].index;
+
+               memcpy(&flip_win->attr, &args->win[i], sizeof(flip_win->attr));
+
+               if (index < 0)
+                       continue;
+
+               ret = tegra_dc_ext_pin_window(user, flip_win);
+               if (ret)
+                       goto fail_pin;
+       }
+
+       ret = lock_windows_for_flip(user, args);
+       if (ret)
+               goto fail_pin;
+
+       syncpt_max = tegra_dc_incr_syncpt_max(ext->dc);
+       data->syncpt_max = syncpt_max;
+
+       args->post_syncpt_val = syncpt_max;
+       args->post_syncpt_id = tegra_dc_get_syncpt_id(ext->dc);
+
+       queue_work(ext->flip_wq, &data->work);
+
+       unlock_windows_for_flip(user, args);
 
-       printk(KERN_ERR "flip\n");
        return 0;
+
+fail_pin:
+       while (i--) {
+               if (!data->win[i].handle)
+                       continue;
+
+               nvmap_unpin(ext->nvmap, data->win[i].handle);
+               nvmap_free(ext->nvmap, data->win[i].handle);
+       }
+       kfree(data);
+
+       return ret;
 }
 
 static long tegra_dc_ioctl(struct file *filp, unsigned int cmd,
@@ -178,6 +475,9 @@ static int tegra_dc_release(struct inode *inode, struct file *filp)
                        tegra_dc_ext_put_window(user, i);
        }
 
+       if (user->nvmap)
+               nvmap_client_put(user->nvmap);
+
        kfree(user);
 
        return 0;
@@ -187,7 +487,7 @@ static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext)
 {
        int i;
 
-       for (i = 0; i < DC_N_WINDOWS; i++) {
+       for (i = 0; i < ext->dc->n_windows; i++) {
                struct tegra_dc_ext_win *win = &ext->win[i];
 
                win->ext = ext;
@@ -237,14 +537,34 @@ struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
                goto cleanup_cdev;
        }
 
+       ext->dc = dc;
+
+       ext->nvmap = nvmap_create_client(nvmap_dev, "tegra_dc_ext");
+       if (!ext->nvmap) {
+               ret = -ENOMEM;
+               goto cleanup_device;
+       }
+
+       ext->flip_wq = create_singlethread_workqueue(dev_name(&ndev->dev));
+       if (!ext->flip_wq) {
+               ret = -ENOMEM;
+               goto cleanup_nvmap;
+       }
+
        ret = tegra_dc_ext_setup_windows(ext);
        if (ret)
-               goto cleanup_device;
+               goto cleanup_wq;
 
        tegra_dc_ext_devno++;
 
        return ext;
 
+cleanup_wq:
+       destroy_workqueue(ext->flip_wq);
+
+cleanup_nvmap:
+       nvmap_client_put(ext->nvmap);
+
 cleanup_device:
        device_del(ext->dev);
 
@@ -259,7 +579,14 @@ cleanup_alloc:
 
 void tegra_dc_ext_unregister(struct tegra_dc_ext *ext)
 {
+
+       flush_workqueue(ext->flip_wq);
+       destroy_workqueue(ext->flip_wq);
+
+       nvmap_client_put(ext->nvmap);
+       device_del(ext->dev);
        cdev_del(&ext->cdev);
+
        kfree(ext);
 }
 
index 27dfbcb..20018c9 100644 (file)
 
 #include <mach/nvmap.h>
 
-struct tegra_dc_ext_user;
+struct tegra_dc_ext;
+
+struct tegra_dc_ext_user {
+       struct tegra_dc_ext     *ext;
+       struct nvmap_client     *nvmap;
+};
 
 struct tegra_dc_ext_win {
        struct tegra_dc_ext     *ext;
@@ -34,18 +39,23 @@ struct tegra_dc_ext_win {
        struct tegra_dc_ext_user *user;
 
        struct mutex            lock;
+
+       u32                     syncpt_id;
+
+       struct nvmap_handle_ref *cur_handle;
 };
 
 struct tegra_dc_ext {
-       struct cdev     cdev;
-       struct device   *dev;
+       struct tegra_dc                 *dc;
+
+       struct cdev                     cdev;
+       struct device                   *dev;
+
+       struct nvmap_client             *nvmap;
 
        struct tegra_dc_ext_win         win[DC_N_WINDOWS];
-};
 
-struct tegra_dc_ext_user {
-       struct tegra_dc_ext     *ext;
-       struct nvmap_client     *nvmap;
+       struct workqueue_struct         *flip_wq;
 };
 
 #endif /* __TEGRA_DC_EXT_PRIV_H */
index 18f2262..346825a 100644 (file)
 
 #include <linux/types.h>
 #include <linux/ioctl.h>
+#if defined(__KERNEL__)
+# include <linux/time.h>
+#else
+# include <time.h>
+#endif
 
 #define TEGRA_DC_EXT_FMT_P1            0
 #define TEGRA_DC_EXT_FMT_P2            1
@@ -68,14 +73,20 @@ struct tegra_dc_ext_flip_windowattr {
        __u32   out_w;
        __u32   out_h;
        __u32   z;
+       __u32   swap_interval;
+       struct timespec timestamp;
        __u32   pre_syncpt_id;
        __u32   pre_syncpt_val;
+       /* Leave some wiggle room for future expansion */
+       __u32   pad[8];
 };
 
 #define TEGRA_DC_EXT_FLIP_N_WINDOWS    3
 
 struct tegra_dc_ext_flip {
        struct tegra_dc_ext_flip_windowattr win[TEGRA_DC_EXT_FLIP_N_WINDOWS];
+       __u32   post_syncpt_id;
+       __u32   post_syncpt_val;
 };
 
 #define TEGRA_DC_EXT_SET_NVMAP_FD \