blob: c68e6749ed1cf9a315c21b014192c4de189ec49e [file] [log] [blame]
/*
* drivers/uio/uio_hantro.c
*
* Hantro video decoder engine UIO driver.
*
* Baoyin Shan <byshan@marvell.com>
*
* 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.
*
* Based on an earlier version by Rashid Zaman <rzaman@marvell.com>.
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/clk-private.h>
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/uio_hantro.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_qos.h>
#include <linux/pm_runtime.h>
#define UIO_HANTRO_VERSION "build-001"
#ifdef HANTRO_DEBUG
#undef PDEBUG
#define PDEBUG(fmt, args...) pr_info("uio_hantro: " fmt, ## args)
#else
#define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#define ENC_IO_SIZE (240 * 4) /* bytes */
static const int dec_hw_id[] = { 0x8190, 0x8170, 0x9170, 0x9190, 0x6731 };
static const int enc_hw_id[] = { 0x6280, 0x7280, 0x8270, 0x8290, 0x4831 };
#define HW_DET_RETRY 3
static struct vpu_instance dec_ins_arr[MAX_NUM_DECINS];
static struct vpu_instance pp_ins_arr[MAX_NUM_PPINS];
static struct vpu_instance enc_ins_arr[MAX_NUM_ENCINS];
/* protect dec/pp/enc shared hw resource such as power*/
static DEFINE_MUTEX(hw_mutex);
/* semaphore to protect dfc and HW access */
static DEFINE_SEMAPHORE(dec_sema);
static DEFINE_SEMAPHORE(pp_sema);
static DEFINE_SEMAPHORE(enc_sema);
int vpu_dfc_lock(struct clk *clk, unsigned int lock)
{
int ret = 0;
if (!strcmp("vpu_decoder", clk->name)) {
if (lock) {
ret = down_interruptible(&dec_sema);
ret = down_interruptible(&pp_sema);
} else {
up(&pp_sema);
up(&dec_sema);
}
} else if (!strcmp("vpu_encoder", clk->name)) {
if (lock)
ret = down_interruptible(&enc_sema);
else
up(&enc_sema);
}
return ret;
}
static struct vpu_instance *__get_ins_arr(struct vpu_dev *vdev)
{
if (vdev->codec_type == HANTRO_DEC)
return dec_ins_arr;
else if (vdev->codec_type == HANTRO_PP)
return pp_ins_arr;
else if (vdev->codec_type == HANTRO_ENC)
return enc_ins_arr;
else
pr_err("%s, vpu get ins arr error!!!\n", __func__);
return NULL;
}
static int __max_ins_num(struct vpu_dev *vdev)
{
if (vdev->codec_type == HANTRO_DEC)
return MAX_NUM_DECINS;
else if (vdev->codec_type == HANTRO_PP)
return MAX_NUM_PPINS;
else if (vdev->codec_type == HANTRO_ENC)
return MAX_NUM_ENCINS;
else
pr_err("%s, vpu get max ins num error!!!\n", __func__);
return -1;
}
static int hantro_check_hw(struct vpu_dev *vdev)
{
long int hw_id;
int ret = 0;
size_t num_hw;
int retry_cnt = 0;
while (retry_cnt++ < HW_DET_RETRY) {
pr_info("%s, %d time...\n", __func__, retry_cnt);
hw_id = readl(vdev->reg_base);
pr_info("%s, HW ID=0x%lx\n", __func__, hw_id);
hw_id = (hw_id >> 16) & 0xFFFF; /* product version only */
if ((vdev->codec_type == HANTRO_DEC) ||
(vdev->codec_type == HANTRO_PP)) {
num_hw = sizeof(dec_hw_id) / sizeof(*dec_hw_id);
while (num_hw--) {
if (hw_id == dec_hw_id[num_hw]) {
pr_info("hx170dec: HW found\n");
goto out;
}
}
} else if (vdev->codec_type == HANTRO_ENC) {
num_hw = sizeof(enc_hw_id) / sizeof(*enc_hw_id);
while (num_hw--) {
if (hw_id == enc_hw_id[num_hw]) {
pr_info("hx280enc: HW found\n");
goto out;
}
}
}
}
pr_err("%s: No Compatible HW found at 0x%ld\n", __func__,
(unsigned long)vdev->uio_info.mem[0].addr);
ret = -ENODEV;
out:
return ret;
}
static int hantro_reset(struct vpu_dev *vdev)
{
int i;
if (!atomic_read(&vdev->clk_on))
return 0;
if ((vdev->codec_type == HANTRO_DEC) ||
(vdev->codec_type == HANTRO_PP)) {
writel(0, vdev->reg_base + 0x04);
for (i = 4; i < vdev->uio_info.mem[0].size; i += 4)
writel(0, vdev->reg_base + i);
} else if (vdev->codec_type == HANTRO_ENC) {
writel(0, vdev->reg_base + 0x38);
for (i = 4; i < ENC_IO_SIZE; i += 4)
writel(0, vdev->reg_base + i);
}
return 0;
}
static int hantro_cleanup(struct vpu_dev *vdev)
{
if (!atomic_read(&vdev->clk_on))
return 0;
switch (vdev->codec_type) {
case HANTRO_DEC:
writel(0, vdev->reg_base + 0x04);
break;
case HANTRO_PP:
writel(0, vdev->reg_base + 0xF0);
break;
case HANTRO_ENC:
writel(0, vdev->reg_base + 0x38);
writel(0, vdev->reg_base + INT_REG_ENC);
break;
default:
return -ENODEV;
}
return 0;
}
static void vpu_lock_init(struct vpu_dev *vdev)
{
switch (vdev->codec_type) {
case HANTRO_DEC:
vdev->sema = &dec_sema;
break;
case HANTRO_PP:
vdev->sema = &pp_sema;
break;
case HANTRO_ENC:
vdev->sema = &enc_sema;
break;
default:
return;
}
}
static int vpu_lock(unsigned long ms, struct vpu_dev *vdev)
{
int ret;
ret = down_timeout(vdev->sema, msecs_to_jiffies(ms));
PDEBUG("%s, dev_type = %d, ret = %d\n",
__func__, vdev->codec_type, ret);
return ret;
}
static int vpu_unlock(struct vpu_dev *vdev)
{
PDEBUG("%s, dev_type = %d, sema_count = %d\n",
__func__, vdev->codec_type, vdev->sema->count);
if (vdev->sema->count == 0) {
up(vdev->sema);
return 0;
} else if (vdev->sema->count == 1) {
return 0;
} else
return -1;
}
static int vpu_clk_on(struct vpu_dev *vdev)
{
if ((NULL != vdev->fclk) && (NULL != vdev->bclk) &&
!atomic_read(&vdev->clk_on)) {
clk_prepare_enable(vdev->fclk);
clk_prepare_enable(vdev->bclk);
atomic_set(&vdev->clk_on, 1);
}
PDEBUG("%s, dev_type = %d\n", __func__, vdev->codec_type);
return 0;
}
static int vpu_clk_off(struct vpu_dev *vdev)
{
if ((NULL != vdev->fclk) && (NULL != vdev->bclk) &&
atomic_cmpxchg(&vdev->clk_on, 1, 0)) {
clk_disable_unprepare(vdev->fclk);
clk_disable_unprepare(vdev->bclk);
}
PDEBUG("%s, dev_type = %d\n", __func__, vdev->codec_type);
return 0;
}
static int vpu_power_on(struct vpu_dev *vdev)
{
mutex_lock(&hw_mutex);
if (!atomic_cmpxchg(&vdev->power_on, 0, 1))
pm_runtime_get_sync(vdev->dev);
mutex_unlock(&hw_mutex);
PDEBUG("%s, dev_type = %d\n", __func__, vdev->codec_type);
return 0;
}
static int vpu_power_off(struct vpu_dev *vdev)
{
mutex_lock(&hw_mutex);
if (atomic_cmpxchg(&vdev->power_on, 1, 0))
pm_runtime_put_sync(vdev->dev);
mutex_unlock(&hw_mutex);
PDEBUG("%s, dev_type = %d\n", __func__, vdev->codec_type);
return 0;
}
static int vpu_turn_on(struct vpu_dev *vdev)
{
int ret;
ret = vpu_power_on(vdev);
if (ret)
return -1;
ret = vpu_clk_on(vdev);
if (ret)
return -1;
return 0;
}
static int vpu_turn_off(struct vpu_dev *vdev)
{
int ret;
ret = vpu_clk_off(vdev);
if (ret)
return -1;
ret = vpu_power_off(vdev);
if (ret)
return -1;
return 0;
}
static int vpu_open(struct uio_info *info, struct inode *inode,
void *file_priv)
{
struct vpu_dev *vdev;
struct uio_listener *listener = file_priv;
struct vpu_instance *vi_arr;
int ret = 0, i, max_ins;
vdev = (struct vpu_dev *)info->priv;
vi_arr = vdev->fd_ins_arr;
mutex_lock(&vdev->mutex);
if (vdev->ins_cnt++ == 0)
vpu_turn_on(vdev);
max_ins = __max_ins_num(vdev);
for (i = 0; i < max_ins; i++) {
if (vi_arr[i].occupied == 0) {
listener->extend = &vi_arr[i];
vi_arr[i].occupied = 1;
vi_arr[i].got_sema = 0;
break;
}
}
if (i == max_ins) {
vdev->ins_cnt--;
pr_err("%s, vpu instance excess max instance num!!\n",
__func__);
ret = -EBUSY;
}
mutex_unlock(&vdev->mutex);
PDEBUG("dev opened by %d, ins_arr[%d]\n", vdev->codec_type, i);
return ret;
}
static int vpu_release(struct uio_info *info, struct inode *inode,
void *file_priv)
{
struct vpu_dev *vdev;
struct uio_listener *listener = file_priv;
struct vpu_instance *vi;
vdev = (struct vpu_dev *)info->priv;
vi = (struct vpu_instance *)listener->extend;
mutex_lock(&vdev->mutex);
if (vi->got_sema) {
pr_err("%s, instance still got semaphore!!!\n", __func__);
hantro_cleanup(vdev);
vpu_unlock(vdev);
vi->got_sema = 0;
}
vi->occupied = 0;
listener->extend = NULL;
if ((vdev->ins_cnt > 0) && (vdev->ins_cnt-- == 1))
vpu_turn_off(vdev);
mutex_unlock(&vdev->mutex);
PDEBUG("dev released by codec_type:%d\n", vdev->codec_type);
return 0;
}
static irqreturn_t vpu_func_irq_handler(int irq, struct uio_info *dev_info)
{
struct vpu_dev *vdev = dev_info->priv;
unsigned long flags;
u32 irq_status_dec, irq_status_pp, irq_status_enc;
spin_lock_irqsave(&vdev->lock, flags);
if (!atomic_read(&vdev->clk_on)) {
spin_unlock_irqrestore(&vdev->lock, flags);
return IRQ_NONE;
}
if (vdev->codec_type == HANTRO_DEC) {
irq_status_dec = readl(vdev->reg_base + INT_REG_DEC);
if (irq_status_dec & DEC_INT_BIT)
writel(irq_status_dec & (~DEC_INT_BIT),
vdev->reg_base + INT_REG_DEC);
else {
spin_unlock_irqrestore(&vdev->lock, flags);
return IRQ_NONE;
}
} else if (vdev->codec_type == HANTRO_PP) {
irq_status_pp = readl(vdev->reg_base + INT_REG_PP);
if (irq_status_pp & PP_INT_BIT)
writel(irq_status_pp & (~PP_INT_BIT),
vdev->reg_base + INT_REG_PP);
else {
spin_unlock_irqrestore(&vdev->lock, flags);
return IRQ_NONE;
}
} else if (vdev->codec_type == HANTRO_ENC) {
irq_status_enc = readl(vdev->reg_base + INT_REG_ENC);
if (irq_status_enc & 0x01)
/* clear enc IRQ and slice ready interrupt bit */
writel(irq_status_enc & (~0x101),
vdev->reg_base + INT_REG_ENC);
/* FIXME Does it need to handle frame ready here?? */
else {
spin_unlock_irqrestore(&vdev->lock, flags);
return IRQ_NONE;
}
}
spin_unlock_irqrestore(&vdev->lock, flags);
return IRQ_HANDLED;
}
static int vpu_ioctl(struct uio_info *info, unsigned int cmd,
unsigned long arg, void *file_priv)
{
int ret = 0;
int ins_id = -1;
void *argp = (void *)arg;
struct vpu_dev *vdev = info->priv;
struct uio_listener *listener = file_priv;
struct vpu_instance *vi;
vi = (struct vpu_instance *)listener->extend;
switch (cmd) {
case HANTRO_CMD_POWER_ON:
mutex_lock(&vdev->mutex);
ret = vpu_power_on(vdev);
mutex_unlock(&vdev->mutex);
break;
case HANTRO_CMD_POWER_OFF:
mutex_lock(&vdev->mutex);
ret = vpu_power_off(vdev);
mutex_unlock(&vdev->mutex);
break;
case HANTRO_CMD_CLK_ON:
mutex_lock(&vdev->mutex);
ret = vpu_clk_on(vdev);
mutex_unlock(&vdev->mutex);
break;
case HANTRO_CMD_CLK_OFF:
mutex_lock(&vdev->mutex);
ret = vpu_clk_off(vdev);
mutex_unlock(&vdev->mutex);
break;
case HANTRO_CMD_LOCK:
if (vi->got_sema == 0) {
ret = vpu_lock(arg, vdev);
uio_event_sync(listener);
if (ret == 0)
vi->got_sema = 1;
}
break;
case HANTRO_CMD_UNLOCK:
if (vi->got_sema) {
ret = vpu_unlock(vdev);
vi->got_sema = 0;
}
break;
case HANTRO_CMD_GET_INS_ID:
ins_id = vi->id;
if (copy_to_user(argp, &ins_id, sizeof(ins_id))) {
pr_err("%s, copy to user error!\n", __func__);
ret = -EFAULT;
}
break;
case HANTRO_CMD_QUERY_CAP:
if (copy_to_user(argp, &vdev->hw_cap, sizeof(vdev->hw_cap))) {
pr_err("%s, copy to user error!\n", __func__);
ret = -EFAULT;
}
break;
default:
ret = -EFAULT;
break;
}
return ret;
}
static int vpu_probe(struct platform_device *pdev)
{
struct resource *res;
struct vpu_dev *vdev;
int ret = 0, i, max_ins;
int irq_num;
int codec_type;
int capacity;
struct device_node *np = pdev->dev.of_node;
if (!np)
return -EINVAL;
if (of_property_read_u32(np, "marvell,codec-type", &codec_type))
return -EINVAL;
if (of_property_read_u32(np, "marvell,hw-capacity", &capacity))
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resources given!!!\n");
return -ENODEV;
}
irq_num = platform_get_irq(pdev, 0);
if (irq_num < 0) {
dev_err(&pdev->dev, "missing irq resource!!!\n");
return -ENODEV;
}
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
if (!vdev) {
dev_err(&pdev->dev, "vpu_dev: out of memory!!!\n");
return -ENOMEM;
}
vdev->fclk = devm_clk_get(&pdev->dev, "vpu-fclk");
if (IS_ERR(vdev->fclk)) {
dev_err(&pdev->dev, "Cannot get fclk ptr.\n");
ret = PTR_ERR(vdev->fclk);
goto err_clk2;
}
vdev->bclk = devm_clk_get(&pdev->dev, "vpu-bclk");
if (IS_ERR(vdev->bclk)) {
dev_err(&pdev->dev, "cannot get bclk ptr.\n");
ret = PTR_ERR(vdev->bclk);
goto err_clk1;
}
vdev->reg_base = (void *)ioremap_nocache(res->start,
resource_size(res));
if (!vdev->reg_base) {
dev_err(&pdev->dev, "can't remap VPU registers!!!\n");
ret = -ENOMEM;
goto err_reg_base;
}
platform_set_drvdata(pdev, vdev);
spin_lock_init(&vdev->lock);
/* DEC: 0, PP: 1, ENC: 2 */
vdev->codec_type = codec_type;
pr_info("%s, vdev->codec_type = %d\n", __func__, vdev->codec_type);
vdev->flags = 0;
atomic_set(&vdev->power_on, 0);
atomic_set(&vdev->clk_on, 0);
vdev->ins_cnt = 0;
vdev->hw_cap = capacity;
vdev->dev = &pdev->dev;
vdev->uio_info.name = UIO_HANTRO_NAME;
vdev->uio_info.version = UIO_HANTRO_VERSION;
vdev->uio_info.mem[0].internal_addr = (void __iomem *)vdev->reg_base;
vdev->uio_info.mem[0].addr = res->start;
vdev->uio_info.mem[0].memtype = UIO_MEM_PHYS;
vdev->uio_info.mem[0].size = resource_size(res);
vdev->uio_info.irq_flags = IRQF_SHARED;
vdev->uio_info.irq = irq_num;
vdev->uio_info.handler = vpu_func_irq_handler;
vdev->uio_info.priv = vdev;
vdev->uio_info.open = vpu_open;
vdev->uio_info.release = vpu_release;
vdev->uio_info.ioctl = vpu_ioctl;
vdev->uio_info.mmap = NULL;
pm_runtime_enable(vdev->dev);
mutex_init(&(vdev->mutex));
vpu_lock_init(vdev);
/* for multi-instance */
max_ins = __max_ins_num(vdev);
vdev->fd_ins_arr = __get_ins_arr(vdev);
for (i = 0; i < max_ins; i++) {
vdev->fd_ins_arr[i].id = i;
vdev->fd_ins_arr[i].occupied = 0;
vdev->fd_ins_arr[i].got_sema = 0;
}
ret = uio_register_device(&pdev->dev, &vdev->uio_info);
if (ret) {
dev_err(&pdev->dev, "failed to register uio device\n");
goto err_uio_register;
}
/* turn on power/clk before check HW */
vpu_turn_on(vdev);
if (hantro_check_hw(vdev)) {
ret = -EBUSY;
vpu_turn_off(vdev);
goto err_uio_register;
}
hantro_reset(vdev);
vpu_turn_off(vdev);
return 0;
err_uio_register:
iounmap(vdev->uio_info.mem[0].internal_addr);
err_reg_base:
devm_clk_put(&pdev->dev, vdev->bclk);
err_clk1:
devm_clk_put(&pdev->dev, vdev->fclk);
err_clk2:
kfree(vdev);
return ret;
}
static int vpu_remove(struct platform_device *pdev)
{
struct vpu_dev *vdev = platform_get_drvdata(pdev);
uio_unregister_device(&vdev->uio_info);
iounmap(vdev->uio_info.mem[0].internal_addr);
devm_clk_put(&pdev->dev, vdev->fclk);
devm_clk_put(&pdev->dev, vdev->bclk);
pm_runtime_disable(vdev->dev);
kfree(vdev);
return 0;
}
#ifdef CONFIG_PM
static int vpu_suspend(struct platform_device *dev, pm_message_t state)
{
struct vpu_dev *vdev = platform_get_drvdata(dev);
/* turn off power if vpu isn't running and power is on,
* otherwise, do nothing.
*/
if (!atomic_read(&vdev->clk_on) && atomic_read(&vdev->power_on)) {
vpu_power_off(vdev);
atomic_set(&vdev->suspend, 1);
}
return 0;
}
static int vpu_resume(struct platform_device *dev)
{
struct vpu_dev *vdev = platform_get_drvdata(dev);
/* Turn on the power if it was turned off in suspend. */
if (atomic_cmpxchg(&vdev->suspend, 1, 0))
vpu_power_on(vdev);
return 0;
}
#endif
static struct of_device_id hantro_dt_ids[] = {
{ .compatible = "mrvl,mmp-hantro",},
{}
};
MODULE_DEVICE_TABLE(of, hantro_dt_ids);
static struct platform_driver vpu_driver = {
.probe = vpu_probe,
.remove = vpu_remove,
#ifdef CONFIG_PM
.suspend = vpu_suspend,
.resume = vpu_resume,
#endif
.driver = {
.name = UIO_HANTRO_NAME,
.owner = THIS_MODULE,
.of_match_table = hantro_dt_ids,
},
};
static int __init uio_vpu_init(void)
{
return platform_driver_register(&vpu_driver);
}
static void __exit uio_vpu_exit(void)
{
platform_driver_unregister(&vpu_driver);
}
module_init(uio_vpu_init);
module_exit(uio_vpu_exit);
MODULE_AUTHOR("Baoyin Shan (byshan@marvell.com)");
MODULE_DESCRIPTION("UIO driver for Hantro codec");
MODULE_LICENSE("GPL v2");