From 3c1a5874b0e9842a8e2ea4065438cc5ebe5fe797 Mon Sep 17 00:00:00 2001 From: Mingwei Yan Date: Mon, 6 Jan 2025 16:06:53 +0800 Subject: [PATCH] media: rockchip: fec: init driver for rv1126b Signed-off-by: Mingwei Yan Signed-off-by: Xu Hongfei Change-Id: I2fa7a5b68c56fadcd664299c987ee4c227f998c7 --- drivers/media/platform/rockchip/Kconfig | 1 + drivers/media/platform/rockchip/Makefile | 1 + drivers/media/platform/rockchip/fec/Kconfig | 15 + drivers/media/platform/rockchip/fec/Makefile | 6 + .../media/platform/rockchip/fec/fec_offline.c | 626 ++++++++++++++++++ .../media/platform/rockchip/fec/fec_offline.h | 109 +++ drivers/media/platform/rockchip/fec/hw.c | 395 +++++++++++ drivers/media/platform/rockchip/fec/hw.h | 79 +++ drivers/media/platform/rockchip/fec/procfs.c | 161 +++++ drivers/media/platform/rockchip/fec/procfs.h | 21 + drivers/media/platform/rockchip/fec/regs.h | 133 ++++ drivers/media/platform/rockchip/fec/version.h | 18 + include/uapi/linux/rk-fec-config.h | 86 +++ include/uapi/linux/rk-video-format.h | 2 + 14 files changed, 1653 insertions(+) create mode 100644 drivers/media/platform/rockchip/fec/Kconfig create mode 100644 drivers/media/platform/rockchip/fec/Makefile create mode 100644 drivers/media/platform/rockchip/fec/fec_offline.c create mode 100644 drivers/media/platform/rockchip/fec/fec_offline.h create mode 100644 drivers/media/platform/rockchip/fec/hw.c create mode 100644 drivers/media/platform/rockchip/fec/hw.h create mode 100644 drivers/media/platform/rockchip/fec/procfs.c create mode 100644 drivers/media/platform/rockchip/fec/procfs.h create mode 100644 drivers/media/platform/rockchip/fec/regs.h create mode 100644 drivers/media/platform/rockchip/fec/version.h create mode 100644 include/uapi/linux/rk-fec-config.h diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig index 903fd2e36136..87ecf195e4fb 100644 --- a/drivers/media/platform/rockchip/Kconfig +++ b/drivers/media/platform/rockchip/Kconfig @@ -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" diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile index 0bdd933719ef..d3f3fcfe4f6c 100644 --- a/drivers/media/platform/rockchip/Makefile +++ b/drivers/media/platform/rockchip/Makefile @@ -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/ diff --git a/drivers/media/platform/rockchip/fec/Kconfig b/drivers/media/platform/rockchip/fec/Kconfig new file mode 100644 index 000000000000..1c0128099946 --- /dev/null +++ b/drivers/media/platform/rockchip/fec/Kconfig @@ -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. diff --git a/drivers/media/platform/rockchip/fec/Makefile b/drivers/media/platform/rockchip/fec/Makefile new file mode 100644 index 000000000000..bdeaea72307e --- /dev/null +++ b/drivers/media/platform/rockchip/fec/Makefile @@ -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 diff --git a/drivers/media/platform/rockchip/fec/fec_offline.c b/drivers/media/platform/rockchip/fec/fec_offline.c new file mode 100644 index 000000000000..e53b7538e319 --- /dev/null +++ b/drivers/media/platform/rockchip/fec/fec_offline.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Rockchip Electronics Co., Ltd. */ + +#include +#include + +#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); diff --git a/drivers/media/platform/rockchip/fec/fec_offline.h b/drivers/media/platform/rockchip/fec/fec_offline.h new file mode 100644 index 000000000000..9073c5539a49 --- /dev/null +++ b/drivers/media/platform/rockchip/fec/fec_offline.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 diff --git a/drivers/media/platform/rockchip/fec/hw.c b/drivers/media/platform/rockchip/fec/hw.c new file mode 100644 index 000000000000..559625136dfb --- /dev/null +++ b/drivers/media/platform/rockchip/fec/hw.c @@ -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); diff --git a/drivers/media/platform/rockchip/fec/hw.h b/drivers/media/platform/rockchip/fec/hw.h new file mode 100644 index 000000000000..d091db6bf05c --- /dev/null +++ b/drivers/media/platform/rockchip/fec/hw.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 diff --git a/drivers/media/platform/rockchip/fec/procfs.c b/drivers/media/platform/rockchip/fec/procfs.c new file mode 100644 index 000000000000..ee2344819926 --- /dev/null +++ b/drivers/media/platform/rockchip/fec/procfs.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) Rockchip Electronics Co., Ltd. */ + +#include + +#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 */ diff --git a/drivers/media/platform/rockchip/fec/procfs.h b/drivers/media/platform/rockchip/fec/procfs.h new file mode 100644 index 000000000000..f59750b6c945 --- /dev/null +++ b/drivers/media/platform/rockchip/fec/procfs.h @@ -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 +#include +#include +#include +#include + +#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 diff --git a/drivers/media/platform/rockchip/fec/regs.h b/drivers/media/platform/rockchip/fec/regs.h new file mode 100644 index 000000000000..d0ab51504c1d --- /dev/null +++ b/drivers/media/platform/rockchip/fec/regs.h @@ -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 */ + diff --git a/drivers/media/platform/rockchip/fec/version.h b/drivers/media/platform/rockchip/fec/version.h new file mode 100644 index 000000000000..37851fdae6ba --- /dev/null +++ b/drivers/media/platform/rockchip/fec/version.h @@ -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 +#include + +/* + * RKFEC DRIVER VERSION NOTE + * + * v0.1.0: + * 1. First version; + */ + +#define RKFEC_DRIVER_VERSION RKFEC_API_VERSION + +#endif diff --git a/include/uapi/linux/rk-fec-config.h b/include/uapi/linux/rk-fec-config.h new file mode 100644 index 000000000000..f362ee7c56bd --- /dev/null +++ b/include/uapi/linux/rk-fec-config.h @@ -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 +#include + +#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 diff --git a/include/uapi/linux/rk-video-format.h b/include/uapi/linux/rk-video-format.h index e08139a05786..7fcfef805f0a 100644 --- a/include/uapi/linux/rk-video-format.h +++ b/include/uapi/linux/rk-video-format.h @@ -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 */