/* * linux/arch/arm/mach-omap2/ipu_drv.c * * OMAP Image Processing Unit Power Management Driver * * Copyright (C) 2010 Texas Instruments * Paul Hunt * * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IPU_CLASS_NAME "ipu-power" #define IPU_DRIVER_NAME "omap-ipu-pm" static struct class *omap_ipu_pm_class; static dev_t omap_ipu_pm_dev; int ipu_pm_first_dev; int need_resume_notifications; static struct proc_dir_entry *ipu_pm_proc_entry; /* we could iterate over something much more * complicated than a set of lines of text * Just debugging. */ static char *lines[] = { "This is the first line from ipu_pm_seq", "This is the second line from ipu_pm_seq", }; static int ipu_pm_num_procs = ARRAY_SIZE(lines); static void *ipu_pm_seq_start(struct seq_file *s, loff_t *pos) { if (*pos >= ipu_pm_num_procs) return NULL;/* no more to read */ return lines[*pos]; } static void *ipu_pm_seq_next(struct seq_file *s, void *v, loff_t *pos) { (*pos)++; if (*pos >= ipu_pm_num_procs) return NULL;/* no more to read */ return lines[*pos]; } static void ipu_pm_seq_stop(struct seq_file *s, void *v) { /* nothing to do */ } static int ipu_pm_seq_show(struct seq_file *s, void *v) { seq_printf(s, "IPU_PM: %s\n", (char *) v); return 0; } static const struct seq_operations ipu_pm_seq_ops = { .start = ipu_pm_seq_start, .next = ipu_pm_seq_next, .stop = ipu_pm_seq_stop, .show = ipu_pm_seq_show, }; static int ipu_pm_proc_open(struct inode *inode, struct file *file) { return seq_open(file, &ipu_pm_seq_ops); } static const struct file_operations ipu_pm_proc_ops = { .owner = THIS_MODULE, .open = ipu_pm_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static void _suspend_stub(void) { pr_info("Suspend IOCTL received\n"); } static void _resume_stub(void) { pr_info("Resume IOCTL received!\n"); } /* arg should encode the IPU-managed HWMOD * to be suspended, 0 for system wide */ static long ipu_pm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret = 0; /* struct omap_ipu_pm *ipu_pm = filp->private_data; */ /* FIXME: check for ipu_pm when requested */ if (_IOC_TYPE(cmd) != IPU_PM_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > IPU_PM_IOC_MAXNR) return -ENOTTY; /* FIXME:not using the next two yet, since no args */ if (_IOC_DIR(cmd) & _IOC_READ) { if (!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) return -EFAULT; } else if (_IOC_DIR(cmd) & _IOC_WRITE) { if (!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) return -EFAULT; } switch (cmd) { case IPU_PM_IOC_SUSPEND: _suspend_stub(); break; case IPU_PM_IOC_RESUME: _resume_stub(); break; default: return -ENOTTY; } return ret; } #ifdef ZERO static const struct sysfs_ops { ssize_t (*show)(struct kobject *, struct attribute *, char *); ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); }; #endif static int is_driver_init; static const struct file_operations ipm_pm_fops = { .owner = THIS_MODULE, .unlocked_ioctl = ipu_pm_ioctl, .open = NULL, }; static struct ipu_pm_dev ipu_pm_dev; static int __devinit ipu_pm_probe(struct platform_device *pdev) { /* FIXME: get pdata */ /* struct ipu_pm_platform_data *pdata = pdev->dev.platform_data; */ /* int id = pdev->id; */ /* ivahd.pdev = pdev; */ need_resume_notifications = 0; if (!is_driver_init) { is_driver_init = 1; /* FIXME: maybe needed for multiple dev */ /* spin_lock_init(&ipu_pm_lock); */ } /* FIXME: add kobjects to kset */ return 0; } static int ipu_pm_drv_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); int retval = 0; if (pdev->id == ipu_pm_first_dev) { pr_debug("%s.%d ASKED TO SUSPEND", pdev->name, pdev->id); /* save any local context, * BIOS timers could be saved locally or on Ducati */ /* FIXME: Currently sending SUSPEND is enough to send * Ducati to hibernate, save ctx can be called at this * point to save ctx and reset remote procs * Currently the save ctx process can be called using * which ever proc_id, maybe this will change when * Tesla support is added. */ /* call notification function */ if (ipu_pm_get_handle(APP_M3)) { if (!(ipu_pm_get_state(APP_M3) & APP_PROC_DOWN)) { retval = ipu_pm_notifications(APP_M3, PM_SUSPEND, NULL); if (retval) goto error; } } if (ipu_pm_get_handle(SYS_M3)) { if (!(ipu_pm_get_state(SYS_M3) & SYS_PROC_DOWN)) { retval = ipu_pm_notifications(SYS_M3, PM_SUSPEND, NULL); if (retval) goto error; /* sysm3 is handling hibernation of ducati * currently */ ipu_pm_save_ctx(SYS_M3); need_resume_notifications = 1; } } /* return result, should be zero if all Ducati clients * returned zero else fail code */ } error: return retval; } static int ipu_pm_drv_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); int retval = 0; if (pdev->id == ipu_pm_first_dev) { pr_debug("%s.%d ASKED TO RESUME", pdev->name, pdev->id); /* restore any local context, * BIOS timers could be restored locally or on Ducati */ /* call our notification function */ if (need_resume_notifications) { if (ipu_pm_get_handle(APP_M3)) { retval = ipu_pm_notifications(APP_M3, PM_RESUME, NULL); if (retval) goto error; } if (ipu_pm_get_handle(SYS_M3)) { retval = ipu_pm_notifications(SYS_M3, PM_RESUME, NULL); if (retval) goto error; } need_resume_notifications = 0; } /* return result, should be zero if all Ducati clients * returned zero else fail code */ } error: return retval; } static const struct dev_pm_ops ipu_pm_ops = { .suspend = ipu_pm_drv_suspend, .resume = ipu_pm_drv_resume, }; static struct platform_driver ipu_pm_driver = { .probe = ipu_pm_probe, /*.remove = ipu_pm_remove, */ .driver = { .name = IPU_DRIVER_NAME, .owner = THIS_MODULE, .pm = &ipu_pm_ops, }, }; static int __init ipu_pm_init(void) { int ret; struct device *tmpdev; if (!is_driver_init) { is_driver_init = 1; /* FIXME: maybe needed for multiple dev */ /* spin_lock_init(&ipu_pm_lock); */ } ret = alloc_chrdev_region(&omap_ipu_pm_dev, 0, 2, IPU_CLASS_NAME); if (ret) { pr_err("%s: alloc_chrdev_region failed: %d\n", __func__, ret); return ret; } /* create device class */ omap_ipu_pm_class = class_create(THIS_MODULE, IPU_CLASS_NAME); if (IS_ERR(omap_ipu_pm_class)) { ret = PTR_ERR(omap_ipu_pm_class); pr_err("%s: class_create failed: %d\n", __func__, ret); return ret; } /* create an instance of this device class */ cdev_init(&ipu_pm_dev.cdev, &ipm_pm_fops); ipu_pm_dev.cdev.owner = THIS_MODULE; ipu_pm_dev.cdev.ops = &ipm_pm_fops; ret = cdev_add(&ipu_pm_dev.cdev, omap_ipu_pm_dev, 1); if (ret) pr_err("%s: cdev_add failed: %d\n", __func__, omap_ipu_pm_dev); tmpdev = device_create(omap_ipu_pm_class, NULL, omap_ipu_pm_dev, NULL, "ipu%d", MINOR(omap_ipu_pm_dev)); if (IS_ERR(tmpdev)) { ret = PTR_ERR(tmpdev); pr_err("%s: device_create failed: %d\n", __func__, ret); /* FIXME: add clean_cdev when error */ /*goto clean_cdev;*/ } dev_info(tmpdev, "Test of writing to the device message log," "done from %s\n", __func__); pr_info("%s initialized %s, major: %d, minor: %d\n", IPU_CLASS_NAME, /*pdata->name,*/ "ipu", MAJOR(omap_ipu_pm_dev), MINOR(omap_ipu_pm_dev)); /* FIXME:add a kset pointing to this new class */ /* omap_ipu_pm_class->dev_kobj */ /* add proc interface */ ipu_pm_proc_entry = create_proc_entry("ipu_proc_loads", 0, NULL); if (ipu_pm_proc_entry) ipu_pm_proc_entry->proc_fops = &ipu_pm_proc_ops; else pr_err("%s: proc entry create failed for: %s\n", __func__, IPU_CLASS_NAME); return platform_driver_register(&ipu_pm_driver); } static void __exit ipu_pm_exit(void) { platform_driver_unregister(&ipu_pm_driver); } /* early_platform_init("earlytimer", &ipu_pm_driver); */ module_init(ipu_pm_init); module_exit(ipu_pm_exit); MODULE_DESCRIPTION("OMAP IMAGE PROCESSING UNIT POWER MGMT DRIVER"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" IPU_CLASS_NAME); MODULE_AUTHOR("Texas Instruments Inc");