media: rockchip: fec: init driver for rv1126b

Signed-off-by: Mingwei Yan <mingwei.yan@rock-chips.com>
Signed-off-by: Xu Hongfei <xuhf@rock-chips.com>
Change-Id: I2fa7a5b68c56fadcd664299c987ee4c227f998c7
This commit is contained in:
Mingwei Yan
2025-01-06 16:06:53 +08:00
committed by Tao Huang
parent 2ea7714258
commit 3c1a5874b0
14 changed files with 1653 additions and 0 deletions

View File

@@ -3,6 +3,7 @@
comment "Rockchip media platform drivers"
source "drivers/media/platform/rockchip/cif/Kconfig"
source "drivers/media/platform/rockchip/fec/Kconfig"
source "drivers/media/platform/rockchip/flexbus_cif/Kconfig"
source "drivers/media/platform/rockchip/isp1/Kconfig"
source "drivers/media/platform/rockchip/isp/Kconfig"

View File

@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += cif/
obj-y += fec/
obj-y += flexbus_cif/
obj-y += isp1/
obj-y += isp/

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
config VIDEO_ROCKCHIP_FEC
tristate "Rockchip Fisheye Correction Sub System driver"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_ROCKCHIP || COMPILE_TEST
select VIDEO_V4L2_SUBDEV_API
select VIDEOBUF2_CMA_SG
select VIDEOBUF2_VMALLOC
default n
help
Support for FEC on the rockchip SoC.
If you are using a Rockchip SoC with an integrated FEC module
and require FEC, say Y or M here.

View File

@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIDEO_ROCKCHIP_FEC) += video_rkfec.o
video_rkfec-objs += hw.o \
fec_offline.o \
procfs.o

View File

