video: rockchip: mpp: new video codec driver

This video codec driver used in kernel 4.19 version,
which is a new design framework.
In this version, codecs integrate similar hardware,
so are divided into vdpux vepux rkvdec rkvenc vepu22,
and so on.

Change-Id: Ic29b28cef8fce394ac9f950472204c172842a2df
Signed-off-by: Ding Wei <leo.ding@rock-chips.com>
This commit is contained in:
Ding Wei
2019-10-16 15:54:04 +08:00
committed by Tao Huang
parent f641dbbc95
commit 5b15717952
31 changed files with 6759 additions and 8195 deletions

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <linux/cdev.h>
#include <linux/clk.h>
#include <linux/dma-buf.h>
#include <linux/kfifo.h>
#include <linux/types.h>
#include <linux/time.h>
#include <linux/workqueue.h>
#include <linux/reset.h>
#include <linux/irqreturn.h>
#include <linux/poll.h>
#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

View File

@@ -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 <linux/types.h>
/*
* 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

View File

@@ -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 <linux/dma-buf.h>
#include <linux/dma-iommu.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/slab.h>
#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;
}

View File

@@ -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 <linux/iommu.h>
#include <linux/dma-mapping.h>
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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <linux/completion.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/mfd/syscon.h>
#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");

View File

@@ -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 <asm/cacheflush.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <soc/rockchip/pm_domains.h>
#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);

View File

@@ -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 <asm/cacheflush.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <soc/rockchip/pm_domains.h>
#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);

View File

@@ -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 <asm/cacheflush.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <soc/rockchip/pm_domains.h>
#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);

View File

@@ -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 <asm/cacheflush.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <soc/rockchip/pm_domains.h>
#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);

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <linux/cdev.h>
#include <linux/dma-buf.h>
#include <linux/rockchip_ion.h>
#include <linux/rockchip-iovmm.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/wakelock.h>
#include <video/rk_vpu_service.h>
extern int mpp_dev_debug;
#define MPP_IOC_CUSTOM_BASE 0x1000
/*
* 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_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
#define DEBUG
#ifdef DEBUG
#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)
#else
#define mpp_debug_func(level, fmt, args...)
#define mpp_debug(level, fmt, args...)
#endif
#define mpp_debug_enter() mpp_debug_func(DEBUG_FUNCTION, "enter\n")
#define mpp_debug_leave() mpp_debug_func(DEBUG_FUNCTION, "leave\n")
#define mpp_err(fmt, args...) \
pr_err("%s:%d: " fmt, __func__, __LINE__, ##args)
struct mpp_trans_info {
const int count;
const char * const table;
};
enum RKVENC_MODE {
RKVENC_MODE_NONE,
RKVENC_MODE_ONEFRAME,
RKVENC_MODE_LINKTABLE_FIX,
RKVENC_MODE_LINKTABLE_UPDATE,
RKVENC_MODE_NUM
};
struct rockchip_mpp_dev;
struct mpp_service;
struct mpp_ctx;
struct mpp_mem_region {
struct list_head srv_lnk;
struct list_head reg_lnk;
struct list_head session_lnk;
/* virtual address for iommu */
unsigned long iova;
unsigned long len;
u32 reg_idx;
int hdl;
};
/**
* struct for process register set
*
* @author ChenHengming (2011-5-4)
*/
struct mpp_ctx {
/* context belong to */
struct rockchip_mpp_dev *mpp;
struct mpp_session *session;
/* link to service session */
struct list_head session_link;
/* link to service list */
struct list_head status_link;
struct list_head mem_region_list;
/* record context running start time */
struct timeval start;
};
enum vpu_ctx_state {
MMU_ACTIVATED = BIT(0),
};
struct extra_info_elem {
u32 index;
u32 offset;
};
#define EXTRA_INFO_MAGIC 0x4C4A46
struct extra_info_for_iommu {
u32 magic;
u32 cnt;
struct extra_info_elem elem[20];
};
struct rockchip_mpp_dev_variant {
u32 data_len;
u32 reg_len;
struct mpp_trans_info *trans_info;
char *mmu_dev_dts_name;
int (*hw_probe)(struct rockchip_mpp_dev *mpp);
void (*hw_remove)(struct rockchip_mpp_dev *mpp);
void (*power_on)(struct rockchip_mpp_dev *mpp);
void (*power_off)(struct rockchip_mpp_dev *mpp);
int (*reset)(struct rockchip_mpp_dev *mpp);
};
struct rockchip_mpp_dev {
struct mpp_dev_ops *ops;
struct cdev cdev;
dev_t dev_t;
struct device *child_dev;
int irq;
struct mpp_service *srv;
void __iomem *reg_base;
struct list_head lnk_service;
struct device *dev;
unsigned long state;
struct vpu_iommu_info *iommu_info;
const struct rockchip_mpp_dev_variant *variant;
struct device *mmu_dev;
u32 iommu_enable;
struct wake_lock wake_lock;
struct delayed_work power_off_work;
/* record previous power-on time */
ktime_t last;
atomic_t power_on_cnt;
atomic_t power_off_cnt;
atomic_t total_running;
atomic_t enabled;
atomic_t reset_request;
};
/**
* struct mpp_dev_ops - context specific operations for mpp_device
*
* @init Prepare for registers file for specific hardware.
* @prepare Check HW status for determining run next task or not.
* @run Start a single {en,de}coding run. Set registers to hardware.
* @done Read back processing results and additional data from hardware.
* @result Read status to userspace.
* @deinit Release the resource allocate during init.
* @ioctl ioctl for special HW besides the common ioctl.
* @irq interrupt service for specific hardware.
* @open a specific instance open operation for hardware.
* @release a specific instance release operation for hardware.
*/
struct mpp_dev_ops {
/* size: in bytes, data sent from userspace, length in bytes */
struct mpp_ctx *(*init)(struct rockchip_mpp_dev *mpp,
struct mpp_session *session,
void __user *src, u32 size);
int (*prepare)(struct rockchip_mpp_dev *mpp);
int (*run)(struct rockchip_mpp_dev *mpp);
int (*done)(struct rockchip_mpp_dev *mpp);
int (*irq)(struct rockchip_mpp_dev *mpp);
int (*result)(struct rockchip_mpp_dev *mpp, struct mpp_ctx *ctx,
u32 __user *dst);
void (*deinit)(struct rockchip_mpp_dev *mpp);
long (*ioctl)(struct mpp_session *isession,
unsigned int cmd, unsigned long arg);
struct mpp_session *(*open)(struct rockchip_mpp_dev *mpp);
void (*release)(struct mpp_session *session);
void (*free)(struct mpp_session *session);
};
void mpp_dump_reg(void __iomem *regs, int count);
void mpp_dump_reg_mem(u32 *regs, int count);
int mpp_reg_address_translate(struct rockchip_mpp_dev *data,
u32 *reg,
struct mpp_ctx *ctx,
int idx);
void mpp_translate_extra_info(struct mpp_ctx *ctx,
struct extra_info_for_iommu *ext_inf,
u32 *reg);
int mpp_dev_common_ctx_init(struct rockchip_mpp_dev *mpp, struct mpp_ctx *cfg);
void mpp_dev_common_ctx_deinit(struct rockchip_mpp_dev *mpp,
struct mpp_ctx *ctx);
void mpp_dev_power_on(struct rockchip_mpp_dev *mpp);
void mpp_dev_power_off(struct rockchip_mpp_dev *mpp);
bool mpp_dev_is_power_on(struct rockchip_mpp_dev *mpp);
static inline void mpp_write_relaxed(struct rockchip_mpp_dev *mpp,
u32 val, u32 reg)
{
mpp_debug(DEBUG_SET_REG, "MARK: set reg[%03d]: %08x\n", reg / 4, val);
writel_relaxed(val, mpp->reg_base + reg);
}
static inline void mpp_write(struct rockchip_mpp_dev *mpp,
u32 val, u32 reg)
{
mpp_debug(DEBUG_SET_REG, "MARK: set reg[%03d]: %08x\n", reg / 4, val);
writel(val, mpp->reg_base + reg);
}
static inline u32 mpp_read(struct rockchip_mpp_dev *mpp, u32 reg)
{
u32 val = readl(mpp->reg_base + reg);
mpp_debug(DEBUG_GET_REG, "MARK: get reg[%03d] 0x%x: %08x\n", reg / 4,
reg, val);
return val;
}
static inline void mpp_time_record(struct mpp_ctx *ctx)
{
if (unlikely(mpp_dev_debug & DEBUG_TIMING) && ctx)
do_gettimeofday(&ctx->start);
}
static inline void mpp_time_diff(struct mpp_ctx *ctx)
{
struct timeval end;
do_gettimeofday(&end);
mpp_debug(DEBUG_TIMING, "consume: %ld us\n",
(end.tv_sec - ctx->start.tv_sec) * 1000000 +
(end.tv_usec - ctx->start.tv_usec));
}
extern const struct rockchip_mpp_dev_variant rkvenc_variant;
extern const struct rockchip_mpp_dev_variant vepu_variant;
extern const struct rockchip_mpp_dev_variant h265e_variant;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,155 +0,0 @@
/*
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: hehua,hh@rock-chips.com
* lixinhuang, buluess.li@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_H265E_H
#define __ROCKCHIP_MPP_DEV_H265E_H
#include "mpp_dev_h265e_define.h"
#include "mpp_service.h"
#include <linux/ioctl.h>
#include <linux/wakelock.h>
#define MPP_DEV_H265E_SET_COLOR_PALETTE \
_IOW(VPU_IOC_MAGIC, MPP_IOC_CUSTOM_BASE + 1, u32)
#define MPP_DEV_H265E_SET_PARAMETER \
_IOW(VPU_IOC_MAGIC, \
MPP_IOC_CUSTOM_BASE + 6, struct mpp_h265e_cfg)
#define MPP_DEV_H265E_GET_HEAD_PARAMETER \
_IOW(VPU_IOC_MAGIC, \
MPP_IOC_CUSTOM_BASE + 7, struct hal_h265e_header)
#define H265E_INSTANCE_NUM 4
enum H265E_MODE {
H265E_MODE_NONE,
H265E_MODE_ONEFRAME,
H265E_MODE_LINKTABLE_FIX,
H265E_MODE_LINKTABLE_UPDATE,
H265E_MODE_NUM
};
struct regmap;
struct h265e_result {
u32 bs_size;
u32 enc_pic_cnt;
u32 pic_type;
u32 num_of_slice;
u32 pick_skipped;
u32 num_intra;
u32 num_merge;
u32 num_skip_block;
u32 avg_ctu_qp;
int recon_frame_index;
u32 gop_idx;
u32 poc;
u32 src_idx;
u32 fail_reason;
};
struct mpp_h265e_buffer {
unsigned long dma_addr;
u32 size;
int hdl;
};
struct mpp_h265e_frame_buffer {
struct mpp_h265e_buffer buffer;
u32 y;
u32 cb;
u32 cr;
};
struct h265e_ctx {
struct mpp_ctx ictx;
enum H265E_MODE mode;
struct mpp_h265e_buffer bs;
char __iomem *bs_data;/*for debug read data*/
struct mpp_h265e_buffer src;
struct mpp_h265e_buffer roi;
struct mpp_h265e_buffer ctu;
struct mpp_h265e_encode_info cfg;
/* store status read from hw, oneframe mode used only */
struct h265e_result result;
};
enum H265E_INSTANCE_STATUS {
H265E_INSTANCE_STATUS_ERROR,
H265E_INSTANCE_STATUS_OPENED,
H265E_INSTANCE_STATUS_SET_PARAMETER,
H265E_INSTANCE_STATUS_ENCODE,
H265E_INSTANCE_STATUS_CLOSE
};
struct rockchip_h265e_instance {
int index;
atomic_t is_used;
struct mpp_h265e_buffer work;
struct mpp_h265e_buffer temp;
struct mpp_h265e_buffer mv;
struct mpp_h265e_buffer fbc_luma;
struct mpp_h265e_buffer fbc_chroma;
struct mpp_h265e_buffer sub_sample;
/*
* for recon frames
*/
struct mpp_h265e_frame_buffer frame_buffer[16];
int min_frame_buffer_count;
int min_src_frame_count;
int src_idx;
int status;
struct mpp_h265e_cfg cfg;
struct mpp_session *session;
};
struct rockchip_h265e_dev {
struct rockchip_mpp_dev dev;
struct rockchip_h265e_instance instance[H265E_INSTANCE_NUM];
struct mpp_h265e_buffer temp;
u32 lkt_index;
u32 irq_status;
atomic_t is_init;
atomic_t load_firmware;
struct delayed_work work_list;
struct mutex lock;
char __iomem *firmware_cpu_addr;
struct mpp_h265e_buffer firmware;
struct clk *aclk;
struct clk *aclk_axi2sram;
struct clk *pclk;
struct clk *core;
struct clk *dsp;
void __iomem *grf_base;
u32 mode_bit;
u32 mode_ctrl;
struct regmap *grf;
};
struct h265e_session {
struct mpp_session isession;
int instance_index;
};
#endif

View File

