summaryrefslogtreecommitdiff
path: root/drivers/sipc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sipc')
-rw-r--r--drivers/sipc/Kconfig69
-rw-r--r--drivers/sipc/Makefile16
-rw-r--r--drivers/sipc/dual_sim_plug.c543
-rw-r--r--drivers/sipc/sblock.c880
-rw-r--r--drivers/sipc/sblock.h103
-rw-r--r--drivers/sipc/sbuf.c651
-rw-r--r--drivers/sipc/sbuf.h82
-rw-r--r--drivers/sipc/seth.c1153
-rw-r--r--drivers/sipc/simple_events.h89
-rw-r--r--drivers/sipc/sipc.c488
-rw-r--r--drivers/sipc/sipc_debugfs.c45
-rw-r--r--drivers/sipc/smem.c180
-rw-r--r--drivers/sipc/smsg.c526
-rw-r--r--drivers/sipc/spipe.c336
-rw-r--r--drivers/sipc/spool.c260
-rw-r--r--drivers/sipc/stty.c412
16 files changed, 5833 insertions, 0 deletions
diff --git a/drivers/sipc/Kconfig b/drivers/sipc/Kconfig
new file mode 100644
index 00000000..0a47fb72
--- /dev/null
+++ b/drivers/sipc/Kconfig
@@ -0,0 +1,69 @@
+
+menu "SIPC modules"
+
+config SIPC
+ bool "Sprd IPC"
+ default n
+ help
+ SIPC is a module for spreadtrum AP/CP communicaiton system.
+
+config SIPC_SPIPE
+ bool "sprd pipe driver based on SBUF"
+ default n
+ depends on SIPC
+ help
+ This driver is a pipe driver base on SBUF, which create
+ general pipes between AP & CP.
+
+config SIPC_SPOOL
+ bool "sprd pool driver based on SBLOCK"
+ default n
+ depends on SIPC
+ help
+ This driver is a pool driver base on SBLOCK, which create
+ general pools between AP & CP.
+
+config SIPC_SETH
+ bool "Sprd Ethernet driver"
+ default n
+ depends on SIPC
+ help
+ This driver supports the Spreadtrum Ethernet based on share
+ memory. Say Y here if you want to use it.
+
+config SETH_OPT
+ bool "Enable performance optimization for SETH (2 Bytes reserved & US gather)"
+ default n
+ depends on SIPC_SETH
+ help
+ Starting from SharkL, the Seth driver is optimized for data thransfer
+ performance, but that is not compatible with former products
+ like TShark. Enable this configuration, IF the modem end surports
+ these optimizations: 2 Bytes reserved in sblock field and packets gathering
+ in upstreaming direction.
+
+config SETH_GRO_DISABLE
+ bool "Disable the gro function in seth"
+ default n
+ depends on SIPC_SETH
+ help
+ GRO function is incompatible with the samsung's GrandPrime, this will make
+ it happen kernel panic and IPV6 download too low. Enable this configuration
+ will let us to bypass these problems temporarily.
+
+config SIPC_STTY
+ bool "Sprd Stty driver"
+ default n
+ depends on SIPC
+ help
+ This driver supports the Spreadtrum Ethernet based on share
+ memory. Say Y here if you want to use it.
+
+config SIPC_DUAL_SIM_PLUG
+ bool "Sprd Dual sim plug driver"
+ default n
+ depends on SIPC
+ help
+ This driver supports the Spreadtrum dual sim plug. Say Y here if you want to use it.
+
+endmenu
diff --git a/drivers/sipc/Makefile b/drivers/sipc/Makefile
new file mode 100644
index 00000000..c92ad54b
--- /dev/null
+++ b/drivers/sipc/Makefile
@@ -0,0 +1,16 @@
+
+ifeq ($(CONFIG_OF), y)
+obj-$(CONFIG_SIPC) += sipc.o smsg.o smem.o sbuf.o sblock.o sipc_debugfs.o
+else
+obj-$(CONFIG_SIPC) += smsg.o smem.o sbuf.o sblock.o sipc_debugfs.o
+endif
+
+obj-$(CONFIG_SIPC_SPIPE) += spipe.o
+
+obj-$(CONFIG_SIPC_SETH) += seth.o
+
+obj-$(CONFIG_SIPC_SPOOL) += spool.o
+
+obj-$(CONFIG_SIPC_STTY) += stty.o
+
+obj-$(CONFIG_SIPC_DUAL_SIM_PLUG) += dual_sim_plug.o
diff --git a/drivers/sipc/dual_sim_plug.c b/drivers/sipc/dual_sim_plug.c
new file mode 100644
index 00000000..f7629ff9
--- /dev/null
+++ b/drivers/sipc/dual_sim_plug.c
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#endif
+
+#include <linux/sipc.h>
+
+#include <linux/dual_sim_plug.h>
+
+#define DELAY_COUNT 1000
+
+#define VALUE_SIM1_PLUGIN 0x11
+#define VALUE_SIM1_PLUGOUT 0x10
+#define VALUE_SIM2_PLUGIN 0x21
+#define VALUE_SIM2_PLUGOUT 0x20
+
+
+#define SMSG_SIM_PLUG 0xA5A5
+
+int flag_low_count_sim1 = 0;
+int flag_high_count_sim1 = 0;
+
+int flag_low_count_sim2 = 0;
+int flag_high_count_sim2 = 0;
+
+struct dual_sim_plug_device{
+ struct dual_sim_plug_init_data *init;
+ struct task_struct *th1;
+ int irq_cd1;
+ int irq_cd2;
+ unsigned int sim1_detect_gpio;
+ unsigned int sim2_detect_gpio;
+};
+
+static irqreturn_t sim1_irq_cd(int irq, void *dev_id)
+{
+ struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)dev_id;
+
+ struct smsg msend;
+ int flag = 0;
+
+ //printk(KERN_INFO "Enter sim1_irq_cd\n");
+
+ flag = gpio_get_value(dual_sim_plug->sim1_detect_gpio);
+
+ //printk(KERN_INFO "Enter sim1_irq_cd,flag = %d\n",flag);
+
+ if(flag == 0)
+ {
+ flag_low_count_sim1++;
+ flag_high_count_sim1 = 0;
+ }
+ else
+ {
+ flag_high_count_sim1++;
+ flag_low_count_sim1= 0;
+ }
+
+ if(flag_low_count_sim1 >= DELAY_COUNT)
+ {
+ flag_low_count_sim1 = 0;
+ irq_set_irq_type(irq,IRQF_TRIGGER_HIGH);
+ printk(KERN_INFO "Enter sim1_irq_cd,flag = low\n");
+ smsg_set(&msend,dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM1_PLUGIN);//plug in
+ smsg_send(dual_sim_plug->init->dst,&msend,-1);
+ printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value);
+
+ }
+ else if(flag_high_count_sim1 >= DELAY_COUNT)
+ {
+ flag_high_count_sim1 = 0;
+ irq_set_irq_type(irq,IRQF_TRIGGER_LOW);
+ printk(KERN_INFO "Enter sim1_irq_cd,flag = high\n");
+ smsg_set(&msend,dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM1_PLUGOUT);//plug out
+ smsg_send(dual_sim_plug->init->dst,&msend, -1);
+ printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value);
+
+ }
+ else
+ {
+ }
+
+ return IRQ_HANDLED;
+
+}
+
+static irqreturn_t sim2_irq_cd (int irq, void *dev_id)
+{
+ struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)dev_id;
+
+ struct smsg msend;
+ int flag = 0;
+ //printk(KERN_INFO "Enter sim2_irq_cd\n");
+ flag = gpio_get_value(dual_sim_plug->sim2_detect_gpio);
+ //printk(KERN_INFO "Enter sim2_irq_cd,flag = %d\n",flag);
+ if(flag == 0)
+ {
+ flag_low_count_sim2++;
+ flag_high_count_sim2 = 0;
+ }
+ else
+ {
+ flag_high_count_sim2++;
+ flag_low_count_sim2 = 0;
+ }
+
+ if(flag_low_count_sim2 >= DELAY_COUNT)
+ {
+ flag_low_count_sim2 = 0;
+ irq_set_irq_type(irq,IRQF_TRIGGER_HIGH);
+ printk(KERN_INFO "Enter sim2_irq_cd,flag = low\n");
+ smsg_set(&msend, dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM2_PLUGIN);//plug in
+ smsg_send(dual_sim_plug->init->dst, &msend, -1);
+ printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value);
+
+ }
+ else if(flag_high_count_sim2 >= DELAY_COUNT)
+ {
+ flag_high_count_sim2 = 0;
+ irq_set_irq_type(irq,IRQF_TRIGGER_LOW);
+ printk(KERN_INFO "Enter sim2_irq_cd,flag = high\n");
+ smsg_set(&msend, dual_sim_plug->init->channel,SMSG_TYPE_CMD,SMSG_SIM_PLUG,VALUE_SIM2_PLUGOUT);//plug out
+ smsg_send(dual_sim_plug->init->dst,&msend, -1);
+ printk(KERN_INFO "dual_sim_plug->init->dst = %d,msend.channel = %d,msend.type = %d,msend.flag = %d,msend.value = %d\n",dual_sim_plug->init->dst,msend.channel,msend.type,msend.flag,msend.value);
+
+ }
+ else
+ {
+ }
+
+ return IRQ_HANDLED;
+}
+static int dual_sim_plug_thread(void *data)
+{
+ struct dual_sim_plug_device *dual_sim_plug = (struct dual_sim_plug_device *)data;
+ struct smsg mrecv;
+ int rval;
+ struct sched_param param = {.sched_priority = 91};
+
+ sched_setscheduler(current,SCHED_RR,&param);
+ printk(KERN_INFO "Enter dual_sim_plug_thread\n");
+
+ while (!kthread_should_stop())
+ {
+ smsg_set(&mrecv, dual_sim_plug->init->channel,0,0,0);
+ rval = smsg_recv(dual_sim_plug->init->dst,&mrecv,-1);
+ printk(KERN_INFO "Enter dual_sim_plug_thread rval = %d,mrecv.channel = %d,mrecv.type =%d,mrecv.flag =%d,mrecv.value =%d\n",rval,mrecv.channel,mrecv.type,mrecv.flag,mrecv.value);
+ if (rval == -EIO)
+ {
+ msleep(5);
+ continue;
+ }
+ switch(mrecv.type)
+ {
+ case SMSG_TYPE_DONE:
+ if(mrecv.flag == SMSG_SIM_PLUG)
+ {
+ if(mrecv.value == VALUE_SIM1_PLUGOUT)//sim1 plug out
+ {
+ printk(KERN_INFO "Sim1 plug out done\n");
+ }
+ else if(mrecv.value == VALUE_SIM1_PLUGIN)//sim1 plug in
+ {
+ printk(KERN_INFO "Sim1 plug in done\n");
+ }
+ else if(mrecv.value == VALUE_SIM2_PLUGOUT)//sim2 plug out
+ {
+ printk(KERN_INFO "Sim2 plug out done\n");
+ }
+ else if(mrecv.value == VALUE_SIM2_PLUGIN)//sim2 plug in
+ {
+ printk(KERN_INFO "Sim2 plug in done\n");
+ }
+ else
+ {
+
+ }
+ }
+ break;
+ case SMSG_TYPE_OPEN:
+ printk(KERN_INFO "SMSG_TYPE_OPEN!!!\n");
+ smsg_open_ack(dual_sim_plug->init->dst, dual_sim_plug->init->channel);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+
+}
+
+static inline void dual_sim_plug_destroy_pdata(struct dual_sim_plug_init_data **init)
+{
+#ifdef CONFIG_OF
+ struct dual_sim_plug_init_data *pdata = *init;
+
+ if (pdata) {
+ kfree(pdata);
+ }
+
+ *init = NULL;
+#else
+ return;
+#endif
+}
+
+static int dual_sim_plug_parse_dt(struct dual_sim_plug_init_data **init, struct device *dev)
+{
+#ifdef CONFIG_OF
+ struct device_node *np = dev->of_node;
+ struct dual_sim_plug_init_data *pdata = NULL;
+ int ret;
+ uint32_t data;
+
+ pdata = kzalloc(sizeof(struct dual_sim_plug_init_data),GFP_KERNEL);
+ if (!pdata) {
+ printk(KERN_ERR "Failed to allocate pdata memory\n");
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_string(np,"sprd,name",(const char**)&pdata->name);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np,"sprd,dst",(uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->dst = (uint8_t)data;
+
+ ret = of_property_read_u32(np,"sprd,channel",(uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->channel = (uint8_t)data;
+
+ ret = of_property_read_u32(np,"sprd,sim1_gpio",(uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->sim1_gpio = (uint32_t)data;
+
+ ret = of_property_read_u32(np,"sprd,sim2_gpio",(uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->sim2_gpio = (uint32_t)data;
+
+ *init = pdata;
+ return ret;
+error:
+ kfree(pdata);
+ *init = NULL;
+ return ret;
+#else
+ return -ENODEV;
+#endif
+}
+
+static int dual_sim_plug_probe(struct platform_device *pdev)//add by xiangru.bi
+{
+ struct dual_sim_plug_init_data *init = pdev->dev.platform_data;
+ struct dual_sim_plug_device *dual_sim_plug;
+ int rval = 0;
+ int ret1,ret2;
+ int result;
+ int ret;
+
+ //struct smsg msend;
+
+
+ printk(KERN_INFO "Dual_sim_plug_probe start\n");
+
+ if (pdev->dev.of_node && !init) {
+ rval = dual_sim_plug_parse_dt(&init, &pdev->dev);
+ if (rval) {
+ printk(KERN_ERR "Failed to parse spipe device tree, ret=%d\n", rval);
+ return rval;
+ }
+ }
+
+ printk(KERN_INFO "Succed to parse spipe device tree, ret=%d,init->name=%s,init->dst=%d,init->channel=%d,init->sim1_gpio=%d,init->sim2_gpio=%d\n", rval,init->name,init->dst,init->channel,init->sim1_gpio,init->sim2_gpio);
+
+ dual_sim_plug = kzalloc(sizeof(struct dual_sim_plug_device),GFP_KERNEL);
+ if (!dual_sim_plug) {
+ printk(KERN_ERR "Failed to allocate dual_sim_plug_device\n");
+ ret = -ENOMEM;
+ goto probe_out;
+ }
+
+ printk(KERN_INFO "Succed to allocate dual_sim_plug_device\n");
+
+ dual_sim_plug->sim1_detect_gpio = init->sim1_gpio;
+ dual_sim_plug->sim2_detect_gpio = init->sim2_gpio;
+
+ dual_sim_plug->init = init;
+
+ ret1 = gpio_request(dual_sim_plug->sim1_detect_gpio,"sim1_detect");
+ if (ret1)
+ {
+ printk(KERN_ERR "failed to get sim1 detect gpio\n");
+ ret = ret1;
+ goto probe_free_dual_sim_plug;
+ }
+
+ gpio_direction_input(dual_sim_plug->sim1_detect_gpio);
+
+ ret1 = gpio_get_value(dual_sim_plug->sim1_detect_gpio);
+ if(ret1 == 0)
+ {
+ printk(KERN_INFO "Sim1 detect gpio = low.\n");
+ }
+ else
+ {
+ printk(KERN_INFO "Sim1 detect gpio = high.\n");
+ }
+
+
+ printk(KERN_INFO "Succed to get sim1 detect gpio\n");
+
+ dual_sim_plug ->irq_cd1 = gpio_to_irq(dual_sim_plug->sim1_detect_gpio);
+
+ printk(KERN_INFO "Succed to get sim1 irq dual_sim_plug ->irq_cd1 =%d\n",dual_sim_plug ->irq_cd1);
+ if (dual_sim_plug ->irq_cd1 >= 0)
+ {
+ if(ret1 == 0)
+ {
+ if (request_irq(dual_sim_plug ->irq_cd1,sim1_irq_cd,IRQF_TRIGGER_HIGH,"sim1_detect_hander",dual_sim_plug))
+ {
+ printk(KERN_ERR "can't get sim1 detect irq.\n");
+ ret = -ENOENT;
+ goto probe_free_sim1_gpio;
+ }
+ printk(KERN_INFO "Get sim1 detect irq.\n");
+ }
+ else
+ {
+ if (request_irq(dual_sim_plug ->irq_cd1,sim1_irq_cd,IRQF_TRIGGER_LOW,"sim1_detect_hander",dual_sim_plug))
+ {
+ printk(KERN_ERR "can't get sim1 detect irq.\n");
+ ret = -ENOENT;
+ goto probe_free_sim1_gpio;
+ }
+ printk(KERN_INFO "Get sim1 detect irq.\n");
+ }
+ }
+ else
+ {
+ printk(KERN_INFO "host detect has no irq available\n");
+ }
+
+ ret2 = gpio_request(dual_sim_plug->sim2_detect_gpio, "sim2_detect");
+ if (ret2)
+ {
+ printk(KERN_ERR "failed to get sim2 detect gpio\n");
+ ret = ret2;
+ goto probe_free_sim1_irq;
+
+ }
+
+ gpio_direction_input(dual_sim_plug->sim2_detect_gpio);
+
+ ret2 = gpio_get_value(dual_sim_plug->sim2_detect_gpio);
+ if(ret2 == 0)
+ {
+ printk(KERN_INFO "Sim2 detect gpio = low.\n");
+ }
+ else
+ {
+ printk(KERN_INFO "Sim2 detect gpio = high.\n");
+ }
+
+
+ printk(KERN_INFO "Succed to get sim2 detect gpio\n");
+
+ dual_sim_plug ->irq_cd2 = gpio_to_irq(dual_sim_plug->sim2_detect_gpio);
+ printk(KERN_INFO "Succed to get sim2 irq dual_sim_plug ->irq_cd2 =%d\n",dual_sim_plug ->irq_cd2);
+ if (dual_sim_plug ->irq_cd2 >= 0)
+ {
+ if(ret2 == 0)
+ {
+ if (request_irq(dual_sim_plug ->irq_cd2,sim2_irq_cd,IRQF_TRIGGER_HIGH,"sim2_detect_hander", dual_sim_plug))
+ {
+ printk(KERN_ERR "can't get sim2 detect irq.\n");
+ ret = -ENOENT;
+ goto probe_free_sim2_gpio;
+ }
+ printk(KERN_INFO "Get sim2 detect irq.\n");
+ }
+ else
+ {
+ if (request_irq(dual_sim_plug ->irq_cd2,sim2_irq_cd,IRQF_TRIGGER_LOW,"sim2_detect_hander", dual_sim_plug))
+ {
+ printk(KERN_ERR "can't get sim2 detect irq.\n");
+ ret = -ENOENT;
+ goto probe_free_sim2_gpio;
+ }
+ printk(KERN_INFO "Get sim2 detect irq.\n");
+ }
+ }
+ else
+ {
+ printk(KERN_INFO "host detect has no irq available\n");
+ }
+
+ rval = smsg_ch_open(init->dst,init->channel,-1);
+
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to open channel: %d\n",init->channel);
+ ret = rval;
+ goto probe_free_sim2_irq;
+ }
+
+ printk(KERN_INFO "Succed to open channel: %d\n",init->channel);
+
+ dual_sim_plug->th1 = kthread_create(dual_sim_plug_thread,dual_sim_plug,"dual_sim_plug-%d-%d",init->dst,init->channel);
+
+ if (IS_ERR(dual_sim_plug->th1))
+ {
+ printk(KERN_ERR "Failed to create kthread: dual_sim_plug-%d-%d\n",init->dst,init->channel);
+ result = PTR_ERR(dual_sim_plug->th1);
+ ret = result;
+ goto probe_close_ch;
+ }
+
+ printk(KERN_INFO "Succed to create kthread: dual_sim_plug-%d-%d\n",init->dst,init->channel);
+
+ wake_up_process(dual_sim_plug->th1);
+
+ platform_set_drvdata(pdev, dual_sim_plug);
+
+ return 0;
+
+probe_close_ch:
+ smsg_ch_close(init->dst,init->channel,-1);
+probe_free_sim2_irq:
+ free_irq(dual_sim_plug ->irq_cd2,dual_sim_plug);
+probe_free_sim2_gpio:
+ gpio_free(dual_sim_plug->sim2_detect_gpio);
+ probe_free_sim1_irq:
+ free_irq(dual_sim_plug ->irq_cd1,dual_sim_plug);
+ probe_free_sim1_gpio:
+ gpio_free(dual_sim_plug->sim1_detect_gpio);
+probe_free_dual_sim_plug:
+ kfree(dual_sim_plug);
+probe_out:
+ dual_sim_plug_destroy_pdata(&init);
+ return ret;
+}
+
+static int dual_sim_plug_remove(struct platform_device *pdev)
+{
+
+ printk(KERN_INFO "Dual_sim_plug_remove start\n");
+ struct dual_sim_plug_device *dual_sim_plug = platform_get_drvdata(pdev);
+
+ printk(KERN_INFO "dual_sim_plug->init->dst = %d,dual_sim_plug->init->channel = %d,dual_sim_plug->init->sim1_gpio=%d,dual_sim_plug->init->sim2_gpio=%d\n",dual_sim_plug->init->dst,dual_sim_plug->init->channel,dual_sim_plug->init->sim1_gpio,dual_sim_plug->init->sim2_gpio);
+
+ if (!IS_ERR_OR_NULL(dual_sim_plug->th1)) {
+ kthread_stop(dual_sim_plug->th1);
+ }
+
+ smsg_ch_close(dual_sim_plug->init->dst,dual_sim_plug->init->channel,-1);
+
+ free_irq(dual_sim_plug ->irq_cd2,dual_sim_plug);
+
+ gpio_free(dual_sim_plug->sim2_detect_gpio);
+
+ free_irq(dual_sim_plug ->irq_cd1,dual_sim_plug);
+
+ gpio_free(dual_sim_plug->sim1_detect_gpio);
+
+ dual_sim_plug_destroy_pdata(&dual_sim_plug->init);
+
+ kfree(dual_sim_plug);
+
+ platform_set_drvdata(pdev,NULL);
+
+ return 0;
+}
+
+static const struct of_device_id dual_sim_plug_match_table[] = {
+ {.compatible = "sprd,dual_sim_plug", },
+ { },
+};
+
+static struct platform_driver dual_sim_plug_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "dual_sim_plug",
+ .of_match_table = dual_sim_plug_match_table,
+ },
+ .probe = dual_sim_plug_probe,
+ .remove = dual_sim_plug_remove,
+};
+
+static int __init dual_sim_plug_init(void)
+{
+ printk(KERN_INFO "Dual_sim_plug_init start\n");
+ return platform_driver_register(&dual_sim_plug_driver);
+}
+
+static void __exit dual_sim_plug_exit(void)
+{
+ printk(KERN_INFO "Dual_sim_plug_exit start\n");
+ platform_driver_unregister(&dual_sim_plug_driver);
+}
+
+module_init(dual_sim_plug_init);
+module_exit(dual_sim_plug_exit);
+
+MODULE_AUTHOR("Bi Xiangru");
+MODULE_DESCRIPTION("SIPC/DUAL_SIM_PLUG driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/sblock.c b/drivers/sipc/sblock.c
new file mode 100644
index 00000000..7f73180f
--- /dev/null
+++ b/drivers/sipc/sblock.c
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/log2.h>
+
+#include <linux/sipc.h>
+#include "sblock.h"
+
+static struct sblock_mgr *sblocks[SIPC_ID_NR][SMSG_CH_NR];
+
+static inline uint32_t sblock_get_index(uint32_t x, uint32_t y)
+{
+ return (x / y);
+}
+
+static inline uint32_t sblock_get_ringpos(uint32_t x, uint32_t y)
+{
+ return is_power_of_2(y) ? (x & (y - 1)) : (x % y);
+}
+
+void sblock_put(uint8_t dst, uint8_t channel, struct sblock *blk)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ unsigned long flags;
+ int txpos;
+ int index;
+
+ if (!sblock) {
+ return;
+ }
+
+ ring = sblock->ring;
+ poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool);
+
+ spin_lock_irqsave(&ring->p_txlock, flags);
+ txpos = sblock_get_ringpos(poolhd->txblk_rdptr - 1, poolhd->txblk_count);
+ ring->r_txblks[txpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr;
+ ring->r_txblks[txpos].length = poolhd->txblk_size;
+ poolhd->txblk_rdptr = poolhd->txblk_rdptr - 1;
+ if ((int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr) == 1) {
+ wake_up_interruptible_all(&(ring->getwait));
+ }
+ index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz);
+ ring->txrecord[index] = SBLOCK_BLK_STATE_DONE;
+
+ spin_unlock_irqrestore(&ring->p_txlock, flags);
+}
+
+static int sblock_recover(uint8_t dst, uint8_t channel)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ unsigned long pflags, qflags;
+ int i, j;
+
+ if (!sblock) {
+ return -ENODEV;
+ }
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+ poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool);
+
+ sblock->state = SBLOCK_STATE_IDLE;
+ wake_up_interruptible_all(&ring->getwait);
+ wake_up_interruptible_all(&ring->recvwait);
+
+ spin_lock_irqsave(&ring->r_txlock, pflags);
+ /* clean txblks ring */
+ ringhd->txblk_wrptr = ringhd->txblk_rdptr;
+
+ spin_lock_irqsave(&ring->p_txlock, qflags);
+ /* recover txblks pool */
+ poolhd->txblk_rdptr = poolhd->txblk_wrptr;
+ for (i = 0, j = 0; i < poolhd->txblk_count; i++) {
+ if (ring->txrecord[i] == SBLOCK_BLK_STATE_DONE) {
+ ring->p_txblks[j].addr = i * sblock->txblksz + poolhd->txblk_addr;
+ ring->p_txblks[j].length = sblock->txblksz;
+ poolhd->txblk_wrptr = poolhd->txblk_wrptr + 1;
+ j++;
+ }
+ }
+ spin_unlock_irqrestore(&ring->p_txlock, qflags);
+ spin_unlock_irqrestore(&ring->r_txlock, pflags);
+
+
+ spin_lock_irqsave(&ring->r_rxlock, pflags);
+ /* clean rxblks ring */
+ ringhd->rxblk_rdptr = ringhd->rxblk_wrptr;
+
+ spin_lock_irqsave(&ring->p_rxlock, qflags);
+ /* recover rxblks pool */
+ poolhd->rxblk_wrptr = poolhd->rxblk_rdptr;
+ for (i = 0, j = 0; i < poolhd->rxblk_count; i++) {
+ if (ring->rxrecord[i] == SBLOCK_BLK_STATE_DONE) {
+ ring->p_rxblks[j].addr = i * sblock->rxblksz + poolhd->rxblk_addr;
+ ring->p_rxblks[j].length = sblock->rxblksz;
+ poolhd->rxblk_wrptr = poolhd->rxblk_wrptr + 1;
+ j++;
+ }
+ }
+ spin_unlock_irqrestore(&ring->p_rxlock, qflags);
+ spin_unlock_irqrestore(&ring->r_rxlock, pflags);
+
+ return 0;
+}
+
+static int sblock_thread(void *data)
+{
+ struct sblock_mgr *sblock = data;
+ struct smsg mcmd, mrecv;
+ int rval;
+ int recovery = 0;
+ struct sched_param param = {.sched_priority = 90};
+
+ /*set the thread as a real time thread, and its priority is 90*/
+ sched_setscheduler(current, SCHED_RR, &param);
+
+ /* since the channel open may hang, we call it in the sblock thread */
+ rval = smsg_ch_open(sblock->dst, sblock->channel, -1);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to open channel %d\n", sblock->channel);
+ /* assign NULL to thread poniter as failed to open channel */
+ sblock->thread = NULL;
+ return rval;
+ }
+
+ /* handle the sblock events */
+ while (!kthread_should_stop()) {
+
+ /* monitor sblock recv smsg */
+ smsg_set(&mrecv, sblock->channel, 0, 0, 0);
+ rval = smsg_recv(sblock->dst, &mrecv, -1);
+ if (rval == -EIO || rval == -ENODEV) {
+ /* channel state is FREE */
+ msleep(5);
+ continue;
+ }
+
+ pr_debug("sblock thread recv msg: dst=%d, channel=%d, "
+ "type=%d, flag=0x%04x, value=0x%08x\n",
+ sblock->dst, sblock->channel,
+ mrecv.type, mrecv.flag, mrecv.value);
+
+ switch (mrecv.type) {
+ case SMSG_TYPE_OPEN:
+ /* handle channel recovery */
+ if (recovery) {
+ if (sblock->handler) {
+ sblock->handler(SBLOCK_NOTIFY_CLOSE, sblock->data);
+ }
+ sblock_recover(sblock->dst, sblock->channel);
+ }
+ smsg_open_ack(sblock->dst, sblock->channel);
+ break;
+ case SMSG_TYPE_CLOSE:
+ /* handle channel recovery */
+ smsg_close_ack(sblock->dst, sblock->channel);
+ if (sblock->handler) {
+ sblock->handler(SBLOCK_NOTIFY_CLOSE, sblock->data);
+ }
+ sblock->state = SBLOCK_STATE_IDLE;
+ break;
+ case SMSG_TYPE_CMD:
+ /* respond cmd done for sblock init */
+ WARN_ON(mrecv.flag != SMSG_CMD_SBLOCK_INIT);
+ smsg_set(&mcmd, sblock->channel, SMSG_TYPE_DONE,
+ SMSG_DONE_SBLOCK_INIT, sblock->smem_addr);
+ smsg_send(sblock->dst, &mcmd, -1);
+ if (sblock->handler) {
+ sblock->handler(SBLOCK_NOTIFY_OPEN, sblock->data);
+ }
+ sblock->state = SBLOCK_STATE_READY;
+ recovery = 1;
+ break;
+ case SMSG_TYPE_EVENT:
+ /* handle sblock send/release events */
+ switch (mrecv.flag) {
+ case SMSG_EVENT_SBLOCK_SEND:
+ wake_up_interruptible_all(&sblock->ring->recvwait);
+ if (sblock->handler) {
+ sblock->handler(SBLOCK_NOTIFY_RECV, sblock->data);
+ }
+ break;
+ case SMSG_EVENT_SBLOCK_RELEASE:
+ wake_up_interruptible_all(&(sblock->ring->getwait));
+ if (sblock->handler) {
+ sblock->handler(SBLOCK_NOTIFY_GET, sblock->data);
+ }
+ break;
+ default:
+ rval = 1;
+ break;
+ }
+ break;
+ default:
+ rval = 1;
+ break;
+ };
+ if (rval) {
+ printk(KERN_WARNING "non-handled sblock msg: %d-%d, %d, %d, %d\n",
+ sblock->dst, sblock->channel,
+ mrecv.type, mrecv.flag, mrecv.value);
+ rval = 0;
+ }
+ }
+
+ printk(KERN_WARNING "sblock %d-%d thread stop", sblock->dst, sblock->channel);
+ return rval;
+}
+
+int sblock_create(uint8_t dst, uint8_t channel,
+ uint32_t txblocknum, uint32_t txblocksize,
+ uint32_t rxblocknum, uint32_t rxblocksize)
+{
+ struct sblock_mgr *sblock = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ uint32_t hsize;
+ int i, result;
+
+ sblock = kzalloc(sizeof(struct sblock_mgr) , GFP_KERNEL);
+ if (!sblock) {
+ return -ENOMEM;
+ }
+
+ sblock->state = SBLOCK_STATE_IDLE;
+ sblock->dst = dst;
+ sblock->channel = channel;
+ sblock->txblksz = txblocksize;
+ sblock->rxblksz = rxblocksize;
+ sblock->txblknum = txblocknum;
+ sblock->rxblknum = rxblocknum;
+
+
+ /* allocate smem */
+ hsize = sizeof(struct sblock_header);
+ sblock->smem_size = hsize + /* for header*/
+ txblocknum * txblocksize + rxblocknum * rxblocksize + /* for blks */
+ (txblocknum + rxblocknum) * sizeof(struct sblock_blks) + /* for ring*/
+ (txblocknum + rxblocknum) * sizeof(struct sblock_blks); /* for pool*/
+
+ sblock->smem_addr = smem_alloc(sblock->smem_size);
+ if (!sblock->smem_addr) {
+ printk(KERN_ERR "Failed to allocate smem for sblock\n");
+ kfree(sblock);
+ return -ENOMEM;
+ }
+ sblock->smem_virt = ioremap_nocache(sblock->smem_addr, sblock->smem_size);
+ if (!sblock->smem_virt) {
+ printk(KERN_ERR "Failed to map smem for sblock\n");
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock);
+ return -EFAULT;
+ }
+
+ /* initialize ring and header */
+ sblock->ring = kzalloc(sizeof(struct sblock_ring), GFP_KERNEL);
+ if (!sblock->ring) {
+ printk(KERN_ERR "Failed to allocate ring for sblock\n");
+ iounmap(sblock->smem_virt);
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock);
+ return -ENOMEM;
+ }
+ ringhd = (volatile struct sblock_ring_header *)(sblock->smem_virt);
+ ringhd->txblk_addr = sblock->smem_addr + hsize;
+ ringhd->txblk_count = txblocknum;
+ ringhd->txblk_size = txblocksize;
+ ringhd->txblk_rdptr = 0;
+ ringhd->txblk_wrptr = 0;
+ ringhd->txblk_blks = sblock->smem_addr + hsize +
+ txblocknum * txblocksize + rxblocknum * rxblocksize;
+ ringhd->rxblk_addr = ringhd->txblk_addr + txblocknum * txblocksize;
+ ringhd->rxblk_count = rxblocknum;
+ ringhd->rxblk_size = rxblocksize;
+ ringhd->rxblk_rdptr = 0;
+ ringhd->rxblk_wrptr = 0;
+ ringhd->rxblk_blks = ringhd->txblk_blks + txblocknum * sizeof(struct sblock_blks);
+
+ poolhd = (volatile struct sblock_ring_header *)(sblock->smem_virt + sizeof(struct sblock_ring_header));
+ poolhd->txblk_addr = sblock->smem_addr + hsize;
+ poolhd->txblk_count = txblocknum;
+ poolhd->txblk_size = txblocksize;
+ poolhd->txblk_rdptr = 0;
+ poolhd->txblk_wrptr = 0;
+ poolhd->txblk_blks = ringhd->rxblk_blks + rxblocknum * sizeof(struct sblock_blks);
+ poolhd->rxblk_addr = ringhd->txblk_addr + txblocknum * txblocksize;
+ poolhd->rxblk_count = rxblocknum;
+ poolhd->rxblk_size = rxblocksize;
+ poolhd->rxblk_rdptr = 0;
+ poolhd->rxblk_wrptr = 0;
+ poolhd->rxblk_blks = poolhd->txblk_blks + txblocknum * sizeof(struct sblock_blks);
+
+ sblock->ring->txrecord = kzalloc(sizeof(int) * txblocknum, GFP_KERNEL);
+ if (!sblock->ring->txrecord) {
+ printk(KERN_ERR "Failed to allocate memory for txrecord\n");
+ iounmap(sblock->smem_virt);
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock->ring);
+ kfree(sblock);
+ return -ENOMEM;
+ }
+
+ sblock->ring->rxrecord = kzalloc(sizeof(int) * rxblocknum, GFP_KERNEL);
+ if (!sblock->ring->rxrecord) {
+ printk(KERN_ERR "Failed to allocate memory for rxrecord\n");
+ iounmap(sblock->smem_virt);
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock->ring->txrecord);
+ kfree(sblock->ring);
+ kfree(sblock);
+ return -ENOMEM;
+ }
+
+ sblock->ring->header = sblock->smem_virt;
+ sblock->ring->txblk_virt = sblock->smem_virt +
+ (ringhd->txblk_addr - sblock->smem_addr);
+ sblock->ring->r_txblks = sblock->smem_virt +
+ (ringhd->txblk_blks - sblock->smem_addr);
+ sblock->ring->rxblk_virt = sblock->smem_virt +
+ (ringhd->rxblk_addr - sblock->smem_addr);
+ sblock->ring->r_rxblks = sblock->smem_virt +
+ (ringhd->rxblk_blks - sblock->smem_addr);
+ sblock->ring->p_txblks = sblock->smem_virt +
+ (poolhd->txblk_blks - sblock->smem_addr);
+ sblock->ring->p_rxblks = sblock->smem_virt +
+ (poolhd->rxblk_blks - sblock->smem_addr);
+
+
+ for (i = 0; i < txblocknum; i++) {
+ sblock->ring->p_txblks[i].addr = poolhd->txblk_addr + i * txblocksize;
+ sblock->ring->p_txblks[i].length = txblocksize;
+ sblock->ring->txrecord[i] = SBLOCK_BLK_STATE_DONE;
+ poolhd->txblk_wrptr++;
+ }
+ for (i = 0; i < rxblocknum; i++) {
+ sblock->ring->p_rxblks[i].addr = poolhd->rxblk_addr + i * rxblocksize;
+ sblock->ring->p_rxblks[i].length = rxblocksize;
+ sblock->ring->rxrecord[i] = SBLOCK_BLK_STATE_DONE;
+ poolhd->rxblk_wrptr++;
+ }
+
+ init_waitqueue_head(&sblock->ring->getwait);
+ init_waitqueue_head(&sblock->ring->recvwait);
+ spin_lock_init(&sblock->ring->r_txlock);
+ spin_lock_init(&sblock->ring->r_rxlock);
+ spin_lock_init(&sblock->ring->p_txlock);
+ spin_lock_init(&sblock->ring->p_rxlock);
+
+ sblock->thread = kthread_create(sblock_thread, sblock,
+ "sblock-%d-%d", dst, channel);
+ if (IS_ERR(sblock->thread)) {
+ printk(KERN_ERR "Failed to create kthread: sblock-%d-%d\n", dst, channel);
+ iounmap(sblock->smem_virt);
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock->ring->txrecord);
+ kfree(sblock->ring->rxrecord);
+ kfree(sblock->ring);
+ result = PTR_ERR(sblock->thread);
+ kfree(sblock);
+ return result;
+ }
+
+ sblocks[dst][channel]=sblock;
+ wake_up_process(sblock->thread);
+
+ return 0;
+}
+
+void sblock_destroy(uint8_t dst, uint8_t channel)
+{
+ struct sblock_mgr *sblock = sblocks[dst][channel];
+
+ if (sblock == NULL) {
+ return;
+ }
+
+ sblock->state = SBLOCK_STATE_IDLE;
+ smsg_ch_close(dst, channel, -1);
+
+ /* stop sblock thread if it's created successfully and still alive */
+ if (!IS_ERR_OR_NULL(sblock->thread)) {
+ kthread_stop(sblock->thread);
+ }
+
+ if (sblock->ring) {
+ wake_up_interruptible_all(&sblock->ring->recvwait);
+ wake_up_interruptible_all(&sblock->ring->getwait);
+ if (sblock->ring->txrecord) {
+ kfree(sblock->ring->txrecord);
+ }
+ if (sblock->ring->rxrecord) {
+ kfree(sblock->ring->rxrecord);
+ }
+ kfree(sblock->ring);
+ }
+ if (sblock->smem_virt) {
+ iounmap(sblock->smem_virt);
+ }
+ smem_free(sblock->smem_addr, sblock->smem_size);
+ kfree(sblock);
+
+ sblocks[dst][channel]=NULL;
+}
+
+int sblock_register_notifier(uint8_t dst, uint8_t channel,
+ void (*handler)(int event, void *data), void *data)
+{
+ struct sblock_mgr *sblock = sblocks[dst][channel];
+
+ if (!sblock) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return -ENODEV;
+ }
+#ifndef CONFIG_SIPC_WCN
+ if (sblock->handler) {
+ printk(KERN_ERR "sblock handler already registered\n");
+ return -EBUSY;
+ }
+#endif
+ sblock->handler = handler;
+ sblock->data = data;
+
+ return 0;
+}
+
+int sblock_get(uint8_t dst, uint8_t channel, struct sblock *blk, int timeout)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ int txpos, index;
+ int rval = 0;
+ unsigned long flags;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return sblock ? -EIO : -ENODEV;
+ }
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+ poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool);
+
+ if (poolhd->txblk_rdptr == poolhd->txblk_wrptr) {
+ if (timeout == 0) {
+ /* no wait */
+ printk(KERN_WARNING "sblock_get %d-%d is empty!\n",
+ dst, channel);
+ rval = -ENODATA;
+ } else if (timeout < 0) {
+ /* wait forever */
+ rval = wait_event_interruptible(ring->getwait,
+ poolhd->txblk_rdptr != poolhd->txblk_wrptr ||
+ sblock->state == SBLOCK_STATE_IDLE);
+ if (rval < 0) {
+ printk(KERN_WARNING "sblock_get wait interrupted!\n");
+ }
+
+ if (sblock->state == SBLOCK_STATE_IDLE) {
+ printk(KERN_ERR "sblock_get sblock state is idle!\n");
+ rval = -EIO;
+ }
+ } else {
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ring->getwait,
+ poolhd->txblk_rdptr != poolhd->txblk_wrptr ||
+ sblock == SBLOCK_STATE_IDLE,
+ timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "sblock_get wait interrupted!\n");
+ } else if (rval == 0) {
+ printk(KERN_WARNING "sblock_get wait timeout!\n");
+ rval = -ETIME;
+ }
+
+ if(sblock->state == SBLOCK_STATE_IDLE) {
+ printk(KERN_ERR "sblock_get sblock state is idle!\n");
+ rval = -EIO;
+ }
+ }
+ }
+
+ if (rval < 0) {
+ return rval;
+ }
+
+ /* multi-gotter may cause got failure */
+ spin_lock_irqsave(&ring->p_txlock, flags);
+ if (poolhd->txblk_rdptr != poolhd->txblk_wrptr &&
+ sblock->state == SBLOCK_STATE_READY) {
+ txpos = sblock_get_ringpos(poolhd->txblk_rdptr, poolhd->txblk_count);
+ blk->addr = sblock->smem_virt + (ring->p_txblks[txpos].addr - sblock->smem_addr);
+ blk->length = poolhd->txblk_size;
+ poolhd->txblk_rdptr = poolhd->txblk_rdptr + 1;
+ index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz);
+ ring->txrecord[index] = SBLOCK_BLK_STATE_PENDING;
+ } else {
+ rval = sblock->state == SBLOCK_STATE_READY ? -EAGAIN : -EIO;
+ }
+ spin_unlock_irqrestore(&ring->p_txlock, flags);
+
+ return rval;
+}
+
+static int sblock_send_ex(uint8_t dst, uint8_t channel, struct sblock *blk, bool yell)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring;
+ volatile struct sblock_ring_header *ringhd;
+ struct smsg mevt;
+ int txpos, index;
+ int rval = 0;
+ unsigned long flags;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return sblock ? -EIO : -ENODEV;
+ }
+
+ pr_debug("sblock_send: dst=%d, channel=%d, addr=%p, len=%d\n",
+ dst, channel, blk->addr, blk->length);
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+
+ spin_lock_irqsave(&ring->r_txlock, flags);
+
+ txpos = sblock_get_ringpos(ringhd->txblk_wrptr, ringhd->txblk_count);
+ ring->r_txblks[txpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr;
+ ring->r_txblks[txpos].length = blk->length;
+ pr_debug("sblock_send: channel=%d, wrptr=%d, txpos=%d, addr=%x\n",
+ channel, ringhd->txblk_wrptr, txpos, ring->r_txblks[txpos].addr);
+ ringhd->txblk_wrptr = ringhd->txblk_wrptr + 1;
+ if (sblock->state == SBLOCK_STATE_READY) {
+ if(yell) {
+ smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_SEND, 0);
+ rval = smsg_send(dst, &mevt, 0);
+ }
+ else if(!ring->yell) {
+ if(((int)(ringhd->txblk_wrptr - ringhd->txblk_rdptr) == 1) /*&&
+ ((int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr) == (sblock->txblknum - 1))*/) {
+ ring->yell = 1;
+ }
+ }
+ }
+ index = sblock_get_index((blk->addr - ring->txblk_virt), sblock->txblksz);
+ ring->txrecord[index] = SBLOCK_BLK_STATE_DONE;
+
+ spin_unlock_irqrestore(&ring->r_txlock, flags);
+
+ return rval ;
+}
+
+int sblock_send(uint8_t dst, uint8_t channel, struct sblock *blk)
+{
+ return sblock_send_ex(dst, channel, blk, true);
+}
+
+int sblock_send_prepare(uint8_t dst, uint8_t channel, struct sblock *blk)
+{
+ return sblock_send_ex(dst, channel, blk, false);
+}
+
+int sblock_send_finish(uint8_t dst, uint8_t channel)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring;
+ volatile struct sblock_ring_header *ringhd;
+ struct smsg mevt;
+ int rval=0;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return sblock ? -EIO : -ENODEV;
+ }
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+
+ if (ringhd->txblk_wrptr != ringhd->txblk_rdptr) {
+ smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_SEND, 0);
+ rval = smsg_send(dst, &mevt, 0);
+ }
+
+ return rval;
+}
+
+int sblock_receive(uint8_t dst, uint8_t channel, struct sblock *blk, int timeout)
+{
+ struct sblock_mgr *sblock = sblocks[dst][channel];
+ struct sblock_ring *ring;
+ volatile struct sblock_ring_header *ringhd;
+ int rxpos, index, rval = 0;
+ unsigned long flags;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return sblock ? -EIO : -ENODEV;
+ }
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+
+ pr_debug("sblock_receive: dst=%d, channel=%d, timeout=%d\n",
+ dst, channel, timeout);
+ pr_debug("sblock_receive: channel=%d, wrptr=%d, rdptr=%d",
+ channel, ringhd->rxblk_wrptr, ringhd->rxblk_rdptr);
+
+ if (ringhd->rxblk_wrptr == ringhd->rxblk_rdptr) {
+ if (timeout == 0) {
+ /* no wait */
+ pr_debug("sblock_receive %d-%d is empty!\n",
+ dst, channel);
+ rval = -ENODATA;
+ } else if (timeout < 0) {
+ /* wait forever */
+ rval = wait_event_interruptible(ring->recvwait,
+ ringhd->rxblk_wrptr != ringhd->rxblk_rdptr);
+ if (rval < 0) {
+ printk(KERN_WARNING "sblock_receive wait interrupted!\n");
+ }
+
+ if (sblock->state == SBLOCK_STATE_IDLE) {
+ printk(KERN_ERR "sblock_receive sblock state is idle!\n");
+ rval = -EIO;
+ }
+
+ } else {
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ring->recvwait,
+ ringhd->rxblk_wrptr != ringhd->rxblk_rdptr, timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "sblock_receive wait interrupted!\n");
+ } else if (rval == 0) {
+ printk(KERN_WARNING "sblock_receive wait timeout!\n");
+ rval = -ETIME;
+ }
+
+ if (sblock->state == SBLOCK_STATE_IDLE) {
+ printk(KERN_ERR "sblock_receive sblock state is idle!\n");
+ rval = -EIO;
+ }
+ }
+ }
+
+ if (rval < 0) {
+ return rval;
+ }
+
+ /* multi-receiver may cause recv failure */
+ spin_lock_irqsave(&ring->r_rxlock, flags);
+
+ if (ringhd->rxblk_wrptr != ringhd->rxblk_rdptr &&
+ sblock->state == SBLOCK_STATE_READY) {
+ rxpos = sblock_get_ringpos(ringhd->rxblk_rdptr, ringhd->rxblk_count);
+ blk->addr = ring->r_rxblks[rxpos].addr - sblock->smem_addr + sblock->smem_virt;
+ blk->length = ring->r_rxblks[rxpos].length;
+ ringhd->rxblk_rdptr = ringhd->rxblk_rdptr + 1;
+ pr_debug("sblock_receive: channel=%d, rxpos=%d, addr=%p, len=%d\n",
+ channel, rxpos, blk->addr, blk->length);
+ index = sblock_get_index((blk->addr - ring->rxblk_virt), sblock->rxblksz);
+ ring->rxrecord[index] = SBLOCK_BLK_STATE_PENDING;
+ } else {
+ rval = sblock->state == SBLOCK_STATE_READY ? -EAGAIN : -EIO;
+ }
+ spin_unlock_irqrestore(&ring->r_rxlock, flags);
+
+ return rval;
+}
+
+int sblock_get_arrived_count(uint8_t dst, uint8_t channel)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ int blk_count = 0;
+ unsigned long flags;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return -ENODEV;
+ }
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+
+ spin_lock_irqsave(&ring->r_rxlock, flags);
+ blk_count = (int)(ringhd->rxblk_wrptr - ringhd->rxblk_rdptr);
+ spin_unlock_irqrestore(&ring->r_rxlock, flags);
+
+ return blk_count;
+
+}
+
+int sblock_get_free_count(uint8_t dst, uint8_t channel)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ int blk_count = 0;
+ unsigned long flags;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return -ENODEV;
+ }
+
+ ring = sblock->ring;
+ poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool);
+
+ spin_lock_irqsave(&ring->p_txlock, flags);
+ blk_count = (int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr);
+ spin_unlock_irqrestore(&ring->p_txlock, flags);
+
+ return blk_count;
+}
+
+int sblock_release(uint8_t dst, uint8_t channel, struct sblock *blk)
+{
+ struct sblock_mgr *sblock = (struct sblock_mgr *)sblocks[dst][channel];
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ struct smsg mevt;
+ unsigned long flags;
+ int rxpos;
+ int index;
+
+ if (!sblock || sblock->state != SBLOCK_STATE_READY) {
+ printk(KERN_ERR "sblock-%d-%d not ready!\n", dst, channel);
+ return -ENODEV;
+ }
+
+ pr_debug("sblock_release: dst=%d, channel=%d, addr=%p, len=%d\n",
+ dst, channel, blk->addr, blk->length);
+
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&ring->header->ring);
+ poolhd = (volatile struct sblock_ring_header *)(&ring->header->pool);
+
+ spin_lock_irqsave(&ring->p_rxlock, flags);
+ rxpos = sblock_get_ringpos(poolhd->rxblk_wrptr, poolhd->rxblk_count);
+ ring->p_rxblks[rxpos].addr = blk->addr - sblock->smem_virt + sblock->smem_addr;
+ ring->p_rxblks[rxpos].length = poolhd->rxblk_size;
+ poolhd->rxblk_wrptr = poolhd->rxblk_wrptr + 1;
+ pr_debug("sblock_release: addr=%x\n", ring->p_rxblks[rxpos].addr);
+
+ if((int)(poolhd->rxblk_wrptr - poolhd->rxblk_rdptr) == 1 &&
+ sblock->state == SBLOCK_STATE_READY) {
+ /* send smsg to notify the peer side */
+ smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBLOCK_RELEASE, 0);
+ smsg_send(dst, &mevt, -1);
+ }
+
+ index = sblock_get_index((blk->addr - ring->rxblk_virt), sblock->rxblksz);
+ ring->rxrecord[index] = SBLOCK_BLK_STATE_DONE;
+
+ spin_unlock_irqrestore(&ring->p_rxlock, flags);
+
+ return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static int sblock_debug_show(struct seq_file *m, void *private)
+{
+ struct sblock_mgr *sblock = NULL;
+ struct sblock_ring *ring = NULL;
+ volatile struct sblock_ring_header *ringhd = NULL;
+ volatile struct sblock_ring_header *poolhd = NULL;
+ int i, j;
+
+ for (i = 0; i < SIPC_ID_NR; i++) {
+ for (j=0; j < SMSG_CH_NR; j++) {
+ sblock = sblocks[i][j];
+ if (!sblock) {
+ continue;
+ }
+ ring = sblock->ring;
+ ringhd = (volatile struct sblock_ring_header *)(&sblock->ring->header->ring);
+ poolhd = (volatile struct sblock_ring_header *)(&sblock->ring->header->pool);
+
+ seq_printf(m, "sblock dst 0x%0x, channel: 0x%0x, state: %d, smem_virt: 0x%lx, smem_addr: 0x%0x, smem_size: 0x%0x, txblksz: %d, rxblksz: %d \n",
+ sblock->dst, sblock->channel, sblock->state,
+ (size_t)sblock->smem_virt, sblock->smem_addr,
+ sblock->smem_size, sblock->txblksz, sblock->rxblksz );
+ seq_printf(m, "sblock ring: txblk_virt :0x%lx, rxblk_virt :0x%lx \n",
+ (size_t)ring->txblk_virt, (size_t)ring->rxblk_virt);
+ seq_printf(m, "sblock ring header: rxblk_addr :0x%0x, rxblk_rdptr :0x%0x, rxblk_wrptr :0x%0x, rxblk_size :%d, rxblk_count :%d, rxblk_blks: 0x%0x \n",
+ ringhd->rxblk_addr, ringhd->rxblk_rdptr,
+ ringhd->rxblk_wrptr, ringhd->rxblk_size,
+ ringhd->rxblk_count, ringhd->rxblk_blks);
+ seq_printf(m, "sblock ring header: txblk_addr :0x%0x, txblk_rdptr :0x%0x, txblk_wrptr :0x%0x, txblk_size :%d, txblk_count :%d, txblk_blks: 0x%0x \n",
+ ringhd->txblk_addr, ringhd->txblk_rdptr,
+ ringhd->txblk_wrptr, ringhd->txblk_size,
+ ringhd->txblk_count, ringhd->txblk_blks );
+ seq_printf(m, "sblock pool header: rxblk_addr :0x%0x, rxblk_rdptr :0x%0x, rxblk_wrptr :0x%0x, rxblk_size :%d, rxpool_count :%d, rxblk_blks: 0x%0x \n",
+ poolhd->rxblk_addr, poolhd->rxblk_rdptr,
+ poolhd->rxblk_wrptr, poolhd->rxblk_size,
+ (int)(poolhd->rxblk_wrptr - poolhd->rxblk_rdptr),
+ poolhd->rxblk_blks);
+ seq_printf(m, "sblock pool header: txblk_addr :0x%0x, txblk_rdptr :0x%0x, txblk_wrptr :0x%0x, txblk_size :%d, txpool_count :%d, txblk_blks: 0x%0x \n",
+ poolhd->txblk_addr, poolhd->txblk_rdptr,
+ poolhd->txblk_wrptr, poolhd->txblk_size,
+ (int)(poolhd->txblk_wrptr - poolhd->txblk_rdptr),
+ poolhd->txblk_blks );
+ }
+ }
+ return 0;
+
+}
+
+static int sblock_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sblock_debug_show, inode->i_private);
+}
+
+static const struct file_operations sblock_debug_fops = {
+ .open = sblock_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int sblock_init_debugfs(void *root )
+{
+ if (!root)
+ return -ENXIO;
+ debugfs_create_file("sblock", S_IRUGO, (struct dentry *)root, NULL, &sblock_debug_fops);
+ return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+EXPORT_SYMBOL(sblock_put);
+EXPORT_SYMBOL(sblock_create);
+EXPORT_SYMBOL(sblock_destroy);
+EXPORT_SYMBOL(sblock_register_notifier);
+EXPORT_SYMBOL(sblock_get);
+EXPORT_SYMBOL(sblock_send);
+EXPORT_SYMBOL(sblock_send_prepare);
+EXPORT_SYMBOL(sblock_send_finish);
+EXPORT_SYMBOL(sblock_receive);
+EXPORT_SYMBOL(sblock_get_arrived_count);
+EXPORT_SYMBOL(sblock_get_free_count);
+EXPORT_SYMBOL(sblock_release);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SBLOCK driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/sblock.h b/drivers/sipc/sblock.h
new file mode 100644
index 00000000..4d8770b0
--- /dev/null
+++ b/drivers/sipc/sblock.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 __SBLOCK_H
+#define __SBLOCK_H
+
+/* flag for CMD/DONE msg type */
+#define SMSG_CMD_SBLOCK_INIT 0x0001
+#define SMSG_DONE_SBLOCK_INIT 0x0002
+
+/* flag for EVENT msg type */
+#define SMSG_EVENT_SBLOCK_SEND 0x0001
+#define SMSG_EVENT_SBLOCK_RELEASE 0x0002
+
+#define SBLOCK_STATE_IDLE 0
+#define SBLOCK_STATE_READY 1
+
+#define SBLOCK_BLK_STATE_DONE 0
+#define SBLOCK_BLK_STATE_PENDING 1
+
+struct sblock_blks {
+ uint32_t addr; /*phy address*/
+ uint32_t length;
+};
+
+/* ring block header */
+struct sblock_ring_header {
+ /* get|send-block info */
+ uint32_t txblk_addr;
+ uint32_t txblk_count;
+ uint32_t txblk_size;
+ uint32_t txblk_blks;
+ uint32_t txblk_rdptr;
+ uint32_t txblk_wrptr;
+
+ /* release|recv-block info */
+ uint32_t rxblk_addr;
+ uint32_t rxblk_count;
+ uint32_t rxblk_size;
+ uint32_t rxblk_blks;
+ uint32_t rxblk_rdptr;
+ uint32_t rxblk_wrptr;
+};
+
+struct sblock_header {
+ struct sblock_ring_header ring;
+ struct sblock_ring_header pool;
+};
+
+struct sblock_ring {
+ struct sblock_header *header;
+ void *txblk_virt; /* virt of header->txblk_addr */
+ void *rxblk_virt; /* virt of header->rxblk_addr */
+
+ struct sblock_blks *r_txblks; /* virt of header->ring->txblk_blks */
+ struct sblock_blks *r_rxblks; /* virt of header->ring->rxblk_blks */
+ struct sblock_blks *p_txblks; /* virt of header->pool->txblk_blks */
+ struct sblock_blks *p_rxblks; /* virt of header->pool->rxblk_blks */
+
+ int *txrecord; /* record the state of every txblk */
+ int *rxrecord; /* record the state of every rxblk */
+ int yell; /* need to notify cp */
+ spinlock_t r_txlock; /* send */
+ spinlock_t r_rxlock; /* recv */
+ spinlock_t p_txlock; /* get */
+ spinlock_t p_rxlock; /* release */
+
+ wait_queue_head_t getwait;
+ wait_queue_head_t recvwait;
+};
+
+struct sblock_mgr {
+ uint8_t dst;
+ uint8_t channel;
+ uint32_t state;
+
+ void *smem_virt;
+ uint32_t smem_addr;
+ uint32_t smem_size;
+
+ uint32_t txblksz;
+ uint32_t rxblksz;
+ uint32_t txblknum;
+ uint32_t rxblknum;
+
+ struct sblock_ring *ring;
+ struct task_struct *thread;
+
+ void (*handler)(int event, void *data);
+ void *data;
+};
+
+#endif
diff --git a/drivers/sipc/sbuf.c b/drivers/sipc/sbuf.c
new file mode 100644
index 00000000..aade2816
--- /dev/null
+++ b/drivers/sipc/sbuf.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <asm/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+
+#include <linux/sipc.h>
+#include "sbuf.h"
+
+
+static struct sbuf_mgr *sbufs[SIPC_ID_NR][SMSG_CH_NR];
+
+static int sbuf_thread(void *data)
+{
+ struct sbuf_mgr *sbuf = data;
+ struct smsg mcmd, mrecv;
+ int rval, bufid;
+ struct sched_param param = {.sched_priority = 90};
+
+ /*set the thread as a real time thread, and its priority is 90*/
+ sched_setscheduler(current, SCHED_RR, &param);
+
+ /* since the channel open may hang, we call it in the sbuf thread */
+ rval = smsg_ch_open(sbuf->dst, sbuf->channel, -1);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to open channel %d\n", sbuf->channel);
+ /* assign NULL to thread poniter as failed to open channel */
+ sbuf->thread = NULL;
+ return rval;
+ }
+
+ /* sbuf init done, handle the ring rx events */
+ while (!kthread_should_stop()) {
+ /* monitor sbuf rdptr/wrptr update smsg */
+ smsg_set(&mrecv, sbuf->channel, 0, 0, 0);
+ rval = smsg_recv(sbuf->dst, &mrecv, -1);
+
+ if (rval == -EIO) {
+ /* channel state is free */
+ msleep(5);
+ continue;
+ }
+
+ pr_debug("sbuf thread recv msg: dst=%d, channel=%d, "
+ "type=%d, flag=0x%04x, value=0x%08x\n",
+ sbuf->dst, sbuf->channel,
+ mrecv.type, mrecv.flag, mrecv.value);
+
+ switch (mrecv.type) {
+ case SMSG_TYPE_OPEN:
+ /* handle channel recovery */
+ smsg_open_ack(sbuf->dst, sbuf->channel);
+ break;
+ case SMSG_TYPE_CLOSE:
+ /* handle channel recovery */
+ smsg_close_ack(sbuf->dst, sbuf->channel);
+ sbuf->state = SBUF_STATE_IDLE;
+ break;
+ case SMSG_TYPE_CMD:
+ /* respond cmd done for sbuf init */
+ WARN_ON(mrecv.flag != SMSG_CMD_SBUF_INIT);
+ smsg_set(&mcmd, sbuf->channel, SMSG_TYPE_DONE,
+ SMSG_DONE_SBUF_INIT, sbuf->smem_addr);
+ smsg_send(sbuf->dst, &mcmd, -1);
+ sbuf->state = SBUF_STATE_READY;
+ break;
+ case SMSG_TYPE_EVENT:
+ bufid = mrecv.value;
+ WARN_ON(bufid >= sbuf->ringnr);
+ switch (mrecv.flag) {
+ case SMSG_EVENT_SBUF_RDPTR:
+ wake_up_interruptible_all(&(sbuf->rings[bufid].txwait));
+ if (sbuf->rings[bufid].handler) {
+ sbuf->rings[bufid].handler(SBUF_NOTIFY_WRITE, sbuf->rings[bufid].data);
+ }
+ break;
+ case SMSG_EVENT_SBUF_WRPTR:
+ wake_up_interruptible_all(&(sbuf->rings[bufid].rxwait));
+ if (sbuf->rings[bufid].handler) {
+ sbuf->rings[bufid].handler(SBUF_NOTIFY_READ, sbuf->rings[bufid].data);
+ }
+ break;
+ default:
+ rval = 1;
+ break;
+ }
+ break;
+ default:
+ rval = 1;
+ break;
+ };
+ if (rval) {
+ printk(KERN_WARNING "non-handled sbuf msg: %d-%d, %d, %d, %d\n",
+ sbuf->dst, sbuf->channel,
+ mrecv.type, mrecv.flag, mrecv.value);
+ rval = 0;
+ }
+ }
+
+ return 0;
+}
+
+int sbuf_create(uint8_t dst, uint8_t channel, uint32_t bufnum,
+ uint32_t txbufsize, uint32_t rxbufsize)
+{
+ struct sbuf_mgr *sbuf;
+ volatile struct sbuf_smem_header *smem;
+ volatile struct sbuf_ring_header *ringhd;
+ int hsize, i, result;
+
+ sbuf = kzalloc(sizeof(struct sbuf_mgr), GFP_KERNEL);
+ if (!sbuf) {
+ printk(KERN_ERR "Failed to allocate mgr for sbuf\n");
+ return -ENOMEM;
+ }
+
+ sbuf->state = SBUF_STATE_IDLE;
+ sbuf->dst = dst;
+ sbuf->channel = channel;
+ sbuf->ringnr = bufnum;
+
+ /* allocate smem */
+ hsize = sizeof(struct sbuf_smem_header) + sizeof(struct sbuf_ring_header) * bufnum;
+ sbuf->smem_size = hsize + (txbufsize + rxbufsize) * bufnum;
+ sbuf->smem_addr = smem_alloc(sbuf->smem_size);
+ if (!sbuf->smem_addr) {
+ printk(KERN_ERR "Failed to allocate smem for sbuf\n");
+ kfree(sbuf);
+ return -ENOMEM;
+ }
+ sbuf->smem_virt = ioremap_nocache(sbuf->smem_addr, sbuf->smem_size);
+ if (!sbuf->smem_virt) {
+ printk(KERN_ERR "Failed to map smem for sbuf\n");
+ smem_free(sbuf->smem_addr, sbuf->smem_size);
+ kfree(sbuf);
+ return -EFAULT;
+ }
+
+ /* allocate rings description */
+ sbuf->rings = kzalloc(sizeof(struct sbuf_ring) * bufnum, GFP_KERNEL);
+ if (!sbuf->rings) {
+ printk(KERN_ERR "Failed to allocate rings for sbuf\n");
+ iounmap(sbuf->smem_virt);
+ smem_free(sbuf->smem_addr, sbuf->smem_size);
+ kfree(sbuf);
+ return -ENOMEM;
+ }
+
+ /* initialize all ring bufs */
+ smem = (volatile struct sbuf_smem_header *)sbuf->smem_virt;
+ smem->ringnr = bufnum;
+ for (i = 0; i < bufnum; i++) {
+ ringhd = (volatile struct sbuf_ring_header *)&(smem->headers[i]);
+ ringhd->txbuf_addr = sbuf->smem_addr + hsize +
+ (txbufsize + rxbufsize) * i;
+ ringhd->txbuf_size = txbufsize;
+ ringhd->txbuf_rdptr = 0;
+ ringhd->txbuf_wrptr = 0;
+ ringhd->rxbuf_addr = smem->headers[i].txbuf_addr + txbufsize;
+ ringhd->rxbuf_size = rxbufsize;
+ ringhd->rxbuf_rdptr = 0;
+ ringhd->rxbuf_wrptr = 0;
+
+ sbuf->rings[i].header = ringhd;
+ sbuf->rings[i].txbuf_virt = sbuf->smem_virt + hsize +
+ (txbufsize + rxbufsize) * i;
+ sbuf->rings[i].rxbuf_virt = sbuf->rings[i].txbuf_virt + txbufsize;
+ init_waitqueue_head(&(sbuf->rings[i].txwait));
+ init_waitqueue_head(&(sbuf->rings[i].rxwait));
+ mutex_init(&(sbuf->rings[i].txlock));
+ mutex_init(&(sbuf->rings[i].rxlock));
+ }
+
+ sbuf->thread = kthread_create(sbuf_thread, sbuf,
+ "sbuf-%d-%d", dst, channel);
+ if (IS_ERR(sbuf->thread)) {
+ printk(KERN_ERR "Failed to create kthread: sbuf-%d-%d\n", dst, channel);
+ kfree(sbuf->rings);
+ iounmap(sbuf->smem_virt);
+ smem_free(sbuf->smem_addr, sbuf->smem_size);
+ result = PTR_ERR(sbuf->thread);
+ kfree(sbuf);
+
+ return result;
+ }
+
+ sbufs[dst][channel] = sbuf;
+ wake_up_process(sbuf->thread);
+
+ return 0;
+}
+
+void sbuf_destroy(uint8_t dst, uint8_t channel)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+ int i;
+
+ if (sbuf == NULL) {
+ return;
+ }
+
+ sbuf->state = SBUF_STATE_IDLE;
+ smsg_ch_close(dst, channel, -1);
+
+ /* stop sbuf thread if it's created successfully and still alive */
+ if (!IS_ERR_OR_NULL(sbuf->thread)) {
+ kthread_stop(sbuf->thread);
+ }
+
+ if (sbuf->rings) {
+ for (i = 0; i < sbuf->ringnr; i++) {
+ wake_up_interruptible_all(&sbuf->rings[i].txwait);
+ wake_up_interruptible_all(&sbuf->rings[i].rxwait);
+ }
+ kfree(sbuf->rings);
+ }
+
+ if (sbuf->smem_virt) {
+ iounmap(sbuf->smem_virt);
+ }
+ smem_free(sbuf->smem_addr, sbuf->smem_size);
+ kfree(sbuf);
+
+ sbufs[dst][channel] = NULL;
+}
+
+int sbuf_write(uint8_t dst, uint8_t channel, uint32_t bufid,
+ void *buf, uint32_t len, int timeout)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+ struct sbuf_ring *ring = NULL;
+ volatile struct sbuf_ring_header *ringhd = NULL;
+ struct smsg mevt;
+ void *txpos;
+ int rval, left, tail, txsize;
+
+ if (!sbuf) {
+ return -ENODEV;
+ }
+ ring = &(sbuf->rings[bufid]);
+ ringhd = ring->header;
+ if (sbuf->state != SBUF_STATE_READY) {
+ printk(KERN_ERR "sbuf-%d-%d not ready to write!\n", dst, channel);
+ return -ENODEV;
+ }
+
+ pr_debug("sbuf_write: dst=%d, channel=%d, bufid=%d, len=%d, timeout=%d\n",
+ dst, channel, bufid, len, timeout);
+ pr_debug("sbuf_write: channel=%d, wrptr=%d, rdptr=%d",
+ channel, ringhd->txbuf_wrptr, ringhd->txbuf_rdptr);
+
+ rval = 0;
+ left = len;
+
+ if (timeout) {
+ mutex_lock(&ring->txlock);
+ } else {
+ if (!mutex_trylock(&(ring->txlock))) {
+ printk(KERN_INFO "sbuf_write busy!\n");
+ return -EBUSY;
+ }
+ }
+
+ if (timeout == 0) {
+ /* no wait */
+ if ((int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) >=
+ ringhd->txbuf_size) {
+ printk(KERN_WARNING "sbuf %d-%d ring %d txbuf is full!\n",
+ dst, channel, bufid);
+ rval = -EBUSY;
+ }
+ } else if (timeout < 0) {
+ /* wait forever */
+ rval = wait_event_interruptible(ring->txwait,
+ (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) <
+ ringhd->txbuf_size || sbuf->state == SBUF_STATE_IDLE);
+ if (rval < 0) {
+ printk(KERN_WARNING "sbuf_write wait interrupted!\n");
+ }
+
+ if (sbuf->state == SBUF_STATE_IDLE) {
+ printk(KERN_ERR "sbuf_write sbuf state is idle!\n");
+ rval = -EIO;
+ }
+ } else {
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ring->txwait,
+ (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) <
+ ringhd->txbuf_size || sbuf->state == SBUF_STATE_IDLE,
+ timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "sbuf_write wait interrupted!\n");
+ } else if (rval == 0) {
+ printk(KERN_WARNING "sbuf_write wait timeout!\n");
+ rval = -ETIME;
+ }
+
+ if (sbuf->state == SBUF_STATE_IDLE) {
+ printk(KERN_ERR "sbuf_write sbuf state is idle!\n");
+ rval = -EIO;
+ }
+
+ }
+
+ while (left && (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr) < ringhd->txbuf_size &&
+ sbuf->state == SBUF_STATE_READY) {
+ /* calc txpos & txsize */
+ txpos = ring->txbuf_virt + ringhd->txbuf_wrptr % ringhd->txbuf_size;
+ txsize = ringhd->txbuf_size - (int)(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr);
+ txsize = min(txsize, left);
+
+ tail = txpos + txsize - (ring->txbuf_virt + ringhd->txbuf_size);
+ if (tail > 0) {
+ /* ring buffer is rounded */
+ if ((uintptr_t)buf > TASK_SIZE) {
+ unalign_memcpy(txpos, buf, txsize - tail);
+ unalign_memcpy(ring->txbuf_virt, buf + txsize - tail, tail);
+ } else {
+ if(unalign_copy_from_user(txpos, (void __user *)buf, txsize - tail) ||
+ unalign_copy_from_user(ring->txbuf_virt,
+ (void __user *)(buf + txsize - tail), tail)) {
+ printk(KERN_ERR "sbuf_write: failed to copy from user!\n");
+ rval = -EFAULT;
+ break;
+ }
+ }
+ } else {
+ if ((uintptr_t)buf > TASK_SIZE) {
+ unalign_memcpy(txpos, buf, txsize);
+ } else {
+ /* handle the user space address */
+ if(unalign_copy_from_user(txpos, (void __user *)buf, txsize)) {
+ printk(KERN_ERR "sbuf_write: failed to copy from user!\n");
+ rval = -EFAULT;
+ break;
+ }
+ }
+ }
+
+
+ pr_debug("sbuf_write: channel=%d, txpos=%p, txsize=%d\n", channel, txpos, txsize);
+
+ /* update tx wrptr */
+ ringhd->txbuf_wrptr = ringhd->txbuf_wrptr + txsize;
+ /* tx ringbuf is empty, so need to notify peer side */
+ if(ringhd->txbuf_wrptr - ringhd->txbuf_rdptr == txsize) {
+ smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBUF_WRPTR, bufid);
+ smsg_send(dst, &mevt, -1);
+ }
+
+ left -= txsize;
+ buf += txsize;
+ }
+
+ mutex_unlock(&ring->txlock);
+
+ pr_debug("sbuf_write done: channel=%d, len=%d\n", channel, len - left);
+
+ if (len == left) {
+ return rval;
+ } else {
+ return (len - left);
+ }
+}
+
+int sbuf_read(uint8_t dst, uint8_t channel, uint32_t bufid,
+ void *buf, uint32_t len, int timeout)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+ struct sbuf_ring *ring = NULL;
+ volatile struct sbuf_ring_header *ringhd = NULL;
+ struct smsg mevt;
+ void *rxpos;
+ int rval, left, tail, rxsize;
+
+ if (!sbuf) {
+ return -ENODEV;
+ }
+ ring = &(sbuf->rings[bufid]);
+ ringhd = ring->header;
+
+ if (sbuf->state != SBUF_STATE_READY) {
+ printk(KERN_ERR "sbuf-%d-%d not ready to read!\n", dst, channel);
+ return -ENODEV;
+ }
+
+ pr_debug("sbuf_read: dst=%d, channel=%d, bufid=%d, len=%d, timeout=%d\n",
+ dst, channel, bufid, len, timeout);
+ pr_debug("sbuf_read: channel=%d, wrptr=%d, rdptr=%d",
+ channel, ringhd->rxbuf_wrptr, ringhd->rxbuf_rdptr);
+
+ rval = 0;
+ left = len;
+
+ if (timeout) {
+ mutex_lock(&ring->rxlock);
+ } else {
+ if (!mutex_trylock(&(ring->rxlock))) {
+ printk(KERN_INFO "sbuf_read busy!\n");
+ return -EBUSY;
+ }
+ }
+
+ if (ringhd->rxbuf_wrptr == ringhd->rxbuf_rdptr) {
+ if (timeout == 0) {
+ /* no wait */
+ printk(KERN_WARNING "sbuf %d-%d ring %d rxbuf is empty!\n",
+ dst, channel, bufid);
+ rval = -ENODATA;
+ } else if (timeout < 0) {
+ /* wait forever */
+ rval = wait_event_interruptible(ring->rxwait,
+ ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr ||
+ sbuf->state == SBUF_STATE_IDLE);
+ if (rval < 0) {
+ printk(KERN_WARNING "sbuf_read wait interrupted!\n");
+ }
+
+ if (sbuf->state == SBUF_STATE_IDLE) {
+ printk(KERN_ERR "sbuf_read sbuf state is idle!\n");
+ rval = -EIO;
+ }
+ } else {
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ring->rxwait,
+ ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr ||
+ sbuf->state == SBUF_STATE_IDLE, timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "sbuf_read wait interrupted!\n");
+ } else if (rval == 0) {
+ printk(KERN_WARNING "sbuf_read wait timeout!\n");
+ rval = -ETIME;
+ }
+
+ if (sbuf->state == SBUF_STATE_IDLE) {
+ printk(KERN_ERR "sbuf_read sbuf state is idle!\n");
+ rval = -EIO;
+ }
+ }
+ }
+
+ while (left && (ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr) &&
+ sbuf->state == SBUF_STATE_READY) {
+ /* calc rxpos & rxsize */
+ rxpos = ring->rxbuf_virt + ringhd->rxbuf_rdptr % ringhd->rxbuf_size;
+ rxsize = (int)(ringhd->rxbuf_wrptr - ringhd->rxbuf_rdptr);
+ /* check overrun */
+ WARN_ON(rxsize > ringhd->rxbuf_size);
+ rxsize = min(rxsize, left);
+
+ pr_debug("sbuf_read: channel=%d, buf=%p, rxpos=%p, rxsize=%d\n", channel, buf, rxpos, rxsize);
+
+ tail = rxpos + rxsize - (ring->rxbuf_virt + ringhd->rxbuf_size);
+
+ if (tail > 0) {
+ /* ring buffer is rounded */
+ if ((uintptr_t)buf > TASK_SIZE) {
+ unalign_memcpy(buf, rxpos, rxsize - tail);
+ unalign_memcpy(buf + rxsize - tail, ring->rxbuf_virt, tail);
+ } else {
+ /* handle the user space address */
+ if(unalign_copy_to_user((void __user *)buf, rxpos, rxsize - tail) ||
+ unalign_copy_to_user((void __user *)(buf + rxsize - tail),
+ ring->rxbuf_virt, tail)) {
+ printk(KERN_ERR "sbuf_read: failed to copy to user!\n");
+ rval = -EFAULT;
+ break;
+ }
+ }
+ } else {
+ if ((uintptr_t)buf > TASK_SIZE) {
+ unalign_memcpy(buf, rxpos, rxsize);
+ } else {
+ /* handle the user space address */
+ if (unalign_copy_to_user((void __user *)buf, rxpos, rxsize)) {
+ printk(KERN_ERR "sbuf_read: failed to copy to user!\n");
+ rval = -EFAULT;
+ break;
+ }
+ }
+ }
+
+ /* update rx rdptr */
+ ringhd->rxbuf_rdptr = ringhd->rxbuf_rdptr + rxsize;
+ /* rx ringbuf is full ,so need to notify peer side */
+ if(ringhd->rxbuf_wrptr - ringhd->rxbuf_rdptr == ringhd->rxbuf_size - rxsize) {
+ smsg_set(&mevt, channel, SMSG_TYPE_EVENT, SMSG_EVENT_SBUF_RDPTR, bufid);
+ smsg_send(dst, &mevt, -1);
+ }
+
+ left -= rxsize;
+ buf += rxsize;
+ }
+
+ mutex_unlock(&ring->rxlock);
+
+ pr_debug("sbuf_read done: channel=%d, len=%d", channel, len - left);
+
+ if (len == left) {
+ return rval;
+ } else {
+ return (len - left);
+ }
+}
+
+int sbuf_poll_wait(uint8_t dst, uint8_t channel, uint32_t bufid,
+ struct file *filp, poll_table *wait)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+ struct sbuf_ring *ring = NULL;
+ volatile struct sbuf_ring_header *ringhd = NULL;
+ unsigned int mask = 0;
+
+ if (!sbuf) {
+ return -ENODEV;
+ }
+ ring = &(sbuf->rings[bufid]);
+ ringhd = ring->header;
+ if (sbuf->state != SBUF_STATE_READY) {
+ printk(KERN_ERR "sbuf-%d-%d not ready to poll !\n", dst, channel);
+ return -ENODEV;
+ }
+
+ poll_wait(filp, &ring->txwait, wait);
+ poll_wait(filp, &ring->rxwait, wait);
+
+ if (ringhd->rxbuf_wrptr != ringhd->rxbuf_rdptr) {
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ if (ringhd->txbuf_wrptr - ringhd->txbuf_rdptr < ringhd->txbuf_size) {
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+ return mask;
+}
+
+int sbuf_status(uint8_t dst, uint8_t channel)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+
+ if (!sbuf) {
+ return -ENODEV;
+ }
+ if (sbuf->state != SBUF_STATE_READY) {
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+int sbuf_register_notifier(uint8_t dst, uint8_t channel, uint32_t bufid,
+ void (*handler)(int event, void *data), void *data)
+{
+ struct sbuf_mgr *sbuf = sbufs[dst][channel];
+ struct sbuf_ring *ring = NULL;
+
+ if (!sbuf) {
+ return -ENODEV;
+ }
+ ring = &(sbuf->rings[bufid]);
+ ring->handler = handler;
+ ring->data = data;
+
+ return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int sbuf_debug_show(struct seq_file *m, void *private)
+{
+ struct sbuf_mgr *sbuf = NULL;
+ struct sbuf_ring *rings = NULL;
+ volatile struct sbuf_ring_header *ring = NULL;
+ int i, j, n;
+
+ for (i = 0; i < SIPC_ID_NR; i++) {
+ for (j=0; j< SMSG_CH_NR; j++) {
+ sbuf = sbufs[i][j];
+ if (!sbuf) {
+ continue;
+ }
+ seq_printf(m, "sbuf dst 0x%0x, channel: 0x%0x, state: %d, smem_virt: 0x%lx, smem_addr: 0x%0x, smem_size: 0x%0x, ringnr: %d \n",
+ sbuf->dst, sbuf->channel, sbuf->state, (size_t)sbuf->smem_virt, sbuf->smem_addr, sbuf->smem_size, sbuf->ringnr);
+
+ for (n=0; n < sbuf->ringnr; n++) {
+ rings = &(sbuf->rings[n]);
+ ring = rings->header;
+ seq_printf(m, "sbuf ring[%d]: rxbuf_addr :0x%0x, rxbuf_rdptr :0x%0x, rxbuf_wrptr :0x%0x, rxbuf_size :0x%0x \n", n, ring->rxbuf_addr, ring->rxbuf_rdptr, ring->rxbuf_wrptr, ring->rxbuf_size);
+ seq_printf(m, "sbuf ring[%d]: txbuf_addr :0x%0x, txbuf_rdptr :0x%0x, txbuf_wrptr :0x%0x, txbuf_size :0x%0x \n", n, ring->txbuf_addr, ring->txbuf_rdptr, ring->txbuf_wrptr, ring->txbuf_size);
+ }
+ }
+ }
+ return 0;
+}
+
+static int sbuf_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sbuf_debug_show, inode->i_private);
+}
+
+static const struct file_operations sbuf_debug_fops = {
+ .open = sbuf_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int sbuf_init_debugfs( void *root )
+{
+ if (!root)
+ return -ENXIO;
+ debugfs_create_file("sbuf", S_IRUGO, (struct dentry *)root, NULL, &sbuf_debug_fops);
+ return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+
+EXPORT_SYMBOL(sbuf_create);
+EXPORT_SYMBOL(sbuf_destroy);
+EXPORT_SYMBOL(sbuf_write);
+EXPORT_SYMBOL(sbuf_read);
+EXPORT_SYMBOL(sbuf_poll_wait);
+EXPORT_SYMBOL(sbuf_status);
+EXPORT_SYMBOL(sbuf_register_notifier);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SBUF driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/sbuf.h b/drivers/sipc/sbuf.h
new file mode 100644
index 00000000..cb14a783
--- /dev/null
+++ b/drivers/sipc/sbuf.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 __SBUF_H
+#define __SBUF_H
+
+/* flag for CMD/DONE msg type */
+#define SMSG_CMD_SBUF_INIT 0x0001
+#define SMSG_DONE_SBUF_INIT 0x0002
+
+/* flag for EVENT msg type */
+#define SMSG_EVENT_SBUF_WRPTR 0x0001
+#define SMSG_EVENT_SBUF_RDPTR 0x0002
+
+/* ring buf header */
+struct sbuf_ring_header {
+ /* send-buffer info */
+ uint32_t txbuf_addr;
+ uint32_t txbuf_size;
+ uint32_t txbuf_rdptr;
+ uint32_t txbuf_wrptr;
+
+ /* recv-buffer info */
+ uint32_t rxbuf_addr;
+ uint32_t rxbuf_size;
+ uint32_t rxbuf_rdptr;
+ uint32_t rxbuf_wrptr;
+};
+
+/* sbuf_mem is the structure of smem for rings */
+struct sbuf_smem_header {
+ uint32_t ringnr;
+
+ struct sbuf_ring_header headers[0];
+};
+
+struct sbuf_ring {
+ /* tx/rx buffer info */
+ volatile struct sbuf_ring_header *header;
+
+ void *txbuf_virt;
+ void *rxbuf_virt;
+
+ /* send/recv wait queue */
+ wait_queue_head_t txwait;
+ wait_queue_head_t rxwait;
+
+ /* send/recv mutex */
+ struct mutex txlock;
+ struct mutex rxlock;
+
+ void (*handler)(int event, void *data);
+ void *data;
+};
+
+#define SBUF_STATE_IDLE 0
+#define SBUF_STATE_READY 1
+
+struct sbuf_mgr {
+ uint8_t dst;
+ uint8_t channel;
+ uint32_t state;
+
+ void *smem_virt;
+ uint32_t smem_addr;
+ uint32_t smem_size;
+ uint32_t ringnr;
+ struct sbuf_ring *rings;
+ struct task_struct *thread;
+};
+
+#endif
diff --git a/drivers/sipc/seth.c b/drivers/sipc/seth.c
new file mode 100644
index 00000000..e8e3d406
--- /dev/null
+++ b/drivers/sipc/seth.c
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/ipv6.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <asm/byteorder.h>
+#include <linux/tty.h>
+#include <linux/platform_device.h>
+#include <linux/atomic.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#endif
+
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#endif
+
+#include <linux/sipc.h>
+#include <linux/seth.h>
+
+#define seth_max(a,b) ((a) > (b) ? (a) : (b))
+#define seth_min(a,b) ((a) < (b) ? (a) : (b))
+
+/* debugging macros */
+#define SETH_INFO(x...) pr_info("SETH: " x)
+#define SETH_DEBUG(x...) pr_debug("SETH: " x)
+#define SETH_ERR(x...) pr_err("SETH: Error: " x)
+
+/* defination of sblock length */
+#define SETH_BLOCK_SIZE (ETH_HLEN + ETH_DATA_LEN + NET_IP_ALIGN)
+
+/* device status */
+#define DEV_ON 1
+#define DEV_OFF 0
+
+/* enable NAPI */
+#define SETH_NAPI
+
+/* tx pkt return value */
+#define SETH_TX_SUCCESS 0
+#define SETH_TX_NO_BLK -1
+#define SETH_TX_INVALID_BLK -2
+
+/* Macro of Packet Field */
+#define SETH_IPV4_HLEN 20
+#define SETH_IPV6_HLEN 40
+#define SETH_ETH_HLEN ETH_HLEN
+#define SETH_ETH_FRAME_LEN ETH_FRAME_LEN
+#define SETH_IPV4_VERSION 0x04
+#define SETH_IPV6_VERSION 0x06
+
+/* Macro of protocol type */
+#define SETH_PRO_TCP 6
+#define SETH_PRO_ICMP 1
+
+/* Macro of ICMP type */
+#define SETH_ICMP_PING ICMP_ECHO
+#define SETH_ICMP6_PING ICMPV6_ECHO_REQUEST
+
+#ifdef SETH_NAPI
+#define SETH_NAPI_WEIGHT 64
+#define SETH_TX_WEIGHT 16
+
+/* struct of data transfer statistics */
+struct seth_dtrans_stats {
+ uint32_t rx_pkt_max;
+ uint32_t rx_pkt_min;
+ uint32_t rx_sum;
+ uint32_t rx_cnt;
+ uint32_t rx_alloc_fails;
+
+ uint32_t tx_pkt_max;
+ uint32_t tx_pkt_min;
+ uint32_t tx_sum;
+ uint32_t tx_cnt;
+ uint32_t tx_ping_cnt;
+ uint32_t tx_ack_cnt;
+};
+
+#endif /* end of SETH_NAPI*/
+
+/*
+ * Device instance data.
+ */
+typedef struct SEth {
+ struct net_device_stats stats; /* net statistics */
+ struct net_device* netdev; /* Linux net device */
+ struct seth_init_data* pdata; /* platform data */
+ int state; /* device state */
+ int txstate; /* device txstate */
+
+#ifdef SETH_NAPI
+ atomic_t rx_busy;
+ struct timer_list rx_timer;
+
+ atomic_t txpending; /* seth tx resend count*/
+ struct timer_list tx_timer;
+ struct napi_struct napi; /* napi instance */
+ struct seth_dtrans_stats dt_stats; /* record data_transfer statistics*/
+#endif /* SETH_NAPI */
+} SEth;
+
+#ifdef CONFIG_DEBUG_FS
+struct dentry *root = NULL;
+uint32_t seth_print_seq = 0;
+static int seth_debugfs_mknod(void *root, void * data);
+#endif
+
+#ifdef SETH_NAPI
+static void seth_rx_timer_handler(unsigned long data);
+
+static inline void seth_dt_stats_init(struct seth_dtrans_stats * stats)
+{
+ memset(stats, 0, sizeof(struct seth_dtrans_stats));
+ stats->rx_pkt_min = 0xff;
+ stats->tx_pkt_min = 0xff;
+}
+
+static inline void seth_rx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt)
+{
+ stats->rx_pkt_max = seth_max(stats->rx_pkt_max, cnt);
+ stats->rx_pkt_min = seth_min(stats->rx_pkt_min, cnt);
+ stats->rx_sum += cnt;
+ stats->rx_cnt++;
+}
+
+static inline void seth_tx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt)
+{
+ stats->tx_pkt_max = seth_max(stats->tx_pkt_max, cnt);
+ stats->tx_pkt_min = seth_min(stats->tx_pkt_min, cnt);
+ stats->tx_sum += cnt;
+ stats->tx_cnt++;
+}
+
+static inline int pkt_is_ping(void *data)
+{
+ uint8_t *idata = (uint8_t *)data;
+ uint8_t ver, protocol;
+ uint32_t ip_hlen;
+ int ret = 0;
+
+ /* parse the version of IP protocol */
+ ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4;
+ if (ver == SETH_IPV4_VERSION) {
+ struct iphdr * ip_hdr;
+ struct icmphdr *icmp_hdr;
+
+ ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]);
+ /* get length of ip header */
+ ip_hlen = ip_hdr->ihl * 4;
+ /* get the protocol */
+ protocol = ip_hdr->protocol;
+ icmp_hdr = (struct icmphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]);
+ if (protocol == SETH_PRO_ICMP && icmp_hdr->type == SETH_ICMP_PING) {
+ ret = 1;
+ }
+ } else if (ver == SETH_IPV6_VERSION) {
+ struct ipv6hdr *ipv6_hdr;
+ struct icmp6hdr *icmp6_hdr;
+
+ ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]);
+ ip_hlen = SETH_ETH_HLEN;
+ /* get the protocol */
+ protocol = ipv6_hdr->nexthdr;
+ icmp6_hdr = (struct icmp6hdr *)(&idata[SETH_ETH_HLEN + ip_hlen]);
+ if (protocol == SETH_PRO_ICMP && icmp6_hdr->icmp6_type == SETH_ICMP6_PING) {
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+static inline int pkt_is_ack(void *data)
+{
+ uint8_t *idata = (uint8_t *)data;
+ uint8_t ver, protocol;
+ uint32_t ip_hlen;
+ struct tcphdr *tcp_hdr;
+ int ret = 0;
+
+ /* parse the version of IP protocol */
+ ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4;
+ if (ver == SETH_IPV4_VERSION) {
+ struct iphdr *ip_hdr;
+
+ ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]);
+ /*get length of ip header */
+ ip_hlen = ip_hdr->ihl * 4;
+ /* get the protocol*/
+ protocol = ip_hdr->protocol;
+ } else if (ver == SETH_IPV6_VERSION){
+ struct ipv6hdr *ipv6_hdr;
+
+ ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]);
+ ip_hlen = SETH_IPV6_HLEN;
+ /* get the protocol */
+ protocol = ipv6_hdr->nexthdr;
+ } else {
+ return 0;
+ }
+
+ tcp_hdr = (struct tcphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]);
+ if (protocol == SETH_PRO_TCP && (tcp_hdr->ack || tcp_hdr->syn)) {
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static inline void pkt_seq_print(void *data)
+{
+ uint8_t *idata = (uint8_t *)data;
+ uint8_t ver, proto;
+ uint32_t seq, ip_hlen;
+ static uint32_t nr = 0;
+ struct tcphdr *tcp_hdr;
+
+ /* parse the version of IP protocol */
+ ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4;
+
+ if (ver == SETH_IPV4_VERSION) {
+ struct iphdr *ip_hdr;
+
+ ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]);
+ /* get length of ip header */
+ ip_hlen = ip_hdr->ihl * 4;
+ /* get the protocol */
+ proto = ip_hdr->protocol;
+ } else if (ver == SETH_IPV6_VERSION) {
+ struct ipv6hdr *ipv6_hdr;
+
+ ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]);
+ ip_hlen = SETH_IPV6_HLEN;
+ /* get protocol */
+ proto = ipv6_hdr->nexthdr;
+ } else {
+ return;
+ }
+
+ /* print tcp seq */
+ if (proto == SETH_PRO_TCP) {
+ tcp_hdr = (struct tcphdr*)(&idata[SETH_ETH_HLEN + ip_hlen]);
+ seq = ntohl(tcp_hdr->seq);
+ pr_err("SETH: rx pkt: no.%u, ipv%u, nseq = 0x%08x, hseq = 0x%08x\n",
+ nr++, proto, tcp_hdr->seq, seq);
+ }
+}
+
+static int seth_rx_poll_handler(struct napi_struct * napi, int budget)
+{
+ struct SEth *seth = container_of(napi, struct SEth, napi);
+ struct sk_buff * skb;
+ struct seth_init_data *pdata;
+ struct sblock blk;
+ struct seth_dtrans_stats *dt_stats;
+ int skb_cnt, blk_ret, ret;
+
+ if (!seth) {
+ SETH_ERR("seth_rx_poll_handler no seth device\n");
+ return 0;
+ }
+
+ pdata = seth->pdata;
+ dt_stats = &seth->dt_stats;
+
+ blk_ret = 0;
+ skb_cnt = 0;
+ /* keep polling, until the sblock rx ring is empty */
+ while ((budget - skb_cnt) && !blk_ret) {
+ blk_ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0);
+ if (blk_ret) {
+ SETH_DEBUG("receive sblock error %d\n", blk_ret);
+ continue;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ /* print the sequence field of tcp packet for debug purpose*/
+ if (seth_print_seq) {
+ pkt_seq_print((void *)(blk.addr + NET_IP_ALIGN));
+ }
+#endif
+
+#ifdef CONFIG_SETH_OPT
+ /*
+ * start from SHARKL, the first 2 bytes of sblock are reserved for optimization,
+ * so IP field has been aligned to 4 Bytes already
+ */
+ skb = dev_alloc_skb (blk.length);
+ if (!skb) {
+ SETH_ERR ("failed to alloc skb!\n");
+ seth->stats.rx_dropped++;
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR ("release sblock failed %d\n", ret);
+ }
+ dt_stats->rx_alloc_fails++;
+ continue;
+ }
+
+ unalign_memcpy(skb->data, blk.addr, blk.length);
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR("release sblock error %d\n", ret);
+ }
+
+ /* update skb poiter */
+ skb_reserve(skb, NET_IP_ALIGN);
+ skb_put (skb, blk.length - NET_IP_ALIGN);
+#else
+ skb = dev_alloc_skb (blk.length + NET_IP_ALIGN);
+ if (!skb) {
+ SETH_ERR ("alloc skbuff failed!\n");
+ seth->stats.rx_dropped++;
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR ("release sblock failed %d\n", ret);
+ }
+ continue;
+ }
+
+ skb_reserve(skb, NET_IP_ALIGN);
+ unalign_memcpy(skb->data, blk.addr, blk.length);
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR("release sblock error %d\n", ret);
+ }
+ skb_put (skb, blk.length);
+#endif
+
+ skb->protocol = eth_type_trans(skb, seth->netdev);
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /* update fifo rd_ptr */
+ seth->stats.rx_bytes += skb->len;
+ seth->stats.rx_packets++;
+
+#ifdef CONFIG_SETH_GRO_DISABLE
+ netif_receive_skb(skb);
+#else
+ napi_gro_receive(napi, skb);
+#endif
+ /* update skb counter*/
+ skb_cnt++;
+ }
+
+ SETH_DEBUG("napi polling done, budget %d, skb cnt %d, cpu %d\n",
+ budget, skb_cnt, smp_processor_id());
+
+ /* update rx statistics */
+ seth_rx_stats_update(dt_stats, skb_cnt);
+
+ if (skb_cnt >= 0 && budget > skb_cnt) {
+ napi_complete(napi);
+ atomic_dec(&seth->rx_busy);
+
+ /* to guarantee that any arrived sblock(s) can be processed even if there are no events issued by CP */
+ if (sblock_get_arrived_count(pdata->dst, pdata->channel)) {
+ /* start a timer with 2 jiffies expries (20 ms) */
+ seth->rx_timer.function = seth_rx_timer_handler;
+ seth->rx_timer.expires = jiffies + HZ/50;
+ seth->rx_timer.data = (unsigned long)seth;
+ mod_timer(&seth->rx_timer, seth->rx_timer.expires);
+ SETH_INFO("start rx_timer, jiffies %lu.\n", jiffies);
+ }
+ }
+
+ return skb_cnt;
+}
+
+#endif
+
+/*
+ * Tx_ready handler.
+ */
+static void
+seth_tx_ready_handler (void* data)
+{
+ SEth* seth = (SEth*) data;
+
+ SETH_DEBUG("seth_tx_ready_handler state %0x\n", seth->state);
+ if (seth->state != DEV_ON) {
+ seth->state = DEV_ON;
+ seth->txstate = DEV_ON;
+ if (!netif_carrier_ok (seth->netdev)) {
+ netif_carrier_on (seth->netdev);
+ }
+ }
+ else {
+ seth->state = DEV_OFF;
+ seth->txstate = DEV_OFF;
+ if (netif_carrier_ok (seth->netdev)) {
+ netif_carrier_off (seth->netdev);
+ }
+ }
+}
+
+/*
+ * Tx_open handler.
+ */
+static void
+seth_tx_open_handler (void* data)
+{
+ SEth* seth = (SEth*) data;
+
+ SETH_DEBUG("seth_tx_open_handler state %0x\n", seth->state);
+ if (seth->state != DEV_ON) {
+ seth->state = DEV_ON;
+ seth->txstate = DEV_ON;
+ if (!netif_carrier_ok (seth->netdev)) {
+ netif_carrier_on (seth->netdev);
+ }
+ }
+}
+
+/*
+ * Tx_close handler.
+ */
+static void
+seth_tx_close_handler (void* data)
+{
+ SEth* seth = (SEth*) data;
+
+ SETH_INFO("seth_tx_close_handler state %0x\n", seth->state);
+ if (seth->state != DEV_OFF) {
+ seth->state = DEV_OFF;
+ seth->txstate = DEV_OFF;
+ if (netif_carrier_ok (seth->netdev)) {
+ netif_carrier_off (seth->netdev);
+ }
+ }
+}
+
+#ifdef SETH_NAPI
+static void seth_rx_handler (void * data)
+{
+ SEth* seth = (SEth *)data;
+
+ if (!seth) {
+ SETH_ERR("seth_rx_handler data is NULL.\n");
+ return;
+ }
+
+ /* if the poll handler has been done, trigger to schedule*/
+ if (!atomic_cmpxchg(&seth->rx_busy, 0, 1)) {
+ /* update rx stats*/
+ napi_schedule(&seth->napi);
+ /* trigger a NET_RX_SOFTIRQ softirq directly */
+ raise_softirq(NET_RX_SOFTIRQ);
+ }
+}
+
+static void seth_rx_timer_handler(unsigned long data)
+{
+ SETH_INFO("rx_timer expried, jiffies %lu.\n", jiffies);
+ seth_rx_handler((void *)data);
+}
+
+#else
+static void seth_rx_handler (void* data)
+{
+ SEth* seth = (SEth *)data;
+ struct seth_init_data *pdata = seth->pdata;
+ struct sblock blk;
+ struct sk_buff* skb;
+ uint32_t cnt;
+ int ret, sblkret;
+
+ sblkret = 0;
+ cnt = 0;
+ while (!sblkret){
+ sblkret = sblock_receive(pdata->dst, pdata->channel, &blk, 0);
+ if (sblkret) {
+ SETH_DEBUG("receive sblock error %d\n", sblkret);
+ break;
+ }
+
+ /* if the seth state is off, drop the comming packet */
+ if (seth->state != DEV_ON) {
+ SETH_ERR ("rx_handler the state of %s is off!\n", seth->netdev->name);
+ sblock_release(pdata->dst, pdata->channel, &blk);
+ continue;
+ }
+
+#ifdef CONFIG_SETH_OPT
+ /*
+ * start from SHARKL, the first 2 bytes of sblock are reserved for optimization,
+ * so IP field has been aligned to 4 Bytes already
+ */
+ skb = dev_alloc_skb (blk.length);
+ if (!skb) {
+ SETH_ERR ("failed to alloc skb!\n");
+ seth->stats.rx_dropped++;
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR ("release sblock failed %d\n", ret);
+ }
+ dt_stats->rx_alloc_fails++;
+ continue;
+ }
+
+ unalign_memcpy(skb->data, blk.addr, blk.length);
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR("release sblock error %d\n", ret);
+ }
+
+ /* update skb poiter */
+ skb_reserve(skb, NET_IP_ALIGN);
+ skb_put (skb, blk.length - NET_IP_ALIGN);
+#else
+ skb = dev_alloc_skb (blk.length + NET_IP_ALIGN);
+ if (!skb) {
+ SETH_ERR ("alloc skbuff failed!\n");
+ seth->stats.rx_dropped++;
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR ("release sblock failed %d\n", ret);
+ }
+ continue;
+ }
+
+ skb_reserve(skb, NET_IP_ALIGN);
+ unalign_memcpy(skb->data, blk.addr, blk.length);
+ ret = sblock_release(pdata->dst, pdata->channel, &blk);
+ if (ret) {
+ SETH_ERR("release sblock error %d\n", ret);
+ }
+ skb_put (skb, blk.length);
+#endif
+
+ skb->dev = seth->netdev;
+ skb->protocol = eth_type_trans (skb, seth->netdev);
+ skb->ip_summed = CHECKSUM_NONE;
+
+ seth->stats.rx_packets++;
+ seth->stats.rx_bytes += skb->len;
+
+ netif_rx (skb);
+
+ seth->netdev->last_rx = jiffies;
+ }
+
+ return;
+}
+#endif
+
+
+/*
+ * Tx_close handler.
+ */
+static void
+seth_tx_pre_handler (void* data)
+{
+ SEth* seth = (SEth*) data;
+
+ if (seth->txstate != DEV_ON) {
+ seth->txstate = DEV_ON;
+ SETH_INFO ("seth_tx_ready_handler txstate %0x\n", seth->txstate );
+ seth->netdev->trans_start = jiffies;
+ if (netif_queue_stopped(seth->netdev)) {
+ netif_wake_queue(seth->netdev);
+ }
+ }
+}
+
+static void
+seth_handler (int event, void* data)
+{
+ SEth *seth = (SEth *)data;
+
+ switch(event) {
+ case SBLOCK_NOTIFY_GET:
+ SETH_DEBUG ("SBLOCK_NOTIFY_GET is received\n");
+ seth_tx_pre_handler(seth);
+ break;
+ case SBLOCK_NOTIFY_RECV:
+ SETH_DEBUG ("SBLOCK_NOTIFY_RECV is received\n");
+ del_timer(&seth->rx_timer);
+ seth_rx_handler(seth);
+ break;
+ case SBLOCK_NOTIFY_STATUS:
+ SETH_DEBUG ("SBLOCK_NOTIFY_STATUS is received\n");
+ seth_tx_ready_handler(seth);
+ break;
+ case SBLOCK_NOTIFY_OPEN:
+ SETH_DEBUG ("SBLOCK_NOTIFY_OPEN is received\n");
+ seth_tx_open_handler(seth);
+ break;
+ case SBLOCK_NOTIFY_CLOSE:
+ SETH_DEBUG ("SBLOCK_NOTIFY_CLOSE is received\n");
+ seth_tx_close_handler(seth);
+ break;
+ default:
+ SETH_ERR ("Received event is invalid(event=%d)\n", event);
+ }
+}
+
+static int
+seth_tx_pkt(void* data, struct sk_buff* skb)
+{
+ struct sblock blk;
+ SEth* seth = netdev_priv (data);
+ struct seth_init_data *pdata = seth->pdata;
+ int ret;
+
+ /*
+ * Get a free sblock.
+ */
+ ret = sblock_get(pdata->dst, pdata->channel, &blk, 0);
+ if(ret) {
+ SETH_INFO("Get free sblock failed(%d), drop data!\n", ret);
+ seth->stats.tx_fifo_errors++;
+ return SETH_TX_NO_BLK;
+ }
+
+ if(blk.length < (skb->len + NET_IP_ALIGN)) {
+ SETH_ERR("The size of sblock is so tiny!\n");
+ /* recycle the unused sblock */
+ sblock_put(pdata->dst, pdata->channel, &blk);
+ seth->stats.tx_fifo_errors++;
+ netif_wake_queue(seth->netdev);
+ return SETH_TX_INVALID_BLK;
+ }
+
+#ifdef CONFIG_SETH_OPT
+ /* padding 2 Bytes in the head */
+ blk.length = skb->len + NET_IP_ALIGN;
+ unalign_memcpy(blk.addr + NET_IP_ALIGN, skb->data, skb->len);
+ /* copy the content into smem in mute */
+ sblock_send_prepare(pdata->dst, pdata->channel, &blk);
+#else
+ blk.length = skb->len;
+ unalign_memcpy(blk.addr, skb->data,skb->len);
+ /* copy the content into smem and trigger a smsg to the peer side */
+ sblock_send(pdata->dst, pdata->channel, &blk);
+#endif
+ /* update the statistics */
+ seth->stats.tx_bytes += skb->len;
+ seth->stats.tx_packets++;
+
+ dev_kfree_skb_any(skb);
+
+#ifdef CONFIG_SETH_OPT
+ atomic_inc(&seth->txpending);
+#endif
+
+ return SETH_TX_SUCCESS;
+}
+
+static void seth_tx_flush(unsigned long data)
+{
+ int ret;
+ uint32_t cnt;
+ SEth* seth = netdev_priv((void *)data);
+ struct seth_init_data *pdata = seth->pdata;
+
+ cnt = (uint32_t)atomic_read(&seth->txpending);
+ ret = sblock_send_finish(pdata->dst, pdata->channel);
+ seth_tx_stats_update(&seth->dt_stats, cnt);
+ if(ret) {
+ SETH_INFO("seth tx failed(%d)!\n", ret);
+ } else {
+ atomic_set(&seth->txpending, 0);
+ }
+}
+
+/*
+ * Transmit interface
+ */
+static int
+seth_start_xmit (struct sk_buff* skb, struct net_device* dev)
+{
+ SEth* seth = netdev_priv(dev);
+ struct seth_dtrans_stats *dt_stats;
+ int flag = 0, ret = 0;
+ int blk_cnt = 0;
+
+ if (seth->state != DEV_ON) {
+ SETH_ERR ("xmit the state of %s is off\n", dev->name);
+ netif_carrier_off (dev);
+ seth->stats.tx_carrier_errors++;
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ /* update tx statistics */
+ dt_stats = &seth->dt_stats;
+ if (pkt_is_ping((void *)skb->data)) {
+ dt_stats->tx_ping_cnt++;
+ flag = 1;
+ } else if (pkt_is_ack((void *)skb->data)) {
+ dt_stats->tx_ack_cnt++;
+ flag = 1;
+ }
+
+ ret = seth_tx_pkt(dev, skb);
+ if (SETH_TX_NO_BLK == ret) {
+ /* if there are no available sblks, enter flow control */
+ seth->txstate = DEV_OFF;
+ netif_stop_queue(dev);
+ /* anyway, flush the stored sblks */
+ seth_tx_flush((unsigned long)dev);
+ return NETDEV_TX_BUSY;
+ } else if (SETH_TX_INVALID_BLK == ret) {
+ return NETDEV_TX_OK;
+ }
+
+ /* if there are no available sblock for subsequent skb, start flow control*/
+ blk_cnt = sblock_get_free_count(seth->pdata->dst, seth->pdata->channel);
+ if (!blk_cnt) {
+ SETH_DEBUG("start flow control\n");
+ seth->txstate = DEV_OFF;
+ netif_stop_queue(dev);
+ }
+
+#ifdef CONFIG_SETH_OPT
+ if ((atomic_read(&seth->txpending) >= SETH_TX_WEIGHT) || flag) {
+ del_timer(&seth->tx_timer);
+ seth_tx_flush((unsigned long)dev);
+ SETH_DEBUG ("seth_start_xmit:at once\n");
+ } else if (atomic_read(&seth->txpending) == 1){
+ /* start a timer with 1 jiffy expries (10 ms) */
+ seth->tx_timer.function = seth_tx_flush;
+ seth->tx_timer.expires = jiffies + HZ/100;
+ seth->tx_timer.data = (unsigned long)dev;
+ mod_timer(&seth->tx_timer, seth->tx_timer.expires);
+ SETH_DEBUG ("seth_start_xmit:Timer\n");
+ }
+#endif
+ dev->trans_start = jiffies;
+ return NETDEV_TX_OK;
+}
+
+/*
+ * Open interface
+ */
+static int seth_open (struct net_device *dev)
+{
+ SEth* seth = netdev_priv(dev);
+ struct seth_init_data *pdata;
+ struct sblock blk;
+ int ret = 0, num = 0;
+
+ SETH_INFO("open seth!\n");
+
+ if (!seth) {
+ return -ENODEV;
+ }
+
+ pdata = seth->pdata;
+
+ /* clean the resident sblocks */
+ while(!ret) {
+ ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0);
+ if (!ret) {
+ sblock_release(pdata->dst, pdata->channel, &blk);
+ num++;
+ }
+ }
+ SETH_INFO("seth_open clean %d resident sblocks\n", num);
+
+ /* Reset stats */
+ memset(&seth->stats, 0, sizeof(seth->stats));
+
+ if (seth->state == DEV_ON && !netif_carrier_ok(seth->netdev)) {
+ SETH_INFO("seth_open netif_carrier_on\n");
+ netif_carrier_on(seth->netdev);
+ }
+
+#ifdef SETH_NAPI
+ atomic_set(&seth->rx_busy, 0);
+ napi_enable(&seth->napi);
+ seth_dt_stats_init(&seth->dt_stats);
+#endif
+
+ seth->txstate = DEV_ON;
+ seth->state = DEV_ON;
+
+ netif_start_queue(dev);
+
+ return 0;
+}
+
+/*
+ * Close interface
+ */
+static int seth_close (struct net_device *dev)
+{
+ SEth* seth = netdev_priv(dev);
+
+ SETH_INFO("close seth!\n");
+
+ seth->txstate = DEV_OFF;
+ seth->state = DEV_OFF;
+
+#ifdef SETH_NAPI
+ napi_disable(&seth->napi);
+#endif
+
+ netif_stop_queue(dev);
+
+ return 0;
+}
+
+static struct net_device_stats * seth_get_stats(struct net_device *dev)
+{
+ SEth * seth = netdev_priv(dev);
+ return &(seth->stats);
+}
+
+static void seth_tx_timeout(struct net_device *dev)
+{
+ SEth * seth = netdev_priv(dev);
+
+ SETH_INFO ("seth_tx_timeout()\n");
+ if (seth->txstate != DEV_ON) {
+ seth->txstate = DEV_ON;
+ dev->trans_start = jiffies;
+ netif_wake_queue(dev);
+ }
+}
+
+static struct net_device_ops seth_ops = {
+ .ndo_open = seth_open,
+ .ndo_stop = seth_close,
+ .ndo_start_xmit = seth_start_xmit,
+ .ndo_get_stats = seth_get_stats,
+ .ndo_tx_timeout = seth_tx_timeout,
+};
+
+static int seth_parse_dt(struct seth_init_data **init, struct device *dev)
+{
+#ifdef CONFIG_OF
+ struct seth_init_data *pdata = NULL;
+ struct device_node *np = dev->of_node;
+ int ret;
+ uint32_t data;
+
+ pdata = kzalloc(sizeof(struct seth_init_data), GFP_KERNEL);
+ if (!pdata) {
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_string(np, "sprd,name", (const char**)&pdata->name);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->dst = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->channel = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,blknum", (uint32_t *)&pdata->blocknum);
+ if (ret) {
+ goto error;
+ }
+
+ *init = pdata;
+ return 0;
+error:
+ kfree(pdata);
+ *init = NULL;
+ return ret;
+#else
+ return -ENODEV;
+#endif
+}
+
+static inline void seth_destroy_pdata(struct seth_init_data **init)
+{
+#ifdef CONFIG_OF
+ struct seth_init_data *pdata = *init;
+
+ if (pdata) {
+ kfree(pdata);
+ }
+ *init = NULL;
+#else
+ return;
+#endif
+}
+
+static int seth_probe(struct platform_device *pdev)
+{
+ struct seth_init_data *pdata = pdev->dev.platform_data;
+ struct net_device* netdev;
+ SEth* seth;
+ char ifname[IFNAMSIZ];
+ int ret;
+
+ if (pdev->dev.of_node && !pdata) {
+ ret = seth_parse_dt(&pdata, &pdev->dev);
+ if (ret) {
+ SETH_ERR( "failed to parse seth device tree, ret=%d\n", ret);
+ return ret;
+ }
+ }
+ SETH_INFO("after parse device tree, name=%s, dst=%u, channel=%u, blocknum=%u\n",
+ pdata->name, pdata->dst, pdata->channel, pdata->blocknum);
+
+ if(pdata->name[0])
+ strlcpy(ifname, pdata->name, IFNAMSIZ);
+ else
+ strcpy(ifname, "veth%d");
+
+ netdev = alloc_netdev (sizeof (SEth), ifname, ether_setup);
+ if (!netdev) {
+ seth_destroy_pdata(&pdata);
+ SETH_ERR ("alloc_netdev() failed.\n");
+ return -ENOMEM;
+ }
+ seth = netdev_priv (netdev);
+ seth->pdata = pdata;
+ seth->netdev = netdev;
+ seth->state = DEV_OFF;
+
+#ifdef SETH_NAPI
+ atomic_set(&seth->rx_busy, 0);
+ atomic_set(&seth->txpending, 0);
+
+ init_timer(&seth->rx_timer);
+ init_timer(&seth->tx_timer);
+ seth_dt_stats_init(&seth->dt_stats);
+#endif
+
+ netdev->netdev_ops = &seth_ops;
+ netdev->watchdog_timeo = 1*HZ;
+ netdev->irq = 0;
+ netdev->dma = 0;
+
+ random_ether_addr(netdev->dev_addr);
+
+#ifdef SETH_NAPI
+ netif_napi_add(netdev, &seth->napi, seth_rx_poll_handler, SETH_NAPI_WEIGHT);
+#endif
+
+ ret = sblock_create(pdata->dst, pdata->channel,
+ pdata->blocknum, SETH_BLOCK_SIZE,
+ pdata->blocknum, SETH_BLOCK_SIZE);
+ if (ret) {
+ SETH_ERR ("create sblock failed (%d)\n", ret);
+#ifdef SETH_NAPI
+ netif_napi_del(&seth->napi);
+#endif
+ free_netdev(netdev);
+ seth_destroy_pdata(&pdata);
+ return ret;
+ }
+
+ ret = sblock_register_notifier(pdata->dst, pdata->channel, seth_handler, seth);
+ if (ret) {
+ SETH_ERR ("regitster notifier failed (%d)\n", ret);
+#ifdef SETH_NAPI
+ netif_napi_del(&seth->napi);
+#endif
+ free_netdev(netdev);
+ sblock_destroy(pdata->dst, pdata->channel);
+ seth_destroy_pdata(&pdata);
+ return ret;
+ }
+
+ /* register new Ethernet interface */
+ if ((ret = register_netdev (netdev))) {
+ SETH_ERR ("register_netdev() failed (%d)\n", ret);
+#ifdef SETH_NAPI
+ netif_napi_del(&seth->napi);
+#endif
+ free_netdev(netdev);
+ sblock_destroy(pdata->dst, pdata->channel);
+ seth_destroy_pdata(&pdata);
+ return ret;
+ }
+
+ /* set link as disconnected */
+ netif_carrier_off (netdev);
+
+ platform_set_drvdata(pdev, seth);
+#ifdef CONFIG_DEBUG_FS
+ seth_debugfs_mknod(root, (void *)seth);
+#endif
+ return 0;
+}
+
+/*
+ * Cleanup Ethernet device driver.
+ */
+static int seth_remove (struct platform_device *pdev)
+{
+ struct SEth* seth = platform_get_drvdata(pdev);
+ struct seth_init_data *pdata = seth->pdata;
+
+#ifdef SETH_NAPI
+ netif_napi_del(&seth->napi);
+
+ del_timer_sync(&seth->rx_timer);
+ del_timer_sync(&seth->tx_timer);
+#endif
+
+ sblock_destroy(pdata->dst, pdata->channel);
+ seth_destroy_pdata(&pdata);
+ unregister_netdev(seth->netdev);
+ free_netdev(seth->netdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id seth_match_table[] = {
+ { .compatible = "sprd,seth", },
+ { },
+};
+
+static struct platform_driver seth_driver = {
+ .probe = seth_probe,
+ .remove = seth_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "seth",
+ .of_match_table = seth_match_table,
+ },
+};
+
+static int __init seth_init(void)
+{
+ return platform_driver_register(&seth_driver);
+}
+
+static void __exit seth_exit(void)
+{
+ platform_driver_unregister(&seth_driver);
+}
+
+module_init (seth_init);
+module_exit (seth_exit);
+
+#ifdef CONFIG_DEBUG_FS
+static int seth_debug_show(struct seq_file *m, void *v)
+{
+ struct SEth *seth = (struct SEth*)(m->private);
+ struct seth_dtrans_stats *stats;
+ struct seth_init_data *pdata;
+
+ if(!seth) {
+ SETH_ERR("invalid data, seth is NULL\n");
+ return -EINVAL;
+ }
+
+ pdata = seth->pdata;
+ stats = &seth->dt_stats;
+
+ seq_printf(m, "******************************************************************\n");
+ seq_printf(m, "DEVICE: %s, state %d, NET_SKB_PAD %u\n", pdata->name, seth->state, NET_SKB_PAD);
+ seq_printf(m, "\nRX statistics:\n");
+ seq_printf(m, "rx_pkt_max=%u, rx_pkt_min=%u, rx_sum=%u, rx_cnt=%u\n",
+ stats->rx_pkt_max, stats->rx_pkt_min, stats->rx_sum, stats->rx_cnt);
+ seq_printf(m, "rx_alloc_fails=%u, rx_busy=%d\n", stats->rx_alloc_fails, atomic_read(&seth->rx_busy));
+
+ seq_printf(m, "\nTX statistics:\n");
+ seq_printf(m, "tx_pkt_max=%u, tx_pkt_min=%u, tx_sum=%u, tx_cnt=%u\n",
+ stats->tx_pkt_max, stats->tx_pkt_min, stats->tx_sum, stats->tx_cnt);
+ seq_printf(m, "tx_ping_cnt=%u, tx_ack_cnt=%u\n",
+ stats->tx_ping_cnt, stats->tx_ack_cnt);
+ seq_printf(m, "******************************************************************\n");
+ return 0;
+}
+
+static int seth_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, seth_debug_show, inode->i_private);
+}
+
+static const struct file_operations seth_debug_fops = {
+ .open = seth_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int seth_debugfs_mknod(void *root, void * data)
+{
+ struct SEth *seth = (struct SEth*) data;
+ struct seth_init_data *pdata;
+
+ if (!seth) {
+ return -ENODEV;
+ }
+ pdata = seth->pdata;
+
+ if (!root)
+ return -ENXIO;
+
+ debugfs_create_file(pdata->name, S_IRUGO, (struct dentry *)root, data, &seth_debug_fops);
+ return 0;
+}
+
+static int __init seth_debugfs_init(void)
+{
+ root = debugfs_create_dir("seth", NULL);
+ if (!root) {
+ SETH_ERR("failed to create seth debugfs dir\n");
+ return -ENODEV;
+ }
+
+ debugfs_create_u32("print_seq", S_IRUGO | S_IWUGO, root, &seth_print_seq);
+
+ return 0;
+}
+
+fs_initcall(seth_debugfs_init);
+#endif
+
+MODULE_AUTHOR("Qiu Yi");
+MODULE_DESCRIPTION ("Spreadtrum Ethernet device driver");
+MODULE_LICENSE ("GPL");
+
diff --git a/drivers/sipc/simple_events.h b/drivers/sipc/simple_events.h
new file mode 100644
index 00000000..03851b32
--- /dev/null
+++ b/drivers/sipc/simple_events.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 _SIMPLE_EVENTS_H_
+#define _SIMPLE_EVENTS_H_
+
+#include <linux/wait.h>
+
+
+
+struct __simple_event {
+ spinlock_t lock;
+ wait_queue_head_t wq;
+ unsigned int flags;
+
+};
+
+typedef struct __simple_event simple_event_t;
+
+
+static inline void simple_evnet_init(simple_event_t* evt)
+{
+ spin_lock_init(&evt->lock);
+ init_waitqueue_head(&evt->wq);
+ evt->flags = 0;
+}
+
+static inline int simple_evnet_get(simple_event_t* evt, unsigned int req_flags,
+ unsigned int *actual_flags)
+{
+ unsigned long flags;
+
+ if(!evt) return -1;
+
+ *actual_flags = 0;
+
+ while(!(*actual_flags)) {
+ wait_event(evt->wq, (evt->flags & req_flags));
+
+ spin_lock_irqsave(&evt->lock, flags);
+ *actual_flags = evt->flags & req_flags;
+ evt->flags &= ~req_flags;
+ spin_unlock_irqrestore(&evt->lock, flags);
+ }
+
+ return 0;
+}
+
+
+static inline int simple_evnet_set(simple_event_t* evt, unsigned int flags,
+ int opt_and)
+{
+ unsigned long irq_flags;
+ int need_wake = 0;
+
+ if(!evt) return -1;
+
+ spin_lock_irqsave(&evt->lock, irq_flags);
+ if(!opt_and) {
+ evt->flags |= flags;
+ } else {
+ evt->flags &= flags;
+ }
+
+ if(evt->flags) {
+ need_wake = 1;
+ }
+ spin_unlock_irqrestore(&evt->lock, irq_flags);
+
+ if(need_wake) {
+ wake_up(&evt->wq);
+ }
+
+ return 0;
+}
+
+
+#endif /* !_SIMPLE_EVENTS_H_ */
+
diff --git a/drivers/sipc/sipc.c b/drivers/sipc/sipc.c
new file mode 100644
index 00000000..963d0a25
--- /dev/null
+++ b/drivers/sipc/sipc.c
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+#ifdef CONFIG_SPRD_MAILBOX
+#include <soc/sprd/mailbox.h>
+#endif
+
+#include <linux/sipc.h>
+#include <linux/sipc_priv.h>
+
+#define SMSG_TXBUF_ADDR (0)
+#define SMSG_TXBUF_SIZE (SZ_1K)
+#define SMSG_RXBUF_ADDR (SMSG_TXBUF_SIZE)
+#define SMSG_RXBUF_SIZE (SZ_1K)
+
+#define SMSG_RINGHDR (SMSG_TXBUF_SIZE + SMSG_RXBUF_SIZE)
+#define SMSG_TXBUF_RDPTR (SMSG_RINGHDR + 0)
+#define SMSG_TXBUF_WRPTR (SMSG_RINGHDR + 4)
+#define SMSG_RXBUF_RDPTR (SMSG_RINGHDR + 8)
+#define SMSG_RXBUF_WRPTR (SMSG_RINGHDR + 12)
+
+/* if it's upon mailbox arch, overwrite the implementation*/
+#ifdef CONFIG_SPRD_MAILBOX
+
+#define DEFINE_SIPC_RXIRQ_STATUS_FN(id) \
+static int sipc_rxirq_status##id(void) \
+{ \
+ return 1;\
+}
+
+#define DEFINE_SIPC_RXIRQ_CLEAR_FN(id) \
+static void sipc_rxirq_clear##id(void) \
+{ \
+ return;\
+}
+
+#define DEFINE_SIPC_TXIRQ_TRIGGER_FN(id) \
+static void sipc_txirq_trigger##id(void) \
+{ \
+ struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\
+ mbox_raw_sent(info->core_id, 0);\
+}
+
+#else
+
+#define DEFINE_SIPC_RXIRQ_STATUS_FN(id) \
+static int sipc_rxirq_status##id(void) \
+{ \
+ return 1;\
+}
+
+#define DEFINE_SIPC_RXIRQ_CLEAR_FN(id) \
+static void sipc_rxirq_clear##id(void) \
+{ \
+ struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\
+ __raw_writel(info->ap2cp_bit_clr, (volatile void *)info->cp2ap_int_ctrl);\
+}
+
+#define DEFINE_SIPC_TXIRQ_TRIGGER_FN(id) \
+static void sipc_txirq_trigger##id(void) \
+{ \
+ struct sipc_child_node_info *info = &sipc_dev->pdata->info_table[id];\
+ __raw_writel(info->ap2cp_bit_trig, (volatile void *)info->ap2cp_int_ctrl);\
+}
+
+#endif
+
+#define __GET_SIPC_FN_NAME(fn, id) fn##id
+
+#define __GET_SIPC_FN(fn, id) \
+({ \
+ void* ret; \
+ switch(id) { \
+ case 0: \
+ ret = __GET_SIPC_FN_NAME(fn, 0);\
+ break; \
+ case 1: \
+ ret = __GET_SIPC_FN_NAME(fn, 1);\
+ break; \
+ case 2: \
+ ret = __GET_SIPC_FN_NAME(fn, 2);\
+ break; \
+ default: \
+ break; \
+ } \
+ ret; \
+}) \
+
+#define GET_SIPC_RXIRQ_STATUS_FN(id) __GET_SIPC_FN(sipc_rxirq_status, id)
+#define GET_SIPC_RXIRQ_CLEAR_FN(id) __GET_SIPC_FN(sipc_rxirq_clear, id)
+#define GET_SIPC_TXIRQ_TRIGGER_FN(id) __GET_SIPC_FN(sipc_txirq_trigger, id)
+
+struct sipc_child_node_info {
+ uint8_t dst;
+ char * name;
+
+ uint32_t cp_base;
+ uint32_t cp_size;
+
+ uint32_t ring_base;
+ uint32_t ring_size;
+
+ uint32_t smem_base;
+ uint32_t smem_size;
+
+#ifdef CONFIG_SPRD_MAILBOX
+ uint32_t core_id;
+#else
+ uint32_t ap2cp_int_ctrl;
+ uint32_t cp2ap_int_ctrl;
+ uint32_t ap2cp_bit_trig;
+ uint32_t ap2cp_bit_clr;
+
+ uint32_t irq;
+#endif
+};
+
+struct sipc_init_data {
+ int is_alloc;
+ uint32_t chd_nr;
+ uint32_t smem_base;
+ uint32_t smem_size;
+ struct sipc_child_node_info info_table[0];
+};
+
+struct sipc_device {
+ int status;
+ uint32_t inst_nr;
+ struct sipc_init_data *pdata;
+ struct smsg_ipc *smsg_inst;
+};
+
+struct sipc_device *sipc_dev;
+
+DEFINE_SIPC_TXIRQ_TRIGGER_FN(0)
+DEFINE_SIPC_TXIRQ_TRIGGER_FN(1)
+DEFINE_SIPC_TXIRQ_TRIGGER_FN(2)
+
+DEFINE_SIPC_RXIRQ_CLEAR_FN(0)
+DEFINE_SIPC_RXIRQ_CLEAR_FN(1)
+DEFINE_SIPC_RXIRQ_CLEAR_FN(2)
+
+DEFINE_SIPC_RXIRQ_STATUS_FN(0)
+DEFINE_SIPC_RXIRQ_STATUS_FN(1)
+DEFINE_SIPC_RXIRQ_STATUS_FN(2)
+
+
+static int sipc_create(struct sipc_device *sipc)
+{
+ struct sipc_init_data *pdata = sipc->pdata;
+ struct smsg_ipc *inst;
+ struct sipc_child_node_info *info;
+ size_t base;
+ uint32_t num;
+ int ret, i;
+
+ if (!pdata) {
+ return -ENODEV;
+ }
+
+ num = pdata->chd_nr;
+ inst = sipc->smsg_inst;
+ info = pdata->info_table;
+
+ for (i = 0; i < num; i++) {
+ base = (size_t)ioremap_nocache((uint32_t)info[i].ring_base, info[i].ring_size);
+ if(base == NULL){
+ pr_info("sipc:[%d] ioremap return NULL\n");
+ return ENOMEM;
+ }
+ pr_info("sipc:[%d] after ioremap vbase=0x%lx, pbase=0x%x, size=0x%x\n",
+ i, base, info[i].ring_base, info[i].ring_size);
+ inst[i].txbuf_size = SMSG_TXBUF_SIZE / sizeof(struct smsg);
+ inst[i].txbuf_addr = base + SMSG_TXBUF_ADDR;
+ inst[i].txbuf_rdptr = base + SMSG_TXBUF_RDPTR;
+ inst[i].txbuf_wrptr = base + SMSG_TXBUF_WRPTR;
+
+ inst[i].rxbuf_size = SMSG_RXBUF_SIZE / sizeof(struct smsg);
+ inst[i].rxbuf_addr = base + SMSG_RXBUF_ADDR;
+ inst[i].rxbuf_rdptr = base + SMSG_RXBUF_RDPTR;
+ inst[i].rxbuf_wrptr = base + SMSG_RXBUF_WRPTR;
+
+ ret = smsg_ipc_create(inst[i].dst, &inst[i]);
+
+ pr_info("sipc:[%d] created\n", i);
+ if (ret) {
+ break;
+ }
+ }
+ return ret;
+}
+
+
+static int sipc_parse_dt(struct sipc_init_data **init, struct device *dev)
+{
+ struct device_node *np = dev->of_node, *nchd;
+ struct sipc_init_data *pdata;
+ struct sipc_child_node_info *info;
+ struct resource res;
+ uint32_t data, nr, int_ctrl_phy[2];
+ int ret, i;
+
+ nr = of_get_child_count(np);
+ if (nr == 0 || nr >= SIPC_ID_NR) {
+ return (!nr ? -ENODEV : -EINVAL);
+ }
+ pdata = kzalloc(sizeof(struct sipc_init_data) + nr*sizeof(struct sipc_child_node_info), GFP_KERNEL);
+ if (!pdata) {
+ pr_err("sipc: failed to alloc mem for pdata\n");
+ return -ENOMEM;
+ }
+
+ pdata->is_alloc = 1;
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ goto error;
+ }
+ pdata->chd_nr = nr;
+ pdata->smem_base = (uint32_t)res.start;
+ pdata->smem_size = (uint32_t)(res.end - res.start + 1);
+ pr_info("sipc: chd_nr=%d smem_base=0x%x, smem_size=0x%x\n", pdata->chd_nr, pdata->smem_base, pdata->smem_size);
+
+ i = 0;
+ info = pdata->info_table;
+
+ for_each_child_of_node(np, nchd) {
+ ret = of_property_read_string(nchd, "sprd,name", (const char **)&info[i].name);
+ if (ret) {
+ goto error;
+ }
+ pr_info("sipc:[%d] name=%s\n", i, info[i].name);
+
+ ret = of_property_read_u32(nchd, "sprd,dst", &data);
+ if (ret) {
+ goto error;
+ }
+ info[i].dst = (uint8_t)data;
+ pr_info("sipc:[%d] dst=%u\n", i, info[i].dst);
+
+#ifndef CONFIG_SPRD_MAILBOX
+ /* get ipi base addr */
+ ret = of_property_read_u32(nchd, "sprd,ap2cp", &int_ctrl_phy[0]);
+ if (ret) {
+ goto error;
+ }
+ info[i].ap2cp_int_ctrl = SPRD_DEV_P2V(int_ctrl_phy[0]);
+
+ ret = of_property_read_u32(nchd, "sprd,cp2ap", &int_ctrl_phy[1]);
+ if (ret) {
+ goto error;
+ }
+ info[i].cp2ap_int_ctrl = SPRD_DEV_P2V(int_ctrl_phy[1]);
+
+ pr_info("sipc:[%d] ap2cp_int_ctrl=0x%lx, ap2cp_ctrl_phy=0x%x, cp2ap_int_ctrl=0x%lx, cp2ap_ctrl_phy=0x%x\n",
+ i, info[i].ap2cp_int_ctrl, int_ctrl_phy[0], info[i].cp2ap_int_ctrl, int_ctrl_phy[1]);
+
+ ret = of_property_read_u32(nchd, "sprd,trig", &info[i].ap2cp_bit_trig);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(nchd, "sprd,clr", &info[i].ap2cp_bit_clr);
+ if (ret) {
+ goto error;
+ }
+ pr_info("sipc:[%d] ap2cp_bit_trig=0x%x, ap2cp_bit_clr=0x%x\n",
+ i, info[i].ap2cp_bit_trig, info[i].ap2cp_bit_clr);
+#endif
+ if (strcmp(info[i].name, "sipc-pmsys") != 0) {
+ /* get cp base addr */
+ ret = of_address_to_resource(nchd, 0, &res);
+ if (ret) {
+ goto error;
+ }
+ info[i].cp_base = (uint32_t)res.start;
+ info[i].cp_size = (uint32_t)(res.end - res.start + 1);
+ pr_info("sipc:[%d] cp_base=0x%x, cp_size=0x%x\n",
+ i, info[i].cp_base, info[i].cp_size);
+
+ /* get smem base addr */
+ ret = of_address_to_resource(nchd, 1, &res);
+ if (ret) {
+ goto error;
+ }
+ info[i].smem_base = (uint32_t)res.start;
+ info[i].smem_size = (uint32_t)(res.end - res.start + 1);
+ pr_info("sipc:[%d] smem_base=0x%x, smem_size=0x%x\n", i, info[i].smem_base, info[i].smem_size);
+
+ /* get ring base addr */
+ ret = of_address_to_resource(nchd, 2, &res);
+ if (ret) {
+ goto error;
+ }
+ info[i].ring_base = (uint32_t)res.start;
+ info[i].ring_size = (uint32_t)(res.end - res.start + 1);
+ pr_info("sipc:[%d] ring_base=0x%x, ring_size=0x%x\n", i, info[i].ring_base, info[i].ring_size);
+ } else {
+ /* get ring base addr */
+ ret = of_address_to_resource(nchd, 0, &res);
+ if (ret) {
+ goto error;
+ }
+ info[i].ring_base = (uint32_t)res.start;
+ info[i].ring_size = (uint32_t)(res.end - res.start + 1);
+ pr_info("sipc:[%d] pmsys ring_base=0x%x, ring_size=0x%x\n", i, info[i].ring_base, info[i].ring_size);
+ }
+
+#ifdef CONFIG_SPRD_MAILBOX
+ ret = of_property_read_u32(nchd, "mailbox,core", &info[i].core_id);
+ if (ret) {
+ goto error;
+ }
+ pr_info("sipc:[%d] core_id=%u\n", i, info[i].core_id);
+#else
+ /* get irq */
+ info[i].irq = irq_of_parse_and_map(nchd, 0);
+ if (!info[i].irq) {
+ ret = -EINVAL;
+ goto error;
+ }
+ pr_info("sipc:[%d] irq=%d\n", i, info[i].irq);
+#endif
+ i++;
+ }
+ *init = pdata;
+ return 0;
+error:
+ kfree(pdata);
+ return ret;
+}
+
+static void sipc_destroy_pdata(struct sipc_init_data **ppdata)
+{
+ struct sipc_init_data *pdata = ppdata;
+ if (pdata && pdata->is_alloc) {
+ kfree(pdata);
+ }
+ return;
+}
+
+static int sipc_probe(struct platform_device *pdev)
+{
+ struct sipc_init_data *pdata = pdev->dev.platform_data;
+ struct sipc_device *sipc;
+ struct sipc_child_node_info *info;
+ struct smsg_ipc *smsg;
+ uint32_t num;
+ int ret, i;
+
+ pr_info("sipc:[0] define status=0x%x, clear=0x%x, trigger=0x%x\n",
+ (uint32_t)sipc_rxirq_status0, (uint32_t)sipc_rxirq_clear0, (uint32_t)sipc_txirq_trigger0);
+
+ pr_info("sipc:[1] define status=0x%x, clear=0x%x, trigger=0x%x\n",
+ (uint32_t)sipc_rxirq_status1, (uint32_t)sipc_rxirq_clear1, (uint32_t)sipc_txirq_trigger1);
+
+ pr_info("sipc:[2] define status=0x%x, clear=0x%x, trigger=0x%x\n",
+ (uint32_t)sipc_rxirq_status2, (uint32_t)sipc_rxirq_clear2, (uint32_t)sipc_txirq_trigger2);
+
+ if (!pdata && pdev->dev.of_node) {
+ ret = sipc_parse_dt(&pdata, &pdev->dev);
+ if (ret) {
+ pr_err("sipc: failed to parse dt, ret=%d\n", ret);
+ return -ENODEV;
+ }
+ }
+
+ sipc = kzalloc(sizeof(struct sipc_device), GFP_KERNEL);
+ if (!sipc) {
+ sipc_destroy_pdata(&pdata);
+ return -ENOMEM;
+ }
+
+ num = pdata->chd_nr;
+ smsg = kzalloc(num * sizeof(struct smsg_ipc), GFP_KERNEL);
+ if (!smsg)
+ {
+ sipc_destroy_pdata(&pdata);
+ kfree(sipc);
+ return -ENOMEM;
+ }
+ pr_info("sipc: num=%d\n", num);
+ info = pdata->info_table;
+ for (i = 0; i < num; i++) {
+ smsg[i].name = info[i].name;
+ smsg[i].dst = info[i].dst;
+#ifdef CONFIG_SPRD_MAILBOX
+ smsg[i].core_id = info[i].core_id;
+#else
+ smsg[i].irq = info[i].irq;
+#endif
+ smsg[i].rxirq_status = GET_SIPC_RXIRQ_STATUS_FN(i);
+ smsg[i].rxirq_clear = GET_SIPC_RXIRQ_CLEAR_FN(i);
+ smsg[i].txirq_trigger = GET_SIPC_TXIRQ_TRIGGER_FN(i);
+
+ pr_info("sipc: [%d] fn status=0x%x, clear=0x%x, trigger=0x%x\n",
+ i, (uint32_t)smsg[i].rxirq_status, (uint32_t)smsg[i].rxirq_clear,
+ (uint32_t)smsg[i].txirq_trigger);
+#ifdef CONFIG_SPRD_MAILBOX
+ pr_info("sipc:[%d] smsg name=%s, dst=%u, core_id=%d\n",
+ i, smsg[i].name, smsg[i].dst, smsg[i].core_id);
+#else
+ pr_info("sipc:[%d] smsg name=%s, dst=%u, irq=%d\n",
+ i, smsg[i].name, smsg[i].dst, smsg[i].irq);
+#endif
+ }
+
+ sipc->pdata = pdata;
+ sipc->smsg_inst = smsg;
+ sipc_dev = sipc;
+
+ smsg_suspend_init();
+
+ sipc_create(sipc);
+
+ pr_info("sipc: smem_base=0x%x, smem_size=0x%x\n", pdata->smem_base, pdata->smem_size);
+ smem_init(pdata->smem_base, pdata->smem_size);
+
+ platform_set_drvdata(pdev, sipc);
+ return 0;
+}
+
+static int sipc_remove(struct platform_device *pdev)
+{
+ struct sipc_device *sipc = platform_get_drvdata(pdev);
+
+ sipc_destroy_pdata(&sipc->pdata);
+ kfree(sipc->smsg_inst);
+ kfree(sipc);
+ return 0;
+}
+
+
+static const struct of_device_id sipc_match_table[] = {
+ {.compatible = "sprd,sipc", },
+ { },
+};
+
+static struct platform_driver sipc_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sipc",
+ .of_match_table = sipc_match_table,
+ },
+ .probe = sipc_probe,
+ .remove = sipc_remove,
+};
+
+static int __init sipc_init(void)
+{
+ return platform_driver_register(&sipc_driver);
+}
+
+static void __exit sipc_exit(void)
+{
+ platform_driver_unregister(&sipc_driver);
+}
+
+arch_initcall(sipc_init);
+module_exit(sipc_exit);
+
+MODULE_AUTHOR("Qiu Yi");
+MODULE_DESCRIPTION("SIPC module driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/sipc_debugfs.c b/drivers/sipc/sipc_debugfs.c
new file mode 100644
index 00000000..fae779f6
--- /dev/null
+++ b/drivers/sipc/sipc_debugfs.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <asm/uaccess.h>
+
+#include <linux/sipc.h>
+
+#if defined(CONFIG_DEBUG_FS)
+
+extern int sbuf_init_debugfs( void *root );
+extern int smsg_init_debugfs( void *root );
+extern int sblock_init_debugfs( void *root );
+extern int smem_init_debugfs( void *root );
+
+static int __init sipc_init_debugfs( void)
+{
+ struct dentry *root = debugfs_create_dir("sipc", NULL);
+ if (!root)
+ return -ENXIO;
+
+ smsg_init_debugfs(root);
+ sbuf_init_debugfs(root);
+ sblock_init_debugfs(root);
+ smem_init_debugfs(root);
+ return 0;
+}
+
+__initcall(sipc_init_debugfs);
+
+#endif /* CONFIG_DEBUG_FS */
diff --git a/drivers/sipc/smem.c b/drivers/sipc/smem.c
new file mode 100644
index 00000000..550f4411
--- /dev/null
+++ b/drivers/sipc/smem.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/genalloc.h>
+#include <linux/mm.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/sched.h>
+
+#include <linux/sipc.h>
+
+struct smem_pool {
+ struct list_head smem_head;
+ spinlock_t lock;
+
+ uint32_t addr;
+ uint32_t size;
+ atomic_t used;
+
+ struct gen_pool *gen;
+};
+
+struct smem_record {
+ struct list_head smem_list;
+ struct task_struct *task;
+ uint32_t size;
+ uint32_t addr;
+};
+
+static struct smem_pool mem_pool;
+
+int smem_init(uint32_t addr, uint32_t size)
+{
+ struct smem_pool *spool = &mem_pool;
+
+ spool->addr = addr;
+ spool->size = PAGE_ALIGN(size);
+ atomic_set(&spool->used, 0);
+ spin_lock_init(&spool->lock);
+ INIT_LIST_HEAD(&spool->smem_head);
+
+ /* allocator block size is times of pages */
+ spool->gen = gen_pool_create(PAGE_SHIFT, -1);
+ if (!spool->gen) {
+ printk(KERN_ERR "Failed to create smem gen pool!\n");
+ return -1;
+ }
+
+ if (gen_pool_add(spool->gen, spool->addr, spool->size, -1) != 0) {
+ printk(KERN_ERR "Failed to add smem gen pool!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/* ****************************************************************** */
+
+uint32_t smem_alloc(uint32_t size)
+{
+ struct smem_pool *spool = &mem_pool;
+ struct smem_record *recd;
+ unsigned long flags;
+ uint32_t addr;
+
+ recd = kzalloc(sizeof(struct smem_record), GFP_KERNEL);
+ if (!recd) {
+ printk(KERN_ERR "failed to alloc smem record\n");
+ addr = 0;
+ goto error;
+ }
+
+ size = PAGE_ALIGN(size);
+ addr = gen_pool_alloc(spool->gen, size);
+ if (!addr) {
+ printk(KERN_ERR "failed to alloc smem from gen pool\n");
+ kfree(recd);
+ goto error;
+ }
+
+ /* record smem alloc info */
+ atomic_add(size, &spool->used);
+ recd->size = size;
+ recd->task = current;
+ recd->addr = addr;
+ spin_lock_irqsave(&spool->lock, flags);
+ list_add_tail(&recd->smem_list, &spool->smem_head);
+ spin_unlock_irqrestore(&spool->lock, flags);
+
+error:
+ return addr;
+}
+
+void smem_free(uint32_t addr, uint32_t size)
+{
+ struct smem_pool *spool = &mem_pool;
+ struct smem_record *recd, *next;
+ unsigned long flags;
+
+ size = PAGE_ALIGN(size);
+ atomic_sub(size, &spool->used);
+ gen_pool_free(spool->gen, addr, size);
+ /* delete record node from list */
+ spin_lock_irqsave(&spool->lock, flags);
+ list_for_each_entry_safe(recd, next, &spool->smem_head, smem_list) {
+ if (recd->addr == addr) {
+ list_del(&recd->smem_list);
+ kfree(recd);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&spool->lock, flags);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int smem_debug_show(struct seq_file *m, void *private)
+{
+ struct smem_pool *spool = &mem_pool;
+ struct smem_record *recd;
+ uint32_t fsize;
+ unsigned long flags;
+
+ fsize = gen_pool_avail(spool->gen);
+
+ seq_printf(m, "smem pool infomation:\n");
+ seq_printf(m, "phys_addr=0x%x, total=0x%x, used=0x%x, free=0x%x\n",
+ spool->addr, spool->size, spool->used, fsize);
+ seq_printf(m, "smem record list:\n");
+
+ spin_lock_irqsave(&spool->lock, flags);
+ list_for_each_entry(recd, &spool->smem_head, smem_list) {
+ seq_printf(m, "task %s: pid=%u, addr=0x%x, size=0x%x\n",
+ recd->task->comm, recd->task->pid, recd->addr, recd->size);
+ }
+ spin_unlock_irqrestore(&spool->lock, flags);
+ return 0;
+}
+
+static int smem_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, smem_debug_show, inode->i_private);
+}
+
+static const struct file_operations smem_debug_fops = {
+ .open = smem_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int smem_init_debugfs(void *root)
+{
+ if (!root)
+ return -ENXIO;
+ debugfs_create_file("smem", S_IRUGO, (struct dentry *)root, NULL, &smem_debug_fops);
+ return 0;
+}
+
+#endif //endof CONFIG_DEBUG_FS
+
+EXPORT_SYMBOL(smem_alloc);
+EXPORT_SYMBOL(smem_free);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SMEM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/smsg.c b/drivers/sipc/smsg.c
new file mode 100644
index 00000000..437189dc
--- /dev/null
+++ b/drivers/sipc/smsg.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#ifdef CONFIG_SPRD_MAILBOX
+#include <soc/sprd/mailbox.h>
+#endif
+
+#include <linux/sipc.h>
+#include <linux/sipc_priv.h>
+#include <linux/wakelock.h>
+#include <linux/syscore_ops.h>
+
+static struct smsg_ipc *smsg_ipcs[SIPC_ID_NR];
+
+static ushort debug_enable = 0;
+
+static struct wake_lock sipc_wake_lock;
+
+module_param_named(debug_enable, debug_enable, ushort, 0644);
+
+irqreturn_t smsg_irq_handler(int irq, void *dev_id)
+{
+ struct smsg_ipc *ipc = (struct smsg_ipc *)dev_id;
+ struct smsg *msg;
+ struct smsg_channel *ch;
+ uintptr_t rxpos;
+ uint32_t wr;
+
+ if (ipc->rxirq_status()) {
+ ipc->rxirq_clear();
+ }
+
+ while (readl(ipc->rxbuf_wrptr) != readl(ipc->rxbuf_rdptr)) {
+ rxpos = (readl(ipc->rxbuf_rdptr) & (ipc->rxbuf_size - 1)) *
+ sizeof (struct smsg) + ipc->rxbuf_addr;
+ msg = (struct smsg *)rxpos;
+
+ pr_debug("irq get smsg: wrptr=%u, rdptr=%u, rxpos=0x%lx\n",
+ readl(ipc->rxbuf_wrptr), readl(ipc->rxbuf_rdptr), rxpos);
+ pr_debug("irq read smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ if(msg->type == SMSG_TYPE_DIE) {
+ if(debug_enable)
+ panic("cpcrash");
+ else {
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+ }
+ if (msg->channel >= SMSG_CH_NR || msg->type >= SMSG_TYPE_NR) {
+ /* invalid msg */
+ printk(KERN_ERR "invalid smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+
+ ch = ipc->channels[msg->channel];
+ if (!ch) {
+ if (ipc->states[msg->channel] == CHAN_STATE_UNUSED &&
+ msg->type == SMSG_TYPE_OPEN &&
+ msg->flag == SMSG_OPEN_MAGIC) {
+
+ ipc->states[msg->channel] = CHAN_STATE_WAITING;
+ } else {
+ /* drop this bad msg since channel is not opened */
+ printk(KERN_ERR "smsg channel %d not opened! "
+ "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ }
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ continue;
+ }
+
+ atomic_inc(&(ipc->busy[msg->channel]));
+
+ if ((int)(readl(ch->wrptr) - readl(ch->rdptr)) >= SMSG_CACHE_NR) {
+ /* msg cache is full, drop this msg */
+ printk(KERN_ERR "smsg channel %d recv cache is full! "
+ "drop smsg: type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+ } else {
+ /* write smsg to cache */
+ wr = readl(ch->wrptr) & (SMSG_CACHE_NR - 1);
+ memcpy(&(ch->caches[wr]), msg, sizeof(struct smsg));
+ writel(readl(ch->wrptr) + 1, ch->wrptr);
+ }
+
+ /* update smsg rdptr */
+ writel(readl(ipc->rxbuf_rdptr) + 1, ipc->rxbuf_rdptr);
+
+ wake_up_interruptible_all(&(ch->rxwait));
+
+ atomic_dec(&(ipc->busy[msg->channel]));
+ }
+
+ wake_lock_timeout(&sipc_wake_lock, HZ / 2);
+
+ return IRQ_HANDLED;
+}
+
+int smsg_ipc_create(uint8_t dst, struct smsg_ipc *ipc)
+{
+ int rval;
+
+ if (!ipc->irq_handler) {
+ ipc->irq_handler = smsg_irq_handler;
+ }
+
+ spin_lock_init(&(ipc->txpinlock));
+
+ smsg_ipcs[dst] = ipc;
+
+#ifdef CONFIG_SPRD_MAILBOX
+ /* explicitly call irq handler in case of missing irq on boot */
+ ipc->irq_handler(ipc->core_id, ipc);
+
+ rval = mbox_register_irq_handle(ipc->core_id, ipc->irq_handler, ipc);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to register irq handler in mailbox: %s\n",
+ ipc->name);
+ return rval;
+ }
+#else
+ /* explicitly call irq handler in case of missing irq on boot */
+ ipc->irq_handler(ipc->irq, ipc);
+
+ /* register IPI irq */
+ rval = request_irq(ipc->irq, ipc->irq_handler,
+ IRQF_NO_SUSPEND, ipc->name, ipc);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to request irq %s: %d\n",
+ ipc->name, ipc->irq);
+ return rval;
+ }
+#endif
+ return 0;
+}
+
+int smsg_ipc_destroy(uint8_t dst)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+
+ kthread_stop(ipc->thread);
+ free_irq(ipc->irq, ipc);
+ smsg_ipcs[dst] = NULL;
+
+ return 0;
+}
+
+/* ****************************************************************** */
+
+int smsg_ch_open(uint8_t dst, uint8_t channel, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch;
+ struct smsg mopen, mrecv;
+ int rval = 0;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ ch = kzalloc(sizeof(struct smsg_channel), GFP_KERNEL);
+ if (!ch) {
+ return -ENOMEM;
+ }
+
+ atomic_set(&(ipc->busy[channel]), 1);
+ init_waitqueue_head(&(ch->rxwait));
+ mutex_init(&(ch->rxlock));
+ ipc->channels[channel] = ch;
+
+ smsg_set(&mopen, channel, SMSG_TYPE_OPEN, SMSG_OPEN_MAGIC, 0);
+ rval = smsg_send(dst, &mopen, timeout);
+ if (rval != 0) {
+ printk(KERN_ERR "smsg_ch_open smsg send error, errno %d!\n", rval);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return rval;
+ }
+
+ /* open msg might be got before */
+ if (ipc->states[channel] == CHAN_STATE_WAITING) {
+ goto open_done;
+ }
+
+ smsg_set(&mrecv, channel, 0, 0, 0);
+ rval = smsg_recv(dst, &mrecv, timeout);
+ if (rval != 0) {
+ printk(KERN_ERR "smsg_ch_open smsg receive error, errno %d!\n", rval);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return rval;
+ }
+
+ if (mrecv.type != SMSG_TYPE_OPEN || mrecv.flag != SMSG_OPEN_MAGIC) {
+ printk(KERN_ERR "Got bad open msg on channel %d-%d\n", dst, channel);
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+ ipc->channels[channel] = NULL;
+ atomic_dec(&(ipc->busy[channel]));
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+
+ return -EIO;
+ }
+
+open_done:
+ ipc->states[channel] = CHAN_STATE_OPENED;
+ atomic_dec(&(ipc->busy[channel]));
+
+ return 0;
+}
+
+int smsg_ch_close(uint8_t dst, uint8_t channel, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch = ipc->channels[channel];
+ struct smsg mclose;
+
+ if (!ch) {
+ return 0;
+ }
+
+ smsg_set(&mclose, channel, SMSG_TYPE_CLOSE, SMSG_CLOSE_MAGIC, 0);
+ smsg_send(dst, &mclose, timeout);
+
+ ipc->states[channel] = CHAN_STATE_FREE;
+ wake_up_interruptible_all(&(ch->rxwait));
+
+ /* wait for the channel beeing unused */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+
+ /* maybe channel has been free for smsg_ch_open failed */
+ if (ipc->channels[channel]){
+ ipc->channels[channel] = NULL;
+ /* guarantee that channel resource isn't used in irq handler */
+ while(atomic_read(&(ipc->busy[channel]))) {
+ ;
+ }
+ kfree(ch);
+ }
+
+ /* finally, update the channel state*/
+ ipc->states[channel] = CHAN_STATE_UNUSED;
+
+ return 0;
+}
+
+int smsg_send(uint8_t dst, struct smsg *msg, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ uintptr_t txpos;
+ int rval = 0;
+ unsigned long flags;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ if (!ipc->channels[msg->channel]) {
+ printk(KERN_ERR "channel %d not inited!\n", msg->channel);
+ return -ENODEV;
+ }
+
+ if (ipc->states[msg->channel] != CHAN_STATE_OPENED &&
+ msg->type != SMSG_TYPE_OPEN &&
+ msg->type != SMSG_TYPE_CLOSE) {
+ printk(KERN_ERR "channel %d not opened!\n", msg->channel);
+ return -EINVAL;
+ }
+
+ pr_debug("smsg_send: dst=%d, channel=%d, timeout=%d\n",
+ dst, msg->channel, timeout);
+ pr_debug("send smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+ spin_lock_irqsave(&(ipc->txpinlock), flags);
+ if ((int)(readl(ipc->txbuf_wrptr) -
+ readl(ipc->txbuf_rdptr)) >= ipc->txbuf_size) {
+ printk(KERN_WARNING "smsg txbuf is full!\n");
+ rval = -EBUSY;
+ goto send_failed;
+ }
+
+ /* calc txpos and write smsg */
+ txpos = (readl(ipc->txbuf_wrptr) & (ipc->txbuf_size - 1)) *
+ sizeof(struct smsg) + ipc->txbuf_addr;
+ memcpy((void *)txpos, msg, sizeof(struct smsg));
+
+ pr_debug("write smsg: wrptr=%u, rdptr=%u, txpos=0x%lx\n",
+ readl(ipc->txbuf_wrptr),
+ readl(ipc->txbuf_rdptr), txpos);
+
+ /* update wrptr */
+ writel(readl(ipc->txbuf_wrptr) + 1, ipc->txbuf_wrptr);
+ ipc->txirq_trigger();
+
+send_failed:
+ spin_unlock_irqrestore(&(ipc->txpinlock), flags);
+
+ return rval;
+}
+
+int smsg_recv(uint8_t dst, struct smsg *msg, int timeout)
+{
+ struct smsg_ipc *ipc = smsg_ipcs[dst];
+ struct smsg_channel *ch;
+ uint32_t rd;
+ int rval = 0;
+
+ if(!ipc) {
+ return -ENODEV;
+ }
+
+ atomic_inc(&(ipc->busy[msg->channel]));
+
+ ch = ipc->channels[msg->channel];
+
+ if (!ch) {
+ printk(KERN_ERR "channel %d not opened!\n", msg->channel);
+ atomic_dec(&(ipc->busy[msg->channel]));
+ return -ENODEV;
+ }
+
+ pr_debug("smsg_recv: dst=%d, channel=%d, timeout=%d\n",
+ dst, msg->channel, timeout);
+
+
+ if (timeout == 0) {
+ if (!mutex_trylock(&(ch->rxlock))) {
+ printk(KERN_INFO "recv smsg busy!\n");
+ atomic_dec(&(ipc->busy[msg->channel]));
+
+ return -EBUSY;
+ }
+
+ /* no wait */
+ if (readl(ch->wrptr) == readl(ch->rdptr)) {
+ printk(KERN_WARNING "smsg rx cache is empty!\n");
+ rval = -ENODATA;
+
+ goto recv_failed;
+ }
+ } else if (timeout < 0) {
+ mutex_lock(&(ch->rxlock));
+ /* wait forever */
+ rval = wait_event_interruptible(ch->rxwait,
+ (readl(ch->wrptr) != readl(ch->rdptr)) ||
+ (ipc->states[msg->channel] == CHAN_STATE_FREE));
+ if (rval < 0) {
+ printk(KERN_WARNING "smsg_recv wait interrupted!\n");
+
+ goto recv_failed;
+ }
+
+ if (ipc->states[msg->channel] == CHAN_STATE_FREE) {
+ printk(KERN_WARNING "smsg_recv smsg channel is free!\n");
+ rval = -EIO;
+
+ goto recv_failed;
+ }
+ } else {
+ mutex_lock(&(ch->rxlock));
+ /* wait timeout */
+ rval = wait_event_interruptible_timeout(ch->rxwait,
+ (readl(ch->wrptr) != readl(ch->rdptr)) ||
+ (ipc->states[msg->channel] == CHAN_STATE_FREE), timeout);
+ if (rval < 0) {
+ printk(KERN_WARNING "smsg_recv wait interrupted!\n");
+
+ goto recv_failed;
+ } else if (rval == 0) {
+ printk(KERN_WARNING "smsg_recv wait timeout!\n");
+ rval = -ETIME;
+
+ goto recv_failed;
+ }
+
+ if (ipc->states[msg->channel] == CHAN_STATE_FREE) {
+ printk(KERN_ERR "smsg_recv smsg channel is free!\n");
+ rval = -EIO;
+
+ goto recv_failed;
+ }
+ }
+
+ /* read smsg from cache */
+ rd = readl(ch->rdptr) & (SMSG_CACHE_NR - 1);
+ memcpy(msg, &(ch->caches[rd]), sizeof(struct smsg));
+ writel(readl(ch->rdptr) + 1, ch->rdptr);
+
+ pr_debug("read smsg: wrptr=%d, rdptr=%d, rd=%d\n",
+ readl(ch->wrptr), readl(ch->rdptr), rd);
+ pr_debug("recv smsg: channel=%d, type=%d, flag=0x%04x, value=0x%08x\n",
+ msg->channel, msg->type, msg->flag, msg->value);
+
+recv_failed:
+ mutex_unlock(&(ch->rxlock));
+ atomic_dec(&(ipc->busy[msg->channel]));
+ return rval;
+}
+
+
+#if defined(CONFIG_DEBUG_FS)
+static int smsg_debug_show(struct seq_file *m, void *private)
+{
+ struct smsg_ipc *smsg_sipc = NULL;
+ int i, j;
+
+ for (i = 0; i < SIPC_ID_NR; i++) {
+ smsg_sipc = smsg_ipcs[i];
+ if (!smsg_sipc) {
+ continue;
+ }
+ seq_printf(m, "sipc: %s: \n", smsg_sipc->name);
+ seq_printf(m, "dst: 0x%0x, irq: 0x%0x\n",
+ smsg_sipc->dst, smsg_sipc->irq);
+ seq_printf(m, "txbufAddr: 0x%0x, txbufsize: 0x%0x, txbufrdptr: [0x%0x]=%lu, txbufwrptr: [0x%0x]=%lu\n",
+ smsg_sipc->txbuf_addr, smsg_sipc->txbuf_size, smsg_sipc->txbuf_rdptr, readl(smsg_sipc->txbuf_rdptr), smsg_sipc->txbuf_wrptr, readl(smsg_sipc->txbuf_wrptr));
+ seq_printf(m, "rxbufAddr: 0x%0x, rxbufsize: 0x%0x, rxbufrdptr: [0x%0x]=%lu, rxbufwrptr: [0x%0x]=%lu\n",
+ smsg_sipc->rxbuf_addr, smsg_sipc->rxbuf_size, smsg_sipc->rxbuf_rdptr, readl(smsg_sipc->rxbuf_rdptr), smsg_sipc->rxbuf_wrptr, readl(smsg_sipc->rxbuf_wrptr));
+
+ for (j=0; j<SMSG_CH_NR; j++) {
+ seq_printf(m, "channel[%d] states: %d\n", j, smsg_sipc->states[j]);
+ }
+ }
+ return 0;
+}
+
+static int smsg_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, smsg_debug_show, inode->i_private);
+}
+
+static const struct file_operations smsg_debug_fops = {
+ .open = smsg_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int smsg_init_debugfs(void *root )
+{
+ if (!root)
+ return -ENXIO;
+ debugfs_create_file("smsg", S_IRUGO, (struct dentry *)root, NULL, &smsg_debug_fops);
+ return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int sipc_syscore_suspend(void)
+{
+ int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0;
+ return ret;
+}
+
+static struct syscore_ops sipc_syscore_ops = {
+ .suspend = sipc_syscore_suspend,
+};
+
+int smsg_suspend_init(void)
+{
+ wake_lock_init(&sipc_wake_lock, WAKE_LOCK_SUSPEND, "sipc-smsg");
+ register_syscore_ops(&sipc_syscore_ops);
+
+ return 0;
+}
+
+
+EXPORT_SYMBOL(smsg_ch_open);
+EXPORT_SYMBOL(smsg_ch_close);
+EXPORT_SYMBOL(smsg_send);
+EXPORT_SYMBOL(smsg_recv);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SMSG driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/spipe.c b/drivers/sipc/spipe.c
new file mode 100644
index 00000000..0cee34fb
--- /dev/null
+++ b/drivers/sipc/spipe.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#endif
+
+#include <linux/sipc.h>
+#include <linux/spipe.h>
+
+struct spipe_device {
+ struct spipe_init_data *init;
+ int major;
+ int minor;
+ struct cdev cdev;
+};
+
+struct spipe_sbuf {
+ uint8_t dst;
+ uint8_t channel;
+ uint32_t bufid;
+};
+
+static struct class *spipe_class;
+
+static int spipe_open(struct inode *inode, struct file *filp)
+{
+ int minor = iminor(filp->f_path.dentry->d_inode);
+ struct spipe_device *spipe;
+ struct spipe_sbuf *sbuf;
+
+ spipe = container_of(inode->i_cdev, struct spipe_device, cdev);
+ if (sbuf_status(spipe->init->dst, spipe->init->channel) != 0) {
+ printk(KERN_ERR "spipe %d-%d not ready to open!\n", spipe->init->dst, spipe->init->channel);
+ filp->private_data = NULL;
+ return -ENODEV;
+ }
+
+ sbuf = kmalloc(sizeof(struct spipe_sbuf), GFP_KERNEL);
+ if (!sbuf) {
+ return -ENOMEM;
+ }
+ filp->private_data = sbuf;
+
+ sbuf->dst = spipe->init->dst;
+ sbuf->channel = spipe->init->channel;
+ sbuf->bufid = minor - spipe->minor;
+
+ return 0;
+}
+
+static int spipe_release(struct inode *inode, struct file *filp)
+{
+ struct spipe_sbuf *sbuf = filp->private_data;
+
+ if (sbuf) {
+ kfree(sbuf);
+ }
+
+ return 0;
+}
+
+static ssize_t spipe_read(struct file *filp,
+ char __user *buf, size_t count, loff_t *ppos)
+{
+ struct spipe_sbuf *sbuf = filp->private_data;
+ int timeout = -1;
+
+ if (filp->f_flags & O_NONBLOCK) {
+ timeout = 0;
+ }
+
+ return sbuf_read(sbuf->dst, sbuf->channel, sbuf->bufid,
+ (void *)buf, count, timeout);
+}
+
+static ssize_t spipe_write(struct file *filp,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct spipe_sbuf *sbuf = filp->private_data;
+ int timeout = -1;
+
+ if (filp->f_flags & O_NONBLOCK) {
+ timeout = 0;
+ }
+
+ return sbuf_write(sbuf->dst, sbuf->channel, sbuf->bufid,
+ (void *)buf, count, timeout);
+}
+
+static unsigned int spipe_poll(struct file *filp, poll_table *wait)
+{
+ struct spipe_sbuf *sbuf = filp->private_data;
+
+ return sbuf_poll_wait(sbuf->dst, sbuf->channel, sbuf->bufid,
+ filp, wait);
+}
+
+static long spipe_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return 0;
+}
+
+static const struct file_operations spipe_fops = {
+ .open = spipe_open,
+ .release = spipe_release,
+ .read = spipe_read,
+ .write = spipe_write,
+ .poll = spipe_poll,
+ .unlocked_ioctl = spipe_ioctl,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
+static int spipe_parse_dt(struct spipe_init_data **init, struct device *dev)
+{
+#ifdef CONFIG_OF
+ struct device_node *np = dev->of_node;
+ struct spipe_init_data *pdata = NULL;
+ int ret;
+ uint32_t data;
+
+ pdata = kzalloc(sizeof(struct spipe_init_data), GFP_KERNEL);
+ if (!pdata) {
+ printk(KERN_ERR "Failed to allocate pdata memory\n");
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_string(np, "sprd,name", (const char**)&pdata->name);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->dst = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->channel = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,ringnr", (uint32_t *)&pdata->ringnr);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np, "sprd,size-rxbuf", (uint32_t *)&pdata->rxbuf_size);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np, "sprd,size-txbuf", (uint32_t *)&pdata->txbuf_size);
+ if (ret) {
+ goto error;
+ }
+ *init = pdata;
+ return ret;
+error:
+ kfree(pdata);
+ *init = NULL;
+ return ret;
+#else
+ return -ENODEV;
+#endif
+}
+
+static inline void spipe_destroy_pdata(struct spipe_init_data **init)
+{
+#ifdef CONFIG_OF
+ struct spipe_init_data *pdata = *init;
+
+ if (pdata) {
+ kfree(pdata);
+ }
+
+ *init = NULL;
+#else
+ return;
+#endif
+}
+
+static int spipe_probe(struct platform_device *pdev)
+{
+ struct spipe_init_data *init = pdev->dev.platform_data;
+ struct spipe_device *spipe;
+ dev_t devid;
+ int i, rval;
+
+ if (pdev->dev.of_node && !init) {
+ rval = spipe_parse_dt(&init, &pdev->dev);
+ if (rval) {
+ printk(KERN_ERR "Failed to parse spipe device tree, ret=%d\n", rval);
+ return rval;
+ }
+ }
+ pr_info("spipe: after parse device tree, name=%s, dst=%u, channel=%u, ringnr=%u, rxbuf_size=%u, txbuf_size=%u\n",
+ init->name, init->dst, init->channel, init->ringnr, init->rxbuf_size, init->txbuf_size);
+
+
+ rval = sbuf_create(init->dst, init->channel, init->ringnr,
+ init->txbuf_size, init->rxbuf_size);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to create sbuf: %d\n", rval);
+ spipe_destroy_pdata(&init);
+ return rval;
+ }
+
+ spipe = kzalloc(sizeof(struct spipe_device), GFP_KERNEL);
+ if (spipe == NULL) {
+ sbuf_destroy(init->dst, init->channel);
+ spipe_destroy_pdata(&init);
+ printk(KERN_ERR "Failed to allocate spipe_device\n");
+ return -ENOMEM;
+ }
+
+ rval = alloc_chrdev_region(&devid, 0, init->ringnr, init->name);
+ if (rval != 0) {
+ sbuf_destroy(init->dst, init->channel);
+ kfree(spipe);
+ spipe_destroy_pdata(&init);
+ printk(KERN_ERR "Failed to alloc spipe chrdev\n");
+ return rval;
+ }
+
+ cdev_init(&(spipe->cdev), &spipe_fops);
+ rval = cdev_add(&(spipe->cdev), devid, init->ringnr);
+ if (rval != 0) {
+ sbuf_destroy(init->dst, init->channel);
+ kfree(spipe);
+ unregister_chrdev_region(devid, init->ringnr);
+ spipe_destroy_pdata(&init);
+ printk(KERN_ERR "Failed to add spipe cdev\n");
+ return rval;
+ }
+
+ spipe->major = MAJOR(devid);
+ spipe->minor = MINOR(devid);
+ if (init->ringnr > 1) {
+ for (i = 0; i < init->ringnr; i++) {
+ device_create(spipe_class, NULL,
+ MKDEV(spipe->major, spipe->minor + i),
+ NULL, "%s%d", init->name, i);
+ }
+ } else {
+ device_create(spipe_class, NULL,
+ MKDEV(spipe->major, spipe->minor),
+ NULL, "%s", init->name);
+ }
+
+ spipe->init = init;
+
+ platform_set_drvdata(pdev, spipe);
+
+ return 0;
+}
+
+static int spipe_remove(struct platform_device *pdev)
+{
+ struct spipe_device *spipe = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < spipe->init->ringnr; i++) {
+ device_destroy(spipe_class,
+ MKDEV(spipe->major, spipe->minor + i));
+ }
+ cdev_del(&(spipe->cdev));
+ unregister_chrdev_region(
+ MKDEV(spipe->major, spipe->minor), spipe->init->ringnr);
+
+ sbuf_destroy(spipe->init->dst, spipe->init->channel);
+
+ spipe_destroy_pdata(&spipe->init);
+
+ kfree(spipe);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id spipe_match_table[] = {
+ {.compatible = "sprd,spipe", },
+ { },
+};
+
+static struct platform_driver spipe_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "spipe",
+ .of_match_table = spipe_match_table,
+ },
+ .probe = spipe_probe,
+ .remove = spipe_remove,
+};
+
+static int __init spipe_init(void)
+{
+ spipe_class = class_create(THIS_MODULE, "spipe");
+ if (IS_ERR(spipe_class))
+ return PTR_ERR(spipe_class);
+
+ return platform_driver_register(&spipe_driver);
+}
+
+static void __exit spipe_exit(void)
+{
+ class_destroy(spipe_class);
+ platform_driver_unregister(&spipe_driver);
+}
+
+module_init(spipe_init);
+module_exit(spipe_exit);
+
+MODULE_AUTHOR("Chen Gaopeng");
+MODULE_DESCRIPTION("SIPC/SPIPE driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/spool.c b/drivers/sipc/spool.c
new file mode 100644
index 00000000..3db07e2e
--- /dev/null
+++ b/drivers/sipc/spool.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/spool.h>
+#include <linux/sipc.h>
+
+struct spool_device {
+ struct spool_init_data *init;
+ int major;
+ int minor;
+ struct cdev cdev;
+};
+
+struct spool_sblock {
+ uint8_t dst;
+ uint8_t channel;
+};
+
+static struct class *spool_class;
+
+static int spool_open(struct inode *inode, struct file *filp)
+{
+ static struct spool_device *spool;
+ struct spool_sblock *sblock;
+
+ spool = container_of(inode->i_cdev, struct spool_device, cdev);
+ sblock = kmalloc(sizeof(struct spool_sblock), GFP_KERNEL);
+ if (!sblock) {
+ return -ENOMEM;
+ }
+ filp->private_data = sblock;
+
+ sblock->dst = spool->init->dst;
+ sblock->channel = spool->init->channel;
+
+ return 0;
+}
+
+static int spool_release(struct inode *inode, struct file *filp)
+{
+ struct spool_sblock *sblock = filp->private_data;
+
+ kfree(sblock);
+
+ return 0;
+}
+
+static ssize_t spool_read(struct file *filp,
+ char __user *buf, size_t count, loff_t *ppos)
+{
+ struct spool_sblock *sblock = filp->private_data;
+ int timeout = -1;
+ int ret = 0;
+ int rdsize = 0;
+ struct sblock blk = {0};
+
+ if (filp->f_flags & O_NONBLOCK) {
+ timeout = 0;
+ }
+
+ if((ret = sblock_receive(sblock->dst, sblock->channel, &blk, timeout)) < 0){
+ pr_debug("spool_read: failed to receive block!\n");
+ return ret;
+ }
+
+ rdsize = blk.length > count ? count : blk.length;
+
+ if(copy_to_user(buf, blk.addr, rdsize)){
+ pr_debug("spool_read: failed to copy to user!\n");
+ ret = -EFAULT;
+ }else{
+ ret = rdsize;
+ }
+
+ if(sblock_release(sblock->dst, sblock->channel, &blk)){
+ pr_debug("failed to release block!\n");
+ }
+
+ return ret;
+}
+
+static ssize_t spool_write(struct file *filp,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct spool_sblock *sblock = filp->private_data;
+ int timeout = -1;
+ int ret = 0;
+ int wrsize = 0;
+ int pos = 0;
+ struct sblock blk = {0};
+ size_t len = count;
+
+ if(filp->f_flags & O_NONBLOCK){
+ timeout = 0;
+ }
+
+ do{
+ if((ret = sblock_get(sblock->dst, sblock->channel, &blk, timeout)) < 0){
+ printk(KERN_WARNING "spool_write: failed to get block!\n");
+ return ret;
+ }
+
+ wrsize = (blk.length > len ? len : blk.length);
+ pr_debug("spool_write: blk_len %d, count %d, wsize %d\n", blk.length, len, wrsize);
+ if(copy_from_user(blk.addr, buf + pos, wrsize)){
+ printk(KERN_WARNING "spool_write: failed to copy from user!\n");
+ ret = -EFAULT;
+ }else{
+ blk.length = wrsize;
+ len -= wrsize;
+ pos += wrsize;
+ }
+
+ if(sblock_send(sblock->dst, sblock->channel, &blk)){
+ pr_debug("spool_write: failed to send block!");
+ }
+
+ pr_debug("spool_write len= %u, ret= %d\n", len, ret);
+ }while(len > 0 && ret == 0);
+
+ return count - len;
+}
+
+static unsigned int spool_poll(struct file *filp, poll_table *wait)
+{
+ return 0;
+}
+
+static long spool_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return 0;
+}
+
+static const struct file_operations spool_fops = {
+ .open = spool_open,
+ .release = spool_release,
+ .read = spool_read,
+ .write = spool_write,
+ .poll = spool_poll,
+ .unlocked_ioctl = spool_ioctl,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
+static int __devinit spool_probe(struct platform_device *pdev)
+{
+ struct spool_init_data *init = pdev->dev.platform_data;
+ struct spool_device *spool;
+ dev_t devid;
+ int rval;
+
+ rval = sblock_create(init->dst, init->channel, init->txblocknum,
+ init->txblocksize, init->rxblocknum, init->rxblocksize);
+ if (rval != 0) {
+ printk(KERN_ERR "Failed to create sblock: %d\n", rval);
+ return rval;
+ }
+
+ spool = kzalloc(sizeof(struct spool_device), GFP_KERNEL);
+ if (spool == NULL) {
+ sblock_destroy(init->dst, init->channel);
+ printk(KERN_ERR "Failed to allocate spool_device\n");
+ return -ENOMEM;
+ }
+
+ rval = alloc_chrdev_region(&devid, 0, 1, init->name);
+ if (rval != 0) {
+ sblock_destroy(init->dst, init->channel);
+ kfree(spool);
+ printk(KERN_ERR "Failed to alloc spool chrdev\n");
+ return rval;
+ }
+ cdev_init(&(spool->cdev), &spool_fops);
+ rval = cdev_add(&(spool->cdev), devid, 1);
+ if (rval != 0) {
+ sblock_destroy(init->dst, init->channel);
+ kfree(spool);
+ unregister_chrdev_region(devid, 1);
+ printk(KERN_ERR "Failed to add spool cdev\n");
+ return rval;
+ }
+
+ spool->major = MAJOR(devid);
+ spool->minor = MINOR(devid);
+
+ device_create(spool_class, NULL,
+ MKDEV(spool->major, spool->minor),
+ NULL, "%s", init->name);
+
+ spool->init = init;
+
+ platform_set_drvdata(pdev, spool);
+
+ return 0;
+}
+
+static int __devexit spool_remove(struct platform_device *pdev)
+{
+ struct spool_device *spool = platform_get_drvdata(pdev);
+ device_destroy(spool_class, MKDEV(spool->major, spool->minor));
+
+ cdev_del(&(spool->cdev));
+ unregister_chrdev_region(
+ MKDEV(spool->major, spool->minor), 1);
+
+ sblock_destroy(spool->init->dst, spool->init->channel);
+ kfree(spool);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver spool_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "spool",
+ },
+ .probe = spool_probe,
+ .remove = __devexit_p(spool_remove),
+};
+
+static int __init spool_init(void)
+{
+ spool_class = class_create(THIS_MODULE, "spool");
+ if (IS_ERR(spool_class))
+ return PTR_ERR(spool_class);
+
+ return platform_driver_register(&spool_driver);
+}
+
+static void __exit spool_exit(void)
+{
+ class_destroy(spool_class);
+ platform_driver_unregister(&spool_driver);
+}
+
+module_init(spool_init);
+module_exit(spool_exit);
+
+MODULE_AUTHOR("Qiu Yi");
+MODULE_DESCRIPTION("SIPC/SPOOL driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/sipc/stty.c b/drivers/sipc/stty.c
new file mode 100644
index 00000000..1f1d5c31
--- /dev/null
+++ b/drivers/sipc/stty.c
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/sipc.h>
+#include <linux/stty.h>
+#include <linux/types.h>
+#include <linux/kdev_t.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#endif
+
+#include <linux/compat.h>
+#include <linux/tty_flip.h>
+#include <linux/kthread.h>
+
+#ifdef CONFIG_SPRD_2351
+#include <linux/sprd_2351.h>
+#endif
+
+
+#define STTY_DEV_MAX_NR 1
+#define STTY_MAX_DATA_LEN 4096
+
+#define STTY_STATE_OPEN 1
+#define STTY_STATE_CLOSE 0
+
+struct stty_device {
+ struct stty_init_data *pdata;
+ struct tty_port *port;
+ struct tty_struct *tty;
+ struct tty_driver *driver;
+
+ /* stty state */
+ uint32_t state;
+ struct mutex stat_lock;
+};
+
+static void stty_handler (int event, void* data)
+{
+ struct stty_device *stty = data;
+ int i, cnt = 0;
+ unsigned char buf[STTY_MAX_DATA_LEN] = {0};
+
+ pr_debug("stty handler event=%d \n", event);
+
+ switch(event) {
+ case SBUF_NOTIFY_WRITE:
+ break;
+ case SBUF_NOTIFY_READ:
+ cnt = sbuf_read(stty->pdata->dst, stty->pdata->channel,
+ stty->pdata->bufid,(void *)buf, STTY_MAX_DATA_LEN, 0);
+ pr_debug("stty handler read data len =%d \n", cnt);
+ mutex_lock(&(stty->stat_lock));
+ if ((stty->state == STTY_STATE_OPEN) && (cnt > 0)) {
+ for(i = 0; i < cnt; i++) {
+ tty_insert_flip_char(stty->port, buf[i], TTY_NORMAL);
+ }
+ tty_schedule_flip(stty->port);
+ }
+ mutex_unlock(&(stty->stat_lock));
+ break;
+ default:
+ printk(KERN_ERR "Received event is invalid(event=%d)\n", event);
+ }
+
+ return;
+}
+
+static int stty_open(struct tty_struct *tty, struct file * filp)
+{
+ struct stty_device *stty = NULL;
+ struct tty_driver *driver = NULL;
+
+ if (tty == NULL) {
+ printk(KERN_ERR "stty open input tty is NULL!\n");
+ return -ENOMEM;
+ }
+ driver = tty->driver;
+ stty = (struct stty_device *)driver->driver_state;
+
+ if(stty == NULL) {
+ printk(KERN_ERR "stty open input stty NULL!\n");
+ return -ENOMEM;
+ }
+
+ if (sbuf_status(stty->pdata->dst, stty->pdata->channel) != 0) {
+ printk(KERN_ERR "stty_open sbuf not ready to open!dst=%d,channel=%d\n"
+ ,stty->pdata->dst,stty->pdata->channel);
+ return -ENODEV;
+ }
+ stty->tty = tty;
+ tty->driver_data = (void *)stty;
+
+ mutex_lock(&(stty->stat_lock));
+ stty->state = STTY_STATE_OPEN;
+ mutex_unlock(&(stty->stat_lock));
+
+#ifdef CONFIG_SPRD_2351
+ rf2351_gpio_ctrl_power_enable(1);
+#endif
+#ifdef CONFIG_ARCH_SCX20
+ rf2351_vddwpa_ctrl_power_enable(1);
+#endif
+
+ pr_debug("stty_open device success! \n");
+
+ return 0;
+}
+
+static void stty_close(struct tty_struct *tty, struct file * filp)
+{
+ struct stty_device *stty = NULL;
+
+ if (tty == NULL) {
+ printk(KERN_ERR "stty close input tty is NULL!\n");
+ return;
+ }
+ stty = (struct stty_device *) tty->driver_data;
+ if (stty == NULL) {
+ printk(KERN_ERR "stty close s tty is NULL!\n");
+ return;
+ }
+
+ mutex_lock(&(stty->stat_lock));
+ stty->state = STTY_STATE_CLOSE;
+ mutex_unlock(&(stty->stat_lock));
+
+ pr_debug("stty_close device success !\n");
+
+#ifdef CONFIG_SPRD_2351
+ rf2351_gpio_ctrl_power_enable(0);
+#endif
+#ifdef CONFIG_ARCH_SCX20
+ rf2351_vddwpa_ctrl_power_enable(0);
+#endif
+
+ return;
+}
+
+static int stty_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct stty_device *stty = tty->driver_data;
+ int cnt;
+ pr_debug("stty write count=%d \n", count);
+
+ cnt = sbuf_write(stty->pdata->dst, stty->pdata->channel,
+ stty->pdata->bufid, (void *)buf, count, -1);
+ return cnt;
+}
+
+
+static void stty_flush_chars(struct tty_struct *tty)
+{
+ return;
+}
+
+static int stty_write_room(struct tty_struct *tty)
+{
+ return INT_MAX;
+}
+
+static const struct tty_operations stty_ops = {
+ .open = stty_open,
+ .close = stty_close,
+ .write = stty_write,
+ .flush_chars = stty_flush_chars,
+ .write_room = stty_write_room,
+};
+
+static struct tty_port *stty_port_init()
+{
+ struct tty_port *port = NULL;
+
+ port = kzalloc(sizeof(struct tty_port),GFP_KERNEL);
+ if (port == NULL) {
+ printk(KERN_ERR "stty_port_init Failed to allocate device!\n");
+ return NULL;
+ }
+ tty_port_init(port);
+ return port;
+}
+
+static int stty_driver_init(struct stty_device *device)
+{
+ struct tty_driver *driver;
+ int ret = 0;
+
+ mutex_init(&(device->stat_lock));
+
+ device->port = stty_port_init();
+ if (!device->port) {
+ return -ENOMEM;
+ }
+
+ driver = alloc_tty_driver(STTY_DEV_MAX_NR);
+ if (!driver)
+ return -ENOMEM;
+ /*
+ * Initialize the tty_driver structure
+ * Entries in stty_driver that are NOT initialized:
+ * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc
+ */
+ driver->owner = THIS_MODULE;
+ driver->driver_name = device->pdata->name;
+ driver->name = device->pdata->name;
+ driver->major = 0;
+ driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ driver->subtype = SYSTEM_TYPE_TTY;
+ driver->init_termios = tty_std_termios;
+ driver->driver_state = (void*)device;
+ device->driver = driver;
+ /* initialize the tty driver */
+ tty_set_operations(driver, &stty_ops);
+ tty_port_link_device(device->port, driver, 0);
+ ret = tty_register_driver(driver);
+ if (ret) {
+ put_tty_driver(driver);
+ tty_port_destroy(device->port);
+ return ret;
+ }
+ return ret;
+}
+
+static void stty_driver_exit(struct stty_device *device)
+{
+ struct tty_driver *driver = device->driver;
+ tty_unregister_driver(driver);
+ tty_port_destroy(device->port);
+}
+
+static int stty_parse_dt(struct stty_init_data **init, struct device *dev)
+{
+#ifdef CONFIG_OF
+ struct of_device_node *np = dev->of_node;
+ struct stty_init_data *pdata = NULL;
+ int ret;
+ uint32_t data;
+
+ pdata = kzalloc(sizeof(struct stty_init_data), GFP_KERNEL);
+ if (!pdata) {
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_string(np, "sprd,name", (const char **)&pdata->name);
+ if (ret) {
+ goto error;
+ }
+
+ ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->dst = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data);
+ if (ret) {
+ goto error;
+ }
+ pdata->channel = (uint8_t)data;
+
+ ret = of_property_read_u32(np, "sprd,bufid", (uint32_t *)&pdata->bufid);
+ if (ret) {
+ goto error;
+ }
+
+ *init = pdata;
+ return 0;
+error:
+ kfree(pdata);
+ *init = NULL;
+ return ret;
+#else
+ return -ENODEV;
+#endif
+}
+
+static inline void stty_destroy_pdata(struct stty_init_data **init)
+{
+#ifdef CONFIG_OF
+ struct stty_init_data *pdata = *init;
+
+ if (pdata) {
+ kfree(pdata);
+ }
+
+ *init = NULL;
+#else
+ return;
+#endif
+}
+
+
+static int stty_probe(struct platform_device *pdev)
+{
+ struct stty_init_data *pdata = (struct stty_init_data*)pdev->dev.platform_data;
+ struct stty_device *stty;
+ int rval= 0;
+
+ if (pdev->dev.of_node && !pdata) {
+ rval = stty_parse_dt(&pdata, &pdev->dev);
+ if (rval) {
+ printk(KERN_ERR "failed to parse styy device tree, ret=%d\n", rval);
+ return rval;
+ }
+ }
+ pr_info("stty: after parse device tree, name=%s, dst=%u, channel=%u, bufid=%u\n",
+ pdata->name, pdata->dst, pdata->channel, pdata->bufid);
+
+ stty = kzalloc(sizeof(struct stty_device),GFP_KERNEL);
+ if (stty == NULL) {
+ stty_destroy_pdata(&pdata);
+ printk(KERN_ERR "stty Failed to allocate device!\n");
+ return -ENOMEM;
+ }
+
+ stty->pdata = pdata;
+ rval = stty_driver_init(stty);
+ if (rval) {
+ kfree(stty);
+ stty_destroy_pdata(&pdata);
+ printk(KERN_ERR "stty driver init error!\n");
+ return -EINVAL;
+ }
+
+ rval = sbuf_register_notifier(pdata->dst, pdata->channel,
+ pdata->bufid, stty_handler, stty);
+ if (rval) {
+ stty_driver_exit(stty);
+ kfree(stty->port);
+ kfree(stty);
+ printk(KERN_ERR "regitster notifier failed (%d)\n", rval);
+ return rval;
+ }
+
+ printk( "stty_probe init device addr: 0x%0x\n", (void *)stty);
+ platform_set_drvdata(pdev, stty);
+
+ return 0;
+}
+
+static int stty_remove(struct platform_device *pdev)
+{
+ struct stty_device *stty = platform_get_drvdata(pdev);
+ int rval;
+
+ rval = sbuf_register_notifier(stty->pdata->dst, stty->pdata->channel,
+ stty->pdata->bufid, NULL, NULL);
+ if (rval) {
+ printk(KERN_ERR " unregitster notifier failed (%d)\n", rval);
+ return rval;
+ }
+
+ stty_driver_exit(stty);
+ kfree(stty->port);
+ stty_destroy_pdata(&stty->pdata);
+ kfree(stty);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static const struct of_device_id stty_match_table[] = {
+ { .compatible = "sprd,stty4bt", },
+ { },
+};
+
+static struct platform_driver stty_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sttybt",
+ .of_match_table = stty_match_table,
+ },
+ .probe = stty_probe,
+ .remove = stty_remove,
+};
+
+static int __init stty_init(void)
+{
+ return platform_driver_register(&stty_driver);
+}
+
+static void __exit stty_exit(void)
+{
+ platform_driver_unregister(&stty_driver);
+}
+
+late_initcall(stty_init);
+module_exit(stty_exit);
+
+MODULE_AUTHOR("Dewu Jiang");
+MODULE_DESCRIPTION("SIPC/stty driver");
+MODULE_LICENSE("GPL");