mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-09-21 03:43:03 +00:00
esoc: Add external soc control framework
External slave soc can be powered on/off and monitored for power states and crash events. Change-Id: I9c3317f798b204b754d612a3115dcd71935b5ef2 Signed-off-by: Hanumant Singh <hanumant@codeaurora.org>
This commit is contained in:
parent
40b27baaea
commit
5ad7ffa13d
|
@ -114,6 +114,8 @@ source "drivers/edac/Kconfig"
|
|||
|
||||
source "drivers/rtc/Kconfig"
|
||||
|
||||
source "drivers/esoc/Kconfig"
|
||||
|
||||
source "drivers/dma/Kconfig"
|
||||
|
||||
source "drivers/dca/Kconfig"
|
||||
|
|
|
@ -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/
|
||||
|
|
28
drivers/esoc/Kconfig
Normal file
28
drivers/esoc/Kconfig
Normal file
|
@ -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
|
||||
|
6
drivers/esoc/Makefile
Normal file
6
drivers/esoc/Makefile
Normal file
|
@ -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
|
||||
|
150
drivers/esoc/esoc.h
Normal file
150
drivers/esoc/esoc.h
Normal file
|
@ -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 <linux/cdev.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/esoc_ctrl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <mach/subsystem_notif.h>
|
||||
#include <mach/subsystem_restart.h>
|
||||
|
||||
#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
|
336
drivers/esoc/esoc_bus.c
Normal file
336
drivers/esoc/esoc_bus.c
Normal file
|
@ -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 <linux/idr.h>
|
||||
#include <linux/slab.h>
|
||||
#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");
|
380
drivers/esoc/esoc_dev.c
Normal file
380
drivers/esoc/esoc_dev.c
Normal file
|
@ -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 <linux/kfifo.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
#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);
|
|
@ -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
|
||||
|
|
58
include/uapi/linux/esoc_ctrl.h
Normal file
58
include/uapi/linux/esoc_ctrl.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue