blob: 6a1334c0fe9e6e98efa3644ef0753051a199c478 [file] [log] [blame]
/*
* Marvell sulog driver
*
* Tzviel Lemberger <tzviel@marvell.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
* (c) 2010
*
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <asm/io.h>
#include <linux/debugfs.h>
#include <linux/usb.h>
#include <linux/timer.h>
/* TODO: find a proper solution to the 2 functions included from pxa182x_serial_debug.h */
extern struct debug_serial_direct_callbacks *pxa_register_debug_serial_streaming(void);
extern void pxa_unregister_debug_serial_streaming(void);
extern struct debug_serial_direct_callbacks *pxa_register_diag_serial_streaming(void);
extern void pxa_unregister_diag_serial_streaming(void);
//#include "pxa182x_serial_debug.h"
/*
* NOTE: send_buf_func called from an atomic context (timer callback).
* You are responsible to check wether the send_buf_func can be called
* from atomic context.
*/
static int (*send_buf_func)(const unsigned char *buf, int count);
/* Sulog macros */
#define PR_ERR(fmt, ...) \
pr_err("misc sulog: %s(%d): " \
fmt, __func__, __LINE__ , ##__VA_ARGS__)
#define DEV_ERR(dev, fmt, arg...) \
pr_err("%s %s: %s(%d): " fmt, dev_driver_string(dev), \
dev_name(dev), __func__, __LINE__, ## arg)
#define DEV_INFO(dev, fmt, arg...) \
pr_info("%s %s: %s(%d): " fmt, dev_driver_string(dev), \
dev_name(dev), __func__, __LINE__, ## arg)
/*#define SULOG_DBG 1*/
#ifdef SULOG_DBG
#define DEV_DBG(dev, fmt, arg...) \
pr_debug("%s %s: %s(%d): " fmt, dev_driver_string(dev), \
dev_name(dev), __func__, __LINE__, ## arg)
#else
#define DEV_DBG(dev, fmt, arg...)
#endif
#define SULOG_ENABLE 1
#define SULOG_ENABLE2 2 /* sulog thru diag port */
#define SULOG_DISABLE 0
/*Default AP DDR memory allocation */
#define AP_DDR_DEFAULT_SIZE 0x7f000
#define NUM_OF_BUFFERS 2
/*Default high water mark level */
#define AP_HIGHWATERMARK 0x1000
/*Comm maximum DDR size*/
#define CP_MAX_DDR_SIZE 0x100000
/*Register offsets*/
#define RIPC_STATUS_REG 0x0
#define RIPC_APPS_INT_REG 0x4
#define RIPC_SG_INT_REG 0x8
#define RIPC_GB_INT_REG 0xC
#define RIPC_APPS_INFO_REG 0x14
#define RIPC_SG_INFO_REG 0x18
#define RIPC_GB_INFO_REG 0x1C
/*Two base addresses for RIPC - registers and Clk*/
#define RIPC_BASE_ADDRESSES 0x2
#define RIPC_APPS_CLK_REG 0x0
#define RIPC_CLR_INTRRUPT_BIT 0x1
#define TARGET_SD 0x0
#define TARGET_USB 0x1
/* Used to ioremap large enough space
in case of address outside of expected CP memory */
#define COMM_DSP_MAX_BUFFER_SIZE 0x200000
/* Used to test if requseted buffer size is not too large */
#define COMM_DSP_DOUBLE_BUFFER_SIZE 0x100000
/*Static variables*/
static struct sulog_struct *sulog_str;
static int sulog_thru_diag;
volatile static int glb_cnt;
/*General Structures*/
struct sulog_resource {
unsigned int *ripc_base_address;
unsigned char *comm_strt_addr;
unsigned int comm_addr_offst;
unsigned char *comm_base_addr;
unsigned int ripc_size;
int sulog_irq_resource;
char sulog_irq_name[20];
struct resource *resource;
unsigned int remapped;
};
struct sulog_app_mem {
unsigned int *sulog_app_ptr;
unsigned int *sulog_app_buf[NUM_OF_BUFFERS];
unsigned int sulog_allocbuf_size;
unsigned int app_buff_size;
unsigned int high_watermark;
unsigned int sulog_target_type;
};
struct sulog_statistic {
unsigned int irq_count;
unsigned int tot_data_sum;
unsigned int packets_pass;
unsigned int packets_dropped;
};
struct sulog_struct {
struct device *dev;
struct sulog_resource res[RIPC_BASE_ADDRESSES];
struct sulog_app_mem app_mem;
unsigned int sulog_driver_status;
struct tasklet_struct sulog_copy_tasklet;
struct workqueue_struct *sulog_workqueue_ptr;
struct work_struct sulog_workstruct;
struct sulog_statistic sulog_statistic;
wait_queue_head_t read_queue;
};
/*Function declerations*/
static void sulog_comm_map(struct work_struct *work);
static void sulog_copy_tasklet(unsigned long data);
static int sulog_driver_enable(void);
static int sulog_driver_disable(void);
static int sulog_change_status(unsigned int input);
static ssize_t sulog_driver_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t sulog_driver_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count);
static ssize_t sulog_watermark_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t sulog_watermark_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count);
static ssize_t sulog_buff_size_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t sulog_buff_size_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count);
static ssize_t sulog_target_type_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t sulog_target_type_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count);
static ssize_t sulog_statistics_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t sulog_statistics_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count);
static irqreturn_t sulog_irq_handler(int this_irq, void *dev_id);
static int sulog_open(struct inode *inode, struct file *file);
static ssize_t sulog_read(struct file *filp, char __user *buf,
size_t count, loff_t *unused);
/*Static structures*/
static const struct file_operations sulog_fops = {
.owner = THIS_MODULE,
.open = sulog_open,
.read = sulog_read,
};
static struct miscdevice sulog_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sulog_driver",
.fops = &sulog_fops,
};
static DEVICE_ATTR(status, 0644, sulog_driver_show, sulog_driver_store);
static DEVICE_ATTR(watermark, 0644, sulog_watermark_show,
sulog_watermark_store);
static DEVICE_ATTR(buff_size, 0644, sulog_buff_size_show,
sulog_buff_size_store);
static DEVICE_ATTR(target_type, 0644, sulog_target_type_show,
sulog_target_type_store);
static DEVICE_ATTR(statistics, 0644, sulog_statistics_show,
sulog_statistics_store);
static const struct device_attribute *dev_attr_arr[] = {
&dev_attr_status,
&dev_attr_watermark,
&dev_attr_buff_size,
&dev_attr_target_type,
&dev_attr_statistics,
};
/*Static functions*/
static void sulog_comm_map(struct work_struct *work)
{
/* The workqueue is responsible to deliver the tasklet with a valid address to work with.
* if physical address is inside kernel memory - translate it to a virtual address and continue.
* if physical address is outside of kernel memory - remap it first. */
if (sulog_str->res[0].comm_strt_addr && sulog_str->res[0].remapped)
iounmap(sulog_str->res[0].comm_strt_addr);
if (pfn_valid(__phys_to_pfn((unsigned int)sulog_str->res[0].comm_base_addr))) {
/* Physical address inside kernel memory */
sulog_str->res[0].remapped = 0;
sulog_str->res[0].comm_strt_addr =
phys_to_virt((unsigned int)sulog_str->res[0].comm_base_addr);
} else {
/* Physical address outside of kernel memory */
sulog_str->res[0].remapped = 1;
sulog_str->res[0].comm_strt_addr =
ioremap((unsigned int)sulog_str->res[0].comm_base_addr,
COMM_DSP_MAX_BUFFER_SIZE);
}
DEV_DBG(sulog_str->dev, "Comm memory remapped, triggering tasklet\n");
tasklet_schedule(&sulog_str->sulog_copy_tasklet);
return;
}
static void sulog_copy_tasklet(unsigned long data)
{
unsigned int data_size;
unsigned char *resource_p;
DEV_DBG(sulog_str->dev,
"Start copying information from SHMEM to Ap DDR\n");
data_size = (unsigned int) sulog_str->res[0].ripc_size;
resource_p = (sulog_str->res[0].comm_strt_addr +
sulog_str->res[0].comm_addr_offst);
if (sulog_str->app_mem.sulog_target_type == TARGET_USB) {
/*Output is usb: Copying data from SHMEM to kernel memory */
if (!send_buf_func(resource_p, data_size))
sulog_str->sulog_statistic.packets_pass++;
else
sulog_str->sulog_statistic.packets_dropped++;
} else { /* TARGET SD */
/* Output is sdcard: Copying data from SHMEM to AP DDR*/
if (unlikely
((sulog_str->app_mem.app_buff_size + data_size) >
sulog_str->app_mem.sulog_allocbuf_size)) {
/*We will exceed the maximum allocation size */
DEV_INFO(sulog_str->dev,
"Data exceed maximum size, possible data lost\n");
/*Allocate all remaining space for copy */
data_size =
sulog_str->app_mem.sulog_allocbuf_size -
sulog_str->app_mem.app_buff_size;
/* We missed some data. mark as lost packet */
sulog_str->sulog_statistic.packets_dropped++;
}
memcpy((unsigned char *)
((unsigned int)sulog_str->app_mem.sulog_app_ptr +
(unsigned int)sulog_str->app_mem.app_buff_size),
resource_p,
data_size);
sulog_str->app_mem.app_buff_size += data_size;
if (sulog_str->app_mem.app_buff_size >
sulog_str->app_mem.high_watermark) {
/*Allow user space to access the data */
disable_irq(sulog_str->res[0].sulog_irq_resource);
DEV_DBG(sulog_str->dev, "waking up read to user space\n\n");
wake_up(&sulog_str->read_queue);
}
}
return;
}
static int sulog_driver_enable(void)
{
int i;
struct debug_serial_direct_callbacks *callback_ptr;
if (sulog_str->app_mem.sulog_target_type == TARGET_SD) {
/*We'll allocate double buffer*/
for (i = 0; i < NUM_OF_BUFFERS; i++) {
sulog_str->app_mem.sulog_app_buf[i] =
vmalloc((unsigned long)
sulog_str->app_mem.sulog_allocbuf_size);
if (sulog_str->app_mem.sulog_app_buf[i] == NULL) {
PR_ERR("Failed to allocate memory!\n");
return -ENOMEM;
}
}
sulog_str->app_mem.sulog_app_ptr =
sulog_str->app_mem.sulog_app_buf[glb_cnt];
}
/*Set RIPC clk to 1 - JIRA NEZHA3-90*/
__raw_writel(0x1, sulog_str->res[1].ripc_base_address);
if (sulog_str->app_mem.sulog_target_type == TARGET_USB) {
if (sulog_thru_diag) {
pr_info("Sulog is sent through diag port!\n");
callback_ptr = pxa_register_diag_serial_streaming();
} else {
callback_ptr = pxa_register_debug_serial_streaming();
}
if (!callback_ptr) {
pr_err("callback function pointer is %p\n", callback_ptr);
return 0;
}
send_buf_func = callback_ptr->send_buff_cb;
if (!send_buf_func)
return 0;
}
/* only enable tasklet when it's not enabled */
if (atomic_read(&sulog_str->sulog_copy_tasklet.count))
tasklet_enable(&sulog_str->sulog_copy_tasklet);
/*Init statistic values*/
sulog_str->sulog_statistic.irq_count = 0;
sulog_str->sulog_statistic.packets_dropped = 0;
sulog_str->sulog_statistic.packets_pass = 0;
sulog_str->sulog_statistic.tot_data_sum = 0;
/*Enable IRQ */
if (sulog_thru_diag)
sulog_str->sulog_driver_status = 2;
else
sulog_str->sulog_driver_status = 1;
enable_irq(sulog_str->res[0].sulog_irq_resource);
return 0;
}
static int sulog_driver_disable(void)
{
int i;
struct irq_desc *desc;
/* orignal: Disable IRQ */
/* new: don't disable irq as ripc0 wakeup irq share
* the same irq line with sulog ripc notification irq
*/
sulog_str->sulog_driver_status = 0;
desc = irq_to_desc(sulog_str->res[0].sulog_irq_resource);
if (!(desc && desc->action && desc->action->next)) {
DEV_INFO(sulog_str->dev, "disable sulog irq\n");
disable_irq(sulog_str->res[0].sulog_irq_resource);
}
/* disable tasklet when it's enabled */
if (atomic_read(&sulog_str->sulog_copy_tasklet.count) == 0)
tasklet_disable(&sulog_str->sulog_copy_tasklet);
if (sulog_str->app_mem.sulog_target_type == TARGET_USB) {
if (sulog_thru_diag) {
pr_info("Disable Sulog through diag port!\n");
pxa_unregister_diag_serial_streaming();
} else {
pxa_unregister_debug_serial_streaming();
}
}
/*Set trace counter back to inital value */
DEV_DBG(sulog_str->dev, "disable data copy tasklet\n");
if (sulog_str->app_mem.sulog_target_type == TARGET_SD) {
/* free 2 buffers */
for (i = 0; i < NUM_OF_BUFFERS; i++)
vfree(sulog_str->app_mem.sulog_app_buf[i]);
}
/* clr pending ripc event if any */
if (__raw_readl((unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG)) & 1) {
printk_ratelimited("%s: clr sulog APPS INT\n", __func__);
/*Clear LSB to mark interrupt as handled*/
__raw_writel(0, (unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG));
}
DEV_INFO(sulog_str->dev, "Sulog driver disabled\n");
return 0;
}
static int sulog_change_status(unsigned int input)
{
int ret_val = 0;
DEV_INFO(sulog_str->dev, "Changing sulog driver status. Selected status 0x%x\n", input);
switch (input) {
case (SULOG_ENABLE2):
sulog_thru_diag = 1;
/* fall through */
case (SULOG_ENABLE):
ret_val = sulog_driver_enable();
break;
case (SULOG_DISABLE):
ret_val = sulog_driver_disable();
sulog_thru_diag = 0;
break;
default:
/*User request is not valid */
DEV_INFO(sulog_str->dev,
"Input value is not valid: %d\n", input);
ret_val = -ESRCH;
}
return ret_val;
}
static ssize_t sulog_driver_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "Sulog driver status is 0x%x\n",
sulog_str->sulog_driver_status);
}
static ssize_t sulog_driver_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count)
{
unsigned int input, ret_val;
if (sscanf(buff, "%x", &input) < 1) {
PR_ERR("Storing information failed\n");
return count;
}
if (input != SULOG_ENABLE2)
input = (input == 0) ? 0 : 1;
if (sulog_str->sulog_driver_status == input)
return count;
ret_val = sulog_change_status(input);
if (ret_val)
PR_ERR("Sulog driver failed with return code 0x%x\n",
ret_val);
else
DEV_DBG(sulog_str->dev, "Sulog driver status is 0x%x\n",
ret_val);
return count;
}
static ssize_t sulog_watermark_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf,
"Sulog Hightwater mark buffer size is 0x%x\n",
sulog_str->app_mem.high_watermark);
}
static ssize_t sulog_watermark_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count)
{
unsigned int input;
if (sscanf(buff, "0x%x", &input) < 1) {
PR_ERR("Reading watermark size from user failed\n");
return count;
}
if (sulog_str->app_mem.sulog_allocbuf_size < input)
PR_ERR("Requested Watermark level is higher then memory allocation, update it too\n");
sulog_str->app_mem.high_watermark = input;
DEV_DBG(sulog_str->dev,
"High watermark was set to 0x%x\n", input);
return count;
}
static ssize_t sulog_buff_size_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "Sulog App buffer size is 0x%08x\n",
sulog_str->app_mem.sulog_allocbuf_size);
}
static ssize_t sulog_buff_size_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count)
{
unsigned int input;
if (sulog_str->sulog_driver_status) {
PR_ERR("Cannot change memory allocation while driver is running\n");
return count;
}
if (sscanf(buff, "0x%x", &input) < 1) {
PR_ERR("Reading buffer size input from user failed\n");
return count;
}
if (sulog_str->app_mem.high_watermark > input)
PR_ERR("Current High watermark is larger then requested memory allocation, update it too\n");
sulog_str->app_mem.sulog_allocbuf_size = input;
DEV_DBG(sulog_str->dev,
"App memory allocation was set to 0x%x\n", input);
return count;
}
static ssize_t sulog_target_type_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
if (sulog_str->app_mem.sulog_target_type == TARGET_SD)
return sprintf(buf, "Sulog target type is SD\n");
else
return sprintf(buf, "Sulog target type is USB\n");
}
static ssize_t sulog_target_type_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count)
{
unsigned int input;
if (sscanf(buff, "%x", &input) < 1) {
PR_ERR("Storing information failed\n");
return count;
}
if (sulog_str->sulog_driver_status) {
PR_ERR("Cannot change target type while driver is running\n");
return count;
}
if (input != TARGET_SD && input != TARGET_USB) {
PR_ERR("Wrong input value.\n\
0 - Output to SDcard\n\
1 - Output to usb\n");
return count;
}
sulog_str->app_mem.sulog_target_type = input;
if (sulog_str->app_mem.sulog_target_type == TARGET_SD)
DEV_INFO(sulog_str->dev, "Sulog target type is SD\n");
else
DEV_INFO(sulog_str->dev, "Sulog target type is USB\n");
return count;
}
static ssize_t sulog_statistics_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "Sulog driver statistics:\n\
Number of interrupts received - %d\n\
Amount of data passed in bytes 0x%x\n\
Number of packates passed - %d\n\
Number of packates dropped - %d\n", \
sulog_str->sulog_statistic.irq_count, \
sulog_str->sulog_statistic.tot_data_sum, \
sulog_str->sulog_statistic.packets_pass, \
sulog_str->sulog_statistic.packets_dropped);
}
static ssize_t sulog_statistics_store(struct device *dev,
struct device_attribute *attr,
const char *buff, size_t count)
{
return 0;
}
static int sulog_open(struct inode *inode, struct file *file)
{
DEV_DBG(sulog_str->dev, "sulog open command triggered\n");
return 0;
}
static ssize_t sulog_read(struct file *filp, char __user *buf,
size_t count, loff_t *unused)
{
/*Todo - check there is enough space for write*/
int ret_val;
size_t data_count;
ret_val = wait_event_interruptible(sulog_str->read_queue,
(sulog_str->app_mem.app_buff_size >
sulog_str->app_mem.high_watermark));
if (ret_val == -ERESTARTSYS)
return 0;
if (likely(count >= sulog_str->app_mem.app_buff_size)) {
data_count = sulog_str->app_mem.app_buff_size;
} else {
data_count = count;
DEV_INFO(sulog_str->dev, "User space didn't allocate enough read space, possible data lost\n");
}
/*Moving to other apps buffer*/
if (glb_cnt == 0) {
sulog_str->app_mem.sulog_app_ptr =
sulog_str->app_mem.sulog_app_buf[glb_cnt+1];
} else {
sulog_str->app_mem.sulog_app_ptr =
sulog_str->app_mem.sulog_app_buf[glb_cnt-1];
}
/*AP memory is cyclic*/
sulog_str->app_mem.app_buff_size = 0;
enable_irq(sulog_str->res[0].sulog_irq_resource);
ret_val = copy_to_user(buf,
sulog_str->app_mem.sulog_app_buf[glb_cnt], data_count);
/*Moving to other apps buffer*/
glb_cnt = ((glb_cnt+1) % 2);
if (unlikely(ret_val)) {
DEV_ERR(sulog_str->dev, "copy to user failed\n\n");
sulog_str->sulog_statistic.packets_dropped++;
return -EFAULT;
} else {
sulog_str->sulog_statistic.packets_pass++;
}
return data_count;
}
static irqreturn_t sulog_irq_handler(int this_irq, void *dev_id)
{
unsigned char *tmp_start_addr;
/*In order to prevent race conditions */
if (unlikely(!sulog_str->sulog_driver_status)) {
if (__raw_readl((unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG)) & 1) {
printk_ratelimited(KERN_DEBUG "%s: clr sulog APPS INT\n", __func__);
/*Clear LSB to mark interrupt as handled*/
__raw_writel(0, (unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG));
}
return IRQ_HANDLED;
}
if (unlikely(!(__raw_readl((unsigned int *)
(((unsigned int)sulog_str->
res[0].ripc_base_address +
RIPC_APPS_INFO_REG)))))) {
/*Clear LSB to mark interrupt as handled*/
__raw_writel(0, (unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG));
return IRQ_HANDLED;
}
sulog_str->res[0].ripc_size =
__raw_readl((unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG));
sulog_str->res[0].ripc_size = (sulog_str->res[0].ripc_size >> 1) * 4;
/* Check if given size is reasonable */
BUG_ON(sulog_str->res[0].ripc_size > COMM_DSP_DOUBLE_BUFFER_SIZE);
tmp_start_addr = (unsigned char *)__raw_readl((unsigned int *)
(((unsigned int)sulog_str->
res[0].ripc_base_address +
RIPC_APPS_INFO_REG)));
/* Check if first run or if start address is less than base*/
if (unlikely((!sulog_str->res[0].comm_strt_addr)) ||
(tmp_start_addr < sulog_str->res[0].comm_base_addr)) {
/* First run - go to workqueue to ioremap the whole comm buffer, then continue
or if bug, base address id less than last round, got to remap again*/
sulog_str->res[0].comm_base_addr = tmp_start_addr;
queue_work(sulog_str->sulog_workqueue_ptr,
&sulog_str->sulog_workstruct);
} else {
/* Not first run - get offset from buffer and continue */
sulog_str->res[0].comm_addr_offst =
tmp_start_addr - sulog_str->res[0].comm_base_addr;
if (likely((sulog_str->res[0].comm_addr_offst >= 0) && (sulog_str->res[0].ripc_size > 0))) {
tasklet_schedule(&sulog_str->sulog_copy_tasklet);
sulog_str->sulog_statistic.tot_data_sum +=
sulog_str->res[0].ripc_size;
} else
/*We shouldn't reach here, only happen if Current address
is smaller then base address*/
sulog_str->sulog_statistic.packets_dropped++;
}
/*Clear LSB to mark interrupt as handled*/
__raw_writel(0, (unsigned int *)
((unsigned int)sulog_str->res[0].ripc_base_address +
RIPC_APPS_INT_REG));
sulog_str->sulog_statistic.irq_count++;
return IRQ_HANDLED;
}
static int sulog_probe(struct platform_device *pdev)
{
int ret_val, irq, fs_cnt, i = 0;
unsigned long flags;
sulog_str = kzalloc(sizeof(struct sulog_struct), GFP_KERNEL);
if (sulog_str == NULL) {
PR_ERR("Failed to allocate memory!\n");
ret_val = -ENOMEM;
goto err_mem;
}
ret_val = misc_register(&sulog_miscdev);
if (ret_val != 0) {
PR_ERR("misc_register failed\n");
goto err_misc_register;
}
sulog_str->dev = sulog_miscdev.this_device;
/* Submit resourses */
for (i = 0; i < RIPC_BASE_ADDRESSES; i++) {
sulog_str->res[i].resource = platform_get_resource(
pdev, IORESOURCE_MEM, i);
if (sulog_str->res[i].resource == NULL) {
PR_ERR("Failed to receive driver resources\n");
ret_val = -ENOMEM;
goto err_malloc;
}
#if 0
if (!request_mem_region(
sulog_str->res[i].resource->start,
resource_size(sulog_str->res[i].resource),
sulog_str->res[i].resource->name)) {
ret_val = -ENOMEM;
goto mem_reg;
}
#endif
sulog_str->res[i].ripc_base_address = ioremap(
sulog_str->res[i].resource->start,
resource_size(sulog_str->res[i].resource));
if (sulog_str->res[i].ripc_base_address == NULL) {
DEV_ERR(sulog_str->dev,
"failed to get platform register values!\n");
ret_val = -ENXIO;
goto err_malloc;
}
}
/*Set default values */
sulog_str->app_mem.sulog_allocbuf_size = AP_DDR_DEFAULT_SIZE;
sulog_str->app_mem.high_watermark = AP_HIGHWATERMARK;
sulog_str->app_mem.app_buff_size = 0;
sulog_str->sulog_driver_status = 0;
sulog_str->app_mem.sulog_target_type = TARGET_USB;
platform_set_drvdata(pdev, sulog_str);
/*Submit IRQ */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
DEV_ERR(sulog_str->dev, "failed to get irq!\n");
ret_val = -ENODEV;
goto err_malloc;
}
local_irq_save(flags);
ret_val = request_irq(irq, sulog_irq_handler,
/* IRQF_DISABLED | */IRQF_SHARED, pdev->name, sulog_str);
if (ret_val) {
DEV_ERR(sulog_str->dev, "failed to request irq!\n");
local_irq_restore(flags);
goto err_malloc;
}
disable_irq(irq);
local_irq_restore(flags);
/*Todo-add irq name*/
sulog_str->res[0].sulog_irq_resource = irq;
/*Todo - register on DVFM */
/* Create sysfs files */
for (fs_cnt = 0; fs_cnt < ARRAY_SIZE(dev_attr_arr); fs_cnt++) {
ret_val = device_create_file(sulog_str->dev,
dev_attr_arr[fs_cnt]);
if (ret_val < 0) {
DEV_ERR(sulog_str->dev, "failed to create sysfs!\n");
goto err_sysfs;
}
}
init_waitqueue_head(&sulog_str->read_queue);
/*Init workqueue*/
sulog_str->sulog_workqueue_ptr = create_workqueue("sulog_workqueue");
if (!sulog_str->sulog_workqueue_ptr) {
DEV_ERR(sulog_str->dev, "create workqueue error\n");
ret_val = -ENOMEM;
goto err_sysfs;
}
INIT_WORK(&sulog_str->sulog_workstruct, sulog_comm_map);
tasklet_init(&sulog_str->sulog_copy_tasklet,
sulog_copy_tasklet, 0);
DEV_INFO(sulog_str->dev, "sulog_driver_init finished successfully\n");
return 0;
err_sysfs:
for (fs_cnt--; fs_cnt >= 0; fs_cnt--)
device_remove_file(sulog_str->dev,
dev_attr_arr[fs_cnt]);
mem_reg:
for (; i >= 0 ; i--)
release_mem_region(sulog_str->res[i].resource->start,
resource_size(sulog_str->res[i].resource));
err_malloc:
kfree(sulog_str);
if (i-- > 0)
goto mem_reg;
err_mem:
misc_deregister(&sulog_miscdev);
err_misc_register:
PR_ERR("dbg_macro probe error %d\n", ret_val);
return ret_val;
}
static int sulog_remove(struct platform_device *pdev)
{
int i, fs_cnt = ARRAY_SIZE(dev_attr_arr);
disable_irq(sulog_str->res[0].sulog_irq_resource);
destroy_workqueue(sulog_str->sulog_workqueue_ptr);
if (sulog_str->res[0].comm_strt_addr && sulog_str->res[0].remapped)
iounmap(sulog_str->res[0].comm_strt_addr);
/* free 2 buffers */
for (i = 0; i < NUM_OF_BUFFERS; i++)
vfree(sulog_str->app_mem.sulog_app_buf[i]);
for (fs_cnt--; fs_cnt >= 0; fs_cnt--)
device_remove_file(sulog_str->dev,
dev_attr_arr[fs_cnt]);
for (i = 0; i < RIPC_BASE_ADDRESSES; i++) {
release_mem_region(sulog_str->res[i].resource->start,
resource_size(sulog_str->res[i].resource));
}
kfree(sulog_str);
misc_deregister(&sulog_miscdev);
sulog_miscdev.minor = MISC_DYNAMIC_MINOR;
DEV_INFO(sulog_str->dev,
"Sulog remove completed\n");
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id sulog_of_match[] = {
{.compatible = "mrvl,mmp-sulog",},
{},
};
MODULE_DEVICE_TABLE(of, sulog_of_match);
#endif
static struct platform_driver sulog_driver = {
.probe = sulog_probe,
.remove = sulog_remove,
.driver = {
.name = "mmp-sulog",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = sulog_of_match,
#endif
},
};
static int __init sulog_init(void)
{
return platform_driver_register(&sulog_driver);
}
static void __exit sulog_exit(void)
{
platform_driver_unregister(&sulog_driver);
}
module_init(sulog_init);
module_exit(sulog_exit);
MODULE_AUTHOR("Tzviel Lemberger <tzviel@marvell.com>");
MODULE_DESCRIPTION("Super log driver");
MODULE_LICENSE("GPL");