diff options
Diffstat (limited to 'drivers/video/tegra/dc/ext/dev.c')
-rw-r--r-- | drivers/video/tegra/dc/ext/dev.c | 1167 |
1 files changed, 1167 insertions, 0 deletions
diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c new file mode 100644 index 000000000000..37a6d9bd3f80 --- /dev/null +++ b/drivers/video/tegra/dc/ext/dev.c @@ -0,0 +1,1167 @@ +/* + * drivers/video/tegra/dc/dev.c + * + * Copyright (c) 2011-2012, NVIDIA CORPORATION, All rights reserved. + * + * Author: Robert Morell <rmorell@nvidia.com> + * Some code based on fbdev extensions written by: + * Erik Gilling <konkers@android.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <video/tegra_dc_ext.h> + +#include <mach/dc.h> +#include <linux/nvmap.h> +#include <mach/tegra_dc_ext.h> + +/* XXX ew */ +#include "../dc_priv.h" +#include "../dc_config.h" +/* XXX ew 2 */ +#include "../../host/dev.h" +/* XXX ew 3 */ +#include "../../nvmap/nvmap.h" +#include "tegra_dc_ext_priv.h" + +int tegra_dc_ext_devno; +struct class *tegra_dc_ext_class; +static int head_count; + +struct tegra_dc_ext_flip_win { + struct tegra_dc_ext_flip_windowattr attr; + struct nvmap_handle_ref *handle[TEGRA_DC_NUM_PLANES]; + dma_addr_t phys_addr; + dma_addr_t phys_addr_u; + dma_addr_t phys_addr_v; + u32 syncpt_max; +}; + +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]; +#ifndef CONFIG_ANDROID + struct list_head timestamp_node; +#endif /* !CONFIG_ANDROID */ +}; + +int tegra_dc_ext_get_num_outputs(void) +{ + /* TODO: decouple output count from head count */ + return head_count; +} + +static int tegra_dc_ext_set_nvmap_fd(struct tegra_dc_ext_user *user, + int fd) +{ + struct nvmap_client *nvmap = NULL; + + 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); + + user->nvmap = nvmap; + + return 0; +} + +static int tegra_dc_ext_get_window(struct tegra_dc_ext_user *user, + unsigned int n) +{ + struct tegra_dc_ext *ext = user->ext; + struct tegra_dc_ext_win *win; + int ret = 0; + + if (n >= DC_N_WINDOWS) + return -EINVAL; + + win = &ext->win[n]; + + mutex_lock(&win->lock); + + if (!win->user) + win->user = user; + else if (win->user != user) + ret = -EBUSY; + + mutex_unlock(&win->lock); + + return ret; +} + +static int tegra_dc_ext_put_window(struct tegra_dc_ext_user *user, + unsigned int n) +{ + struct tegra_dc_ext *ext = user->ext; + struct tegra_dc_ext_win *win; + int ret = 0; + + if (n >= DC_N_WINDOWS) + return -EINVAL; + + win = &ext->win[n]; + + mutex_lock(&win->lock); + + if (win->user == user) { + flush_workqueue(win->flip_wq); + win->user = 0; + } else { + ret = -EACCES; + } + + mutex_unlock(&win->lock); + + return ret; +} + +static void set_enable(struct tegra_dc_ext *ext, bool en) +{ + int i; + + /* + * Take all locks to make sure any flip requests or cursor moves are + * out of their critical sections + */ + for (i = 0; i < ext->dc->n_windows; i++) + mutex_lock(&ext->win[i].lock); + mutex_lock(&ext->cursor.lock); + + ext->enabled = en; + + mutex_unlock(&ext->cursor.lock); + for (i = ext->dc->n_windows - 1; i >= 0 ; i--) + mutex_unlock(&ext->win[i].lock); +} + +void tegra_dc_ext_enable(struct tegra_dc_ext *ext) +{ + set_enable(ext, true); +} + +void tegra_dc_ext_disable(struct tegra_dc_ext *ext) +{ + int i; + set_enable(ext, false); + + /* + * Flush the flip queue -- note that this must be called with dc->lock + * unlocked or else it will hang. + */ + for (i = 0; i < ext->dc->n_windows; i++) { + struct tegra_dc_ext_win *win = &ext->win[i]; + + flush_workqueue(win->flip_wq); + } +} + +int tegra_dc_ext_check_windowattr(struct tegra_dc_ext *ext, + struct tegra_dc_win *win) +{ + long *addr; + struct tegra_dc *dc = ext->dc; + + /* Check the window format */ + addr = tegra_dc_parse_feature(dc, win->idx, GET_WIN_FORMATS); + if (!test_bit(win->fmt, addr)) { + dev_err(&dc->ndev->dev, "Color format of window %d is" + " invalid.\n", win->idx); + goto fail; + } + + /* Check window size */ + addr = tegra_dc_parse_feature(dc, win->idx, GET_WIN_SIZE); + if (CHECK_SIZE(win->out_w, addr[MIN_WIDTH], addr[MAX_WIDTH]) || + CHECK_SIZE(win->out_h, addr[MIN_HEIGHT], addr[MAX_HEIGHT])) { + dev_err(&dc->ndev->dev, "Size of window %d is" + " invalid.\n", win->idx); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +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) +{ + int err = 0; + struct tegra_dc_ext_win *ext_win = &ext->win[win->idx]; +#ifndef CONFIG_ANDROID + s64 timestamp_ns; +#endif /* !CONFIG_ANDROID */ + + if (flip_win->handle[TEGRA_DC_Y] == NULL) { + win->flags = 0; + memset(ext_win->cur_handle, 0, sizeof(ext_win->cur_handle)); + 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; + if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_TILED) + win->flags |= TEGRA_WIN_FLAG_TILED; + if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_H) + win->flags |= TEGRA_WIN_FLAG_INVERT_H; + if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_INVERT_V) + win->flags |= TEGRA_WIN_FLAG_INVERT_V; + if (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_GLOBAL_ALPHA) + win->global_alpha = flip_win->attr.global_alpha; + else + win->global_alpha = 255; + win->fmt = flip_win->attr.pixformat; + win->x.full = flip_win->attr.x; + win->y.full = flip_win->attr.y; + win->w.full = flip_win->attr.w; + win->h.full = 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; + memcpy(ext_win->cur_handle, flip_win->handle, + sizeof(ext_win->cur_handle)); + + /* XXX verify that this won't read outside of the surface */ + win->phys_addr = flip_win->phys_addr + flip_win->attr.offset; + + win->phys_addr_u = flip_win->handle[TEGRA_DC_U] ? + flip_win->phys_addr_u : flip_win->phys_addr; + win->phys_addr_u += flip_win->attr.offset_u; + + win->phys_addr_v = flip_win->handle[TEGRA_DC_V] ? + flip_win->phys_addr_v : flip_win->phys_addr; + win->phys_addr_v += flip_win->attr.offset_v; + + win->stride = flip_win->attr.stride; + win->stride_uv = flip_win->attr.stride_uv; + + err = tegra_dc_ext_check_windowattr(ext, win); + if (err < 0) + dev_err(&ext->dc->ndev->dev, + "Window atrributes are invalid.\n"); + + if ((s32)flip_win->attr.pre_syncpt_id >= 0) { + nvhost_syncpt_wait_timeout( + &nvhost_get_host(ext->dc->ndev)->syncpt, + flip_win->attr.pre_syncpt_id, + flip_win->attr.pre_syncpt_val, + msecs_to_jiffies(500), NULL); + } + +#ifndef CONFIG_ANDROID +#ifndef CONFIG_TEGRA_SIMULATION_PLATFORM + timestamp_ns = timespec_to_ns(&flip_win->attr.timestamp); + + if (timestamp_ns) { + /* XXX: Should timestamping be overridden by "no_vsync" flag */ + tegra_dc_config_frame_end_intr(win->dc, true); + trace_printk("%s:Before timestamp wait\n", win->dc->ndev->name); + err = wait_event_interruptible(win->dc->timestamp_wq, + tegra_dc_is_within_n_vsync(win->dc, timestamp_ns)); + trace_printk("%s:After timestamp wait\n", win->dc->ndev->name); + tegra_dc_config_frame_end_intr(win->dc, false); + } +#endif + return err; +#else /* !CONFIG_ANDROID */ + return 0; +#endif /* !CONFIG_ANDROID */ +} + +static int (*flip_callback)(void); +static spinlock_t flip_callback_lock; +static bool init_tegra_dc_flip_callback_called; + +static int __init init_tegra_dc_flip_callback(void) +{ + spin_lock_init(&flip_callback_lock); + init_tegra_dc_flip_callback_called = true; + return 0; +} + +pure_initcall(init_tegra_dc_flip_callback); + +int tegra_dc_set_flip_callback(int (*callback)(void)) +{ + WARN_ON(!init_tegra_dc_flip_callback_called); + + spin_lock(&flip_callback_lock); + flip_callback = callback; + spin_unlock(&flip_callback_lock); + + return 0; +} +EXPORT_SYMBOL(tegra_dc_set_flip_callback); + +int tegra_dc_unset_flip_callback() +{ + spin_lock(&flip_callback_lock); + flip_callback = NULL; + spin_unlock(&flip_callback_lock); + + return 0; +} +EXPORT_SYMBOL(tegra_dc_unset_flip_callback); + +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 * + TEGRA_DC_NUM_PLANES]; + struct nvmap_handle_ref *old_handle; + int i, nr_unpin = 0, nr_win = 0; + bool skip_flip = false; + + 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; +#ifndef CONFIG_ANDROID + int j = 0; + struct tegra_dc_ext_flip_data *temp = NULL; + s64 head_timestamp = 0; +#endif /* !CONFIG_ANDROID */ + + if (index < 0) + continue; + + win = tegra_dc_get_window(ext->dc, index); + ext_win = &ext->win[index]; + + if (!(atomic_dec_and_test(&ext_win->nr_pending_flips)) && + (flip_win->attr.flags & TEGRA_DC_EXT_FLIP_FLAG_CURSOR)) + skip_flip = true; + +#ifndef CONFIG_ANDROID + mutex_lock(&ext_win->queue_lock); + list_for_each_entry(temp, &ext_win->timestamp_queue, + timestamp_node) { + if (j == 0) { + if (unlikely(temp != data)) + dev_err(&win->dc->ndev->dev, + "work queue did NOT dequeue head!!!"); + else + head_timestamp = + timespec_to_ns(&flip_win->attr.timestamp); + } else { + s64 timestamp = + timespec_to_ns(&temp->win[i].attr.timestamp); + + skip_flip = !tegra_dc_does_vsync_separate(ext->dc, + timestamp, head_timestamp); + /* Look ahead only one flip */ + break; + } + j++; + } + if (!list_empty(&ext_win->timestamp_queue)) + list_del(&data->timestamp_node); + mutex_unlock(&ext_win->queue_lock); +#endif /* !CONFIG_ANDROID */ + + if (win->flags & TEGRA_WIN_FLAG_ENABLED) { + int j; + for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) { + if (skip_flip) + old_handle = flip_win->handle[j]; + else + old_handle = ext_win->cur_handle[j]; + + if (!old_handle) + continue; + + unpin_handles[nr_unpin++] = old_handle; + } + } + + if (!skip_flip) + tegra_dc_ext_set_windowattr(ext, win, &data->win[i]); + + wins[nr_win++] = win; + } + + if (!skip_flip) { + tegra_dc_update_windows(wins, nr_win); + /* TODO: implement swapinterval here */ + tegra_dc_sync_windows(wins, nr_win); + if (!tegra_dc_has_multiple_dc()) { + spin_lock(&flip_callback_lock); + if (flip_callback) + flip_callback(); + spin_unlock(&flip_callback_lock); + } +#ifdef CONFIG_ANDROID + } +#endif /* CONFIG_ANDROID */ + + 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; + + if (index < 0) + continue; + + tegra_dc_incr_syncpt_min(ext->dc, index, + flip_win->syncpt_max); + } +#ifndef CONFIG_ANDROID + } +#endif /* !CONFIG_ANDROID */ + + /* 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 lock_windows_for_flip(struct tegra_dc_ext_user *user, + struct tegra_dc_ext_flip *args) +{ + struct tegra_dc_ext *ext = user->ext; + u8 idx_mask = 0; + int i; + + for (i = 0; i < DC_N_WINDOWS; i++) { + int index = args->win[i].index; + + if (index < 0) + continue; + + idx_mask |= BIT(index); + } + + for (i = 0; i < DC_N_WINDOWS; i++) { + struct tegra_dc_ext_win *win; + + if (!(idx_mask & BIT(i))) + continue; + + win = &ext->win[i]; + + mutex_lock(&win->lock); + + if (win->user != user) + goto fail_unlock; + } + + return 0; + +fail_unlock: + do { + if (!(idx_mask & BIT(i))) + continue; + + mutex_unlock(&ext->win[i].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; + u8 idx_mask = 0; + int i; + + for (i = 0; i < DC_N_WINDOWS; i++) { + int index = args->win[i].index; + + if (index < 0) + continue; + + idx_mask |= BIT(index); + } + + for (i = DC_N_WINDOWS - 1; i >= 0; i--) { + if (!(idx_mask & BIT(i))) + continue; + + mutex_unlock(&ext->win[i].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; + int work_index = -1; + int i, ret = 0; +#ifndef CONFIG_ANDROID + bool has_timestamp = false; +#endif /* !CONFIG_ANDROID */ + +#ifdef CONFIG_ANDROID + int index_check[DC_N_WINDOWS] = {0, }; + int zero_index_id = 0; +#endif + + if (!user->nvmap) + 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; + +#ifdef CONFIG_ANDROID + for (i = 0; i < DC_N_WINDOWS; i++) { + index_check[i] = args->win[i].index; + if (index_check[i] == 0) + zero_index_id = i; + } + + if (index_check[DC_N_WINDOWS - 1] != 0) { + struct tegra_dc_ext_flip_windowattr win_temp; + win_temp = args->win[DC_N_WINDOWS - 1]; + args->win[DC_N_WINDOWS - 1] = args->win[zero_index_id]; + args->win[zero_index_id] = win_temp; + } +#endif + + 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)); +#ifndef CONFIG_ANDROID + if (timespec_to_ns(&flip_win->attr.timestamp)) + has_timestamp = true; +#endif /* !CONFIG_ANDROID */ + + if (index < 0) + continue; + + ret = tegra_dc_ext_pin_window(user, flip_win->attr.buff_id, + &flip_win->handle[TEGRA_DC_Y], + &flip_win->phys_addr); + if (ret) + goto fail_pin; + + if (flip_win->attr.buff_id_u) { + ret = tegra_dc_ext_pin_window(user, + flip_win->attr.buff_id_u, + &flip_win->handle[TEGRA_DC_U], + &flip_win->phys_addr_u); + if (ret) + goto fail_pin; + } else { + flip_win->handle[TEGRA_DC_U] = NULL; + flip_win->phys_addr_u = 0; + } + + if (flip_win->attr.buff_id_v) { + ret = tegra_dc_ext_pin_window(user, + flip_win->attr.buff_id_v, + &flip_win->handle[TEGRA_DC_V], + &flip_win->phys_addr_v); + if (ret) + goto fail_pin; + } else { + flip_win->handle[TEGRA_DC_V] = NULL; + flip_win->phys_addr_v = 0; + } + } + + ret = lock_windows_for_flip(user, args); + if (ret) + goto fail_pin; + + if (!ext->enabled) { + ret = -ENXIO; + goto unlock; + } + + for (i = 0; i < DC_N_WINDOWS; i++) { + u32 syncpt_max; + int index = args->win[i].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]; + + syncpt_max = tegra_dc_incr_syncpt_max(ext->dc, index); + + data->win[i].syncpt_max = syncpt_max; + + /* + * Any of these windows' syncpoints should be equivalent for + * the client, so we just send back an arbitrary one of them + */ + args->post_syncpt_val = syncpt_max; + args->post_syncpt_id = tegra_dc_get_syncpt_id(ext->dc, index); + work_index = index; + + atomic_inc(&ext->win[work_index].nr_pending_flips); + } + if (work_index < 0) { + ret = -EINVAL; + goto unlock; + } +#ifndef CONFIG_ANDROID + if (has_timestamp) { + mutex_lock(&ext->win[work_index].queue_lock); + list_add_tail(&data->timestamp_node, &ext->win[work_index].timestamp_queue); + mutex_unlock(&ext->win[work_index].queue_lock); + } +#endif /* !CONFIG_ANDROID */ + queue_work(ext->win[work_index].flip_wq, &data->work); + + unlock_windows_for_flip(user, args); + + return 0; + +unlock: + unlock_windows_for_flip(user, args); + +fail_pin: + for (i = 0; i < DC_N_WINDOWS; i++) { + int j; + for (j = 0; j < TEGRA_DC_NUM_PLANES; j++) { + if (!data->win[i].handle[j]) + continue; + + nvmap_unpin(ext->nvmap, data->win[i].handle[j]); + nvmap_free(ext->nvmap, data->win[i].handle[j]); + } + } + kfree(data); + + return ret; +} + +static int tegra_dc_ext_set_csc(struct tegra_dc_ext_user *user, + struct tegra_dc_ext_csc *new_csc) +{ + unsigned int index = new_csc->win_index; + struct tegra_dc *dc = user->ext->dc; + struct tegra_dc_ext_win *ext_win; + struct tegra_dc_csc *csc; + + if (index >= DC_N_WINDOWS) + return -EINVAL; + + ext_win = &user->ext->win[index]; + csc = &dc->windows[index].csc; + + mutex_lock(&ext_win->lock); + + if (ext_win->user != user) { + mutex_unlock(&ext_win->lock); + return -EACCES; + } + + csc->yof = new_csc->yof; + csc->kyrgb = new_csc->kyrgb; + csc->kur = new_csc->kur; + csc->kvr = new_csc->kvr; + csc->kug = new_csc->kug; + csc->kvg = new_csc->kvg; + csc->kub = new_csc->kub; + csc->kvb = new_csc->kvb; + + tegra_dc_update_csc(dc, index); + + mutex_unlock(&ext_win->lock); + + return 0; +} + +static int set_lut_channel(u16 *channel_from_user, + u8 *channel_to, + u32 start, + u32 len) +{ + int i; + u16 lut16bpp[256]; + + if (channel_from_user) { + if (copy_from_user(lut16bpp, channel_from_user, len<<1)) + return 1; + + for (i = 0; i < len; i++) + channel_to[start+i] = lut16bpp[i]>>8; + } else { + for (i = 0; i < len; i++) + channel_to[start+i] = start+i; + } + + return 0; +} + +static int tegra_dc_ext_set_lut(struct tegra_dc_ext_user *user, + struct tegra_dc_ext_lut *new_lut) +{ + int err; + unsigned int index = new_lut->win_index; + u32 start = new_lut->start; + u32 len = new_lut->len; + + struct tegra_dc *dc = user->ext->dc; + struct tegra_dc_ext_win *ext_win; + struct tegra_dc_lut *lut; + + if (index >= DC_N_WINDOWS) + return -EINVAL; + + if ((start >= 256) || (len > 256) || ((start + len) > 256)) + return -EINVAL; + + ext_win = &user->ext->win[index]; + lut = &dc->windows[index].lut; + + mutex_lock(&ext_win->lock); + + if (ext_win->user != user) { + mutex_unlock(&ext_win->lock); + return -EACCES; + } + + err = set_lut_channel(new_lut->r, lut->r, start, len) | + set_lut_channel(new_lut->g, lut->g, start, len) | + set_lut_channel(new_lut->b, lut->b, start, len); + + if (err) { + mutex_unlock(&ext_win->lock); + return -EFAULT; + } + + tegra_dc_update_lut(dc, index, + new_lut->flags & TEGRA_DC_EXT_LUT_FLAGS_FBOVERRIDE); + + mutex_unlock(&ext_win->lock); + + return 0; +} + +static u32 tegra_dc_ext_get_vblank_syncpt(struct tegra_dc_ext_user *user) +{ + struct tegra_dc *dc = user->ext->dc; + + return dc->vblank_syncpt; +} + +static int tegra_dc_ext_get_status(struct tegra_dc_ext_user *user, + struct tegra_dc_ext_status *status) +{ + struct tegra_dc *dc = user->ext->dc; + + memset(status, 0, sizeof(*status)); + + if (dc->enabled) + status->flags |= TEGRA_DC_EXT_FLAGS_ENABLED; + + return 0; +} + +static int tegra_dc_ext_get_feature(struct tegra_dc_ext_user *user, + struct tegra_dc_ext_feature *feature) +{ + struct tegra_dc *dc = user->ext->dc; + struct tegra_dc_feature *table = dc->feature; + + if (dc->enabled && feature->entries) { + feature->length = table->num_entries; + memcpy(feature->entries, table->entries, table->num_entries * + sizeof(struct tegra_dc_feature_entry)); + } + + return 0; +} + +static long tegra_dc_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + void __user *user_arg = (void __user *)arg; + struct tegra_dc_ext_user *user = filp->private_data; + + switch (cmd) { + case TEGRA_DC_EXT_SET_NVMAP_FD: + return tegra_dc_ext_set_nvmap_fd(user, arg); + + case TEGRA_DC_EXT_GET_WINDOW: + return tegra_dc_ext_get_window(user, arg); + case TEGRA_DC_EXT_PUT_WINDOW: + return tegra_dc_ext_put_window(user, arg); + + case TEGRA_DC_EXT_FLIP: + { + struct tegra_dc_ext_flip args; + int ret; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + ret = tegra_dc_ext_flip(user, &args); + + if (copy_to_user(user_arg, &args, sizeof(args))) + return -EFAULT; + + return ret; + } + + case TEGRA_DC_EXT_GET_CURSOR: + return tegra_dc_ext_get_cursor(user); + case TEGRA_DC_EXT_PUT_CURSOR: + return tegra_dc_ext_put_cursor(user); + case TEGRA_DC_EXT_SET_CURSOR_IMAGE: + { + struct tegra_dc_ext_cursor_image args; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + return tegra_dc_ext_set_cursor_image(user, &args); + } + case TEGRA_DC_EXT_SET_CURSOR: + { + struct tegra_dc_ext_cursor args; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + return tegra_dc_ext_set_cursor(user, &args); + } + + case TEGRA_DC_EXT_SET_CSC: + { + struct tegra_dc_ext_csc args; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + return tegra_dc_ext_set_csc(user, &args); + } + + case TEGRA_DC_EXT_GET_VBLANK_SYNCPT: + { + u32 syncpt = tegra_dc_ext_get_vblank_syncpt(user); + + if (copy_to_user(user_arg, &syncpt, sizeof(syncpt))) + return -EFAULT; + + return 0; + } + + case TEGRA_DC_EXT_GET_STATUS: + { + struct tegra_dc_ext_status args; + int ret; + + ret = tegra_dc_ext_get_status(user, &args); + + if (copy_to_user(user_arg, &args, sizeof(args))) + return -EFAULT; + + return ret; + } + + case TEGRA_DC_EXT_SET_LUT: + { + struct tegra_dc_ext_lut args; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + return tegra_dc_ext_set_lut(user, &args); + } + + case TEGRA_DC_EXT_GET_FEATURES: + { + struct tegra_dc_ext_feature args; + int ret; + + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + ret = tegra_dc_ext_get_feature(user, &args); + + if (copy_to_user(user_arg, &args, sizeof(args))) + return -EFAULT; + + return ret; + } + + default: + return -EINVAL; + } +} + +static int tegra_dc_open(struct inode *inode, struct file *filp) +{ + struct tegra_dc_ext_user *user; + struct tegra_dc_ext *ext; + + user = kzalloc(sizeof(*user), GFP_KERNEL); + if (!user) + return -ENOMEM; + + ext = container_of(inode->i_cdev, struct tegra_dc_ext, cdev); + user->ext = ext; + + filp->private_data = user; + + return 0; +} + +static int tegra_dc_release(struct inode *inode, struct file *filp) +{ + struct tegra_dc_ext_user *user = filp->private_data; + struct tegra_dc_ext *ext = user->ext; + unsigned int i; + + for (i = 0; i < DC_N_WINDOWS; i++) { + if (ext->win[i].user == user) + tegra_dc_ext_put_window(user, i); + } + if (ext->cursor.user == user) + tegra_dc_ext_put_cursor(user); + + if (user->nvmap) + nvmap_client_put(user->nvmap); + + kfree(user); + + return 0; +} + +static int tegra_dc_ext_setup_windows(struct tegra_dc_ext *ext) +{ + int i, ret; + + for (i = 0; i < ext->dc->n_windows; i++) { + struct tegra_dc_ext_win *win = &ext->win[i]; + char name[32]; + + win->ext = ext; + win->idx = i; + + snprintf(name, sizeof(name), "tegradc.%d/%c", + ext->dc->ndev->id, 'a' + i); + win->flip_wq = create_singlethread_workqueue(name); + if (!win->flip_wq) { + ret = -ENOMEM; + goto cleanup; + } + + mutex_init(&win->lock); +#ifndef CONFIG_ANDROID + mutex_init(&win->queue_lock); + INIT_LIST_HEAD(&win->timestamp_queue); +#endif /* !CONFIG_ANDROID */ + } + + return 0; + +cleanup: + while (i--) { + struct tegra_dc_ext_win *win = &ext->win[i]; + destroy_workqueue(win->flip_wq); + } + + return ret; +} + +static const struct file_operations tegra_dc_devops = { + .owner = THIS_MODULE, + .open = tegra_dc_open, + .release = tegra_dc_release, + .unlocked_ioctl = tegra_dc_ioctl, +}; + +struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev, + struct tegra_dc *dc) +{ + int ret; + struct tegra_dc_ext *ext; + int devno; + + ext = kzalloc(sizeof(*ext), GFP_KERNEL); + if (!ext) + return ERR_PTR(-ENOMEM); + + BUG_ON(!tegra_dc_ext_devno); + devno = tegra_dc_ext_devno + head_count + 1; + + cdev_init(&ext->cdev, &tegra_dc_devops); + ext->cdev.owner = THIS_MODULE; + ret = cdev_add(&ext->cdev, devno, 1); + if (ret) { + dev_err(&ndev->dev, "Failed to create character device\n"); + goto cleanup_alloc; + } + + ext->dev = device_create(tegra_dc_ext_class, + &ndev->dev, + devno, + NULL, + "tegra_dc_%d", + ndev->id); + + if (IS_ERR(ext->dev)) { + ret = PTR_ERR(ext->dev); + goto cleanup_cdev; + } + + ext->dc = dc; + + ext->nvmap = nvmap_create_client(nvmap_dev, "tegra_dc_ext"); + if (!ext->nvmap) { + ret = -ENOMEM; + goto cleanup_device; + } + + ret = tegra_dc_ext_setup_windows(ext); + if (ret) + goto cleanup_nvmap; + + mutex_init(&ext->cursor.lock); + + head_count++; + + return ext; + +cleanup_nvmap: + nvmap_client_put(ext->nvmap); + +cleanup_device: + device_del(ext->dev); + +cleanup_cdev: + cdev_del(&ext->cdev); + +cleanup_alloc: + kfree(ext); + + return ERR_PTR(ret); +} + +void tegra_dc_ext_unregister(struct tegra_dc_ext *ext) +{ + int i; + + for (i = 0; i < ext->dc->n_windows; i++) { + struct tegra_dc_ext_win *win = &ext->win[i]; + + flush_workqueue(win->flip_wq); + destroy_workqueue(win->flip_wq); + } + + nvmap_client_put(ext->nvmap); + device_del(ext->dev); + cdev_del(&ext->cdev); + + kfree(ext); + + head_count--; +} + +int __init tegra_dc_ext_module_init(void) +{ + int ret; + + tegra_dc_ext_class = class_create(THIS_MODULE, "tegra_dc_ext"); + if (!tegra_dc_ext_class) { + printk(KERN_ERR "tegra_dc_ext: failed to create class\n"); + return -ENOMEM; + } + + /* Reserve one character device per head, plus the control device */ + ret = alloc_chrdev_region(&tegra_dc_ext_devno, + 0, TEGRA_MAX_DC + 1, + "tegra_dc_ext"); + if (ret) + goto cleanup_class; + + ret = tegra_dc_ext_control_init(); + if (ret) + goto cleanup_region; + + return 0; + +cleanup_region: + unregister_chrdev_region(tegra_dc_ext_devno, TEGRA_MAX_DC); + +cleanup_class: + class_destroy(tegra_dc_ext_class); + + return ret; +} + +void __exit tegra_dc_ext_module_exit(void) +{ + unregister_chrdev_region(tegra_dc_ext_devno, TEGRA_MAX_DC); + class_destroy(tegra_dc_ext_class); +} |