diff --git a/drivers/video/rockchip/mpp/Kconfig b/drivers/video/rockchip/mpp/Kconfig new file mode 100644 index 000000000000..285d6af19626 --- /dev/null +++ b/drivers/video/rockchip/mpp/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +menuconfig ROCKCHIP_MPP_SERVICE + tristate "mpp service framework" + depends on ARCH_ROCKCHIP + default n + help + rockchip mpp service framework. + +config ROCKCHIP_MPP_RKVDEC + tristate "RKV decoder device driver" + depends on ROCKCHIP_MPP_SERVICE + default n + help + rockchip mpp rkv combo decoder and hevc decoder. + +config ROCKCHIP_MPP_VDPU1 + tristate "VPU decoder v1 device driver" + depends on ROCKCHIP_MPP_SERVICE + default n + help + rockchip mpp vpu decoder v1. + +config ROCKCHIP_MPP_VEPU1 + tristate "VPU encoder v1 device driver" + depends on ROCKCHIP_MPP_SERVICE + default n + help + rockchip mpp vpu encoder v1. + +config ROCKCHIP_MPP_VDPU2 + tristate "VPU decoder v2 device driver" + depends on ROCKCHIP_MPP_SERVICE + default n + +config ROCKCHIP_MPP_VEPU2 + tristate "VPU encoder v2 device driver" + depends on ROCKCHIP_MPP_SERVICE + default n + help + rockchip mpp vpu encoder v2 diff --git a/drivers/video/rockchip/mpp/Makefile b/drivers/video/rockchip/mpp/Makefile new file mode 100644 index 000000000000..b011caadac84 --- /dev/null +++ b/drivers/video/rockchip/mpp/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +rk_vcodec-objs := mpp_service.o mpp_common.o mpp_iommu.o + +rk_vcodec-$(CONFIG_ROCKCHIP_MPP_RKVDEC) += mpp_rkvdec.o +rk_vcodec-$(CONFIG_ROCKCHIP_MPP_VDPU1) += mpp_vdpu1.o +rk_vcodec-$(CONFIG_ROCKCHIP_MPP_VEPU1) += mpp_vepu1.o +rk_vcodec-$(CONFIG_ROCKCHIP_MPP_VDPU2) += mpp_vdpu2.o +rk_vcodec-$(CONFIG_ROCKCHIP_MPP_VEPU2) += mpp_vepu2.o + +obj-$(CONFIG_ROCKCHIP_MPP_SERVICE) += rk_vcodec.o diff --git a/drivers/video/rockchip/mpp/mpp_common.c b/drivers/video/rockchip/mpp/mpp_common.c new file mode 100644 index 000000000000..094d0b2ecb7c --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_common.c @@ -0,0 +1,1139 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define MPP_TIMEOUT_DELAY (2000) + +#define MPP_SESSION_MAX_DONE_TASK (20) + +#ifdef CONFIG_COMPAT +struct compat_mpp_request { + compat_uptr_t req; + u32 size; +}; +#endif + +/* task queue schedule */ +static int +mpp_taskqueue_push_pending(struct mpp_taskqueue *queue, + struct mpp_task *task) +{ + mutex_lock(&queue->lock); + list_add_tail(&task->service_link, &queue->pending); + mutex_unlock(&queue->lock); + + return 0; +} + +static struct mpp_task * +mpp_taskqueue_get_pending_task(struct mpp_taskqueue *queue) +{ + struct mpp_task *task = NULL; + + if (!list_empty(&queue->pending)) { + task = list_first_entry(&queue->pending, + struct mpp_task, + service_link); + list_del_init(&task->service_link); + } + + return task; +} + +static int +mpp_taskqueue_is_running(struct mpp_taskqueue *queue) +{ + return atomic_read(&queue->running); +} + +static int +mpp_taskqueue_wait_to_run(struct mpp_taskqueue *queue, + struct mpp_task *task) +{ + atomic_inc(&queue->running); + queue->cur_task = task; + + return 0; +} + +static struct mpp_task * +mpp_taskqueue_get_cur_task(struct mpp_taskqueue *queue) +{ + return queue->cur_task; +} + +static int +mpp_taskqueue_done(struct mpp_taskqueue *queue, + struct mpp_task *task) +{ + queue->cur_task = NULL; + atomic_set(&queue->running, 0); + + return 0; +} + +static int +mpp_taskqueue_abort(struct mpp_taskqueue *queue, + struct mpp_task *task) +{ + if (task) { + if (queue->cur_task == task) + queue->cur_task = NULL; + } + atomic_set(&queue->running, 0); + + return 0; +} + +static void * +mpp_taskqueue_attach(struct mpp_service *srv, u32 node) +{ + struct mpp_taskqueue *queue = NULL; + + mpp_debug_enter(); + + queue = srv->task_queues[node]; + queue->srv = srv; + + mpp_debug_leave(); + + return queue; +} + +static int mpp_power_on(struct mpp_dev *mpp) +{ + pm_runtime_get_sync(mpp->dev); + pm_stay_awake(mpp->dev); + + if (mpp->hw_ops->power_on) + mpp->hw_ops->power_on(mpp); + + return 0; +} + +static int mpp_power_off(struct mpp_dev *mpp) +{ + if (mpp->hw_ops->power_off) + mpp->hw_ops->power_off(mpp); + + pm_runtime_mark_last_busy(mpp->dev); + pm_runtime_put_autosuspend(mpp->dev); + pm_relax(mpp->dev); + + return 0; +} + +static void * +mpp_fd_to_mem_region(struct mpp_dev *mpp, + struct mpp_dma_session *dma, int fd) +{ + struct mpp_dma_buffer *buffer = NULL; + struct mpp_mem_region *mem_region = NULL; + + if (fd <= 0 || !dma || !mpp) + return ERR_PTR(-EINVAL); + + down_read(&mpp->rw_sem); + buffer = mpp_dma_import_fd(dma, fd); + up_read(&mpp->rw_sem); + if (IS_ERR_OR_NULL(buffer)) { + mpp_err("can't import dma-buf %d\n", fd); + return ERR_PTR(-EINVAL); + } + + mem_region = kzalloc(sizeof(*mem_region), GFP_KERNEL); + if (!mem_region) { + down_read(&mpp->rw_sem); + mpp_dma_release_fd(dma, fd); + up_read(&mpp->rw_sem); + return ERR_PTR(-ENOMEM); + } + + mem_region->hdl = (void *)(long)fd; + mem_region->iova = buffer->iova; + mem_region->len = buffer->size; + + return mem_region; +} + +static int +mpp_session_push_pending(struct mpp_session *session, + struct mpp_task *task) +{ + mutex_lock(&session->lock); + list_add_tail(&task->session_link, &session->pending); + mutex_unlock(&session->lock); + + return 0; +} + +static int mpp_session_push_done(struct mpp_task *task) +{ + struct mpp_session *session = NULL; + + session = task->session; + + mutex_lock(&session->lock); + list_del_init(&task->session_link); + mutex_unlock(&session->lock); + + kfifo_in(&session->done_fifo, &task, 1); + wake_up(&session->wait); + + return 0; +} + +static struct mpp_task * +mpp_session_pull_done(struct mpp_session *session) +{ + struct mpp_task *task = NULL; + + if (kfifo_out(&session->done_fifo, &task, 1)) + return task; + return NULL; +} + +static struct mpp_task * +mpp_alloc_task(struct mpp_dev *mpp, + struct mpp_session *session, + void __user *src, u32 size) +{ + struct mpp_task *task = NULL; + + if (mpp->dev_ops->alloc_task) + task = mpp->dev_ops->alloc_task(session, src, size); + + if (task && mpp->hw_ops->get_freq) + mpp->hw_ops->get_freq(mpp, task); + + return task; +} + +static int +mpp_free_task(struct mpp_session *session, + struct mpp_task *task) +{ + struct mpp_dev *mpp = session->mpp; + + if (mpp->dev_ops->free_task) + mpp->dev_ops->free_task(session, task); + return 0; +} + +static int mpp_flush_pm_runtime(struct device *dev) +{ + struct device_link *link; + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + pm_runtime_get_sync(link->supplier); + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + pm_runtime_put_sync(link->supplier); + + return 0; +} + +static int mpp_dev_reset(struct mpp_dev *mpp) +{ + dev_info(mpp->dev, "resetting...\n"); + + /* + * before running, we have to switch grf ctrl bit to ensure + * working in current hardware + */ + mpp_set_grf(mpp->grf_info); + + if (mpp->hw_ops->reduce_freq) + mpp->hw_ops->reduce_freq(mpp); + /* FIXME lock resource lock of the other devices in combo */ + down_write(&mpp->rw_sem); + atomic_set(&mpp->reset_request, 0); + mpp_iommu_detach(mpp->iommu_info); + rockchip_save_qos(mpp->dev); + if (mpp->hw_ops->reset) + mpp->hw_ops->reset(mpp); + rockchip_restore_qos(mpp->dev); + /* flush power domain runtime */ + mpp_flush_pm_runtime(mpp->dev); + mpp_iommu_attach(mpp->iommu_info); + up_write(&mpp->rw_sem); + + dev_info(mpp->dev, "reset done\n"); + + return 0; +} + +static int mpp_dev_abort(struct mpp_dev *mpp) +{ + int ret; + + mpp_debug_enter(); + + mpp_dev_reset(mpp); + /* destroy the current task after hardware reset */ + ret = mpp_taskqueue_is_running(mpp->queue); + if (ret) { + struct mpp_task *task = NULL; + + task = mpp_taskqueue_get_cur_task(mpp->queue); + list_del_init(&task->session_link); + mpp_taskqueue_abort(mpp->queue, task); + atomic_dec(&task->session->task_running); + mpp_free_task(task->session, task); + } else { + mpp_taskqueue_abort(mpp->queue, NULL); + } + + mpp_debug_leave(); + + return ret; +} + +static int mpp_task_run(struct mpp_dev *mpp, + struct mpp_task *task) +{ + mpp_debug_enter(); + + /* + * before running, we have to switch grf ctrl bit to ensure + * working in current hardware + */ + mpp_set_grf(mpp->grf_info); + mpp_power_on(mpp); + mpp_time_record(task); + mpp_debug(DEBUG_TASK_INFO, "pid %d, start hw %s\n", + task->session->pid, dev_name(mpp->dev)); + + if (mpp->hw_ops->set_freq) + mpp->hw_ops->set_freq(mpp, task); + /* + * TODO: Lock the reader locker of the device resource lock here, + * release at the finish operation + */ + if (mpp->dev_ops->run) + mpp->dev_ops->run(mpp, task); + + mpp_debug_leave(); + + return 0; +} + +static void mpp_task_try_run(struct work_struct *work_s) +{ + int ret; + struct mpp_task *task; + struct mpp_dev *mpp = container_of(work_s, struct mpp_dev, work); + + mpp_debug_enter(); + + ret = mpp_taskqueue_is_running(mpp->queue); + if (ret) + goto done; + + if (atomic_read(&mpp->reset_request) > 0) + goto done; + + task = mpp_taskqueue_get_pending_task(mpp->queue); + if (!task) + goto done; + /* + * In the link table mode, the prepare function of the device + * will check whether I can insert a new task into device. + * If the device supports the task status query(like the HEVC + * encoder), it can report whether the device is busy. + * If the device does not support multiple task or task status + * query, leave this job to mpp service. + */ + if (mpp->dev_ops->prepare) + mpp->dev_ops->prepare(mpp, task); + /* + * FIXME if the hardware supports task query, but we still need to lock + * the running list and lock the mpp service in the current state. + */ + mpp_taskqueue_wait_to_run(mpp->queue, task); + /* Push a pending task to running queue */ + mpp_task_run(mpp, task); +done: + mpp_debug_leave(); +} + +static int mpp_session_clear(struct mpp_dev *mpp, + struct mpp_session *session) +{ + struct mpp_task *task, *n, *task_done; + + list_for_each_entry_safe(task, n, + &session->pending, + session_link) { + list_del_init(&task->session_link); + mpp_free_task(session, task); + } + + while (kfifo_out(&session->done_fifo, &task_done, 1)) + mpp_free_task(session, task_done); + + return 0; +} + +static int mpp_task_result(struct mpp_dev *mpp, + struct mpp_task *task, + u32 __user *dst, u32 size) +{ + mpp_debug_enter(); + + if (!mpp || !task) + return -EINVAL; + + if (mpp->dev_ops->result) + mpp->dev_ops->result(mpp, task, dst, size); + + mpp_free_task(task->session, task); + + mpp_debug_leave(); + + return 0; +} + +static int mpp_wait_result(struct mpp_session *session, + struct mpp_dev *mpp, + struct mpp_request req) +{ + int ret; + struct mpp_task *task; + + ret = wait_event_timeout(session->wait, + !kfifo_is_empty(&session->done_fifo), + msecs_to_jiffies(MPP_TIMEOUT_DELAY)); + if (ret > 0) { + ret = 0; + task = mpp_session_pull_done(session); + mpp_task_result(mpp, task, req.req, req.size); + } else { + mpp_err("error: pid %d wait %d task done timeout\n", + session->pid, atomic_read(&session->task_running)); + ret = -ETIMEDOUT; + mutex_lock(&mpp->reset_lock); + mpp_dev_abort(mpp); + mutex_unlock(&mpp->reset_lock); + } + atomic_dec(&mpp->total_running); + mpp_power_off(mpp); + + return ret; +} + +static int mpp_attach_service(struct mpp_dev *mpp, struct device *dev) +{ + u32 node = 0; + struct device_node *np = NULL; + struct platform_device *pdev = NULL; + + of_property_read_u32(dev->of_node, + "rockchip,taskqueue-node", &node); + if (node >= MPP_DEVICE_BUTT) { + dev_err(dev, "rockchip,taskqueue-node %d must less than %d\n", + node, MPP_DEVICE_BUTT); + return -ENODEV; + } + + np = of_parse_phandle(dev->of_node, "rockchip,srv", 0); + if (!np || !of_device_is_available(np)) { + dev_err(dev, "failed to get the mpp service node\n"); + return -ENODEV; + } + + pdev = of_find_device_by_node(np); + if (!pdev) { + of_node_put(np); + dev_err(dev, "failed to get mpp service from node\n"); + return -ENODEV; + } + + device_lock(&pdev->dev); + mpp->srv = platform_get_drvdata(pdev); + if (mpp->srv) { + /* register current device to mpp service */ + mpp->srv->sub_devices[mpp->var->device_type] = mpp; + /* set taskqueue which set in dtsi */ + mpp->queue = mpp_taskqueue_attach(mpp->srv, node); + } else { + dev_err(&pdev->dev, "failed attach service\n"); + return -EINVAL; + } + device_unlock(&pdev->dev); + + put_device(&pdev->dev); + of_node_put(np); + + return 0; +} + +long mpp_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct mpp_dev *mpp; + struct mpp_service *srv; + struct mpp_session *session = + (struct mpp_session *)filp->private_data; + + mpp_debug_enter(); + if (!session) + return -EINVAL; + + srv = session->srv; + if (!srv) + return -EINVAL; + + if (atomic_read(&srv->shutdown_request) > 0) + return -EBUSY; + + mpp_debug(DEBUG_IOCTL, "cmd=%x\n", cmd); + switch (cmd) { + case MPP_IOC_SET_CLIENT_TYPE: + session->device_type = (enum MPP_DEVICE_TYPE)(arg); + mpp_debug(DEBUG_IOCTL, "pid %d set client type %d\n", + session->pid, session->device_type); + + mpp = srv->sub_devices[session->device_type]; + if (IS_ERR_OR_NULL(mpp)) { + mpp_err("pid %d set client type %d failed\n", + session->pid, session->device_type); + return -EINVAL; + } + session->dma = mpp_dma_session_create(mpp->dev); + session->dma->max_buffers = mpp->session_max_buffers; + if (mpp->dev_ops->init_session) + mpp->dev_ops->init_session(mpp); + session->mpp = mpp; + break; + case MPP_IOC_SET_REG: { + struct mpp_request req; + struct mpp_task *task; + + mpp = session->mpp; + if (IS_ERR_OR_NULL(mpp)) { + mpp_err("pid %d not find clinet %d\n", + session->pid, session->device_type); + return -EINVAL; + } + mpp_debug(DEBUG_IOCTL, "pid %d set reg, client type %d\n", + session->pid, session->device_type); + if (copy_from_user(&req, (void __user *)arg, + sizeof(req))) { + mpp_err("error: set reg copy_from_user failed\n"); + return -EFAULT; + } + task = mpp_alloc_task(mpp, session, + (void __user *)req.req, + req.size); + if (IS_ERR_OR_NULL(task)) + return -EFAULT; + mpp_taskqueue_push_pending(mpp->queue, task); + mpp_session_push_pending(session, task); + atomic_inc(&session->task_running); + atomic_inc(&mpp->total_running); + /* TODO: processing the current task */ + mutex_lock(&mpp->queue->lock); + queue_work(mpp->workq, &mpp->work); + mutex_unlock(&mpp->queue->lock); + } break; + case MPP_IOC_GET_REG: { + struct mpp_request req; + + mpp = session->mpp; + if (IS_ERR_OR_NULL(mpp)) { + mpp_err("pid %d not find clinet %d\n", + session->pid, session->device_type); + return -EINVAL; + } + mpp_debug(DEBUG_IOCTL, "pid %d get reg, client type %d\n", + session->pid, session->device_type); + if (copy_from_user(&req, (void __user *)arg, + sizeof(req))) { + mpp_err("get reg copy_from_user failed\n"); + return -EFAULT; + } + + return mpp_wait_result(session, mpp, req); + } break; + case MPP_IOC_PROBE_IOMMU_STATUS: { + int iommu_enable = 1; + + mpp_debug(DEBUG_IOCTL, "MPP_IOC_PROBE_IOMMU_STATUS\n"); + + if (put_user(iommu_enable, ((u32 __user *)arg))) { + mpp_err("iommu status copy_to_user fail\n"); + return -EFAULT; + } + break; + } + case MPP_IOC_SET_DRIVER_DATA: { + u32 val; + + mpp = session->mpp; + if (IS_ERR_OR_NULL(mpp)) { + mpp_err("pid %d not find clinet %d\n", + session->pid, session->device_type); + return -EINVAL; + } + if (copy_from_user(&val, (void __user *)arg, + sizeof(val))) { + mpp_err("MPP_IOC_SET_DRIVER_DATA copy_from_user fail\n"); + return -EFAULT; + } + + if (mpp->grf_info->grf) + regmap_write(mpp->grf_info->grf, 0x5d8, val); + } break; + default: { + mpp = session->mpp; + if (IS_ERR_OR_NULL(mpp)) { + mpp_err("pid %d not find clinet %d\n", + session->pid, session->device_type); + return -EINVAL; + } + if (mpp->dev_ops->ioctl) + return mpp->dev_ops->ioctl(session, cmd, arg); + + mpp_err("unknown mpp ioctl cmd %x\n", cmd); + return -ENOIOCTLCMD; + } break; + } + + mpp_debug_leave(); + + return 0; +} + +#ifdef CONFIG_COMPAT +#define MPP_IOC_SET_CLIENT_TYPE32 _IOW(MPP_IOC_MAGIC, 1, u32) +#define MPP_IOC_GET_HW_FUSE_STATUS32 _IOW(MPP_IOC_MAGIC, 2, \ + compat_ulong_t) +#define MPP_IOC_SET_REG32 _IOW(MPP_IOC_MAGIC, 3, \ + compat_ulong_t) +#define MPP_IOC_GET_REG32 _IOW(MPP_IOC_MAGIC, 4, \ + compat_ulong_t) +#define MPP_IOC_PROBE_IOMMU_STATUS32 _IOR(MPP_IOC_MAGIC, 5, u32) +#define MPP_IOC_SET_DRIVER_DATA32 _IOW(MPP_IOC_MAGIC, 64, u32) + +static long native_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + long ret = -ENOIOCTLCMD; + + if (file->f_op->unlocked_ioctl) + ret = file->f_op->unlocked_ioctl(file, cmd, arg); + + return ret; +} + +long mpp_dev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mpp_request req; + void __user *up = compat_ptr(arg); + int compatible_arg = 1; + long err = 0; + + mpp_debug_enter(); + mpp_debug(DEBUG_IOCTL, "cmd %x, MPP_IOC_SET_CLIENT_TYPE32 %x\n", + cmd, (u32)MPP_IOC_SET_CLIENT_TYPE32); + /* First, convert the command. */ + switch (cmd) { + case MPP_IOC_SET_CLIENT_TYPE32: + cmd = MPP_IOC_SET_CLIENT_TYPE; + break; + case MPP_IOC_GET_HW_FUSE_STATUS32: + cmd = MPP_IOC_GET_HW_FUSE_STATUS; + break; + case MPP_IOC_SET_REG32: + cmd = MPP_IOC_SET_REG; + break; + case MPP_IOC_GET_REG32: + cmd = MPP_IOC_GET_REG; + break; + case MPP_IOC_PROBE_IOMMU_STATUS32: + cmd = MPP_IOC_PROBE_IOMMU_STATUS; + break; + case MPP_IOC_SET_DRIVER_DATA32: + cmd = MPP_IOC_SET_DRIVER_DATA; + break; + default: + break; + } + switch (cmd) { + case MPP_IOC_SET_REG: + case MPP_IOC_GET_REG: + case MPP_IOC_GET_HW_FUSE_STATUS: { + compat_uptr_t req_ptr; + struct compat_mpp_request __user *req32 = NULL; + + req32 = (struct compat_mpp_request __user *)up; + memset(&req, 0, sizeof(req)); + + if (get_user(req_ptr, &req32->req) || + get_user(req.size, &req32->size)) { + mpp_err("compat get hw status copy_from_user failed\n"); + return -EFAULT; + } + req.req = compat_ptr(req_ptr); + compatible_arg = 0; + } break; + default: + break; + } + + if (compatible_arg) { + err = native_ioctl(file, cmd, (unsigned long)up); + } else { + mm_segment_t old_fs = get_fs(); + + set_fs(KERNEL_DS); + err = native_ioctl(file, cmd, (unsigned long)&req); + set_fs(old_fs); + } + + mpp_debug_leave(); + return err; +} +#endif + +int mpp_dev_open(struct inode *inode, struct file *filp) +{ + int ret; + struct mpp_session *session = NULL; + struct mpp_service *srv = container_of(inode->i_cdev, + struct mpp_service, + mpp_cdev); + mpp_debug_enter(); + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return -ENOMEM; + + session->srv = srv; + session->pid = current->pid; + + mutex_init(&session->lock); + INIT_LIST_HEAD(&session->pending); + init_waitqueue_head(&session->wait); + ret = kfifo_alloc(&session->done_fifo, + MPP_SESSION_MAX_DONE_TASK, + GFP_KERNEL); + if (ret < 0) { + ret = -ENOMEM; + goto failed_kfifo; + } + + atomic_set(&session->task_running, 0); + filp->private_data = (void *)session; + + mpp_debug_leave(); + + return nonseekable_open(inode, filp); + +failed_kfifo: + kfree(session); + return ret; +} + +int mpp_dev_release(struct inode *inode, struct file *filp) +{ + int task_running; + struct mpp_dev *mpp; + struct mpp_session *session = filp->private_data; + + mpp_debug_enter(); + + if (!session) { + mpp_err("session is null\n"); + return -EINVAL; + } + + task_running = atomic_read(&session->task_running); + if (task_running) { + mpp_err("session %d still has %d task running when closing\n", + session->pid, task_running); + msleep(50); + } + wake_up(&session->wait); + + /* release device resource */ + mpp = session->mpp; + if (mpp) { + if (mpp->dev_ops->release_session) + mpp->dev_ops->release_session(session); + + /* remove this filp from the asynchronusly notified filp's */ + mpp_session_clear(mpp, session); + + down_read(&mpp->rw_sem); + mpp_dma_session_destroy(session->dma); + up_read(&mpp->rw_sem); + } + + kfifo_free(&session->done_fifo); + kfree(session); + filp->private_data = NULL; + + mpp_debug_leave(); + return 0; +} + +unsigned int mpp_dev_poll(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + struct mpp_session *session = + (struct mpp_session *)filp->private_data; + + poll_wait(filp, &session->wait, wait); + if (kfifo_len(&session->done_fifo)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +struct mpp_mem_region * +mpp_task_attach_fd(struct mpp_task *task, int fd) +{ + struct mpp_mem_region *mem_region = NULL; + + mem_region = mpp_fd_to_mem_region(task->session->mpp, + task->session->dma, fd); + if (IS_ERR(mem_region)) + return mem_region; + + INIT_LIST_HEAD(&mem_region->reg_lnk); + list_add_tail(&mem_region->reg_lnk, &task->mem_region_list); + + return mem_region; +} + +int mpp_translate_reg_address(struct mpp_dev *mpp, + struct mpp_task *task, + int fmt, u32 *reg) +{ + struct mpp_trans_info *trans_info = mpp->var->trans_info; + const u8 *tbl = trans_info[fmt].table; + int size = trans_info[fmt].count; + int i; + + mpp_debug_enter(); + for (i = 0; i < size; i++) { + struct mpp_mem_region *mem_region = NULL; + int usr_fd = reg[tbl[i]] & 0x3FF; + int offset = reg[tbl[i]] >> 10; + + if (usr_fd == 0) + continue; + + mem_region = mpp_task_attach_fd(task, usr_fd); + if (IS_ERR(mem_region)) { + mpp_debug(DEBUG_IOMMU, "reg[%3d]: %08x failed\n", + tbl[i], reg[tbl[i]]); + return PTR_ERR(mem_region); + } + + mem_region->reg_idx = tbl[i]; + mpp_debug(DEBUG_IOMMU, "reg[%3d]: %3d => %pad + offset %10d\n", + tbl[i], usr_fd, &mem_region->iova, offset); + reg[tbl[i]] = mem_region->iova + offset; + } + + mpp_debug_leave(); + + return 0; +} + +int mpp_translate_extra_info(struct mpp_task *task, + struct extra_info_for_iommu *ext_inf, + u32 *reg) +{ + mpp_debug_enter(); + if (ext_inf) { + int i; + + if (ext_inf->magic != EXTRA_INFO_MAGIC) + return -EINVAL; + + for (i = 0; i < ext_inf->cnt; i++) { + mpp_debug(DEBUG_IOMMU, "reg[%d] + offset %d\n", + ext_inf->elem[i].index, + ext_inf->elem[i].offset); + reg[ext_inf->elem[i].index] += ext_inf->elem[i].offset; + } + } + mpp_debug_leave(); + + return 0; +} + +int mpp_task_init(struct mpp_session *session, + struct mpp_task *task) +{ + INIT_LIST_HEAD(&task->session_link); + INIT_LIST_HEAD(&task->service_link); + INIT_LIST_HEAD(&task->mem_region_list); + + task->session = session; + + return 0; +} + +int mpp_task_finish(struct mpp_session *session, + struct mpp_task *task) +{ + struct mpp_dev *mpp = session->mpp; + + mutex_lock(&mpp->queue->lock); + + if (mpp->dev_ops->finish) + mpp->dev_ops->finish(mpp, task); + atomic_dec(&task->session->task_running); + + /* FIXME lock resource lock of combo device */ + mutex_lock(&mpp->reset_lock); + if (atomic_read(&mpp->reset_request) > 0) + mpp_dev_reset(mpp); + mutex_unlock(&mpp->reset_lock); + + mpp_taskqueue_done(mpp->queue, task); + /* Wake up the GET thread */ + mpp_session_push_done(task); + /* processing the next task */ + queue_work(mpp->workq, &mpp->work); + + mutex_unlock(&mpp->queue->lock); + + return 0; +} + +int mpp_task_finalize(struct mpp_session *session, + struct mpp_task *task) +{ + struct mpp_dev *mpp = NULL; + struct mpp_mem_region *mem_region = NULL, *n; + + mpp = session->mpp; + /* release memory region attach to this registers table. */ + list_for_each_entry_safe(mem_region, n, + &task->mem_region_list, + reg_lnk) { + down_read(&mpp->rw_sem); + mpp_dma_release_fd(session->dma, (long)mem_region->hdl); + up_read(&mpp->rw_sem); + list_del_init(&mem_region->reg_lnk); + kfree(mem_region); + } + + return 0; +} + +/* The device will do more probing work after this */ +int mpp_dev_probe(struct mpp_dev *mpp, + struct platform_device *pdev) +{ + int ret; + struct resource *res = NULL; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + /* Get and attach to service */ + ret = mpp_attach_service(mpp, dev); + if (ret) { + dev_err(dev, "failed to attach service\n"); + return -ENODEV; + } + + mpp->workq = create_singlethread_workqueue(np->name); + if (!mpp->workq) { + dev_err(dev, "failed to create workqueue\n"); + return -ENOMEM; + } + + mpp->dev = dev; + mpp->hw_ops = mpp->var->hw_ops; + mpp->dev_ops = mpp->var->dev_ops; + + init_rwsem(&mpp->rw_sem); + mutex_init(&mpp->reset_lock); + atomic_set(&mpp->reset_request, 0); + atomic_set(&mpp->total_running, 0); + INIT_WORK(&mpp->work, mpp_task_try_run); + + device_init_wakeup(dev, true); + /* power domain autosuspend delay 2s */ + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + mpp->irq = platform_get_irq(pdev, 0); + if (mpp->irq < 0) { + dev_err(dev, "No interrupt resource found\n"); + ret = -ENODEV; + goto failed; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto failed; + } + mpp->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(mpp->reg_base)) { + ret = PTR_ERR(mpp->reg_base); + goto failed; + } + + pm_runtime_get_sync(dev); + /* + * TODO: here or at the device itself, some device does not + * have the iommu, maybe in the device is better. + */ + mpp->iommu_info = mpp_iommu_probe(dev); + if (IS_ERR(mpp->iommu_info)) { + dev_err(dev, "failed to attach iommu: %ld\n", + PTR_ERR(mpp->iommu_info)); + } + if (mpp->hw_ops->init) + mpp->hw_ops->init(mpp); + pm_runtime_put_sync(dev); + + return 0; + +failed: + destroy_workqueue(mpp->workq); + device_init_wakeup(dev, false); + pm_runtime_disable(dev); + + return ret; +} + +int mpp_dev_remove(struct mpp_dev *mpp) +{ + if (mpp->hw_ops->exit) + mpp->hw_ops->exit(mpp); + + if (mpp->workq) { + destroy_workqueue(mpp->workq); + mpp->workq = NULL; + } + + device_init_wakeup(mpp->dev, false); + pm_runtime_disable(mpp->dev); + + return 0; +} + +irqreturn_t mpp_dev_irq(int irq, void *param) +{ + irqreturn_t ret = IRQ_NONE; + struct mpp_dev *mpp = param; + + if (mpp->dev_ops->irq) + ret = mpp->dev_ops->irq(mpp); + + return ret; +} + +irqreturn_t mpp_dev_isr_sched(int irq, void *param) +{ + irqreturn_t ret = IRQ_NONE; + struct mpp_dev *mpp = param; + + if (mpp->hw_ops->reduce_freq && + list_empty(&mpp->queue->pending)) + mpp->hw_ops->reduce_freq(mpp); + + if (mpp->dev_ops->isr) + ret = mpp->dev_ops->isr(mpp); + + return ret; +} + +int mpp_safe_reset(struct reset_control *rst) +{ + if (rst) + reset_control_assert(rst); + return 0; +} + +int mpp_safe_unreset(struct reset_control *rst) +{ + if (rst) + reset_control_deassert(rst); + return 0; +} + +int mpp_set_grf(struct mpp_grf_info *grf_info) +{ + if (grf_info->grf && grf_info->mode_val) + regmap_write(grf_info->grf, + grf_info->mode_ctrl, + grf_info->mode_val); + + return 0; +} + +int mpp_time_record(struct mpp_task *task) +{ + if (mpp_debug_unlikely(DEBUG_TIMING) && task) + do_gettimeofday(&task->start); + + return 0; +} + +int mpp_time_diff(struct mpp_task *task) +{ + struct timeval end; + struct mpp_dev *mpp = task->session->mpp; + + do_gettimeofday(&end); + mpp_debug(DEBUG_TIMING, "%s: pid:%d time: %ld ms\n", + dev_name(mpp->dev), task->session->pid, + (end.tv_sec - task->start.tv_sec) * 1000 + + (end.tv_usec - task->start.tv_usec) / 1000); + + return 0; +} + +int mpp_dump_reg(u32 *regs, u32 start_idx, u32 end_idx) +{ + u32 i; + + if (mpp_debug_unlikely(DEBUG_DUMP_ERR_REG)) { + pr_info("Dumping registers: %p\n", regs); + + for (i = start_idx; i < end_idx; i++) + pr_info("reg[%03d]: %08x\n", i, regs[i]); + } + + return 0; +} diff --git a/drivers/video/rockchip/mpp/mpp_common.h b/drivers/video/rockchip/mpp/mpp_common.h new file mode 100644 index 000000000000..813e4e4b30af --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_common.h @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#ifndef __ROCKCHIP_MPP_COMMON_H__ +#define __ROCKCHIP_MPP_COMMON_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MHZ (1000 * 1000) + +#define EXTRA_INFO_MAGIC (0x4C4A46) +#define JPEG_IOC_EXTRA_SIZE (48) + +/* Use 'l' as magic number */ +#define MPP_IOC_MAGIC 'l' + +#define MPP_IOC_SET_CLIENT_TYPE _IOW(MPP_IOC_MAGIC, 1, __u32) +#define MPP_IOC_GET_HW_FUSE_STATUS _IOW(MPP_IOC_MAGIC, 2, unsigned long) + +#define MPP_IOC_SET_REG _IOW(MPP_IOC_MAGIC, 3, unsigned long) +#define MPP_IOC_GET_REG _IOW(MPP_IOC_MAGIC, 4, unsigned long) + +#define MPP_IOC_PROBE_IOMMU_STATUS _IOR(MPP_IOC_MAGIC, 5, __u32) +#define MPP_IOC_SET_DRIVER_DATA _IOW(MPP_IOC_MAGIC, 64, u32) + +#define MPP_IOC_CUSTOM_BASE (0x1000) + +enum MPP_DEVICE_TYPE { + MPP_DEVICE_ENC = 0x0, + MPP_DEVICE_DEC = 0x1, + MPP_DEVICE_PP = 0x2, + MPP_DEVICE_DEC_PP = 0x3, + MPP_DEVICE_DEC_HEVC = 0x4, + MPP_DEVICE_DEC_RKV = 0x5, + MPP_DEVICE_ENC_RKV = 0x6, + MPP_DEVICE_DEC_AVSPLUS = 0x7, + MPP_DEVICE_ENC_VEPU22 = 0x8, + MPP_DEVICE_BUTT, +}; + +enum MPP_DRIVER_TYPE { + MPP_DRIVER_NULL = 0, + MPP_DRIVER_VDPU1, + MPP_DRIVER_VEPU1, + MPP_DRIVER_VDPU2, + MPP_DRIVER_VEPU2, + MPP_DRIVER_VEPU22, + MPP_DRIVER_RKVDEC, + MPP_DRIVER_RKVENC, + MPP_DRIVER_BUTT, +}; + +struct mpp_request { + __u32 *req; + __u32 size; +}; + +struct mpp_grf_info { + u32 mode_ctrl; + u32 mode_val; + struct regmap *grf; +}; + +/** + * struct for hardware info + */ +struct mpp_hw_info { + /* register number */ + u32 reg_num; + /* start index of register */ + u32 regidx_start; + /* end index of register */ + u32 regidx_end; + /* register of enable hardware */ + int regidx_en; +}; + +struct mpp_trans_info { + const int count; + const char * const table; +}; + +struct extra_info_elem { + u32 index; + u32 offset; +}; + +struct extra_info_for_iommu { + u32 magic; + u32 cnt; + struct extra_info_elem elem[20]; +}; + +struct mpp_dev_var { + enum MPP_DEVICE_TYPE device_type; + + /* info for each hardware */ + struct mpp_hw_info *hw_info; + struct mpp_trans_info *trans_info; + struct mpp_hw_ops *hw_ops; + struct mpp_dev_ops *dev_ops; +}; + +struct mpp_mem_region { + struct list_head srv_lnk; + struct list_head reg_lnk; + struct list_head session_lnk; + /* address for iommu */ + dma_addr_t iova; + unsigned long len; + u32 reg_idx; + void *hdl; +}; + +struct mpp_dma_session; + +struct mpp_taskqueue; + +struct mpp_dev { + struct device *dev; + const struct mpp_dev_var *var; + struct mpp_hw_ops *hw_ops; + struct mpp_dev_ops *dev_ops; + + int irq; + u32 irq_status; + + void __iomem *reg_base; + struct mpp_grf_info *grf_info; + struct mpp_iommu_info *iommu_info; + + struct rw_semaphore rw_sem; + /* lock for reset */ + struct mutex reset_lock; + atomic_t reset_request; + atomic_t total_running; + /* task for work queue */ + struct workqueue_struct *workq; + struct work_struct work; + /* set session max buffers */ + u32 session_max_buffers; + /* point to MPP Service */ + struct mpp_taskqueue *queue; + struct mpp_service *srv; +}; + +struct mpp_task; + +struct mpp_session { + enum MPP_DEVICE_TYPE device_type; + /* the session related device private data */ + struct mpp_service *srv; + struct mpp_dev *mpp; + struct mpp_dma_session *dma; + + /* session tasks list lock */ + struct mutex lock; + struct list_head pending; + + DECLARE_KFIFO_PTR(done_fifo, struct mpp_task *); + + wait_queue_head_t wait; + pid_t pid; + atomic_t task_running; +}; + +/* The context for the a task */ +struct mpp_task { + /* context belong to */ + struct mpp_session *session; + + /* link to session pending */ + struct list_head session_link; + /* link to service node pending */ + struct list_head service_link; + /* The DMA buffer used in this task */ + struct list_head mem_region_list; + + /* record context running start time */ + struct timeval start; +}; + +struct mpp_taskqueue { + /* taskqueue structure global lock */ + struct mutex lock; + + struct list_head pending; + atomic_t running; + struct mpp_task *cur_task; + struct mpp_service *srv; +}; + +struct mpp_service { + struct class *cls; + struct device *dev; + dev_t dev_id; + struct cdev mpp_cdev; + struct device *child_dev; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + atomic_t shutdown_request; + /* follows for device probe */ + struct mpp_grf_info grf_infos[MPP_DRIVER_BUTT]; + struct platform_driver *sub_drivers[MPP_DRIVER_BUTT]; + /* follows for attach service */ + struct mpp_dev *sub_devices[MPP_DEVICE_BUTT]; + struct mpp_taskqueue *task_queues[MPP_DEVICE_BUTT]; +}; + +/* + * struct mpp_hw_ops - context specific operations for device + * @init Do something when hardware probe. + * @exit Do something when hardware remove. + * @power_on Get pm and enable clks. + * @power_off Put pm and disable clks. + * @get_freq Get special freq for setting. + * @set_freq Set freq to hardware. + * @reduce_freq Reduce freq when hardware is not running. + * @reset When error, reset hardware. + */ +struct mpp_hw_ops { + int (*init)(struct mpp_dev *mpp); + int (*exit)(struct mpp_dev *mpp); + int (*power_on)(struct mpp_dev *mpp); + int (*power_off)(struct mpp_dev *mpp); + int (*get_freq)(struct mpp_dev *mpp, + struct mpp_task *mpp_task); + int (*set_freq)(struct mpp_dev *mpp, + struct mpp_task *mpp_task); + int (*reduce_freq)(struct mpp_dev *mpp); + int (*reset)(struct mpp_dev *mpp); +}; + +/* + * struct mpp_dev_ops - context specific operations for task + * @alloc_task Alloc and set task. + * @prepare Check HW status for determining run next task or not. + * @run Start a single {en,de}coding run. Set registers to hardware. + * @irq Deal with hardware interrupt top-half. + * @isr Deal with hardware interrupt bottom-half. + * @finish Read back processing results and additional data from hardware. + * @result Read status to userspace. + * @free_task Release the resource allocate which alloc. + * @ioctl Special cammand from userspace. + * @open Open a instance for hardware when set client. + * @release Specific instance release operation for hardware. + * @free Specific instance free operation for hardware. + */ +struct mpp_dev_ops { + void *(*alloc_task)(struct mpp_session *session, + void __user *src, u32 size); + int (*prepare)(struct mpp_dev *mpp, struct mpp_task *task); + int (*run)(struct mpp_dev *mpp, struct mpp_task *task); + int (*irq)(struct mpp_dev *mpp); + int (*isr)(struct mpp_dev *mpp); + int (*finish)(struct mpp_dev *mpp, struct mpp_task *task); + int (*result)(struct mpp_dev *mpp, struct mpp_task *task, + u32 __user *dst, u32 size); + int (*free_task)(struct mpp_session *session, + struct mpp_task *task); + long (*ioctl)(struct mpp_session *isession, + unsigned int cmd, unsigned long arg); + struct mpp_session *(*init_session)(struct mpp_dev *mpp); + int (*release_session)(struct mpp_session *session); +}; + +/* It can handle the default ioctl */ +long mpp_dev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); +#ifdef CONFIG_COMPAT +long mpp_dev_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); +#endif +int mpp_dev_open(struct inode *inode, struct file *filp); +int mpp_dev_release(struct inode *inode, struct file *filp); +unsigned int mpp_dev_poll(struct file *filp, poll_table *wait); + +struct mpp_mem_region * +mpp_task_attach_fd(struct mpp_task *task, int fd); +int mpp_translate_reg_address(struct mpp_dev *data, + struct mpp_task *task, + int fmt, u32 *reg); +int mpp_translate_extra_info(struct mpp_task *task, + struct extra_info_for_iommu *ext_inf, + u32 *reg); + +int mpp_task_init(struct mpp_session *session, + struct mpp_task *task); +int mpp_task_finish(struct mpp_session *session, + struct mpp_task *task); +int mpp_task_finalize(struct mpp_session *session, + struct mpp_task *task); + +int mpp_dev_probe(struct mpp_dev *mpp, + struct platform_device *pdev); +int mpp_dev_remove(struct mpp_dev *mpp); + +irqreturn_t mpp_dev_irq(int irq, void *param); +irqreturn_t mpp_dev_isr_sched(int irq, void *param); + +int mpp_safe_reset(struct reset_control *rst); +int mpp_safe_unreset(struct reset_control *rst); + +int mpp_set_grf(struct mpp_grf_info *grf_info); + +int mpp_time_record(struct mpp_task *task); +int mpp_time_diff(struct mpp_task *task); + +int mpp_dump_reg(u32 *regs, u32 start_idx, u32 end_idx); + +static inline int mpp_write(struct mpp_dev *mpp, u32 reg, u32 val) +{ + int idx = reg / sizeof(u32); + + mpp_debug(DEBUG_SET_REG, "write reg[%d]: %08x\n", idx, val); + writel(val, mpp->reg_base + reg); + + return 0; +} + +static inline int mpp_write_relaxed(struct mpp_dev *mpp, u32 reg, u32 val) +{ + int idx = reg / sizeof(u32); + + mpp_debug(DEBUG_SET_REG, "write reg[%d]: %08x\n", idx, val); + writel_relaxed(val, mpp->reg_base + reg); + + return 0; +} + +static inline u32 mpp_read(struct mpp_dev *mpp, u32 reg) +{ + int idx = reg / sizeof(u32); + u32 val = readl(mpp->reg_base + reg); + + mpp_debug(DEBUG_GET_REG, "read reg[%d] 0x%x: %08x\n", idx, reg, val); + + return val; +} + +static inline u32 mpp_read_relaxed(struct mpp_dev *mpp, u32 reg) +{ + int idx = reg / sizeof(u32); + u32 val = readl_relaxed(mpp->reg_base + reg); + + mpp_debug(DEBUG_GET_REG, "read reg[%d] 0x%x: %08x\n", idx, reg, val); + + return val; +} + +extern struct platform_driver rockchip_rkvdec_driver; +extern struct platform_driver rockchip_rkvenc_driver; +extern struct platform_driver rockchip_vdpu1_driver; +extern struct platform_driver rockchip_vepu1_driver; +extern struct platform_driver rockchip_vdpu2_driver; +extern struct platform_driver rockchip_vepu2_driver; +extern struct platform_driver rockchip_vepu22_driver; + +#endif diff --git a/drivers/video/rockchip/mpp/mpp_debug.h b/drivers/video/rockchip/mpp/mpp_debug.h new file mode 100644 index 000000000000..b7cce94c848d --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_debug.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#ifndef __ROCKCHIP_MPP_DEBUG_H__ +#define __ROCKCHIP_MPP_DEBUG_H__ + +#include + +/* + * debug flag usage: + * +------+-------------------+ + * | 8bit | 24bit | + * +------+-------------------+ + * 0~23 bit is for different information type + * 24~31 bit is for information print format + */ + +#define DEBUG_POWER 0x00000001 +#define DEBUG_CLOCK 0x00000002 +#define DEBUG_IRQ_STATUS 0x00000004 +#define DEBUG_IOMMU 0x00000008 +#define DEBUG_IOCTL 0x00000010 +#define DEBUG_FUNCTION 0x00000020 +#define DEBUG_REGISTER 0x00000040 +#define DEBUG_EXTRA_INFO 0x00000080 +#define DEBUG_TIMING 0x00000100 +#define DEBUG_TASK_INFO 0x00000200 +#define DEBUG_DUMP_ERR_REG 0x00000400 +#define DEBUG_LINK_TABLE 0x00000800 + +#define DEBUG_SET_REG 0x00001000 +#define DEBUG_GET_REG 0x00002000 +#define DEBUG_PPS_FILL 0x00004000 +#define DEBUG_IRQ_CHECK 0x00008000 +#define DEBUG_CACHE_32B 0x00010000 + +#define DEBUG_RESET 0x00020000 + +#define PRINT_FUNCTION 0x80000000 +#define PRINT_LINE 0x40000000 + +extern unsigned int mpp_dev_debug; + +#define mpp_debug_unlikely(type) \ + (unlikely(mpp_dev_debug & type)) + +#define mpp_debug_func(type, fmt, args...) \ + do { \ + if (unlikely(mpp_dev_debug & type)) { \ + pr_info("%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } \ + } while (0) +#define mpp_debug(type, fmt, args...) \ + do { \ + if (unlikely(mpp_dev_debug & type)) { \ + pr_info(fmt, ##args); \ + } \ + } while (0) + +#define mpp_debug_enter() \ + do { \ + if (unlikely(mpp_dev_debug & DEBUG_FUNCTION)) { \ + pr_info("%s:%d: enter\n", \ + __func__, __LINE__); \ + } \ + } while (0) + +#define mpp_debug_leave() \ + do { \ + if (unlikely(mpp_dev_debug & DEBUG_FUNCTION)) { \ + pr_info("%s:%d: leave\n", \ + __func__, __LINE__); \ + } \ + } while (0) + +#define mpp_err(fmt, args...) \ + pr_err("%s:%d: " fmt, __func__, __LINE__, ##args) + +#endif diff --git a/drivers/video/rockchip/mpp/mpp_iommu.c b/drivers/video/rockchip/mpp/mpp_iommu.c new file mode 100644 index 000000000000..031f9b8b8c16 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_iommu.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include + +#include "mpp_iommu.h" + +static struct mpp_dma_buffer * +mpp_dma_find_buffer_fd(struct mpp_dma_session *session, int fd) +{ + struct dma_buf *dmabuf; + struct mpp_dma_buffer *out = NULL; + struct mpp_dma_buffer *buffer = NULL, *n; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return NULL; + + list_for_each_entry_safe(buffer, n, + &session->buffer_list, list) { + /* + * As long as the last reference is hold by the buffer pool, + * the same fd won't be assigned to the other application. + */ + if (buffer->fd == fd && + buffer->dmabuf == dmabuf) { + out = buffer; + break; + } + } + dma_buf_put(dmabuf); + + return out; +} + +/* Release the buffer from the current list */ +static void mpp_dma_release_buffer(struct kref *ref) +{ + struct mpp_dma_buffer *buffer = + container_of(ref, struct mpp_dma_buffer, ref); + + mutex_lock(&buffer->session->list_mutex); + buffer->session->buffer_count--; + list_del_init(&buffer->list); + mutex_unlock(&buffer->session->list_mutex); + + dma_buf_unmap_attachment(buffer->attach, buffer->sgt, buffer->dir); + dma_buf_detach(buffer->dmabuf, buffer->attach); + dma_buf_put(buffer->dmabuf); + kfree(buffer); +} + +/* Remove the oldest buffer when count more than the setting */ +static int +mpp_dma_remove_extra_buffer(struct mpp_dma_session *session) +{ + struct mpp_dma_buffer *n; + struct mpp_dma_buffer *oldest = NULL, *buffer = NULL; + ktime_t oldest_time = ktime_set(0, 0); + + if (session->buffer_count > session->max_buffers) { + list_for_each_entry_safe(buffer, n, + &session->buffer_list, + list) { + if (ktime_to_ns(oldest_time) == 0 || + ktime_after(oldest_time, buffer->last_used)) { + oldest_time = buffer->last_used; + oldest = buffer; + } + } + kref_put(&oldest->ref, mpp_dma_release_buffer); + } + + return 0; +} + +int mpp_dma_release(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer) +{ + if (IS_ERR_OR_NULL(buffer)) + return -EINVAL; + + kref_put(&buffer->ref, mpp_dma_release_buffer); + + return 0; +} + +int mpp_dma_release_fd(struct mpp_dma_session *session, int fd) +{ + struct device *dev = session->dev; + struct mpp_dma_buffer *buffer = NULL; + + buffer = mpp_dma_find_buffer_fd(session, fd); + if (IS_ERR_OR_NULL(buffer)) { + dev_err(dev, "can not find %d buffer in list\n", fd); + + return -EINVAL; + } + + kref_put(&buffer->ref, mpp_dma_release_buffer); + + return 0; +} + +struct mpp_dma_buffer * +mpp_dma_alloc(struct mpp_dma_session *session, size_t size) +{ + size_t align_size; + dma_addr_t iova; + struct mpp_dma_buffer *buffer; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return NULL; + + align_size = PAGE_ALIGN(size); + buffer->vaddr = dma_alloc_coherent(session->dev, + align_size, + &iova, + GFP_KERNEL); + if (!buffer->vaddr) + goto fail_dma_alloc; + + buffer->size = PAGE_ALIGN(size); + buffer->iova = iova; + + return buffer; +fail_dma_alloc: + kfree(buffer); + return NULL; +} + +int mpp_dma_free(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer) +{ + dma_free_coherent(session->dev, buffer->size, + buffer->vaddr, buffer->iova); + buffer->vaddr = NULL; + buffer->iova = 0; + buffer->size = 0; + + return 0; +} + +struct mpp_dma_buffer * +mpp_dma_import_fd(struct mpp_dma_session *session, int fd) +{ + int ret = 0; + struct sg_table *sgt; + struct dma_buf *dmabuf; + struct mpp_dma_buffer *buffer; + struct dma_buf_attachment *attach; + + if (!session) + return ERR_PTR(-EINVAL); + + /* remove the oldest before add buffer */ + mpp_dma_remove_extra_buffer(session); + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return NULL; + + /* Check whether in session */ + buffer = mpp_dma_find_buffer_fd(session, fd); + if (!IS_ERR_OR_NULL(buffer)) { + if (buffer->dmabuf == dmabuf) { + if (kref_get_unless_zero(&buffer->ref)) { + buffer->last_used = ktime_get(); + dma_buf_put(dmabuf); + return buffer; + } + } + dev_dbg(session->dev, "missing the fd %d\n", fd); + kref_put(&buffer->ref, mpp_dma_release_buffer); + } + + /* A new DMA buffer */ + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto fail; + } + + buffer->dmabuf = dmabuf; + buffer->fd = fd; + buffer->dir = DMA_BIDIRECTIONAL; + buffer->last_used = ktime_get(); + + attach = dma_buf_attach(buffer->dmabuf, session->dev); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto fail_attach; + } + + sgt = dma_buf_map_attachment(attach, buffer->dir); + if (IS_ERR(sgt)) { + ret = PTR_ERR(sgt); + goto fail_map; + } + + buffer->iova = sg_dma_address(sgt->sgl); + buffer->size = sg_dma_len(sgt->sgl); + + buffer->attach = attach; + buffer->sgt = sgt; + buffer->session = session; + + kref_init(&buffer->ref); + /* Increase the reference for used outside the buffer pool */ + kref_get(&buffer->ref); + INIT_LIST_HEAD(&buffer->list); + + mutex_lock(&session->list_mutex); + session->buffer_count++; + list_add_tail(&buffer->list, &session->buffer_list); + mutex_unlock(&session->list_mutex); + + return buffer; + +fail_map: + dma_buf_detach(buffer->dmabuf, attach); +fail_attach: + kfree(buffer); +fail: + dma_buf_put(dmabuf); + return ERR_PTR(ret); +} + +int mpp_dma_unmap_kernel(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer) +{ + void *vaddr = buffer->vaddr; + struct dma_buf *dmabuf = buffer->dmabuf; + + if (IS_ERR_OR_NULL(vaddr) || + IS_ERR_OR_NULL(dmabuf)) + return -EINVAL; + + dma_buf_vunmap(dmabuf, vaddr); + buffer->vaddr = NULL; + + dma_buf_end_cpu_access(dmabuf, DMA_FROM_DEVICE); + + return 0; +} + +int mpp_dma_map_kernel(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer) +{ + int ret; + void *vaddr; + struct dma_buf *dmabuf = buffer->dmabuf; + + if (IS_ERR_OR_NULL(dmabuf)) + return -EINVAL; + + ret = dma_buf_begin_cpu_access(dmabuf, DMA_FROM_DEVICE); + if (ret) { + dev_dbg(session->dev, "can't access the dma buffer\n"); + goto failed_access; + } + + vaddr = dma_buf_vmap(dmabuf); + if (!vaddr) { + dev_dbg(session->dev, "can't vmap the dma buffer\n"); + ret = -EIO; + goto failed_vmap; + } + + buffer->vaddr = vaddr; + + return 0; + +failed_vmap: + dma_buf_end_cpu_access(dmabuf, DMA_FROM_DEVICE); +failed_access: + + return ret; +} + +int mpp_dma_session_destroy(struct mpp_dma_session *session) +{ + struct mpp_dma_buffer *n, *buffer = NULL; + + if (!session) + return -EINVAL; + + list_for_each_entry_safe(buffer, n, + &session->buffer_list, + list) { + kref_put(&buffer->ref, mpp_dma_release_buffer); + } + + kfree(session); + + return 0; +} + +struct mpp_dma_session * +mpp_dma_session_create(struct device *dev) +{ + struct mpp_dma_session *session = NULL; + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return session; + + INIT_LIST_HEAD(&session->buffer_list); + mutex_init(&session->list_mutex); + + session->dev = dev; + + return session; +} + +int mpp_iommu_detach(struct mpp_iommu_info *info) +{ + struct iommu_domain *domain = info->domain; + struct iommu_group *group = info->group; + + iommu_detach_group(domain, group); + + return 0; +} + +int mpp_iommu_attach(struct mpp_iommu_info *info) +{ + struct iommu_domain *domain = info->domain; + struct iommu_group *group = info->group; + int ret; + + ret = iommu_attach_group(domain, group); + if (ret) + return ret; + + return 0; +} + +struct mpp_iommu_info * +mpp_iommu_probe(struct device *dev) +{ + struct mpp_iommu_info *info = NULL; + int ret = 0; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto err; + } + + info->group = iommu_group_get(dev); + if (!info->group) { + ret = -EINVAL; + goto err_free_info; + } + + info->domain = iommu_get_domain_for_dev(dev); + if (!info->domain) { + ret = -EINVAL; + goto err_put_group; + } + + return info; + +err_put_group: + iommu_group_put(info->group); +err_free_info: + kfree(info); +err: + return ERR_PTR(ret); +} + +int mpp_iommu_remove(struct mpp_iommu_info *info) +{ + iommu_group_put(info->group); + kfree(info); + + return 0; +} diff --git a/drivers/video/rockchip/mpp/mpp_iommu.h b/drivers/video/rockchip/mpp/mpp_iommu.h new file mode 100644 index 000000000000..065b6d75a617 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_iommu.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#ifndef __ROCKCHIP_MPP_IOMMU_H__ +#define __ROCKCHIP_MPP_IOMMU_H__ + +#include +#include + +struct mpp_dma_buffer { + struct list_head list; + struct mpp_dma_session *session; + + /* DMABUF information */ + struct dma_buf *dmabuf; + struct dma_buf_attachment *attach; + struct sg_table *sgt; + struct sg_table *copy_sgt; + enum dma_data_direction dir; + + int fd; + dma_addr_t iova; + unsigned long size; + void *vaddr; + + struct kref ref; + ktime_t last_used; +}; + +struct mpp_dma_session { + struct list_head buffer_list; + /* the mutex for the above buffer list */ + struct mutex list_mutex; + /* the max buffer num for the buffer list */ + u32 max_buffers; + /* the count for the buffer list */ + int buffer_count; + + struct device *dev; +}; + +struct mpp_iommu_info { + struct iommu_domain *domain; + struct iommu_group *group; +}; + +struct mpp_dma_session * +mpp_dma_session_create(struct device *dev); +int mpp_dma_session_destroy(struct mpp_dma_session *session); + +struct mpp_dma_buffer * +mpp_dma_alloc(struct mpp_dma_session *session, size_t size); +int mpp_dma_free(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer); + +struct mpp_dma_buffer * +mpp_dma_import_fd(struct mpp_dma_session *session, int fd); +int mpp_dma_release(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer); +int mpp_dma_release_fd(struct mpp_dma_session *session, int fd); + +int mpp_dma_unmap_kernel(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer); +int mpp_dma_map_kernel(struct mpp_dma_session *session, + struct mpp_dma_buffer *buffer); + +struct mpp_iommu_info * +mpp_iommu_probe(struct device *dev); +int mpp_iommu_remove(struct mpp_iommu_info *info); + +int mpp_iommu_attach(struct mpp_iommu_info *info); +int mpp_iommu_detach(struct mpp_iommu_info *info); + +#endif diff --git a/drivers/video/rockchip/mpp/mpp_rkvdec.c b/drivers/video/rockchip/mpp/mpp_rkvdec.c new file mode 100644 index 000000000000..93989b574840 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_rkvdec.c @@ -0,0 +1,1600 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define RKVDEC_DRIVER_NAME "mpp_rkvdec" + +#define IOMMU_GET_BUS_ID(x) (((x) >> 6) & 0x1f) +#define IOMMU_PAGE_SIZE SZ_4K + +#define RKVDEC_SESSION_MAX_BUFFERS 40 +/* The maximum registers number of all the version */ +#define HEVC_DEC_REG_NUM 68 +#define HEVC_DEC_REG_START_INDEX 0 +#define HEVC_DEC_REG_END_INDEX 67 + +#define RKVDEC_V1_REG_NUM 78 +#define RKVDEC_V1_REG_START_INDEX 0 +#define RKVDEC_V1_REG_END_INDEX 77 + +#define RKVDEC_V2_REG_NUM 109 +#define RKVDEC_V2_REG_START_INDEX 0 +#define RKVDEC_V2_REG_END_INDEX 108 + +#define RKVDEC_REG_INT_EN 0x004 +#define RKVDEC_REG_INT_EN_INDEX (1) +#define RKVDEC_WR_DDR_ALIGN_EN BIT(23) +#define RKVDEC_FORCE_SOFT_RESET_VALID BIT(21) +#define RKVDEC_SOFTWARE_RESET_EN BIT(20) +#define RKVDEC_INT_COLMV_REF_ERROR BIT(17) +#define RKVDEC_INT_BUF_EMPTY BIT(16) +#define RKVDEC_INT_TIMEOUT BIT(15) +#define RKVDEC_INT_STRM_ERROR BIT(14) +#define RKVDEC_INT_BUS_ERROR BIT(13) +#define RKVDEC_DEC_INT_RAW BIT(9) +#define RKVDEC_DEC_INT BIT(8) +#define RKVDEC_DEC_TIMEOUT_EN BIT(5) +#define RKVDEC_DEC_IRQ_DIS BIT(4) +#define RKVDEC_CLOCK_GATE_EN BIT(1) +#define RKVDEC_DEC_START BIT(0) + +#define RKVDEC_REG_SYS_CTRL 0x008 +#define RKVDEC_REG_SYS_CTRL_INDEX (2) +#define RKVDEC_GET_FORMAT(x) (((x) >> 20) & 0x3) +#define RKVDEC_FMT_H265D (0) +#define RKVDEC_FMT_H264D (1) +#define RKVDEC_FMT_VP9D (2) + +#define RKVDEC_REG_RLC_BASE 0x010 +#define RKVDEC_REG_RLC_BASE_INDEX (4) + +#define RKVDEC_REG_PPS_BASE 0x0a0 +#define RKVDEC_REG_PPS_BASE_INDEX (42) + +#define RKVDEC_REG_VP9_REFCOLMV_BASE 0x0d0 +#define RKVDEC_REG_VP9_REFCOLMV_BASE_INDEX (52) + +#define RKVDEC_REG_CACHE0_SIZE_BASE 0x41c +#define RKVDEC_REG_CACHE1_SIZE_BASE 0x45c +#define RKVDEC_REG_CLR_CACHE0_BASE 0x410 +#define RKVDEC_REG_CLR_CACHE1_BASE 0x450 + +#define RKVDEC_CACHE_PERMIT_CACHEABLE_ACCESS BIT(0) +#define RKVDEC_CACHE_PERMIT_READ_ALLOCATE BIT(1) +#define RKVDEC_CACHE_LINE_SIZE_64_BYTES BIT(4) + +#define RKVDEC_POWER_CTL_INDEX (99) +#define RKVDEC_POWER_CTL_BASE 0x018c + +#define FALLBACK_STATIC_TEMPERATURE 55000 + +#define to_rkvdec_task(task) \ + container_of(task, struct rkvdec_task, mpp_task) +#define to_rkvdec_dev(dev) \ + container_of(dev, struct rkvdec_dev, mpp) + +enum RKVDEC_STATE { + RKVDEC_STATE_NORMAL, + RKVDEC_STATE_LT_START, + RKVDEC_STATE_LT_RUN, +}; + +enum SET_CLK_EVENT { + EVENT_POWER_ON = 0, + EVENT_POWER_OFF, + EVENT_ADJUST, + EVENT_THERMAL, + EVENT_BUTT, +}; + +struct rkvdec_task { + struct mpp_task mpp_task; + struct mpp_hw_info *hw_info; + + unsigned long aclk_freq; + unsigned long clk_core_freq; + unsigned long clk_cabac_freq; + + u32 reg[RKVDEC_V2_REG_NUM]; + u32 idx; + + u32 strm_addr; + u32 irq_status; +}; + +struct rkvdec_dev { + struct mpp_dev mpp; + /* sip smc reset lock */ + struct mutex sip_reset_lock; + + struct clk *aclk; + struct clk *hclk; + struct clk *clk_core; + struct clk *clk_cabac; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + u32 aclk_debug; + u32 clk_core_debug; + u32 clk_cabac_debug; + u32 session_max_buffers_debug; + + struct reset_control *rst_a; + struct reset_control *rst_h; + struct reset_control *rst_niu_a; + struct reset_control *rst_niu_h; + struct reset_control *rst_core; + struct reset_control *rst_cabac; + + enum RKVDEC_STATE state; + unsigned long aux_iova; + struct page *aux_page; + struct rkvdec_task *current_task; + /* regulator and devfreq */ + struct regulator *vdd; + struct devfreq *devfreq; + struct devfreq *parent_devfreq; + struct notifier_block devfreq_nb; + struct thermal_cooling_device *devfreq_cooling; + struct thermal_zone_device *thermal_zone; + u32 static_power_coeff; + s32 ts[4]; + struct mutex set_clk_lock; /* set clk lock */ + unsigned int thermal_div; + unsigned long volt; + unsigned long aclk_devf; + unsigned long clk_core_devf; + unsigned long clk_cabac_devf; +}; + +/* + * hardware information + */ +static struct mpp_hw_info rk_hevcdec_hw_info = { + .reg_num = HEVC_DEC_REG_NUM, + .regidx_start = HEVC_DEC_REG_START_INDEX, + .regidx_end = HEVC_DEC_REG_END_INDEX, + .regidx_en = RKVDEC_REG_INT_EN_INDEX, +}; + +static struct mpp_hw_info rkvdec_v1_hw_info = { + .reg_num = RKVDEC_V1_REG_NUM, + .regidx_start = RKVDEC_V1_REG_START_INDEX, + .regidx_end = RKVDEC_V1_REG_END_INDEX, + .regidx_en = RKVDEC_REG_INT_EN_INDEX, +}; + +/* + * file handle translate information + */ +static const char trans_tbl_h264d[] = { + 4, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 41, 42, 43, 48, 75 +}; + +static const char trans_tbl_h265d[] = { + 4, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 42, 43 +}; + +static const char trans_tbl_vp9d[] = { + 4, 6, 7, 11, 12, 13, 14, 15, 16 +}; + +static struct mpp_trans_info rk_hevcdec_trans[] = { + [RKVDEC_FMT_H265D] = { + .count = sizeof(trans_tbl_h265d), + .table = trans_tbl_h265d, + }, +}; + +static struct mpp_trans_info rkvdec_v1_trans[] = { + [RKVDEC_FMT_H265D] = { + .count = sizeof(trans_tbl_h265d), + .table = trans_tbl_h265d, + }, + [RKVDEC_FMT_H264D] = { + .count = sizeof(trans_tbl_h264d), + .table = trans_tbl_h264d, + }, + [RKVDEC_FMT_VP9D] = { + .count = sizeof(trans_tbl_vp9d), + .table = trans_tbl_vp9d, + }, +}; + +static int rkvdec_set_clk(struct rkvdec_dev *dec, + unsigned long aclk_freq, + unsigned long core_freq, + unsigned long cabac_freq, + unsigned int event) +{ + mutex_lock(&dec->set_clk_lock); + + switch (event) { + case EVENT_POWER_ON: + clk_set_rate(dec->aclk, dec->aclk_devf); + clk_set_rate(dec->clk_core, dec->clk_core_devf); + clk_set_rate(dec->clk_cabac, dec->clk_cabac_devf); + dec->thermal_div = 0; + break; + case EVENT_POWER_OFF: + clk_set_rate(dec->aclk, aclk_freq); + clk_set_rate(dec->clk_core, core_freq); + clk_set_rate(dec->clk_cabac, cabac_freq); + dec->thermal_div = 0; + break; + case EVENT_ADJUST: + if (!dec->thermal_div) { + clk_set_rate(dec->aclk, aclk_freq); + clk_set_rate(dec->clk_core, core_freq); + clk_set_rate(dec->clk_cabac, cabac_freq); + } else { + clk_set_rate(dec->aclk, + aclk_freq / dec->thermal_div); + clk_set_rate(dec->clk_core, + core_freq / dec->thermal_div); + clk_set_rate(dec->clk_cabac, + cabac_freq / dec->thermal_div); + } + dec->aclk_devf = aclk_freq; + dec->clk_core_devf = core_freq; + dec->clk_cabac_devf = cabac_freq; + break; + case EVENT_THERMAL: + dec->thermal_div = dec->aclk_devf / aclk_freq; + if (dec->thermal_div > 4) + dec->thermal_div = 4; + if (dec->thermal_div) { + clk_set_rate(dec->aclk, + dec->aclk_devf / dec->thermal_div); + clk_set_rate(dec->clk_core, + dec->clk_core_devf / dec->thermal_div); + clk_set_rate(dec->clk_cabac, + dec->clk_cabac_devf / dec->thermal_div); + } + break; + } + + mutex_unlock(&dec->set_clk_lock); + + return 0; +} + +static int devfreq_target(struct device *dev, + unsigned long *freq, u32 flags) +{ + int ret = 0; + unsigned int clk_event; + struct dev_pm_opp *opp; + unsigned long target_volt, target_freq; + unsigned long aclk_freq, core_freq, cabac_freq; + + struct rkvdec_dev *dec = dev_get_drvdata(dev); + struct devfreq *devfreq = dec->devfreq; + struct devfreq_dev_status *stat = &devfreq->last_status; + unsigned long old_clk_rate = stat->current_frequency; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) { + dev_err(dev, "Failed to find opp for %lu Hz\n", *freq); + return PTR_ERR(opp); + } + target_freq = dev_pm_opp_get_freq(opp); + target_volt = dev_pm_opp_get_voltage(opp); + dev_pm_opp_put(opp); + + if (target_freq < *freq) { + clk_event = EVENT_THERMAL; + aclk_freq = target_freq; + core_freq = target_freq; + cabac_freq = target_freq; + } else { + clk_event = stat->busy_time ? EVENT_POWER_ON : EVENT_POWER_OFF; + aclk_freq = dec->aclk_devf; + core_freq = dec->clk_core_devf; + cabac_freq = dec->clk_cabac_devf; + } + + if (old_clk_rate == target_freq) { + if (dec->volt == target_volt) + return ret; + ret = regulator_set_voltage(dec->vdd, target_volt, INT_MAX); + if (ret) { + dev_err(dev, "Cannot set voltage %lu uV\n", + target_volt); + return ret; + } + dec->volt = target_volt; + return 0; + } + + if (old_clk_rate < target_freq) { + ret = regulator_set_voltage(dec->vdd, target_volt, INT_MAX); + if (ret) { + dev_err(dev, "set voltage %lu uV\n", target_volt); + return ret; + } + } + + dev_dbg(dev, "%lu-->%lu\n", old_clk_rate, target_freq); + rkvdec_set_clk(dec, aclk_freq, core_freq, cabac_freq, clk_event); + stat->current_frequency = target_freq; + + if (old_clk_rate > target_freq) { + ret = regulator_set_voltage(dec->vdd, target_volt, INT_MAX); + if (ret) { + dev_err(dev, "set vol %lu uV\n", target_volt); + return ret; + } + } + dec->volt = target_volt; + + return ret; +} + +static int devfreq_get_cur_freq(struct device *dev, + unsigned long *freq) +{ + struct rkvdec_dev *dec = dev_get_drvdata(dev); + + *freq = clk_get_rate(dec->aclk); + + return 0; +} + +static int devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + struct rkvdec_dev *dec = dev_get_drvdata(dev); + struct devfreq *devfreq = dec->devfreq; + + memcpy(stat, &devfreq->last_status, sizeof(*stat)); + + return 0; +} + +static struct devfreq_dev_profile devfreq_profile = { + .target = devfreq_target, + .get_cur_freq = devfreq_get_cur_freq, + .get_dev_status = devfreq_get_dev_status, +}; + +static unsigned long +model_static_power(struct devfreq *devfreq, + unsigned long voltage) +{ + struct device *dev = devfreq->dev.parent; + struct rkvdec_dev *dec = dev_get_drvdata(dev); + struct thermal_zone_device *tz = dec->thermal_zone; + + int temperature; + unsigned long temp; + unsigned long temp_squared, temp_cubed, temp_scaling_factor; + const unsigned long voltage_cubed = (voltage * voltage * voltage) >> 10; + + if (!IS_ERR_OR_NULL(tz) && tz->ops->get_temp) { + int ret; + + ret = tz->ops->get_temp(tz, &temperature); + if (ret) { + dev_warn_ratelimited(dev, "ddr thermal zone failed\n"); + temperature = FALLBACK_STATIC_TEMPERATURE; + } + } else { + temperature = FALLBACK_STATIC_TEMPERATURE; + } + + /* + * Calculate the temperature scaling factor. To be applied to the + * voltage scaled power. + */ + temp = temperature / 1000; + temp_squared = temp * temp; + temp_cubed = temp_squared * temp; + temp_scaling_factor = (dec->ts[3] * temp_cubed) + + (dec->ts[2] * temp_squared) + (dec->ts[1] * temp) + dec->ts[0]; + + return (((dec->static_power_coeff * voltage_cubed) >> 20) + * temp_scaling_factor) / 1000000; +} + +static struct devfreq_cooling_power cooling_power_data = { + .get_static_power = model_static_power, + .dyn_power_coeff = 120, +}; + +static int power_model_simple_init(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + u32 temp; + const char *tz_name; + struct device_node *power_model_node; + + power_model_node = of_get_child_by_name(np, "vcodec_power_model"); + if (!power_model_node) { + dev_err(mpp->dev, "could not find power_model node\n"); + return -ENODEV; + } + + if (of_property_read_string(power_model_node, + "thermal-zone", + &tz_name)) { + dev_err(mpp->dev, "ts in power_model not available\n"); + return -EINVAL; + } + + dec->thermal_zone = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(dec->thermal_zone)) { + pr_warn("Error getting ddr thermal zone, not yet ready?\n"); + dec->thermal_zone = NULL; + return -EPROBE_DEFER; + } + + if (of_property_read_u32(power_model_node, + "static-power-coefficient", + &dec->static_power_coeff)) { + dev_err(mpp->dev, "static-power-coefficient not available\n"); + return -EINVAL; + } + if (of_property_read_u32(power_model_node, + "dynamic-power-coefficient", + &temp)) { + dev_err(mpp->dev, "dynamic-power-coefficient not available\n"); + return -EINVAL; + } + cooling_power_data.dyn_power_coeff = (unsigned long)temp; + + if (of_property_read_u32_array(power_model_node, + "ts", + (u32 *)dec->ts, + 4)) { + dev_err(mpp->dev, "ts in power_model not available\n"); + return -EINVAL; + } + + return 0; +} + +static int devfreq_notifier_call(struct notifier_block *nb, + unsigned long event, + void *data) +{ + struct rkvdec_dev *dec = container_of(nb, + struct rkvdec_dev, + devfreq_nb); + + if (!dec) + return NOTIFY_OK; + + if (event == DEVFREQ_PRECHANGE) + mutex_lock(&dec->sip_reset_lock); + else if (event == DEVFREQ_POSTCHANGE) + mutex_unlock(&dec->sip_reset_lock); + + return NOTIFY_OK; +} + +/* + * NOTE: rkvdec/rkhevc put scaling list address in pps buffer hardware will read + * it by pps id in video stream data. + * + * So we need to translate the address in iommu case. The address data is also + * 10bit fd + 22bit offset mode. + * Because userspace decoder do not give the pps id in the register file sets + * kernel driver need to translate each scaling list address in pps buffer which + * means 256 pps for H.264, 64 pps for H.265. + * + * In order to optimize the performance kernel driver ask userspace decoder to + * set all scaling list address in pps buffer to the same one which will be used + * on current decoding task. Then kernel driver can only translate the first + * address then copy it all pps buffer. + */ +static int fill_scaling_list_pps(struct rkvdec_task *task, + int fd, int offset, int count, + int pps_info_size, int sub_addr_offset) +{ + struct dma_buf *dmabuf = NULL; + void *vaddr = NULL; + u8 *pps = NULL; + u32 scaling_fd = 0; + u32 scaling_offset; + int ret = 0; + u32 base = sub_addr_offset; + + dmabuf = dma_buf_get(fd); + if (IS_ERR_OR_NULL(dmabuf)) { + mpp_err("invliad pps buffer\n"); + return -ENOENT; + } + + ret = dma_buf_begin_cpu_access(dmabuf, DMA_FROM_DEVICE); + if (ret) { + mpp_err("can't access the pps buffer\n"); + goto done; + } + + vaddr = dma_buf_vmap(dmabuf); + if (!vaddr) { + mpp_err("can't access the pps buffer\n"); + ret = -EIO; + goto done; + } + pps = vaddr + offset; + + memcpy(&scaling_offset, pps + base, sizeof(scaling_offset)); + scaling_offset = le32_to_cpu(scaling_offset); + + scaling_fd = scaling_offset & 0x3ff; + scaling_offset = scaling_offset >> 10; + + if (scaling_fd > 0) { + struct mpp_mem_region *mem_region = NULL; + u32 tmp = 0; + int i = 0; + + mem_region = mpp_task_attach_fd(&task->mpp_task, + scaling_fd); + if (IS_ERR(mem_region)) { + ret = PTR_ERR(mem_region); + goto done; + } + + tmp = mem_region->iova & 0xffffffff; + tmp += scaling_offset; + tmp = cpu_to_le32(tmp); + mpp_debug(DEBUG_PPS_FILL, + "pps at %p, scaling fd: %3d => %pad + offset %10d\n", + pps, scaling_fd, &mem_region->iova, offset); + + /* Fill the scaling list address in each pps entries */ + for (i = 0; i < count; i++, base += pps_info_size) + memcpy(pps + base, &tmp, sizeof(tmp)); + } + +done: + dma_buf_vunmap(dmabuf, vaddr); + dma_buf_end_cpu_access(dmabuf, DMA_FROM_DEVICE); + dma_buf_put(dmabuf); + + return ret; +} + +static void *rkvdec_alloc_task(struct mpp_session *session, + void __user *src, u32 size) +{ + u32 reg_len; + u32 fmt = 0; + int pps_fd; + u32 pps_offset; + int ret = -EFAULT; + struct rkvdec_task *task = NULL; + u32 dwsize = size / sizeof(u32); + struct mpp_dev *mpp = session->mpp; + + mpp_debug_enter(); + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (!task) + return NULL; + + mpp_task_init(session, &task->mpp_task); + + task->hw_info = mpp->var->hw_info; + reg_len = min(task->hw_info->reg_num, dwsize); + if (copy_from_user(task->reg, src, reg_len * 4)) { + mpp_err("error: copy_from_user failed in reg_init\n"); + ret = -EFAULT; + goto fail; + } + + fmt = RKVDEC_GET_FORMAT(task->reg[RKVDEC_REG_SYS_CTRL_INDEX]); + /* + * special offset scale case + * + * This translation is for fd + offset translation. + * One register has 32bits. We need to transfer both buffer file + * handle and the start address offset so we packet file handle + * and offset together using below format. + * + * 0~9 bit for buffer file handle range 0 ~ 1023 + * 10~31 bit for offset range 0 ~ 4M + * + * But on 4K case the offset can be larger the 4M + * So on VP9 4K decoder colmv base we scale the offset by 16 + */ + if (fmt == RKVDEC_FMT_VP9D) { + struct mpp_mem_region *mem_region = NULL; + dma_addr_t iova = 0; + u32 offset = task->reg[RKVDEC_REG_VP9_REFCOLMV_BASE_INDEX]; + int fd = task->reg[RKVDEC_REG_VP9_REFCOLMV_BASE_INDEX] & 0x3ff; + + offset = offset >> 10 << 4; + mem_region = mpp_task_attach_fd(&task->mpp_task, fd); + if (IS_ERR(mem_region)) { + ret = PTR_ERR(mem_region); + goto fail; + } + + iova = mem_region->iova; + task->reg[RKVDEC_REG_VP9_REFCOLMV_BASE_INDEX] = iova + offset; + } + + pps_fd = task->reg[RKVDEC_REG_PPS_BASE_INDEX] & 0x3ff; + pps_offset = task->reg[RKVDEC_REG_PPS_BASE_INDEX] >> 10; + if (pps_fd > 0) { + int pps_info_offset; + int pps_info_count; + int pps_info_size; + int scaling_list_addr_offset; + + switch (fmt) { + case RKVDEC_FMT_H264D: + pps_info_offset = pps_offset; + pps_info_count = 256; + pps_info_size = 32; + scaling_list_addr_offset = 23; + break; + case RKVDEC_FMT_H265D: + pps_info_offset = pps_offset; + pps_info_count = 64; + pps_info_size = 80; + scaling_list_addr_offset = 74; + break; + default: + pps_info_offset = 0; + pps_info_count = 0; + pps_info_size = 0; + scaling_list_addr_offset = 0; + break; + } + + mpp_debug(DEBUG_PPS_FILL, + "scaling list filling parameter:\n"); + mpp_debug(DEBUG_PPS_FILL, + "pps_info_offset %d\n", pps_info_offset); + mpp_debug(DEBUG_PPS_FILL, + "pps_info_count %d\n", pps_info_count); + mpp_debug(DEBUG_PPS_FILL, + "pps_info_size %d\n", pps_info_size); + mpp_debug(DEBUG_PPS_FILL, + "scaling_list_addr_offset %d\n", + scaling_list_addr_offset); + + if (pps_info_count) { + ret = fill_scaling_list_pps(task, pps_fd, + pps_info_offset, + pps_info_count, + pps_info_size, + scaling_list_addr_offset); + if (ret) { + mpp_err("fill pps failed\n"); + goto fail; + } + } + } + + ret = mpp_translate_reg_address(session->mpp, + &task->mpp_task, + fmt, task->reg); + if (ret) { + mpp_err("error: translate reg address failed.\n"); + mpp_dump_reg(task->reg, + task->hw_info->regidx_start, + task->hw_info->regidx_end); + goto fail; + } + + task->strm_addr = task->reg[RKVDEC_REG_RLC_BASE_INDEX]; + + mpp_debug_leave(); + + return &task->mpp_task; + +fail: + mpp_task_finalize(session, &task->mpp_task); + kfree(task); + return ERR_PTR(ret); +} + +static int rkvdec_prepare(struct mpp_dev *mpp, + struct mpp_task *task) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + if (dec->state == RKVDEC_STATE_NORMAL) + return -EINVAL; + /* + * Don't do soft reset before running or you will meet 0x00408322 + * if you will decode a HEVC stream. Different error for the AVC. + */ + + return 0; +} + +static int rkvdec_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 regidx_en; + u32 reg = 0; + struct rkvdec_dev *dec = NULL; + struct rkvdec_task *task = NULL; + + mpp_debug_enter(); + + dec = to_rkvdec_dev(mpp); + task = to_rkvdec_task(mpp_task); + + switch (dec->state) { + case RKVDEC_STATE_NORMAL: + /* FIXME: spin lock here */ + dec->current_task = task; + + /* set cache size */ + reg = RKVDEC_CACHE_PERMIT_CACHEABLE_ACCESS + | RKVDEC_CACHE_PERMIT_READ_ALLOCATE; + if (!mpp_debug_unlikely(DEBUG_CACHE_32B)) + reg |= RKVDEC_CACHE_LINE_SIZE_64_BYTES; + + mpp_write_relaxed(mpp, RKVDEC_REG_CACHE0_SIZE_BASE, reg); + mpp_write_relaxed(mpp, RKVDEC_REG_CACHE0_SIZE_BASE, reg); + /* clear cache */ + mpp_write_relaxed(mpp, RKVDEC_REG_CLR_CACHE0_BASE, 0); + mpp_write_relaxed(mpp, RKVDEC_REG_CLR_CACHE1_BASE, 0); + /* set registers for hardware */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + regidx_en = task->hw_info->regidx_en; + for (i = regidx_start; i <= regidx_end; i++) { + if (i == regidx_en) + continue; + mpp_write_relaxed(mpp, i * sizeof(u32), task->reg[i]); + } + /* Flush the register before the start the device */ + wmb(); + mpp_write(mpp, RKVDEC_REG_INT_EN, + task->reg[regidx_en] | RKVDEC_DEC_START); + break; + default: + break; + } + + mpp_debug_leave(); + + return 0; +} + +static int rkvdec_3328_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 fmt = 0; + u32 cfg = 0; + struct rkvdec_task *task = NULL; + + mpp_debug_enter(); + + task = to_rkvdec_task(mpp_task); + + /* + * HW defeat workaround: VP9 power save optimization cause decoding + * corruption, disable optimization here. + */ + fmt = RKVDEC_GET_FORMAT(task->reg[RKVDEC_REG_SYS_CTRL_INDEX]); + if (fmt == RKVDEC_FMT_VP9D) { + cfg = task->reg[RKVDEC_POWER_CTL_INDEX] | 0xFFFF; + task->reg[RKVDEC_POWER_CTL_INDEX] = cfg & (~(1 << 12)); + mpp_write_relaxed(mpp, RKVDEC_POWER_CTL_BASE, + task->reg[RKVDEC_POWER_CTL_INDEX]); + } + + rkvdec_run(mpp, mpp_task); + + mpp_debug_leave(); + + return 0; +} + +static int rkvdec_irq(struct mpp_dev *mpp) +{ + mpp->irq_status = mpp_read(mpp, RKVDEC_REG_INT_EN); + if (!(mpp->irq_status & RKVDEC_DEC_INT_RAW)) + return IRQ_NONE; + + mpp_write(mpp, RKVDEC_REG_INT_EN, 0); + + return IRQ_WAKE_THREAD; +} + +static int rkvdec_isr(struct mpp_dev *mpp) +{ + u32 err_mask; + struct rkvdec_task *task = NULL; + struct mpp_task *mpp_task = NULL; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + /* FIXME use a spin lock here */ + task = dec->current_task; + if (!task) { + dev_err(dec->mpp.dev, "no current task\n"); + return IRQ_HANDLED; + } + mpp_time_diff(&task->mpp_task); + dec->current_task = NULL; + task->irq_status = mpp->irq_status; + switch (dec->state) { + case RKVDEC_STATE_NORMAL: + mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", + task->irq_status); + + err_mask = RKVDEC_INT_BUF_EMPTY + | RKVDEC_INT_BUS_ERROR + | RKVDEC_INT_COLMV_REF_ERROR + | RKVDEC_INT_STRM_ERROR + | RKVDEC_INT_TIMEOUT; + + if (err_mask & task->irq_status) + atomic_inc(&mpp->reset_request); + + mpp_task = &task->mpp_task; + mpp_task_finish(mpp_task->session, mpp_task); + + mpp_debug_leave(); + return IRQ_HANDLED; + default: + goto fail; + } +fail: + return IRQ_HANDLED; +} + +static int rkvdec_finish(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 dec_get; + s32 dec_length; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + + mpp_debug_enter(); + + switch (dec->state) { + case RKVDEC_STATE_NORMAL: { + /* read register after running */ + regidx_start = 2;//task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + for (i = regidx_start; i <= regidx_end; i++) + task->reg[i] = mpp_read_relaxed(mpp, i * sizeof(u32)); + task->reg[RKVDEC_REG_INT_EN_INDEX] = task->irq_status; + /* revert hack for decoded length */ + dec_get = task->reg[RKVDEC_REG_RLC_BASE_INDEX]; + dec_length = dec_get - task->strm_addr; + task->reg[RKVDEC_REG_RLC_BASE_INDEX] = dec_length << 10; + mpp_debug(DEBUG_REGISTER, + "dec_get %08x dec_length %d\n", + dec_get, dec_length); + } break; + default: + break; + } + + mpp_debug_leave(); + + return 0; +} + +static int rkvdec_result(struct mpp_dev *mpp, + struct mpp_task *mpp_task, + u32 __user *dst, u32 size) +{ + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + + /* FIXME may overflow the kernel */ + if (copy_to_user(dst, task->reg, size)) { + mpp_err("copy_to_user failed\n"); + return -EIO; + } + + return 0; +} + +static int rkvdec_free_task(struct mpp_session *session, + struct mpp_task *mpp_task) +{ + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + + mpp_task_finalize(session, mpp_task); + kfree(task); + + return 0; +} + +static int rkvdec_debugfs_remove(struct mpp_dev *mpp) +{ +#ifdef CONFIG_DEBUG_FS + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + debugfs_remove_recursive(dec->debugfs); +#endif + return 0; +} + +static int rkvdec_debugfs_init(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + dec->aclk_debug = 0; + dec->clk_core_debug = 0; + dec->clk_cabac_debug = 0; + dec->session_max_buffers_debug = 0; +#ifdef CONFIG_DEBUG_FS + dec->debugfs = debugfs_create_dir(np->name, mpp->srv->debugfs); + if (IS_ERR_OR_NULL(dec->debugfs)) { + mpp_err("failed on open debugfs\n"); + dec->debugfs = NULL; + return -EIO; + } + debugfs_create_u32("aclk", 0644, + dec->debugfs, &dec->aclk_debug); + debugfs_create_u32("clk_core", 0644, + dec->debugfs, &dec->clk_core_debug); + debugfs_create_u32("clk_cabac", 0644, + dec->debugfs, &dec->clk_cabac_debug); + debugfs_create_u32("session_buffers", 0644, + dec->debugfs, &dec->session_max_buffers_debug); +#endif + if (dec->session_max_buffers_debug) + mpp->session_max_buffers = dec->session_max_buffers_debug; + + return 0; +} + +static int rkvdec_init(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + mutex_init(&dec->sip_reset_lock); + mutex_init(&dec->set_clk_lock); + + mpp->grf_info = &mpp->srv->grf_infos[MPP_DRIVER_RKVDEC]; + + dec->aclk = devm_clk_get(mpp->dev, "aclk_vcodec"); + if (IS_ERR_OR_NULL(dec->aclk)) { + mpp_err("failed on clk_get aclk_vcodec\n"); + dec->aclk = NULL; + } + dec->hclk = devm_clk_get(mpp->dev, "hclk_vcodec"); + if (IS_ERR_OR_NULL(dec->hclk)) { + mpp_err("failed on clk_get hclk_vcodec\n"); + dec->hclk = NULL; + } + dec->clk_cabac = devm_clk_get(mpp->dev, "clk_cabac"); + if (IS_ERR_OR_NULL(dec->clk_cabac)) { + mpp_err("failed on clk_get clk_cabac\n"); + dec->clk_cabac = NULL; + } + dec->clk_core = devm_clk_get(mpp->dev, "clk_core"); + if (IS_ERR_OR_NULL(dec->clk_core)) { + mpp_err("failed on clk_get clk_core\n"); + dec->clk_core = NULL; + } + + dec->rst_a = devm_reset_control_get_shared(mpp->dev, "video_a"); + if (IS_ERR_OR_NULL(dec->rst_a)) { + mpp_err("No aclk reset resource define\n"); + dec->rst_a = NULL; + } + dec->rst_h = devm_reset_control_get_shared(mpp->dev, "video_h"); + if (IS_ERR_OR_NULL(dec->rst_h)) { + mpp_err("No hclk reset resource define\n"); + dec->rst_h = NULL; + } + dec->rst_niu_a = devm_reset_control_get_shared(mpp->dev, "niu_a"); + if (IS_ERR_OR_NULL(dec->rst_niu_a)) { + mpp_err("No niu aclk reset resource define\n"); + dec->rst_niu_a = NULL; + } + dec->rst_niu_h = devm_reset_control_get_shared(mpp->dev, "niu_h"); + if (IS_ERR_OR_NULL(dec->rst_niu_h)) { + mpp_err("No niu hclk reset resource define\n"); + dec->rst_niu_h = NULL; + } + dec->rst_cabac = devm_reset_control_get_shared(mpp->dev, "video_cabac"); + if (IS_ERR_OR_NULL(dec->rst_cabac)) { + mpp_err("No cabac reset resource define\n"); + dec->rst_cabac = NULL; + } + dec->rst_core = devm_reset_control_get_shared(mpp->dev, "video_core"); + if (IS_ERR_OR_NULL(dec->rst_core)) { + mpp_err("No core reset resource define\n"); + dec->rst_core = NULL; + } + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + mpp_safe_unreset(dec->rst_niu_h); + mpp_safe_unreset(dec->rst_niu_a); + mpp_safe_unreset(dec->rst_cabac); + mpp_safe_unreset(dec->rst_core); + + return 0; +} + +static int rkvdec_3328_iommu_hdl(struct iommu_domain *iommu, + struct device *iommu_dev, + unsigned long iova, + int status, void *arg) +{ + int ret = 0; + struct device *dev = (struct device *)arg; + struct platform_device *pdev = NULL; + struct rkvdec_dev *dec = NULL; + struct mpp_dev *mpp = NULL; + + pdev = container_of(dev, struct platform_device, dev); + if (!pdev) { + dev_err(dev, "invalid platform_device\n"); + ret = -ENXIO; + goto done; + } + + dec = platform_get_drvdata(pdev); + if (!dec) { + dev_err(dev, "invalid device instance\n"); + ret = -ENXIO; + goto done; + } + mpp = &dec->mpp; + + /* + * defeat workaround, invalidate address generated when rk322x + * hevc decoder tile mode pre-fetch colmv data. + */ + if (IOMMU_GET_BUS_ID(status) == 2) { + unsigned long page_iova = 0; + /* avoid another page fault occur after page fault */ + if (dec->aux_iova) + iommu_unmap(mpp->iommu_info->domain, + dec->aux_iova, + IOMMU_PAGE_SIZE); + + page_iova = round_down(iova, IOMMU_PAGE_SIZE); + ret = iommu_map(mpp->iommu_info->domain, + page_iova, + page_to_phys(dec->aux_page), + IOMMU_PAGE_SIZE, + DMA_FROM_DEVICE); + if (!ret) + dec->aux_iova = page_iova; + } + +done: + return ret; +} + +static int rkvdec_devfreq_remove(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + devfreq_unregister_opp_notifier(mpp->dev, dec->devfreq); + dev_pm_opp_of_remove_table(mpp->dev); + + return 0; +} + +static int rkvdec_devfreq_init(struct mpp_dev *mpp) +{ + int ret = 0; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + dec->vdd = devm_regulator_get_optional(mpp->dev, "vcodec"); + if (IS_ERR_OR_NULL(dec->vdd)) { + dev_warn(mpp->dev, "no regulator for %s\n", + dev_name(mpp->dev)); + dec->vdd = NULL; + ret = -EINVAL; + goto done; + } else { + struct devfreq_dev_status *stat; + + ret = rockchip_init_opp_table(mpp->dev, NULL, + "rkvdec_leakage", "vcodec"); + if (ret) { + dev_err(mpp->dev, "Failed to init_opp_table\n"); + goto done; + } + dec->devfreq = devm_devfreq_add_device(mpp->dev, + &devfreq_profile, + "userspace", + NULL); + if (IS_ERR(dec->devfreq)) { + ret = PTR_ERR(dec->devfreq); + goto done; + } + + stat = &dec->devfreq->last_status; + stat->current_frequency = clk_get_rate(dec->aclk); + + ret = devfreq_register_opp_notifier(mpp->dev, dec->devfreq); + if (ret) + goto done; + } + + dec->parent_devfreq = devfreq_get_devfreq_by_phandle(mpp->dev, 0); + if (IS_ERR_OR_NULL(dec->parent_devfreq)) { + dev_warn(mpp->dev, "parent devfreq is error\n"); + dec->parent_devfreq = NULL; + ret = -EINVAL; + goto done; + } else { + dec->devfreq_nb.notifier_call = devfreq_notifier_call; + devm_devfreq_register_notifier(mpp->dev, + dec->parent_devfreq, + &dec->devfreq_nb, + DEVFREQ_TRANSITION_NOTIFIER); + } + /* power simplle init */ + ret = power_model_simple_init(mpp); + if (!ret && dec->devfreq) { + dec->devfreq_cooling = + of_devfreq_cooling_register_power(mpp->dev->of_node, + dec->devfreq, + &cooling_power_data); + if (IS_ERR_OR_NULL(dec->devfreq_cooling)) { + ret = -ENXIO; + dev_err(mpp->dev, "Failed to register cooling\n"); + goto done; + } + } + +done: + return ret; +} + +static int rkvdec_3328_init(struct mpp_dev *mpp) +{ + int ret = 0; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + rkvdec_init(mpp); + + /* warkaround for mmu pagefault */ + dec->aux_page = alloc_page(GFP_KERNEL); + if (!dec->aux_page) { + dev_err(mpp->dev, "allocate a page for auxiliary usage\n"); + ret = -ENOMEM; + goto done; + } + dec->aux_iova = 0; + iommu_set_fault_handler(mpp->iommu_info->domain, + rkvdec_3328_iommu_hdl, + mpp->dev); + + ret = rkvdec_devfreq_init(mpp); +done: + return ret; +} + +static int rkvdec_3328_exit(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + if (dec->aux_page) + __free_page(dec->aux_page); + + if (dec->aux_iova) + iommu_unmap(mpp->iommu_info->domain, + dec->aux_iova, + IOMMU_PAGE_SIZE); + rkvdec_devfreq_remove(mpp); + + return 0; +} + +static int rkvdec_power_on(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + if (dec->aclk) + clk_prepare_enable(dec->aclk); + if (dec->hclk) + clk_prepare_enable(dec->hclk); + if (dec->clk_cabac) + clk_prepare_enable(dec->clk_cabac); + if (dec->clk_core) + clk_prepare_enable(dec->clk_core); + + return 0; +} + +static int rkvdec_power_off(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + if (dec->aclk) + clk_disable_unprepare(dec->aclk); + if (dec->hclk) + clk_disable_unprepare(dec->hclk); + if (dec->clk_cabac) + clk_disable_unprepare(dec->clk_cabac); + if (dec->clk_core) + clk_disable_unprepare(dec->clk_core); + + return 0; +} + +static int rkvdec_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + + task->aclk_freq = 300; + task->clk_cabac_freq = 200; + task->clk_core_freq = 200; + + return 0; +} + +static int rkvdec_3328_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + u32 ddr_align_en = task->reg[RKVDEC_REG_INT_EN_INDEX] & + RKVDEC_WR_DDR_ALIGN_EN; + u32 fmt = RKVDEC_GET_FORMAT(task->reg[RKVDEC_REG_SYS_CTRL_INDEX]); + + if (ddr_align_en) { + if (fmt == RKVDEC_FMT_H264D) { + task->aclk_freq = 400; + task->clk_cabac_freq = 400; + task->clk_core_freq = 250; + } else { + task->aclk_freq = 500; + task->clk_cabac_freq = 400; + task->clk_core_freq = 250; + } + } else { + if (fmt == RKVDEC_FMT_H264D) { + task->aclk_freq = 400; + task->clk_cabac_freq = 400; + task->clk_core_freq = 300; + } else { + task->aclk_freq = 500; + task->clk_cabac_freq = 300; + task->clk_core_freq = 400; + } + } + + return 0; +} + +static int rkvdec_set_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct devfreq_dev_status *stat; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + struct rkvdec_task *task = to_rkvdec_task(mpp_task); + + task->aclk_freq = dec->aclk_debug ? + dec->aclk_debug : task->aclk_freq; + task->clk_cabac_freq = dec->clk_cabac_debug ? + dec->clk_cabac_debug : task->clk_cabac_freq; + task->clk_core_freq = dec->clk_core_debug ? + dec->clk_core_debug : task->clk_core_freq; + + if (dec->devfreq) { + stat = &dec->devfreq->last_status; + stat->busy_time = 1; + stat->total_time = 1; + rkvdec_set_clk(dec, task->aclk_freq * MHZ, + task->clk_core_freq * MHZ, + task->clk_cabac_freq * MHZ, + EVENT_ADJUST); + } else { + clk_set_rate(dec->aclk, task->aclk_freq * MHZ); + clk_set_rate(dec->clk_cabac, task->clk_cabac_freq * MHZ); + clk_set_rate(dec->clk_core, task->clk_core_freq * MHZ); + } + + return 0; +} + +static int rkvdec_reduce_freq(struct mpp_dev *mpp) +{ + struct devfreq_dev_status *stat; + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + if (dec->devfreq) { + stat = &dec->devfreq->last_status; + stat->busy_time = 0; + stat->total_time = 1; + rkvdec_set_clk(dec, 50 * MHZ, 50 * MHZ, 50 * MHZ, EVENT_ADJUST); + } else { + if (dec->aclk) + clk_set_rate(dec->aclk, 50 * MHZ); + if (dec->clk_cabac) + clk_set_rate(dec->clk_cabac, 50 * MHZ); + if (dec->clk_core) + clk_set_rate(dec->clk_core, 50 * MHZ); + } + + return 0; +} + +static int rkvdec_reset(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + + mpp_debug_enter(); + if (dec->rst_a && dec->rst_h) { + rockchip_pmu_idle_request(mpp->dev, true); + mpp_safe_reset(dec->rst_niu_a); + mpp_safe_reset(dec->rst_niu_h); + mpp_safe_reset(dec->rst_a); + mpp_safe_reset(dec->rst_h); + mpp_safe_reset(dec->rst_core); + mpp_safe_reset(dec->rst_cabac); + udelay(5); + mpp_safe_unreset(dec->rst_niu_h); + mpp_safe_unreset(dec->rst_niu_a); + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + mpp_safe_unreset(dec->rst_core); + mpp_safe_unreset(dec->rst_cabac); + rockchip_pmu_idle_request(mpp->dev, false); + } + mpp_debug_leave(); + + return 0; +} + +static int rkvdec_sip_reset(struct mpp_dev *mpp) +{ + struct rkvdec_dev *dec = to_rkvdec_dev(mpp); + +/* The reset flow in arm trustzone firmware */ +#if CONFIG_ROCKCHIP_SIP + mutex_lock(&dec->sip_reset_lock); + sip_smc_vpu_reset(0, 0, 0); + mutex_unlock(&dec->sip_reset_lock); + + return 0; +#else + return rkvdec_reset(mpp); +#endif +} + +static struct mpp_hw_ops rkvdec_v1_hw_ops = { + .init = rkvdec_init, + .power_on = rkvdec_power_on, + .power_off = rkvdec_power_off, + .get_freq = rkvdec_get_freq, + .set_freq = rkvdec_set_freq, + .reduce_freq = rkvdec_reduce_freq, + .reset = rkvdec_reset, +}; + +static struct mpp_hw_ops rkvdec_3399_hw_ops = { + .init = rkvdec_init, + .power_on = rkvdec_power_on, + .power_off = rkvdec_power_off, + .get_freq = rkvdec_get_freq, + .set_freq = rkvdec_set_freq, + .reduce_freq = rkvdec_reduce_freq, + .reset = rkvdec_sip_reset, +}; + +static struct mpp_dev_ops rkvdec_v1_dev_ops = { + .alloc_task = rkvdec_alloc_task, + .prepare = rkvdec_prepare, + .run = rkvdec_run, + .irq = rkvdec_irq, + .isr = rkvdec_isr, + .finish = rkvdec_finish, + .result = rkvdec_result, + .free_task = rkvdec_free_task, +}; + +static struct mpp_hw_ops rkvdec_3328_hw_ops = { + .init = rkvdec_3328_init, + .exit = rkvdec_3328_exit, + .power_on = rkvdec_power_on, + .power_off = rkvdec_power_off, + .get_freq = rkvdec_3328_get_freq, + .set_freq = rkvdec_set_freq, + .reduce_freq = rkvdec_reduce_freq, + .reset = rkvdec_sip_reset, +}; + +static struct mpp_dev_ops rkvdec_3328_dev_ops = { + .alloc_task = rkvdec_alloc_task, + .prepare = rkvdec_prepare, + .run = rkvdec_3328_run, + .irq = rkvdec_irq, + .isr = rkvdec_isr, + .finish = rkvdec_finish, + .result = rkvdec_result, + .free_task = rkvdec_free_task, +}; + +static const struct mpp_dev_var rk_hevcdec_data = { + .device_type = MPP_DEVICE_DEC_RKV, + .hw_info = &rk_hevcdec_hw_info, + .trans_info = rk_hevcdec_trans, + .hw_ops = &rkvdec_v1_hw_ops, + .dev_ops = &rkvdec_v1_dev_ops, +}; + +static const struct mpp_dev_var rkvdec_v1_data = { + .device_type = MPP_DEVICE_DEC_RKV, + .hw_info = &rkvdec_v1_hw_info, + .trans_info = rkvdec_v1_trans, + .hw_ops = &rkvdec_v1_hw_ops, + .dev_ops = &rkvdec_v1_dev_ops, +}; + +static const struct mpp_dev_var rkvdec_3399_data = { + .device_type = MPP_DEVICE_DEC_RKV, + .hw_info = &rkvdec_v1_hw_info, + .trans_info = rkvdec_v1_trans, + .hw_ops = &rkvdec_3399_hw_ops, + .dev_ops = &rkvdec_v1_dev_ops, +}; + +static const struct mpp_dev_var rkvdec_3328_data = { + .device_type = MPP_DEVICE_DEC_RKV, + .hw_info = &rkvdec_v1_hw_info, + .trans_info = rkvdec_v1_trans, + .hw_ops = &rkvdec_3328_hw_ops, + .dev_ops = &rkvdec_3328_dev_ops, +}; + +static const struct of_device_id mpp_rkvdec_dt_match[] = { + { + .compatible = "rockchip,hevc-decoder", + .data = &rk_hevcdec_data, + }, + { + .compatible = "rockchip,rkv-decoder-v1", + .data = &rkvdec_v1_data, + }, + { + .compatible = "rockchip,rkv-decoder-rk3399", + .data = &rkvdec_3399_data, + }, + { + .compatible = "rockchip,rkv-decoder-rk3328", + .data = &rkvdec_3328_data, + }, + {}, +}; + +static int rkvdec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rkvdec_dev *dec = NULL; + struct mpp_dev *mpp = NULL; + const struct of_device_id *match = NULL; + int ret = 0; + + dev_info(dev, "probing start\n"); + dec = devm_kzalloc(dev, sizeof(*dec), GFP_KERNEL); + if (!dec) + return -ENOMEM; + + mpp = &dec->mpp; + platform_set_drvdata(pdev, dec); + + if (pdev->dev.of_node) { + match = of_match_node(mpp_rkvdec_dt_match, + pdev->dev.of_node); + if (match) + mpp->var = (struct mpp_dev_var *)match->data; + } + + ret = mpp_dev_probe(mpp, pdev); + if (ret) { + dev_err(dev, "probe sub driver failed\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mpp->irq, + mpp_dev_irq, + mpp_dev_isr_sched, + IRQF_SHARED, + dev_name(dev), mpp); + if (ret) { + dev_err(dev, "register interrupter runtime failed\n"); + return -EINVAL; + } + + dec->state = RKVDEC_STATE_NORMAL; + mpp->session_max_buffers = RKVDEC_SESSION_MAX_BUFFERS; + rkvdec_debugfs_init(mpp); + dev_info(dev, "probing finish\n"); + + return 0; +} + +static int rkvdec_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rkvdec_dev *dec = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + mpp_dev_remove(&dec->mpp); + rkvdec_debugfs_remove(&dec->mpp); + + return 0; +} + +static void rkvdec_shutdown(struct platform_device *pdev) +{ + int ret; + int val; + struct device *dev = &pdev->dev; + struct rkvdec_dev *dec = platform_get_drvdata(pdev); + struct mpp_dev *mpp = &dec->mpp; + + dev_info(dev, "shutdown device\n"); + + atomic_inc(&mpp->srv->shutdown_request); + ret = readx_poll_timeout(atomic_read, + &mpp->total_running, + val, val == 0, 20000, 200000); + if (ret == -ETIMEDOUT) + dev_err(dev, "wait total running time out\n"); +} + +struct platform_driver rockchip_rkvdec_driver = { + .probe = rkvdec_probe, + .remove = rkvdec_remove, + .shutdown = rkvdec_shutdown, + .driver = { + .name = RKVDEC_DRIVER_NAME, + .of_match_table = of_match_ptr(mpp_rkvdec_dt_match), + }, +}; +EXPORT_SYMBOL(rockchip_rkvdec_driver); diff --git a/drivers/video/rockchip/mpp/mpp_service.c b/drivers/video/rockchip/mpp/mpp_service.c new file mode 100644 index 000000000000..efd9dcb22cc5 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_service.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define MPP_CLASS_NAME "mpp_class" +#define MPP_SERVICE_NAME "mpp_service" + +#define MPP_REGISTER_DRIVER(X, x) {\ + if (IS_ENABLED(CONFIG_ROCKCHIP_MPP_##X))\ + mpp_add_driver(MPP_DRIVER_##X, &rockchip_##x##_driver);\ + } + +unsigned int mpp_dev_debug; +module_param(mpp_dev_debug, uint, 0644); +MODULE_PARM_DESC(mpp_dev_debug, "bit switch for mpp debug information"); + +static struct mpp_grf_info *mpp_grf_infos; +static struct platform_driver **mpp_sub_drivers; + +static int mpp_init_grf(struct device_node *np, + const char *name, + struct mpp_grf_info *grf_info) +{ + grf_info->grf = syscon_regmap_lookup_by_phandle(np, name); + if (IS_ERR_OR_NULL(grf_info->grf)) { + grf_info->grf = NULL; + } else { + of_property_read_u32_index(np, name, 1, + &grf_info->mode_ctrl); + of_property_read_u32_index(np, name, 2, + &grf_info->mode_val); + } + + return 0; +} + +static int mpp_init_drvdata(struct mpp_taskqueue *queue, + struct mpp_service *srv) +{ + mutex_init(&queue->lock); + atomic_set(&queue->running, 0); + INIT_LIST_HEAD(&queue->pending); + + queue->srv = srv; + queue->cur_task = NULL; + + return 0; +} + +static const struct file_operations mpp_dev_fops = { + .unlocked_ioctl = mpp_dev_ioctl, + .open = mpp_dev_open, + .release = mpp_dev_release, + .poll = mpp_dev_poll, +#ifdef CONFIG_COMPAT + .compat_ioctl = mpp_dev_compat_ioctl, +#endif +}; + +static int mpp_register_service(struct mpp_service *srv, + const char *service_name) +{ + int ret; + struct device *dev = srv->dev; + + /* create a device */ + ret = alloc_chrdev_region(&srv->dev_id, 0, 1, service_name); + if (ret) { + dev_err(dev, "alloc dev_t failed\n"); + return ret; + } + + cdev_init(&srv->mpp_cdev, &mpp_dev_fops); + srv->mpp_cdev.owner = THIS_MODULE; + srv->mpp_cdev.ops = &mpp_dev_fops; + + ret = cdev_add(&srv->mpp_cdev, srv->dev_id, 1); + if (ret) { + unregister_chrdev_region(srv->dev_id, 1); + dev_err(dev, "add device failed\n"); + return ret; + } + + srv->child_dev = device_create(srv->cls, dev, srv->dev_id, + NULL, "%s", service_name); + + return 0; +} + +static int mpp_remove_service(struct mpp_service *srv) +{ + device_destroy(srv->cls, srv->dev_id); + cdev_del(&srv->mpp_cdev); + unregister_chrdev_region(srv->dev_id, 1); + + return 0; +} + +static int mpp_debugfs_remove(struct mpp_service *srv) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(srv->debugfs); +#endif + return 0; +} + +static int mpp_debugfs_init(struct mpp_service *srv) +{ +#ifdef CONFIG_DEBUG_FS + srv->debugfs = debugfs_create_dir(MPP_SERVICE_NAME, NULL); + if (IS_ERR_OR_NULL(srv->debugfs)) { + mpp_err("failed on open debugfs\n"); + srv->debugfs = NULL; + } +#endif + + return 0; +} + +static int mpp_service_probe(struct platform_device *pdev) +{ + int ret; + u32 taskqueue_cnt; + struct mpp_service *srv = NULL; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + dev_info(dev, "probe start\n"); + srv = devm_kzalloc(dev, sizeof(*srv), GFP_KERNEL); + if (!srv) + return -ENOMEM; + + srv->dev = dev; + atomic_set(&srv->shutdown_request, 0); + platform_set_drvdata(pdev, srv); + + srv->cls = class_create(THIS_MODULE, MPP_CLASS_NAME); + if (PTR_ERR_OR_ZERO(srv->cls)) + return PTR_ERR(srv->cls); + + of_property_read_u32(np, "rockchip,taskqueue-count", + &taskqueue_cnt); + if (taskqueue_cnt > MPP_DEVICE_BUTT) { + dev_err(dev, "rockchip,taskqueue-count %d must less than %d\n", + taskqueue_cnt, MPP_DEVICE_BUTT); + return -EINVAL; + } + + if (taskqueue_cnt) { + u32 i = 0; + struct mpp_taskqueue *queue; + + for (i = 0; i < taskqueue_cnt; i++) { + queue = devm_kzalloc(dev, sizeof(*queue), GFP_KERNEL); + if (!queue) + continue; + + mpp_init_drvdata(queue, srv); + srv->task_queues[i] = queue; + } + } + + mpp_init_grf(np, "rkvdec,grf", &srv->grf_infos[MPP_DRIVER_RKVDEC]); + mpp_init_grf(np, "rkvenc,grf", &srv->grf_infos[MPP_DRIVER_RKVENC]); + mpp_init_grf(np, "vdpu1,grf", &srv->grf_infos[MPP_DRIVER_VDPU1]); + mpp_init_grf(np, "vepu1,grf", &srv->grf_infos[MPP_DRIVER_VEPU1]); + mpp_init_grf(np, "vdpu2,grf", &srv->grf_infos[MPP_DRIVER_VDPU2]); + mpp_init_grf(np, "vepu2,grf", &srv->grf_infos[MPP_DRIVER_VEPU2]); + mpp_init_grf(np, "vepu22,grf", &srv->grf_infos[MPP_DRIVER_VEPU22]); + mpp_grf_infos = srv->grf_infos; + + ret = mpp_register_service(srv, MPP_SERVICE_NAME); + if (ret) { + dev_err(dev, "register %s device\n", MPP_SERVICE_NAME); + goto fail_register; + } + mpp_sub_drivers = srv->sub_drivers; + + mpp_debugfs_init(srv); + + dev_info(dev, "probe success\n"); + + return 0; + +fail_register: + class_destroy(srv->cls); + + return ret; +} + +static int mpp_service_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mpp_service *srv = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + + mpp_remove_service(srv); + class_destroy(srv->cls); + mpp_debugfs_remove(srv); + + return 0; +} + +static const struct of_device_id mpp_dt_ids[] = { + { + .compatible = "rockchip,mpp-service", + }, + { }, +}; + +static struct platform_driver mpp_service_driver = { + .probe = mpp_service_probe, + .remove = mpp_service_remove, + .driver = { + .name = "mpp_service", + .of_match_table = of_match_ptr(mpp_dt_ids), + }, +}; + +static int mpp_add_driver(enum MPP_DRIVER_TYPE type, + struct platform_driver *driver) +{ + int ret; + + mpp_set_grf(&mpp_grf_infos[type]); + + ret = platform_driver_register(driver); + if (ret) + return ret; + + mpp_sub_drivers[type] = driver; + + return 0; +} + +static int mpp_remove_driver(int i, struct platform_driver *driver) +{ + if (driver) { + mpp_set_grf(&mpp_grf_infos[i]); + platform_driver_unregister(driver); + } + + return 0; +} + +static int __init mpp_service_init(void) +{ + int ret; + + ret = platform_driver_register(&mpp_service_driver); + if (ret) { + pr_err("Mpp service device register failed (%d).\n", ret); + return ret; + } + MPP_REGISTER_DRIVER(RKVDEC, rkvdec); + MPP_REGISTER_DRIVER(RKVENC, rkvenc); + MPP_REGISTER_DRIVER(VDPU1, vdpu1); + MPP_REGISTER_DRIVER(VEPU1, vepu1); + MPP_REGISTER_DRIVER(VDPU2, vdpu2); + MPP_REGISTER_DRIVER(VEPU2, vepu2); + MPP_REGISTER_DRIVER(VEPU22, vepu22); + + return 0; +} + +static void __exit mpp_service_exit(void) +{ + int i; + + for (i = 0; i < MPP_DRIVER_BUTT; i++) + mpp_remove_driver(i, mpp_sub_drivers[i]); + + platform_driver_unregister(&mpp_service_driver); +} + +module_init(mpp_service_init); +module_exit(mpp_service_exit); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_VERSION("1.0.build.201911131848"); +MODULE_AUTHOR("Ding Wei leo.ding@rock-chips.com"); +MODULE_DESCRIPTION("Rockchip mpp service driver"); diff --git a/drivers/video/rockchip/mpp/mpp_vdpu1.c b/drivers/video/rockchip/mpp/mpp_vdpu1.c new file mode 100644 index 000000000000..1139b4f3b71b --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_vdpu1.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define VDPU1_DRIVER_NAME "mpp_vdpu1" + +#define VDPU1_SESSION_MAX_BUFFERS 40 +/* The maximum registers number of all the version */ +#define VDPU1_REG_NUM 60 +#define VDPU1_REG_START_INDEX 0 +#define VDPU1_REG_END_INDEX 59 + +#define VDPU1_REG_PP_NUM 101 +#define VDPU1_REG_PP_START_INDEX 0 +#define VDPU1_REG_PP_END_INDEX 100 + +#define VDPU1_REG_DEC_INT_EN 0x004 +#define VDPU1_REG_DEC_INT_EN_INDEX (1) +/* B slice detected, used in 8190 decoder and later */ +#define VDPU1_INT_PIC_INF BIT(24) +#define VDPU1_INT_TIMEOUT BIT(18) +#define VDPU1_INT_SLICE BIT(17) +#define VDPU1_INT_STRM_ERROR BIT(16) +#define VDPU1_INT_ASO_ERROR BIT(15) +#define VDPU1_INT_BUF_EMPTY BIT(14) +#define VDPU1_INT_BUS_ERROR BIT(13) +#define VDPU1_DEC_INT BIT(12) +#define VDPU1_DEC_INT_RAW BIT(8) +#define VDPU1_DEC_IRQ_DIS BIT(4) +#define VDPU1_DEC_START BIT(0) + +/* NOTE: Don't enable it or decoding AVC would meet problem at rk3288 */ +#define VDPU1_REG_DEC_EN 0x008 +#define VDPU1_CLOCK_GATE_EN BIT(10) + +#define VDPU1_REG_SYS_CTRL 0x00c +#define VDPU1_REG_SYS_CTRL_INDEX (3) +#define VDPU1_GET_FORMAT(x) (((x) >> 28) & 0xf) +#define VDPU1_FMT_H264D 0 +#define VDPU1_FMT_MPEG4D 1 +#define VDPU1_FMT_H263D 2 +#define VDPU1_FMT_JPEGD 3 +#define VDPU1_FMT_VC1D 4 +#define VDPU1_FMT_MPEG2D 5 +#define VDPU1_FMT_MPEG1D 6 +#define VDPU1_FMT_VP6D 7 +#define VDPU1_FMT_RESERVED 8 +#define VDPU1_FMT_VP7D 9 +#define VDPU1_FMT_VP8D 10 +#define VDPU1_FMT_AVSD 11 + +#define VDPU1_REG_STREAM_RLC_BASE 0x030 +#define VDPU1_REG_STREAM_RLC_BASE_INDEX (12) + +#define VDPU1_REG_DIR_MV_BASE 0x0a4 +#define VDPU1_REG_DIR_MV_BASE_INDEX (41) + +#define VDPU1_REG_CLR_CACHE_BASE 0x810 + +#define to_vdpu_task(task) \ + container_of(task, struct vdpu_task, mpp_task) +#define to_vdpu_dev(dev) \ + container_of(dev, struct vdpu_dev, mpp) + +struct vdpu_task { + struct mpp_task mpp_task; + struct mpp_hw_info *hw_info; + /* enable of post process */ + bool pp_enable; + + unsigned long aclk_freq; + u32 reg[VDPU1_REG_PP_NUM]; + u32 idx; + struct extra_info_for_iommu ext_inf; + u32 strm_addr; + u32 irq_status; +}; + +struct vdpu_dev { + struct mpp_dev mpp; + + struct clk *aclk; + struct clk *hclk; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + u32 aclk_debug; + u32 session_max_buffers_debug; + + struct reset_control *rst_a; + struct reset_control *rst_h; + struct vdpu_task *current_task; +}; + +static struct mpp_hw_info vdpu_v1_hw_info = { + .reg_num = VDPU1_REG_NUM, + .regidx_start = VDPU1_REG_START_INDEX, + .regidx_end = VDPU1_REG_END_INDEX, + .regidx_en = VDPU1_REG_DEC_INT_EN_INDEX, +}; + +static struct mpp_hw_info vdpu_pp_v1_hw_info = { + .reg_num = VDPU1_REG_PP_NUM, + .regidx_start = VDPU1_REG_PP_START_INDEX, + .regidx_end = VDPU1_REG_PP_END_INDEX, + .regidx_en = VDPU1_REG_DEC_INT_EN_INDEX, +}; + +/* + * file handle translate information + */ +static const char trans_tbl_avsd[] = { + 12, 13, 14, 15, 16, 17, 40, 41, 45 +}; + +static const char trans_tbl_default[] = { + 12, 13, 14, 15, 16, 17, 40, 41 +}; + +static const char trans_tbl_jpegd[] = { + 12, 13, 14, 40, 66, 67 +}; + +static const char trans_tbl_h264d[] = { + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 40 +}; + +static const char trans_tbl_vc1d[] = { + 12, 13, 14, 15, 16, 17, 27, 41 +}; + +static const char trans_tbl_vp6d[] = { + 12, 13, 14, 18, 27, 40 +}; + +static const char trans_tbl_vp8d[] = { + 10, 12, 13, 14, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 40 +}; + +static struct mpp_trans_info vdpu_v1_trans[] = { + [VDPU1_FMT_H264D] = { + .count = sizeof(trans_tbl_h264d), + .table = trans_tbl_h264d, + }, + [VDPU1_FMT_H263D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU1_FMT_MPEG4D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU1_FMT_JPEGD] = { + .count = sizeof(trans_tbl_jpegd), + .table = trans_tbl_jpegd, + }, + [VDPU1_FMT_VC1D] = { + .count = sizeof(trans_tbl_vc1d), + .table = trans_tbl_vc1d, + }, + [VDPU1_FMT_MPEG2D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU1_FMT_MPEG1D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU1_FMT_VP6D] = { + .count = sizeof(trans_tbl_vp6d), + .table = trans_tbl_vp6d, + }, + [VDPU1_FMT_RESERVED] = { + .count = 0, + .table = NULL, + }, + [VDPU1_FMT_VP7D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU1_FMT_VP8D] = { + .count = sizeof(trans_tbl_vp8d), + .table = trans_tbl_vp8d, + }, + [VDPU1_FMT_AVSD] = { + .count = sizeof(trans_tbl_avsd), + .table = trans_tbl_avsd, + }, +}; + +static void *vdpu_alloc_task(struct mpp_session *session, + void __user *src, u32 size) +{ + u32 reg_len; + u32 extinf_len; + u32 fmt = 0; + int err = -EFAULT; + struct vdpu_task *task = NULL; + u32 dwsize = size / sizeof(u32); + struct mpp_dev *mpp = session->mpp; + + mpp_debug_enter(); + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (!task) + return NULL; + + mpp_task_init(session, &task->mpp_task); + + if (session->device_type == MPP_DEVICE_DEC_PP) { + task->pp_enable = true; + task->hw_info = &vdpu_pp_v1_hw_info; + } else { + task->hw_info = mpp->var->hw_info; + } + reg_len = min(task->hw_info->reg_num, dwsize); + extinf_len = dwsize > reg_len ? (dwsize - reg_len) * 4 : 0; + + if (copy_from_user(task->reg, src, reg_len * 4)) { + mpp_err("error: copy_from_user failed in reg_init\n"); + err = -EFAULT; + goto fail; + } + + fmt = VDPU1_GET_FORMAT(task->reg[VDPU1_REG_SYS_CTRL_INDEX]); + if (extinf_len > 0) { + if (likely(fmt == VDPU1_FMT_JPEGD)) { + err = copy_from_user(&task->ext_inf, + (u8 *)src + size + - JPEG_IOC_EXTRA_SIZE, + JPEG_IOC_EXTRA_SIZE); + } else { + u32 ext_cpy = min_t(size_t, extinf_len, + sizeof(task->ext_inf)); + err = copy_from_user(&task->ext_inf, + (u32 *)src + reg_len, + ext_cpy); + } + + if (err) { + mpp_err("copy_from_user failed when extra info\n"); + err = -EFAULT; + goto fail; + } + } + + err = mpp_translate_reg_address(session->mpp, + &task->mpp_task, + fmt, + task->reg); + if (err) { + mpp_err("error: translate reg address failed.\n"); + mpp_dump_reg(task->reg, + task->hw_info->regidx_start, + task->hw_info->regidx_end); + goto fail; + } + /* + * special offset scale case + * + * This translation is for fd + offset translation. + * One register has 32bits. We need to transfer both buffer file + * handle and the start address offset so we packet file handle + * and offset together using below format. + * + * 0~9 bit for buffer file handle range 0 ~ 1023 + * 10~31 bit for offset range 0 ~ 4M + * + * But on 4K case the offset can be larger the 4M + */ + if (likely(fmt == VDPU1_FMT_H264D)) { + struct mpp_mem_region *mem_region = NULL; + dma_addr_t iova = 0; + u32 offset = task->reg[VDPU1_REG_DIR_MV_BASE_INDEX]; + int fd = task->reg[VDPU1_REG_DIR_MV_BASE_INDEX] & 0x3ff; + + offset = offset >> 10 << 4; + mem_region = mpp_task_attach_fd(&task->mpp_task, fd); + if (IS_ERR(mem_region)) { + err = PTR_ERR(mem_region); + goto fail; + } + + iova = mem_region->iova; + mpp_debug(DEBUG_IOMMU, "DMV[%3d]: %3d => %pad + offset %10d\n", + VDPU1_REG_DIR_MV_BASE_INDEX, fd, &iova, offset); + task->reg[VDPU1_REG_DIR_MV_BASE_INDEX] = iova + offset; + } + + task->strm_addr = task->reg[VDPU1_REG_STREAM_RLC_BASE_INDEX]; + + mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x", + task->ext_inf.cnt, task->ext_inf.magic); + mpp_translate_extra_info(&task->mpp_task, &task->ext_inf, task->reg); + + mpp_debug_leave(); + + return &task->mpp_task; + +fail: + mpp_task_finalize(session, &task->mpp_task); + kfree(task); + return ERR_PTR(err); +} + +static int vdpu_prepare(struct mpp_dev *mpp, + struct mpp_task *task) +{ + return -EINVAL; +} + +static int vdpu_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 regidx_en; + struct vdpu_task *task = NULL; + struct vdpu_dev *dec = NULL; + + mpp_debug_enter(); + + task = to_vdpu_task(mpp_task); + dec = to_vdpu_dev(mpp); + + /* FIXME: spin lock here */ + dec->current_task = task; + /* clear cache */ + mpp_write_relaxed(mpp, VDPU1_REG_CLR_CACHE_BASE, 0); + /* set registers for hardware */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + regidx_en = task->hw_info->regidx_en; + for (i = regidx_start; i <= regidx_end; i++) { + if (i == regidx_en) + continue; + mpp_write_relaxed(mpp, i * sizeof(u32), task->reg[i]); + } + /* Flush the registers */ + wmb(); + mpp_write(mpp, VDPU1_REG_DEC_INT_EN, + task->reg[regidx_en] | VDPU1_DEC_START); + + mpp_debug_leave(); + + return 0; +} + +static int vdpu_finish(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 dec_get; + s32 dec_length; + struct vdpu_task *task = to_vdpu_task(mpp_task); + + mpp_debug_enter(); + + /* read register after running */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + for (i = regidx_start; i <= regidx_end; i++) + task->reg[i] = mpp_read_relaxed(mpp, i * sizeof(u32)); + task->reg[VDPU1_REG_DEC_INT_EN_INDEX] = task->irq_status; + /* revert hack for decoded length */ + dec_get = task->reg[VDPU1_REG_STREAM_RLC_BASE_INDEX]; + dec_length = dec_get - task->strm_addr; + task->reg[VDPU1_REG_STREAM_RLC_BASE_INDEX] = dec_length << 10; + mpp_debug(DEBUG_REGISTER, + "dec_get %08x dec_length %d\n", + dec_get, dec_length); + + mpp_debug_leave(); + + return 0; +} + +static int vdpu_result(struct mpp_dev *mpp, + struct mpp_task *mpp_task, + u32 __user *dst, u32 size) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + /* FIXME may overflow the kernel */ + if (copy_to_user(dst, task->reg, size)) { + mpp_err("copy_to_user failed\n"); + return -EIO; + } + + return 0; +} + +static int vdpu_free_task(struct mpp_session *session, + struct mpp_task *mpp_task) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + mpp_task_finalize(session, mpp_task); + kfree(task); + + return 0; +} + +static int vdpu_debugfs_remove(struct mpp_dev *mpp) +{ +#ifdef CONFIG_DEBUG_FS + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + debugfs_remove_recursive(dec->debugfs); +#endif + return 0; +} + +static int vdpu_debugfs_init(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + dec->aclk_debug = 0; + dec->session_max_buffers_debug = 0; + +#ifdef CONFIG_DEBUG_FS + dec->debugfs = debugfs_create_dir(np->name, mpp->srv->debugfs); + if (IS_ERR_OR_NULL(dec->debugfs)) { + mpp_err("failed on open debugfs\n"); + dec->debugfs = NULL; + return -EIO; + } + debugfs_create_u32("aclk", 0644, + dec->debugfs, &dec->aclk_debug); + debugfs_create_u32("session_buffers", 0644, + dec->debugfs, &dec->session_max_buffers_debug); +#endif + if (dec->session_max_buffers_debug) + mpp->session_max_buffers = dec->session_max_buffers_debug; + + return 0; +} + +static int vdpu_init(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + mpp->grf_info = &mpp->srv->grf_infos[MPP_DRIVER_VDPU1]; + + dec->aclk = devm_clk_get(mpp->dev, "aclk_vcodec"); + if (IS_ERR_OR_NULL(dec->aclk)) { + mpp_err("failed on clk_get aclk_vcodec\n"); + dec->aclk = NULL; + } + dec->hclk = devm_clk_get(mpp->dev, "hclk_vcodec"); + if (IS_ERR_OR_NULL(dec->hclk)) { + mpp_err("failed on clk_get hclk_vcodec\n"); + dec->hclk = NULL; + } + + dec->rst_a = devm_reset_control_get_shared(mpp->dev, "video_a"); + if (IS_ERR_OR_NULL(dec->rst_a)) { + mpp_err("No aclk reset resource define\n"); + dec->rst_a = NULL; + } + dec->rst_h = devm_reset_control_get_shared(mpp->dev, "video_h"); + if (IS_ERR_OR_NULL(dec->rst_h)) { + mpp_err("No hclk reset resource define\n"); + dec->rst_h = NULL; + } + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + + return 0; +} + +static int vdpu_power_on(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_prepare_enable(dec->aclk); + if (dec->hclk) + clk_prepare_enable(dec->hclk); + + return 0; +} + +static int vdpu_power_off(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_disable_unprepare(dec->aclk); + if (dec->hclk) + clk_disable_unprepare(dec->hclk); + + return 0; +} + +static int vdpu_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + task->aclk_freq = 300; + + return 0; +} + +static int vdpu_set_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + struct vdpu_task *task = to_vdpu_task(mpp_task); + + /* check whether use debug freq */ + task->aclk_freq = dec->aclk_debug ? + dec->aclk_debug : task->aclk_freq; + + clk_set_rate(dec->aclk, task->aclk_freq * MHZ); + + return 0; +} + +static int vdpu_reduce_freq(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_set_rate(dec->aclk, 50 * MHZ); + + return 0; +} + +static int vdpu_irq(struct mpp_dev *mpp) +{ + mpp->irq_status = mpp_read(mpp, VDPU1_REG_DEC_INT_EN); + if (!(mpp->irq_status & VDPU1_DEC_INT_RAW)) + return IRQ_NONE; + + mpp_write(mpp, VDPU1_REG_DEC_INT_EN, 0); + /* set clock gating to save power */ + mpp_write(mpp, VDPU1_REG_DEC_EN, VDPU1_CLOCK_GATE_EN); + + return IRQ_WAKE_THREAD; +} + +static int vdpu_isr(struct mpp_dev *mpp) +{ + u32 err_mask; + struct vdpu_task *task = NULL; + struct mpp_task *mpp_task = NULL; + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + /* FIXME use a spin lock here */ + task = dec->current_task; + if (!task) { + dev_err(dec->mpp.dev, "no current task\n"); + return IRQ_HANDLED; + } + + mpp_task = &task->mpp_task; + mpp_time_diff(mpp_task); + dec->current_task = NULL; + task->irq_status = mpp->irq_status; + mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", + task->irq_status); + + err_mask = VDPU1_INT_TIMEOUT + | VDPU1_INT_STRM_ERROR + | VDPU1_INT_ASO_ERROR + | VDPU1_INT_BUF_EMPTY + | VDPU1_INT_BUS_ERROR; + + if (err_mask & task->irq_status) + atomic_inc(&mpp->reset_request); + + mpp_task_finish(mpp_task->session, mpp_task); + + mpp_debug_leave(); + + return IRQ_HANDLED; +} + +static int vdpu_reset(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->rst_a && dec->rst_h) { + mpp_debug(DEBUG_RESET, "reset in\n"); + + /* Don't skip this or iommu won't work after reset */ + rockchip_pmu_idle_request(mpp->dev, true); + mpp_safe_reset(dec->rst_a); + mpp_safe_reset(dec->rst_h); + udelay(5); + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + rockchip_pmu_idle_request(mpp->dev, false); + + mpp_debug(DEBUG_RESET, "reset out\n"); + } + mpp_write(mpp, VDPU1_REG_DEC_INT_EN, 0); + + return 0; +} + +static struct mpp_hw_ops vdpu_v1_hw_ops = { + .init = vdpu_init, + .power_on = vdpu_power_on, + .power_off = vdpu_power_off, + .get_freq = vdpu_get_freq, + .set_freq = vdpu_set_freq, + .reduce_freq = vdpu_reduce_freq, + .reset = vdpu_reset, +}; + +static struct mpp_dev_ops vdpu_v1_dev_ops = { + .alloc_task = vdpu_alloc_task, + .prepare = vdpu_prepare, + .run = vdpu_run, + .irq = vdpu_irq, + .isr = vdpu_isr, + .finish = vdpu_finish, + .result = vdpu_result, + .free_task = vdpu_free_task, +}; + +static const struct mpp_dev_var vdpu_v1_data = { + .device_type = MPP_DEVICE_DEC, + .hw_info = &vdpu_v1_hw_info, + .trans_info = vdpu_v1_trans, + .hw_ops = &vdpu_v1_hw_ops, + .dev_ops = &vdpu_v1_dev_ops, +}; + +static const struct mpp_dev_var avsd_plus_data = { + .device_type = MPP_DEVICE_DEC_AVSPLUS, + .hw_info = &vdpu_v1_hw_info, + .trans_info = vdpu_v1_trans, + .hw_ops = &vdpu_v1_hw_ops, + .dev_ops = &vdpu_v1_dev_ops, +}; + +static const struct of_device_id mpp_vdpu1_dt_match[] = { + { + .compatible = "rockchip,vpu-decoder-v1", + .data = &vdpu_v1_data, + }, + { + .compatible = "rockchip,avs-plus-decoder", + .data = &avsd_plus_data, + }, + {}, +}; + +static int vdpu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = NULL; + struct mpp_dev *mpp = NULL; + const struct of_device_id *match = NULL; + int ret = 0; + + dev_info(dev, "probe device\n"); + dec = devm_kzalloc(dev, sizeof(struct vdpu_dev), GFP_KERNEL); + if (!dec) + return -ENOMEM; + platform_set_drvdata(pdev, dec); + + mpp = &dec->mpp; + if (pdev->dev.of_node) { + match = of_match_node(mpp_vdpu1_dt_match, pdev->dev.of_node); + if (match) + mpp->var = (struct mpp_dev_var *)match->data; + } + + ret = mpp_dev_probe(mpp, pdev); + if (ret) { + dev_err(dev, "probe sub driver failed\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mpp->irq, + mpp_dev_irq, + mpp_dev_isr_sched, + IRQF_SHARED, + dev_name(dev), mpp); + if (ret) { + dev_err(dev, "register interrupter runtime failed\n"); + return -EINVAL; + } + + if (mpp->var->device_type == MPP_DEVICE_DEC) { + mpp->srv->sub_devices[MPP_DEVICE_PP] = mpp; + mpp->srv->sub_devices[MPP_DEVICE_DEC_PP] = mpp; + } + mpp->session_max_buffers = VDPU1_SESSION_MAX_BUFFERS; + vdpu_debugfs_init(mpp); + dev_info(dev, "probing finish\n"); + + return 0; +} + +static int vdpu_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + mpp_dev_remove(&dec->mpp); + vdpu_debugfs_remove(&dec->mpp); + + return 0; +} + +static void vdpu_shutdown(struct platform_device *pdev) +{ + int ret; + int val; + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = platform_get_drvdata(pdev); + struct mpp_dev *mpp = &dec->mpp; + + dev_info(dev, "shutdown device\n"); + + atomic_inc(&mpp->srv->shutdown_request); + ret = readx_poll_timeout(atomic_read, + &mpp->total_running, + val, val == 0, 20000, 200000); + if (ret == -ETIMEDOUT) + dev_err(dev, "wait total running time out\n"); +} + +struct platform_driver rockchip_vdpu1_driver = { + .probe = vdpu_probe, + .remove = vdpu_remove, + .shutdown = vdpu_shutdown, + .driver = { + .name = VDPU1_DRIVER_NAME, + .of_match_table = of_match_ptr(mpp_vdpu1_dt_match), + }, +}; +EXPORT_SYMBOL(rockchip_vdpu1_driver); diff --git a/drivers/video/rockchip/mpp/mpp_vdpu2.c b/drivers/video/rockchip/mpp/mpp_vdpu2.c new file mode 100644 index 000000000000..24af5cdb523c --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_vdpu2.c @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define VDPU2_DRIVER_NAME "mpp_vdpu2" + +#define VDPU2_SESSION_MAX_BUFFERS 40 +/* The maximum registers number of all the version */ +#define VDPU2_REG_NUM 159 +#define VDPU2_REG_START_INDEX 50 +#define VDPU2_REG_END_INDEX 158 + +#define VDPU2_REG_SYS_CTRL 0x0d4 +#define VDPU2_REG_SYS_CTRL_INDEX (53) +#define VDPU2_GET_FORMAT(x) ((x) & 0xf) +#define VDPU2_FMT_H264D 0 +#define VDPU2_FMT_MPEG4D 1 +#define VDPU2_FMT_H263D 2 +#define VDPU2_FMT_JPEGD 3 +#define VDPU2_FMT_VC1D 4 +#define VDPU2_FMT_MPEG2D 5 +#define VDPU2_FMT_MPEG1D 6 +#define VDPU2_FMT_VP6D 7 +#define VDPU2_FMT_RESERVED 8 +#define VDPU2_FMT_VP7D 9 +#define VDPU2_FMT_VP8D 10 +#define VDPU2_FMT_AVSD 11 + +#define VDPU2_REG_DEC_INT 0x0dc +#define VDPU2_REG_DEC_INT_INDEX (55) +#define VDPU2_INT_TIMEOUT BIT(13) +#define VDPU2_INT_STRM_ERROR BIT(12) +#define VDPU2_INT_SLICE BIT(9) +#define VDPU2_INT_ASO_ERROR BIT(8) +#define VDPU2_INT_BUF_EMPTY BIT(6) +#define VDPU2_INT_BUS_ERROR BIT(5) +#define VDPU2_DEC_INT BIT(4) +#define VDPU2_DEC_IRQ_DIS BIT(1) +#define VDPU2_DEC_INT_RAW BIT(0) + +#define VDPU2_REG_DEC_EN 0x0e4 +#define VDPU2_REG_DEC_EN_INDEX (57) +#define VDPU2_DEC_CLOCK_GATE_EN BIT(4) +#define VDPU2_DEC_START BIT(0) + +#define VDPU2_REG_DIR_MV_BASE 0x0f8 +#define VDPU2_REG_DIR_MV_BASE_INDEX (62) + +#define VDPU2_REG_STREAM_RLC_BASE 0x100 +#define VDPU2_REG_STREAM_RLC_BASE_INDEX (64) + +#define VDPU2_REG_CLR_CACHE_BASE 0x810 + +#define to_vdpu_task(task) \ + container_of(task, struct vdpu_task, mpp_task) +#define to_vdpu_dev(dev) \ + container_of(dev, struct vdpu_dev, mpp) + +struct vdpu_task { + struct mpp_task mpp_task; + struct mpp_hw_info *hw_info; + /* enable of post process */ + bool pp_enable; + + unsigned long aclk_freq; + u32 reg[VDPU2_REG_NUM]; + u32 idx; + struct extra_info_for_iommu ext_inf; + u32 strm_addr; + u32 irq_status; +}; + +struct vdpu_dev { + struct mpp_dev mpp; + + struct clk *aclk; + struct clk *hclk; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + u32 aclk_debug; + u32 session_max_buffers_debug; + + struct reset_control *rst_a; + struct reset_control *rst_h; + struct vdpu_task *current_task; +}; + +static struct mpp_hw_info vdpu_v2_hw_info = { + .reg_num = VDPU2_REG_NUM, + .regidx_start = VDPU2_REG_START_INDEX, + .regidx_end = VDPU2_REG_END_INDEX, + .regidx_en = VDPU2_REG_DEC_EN_INDEX, +}; + +/* + * file handle translate information + */ +static const char trans_tbl_default[] = { + 61, 62, 63, 64, 131, 134, 135, 148 +}; + +static const char trans_tbl_jpegd[] = { + 21, 22, 61, 63, 64, 131 +}; + +static const char trans_tbl_h264d[] = { + 61, 63, 64, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99 +}; + +static const char trans_tbl_vc1d[] = { + 62, 63, 64, 131, 134, 135, 145, 148 +}; + +static const char trans_tbl_vp6d[] = { + 61, 63, 64, 131, 136, 145 +}; + +static const char trans_tbl_vp8d[] = { + 61, 63, 64, 131, 136, 137, 140, 141, 142, 143, 144, 145, 146, 147, 149 +}; + +static struct mpp_trans_info vdpu_v2_trans[] = { + [VDPU2_FMT_H264D] = { + .count = sizeof(trans_tbl_h264d), + .table = trans_tbl_h264d, + }, + [VDPU2_FMT_H263D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU2_FMT_MPEG4D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU2_FMT_JPEGD] = { + .count = sizeof(trans_tbl_jpegd), + .table = trans_tbl_jpegd, + }, + [VDPU2_FMT_VC1D] = { + .count = sizeof(trans_tbl_vc1d), + .table = trans_tbl_vc1d, + }, + [VDPU2_FMT_MPEG2D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU2_FMT_MPEG1D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU2_FMT_VP6D] = { + .count = sizeof(trans_tbl_vp6d), + .table = trans_tbl_vp6d, + }, + [VDPU2_FMT_RESERVED] = { + .count = 0, + .table = NULL, + }, + [VDPU2_FMT_VP7D] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VDPU2_FMT_VP8D] = { + .count = sizeof(trans_tbl_vp8d), + .table = trans_tbl_vp8d, + }, + [VDPU2_FMT_AVSD] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, +}; + +static void *vdpu_alloc_task(struct mpp_session *session, + void __user *src, u32 size) +{ + u32 reg_len; + u32 extinf_len; + u32 fmt = 0; + int err = -EFAULT; + struct vdpu_task *task = NULL; + u32 dwsize = size / sizeof(u32); + struct mpp_dev *mpp = session->mpp; + + mpp_debug_enter(); + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (!task) + return NULL; + + mpp_task_init(session, &task->mpp_task); + + task->hw_info = mpp->var->hw_info; + reg_len = min(task->hw_info->reg_num, dwsize); + extinf_len = dwsize > reg_len ? (dwsize - reg_len) * 4 : 0; + + if (copy_from_user(task->reg, src, reg_len * 4)) { + mpp_err("error: copy_from_user failed in reg_init\n"); + err = -EFAULT; + goto fail; + } + + fmt = VDPU2_GET_FORMAT(task->reg[VDPU2_REG_SYS_CTRL_INDEX]); + if (extinf_len > 0) { + if (likely(fmt == VDPU2_FMT_JPEGD)) { + err = copy_from_user(&task->ext_inf, + (u8 *)src + size + - JPEG_IOC_EXTRA_SIZE, + JPEG_IOC_EXTRA_SIZE); + } else { + u32 ext_cpy = min_t(size_t, extinf_len, + sizeof(task->ext_inf)); + err = copy_from_user(&task->ext_inf, + (u32 *)src + reg_len, ext_cpy); + } + + if (err) { + mpp_err("copy_from_user failed when extra info\n"); + err = -EFAULT; + goto fail; + } + } + + err = mpp_translate_reg_address(session->mpp, + &task->mpp_task, + fmt, task->reg); + if (err) { + mpp_err("error: translate reg address failed.\n"); + mpp_dump_reg(task->reg, + task->hw_info->regidx_start, + task->hw_info->regidx_end); + goto fail; + } + + if (likely(fmt == VDPU2_FMT_H264D)) { + struct mpp_mem_region *mem_region = NULL; + dma_addr_t iova = 0; + u32 offset = task->reg[VDPU2_REG_DIR_MV_BASE_INDEX]; + int fd = task->reg[VDPU2_REG_DIR_MV_BASE_INDEX] & 0x3ff; + + offset = offset >> 10 << 4; + mem_region = mpp_task_attach_fd(&task->mpp_task, fd); + if (IS_ERR(mem_region)) { + err = PTR_ERR(mem_region); + goto fail; + } + + iova = mem_region->iova; + mpp_debug(DEBUG_IOMMU, "DMV[%3d]: %3d => %pad + offset %10d\n", + VDPU2_REG_DIR_MV_BASE_INDEX, fd, &iova, offset); + task->reg[VDPU2_REG_DIR_MV_BASE_INDEX] = iova + offset; + } + + task->strm_addr = task->reg[VDPU2_REG_STREAM_RLC_BASE_INDEX]; + + mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x", + task->ext_inf.cnt, task->ext_inf.magic); + mpp_translate_extra_info(&task->mpp_task, &task->ext_inf, task->reg); + + mpp_debug_leave(); + + return &task->mpp_task; + +fail: + mpp_task_finalize(session, &task->mpp_task); + kfree(task); + return ERR_PTR(err); +} + +static int vdpu_prepare(struct mpp_dev *mpp, + struct mpp_task *task) +{ + return -EINVAL; +} + +static int vdpu_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 regidx_en; + struct vdpu_task *task = NULL; + struct vdpu_dev *dec = NULL; + + mpp_debug_enter(); + + task = to_vdpu_task(mpp_task); + dec = to_vdpu_dev(mpp); + + /* FIXME: spin lock here */ + dec->current_task = task; + + /* clear cache */ + mpp_write_relaxed(mpp, VDPU2_REG_CLR_CACHE_BASE, 0); + /* set registers for hardware */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + regidx_en = task->hw_info->regidx_en; + for (i = regidx_start; i <= regidx_end; i++) { + if (i == regidx_en) + continue; + mpp_write_relaxed(mpp, i * sizeof(u32), task->reg[i]); + } + /* Flush the registers */ + wmb(); + mpp_write(mpp, VDPU2_REG_DEC_EN, + task->reg[regidx_en] | VDPU2_DEC_START); + + mpp_debug_leave(); + + return 0; +} + +static int vdpu_finish(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 dec_get; + s32 dec_length; + struct vdpu_task *task = to_vdpu_task(mpp_task); + + mpp_debug_enter(); + + /* read register after running */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + for (i = regidx_start; i <= regidx_end; i++) + task->reg[i] = mpp_read_relaxed(mpp, i * sizeof(u32)); + task->reg[VDPU2_REG_DEC_INT_INDEX] = task->irq_status; + /* revert hack for decoded length */ + dec_get = task->reg[VDPU2_REG_STREAM_RLC_BASE_INDEX]; + dec_length = dec_get - task->strm_addr; + task->reg[VDPU2_REG_STREAM_RLC_BASE_INDEX] = dec_length << 10; + mpp_debug(DEBUG_REGISTER, + "dec_get %08x dec_length %d\n", + dec_get, dec_length); + + mpp_debug_leave(); + + return 0; +} + +static int vdpu_result(struct mpp_dev *mpp, + struct mpp_task *mpp_task, + u32 __user *dst, u32 size) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + /* FIXME may overflow the kernel */ + if (copy_to_user(dst, task->reg, size)) { + mpp_err("copy_to_user failed\n"); + return -EIO; + } + + return 0; +} + +static int vdpu_free_task(struct mpp_session *session, + struct mpp_task *mpp_task) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + mpp_task_finalize(session, mpp_task); + kfree(task); + + return 0; +} + +static int vdpu_debugfs_remove(struct mpp_dev *mpp) +{ +#ifdef CONFIG_DEBUG_FS + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + debugfs_remove_recursive(dec->debugfs); +#endif + return 0; +} + +static int vdpu_debugfs_init(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + dec->aclk_debug = 0; + dec->session_max_buffers_debug = 0; +#ifdef CONFIG_DEBUG_FS + dec->debugfs = debugfs_create_dir(np->name, mpp->srv->debugfs); + if (IS_ERR_OR_NULL(dec->debugfs)) { + mpp_err("failed on open debugfs\n"); + dec->debugfs = NULL; + return -EIO; + } + debugfs_create_u32("aclk", 0644, + dec->debugfs, &dec->aclk_debug); + debugfs_create_u32("session_buffers", 0644, + dec->debugfs, &dec->session_max_buffers_debug); +#endif + if (dec->session_max_buffers_debug) + mpp->session_max_buffers = dec->session_max_buffers_debug; + + return 0; +} + +static int vdpu_init(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + mpp->grf_info = &mpp->srv->grf_infos[MPP_DRIVER_VDPU2]; + + dec->aclk = devm_clk_get(mpp->dev, "aclk_vcodec"); + if (IS_ERR(dec->aclk)) { + mpp_err("failed on clk_get aclk_vcodec\n"); + dec->aclk = NULL; + } + dec->hclk = devm_clk_get(mpp->dev, "hclk_vcodec"); + if (IS_ERR(dec->hclk)) { + mpp_err("failed on clk_get hclk_vcodec\n"); + dec->hclk = NULL; + } + + dec->rst_a = devm_reset_control_get_shared(mpp->dev, "video_a"); + if (IS_ERR_OR_NULL(dec->rst_a)) { + mpp_err("No aclk reset resource define\n"); + dec->rst_a = NULL; + } + dec->rst_h = devm_reset_control_get_shared(mpp->dev, "video_h"); + if (IS_ERR_OR_NULL(dec->rst_h)) { + mpp_err("No hclk reset resource define\n"); + dec->rst_h = NULL; + } + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + + return 0; +} + +static int vdpu_power_on(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_prepare_enable(dec->aclk); + if (dec->hclk) + clk_prepare_enable(dec->hclk); + + return 0; +} + +static int vdpu_power_off(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_disable_unprepare(dec->aclk); + if (dec->hclk) + clk_disable_unprepare(dec->hclk); + + return 0; +} + +static int vdpu_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vdpu_task *task = to_vdpu_task(mpp_task); + + task->aclk_freq = 300; + + return 0; +} + +static int vdpu_set_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + struct vdpu_task *task = to_vdpu_task(mpp_task); + + /* check whether use debug freq */ + task->aclk_freq = dec->aclk_debug ? + dec->aclk_debug : task->aclk_freq; + + clk_set_rate(dec->aclk, task->aclk_freq * MHZ); + + return 0; +} + +static int vdpu_reduce_freq(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + if (dec->aclk) + clk_set_rate(dec->aclk, 50 * MHZ); + + return 0; +} + +static int vdpu_irq(struct mpp_dev *mpp) +{ + mpp->irq_status = mpp_read(mpp, VDPU2_REG_DEC_INT); + if (!(mpp->irq_status & VDPU2_DEC_INT_RAW)) + return IRQ_NONE; + + mpp_write(mpp, VDPU2_REG_DEC_INT, 0); + /* set clock gating to save power */ + mpp_write(mpp, VDPU2_REG_DEC_EN, VDPU2_DEC_CLOCK_GATE_EN); + + return IRQ_WAKE_THREAD; +} + +static int vdpu_isr(struct mpp_dev *mpp) +{ + u32 err_mask; + struct vdpu_task *task = NULL; + struct mpp_task *mpp_task = NULL; + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + /* FIXME use a spin lock here */ + task = dec->current_task; + if (!task) { + dev_err(dec->mpp.dev, "no current task\n"); + return IRQ_HANDLED; + } + + mpp_task = &task->mpp_task; + mpp_time_diff(mpp_task); + dec->current_task = NULL; + task->irq_status = mpp->irq_status; + mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", + task->irq_status); + + err_mask = VDPU2_INT_TIMEOUT + | VDPU2_INT_STRM_ERROR + | VDPU2_INT_ASO_ERROR + | VDPU2_INT_BUF_EMPTY + | VDPU2_INT_BUS_ERROR; + + if (err_mask & task->irq_status) + atomic_inc(&mpp->reset_request); + + mpp_task_finish(mpp_task->session, mpp_task); + + mpp_debug_leave(); + + return IRQ_HANDLED; +} + +static int vdpu_reset(struct mpp_dev *mpp) +{ + struct vdpu_dev *dec = to_vdpu_dev(mpp); + + mpp_write(mpp, VDPU2_REG_DEC_EN, 0); + mpp_write(mpp, VDPU2_REG_DEC_INT, 0); + if (dec->rst_a && dec->rst_h) { + /* Don't skip this or iommu won't work after reset */ + rockchip_pmu_idle_request(mpp->dev, true); + mpp_safe_reset(dec->rst_a); + mpp_safe_reset(dec->rst_h); + udelay(5); + mpp_safe_unreset(dec->rst_a); + mpp_safe_unreset(dec->rst_h); + rockchip_pmu_idle_request(mpp->dev, false); + } + + return 0; +} + +static struct mpp_hw_ops vdpu_v2_hw_ops = { + .init = vdpu_init, + .power_on = vdpu_power_on, + .power_off = vdpu_power_off, + .get_freq = vdpu_get_freq, + .set_freq = vdpu_set_freq, + .reduce_freq = vdpu_reduce_freq, + .reset = vdpu_reset, +}; + +static struct mpp_dev_ops vdpu_v2_dev_ops = { + .alloc_task = vdpu_alloc_task, + .prepare = vdpu_prepare, + .run = vdpu_run, + .irq = vdpu_irq, + .isr = vdpu_isr, + .finish = vdpu_finish, + .result = vdpu_result, + .free_task = vdpu_free_task, +}; + +static const struct mpp_dev_var vdpu_v2_data = { + .device_type = MPP_DEVICE_DEC, + .hw_info = &vdpu_v2_hw_info, + .trans_info = vdpu_v2_trans, + .hw_ops = &vdpu_v2_hw_ops, + .dev_ops = &vdpu_v2_dev_ops, +}; + +static const struct of_device_id mpp_vdpu2_dt_match[] = { + { + .compatible = "rockchip,vpu-decoder-v2", + .data = &vdpu_v2_data, + }, + {}, +}; + +static int vdpu_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = NULL; + struct mpp_dev *mpp = NULL; + const struct of_device_id *match = NULL; + + dev_info(dev, "probe device\n"); + dec = devm_kzalloc(dev, sizeof(struct vdpu_dev), GFP_KERNEL); + if (!dec) + return -ENOMEM; + platform_set_drvdata(pdev, dec); + + mpp = &dec->mpp; + + if (pdev->dev.of_node) { + match = of_match_node(mpp_vdpu2_dt_match, + pdev->dev.of_node); + if (match) + mpp->var = (struct mpp_dev_var *)match->data; + } + + ret = mpp_dev_probe(mpp, pdev); + if (ret) { + dev_err(dev, "probe sub driver failed\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mpp->irq, + mpp_dev_irq, + mpp_dev_isr_sched, + IRQF_SHARED, + dev_name(dev), mpp); + if (ret) { + dev_err(dev, "register interrupter runtime failed\n"); + return -EINVAL; + } + + if (mpp->var->device_type == MPP_DEVICE_DEC) { + mpp->srv->sub_devices[MPP_DEVICE_PP] = mpp; + mpp->srv->sub_devices[MPP_DEVICE_DEC_PP] = mpp; + } + mpp->session_max_buffers = VDPU2_SESSION_MAX_BUFFERS; + vdpu_debugfs_init(mpp); + dev_info(dev, "probing finish\n"); + + return 0; +} + +static int vdpu_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + mpp_dev_remove(&dec->mpp); + vdpu_debugfs_remove(&dec->mpp); + + return 0; +} + +static void vdpu_shutdown(struct platform_device *pdev) +{ + int ret; + int val; + struct device *dev = &pdev->dev; + struct vdpu_dev *dec = platform_get_drvdata(pdev); + struct mpp_dev *mpp = &dec->mpp; + + dev_info(dev, "shutdown device\n"); + + atomic_inc(&mpp->srv->shutdown_request); + ret = readx_poll_timeout(atomic_read, + &mpp->total_running, + val, val == 0, 20000, 200000); + if (ret == -ETIMEDOUT) + dev_err(dev, "wait total running time out\n"); +} + +struct platform_driver rockchip_vdpu2_driver = { + .probe = vdpu_probe, + .remove = vdpu_remove, + .shutdown = vdpu_shutdown, + .driver = { + .name = VDPU2_DRIVER_NAME, + .of_match_table = of_match_ptr(mpp_vdpu2_dt_match), + }, +}; +EXPORT_SYMBOL(rockchip_vdpu2_driver); diff --git a/drivers/video/rockchip/mpp/mpp_vepu1.c b/drivers/video/rockchip/mpp/mpp_vepu1.c new file mode 100644 index 000000000000..cdbc982df5f8 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_vepu1.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define VEPU1_DRIVER_NAME "mpp_vepu1" + +#define VEPU1_SESSION_MAX_BUFFERS 20 +/* The maximum registers number of all the version */ +#define VEPU1_REG_NUM 164 +#define VEPU1_REG_START_INDEX 0 +#define VEPU1_REG_END_INDEX 163 + +#define VEPU1_REG_INT 0x004 +#define VEPU1_REG_INT_INDEX (1) +#define VEPU1_INT_SLICE BIT(8) +#define VEPU1_INT_TIMEOUT BIT(6) +#define VEPU1_INT_BUF_FULL BIT(5) +#define VEPU1_INT_RESET BIT(4) +#define VEPU1_INT_BUS_ERROR BIT(3) +#define VEPU1_INT_RDY BIT(2) +#define VEPU1_IRQ_DIS BIT(1) +#define VEPU1_INT_RAW BIT(0) + +#define VEPU1_REG_ENC_EN 0x038 +#define VEPU1_REG_ENC_EN_INDEX (14) +#define VEPU1_INT_TIMEOUT_EN BIT(31) +#define VEPU1_INT_SLICE_EN BIT(28) +#define VEPU1_ENC_START BIT(0) + +#define VEPU1_GET_FORMAT(x) (((x) >> 1) & 0x3) +#define VEPU1_FORMAT_MASK (0x06) + +#define VEPU1_FMT_RESERVED (0) +#define VEPU1_FMT_VP8E (1) +#define VEPU1_FMT_JPEGE (2) +#define VEPU1_FMT_H264E (3) + +#define VEPU1_REG_CLR_CACHE_BASE 0xc10 + +#define to_vepu_task(task) \ + container_of(task, struct vepu_task, mpp_task) +#define to_vepu_dev(dev) \ + container_of(dev, struct vepu_dev, mpp) + +struct vepu_task { + struct mpp_task mpp_task; + struct mpp_hw_info *hw_info; + + unsigned long aclk_freq; + u32 reg[VEPU1_REG_NUM]; + u32 idx; + struct extra_info_for_iommu ext_inf; + u32 irq_status; +}; + +struct vepu_dev { + struct mpp_dev mpp; + + struct clk *aclk; + struct clk *hclk; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + u32 aclk_debug; + u32 session_max_buffers_debug; + + struct reset_control *rst_a; + struct reset_control *rst_h; + + struct vepu_task *current_task; +}; + +static struct mpp_hw_info vepu_v1_hw_info = { + .reg_num = VEPU1_REG_NUM, + .regidx_start = VEPU1_REG_START_INDEX, + .regidx_end = VEPU1_REG_END_INDEX, + .regidx_en = VEPU1_REG_ENC_EN_INDEX, +}; + +/* + * file handle translate information + */ +static const char trans_tbl_default[] = { + 5, 6, 7, 8, 9, 10, 11, 12, 13, 51 +}; + +static const char trans_tbl_vp8e[] = { + 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 26, 51, 52, 58, 59 +}; + +static struct mpp_trans_info trans_rk_vepu1[] = { + [VEPU1_FMT_RESERVED] = { + .count = 0, + .table = NULL, + }, + [VEPU1_FMT_VP8E] = { + .count = sizeof(trans_tbl_vp8e), + .table = trans_tbl_vp8e, + }, + [VEPU1_FMT_JPEGE] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VEPU1_FMT_H264E] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, +}; + +static void *vepu_alloc_task(struct mpp_session *session, + void __user *src, u32 size) +{ + u32 reg_len; + u32 extinf_len; + u32 fmt = 0; + int err = -EFAULT; + struct vepu_task *task = NULL; + u32 dwsize = size / sizeof(u32); + struct mpp_dev *mpp = session->mpp; + + mpp_debug_enter(); + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (!task) + return NULL; + + mpp_task_init(session, &task->mpp_task); + + task->hw_info = mpp->var->hw_info; + reg_len = min(task->hw_info->reg_num, dwsize); + extinf_len = dwsize > reg_len ? (dwsize - reg_len) * 4 : 0; + + if (copy_from_user(task->reg, src, reg_len * 4)) { + mpp_err("error: copy_from_user failed in reg_init\n"); + err = -EFAULT; + goto fail; + } + + fmt = VEPU1_GET_FORMAT(task->reg[VEPU1_REG_ENC_EN_INDEX]); + if (extinf_len > 0) { + if (likely(fmt == VEPU1_FMT_JPEGE)) { + err = copy_from_user(&task->ext_inf, + (u8 *)src + size + - JPEG_IOC_EXTRA_SIZE, + JPEG_IOC_EXTRA_SIZE); + } else { + u32 ext_cpy = min_t(size_t, extinf_len, + sizeof(task->ext_inf)); + err = copy_from_user(&task->ext_inf, + (u32 *)src + reg_len, ext_cpy); + } + + if (err) { + mpp_err("copy_from_user failed when extra info\n"); + err = -EFAULT; + goto fail; + } + } + + err = mpp_translate_reg_address(session->mpp, + &task->mpp_task, + fmt, task->reg); + if (err) { + mpp_err("error: translate reg address failed.\n"); + mpp_dump_reg(task->reg, + task->hw_info->regidx_start, + task->hw_info->regidx_end); + goto fail; + } + + mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x", + task->ext_inf.cnt, task->ext_inf.magic); + mpp_translate_extra_info(&task->mpp_task, + &task->ext_inf, + task->reg); + + mpp_debug_leave(); + + return &task->mpp_task; + +fail: + mpp_task_finalize(session, &task->mpp_task); + kfree(task); + return ERR_PTR(err); +} + +static int vepu_prepare(struct mpp_dev *mpp, + struct mpp_task *task) +{ + return -EINVAL; +} + +static int vepu_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 regidx_en; + struct vepu_task *task = NULL; + struct vepu_dev *enc = NULL; + + mpp_debug_enter(); + + task = to_vepu_task(mpp_task); + enc = to_vepu_dev(mpp); + + /* FIXME: spin lock here */ + enc->current_task = task; + /* clear cache */ + mpp_write_relaxed(mpp, VEPU1_REG_CLR_CACHE_BASE, 0); + /* set registers for hardware */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + regidx_en = task->hw_info->regidx_en; + /* First, flush correct encoder format */ + mpp_write_relaxed(mpp, VEPU1_REG_ENC_EN, + task->reg[regidx_en] & VEPU1_FORMAT_MASK); + /* Second, flush others register */ + for (i = regidx_start; i <= regidx_end; i++) { + if (i == regidx_en) + continue; + mpp_write_relaxed(mpp, i * sizeof(u32), task->reg[i]); + } + /* Last, flush start registers */ + wmb(); + mpp_write(mpp, VEPU1_REG_ENC_EN, + task->reg[regidx_en] | VEPU1_ENC_START); + + mpp_debug_leave(); + + return 0; +} + +static int vepu_irq(struct mpp_dev *mpp) +{ + mpp->irq_status = mpp_read(mpp, VEPU1_REG_INT); + if (!(mpp->irq_status & VEPU1_INT_RAW)) + return IRQ_NONE; + + mpp_write(mpp, VEPU1_REG_INT, 0); + + return IRQ_WAKE_THREAD; +} + +static int vepu_isr(struct mpp_dev *mpp) +{ + u32 err_mask; + struct vepu_task *task = NULL; + struct mpp_task *mpp_task = NULL; + struct vepu_dev *enc = to_vepu_dev(mpp); + + /* FIXME use a spin lock here */ + task = enc->current_task; + if (!task) { + dev_err(enc->mpp.dev, "no current task\n"); + return IRQ_HANDLED; + } + + mpp_task = &task->mpp_task; + mpp_time_diff(mpp_task); + enc->current_task = NULL; + task->irq_status = mpp->irq_status; + mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", + task->irq_status); + + err_mask = VEPU1_INT_TIMEOUT + | VEPU1_INT_BUF_FULL + | VEPU1_INT_BUS_ERROR; + + if (err_mask & task->irq_status) + atomic_inc(&mpp->reset_request); + + mpp_task_finish(mpp_task->session, mpp_task); + + mpp_debug_leave(); + return IRQ_HANDLED; +} + +static int vepu_finish(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + struct vepu_task *task = to_vepu_task(mpp_task); + + mpp_debug_enter(); + /* read register after running */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + for (i = regidx_start; i <= regidx_end; i++) + task->reg[i] = mpp_read_relaxed(mpp, i * sizeof(u32)); + task->reg[VEPU1_REG_INT_INDEX] = task->irq_status; + + mpp_debug_leave(); + + return 0; +} + +static int vepu_result(struct mpp_dev *mpp, + struct mpp_task *mpp_task, + u32 __user *dst, u32 size) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + /* FIXME may overflow the kernel */ + if (copy_to_user(dst, task->reg, size)) { + mpp_err("copy_to_user failed\n"); + return -EIO; + } + + return 0; +} + +static int vepu_free_task(struct mpp_session *session, + struct mpp_task *mpp_task) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + mpp_task_finalize(session, mpp_task); + kfree(task); + + return 0; +} + +static int vepu_debugfs_remove(struct mpp_dev *mpp) +{ +#ifdef CONFIG_DEBUG_FS + struct vepu_dev *enc = to_vepu_dev(mpp); + + debugfs_remove_recursive(enc->debugfs); +#endif + return 0; +} + +static int vepu_debugfs_init(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + enc->aclk_debug = 0; + enc->session_max_buffers_debug = 0; +#ifdef CONFIG_DEBUG_FS + enc->debugfs = debugfs_create_dir(np->name, mpp->srv->debugfs); + if (IS_ERR_OR_NULL(enc->debugfs)) { + mpp_err("failed on open debugfs\n"); + enc->debugfs = NULL; + return -EIO; + } + debugfs_create_u32("aclk", 0644, + enc->debugfs, &enc->aclk_debug); + debugfs_create_u32("session_buffers", 0644, + enc->debugfs, &enc->session_max_buffers_debug); +#endif + if (enc->session_max_buffers_debug) + mpp->session_max_buffers = enc->session_max_buffers_debug; + + return 0; +} + +static int vepu_init(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + mpp->grf_info = &mpp->srv->grf_infos[MPP_DRIVER_VEPU1]; + + enc->aclk = devm_clk_get(mpp->dev, "aclk_vcodec"); + if (IS_ERR(enc->aclk)) { + mpp_err("failed on clk_get aclk_vcodec\n"); + enc->aclk = NULL; + } + enc->hclk = devm_clk_get(mpp->dev, "hclk_vcodec"); + if (IS_ERR(enc->hclk)) { + mpp_err("failed on clk_get hclk_vcodec\n"); + enc->hclk = NULL; + } + + enc->rst_a = devm_reset_control_get_shared(mpp->dev, "video_a"); + if (IS_ERR_OR_NULL(enc->rst_a)) { + mpp_err("No aclk reset resource define\n"); + enc->rst_a = NULL; + } + enc->rst_h = devm_reset_control_get_shared(mpp->dev, "video_h"); + if (IS_ERR_OR_NULL(enc->rst_h)) { + mpp_err("No hclk reset resource define\n"); + enc->rst_h = NULL; + } + mpp_safe_unreset(enc->rst_a); + mpp_safe_unreset(enc->rst_h); + + return 0; +} + +static int vepu_power_on(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_prepare_enable(enc->aclk); + if (enc->hclk) + clk_prepare_enable(enc->hclk); + + return 0; +} + +static int vepu_power_off(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_disable_unprepare(enc->aclk); + if (enc->hclk) + clk_disable_unprepare(enc->hclk); + + return 0; +} + +static int vepu_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + task->aclk_freq = 300; + + return 0; +} + +static int vepu_set_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + struct vepu_task *task = to_vepu_task(mpp_task); + + /* check whether use debug freq */ + task->aclk_freq = enc->aclk_debug ? + enc->aclk_debug : task->aclk_freq; + + clk_set_rate(enc->aclk, task->aclk_freq * MHZ); + + return 0; +} + +static int vepu_reduce_freq(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_set_rate(enc->aclk, 50 * MHZ); + + return 0; +} + +static int vepu_reset(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->rst_a && enc->rst_h) { + /* Don't skip this or iommu won't work after reset */ + rockchip_pmu_idle_request(mpp->dev, true); + mpp_safe_reset(enc->rst_a); + mpp_safe_reset(enc->rst_h); + udelay(5); + mpp_safe_unreset(enc->rst_a); + mpp_safe_unreset(enc->rst_h); + rockchip_pmu_idle_request(mpp->dev, false); + } + mpp_write(mpp, VEPU1_REG_ENC_EN, 0); + + return 0; +} + +static struct mpp_hw_ops vepu_v1_hw_ops = { + .init = vepu_init, + .power_on = vepu_power_on, + .power_off = vepu_power_off, + .get_freq = vepu_get_freq, + .set_freq = vepu_set_freq, + .reduce_freq = vepu_reduce_freq, + .reset = vepu_reset, +}; + +static struct mpp_dev_ops vepu_v1_dev_ops = { + .alloc_task = vepu_alloc_task, + .prepare = vepu_prepare, + .run = vepu_run, + .irq = vepu_irq, + .isr = vepu_isr, + .finish = vepu_finish, + .result = vepu_result, + .free_task = vepu_free_task, +}; + +static const struct mpp_dev_var vepu_v1_data = { + .device_type = MPP_DEVICE_ENC, + .hw_info = &vepu_v1_hw_info, + .trans_info = trans_rk_vepu1, + .hw_ops = &vepu_v1_hw_ops, + .dev_ops = &vepu_v1_dev_ops, +}; + +static const struct of_device_id mpp_vepu1_dt_match[] = { + { + .compatible = "rockchip,vpu-encoder-v1", + .data = &vepu_v1_data, + }, + {}, +}; + +static int vepu_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct vepu_dev *enc = NULL; + struct mpp_dev *mpp = NULL; + const struct of_device_id *match = NULL; + + dev_info(dev, "probe device\n"); + enc = devm_kzalloc(dev, sizeof(struct vepu_dev), GFP_KERNEL); + if (!enc) + return -ENOMEM; + + mpp = &enc->mpp; + platform_set_drvdata(pdev, enc); + + if (pdev->dev.of_node) { + match = of_match_node(mpp_vepu1_dt_match, pdev->dev.of_node); + if (match) + mpp->var = (struct mpp_dev_var *)match->data; + } + + ret = mpp_dev_probe(mpp, pdev); + if (ret) { + dev_err(dev, "probe sub driver failed\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mpp->irq, + mpp_dev_irq, + mpp_dev_isr_sched, + IRQF_SHARED, + dev_name(dev), mpp); + if (ret) { + dev_err(dev, "register interrupter runtime failed\n"); + return -EINVAL; + } + + mpp->session_max_buffers = VEPU1_SESSION_MAX_BUFFERS; + vepu_debugfs_init(mpp); + dev_info(dev, "probing finish\n"); + + return 0; +} + +static int vepu_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vepu_dev *enc = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + mpp_dev_remove(&enc->mpp); + vepu_debugfs_remove(&enc->mpp); + + return 0; +} + +static void vepu_shutdown(struct platform_device *pdev) +{ + int ret; + int val; + struct device *dev = &pdev->dev; + struct vepu_dev *enc = platform_get_drvdata(pdev); + struct mpp_dev *mpp = &enc->mpp; + + dev_info(dev, "shutdown device\n"); + + atomic_inc(&mpp->srv->shutdown_request); + ret = readx_poll_timeout(atomic_read, + &mpp->total_running, + val, val == 0, 20000, 200000); + if (ret == -ETIMEDOUT) + dev_err(dev, "wait total running time out\n"); +} + +struct platform_driver rockchip_vepu1_driver = { + .probe = vepu_probe, + .remove = vepu_remove, + .shutdown = vepu_shutdown, + .driver = { + .name = VEPU1_DRIVER_NAME, + .of_match_table = of_match_ptr(mpp_vepu1_dt_match), + }, +}; +EXPORT_SYMBOL(rockchip_vepu1_driver); diff --git a/drivers/video/rockchip/mpp/mpp_vepu2.c b/drivers/video/rockchip/mpp/mpp_vepu2.c new file mode 100644 index 000000000000..6e40a5a87fe3 --- /dev/null +++ b/drivers/video/rockchip/mpp/mpp_vepu2.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * + * author: + * Alpha Lin, alpha.lin@rock-chips.com + * Randy Li, randy.li@rock-chips.com + * Ding Wei, leo.ding@rock-chips.com + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpp_debug.h" +#include "mpp_common.h" +#include "mpp_iommu.h" + +#define VEPU2_DRIVER_NAME "mpp_vepu2" + +#define VEPU2_SESSION_MAX_BUFFERS 20 +/* The maximum registers number of all the version */ +#define VEPU2_REG_NUM 184 +#define VEPU2_REG_START_INDEX 0 +#define VEPU2_REG_END_INDEX 183 + +#define VEPU2_REG_ENC_EN 0x19c +#define VEPU2_REG_ENC_EN_INDEX (103) +#define VEPU2_ENC_START BIT(0) + +#define VEPU2_GET_FORMAT(x) (((x) >> 4) & 0x3) +#define VEPU2_FORMAT_MASK (0x30) + +#define VEPU2_FMT_RESERVED (0) +#define VEPU2_FMT_VP8E (1) +#define VEPU2_FMT_JPEGE (2) +#define VEPU2_FMT_H264E (3) + +#define VEPU2_REG_MB_CTRL 0x1a0 +#define VEPU2_REG_MB_CTRL_INDEX (104) + +#define VEPU2_REG_INT 0x1b4 +#define VEPU2_REG_INT_INDEX (109) +#define VEPU2_MV_SAD_WR_EN BIT(24) +#define VEPU2_ROCON_WRITE_DIS BIT(20) +#define VEPU2_INT_SLICE_EN BIT(16) +#define VEPU2_CLOCK_GATE_EN BIT(12) +#define VEPU2_INT_TIMEOUT_EN BIT(10) +#define VEPU2_INT_CLEAR BIT(9) +#define VEPU2_IRQ_DIS BIT(8) +#define VEPU2_INT_TIMEOUT BIT(6) +#define VEPU2_INT_BUF_FULL BIT(5) +#define VEPU2_INT_BUS_ERROR BIT(4) +#define VEPU2_INT_SLICE BIT(2) +#define VEPU2_INT_RDY BIT(1) +#define VEPU2_INT_RAW BIT(0) + +#define RKVPUE2_REG_DMV_4P_1P(i) (0x1e0 + ((i) << 4)) +#define RKVPUE2_REG_DMV_4P_1P_INDEX(i) (120 + (i)) + +#define VEPU2_REG_CLR_CACHE_BASE 0xc10 + +#define to_vepu_task(task) \ + container_of(task, struct vepu_task, mpp_task) +#define to_vepu_dev(dev) \ + container_of(dev, struct vepu_dev, mpp) + +struct vepu_task { + struct mpp_task mpp_task; + struct mpp_hw_info *hw_info; + unsigned long aclk_freq; + u32 reg[VEPU2_REG_NUM]; + u32 idx; + struct extra_info_for_iommu ext_inf; + u32 irq_status; +}; + +struct vepu_dev { + struct mpp_dev mpp; + + struct clk *aclk; + struct clk *hclk; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + u32 aclk_debug; + u32 session_max_buffers_debug; + + struct reset_control *rst_a; + struct reset_control *rst_h; + struct vepu_task *current_task; +}; + +static struct mpp_hw_info vepu_v2_hw_info = { + .reg_num = VEPU2_REG_NUM, + .regidx_start = VEPU2_REG_START_INDEX, + .regidx_end = VEPU2_REG_END_INDEX, + .regidx_en = VEPU2_REG_ENC_EN_INDEX, +}; + +/* + * file handle translate information + */ +static const char trans_tbl_default[] = { + 48, 49, 50, 56, 57, 63, 64, 77, 78, 81 +}; + +static const char trans_tbl_vp8e[] = { + 27, 44, 45, 48, 49, 50, 56, 57, 63, 64, + 76, 77, 78, 80, 81, 106, 108, +}; + +static struct mpp_trans_info trans_rk_vepu2[] = { + [VEPU2_FMT_RESERVED] = { + .count = 0, + .table = NULL, + }, + [VEPU2_FMT_VP8E] = { + .count = sizeof(trans_tbl_vp8e), + .table = trans_tbl_vp8e, + }, + [VEPU2_FMT_JPEGE] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, + [VEPU2_FMT_H264E] = { + .count = sizeof(trans_tbl_default), + .table = trans_tbl_default, + }, +}; + +static void *vepu_alloc_task(struct mpp_session *session, + void __user *src, u32 size) +{ + u32 reg_len; + u32 extinf_len; + u32 fmt = 0; + int err = -EFAULT; + struct vepu_task *task = NULL; + u32 dwsize = size / sizeof(u32); + struct mpp_dev *mpp = session->mpp; + + mpp_debug_enter(); + + task = kzalloc(sizeof(*task), GFP_KERNEL); + if (!task) + return NULL; + + mpp_task_init(session, &task->mpp_task); + task->hw_info = mpp->var->hw_info; + reg_len = min(task->hw_info->reg_num, dwsize); + extinf_len = dwsize > reg_len ? (dwsize - reg_len) * 4 : 0; + + if (copy_from_user(task->reg, src, reg_len * 4)) { + mpp_err("error: copy_from_user failed in reg_init\n"); + err = -EFAULT; + goto fail; + } + + fmt = VEPU2_GET_FORMAT(task->reg[VEPU2_REG_ENC_EN_INDEX]); + if (extinf_len > 0) { + if (likely(fmt == VEPU2_FMT_JPEGE)) { + err = copy_from_user(&task->ext_inf, + (u8 *)src + size + - JPEG_IOC_EXTRA_SIZE, + JPEG_IOC_EXTRA_SIZE); + } else { + u32 ext_cpy = min_t(size_t, extinf_len, + sizeof(task->ext_inf)); + err = copy_from_user(&task->ext_inf, + (u32 *)src + reg_len, + ext_cpy); + } + + if (err) { + mpp_err("copy_from_user failed when extra info\n"); + err = -EFAULT; + goto fail; + } + } + + err = mpp_translate_reg_address(session->mpp, + &task->mpp_task, + fmt, task->reg); + if (err) { + mpp_err("error: translate reg address failed.\n"); + mpp_dump_reg(task->reg, + task->hw_info->regidx_start, + task->hw_info->regidx_end); + goto fail; + } + + mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x", + task->ext_inf.cnt, task->ext_inf.magic); + mpp_translate_extra_info(&task->mpp_task, + &task->ext_inf, task->reg); + + mpp_debug_leave(); + + return &task->mpp_task; + +fail: + mpp_task_finalize(session, &task->mpp_task); + kfree(task); + return ERR_PTR(err); +} + +static int vepu_prepare(struct mpp_dev *mpp, + struct mpp_task *task) +{ + return -EINVAL; +} + +static int vepu_run(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + u32 regidx_en; + struct vepu_task *task = NULL; + struct vepu_dev *enc = NULL; + + mpp_debug_enter(); + + task = to_vepu_task(mpp_task); + enc = to_vepu_dev(mpp); + + /* FIXME: spin lock here */ + enc->current_task = task; + /* clear cache */ + mpp_write_relaxed(mpp, VEPU2_REG_CLR_CACHE_BASE, 0); + + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + regidx_en = task->hw_info->regidx_en; + /* First, flush correct encoder format */ + mpp_write_relaxed(mpp, VEPU2_REG_ENC_EN, + task->reg[regidx_en] & VEPU2_FORMAT_MASK); + /* Second, flush others register */ + for (i = regidx_start; i <= regidx_end; i++) { + if (i == regidx_en) + continue; + mpp_write_relaxed(mpp, i * sizeof(u32), task->reg[i]); + } + /* Last, flush the registers */ + wmb(); + mpp_write(mpp, VEPU2_REG_ENC_EN, + task->reg[regidx_en] | VEPU2_ENC_START); + + mpp_debug_leave(); + + return 0; +} + +static int vepu_irq(struct mpp_dev *mpp) +{ + mpp->irq_status = mpp_read(mpp, VEPU2_REG_INT); + if (!(mpp->irq_status & VEPU2_INT_RAW)) + return IRQ_NONE; + + mpp_write(mpp, VEPU2_REG_INT, 0); + + return IRQ_WAKE_THREAD; +} + +static int vepu_isr(struct mpp_dev *mpp) +{ + u32 err_mask; + struct vepu_task *task = NULL; + struct mpp_task *mpp_task = NULL; + struct vepu_dev *enc = to_vepu_dev(mpp); + + /* FIXME use a spin lock here */ + task = enc->current_task; + if (!task) { + dev_err(enc->mpp.dev, "no current task\n"); + return IRQ_HANDLED; + } + + mpp_task = &task->mpp_task; + mpp_time_diff(mpp_task); + enc->current_task = NULL; + task->irq_status = mpp->irq_status; + mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", + task->irq_status); + + err_mask = VEPU2_INT_TIMEOUT + | VEPU2_INT_BUF_FULL + | VEPU2_INT_BUS_ERROR; + + if (err_mask & task->irq_status) + atomic_inc(&mpp->reset_request); + + mpp_task_finish(mpp_task->session, mpp_task); + + mpp_debug_leave(); + + return IRQ_HANDLED; +} + +static int vepu_finish(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + u32 i; + u32 regidx_start; + u32 regidx_end; + struct vepu_task *task = to_vepu_task(mpp_task); + + mpp_debug_enter(); + /* read register after running */ + regidx_start = task->hw_info->regidx_start; + regidx_end = task->hw_info->regidx_end; + for (i = regidx_start; i <= regidx_end; i++) + task->reg[i] = mpp_read_relaxed(mpp, i * sizeof(u32)); + task->reg[VEPU2_REG_INT_INDEX] = task->irq_status; + + mpp_debug_leave(); + + return 0; +} + +static int vepu_result(struct mpp_dev *mpp, + struct mpp_task *mpp_task, + u32 __user *dst, u32 size) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + /* FIXME may overflow the kernel */ + if (copy_to_user(dst, task->reg, size)) { + mpp_err("copy_to_user failed\n"); + return -EIO; + } + + return 0; +} + +static int vepu_free_task(struct mpp_session *session, + struct mpp_task *mpp_task) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + mpp_task_finalize(session, mpp_task); + kfree(task); + + return 0; +} + +static int vepu_debugfs_remove(struct mpp_dev *mpp) +{ +#ifdef CONFIG_DEBUG_FS + struct vepu_dev *enc = to_vepu_dev(mpp); + + debugfs_remove_recursive(enc->debugfs); +#endif + return 0; +} + +static int vepu_debugfs_init(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + struct device_node *np = mpp->dev->of_node; + + enc->aclk_debug = 0; + enc->session_max_buffers_debug = 0; +#ifdef CONFIG_DEBUG_FS + enc->debugfs = debugfs_create_dir(np->name, mpp->srv->debugfs); + if (IS_ERR_OR_NULL(enc->debugfs)) { + mpp_err("failed on open debugfs\n"); + enc->debugfs = NULL; + return -EIO; + } + debugfs_create_u32("aclk", 0644, + enc->debugfs, &enc->aclk_debug); + debugfs_create_u32("session_buffers", 0644, + enc->debugfs, &enc->session_max_buffers_debug); +#endif + if (enc->session_max_buffers_debug) + mpp->session_max_buffers = enc->session_max_buffers_debug; + return 0; +} + +static int vepu_init(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + mpp->grf_info = &mpp->srv->grf_infos[MPP_DRIVER_VEPU2]; + + enc->aclk = devm_clk_get(mpp->dev, "aclk_vcodec"); + if (IS_ERR(enc->aclk)) { + mpp_err("failed on clk_get aclk_vcodec\n"); + enc->aclk = NULL; + } + enc->hclk = devm_clk_get(mpp->dev, "hclk_vcodec"); + if (IS_ERR(enc->hclk)) { + mpp_err("failed on clk_get hclk_vcodec\n"); + enc->hclk = NULL; + } + + enc->rst_a = devm_reset_control_get_shared(mpp->dev, "video_a"); + if (IS_ERR_OR_NULL(enc->rst_a)) { + mpp_err("No aclk reset resource define\n"); + enc->rst_a = NULL; + } + enc->rst_h = devm_reset_control_get_shared(mpp->dev, "video_h"); + if (IS_ERR_OR_NULL(enc->rst_h)) { + mpp_err("No hclk reset resource define\n"); + enc->rst_h = NULL; + } + mpp_safe_unreset(enc->rst_a); + mpp_safe_unreset(enc->rst_h); + + return 0; +} + +static int vepu_power_on(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_prepare_enable(enc->aclk); + if (enc->hclk) + clk_prepare_enable(enc->hclk); + + return 0; +} + +static int vepu_power_off(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_disable_unprepare(enc->aclk); + if (enc->hclk) + clk_disable_unprepare(enc->hclk); + + return 0; +} + +static int vepu_get_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vepu_task *task = to_vepu_task(mpp_task); + + task->aclk_freq = 300; + + return 0; +} + +static int vepu_set_freq(struct mpp_dev *mpp, + struct mpp_task *mpp_task) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + struct vepu_task *task = to_vepu_task(mpp_task); + + /* check whether use debug freq */ + task->aclk_freq = enc->aclk_debug ? + enc->aclk_debug : task->aclk_freq; + + clk_set_rate(enc->aclk, task->aclk_freq * MHZ); + + return 0; +} + +static int vepu_reduce_freq(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->aclk) + clk_set_rate(enc->aclk, 50 * MHZ); + + return 0; +} + +static int vepu_reset(struct mpp_dev *mpp) +{ + struct vepu_dev *enc = to_vepu_dev(mpp); + + if (enc->rst_a && enc->rst_h) { + /* Don't skip this or iommu won't work after reset */ + rockchip_pmu_idle_request(mpp->dev, true); + mpp_safe_reset(enc->rst_a); + mpp_safe_reset(enc->rst_h); + udelay(5); + mpp_safe_unreset(enc->rst_a); + mpp_safe_unreset(enc->rst_h); + rockchip_pmu_idle_request(mpp->dev, false); + } + mpp_write(mpp, VEPU2_REG_INT, VEPU2_INT_CLEAR); + + return 0; +} + +static struct mpp_hw_ops vepu_v2_hw_ops = { + .init = vepu_init, + .power_on = vepu_power_on, + .power_off = vepu_power_off, + .get_freq = vepu_get_freq, + .set_freq = vepu_set_freq, + .reduce_freq = vepu_reduce_freq, + .reset = vepu_reset, +}; + +static struct mpp_dev_ops vepu_v2_dev_ops = { + .alloc_task = vepu_alloc_task, + .prepare = vepu_prepare, + .run = vepu_run, + .irq = vepu_irq, + .isr = vepu_isr, + .finish = vepu_finish, + .result = vepu_result, + .free_task = vepu_free_task, +}; + +static const struct mpp_dev_var vepu_v2_data = { + .device_type = MPP_DEVICE_ENC, + .hw_info = &vepu_v2_hw_info, + .trans_info = trans_rk_vepu2, + .hw_ops = &vepu_v2_hw_ops, + .dev_ops = &vepu_v2_dev_ops, +}; + +static const struct of_device_id mpp_vepu2_dt_match[] = { + { + .compatible = "rockchip,vpu-encoder-v2", + .data = &vepu_v2_data, + }, + {}, +}; + +static int vepu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vepu_dev *enc = NULL; + struct mpp_dev *mpp = NULL; + const struct of_device_id *match = NULL; + int ret = 0; + + dev_info(dev, "probe device\n"); + enc = devm_kzalloc(dev, sizeof(struct vepu_dev), GFP_KERNEL); + if (!enc) + return -ENOMEM; + + mpp = &enc->mpp; + platform_set_drvdata(pdev, enc); + + if (pdev->dev.of_node) { + match = of_match_node(mpp_vepu2_dt_match, pdev->dev.of_node); + if (match) + mpp->var = (struct mpp_dev_var *)match->data; + } + + ret = mpp_dev_probe(mpp, pdev); + if (ret) { + dev_err(dev, "probe sub driver failed\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mpp->irq, + mpp_dev_irq, + mpp_dev_isr_sched, + IRQF_SHARED, + dev_name(dev), mpp); + if (ret) { + dev_err(dev, "register interrupter runtime failed\n"); + return -EINVAL; + } + + mpp->session_max_buffers = VEPU2_SESSION_MAX_BUFFERS; + vepu_debugfs_init(mpp); + dev_info(dev, "probing finish\n"); + + return 0; +} + +static int vepu_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vepu_dev *enc = platform_get_drvdata(pdev); + + dev_info(dev, "remove device\n"); + mpp_dev_remove(&enc->mpp); + vepu_debugfs_remove(&enc->mpp); + + return 0; +} + +static void vepu_shutdown(struct platform_device *pdev) +{ + int ret; + int val; + struct device *dev = &pdev->dev; + struct vepu_dev *enc = platform_get_drvdata(pdev); + struct mpp_dev *mpp = &enc->mpp; + + dev_info(dev, "shutdown device\n"); + + atomic_inc(&mpp->srv->shutdown_request); + ret = readx_poll_timeout(atomic_read, + &mpp->total_running, + val, val == 0, 20000, 200000); + if (ret == -ETIMEDOUT) + dev_err(dev, "wait total running time out\n"); +} + +struct platform_driver rockchip_vepu2_driver = { + .probe = vepu_probe, + .remove = vepu_remove, + .shutdown = vepu_shutdown, + .driver = { + .name = VEPU2_DRIVER_NAME, + .of_match_table = of_match_ptr(mpp_vepu2_dt_match), + }, +}; +EXPORT_SYMBOL(rockchip_vepu2_driver); diff --git a/drivers/video/rockchip/vpu/Kconfig b/drivers/video/rockchip/vpu/Kconfig deleted file mode 100644 index 8a0c459c9f3c..000000000000 --- a/drivers/video/rockchip/vpu/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -menu "ROCKCHIP_MPP" - depends on ARCH_ROCKCHIP - -config ROCKCHIP_MPP_SERVICE - tristate "ROCKCHIP MPP SERVICE driver" - default n - help - rockchip mpp service. - -config ROCKCHIP_MPP_DEVICE - tristate "ROCKCHIP MPP DEVICE driver" - depends on ROCKCHIP_MPP_SERVICE - default n - help - rockchip mpp module. - -endmenu diff --git a/drivers/video/rockchip/vpu/Makefile b/drivers/video/rockchip/vpu/Makefile deleted file mode 100644 index 40a0a0df083e..000000000000 --- a/drivers/video/rockchip/vpu/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_ROCKCHIP_MPP_SERVICE) += mpp_service.o -obj-$(CONFIG_ROCKCHIP_MPP_DEVICE) += mpp_dev_rkvenc.o mpp_dev_vepu.o \ - mpp_dev_h265e.o mpp_dev_common.o vpu_iommu_drm.o vpu_iommu_ion.o \ - vpu_iommu_ops.o diff --git a/drivers/video/rockchip/vpu/mpp_dev_common.c b/drivers/video/rockchip/vpu/mpp_dev_common.c deleted file mode 100644 index 1cf78ec1984d..000000000000 --- a/drivers/video/rockchip/vpu/mpp_dev_common.c +++ /dev/null @@ -1,1078 +0,0 @@ -/** - * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd - * author: chenhengming chm@rock-chips.com - * Alpha Lin, alpha.lin@rock-chips.com - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "vpu_iommu_ops.h" -#include "mpp_dev_common.h" -#include "mpp_service.h" - -int mpp_dev_debug; -module_param(mpp_dev_debug, int, 0644); -MODULE_PARM_DESC(mpp_dev_debug, "bit switch for mpp_dev debug information"); - -static int mpp_bufid_to_iova(struct rockchip_mpp_dev *mpp, const u8 *tbl, - int size, u32 *reg, struct mpp_ctx *ctx) -{ - int hdl; - int ret = 0; - struct mpp_mem_region *mem_region, *n; - int i; - int offset = 0; - int retval = 0; - - if (!tbl || size <= 0) { - mpp_err("input arguments invalidate, table %p, size %d\n", - tbl, size); - return -1; - } - - for (i = 0; i < size; i++) { - int usr_fd = reg[tbl[i]] & 0x3FF; - - mpp_debug(DEBUG_IOMMU, "reg[%03d] fd = %d\n", tbl[i], usr_fd); - - /* if userspace do not set the fd at this register, skip */ - if (usr_fd == 0) - continue; - - offset = reg[tbl[i]] >> 10; - - mpp_debug(DEBUG_IOMMU, "pos %3d fd %3d offset %10d\n", - tbl[i], usr_fd, offset); - - hdl = vpu_iommu_import(mpp->iommu_info, ctx->session, usr_fd); - if (hdl < 0) { - mpp_err("import dma-buf from fd %d failed, reg[%d]\n", - usr_fd, tbl[i]); - retval = hdl; - goto fail; - } - - mem_region = kzalloc(sizeof(*mem_region), GFP_KERNEL); - - if (!mem_region) { - vpu_iommu_free(mpp->iommu_info, ctx->session, hdl); - retval = -1; - goto fail; - } - - mem_region->hdl = hdl; - mem_region->reg_idx = tbl[i]; - - ret = vpu_iommu_map_iommu(mpp->iommu_info, ctx->session, - hdl, (void *)&mem_region->iova, - &mem_region->len); - - if (ret < 0) { - mpp_err("reg %d fd %d ion map iommu failed\n", - tbl[i], usr_fd); - kfree(mem_region); - vpu_iommu_free(mpp->iommu_info, ctx->session, hdl); - retval = -1; - goto fail; - } - - reg[tbl[i]] = mem_region->iova + offset; - INIT_LIST_HEAD(&mem_region->reg_lnk); - list_add_tail(&mem_region->reg_lnk, &ctx->mem_region_list); - } - - return 0; - -fail: - list_for_each_entry_safe(mem_region, n, - &ctx->mem_region_list, reg_lnk) { - vpu_iommu_free(mpp->iommu_info, ctx->session, mem_region->hdl); - list_del_init(&mem_region->reg_lnk); - kfree(mem_region); - } - - return retval; -} - -int mpp_reg_address_translate(struct rockchip_mpp_dev *mpp, - u32 *reg, - struct mpp_ctx *ctx, - int idx) -{ - struct mpp_trans_info *trans_info = mpp->variant->trans_info; - const u8 *tbl = trans_info[idx].table; - int size = trans_info[idx].count; - - return mpp_bufid_to_iova(mpp, tbl, size, reg, ctx); -} - -void mpp_translate_extra_info(struct mpp_ctx *ctx, - struct extra_info_for_iommu *ext_inf, - u32 *reg) -{ - if (ext_inf) { - int i; - - for (i = 0; i < ext_inf->cnt; i++) { - mpp_debug(DEBUG_IOMMU, "reg[%d] + offset %d\n", - ext_inf->elem[i].index, - ext_inf->elem[i].offset); - reg[ext_inf->elem[i].index] += ext_inf->elem[i].offset; - } - } -} - -void mpp_dump_reg(void __iomem *regs, int count) -{ - int i; - - pr_info("dumping registers:"); - - for (i = 0; i < count; i++) - pr_info("reg[%02d]: %08x\n", i, readl_relaxed(regs + i * 4)); -} - -void mpp_dump_reg_mem(u32 *regs, int count) -{ - int i; - - pr_info("Dumping mpp_service registers:\n"); - - for (i = 0; i < count; i++) - pr_info("reg[%03d]: %08x\n", i, regs[i]); -} - -int mpp_dev_common_ctx_init(struct rockchip_mpp_dev *mpp, struct mpp_ctx *cfg) -{ - INIT_LIST_HEAD(&cfg->session_link); - INIT_LIST_HEAD(&cfg->status_link); - INIT_LIST_HEAD(&cfg->mem_region_list); - - return 0; -} - -#ifdef CONFIG_COMPAT -struct compat_mpp_request { - compat_uptr_t req; - u32 size; -}; -#endif - -#define MPP_TIMEOUT_DELAY (2 * HZ) -#define MPP_POWER_OFF_DELAY (4 * HZ) - -static void mpp_dev_session_clear(struct rockchip_mpp_dev *mpp, - struct mpp_session *session) -{ - struct mpp_ctx *ctx, *n; - - list_for_each_entry_safe(ctx, n, &session->done, session_link) { - mpp_dev_common_ctx_deinit(mpp, ctx); - } -} - -static struct mpp_ctx *ctx_init(struct rockchip_mpp_dev *mpp, - struct mpp_session *session, - void __user *src, u32 size) -{ - struct mpp_ctx *ctx; - - mpp_debug_enter(); - - if (mpp->ops->init) - ctx = mpp->ops->init(mpp, session, src, size); - else - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); - - if (!ctx) - return NULL; - - ctx->session = session; - ctx->mpp = mpp; - - mpp_srv_pending_locked(mpp->srv, ctx); - - mpp_debug_leave(); - - return ctx; -} - -void mpp_dev_common_ctx_deinit(struct rockchip_mpp_dev *mpp, - struct mpp_ctx *ctx) -{ - struct mpp_mem_region *mem_region = NULL, *n; - - list_del_init(&ctx->session_link); - list_del_init(&ctx->status_link); - - /* release memory region attach to this registers table. */ - list_for_each_entry_safe(mem_region, n, - &ctx->mem_region_list, reg_lnk) { - vpu_iommu_unmap_iommu(mpp->iommu_info, ctx->session, - mem_region->hdl); - vpu_iommu_free(mpp->iommu_info, ctx->session, mem_region->hdl); - list_del_init(&mem_region->reg_lnk); - kfree(mem_region); - } - - kfree(ctx); -} - -static inline void mpp_queue_power_off_work(struct rockchip_mpp_dev *mpp) -{ - queue_delayed_work(system_wq, &mpp->power_off_work, - MPP_POWER_OFF_DELAY); -} - -static void mpp_power_off_work(struct work_struct *work_s) -{ - struct delayed_work *dlwork = container_of(work_s, - struct delayed_work, work); - struct rockchip_mpp_dev *mpp = - container_of(dlwork, - struct rockchip_mpp_dev, - power_off_work); - - if (mutex_trylock(&mpp->srv->lock)) { - mpp_dev_power_off(mpp); - mutex_unlock(&mpp->srv->lock); - } else { - /* Come back later if the device is busy... */ - mpp_queue_power_off_work(mpp); - } -} - -static void mpp_dev_reset(struct rockchip_mpp_dev *mpp) -{ - mpp_debug_enter(); - - atomic_set(&mpp->reset_request, 0); - - mpp->variant->reset(mpp); - - if (!mpp->iommu_enable) - return; - - if (test_bit(MMU_ACTIVATED, &mpp->state)) { - if (atomic_read(&mpp->enabled)) - vpu_iommu_detach(mpp->iommu_info); - else - WARN_ON(!atomic_read(&mpp->enabled)); - - vpu_iommu_attach(mpp->iommu_info); - } - - mpp_debug_leave(); -} - -void mpp_dev_power_on(struct rockchip_mpp_dev *mpp) -{ - int ret; - ktime_t now = ktime_get(); - - if (ktime_to_ns(ktime_sub(now, mpp->last)) > NSEC_PER_SEC) { - cancel_delayed_work_sync(&mpp->power_off_work); - mpp_queue_power_off_work(mpp); - mpp->last = now; - } - ret = atomic_add_unless(&mpp->enabled, 1, 1); - if (!ret) - return; - - pr_info("%s: power on\n", dev_name(mpp->dev)); - - mpp->variant->power_on(mpp); - if (mpp->iommu_enable) { - set_bit(MMU_ACTIVATED, &mpp->state); - vpu_iommu_attach(mpp->iommu_info); - } - atomic_add(1, &mpp->power_on_cnt); - wake_lock(&mpp->wake_lock); -} - -void mpp_dev_power_off(struct rockchip_mpp_dev *mpp) -{ - int total_running; - int ret = atomic_add_unless(&mpp->enabled, -1, 0); - - if (!ret) - return; - - total_running = atomic_read(&mpp->total_running); - if (total_running) { - pr_alert("alert: power off when %d task running!!\n", - total_running); - mdelay(50); - pr_alert("alert: delay 50 ms for running task\n"); - } - - pr_info("%s: power off...", dev_name(mpp->dev)); - - if (mpp->iommu_enable) { - clear_bit(MMU_ACTIVATED, &mpp->state); - vpu_iommu_detach(mpp->iommu_info); - } - mpp->variant->power_off(mpp); - - atomic_add(1, &mpp->power_off_cnt); - wake_unlock(&mpp->wake_lock); - pr_info("done\n"); -} - -bool mpp_dev_is_power_on(struct rockchip_mpp_dev *mpp) -{ - return !!atomic_read(&mpp->enabled); -} - -static void rockchip_mpp_run(struct rockchip_mpp_dev *mpp) -{ - struct mpp_ctx *ctx; - - mpp_debug_enter(); - - mpp_srv_run(mpp->srv); - - ctx = mpp_srv_get_last_running_ctx(mpp->srv); - mpp_time_record(ctx); - - mpp_dev_power_on(mpp); - - mpp_debug(DEBUG_TASK_INFO, "pid %d, start hw %s\n", - ctx->session->pid, dev_name(mpp->dev)); - - if (atomic_read(&mpp->reset_request)) - mpp_dev_reset(mpp); - - if (unlikely(mpp_dev_debug & DEBUG_REGISTER)) - mpp_dump_reg(mpp->reg_base, mpp->variant->reg_len); - - atomic_add(1, &mpp->total_running); - if (mpp->ops->run) - mpp->ops->run(mpp); - - mpp_debug_leave(); -} - -static void rockchip_mpp_try_run(struct rockchip_mpp_dev *mpp) -{ - int ret = 0; - struct rockchip_mpp_dev *pending; - struct mpp_ctx *ctx; - - mpp_debug_enter(); - - if (!mpp_srv_pending_is_empty(mpp->srv)) { - /* - * if prepare func in hw driver define, state will be determined - * by hw driver prepare func, or state will be determined by - * service. ret = 0, run ready ctx. - */ - ctx = mpp_srv_get_pending_ctx(mpp->srv); - pending = ctx->mpp; - if (mpp->ops->prepare) - ret = mpp->ops->prepare(pending); - else if (mpp_srv_is_running(mpp->srv)) - ret = -1; - - if (ret == 0) - rockchip_mpp_run(pending); - } - - mpp_debug_leave(); -} - -static int rockchip_mpp_result(struct rockchip_mpp_dev *mpp, - struct mpp_ctx *ctx, u32 __user *dst) -{ - mpp_debug_enter(); - - if (mpp->ops->result) - mpp->ops->result(mpp, ctx, dst); - - mpp_dev_common_ctx_deinit(mpp, ctx); - - mpp_debug_leave(); - return 0; -} - -static int mpp_dev_wait_result(struct mpp_session *session, - struct rockchip_mpp_dev *mpp, - u32 __user *req) -{ - struct mpp_ctx *ctx; - int ret; - - ret = wait_event_timeout(session->wait, - !list_empty(&session->done), - MPP_TIMEOUT_DELAY); - - if (!list_empty(&session->done)) { - if (ret < 0) - mpp_err("warning: pid %d wait task error ret %d\n", - session->pid, ret); - ret = 0; - } else { - if (unlikely(ret < 0)) { - mpp_err("error: pid %d wait task ret %d\n", - session->pid, ret); - } else if (ret == 0) { - mpp_err("error: pid %d wait %d task done timeout\n", - session->pid, - atomic_read(&session->task_running)); - ret = -ETIMEDOUT; - - mpp_dump_reg(mpp->reg_base, mpp->variant->reg_len); - } - } - - if (ret < 0) { - mpp_srv_lock(mpp->srv); - atomic_sub(1, &mpp->total_running); - - if (mpp->variant->reset) - mpp->variant->reset(mpp); - mpp_srv_unlock(mpp->srv); - return ret; - } - - mpp_srv_lock(mpp->srv); - ctx = mpp_srv_get_done_ctx(session); - rockchip_mpp_result(mpp, ctx, req); - mpp_srv_unlock(mpp->srv); - - return 0; -} - -static long mpp_dev_ioctl(struct file *filp, unsigned int cmd, - unsigned long arg) -{ - struct rockchip_mpp_dev *mpp = - container_of(filp->f_path.dentry->d_inode->i_cdev, - struct rockchip_mpp_dev, - cdev); - struct mpp_session *session = (struct mpp_session *)filp->private_data; - - mpp_debug_enter(); - if (!session) - return -EINVAL; - - switch (cmd) { - case VPU_IOC_SET_CLIENT_TYPE: - break; - case VPU_IOC_SET_REG: { - struct vpu_request req; - struct mpp_ctx *ctx; - - mpp_debug(DEBUG_IOCTL, "pid %d set reg\n", - session->pid); - if (copy_from_user(&req, (void __user *)arg, - sizeof(struct vpu_request))) { - mpp_err("error: set reg copy_from_user failed\n"); - return -EFAULT; - } - ctx = ctx_init(mpp, session, (void __user *)req.req, - req.size); - if (!ctx) - return -EFAULT; - - mpp_srv_lock(mpp->srv); - rockchip_mpp_try_run(mpp); - mpp_srv_unlock(mpp->srv); - } break; - case VPU_IOC_GET_REG: { - struct vpu_request req; - - mpp_debug(DEBUG_IOCTL, "pid %d get reg\n", - session->pid); - if (copy_from_user(&req, (void __user *)arg, - sizeof(struct vpu_request))) { - mpp_err("error: get reg copy_from_user failed\n"); - return -EFAULT; - } - - return mpp_dev_wait_result(session, mpp, req.req); - } break; - case VPU_IOC_PROBE_IOMMU_STATUS: { - int iommu_enable = 1; - - mpp_debug(DEBUG_IOCTL, "VPU_IOC_PROBE_IOMMU_STATUS\n"); - - if (copy_to_user((void __user *)arg, - &iommu_enable, sizeof(int))) { - mpp_err("error: iommu status copy_to_user failed\n"); - return -EFAULT; - } - break; - } - default: { - if (mpp->ops->ioctl) - return mpp->ops->ioctl(session, cmd, arg); - - mpp_err("unknown mpp ioctl cmd %x\n", cmd); - return -ENOIOCTLCMD; - } break; - } - - mpp_debug_leave(); - return 0; -} - -#ifdef CONFIG_COMPAT -#define VPU_IOC_SET_CLIENT_TYPE32 _IOW(VPU_IOC_MAGIC, 1, u32) -#define VPU_IOC_GET_HW_FUSE_STATUS32 _IOW(VPU_IOC_MAGIC, 2, \ - compat_ulong_t) -#define VPU_IOC_SET_REG32 _IOW(VPU_IOC_MAGIC, 3, \ - compat_ulong_t) -#define VPU_IOC_GET_REG32 _IOW(VPU_IOC_MAGIC, 4, \ - compat_ulong_t) -#define VPU_IOC_PROBE_IOMMU_STATUS32 _IOR(VPU_IOC_MAGIC, 5, u32) - -static long native_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - long ret = -ENOIOCTLCMD; - - if (file->f_op->unlocked_ioctl) - ret = file->f_op->unlocked_ioctl(file, cmd, arg); - - return ret; -} - -static long compat_mpp_dev_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct vpu_request req; - void __user *up = compat_ptr(arg); - int compatible_arg = 1; - long err = 0; - - mpp_debug_enter(); - mpp_debug(DEBUG_IOCTL, "cmd %x, VPU_IOC_SET_CLIENT_TYPE32 %x\n", cmd, - (u32)VPU_IOC_SET_CLIENT_TYPE32); - /* First, convert the command. */ - switch (cmd) { - case VPU_IOC_SET_CLIENT_TYPE32: - cmd = VPU_IOC_SET_CLIENT_TYPE; - break; - case VPU_IOC_GET_HW_FUSE_STATUS32: - cmd = VPU_IOC_GET_HW_FUSE_STATUS; - break; - case VPU_IOC_SET_REG32: - cmd = VPU_IOC_SET_REG; - break; - case VPU_IOC_GET_REG32: - cmd = VPU_IOC_GET_REG; - break; - case VPU_IOC_PROBE_IOMMU_STATUS32: - cmd = VPU_IOC_PROBE_IOMMU_STATUS; - break; - } - switch (cmd) { - case VPU_IOC_SET_REG: - case VPU_IOC_GET_REG: - case VPU_IOC_GET_HW_FUSE_STATUS: { - compat_uptr_t req_ptr; - struct compat_mpp_request __user *req32 = NULL; - - req32 = (struct compat_mpp_request __user *)up; - memset(&req, 0, sizeof(req)); - - if (get_user(req_ptr, &req32->req) || - get_user(req.size, &req32->size)) { - mpp_err("error: compat get hw status copy_from_user failed\n"); - return -EFAULT; - } - req.req = compat_ptr(req_ptr); - compatible_arg = 0; - } break; - } - - if (compatible_arg) { - err = native_ioctl(file, cmd, (unsigned long)up); - } else { - mm_segment_t old_fs = get_fs(); - - set_fs(KERNEL_DS); - err = native_ioctl(file, cmd, (unsigned long)&req); - set_fs(old_fs); - } - - mpp_debug_leave(); - return err; -} -#endif - -static int mpp_dev_open(struct inode *inode, struct file *filp) -{ - struct rockchip_mpp_dev *mpp = - container_of(inode->i_cdev, - struct rockchip_mpp_dev, - cdev); - struct mpp_session *session; - - mpp_debug_enter(); - - if (mpp->ops->open) - session = mpp->ops->open(mpp); - else - session = kzalloc(sizeof(*session), GFP_KERNEL); - - if (!session) - return -ENOMEM; - - session->pid = current->pid; - session->mpp = mpp; - INIT_LIST_HEAD(&session->done); - INIT_LIST_HEAD(&session->list_session); - init_waitqueue_head(&session->wait); - atomic_set(&session->task_running, 0); - mpp_srv_lock(mpp->srv); - list_add_tail(&session->list_session, &mpp->srv->session); - filp->private_data = (void *)session; - mpp_srv_unlock(mpp->srv); - - mpp_debug_leave(); - - return nonseekable_open(inode, filp); -} - -static int mpp_dev_release(struct inode *inode, struct file *filp) -{ - struct rockchip_mpp_dev *mpp = container_of( - inode->i_cdev, - struct rockchip_mpp_dev, - cdev); - int task_running; - struct mpp_session *session = filp->private_data; - - mpp_debug_enter(); - if (!session) - return -EINVAL; - - task_running = atomic_read(&session->task_running); - if (task_running) { - pr_err("session %d still has %d task running when closing\n", - session->pid, task_running); - msleep(50); - } - wake_up(&session->wait); - - if (mpp->ops->release) - mpp->ops->release(session); - mpp_srv_lock(mpp->srv); - /* remove this filp from the asynchronusly notified filp's */ - list_del_init(&session->list_session); - mpp_dev_session_clear(mpp, session); - vpu_iommu_clear(mpp->iommu_info, session); - filp->private_data = NULL; - mpp_srv_unlock(mpp->srv); - if (mpp->ops->free) - mpp->ops->free(session); - else - kfree(session); - - pr_debug("dev closed\n"); - mpp_debug_leave(); - return 0; -} - -static const struct file_operations mpp_dev_fops = { - .unlocked_ioctl = mpp_dev_ioctl, - .open = mpp_dev_open, - .release = mpp_dev_release, -#ifdef CONFIG_COMPAT - .compat_ioctl = compat_mpp_dev_ioctl, -#endif -}; - -static irqreturn_t mpp_irq(int irq, void *dev_id) -{ - struct rockchip_mpp_dev *mpp = dev_id; - - int ret = -1; - - if (mpp->ops->irq) - ret = mpp->ops->irq(mpp); - - if (ret < 0) - return IRQ_NONE; - else - return IRQ_WAKE_THREAD; -} - -static irqreturn_t mpp_isr(int irq, void *dev_id) -{ - struct rockchip_mpp_dev *mpp = dev_id; - struct mpp_ctx *ctx; - int ret = 0; - - ctx = mpp_srv_get_current_ctx(mpp->srv); - if (IS_ERR_OR_NULL(ctx)) { - mpp_err("no current context present\n"); - return IRQ_HANDLED; - } - - mpp_time_diff(ctx); - mpp_srv_lock(mpp->srv); - - if (mpp->ops->done) - ret = mpp->ops->done(mpp); - - if (ret == 0) - mpp_srv_done(mpp->srv); - - atomic_sub(1, &mpp->total_running); - rockchip_mpp_try_run(mpp); - - mpp_srv_unlock(mpp->srv); - - return IRQ_HANDLED; -} - -#ifdef CONFIG_IOMMU_API -static inline void platform_set_sysmmu(struct device *iommu, - struct device *dev) -{ - dev->archdata.iommu = iommu; -} -#else -static inline void platform_set_sysmmu(struct device *iommu, - struct device *dev) -{ -} -#endif - -static int mpp_sysmmu_fault_hdl(struct device *dev, - enum rk_iommu_inttype itype, - unsigned long pgtable_base, - unsigned long fault_addr, unsigned int status) -{ - struct platform_device *pdev; - struct rockchip_mpp_dev *mpp; - struct mpp_ctx *ctx; - - mpp_debug_enter(); - - if (!dev) { - mpp_err("invalid NULL dev\n"); - return 0; - } - - pdev = container_of(dev, struct platform_device, dev); - if (!pdev) { - mpp_err("invalid NULL platform_device\n"); - return 0; - } - - mpp = platform_get_drvdata(pdev); - if (!mpp || !mpp->srv) { - mpp_err("invalid mpp_dev or mpp_srv\n"); - return 0; - } - - ctx = mpp_srv_get_current_ctx(mpp->srv); - if (ctx) { - struct mpp_mem_region *mem, *n; - int i = 0; - - mpp_err("mpp, fault addr 0x%08lx\n", fault_addr); - if (!list_empty(&ctx->mem_region_list)) { - list_for_each_entry_safe(mem, n, &ctx->mem_region_list, - reg_lnk) { - mpp_err("mpp, reg[%02u] mem[%02d] 0x%lx %lx\n", - mem->reg_idx, i, mem->iova, mem->len); - i++; - } - } else { - mpp_err("no memory region mapped\n"); - } - - if (ctx->mpp) { - struct rockchip_mpp_dev *mpp = ctx->mpp; - - mpp_err("current errror register set:\n"); - mpp_dump_reg(mpp->reg_base, mpp->variant->reg_len); - } - - if (mpp->variant->reset) - mpp->variant->reset(mpp); - } - - mpp_debug_leave(); - - return 0; -} - -static struct device *rockchip_get_sysmmu_dev(const char *compt) -{ - struct device_node *dn = NULL; - struct platform_device *pd = NULL; - struct device *ret = NULL; - - dn = of_find_compatible_node(NULL, NULL, compt); - if (!dn) { - pr_err("can't find device node %s \r\n", compt); - return NULL; - } - - pd = of_find_device_by_node(dn); - if (!pd) { - pr_err("can't find platform device in device node %s\n", compt); - return NULL; - } - ret = &pd->dev; - - return ret; -} - -#if defined(CONFIG_OF) -static const struct of_device_id mpp_dev_dt_ids[] = { - { .compatible = "rockchip,rkvenc", .data = &rkvenc_variant, }, - { .compatible = "rockchip,vepu", .data = &vepu_variant, }, - { .compatible = "rockchip,h265e", .data = &h265e_variant, }, - { }, -}; -#endif - -static int mpp_dev_probe(struct platform_device *pdev) -{ - int ret = 0; - struct device *dev = &pdev->dev; - char *name = (char *)dev_name(dev); - struct device_node *np = pdev->dev.of_node; - struct rockchip_mpp_dev *mpp = NULL; - const struct of_device_id *match; - const struct rockchip_mpp_dev_variant *variant; - struct device_node *srv_np, *mmu_np; - struct platform_device *srv_pdev; - struct resource *res = NULL; - struct mpp_session *session; - int allocator_type; - - pr_info("probe device %s\n", dev_name(dev)); - - match = of_match_node(mpp_dev_dt_ids, dev->of_node); - variant = match->data; - - mpp = devm_kzalloc(dev, variant->data_len, GFP_KERNEL); - - /* Get service */ - srv_np = of_parse_phandle(np, "rockchip,srv", 0); - srv_pdev = of_find_device_by_node(srv_np); - - mpp->srv = platform_get_drvdata(srv_pdev); - - mpp->dev = dev; - mpp->state = 0; - mpp->variant = variant; - - wake_lock_init(&mpp->wake_lock, WAKE_LOCK_SUSPEND, "mpp"); - atomic_set(&mpp->enabled, 0); - atomic_set(&mpp->power_on_cnt, 0); - atomic_set(&mpp->power_off_cnt, 0); - atomic_set(&mpp->total_running, 0); - atomic_set(&mpp->reset_request, 0); - - INIT_DELAYED_WORK(&mpp->power_off_work, mpp_power_off_work); - mpp->last.tv64 = 0; - - of_property_read_string(np, "name", (const char **)&name); - of_property_read_u32(np, "iommu_enabled", &mpp->iommu_enable); - - if (mpp->srv->reg_base == 0) { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mpp->reg_base = devm_ioremap_resource(dev, res); - if (IS_ERR(mpp->reg_base)) { - ret = PTR_ERR(mpp->reg_base); - goto err; - } - } else { - mpp->reg_base = mpp->srv->reg_base; - } - - mpp->irq = platform_get_irq(pdev, 0); - if (mpp->irq > 0) { - ret = devm_request_threaded_irq(dev, mpp->irq, - mpp_irq, mpp_isr, - IRQF_SHARED, dev_name(dev), - (void *)mpp); - if (ret) { - dev_err(dev, "error: can't request vepu irq %d\n", - mpp->irq); - goto err; - } - } else { - dev_info(dev, "No interrupt resource found\n"); - } - - mmu_np = of_parse_phandle(np, "iommus", 0); - if (mmu_np) { - struct platform_device *pd = NULL; - - pd = of_find_device_by_node(mmu_np); - mpp->mmu_dev = &pd->dev; - if (!mpp->mmu_dev) { - mpp->iommu_enable = false; - dev_err(dev, "get iommu dev failed"); - } - } else { - mpp->mmu_dev = - rockchip_get_sysmmu_dev(mpp->variant->mmu_dev_dts_name); - if (mpp->mmu_dev) { - platform_set_sysmmu(mpp->mmu_dev, dev); - rockchip_iovmm_set_fault_handler(dev, - mpp_sysmmu_fault_hdl); - } else { - dev_err(dev, - "get iommu dev %s failed, set iommu_enable to false\n", - mpp->variant->mmu_dev_dts_name); - mpp->iommu_enable = false; - } - } - - dev_info(dev, "try to get iommu dev %p\n", - mpp->mmu_dev); - - of_property_read_u32(np, "allocator", &allocator_type); - mpp->iommu_info = vpu_iommu_info_create(dev, mpp->mmu_dev, - allocator_type); - if (IS_ERR(mpp->iommu_info)) { - dev_err(dev, "failed to create ion client for mpp ret %ld\n", - PTR_ERR(mpp->iommu_info)); - } - - /* - * this session is global session, each dev - * only has one global session, and will be - * release when dev remove - */ - session = devm_kzalloc(dev, sizeof(*session), GFP_KERNEL); - - if (!session) - return -ENOMEM; - - session->mpp = mpp; - INIT_LIST_HEAD(&session->done); - INIT_LIST_HEAD(&session->list_session); - init_waitqueue_head(&session->wait); - atomic_set(&session->task_running, 0); - /* this first session of each dev is global session */ - list_add_tail(&session->list_session, &mpp->srv->session); - - ret = mpp->variant->hw_probe(mpp); - if (ret) - goto err; - - dev_info(dev, "resource ready, register device\n"); - /* create device node */ - ret = alloc_chrdev_region(&mpp->dev_t, 0, 1, name); - if (ret) { - dev_err(dev, "alloc dev_t failed\n"); - goto err; - } - - cdev_init(&mpp->cdev, &mpp_dev_fops); - - mpp->cdev.owner = THIS_MODULE; - mpp->cdev.ops = &mpp_dev_fops; - - ret = cdev_add(&mpp->cdev, mpp->dev_t, 1); - if (ret) { - unregister_chrdev_region(mpp->dev_t, 1); - dev_err(dev, "add dev_t failed\n"); - goto err; - } - - mpp->child_dev = device_create(mpp->srv->cls, dev, - mpp->dev_t, NULL, name); - - mpp_srv_attach(mpp->srv, &mpp->lnk_service); - - platform_set_drvdata(pdev, mpp); - - return 0; -err: - wake_lock_destroy(&mpp->wake_lock); - return ret; -} - -static int mpp_dev_remove(struct platform_device *pdev) -{ - struct rockchip_mpp_dev *mpp = platform_get_drvdata(pdev); - struct mpp_session *session = list_first_entry(&mpp->srv->session, - struct mpp_session, - list_session); - - mpp->variant->hw_remove(mpp); - - vpu_iommu_clear(mpp->iommu_info, session); - vpu_iommu_destroy(mpp->iommu_info); - kfree(session); - - mpp_srv_lock(mpp->srv); - cancel_delayed_work_sync(&mpp->power_off_work); - mpp_dev_power_off(mpp); - mpp_srv_detach(mpp->srv, &mpp->lnk_service); - mpp_srv_unlock(mpp->srv); - - device_destroy(mpp->srv->cls, mpp->dev_t); - cdev_del(&mpp->cdev); - unregister_chrdev_region(mpp->dev_t, 1); - - return 0; -} - -static struct platform_driver mpp_dev_driver = { - .probe = mpp_dev_probe, - .remove = mpp_dev_remove, - .driver = { - .name = "mpp_dev", -#if defined(CONFIG_OF) - .of_match_table = of_match_ptr(mpp_dev_dt_ids), -#endif - }, -}; - -static int __init mpp_dev_init(void) -{ - int ret = platform_driver_register(&mpp_dev_driver); - - if (ret) { - mpp_err("Platform device register failed (%d).\n", ret); - return ret; - } - - return ret; -} - -static void __exit mpp_dev_exit(void) -{ - platform_driver_unregister(&mpp_dev_driver); -} - -module_init(mpp_dev_init); -module_exit(mpp_dev_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_VERSION("1.0.build.201610121711"); -MODULE_AUTHOR("Alpha Lin alpha.lin@rock-chips.com"); -MODULE_DESCRIPTION("Rockchip mpp device driver"); diff --git a/drivers/video/rockchip/vpu/mpp_dev_common.h b/drivers/video/rockchip/vpu/mpp_dev_common.h deleted file mode 100644 index 9142f5e76ea0..000000000000 --- a/drivers/video/rockchip/vpu/mpp_dev_common.h +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd - * author: chenhengming chm@rock-chips.com - * Alpha Lin, alpha.lin@rock-chips.com - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * - */ - -#ifndef __ROCKCHIP_MPP_DEV_COMMON_H -#define __ROCKCHIP_MPP_DEV_COMMON_H - -#include -#include -#include -#include -#include -#include -#include - -#include