@@ -0,0 +1,626 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Rockchip Electronics Co., Ltd. */
#include <linux/version.h>
#include <linux/rk-video-format.h>
#include "fec_offline.h"
#include "hw.h"
#include "procfs.h"
#include "regs.h"
int rkfec_debug;
module_param_named(debug, rkfec_debug, int, 0644);
MODULE_PARM_DESC(debug, "Debug level (0-6)");
#if IS_LINUX_VERSION_AT_LEAST_6_1
#define GET_SG_TABLE(mem_ops, off_buf) mem_ops->cookie(&(off_buf)->vb, (off_buf)->mem)
#else
#define GET_SG_TABLE(mem_ops, off_buf) mem_ops->cookie((off_buf)->mem)
#endif
static void buf_del(struct file *file, int fd, bool is_all);
#if IS_LINUX_VERSION_AT_LEAST_6_1
static void init_vb2(struct rkfec_offline_dev *ofl,
struct rkfec_offline_buf *buf)
{
struct rkfec_hw_dev *hw = ofl->hw;
unsigned long attrs = DMA_ATTR_NO_KERNEL_MAPPING;
if (!buf)
return;
memset(&buf->vb, 0, sizeof(buf->vb));
ofl->vb2_queue.gfp_flags = GFP_KERNEL | GFP_DMA32;
ofl->vb2_queue.dma_dir = DMA_BIDIRECTIONAL;
if (hw->is_dma_config)
attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
ofl->vb2_queue.dma_attrs = attrs;
buf->vb.vb2_queue = &ofl->vb2_queue;
}
#endif
static int buf_alloc(struct file *file, struct rkfec_buf *info)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
struct rkfec_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkfec_offline_buf *buf;
struct dma_buf *dbuf;
int fd, size;
void *mem;
info->buf_fd = -1;
size = PAGE_ALIGN(info->size);
if (!size)
return -EINVAL;
buf = kzalloc(sizeof(struct rkfec_offline_buf), GFP_KERNEL);
if (!buf)
return -ENOMEM;
#if IS_LINUX_VERSION_AT_LEAST_6_1
init_vb2(ofl, buf);
mem = ops->alloc(&buf->vb, hw->dev, info->size);
#else
mem = ops->alloc(hw->dev, DMA_ATTR_NO_KERNEL_MAPPING, size,
DMA_BIDIRECTIONAL, GFP_KERNEL | GFP_DMA32);
#endif
if (IS_ERR_OR_NULL(mem)) {
v4l2_err(&ofl->v4l2_dev, "failed to alloc dmabuf\n");
goto err_alloc;
}
#if IS_LINUX_VERSION_AT_LEAST_6_1
dbuf = ops->get_dmabuf(&buf->vb, mem, O_RDWR);
#else
dbuf = ops->get_dmabuf(mem, O_RDWR);
#endif
if (IS_ERR_OR_NULL(dbuf)) {
v4l2_err(&ofl->v4l2_dev, "failed to get dmabuf\n");
goto err_dmabuf;
}
fd = dma_buf_fd(dbuf, O_CLOEXEC);
if (fd < 0) {
v4l2_err(&ofl->v4l2_dev, "failed to get dmabuf fd\n");
goto err_dmabuf_fd;
}
get_dma_buf(dbuf);
info->buf_fd = fd;
buf->fd = fd;
buf->file = file;
buf->dbuf = dbuf;
buf->mem = mem;
buf->memory = RKFEC_MEMORY_MMAP;
ops->prepare(buf->mem);
mutex_lock(&hw->dev_lock);
list_add_tail(&buf->list, &ofl->list);
mutex_unlock(&hw->dev_lock);
v4l2_dbg(1, rkfec_debug, &ofl->v4l2_dev, "%s file:%p, fd:%d dbuf:%p size %d\n",
__func__, file, fd, dbuf, info->size);
return 0;
err_dmabuf_fd:
dma_buf_put(dbuf);
err_dmabuf:
ops->put(mem);
err_alloc:
kfree(buf);
return -ENOMEM;
}
static struct rkfec_offline_buf *buf_add(struct file *file, int fd, int size)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
struct rkfec_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkfec_offline_buf *buf = NULL, *next = NULL;
struct dma_buf *dbuf;
void *mem = NULL;
bool need_add = true;
dbuf = dma_buf_get(fd);
if (IS_ERR_OR_NULL(dbuf)) {
v4l2_err(&ofl->v4l2_dev, "invalid dmabuf fd:%d\n", fd);
return buf;
}
if (size && dbuf->size < size) {
v4l2_err(&ofl->v4l2_dev,
"input fd:%d size error:%zu < %u\n",
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->fd == fd && buf->dbuf == dbuf) {
need_add = false;
break;
}
}
if (need_add) {
buf = kzalloc(sizeof(struct rkfec_offline_buf), GFP_KERNEL);
if (!buf)
goto err_kzalloc;
#if IS_LINUX_VERSION_AT_LEAST_6_1
init_vb2(ofl, buf);
mem = ops->attach_dmabuf(&buf->vb, hw->dev, dbuf, dbuf->size);
#else
mem = ops->attach_dmabuf(hw->dev, dbuf, dbuf->size,
DMA_BIDIRECTIONAL);
#endif
if (IS_ERR(mem)) {
v4l2_err(&ofl->v4l2_dev, "failed to attach dmabuf, fd:%d\n", fd);
goto err_attach;
}
if (ops->map_dmabuf(mem)) {
v4l2_err(&ofl->v4l2_dev, "failed to map, fd:%d\n", fd);
ops->detach_dmabuf(mem);
goto err_map;
}
buf->fd = fd;
buf->file = file;
buf->dbuf = dbuf;
buf->mem = mem;
buf->memory = RKFEC_MEMORY_DMABUF;
list_add_tail(&buf->list, &ofl->list);
v4l2_dbg(1, rkfec_debug, &ofl->v4l2_dev,
"%s file:%p fd:%d dbuf:%p size:%d\n",
__func__, file, fd, dbuf, size);
} else {
dma_buf_put(dbuf);
}
mutex_unlock(&hw->dev_lock);
return buf;
err_map:
ops->detach_dmabuf(mem);
err_attach:
dma_buf_put(dbuf);
kfree(buf);
err_kzalloc:
mutex_unlock(&hw->dev_lock);
return NULL;
}
static void buf_del(struct file *file, int fd, bool is_all)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
struct rkfec_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *ops = hw->mem_ops;
struct rkfec_offline_buf *buf, *next;
mutex_lock(&hw->dev_lock);
list_for_each_entry_safe(buf, next, &ofl->list, list) {
if ((is_all || buf->fd == fd)) {
v4l2_dbg(1, rkfec_debug, &ofl->v4l2_dev,
"%s file:%p fd:%d dbuf:%p, memory:%d\n",
__func__, file, buf->fd, buf->dbuf, buf->memory);
if (buf->memory == RKFEC_MEMORY_DMABUF) {
ops->unmap_dmabuf(buf->mem);
ops->detach_dmabuf(buf->mem);
} else {
ops->put(buf->mem);
}
dma_buf_put(buf->dbuf);
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 int fec_running(struct file *file, struct rkfec_in_out *buf)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
struct rkfec_hw_dev *hw = ofl->hw;
const struct vb2_mem_ops *mem_ops = hw->mem_ops;
struct sg_table *sg_talbe;
struct rkfec_offline_buf *off_buf;
u32 in_fmt, out_fmt;
u32 rd_mode, wr_mode;
u32 val;
u32 in_w = buf->in_width, in_h = buf->in_height;
u32 out_w = buf->out_width, out_h = buf->out_height;
u32 in_stride, out_stride_y, out_stride_uv;
u32 in_uv_offset, out_uv_offset;
void __iomem *base = ofl->hw->base_addr;
int ret = -EINVAL;
ktime_t t = 0;
s64 us = 0;
t = ktime_get();
v4l2_dbg(3, rkfec_debug, &ofl->v4l2_dev,
"%s enter %dx%d->%dx%d format(in:%c%c%c%c out:%c%c%c%c)\n",
__func__, in_w, in_h, out_w, out_h,
buf->in_fourcc, buf->in_fourcc >> 8,
buf->in_fourcc >> 16, buf->in_fourcc >> 24,
buf->out_fourcc, buf->out_fourcc >> 8,
buf->out_fourcc >> 16, buf->out_fourcc >> 24);
//clk tosee
init_completion(&ofl->cmpl);
switch (buf->in_fourcc) {
case V4L2_PIX_FMT_NV12:
in_fmt = SW_FEC_RD_FMT(1);
rd_mode = SW_FEC_RD_MODE(0);
in_stride = (buf->buf_cfg.in_stride + 15) / 16 * 16;
in_uv_offset = in_stride * in_h;
break;
case V4L2_PIX_FMT_TILE420:
in_fmt = SW_FEC_RD_FMT(0);
rd_mode = SW_FEC_RD_MODE(1);
in_stride = (buf->buf_cfg.in_stride * 6 + 15) / 16 * 16;
in_uv_offset = in_stride * in_h;
break;
default:
v4l2_err(&ofl->v4l2_dev,
"no support in format:%c%c%c%c\n",
buf->in_fourcc, buf->in_fourcc >> 8,
buf->in_fourcc >> 16, buf->in_fourcc >> 24);
return -EINVAL;
}
switch (buf->out_fourcc) {
case V4L2_PIX_FMT_NV12:
out_fmt = SW_FEC_WR_FMT(1);
wr_mode = SW_FEC_WR_MODE(0);
out_stride_y = (buf->buf_cfg.out_stride + 15) / 16 * 16;
out_stride_uv = out_stride_y;
out_uv_offset = out_stride_y * out_h;
break;
case V4L2_PIX_FMT_TILE420:
out_fmt = SW_FEC_WR_FMT(0);
wr_mode = SW_FEC_WR_MODE(1);
out_stride_y = (buf->buf_cfg.out_stride * 6 + 15) / 16 * 16;
out_stride_uv = out_stride_y;
out_uv_offset = out_stride_y * out_h;
break;
case V4L2_PIX_FMT_FBC0:
out_fmt = SW_FEC_WR_FMT(0);
wr_mode = SW_FEC_WR_MODE(2);
out_stride_y = (buf->buf_cfg.out_stride + 63) / 64 * 384;
out_stride_uv = (buf->buf_cfg.out_stride + 63) / 64 * 16;
// Head stride is c channel
out_uv_offset = out_stride_uv * out_h / 4;
break;
case V4L2_PIX_FMT_QUAD:
out_fmt = SW_FEC_WR_FMT(0);
wr_mode = SW_FEC_WR_MODE(3);
out_stride_y = (buf->buf_cfg.out_stride * 3 + 15) / 16 * 16;
out_stride_uv = out_stride_y;
out_uv_offset = out_stride_y * out_h;
break;
default:
v4l2_err(&ofl->v4l2_dev, "no support out format:%c%c%c%c\n",
buf->out_fourcc, buf->out_fourcc >> 8,
buf->out_fourcc >> 16, buf->out_fourcc >> 24);
return -EINVAL;
}
/* input picture buf */
off_buf = buf_add(file, buf->buf_cfg.in_pic_fd, buf->buf_cfg.in_size);
if (!off_buf)
return -ENOMEM;
sg_talbe = GET_SG_TABLE(mem_ops, off_buf);
if (!sg_talbe)
goto free_buf;
val = sg_dma_address(sg_talbe->sgl);
val += buf->buf_cfg.in_offs;
writel(val, base + RKFEC_RD_Y_BASE);
val += in_uv_offset;
writel(val, base + RKFEC_RD_C_BASE);
/* output picture buf */
off_buf = buf_add(file, buf->buf_cfg.out_pic_fd, buf->buf_cfg.out_size);
if (!off_buf)
goto free_buf;
sg_talbe = GET_SG_TABLE(mem_ops, off_buf);
if (!sg_talbe)
goto free_buf;
val = sg_dma_address(sg_talbe->sgl);
if (buf->out_fourcc == V4L2_PIX_FMT_FBC0) {
val += buf->buf_cfg.out_offs;
writel(val, base + RKFEC_WR_C_BASE);
val += out_uv_offset;
writel(val, base + RKFEC_WR_Y_BASE);
} else {
val += buf->buf_cfg.out_offs;
writel(val, base + RKFEC_WR_Y_BASE);
val += out_uv_offset;
writel(val, base + RKFEC_WR_C_BASE);
}
/* lut buf */
off_buf = buf_add(file, buf->buf_cfg.lut_fd, buf->buf_cfg.lut_size);
if (!off_buf)
goto free_buf;
sg_talbe = GET_SG_TABLE(mem_ops, off_buf);
if (!sg_talbe)
goto free_buf;
val = sg_dma_address(sg_talbe->sgl);
writel(val, base + RKFEC_LUT_BASE);
//fmt
val = in_fmt | out_fmt | rd_mode | wr_mode;
writel(val, base + RKFEC_CTRL);
//stride
val = FEC_RD_VIR_STRIDE_Y(in_stride / 4) | FEC_RD_VIR_STRIDE_C(in_stride / 4);
writel(val, base + RKFEC_RD_VIR_STRIDE);
val = FEC_WR_VIR_STRIDE_Y(out_stride_y / 4) | FEC_WR_VIR_STRIDE_C(out_stride_uv / 4);
writel(val, base + RKFEC_WR_VIR_STRIDE);
//with height lut_size
val = SW_FEC_SRC_WIDTH(buf->in_width) | Sw_FEC_SRC_HEIGHT(buf->in_height);
writel(val, base + RKFEC_SRC_SIZE);
val = SW_FEC_DST_WIDTH(buf->out_width) | Sw_FEC_DST_HEIGHT(buf->out_height);
writel(val, base + RKFEC_DST_SIZE);
val = SW_LUT_SIZE(buf->buf_cfg.lut_size);
writel(val, base + RKFEC_LUT_SIZE);
//new bg val
val = SW_BG_Y_VALUE(buf->bg_val.bg_y) |
SW_BG_U_VALUE(buf->bg_val.bg_u) |
SW_BG_Y_VALUE(buf->bg_val.bg_v);
writel(val, base + RKFEC_BG_VALUE);
//core_ctrl
val = SW_FEC_BIC_MODE(buf->core_ctrl.bic_mode) |
SW_LUT_DENSITY(buf->core_ctrl.density) |
SW_FEC_BORDER_MODE(buf->core_ctrl.border_mode) |
SW_FEC_PBUF_CRS_DIS(buf->core_ctrl.pbuf_crs_dis) |
SW_FEC_CRS_BUF_MODE(buf->core_ctrl.buf_mode) |
SYS_FEC_ST;
writel(val, base + RKFEC_CORE_CTRL);
writel(0, base + RKFEC_CLK_DIS);
writel(0x1c, base + RKFEC_CACHE_MAX_READS);
writel(0x27, base + RKFEC_CACHE_CTRL);
//update
writel(SYS_FEC_FORCE_UPD, base + RKFEC_UPD);
//start
if (!hw->is_shutdown)
writel(SYS_FEC_ST, base + RKFEC_STRT);
ofl->state = RKFEC_START;
// add info for procfs
ofl->in_fmt.width = in_w;
ofl->in_fmt.height = in_h;
ofl->in_fmt.pixelformat = buf->in_fourcc;
ofl->in_fmt.bytesperline = buf->buf_cfg.in_stride;
ofl->in_fmt.sizeimage = buf->buf_cfg.in_size;
ofl->out_fmt.width = out_w;
ofl->out_fmt.height = out_h;
ofl->out_fmt.pixelformat = buf->out_fourcc;
ofl->out_fmt.bytesperline = buf->buf_cfg.out_stride;
ofl->out_fmt.sizeimage = buf->buf_cfg.out_size;
ofl->curr_frame.fs_seq++;
ofl->curr_frame.fs_timestamp = ktime_get_ns();
ret = wait_for_completion_timeout(&ofl->cmpl, msecs_to_jiffies(300));
if (!ret) {
v4l2_err(&ofl->v4l2_dev, "fec working timeout\n");
ret = -EAGAIN;
} else {
ret = 0;
}
us = ktime_us_delta(ktime_get(), t);
v4l2_dbg(3, rkfec_debug, &ofl->v4l2_dev,
"%s exit ret:%d, time:%lldus\n", __func__, ret, us);
ofl->debug.interval = us;
ofl->state = RKFEC_FRAME_END;
if (!ret) {
ofl->curr_frame.fe_seq++;
ofl->curr_frame.fe_timestamp = ktime_get_ns();
if (ofl->curr_frame.fe_seq > ofl->prev_frame.fe_seq &&
ofl->curr_frame.fe_seq - ofl->prev_frame.fe_seq > 1)
ofl->debug.frameloss += ofl->curr_frame.fe_seq - ofl->prev_frame.fe_seq - 1;
} else {
ofl->debug.frameloss++;
}
ofl->prev_frame = ofl->curr_frame;
return ret;
free_buf:
v4l2_dbg(3, rkfec_debug, &ofl->v4l2_dev,
"%s sg_talbe error\n", __func__);
buf_del(file, 0, true);
return ret;
}
static long rkfec_ofl_ioctl(struct file *file, void *fh,
bool valid_prio,
unsigned int cmd, void *arg)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
struct rkfec_offline_buf *buf = NULL;
long ret = 0;
ofl->pm_need_wait = true;
if (!arg) {
ret = -EINVAL;
goto out;
}
switch (cmd) {
case RKFEC_CMD_IN_OUT:
ret = fec_running(file, arg);
break;
case RKFEC_CMD_BUF_ALLOC:
buf_alloc(file, arg);
break;
case RKFEC_CMD_BUF_ADD:
buf = buf_add(file, *(int *)arg, 0);
if (!buf)
ret = -ENOMEM;
break;
case RKFEC_CMD_BUF_DEL:
buf_del(file, *(int *)arg, false);
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 = rkfec_ofl_ioctl,
};
static int ofl_open(struct file *file)
{
struct rkfec_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, rkfec_debug, &ofl->v4l2_dev, "%s ret:%d\n", __func__, ret);
return (ret > 0) ? 0 : ret;
}
static int ofl_release(struct file *file)
{
struct rkfec_offline_dev *ofl = video_drvdata(file);
int ret;
v4l2_dbg(1, rkfec_debug, &ofl->v4l2_dev, "%s\n", __func__);
ret = v4l2_fh_release(file);
if (!ret) {
buf_del(file, 0, true);
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 = "rkfec_offline",
.vfl_dir = VFL_DIR_RX,
.fops = &offline_fops,
.ioctl_ops = &offline_ioctl_ops,
.minor = -1,
.release = video_device_release_empty,
};
void rkfec_offline_irq(struct rkfec_hw_dev *hw, u32 irq)
{
struct rkfec_offline_dev *ofl = &hw->ofl_dev;
v4l2_dbg(3, rkfec_debug, &ofl->v4l2_dev, "%s 0x%x\n", __func__, irq);
if (!completion_done(&ofl->cmpl))
complete(&ofl->cmpl);
}
int rkfec_register_offline(struct rkfec_hw_dev *hw)
{
struct rkfec_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;
vfd = &ofl->vfd;
vfd->device_caps = V4L2_CAP_STREAMING;
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);
rkfec_offline_proc_init(ofl);
ofl->state = RKFEC_STOP;
//todo
init_completion(&ofl->pm_cmpl);
memset(&ofl->vb2_queue, 0, sizeof(ofl->vb2_queue));
memset(&ofl->curr_frame, 0, sizeof(ofl->curr_frame));
memset(&ofl->prev_frame, 0, sizeof(ofl->prev_frame));
v4l2_info(&ofl->v4l2_dev, "%s success\n", __func__);
return 0;
unreg_v4l2:
mutex_destroy(&ofl->apilock);
v4l2_device_unregister(v4l2_dev);
return ret;
}
void rkfec_unregister_offline(struct rkfec_hw_dev *hw)
{
struct rkfec_offline_dev *ofl = &hw->ofl_dev;
rkfec_offline_proc_cleanup(&hw->ofl_dev);
mutex_destroy(&ofl->apilock);
video_unregister_device(&ofl->vfd);
v4l2_device_unregister(&ofl->v4l2_dev);
}
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(DMA_BUF);

