blob: a64d22c54df9a84a8f76f4fd07f197e34e567c8f [file] [log] [blame]
/*
* drivers/uio/uio_codaL.c
*
* chip&media codaL UIO driver
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#include <linux/uio_codaL.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#ifdef CONFIG_ARM_SMMU
#include <linux/iommu.h>
#include <asm/dma-iommu.h>
extern struct dma_iommu_mapping *pxa_ion_iommu_mapping;
#endif
#define VDEC_WORKING_BUFFER_SIZE SZ_1M
#define UIO_CODAL_VERSION "build-001"
/* HW capability is 16, only use 10 instance to reduce memory requirements. */
#define MAX_NUM_VPUSLOT 10
#define MAX_NUM_FILEHANDLE 10
#define BIT_INT_CLEAR (0xC)
#define BIT_BUSY_FLAG (0x160)
#define BIT_INT_ENABLE (0x170)
#define MJPEG_PIC_STATUS_REG (0x804)
#define CODAL_TYPE_VPU (0)
#define CODAL_TYPE_JPU (1)
struct uio_codaL_dev {
struct uio_info uio_info;
void *reg_base;
struct clk *fclk;
struct clk *aclk;
struct mutex mutex; /* used to protect open/release */
int power_status; /* 0-off 1-on */
int clk_status;
int filehandle_ins_num;
int firmware_download;
struct semaphore sema;
unsigned int coda_features;
unsigned int jpu_status;
unsigned int codaL_type;
struct device *dev;
};
/*
* use following data structure to assicate fd and codec instance,
* one fd corresponding to one codec instance
*/
struct codaL_instance {
int occupied;
int gotsemaphore;
int vpuslotidx; /* the item idex in fd_codecinst_arr[]*/
int slotusing;
unsigned int codectypeid;
};
static struct codaL_instance fd_codecinst_arr[MAX_NUM_VPUSLOT];
struct vpu_info {
unsigned int off;
#ifdef CONFIG_COMPAT
compat_uptr_t value;
#else
void *value;
#endif
};
static inline unsigned long compat_copy_to_user(unsigned long to,
const void *from, unsigned long n)
{
#ifdef CONFIG_COMPAT
return copy_to_user(compat_ptr(to), from, n);
#else
return copy_to_user((void __user *)to, from, n);
#endif
}
static int codaL_power_on(struct uio_codaL_dev *cdev)
{
if (cdev->power_status == 1)
return 0;
#ifdef CONFIG_MMP_PM_DOMAIN_COMMON
pm_runtime_get_sync(cdev->dev);
#endif
cdev->power_status = 1;
return 0;
}
static int codaL_clk_on(struct uio_codaL_dev *cdev)
{
if (cdev->clk_status != 0)
return 0;
clk_prepare_enable(cdev->fclk);
clk_prepare_enable(cdev->aclk);
cdev->clk_status = 1;
return 0;
}
static int codaL_power_off(struct uio_codaL_dev *cdev)
{
if (cdev->power_status == 0)
return 0;
#ifdef CONFIG_MMP_PM_DOMAIN_COMMON
pm_runtime_put_sync(cdev->dev);
#endif
cdev->power_status = 0;
return 0;
}
static int codaL_clk_off(struct uio_codaL_dev *cdev)
{
if (cdev->clk_status == 0)
return 0;
clk_disable_unprepare(cdev->aclk);
clk_disable_unprepare(cdev->fclk);
cdev->clk_status = 0;
return 0;
}
static int active_codaL(struct uio_codaL_dev *cdev, int on)
{
int i, ret;
if (on) {
ret = codaL_power_on(cdev);
if (ret)
return -1;
ret = codaL_clk_on(cdev);
if (ret)
return -1;
sema_init(&cdev->sema, 1);
} else {
ret = codaL_clk_off(cdev);
if (ret)
return -1;
ret = codaL_power_off(cdev);
if (ret)
return -1;
}
cdev->firmware_download = 0;
/*clear fd_codecinst_arr */
for (i = 0; i < MAX_NUM_VPUSLOT; i++) {
fd_codecinst_arr[i].occupied = 0;
fd_codecinst_arr[i].slotusing = 0;
fd_codecinst_arr[i].codectypeid = 0;
}
return 0;
}
static void codaL_cleanup(struct uio_codaL_dev *cdev)
{
pr_info("%s: process was killed abnormal\n", __func__);
codaL_clk_on(cdev);
__raw_writel(0x0, cdev->reg_base + BIT_INT_ENABLE);
__raw_writel(0x1, cdev->reg_base + BIT_INT_CLEAR);
codaL_clk_off(cdev);
}
static int __maybe_unused codaL_abnormal_stop(struct uio_codaL_dev *cdev)
{
int i;
pr_info("%s: process was killed abnormal\n", __func__);
/*
*currently, we couldn't do other thing except waiting
*for CNM VPU stop by itself
*/
for (i = 0; i < 6; i++) {
msleep(5);
if (0 == __raw_readl(cdev->reg_base + BIT_BUSY_FLAG))
return 0;
}
return -1;
}
static int codaL_open(struct uio_info *info, struct inode *inode,
void *file_priv)
{
struct uio_codaL_dev *cdev;
struct uio_listener *listener = file_priv;
int ret = 0, i;
pr_debug("Enter codaL_open()\n");
cdev = (struct uio_codaL_dev *)info->priv;
mutex_lock(&cdev->mutex);
if (cdev->filehandle_ins_num < MAX_NUM_FILEHANDLE)
cdev->filehandle_ins_num++;
else {
pr_info(KERN_ERR "current codaL cannot accept more file handle instance!!\n");
ret = -EACCES;
goto out;
}
if (cdev->filehandle_ins_num == 1) {
if (0 != active_codaL(cdev, 1)) {
cdev->filehandle_ins_num = 0;
ret = -EACCES;
goto out;
}
}
for (i = 0; i < MAX_NUM_VPUSLOT; i++) {
if (fd_codecinst_arr[i].occupied == 0) {
listener->extend = &fd_codecinst_arr[i];
fd_codecinst_arr[i].occupied = 1;
fd_codecinst_arr[i].gotsemaphore = 0;
break;
}
}
if (i == MAX_NUM_VPUSLOT) {
cdev->filehandle_ins_num--;
pr_info(KERN_ERR "couldn't found any idle seat for new fd!!\n");
ret = -EACCES;
}
out:
mutex_unlock(&cdev->mutex);
pr_debug("Leave codaL_open(), ret = %d\n", ret);
return ret;
}
static int codaL_release(struct uio_info *info, struct inode *inode,
void *file_priv)
{
struct uio_codaL_dev *cdev;
struct uio_listener *listener = file_priv;
struct codaL_instance *pinst;
int i, ret = 0;
pr_debug("Enter codaL_release()\n");
cdev = (struct uio_codaL_dev *)info->priv;
pinst = (struct codaL_instance *)listener->extend;
mutex_lock(&cdev->mutex);
/*
* if this codec instance got semaphore, it means this codec instance
* want VPU to do something, and other codec instance will wait.
* If process is killed before VPU finished working for this codec
* instance and instance release semaphore, we need do something.
*/
if (pinst->gotsemaphore) {
if (cdev->clk_status == 1) {
for (i = 0; i < 30; i++) {
msleep(10);
if (0 == __raw_readl(cdev->reg_base + BIT_BUSY_FLAG)) {
/*
* the additional sleep is to
* ensure interrupt is handled,
* as we are not sure which occur
* at first: between BUSY->0 and
* interrupt.
*/
msleep(10);
break;
}
}
if (i == 30)
pr_info("%s: VPU could not stop, will force clean\n", __func__);
codaL_cleanup(cdev);
}
up(&cdev->sema);
pinst->gotsemaphore = 0;
}
if (pinst->slotusing)
pinst->slotusing = 0;
pinst->occupied = 0;
pinst->codectypeid = 0;
listener->extend = NULL;
if (cdev->filehandle_ins_num > 0) {
cdev->filehandle_ins_num--;
if (cdev->filehandle_ins_num == 0) {
if (0 != active_codaL(cdev, 0))
ret = -EFAULT;
}
}
mutex_unlock(&cdev->mutex);
pr_debug("Leave codaL_release(), ret = %d\n", ret);
return ret;
}
static irqreturn_t codaL_irq_handler(int irq, struct uio_info *dev_info)
{
struct uio_codaL_dev *cdev = dev_info->priv;
if (cdev->codaL_type == CODAL_TYPE_VPU) {
/* clear the interrupt */
__raw_writel(0x1, cdev->reg_base + BIT_INT_CLEAR);
} else if (cdev->codaL_type == CODAL_TYPE_JPU) {
cdev->jpu_status = __raw_readl(cdev->reg_base + MJPEG_PIC_STATUS_REG);
if (cdev->jpu_status)
__raw_writel(cdev->jpu_status, cdev->reg_base + MJPEG_PIC_STATUS_REG);
} else
pr_err("codaL: error device type!\n");
return IRQ_HANDLED;
}
static int codaL_ioctl(struct uio_info *info, unsigned int cmd,
unsigned long arg, void *file_priv)
{
unsigned int value;
int ret, idx;
struct vpu_info vpu_info;
void __user *argp = (void __user *)arg;
struct uio_codaL_dev *cdev = info->priv;
struct codaL_instance *pinst;
struct uio_listener *listener = file_priv;
pinst = (struct codaL_instance *)listener->extend;
switch (cmd) {
case CODAL_POWER_ON:
mutex_lock(&cdev->mutex);
ret = codaL_power_on(cdev);
if (0 != ret) {
mutex_unlock(&cdev->mutex);
return -EFAULT;
}
mutex_unlock(&cdev->mutex);
break;
case CODAL_POWER_OFF:
mutex_lock(&cdev->mutex);
ret = codaL_power_off(cdev);
if (0 != ret) {
mutex_unlock(&cdev->mutex);
return -EFAULT;
}
mutex_unlock(&cdev->mutex);
break;
case CODAL_CLK_ON:
mutex_lock(&cdev->mutex);
ret = codaL_clk_on(cdev);
if (0 != ret) {
mutex_unlock(&cdev->mutex);
return -EFAULT;
}
mutex_unlock(&cdev->mutex);
break;
case CODAL_CLK_OFF:
mutex_lock(&cdev->mutex);
ret = codaL_clk_off(cdev);
if (0 != ret) {
mutex_unlock(&cdev->mutex);
return -EFAULT;
}
mutex_unlock(&cdev->mutex);
break;
case CODAL_LOCK:
if (copy_from_user(&vpu_info, argp, sizeof(struct vpu_info)))
return -EFAULT;
if (pinst->gotsemaphore == 0) {
value = down_timeout(&cdev->sema,
msecs_to_jiffies(vpu_info.off));
if (value == 0)
pinst->gotsemaphore = 1;
} else
value = 0;
if (compat_copy_to_user((unsigned long)vpu_info.value, &value,
sizeof(unsigned int)))
return -EFAULT;
break;
case CODAL_UNLOCK:
if (pinst->gotsemaphore) {
up(&cdev->sema);
pinst->gotsemaphore = 0;
}
break;
case CODAL_GETSET_INFO:
if (copy_from_user(&vpu_info, argp, sizeof(struct vpu_info)))
return -EFAULT;
switch (vpu_info.off) {
case 0:
/* 0 get firmware_download */
if (compat_copy_to_user((unsigned long)vpu_info.value,
&cdev->firmware_download, sizeof(int)))
return -EFAULT;
break;
case 1:
/* 1 set firmware_download */
cdev->firmware_download = 1;
break;
case 2:
/* 2 get vpu slot */
if (pinst->slotusing == 0 && pinst->vpuslotidx
< MAX_NUM_VPUSLOT)
idx = pinst->vpuslotidx;
else
idx = -1;
if (compat_copy_to_user((unsigned long)vpu_info.value,
&idx, sizeof(idx)))
return -EFAULT;
pinst->slotusing = 1;
break;
case 3:
/* 3 release vpu slot */
if (pinst->vpuslotidx < MAX_NUM_VPUSLOT)
pinst->slotusing = 0;
break;
case 4:
if (compat_copy_to_user((unsigned long)vpu_info.value,
&cdev->coda_features,
sizeof(cdev->coda_features)))
return -EFAULT;
break;
case 6:
pinst->codectypeid = (unsigned int)(vpu_info.value);
break;
default:
return -EFAULT;
}
break;
case CODAL_GET_JPU_STATUS:
if (compat_copy_to_user((unsigned long)argp, &cdev->jpu_status, sizeof(cdev->jpu_status))) {
pr_err("%s, copy to user error!\n", __func__);
ret = -EFAULT;
}
break;
default:
return -EFAULT;
}
return 0;
}
static int codaL_probe(struct platform_device *pdev)
{
struct resource *res, *sram_res;
struct uio_codaL_dev *cdev;
int i, irq_func, ret = 0;
int sram_internal, nv21_support, seconddaxi_none = SECONDAXI_SUPPORT;
dma_addr_t mem_dma_addr;
void *mem_vir_addr;
int codaL_type;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resources given\n");
return -ENODEV;
}
sram_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!sram_res)
dev_info(&pdev->dev, "no sram resources given\n");
irq_func = platform_get_irq(pdev, 0);
if (irq_func < 0) {
dev_err(&pdev->dev, "missing irq resource in interrupt mode\n");
return -ENODEV;
}
cdev = devm_kzalloc(&pdev->dev, sizeof(*cdev), GFP_KERNEL);
if (!cdev) {
dev_err(&pdev->dev, "uio_codaL_dev: out of memory\n");
return -ENOMEM;
}
cdev->fclk = devm_clk_get(&pdev->dev, "VPUCLK");
if (IS_ERR(cdev->fclk)) {
dev_err(&pdev->dev, "cannot get codaL func clock\n");
ret = PTR_ERR(cdev->fclk);
goto err_fclk;
}
cdev->aclk = devm_clk_get(&pdev->dev, "VPUACLK");
if (IS_ERR(cdev->aclk)) {
dev_err(&pdev->dev, "cannot get codaL axi clock\n");
ret = PTR_ERR(cdev->aclk);
goto err_aclk;
}
cdev->reg_base = (void *)ioremap(res->start, resource_size(res));
if (!cdev->reg_base) {
dev_err(&pdev->dev, "can't remap register area\n");
ret = -ENOMEM;
goto err_reg_base;
}
platform_set_drvdata(pdev, cdev);
cdev->dev = &pdev->dev;
cdev->uio_info.name = UIO_CODAL_NAME;
cdev->uio_info.version = UIO_CODAL_VERSION;
cdev->uio_info.mem[0].internal_addr = (void __iomem *)cdev->reg_base;
cdev->uio_info.mem[0].addr = res->start;
cdev->uio_info.mem[0].memtype = UIO_MEM_PHYS;
cdev->uio_info.mem[0].size = resource_size(res);
#ifdef CONFIG_MMP_PM_DOMAIN_COMMON
pm_runtime_enable(cdev->dev);
#endif
/*
* this 2MB buffer contains firmware buffer, working buffer
* and parameter buffer, which shares in multi-process.
*/
mem_vir_addr = (void *)__get_free_pages(GFP_DMA | GFP_KERNEL,
get_order(VDEC_WORKING_BUFFER_SIZE));
if (!mem_vir_addr) {
ret = -ENOMEM;
goto err_uio_mem;
}
mem_dma_addr = (dma_addr_t)__virt_to_phys((unsigned long int)mem_vir_addr);
#ifdef CONFIG_ARM_SMMU
if (pxa_ion_iommu_mapping)
iommu_map(pxa_ion_iommu_mapping->domain, mem_dma_addr,
mem_dma_addr, VDEC_WORKING_BUFFER_SIZE,
IOMMU_READ | IOMMU_WRITE);
#endif
cdev->uio_info.mem[1].internal_addr = (void __iomem *)mem_vir_addr;
cdev->uio_info.mem[1].addr = mem_dma_addr;
cdev->uio_info.mem[1].memtype = UIO_MEM_PHYS;
cdev->uio_info.mem[1].size = VDEC_WORKING_BUFFER_SIZE;
dev_info(&pdev->dev, "[1] internal addr[%p], addr[0x%08x] size[%ld]\n",
cdev->uio_info.mem[1].internal_addr,
(unsigned int)cdev->uio_info.mem[1].addr,
cdev->uio_info.mem[1].size);
if (sram_res) {
cdev->uio_info.mem[2].addr = sram_res->start;
cdev->uio_info.mem[2].memtype = UIO_MEM_PHYS;
cdev->uio_info.mem[2].size = resource_size(sram_res);
dev_info(&pdev->dev, "[2] addr[0x%08x] size[%ld]\n",
(unsigned int)cdev->uio_info.mem[2].addr,
cdev->uio_info.mem[2].size);
}
cdev->uio_info.irq_flags = IRQF_DISABLED;
cdev->uio_info.irq = irq_func;
cdev->uio_info.handler = codaL_irq_handler;
cdev->uio_info.priv = cdev;
cdev->uio_info.open = codaL_open;
cdev->uio_info.release = codaL_release;
cdev->uio_info.ioctl = codaL_ioctl;
cdev->uio_info.mmap = NULL;
if (IS_ENABLED(CONFIG_OF)) {
struct device_node *np = pdev->dev.of_node;
if (!np) {
ret = -EINVAL;
goto err_uio_mem;
}
if (of_property_read_u32(np, "marvell,codaL-type",
&codaL_type)) {
ret = -EINVAL;
goto err_uio_mem;
}
if (of_property_read_u32(np, "marvell,sram-internal",
&sram_internal)) {
ret = -EINVAL;
goto err_uio_mem;
}
if (of_property_read_u32(np, "marvell,nv21_support",
&nv21_support)) {
ret = -EINVAL;
goto err_uio_mem;
}
if (of_property_read_u32(np, "marvell,seconddaxi_none",
&seconddaxi_none)) {
seconddaxi_none = SECONDAXI_SUPPORT;
}
cdev->coda_features =
VPU_FEATURE_BITMASK_SRAM(sram_internal) |
VPU_FEATURE_BITMASK_NV21(nv21_support) |
VPU_FEATURE_BITMASK_NO2NDAXI(seconddaxi_none);
cdev->codaL_type = codaL_type;
} else {
if (pdev->dev.platform_data)
cdev->coda_features =
*((unsigned int *)(pdev->dev.platform_data));
}
mutex_init(&(cdev->mutex));
cdev->filehandle_ins_num = 0;
for (i = 0; i < MAX_NUM_VPUSLOT; i++)
fd_codecinst_arr[i].vpuslotidx = i;
ret = uio_register_device(&pdev->dev, &cdev->uio_info);
if (ret) {
dev_err(&pdev->dev, "failed to register uio device\n");
goto err_uio_register;
}
return 0;
err_uio_register:
free_pages((unsigned long)cdev->uio_info.mem[1].internal_addr,
get_order(VDEC_WORKING_BUFFER_SIZE));
err_uio_mem:
iounmap(cdev->uio_info.mem[0].internal_addr);
err_reg_base:
clk_put(cdev->aclk);
err_aclk:
clk_put(cdev->fclk);
err_fclk:
devm_kfree(&pdev->dev, cdev);
return ret;
}
static int codaL_remove(struct platform_device *pdev)
{
struct uio_codaL_dev *cdev = platform_get_drvdata(pdev);
uio_unregister_device(&cdev->uio_info);
free_pages((unsigned long)cdev->uio_info.mem[1].internal_addr,
get_order(VDEC_WORKING_BUFFER_SIZE));
iounmap(cdev->uio_info.mem[0].internal_addr);
clk_put(cdev->aclk);
clk_put(cdev->fclk);
#ifdef CONFIG_MMP_PM_DOMAIN_COMMON
pm_runtime_disable(cdev->dev);
#endif
return 0;
}
#ifndef CONFIG_MMP_PM_DOMAIN_COMMON
static bool is_suspend;
#endif
static int codaL_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct uio_codaL_dev *cdev = platform_get_drvdata(pdev);
int i;
bool cannot_off = false;
if (cdev->filehandle_ins_num <= 0)
return 0;
if (cdev->clk_status != 0)
return 0;
for (i = 0; i < MAX_NUM_VPUSLOT; i++) {
if ((fd_codecinst_arr[i].occupied == 1) &&
((fd_codecinst_arr[i].codectypeid & VPU_CODEC_TYPEID_ENC_MASK)
|| fd_codecinst_arr[i].codectypeid == 0)) {
cannot_off = true;
break;
}
}
#ifdef CONFIG_MMP_PM_DOMAIN_COMMON
if (!cannot_off)
return -EAGAIN;
#else
if (!cannot_off) {
dev_info(dev, "codaL poweroff in suspend\n");
codaL_power_off(cdev);
is_suspend = true;
}
#endif
return 0;
}
static int codaL_resume(struct device *dev)
{
#ifndef CONFIG_MMP_PM_DOMAIN_COMMON
struct platform_device *pdev = to_platform_device(dev);
struct uio_codaL_dev *cdev = platform_get_drvdata(pdev);
if (is_suspend) {
dev_info(dev, "codaL poweron in resume\n");
codaL_power_on(cdev);
is_suspend = false;
}
#endif
return 0;
}
static void codaL_shutdown(struct platform_device *dev)
{
}
static const struct dev_pm_ops codaL_pm_ops = {
.suspend = codaL_suspend,
.resume = codaL_resume,
};
static struct of_device_id codaL_dt_ids[] = {
{ .compatible = "mrvl,mmp-codaL",},
{}
};
static struct platform_driver codaL_driver = {
.probe = codaL_probe,
.remove = codaL_remove,
.shutdown = codaL_shutdown,
.driver = {
.name = UIO_CODAL_NAME,
.owner = THIS_MODULE,
.of_match_table = codaL_dt_ids,
.pm = &codaL_pm_ops,
},
};
module_platform_driver(codaL_driver);
MODULE_AUTHOR("Leo Song (liangs@marvell.com)");
MODULE_DESCRIPTION("UIO driver for Chip and Media CodaL codec");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" UIO_CODAL_NAME);