diff --git a/drivers/Kconfig b/drivers/Kconfig index ffa52e5eaf7b..58a0d3539bba 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -114,6 +114,8 @@ source "drivers/edac/Kconfig" source "drivers/rtc/Kconfig" +source "drivers/esoc/Kconfig" + source "drivers/dma/Kconfig" source "drivers/dca/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index bdd3fd5cfa07..aee8d366ba81 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -160,5 +160,6 @@ obj-$(CONFIG_NTB) += ntb/ obj-$(CONFIG_MOBICORE_SUPPORT) += gud/ obj-$(CONFIG_CORESIGHT) += coresight/ +obj-$(CONFIG_ESOC) += esoc/ obj-$(CONFIG_BIF) += bif/ diff --git a/drivers/esoc/Kconfig b/drivers/esoc/Kconfig new file mode 100644 index 000000000000..0dea0b33e70d --- /dev/null +++ b/drivers/esoc/Kconfig @@ -0,0 +1,28 @@ +# +# External soc control infrastructure and drivers +# +menuconfig ESOC + bool "External SOCs Control" + help + External SOCs can be powered on and monitored by user + space or kernel drivers. Additionally they can be controlled + to respond to control commands. This framework provides an + interface to track events related to the external slave socs. + +if ESOC + +config ESOC_DEV + bool "ESOC userspace interface" + help + Say yes here to enable a userspace representation of the control + link. Userspace can register a request engine or a command engine + for the external soc. It can receive event notifications from the + control link. + +config ESOC_DEBUG + bool "ESOC debug support" + help + Say yes here to enable debugging support in the ESOC framework + and individual esoc drivers. +endif + diff --git a/drivers/esoc/Makefile b/drivers/esoc/Makefile new file mode 100644 index 000000000000..8720bda39527 --- /dev/null +++ b/drivers/esoc/Makefile @@ -0,0 +1,6 @@ +# generic external soc control support + +ccflags-$(CONFIG_ESOC_DEBUG) := -DDEBUG +obj-$(CONFIG_ESOC) += esoc_bus.o +obj-$(CONFIG_ESOC_DEV) += esoc_dev.o + diff --git a/drivers/esoc/esoc.h b/drivers/esoc/esoc.h new file mode 100644 index 000000000000..d6daa5ee77ca --- /dev/null +++ b/drivers/esoc/esoc.h @@ -0,0 +1,150 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ +#ifndef __ESOC_H__ +#define __ESOC_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ESOC_DEV_MAX 4 +#define ESOC_NAME_LEN 20 +#define ESOC_LINK_LEN 8 + +struct esoc_clink; +/** + * struct esoc_eng: Engine of the esoc control link + * @handle_clink_req: handle incoming esoc requests. + * @handle_clink_evt: handle for esoc events. + * @esoc_clink: pointer to esoc control link. + */ +struct esoc_eng { + void (*handle_clink_req)(enum esoc_req req, + struct esoc_eng *eng); + void (*handle_clink_evt)(enum esoc_evt evt, + struct esoc_eng *eng); + struct esoc_clink *esoc_clink; +}; + +/** + * struct esoc_clink: Representation of external esoc device + * @name: Name of the external esoc. + * @link_name: name of the physical link. + * @parent: parent device. + * @dev: device for userspace interface. + * @id: id of the external device. + * @owner: owner of the device. + * @clink_ops: control operations for the control link + * @req_eng: handle for request engine. + * @cmd_eng: handle for command engine. + * @clink_data: private data of esoc control link. + * @compat_data: compat data of esoc driver. + * @subsys_desc: descriptor for subsystem restart + * @subsys_dev: ssr device handle. + */ +struct esoc_clink { + const char *name; + const char *link_name; + struct device *parent; + struct device dev; + unsigned int id; + struct module *owner; + const struct esoc_clink_ops const *clink_ops; + struct esoc_eng *req_eng; + struct esoc_eng *cmd_eng; + spinlock_t notify_lock; + void *clink_data; + void *compat_data; + struct subsys_desc subsys; + struct subsys_device *subsys_dev; +}; + +/** + * struct esoc_clink_ops: Operations to control external soc + * @cmd_exe: Execute control command + * @get_status: Get current status, or response to previous command + * @notify_esoc: notify external soc of events + */ +struct esoc_clink_ops { + int (*cmd_exe)(enum esoc_cmd cmd, struct esoc_clink *dev); + int (*get_status)(u32 *status, struct esoc_clink *dev); + void (*notify)(enum esoc_notify notify, struct esoc_clink *dev); +}; + +/** + * struct esoc_compat: Compatibility of esoc drivers. + * @name: esoc link that driver is compatible with. + * @data: driver data associated with esoc clink. + */ +struct esoc_compat { + const char *name; + void *data; +}; + +/** + * struct esoc_drv: Driver for an esoc clink + * @driver: drivers for esoc. + * @owner: module owner of esoc driver. + * @compat_table: compatible table for driver. + * @compat_entries + * @probe: probe function for esoc driver. + */ +struct esoc_drv { + struct device_driver driver; + struct module *owner; + struct esoc_compat *compat_table; + unsigned int compat_entries; + int (*probe)(struct esoc_clink *esoc_clink); +}; + +#define to_esoc_clink(d) container_of(d, struct esoc_clink, dev) +#define to_esoc_drv(d) container_of(d, struct esoc_drv, driver) + +extern struct bus_type esoc_bus_type; + + +/* Exported apis */ +void esoc_dev_exit(void); +int esoc_dev_init(void); +void esoc_clink_unregister(struct esoc_clink *esoc_dev); +int esoc_clink_register(struct esoc_clink *esoc_dev); +struct esoc_clink *get_esoc_clink(int id); +void put_esoc_clink(struct esoc_clink *esoc_clink); +void *get_esoc_clink_data(struct esoc_clink *esoc); +void set_esoc_clink_data(struct esoc_clink *esoc, void *data); +void esoc_clink_evt_notify(enum esoc_evt, struct esoc_clink *esoc_dev); +void esoc_clink_queue_request(enum esoc_req req, struct esoc_clink *esoc_dev); +void esoc_for_each_dev(void *data, int (*fn)(struct device *dev, + void *data)); +int esoc_clink_register_cmd_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng); +void esoc_clink_unregister_cmd_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng); +int esoc_clink_register_req_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng); +void esoc_clink_unregister_req_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng); +int esoc_drv_register(struct esoc_drv *driver); +void esoc_set_drv_data(struct esoc_clink *esoc_clink, void *data); +void *esoc_get_drv_data(struct esoc_clink *esoc_clink); +/* ssr operations */ +int esoc_clink_register_ssr(struct esoc_clink *esoc_clink); +int esoc_clink_request_ssr(struct esoc_clink *esoc_clink); +void esoc_clink_unregister_ssr(struct esoc_clink *esoc_clink); +#endif diff --git a/drivers/esoc/esoc_bus.c b/drivers/esoc/esoc_bus.c new file mode 100644 index 000000000000..4a32fddac711 --- /dev/null +++ b/drivers/esoc/esoc_bus.c @@ -0,0 +1,336 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#include +#include +#include "esoc.h" + +static DEFINE_IDA(esoc_ida); + +/* SYSFS */ +static ssize_t +esoc_name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, ESOC_NAME_LEN, "%s", to_esoc_clink(dev)->name); +} + +static ssize_t +esoc_link_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, ESOC_LINK_LEN, "%s", + to_esoc_clink(dev)->link_name); +} + +static struct device_attribute esoc_clink_attrs[] = { + + __ATTR_RO(esoc_name), + __ATTR_RO(esoc_link), + __ATTR_NULL, +}; + +static int esoc_bus_match(struct device *dev, struct device_driver *drv) +{ + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + + return !memcmp(esoc_clink->name, drv->name, + strlen(drv->name)); +} + +static int esoc_bus_probe(struct device *dev) +{ + int ret; + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + struct esoc_drv *esoc_drv = to_esoc_drv(dev->driver); + + ret = esoc_drv->probe(esoc_clink); + if (ret) { + pr_err("failed to probe %s dev\n", esoc_clink->name); + return ret; + } + return 0; +} + +struct bus_type esoc_bus_type = { + .name = "esoc", + .match = esoc_bus_match, + .dev_attrs = esoc_clink_attrs, +}; +EXPORT_SYMBOL(esoc_bus_type); + +struct device esoc_bus = { + .init_name = "esoc-bus" +}; +EXPORT_SYMBOL(esoc_bus); + +/* bus accessor */ +static void esoc_clink_release(struct device *dev) +{ + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + ida_simple_remove(&esoc_ida, esoc_clink->id); + kfree(esoc_clink); +} + +static int esoc_clink_match_id(struct device *dev, void *id) +{ + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + int *esoc_id = (int *)id; + + if (esoc_clink->id == *esoc_id) { + if (!try_module_get(esoc_clink->owner)) + return 0; + return 1; + } + return 0; +} + +void esoc_for_each_dev(void *data, int (*fn)(struct device *dev, void *)) +{ + int ret; + + ret = bus_for_each_dev(&esoc_bus_type, NULL, data, fn); + return; +} +EXPORT_SYMBOL(esoc_for_each_dev); + +struct esoc_clink *get_esoc_clink(int id) +{ + struct esoc_clink *esoc_clink; + struct device *dev; + + dev = bus_find_device(&esoc_bus_type, NULL, &id, esoc_clink_match_id); + if (IS_ERR(dev)) + return NULL; + esoc_clink = to_esoc_clink(dev); + return esoc_clink; +} +EXPORT_SYMBOL(get_esoc_clink); + +void put_esoc_clink(struct esoc_clink *esoc_clink) +{ + module_put(esoc_clink->owner); +} +EXPORT_SYMBOL(put_esoc_clink); + +/* ssr operations */ +int esoc_clink_register_ssr(struct esoc_clink *esoc_clink) +{ + int ret; + int len; + char *subsys_name; + + len = strlen("esoc") + sizeof(esoc_clink->id); + subsys_name = kzalloc(len, GFP_KERNEL); + if (IS_ERR(subsys_name)) + return PTR_ERR(subsys_name); + snprintf(subsys_name, len, "esoc%d", esoc_clink->id); + esoc_clink->subsys.name = subsys_name; + esoc_clink->subsys.dev = &esoc_clink->dev; + esoc_clink->subsys_dev = subsys_register(&esoc_clink->subsys); + if (IS_ERR(esoc_clink->subsys_dev)) { + dev_err(&esoc_clink->dev, "failed to register ssr node\n"); + ret = PTR_ERR(esoc_clink->subsys_dev); + goto subsys_err; + } + return 0; +subsys_err: + kfree(subsys_name); + return ret; +} +EXPORT_SYMBOL(esoc_clink_register_ssr); + +void esoc_clink_unregister_ssr(struct esoc_clink *esoc_clink) +{ + subsys_unregister(esoc_clink->subsys_dev); + kfree(esoc_clink->subsys.name); +} +EXPORT_SYMBOL(esoc_clink_unregister_ssr); + +int esoc_clink_request_ssr(struct esoc_clink *esoc_clink) +{ + subsystem_restart_dev(esoc_clink->subsys_dev); + return 0; +} +EXPORT_SYMBOL(esoc_clink_request_ssr); + +/* bus operations */ +void esoc_clink_evt_notify(enum esoc_evt evt, struct esoc_clink *esoc_clink) +{ + unsigned long flags; + + spin_lock_irqsave(&esoc_clink->notify_lock, flags); + if (esoc_clink->req_eng && esoc_clink->req_eng->handle_clink_evt) + esoc_clink->req_eng->handle_clink_evt(evt, esoc_clink->req_eng); + if (esoc_clink->cmd_eng && esoc_clink->cmd_eng->handle_clink_evt) + esoc_clink->cmd_eng->handle_clink_evt(evt, esoc_clink->cmd_eng); + spin_unlock_irqrestore(&esoc_clink->notify_lock, flags); +} +EXPORT_SYMBOL(esoc_clink_evt_notify); + +void *get_esoc_clink_data(struct esoc_clink *esoc) +{ + return esoc->clink_data; +} +EXPORT_SYMBOL(get_esoc_clink_data); + +void set_esoc_clink_data(struct esoc_clink *esoc, void *data) +{ + esoc->clink_data = data; +} +EXPORT_SYMBOL(set_esoc_clink_data); + +void esoc_clink_queue_request(enum esoc_req req, struct esoc_clink *esoc_clink) +{ + unsigned long flags; + struct esoc_eng *req_eng; + + spin_lock_irqsave(&esoc_clink->notify_lock, flags); + if (esoc_clink->req_eng != NULL) { + req_eng = esoc_clink->req_eng; + req_eng->handle_clink_req(req, req_eng); + } + spin_unlock_irqrestore(&esoc_clink->notify_lock, flags); +} +EXPORT_SYMBOL(esoc_clink_queue_request); + +void esoc_set_drv_data(struct esoc_clink *esoc_clink, void *data) +{ + dev_set_drvdata(&esoc_clink->dev, data); +} +EXPORT_SYMBOL(esoc_set_drv_data); + +void *esoc_get_drv_data(struct esoc_clink *esoc_clink) +{ + return dev_get_drvdata(&esoc_clink->dev); +} +EXPORT_SYMBOL(esoc_get_drv_data); + +/* bus registration functions */ +void esoc_clink_unregister(struct esoc_clink *esoc_clink) +{ + if (get_device(&esoc_clink->dev) != NULL) { + device_unregister(&esoc_clink->dev); + put_device(&esoc_clink->dev); + } +} +EXPORT_SYMBOL(esoc_clink_unregister); + +int esoc_clink_register(struct esoc_clink *esoc_clink) +{ + int id, err; + struct device *dev; + + if (!esoc_clink->name || !esoc_clink->link_name || + !esoc_clink->clink_ops) { + dev_err(esoc_clink->parent, "invalid esoc arguments\n"); + return -EINVAL; + } + id = ida_simple_get(&esoc_ida, 0, ESOC_DEV_MAX, GFP_KERNEL); + if (id < 0) { + err = id; + goto exit_ida; + } + esoc_clink->id = id; + dev = &esoc_clink->dev; + dev->bus = &esoc_bus_type; + dev->release = esoc_clink_release; + if (!esoc_clink->parent) + dev->parent = &esoc_bus; + else + dev->parent = esoc_clink->parent; + dev_set_name(dev, "esoc%d", id); + err = device_register(dev); + if (err) { + dev_err(esoc_clink->parent, "esoc device register failed\n"); + goto exit_ida; + } + spin_lock_init(&esoc_clink->notify_lock); + return 0; +exit_ida: + ida_simple_remove(&esoc_ida, id); + pr_err("unable to register %s, err = %d\n", esoc_clink->name, err); + return err; +} +EXPORT_SYMBOL(esoc_clink_register); + +int esoc_clink_register_req_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng) +{ + if (esoc_clink->req_eng) + return -EBUSY; + if (!eng->handle_clink_req) + return -EINVAL; + esoc_clink->req_eng = eng; + eng->esoc_clink = esoc_clink; + return 0; +} +EXPORT_SYMBOL(esoc_clink_register_req_eng); + +int esoc_clink_register_cmd_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng) +{ + if (esoc_clink->cmd_eng) + return -EBUSY; + esoc_clink->cmd_eng = eng; + eng->esoc_clink = esoc_clink; + return 0; +} +EXPORT_SYMBOL(esoc_clink_register_cmd_eng); + +void esoc_clink_unregister_req_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng) +{ + esoc_clink->req_eng = NULL; +} +EXPORT_SYMBOL(esoc_clink_unregister_req_eng); + +void esoc_clink_unregister_cmd_eng(struct esoc_clink *esoc_clink, + struct esoc_eng *eng) +{ + esoc_clink->cmd_eng = NULL; +} +EXPORT_SYMBOL(esoc_clink_unregister_cmd_eng); + +int esoc_drv_register(struct esoc_drv *driver) +{ + int ret; + + driver->driver.bus = &esoc_bus_type; + driver->driver.probe = esoc_bus_probe; + ret = driver_register(&driver->driver); + if (ret) + return ret; + return 0; +} +EXPORT_SYMBOL(esoc_drv_register); + +static int __init esoc_init(void) +{ + int ret; + + ret = device_register(&esoc_bus); + if (ret) { + pr_err("esoc bus device register fail\n"); + return ret; + } + ret = bus_register(&esoc_bus_type); + if (ret) { + pr_err("esoc bus register fail\n"); + return ret; + } + pr_debug("esoc bus registration done\n"); + return 0; +} + +subsys_initcall(esoc_init); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/esoc/esoc_dev.c b/drivers/esoc/esoc_dev.c new file mode 100644 index 000000000000..57a7fbf6baed --- /dev/null +++ b/drivers/esoc/esoc_dev.c @@ -0,0 +1,380 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ +#include +#include +#include +#include +#include +#include "esoc.h" + +/** + * struct esoc_udev: Userspace char interface + * @dev: interface device. + * @req_fifio: fifo for clink requests. + * @req_wait: signal availability of request from clink + * @req_fifo_lock: serialize access to req fifo + * @evt_fito: fifo for clink events + * @evt_wait: signal availablity of clink event + * @evt_fifo_lock: serialize access to event fifo + * @list: entry in esoc dev list. + * @clink: reference to contorl link + */ +struct esoc_udev { + struct device *dev; + struct kfifo req_fifo; + wait_queue_head_t req_wait; + spinlock_t req_fifo_lock; + struct kfifo evt_fifo; + wait_queue_head_t evt_wait; + spinlock_t evt_fifo_lock; + struct list_head list; + struct esoc_clink *clink; +}; + +/** + * struct esoc_uhandle: Userspace handle of esoc + * @esoc_clink: esoc control link. + * @eng: esoc engine for commands/ requests. + * @esoc_udev: user interface device. + */ +struct esoc_uhandle { + struct esoc_clink *esoc_clink; + struct esoc_eng eng; + struct esoc_udev *esoc_udev; +}; + +#define ESOC_MAX_MINOR 256 +#define ESOC_MAX_REQ 8 +#define ESOC_MAX_EVT 4 + +static LIST_HEAD(esoc_udev_list); +static DEFINE_SPINLOCK(esoc_udev_list_lock); +struct class *esoc_class; +static int esoc_major; + +static struct esoc_udev *get_free_esoc_udev(struct esoc_clink *esoc_clink) +{ + struct esoc_udev *esoc_udev; + int err; + + if (esoc_clink->id > ESOC_MAX_MINOR) { + pr_err("too many esoc devices\n"); + return ERR_PTR(-ENODEV); + } + esoc_udev = kzalloc(sizeof(*esoc_udev), GFP_KERNEL); + if (!esoc_udev) + return ERR_PTR(-ENOMEM); + err = kfifo_alloc(&esoc_udev->req_fifo, (sizeof(u32)) * ESOC_MAX_REQ, + GFP_KERNEL); + if (err) { + pr_err("unable to allocate request fifo for %s\n", + esoc_clink->name); + goto req_fifo_fail; + } + err = kfifo_alloc(&esoc_udev->req_fifo, (sizeof(u32)) * ESOC_MAX_EVT, + GFP_KERNEL); + if (err) { + pr_err("unable to allocate evt fifo for %s\n", + esoc_clink->name); + goto evt_fifo_fail; + } + init_waitqueue_head(&esoc_udev->req_wait); + init_waitqueue_head(&esoc_udev->evt_wait); + spin_lock_init(&esoc_udev->req_fifo_lock); + spin_lock_init(&esoc_udev->evt_fifo_lock); + esoc_udev->clink = esoc_clink; + spin_lock(&esoc_udev_list_lock); + list_add_tail(&esoc_udev->list, &esoc_udev_list); + spin_unlock(&esoc_udev_list_lock); + return esoc_udev; +evt_fifo_fail: + kfifo_free(&esoc_udev->req_fifo); +req_fifo_fail: + kfree(esoc_udev); + return ERR_PTR(-ENODEV); +} + +static void return_esoc_udev(struct esoc_udev *esoc_udev) +{ + spin_lock(&esoc_udev_list_lock); + list_del(&esoc_udev->list); + spin_unlock(&esoc_udev_list_lock); + kfifo_free(&esoc_udev->req_fifo); + kfifo_free(&esoc_udev->evt_fifo); + kfree(esoc_udev); +} + +static struct esoc_udev *esoc_udev_get_by_minor(unsigned index) +{ + struct esoc_udev *esoc_udev; + + spin_lock(&esoc_udev_list_lock); + list_for_each_entry(esoc_udev, &esoc_udev_list, list) { + if (esoc_udev->clink->id == index) + goto found; + } + esoc_udev = NULL; +found: + spin_unlock(&esoc_udev_list_lock); + return esoc_udev; +} + +void esoc_udev_handle_clink_req(enum esoc_req req, struct esoc_eng *eng) +{ + int err; + u32 clink_req; + struct esoc_clink *esoc_clink = eng->esoc_clink; + struct esoc_udev *esoc_udev = esoc_udev_get_by_minor(esoc_clink->id); + + if (!esoc_udev) + return; + clink_req = (u32)req; + err = kfifo_in_spinlocked(&esoc_udev->req_fifo, &clink_req, + sizeof(clink_req), + &esoc_udev->req_fifo_lock); + if (err != sizeof(clink_req)) { + pr_err("unable to queue request for %s\n", esoc_clink->name); + return; + } + wake_up_interruptible(&esoc_udev->req_wait); +} + +void esoc_udev_handle_clink_evt(enum esoc_evt evt, struct esoc_eng *eng) +{ + int err; + u32 clink_evt; + struct esoc_clink *esoc_clink = eng->esoc_clink; + struct esoc_udev *esoc_udev = esoc_udev_get_by_minor(esoc_clink->id); + if (!esoc_udev) + return; + clink_evt = (u32)evt; + err = kfifo_in_spinlocked(&esoc_udev->evt_fifo, &clink_evt, + sizeof(clink_evt), + &esoc_udev->evt_fifo_lock); + if (err != sizeof(clink_evt)) { + pr_err("unable to queue event for %s\n", esoc_clink->name); + return; + } + wake_up_interruptible(&esoc_udev->evt_wait); +} + +static long esoc_dev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err; + u32 esoc_cmd , status, req, evt; + struct esoc_uhandle *uhandle = file->private_data; + struct esoc_udev *esoc_udev = uhandle->esoc_udev; + struct esoc_clink *esoc_clink = uhandle->esoc_clink; + const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; + void __user *uarg = (void __user *)arg; + + switch (cmd) { + case ESOC_REG_REQ_ENG: + err = esoc_clink_register_req_eng(esoc_clink, &uhandle->eng); + if (err) + return err; + break; + case ESOC_REG_CMD_ENG: + err = esoc_clink_register_cmd_eng(esoc_clink, &uhandle->eng); + if (err) + return err; + break; + case ESOC_CMD_EXE: + if (esoc_clink->cmd_eng != &uhandle->eng) + return -EACCES; + get_user(esoc_cmd, (u32 __user *)arg); + return clink_ops->cmd_exe(esoc_cmd, esoc_clink); + case ESOC_WAIT_FOR_REQ: + if (esoc_clink->req_eng != &uhandle->eng) + return -EACCES; + err = wait_event_interruptible(esoc_udev->req_wait, + !kfifo_is_empty(&esoc_udev->req_fifo)); + if (!err) { + err = kfifo_out_spinlocked(&esoc_udev->req_fifo, &req, + sizeof(req), + &esoc_udev->req_fifo_lock); + if (err != sizeof(req)) { + pr_err("read from clink %s req q failed\n", + esoc_clink->name); + return -EIO; + } + put_user(req, (unsigned long __user *)uarg); + + } + return err; + break; + case ESOC_NOTIFY: + get_user(esoc_cmd, (u32 __user *)arg); + clink_ops->notify(esoc_cmd, esoc_clink); + break; + case ESOC_GET_STATUS: + err = clink_ops->get_status(&status, esoc_clink); + if (err) + return err; + put_user(status, (unsigned long __user *)uarg); + break; + case ESOC_WAIT_FOR_CRASH: + err = wait_event_interruptible(esoc_udev->evt_wait, + !kfifo_is_empty(&esoc_udev->evt_fifo)); + if (!err) { + err = kfifo_out_spinlocked(&esoc_udev->evt_fifo, &evt, + sizeof(evt), + &esoc_udev->evt_fifo_lock); + if (err != sizeof(evt)) { + pr_err("read from clink %s evt q failed\n", + esoc_clink->name); + return -EIO; + } + put_user(evt, (unsigned long __user *)uarg); + } + return err; + break; + default: + return -EINVAL; + }; + return 0; +} + +static int esoc_dev_open(struct inode *inode, struct file *file) +{ + struct esoc_uhandle *uhandle; + struct esoc_udev *esoc_udev; + struct esoc_clink *esoc_clink; + struct esoc_eng *eng; + unsigned int minor = iminor(inode); + + esoc_udev = esoc_udev_get_by_minor(minor); + esoc_clink = get_esoc_clink(esoc_udev->clink->id); + + uhandle = kzalloc(sizeof(*uhandle), GFP_KERNEL); + if (!uhandle) { + pr_err("failed to allocate memory for uhandle\n"); + put_esoc_clink(esoc_clink); + return -ENOMEM; + } + uhandle->esoc_udev = esoc_udev; + uhandle->esoc_clink = esoc_clink; + eng = &uhandle->eng; + eng->handle_clink_req = esoc_udev_handle_clink_req; + eng->handle_clink_evt = esoc_udev_handle_clink_evt; + file->private_data = uhandle; + return 0; +} + +static int esoc_dev_release(struct inode *inode, struct file *file) +{ + struct esoc_clink *esoc_clink; + struct esoc_uhandle *uhandle = file->private_data; + + esoc_clink = uhandle->esoc_clink; + put_esoc_clink(esoc_clink); + kfree(uhandle); + return 0; +} +static const struct file_operations esoc_dev_fops = { + .owner = THIS_MODULE, + .open = esoc_dev_open, + .unlocked_ioctl = esoc_dev_ioctl, + .release = esoc_dev_release, +}; + +int esoc_clink_add_device(struct device *dev, void *dummy) +{ + struct esoc_udev *esoc_udev; + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + + esoc_udev = get_free_esoc_udev(esoc_clink); + if (IS_ERR(esoc_udev)) + return PTR_ERR(esoc_udev); + esoc_udev->dev = device_create(esoc_class, &esoc_clink->dev, + MKDEV(esoc_major, esoc_clink->id), + esoc_clink, "esoc-%d", esoc_clink->id); + if (IS_ERR(esoc_udev->dev)) { + pr_err("failed to create user device\n"); + goto dev_err; + } + return 0; +dev_err: + return_esoc_udev(esoc_udev); + return -ENODEV; +} + +int esoc_clink_del_device(struct device *dev, void *dummy) +{ + struct esoc_udev *esoc_udev; + struct esoc_clink *esoc_clink = to_esoc_clink(dev); + + esoc_udev = esoc_udev_get_by_minor(esoc_clink->id); + if (!esoc_udev) + return 0; + return_esoc_udev(esoc_udev); + device_destroy(esoc_class, MKDEV(esoc_major, esoc_clink->id)); + return_esoc_udev(esoc_udev); + return 0; +} + +static int esoc_dev_notifier_call(struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return esoc_clink_add_device(dev, NULL); + case BUS_NOTIFY_DEL_DEVICE: + return esoc_clink_del_device(dev, NULL); + }; + return 0; +} + +static struct notifier_block esoc_dev_notifier = { + .notifier_call = esoc_dev_notifier_call, +}; + +int __init esoc_dev_init(void) +{ + int ret = 0; + esoc_class = class_create(THIS_MODULE, "esoc-dev"); + if (IS_ERR(esoc_class)) { + pr_err("coudn't create class"); + return PTR_ERR(esoc_class); + } + esoc_major = register_chrdev(0, "esoc", &esoc_dev_fops); + if (esoc_major < 0) { + pr_err("failed to allocate char dev\n"); + ret = esoc_major; + goto class_unreg; + } + ret = bus_register_notifier(&esoc_bus_type, &esoc_dev_notifier); + if (ret) + goto chrdev_unreg; + esoc_for_each_dev(NULL, esoc_clink_add_device); + return ret; +chrdev_unreg: + unregister_chrdev(esoc_major, "esoc"); +class_unreg: + class_destroy(esoc_class); + return 0; +} + +void __exit esoc_dev_exit(void) +{ + bus_unregister_notifier(&esoc_bus_type, &esoc_dev_notifier); + class_destroy(esoc_class); + unregister_chrdev(esoc_major, "esoc-dev"); +} + +MODULE_LICENSE("GPLv2"); +module_init(esoc_dev_init); +module_exit(esoc_dev_exit); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index d38d1a8fb875..faeee39301f1 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -114,6 +114,7 @@ header-y += elfcore.h header-y += epm_adc.h header-y += errno.h header-y += errqueue.h +header-y += esoc_ctrl.h header-y += ethtool.h header-y += eventpoll.h header-y += fadvise.h diff --git a/include/uapi/linux/esoc_ctrl.h b/include/uapi/linux/esoc_ctrl.h new file mode 100644 index 000000000000..8564d4f7db12 --- /dev/null +++ b/include/uapi/linux/esoc_ctrl.h @@ -0,0 +1,58 @@ +#ifndef _UAPI_ESOC_CTRL_H_ +#define _UAPI_ESOC_CTRL_H_ + +#define ESOC_CODE 0xCC + +#define ESOC_CMD_EXE _IOW(ESOC_CODE, 1, u32) +#define ESOC_WAIT_FOR_REQ _IOR(ESOC_CODE, 2, u32) +#define ESOC_NOTIFY _IOW(ESOC_CODE, 3, u32) +#define ESOC_GET_STATUS _IOR(ESOC_CODE, 4, u32) +#define ESOC_WAIT_FOR_CRASH _IOR(ESOC_CODE, 6, u32) +#define ESOC_REG_REQ_ENG _IO(ESOC_CODE, 7) +#define ESOC_REG_CMD_ENG _IO(ESOC_CODE, 8) + +enum esoc_evt { + ESOC_RUN_STATE = 0x1, + ESOC_UNEXPECTED_RESET, + ESOC_ERR_FATAL, + ESOC_IN_DEBUG, + ESOC_BOOT_FAIL, +}; + +enum esoc_cmd { + ESOC_PWR_ON = 1, + ESOC_PWR_OFF, + ESOC_RESET, + ESOC_PREPARE_DEBUG, + ESOC_EXE_DEBUG, + ESOC_EXIT_DEBUG, +}; + +enum esoc_notify { + ESOC_IMG_XFER_DONE = 1, + ESOC_IMG_XFER_RETRY, + ESOC_IMG_XFER_FAIL, + ESOC_UPGRADE_AVAILABLE, + ESOC_DEBUG_DONE, + ESOC_DEBUG_FAIL, +}; + +enum esoc_req { + ESOC_REQ_IMG = 1, + ESOC_REQ_DEBUG, +}; + +#ifdef __KERNEL__ +/** + * struct esoc_handle: Handle for clients of esoc + * @name: name of the external soc. + * @link: link of external soc. + * @id: id of external soc. + */ +struct esoc_handle { + const char *name; + const char *link; + unsigned int id; +}; +#endif +#endif