diff -drupN a/drivers/media/tsc/tscdrv.c b/drivers/media/tsc/tscdrv.c --- a/drivers/media/tsc/tscdrv.c 1970-01-01 03:00:00.000000000 +0300 +++ b/drivers/media/tsc/tscdrv.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,788 @@ +/* + * drivers/media/tsc/tscdrv.c + * (C) Copyright 2010-2015 + * Allwinner Technology Co., Ltd. + * csjamesdeng + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +//#include +#include "dvb_drv_sunxi.h" +#include "tscdrv.h" +#include +#include +#include +#include +#include +#include +#include +//#include + +#define TSC_MSG(fmt, arg...) pr_warn("[tsc]: "fmt, ##arg) +#define TSC_DBG(fmt, arg...) \ + pr_debug("[tsc]: %s()%d - "fmt, __func__, __LINE__, ##arg) +#define TSC_ERR(fmt, arg...) \ + pr_warn("[tsc]: %s()%d - "fmt, __func__, __LINE__, ##arg) +#define TSC_INFO(fmt, arg...) \ + pr_warn("[tsc]: %s()%d - "fmt, __func__, __LINE__, ##arg) + +static struct tsc_dev *tsc_devp; +static struct of_device_id sunxi_tsc_match[] = { + { .compatible = "allwinner,sun8i-tsc",}, + { .compatible = "allwinner,sun50i-tsc",}, + {} +}; + +#define TSC_MODULE_CLK_RATE 120000000 + +MODULE_DEVICE_TABLE(of, sunxi_tsc_match); + +static DECLARE_WAIT_QUEUE_HEAD(wait_proc); + +int sunxi_tsc_enable_hw_clk(void) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&tsc_devp->lock, flags); + if (clk_prepare_enable(tsc_devp->mclk)) { + TSC_MSG("enable mclk failed.\n"); + ret = -EFAULT; + goto enable_out; + } +enable_out: + spin_unlock_irqrestore(&tsc_devp->lock, flags); + return ret; +} + +int sunxi_tsc_disable_hw_clk(void) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&tsc_devp->lock, flags); + if ((NULL == tsc_devp->mclk) + || (IS_ERR(tsc_devp->mclk))) { + TSC_MSG("mclk is invalid!\n"); + ret = -EFAULT; + } else { + clk_disable_unprepare(tsc_devp->mclk); + } + + spin_unlock_irqrestore(&tsc_devp->lock, flags); + return ret; +} + +/* + * interrupt service routine + * To wake up wait queue + */ +static irqreturn_t sunxi_tsc_irq_handle(int irq, void *dev_id) +{ + struct iomap_para addrs = tsc_devp->iomap_addrs; + unsigned long tsc_int_status_reg; + unsigned long tsc_int_ctrl_reg; + unsigned int tsc_status; + unsigned int tsc_interrupt_enable; + + tsc_int_ctrl_reg = (unsigned long)(addrs.regs_macc + 0x80 + 0x08); + tsc_int_status_reg = (unsigned long)(addrs.regs_macc + 0x80 + 0x18); + tsc_interrupt_enable = ioread32((void *)(tsc_int_ctrl_reg)); + tsc_status = ioread32((void *)(tsc_int_status_reg)); + iowrite32(tsc_interrupt_enable, (void *)(tsc_int_ctrl_reg)); + iowrite32(tsc_status, (void *)(tsc_int_status_reg)); + tsc_devp->irq_flag = 1; + wake_up_interruptible(&wait_proc); + + return IRQ_HANDLED; +} + +static void sunxi_tsc_close_all_filters(struct tsc_dev *devp) +{ + int i; + unsigned int value = 0; + struct iomap_para addrs = tsc_devp->iomap_addrs; + + /*close tsf0*/ + iowrite32(0, (void *)(addrs.regs_macc + 0x80 + 0x10)); + iowrite32(0, (void *)(addrs.regs_macc + 0x80 + 0x30)); + for (i = 0; i < 32; i++) { + iowrite32(i, (void *)(addrs.regs_macc + 0x80 + 0x3c)); + value = (0<<16 | 0x1fff); + iowrite32(value, (void *)(addrs.regs_macc + 0x80 + 0x4c)); + } +} + +static int sunxi_tsc_select_gpio_state(struct pinctrl *pctrl, char *name) +{ + int ret = 0; + struct pinctrl_state *pctrl_state = NULL; + + pctrl_state = pinctrl_lookup_state(pctrl, name); + if (IS_ERR(pctrl_state)) { + TSC_MSG("pinctrl lookup state(%s) failed!\n", name); + return -1; + } + + ret = pinctrl_select_state(pctrl, pctrl_state); + if (ret < 0) + TSC_MSG("pintrcl select state(%s) failed\n", name); + + return ret; +} + + +static int sunxi_tsc_request_gpio(struct platform_device *pdev) +{ + int ret = 0; +#ifdef CONFIG_ARCH_SUN50IW2 + if ((tsc_devp->port_config.port0config && + tsc_devp->port_config.port1config) + || (tsc_devp->port_config.port2config && + tsc_devp->port_config.port3config)) { + TSC_MSG("port config error!\n"); + return -EINVAL; + } +#endif + if (tsc_devp->port_config.port0config + || tsc_devp->port_config.port1config + || tsc_devp->port_config.port2config + || tsc_devp->port_config.port3config) { + if (!tsc_devp->pinctrl) { + tsc_devp->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR_OR_NULL(tsc_devp->pinctrl)) { + TSC_MSG("request pinctrl handle failed!\n"); + return -EINVAL; + } + } + } + if (tsc_devp->port_config.port0config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts0-default"); + if (ret) + TSC_MSG("set gpio default err!\n"); + } + if (tsc_devp->port_config.port1config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts1-default"); + if (ret) + TSC_MSG("set gpio default err!\n"); + } + if (tsc_devp->port_config.port2config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts2-default"); + if (ret) + TSC_MSG("set gpio default err!\n"); + } + if (tsc_devp->port_config.port3config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts3-default"); + if (ret) + TSC_MSG("set gpio default err!\n"); + } + + return ret; +} + +static void sunxi_tsc_release_gpio(void) +{ + devm_pinctrl_put(tsc_devp->pinctrl); + tsc_devp->pinctrl = NULL; +} + +static int sunxi_tsc_disable_gpio(void) +{ + int ret = 0; + if (tsc_devp->port_config.port0config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts0-sleep"); + if (ret) { + TSC_ERR("select sleep state failed!\n"); + return ret; + } + } + if (tsc_devp->port_config.port1config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts1-sleep"); + if (ret) { + TSC_ERR("select sleep state failed!\n"); + return ret; + } + } + if (tsc_devp->port_config.port2config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts2-sleep"); + if (ret) { + TSC_ERR("select sleep state failed!\n"); + return ret; + } + + } + if (tsc_devp->port_config.port3config) { + ret = sunxi_tsc_select_gpio_state(tsc_devp->pinctrl, + "ts3-sleep"); + if (ret) { + TSC_ERR("select sleep state failed!\n"); + return ret; + } + + } + return ret; +} + +static int sunxi_tsc_hw_clk_init(struct platform_device *pdev) +{ + struct device_node *node; + unsigned int mclk_rate; + int ret = 0; + + node = pdev->dev.of_node; + tsc_devp->pclk = of_clk_get(node, 0); + if ((!tsc_devp->pclk) || IS_ERR(tsc_devp->pclk)) { + TSC_MSG("try to get parent pll clk failed!\n"); + ret = -EINVAL; + return ret; + } + tsc_devp->mclk = of_clk_get(node, 1); + if (!tsc_devp->mclk || IS_ERR(tsc_devp->mclk)) { + TSC_MSG("get mclk failed.\n"); + ret = -EINVAL; + return ret; + } + ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", + &mclk_rate); + if (ret) { + TSC_MSG("don't set clock-frequency by dts\n"); + mclk_rate = TSC_MODULE_CLK_RATE; + } + /* reset tsc module. */ + sunxi_periph_reset_assert(tsc_devp->mclk); + clk_set_parent(tsc_devp->mclk, tsc_devp->pclk); + + if (clk_set_rate(tsc_devp->mclk, mclk_rate) < 0) { + TSC_MSG("set clk rate failed\n"); + ret = -EINVAL; + } + TSC_MSG("set clock rate is %ld\n", clk_get_rate(tsc_devp->mclk)); + + if (clk_prepare_enable(tsc_devp->mclk)) { + TSC_MSG("enable moudule clock failed\n"); + return -EBUSY; + } + + return clk_get_rate(tsc_devp->mclk); +} + + +static int sunxi_tsc_get_port_config(struct platform_device *pdev) +{ + int ret = 0; + unsigned int temp_val = 0; + struct device_node *node; + + node = pdev->dev.of_node; + ret = of_property_read_u32(node, "ts0config", &temp_val); + if (ret < 0) { + TSC_MSG("ts0config missing or invalid.\n"); + tsc_devp->port_config.port0config = 0; + } else { + tsc_devp->port_config.port0config = temp_val; + } + ret = of_property_read_u32(node, "ts1config", &temp_val); + if (ret < 0) { + TSC_MSG("ts1config missing or invalid.\n"); + tsc_devp->port_config.port1config = 0; + } else { + tsc_devp->port_config.port1config = temp_val; + } + ret = of_property_read_u32(node, "ts2config", &temp_val); + if (ret < 0) { + TSC_MSG("ts2config missing or invalid.\n"); + tsc_devp->port_config.port2config = 0; + } else { + tsc_devp->port_config.port2config = temp_val; + } + ret = of_property_read_u32(node, "ts3config", &temp_val); + if (ret < 0) { + TSC_MSG("ts3config missing or invalid.\n"); + tsc_devp->port_config.port3config = 0; + } else { + tsc_devp->port_config.port3config = temp_val; + } + + if (tsc_devp->port_config.port0config || + tsc_devp->port_config.port1config || + tsc_devp->port_config.port2config || + tsc_devp->port_config.port3config) + return 0; + else + return -EINVAL; +} +/* + * poll operateion for wait for TS irq + */ +unsigned int tscdev_poll(struct file *filp, struct poll_table_struct *wait) +{ + int mask = 0; + + poll_wait(filp, &tsc_devp->wq, wait); + if (tsc_devp->irq_flag == 1) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/* + * ioctl function + */ +long tscdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + long ret; + struct intrstatus statusdata; + int arg_rate = (int)arg; + unsigned long tsc_pclk_rate; + + ret = 0; + if (_IOC_TYPE(cmd) != TSCDEV_IOC_MAGIC) + return -EINVAL; + if (_IOC_NR(cmd) > TSCDEV_IOC_MAXNR) + return -EINVAL; + + switch (cmd) { + case TSCDEV_WAIT_INT: + ret = wait_event_interruptible_timeout(wait_proc, + tsc_devp->irq_flag, HZ * 1); + if (!ret && !tsc_devp->irq_flag) { + /* case: wait timeout. */ + TSC_MSG("wait interrupt timeout.\n"); + memset(&statusdata, 0, sizeof(statusdata)); + } else { + /* case: interrupt occured. */ + tsc_devp->irq_flag = 0; + statusdata.port0chan = tsc_devp->intstatus.port0chan; + statusdata.port0pcr = tsc_devp->intstatus.port0pcr; + } + /* copy status data to user. */ + if (copy_to_user((struct intrstatus *)arg, + &(tsc_devp->intstatus), + sizeof(struct intrstatus))) { + return -EFAULT; + } + break; + + case TSCDEV_GET_PHYSICS: + return 0; + + case TSCDEV_ENABLE_INT: + enable_irq(tsc_devp->irq); + break; + + case TSCDEV_DISABLE_INT: + tsc_devp->irq_flag = 1; + wake_up_interruptible(&wait_proc); + disable_irq(tsc_devp->irq); + break; + + case TSCDEV_RELEASE_SEM: + tsc_devp->irq_flag = 1; + wake_up_interruptible(&wait_proc); + break; + + case TSCDEV_GET_CLK: + if (!tsc_devp->mclk || IS_ERR(tsc_devp->mclk)) { + TSC_MSG("get tsc clk failed.\n"); + ret = -EINVAL; + } + break; + + case TSCDEV_PUT_CLK: + + clk_put(tsc_devp->mclk); + + break; + + case TSCDEV_ENABLE_CLK: + /* clk_prepare_enable(tsc_devp->mclk); */ + ret = sunxi_tsc_enable_hw_clk(); + if (ret < 0) { + TSC_ERR("tsc clk enable failed!\n"); + return -EFAULT; + } + break; + + case TSCDEV_DISABLE_CLK: + /* clk_disable_unprepare(tsc_devp->mclk); */ + ret = sunxi_tsc_disable_hw_clk(); + if (ret < 0) { + TSC_ERR("tsc clk disable failed!\n"); + } + break; + + case TSCDEV_GET_CLK_FREQ: + ret = clk_get_rate(tsc_devp->mclk); + break; + + case TSCDEV_SET_SRC_CLK_FREQ: + writel(0x1, tsc_devp->iomap_addrs.regs_macc); + break; + + case TSCDEV_SET_CLK_FREQ: + if (clk_get_rate(tsc_devp->mclk)/1000000 != arg_rate) { + if (!clk_set_rate(tsc_devp->pclk, arg_rate*1000000)) { + tsc_pclk_rate = clk_get_rate(tsc_devp->pclk); + if (clk_set_rate(tsc_devp->mclk, tsc_pclk_rate)) + TSC_MSG("set tsc clock failed!\n"); + } else + TSC_MSG("set pll4 clock failed!\n"); + } + ret = clk_get_rate(tsc_devp->mclk); + break; + + default: + TSC_MSG("invalid cmd!\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +static int tscdev_open(struct inode *inode, struct file *filp) +{ + /* unsigned long clk_rate; */ + if (down_interruptible(&tsc_devp->sem)) { + TSC_MSG("down interruptible failed!\n"); + return -ERESTARTSYS; + } + /* init other resource here */ + tsc_devp->irq_flag = 0; + up(&tsc_devp->sem); + nonseekable_open(inode, filp); + return 0; +} + +static int tscdev_release(struct inode *inode, struct file *filp) +{ + if (down_interruptible(&tsc_devp->sem)) + return -ERESTARTSYS; + sunxi_tsc_close_all_filters(tsc_devp); + /* release other resource here */ + tsc_devp->irq_flag = 1; + up(&tsc_devp->sem); + return 0; +} + +void tscdev_vma_open(struct vm_area_struct *vma) +{ + TSC_INFO(); +} + +void tscdev_vma_close(struct vm_area_struct *vma) +{ + TSC_INFO(); +} + +static struct vm_operations_struct tscdev_remap_vm_ops = { + .open = tscdev_vma_open, + .close = tscdev_vma_close, +}; + +static int tscdev_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long temp_pfn; + + if (vma->vm_end - vma->vm_start == 0) { + TSC_MSG("vm_end is equal vm_start : %lx\n", vma->vm_start); + return 0; + } + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) { + TSC_MSG( + "vm_pgoff is %lx,it is large than the largest page number\n", + vma->vm_pgoff); + return -EINVAL; + } + temp_pfn = tsc_devp->mapbase >> 12; + /* Set reserved and I/O flag for the area. */ + vma->vm_flags |= /*VM_RESERVED | */VM_IO; + /* Select uncached access. */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (io_remap_pfn_range(vma, vma->vm_start, temp_pfn, + (vma->vm_end - vma->vm_start), vma->vm_page_prot)) { + return -EAGAIN; + } + vma->vm_ops = &tscdev_remap_vm_ops; + tscdev_vma_open(vma); + return 0; +} + + +static struct file_operations tscdev_fops = { + .owner = THIS_MODULE, + .mmap = tscdev_mmap, + .poll = tscdev_poll, + .open = tscdev_open, + .release = tscdev_release, + .llseek = no_llseek, + .unlocked_ioctl = tscdev_ioctl, +}; + +static int tscdev_init(struct platform_device *pdev) +{ + int ret = 0; + int devno; + struct device_node *node; + struct resource *mem_res; + dev_t dev; + + dev = 0; + node = pdev->dev.of_node; + + tsc_devp = kmalloc(sizeof(struct tsc_dev), GFP_KERNEL); + if (tsc_devp == NULL) { + TSC_MSG("malloc mem for tsc device err\n"); + ret = -ENOMEM; + goto error0; + } + memset(tsc_devp, 0, sizeof(struct tsc_dev)); + /* register or alloc the device number. */ + tsc_devp->major = TSCDEV_MAJOR; + tsc_devp->minor = TSCDEV_MINOR; + if (tsc_devp->major) { + dev = MKDEV(tsc_devp->major, tsc_devp->minor); + ret = register_chrdev_region(dev, 1, "ts0"); + } else { + ret = alloc_chrdev_region(&dev, tsc_devp->minor, 1, "ts0"); + tsc_devp->major = MAJOR(dev); + tsc_devp->minor = MINOR(dev); + } + if (ret < 0) { + TSC_MSG("ts0: can't get major: %d.\n", tsc_devp->major); + ret = -EINVAL; + goto error0; + } + spin_lock_init(&tsc_devp->lock); + + + printk("[tsc] %s() - %d \n", __func__, __LINE__); + /* get physical address */ + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res == NULL) { + TSC_MSG("get memory resource failed\n"); + ret = -ENOMEM; + goto error0; + } + tsc_devp->mapbase = mem_res->start; + + tsc_devp->irq = irq_of_parse_and_map(node, 0); + if (tsc_devp->irq <= 0) { + TSC_MSG("can not parse irq.\n"); + ret = -EINVAL; + goto error0; + } + + printk("[tsc] %s() - %d \n", __func__, __LINE__); + sema_init(&tsc_devp->sem, 1); + init_waitqueue_head(&tsc_devp->wq); + memset(&tsc_devp->iomap_addrs, 0, sizeof(struct iomap_para)); + ret = request_irq(tsc_devp->irq, sunxi_tsc_irq_handle, 0, "ts0", NULL); + if (ret < 0) { + TSC_MSG("request irq err\n"); + ret = -EINVAL; + goto error0; + } + /* map for macc io space */ + tsc_devp->iomap_addrs.regs_macc = of_iomap(node, 0); + if (!tsc_devp->iomap_addrs.regs_macc) { + TSC_MSG("tsc can't map registers.\n"); + ret = -EINVAL; + goto error0; + } + /* init tsc hw clk */ + sunxi_tsc_hw_clk_init(pdev); + /* Create char device */ + devno = MKDEV(tsc_devp->major, tsc_devp->minor); + cdev_init(&tsc_devp->cdev, &tscdev_fops); + tsc_devp->cdev.owner = THIS_MODULE; + ret = cdev_add(&tsc_devp->cdev, devno, 1); + if (ret) { + TSC_MSG("err:%d add tscdev.", ret); + ret = -EINVAL; + goto error0; + } + tsc_devp->tsc_class = class_create(THIS_MODULE, "ts0"); + tsc_devp->dev = device_create(tsc_devp->tsc_class, NULL, devno, NULL, "ts0"); + + if (sunxi_tsc_get_port_config(pdev) < 0) { + TSC_ERR("get tsc port config failed!\n"); + ret = -EINVAL; + goto error0; + } + + if (sunxi_tsc_request_gpio(pdev)) { + TSC_ERR("request tsc pio failed!\n"); + ret = -EINVAL; + goto error0; + } + + printk("[tsc] %s() - %d \n", __func__, __LINE__); + TSC_MSG("init succussful\n"); + return 0; + +error0: + if (tsc_devp) { + kfree(tsc_devp); + tsc_devp = NULL; + } + return ret; +} + +static void tscdev_exit(void) +{ + dev_t dev; + int ret = 0; + + dev = MKDEV(tsc_devp->major, tsc_devp->minor); + free_irq(tsc_devp->irq, NULL); + iounmap(tsc_devp->iomap_addrs.regs_macc); + /* Destroy char device */ + if (tsc_devp) { + cdev_del(&tsc_devp->cdev); + device_destroy(tsc_devp->tsc_class, dev); + class_destroy(tsc_devp->tsc_class); + } + if (NULL == tsc_devp->mclk + || IS_ERR(tsc_devp->mclk)) { + TSC_MSG("mclk handle is invalid.\n"); + } else { + /* clk_disable_unprepare(tsc_devp->mclk); */ + ret = sunxi_tsc_disable_hw_clk(); + if (ret < 0) { + TSC_ERR("tsc clk disable failed.\n"); + } + clk_put(tsc_devp->mclk); + tsc_devp->mclk = NULL; + } + if (NULL == tsc_devp->pclk + || IS_ERR(tsc_devp->pclk)) { + TSC_MSG("parent pll clk handle is invalid.\n"); + } else { + clk_put(tsc_devp->pclk); + } + /* release ts pin */ + sunxi_tsc_disable_gpio(); + sunxi_tsc_release_gpio(); + unregister_chrdev_region(dev, 1); + if (tsc_devp) { + kfree(tsc_devp); + tsc_devp = NULL; + } +} + + +#ifdef CONFIG_PM +static int sw_tsc_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret = 0; + + ret = sunxi_tsc_disable_gpio(); + if (ret < 0) { + TSC_MSG("tsc release gpio failed!\n"); + return -EFAULT; + } + ret = sunxi_tsc_disable_hw_clk(); + if (ret < 0) { + TSC_ERR("tsc clk disable failed!\n"); + return -EFAULT; + } + TSC_MSG("standby suspend succeed!\n"); + return 0; +} +static int sw_tsc_resume(struct platform_device *pdev) +{ + int ret = 0; + + ret = sunxi_tsc_enable_hw_clk(); + if (ret < 0) { + TSC_ERR("tsc clk enable failed!\n"); + return -EFAULT; + } + ret = sunxi_tsc_request_gpio(pdev); + if (ret < 0) { + TSC_ERR("request tsc pio failed!\n"); + return -EFAULT; + } + TSC_MSG("standby resume succeed!\n"); + return 0; +} +#endif + +static int sunxi_tsc_remove(struct platform_device *pdev) +{ + tscdev_exit(); + return 0; +} + +static int sunxi_tsc_probe(struct platform_device *pdev) +{ + printk("[tsc] %s() - %d \n", __func__, __LINE__); + tscdev_init(pdev); + return 0; +} + +static struct platform_driver sunxi_tsc_driver = { + .probe = sunxi_tsc_probe, + .remove = sunxi_tsc_remove, +#ifdef CONFIG_PM + .suspend = sw_tsc_suspend, + .resume = sw_tsc_resume, +#endif + .driver = { + .name = "ts0", + .owner = THIS_MODULE, + .of_match_table = sunxi_tsc_match, + }, +}; + +static int __init sunxi_tsc_init(void) + +{ + int ret; + ret = platform_driver_register(&sunxi_tsc_driver); + return ret; +} + +static void __exit sunxi_tsc_exit(void) +{ + platform_driver_unregister(&sunxi_tsc_driver); +} + +module_init(sunxi_tsc_init); +module_exit(sunxi_tsc_exit); +MODULE_AUTHOR("Soft-Reuuimlla"); +MODULE_DESCRIPTION("User mode tsc device interface"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:tsc-sunxi");