@@ -1,759 +0,0 @@
/*
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: hehua,hh@rock-chips.com
* lixinhuang, buluess.li@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 __MPP_DEV_H265E_DEFINE_H__
#define __MPP_DEV_H265E_DEFINE_H__
#include <linux/bitops.h>
#define H265E_MVCOL_BUF_SIZE(w, h) \
((((w) + 63) / 64) * (((h) + 63) / 64) * 128)
#define H265E_FBC_LUMA_TABLE_SIZE(w, h) \
((((h) + 15) / 16) * (((w) + 255) / 256) * 128)
#define H265E_FBC_CHROMA_TABLE_SIZE(w, h) \
((((h) + 15) / 16) * (((w) / 2 + 255) / 256) * 128)
#define H265E_SUBSAMPLED_ONE_SIZE(w, h) \
(((((w) / 4) + 15) & ~15) * ((((h) / 4) + 7) & ~7))
#define H265E_PIC_TYPE_I 0
#define H265E_PIC_TYPE_P 1
enum H265E_VPU_COMMAND {
H265E_CMD_INIT_VPU = 0x0001,
H265E_CMD_SET_PARAM = 0x0002,
H265E_CMD_FINI_SEQ = 0x0004,
H265E_CMD_ENC_PIC = 0x0008,
H265E_CMD_SET_FRAMEBUF = 0x0010,
H265E_CMD_FLUSH_DECODER = 0x0020,
H265E_CMD_GET_FW_VERSION = 0x0100,
H265E_CMD_QUERY_DECODER = 0x0200,
H265E_CMD_SLEEP_VPU = 0x0400,
H265E_CMD_WAKEUP_VPU = 0x0800,
H265E_CMD_CREATE_INSTANCE = 0x4000,
H265E_CMD_RESET_VPU = 0x10000,
H265E_CMD_MAX_VPU_COMD = 0x10000,
};
enum H265E_PIC_CODE_OPTION {
CODEOPT_ENC_HEADER_IMPLICIT = BIT(0),
CODEOPT_ENC_VCL = BIT(1),
CODEOPT_ENC_VPS = BIT(2),
CODEOPT_ENC_SPS = BIT(3),
CODEOPT_ENC_PPS = BIT(4),
CODEOPT_ENC_AUD = BIT(5),
CODEOPT_ENC_EOS = BIT(6),
CODEOPT_ENC_EOB = BIT(7),
CODEOPT_ENC_RESERVED = BIT(8),
CODEOPT_ENC_VUI = BIT(9),
};
enum H265E_TILED_MAP_TYPE {
LINEAR_FRAME_MAP = 0,
TILED_FRAME_V_MAP = 1,
TILED_FRAME_H_MAP = 2,
TILED_FIELD_V_MAP = 3,
TILED_MIXED_V_MAP = 4,
TILED_FRAME_MB_RASTER_MAP = 5,
TILED_FIELD_MB_RASTER_MAP = 6,
TILED_FRAME_NO_BANK_MAP = 7,
TILED_FIELD_NO_BANK_MAP = 8,
LINEAR_FIELD_MAP = 9,
CODA_TILED_MAP_TYPE_MAX = 10,
COMPRESSED_FRAME_MAP = 10,
TILED_SUB_CTU_MAP = 11,
ARM_COMPRESSED_FRAME_MAP = 12,
};
#define H265E_MAX_NUM_TEMPORAL_LAYER 7
#define H265E_MAX_GOP_NUM 8
#define H265E_MIN_PIC_WIDTH 256
#define H265E_MIN_PIC_HEIGHT 128
#define H265E_MAX_PIC_WIDTH 1920
#define H265E_MAX_PIC_HEIGHT 1080
#define MAX_ROI_NUMBER 64
enum H265E_GOP_PRESET_IDX {
PRESET_IDX_CUSTOM_GOP = 0,
PRESET_IDX_ALL_I = 1,
PRESET_IDX_IPP = 2,
PRESET_IDX_IPPPP = 6,
};
enum H265E_SET_PARAM_OPTION {
H265E_OPT_COMMON = 0,
H265E_OPT_CUSTOM_GOP = 1,
H265E_OPT_CUSTOM_HEADER = 2,
H265E_OPT_VUI = 3,
H265E_OPT_ALL_PARAM = 0xffffffff
};
enum H265E_PARAM_CHANEGED {
H265E_PARAM_CHANEGED_COMMON = 1,
H265E_PARAM_CHANEGED_CUSTOM_GOP = 2,
H265E_PARAM_CHANEGED_VUI = 4,
H265E_PARAM_CHANEGED_REGISTER_BUFFER = 8,
};
enum H265E_COMON_CFG_MASK {
/* COMMON parameters*/
H265E_CFG_SEQ_SRC_SIZE_CHANGE = BIT(0),
H265E_CFG_SEQ_PARAM_CHANGE = BIT(1),
H265E_CFG_GOP_PARAM_CHANGE = BIT(2),
H265E_CFG_INTRA_PARAM_CHANGE = BIT(3),
H265E_CFG_CONF_WIN_TOP_BOT_CHANGE = BIT(4),
H265E_CFG_CONF_WIN_LEFT_RIGHT_CHANGE = BIT(5),
H265E_CFG_FRAME_RATE_CHANGE = BIT(6),
H265E_CFG_INDEPENDENT_SLICE_CHANGE = BIT(7),
H265E_CFG_DEPENDENT_SLICE_CHANGE = BIT(8),
H265E_CFG_INTRA_REFRESH_CHANGE = BIT(9),
H265E_CFG_PARAM_CHANGE = BIT(10),
H265E_CFG_CHANGE_RESERVED = BIT(11),
H265E_CFG_RC_PARAM_CHANGE = BIT(12),
H265E_CFG_RC_MIN_MAX_QP_CHANGE = BIT(13),
H265E_CFG_RC_TARGET_RATE_LAYER_0_3_CHANGE = BIT(14),
H265E_CFG_RC_TARGET_RATE_LAYER_4_7_CHANGE = BIT(15),
H265E_CFG_SET_NUM_UNITS_IN_TICK = BIT(18),
H265E_CFG_SET_TIME_SCALE = BIT(19),
H265E_CFG_SET_NUM_TICKS_POC_DIFF_ONE = BIT(20),
H265E_CFG_RC_TRANS_RATE_CHANGE = BIT(21),
H265E_CFG_RC_TARGET_RATE_CHANGE = BIT(22),
H265E_CFG_ROT_PARAM_CHANGE = BIT(23),
H265E_CFG_NR_PARAM_CHANGE = BIT(24),
H265E_CFG_NR_WEIGHT_CHANGE = BIT(25),
H265E_CFG_SET_VCORE_LIMIT = BIT(27),
H265E_CFG_CHANGE_SET_PARAM_ALL = (0xFFFFFFFF),
};
/**
* @brief This is a data structure for setting
* CTU level options (ROI, CTU mode, CTU QP) in HEVC encoder.
*/
struct h265e_ctu {
u32 roi_enable;
u32 roi_delta_qp;
u32 map_endian;
/*
* Stride of CTU-level ROI/mode/QP map
* Set this with (Width + CTB_SIZE - 1) / CTB_SIZE
*/
u32 map_stride;
/*
* It enables CTU QP map that allows
* CTUs to be encoded with the given QPs.
* NOTE: rcEnable should be turned off for this,
* encoding with the given CTU QPs.
*/
u32 ctu_qp_enable;
};
struct h265e_sei {
u8 prefix_sei_nal_enable;
/*
* A flag whether to encode PREFIX_SEI_DATA
* with a picture of this command or with a source
* picture of the buffer at the moment
* 0 : encode PREFIX_SEI_DATA when a source picture is encoded.
* 1 : encode PREFIX_SEI_DATA at this command.
*/
u8 prefix_sei_data_order;
/*
* enables to encode the suffix SEI NAL which is given by host.
*/
u8 suffix_sei_nal_enable;
/*
* A flag whether to encode SUFFIX_SEI_DATA
* with a picture of this command or with a source
* picture of the buffer at the moment
* 0 : encode SUFFIX_SEI_DATA when a source picture is encoded.
* 1 : encode SUFFIX_SEI_DATA at this command.
*/
u8 suffix_sei_data_enc_order;
/*
* The total byte size of the prefix SEI
*/
u32 prefix_sei_data_size;
/*
* The start address of the total prefix SEI NALs to be encoded
*/
u32 prefix_sei_nal_addr;
/*
* The total byte size of the suffix SEI
*/
u32 suffix_sei_data_size;
/*
* The start address of the total suffix SEI NALs to be encoded
*/
u32 suffix_sei_nal_addr;
};
/**
* @brief This is a data structure for setting
* VUI parameters in HEVC encoder.
*/
struct h265e_vui {
/*
* VUI parameter flag
*/
u32 flags;
/**< aspect_ratio_idc */
u32 aspect_ratio_idc;
/**< sar_width, sar_height
* (only valid when aspect_ratio_idc is equal to 255)
*/
u32 sar_size;
/**< overscan_appropriate_flag */
u32 over_scan_appropriate;
/**< VUI parameter flag */
u32 signal;
/**< chroma_sample_loc_type_top_field,
*chroma_sample_loc_type_bottom_field
*/
u32 chroma_sample_loc;
/**< def_disp_win_left_offset, def_disp_win_right_offset */
u32 disp_win_left_right;
/**< def_disp_win_top_offset, def_disp_win_bottom_offset */
u32 disp_win_top_bottom;
};
/**
* @brief This is a data structure for
*custom GOP parameters of the given picture.
*/
struct h265e_custom_gop_pic {
/**< A picture type of #th picture in the custom GOP */
u32 type;
/**< A POC offset of #th picture in the custom GOP */
u32 offset;
/**< A quantization parameter of #th picture in the custom GOP */
u32 qp;
/**< POC offset of reference L0 of #th picture in the custom GOP */
u32 ref_poc_l0;
/**< POC offset of reference L1 of #th picture in the custom GOP */
u32 ref_poc_l1;
/**< A temporal ID of #th picture in the custom GOP */
u32 temporal_id;
};
/**
* @brief This is a data structure for custom GOP parameters.
*/
struct h265e_custom_gop {
/**< Size of the custom GOP (0~8) */
u32 custom_gop_size;
/**< It derives a lamda weight internally
* instead of using lamda weight specified.
*/
u32 use_derive_lambda_weight;
/**< picture parameters of #th picture in the custom gop */
struct h265e_custom_gop_pic pic[H265E_MAX_GOP_NUM];
/**< a lamda weight of #th picture in the custom gop */
u32 gop_pic_lambda[H265E_MAX_GOP_NUM];
};
struct enc_code_opt {
/**< whether host encode a header implicitly or not.
* if this value is 1, below encode options will be ignored
*/
int implicit_header_encode;
int encode_vcl;/**< a flag to encode vcl nal unit explicitly*/
int encode_vps;/**< a flag to encode vps nal unit explicitly*/
int encode_sps;/**< a flag to encode sps nal unit explicitly*/
int encode_pps;/**< a flag to encode pps nal unit explicitly*/
int encode_aud;/**< a flag to encode aud nal unit explicitly*/
int encode_eos;/**< a flag to encode eos nal unit explicitly*/
int encode_eob;/**< a flag to encode eob nal unit explicitly*/
int encode_vui;/**< a flag to encode vui nal unit explicitly*/
};
enum H265E_SRC_FORMAT {
H265E_SRC_YUV_420 = 0,
H265E_SRC_YUV_420_YU12 = 0, /* 3Plane 1.Y, 2.U, 3.V*/
H265E_SRC_YUV_420_YV12, /* 3Plane 1.Y, 2.V, 3.U*/
H265E_SRC_YUV_420_NV12, /* 2 Plane 1.Y 2. UV*/
H265E_SRC_YUV_420_NV21, /* 2 Plane 1.Y 2. VU*/
H265E_SRC_YUV_420_MAX,
};
struct hal_h265e_header {
u32 buf;
u32 size;
};
struct mpp_h265e_cfg {
/*
* A profile indicator
* 1 : main
* 2 : main10
*/
u8 profile;
/*
* only support to level 4.1
*/
u8 level; /**< A level indicator (level * 10) */
/*
* A tier indicator
* 0 : main
* 1 : high
*/
u8 tier;
/*
* A chroma format indecator, only support YUV420
*/
u8 chroma_idc;
/*
* the source's width and height
*/
u16 width;
u16 height;
u16 width_stride;
u16 height_stride;
/*
* bitdepth,only support 8 bits(only support 8 bits)
*/
u8 bit_depth;
/*
* source yuv's format. The value is defined
* in H265E_FrameBufferFormat(only support YUV420)
* the value could be YU12,YV12,NV12,NV21
*/
u8 src_format;
u8 src_endian;
u8 bs_endian;
u8 fb_endian;
u8 frame_rate;
u8 frame_skip;
u32 bit_rate;
u32 map_type;
u32 line_buf_int_en;
u32 slice_int_enable;
u32 ring_buffer_enable;
struct enc_code_opt code_option;
/*
* A chroma format indecator, only support YUV420
*/
int lossless_enable;/**< It enables lossless coding */
/**< It enables constrained intra prediction */
int const_intra_pred_flag;
/**< The value of chroma(cb) qp offset (only for WAVE420L) */
int chroma_cb_qp_offset;
/**< The value of chroma(cr) qp offset (only for WAVE420L) */
int chroma_cr_qp_offset;
/**
* A GOP structure option
* 0: Custom GOP
* 1 : I-I-I-I,..I (all intra, gop_size=1)
* 2 : I-P-P-P,... P (consecutive P, gop_size=1)
* 6 : I-P-P-P-P (consecutive P, gop_size=4)
*/
u32 gop_idx;
/**
* An intra picture refresh mode
* 0 : Non-IRAP
* 1 : CRA
* 2 : IDR
*/
u32 decoding_refresh_type;
/*
* A quantization parameter of intra picture
*/
u32 intra_qp;
/*
* A period of intra picture in GOP size
*/
u32 intra_period;
/** A conformance window size of TOP,BUTTOM,LEFT,RIGHT */
u16 conf_win_top;
u16 conf_win_bot;
u16 conf_win_left;
u16 conf_win_right;
/*
* A slice mode for independent slice
* 0 : no multi-slice
* 1 : Slice in CTU number
* 2 : Slice in number of byte
*/
u32 independ_slice_mode;
/*
* The number of CTU or bytes for a slice
* when independ_slice_mode is set with 1 or 2.
*/
u32 independ_slice_mode_arg;
/**
*A slice mode for dependent slice
* 0 : no multi-slice
* 1 : Slice in CTU number
* 2 : Slice in number of byte
*/
u32 depend_slice_mode;
/*
* The number of CTU or bytes for a slice
* when depend_slice_mode is set with 1 or 2.
*/
u32 depend_slice_mode_arg;
/*
* An intra refresh mode
* 0 : No intra refresh
* 1 : Row
* 2 : Column
* 3 : Step size in CTU
*/
u32 intra_refresh_mode;
/*
* The number of CTU (only valid when intraRefreshMode is 3.)
*/
u32 intra_refresh_arg;
/*
* It uses one of the recommended encoder parameter presets.
* 0 : Custom
* 1 : Recommend enc params
* (slow encoding speed, highest picture quality)
* 2 : Boost mode (normal encoding speed, normal picture quality)
* 3 : Fast mode (high encoding speed, low picture quality)
*/
u8 use_recommend_param;
u8 scaling_list_enable; /**< It enables a scaling list */
/*
* It specifies CU size.
* 3'b001: 8x8
* 3'b010: 16x16
* 3'b100 : 32x32
*/
u8 cu_size_mode;
u8 tmvp_enable;
u8 wpp_enable; /**< It enables wave-front parallel processing. */
u8 max_num_merge; /**< Maximum number of merge candidates (0~2) */
u8 dynamic_merge_8x8_enable;
u8 dynamic_merge_16x16_enable;
u8 dynamic_merge_32x32_enable;
u8 disable_deblk; /**< It disables in-loop deblocking filtering. */
/**< it enables filtering across slice
* boundaries for in-loop deblocking.
*/
u8 lf_cross_slice_boundary_enable;
/**< BetaOffsetDiv2 for deblocking filter */
u8 beta_offset_div2;
/**< TcOffsetDiv3 for deblocking filter */
u8 tc_offset_div2;
/**< It enables transform skip for an intra CU. */
u8 skip_intra_trans;
/**< It enables SAO (sample adaptive offset). */
u8 sao_enable;
/**< It enables to make intra CUs in an inter slice. */
u8 intra_in_inter_slice_enable;
/**< It enables intra NxN PUs. */
u8 intra_nxn_enable;
/*
* specifies intra QP offset relative
* to inter QP (Only available when rc_enable is enabled)
*/
s8 intra_qp_offset;
/*
* It specifies encoder initial delay,
* Only available when RateControl is enabled
* (encoder initial delay = initial_delay * init_buf_levelx8 / 8)
*/
int init_buf_levelx8;
/*
* specifies picture bits allocation mode.
* Only available when RateControl is enabled
* and GOP size is larger than 1
* 0: More referenced pictures have
* better quality than less referenced pictures
* 1: All pictures in a GOP have similar image quality
* 2: Each picture bits in a GOP is allocated according to FixedRatioN
*/
u8 bit_alloc_mode;
/*
* A fixed bit ratio (1 ~ 255) for each picture of GOP's bitallocation
* N = 0 ~ (MAX_GOP_SIZE - 1)
* MAX_GOP_SIZE = 8
* For instance when MAX_GOP_SIZE is 3, FixedBitRaio0
* to FixedBitRaio2 can be set as 2, 1, and 1 respectively for
* the fixed bit ratio 2:1:1. This is only valid when BitAllocMode is 2.
*/
u8 fixed_bit_ratio[H265E_MAX_GOP_NUM];
/*
* enable rate control
*/
u32 rc_enable;
/*
* enable CU level rate control
*/
u8 cu_level_rc_enable;
/*
* enable CU QP adjustment for subjective quality enhancement
*/
u8 hvs_qp_enable;
/*
* enable QP scaling factor for CU QP adjustment when hvs_qp_enable = 1
*/
u8 hvs_qp_scale_enable;
/*
* A QP scaling factor for CU QP adjustment when hvs_qp_enable = 1
*/
s8 hvs_qp_scale;
/*
* A minimum QP for rate control
*/
u8 min_qp;
/*
* A maximum QP for rate control
*/
u8 max_qp;
/*
* A maximum delta QP for rate control
*/
u8 max_delta_qp;
/*
* A peak transmission bitrate in bps
*/
u32 trans_rate;
/*< It specifies the number of time units of
* a clock operating at the frequency time_scale Hz
*/
u32 num_units_in_tick;
/**< It specifies the number of time units that pass in one second */
u32 time_scale;
/**< It specifies the number of clock ticks corresponding to a
* difference of picture order count values equal to 1
*/
u32 num_ticks_poc_diff_one;
/*< The value of initial QP by host.
* This value is meaningless if INITIAL_RC_QP == 63
*/
int initial_rc_qp;
/*
* enables noise reduction algorithm to Y/Cb/Cr component.
*/
u8 nr_y_enable;
u8 nr_cb_enable;
u8 nr_cr_enable;
/*
* enables noise estimation for reduction. When this is disabled,
* noise estimation is carried out ouside VPU.
*/
u8 nr_noise_est_enable;
/*
* It specifies Y/Cb/Cr noise standard deviation
* if no use of noise estimation (nr_noise_est_enable=0)
*/
u8 nr_noise_sigma_y;
u8 nr_noise_sigma_cb;
u8 nr_noise_sigma_cr;
/* ENC_NR_WEIGHT*/
/*< A weight to Y noise level for intra picture (0 ~ 31).
* nr_intra_weight_y/4 is multiplied to the noise
* level that has been estimated.
* This weight is put for intra frame to be filtered more strongly or
* more weakly than just with the estimated noise level.
*/
u8 nr_intra_weight_y;
/**< A weight to Cb noise level for intra picture (0 ~ 31). */
u8 nr_intra_weight_cb;
/**< A weight to Cr noise level for intra picture (0 ~ 31). */
u8 nr_intra_weight_cr;
/*< A weight to Y noise level for inter picture (0 ~ 31).
* nr_inter_weight_y/4 is multiplied to the noise
* level that has been estimated.
* This weight is put for inter frame to be filtered more strongly or
* more weakly than just with the estimated noise level.
*/
u8 nr_inter_weight_y;
/**< A weight to Cb noise level for inter picture (0 ~ 31). */
u8 nr_inter_weight_cb;
/**< A weight to Cr noise level for inter picture (0 ~ 31). */
u8 nr_inter_weight_cr;
/*
* a minimum QP for intra picture (0 ~ 51).
* It is only available when rc_enable is 1.
*/
u8 intra_min_qp;
/*
* a maximum QP for intra picture (0 ~ 51).
* It is only available when rc_enable is 1.
*/
u8 intra_max_qp;
u32 initial_delay;
u8 hrd_rbsp_in_vps;
u8 hrd_rbsp_in_vui;
u32 vui_rbsp;
u32 hrd_rbsp_data_size; /**< The size of the HRD rbsp data */
u32 hrd_rbsp_data_addr; /**< The address of the HRD rbsp data */
u32 vui_rbsp_data_size; /**< The size of the VUI rbsp data */
u32 vui_rbsp_data_addr; /**< The address of the VUI rbsp data */
u8 use_long_term;
u8 use_cur_as_longterm_pic;
u8 use_longterm_ref;
struct h265e_custom_gop gop;
struct h265e_ctu ctu;
struct h265e_vui vui;
struct h265e_sei sei;
/*
* define which type of parameters are changed,
* only support common parameter chanegd now,
* see H265eCommonCfgMask
*/
u32 cfg_option;
/*
* define which parameters are changed,see H265E_SET_PARAM_OPTION
*/
u32 cfg_mask;
};
struct mpp_h265e_encode_info {
/*
* the address of source(yuv) data for encoding
*/
u32 src_fd;
/*
* the size of source(yuv) data for encoding
*/
u32 src_size;
/*
* the address of bitstream buffer
*/
u32 bs_fd;
/*
* the size of bitstream buffer
*/
u32 bs_size;
u32 roi_fd;
u32 ctu_qp_fd;
u32 stream_end;
/*
* skip current frame
*/
u32 skip_pic;
/*
* A flag to use a force picture quantization parameter
*/
u32 force_qp_enable;
/*
*Force picture quantization parameter for I picture
*/
u32 force_qp_i;
/*
* Force picture quantization parameter for P picture
*/
u32 force_qp_p;
/*
* A flag to use a force picture type
*/
u32 force_frame_type_enable;
/*
* A force picture type (I, P, B, IDR, CRA)
*/
u32 force_frame_type;
};
enum INTERRUPT_BIT {
INT_BIT_INIT = 0,
INT_BIT_SEQ_INIT = 1,
INT_BIT_SEQ_END = 2,
INT_BIT_PIC_RUN = 3,
INT_BIT_FRAMEBUF_SET = 4,
INT_BIT_ENC_HEADER = 5,
INT_BIT_DEC_PARA_SET = 7,
INT_BIT_DEC_BUF_FLUSH = 8,
INT_BIT_USERDATA = 9,
INT_BIT_DEC_FIELD = 10,
INT_BIT_DEC_MB_ROWS = 13,
INT_BIT_BIT_BUF_EMPTY = 14,
INT_BIT_BIT_BUF_FULL = 15
};
enum H265E_INTERRUPT_BIT {
INT_H265E_INIT = 0,
INT_H265E_DEC_PIC_HDR = 1,
INT_H265E_FINI_SEQ = 2,
INT_H265E_ENC_PIC = 3,
INT_H265E_SET_FRAMEBUF = 4,
INT_H265E_FLUSH_DECODER = 5,
INT_H265E_GET_FW_VERSION = 8,
INT_H265E_QUERY_DECODER = 9,
INT_H265E_SLEEP_VPU = 10,
INT_H265E_WAKEUP_VPU = 11,
INT_H265E_CHANGE_INST = 12,
INT_H265E_CREATE_INSTANCE = 14,
INT_H265E_BIT_BUF_EMPTY = 15,
INT_H265E_BIT_BUF_FULL = 15, /* Encoder */
};
#endif