View File

@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2025 Rockchip Electronics Co., Ltd. */
#ifndef _RKFEC_FEC_OFFLINE_H
#define _RKFEC_FEC_OFFLINE_H
#include <linux/clk.h>
#include <linux/delay.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>
#include <linux/pm_runtime.h>
#include <uapi/linux/rk-fec-config.h>
#include <uapi/linux/rk-video-format.h>
extern int rkfec_debug;
/**
* enum rkfec_memory - type of memory model used to make the buffers visible
* on userspace.
*
* @RKFEC_MEMORY_UNKNOWN: Buffer status is unknown or it is not used yet on
* userspace.
* @RKFEC_MEMORY_MMAP: The buffers are allocated by the Kernel and it is
* memory mapped via mmap() ioctl.
* @RKFEC_MEMORY_DMABUF: The buffers are passed to userspace via DMA buffer.
*/
enum rkfec_memory {
RKFEC_MEMORY_UNKNOWN = 0,
RKFEC_MEMORY_MMAP = 1,
RKFEC_MEMORY_DMABUF = 2,
};
/*
* V I D E O I M A G E F O R M A T
*/
struct rkfec_pix_format {
u32 width;
u32 height;
u32 pixelformat;
u32 offset;
u32 bytesperline;
u32 sizeimage;
};
enum rkfec_state {
RKFEC_FRAME_END = BIT(1),
RKFEC_STOP = BIT(16),
RKFEC_START = BIT(17),
RKFEC_ERROR = BIT(18),
};
struct rkfec_frame_info {
u32 fs_seq;
u32 fe_seq;
u64 fs_timestamp;
u64 fe_timestamp;
};
struct rkfec_debug_info {
u32 interval;
u32 frameloss;
};
struct rkfec_offline_dev {
struct rkfec_hw_dev *hw;
struct v4l2_device v4l2_dev;
struct video_device vfd;
struct mutex apilock;
struct completion cmpl;
struct completion pm_cmpl;
struct list_head list;
bool pm_need_wait;
struct vb2_queue vb2_queue;
struct proc_dir_entry *procfs;
unsigned int isr_cnt;
unsigned int err_cnt;
unsigned int state;
unsigned int in_seq;
unsigned int out_seq;
struct rkfec_frame_info prev_frame;
struct rkfec_frame_info curr_frame;
struct rkfec_debug_info debug;
struct rkfec_pix_format in_fmt;
struct rkfec_pix_format out_fmt;
};
/*
* rkfec_offline_buf
* @memory: current memory type used
*/
struct rkfec_offline_buf {
struct list_head list;
struct vb2_buffer vb;
struct file *file;
struct dma_buf *dbuf;
void *mem;
int fd;
unsigned int memory;
};
int rkfec_register_offline(struct rkfec_hw_dev *hw);
void rkfec_unregister_offline(struct rkfec_hw_dev *hw);
void rkfec_offline_irq(struct rkfec_hw_dev *hw, u32 irq);
#endif

