android13/kernel-5.10/drivers/sprd_pcie/sipc/spipe.c

480 lines
11 KiB
C
Executable File

/*
* Copyright (C) 2018 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/cdev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "../include/sipc.h"
#include "sipc_priv.h"
#include "spipe.h"
#define SPIPE_NR_BASE_NUM MKDEV(254, 0)
#define SDIAG_NR_BASE_NUM MKDEV(253, 0)
#define STTY_NR_BASE_NUM MKDEV(252, 0)
#define SNV_NR_BASE_NUM MKDEV(251, 0)
struct spipe_device {
struct spipe_init_data *init;
int major;
int minor;
struct cdev cdev;
};
struct spipe_sbuf {
u8 dst;
u8 channel;
u32 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("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;
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,
};
#ifdef SPRD_PCIE_USE_DTS
static int spipe_parse_dt(struct spipe_init_data **init,
struct device_node *np, struct device *dev, dev_t *devid)
{
struct spipe_init_data *pdata = NULL;
int ret;
u32 data;
pdata = devm_kzalloc(dev, sizeof(struct spipe_init_data), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
ret = of_property_read_string(np,
"sprd,name",
(const char **)&pdata->name);
if (ret)
goto error;
if (!strcmp(pdata->name, "spipe_nr"))
*devid = SPIPE_NR_BASE_NUM;
else if (!strcmp(pdata->name, "sdiag_nr"))
*devid = SDIAG_NR_BASE_NUM;
else if (!strcmp(pdata->name, "stty_nr"))
*devid = STTY_NR_BASE_NUM;
else if (!strcmp(pdata->name, "snv_nr"))
*devid = SNV_NR_BASE_NUM;
ret = of_property_read_u32(np, "sprd,dst", (u32 *)&data);
if (ret)
goto error;
pdata->dst = (u8)data;
ret = of_property_read_u32(np, "sprd,channel", (u32 *)&data);
if (ret)
goto error;
pdata->channel = (u8)data;
ret = of_property_read_u32(np,
"sprd,ringnr",
(u32 *)&pdata->ringnr);
if (ret)
goto error;
ret = of_property_read_u32(np,
"sprd,size-rxbuf",
(u32 *)&pdata->rxbuf_size);
if (ret)
goto error;
ret = of_property_read_u32(np,
"sprd,size-txbuf",
(u32 *)&pdata->txbuf_size);
if (ret)
goto error;
*init = pdata;
return ret;
error:
devm_kfree(dev, pdata);
*init = NULL;
return ret;
}
#else
static int spipe_parse_dt(struct spipe_init_data **init,
struct device_node *np, struct device *dev, dev_t *devid)
{
struct spipe_init_data *pdata = *init;
if (!strcmp(pdata->name, "spipe_nr"))
*devid = SPIPE_NR_BASE_NUM;
else if (!strcmp(pdata->name, "sdiag_nr"))
*devid = SDIAG_NR_BASE_NUM;
else if (!strcmp(pdata->name, "stty_nr"))
*devid = STTY_NR_BASE_NUM;
else if (!strcmp(pdata->name, "snv_nr"))
*devid = SNV_NR_BASE_NUM;
return 0;
}
#endif
static inline void spipe_destroy_pdata(struct spipe_init_data **init,
struct device *dev)
{
*init = NULL;
}
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;
struct device_node *np;
printk("%s!\n", __func__);
if (1) {
np = pdev->dev.of_node;
rval = spipe_parse_dt(&init, np, &pdev->dev, &devid);
if (rval) {
pr_err("Failed to parse spipe device tree, ret=%d\n", rval);
return rval;
}
printk("spipe: after parse device tree, name=%s, dst=%u, channel=%u, ringnr=%u, rxbuf_size=0x%x, txbuf_size=0x%x\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("Failed to create sbuf: %d\n", rval);
spipe_destroy_pdata(&init, &pdev->dev);
return rval;
}
spipe = devm_kzalloc(&pdev->dev,
sizeof(struct spipe_device),
GFP_KERNEL);
if (spipe == NULL) {
sbuf_destroy(init->dst, init->channel);
spipe_destroy_pdata(&init, &pdev->dev);
printk("Failed to allocate spipe_device\n");
return -ENOMEM;
}
rval = alloc_chrdev_region(&devid, 0, init->ringnr, init->name);
//rval = register_chrdev_region(devid, init->ringnr, init->name);
if (rval != 0) {
sbuf_destroy(init->dst, init->channel);
devm_kfree(&pdev->dev, spipe);
spipe_destroy_pdata(&init, &pdev->dev);
printk("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);
devm_kfree(&pdev->dev, spipe);
unregister_chrdev_region(devid, init->ringnr);
spipe_destroy_pdata(&init, &pdev->dev);
printk("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;
if (spipe) {
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, &pdev->dev);
devm_kfree(&pdev->dev, spipe);
platform_set_drvdata(pdev, NULL);
}
return 0;
}
#ifdef SPRD_PCIE_USE_DTS
static const struct of_device_id spipe_match_table[] = {
{.compatible = "sprd,spipe", },
{ },
};
#endif
static struct platform_driver spipe_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "spipe",
#ifdef SPRD_PCIE_USE_DTS
.of_match_table = spipe_match_table,
#endif
},
.probe = spipe_probe,
.remove = spipe_remove,
};
static struct platform_device *spipe_pdev[MAX_SPIPE_CHN_NUM];
static struct spipe_init_data spipe_data[MAX_SPIPE_CHN_NUM] = {
{
.sipc_name = SPIPE_DRVIER_NMAE,
.name = "spipe_nr",
.dst = 1,
.channel = 4,
.ringnr = 15,
.txbuf_size = 0x1000,
.rxbuf_size = 0x1000
},
{
.sipc_name = SPIPE_DRVIER_NMAE,
.name = "sdiag_nr",
.dst = 1,
.channel = 21,
.ringnr = 1,
.txbuf_size = 0x40000,
.rxbuf_size = 0x8000
},
{
.sipc_name = SPIPE_DRVIER_NMAE,
.name = "stty_nr",
.dst = 1,
.channel = 6,
.ringnr = 32,
.txbuf_size = 0x0800,
.rxbuf_size = 0x0800
},
{
.sipc_name = SPIPE_DRVIER_NMAE,
.name = "snv_nr",
.dst = 1,
.channel = 40,
.ringnr = 1,
.txbuf_size = 0x40400,
.rxbuf_size = 0x1000
}
};
static int spipe_platform_device_reigster(void)
{
int retval = -ENOMEM;
int i;
for(i = 0; i < MAX_SPIPE_CHN_NUM; i++) {
spipe_pdev[i] = platform_device_alloc(SPIPE_DRVIER_NMAE, i);
if (!spipe_pdev[i]) {
i--;
while (i >= 0)
platform_device_put(spipe_pdev[i--]);
return retval;
}
}
for (i = 0; i < MAX_SPIPE_CHN_NUM; i++) {
retval = platform_device_add_data(spipe_pdev[i], &spipe_data[i],
sizeof(struct spipe_init_data));
if (retval)
goto err_add_pdata;
}
for (i = 0; i < MAX_SPIPE_CHN_NUM; i++) {
retval = platform_device_add(spipe_pdev[i]);
if (retval < 0) {
i--;
while (i >= 0)
platform_device_del(spipe_pdev[i]);
goto err_add_pdata;
}
}
return retval;
err_add_pdata:
for (i = 0; i < MAX_SPIPE_CHN_NUM; i++)
platform_device_put(spipe_pdev[i]);
return retval;
}
static void spipe_platform_device_unreigster(void)
{
int i;
for (i = 0; i < MAX_SPIPE_CHN_NUM; i++) {
platform_device_unregister(spipe_pdev[i]);
}
}
int spipe_init(void)
{
int ret;
spipe_class = class_create(THIS_MODULE, "spipe");
if (IS_ERR(spipe_class))
return PTR_ERR(spipe_class);
#ifndef SPRD_PCIE_USE_DTS
if((ret = spipe_platform_device_reigster()))
return ret;
#endif
if((ret = platform_driver_register(&spipe_driver))) {
#ifndef SPRD_PCIE_USE_DTS
spipe_platform_device_unreigster();
#endif
return ret;
}
return ret;
}
EXPORT_SYMBOL_GPL(spipe_init);
void spipe_exit(void)
{
platform_driver_unregister(&spipe_driver);
#ifndef SPRD_PCIE_USE_DTS
spipe_platform_device_unreigster();
#endif
class_destroy(spipe_class);
}
void spipe_device_down(void)
{
int i;
for(i = 0; i < MAX_SPIPE_CHN_NUM; i++) {
sbuf_down(spipe_data[i].dst, spipe_data[i].channel);
}
}
EXPORT_SYMBOL_GPL(spipe_exit);
EXPORT_SYMBOL_GPL(spipe_device_down);