View File

@@ -1,235 +0,0 @@
/*
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: hehua,hh@rock-chips.com
* lixinhuang, buluess.li@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 _MPP_DEV_H265E_REGISTER_H_
#define _MPP_DEV_H265E_REGISTER_H_
#define H265E_PO_CONF 0x0000
#define H265E_VCPU_CUR_PC 0x0004
#define H265E_VPU_PDBG_CTRL 0x0010
#define H265E_VPU_PDBG_IDX_REG 0x0014
#define H265E_VPU_PDBG_WDATA_REG 0x0018
#define H265E_VPU_PDBG_RDATA_REG 0x001C
#define H265E_VPU_FIO_CTRL_ADDR 0x0020
#define H265E_VPU_FIO_DATA 0x0024
#define H265E_VPU_VINT_REASON_USR 0x0030
#define H265E_VPU_VINT_REASON_CLR 0x0034
#define H265E_VPU_HOST_INT_REQ 0x0038
#define H265E_VPU_VINT_CLEAR 0x003C
#define H265E_VPU_HINT_CLEAR 0x0040
#define H265E_VPU_VPU_INT_STS 0x0044
#define H265E_VPU_VINT_ENABLE 0x0048
#define H265E_CMD_REG_END 0x0200
#define H265E_VPU_VINT_REASON 0x004c
#define H265E_VPU_RESET_REQ 0x0050
#define H265E_VPU_RESET_STATUS 0x0070
#define H265E_VPU_REMAP_CTRL 0x0060
#define H265E_VPU_REMAP_VADDR 0x0064
#define H265E_VPU_REMAP_PADDR 0x0068
#define H265E_VPU_REMAP_CORE_START 0x006C
#define H265E_VPU_BUSY_STATUS 0x0070
#define H265E_COMMAND 0x0100
#define H265E_CORE_INDEX 0x0104
#define H265E_INST_INDEX 0x0108
#define H265E_ENC_SET_PARAM_OPTION 0x010C
#define H265E_RET_FW_VERSION 0x0118
#define H265E_ADDR_CODE_BASE 0x0118
#define H265E_CODE_SIZE 0x011C
#define H265E_CODE_PARAM 0x0120
#define H265E_HW_OPTION 0x0124
#define H265E_RET_SUCCESS 0x0110
#define H265E_VPU_HOST_INT_REQ 0x0038
#define H265E_SFB_OPTION 0x010C
#define H265E_RET_FAIL_REASON 0x0114
#define H265E_BS_START_ADDR 0x0120
#define H265E_COMMON_PIC_INFO 0x0120
#define H265E_BS_SIZE 0x0124
#define H265E_PIC_SIZE 0x0124
#define H265E_BS_PARAM 0x0128
#define H265E_SET_FB_NUM 0x0128
#define H265E_BS_OPTION 0x012C
#define H265E_BS_RD_PTR 0x0130
#define H265E_BS_WR_PTR 0x0134
#define H265E_ADDR_WORK_BASE 0x0138
#define H265E_WORK_SIZE 0x013c
#define H265E_WORK_PARAM 0x0140
#define H265E_ADDR_TEMP_BASE 0x0144
#define H265E_TEMP_SIZE 0x0148
#define H265E_TEMP_PARAM 0x014C
#define H265E_FBC_STRIDE 0x0154
#define H265E_ENC_SET_PARAM_ENABLE 0x015C
#define H265E_ENC_SEQ_SRC_SIZE 0x0160
#define H265E_ADDR_LUMA_BASE0 0x0160
#define H265E_ADDR_CB_BASE0 0x0164
#define H265E_ADDR_CR_BASE0 0x0168
#define H265E_ADDR_FBC_Y_OFFSET0 0x0168
#define H265E_ADDR_FBC_C_OFFSET0 0x016C
#define H265E_ENC_SEQ_PARAM 0x016C
#define H265E_ENC_SEQ_GOP_PARAM 0x0170
#define H265E_ENC_SRC_PIC_IDX 0x0170
#define H265E_ENC_SEQ_INTRA_PARAM 0x0174
#define H265E_ENC_SEQ_CONF_WIN_TOP_BOT 0x0178
#define H265E_ENC_SEQ_CONF_WIN_LEFT_RIGHT 0x017C
#define H265E_ENC_SEQ_FRAME_RATE 0x0180
#define H265E_ENC_SEQ_INDEPENDENT_SLICE 0x0184
#define H265E_ENC_SEQ_DEPENDENT_SLICE 0x0188
#define H265E_ENC_SEQ_INTRA_REFRESH 0x018C
#define H265E_ENC_PARAM 0x0190
#define H265E_ENC_RC_INTRA_MIN_MAX_QP 0x0194
#define H265E_ENC_RC_PARAM 0x0198
#define H265E_ENC_RC_MIN_MAX_QP 0x019C
#define H265E_ENC_RC_BIT_RATIO_LAYER_0_3 0x01A0
#define H265E_ENC_RC_BIT_RATIO_LAYER_4_7 0x01A4
#define H265E_ENC_NR_PARAM 0x01A8
#define H265E_ENC_NR_WEIGHT 0x01AC
#define H265E_ENC_NUM_UNITS_IN_TICK 0x01B0
#define H265E_ENC_TIME_SCALE 0x01B4
#define H265E_ENC_NUM_TICKS_POC_DIFF_ONE 0x01B8
#define H265E_ENC_RC_TRANS_RATE 0x01BC
#define H265E_ENC_RC_TARGET_RATE 0x01C0
#define H265E_ENC_ROT_PARAM 0x01C4
#define H265E_ENC_ROT_RESERVED 0x01C8
#define H265E_RET_ENC_MIN_FB_NUM 0x01CC
#define H265E_RET_ENC_NAL_INFO_TO_BE_ENCODED 0x01D0
#define H265E_RET_ENC_MIN_SRC_BUF_NUM 0x01D8
#define H265E_ADDR_MV_COL0 0x01E0
#define H265E_ADDR_MV_COL1 0x01E4
#define H265E_ADDR_MV_COL2 0x01E8
#define H265E_ADDR_MV_COL3 0x01EC
#define H265E_ADDR_MV_COL4 0x01F0
#define H265E_ADDR_MV_COL5 0x01F4
#define H265E_ADDR_MV_COL6 0x01F8
#define H265E_ADDR_MV_COL7 0x01FC
#define H265E_ADDR_SEC_AXI_BASE 0x150
#define H265E_SEC_AXI_SIZE 0x154
#define H265E_USE_SEC_AXI 0x158
/************************************************************************/
/*H265 ENCODER - SET_PARAM + CUSTOM_GOP */
/************************************************************************/
#define H265E_ENC_SET_CUSTOM_GOP_ENABLE 0x015C
#define H265E_ENC_CUSTOM_GOP_PARAM 0x0160
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_0 0x0164
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_1 0x0168
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_2 0x016C
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_3 0x0170
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_4 0x0174
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_5 0x0178
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_6 0x017C
#define H265E_ENC_CUSTOM_GOP_PIC_PARAM_7 0x0180
#define H265E_ENC_CUSTOM_GOP_RESERVED 0x0184
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_0 0x0188
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_1 0x018C
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_2 0x0190
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_3 0x0194
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_4 0x0198
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_5 0x019C
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_6 0x01A0
#define H265E_ENC_CUSTOM_GOP_PIC_LAMBDA_7 0x01A4
/************************************************************************/
/* H265 ENCODER - SET_PARAM + VUI */
/************************************************************************/
#define H265E_ENC_VUI_PARAM_FLAGS 0x015C
#define H265E_ENC_VUI_ASPECT_RATIO_IDC 0x0160
#define H265E_ENC_VUI_SAR_SIZE 0x0164
#define H265E_ENC_VUI_OVERSCAN_APPROPRIATE 0x0168
#define H265E_ENC_VUI_VIDEO_SIGNAL 0x016C
#define H265E_ENC_VUI_CHROMA_SAMPLE_LOC 0x0170
#define H265E_ENC_VUI_DISP_WIN_LEFT_RIGHT 0x0174
#define H265E_ENC_VUI_DISP_WIN_TOP_BOT 0x0178
#define H265E_ENC_VUI_HRD_RBSP_PARAM_FLAG 0x017C
#define H265E_ENC_VUI_RBSP_ADDR 0x0180
#define H265E_ENC_VUI_RBSP_SIZE 0x0184
#define H265E_ENC_HRD_RBSP_ADDR 0x0188
#define H265E_ENC_HRD_RBSP_SIZE 0x018C
/************************************************************************/
/* H265 ENCODER - SET_FRAMEBUF */
/************************************************************************/
#define H265E_FBC_STRIDE_Y 0x150
#define H265E_FBC_STRIDE_C 0x154
/* 1/4 sub-sampled buffer (for S2 ME)
* SUB_SAMPLED_ONE_FB_SIZE = ALIGN16(width/4) * ALIGN8(height/4)
* total size for sub-sampled buffer = SUB_SAMPLED_ONE_FB_SIZE * SET_FB_NUM
*/
#define H265E_ADDR_SUB_SAMPLED_FB_BASE 0x0158
#define H265E_SUB_SAMPLED_ONE_FB_SIZE 0x015C
/************************************************************************/
/* ENCODER - ENC_PIC */
/************************************************************************/
#define H265E_CMD_ENC_ADDR_REPORT_BASE 0x015C
#define H265E_CMD_ENC_REPORT_SIZE 0x0160
#define H265E_CMD_ENC_REPORT_PARAM 0x0164
#define H265E_CMD_ENC_CODE_OPTION 0x0168
#define H265E_CMD_ENC_PIC_PARAM 0x016C
#define H265E_CMD_ENC_SRC_PIC_IDX 0x0170
#define H265E_CMD_ENC_SRC_ADDR_Y 0x0174
#define H265E_CMD_ENC_SRC_ADDR_U 0x0178
#define H265E_CMD_ENC_SRC_ADDR_V 0x017C
#define H265E_CMD_ENC_SRC_STRIDE 0x0180
#define H265E_CMD_ENC_SRC_FORMAT 0x0184
#define H265E_CMD_ENC_PREFIX_SEI_NAL_ADDR 0x0188
#define H265E_CMD_ENC_PREFIX_SEI_INFO 0x018C
#define H265E_CMD_ENC_SUFFIX_SEI_NAL_ADDR 0x0190
#define H265E_CMD_ENC_SUFFIX_SEI_INFO 0x0194
#define H265E_CMD_ENC_LONGTERM_PIC 0x0198
#define H265E_CMD_ENC_SUB_FRAME_SYNC_CONFIG 0x019C
#define H265E_CMD_ENC_CTU_OPT_PARAM 0x01A0
#define H265E_CMD_ENC_ROI_ADDR_CTU_MAP 0x01A4
#define H265E_CMD_ENC_CTU_QP_MAP_ADDR 0x01AC
#define H265E_CMD_ENC_SRC_TIMESTAMP_LOW 0x01B0
#define H265E_CMD_ENC_SRC_TIMESTAMP_HIGH 0x01B4
#define H265E_CMD_ENC_FC_PARAM 0x01E8
#define H265E_CMD_ENC_FC_TABLE_ADDR_Y 0x01EC
#define H265E_CMD_ENC_FC_TABLE_ADDR_C 0x01F0
#define H265E_RET_ENC_PIC_IDX 0x01A8
#define H265E_RET_ENC_PIC_SLICE_NUM 0x01AC
#define H265E_RET_ENC_PIC_SKIP 0x01B0
#define H265E_RET_ENC_PIC_NUM_INTRA 0x01B4
#define H265E_RET_ENC_PIC_NUM_MERGE 0x01B8
#define H265E_RET_ENC_PIC_FLAG 0x01BC
#define H265E_RET_ENC_PIC_NUM_SKIP 0x01C0
#define H265E_RET_ENC_PIC_AVG_CU_QP 0x01C4
#define H265E_RET_ENC_PIC_BYTE 0x01C8
#define H265E_RET_ENC_GOP_PIC_IDX 0x01CC
#define H265E_RET_ENC_PIC_POC 0x01D0
#define H265E_RET_ENC_USED_SRC_IDX 0x01D8
#define H265E_RET_ENC_PIC_NUM 0x01DC
#define H265E_RET_ENC_PIC_TYPE 0x01E0
#define H265E_RET_ENC_VCL_NUT 0x01E4
#define H265E_PERF_AXI_CTRL 0x0240
#define H265E_PERF_LATENCY_CTRL0 0x0264
#define H265E_PERF_LATENCY_CTRL1 0x0268
#define H265E_PERF_RD_MAX_LATENCY_NUM0 0x026C
#define H265E_PERF_RD_LATENCY_SAMP_NUM 0x0270
#define H265E_PERF_RD_LATENCY_ACC_SUM 0x0274
#define H265E_PERF_RD_AXI_TOTAL_BYTE 0x0278
#define H265E_PERF_WR_AXI_TOTAL_BYTE 0x027C
#define H265E_PERF_WORKING_CNT 0x0280
#endif

View File

