/* * 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 #include #include #include #include #include #ifdef CONFIG_OF #include #endif #include #include 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");