summaryrefslogtreecommitdiff
path: root/drivers/video/tegra/dc/ext
diff options
context:
space:
mode:
authorRobert Morell <rmorell@nvidia.com>2011-03-03 15:02:53 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:48:12 -0800
commit384b754c2f3c70c15da177c8b11a0a51f23c76fc (patch)
tree50765782b4804e87d9bb587479aceff7965bc652 /drivers/video/tegra/dc/ext
parent63a79e26218daabc625acf3a21224be2ad6624ed (diff)
video: tegra: Add control device to dc extension driver
This device exposes control over everything that's not specific to one of T20's two display controllers. It supports: - output devices - event delivery - hotplug events bug 818525 Original-Change-Id: I3a46f1dddc483b08ed3ee91a4f9c64111c1fd7eb Signed-off-by: Robert Morell <rmorell@nvidia.com> Reviewed-on: http://git-master/r/40520 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com> Rebase-Id: R9e49fe41f3327b797ec65c3729f4f94edbb45307
Diffstat (limited to 'drivers/video/tegra/dc/ext')
-rw-r--r--drivers/video/tegra/dc/ext/Makefile2
-rw-r--r--drivers/video/tegra/dc/ext/control.c220
-rw-r--r--drivers/video/tegra/dc/ext/dev.c39
-rw-r--r--drivers/video/tegra/dc/ext/events.c183
-rw-r--r--drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h51
5 files changed, 488 insertions, 7 deletions
diff --git a/drivers/video/tegra/dc/ext/Makefile b/drivers/video/tegra/dc/ext/Makefile
index 1a202f95f391..19860ab5db11 100644
--- a/drivers/video/tegra/dc/ext/Makefile
+++ b/drivers/video/tegra/dc/ext/Makefile
@@ -1,3 +1,5 @@
obj-y += dev.o
obj-y += util.o
obj-y += cursor.o
+obj-y += events.o
+obj-y += control.o
diff --git a/drivers/video/tegra/dc/ext/control.c b/drivers/video/tegra/dc/ext/control.c
new file mode 100644
index 000000000000..6cc2f9a7d54a
--- /dev/null
+++ b/drivers/video/tegra/dc/ext/control.c
@@ -0,0 +1,220 @@
+/*
+ * drivers/video/tegra/dc/ext/control.c
+ *
+ * Copyright (C) 2011, NVIDIA Corporation
+ *
+ * Author: Robert Morell <rmorell@nvidia.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/device.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#include "tegra_dc_ext_priv.h"
+
+static struct tegra_dc_ext_control g_control;
+
+int tegra_dc_ext_process_hotplug(int output)
+{
+ return tegra_dc_ext_queue_hotplug(&g_control, output);
+}
+
+static int
+get_output_properties(struct tegra_dc_ext_control_output_properties *properties)
+{
+ /* TODO: this should be more dynamic */
+ if (properties->handle > 2)
+ return -EINVAL;
+
+ switch (properties->handle) {
+ case 0:
+ properties->type = TEGRA_DC_EXT_LVDS;
+ properties->connected = 1;
+ break;
+ case 1:
+ properties->type = TEGRA_DC_EXT_HDMI;
+ properties->connected = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ properties->associated_head = properties->handle;
+
+ return 0;
+}
+
+static int get_output_edid(struct tegra_dc_ext_control_output_edid *edid)
+{
+ /* XXX implement me */
+ return -ENOTSUPP;
+}
+
+static int set_event_mask(struct tegra_dc_ext_control_user *user, u32 mask)
+{
+ struct list_head *list, *tmp;
+
+ if (mask & ~TEGRA_DC_EXT_EVENT_MASK_ALL)
+ return -EINVAL;
+
+ mutex_lock(&user->lock);
+
+ user->event_mask = mask;
+
+ list_for_each_safe(list, tmp, &user->event_list) {
+ struct tegra_dc_ext_event_list *ev_list;
+ ev_list = list_entry(list, struct tegra_dc_ext_event_list,
+ list);
+ if (!(mask & ev_list->event.type)) {
+ list_del(list);
+ kfree(ev_list);
+ }
+ }
+ mutex_unlock(&user->lock);
+
+ return 0;
+}
+
+static long tegra_dc_ext_control_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *user_arg = (void __user *)arg;
+ struct tegra_dc_ext_control_user *user = filp->private_data;
+
+ switch (cmd) {
+ case TEGRA_DC_EXT_CONTROL_GET_NUM_OUTPUTS:
+ {
+ u32 num = tegra_dc_ext_get_num_outputs();
+
+ if (copy_to_user(user_arg, &num, sizeof(num)))
+ return -EFAULT;
+
+ return 0;
+ }
+ case TEGRA_DC_EXT_CONTROL_GET_OUTPUT_PROPERTIES:
+ {
+ struct tegra_dc_ext_control_output_properties args;
+ int ret;
+
+ if (copy_from_user(&args, user_arg, sizeof(args)))
+ return -EFAULT;
+
+ ret = get_output_properties(&args);
+
+ if (copy_to_user(user_arg, &args, sizeof(args)))
+ return -EFAULT;
+
+ return ret;
+ }
+ case TEGRA_DC_EXT_CONTROL_GET_OUTPUT_EDID:
+ {
+ struct tegra_dc_ext_control_output_edid args;
+ int ret;
+
+ if (copy_from_user(&args, user_arg, sizeof(args)))
+ return -EFAULT;
+
+ ret = get_output_edid(&args);
+
+ if (copy_to_user(user_arg, &args, sizeof(args)))
+ return -EFAULT;
+
+ return ret;
+ }
+ case TEGRA_DC_EXT_CONTROL_SET_EVENT_MASK:
+ return set_event_mask(user, (u32) arg);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int tegra_dc_ext_control_open(struct inode *inode, struct file *filp)
+{
+ struct tegra_dc_ext_control_user *user;
+ struct tegra_dc_ext_control *control;
+
+ user = kzalloc(sizeof(*user), GFP_KERNEL);
+ if (!user)
+ return -ENOMEM;
+
+ control = container_of(inode->i_cdev, struct tegra_dc_ext_control,
+ cdev);
+ user->control = control;;
+
+ INIT_LIST_HEAD(&user->event_list);
+ mutex_init(&user->lock);
+
+ filp->private_data = user;
+
+ mutex_lock(&control->lock);
+ list_add(&user->list, &control->users);
+ mutex_unlock(&control->lock);
+
+ return 0;
+}
+
+static int tegra_dc_ext_control_release(struct inode *inode, struct file *filp)
+{
+ struct tegra_dc_ext_control_user *user = filp->private_data;
+ struct tegra_dc_ext_control *control = user->control;
+
+ /* This will free any pending events for this user */
+ set_event_mask(user, 0);
+
+ mutex_lock(&control->lock);
+ list_del(&user->list);
+ mutex_unlock(&control->lock);
+
+ kfree(user);
+
+ return 0;
+}
+
+static const struct file_operations tegra_dc_ext_event_devops = {
+ .owner = THIS_MODULE,
+ .open = tegra_dc_ext_control_open,
+ .release = tegra_dc_ext_control_release,
+ .read = tegra_dc_ext_event_read,
+ .unlocked_ioctl = tegra_dc_ext_control_ioctl,
+};
+
+int tegra_dc_ext_control_init(void)
+{
+ struct tegra_dc_ext_control *control = &g_control;
+ int ret;
+
+ cdev_init(&control->cdev, &tegra_dc_ext_event_devops);
+ control->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&control->cdev, tegra_dc_ext_devno, 1);
+ if (ret)
+ return ret;
+
+ control->dev = device_create(tegra_dc_ext_class,
+ NULL,
+ tegra_dc_ext_devno,
+ NULL,
+ "tegra_dc_ctrl");
+ if (IS_ERR(control->dev)) {
+ ret = PTR_ERR(control->dev);
+ cdev_del(&control->cdev);
+ }
+
+ mutex_init(&control->lock);
+
+ INIT_LIST_HEAD(&control->users);
+
+ return ret;
+}
diff --git a/drivers/video/tegra/dc/ext/dev.c b/drivers/video/tegra/dc/ext/dev.c
index 7e077737f910..05569ea74d0a 100644
--- a/drivers/video/tegra/dc/ext/dev.c
+++ b/drivers/video/tegra/dc/ext/dev.c
@@ -38,8 +38,9 @@
#include "../../nvmap/nvmap.h"
#include "tegra_dc_ext_priv.h"
-static int tegra_dc_ext_devno;
-static struct class *tegra_dc_ext_class;
+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;
@@ -55,6 +56,12 @@ struct tegra_dc_ext_flip_data {
struct tegra_dc_ext_flip_win win[DC_N_WINDOWS];
};
+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)
{
@@ -574,15 +581,18 @@ struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
{
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, tegra_dc_ext_devno, 1);
+ ret = cdev_add(&ext->cdev, devno, 1);
if (ret) {
dev_err(&ndev->dev, "Failed to create character device\n");
goto cleanup_alloc;
@@ -590,7 +600,7 @@ struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
ext->dev = device_create(tegra_dc_ext_class,
&ndev->dev,
- tegra_dc_ext_devno,
+ devno,
NULL,
"tegra_dc_%d",
ndev->id);
@@ -614,7 +624,7 @@ struct tegra_dc_ext *tegra_dc_ext_register(struct nvhost_device *ndev,
mutex_init(&ext->cursor.lock);
- tegra_dc_ext_devno++;
+ head_count++;
return ext;
@@ -649,6 +659,8 @@ void tegra_dc_ext_unregister(struct tegra_dc_ext *ext)
cdev_del(&ext->cdev);
kfree(ext);
+
+ head_count--;
}
int __init tegra_dc_ext_module_init(void)
@@ -661,11 +673,24 @@ int __init tegra_dc_ext_module_init(void)
return -ENOMEM;
}
+ /* Reserve one character device per head, plus the control device */
ret = alloc_chrdev_region(&tegra_dc_ext_devno,
- 0, TEGRA_MAX_DC,
+ 0, TEGRA_MAX_DC + 1,
"tegra_dc_ext");
if (ret)
- class_destroy(tegra_dc_ext_class);
+ 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;
}
diff --git a/drivers/video/tegra/dc/ext/events.c b/drivers/video/tegra/dc/ext/events.c
new file mode 100644
index 000000000000..226571e98fd7
--- /dev/null
+++ b/drivers/video/tegra/dc/ext/events.c
@@ -0,0 +1,183 @@
+/*
+ * drivers/video/tegra/dc/ext/events.c
+ *
+ * Copyright (C) 2011, NVIDIA Corporation
+ *
+ * Author: Robert Morell <rmorell@nvidia.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/err.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "tegra_dc_ext_priv.h"
+
+static DECLARE_WAIT_QUEUE_HEAD(event_wait);
+
+static int get_next_event(struct tegra_dc_ext_control_user *user,
+ struct tegra_dc_ext_event_list *event,
+ bool block)
+{
+ struct list_head *list = &user->event_list;
+ struct tegra_dc_ext_event_list *next_event;
+ int ret;
+
+ if (block) {
+ ret = wait_event_interruptible(event_wait,
+ atomic_read(&user->num_events));
+
+ if (unlikely(ret)) {
+ if (ret == -ERESTARTSYS)
+ return -EAGAIN;
+ return ret;
+ }
+ } else {
+ if (!atomic_read(&user->num_events))
+ return 0;
+ }
+
+ mutex_lock(&user->lock);
+
+ BUG_ON(list_empty(list));
+ next_event = list_first_entry(list, struct tegra_dc_ext_event_list,
+ list);
+ *event = *next_event;
+ list_del(&next_event->list);
+ kfree(next_event);
+
+ atomic_dec(&user->num_events);
+
+ mutex_unlock(&user->lock);
+
+ return 1;
+}
+
+ssize_t tegra_dc_ext_event_read(struct file *filp, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct tegra_dc_ext_control_user *user = filp->private_data;
+ struct tegra_dc_ext_event_list event_elem;
+ struct tegra_dc_ext_event *event = &event_elem.event;
+ ssize_t retval = 0, to_copy, event_size, pending;
+ loff_t previously_copied = 0;
+ char *to_copy_ptr;
+
+ if (size == 0)
+ return 0;
+
+ if (user->partial_copy) {
+ /*
+ * We didn't transfer the entire event last time, need to
+ * finish it up
+ */
+ event_elem = user->event_to_copy;
+ previously_copied = user->partial_copy;
+ } else {
+ /* Get the next event, if any */
+ pending = get_next_event(user, &event_elem,
+ !(filp->f_flags & O_NONBLOCK));
+ if (pending <= 0)
+ return pending;
+ }
+
+ /* Write the event to the user */
+ event_size = sizeof(*event) + event->data_size;
+ BUG_ON(event_size <= previously_copied);
+ event_size -= previously_copied;
+
+ to_copy_ptr = (char *)event + previously_copied;
+ to_copy = min_t(ssize_t, size, event_size);
+ if (copy_to_user(buf, to_copy_ptr, to_copy)) {
+ retval = -EFAULT;
+ to_copy = 0;
+ }
+
+ /* Note that we currently only deliver one event at a time */
+
+ if (event_size > to_copy) {
+ /*
+ * We were only able to copy part of this event. Stash it for
+ * next time.
+ */
+ user->event_to_copy = event_elem;
+ user->partial_copy = previously_copied + to_copy;
+ } else {
+ user->partial_copy = 0;
+ }
+
+ return to_copy ? to_copy : retval;
+}
+
+static int tegra_dc_ext_queue_event(struct tegra_dc_ext_control *control,
+ struct tegra_dc_ext_event *event)
+{
+ struct list_head *cur;
+ int retval = 0;
+
+ mutex_lock(&control->lock);
+ list_for_each(cur, &control->users) {
+ struct tegra_dc_ext_control_user *user;
+ struct tegra_dc_ext_event_list *ev_list;
+
+ user = container_of(cur, struct tegra_dc_ext_control_user,
+ list);
+ mutex_lock(&user->lock);
+
+ if (!(user->event_mask & event->type)) {
+ mutex_unlock(&user->lock);
+ continue;
+ }
+
+ ev_list = kmalloc(sizeof(*ev_list), GFP_KERNEL);
+ if (!ev_list) {
+ retval = -ENOMEM;
+ mutex_unlock(&user->lock);
+ continue;
+ }
+
+ memcpy(&ev_list->event, event,
+ sizeof(*event) + event->data_size);
+
+ list_add_tail(&ev_list->list, &user->event_list);
+
+ atomic_inc(&user->num_events);
+
+ mutex_unlock(&user->lock);
+ }
+ mutex_unlock(&control->lock);
+
+ /* Is it worth it to track waiters with more granularity? */
+ wake_up(&event_wait);
+
+ return retval;
+}
+
+int tegra_dc_ext_queue_hotplug(struct tegra_dc_ext_control *control, int output)
+{
+ struct {
+ struct tegra_dc_ext_event event;
+ struct tegra_dc_ext_control_event_hotplug hotplug;
+ } __packed pack;
+
+ pack.event.type = TEGRA_DC_EXT_EVENT_HOTPLUG;
+ pack.event.data_size = sizeof(pack.hotplug);
+
+ pack.hotplug.handle = output;
+
+ tegra_dc_ext_queue_event(control, &pack.event);
+
+ return 0;
+}
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 251c072683c7..a1cd88325896 100644
--- a/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
+++ b/drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
@@ -20,6 +20,7 @@
#define __TEGRA_DC_EXT_PRIV_H
#include <linux/cdev.h>
+#include <linux/list.h>
#include <linux/mutex.h>
#include <mach/dc.h>
@@ -67,6 +68,47 @@ struct tegra_dc_ext {
bool enabled;
};
+#define TEGRA_DC_EXT_EVENT_MASK_ALL \
+ TEGRA_DC_EXT_EVENT_HOTPLUG
+
+#define TEGRA_DC_EXT_EVENT_MAX_SZ 8
+
+struct tegra_dc_ext_event_list {
+ struct tegra_dc_ext_event event;
+ /* The data field _must_ follow the event field. */
+ char data[TEGRA_DC_EXT_EVENT_MAX_SZ];
+
+ struct list_head list;
+};
+
+struct tegra_dc_ext_control_user {
+ struct tegra_dc_ext_control *control;
+
+ struct list_head event_list;
+ atomic_t num_events;
+
+ u32 event_mask;
+
+ struct tegra_dc_ext_event_list event_to_copy;
+ loff_t partial_copy;
+
+ struct mutex lock;
+
+ struct list_head list;
+};
+
+struct tegra_dc_ext_control {
+ struct cdev cdev;
+ struct device *dev;
+
+ struct list_head users;
+
+ struct mutex lock;
+};
+
+extern int tegra_dc_ext_devno;
+extern struct class *tegra_dc_ext_class;
+
extern int tegra_dc_ext_pin_window(struct tegra_dc_ext_user *user, u32 id,
struct nvmap_handle_ref **handle,
dma_addr_t *phys_addr);
@@ -78,4 +120,13 @@ extern int tegra_dc_ext_set_cursor_image(struct tegra_dc_ext_user *user,
extern int tegra_dc_ext_set_cursor(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor *);
+extern int tegra_dc_ext_control_init(void);
+
+extern int tegra_dc_ext_queue_hotplug(struct tegra_dc_ext_control *,
+ int output);
+extern ssize_t tegra_dc_ext_event_read(struct file *filp, char __user *buf,
+ size_t size, loff_t *ppos);
+
+extern int tegra_dc_ext_get_num_outputs(void);
+
#endif /* __TEGRA_DC_EXT_PRIV_H */