summaryrefslogtreecommitdiff
path: root/drivers/video/tegra/dc/ext
diff options
context:
space:
mode:
authorRobert Morell <rmorell@nvidia.com>2011-02-16 10:45:53 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:48:11 -0800
commitcaec6505db7d6297dbf5767c48f23a346d7ef7be (patch)
treeae4179512abbfac47f0230f03878a89bb861e587 /drivers/video/tegra/dc/ext
parentead8aaa0c6be72c6028cc1a75ba70baaf31ff2d9 (diff)
video: tegra: Implement FLIP dc extension ioctl.
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
Diffstat (limited to 'drivers/video/tegra/dc/ext')
-rw-r--r--drivers/video/tegra/dc/ext/dev.c347
-rw-r--r--drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h24
2 files changed, 354 insertions, 17 deletions
diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c
index e5f8299c1366..8905402954bf 100644
--- a/drivers/video/tegra/dc/ext/dev.c
+++ b/drivers/video/tegra/dc/ext/dev.c
@@ -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>
@@ -31,22 +32,39 @@
/* 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);
}
diff --git a/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h b/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
index 27dfbcbffb3a..20018c9fbe20 100644
--- a/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
+++ b/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
@@ -24,7 +24,12 @@
#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 */