summaryrefslogtreecommitdiff
path: root/arch/arm/plat-omap/remoteproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/plat-omap/remoteproc.c')
-rw-r--r--arch/arm/plat-omap/remoteproc.c587
1 files changed, 587 insertions, 0 deletions
diff --git a/arch/arm/plat-omap/remoteproc.c b/arch/arm/plat-omap/remoteproc.c
new file mode 100644
index 00000000000..6cd2aff8a2b
--- /dev/null
+++ b/arch/arm/plat-omap/remoteproc.c
@@ -0,0 +1,587 @@
+/*
+ * OMAP Remote Processor driver
+ *
+ * Copyright (C) 2010 Texas Instruments Inc.
+ *
+ * Written by Ohad Ben-Cohen <ohad@wizery.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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/file.h>
+#include <linux/poll.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/eventfd.h>
+
+#include <plat/remoteproc.h>
+
+#define OMAP_RPROC_NAME "omap-rproc"
+
+static struct class *omap_rproc_class;
+static dev_t omap_rproc_dev;
+static atomic_t num_of_rprocs;
+
+
+static void rproc_eventfd_ntfy(struct omap_rproc *obj, int event)
+{
+ struct omap_rproc_ntfy *fd_reg;
+
+ spin_lock_irq(&obj->event_lock);
+ list_for_each_entry(fd_reg, &obj->event_list, list)
+ if (fd_reg->event == event)
+ eventfd_signal(fd_reg->evt_ctx, 1);
+ spin_unlock_irq(&obj->event_lock);
+}
+
+int rproc_start(struct omap_rproc *rproc, const void __user *arg)
+{
+ int ret;
+ struct omap_rproc_platform_data *pdata;
+ struct omap_rproc_start_args start_args;
+
+ start_args.start_addr = 0;
+
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+ if (!pdata->ops)
+ return -EINVAL;
+
+#if 0
+ if (copy_from_user(&start_args, arg, sizeof(start_args)))
+ return -EFAULT;
+#endif
+ ret = mutex_lock_interruptible(&rproc->lock);
+ if (ret)
+ return ret;
+
+ ret = pdata->ops->start(rproc->dev, start_args.start_addr);
+
+ if (!ret) {
+ omap_rproc_notify_event(rproc, OMAP_RPROC_START, NULL);
+ rproc_eventfd_ntfy(rproc, PROC_START);
+ }
+
+ mutex_unlock(&rproc->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rproc_start);
+
+int rproc_stop(struct omap_rproc *rproc)
+{
+ int ret;
+ struct omap_rproc_platform_data *pdata;
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+ if (!pdata->ops)
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&rproc->lock);
+ if (ret)
+ return ret;
+
+ ret = pdata->ops->stop(rproc->dev);
+
+ if (!ret) {
+ omap_rproc_notify_event(rproc, OMAP_RPROC_STOP, NULL);
+ rproc_eventfd_ntfy(rproc, PROC_STOP);
+ }
+
+ mutex_unlock(&rproc->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rproc_stop);
+
+int rproc_sleep(struct omap_rproc *rproc)
+{
+ int ret;
+ struct omap_rproc_platform_data *pdata;
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+ if (!pdata->ops)
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&rproc->lock);
+ if (ret)
+ return ret;
+
+ ret = pdata->ops->sleep(rproc->dev);
+
+ mutex_unlock(&rproc->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rproc_sleep);
+
+int rproc_wakeup(struct omap_rproc *rproc)
+{
+ int ret;
+ struct omap_rproc_platform_data *pdata;
+
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+ if (!pdata->ops)
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&rproc->lock);
+ if (ret)
+ return ret;
+
+ ret = pdata->ops->wakeup(rproc->dev);
+
+ mutex_unlock(&rproc->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rproc_wakeup);
+
+static inline int rproc_get_state(struct omap_rproc *rproc)
+{
+ struct omap_rproc_platform_data *pdata;
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+ if (!pdata->ops)
+ return -EINVAL;
+
+ return pdata->ops->get_state(rproc->dev);
+}
+
+static int rproc_reg_user_event(struct omap_rproc *rproc,
+ const void __user *arg)
+{
+ struct omap_rproc_ntfy *fd_reg;
+ int state;
+ struct omap_rproc_reg_event_args args;
+ int size;
+
+ size = copy_from_user(&args, arg,
+ sizeof(struct omap_rproc_reg_event_args));
+ if (size)
+ return -EINVAL;
+
+ fd_reg = kzalloc(sizeof(struct omap_rproc_ntfy), GFP_KERNEL);
+ if (!fd_reg)
+ return -ENOMEM;
+
+ fd_reg->fd = args.fd;
+ fd_reg->evt_ctx = eventfd_ctx_fdget(args.fd);
+ fd_reg->event = args.event;
+
+ INIT_LIST_HEAD(&fd_reg->list);
+
+ spin_lock_irq(&rproc->event_lock);
+ list_add_tail(&fd_reg->list, &rproc->event_list);
+ spin_unlock_irq(&rproc->event_lock);
+
+ /*
+ * Check the state of remote proc and send the notification
+ * if it is already in the desired state.
+ */
+ state = rproc_get_state(rproc);
+
+ switch (state) {
+ case OMAP_RPROC_RUNNING:
+ case OMAP_RPROC_HIBERNATING:
+ if (args.event == PROC_START)
+ rproc_eventfd_ntfy(rproc, args.event);
+ break;
+ case OMAP_RPROC_STOPPED:
+ if (args.event == PROC_STOP)
+ rproc_eventfd_ntfy(rproc, args.event);
+ break;
+ }
+
+ return 0;
+}
+
+static int rproc_unreg_user_event(struct omap_rproc *rproc,
+ const void __user *arg)
+{
+ struct omap_rproc_ntfy *fd_reg, *temp_reg;
+ struct omap_rproc_reg_event_args args;
+ int size;
+ int ret = -ENOENT;
+
+ size = copy_from_user(&args, arg,
+ sizeof(struct omap_rproc_reg_event_args));
+ if (size)
+ return -EINVAL;
+
+ spin_lock_irq(&rproc->event_lock);
+ list_for_each_entry_safe(fd_reg, temp_reg,
+ &rproc->event_list, list) {
+ if (fd_reg->fd == args.fd) {
+ list_del(&fd_reg->list);
+ kfree(fd_reg);
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock_irq(&rproc->event_lock);
+ return ret;
+}
+
+int omap_rproc_notify_event(struct omap_rproc *rproc, int event, void *data)
+{
+ return blocking_notifier_call_chain(&rproc->notifier, event, data);
+}
+
+int omap_rproc_register_notifier(struct omap_rproc *rproc,
+ struct notifier_block *nb)
+{
+ if (!nb)
+ return -EINVAL;
+ return blocking_notifier_chain_register(&rproc->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(omap_rproc_register_notifier);
+
+int omap_rproc_unregister_notifier(struct omap_rproc *rproc,
+ struct notifier_block *nb)
+{
+ if (!nb)
+ return -EINVAL;
+ return blocking_notifier_chain_unregister(&rproc->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(omap_rproc_unregister_notifier);
+
+static int omap_rproc_open(struct inode *inode, struct file *filp)
+{
+ unsigned int count, dev_num = iminor(inode);
+ struct omap_rproc *rproc;
+ struct omap_rproc_platform_data *pdata;
+
+ rproc = container_of(inode->i_cdev, struct omap_rproc, cdev);
+ if (!rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+
+ count = atomic_inc_return(&rproc->count);
+ dev_info(rproc->dev, "%s: dev num %d, name %s, count %d\n", __func__,
+ dev_num,
+ pdata->name,
+ count);
+ filp->private_data = rproc;
+
+ return 0;
+}
+
+static int omap_rproc_release(struct inode *inode, struct file *filp)
+{
+ unsigned int count;
+ struct omap_rproc_platform_data *pdata;
+ struct omap_rproc *rproc = filp->private_data;
+ if (!rproc || !rproc->dev)
+ return -EINVAL;
+
+ pdata = rproc->dev->platform_data;
+
+ count = atomic_dec_return(&rproc->count);
+ if (!count && (rproc->state != OMAP_RPROC_STOPPED))
+ rproc_stop(rproc);
+
+ return 0;
+}
+
+static long omap_rproc_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+ struct omap_rproc *rproc = filp->private_data;
+
+ if (!rproc)
+ return -EINVAL;
+
+ dev_info(rproc->dev, "%s\n", __func__);
+
+ if (_IOC_TYPE(cmd) != RPROC_IOC_MAGIC)
+ return -ENOTTY;
+ if (_IOC_NR(cmd) > RPROC_IOC_MAXNR)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case RPROC_IOCSTART:
+ /* FIXME: re-visit this check to perform
+ proper permission checks */
+ /* if (!capable(CAP_SYS_ADMIN))
+ return -EPERM; */
+ rc = rproc_start(rproc, (const void __user *) arg);
+ break;
+ case RPROC_IOCSTOP:
+ /* FIXME: re-visit this check to perform
+ proper permission checks */
+ /* if (!capable(CAP_SYS_ADMIN))
+ return -EPERM; */
+ rc = rproc_stop(rproc);
+ break;
+ case RPROC_IOCGETSTATE:
+ rc = rproc_get_state(rproc);
+ break;
+ case RPROC_IOCREGEVENT:
+ rc = rproc_reg_user_event(rproc, (const void __user *) arg);
+ break;
+ case RPROC_IOCUNREGEVENT:
+ rc = rproc_unreg_user_event(rproc, (const void __user *) arg);
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ /* First element of arg is the status */
+ copy_to_user((void __user *)arg, &rc, sizeof(rc));
+
+ return 0;
+}
+
+static int omap_rproc_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+
+ vma->vm_page_prot = pgprot_dmacoherent(vma->vm_page_prot);
+ vma->vm_flags |= VM_RESERVED;
+
+ if (remap_pfn_range(vma,
+ vma->vm_start,
+ vma->vm_pgoff,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot)) {
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static const struct file_operations omap_rproc_fops = {
+ .open = omap_rproc_open,
+ .release = omap_rproc_release,
+ .unlocked_ioctl = omap_rproc_ioctl,
+ .mmap = omap_rproc_mmap,
+ .owner = THIS_MODULE,
+};
+
+static int omap_rproc_probe(struct platform_device *pdev)
+{
+ int ret = 0, major, minor;
+ struct device *tmpdev;
+ struct device *dev = &pdev->dev;
+ struct omap_rproc_platform_data *pdata = dev->platform_data;
+ struct omap_rproc *rproc;
+
+ if (!pdata || !pdata->name || !pdata->oh_name || !pdata->ops)
+ return -EINVAL;
+
+ dev_info(dev, "%s: adding rproc %s\n", __func__, pdata->name);
+
+ rproc = kzalloc(sizeof(struct omap_rproc), GFP_KERNEL);
+ if (!rproc) {
+ dev_err(dev, "%s: kzalloc failed\n", __func__);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ platform_set_drvdata(pdev, rproc);
+ major = MAJOR(omap_rproc_dev);
+ minor = atomic_read(&num_of_rprocs);
+ atomic_inc(&num_of_rprocs);
+
+ rproc->dev = dev;
+ rproc->minor = minor;
+ atomic_set(&rproc->count, 0);
+ rproc->name = pdata->name;
+
+ rproc->timer_clk_id = pdata->timer_clk_id;
+ rproc->timer_hib_id = pdata->timer_hib_id;
+
+ mutex_init(&rproc->lock);
+ BLOCKING_INIT_NOTIFIER_HEAD(&rproc->notifier);
+
+ spin_lock_init(&rproc->event_lock);
+ INIT_LIST_HEAD(&rproc->event_list);
+
+ cdev_init(&rproc->cdev, &omap_rproc_fops);
+ rproc->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&rproc->cdev, MKDEV(major, minor), 1);
+ if (ret) {
+ dev_err(dev, "%s: cdev_add failed: %d\n", __func__, ret);
+ goto free_rproc;
+ }
+
+ tmpdev = device_create(omap_rproc_class, NULL,
+ MKDEV(major, minor),
+ NULL,
+ OMAP_RPROC_NAME "%d", minor);
+ if (IS_ERR(tmpdev)) {
+ ret = PTR_ERR(tmpdev);
+ dev_err(dev, "%s: device_create failed: %d\n", __func__, ret);
+ goto clean_cdev;
+ }
+
+ dev_info(dev, "%s initialized %s, major: %d, base-minor: %d\n",
+ OMAP_RPROC_NAME,
+ pdata->name,
+ MAJOR(omap_rproc_dev),
+ minor);
+ return 0;
+
+clean_cdev:
+ cdev_del(&rproc->cdev);
+free_rproc:
+ kfree(rproc);
+out:
+ return ret;
+}
+
+static int __devexit omap_rproc_remove(struct platform_device *pdev)
+{
+ int major = MAJOR(omap_rproc_dev);
+ struct device *dev = &pdev->dev;
+ struct omap_rproc_platform_data *pdata = dev->platform_data;
+ struct omap_rproc *rproc = platform_get_drvdata(pdev);
+
+ if (!pdata || !rproc)
+ return -EINVAL;
+
+ dev_info(dev, "%s removing %s, major: %d, base-minor: %d\n",
+ OMAP_RPROC_NAME,
+ pdata->name,
+ major,
+ rproc->minor);
+
+ device_destroy(omap_rproc_class, MKDEV(major, rproc->minor));
+ cdev_del(&rproc->cdev);
+
+ return 0;
+}
+
+static struct platform_driver omap_rproc_driver = {
+ .probe = omap_rproc_probe,
+ .remove = __devexit_p(omap_rproc_remove),
+ .driver = {
+ .name = "omap-remoteproc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int device_match_by_alias(struct device *dev, void *data)
+{
+ struct omap_rproc *obj = (struct omap_rproc *)platform_get_drvdata(
+ to_platform_device(dev));
+ const char *name = data;
+
+ pr_debug("%s: %s %s\n", __func__, obj->name, name);
+
+ return strcmp(obj->name, name) == 0;
+}
+
+struct omap_rproc *omap_rproc_get(const char *name)
+{
+ struct device *dev;
+ struct omap_rproc *rproc;
+ dev = driver_find_device(&omap_rproc_driver.driver, NULL, (void *)name,
+ device_match_by_alias);
+ if (!dev)
+ return ERR_PTR(-ENODEV);
+ rproc = (struct omap_rproc *)platform_get_drvdata(
+ to_platform_device(dev));
+
+ dev_dbg(rproc->dev, "%s: %s\n", __func__, rproc->name);
+ return rproc;
+}
+EXPORT_SYMBOL_GPL(omap_rproc_get);
+
+void omap_rproc_put(struct omap_rproc *rproc)
+{
+ if (!rproc || IS_ERR(rproc))
+ return;
+
+ dev_dbg(rproc->dev, "%s: %s\n", __func__, rproc->name);
+}
+EXPORT_SYMBOL_GPL(omap_rproc_put);
+
+static int __init omap_rproc_init(void)
+{
+ int num;
+ int ret;
+
+ if (cpu_is_omap44xx())
+ num = NR_RPROC_OMAP4_DEVICES;
+ else
+ return 0;
+
+ ret = alloc_chrdev_region(&omap_rproc_dev, 0, num, OMAP_RPROC_NAME);
+ if (ret) {
+ pr_err("%s: alloc_chrdev_region failed: %d\n", __func__, ret);
+ goto out;
+ }
+
+ omap_rproc_class = class_create(THIS_MODULE, OMAP_RPROC_NAME);
+ if (IS_ERR(omap_rproc_class)) {
+ ret = PTR_ERR(omap_rproc_class);
+ pr_err("%s: class_create failed: %d\n", __func__, ret);
+ goto unreg_region;
+ }
+
+ atomic_set(&num_of_rprocs, 0);
+
+ ret = platform_driver_register(&omap_rproc_driver);
+ if (ret) {
+ pr_err("%s: platform_driver_register failed: %d\n", __func__,
+ ret);
+ goto out;
+ }
+ return 0;
+
+unreg_region:
+ unregister_chrdev_region(omap_rproc_dev, num);
+out:
+ return ret;
+}
+module_init(omap_rproc_init);
+
+static void __exit omap_rproc_exit(void)
+{
+ int num;
+
+ if (cpu_is_omap44xx())
+ num = NR_RPROC_OMAP4_DEVICES;
+ else
+ return;
+
+ class_destroy(omap_rproc_class);
+ unregister_chrdev_region(omap_rproc_dev, num);
+}
+module_exit(omap_rproc_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("OMAP Remote Processor driver");
+MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>");
+MODULE_AUTHOR("Hari Kanigeri <h-kanigeri2@ti.com>");