2232 lines
68 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Rockchip Electronics Co., Ltd. */
#include <linux/clk.h>
#include <linux/delay.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-v4l2.h>
#include <linux/pm_runtime.h>
#include <linux/rk-vpss-config.h>
#include <linux/rk-video-format.h>
#include "dev.h"
#include "hw.h"
#include "regs.h"
struct rkvpss_output_ch {
u32 ctrl;
u32 size;
u32 c_offs;
};
struct rkvpss_offline_buf {
struct list_head list;
struct vb2_buffer vb;
struct vb2_queue vb2_queue;
struct file *file;
struct dma_buf *dbuf;
void *mem;
int dev_id;
int fd;
bool alloc;
};
static void init_vb2(struct rkvpss_offline_dev *ofl,
struct rkvpss_offline_buf *buf)
{
struct rkvpss_hw_dev *hw = ofl->hw;
unsigned long attrs = DMA_ATTR_NO_KERNEL_MAPPING;
if (!buf)
return;
memset(&buf->vb, 0, sizeof(buf->vb));
memset(&buf->vb2_queue, 0, sizeof(buf->vb2_queue));
buf->vb2_queue.gfp_flags = GFP_KERNEL | GFP_DMA32;
buf->vb2_queue.dma_dir = DMA_BIDIRECTIONAL;
if (hw->is_dma_contig)
attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
buf->vb2_queue.dma_attrs = attrs;
buf->vb.vb2_queue = &buf->vb2_queue;
}
static void buf_del(struct file *file, int id, int fd, bool is_all, bool running)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkvpss_offline_buf *buf, *next;
mutex_lock(&hw->dev_lock);
list_for_each_entry_safe(buf, next, &ofl->list, list) {
if (buf->file == file && (is_all || buf->fd == fd)) {
if (!is_all && running && buf->alloc)
break;
v4l2_dbg(1, rkvpss_debug, &ofl->v4l2_dev,
"%s file:%p dev_id:%d fd:%d dbuf:%p\n",
__func__, file, buf->dev_id, buf->fd, buf->dbuf);
if (!buf->alloc) {
ops->unmap_dmabuf(buf->mem);
ops->detach_dmabuf(buf->mem);
dma_buf_put(buf->dbuf);
} else {
dma_buf_put(buf->dbuf);
ops->put(buf->mem);
}
buf->file = NULL;
buf->mem = NULL;
buf->dbuf = NULL;
buf->fd = -1;
list_del(&buf->list);
kfree(buf);
if (!is_all)
break;
}
}
mutex_unlock(&hw->dev_lock);
}
static struct rkvpss_offline_buf *buf_add(struct file *file, int id, int fd, int size)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkvpss_offline_buf *buf = NULL, *next = NULL;
struct dma_buf *dbuf;
void *mem = NULL;
bool is_add = true;
dbuf = dma_buf_get(fd);
if (IS_ERR_OR_NULL(dbuf)) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d invalid dmabuf fd:%d", id, fd);
return buf;
}
if (size && dbuf->size < size) {
v4l2_err(&ofl->v4l2_dev,
"dev_id:%d input fd:%d size error:%zu < %u\n",
id, fd, dbuf->size, size);
dma_buf_put(dbuf);
return buf;
}
mutex_lock(&hw->dev_lock);
list_for_each_entry_safe(buf, next, &ofl->list, list) {
if (buf->file == file && buf->fd == fd && buf->dbuf == dbuf) {
is_add = false;
break;
}
}
if (is_add) {
buf = kzalloc(sizeof(struct rkvpss_offline_buf), GFP_KERNEL);
if (!buf)
goto end;
init_vb2(ofl, buf);
mem = ops->attach_dmabuf(&buf->vb, hw->dev, dbuf, dbuf->size);
if (IS_ERR(mem)) {
v4l2_err(&ofl->v4l2_dev, "failed to attach dmabuf, fd:%d\n", fd);
dma_buf_put(dbuf);
kfree(buf);
buf = NULL;
goto end;
}
if (ops->map_dmabuf(mem)) {
v4l2_err(&ofl->v4l2_dev, "failed to map, fd:%d\n", fd);
ops->detach_dmabuf(mem);
dma_buf_put(dbuf);
mem = NULL;
kfree(buf);
buf = NULL;
goto end;
}
buf->dev_id = id;
buf->fd = fd;
buf->file = file;
buf->dbuf = dbuf;
buf->mem = mem;
buf->alloc = false;
list_add_tail(&buf->list, &ofl->list);
v4l2_dbg(1, rkvpss_debug, &ofl->v4l2_dev,
"%s file:%p dev_id:%d fd:%d dbuf:%p size:%d\n", __func__,
file, id, fd, dbuf, size);
} else {
dma_buf_put(dbuf);
}
end:
mutex_unlock(&hw->dev_lock);
return buf;
}
static int internal_buf_alloc(struct file *file, struct rkvpss_buf_info *info)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkvpss_offline_buf *buf;
struct dma_buf *dbuf;
int fd, i, size;
void *mem;
for (i = 0; i < info->buf_cnt; i++) {
info->buf_fd[i] = -1;
size = PAGE_ALIGN(info->buf_size[i]);
if (!size)
continue;
buf = kzalloc(sizeof(struct rkvpss_offline_buf), GFP_KERNEL);
if (!buf)
goto err;
init_vb2(ofl, buf);
mem = ops->alloc(&buf->vb, hw->dev, size);
if (IS_ERR_OR_NULL(mem)) {
kfree(buf);
goto err;
}
dbuf = ops->get_dmabuf(&buf->vb, mem, O_RDWR);
if (IS_ERR_OR_NULL(dbuf)) {
ops->put(mem);
kfree(buf);
goto err;
}
fd = dma_buf_fd(dbuf, O_CLOEXEC);
if (fd < 0) {
dma_buf_put(dbuf);
ops->put(mem);
kfree(buf);
goto err;
}
get_dma_buf(dbuf);
info->buf_fd[i] = fd;
buf->fd = fd;
buf->file = file;
buf->dbuf = dbuf;
buf->mem = mem;
buf->alloc = true;
buf->dev_id = info->dev_id;
ops->prepare(buf->mem);
mutex_lock(&hw->dev_lock);
list_add_tail(&buf->list, &ofl->list);
mutex_unlock(&hw->dev_lock);
v4l2_dbg(1, rkvpss_debug, &ofl->v4l2_dev,
"%s file:%p dev_id:%d fd:%d dbuf:%p\n",
__func__, file, buf->dev_id, fd, dbuf);
}
return 0;
err:
for (i -= 1; i >= 0; i--)
buf_del(file, info->dev_id, info->buf_fd[i], false, false);
return -ENOMEM;
}
static int external_buf_add(struct file *file, struct rkvpss_buf_info *info)
{
void *mem;
int i;
for (i = 0; i < info->buf_cnt; i++) {
mem = buf_add(file, info->dev_id, info->buf_fd[i], info->buf_size[i]);
if (!mem)
goto err;
}
return 0;
err:
for (i -= 1; i >= 0; i--)
buf_del(file, info->dev_id, info->buf_fd[i], false, false);
return -ENOMEM;
}
static int rkvpss_ofl_buf_add(struct file *file, struct rkvpss_buf_info *info)
{
int ret;
if (info->buf_alloc)
ret = internal_buf_alloc(file, info);
else
ret = external_buf_add(file, info);
return ret;
}
static void rkvpss_ofl_buf_del(struct file *file, struct rkvpss_buf_info *info)
{
int i;
for (i = 0; i < info->buf_cnt; i++)
buf_del(file, info->dev_id, info->buf_fd[i], false, false);
}
static void poly_phase_scale(struct rkvpss_frame_cfg *frame_cfg,
struct rkvpss_offline_dev *ofl,
struct rkvpss_output_cfg *cfg, bool unite, bool left)
{
struct rkvpss_hw_dev *hw = ofl->hw;
u32 in_w = cfg->crop_width, in_h = cfg->crop_height;
u32 out_w = cfg->scl_width, out_h = cfg->scl_height;
u32 ctrl, y_xscl_fac, y_yscl_fac, uv_xscl_fac, uv_yscl_fac;
u32 i, j, idx, ratio, val, in_div, out_div, factor;
bool dering_en = false, yuv420_in = false, yuv422_to_420 = false;
if (in_w == out_w && in_h == out_w) {
rkvpss_hw_write(hw, RKVPSS_ZME_Y_SCL_CTRL, 0);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_SCL_CTRL, 0);
rkvpss_hw_clear_bits(hw, RKVPSS_VPSS_CLK_GATE, RKVPSS_SCL0_CKG_DIS);
goto end;
} else {
rkvpss_hw_set_bits(hw, RKVPSS_VPSS_CLK_GATE, RKVPSS_SCL0_CKG_DIS,
RKVPSS_SCL0_CKG_DIS);
}
/* TODO diff for input and output format */
if (yuv420_in) {
in_div = 2;
out_div = 2;
} else if (yuv422_to_420) {
in_div = 1;
out_div = 2;
} else {
in_div = 1;
out_div = 1;
}
if (unite) {
if (left) {
if (in_w == out_w)
val = (cfg->crop_width / 2 - 1) |
((cfg->crop_height - 1) << 16);
else
val = (cfg->crop_width / 2 + UNITE_ENLARGE - 1) |
((cfg->crop_height - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_SRC_SIZE, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_SRC_SIZE, val);
} else {
val = (ALIGN(ofl->unite_params[0].right_scl_need_size_y + 3, 2) - 1) |
((cfg->crop_height - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_SRC_SIZE, val);
val = (ALIGN(ofl->unite_params[0].right_scl_need_size_c + 6, 2) - 1) |
((cfg->crop_height - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_SRC_SIZE, val);
}
val = (cfg->scl_width / 2 - 1) | ((cfg->scl_height - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_DST_SIZE, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_DST_SIZE, val);
} else {
val = (in_w - 1) | ((in_h - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_SRC_SIZE, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_SRC_SIZE, val);
val = (out_w - 1) | ((out_h - 1) << 16);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_DST_SIZE, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_DST_SIZE, val);
}
ctrl = RKVPSS_ZME_XSCL_MODE | RKVPSS_ZME_YSCL_MODE;
if (dering_en) {
ctrl |= RKVPSS_ZME_DERING_EN;
rkvpss_hw_write(hw, RKVPSS_ZME_Y_DERING_PARA, 0xd10410);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_DERING_PARA, 0xd10410);
}
if (in_w != out_w) {
if (in_w > out_w) {
factor = 4096;
ctrl |= RKVPSS_ZME_XSD_EN;
} else {
factor = 65536;
ctrl |= RKVPSS_ZME_XSU_EN;
}
y_xscl_fac = (in_w - 1) * factor / (out_w - 1);
uv_xscl_fac = (in_w / 2 - 1) * factor / (out_w / 2 - 1);
ratio = y_xscl_fac * 10000 / factor;
idx = rkvpss_get_zme_tap_coe_index(ratio);
for (i = 0; i < 17; i++) {
for (j = 0; j < 8; j += 2) {
val = RKVPSS_ZME_TAP_COE(rkvpss_zme_tap8_coe[idx][i][j],
rkvpss_zme_tap8_coe[idx][i][j + 1]);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_HOR_COE0_10 + i * 16 + j * 2, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_HOR_COE0_10 + i * 16 + j * 2, val);
}
}
} else {
y_xscl_fac = 0;
uv_xscl_fac = 0;
}
if (unite && !left) {
rkvpss_hw_write(hw, RKVPSS_ZME_Y_XSCL_FACTOR, y_xscl_fac |
(ofl->unite_params[0].y_w_phase << 16));
rkvpss_hw_write(hw, RKVPSS_ZME_UV_XSCL_FACTOR, uv_xscl_fac |
(ofl->unite_params[0].c_w_phase << 16));
} else {
rkvpss_hw_write(hw, RKVPSS_ZME_Y_XSCL_FACTOR, y_xscl_fac);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_XSCL_FACTOR, uv_xscl_fac);
}
if (in_h != out_h) {
if (in_h > out_h) {
factor = 4096;
ctrl |= RKVPSS_ZME_YSD_EN;
} else {
factor = 65536;
ctrl |= RKVPSS_ZME_YSU_EN;
}
y_yscl_fac = (in_h - 1) * factor / (out_h - 1);
uv_yscl_fac = (in_h / in_div - 1) * factor / (out_h / out_div - 1);
ratio = y_yscl_fac * 10000 / factor;
idx = rkvpss_get_zme_tap_coe_index(ratio);
for (i = 0; i < 17; i++) {
for (j = 0; j < 8; j += 2) {
val = RKVPSS_ZME_TAP_COE(rkvpss_zme_tap6_coe[idx][i][j],
rkvpss_zme_tap6_coe[idx][i][j + 1]);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_VER_COE0_10 + i * 16 + j * 2, val);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_VER_COE0_10 + i * 16 + j * 2, val);
}
}
} else {
y_yscl_fac = 0;
uv_yscl_fac = 0;
}
rkvpss_hw_write(hw, RKVPSS_ZME_Y_YSCL_FACTOR, y_yscl_fac);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_YSCL_FACTOR, uv_yscl_fac);
rkvpss_hw_write(hw, RKVPSS_ZME_Y_SCL_CTRL, ctrl);
rkvpss_hw_write(hw, RKVPSS_ZME_UV_SCL_CTRL, ctrl);
if (unite) {
val = cfg->scl_width / 2;
rkvpss_hw_write(hw, RKVPSS_ZME_H_SIZE, (val << 16) | val);
rkvpss_hw_write(hw, RKVPSS_ZME_H_OFFS, 0);
if (!left) {
val = cfg->scl_width / 2 - ALIGN_DOWN(cfg->scl_width / 2, 16);
rkvpss_hw_write(hw, RKVPSS_ZME_H_OFFS, (3 << 20) | (3 << 16) |
(ofl->unite_params[0].scl_in_crop_w_y << 8) |
(ofl->unite_params[0].scl_in_crop_w_c << 12) |
(val << 4) | val);
}
}
ctrl = RKVPSS_ZME_GATING_EN;
if (yuv420_in)
ctrl |= RKVPSS_ZME_SCL_YUV420_REAL_EN;
if (yuv422_to_420)
ctrl |= RKVPSS_ZME_422TO420_EN;
if (unite) {
if (left)
ctrl |= RKVPSS_ZME_CLIP_EN | RKVPSS_ZME_8K_EN;
else
ctrl |= RKVPSS_ZME_CLIP_EN | RKVPSS_ZME_IN_CLIP_EN | RKVPSS_ZME_8K_EN;
}
rkvpss_hw_write(hw, RKVPSS_ZME_CTRL, ctrl);
end:
val = RKVPSS_ZME_GEN_UPD | RKVPSS_ZME_FORCE_UPD;
rkvpss_hw_write(hw, RKVPSS_ZME_UPDATE, val);
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s unite:%d left:%d hw ctrl:0x%x y_src_size:0x%x y_dst_size:0x%x\n",
__func__, unite, left,
rkvpss_hw_read(hw, RKVPSS_ZME_CTRL),
rkvpss_hw_read(hw, RKVPSS_ZME_Y_SRC_SIZE),
rkvpss_hw_read(hw, RKVPSS_ZME_Y_DST_SIZE));
}
static void bilinear_scale(struct rkvpss_frame_cfg *frame_cfg,
struct rkvpss_offline_dev *ofl,
struct rkvpss_output_cfg *cfg, int idx, bool unite, bool left)
{
struct rkvpss_hw_dev *hw = ofl->hw;
u32 in_w = cfg->crop_width, in_h = cfg->crop_height;
u32 out_w = cfg->scl_width, out_h = cfg->scl_height;
u32 reg_base, in_div, out_div, val, ctrl = 0, clk_mask = 0;
bool yuv420_in = false, yuv422_to_420 = false;
switch (idx) {
case RKVPSS_OUTPUT_CH1:
reg_base = RKVPSS_SCALE1_BASE;
clk_mask = RKVPSS_SCL1_CKG_DIS;
break;
case RKVPSS_OUTPUT_CH2:
reg_base = RKVPSS_SCALE2_BASE;
clk_mask = RKVPSS_SCL2_CKG_DIS;
break;
case RKVPSS_OUTPUT_CH3:
reg_base = RKVPSS_SCALE3_BASE;
clk_mask = RKVPSS_SCL3_CKG_DIS;
break;
default:
return;
}
/*config scl clk gate*/
if (in_w == out_w && in_h == out_h)
rkvpss_hw_clear_bits(hw, RKVPSS_VPSS_CLK_GATE, clk_mask);
else
rkvpss_hw_set_bits(hw, RKVPSS_VPSS_CLK_GATE, clk_mask, clk_mask);
if (!unite) {
if (in_w == out_w && in_h == out_w)
goto end;
/* TODO diff for input and output format */
if (yuv420_in) {
in_div = 2;
out_div = 2;
} else if (yuv422_to_420) {
in_div = 1;
out_div = 2;
} else {
in_div = 1;
out_div = 1;
}
val = in_w | (in_h << 16);
rkvpss_hw_write(hw, reg_base + 0x8, val);
val = out_w | (out_h << 16);
rkvpss_hw_write(hw, reg_base + 0xc, val);
if (in_w != out_w) {
val = (in_w - 1) * 4096 / (out_w - 1);
rkvpss_hw_write(hw, reg_base + 0x10, val);
val = (in_w / 2 - 1) * 4096 / (out_w / 2 - 1);
rkvpss_hw_write(hw, reg_base + 0x14, val);
ctrl |= RKVPSS_SCL_HY_EN | RKVPSS_SCL_HC_EN;
}
if (in_h != out_h) {
val = (in_h - 1) * 4096 / (out_h - 1);
rkvpss_hw_write(hw, reg_base + 0x18, val);
val = (in_h / in_div - 1) * 4096 / (out_h / out_div - 1);
rkvpss_hw_write(hw, reg_base + 0x1c, val);
ctrl |= RKVPSS_SCL_VY_EN | RKVPSS_SCL_VC_EN;
}
} else {
if (left) {
rkvpss_hw_write(hw, reg_base + 0x50, 0);
rkvpss_hw_write(hw, reg_base + 0x20, 0);
rkvpss_hw_write(hw, reg_base + 0x24, 0);
rkvpss_hw_write(hw, reg_base + 0x48, 0);
rkvpss_hw_write(hw, reg_base + 0x4c, 0);
if (in_w == out_w)
val = (cfg->crop_width / 2) | (cfg->crop_height << 16);
else
val = (cfg->crop_width / 2 + UNITE_ENLARGE) |
(cfg->crop_height << 16);
rkvpss_hw_write(hw, reg_base + 0x8, val);
val = cfg->scl_width / 2 | (cfg->scl_height << 16);
rkvpss_hw_write(hw, reg_base + 0xc, val);
ctrl |= RKVPSS_SCL_CLIP_EN;
} else {
val = ofl->unite_params[idx].scl_in_crop_w_y |
(ofl->unite_params[idx].scl_in_crop_w_c << 4);
rkvpss_hw_write(hw, reg_base + 0x50, val);
rkvpss_hw_write(hw, reg_base + 0x20, ofl->unite_params[idx].y_w_phase);
rkvpss_hw_write(hw, reg_base + 0x24, ofl->unite_params[idx].c_w_phase);
val = cfg->scl_width / 2 - ALIGN_DOWN(cfg->scl_width / 2, 16);
rkvpss_hw_write(hw, reg_base + 0x48, val);
rkvpss_hw_write(hw, reg_base + 0x4c, val);
val = (cfg->crop_width / 2 + ofl->unite_right_enlarge) |
(cfg->crop_height << 16);
rkvpss_hw_write(hw, reg_base + 0x8, val);
val = cfg->scl_width / 2 | (cfg->scl_height << 16);
rkvpss_hw_write(hw, reg_base + 0xc, val);
ctrl |= RKVPSS_SCL_CLIP_EN | RKVPSS_SCL_IN_CLIP_EN;
}
if (cfg->scl_width != frame_cfg->input.width) {
rkvpss_hw_write(hw, reg_base + 0x10, ofl->unite_params[idx].y_w_fac);
rkvpss_hw_write(hw, reg_base + 0x14, ofl->unite_params[idx].c_w_fac);
ctrl |= RKVPSS_SCL_HY_EN | RKVPSS_SCL_HC_EN;
}
if (cfg->scl_height != frame_cfg->input.height) {
rkvpss_hw_write(hw, reg_base + 0x18, ofl->unite_params[idx].y_h_fac);
rkvpss_hw_write(hw, reg_base + 0x1c, ofl->unite_params[idx].c_h_fac);
ctrl |= RKVPSS_SCL_VY_EN | RKVPSS_SCL_VC_EN;
}
}
end:
rkvpss_hw_write(hw, reg_base, ctrl);
val = RKVPSS_SCL_GEN_UPD | RKVPSS_SCL_FORCE_UPD;
rkvpss_hw_write(hw, reg_base + 0x4, val);
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s unite:%d left:%d hw ch:%d ctrl:0x%x y_src_size:0x%x y_dst_size:0x%x\n",
__func__, unite, left, idx,
rkvpss_hw_read(hw, reg_base),
rkvpss_hw_read(hw, reg_base + 0x8),
rkvpss_hw_read(hw, reg_base + 0xc));
}
static void scale_config(struct file *file,
struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
int i;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
if (i == RKVPSS_OUTPUT_CH0)
poly_phase_scale(cfg, ofl, &cfg->output[i], unite, left);
else
bilinear_scale(cfg, ofl, &cfg->output[i], i, unite, left);
}
}
static int cmsc_config(struct rkvpss_offline_dev *ofl,
struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_hw_dev *hw = ofl->hw;
struct rkvpss_cmsc_cfg *cmsc_cfg, tmp_cfg = {0};
struct rkvpss_cmsc_win *win;
struct rkvpss_cmsc_point *point;
int i, j, k;
u32 ch_win_en[RKVPSS_OUTPUT_MAX];
u32 ch_win_mode[RKVPSS_OUTPUT_MAX];
u32 win_color[RKVPSS_CMSC_WIN_MAX];
u32 val, slope, hor, mask, mosaic_block = 0, ctrl = 0;
u32 hw_in_w, hw_in_h;
if (!hw->is_ofl_cmsc)
return 0;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
ch_win_en[i] = 0;
ch_win_mode[i] = 0;
cmsc_cfg = &cfg->output[i].cmsc;
for (j = 0; j < RKVPSS_CMSC_WIN_MAX; j++) {
if (i == 0)
win_color[j] = 0;
if (!cmsc_cfg->win[j].win_en)
continue;
ch_win_en[i] |= BIT(j);
ch_win_mode[i] |= cmsc_cfg->win[j].mode ? BIT(j) : 0;
/** mosaic_block use the last channel **/
if (cmsc_cfg->win[j].mode)
mosaic_block = cfg->output[i].cmsc.mosaic_block;
/** window cover all channel consistent **/
if (!cfg->output[i].cmsc.win[j].mode) {
win_color[j] = RKVPSS_CMSK_WIN_YUV(cfg->output[i].cmsc.win[j].cover_color_y,
cfg->output[i].cmsc.win[j].cover_color_u,
cfg->output[i].cmsc.win[j].cover_color_v);
if (cfg->output[i].cmsc.win[j].cover_color_a > 15)
cfg->output[i].cmsc.win[j].cover_color_a = 15;
win_color[j] |= RKVPSS_CMSC_WIN_ALPHA(cfg->output[i].cmsc.win[j].cover_color_a);
}
for (k = 0; k < RKVPSS_CMSC_POINT_MAX; k++)
tmp_cfg.win[j].point[k] = cmsc_cfg->win[j].point[k];
}
}
/* deal unite left params */
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!unite || !left)
break;
if (!cfg->output[i].enable)
continue;
for (j = 0; j < RKVPSS_CMSC_WIN_MAX; j++) {
win = &tmp_cfg.win[j];
if (!(ch_win_en[i] & BIT(j)))
continue;
mask = 0;
for (k = 0; k < RKVPSS_CMSC_POINT_MAX; k++) {
point = &win->point[k];
if (point->x >= cfg->input.width / 2)
mask |= BIT(k);
else
mask &= ~BIT(k);
}
if (mask == 0xf) {
/** all right **/
ch_win_en[i] &= ~BIT(j);
} else if (mask != 0) {
/** middle need avoid pentagon **/
if (win->point[0].x != win->point[3].x ||
win->point[1].x != win->point[2].x) {
ch_win_en[i] &= ~BIT(j);
} else {
win->point[1].x = cfg->input.width / 2;
win->point[2].x = cfg->input.width / 2;
}
}
}
}
/* deal unite right params */
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!unite || left)
break;
if (!cfg->output[i].enable)
continue;
for (j = 0; j < RKVPSS_CMSC_WIN_MAX; j++) {
win = &tmp_cfg.win[j];
if (!(ch_win_en[i] & BIT(j)))
continue;
mask = 0;
for (k = 0; k < RKVPSS_CMSC_POINT_MAX; k++) {
point = &win->point[k];
if (point->x <= cfg->input.width / 2)
mask |= BIT(k);
else
mask &= ~BIT(k);
}
if (mask == 0xf) {
/** all left **/
ch_win_en[i] &= ~BIT(j);
} else if (mask != 0) {
/** middle need avoid pentagon **/
if (win->point[0].x != win->point[3].x ||
win->point[1].x != win->point[2].x) {
ch_win_en[i] &= ~BIT(j);
} else {
win->point[0].x = ofl->unite_right_enlarge;
win->point[3].x = ofl->unite_right_enlarge;
win->point[1].x = win->point[1].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
win->point[2].x = win->point[2].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
}
} else {
/** all right **/
win->point[0].x = win->point[0].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
win->point[1].x = win->point[1].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
win->point[2].x = win->point[2].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
win->point[3].x = win->point[3].x -
(cfg->input.width / 2) +
ofl->unite_right_enlarge;
}
}
}
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
if (ch_win_en[i]) {
ctrl |= RKVPSS_CMSC_EN;
ctrl |= RKVPSS_CMSC_CHN_EN(i);
}
rkvpss_hw_write(hw, RKVPSS_CMSC_CHN0_WIN + i * 4, ch_win_en[i]);
rkvpss_hw_write(hw, RKVPSS_CMSC_CHN0_MODE + i * 4, ch_win_mode[i]);
hw_in_w = rkvpss_hw_read(hw, RKVPSS_MI_RD_Y_WIDTH);
hw_in_h = rkvpss_hw_read(hw, RKVPSS_MI_RD_Y_HEIGHT);
for (j = 0; j < RKVPSS_CMSC_WIN_MAX; j++) {
if (!(ch_win_en[i] & BIT(j)))
continue;
for (k = 0; k < RKVPSS_CMSC_POINT_MAX; k++) {
if (tmp_cfg.win[j].point[k].x > hw_in_w ||
tmp_cfg.win[j].point[k].y > hw_in_h) {
v4l2_err(&ofl->v4l2_dev,
"%s cmsc coordinate error dev_id:%d unite:%u left:%u ch:%d win:%d point:%d x:%u y:%u hw_in_w:%u hw_in_h:%u\n",
__func__, cfg->dev_id, unite, left,
i, j, k,
tmp_cfg.win[j].point[k].x,
tmp_cfg.win[j].point[k].y,
hw_in_w, hw_in_w);
return -EINVAL;
}
val = RKVPSS_CMSC_WIN_VTX(tmp_cfg.win[j].point[k].x,
tmp_cfg.win[j].point[k].y);
rkvpss_hw_write(hw, RKVPSS_CMSC_WIN0_L0_VTX + k * 8 + j * 32, val);
rkvpss_cmsc_slop(&tmp_cfg.win[j].point[k],
(k + 1 == RKVPSS_CMSC_POINT_MAX) ?
&tmp_cfg.win[j].point[0] : &tmp_cfg.win[j].point[k + 1],
&slope, &hor);
val = RKVPSS_CMSC_WIN_SLP(slope, hor);
rkvpss_hw_write(hw, RKVPSS_CMSC_WIN0_L0_SLP + k * 8 + j * 32, val);
v4l2_dbg(4, rkvpss_debug, &ofl->v4l2_dev,
"%s dev_id:%d, unite:%d left:%d ch:%d win:%d point:%d x:%u y:%u",
__func__, cfg->dev_id, unite, left, i, j, k,
tmp_cfg.win[j].point[k].x,
tmp_cfg.win[j].point[k].y);
}
if ((ch_win_mode[i] & BIT(j)))
continue;
rkvpss_hw_write(hw, RKVPSS_CMSC_WIN0_PARA + j * 4, win_color[j]);
}
}
ctrl |= RKVPSS_CMSC_BLK_SZIE(mosaic_block);
rkvpss_hw_write(hw, RKVPSS_CMSC_CTRL, ctrl);
val = RKVPSS_CMSC_GEN_UPD | RKVPSS_CMSC_FORCE_UPD;
rkvpss_hw_write(hw, RKVPSS_CMSC_UPDATE, val);
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s dev_id:%d, unite:%d left:%d hw ctrl:0x%x update_val:0x%x",
__func__, cfg->dev_id, unite, left, ctrl, val);
return 0;
}
static void aspt_config(struct file *file,
struct rkvpss_frame_cfg *cfg)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
u32 reg_base, val;
int i;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
switch (i) {
case RKVPSS_OUTPUT_CH0:
reg_base = RKVPSS_RATIO0_BASE;
break;
case RKVPSS_OUTPUT_CH1:
reg_base = RKVPSS_RATIO1_BASE;
break;
case RKVPSS_OUTPUT_CH2:
reg_base = RKVPSS_RATIO2_BASE;
break;
case RKVPSS_OUTPUT_CH3:
reg_base = RKVPSS_RATIO3_BASE;
break;
default:
return;
}
if (!cfg->output[i].aspt.enable) {
rkvpss_hw_write(hw, reg_base, 0);
val = RKVPSS_RATIO_FORCE_UPD | RKVPSS_RATIO_GEN_UPD;
rkvpss_hw_write(hw, reg_base + 0x4, val);
continue;
}
val = cfg->output[i].scl_width | (cfg->output[i].scl_height << 16);
rkvpss_hw_write(hw, reg_base + 0x10, val);
val = cfg->output[i].aspt.width | (cfg->output[i].aspt.height << 16);
rkvpss_hw_write(hw, reg_base + 0x14, val);
val = cfg->output[i].aspt.h_offs | (cfg->output[i].aspt.v_offs << 16);
rkvpss_hw_write(hw, reg_base + 0x18, val);
val = cfg->output[i].aspt.color_y |
(cfg->output[i].aspt.color_u << 16) |
(cfg->output[i].aspt.color_v << 24);
rkvpss_hw_write(hw, reg_base + 0x1c, val);
rkvpss_hw_write(hw, reg_base, RKVPSS_RATIO_EN);
val = RKVPSS_RATIO_FORCE_UPD | RKVPSS_RATIO_GEN_UPD;
rkvpss_hw_write(hw, reg_base + 0x4, val);
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s hw ch:%d ctrl:0x%x in_size:0x%x out_size:0x%x offset:0x%x\n",
__func__, i,
rkvpss_hw_read(hw, reg_base),
rkvpss_hw_read(hw, reg_base + 0x10),
rkvpss_hw_read(hw, reg_base + 0x14),
rkvpss_hw_read(hw, reg_base + 0x18));
}
}
static void add_cfginfo(struct rkvpss_offline_dev *ofl, struct rkvpss_frame_cfg *cfg)
{
struct rkvpss_ofl_cfginfo *cfginfo = NULL, *new_cfg = NULL, *first_cfg = NULL;
int i, count = 0;
new_cfg = kzalloc(sizeof(struct rkvpss_ofl_cfginfo), GFP_KERNEL);
new_cfg->dev_id = cfg->dev_id;
new_cfg->sequence = cfg->sequence;
new_cfg->input.buf_fd = cfg->input.buf_fd;
new_cfg->input.format = cfg->input.format;
new_cfg->input.width = cfg->input.width;
new_cfg->input.height = cfg->input.height;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
new_cfg->output[i].enable = cfg->output[i].enable;
new_cfg->output[i].buf_fd = cfg->output[i].buf_fd;
new_cfg->output[i].format = cfg->output[i].format;
new_cfg->output[i].crop_v_offs = cfg->output[i].crop_v_offs;
new_cfg->output[i].crop_h_offs = cfg->output[i].crop_h_offs;
new_cfg->output[i].crop_width = cfg->output[i].crop_width;
new_cfg->output[i].crop_height = cfg->output[i].crop_height;
new_cfg->output[i].scl_width = cfg->output[i].scl_width;
new_cfg->output[i].scl_height = cfg->output[i].scl_height;
}
mutex_lock(&ofl->ofl_lock);
list_for_each_entry(cfginfo, &ofl->cfginfo_list, list) {
count++;
}
while (count >= rkvpss_cfginfo_num && count != 0) {
first_cfg = list_first_entry(&ofl->cfginfo_list, struct rkvpss_ofl_cfginfo, list);
list_del_init(&first_cfg->list);
kfree(first_cfg);
count--;
}
if (rkvpss_cfginfo_num)
list_add_tail(&new_cfg->list, &ofl->cfginfo_list);
else
kfree(new_cfg);
mutex_unlock(&ofl->ofl_lock);
}
static int read_config(struct file *file, struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *mem_ops = hw->mem_ops;
struct sg_table *sg_tbl;
struct rkvpss_offline_buf *buf;
u32 in_ctrl, in_size, in_c_offs, unite_r_offs, val, mask, unite_off = 0, enlarge = 0;
in_c_offs = 0;
in_ctrl = 0;
switch (cfg->input.format) {
case V4L2_PIX_FMT_NV16:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = cfg->input.stride * cfg->input.height;
in_size = cfg->input.stride * cfg->input.height * 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_422SP;
unite_off = 8;
break;
case V4L2_PIX_FMT_NV12:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = cfg->input.stride * cfg->input.height;
in_size = cfg->input.stride * cfg->input.height * 3 / 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_420SP;
unite_off = 8;
break;
case V4L2_PIX_FMT_NV61:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = cfg->input.stride * cfg->input.height;
in_size = cfg->input.stride * cfg->input.height * 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_422SP | RKVPSS_MI_RD_UV_SWAP;
unite_off = 8;
break;
case V4L2_PIX_FMT_NV21:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = cfg->input.stride * cfg->input.height;
in_size = cfg->input.stride * cfg->input.height * 3 / 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_420SP | RKVPSS_MI_RD_UV_SWAP;
unite_off = 8;
break;
case V4L2_PIX_FMT_RGB565:
if (cfg->input.stride < ALIGN(cfg->input.width * 2, 16))
cfg->input.stride = ALIGN(cfg->input.width * 2, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_BGR565;
unite_off = 16;
break;
case V4L2_PIX_FMT_RGB565X:
if (cfg->input.stride < ALIGN(cfg->input.width * 2, 16))
cfg->input.stride = ALIGN(cfg->input.width * 2, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_BGR565 | RKVPSS_MI_RD_RB_SWAP;
unite_off = 16;
break;
case V4L2_PIX_FMT_RGB24:
if (cfg->input.stride < ALIGN(cfg->input.width * 3, 16))
cfg->input.stride = ALIGN(cfg->input.width * 3, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_BGR888;
unite_off = 24;
break;
case V4L2_PIX_FMT_BGR24:
if (cfg->input.stride < ALIGN(cfg->input.width * 3, 16))
cfg->input.stride = ALIGN(cfg->input.width * 3, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_BGR888 | RKVPSS_MI_RD_RB_SWAP;
unite_off = 24;
break;
case V4L2_PIX_FMT_XRGB32:
if (cfg->input.stride < ALIGN(cfg->input.width * 4, 16))
cfg->input.stride = ALIGN(cfg->input.width * 4, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_ABGR888 | RKVPSS_MI_RD_RB_SWAP;
unite_off = 32;
break;
case V4L2_PIX_FMT_XBGR32:
if (cfg->input.stride < ALIGN(cfg->input.width * 4, 16))
cfg->input.stride = ALIGN(cfg->input.width * 4, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_ABGR888 | RKVPSS_MI_RD_RB_SWAP;
unite_off = 32;
break;
case V4L2_PIX_FMT_RGBX32:
if (cfg->input.stride < ALIGN(cfg->input.width * 4, 16))
cfg->input.stride = ALIGN(cfg->input.width * 4, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_ABGR888
| RKVPSS_MI_RD_RB_SWAP
| RKVPSS_MI_RD_ALPHA_SWAP;
unite_off = 32;
break;
case V4L2_PIX_FMT_BGRX32:
if (cfg->input.stride < ALIGN(cfg->input.width * 4, 16))
cfg->input.stride = ALIGN(cfg->input.width * 4, 16);
in_size = cfg->input.stride * cfg->input.height;
in_ctrl |= RKVPSS_MI_RD_INPUT_ABGR888
| RKVPSS_MI_RD_RB_SWAP
| RKVPSS_MI_RD_ALPHA_SWAP;
unite_off = 32;
break;
case V4L2_PIX_FMT_FBC0:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = 0;
in_size = cfg->input.stride * cfg->input.height * 3 / 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_420SP;
break;
case V4L2_PIX_FMT_FBC2:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = 0;
in_size = cfg->input.stride * cfg->input.height * 2;
in_ctrl |= RKVPSS_MI_RD_INPUT_422SP;
break;
case V4L2_PIX_FMT_FBC4:
if (cfg->input.stride < ALIGN(cfg->input.width, 16))
cfg->input.stride = ALIGN(cfg->input.width, 16);
in_c_offs = 0;
in_size = cfg->input.stride * cfg->input.height * 3;
in_ctrl |= RKVPSS_MI_RD_INPUT_422SP | RKVPSS_MI_RD_FBCD_YUV444_EN;
break;
case V4L2_PIX_FMT_TILE420:
if (cfg->input.stride < ALIGN(cfg->input.width * 6, 16))
cfg->input.stride = ALIGN(cfg->input.width * 6, 16);
in_c_offs = 0;
in_size = cfg->input.stride * (cfg->input.height / 4);
in_ctrl |= RKVPSS_MI_RD_INPUT_420SP;
break;
case V4L2_PIX_FMT_TILE422:
if (cfg->input.stride < ALIGN(cfg->input.width * 8, 16))
cfg->input.stride = ALIGN(cfg->input.width * 8, 16);
in_c_offs = 0;
in_size = cfg->input.stride * (cfg->input.height / 4);
in_ctrl |= RKVPSS_MI_RD_INPUT_422SP;
break;
default:
v4l2_err(&ofl->v4l2_dev, "dev_id:%d no support input format:%c%c%c%c\n",
cfg->dev_id, cfg->input.format, cfg->input.format >> 8,
cfg->input.format >> 16, cfg->input.format >> 24);
return -EINVAL;
}
buf = buf_add(file, cfg->dev_id, cfg->input.buf_fd, in_size);
if (!buf)
return -ENOMEM;
sg_tbl = mem_ops->cookie(&buf->vb, buf->mem);
if (!unite) {
val = cfg->input.width;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_WIDTH, val);
val = cfg->input.height;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_HEIGHT, val);
val = sg_dma_address(sg_tbl->sgl);
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_BASE, val);
val += in_c_offs;
rkvpss_hw_write(hw, RKVPSS_MI_RD_C_BASE, val);
} else {
val = cfg->input.height;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_HEIGHT, val);
ofl->unite_right_enlarge = ALIGN(cfg->input.width / 2, 16) -
(cfg->input.width / 2) + 16;
if (left) {
if (!cfg->mirror)
enlarge = UNITE_LEFT_ENLARGE;
else
enlarge = ofl->unite_right_enlarge;
val = cfg->input.width / 2 + enlarge;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_WIDTH, val);
val = sg_dma_address(sg_tbl->sgl);
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_BASE, val);
val += in_c_offs;
rkvpss_hw_write(hw, RKVPSS_MI_RD_C_BASE, val);
} else {
if (!cfg->mirror)
enlarge = ofl->unite_right_enlarge;
else
enlarge = UNITE_LEFT_ENLARGE;
val = cfg->input.width / 2 + enlarge;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_WIDTH, val);
val = (cfg->input.width / 2 - enlarge) * unite_off;
unite_r_offs = ALIGN_DOWN(val / 8, 16);
val = sg_dma_address(sg_tbl->sgl) + unite_r_offs;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_BASE, val);
val += in_c_offs;
rkvpss_hw_write(hw, RKVPSS_MI_RD_C_BASE, val);
}
}
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s unite:%d left:%d hw width:%d height:%d y_base:0x%x\n",
__func__, unite, left,
rkvpss_hw_read(hw, RKVPSS_MI_RD_Y_WIDTH),
rkvpss_hw_read(hw, RKVPSS_MI_RD_Y_HEIGHT),
rkvpss_hw_read(hw, RKVPSS_MI_RD_Y_BASE));
if (cfg->input.format == V4L2_PIX_FMT_FBC0 ||
cfg->input.format == V4L2_PIX_FMT_FBC2 ||
cfg->input.format == V4L2_PIX_FMT_FBC4) {
in_ctrl |= RKVPSS_MI_RD_MODE(2) | RKVPSS_MI_RD_FBCD_OPT_DIS;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_STRIDE, 0);
} else {
if (cfg->input.format == V4L2_PIX_FMT_TILE420 ||
cfg->input.format == V4L2_PIX_FMT_TILE422) {
in_ctrl |= RKVPSS_MI_RD_MODE(1);
switch (cfg->input.rotate) {
case ROTATE_90:
in_ctrl |= RKVPSS_MI_RD_ROT_90;
break;
case ROTATE_180:
in_ctrl |= RKVPSS_MI_RD_ROT_180;
break;
case ROTATE_270:
in_ctrl |= RKVPSS_MI_RD_ROT_270;
break;
default:
in_ctrl |= RKVPSS_MI_RD_ROT_0;
break;
}
}
val = cfg->input.stride;
rkvpss_hw_write(hw, RKVPSS_MI_RD_Y_STRIDE, val);
}
mask = RKVPSS_MI_RD_GROUP_MODE(3) | RKVPSS_MI_RD_BURST16_LEN;
rkvpss_hw_set_bits(hw, RKVPSS_MI_RD_CTRL, ~mask, in_ctrl);
rkvpss_hw_write(hw, RKVPSS_MI_RD_INIT, RKVPSS_MI_RD_FORCE_UPD);
return 0;
}
static void crop_config(struct file *file, struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
int i;
u32 reg, val, crop_en;
crop_en = 0;
if (!unite) {
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
reg = RKVPSS_CROP0_0_H_OFFS;
val = cfg->output[i].crop_h_offs;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_OFFS;
val = cfg->output[i].crop_v_offs;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_H_SIZE;
val = cfg->output[i].crop_width;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_SIZE;
val = cfg->output[i].crop_height;
rkvpss_hw_write(hw, reg + i * 0x10, val);
crop_en |= RKVPSS_CROP_CHN_EN(i);
}
} else {
if (left) {
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
reg = RKVPSS_CROP0_0_H_OFFS;
val = cfg->output[i].crop_h_offs;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_OFFS;
val = cfg->output[i].crop_v_offs;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_H_SIZE;
/*if no scale, left don't enlarge*/
if (cfg->output[i].crop_width == cfg->output[i].scl_width)
val = cfg->output[i].crop_width / 2;
else
val = cfg->output[i].crop_width / 2 + UNITE_LEFT_ENLARGE;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_SIZE;
val = cfg->output[i].crop_height;
rkvpss_hw_write(hw, reg + i * 0x10, val);
crop_en |= RKVPSS_CROP_CHN_EN(i);
}
} else {
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
reg = RKVPSS_CROP0_0_H_OFFS;
val = ofl->unite_params[i].quad_crop_w;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_OFFS;
val = cfg->output[i].crop_v_offs;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_H_SIZE;
val = cfg->output[i].crop_width / 2 +
ofl->unite_right_enlarge -
ofl->unite_params[i].quad_crop_w;
rkvpss_hw_write(hw, reg + i * 0x10, val);
reg = RKVPSS_CROP0_0_V_SIZE;
val = cfg->output[i].crop_height;
rkvpss_hw_write(hw, reg + i * 0x10, val);
crop_en |= RKVPSS_CROP_CHN_EN(i);
}
}
}
rkvpss_hw_write(hw, RKVPSS_CROP0_CTRL, crop_en);
rkvpss_hw_write(hw, RKVPSS_CROP0_UPDATE, RKVPSS_CROP_FORCE_UPD);
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s unite:%d left:%d hw ch:%d h_offs:%d v_offs:%d width:%d height:%d\n",
__func__, unite, left, i,
rkvpss_hw_read(hw, RKVPSS_CROP0_0_H_OFFS + i * 0x10),
rkvpss_hw_read(hw, RKVPSS_CROP0_0_V_OFFS + i * 0x10),
rkvpss_hw_read(hw, RKVPSS_CROP0_0_H_SIZE + i * 0x10),
rkvpss_hw_read(hw, RKVPSS_CROP0_0_V_SIZE + i * 0x10));
}
}
static int write_config(struct file *file, struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *mem_ops = hw->mem_ops;
struct sg_table *sg_tbl;
struct rkvpss_offline_buf *buf;
struct rkvpss_output_ch out_ch[RKVPSS_OUTPUT_MAX] = { 0 };
int i;
u32 w, h, val, reg, mask, mi_update, flip_en, unite_off = 0;
bool ch_en = false, wr_uv_swap = false;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!hw->is_ofl_ch[i] && cfg->output[i].enable) {
v4l2_err(&ofl->v4l2_dev,
"dev_id:%d ch%d no select for offline mode, set to disable\n",
cfg->dev_id, i);
cfg->output[i].enable = 0;
}
if (!cfg->output[i].enable)
continue;
ch_en = true;
if (cfg->output[i].aspt.enable) {
w = cfg->output[i].aspt.width;
h = cfg->output[i].aspt.height;
} else {
w = cfg->output[i].scl_width;
h = cfg->output[i].scl_height;
}
if (i == RKVPSS_OUTPUT_CH1) {
bool is_fmt_find = true;
switch (cfg->output[i].format) {
case V4L2_PIX_FMT_RGB565:
if (cfg->output[i].stride < ALIGN(w * 2, 16))
cfg->output[i].stride = ALIGN(w * 2, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_RGB565 | RKVPSS_MI_CHN_WR_RB_SWAP;
break;
case V4L2_PIX_FMT_RGB24:
if (cfg->output[i].stride < ALIGN(w * 3, 16))
cfg->output[i].stride = ALIGN(w * 3, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_RGB888 | RKVPSS_MI_CHN_WR_RB_SWAP;
break;
case V4L2_PIX_FMT_RGB565X:
if (cfg->output[i].stride < ALIGN(w * 2, 16))
cfg->output[i].stride = ALIGN(w * 2, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_RGB565;
break;
case V4L2_PIX_FMT_BGR24:
if (cfg->output[i].stride < ALIGN(w * 3, 16))
cfg->output[i].stride = ALIGN(w * 3, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_RGB888;
break;
case V4L2_PIX_FMT_XBGR32:
if (cfg->output[i].stride < ALIGN(w * 4, 16))
cfg->output[i].stride = ALIGN(w * 4, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_ARGB888;
break;
case V4L2_PIX_FMT_XRGB32:
if (cfg->output[i].stride < ALIGN(w * 4, 16))
cfg->output[i].stride = ALIGN(w * 4, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_ARGB888
| RKVPSS_MI_CHN_WR_RB_SWAP;
break;
default:
is_fmt_find = false;
}
if (is_fmt_find) {
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_EN | RKVPSS_MI_CHN_WR_AUTO_UPD;
out_ch[i].size = cfg->output[i].stride * h;
continue;
}
}
switch (cfg->output[i].format) {
case V4L2_PIX_FMT_UYVY:
if (cfg->output[i].stride < ALIGN(w * 2, 16))
cfg->output[i].stride = ALIGN(w * 2, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_422P | RKVPSS_MI_CHN_WR_OUTPUT_YUV422;
out_ch[i].size = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_NV16:
if (cfg->output[i].stride < ALIGN(w, 16))
cfg->output[i].stride = ALIGN(w, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_42XSP | RKVPSS_MI_CHN_WR_OUTPUT_YUV422;
out_ch[i].size = cfg->output[i].stride * h * 2;
out_ch[i].c_offs = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_NV12:
if (cfg->output[i].stride < ALIGN(w, 16))
cfg->output[i].stride = ALIGN(w, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_42XSP | RKVPSS_MI_CHN_WR_OUTPUT_YUV420;
out_ch[i].size = cfg->output[i].stride * h * 3 / 2;
out_ch[i].c_offs = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_GREY:
if (cfg->output[i].stride < ALIGN(w, 16))
cfg->output[i].stride = ALIGN(w, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_42XSP | RKVPSS_MI_CHN_WR_OUTPUT_YUV400;
out_ch[i].size = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_VYUY:
if (cfg->output[i].stride < ALIGN(w * 2, 16))
cfg->output[i].stride = ALIGN(w * 2, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_422P | RKVPSS_MI_CHN_WR_OUTPUT_YUV422;
out_ch[i].size = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_NV61:
if (cfg->output[i].stride < ALIGN(w, 16))
cfg->output[i].stride = ALIGN(w, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_42XSP | RKVPSS_MI_CHN_WR_OUTPUT_YUV422;
out_ch[i].size = cfg->output[i].stride * h * 2;
out_ch[i].c_offs = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_NV21:
if (cfg->output[i].stride < ALIGN(w, 16))
cfg->output[i].stride = ALIGN(w, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_42XSP | RKVPSS_MI_CHN_WR_OUTPUT_YUV420;
out_ch[i].size = cfg->output[i].stride * h * 3 / 2;
out_ch[i].c_offs = cfg->output[i].stride * h;
break;
case V4L2_PIX_FMT_TILE420:
if (cfg->output[i].stride < ALIGN(w * 6, 16))
cfg->output[i].stride = ALIGN(w * 6, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_YUV420;
out_ch[i].size = cfg->output[i].stride * (h / 4);
out_ch[i].c_offs = 0;
break;
case V4L2_PIX_FMT_TILE422:
if (cfg->output[i].stride < ALIGN(w * 8, 16))
cfg->output[i].stride = ALIGN(w * 8, 16);
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_OUTPUT_YUV422;
out_ch[i].size = cfg->output[i].stride * (h / 4);
out_ch[i].c_offs = 0;
break;
default:
v4l2_err(&ofl->v4l2_dev, "dev_id:%d no support output ch%d format:%c%c%c%c\n",
cfg->dev_id, i,
cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24);
return -EINVAL;
}
out_ch[i].ctrl |= RKVPSS_MI_CHN_WR_EN | RKVPSS_MI_CHN_WR_AUTO_UPD;
}
if (!ch_en) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d no output channel enable\n", cfg->dev_id);
return -EINVAL;
}
mi_update = 0;
flip_en = 0;
mask = 0;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (hw->is_ofl_ch[i])
mask |= RKVPSS_MI_CHN_V_FLIP(i);
if (!cfg->output[i].enable)
continue;
buf = buf_add(file, cfg->dev_id, cfg->output[i].buf_fd, out_ch[i].size);
if (!buf)
goto free_buf;
if (unite && !left)
unite_off = (ALIGN_DOWN(cfg->output[i].scl_width / 2, 16) * 8) / 8;
if (cfg->output[i].aspt.enable)
h = cfg->output[i].aspt.height;
else
h = cfg->output[i].scl_height;
sg_tbl = mem_ops->cookie(&buf->vb, buf->mem);
val = sg_dma_address(sg_tbl->sgl) + unite_off;
reg = RKVPSS_MI_CHN0_WR_Y_BASE;
rkvpss_hw_write(hw, reg + i * 0x100, val);
reg = RKVPSS_MI_CHN0_WR_CB_BASE;
val += out_ch[i].c_offs;
rkvpss_hw_write(hw, reg + i * 0x100, val);
reg = RKVPSS_MI_CHN0_WR_Y_STRIDE;
val = cfg->output[i].stride;
rkvpss_hw_write(hw, reg + i * 0x100, val);
reg = RKVPSS_MI_CHN0_WR_Y_SIZE;
if (cfg->output[i].format == V4L2_PIX_FMT_TILE420 ||
cfg->output[i].format == V4L2_PIX_FMT_TILE422)
val = cfg->output[i].stride * (ALIGN(h, 4) / 4);
else
val = cfg->output[i].stride * h;
rkvpss_hw_write(hw, reg + i * 0x100, val);
reg = RKVPSS_MI_CHN0_WR_CB_SIZE;
val = out_ch[i].size - val;
rkvpss_hw_write(hw, reg + i * 0x100, val);
reg = RKVPSS_MI_CHN0_WR_CTRL;
val = out_ch[i].ctrl;
rkvpss_hw_write(hw, reg + i * 0x100, val);
if (cfg->output[i].flip &&
!(cfg->output[i].format == V4L2_PIX_FMT_TILE420) &&
!(cfg->output[i].format == V4L2_PIX_FMT_TILE422)) {
flip_en |= RKVPSS_MI_CHN_V_FLIP(i);
switch (cfg->output[i].format) {
case V4L2_PIX_FMT_RGB565:
case V4L2_PIX_FMT_RGB565X:
val = cfg->output[i].stride / 2;
break;
case V4L2_PIX_FMT_XBGR32:
case V4L2_PIX_FMT_XRGB32:
val = cfg->output[i].stride / 4;
break;
default:
val = cfg->output[i].stride;
break;
}
val = val * h;
reg = RKVPSS_MI_CHN0_WR_Y_PIC_SIZE;
rkvpss_hw_write(hw, reg + i * 0x100, val);
}
mi_update |= (RKVPSS_MI_CHN0_FORCE_UPD << i);
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s unite:%d left:%d hw ch:%d y_size:%d y_stride:%d y_pic_size:%d y_base:0x%x",
__func__, unite, left, i,
rkvpss_hw_read(hw, RKVPSS_MI_CHN0_WR_Y_SIZE + i * 100),
rkvpss_hw_read(hw, RKVPSS_MI_CHN0_WR_Y_STRIDE + i * 100),
rkvpss_hw_read(hw, RKVPSS_MI_CHN0_WR_Y_PIC_SIZE + i * 100),
rkvpss_hw_read(hw, RKVPSS_MI_CHN0_WR_Y_BASE + i * 100));
}
if (flip_en)
rkvpss_hw_set_bits(hw, RKVPSS_MI_WR_VFLIP_CTRL, mask, flip_en);
/* config output uv swap */
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (cfg->output[i].enable &&
(cfg->output[i].format == V4L2_PIX_FMT_VYUY ||
cfg->output[i].format == V4L2_PIX_FMT_NV21 ||
cfg->output[i].format == V4L2_PIX_FMT_NV61))
wr_uv_swap = true;
}
if (wr_uv_swap) {
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (cfg->output[i].enable && (cfg->output[i].format == V4L2_PIX_FMT_UYVY ||
cfg->output[i].format == V4L2_PIX_FMT_NV12 ||
cfg->output[i].format == V4L2_PIX_FMT_NV16)) {
v4l2_err(&ofl->v4l2_dev,
"dev_id:%d wr_uv_swap need to be consistent\n",
cfg->dev_id);
return -EAGAIN;
}
}
}
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (cfg->output[i].format == V4L2_PIX_FMT_VYUY ||
cfg->output[i].format == V4L2_PIX_FMT_NV21 ||
cfg->output[i].format == V4L2_PIX_FMT_NV61) {
mask = RKVPSS_MI_WR_UV_SWAP;
val = RKVPSS_MI_WR_UV_SWAP;
rkvpss_hw_set_bits(hw, RKVPSS_MI_WR_CTRL, mask, val);
break;
}
}
for (i = 0; i <= RKVPSS_OUTPUT_CH1; i++) {
if (cfg->output[i].format == V4L2_PIX_FMT_TILE420 ||
cfg->output[i].format == V4L2_PIX_FMT_TILE422) {
mask = RKVPSS_MI_WR_TILE_SEL(3);
val = RKVPSS_MI_WR_TILE_SEL(i + 1);
rkvpss_hw_set_bits(hw, RKVPSS_MI_WR_CTRL, mask, val);
}
}
/* need update two for online2 mode */
rkvpss_hw_write(hw, RKVPSS_MI_WR_INIT, mi_update);
rkvpss_hw_write(hw, RKVPSS_MI_WR_INIT, mi_update);
return 0;
free_buf:
for (i -= 1; i >= 0; i--) {
if (!cfg->output[i].enable)
continue;
buf_del(file, cfg->dev_id, cfg->output[i].buf_fd, false, true);
}
buf_del(file, cfg->dev_id, cfg->input.buf_fd, false, true);
return -ENOMEM;
}
static void calc_unite_scl_params(struct file *file, struct rkvpss_frame_cfg *cfg)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_unite_scl_params *params;
int i;
u32 right_scl_need_size_y, right_scl_need_size_c;
u32 left_in_used_size_y, left_in_used_size_c;
u32 right_fst_position_y, right_fst_position_c;
u32 right_y_crop_total;
u32 right_c_crop_total;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (cfg->output[i].enable == 0)
continue;
params = &ofl->unite_params[i];
params->y_w_fac = (cfg->output[i].crop_width - 1) * 4096 /
(cfg->output[i].scl_width - 1);
params->c_w_fac = (cfg->output[i].crop_width / 2 - 1) * 4096 /
(cfg->output[i].scl_width / 2 - 1);
params->y_h_fac = (cfg->output[i].crop_height - 1) * 4096 /
(cfg->output[i].scl_height - 1);
params->c_h_fac = (cfg->output[i].crop_height - 1) * 4096 /
(cfg->output[i].scl_height - 1);
right_fst_position_y = cfg->output[i].scl_width / 2 *
params->y_w_fac;
right_fst_position_c = cfg->output[i].scl_width / 2 / 2 *
params->c_w_fac;
left_in_used_size_y = right_fst_position_y >> 12;
left_in_used_size_c = (right_fst_position_c >> 12) * 2;
params->y_w_phase = right_fst_position_y & 0xfff;
params->c_w_phase = right_fst_position_c & 0xfff;
right_scl_need_size_y = cfg->output[i].crop_width -
left_in_used_size_y;
params->right_scl_need_size_y = right_scl_need_size_y;
right_scl_need_size_c = cfg->output[i].crop_width -
left_in_used_size_c;
params->right_scl_need_size_c = right_scl_need_size_c;
if (i == 0 && cfg->output[i].crop_width != cfg->output[i].scl_width) {
right_y_crop_total = cfg->output[i].crop_width / 2 +
ofl->unite_right_enlarge -
right_scl_need_size_y - 3;
right_c_crop_total = cfg->output[i].crop_width / 2 +
ofl->unite_right_enlarge -
right_scl_need_size_c - 6;
} else {
right_y_crop_total = cfg->output[i].crop_width / 2 +
ofl->unite_right_enlarge -
right_scl_need_size_y;
right_c_crop_total = cfg->output[i].crop_width / 2 +
ofl->unite_right_enlarge -
right_scl_need_size_c;
}
params->quad_crop_w = ALIGN_DOWN(min(right_y_crop_total, right_c_crop_total), 2);
params->scl_in_crop_w_y = right_y_crop_total - params->quad_crop_w;
params->scl_in_crop_w_c = right_c_crop_total - params->quad_crop_w;
if (rkvpss_debug >= 4) {
v4l2_info(&ofl->v4l2_dev,
"%s dev_id:%d seq:%d ch:%d y_w_fac:%u c_w_fac:%u y_h_fac:%u c_h_fac:%u\n",
__func__, cfg->dev_id, cfg->sequence, i, params->y_w_fac,
params->c_w_fac, params->y_h_fac, params->c_h_fac);
v4l2_info(&ofl->v4l2_dev,
"\t\t\t\t\t\t unite_right_enlarge:%u",
ofl->unite_right_enlarge);
v4l2_info(&ofl->v4l2_dev,
"\t\t\t\t\t\t y_w_phase:%u c_w_phase:%u quad_crop_w:%u scl_in_crop_w_y:%u scl_in_crop_w_c:%u\n",
params->y_w_phase, params->c_w_phase,
params->quad_crop_w,
params->scl_in_crop_w_y, params->scl_in_crop_w_c);
v4l2_info(&ofl->v4l2_dev,
"\t\t\t\t\t\t right_scl_need_size_y:%u right_scl_need_size_c:%u\n",
params->right_scl_need_size_y,
params->right_scl_need_size_c);
}
}
}
static int rkvpss_ofl_run(struct file *file, struct rkvpss_frame_cfg *cfg, bool unite, bool left)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
u32 update, val, mask;
int ret, i;
bool left_tmp;
if (!unite || left)
add_cfginfo(ofl, cfg);
init_completion(&ofl->cmpl);
ofl->mode_sel_en = false;
ret = read_config(file, cfg, unite, left);
if (ret < 0)
return ret;
if (unite && left)
calc_unite_scl_params(file, cfg);
if (unite && cfg->mirror)
left_tmp = !left;
else
left_tmp = left;
ret = cmsc_config(ofl, cfg, unite, left_tmp);
if (ret)
return ret;
crop_config(file, cfg, unite, left_tmp);
scale_config(file, cfg, unite, left_tmp);
if (!unite)
aspt_config(file, cfg);
ret = write_config(file, cfg, unite, left_tmp);
if (ret < 0)
return ret;
mask = 0;
val = 0;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!hw->is_ofl_ch[i])
continue;
mask |= (RKVPSS_ISP2VPSS_CHN0_SEL(3) << i * 2);
if (hw->is_ofl_cmsc)
mask |= RKVPSS_ISP2VPSS_ONLINE2_CMSC_EN;
if (cfg->output[i].enable)
val |= (RKVPSS_ISP2VPSS_CHN0_SEL(1) << i * 2);
}
rkvpss_hw_set_bits(hw, RKVPSS_VPSS_ONLINE, mask, val);
update = 0;
mask = hw->is_ofl_cmsc ? RKVPSS_MIR_EN : 0;
val = (mask && cfg->mirror) ? RKVPSS_MIR_EN : 0;
if (mask) {
rkvpss_hw_set_bits(hw, RKVPSS_VPSS_CTRL, mask, val);
update |= RKVPSS_MIR_FORCE_UPD;
}
update |= RKVPSS_CHN_FORCE_UPD | RKVPSS_CFG_GEN_UPD | RKVPSS_MIR_GEN_UPD;
rkvpss_hw_write(hw, RKVPSS_VPSS_UPDATE, update);
rkvpss_hw_set_bits(hw, RKVPSS_VPSS_IMSC, 0, RKVPSS_ALL_FRM_END);
rkvpss_hw_write(hw, RKVPSS_MI_RD_START, RKVPSS_MI_RD_ST);
ret = wait_for_completion_timeout(&ofl->cmpl, msecs_to_jiffies(500));
if (!ret) {
v4l2_err(&ofl->v4l2_dev, "working timeout\n");
ret = -EAGAIN;
} else {
ret = 0;
}
return ret;
}
static int rkvpss_module_get(struct file *file,
struct rkvpss_module_sel *get)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
int i, ret = 0;
mutex_lock(&hw->dev_lock);
if (hw->is_ofl_cmsc)
get->mirror_cmsc_en = 1;
else
get->mirror_cmsc_en = 0;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (hw->is_ofl_ch[i])
get->ch_en[i] = 1;
else
get->ch_en[i] = 0;
}
mutex_unlock(&hw->dev_lock);
return ret;
}
static int rkvpss_module_sel(struct file *file,
struct rkvpss_module_sel *sel)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
struct rkvpss_hw_dev *hw = ofl->hw;
struct rkvpss_device *vpss;
int i, ret = 0;
mutex_lock(&hw->dev_lock);
if (!ofl->mode_sel_en) {
v4l2_err(&ofl->v4l2_dev, "already set module_sel\n");
ret = -EINVAL;
goto unlock;
}
for (i = 0; i < hw->dev_num; i++) {
vpss = hw->vpss[i];
if (vpss && (vpss->vpss_sdev.state & VPSS_START)) {
v4l2_err(&ofl->v4l2_dev, "no support set mode when vpss working\n");
ret = -EINVAL;
goto unlock;
}
}
hw->is_ofl_cmsc = !!sel->mirror_cmsc_en;
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++)
hw->is_ofl_ch[i] = !!sel->ch_en[i];
unlock:
mutex_unlock(&hw->dev_lock);
return ret;
}
static int rkvpss_check_params(struct file *file, struct rkvpss_frame_cfg *cfg, bool *unite)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
int i, ret = 0, tile_num = 0;
/* check dev id out of range */
if (cfg->dev_id >= DEV_NUM_MAX) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d is out of range. range[0, %d]\n",
cfg->dev_id, DEV_NUM_MAX);
ret = -EINVAL;
goto end;
}
/* set unite mode */
if (cfg->input.width > RKVPSS_MAX_WIDTH)
*unite = true;
else
*unite = false;
/* check input format */
switch (cfg->input.format) {
case V4L2_PIX_FMT_NV16:
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV61:
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_RGB565:
case V4L2_PIX_FMT_RGB565X:
case V4L2_PIX_FMT_RGB24:
case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_XRGB32:
case V4L2_PIX_FMT_XBGR32:
case V4L2_PIX_FMT_RGBX32:
case V4L2_PIX_FMT_BGRX32:
case V4L2_PIX_FMT_FBC0:
case V4L2_PIX_FMT_FBC2:
case V4L2_PIX_FMT_FBC4:
case V4L2_PIX_FMT_TILE420:
case V4L2_PIX_FMT_TILE422:
break;
default:
v4l2_err(&ofl->v4l2_dev, "dev_id:%d no support input format:%c%c%c%c\n",
cfg->dev_id, cfg->input.format, cfg->input.format >> 8,
cfg->input.format >> 16, cfg->input.format >> 24);
ret = -EINVAL;
goto end;
}
/* check input size */
if (cfg->input.width > RKVPSS_UNITE_MAX_WIDTH ||
cfg->input.height > RKVPSS_UNITE_MAX_HEIGHT ||
cfg->input.width < RKVPSS_MIN_WIDTH ||
cfg->input.height < RKVPSS_MIN_HEIGHT) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d input size not support width:%d height:%d\n",
cfg->dev_id, cfg->input.width, cfg->input.height);
ret = -EINVAL;
goto end;
}
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
/* check output format */
switch (cfg->output[i].format) {
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_NV16:
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_GREY:
case V4L2_PIX_FMT_VYUY:
case V4L2_PIX_FMT_NV61:
case V4L2_PIX_FMT_NV21:
break;
case V4L2_PIX_FMT_TILE420:
case V4L2_PIX_FMT_TILE422:
if (i == RKVPSS_OUTPUT_CH0 || i == RKVPSS_OUTPUT_CH1) {
tile_num++;
if (tile_num > 1) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d only ch0 or ch1 can tile write\n",
cfg->dev_id);
ret = -EINVAL;
goto end;
}
if (cfg->output[i].flip) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch:%d tile write no support flip\n",
cfg->dev_id, i);
ret = -EINVAL;
goto end;
}
} else {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch:%d no support output format:%c%c%c%c\n",
cfg->dev_id, i, cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24);
ret = -EINVAL;
goto end;
}
break;
case V4L2_PIX_FMT_RGB565:
case V4L2_PIX_FMT_RGB24:
case V4L2_PIX_FMT_RGB565X:
case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_XBGR32:
case V4L2_PIX_FMT_XRGB32:
if (i != RKVPSS_OUTPUT_CH1) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch:%d no support output format:%c%c%c%c\n",
cfg->dev_id, i, cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24);
ret = -EINVAL;
goto end;
}
break;
default:
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch:%d no support output format:%c%c%c%c\n",
cfg->dev_id, i, cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24);
ret = -EINVAL;
goto end;
}
/* check output size */
if (cfg->output[i].scl_width > RKVPSS_UNITE_MAX_WIDTH ||
cfg->output[i].scl_height > RKVPSS_UNITE_MAX_HEIGHT ||
cfg->output[i].scl_width < RKVPSS_MIN_WIDTH ||
cfg->output[i].scl_height < RKVPSS_MIN_HEIGHT) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d output size not support width:%d height:%d\n",
cfg->dev_id, cfg->output[i].scl_width, cfg->output[i].scl_height);
ret = -EINVAL;
goto end;
}
/* check crop */
cfg->output[i].crop_h_offs = ALIGN(cfg->output[i].crop_h_offs, 2);
cfg->output[i].crop_v_offs = ALIGN(cfg->output[i].crop_v_offs, 2);
cfg->output[i].crop_width = ALIGN(cfg->output[i].crop_width, 2);
cfg->output[i].crop_height = ALIGN(cfg->output[i].crop_height, 2);
if (!cfg->input.rotate || cfg->input.rotate == 2) {
if (cfg->output[i].crop_width + cfg->output[i].crop_h_offs > cfg->input.width) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch%d inval crop(offs:%d w:%d) input width:%d\n",
i, cfg->dev_id, cfg->output[i].crop_h_offs,
cfg->output[i].crop_width, cfg->input.width);
ret = -EINVAL;
goto end;
}
if (cfg->output[i].crop_height + cfg->output[i].crop_v_offs > cfg->input.height) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch%d inval crop(offs:%d h:%d) input height:%d\n",
cfg->dev_id, i, cfg->output[i].crop_v_offs,
cfg->output[i].crop_height, cfg->input.height);
ret = -EINVAL;
goto end;
}
} else {
if (cfg->output[i].crop_width + cfg->output[i].crop_h_offs > cfg->input.height) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch%d rotate inval crop(offs:%d w:%d) input height:%d\n",
i, cfg->dev_id, cfg->output[i].crop_h_offs,
cfg->output[i].crop_width, cfg->input.height);
ret = -EINVAL;
goto end;
}
if (cfg->output[i].crop_height + cfg->output[i].crop_v_offs > cfg->input.width) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch%d rotate inval crop(offs:%d h:%d) input width:%d\n",
cfg->dev_id, i, cfg->output[i].crop_v_offs,
cfg->output[i].crop_height, cfg->input.width);
ret = -EINVAL;
goto end;
}
}
if (*unite) {
if (cfg->output[i].crop_h_offs != (cfg->input.width -
(cfg->output[i].crop_h_offs +
cfg->output[i].crop_width))) {
v4l2_err(&ofl->v4l2_dev, " dev_id:%d ch%d unite crop_v need centered crop(h_offs:%d w:%d) input width:%d\n",
cfg->dev_id, i, cfg->output[i].crop_h_offs,
cfg->output[i].crop_width, cfg->input.width);
ret = -EINVAL;
goto end;
}
}
/* check scale */
if (i == RKVPSS_OUTPUT_CH2 || i == RKVPSS_OUTPUT_CH3) {
if (cfg->output[i].crop_width != cfg->output[i].scl_width &&
cfg->output[i].crop_height != cfg->output[i].scl_height) {
if ((!*unite && cfg->output[i].scl_width > 1920) ||
(*unite && cfg->output[i].scl_width > 1920 * 2)) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d ch%d single scale max width 1920\n",
cfg->dev_id, i);
ret = -EINVAL;
goto end;
}
}
}
}
/* check rotate */
switch (cfg->input.rotate) {
case ROTATE_90:
case ROTATE_180:
case ROTATE_270:
if (cfg->input.format != V4L2_PIX_FMT_TILE420 &&
cfg->input.format != V4L2_PIX_FMT_TILE422) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d input format:%c%c%c%c not support rotate\n",
cfg->dev_id, cfg->input.format, cfg->input.format >> 8,
cfg->input.format >> 16, cfg->input.format >> 24);
ret = -EINVAL;
goto end;
}
break;
default:
break;
}
/** unite constraints **/
if (*unite) {
if (cfg->input.format == V4L2_PIX_FMT_FBC0 ||
cfg->input.format == V4L2_PIX_FMT_FBC2 ||
cfg->input.format == V4L2_PIX_FMT_FBC4 ||
cfg->input.format == V4L2_PIX_FMT_TILE420 ||
cfg->input.format == V4L2_PIX_FMT_TILE422) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d unite no support input this format:%c%c%c%c\n",
cfg->dev_id, cfg->input.format, cfg->input.format >> 8,
cfg->input.format >> 16, cfg->input.format >> 24);
ret = -EINVAL;
goto end;
}
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
if (!cfg->output[i].enable)
continue;
if (cfg->output[i].format != V4L2_PIX_FMT_NV12 &&
cfg->output[i].format != V4L2_PIX_FMT_NV16 &&
cfg->output[i].format != V4L2_PIX_FMT_NV21 &&
cfg->output[i].format != V4L2_PIX_FMT_NV61) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d unite no support output this format:%c%c%c%c\n",
cfg->dev_id, cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24);
ret = -EINVAL;
goto end;
}
if (cfg->output[i].scl_width > cfg->input.width) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d unite horizontal no support scale up\n",
cfg->dev_id);
ret = -EINVAL;
goto end;
}
if (cfg->output[i].aspt.enable) {
v4l2_err(&ofl->v4l2_dev, "dev_id:%d unite no support aspt\n",
cfg->dev_id);
ret = -EINVAL;
goto end;
}
}
}
end:
return ret;
}
static int rkvpss_prepare_run(struct file *file, struct rkvpss_frame_cfg *cfg)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
int ret = 0;
bool unite;
int i, j, k;
s64 us = 0;
u64 ns;
ktime_t t = 0;
ns = ktime_get_ns();
ofl->dev_rate[cfg->dev_id].in_rate = ns - ofl->dev_rate[cfg->dev_id].in_timestamp;
ofl->dev_rate[cfg->dev_id].in_timestamp = ns;
ret = rkvpss_check_params(file, cfg, &unite);
if (ret < 0)
goto end;
/******** show cfg info ********/
if (rkvpss_debug >= 2) {
t = ktime_get();
v4l2_info(&ofl->v4l2_dev,
"%s dev_id:%d seq:%d mirror:%d input:%dx%d buffd:%d format:%c%c%c%c stride:%d rotate:%d\n",
__func__, cfg->dev_id, cfg->sequence, cfg->mirror,
cfg->input.width, cfg->input.height, cfg->input.buf_fd,
cfg->input.format, cfg->input.format >> 8,
cfg->input.format >> 16, cfg->input.format >> 24,
cfg->input.stride, cfg->input.rotate);
for (i = 0; i < RKVPSS_OUTPUT_MAX; i++) {
v4l2_info(&ofl->v4l2_dev,
"\t\t\tch%d enable:%d crop:(%d,%d)/%dx%d scl:%dx%d flip:%d buffd:%d format:%c%c%c%c stride:%d\n",
i, cfg->output[i].enable,
cfg->output[i].crop_h_offs, cfg->output[i].crop_v_offs,
cfg->output[i].crop_width, cfg->output[i].crop_height,
cfg->output[i].scl_width, cfg->output[i].scl_height,
cfg->output[i].flip, cfg->output[i].buf_fd,
cfg->output[i].format, cfg->output[i].format >> 8,
cfg->output[i].format >> 16, cfg->output[i].format >> 24,
cfg->output[i].stride);
if (rkvpss_debug < 4)
break;
if (!cfg->output[i].enable)
continue;
v4l2_info(&ofl->v4l2_dev,
"\t\t\tcmsc mosaic_block:%d width_ro:%d height_ro:%d\n",
cfg->output[i].cmsc.mosaic_block,
cfg->output[i].cmsc.width_ro,
cfg->output[i].cmsc.height_ro);
for (j = 0; j < RKVPSS_CMSC_WIN_MAX; j++) {
if (!cfg->output[i].cmsc.win[j].win_en)
continue;
v4l2_info(&ofl->v4l2_dev,
"\t\t\t\twin:%d win_en:%u mode:%u color_y:%u color_u:%u color_v:%u color_a:%u\n",
j,
cfg->output[i].cmsc.win[j].win_en,
cfg->output[i].cmsc.win[j].mode,
cfg->output[i].cmsc.win[j].cover_color_y,
cfg->output[i].cmsc.win[j].cover_color_u,
cfg->output[i].cmsc.win[j].cover_color_v,
cfg->output[i].cmsc.win[j].cover_color_a);
for (k = 0; k < RKVPSS_CMSC_POINT_MAX; k++) {
v4l2_info(&ofl->v4l2_dev,
"\t\t\t\t\tpoint:%d x:%d y:%d\n",
k,
cfg->output[i].cmsc.win[j].point[k].x,
cfg->output[i].cmsc.win[j].point[k].y);
}
}
v4l2_info(&ofl->v4l2_dev,
"\t\t\taspt_en:%d w:%d h:%d h_offs:%d v_offs:%d color_y:%d color_u:%d color_v:%d\n",
cfg->output[i].aspt.enable,
cfg->output[i].aspt.width,
cfg->output[i].aspt.height,
cfg->output[i].aspt.h_offs,
cfg->output[i].aspt.v_offs,
cfg->output[i].aspt.color_y,
cfg->output[i].aspt.color_u,
cfg->output[i].aspt.color_v);
}
}
if (!unite) {
ret = rkvpss_ofl_run(file, cfg, false, false);
if (ret < 0)
goto end;
} else {
ret = rkvpss_ofl_run(file, cfg, true, true);
if (ret < 0) {
v4l2_err(&ofl->v4l2_dev, "unite left error\n");
goto end;
}
ret = rkvpss_ofl_run(file, cfg, true, false);
if (ret < 0) {
v4l2_err(&ofl->v4l2_dev, "unite right error\n");
goto end;
}
}
if (rkvpss_debug >= 2) {
us = ktime_us_delta(ktime_get(), t);
v4l2_info(&ofl->v4l2_dev,
"%s end, time:%lldus\n", __func__, us);
}
ns = ktime_get_ns();
ofl->dev_rate[cfg->dev_id].out_rate = ns - ofl->dev_rate[cfg->dev_id].out_timestamp;
ofl->dev_rate[cfg->dev_id].out_timestamp = ns;
ofl->dev_rate[cfg->dev_id].sequence = cfg->sequence;
ofl->dev_rate[cfg->dev_id].delay = ofl->dev_rate[cfg->dev_id].out_timestamp -
ofl->dev_rate[cfg->dev_id].in_timestamp;
end:
return ret;
}
static long rkvpss_ofl_ioctl(struct file *file, void *fh,
bool valid_prio, unsigned int cmd, void *arg)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
long ret = 0;
bool unite;
ofl->pm_need_wait = true;
if (!arg) {
ret = -EINVAL;
goto out;
}
switch (cmd) {
case RKVPSS_CMD_MODULE_SEL:
ret = rkvpss_module_sel(file, arg);
break;
case RKVPSS_CMD_MODULE_GET:
ret = rkvpss_module_get(file, arg);
break;
case RKVPSS_CMD_FRAME_HANDLE:
ret = rkvpss_prepare_run(file, arg);
break;
case RKVPSS_CMD_BUF_ADD:
ret = rkvpss_ofl_buf_add(file, arg);
break;
case RKVPSS_CMD_BUF_DEL:
rkvpss_ofl_buf_del(file, arg);
break;
case RKVPSS_CMD_CHECKPARAMS:
ret = rkvpss_check_params(file, arg, &unite);
break;
default:
ret = -EFAULT;
}
out:
/* notify hw suspend */
if (ofl->hw->is_suspend)
complete(&ofl->pm_cmpl);
ofl->pm_need_wait = false;
return ret;
}
static const struct v4l2_ioctl_ops offline_ioctl_ops = {
.vidioc_default = rkvpss_ofl_ioctl,
};
static int ofl_open(struct file *file)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
int ret;
ret = v4l2_fh_open(file);
if (ret)
goto end;
mutex_lock(&ofl->hw->dev_lock);
ret = pm_runtime_get_sync(ofl->hw->dev);
mutex_unlock(&ofl->hw->dev_lock);
if (ret < 0)
v4l2_fh_release(file);
end:
v4l2_dbg(1, rkvpss_debug, &ofl->v4l2_dev,
"%s file:%p ret:%d\n", __func__, file, ret);
return (ret > 0) ? 0 : ret;
}
static int ofl_release(struct file *file)
{
struct rkvpss_offline_dev *ofl = video_drvdata(file);
v4l2_dbg(1, rkvpss_debug, &ofl->v4l2_dev,
"%s file:%p\n", __func__, file);
v4l2_fh_release(file);
buf_del(file, 0, 0, true, false);
mutex_lock(&ofl->hw->dev_lock);
pm_runtime_put_sync(ofl->hw->dev);
mutex_unlock(&ofl->hw->dev_lock);
return 0;
}
static const struct v4l2_file_operations offline_fops = {
.owner = THIS_MODULE,
.open = ofl_open,
.release = ofl_release,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = video_ioctl2,
#endif
};
static const struct video_device offline_videodev = {
.name = "rkvpss-offline",
.vfl_dir = VFL_DIR_RX,
.fops = &offline_fops,
.ioctl_ops = &offline_ioctl_ops,
.minor = -1,
.release = video_device_release_empty,
};
void rkvpss_offline_irq(struct rkvpss_hw_dev *hw, u32 irq)
{
struct rkvpss_offline_dev *ofl = &hw->ofl_dev;
v4l2_dbg(3, rkvpss_debug, &ofl->v4l2_dev,
"%s 0x%x\n", __func__, irq);
if (!completion_done(&ofl->cmpl))
complete(&ofl->cmpl);
}
int rkvpss_register_offline(struct rkvpss_hw_dev *hw)
{
struct rkvpss_offline_dev *ofl = &hw->ofl_dev;
struct v4l2_device *v4l2_dev;
struct video_device *vfd;
int ret;
ofl->hw = hw;
v4l2_dev = &ofl->v4l2_dev;
strscpy(v4l2_dev->name, offline_videodev.name, sizeof(v4l2_dev->name));
ret = v4l2_device_register(hw->dev, v4l2_dev);
if (ret)
return ret;
mutex_init(&ofl->apilock);
ofl->vfd = offline_videodev;
ofl->mode_sel_en = true;
vfd = &ofl->vfd;
vfd->device_caps = V4L2_CAP_STREAMING;
vfd->lock = &ofl->apilock;
vfd->v4l2_dev = v4l2_dev;
ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0);
if (ret) {
v4l2_err(v4l2_dev, "Failed to register video device\n");
goto unreg_v4l2;
}
video_set_drvdata(vfd, ofl);
INIT_LIST_HEAD(&ofl->list);
INIT_LIST_HEAD(&ofl->cfginfo_list);
mutex_init(&ofl->ofl_lock);
rkvpss_offline_proc_init(ofl);
ofl->pm_need_wait = false;
init_completion(&ofl->pm_cmpl);
return 0;
unreg_v4l2:
mutex_destroy(&ofl->apilock);
v4l2_device_unregister(v4l2_dev);
return ret;
}
void rkvpss_unregister_offline(struct rkvpss_hw_dev *hw)
{
mutex_destroy(&hw->ofl_dev.apilock);
video_unregister_device(&hw->ofl_dev.vfd);
v4l2_device_unregister(&hw->ofl_dev.v4l2_dev);
mutex_destroy(&hw->ofl_dev.ofl_lock);
rkvpss_offline_proc_cleanup(&hw->ofl_dev);
}