1063 lines
26 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
*
* author:
* Ding Wei, leo.ding@rock-chips.com
*
*/
#define pr_fmt(fmt) "mpp_av1dec: " fmt
#include <asm/cacheflush.h>
#include <linux/clk.h>
#include <linux/clk/clk-conf.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/clk/clk-conf.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/proc_fs.h>
#include <soc/rockchip/pm_domains.h>
#include "mpp_debug.h"
#include "mpp_common.h"
#include "mpp_iommu.h"
#define AV1DEC_DRIVER_NAME "mpp_av1dec"
#define AV1DEC_SESSION_MAX_BUFFERS 40
/* REG_DEC_INT, bits for interrupt */
#define AV1DEC_INT_PIC_INF BIT(24)
#define AV1DEC_INT_TIMEOUT BIT(18)
#define AV1DEC_INT_SLICE BIT(17)
#define AV1DEC_INT_STRM_ERROR BIT(16)
#define AV1DEC_INT_ASO_ERROR BIT(15)
#define AV1DEC_INT_BUF_EMPTY BIT(14)
#define AV1DEC_INT_BUS_ERROR BIT(13)
#define AV1DEC_DEC_INT BIT(12)
#define AV1DEC_DEC_INT_RAW BIT(8)
#define AV1DEC_DEC_IRQ_DIS BIT(4)
#define AV1DEC_DEC_START BIT(0)
#define MPP_ALIGN(x, a) (((x)+(a)-1)&~((a)-1))
/* REG_DEC_EN, bit for gate */
#define AV1DEC_CLOCK_GATE_EN BIT(10)
#define to_av1dec_info(info) \
container_of(info, struct av1dec_hw_info, hw)
#define to_av1dec_task(ctx) \
container_of(ctx, struct av1dec_task, mpp_task)
#define to_av1dec_dev(dev) \
container_of(dev, struct av1dec_dev, mpp)
/* define functions */
#define MPP_GET_BITS(v, p, b) (((v) >> (p)) & ((1 << (b)) - 1))
#define MPP_BASE_TO_IDX(a) ((a) / sizeof(u32))
enum AV1DEC_CLASS_TYPE {
AV1DEC_CLASS_VCD = 0,
AV1DEC_CLASS_CACHE = 1,
AV1DEC_CLASS_AFBC = 2,
AV1DEC_CLASS_BUTT,
};
enum av1dec_trans_type {
AV1DEC_TRANS_BASE = 0x0000,
AV1DEC_TRANS_VCD = AV1DEC_TRANS_BASE + 0,
AV1DEC_TRANS_CACHE = AV1DEC_TRANS_BASE + 1,
AV1DEC_TRANS_AFBC = AV1DEC_TRANS_BASE + 2,
AV1DEC_TRANS_BUTT,
};
struct av1dec_hw_info {
struct mpp_hw_info hw;
/* register range by class */
u32 reg_class_num;
struct {
u32 base_s;
u32 base_e;
} reg_class[AV1DEC_CLASS_BUTT];
/* fd translate for class */
u32 trans_class_num;
struct {
u32 class;
u32 trans_fmt;
} trans_class[AV1DEC_TRANS_BUTT];
/* interrupt config register */
int int_base;
/* enable hardware register */
int en_base;
/* status register */
int sta_base;
/* clear irq register */
int clr_base;
/* stream register */
int strm_base;
u32 err_mask;
};
struct av1dec_task {
struct mpp_task mpp_task;
struct av1dec_hw_info *hw_info;
/* for malloc register data buffer */
u32 *reg_data;
/* class register */
struct {
u32 valid;
u32 base;
u32 *data;
/* offset base reg_data */
u32 off;
/* length for class */
u32 len;
} reg_class[AV1DEC_CLASS_BUTT];
/* register offset info */
struct reg_offset_info off_inf;
enum MPP_CLOCK_MODE clk_mode;
u32 irq_status;
/* req for current task */
u32 w_req_cnt;
struct mpp_request w_reqs[MPP_MAX_MSG_NUM];
u32 r_req_cnt;
struct mpp_request r_reqs[MPP_MAX_MSG_NUM];
};
struct av1dec_dev {
struct mpp_dev mpp;
struct av1dec_hw_info *hw_info;
struct mpp_clk_info aclk_info;
struct mpp_clk_info hclk_info;
u32 default_max_load;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *procfs;
#endif
struct reset_control *rst_a;
struct reset_control *rst_h;
void __iomem *reg_base[AV1DEC_CLASS_BUTT];
int irq[AV1DEC_CLASS_BUTT];
};
static struct av1dec_hw_info av1dec_hw_info = {
.hw = {
.reg_num = 512,
.reg_id = 0,
.reg_en = 1,
.reg_start = 1,
.reg_end = 319,
},
.reg_class_num = 3,
.reg_class[AV1DEC_CLASS_VCD] = {
.base_s = 0x0000,
.base_e = 0x07fc,
},
.reg_class[AV1DEC_CLASS_CACHE] = {
.base_s = 0x10000,
.base_e = 0x10294,
},
.reg_class[AV1DEC_CLASS_AFBC] = {
.base_s = 0x20000,
.base_e = 0x2034c,
},
.trans_class_num = AV1DEC_TRANS_BUTT,
.trans_class[AV1DEC_CLASS_VCD] = {
.class = AV1DEC_CLASS_VCD,
.trans_fmt = AV1DEC_TRANS_VCD,
},
.trans_class[AV1DEC_CLASS_CACHE] = {
.class = AV1DEC_CLASS_CACHE,
.trans_fmt = AV1DEC_TRANS_CACHE,
},
.trans_class[AV1DEC_CLASS_AFBC] = {
.class = AV1DEC_CLASS_AFBC,
.trans_fmt = AV1DEC_TRANS_AFBC,
},
.int_base = 0x0004,
.en_base = 0x0004,
.sta_base = 0x0004,
.clr_base = 0x0004,
.strm_base = 0x02a4,
.err_mask = 0x7e000,
};
/*
* file handle translate information for v2
*/
static const u16 trans_tbl_av1_vcd[] = {
65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91,
93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115,
117, 133, 135, 137, 139, 141, 143, 145, 147,
167, 169, 171, 173, 175, 177, 179, 183, 190, 192, 194,
196, 198, 200, 202, 204, 224, 226, 228, 230, 232, 234,
236, 238, 326, 328, 339, 341, 348, 350, 505, 507
};
static const u16 trans_tbl_av1_cache[] = {
13, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88,
134, 135, 138, 139, 142, 143, 146, 147,
};
static const u16 trans_tbl_av1_afbc[] = {
32, 33, 34, 35, 48, 49, 50, 51, 96, 97, 98, 99
};
static struct mpp_trans_info trans_av1dec[] = {
[AV1DEC_TRANS_VCD] = {
.count = ARRAY_SIZE(trans_tbl_av1_vcd),
.table = trans_tbl_av1_vcd,
},
[AV1DEC_TRANS_CACHE] = {
.count = ARRAY_SIZE(trans_tbl_av1_cache),
.table = trans_tbl_av1_cache,
},
[AV1DEC_TRANS_AFBC] = {
.count = ARRAY_SIZE(trans_tbl_av1_afbc),
.table = trans_tbl_av1_afbc,
},
};
static bool req_over_class(struct mpp_request *req,
struct av1dec_task *task, int class)
{
bool ret;
u32 base_s, base_e, req_e;
struct av1dec_hw_info *hw = task->hw_info;
if (class > hw->reg_class_num)
return false;
base_s = hw->reg_class[class].base_s;
base_e = hw->reg_class[class].base_e;
req_e = req->offset + req->size - sizeof(u32);
ret = (req->offset <= base_e && req_e >= base_s) ? true : false;
return ret;
}
static int av1dec_alloc_reg_class(struct av1dec_task *task)
{
int i;
u32 data_size;
struct av1dec_hw_info *hw = task->hw_info;
data_size = 0;
for (i = 0; i < hw->reg_class_num; i++) {
u32 base_s = hw->reg_class[i].base_s;
u32 base_e = hw->reg_class[i].base_e;
task->reg_class[i].base = base_s;
task->reg_class[i].off = data_size;
task->reg_class[i].len = base_e - base_s + sizeof(u32);
data_size += task->reg_class[i].len;
}
task->reg_data = kzalloc(data_size, GFP_KERNEL);
if (!task->reg_data)
return -ENOMEM;
for (i = 0; i < hw->reg_class_num; i++)
task->reg_class[i].data = task->reg_data + (task->reg_class[i].off / sizeof(u32));
return 0;
}
static int av1dec_update_req(struct av1dec_task *task, int class,
struct mpp_request *req_in,
struct mpp_request *req_out)
{
u32 base_s, base_e, req_e, s, e;
struct av1dec_hw_info *hw = task->hw_info;
if (class > hw->reg_class_num)
return -EINVAL;
base_s = hw->reg_class[class].base_s;
base_e = hw->reg_class[class].base_e;
req_e = req_in->offset + req_in->size - sizeof(u32);
s = max(req_in->offset, base_s);
e = min(req_e, base_e);
req_out->offset = s;
req_out->size = e - s + sizeof(u32);
req_out->data = (u8 *)req_in->data + (s - req_in->offset);
mpp_debug(DEBUG_TASK_INFO, "req_out->offset=%08x, req_out->size=%d\n",
req_out->offset, req_out->size);
return 0;
}
static int av1dec_extract_task_msg(struct av1dec_task *task,
struct mpp_task_msgs *msgs)
{
int ret;
u32 i;
struct mpp_request *req;
struct av1dec_hw_info *hw = task->hw_info;
mpp_debug_enter();
mpp_debug(DEBUG_TASK_INFO, "req_cnt=%d, set_cnt=%d, poll_cnt=%d, reg_class=%d\n",
msgs->req_cnt, msgs->set_cnt, msgs->poll_cnt, hw->reg_class_num);
for (i = 0; i < msgs->req_cnt; i++) {
req = &msgs->reqs[i];
mpp_debug(DEBUG_TASK_INFO, "msg: cmd %08x, offset %08x, size %d\n",
req->cmd, req->offset, req->size);
if (!req->size)
continue;
switch (req->cmd) {
case MPP_CMD_SET_REG_WRITE: {
u32 class;
u32 base, *regs;
struct mpp_request *wreq;
for (class = 0; class < hw->reg_class_num; class++) {
if (!req_over_class(req, task, class))
continue;
mpp_debug(DEBUG_TASK_INFO, "found write_calss %d\n", class);
wreq = &task->w_reqs[task->w_req_cnt];
av1dec_update_req(task, class, req, wreq);
base = task->reg_class[class].base;
regs = (u32 *)task->reg_class[class].data;
regs += MPP_BASE_TO_IDX(req->offset - base);
if (copy_from_user(regs, wreq->data, wreq->size)) {
mpp_err("copy_from_user fail, offset %08x\n", wreq->offset);
ret = -EIO;
goto fail;
}
task->w_req_cnt++;
}
} break;
case MPP_CMD_SET_REG_READ: {
u32 class;
struct mpp_request *rreq;
for (class = 0; class < hw->reg_class_num; class++) {
if (!req_over_class(req, task, class))
continue;
mpp_debug(DEBUG_TASK_INFO, "found read_calss %d\n", class);
rreq = &task->r_reqs[task->r_req_cnt];
av1dec_update_req(task, class, req, rreq);
task->r_req_cnt++;
}
} break;
case MPP_CMD_SET_REG_ADDR_OFFSET: {
mpp_extract_reg_offset_info(&task->off_inf, req);
} break;
default:
break;
}
}
mpp_debug(DEBUG_TASK_INFO, "w_req_cnt=%d, r_req_cnt=%d\n",
task->w_req_cnt, task->r_req_cnt);
mpp_debug_leave();
return 0;
fail:
mpp_debug_leave();
return ret;
}
static void *av1dec_alloc_task(struct mpp_session *session,
struct mpp_task_msgs *msgs)
{
int ret;
u32 i, j;
struct mpp_task *mpp_task = NULL;
struct av1dec_task *task = NULL;
struct mpp_dev *mpp = session->mpp;
mpp_debug_enter();
task = kzalloc(sizeof(*task), GFP_KERNEL);
if (!task)
return NULL;
mpp_task = &task->mpp_task;
mpp_task_init(session, mpp_task);
mpp_task->hw_info = mpp->var->hw_info;
task->hw_info = to_av1dec_info(mpp_task->hw_info);
/* alloc reg data for task */
ret = av1dec_alloc_reg_class(task);
if (ret)
goto free_task;
mpp_task->reg = task->reg_class[0].data;
/* extract reqs for current task */
ret = av1dec_extract_task_msg(task, msgs);
if (ret)
goto free_reg_class;
/* process fd in register */
if (!(msgs->flags & MPP_FLAGS_REG_FD_NO_TRANS)) {
int cnt;
const u16 *tbl;
u32 offset;
struct av1dec_hw_info *hw = task->hw_info;
for (i = 0; i < task->w_req_cnt; i++) {
struct mpp_request *req = &task->w_reqs[i];
for (i = 0; i < hw->trans_class_num; i++) {
u32 class = hw->trans_class[i].class;
u32 fmt = hw->trans_class[i].trans_fmt;
u32 *reg = task->reg_class[class].data;
u32 base_idx = MPP_BASE_TO_IDX(task->reg_class[class].base);
if (!req_over_class(req, task, i))
continue;
mpp_debug(DEBUG_TASK_INFO, "class=%d, base_idx=%d\n",
class, base_idx);
if (!reg)
continue;
ret = mpp_translate_reg_address(session, mpp_task, fmt, reg, NULL);
if (ret)
goto fail;
cnt = mpp->var->trans_info[fmt].count;
tbl = mpp->var->trans_info[fmt].table;
for (j = 0; j < cnt; j++) {
offset = mpp_query_reg_offset_info(&task->off_inf,
tbl[j] + base_idx);
mpp_debug(DEBUG_IOMMU,
"reg[%d] + offset %d\n", tbl[j] + base_idx, offset);
reg[tbl[j]] += offset;
}
}
}
}
task->clk_mode = CLK_MODE_NORMAL;
mpp_debug_leave();
return mpp_task;
fail:
mpp_task_dump_mem_region(mpp, mpp_task);
mpp_task_dump_reg(mpp, mpp_task);
mpp_task_finalize(session, mpp_task);
free_reg_class:
kfree(task->reg_data);
free_task:
kfree(task);
return NULL;
}
#define AV1_PP_CONFIG_INDEX 321
#define AV1_PP_TILE_SIZE GENMASK_ULL(10, 9)
#define AV1_PP_TILE_16X16 BIT(10)
#define REG_CONTROL 0x20
#define REG_INTRENBL 0x34
#define REG_ACKNOWLEDGE 0x38
#define REG_FORMAT 0x100
#define REG_COMPRESSENABLE 0x340
#define REG_HEADERBASE 0x80
#define REG_PAYLOADBASE 0xC0
#define REG_INPUTBUFBASE 0x180
#define REG_INPUTBUFSTRIDE 0x200
#define REG_INPUTBUFSIZE 0x140
static int av1dec_set_afbc(struct av1dec_dev *dec, struct av1dec_task *task)
{
u32 *regs = (u32 *)task->reg_class[0].data;
u32 width = (regs[4] >> 19) * 8;
u32 height = ((regs[4] >> 6) & 0x1fff) * 8;
u32 pixel_width_y, pixel_width_c, pixel_width = 8;
u32 vir_top = (((regs[503]) >> 16) & 0xf);
u32 vir_left = (((regs[503]) >> 20) & 0xf);
u32 vir_bottom = (((regs[503]) >> 24) & 0xf);
u32 vir_right = (((regs[503]) >> 28) & 0xf);
u32 fbc_format = 0;
u32 fbc_stream_number = 0;
u32 fbc_comp_en[2] = {0, 0};
u32 pp_width_final[2] = {0, 0};
u32 pp_height_final[2] = {0, 0};
u32 pp_hdr_base[2] = {0, 0};
u32 pp_payload_base[2] = {0, 0};
u32 pp_input_base[2] = {0, 0};
u32 pp_input_stride[2] = {0, 0};
u32 bus_address;
u32 i = 0;
pixel_width_y = ((regs[8] >> 6) & 0x3) + 8;
pixel_width_c = ((regs[8] >> 4) & 0x3) + 8;
pixel_width = (pixel_width_y == 8 && pixel_width_c == 8) ? 8 : 10;
if ((regs[AV1_PP_CONFIG_INDEX] & AV1_PP_TILE_SIZE) == AV1_PP_TILE_16X16) {
u32 offset = MPP_ALIGN((vir_left + width + vir_right) *
(height + 28) / 16, 64);
bus_address = regs[505];
fbc_stream_number++;
if (pixel_width == 10)
fbc_format = 3;
else
fbc_format = 9;
fbc_comp_en[0] = 1;
fbc_comp_en[1] = 1;
pp_width_final[0] = pp_width_final[1] = vir_left + width + vir_right;
pp_height_final[0] = pp_height_final[1] = vir_top + height + vir_bottom;
if (pixel_width == 10)
pp_input_stride[0] = pp_input_stride[1] = 2 * pp_width_final[0];
else
pp_input_stride[0] = pp_input_stride[1] = pp_width_final[0];
pp_hdr_base[0] = pp_hdr_base[1] = bus_address;
pp_payload_base[0] = pp_payload_base[1] = bus_address + offset;
pp_input_base[0] = pp_input_base[1] = bus_address;
writel_relaxed((fbc_stream_number << 9),
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
writel_relaxed(0x1, dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INTRENBL);
for (i = 0; i < 2; i++) {
writel_relaxed(fbc_format,
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_FORMAT + i * 4);
writel_relaxed(fbc_comp_en[i], dec->reg_base[AV1DEC_CLASS_AFBC] +
REG_COMPRESSENABLE + i * 4);
/* hdr base */
writel_relaxed(pp_hdr_base[i],
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_HEADERBASE + i * 4);
/* payload */
writel_relaxed(pp_payload_base[i],
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_PAYLOADBASE + i * 4);
/* bufsize */
writel_relaxed(((pp_height_final[i] << 15) | pp_width_final[i]),
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INPUTBUFSIZE + i * 4);
/* buf */
writel_relaxed(pp_input_base[i],
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INPUTBUFBASE + i * 4);
/* stride */
writel_relaxed(pp_input_stride[i], dec->reg_base[AV1DEC_CLASS_AFBC] +
REG_INPUTBUFSTRIDE + i * 4);
}
/* wmb */
wmb();
writel(((fbc_stream_number << 9) | (1 << 7)),
dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL); /* update */
writel((fbc_stream_number << 9), dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
}
return 0;
}
static int av1dec_run(struct mpp_dev *mpp, struct mpp_task *mpp_task)
{
int i;
u32 en_val = 0;
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_hw_info *hw = dec->hw_info;
struct av1dec_task *task = to_av1dec_task(mpp_task);
u32 timing_en = mpp->srv->timing_en;
mpp_debug_enter();
mpp_iommu_flush_tlb(mpp->iommu_info);
av1dec_set_afbc(dec, task);
for (i = 0; i < task->w_req_cnt; i++) {
int class;
struct mpp_request *req = &task->w_reqs[i];
for (class = 0; class < hw->reg_class_num; class++) {
int j, s, e;
u32 base, *regs;
if (!req_over_class(req, task, class))
continue;
base = task->reg_class[class].base;
s = MPP_BASE_TO_IDX(req->offset - base);
e = s + req->size / sizeof(u32);
regs = (u32 *)task->reg_class[class].data;
mpp_debug(DEBUG_TASK_INFO, "found rd_class %d, base=%08x, s=%d, e=%d\n",
class, base, s, e);
for (j = s; j < e; j++) {
if (class == 0 && j == hw->hw.reg_en) {
en_val = regs[j];
continue;
}
writel_relaxed(regs[j], dec->reg_base[class] + j * sizeof(u32));
}
}
}
/* init current task */
mpp->cur_task = mpp_task;
mpp_task_run_begin(mpp_task, timing_en, MPP_WORK_TIMEOUT_DELAY);
/* Flush the register before the start the device */
wmb();
mpp_write(mpp, hw->en_base, en_val);
mpp_task_run_end(mpp_task, timing_en);
mpp_debug_leave();
return 0;
}
static int av1dec_vcd_irq(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_hw_info *hw = dec->hw_info;
mpp_debug_enter();
mpp->irq_status = mpp_read(mpp, hw->sta_base);
if (!mpp->irq_status)
return IRQ_NONE;
mpp_write(mpp, hw->clr_base, 0);
mpp_debug_leave();
return IRQ_WAKE_THREAD;
}
static int av1dec_isr(struct mpp_dev *mpp)
{
struct mpp_task *mpp_task = mpp->cur_task;
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_task *task = to_av1dec_task(mpp_task);
u32 *regs = (u32 *)task->reg_class[0].data;
mpp_debug_enter();
/* FIXME use a spin lock here */
if (!mpp_task) {
dev_err(mpp->dev, "no current task\n");
return IRQ_HANDLED;
}
mpp_time_diff(mpp_task);
mpp->cur_task = NULL;
/* clear l2 cache status */
writel_relaxed(0x0, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x020);
writel_relaxed(0x0, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x204);
/* multi id enable bit */
writel_relaxed(0x00000000, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x208);
if (((regs[321] >> 9) & 0x3) == 0x2) {
u32 ack_status = readl(dec->reg_base[AV1DEC_CLASS_AFBC] + REG_ACKNOWLEDGE);
if ((ack_status & 0x1) == 0x1) {
u32 ctl_val = readl(dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
ctl_val |= 1;
writel_relaxed(ctl_val, dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
}
}
task->irq_status = mpp->irq_status;
mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", task->irq_status);
if (task->irq_status & dec->hw_info->err_mask) {
atomic_inc(&mpp->reset_request);
/* dump register */
if (mpp_debug_unlikely(DEBUG_DUMP_ERR_REG)) {
mpp_debug(DEBUG_DUMP_ERR_REG, "irq_status: %08x\n",
task->irq_status);
mpp_task_dump_hw_reg(mpp);
}
}
mpp_task_finish(mpp_task->session, mpp_task);
mpp_debug_leave();
return IRQ_HANDLED;
}
static int av1dec_finish(struct mpp_dev *mpp,
struct mpp_task *mpp_task)
{
u32 i;
struct av1dec_task *task = to_av1dec_task(mpp_task);
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_hw_info *hw = dec->hw_info;
mpp_debug_enter();
for (i = 0; i < task->r_req_cnt; i++) {
int class;
struct mpp_request *req = &task->r_reqs[i];
for (class = 0; class < hw->reg_class_num; class++) {
int j, s, e;
u32 base, *regs;
if (!req_over_class(req, task, class))
continue;
base = task->reg_class[class].base;
s = MPP_BASE_TO_IDX(req->offset - base);
e = s + req->size / sizeof(u32);
regs = (u32 *)task->reg_class[class].data;
mpp_debug(DEBUG_TASK_INFO, "found rd_class %d, base=%08x, s=%d, e=%d\n",
class, base, s, e);
for (j = s; j < e; j++) {
/* revert hack for irq status */
if (class == 0 && j == MPP_BASE_TO_IDX(hw->sta_base)) {
regs[j] = task->irq_status;
continue;
}
regs[j] = readl_relaxed(dec->reg_base[class] + j * sizeof(u32));
}
}
}
mpp_debug_leave();
return 0;
}
static int av1dec_result(struct mpp_dev *mpp,
struct mpp_task *mpp_task,
struct mpp_task_msgs *msgs)
{
u32 i;
struct av1dec_task *task = to_av1dec_task(mpp_task);
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_hw_info *hw = dec->hw_info;
mpp_debug_enter();
for (i = 0; i < task->r_req_cnt; i++) {
int class;
struct mpp_request *req = &task->r_reqs[i];
for (class = 0; class < hw->reg_class_num; class++) {
u32 base, *regs;
if (!req_over_class(req, task, class))
continue;
base = task->reg_class[class].base;
regs = (u32 *)task->reg_class[class].data;
regs += MPP_BASE_TO_IDX(req->offset - base);
if (copy_to_user(req->data, regs, req->size)) {
mpp_err("copy_to_user reg fail\n");
return -EIO;
}
}
}
mpp_debug_leave();
return 0;
}
static int av1dec_free_task(struct mpp_session *session,
struct mpp_task *mpp_task)
{
struct av1dec_task *task = to_av1dec_task(mpp_task);
mpp_task_finalize(session, mpp_task);
kfree(task->reg_data);
kfree(task);
return 0;
}
#ifdef CONFIG_PROC_FS
static int av1dec_procfs_remove(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
if (dec->procfs) {
proc_remove(dec->procfs);
dec->procfs = NULL;
}
return 0;
}
static int av1dec_procfs_init(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
dec->procfs = proc_mkdir(mpp->dev->of_node->name, mpp->srv->procfs);
if (IS_ERR_OR_NULL(dec->procfs)) {
mpp_err("failed on open procfs\n");
dec->procfs = NULL;
return -EIO;
}
/* for common mpp_dev options */
mpp_procfs_create_common(dec->procfs, mpp);
/* for debug */
mpp_procfs_create_u32("aclk", 0644,
dec->procfs, &dec->aclk_info.debug_rate_hz);
mpp_procfs_create_u32("session_buffers", 0644,
dec->procfs, &mpp->session_max_buffers);
return 0;
}
#else
static inline int av1dec_procfs_remove(struct mpp_dev *mpp)
{
return 0;
}
static inline int av1dec_procfs_init(struct mpp_dev *mpp)
{
return 0;
}
#endif
static int av1dec_init(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
int ret = 0;
/* Get clock info from dtsi */
ret = mpp_get_clk_info(mpp, &dec->aclk_info, "aclk_vcodec");
if (ret)
mpp_err("failed on clk_get aclk_vcodec\n");
ret = mpp_get_clk_info(mpp, &dec->hclk_info, "hclk_vcodec");
if (ret)
mpp_err("failed on clk_get hclk_vcodec\n");
/* Get normal max workload from dtsi */
of_property_read_u32(mpp->dev->of_node,
"rockchip,default-max-load",
&dec->default_max_load);
/* Set default rates */
mpp_set_clk_info_rate_hz(&dec->aclk_info, CLK_MODE_DEFAULT, 300 * MHZ);
/* Get reset control from dtsi */
dec->rst_a = mpp_reset_control_get(mpp, RST_TYPE_A, "video_a");
if (!dec->rst_a)
mpp_err("No aclk reset resource define\n");
dec->rst_h = mpp_reset_control_get(mpp, RST_TYPE_H, "video_h");
if (!dec->rst_h)
mpp_err("No hclk reset resource define\n");
return 0;
}
static int av1dec_reset(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
mpp_debug_enter();
if (dec->rst_a && dec->rst_h) {
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_leave();
return 0;
}
static int av1dec_clk_on(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
mpp_clk_safe_enable(dec->aclk_info.clk);
mpp_clk_safe_enable(dec->hclk_info.clk);
return 0;
}
static int av1dec_clk_off(struct mpp_dev *mpp)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
clk_disable_unprepare(dec->aclk_info.clk);
clk_disable_unprepare(dec->hclk_info.clk);
return 0;
}
static int av1dec_set_freq(struct mpp_dev *mpp,
struct mpp_task *mpp_task)
{
struct av1dec_dev *dec = to_av1dec_dev(mpp);
struct av1dec_task *task = to_av1dec_task(mpp_task);
mpp_clk_set_rate(&dec->aclk_info, task->clk_mode);
return 0;
}
static struct mpp_hw_ops av1dec_hw_ops = {
.init = av1dec_init,
.clk_on = av1dec_clk_on,
.clk_off = av1dec_clk_off,
.set_freq = av1dec_set_freq,
.reset = av1dec_reset,
};
static struct mpp_dev_ops av1dec_dev_ops = {
.alloc_task = av1dec_alloc_task,
.run = av1dec_run,
.irq = av1dec_vcd_irq,
.isr = av1dec_isr,
.finish = av1dec_finish,
.result = av1dec_result,
.free_task = av1dec_free_task,
};
static const struct mpp_dev_var av1dec_data = {
.device_type = MPP_DEVICE_AV1DEC,
.hw_info = &av1dec_hw_info.hw,
.trans_info = trans_av1dec,
.hw_ops = &av1dec_hw_ops,
.dev_ops = &av1dec_dev_ops,
};
static const struct of_device_id mpp_av1dec_dt_match[] = {
{
.compatible = "rockchip,av1-decoder",
.data = &av1dec_data,
},
{},
};
static int av1dec_cache_init(struct platform_device *pdev, struct av1dec_dev *dec)
{
struct resource *res;
struct device *dev = &pdev->dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cache");
if (!res)
return -ENOMEM;
dec->reg_base[AV1DEC_CLASS_CACHE] = devm_ioremap(dev, res->start, resource_size(res));
if (!dec->reg_base[AV1DEC_CLASS_CACHE]) {
dev_err(dev, "ioremap failed for resource %pR\n", res);
return -EINVAL;
}
return 0;
}
static int av1dec_afbc_init(struct platform_device *pdev, struct av1dec_dev *dec)
{
struct resource *res;
struct device *dev = &pdev->dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "afbc");
if (!res)
return -ENOMEM;
dec->reg_base[AV1DEC_CLASS_AFBC] = devm_ioremap(dev, res->start, resource_size(res));
if (!dec->reg_base[AV1DEC_CLASS_AFBC]) {
dev_err(dev, "ioremap failed for resource %pR\n", res);
return -EINVAL;
}
dec->irq[AV1DEC_CLASS_AFBC] = platform_get_irq(pdev, 2);
return 0;
}
static int av1dec_probe(struct platform_device *pdev)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct av1dec_dev *dec = NULL;
struct mpp_dev *mpp = NULL;
const struct of_device_id *match = NULL;
dev_info(dev, "probing start\n");
dec = devm_kzalloc(dev, sizeof(*dec), GFP_KERNEL);
if (!dec)
return -ENOMEM;
mpp = &dec->mpp;
platform_set_drvdata(pdev, mpp);
if (pdev->dev.of_node) {
match = of_match_node(mpp_av1dec_dt_match, pdev->dev.of_node);
if (match)
mpp->var = (struct mpp_dev_var *)match->data;
}
/* get vcd resource */
ret = mpp_dev_probe(mpp, pdev);
if (ret)
return ret;
dec->reg_base[AV1DEC_CLASS_VCD] = mpp->reg_base;
ret = devm_request_threaded_irq(dev, mpp->irq,
mpp_dev_irq,
NULL,
IRQF_SHARED,
dev_name(dev), mpp);
if (ret) {
dev_err(dev, "register interrupter runtime failed\n");
goto failed_get_irq;
}
dec->irq[AV1DEC_CLASS_VCD] = mpp->irq;
/* get cache resource */
ret = av1dec_cache_init(pdev, dec);
if (ret)
goto failed_get_irq;
/* get afbc resource */
ret = av1dec_afbc_init(pdev, dec);
if (ret)
goto failed_get_irq;
mpp->session_max_buffers = AV1DEC_SESSION_MAX_BUFFERS;
dec->hw_info = to_av1dec_info(mpp->var->hw_info);
av1dec_procfs_init(mpp);
mpp_dev_register_srv(mpp, mpp->srv);
dev_info(dev, "probing finish\n");
return 0;
failed_get_irq:
mpp_dev_remove(mpp);
return ret;
}
static int av1dec_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mpp_dev *mpp = platform_get_drvdata(pdev);
dev_info(dev, "remove device\n");
mpp_dev_remove(mpp);
av1dec_procfs_remove(mpp);
return 0;
}
struct platform_driver rockchip_av1dec_driver = {
.probe = av1dec_probe,
.remove = av1dec_remove,
.shutdown = mpp_dev_shutdown,
.driver = {
.name = AV1DEC_DRIVER_NAME,
.of_match_table = of_match_ptr(mpp_av1dec_dt_match),
},
};