@@ -1,813 +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 <linux/clk.h>
#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/reset.h>
#include "vpu_iommu_ops.h"
#include "mpp_service.h"
#include "mpp_dev_common.h"
#include "mpp_dev_rkvenc.h"
#define MPP_ALIGN_SIZE 0x1000
#define LINK_TABLE_START 12
#define LINK_TABLE_LEN 128
#define RKVENC_ENC_START 0x004
#define RKVENC_LKT_NUM(x) (((x) & 0xff) << 0)
#define RKVENC_CMD(x) (((x) & 0x3) << 8)
#define RKVENC_CLK_GATE_EN BIT(16)
#define RKVENC_SAFE_CLR 0x008
#define RKVENC_LKT_ADDR 0x00c
#define RKVENC_INT_EN 0x010
#define RKVENC_INT_EN_SAFE_CLEAR BIT(2)
#define RKVENC_INT_EN_TIMEOUT BIT(8)
#define RKVENC_INT_MSK 0x014
#define RKVENC_INT_MSK_OVERFLOW BIT(4)
#define RKVENC_INT_MSK_W_FIFO_FULL BIT(5)
#define RKVENC_INT_MSK_W_CHN_ERROR BIT(6)
#define RKVENC_INT_MSK_R_CHN_ERROR BIT(7)
#define RKVENC_INT_MSK_TIMEOUT BIT(8)
#define RKVENC_INT_CLR 0x018
#define RKVENC_INT_STATUS 0x01c
#define RKVENC_ONE_FRAME_FINISH BIT(0)
#define RKVENC_LINK_TABLE_FINISH BIT(1)
#define RKVENC_SAFE_CLEAR_FINISH BIT(2)
#define RKVENC_ONE_SLICE_FINISH BIT(3)
#define RKVENC_BIT_STREAM_OVERFLOW BIT(4)
#define RKVENC_AXI_WRITE_FIFO_FULL BIT(5)
#define RKVENC_AXI_WRITE_CHANNEL_ERROR BIT(6)
#define RKVENC_AXI_READ_CHANNEL_ERROR BIT(7)
#define RKVENC_TIMEOUT_ERROR BIT(8)
#define RKVENC_INT_ERROR_BITS ((RKVENC_BIT_STREAM_OVERFLOW) | \
(RKVENC_AXI_WRITE_FIFO_FULL) | \
(RKVENC_AXI_WRITE_CHANNEL_ERROR) |\
(RKVENC_AXI_READ_CHANNEL_ERROR) | \
(RKVENC_TIMEOUT_ERROR))
#define RKVENC_ENC_PIC 0x034
#define RKVENC_ENC_PIC_NODE_INT_EN BIT(31)
#define RKVENC_ENC_WDG 0x038
#define RKVENC_PPLN_ENC_LMT(x) (((x) & 0xff) << 0)
#define RKVENC_OSD_CFG 0x1c0
#define RKVENC_OSD_PLT_TYPE BIT(17)
#define RKVENC_OSD_CLK_SEL_BIT BIT(16)
#define RKVENC_STATUS(i) (0x210 + (4 * (i)))
#define RKVENC_BSL_STATUS 0x210
#define RKVENC_BITSTREAM_LENGTH(x) ((x) & 0x7FFFFFF)
#define RKVENC_ENC_STATUS 0x220
#define RKVENC_ENC_STATUS_ENC(x) (((x) >> 0) & 0x3)
#define RKVENC_LKT_STATUS 0x224
#define RKVENC_LKT_STATUS_FNUM_ENC(x) (((x) >> 0) & 0xff)
#define RKVENC_LKT_STATUS_FNUM_CFG(x) (((x) >> 8) & 0xff)
#define RKVENC_LKT_STATUS_FNUM_INT(x) (((x) >> 16) & 0xff)
#define RKVENC_OSD_PLT(i) (0x400 + (4 * (i)))
#define to_rkvenc_ctx(ctx) \
container_of(ctx, struct rkvenc_ctx, ictx)
#define to_rkvenc_session(session) \
container_of(session, struct rkvenc_session, isession)
#define to_rkvenc_dev(dev) \
container_of(dev, struct rockchip_rkvenc_dev, idev)
/*
* file handle translate information
*/
static const char trans_tbl_rkvenc[] = {
70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
124, 125, 126, 127, 128, 129, 130, 131
};
static struct mpp_trans_info trans_rkvenc[1] = {
[0] = {
.count = sizeof(trans_tbl_rkvenc),
.table = trans_tbl_rkvenc,
},
};
static struct mpp_dev_rkvenc_reg mpp_rkvenc_dummy_reg = {
.enc_rsl = 0x00070007, /* 64x64 */
.enc_pic = 0x00001714, /* h264, qp 30 */
.enc_wdg = 0x00000002,
.dtrns_map = 0x00007000,
.dtrns_cfg = 0x0000007f,
.src_fmt = 0x00000018, /* nv12 */
.src_strd = 0x003f003f,
.sli_spl = 0x00000004,
.me_rnge = 0x00002f7b,
.me_cnst = 0x000e0505,
.me_ram = 0x000e79ab,
.rc_qp = 0x07340000,
.rdo_cfg = 0x00000002,
.synt_nal = 0x00000017,
.synt_sps = 0x0000019c,
.synt_pps = 0x01000d03,
.synt_sli0 = 0x00000002,
};
static int rockchip_mpp_rkvenc_reset(struct rockchip_mpp_dev *mpp);
/*
* In order to workaround hw bug which make the first frame run failure with
* timeout interrupt occur, we make a dummy 64x64 encoding on power on here to
* cover the hw bug.
*/
static void rockchip_mpp_war_init(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
size_t img_width = 64;
size_t img_height = 64;
size_t img_y_size = img_width * img_height;
size_t img_uv_size = img_y_size / 2;
size_t img_u_size = img_uv_size / 2;
size_t img_size = img_y_size + img_uv_size;
enc->war_reg = &mpp_rkvenc_dummy_reg;
/* 4k align required */
enc->war_reg->adr_rfpw = enc->war_dma_addr;
enc->war_reg->adr_srcy = enc->war_reg->adr_rfpw + img_size;
enc->war_reg->adr_srcu = enc->war_reg->adr_srcy + img_y_size;
enc->war_reg->adr_srcv = enc->war_reg->adr_srcu + img_u_size;
enc->war_reg->adr_bsbb = enc->war_reg->adr_srcv + img_u_size;
enc->war_reg->adr_bsbt = enc->war_reg->adr_bsbb + img_size;
enc->war_reg->adr_bsbr = enc->war_reg->adr_bsbb;
enc->war_reg->adr_bsbw = enc->war_reg->adr_bsbb;
/* 1k align required */
enc->war_reg->adr_dspw = enc->war_dma_addr + 0x4000;
enc->war_reg->adr_dspr = enc->war_reg->adr_dspw + 0x400;
enc->dummy_ctx = kzalloc(sizeof(*enc->dummy_ctx), GFP_KERNEL);
if (!enc->dummy_ctx)
return;
enc->dummy_ctx->ictx.mpp = mpp;
enc->dummy_ctx->ictx.session = NULL;
enc->dummy_ctx->mode = RKVENC_MODE_ONEFRAME;
enc->dummy_ctx->cfg.mode = RKVENC_MODE_ONEFRAME;
atomic_set(&enc->dummy_ctx_in_used, 0);
memcpy(enc->dummy_ctx->cfg.elem[0].reg, enc->war_reg,
sizeof(*enc->war_reg));
enc->dummy_ctx->cfg.elem[0].reg_num = sizeof(*enc->war_reg) / 4;
}
static void rockchip_mpp_rkvenc_cfg_palette(struct rockchip_mpp_dev *mpp,
struct mpp_session *isession)
{
struct rkvenc_session *session;
int i;
u32 reg;
mpp_debug_enter();
if (!isession) {
mpp_debug(DEBUG_TASK_INFO, "fake ctx, do not cfg palette\n");
return;
}
session = to_rkvenc_session(isession);
if (!session->palette_valid)
return;
reg = mpp_read(mpp, RKVENC_OSD_CFG);
mpp_write(mpp, reg & (~RKVENC_OSD_CLK_SEL_BIT), RKVENC_OSD_CFG);
for (i = 0; i < RKVENC_OSD_PLT_LEN; i++)
mpp_write(mpp, session->palette.plalette[i].elem,
RKVENC_OSD_PLT(i));
mpp_write(mpp, reg | RKVENC_OSD_CLK_SEL_BIT, RKVENC_OSD_CFG);
mpp_debug_leave();
}
static struct mpp_ctx *rockchip_mpp_rkvenc_init(struct rockchip_mpp_dev *mpp,
struct mpp_session *session,
void __user *src, u32 size)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
struct rkvenc_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
int i;
mpp_debug_enter();
if (!ctx)
return NULL;
/* HW defeat workaround start */
if (!mpp_dev_is_power_on(mpp) && enc->dummy_ctx &&
atomic_inc_return(&enc->dummy_ctx_in_used) == 1) {
mpp_debug(DEBUG_RESET, "add a dummy ctx\n");
mpp_srv_pending_locked(mpp->srv, &enc->dummy_ctx->ictx);
}
mpp_dev_common_ctx_init(mpp, &ctx->ictx);
ctx->ictx.session = session;
ctx->mode = RKVENC_MODE_LINKTABLE_FIX;
size = size > sizeof(ctx->cfg) ? sizeof(ctx->cfg) : size;
if (copy_from_user(&ctx->cfg, src, size)) {
mpp_err("error: copy_from_user failed in reg_init\n");
kfree(ctx);
return NULL;
}
ctx->mode = ctx->cfg.mode;
if (ctx->mode >= RKVENC_MODE_NUM || ctx->mode == RKVENC_MODE_NONE) {
mpp_err("Invalid rkvenc running mode %d\n", (int)ctx->mode);
kfree(ctx);
return NULL;
} else if (ctx->mode == RKVENC_MODE_ONEFRAME && ctx->cfg.tbl_num > 1) {
mpp_err("Configuration miss match, ignore redundant cfg\n");
ctx->cfg.tbl_num = 1;
}
mpp_debug(DEBUG_SET_REG, "tbl num %u, mode %u\n",
ctx->cfg.tbl_num, ctx->cfg.mode);
for (i = 0; i < ctx->cfg.tbl_num; i++) {
if (mpp_reg_address_translate(mpp, ctx->cfg.elem[i].reg,
&ctx->ictx, 0) < 0) {
mpp_err("error: translate reg address failed.\n");
if (unlikely(mpp_dev_debug & DEBUG_DUMP_ERR_REG))
mpp_dump_reg_mem(ctx->cfg.elem[i].reg,
ctx->cfg.elem[i].reg_num);
mpp_dev_common_ctx_deinit(mpp, &ctx->ictx);
kfree(ctx);
return NULL;
}
mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x",
ctx->cfg.elem[i].ext_inf.cnt,
ctx->cfg.elem[i].ext_inf.magic);
mpp_translate_extra_info(&ctx->ictx, &ctx->cfg.elem[i].ext_inf,
ctx->cfg.elem[i].reg);
}
mpp_debug_leave();
return &ctx->ictx;
}
static int rockchip_mpp_rkvenc_reset_init(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
mpp_debug(DEBUG_RESET, "reset init in:\n");
enc->rst_a = devm_reset_control_get(mpp->dev, "video_a");
enc->rst_h = devm_reset_control_get(mpp->dev, "video_h");
enc->rst_v = devm_reset_control_get(mpp->dev, "video_c");
if (IS_ERR_OR_NULL(enc->rst_a)) {
mpp_err("No aclk reset resource define\n");
enc->rst_a = NULL;
}
if (IS_ERR_OR_NULL(enc->rst_h)) {
mpp_err("No hclk reset resource define\n");
enc->rst_h = NULL;
}
if (IS_ERR_OR_NULL(enc->rst_v)) {
mpp_err("No core reset resource define\n");
enc->rst_v = NULL;
}
return 0;
}
static int rockchip_mpp_rkvenc_reset(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
int cnt = 100;
if (enc->rst_a && enc->rst_h && enc->rst_v) {
mpp_debug(DEBUG_RESET, "reset in\n");
mpp_write(mpp, 0, RKVENC_INT_EN);
mpp_write(mpp, 1, RKVENC_SAFE_CLR);
while (cnt-- > 0) {
int status;
usleep_range(100, 200);
status = mpp_read(mpp, RKVENC_ENC_STATUS);
if (status & 4) {
mpp_debug(DEBUG_RESET, "st_enc %08x\n", status);
break;
}
}
reset_control_assert(enc->rst_v);
reset_control_assert(enc->rst_a);
reset_control_assert(enc->rst_h);
udelay(1);
reset_control_deassert(enc->rst_v);
reset_control_deassert(enc->rst_a);
reset_control_deassert(enc->rst_h);
mpp_debug(DEBUG_RESET, "reset out\n");
}
return 0;
}
static int rockchip_mpp_rkvenc_prepare(struct rockchip_mpp_dev *mpp)
{
struct rkvenc_ctx *ctx_curr;
struct rkvenc_ctx *ctx_ready;
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
u32 lkt_status;
u32 fnum_int;
u32 fnum_cfg;
u32 fnum_enc;
u8 *cpu_addr;
int i;
u32 reg = 0;
mpp_debug_enter();
if (!mpp_srv_is_running(mpp->srv))
return 0;
/* if service running, determine link table mode */
ctx_curr = to_rkvenc_ctx(mpp_srv_get_current_ctx(mpp->srv));
ctx_ready = to_rkvenc_ctx(mpp_srv_get_pending_ctx(mpp->srv));
if (ctx_curr->mode != RKVENC_MODE_LINKTABLE_UPDATE ||
ctx_ready->mode != ctx_curr->mode) {
mpp_debug(DEBUG_TASK_INFO,
"link table condition not fulfill\n");
return -1;
}
lkt_status = mpp_read(mpp, RKVENC_LKT_STATUS);
fnum_int = RKVENC_LKT_STATUS_FNUM_INT(lkt_status);
fnum_cfg = RKVENC_LKT_STATUS_FNUM_CFG(lkt_status);
fnum_enc = RKVENC_LKT_STATUS_FNUM_ENC(lkt_status);
cpu_addr = (u8 *)enc->lkt_cpu_addr + fnum_cfg * LINK_TABLE_LEN * 4;
mpp_dev_power_on(mpp);
mpp_debug(DEBUG_GET_REG, "frame number int %u, cfg %u, enc %u\n",
fnum_int, fnum_cfg, fnum_enc);
for (i = 0; i < ctx_ready->cfg.tbl_num; i++) {
u32 *src = ctx_ready->cfg.elem[i].reg;
memcpy(cpu_addr + i * LINK_TABLE_LEN * 4,
&src[LINK_TABLE_START], LINK_TABLE_LEN * 4);
}
reg = RKVENC_CLK_GATE_EN |
RKVENC_CMD(ctx_curr->mode) |
RKVENC_LKT_NUM(ctx_ready->cfg.tbl_num);
mpp_write_relaxed(mpp, reg, RKVENC_ENC_START);
/* remove from pending queue */
mpp_dev_common_ctx_deinit(mpp, &ctx_ready->ictx);
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_rkvenc_run(struct rockchip_mpp_dev *mpp)
{
struct rkvenc_ctx *ctx =
to_rkvenc_ctx(mpp_srv_get_current_ctx(mpp->srv));
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
u32 reg;
int i;
mpp_debug_enter();
switch (ctx->mode) {
case RKVENC_MODE_ONEFRAME:
{
u32 *src = ctx->cfg.elem[0].reg;
for (i = 2; i < (LINK_TABLE_START + LINK_TABLE_LEN); i++)
mpp_write_relaxed(mpp, src[i], i * 4);
rockchip_mpp_rkvenc_cfg_palette(mpp, ctx->ictx.session);
mpp_write_relaxed(mpp, 0x1ff, RKVENC_INT_EN);
reg = RKVENC_CLK_GATE_EN
| RKVENC_CMD(1);
mpp_write(mpp, reg, RKVENC_ENC_START);
break;
}
case RKVENC_MODE_LINKTABLE_FIX:
case RKVENC_MODE_LINKTABLE_UPDATE:
{
for (i = 0; i < ctx->cfg.tbl_num; i++) {
u32 *src = ctx->cfg.elem[i].reg;
memcpy(enc->lkt_cpu_addr +
i * LINK_TABLE_LEN * 4,
&src[LINK_TABLE_START],
LINK_TABLE_LEN * 4);
}
rockchip_mpp_rkvenc_cfg_palette(mpp, ctx->ictx.session);
mpp_write_relaxed(mpp,
enc->lkt_dma_addr,
RKVENC_LKT_ADDR);
mpp_write_relaxed(mpp, 0xffffffff, RKVENC_INT_EN);
reg = RKVENC_LKT_NUM(ctx->cfg.tbl_num) |
RKVENC_CMD(RKVENC_MODE_LINKTABLE_FIX) |
RKVENC_CLK_GATE_EN;
mpp_write_relaxed(mpp, reg, RKVENC_ENC_START);
break;
}
default:
break;
}
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_rkvenc_done(struct rockchip_mpp_dev *mpp)
{
struct mpp_ctx *ictx = mpp_srv_get_current_ctx(mpp->srv);
struct rkvenc_ctx *ctx;
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
struct rkvenc_result *result;
int i;
mpp_debug_enter();
if (IS_ERR_OR_NULL(ictx)) {
mpp_err("Invaidate context to save result\n");
return -1;
}
ctx = to_rkvenc_ctx(ictx);
if (enc->irq_status & RKVENC_INT_ERROR_BITS)
/*
* according to war running, if the dummy encoding
* running with timeout, we enable a safe clear process,
* we reset the ip, and complete the war procedure.
*/
atomic_inc(&mpp->reset_request);
if (ctx == enc->dummy_ctx) {
mpp_debug(DEBUG_RESET, "war done\n");
/* for war do not trigger service done process */
list_del_init(&ictx->status_link);
atomic_set(&enc->dummy_ctx_in_used, 0);
/* dummy ctx, do not trigger service to wake up done process */
return -1;
}
result = &ctx->result;
switch (ctx->mode) {
case RKVENC_MODE_ONEFRAME:
result->tbl_num = 1;
result->elem[0].status = enc->irq_status;
for (i = 0; i < sizeof(result->elem[0].result) / 4; i++)
result->elem[0].result[i] =
mpp_read(mpp,
RKVENC_STATUS(i));
break;
case RKVENC_MODE_LINKTABLE_FIX:
case RKVENC_MODE_LINKTABLE_UPDATE:
{
u32 lkt_status = mpp_read(mpp, RKVENC_LKT_STATUS);
u32 fnum_int = RKVENC_LKT_STATUS_FNUM_INT(lkt_status);
u32 fnum_cfg = RKVENC_LKT_STATUS_FNUM_CFG(lkt_status);
u32 fnum_enc = RKVENC_LKT_STATUS_FNUM_ENC(lkt_status);
u32 *lkt_cpu_addr = (u32 *)enc->lkt_cpu_addr;
if (unlikely(mpp_dev_debug & DEBUG_DUMP_ERR_REG))
mpp_dump_reg_mem(lkt_cpu_addr, LINK_TABLE_LEN);
result->tbl_num = fnum_int;
for (i = 0; i < fnum_int; i++) {
result->elem[i].status = enc->irq_status;
memcpy(result->elem[i].result,
&lkt_cpu_addr[i * LINK_TABLE_LEN + 120],
sizeof(result->elem[i].result));
mpp_debug(DEBUG_GET_REG, "stream length %u\n",
result->elem[i].result[0]);
}
mpp_debug(DEBUG_GET_REG, "frame number %u, %u, %u\n",
fnum_int, fnum_cfg, fnum_enc);
break;
}
default:
break;
}
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_rkvenc_irq(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
enc->irq_status = mpp_read(mpp, RKVENC_INT_STATUS);
mpp_debug_enter();
if (enc->irq_status == 0)
return -1;
mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", enc->irq_status);
mpp_write(mpp, 0xffffffff, RKVENC_INT_CLR);
if (enc->irq_status & RKVENC_INT_ERROR_BITS) {
mpp_err("error irq %08x\n", enc->irq_status);
/* time out error */
mpp_write(mpp, RKVENC_INT_ERROR_BITS, RKVENC_INT_MSK);
}
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_rkvenc_result(struct rockchip_mpp_dev *mpp,
struct mpp_ctx *ictx, u32 __user *dst)
{
struct rkvenc_ctx *ctx = to_rkvenc_ctx(ictx);
struct rkvenc_result *result = &ctx->result;
unsigned long tbl_size = sizeof(result->tbl_num) +
sizeof(result->elem[0]) * result->tbl_num;
switch (ctx->mode) {
case RKVENC_MODE_ONEFRAME:
case RKVENC_MODE_LINKTABLE_FIX:
case RKVENC_MODE_LINKTABLE_UPDATE:
{
if (copy_to_user(dst, &ctx->result, tbl_size)) {
mpp_err("copy result to user failed\n");
return -1;
}
break;
}
default:
mpp_err("invalid context mode %d\n", (int)ctx->mode);
return -1;
}
return 0;
}
static long rockchip_mpp_rkvenc_ioctl(struct mpp_session *isession,
unsigned int cmd,
unsigned long arg)
{
struct rkvenc_session *session = to_rkvenc_session(isession);
mpp_debug_enter();
switch (cmd) {
case MPP_DEV_RKVENC_SET_COLOR_PALETTE:
if (copy_from_user(&session->palette, (void __user *)arg,
sizeof(session->palette))) {
mpp_err("copy palette from user failed\n");
return -EINVAL;
}
session->palette_valid = true;
break;
default:
mpp_err("%s, unknown ioctl cmd %x\n",
dev_name(isession->mpp->dev), cmd);
break;
}
mpp_debug_leave();
return 0;
}
static struct mpp_session *mpp_dev_rkvenc_open(struct rockchip_mpp_dev *mpp)
{
struct rkvenc_session *session = kzalloc(sizeof(*session), GFP_KERNEL);
mpp_debug_enter();
if (!session)
return NULL;
session->palette_valid = false;
mpp_debug_leave();
return &session->isession;
}
static void mpp_dev_rkvenc_free(struct mpp_session *isession)
{
struct rkvenc_session *session = to_rkvenc_session(isession);
kfree(session);
}
struct mpp_dev_ops rkvenc_ops = {
.init = rockchip_mpp_rkvenc_init,
.prepare = rockchip_mpp_rkvenc_prepare,
.run = rockchip_mpp_rkvenc_run,
.done = rockchip_mpp_rkvenc_done,
.irq = rockchip_mpp_rkvenc_irq,
.result = rockchip_mpp_rkvenc_result,
.ioctl = rockchip_mpp_rkvenc_ioctl,
.open = mpp_dev_rkvenc_open,
.free = mpp_dev_rkvenc_free,
};
static void rockchip_mpp_rkvenc_power_on(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
if (enc->aclk)
clk_prepare_enable(enc->aclk);
if (enc->hclk)
clk_prepare_enable(enc->hclk);
if (enc->core)
clk_prepare_enable(enc->core);
/*
* Because hw cannot reset status fully in all its modules, we make a
* reset here to make sure the hw status fully reset.
*/
rockchip_mpp_rkvenc_reset(mpp);
}
static void rockchip_mpp_rkvenc_power_off(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
if (enc->core)
clk_disable_unprepare(enc->core);
if (enc->hclk)
clk_disable_unprepare(enc->hclk);
if (enc->aclk)
clk_disable_unprepare(enc->aclk);
}
static int rockchip_mpp_rkvenc_probe(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
struct mpp_session *session = list_first_entry(&mpp->srv->session,
struct mpp_session,
list_session);
int ret;
size_t tmp;
enc->idev.ops = &rkvenc_ops;
enc->lkt_hdl = vpu_iommu_alloc(mpp->iommu_info, session,
LINK_TABLE_LEN * 4 * 256,
MPP_ALIGN_SIZE);
if (enc->lkt_hdl < 0) {
dev_err(mpp->dev, "allocate link table buffer failure\n");
return -1;
}
ret = vpu_iommu_map_iommu(mpp->iommu_info, session,
enc->lkt_hdl, &enc->lkt_dma_addr, &tmp);
if (ret < 0) {
dev_err(mpp->dev, "get link table dma_addr failed\n");
goto fail;
}
enc->lkt_cpu_addr = vpu_iommu_map_kernel(mpp->iommu_info,
session, enc->lkt_hdl);
/*
* buffer for workaround context running, include input picture, output
* stream, reconstruction picture. we set the output stream buffer to 1
* time picture size, so the total buffer size is 3 times picture size,
* 64 * 64 * 3 / 2 * 3 = 4.5 * 4k.
*/
enc->war_hdl = vpu_iommu_alloc(mpp->iommu_info, session,
MPP_ALIGN_SIZE * 5,
MPP_ALIGN_SIZE);
if (enc->war_hdl < 0) {
dev_err(mpp->dev, "allocate workaround buffer failure\n");
goto fail;
}
ret = vpu_iommu_map_iommu(mpp->iommu_info, session,
enc->war_hdl, &enc->war_dma_addr, &tmp);
if (ret < 0) {
dev_err(mpp->dev, "get war dma_addr failed\n");
goto fail;
}
rockchip_mpp_war_init(mpp);
enc->aclk = devm_clk_get(mpp->dev, "aclk_vcodec");
if (IS_ERR_OR_NULL(enc->aclk)) {
dev_err(mpp->dev, "failed on clk_get aclk\n");
goto fail;
}
enc->hclk = devm_clk_get(mpp->dev, "hclk_vcodec");
if (IS_ERR_OR_NULL(enc->hclk)) {
dev_err(mpp->dev, "failed on clk_get hclk\n");
goto fail;
}
enc->core = devm_clk_get(mpp->dev, "clk_core");
if (IS_ERR_OR_NULL(enc->core)) {
dev_err(mpp->dev, "failed on clk_get core\n");
goto fail;
}
rockchip_mpp_rkvenc_reset_init(mpp);
return 0;
fail:
kfree(enc->dummy_ctx);
if (enc->war_hdl >= 0) {
vpu_iommu_unmap_iommu(mpp->iommu_info,
session, enc->war_hdl);
vpu_iommu_free(mpp->iommu_info, session, enc->war_hdl);
}
if (enc->lkt_cpu_addr)
vpu_iommu_unmap_kernel(mpp->iommu_info, session, enc->lkt_hdl);
if (enc->lkt_hdl >= 0) {
vpu_iommu_unmap_iommu(mpp->iommu_info,
session, enc->lkt_hdl);
vpu_iommu_free(mpp->iommu_info, session, enc->lkt_hdl);
}
return -1;
}
static void rockchip_mpp_rkvenc_remove(struct rockchip_mpp_dev *mpp)
{
struct rockchip_rkvenc_dev *enc = to_rkvenc_dev(mpp);
struct mpp_session *session = list_first_entry(&mpp->srv->session,
struct mpp_session,
list_session);
vpu_iommu_unmap_kernel(mpp->iommu_info, session, enc->lkt_hdl);
vpu_iommu_unmap_iommu(mpp->iommu_info,
session, enc->lkt_hdl);
vpu_iommu_free(mpp->iommu_info, session, enc->lkt_hdl);
vpu_iommu_unmap_iommu(mpp->iommu_info,
session, enc->war_hdl);
vpu_iommu_free(mpp->iommu_info, session, enc->war_hdl);
kfree(enc->dummy_ctx);
}
const struct rockchip_mpp_dev_variant rkvenc_variant = {
.data_len = sizeof(struct rockchip_rkvenc_dev),
.reg_len = 140,
.trans_info = trans_rkvenc,
.hw_probe = rockchip_mpp_rkvenc_probe,
.hw_remove = rockchip_mpp_rkvenc_remove,
.power_on = rockchip_mpp_rkvenc_power_on,
.power_off = rockchip_mpp_rkvenc_power_off,
.reset = rockchip_mpp_rkvenc_reset,
};
EXPORT_SYMBOL(rkvenc_variant);

View File

@@ -1,177 +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_RKVENC_H
#define __ROCKCHIP_MPP_DEV_RKVENC_H
union rkvenc_osd_palette_elem {
struct {
u8 y;
u8 u;
u8 v;
u8 alpha;
};
u32 elem;
};
#define RKVENC_OSD_PLT_LEN 256
struct rkvenc_osd_palette {
union rkvenc_osd_palette_elem plalette[RKVENC_OSD_PLT_LEN];
};
#define MPP_DEV_RKVENC_SET_COLOR_PALETTE \
_IOW(VPU_IOC_MAGIC, MPP_IOC_CUSTOM_BASE + 1, \
struct rkvenc_osd_palette)
struct rkvenc_config_elem {
u32 reg_num;
u32 reg[140];
struct extra_info_for_iommu ext_inf;
};
struct rkvenc_config {
u32 mode;
u32 tbl_num;
struct rkvenc_config_elem elem[10];
};
struct rkvenc_result_elem {
u32 status;
u32 result[11];
};
struct rkvenc_result {
u32 tbl_num;
struct rkvenc_result_elem elem[10];
};
struct rkvenc_ctx {
struct mpp_ctx ictx;
enum RKVENC_MODE mode;
struct rkvenc_config cfg;
/* store status read from hw, oneframe mode used only */
struct rkvenc_result result;
};
struct rkvenc_session {
struct mpp_session isession;
struct rkvenc_osd_palette palette;
bool palette_valid;
};
struct mpp_dev_rkvenc_reg {
u32 unused_00;
u32 enc_strt;
u32 enc_clr;
u32 lkt_addr;
u32 int_en;
u32 int_msk;
u32 int_clr;
u32 unused_20[4];
u32 int_stus;
/* 12 */
u32 enc_rsl;
u32 enc_pic;
u32 enc_wdg;
u32 dtrns_map;
u32 dtrns_cfg;
u32 src_fmt;
u32 src_udfy;
u32 src_udfu;
u32 src_udfv;
u32 src_udfo;
u32 src_proc;
u32 src_tthrd;
u32 src_stbl[5];
u32 h3d_tbl[40];
u32 src_strd;
u32 adr_srcy;
u32 adr_srcu;
u32 adr_srcv;
u32 adr_fltw;
u32 adr_fltr;
u32 adr_ctuc;
u32 adr_rfpw;
u32 adr_rfpr;
u32 adr_cmvw;
u32 adr_cmvr;
u32 adr_dspw;
u32 adr_dspr;
u32 adr_meiw;
u32 adr_bsbt;
u32 adr_bsbb;
u32 adr_bsbr;
u32 adr_bsbw;
u32 sli_spl;
u32 sli_spl_byte;
u32 me_rnge;
u32 me_cnst;
u32 me_ram;
u32 rc_cfg;
u32 rc_erp[5];
u32 rc_adj[2];
u32 rc_qp;
u32 rc_tgt;
u32 rdo_cfg;
u32 synt_nal;
u32 synt_sps;
u32 synt_pps;
u32 synt_sli0;
u32 synt_sli1;
u32 synt_sli2_rodr;
u32 synt_ref_mark0;
u32 synt_ref_mark1;
u32 osd_cfg;
u32 osd_inv;
u32 unused_1c8[2];
u32 osd_pos[8];
u32 osd_addr[8];
u32 unused_210[9];
};
struct rockchip_rkvenc_dev {
struct rockchip_mpp_dev idev;
unsigned long lkt_dma_addr;
int lkt_hdl;
void *lkt_cpu_addr;
u32 irq_status;
unsigned long war_dma_addr;
int war_hdl;
struct mpp_dev_rkvenc_reg *war_reg;
struct rkvenc_ctx *dummy_ctx;
atomic_t dummy_ctx_in_used;
struct clk *aclk;
struct clk *hclk;
struct clk *core;
struct reset_control *rst_a;
struct reset_control *rst_h;
struct reset_control *rst_v;
};
struct link_table_elem {
unsigned long lkt_dma_addr;
int lkt_hdl;
void *lkt_cpu_addr;
u32 lkt_index;
struct list_head list;
};
#endif

View File

@@ -1,383 +0,0 @@
/**
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* 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 <linux/clk.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/rockchip/grf.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "vpu_iommu_ops.h"
#include "mpp_dev_common.h"
#include "mpp_dev_vepu.h"
#include "mpp_service.h"
#define to_vepu_ctx(ctx) \
container_of(ctx, struct vepu_ctx, ictx)
#define to_vepu_dev(dev) \
container_of(dev, struct rockchip_vepu_dev, idev)
#define VEPU_REG_INTERRUPT 0x1b4
#define VEPU_REG_ENC_START 0x19c
#define VEPU_ENC_GET_FORMAT(x) (((x) >> 4) & 0x3)
#define VEPU_ENC_FMT_VP8E 1
#define VEPU_ENC_ENABLE BIT(0)
#define MHZ (1000 * 1000)
#define DEFAULT_ACLK_RATE_ON (200 * MHZ)
#define DEFAULT_CCLK_RATE_ON (300 * MHZ)
#define DEFAULT_ACLK_RATE_OFF (50 * MHZ)
#define DEFAULT_CCLK_RATE_OFF (50 * MHZ)
/*
* file handle translate information
*/
static const char trans_tbl_default[] = {
77, 78, 56, 57, 63, 64, 48, 49, 50, 81
};
static const char trans_tbl_vp8e[] = {
77, 78, 56, 57, 63, 64, 48, 49, 50, 76, 106, 108, 81, 80, 44, 45, 27
};
static struct mpp_trans_info trans_vepu[2] = {
[0] = {
.count = sizeof(trans_tbl_default),
.table = trans_tbl_default,
},
[1] = {
.count = sizeof(trans_tbl_vp8e),
.table = trans_tbl_vp8e,
},
};
static struct mpp_ctx *rockchip_mpp_vepu_init(struct rockchip_mpp_dev *mpp,
struct mpp_session *session,
void __user *src, u32 size)
{
struct vepu_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
u32 reg_len;
u32 extinf_len;
u32 fmt = 0;
int trans_idx = 0;
u32 dwsize = size / sizeof(u32);
mpp_debug_enter();
if (!ctx)
return NULL;
mpp_dev_common_ctx_init(mpp, &ctx->ictx);
ctx->ictx.session = session;
reg_len = dwsize > ROCKCHIP_VEPU_REG_LEN ?
ROCKCHIP_VEPU_REG_LEN : dwsize;
extinf_len = dwsize > reg_len ? (dwsize - reg_len) * 4 : 0;
if (copy_from_user(ctx->reg, src, reg_len * 4)) {
mpp_err("error: copy_from_user failed in reg_init\n");
kfree(ctx);
return NULL;
}
if (extinf_len > 0) {
u32 ext_cpy = min_t(size_t, extinf_len, sizeof(ctx->ext_inf));
if (copy_from_user(&ctx->ext_inf, (u8 *)src +
reg_len * sizeof(u32),
ext_cpy)) {
mpp_err("copy_from_user failed when extra info\n");
kfree(ctx);
return NULL;
}
}
fmt = VEPU_ENC_GET_FORMAT(ctx->reg[VEPU_REG_ENC_START / 4]);
if (fmt == VEPU_ENC_FMT_VP8E)
trans_idx = 1;
if (mpp_reg_address_translate(mpp, ctx->reg, &ctx->ictx,
trans_idx) < 0) {
mpp_err("error: translate reg address failed.\n");
if (unlikely(mpp_dev_debug & DEBUG_DUMP_ERR_REG))
mpp_dump_reg_mem(ctx->reg, ROCKCHIP_VEPU_REG_LEN);
mpp_dev_common_ctx_deinit(mpp, &ctx->ictx);
kfree(ctx);
return NULL;
}
mpp_debug(DEBUG_SET_REG, "extra info cnt %u, magic %08x",
ctx->ext_inf.cnt, ctx->ext_inf.magic);
mpp_translate_extra_info(&ctx->ictx, &ctx->ext_inf, ctx->reg);
mpp_debug_leave();
return &ctx->ictx;
}
static int rockchip_mpp_vepu_reset_init(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
mpp_debug(DEBUG_RESET, "reset init in:\n");
enc->rst_a = devm_reset_control_get(mpp->dev, "video_a");
enc->rst_h = devm_reset_control_get(mpp->dev, "video_h");
if (IS_ERR_OR_NULL(enc->rst_a)) {
mpp_err("No aclk reset resource define\n");
enc->rst_a = NULL;
}
if (IS_ERR_OR_NULL(enc->rst_h)) {
mpp_err("No hclk reset resource define\n");
enc->rst_h = NULL;
}
return 0;
}
static int rockchip_mpp_vepu_reset(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
if (enc->rst_a && enc->rst_h) {
mpp_debug(DEBUG_RESET, "reset in\n");
reset_control_assert(enc->rst_a);
reset_control_assert(enc->rst_h);
udelay(1);
reset_control_deassert(enc->rst_a);
reset_control_deassert(enc->rst_h);
mpp_debug(DEBUG_RESET, "reset out\n");
}
return 0;
}
static int rockchip_mpp_vepu_run(struct rockchip_mpp_dev *mpp)
{
struct vepu_ctx *ctx =
to_vepu_ctx(mpp_srv_get_current_ctx(mpp->srv));
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
int i;
mpp_debug_enter();
/*
* before encoding running, we have to switch grf ctrl bit to ensure
* ip inner-sram controlled by vepu
*/
#ifdef CONFIG_MFD_SYSCON
if (enc->grf) {
u32 raw;
u32 bits = BIT(enc->mode_bit);
regmap_read(enc->grf, enc->mode_ctrl, &raw);
regmap_write(enc->grf, enc->mode_ctrl,
(raw & (~bits)) | (bits << 16));
}
#endif
/*
* NOTE: encoder need to setup mode first
*/
mpp_write(mpp,
ctx->reg[VEPU_REG_ENC_START / 4] & (~VEPU_ENC_ENABLE),
VEPU_REG_ENC_START);
for (i = 0; i < ROCKCHIP_VEPU_REG_LEN; i++) {
if (i * 4 != VEPU_REG_ENC_START)
mpp_write_relaxed(mpp, ctx->reg[i], i * 4);
}
mpp_write(mpp, ctx->reg[VEPU_REG_ENC_START / 4], VEPU_REG_ENC_START);
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_vepu_done(struct rockchip_mpp_dev *mpp)
{
struct mpp_ctx *ictx = mpp_srv_get_current_ctx(mpp->srv);
struct vepu_ctx *ctx;
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
int i;
mpp_debug_enter();
if (IS_ERR_OR_NULL(ictx)) {
mpp_err("Invaidate context to save result\n");
return -1;
}
ctx = to_vepu_ctx(ictx);
for (i = 0; i < ROCKCHIP_VEPU_REG_LEN; i++)
ctx->reg[i] = mpp_read(mpp, i * 4);
ctx->reg[VEPU_REG_INTERRUPT / 4] = enc->irq_status;
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_vepu_irq(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
enc->irq_status = mpp_read(mpp, VEPU_REG_INTERRUPT);
mpp_debug_enter();
if (enc->irq_status == 0)
return -1;
mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", enc->irq_status);
mpp_write(mpp, 0, VEPU_REG_INTERRUPT);
mpp_debug_leave();
return 0;
}
static int rockchip_mpp_vepu_result(struct rockchip_mpp_dev *mpp,
struct mpp_ctx *ictx, u32 __user *dst)
{
struct vepu_ctx *ctx = to_vepu_ctx(ictx);
if (copy_to_user(dst, ctx->reg, ROCKCHIP_VEPU_REG_LEN * 4)) {
mpp_err("copy_to_user failed\n");
return -1;
}
return 0;
}
struct mpp_dev_ops vepu_ops = {
.init = rockchip_mpp_vepu_init,
.run = rockchip_mpp_vepu_run,
.done = rockchip_mpp_vepu_done,
.irq = rockchip_mpp_vepu_irq,
.result = rockchip_mpp_vepu_result,
};
static void rockchip_mpp_vepu_power_on(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
if (enc->aclk) {
clk_set_rate(enc->aclk, DEFAULT_ACLK_RATE_ON);
clk_prepare_enable(enc->aclk);
}
if (enc->hclk)
clk_prepare_enable(enc->hclk);
if (enc->cclk) {
clk_set_rate(enc->cclk, DEFAULT_CCLK_RATE_ON);
clk_prepare_enable(enc->cclk);
}
}
static void rockchip_mpp_vepu_power_off(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
if (enc->aclk) {
clk_set_rate(enc->aclk, DEFAULT_ACLK_RATE_OFF);
clk_disable_unprepare(enc->aclk);
}
if (enc->hclk)
clk_disable_unprepare(enc->hclk);
if (enc->cclk) {
clk_set_rate(enc->cclk, DEFAULT_CCLK_RATE_OFF);
clk_disable_unprepare(enc->cclk);
}
}
static int rockchip_mpp_vepu_probe(struct rockchip_mpp_dev *mpp)
{
struct rockchip_vepu_dev *enc = to_vepu_dev(mpp);
struct device_node *np = mpp->dev->of_node;
enc->idev.ops = &vepu_ops;
enc->aclk = devm_clk_get(mpp->dev, "aclk_vcodec");
if (IS_ERR_OR_NULL(enc->aclk)) {
dev_err(mpp->dev, "failed on clk_get aclk\n");
goto fail;
}
enc->hclk = devm_clk_get(mpp->dev, "hclk_vcodec");
if (IS_ERR_OR_NULL(enc->hclk)) {
dev_err(mpp->dev, "failed on clk_get hclk\n");
goto fail;
}
enc->cclk = devm_clk_get(mpp->dev, "clk_core");
if (IS_ERR_OR_NULL(enc->cclk)) {
dev_err(mpp->dev, "failed on clk_get cclk\n");
goto fail;
}
if (of_property_read_bool(np, "mode_ctrl")) {
of_property_read_u32(np, "mode_bit", &enc->mode_bit);
of_property_read_u32(np, "mode_ctrl", &enc->mode_ctrl);
#ifdef COFNIG_MFD_SYSCON
enc->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR_OR_NULL(enc->grf)) {
enc->grf = NULL;
mpp_err("can't find vpu grf property\n");
goto fail;
}
#endif
}
rockchip_mpp_vepu_reset_init(mpp);
return 0;
fail:
return -1;
}
static void rockchip_mpp_vepu_remove(struct rockchip_mpp_dev *mpp)
{
}
const struct rockchip_mpp_dev_variant vepu_variant = {
.data_len = sizeof(struct rockchip_vepu_dev),
.reg_len = ROCKCHIP_VEPU_REG_LEN,
.trans_info = trans_vepu,
.mmu_dev_dts_name = NULL,
.hw_probe = rockchip_mpp_vepu_probe,
.hw_remove = rockchip_mpp_vepu_remove,
.power_on = rockchip_mpp_vepu_power_on,
.power_off = rockchip_mpp_vepu_power_off,
.reset = rockchip_mpp_vepu_reset,
};
EXPORT_SYMBOL(vepu_variant);

View File

@@ -1,47 +0,0 @@
/**
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* 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_VEPU_H
#define __ROCKCHIP_MPP_DEV_VEPU_H
#define ROCKCHIP_VEPU_REG_LEN 184
struct regmap;
struct rockchip_vepu_dev {
struct rockchip_mpp_dev idev;
u32 irq_status;
struct clk *aclk;
struct clk *hclk;
struct clk *cclk;
struct reset_control *rst_a;
struct reset_control *rst_h;
u32 mode_bit;
u32 mode_ctrl;
struct regmap *grf;
};
struct vepu_ctx {
struct mpp_ctx ictx;
u32 reg[ROCKCHIP_VEPU_REG_LEN];
struct extra_info_for_iommu ext_inf;
};
#endif

View File

@@ -1,228 +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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include "mpp_dev_common.h"
#include "mpp_service.h"
void mpp_srv_lock(struct mpp_service *pservice)
{
mutex_lock(&pservice->lock);
}
EXPORT_SYMBOL(mpp_srv_lock);
void mpp_srv_unlock(struct mpp_service *pservice)
{
mutex_unlock(&pservice->lock);
}
EXPORT_SYMBOL(mpp_srv_unlock);
/* service queue schedule */
void mpp_srv_pending_locked(struct mpp_service *pservice,
struct mpp_ctx *ctx)
{
mpp_srv_lock(pservice);
list_add_tail(&ctx->status_link, &pservice->pending);
mpp_srv_unlock(pservice);
}
EXPORT_SYMBOL(mpp_srv_pending_locked);
void mpp_srv_run(struct mpp_service *pservice)
{
struct mpp_ctx *ctx = mpp_srv_get_pending_ctx(pservice);
list_del_init(&ctx->status_link);
list_add_tail(&ctx->status_link, &pservice->running);
}
EXPORT_SYMBOL(mpp_srv_run);
void mpp_srv_done(struct mpp_service *pservice)
{
struct mpp_ctx *ctx = list_entry(pservice->running.next,
struct mpp_ctx, status_link);
list_del_init(&ctx->session_link);
list_add_tail(&ctx->session_link, &ctx->session->done);
list_del_init(&ctx->status_link);
list_add_tail(&ctx->status_link, &pservice->done);
wake_up(&ctx->session->wait);
}
EXPORT_SYMBOL(mpp_srv_done);
struct mpp_ctx *mpp_srv_get_pending_ctx(struct mpp_service *pservice)
{
return list_entry(pservice->pending.next, struct mpp_ctx, status_link);
}
EXPORT_SYMBOL(mpp_srv_get_pending_ctx);
struct mpp_ctx *mpp_srv_get_current_ctx(struct mpp_service *pservice)
{
return list_entry(pservice->running.next, struct mpp_ctx, status_link);
}
EXPORT_SYMBOL(mpp_srv_get_current_ctx);
struct mpp_ctx *mpp_srv_get_last_running_ctx(struct mpp_service *pservice)
{
return list_entry(pservice->running.prev, struct mpp_ctx, status_link);
}
EXPORT_SYMBOL(mpp_srv_get_last_running_ctx);
struct mpp_session *mpp_srv_get_current_session(struct mpp_service *pservice)
{
struct mpp_ctx *ctx = list_entry(pservice->running.next,
struct mpp_ctx, status_link);
return ctx ? ctx->session : NULL;
}
EXPORT_SYMBOL(mpp_srv_get_current_session);
struct mpp_ctx *mpp_srv_get_done_ctx(struct mpp_session *session)
{
return list_entry(session->done.next, struct mpp_ctx, session_link);
}
EXPORT_SYMBOL(mpp_srv_get_done_ctx);
bool mpp_srv_pending_is_empty(struct mpp_service *pservice)
{
return !!list_empty(&pservice->pending);
}
EXPORT_SYMBOL(mpp_srv_pending_is_empty);
void mpp_srv_attach(struct mpp_service *pservice, struct list_head *elem)
{
INIT_LIST_HEAD(elem);
list_add_tail(elem, &pservice->subdev_list);
pservice->dev_cnt++;
}
EXPORT_SYMBOL(mpp_srv_attach);
void mpp_srv_detach(struct mpp_service *pservice, struct list_head *elem)
{
list_del_init(elem);
pservice->dev_cnt--;
}
EXPORT_SYMBOL(mpp_srv_detach);
bool mpp_srv_is_running(struct mpp_service *pservice)
{
return !list_empty(&pservice->running);
}
EXPORT_SYMBOL(mpp_srv_is_running);
static void mpp_init_drvdata(struct mpp_service *pservice)
{
INIT_LIST_HEAD(&pservice->pending);
mutex_init(&pservice->lock);
INIT_LIST_HEAD(&pservice->done);
INIT_LIST_HEAD(&pservice->session);
INIT_LIST_HEAD(&pservice->subdev_list);
INIT_LIST_HEAD(&pservice->running);
}
#if defined(CONFIG_OF)
static const struct of_device_id mpp_service_dt_ids[] = {
{ .compatible = "rockchip,mpp_service", },
{ },
};
#endif
static int mpp_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *res = NULL;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct mpp_service *pservice =
devm_kzalloc(dev, sizeof(*pservice),
GFP_KERNEL);
dev_info(dev, "%s enter\n", __func__);
pservice->dev = dev;
mpp_init_drvdata(pservice);
if (of_property_read_bool(np, "reg")) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pservice->reg_base = devm_ioremap_resource(pservice->dev, res);
if (IS_ERR(pservice->reg_base)) {
dev_err(dev, "ioremap registers base failed\n");
ret = PTR_ERR(pservice->reg_base);
pservice->reg_base = 0;
}
} else {
pservice->reg_base = 0;
}
pservice->cls = class_create(THIS_MODULE, dev_name(dev));
if (IS_ERR(pservice->cls)) {
ret = PTR_ERR(pservice->cls);
dev_err(dev, "class_create err:%d\n", ret);
return -1;
}
platform_set_drvdata(pdev, pservice);
dev_info(dev, "init success\n");
return 0;
}
static int mpp_remove(struct platform_device *pdev)
{
struct mpp_service *pservice = platform_get_drvdata(pdev);
class_destroy(pservice->cls);
return 0;
}
static struct platform_driver mpp_driver = {
.probe = mpp_probe,
.remove = mpp_remove,
.driver = {
.name = "mpp",
.owner = THIS_MODULE,
#if defined(CONFIG_OF)
.of_match_table = of_match_ptr(mpp_service_dt_ids),
#endif
},
};
static int __init mpp_service_init(void)
{
int ret = platform_driver_register(&mpp_driver);
if (ret) {
mpp_err("Platform device register failed (%d).\n", ret);
return ret;
}
return ret;
}
subsys_initcall(mpp_service_init);
MODULE_LICENSE("GPL");

View File

@@ -1,75 +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_SERVICE_H
#define __ROCKCHIP_MPP_SERVICE_H
#include <linux/ioctl.h>
#include "mpp_dev_common.h"
struct mpp_session {
/* a linked list of data so we can access them for debugging */
struct list_head list_session;
/* the session related device private data */
struct rockchip_mpp_dev *mpp;
struct list_head done;
wait_queue_head_t wait;
pid_t pid;
atomic_t task_running;
};
enum mpp_srv_state {
HW_RUNNING = BIT(1)
};
struct mpp_service {
/* service structure global lock */
struct mutex lock;
struct list_head pending;
struct list_head done;
struct list_head running;
/* link to list_session in struct mpp_session */
struct list_head session;
struct device *dev;
void __iomem *reg_base;
struct class *cls;
u32 dev_cnt;
struct list_head subdev_list;
};
void mpp_srv_lock(struct mpp_service *pservice);
void mpp_srv_unlock(struct mpp_service *pservice);
void mpp_srv_pending_locked(struct mpp_service *pservice, struct mpp_ctx *ctx);
void mpp_srv_run(struct mpp_service *pservice);
void mpp_srv_done(struct mpp_service *pservice);
void mpp_srv_attach(struct mpp_service *pservice, struct list_head *elem);
void mpp_srv_detach(struct mpp_service *pservice, struct list_head *elem);
struct mpp_ctx *mpp_srv_get_pending_ctx(struct mpp_service *pservice);
struct mpp_ctx *mpp_srv_get_current_ctx(struct mpp_service *pservice);
struct mpp_ctx *mpp_srv_get_last_running_ctx(struct mpp_service *pservice);
struct mpp_session *mpp_srv_get_current_session(struct mpp_service *pservice);
bool mpp_srv_pending_is_empty(struct mpp_service *pservice);
struct mpp_ctx *mpp_srv_get_done_ctx(struct mpp_session *session);
bool mpp_srv_is_power_on(struct mpp_service *pservice);
bool mpp_srv_is_running(struct mpp_service *pservice);
#endif

View File

@@ -1,936 +0,0 @@
/*
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: Jung Zhao jung.zhao@rock-chips.com
* Randy Li, randy.li@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 <linux/dma-iommu.h>
#include <linux/dma-buf.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_sync_helper.h>
#include <drm/rockchip_drm.h>
#include <linux/dma-mapping.h>
#include <linux/rockchip-iovmm.h>
#include <linux/pm_runtime.h>
#include <linux/memblock.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_graph.h>
#include <linux/component.h>
#include <linux/fence.h>
#include <linux/console.h>
#include <linux/kref.h>
#include <linux/fdtable.h>
#include <linux/ktime.h>
#include <linux/iova.h>
#include "vpu_iommu_ops.h"
#define VPU_DRM_BUFFER_ALLOC 0x00000001
struct vpu_drm_buffer {
struct list_head list;
struct dma_buf *dma_buf;
union {
unsigned long iova;
unsigned long phys;
};
void *cpu_addr;
unsigned long size;
int index;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
struct sg_table *copy_sgt;
struct page **pages;
struct kref ref;
struct vpu_iommu_session_info *session_info;
ktime_t last_used;
int flags;
};
struct vpu_iommu_drm_info {
struct iommu_domain *domain;
bool attached;
};
static struct vpu_drm_buffer *
vpu_drm_get_buffer_no_lock(struct vpu_iommu_session_info *session_info,
int idx)
{
struct vpu_drm_buffer *drm_buffer = NULL, *n;
list_for_each_entry_safe(drm_buffer, n, &session_info->buffer_list,
list) {
if (drm_buffer->index == idx) {
drm_buffer->last_used = ktime_get();
return drm_buffer;
}
}
return NULL;
}
static struct vpu_drm_buffer*
vpu_drm_get_buffer_fd_no_lock(struct vpu_iommu_session_info *session_info,
int fd)
{
struct vpu_drm_buffer *drm_buffer = NULL, *n;
struct dma_buf *dma_buf = NULL;
dma_buf = dma_buf_get(fd);
list_for_each_entry_safe(drm_buffer, n, &session_info->buffer_list,
list) {
if (drm_buffer->dma_buf == dma_buf) {
drm_buffer->last_used = ktime_get();
dma_buf_put(dma_buf);
return drm_buffer;
}
}
dma_buf_put(dma_buf);
return NULL;
}
static void vpu_drm_detach(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
struct device *dev = iommu_info->dev;
struct iommu_domain *domain = drm_info->domain;
mutex_lock(&iommu_info->iommu_mutex);
if (!drm_info->attached) {
mutex_unlock(&iommu_info->iommu_mutex);
return;
}
iommu_detach_device(domain, dev);
drm_info->attached = false;
mutex_unlock(&iommu_info->iommu_mutex);
}
static int vpu_drm_attach_unlock(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
struct device *dev = iommu_info->dev;
struct iommu_domain *domain = drm_info->domain;
int ret = 0;
ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
if (ret)
return ret;
dma_set_max_seg_size(dev, DMA_BIT_MASK(32));
ret = iommu_attach_device(domain, dev);
if (ret) {
dev_err(dev, "Failed to attach iommu device\n");
return ret;
}
return ret;
}
static int vpu_drm_attach(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
int ret;
mutex_lock(&iommu_info->iommu_mutex);
if (drm_info->attached) {
mutex_unlock(&iommu_info->iommu_mutex);
return 0;
}
ret = vpu_drm_attach_unlock(iommu_info);
if (ret) {
mutex_unlock(&iommu_info->iommu_mutex);
return ret;
}
drm_info->attached = true;
mutex_unlock(&iommu_info->iommu_mutex);
return ret;
}
static void *vpu_drm_sgt_map_kernel(struct vpu_drm_buffer *drm_buffer)
{
struct vpu_iommu_session_info *session_info =
drm_buffer->session_info;
struct device *dev = session_info->dev;
struct scatterlist *sgl, *sg;
int nr_pages = PAGE_ALIGN(drm_buffer->size) >> PAGE_SHIFT;
int i = 0, j = 0, k = 0;
struct page *page;
drm_buffer->pages = kmalloc_array(nr_pages, sizeof(*drm_buffer->pages),
GFP_KERNEL);
if (!(drm_buffer->pages)) {
dev_err(dev, "drm map can not alloc pages\n");
return NULL;
}
sgl = drm_buffer->copy_sgt->sgl;
for_each_sg(sgl, sg, drm_buffer->copy_sgt->nents, i) {
page = sg_page(sg);
for (j = 0; j < sg->length / PAGE_SIZE; j++)
drm_buffer->pages[k++] = page++;
}
return vmap(drm_buffer->pages, nr_pages, VM_MAP,
pgprot_noncached(PAGE_KERNEL));
}
static void vpu_drm_sgt_unmap_kernel(struct vpu_drm_buffer *drm_buffer)
{
vunmap(drm_buffer->cpu_addr);
kfree(drm_buffer->pages);
}
static int vpu_finalise_sg(struct scatterlist *sg,
int nents,
dma_addr_t dma_addr)
{
struct scatterlist *s, *cur = sg;
unsigned long seg_mask = DMA_BIT_MASK(32);
unsigned int cur_len = 0, max_len = DMA_BIT_MASK(32);
int i, count = 0;
for_each_sg(sg, s, nents, i) {
/* Restore this segment's original unaligned fields first */
unsigned int s_iova_off = sg_dma_address(s);
unsigned int s_length = sg_dma_len(s);
unsigned int s_iova_len = s->length;
s->offset += s_iova_off;
s->length = s_length;
sg_dma_address(s) = DMA_ERROR_CODE;
sg_dma_len(s) = 0;
/*
* Now fill in the real DMA data. If...
* - there is a valid output segment to append to
* - and this segment starts on an IOVA page boundary
* - but doesn't fall at a segment boundary
* - and wouldn't make the resulting output segment too long
*/
if (cur_len && !s_iova_off && (dma_addr & seg_mask) &&
(cur_len + s_length <= max_len)) {
/* ...then concatenate it with the previous one */
cur_len += s_length;
} else {
/* Otherwise start the next output segment */
if (i > 0)
cur = sg_next(cur);
cur_len = s_length;
count++;
sg_dma_address(cur) = dma_addr + s_iova_off;
}
sg_dma_len(cur) = cur_len;
dma_addr += s_iova_len;
if (s_length + s_iova_off < s_iova_len)
cur_len = 0;
}
return count;
}
static void vpu_invalidate_sg(struct scatterlist *sg, int nents)
{
struct scatterlist *s;
int i;
for_each_sg(sg, s, nents, i) {
if (sg_dma_address(s) != DMA_ERROR_CODE)
s->offset += sg_dma_address(s);
if (sg_dma_len(s))
s->length = sg_dma_len(s);
sg_dma_address(s) = DMA_ERROR_CODE;
sg_dma_len(s) = 0;
}
}
static dma_addr_t vpu_dma_map_sg(struct iommu_domain *domain,
struct scatterlist *sg,
int nents, int prot)
{
struct iova_domain *iovad = domain->iova_cookie;
struct iova *iova;
struct scatterlist *s, *prev = NULL;
dma_addr_t dma_addr;
size_t iova_len = 0;
unsigned long mask = DMA_BIT_MASK(32);
unsigned long shift = iova_shift(iovad);
int i;
/*
* Work out how much IOVA space we need, and align the segments to
* IOVA granules for the IOMMU driver to handle. With some clever
* trickery we can modify the list in-place, but reversibly, by
* stashing the unaligned parts in the as-yet-unused DMA fields.
*/
for_each_sg(sg, s, nents, i) {
size_t s_iova_off = iova_offset(iovad, s->offset);
size_t s_length = s->length;
size_t pad_len = (mask - iova_len + 1) & mask;
sg_dma_address(s) = s_iova_off;
sg_dma_len(s) = s_length;
s->offset -= s_iova_off;
s_length = iova_align(iovad, s_length + s_iova_off);
s->length = s_length;
/*
* Due to the alignment of our single IOVA allocation, we can
* depend on these assumptions about the segment boundary mask:
* - If mask size >= IOVA size, then the IOVA range cannot
* possibly fall across a boundary, so we don't care.
* - If mask size < IOVA size, then the IOVA range must start
* exactly on a boundary, therefore we can lay things out
* based purely on segment lengths without needing to know
* the actual addresses beforehand.
* - The mask must be a power of 2, so pad_len == 0 if
* iova_len == 0, thus we cannot dereference prev the first
* time through here (i.e. before it has a meaningful value).
*/
if (pad_len && pad_len < s_length - 1) {
prev->length += pad_len;
iova_len += pad_len;
}
iova_len += s_length;
prev = s;
}
iova = alloc_iova(iovad, iova_align(iovad, iova_len) >> shift,
mask >> shift, true);
if (!iova)
goto out_restore_sg;
/*
* We'll leave any physical concatenation to the IOMMU driver's
* implementation - it knows better than we do.
*/
dma_addr = iova_dma_addr(iovad, iova);
if (iommu_map_sg(domain, dma_addr, sg, nents, prot) < iova_len)
goto out_free_iova;
return vpu_finalise_sg(sg, nents, dma_addr);
out_free_iova:
__free_iova(iovad, iova);
out_restore_sg:
vpu_invalidate_sg(sg, nents);
return 0;
}
static void vpu_dma_unmap_sg(struct iommu_domain *domain,
dma_addr_t dma_addr)
{
struct iova_domain *iovad = domain->iova_cookie;
unsigned long shift = iova_shift(iovad);
unsigned long pfn = dma_addr >> shift;
struct iova *iova = find_iova(iovad, pfn);
size_t size;
if (WARN_ON(!iova))
return;
size = iova_size(iova) << shift;
size -= iommu_unmap(domain, pfn << shift, size);
/* ...and if we can't, then something is horribly, horribly wrong */
WARN_ON(size > 0);
__free_iova(iovad, iova);
}
static void vpu_drm_clear_map(struct kref *ref)
{
struct vpu_drm_buffer *drm_buffer =
container_of(ref,
struct vpu_drm_buffer,
ref);
struct vpu_iommu_session_info *session_info =
drm_buffer->session_info;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
struct sg_table *table;
struct scatterlist *sg;
struct page *page;
int i;
mutex_lock(&iommu_info->iommu_mutex);
drm_info = session_info->iommu_info->private;
if (drm_buffer->cpu_addr) {
vpu_drm_sgt_unmap_kernel(drm_buffer);
drm_buffer->cpu_addr = NULL;
}
vpu_dma_unmap_sg(drm_info->domain, drm_buffer->iova);
if (drm_buffer->flags & VPU_DRM_BUFFER_ALLOC) {
table = drm_buffer->copy_sgt;
for_each_sg(table->sgl, sg, table->nents, i) {
page = sg_page(sg);
__free_pages(page, compound_order(page));
}
}
sg_free_table(drm_buffer->copy_sgt);
kfree(drm_buffer->copy_sgt);
if (drm_buffer->attach) {
dma_buf_unmap_attachment(drm_buffer->attach, drm_buffer->sgt,
DMA_BIDIRECTIONAL);
dma_buf_detach(drm_buffer->dma_buf, drm_buffer->attach);
dma_buf_put(drm_buffer->dma_buf);
drm_buffer->attach = NULL;
}
mutex_unlock(&iommu_info->iommu_mutex);
}
static void vcdoec_drm_dump_info(struct vpu_iommu_session_info *session_info)
{
struct vpu_drm_buffer *drm_buffer = NULL, *n;
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_OPS_DUMP,
"still there are below buffers stored in list\n");
list_for_each_entry_safe(drm_buffer, n, &session_info->buffer_list,
list) {
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_OPS_DUMP,
"index %d drm_buffer dma_buf %p cpu_addr %p\n",
drm_buffer->index,
drm_buffer->dma_buf, drm_buffer->cpu_addr);
}
}
static int vpu_drm_free(struct vpu_iommu_session_info *session_info,
int idx)
{
struct device *dev = session_info->dev;
/* please double-check all maps have been release */
struct vpu_drm_buffer *drm_buffer;
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_no_lock(session_info, idx);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", idx);
mutex_unlock(&session_info->list_mutex);
return -EINVAL;
}
if (atomic_read(&drm_buffer->ref.refcount) == 0) {
if (drm_buffer->dma_buf)
dma_buf_put(drm_buffer->dma_buf);
list_del_init(&drm_buffer->list);
kfree(drm_buffer);
session_info->buffer_nums--;
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_NORMAL,
"buffer nums %d\n", session_info->buffer_nums);
}
mutex_unlock(&session_info->list_mutex);
return 0;
}
static int
vpu_drm_unmap_iommu(struct vpu_iommu_session_info *session_info,
int idx)
{
struct device *dev = session_info->dev;
struct vpu_drm_buffer *drm_buffer;
/* Force to flush iommu table */
if (of_machine_is_compatible("rockchip,rk3288"))
rockchip_iovmm_invalidate_tlb(session_info->mmu_dev);
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", idx);
return -EINVAL;
}
kref_put(&drm_buffer->ref, vpu_drm_clear_map);
return 0;
}
static int vpu_drm_map_iommu(struct vpu_iommu_session_info *session_info,
int idx,
unsigned long *iova,
unsigned long *size)
{
struct device *dev = session_info->dev;
struct vpu_drm_buffer *drm_buffer;
/* Force to flush iommu table */
if (of_machine_is_compatible("rockchip,rk3288"))
rockchip_iovmm_invalidate_tlb(session_info->mmu_dev);
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", idx);
return -EINVAL;
}
kref_get(&drm_buffer->ref);
if (iova)
*iova = drm_buffer->iova;
if (size)
*size = drm_buffer->size;
return 0;
}
static int
vpu_drm_unmap_kernel(struct vpu_iommu_session_info *session_info, int idx)
{
struct device *dev = session_info->dev;
struct vpu_drm_buffer *drm_buffer;
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", idx);
return -EINVAL;
}
if (drm_buffer->cpu_addr) {
vpu_drm_sgt_unmap_kernel(drm_buffer);
drm_buffer->cpu_addr = NULL;
}
kref_put(&drm_buffer->ref, vpu_drm_clear_map);
return 0;
}
static int
vpu_drm_free_fd(struct vpu_iommu_session_info *session_info, int fd)
{
struct device *dev = session_info->dev;
/* please double-check all maps have been release */
struct vpu_drm_buffer *drm_buffer = NULL;
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_fd_no_lock(session_info, fd);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", fd);
mutex_unlock(&session_info->list_mutex);
return -EINVAL;
}
mutex_unlock(&session_info->list_mutex);
vpu_drm_unmap_iommu(session_info, drm_buffer->index);
mutex_lock(&session_info->list_mutex);
if (atomic_read(&drm_buffer->ref.refcount) == 0) {
if (drm_buffer->dma_buf)
dma_buf_put(drm_buffer->dma_buf);
list_del_init(&drm_buffer->list);
kfree(drm_buffer);
session_info->buffer_nums--;
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_NORMAL,
"buffer nums %d\n", session_info->buffer_nums);
}
mutex_unlock(&session_info->list_mutex);
return 0;
}
static void
vpu_drm_clear_session(struct vpu_iommu_session_info *session_info)
{
struct vpu_drm_buffer *drm_buffer = NULL, *n;
list_for_each_entry_safe(drm_buffer, n, &session_info->buffer_list,
list) {
kref_put(&drm_buffer->ref, vpu_drm_clear_map);
vpu_drm_free(session_info, drm_buffer->index);
}
}
static void*
vpu_drm_map_kernel(struct vpu_iommu_session_info *session_info, int idx)
{
struct device *dev = session_info->dev;
struct vpu_drm_buffer *drm_buffer;
mutex_lock(&session_info->list_mutex);
drm_buffer = vpu_drm_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!drm_buffer) {
dev_err(dev, "can not find %d buffer in list\n", idx);
return NULL;
}
if (!drm_buffer->cpu_addr)
drm_buffer->cpu_addr =
vpu_drm_sgt_map_kernel(drm_buffer);
kref_get(&drm_buffer->ref);
return drm_buffer->cpu_addr;
}
static void vpu_drm_remove_extra_buffer_no_lock(struct vpu_iommu_session_info *session_info)
{
struct vpu_drm_buffer *oldest_buffer = NULL, *loop_buffer = NULL;
struct vpu_drm_buffer *n;
ktime_t oldest_time = ktime_set(0, 0);
if (session_info->buffer_nums > BUFFER_LIST_MAX_NUMS) {
list_for_each_entry_safe(loop_buffer, n,
&session_info->buffer_list, list) {
if (loop_buffer->flags & VPU_DRM_BUFFER_ALLOC)
continue;
if (ktime_to_ns(oldest_time) == 0 ||
ktime_after(oldest_time,
loop_buffer->last_used)) {
oldest_time = loop_buffer->last_used;
oldest_buffer = loop_buffer;
}
}
kref_put(&oldest_buffer->ref, vpu_drm_clear_map);
dma_buf_put(oldest_buffer->dma_buf);
list_del_init(&oldest_buffer->list);
kfree(oldest_buffer);
session_info->buffer_nums--;
}
}
static int vpu_drm_import(struct vpu_iommu_session_info *session_info,
int fd)
{
struct vpu_drm_buffer *drm_buffer = NULL, *n;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
struct device *dev = session_info->dev;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
struct dma_buf *dma_buf;
struct scatterlist *sg, *s;
int i;
int ret = 0;
dma_buf = dma_buf_get(fd);
if (IS_ERR(dma_buf)) {
ret = PTR_ERR(dma_buf);
return ret;
}
list_for_each_entry_safe(drm_buffer, n,
&session_info->buffer_list, list) {
if (drm_buffer->dma_buf == dma_buf) {
dma_buf_put(dma_buf);
drm_buffer->last_used = ktime_get();
return drm_buffer->index;
}
}
drm_buffer = kzalloc(sizeof(*drm_buffer), GFP_KERNEL);
if (!drm_buffer) {
ret = -ENOMEM;
return ret;
}
drm_buffer->dma_buf = dma_buf;
drm_buffer->session_info = session_info;
drm_buffer->last_used = ktime_get();
kref_init(&drm_buffer->ref);
mutex_lock(&iommu_info->iommu_mutex);
drm_info = session_info->iommu_info->private;
attach = dma_buf_attach(drm_buffer->dma_buf, dev);
if (IS_ERR(attach)) {
ret = PTR_ERR(attach);
goto fail_out;
}
get_dma_buf(drm_buffer->dma_buf);
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
if (IS_ERR(sgt)) {
ret = PTR_ERR(sgt);
goto fail_detach;
}
/*
* Since we call dma_buf_map_attachment outside attach/detach, this
* will cause incorrectly map. we have to re-build map table native
* and for avoiding destroy their origin map table, we need use a
* copy one sg_table.
*/
drm_buffer->copy_sgt = kmalloc(sizeof(*drm_buffer->copy_sgt),
GFP_KERNEL);
if (!drm_buffer->copy_sgt) {
ret = -ENOMEM;
goto fail_detach;
}
ret = sg_alloc_table(drm_buffer->copy_sgt, sgt->nents, GFP_KERNEL);
s = drm_buffer->copy_sgt->sgl;
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
sg_set_page(s, sg_page(sg),
PAGE_SIZE << compound_order(sg_page(sg)), 0);
sg_dma_address(s) = page_to_phys(sg_page(sg));
s->offset = sg->offset;
s->length = sg->length;
s = sg_next(s);
}
vpu_dma_map_sg(drm_info->domain, drm_buffer->copy_sgt->sgl,
drm_buffer->copy_sgt->nents,
IOMMU_READ | IOMMU_WRITE);
drm_buffer->iova = sg_dma_address(drm_buffer->copy_sgt->sgl);
drm_buffer->size = drm_buffer->dma_buf->size;
drm_buffer->attach = attach;
drm_buffer->sgt = sgt;
mutex_unlock(&iommu_info->iommu_mutex);
INIT_LIST_HEAD(&drm_buffer->list);
mutex_lock(&session_info->list_mutex);
session_info->buffer_nums++;
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_NORMAL,
"buffer nums %d\n", session_info->buffer_nums);
vpu_drm_remove_extra_buffer_no_lock(session_info);
drm_buffer->index = session_info->max_idx;
list_add_tail(&drm_buffer->list, &session_info->buffer_list);
session_info->max_idx++;
if ((session_info->max_idx & 0xfffffff) == 0)
session_info->max_idx = 0;
mutex_unlock(&session_info->list_mutex);
return drm_buffer->index;
fail_detach:
dev_err(dev, "dmabuf map attach failed\n");
dma_buf_detach(drm_buffer->dma_buf, attach);
dma_buf_put(drm_buffer->dma_buf);
fail_out:
kfree(drm_buffer);
mutex_unlock(&iommu_info->iommu_mutex);
return ret;
}
static int vpu_drm_alloc(struct vpu_iommu_session_info *session_info,
unsigned long size,
unsigned long align)
{
struct sg_table *table;
struct scatterlist *sg;
struct list_head pages;
struct page *page, *tmp_page;
long size_remaining = PAGE_ALIGN(size);
struct vpu_drm_buffer *drm_buffer;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
int i;
if (align > PAGE_SIZE)
return -EINVAL;
if (size / PAGE_SIZE > totalram_pages / 2)
return -ENOMEM;
drm_buffer = kzalloc(sizeof(*drm_buffer), GFP_KERNEL);
if (!drm_buffer)
return -ENOMEM;
drm_buffer->session_info = session_info;
drm_buffer->last_used = ktime_set(0, 0);
kref_init(&drm_buffer->ref);
INIT_LIST_HEAD(&pages);
i = 0;
while (size_remaining > 0) {
gfp_t gfp_flags = (GFP_HIGHUSER | __GFP_ZERO | __GFP_NOWARN |
__GFP_NORETRY) & ~__GFP_DIRECT_RECLAIM;
page = alloc_pages(gfp_flags | __GFP_COMP, 8);
if (!page)
goto free_pages;
size_remaining -= PAGE_SIZE << compound_order(page);
list_add_tail(&page->lru, &pages);
i++;
}
table = kmalloc(sizeof(*table), GFP_KERNEL);
if (!table)
goto free_pages;
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_table;
sg = table->sgl;
list_for_each_entry_safe(page, tmp_page, &pages, lru) {
sg_set_page(sg, page, PAGE_SIZE << compound_order(page), 0);
sg = sg_next(sg);
list_del(&page->lru);
}
mutex_lock(&iommu_info->iommu_mutex);
drm_info = session_info->iommu_info->private;
drm_buffer->copy_sgt = table;
vpu_dma_map_sg(drm_info->domain, drm_buffer->copy_sgt->sgl,
drm_buffer->copy_sgt->nents,
IOMMU_READ | IOMMU_WRITE);
drm_buffer->iova = sg_dma_address(drm_buffer->copy_sgt->sgl);
drm_buffer->size = size;
drm_buffer->flags = VPU_DRM_BUFFER_ALLOC;
mutex_unlock(&iommu_info->iommu_mutex);
INIT_LIST_HEAD(&drm_buffer->list);
mutex_lock(&session_info->list_mutex);
session_info->buffer_nums++;
vpu_iommu_debug(session_info->debug_level, DEBUG_IOMMU_NORMAL,
"buffer nums %d\n", session_info->buffer_nums);
vpu_drm_remove_extra_buffer_no_lock(session_info);
drm_buffer->index = session_info->max_idx;
list_add_tail(&drm_buffer->list, &session_info->buffer_list);
session_info->max_idx++;
if ((session_info->max_idx & 0xfffffff) == 0)
session_info->max_idx = 0;
mutex_unlock(&session_info->list_mutex);
return drm_buffer->index;
free_table:
kfree(table);
free_pages:
list_for_each_entry_safe(page, tmp_page, &pages, lru)
__free_pages(page, 8);
kfree(drm_buffer);
return -ENOMEM;
}
static int vpu_drm_create(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_drm_info *drm_info;
struct iommu_group *group;
int ret;
iommu_info->private = kzalloc(sizeof(*drm_info),
GFP_KERNEL);
drm_info = iommu_info->private;
if (!drm_info)
return -ENOMEM;
drm_info->domain = iommu_domain_alloc(&platform_bus_type);
drm_info->attached = false;
if (!drm_info->domain)
return -ENOMEM;
ret = iommu_get_dma_cookie(drm_info->domain);
if (ret)
goto err_free_domain;
group = iommu_group_get(iommu_info->dev);
if (!group) {
group = iommu_group_alloc();
if (IS_ERR(group)) {
dev_err(iommu_info->dev,
"Failed to allocate IOMMU group\n");
goto err_put_cookie;
}
ret = iommu_group_add_device(group, iommu_info->dev);
if (ret) {
dev_err(iommu_info->dev,
"failed to add device to IOMMU group\n");
goto err_put_cookie;
}
}
iommu_dma_init_domain(drm_info->domain, 0x10000000, SZ_2G);
iommu_group_put(group);
return 0;
err_put_cookie:
iommu_put_dma_cookie(drm_info->domain);
err_free_domain:
iommu_domain_free(drm_info->domain);
return ret;
}
static int vpu_drm_destroy(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_drm_info *drm_info = iommu_info->private;
iommu_put_dma_cookie(drm_info->domain);
iommu_domain_free(drm_info->domain);
kfree(drm_info);
iommu_info->private = NULL;
return 0;
}
static struct vpu_iommu_ops drm_ops = {
.create = vpu_drm_create,
.alloc = vpu_drm_alloc,
.import = vpu_drm_import,
.free = vpu_drm_free,
.free_fd = vpu_drm_free_fd,
.map_kernel = vpu_drm_map_kernel,
.unmap_kernel = vpu_drm_unmap_kernel,
.map_iommu = vpu_drm_map_iommu,
.unmap_iommu = vpu_drm_unmap_iommu,
.destroy = vpu_drm_destroy,
.dump = vcdoec_drm_dump_info,
.attach = vpu_drm_attach,
.detach = vpu_drm_detach,
.clear = vpu_drm_clear_session,
};
void vpu_iommu_drm_set_ops(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info)
return;
iommu_info->ops = &drm_ops;
}