View File

@@ -0,0 +1,395 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Rockchip Electronics Co., Ltd. */
#include "fec_offline.h"
#include "hw.h"
#include "regs.h"
#include "version.h"
static const char * const rv1126b_fec_clks[] = {
"clk_fec",
"aclk_fec",
"hclk_fec",
};
static void rkfec_set_clk_rate(struct clk *clk, unsigned long rate)
{
clk_set_rate(clk, rate);
}
static void rkfec_soft_reset(struct rkfec_hw_dev *hw)
{
u32 val;
/* reset */
val = SYS_SOFT_RST_FBCE | SYS_SOFT_RST_ACLK;
writel(val, hw->base_addr + RKFEC_CLK_DIS);
udelay(10);
writel(~val, hw->base_addr + RKFEC_CLK_DIS);
if (hw->reset) {
reset_control_assert(hw->reset);
udelay(20);
reset_control_deassert(hw->reset);
udelay(20);
}
/* refresh iommu after reset */
if (hw->is_mmu) {
rockchip_iommu_disable(hw->dev);
rockchip_iommu_enable(hw->dev);
}
/* clk_dis */
val = SYS_FEC_LGC_CKG_DIS | SYS_FEC_RAM_CKG_DIS;
writel(val, hw->base_addr + RKFEC_CLK_DIS);
/* int en */
val = FRM_END_P_FEC;
writel(val, hw->base_addr + RKFEC_INT_EN);
}
static inline bool is_iommu_enable(struct device *dev)
{
struct device_node *iommu;
iommu = of_parse_phandle(dev->of_node, "iommus", 0);
if (!iommu) {
dev_info(dev, "no iommu attached, using non-iommu buffers\n");
return false;
} else if (!of_device_is_available(iommu)) {
dev_info(dev, "iommu is disable, using non-iommu buffers\n");
of_node_put(iommu);
return false;
}
of_node_put(iommu);
return true;
}
static void disable_sys_clk(struct rkfec_hw_dev *dev)
{
int i;
for (i = 0; i < dev->clks_num; i++)
clk_disable_unprepare(dev->clks[i]);
}
static int enable_sys_clk(struct rkfec_hw_dev *dev)
{
int i, ret;
for (i = 0; i < dev->clks_num; i++) {
ret = clk_prepare_enable(dev->clks[i]);
if (ret < 0)
goto err;
}
//tosee
rkfec_set_clk_rate(dev->clks[0],
dev->clk_rate_tbl[dev->clk_rate_tbl_num - 1].clk_rate * 1000000);
return 0;
err:
for (--i; i >= 0; --i)
clk_disable_unprepare(dev->clks[i]);
return ret;
}
static irqreturn_t rkfec_irq_hdl(int irq, void *ctx)
{
struct device *dev = ctx;
struct rkfec_hw_dev *hw_dev = dev_get_drvdata(dev);
void __iomem *base = hw_dev->base_addr;
unsigned int mis_val;
spin_lock(&hw_dev->irq_lock);
mis_val = readl(base + RKFEC_INT_MSK);
writel(mis_val, base + RKFEC_INT_CLR);
spin_unlock(&hw_dev->irq_lock);
v4l2_dbg(3, rkfec_debug, &hw_dev->ofl_dev.v4l2_dev,
"fec isr:0x%x\n", mis_val);
hw_dev->ofl_dev.isr_cnt++;
if (mis_val & FRM_END_P_FEC) {
mis_val &= -FRM_END_P_FEC;
rkfec_offline_irq(hw_dev, mis_val);
}
if (mis_val & (BIT(2) | BIT(3) | BIT(4) | BIT(5)))
hw_dev->ofl_dev.err_cnt++;
return IRQ_HANDLED;
}
static const struct fec_clk_info rv1126b_fec_clk_rate[] = {
{
.clk_rate = 300,
.refer_data = 1920,
}, {
.clk_rate = 400,
.refer_data = 2688,
}, {
.clk_rate = 500,
.refer_data = 3072,
}, {
.clk_rate = 600,
.refer_data = 3840,
}, {
.clk_rate = 702,
.refer_data = 4672,
}
};
static struct irqs_data rv1126b_fec_irqs[] = {
{"fec_irq", rkfec_irq_hdl},
};
static const struct fec_match_data rv1126b_fec_match_data = {
.fec_ver = RKFEC_V20,
.clks = rv1126b_fec_clks,
.clks_num = ARRAY_SIZE(rv1126b_fec_clks),
.clk_rate_tbl = rv1126b_fec_clk_rate,
.clk_rate_tbl_num = ARRAY_SIZE(rv1126b_fec_clk_rate),
.irqs = rv1126b_fec_irqs,
.num_irqs = ARRAY_SIZE(rv1126b_fec_irqs),
};
static const struct of_device_id rkfec_hw_of_match[] = {
{
.compatible = "rockchip,rv1126b-rkfec",
.data = &rv1126b_fec_match_data,
},
{},
};
static int rkfec_hw_probe(struct platform_device *pdev)
{
const struct fec_match_data *match_data;
struct device *dev = &pdev->dev;
struct rkfec_hw_dev *hw_dev;
struct resource *res;
int i, ret, irq;
bool is_mem_reserved = true;
dev_info(dev, "RK FEC Version: %d.%d.%d\n",
(RKFEC_DRIVER_VERSION >> 16) & 0xFF,
(RKFEC_DRIVER_VERSION >> 8) & 0xFF,
RKFEC_DRIVER_VERSION & 0xFF);
match_data = device_get_match_data(&pdev->dev);
if (!match_data) {
dev_err(dev, "no of match data provided\n");
return -EINVAL;
}
hw_dev = devm_kzalloc(dev, sizeof(*hw_dev), GFP_KERNEL);
if (!hw_dev)
return -ENOMEM;
dev_set_drvdata(dev, hw_dev);
hw_dev->dev = dev;
hw_dev->match_data = match_data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "get resource failed\n");
ret = -EINVAL;
goto err;
}
hw_dev->base_addr = devm_ioremap_resource(dev, res);
if (PTR_ERR(hw_dev->base_addr) == -EBUSY) {
resource_size_t offset = res->start;
resource_size_t size = resource_size(res);
hw_dev->base_addr = devm_ioremap(dev, offset, size);
}
if (IS_ERR(hw_dev->base_addr)) {
dev_err(dev, "ioremap failed\n");
ret = PTR_ERR(hw_dev->base_addr);
goto err;
}
/* three are irq names in dts */
spin_lock_init(&hw_dev->irq_lock);
for (i = 0; i < match_data->num_irqs; i++) {
irq = platform_get_irq_byname(pdev,
match_data->irqs[i].name);
if (irq < 0) {
dev_err(dev, "no irq %s in dts\n",
match_data->irqs[i].name);
ret = irq;
goto err;
}
ret = devm_request_irq(dev, irq,
match_data->irqs[i].irq_hdl,
IRQF_SHARED,
dev_driver_string(dev),
dev);
if (ret < 0) {
dev_err(dev, "request %s failed: %d\n",
match_data->irqs[i].name, ret);
goto err;
}
dev_err(dev, "request %s : %d\n",
match_data->irqs[i].name, irq);
}
for (i = 0; i < match_data->clks_num; i++) {
struct clk *clk = devm_clk_get(dev, match_data->clks[i]);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get %s\n",
match_data->clks[i]);
ret = PTR_ERR(clk);
goto err;
}
hw_dev->clks[i] = clk;
}
hw_dev->clks_num = match_data->clks_num;
hw_dev->clk_rate_tbl = match_data->clk_rate_tbl;
hw_dev->clk_rate_tbl_num = match_data->clk_rate_tbl_num;
hw_dev->reset = devm_reset_control_array_get(dev, false, false);
if (IS_ERR(hw_dev->reset)) {
dev_info(dev, "failed to get cru reset\n");
hw_dev->reset = NULL;
}
mutex_init(&hw_dev->dev_lock);
hw_dev->is_idle = true;
hw_dev->is_dma_config = true;
hw_dev->is_dma_sg_ops = true;
hw_dev->is_shutdown = false;
hw_dev->is_mmu = is_iommu_enable(dev);
ret = of_reserved_mem_device_init(dev);
if (ret) {
is_mem_reserved = false;
if (!hw_dev->is_mmu)
dev_info(dev, "No reserved memory region. default cma area!\n");
}
if (hw_dev->is_mmu && !is_mem_reserved)
hw_dev->is_dma_config = false;
hw_dev->mem_ops = &vb2_cma_sg_memops;
rkfec_register_offline(hw_dev);
dev_info(dev, "%s success\n", __func__);
pm_runtime_enable(&pdev->dev);
return 0;
err:
return ret;
}
static int rkfec_hw_remove(struct platform_device *pdev)
{
struct rkfec_hw_dev *hw_dev = platform_get_drvdata(pdev);
rkfec_unregister_offline(hw_dev);
pm_runtime_disable(&pdev->dev);
mutex_destroy(&hw_dev->dev_lock);
return 0;
}
static void rkfec_hw_shutdown(struct platform_device *pdev)
{
struct rkfec_hw_dev *hw_dev = platform_get_drvdata(pdev);
u32 val;
hw_dev->is_shutdown = true;
if (pm_runtime_active(&pdev->dev)) {
writel(0, hw_dev->base_addr + RKFEC_INT_EN);
val = SYS_SOFT_RST_FBCE | SYS_SOFT_RST_ACLK;
writel(val, hw_dev->base_addr + RKFEC_CLK_DIS);
udelay(10);
writel(~val, hw_dev->base_addr + RKFEC_CLK_DIS);
}
dev_info(&pdev->dev, "%s\n", __func__);
}
static int __maybe_unused rkfec_hw_runtime_suspend(struct device *dev)
{
struct rkfec_hw_dev *hw_dev = dev_get_drvdata(dev);
if (rkfec_debug >= 4)
dev_info(dev, "%s enter\n", __func__);
if (dev->power.runtime_status)
writel(0, hw_dev->base_addr + RKFEC_INT_EN);
disable_sys_clk(hw_dev);
if (rkfec_debug >= 4)
dev_info(dev, "%s exit\n", __func__);
return 0;
}
static int __maybe_unused rkfec_hw_runtime_resume(struct device *dev)
{
struct rkfec_hw_dev *hw_dev = dev_get_drvdata(dev);
if (rkfec_debug >= 4)
dev_info(dev, "%s enter\n", __func__);
enable_sys_clk(hw_dev);
rkfec_soft_reset(hw_dev);
if (dev->power.runtime_status) {
//toto
} else {
//toto
}
hw_dev->is_idle = true;
if (rkfec_debug >= 4)
dev_info(dev, "%s exit\n", __func__);
return 0;
}
static const struct dev_pm_ops rkfec_hw_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(rkfec_hw_runtime_suspend,
rkfec_hw_runtime_resume, NULL)
};
static struct platform_driver rkfec_hw_drv = {
.driver = {
.name = "rkfec_hw",
.of_match_table = of_match_ptr(rkfec_hw_of_match),
.pm = &rkfec_hw_pm_ops,
},
.probe = rkfec_hw_probe,
.remove = rkfec_hw_remove,
.shutdown = rkfec_hw_shutdown,
};
static int __init rkfec_hw_drv_init(void)
{
int ret;
ret = platform_driver_register(&rkfec_hw_drv);
return ret;
}
static void __exit rkfec_hw_drv_exit(void)
{
platform_driver_unregister(&rkfec_hw_drv);
}
module_init(rkfec_hw_drv_init);
module_exit(rkfec_hw_drv_exit);

