aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/omap2/dsscomp/device.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/omap2/dsscomp/device.c')
-rw-r--r--drivers/video/omap2/dsscomp/device.c634
1 files changed, 634 insertions, 0 deletions
diff --git a/drivers/video/omap2/dsscomp/device.c b/drivers/video/omap2/dsscomp/device.c
new file mode 100644
index 0000000..80cc21b
--- /dev/null
+++ b/drivers/video/omap2/dsscomp/device.c
@@ -0,0 +1,634 @@
+/*
+ * linux/drivers/video/omap2/dsscomp/device.c
+ *
+ * DSS Composition file device and ioctl support
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc
+ * Author: Lajos Molnar <molnar@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define DEBUG
+
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/anon_inodes.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+#include <linux/syscalls.h>
+
+#define MODULE_NAME "dsscomp"
+
+#include <video/omapdss.h>
+#include <video/dsscomp.h>
+#include <plat/dsscomp.h>
+#include "dsscomp.h"
+
+#include <linux/debugfs.h>
+
+static DECLARE_WAIT_QUEUE_HEAD(waitq);
+static DEFINE_MUTEX(wait_mtx);
+
+static u32 hwc_virt_to_phys(u32 arg)
+{
+ pmd_t *pmd;
+ pte_t *ptep;
+
+ pgd_t *pgd = pgd_offset(current->mm, arg);
+ if (pgd_none(*pgd) || pgd_bad(*pgd))
+ return 0;
+
+ pmd = pmd_offset(pgd, arg);
+ if (pmd_none(*pmd) || pmd_bad(*pmd))
+ return 0;
+
+ ptep = pte_offset_map(pmd, arg);
+ if (ptep && pte_present(*ptep))
+ return (PAGE_MASK & *ptep) | (~PAGE_MASK & arg);
+
+ return 0;
+}
+
+/*
+ * ===========================================================================
+ * WAIT OPERATIONS
+ * ===========================================================================
+ */
+
+static void sync_drop(struct dsscomp_sync_obj *sync)
+{
+ if (sync && atomic_dec_and_test(&sync->refs)) {
+ if (debug & DEBUG_WAITS)
+ pr_info("free sync [%p]\n", sync);
+
+ kfree(sync);
+ }
+}
+
+static int sync_setup(const char *name, const struct file_operations *fops,
+ struct dsscomp_sync_obj *sync, int flags)
+{
+ if (!sync)
+ return -ENOMEM;
+
+ sync->refs.counter = 1;
+ sync->fd = anon_inode_getfd(name, fops, sync, flags);
+ return sync->fd < 0 ? sync->fd : 0;
+}
+
+static int sync_finalize(struct dsscomp_sync_obj *sync, int r)
+{
+ if (sync) {
+ if (r < 0)
+ /* delete sync object on failure */
+ sys_close(sync->fd);
+ else
+ /* return file descriptor on success */
+ r = sync->fd;
+ }
+ return r;
+}
+
+/* wait for programming or release of a composition */
+int dsscomp_wait(struct dsscomp_sync_obj *sync, enum dsscomp_wait_phase phase,
+ int timeout)
+{
+ mutex_lock(&wait_mtx);
+ if (debug & DEBUG_WAITS)
+ pr_info("wait %s on [%p]\n",
+ phase == DSSCOMP_WAIT_DISPLAYED ? "display" :
+ phase == DSSCOMP_WAIT_PROGRAMMED ? "program" :
+ "release", sync);
+
+ if (sync->state < phase) {
+ mutex_unlock(&wait_mtx);
+
+ timeout = wait_event_interruptible_timeout(waitq,
+ sync->state >= phase, timeout);
+ if (debug & DEBUG_WAITS)
+ pr_info("wait over [%p]: %s %d\n", sync,
+ timeout < 0 ? "signal" :
+ timeout > 0 ? "ok" : "timeout",
+ timeout);
+ if (timeout <= 0)
+ return timeout ? : -ETIME;
+
+ mutex_lock(&wait_mtx);
+ }
+ mutex_unlock(&wait_mtx);
+
+ return 0;
+}
+EXPORT_SYMBOL(dsscomp_wait);
+
+static void dsscomp_queue_cb(void *data, int status)
+{
+ struct dsscomp_sync_obj *sync = data;
+ enum dsscomp_wait_phase phase =
+ status == DSS_COMPLETION_PROGRAMMED ? DSSCOMP_WAIT_PROGRAMMED :
+ status == DSS_COMPLETION_DISPLAYED ? DSSCOMP_WAIT_DISPLAYED :
+ DSSCOMP_WAIT_RELEASED, old_phase;
+
+ mutex_lock(&wait_mtx);
+ old_phase = sync->state;
+ if (old_phase < phase)
+ sync->state = phase;
+ mutex_unlock(&wait_mtx);
+
+ if (status & DSS_COMPLETION_RELEASED)
+ sync_drop(sync);
+ if (old_phase < phase)
+ wake_up_interruptible_sync(&waitq);
+}
+
+static int sync_release(struct inode *inode, struct file *filp)
+{
+ struct dsscomp_sync_obj *sync = filp->private_data;
+ sync_drop(sync);
+ return 0;
+}
+
+static long sync_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int r = 0;
+ struct dsscomp_sync_obj *sync = filp->private_data;
+ void __user *ptr = (void __user *)arg;
+
+ switch (cmd) {
+ case DSSCIOC_WAIT:
+ {
+ struct dsscomp_wait_data wd;
+ r = copy_from_user(&wd, ptr, sizeof(wd)) ? :
+ dsscomp_wait(sync, wd.phase,
+ usecs_to_jiffies(wd.timeout_us));
+ break;
+ }
+ default:
+ r = -EINVAL;
+ }
+ return r;
+}
+
+static const struct file_operations sync_fops = {
+ .owner = THIS_MODULE,
+ .release = sync_release,
+ .unlocked_ioctl = sync_ioctl,
+};
+
+static long setup_mgr(struct dsscomp_dev *cdev,
+ struct dsscomp_setup_mgr_data *d)
+{
+ int i, r;
+ struct omap_dss_device *dev;
+ struct omap_overlay_manager *mgr;
+ dsscomp_t comp;
+ struct dsscomp_sync_obj *sync = NULL;
+
+ dump_comp_info(cdev, d, "queue");
+ for (i = 0; i < d->num_ovls; i++)
+ dump_ovl_info(cdev, d->ovls + i);
+
+ /* verify display is valid and connected */
+ if (d->mgr.ix >= cdev->num_displays)
+ return -EINVAL;
+ dev = cdev->displays[d->mgr.ix];
+ if (!dev)
+ return -EINVAL;
+ mgr = dev->manager;
+ if (!mgr)
+ return -ENODEV;
+
+ comp = dsscomp_new(mgr);
+ if (IS_ERR(comp))
+ return PTR_ERR(comp);
+
+ /* swap red & blue if requested */
+ if (d->mgr.swap_rb) {
+ swap_rb_in_mgr_info(&d->mgr);
+ for (i = 0; i < d->num_ovls; i++)
+ swap_rb_in_ovl_info(d->ovls + i);
+ }
+
+ r = dsscomp_set_mgr(comp, &d->mgr);
+
+ for (i = 0; i < d->num_ovls; i++) {
+ struct dss2_ovl_info *oi = d->ovls + i;
+ u32 addr = (u32) oi->address;
+
+ /* convert addresses to user space */
+ if (oi->cfg.color_mode == OMAP_DSS_COLOR_NV12)
+ oi->uv = hwc_virt_to_phys(addr +
+ oi->cfg.height * oi->cfg.stride);
+ oi->ba = hwc_virt_to_phys(addr);
+
+ r = r ? : dsscomp_set_ovl(comp, oi);
+ }
+
+ r = r ? : dsscomp_setup(comp, d->mode, d->win);
+
+ /* create sync object */
+ if (d->get_sync_obj) {
+ sync = kzalloc(sizeof(*sync), GFP_KERNEL);
+ r = sync_setup("dsscomp_sync", &sync_fops, sync, O_RDONLY);
+ if (sync && (debug & DEBUG_WAITS))
+ dev_info(DEV(cdev), "new sync [%p] on #%d\n", sync,
+ sync->fd);
+ if (r)
+ sync_drop(sync);
+ }
+
+ /* drop composition if failed to create */
+ if (r) {
+ dsscomp_drop(comp);
+ return r;
+ }
+
+ if (sync) {
+ sync->refs.counter++;
+ comp->extra_cb = dsscomp_queue_cb;
+ comp->extra_cb_data = sync;
+ }
+ if (d->mode & DSSCOMP_SETUP_APPLY)
+ r = dsscomp_delayed_apply(comp);
+
+ /* delete sync object if failed to apply or create file */
+ if (sync) {
+ r = sync_finalize(sync, r);
+ if (r < 0)
+ sync_drop(sync);
+ }
+ return r;
+}
+
+static long query_display(struct dsscomp_dev *cdev,
+ struct dsscomp_display_info *dis)
+{
+ struct omap_dss_device *dev;
+ struct omap_overlay_manager *mgr;
+ int i;
+
+ /* get display */
+ if (dis->ix >= cdev->num_displays)
+ return -EINVAL;
+ dev = cdev->displays[dis->ix];
+ if (!dev)
+ return -EINVAL;
+ mgr = dev->manager;
+
+ /* fill out display information */
+ dis->channel = dev->channel;
+ dis->enabled = (dev->state == OMAP_DSS_DISPLAY_SUSPENDED) ?
+ dev->activate_after_resume :
+ (dev->state == OMAP_DSS_DISPLAY_ACTIVE);
+ dis->overlays_available = 0;
+ dis->overlays_owned = 0;
+#if 0
+ dis->s3d_info = dev->panel.s3d_info;
+#endif
+ dis->state = dev->state;
+ dis->timings = dev->panel.timings;
+
+ dis->width_in_mm = DIV_ROUND_CLOSEST(dev->panel.width_in_um, 1000);
+ dis->height_in_mm = DIV_ROUND_CLOSEST(dev->panel.height_in_um, 1000);
+
+ /* find all overlays available for/owned by this display */
+ for (i = 0; i < cdev->num_ovls && dis->enabled; i++) {
+ if (cdev->ovls[i]->manager == mgr)
+ dis->overlays_owned |= 1 << i;
+ else if (!cdev->ovls[i]->info.enabled)
+ dis->overlays_available |= 1 << i;
+ }
+ dis->overlays_available |= dis->overlays_owned;
+
+ /* fill out manager information */
+ if (mgr) {
+ dis->mgr.alpha_blending = mgr->info.alpha_enabled;
+ dis->mgr.default_color = mgr->info.default_color;
+#if 0
+ dis->mgr.interlaced = !strcmp(dev->name, "hdmi") &&
+ is_hdmi_interlaced()
+#else
+ dis->mgr.interlaced = 0;
+#endif
+ dis->mgr.trans_enabled = mgr->info.trans_enabled;
+ dis->mgr.trans_key = mgr->info.trans_key;
+ dis->mgr.trans_key_type = mgr->info.trans_key_type;
+ } else {
+ /* display is disabled if it has no manager */
+ memset(&dis->mgr, 0, sizeof(dis->mgr));
+ }
+ dis->mgr.ix = dis->ix;
+
+ if (dis->modedb_len && dev->driver->get_modedb)
+ dis->modedb_len = dev->driver->get_modedb(dev,
+ (struct fb_videomode *) dis->modedb, dis->modedb_len);
+ return 0;
+}
+
+static long check_ovl(struct dsscomp_dev *cdev,
+ struct dsscomp_check_ovl_data *chk)
+{
+ /* for now return all overlays as possible */
+ return (1 << cdev->num_ovls) - 1;
+}
+
+static long setup_display(struct dsscomp_dev *cdev,
+ struct dsscomp_setup_display_data *dis)
+{
+ struct omap_dss_device *dev;
+
+ /* get display */
+ if (dis->ix >= cdev->num_displays)
+ return -EINVAL;
+ dev = cdev->displays[dis->ix];
+ if (!dev)
+ return -EINVAL;
+
+ if (dev->driver->set_mode)
+ return dev->driver->set_mode(dev,
+ (struct fb_videomode *) &dis->mode);
+ else
+ return 0;
+}
+
+static void fill_cache(struct dsscomp_dev *cdev)
+{
+ unsigned long i;
+ struct omap_dss_device *dssdev = NULL;
+
+ cdev->num_ovls = min(omap_dss_get_num_overlays(), MAX_OVERLAYS);
+ for (i = 0; i < cdev->num_ovls; i++)
+ cdev->ovls[i] = omap_dss_get_overlay(i);
+
+ cdev->num_mgrs = min(omap_dss_get_num_overlay_managers(), MAX_MANAGERS);
+ for (i = 0; i < cdev->num_mgrs; i++)
+ cdev->mgrs[i] = omap_dss_get_overlay_manager(i);
+
+ for_each_dss_dev(dssdev) {
+ const char *name = dev_name(&dssdev->dev);
+ if (strncmp(name, "display", 7) ||
+ strict_strtoul(name + 7, 10, &i) ||
+ i >= MAX_DISPLAYS)
+ continue;
+
+ if (cdev->num_displays <= i)
+ cdev->num_displays = i + 1;
+
+ cdev->displays[i] = dssdev;
+ dev_dbg(DEV(cdev), "display%lu=%s\n", i, dssdev->driver_name);
+
+ cdev->state_notifiers[i].notifier_call = dsscomp_state_notifier;
+ blocking_notifier_chain_register(&dssdev->state_notifiers,
+ cdev->state_notifiers + i);
+ }
+ dev_info(DEV(cdev), "found %d displays and %d overlays\n",
+ cdev->num_displays, cdev->num_ovls);
+}
+
+static long comp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int r = 0;
+ struct miscdevice *dev = filp->private_data;
+ struct dsscomp_dev *cdev = container_of(dev, struct dsscomp_dev, dev);
+ void __user *ptr = (void __user *)arg;
+
+ union {
+ struct {
+ struct dsscomp_setup_mgr_data set;
+ struct dss2_ovl_info ovl[MAX_OVERLAYS];
+ } m;
+ struct dsscomp_setup_dispc_data dispc;
+ struct dsscomp_display_info dis;
+ struct dsscomp_check_ovl_data chk;
+ struct dsscomp_setup_display_data sdis;
+ } u;
+
+ dsscomp_gralloc_init(cdev);
+
+ switch (cmd) {
+ case DSSCIOC_SETUP_MGR:
+ {
+ r = copy_from_user(&u.m.set, ptr, sizeof(u.m.set)) ? :
+ u.m.set.num_ovls >= ARRAY_SIZE(u.m.ovl) ? -EINVAL :
+ copy_from_user(&u.m.ovl,
+ (void __user *)arg + sizeof(u.m.set),
+ sizeof(*u.m.ovl) * u.m.set.num_ovls) ? :
+ setup_mgr(cdev, &u.m.set);
+ break;
+ }
+ case DSSCIOC_SETUP_DISPC:
+ {
+ r = copy_from_user(&u.dispc, ptr, sizeof(u.dispc)) ? :
+ dsscomp_gralloc_queue_ioctl(&u.dispc);
+ break;
+ }
+ case DSSCIOC_QUERY_DISPLAY:
+ {
+ struct dsscomp_display_info *dis = NULL;
+ r = copy_from_user(&u.dis, ptr, sizeof(u.dis));
+ if (!r) {
+ /* impose a safe limit on modedb_len to prevent
+ * wrap around/overflow calculation of the alloced
+ * size that would make it smaller than
+ * struct dsscomp_display_info and cause heap
+ * corruption.
+ */
+ u.dis.modedb_len = clamp_val(u.dis.modedb_len, 0, 256);
+
+ dis = kzalloc(sizeof(*dis->modedb) * u.dis.modedb_len +
+ sizeof(*dis), GFP_KERNEL);
+ }
+ if (dis) {
+ *dis = u.dis;
+ r = query_display(cdev, dis) ? :
+ copy_to_user(ptr, dis, sizeof(*dis) +
+ sizeof(*dis->modedb) * dis->modedb_len);
+ kfree(dis);
+ } else {
+ r = r ? : -ENOMEM;
+ }
+ break;
+ }
+ case DSSCIOC_CHECK_OVL:
+ {
+ r = copy_from_user(&u.chk, ptr, sizeof(u.chk)) ? :
+ check_ovl(cdev, &u.chk);
+ break;
+ }
+ case DSSCIOC_SETUP_DISPLAY:
+ {
+ r = copy_from_user(&u.sdis, ptr, sizeof(u.sdis)) ? :
+ setup_display(cdev, &u.sdis);
+ }
+ default:
+ r = -EINVAL;
+ }
+ return r;
+}
+
+/* must implement open for filp->private_data to be filled */
+static int comp_open(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static const struct file_operations comp_fops = {
+ .owner = THIS_MODULE,
+ .open = comp_open,
+ .unlocked_ioctl = comp_ioctl,
+};
+
+static int dsscomp_debug_show(struct seq_file *s, void *unused)
+{
+ void (*fn)(struct seq_file *s) = s->private;
+ fn(s);
+ return 0;
+}
+
+static int dsscomp_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dsscomp_debug_show, inode->i_private);
+}
+
+static const struct file_operations dsscomp_debug_fops = {
+ .open = dsscomp_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dsscomp_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct dsscomp_dev *cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+ if (!cdev) {
+ pr_err("dsscomp: failed to allocate device.\n");
+ return -ENOMEM;
+ }
+ cdev->dev.minor = MISC_DYNAMIC_MINOR;
+ cdev->dev.name = "dsscomp";
+ cdev->dev.mode = 0666;
+ cdev->dev.fops = &comp_fops;
+
+ ret = misc_register(&cdev->dev);
+ if (ret) {
+ pr_err("dsscomp: failed to register misc device.\n");
+ return ret;
+ }
+ cdev->dbgfs = debugfs_create_dir("dsscomp", NULL);
+ if (IS_ERR_OR_NULL(cdev->dbgfs))
+ dev_warn(DEV(cdev), "failed to create debug files.\n");
+ else {
+ debugfs_create_file("comps", S_IRUGO,
+ cdev->dbgfs, dsscomp_dbg_comps, &dsscomp_debug_fops);
+ debugfs_create_file("gralloc", S_IRUGO,
+ cdev->dbgfs, dsscomp_dbg_gralloc, &dsscomp_debug_fops);
+#ifdef CONFIG_DSSCOMP_DEBUG_LOG
+ debugfs_create_file("log", S_IRUGO,
+ cdev->dbgfs, dsscomp_dbg_events, &dsscomp_debug_fops);
+#endif
+ }
+
+ platform_set_drvdata(pdev, cdev);
+
+ pr_info("dsscomp: initializing.\n");
+
+ fill_cache(cdev);
+
+ /* initialize queues */
+ dsscomp_queue_init(cdev);
+ dsscomp_gralloc_init(cdev);
+
+ return 0;
+}
+
+static int dsscomp_remove(struct platform_device *pdev)
+{
+ struct dsscomp_dev *cdev = platform_get_drvdata(pdev);
+ misc_deregister(&cdev->dev);
+ debugfs_remove_recursive(cdev->dbgfs);
+ dsscomp_queue_exit();
+ dsscomp_gralloc_exit();
+ kfree(cdev);
+
+ return 0;
+}
+
+static struct platform_driver dsscomp_pdriver = {
+ .probe = dsscomp_probe,
+ .remove = dsscomp_remove,
+ .driver = { .name = MODULE_NAME, .owner = THIS_MODULE }
+};
+
+static struct platform_device dsscomp_pdev = {
+ .name = MODULE_NAME,
+ .id = -1
+};
+
+static int __init dsscomp_init(void)
+{
+ int err = platform_driver_register(&dsscomp_pdriver);
+ if (err)
+ return err;
+
+ err = platform_device_register(&dsscomp_pdev);
+ if (err)
+ platform_driver_unregister(&dsscomp_pdriver);
+ return err;
+}
+
+static void __exit dsscomp_exit(void)
+{
+ platform_device_unregister(&dsscomp_pdev);
+ platform_driver_unregister(&dsscomp_pdriver);
+}
+
+#define DUMP_CHUNK 256
+static char dump_buf[64 * 1024];
+void dsscomp_kdump(void)
+{
+ struct seq_file s = {
+ .buf = dump_buf,
+ .size = sizeof(dump_buf) - 1,
+ };
+ int i;
+
+ dsscomp_dbg_events(&s);
+ dsscomp_dbg_comps(&s);
+ dsscomp_dbg_gralloc(&s);
+
+ for (i = 0; i < s.count; i += DUMP_CHUNK) {
+ if ((s.count - i) > DUMP_CHUNK) {
+ char c = s.buf[i + DUMP_CHUNK];
+ s.buf[i + DUMP_CHUNK] = 0;
+ pr_cont("%s", s.buf + i);
+ s.buf[i + DUMP_CHUNK] = c;
+ } else {
+ s.buf[s.count] = 0;
+ pr_cont("%s", s.buf + i);
+ }
+ }
+}
+EXPORT_SYMBOL(dsscomp_kdump);
+
+MODULE_LICENSE("GPL v2");
+module_init(dsscomp_init);
+module_exit(dsscomp_exit);