summaryrefslogtreecommitdiff
path: root/drivers/sipc/spipe.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sipc/spipe.c')
-rw-r--r--drivers/sipc/spipe.c336
1 files changed, 336 insertions, 0 deletions
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");