diff options
author | Robert Morell <rmorell@nvidia.com> | 2011-02-16 10:45:53 -0800 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:48:11 -0800 |
commit | caec6505db7d6297dbf5767c48f23a346d7ef7be (patch) | |
tree | ae4179512abbfac47f0230f03878a89bb861e587 /drivers/video/tegra/dc/ext/dev.c | |
parent | ead8aaa0c6be72c6028cc1a75ba70baaf31ff2d9 (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/dev.c')
-rw-r--r-- | drivers/video/tegra/dc/ext/dev.c | 347 |
1 files changed, 337 insertions, 10 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); } |