blob: 089daf7f99784f3d2c7ac79cf6fc5aa989c1fbf7 [file] [log] [blame]
/*
* Copyright 2006-2015 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
*/
/*!
* @file mxc_vpu.c
*
* @brief VPU system initialization and file operation implementation
*
* @ingroup VPU
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/stat.h>
#include <linux/platform_device.h>
#include <linux/kdev_t.h>
#include <linux/dma-mapping.h>
#include <linux/wait.h>
#include <linux/list.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/fsl_devices.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/sched/signal.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/regulator/consumer.h>
#include <linux/page-flags.h>
#include <linux/mm_types.h>
#include <linux/types.h>
#include <linux/memblock.h>
#include <linux/memory.h>
#include <linux/version.h>
#include <asm/page.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/sizes.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
#include <linux/iram_alloc.h>
#include <mach/clock.h>
#include <mach/hardware.h>
#include <mach/mxc_vpu.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/genalloc.h>
#include <linux/mxc_vpu.h>
#include <linux/of.h>
#include <linux/reset.h>
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
#include <mach/busfreq.h>
#include <mach/common.h>
#else
#include <asm/sizes.h>
#endif
/* Define one new pgprot which combined uncached and XN(never executable) */
#define pgprot_noncachedxn(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED | L_PTE_XN)
struct vpu_priv {
struct fasync_struct *async_queue;
struct work_struct work;
struct workqueue_struct *workqueue;
struct mutex lock;
};
/* To track the allocated memory buffer */
struct memalloc_record {
struct list_head list;
struct vpu_mem_desc mem;
};
struct iram_setting {
u32 start;
u32 end;
};
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
static struct gen_pool *iram_pool;
static u32 iram_base;
#endif
static LIST_HEAD(head);
static int vpu_major;
static int vpu_clk_usercount;
static struct class *vpu_class;
static struct vpu_priv vpu_data;
static u8 open_count;
static struct clk *vpu_clk;
static struct vpu_mem_desc bitwork_mem = { 0 };
static struct vpu_mem_desc pic_para_mem = { 0 };
static struct vpu_mem_desc user_data_mem = { 0 };
static struct vpu_mem_desc share_mem = { 0 };
static struct vpu_mem_desc vshare_mem = { 0 };
static void __iomem *vpu_base;
static int vpu_ipi_irq;
static u32 phy_vpu_base_addr;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
static phys_addr_t top_address_DRAM;
static struct mxc_vpu_platform_data *vpu_plat;
#endif
static struct device *vpu_dev;
/* IRAM setting */
static struct iram_setting iram;
/* implement the blocking ioctl */
static int irq_status;
static int codec_done;
static wait_queue_head_t vpu_queue;
#ifdef CONFIG_SOC_IMX6Q
#define MXC_VPU_HAS_JPU
#endif
#ifdef MXC_VPU_HAS_JPU
static int vpu_jpu_irq;
#endif
#ifdef CONFIG_PM
static unsigned int regBk[64];
static unsigned int pc_before_suspend;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
static struct regulator *vpu_regulator;
#endif
#endif
static atomic_t clk_cnt_from_ioc = ATOMIC_INIT(0);
#define READ_REG(x) readl_relaxed(vpu_base + x)
#define WRITE_REG(val, x) writel_relaxed(val, vpu_base + x)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
/* redirect to static functions */
static int cpu_is_mx6dl(void)
{
int ret;
ret = of_machine_is_compatible("fsl,imx6dl");
return ret;
}
static int cpu_is_mx6q(void)
{
int ret;
ret = of_machine_is_compatible("fsl,imx6q");
return ret;
}
#endif
static void vpu_reset(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
int ret;
ret = device_reset(vpu_dev);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
imx_src_reset_vpu();
#else
if (vpu_plat->reset)
vpu_plat->reset();
#endif
}
static long vpu_power_get(bool on)
{
long ret = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
if (on) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
vpu_regulator = regulator_get(NULL, "cpu_vddvpu");
ret = IS_ERR(vpu_regulator);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
vpu_regulator = devm_regulator_get(vpu_dev, "pu");
ret = IS_ERR(vpu_regulator);
#endif
} else {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (!IS_ERR(vpu_regulator))
regulator_put(vpu_regulator);
#endif
}
#endif
return ret;
}
static void vpu_power_up(bool on)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
if (on)
pm_runtime_get_sync(vpu_dev);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) || LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
if (on) {
if (!IS_ERR(vpu_regulator)) {
if (regulator_enable(vpu_regulator))
dev_err(vpu_dev, "failed to power up vpu\n");
}
} else {
if (!IS_ERR(vpu_regulator)) {
if (regulator_disable(vpu_regulator))
dev_err(vpu_dev, "failed to power down vpu\n");
}
}
#else
imx_gpc_power_up_pu(on);
#endif
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
if (!on)
pm_runtime_put_sync_suspend(vpu_dev);
#endif
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
static int cpu_is_mx53(void)
{
return 0;
}
static int cpu_is_mx51(void)
{
return 0;
}
#define VM_RESERVED 0
#endif
/*!
* Private function to alloc dma buffer
* @return status 0 success.
*/
static int vpu_alloc_dma_buffer(struct vpu_mem_desc *mem)
{
mem->cpu_addr = (unsigned long)
dma_alloc_coherent(NULL, PAGE_ALIGN(mem->size),
(dma_addr_t *) (&mem->phy_addr),
GFP_DMA | GFP_KERNEL);
dev_dbg(vpu_dev, "[ALLOC] mem alloc cpu_addr = 0x%x\n", mem->cpu_addr);
if ((void *)(mem->cpu_addr) == NULL) {
dev_err(vpu_dev, "Physical memory allocation error!\n");
return -1;
}
return 0;
}
/*!
* Private function to free dma buffer
*/
static void vpu_free_dma_buffer(struct vpu_mem_desc *mem)
{
if (mem->cpu_addr != 0) {
dma_free_coherent(0, PAGE_ALIGN(mem->size),
(void *)mem->cpu_addr, mem->phy_addr);
}
}
/*!
* Private function to free buffers
* @return status 0 success.
*/
static int vpu_free_buffers(void)
{
struct memalloc_record *rec, *n;
struct vpu_mem_desc mem;
list_for_each_entry_safe(rec, n, &head, list) {
mem = rec->mem;
if (mem.cpu_addr != 0) {
vpu_free_dma_buffer(&mem);
dev_dbg(vpu_dev, "[FREE] freed paddr=0x%08X\n", mem.phy_addr);
/* delete from list */
list_del(&rec->list);
kfree(rec);
}
}
return 0;
}
static inline void vpu_worker_callback(struct work_struct *w)
{
struct vpu_priv *dev = container_of(w, struct vpu_priv,
work);
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
irq_status = 1;
/*
* Clock is gated on when dec/enc started, gate it off when
* codec is done.
*/
if (codec_done)
codec_done = 0;
wake_up_interruptible(&vpu_queue);
}
/*!
* @brief vpu interrupt handler
*/
static irqreturn_t vpu_ipi_irq_handler(int irq, void *dev_id)
{
struct vpu_priv *dev = dev_id;
unsigned long reg;
reg = READ_REG(BIT_INT_REASON);
if (reg & 0x8)
codec_done = 1;
WRITE_REG(0x1, BIT_INT_CLEAR);
queue_work(dev->workqueue, &dev->work);
return IRQ_HANDLED;
}
/*!
* @brief vpu jpu interrupt handler
*/
#ifdef MXC_VPU_HAS_JPU
static irqreturn_t vpu_jpu_irq_handler(int irq, void *dev_id)
{
struct vpu_priv *dev = dev_id;
unsigned long reg;
reg = READ_REG(MJPEG_PIC_STATUS_REG);
if (reg & 0x3)
codec_done = 1;
queue_work(dev->workqueue, &dev->work);
return IRQ_HANDLED;
}
#endif
/*!
* @brief check phy memory prepare to pass to vpu is valid or not, we
* already address some issue that if pass a wrong address to vpu
* (like virtual address), system will hang.
*
* @return true return is a valid phy memory address, false return not.
*/
bool vpu_is_valid_phy_memory(u32 paddr)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (paddr > top_address_DRAM)
return false;
#endif
return true;
}
/*!
* @brief open function for vpu file operation
*
* @return 0 on success or negative error code on error
*/
static int vpu_open(struct inode *inode, struct file *filp)
{
mutex_lock(&vpu_data.lock);
if (open_count++ == 0) {
vpu_power_up(true);
#ifdef CONFIG_SOC_IMX6Q
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
if (READ_REG(BIT_CUR_PC))
dev_dbg(vpu_dev, "Not power off before vpu open!\n");
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
#endif
}
filp->private_data = (void *)(&vpu_data);
mutex_unlock(&vpu_data.lock);
return 0;
}
/*!
* @brief IO ctrl function for vpu file operation
* @param cmd IO ctrl command
* @return 0 on success or negative error code on error
*/
static long vpu_ioctl(struct file *filp, u_int cmd,
u_long arg)
{
int ret = 0;
switch (cmd) {
case VPU_IOC_PHYMEM_ALLOC:
{
struct memalloc_record *rec;
rec = kzalloc(sizeof(*rec), GFP_KERNEL);
if (!rec)
return -ENOMEM;
ret = copy_from_user(&(rec->mem),
(struct vpu_mem_desc *)arg,
sizeof(struct vpu_mem_desc));
if (ret) {
kfree(rec);
return -EFAULT;
}
dev_dbg(vpu_dev, "[ALLOC] mem alloc size = 0x%x\n",
rec->mem.size);
ret = vpu_alloc_dma_buffer(&(rec->mem));
if (ret == -1) {
kfree(rec);
dev_err(vpu_dev,
"Physical memory allocation error!\n");
break;
}
ret = copy_to_user((void __user *)arg, &(rec->mem),
sizeof(struct vpu_mem_desc));
if (ret) {
kfree(rec);
ret = -EFAULT;
break;
}
mutex_lock(&vpu_data.lock);
list_add(&rec->list, &head);
mutex_unlock(&vpu_data.lock);
break;
}
case VPU_IOC_PHYMEM_FREE:
{
struct memalloc_record *rec, *n;
struct vpu_mem_desc vpu_mem;
ret = copy_from_user(&vpu_mem,
(struct vpu_mem_desc *)arg,
sizeof(struct vpu_mem_desc));
if (ret)
return -EACCES;
dev_dbg(vpu_dev, "[FREE] mem freed cpu_addr = 0x%x\n",
vpu_mem.cpu_addr);
if ((void *)vpu_mem.cpu_addr != NULL)
vpu_free_dma_buffer(&vpu_mem);
mutex_lock(&vpu_data.lock);
list_for_each_entry_safe(rec, n, &head, list) {
if (rec->mem.cpu_addr == vpu_mem.cpu_addr) {
/* delete from list */
list_del(&rec->list);
kfree(rec);
break;
}
}
mutex_unlock(&vpu_data.lock);
break;
}
case VPU_IOC_WAIT4INT:
{
u_long timeout = (u_long) arg;
if (!wait_event_interruptible_timeout
(vpu_queue, irq_status != 0,
msecs_to_jiffies(timeout))) {
dev_warn(vpu_dev, "VPU blocking: timeout.\n");
ret = -ETIME;
} else if (signal_pending(current)) {
dev_warn(vpu_dev, "VPU interrupt received.\n");
ret = -ERESTARTSYS;
} else
irq_status = 0;
break;
}
case VPU_IOC_IRAM_SETTING:
{
ret = copy_to_user((void __user *)arg, &iram,
sizeof(struct iram_setting));
if (ret)
ret = -EFAULT;
break;
}
case VPU_IOC_CLKGATE_SETTING:
{
u32 clkgate_en;
if (get_user(clkgate_en, (u32 __user *) arg))
return -EFAULT;
if (clkgate_en) {
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
atomic_inc(&clk_cnt_from_ioc);
} else {
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
atomic_dec(&clk_cnt_from_ioc);
}
break;
}
case VPU_IOC_GET_SHARE_MEM:
{
mutex_lock(&vpu_data.lock);
if (share_mem.cpu_addr != 0) {
ret = copy_to_user((void __user *)arg,
&share_mem,
sizeof(struct vpu_mem_desc));
mutex_unlock(&vpu_data.lock);
break;
} else {
if (copy_from_user(&share_mem,
(struct vpu_mem_desc *)arg,
sizeof(struct vpu_mem_desc))) {
mutex_unlock(&vpu_data.lock);
return -EFAULT;
}
if (vpu_alloc_dma_buffer(&share_mem) == -1)
ret = -EFAULT;
else {
if (copy_to_user((void __user *)arg,
&share_mem,
sizeof(struct
vpu_mem_desc)))
ret = -EFAULT;
}
}
mutex_unlock(&vpu_data.lock);
break;
}
case VPU_IOC_REQ_VSHARE_MEM:
{
mutex_lock(&vpu_data.lock);
if (vshare_mem.cpu_addr != 0) {
ret = copy_to_user((void __user *)arg,
&vshare_mem,
sizeof(struct vpu_mem_desc));
mutex_unlock(&vpu_data.lock);
break;
} else {
if (copy_from_user(&vshare_mem,
(struct vpu_mem_desc *)arg,
sizeof(struct
vpu_mem_desc))) {
mutex_unlock(&vpu_data.lock);
return -EFAULT;
}
/* vmalloc shared memory if not allocated */
if (!vshare_mem.cpu_addr)
vshare_mem.cpu_addr =
(unsigned long)
vmalloc_user(vshare_mem.size);
if (copy_to_user
((void __user *)arg, &vshare_mem,
sizeof(struct vpu_mem_desc)))
ret = -EFAULT;
}
mutex_unlock(&vpu_data.lock);
break;
}
case VPU_IOC_GET_WORK_ADDR:
{
if (bitwork_mem.cpu_addr != 0) {
ret =
copy_to_user((void __user *)arg,
&bitwork_mem,
sizeof(struct vpu_mem_desc));
break;
} else {
if (copy_from_user(&bitwork_mem,
(struct vpu_mem_desc *)arg,
sizeof(struct vpu_mem_desc)))
return -EFAULT;
if (vpu_alloc_dma_buffer(&bitwork_mem) == -1)
ret = -EFAULT;
else if (copy_to_user((void __user *)arg,
&bitwork_mem,
sizeof(struct
vpu_mem_desc)))
ret = -EFAULT;
}
break;
}
/*
* The following two ioctl is used when user allocates working buffer
* and register it to vpu driver.
*/
case VPU_IOC_QUERY_BITWORK_MEM:
{
if (copy_to_user((void __user *)arg,
&bitwork_mem,
sizeof(struct vpu_mem_desc)))
ret = -EFAULT;
break;
}
case VPU_IOC_SET_BITWORK_MEM:
{
if (copy_from_user(&bitwork_mem,
(struct vpu_mem_desc *)arg,
sizeof(struct vpu_mem_desc)))
ret = -EFAULT;
break;
}
case VPU_IOC_SYS_SW_RESET:
{
vpu_reset();
break;
}
case VPU_IOC_REG_DUMP:
break;
case VPU_IOC_PHYMEM_DUMP:
break;
case VPU_IOC_PHYMEM_CHECK:
{
struct vpu_mem_desc check_memory;
ret = copy_from_user(&check_memory,
(void __user *)arg,
sizeof(struct vpu_mem_desc));
if (ret != 0) {
dev_err(vpu_dev, "copy from user failure:%d\n", ret);
ret = -EFAULT;
break;
}
ret = vpu_is_valid_phy_memory((u32)check_memory.phy_addr);
dev_dbg(vpu_dev, "vpu: memory phy:0x%x %s phy memory\n",
check_memory.phy_addr, (ret ? "is" : "isn't"));
/* borrow .size to pass back the result. */
check_memory.size = ret;
ret = copy_to_user((void __user *)arg, &check_memory,
sizeof(struct vpu_mem_desc));
if (ret) {
ret = -EFAULT;
break;
}
break;
}
case VPU_IOC_LOCK_DEV:
{
u32 lock_en;
if (get_user(lock_en, (u32 __user *) arg))
return -EFAULT;
if (lock_en)
mutex_lock(&vpu_data.lock);
else
mutex_unlock(&vpu_data.lock);
break;
}
default:
{
dev_err(vpu_dev, "No such IOCTL, cmd is %d\n", cmd);
ret = -EINVAL;
break;
}
}
return ret;
}
/*!
* @brief Release function for vpu file operation
* @return 0 on success or negative error code on error
*/
static int vpu_release(struct inode *inode, struct file *filp)
{
int i;
unsigned long timeout;
mutex_lock(&vpu_data.lock);
if (open_count > 0 && !(--open_count)) {
/* Wait for vpu go to idle state */
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
if (READ_REG(BIT_CUR_PC)) {
timeout = jiffies + HZ;
while (READ_REG(BIT_BUSY_FLAG)) {
msleep(1);
if (time_after(jiffies, timeout)) {
dev_warn(vpu_dev, "VPU timeout during release\n");
break;
}
}
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
/* Clean up interrupt */
cancel_work_sync(&vpu_data.work);
flush_workqueue(vpu_data.workqueue);
irq_status = 0;
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
if (READ_REG(BIT_BUSY_FLAG)) {
if (cpu_is_mx51() || cpu_is_mx53()) {
dev_err(vpu_dev,
"fatal error: can't gate/power off when VPU is busy\n");
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
mutex_unlock(&vpu_data.lock);
return -EFAULT;
}
#ifdef CONFIG_SOC_IMX6Q
if (cpu_is_mx6dl() || cpu_is_mx6q()) {
WRITE_REG(0x11, 0x10F0);
timeout = jiffies + HZ;
while (READ_REG(0x10F4) != 0x77) {
msleep(1);
if (time_after(jiffies, timeout))
break;
}
if (READ_REG(0x10F4) != 0x77) {
dev_err(vpu_dev,
"fatal error: can't gate/power off when VPU is busy\n");
WRITE_REG(0x0, 0x10F0);
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
mutex_unlock(&vpu_data.lock);
return -EFAULT;
} else
vpu_reset();
}
#endif
}
}
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
vpu_free_buffers();
/* Free shared memory when vpu device is idle */
vpu_free_dma_buffer(&share_mem);
share_mem.cpu_addr = 0;
vfree((void *)vshare_mem.cpu_addr);
vshare_mem.cpu_addr = 0;
vpu_clk_usercount = atomic_read(&clk_cnt_from_ioc);
for (i = 0; i < vpu_clk_usercount; i++) {
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
atomic_dec(&clk_cnt_from_ioc);
}
vpu_power_up(false);
}
mutex_unlock(&vpu_data.lock);
return 0;
}
/*!
* @brief fasync function for vpu file operation
* @return 0 on success or negative error code on error
*/
static int vpu_fasync(int fd, struct file *filp, int mode)
{
struct vpu_priv *dev = (struct vpu_priv *)filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
/*!
* @brief memory map function of harware registers for vpu file operation
* @return 0 on success or negative error code on error
*/
static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm)
{
unsigned long pfn;
vm->vm_flags |= VM_IO | VM_RESERVED;
/*
* Since vpu registers have been mapped with ioremap() at probe
* which L_PTE_XN is 1, and the same physical address must be
* mapped multiple times with same type, so set L_PTE_XN to 1 here.
* Otherwise, there may be unexpected result in video codec.
*/
vm->vm_page_prot = pgprot_noncachedxn(vm->vm_page_prot);
pfn = phy_vpu_base_addr >> PAGE_SHIFT;
dev_dbg(vpu_dev, "size=0x%x, page no.=0x%x\n",
(int)(vm->vm_end - vm->vm_start), (int)pfn);
return remap_pfn_range(vm, vm->vm_start, pfn, vm->vm_end - vm->vm_start,
vm->vm_page_prot) ? -EAGAIN : 0;
}
/*!
* @brief memory map function of memory for vpu file operation
* @return 0 on success or negative error code on error
*/
static int vpu_map_dma_mem(struct file *fp, struct vm_area_struct *vm)
{
int request_size;
request_size = vm->vm_end - vm->vm_start;
dev_dbg(vpu_dev, "start=0x%x, pgoff=0x%x, size=0x%x\n",
(unsigned int)(vm->vm_start), (unsigned int)(vm->vm_pgoff),
request_size);
vm->vm_flags |= VM_IO | VM_RESERVED;
vm->vm_page_prot = pgprot_writecombine(vm->vm_page_prot);
return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff,
request_size, vm->vm_page_prot) ? -EAGAIN : 0;
}
/* !
* @brief memory map function of vmalloced share memory
* @return 0 on success or negative error code on error
*/
static int vpu_map_vshare_mem(struct file *fp, struct vm_area_struct *vm)
{
int ret = -EINVAL;
ret = remap_vmalloc_range(vm, (void *)(vm->vm_pgoff << PAGE_SHIFT), 0);
vm->vm_flags |= VM_IO;
return ret;
}
/*!
* @brief memory map interface for vpu file operation
* @return 0 on success or negative error code on error
*/
static int vpu_mmap(struct file *fp, struct vm_area_struct *vm)
{
unsigned long offset;
offset = vshare_mem.cpu_addr >> PAGE_SHIFT;
if (vm->vm_pgoff && (vm->vm_pgoff == offset))
return vpu_map_vshare_mem(fp, vm);
else if (vm->vm_pgoff)
return vpu_map_dma_mem(fp, vm);
else
return vpu_map_hwregs(fp, vm);
}
const struct file_operations vpu_fops = {
.owner = THIS_MODULE,
.open = vpu_open,
.unlocked_ioctl = vpu_ioctl,
.release = vpu_release,
.fasync = vpu_fasync,
.mmap = vpu_mmap,
};
/*!
* This function is called by the driver framework to initialize the vpu device.
* @param dev The device structure for the vpu passed in by the framework.
* @return 0 on success or negative error code on error
*/
static int vpu_dev_probe(struct platform_device *pdev)
{
int err = 0;
struct device *temp_class;
struct resource *res;
unsigned long addr = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
struct device_node *np = pdev->dev.of_node;
u32 iramsize;
err = of_property_read_u32(np, "iramsize", (u32 *)&iramsize);
if (!err && iramsize)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
{
iram_pool = of_gen_pool_get(np, "iram", 0);
if (!iram_pool) {
dev_err(&pdev->dev, "iram pool not available\n");
return -ENOMEM;
}
iram_base = gen_pool_alloc(iram_pool, iramsize);
if (!iram_base) {
dev_err(&pdev->dev, "unable to alloc iram\n");
return -ENOMEM;
}
addr = gen_pool_virt_to_phys(iram_pool, iram_base);
}
#else
iram_alloc(iramsize, &addr);
#endif
if (addr == 0)
iram.start = iram.end = 0;
else {
iram.start = addr;
iram.end = addr + iramsize - 1;
}
#else
vpu_plat = pdev->dev.platform_data;
if (vpu_plat && vpu_plat->iram_enable && vpu_plat->iram_size)
iram_alloc(vpu_plat->iram_size, &addr);
if (addr == 0)
iram.start = iram.end = 0;
else {
iram.start = addr;
iram.end = addr + vpu_plat->iram_size - 1;
}
#endif
vpu_dev = &pdev->dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpu_regs");
if (!res) {
dev_err(vpu_dev, "vpu: unable to get vpu base addr\n");
return -ENODEV;
}
phy_vpu_base_addr = res->start;
vpu_base = ioremap(res->start, res->end - res->start);
vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops);
if (vpu_major < 0) {
dev_err(vpu_dev, "vpu: unable to get a major for VPU\n");
err = -EBUSY;
goto error;
}
vpu_class = class_create(THIS_MODULE, "mxc_vpu");
if (IS_ERR(vpu_class)) {
err = PTR_ERR(vpu_class);
goto err_out_chrdev;
}
temp_class = device_create(vpu_class, NULL, MKDEV(vpu_major, 0),
NULL, "mxc_vpu");
if (IS_ERR(temp_class)) {
err = PTR_ERR(temp_class);
goto err_out_class;
}
vpu_clk = clk_get(&pdev->dev, "vpu_clk");
if (IS_ERR(vpu_clk)) {
err = -ENOENT;
goto err_out_class;
}
vpu_ipi_irq = platform_get_irq_byname(pdev, "vpu_ipi_irq");
if (vpu_ipi_irq < 0) {
dev_err(vpu_dev, "vpu: unable to get vpu interrupt\n");
err = -ENXIO;
goto err_out_class;
}
err = request_irq(vpu_ipi_irq, vpu_ipi_irq_handler, 0, "VPU_CODEC_IRQ",
(void *)(&vpu_data));
if (err)
goto err_out_class;
if (vpu_power_get(true)) {
if (!(cpu_is_mx51() || cpu_is_mx53())) {
dev_err(vpu_dev, "failed to get vpu power\n");
goto err_out_class;
} else {
/* regulator_get will return error on MX5x,
* just igore it everywhere*/
dev_warn(vpu_dev, "failed to get vpu power\n");
}
}
#ifdef MXC_VPU_HAS_JPU
vpu_jpu_irq = platform_get_irq_byname(pdev, "vpu_jpu_irq");
if (vpu_jpu_irq < 0) {
dev_err(vpu_dev, "vpu: unable to get vpu jpu interrupt\n");
err = -ENXIO;
free_irq(vpu_ipi_irq, &vpu_data);
goto err_out_class;
}
err = request_irq(vpu_jpu_irq, vpu_jpu_irq_handler, IRQF_TRIGGER_RISING,
"VPU_JPG_IRQ", (void *)(&vpu_data));
if (err) {
free_irq(vpu_ipi_irq, &vpu_data);
goto err_out_class;
}
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
pm_runtime_enable(&pdev->dev);
#endif
vpu_data.workqueue = create_workqueue("vpu_wq");
INIT_WORK(&vpu_data.work, vpu_worker_callback);
mutex_init(&vpu_data.lock);
dev_info(vpu_dev, "VPU initialized\n");
goto out;
err_out_class:
device_destroy(vpu_class, MKDEV(vpu_major, 0));
class_destroy(vpu_class);
err_out_chrdev:
unregister_chrdev(vpu_major, "mxc_vpu");
error:
iounmap(vpu_base);
out:
return err;
}
static int vpu_dev_remove(struct platform_device *pdev)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
pm_runtime_disable(&pdev->dev);
#endif
free_irq(vpu_ipi_irq, &vpu_data);
#ifdef MXC_VPU_HAS_JPU
free_irq(vpu_jpu_irq, &vpu_data);
#endif
cancel_work_sync(&vpu_data.work);
flush_workqueue(vpu_data.workqueue);
destroy_workqueue(vpu_data.workqueue);
iounmap(vpu_base);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
if (iram.start)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
gen_pool_free(iram_pool, iram_base, iram.end-iram.start+1);
#else
iram_free(iram.start, iram.end-iram.start+1);
#endif
#else
if (vpu_plat && vpu_plat->iram_enable && vpu_plat->iram_size)
iram_free(iram.start, vpu_plat->iram_size);
#endif
vpu_power_get(false);
return 0;
}
#ifdef CONFIG_PM
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
static int vpu_suspend(struct device *dev)
#else
static int vpu_suspend(struct platform_device *pdev, pm_message_t state)
#endif
{
int i;
unsigned long timeout;
mutex_lock(&vpu_data.lock);
if (open_count == 0) {
/* VPU is released (all instances are freed),
* clock is already off, context is no longer needed,
* power is already off on MX6,
* gate power on MX51 */
if (cpu_is_mx51()) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (vpu_plat->pg)
vpu_plat->pg(1);
#endif
}
} else {
/* Wait for vpu go to idle state, suspect vpu cannot be changed
to idle state after about 1 sec */
timeout = jiffies + HZ;
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
while (READ_REG(BIT_BUSY_FLAG)) {
msleep(1);
if (time_after(jiffies, timeout)) {
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
mutex_unlock(&vpu_data.lock);
return -EAGAIN;
}
}
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
/* Make sure clock is disabled before suspend */
vpu_clk_usercount = atomic_read(&clk_cnt_from_ioc);
for (i = 0; i < vpu_clk_usercount; i++) {
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
}
if (cpu_is_mx53()) {
mutex_unlock(&vpu_data.lock);
return 0;
}
if (bitwork_mem.cpu_addr != 0) {
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
/* Save 64 registers from BIT_CODE_BUF_ADDR */
for (i = 0; i < 64; i++)
regBk[i] = READ_REG(BIT_CODE_BUF_ADDR + (i * 4));
pc_before_suspend = READ_REG(BIT_CUR_PC);
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (vpu_plat->pg)
vpu_plat->pg(1);
#endif
/* If VPU is working before suspend, disable
* regulator to make usecount right. */
vpu_power_up(false);
}
mutex_unlock(&vpu_data.lock);
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
static int vpu_resume(struct device *dev)
#else
static int vpu_resume(struct platform_device *pdev)
#endif
{
int i;
mutex_lock(&vpu_data.lock);
if (open_count == 0) {
/* VPU is released (all instances are freed),
* clock should be kept off, context is no longer needed,
* power should be kept off on MX6,
* disable power gating on MX51 */
if (cpu_is_mx51()) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (vpu_plat->pg)
vpu_plat->pg(0);
#endif
}
} else {
if (cpu_is_mx53())
goto recover_clk;
/* If VPU is working before suspend, enable
* regulator to make usecount right. */
vpu_power_up(true);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
if (vpu_plat->pg)
vpu_plat->pg(0);
#endif
if (bitwork_mem.cpu_addr != 0) {
u32 *p = (u32 *) bitwork_mem.cpu_addr;
u32 data, pc;
u16 data_hi;
u16 data_lo;
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
pc = READ_REG(BIT_CUR_PC);
if (pc) {
dev_warn(vpu_dev, "Not power off after suspend (PC=0x%x)\n", pc);
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
goto recover_clk;
}
/* Restore registers */
for (i = 0; i < 64; i++)
WRITE_REG(regBk[i], BIT_CODE_BUF_ADDR + (i * 4));
WRITE_REG(0x0, BIT_RESET_CTRL);
WRITE_REG(0x0, BIT_CODE_RUN);
/* MX6 RTL has a bug not to init MBC_SET_SUBBLK_EN on reset */
#ifdef CONFIG_SOC_IMX6Q
WRITE_REG(0x0, MBC_SET_SUBBLK_EN);
#endif
/*
* Re-load boot code, from the codebuffer in external RAM.
* Thankfully, we only need 4096 bytes, same for all platforms.
*/
for (i = 0; i < 2048; i += 4) {
data = p[(i / 2) + 1];
data_hi = (data >> 16) & 0xFFFF;
data_lo = data & 0xFFFF;
WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN);
WRITE_REG(((i + 1) << 16) | data_lo,
BIT_CODE_DOWN);
data = p[i / 2];
data_hi = (data >> 16) & 0xFFFF;
data_lo = data & 0xFFFF;
WRITE_REG(((i + 2) << 16) | data_hi,
BIT_CODE_DOWN);
WRITE_REG(((i + 3) << 16) | data_lo,
BIT_CODE_DOWN);
}
if (pc_before_suspend) {
WRITE_REG(0x1, BIT_BUSY_FLAG);
WRITE_REG(0x1, BIT_CODE_RUN);
while (READ_REG(BIT_BUSY_FLAG))
;
} else {
dev_warn(vpu_dev, "PC=0 before suspend\n");
}
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
}
recover_clk:
/* Recover vpu clock */
for (i = 0; i < vpu_clk_usercount; i++) {
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
}
}
mutex_unlock(&vpu_data.lock);
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
static int vpu_runtime_suspend(struct device *dev)
{
release_bus_freq(BUS_FREQ_HIGH);
return 0;
}
static int vpu_runtime_resume(struct device *dev)
{
request_bus_freq(BUS_FREQ_HIGH);
return 0;
}
static const struct dev_pm_ops vpu_pm_ops = {
SET_RUNTIME_PM_OPS(vpu_runtime_suspend, vpu_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(vpu_suspend, vpu_resume)
};
#endif
#else
#define vpu_suspend NULL
#define vpu_resume NULL
#endif /* !CONFIG_PM */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
static const struct of_device_id vpu_of_match[] = {
{ .compatible = "fsl,imx6-vpu", },
{/* sentinel */}
};
MODULE_DEVICE_TABLE(of, vpu_of_match);
#endif
/*! Driver definition
*
*/
static struct platform_driver mxcvpu_driver = {
.driver = {
.name = "mxc_vpu",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
.of_match_table = vpu_of_match,
#ifdef CONFIG_PM
.pm = &vpu_pm_ops,
#endif
#endif
},
.probe = vpu_dev_probe,
.remove = vpu_dev_remove,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
.suspend = vpu_suspend,
.resume = vpu_resume,
#endif
};
static int __init vpu_init(void)
{
int ret = platform_driver_register(&mxcvpu_driver);
init_waitqueue_head(&vpu_queue);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)
memblock_analyze();
top_address_DRAM = memblock_end_of_DRAM_with_reserved();
#endif
return ret;
}
static void __exit vpu_exit(void)
{
if (vpu_major > 0) {
device_destroy(vpu_class, MKDEV(vpu_major, 0));
class_destroy(vpu_class);
unregister_chrdev(vpu_major, "mxc_vpu");
vpu_major = 0;
}
vpu_free_dma_buffer(&bitwork_mem);
vpu_free_dma_buffer(&pic_para_mem);
vpu_free_dma_buffer(&user_data_mem);
/* reset VPU state */
vpu_power_up(true);
clk_prepare(vpu_clk);
clk_enable(vpu_clk);
vpu_reset();
clk_disable(vpu_clk);
clk_unprepare(vpu_clk);
vpu_power_up(false);
clk_put(vpu_clk);
platform_driver_unregister(&mxcvpu_driver);
return;
}
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC");
MODULE_LICENSE("GPL");
module_init(vpu_init);
module_exit(vpu_exit);