video: tegra: Add control device to dc extension driver
Robert Morell [Thu, 3 Mar 2011 23:02:53 +0000 (15:02 -0800)]
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

arch/arm/mach-tegra/include/mach/tegra_dc_ext.h
drivers/video/tegra/dc/ext/Makefile
drivers/video/tegra/dc/ext/control.c [new file with mode: 0644]
drivers/video/tegra/dc/ext/dev.c
drivers/video/tegra/dc/ext/events.c [new file with mode: 0644]
drivers/video/tegra/dc/ext/tegra_dc_ext_priv.h
drivers/video/tegra/dc/hdmi.c
include/video/tegra_dc_ext.h

index c942745..f94626f 100644 (file)
@@ -35,6 +35,8 @@ void tegra_dc_ext_unregister(struct tegra_dc_ext *dc_ext);
 void tegra_dc_ext_enable(struct tegra_dc_ext *dc_ext);
 void tegra_dc_ext_disable(struct tegra_dc_ext *dc_ext);
 
+int tegra_dc_ext_process_hotplug(int output);
+
 #else /* CONFIG_TEGRA_DC_EXTENSIONS */
 
 static inline
@@ -65,6 +67,11 @@ static inline
 void tegra_dc_ext_disable(struct tegra_dc_ext *dc_ext)
 {
 }
+static inline
+int tegra_dc_ext_process_hotplug(int output)
+{
+       return 0;
+}
 #endif /* CONFIG_TEGRA_DC_EXTENSIONS */
 
 #endif /* __MACH_TEGRA_DC_EXT_H */
index 1a202f9..19860ab 100644 (file)
@@ -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 (file)
index 0000000..6cc2f9a
--- /dev/null
@@ -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;
+}
index 7e07773..05569ea 100644 (file)
@@ -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 (file)
index 0000000..226571e
--- /dev/null
@@ -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;
+}
index 251c072..a1cd883 100644 (file)
@@ -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 */
index 164ecb2..bb912d9 100644 (file)
@@ -744,6 +744,9 @@ static bool tegra_dc_hdmi_detect(struct tegra_dc *dc)
 
        tegra_fb_update_monspecs(dc->fb, &specs, tegra_dc_hdmi_mode_filter);
        dev_info(&dc->ndev->dev, "display detected\n");
+
+       tegra_dc_ext_process_hotplug(dc->ndev->id);
+
        return true;
 
 fail:
@@ -764,6 +767,8 @@ static void tegra_dc_hdmi_detect_worker(struct work_struct *work)
        if (!tegra_dc_hdmi_detect(dc)) {
                tegra_dc_disable(dc);
                tegra_fb_update_monspecs(dc->fb, NULL, NULL);
+
+               tegra_dc_ext_process_hotplug(dc->ndev->id);
        }
 }
 
index f9935ef..6f43ee6 100644 (file)
@@ -25,6 +25,7 @@
 # include <linux/time.h>
 #else
 # include <time.h>
+# include <unistd.h>
 #endif
 
 #define TEGRA_DC_EXT_FMT_P1            0
@@ -145,4 +146,46 @@ struct tegra_dc_ext_cursor {
 #define TEGRA_DC_EXT_SET_CURSOR \
        _IOW('D', 0x07, struct tegra_dc_ext_cursor)
 
+
+enum tegra_dc_ext_control_output_type {
+       TEGRA_DC_EXT_DSI,
+       TEGRA_DC_EXT_LVDS,
+       TEGRA_DC_EXT_VGA,
+       TEGRA_DC_EXT_HDMI,
+       TEGRA_DC_EXT_DVI,
+};
+
+struct tegra_dc_ext_control_output_properties {
+       __u32 handle;
+       enum tegra_dc_ext_control_output_type type;
+       __u32 connected;
+       __s32 associated_head;
+};
+
+struct tegra_dc_ext_control_output_edid {
+       __u32 handle;
+       __u32 size;
+       void *data;
+};
+
+struct tegra_dc_ext_event {
+       __u32   type;
+       ssize_t data_size;
+       char    data[0];
+};
+
+#define TEGRA_DC_EXT_EVENT_HOTPLUG     0x1
+struct tegra_dc_ext_control_event_hotplug {
+       __u32 handle;
+};
+
+#define TEGRA_DC_EXT_CONTROL_GET_NUM_OUTPUTS \
+       _IOR('C', 0x00, __u32)
+#define TEGRA_DC_EXT_CONTROL_GET_OUTPUT_PROPERTIES \
+       _IOWR('C', 0x01, struct tegra_dc_ext_control_output_properties)
+#define TEGRA_DC_EXT_CONTROL_GET_OUTPUT_EDID \
+       _IOWR('C', 0x02, struct tegra_dc_ext_control_output_edid)
+#define TEGRA_DC_EXT_CONTROL_SET_EVENT_MASK \
+       _IOW('C', 0x03, __u32)
+
 #endif /* __TEGRA_DC_EXT_H */