| /* |
| * 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"); |