Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,15 @@
config VIDEO_ROCKCHIP_RGA
tristate "Rockchip Raster 2d Graphic Acceleration Unit"
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_ROCKCHIP || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select VIDEOBUF2_DMA_SG
select V4L2_MEM2MEM_DEV
help
This is a v4l2 driver for Rockchip SOC RGA 2d graphics accelerator.
Rockchip RGA is a separate 2D raster graphic acceleration unit.
It accelerates 2D graphics operations, such as point/line drawing,
image scaling, rotation, BitBLT, alpha blending and image blur/sharpness.
To compile this driver as a module choose m here.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
rockchip-rga-objs := rga.o rga-hw.o rga3-hw.o rga-buf.o
obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip-rga.o
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 Rockchip Electronics Co., Ltd.
* Author: Jacob Chen <jacob-chen@iotwrt.com>
*/
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
#include "rga.h"
static ssize_t fill_descriptors(struct rga_dma_desc *desc, size_t max_desc,
struct sg_table *sgt)
{
struct sg_dma_page_iter iter;
struct rga_dma_desc *tmp = desc;
size_t n_desc = 0;
dma_addr_t addr;
for_each_sgtable_dma_page(sgt, &iter, 0) {
if (n_desc > max_desc)
return -EINVAL;
addr = sg_page_iter_dma_address(&iter);
tmp->addr = lower_32_bits(addr);
tmp++;
n_desc++;
}
return n_desc;
}
static int
rga_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct rga_ctx *ctx = vb2_get_drv_priv(vq);
struct rga_frame *f = rga_get_frame(ctx, vq->type);
const struct v4l2_pix_format_mplane *pix_fmt;
int i;
if (IS_ERR(f))
return PTR_ERR(f);
pix_fmt = &f->pix;
if (*nplanes) {
if (*nplanes != pix_fmt->num_planes)
return -EINVAL;
for (i = 0; i < pix_fmt->num_planes; i++)
if (sizes[i] < pix_fmt->plane_fmt[i].sizeimage)
return -EINVAL;
return 0;
}
*nplanes = pix_fmt->num_planes;
for (i = 0; i < pix_fmt->num_planes; i++)
sizes[i] = pix_fmt->plane_fmt[i].sizeimage;
return 0;
}
static int rga_buf_init(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct rga_vb_buffer *rbuf = vb_to_rga(vbuf);
struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct rockchip_rga *rga = ctx->rga;
struct rga_frame *f = rga_get_frame(ctx, vb->vb2_queue->type);
size_t n_desc = 0;
u32 size = 0;
u8 i;
if (IS_ERR(f))
return PTR_ERR(f);
if (!rga_has_internal_iommu(rga))
return 0;
for (i = 0; i < f->pix.num_planes; i++)
size += f->pix.plane_fmt[i].sizeimage;
n_desc = DIV_ROUND_UP(size, PAGE_SIZE);
rbuf->n_desc = n_desc;
rbuf->dma_desc = dma_alloc_coherent(rga->dev,
rbuf->n_desc * sizeof(*rbuf->dma_desc),
&rbuf->dma_desc_pa, GFP_KERNEL);
if (!rbuf->dma_desc)
return -ENOMEM;
return 0;
}
static int get_plane_offset(struct rga_frame *f,
const struct v4l2_format_info *info,
int plane)
{
u32 stride = f->pix.plane_fmt[0].bytesperline;
if (plane == 0)
return 0;
if (plane == 1)
return stride * f->pix.height;
if (plane == 2)
return stride * f->pix.height +
(stride * f->pix.height / info->hdiv / info->vdiv);
return -EINVAL;
}
static int rga_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct rga_vb_buffer *rbuf = vb_to_rga(vbuf);
struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct rga_frame *f = rga_get_frame(ctx, vb->vb2_queue->type);
ssize_t n_desc = 0;
size_t curr_desc = 0;
int i;
const struct v4l2_format_info *info;
dma_addr_t dma_addrs[VIDEO_MAX_PLANES];
if (IS_ERR(f))
return PTR_ERR(f);
if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
if (vbuf->field == V4L2_FIELD_ANY)
vbuf->field = V4L2_FIELD_NONE;
if (vbuf->field != V4L2_FIELD_NONE)
return -EINVAL;
}
for (i = 0; i < vb->num_planes; i++) {
vb2_set_plane_payload(vb, i, f->pix.plane_fmt[i].sizeimage);
if (rga_has_internal_iommu(ctx->rga)) {
/* Create local MMU table for RGA */
n_desc = fill_descriptors(&rbuf->dma_desc[curr_desc],
rbuf->n_desc - curr_desc,
vb2_dma_sg_plane_desc(vb, i));
if (n_desc < 0) {
v4l2_err(&ctx->rga->v4l2_dev,
"Failed to map video buffer to RGA\n");
return n_desc;
}
dma_addrs[i] = curr_desc << PAGE_SHIFT;
curr_desc += n_desc;
} else {
dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb, i);
}
}
/* Fill the remaining planes */
info = v4l2_format_info(f->pix.pixelformat);
for (i = info->mem_planes; i < info->comp_planes; i++)
dma_addrs[i] = dma_addrs[0] + get_plane_offset(f, info, i);
rbuf->dma_addrs.y_addr = dma_addrs[0];
rbuf->dma_addrs.u_addr = dma_addrs[1];
rbuf->dma_addrs.v_addr = dma_addrs[2];
return 0;
}
static void rga_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
}
static void rga_buf_cleanup(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct rga_vb_buffer *rbuf = vb_to_rga(vbuf);
struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct rockchip_rga *rga = ctx->rga;
if (!rga_has_internal_iommu(rga))
return;
dma_free_coherent(rga->dev, rbuf->n_desc * sizeof(*rbuf->dma_desc),
rbuf->dma_desc, rbuf->dma_desc_pa);
}
static void rga_buf_return_buffers(struct vb2_queue *q,
enum vb2_buffer_state state)
{
struct rga_ctx *ctx = vb2_get_drv_priv(q);
struct vb2_v4l2_buffer *vbuf;
for (;;) {
if (V4L2_TYPE_IS_OUTPUT(q->type))
vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
else
vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
if (!vbuf)
break;
v4l2_m2m_buf_done(vbuf, state);
}
}
static int rga_buf_prepare_streaming(struct vb2_queue *q)
{
struct rga_ctx *ctx = vb2_get_drv_priv(q);
const struct rga_hw *hw = ctx->rga->hw;
int ret;
/* It's safe to check the streaming state of the other queue,
* as the streamon ioctl's can't race due to the lock set in
* the queue_init function.
*/
if ((V4L2_TYPE_IS_OUTPUT(q->type) &&
vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx))) ||
(V4L2_TYPE_IS_CAPTURE(q->type) &&
vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx)))) {
/*
* As the other side is already streaming,
* check that the max scaling factor isn't exceeded.
*/
ret = rga_check_scaling(hw, &ctx->in.crop, &ctx->out.crop,
ctx->rotate);
if (ret < 0)
return ret;
}
return 0;
}
static int rga_buf_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct rga_ctx *ctx = vb2_get_drv_priv(q);
struct rockchip_rga *rga = ctx->rga;
int ret;
ret = pm_runtime_resume_and_get(rga->dev);
if (ret < 0) {
rga_buf_return_buffers(q, VB2_BUF_STATE_QUEUED);
return ret;
}
if (V4L2_TYPE_IS_OUTPUT(q->type))
ctx->osequence = 0;
else
ctx->csequence = 0;
return 0;
}
static void rga_buf_stop_streaming(struct vb2_queue *q)
{
struct rga_ctx *ctx = vb2_get_drv_priv(q);
struct rockchip_rga *rga = ctx->rga;
rga_buf_return_buffers(q, VB2_BUF_STATE_ERROR);
pm_runtime_put(rga->dev);
}
const struct vb2_ops rga_qops = {
.queue_setup = rga_queue_setup,
.buf_init = rga_buf_init,
.buf_prepare = rga_buf_prepare,
.buf_queue = rga_buf_queue,
.buf_cleanup = rga_buf_cleanup,
.prepare_streaming = rga_buf_prepare_streaming,
.start_streaming = rga_buf_start_streaming,
.stop_streaming = rga_buf_stop_streaming,
};
@@ -0,0 +1,615 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Jacob Chen <jacob-chen@iotwrt.com>
*/
#include <linux/pm_runtime.h>
#include "rga-hw.h"
#include "rga.h"
enum e_rga_start_pos {
LT = 0,
LB = 1,
RT = 2,
RB = 3,
};
struct rga_corners_addrs {
struct rga_addrs left_top;
struct rga_addrs right_top;
struct rga_addrs left_bottom;
struct rga_addrs right_bottom;
};
static unsigned int rga_get_scaling(unsigned int src, unsigned int dst)
{
/*
* The rga hw scaling factor is a normalized inverse of the
* scaling factor.
* For example: When source width is 100 and destination width is 200
* (scaling of 2x), then the hw factor is NC * 100 / 200.
* The normalization factor (NC) is 2^16 = 0x10000.
*/
return (src > dst) ? ((dst << 16) / src) : ((src << 16) / dst);
}
static struct rga_corners_addrs
rga_get_corner_addrs(struct rga_frame *frm, struct rga_addrs *addrs,
unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
struct rga_corners_addrs corner_addrs;
struct rga_addrs *lt, *lb, *rt, *rb;
const struct v4l2_format_info *format_info;
unsigned int x_div = 0,
y_div = 0, y_stride = 0, uv_stride = 0, pixel_width = 0;
lt = &corner_addrs.left_top;
lb = &corner_addrs.left_bottom;
rt = &corner_addrs.right_top;
rb = &corner_addrs.right_bottom;
format_info = v4l2_format_info(frm->pix.pixelformat);
/* x_div is only used for the u/v planes.
* When the format doesn't have these, use 1 to avoid a division by zero.
*/
if (format_info->bpp[1])
x_div = format_info->hdiv * format_info->bpp_div[1] /
format_info->bpp[1];
else
x_div = 1;
y_div = format_info->vdiv;
y_stride = frm->pix.plane_fmt[0].bytesperline;
uv_stride = y_stride / x_div;
pixel_width = y_stride / frm->pix.width;
lt->y_addr = addrs->y_addr + y * y_stride + x * pixel_width;
lt->u_addr = addrs->u_addr + (y / y_div) * uv_stride + x / x_div;
lt->v_addr = addrs->v_addr + (y / y_div) * uv_stride + x / x_div;
lb->y_addr = lt->y_addr + (h - 1) * y_stride;
lb->u_addr = lt->u_addr + (h / y_div - 1) * uv_stride;
lb->v_addr = lt->v_addr + (h / y_div - 1) * uv_stride;
rt->y_addr = lt->y_addr + (w - 1) * pixel_width;
rt->u_addr = lt->u_addr + w / x_div - 1;
rt->v_addr = lt->v_addr + w / x_div - 1;
rb->y_addr = lb->y_addr + (w - 1) * pixel_width;
rb->u_addr = lb->u_addr + w / x_div - 1;
rb->v_addr = lb->v_addr + w / x_div - 1;
return corner_addrs;
}
static struct rga_addrs *rga_lookup_draw_pos(struct rga_corners_addrs *corner_addrs,
u32 rotate_mode,
u32 mirr_mode)
{
static enum e_rga_start_pos rot_mir_point_matrix[4][4] = {
{
LT, RT, LB, RB,
},
{
RT, LT, RB, LB,
},
{
RB, LB, RT, LT,
},
{
LB, RB, LT, RT,
},
};
if (!corner_addrs)
return NULL;
switch (rot_mir_point_matrix[rotate_mode][mirr_mode]) {
case LT:
return &corner_addrs->left_top;
case LB:
return &corner_addrs->left_bottom;
case RT:
return &corner_addrs->right_top;
case RB:
return &corner_addrs->right_bottom;
}
return NULL;
}
static void rga_cmd_set_src_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC_BASE - RGA_MODE_BASE_REG;
dest[reg >> 2] = dma_addr >> 4;
reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG;
dest[reg >> 2] |= 0x7;
}
static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_SRC1_BASE - RGA_MODE_BASE_REG;
dest[reg >> 2] = dma_addr >> 4;
reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG;
dest[reg >> 2] |= 0x7 << 4;
}
static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, dma_addr_t dma_addr)
{
u32 *dest = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA_MMU_DST_BASE - RGA_MODE_BASE_REG;
dest[reg >> 2] = dma_addr >> 4;
reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG;
dest[reg >> 2] |= 0x7 << 8;
}
static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
{
struct rockchip_rga *rga = ctx->rga;
u32 *dest = ctx->cmdbuf_virt;
unsigned int scale_dst_w, scale_dst_h;
unsigned int src_h, src_w, dst_h, dst_w;
union rga_src_info src_info;
union rga_dst_info dst_info;
union rga_src_x_factor x_factor;
union rga_src_y_factor y_factor;
union rga_src_vir_info src_vir_info;
union rga_src_act_info src_act_info;
union rga_dst_vir_info dst_vir_info;
union rga_dst_act_info dst_act_info;
u32 in_stride, out_stride;
struct rga_fmt *in_fmt = ctx->in.fmt;
struct rga_fmt *out_fmt = ctx->out.fmt;
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
dst_h = ctx->out.crop.height;
dst_w = ctx->out.crop.width;
src_info.val = dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2];
dst_info.val = dest[(RGA_DST_INFO - RGA_MODE_BASE_REG) >> 2];
x_factor.val = dest[(RGA_SRC_X_FACTOR - RGA_MODE_BASE_REG) >> 2];
y_factor.val = dest[(RGA_SRC_Y_FACTOR - RGA_MODE_BASE_REG) >> 2];
src_vir_info.val = dest[(RGA_SRC_VIR_INFO - RGA_MODE_BASE_REG) >> 2];
src_act_info.val = dest[(RGA_SRC_ACT_INFO - RGA_MODE_BASE_REG) >> 2];
dst_vir_info.val = dest[(RGA_DST_VIR_INFO - RGA_MODE_BASE_REG) >> 2];
dst_act_info.val = dest[(RGA_DST_ACT_INFO - RGA_MODE_BASE_REG) >> 2];
src_info.data.format = in_fmt->hw_format;
src_info.data.swap = in_fmt->color_swap;
dst_info.data.format = out_fmt->hw_format;
dst_info.data.swap = out_fmt->color_swap;
/*
* CSC mode must only be set when the colorspace families differ between
* input and output. It must remain unset (zeroed) if both are the same.
*/
if (RGA_COLOR_FMT_IS_YUV(in_fmt->hw_format) &&
RGA_COLOR_FMT_IS_RGB(out_fmt->hw_format)) {
switch (ctx->in.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
break;
default:
src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT601_R0;
break;
}
}
if (RGA_COLOR_FMT_IS_RGB(in_fmt->hw_format) &&
RGA_COLOR_FMT_IS_YUV(out_fmt->hw_format)) {
switch (ctx->out.pix.colorspace) {
case V4L2_COLORSPACE_REC709:
dst_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0;
break;
default:
dst_info.data.csc_mode = RGA_DST_CSC_MODE_BT601_R0;
break;
}
}
if (ctx->vflip)
src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X;
if (ctx->hflip)
src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_Y;
switch (ctx->rotate) {
case 90:
src_info.data.rot_mode = RGA_SRC_ROT_MODE_90_DEGREE;
break;
case 180:
src_info.data.rot_mode = RGA_SRC_ROT_MODE_180_DEGREE;
break;
case 270:
src_info.data.rot_mode = RGA_SRC_ROT_MODE_270_DEGREE;
break;
default:
src_info.data.rot_mode = RGA_SRC_ROT_MODE_0_DEGREE;
break;
}
/*
* Calculate the up/down scaling mode/factor.
*
* RGA used to scale the picture first, and then rotate second,
* so we need to swap the w/h when rotate degree is 90/270.
*/
if (src_info.data.rot_mode == RGA_SRC_ROT_MODE_90_DEGREE ||
src_info.data.rot_mode == RGA_SRC_ROT_MODE_270_DEGREE) {
if (rga->version.major == 0 || rga->version.minor == 0) {
if (dst_w == src_h)
src_h -= 8;
if (abs(src_w - dst_h) < 16)
src_w -= 16;
}
scale_dst_h = dst_w;
scale_dst_w = dst_h;
} else {
scale_dst_w = dst_w;
scale_dst_h = dst_h;
}
if (src_w == scale_dst_w) {
src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_NO;
x_factor.val = 0;
} else if (src_w > scale_dst_w) {
src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_DOWN;
x_factor.data.down_scale_factor =
rga_get_scaling(src_w, scale_dst_w) + 1;
} else {
src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_UP;
x_factor.data.up_scale_factor =
rga_get_scaling(src_w - 1, scale_dst_w - 1);
}
if (src_h == scale_dst_h) {
src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_NO;
y_factor.val = 0;
} else if (src_h > scale_dst_h) {
src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_DOWN;
y_factor.data.down_scale_factor =
rga_get_scaling(src_h, scale_dst_h) + 1;
} else {
src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_UP;
y_factor.data.up_scale_factor =
rga_get_scaling(src_h - 1, scale_dst_h - 1);
}
/*
* Calculate the framebuffer virtual strides and active size,
* note that the step of vir_stride / vir_width is 4 byte words
*/
in_stride = ctx->in.pix.plane_fmt[0].bytesperline;
src_vir_info.data.vir_stride = in_stride >> 2;
src_vir_info.data.vir_width = in_stride >> 2;
src_act_info.data.act_height = src_h - 1;
src_act_info.data.act_width = src_w - 1;
out_stride = ctx->out.pix.plane_fmt[0].bytesperline;
dst_vir_info.data.vir_stride = out_stride >> 2;
dst_act_info.data.act_height = dst_h - 1;
dst_act_info.data.act_width = dst_w - 1;
dest[(RGA_SRC_X_FACTOR - RGA_MODE_BASE_REG) >> 2] = x_factor.val;
dest[(RGA_SRC_Y_FACTOR - RGA_MODE_BASE_REG) >> 2] = y_factor.val;
dest[(RGA_SRC_VIR_INFO - RGA_MODE_BASE_REG) >> 2] = src_vir_info.val;
dest[(RGA_SRC_ACT_INFO - RGA_MODE_BASE_REG) >> 2] = src_act_info.val;
dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2] = src_info.val;
dest[(RGA_DST_VIR_INFO - RGA_MODE_BASE_REG) >> 2] = dst_vir_info.val;
dest[(RGA_DST_ACT_INFO - RGA_MODE_BASE_REG) >> 2] = dst_act_info.val;
dest[(RGA_DST_INFO - RGA_MODE_BASE_REG) >> 2] = dst_info.val;
}
static void rga_cmd_set_src_info(struct rga_ctx *ctx,
struct rga_addrs *addrs)
{
struct rga_corners_addrs src_corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int src_h, src_w, src_x, src_y;
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
src_x = ctx->in.crop.left;
src_y = ctx->in.crop.top;
/*
* Calculate the source framebuffer base address with offset pixel.
*/
src_corner_addrs = rga_get_corner_addrs(&ctx->in, addrs,
src_x, src_y, src_w, src_h);
dest[(RGA_SRC_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
src_corner_addrs.left_top.y_addr;
dest[(RGA_SRC_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
src_corner_addrs.left_top.u_addr;
dest[(RGA_SRC_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
src_corner_addrs.left_top.v_addr;
}
static void rga_cmd_set_dst_info(struct rga_ctx *ctx,
struct rga_addrs *addrs)
{
struct rga_addrs *dst_addrs;
struct rga_corners_addrs corner_addrs;
u32 *dest = ctx->cmdbuf_virt;
unsigned int dst_h, dst_w, dst_x, dst_y;
unsigned int mir_mode = 0;
unsigned int rot_mode = 0;
dst_h = ctx->out.crop.height;
dst_w = ctx->out.crop.width;
dst_x = ctx->out.crop.left;
dst_y = ctx->out.crop.top;
if (ctx->vflip)
mir_mode |= RGA_SRC_MIRR_MODE_X;
if (ctx->hflip)
mir_mode |= RGA_SRC_MIRR_MODE_Y;
switch (ctx->rotate) {
case 90:
rot_mode = RGA_SRC_ROT_MODE_90_DEGREE;
break;
case 180:
rot_mode = RGA_SRC_ROT_MODE_180_DEGREE;
break;
case 270:
rot_mode = RGA_SRC_ROT_MODE_270_DEGREE;
break;
default:
rot_mode = RGA_SRC_ROT_MODE_0_DEGREE;
break;
}
/*
* Configure the dest framebuffer base address with pixel offset.
*/
corner_addrs = rga_get_corner_addrs(&ctx->out, addrs, dst_x, dst_y, dst_w, dst_h);
dst_addrs = rga_lookup_draw_pos(&corner_addrs, rot_mode, mir_mode);
dest[(RGA_DST_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
dst_addrs->y_addr;
dest[(RGA_DST_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
dst_addrs->u_addr;
dest[(RGA_DST_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] =
dst_addrs->v_addr;
}
static void rga_cmd_set_mode(struct rga_ctx *ctx)
{
u32 *dest = ctx->cmdbuf_virt;
union rga_mode_ctrl mode;
union rga_alpha_ctrl0 alpha_ctrl0;
union rga_alpha_ctrl1 alpha_ctrl1;
mode.val = 0;
alpha_ctrl0.val = 0;
alpha_ctrl1.val = 0;
mode.data.gradient_sat = 1;
mode.data.render = RGA_MODE_RENDER_BITBLT;
mode.data.bitblt = RGA_MODE_BITBLT_MODE_SRC_TO_DST;
/* disable alpha blending */
dest[(RGA_ALPHA_CTRL0 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl0.val;
dest[(RGA_ALPHA_CTRL1 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl1.val;
dest[(RGA_MODE_CTRL - RGA_MODE_BASE_REG) >> 2] = mode.val;
}
static void rga_cmd_set(struct rga_ctx *ctx,
struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
{
struct rockchip_rga *rga = ctx->rga;
rga_cmd_set_src_addr(ctx, src->dma_desc_pa);
/*
* Due to hardware bug,
* src1 mmu also should be configured when using alpha blending.
*/
rga_cmd_set_src1_addr(ctx, dst->dma_desc_pa);
rga_cmd_set_dst_addr(ctx, dst->dma_desc_pa);
rga_cmd_set_src_info(ctx, &src->dma_addrs);
rga_cmd_set_dst_info(ctx, &dst->dma_addrs);
rga_write(rga, RGA_CMD_BASE, ctx->cmdbuf_phy);
/* sync CMD buf for RGA */
dma_sync_single_for_device(rga->dev, ctx->cmdbuf_phy,
PAGE_SIZE, DMA_BIDIRECTIONAL);
}
static void rga_hw_setup_cmdbuf(struct rga_ctx *ctx)
{
memset(ctx->cmdbuf_virt, 0, RGA_CMDBUF_SIZE);
rga_cmd_set_mode(ctx);
rga_cmd_set_trans_info(ctx);
}
static void rga_hw_start(struct rockchip_rga *rga,
struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
{
struct rga_ctx *ctx = rga->curr;
rga_cmd_set(ctx, src, dst);
rga_write(rga, RGA_SYS_CTRL, 0x00);
rga_write(rga, RGA_SYS_CTRL, 0x22);
rga_write(rga, RGA_INT, 0x600);
rga_write(rga, RGA_CMD_CTRL, 0x1);
}
static bool rga_handle_irq(struct rockchip_rga *rga)
{
int intr;
intr = rga_read(rga, RGA_INT) & 0xf;
rga_mod(rga, RGA_INT, intr << 4, 0xf << 4);
return intr & RGA_INT_COMMAND_FINISHED;
}
static void rga_get_version(struct rockchip_rga *rga)
{
rga->version.major = (rga_read(rga, RGA_VERSION_INFO) >> 24) & 0xFF;
rga->version.minor = (rga_read(rga, RGA_VERSION_INFO) >> 20) & 0x0F;
}
static struct rga_fmt formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB32,
.color_swap = RGA_COLOR_ALPHA_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
},
{
.fourcc = V4L2_PIX_FMT_ABGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR8888,
},
{
.fourcc = V4L2_PIX_FMT_XBGR32,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_XBGR8888,
},
{
.fourcc = V4L2_PIX_FMT_RGB24,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
},
{
.fourcc = V4L2_PIX_FMT_BGR24,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_RGB888,
},
{
.fourcc = V4L2_PIX_FMT_ARGB444,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR4444,
},
{
.fourcc = V4L2_PIX_FMT_ARGB555,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_ABGR1555,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.color_swap = RGA_COLOR_RB_SWAP,
.hw_format = RGA_COLOR_FMT_BGR565,
},
{
.fourcc = V4L2_PIX_FMT_NV21,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
},
{
.fourcc = V4L2_PIX_FMT_NV61,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
},
{
.fourcc = V4L2_PIX_FMT_NV12,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
},
{
.fourcc = V4L2_PIX_FMT_NV12M,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420SP,
},
{
.fourcc = V4L2_PIX_FMT_NV16,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422SP,
},
{
.fourcc = V4L2_PIX_FMT_YUV420,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
},
{
.fourcc = V4L2_PIX_FMT_YUV422P,
.color_swap = RGA_COLOR_NONE_SWAP,
.hw_format = RGA_COLOR_FMT_YUV422P,
},
{
.fourcc = V4L2_PIX_FMT_YVU420,
.color_swap = RGA_COLOR_UV_SWAP,
.hw_format = RGA_COLOR_FMT_YUV420P,
},
};
static void *rga_adjust_and_map_format(struct rga_ctx *ctx,
struct v4l2_pix_format_mplane *format,
bool is_output)
{
unsigned int i;
if (!format)
return &formats[0];
for (i = 0; i < ARRAY_SIZE(formats); i++) {
if (formats[i].fourcc == format->pixelformat)
return &formats[i];
}
format->pixelformat = formats[0].fourcc;
return &formats[0];
}
static int rga_enum_format(struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
f->pixelformat = formats[f->index].fourcc;
return 0;
}
const struct rga_hw rga2_hw = {
.card_type = "rga2",
.has_internal_iommu = true,
.cmdbuf_size = RGA_CMDBUF_SIZE,
.min_width = MIN_WIDTH,
.max_width = MAX_WIDTH,
.min_height = MIN_HEIGHT,
.max_height = MAX_HEIGHT,
.max_scaling_factor = MAX_SCALING_FACTOR,
.stride_alignment = 4,
.features = RGA_FEATURE_FLIP
| RGA_FEATURE_ROTATE
| RGA_FEATURE_BG_COLOR,
.setup_cmdbuf = rga_hw_setup_cmdbuf,
.start = rga_hw_start,
.handle_irq = rga_handle_irq,
.get_version = rga_get_version,
.adjust_and_map_format = rga_adjust_and_map_format,
.enum_format = rga_enum_format,
};
@@ -0,0 +1,442 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Jacob Chen <jacob-chen@iotwrt.com>
*/
#ifndef __RGA_HW_H__
#define __RGA_HW_H__
#include <linux/types.h>
#define RGA_CMDBUF_SIZE 0x80
/* Hardware limits */
#define MAX_WIDTH 8192
#define MAX_HEIGHT 8192
#define MIN_WIDTH 34
#define MIN_HEIGHT 34
#define MAX_SCALING_FACTOR 16
#define RGA_TIMEOUT 500
/* Registers address */
#define RGA_SYS_CTRL 0x0000
#define RGA_CMD_CTRL 0x0004
#define RGA_CMD_BASE 0x0008
#define RGA_INT 0x0010
#define RGA_MMU_CTRL0 0x0014
#define RGA_VERSION_INFO 0x0028
#define RGA_MODE_BASE_REG 0x0100
#define RGA_MODE_MAX_REG 0x017C
#define RGA_MODE_CTRL 0x0100
#define RGA_SRC_INFO 0x0104
#define RGA_SRC_Y_RGB_BASE_ADDR 0x0108
#define RGA_SRC_CB_BASE_ADDR 0x010c
#define RGA_SRC_CR_BASE_ADDR 0x0110
#define RGA_SRC1_RGB_BASE_ADDR 0x0114
#define RGA_SRC_VIR_INFO 0x0118
#define RGA_SRC_ACT_INFO 0x011c
#define RGA_SRC_X_FACTOR 0x0120
#define RGA_SRC_Y_FACTOR 0x0124
#define RGA_SRC_BG_COLOR 0x0128
#define RGA_SRC_FG_COLOR 0x012c
#define RGA_SRC_TR_COLOR0 0x0130
#define RGA_SRC_TR_COLOR1 0x0134
#define RGA_DST_INFO 0x0138
#define RGA_DST_Y_RGB_BASE_ADDR 0x013c
#define RGA_DST_CB_BASE_ADDR 0x0140
#define RGA_DST_CR_BASE_ADDR 0x0144
#define RGA_DST_VIR_INFO 0x0148
#define RGA_DST_ACT_INFO 0x014c
#define RGA_ALPHA_CTRL0 0x0150
#define RGA_ALPHA_CTRL1 0x0154
#define RGA_FADING_CTRL 0x0158
#define RGA_PAT_CON 0x015c
#define RGA_ROP_CON0 0x0160
#define RGA_ROP_CON1 0x0164
#define RGA_MASK_BASE 0x0168
#define RGA_MMU_CTRL1 0x016C
#define RGA_MMU_SRC_BASE 0x0170
#define RGA_MMU_SRC1_BASE 0x0174
#define RGA_MMU_DST_BASE 0x0178
/* Registers value */
#define RGA_MODE_RENDER_BITBLT 0
#define RGA_MODE_RENDER_COLOR_PALETTE 1
#define RGA_MODE_RENDER_RECTANGLE_FILL 2
#define RGA_MODE_RENDER_UPDATE_PALETTE_LUT_RAM 3
#define RGA_MODE_BITBLT_MODE_SRC_TO_DST 0
#define RGA_MODE_BITBLT_MODE_SRC_SRC1_TO_DST 1
#define RGA_MODE_CF_ROP4_SOLID 0
#define RGA_MODE_CF_ROP4_PATTERN 1
#define RGA_COLOR_FMT_ABGR8888 0
#define RGA_COLOR_FMT_XBGR8888 1
#define RGA_COLOR_FMT_RGB888 2
#define RGA_COLOR_FMT_BGR565 4
#define RGA_COLOR_FMT_ABGR1555 5
#define RGA_COLOR_FMT_ABGR4444 6
#define RGA_COLOR_FMT_YUV422SP 8
#define RGA_COLOR_FMT_YUV422P 9
#define RGA_COLOR_FMT_YUV420SP 10
#define RGA_COLOR_FMT_YUV420P 11
/* SRC_COLOR Palette */
#define RGA_COLOR_FMT_CP_1BPP 12
#define RGA_COLOR_FMT_CP_2BPP 13
#define RGA_COLOR_FMT_CP_4BPP 14
#define RGA_COLOR_FMT_CP_8BPP 15
#define RGA_COLOR_FMT_MASK 15
#define RGA_COLOR_FMT_IS_YUV(fmt) \
(((fmt) >= RGA_COLOR_FMT_YUV422SP) && ((fmt) < RGA_COLOR_FMT_CP_1BPP))
#define RGA_COLOR_FMT_IS_RGB(fmt) \
((fmt) < RGA_COLOR_FMT_YUV422SP)
#define RGA_COLOR_NONE_SWAP 0
#define RGA_COLOR_RB_SWAP 1
#define RGA_COLOR_ALPHA_SWAP 2
#define RGA_COLOR_UV_SWAP 4
#define RGA_SRC_CSC_MODE_BYPASS 0
#define RGA_SRC_CSC_MODE_BT601_R0 1
#define RGA_SRC_CSC_MODE_BT601_R1 2
#define RGA_SRC_CSC_MODE_BT709_R0 3
#define RGA_SRC_CSC_MODE_BT709_R1 4
#define RGA_SRC_ROT_MODE_0_DEGREE 0
#define RGA_SRC_ROT_MODE_90_DEGREE 1
#define RGA_SRC_ROT_MODE_180_DEGREE 2
#define RGA_SRC_ROT_MODE_270_DEGREE 3
#define RGA_SRC_MIRR_MODE_NO 0
#define RGA_SRC_MIRR_MODE_X 1
#define RGA_SRC_MIRR_MODE_Y 2
#define RGA_SRC_MIRR_MODE_X_Y 3
#define RGA_SRC_HSCL_MODE_NO 0
#define RGA_SRC_HSCL_MODE_DOWN 1
#define RGA_SRC_HSCL_MODE_UP 2
#define RGA_SRC_VSCL_MODE_NO 0
#define RGA_SRC_VSCL_MODE_DOWN 1
#define RGA_SRC_VSCL_MODE_UP 2
#define RGA_SRC_TRANS_ENABLE_R 1
#define RGA_SRC_TRANS_ENABLE_G 2
#define RGA_SRC_TRANS_ENABLE_B 4
#define RGA_SRC_TRANS_ENABLE_A 8
#define RGA_SRC_BIC_COE_SELEC_CATROM 0
#define RGA_SRC_BIC_COE_SELEC_MITCHELL 1
#define RGA_SRC_BIC_COE_SELEC_HERMITE 2
#define RGA_SRC_BIC_COE_SELEC_BSPLINE 3
#define RGA_DST_DITHER_MODE_888_TO_666 0
#define RGA_DST_DITHER_MODE_888_TO_565 1
#define RGA_DST_DITHER_MODE_888_TO_555 2
#define RGA_DST_DITHER_MODE_888_TO_444 3
#define RGA_DST_CSC_MODE_BYPASS 0
#define RGA_DST_CSC_MODE_BT601_R0 1
#define RGA_DST_CSC_MODE_BT601_R1 2
#define RGA_DST_CSC_MODE_BT709_R0 3
#define RGA_ALPHA_ROP_MODE_2 0
#define RGA_ALPHA_ROP_MODE_3 1
#define RGA_ALPHA_ROP_MODE_4 2
#define RGA_ALPHA_SELECT_ALPHA 0
#define RGA_ALPHA_SELECT_ROP 1
#define RGA_ALPHA_MASK_BIG_ENDIAN 0
#define RGA_ALPHA_MASK_LITTLE_ENDIAN 1
#define RGA_ALPHA_NORMAL 0
#define RGA_ALPHA_REVERSE 1
#define RGA_ALPHA_BLEND_GLOBAL 0
#define RGA_ALPHA_BLEND_NORMAL 1
#define RGA_ALPHA_BLEND_MULTIPLY 2
#define RGA_ALPHA_CAL_CUT 0
#define RGA_ALPHA_CAL_NORMAL 1
#define RGA_ALPHA_FACTOR_ZERO 0
#define RGA_ALPHA_FACTOR_ONE 1
#define RGA_ALPHA_FACTOR_OTHER 2
#define RGA_ALPHA_FACTOR_OTHER_REVERSE 3
#define RGA_ALPHA_FACTOR_SELF 4
#define RGA_ALPHA_COLOR_NORMAL 0
#define RGA_ALPHA_COLOR_MULTIPLY_CAL 1
#define RGA_INT_COMMAND_FINISHED 4
/* Registers union */
union rga_mode_ctrl {
unsigned int val;
struct {
/* [0:2] */
unsigned int render:3;
/* [3:6] */
unsigned int bitblt:1;
unsigned int cf_rop4_pat:1;
unsigned int alpha_zero_key:1;
unsigned int gradient_sat:1;
/* [7:31] */
unsigned int reserved:25;
} data;
};
union rga_src_info {
unsigned int val;
struct {
/* [0:3] */
unsigned int format:4;
/* [4:7] */
unsigned int swap:3;
unsigned int cp_endian:1;
/* [8:17] */
unsigned int csc_mode:2;
unsigned int rot_mode:2;
unsigned int mir_mode:2;
unsigned int hscl_mode:2;
unsigned int vscl_mode:2;
/* [18:22] */
unsigned int trans_mode:1;
unsigned int trans_enable:4;
/* [23:25] */
unsigned int dither_up_en:1;
unsigned int bic_coe_sel:2;
/* [26:31] */
unsigned int reserved:6;
} data;
};
union rga_src_vir_info {
unsigned int val;
struct {
/* [0:15] */
unsigned int vir_width:15;
unsigned int reserved:1;
/* [16:25] */
unsigned int vir_stride:10;
/* [26:31] */
unsigned int reserved1:6;
} data;
};
union rga_src_act_info {
unsigned int val;
struct {
/* [0:15] */
unsigned int act_width:13;
unsigned int reserved:3;
/* [16:31] */
unsigned int act_height:13;
unsigned int reserved1:3;
} data;
};
union rga_src_x_factor {
unsigned int val;
struct {
/* [0:15] */
unsigned int down_scale_factor:16;
/* [16:31] */
unsigned int up_scale_factor:16;
} data;
};
union rga_src_y_factor {
unsigned int val;
struct {
/* [0:15] */
unsigned int down_scale_factor:16;
/* [16:31] */
unsigned int up_scale_factor:16;
} data;
};
/* Alpha / Red / Green / Blue */
union rga_src_cp_gr_color {
unsigned int val;
struct {
/* [0:15] */
unsigned int gradient_x:16;
/* [16:31] */
unsigned int gradient_y:16;
} data;
};
union rga_src_transparency_color0 {
unsigned int val;
struct {
/* [0:7] */
unsigned int trans_rmin:8;
/* [8:15] */
unsigned int trans_gmin:8;
/* [16:23] */
unsigned int trans_bmin:8;
/* [24:31] */
unsigned int trans_amin:8;
} data;
};
union rga_src_transparency_color1 {
unsigned int val;
struct {
/* [0:7] */
unsigned int trans_rmax:8;
/* [8:15] */
unsigned int trans_gmax:8;
/* [16:23] */
unsigned int trans_bmax:8;
/* [24:31] */
unsigned int trans_amax:8;
} data;
};
union rga_dst_info {
unsigned int val;
struct {
/* [0:3] */
unsigned int format:4;
/* [4:6] */
unsigned int swap:3;
/* [7:9] */
unsigned int src1_format:3;
/* [10:11] */
unsigned int src1_swap:2;
/* [12:15] */
unsigned int dither_up_en:1;
unsigned int dither_down_en:1;
unsigned int dither_down_mode:2;
/* [16:18] */
unsigned int csc_mode:2;
unsigned int csc_clip:1;
/* [19:31] */
unsigned int reserved:13;
} data;
};
union rga_dst_vir_info {
unsigned int val;
struct {
/* [0:15] */
unsigned int vir_stride:15;
unsigned int reserved:1;
/* [16:31] */
unsigned int src1_vir_stride:15;
unsigned int reserved1:1;
} data;
};
union rga_dst_act_info {
unsigned int val;
struct {
/* [0:15] */
unsigned int act_width:12;
unsigned int reserved:4;
/* [16:31] */
unsigned int act_height:12;
unsigned int reserved1:4;
} data;
};
union rga_alpha_ctrl0 {
unsigned int val;
struct {
/* [0:3] */
unsigned int rop_en:1;
unsigned int rop_select:1;
unsigned int rop_mode:2;
/* [4:11] */
unsigned int src_fading_val:8;
/* [12:20] */
unsigned int dst_fading_val:8;
unsigned int mask_endian:1;
/* [21:31] */
unsigned int reserved:11;
} data;
};
union rga_alpha_ctrl1 {
unsigned int val;
struct {
/* [0:1] */
unsigned int dst_color_m0:1;
unsigned int src_color_m0:1;
/* [2:7] */
unsigned int dst_factor_m0:3;
unsigned int src_factor_m0:3;
/* [8:9] */
unsigned int dst_alpha_cal_m0:1;
unsigned int src_alpha_cal_m0:1;
/* [10:13] */
unsigned int dst_blend_m0:2;
unsigned int src_blend_m0:2;
/* [14:15] */
unsigned int dst_alpha_m0:1;
unsigned int src_alpha_m0:1;
/* [16:21] */
unsigned int dst_factor_m1:3;
unsigned int src_factor_m1:3;
/* [22:23] */
unsigned int dst_alpha_cal_m1:1;
unsigned int src_alpha_cal_m1:1;
/* [24:27] */
unsigned int dst_blend_m1:2;
unsigned int src_blend_m1:2;
/* [28:29] */
unsigned int dst_alpha_m1:1;
unsigned int src_alpha_m1:1;
/* [30:31] */
unsigned int reserved:2;
} data;
};
union rga_fading_ctrl {
unsigned int val;
struct {
/* [0:7] */
unsigned int fading_offset_r:8;
/* [8:15] */
unsigned int fading_offset_g:8;
/* [16:23] */
unsigned int fading_offset_b:8;
/* [24:31] */
unsigned int fading_en:1;
unsigned int reserved:7;
} data;
};
union rga_pat_con {
unsigned int val;
struct {
/* [0:7] */
unsigned int width:8;
/* [8:15] */
unsigned int height:8;
/* [16:23] */
unsigned int offset_x:8;
/* [24:31] */
unsigned int offset_y:8;
} data;
};
struct rga_fmt {
u32 fourcc;
u8 color_swap;
u8 hw_format;
};
#endif
@@ -0,0 +1,939 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Jacob Chen <jacob-chen@iotwrt.com>
*/
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-dma-sg.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
#include "rga.h"
static int debug;
module_param(debug, int, 0644);
static void device_run(void *prv)
{
struct rga_ctx *ctx = prv;
struct rockchip_rga *rga = ctx->rga;
struct vb2_v4l2_buffer *src, *dst;
unsigned long flags;
spin_lock_irqsave(&rga->ctrl_lock, flags);
if (ctx->cmdbuf_dirty) {
ctx->cmdbuf_dirty = false;
rga->hw->setup_cmdbuf(ctx);
}
spin_unlock_irqrestore(&rga->ctrl_lock, flags);
rga->curr = ctx;
src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
src->sequence = ctx->osequence++;
dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
rga->hw->start(rga, vb_to_rga(src), vb_to_rga(dst));
}
static irqreturn_t rga_isr(int irq, void *prv)
{
struct rockchip_rga *rga = prv;
if (rga->hw->handle_irq(rga)) {
struct vb2_v4l2_buffer *src, *dst;
struct rga_ctx *ctx = rga->curr;
WARN_ON(!ctx);
rga->curr = NULL;
src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
WARN_ON(!src);
WARN_ON(!dst);
v4l2_m2m_buf_copy_metadata(src, dst);
dst->sequence = ctx->csequence++;
v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(rga->m2m_dev, ctx->fh.m2m_ctx);
}
return IRQ_HANDLED;
}
static const struct v4l2_m2m_ops rga_m2m_ops = {
.device_run = device_run,
};
static int
queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
{
struct rga_ctx *ctx = priv;
int ret;
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
src_vq->drv_priv = ctx;
src_vq->ops = &rga_qops;
if (rga_has_internal_iommu(ctx->rga))
src_vq->mem_ops = &vb2_dma_sg_memops;
else
src_vq->mem_ops = &vb2_dma_contig_memops;
src_vq->gfp_flags = __GFP_DMA32;
src_vq->buf_struct_size = sizeof(struct rga_vb_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
src_vq->lock = &ctx->rga->mutex;
src_vq->dev = ctx->rga->v4l2_dev.dev;
ret = vb2_queue_init(src_vq);
if (ret)
return ret;
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
dst_vq->drv_priv = ctx;
dst_vq->ops = &rga_qops;
if (rga_has_internal_iommu(ctx->rga))
dst_vq->mem_ops = &vb2_dma_sg_memops;
else
dst_vq->mem_ops = &vb2_dma_contig_memops;
dst_vq->gfp_flags = __GFP_DMA32;
dst_vq->buf_struct_size = sizeof(struct rga_vb_buffer);
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
dst_vq->lock = &ctx->rga->mutex;
dst_vq->dev = ctx->rga->v4l2_dev.dev;
return vb2_queue_init(dst_vq);
}
static int rga_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct rga_ctx *ctx = container_of(ctrl->handler, struct rga_ctx,
ctrl_handler);
const struct rga_hw *hw = ctx->rga->hw;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&ctx->rga->ctrl_lock, flags);
switch (ctrl->id) {
case V4L2_CID_HFLIP:
ctx->hflip = ctrl->val;
break;
case V4L2_CID_VFLIP:
ctx->vflip = ctrl->val;
break;
case V4L2_CID_ROTATE:
if (vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx)) &&
vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx))) {
ret = rga_check_scaling(hw, &ctx->in.crop,
&ctx->out.crop, ctrl->val);
if (ret < 0)
goto s_ctrl_done;
}
ctx->rotate = ctrl->val;
break;
case V4L2_CID_BG_COLOR:
ctx->fill_color = ctrl->val;
break;
}
ctx->cmdbuf_dirty = true;
s_ctrl_done:
spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
return ret;
}
static const struct v4l2_ctrl_ops rga_ctrl_ops = {
.s_ctrl = rga_s_ctrl,
};
static int rga_setup_ctrls(struct rga_ctx *ctx)
{
struct rockchip_rga *rga = ctx->rga;
v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4);
if (rga->hw->features & RGA_FEATURE_FLIP) {
v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
}
if (rga->hw->features & RGA_FEATURE_ROTATE)
v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
V4L2_CID_ROTATE, 0, 270, 90, 0);
if (rga->hw->features & RGA_FEATURE_BG_COLOR)
v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0);
if (ctx->ctrl_handler.error) {
int err = ctx->ctrl_handler.error;
v4l2_err(&rga->v4l2_dev, "%s failed\n", __func__);
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
return err;
}
return 0;
}
static bool check_scaling_factor(const struct rga_hw *hw, u32 src_size,
u32 dst_size)
{
if (src_size < dst_size)
return src_size * hw->max_scaling_factor >= dst_size;
else
return dst_size * hw->max_scaling_factor >= src_size;
}
int rga_check_scaling(const struct rga_hw *hw, const struct v4l2_rect *crop_in,
const struct v4l2_rect *crop_out, u32 rotate)
{
u32 scaled_width;
u32 scaled_height;
if (rotate == 90 || rotate == 270) {
scaled_width = crop_out->height;
scaled_height = crop_out->width;
} else {
scaled_width = crop_out->width;
scaled_height = crop_out->height;
}
if (!check_scaling_factor(hw, crop_in->width, scaled_width))
return -EINVAL;
if (!check_scaling_factor(hw, crop_in->height, scaled_height))
return -EINVAL;
return 0;
}
struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
return &ctx->in;
if (V4L2_TYPE_IS_CAPTURE(type))
return &ctx->out;
return ERR_PTR(-EINVAL);
}
static int rga_open(struct file *file)
{
struct rockchip_rga *rga = video_drvdata(file);
struct rga_ctx *ctx = NULL;
int ret = 0;
u32 def_width = clamp(DEFAULT_WIDTH, rga->hw->min_width, rga->hw->max_width);
u32 def_height = clamp(DEFAULT_HEIGHT, rga->hw->min_height, rga->hw->max_height);
struct rga_frame def_frame = {
.crop.left = 0,
.crop.top = 0,
.crop.width = def_width,
.crop.height = def_height,
};
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
/* Create CMD buffer */
ctx->cmdbuf_virt = dma_alloc_attrs(rga->dev, rga->hw->cmdbuf_size,
&ctx->cmdbuf_phy, GFP_KERNEL,
DMA_ATTR_WRITE_COMBINE);
if (!ctx->cmdbuf_virt) {
ret = -ENOMEM;
goto rel_ctx;
}
ctx->cmdbuf_dirty = true;
ctx->rga = rga;
/* Set default formats */
ctx->in = def_frame;
ctx->out = def_frame;
ctx->in.fmt = rga->hw->adjust_and_map_format(ctx, &ctx->in.pix, true);
v4l2_fill_pixfmt_mp_aligned(&ctx->in.pix, ctx->in.pix.pixelformat,
def_width, def_height, rga->hw->stride_alignment);
ctx->out.fmt =
rga->hw->adjust_and_map_format(ctx, &ctx->out.pix, false);
v4l2_fill_pixfmt_mp_aligned(&ctx->out.pix, ctx->out.pix.pixelformat,
def_width, def_height, rga->hw->stride_alignment);
if (mutex_lock_interruptible(&rga->mutex)) {
ret = -ERESTARTSYS;
goto rel_cmdbuf;
}
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rga->m2m_dev, ctx, &queue_init);
if (IS_ERR(ctx->fh.m2m_ctx)) {
ret = PTR_ERR(ctx->fh.m2m_ctx);
goto unlock_mutex;
}
v4l2_fh_init(&ctx->fh, video_devdata(file));
v4l2_fh_add(&ctx->fh, file);
rga_setup_ctrls(ctx);
/* Write the default values to the ctx struct */
v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
ctx->fh.ctrl_handler = &ctx->ctrl_handler;
mutex_unlock(&rga->mutex);
return 0;
unlock_mutex:
mutex_unlock(&rga->mutex);
rel_cmdbuf:
dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
rel_ctx:
kfree(ctx);
return ret;
}
static int rga_release(struct file *file)
{
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rockchip_rga *rga = ctx->rga;
mutex_lock(&rga->mutex);
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh, file);
v4l2_fh_exit(&ctx->fh);
dma_free_attrs(rga->dev, rga->hw->cmdbuf_size, ctx->cmdbuf_virt,
ctx->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE);
kfree(ctx);
mutex_unlock(&rga->mutex);
return 0;
}
static const struct v4l2_file_operations rga_fops = {
.owner = THIS_MODULE,
.open = rga_open,
.release = rga_release,
.poll = v4l2_m2m_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = v4l2_m2m_fop_mmap,
};
static int
vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
{
struct rockchip_rga *rga = video_drvdata(file);
strscpy(cap->driver, RGA_NAME, sizeof(cap->driver));
strscpy(cap->card, rga->hw->card_type, sizeof(cap->card));
strscpy(cap->bus_info, "platform:rga", sizeof(cap->bus_info));
return 0;
}
static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
struct rockchip_rga *rga = video_drvdata(file);
int ret;
ret = rga->hw->enum_format(f);
if (ret != 0)
return ret;
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
return 0;
/* allow changing the quantization and xfer func for YUV formats */
if (v4l2_is_format_yuv(v4l2_format_info(f->pixelformat)))
f->flags |= V4L2_FMT_FLAG_CSC_QUANTIZATION |
V4L2_FMT_FLAG_CSC_YCBCR_ENC;
return 0;
}
static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rga_frame *frm;
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
*pix_fmt = frm->pix;
pix_fmt->field = V4L2_FIELD_NONE;
return 0;
}
static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
struct rga_ctx *ctx = file_to_rga_ctx(file);
const struct rga_hw *hw = ctx->rga->hw;
struct v4l2_frmsize_stepwise frmsize = {
.min_width = hw->min_width,
.max_width = hw->max_width,
.min_height = hw->min_height,
.max_height = hw->max_height,
.step_width = 1,
.step_height = 1,
};
if (v4l2_is_format_yuv(v4l2_format_info(pix_fmt->pixelformat))) {
frmsize.step_width = 2;
frmsize.step_height = 2;
}
if (V4L2_TYPE_IS_CAPTURE(f->type)) {
const struct rga_frame *frm;
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
if (!(pix_fmt->flags & V4L2_PIX_FMT_FLAG_SET_CSC)) {
pix_fmt->quantization = frm->pix.quantization;
pix_fmt->ycbcr_enc = frm->pix.ycbcr_enc;
}
/* disallow values not announced in vidioc_enum_fmt */
pix_fmt->colorspace = frm->pix.colorspace;
pix_fmt->xfer_func = frm->pix.xfer_func;
}
hw->adjust_and_map_format(ctx, pix_fmt, V4L2_TYPE_IS_OUTPUT(f->type));
v4l2_apply_frmsize_constraints(&pix_fmt->width, &pix_fmt->height, &frmsize);
v4l2_fill_pixfmt_mp_aligned(pix_fmt, pix_fmt->pixelformat,
pix_fmt->width, pix_fmt->height, hw->stride_alignment);
pix_fmt->field = V4L2_FIELD_NONE;
return 0;
}
static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pix_fmt = &f->fmt.pix_mp;
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rockchip_rga *rga = ctx->rga;
struct vb2_queue *vq;
struct rga_frame *frm;
int ret = 0;
int i;
/* Adjust all values accordingly to the hardware capabilities
* and chosen format.
*/
ret = vidioc_try_fmt(file, priv, f);
if (ret)
return ret;
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
if (vb2_is_busy(vq)) {
v4l2_err(&rga->v4l2_dev, "queue (%d) bust\n", f->type);
return -EBUSY;
}
frm = rga_get_frame(ctx, f->type);
if (IS_ERR(frm))
return PTR_ERR(frm);
frm->fmt = rga->hw->adjust_and_map_format(ctx, pix_fmt,
V4L2_TYPE_IS_OUTPUT(f->type));
/*
* Copy colorimetry from output to capture as required by the
* v4l2-compliance tests
*/
if (V4L2_TYPE_IS_OUTPUT(f->type)) {
ctx->out.pix.colorspace = pix_fmt->colorspace;
ctx->out.pix.ycbcr_enc = pix_fmt->ycbcr_enc;
ctx->out.pix.quantization = pix_fmt->quantization;
ctx->out.pix.xfer_func = pix_fmt->xfer_func;
}
/* Reset crop settings */
frm->crop.left = 0;
frm->crop.top = 0;
frm->crop.width = pix_fmt->width;
frm->crop.height = pix_fmt->height;
frm->pix = *pix_fmt;
ctx->cmdbuf_dirty = true;
v4l2_dbg(debug, 1, &rga->v4l2_dev,
"[%s] fmt - %p4cc %dx%d (stride %d)\n",
V4L2_TYPE_IS_OUTPUT(f->type) ? "OUTPUT" : "CAPTURE",
&pix_fmt->pixelformat, pix_fmt->width, pix_fmt->height,
pix_fmt->plane_fmt[0].bytesperline);
for (i = 0; i < pix_fmt->num_planes; i++) {
v4l2_dbg(debug, 1, &rga->v4l2_dev,
"plane[%d]: size %d, bytesperline %d\n",
i, pix_fmt->plane_fmt[i].sizeimage,
pix_fmt->plane_fmt[i].bytesperline);
}
return 0;
}
static int vidioc_g_selection(struct file *file, void *priv,
struct v4l2_selection *s)
{
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rga_frame *f;
bool use_frame = false;
f = rga_get_frame(ctx, s->type);
if (IS_ERR(f))
return PTR_ERR(f);
switch (s->target) {
case V4L2_SEL_TGT_COMPOSE_DEFAULT:
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
if (!V4L2_TYPE_IS_CAPTURE(s->type))
return -EINVAL;
break;
case V4L2_SEL_TGT_CROP_DEFAULT:
case V4L2_SEL_TGT_CROP_BOUNDS:
if (!V4L2_TYPE_IS_OUTPUT(s->type))
return -EINVAL;
break;
case V4L2_SEL_TGT_COMPOSE:
if (!V4L2_TYPE_IS_CAPTURE(s->type))
return -EINVAL;
use_frame = true;
break;
case V4L2_SEL_TGT_CROP:
if (!V4L2_TYPE_IS_OUTPUT(s->type))
return -EINVAL;
use_frame = true;
break;
default:
return -EINVAL;
}
if (use_frame) {
s->r = f->crop;
} else {
s->r.left = 0;
s->r.top = 0;
s->r.width = f->pix.width;
s->r.height = f->pix.height;
}
return 0;
}
static int vidioc_s_selection(struct file *file, void *priv,
struct v4l2_selection *s)
{
struct rga_ctx *ctx = file_to_rga_ctx(file);
struct rockchip_rga *rga = ctx->rga;
struct rga_frame *f;
f = rga_get_frame(ctx, s->type);
if (IS_ERR(f))
return PTR_ERR(f);
switch (s->target) {
case V4L2_SEL_TGT_COMPOSE:
/*
* COMPOSE target is only valid for capture buffer type, return
* error for output buffer type
*/
if (!V4L2_TYPE_IS_CAPTURE(s->type))
return -EINVAL;
break;
case V4L2_SEL_TGT_CROP:
/*
* CROP target is only valid for output buffer type, return
* error for capture buffer type
*/
if (!V4L2_TYPE_IS_OUTPUT(s->type))
return -EINVAL;
break;
/*
* bound and default crop/compose targets are invalid targets to
* try/set
*/
default:
return -EINVAL;
}
if (s->r.top < 0 || s->r.left < 0) {
v4l2_dbg(debug, 1, &rga->v4l2_dev,
"doesn't support negative values for top & left.\n");
return -EINVAL;
}
if (s->r.left + s->r.width > f->pix.width ||
s->r.top + s->r.height > f->pix.height ||
s->r.width < rga->hw->min_width || s->r.height < rga->hw->min_height) {
v4l2_dbg(debug, 1, &rga->v4l2_dev, "unsupported crop value.\n");
return -EINVAL;
}
if (vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx)) &&
vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx))) {
int ret = 0;
if (V4L2_TYPE_IS_OUTPUT(s->type))
ret = rga_check_scaling(rga->hw, &s->r, &ctx->out.crop,
ctx->rotate);
else
ret = rga_check_scaling(rga->hw, &ctx->in.crop, &s->r,
ctx->rotate);
if (ret < 0)
return ret;
}
f->crop = s->r;
ctx->cmdbuf_dirty = true;
return 0;
}
static const struct v4l2_ioctl_ops rga_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt,
.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt,
.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt,
.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
.vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt,
.vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt,
.vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt,
.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_streamon = v4l2_m2m_ioctl_streamon,
.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
.vidioc_g_selection = vidioc_g_selection,
.vidioc_s_selection = vidioc_s_selection,
};
static const struct video_device rga_videodev = {
.name = "rockchip-rga",
.fops = &rga_fops,
.ioctl_ops = &rga_ioctl_ops,
.minor = -1,
.release = video_device_release,
.vfl_dir = VFL_DIR_M2M,
.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING,
};
static int rga_parse_dt(struct rockchip_rga *rga)
{
struct reset_control *core_rst, *axi_rst, *ahb_rst;
int ret;
core_rst = devm_reset_control_get(rga->dev, "core");
if (IS_ERR(core_rst)) {
dev_err(rga->dev, "failed to get core reset controller\n");
return PTR_ERR(core_rst);
}
axi_rst = devm_reset_control_get(rga->dev, "axi");
if (IS_ERR(axi_rst)) {
dev_err(rga->dev, "failed to get axi reset controller\n");
return PTR_ERR(axi_rst);
}
ahb_rst = devm_reset_control_get(rga->dev, "ahb");
if (IS_ERR(ahb_rst)) {
dev_err(rga->dev, "failed to get ahb reset controller\n");
return PTR_ERR(ahb_rst);
}
reset_control_assert(core_rst);
udelay(1);
reset_control_deassert(core_rst);
reset_control_assert(axi_rst);
udelay(1);
reset_control_deassert(axi_rst);
reset_control_assert(ahb_rst);
udelay(1);
reset_control_deassert(ahb_rst);
ret = devm_clk_bulk_get_all(rga->dev, &rga->clks);
if (ret < 0) {
dev_err(rga->dev, "failed to get clocks\n");
return ret;
}
rga->num_clks = ret;
return 0;
}
/*
* Some SoCs, like RK3588 have multiple identical RGA3 cores, but the
* kernel is currently missing support for multi-core handling. Exposing
* separate devices for each core to userspace is bad, since that does
* not allow scheduling tasks properly (and creates ABI). With this workaround
* the driver will only probe for the first core and early exit for the other
* cores. Once the driver gains multi-core support, the same technique
* for detecting the main core can be used to cluster all cores together.
*/
static int rga_disable_multicore(struct device *dev)
{
struct device_node *node = NULL;
const char *compatible;
bool is_main_core;
int ret;
/* Intentionally ignores the fallback strings */
ret = of_property_read_string(dev->of_node, "compatible", &compatible);
if (ret)
return ret;
/* The first compatible and available node found is considered the main core */
do {
node = of_find_compatible_node(node, NULL, compatible);
if (of_device_is_available(node))
break;
} while (node);
if (!node)
return -EINVAL;
is_main_core = (dev->of_node == node);
of_node_put(node);
if (!is_main_core) {
dev_info(dev, "missing multi-core support, ignoring this instance\n");
return -ENODEV;
}
return 0;
}
static int rga_probe(struct platform_device *pdev)
{
struct rockchip_rga *rga;
struct video_device *vfd;
int ret = 0;
int irq;
if (!pdev->dev.of_node)
return -ENODEV;
ret = rga_disable_multicore(&pdev->dev);
if (ret)
return ret;
rga = devm_kzalloc(&pdev->dev, sizeof(*rga), GFP_KERNEL);
if (!rga)
return -ENOMEM;
rga->hw = of_device_get_match_data(&pdev->dev);
if (!rga->hw)
return dev_err_probe(&pdev->dev, -ENODEV, "failed to get match data\n");
rga->dev = &pdev->dev;
spin_lock_init(&rga->ctrl_lock);
mutex_init(&rga->mutex);
ret = rga_parse_dt(rga);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Unable to parse OF data\n");
pm_runtime_enable(rga->dev);
rga->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rga->regs)) {
ret = PTR_ERR(rga->regs);
goto err_put_clk;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto err_put_clk;
}
ret = devm_request_irq(rga->dev, irq, rga_isr,
rga_has_internal_iommu(rga) ? 0 : IRQF_SHARED,
dev_name(rga->dev), rga);
if (ret < 0) {
dev_err(rga->dev, "failed to request irq\n");
goto err_put_clk;
}
ret = dma_set_mask_and_coherent(rga->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(rga->dev, "32-bit DMA not supported");
goto err_put_clk;
}
ret = v4l2_device_register(&pdev->dev, &rga->v4l2_dev);
if (ret)
goto err_put_clk;
vfd = video_device_alloc();
if (!vfd) {
v4l2_err(&rga->v4l2_dev, "Failed to allocate video device\n");
ret = -ENOMEM;
goto unreg_v4l2_dev;
}
*vfd = rga_videodev;
vfd->lock = &rga->mutex;
vfd->v4l2_dev = &rga->v4l2_dev;
video_set_drvdata(vfd, rga);
rga->vfd = vfd;
platform_set_drvdata(pdev, rga);
rga->m2m_dev = v4l2_m2m_init(&rga_m2m_ops);
if (IS_ERR(rga->m2m_dev)) {
v4l2_err(&rga->v4l2_dev, "Failed to init mem2mem device\n");
ret = PTR_ERR(rga->m2m_dev);
goto rel_vdev;
}
ret = pm_runtime_resume_and_get(rga->dev);
if (ret < 0)
goto rel_m2m;
rga->hw->get_version(rga);
v4l2_info(&rga->v4l2_dev, "HW Version: 0x%02x.%02x\n",
rga->version.major, rga->version.minor);
pm_runtime_put(rga->dev);
ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
if (ret) {
v4l2_err(&rga->v4l2_dev, "Failed to register video device\n");
goto rel_m2m;
}
v4l2_info(&rga->v4l2_dev, "Registered %s as /dev/%s\n",
vfd->name, video_device_node_name(vfd));
return 0;
rel_m2m:
v4l2_m2m_release(rga->m2m_dev);
rel_vdev:
video_device_release(vfd);
unreg_v4l2_dev:
v4l2_device_unregister(&rga->v4l2_dev);
err_put_clk:
pm_runtime_disable(rga->dev);
return ret;
}
static void rga_remove(struct platform_device *pdev)
{
struct rockchip_rga *rga = platform_get_drvdata(pdev);
v4l2_info(&rga->v4l2_dev, "Removing\n");
v4l2_m2m_release(rga->m2m_dev);
video_unregister_device(rga->vfd);
v4l2_device_unregister(&rga->v4l2_dev);
pm_runtime_disable(rga->dev);
}
static int __maybe_unused rga_runtime_suspend(struct device *dev)
{
struct rockchip_rga *rga = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(rga->num_clks, rga->clks);
return 0;
}
static int __maybe_unused rga_runtime_resume(struct device *dev)
{
struct rockchip_rga *rga = dev_get_drvdata(dev);
return clk_bulk_prepare_enable(rga->num_clks, rga->clks);
}
static const struct dev_pm_ops rga_pm = {
SET_RUNTIME_PM_OPS(rga_runtime_suspend,
rga_runtime_resume, NULL)
};
static const struct of_device_id rockchip_rga_match[] = {
{
.compatible = "rockchip,rk3288-rga",
.data = &rga2_hw,
},
{
.compatible = "rockchip,rk3399-rga",
.data = &rga2_hw,
},
{
.compatible = "rockchip,rk3588-rga3",
.data = &rga3_hw,
},
{},
};
MODULE_DEVICE_TABLE(of, rockchip_rga_match);
static struct platform_driver rga_pdrv = {
.probe = rga_probe,
.remove = rga_remove,
.driver = {
.name = RGA_NAME,
.pm = &rga_pm,
.of_match_table = rockchip_rga_match,
},
};
module_platform_driver(rga_pdrv);
MODULE_AUTHOR("Jacob Chen <jacob-chen@iotwrt.com>");
MODULE_DESCRIPTION("Rockchip Raster 2d Graphic Acceleration Unit");
MODULE_LICENSE("GPL");
@@ -0,0 +1,174 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
* Author: Jacob Chen <jacob-chen@iotwrt.com>
*/
#ifndef __RGA_H__
#define __RGA_H__
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#define RGA_NAME "rockchip-rga"
#define DEFAULT_WIDTH 100
#define DEFAULT_HEIGHT 100
struct rga_frame {
/* Crop */
struct v4l2_rect crop;
/* Image format */
void *fmt;
struct v4l2_pix_format_mplane pix;
};
struct rga_dma_desc {
u32 addr;
};
struct rockchip_rga_version {
u32 major;
u32 minor;
};
struct rga_ctx {
struct v4l2_fh fh;
struct rockchip_rga *rga;
struct rga_frame in;
struct rga_frame out;
struct v4l2_ctrl_handler ctrl_handler;
void *cmdbuf_virt;
dma_addr_t cmdbuf_phy;
bool cmdbuf_dirty;
int osequence;
int csequence;
/* Control values */
u32 op;
u32 hflip;
u32 vflip;
u32 rotate;
u32 fill_color;
};
static inline struct rga_ctx *file_to_rga_ctx(struct file *filp)
{
return container_of(file_to_v4l2_fh(filp), struct rga_ctx, fh);
}
struct rga_hw;
struct rockchip_rga {
struct v4l2_device v4l2_dev;
struct v4l2_m2m_dev *m2m_dev;
struct video_device *vfd;
struct device *dev;
struct regmap *grf;
void __iomem *regs;
struct clk_bulk_data *clks;
int num_clks;
struct rockchip_rga_version version;
/* vfd lock */
struct mutex mutex;
/* ctrl parm lock */
spinlock_t ctrl_lock;
struct rga_ctx *curr;
const struct rga_hw *hw;
};
struct rga_addrs {
dma_addr_t y_addr;
dma_addr_t u_addr;
dma_addr_t v_addr;
};
struct rga_vb_buffer {
struct vb2_v4l2_buffer vb_buf;
struct list_head queue;
/* RGA MMU mapping for this buffer */
struct rga_dma_desc *dma_desc;
dma_addr_t dma_desc_pa;
size_t n_desc;
/* Plane DMA addresses after the MMU mapping of the buffer */
struct rga_addrs dma_addrs;
};
static inline struct rga_vb_buffer *vb_to_rga(struct vb2_v4l2_buffer *vb)
{
return container_of(vb, struct rga_vb_buffer, vb_buf);
}
struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type);
int rga_check_scaling(const struct rga_hw *hw, const struct v4l2_rect *crop_in,
const struct v4l2_rect *crop_out, u32 rotate);
/* RGA Buffers Manage */
extern const struct vb2_ops rga_qops;
/* RGA Hardware */
static inline void rga_write(struct rockchip_rga *rga, u32 reg, u32 value)
{
writel(value, rga->regs + reg);
};
static inline u32 rga_read(struct rockchip_rga *rga, u32 reg)
{
return readl(rga->regs + reg);
};
static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask)
{
u32 temp = rga_read(rga, reg) & ~(mask);
temp |= val & mask;
rga_write(rga, reg, temp);
};
#define RGA_FEATURE_FLIP BIT(0)
#define RGA_FEATURE_ROTATE BIT(1)
#define RGA_FEATURE_BG_COLOR BIT(2)
struct rga_hw {
const char *card_type;
bool has_internal_iommu;
size_t cmdbuf_size;
u32 min_width, min_height;
u32 max_width, max_height;
u8 max_scaling_factor;
u8 stride_alignment;
u8 features;
void (*setup_cmdbuf)(struct rga_ctx *ctx);
void (*start)(struct rockchip_rga *rga,
struct rga_vb_buffer *src, struct rga_vb_buffer *dst);
bool (*handle_irq)(struct rockchip_rga *rga);
void (*get_version)(struct rockchip_rga *rga);
void *(*adjust_and_map_format)(struct rga_ctx *ctx,
struct v4l2_pix_format_mplane *format,
bool is_output);
int (*enum_format)(struct v4l2_fmtdesc *f);
};
static inline bool rga_has_internal_iommu(const struct rockchip_rga *rga)
{
return rga->hw->has_internal_iommu;
}
extern const struct rga_hw rga2_hw;
extern const struct rga_hw rga3_hw;
#endif
@@ -0,0 +1,507 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025-2026 Pengutronix e.K.
* Author: Sven Püschel <s.pueschel@pengutronix.de>
*/
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/printk.h>
#include <media/v4l2-common.h>
#include "rga3-hw.h"
#include "rga.h"
static unsigned int rga3_get_scaling(unsigned int src, unsigned int dst)
{
/*
* RGA3 scaling factor calculation as described in chapter 5.4.7 Resize
* of the TRM Part 2. The resulting scaling factor is a 16-bit value
* and therefore normalized with 2^16.
*
* While the TRM also mentions (dst-1)/(src-1) for the up-scaling case,
* it didn't work as the value always exceeds 16 bit. Flipping the
* factors results in a correct up-scaling. This is possible as the
* RGA3 has the RGA3_WIN_SCALE_XXX_UP bit to determine if it does
* an up or downscale.
*
* The scaling factor can potentially cause a slightly larger scaling
* (e.g. 1/2px larger scale and then cropped to the destination size).
* This can be seen when scaling 128x128px RGBA to 256x256px RGBA.
* The RGA2 scaling factor calculation (without the various +/-1
* doesn't work for the RGA3. It's assumed that this is an hardware
* accuracy limitation, as the vendor kernel driver uses the same
* scaling factor calculation.
*
* With a scaling factor of 1.0 the calculation technically also
* overflows 16 bit. This isn't relevant, as in this case the
* RGA3_WIN_SCALE_XXX_BYPASS bit completely skips the scaling operation.
*/
if (dst > src) {
if (((src - 1) << 16) % (dst - 1) == 0)
return ((src - 1) << 16) / (dst - 1) - 1;
else
return ((src - 1) << 16) / (dst - 1);
} else {
return ((dst - 1) << 16) / (src - 1) + 1;
}
}
/*
* Check if the given format can be captured, as the RGA3 doesn't support all
* input formats also on it's output.
*/
static bool rga3_can_capture(const struct rga3_fmt *fmt)
{
return fmt->hw_format <= RGA3_COLOR_FMT_LAST_OUTPUT;
}
/*
* Map the transformations to the RGA3 command buffer.
* Currently this is just the scaling settings and a fixed alpha value.
*/
static void rga3_cmd_set_trans_info(struct rga_ctx *ctx)
{
u32 *cmd = ctx->cmdbuf_virt;
unsigned int src_h, src_w, dst_h, dst_w;
unsigned int reg;
u16 hor_scl_fac, ver_scl_fac;
const struct rga3_fmt *in = ctx->in.fmt;
/* Support basic input cropping to support 1088px inputs */
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
dst_h = ctx->out.pix.height;
dst_w = ctx->out.pix.width;
reg = RGA3_WIN0_RD_CTRL - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] |= FIELD_PREP(RGA3_WIN_SCALE_HOR_UP, dst_w > src_w)
| FIELD_PREP(RGA3_WIN_SCALE_HOR_BYPASS, dst_w == src_w)
| FIELD_PREP(RGA3_WIN_SCALE_VER_UP, dst_h > src_h)
| FIELD_PREP(RGA3_WIN_SCALE_VER_BYPASS, dst_h == src_h);
hor_scl_fac = rga3_get_scaling(src_w, dst_w);
ver_scl_fac = rga3_get_scaling(src_h, dst_h);
reg = RGA3_WIN0_SCL_FAC - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_SCALE_HOR_FAC, hor_scl_fac)
| FIELD_PREP(RGA3_SCALE_VER_FAC, ver_scl_fac);
if (v4l2_format_info(in->fourcc)->has_alpha) {
/* copy alpha from input */
reg = RGA3_OVLP_TOP_ALPHA - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_ALPHA_SELECT_MODE, 1)
| FIELD_PREP(RGA3_ALPHA_BLEND_MODE, 1);
reg = RGA3_OVLP_BOT_ALPHA - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_ALPHA_SELECT_MODE, 1)
| FIELD_PREP(RGA3_ALPHA_BLEND_MODE, 1);
} else {
/* just use a 255 alpha value */
reg = RGA3_OVLP_TOP_CTRL - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_OVLP_GLOBAL_ALPHA, 0xff)
| FIELD_PREP(RGA3_OVLP_COLOR_MODE, 1);
reg = RGA3_OVLP_BOT_CTRL - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_OVLP_GLOBAL_ALPHA, 0xff)
| FIELD_PREP(RGA3_OVLP_COLOR_MODE, 1);
}
}
static void rga3_cmd_set_win0_addr(struct rga_ctx *ctx,
const struct rga_addrs *addrs)
{
u32 *cmd = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA3_WIN0_Y_BASE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = addrs->y_addr;
reg = RGA3_WIN0_U_BASE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = addrs->u_addr;
}
static void rga3_cmd_set_wr_addr(struct rga_ctx *ctx,
const struct rga_addrs *addrs)
{
u32 *cmd = ctx->cmdbuf_virt;
unsigned int reg;
reg = RGA3_WR_Y_BASE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = addrs->y_addr;
reg = RGA3_WR_U_BASE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = addrs->u_addr;
}
/* Map the input pixel format to win0 of the comamnd buffer. */
static void rga3_cmd_set_win0_format(struct rga_ctx *ctx)
{
u32 *cmd = ctx->cmdbuf_virt;
const struct rga3_fmt *in = ctx->in.fmt;
const struct rga3_fmt *out = ctx->out.fmt;
const struct v4l2_format_info *in_fmt, *out_fmt;
unsigned int act_h, act_w, src_h, src_w;
bool r2y, y2r;
u8 rd_format;
const struct v4l2_pix_format_mplane *csc_pix;
u8 csc_mode;
unsigned int reg;
act_h = ctx->in.pix.height;
act_w = ctx->in.pix.width;
/* Support basic input cropping to support 1088px inputs */
src_h = ctx->in.crop.height;
src_w = ctx->in.crop.width;
in_fmt = v4l2_format_info(in->fourcc);
out_fmt = v4l2_format_info(out->fourcc);
r2y = v4l2_is_format_rgb(in_fmt) && v4l2_is_format_yuv(out_fmt);
y2r = v4l2_is_format_yuv(in_fmt) && v4l2_is_format_rgb(out_fmt);
/* The Hardware only supports formats with 1/2 planes */
if (in_fmt->comp_planes == 2)
rd_format = RGA3_RDWR_FORMAT_SEMI_PLANAR;
else
rd_format = RGA3_RDWR_FORMAT_INTERLEAVED;
/* set pixel format and CSC */
csc_pix = r2y ? &ctx->out.pix : &ctx->in.pix;
switch (csc_pix->ycbcr_enc) {
case V4L2_YCBCR_ENC_BT2020:
csc_mode = RGA3_WIN_CSC_MODE_BT2020_L;
break;
case V4L2_YCBCR_ENC_709:
csc_mode = RGA3_WIN_CSC_MODE_BT709_L;
break;
default: /* should be fixed to BT601 in adjust_and_map_format */
if (csc_pix->quantization == V4L2_QUANTIZATION_LIM_RANGE)
csc_mode = RGA3_WIN_CSC_MODE_BT601_L;
else
csc_mode = RGA3_WIN_CSC_MODE_BT601_F;
break;
}
reg = RGA3_WIN0_RD_CTRL - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] |= FIELD_PREP(RGA3_WIN_ENABLE, 1)
| FIELD_PREP(RGA3_WIN_PIC_FORMAT, in->hw_format)
| FIELD_PREP(RGA3_WIN_YC_SWAP, in->yc_swap)
| FIELD_PREP(RGA3_WIN_RBUV_SWAP, in->rbuv_swap)
| FIELD_PREP(RGA3_WIN_RD_FORMAT, rd_format)
| FIELD_PREP(RGA3_WIN_R2Y, r2y)
| FIELD_PREP(RGA3_WIN_Y2R, y2r)
| FIELD_PREP(RGA3_WIN_CSC_MODE, csc_mode);
/* set stride */
reg = RGA3_WIN0_VIR_STRIDE - RGA3_FIRST_CMD_REG;
/* stride needs to be in words */
cmd[reg >> 2] = ctx->in.pix.plane_fmt[0].bytesperline >> 2;
reg = RGA3_WIN0_UV_VIR_STRIDE - RGA3_FIRST_CMD_REG;
/* The Hardware only supports formats with 1/2 planes */
if (ctx->in.pix.num_planes == 2)
cmd[reg >> 2] = ctx->in.pix.plane_fmt[1].bytesperline >> 2;
else
cmd[reg >> 2] = ctx->in.pix.plane_fmt[0].bytesperline >> 2;
/* set size */
reg = RGA3_WIN0_ACT_SIZE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, act_w)
| FIELD_PREP(RGA3_HEIGHT, act_h);
reg = RGA3_WIN0_SRC_SIZE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, src_w)
| FIELD_PREP(RGA3_HEIGHT, src_h);
}
/* Map the output pixel format to the command buffer */
static void rga3_cmd_set_wr_format(struct rga_ctx *ctx)
{
u32 *cmd = ctx->cmdbuf_virt;
const struct rga3_fmt *out = ctx->out.fmt;
const struct v4l2_format_info *out_fmt;
unsigned int dst_h, dst_w;
u8 wr_format;
unsigned int reg;
dst_h = ctx->out.pix.height;
dst_w = ctx->out.pix.width;
out_fmt = v4l2_format_info(out->fourcc);
/* The Hardware only supports formats with 1/2 planes */
if (out_fmt->comp_planes == 2)
wr_format = RGA3_RDWR_FORMAT_SEMI_PLANAR;
else
wr_format = RGA3_RDWR_FORMAT_INTERLEAVED;
/* set pixel format */
reg = RGA3_WR_CTRL - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_WR_PIC_FORMAT, out->hw_format)
| FIELD_PREP(RGA3_WR_YC_SWAP, out->yc_swap)
| FIELD_PREP(RGA3_WR_RBUV_SWAP, out->rbuv_swap)
| FIELD_PREP(RGA3_WR_FORMAT, wr_format)
/* Use the max value to avoid limiting the write speed */
| FIELD_PREP(RGA3_WR_SW_OUTSTANDING_MAX, 63);
/* set stride */
reg = RGA3_WR_VIR_STRIDE - RGA3_FIRST_CMD_REG;
/* stride needs to be in words */
cmd[reg >> 2] = ctx->out.pix.plane_fmt[0].bytesperline >> 2;
reg = RGA3_WR_PL_VIR_STRIDE - RGA3_FIRST_CMD_REG;
/* The Hardware only supports formats with 1/2 planes */
if (ctx->out.pix.num_planes == 2)
cmd[reg >> 2] = ctx->out.pix.plane_fmt[1].bytesperline >> 2;
else
cmd[reg >> 2] = ctx->out.pix.plane_fmt[0].bytesperline >> 2;
/* Set size.
* As two inputs are not supported, we don't use win1.
* Therefore only set the size for win0.
*/
reg = RGA3_WIN0_DST_SIZE - RGA3_FIRST_CMD_REG;
cmd[reg >> 2] = FIELD_PREP(RGA3_WIDTH, dst_w)
| FIELD_PREP(RGA3_HEIGHT, dst_h);
}
static void rga3_hw_setup_cmdbuf(struct rga_ctx *ctx)
{
memset(ctx->cmdbuf_virt, 0, RGA3_CMDBUF_SIZE);
rga3_cmd_set_win0_format(ctx);
rga3_cmd_set_trans_info(ctx);
rga3_cmd_set_wr_format(ctx);
}
static void rga3_hw_start(struct rockchip_rga *rga,
struct rga_vb_buffer *src, struct rga_vb_buffer *dst)
{
struct rga_ctx *ctx = rga->curr;
rga3_cmd_set_win0_addr(ctx, &src->dma_addrs);
rga3_cmd_set_wr_addr(ctx, &dst->dma_addrs);
rga_write(rga, RGA3_CMD_ADDR, ctx->cmdbuf_phy);
/* sync CMD buf for RGA */
dma_sync_single_for_device(rga->dev, ctx->cmdbuf_phy,
PAGE_SIZE, DMA_BIDIRECTIONAL);
/* set to master mode and start the conversion */
rga_write(rga, RGA3_SYS_CTRL,
FIELD_PREP(RGA3_CMD_MODE, RGA3_CMD_MODE_MASTER));
rga_write(rga, RGA3_INT_EN, FIELD_PREP(RGA3_INT_FRM_DONE, 1));
rga_write(rga, RGA3_CMD_CTRL,
FIELD_PREP(RGA3_CMD_LINE_START_PULSE, 1));
}
static bool rga3_handle_irq(struct rockchip_rga *rga)
{
u32 intr;
intr = rga_read(rga, RGA3_INT_RAW);
/* clear all interrupts */
rga_write(rga, RGA3_INT_CLR, intr);
return FIELD_GET(RGA3_INT_FRM_DONE, intr);
}
static void rga3_get_version(struct rockchip_rga *rga)
{
u32 version = rga_read(rga, RGA3_VERSION_NUM);
rga->version.major = FIELD_GET(RGA3_VERSION_NUM_MAJOR, version);
rga->version.minor = FIELD_GET(RGA3_VERSION_NUM_MINOR, version);
}
static struct rga3_fmt rga3_formats[] = {
{
.fourcc = V4L2_PIX_FMT_RGB24,
.hw_format = RGA3_COLOR_FMT_BGR888,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_BGR24,
.hw_format = RGA3_COLOR_FMT_BGR888,
},
{
.fourcc = V4L2_PIX_FMT_ABGR32,
.hw_format = RGA3_COLOR_FMT_BGRA8888,
},
{
.fourcc = V4L2_PIX_FMT_RGBA32,
.hw_format = RGA3_COLOR_FMT_BGRA8888,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_XBGR32,
.hw_format = RGA3_COLOR_FMT_BGRA8888,
},
{
.fourcc = V4L2_PIX_FMT_RGBX32,
.hw_format = RGA3_COLOR_FMT_BGRA8888,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.hw_format = RGA3_COLOR_FMT_BGR565,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV12M,
.hw_format = RGA3_COLOR_FMT_YUV420,
},
{
.fourcc = V4L2_PIX_FMT_NV12,
.hw_format = RGA3_COLOR_FMT_YUV420,
},
{
.fourcc = V4L2_PIX_FMT_NV21M,
.hw_format = RGA3_COLOR_FMT_YUV420,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV21,
.hw_format = RGA3_COLOR_FMT_YUV420,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV16M,
.hw_format = RGA3_COLOR_FMT_YUV422,
},
{
.fourcc = V4L2_PIX_FMT_NV16,
.hw_format = RGA3_COLOR_FMT_YUV422,
},
{
.fourcc = V4L2_PIX_FMT_NV61M,
.hw_format = RGA3_COLOR_FMT_YUV422,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_NV61,
.hw_format = RGA3_COLOR_FMT_YUV422,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_YUYV,
.hw_format = RGA3_COLOR_FMT_YUV422,
.yc_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_YVYU,
.hw_format = RGA3_COLOR_FMT_YUV422,
.yc_swap = 1,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_UYVY,
.hw_format = RGA3_COLOR_FMT_YUV422,
},
{
.fourcc = V4L2_PIX_FMT_VYUY,
.hw_format = RGA3_COLOR_FMT_YUV422,
.rbuv_swap = 1,
},
/* Input only formats last to keep rga3_enum_format simple */
{
.fourcc = V4L2_PIX_FMT_ARGB32,
.hw_format = RGA3_COLOR_FMT_ABGR8888,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_BGRA32,
.hw_format = RGA3_COLOR_FMT_ABGR8888,
},
{
.fourcc = V4L2_PIX_FMT_XRGB32,
.hw_format = RGA3_COLOR_FMT_ABGR8888,
.rbuv_swap = 1,
},
{
.fourcc = V4L2_PIX_FMT_BGRX32,
.hw_format = RGA3_COLOR_FMT_ABGR8888,
},
};
static int rga3_enum_format(struct v4l2_fmtdesc *f)
{
struct rga3_fmt *fmt;
if (f->index >= ARRAY_SIZE(rga3_formats))
return -EINVAL;
fmt = &rga3_formats[f->index];
if (V4L2_TYPE_IS_CAPTURE(f->type) && !rga3_can_capture(fmt))
return -EINVAL;
f->pixelformat = fmt->fourcc;
return 0;
}
static void *rga3_adjust_and_map_format(struct rga_ctx *ctx,
struct v4l2_pix_format_mplane *format,
bool is_output)
{
unsigned int i;
const struct v4l2_format_info *format_info;
const struct v4l2_pix_format_mplane *other_format;
const struct v4l2_format_info *other_format_info;
if (!format)
return &rga3_formats[0];
format_info = v4l2_format_info(format->pixelformat);
other_format = is_output ? &ctx->out.pix : &ctx->in.pix;
other_format_info = v4l2_format_info(other_format->pixelformat);
if ((v4l2_is_format_rgb(format_info) &&
v4l2_is_format_yuv(other_format_info)) ||
(v4l2_is_format_yuv(format_info) &&
v4l2_is_format_rgb(other_format_info))) {
/*
* The RGA3 only supports BT601, BT709 and BT2020 RGB<->YUV conversions
* Additionally BT709 and BT2020 only support limited range YUV.
*/
switch (format->ycbcr_enc) {
case V4L2_YCBCR_ENC_601:
/* supports full and limited range */
break;
case V4L2_YCBCR_ENC_709:
case V4L2_YCBCR_ENC_BT2020:
format->quantization = V4L2_QUANTIZATION_LIM_RANGE;
break;
default:
format->ycbcr_enc = V4L2_YCBCR_ENC_601;
format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
break;
}
}
for (i = 0; i < ARRAY_SIZE(rga3_formats); i++) {
if (!is_output && !rga3_can_capture(&rga3_formats[i]))
continue;
if (rga3_formats[i].fourcc == format->pixelformat)
return &rga3_formats[i];
}
format->pixelformat = rga3_formats[0].fourcc;
return &rga3_formats[0];
}
const struct rga_hw rga3_hw = {
.card_type = "rga3",
.has_internal_iommu = false,
.cmdbuf_size = RGA3_CMDBUF_SIZE,
.min_width = RGA3_MIN_WIDTH,
.min_height = RGA3_MIN_HEIGHT,
/* use output size, as it's a bit smaller than the input size */
.max_width = RGA3_MAX_OUTPUT_WIDTH,
.max_height = RGA3_MAX_OUTPUT_HEIGHT,
.max_scaling_factor = RGA3_MAX_SCALING_FACTOR,
.stride_alignment = 16,
.features = 0,
.setup_cmdbuf = rga3_hw_setup_cmdbuf,
.start = rga3_hw_start,
.handle_irq = rga3_handle_irq,
.get_version = rga3_get_version,
.enum_format = rga3_enum_format,
.adjust_and_map_format = rga3_adjust_and_map_format,
};
@@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) Pengutronix e.K.
* Author: Sven Püschel <s.pueschel@pengutronix.de>
*/
#ifndef __RGA3_HW_H__
#define __RGA3_HW_H__
#include <linux/bits.h>
#include <linux/types.h>
#define RGA3_CMDBUF_SIZE 0xb8
#define RGA3_MIN_WIDTH 128
#define RGA3_MIN_HEIGHT 128
#define RGA3_MAX_INPUT_WIDTH (8192 - 16)
#define RGA3_MAX_INPUT_HEIGHT (8192 - 16)
#define RGA3_MAX_OUTPUT_WIDTH (8192 - 64)
#define RGA3_MAX_OUTPUT_HEIGHT (8192 - 64)
#define RGA3_MAX_SCALING_FACTOR 8
#define RGA3_RESET_TIMEOUT 1000
/* Registers address */
/* sys reg */
#define RGA3_SYS_CTRL 0x000
#define RGA3_CMD_CTRL 0x004
#define RGA3_CMD_ADDR 0x008
#define RGA3_MI_GROUP_CTRL 0x00c
#define RGA3_ARQOS_CTRL 0x010
#define RGA3_VERSION_NUM 0x018
#define RGA3_VERSION_TIM 0x01c
#define RGA3_INT_EN 0x020
#define RGA3_INT_RAW 0x024
#define RGA3_INT_MSK 0x028
#define RGA3_INT_CLR 0x02c
#define RGA3_RO_SRST 0x030
#define RGA3_STATUS0 0x034
#define RGA3_SCAN_CNT 0x038
#define RGA3_CMD_STATE 0x040
/* cmd reg */
#define RGA3_WIN0_RD_CTRL 0x100
#define RGA3_FIRST_CMD_REG RGA3_WIN0_RD_CTRL
#define RGA3_WIN0_Y_BASE 0x110
#define RGA3_WIN0_U_BASE 0x114
#define RGA3_WIN0_V_BASE 0x118
#define RGA3_WIN0_VIR_STRIDE 0x11c
#define RGA3_WIN0_FBC_OFF 0x120
#define RGA3_WIN0_SRC_SIZE 0x124
#define RGA3_WIN0_ACT_OFF 0x128
#define RGA3_WIN0_ACT_SIZE 0x12c
#define RGA3_WIN0_DST_SIZE 0x130
#define RGA3_WIN0_SCL_FAC 0x134
#define RGA3_WIN0_UV_VIR_STRIDE 0x138
#define RGA3_WIN1_RD_CTRL 0x140
#define RGA3_WIN1_Y_BASE 0x150
#define RGA3_WIN1_U_BASE 0x154
#define RGA3_WIN1_V_BASE 0x158
#define RGA3_WIN1_VIR_STRIDE 0x15c
#define RGA3_WIN1_FBC_OFF 0x160
#define RGA3_WIN1_SRC_SIZE 0x164
#define RGA3_WIN1_ACT_OFF 0x168
#define RGA3_WIN1_ACT_SIZE 0x16c
#define RGA3_WIN1_DST_SIZE 0x170
#define RGA3_WIN1_SCL_FAC 0x174
#define RGA3_WIN1_UV_VIR_STRIDE 0x178
#define RGA3_OVLP_CTRL 0x180
#define RGA3_OVLP_OFF 0x184
#define RGA3_OVLP_TOP_KEY_MIN 0x188
#define RGA3_OVLP_TOP_KEY_MAX 0x18c
#define RGA3_OVLP_TOP_CTRL 0x190
#define RGA3_OVLP_BOT_CTRL 0x194
#define RGA3_OVLP_TOP_ALPHA 0x198
#define RGA3_OVLP_BOT_ALPHA 0x19c
#define RGA3_WR_CTRL 0x1a0
#define RGA3_WR_FBCE_CTRL 0x1a4
#define RGA3_WR_VIR_STRIDE 0x1a8
#define RGA3_WR_PL_VIR_STRIDE 0x1ac
#define RGA3_WR_Y_BASE 0x1b0
#define RGA3_WR_U_BASE 0x1b4
#define RGA3_WR_V_BASE 0x1b8
/* Registers value */
#define RGA3_COLOR_FMT_YUV420 0x0
#define RGA3_COLOR_FMT_YUV422 0x1
#define RGA3_COLOR_FMT_YUV420_10B 0x2
#define RGA3_COLOR_FMT_YUV422_10B 0x3
/*
* Use memory ordering names
* instead of the datasheet naming RGB formats in big endian order
*/
#define RGA3_COLOR_FMT_BGR565 0x4
#define RGA3_COLOR_FMT_BGR888 0x5
#define RGA3_COLOR_FMT_FIRST_HAS_ALPHA RGA3_COLOR_FMT_BGRA8888
#define RGA3_COLOR_FMT_BGRA8888 0x6
#define RGA3_COLOR_FMT_LAST_OUTPUT RGA3_COLOR_FMT_BGRA8888
/* the following are only supported as inputs */
#define RGA3_COLOR_FMT_ABGR8888 0x7
/*
* the following seem to be unnecessary,
* as they can be achieved with RB swaps
*/
#define RGA3_COLOR_FMT_RGBA8888 0x8
#define RGA3_COLOR_FMT_ARGB8888 0x9
#define RGA3_RDWR_FORMAT_SEMI_PLANAR 0x1
#define RGA3_RDWR_FORMAT_INTERLEAVED 0x2
#define RGA3_CMD_MODE_MASTER 0x1
#define RGA3_WIN_CSC_MODE_BT601_L 0x0
#define RGA3_WIN_CSC_MODE_BT709_L 0x1
#define RGA3_WIN_CSC_MODE_BT601_F 0x2
#define RGA3_WIN_CSC_MODE_BT2020_L 0x3
/* RGA masks */
/* SYS_CTRL */
#define RGA3_CCLK_SRESET BIT(4)
#define RGA3_ACLK_SRESET BIT(3)
#define RGA3_CMD_MODE BIT(1)
/* CMD_CTRL */
#define RGA3_CMD_LINE_START_PULSE BIT(0)
/* VERSION_NUM */
#define RGA3_VERSION_NUM_MAJOR GENMASK(31, 28)
#define RGA3_VERSION_NUM_MINOR GENMASK(27, 20)
/* INT_* */
#define RGA3_INT_FRM_DONE BIT(0)
#define RGA3_INT_DMA_READ_BUS_ERR BIT(2)
#define RGA3_INT_WIN0_FBC_DEC_ERR BIT(5)
#define RGA3_INT_WIN0_HOR_ERR BIT(6)
#define RGA3_INT_WIN0_VER_ERR BIT(7)
#define RGA3_INT_WR_VER_ERR BIT(13)
#define RGA3_INT_WR_HOR_ERR BIT(14)
#define RGA3_INT_WR_BUS_ERR BIT(15)
#define RGA3_INT_WIN0_IN_FIFO_WR_ERR BIT(16)
#define RGA3_INT_WIN0_IN_FIFO_RD_ERR BIT(17)
#define RGA3_INT_WIN0_HOR_FIFO_WR_ERR BIT(18)
#define RGA3_INT_WIN0_HOR_FIFO_RD_ERR BIT(19)
#define RGA3_INT_WIN0_VER_FIFO_WR_ERR BIT(20)
#define RGA3_INT_WIN0_VER_FIFO_RD_ERR BIT(21)
/* RO_SRST */
#define RGA3_RO_SRST_DONE GENMASK(5, 0)
/* *_SIZE */
#define RGA3_HEIGHT GENMASK(28, 16)
#define RGA3_WIDTH GENMASK(12, 0)
/* SCL_FAC */
#define RGA3_SCALE_VER_FAC GENMASK(31, 16)
#define RGA3_SCALE_HOR_FAC GENMASK(15, 0)
/* WINx_CTRL */
#define RGA3_WIN_CSC_MODE GENMASK(27, 26)
#define RGA3_WIN_R2Y BIT(25)
#define RGA3_WIN_Y2R BIT(24)
#define RGA3_WIN_SCALE_VER_UP BIT(23)
#define RGA3_WIN_SCALE_VER_BYPASS BIT(22)
#define RGA3_WIN_SCALE_HOR_UP BIT(21)
#define RGA3_WIN_SCALE_HOR_BYPASS BIT(20)
#define RGA3_WIN_YC_SWAP BIT(13)
#define RGA3_WIN_RBUV_SWAP BIT(12)
#define RGA3_WIN_RD_FORMAT GENMASK(9, 8)
#define RGA3_WIN_PIC_FORMAT GENMASK(7, 4)
#define RGA3_WIN_ENABLE BIT(0)
/* COLOR_CTRL */
#define RGA3_OVLP_GLOBAL_ALPHA GENMASK(23, 16)
#define RGA3_OVLP_COLOR_MODE BIT(0)
/* ALPHA_CTRL */
#define RGA3_ALPHA_SELECT_MODE BIT(4)
#define RGA3_ALPHA_BLEND_MODE GENMASK(3, 2)
/* WR_CTRL */
#define RGA3_WR_YC_SWAP BIT(20)
#define RGA3_WR_SW_OUTSTANDING_MAX GENMASK(18, 13)
#define RGA3_WR_RBUV_SWAP BIT(12)
#define RGA3_WR_FORMAT GENMASK(9, 8)
#define RGA3_WR_PIC_FORMAT GENMASK(7, 4)
struct rga3_fmt {
u32 fourcc;
u8 hw_format;
bool rbuv_swap;
bool yc_swap;
};
#endif