| /* |
| * 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) |
| 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); |