/* * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /* * Encoder device driver (kernel module) * * Copyright (C) 2005 Hantro Products Oy. * * 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 /* __init,__exit directives */ #include /* remap_page_range / remap_pfn_range */ #include /* for struct file_operations */ #include /* standard error codes */ #include /* for device registeration for PM */ #include /* for msleep_interruptible */ #include #include /* for dma_alloc_consistent */ #include #include /* for ioctl __get_user, __put_user */ #include #include "mxc_hmp4e.h" /* MPEG4 encoder specific */ /* here's all the must remember stuff */ typedef struct { ulong buffaddr; u32 buffsize; ulong iobaseaddr; u32 iosize; volatile u32 *hwregs; u32 irq; u16 hwid_offset; u16 intr_offset; u16 busy_offset; u16 type; /* Encoder type, CIF = 0, VGA = 1 */ u16 clk_gate; u16 busy_val; struct fasync_struct *async_queue; #ifdef CONFIG_PM s32 suspend_state; wait_queue_head_t power_queue; #endif } hmp4e_t; /* and this is our MAJOR; use 0 for dynamic allocation (recommended)*/ static s32 hmp4e_major; static u32 hmp4e_phys; static struct class *hmp4e_class; static hmp4e_t hmp4e_data; /*! MPEG4 enc clock handle. */ static struct clk *hmp4e_clk; /* * avoid "enable_irq(x) unbalanced from ..." * error messages from the kernel, since {ena,dis}able_irq() * calls are stacked in kernel. */ static bool irq_enable = false; ulong base_port = MPEG4_ENC_BASE_ADDR; u32 irq = MXC_INT_MPEG4_ENCODER; module_param(base_port, long, 000); module_param(irq, int, 000); /*! * These variables store the register values when HMP4E is in suspend mode. */ #ifdef CONFIG_PM u32 io_regs[64]; #endif static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma); static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma); static void hmp4e_reset(hmp4e_t * dev); irqreturn_t hmp4e_isr(s32 irq, void *dev_id); /*! * This funtion is called to write h/w register. * * @param val value to be written into the register * @param offset register offset * */ static inline void hmp4e_write(u32 val, u32 offset) { hmp4e_t *dev = &hmp4e_data; __raw_writel(val, (dev->hwregs + offset)); } /*! * This funtion is called to read h/w register. * * @param offset register offset * * @return This function returns the value read from the register. * */ static inline u32 hmp4e_read(u32 offset) { hmp4e_t *dev = &hmp4e_data; u32 val; val = __raw_readl(dev->hwregs + offset); return val; } /*! * The device's mmap method. The VFS has kindly prepared the process's * vm_area_struct for us, so we examine this to see what was requested. * * @param filp pointer to struct file * @param vma pointer to struct vma_area_struct * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_mmap(struct file *filp, struct vm_area_struct *vma) { s32 result; ulong offset = vma->vm_pgoff << PAGE_SHIFT; pr_debug("hmp4e_mmap: size = %lu off = 0x%08lx\n", (unsigned long)(vma->vm_end - vma->vm_start), offset); if (offset == 0) { result = hmp4e_map_buffer(filp, vma); } else if (offset == hmp4e_data.iobaseaddr) { result = hmp4e_map_hwregs(filp, vma); } else { pr_debug("hmp4e: mmap invalid value\n"); result = -EINVAL; } return result; } /*! * This funtion is called to handle ioctls. * * @param inode pointer to struct inode * @param filp pointer to struct file * @param cmd ioctl command * @param arg user data * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_ioctl(struct inode *inode, struct file *filp, u32 cmd, ulong arg) { s32 err = 0, retval = 0; ulong offset = 0; hmp4e_t *dev = &hmp4e_data; write_t bwrite; #ifdef CONFIG_PM wait_event_interruptible(hmp4e_data.power_queue, hmp4e_data.suspend_state == 0); #endif /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != HMP4E_IOC_MAGIC) { pr_debug("hmp4e: ioctl invalid magic\n"); return -ENOTTY; } if (_IOC_NR(cmd) > HMP4E_IOC_MAXNR) { pr_debug("hmp4e: ioctl exceeds max ioctl\n"); return -ENOTTY; } /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) { err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); } else if (_IOC_DIR(cmd) & _IOC_WRITE) { err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); } if (err) { pr_debug("hmp4e: ioctl invalid direction\n"); return -EFAULT; } switch (cmd) { case HMP4E_IOCHARDRESET: break; case HMP4E_IOCGBUFBUSADDRESS: retval = __put_user((ulong) hmp4e_phys, (u32 *) arg); break; case HMP4E_IOCGBUFSIZE: retval = __put_user(hmp4e_data.buffsize, (u32 *) arg); break; case HMP4E_IOCSREGWRITE: if (dev->type != 1) { /* This ioctl only for VGA */ pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); retval = -EINVAL; break; } retval = __copy_from_user(&bwrite, (u32 *) arg, sizeof(write_t)); if (bwrite.offset <= hmp4e_data.iosize - 4) { hmp4e_write(bwrite.data, (bwrite.offset / 4)); } else { pr_debug("hmp4e: HMP4E_IOCSREGWRITE failed\n"); retval = -EFAULT; } break; case HMP4E_IOCXREGREAD: if (dev->type != 1) { /* This ioctl only for VGA */ pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); retval = -EINVAL; break; } retval = __get_user(offset, (ulong *) arg); if (offset <= hmp4e_data.iosize - 4) { __put_user(hmp4e_read((offset / 4)), (ulong *) arg); } else { pr_debug("hmp4e: HMP4E_IOCXREGREAD failed\n"); retval = -EFAULT; } break; case HMP4E_IOCGHWOFFSET: __put_user(hmp4e_data.iobaseaddr, (ulong *) arg); break; case HMP4E_IOCGHWIOSIZE: __put_user(hmp4e_data.iosize, (u32 *) arg); break; case HMP4E_IOC_CLI: if (irq_enable == true) { disable_irq(hmp4e_data.irq); irq_enable = false; } break; case HMP4E_IOC_STI: if (irq_enable == false) { enable_irq(hmp4e_data.irq); irq_enable = true; } break; default: pr_debug("unknown case %x\n", cmd); } return retval; } /*! * This funtion is called when the device is opened. * * @param inode pointer to struct inode * @param filp pointer to struct file * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_open(struct inode *inode, struct file *filp) { hmp4e_t *dev = &hmp4e_data; filp->private_data = (void *)dev; if (request_irq(dev->irq, hmp4e_isr, 0, "mxc_hmp4e", dev) != 0) { pr_debug("hmp4e: request irq failed\n"); return -EBUSY; } if (irq_enable == false) { irq_enable = true; } clk_enable(hmp4e_clk); return 0; } static s32 hmp4e_fasync(s32 fd, struct file *filp, s32 mode) { hmp4e_t *dev = (hmp4e_t *) filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); } /*! * This funtion is called when the device is closed. * * @param inode pointer to struct inode * @param filp pointer to struct file * * @return This function returns 0. * */ static s32 hmp4e_release(struct inode *inode, struct file *filp) { hmp4e_t *dev = (hmp4e_t *) filp->private_data; /* this is necessary if user process exited asynchronously */ if (irq_enable == true) { disable_irq(dev->irq); irq_enable = false; } /* reset hardware */ hmp4e_reset(&hmp4e_data); /* free the encoder IRQ */ free_irq(dev->irq, (void *)dev); /* remove this filp from the asynchronusly notified filp's */ hmp4e_fasync(-1, filp, 0); clk_disable(hmp4e_clk); return 0; } /* VFS methods */ static struct file_operations hmp4e_fops = { .owner = THIS_MODULE, .open = hmp4e_open, .release = hmp4e_release, .ioctl = hmp4e_ioctl, .mmap = hmp4e_mmap, .fasync = hmp4e_fasync, }; /*! * This funtion allocates physical contigous memory. * * @param size size of memory to be allocated * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_alloc(u32 size) { hmp4e_data.buffsize = PAGE_ALIGN(size); hmp4e_data.buffaddr = (ulong) dma_alloc_coherent(NULL, hmp4e_data.buffsize, (dma_addr_t *) & hmp4e_phys, GFP_DMA | GFP_KERNEL); if (hmp4e_data.buffaddr == 0) { printk(KERN_ERR "hmp4e: couldn't allocate data buffer\n"); return -ENOMEM; } memset((s8 *) hmp4e_data.buffaddr, 0, hmp4e_data.buffsize); return 0; } /*! * This funtion frees the DMAed memory. */ static void hmp4e_free(void) { if (hmp4e_data.buffaddr != 0) { dma_free_coherent(NULL, hmp4e_data.buffsize, (void *)hmp4e_data.buffaddr, hmp4e_phys); hmp4e_data.buffaddr = 0; } } /*! * This funtion maps the shared buffer in memory. * * @param filp pointer to struct file * @param vma pointer to struct vm_area_struct * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma) { ulong phys; ulong start = (u32) vma->vm_start; ulong size = (u32) (vma->vm_end - vma->vm_start); /* if userspace tries to mmap beyond end of our buffer, fail */ if (size > hmp4e_data.buffsize) { pr_debug("hmp4e: hmp4e_map_buffer, invalid size\n"); return -EINVAL; } vma->vm_flags |= VM_RESERVED | VM_IO; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); phys = hmp4e_phys; if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, size, vma->vm_page_prot)) { pr_debug("hmp4e: failed mmapping shared buffer\n"); return -EAGAIN; } return 0; } /*! * This funtion maps the h/w register space in memory. * * @param filp pointer to struct file * @param vma pointer to struct vm_area_struct * * @return This function returns 0 if successful or -ve value on error. * */ static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma) { ulong phys; ulong start = (unsigned long)vma->vm_start; ulong size = (unsigned long)(vma->vm_end - vma->vm_start); /* if userspace tries to mmap beyond end of our buffer, fail */ if (size > PAGE_SIZE) { pr_debug("hmp4e: hmp4e_map_hwregs, invalid size\n"); return -EINVAL; } vma->vm_flags |= VM_RESERVED | VM_IO; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /* Remember this won't work for vmalloc()d memory ! */ phys = hmp4e_data.iobaseaddr; if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, hmp4e_data.iosize, vma->vm_page_prot)) { pr_debug("hmp4e: failed mmapping HW registers\n"); return -EAGAIN; } return 0; } /*! * This function is the interrupt service routine. * * @param irq the irq number * @param dev_id driver data when ISR was regiatered * * @return The return value is IRQ_HANDLED. * */ irqreturn_t hmp4e_isr(s32 irq, void *dev_id) { hmp4e_t *dev = (hmp4e_t *) dev_id; u32 offset = dev->intr_offset; u32 irq_status = hmp4e_read(offset); /* clear enc IRQ */ hmp4e_write(irq_status & (~0x01), offset); if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); return IRQ_HANDLED; } /*! * This function is called to reset the encoder. * * @param dev pointer to struct hmp4e_data * */ static void hmp4e_reset(hmp4e_t * dev) { s32 i; /* enable HCLK for register reset */ hmp4e_write(dev->clk_gate, 0); /* Reset registers, except ECR0 (0x00) and ID (read-only) */ for (i = 1; i < (dev->iosize / 4); i += 1) { if (i == dev->hwid_offset) /* ID is read only */ continue; /* Only for CIF, not used */ if ((dev->type == 0) && (i == 14)) continue; hmp4e_write(0, i); } /* disable HCLK */ hmp4e_write(0, 0); return; } /*! * This function is called during the driver binding process. This function * does the hardware initialization. * * @param dev the device structure used to store device specific * information that is used by the suspend, resume and remove * functions * * @return The function returns 0 if successful. */ static s32 hmp4e_probe(struct platform_device *pdev) { s32 result; u32 hwid; struct device *temp_class; hmp4e_data.iobaseaddr = base_port; hmp4e_data.irq = irq; hmp4e_data.buffaddr = 0; /* map hw i/o registers into kernel space */ hmp4e_data.hwregs = (volatile void *)IO_ADDRESS(hmp4e_data.iobaseaddr); hmp4e_clk = clk_get(&pdev->dev, "mpeg4_clk"); if (IS_ERR(hmp4e_clk)) { printk(KERN_INFO "hmp4e: Unable to get clock\n"); return -EIO; } clk_enable(hmp4e_clk); /* check hw id for encoder signature */ hwid = hmp4e_read(7); if ((hwid & 0xffff) == 0x1882) { /* CIF first */ hmp4e_data.type = 0; hmp4e_data.iosize = (16 * 4); hmp4e_data.hwid_offset = 7; hmp4e_data.intr_offset = 5; hmp4e_data.clk_gate = (1 << 1); hmp4e_data.buffsize = 512000; hmp4e_data.busy_offset = 0; hmp4e_data.busy_val = 1; } else { hwid = hmp4e_read((0x88 / 4)); if ((hwid & 0xffff0000) == 0x52510000) { /* VGA */ hmp4e_data.type = 1; hmp4e_data.iosize = (35 * 4); hmp4e_data.hwid_offset = (0x88 / 4); hmp4e_data.intr_offset = (0x10 / 4); hmp4e_data.clk_gate = (1 << 12); hmp4e_data.buffsize = 1048576; hmp4e_data.busy_offset = (0x10 / 4); hmp4e_data.busy_val = (1 << 1); } else { printk(KERN_INFO "hmp4e: HW ID not found\n"); goto error1; } } /* Reset hardware */ hmp4e_reset(&hmp4e_data); /* allocate memory shared with ewl */ result = hmp4e_alloc(hmp4e_data.buffsize); if (result < 0) goto error1; result = register_chrdev(hmp4e_major, "hmp4e", &hmp4e_fops); if (result <= 0) { pr_debug("hmp4e: unable to get major %d\n", hmp4e_major); goto error2; } hmp4e_major = result; hmp4e_class = class_create(THIS_MODULE, "hmp4e"); if (IS_ERR(hmp4e_class)) { pr_debug("Error creating hmp4e class.\n"); goto error3; } temp_class = device_create(hmp4e_class, NULL, MKDEV(hmp4e_major, 0), NULL, "hmp4e"); if (IS_ERR(temp_class)) { pr_debug("Error creating hmp4e class device.\n"); goto error4; } platform_set_drvdata(pdev, &hmp4e_data); #ifdef CONFIG_PM hmp4e_data.async_queue = NULL; hmp4e_data.suspend_state = 0; init_waitqueue_head(&hmp4e_data.power_queue); #endif printk(KERN_INFO "hmp4e: %s encoder initialized\n", hmp4e_data.type ? "VGA" : "CIF"); clk_disable(hmp4e_clk); return 0; error4: class_destroy(hmp4e_class); error3: unregister_chrdev(hmp4e_major, "hmp4e"); error2: hmp4e_free(); error1: clk_disable(hmp4e_clk); clk_put(hmp4e_clk); printk(KERN_INFO "hmp4e: module not inserted\n"); return -EIO; } /*! * Dissociates the driver. * * @param dev the device structure * * @return The function always returns 0. */ static s32 hmp4e_remove(struct platform_device *pdev) { device_destroy(hmp4e_class, MKDEV(hmp4e_major, 0)); class_destroy(hmp4e_class); unregister_chrdev(hmp4e_major, "hmp4e"); hmp4e_free(); clk_disable(hmp4e_clk); clk_put(hmp4e_clk); platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM /*! * This is the suspend of power management for the Hantro MPEG4 module * * @param dev the device * @param state the state * * @return This function always returns 0. */ static s32 hmp4e_suspend(struct platform_device *pdev, pm_message_t state) { s32 i; hmp4e_t *pdata = &hmp4e_data; /* * how many times msleep_interruptible will be called before * giving up */ s32 timeout = 10; pr_debug("hmp4e: Suspend\n"); hmp4e_data.suspend_state = 1; /* check if encoder is currently running */ while ((hmp4e_read(pdata->busy_offset) & (pdata->busy_val)) && --timeout) { pr_debug("hmp4e: encoder is running, going to sleep\n"); msleep_interruptible((unsigned int)30); } if (!timeout) { pr_debug("hmp4e: timeout suspending, resetting encoder\n"); hmp4e_write(hmp4e_read(pdata->busy_offset) & (~pdata->busy_val), pdata->busy_offset); } /* first read register 0 */ io_regs[0] = hmp4e_read(0); /* then override HCLK to make sure other registers can be read */ hmp4e_write(pdata->clk_gate, 0); /* read other registers */ for (i = 1; i < (pdata->iosize / 4); i += 1) { /* Only for CIF, not used */ if ((pdata->type == 0) && (i == 14)) continue; io_regs[i] = hmp4e_read(i); } /* restore value of register 0 */ hmp4e_write(io_regs[0], 0); /* stop HCLK */ hmp4e_write(0, 0); clk_disable(hmp4e_clk); return 0; }; /*! * This is the resume of power management for the Hantro MPEG4 module * It suports RESTORE state. * * @param pdev the platform device * * @return This function always returns 0 */ static s32 hmp4e_resume(struct platform_device *pdev) { s32 i; u32 status; hmp4e_t *pdata = &hmp4e_data; pr_debug("hmp4e: Resume\n"); clk_enable(hmp4e_clk); /* override HCLK to make sure registers can be written */ hmp4e_write(pdata->clk_gate, 0x00); for (i = 1; i < (pdata->iosize / 4); i += 1) { if (i == pdata->hwid_offset) /* Read only */ continue; /* Only for CIF, not used */ if ((pdata->type == 0) && (i == 14)) continue; hmp4e_write(io_regs[i], i); } /* write register 0 last */ hmp4e_write(io_regs[0], 0x00); /* Clear the suspend flag */ hmp4e_data.suspend_state = 0; /* Unblock the wait queue */ wake_up_interruptible(&hmp4e_data.power_queue); /* Continue operations */ status = hmp4e_read(pdata->intr_offset); if (status & 0x1) { hmp4e_write(status & (~0x01), pdata->intr_offset); if (hmp4e_data.async_queue) kill_fasync(&hmp4e_data.async_queue, SIGIO, POLL_IN); } return 0; }; #endif static struct platform_driver hmp4e_driver = { .driver = { .name = "mxc_hmp4e", }, .probe = hmp4e_probe, .remove = hmp4e_remove, #ifdef CONFIG_PM .suspend = hmp4e_suspend, .resume = hmp4e_resume, #endif }; static s32 __init hmp4e_init(void) { printk(KERN_INFO "hmp4e: init\n"); platform_driver_register(&hmp4e_driver); return 0; } static void __exit hmp4e_cleanup(void) { platform_driver_unregister(&hmp4e_driver); printk(KERN_INFO "hmp4e: module removed\n"); } module_init(hmp4e_init); module_exit(hmp4e_cleanup); /* module description */ MODULE_AUTHOR("Hantro Products Oy"); MODULE_DESCRIPTION("Device driver for Hantro's hardware based MPEG4 encoder"); MODULE_SUPPORTED_DEVICE("5251/4251 MPEG4 Encoder"); MODULE_LICENSE("GPL");