/* * Copyright (C) 2021 - 2022 Alibaba Group. All rights reserved. * * 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "isp_venc_shake_driver.h" #define IVS_SWREG_AMOUNT 27 typedef struct _theadivs_dev { struct cdev cdev; dev_t devt; struct class *class; unsigned long base_addr; u32 iosize; volatile u8 *hwregs; int irq; int state; } theadivs_dev; static int theadivs_major = 0; /* dynamic */ static int theadivs_minor = 0; static theadivs_dev* theadivs_data = NULL; static unsigned int device_register_index = 0; struct class *theadivs_class; static irqreturn_t theadivs_isr(int irq, void *dev_id); static void print_registers(void) { printk("pic_width = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x00))); printk("pic_height = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x04))); printk("encode_width = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x08))); printk("encode_height = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x0c))); printk("wid_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x10))); printk("wid_uv = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x14))); printk("sram_size = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x18))); printk("encode_n = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x1c))); printk("stride_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x20))); printk("stride_uv = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x24))); printk("encode_x = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x2c))); printk("encode_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x30))); printk("int_state = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x38))); printk("int_mask = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x40))); } static int config_ivs(struct file *filp, struct ivs_parameter *params) { iowrite32(params->pic_width, (void*)(theadivs_data->hwregs + 0x00)); iowrite32(params->pic_height, (void*)(theadivs_data->hwregs + 0x04)); iowrite32(params->encode_width, (void*)(theadivs_data->hwregs + 0x08)); iowrite32(params->encode_height, (void*)(theadivs_data->hwregs + 0x0c)); iowrite32(params->wid_y, (void*)(theadivs_data->hwregs + 0x10)); iowrite32(params->wid_uv, (void*)(theadivs_data->hwregs + 0x14)); iowrite32(params->sram_size, (void*)(theadivs_data->hwregs + 0x18)); iowrite32(params->encode_n, (void*)(theadivs_data->hwregs + 0x1c)); iowrite32(params->stride_y, (void*)(theadivs_data->hwregs + 0x20)); iowrite32(params->stride_uv, (void*)(theadivs_data->hwregs + 0x24)); iowrite32(1, (void*)(theadivs_data->hwregs + 0x28)); // CLEAR iowrite32(params->encode_x, (void*)(theadivs_data->hwregs + 0x2c)); iowrite32(params->encode_y, (void*)(theadivs_data->hwregs + 0x30)); iowrite32(0, (void*)(theadivs_data->hwregs + 0x34)); // START iowrite32(0, (void*)(theadivs_data->hwregs + 0x3c)); // INT_CLEAN iowrite32(params->int_mask, (void*)(theadivs_data->hwregs + 0x40)); return 0; } static long theadivs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; if (_IOC_TYPE(cmd) != THEAD_IOC_MAGIC) return -ENOTTY; else if (_IOC_NR(cmd) > THEAD_IOC_MAXNR) return -ENOTTY; if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok((void *) arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok((void *) arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch (cmd) { case THEAD_IOCH_CONFIG_IVS: { struct ivs_parameter params; printk("%s: THEAD_IOCH_CONFIG_IVS\n", __func__); err = copy_from_user(¶ms, (struct ivs_parameter*)arg, sizeof(struct ivs_parameter)); config_ivs(filp, ¶ms); print_registers(); theadivs_data->state = THEADIVS_READY; break; } case THEAD_IOCH_START_IVS: { printk("%s: THEAD_IOCH_START_IVS\n", __func__); iowrite32(1, theadivs_data->hwregs + 0x34); // START print_registers(); theadivs_data->state = THEADIVS_RUNNING; break; } case THEAD_IOCH_RESET_IVS: { printk("%s: THEAD_IOCH_RESET_IVS\n", __func__); iowrite32(1, theadivs_data->hwregs + 0x40); // INT_MASK iowrite32(1, theadivs_data->hwregs + 0x28); // CLEAR iowrite32(0, theadivs_data->hwregs + 0x40); // INT_MASK print_registers(); theadivs_data->state = THEADIVS_IDLE; break; } case THEAD_IOCH_GET_STATE: { printk("%s: THEAD_IOCH_GET_STATE: %d\n", __func__, theadivs_data->state); err = copy_to_user((int *)arg, &theadivs_data->state, sizeof(int)); break; } default: { printk("%s: undefined command: 0x%x\n", __func__, cmd); } } return 0; } static int theadivs_open(struct inode *inode, struct file *filp) { int result = 0; //theadivs_dev *dev = theadivs_data; //filp->private_data = (void *) dev; return result; } static int theadivs_release(struct inode *inode, struct file *filp) { //theadivs_dev *dev = (theadivs_dev *) filp->private_data; return 0; } static struct file_operations theadivs_fops = { .owner= THIS_MODULE, .open = theadivs_open, .release = theadivs_release, .unlocked_ioctl = theadivs_ioctl, .fasync = NULL, }; static const struct of_device_id thead_of_match[] = { { .compatible = "thead,light-ivs", }, { /* sentinel */ }, }; static int theadivs_reserve_IO(void) { if(!request_mem_region (theadivs_data->base_addr, theadivs_data->iosize, "shake")) { printk(KERN_INFO "theadivs: failed to reserve HW regs\n"); printk(KERN_INFO "theadivs: base_addr = 0x%08lx, iosize = %d\n", theadivs_data->base_addr, theadivs_data->iosize); return -1; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)) theadivs_data->hwregs = (volatile u8 *) ioremap_nocache(theadivs_data->base_addr, theadivs_data->iosize); #else theadivs_data->hwregs = (volatile u8 *) ioremap(theadivs_data->base_addr, theadivs_data->iosize); #endif if (theadivs_data->hwregs == NULL) { printk(KERN_INFO "theadivs: failed to ioremap HW regs\n"); release_mem_region(theadivs_data->base_addr, theadivs_data->iosize); return -1; } printk("theadivs: mapped from 0x%lx to %p with size %d\n", theadivs_data->base_addr, theadivs_data->hwregs, theadivs_data->iosize); return 0; } static void theadivs_release_IO(void) { if(theadivs_data->hwregs) { iounmap((void *) theadivs_data->hwregs); release_mem_region(theadivs_data->base_addr, theadivs_data->iosize); theadivs_data->hwregs = NULL; } } int __init theadivs_probe(struct platform_device *pdev) { int result = -1; struct resource *mem; printk("enter %s\n",__func__); theadivs_data = (theadivs_dev *)vmalloc(sizeof(theadivs_dev)); if (theadivs_data == NULL) return result; memset(theadivs_data, 0, sizeof(theadivs_dev)); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if(mem->start) theadivs_data->base_addr = mem->start; theadivs_data->irq = platform_get_irq(pdev, 0); printk("%s:get irq %d\n", __func__, theadivs_data->irq); theadivs_data->iosize = IVS_SWREG_AMOUNT * 4; #if 1 if (device_register_index == 0) { if (theadivs_major == 0) { result = alloc_chrdev_region(&theadivs_data->devt, 0, 1, "shake"); if (result != 0) { printk("%s:alloc_chrdev_region error\n", __func__); goto err1; } theadivs_major = MAJOR(theadivs_data->devt); theadivs_minor = MINOR(theadivs_data->devt); } else { theadivs_data->devt = MKDEV(theadivs_major, theadivs_minor); result = register_chrdev_region(theadivs_data->devt, 1, "shake"); if (result) { printk("%s:register_chrdev_region error\n", __func__); goto err1; } } theadivs_class = class_create(THIS_MODULE, "shake"); if (IS_ERR(theadivs_class)) { printk("%s[%d]:class_create error!\n", __func__, __LINE__); goto err; } } theadivs_data->devt = MKDEV(theadivs_major, theadivs_minor + pdev->id); cdev_init(&theadivs_data->cdev, &theadivs_fops); result = cdev_add(&theadivs_data->cdev, theadivs_data->devt, 1); if ( result ) { printk("%s[%d]:cdev_add error!\n", __func__, __LINE__); goto err; } theadivs_data->class = theadivs_class; device_create(theadivs_data->class, NULL, theadivs_data->devt, theadivs_data, "shake"); device_register_index++; #else result = register_chrdev(theadivs_major, "shake", &theadivs_fops); if (result < 0) { printk(KERN_INFO "theadivs_driver: unable to get major <%d>\n", theadivs_major); goto err1; } else if (result != 0) /* this is for dynamic major */ { theadivs_major = result; } #endif theadivs_reserve_IO(); /* get the IRQ line */ if (theadivs_data->irq!= -1) { result = request_irq(theadivs_data->irq, theadivs_isr, IRQF_SHARED, "shake", (void *)theadivs_data); if (result == -EINVAL) { printk(KERN_ERR "theadivs_driver: Bad irq number or handler.\n"); theadivs_release_IO(); goto err; } else if (result == -EBUSY) { printk(KERN_ERR "theadivs_driver: IRQ <%d> busy, change your config.\n", theadivs_data->irq); theadivs_release_IO(); goto err; } } else { printk(KERN_INFO "theadivs_driver: IRQ not in use!\n"); } printk(KERN_INFO "theadivs_driver: module inserted. Major <%d>\n", theadivs_major); return 0; err: //unregister_chrdev(theadivs_major, "shake"); unregister_chrdev_region(theadivs_data->devt, 1); err1: if (theadivs_data != NULL) vfree(theadivs_data); printk(KERN_INFO "theadivs_driver: module not inserted\n"); return result; } static int theadivs_remove(struct platform_device *pdev) { free_irq(theadivs_data->irq, theadivs_data); theadivs_release_IO(); //unregister_chrdev(theadivs_major, "shake"); device_register_index--; cdev_del(&theadivs_data->cdev); device_destroy(theadivs_data->class, theadivs_data->devt); unregister_chrdev_region(theadivs_data->devt, 1); if (device_register_index == 0) { class_destroy(theadivs_data->class); } if (theadivs_data != NULL) vfree(theadivs_data); printk(KERN_INFO "theadivs_driver: module removed\n"); return 0; } static struct platform_driver theadivs_driver = { .probe = theadivs_probe, .remove = theadivs_remove, .driver = { .name = "shake", .owner = THIS_MODULE, .of_match_table = of_match_ptr(thead_of_match), } }; int __init theadivs_init(void) { int ret = 0; printk("enter %s\n",__func__); #if 1 ret = platform_driver_register(&theadivs_driver); if (ret) { pr_err("register platform driver failed!\n"); } #endif return ret; } void __exit theadivs_cleanup(void) { printk("enter %s\n",__func__); platform_driver_unregister(&theadivs_driver); return; } static irqreturn_t theadivs_isr(int irq, void *dev_id) { theadivs_dev *dev = (theadivs_dev *) dev_id; u32 irq_status = 0; printk( "theadivs_isr: received IRQ!\n"); irq_status = (u32)ioread32((void*)dev->hwregs + 0x38); // INT_STATE printk( "INT_STATE of is: 0x%x\n", irq_status); theadivs_data->state = THEADIVS_ERROR; iowrite32(1, theadivs_data->hwregs + 0x40); // INT_MASK iowrite32(1, theadivs_data->hwregs + 0x3c); // INT_CLEAN iowrite32(1, theadivs_data->hwregs + 0x28); // CLEAR iowrite32(0, theadivs_data->hwregs + 0x40); // INT_MASK return IRQ_HANDLED; } module_init(theadivs_init); module_exit(theadivs_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("T-HEAD"); MODULE_DESCRIPTION("T-HEAD ISP-VENC-SHAKE driver");