View File

@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2025 Rockchip Electronics Co., Ltd. */
#ifndef _RKFEC_HW_H
#define _RKFEC_HW_H
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <media/videobuf2-cma-sg.h>
#include <media/videobuf2-dma-sg.h>
#include <soc/rockchip/rockchip_iommu.h>
#define FEC_MAX_BUS_CLK 4
enum rkfec_fec_ver {
RKFEC_V10 = 0x00, /* Version 1.0 of the FEC */
RKFEC_V20 = 0x20, /* Version 2.0 of the FEC */
};
struct fec_clk_info {
u32 clk_rate;
u32 refer_data;
};
struct irqs_data {
const char *name;
irqreturn_t (*irq_hdl)(int irq, void *ctx);
};
struct fec_match_data {
enum rkfec_fec_ver fec_ver;
int clks_num;
const char * const *clks;
int clk_rate_tbl_num;
const struct fec_clk_info *clk_rate_tbl;
struct irqs_data *irqs;
int num_irqs;
};
struct rkfec_hw_dev {
struct device *dev;
void __iomem *base_addr;
const struct fec_match_data *match_data;
const struct fec_clk_info *clk_rate_tbl;
struct reset_control *reset;
struct clk *clks[FEC_MAX_BUS_CLK];
struct rkfec_offline_dev ofl_dev;
int clk_rate_tbl_num;
int clks_num;
/* lock for hw */
struct mutex dev_lock;
/* lock for irq */
spinlock_t irq_lock;
const struct vb2_mem_ops *mem_ops;
bool is_mmu;
bool is_idle;
bool is_dma_config;
bool is_dma_sg_ops;
bool is_shutdown;
bool is_suspend;
};
#ifndef IS_LINUX_VERSION_AT_LEAST_6_1
#define IS_LINUX_VERSION_AT_LEAST_6_1 (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE)
#endif
#endif