View File

@@ -1,410 +0,0 @@
/*
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: Jung Zhao jung.zhao@rock-chips.com
* Randy Li, randy.li@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 "vpu_iommu_ops.h"
#if defined(CONFIG_ION_ROCKCHIP)
#include <linux/rockchip_ion.h>
#include <linux/rockchip-iovmm.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/memblock.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_graph.h>
#include <linux/component.h>
#include <linux/fence.h>
#include <linux/console.h>
#include <linux/kref.h>
#include <linux/fdtable.h>
struct vpu_ion_buffer {
struct list_head list;
struct ion_handle *handle;
int index;
};
struct vpu_iommu_ion_info {
struct ion_client *ion_client;
bool attached;
};
static struct vpu_ion_buffer*
vpu_ion_get_buffer_no_lock(struct vpu_iommu_session_info *session_info,
int idx)
{
struct vpu_ion_buffer *ion_buffer = NULL, *n;
list_for_each_entry_safe(ion_buffer, n,
&session_info->buffer_list, list) {
if (ion_buffer->index == idx)
return ion_buffer;
}
return NULL;
}
static void
vpu_ion_clear_session(struct vpu_iommu_session_info *session_info)
{
/* do nothing */
}
static int vpu_ion_attach(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
int ret;
mutex_lock(&iommu_info->iommu_mutex);
if (ion_info->attached) {
mutex_unlock(&iommu_info->iommu_mutex);
return 0;
}
rockchip_iovmm_activate(iommu_info->dev);
ion_info->attached = true;
mutex_unlock(&iommu_info->iommu_mutex);
return ret;
}
static void vpu_ion_detach(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
mutex_lock(&iommu_info->iommu_mutex);
if (!ion_info->attached) {
mutex_unlock(&iommu_info->iommu_mutex);
return;
}
rockchip_iovmm_deactivate(iommu_info->dev);
ion_info->attached = false;
mutex_unlock(&iommu_info->iommu_mutex);
}
static int vpu_ion_destroy(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
vpu_ion_detach(iommu_info);
kfree(ion_info);
iommu_info->private = NULL;
return 0;
}
static int
vpu_ion_free(struct vpu_iommu_session_info *session_info, int idx)
{
struct vpu_ion_buffer *ion_buffer;
mutex_lock(&session_info->list_mutex);
ion_buffer = vpu_ion_get_buffer_no_lock(session_info, idx);
if (!ion_buffer) {
mutex_unlock(&session_info->list_mutex);
pr_err("%s can not find %d buffer in list\n", __func__, idx);
return -EINVAL;
}
list_del_init(&ion_buffer->list);
mutex_unlock(&session_info->list_mutex);
kfree(ion_buffer);
return 0;
}
static int
vpu_ion_unmap_iommu(struct vpu_iommu_session_info *session_info, int idx)
{
struct vpu_ion_buffer *ion_buffer;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
mutex_lock(&session_info->list_mutex);
ion_buffer = vpu_ion_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!ion_buffer) {
pr_err("%s can not find %d buffer in list\n", __func__, idx);
return -EINVAL;
}
ion_free(ion_info->ion_client, ion_buffer->handle);
return 0;
}
static int
vpu_ion_map_iommu(struct vpu_iommu_session_info *session_info, int idx,
unsigned long *iova, unsigned long *size)
{
struct vpu_ion_buffer *ion_buffer;
struct device *dev = session_info->dev;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
int ret = 0;
/* Force to flush iommu table */
rockchip_iovmm_invalidate_tlb(session_info->dev);
mutex_lock(&session_info->list_mutex);
ion_buffer = vpu_ion_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!ion_buffer) {
pr_err("%s can not find %d buffer in list\n", __func__, idx);
return -EINVAL;
}
if (session_info->mmu_dev)
ret = ion_map_iommu(dev, ion_info->ion_client,
ion_buffer->handle, iova, size);
else
ret = ion_phys(ion_info->ion_client, ion_buffer->handle,
iova, (size_t *)size);
return ret;
}
static int
vpu_ion_unmap_kernel(struct vpu_iommu_session_info *session_info,
int idx)
{
struct vpu_ion_buffer *ion_buffer;
mutex_lock(&session_info->list_mutex);
ion_buffer = vpu_ion_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!ion_buffer) {
pr_err("%s can not find %d buffer in list\n", __func__, idx);
return -EINVAL;
}
return 0;
}
static void*
vpu_ion_map_kernel(struct vpu_iommu_session_info *session_info, int idx)
{
struct vpu_ion_buffer *ion_buffer;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
rockchip_iovmm_invalidate_tlb(session_info->dev);
mutex_lock(&session_info->list_mutex);
ion_buffer = vpu_ion_get_buffer_no_lock(session_info, idx);
mutex_unlock(&session_info->list_mutex);
if (!ion_buffer) {
pr_err("%s can not find %d buffer in list\n", __func__, idx);
return NULL;
}
return ion_map_kernel(ion_info->ion_client, ion_buffer->handle);
}
static int vpu_ion_alloc(struct vpu_iommu_session_info *session_info,
unsigned long size,
unsigned long align)
{
struct vpu_ion_buffer *ion_buffer = NULL;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
unsigned int heap_id_mask;
if (iommu_info->mmu_dev)
heap_id_mask = ION_HEAP_TYPE_SYSTEM;
else
heap_id_mask = ION_HEAP_TYPE_DMA;
ion_buffer = kzalloc(sizeof(*ion_buffer), GFP_KERNEL);
if (!ion_buffer)
return -ENOMEM;
ion_buffer->handle = ion_alloc(ion_info->ion_client, size,
align, heap_id_mask, 0);
INIT_LIST_HEAD(&ion_buffer->list);
mutex_lock(&session_info->list_mutex);
ion_buffer->index = session_info->max_idx;
list_add_tail(&ion_buffer->list, &session_info->buffer_list);
session_info->max_idx++;
if ((session_info->max_idx & 0xfffffff) == 0)
session_info->max_idx = 0;
mutex_unlock(&session_info->list_mutex);
return ion_buffer->index;
}
static int
vpu_ion_import(struct vpu_iommu_session_info *session_info, int fd)
{
struct vpu_ion_buffer *ion_buffer = NULL;
struct vpu_iommu_info *iommu_info = session_info->iommu_info;
struct vpu_iommu_ion_info *ion_info = iommu_info->private;
ion_buffer = kzalloc(sizeof(*ion_buffer), GFP_KERNEL);
if (!ion_buffer)
return -ENOMEM;
ion_buffer->handle = ion_import_dma_buf(ion_info->ion_client, fd);
INIT_LIST_HEAD(&ion_buffer->list);
mutex_lock(&session_info->list_mutex);
ion_buffer->index = session_info->max_idx;
list_add_tail(&ion_buffer->list, &session_info->buffer_list);
session_info->max_idx++;
if ((session_info->max_idx & 0xfffffff) == 0)
session_info->max_idx = 0;
mutex_unlock(&session_info->list_mutex);
return ion_buffer->index;
}
static int vpu_ion_create(struct vpu_iommu_info *iommu_info)
{
struct vpu_iommu_ion_info *ion_info;
iommu_info->private = kmalloc(sizeof(*ion_info), GFP_KERNEL);
ion_info = iommu_info->private;
if (!ion_info)
return -ENOMEM;
ion_info->ion_client = rockchip_ion_client_create("vpu");
ion_info->attached = false;
vpu_ion_attach(iommu_info);
return IS_ERR(ion_info->ion_client) ? -1 : 0;
}
#else
static void
vpu_ion_clear_session(struct vpu_iommu_session_info *session_info)
{
/* do nothing */
}
static int vpu_ion_attach(struct vpu_iommu_info *iommu_info)
{
return 0;
}
static void vpu_ion_detach(struct vpu_iommu_info *iommu_info)
{
}
static int vpu_ion_destroy(struct vpu_iommu_info *iommu_info)
{
return 0;
}
static int
vpu_ion_free(struct vpu_iommu_session_info *session_info, int idx)
{
return 0;
}
static int
vpu_ion_unmap_iommu(struct vpu_iommu_session_info *session_info, int idx)
{
return 0;
}
static int
vpu_ion_map_iommu(struct vpu_iommu_session_info *session_info, int idx,
unsigned long *iova, unsigned long *size)
{
return 0;
}
static int
vpu_ion_unmap_kernel(struct vpu_iommu_session_info *session_info,
int idx)
{
return 0;
}
static void*
vpu_ion_map_kernel(struct vpu_iommu_session_info *session_info, int idx)
{
return NULL;
}
static int vpu_ion_alloc(struct vpu_iommu_session_info *session_info,
unsigned long size,
unsigned long align)
{
return 0;
}
static int
vpu_ion_import(struct vpu_iommu_session_info *session_info, int fd)
{
return 0;
}
static int vpu_ion_create(struct vpu_iommu_info *iommu_info)
{
return -1;
}
#endif
static struct vpu_iommu_ops ion_ops = {
.create = vpu_ion_create,
.destroy = vpu_ion_destroy,
.alloc = vpu_ion_alloc,
.import = vpu_ion_import,
.free = vpu_ion_free,
.free_fd = NULL,
.map_kernel = vpu_ion_map_kernel,
.unmap_kernel = vpu_ion_unmap_kernel,
.map_iommu = vpu_ion_map_iommu,
.unmap_iommu = vpu_ion_unmap_iommu,
.dump = NULL,
.attach = vpu_ion_attach,
.detach = vpu_ion_detach,
.clear = vpu_ion_clear_session,
};
/*
* we do not manage the ref number ourselves,
* since ion will help us to do that. what we
* need to do is just map/unmap and import/free
* every time
*/
void vpu_iommu_ion_set_ops(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info)
return;
iommu_info->ops = &ion_ops;
}

