// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2023 Rockchip Electronics Co., Ltd. */ #include #include #include #include #include #include #include #include #include #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); }