View File

@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Rockchip Electronics Co., Ltd. */
#include <linux/io.h>
#include "fec_offline.h"
#include "hw.h"
#include "procfs.h"
#include "regs.h"
#include "version.h"
#ifdef CONFIG_PROC_FS
/************************offline************************/
static void offline_fec_show_hw(struct seq_file *p, struct rkfec_hw_dev *hw)
{
u32 val;
static const char * const wr_mode[] = {
"rast",
"til4x4",
"fbce",
"quad"
};
static const char * const bic_mode[] = {
"precise",
"spline",
"catrom",
"mitchell"
};
static const char * const lut_density[] = {
"16x8",
"32x16",
"4x4"
};
if (hw->dev->power.usage_count.counter <= 0) {
seq_printf(p, "\n%s\n", "HW close");
return;
}
val = readl(hw->base_addr + RKFEC_CTRL);
seq_printf(p, "%-10s RD_fmt:%s RD_mode:%s WR_fmt:%s WR_mode:%s WR_fbce:%s (0x%x)\n", "CTRL",
val & BIT(2) ? "semi" : "interleave",
(val >> 4) & 0x3 ? "semi" : "rast",
val & BIT(8) ? "semi" : "interleave", wr_mode[val >> 9],
val & BIT(13) ? "on" : "off", val);
val = readl(hw->base_addr + RKFEC_CORE_CTRL);
seq_printf(p, "%-10s Bic:%s Lut_density:%s, Border_fill:%s, Cross_fill:%s (0x%x)\n",
"CORE_CTRL",
bic_mode[(val >> 3) & 0x3], lut_density[(val >> 5) & 0x3],
val & BIT(7) ? "nearest" : "bg", val & BIT(10) ? "nearest" : "bg", val);
val = readl(hw->base_addr + RKFEC_RD_VIR_STRIDE);
seq_printf(p, "%-10s Y:%d C:%d\n", "RD_VIR", (val & 0x3FFF) * 4,
((val >> 16) & 0x3FFF) * 4);
val = readl(hw->base_addr + RKFEC_WR_VIR_STRIDE);
seq_printf(p, "%-10s Y:%d C:%d\n", "WR_VIR", (val & 0x3FFF) * 4,
((val >> 16) & 0x3FFF) * 4);
val = readl(hw->base_addr + RKFEC_BG_VALUE);
seq_printf(p, "%-10s Y:%d U:%d V:%d\n", "BG_VALUE", val & 0xFF,
(val >> 10) & 0xFF, (val >> 20) & 0xFF);
val = readl(hw->base_addr + RKFEC_LUT_SIZE);
seq_printf(p, "%-10s Size: %d\n", "LUT", val & 0x3FFFFF);
val = readl(hw->base_addr + RKFEC_STATUS0);
seq_printf(p, "%-10s 0x%x\n", "STATUS0", val & 0x3FFFFF);
val = readl(hw->base_addr + RKFEC_STATUS1);
seq_printf(p, "%-10s 0x%x\n", "STATUS1", val & 0x3FFFFF);
}
static int offline_fec_show(struct seq_file *p, void *v)
{
struct rkfec_offline_dev *ofl = p->private;
struct rkfec_hw_dev *hw = ofl->hw;
int i;
seq_printf(p, "%-10s Version:v%02x.%02x.%02x\n", ofl->v4l2_dev.name,
RKFEC_DRIVER_VERSION >> 16,
(RKFEC_DRIVER_VERSION & 0xff00) >> 8,
RKFEC_DRIVER_VERSION & 0x00ff);
for (i = 0; i < ofl->hw->clks_num; i++) {
seq_printf(p, "%-10s %ld\n", ofl->hw->match_data->clks[i],
clk_get_rate(ofl->hw->clks[i]));
}
seq_printf(p, "%-10s Cnt:%d ErrCnt:%d\n", "Interrupt", ofl->isr_cnt,
ofl->err_cnt);
seq_printf(p, "%-10s Format:%c%c%c%c Size:%dx%d Offset(%d) Sizeimage(%d)\n", "Input",
ofl->in_fmt.pixelformat, ofl->in_fmt.pixelformat >> 8,
ofl->in_fmt.pixelformat >> 16, ofl->in_fmt.pixelformat >> 24,
ofl->in_fmt.width, ofl->in_fmt.height, ofl->in_fmt.offset,
ofl->in_fmt.sizeimage);
seq_printf(p, "%-10s (frame:%d rate:%dms state:%s time:%dms frameloss:%d)\n",
"Fec offline",
ofl->curr_frame.fs_seq,
(u32)(ofl->curr_frame.fe_timestamp - ofl->prev_frame.fe_timestamp) / 1000,
(ofl->state & RKFEC_FRAME_END) ? "idle" : "working",
ofl->debug.interval / 1000,
ofl->debug.frameloss);
seq_printf(p, "%-10s Format:%c%c%c%c Size:%dx%d Sizeimage(%d) (frame:%d rate:%dms frameloss:%d\n",
"Output",
ofl->out_fmt.pixelformat,
ofl->out_fmt.pixelformat >> 8,
ofl->out_fmt.pixelformat >> 16,
ofl->out_fmt.pixelformat >> 24,
ofl->out_fmt.width,
ofl->out_fmt.height,
ofl->out_fmt.sizeimage,
ofl->curr_frame.fe_seq,
ofl->debug.interval / 1000, ofl->debug.frameloss);
offline_fec_show_hw(p, hw);
return 0;
}
static int offline_fec_open(struct inode *inode, struct file *file)
{
#if IS_LINUX_VERSION_AT_LEAST_6_1
struct rkfec_offline_dev *data = pde_data(inode);
#else
struct rkfec_offline_dev *data = PDE_DATA(inode);
#endif
return single_open(file, offline_fec_show, data);
}
static const struct proc_ops offline_ops = {
.proc_open = offline_fec_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
int rkfec_offline_proc_init(struct rkfec_offline_dev *dev)
{
dev->procfs = proc_create_data(dev->v4l2_dev.name, 0, NULL, &offline_ops, dev);
if (!dev->procfs)
return -EINVAL;
return 0;
}
void rkfec_offline_proc_cleanup(struct rkfec_offline_dev *dev)
{
if (dev->procfs)
remove_proc_entry(dev->v4l2_dev.name, NULL);
dev->procfs = NULL;
}
#endif /* CONFIG_PROC_FS */

View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2025 Rockchip Electronics Co., Ltd. */
#ifndef _RKFEC_PROCFS_H
#define _RKFEC_PROCFS_H
#include <linux/clk.h>
#include <linux/proc_fs.h>
#include <linux/sem.h>
#include <linux/seq_file.h>
#include <media/v4l2-common.h>
#ifdef CONFIG_PROC_FS
int rkfec_offline_proc_init(struct rkfec_offline_dev *dev);
void rkfec_offline_proc_cleanup(struct rkfec_offline_dev *dev);
#else
static inline int rkfec_offline_proc_init(struct rkfec_offline_dev *dev) { return 0; }
static inline void rkfec_offline_proc_cleanup(struct rkfec_offline_dev *dev) {}
#endif
#endif

View File

@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2025 Rockchip Electronics Co., Ltd. */
#ifndef _RKFEC_REGS_H
#define _RKFEC_REGS_H
#define RKFEC_BASE 0x0000
#define RKFEC_SYS_AR_QOS (RKFEC_BASE + 0x0014)
#define RKFEC_STRT (RKFEC_BASE + 0x0200)
#define RKFEC_UPD (RKFEC_BASE + 0x0204)
#define RKFEC_CLK_DIS (RKFEC_BASE + 0x0208)
#define RKFEC_CTRL (RKFEC_BASE + 0x0210)
#define RKFEC_RD_VIR_STRIDE (RKFEC_BASE + 0x0214)
#define RKFEC_RD_Y_BASE (RKFEC_BASE + 0x0218)
#define RKFEC_RD_C_BASE (RKFEC_BASE + 0x021c)
#define RKFEC_LUT_BASE (RKFEC_BASE + 0x0220)
#define RKFEC_WR_Y_BASE (RKFEC_BASE + 0x0234)
#define RKFEC_WR_C_BASE (RKFEC_BASE + 0x0238)
#define RKFEC_WR_FBCE_HEAD_OFFSET (RKFEC_BASE + 0x023c)
#define RKFEC_RD_Y_BASE_SHD (RKFEC_BASE + 0x0240)
#define RKFEC_RD_C_BASE_SHD (RKFEC_BASE + 0x0244)
#define RKFEC_LUT_BASE_SHD (RKFEC_BASE + 0x0248)
#define RKFEC_WR_FBCE_HEAD_OFFSET_SHD (RKFEC_BASE + 0x024c)
#define RKFEC_CORE_CTRL (RKFEC_BASE + 0x0280)
#define RKFEC_BG_VALUE (RKFEC_BASE + 0x0284)
#define RKFEC_DST_SIZE (RKFEC_BASE + 0x0288)
#define RKFEC_LUT_SIZE (RKFEC_BASE + 0x028c)
#define RKFEC_STATUS0 (RKFEC_BASE + 0x0290)
#define RKFEC_STATUS1 (RKFEC_BASE + 0x0294)
#define RKFEC_SRC_SIZE (RKFEC_BASE + 0x0298)
#define RKFEC_STOP_REG (RKFEC_BASE + 0x029c)
#define RKFEC_WR_VIR_STRIDE (RKFEC_BASE + 0x02a0)
#define RKFEC_INT_EN (RKFEC_BASE + 0x02b0)
#define RKFEC_INT_RAW (RKFEC_BASE + 0x02b4)
#define RKFEC_INT_MSK (RKFEC_BASE + 0x02b8)
#define RKFEC_INT_CLR (RKFEC_BASE + 0x02bc)
#define RKFEC_CACHE_STATUS (RKFEC_BASE + 0x0e08)
#define RKFEC_CACHE_COMMAND (RKFEC_BASE + 0x0e10)
#define RKFEC_CACHE_CLEAR_PAGE (RKFEC_BASE + 0x0e14)
#define RKFEC_CACHE_MAX_READS (RKFEC_BASE + 0x0e18)
#define RKFEC_CACHE_CTRL (RKFEC_BASE + 0x0e1c)
#define RKFEC_CACHE_PERFCNT_SRC0 (RKFEC_BASE + 0x0e20)
#define RKFEC_CACHE_PERFCNT_VAL0 (RKFEC_BASE + 0x0e24)
#define RKFEC_CACHE_PERFCNT_SRC1 (RKFEC_BASE + 0x0e28)
#define RKFEC_CACHE_RERFCNT_VAL1 (RKFEC_BASE + 0x0e2c)
#define RKFEC_MMU_DTE_ADDR (RKFEC_BASE + 0x0f00)
#define RKFEC_MMU_STATUS (RKFEC_BASE + 0x0f04)
#define RKFEC_MMU_COMMAND (RKFEC_BASE + 0x0f08)
#define RKFEC_MMU_PAGE_FAULT_ADDR (RKFEC_BASE + 0x0f0c)
#define RKFEC_MMU_ZAP_ONE_LINE (RKFEC_BASE + 0x0f10)
#define RKFEC_MMU_INT_RAWSTAT (RKFEC_BASE + 0x0f14)
#define RKFEC_MMU_INT_CLEAR (RKFEC_BASE + 0x0f18)
#define RKFEC_MMU_INT_MASK (RKFEC_BASE + 0x0f1c)
#define RKFEC_MMU_INT_STATUS (RKFEC_BASE + 0x0f20)
#define RKFEC_MMU_AUTO_GATING (RKFEC_BASE + 0x0f24)
#define RKFEC_MMU_REG_LOAD_EN (RKFEC_BASE + 0x0f28)
/****************** BIT *******************/
/* FEC_STRT */
#define SYS_FEC_ST BIT(0)
/* FEC_UPD */
#define SYS_FEC_FORCE_UPD BIT(0)
/* FEC_CLK_DIS */
#define SYS_FEC_LGC_CKG_DIS BIT(0)
#define SYS_FEC_RAM_CKG_DIS BIT(1)
#define SYS_RST_PROTECT_DIS BIT(12)
#define SYS_SOFT_RST_FBCE BIT(13)
#define SYS_SOFT_RST_ACLK BIT(14)
#define ARST_SAFETY_FEC_DONE BIT(15)
#define SW_FEC_HURRY_EN BIT(16)
#define SW_FEC_HURRY_NUM(n) ((n & 0x7fff) << 17)
/* FEC_CTRL */
#define SW_FEC_RD_PIC_FORMAT BIT(1)
#define SW_FEC_RD_FORMAT BIT(2)
#define SW_FEC_RD_MODE(x) ((x & 0x3) << 4)
#define SW_FEC_WR_PIC_FORMAT BIT(7)
#define SW_FEC_WR_FORMAT BIT(8)
#define SW_FEC_WR_MODE(x) ((x & 0x3) << 9)
#define SW_FEC_WR_FBCE_FORCE_UNC_EN BIT(13)
#define SW_FEC_RD_FMT(x) ((x & 0xf) << 2)
#define SW_FEC_WR_FMT(x) ((x & 0xf) << 8)
/* FEC_CORE_CTRL */
#define SW_FEC_EN BIT(0)
#define SW_FEC_BIC_MODE(x) ((x & 0x3) << 3)
#define SW_LUT_DENSITY(x) ((x & 0x3) << 5)
#define SW_FEC_BORDER_MODE(x) ((x & 0x1) << 7)
#define SW_FEC_PBUF_CRS_DIS(x) ((x & 0x1) << 8)
#define SW_FEC_CRS_BUF_MODE(x) ((x & 0x1) << 10)
#define SW_FEC_EN_SHD BIT(31)
/* FEC_RD_VIR_STRIDE */
#define FEC_RD_VIR_STRIDE_Y(x) ((x) & 0x3fff)
#define FEC_RD_VIR_STRIDE_C(x) (((x) & 0x3fff) << 16)
/* FEC_BG_VALUE */
#define SW_BG_Y_VALUE(y) ((y & 0xff))
#define SW_BG_U_VALUE(u) ((u & 0xff) << 10)
#define SW_BG_V_VALUE(v) ((v & 0xff) << 20)
/* FEC_DST_SIZE */
#define SW_FEC_DST_WIDTH(x) ((x) & 0x1fff)
#define Sw_FEC_DST_HEIGHT(x) (((x) & 0x1fff) << 16)
/* FEC_SRC_SIZE */
#define SW_FEC_SRC_WIDTH(x) ((x) & 0x1fff)
#define Sw_FEC_SRC_HEIGHT(x) (((x) & 0x1fff) << 16)
/* FEC_WR_VIR_STRIDE */
#define FEC_WR_VIR_STRIDE_Y(x) ((x) & 0x3fff)
#define FEC_WR_VIR_STRIDE_C(x) (((x) & 0x3fff) << 16)
/* LUT SIZE */
#define SW_LUT_SIZE(x) ((x) & 0x3fffff)
/* FEC_INT_EN */
#define PBUF_BD_CRS_P BIT(0)
#define FEC_STOP_IRQ BIT(1)
#define MBUF_PP_FAIL BIT(2)
#define PBUF_PP_FAIL BIT(3)
#define AXI_MS_BRESP_ERR BIT(4)
#define AXI_MS_RRESP_ERR BIT(5)
#define FRM_END_P_FEC BIT(7)
#endif /* _RKISPP_REGS_H */

View File

@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2025 Rockchip Electronics Co., Ltd. */
#ifndef _RKFEC_VERSION_H
#define _RKFEC_VERSION_H
#include <linux/version.h>
#include <linux/rk-fec-config.h>
/*
* RKFEC DRIVER VERSION NOTE
*
* v0.1.0:
* 1. First version;
*/
#define RKFEC_DRIVER_VERSION RKFEC_API_VERSION
#endif

View File

@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: (GPL-2.0+ WITH Linux-syscall-note) OR MIT
*
* Rockchip FEC
* Copyright (C) 2025 Rockchip Electronics Co., Ltd.
*/
#ifndef _UAPI_RK_FEC_CONFIG_H
#define _UAPI_RK_FEC_CONFIG_H
#include <linux/types.h>
#include <linux/v4l2-controls.h>
#define RKFEC_API_VERSION KERNEL_VERSION(0, 1, 0)
#define FEC_BUF_CNT 3
/*************VIDIOC_PRIVATE*************/
#define RKFEC_CMD_IN_OUT \
_IOW('V', BASE_VIDIOC_PRIVATE + 10, struct rkfec_in_out)
#define RKFEC_CMD_BUF_ADD \
_IOW('V', BASE_VIDIOC_PRIVATE + 1, int)
#define RKFEC_CMD_BUF_DEL \
_IOW('V', BASE_VIDIOC_PRIVATE + 2, int)
#define RKFEC_CMD_BUF_ALLOC \
_IOWR('V', BASE_VIDIOC_PRIVATE + 3, struct rkfec_buf)
/* rkfec_buf_info
* @in_offs: in_buf c addr offset
* @out_offs: out_buf c addr offset
*/
struct rkfec_buf_cfg {
int in_pic_fd;
int out_pic_fd;
int lut_fd;
int in_stride;
int out_stride;
int in_size;
int out_size;
int lut_size;
int in_offs;
int out_offs;
} __attribute__ ((packed));
/* rkfec_core_ctrl
* @ bic_mode: 0:precise 1:spline 2:catrom 3: mitchell
* @ border_mode: 0:fill with bg_value 1:copy with the nearest pixel
* @ buf_mode: 0:fill with bg_value 1:copy with the nearest pixel
* @ pbuf_crs_dis
* @ density: 0:16x8; 1:32x16; 2:4x4
*/
struct rkfec_core_ctrl {
int bic_mode;
int density;
int border_mode;
int pbuf_crs_dis;
int buf_mode;
} __attribute__ ((packed));
struct rkfec_bg_val {
int bg_y;
int bg_u;
int bg_v;
} __attribute__ ((packed));
struct rkfec_in_out {
int in_width;
int in_height;
int out_width;
int out_height;
int in_fourcc;
int out_fourcc;
struct rkfec_buf_cfg buf_cfg;
struct rkfec_core_ctrl core_ctrl;
struct rkfec_bg_val bg_val;
} __attribute__ ((packed));
struct rkfec_buf {
int size;
int buf_fd;
} __attribute__ ((packed));
#endif

View File

@@ -27,6 +27,8 @@
#define V4L2_PIX_FMT_TILE420 v4l2_fourcc('T', 'I', 'L', '0')
/* yuv422 tile */
#define V4L2_PIX_FMT_TILE422 v4l2_fourcc('T', 'I', 'L', '2')
/* yuv420 quad */
#define V4L2_PIX_FMT_QUAD v4l2_fourcc('Q', 'U', 'A', 'D')
/* Vendor specific - used for Rockchip ISP1 camera sub-system */
#define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 params */