View File

@@ -1,291 +0,0 @@
/**
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: Jung Zhao jung.zhao@rock-chips.com
* Randy Li, randy.li@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 <linux/slab.h>
#include "vpu_iommu_ops.h"
static
struct vpu_iommu_session_info *vpu_iommu_get_session_info(struct vpu_iommu_info *iommu_info,
struct mpp_session *session)
{
struct vpu_iommu_session_info *session_info = NULL, *n;
list_for_each_entry_safe(session_info, n, &iommu_info->session_list,
head) {
if (session_info->session == session)
return session_info;
}
return NULL;
}
int vpu_iommu_create(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info || !iommu_info->ops->create)
return -EINVAL;
return iommu_info->ops->create(iommu_info);
}
int vpu_iommu_alloc(struct vpu_iommu_info *iommu_info,
struct mpp_session *session,
unsigned long size,
unsigned long align)
{
struct vpu_iommu_session_info *session_info = NULL;
if (!iommu_info || !iommu_info->ops->alloc || !session)
return -EINVAL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!session_info) {
session_info = kzalloc(sizeof(*session_info), GFP_KERNEL);
if (!session_info)
return -ENOMEM;
INIT_LIST_HEAD(&session_info->head);
INIT_LIST_HEAD(&session_info->buffer_list);
mutex_init(&session_info->list_mutex);
session_info->max_idx = 0;
session_info->session = session;
session_info->mmu_dev = iommu_info->mmu_dev;
session_info->dev = iommu_info->dev;
session_info->iommu_info = iommu_info;
session_info->buffer_nums = 0;
mutex_lock(&iommu_info->list_mutex);
list_add_tail(&session_info->head, &iommu_info->session_list);
mutex_unlock(&iommu_info->list_mutex);
}
session_info->debug_level = iommu_info->debug_level;
return iommu_info->ops->alloc(session_info, size, align);
}
int vpu_iommu_import(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int fd)
{
struct vpu_iommu_session_info *session_info = NULL;
if (!iommu_info || !iommu_info->ops->import || !session)
return -EINVAL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!session_info) {
session_info = kzalloc(sizeof(*session_info), GFP_KERNEL);
if (!session_info)
return -ENOMEM;
INIT_LIST_HEAD(&session_info->head);
INIT_LIST_HEAD(&session_info->buffer_list);
mutex_init(&session_info->list_mutex);
session_info->max_idx = 0;
session_info->session = session;
session_info->mmu_dev = iommu_info->mmu_dev;
session_info->dev = iommu_info->dev;
session_info->iommu_info = iommu_info;
session_info->buffer_nums = 0;
mutex_lock(&iommu_info->list_mutex);
list_add_tail(&session_info->head, &iommu_info->session_list);
mutex_unlock(&iommu_info->list_mutex);
}
session_info->debug_level = iommu_info->debug_level;
return iommu_info->ops->import(session_info, fd);
}
int vpu_iommu_free(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->free || !session_info)
return -EINVAL;
return iommu_info->ops->free(session_info, idx);
}
int vpu_iommu_free_fd(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int fd)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->free_fd || !session_info)
return -EINVAL;
return iommu_info->ops->free_fd(session_info, fd);
}
void *vpu_iommu_map_kernel(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->map_kernel || !session_info)
return NULL;
return iommu_info->ops->map_kernel(session_info, idx);
}
int vpu_iommu_unmap_kernel(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->unmap_kernel || !session_info)
return -EINVAL;
return iommu_info->ops->unmap_kernel(session_info, idx);
}
int vpu_iommu_map_iommu(struct vpu_iommu_info *iommu_info,
struct mpp_session *session,
int idx, unsigned long *iova,
unsigned long *size)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->map_iommu || !session_info)
return -EINVAL;
return iommu_info->ops->map_iommu(session_info, idx, iova, size);
}
int vpu_iommu_unmap_iommu(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->unmap_iommu || !session_info)
return -EINVAL;
return iommu_info->ops->unmap_iommu(session_info, idx);
}
int vpu_iommu_destroy(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info || !iommu_info->ops->destroy)
return -EINVAL;
return iommu_info->ops->destroy(iommu_info);
}
void vpu_iommu_dump(struct vpu_iommu_info *iommu_info,
struct mpp_session *session)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->dump || !session_info)
return;
iommu_info->ops->dump(session_info);
}
void vpu_iommu_clear(struct vpu_iommu_info *iommu_info,
struct mpp_session *session)
{
struct vpu_iommu_session_info *session_info = NULL;
session_info = vpu_iommu_get_session_info(iommu_info, session);
if (!iommu_info || !iommu_info->ops->clear || !session_info)
return;
iommu_info->ops->clear(session_info);
mutex_lock(&iommu_info->list_mutex);
list_del_init(&session_info->head);
kfree(session_info);
mutex_unlock(&iommu_info->list_mutex);
}
int vpu_iommu_attach(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info || !iommu_info->ops->attach)
return 0;
return iommu_info->ops->attach(iommu_info);
}
void vpu_iommu_detach(struct vpu_iommu_info *iommu_info)
{
if (!iommu_info || !iommu_info->ops->detach)
return;
return iommu_info->ops->detach(iommu_info);
}
struct vpu_iommu_info*
vpu_iommu_info_create(struct device *dev,
struct device *mmu_dev,
int alloc_type)
{
struct vpu_iommu_info *iommu_info = NULL;
iommu_info = kzalloc(sizeof(*iommu_info), GFP_KERNEL);
if (!iommu_info)
return NULL;
iommu_info->dev = dev;
INIT_LIST_HEAD(&iommu_info->session_list);
mutex_init(&iommu_info->list_mutex);
mutex_init(&iommu_info->iommu_mutex);
switch (alloc_type) {
#ifdef CONFIG_DRM
case ALLOCATOR_USE_DRM:
vpu_iommu_drm_set_ops(iommu_info);
break;
#endif
#ifdef CONFIG_ION
case ALLOCATOR_USE_ION:
vpu_iommu_ion_set_ops(iommu_info);
break;
#endif
default:
iommu_info->ops = NULL;
break;
}
iommu_info->mmu_dev = mmu_dev;
vpu_iommu_create(iommu_info);
return iommu_info;
}
int vpu_iommu_info_destroy(struct vpu_iommu_info *iommu_info)
{
vpu_iommu_destroy(iommu_info);
kfree(iommu_info);
return 0;
}

View File

@@ -1,139 +0,0 @@
/**
* Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
* author: Jung Zhao jung.zhao@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 __VPU_IOMMU_OPS_H__
#define __VPU_IOMMU_OPS_H__
#include <linux/platform_device.h>
#include "mpp_service.h"
#define BUFFER_LIST_MAX_NUMS 30
#define ALLOCATOR_USE_ION 0x00000000
#define ALLOCATOR_USE_DRM 0x00000001
#define DEBUG_IOMMU_OPS_DUMP 0x00020000
#define DEBUG_IOMMU_NORMAL 0x00040000
#define vpu_iommu_debug_func(debug_level, type, fmt, args...) \
do { \
if (unlikely((debug_level) & type)) { \
pr_info("%s:%d: " fmt, \
__func__, __LINE__, ##args); \
} \
} while (0)
#define vpu_iommu_debug(debug_level, type, fmt, args...) \
do { \
if (unlikely((debug_level) & type)) { \
pr_info(fmt, ##args); \
} \
} while (0)
struct vpu_iommu_info;
struct vpu_iommu_session_info;
struct vpu_iommu_ops {
int (*create)(struct vpu_iommu_info *iommu_info);
int (*alloc)(struct vpu_iommu_session_info *session_info,
unsigned long size,
unsigned long align);
int (*import)(struct vpu_iommu_session_info *session_info, int fd);
int (*free)(struct vpu_iommu_session_info *session_info, int idx);
int (*free_fd)(struct vpu_iommu_session_info *session_info, int fd);
void* (*map_kernel)(struct vpu_iommu_session_info *session_info,
int idx);
int (*unmap_kernel)(struct vpu_iommu_session_info *session_info,
int idx);
int (*map_iommu)(struct vpu_iommu_session_info *session_info,
int idx,
unsigned long *iova, unsigned long *size);
int (*unmap_iommu)(struct vpu_iommu_session_info *session_info,
int idx);
int (*destroy)(struct vpu_iommu_info *iommu_info);
void (*dump)(struct vpu_iommu_session_info *session_info);
int (*attach)(struct vpu_iommu_info *iommu_info);
void (*detach)(struct vpu_iommu_info *iommu_info);
void (*clear)(struct vpu_iommu_session_info *session_info);
};
struct vpu_iommu_session_info {
struct list_head head;
struct mpp_session *session;
int buffer_nums;
struct list_head buffer_list;
struct mutex list_mutex;
int max_idx;
struct device *dev;
struct device *mmu_dev;
struct vpu_iommu_info *iommu_info;
int debug_level;
};
struct vpu_iommu_info {
struct list_head session_list;
struct mutex list_mutex;
struct mutex iommu_mutex;
struct device *dev;
struct device *mmu_dev;
struct vpu_iommu_ops *ops;
int debug_level;
void *private;
};
#ifdef CONFIG_DRM
void vpu_iommu_drm_set_ops(struct vpu_iommu_info *iommu_info);
#endif
#ifdef CONFIG_ION
void vpu_iommu_ion_set_ops(struct vpu_iommu_info *iommu_info);
#endif
struct vpu_iommu_info *vpu_iommu_info_create(struct device *dev,
struct device *mmu_dev,
int alloc_type);
int vpu_iommu_info_destroy(struct vpu_iommu_info *iommu_info);
int vpu_iommu_create(struct vpu_iommu_info *iommu_info);
int vpu_iommu_alloc(struct vpu_iommu_info *iommu_info,
struct mpp_session *session,
unsigned long size,
unsigned long align);
int vpu_iommu_import(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int fd);
int vpu_iommu_free(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx);
int vpu_iommu_free_fd(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int fd);
void *vpu_iommu_map_kernel(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx);
int vpu_iommu_unmap_kernel(struct vpu_iommu_info *iommu_info,
struct mpp_session *session, int idx);
int vpu_iommu_map_iommu(struct vpu_iommu_info *iommu_info,
struct mpp_session *session,
int idx,
unsigned long *iova,
unsigned long *size);
int vpu_iommu_unmap_iommu(struct vpu_iommu_info *iommu_info,
struct mpp_session *session,
int idx);
int vpu_iommu_destroy(struct vpu_iommu_info *iommu_info);
void vpu_iommu_dump(struct vpu_iommu_info *iommu_info,
struct mpp_session *session);
void vpu_iommu_clear(struct vpu_iommu_info *iommu_info,
struct mpp_session *session);
int vpu_iommu_attach(struct vpu_iommu_info *iommu_info);
void vpu_iommu_detach(struct vpu_iommu_info *iommu_info);
#endif