diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/media/platform/nxp | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/platform/nxp')
30 files changed, 20755 insertions, 0 deletions
diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig new file mode 100644 index 0000000000..40e3436669 --- /dev/null +++ b/drivers/media/platform/nxp/Kconfig @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# V4L drivers + +comment "NXP media platform drivers" + +config VIDEO_IMX7_CSI + tristate "NXP CSI Bridge driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_DMA + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEOBUF2_DMA_CONTIG + select VIDEO_V4L2_SUBDEV_API + help + Driver for the NXP Camera Sensor Interface (CSI) Bridge. This device + is found in the i.MX6UL/L, i.MX7 and i.MX8M[MQ] SoCs. + +config VIDEO_IMX8MQ_MIPI_CSI2 + tristate "NXP i.MX8MQ MIPI CSI-2 receiver" + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 driver for the MIPI CSI-2 receiver found on the i.MX8MQ + SoC. + +config VIDEO_IMX_MIPI_CSIS + tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 sub-device driver for the MIPI CSI-2 CSIS receiver + v3.3/v3.6.3 found on some i.MX7 and i.MX8 SoCs. + +source "drivers/media/platform/nxp/imx8-isi/Kconfig" + +# mem2mem drivers + +config VIDEO_IMX_PXP + tristate "NXP i.MX Pixel Pipeline (PXP)" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && (ARCH_MXC || COMPILE_TEST) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + The i.MX Pixel Pipeline is a memory-to-memory engine for scaling, + color space conversion, and rotation. + +config VIDEO_MX2_EMMAPRP + tristate "NXP MX2 eMMa-PrP support" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on SOC_IMX27 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + MX2X chips have a PrP that can be used to process buffers from + memory to memory. Operations include resizing and format + conversion. + +source "drivers/media/platform/nxp/dw100/Kconfig" +source "drivers/media/platform/nxp/imx-jpeg/Kconfig" diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile new file mode 100644 index 0000000000..4d90eb7136 --- /dev/null +++ b/drivers/media/platform/nxp/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += dw100/ +obj-y += imx-jpeg/ +obj-y += imx8-isi/ + +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o +obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o +obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o +obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o +obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/dw100/Kconfig b/drivers/media/platform/nxp/dw100/Kconfig new file mode 100644 index 0000000000..cd4531bb31 --- /dev/null +++ b/drivers/media/platform/nxp/dw100/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_DW100 + tristate "NXP i.MX DW100 dewarper" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_MXC || COMPILE_TEST + select MEDIA_CONTROLLER + select V4L2_MEM2MEM_DEV + select VIDEOBUF2_DMA_CONTIG + help + DW100 is a memory-to-memory engine performing geometrical + transformation on source images through a programmable dewarping map. + + To compile this driver as a module, choose M here: the module + will be called dw100. diff --git a/drivers/media/platform/nxp/dw100/Makefile b/drivers/media/platform/nxp/dw100/Makefile new file mode 100644 index 0000000000..49db80589e --- /dev/null +++ b/drivers/media/platform/nxp/dw100/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_VIDEO_DW100) += dw100.o diff --git a/drivers/media/platform/nxp/dw100/dw100.c b/drivers/media/platform/nxp/dw100/dw100.c new file mode 100644 index 0000000000..0024d6175a --- /dev/null +++ b/drivers/media/platform/nxp/dw100/dw100.c @@ -0,0 +1,1703 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DW100 Hardware dewarper + * + * Copyright 2022 NXP + * Author: Xavier Roumegue (xavier.roumegue@oss.nxp.com) + * + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <media/v4l2-ctrls.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-contig.h> + +#include <uapi/linux/dw100.h> + +#include "dw100_regs.h" + +#define DRV_NAME "dw100" + +#define DW100_MIN_W 176u +#define DW100_MIN_H 144u +#define DW100_MAX_W 4096u +#define DW100_MAX_H 3072u +#define DW100_ALIGN_W 3 +#define DW100_ALIGN_H 3 + +#define DW100_BLOCK_SIZE 16 + +#define DW100_DEF_W 640u +#define DW100_DEF_H 480u +#define DW100_DEF_LUT_W (DIV_ROUND_UP(DW100_DEF_W, DW100_BLOCK_SIZE) + 1) +#define DW100_DEF_LUT_H (DIV_ROUND_UP(DW100_DEF_H, DW100_BLOCK_SIZE) + 1) + +/* + * 16 controls have been reserved for this driver for future extension, but + * let's limit the related driver allocation to the effective number of controls + * in use. + */ +#define DW100_MAX_CTRLS 1 +#define DW100_CTRL_DEWARPING_MAP 0 + +enum { + DW100_QUEUE_SRC = 0, + DW100_QUEUE_DST = 1, +}; + +enum { + DW100_FMT_CAPTURE = BIT(0), + DW100_FMT_OUTPUT = BIT(1), +}; + +struct dw100_device { + struct platform_device *pdev; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct media_device mdev; + /* Video device lock */ + struct mutex vfd_mutex; + void __iomem *mmio; + struct clk_bulk_data *clks; + int num_clks; + struct dentry *debugfs_root; +}; + +struct dw100_q_data { + struct v4l2_pix_format_mplane pix_fmt; + unsigned int sequence; + const struct dw100_fmt *fmt; + struct v4l2_rect crop; +}; + +struct dw100_ctx { + struct v4l2_fh fh; + struct dw100_device *dw_dev; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *ctrls[DW100_MAX_CTRLS]; + /* per context m2m queue lock */ + struct mutex vq_mutex; + + /* Look Up Table for pixel remapping */ + unsigned int *map; + dma_addr_t map_dma; + size_t map_size; + unsigned int map_width; + unsigned int map_height; + bool user_map_is_set; + + /* Source and destination queue data */ + struct dw100_q_data q_data[2]; +}; + +static const struct v4l2_frmsize_stepwise dw100_frmsize_stepwise = { + .min_width = DW100_MIN_W, + .min_height = DW100_MIN_H, + .max_width = DW100_MAX_W, + .max_height = DW100_MAX_H, + .step_width = 1UL << DW100_ALIGN_W, + .step_height = 1UL << DW100_ALIGN_H, +}; + +static const struct dw100_fmt { + u32 fourcc; + u32 types; + u32 reg_format; + bool reg_swap_uv; +} formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV16M, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV61M, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV12M, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV21M, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = true, + }, +}; + +static inline int to_dw100_fmt_type(enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return DW100_FMT_OUTPUT; + else + return DW100_FMT_CAPTURE; +} + +static const struct dw100_fmt *dw100_find_pixel_format(u32 pixel_format, + int fmt_type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + const struct dw100_fmt *fmt = &formats[i]; + + if (fmt->fourcc == pixel_format && fmt->types & fmt_type) + return fmt; + } + + return NULL; +} + +static const struct dw100_fmt *dw100_find_format(struct v4l2_format *f) +{ + return dw100_find_pixel_format(f->fmt.pix_mp.pixelformat, + to_dw100_fmt_type(f->type)); +} + +static inline u32 dw100_read(struct dw100_device *dw_dev, u32 reg) +{ + return readl(dw_dev->mmio + reg); +} + +static inline void dw100_write(struct dw100_device *dw_dev, u32 reg, u32 val) +{ + writel(val, dw_dev->mmio + reg); +} + +static inline int dw100_dump_regs(struct seq_file *m) +{ + struct dw100_device *dw_dev = m->private; +#define __DECLARE_REG(x) { #x, x } + unsigned int i; + static const struct reg_desc { + const char * const name; + unsigned int addr; + } dw100_regs[] = { + __DECLARE_REG(DW100_DEWARP_ID), + __DECLARE_REG(DW100_DEWARP_CTRL), + __DECLARE_REG(DW100_MAP_LUT_ADDR), + __DECLARE_REG(DW100_MAP_LUT_SIZE), + __DECLARE_REG(DW100_MAP_LUT_ADDR2), + __DECLARE_REG(DW100_MAP_LUT_SIZE2), + __DECLARE_REG(DW100_SRC_IMG_Y_BASE), + __DECLARE_REG(DW100_SRC_IMG_UV_BASE), + __DECLARE_REG(DW100_SRC_IMG_SIZE), + __DECLARE_REG(DW100_SRC_IMG_STRIDE), + __DECLARE_REG(DW100_DST_IMG_Y_BASE), + __DECLARE_REG(DW100_DST_IMG_UV_BASE), + __DECLARE_REG(DW100_DST_IMG_SIZE), + __DECLARE_REG(DW100_DST_IMG_STRIDE), + __DECLARE_REG(DW100_DST_IMG_Y_SIZE1), + __DECLARE_REG(DW100_DST_IMG_UV_SIZE1), + __DECLARE_REG(DW100_SRC_IMG_Y_BASE2), + __DECLARE_REG(DW100_SRC_IMG_UV_BASE2), + __DECLARE_REG(DW100_SRC_IMG_SIZE2), + __DECLARE_REG(DW100_SRC_IMG_STRIDE2), + __DECLARE_REG(DW100_DST_IMG_Y_BASE2), + __DECLARE_REG(DW100_DST_IMG_UV_BASE2), + __DECLARE_REG(DW100_DST_IMG_SIZE2), + __DECLARE_REG(DW100_DST_IMG_STRIDE2), + __DECLARE_REG(DW100_DST_IMG_Y_SIZE2), + __DECLARE_REG(DW100_DST_IMG_UV_SIZE2), + __DECLARE_REG(DW100_SWAP_CONTROL), + __DECLARE_REG(DW100_VERTICAL_SPLIT_LINE), + __DECLARE_REG(DW100_HORIZON_SPLIT_LINE), + __DECLARE_REG(DW100_SCALE_FACTOR), + __DECLARE_REG(DW100_ROI_START), + __DECLARE_REG(DW100_BOUNDARY_PIXEL), + __DECLARE_REG(DW100_INTERRUPT_STATUS), + __DECLARE_REG(DW100_BUS_CTRL), + __DECLARE_REG(DW100_BUS_CTRL1), + __DECLARE_REG(DW100_BUS_TIME_OUT_CYCLE), + }; + + for (i = 0; i < ARRAY_SIZE(dw100_regs); i++) + seq_printf(m, "%s: %#x\n", dw100_regs[i].name, + dw100_read(dw_dev, dw100_regs[i].addr)); + + return 0; +} + +static inline struct dw100_ctx *dw100_file2ctx(struct file *file) +{ + return container_of(file->private_data, struct dw100_ctx, fh); +} + +static struct dw100_q_data *dw100_get_q_data(struct dw100_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return &ctx->q_data[DW100_QUEUE_SRC]; + else + return &ctx->q_data[DW100_QUEUE_DST]; +} + +static u32 dw100_get_n_vertices_from_length(u32 length) +{ + return DIV_ROUND_UP(length, DW100_BLOCK_SIZE) + 1; +} + +static u16 dw100_map_convert_to_uq12_4(u32 a) +{ + return (u16)((a & 0xfff) << 4); +} + +static u32 dw100_map_format_coordinates(u16 xq, u16 yq) +{ + return (u32)((yq << 16) | xq); +} + +static u32 *dw100_get_user_map(struct dw100_ctx *ctx) +{ + struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; + + return ctrl->p_cur.p_u32; +} + +/* + * Create the dewarp map used by the hardware from the V4L2 control values which + * have been initialized with an identity map or set by the application. + */ +static int dw100_create_mapping(struct dw100_ctx *ctx) +{ + u32 *user_map; + + if (ctx->map) + dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + ctx->map, ctx->map_dma); + + ctx->map = dma_alloc_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + &ctx->map_dma, GFP_KERNEL); + + if (!ctx->map) + return -ENOMEM; + + user_map = dw100_get_user_map(ctx); + memcpy(ctx->map, user_map, ctx->map_size); + + dev_dbg(&ctx->dw_dev->pdev->dev, + "%ux%u %s mapping created (d:%pad-c:%p) for stream %ux%u->%ux%u\n", + ctx->map_width, ctx->map_height, + ctx->user_map_is_set ? "user" : "identity", + &ctx->map_dma, ctx->map, + ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, + ctx->q_data[DW100_QUEUE_DST].pix_fmt.height, + ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, + ctx->q_data[DW100_QUEUE_DST].pix_fmt.height); + + return 0; +} + +static void dw100_destroy_mapping(struct dw100_ctx *ctx) +{ + if (ctx->map) { + dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + ctx->map, ctx->map_dma); + ctx->map = NULL; + } +} + +static int dw100_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw100_ctx *ctx = + container_of(ctrl->handler, struct dw100_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP: + ctx->user_map_is_set = true; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops dw100_ctrl_ops = { + .s_ctrl = dw100_s_ctrl, +}; + +/* + * Initialize the dewarping map with an identity mapping. + * + * A 16 pixels cell size grid is mapped on the destination image. + * The last cells width/height might be lesser than 16 if the destination image + * width/height is not divisible by 16. This dewarping grid map specifies the + * source image pixel location (x, y) on each grid intersection point. + * Bilinear interpolation is used to compute inner cell points locations. + * + * The coordinates are saved in UQ12.4 fixed point format. + */ +static void dw100_ctrl_dewarping_map_init(const struct v4l2_ctrl *ctrl, + u32 from_idx, + union v4l2_ctrl_ptr ptr) +{ + struct dw100_ctx *ctx = + container_of(ctrl->handler, struct dw100_ctx, hdl); + + u32 sw, sh, mw, mh, idx; + u16 qx, qy, qdx, qdy, qsh, qsw; + u32 *map = ctrl->p_cur.p_u32; + + sw = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width; + sh = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.height; + + mw = ctrl->dims[0]; + mh = ctrl->dims[1]; + + qsw = dw100_map_convert_to_uq12_4(sw); + qsh = dw100_map_convert_to_uq12_4(sh); + qdx = qsw / (mw - 1); + qdy = qsh / (mh - 1); + + ctx->map_width = mw; + ctx->map_height = mh; + ctx->map_size = mh * mw * sizeof(u32); + + for (idx = from_idx; idx < ctrl->elems; idx++) { + qy = min_t(u32, (idx / mw) * qdy, qsh); + qx = min_t(u32, (idx % mw) * qdx, qsw); + map[idx] = dw100_map_format_coordinates(qx, qy); + } + + ctx->user_map_is_set = false; +} + +static const struct v4l2_ctrl_type_ops dw100_ctrl_type_ops = { + .init = dw100_ctrl_dewarping_map_init, + .validate = v4l2_ctrl_type_op_validate, + .log = v4l2_ctrl_type_op_log, + .equal = v4l2_ctrl_type_op_equal, +}; + +static const struct v4l2_ctrl_config controls[] = { + [DW100_CTRL_DEWARPING_MAP] = { + .ops = &dw100_ctrl_ops, + .type_ops = &dw100_ctrl_type_ops, + .id = V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP, + .name = "Dewarping Vertex Map", + .type = V4L2_CTRL_TYPE_U32, + .min = 0x00000000, + .max = 0xffffffff, + .step = 1, + .def = 0, + .dims = { DW100_DEF_LUT_W, DW100_DEF_LUT_H }, + }, +}; + +static int dw100_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(vq); + const struct v4l2_pix_format_mplane *format; + unsigned int i; + + format = &dw100_get_q_data(ctx, vq->type)->pix_fmt; + + if (*nplanes) { + if (*nplanes != format->num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; ++i) { + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + return 0; + } + + *nplanes = format->num_planes; + + for (i = 0; i < format->num_planes; ++i) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +static int dw100_buf_prepare(struct vb2_buffer *vb) +{ + unsigned int i; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct dw100_device *dw_dev = ctx->dw_dev; + const struct v4l2_pix_format_mplane *pix_fmt = + &dw100_get_q_data(ctx, vb->vb2_queue->type)->pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field != V4L2_FIELD_NONE) { + dev_dbg(&dw_dev->pdev->dev, "%x field isn't supported\n", + vbuf->field); + return -EINVAL; + } + } + + for (i = 0; i < pix_fmt->num_planes; i++) { + unsigned long size = pix_fmt->plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_dbg(&dw_dev->pdev->dev, + "User buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void dw100_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void dw100_return_all_buffers(struct vb2_queue *q, + enum vb2_buffer_state state) +{ + struct dw100_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) + return; + v4l2_m2m_buf_done(vbuf, state); + } +} + +static int dw100_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(q); + struct dw100_q_data *q_data = dw100_get_q_data(ctx, q->type); + int ret; + + q_data->sequence = 0; + + ret = dw100_create_mapping(ctx); + if (ret) + goto err; + + ret = pm_runtime_resume_and_get(&ctx->dw_dev->pdev->dev); + if (ret) { + dw100_destroy_mapping(ctx); + goto err; + } + + return 0; +err: + dw100_return_all_buffers(q, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void dw100_stop_streaming(struct vb2_queue *q) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(q); + + dw100_return_all_buffers(q, VB2_BUF_STATE_ERROR); + + pm_runtime_put_sync(&ctx->dw_dev->pdev->dev); + + dw100_destroy_mapping(ctx); +} + +static const struct vb2_ops dw100_qops = { + .queue_setup = dw100_queue_setup, + .buf_prepare = dw100_buf_prepare, + .buf_queue = dw100_buf_queue, + .start_streaming = dw100_start_streaming, + .stop_streaming = dw100_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int dw100_m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct dw100_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->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &dw100_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->vq_mutex; + src_vq->dev = ctx->dw_dev->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->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &dw100_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->vq_mutex; + dst_vq->dev = ctx->dw_dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int dw100_open(struct file *file) +{ + struct dw100_device *dw_dev = video_drvdata(file); + struct dw100_ctx *ctx; + struct v4l2_ctrl_handler *hdl; + struct v4l2_pix_format_mplane *pix_fmt; + int ret, i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_init(&ctx->vq_mutex); + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dw_dev = dw_dev; + + ctx->q_data[DW100_QUEUE_SRC].fmt = &formats[0]; + + pix_fmt = &ctx->q_data[DW100_QUEUE_SRC].pix_fmt; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = V4L2_COLORSPACE_REC709; + pix_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_fmt->colorspace); + pix_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_fmt->colorspace); + pix_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, pix_fmt->colorspace, + pix_fmt->ycbcr_enc); + + v4l2_fill_pixfmt_mp(pix_fmt, formats[0].fourcc, DW100_DEF_W, DW100_DEF_H); + + ctx->q_data[DW100_QUEUE_SRC].crop.top = 0; + ctx->q_data[DW100_QUEUE_SRC].crop.left = 0; + ctx->q_data[DW100_QUEUE_SRC].crop.width = DW100_DEF_W; + ctx->q_data[DW100_QUEUE_SRC].crop.height = DW100_DEF_H; + + ctx->q_data[DW100_QUEUE_DST] = ctx->q_data[DW100_QUEUE_SRC]; + + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, ARRAY_SIZE(controls)); + for (i = 0; i < ARRAY_SIZE(controls); i++) { + ctx->ctrls[i] = v4l2_ctrl_new_custom(hdl, &controls[i], NULL); + if (hdl->error) { + dev_err(&ctx->dw_dev->pdev->dev, + "Adding control (%d) failed\n", i); + ret = hdl->error; + goto err; + } + } + ctx->fh.ctrl_handler = hdl; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dw_dev->m2m_dev, + ctx, &dw100_m2m_queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto err; + } + + v4l2_fh_add(&ctx->fh); + + return 0; + +err: + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->vq_mutex); + kfree(ctx); + + return ret; +} + +static int dw100_release(struct file *file) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_destroy(&ctx->vq_mutex); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations dw100_fops = { + .owner = THIS_MODULE, + .open = dw100_open, + .release = dw100_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int dw100_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, "DW100 dewarper", sizeof(cap->card)); + + return 0; +} + +static int dw100_enum_fmt_vid(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int i, num = 0; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].types & to_dw100_fmt_type(f->type)) { + if (num == f->index) { + f->pixelformat = formats[i].fourcc; + return 0; + } + ++num; + } + } + + return -EINVAL; +} + +static int dw100_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct dw100_fmt *fmt; + + if (fsize->index) + return -EINVAL; + + fmt = dw100_find_pixel_format(fsize->pixel_format, + DW100_FMT_OUTPUT | DW100_FMT_CAPTURE); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = dw100_frmsize_stepwise; + + return 0; +} + +static int dw100_g_fmt_vid(struct file *file, void *priv, struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct vb2_queue *vq; + struct dw100_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = dw100_get_q_data(ctx, f->type); + + f->fmt.pix_mp = q_data->pix_fmt; + + return 0; +} + +static int dw100_try_fmt(struct file *file, struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + const struct dw100_fmt *fmt; + + fmt = dw100_find_format(f); + if (!fmt) { + fmt = &formats[0]; + pix->pixelformat = fmt->fourcc; + } + + v4l2_apply_frmsize_constraints(&pix->width, &pix->height, + &dw100_frmsize_stepwise); + + v4l2_fill_pixfmt_mp(pix, fmt->fourcc, pix->width, pix->height); + + pix->field = V4L2_FIELD_NONE; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) + pix->colorspace = V4L2_COLORSPACE_REC709; + if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT) + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) + pix->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, + pix->colorspace, + pix->ycbcr_enc); + } else { + /* + * The DW100 can't perform colorspace conversion, the colorspace + * on the capture queue must be identical to the output queue. + */ + const struct dw100_q_data *q_data = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + pix->colorspace = q_data->pix_fmt.colorspace; + pix->xfer_func = q_data->pix_fmt.xfer_func; + pix->ycbcr_enc = q_data->pix_fmt.ycbcr_enc; + pix->quantization = q_data->pix_fmt.quantization; + } + + return 0; +} + +static int dw100_s_fmt(struct dw100_ctx *ctx, struct v4l2_format *f) +{ + struct dw100_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = dw100_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + dev_dbg(&ctx->dw_dev->pdev->dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = dw100_find_format(f); + q_data->pix_fmt = f->fmt.pix_mp; + q_data->crop.top = 0; + q_data->crop.left = 0; + q_data->crop.width = f->fmt.pix_mp.width; + q_data->crop.height = f->fmt.pix_mp.height; + + /* Propagate buffers encoding */ + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + struct dw100_q_data *dst_q_data = + dw100_get_q_data(ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + dst_q_data->pix_fmt.colorspace = q_data->pix_fmt.colorspace; + dst_q_data->pix_fmt.ycbcr_enc = q_data->pix_fmt.ycbcr_enc; + dst_q_data->pix_fmt.quantization = q_data->pix_fmt.quantization; + dst_q_data->pix_fmt.xfer_func = q_data->pix_fmt.xfer_func; + } + + dev_dbg(&ctx->dw_dev->pdev->dev, + "Setting format for type %u, wxh: %ux%u, fmt: %p4cc\n", + f->type, q_data->pix_fmt.width, q_data->pix_fmt.height, + &q_data->pix_fmt.pixelformat); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + int ret; + u32 dims[V4L2_CTRL_MAX_DIMS] = {}; + struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; + + dims[0] = dw100_get_n_vertices_from_length(q_data->pix_fmt.width); + dims[1] = dw100_get_n_vertices_from_length(q_data->pix_fmt.height); + + ret = v4l2_ctrl_modify_dimensions(ctrl, dims); + + if (ret) { + dev_err(&ctx->dw_dev->pdev->dev, + "Modifying LUT dimensions failed with error %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int dw100_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + return dw100_try_fmt(file, f); +} + +static int dw100_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + int ret; + + ret = dw100_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret = dw100_s_fmt(ctx, f); + if (ret) + return ret; + + return 0; +} + +static int dw100_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + return dw100_try_fmt(file, f); +} + +static int dw100_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + int ret; + + ret = dw100_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = dw100_s_fmt(ctx, f); + if (ret) + return ret; + + return 0; +} + +static int dw100_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct dw100_q_data *src_q_data; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = src_q_data->pix_fmt.width; + sel->r.height = src_q_data->pix_fmt.height; + break; + case V4L2_SEL_TGT_CROP: + sel->r.top = src_q_data->crop.top; + sel->r.left = src_q_data->crop.left; + sel->r.width = src_q_data->crop.width; + sel->r.height = src_q_data->crop.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dw100_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct dw100_q_data *src_q_data; + u32 qscalex, qscaley, qscale; + int x, y, w, h; + unsigned int wframe, hframe; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + dev_dbg(&ctx->dw_dev->pdev->dev, + ">>> Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", + sel->type, sel->target, + sel->r.width, sel->r.height, sel->r.left, sel->r.top); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + wframe = src_q_data->pix_fmt.width; + hframe = src_q_data->pix_fmt.height; + + sel->r.top = clamp_t(int, sel->r.top, 0, hframe - DW100_MIN_H); + sel->r.left = clamp_t(int, sel->r.left, 0, wframe - DW100_MIN_W); + sel->r.height = + clamp(sel->r.height, DW100_MIN_H, hframe - sel->r.top); + sel->r.width = + clamp(sel->r.width, DW100_MIN_W, wframe - sel->r.left); + + /* UQ16.16 for float operations */ + qscalex = (sel->r.width << 16) / wframe; + qscaley = (sel->r.height << 16) / hframe; + y = sel->r.top; + x = sel->r.left; + if (qscalex == qscaley) { + qscale = qscalex; + } else { + switch (sel->flags) { + case 0: + qscale = (qscalex + qscaley) / 2; + break; + case V4L2_SEL_FLAG_GE: + qscale = max(qscaley, qscalex); + break; + case V4L2_SEL_FLAG_LE: + qscale = min(qscaley, qscalex); + break; + case V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE: + return -ERANGE; + default: + return -EINVAL; + } + } + + w = (u32)((((u64)wframe << 16) * qscale) >> 32); + h = (u32)((((u64)hframe << 16) * qscale) >> 32); + x = x + (sel->r.width - w) / 2; + y = y + (sel->r.height - h) / 2; + x = min(wframe - w, (unsigned int)max(0, x)); + y = min(hframe - h, (unsigned int)max(0, y)); + + sel->r.top = y; + sel->r.left = x; + sel->r.width = w; + sel->r.height = h; + + src_q_data->crop.top = sel->r.top; + src_q_data->crop.left = sel->r.left; + src_q_data->crop.width = sel->r.width; + src_q_data->crop.height = sel->r.height; + break; + + default: + return -EINVAL; + } + + dev_dbg(&ctx->dw_dev->pdev->dev, + "<<< Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", + sel->type, sel->target, + sel->r.width, sel->r.height, sel->r.left, sel->r.top); + + return 0; +} + +static const struct v4l2_ioctl_ops dw100_ioctl_ops = { + .vidioc_querycap = dw100_querycap, + + .vidioc_enum_fmt_vid_cap = dw100_enum_fmt_vid, + .vidioc_enum_framesizes = dw100_enum_framesizes, + .vidioc_g_fmt_vid_cap_mplane = dw100_g_fmt_vid, + .vidioc_try_fmt_vid_cap_mplane = dw100_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = dw100_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = dw100_enum_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = dw100_g_fmt_vid, + .vidioc_try_fmt_vid_out_mplane = dw100_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = dw100_s_fmt_vid_out, + + .vidioc_g_selection = dw100_g_selection, + .vidioc_s_selection = dw100_s_selection, + .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_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void dw100_job_finish(struct dw100_device *dw_dev, bool with_error) +{ + struct dw100_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + enum vb2_buffer_state buf_state; + + curr_ctx = v4l2_m2m_get_curr_priv(dw_dev->m2m_dev); + + if (!curr_ctx) { + dev_err(&dw_dev->pdev->dev, + "Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + if (likely(!with_error)) + buf_state = VB2_BUF_STATE_DONE; + else + buf_state = VB2_BUF_STATE_ERROR; + + v4l2_m2m_buf_done(src_vb, buf_state); + v4l2_m2m_buf_done(dst_vb, buf_state); + + dev_dbg(&dw_dev->pdev->dev, "Finishing transaction with%s error(s)\n", + with_error ? "" : "out"); + + v4l2_m2m_job_finish(dw_dev->m2m_dev, curr_ctx->fh.m2m_ctx); +} + +static void dw100_hw_reset(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + val |= DW100_DEWARP_CTRL_ENABLE; + val |= DW100_DEWARP_CTRL_SOFT_RESET; + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + val &= ~DW100_DEWARP_CTRL_SOFT_RESET; + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void _dw100_hw_set_master_bus_enable(struct dw100_device *dw_dev, + unsigned int enable) +{ + u32 val; + + dev_dbg(&dw_dev->pdev->dev, "%sable master bus\n", + enable ? "En" : "Dis"); + + val = dw100_read(dw_dev, DW100_BUS_CTRL); + + if (enable) + val |= DW100_BUS_CTRL_AXI_MASTER_ENABLE; + else + val &= ~DW100_BUS_CTRL_AXI_MASTER_ENABLE; + + dw100_write(dw_dev, DW100_BUS_CTRL, val); +} + +static void dw100_hw_master_bus_enable(struct dw100_device *dw_dev) +{ + _dw100_hw_set_master_bus_enable(dw_dev, 1); +} + +static void dw100_hw_master_bus_disable(struct dw100_device *dw_dev) +{ + _dw100_hw_set_master_bus_enable(dw_dev, 0); +} + +static void dw100_hw_dewarp_start(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + dev_dbg(&dw_dev->pdev->dev, "Starting Hardware CTRL:0x%08x\n", val); + dw100_write(dw_dev, DW100_DEWARP_CTRL, val | DW100_DEWARP_CTRL_START); + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void dw100_hw_init_ctrl(struct dw100_device *dw_dev) +{ + u32 val; + /* + * Input format YUV422_SP + * Output format YUV422_SP + * No hardware handshake (SW) + * No automatic double src buffering (Single) + * No automatic double dst buffering (Single) + * No Black Line + * Prefetch image pixel traversal + */ + + val = DW100_DEWARP_CTRL_ENABLE + /* Valid only for auto prefetch mode*/ + | DW100_DEWARP_CTRL_PREFETCH_THRESHOLD(32); + + /* + * Calculation mode required to support any scaling factor, + * but x4 slower than traversal mode. + * + * DW100_DEWARP_CTRL_PREFETCH_MODE_TRAVERSAL + * DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION + * DW100_DEWARP_CTRL_PREFETCH_MODE_AUTO + * + * TODO: Find heuristics requiring calculation mode + */ + val |= DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION; + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void dw100_hw_set_pixel_boundary(struct dw100_device *dw_dev) +{ + u32 val; + + val = DW100_BOUNDARY_PIXEL_V(128) + | DW100_BOUNDARY_PIXEL_U(128) + | DW100_BOUNDARY_PIXEL_Y(0); + + dw100_write(dw_dev, DW100_BOUNDARY_PIXEL, val); +} + +static void dw100_hw_set_scale(struct dw100_device *dw_dev, u8 scale) +{ + dev_dbg(&dw_dev->pdev->dev, "Setting scale factor to %u\n", scale); + + dw100_write(dw_dev, DW100_SCALE_FACTOR, scale); +} + +static void dw100_hw_set_roi(struct dw100_device *dw_dev, u32 x, u32 y) +{ + u32 val; + + dev_dbg(&dw_dev->pdev->dev, "Setting ROI region to %u.%u\n", x, y); + + val = DW100_ROI_START_X(x) | DW100_ROI_START_Y(y); + + dw100_write(dw_dev, DW100_ROI_START, val); +} + +static void dw100_hw_set_src_crop(struct dw100_device *dw_dev, + const struct dw100_q_data *src_q_data, + const struct dw100_q_data *dst_q_data) +{ + const struct v4l2_rect *rect = &src_q_data->crop; + u32 src_scale, qscale, left_scale, top_scale; + + /* HW Scale is UQ1.7 encoded */ + src_scale = (rect->width << 7) / src_q_data->pix_fmt.width; + dw100_hw_set_scale(dw_dev, src_scale); + + qscale = (dst_q_data->pix_fmt.width << 7) / src_q_data->pix_fmt.width; + + left_scale = ((rect->left << 7) * qscale) >> 14; + top_scale = ((rect->top << 7) * qscale) >> 14; + + dw100_hw_set_roi(dw_dev, left_scale, top_scale); +} + +static void dw100_hw_set_source(struct dw100_device *dw_dev, + const struct dw100_q_data *q_data, + struct vb2_buffer *buffer) +{ + u32 width, height, stride, fourcc, val; + const struct dw100_fmt *fmt = q_data->fmt; + dma_addr_t addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); + dma_addr_t addr_uv; + + width = q_data->pix_fmt.width; + height = q_data->pix_fmt.height; + stride = q_data->pix_fmt.plane_fmt[0].bytesperline; + fourcc = q_data->fmt->fourcc; + + if (q_data->pix_fmt.num_planes == 2) + addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); + else + addr_uv = addr_y + (stride * height); + + dev_dbg(&dw_dev->pdev->dev, + "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", + width, height, stride, &fourcc, &addr_y); + + /* Pixel Format */ + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + val &= ~DW100_DEWARP_CTRL_INPUT_FORMAT_MASK; + val |= DW100_DEWARP_CTRL_INPUT_FORMAT(fmt->reg_format); + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + + /* Swap */ + val = dw100_read(dw_dev, DW100_SWAP_CONTROL); + + val &= ~DW100_SWAP_CONTROL_SRC_MASK; + /* + * Data swapping is performed only on Y plane for source image. + */ + if (fmt->reg_swap_uv && + fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) + val |= DW100_SWAP_CONTROL_SRC(DW100_SWAP_CONTROL_Y + (DW100_SWAP_CONTROL_BYTE)); + + dw100_write(dw_dev, DW100_SWAP_CONTROL, val); + + /* Image resolution */ + dw100_write(dw_dev, DW100_SRC_IMG_SIZE, + DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); + + dw100_write(dw_dev, DW100_SRC_IMG_STRIDE, stride); + + /* Buffers */ + dw100_write(dw_dev, DW100_SRC_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); + dw100_write(dw_dev, DW100_SRC_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); +} + +static void dw100_hw_set_destination(struct dw100_device *dw_dev, + const struct dw100_q_data *q_data, + const struct dw100_fmt *ifmt, + struct vb2_buffer *buffer) +{ + u32 width, height, stride, fourcc, val, size_y, size_uv; + const struct dw100_fmt *fmt = q_data->fmt; + dma_addr_t addr_y, addr_uv; + + width = q_data->pix_fmt.width; + height = q_data->pix_fmt.height; + stride = q_data->pix_fmt.plane_fmt[0].bytesperline; + fourcc = fmt->fourcc; + + addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); + size_y = q_data->pix_fmt.plane_fmt[0].sizeimage; + + if (q_data->pix_fmt.num_planes == 2) { + addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); + size_uv = q_data->pix_fmt.plane_fmt[1].sizeimage; + } else { + addr_uv = addr_y + ALIGN(stride * height, 16); + size_uv = size_y; + if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV420_SP) + size_uv /= 2; + } + + dev_dbg(&dw_dev->pdev->dev, + "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", + width, height, stride, &fourcc, &addr_y); + + /* Pixel Format */ + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + val &= ~DW100_DEWARP_CTRL_OUTPUT_FORMAT_MASK; + val |= DW100_DEWARP_CTRL_OUTPUT_FORMAT(fmt->reg_format); + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + + /* Swap */ + val = dw100_read(dw_dev, DW100_SWAP_CONTROL); + + val &= ~DW100_SWAP_CONTROL_DST_MASK; + + /* + * Avoid to swap twice + */ + if (fmt->reg_swap_uv ^ + (ifmt->reg_swap_uv && ifmt->reg_format != + DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED)) { + if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) + val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_Y + (DW100_SWAP_CONTROL_BYTE)); + else + val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_UV + (DW100_SWAP_CONTROL_BYTE)); + } + + dw100_write(dw_dev, DW100_SWAP_CONTROL, val); + + /* Image resolution */ + dw100_write(dw_dev, DW100_DST_IMG_SIZE, + DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); + dw100_write(dw_dev, DW100_DST_IMG_STRIDE, stride); + dw100_write(dw_dev, DW100_DST_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); + dw100_write(dw_dev, DW100_DST_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); + dw100_write(dw_dev, DW100_DST_IMG_Y_SIZE1, DW100_DST_IMG_Y_SIZE(size_y)); + dw100_write(dw_dev, DW100_DST_IMG_UV_SIZE1, + DW100_DST_IMG_UV_SIZE(size_uv)); +} + +static void dw100_hw_set_mapping(struct dw100_device *dw_dev, dma_addr_t addr, + u32 width, u32 height) +{ + dev_dbg(&dw_dev->pdev->dev, + "Set HW mapping registers for %ux%u addr:%pad", + width, height, &addr); + + dw100_write(dw_dev, DW100_MAP_LUT_ADDR, DW100_MAP_LUT_ADDR_ADDR(addr)); + dw100_write(dw_dev, DW100_MAP_LUT_SIZE, DW100_MAP_LUT_SIZE_WIDTH(width) + | DW100_MAP_LUT_SIZE_HEIGHT(height)); +} + +static void dw100_hw_clear_irq(struct dw100_device *dw_dev, unsigned int irq) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, + DW100_INTERRUPT_STATUS_INT_CLEAR(irq)); +} + +static void dw100_hw_enable_irq(struct dw100_device *dw_dev) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, + DW100_INTERRUPT_STATUS_INT_ENABLE_MASK); +} + +static void dw100_hw_disable_irq(struct dw100_device *dw_dev) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, 0); +} + +static u32 dw_hw_get_pending_irqs(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_INTERRUPT_STATUS); + + return DW100_INTERRUPT_STATUS_INT_STATUS(val); +} + +static irqreturn_t dw100_irq_handler(int irq, void *dev_id) +{ + struct dw100_device *dw_dev = dev_id; + u32 pending_irqs, err_irqs, frame_done_irq; + bool with_error = true; + + pending_irqs = dw_hw_get_pending_irqs(dw_dev); + frame_done_irq = pending_irqs & DW100_INTERRUPT_STATUS_INT_FRAME_DONE; + err_irqs = DW100_INTERRUPT_STATUS_INT_ERR_STATUS(pending_irqs); + + if (frame_done_irq) { + dev_dbg(&dw_dev->pdev->dev, "Frame done interrupt\n"); + with_error = false; + err_irqs &= ~DW100_INTERRUPT_STATUS_INT_ERR_STATUS + (DW100_INTERRUPT_STATUS_INT_ERR_FRAME_DONE); + } + + if (err_irqs) + dev_err(&dw_dev->pdev->dev, "Interrupt error: %#x\n", err_irqs); + + dw100_hw_disable_irq(dw_dev); + dw100_hw_master_bus_disable(dw_dev); + dw100_hw_clear_irq(dw_dev, pending_irqs | + DW100_INTERRUPT_STATUS_INT_ERR_TIME_OUT); + + dw100_job_finish(dw_dev, with_error); + + return IRQ_HANDLED; +} + +static void dw100_start(struct dw100_ctx *ctx, struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct dw100_device *dw_dev = ctx->dw_dev; + + out_vb->sequence = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)->sequence++; + in_vb->sequence = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)->sequence++; + + dev_dbg(&ctx->dw_dev->pdev->dev, + "Starting queues %p->%p, sequence %u->%u\n", + v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE), + v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE), + in_vb->sequence, out_vb->sequence); + + v4l2_m2m_buf_copy_metadata(in_vb, out_vb, true); + + /* Now, let's deal with hardware ... */ + dw100_hw_master_bus_disable(dw_dev); + dw100_hw_init_ctrl(dw_dev); + dw100_hw_set_pixel_boundary(dw_dev); + dw100_hw_set_src_crop(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], + &ctx->q_data[DW100_QUEUE_DST]); + dw100_hw_set_source(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], + &in_vb->vb2_buf); + dw100_hw_set_destination(dw_dev, &ctx->q_data[DW100_QUEUE_DST], + ctx->q_data[DW100_QUEUE_SRC].fmt, + &out_vb->vb2_buf); + dw100_hw_set_mapping(dw_dev, ctx->map_dma, + ctx->map_width, ctx->map_height); + dw100_hw_enable_irq(dw_dev); + dw100_hw_dewarp_start(dw_dev); + + /* Enable Bus */ + dw100_hw_master_bus_enable(dw_dev); +} + +static void dw100_device_run(void *priv) +{ + struct dw100_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + dw100_start(ctx, src_buf, dst_buf); +} + +static const struct v4l2_m2m_ops dw100_m2m_ops = { + .device_run = dw100_device_run, +}; + +static struct video_device *dw100_init_video_device(struct dw100_device *dw_dev) +{ + struct video_device *vfd = &dw_dev->vfd; + + vfd->vfl_dir = VFL_DIR_M2M; + vfd->fops = &dw100_fops; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + vfd->ioctl_ops = &dw100_ioctl_ops; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->v4l2_dev = &dw_dev->v4l2_dev; + vfd->lock = &dw_dev->vfd_mutex; + + strscpy(vfd->name, DRV_NAME, sizeof(vfd->name)); + mutex_init(vfd->lock); + video_set_drvdata(vfd, dw_dev); + + return vfd; +} + +static int dw100_dump_regs_show(struct seq_file *m, void *private) +{ + struct dw100_device *dw_dev = m->private; + int ret; + + ret = pm_runtime_resume_and_get(&dw_dev->pdev->dev); + if (ret < 0) + return ret; + + ret = dw100_dump_regs(m); + + pm_runtime_put_sync(&dw_dev->pdev->dev); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(dw100_dump_regs); + +static void dw100_debugfs_init(struct dw100_device *dw_dev) +{ + dw_dev->debugfs_root = + debugfs_create_dir(dev_name(&dw_dev->pdev->dev), NULL); + + debugfs_create_file("dump_regs", 0600, dw_dev->debugfs_root, dw_dev, + &dw100_dump_regs_fops); +} + +static void dw100_debugfs_exit(struct dw100_device *dw_dev) +{ + debugfs_remove_recursive(dw_dev->debugfs_root); +} + +static int dw100_probe(struct platform_device *pdev) +{ + struct dw100_device *dw_dev; + struct video_device *vfd; + int ret, irq; + + dw_dev = devm_kzalloc(&pdev->dev, sizeof(*dw_dev), GFP_KERNEL); + if (!dw_dev) + return -ENOMEM; + dw_dev->pdev = pdev; + + ret = devm_clk_bulk_get_all(&pdev->dev, &dw_dev->clks); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to get clocks: %d\n", ret); + return ret; + } + dw_dev->num_clks = ret; + + dw_dev->mmio = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(dw_dev->mmio)) + return PTR_ERR(dw_dev->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + platform_set_drvdata(pdev, dw_dev); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to resume the device: %d\n", ret); + goto err_pm; + } + + pm_runtime_put_sync(&pdev->dev); + + ret = devm_request_irq(&pdev->dev, irq, dw100_irq_handler, IRQF_ONESHOT, + dev_name(&pdev->dev), dw_dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_pm; + } + + ret = v4l2_device_register(&pdev->dev, &dw_dev->v4l2_dev); + if (ret) + goto err_pm; + + vfd = dw100_init_video_device(dw_dev); + + dw_dev->m2m_dev = v4l2_m2m_init(&dw100_m2m_ops); + if (IS_ERR(dw_dev->m2m_dev)) { + dev_err(&pdev->dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dw_dev->m2m_dev); + goto err_v4l2; + } + + dw_dev->mdev.dev = &pdev->dev; + strscpy(dw_dev->mdev.model, "dw100", sizeof(dw_dev->mdev.model)); + media_device_init(&dw_dev->mdev); + dw_dev->v4l2_dev.mdev = &dw_dev->mdev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(&pdev->dev, "Failed to register video device\n"); + goto err_m2m; + } + + ret = v4l2_m2m_register_media_controller(dw_dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_SCALER); + if (ret) { + dev_err(&pdev->dev, "Failed to init mem2mem media controller\n"); + goto error_v4l2; + } + + ret = media_device_register(&dw_dev->mdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register mem2mem media device\n"); + goto error_m2m_mc; + } + + dw100_debugfs_init(dw_dev); + + dev_info(&pdev->dev, + "dw100 v4l2 m2m registered as /dev/video%u\n", vfd->num); + + return 0; + +error_m2m_mc: + v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); +error_v4l2: + video_unregister_device(vfd); +err_m2m: + media_device_cleanup(&dw_dev->mdev); + v4l2_m2m_release(dw_dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dw_dev->v4l2_dev); +err_pm: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void dw100_remove(struct platform_device *pdev) +{ + struct dw100_device *dw_dev = platform_get_drvdata(pdev); + + dw100_debugfs_exit(dw_dev); + + pm_runtime_disable(&pdev->dev); + + media_device_unregister(&dw_dev->mdev); + v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); + media_device_cleanup(&dw_dev->mdev); + + video_unregister_device(&dw_dev->vfd); + mutex_destroy(dw_dev->vfd.lock); + v4l2_m2m_release(dw_dev->m2m_dev); + v4l2_device_unregister(&dw_dev->v4l2_dev); +} + +static int __maybe_unused dw100_runtime_suspend(struct device *dev) +{ + struct dw100_device *dw_dev = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(dw_dev->num_clks, dw_dev->clks); + + return 0; +} + +static int __maybe_unused dw100_runtime_resume(struct device *dev) +{ + int ret; + struct dw100_device *dw_dev = dev_get_drvdata(dev); + + ret = clk_bulk_prepare_enable(dw_dev->num_clks, dw_dev->clks); + + if (ret) + return ret; + + dw100_hw_reset(dw_dev); + + return 0; +} + +static const struct dev_pm_ops dw100_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(dw100_runtime_suspend, + dw100_runtime_resume, NULL) +}; + +static const struct of_device_id dw100_dt_ids[] = { + { .compatible = "nxp,imx8mp-dw100", .data = NULL }, + { }, +}; +MODULE_DEVICE_TABLE(of, dw100_dt_ids); + +static struct platform_driver dw100_driver = { + .probe = dw100_probe, + .remove_new = dw100_remove, + .driver = { + .name = DRV_NAME, + .pm = &dw100_pm, + .of_match_table = dw100_dt_ids, + }, +}; + +module_platform_driver(dw100_driver); + +MODULE_DESCRIPTION("DW100 Hardware dewarper"); +MODULE_AUTHOR("Xavier Roumegue <Xavier.Roumegue@oss.nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/dw100/dw100_regs.h b/drivers/media/platform/nxp/dw100/dw100_regs.h new file mode 100644 index 0000000000..e85dfeff90 --- /dev/null +++ b/drivers/media/platform/nxp/dw100/dw100_regs.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * DW100 Hardware dewarper + * + * Copyright 2022 NXP + * Author: Xavier Roumegue (xavier.roumegue@oss.nxp.com) + */ + +#ifndef _DW100_REGS_H_ +#define _DW100_REGS_H_ + +/* AHB register offset */ +#define DW100_DEWARP_ID 0x00 +#define DW100_DEWARP_CTRL 0x04 +#define DW100_DEWARP_CTRL_ENABLE BIT(0) +#define DW100_DEWARP_CTRL_START BIT(1) +#define DW100_DEWARP_CTRL_SOFT_RESET BIT(2) +#define DW100_DEWARP_CTRL_FORMAT_YUV422_SP 0UL +#define DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED 1UL +#define DW100_DEWARP_CTRL_FORMAT_YUV420_SP 2UL +#define DW100_DEWARP_CTRL_INPUT_FORMAT_MASK GENMASK(5, 4) +#define DW100_DEWARP_CTRL_INPUT_FORMAT(x) ((x) << 4) +#define DW100_DEWARP_CTRL_OUTPUT_FORMAT(x) ((x) << 6) +#define DW100_DEWARP_CTRL_OUTPUT_FORMAT_MASK GENMASK(7, 6) +#define DW100_DEWARP_CTRL_SRC_AUTO_SHADOW BIT(8) +#define DW100_DEWARP_CTRL_HW_HANDSHAKE BIT(9) +#define DW100_DEWARP_CTRL_DST_AUTO_SHADOW BIT(10) +#define DW100_DEWARP_CTRL_SPLIT_LINE BIT(11) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_MASK GENMASK(17, 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_TRAVERSAL (0UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION (1UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_AUTO (2UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_THRESHOLD_MASK GENMASK(24, 18) +#define DW100_DEWARP_CTRL_PREFETCH_THRESHOLD(x) ((x) << 18) + +#define DW100_MAP_LUT_ADDR 0x08 +#define DW100_MAP_LUT_ADDR_ADDR(addr) (((addr) >> 4) & GENMASK(29, 0)) +#define DW100_MAP_LUT_SIZE 0x0c +#define DW100_MAP_LUT_SIZE_WIDTH(w) (((w) & GENMASK(10, 0)) << 0) +#define DW100_MAP_LUT_SIZE_HEIGHT(h) (((h) & GENMASK(10, 0)) << 16) +#define DW100_SRC_IMG_Y_BASE 0x10 +#define DW100_IMG_Y_BASE(base) (((base) >> 4) & GENMASK(29, 0)) +#define DW100_SRC_IMG_UV_BASE 0x14 +#define DW100_IMG_UV_BASE(base) (((base) >> 4) & GENMASK(29, 0)) +#define DW100_SRC_IMG_SIZE 0x18 +#define DW100_IMG_SIZE_WIDTH(w) (((w) & GENMASK(12, 0)) << 0) +#define DW100_IMG_SIZE_HEIGHT(h) (((h) & GENMASK(12, 0)) << 16) + +#define DW100_SRC_IMG_STRIDE 0x1c +#define DW100_MAP_LUT_ADDR2 0x20 +#define DW100_MAP_LUT_SIZE2 0x24 +#define DW100_SRC_IMG_Y_BASE2 0x28 +#define DW100_SRC_IMG_UV_BASE2 0x2c +#define DW100_SRC_IMG_SIZE2 0x30 +#define DW100_SRC_IMG_STRIDE2 0x34 +#define DW100_DST_IMG_Y_BASE 0x38 +#define DW100_DST_IMG_UV_BASE 0x3c +#define DW100_DST_IMG_SIZE 0x40 +#define DW100_DST_IMG_STRIDE 0x44 +#define DW100_DST_IMG_Y_BASE2 0x48 +#define DW100_DST_IMG_UV_BASE2 0x4c +#define DW100_DST_IMG_SIZE2 0x50 +#define DW100_DST_IMG_STRIDE2 0x54 +#define DW100_SWAP_CONTROL 0x58 +#define DW100_SWAP_CONTROL_BYTE BIT(0) +#define DW100_SWAP_CONTROL_SHORT BIT(1) +#define DW100_SWAP_CONTROL_WORD BIT(2) +#define DW100_SWAP_CONTROL_LONG BIT(3) +#define DW100_SWAP_CONTROL_Y(x) (((x) & GENMASK(3, 0)) << 0) +#define DW100_SWAP_CONTROL_UV(x) (((x) & GENMASK(3, 0)) << 4) +#define DW100_SWAP_CONTROL_SRC(x) (((x) & GENMASK(7, 0)) << 0) +#define DW100_SWAP_CONTROL_DST(x) (((x) & GENMASK(7, 0)) << 8) +#define DW100_SWAP_CONTROL_SRC2(x) (((x) & GENMASK(7, 0)) << 16) +#define DW100_SWAP_CONTROL_DST2(x) (((x) & GENMASK(7, 0)) << 24) +#define DW100_SWAP_CONTROL_SRC_MASK GENMASK(7, 0) +#define DW100_SWAP_CONTROL_DST_MASK GENMASK(15, 8) +#define DW100_SWAP_CONTROL_SRC2_MASK GENMASK(23, 16) +#define DW100_SWAP_CONTROL_DST2_MASK GENMASK(31, 24) +#define DW100_VERTICAL_SPLIT_LINE 0x5c +#define DW100_HORIZON_SPLIT_LINE 0x60 +#define DW100_SCALE_FACTOR 0x64 +#define DW100_ROI_START 0x68 +#define DW100_ROI_START_X(x) (((x) & GENMASK(12, 0)) << 0) +#define DW100_ROI_START_Y(y) (((y) & GENMASK(12, 0)) << 16) +#define DW100_BOUNDARY_PIXEL 0x6c +#define DW100_BOUNDARY_PIXEL_V(v) (((v) & GENMASK(7, 0)) << 0) +#define DW100_BOUNDARY_PIXEL_U(u) (((u) & GENMASK(7, 0)) << 8) +#define DW100_BOUNDARY_PIXEL_Y(y) (((y) & GENMASK(7, 0)) << 16) + +#define DW100_INTERRUPT_STATUS 0x70 +#define DW100_INTERRUPT_STATUS_INT_FRAME_DONE BIT(0) +#define DW100_INTERRUPT_STATUS_INT_ERR_TIME_OUT BIT(1) +#define DW100_INTERRUPT_STATUS_INT_ERR_AXI_RESP BIT(2) +#define DW100_INTERRUPT_STATUS_INT_ERR_X BIT(3) +#define DW100_INTERRUPT_STATUS_INT_ERR_MB_FETCH BIT(4) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME2 BIT(5) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME3 BIT(6) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME_DONE BIT(7) +#define DW100_INTERRUPT_STATUS_INT_ERR_STATUS(x) (((x) >> 1) & 0x7f) +#define DW100_INTERRUPT_STATUS_INT_STATUS(x) ((x) & 0xff) + +#define DW100_INTERRUPT_STATUS_INT_ENABLE_MASK GENMASK(15, 8) +#define DW100_INTERRUPT_STATUS_INT_ENABLE(x) (((x) & GENMASK(7, 0)) << 8) +#define DW100_INTERRUPT_STATUS_FRAME_BUSY BIT(16) +#define DW100_INTERRUPT_STATUS_INT_CLEAR(x) (((x) & GENMASK(7, 0)) << 24) +#define DW100_BUS_CTRL 0x74 +#define DW100_BUS_CTRL_AXI_MASTER_ENABLE BIT(31) +#define DW100_BUS_CTRL1 0x78 +#define DW100_BUS_TIME_OUT_CYCLE 0x7c +#define DW100_DST_IMG_Y_SIZE1 0x80 +#define DW100_DST_IMG_Y_SIZE(sz) (((sz) >> 4) & GENMASK(29, 0)) +#define DW100_DST_IMG_UV_SIZE(sz) (((sz) >> 4) & GENMASK(29, 0)) +#define DW100_DST_IMG_UV_SIZE1 0x84 +#define DW100_DST_IMG_Y_SIZE2 0x88 +#define DW100_DST_IMG_UV_SIZE2 0x8c + +#endif /* _DW100_REGS_H_ */ diff --git a/drivers/media/platform/nxp/imx-jpeg/Kconfig b/drivers/media/platform/nxp/imx-jpeg/Kconfig new file mode 100644 index 0000000000..5214dcd7fa --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +config VIDEO_IMX8_JPEG + tristate "IMX8 JPEG Encoder/Decoder" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select V4L2_JPEG_HELPER + help + This is a video4linux2 driver for the i.MX8 QXP/QM integrated + JPEG encoder/decoder. diff --git a/drivers/media/platform/nxp/imx-jpeg/Makefile b/drivers/media/platform/nxp/imx-jpeg/Makefile new file mode 100644 index 0000000000..bf19c82e61 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +mxc-jpeg-encdec-objs := mxc-jpeg-hw.o mxc-jpeg.o +obj-$(CONFIG_VIDEO_IMX8_JPEG) += mxc-jpeg-encdec.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c new file mode 100644 index 0000000000..9a6e8b332e --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include <linux/delay.h> +#include <media/videobuf2-core.h> +#include "mxc-jpeg-hw.h" + +#define print_wrapper_reg(dev, base_address, reg_offset)\ + internal_print_wrapper_reg(dev, (base_address), #reg_offset,\ + (reg_offset)) +#define internal_print_wrapper_reg(dev, base_address, reg_name, reg_offset) {\ + int val;\ + val = readl((base_address) + (reg_offset));\ + dev_dbg(dev, "Wrapper reg %s = 0x%x\n", reg_name, val);\ +} + +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc) +{ + dev_dbg(dev, " MXC JPEG NEXT_DESCPT_PTR 0x%x\n", + desc->next_descpt_ptr); + dev_dbg(dev, " MXC JPEG BUF_BASE0 0x%x\n", desc->buf_base0); + dev_dbg(dev, " MXC JPEG BUF_BASE1 0x%x\n", desc->buf_base1); + dev_dbg(dev, " MXC JPEG LINE_PITCH %d\n", desc->line_pitch); + dev_dbg(dev, " MXC JPEG STM_BUFBASE 0x%x\n", desc->stm_bufbase); + dev_dbg(dev, " MXC JPEG STM_BUFSIZE %d\n", desc->stm_bufsize); + dev_dbg(dev, " MXC JPEG IMGSIZE %x (%d x %d)\n", desc->imgsize, + desc->imgsize >> 16, desc->imgsize & 0xFFFF); + dev_dbg(dev, " MXC JPEG STM_CTRL 0x%x\n", desc->stm_ctrl); +} + +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode) +{ + dev_dbg(dev, "CAST IP status regs:\n"); + print_wrapper_reg(dev, reg, CAST_STATUS0); + print_wrapper_reg(dev, reg, CAST_STATUS1); + print_wrapper_reg(dev, reg, CAST_STATUS2); + print_wrapper_reg(dev, reg, CAST_STATUS3); + print_wrapper_reg(dev, reg, CAST_STATUS4); + print_wrapper_reg(dev, reg, CAST_STATUS5); + print_wrapper_reg(dev, reg, CAST_STATUS6); + print_wrapper_reg(dev, reg, CAST_STATUS7); + print_wrapper_reg(dev, reg, CAST_STATUS8); + print_wrapper_reg(dev, reg, CAST_STATUS9); + print_wrapper_reg(dev, reg, CAST_STATUS10); + print_wrapper_reg(dev, reg, CAST_STATUS11); + print_wrapper_reg(dev, reg, CAST_STATUS12); + print_wrapper_reg(dev, reg, CAST_STATUS13); + if (mode == MXC_JPEG_DECODE) + return; + print_wrapper_reg(dev, reg, CAST_STATUS14); + print_wrapper_reg(dev, reg, CAST_STATUS15); + print_wrapper_reg(dev, reg, CAST_STATUS16); + print_wrapper_reg(dev, reg, CAST_STATUS17); + print_wrapper_reg(dev, reg, CAST_STATUS18); + print_wrapper_reg(dev, reg, CAST_STATUS19); +} + +void print_wrapper_info(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "Wrapper regs:\n"); + print_wrapper_reg(dev, reg, GLB_CTRL); + print_wrapper_reg(dev, reg, COM_STATUS); + print_wrapper_reg(dev, reg, BUF_BASE0); + print_wrapper_reg(dev, reg, BUF_BASE1); + print_wrapper_reg(dev, reg, LINE_PITCH); + print_wrapper_reg(dev, reg, STM_BUFBASE); + print_wrapper_reg(dev, reg, STM_BUFSIZE); + print_wrapper_reg(dev, reg, IMGSIZE); + print_wrapper_reg(dev, reg, STM_CTRL); +} + +void mxc_jpeg_enable_irq(void __iomem *reg, int slot) +{ + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(0xF0C, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); +} + +void mxc_jpeg_disable_irq(void __iomem *reg, int slot) +{ + writel(0x0, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); +} + +void mxc_jpeg_sw_reset(void __iomem *reg) +{ + /* + * engine soft reset, internal state machine reset + * this will not reset registers, however, it seems + * the registers may remain inconsistent with the internal state + * so, on purpose, at least let GLB_CTRL bits clear after this reset + */ + writel(GLB_CTRL_SFT_RST, reg + GLB_CTRL); +} + +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg, u8 extseq) +{ + dev_dbg(dev, "CAST Encoder CONFIG...\n"); + /* + * "Config_Mode" enabled, "Config_Mode auto clear enabled", + */ + if (extseq) + writel(0xb0, reg + CAST_MODE); + else + writel(0xa0, reg + CAST_MODE); + + /* all markers and segments */ + writel(0x3ff, reg + CAST_CFG_MODE); +} + +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg, u8 extseq) +{ + dev_dbg(dev, "CAST Encoder GO...\n"); + /* + * "GO" enabled, "GO bit auto clear" enabled + */ + if (extseq) + writel(0x150, reg + CAST_MODE); + else + writel(0x140, reg + CAST_MODE); +} + +void mxc_jpeg_enc_set_quality(struct device *dev, void __iomem *reg, u8 quality) +{ + dev_dbg(dev, "CAST Encoder Quality %d...\n", quality); + + /* quality factor */ + writel(quality, reg + CAST_QUALITY); +} + +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Decoder GO...\n"); + writel(MXC_DEC_EXIT_IDLE_MODE, reg + CAST_CTRL); +} + +int mxc_jpeg_enable(void __iomem *reg) +{ + u32 regval; + + writel(GLB_CTRL_JPG_EN, reg + GLB_CTRL); + regval = readl(reg); + return regval; +} + +void mxc_jpeg_enable_slot(void __iomem *reg, int slot) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + writel(GLB_CTRL_SLOT_EN(slot) | regval, reg + GLB_CTRL); +} + +void mxc_jpeg_set_l_endian(void __iomem *reg, int le) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + regval &= ~GLB_CTRL_L_ENDIAN(1); /* clear */ + writel(GLB_CTRL_L_ENDIAN(le) | regval, reg + GLB_CTRL); /* set */ +} + +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize) +{ + desc->stm_bufsize = bufsize; +} + +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h) +{ + desc->imgsize = w << 16 | h; +} + +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch) +{ + desc->line_pitch = line_pitch; +} + +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot) +{ + writel(desc | MXC_NXT_DESCPT_EN, + reg + MXC_SLOT_OFFSET(slot, SLOT_NXT_DESCPT_PTR)); +} + +void mxc_jpeg_clr_desc(void __iomem *reg, int slot) +{ + writel(0, reg + MXC_SLOT_OFFSET(slot, SLOT_NXT_DESCPT_PTR)); +} diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h new file mode 100644 index 0000000000..a2b4fb9e29 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#ifndef _MXC_JPEG_HW_H +#define _MXC_JPEG_HW_H + +/* JPEG Decoder/Encoder Wrapper Register Map */ +#define GLB_CTRL 0x0 +#define COM_STATUS 0x4 +#define BUF_BASE0 0x14 +#define BUF_BASE1 0x18 +#define LINE_PITCH 0x1C +#define STM_BUFBASE 0x20 +#define STM_BUFSIZE 0x24 +#define IMGSIZE 0x28 +#define STM_CTRL 0x2C + +/* CAST JPEG-Decoder/Encoder Status Register Map (read-only)*/ +#define CAST_STATUS0 0x100 +#define CAST_STATUS1 0x104 +#define CAST_STATUS2 0x108 +#define CAST_STATUS3 0x10c +#define CAST_STATUS4 0x110 +#define CAST_STATUS5 0x114 +#define CAST_STATUS6 0x118 +#define CAST_STATUS7 0x11c +#define CAST_STATUS8 0x120 +#define CAST_STATUS9 0x124 +#define CAST_STATUS10 0x128 +#define CAST_STATUS11 0x12c +#define CAST_STATUS12 0x130 +#define CAST_STATUS13 0x134 +/* the following are for encoder only */ +#define CAST_STATUS14 0x138 +#define CAST_STATUS15 0x13c +#define CAST_STATUS16 0x140 +#define CAST_STATUS17 0x144 +#define CAST_STATUS18 0x148 +#define CAST_STATUS19 0x14c + +/* CAST JPEG-Decoder Control Register Map (write-only) */ +#define CAST_CTRL CAST_STATUS13 + +/* CAST JPEG-Encoder Control Register Map (write-only) */ +#define CAST_MODE CAST_STATUS0 +#define CAST_CFG_MODE CAST_STATUS1 +#define CAST_QUALITY CAST_STATUS2 +#define CAST_RSVD CAST_STATUS3 +#define CAST_REC_REGS_SEL CAST_STATUS4 +#define CAST_LUMTH CAST_STATUS5 +#define CAST_CHRTH CAST_STATUS6 +#define CAST_NOMFRSIZE_LO CAST_STATUS16 +#define CAST_NOMFRSIZE_HI CAST_STATUS17 +#define CAST_OFBSIZE_LO CAST_STATUS18 +#define CAST_OFBSIZE_HI CAST_STATUS19 + +/* JPEG-Decoder Wrapper Slot Registers 0..3 */ +#define SLOT_BASE 0x10000 +#define SLOT_STATUS 0x0 +#define SLOT_IRQ_EN 0x4 +#define SLOT_BUF_PTR 0x8 +#define SLOT_CUR_DESCPT_PTR 0xC +#define SLOT_NXT_DESCPT_PTR 0x10 +#define MXC_SLOT_OFFSET(slot, offset) ((SLOT_BASE * ((slot) + 1)) + (offset)) + +/* GLB_CTRL fields */ +#define GLB_CTRL_JPG_EN 0x1 +#define GLB_CTRL_SFT_RST (0x1 << 1) +#define GLB_CTRL_DEC_GO (0x1 << 2) +#define GLB_CTRL_L_ENDIAN(le) ((le) << 3) +#define GLB_CTRL_SLOT_EN(slot) (0x1 << ((slot) + 4)) + +/* COM_STAUS fields */ +#define COM_STATUS_DEC_ONGOING(r) (((r) & (1 << 31)) >> 31) +#define COM_STATUS_CUR_SLOT(r) (((r) & (0x3 << 29)) >> 29) + +/* STM_CTRL fields */ +#define STM_CTRL_PIXEL_PRECISION (0x1 << 2) +#define STM_CTRL_IMAGE_FORMAT(img_fmt) ((img_fmt) << 3) +#define STM_CTRL_IMAGE_FORMAT_MASK (0xF << 3) +#define STM_CTRL_BITBUF_PTR_CLR(clr) ((clr) << 7) +#define STM_CTRL_AUTO_START(go) ((go) << 8) +#define STM_CTRL_CONFIG_MOD(mod) ((mod) << 9) + +/* SLOT_STATUS fields for slots 0..3 */ +#define SLOT_STATUS_FRMDONE (0x1 << 3) +#define SLOT_STATUS_ENC_CONFIG_ERR (0x1 << 8) + +/* SLOT_IRQ_EN fields TBD */ + +#define MXC_NXT_DESCPT_EN 0x1 +#define MXC_DEC_EXIT_IDLE_MODE 0x4 + +/* JPEG-Decoder Wrapper - STM_CTRL Register Fields */ +#define MXC_PIXEL_PRECISION(precision) ((precision) / 8 << 2) +enum mxc_jpeg_image_format { + MXC_JPEG_INVALID = -1, + MXC_JPEG_YUV420 = 0x0, /* 2 Plannar, Y=1st plane UV=2nd plane */ + MXC_JPEG_YUV422 = 0x1, /* 1 Plannar, YUYV sequence */ + MXC_JPEG_BGR = 0x2, /* BGR packed format */ + MXC_JPEG_YUV444 = 0x3, /* 1 Plannar, YUVYUV sequence */ + MXC_JPEG_GRAY = 0x4, /* Y8 or Y12 or Single Component */ + MXC_JPEG_RESERVED = 0x5, + MXC_JPEG_ABGR = 0x6, +}; + +#include "mxc-jpeg.h" +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc); +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode); +void print_wrapper_info(struct device *dev, void __iomem *reg); +void mxc_jpeg_sw_reset(void __iomem *reg); +int mxc_jpeg_enable(void __iomem *reg); +void wait_frmdone(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg, u8 extseq); +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg, u8 extseq); +void mxc_jpeg_enc_set_quality(struct device *dev, void __iomem *reg, u8 quality); +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg); +int mxc_jpeg_get_slot(void __iomem *reg); +u32 mxc_jpeg_get_offset(void __iomem *reg, int slot); +void mxc_jpeg_enable_slot(void __iomem *reg, int slot); +void mxc_jpeg_set_l_endian(void __iomem *reg, int le); +void mxc_jpeg_enable_irq(void __iomem *reg, int slot); +void mxc_jpeg_disable_irq(void __iomem *reg, int slot); +int mxc_jpeg_set_input(void __iomem *reg, u32 in_buf, u32 bufsize); +int mxc_jpeg_set_output(void __iomem *reg, u16 out_pitch, u32 out_buf, + u16 w, u16 h); +void mxc_jpeg_set_config_mode(void __iomem *reg, int config_mode); +int mxc_jpeg_set_params(struct mxc_jpeg_desc *desc, u32 bufsize, u16 + out_pitch, u32 format); +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize); +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h); +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch); +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot); +void mxc_jpeg_clr_desc(void __iomem *reg, int slot); +void mxc_jpeg_set_regs_from_desc(struct mxc_jpeg_desc *desc, + void __iomem *reg); +#endif diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c new file mode 100644 index 0000000000..0c8b204535 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -0,0 +1,2966 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 driver for the JPEG encoder/decoder from i.MX8QXP/i.MX8QM application + * processors. + * + * The multi-planar buffers API is used. + * + * Baseline and extended sequential jpeg decoding is supported. + * Progressive jpeg decoding is not supported by the IP. + * Supports encode and decode of various formats: + * YUV444, YUV422, YUV420, BGR, ABGR, Gray + * YUV420 is the only multi-planar format supported. + * Minimum resolution is 64 x 64, maximum 8192 x 8192. + * To achieve 8192 x 8192, modify in defconfig: CONFIG_CMA_SIZE_MBYTES=320 + * The alignment requirements for the resolution depend on the format, + * multiple of 16 resolutions should work for all formats. + * Special workarounds are made in the driver to support NV12 1080p. + * When decoding, the driver detects image resolution and pixel format + * from the jpeg stream, by parsing the jpeg markers. + * + * The IP has 4 slots available for context switching, but only slot 0 + * was fully tested to work. Context switching is not used by the driver. + * Each driver instance (context) allocates a slot for itself, but this + * is postponed until device_run, to allow unlimited opens. + * + * The driver submits jobs to the IP by setting up a descriptor for the + * used slot, and then validating it. The encoder has an additional descriptor + * for the configuration phase. The driver expects FRM_DONE interrupt from + * IP to mark the job as finished. + * + * The decoder IP has some limitations regarding the component ID's, + * but the driver works around this by replacing them in the jpeg stream. + * + * A module parameter is available for debug purpose (jpeg_tracing), to enable + * it, enable dynamic debug for this module and: + * echo 1 > /sys/module/mxc_jpeg_encdec/parameters/jpeg_tracing + * + * This is inspired by the drivers/media/platform/samsung/s5p-jpeg driver + * + * Copyright 2018-2019 NXP + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/irqreturn.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/string.h> + +#include <media/v4l2-jpeg.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "mxc-jpeg-hw.h" +#include "mxc-jpeg.h" + +static const struct mxc_jpeg_fmt mxc_formats[] = { + { + .name = "JPEG", + .fourcc = V4L2_PIX_FMT_JPEG, + .subsampling = -1, + .nc = -1, + .mem_planes = 1, + .comp_planes = 1, + .flags = MXC_JPEG_FMT_TYPE_ENC, + }, + { + .name = "BGR", /*BGR packed format*/ + .fourcc = V4L2_PIX_FMT_BGR24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + .is_rgb = 1, + }, + { + .name = "BGR 12bit", /*12-bit BGR packed format*/ + .fourcc = V4L2_PIX_FMT_BGR48_12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 36, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + .is_rgb = 1, + }, + { + .name = "ABGR", /* ABGR packed format */ + .fourcc = V4L2_PIX_FMT_ABGR32, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 4, + .depth = 32, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + .is_rgb = 1, + }, + { + .name = "ABGR 12bit", /* 12-bit ABGR packed format */ + .fourcc = V4L2_PIX_FMT_ABGR64_12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 4, + .depth = 48, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + .is_rgb = 1, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12M, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .mem_planes = 2, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .mem_planes = 1, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV420 12bit", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_P012M, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 18, /* 6 x 12 bits (4Y + UV) for 4 pixels */ + .mem_planes = 2, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + }, + { + .name = "YUV420 12bit", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_P012, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 18, /* 6 x 12 bits (4Y + UV) for 4 pixels */ + .mem_planes = 1, + .comp_planes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + }, + { + .name = "YUV422", /* YUYV */ + .fourcc = V4L2_PIX_FMT_YUYV, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + .nc = 3, + .depth = 16, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 4, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV422 12bit", /* YUYV */ + .fourcc = V4L2_PIX_FMT_Y212, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + .nc = 3, + .depth = 24, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 4, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + }, + { + .name = "YUV444", /* YUVYUV */ + .fourcc = V4L2_PIX_FMT_YUV24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV444 12bit", /* YUVYUV */ + .fourcc = V4L2_PIX_FMT_YUV48_12, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 36, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + }, + { + .name = "Gray", /* Gray (Y8/Y12) or Single Comp */ + .fourcc = V4L2_PIX_FMT_GREY, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + .nc = 1, + .depth = 8, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "Gray 12bit", /* Gray (Y8/Y12) or Single Comp */ + .fourcc = V4L2_PIX_FMT_Y012, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + .nc = 1, + .depth = 12, + .mem_planes = 1, + .comp_planes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 12, + }, +}; + +#define MXC_JPEG_NUM_FORMATS ARRAY_SIZE(mxc_formats) + +static const int mxc_decode_mode = MXC_JPEG_DECODE; +static const int mxc_encode_mode = MXC_JPEG_ENCODE; + +static const struct of_device_id mxc_jpeg_match[] = { + { + .compatible = "nxp,imx8qxp-jpgdec", + .data = &mxc_decode_mode, + }, + { + .compatible = "nxp,imx8qxp-jpgenc", + .data = &mxc_encode_mode, + }, + { }, +}; + +/* + * default configuration stream, 64x64 yuv422 + * split by JPEG marker, so it's easier to modify & use + */ +static const unsigned char jpeg_soi[] = { + 0xFF, 0xD8 +}; + +static const unsigned char jpeg_app0[] = { + 0xFF, 0xE0, + 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00 +}; + +static const unsigned char jpeg_app14[] = { + 0xFF, 0xEE, + 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const unsigned char jpeg_dqt[] = { + 0xFF, 0xDB, + 0x00, 0x84, 0x00, 0x10, 0x0B, 0x0C, 0x0E, + 0x0C, 0x0A, 0x10, 0x0E, 0x0D, 0x0E, 0x12, + 0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, + 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, 0x1D, + 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, + 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, + 0x51, 0x57, 0x5F, 0x62, 0x67, 0x68, 0x67, + 0x3E, 0x4D, 0x71, 0x79, 0x70, 0x64, 0x78, + 0x5C, 0x65, 0x67, 0x63, 0x01, 0x11, 0x12, + 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, + 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const unsigned char jpeg_dqt_extseq[] = { + 0xFF, 0xDB, + 0x01, 0x04, + 0x10, + 0x00, 0x80, 0x00, 0x58, 0x00, 0x60, 0x00, 0x70, + 0x00, 0x60, 0x00, 0x50, 0x00, 0x80, 0x00, 0x70, + 0x00, 0x68, 0x00, 0x70, 0x00, 0x90, 0x00, 0x88, + 0x00, 0x80, 0x00, 0x98, 0x00, 0xC0, 0x01, 0x40, + 0x00, 0xD0, 0x00, 0xC0, 0x00, 0xB0, 0x00, 0xB0, + 0x00, 0xC0, 0x01, 0x88, 0x01, 0x18, 0x01, 0x28, + 0x00, 0xE8, 0x01, 0x40, 0x01, 0xD0, 0x01, 0x98, + 0x01, 0xE8, 0x01, 0xE0, 0x01, 0xC8, 0x01, 0x98, + 0x01, 0xC0, 0x01, 0xB8, 0x02, 0x00, 0x02, 0x40, + 0x02, 0xE0, 0x02, 0x70, 0x02, 0x00, 0x02, 0x20, + 0x02, 0xB8, 0x02, 0x28, 0x01, 0xB8, 0x01, 0xC0, + 0x02, 0x80, 0x03, 0x68, 0x02, 0x88, 0x02, 0xB8, + 0x02, 0xF8, 0x03, 0x10, 0x03, 0x38, 0x03, 0x40, + 0x03, 0x38, 0x01, 0xF0, 0x02, 0x68, 0x03, 0x88, + 0x03, 0xC8, 0x03, 0x80, 0x03, 0x20, 0x03, 0xC0, + 0x02, 0xE0, 0x03, 0x28, 0x03, 0x38, 0x03, 0x18, + 0x11, + 0x00, 0x88, 0x00, 0x90, 0x00, 0x90, 0x00, 0xC0, + 0x00, 0xA8, 0x00, 0xC0, 0x01, 0x78, 0x00, 0xD0, + 0x00, 0xD0, 0x01, 0x78, 0x03, 0x18, 0x02, 0x10, + 0x01, 0xC0, 0x02, 0x10, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, + 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, 0x03, 0x18, +}; + +static const unsigned char jpeg_sof_maximal[] = { + 0xFF, 0xC0, + 0x00, 0x14, 0x08, 0x00, 0x40, 0x00, 0x40, + 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01, 0x04, 0x11, 0x01 +}; + +static const unsigned char jpeg_sof_extseq[] = { + 0xFF, 0xC1, + 0x00, 0x14, 0x08, 0x00, 0x40, 0x00, 0x40, + 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01, 0x04, 0x11, 0x01 +}; + +static const unsigned char jpeg_dht[] = { + 0xFF, 0xC4, + 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, + 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, + 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, + 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, + 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, + 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, + 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, + 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, + 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, + 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, + 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, + 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, + 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, + 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; + +static const unsigned char jpeg_dht_extseq[] = { + 0xFF, 0xC4, + 0x02, 0x2a, 0x00, 0x00, 0x01, 0x05, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, + 0x04, 0x03, 0x05, 0x05, 0x02, 0x03, 0x02, + 0x00, 0x00, 0xbf, 0x01, 0x02, 0x03, 0x00, + 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, + 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, + 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf8, 0xf9, 0xfa, 0x0b, 0x0c, 0x0d, + 0x0e, 0x1b, 0x1c, 0x1d, 0x1e, 0x2b, 0x2c, + 0x2d, 0x2e, 0x3b, 0x3c, 0x3d, 0x3e, 0x4b, + 0x4c, 0x4d, 0x4e, 0x5b, 0x5c, 0x5d, 0x5e, + 0x6b, 0x6c, 0x6d, 0x6e, 0x7b, 0x7c, 0x7d, + 0x7e, 0x8b, 0x8c, 0x8d, 0x8e, 0x9b, 0x9c, + 0x9d, 0x9e, 0xab, 0xac, 0xad, 0xae, 0xbb, + 0xbc, 0xbd, 0xbe, 0xcb, 0xcc, 0xcd, 0xce, + 0xdb, 0xdc, 0xdd, 0xde, 0xeb, 0xec, 0xed, + 0xee, 0xfb, 0xfc, 0xfd, 0xfe, 0x01, 0x00, + 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x11, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, + 0x02, 0x03, 0x02, 0x00, 0x00, 0xbf, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, + 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, + 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0x0b, 0x0c, 0x0d, 0x0e, 0x1b, 0x1c, 0x1d, + 0x1e, 0x2b, 0x2c, 0x2d, 0x2e, 0x3b, 0x3c, + 0x3d, 0x3e, 0x4b, 0x4c, 0x4d, 0x4e, 0x5b, + 0x5c, 0x5d, 0x5e, 0x6b, 0x6c, 0x6d, 0x6e, + 0x7b, 0x7c, 0x7d, 0x7e, 0x8b, 0x8c, 0x8d, + 0x8e, 0x9b, 0x9c, 0x9d, 0x9e, 0xab, 0xac, + 0xad, 0xae, 0xbb, 0xbc, 0xbd, 0xbe, 0xcb, + 0xcc, 0xcd, 0xce, 0xdb, 0xdc, 0xdd, 0xde, + 0xeb, 0xec, 0xed, 0xee, 0xfb, 0xfc, 0xfd, + 0xfe, +}; + +static const unsigned char jpeg_dri[] = { + 0xFF, 0xDD, + 0x00, 0x04, 0x00, 0x20 +}; + +static const unsigned char jpeg_sos_maximal[] = { + 0xFF, 0xDA, + 0x00, 0x0C, 0x04, 0x01, 0x00, 0x02, 0x11, 0x03, + 0x11, 0x04, 0x11, 0x00, 0x3F, 0x00 +}; + +static const unsigned char jpeg_image_red[] = { + 0xFC, 0x5F, 0xA2, 0xBF, 0xCA, 0x73, 0xFE, 0xFE, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00 +}; + +static const unsigned char jpeg_eoi[] = { + 0xFF, 0xD9 +}; + +struct mxc_jpeg_src_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer b; + struct list_head list; + + /* mxc-jpeg specific */ + bool dht_needed; + bool jpeg_parse_error; + const struct mxc_jpeg_fmt *fmt; + int w; + int h; +}; + +static inline struct mxc_jpeg_src_buf *vb2_to_mxc_buf(struct vb2_buffer *vb) +{ + return container_of(to_vb2_v4l2_buffer(vb), + struct mxc_jpeg_src_buf, b); +} + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-3)"); + +static unsigned int hw_timeout = 2000; +module_param(hw_timeout, int, 0644); +MODULE_PARM_DESC(hw_timeout, "MXC JPEG hw timeout, the number of milliseconds"); + +static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision); +static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q); + +static void _bswap16(u16 *a) +{ + *a = ((*a & 0x00FF) << 8) | ((*a & 0xFF00) >> 8); +} + +static void print_mxc_buf(struct mxc_jpeg_dev *jpeg, struct vb2_buffer *buf, + unsigned long len) +{ + unsigned int plane_no; + u32 dma_addr; + void *vaddr; + unsigned long payload; + + if (debug < 3) + return; + + for (plane_no = 0; plane_no < buf->num_planes; plane_no++) { + payload = vb2_get_plane_payload(buf, plane_no); + if (len == 0) + len = payload; + dma_addr = vb2_dma_contig_plane_dma_addr(buf, plane_no); + vaddr = vb2_plane_vaddr(buf, plane_no); + v4l2_dbg(3, debug, &jpeg->v4l2_dev, + "plane %d (vaddr=%p dma_addr=%x payload=%ld):", + plane_no, vaddr, dma_addr, payload); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + vaddr, len, false); + } +} + +static inline struct mxc_jpeg_ctx *mxc_jpeg_fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mxc_jpeg_ctx, fh); +} + +static int enum_fmt(const struct mxc_jpeg_fmt *mxc_formats, int n, + struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + + for (i = 0; i < n; ++i) { + if (mxc_formats[i].flags == type) { + /* index-th format of searched type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + /* Format not found */ + if (i >= n) + return -EINVAL; + + f->pixelformat = mxc_formats[i].fourcc; + + return 0; +} + +static const struct mxc_jpeg_fmt *mxc_jpeg_find_format(u32 pixelformat) +{ + unsigned int k; + + for (k = 0; k < MXC_JPEG_NUM_FORMATS; k++) { + const struct mxc_jpeg_fmt *fmt = &mxc_formats[k]; + + if (fmt->fourcc == pixelformat) + return fmt; + } + return NULL; +} + +static enum mxc_jpeg_image_format mxc_jpeg_fourcc_to_imgfmt(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y012: + return MXC_JPEG_GRAY; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_Y212: + return MXC_JPEG_YUV422; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_P012: + case V4L2_PIX_FMT_P012M: + return MXC_JPEG_YUV420; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_YUV48_12: + return MXC_JPEG_YUV444; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR48_12: + return MXC_JPEG_BGR; + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_ABGR64_12: + return MXC_JPEG_ABGR; + default: + return MXC_JPEG_INVALID; + } +} + +static struct mxc_jpeg_q_data *mxc_jpeg_get_q_data(struct mxc_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + return &ctx->cap_q; +} + +static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, + struct vb2_buffer *raw_buf, + struct vb2_buffer *jpeg_buf, int offset) +{ + int img_fmt = desc->stm_ctrl & STM_CTRL_IMAGE_FORMAT_MASK; + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(raw_buf->vb2_queue); + struct mxc_jpeg_q_data *q_data; + + q_data = mxc_jpeg_get_q_data(ctx, raw_buf->type); + desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); + desc->buf_base1 = 0; + if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { + if (raw_buf->num_planes == 2) + desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + else + desc->buf_base1 = desc->buf_base0 + q_data->sizeimage[0]; + } + desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + + offset; +} + +static bool mxc_jpeg_is_extended_sequential(const struct mxc_jpeg_fmt *fmt) +{ + if (!fmt || !(fmt->flags & MXC_JPEG_FMT_TYPE_RAW)) + return false; + + if (fmt->precision > 8) + return true; + + return false; +} + +static void notify_eos(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event EOS reached"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static void notify_src_chg(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event SRC_CH_RESOLUTION"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static int mxc_get_free_slot(struct mxc_jpeg_slot_data *slot_data) +{ + if (!slot_data->used) + return slot_data->slot; + return -1; +} + +static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg) +{ + struct mxc_jpeg_desc *desc; + struct mxc_jpeg_desc *cfg_desc; + void *cfg_stm; + + if (jpeg->slot_data.desc) + goto skip_alloc; /* already allocated, reuse it */ + + /* allocate descriptor for decoding/encoding phase */ + desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data.desc_handle, + GFP_ATOMIC); + if (!desc) + goto err; + jpeg->slot_data.desc = desc; + + /* allocate descriptor for configuration phase (encoder only) */ + cfg_desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data.cfg_desc_handle, + GFP_ATOMIC); + if (!cfg_desc) + goto err; + jpeg->slot_data.cfg_desc = cfg_desc; + + /* allocate configuration stream */ + cfg_stm = dma_alloc_coherent(jpeg->dev, + MXC_JPEG_MAX_CFG_STREAM, + &jpeg->slot_data.cfg_stream_handle, + GFP_ATOMIC); + if (!cfg_stm) + goto err; + jpeg->slot_data.cfg_stream_vaddr = cfg_stm; + +skip_alloc: + jpeg->slot_data.used = true; + + return true; +err: + dev_err(jpeg->dev, "Could not allocate descriptors for slot %d", jpeg->slot_data.slot); + + return false; +} + +static void mxc_jpeg_free_slot_data(struct mxc_jpeg_dev *jpeg) +{ + /* free descriptor for decoding/encoding phase */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data.desc, + jpeg->slot_data.desc_handle); + + /* free descriptor for encoder configuration phase / decoder DHT */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data.cfg_desc, + jpeg->slot_data.cfg_desc_handle); + + /* free configuration stream */ + dma_free_coherent(jpeg->dev, MXC_JPEG_MAX_CFG_STREAM, + jpeg->slot_data.cfg_stream_vaddr, + jpeg->slot_data.cfg_stream_handle); + + jpeg->slot_data.used = false; +} + +static void mxc_jpeg_check_and_set_last_buffer(struct mxc_jpeg_ctx *ctx, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + if (v4l2_m2m_is_last_draining_src_buf(ctx->fh.m2m_ctx, src_buf)) { + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx); + notify_eos(ctx); + ctx->header_parsed = false; + } +} + +static void mxc_jpeg_job_finish(struct mxc_jpeg_ctx *ctx, enum vb2_buffer_state state, bool reset) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, state); + v4l2_m2m_buf_done(dst_buf, state); + + mxc_jpeg_disable_irq(reg, ctx->slot); + jpeg->slot_data.used = false; + if (reset) + mxc_jpeg_sw_reset(reg); +} + +static u32 mxc_jpeg_get_plane_size(struct mxc_jpeg_q_data *q_data, u32 plane_no) +{ + const struct mxc_jpeg_fmt *fmt = q_data->fmt; + u32 size; + int i; + + if (plane_no >= fmt->mem_planes) + return 0; + + if (fmt->mem_planes == fmt->comp_planes) + return q_data->sizeimage[plane_no]; + + if (plane_no < fmt->mem_planes - 1) + return q_data->sizeimage[plane_no]; + + size = q_data->sizeimage[fmt->mem_planes - 1]; + + /* Should be impossible given mxc_formats. */ + if (WARN_ON_ONCE(fmt->comp_planes > ARRAY_SIZE(q_data->sizeimage))) + return size; + + for (i = fmt->mem_planes; i < fmt->comp_planes; i++) + size += q_data->sizeimage[i]; + + return size; +} + +static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) +{ + struct mxc_jpeg_dev *jpeg = priv; + struct mxc_jpeg_ctx *ctx; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mxc_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state; + u32 dec_ret, com_status; + unsigned long payload; + struct mxc_jpeg_q_data *q_data; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + unsigned int slot; + + spin_lock(&jpeg->hw_lock); + + com_status = readl(reg + COM_STATUS); + slot = COM_STATUS_CUR_SLOT(com_status); + dev_dbg(dev, "Irq %d on slot %d.\n", irq, slot); + + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + if (WARN_ON(!ctx)) + goto job_unlock; + + if (slot != ctx->slot) { + /* TODO investigate when adding multi-instance support */ + dev_warn(dev, "IRQ slot %d != context slot %d.\n", + slot, ctx->slot); + goto job_unlock; + } + + if (!jpeg->slot_data.used) + goto job_unlock; + + dec_ret = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(dec_ret, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); /* w1c */ + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!dst_buf || !src_buf) { + dev_err(dev, "No source or destination buffer.\n"); + goto job_unlock; + } + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + + if (dec_ret & SLOT_STATUS_ENC_CONFIG_ERR) { + u32 ret = readl(reg + CAST_STATUS12); + + dev_err(dev, "Encoder/decoder error, dec_ret = 0x%08x, status=0x%08x", + dec_ret, ret); + mxc_jpeg_clr_desc(reg, slot); + mxc_jpeg_sw_reset(reg); + buf_state = VB2_BUF_STATE_ERROR; + goto buffers_done; + } + + if (!(dec_ret & SLOT_STATUS_FRMDONE)) + goto job_unlock; + + if (jpeg->mode == MXC_JPEG_ENCODE && + ctx->enc_state == MXC_JPEG_ENC_CONF) { + q_data = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + ctx->enc_state = MXC_JPEG_ENCODING; + dev_dbg(dev, "Encoder config finished. Start encoding...\n"); + mxc_jpeg_enc_set_quality(dev, reg, ctx->jpeg_quality); + mxc_jpeg_enc_mode_go(dev, reg, mxc_jpeg_is_extended_sequential(q_data->fmt)); + goto job_unlock; + } + if (jpeg->mode == MXC_JPEG_DECODE && jpeg_src_buf->dht_needed) { + jpeg_src_buf->dht_needed = false; + dev_dbg(dev, "Decoder DHT cfg finished. Start decoding...\n"); + goto job_unlock; + } + + if (jpeg->mode == MXC_JPEG_ENCODE) { + payload = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_BUF_PTR)); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + dev_dbg(dev, "Encoding finished, payload size: %ld\n", + payload); + } else { + q_data = mxc_jpeg_get_q_data(ctx, cap_type); + payload = mxc_jpeg_get_plane_size(q_data, 0); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); + if (q_data->fmt->mem_planes == 2) { + payload = mxc_jpeg_get_plane_size(q_data, 1); + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); + } + dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", + vb2_get_plane_payload(&dst_buf->vb2_buf, 0), + vb2_get_plane_payload(&dst_buf->vb2_buf, 1)); + } + + /* short preview of the results */ + dev_dbg(dev, "src_buf preview: "); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 32); + dev_dbg(dev, "dst_buf preview: "); + print_mxc_buf(jpeg, &dst_buf->vb2_buf, 32); + buf_state = VB2_BUF_STATE_DONE; + +buffers_done: + mxc_jpeg_job_finish(ctx, buf_state, false); + spin_unlock(&jpeg->hw_lock); + cancel_delayed_work(&ctx->task_timer); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return IRQ_HANDLED; +job_unlock: + spin_unlock(&jpeg->hw_lock); + return IRQ_HANDLED; +} + +static int mxc_jpeg_fixup_sof(struct mxc_jpeg_sof *sof, + u32 fourcc, + u16 w, u16 h) +{ + int sof_length; + const struct mxc_jpeg_fmt *fmt = mxc_jpeg_find_format(fourcc); + + if (fmt) + sof->precision = fmt->precision; + else + sof->precision = 8; /* TODO allow 8/12 bit precision*/ + sof->height = h; + _bswap16(&sof->height); + sof->width = w; + _bswap16(&sof->width); + + switch (fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_P012: + case V4L2_PIX_FMT_P012M: + sof->components_no = 3; + sof->comp[0].v = 0x2; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_Y212: + sof->components_no = 3; + sof->comp[0].v = 0x1; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_YUV48_12: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR48_12: + default: + sof->components_no = 3; + break; + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_ABGR64_12: + sof->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y012: + sof->components_no = 1; + break; + } + sof_length = 8 + 3 * sof->components_no; + sof->length = sof_length; + _bswap16(&sof->length); + + return sof_length; /* not swaped */ +} + +static int mxc_jpeg_fixup_sos(struct mxc_jpeg_sos *sos, + u32 fourcc) +{ + int sos_length; + u8 *sof_u8 = (u8 *)sos; + + switch (fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_P012: + case V4L2_PIX_FMT_P012M: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_Y212: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_YUV48_12: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR48_12: + default: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_ABGR64_12: + sos->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y012: + sos->components_no = 1; + break; + } + sos_length = 6 + 2 * sos->components_no; + sos->length = sos_length; + _bswap16(&sos->length); + + /* SOS ignorable bytes, not so ignorable after all */ + sof_u8[sos_length - 1] = 0x0; + sof_u8[sos_length - 2] = 0x3f; + sof_u8[sos_length - 3] = 0x0; + + return sos_length; /* not swaped */ +} + +static unsigned int mxc_jpeg_setup_cfg_stream(void *cfg_stream_vaddr, + u32 fourcc, + u16 w, u16 h) +{ + /* + * There is a hardware issue that first 128 bytes of configuration data + * can't be loaded correctly. + * To avoid this issue, we need to write the configuration from + * an offset which should be no less than 0x80 (128 bytes). + */ + unsigned int offset = 0x80; + u8 *cfg = (u8 *)cfg_stream_vaddr; + struct mxc_jpeg_sof *sof; + struct mxc_jpeg_sos *sos; + const struct mxc_jpeg_fmt *fmt = mxc_jpeg_find_format(fourcc); + + if (!fmt) + return 0; + + memcpy(cfg + offset, jpeg_soi, ARRAY_SIZE(jpeg_soi)); + offset += ARRAY_SIZE(jpeg_soi); + + if (fmt->is_rgb) { + memcpy(cfg + offset, jpeg_app14, sizeof(jpeg_app14)); + offset += sizeof(jpeg_app14); + } else { + memcpy(cfg + offset, jpeg_app0, sizeof(jpeg_app0)); + offset += sizeof(jpeg_app0); + } + + if (mxc_jpeg_is_extended_sequential(fmt)) { + memcpy(cfg + offset, jpeg_dqt_extseq, sizeof(jpeg_dqt_extseq)); + offset += sizeof(jpeg_dqt_extseq); + + memcpy(cfg + offset, jpeg_sof_extseq, sizeof(jpeg_sof_extseq)); + } else { + memcpy(cfg + offset, jpeg_dqt, sizeof(jpeg_dqt)); + offset += sizeof(jpeg_dqt); + + memcpy(cfg + offset, jpeg_sof_maximal, sizeof(jpeg_sof_maximal)); + } + offset += 2; /* skip marker ID */ + sof = (struct mxc_jpeg_sof *)(cfg + offset); + offset += mxc_jpeg_fixup_sof(sof, fourcc, w, h); + + if (mxc_jpeg_is_extended_sequential(fmt)) { + memcpy(cfg + offset, jpeg_dht_extseq, sizeof(jpeg_dht_extseq)); + offset += sizeof(jpeg_dht_extseq); + } else { + memcpy(cfg + offset, jpeg_dht, sizeof(jpeg_dht)); + offset += sizeof(jpeg_dht); + } + + memcpy(cfg + offset, jpeg_dri, sizeof(jpeg_dri)); + offset += sizeof(jpeg_dri); + + memcpy(cfg + offset, jpeg_sos_maximal, sizeof(jpeg_sos_maximal)); + offset += 2; /* skip marker ID */ + sos = (struct mxc_jpeg_sos *)(cfg + offset); + offset += mxc_jpeg_fixup_sos(sos, fourcc); + + memcpy(cfg + offset, jpeg_image_red, sizeof(jpeg_image_red)); + offset += sizeof(jpeg_image_red); + + memcpy(cfg + offset, jpeg_eoi, sizeof(jpeg_eoi)); + offset += sizeof(jpeg_eoi); + + return offset; +} + +static void mxc_jpeg_config_dec_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + struct mxc_jpeg_q_data *q_data_cap; + enum mxc_jpeg_image_format img_fmt; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data.desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data.cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data.desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data.cfg_desc_handle; + dma_addr_t cfg_stream_handle = jpeg->slot_data.cfg_stream_handle; + unsigned int *cfg_size = &jpeg->slot_data.cfg_stream_size; + void *cfg_stream_vaddr = jpeg->slot_data.cfg_stream_vaddr; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + jpeg_src_buf = vb2_to_mxc_buf(src_buf); + + /* setup the decoding descriptor */ + desc->next_descpt_ptr = 0; /* end of chain */ + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + desc->imgsize = q_data_cap->w_adjusted << 16 | q_data_cap->h_adjusted; + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data_cap->fmt->fourcc); + desc->stm_ctrl &= ~STM_CTRL_IMAGE_FORMAT(0xF); /* clear image format */ + desc->stm_ctrl |= STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + if (mxc_jpeg_is_extended_sequential(jpeg_src_buf->fmt)) + desc->stm_ctrl |= STM_CTRL_PIXEL_PRECISION; + else + desc->stm_ctrl &= ~STM_CTRL_PIXEL_PRECISION; + desc->line_pitch = q_data_cap->bytesperline[0]; + mxc_jpeg_addrs(desc, dst_buf, src_buf, 0); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(src_buf, 0), 1024)); + print_descriptor_info(jpeg->dev, desc); + + if (!jpeg_src_buf->dht_needed) { + /* validate the decoding descriptor */ + mxc_jpeg_set_desc(desc_handle, reg, slot); + return; + } + + /* + * if a default huffman table is needed, use the config descriptor to + * inject a DHT, by chaining it before the decoding descriptor + */ + *cfg_size = mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + V4L2_PIX_FMT_YUYV, + MXC_JPEG_MIN_WIDTH, + MXC_JPEG_MIN_HEIGHT); + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + cfg_desc->buf_base0 = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + cfg_desc->buf_base1 = 0; + cfg_desc->imgsize = MXC_JPEG_MIN_WIDTH << 16; + cfg_desc->imgsize |= MXC_JPEG_MIN_HEIGHT; + cfg_desc->line_pitch = MXC_JPEG_MIN_WIDTH * 2; + cfg_desc->stm_ctrl = STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV422); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + cfg_desc->stm_bufbase = cfg_stream_handle; + cfg_desc->stm_bufsize = ALIGN(*cfg_size, 1024); + print_descriptor_info(jpeg->dev, cfg_desc); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data.desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data.cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data.desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data.cfg_desc_handle; + void *cfg_stream_vaddr = jpeg->slot_data.cfg_stream_vaddr; + struct mxc_jpeg_q_data *q_data; + enum mxc_jpeg_image_format img_fmt; + int w, h; + + q_data = mxc_jpeg_get_q_data(ctx, src_buf->vb2_queue->type); + + jpeg->slot_data.cfg_stream_size = + mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + q_data->fmt->fourcc, + q_data->crop.width, + q_data->crop.height); + + /* chain the config descriptor with the encoding descriptor */ + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + + cfg_desc->buf_base0 = jpeg->slot_data.cfg_stream_handle; + cfg_desc->buf_base1 = 0; + cfg_desc->line_pitch = 0; + cfg_desc->stm_bufbase = 0; /* no output expected */ + cfg_desc->stm_bufsize = 0x0; + cfg_desc->imgsize = 0; + cfg_desc->stm_ctrl = STM_CTRL_CONFIG_MOD(1); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + + desc->next_descpt_ptr = 0; /* end of chain */ + + /* use adjusted resolution for CAST IP job */ + w = q_data->crop.width; + h = q_data->crop.height; + v4l_bound_align_image(&w, w, MXC_JPEG_MAX_WIDTH, q_data->fmt->h_align, + &h, h, MXC_JPEG_MAX_HEIGHT, q_data->fmt->v_align, 0); + mxc_jpeg_set_res(desc, w, h); + mxc_jpeg_set_line_pitch(desc, q_data->bytesperline[0]); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(dst_buf, 0), 1024)); + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data->fmt->fourcc); + if (img_fmt == MXC_JPEG_INVALID) + dev_err(jpeg->dev, "No valid image format detected\n"); + desc->stm_ctrl = STM_CTRL_CONFIG_MOD(0) | + STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + if (mxc_jpeg_is_extended_sequential(q_data->fmt)) + desc->stm_ctrl |= STM_CTRL_PIXEL_PRECISION; + else + desc->stm_ctrl &= ~STM_CTRL_PIXEL_PRECISION; + mxc_jpeg_addrs(desc, src_buf, dst_buf, 0); + dev_dbg(jpeg->dev, "cfg_desc:\n"); + print_descriptor_info(jpeg->dev, cfg_desc); + dev_dbg(jpeg->dev, "enc desc:\n"); + print_descriptor_info(jpeg->dev, desc); + print_wrapper_info(jpeg->dev, reg); + print_cast_status(jpeg->dev, reg, MXC_JPEG_ENCODE); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static const struct mxc_jpeg_fmt *mxc_jpeg_get_sibling_format(const struct mxc_jpeg_fmt *fmt) +{ + int i; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_formats[i].subsampling == fmt->subsampling && + mxc_formats[i].nc == fmt->nc && + mxc_formats[i].precision == fmt->precision && + mxc_formats[i].is_rgb == fmt->is_rgb && + mxc_formats[i].fourcc != fmt->fourcc) + return &mxc_formats[i]; + } + + return NULL; +} + +static bool mxc_jpeg_compare_format(const struct mxc_jpeg_fmt *fmt1, + const struct mxc_jpeg_fmt *fmt2) +{ + if (fmt1 == fmt2) + return true; + if (mxc_jpeg_get_sibling_format(fmt1) == fmt2) + return true; + return false; +} + +static void mxc_jpeg_set_last_buffer(struct mxc_jpeg_ctx *ctx) +{ + struct vb2_v4l2_buffer *next_dst_buf; + + next_dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!next_dst_buf) { + ctx->fh.m2m_ctx->is_draining = true; + ctx->fh.m2m_ctx->next_buf_last = true; + return; + } + + v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, next_dst_buf); +} + +static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, + struct mxc_jpeg_src_buf *jpeg_src_buf) +{ + struct device *dev = ctx->mxc_jpeg->dev; + struct mxc_jpeg_q_data *q_data_cap; + + if (!jpeg_src_buf->fmt) + return false; + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (mxc_jpeg_compare_format(q_data_cap->fmt, jpeg_src_buf->fmt)) + jpeg_src_buf->fmt = q_data_cap->fmt; + if (ctx->need_initial_source_change_evt || + q_data_cap->fmt != jpeg_src_buf->fmt || + q_data_cap->w != jpeg_src_buf->w || + q_data_cap->h != jpeg_src_buf->h) { + dev_dbg(dev, "Detected jpeg res=(%dx%d)->(%dx%d), pixfmt=%c%c%c%c\n", + q_data_cap->w, q_data_cap->h, + jpeg_src_buf->w, jpeg_src_buf->h, + (jpeg_src_buf->fmt->fourcc & 0xff), + (jpeg_src_buf->fmt->fourcc >> 8) & 0xff, + (jpeg_src_buf->fmt->fourcc >> 16) & 0xff, + (jpeg_src_buf->fmt->fourcc >> 24) & 0xff); + + /* + * set-up the capture queue with the pixelformat and resolution + * detected from the jpeg output stream + */ + q_data_cap->w = jpeg_src_buf->w; + q_data_cap->h = jpeg_src_buf->h; + q_data_cap->fmt = jpeg_src_buf->fmt; + q_data_cap->w_adjusted = q_data_cap->w; + q_data_cap->h_adjusted = q_data_cap->h; + q_data_cap->crop.left = 0; + q_data_cap->crop.top = 0; + q_data_cap->crop.width = jpeg_src_buf->w; + q_data_cap->crop.height = jpeg_src_buf->h; + + /* + * align up the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data_cap->w_adjusted, + q_data_cap->w_adjusted, /* adjust up */ + MXC_JPEG_MAX_WIDTH, + q_data_cap->fmt->h_align, + &q_data_cap->h_adjusted, + q_data_cap->h_adjusted, /* adjust up */ + MXC_JPEG_MAX_HEIGHT, + q_data_cap->fmt->v_align, + 0); + + /* setup bytesperline/sizeimage for capture queue */ + mxc_jpeg_bytesperline(q_data_cap, jpeg_src_buf->fmt->precision); + mxc_jpeg_sizeimage(q_data_cap); + notify_src_chg(ctx); + ctx->source_change = 1; + ctx->need_initial_source_change_evt = false; + if (vb2_is_streaming(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx))) + mxc_jpeg_set_last_buffer(ctx); + } + + return ctx->source_change ? true : false; +} + +static int mxc_jpeg_job_ready(void *priv) +{ + struct mxc_jpeg_ctx *ctx = priv; + + return ctx->source_change ? 0 : 1; +} + +static void mxc_jpeg_device_run_timeout(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct mxc_jpeg_ctx *ctx = container_of(dwork, struct mxc_jpeg_ctx, task_timer); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + unsigned long flags; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + if (ctx->mxc_jpeg->slot_data.used) { + dev_warn(jpeg->dev, "%s timeout, cancel it\n", + ctx->mxc_jpeg->mode == MXC_JPEG_DECODE ? "decode" : "encode"); + mxc_jpeg_job_finish(ctx, VB2_BUF_STATE_ERROR, true); + v4l2_m2m_job_finish(ctx->mxc_jpeg->m2m_dev, ctx->fh.m2m_ctx); + } + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + +static void mxc_jpeg_device_run(void *priv) +{ + struct mxc_jpeg_ctx *ctx = priv; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long flags; + struct mxc_jpeg_q_data *q_data_cap, *q_data_out; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!src_buf || !dst_buf) { + dev_err(dev, "Null src or dst buf\n"); + goto end; + } + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (!q_data_cap) + goto end; + q_data_out = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (!q_data_out) + goto end; + src_buf->sequence = q_data_out->sequence++; + dst_buf->sequence = q_data_cap->sequence++; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + if (q_data_cap->fmt->mem_planes != dst_buf->vb2_buf.num_planes) { + dev_err(dev, "Capture format %s has %d planes, but capture buffer has %d planes\n", + q_data_cap->fmt->name, q_data_cap->fmt->mem_planes, + dst_buf->vb2_buf.num_planes); + jpeg_src_buf->jpeg_parse_error = true; + } + if (jpeg_src_buf->jpeg_parse_error) { + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + + return; + } + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) { + if (ctx->source_change || mxc_jpeg_source_change(ctx, jpeg_src_buf)) { + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + } + + mxc_jpeg_enable(reg); + mxc_jpeg_set_l_endian(reg, 1); + + ctx->slot = mxc_get_free_slot(&jpeg->slot_data); + if (ctx->slot < 0) { + dev_err(dev, "No more free slots\n"); + goto end; + } + if (!mxc_jpeg_alloc_slot_data(jpeg)) { + dev_err(dev, "Cannot allocate slot data\n"); + goto end; + } + + mxc_jpeg_enable_slot(reg, ctx->slot); + mxc_jpeg_enable_irq(reg, ctx->slot); + + if (jpeg->mode == MXC_JPEG_ENCODE) { + dev_dbg(dev, "Encoding on slot %d\n", ctx->slot); + ctx->enc_state = MXC_JPEG_ENC_CONF; + mxc_jpeg_config_enc_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + /* start config phase */ + mxc_jpeg_enc_mode_conf(dev, reg, + mxc_jpeg_is_extended_sequential(q_data_out->fmt)); + } else { + dev_dbg(dev, "Decoding on slot %d\n", ctx->slot); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 0); + mxc_jpeg_config_dec_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + mxc_jpeg_dec_mode_go(dev, reg); + } + schedule_delayed_work(&ctx->task_timer, msecs_to_jiffies(hw_timeout)); +end: + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + +static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx))) + return 0; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + ret = v4l2_m2m_ioctl_decoder_cmd(file, priv, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + if (ret < 0) + return ret; + + if (cmd->cmd == V4L2_DEC_CMD_STOP && + v4l2_m2m_has_stopped(fh->m2m_ctx)) { + notify_eos(ctx); + ctx->header_parsed = false; + } + + if (cmd->cmd == V4L2_DEC_CMD_START && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + vb2_clear_last_buffer_dequeued(&fh->m2m_ctx->cap_q_ctx.q); + return 0; +} + +static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + unsigned long flags; + int ret; + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx)) || + !vb2_is_streaming(v4l2_m2m_get_dst_vq(fh->m2m_ctx))) + return 0; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + ret = v4l2_m2m_ioctl_encoder_cmd(file, fh, cmd); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + if (ret < 0) + return 0; + + if (cmd->cmd == V4L2_ENC_CMD_STOP && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + notify_eos(ctx); + + if (cmd->cmd == V4L2_ENC_CMD_START && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + vb2_clear_last_buffer_dequeued(&fh->m2m_ctx->cap_q_ctx.q); + + return 0; +} + +static int mxc_jpeg_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = NULL; + int i; + + q_data = mxc_jpeg_get_q_data(ctx, q->type); + if (!q_data) + return -EINVAL; + + /* Handle CREATE_BUFS situation - *nplanes != 0 */ + if (*nplanes) { + if (*nplanes != q_data->fmt->mem_planes) + return -EINVAL; + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < mxc_jpeg_get_plane_size(q_data, i)) + return -EINVAL; + } + return 0; + } + + /* Handle REQBUFS situation */ + *nplanes = q_data->fmt->mem_planes; + for (i = 0; i < *nplanes; i++) + sizes[i] = mxc_jpeg_get_plane_size(q_data, i); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + ctx->need_initial_source_change_evt = true; + + return 0; +} + +static int mxc_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, q->type); + int ret; + + v4l2_m2m_update_start_streaming_state(ctx->fh.m2m_ctx, q); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE && V4L2_TYPE_IS_CAPTURE(q->type)) + ctx->source_change = 0; + dev_dbg(ctx->mxc_jpeg->dev, "Start streaming ctx=%p", ctx); + q_data->sequence = 0; + + ret = pm_runtime_resume_and_get(ctx->mxc_jpeg->dev); + if (ret < 0) { + dev_err(ctx->mxc_jpeg->dev, "Failed to power up jpeg\n"); + return ret; + } + + return 0; +} + +static void mxc_jpeg_stop_streaming(struct vb2_queue *q) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + dev_dbg(ctx->mxc_jpeg->dev, "Stop streaming ctx=%p", ctx); + + /* Release all active buffers */ + 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, VB2_BUF_STATE_ERROR); + } + + v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); + /* if V4L2_DEC_CMD_STOP is sent before the source change triggered, + * restore the is_draining flag + */ + if (V4L2_TYPE_IS_CAPTURE(q->type) && ctx->source_change && ctx->fh.m2m_ctx->last_src_buf) + ctx->fh.m2m_ctx->is_draining = true; + + if (V4L2_TYPE_IS_OUTPUT(q->type) && + v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) { + notify_eos(ctx); + ctx->header_parsed = false; + } + + pm_runtime_put_sync(&ctx->mxc_jpeg->pdev->dev); +} + +static int mxc_jpeg_valid_comp_id(struct device *dev, + struct mxc_jpeg_sof *sof, + struct mxc_jpeg_sos *sos) +{ + int valid = 1; + int i; + + /* + * there's a limitation in the IP that the component IDs must be + * between 0..4, if they are not, let's patch them + */ + for (i = 0; i < sof->components_no; i++) + if (sof->comp[i].id > MXC_JPEG_MAX_COMPONENTS) { + valid = 0; + dev_err(dev, "Component %d has invalid ID: %d", + i, sof->comp[i].id); + } + if (!valid) + /* patch all comp IDs if at least one is invalid */ + for (i = 0; i < sof->components_no; i++) { + dev_warn(dev, "Component %d ID patched to: %d", + i, i + 1); + sof->comp[i].id = i + 1; + sos->comp[i].id = i + 1; + } + + return valid; +} + +static bool mxc_jpeg_match_image_format(const struct mxc_jpeg_fmt *fmt, + const struct v4l2_jpeg_header *header) +{ + if (fmt->subsampling != header->frame.subsampling || + fmt->nc != header->frame.num_components || + fmt->precision != header->frame.precision) + return false; + + /* + * If the transform flag from APP14 marker is 0, images that are + * encoded with 3 components have RGB colorspace, see Recommendation + * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding + */ + if (header->frame.subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { + u8 is_rgb = header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB ? 1 : 0; + + if (is_rgb != fmt->is_rgb) + return false; + } + return true; +} + +static u32 mxc_jpeg_get_image_format(struct device *dev, + const struct v4l2_jpeg_header *header) +{ + int i; + u32 fourcc = 0; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) { + if (mxc_jpeg_match_image_format(&mxc_formats[i], header)) { + fourcc = mxc_formats[i].fourcc; + break; + } + } + if (fourcc == 0) { + dev_err(dev, + "Could not identify image format nc=%d, subsampling=%d, precision=%d\n", + header->frame.num_components, + header->frame.subsampling, + header->frame.precision); + return fourcc; + } + + return fourcc; +} + +static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision) +{ + /* Bytes distance between the leftmost pixels in two adjacent lines */ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* bytesperline unused for compressed formats */ + q->bytesperline[0] = 0; + q->bytesperline[1] = 0; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { + /* When the image format is planar the bytesperline value + * applies to the first plane and is divided by the same factor + * as the width field for the other planes + */ + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); + q->bytesperline[1] = q->bytesperline[0]; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) { + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * 2; + q->bytesperline[1] = 0; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8) * q->fmt->nc; + q->bytesperline[1] = 0; + } else { + /* grayscale */ + q->bytesperline[0] = q->w_adjusted * DIV_ROUND_UP(precision, 8); + q->bytesperline[1] = 0; + } +} + +static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q) +{ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* if no sizeimage from user, assume worst jpeg compression */ + if (!q->sizeimage[0]) + q->sizeimage[0] = 6 * q->w * q->h; + q->sizeimage[1] = 0; + + if (q->sizeimage[0] > MXC_JPEG_MAX_SIZEIMAGE) + q->sizeimage[0] = MXC_JPEG_MAX_SIZEIMAGE; + + /* jpeg stream size must be multiple of 1K */ + q->sizeimage[0] = ALIGN(q->sizeimage[0], 1024); + } else { + q->sizeimage[0] = q->bytesperline[0] * q->h_adjusted; + q->sizeimage[1] = 0; + if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420) + q->sizeimage[1] = q->sizeimage[0] / 2; + } +} + +static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) +{ + struct device *dev = ctx->mxc_jpeg->dev; + struct mxc_jpeg_q_data *q_data_out; + struct mxc_jpeg_q_data *q_data_cap; + u32 fourcc; + struct v4l2_jpeg_header header; + struct mxc_jpeg_sof *psof = NULL; + struct mxc_jpeg_sos *psos = NULL; + struct mxc_jpeg_src_buf *jpeg_src_buf = vb2_to_mxc_buf(vb); + u8 *src_addr = (u8 *)vb2_plane_vaddr(vb, 0); + u32 size = vb2_get_plane_payload(vb, 0); + int ret; + + memset(&header, 0, sizeof(header)); + ret = v4l2_jpeg_parse_header((void *)src_addr, size, &header); + if (ret < 0) { + dev_err(dev, "Error parsing JPEG stream markers\n"); + return ret; + } + + /* if DHT marker present, no need to inject default one */ + jpeg_src_buf->dht_needed = (header.num_dht == 0); + + q_data_out = mxc_jpeg_get_q_data(ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (q_data_out->w == 0 && q_data_out->h == 0) { + dev_warn(dev, "Invalid user resolution 0x0"); + dev_warn(dev, "Keeping resolution from JPEG: %dx%d", + header.frame.width, header.frame.height); + } else if (header.frame.width != q_data_out->w || + header.frame.height != q_data_out->h) { + dev_err(dev, + "Resolution mismatch: %dx%d (JPEG) versus %dx%d(user)", + header.frame.width, header.frame.height, + q_data_out->w, q_data_out->h); + } + q_data_out->w = header.frame.width; + q_data_out->h = header.frame.height; + if (header.frame.width > MXC_JPEG_MAX_WIDTH || + header.frame.height > MXC_JPEG_MAX_HEIGHT) { + dev_err(dev, "JPEG width or height should be <= 8192: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.width < MXC_JPEG_MIN_WIDTH || + header.frame.height < MXC_JPEG_MIN_HEIGHT) { + dev_err(dev, "JPEG width or height should be > 64: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.num_components > V4L2_JPEG_MAX_COMPONENTS) { + dev_err(dev, "JPEG number of components should be <=%d", + V4L2_JPEG_MAX_COMPONENTS); + return -EINVAL; + } + /* check and, if necessary, patch component IDs*/ + psof = (struct mxc_jpeg_sof *)header.sof.start; + psos = (struct mxc_jpeg_sos *)header.sos.start; + if (!mxc_jpeg_valid_comp_id(dev, psof, psos)) + dev_warn(dev, "JPEG component ids should be 0-3 or 1-4"); + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (q_data_cap->fmt && mxc_jpeg_match_image_format(q_data_cap->fmt, &header)) + fourcc = q_data_cap->fmt->fourcc; + else + fourcc = mxc_jpeg_get_image_format(dev, &header); + if (fourcc == 0) + return -EINVAL; + + jpeg_src_buf->fmt = mxc_jpeg_find_format(fourcc); + jpeg_src_buf->w = header.frame.width; + jpeg_src_buf->h = header.frame.height; + ctx->header_parsed = true; + + if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx)) + mxc_jpeg_source_change(ctx, jpeg_src_buf); + + return 0; +} + +static void mxc_jpeg_buf_queue(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_src_buf *jpeg_src_buf; + + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && + vb2_is_streaming(vb->vb2_queue) && + v4l2_m2m_dst_buf_is_last(ctx->fh.m2m_ctx)) { + struct mxc_jpeg_q_data *q_data; + + q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); + vbuf->field = V4L2_FIELD_NONE; + vbuf->sequence = q_data->sequence++; + v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vbuf); + notify_eos(ctx); + ctx->header_parsed = false; + return; + } + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + goto end; + + /* for V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE */ + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + goto end; + + jpeg_src_buf = vb2_to_mxc_buf(vb); + jpeg_src_buf->jpeg_parse_error = false; + ret = mxc_jpeg_parse(ctx, vb); + if (ret) + jpeg_src_buf->jpeg_parse_error = true; + +end: + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int mxc_jpeg_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_q_data *q_data = NULL; + struct device *dev = ctx->mxc_jpeg->dev; + unsigned long sizeimage; + int i; + + vbuf->field = V4L2_FIELD_NONE; + + q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); + if (!q_data) + return -EINVAL; + for (i = 0; i < q_data->fmt->mem_planes; i++) { + sizeimage = mxc_jpeg_get_plane_size(q_data, i); + if (!ctx->source_change && vb2_plane_size(vb, i) < sizeimage) { + dev_err(dev, "plane %d too small (%lu < %lu)", + i, vb2_plane_size(vb, i), sizeimage); + return -EINVAL; + } + } + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) { + vb2_set_plane_payload(vb, 0, 0); + vb2_set_plane_payload(vb, 1, 0); + } + return 0; +} + +static const struct vb2_ops mxc_jpeg_qops = { + .queue_setup = mxc_jpeg_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_out_validate = mxc_jpeg_buf_out_validate, + .buf_prepare = mxc_jpeg_buf_prepare, + .start_streaming = mxc_jpeg_start_streaming, + .stop_streaming = mxc_jpeg_stop_streaming, + .buf_queue = mxc_jpeg_buf_queue, +}; + +static int mxc_jpeg_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mxc_jpeg_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->buf_struct_size = sizeof(struct mxc_jpeg_src_buf); + src_vq->ops = &mxc_jpeg_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->mxc_jpeg->lock; + src_vq->dev = ctx->mxc_jpeg->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->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &mxc_jpeg_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->mxc_jpeg->lock; + dst_vq->dev = ctx->mxc_jpeg->dev; + + ret = vb2_queue_init(dst_vq); + return ret; +} + +static void mxc_jpeg_set_default_params(struct mxc_jpeg_ctx *ctx) +{ + struct mxc_jpeg_q_data *out_q = &ctx->out_q; + struct mxc_jpeg_q_data *cap_q = &ctx->cap_q; + struct mxc_jpeg_q_data *q[2] = {out_q, cap_q}; + int i; + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) { + out_q->fmt = mxc_jpeg_find_format(MXC_JPEG_DEFAULT_PFMT); + cap_q->fmt = mxc_jpeg_find_format(V4L2_PIX_FMT_JPEG); + } else { + out_q->fmt = mxc_jpeg_find_format(V4L2_PIX_FMT_JPEG); + cap_q->fmt = mxc_jpeg_find_format(MXC_JPEG_DEFAULT_PFMT); + } + + for (i = 0; i < 2; i++) { + q[i]->w = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->w_adjusted = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h_adjusted = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->crop.left = 0; + q[i]->crop.top = 0; + q[i]->crop.width = MXC_JPEG_DEFAULT_WIDTH; + q[i]->crop.height = MXC_JPEG_DEFAULT_HEIGHT; + mxc_jpeg_bytesperline(q[i], q[i]->fmt->precision); + mxc_jpeg_sizeimage(q[i]); + } +} + +static int mxc_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_jpeg_ctx *ctx = + container_of(ctrl->handler, struct mxc_jpeg_ctx, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ctx->jpeg_quality = ctrl->val; + break; + default: + dev_err(ctx->mxc_jpeg->dev, "Invalid control, id = %d, val = %d\n", + ctrl->id, ctrl->val); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mxc_jpeg_ctrl_ops = { + .s_ctrl = mxc_jpeg_s_ctrl, +}; + +static void mxc_jpeg_encode_ctrls(struct mxc_jpeg_ctx *ctx) +{ + v4l2_ctrl_new_std(&ctx->ctrl_handler, &mxc_jpeg_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 75); +} + +static int mxc_jpeg_ctrls_setup(struct mxc_jpeg_ctx *ctx) +{ + int err; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 2); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) + mxc_jpeg_encode_ctrls(ctx); + + if (ctx->ctrl_handler.error) { + err = ctx->ctrl_handler.error; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + err = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (err) + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; +} + +static int mxc_jpeg_open(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct video_device *mxc_vfd = video_devdata(file); + struct device *dev = mxc_jpeg->dev; + struct mxc_jpeg_ctx *ctx; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&mxc_jpeg->lock)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, mxc_vfd); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->mxc_jpeg = mxc_jpeg; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(mxc_jpeg->m2m_dev, ctx, + mxc_jpeg_queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error; + } + + ret = mxc_jpeg_ctrls_setup(ctx); + if (ret) { + dev_err(ctx->mxc_jpeg->dev, "failed to setup mxc jpeg controls\n"); + goto err_ctrls_setup; + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mxc_jpeg_set_default_params(ctx); + ctx->slot = -1; /* slot not allocated yet */ + INIT_DELAYED_WORK(&ctx->task_timer, mxc_jpeg_device_run_timeout); + + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Opened JPEG decoder instance %p\n", ctx); + else + dev_dbg(dev, "Opened JPEG encoder instance %p\n", ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; + +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&mxc_jpeg->lock); +free: + kfree(ctx); + return ret; +} + +static int mxc_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MXC_JPEG_NAME " codec", sizeof(cap->driver)); + strscpy(cap->card, MXC_JPEG_NAME " codec", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, f->type); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) { + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_ENC); + } else if (!ctx->header_parsed) { + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_RAW); + } else { + /* For the decoder CAPTURE queue, only enumerate the raw formats + * supported for the format currently active on OUTPUT + * (more precisely what was propagated on capture queue + * after jpeg parse on the output buffer) + */ + int ret = -EINVAL; + const struct mxc_jpeg_fmt *sibling; + + switch (f->index) { + case 0: + f->pixelformat = q_data->fmt->fourcc; + ret = 0; + break; + case 1: + sibling = mxc_jpeg_get_sibling_format(q_data->fmt); + if (sibling) { + f->pixelformat = sibling->fourcc; + ret = 0; + } + break; + default: + break; + } + return ret; + } +} + +static int mxc_jpeg_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + u32 type = ctx->mxc_jpeg->mode == MXC_JPEG_DECODE ? MXC_JPEG_FMT_TYPE_ENC : + MXC_JPEG_FMT_TYPE_RAW; + int ret; + + ret = enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, type); + if (ret) + return ret; + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + f->flags = V4L2_FMT_FLAG_DYN_RESOLUTION; + return 0; +} + +static u32 mxc_jpeg_get_fmt_type(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; + else + return V4L2_TYPE_IS_CAPTURE(type) ? MXC_JPEG_FMT_TYPE_ENC : MXC_JPEG_FMT_TYPE_RAW; +} + +static u32 mxc_jpeg_get_default_fourcc(struct mxc_jpeg_ctx *ctx, u32 type) +{ + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return V4L2_TYPE_IS_OUTPUT(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; + else + return V4L2_TYPE_IS_CAPTURE(type) ? V4L2_PIX_FMT_JPEG : MXC_JPEG_DEFAULT_PFMT; +} + +static u32 mxc_jpeg_try_fourcc(struct mxc_jpeg_ctx *ctx, u32 fourcc) +{ + const struct mxc_jpeg_fmt *sibling; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + return fourcc; + if (!ctx->header_parsed) + return fourcc; + + q_data_cap = &ctx->cap_q; + if (q_data_cap->fmt->fourcc == fourcc) + return fourcc; + + sibling = mxc_jpeg_get_sibling_format(q_data_cap->fmt); + if (sibling && sibling->fourcc == fourcc) + return sibling->fourcc; + + return q_data_cap->fmt->fourcc; +} + +static int mxc_jpeg_try_fmt(struct v4l2_format *f, + struct mxc_jpeg_ctx *ctx, struct mxc_jpeg_q_data *q_data) +{ + const struct mxc_jpeg_fmt *fmt; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; + u32 w = (pix_mp->width < MXC_JPEG_MAX_WIDTH) ? + pix_mp->width : MXC_JPEG_MAX_WIDTH; + u32 h = (pix_mp->height < MXC_JPEG_MAX_HEIGHT) ? + pix_mp->height : MXC_JPEG_MAX_HEIGHT; + int i; + + fmt = mxc_jpeg_find_format(fourcc); + if (!fmt || fmt->flags != mxc_jpeg_get_fmt_type(ctx, f->type)) { + dev_warn(ctx->mxc_jpeg->dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + fourcc = mxc_jpeg_get_default_fourcc(ctx, f->type); + fmt = mxc_jpeg_find_format(fourcc); + if (!fmt) + return -EINVAL; + f->fmt.pix_mp.pixelformat = fourcc; + } + q_data->fmt = fmt; + + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = fmt->mem_planes; + pix_mp->pixelformat = fmt->fourcc; + + q_data->w = w; + q_data->h = h; + q_data->w_adjusted = w; + q_data->h_adjusted = h; + v4l_bound_align_image(&q_data->w_adjusted, + w, /* adjust upwards*/ + MXC_JPEG_MAX_WIDTH, + fmt->h_align, + &q_data->h_adjusted, + h, /* adjust upwards*/ + MXC_JPEG_MAX_HEIGHT, + fmt->v_align, + 0); + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + q_data->bytesperline[i] = pfmt->bytesperline; + q_data->sizeimage[i] = pfmt->sizeimage; + } + + /* calculate bytesperline & sizeimage */ + mxc_jpeg_bytesperline(q_data, fmt->precision); + mxc_jpeg_sizeimage(q_data); + + /* adjust user format according to our calculations */ + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + memset(pfmt->reserved, 0, sizeof(pfmt->reserved)); + pfmt->bytesperline = q_data->bytesperline[i]; + pfmt->sizeimage = mxc_jpeg_get_plane_size(q_data, i); + } + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + /* + * this hardware does not change the range of the samples + * but since inside JPEG the YUV quantization is full-range, + * this driver will always use full-range for the raw frames, too + */ + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + if (fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + q_data->crop.left = 0; + q_data->crop.top = 0; + q_data->crop.width = q_data->w; + q_data->crop.height = q_data->h; + } + + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + + return 0; +} + +static int mxc_jpeg_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + struct mxc_jpeg_q_data tmp_q; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE && V4L2_TYPE_IS_CAPTURE(f->type)) + f->fmt.pix_mp.pixelformat = mxc_jpeg_try_fourcc(ctx, f->fmt.pix_mp.pixelformat); + + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); +} + +static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + struct mxc_jpeg_q_data tmp_q; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + return mxc_jpeg_try_fmt(f, ctx, &tmp_q); +} + +static void mxc_jpeg_s_parsed_fmt(struct mxc_jpeg_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data_cap; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE || !V4L2_TYPE_IS_CAPTURE(f->type)) + return; + if (!ctx->header_parsed) + return; + + q_data_cap = mxc_jpeg_get_q_data(ctx, f->type); + pix_mp->pixelformat = mxc_jpeg_try_fourcc(ctx, pix_mp->pixelformat); + pix_mp->width = q_data_cap->w; + pix_mp->height = q_data_cap->h; +} + +static int mxc_jpeg_s_fmt(struct mxc_jpeg_ctx *ctx, + struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); + return -EBUSY; + } + + mxc_jpeg_s_parsed_fmt(ctx, f); + + return mxc_jpeg_try_fmt(f, ctx, mxc_jpeg_get_q_data(ctx, f->type)); +} + +static int mxc_jpeg_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); +} + +static int mxc_jpeg_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct vb2_queue *dst_vq; + struct mxc_jpeg_q_data *q_data_cap; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + struct v4l2_format fc; + + ret = mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); + if (ret) + return ret; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + return 0; + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, cap_type); + if (!dst_vq) + return -EINVAL; + + if (vb2_is_busy(dst_vq)) + return 0; + + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + if (q_data_cap->w == f->fmt.pix_mp.width && q_data_cap->h == f->fmt.pix_mp.height) + return 0; + memset(&fc, 0, sizeof(fc)); + fc.type = cap_type; + fc.fmt.pix_mp.pixelformat = q_data_cap->fmt->fourcc; + fc.fmt.pix_mp.width = f->fmt.pix_mp.width; + fc.fmt.pix_mp.height = f->fmt.pix_mp.height; + + return mxc_jpeg_s_fmt_vid_cap(file, priv, &fc); +} + +static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, f->type); + int i; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "G_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->width = q_data->w; + pix_mp->height = q_data->h; + pix_mp->field = V4L2_FIELD_NONE; + if (q_data->fmt->flags == MXC_JPEG_FMT_TYPE_RAW) { + pix_mp->width = q_data->w_adjusted; + pix_mp->height = q_data->h_adjusted; + } + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + pix_mp->num_planes = q_data->fmt->mem_planes; + for (i = 0; i < pix_mp->num_planes; i++) { + pix_mp->plane_fmt[i].bytesperline = q_data->bytesperline[i]; + pix_mp->plane_fmt[i].sizeimage = mxc_jpeg_get_plane_size(q_data, i); + } + + return 0; +} + +static int mxc_jpeg_dec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_cap; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + q_data_cap = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r = q_data_cap->crop; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_cap->w_adjusted; + s->r.height = q_data_cap->h_adjusted; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxc_jpeg_enc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data_out->w; + s->r.height = q_data_out->h; + break; + case V4L2_SEL_TGT_CROP: + s->r = q_data_out->crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxc_jpeg_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return mxc_jpeg_dec_g_selection(file, fh, s); + else + return mxc_jpeg_enc_g_selection(file, fh, s); +} + +static int mxc_jpeg_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct mxc_jpeg_q_data *q_data_out; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_ENCODE) + return -ENOTTY; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + q_data_out = mxc_jpeg_get_q_data(ctx, s->type); + if (s->r.left || s->r.top) + return -EINVAL; + if (s->r.width > q_data_out->w || s->r.height > q_data_out->h) + return -EINVAL; + + q_data_out->crop.left = 0; + q_data_out->crop.top = 0; + q_data_out->crop.width = s->r.width; + q_data_out->crop.height = s->r.height; + + return 0; +} + +static int mxc_jpeg_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops mxc_jpeg_ioctl_ops = { + .vidioc_querycap = mxc_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = mxc_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mxc_jpeg_enum_fmt_vid_out, + + .vidioc_try_fmt_vid_cap_mplane = mxc_jpeg_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out_mplane = mxc_jpeg_try_fmt_vid_out, + + .vidioc_s_fmt_vid_cap_mplane = mxc_jpeg_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out_mplane = mxc_jpeg_s_fmt_vid_out, + + .vidioc_g_fmt_vid_cap_mplane = mxc_jpeg_g_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = mxc_jpeg_g_fmt_vid, + + .vidioc_g_selection = mxc_jpeg_g_selection, + .vidioc_s_selection = mxc_jpeg_s_selection, + + .vidioc_subscribe_event = mxc_jpeg_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = mxc_jpeg_decoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = mxc_jpeg_encoder_cmd, + + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static int mxc_jpeg_release(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(file->private_data); + struct device *dev = mxc_jpeg->dev; + + mutex_lock(&mxc_jpeg->lock); + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Release JPEG decoder instance on slot %d.", + ctx->slot); + else + dev_dbg(dev, "Release JPEG encoder instance on slot %d.", + ctx->slot); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; +} + +static const struct v4l2_file_operations mxc_jpeg_fops = { + .owner = THIS_MODULE, + .open = mxc_jpeg_open, + .release = mxc_jpeg_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_m2m_ops mxc_jpeg_m2m_ops = { + .job_ready = mxc_jpeg_job_ready, + .device_run = mxc_jpeg_device_run, +}; + +static void mxc_jpeg_detach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + int i; + + for (i = 0; i < jpeg->num_domains; i++) { + if (jpeg->pd_link[i] && !IS_ERR(jpeg->pd_link[i])) + device_link_del(jpeg->pd_link[i]); + if (jpeg->pd_dev[i] && !IS_ERR(jpeg->pd_dev[i])) + dev_pm_domain_detach(jpeg->pd_dev[i], true); + jpeg->pd_dev[i] = NULL; + jpeg->pd_link[i] = NULL; + } +} + +static int mxc_jpeg_attach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + struct device *dev = jpeg->dev; + struct device_node *np = jpeg->pdev->dev.of_node; + int i; + int ret; + + jpeg->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + if (jpeg->num_domains < 0) { + dev_err(dev, "No power domains defined for jpeg node\n"); + return jpeg->num_domains; + } + if (jpeg->num_domains == 1) { + /* genpd_dev_pm_attach() attach automatically if power domains count is 1 */ + jpeg->num_domains = 0; + return 0; + } + + jpeg->pd_dev = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_dev), GFP_KERNEL); + if (!jpeg->pd_dev) + return -ENOMEM; + + jpeg->pd_link = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_link), GFP_KERNEL); + if (!jpeg->pd_link) + return -ENOMEM; + + for (i = 0; i < jpeg->num_domains; i++) { + jpeg->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(jpeg->pd_dev[i])) { + ret = PTR_ERR(jpeg->pd_dev[i]); + goto fail; + } + + jpeg->pd_link[i] = device_link_add(dev, jpeg->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME); + if (!jpeg->pd_link[i]) { + ret = -EINVAL; + goto fail; + } + } + + return 0; +fail: + mxc_jpeg_detach_pm_domains(jpeg); + return ret; +} + +static int mxc_jpeg_probe(struct platform_device *pdev) +{ + struct mxc_jpeg_dev *jpeg; + struct device *dev = &pdev->dev; + int dec_irq; + int ret; + int mode; + const struct of_device_id *of_id; + + of_id = of_match_node(mxc_jpeg_match, dev->of_node); + if (!of_id) + return -ENODEV; + mode = *(const int *)of_id->data; + + jpeg = devm_kzalloc(dev, sizeof(struct mxc_jpeg_dev), GFP_KERNEL); + if (!jpeg) + return -ENOMEM; + + mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->hw_lock); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available.\n"); + goto err_irq; + } + + jpeg->base_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->base_reg)) + return PTR_ERR(jpeg->base_reg); + + ret = of_property_read_u32_index(pdev->dev.of_node, "slot", 0, &jpeg->slot_data.slot); + if (ret) + jpeg->slot_data.slot = 0; + dev_info(&pdev->dev, "choose slot %d\n", jpeg->slot_data.slot); + dec_irq = platform_get_irq(pdev, 0); + if (dec_irq < 0) { + ret = dec_irq; + goto err_irq; + } + ret = devm_request_irq(&pdev->dev, dec_irq, mxc_jpeg_dec_irq, + 0, pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d (%d)\n", + dec_irq, ret); + goto err_irq; + } + + jpeg->pdev = pdev; + jpeg->dev = dev; + jpeg->mode = mode; + + /* Get clocks */ + ret = devm_clk_bulk_get_all(&pdev->dev, &jpeg->clks); + if (ret < 0) { + dev_err(dev, "failed to get clock\n"); + goto err_clk; + } + jpeg->num_clks = ret; + + ret = mxc_jpeg_attach_pm_domains(jpeg); + if (ret < 0) { + dev_err(dev, "failed to attach power domains %d\n", ret); + return ret; + } + + /* v4l2 */ + ret = v4l2_device_register(dev, &jpeg->v4l2_dev); + if (ret) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_register; + } + jpeg->m2m_dev = v4l2_m2m_init(&mxc_jpeg_m2m_ops); + if (IS_ERR(jpeg->m2m_dev)) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = PTR_ERR(jpeg->m2m_dev); + goto err_m2m; + } + + jpeg->dec_vdev = video_device_alloc(); + if (!jpeg->dec_vdev) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = -ENOMEM; + goto err_vdev_alloc; + } + if (mode == MXC_JPEG_ENCODE) + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-enc", MXC_JPEG_NAME); + else + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-dec", MXC_JPEG_NAME); + + jpeg->dec_vdev->fops = &mxc_jpeg_fops; + jpeg->dec_vdev->ioctl_ops = &mxc_jpeg_ioctl_ops; + jpeg->dec_vdev->minor = -1; + jpeg->dec_vdev->release = video_device_release; + jpeg->dec_vdev->lock = &jpeg->lock; /* lock for ioctl serialization */ + jpeg->dec_vdev->v4l2_dev = &jpeg->v4l2_dev; + jpeg->dec_vdev->vfl_dir = VFL_DIR_M2M; + jpeg->dec_vdev->device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + if (mode == MXC_JPEG_ENCODE) { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_DECODER_CMD); + } else { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_ENCODER_CMD); + } + ret = video_register_device(jpeg->dec_vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "failed to register video device\n"); + goto err_vdev_register; + } + video_set_drvdata(jpeg->dec_vdev, jpeg); + if (mode == MXC_JPEG_ENCODE) + v4l2_info(&jpeg->v4l2_dev, + "encoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + else + v4l2_info(&jpeg->v4l2_dev, + "decoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + + platform_set_drvdata(pdev, jpeg); + pm_runtime_enable(dev); + + return 0; + +err_vdev_register: + video_device_release(jpeg->dec_vdev); + +err_vdev_alloc: + v4l2_m2m_release(jpeg->m2m_dev); + +err_m2m: + v4l2_device_unregister(&jpeg->v4l2_dev); + +err_register: + mxc_jpeg_detach_pm_domains(jpeg); + +err_irq: +err_clk: + return ret; +} + +#ifdef CONFIG_PM +static int mxc_jpeg_runtime_resume(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(jpeg->num_clks, jpeg->clks); + if (ret < 0) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + return 0; +} + +static int mxc_jpeg_runtime_suspend(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(jpeg->num_clks, jpeg->clks); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int mxc_jpeg_suspend(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + + v4l2_m2m_suspend(jpeg->m2m_dev); + return pm_runtime_force_suspend(dev); +} + +static int mxc_jpeg_resume(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + v4l2_m2m_resume(jpeg->m2m_dev); + return ret; +} +#endif + +static const struct dev_pm_ops mxc_jpeg_pm_ops = { + SET_RUNTIME_PM_OPS(mxc_jpeg_runtime_suspend, + mxc_jpeg_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(mxc_jpeg_suspend, mxc_jpeg_resume) +}; + +static void mxc_jpeg_remove(struct platform_device *pdev) +{ + struct mxc_jpeg_dev *jpeg = platform_get_drvdata(pdev); + + mxc_jpeg_free_slot_data(jpeg); + + pm_runtime_disable(&pdev->dev); + video_unregister_device(jpeg->dec_vdev); + v4l2_m2m_release(jpeg->m2m_dev); + v4l2_device_unregister(&jpeg->v4l2_dev); + mxc_jpeg_detach_pm_domains(jpeg); +} + +MODULE_DEVICE_TABLE(of, mxc_jpeg_match); + +static struct platform_driver mxc_jpeg_driver = { + .probe = mxc_jpeg_probe, + .remove_new = mxc_jpeg_remove, + .driver = { + .name = "mxc-jpeg", + .of_match_table = mxc_jpeg_match, + .pm = &mxc_jpeg_pm_ops, + }, +}; +module_platform_driver(mxc_jpeg_driver); + +MODULE_AUTHOR("Zhengyu Shen <zhengyu.shen_1@nxp.com>"); +MODULE_AUTHOR("Mirela Rabulea <mirela.rabulea@nxp.com>"); +MODULE_DESCRIPTION("V4L2 driver for i.MX8 QXP/QM JPEG encoder/decoder"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h new file mode 100644 index 0000000000..dc4afeeff5 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> + +#ifndef _MXC_JPEG_CORE_H +#define _MXC_JPEG_CORE_H + +#define MXC_JPEG_NAME "mxc-jpeg" +#define MXC_JPEG_FMT_TYPE_ENC 0 +#define MXC_JPEG_FMT_TYPE_RAW 1 +#define MXC_JPEG_DEFAULT_WIDTH 1280 +#define MXC_JPEG_DEFAULT_HEIGHT 720 +#define MXC_JPEG_DEFAULT_PFMT V4L2_PIX_FMT_BGR24 +#define MXC_JPEG_MIN_WIDTH 64 +#define MXC_JPEG_MIN_HEIGHT 64 +#define MXC_JPEG_MAX_WIDTH 0x2000 +#define MXC_JPEG_MAX_HEIGHT 0x2000 +#define MXC_JPEG_MAX_CFG_STREAM 0x1000 +#define MXC_JPEG_H_ALIGN 3 +#define MXC_JPEG_W_ALIGN 3 +#define MXC_JPEG_MAX_SIZEIMAGE 0xFFFFFC00 +#define MXC_JPEG_MAX_PLANES 2 + +enum mxc_jpeg_enc_state { + MXC_JPEG_ENCODING = 0, /* jpeg encode phase */ + MXC_JPEG_ENC_CONF = 1, /* jpeg encoder config phase */ +}; + +enum mxc_jpeg_mode { + MXC_JPEG_DECODE = 0, /* jpeg decode mode */ + MXC_JPEG_ENCODE = 1, /* jpeg encode mode */ +}; + +/** + * struct mxc_jpeg_fmt - driver's internal color format data + * @name: format description + * @fourcc: fourcc code, 0 if not applicable + * @subsampling: subsampling of jpeg components + * @nc: number of color components + * @depth: number of bits per pixel + * @mem_planes: number of memory planes (1 for packed formats) + * @comp_planes:number of component planes, which includes the alpha plane (1 to 4). + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @flags: flags describing format applicability + * @precision: jpeg sample precision + * @is_rgb: is an RGB pixel format + */ +struct mxc_jpeg_fmt { + const char *name; + u32 fourcc; + enum v4l2_jpeg_chroma_subsampling subsampling; + int nc; + int depth; + int mem_planes; + int comp_planes; + int h_align; + int v_align; + u32 flags; + u8 precision; + u8 is_rgb; +}; + +struct mxc_jpeg_desc { + u32 next_descpt_ptr; + u32 buf_base0; + u32 buf_base1; + u32 line_pitch; + u32 stm_bufbase; + u32 stm_bufsize; + u32 imgsize; + u32 stm_ctrl; +} __packed; + +struct mxc_jpeg_q_data { + const struct mxc_jpeg_fmt *fmt; + u32 sizeimage[MXC_JPEG_MAX_PLANES]; + u32 bytesperline[MXC_JPEG_MAX_PLANES]; + int w; + int w_adjusted; + int h; + int h_adjusted; + unsigned int sequence; + struct v4l2_rect crop; +}; + +struct mxc_jpeg_ctx { + struct mxc_jpeg_dev *mxc_jpeg; + struct mxc_jpeg_q_data out_q; + struct mxc_jpeg_q_data cap_q; + struct v4l2_fh fh; + enum mxc_jpeg_enc_state enc_state; + int slot; + unsigned int source_change; + bool need_initial_source_change_evt; + bool header_parsed; + struct v4l2_ctrl_handler ctrl_handler; + u8 jpeg_quality; + struct delayed_work task_timer; +}; + +struct mxc_jpeg_slot_data { + int slot; + bool used; + struct mxc_jpeg_desc *desc; // enc/dec descriptor + struct mxc_jpeg_desc *cfg_desc; // configuration descriptor + void *cfg_stream_vaddr; // configuration bitstream virtual address + unsigned int cfg_stream_size; + dma_addr_t desc_handle; + dma_addr_t cfg_desc_handle; // configuration descriptor dma address + dma_addr_t cfg_stream_handle; // configuration bitstream dma address +}; + +struct mxc_jpeg_dev { + spinlock_t hw_lock; /* hardware access lock */ + unsigned int mode; + struct mutex lock; /* v4l2 ioctls serialization */ + struct clk_bulk_data *clks; + int num_clks; + struct platform_device *pdev; + struct device *dev; + void __iomem *base_reg; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *dec_vdev; + struct mxc_jpeg_slot_data slot_data; + int num_domains; + struct device **pd_dev; + struct device_link **pd_link; +}; + +/** + * struct mxc_jpeg_sof_comp - JPEG Start Of Frame component fields + * @id: component id + * @v: vertical sampling + * @h: horizontal sampling + * @quantization_table_no: id of quantization table + */ +struct mxc_jpeg_sof_comp { + u8 id; + u8 v :4; + u8 h :4; + u8 quantization_table_no; +} __packed; + +#define MXC_JPEG_MAX_COMPONENTS 4 +/** + * struct mxc_jpeg_sof - JPEG Start Of Frame marker fields + * @length: Start of Frame length + * @precision: precision (bits per pixel per color component) + * @height: image height + * @width: image width + * @components_no: number of color components + * @comp: component fields for each color component + */ +struct mxc_jpeg_sof { + u16 length; + u8 precision; + u16 height, width; + u8 components_no; + struct mxc_jpeg_sof_comp comp[MXC_JPEG_MAX_COMPONENTS]; +} __packed; + +/** + * struct mxc_jpeg_sos_comp - JPEG Start Of Scan component fields + * @id: component id + * @huffman_table_no: id of the Huffman table + */ +struct mxc_jpeg_sos_comp { + u8 id; /*component id*/ + u8 huffman_table_no; +} __packed; + +/** + * struct mxc_jpeg_sos - JPEG Start Of Scan marker fields + * @length: Start of Frame length + * @components_no: number of color components + * @comp: SOS component fields for each color component + * @ignorable_bytes: ignorable bytes + */ +struct mxc_jpeg_sos { + u16 length; + u8 components_no; + struct mxc_jpeg_sos_comp comp[MXC_JPEG_MAX_COMPONENTS]; + u8 ignorable_bytes[3]; +} __packed; + +#endif diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c new file mode 100644 index 0000000000..142ac7b73e --- /dev/null +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -0,0 +1,1540 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung CSIS MIPI CSI-2 receiver driver. + * + * The Samsung CSIS IP is a MIPI CSI-2 receiver found in various NXP i.MX7 and + * i.MX8 SoCs. The i.MX7 features version 3.3 of the IP, while i.MX8 features + * version 3.6.3. + * + * Copyright (C) 2019 Linaro Ltd + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +#define CSIS_DRIVER_NAME "imx-mipi-csis" + +#define CSIS_PAD_SINK 0 +#define CSIS_PAD_SOURCE 1 +#define CSIS_PADS_NUM 2 + +#define MIPI_CSIS_DEF_PIX_WIDTH 640 +#define MIPI_CSIS_DEF_PIX_HEIGHT 480 + +/* Register map definition */ + +/* CSIS version */ +#define MIPI_CSIS_VERSION 0x00 +#define MIPI_CSIS_VERSION_IMX7D 0x03030505 +#define MIPI_CSIS_VERSION_IMX8MP 0x03060301 + +/* CSIS common control */ +#define MIPI_CSIS_CMN_CTRL 0x04 +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW BIT(16) +#define MIPI_CSIS_CMN_CTRL_INTER_MODE BIT(10) +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL BIT(2) +#define MIPI_CSIS_CMN_CTRL_RESET BIT(1) +#define MIPI_CSIS_CMN_CTRL_ENABLE BIT(0) + +#define MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET 8 +#define MIPI_CSIS_CMN_CTRL_LANE_NR_MASK (3 << 8) + +/* CSIS clock control */ +#define MIPI_CSIS_CLK_CTRL 0x08 +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH3(x) ((x) << 28) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH2(x) ((x) << 24) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH1(x) ((x) << 20) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(x) ((x) << 16) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK (0xf << 4) +#define MIPI_CSIS_CLK_CTRL_WCLK_SRC BIT(0) + +/* CSIS Interrupt mask */ +#define MIPI_CSIS_INT_MSK 0x10 +#define MIPI_CSIS_INT_MSK_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_MSK_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_MSK_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_MSK_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_MSK_FRAME_START BIT(24) +#define MIPI_CSIS_INT_MSK_FRAME_END BIT(20) +#define MIPI_CSIS_INT_MSK_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_MSK_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_MSK_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_MSK_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_MSK_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_MSK_ERR_UNKNOWN BIT(0) + +/* CSIS Interrupt source */ +#define MIPI_CSIS_INT_SRC 0x14 +#define MIPI_CSIS_INT_SRC_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_SRC_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_SRC_EVEN BIT(30) +#define MIPI_CSIS_INT_SRC_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_SRC_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_SRC_ODD (0x3 << 28) +#define MIPI_CSIS_INT_SRC_NON_IMAGE_DATA (0xf << 28) +#define MIPI_CSIS_INT_SRC_FRAME_START BIT(24) +#define MIPI_CSIS_INT_SRC_FRAME_END BIT(20) +#define MIPI_CSIS_INT_SRC_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_SRC_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_SRC_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_SRC_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_SRC_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_SRC_ERR_UNKNOWN BIT(0) +#define MIPI_CSIS_INT_SRC_ERRORS 0xfffff + +/* D-PHY status control */ +#define MIPI_CSIS_DPHY_STATUS 0x20 +#define MIPI_CSIS_DPHY_STATUS_ULPS_DAT BIT(8) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_DAT BIT(4) +#define MIPI_CSIS_DPHY_STATUS_ULPS_CLK BIT(1) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_CLK BIT(0) + +/* D-PHY common control */ +#define MIPI_CSIS_DPHY_CMN_CTRL 0x24 +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(n) ((n) << 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE_MASK GENMASK(31, 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(n) ((n) << 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE_MASK GENMASK(23, 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_CLK BIT(6) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_DAT BIT(5) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_DAT BIT(1) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_CLK BIT(0) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE (0x1f << 0) + +/* D-PHY Master and Slave Control register Low */ +#define MIPI_CSIS_DPHY_BCTRL_L 0x30 +#define MIPI_CSIS_DPHY_BCTRL_L_USER_DATA_PATTERN_LOW(n) (((n) & 3U) << 30) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV (0 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_724MV (1 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_733MV (2 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_706MV (3 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ (0 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_1_5MHZ (1 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_VREG12_EXTPWR_EN_CTL BIT(26) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V (0 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_23V (1 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_17V (2 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_26V (3 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_1P2_LVL_SEL BIT(23) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV (0 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_100MV (1 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_120MV (2 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_140MV (3 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_VREF_SRC_SEL BIT(20) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV (0 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_743MV (1 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_650MV (2 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_682MV (3 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_PULSE_REJECT BIT(17) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_0 (0 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_15P (1 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_30P (3 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_UP BIT(14) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV (0 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_70MV (1 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_EN BIT(12) +#define MIPI_CSIS_DPHY_BCTRL_L_ERRCONTENTION_LP_EN BIT(11) +#define MIPI_CSIS_DPHY_BCTRL_L_TXTRIGGER_CLK_EN BIT(10) +#define MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(n) (((n) * 25 / 1000000) << 0) + +/* D-PHY Master and Slave Control register High */ +#define MIPI_CSIS_DPHY_BCTRL_H 0x34 +/* D-PHY Slave Control register Low */ +#define MIPI_CSIS_DPHY_SCTRL_L 0x38 +/* D-PHY Slave Control register High */ +#define MIPI_CSIS_DPHY_SCTRL_H 0x3c + +/* ISP Configuration register */ +#define MIPI_CSIS_ISP_CONFIG_CH(n) (0x40 + (n) * 0x10) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP_MSK (0xff << 24) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP(x) ((x) << 24) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_SINGLE (0 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL (1 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_QUAD (2 << 12) /* i.MX8M[MNP] only */ +#define MIPI_CSIS_ISPCFG_PIXEL_MASK (3 << 12) +#define MIPI_CSIS_ISPCFG_ALIGN_32BIT BIT(11) +#define MIPI_CSIS_ISPCFG_FMT(fmt) ((fmt) << 2) +#define MIPI_CSIS_ISPCFG_FMT_MASK (0x3f << 2) + +/* ISP Image Resolution register */ +#define MIPI_CSIS_ISP_RESOL_CH(n) (0x44 + (n) * 0x10) +#define CSIS_MAX_PIX_WIDTH 0xffff +#define CSIS_MAX_PIX_HEIGHT 0xffff + +/* ISP SYNC register */ +#define MIPI_CSIS_ISP_SYNC_CH(n) (0x48 + (n) * 0x10) +#define MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET 18 +#define MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET 12 +#define MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET 0 + +/* ISP shadow registers */ +#define MIPI_CSIS_SDW_CONFIG_CH(n) (0x80 + (n) * 0x10) +#define MIPI_CSIS_SDW_RESOL_CH(n) (0x84 + (n) * 0x10) +#define MIPI_CSIS_SDW_SYNC_CH(n) (0x88 + (n) * 0x10) + +/* Debug control register */ +#define MIPI_CSIS_DBG_CTRL 0xc0 +#define MIPI_CSIS_DBG_INTR_MSK 0xc4 +#define MIPI_CSIS_DBG_INTR_MSK_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_MSK_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_MSK_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_MSK_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_RISE BIT(0) +#define MIPI_CSIS_DBG_INTR_SRC 0xc8 +#define MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE BIT(0) + +#define MIPI_CSIS_FRAME_COUNTER_CH(n) (0x0100 + (n) * 4) + +/* Non-image packet data buffers */ +#define MIPI_CSIS_PKTDATA_ODD 0x2000 +#define MIPI_CSIS_PKTDATA_EVEN 0x3000 +#define MIPI_CSIS_PKTDATA_SIZE SZ_4K + +#define DEFAULT_SCLK_CSIS_FREQ 166000000UL + +/* MIPI CSI-2 Data Types */ +#define MIPI_CSI2_DATA_TYPE_YUV420_8 0x18 +#define MIPI_CSI2_DATA_TYPE_YUV420_10 0x19 +#define MIPI_CSI2_DATA_TYPE_LE_YUV420_8 0x1a +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_8 0x1c +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_10 0x1d +#define MIPI_CSI2_DATA_TYPE_YUV422_8 0x1e +#define MIPI_CSI2_DATA_TYPE_YUV422_10 0x1f +#define MIPI_CSI2_DATA_TYPE_RGB565 0x22 +#define MIPI_CSI2_DATA_TYPE_RGB666 0x23 +#define MIPI_CSI2_DATA_TYPE_RGB888 0x24 +#define MIPI_CSI2_DATA_TYPE_RAW6 0x28 +#define MIPI_CSI2_DATA_TYPE_RAW7 0x29 +#define MIPI_CSI2_DATA_TYPE_RAW8 0x2a +#define MIPI_CSI2_DATA_TYPE_RAW10 0x2b +#define MIPI_CSI2_DATA_TYPE_RAW12 0x2c +#define MIPI_CSI2_DATA_TYPE_RAW14 0x2d +#define MIPI_CSI2_DATA_TYPE_USER(x) (0x30 + (x)) + +struct mipi_csis_event { + bool debug; + u32 mask; + const char * const name; + unsigned int counter; +}; + +static const struct mipi_csis_event mipi_csis_events[] = { + /* Errors */ + { false, MIPI_CSIS_INT_SRC_ERR_SOT_HS, "SOT Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FS, "Lost Frame Start Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FE, "Lost Frame End Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_OVER, "FIFO Overflow Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_WRONG_CFG, "Wrong Configuration Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_ECC, "ECC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_CRC, "CRC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_UNKNOWN, "Unknown Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT, "Data Type Not Supported" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE, "Data Type Ignored" }, + { true, MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE, "Frame Size Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME, "Truncated Frame" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FE, "Early Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FS, "Early Frame Start" }, + /* Non-image data receive events */ + { false, MIPI_CSIS_INT_SRC_EVEN_BEFORE, "Non-image data before even frame" }, + { false, MIPI_CSIS_INT_SRC_EVEN_AFTER, "Non-image data after even frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_BEFORE, "Non-image data before odd frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_AFTER, "Non-image data after odd frame" }, + /* Frame start/end */ + { false, MIPI_CSIS_INT_SRC_FRAME_START, "Frame Start" }, + { false, MIPI_CSIS_INT_SRC_FRAME_END, "Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL, "VSYNC Falling Edge" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE, "VSYNC Rising Edge" }, +}; + +#define MIPI_CSIS_NUM_EVENTS ARRAY_SIZE(mipi_csis_events) + +enum mipi_csis_clk { + MIPI_CSIS_CLK_PCLK, + MIPI_CSIS_CLK_WRAP, + MIPI_CSIS_CLK_PHY, + MIPI_CSIS_CLK_AXI, +}; + +static const char * const mipi_csis_clk_id[] = { + "pclk", + "wrap", + "phy", + "axi", +}; + +enum mipi_csis_version { + MIPI_CSIS_V3_3, + MIPI_CSIS_V3_6_3, +}; + +struct mipi_csis_info { + enum mipi_csis_version version; + unsigned int num_clocks; +}; + +struct mipi_csis_device { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clks; + struct reset_control *mrst; + struct regulator *mipi_phy_regulator; + const struct mipi_csis_info *info; + + struct v4l2_subdev sd; + struct media_pad pads[CSIS_PADS_NUM]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + + struct v4l2_mbus_config_mipi_csi2 bus; + u32 clk_frequency; + u32 hs_settle; + u32 clk_settle; + + spinlock_t slock; /* Protect events */ + struct mipi_csis_event events[MIPI_CSIS_NUM_EVENTS]; + struct dentry *debugfs_root; + struct { + bool enable; + u32 hs_settle; + u32 clk_settle; + } debug; +}; + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct csis_pix_format { + u32 code; + u32 output; + u32 data_type; + u8 width; +}; + +static const struct csis_pix_format mipi_csis_formats[] = { + /* YUV formats. */ + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .output = MEDIA_BUS_FMT_UYVY8_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_YUV422_8, + .width = 16, + }, + /* RGB formats. */ + { + .code = MEDIA_BUS_FMT_RGB565_1X16, + .output = MEDIA_BUS_FMT_RGB565_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_RGB565, + .width = 16, + }, { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .output = MEDIA_BUS_FMT_RGB888_1X24, + .data_type = MIPI_CSI2_DATA_TYPE_RGB888, + .width = 24, + }, + /* RAW (Bayer and greyscale) formats. */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .output = MEDIA_BUS_FMT_SBGGR8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .output = MEDIA_BUS_FMT_SGBRG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .output = MEDIA_BUS_FMT_SGRBG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .output = MEDIA_BUS_FMT_SRGGB8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .output = MEDIA_BUS_FMT_Y8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .output = MEDIA_BUS_FMT_SBGGR10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .output = MEDIA_BUS_FMT_SGBRG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .output = MEDIA_BUS_FMT_SGRBG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .output = MEDIA_BUS_FMT_SRGGB10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .output = MEDIA_BUS_FMT_Y10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .output = MEDIA_BUS_FMT_SBGGR12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .output = MEDIA_BUS_FMT_SGBRG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .output = MEDIA_BUS_FMT_SGRBG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .output = MEDIA_BUS_FMT_SRGGB12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .output = MEDIA_BUS_FMT_Y12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .output = MEDIA_BUS_FMT_SBGGR14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .output = MEDIA_BUS_FMT_SGBRG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .output = MEDIA_BUS_FMT_SGRBG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .output = MEDIA_BUS_FMT_SRGGB14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, + /* JPEG */ + { + .code = MEDIA_BUS_FMT_JPEG_1X8, + .output = MEDIA_BUS_FMT_JPEG_1X8, + /* + * Map JPEG_1X8 to the RAW8 datatype. + * + * The CSI-2 specification suggests in Annex A "JPEG8 Data + * Format (informative)" to transmit JPEG data using one of the + * Data Types aimed to represent arbitrary data, such as the + * "User Defined Data Type 1" (0x30). + * + * However, when configured with a User Defined Data Type, the + * CSIS outputs data in quad pixel mode regardless of the mode + * selected in the MIPI_CSIS_ISP_CONFIG_CH register. Neither of + * the IP cores connected to the CSIS in i.MX SoCs (CSI bridge + * or ISI) support quad pixel mode, so this will never work in + * practice. + * + * Some sensors (such as the OV5640) send JPEG data using the + * RAW8 data type. This is usable and works, so map the JPEG + * format to RAW8. If the CSIS ends up being integrated in an + * SoC that can support quad pixel mode, this will have to be + * revisited. + */ + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + } +}; + +static const struct csis_pix_format *find_csis_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mipi_csis_formats); i++) + if (code == mipi_csis_formats[i].code) + return &mipi_csis_formats[i]; + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static inline u32 mipi_csis_read(struct mipi_csis_device *csis, u32 reg) +{ + return readl(csis->regs + reg); +} + +static inline void mipi_csis_write(struct mipi_csis_device *csis, u32 reg, + u32 val) +{ + writel(val, csis->regs + reg); +} + +static void mipi_csis_enable_interrupts(struct mipi_csis_device *csis, bool on) +{ + mipi_csis_write(csis, MIPI_CSIS_INT_MSK, on ? 0xffffffff : 0); + mipi_csis_write(csis, MIPI_CSIS_DBG_INTR_MSK, on ? 0xffffffff : 0); +} + +static void mipi_csis_sw_reset(struct mipi_csis_device *csis) +{ + u32 val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_RESET); + usleep_range(10, 20); +} + +static void mipi_csis_system_enable(struct mipi_csis_device *csis, int on) +{ + u32 val, mask; + + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + if (on) + val |= MIPI_CSIS_CMN_CTRL_ENABLE; + else + val &= ~MIPI_CSIS_CMN_CTRL_ENABLE; + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, val); + + val = mipi_csis_read(csis, MIPI_CSIS_DPHY_CMN_CTRL); + val &= ~MIPI_CSIS_DPHY_CMN_CTRL_ENABLE; + if (on) { + mask = (1 << (csis->bus.num_data_lanes + 1)) - 1; + val |= (mask & MIPI_CSIS_DPHY_CMN_CTRL_ENABLE); + } + mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, val); +} + +static void __mipi_csis_set_format(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) +{ + u32 val; + + /* Color format */ + val = mipi_csis_read(csis, MIPI_CSIS_ISP_CONFIG_CH(0)); + val &= ~(MIPI_CSIS_ISPCFG_ALIGN_32BIT | MIPI_CSIS_ISPCFG_FMT_MASK + | MIPI_CSIS_ISPCFG_PIXEL_MASK); + + /* + * YUV 4:2:2 can be transferred with 8 or 16 bits per clock sample + * (referred to in the documentation as single and dual pixel modes + * respectively, although the 8-bit mode transfers half a pixel per + * clock sample and the 16-bit mode one pixel). While both mode work + * when the CSIS is connected to a receiver that supports either option, + * single pixel mode requires clock rates twice as high. As all SoCs + * that integrate the CSIS can operate in 16-bit bit mode, and some do + * not support 8-bit mode (this is the case of the i.MX8MP), use dual + * pixel mode unconditionally. + * + * TODO: Verify which other formats require DUAL (or QUAD) modes. + */ + if (csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) + val |= MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL; + + val |= MIPI_CSIS_ISPCFG_FMT(csis_fmt->data_type); + mipi_csis_write(csis, MIPI_CSIS_ISP_CONFIG_CH(0), val); + + /* Pixel resolution */ + val = format->width | (format->height << 16); + mipi_csis_write(csis, MIPI_CSIS_ISP_RESOL_CH(0), val); +} + +static int mipi_csis_calculate_params(struct mipi_csis_device *csis, + const struct csis_pix_format *csis_fmt) +{ + s64 link_freq; + u32 lane_rate; + + /* Calculate the line rate from the pixel rate. */ + link_freq = v4l2_get_link_freq(csis->src_sd->ctrl_handler, + csis_fmt->width, + csis->bus.num_data_lanes * 2); + if (link_freq < 0) { + dev_err(csis->dev, "Unable to obtain link frequency: %d\n", + (int)link_freq); + return link_freq; + } + + lane_rate = link_freq * 2; + + if (lane_rate < 80000000 || lane_rate > 1500000000) { + dev_dbg(csis->dev, "Out-of-bound lane rate %u\n", lane_rate); + return -EINVAL; + } + + /* + * The HSSETTLE counter value is document in a table, but can also + * easily be calculated. Hardcode the CLKSETTLE value to 0 for now + * (which is documented as corresponding to CSI-2 v0.87 to v1.00) until + * we figure out how to compute it correctly. + */ + csis->hs_settle = (lane_rate - 5000000) / 45000000; + csis->clk_settle = 0; + + dev_dbg(csis->dev, "lane rate %u, Tclk_settle %u, Ths_settle %u\n", + lane_rate, csis->clk_settle, csis->hs_settle); + + if (csis->debug.hs_settle < 0xff) { + dev_dbg(csis->dev, "overriding Ths_settle with %u\n", + csis->debug.hs_settle); + csis->hs_settle = csis->debug.hs_settle; + } + + if (csis->debug.clk_settle < 4) { + dev_dbg(csis->dev, "overriding Tclk_settle with %u\n", + csis->debug.clk_settle); + csis->clk_settle = csis->debug.clk_settle; + } + + return 0; +} + +static void mipi_csis_set_params(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) +{ + int lanes = csis->bus.num_data_lanes; + u32 val; + + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + val &= ~MIPI_CSIS_CMN_CTRL_LANE_NR_MASK; + val |= (lanes - 1) << MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET; + if (csis->info->version == MIPI_CSIS_V3_3) + val |= MIPI_CSIS_CMN_CTRL_INTER_MODE; + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, val); + + __mipi_csis_set_format(csis, format, csis_fmt); + + mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, + MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(csis->hs_settle) | + MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(csis->clk_settle)); + + val = (0 << MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET); + mipi_csis_write(csis, MIPI_CSIS_ISP_SYNC_CH(0), val); + + val = mipi_csis_read(csis, MIPI_CSIS_CLK_CTRL); + val |= MIPI_CSIS_CLK_CTRL_WCLK_SRC; + val |= MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(15); + val &= ~MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK; + mipi_csis_write(csis, MIPI_CSIS_CLK_CTRL, val); + + mipi_csis_write(csis, MIPI_CSIS_DPHY_BCTRL_L, + MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV | + MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ | + MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV | + MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(20000000)); + mipi_csis_write(csis, MIPI_CSIS_DPHY_BCTRL_H, 0); + + /* Update the shadow register. */ + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW | + MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL); +} + +static int mipi_csis_clk_enable(struct mipi_csis_device *csis) +{ + return clk_bulk_prepare_enable(csis->info->num_clocks, csis->clks); +} + +static void mipi_csis_clk_disable(struct mipi_csis_device *csis) +{ + clk_bulk_disable_unprepare(csis->info->num_clocks, csis->clks); +} + +static int mipi_csis_clk_get(struct mipi_csis_device *csis) +{ + unsigned int i; + int ret; + + csis->clks = devm_kcalloc(csis->dev, csis->info->num_clocks, + sizeof(*csis->clks), GFP_KERNEL); + + if (!csis->clks) + return -ENOMEM; + + for (i = 0; i < csis->info->num_clocks; i++) + csis->clks[i].id = mipi_csis_clk_id[i]; + + ret = devm_clk_bulk_get(csis->dev, csis->info->num_clocks, + csis->clks); + if (ret < 0) + return ret; + + /* Set clock rate */ + ret = clk_set_rate(csis->clks[MIPI_CSIS_CLK_WRAP].clk, + csis->clk_frequency); + if (ret < 0) + dev_err(csis->dev, "set rate=%d failed: %d\n", + csis->clk_frequency, ret); + + return ret; +} + +static void mipi_csis_start_stream(struct mipi_csis_device *csis, + const struct v4l2_mbus_framefmt *format, + const struct csis_pix_format *csis_fmt) +{ + mipi_csis_sw_reset(csis); + mipi_csis_set_params(csis, format, csis_fmt); + mipi_csis_system_enable(csis, true); + mipi_csis_enable_interrupts(csis, true); +} + +static void mipi_csis_stop_stream(struct mipi_csis_device *csis) +{ + mipi_csis_enable_interrupts(csis, false); + mipi_csis_system_enable(csis, false); +} + +static irqreturn_t mipi_csis_irq_handler(int irq, void *dev_id) +{ + struct mipi_csis_device *csis = dev_id; + unsigned long flags; + unsigned int i; + u32 status; + u32 dbg_status; + + status = mipi_csis_read(csis, MIPI_CSIS_INT_SRC); + dbg_status = mipi_csis_read(csis, MIPI_CSIS_DBG_INTR_SRC); + + spin_lock_irqsave(&csis->slock, flags); + + /* Update the event/error counters */ + if ((status & MIPI_CSIS_INT_SRC_ERRORS) || csis->debug.enable) { + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) { + struct mipi_csis_event *event = &csis->events[i]; + + if ((!event->debug && (status & event->mask)) || + (event->debug && (dbg_status & event->mask))) + event->counter++; + } + } + spin_unlock_irqrestore(&csis->slock, flags); + + mipi_csis_write(csis, MIPI_CSIS_INT_SRC, status); + mipi_csis_write(csis, MIPI_CSIS_DBG_INTR_SRC, dbg_status); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * PHY regulator and reset + */ + +static int mipi_csis_phy_enable(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_enable(csis->mipi_phy_regulator); +} + +static int mipi_csis_phy_disable(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_disable(csis->mipi_phy_regulator); +} + +static void mipi_csis_phy_reset(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return; + + reset_control_assert(csis->mrst); + msleep(20); + reset_control_deassert(csis->mrst); +} + +static int mipi_csis_phy_init(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + /* Get MIPI PHY reset and regulator. */ + csis->mrst = devm_reset_control_get_exclusive(csis->dev, NULL); + if (IS_ERR(csis->mrst)) + return PTR_ERR(csis->mrst); + + csis->mipi_phy_regulator = devm_regulator_get(csis->dev, "phy"); + if (IS_ERR(csis->mipi_phy_regulator)) + return PTR_ERR(csis->mipi_phy_regulator); + + return regulator_set_voltage(csis->mipi_phy_regulator, 1000000, + 1000000); +} + +/* ----------------------------------------------------------------------------- + * Debug + */ + +static void mipi_csis_clear_counters(struct mipi_csis_device *csis) +{ + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&csis->slock, flags); + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) + csis->events[i].counter = 0; + spin_unlock_irqrestore(&csis->slock, flags); +} + +static void mipi_csis_log_counters(struct mipi_csis_device *csis, bool non_errors) +{ + unsigned int num_events = non_errors ? MIPI_CSIS_NUM_EVENTS + : MIPI_CSIS_NUM_EVENTS - 8; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&csis->slock, flags); + + for (i = 0; i < num_events; ++i) { + if (csis->events[i].counter > 0 || csis->debug.enable) + dev_info(csis->dev, "%s events: %d\n", + csis->events[i].name, + csis->events[i].counter); + } + spin_unlock_irqrestore(&csis->slock, flags); +} + +static int mipi_csis_dump_regs(struct mipi_csis_device *csis) +{ + static const struct { + u32 offset; + const char * const name; + } registers[] = { + { MIPI_CSIS_CMN_CTRL, "CMN_CTRL" }, + { MIPI_CSIS_CLK_CTRL, "CLK_CTRL" }, + { MIPI_CSIS_INT_MSK, "INT_MSK" }, + { MIPI_CSIS_DPHY_STATUS, "DPHY_STATUS" }, + { MIPI_CSIS_DPHY_CMN_CTRL, "DPHY_CMN_CTRL" }, + { MIPI_CSIS_DPHY_SCTRL_L, "DPHY_SCTRL_L" }, + { MIPI_CSIS_DPHY_SCTRL_H, "DPHY_SCTRL_H" }, + { MIPI_CSIS_ISP_CONFIG_CH(0), "ISP_CONFIG_CH0" }, + { MIPI_CSIS_ISP_RESOL_CH(0), "ISP_RESOL_CH0" }, + { MIPI_CSIS_SDW_CONFIG_CH(0), "SDW_CONFIG_CH0" }, + { MIPI_CSIS_SDW_RESOL_CH(0), "SDW_RESOL_CH0" }, + { MIPI_CSIS_DBG_CTRL, "DBG_CTRL" }, + { MIPI_CSIS_FRAME_COUNTER_CH(0), "FRAME_COUNTER_CH0" }, + }; + + unsigned int i; + u32 cfg; + + if (!pm_runtime_get_if_in_use(csis->dev)) + return 0; + + dev_info(csis->dev, "--- REGISTERS ---\n"); + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + cfg = mipi_csis_read(csis, registers[i].offset); + dev_info(csis->dev, "%14s: 0x%08x\n", registers[i].name, cfg); + } + + pm_runtime_put(csis->dev); + + return 0; +} + +static int mipi_csis_dump_regs_show(struct seq_file *m, void *private) +{ + struct mipi_csis_device *csis = m->private; + + return mipi_csis_dump_regs(csis); +} +DEFINE_SHOW_ATTRIBUTE(mipi_csis_dump_regs); + +static void mipi_csis_debugfs_init(struct mipi_csis_device *csis) +{ + csis->debug.hs_settle = UINT_MAX; + csis->debug.clk_settle = UINT_MAX; + + csis->debugfs_root = debugfs_create_dir(dev_name(csis->dev), NULL); + + debugfs_create_bool("debug_enable", 0600, csis->debugfs_root, + &csis->debug.enable); + debugfs_create_file("dump_regs", 0600, csis->debugfs_root, csis, + &mipi_csis_dump_regs_fops); + debugfs_create_u32("tclk_settle", 0600, csis->debugfs_root, + &csis->debug.clk_settle); + debugfs_create_u32("ths_settle", 0600, csis->debugfs_root, + &csis->debug.hs_settle); +} + +static void mipi_csis_debugfs_exit(struct mipi_csis_device *csis) +{ + debugfs_remove_recursive(csis->debugfs_root); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct mipi_csis_device *sd_to_mipi_csis_device(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct mipi_csis_device, sd); +} + +static int mipi_csis_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + const struct v4l2_mbus_framefmt *format; + const struct csis_pix_format *csis_fmt; + struct v4l2_subdev_state *state; + int ret; + + if (!enable) { + v4l2_subdev_call(csis->src_sd, video, s_stream, 0); + + mipi_csis_stop_stream(csis); + if (csis->debug.enable) + mipi_csis_log_counters(csis, true); + + pm_runtime_put(csis->dev); + + return 0; + } + + state = v4l2_subdev_lock_and_get_active_state(sd); + + format = v4l2_subdev_get_pad_format(sd, state, CSIS_PAD_SINK); + csis_fmt = find_csis_format(format->code); + + ret = mipi_csis_calculate_params(csis, csis_fmt); + if (ret < 0) + goto err_unlock; + + mipi_csis_clear_counters(csis); + + ret = pm_runtime_resume_and_get(csis->dev); + if (ret < 0) + goto err_unlock; + + mipi_csis_start_stream(csis, format, csis_fmt); + + ret = v4l2_subdev_call(csis->src_sd, video, s_stream, 1); + if (ret < 0) + goto err_stop; + + mipi_csis_log_counters(csis, true); + + v4l2_subdev_unlock_state(state); + + return 0; + +err_stop: + mipi_csis_stop_stream(csis); + pm_runtime_put(csis->dev); +err_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* + * The CSIS can't transcode in any way, the source format is identical + * to the sink format. + */ + if (code->pad == CSIS_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_get_pad_format(sd, sd_state, code->pad); + code->code = fmt->code; + return 0; + } + + if (code->pad != CSIS_PAD_SINK) + return -EINVAL; + + if (code->index >= ARRAY_SIZE(mipi_csis_formats)) + return -EINVAL; + + code->code = mipi_csis_formats[code->index].code; + + return 0; +} + +static int mipi_csis_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct csis_pix_format const *csis_fmt; + struct v4l2_mbus_framefmt *fmt; + unsigned int align; + + /* + * The CSIS can't transcode in any way, the source format can't be + * modified. + */ + if (sdformat->pad == CSIS_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, sdformat); + + if (sdformat->pad != CSIS_PAD_SINK) + return -EINVAL; + + /* + * Validate the media bus code and clamp and align the size. + * + * The total number of bits per line must be a multiple of 8. We thus + * need to align the width for formats that are not multiples of 8 + * bits. + */ + csis_fmt = find_csis_format(sdformat->format.code); + if (!csis_fmt) + csis_fmt = &mipi_csis_formats[0]; + + switch (csis_fmt->width % 8) { + case 0: + align = 0; + break; + case 4: + align = 1; + break; + case 2: + case 6: + align = 2; + break; + default: + /* 1, 3, 5, 7 */ + align = 3; + break; + } + + v4l_bound_align_image(&sdformat->format.width, 1, + CSIS_MAX_PIX_WIDTH, align, + &sdformat->format.height, 1, + CSIS_MAX_PIX_HEIGHT, 0, 0); + + fmt = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); + + fmt->code = csis_fmt->code; + fmt->width = sdformat->format.width; + fmt->height = sdformat->format.height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = sdformat->format.colorspace; + fmt->quantization = sdformat->format.quantization; + fmt->xfer_func = sdformat->format.xfer_func; + fmt->ycbcr_enc = sdformat->format.ycbcr_enc; + + sdformat->format = *fmt; + + /* Propagate the format from sink to source. */ + fmt = v4l2_subdev_get_pad_format(sd, sd_state, CSIS_PAD_SOURCE); + *fmt = sdformat->format; + + /* The format on the source pad might change due to unpacking. */ + fmt->code = csis_fmt->output; + + return 0; +} + +static int mipi_csis_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0]; + const struct csis_pix_format *csis_fmt; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + + if (pad != CSIS_PAD_SOURCE) + return -EINVAL; + + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_get_pad_format(sd, state, CSIS_PAD_SOURCE); + csis_fmt = find_csis_format(fmt->code); + v4l2_subdev_unlock_state(state); + + if (!csis_fmt) + return -EPIPE; + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; + fd->num_entries = 1; + + memset(entry, 0, sizeof(*entry)); + + entry->flags = 0; + entry->pixelcode = csis_fmt->code; + entry->bus.csi2.vc = 0; + entry->bus.csi2.dt = csis_fmt->data_type; + + return 0; +} + +static int mipi_csis_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = { + .pad = CSIS_PAD_SINK, + }; + + fmt.format.code = mipi_csis_formats[0].code; + fmt.format.width = MIPI_CSIS_DEF_PIX_WIDTH; + fmt.format.height = MIPI_CSIS_DEF_PIX_HEIGHT; + + fmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt.format.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt.format.colorspace); + fmt.format.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt.format.colorspace); + fmt.format.quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt.format.colorspace, + fmt.format.ycbcr_enc); + + return mipi_csis_set_fmt(sd, sd_state, &fmt); +} + +static int mipi_csis_log_status(struct v4l2_subdev *sd) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + + mipi_csis_log_counters(csis, true); + if (csis->debug.enable) + mipi_csis_dump_regs(csis); + + return 0; +} + +static const struct v4l2_subdev_core_ops mipi_csis_core_ops = { + .log_status = mipi_csis_log_status, +}; + +static const struct v4l2_subdev_video_ops mipi_csis_video_ops = { + .s_stream = mipi_csis_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = { + .init_cfg = mipi_csis_init_cfg, + .enum_mbus_code = mipi_csis_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mipi_csis_set_fmt, + .get_frame_desc = mipi_csis_get_frame_desc, +}; + +static const struct v4l2_subdev_ops mipi_csis_subdev_ops = { + .core = &mipi_csis_core_ops, + .video = &mipi_csis_video_ops, + .pad = &mipi_csis_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static int mipi_csis_link_setup(struct media_entity *entity, + const struct media_pad *local_pad, + const struct media_pad *remote_pad, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct v4l2_subdev *remote_sd; + + dev_dbg(csis->dev, "link setup %s -> %s", remote_pad->entity->name, + local_pad->entity->name); + + /* We only care about the link to the source. */ + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) + return 0; + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csis->src_sd) + return -EBUSY; + + csis->src_sd = remote_sd; + } else { + csis->src_sd = NULL; + } + + return 0; +} + +static const struct media_entity_operations mipi_csis_entity_ops = { + .link_setup = mipi_csis_link_setup, + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Async subdev notifier + */ + +static struct mipi_csis_device * +mipi_notifier_to_csis_state(struct v4l2_async_notifier *n) +{ + return container_of(n, struct mipi_csis_device, notifier); +} + +static int mipi_csis_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct mipi_csis_device *csis = mipi_notifier_to_csis_state(notifier); + struct media_pad *sink = &csis->sd.entity.pads[CSIS_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); +} + +static const struct v4l2_async_notifier_operations mipi_csis_notify_ops = { + .bound = mipi_csis_notify_bound, +}; + +static int mipi_csis_async_register(struct mipi_csis_device *csis) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_connection *asd; + struct fwnode_handle *ep; + unsigned int i; + int ret; + + v4l2_async_subdev_nf_init(&csis->notifier, &csis->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csis->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { + dev_err(csis->dev, + "data lanes reordering is not supported"); + ret = -EINVAL; + goto err_parse; + } + } + + csis->bus = vep.bus.mipi_csi2; + + dev_dbg(csis->dev, "data lanes: %d\n", csis->bus.num_data_lanes); + dev_dbg(csis->dev, "flags: 0x%08x\n", csis->bus.flags); + + asd = v4l2_async_nf_add_fwnode_remote(&csis->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_parse; + } + + fwnode_handle_put(ep); + + csis->notifier.ops = &mipi_csis_notify_ops; + + ret = v4l2_async_nf_register(&csis->notifier); + if (ret) + return ret; + + return v4l2_async_register_subdev(&csis->sd); + +err_parse: + fwnode_handle_put(ep); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend/resume + */ + +static int __maybe_unused mipi_csis_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + int ret; + + ret = mipi_csis_phy_disable(csis); + if (ret) + return -EAGAIN; + + mipi_csis_clk_disable(csis); + + return 0; +} + +static int __maybe_unused mipi_csis_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + int ret; + + ret = mipi_csis_phy_enable(csis); + if (ret) + return -EAGAIN; + + ret = mipi_csis_clk_enable(csis); + if (ret) { + mipi_csis_phy_disable(csis); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops mipi_csis_pm_ops = { + SET_RUNTIME_PM_OPS(mipi_csis_runtime_suspend, mipi_csis_runtime_resume, + NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe/remove & platform driver + */ + +static int mipi_csis_subdev_init(struct mipi_csis_device *csis) +{ + struct v4l2_subdev *sd = &csis->sd; + int ret; + + v4l2_subdev_init(sd, &mipi_csis_subdev_ops); + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "csis-%s", + dev_name(csis->dev)); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->ctrl_handler = NULL; + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &mipi_csis_entity_ops; + + sd->dev = csis->dev; + + csis->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + csis->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, csis->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; +} + +static int mipi_csis_parse_dt(struct mipi_csis_device *csis) +{ + struct device_node *node = csis->dev->of_node; + + if (of_property_read_u32(node, "clock-frequency", + &csis->clk_frequency)) + csis->clk_frequency = DEFAULT_SCLK_CSIS_FREQ; + + return 0; +} + +static int mipi_csis_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mipi_csis_device *csis; + int irq; + int ret; + + csis = devm_kzalloc(dev, sizeof(*csis), GFP_KERNEL); + if (!csis) + return -ENOMEM; + + spin_lock_init(&csis->slock); + + csis->dev = dev; + csis->info = of_device_get_match_data(dev); + + memcpy(csis->events, mipi_csis_events, sizeof(csis->events)); + + /* Parse DT properties. */ + ret = mipi_csis_parse_dt(csis); + if (ret < 0) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + /* Acquire resources. */ + csis->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csis->regs)) + return PTR_ERR(csis->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = mipi_csis_phy_init(csis); + if (ret < 0) + return ret; + + ret = mipi_csis_clk_get(csis); + if (ret < 0) + return ret; + + /* Reset PHY and enable the clocks. */ + mipi_csis_phy_reset(csis); + + /* Now that the hardware is initialized, request the interrupt. */ + ret = devm_request_irq(dev, irq, mipi_csis_irq_handler, 0, + dev_name(dev), csis); + if (ret) { + dev_err(dev, "Interrupt request failed\n"); + return ret; + } + + /* Initialize and register the subdev. */ + ret = mipi_csis_subdev_init(csis); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, &csis->sd); + + ret = mipi_csis_async_register(csis); + if (ret < 0) { + dev_err(dev, "async register failed: %d\n", ret); + goto err_cleanup; + } + + /* Initialize debugfs. */ + mipi_csis_debugfs_init(csis); + + /* Enable runtime PM. */ + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = mipi_csis_runtime_resume(dev); + if (ret < 0) + goto err_unregister_all; + } + + dev_info(dev, "lanes: %d, freq: %u\n", + csis->bus.num_data_lanes, csis->clk_frequency); + + return 0; + +err_unregister_all: + mipi_csis_debugfs_exit(csis); +err_cleanup: + v4l2_subdev_cleanup(&csis->sd); + media_entity_cleanup(&csis->sd.entity); + v4l2_async_nf_unregister(&csis->notifier); + v4l2_async_nf_cleanup(&csis->notifier); + v4l2_async_unregister_subdev(&csis->sd); + + return ret; +} + +static void mipi_csis_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + + mipi_csis_debugfs_exit(csis); + v4l2_async_nf_unregister(&csis->notifier); + v4l2_async_nf_cleanup(&csis->notifier); + v4l2_async_unregister_subdev(&csis->sd); + + if (!pm_runtime_enabled(&pdev->dev)) + mipi_csis_runtime_suspend(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + v4l2_subdev_cleanup(&csis->sd); + media_entity_cleanup(&csis->sd.entity); + pm_runtime_set_suspended(&pdev->dev); +} + +static const struct of_device_id mipi_csis_of_match[] = { + { + .compatible = "fsl,imx7-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_3, + .num_clocks = 3, + }, + }, { + .compatible = "fsl,imx8mm-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_6_3, + .num_clocks = 4, + }, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mipi_csis_of_match); + +static struct platform_driver mipi_csis_driver = { + .probe = mipi_csis_probe, + .remove_new = mipi_csis_remove, + .driver = { + .of_match_table = mipi_csis_of_match, + .name = CSIS_DRIVER_NAME, + .pm = &mipi_csis_pm_ops, + }, +}; + +module_platform_driver(mipi_csis_driver); + +MODULE_DESCRIPTION("i.MX7 & i.MX8 MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-mipi-csi2"); diff --git a/drivers/media/platform/nxp/imx-pxp.c b/drivers/media/platform/nxp/imx-pxp.c new file mode 100644 index 0000000000..e62dc5c1a4 --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.c @@ -0,0 +1,1954 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX Pixel Pipeline (PXP) mem-to-mem scaler/CSC/rotator driver + * + * Copyright (c) 2018 Pengutronix, Philipp Zabel + * + * based on vim2m + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <media/media-device.h> +#include <media/v4l2-ctrls.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-contig.h> + +#include "imx-pxp.h" + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); + +#define MIN_W 8 +#define MIN_H 8 +#define MAX_W 4096 +#define MAX_H 4096 +#define ALIGN_W 3 /* 8x8 pixel blocks */ +#define ALIGN_H 3 + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "pxp" + +/* Flags that indicate processing mode */ +#define MEM2MEM_HFLIP (1 << 0) +#define MEM2MEM_VFLIP (1 << 1) + +#define PXP_VERSION_MAJOR(version) \ + FIELD_GET(BM_PXP_VERSION_MAJOR, version) +#define PXP_VERSION_MINOR(version) \ + FIELD_GET(BM_PXP_VERSION_MINOR, version) + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +struct pxp_fmt { + u32 fourcc; + int depth; + /* Types the format can be used for */ + u32 types; +}; + +static struct pxp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = 32, + /* Both capture and output format */ + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + .depth = 32, + /* Capture-only format */ + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VUYA32, + .depth = 32, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_VUYX32, + .depth = 32, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + /* Output-only format */ + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_Y4, + .depth = 4, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .types = MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct pxp_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + unsigned int sequence; + struct pxp_fmt *fmt; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quant; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +static const struct regmap_config pxp_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = HW_PXP_VERSION, +}; + +static struct pxp_fmt *find_format(unsigned int pixelformat) +{ + struct pxp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct pxp_ctx; + +struct pxp_pdata { + u32 (*data_path_ctrl0)(struct pxp_ctx *ctx); +}; + +struct pxp_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif + + struct clk *clk; + struct regmap *regmap; + + const struct pxp_pdata *pdata; + + atomic_t num_inst; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct pxp_ctx { + struct v4l2_fh fh; + struct pxp_dev *dev; + + struct v4l2_ctrl_handler hdl; + + /* Abort requested by m2m */ + int aborting; + + /* Processing mode */ + int mode; + u8 alpha_component; + u8 rotation; + + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + + /* Source and destination queue data */ + struct pxp_q_data q_data[2]; +}; + +static inline struct pxp_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct pxp_ctx, fh); +} + +static struct pxp_q_data *get_q_data(struct pxp_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + return &ctx->q_data[V4L2_M2M_SRC]; + else + return &ctx->q_data[V4L2_M2M_DST]; +} + +static inline u32 pxp_read(struct pxp_dev *dev, u32 reg) +{ + u32 value; + + regmap_read(dev->regmap, reg, &value); + + return value; +} + +static inline void pxp_write(struct pxp_dev *dev, u32 reg, u32 value) +{ + regmap_write(dev->regmap, reg, value); +} + +static u32 pxp_v4l2_pix_fmt_to_ps_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_PS_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_RGB555: return BV_PXP_PS_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_PS_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_PS_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYX32: return BV_PXP_PS_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_YUYV: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_YVYU: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_PS_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_PS_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_PS_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_PS_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV21: return BV_PXP_PS_CTRL_FORMAT__YVU2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_PS_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_YUV422P: return BV_PXP_PS_CTRL_FORMAT__YUV422; + case V4L2_PIX_FMT_YUV420: return BV_PXP_PS_CTRL_FORMAT__YUV420; + } +} + +static u32 pxp_v4l2_pix_fmt_to_out_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_OUT_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_ABGR32: return BV_PXP_OUT_CTRL_FORMAT__ARGB8888; + case V4L2_PIX_FMT_BGR24: return BV_PXP_OUT_CTRL_FORMAT__RGB888P; + /* Missing V4L2 pixel formats for ARGB1555 and ARGB4444 */ + case V4L2_PIX_FMT_RGB555: return BV_PXP_OUT_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_OUT_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_OUT_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: return BV_PXP_OUT_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_OUT_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_OUT_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_OUT_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_OUT_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_OUT_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_OUT_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_OUT_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_NV21: return BV_PXP_OUT_CTRL_FORMAT__YVU2P420; + } +} + +static bool pxp_v4l2_pix_fmt_is_yuv(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + return true; + default: + return false; + } +} + +static void pxp_setup_csc(struct pxp_ctx *ctx) +{ + struct pxp_dev *dev = ctx->dev; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + !pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC1 YUV/YCbCr to RGB conversion is implemented as follows: + * + * |R| |C0 0 C1| |Y + Yoffset | + * |G| = |C0 C3 C2| * |Cb + UVoffset| + * |B| |C0 C4 0 | |Cr + UVoffset| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |R| |1.1644 0.0000 1.5960| |Y - 16 | + * |G| = |1.1644 -0.3917 -0.8129| * |Cb - 128| + * |B| |1.1644 2.0172 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x198) | /* 1.5938 (-0.23 %) */ + BF_PXP_CSC1_COEF1_C4(0x204), /* 2.0156 (-0.16 %) */ + BF_PXP_CSC1_COEF2_C2(0x730) | /* -0.8125 (+0.04 %) */ + BF_PXP_CSC1_COEF2_C3(0x79c), /* -0.3906 (+0.11 %) */ + }; + /* + * BT.601 full range: + * + * |R| |1.0000 0.0000 1.4020| |Y + 0 | + * |G| = |1.0000 -0.3441 -0.7141| * |Cb - 128| + * |B| |1.0000 1.7720 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x166) | /* 1.3984 (-0.36 %) */ + BF_PXP_CSC1_COEF1_C4(0x1c5), /* 1.7695 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x74a) | /* -0.7109 (+0.32 %) */ + BF_PXP_CSC1_COEF2_C3(0x7a8), /* -0.3438 (+0.04 %) */ + }; + /* + * Rec.709 limited range: + * + * |R| |1.1644 0.0000 1.7927| |Y - 16 | + * |G| = |1.1644 -0.2132 -0.5329| * |Cb - 128| + * |B| |1.1644 2.1124 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ca) | /* 1.7891 (-0.37 %) */ + BF_PXP_CSC1_COEF1_C4(0x21c), /* 2.1094 (-0.30 %) */ + BF_PXP_CSC1_COEF2_C2(0x778) | /* -0.5312 (+0.16 %) */ + BF_PXP_CSC1_COEF2_C3(0x7ca), /* -0.2109 (+0.23 %) */ + }; + /* + * Rec.709 full range: + * + * |R| |1.0000 0.0000 1.5748| |Y + 0 | + * |G| = |1.0000 -0.1873 -0.4681| * |Cb - 128| + * |B| |1.0000 1.8556 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.06 %) */ + BF_PXP_CSC1_COEF1_C4(0x1db), /* 1.8555 (-0.01 %) */ + BF_PXP_CSC1_COEF2_C2(0x789) | /* -0.4648 (+0.33 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.37 %) */ + }; + /* + * BT.2020 limited range: + * + * |R| |1.1644 0.0000 1.6787| |Y - 16 | + * |G| = |1.1644 -0.1874 -0.6505| * |Cb - 128| + * |B| |1.1644 2.1418 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ad) | /* 1.6758 (-0.29 %) */ + BF_PXP_CSC1_COEF1_C4(0x224), /* 2.1406 (-0.11 %) */ + BF_PXP_CSC1_COEF2_C2(0x75a) | /* -0.6484 (+0.20 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.38 %) */ + }; + /* + * BT.2020 full range: + * + * |R| |1.0000 0.0000 1.4746| |Y + 0 | + * |G| = |1.0000 -0.1646 -0.5714| * |Cb - 128| + * |B| |1.0000 1.8814 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x179) | /* 1.4727 (-0.19 %) */ + BF_PXP_CSC1_COEF1_C4(0x1e1), /* 1.8789 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x76e) | /* -0.5703 (+0.11 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d6), /* -0.1641 (+0.05 %) */ + }; + /* + * SMPTE 240m limited range: + * + * |R| |1.1644 0.0000 1.7937| |Y - 16 | + * |G| = |1.1644 -0.2565 -0.5427| * |Cb - 128| + * |B| |1.1644 2.0798 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1cb) | /* 1.7930 (-0.07 %) */ + BF_PXP_CSC1_COEF1_C4(0x214), /* 2.0781 (-0.17 %) */ + BF_PXP_CSC1_COEF2_C2(0x776) | /* -0.5391 (+0.36 %) */ + BF_PXP_CSC1_COEF2_C3(0x7bf), /* -0.2539 (+0.26 %) */ + }; + /* + * SMPTE 240m full range: + * + * |R| |1.0000 0.0000 1.5756| |Y + 0 | + * |G| = |1.0000 -0.2253 -0.4767| * |Cb - 128| + * |B| |1.0000 1.8270 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.14 %) */ + BF_PXP_CSC1_COEF1_C4(0x1d3), /* 1.8242 (-0.28 %) */ + BF_PXP_CSC1_COEF2_C2(0x786) | /* -0.4766 (+0.01 %) */ + BF_PXP_CSC1_COEF2_C3(0x7c7), /* -0.2227 (+0.26 %) */ + }; + const u32 *csc1_coef; + + ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_SRC].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt601_full; + else + csc1_coef = csc1_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_rec709_full; + else + csc1_coef = csc1_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt2020_full; + else + csc1_coef = csc1_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_smpte240m_full; + else + csc1_coef = csc1_coef_smpte240m_lim; + } + + pxp_write(dev, HW_PXP_CSC1_COEF0, csc1_coef[0]); + pxp_write(dev, HW_PXP_CSC1_COEF1, csc1_coef[1]); + pxp_write(dev, HW_PXP_CSC1_COEF2, csc1_coef[2]); + } else { + pxp_write(dev, HW_PXP_CSC1_COEF0, BM_PXP_CSC1_COEF0_BYPASS); + } + + if (!pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC2 RGB to YUV/YCbCr conversion is implemented as follows: + * + * |Y | |A1 A2 A3| |R| |D1| + * |Cb| = |B1 B2 B3| * |G| + |D2| + * |Cr| |C1 C2 C3| |B| |D3| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |Y | | 0.2568 0.5041 0.0979| |R| |16 | + * |Cb| = |-0.1482 -0.2910 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3678| |B| |128| + */ + static const u32 csc2_coef_bt601_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x081) | /* 0.5039 (-0.02 %) */ + BF_PXP_CSC2_COEF0_A1(0x041), /* 0.2539 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7db) | /* -0.1445 (+0.37 %) */ + BF_PXP_CSC2_COEF1_A3(0x019), /* 0.0977 (-0.02 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7b6), /* -0.2891 (+0.20 %) */ + BF_PXP_CSC2_COEF3_C2(0x7a2) | /* -0.3672 (+0.06 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7ee), /* -0.0703 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.601 full range: + * + * |Y | | 0.2990 0.5870 0.1140| |R| |0 | + * |Cb| = |-0.1687 -0.3313 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4187| |B| |128| + */ + static const u32 csc2_coef_bt601_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x096) | /* 0.5859 (-0.11 %) */ + BF_PXP_CSC2_COEF0_A1(0x04c), /* 0.2969 (-0.21 %) */ + BF_PXP_CSC2_COEF1_B1(0x7d5) | /* -0.1680 (+0.07 %) */ + BF_PXP_CSC2_COEF1_A3(0x01d), /* 0.1133 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7ac), /* -0.3281 (+0.32 %) */ + BF_PXP_CSC2_COEF3_C2(0x795) | /* -0.4180 (+0.07 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7ec), /* -0.0781 (+0.32 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 limited range: + * + * |Y | | 0.1826 0.6142 0.0620| |R| |16 | + * |Cb| = |-0.1007 -0.3385 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3990| |B| |128| + */ + static const u32 csc2_coef_rec709_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09d) | /* 0.6133 (-0.09 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e7) | /* -0.0977 (+0.30 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.34 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x79a) | /* -0.3984 (+0.05 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.12 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 full range: + * + * |Y | | 0.2126 0.7152 0.0722| |R| |0 | + * |Cb| = |-0.1146 -0.3854 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4542| |B| |128| + */ + static const u32 csc2_coef_rec709_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b7) | /* 0.7148 (-0.04 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.17 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.13 %) */ + BF_PXP_CSC2_COEF1_A3(0x012), /* 0.0703 (-0.19 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x78c) | /* -0.4531 (+0.11 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f5), /* -0.0430 (+0.28 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 limited range: + * + * |Y | | 0.2256 0.5823 0.0509| |R| |16 | + * |Cb| = |-0.1226 -0.3166 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.4039| |B| |128| + */ + static const u32 csc2_coef_bt2020_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x095) | /* 0.5820 (-0.03 %) */ + BF_PXP_CSC2_COEF0_A1(0x039), /* 0.2227 (-0.30 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e1) | /* -0.1211 (+0.15 %) */ + BF_PXP_CSC2_COEF1_A3(0x00d), /* 0.0508 (-0.01 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7af), /* -0.3164 (+0.02 %) */ + BF_PXP_CSC2_COEF3_C2(0x799) | /* -0.4023 (+0.16 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f7), /* -0.0352 (+0.02 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 full range: + * + * |Y | | 0.2627 0.6780 0.0593| |R| |0 | + * |Cb| = |-0.1396 -0.3604 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4598| |B| |128| + */ + static const u32 csc2_coef_bt2020_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0ad) | /* 0.6758 (-0.22 %) */ + BF_PXP_CSC2_COEF0_A1(0x043), /* 0.2617 (-0.10 %) */ + BF_PXP_CSC2_COEF1_B1(0x7dd) | /* -0.1367 (+0.29 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7a4), /* -0.3594 (+0.10 %) */ + BF_PXP_CSC2_COEF3_C2(0x78b) | /* -0.4570 (+0.28 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m limited range: + * + * |Y | | 0.1821 0.6020 0.0747| |R| |16 | + * |Cb| = |-0.1019 -0.3373 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3909| |B| |128| + */ + static const u32 csc2_coef_smpte240m_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09a) | /* 0.6016 (-0.05 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.24 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e6) | /* -0.1016 (+0.03 %) */ + BF_PXP_CSC2_COEF1_A3(0x013), /* 0.0742 (-0.05 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.14 %) */ + BF_PXP_CSC2_COEF3_C2(0x79c) | /* -0.3906 (+0.03 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f4), /* -0.0469 (+0.14 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m full range: + * + * |Y | | 0.2120 0.7010 0.0870| |R| |0 | + * |Cb| = |-0.1160 -0.3840 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4450| |B| |128| + */ + static const u32 csc2_coef_smpte240m_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b3) | /* 0.6992 (-0.18 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.11 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.27 %) */ + BF_PXP_CSC2_COEF1_A3(0x016), /* 0.0859 (-0.11 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.12 %) */ + BF_PXP_CSC2_COEF3_C2(0x78f) | /* -0.4414 (+0.36 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f2), /* -0.0547 (+0.03 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + const u32 *csc2_coef; + u32 csc2_ctrl; + + ycbcr_enc = ctx->q_data[V4L2_M2M_DST].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_DST].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt601_full; + else + csc2_coef = csc2_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_rec709_full; + else + csc2_coef = csc2_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt2020_full; + else + csc2_coef = csc2_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_smpte240m_full; + else + csc2_coef = csc2_coef_smpte240m_lim; + } + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV << + BP_PXP_CSC2_CTRL_CSC_MODE; + } else { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr << + BP_PXP_CSC2_CTRL_CSC_MODE; + } + + pxp_write(dev, HW_PXP_CSC2_CTRL, csc2_ctrl); + pxp_write(dev, HW_PXP_CSC2_COEF0, csc2_coef[0]); + pxp_write(dev, HW_PXP_CSC2_COEF1, csc2_coef[1]); + pxp_write(dev, HW_PXP_CSC2_COEF2, csc2_coef[2]); + pxp_write(dev, HW_PXP_CSC2_COEF3, csc2_coef[3]); + pxp_write(dev, HW_PXP_CSC2_COEF4, csc2_coef[4]); + pxp_write(dev, HW_PXP_CSC2_COEF5, csc2_coef[5]); + } else { + pxp_write(dev, HW_PXP_CSC2_CTRL, BM_PXP_CSC2_CTRL_BYPASS); + } +} + +static u32 pxp_imx6ull_data_path_ctrl0(struct pxp_ctx *ctx) +{ + u32 ctrl0; + + ctrl0 = 0; + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(3); + /* Bypass Dithering x3CH */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(3); + /* Select Rotation */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(0); + /* Bypass LUT */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(3); + /* Select CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(3); + /* Bypass Rotation 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(3); + + return ctrl0; +} + +static u32 pxp_imx7d_data_path_ctrl0(struct pxp_ctx *ctx) +{ + u32 ctrl0; + + ctrl0 = 0; + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(3); + /* Select Rotation 0 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(3); + /* Select MUX11 for Rotation 0 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(1); + /* Bypass LUT */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(3); + /* Select CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(3); + /* Select Composite Alpha Blending/Color Key 0 for CSC 2 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(1); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(3); + /* Bypass Rotation 1 */ + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(3); + ctrl0 |= BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(3); + + return ctrl0; +} + +static void pxp_set_data_path(struct pxp_ctx *ctx) +{ + struct pxp_dev *dev = ctx->dev; + u32 ctrl0; + u32 ctrl1; + + ctrl0 = dev->pdata->data_path_ctrl0(ctx); + + ctrl1 = 0; + ctrl1 |= BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(3); + ctrl1 |= BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(3); + + pxp_write(dev, HW_PXP_DATA_PATH_CTRL0, ctrl0); + pxp_write(dev, HW_PXP_DATA_PATH_CTRL1, ctrl1); +} + +static int pxp_start(struct pxp_ctx *ctx, struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + u32 src_width, src_height, src_stride, src_fourcc; + u32 dst_width, dst_height, dst_stride, dst_fourcc; + dma_addr_t p_in, p_out; + u32 ctrl, out_ctrl, out_buf, out_buf2, out_pitch, out_lrc, out_ps_ulc; + u32 out_ps_lrc; + u32 ps_ctrl, ps_buf, ps_ubuf, ps_vbuf, ps_pitch, ps_scale, ps_offset; + u32 as_ulc, as_lrc; + u32 y_size; + u32 decx, decy, xscale, yscale; + + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + src_width = ctx->q_data[V4L2_M2M_SRC].width; + dst_width = ctx->q_data[V4L2_M2M_DST].width; + src_height = ctx->q_data[V4L2_M2M_SRC].height; + dst_height = ctx->q_data[V4L2_M2M_DST].height; + src_stride = ctx->q_data[V4L2_M2M_SRC].bytesperline; + dst_stride = ctx->q_data[V4L2_M2M_DST].bytesperline; + src_fourcc = ctx->q_data[V4L2_M2M_SRC].fmt->fourcc; + dst_fourcc = ctx->q_data[V4L2_M2M_DST].fmt->fourcc; + + p_in = vb2_dma_contig_plane_dma_addr(&in_vb->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&out_vb->vb2_buf, 0); + + if (!p_in || !p_out) { + v4l2_err(&dev->v4l2_dev, + "Acquiring DMA addresses of buffers failed\n"); + return -EFAULT; + } + + out_vb->sequence = + get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE)->sequence++; + in_vb->sequence = q_data->sequence++; + out_vb->vb2_buf.timestamp = in_vb->vb2_buf.timestamp; + + if (in_vb->flags & V4L2_BUF_FLAG_TIMECODE) + out_vb->timecode = in_vb->timecode; + out_vb->field = in_vb->field; + out_vb->flags = in_vb->flags & + (V4L2_BUF_FLAG_TIMECODE | + V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + /* 8x8 block size */ + ctrl = BF_PXP_CTRL_VFLIP0(!!(ctx->mode & MEM2MEM_VFLIP)) | + BF_PXP_CTRL_HFLIP0(!!(ctx->mode & MEM2MEM_HFLIP)) | + BF_PXP_CTRL_ROTATE0(ctx->rotation); + /* Always write alpha value as V4L2_CID_ALPHA_COMPONENT */ + out_ctrl = BF_PXP_OUT_CTRL_ALPHA(ctx->alpha_component) | + BF_PXP_OUT_CTRL_ALPHA_OUTPUT(1) | + pxp_v4l2_pix_fmt_to_out_format(dst_fourcc); + out_buf = p_out; + + if (ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_90 || + ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_270) + swap(dst_width, dst_height); + + switch (dst_fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + out_buf2 = out_buf + dst_stride * dst_height; + break; + default: + out_buf2 = 0; + } + + out_pitch = BF_PXP_OUT_PITCH_PITCH(dst_stride); + out_lrc = BF_PXP_OUT_LRC_X(dst_width - 1) | + BF_PXP_OUT_LRC_Y(dst_height - 1); + /* PS covers whole output */ + out_ps_ulc = BF_PXP_OUT_PS_ULC_X(0) | BF_PXP_OUT_PS_ULC_Y(0); + out_ps_lrc = BF_PXP_OUT_PS_LRC_X(dst_width - 1) | + BF_PXP_OUT_PS_LRC_Y(dst_height - 1); + /* no AS */ + as_ulc = BF_PXP_OUT_AS_ULC_X(1) | BF_PXP_OUT_AS_ULC_Y(1); + as_lrc = BF_PXP_OUT_AS_LRC_X(0) | BF_PXP_OUT_AS_LRC_Y(0); + + decx = (src_width <= dst_width) ? 0 : ilog2(src_width / dst_width); + decy = (src_height <= dst_height) ? 0 : ilog2(src_height / dst_height); + ps_ctrl = BF_PXP_PS_CTRL_DECX(decx) | BF_PXP_PS_CTRL_DECY(decy) | + pxp_v4l2_pix_fmt_to_ps_format(src_fourcc); + ps_buf = p_in; + y_size = src_stride * src_height; + switch (src_fourcc) { + case V4L2_PIX_FMT_YUV420: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 4; + break; + case V4L2_PIX_FMT_YUV422P: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 2; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + ps_ubuf = ps_buf + y_size; + ps_vbuf = 0; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + ps_ubuf = 0; + /* In grayscale mode, ps_vbuf contents are reused as CbCr */ + ps_vbuf = 0x8080; + break; + default: + ps_ubuf = 0; + ps_vbuf = 0; + break; + } + ps_pitch = BF_PXP_PS_PITCH_PITCH(src_stride); + if (decx) { + xscale = (src_width >> decx) * 0x1000 / dst_width; + } else { + switch (src_fourcc) { + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUV420: + /* + * This avoids sampling past the right edge for + * horizontally chroma subsampled formats. + */ + xscale = (src_width - 2) * 0x1000 / (dst_width - 1); + break; + default: + xscale = (src_width - 1) * 0x1000 / (dst_width - 1); + break; + } + } + if (decy) + yscale = (src_height >> decy) * 0x1000 / dst_height; + else + yscale = (src_height - 1) * 0x1000 / (dst_height - 1); + ps_scale = BF_PXP_PS_SCALE_YSCALE(yscale) | + BF_PXP_PS_SCALE_XSCALE(xscale); + ps_offset = BF_PXP_PS_OFFSET_YOFFSET(0) | BF_PXP_PS_OFFSET_XOFFSET(0); + + pxp_write(dev, HW_PXP_CTRL, ctrl); + /* skip STAT */ + pxp_write(dev, HW_PXP_OUT_CTRL, out_ctrl); + pxp_write(dev, HW_PXP_OUT_BUF, out_buf); + pxp_write(dev, HW_PXP_OUT_BUF2, out_buf2); + pxp_write(dev, HW_PXP_OUT_PITCH, out_pitch); + pxp_write(dev, HW_PXP_OUT_LRC, out_lrc); + pxp_write(dev, HW_PXP_OUT_PS_ULC, out_ps_ulc); + pxp_write(dev, HW_PXP_OUT_PS_LRC, out_ps_lrc); + pxp_write(dev, HW_PXP_OUT_AS_ULC, as_ulc); + pxp_write(dev, HW_PXP_OUT_AS_LRC, as_lrc); + pxp_write(dev, HW_PXP_PS_CTRL, ps_ctrl); + pxp_write(dev, HW_PXP_PS_BUF, ps_buf); + pxp_write(dev, HW_PXP_PS_UBUF, ps_ubuf); + pxp_write(dev, HW_PXP_PS_VBUF, ps_vbuf); + pxp_write(dev, HW_PXP_PS_PITCH, ps_pitch); + pxp_write(dev, HW_PXP_PS_BACKGROUND_0, 0x00ffffff); + pxp_write(dev, HW_PXP_PS_SCALE, ps_scale); + pxp_write(dev, HW_PXP_PS_OFFSET, ps_offset); + /* disable processed surface color keying */ + pxp_write(dev, HW_PXP_PS_CLRKEYLOW_0, 0x00ffffff); + pxp_write(dev, HW_PXP_PS_CLRKEYHIGH_0, 0x00000000); + + /* disable alpha surface color keying */ + pxp_write(dev, HW_PXP_AS_CLRKEYLOW_0, 0x00ffffff); + pxp_write(dev, HW_PXP_AS_CLRKEYHIGH_0, 0x00000000); + + /* setup CSC */ + pxp_setup_csc(ctx); + + /* bypass LUT */ + pxp_write(dev, HW_PXP_LUT_CTRL, BM_PXP_LUT_CTRL_BYPASS); + + pxp_set_data_path(ctx); + + pxp_write(dev, HW_PXP_IRQ_MASK, 0xffff); + + /* ungate, enable PS/AS/OUT and PXP operation */ + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_IRQ_ENABLE); + pxp_write(dev, HW_PXP_CTRL_SET, + BM_PXP_CTRL_ENABLE | BM_PXP_CTRL_ENABLE_CSC2 | + BM_PXP_CTRL_ENABLE_ROTATE0 | BM_PXP_CTRL_ENABLE_PS_AS_OUT); + + return 0; +} + +static void pxp_job_finish(struct pxp_dev *dev) +{ + struct pxp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + + curr_ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + spin_lock_irqsave(&dev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&dev->irqlock, flags); + + dprintk(curr_ctx->dev, "Finishing transaction\n"); + v4l2_m2m_job_finish(dev->m2m_dev, curr_ctx->fh.m2m_ctx); +} + +/* + * mem2mem callbacks + */ +static void pxp_device_run(void *priv) +{ + struct pxp_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + pxp_start(ctx, src_buf, dst_buf); +} + +static int pxp_job_ready(void *priv) +{ + struct pxp_ctx *ctx = priv; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) < 1 || + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) < 1) { + dprintk(ctx->dev, "Not enough buffers available\n"); + return 0; + } + + return 1; +} + +static void pxp_job_abort(void *priv) +{ + struct pxp_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +/* + * interrupt handler + */ +static irqreturn_t pxp_irq_handler(int irq, void *dev_id) +{ + struct pxp_dev *dev = dev_id; + u32 stat; + + stat = pxp_read(dev, HW_PXP_STAT); + + if (stat & BM_PXP_STAT_IRQ0) { + /* we expect x = 0, y = height, irq0 = 1 */ + if (stat & ~(BM_PXP_STAT_BLOCKX | BM_PXP_STAT_BLOCKY | + BM_PXP_STAT_IRQ0)) + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + pxp_write(dev, HW_PXP_STAT_CLR, BM_PXP_STAT_IRQ0); + + pxp_job_finish(dev); + } else { + u32 irq = pxp_read(dev, HW_PXP_IRQ); + + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + dprintk(dev, "%s: irq = 0x%08x\n", __func__, irq); + + pxp_write(dev, HW_PXP_IRQ_CLR, irq); + } + + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int pxp_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + return 0; +} + +static int pxp_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct pxp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* + * Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int pxp_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int pxp_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int pxp_g_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct pxp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.bytesperline = q_data->bytesperline; + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = q_data->ycbcr_enc; + f->fmt.pix.quantization = q_data->quant; + + return 0; +} + +static int pxp_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static int pxp_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static inline u32 pxp_bytesperline(struct pxp_fmt *fmt, u32 width) +{ + switch (fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + return width; + default: + return (width * fmt->depth) >> 3; + } +} + +static inline u32 pxp_sizeimage(struct pxp_fmt *fmt, u32 width, u32 height) +{ + return (fmt->depth * width * height) >> 3; +} + +static int pxp_try_fmt(struct v4l2_format *f, struct pxp_fmt *fmt) +{ + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, ALIGN_W, + &f->fmt.pix.height, MIN_H, MAX_H, ALIGN_H, 0); + + f->fmt.pix.bytesperline = pxp_bytesperline(fmt, f->fmt.pix.width); + f->fmt.pix.sizeimage = pxp_sizeimage(fmt, f->fmt.pix.width, + f->fmt.pix.height); + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static void +pxp_fixup_colorimetry_cap(struct pxp_ctx *ctx, u32 dst_fourcc, + enum v4l2_ycbcr_encoding *ycbcr_enc, + enum v4l2_quantization *quantization) +{ + bool dst_is_yuv = pxp_v4l2_pix_fmt_is_yuv(dst_fourcc); + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) == + dst_is_yuv) { + /* + * There is no support for conversion between different YCbCr + * encodings or between RGB limited and full range. + */ + *ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + *quantization = ctx->q_data[V4L2_M2M_SRC].quant; + } else { + *ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(ctx->colorspace); + *quantization = V4L2_MAP_QUANTIZATION_DEFAULT(!dst_is_yuv, + ctx->colorspace, + *ycbcr_enc); + } +} + +static int pxp_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f->fmt.pix.pixelformat); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f->fmt.pix.pixelformat); + } + if (!(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + + pxp_fixup_colorimetry_cap(ctx, fmt->fourcc, + &f->fmt.pix.ycbcr_enc, + &f->fmt.pix.quantization); + + return pxp_try_fmt(f, fmt); +} + +static int pxp_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f->fmt.pix.pixelformat); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f->fmt.pix.pixelformat); + } + if (!(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + return pxp_try_fmt(f, fmt); +} + +static int pxp_s_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct pxp_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f->fmt.pix.pixelformat); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->bytesperline = f->fmt.pix.bytesperline; + q_data->sizeimage = f->fmt.pix.sizeimage; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int pxp_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->q_data[V4L2_M2M_DST].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_DST].quant = f->fmt.pix.quantization; + + return 0; +} + +static int pxp_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->q_data[V4L2_M2M_SRC].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_SRC].quant = f->fmt.pix.quantization; + + pxp_fixup_colorimetry_cap(ctx, ctx->q_data[V4L2_M2M_DST].fmt->fourcc, + &ctx->q_data[V4L2_M2M_DST].ycbcr_enc, + &ctx->q_data[V4L2_M2M_DST].quant); + + return 0; +} + +static int pxp_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + if (!find_format(fsize->pixel_format)) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = MIN_W; + fsize->stepwise.max_width = MAX_W; + fsize->stepwise.step_width = 1 << ALIGN_W; + fsize->stepwise.min_height = MIN_H; + fsize->stepwise.max_height = MAX_H; + fsize->stepwise.step_height = 1 << ALIGN_H; + + return 0; +} + +static u8 pxp_degrees_to_rot_mode(u32 degrees) +{ + switch (degrees) { + case 90: + return BV_PXP_CTRL_ROTATE0__ROT_90; + case 180: + return BV_PXP_CTRL_ROTATE0__ROT_180; + case 270: + return BV_PXP_CTRL_ROTATE0__ROT_270; + case 0: + default: + return BV_PXP_CTRL_ROTATE0__ROT_0; + } +} + +static int pxp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct pxp_ctx *ctx = + container_of(ctrl->handler, struct pxp_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_HFLIP; + else + ctx->mode &= ~MEM2MEM_HFLIP; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_VFLIP; + else + ctx->mode &= ~MEM2MEM_VFLIP; + break; + + case V4L2_CID_ROTATE: + ctx->rotation = pxp_degrees_to_rot_mode(ctrl->val); + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->alpha_component = ctrl->val; + break; + + default: + v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops pxp_ctrl_ops = { + .s_ctrl = pxp_s_ctrl, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_enum_fmt_vid_cap = pxp_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = pxp_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = pxp_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = pxp_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = pxp_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = pxp_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = pxp_s_fmt_vid_out, + + .vidioc_enum_framesizes = pxp_enum_framesizes, + + .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_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Queue operations + */ +static int pxp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(vq); + struct pxp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + size = q_data->sizeimage; + + *nbuffers = count; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int pxp_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + 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) { + dprintk(dev, "%s field isn't supported\n", __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void pxp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int pxp_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct pxp_q_data *q_data = get_q_data(ctx, q->type); + + q_data->sequence = 0; + return 0; +} + +static void pxp_stop_streaming(struct vb2_queue *q) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + unsigned long flags; + + 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 == NULL) + return; + spin_lock_irqsave(&ctx->dev->irqlock, flags); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->dev->irqlock, flags); + } +} + +static const struct vb2_ops pxp_qops = { + .queue_setup = pxp_queue_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .start_streaming = pxp_start_streaming, + .stop_streaming = pxp_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct pxp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &pxp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = ctx->dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &pxp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int pxp_open(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, + 0, 255, 1, 255); + if (hdl->error) { + rc = hdl->error; + v4l2_ctrl_handler_free(hdl); + kfree(ctx); + goto open_unlock; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[0]; + ctx->q_data[V4L2_M2M_SRC].width = 640; + ctx->q_data[V4L2_M2M_SRC].height = 480; + ctx->q_data[V4L2_M2M_SRC].bytesperline = + pxp_bytesperline(&formats[0], 640); + ctx->q_data[V4L2_M2M_SRC].sizeimage = + pxp_sizeimage(&formats[0], 640, 480); + ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; + ctx->colorspace = V4L2_COLORSPACE_REC709; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh); + atomic_inc(&dev->num_inst); + + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + +open_unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int pxp_release(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = file2ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->dev_mutex); + kfree(ctx); + + atomic_dec(&dev->num_inst); + + return 0; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device pxp_videodev = { + .name = MEM2MEM_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &pxp_fops, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, + .ioctl_ops = &pxp_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = pxp_device_run, + .job_ready = pxp_job_ready, + .job_abort = pxp_job_abort, +}; + +static int pxp_soft_reset(struct pxp_dev *dev) +{ + int ret; + u32 val; + + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_SFTRST); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_CLKGATE); + + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); + + ret = regmap_read_poll_timeout(dev->regmap, HW_PXP_CTRL, val, + val & BM_PXP_CTRL_CLKGATE, 0, 100); + if (ret < 0) + return ret; + + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_SFTRST); + pxp_write(dev, HW_PXP_CTRL_CLR, BM_PXP_CTRL_CLKGATE); + + return 0; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxp_dev *dev; + struct video_device *vfd; + u32 hw_version; + int irq; + int ret; + void __iomem *mmio; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->pdata = of_device_get_match_data(&pdev->dev); + + dev->clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + dev_err(&pdev->dev, "Failed to get clk: %d\n", ret); + return ret; + } + + mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + dev->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, + &pxp_regmap_config); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + spin_lock_init(&dev->irqlock); + + ret = devm_request_irq(&pdev->dev, irq, pxp_irq_handler, 0, + dev_name(&pdev->dev), dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + + ret = pxp_soft_reset(dev); + if (ret < 0) { + dev_err(&pdev->dev, "PXP reset timeout: %d\n", ret); + goto err_clk; + } + + hw_version = pxp_read(dev, HW_PXP_VERSION); + dev_dbg(&pdev->dev, "PXP Version %u.%u\n", + PXP_VERSION_MAJOR(hw_version), PXP_VERSION_MINOR(hw_version)); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto err_clk; + + atomic_set(&dev->num_inst, 0); + mutex_init(&dev->dev_mutex); + + dev->vfd = pxp_videodev; + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", pxp_videodev.name); + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto err_v4l2; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_m2m; + } + +#ifdef CONFIG_MEDIA_CONTROLLER + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, MEM2MEM_NAME, sizeof(dev->mdev.model)); + media_device_init(&dev->mdev); + dev->v4l2_dev.mdev = &dev->mdev; + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize media device\n"); + goto err_vfd; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register media device\n"); + goto err_m2m_mc; + } +#endif + + return 0; + +#ifdef CONFIG_MEDIA_CONTROLLER +err_m2m_mc: + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +err_vfd: + video_unregister_device(vfd); +#endif +err_m2m: + v4l2_m2m_release(dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); +err_clk: + clk_disable_unprepare(dev->clk); + + return ret; +} + +static void pxp_remove(struct platform_device *pdev) +{ + struct pxp_dev *dev = platform_get_drvdata(pdev); + + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_CLKGATE); + pxp_write(dev, HW_PXP_CTRL_SET, BM_PXP_CTRL_SFTRST); + + clk_disable_unprepare(dev->clk); + + v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME); + +#ifdef CONFIG_MEDIA_CONTROLLER + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->m2m_dev); +#endif + video_unregister_device(&dev->vfd); + v4l2_m2m_release(dev->m2m_dev); + v4l2_device_unregister(&dev->v4l2_dev); +} + +static const struct pxp_pdata pxp_imx6ull_pdata = { + .data_path_ctrl0 = pxp_imx6ull_data_path_ctrl0, +}; + +static const struct pxp_pdata pxp_imx7d_pdata = { + .data_path_ctrl0 = pxp_imx7d_data_path_ctrl0, +}; + +static const struct of_device_id pxp_dt_ids[] = { + { .compatible = "fsl,imx6ull-pxp", .data = &pxp_imx6ull_pdata }, + { .compatible = "fsl,imx7d-pxp", .data = &pxp_imx7d_pdata }, + { }, +}; +MODULE_DEVICE_TABLE(of, pxp_dt_ids); + +static struct platform_driver pxp_driver = { + .probe = pxp_probe, + .remove_new = pxp_remove, + .driver = { + .name = MEM2MEM_NAME, + .of_match_table = pxp_dt_ids, + }, +}; + +module_platform_driver(pxp_driver); + +MODULE_DESCRIPTION("i.MX PXP mem2mem scaler/CSC/rotator"); +MODULE_AUTHOR("Philipp Zabel <kernel@pengutronix.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/imx-pxp.h b/drivers/media/platform/nxp/imx-pxp.h new file mode 100644 index 0000000000..44f95c749d --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.h @@ -0,0 +1,1685 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Freescale PXP Register Definitions + * + * based on pxp_dma_v3.h, Xml Revision: 1.77, Template Revision: 1.3 + * + * Copyright 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#ifndef __IMX_PXP_H__ +#define __IMX_PXP_H__ + +#define HW_PXP_CTRL (0x00000000) +#define HW_PXP_CTRL_SET (0x00000004) +#define HW_PXP_CTRL_CLR (0x00000008) +#define HW_PXP_CTRL_TOG (0x0000000c) + +#define BM_PXP_CTRL_SFTRST 0x80000000 +#define BF_PXP_CTRL_SFTRST(v) \ + (((v) << 31) & BM_PXP_CTRL_SFTRST) +#define BM_PXP_CTRL_CLKGATE 0x40000000 +#define BF_PXP_CTRL_CLKGATE(v) \ + (((v) << 30) & BM_PXP_CTRL_CLKGATE) +#define BM_PXP_CTRL_RSVD4 0x20000000 +#define BF_PXP_CTRL_RSVD4(v) \ + (((v) << 29) & BM_PXP_CTRL_RSVD4) +#define BM_PXP_CTRL_EN_REPEAT 0x10000000 +#define BF_PXP_CTRL_EN_REPEAT(v) \ + (((v) << 28) & BM_PXP_CTRL_EN_REPEAT) +#define BM_PXP_CTRL_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL_ENABLE_ROTATE1) +#define BM_PXP_CTRL_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL_ENABLE_ROTATE0) +#define BM_PXP_CTRL_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL_ENABLE_LUT) +#define BM_PXP_CTRL_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL_ENABLE_CSC2) +#define BM_PXP_CTRL_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL_BLOCK_SIZE) +#define BV_PXP_CTRL_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL_RSVD1 0x00400000 +#define BF_PXP_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_CTRL_RSVD1) +#define BM_PXP_CTRL_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL_ENABLE_ALPHA_B) +#define BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL_ENABLE_WFE_B) +#define BM_PXP_CTRL_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL_ENABLE_WFE_A) +#define BM_PXP_CTRL_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL_ENABLE_DITHER) +#define BM_PXP_CTRL_ENABLE_PS_AS_OUT 0x00010000 +#define BF_PXP_CTRL_ENABLE_PS_AS_OUT(v) \ + (((v) << 16) & BM_PXP_CTRL_ENABLE_PS_AS_OUT) +#define BM_PXP_CTRL_VFLIP1 0x00008000 +#define BF_PXP_CTRL_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL_VFLIP1) +#define BM_PXP_CTRL_HFLIP1 0x00004000 +#define BF_PXP_CTRL_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL_HFLIP1) +#define BP_PXP_CTRL_ROTATE1 12 +#define BM_PXP_CTRL_ROTATE1 0x00003000 +#define BF_PXP_CTRL_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL_ROTATE1) +#define BV_PXP_CTRL_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL_VFLIP0 0x00000800 +#define BF_PXP_CTRL_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL_VFLIP0) +#define BM_PXP_CTRL_HFLIP0 0x00000400 +#define BF_PXP_CTRL_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL_HFLIP0) +#define BP_PXP_CTRL_ROTATE0 8 +#define BM_PXP_CTRL_ROTATE0 0x00000300 +#define BF_PXP_CTRL_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL_ROTATE0) +#define BV_PXP_CTRL_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL_RSVD0 6 +#define BM_PXP_CTRL_RSVD0 0x000000C0 +#define BF_PXP_CTRL_RSVD0(v) \ + (((v) << 6) & BM_PXP_CTRL_RSVD0) +#define BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP 0x00000020 +#define BF_PXP_CTRL_HANDSHAKE_ABORT_SKIP(v) \ + (((v) << 5) & BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP) +#define BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE 0x00000010 +#define BF_PXP_CTRL_ENABLE_LCD0_HANDSHAKE(v) \ + (((v) << 4) & BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE) +#define BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE 0x00000008 +#define BF_PXP_CTRL_LUT_DMA_IRQ_ENABLE(v) \ + (((v) << 3) & BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE) +#define BM_PXP_CTRL_NEXT_IRQ_ENABLE 0x00000004 +#define BF_PXP_CTRL_NEXT_IRQ_ENABLE(v) \ + (((v) << 2) & BM_PXP_CTRL_NEXT_IRQ_ENABLE) +#define BM_PXP_CTRL_IRQ_ENABLE 0x00000002 +#define BF_PXP_CTRL_IRQ_ENABLE(v) \ + (((v) << 1) & BM_PXP_CTRL_IRQ_ENABLE) +#define BM_PXP_CTRL_ENABLE 0x00000001 +#define BF_PXP_CTRL_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL_ENABLE) + +#define HW_PXP_STAT (0x00000010) +#define HW_PXP_STAT_SET (0x00000014) +#define HW_PXP_STAT_CLR (0x00000018) +#define HW_PXP_STAT_TOG (0x0000001c) + +#define BP_PXP_STAT_BLOCKX 24 +#define BM_PXP_STAT_BLOCKX 0xFF000000 +#define BF_PXP_STAT_BLOCKX(v) \ + (((v) << 24) & BM_PXP_STAT_BLOCKX) +#define BP_PXP_STAT_BLOCKY 16 +#define BM_PXP_STAT_BLOCKY 0x00FF0000 +#define BF_PXP_STAT_BLOCKY(v) \ + (((v) << 16) & BM_PXP_STAT_BLOCKY) +#define BP_PXP_STAT_AXI_ERROR_ID_1 12 +#define BM_PXP_STAT_AXI_ERROR_ID_1 0x0000F000 +#define BF_PXP_STAT_AXI_ERROR_ID_1(v) \ + (((v) << 12) & BM_PXP_STAT_AXI_ERROR_ID_1) +#define BM_PXP_STAT_RSVD2 0x00000800 +#define BF_PXP_STAT_RSVD2(v) \ + (((v) << 11) & BM_PXP_STAT_RSVD2) +#define BM_PXP_STAT_AXI_READ_ERROR_1 0x00000400 +#define BF_PXP_STAT_AXI_READ_ERROR_1(v) \ + (((v) << 10) & BM_PXP_STAT_AXI_READ_ERROR_1) +#define BM_PXP_STAT_AXI_WRITE_ERROR_1 0x00000200 +#define BF_PXP_STAT_AXI_WRITE_ERROR_1(v) \ + (((v) << 9) & BM_PXP_STAT_AXI_WRITE_ERROR_1) +#define BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ 0x00000100 +#define BF_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ(v) \ + (((v) << 8) & BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ) +#define BP_PXP_STAT_AXI_ERROR_ID_0 4 +#define BM_PXP_STAT_AXI_ERROR_ID_0 0x000000F0 +#define BF_PXP_STAT_AXI_ERROR_ID_0(v) \ + (((v) << 4) & BM_PXP_STAT_AXI_ERROR_ID_0) +#define BM_PXP_STAT_NEXT_IRQ 0x00000008 +#define BF_PXP_STAT_NEXT_IRQ(v) \ + (((v) << 3) & BM_PXP_STAT_NEXT_IRQ) +#define BM_PXP_STAT_AXI_READ_ERROR_0 0x00000004 +#define BF_PXP_STAT_AXI_READ_ERROR_0(v) \ + (((v) << 2) & BM_PXP_STAT_AXI_READ_ERROR_0) +#define BM_PXP_STAT_AXI_WRITE_ERROR_0 0x00000002 +#define BF_PXP_STAT_AXI_WRITE_ERROR_0(v) \ + (((v) << 1) & BM_PXP_STAT_AXI_WRITE_ERROR_0) +#define BM_PXP_STAT_IRQ0 0x00000001 +#define BF_PXP_STAT_IRQ0(v) \ + (((v) << 0) & BM_PXP_STAT_IRQ0) + +#define HW_PXP_OUT_CTRL (0x00000020) +#define HW_PXP_OUT_CTRL_SET (0x00000024) +#define HW_PXP_OUT_CTRL_CLR (0x00000028) +#define HW_PXP_OUT_CTRL_TOG (0x0000002c) + +#define BP_PXP_OUT_CTRL_ALPHA 24 +#define BM_PXP_OUT_CTRL_ALPHA 0xFF000000 +#define BF_PXP_OUT_CTRL_ALPHA(v) \ + (((v) << 24) & BM_PXP_OUT_CTRL_ALPHA) +#define BM_PXP_OUT_CTRL_ALPHA_OUTPUT 0x00800000 +#define BF_PXP_OUT_CTRL_ALPHA_OUTPUT(v) \ + (((v) << 23) & BM_PXP_OUT_CTRL_ALPHA_OUTPUT) +#define BP_PXP_OUT_CTRL_RSVD1 10 +#define BM_PXP_OUT_CTRL_RSVD1 0x007FFC00 +#define BF_PXP_OUT_CTRL_RSVD1(v) \ + (((v) << 10) & BM_PXP_OUT_CTRL_RSVD1) +#define BP_PXP_OUT_CTRL_INTERLACED_OUTPUT 8 +#define BM_PXP_OUT_CTRL_INTERLACED_OUTPUT 0x00000300 +#define BF_PXP_OUT_CTRL_INTERLACED_OUTPUT(v) \ + (((v) << 8) & BM_PXP_OUT_CTRL_INTERLACED_OUTPUT) +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__PROGRESSIVE 0x0 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD0 0x1 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD1 0x2 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__INTERLACED 0x3 +#define BP_PXP_OUT_CTRL_RSVD0 5 +#define BM_PXP_OUT_CTRL_RSVD0 0x000000E0 +#define BF_PXP_OUT_CTRL_RSVD0(v) \ + (((v) << 5) & BM_PXP_OUT_CTRL_RSVD0) +#define BP_PXP_OUT_CTRL_FORMAT 0 +#define BM_PXP_OUT_CTRL_FORMAT 0x0000001F +#define BF_PXP_OUT_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_OUT_CTRL_FORMAT) +#define BV_PXP_OUT_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888P 0x5 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_OUT_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_OUT_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_OUT_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_OUT_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_OUT_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_OUT_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_OUT_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_OUT_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P420 0x1B + +#define HW_PXP_OUT_BUF (0x00000030) + +#define BP_PXP_OUT_BUF_ADDR 0 +#define BM_PXP_OUT_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF_ADDR(v) (v) + +#define HW_PXP_OUT_BUF2 (0x00000040) + +#define BP_PXP_OUT_BUF2_ADDR 0 +#define BM_PXP_OUT_BUF2_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF2_ADDR(v) (v) + +#define HW_PXP_OUT_PITCH (0x00000050) + +#define BP_PXP_OUT_PITCH_RSVD 16 +#define BM_PXP_OUT_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_OUT_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_OUT_PITCH_RSVD) +#define BP_PXP_OUT_PITCH_PITCH 0 +#define BM_PXP_OUT_PITCH_PITCH 0x0000FFFF +#define BF_PXP_OUT_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_OUT_PITCH_PITCH) + +#define HW_PXP_OUT_LRC (0x00000060) + +#define BP_PXP_OUT_LRC_RSVD1 30 +#define BM_PXP_OUT_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_LRC_RSVD1) +#define BP_PXP_OUT_LRC_X 16 +#define BM_PXP_OUT_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_LRC_X) +#define BP_PXP_OUT_LRC_RSVD0 14 +#define BM_PXP_OUT_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_LRC_RSVD0) +#define BP_PXP_OUT_LRC_Y 0 +#define BM_PXP_OUT_LRC_Y 0x00003FFF +#define BF_PXP_OUT_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_LRC_Y) + +#define HW_PXP_OUT_PS_ULC (0x00000070) + +#define BP_PXP_OUT_PS_ULC_RSVD1 30 +#define BM_PXP_OUT_PS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_ULC_RSVD1) +#define BP_PXP_OUT_PS_ULC_X 16 +#define BM_PXP_OUT_PS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_ULC_X) +#define BP_PXP_OUT_PS_ULC_RSVD0 14 +#define BM_PXP_OUT_PS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_ULC_RSVD0) +#define BP_PXP_OUT_PS_ULC_Y 0 +#define BM_PXP_OUT_PS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_PS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_ULC_Y) + +#define HW_PXP_OUT_PS_LRC (0x00000080) + +#define BP_PXP_OUT_PS_LRC_RSVD1 30 +#define BM_PXP_OUT_PS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_LRC_RSVD1) +#define BP_PXP_OUT_PS_LRC_X 16 +#define BM_PXP_OUT_PS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_LRC_X) +#define BP_PXP_OUT_PS_LRC_RSVD0 14 +#define BM_PXP_OUT_PS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_LRC_RSVD0) +#define BP_PXP_OUT_PS_LRC_Y 0 +#define BM_PXP_OUT_PS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_PS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_LRC_Y) + +#define HW_PXP_OUT_AS_ULC (0x00000090) + +#define BP_PXP_OUT_AS_ULC_RSVD1 30 +#define BM_PXP_OUT_AS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_ULC_RSVD1) +#define BP_PXP_OUT_AS_ULC_X 16 +#define BM_PXP_OUT_AS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_ULC_X) +#define BP_PXP_OUT_AS_ULC_RSVD0 14 +#define BM_PXP_OUT_AS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_ULC_RSVD0) +#define BP_PXP_OUT_AS_ULC_Y 0 +#define BM_PXP_OUT_AS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_AS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_ULC_Y) + +#define HW_PXP_OUT_AS_LRC (0x000000a0) + +#define BP_PXP_OUT_AS_LRC_RSVD1 30 +#define BM_PXP_OUT_AS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_LRC_RSVD1) +#define BP_PXP_OUT_AS_LRC_X 16 +#define BM_PXP_OUT_AS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_LRC_X) +#define BP_PXP_OUT_AS_LRC_RSVD0 14 +#define BM_PXP_OUT_AS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_LRC_RSVD0) +#define BP_PXP_OUT_AS_LRC_Y 0 +#define BM_PXP_OUT_AS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_AS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_LRC_Y) + +#define HW_PXP_PS_CTRL (0x000000b0) +#define HW_PXP_PS_CTRL_SET (0x000000b4) +#define HW_PXP_PS_CTRL_CLR (0x000000b8) +#define HW_PXP_PS_CTRL_TOG (0x000000bc) + +#define BP_PXP_PS_CTRL_RSVD1 12 +#define BM_PXP_PS_CTRL_RSVD1 0xFFFFF000 +#define BF_PXP_PS_CTRL_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_CTRL_RSVD1) +#define BP_PXP_PS_CTRL_DECX 10 +#define BM_PXP_PS_CTRL_DECX 0x00000C00 +#define BF_PXP_PS_CTRL_DECX(v) \ + (((v) << 10) & BM_PXP_PS_CTRL_DECX) +#define BV_PXP_PS_CTRL_DECX__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECX__DECX2 0x1 +#define BV_PXP_PS_CTRL_DECX__DECX4 0x2 +#define BV_PXP_PS_CTRL_DECX__DECX8 0x3 +#define BP_PXP_PS_CTRL_DECY 8 +#define BM_PXP_PS_CTRL_DECY 0x00000300 +#define BF_PXP_PS_CTRL_DECY(v) \ + (((v) << 8) & BM_PXP_PS_CTRL_DECY) +#define BV_PXP_PS_CTRL_DECY__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECY__DECY2 0x1 +#define BV_PXP_PS_CTRL_DECY__DECY4 0x2 +#define BV_PXP_PS_CTRL_DECY__DECY8 0x3 +#define BM_PXP_PS_CTRL_RSVD0 0x00000080 +#define BF_PXP_PS_CTRL_RSVD0(v) \ + (((v) << 7) & BM_PXP_PS_CTRL_RSVD0) +#define BM_PXP_PS_CTRL_WB_SWAP 0x00000040 +#define BF_PXP_PS_CTRL_WB_SWAP(v) \ + (((v) << 6) & BM_PXP_PS_CTRL_WB_SWAP) +#define BP_PXP_PS_CTRL_FORMAT 0 +#define BM_PXP_PS_CTRL_FORMAT 0x0000003F +#define BF_PXP_PS_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_PS_CTRL_FORMAT) +#define BV_PXP_PS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_PS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_PS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_PS_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_PS_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_PS_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_PS_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_PS_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_PS_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_PS_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_PS_CTRL_FORMAT__YVU2P420 0x1B +#define BV_PXP_PS_CTRL_FORMAT__YUV422 0x1E +#define BV_PXP_PS_CTRL_FORMAT__YUV420 0x1F + +#define HW_PXP_PS_BUF (0x000000c0) + +#define BP_PXP_PS_BUF_ADDR 0 +#define BM_PXP_PS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_BUF_ADDR(v) (v) + +#define HW_PXP_PS_UBUF (0x000000d0) + +#define BP_PXP_PS_UBUF_ADDR 0 +#define BM_PXP_PS_UBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_UBUF_ADDR(v) (v) + +#define HW_PXP_PS_VBUF (0x000000e0) + +#define BP_PXP_PS_VBUF_ADDR 0 +#define BM_PXP_PS_VBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_VBUF_ADDR(v) (v) + +#define HW_PXP_PS_PITCH (0x000000f0) + +#define BP_PXP_PS_PITCH_RSVD 16 +#define BM_PXP_PS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_PS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_PS_PITCH_RSVD) +#define BP_PXP_PS_PITCH_PITCH 0 +#define BM_PXP_PS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_PS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_PS_PITCH_PITCH) + +#define HW_PXP_PS_BACKGROUND_0 (0x00000100) + +#define BP_PXP_PS_BACKGROUND_0_RSVD 24 +#define BM_PXP_PS_BACKGROUND_0_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_0_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_0_RSVD) +#define BP_PXP_PS_BACKGROUND_0_COLOR 0 +#define BM_PXP_PS_BACKGROUND_0_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_0_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_0_COLOR) + +#define HW_PXP_PS_SCALE (0x00000110) + +#define BM_PXP_PS_SCALE_RSVD2 0x80000000 +#define BF_PXP_PS_SCALE_RSVD2(v) \ + (((v) << 31) & BM_PXP_PS_SCALE_RSVD2) +#define BP_PXP_PS_SCALE_YSCALE 16 +#define BM_PXP_PS_SCALE_YSCALE 0x7FFF0000 +#define BF_PXP_PS_SCALE_YSCALE(v) \ + (((v) << 16) & BM_PXP_PS_SCALE_YSCALE) +#define BM_PXP_PS_SCALE_RSVD1 0x00008000 +#define BF_PXP_PS_SCALE_RSVD1(v) \ + (((v) << 15) & BM_PXP_PS_SCALE_RSVD1) +#define BP_PXP_PS_SCALE_XSCALE 0 +#define BM_PXP_PS_SCALE_XSCALE 0x00007FFF +#define BF_PXP_PS_SCALE_XSCALE(v) \ + (((v) << 0) & BM_PXP_PS_SCALE_XSCALE) + +#define HW_PXP_PS_OFFSET (0x00000120) + +#define BP_PXP_PS_OFFSET_RSVD2 28 +#define BM_PXP_PS_OFFSET_RSVD2 0xF0000000 +#define BF_PXP_PS_OFFSET_RSVD2(v) \ + (((v) << 28) & BM_PXP_PS_OFFSET_RSVD2) +#define BP_PXP_PS_OFFSET_YOFFSET 16 +#define BM_PXP_PS_OFFSET_YOFFSET 0x0FFF0000 +#define BF_PXP_PS_OFFSET_YOFFSET(v) \ + (((v) << 16) & BM_PXP_PS_OFFSET_YOFFSET) +#define BP_PXP_PS_OFFSET_RSVD1 12 +#define BM_PXP_PS_OFFSET_RSVD1 0x0000F000 +#define BF_PXP_PS_OFFSET_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_OFFSET_RSVD1) +#define BP_PXP_PS_OFFSET_XOFFSET 0 +#define BM_PXP_PS_OFFSET_XOFFSET 0x00000FFF +#define BF_PXP_PS_OFFSET_XOFFSET(v) \ + (((v) << 0) & BM_PXP_PS_OFFSET_XOFFSET) + +#define HW_PXP_PS_CLRKEYLOW_0 (0x00000130) + +#define BP_PXP_PS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_0 (0x00000140) + +#define BP_PXP_PS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_AS_CTRL (0x00000150) + +#define BP_PXP_AS_CTRL_RSVD1 22 +#define BM_PXP_AS_CTRL_RSVD1 0xFFC00000 +#define BF_PXP_AS_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_AS_CTRL_RSVD1) +#define BM_PXP_AS_CTRL_ALPHA1_INVERT 0x00200000 +#define BF_PXP_AS_CTRL_ALPHA1_INVERT(v) \ + (((v) << 21) & BM_PXP_AS_CTRL_ALPHA1_INVERT) +#define BM_PXP_AS_CTRL_ALPHA0_INVERT 0x00100000 +#define BF_PXP_AS_CTRL_ALPHA0_INVERT(v) \ + (((v) << 20) & BM_PXP_AS_CTRL_ALPHA0_INVERT) +#define BP_PXP_AS_CTRL_ROP 16 +#define BM_PXP_AS_CTRL_ROP 0x000F0000 +#define BF_PXP_AS_CTRL_ROP(v) \ + (((v) << 16) & BM_PXP_AS_CTRL_ROP) +#define BV_PXP_AS_CTRL_ROP__MASKAS 0x0 +#define BV_PXP_AS_CTRL_ROP__MASKNOTAS 0x1 +#define BV_PXP_AS_CTRL_ROP__MASKASNOT 0x2 +#define BV_PXP_AS_CTRL_ROP__MERGEAS 0x3 +#define BV_PXP_AS_CTRL_ROP__MERGENOTAS 0x4 +#define BV_PXP_AS_CTRL_ROP__MERGEASNOT 0x5 +#define BV_PXP_AS_CTRL_ROP__NOTCOPYAS 0x6 +#define BV_PXP_AS_CTRL_ROP__NOT 0x7 +#define BV_PXP_AS_CTRL_ROP__NOTMASKAS 0x8 +#define BV_PXP_AS_CTRL_ROP__NOTMERGEAS 0x9 +#define BV_PXP_AS_CTRL_ROP__XORAS 0xA +#define BV_PXP_AS_CTRL_ROP__NOTXORAS 0xB +#define BP_PXP_AS_CTRL_ALPHA 8 +#define BM_PXP_AS_CTRL_ALPHA 0x0000FF00 +#define BF_PXP_AS_CTRL_ALPHA(v) \ + (((v) << 8) & BM_PXP_AS_CTRL_ALPHA) +#define BP_PXP_AS_CTRL_FORMAT 4 +#define BM_PXP_AS_CTRL_FORMAT 0x000000F0 +#define BF_PXP_AS_CTRL_FORMAT(v) \ + (((v) << 4) & BM_PXP_AS_CTRL_FORMAT) +#define BV_PXP_AS_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_AS_CTRL_FORMAT__RGBA8888 0x1 +#define BV_PXP_AS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_AS_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_AS_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_AS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_AS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_AS_CTRL_FORMAT__RGB565 0xE +#define BM_PXP_AS_CTRL_ENABLE_COLORKEY 0x00000008 +#define BF_PXP_AS_CTRL_ENABLE_COLORKEY(v) \ + (((v) << 3) & BM_PXP_AS_CTRL_ENABLE_COLORKEY) +#define BP_PXP_AS_CTRL_ALPHA_CTRL 1 +#define BM_PXP_AS_CTRL_ALPHA_CTRL 0x00000006 +#define BF_PXP_AS_CTRL_ALPHA_CTRL(v) \ + (((v) << 1) & BM_PXP_AS_CTRL_ALPHA_CTRL) +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Embedded 0x0 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Override 0x1 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Multiply 0x2 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__ROPs 0x3 +#define BM_PXP_AS_CTRL_RSVD0 0x00000001 +#define BF_PXP_AS_CTRL_RSVD0(v) \ + (((v) << 0) & BM_PXP_AS_CTRL_RSVD0) + +#define HW_PXP_AS_BUF (0x00000160) + +#define BP_PXP_AS_BUF_ADDR 0 +#define BM_PXP_AS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_AS_BUF_ADDR(v) (v) + +#define HW_PXP_AS_PITCH (0x00000170) + +#define BP_PXP_AS_PITCH_RSVD 16 +#define BM_PXP_AS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_AS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_AS_PITCH_RSVD) +#define BP_PXP_AS_PITCH_PITCH 0 +#define BM_PXP_AS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_AS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_AS_PITCH_PITCH) + +#define HW_PXP_AS_CLRKEYLOW_0 (0x00000180) + +#define BP_PXP_AS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_0 (0x00000190) + +#define BP_PXP_AS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_CSC1_COEF0 (0x000001a0) + +#define BM_PXP_CSC1_COEF0_YCBCR_MODE 0x80000000 +#define BF_PXP_CSC1_COEF0_YCBCR_MODE(v) \ + (((v) << 31) & BM_PXP_CSC1_COEF0_YCBCR_MODE) +#define BM_PXP_CSC1_COEF0_BYPASS 0x40000000 +#define BF_PXP_CSC1_COEF0_BYPASS(v) \ + (((v) << 30) & BM_PXP_CSC1_COEF0_BYPASS) +#define BM_PXP_CSC1_COEF0_RSVD1 0x20000000 +#define BF_PXP_CSC1_COEF0_RSVD1(v) \ + (((v) << 29) & BM_PXP_CSC1_COEF0_RSVD1) +#define BP_PXP_CSC1_COEF0_C0 18 +#define BM_PXP_CSC1_COEF0_C0 0x1FFC0000 +#define BF_PXP_CSC1_COEF0_C0(v) \ + (((v) << 18) & BM_PXP_CSC1_COEF0_C0) +#define BP_PXP_CSC1_COEF0_UV_OFFSET 9 +#define BM_PXP_CSC1_COEF0_UV_OFFSET 0x0003FE00 +#define BF_PXP_CSC1_COEF0_UV_OFFSET(v) \ + (((v) << 9) & BM_PXP_CSC1_COEF0_UV_OFFSET) +#define BP_PXP_CSC1_COEF0_Y_OFFSET 0 +#define BM_PXP_CSC1_COEF0_Y_OFFSET 0x000001FF +#define BF_PXP_CSC1_COEF0_Y_OFFSET(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF0_Y_OFFSET) + +#define HW_PXP_CSC1_COEF1 (0x000001b0) + +#define BP_PXP_CSC1_COEF1_RSVD1 27 +#define BM_PXP_CSC1_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF1_RSVD1) +#define BP_PXP_CSC1_COEF1_C1 16 +#define BM_PXP_CSC1_COEF1_C1 0x07FF0000 +#define BF_PXP_CSC1_COEF1_C1(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF1_C1) +#define BP_PXP_CSC1_COEF1_RSVD0 11 +#define BM_PXP_CSC1_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF1_RSVD0) +#define BP_PXP_CSC1_COEF1_C4 0 +#define BM_PXP_CSC1_COEF1_C4 0x000007FF +#define BF_PXP_CSC1_COEF1_C4(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF1_C4) + +#define HW_PXP_CSC1_COEF2 (0x000001c0) + +#define BP_PXP_CSC1_COEF2_RSVD1 27 +#define BM_PXP_CSC1_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF2_RSVD1) +#define BP_PXP_CSC1_COEF2_C2 16 +#define BM_PXP_CSC1_COEF2_C2 0x07FF0000 +#define BF_PXP_CSC1_COEF2_C2(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF2_C2) +#define BP_PXP_CSC1_COEF2_RSVD0 11 +#define BM_PXP_CSC1_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF2_RSVD0) +#define BP_PXP_CSC1_COEF2_C3 0 +#define BM_PXP_CSC1_COEF2_C3 0x000007FF +#define BF_PXP_CSC1_COEF2_C3(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF2_C3) + +#define HW_PXP_CSC2_CTRL (0x000001d0) + +#define BP_PXP_CSC2_CTRL_RSVD 3 +#define BM_PXP_CSC2_CTRL_RSVD 0xFFFFFFF8 +#define BF_PXP_CSC2_CTRL_RSVD(v) \ + (((v) << 3) & BM_PXP_CSC2_CTRL_RSVD) +#define BP_PXP_CSC2_CTRL_CSC_MODE 1 +#define BM_PXP_CSC2_CTRL_CSC_MODE 0x00000006 +#define BF_PXP_CSC2_CTRL_CSC_MODE(v) \ + (((v) << 1) & BM_PXP_CSC2_CTRL_CSC_MODE) +#define BV_PXP_CSC2_CTRL_CSC_MODE__YUV2RGB 0x0 +#define BV_PXP_CSC2_CTRL_CSC_MODE__YCbCr2RGB 0x1 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV 0x2 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr 0x3 +#define BM_PXP_CSC2_CTRL_BYPASS 0x00000001 +#define BF_PXP_CSC2_CTRL_BYPASS(v) \ + (((v) << 0) & BM_PXP_CSC2_CTRL_BYPASS) + +#define HW_PXP_CSC2_COEF0 (0x000001e0) + +#define BP_PXP_CSC2_COEF0_RSVD1 27 +#define BM_PXP_CSC2_COEF0_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF0_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF0_RSVD1) +#define BP_PXP_CSC2_COEF0_A2 16 +#define BM_PXP_CSC2_COEF0_A2 0x07FF0000 +#define BF_PXP_CSC2_COEF0_A2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF0_A2) +#define BP_PXP_CSC2_COEF0_RSVD0 11 +#define BM_PXP_CSC2_COEF0_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF0_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF0_RSVD0) +#define BP_PXP_CSC2_COEF0_A1 0 +#define BM_PXP_CSC2_COEF0_A1 0x000007FF +#define BF_PXP_CSC2_COEF0_A1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF0_A1) + +#define HW_PXP_CSC2_COEF1 (0x000001f0) + +#define BP_PXP_CSC2_COEF1_RSVD1 27 +#define BM_PXP_CSC2_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF1_RSVD1) +#define BP_PXP_CSC2_COEF1_B1 16 +#define BM_PXP_CSC2_COEF1_B1 0x07FF0000 +#define BF_PXP_CSC2_COEF1_B1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF1_B1) +#define BP_PXP_CSC2_COEF1_RSVD0 11 +#define BM_PXP_CSC2_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF1_RSVD0) +#define BP_PXP_CSC2_COEF1_A3 0 +#define BM_PXP_CSC2_COEF1_A3 0x000007FF +#define BF_PXP_CSC2_COEF1_A3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF1_A3) + +#define HW_PXP_CSC2_COEF2 (0x00000200) + +#define BP_PXP_CSC2_COEF2_RSVD1 27 +#define BM_PXP_CSC2_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF2_RSVD1) +#define BP_PXP_CSC2_COEF2_B3 16 +#define BM_PXP_CSC2_COEF2_B3 0x07FF0000 +#define BF_PXP_CSC2_COEF2_B3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF2_B3) +#define BP_PXP_CSC2_COEF2_RSVD0 11 +#define BM_PXP_CSC2_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF2_RSVD0) +#define BP_PXP_CSC2_COEF2_B2 0 +#define BM_PXP_CSC2_COEF2_B2 0x000007FF +#define BF_PXP_CSC2_COEF2_B2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF2_B2) + +#define HW_PXP_CSC2_COEF3 (0x00000210) + +#define BP_PXP_CSC2_COEF3_RSVD1 27 +#define BM_PXP_CSC2_COEF3_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF3_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF3_RSVD1) +#define BP_PXP_CSC2_COEF3_C2 16 +#define BM_PXP_CSC2_COEF3_C2 0x07FF0000 +#define BF_PXP_CSC2_COEF3_C2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF3_C2) +#define BP_PXP_CSC2_COEF3_RSVD0 11 +#define BM_PXP_CSC2_COEF3_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF3_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF3_RSVD0) +#define BP_PXP_CSC2_COEF3_C1 0 +#define BM_PXP_CSC2_COEF3_C1 0x000007FF +#define BF_PXP_CSC2_COEF3_C1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF3_C1) + +#define HW_PXP_CSC2_COEF4 (0x00000220) + +#define BP_PXP_CSC2_COEF4_RSVD1 25 +#define BM_PXP_CSC2_COEF4_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF4_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF4_RSVD1) +#define BP_PXP_CSC2_COEF4_D1 16 +#define BM_PXP_CSC2_COEF4_D1 0x01FF0000 +#define BF_PXP_CSC2_COEF4_D1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF4_D1) +#define BP_PXP_CSC2_COEF4_RSVD0 11 +#define BM_PXP_CSC2_COEF4_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF4_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF4_RSVD0) +#define BP_PXP_CSC2_COEF4_C3 0 +#define BM_PXP_CSC2_COEF4_C3 0x000007FF +#define BF_PXP_CSC2_COEF4_C3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF4_C3) + +#define HW_PXP_CSC2_COEF5 (0x00000230) + +#define BP_PXP_CSC2_COEF5_RSVD1 25 +#define BM_PXP_CSC2_COEF5_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF5_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF5_RSVD1) +#define BP_PXP_CSC2_COEF5_D3 16 +#define BM_PXP_CSC2_COEF5_D3 0x01FF0000 +#define BF_PXP_CSC2_COEF5_D3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF5_D3) +#define BP_PXP_CSC2_COEF5_RSVD0 9 +#define BM_PXP_CSC2_COEF5_RSVD0 0x0000FE00 +#define BF_PXP_CSC2_COEF5_RSVD0(v) \ + (((v) << 9) & BM_PXP_CSC2_COEF5_RSVD0) +#define BP_PXP_CSC2_COEF5_D2 0 +#define BM_PXP_CSC2_COEF5_D2 0x000001FF +#define BF_PXP_CSC2_COEF5_D2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF5_D2) + +#define HW_PXP_LUT_CTRL (0x00000240) + +#define BM_PXP_LUT_CTRL_BYPASS 0x80000000 +#define BF_PXP_LUT_CTRL_BYPASS(v) \ + (((v) << 31) & BM_PXP_LUT_CTRL_BYPASS) +#define BP_PXP_LUT_CTRL_RSVD3 26 +#define BM_PXP_LUT_CTRL_RSVD3 0x7C000000 +#define BF_PXP_LUT_CTRL_RSVD3(v) \ + (((v) << 26) & BM_PXP_LUT_CTRL_RSVD3) +#define BP_PXP_LUT_CTRL_LOOKUP_MODE 24 +#define BM_PXP_LUT_CTRL_LOOKUP_MODE 0x03000000 +#define BF_PXP_LUT_CTRL_LOOKUP_MODE(v) \ + (((v) << 24) & BM_PXP_LUT_CTRL_LOOKUP_MODE) +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__CACHE_RGB565 0x0 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8 0x1 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB444 0x2 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB454 0x3 +#define BP_PXP_LUT_CTRL_RSVD2 18 +#define BM_PXP_LUT_CTRL_RSVD2 0x00FC0000 +#define BF_PXP_LUT_CTRL_RSVD2(v) \ + (((v) << 18) & BM_PXP_LUT_CTRL_RSVD2) +#define BP_PXP_LUT_CTRL_OUT_MODE 16 +#define BM_PXP_LUT_CTRL_OUT_MODE 0x00030000 +#define BF_PXP_LUT_CTRL_OUT_MODE(v) \ + (((v) << 16) & BM_PXP_LUT_CTRL_OUT_MODE) +#define BV_PXP_LUT_CTRL_OUT_MODE__RESERVED 0x0 +#define BV_PXP_LUT_CTRL_OUT_MODE__Y8 0x1 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGBW4444CFA 0x2 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGB888 0x3 +#define BP_PXP_LUT_CTRL_RSVD1 11 +#define BM_PXP_LUT_CTRL_RSVD1 0x0000F800 +#define BF_PXP_LUT_CTRL_RSVD1(v) \ + (((v) << 11) & BM_PXP_LUT_CTRL_RSVD1) +#define BM_PXP_LUT_CTRL_SEL_8KB 0x00000400 +#define BF_PXP_LUT_CTRL_SEL_8KB(v) \ + (((v) << 10) & BM_PXP_LUT_CTRL_SEL_8KB) +#define BM_PXP_LUT_CTRL_LRU_UPD 0x00000200 +#define BF_PXP_LUT_CTRL_LRU_UPD(v) \ + (((v) << 9) & BM_PXP_LUT_CTRL_LRU_UPD) +#define BM_PXP_LUT_CTRL_INVALID 0x00000100 +#define BF_PXP_LUT_CTRL_INVALID(v) \ + (((v) << 8) & BM_PXP_LUT_CTRL_INVALID) +#define BP_PXP_LUT_CTRL_RSVD0 1 +#define BM_PXP_LUT_CTRL_RSVD0 0x000000FE +#define BF_PXP_LUT_CTRL_RSVD0(v) \ + (((v) << 1) & BM_PXP_LUT_CTRL_RSVD0) +#define BM_PXP_LUT_CTRL_DMA_START 0x00000001 +#define BF_PXP_LUT_CTRL_DMA_START(v) \ + (((v) << 0) & BM_PXP_LUT_CTRL_DMA_START) + +#define HW_PXP_LUT_ADDR (0x00000250) + +#define BM_PXP_LUT_ADDR_RSVD2 0x80000000 +#define BF_PXP_LUT_ADDR_RSVD2(v) \ + (((v) << 31) & BM_PXP_LUT_ADDR_RSVD2) +#define BP_PXP_LUT_ADDR_NUM_BYTES 16 +#define BM_PXP_LUT_ADDR_NUM_BYTES 0x7FFF0000 +#define BF_PXP_LUT_ADDR_NUM_BYTES(v) \ + (((v) << 16) & BM_PXP_LUT_ADDR_NUM_BYTES) +#define BP_PXP_LUT_ADDR_RSVD1 14 +#define BM_PXP_LUT_ADDR_RSVD1 0x0000C000 +#define BF_PXP_LUT_ADDR_RSVD1(v) \ + (((v) << 14) & BM_PXP_LUT_ADDR_RSVD1) +#define BP_PXP_LUT_ADDR_ADDR 0 +#define BM_PXP_LUT_ADDR_ADDR 0x00003FFF +#define BF_PXP_LUT_ADDR_ADDR(v) \ + (((v) << 0) & BM_PXP_LUT_ADDR_ADDR) + +#define HW_PXP_LUT_DATA (0x00000260) + +#define BP_PXP_LUT_DATA_DATA 0 +#define BM_PXP_LUT_DATA_DATA 0xFFFFFFFF +#define BF_PXP_LUT_DATA_DATA(v) (v) + +#define HW_PXP_LUT_EXTMEM (0x00000270) + +#define BP_PXP_LUT_EXTMEM_ADDR 0 +#define BM_PXP_LUT_EXTMEM_ADDR 0xFFFFFFFF +#define BF_PXP_LUT_EXTMEM_ADDR(v) (v) + +#define HW_PXP_CFA (0x00000280) + +#define BP_PXP_CFA_DATA 0 +#define BM_PXP_CFA_DATA 0xFFFFFFFF +#define BF_PXP_CFA_DATA(v) (v) + +#define HW_PXP_ALPHA_A_CTRL (0x00000290) + +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_A_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_A_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_A_CTRL_RSVD0) +#define BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_A_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x0 +#define BP_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_A_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_A_CTRL_RSVD1) +#define BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_A_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL (0x000002a0) + +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_B_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_B_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_B_CTRL_RSVD0) +#define BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_B_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_B_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_B_CTRL_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_B_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL_1 (0x000002b0) + +#define BP_PXP_ALPHA_B_CTRL_1_RSVD0 8 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD0 0xFFFFFF00 +#define BF_PXP_ALPHA_B_CTRL_1_RSVD0(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_1_RSVD0) +#define BP_PXP_ALPHA_B_CTRL_1_ROP 4 +#define BM_PXP_ALPHA_B_CTRL_1_ROP 0x000000F0 +#define BF_PXP_ALPHA_B_CTRL_1_ROP(v) \ + (((v) << 4) & BM_PXP_ALPHA_B_CTRL_1_ROP) +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKAS 0x0 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKNOTAS 0x1 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKASNOT 0x2 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEAS 0x3 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGENOTAS 0x4 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEASNOT 0x5 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTCOPYAS 0x6 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOT 0x7 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMASKAS 0x8 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMERGEAS 0x9 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__XORAS 0xA +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTXORAS 0xB +#define BP_PXP_ALPHA_B_CTRL_1_RSVD1 2 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD1 0x0000000C +#define BF_PXP_ALPHA_B_CTRL_1_RSVD1(v) \ + (((v) << 2) & BM_PXP_ALPHA_B_CTRL_1_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE 0x00000002 +#define BF_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE) +#define BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_1_ROP_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE) + +#define HW_PXP_PS_BACKGROUND_1 (0x000002c0) + +#define BP_PXP_PS_BACKGROUND_1_RSVD 24 +#define BM_PXP_PS_BACKGROUND_1_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_1_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_1_RSVD) +#define BP_PXP_PS_BACKGROUND_1_COLOR 0 +#define BM_PXP_PS_BACKGROUND_1_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_1_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_1_COLOR) + +#define HW_PXP_PS_CLRKEYLOW_1 (0x000002d0) + +#define BP_PXP_PS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_1 (0x000002e0) + +#define BP_PXP_PS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_AS_CLRKEYLOW_1 (0x000002f0) + +#define BP_PXP_AS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_1 (0x00000300) + +#define BP_PXP_AS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_CTRL2 (0x00000310) +#define HW_PXP_CTRL2_SET (0x00000314) +#define HW_PXP_CTRL2_CLR (0x00000318) +#define HW_PXP_CTRL2_TOG (0x0000031c) + +#define BP_PXP_CTRL2_RSVD3 28 +#define BM_PXP_CTRL2_RSVD3 0xF0000000 +#define BF_PXP_CTRL2_RSVD3(v) \ + (((v) << 28) & BM_PXP_CTRL2_RSVD3) +#define BM_PXP_CTRL2_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL2_ENABLE_ROTATE1) +#define BM_PXP_CTRL2_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL2_ENABLE_ROTATE0) +#define BM_PXP_CTRL2_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL2_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL2_ENABLE_LUT) +#define BM_PXP_CTRL2_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL2_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL2_ENABLE_CSC2) +#define BM_PXP_CTRL2_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL2_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL2_BLOCK_SIZE) +#define BV_PXP_CTRL2_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL2_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL2_RSVD2 0x00400000 +#define BF_PXP_CTRL2_RSVD2(v) \ + (((v) << 22) & BM_PXP_CTRL2_RSVD2) +#define BM_PXP_CTRL2_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL2_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL2_ENABLE_ALPHA_B) +#define BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL2_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL2_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL2_ENABLE_WFE_B) +#define BM_PXP_CTRL2_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL2_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL2_ENABLE_WFE_A) +#define BM_PXP_CTRL2_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL2_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL2_ENABLE_DITHER) +#define BM_PXP_CTRL2_RSVD1 0x00010000 +#define BF_PXP_CTRL2_RSVD1(v) \ + (((v) << 16) & BM_PXP_CTRL2_RSVD1) +#define BM_PXP_CTRL2_VFLIP1 0x00008000 +#define BF_PXP_CTRL2_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL2_VFLIP1) +#define BM_PXP_CTRL2_HFLIP1 0x00004000 +#define BF_PXP_CTRL2_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL2_HFLIP1) +#define BP_PXP_CTRL2_ROTATE1 12 +#define BM_PXP_CTRL2_ROTATE1 0x00003000 +#define BF_PXP_CTRL2_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL2_ROTATE1) +#define BV_PXP_CTRL2_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL2_VFLIP0 0x00000800 +#define BF_PXP_CTRL2_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL2_VFLIP0) +#define BM_PXP_CTRL2_HFLIP0 0x00000400 +#define BF_PXP_CTRL2_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL2_HFLIP0) +#define BP_PXP_CTRL2_ROTATE0 8 +#define BM_PXP_CTRL2_ROTATE0 0x00000300 +#define BF_PXP_CTRL2_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL2_ROTATE0) +#define BV_PXP_CTRL2_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL2_RSVD0 1 +#define BM_PXP_CTRL2_RSVD0 0x000000FE +#define BF_PXP_CTRL2_RSVD0(v) \ + (((v) << 1) & BM_PXP_CTRL2_RSVD0) +#define BM_PXP_CTRL2_ENABLE 0x00000001 +#define BF_PXP_CTRL2_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL2_ENABLE) + +#define HW_PXP_POWER_REG0 (0x00000320) + +#define BP_PXP_POWER_REG0_CTRL 12 +#define BM_PXP_POWER_REG0_CTRL 0xFFFFF000 +#define BF_PXP_POWER_REG0_CTRL(v) \ + (((v) << 12) & BM_PXP_POWER_REG0_CTRL) +#define BP_PXP_POWER_REG0_ROT0_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG0_ROT0_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE) +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 6 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 0x000001C0 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN(v) \ + (((v) << 6) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 3 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 0x00000038 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN(v) \ + (((v) << 3) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0x00000007 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0(v) \ + (((v) << 0) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__SD 0x4 + +#define HW_PXP_POWER_REG1 (0x00000330) + +#define BP_PXP_POWER_REG1_RSVD0 24 +#define BM_PXP_POWER_REG1_RSVD0 0xFF000000 +#define BF_PXP_POWER_REG1_RSVD0(v) \ + (((v) << 24) & BM_PXP_POWER_REG1_RSVD0) +#define BP_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 21 +#define BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 0x00E00000 +#define BF_PXP_POWER_REG1_ALU_B_MEM_LP_STATE(v) \ + (((v) << 21) & BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 18 +#define BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 0x001C0000 +#define BF_PXP_POWER_REG1_ALU_A_MEM_LP_STATE(v) \ + (((v) << 18) & BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 15 +#define BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 0x00038000 +#define BF_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE(v) \ + (((v) << 15) & BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 12 +#define BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 0x00007000 +#define BF_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE(v) \ + (((v) << 12) & BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 6 +#define BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 0x000001C0 +#define BF_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE(v) \ + (((v) << 6) & BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 3 +#define BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 0x00000038 +#define BF_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE(v) \ + (((v) << 3) & BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0 +#define BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0x00000007 +#define BF_PXP_POWER_REG1_ROT1_MEM_LP_STATE(v) \ + (((v) << 0) & BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__SD 0x4 + +#define HW_PXP_DATA_PATH_CTRL0 (0x00000340) +#define HW_PXP_DATA_PATH_CTRL0_SET (0x00000344) +#define HW_PXP_DATA_PATH_CTRL0_CLR (0x00000348) +#define HW_PXP_DATA_PATH_CTRL0_TOG (0x0000034c) + +#define BP_PXP_DATA_PATH_CTRL0_MUX15_SEL 30 +#define BM_PXP_DATA_PATH_CTRL0_MUX15_SEL 0xC0000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(v) \ + (((v) << 30) & BM_PXP_DATA_PATH_CTRL0_MUX15_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX14_SEL 28 +#define BM_PXP_DATA_PATH_CTRL0_MUX14_SEL 0x30000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(v) \ + (((v) << 28) & BM_PXP_DATA_PATH_CTRL0_MUX14_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX13_SEL 26 +#define BM_PXP_DATA_PATH_CTRL0_MUX13_SEL 0x0C000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(v) \ + (((v) << 26) & BM_PXP_DATA_PATH_CTRL0_MUX13_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX12_SEL 24 +#define BM_PXP_DATA_PATH_CTRL0_MUX12_SEL 0x03000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(v) \ + (((v) << 24) & BM_PXP_DATA_PATH_CTRL0_MUX12_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX11_SEL 22 +#define BM_PXP_DATA_PATH_CTRL0_MUX11_SEL 0x00C00000 +#define BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(v) \ + (((v) << 22) & BM_PXP_DATA_PATH_CTRL0_MUX11_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX10_SEL 20 +#define BM_PXP_DATA_PATH_CTRL0_MUX10_SEL 0x00300000 +#define BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(v) \ + (((v) << 20) & BM_PXP_DATA_PATH_CTRL0_MUX10_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX9_SEL 18 +#define BM_PXP_DATA_PATH_CTRL0_MUX9_SEL 0x000C0000 +#define BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(v) \ + (((v) << 18) & BM_PXP_DATA_PATH_CTRL0_MUX9_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX8_SEL 16 +#define BM_PXP_DATA_PATH_CTRL0_MUX8_SEL 0x00030000 +#define BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(v) \ + (((v) << 16) & BM_PXP_DATA_PATH_CTRL0_MUX8_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX7_SEL 14 +#define BM_PXP_DATA_PATH_CTRL0_MUX7_SEL 0x0000C000 +#define BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(v) \ + (((v) << 14) & BM_PXP_DATA_PATH_CTRL0_MUX7_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX6_SEL 12 +#define BM_PXP_DATA_PATH_CTRL0_MUX6_SEL 0x00003000 +#define BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(v) \ + (((v) << 12) & BM_PXP_DATA_PATH_CTRL0_MUX6_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX5_SEL 10 +#define BM_PXP_DATA_PATH_CTRL0_MUX5_SEL 0x00000C00 +#define BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(v) \ + (((v) << 10) & BM_PXP_DATA_PATH_CTRL0_MUX5_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX4_SEL 8 +#define BM_PXP_DATA_PATH_CTRL0_MUX4_SEL 0x00000300 +#define BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(v) \ + (((v) << 8) & BM_PXP_DATA_PATH_CTRL0_MUX4_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX3_SEL 6 +#define BM_PXP_DATA_PATH_CTRL0_MUX3_SEL 0x000000C0 +#define BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(v) \ + (((v) << 6) & BM_PXP_DATA_PATH_CTRL0_MUX3_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX2_SEL 4 +#define BM_PXP_DATA_PATH_CTRL0_MUX2_SEL 0x00000030 +#define BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL0_MUX2_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX1_SEL 2 +#define BM_PXP_DATA_PATH_CTRL0_MUX1_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL0_MUX1_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX0_SEL 0 +#define BM_PXP_DATA_PATH_CTRL0_MUX0_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL0_MUX0_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__3 0x3 + +#define HW_PXP_DATA_PATH_CTRL1 (0x00000350) +#define HW_PXP_DATA_PATH_CTRL1_SET (0x00000354) +#define HW_PXP_DATA_PATH_CTRL1_CLR (0x00000358) +#define HW_PXP_DATA_PATH_CTRL1_TOG (0x0000035c) + +#define BP_PXP_DATA_PATH_CTRL1_RSVD0 4 +#define BM_PXP_DATA_PATH_CTRL1_RSVD0 0xFFFFFFF0 +#define BF_PXP_DATA_PATH_CTRL1_RSVD0(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL1_RSVD0) +#define BP_PXP_DATA_PATH_CTRL1_MUX17_SEL 2 +#define BM_PXP_DATA_PATH_CTRL1_MUX17_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL1_MUX17_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL1_MUX16_SEL 0 +#define BM_PXP_DATA_PATH_CTRL1_MUX16_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL1_MUX16_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__3 0x3 + +#define HW_PXP_INIT_MEM_CTRL (0x00000360) +#define HW_PXP_INIT_MEM_CTRL_SET (0x00000364) +#define HW_PXP_INIT_MEM_CTRL_CLR (0x00000368) +#define HW_PXP_INIT_MEM_CTRL_TOG (0x0000036c) + +#define BM_PXP_INIT_MEM_CTRL_START 0x80000000 +#define BF_PXP_INIT_MEM_CTRL_START(v) \ + (((v) << 31) & BM_PXP_INIT_MEM_CTRL_START) +#define BP_PXP_INIT_MEM_CTRL_SELECT 27 +#define BM_PXP_INIT_MEM_CTRL_SELECT 0x78000000 +#define BF_PXP_INIT_MEM_CTRL_SELECT(v) \ + (((v) << 27) & BM_PXP_INIT_MEM_CTRL_SELECT) +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_LUT 0x0 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR0 0x1 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR1 0x2 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER1_LUT 0x3 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER2_LUT 0x4 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_A 0x5 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_B 0x6 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_A_FETCH 0x7 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_B_FETCH 0x8 +#define BV_PXP_INIT_MEM_CTRL_SELECT__RESERVED 0x15 +#define BP_PXP_INIT_MEM_CTRL_RSVD0 16 +#define BM_PXP_INIT_MEM_CTRL_RSVD0 0x07FF0000 +#define BF_PXP_INIT_MEM_CTRL_RSVD0(v) \ + (((v) << 16) & BM_PXP_INIT_MEM_CTRL_RSVD0) +#define BP_PXP_INIT_MEM_CTRL_ADDR 0 +#define BM_PXP_INIT_MEM_CTRL_ADDR 0x0000FFFF +#define BF_PXP_INIT_MEM_CTRL_ADDR(v) \ + (((v) << 0) & BM_PXP_INIT_MEM_CTRL_ADDR) + +#define HW_PXP_INIT_MEM_DATA (0x00000370) + +#define BP_PXP_INIT_MEM_DATA_DATA 0 +#define BM_PXP_INIT_MEM_DATA_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_DATA(v) (v) + +#define HW_PXP_INIT_MEM_DATA_HIGH (0x00000380) + +#define BP_PXP_INIT_MEM_DATA_HIGH_DATA 0 +#define BM_PXP_INIT_MEM_DATA_HIGH_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_HIGH_DATA(v) (v) + +#define HW_PXP_IRQ_MASK (0x00000390) +#define HW_PXP_IRQ_MASK_SET (0x00000394) +#define HW_PXP_IRQ_MASK_CLR (0x00000398) +#define HW_PXP_IRQ_MASK_TOG (0x0000039c) + +#define BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN 0x80000000 +#define BF_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN(v) \ + (((v) << 31) & BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN) +#define BP_PXP_IRQ_MASK_RSVD1 16 +#define BM_PXP_IRQ_MASK_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_MASK_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_MASK_RSVD1) +#define BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN 0x00008000 +#define BF_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN(v) \ + (((v) << 15) & BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN 0x00004000 +#define BF_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN(v) \ + (((v) << 14) & BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN 0x00002000 +#define BF_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN(v) \ + (((v) << 13) & BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN 0x00001000 +#define BF_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN(v) \ + (((v) << 12) & BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN 0x00000800 +#define BF_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN(v) \ + (((v) << 11) & BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN 0x00000400 +#define BF_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN(v) \ + (((v) << 10) & BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN 0x00000200 +#define BF_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN(v) \ + (((v) << 9) & BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN 0x00000100 +#define BF_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN(v) \ + (((v) << 8) & BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN 0x00000080 +#define BF_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN(v) \ + (((v) << 7) & BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN 0x00000040 +#define BF_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN(v) \ + (((v) << 6) & BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN 0x00000020 +#define BF_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 5) & BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN 0x00000010 +#define BF_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 4) & BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN 0x00000008 +#define BF_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN(v) \ + (((v) << 3) & BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN 0x00000004 +#define BF_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN(v) \ + (((v) << 2) & BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN 0x00000002 +#define BF_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 1) & BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN 0x00000001 +#define BF_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 0) & BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN) + +#define HW_PXP_IRQ (0x000003a0) +#define HW_PXP_IRQ_SET (0x000003a4) +#define HW_PXP_IRQ_CLR (0x000003a8) +#define HW_PXP_IRQ_TOG (0x000003ac) + +#define BM_PXP_IRQ_COMPRESS_DONE_IRQ 0x80000000 +#define BF_PXP_IRQ_COMPRESS_DONE_IRQ(v) \ + (((v) << 31) & BM_PXP_IRQ_COMPRESS_DONE_IRQ) +#define BP_PXP_IRQ_RSVD1 16 +#define BM_PXP_IRQ_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_RSVD1) +#define BM_PXP_IRQ_WFE_B_STORE_IRQ 0x00008000 +#define BF_PXP_IRQ_WFE_B_STORE_IRQ(v) \ + (((v) << 15) & BM_PXP_IRQ_WFE_B_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_STORE_IRQ 0x00004000 +#define BF_PXP_IRQ_WFE_A_STORE_IRQ(v) \ + (((v) << 14) & BM_PXP_IRQ_WFE_A_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_STORE_IRQ 0x00002000 +#define BF_PXP_IRQ_DITHER_STORE_IRQ(v) \ + (((v) << 13) & BM_PXP_IRQ_DITHER_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_STORE_IRQ 0x00001000 +#define BF_PXP_IRQ_FIRST_STORE_IRQ(v) \ + (((v) << 12) & BM_PXP_IRQ_FIRST_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ 0x00000800 +#define BF_PXP_IRQ_WFE_B_CH1_STORE_IRQ(v) \ + (((v) << 11) & BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ 0x00000400 +#define BF_PXP_IRQ_WFE_B_CH0_STORE_IRQ(v) \ + (((v) << 10) & BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ 0x00000200 +#define BF_PXP_IRQ_WFE_A_CH1_STORE_IRQ(v) \ + (((v) << 9) & BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ 0x00000100 +#define BF_PXP_IRQ_WFE_A_CH0_STORE_IRQ(v) \ + (((v) << 8) & BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_STORE_IRQ 0x00000080 +#define BF_PXP_IRQ_DITHER_CH1_STORE_IRQ(v) \ + (((v) << 7) & BM_PXP_IRQ_DITHER_CH1_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_STORE_IRQ 0x00000040 +#define BF_PXP_IRQ_DITHER_CH0_STORE_IRQ(v) \ + (((v) << 6) & BM_PXP_IRQ_DITHER_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ 0x00000020 +#define BF_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ(v) \ + (((v) << 5) & BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ 0x00000010 +#define BF_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ(v) \ + (((v) << 4) & BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_STORE_IRQ 0x00000008 +#define BF_PXP_IRQ_FIRST_CH1_STORE_IRQ(v) \ + (((v) << 3) & BM_PXP_IRQ_FIRST_CH1_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_STORE_IRQ 0x00000004 +#define BF_PXP_IRQ_FIRST_CH0_STORE_IRQ(v) \ + (((v) << 2) & BM_PXP_IRQ_FIRST_CH0_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ 0x00000002 +#define BF_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ(v) \ + (((v) << 1) & BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ 0x00000001 +#define BF_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ(v) \ + (((v) << 0) & BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ) + +#define HW_PXP_NEXT (0x00000400) + +#define BP_PXP_NEXT_POINTER 2 +#define BM_PXP_NEXT_POINTER 0xFFFFFFFC +#define BF_PXP_NEXT_POINTER(v) \ + (((v) << 2) & BM_PXP_NEXT_POINTER) +#define BM_PXP_NEXT_RSVD 0x00000002 +#define BF_PXP_NEXT_RSVD(v) \ + (((v) << 1) & BM_PXP_NEXT_RSVD) +#define BM_PXP_NEXT_ENABLED 0x00000001 +#define BF_PXP_NEXT_ENABLED(v) \ + (((v) << 0) & BM_PXP_NEXT_ENABLED) + +#define HW_PXP_DEBUGCTRL (0x00000410) + +#define BP_PXP_DEBUGCTRL_RSVD 12 +#define BM_PXP_DEBUGCTRL_RSVD 0xFFFFF000 +#define BF_PXP_DEBUGCTRL_RSVD(v) \ + (((v) << 12) & BM_PXP_DEBUGCTRL_RSVD) +#define BP_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 8 +#define BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 0x00000F00 +#define BF_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT(v) \ + (((v) << 8) & BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT) +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MISS_CNT 0x1 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__HIT_CNT 0x2 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__LAT_CNT 0x4 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MAX_LAT 0x8 +#define BP_PXP_DEBUGCTRL_SELECT 0 +#define BM_PXP_DEBUGCTRL_SELECT 0x000000FF +#define BF_PXP_DEBUGCTRL_SELECT(v) \ + (((v) << 0) & BM_PXP_DEBUGCTRL_SELECT) +#define BV_PXP_DEBUGCTRL_SELECT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_SELECT__CTRL 0x1 +#define BV_PXP_DEBUGCTRL_SELECT__PSBUF 0x2 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAX 0x3 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAY 0x4 +#define BV_PXP_DEBUGCTRL_SELECT__ASBUF 0x5 +#define BV_PXP_DEBUGCTRL_SELECT__ROTATION 0x6 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF0 0x7 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF1 0x8 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF2 0x9 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_STAT 0x10 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MISS 0x11 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_HIT 0x12 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_LAT 0x13 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MAX_LAT 0x14 + +#define HW_PXP_DEBUG (0x00000420) + +#define BP_PXP_DEBUG_DATA 0 +#define BM_PXP_DEBUG_DATA 0xFFFFFFFF +#define BF_PXP_DEBUG_DATA(v) (v) + +#define HW_PXP_VERSION (0x00000430) + +#define BP_PXP_VERSION_MAJOR 24 +#define BM_PXP_VERSION_MAJOR 0xFF000000 +#define BF_PXP_VERSION_MAJOR(v) \ + (((v) << 24) & BM_PXP_VERSION_MAJOR) +#define BP_PXP_VERSION_MINOR 16 +#define BM_PXP_VERSION_MINOR 0x00FF0000 +#define BF_PXP_VERSION_MINOR(v) \ + (((v) << 16) & BM_PXP_VERSION_MINOR) +#define BP_PXP_VERSION_STEP 0 +#define BM_PXP_VERSION_STEP 0x0000FFFF +#define BF_PXP_VERSION_STEP(v) \ + (((v) << 0) & BM_PXP_VERSION_STEP) + +#endif /* __IMX_PXP_H__ */ diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c new file mode 100644 index 0000000000..15049c6aab --- /dev/null +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -0,0 +1,2279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Capture CSI Subdev for Freescale i.MX6UL/L / i.MX7 SOC + * + * Copyright (c) 2019 Linaro Ltd + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/math.h> +#include <linux/mfd/syscon.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> + +#define IMX7_CSI_PAD_SINK 0 +#define IMX7_CSI_PAD_SRC 1 +#define IMX7_CSI_PADS_NUM 2 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN BIT(31) +#define BIT_EXT_VSYNC BIT(30) +#define BIT_EOF_INT_EN BIT(29) +#define BIT_PRP_IF_EN BIT(28) +#define BIT_CCIR_MODE BIT(27) +#define BIT_COF_INT_EN BIT(26) +#define BIT_SF_OR_INTEN BIT(25) +#define BIT_RF_OR_INTEN BIT(24) +#define BIT_SFF_DMA_DONE_INTEN BIT(22) +#define BIT_STATFF_INTEN BIT(21) +#define BIT_FB2_DMA_DONE_INTEN BIT(20) +#define BIT_FB1_DMA_DONE_INTEN BIT(19) +#define BIT_RXFF_INTEN BIT(18) +#define BIT_SOF_POL BIT(17) +#define BIT_SOF_INTEN BIT(16) +#define BIT_MCLKDIV(n) ((n) << 12) +#define BIT_MCLKDIV_MASK (0xf << 12) +#define BIT_HSYNC_POL BIT(11) +#define BIT_CCIR_EN BIT(10) +#define BIT_MCLKEN BIT(9) +#define BIT_FCC BIT(8) +#define BIT_PACK_DIR BIT(7) +#define BIT_CLR_STATFIFO BIT(6) +#define BIT_CLR_RXFIFO BIT(5) +#define BIT_GCLK_MODE BIT(4) +#define BIT_INV_DATA BIT(3) +#define BIT_INV_PCLK BIT(2) +#define BIT_REDGE BIT(1) +#define BIT_PIXEL_BIT BIT(0) + +/* control reg 2 */ +#define BIT_DMA_BURST_TYPE_RFF_INCR4 (1 << 30) +#define BIT_DMA_BURST_TYPE_RFF_INCR8 (2 << 30) +#define BIT_DMA_BURST_TYPE_RFF_INCR16 (3 << 30) +#define BIT_DMA_BURST_TYPE_RFF_MASK (3 << 30) + +/* control reg 3 */ +#define BIT_FRMCNT(n) ((n) << 16) +#define BIT_FRMCNT_MASK (0xffff << 16) +#define BIT_FRMCNT_RST BIT(15) +#define BIT_DMA_REFLASH_RFF BIT(14) +#define BIT_DMA_REFLASH_SFF BIT(13) +#define BIT_DMA_REQ_EN_RFF BIT(12) +#define BIT_DMA_REQ_EN_SFF BIT(11) +#define BIT_STATFF_LEVEL(n) ((n) << 8) +#define BIT_STATFF_LEVEL_MASK (0x7 << 8) +#define BIT_HRESP_ERR_EN BIT(7) +#define BIT_RXFF_LEVEL(n) ((n) << 4) +#define BIT_RXFF_LEVEL_MASK (0x7 << 4) +#define BIT_TWO_8BIT_SENSOR BIT(3) +#define BIT_ZERO_PACK_EN BIT(2) +#define BIT_ECC_INT_EN BIT(1) +#define BIT_ECC_AUTO_EN BIT(0) + +/* csi status reg */ +#define BIT_ADDR_CH_ERR_INT BIT(28) +#define BIT_FIELD0_INT BIT(27) +#define BIT_FIELD1_INT BIT(26) +#define BIT_SFF_OR_INT BIT(25) +#define BIT_RFF_OR_INT BIT(24) +#define BIT_DMA_TSF_DONE_SFF BIT(22) +#define BIT_STATFF_INT BIT(21) +#define BIT_DMA_TSF_DONE_FB2 BIT(20) +#define BIT_DMA_TSF_DONE_FB1 BIT(19) +#define BIT_RXFF_INT BIT(18) +#define BIT_EOF_INT BIT(17) +#define BIT_SOF_INT BIT(16) +#define BIT_F2_INT BIT(15) +#define BIT_F1_INT BIT(14) +#define BIT_COF_INT BIT(13) +#define BIT_HRESP_ERR_INT BIT(7) +#define BIT_ECC_INT BIT(1) +#define BIT_DRDY BIT(0) + +/* csi image parameter reg */ +#define BIT_IMAGE_WIDTH(n) ((n) << 16) +#define BIT_IMAGE_HEIGHT(n) (n) + +/* csi control reg 18 */ +#define BIT_CSI_HW_ENABLE BIT(31) +#define BIT_MIPI_DATA_FORMAT_RAW8 (0x2a << 25) +#define BIT_MIPI_DATA_FORMAT_RAW10 (0x2b << 25) +#define BIT_MIPI_DATA_FORMAT_RAW12 (0x2c << 25) +#define BIT_MIPI_DATA_FORMAT_RAW14 (0x2d << 25) +#define BIT_MIPI_DATA_FORMAT_YUV422_8B (0x1e << 25) +#define BIT_MIPI_DATA_FORMAT_MASK (0x3f << 25) +#define BIT_DATA_FROM_MIPI BIT(22) +#define BIT_MIPI_YU_SWAP BIT(21) +#define BIT_MIPI_DOUBLE_CMPNT BIT(20) +#define BIT_MASK_OPTION_FIRST_FRAME (0 << 18) +#define BIT_MASK_OPTION_CSI_EN (1 << 18) +#define BIT_MASK_OPTION_SECOND_FRAME (2 << 18) +#define BIT_MASK_OPTION_ON_DATA (3 << 18) +#define BIT_BASEADDR_CHG_ERR_EN BIT(9) +#define BIT_BASEADDR_SWITCH_SEL BIT(5) +#define BIT_BASEADDR_SWITCH_EN BIT(4) +#define BIT_PARALLEL24_EN BIT(3) +#define BIT_DEINTERLACE_EN BIT(2) +#define BIT_TVDECODER_IN_EN BIT(1) +#define BIT_NTSC_EN BIT(0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +#define CSI_CSICR1 0x00 +#define CSI_CSICR2 0x04 +#define CSI_CSICR3 0x08 +#define CSI_STATFIFO 0x0c +#define CSI_CSIRXFIFO 0x10 +#define CSI_CSIRXCNT 0x14 +#define CSI_CSISR 0x18 + +#define CSI_CSIDBG 0x1c +#define CSI_CSIDMASA_STATFIFO 0x20 +#define CSI_CSIDMATS_STATFIFO 0x24 +#define CSI_CSIDMASA_FB1 0x28 +#define CSI_CSIDMASA_FB2 0x2c +#define CSI_CSIFBUF_PARA 0x30 +#define CSI_CSIIMAG_PARA 0x34 + +#define CSI_CSICR18 0x48 +#define CSI_CSICR19 0x4c + +#define IMX7_CSI_VIDEO_NAME "imx-capture" +/* In bytes, per queue */ +#define IMX7_CSI_VIDEO_MEM_LIMIT SZ_512M +#define IMX7_CSI_VIDEO_EOF_TIMEOUT 2000 + +#define IMX7_CSI_DEF_MBUS_CODE MEDIA_BUS_FMT_UYVY8_2X8 +#define IMX7_CSI_DEF_PIX_FORMAT V4L2_PIX_FMT_UYVY +#define IMX7_CSI_DEF_PIX_WIDTH 640 +#define IMX7_CSI_DEF_PIX_HEIGHT 480 + +enum imx_csi_model { + IMX7_CSI_IMX7 = 0, + IMX7_CSI_IMX8MQ, +}; + +struct imx7_csi_pixfmt { + /* the in-memory FourCC pixel format */ + u32 fourcc; + /* + * the set of equivalent media bus codes for the fourcc. + * NOTE! codes pointer is NULL for in-memory-only formats. + */ + const u32 *codes; + int bpp; /* total bpp */ + bool yuv; +}; + +struct imx7_csi_vb2_buffer { + struct vb2_v4l2_buffer vbuf; + struct list_head list; +}; + +static inline struct imx7_csi_vb2_buffer * +to_imx7_csi_vb2_buffer(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct imx7_csi_vb2_buffer, vbuf); +} + +struct imx7_csi_dma_buf { + void *virt; + dma_addr_t dma_addr; + unsigned long len; +}; + +struct imx7_csi { + struct device *dev; + + /* Resources and locks */ + void __iomem *regbase; + int irq; + struct clk *mclk; + + spinlock_t irqlock; /* Protects last_eof */ + + /* Media and V4L2 device */ + struct media_device mdev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + struct media_pipeline pipe; + + struct v4l2_subdev *src_sd; + bool is_csi2; + + /* V4L2 subdev */ + struct v4l2_subdev sd; + struct media_pad pad[IMX7_CSI_PADS_NUM]; + + /* Video device */ + struct video_device *vdev; /* Video device */ + struct media_pad vdev_pad; /* Video device pad */ + + struct v4l2_pix_format vdev_fmt; /* The user format */ + const struct imx7_csi_pixfmt *vdev_cc; + struct v4l2_rect vdev_compose; /* The compose rectangle */ + + struct mutex vdev_mutex; /* Protect vdev operations */ + + struct vb2_queue q; /* The videobuf2 queue */ + struct list_head ready_q; /* List of queued buffers */ + spinlock_t q_lock; /* Protect ready_q */ + + /* Buffers and streaming state */ + struct imx7_csi_vb2_buffer *active_vb2_buf[2]; + struct imx7_csi_dma_buf underrun_buf; + + bool is_streaming; + int buf_num; + u32 frame_sequence; + + bool last_eof; + struct completion last_eof_completion; + + enum imx_csi_model model; +}; + +static struct imx7_csi * +imx7_csi_notifier_to_dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct imx7_csi, notifier); +} + +/* ----------------------------------------------------------------------------- + * Hardware Configuration + */ + +static u32 imx7_csi_reg_read(struct imx7_csi *csi, unsigned int offset) +{ + return readl(csi->regbase + offset); +} + +static void imx7_csi_reg_write(struct imx7_csi *csi, unsigned int value, + unsigned int offset) +{ + writel(value, csi->regbase + offset); +} + +static u32 imx7_csi_irq_clear(struct imx7_csi *csi) +{ + u32 isr; + + isr = imx7_csi_reg_read(csi, CSI_CSISR); + imx7_csi_reg_write(csi, isr, CSI_CSISR); + + return isr; +} + +static void imx7_csi_init_default(struct imx7_csi *csi) +{ + imx7_csi_reg_write(csi, BIT_SOF_POL | BIT_REDGE | BIT_GCLK_MODE | + BIT_HSYNC_POL | BIT_FCC | BIT_MCLKDIV(1) | + BIT_MCLKEN, CSI_CSICR1); + imx7_csi_reg_write(csi, 0, CSI_CSICR2); + imx7_csi_reg_write(csi, BIT_FRMCNT_RST, CSI_CSICR3); + + imx7_csi_reg_write(csi, BIT_IMAGE_WIDTH(IMX7_CSI_DEF_PIX_WIDTH) | + BIT_IMAGE_HEIGHT(IMX7_CSI_DEF_PIX_HEIGHT), + CSI_CSIIMAG_PARA); + + imx7_csi_reg_write(csi, BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} + +static void imx7_csi_hw_enable_irq(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1); + + cr1 |= BIT_RFF_OR_INT; + cr1 |= BIT_FB1_DMA_DONE_INTEN; + cr1 |= BIT_FB2_DMA_DONE_INTEN; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); +} + +static void imx7_csi_hw_disable_irq(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1); + + cr1 &= ~BIT_RFF_OR_INT; + cr1 &= ~BIT_FB1_DMA_DONE_INTEN; + cr1 &= ~BIT_FB2_DMA_DONE_INTEN; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); +} + +static void imx7_csi_hw_enable(struct imx7_csi *csi) +{ + u32 cr = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr |= BIT_CSI_HW_ENABLE; + + imx7_csi_reg_write(csi, cr, CSI_CSICR18); +} + +static void imx7_csi_hw_disable(struct imx7_csi *csi) +{ + u32 cr = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr &= ~BIT_CSI_HW_ENABLE; + + imx7_csi_reg_write(csi, cr, CSI_CSICR18); +} + +static void imx7_csi_dma_reflash(struct imx7_csi *csi) +{ + u32 cr3; + + cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + cr3 |= BIT_DMA_REFLASH_RFF; + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_rx_fifo_clear(struct imx7_csi *csi) +{ + u32 cr1 = imx7_csi_reg_read(csi, CSI_CSICR1) & ~BIT_FCC; + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); + imx7_csi_reg_write(csi, cr1 | BIT_CLR_RXFIFO, CSI_CSICR1); + imx7_csi_reg_write(csi, cr1 | BIT_FCC, CSI_CSICR1); +} + +static void imx7_csi_dmareq_rff_enable(struct imx7_csi *csi) +{ + u32 cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + + cr3 |= BIT_DMA_REQ_EN_RFF; + cr3 |= BIT_HRESP_ERR_EN; + cr3 &= ~BIT_RXFF_LEVEL_MASK; + cr3 |= BIT_RXFF_LEVEL(2); + + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_dmareq_rff_disable(struct imx7_csi *csi) +{ + u32 cr3 = imx7_csi_reg_read(csi, CSI_CSICR3); + + cr3 &= ~BIT_DMA_REQ_EN_RFF; + cr3 &= ~BIT_HRESP_ERR_EN; + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); +} + +static void imx7_csi_update_buf(struct imx7_csi *csi, dma_addr_t dma_addr, + int buf_num) +{ + if (buf_num == 1) + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB2); + else + imx7_csi_reg_write(csi, dma_addr, CSI_CSIDMASA_FB1); +} + +static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi); + +static void imx7_csi_setup_vb2_buf(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *buf; + struct vb2_buffer *vb2_buf; + int i; + + for (i = 0; i < 2; i++) { + dma_addr_t dma_addr; + + buf = imx7_csi_video_next_buf(csi); + if (buf) { + csi->active_vb2_buf[i] = buf; + vb2_buf = &buf->vbuf.vb2_buf; + dma_addr = vb2_dma_contig_plane_dma_addr(vb2_buf, 0); + } else { + csi->active_vb2_buf[i] = NULL; + dma_addr = csi->underrun_buf.dma_addr; + } + + imx7_csi_update_buf(csi, dma_addr, i); + } +} + +static void imx7_csi_dma_unsetup_vb2_buf(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + struct imx7_csi_vb2_buffer *buf; + int i; + + /* return any remaining active frames with return_status */ + for (i = 0; i < 2; i++) { + buf = csi->active_vb2_buf[i]; + if (buf) { + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, return_status); + csi->active_vb2_buf[i] = NULL; + } + } +} + +static void imx7_csi_free_dma_buf(struct imx7_csi *csi, + struct imx7_csi_dma_buf *buf) +{ + if (buf->virt) + dma_free_coherent(csi->dev, buf->len, buf->virt, buf->dma_addr); + + buf->virt = NULL; + buf->dma_addr = 0; +} + +static int imx7_csi_alloc_dma_buf(struct imx7_csi *csi, + struct imx7_csi_dma_buf *buf, int size) +{ + imx7_csi_free_dma_buf(csi, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(csi->dev, buf->len, &buf->dma_addr, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) + return -ENOMEM; + + return 0; +} + +static int imx7_csi_dma_setup(struct imx7_csi *csi) +{ + int ret; + + ret = imx7_csi_alloc_dma_buf(csi, &csi->underrun_buf, + csi->vdev_fmt.sizeimage); + if (ret < 0) { + v4l2_warn(&csi->sd, "consider increasing the CMA area\n"); + return ret; + } + + csi->frame_sequence = 0; + csi->last_eof = false; + init_completion(&csi->last_eof_completion); + + imx7_csi_setup_vb2_buf(csi); + + return 0; +} + +static void imx7_csi_dma_cleanup(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + imx7_csi_dma_unsetup_vb2_buf(csi, return_status); + imx7_csi_free_dma_buf(csi, &csi->underrun_buf); +} + +static void imx7_csi_dma_stop(struct imx7_csi *csi) +{ + unsigned long timeout_jiffies; + unsigned long flags; + int ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&csi->irqlock, flags); + csi->last_eof = true; + spin_unlock_irqrestore(&csi->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + timeout_jiffies = msecs_to_jiffies(IMX7_CSI_VIDEO_EOF_TIMEOUT); + ret = wait_for_completion_timeout(&csi->last_eof_completion, + timeout_jiffies); + if (ret == 0) + v4l2_warn(&csi->sd, "wait last EOF timeout\n"); + + imx7_csi_hw_disable_irq(csi); +} + +static void imx7_csi_configure(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_pix_format *out_pix = &csi->vdev_fmt; + int width = out_pix->width; + u32 stride = 0; + u32 cr3 = BIT_FRMCNT_RST; + u32 cr1, cr18; + + cr18 = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr18 &= ~(BIT_CSI_HW_ENABLE | BIT_MIPI_DATA_FORMAT_MASK | + BIT_DATA_FROM_MIPI | BIT_MIPI_DOUBLE_CMPNT | + BIT_BASEADDR_CHG_ERR_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_SWITCH_EN | BIT_DEINTERLACE_EN); + + if (out_pix->field == V4L2_FIELD_INTERLACED) { + cr18 |= BIT_DEINTERLACE_EN; + stride = out_pix->width; + } + + if (!csi->is_csi2) { + cr1 = BIT_SOF_POL | BIT_REDGE | BIT_GCLK_MODE | BIT_HSYNC_POL + | BIT_FCC | BIT_MCLKDIV(1) | BIT_MCLKEN; + + cr18 |= BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_CHG_ERR_EN; + + if (out_pix->pixelformat == V4L2_PIX_FMT_UYVY || + out_pix->pixelformat == V4L2_PIX_FMT_YUYV) + width *= 2; + } else { + const struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = v4l2_subdev_get_pad_format(&csi->sd, sd_state, + IMX7_CSI_PAD_SINK); + + cr1 = BIT_SOF_POL | BIT_REDGE | BIT_HSYNC_POL | BIT_FCC + | BIT_MCLKDIV(1) | BIT_MCLKEN; + + cr18 |= BIT_DATA_FROM_MIPI; + + switch (sink_fmt->code) { + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + cr18 |= BIT_MIPI_DATA_FORMAT_RAW8; + break; + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW10; + break; + case MEDIA_BUS_FMT_Y12_1X12: + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW12; + break; + case MEDIA_BUS_FMT_Y14_1X14: + case MEDIA_BUS_FMT_SBGGR14_1X14: + case MEDIA_BUS_FMT_SGBRG14_1X14: + case MEDIA_BUS_FMT_SGRBG14_1X14: + case MEDIA_BUS_FMT_SRGGB14_1X14: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_RAW14; + break; + + /* + * The CSI bridge has a 16-bit input bus. Depending on the + * connected source, data may be transmitted with 8 or 10 bits + * per clock sample (in bits [9:2] or [9:0] respectively) or + * with 16 bits per clock sample (in bits [15:0]). The data is + * then packed into a 32-bit FIFO (as shown in figure 13-11 of + * the i.MX8MM reference manual rev. 3). + * + * The data packing in a 32-bit FIFO input word is controlled by + * the CR3 TWO_8BIT_SENSOR field (also known as SENSOR_16BITS in + * the i.MX8MM reference manual). When set to 0, data packing + * groups four 8-bit input samples (bits [9:2]). When set to 1, + * data packing groups two 16-bit input samples (bits [15:0]). + * + * The register field CR18 MIPI_DOUBLE_CMPNT also needs to be + * configured according to the input format for YUV 4:2:2 data. + * The field controls the gasket between the CSI-2 receiver and + * the CSI bridge. On i.MX7 and i.MX8MM, the field must be set + * to 1 when the CSIS outputs 16-bit samples. On i.MX8MQ, the + * gasket ignores the MIPI_DOUBLE_CMPNT bit and YUV 4:2:2 always + * uses 16-bit samples. Setting MIPI_DOUBLE_CMPNT in that case + * has no effect, but doesn't cause any issue. + */ + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + cr3 |= BIT_TWO_8BIT_SENSOR; + cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B | + BIT_MIPI_DOUBLE_CMPNT; + break; + } + } + + imx7_csi_reg_write(csi, cr1, CSI_CSICR1); + imx7_csi_reg_write(csi, BIT_DMA_BURST_TYPE_RFF_INCR16, CSI_CSICR2); + imx7_csi_reg_write(csi, cr3, CSI_CSICR3); + imx7_csi_reg_write(csi, cr18, CSI_CSICR18); + + imx7_csi_reg_write(csi, (width * out_pix->height) >> 2, CSI_CSIRXCNT); + imx7_csi_reg_write(csi, BIT_IMAGE_WIDTH(width) | + BIT_IMAGE_HEIGHT(out_pix->height), + CSI_CSIIMAG_PARA); + imx7_csi_reg_write(csi, stride, CSI_CSIFBUF_PARA); +} + +static int imx7_csi_init(struct imx7_csi *csi, + struct v4l2_subdev_state *sd_state) +{ + int ret; + + ret = clk_prepare_enable(csi->mclk); + if (ret < 0) + return ret; + + imx7_csi_configure(csi, sd_state); + + ret = imx7_csi_dma_setup(csi); + if (ret < 0) { + clk_disable_unprepare(csi->mclk); + return ret; + } + + return 0; +} + +static void imx7_csi_deinit(struct imx7_csi *csi, + enum vb2_buffer_state return_status) +{ + imx7_csi_dma_cleanup(csi, return_status); + imx7_csi_init_default(csi); + imx7_csi_dmareq_rff_disable(csi); + clk_disable_unprepare(csi->mclk); +} + +static void imx7_csi_baseaddr_switch_on_second_frame(struct imx7_csi *csi) +{ + u32 cr18 = imx7_csi_reg_read(csi, CSI_CSICR18); + + cr18 |= BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL | + BIT_BASEADDR_CHG_ERR_EN; + cr18 |= BIT_MASK_OPTION_SECOND_FRAME; + imx7_csi_reg_write(csi, cr18, CSI_CSICR18); +} + +static void imx7_csi_enable(struct imx7_csi *csi) +{ + /* Clear the Rx FIFO and reflash the DMA controller. */ + imx7_csi_rx_fifo_clear(csi); + imx7_csi_dma_reflash(csi); + + usleep_range(2000, 3000); + + /* Clear and enable the interrupts. */ + imx7_csi_irq_clear(csi); + imx7_csi_hw_enable_irq(csi); + + /* Enable the RxFIFO DMA and the CSI. */ + imx7_csi_dmareq_rff_enable(csi); + imx7_csi_hw_enable(csi); + + if (csi->model == IMX7_CSI_IMX8MQ) + imx7_csi_baseaddr_switch_on_second_frame(csi); +} + +static void imx7_csi_disable(struct imx7_csi *csi) +{ + imx7_csi_dma_stop(csi); + + imx7_csi_dmareq_rff_disable(csi); + + imx7_csi_hw_disable_irq(csi); + + imx7_csi_hw_disable(csi); +} + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static void imx7_csi_error_recovery(struct imx7_csi *csi) +{ + imx7_csi_hw_disable(csi); + + imx7_csi_rx_fifo_clear(csi); + + imx7_csi_dma_reflash(csi); + + imx7_csi_hw_enable(csi); +} + +static void imx7_csi_vb2_buf_done(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *done, *next; + struct vb2_buffer *vb; + dma_addr_t dma_addr; + + done = csi->active_vb2_buf[csi->buf_num]; + if (done) { + done->vbuf.field = csi->vdev_fmt.field; + done->vbuf.sequence = csi->frame_sequence; + vb = &done->vbuf.vb2_buf; + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } + csi->frame_sequence++; + + /* get next queued buffer */ + next = imx7_csi_video_next_buf(csi); + if (next) { + dma_addr = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); + csi->active_vb2_buf[csi->buf_num] = next; + } else { + dma_addr = csi->underrun_buf.dma_addr; + csi->active_vb2_buf[csi->buf_num] = NULL; + } + + imx7_csi_update_buf(csi, dma_addr, csi->buf_num); +} + +static irqreturn_t imx7_csi_irq_handler(int irq, void *data) +{ + struct imx7_csi *csi = data; + u32 status; + + spin_lock(&csi->irqlock); + + status = imx7_csi_irq_clear(csi); + + if (status & BIT_RFF_OR_INT) { + dev_warn(csi->dev, "Rx fifo overflow\n"); + imx7_csi_error_recovery(csi); + } + + if (status & BIT_HRESP_ERR_INT) { + dev_warn(csi->dev, "Hresponse error detected\n"); + imx7_csi_error_recovery(csi); + } + + if (status & BIT_ADDR_CH_ERR_INT) { + imx7_csi_hw_disable(csi); + + imx7_csi_dma_reflash(csi); + + imx7_csi_hw_enable(csi); + } + + if ((status & BIT_DMA_TSF_DONE_FB1) && + (status & BIT_DMA_TSF_DONE_FB2)) { + /* + * For both FB1 and FB2 interrupter bits set case, + * CSI DMA is work in one of FB1 and FB2 buffer, + * but software can not know the state. + * Skip it to avoid base address updated + * when csi work in field0 and field1 will write to + * new base address. + */ + } else if (status & BIT_DMA_TSF_DONE_FB1) { + csi->buf_num = 0; + } else if (status & BIT_DMA_TSF_DONE_FB2) { + csi->buf_num = 1; + } + + if ((status & BIT_DMA_TSF_DONE_FB1) || + (status & BIT_DMA_TSF_DONE_FB2)) { + imx7_csi_vb2_buf_done(csi); + + if (csi->last_eof) { + complete(&csi->last_eof_completion); + csi->last_eof = false; + } + } + + spin_unlock(&csi->irqlock); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Format Helpers + */ + +#define IMX_BUS_FMTS(fmt...) (const u32[]) {fmt, 0} + +/* + * List of supported pixel formats for the subdevs. Keep V4L2_PIX_FMT_UYVY and + * MEDIA_BUS_FMT_UYVY8_2X8 first to match IMX7_CSI_DEF_PIX_FORMAT and + * IMX7_CSI_DEF_MBUS_CODE. + * + * TODO: Restrict the supported formats list based on the SoC integration. + * + * The CSI bridge can be configured to sample pixel components from the Rx queue + * in single (8bpp) or double (16bpp) component modes. Image format variants + * with different sample sizes (ie YUYV_2X8 vs YUYV_1X16) determine the pixel + * components sampling size per each clock cycle and their packing mode (see + * imx7_csi_configure() for details). + * + * As the CSI bridge can be interfaced with different IP blocks depending on the + * SoC model it is integrated on, the Rx queue sampling size should match the + * size of the samples transferred by the transmitting IP block. To avoid + * misconfigurations of the capture pipeline, the enumeration of the supported + * formats should be restricted to match the pixel source transmitting mode. + * + * Example: i.MX8MM SoC integrates the CSI bridge with the Samsung CSIS CSI-2 + * receiver which operates in dual pixel sampling mode. The CSI bridge should + * only expose the 1X16 formats variant which instructs it to operate in dual + * pixel sampling mode. When the CSI bridge is instead integrated on an i.MX7, + * which supports both serial and parallel input, it should expose both + * variants. + * + * This currently only applies to YUYV formats, but other formats might need to + * be handled in the same way. + */ +static const struct imx7_csi_pixfmt pixel_formats[] = { + /*** YUV formats start here ***/ + { + .fourcc = V4L2_PIX_FMT_UYVY, + .codes = IMX_BUS_FMTS( + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16 + ), + .yuv = true, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .codes = IMX_BUS_FMTS( + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YUYV8_1X16 + ), + .yuv = true, + .bpp = 16, + }, + /*** raw bayer and grayscale formats start here ***/ + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SBGGR14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGBRG14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SGRBG14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_SRGGB14_1X14), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y8_1X8), + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y10_1X10), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_Y12, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y12_1X12), + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_Y14, + .codes = IMX_BUS_FMTS(MEDIA_BUS_FMT_Y14_1X14), + .bpp = 16, + }, +}; + +/* + * Search in the pixel_formats[] array for an entry with the given fourcc + * return it. + */ +static const struct imx7_csi_pixfmt *imx7_csi_find_pixel_format(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +/* + * Search in the pixel_formats[] array for an entry with the given media + * bus code and return it. + */ +static const struct imx7_csi_pixfmt *imx7_csi_find_mbus_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (code == fmt->codes[j]) + return fmt; + } + } + + return NULL; +} + +/* + * Enumerate entries in the pixel_formats[] array that match the + * requested search criteria. Return the media-bus code that matches + * the search criteria at the requested match index. + * + * @code: The returned media-bus code that matches the search criteria at + * the requested match index. + * @index: The requested match index. + */ +static int imx7_csi_enum_mbus_formats(u32 *code, u32 index) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (index == 0) { + *code = fmt->codes[j]; + return 0; + } + + index--; + } + } + + return -EINVAL; +} + +/* ----------------------------------------------------------------------------- + * Video Capture Device - IOCTLs + */ + +static int imx7_csi_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct imx7_csi *csi = video_drvdata(file); + + strscpy(cap->driver, IMX7_CSI_VIDEO_NAME, sizeof(cap->driver)); + strscpy(cap->card, IMX7_CSI_VIDEO_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", dev_name(csi->dev)); + + return 0; +} + +static int imx7_csi_video_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + unsigned int index = f->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) { + const struct imx7_csi_pixfmt *fmt = &pixel_formats[i]; + + /* + * If a media bus code is specified, only consider formats that + * match it. + */ + if (f->mbus_code) { + unsigned int j; + + if (!fmt->codes) + continue; + + for (j = 0; fmt->codes[j]; j++) { + if (f->mbus_code == fmt->codes[j]) + break; + } + + if (!fmt->codes[j]) + continue; + } + + if (index == 0) { + f->pixelformat = fmt->fourcc; + return 0; + } + + index--; + } + + return -EINVAL; +} + +static int imx7_csi_video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct imx7_csi_pixfmt *cc; + u32 walign; + + if (fsize->index > 0) + return -EINVAL; + + cc = imx7_csi_find_pixel_format(fsize->pixel_format); + if (!cc) + return -EINVAL; + + /* + * The width alignment is 8 bytes as indicated by the + * CSI_IMAG_PARA.IMAGE_WIDTH documentation. Convert it to pixels. + */ + walign = 8 * 8 / cc->bpp; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = walign; + fsize->stepwise.max_width = round_down(65535U, walign); + fsize->stepwise.min_height = 1; + fsize->stepwise.max_height = 65535; + fsize->stepwise.step_width = walign; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int imx7_csi_video_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct imx7_csi *csi = video_drvdata(file); + + f->fmt.pix = csi->vdev_fmt; + + return 0; +} + +static const struct imx7_csi_pixfmt * +__imx7_csi_video_try_fmt(struct v4l2_pix_format *pixfmt, + struct v4l2_rect *compose) +{ + const struct imx7_csi_pixfmt *cc; + u32 walign; + + if (compose) { + compose->width = pixfmt->width; + compose->height = pixfmt->height; + } + + /* + * Find the pixel format, default to the first supported format if not + * found. + */ + cc = imx7_csi_find_pixel_format(pixfmt->pixelformat); + if (!cc) { + pixfmt->pixelformat = IMX7_CSI_DEF_PIX_FORMAT; + cc = imx7_csi_find_pixel_format(pixfmt->pixelformat); + } + + /* + * The width alignment is 8 bytes as indicated by the + * CSI_IMAG_PARA.IMAGE_WIDTH documentation. Convert it to pixels. + * + * TODO: Implement configurable stride support. + */ + walign = 8 * 8 / cc->bpp; + pixfmt->width = clamp(round_up(pixfmt->width, walign), walign, + round_down(65535U, walign)); + pixfmt->height = clamp(pixfmt->height, 1U, 65535U); + + pixfmt->bytesperline = pixfmt->width * cc->bpp / 8; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + pixfmt->field = V4L2_FIELD_NONE; + + return cc; +} + +static int imx7_csi_video_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + __imx7_csi_video_try_fmt(&f->fmt.pix, NULL); + return 0; +} + +static int imx7_csi_video_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct imx7_csi *csi = video_drvdata(file); + const struct imx7_csi_pixfmt *cc; + + if (vb2_is_busy(&csi->q)) { + dev_err(csi->dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + cc = __imx7_csi_video_try_fmt(&f->fmt.pix, &csi->vdev_compose); + + csi->vdev_cc = cc; + csi->vdev_fmt = f->fmt.pix; + + return 0; +} + +static int imx7_csi_video_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct imx7_csi *csi = video_drvdata(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* The compose rectangle is fixed to the source format. */ + s->r = csi->vdev_compose; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + /* + * The hardware writes with a configurable but fixed DMA burst + * size. If the source format width is not burst size aligned, + * the written frame contains padding to the right. + */ + s->r.left = 0; + s->r.top = 0; + s->r.width = csi->vdev_fmt.width; + s->r.height = csi->vdev_fmt.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ioctl_ops imx7_csi_video_ioctl_ops = { + .vidioc_querycap = imx7_csi_video_querycap, + + .vidioc_enum_fmt_vid_cap = imx7_csi_video_enum_fmt_vid_cap, + .vidioc_enum_framesizes = imx7_csi_video_enum_framesizes, + + .vidioc_g_fmt_vid_cap = imx7_csi_video_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = imx7_csi_video_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = imx7_csi_video_s_fmt_vid_cap, + + .vidioc_g_selection = imx7_csi_video_g_selection, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - Queue Operations + */ + +static int imx7_csi_video_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &csi->vdev_fmt; + unsigned int count = *nbuffers; + + if (vq->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < pix->sizeimage) + return -EINVAL; + count += vq->num_buffers; + } + + count = min_t(__u32, IMX7_CSI_VIDEO_MEM_LIMIT / pix->sizeimage, count); + + if (*nplanes) + *nbuffers = (count < vq->num_buffers) ? 0 : + count - vq->num_buffers; + else + *nbuffers = count; + + *nplanes = 1; + sizes[0] = pix->sizeimage; + + return 0; +} + +static int imx7_csi_video_buf_init(struct vb2_buffer *vb) +{ + struct imx7_csi_vb2_buffer *buf = to_imx7_csi_vb2_buffer(vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int imx7_csi_video_buf_prepare(struct vb2_buffer *vb) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format *pix = &csi->vdev_fmt; + + if (vb2_plane_size(vb, 0) < pix->sizeimage) { + dev_err(csi->dev, + "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), (long)pix->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, pix->sizeimage); + + return 0; +} + +static bool imx7_csi_fast_track_buffer(struct imx7_csi *csi, + struct imx7_csi_vb2_buffer *buf) +{ + unsigned long flags; + dma_addr_t dma_addr; + int buf_num; + u32 isr; + + if (!csi->is_streaming) + return false; + + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vbuf.vb2_buf, 0); + + /* + * buf_num holds the framebuffer ID of the most recently (*not* the + * next anticipated) triggered interrupt. Without loss of generality, + * if buf_num is 0, the hardware is capturing to FB2. If FB1 has been + * programmed with a dummy buffer (as indicated by active_vb2_buf[0] + * being NULL), then we can fast-track the new buffer by programming + * its address in FB1 before the hardware completes FB2, instead of + * adding it to the buffer queue and incurring a delay of one + * additional frame. + * + * The irqlock prevents races with the interrupt handler that updates + * buf_num when it programs the next buffer, but we can still race with + * the hardware if we program the buffer in FB1 just after the hardware + * completes FB2 and switches to FB1 and before buf_num can be updated + * by the interrupt handler for FB2. The fast-tracked buffer would + * then be ignored by the hardware while the driver would think it has + * successfully been processed. + * + * To avoid this problem, if we can't avoid the race, we can detect + * that we have lost it by checking, after programming the buffer in + * FB1, if the interrupt flag indicating completion of FB2 has been + * raised. If that is not the case, fast-tracking succeeded, and we can + * update active_vb2_buf[0]. Otherwise, we may or may not have lost the + * race (as the interrupt flag may have been raised just after + * programming FB1 and before we read the interrupt status register), + * and we need to assume the worst case of a race loss and queue the + * buffer through the slow path. + */ + + spin_lock_irqsave(&csi->irqlock, flags); + + buf_num = csi->buf_num; + if (csi->active_vb2_buf[buf_num]) { + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + imx7_csi_update_buf(csi, dma_addr, buf_num); + + isr = imx7_csi_reg_read(csi, CSI_CSISR); + if (isr & (buf_num ? BIT_DMA_TSF_DONE_FB1 : BIT_DMA_TSF_DONE_FB2)) { + /* + * The interrupt for the /other/ FB just came (the isr hasn't + * run yet though, because we have the lock here); we can't be + * sure we've programmed buf_num FB in time, so queue the buffer + * to the buffer queue normally. No need to undo writing the FB + * register, since we won't return it as active_vb2_buf is NULL, + * so it's okay to potentially write it to both FB1 and FB2; + * only the one where it was queued normally will be returned. + */ + spin_unlock_irqrestore(&csi->irqlock, flags); + return false; + } + + csi->active_vb2_buf[buf_num] = buf; + + spin_unlock_irqrestore(&csi->irqlock, flags); + return true; +} + +static void imx7_csi_video_buf_queue(struct vb2_buffer *vb) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + struct imx7_csi_vb2_buffer *buf = to_imx7_csi_vb2_buffer(vb); + unsigned long flags; + + if (imx7_csi_fast_track_buffer(csi, buf)) + return; + + spin_lock_irqsave(&csi->q_lock, flags); + + list_add_tail(&buf->list, &csi->ready_q); + + spin_unlock_irqrestore(&csi->q_lock, flags); +} + +static int imx7_csi_video_validate_fmt(struct imx7_csi *csi) +{ + struct v4l2_subdev_format fmt_src = { + .pad = IMX7_CSI_PAD_SRC, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct imx7_csi_pixfmt *cc; + int ret; + + /* Retrieve the media bus format on the source subdev. */ + ret = v4l2_subdev_call_state_active(&csi->sd, pad, get_fmt, &fmt_src); + if (ret) + return ret; + + /* + * Verify that the media bus size matches the size set on the video + * node. It is sufficient to check the compose rectangle size without + * checking the rounded size from pix_fmt, as the rounded size is + * derived directly from the compose rectangle size, and will thus + * always match if the compose rectangle matches. + */ + if (csi->vdev_compose.width != fmt_src.format.width || + csi->vdev_compose.height != fmt_src.format.height) + return -EPIPE; + + /* + * Verify that the media bus code is compatible with the pixel format + * set on the video node. + */ + cc = imx7_csi_find_mbus_format(fmt_src.format.code); + if (!cc || csi->vdev_cc->yuv != cc->yuv) + return -EPIPE; + + return 0; +} + +static int imx7_csi_video_start_streaming(struct vb2_queue *vq, + unsigned int count) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct imx7_csi_vb2_buffer *buf, *tmp; + unsigned long flags; + int ret; + + ret = imx7_csi_video_validate_fmt(csi); + if (ret) { + dev_err(csi->dev, "capture format not valid\n"); + goto err_buffers; + } + + mutex_lock(&csi->mdev.graph_mutex); + + ret = __video_device_pipeline_start(csi->vdev, &csi->pipe); + if (ret) + goto err_unlock; + + ret = v4l2_subdev_call(&csi->sd, video, s_stream, 1); + if (ret) + goto err_stop; + + mutex_unlock(&csi->mdev.graph_mutex); + + return 0; + +err_stop: + __video_device_pipeline_stop(csi->vdev); +err_unlock: + mutex_unlock(&csi->mdev.graph_mutex); + dev_err(csi->dev, "pipeline start failed with %d\n", ret); +err_buffers: + spin_lock_irqsave(&csi->q_lock, flags); + list_for_each_entry_safe(buf, tmp, &csi->ready_q, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&csi->q_lock, flags); + return ret; +} + +static void imx7_csi_video_stop_streaming(struct vb2_queue *vq) +{ + struct imx7_csi *csi = vb2_get_drv_priv(vq); + struct imx7_csi_vb2_buffer *frame; + struct imx7_csi_vb2_buffer *tmp; + unsigned long flags; + + mutex_lock(&csi->mdev.graph_mutex); + v4l2_subdev_call(&csi->sd, video, s_stream, 0); + __video_device_pipeline_stop(csi->vdev); + mutex_unlock(&csi->mdev.graph_mutex); + + /* release all active buffers */ + spin_lock_irqsave(&csi->q_lock, flags); + list_for_each_entry_safe(frame, tmp, &csi->ready_q, list) { + list_del(&frame->list); + vb2_buffer_done(&frame->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&csi->q_lock, flags); +} + +static const struct vb2_ops imx7_csi_video_qops = { + .queue_setup = imx7_csi_video_queue_setup, + .buf_init = imx7_csi_video_buf_init, + .buf_prepare = imx7_csi_video_buf_prepare, + .buf_queue = imx7_csi_video_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = imx7_csi_video_start_streaming, + .stop_streaming = imx7_csi_video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - File Operations + */ + +static int imx7_csi_video_open(struct file *file) +{ + struct imx7_csi *csi = video_drvdata(file); + int ret; + + if (mutex_lock_interruptible(&csi->vdev_mutex)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret) { + dev_err(csi->dev, "v4l2_fh_open failed\n"); + goto out; + } + + ret = v4l2_pipeline_pm_get(&csi->vdev->entity); + if (ret) + v4l2_fh_release(file); + +out: + mutex_unlock(&csi->vdev_mutex); + return ret; +} + +static int imx7_csi_video_release(struct file *file) +{ + struct imx7_csi *csi = video_drvdata(file); + struct vb2_queue *vq = &csi->q; + + mutex_lock(&csi->vdev_mutex); + + if (file->private_data == vq->owner) { + vb2_queue_release(vq); + vq->owner = NULL; + } + + v4l2_pipeline_pm_put(&csi->vdev->entity); + + v4l2_fh_release(file); + mutex_unlock(&csi->vdev_mutex); + return 0; +} + +static const struct v4l2_file_operations imx7_csi_video_fops = { + .owner = THIS_MODULE, + .open = imx7_csi_video_open, + .release = imx7_csi_video_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Video Capture Device - Init & Cleanup + */ + +static struct imx7_csi_vb2_buffer *imx7_csi_video_next_buf(struct imx7_csi *csi) +{ + struct imx7_csi_vb2_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&csi->q_lock, flags); + + /* get next queued buffer */ + if (!list_empty(&csi->ready_q)) { + buf = list_entry(csi->ready_q.next, struct imx7_csi_vb2_buffer, + list); + list_del(&buf->list); + } + + spin_unlock_irqrestore(&csi->q_lock, flags); + + return buf; +} + +static void imx7_csi_video_init_format(struct imx7_csi *csi) +{ + struct v4l2_pix_format *pixfmt = &csi->vdev_fmt; + + pixfmt->width = IMX7_CSI_DEF_PIX_WIDTH; + pixfmt->height = IMX7_CSI_DEF_PIX_HEIGHT; + + csi->vdev_cc = __imx7_csi_video_try_fmt(pixfmt, &csi->vdev_compose); +} + +static int imx7_csi_video_register(struct imx7_csi *csi) +{ + struct v4l2_subdev *sd = &csi->sd; + struct v4l2_device *v4l2_dev = sd->v4l2_dev; + struct video_device *vdev = csi->vdev; + int ret; + + vdev->v4l2_dev = v4l2_dev; + + /* Initialize the default format and compose rectangle. */ + imx7_csi_video_init_format(csi); + + /* Register the video device. */ + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(csi->dev, "Failed to register video device\n"); + return ret; + } + + dev_info(csi->dev, "Registered %s as /dev/%s\n", vdev->name, + video_device_node_name(vdev)); + + /* Create the link from the CSI subdev to the video device. */ + ret = media_create_pad_link(&sd->entity, IMX7_CSI_PAD_SRC, + &vdev->entity, 0, MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(csi->dev, "failed to create link to device node\n"); + video_unregister_device(vdev); + return ret; + } + + return 0; +} + +static void imx7_csi_video_unregister(struct imx7_csi *csi) +{ + media_entity_cleanup(&csi->vdev->entity); + video_unregister_device(csi->vdev); +} + +static int imx7_csi_video_init(struct imx7_csi *csi) +{ + struct video_device *vdev; + struct vb2_queue *vq; + int ret; + + mutex_init(&csi->vdev_mutex); + INIT_LIST_HEAD(&csi->ready_q); + spin_lock_init(&csi->q_lock); + + /* Allocate and initialize the video device. */ + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + vdev->fops = &imx7_csi_video_fops; + vdev->ioctl_ops = &imx7_csi_video_ioctl_ops; + vdev->minor = -1; + vdev->release = video_device_release; + vdev->vfl_dir = VFL_DIR_RX; + vdev->tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING + | V4L2_CAP_IO_MC; + vdev->lock = &csi->vdev_mutex; + vdev->queue = &csi->q; + + snprintf(vdev->name, sizeof(vdev->name), "%s capture", csi->sd.name); + + video_set_drvdata(vdev, csi); + csi->vdev = vdev; + + /* Initialize the video device pad. */ + csi->vdev_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad); + if (ret) { + video_device_release(vdev); + return ret; + } + + /* Initialize the vb2 queue. */ + vq = &csi->q; + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_DMABUF; + vq->drv_priv = csi; + vq->buf_struct_size = sizeof(struct imx7_csi_vb2_buffer); + vq->ops = &imx7_csi_video_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->lock = &csi->vdev_mutex; + vq->min_buffers_needed = 2; + vq->dev = csi->dev; + + ret = vb2_queue_init(vq); + if (ret) { + dev_err(csi->dev, "vb2_queue_init failed\n"); + video_device_release(vdev); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdev Operations + */ + +static int imx7_csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct v4l2_subdev_state *sd_state; + int ret = 0; + + sd_state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = imx7_csi_init(csi, sd_state); + if (ret < 0) + goto out_unlock; + + ret = v4l2_subdev_call(csi->src_sd, video, s_stream, 1); + if (ret < 0) { + imx7_csi_deinit(csi, VB2_BUF_STATE_QUEUED); + goto out_unlock; + } + + imx7_csi_enable(csi); + } else { + imx7_csi_disable(csi); + + v4l2_subdev_call(csi->src_sd, video, s_stream, 0); + + imx7_csi_deinit(csi, VB2_BUF_STATE_ERROR); + } + + csi->is_streaming = !!enable; + +out_unlock: + v4l2_subdev_unlock_state(sd_state); + + return ret; +} + +static int imx7_csi_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + const struct imx7_csi_pixfmt *cc; + int i; + + cc = imx7_csi_find_mbus_format(IMX7_CSI_DEF_MBUS_CODE); + + for (i = 0; i < IMX7_CSI_PADS_NUM; i++) { + struct v4l2_mbus_framefmt *mf = + v4l2_subdev_get_pad_format(sd, sd_state, i); + + mf->code = IMX7_CSI_DEF_MBUS_CODE; + mf->width = IMX7_CSI_DEF_PIX_WIDTH; + mf->height = IMX7_CSI_DEF_PIX_HEIGHT; + mf->field = V4L2_FIELD_NONE; + + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(mf->colorspace); + mf->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(mf->colorspace); + mf->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(!cc->yuv, + mf->colorspace, mf->ycbcr_enc); + } + + return 0; +} + +static int imx7_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct v4l2_mbus_framefmt *in_fmt; + int ret = 0; + + in_fmt = v4l2_subdev_get_pad_format(sd, sd_state, IMX7_CSI_PAD_SINK); + + switch (code->pad) { + case IMX7_CSI_PAD_SINK: + ret = imx7_csi_enum_mbus_formats(&code->code, code->index); + break; + + case IMX7_CSI_PAD_SRC: + if (code->index != 0) { + ret = -EINVAL; + break; + } + + code->code = in_fmt->code; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* + * Default the colorspace in tryfmt to SRGB if set to an unsupported + * colorspace or not initialized. Then set the remaining colorimetry + * parameters based on the colorspace if they are uninitialized. + * + * tryfmt->code must be set on entry. + */ +static void imx7_csi_try_colorimetry(struct v4l2_mbus_framefmt *tryfmt) +{ + const struct imx7_csi_pixfmt *cc; + bool is_rgb = false; + + cc = imx7_csi_find_mbus_format(tryfmt->code); + if (cc && !cc->yuv) + is_rgb = true; + + switch (tryfmt->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_JPEG: + case V4L2_COLORSPACE_SRGB: + case V4L2_COLORSPACE_BT2020: + case V4L2_COLORSPACE_OPRGB: + case V4L2_COLORSPACE_DCI_P3: + case V4L2_COLORSPACE_RAW: + break; + default: + tryfmt->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + if (tryfmt->xfer_func == V4L2_XFER_FUNC_DEFAULT) + tryfmt->xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(tryfmt->colorspace); + + if (tryfmt->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + tryfmt->ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(tryfmt->colorspace); + + if (tryfmt->quantization == V4L2_QUANTIZATION_DEFAULT) + tryfmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, + tryfmt->colorspace, + tryfmt->ycbcr_enc); +} + +static void imx7_csi_try_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat, + const struct imx7_csi_pixfmt **cc) +{ + const struct imx7_csi_pixfmt *in_cc; + struct v4l2_mbus_framefmt *in_fmt; + u32 code; + + in_fmt = v4l2_subdev_get_pad_format(sd, sd_state, IMX7_CSI_PAD_SINK); + + switch (sdformat->pad) { + case IMX7_CSI_PAD_SRC: + in_cc = imx7_csi_find_mbus_format(in_fmt->code); + + sdformat->format.width = in_fmt->width; + sdformat->format.height = in_fmt->height; + sdformat->format.code = in_fmt->code; + sdformat->format.field = in_fmt->field; + *cc = in_cc; + + sdformat->format.colorspace = in_fmt->colorspace; + sdformat->format.xfer_func = in_fmt->xfer_func; + sdformat->format.quantization = in_fmt->quantization; + sdformat->format.ycbcr_enc = in_fmt->ycbcr_enc; + break; + + case IMX7_CSI_PAD_SINK: + *cc = imx7_csi_find_mbus_format(sdformat->format.code); + if (!*cc) { + code = IMX7_CSI_DEF_MBUS_CODE; + *cc = imx7_csi_find_mbus_format(code); + sdformat->format.code = code; + } + + if (sdformat->format.field != V4L2_FIELD_INTERLACED) + sdformat->format.field = V4L2_FIELD_NONE; + break; + } + + imx7_csi_try_colorimetry(&sdformat->format); +} + +static int imx7_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + const struct imx7_csi_pixfmt *outcc; + struct v4l2_mbus_framefmt *outfmt; + const struct imx7_csi_pixfmt *cc; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_format format; + + if (csi->is_streaming) + return -EBUSY; + + imx7_csi_try_fmt(sd, sd_state, sdformat, &cc); + + fmt = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); + + *fmt = sdformat->format; + + if (sdformat->pad == IMX7_CSI_PAD_SINK) { + /* propagate format to source pads */ + format.pad = IMX7_CSI_PAD_SRC; + format.which = sdformat->which; + format.format = sdformat->format; + imx7_csi_try_fmt(sd, sd_state, &format, &outcc); + + outfmt = v4l2_subdev_get_pad_format(sd, sd_state, + IMX7_CSI_PAD_SRC); + *outfmt = format.format; + } + + return 0; +} + +static int imx7_csi_pad_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + struct media_pad *pad = NULL; + unsigned int i; + int ret; + + /* + * Validate the source link, and record whether the source uses the + * parallel input or the CSI-2 receiver. + */ + ret = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); + if (ret) + return ret; + + switch (csi->src_sd->entity.function) { + case MEDIA_ENT_F_VID_IF_BRIDGE: + /* The input is the CSI-2 receiver. */ + csi->is_csi2 = true; + break; + + case MEDIA_ENT_F_VID_MUX: + /* The input is the mux, check its input. */ + for (i = 0; i < csi->src_sd->entity.num_pads; i++) { + struct media_pad *spad = &csi->src_sd->entity.pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + + pad = media_pad_remote_pad_first(spad); + if (pad) + break; + } + + if (!pad) + return -ENODEV; + + csi->is_csi2 = pad->entity->function == MEDIA_ENT_F_VID_IF_BRIDGE; + break; + + default: + /* + * The input is an external entity, it must use the parallel + * bus. + */ + csi->is_csi2 = false; + break; + } + + return 0; +} + +static int imx7_csi_registered(struct v4l2_subdev *sd) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + int ret; + + ret = imx7_csi_video_init(csi); + if (ret) + return ret; + + ret = imx7_csi_video_register(csi); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev); + if (ret) + goto err_unreg; + + ret = media_device_register(&csi->mdev); + if (ret) + goto err_unreg; + + return 0; + +err_unreg: + imx7_csi_video_unregister(csi); + return ret; +} + +static void imx7_csi_unregistered(struct v4l2_subdev *sd) +{ + struct imx7_csi *csi = v4l2_get_subdevdata(sd); + + imx7_csi_video_unregister(csi); +} + +static const struct v4l2_subdev_video_ops imx7_csi_video_ops = { + .s_stream = imx7_csi_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx7_csi_pad_ops = { + .init_cfg = imx7_csi_init_cfg, + .enum_mbus_code = imx7_csi_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx7_csi_set_fmt, + .link_validate = imx7_csi_pad_link_validate, +}; + +static const struct v4l2_subdev_ops imx7_csi_subdev_ops = { + .video = &imx7_csi_video_ops, + .pad = &imx7_csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx7_csi_internal_ops = { + .registered = imx7_csi_registered, + .unregistered = imx7_csi_unregistered, +}; + +/* ----------------------------------------------------------------------------- + * Media Entity Operations + */ + +static const struct media_entity_operations imx7_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + +static int imx7_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct imx7_csi *csi = imx7_csi_notifier_to_dev(notifier); + struct media_pad *sink = &csi->sd.entity.pads[IMX7_CSI_PAD_SINK]; + + csi->src_sd = sd; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static int imx7_csi_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct imx7_csi *csi = imx7_csi_notifier_to_dev(notifier); + + return v4l2_device_register_subdev_nodes(&csi->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations imx7_csi_notify_ops = { + .bound = imx7_csi_notify_bound, + .complete = imx7_csi_notify_complete, +}; + +static int imx7_csi_async_register(struct imx7_csi *csi) +{ + struct v4l2_async_connection *asd; + struct fwnode_handle *ep; + int ret; + + v4l2_async_nf_init(&csi->notifier, &csi->v4l2_dev); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) { + ret = dev_err_probe(csi->dev, -ENOTCONN, + "Failed to get remote endpoint\n"); + goto error; + } + + asd = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep, + struct v4l2_async_connection); + + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + ret = dev_err_probe(csi->dev, PTR_ERR(asd), + "Failed to add remote subdev to notifier\n"); + goto error; + } + + csi->notifier.ops = &imx7_csi_notify_ops; + + ret = v4l2_async_nf_register(&csi->notifier); + if (ret) + goto error; + + return 0; + +error: + v4l2_async_nf_cleanup(&csi->notifier); + return ret; +} + +static void imx7_csi_media_cleanup(struct imx7_csi *csi) +{ + v4l2_device_unregister(&csi->v4l2_dev); + media_device_unregister(&csi->mdev); + v4l2_subdev_cleanup(&csi->sd); + media_device_cleanup(&csi->mdev); +} + +static const struct media_device_ops imx7_csi_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +static int imx7_csi_media_dev_init(struct imx7_csi *csi) +{ + int ret; + + strscpy(csi->mdev.model, "imx-media", sizeof(csi->mdev.model)); + csi->mdev.ops = &imx7_csi_media_ops; + csi->mdev.dev = csi->dev; + + csi->v4l2_dev.mdev = &csi->mdev; + strscpy(csi->v4l2_dev.name, "imx-media", + sizeof(csi->v4l2_dev.name)); + snprintf(csi->mdev.bus_info, sizeof(csi->mdev.bus_info), + "platform:%s", dev_name(csi->mdev.dev)); + + media_device_init(&csi->mdev); + + ret = v4l2_device_register(csi->dev, &csi->v4l2_dev); + if (ret < 0) { + v4l2_err(&csi->v4l2_dev, + "Failed to register v4l2_device: %d\n", ret); + goto cleanup; + } + + return 0; + +cleanup: + media_device_cleanup(&csi->mdev); + + return ret; +} + +static int imx7_csi_media_init(struct imx7_csi *csi) +{ + unsigned int i; + int ret; + + /* add media device */ + ret = imx7_csi_media_dev_init(csi); + if (ret) + return ret; + + v4l2_subdev_init(&csi->sd, &imx7_csi_subdev_ops); + v4l2_set_subdevdata(&csi->sd, csi); + csi->sd.internal_ops = &imx7_csi_internal_ops; + csi->sd.entity.ops = &imx7_csi_entity_ops; + csi->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi->sd.dev = csi->dev; + csi->sd.owner = THIS_MODULE; + csi->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(csi->sd.name, sizeof(csi->sd.name), "csi"); + + for (i = 0; i < IMX7_CSI_PADS_NUM; i++) + csi->pad[i].flags = (i == IMX7_CSI_PAD_SINK) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi->sd.entity, IMX7_CSI_PADS_NUM, + csi->pad); + if (ret) + goto error; + + ret = v4l2_subdev_init_finalize(&csi->sd); + if (ret) + goto error; + + ret = v4l2_device_register_subdev(&csi->v4l2_dev, &csi->sd); + if (ret) + goto error; + + return 0; + +error: + imx7_csi_media_cleanup(csi); + return ret; +} + +static int imx7_csi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx7_csi *csi; + int ret; + + csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + + csi->dev = dev; + platform_set_drvdata(pdev, csi); + + spin_lock_init(&csi->irqlock); + + /* Acquire resources and install interrupt handler. */ + csi->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(csi->mclk)) { + ret = PTR_ERR(csi->mclk); + dev_err(dev, "Failed to get mclk: %d", ret); + return ret; + } + + csi->irq = platform_get_irq(pdev, 0); + if (csi->irq < 0) + return csi->irq; + + csi->regbase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi->regbase)) + return PTR_ERR(csi->regbase); + + csi->model = (enum imx_csi_model)(uintptr_t)of_device_get_match_data(&pdev->dev); + + ret = devm_request_irq(dev, csi->irq, imx7_csi_irq_handler, 0, "csi", + (void *)csi); + if (ret < 0) { + dev_err(dev, "Request CSI IRQ failed.\n"); + return ret; + } + + /* Initialize all the media device infrastructure. */ + ret = imx7_csi_media_init(csi); + if (ret) + return ret; + + ret = imx7_csi_async_register(csi); + if (ret) + goto err_media_cleanup; + + return 0; + +err_media_cleanup: + imx7_csi_media_cleanup(csi); + + return ret; +} + +static void imx7_csi_remove(struct platform_device *pdev) +{ + struct imx7_csi *csi = platform_get_drvdata(pdev); + + imx7_csi_media_cleanup(csi); + + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); + v4l2_async_unregister_subdev(&csi->sd); +} + +static const struct of_device_id imx7_csi_of_match[] = { + { .compatible = "fsl,imx8mq-csi", .data = (void *)IMX7_CSI_IMX8MQ }, + { .compatible = "fsl,imx7-csi", .data = (void *)IMX7_CSI_IMX7 }, + { .compatible = "fsl,imx6ul-csi", .data = (void *)IMX7_CSI_IMX7 }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx7_csi_of_match); + +static struct platform_driver imx7_csi_driver = { + .probe = imx7_csi_probe, + .remove_new = imx7_csi_remove, + .driver = { + .of_match_table = imx7_csi_of_match, + .name = "imx7-csi", + }, +}; +module_platform_driver(imx7_csi_driver); + +MODULE_DESCRIPTION("i.MX7 CSI subdev driver"); +MODULE_AUTHOR("Rui Miguel Silva <rui.silva@linaro.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx7-csi"); diff --git a/drivers/media/platform/nxp/imx8-isi/Kconfig b/drivers/media/platform/nxp/imx8-isi/Kconfig new file mode 100644 index 0000000000..fcff33fc26 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_IMX8_ISI + tristate "i.MX8 Image Sensor Interface (ISI) driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_DMA && PM + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select V4L2_MEM2MEM_DEV if VIDEO_IMX8_ISI_M2M + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + help + V4L2 driver for the Image Sensor Interface (ISI) found in various + i.MX8 SoCs. + +config VIDEO_IMX8_ISI_M2M + bool "i.MX8 Image Sensor Interface (ISI) memory-to-memory support" + depends on VIDEO_IMX8_ISI + help + Select 'yes' here to enable support for memory-to-memory processing + in the ISI driver. diff --git a/drivers/media/platform/nxp/imx8-isi/Makefile b/drivers/media/platform/nxp/imx8-isi/Makefile new file mode 100644 index 0000000000..4713c4e8b6 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +imx8-isi-y := imx8-isi-core.o imx8-isi-crossbar.o imx8-isi-gasket.o \ + imx8-isi-hw.o imx8-isi-pipe.o imx8-isi-video.o +imx8-isi-$(CONFIG_DEBUG_FS) += imx8-isi-debug.o +imx8-isi-$(CONFIG_VIDEO_IMX8_ISI_M2M) += imx8-isi-m2m.o + +obj-$(CONFIG_VIDEO_IMX8_ISI) += imx8-isi.o diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c new file mode 100644 index 0000000000..81be744e9f --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019-2020 NXP + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <media/media-device.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mc.h> + +#include "imx8-isi-core.h" + +/* ----------------------------------------------------------------------------- + * V4L2 async subdevs + */ + +struct mxc_isi_async_subdev { + struct v4l2_async_connection asd; + unsigned int port; +}; + +static inline struct mxc_isi_async_subdev * +asd_to_mxc_isi_async_subdev(struct v4l2_async_connection *asd) +{ + return container_of(asd, struct mxc_isi_async_subdev, asd); +}; + +static inline struct mxc_isi_dev * +notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct mxc_isi_dev, notifier); +}; + +static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); + struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asc); + struct media_pad *pad = &isi->crossbar.pads[masd->port]; + struct device_link *link; + + dev_dbg(isi->dev, "Bound subdev %s to crossbar input %u\n", sd->name, + masd->port); + + /* + * Enforce suspend/resume ordering between the source (supplier) and + * the ISI (consumer). The source will be suspended before and resume + * after the ISI. + */ + link = device_link_add(isi->dev, sd->dev, DL_FLAG_STATELESS); + if (!link) { + dev_err(isi->dev, + "Failed to create device link to source %s\n", sd->name); + return -EINVAL; + } + + return v4l2_create_fwnode_links_to_pad(sd, pad, link_flags); +} + +static int mxc_isi_async_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); + int ret; + + dev_dbg(isi->dev, "All subdevs bound\n"); + + ret = v4l2_device_register_subdev_nodes(&isi->v4l2_dev); + if (ret < 0) { + dev_err(isi->dev, + "Failed to register subdev nodes: %d\n", ret); + return ret; + } + + return media_device_register(&isi->media_dev); +} + +static const struct v4l2_async_notifier_operations mxc_isi_async_notifier_ops = { + .bound = mxc_isi_async_notifier_bound, + .complete = mxc_isi_async_notifier_complete, +}; + +static int mxc_isi_pipe_register(struct mxc_isi_pipe *pipe) +{ + int ret; + + ret = v4l2_device_register_subdev(&pipe->isi->v4l2_dev, &pipe->sd); + if (ret < 0) + return ret; + + return mxc_isi_video_register(pipe, &pipe->isi->v4l2_dev); +} + +static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe) +{ + mxc_isi_video_unregister(pipe); +} + +static int mxc_isi_v4l2_init(struct mxc_isi_dev *isi) +{ + struct fwnode_handle *node = dev_fwnode(isi->dev); + struct media_device *media_dev = &isi->media_dev; + struct v4l2_device *v4l2_dev = &isi->v4l2_dev; + unsigned int i; + int ret; + + /* Initialize the media device. */ + strscpy(media_dev->model, "FSL Capture Media Device", + sizeof(media_dev->model)); + media_dev->dev = isi->dev; + + media_device_init(media_dev); + + /* Initialize and register the V4L2 device. */ + v4l2_dev->mdev = media_dev; + strscpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(isi->dev, v4l2_dev); + if (ret < 0) { + dev_err(isi->dev, + "Failed to register V4L2 device: %d\n", ret); + goto err_media; + } + + /* Register the crossbar switch subdev. */ + ret = mxc_isi_crossbar_register(&isi->crossbar); + if (ret < 0) { + dev_err(isi->dev, "Failed to register crossbar: %d\n", ret); + goto err_v4l2; + } + + /* Register the pipeline subdevs and link them to the crossbar switch. */ + for (i = 0; i < isi->pdata->num_channels; ++i) { + struct mxc_isi_pipe *pipe = &isi->pipes[i]; + + ret = mxc_isi_pipe_register(pipe); + if (ret < 0) { + dev_err(isi->dev, "Failed to register pipe%u: %d\n", i, + ret); + goto err_v4l2; + } + + ret = media_create_pad_link(&isi->crossbar.sd.entity, + isi->crossbar.num_sinks + i, + &pipe->sd.entity, + MXC_ISI_PIPE_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret < 0) + goto err_v4l2; + } + + /* Register the M2M device. */ + ret = mxc_isi_m2m_register(isi, v4l2_dev); + if (ret < 0) { + dev_err(isi->dev, "Failed to register M2M device: %d\n", ret); + goto err_v4l2; + } + + /* Initialize, fill and register the async notifier. */ + v4l2_async_nf_init(&isi->notifier, v4l2_dev); + isi->notifier.ops = &mxc_isi_async_notifier_ops; + + for (i = 0; i < isi->pdata->num_ports; ++i) { + struct mxc_isi_async_subdev *masd; + struct fwnode_handle *ep; + + ep = fwnode_graph_get_endpoint_by_id(node, i, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + + if (!ep) + continue; + + masd = v4l2_async_nf_add_fwnode_remote(&isi->notifier, ep, + struct mxc_isi_async_subdev); + fwnode_handle_put(ep); + + if (IS_ERR(masd)) { + ret = PTR_ERR(masd); + goto err_m2m; + } + + masd->port = i; + } + + ret = v4l2_async_nf_register(&isi->notifier); + if (ret < 0) { + dev_err(isi->dev, + "Failed to register async notifier: %d\n", ret); + goto err_m2m; + } + + return 0; + +err_m2m: + mxc_isi_m2m_unregister(isi); + v4l2_async_nf_cleanup(&isi->notifier); +err_v4l2: + v4l2_device_unregister(v4l2_dev); +err_media: + media_device_cleanup(media_dev); + return ret; +} + +static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi) +{ + unsigned int i; + + v4l2_async_nf_unregister(&isi->notifier); + v4l2_async_nf_cleanup(&isi->notifier); + + v4l2_device_unregister(&isi->v4l2_dev); + media_device_unregister(&isi->media_dev); + + mxc_isi_m2m_unregister(isi); + + for (i = 0; i < isi->pdata->num_channels; ++i) + mxc_isi_pipe_unregister(&isi->pipes[i]); + + mxc_isi_crossbar_unregister(&isi->crossbar); + + media_device_cleanup(&isi->media_dev); +} + +/* ----------------------------------------------------------------------------- + * Device information + */ + +/* Panic will assert when the buffers are 50% full */ + +/* For i.MX8QXP C0 and i.MX8MN ISI IER version */ +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { + .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 }, + .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 }, + .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 }, + + .panic_y_buf_en = {.offset = 20, .mask = 0x100000 }, + .panic_u_buf_en = {.offset = 22, .mask = 0x400000 }, + .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 }, +}; + +/* For i.MX8MP ISI IER version */ +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { + .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 }, + .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 }, + .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 }, + + .panic_y_buf_en = {.offset = 19, .mask = 0x80000 }, + .panic_u_buf_en = {.offset = 21, .mask = 0x200000 }, + .panic_v_buf_en = {.offset = 23, .mask = 0x800000 }, +}; + +/* Panic will assert when the buffers are 50% full */ +static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = { + .panic_set_thd_y = { .mask = 0x0000f, .offset = 0, .threshold = 0x7 }, + .panic_set_thd_u = { .mask = 0x00f00, .offset = 8, .threshold = 0x7 }, + .panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 }, +}; + +static const struct clk_bulk_data mxc_imx8mn_clks[] = { + { .id = "axi" }, + { .id = "apb" }, +}; + +static const struct mxc_isi_plat_data mxc_imx8mn_data = { + .model = MXC_ISI_IMX8MN, + .num_ports = 1, + .num_channels = 1, + .reg_offset = 0, + .ier_reg = &mxc_imx8_isi_ier_v1, + .set_thd = &mxc_imx8_isi_thd_v1, + .clks = mxc_imx8mn_clks, + .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), + .buf_active_reverse = false, + .gasket_ops = &mxc_imx8_gasket_ops, + .has_36bit_dma = false, +}; + +static const struct mxc_isi_plat_data mxc_imx8mp_data = { + .model = MXC_ISI_IMX8MP, + .num_ports = 2, + .num_channels = 2, + .reg_offset = 0x2000, + .ier_reg = &mxc_imx8_isi_ier_v2, + .set_thd = &mxc_imx8_isi_thd_v1, + .clks = mxc_imx8mn_clks, + .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), + .buf_active_reverse = true, + .gasket_ops = &mxc_imx8_gasket_ops, + .has_36bit_dma = true, +}; + +static const struct mxc_isi_plat_data mxc_imx93_data = { + .model = MXC_ISI_IMX93, + .num_ports = 1, + .num_channels = 1, + .reg_offset = 0, + .ier_reg = &mxc_imx8_isi_ier_v2, + .set_thd = &mxc_imx8_isi_thd_v1, + .clks = mxc_imx8mn_clks, + .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), + .buf_active_reverse = true, + .gasket_ops = &mxc_imx93_gasket_ops, + .has_36bit_dma = false, +}; + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int mxc_isi_pm_suspend(struct device *dev) +{ + struct mxc_isi_dev *isi = dev_get_drvdata(dev); + unsigned int i; + + for (i = 0; i < isi->pdata->num_channels; ++i) { + struct mxc_isi_pipe *pipe = &isi->pipes[i]; + + mxc_isi_video_suspend(pipe); + } + + return pm_runtime_force_suspend(dev); +} + +static int mxc_isi_pm_resume(struct device *dev) +{ + struct mxc_isi_dev *isi = dev_get_drvdata(dev); + unsigned int i; + int err = 0; + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + for (i = 0; i < isi->pdata->num_channels; ++i) { + struct mxc_isi_pipe *pipe = &isi->pipes[i]; + + ret = mxc_isi_video_resume(pipe); + if (ret) { + dev_err(dev, "Failed to resume pipeline %u (%d)\n", i, + ret); + /* + * Record the last error as it's as meaningful as any, + * and continue resuming the other pipelines. + */ + err = ret; + } + } + + return err; +} + +static int mxc_isi_runtime_suspend(struct device *dev) +{ + struct mxc_isi_dev *isi = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks); + + return 0; +} + +static int mxc_isi_runtime_resume(struct device *dev) +{ + struct mxc_isi_dev *isi = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks); + if (ret) { + dev_err(dev, "Failed to enable clocks (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops mxc_isi_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume) + RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe, remove & driver + */ + +static int mxc_isi_clk_get(struct mxc_isi_dev *isi) +{ + unsigned int size = isi->pdata->num_clks + * sizeof(*isi->clks); + int ret; + + isi->clks = devm_kmalloc(isi->dev, size, GFP_KERNEL); + if (!isi->clks) + return -ENOMEM; + + memcpy(isi->clks, isi->pdata->clks, size); + + ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks, + isi->clks); + if (ret < 0) { + dev_err(isi->dev, "Failed to acquire clocks: %d\n", + ret); + return ret; + } + + return 0; +} + +static int mxc_isi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxc_isi_dev *isi; + unsigned int dma_size; + unsigned int i; + int ret = 0; + + isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL); + if (!isi) + return -ENOMEM; + + isi->dev = dev; + platform_set_drvdata(pdev, isi); + + isi->pdata = of_device_get_match_data(dev); + + isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]), + GFP_KERNEL); + if (!isi->pipes) + return -ENOMEM; + + ret = mxc_isi_clk_get(isi); + if (ret < 0) { + dev_err(dev, "Failed to get clocks\n"); + return ret; + } + + isi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(isi->regs)) { + dev_err(dev, "Failed to get ISI register map\n"); + return PTR_ERR(isi->regs); + } + + if (isi->pdata->gasket_ops) { + isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node, + "fsl,blk-ctrl"); + if (IS_ERR(isi->gasket)) { + ret = PTR_ERR(isi->gasket); + dev_err(dev, "failed to get gasket: %d\n", ret); + return ret; + } + } + + dma_size = isi->pdata->has_36bit_dma ? 36 : 32; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); + if (ret) { + dev_err(dev, "failed to set DMA mask\n"); + return ret; + } + + pm_runtime_enable(dev); + + ret = mxc_isi_crossbar_init(isi); + if (ret) { + dev_err(dev, "Failed to initialize crossbar: %d\n", ret); + goto err_pm; + } + + for (i = 0; i < isi->pdata->num_channels; ++i) { + ret = mxc_isi_pipe_init(isi, i); + if (ret < 0) { + dev_err(dev, "Failed to initialize pipe%u: %d\n", i, + ret); + goto err_xbar; + } + } + + ret = mxc_isi_v4l2_init(isi); + if (ret < 0) { + dev_err(dev, "Failed to initialize V4L2: %d\n", ret); + goto err_xbar; + } + + mxc_isi_debug_init(isi); + + return 0; + +err_xbar: + mxc_isi_crossbar_cleanup(&isi->crossbar); +err_pm: + pm_runtime_disable(isi->dev); + return ret; +} + +static int mxc_isi_remove(struct platform_device *pdev) +{ + struct mxc_isi_dev *isi = platform_get_drvdata(pdev); + unsigned int i; + + mxc_isi_debug_cleanup(isi); + + for (i = 0; i < isi->pdata->num_channels; ++i) { + struct mxc_isi_pipe *pipe = &isi->pipes[i]; + + mxc_isi_pipe_cleanup(pipe); + } + + mxc_isi_crossbar_cleanup(&isi->crossbar); + mxc_isi_v4l2_cleanup(isi); + + pm_runtime_disable(isi->dev); + + return 0; +} + +static const struct of_device_id mxc_isi_of_match[] = { + { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, + { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data }, + { .compatible = "fsl,imx93-isi", .data = &mxc_imx93_data }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mxc_isi_of_match); + +static struct platform_driver mxc_isi_driver = { + .probe = mxc_isi_probe, + .remove = mxc_isi_remove, + .driver = { + .of_match_table = mxc_isi_of_match, + .name = MXC_ISI_DRIVER_NAME, + .pm = pm_ptr(&mxc_isi_pm_ops), + } +}; +module_platform_driver(mxc_isi_driver); + +MODULE_ALIAS("ISI"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h new file mode 100644 index 0000000000..2810ebe9b5 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * V4L2 Capture ISI subdev for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor to memory or DC + * Copyright 2019-2020 NXP + */ + +#ifndef __MXC_ISI_CORE_H__ +#define __MXC_ISI_CORE_H__ + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/media-device.h> +#include <media/media-entity.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-v4l2.h> + +struct clk_bulk_data; +struct dentry; +struct device; +struct media_intf_devnode; +struct regmap; +struct v4l2_m2m_dev; + +/* Pipeline pads */ +#define MXC_ISI_PIPE_PAD_SINK 0 +#define MXC_ISI_PIPE_PAD_SOURCE 1 +#define MXC_ISI_PIPE_PADS_NUM 2 + +#define MXC_ISI_MIN_WIDTH 1U +#define MXC_ISI_MIN_HEIGHT 1U +#define MXC_ISI_MAX_WIDTH_UNCHAINED 2048U +#define MXC_ISI_MAX_WIDTH_CHAINED 4096U +#define MXC_ISI_MAX_HEIGHT 8191U + +#define MXC_ISI_DEF_WIDTH 1920U +#define MXC_ISI_DEF_HEIGHT 1080U +#define MXC_ISI_DEF_MBUS_CODE_SINK MEDIA_BUS_FMT_UYVY8_1X16 +#define MXC_ISI_DEF_MBUS_CODE_SOURCE MEDIA_BUS_FMT_YUV8_1X24 +#define MXC_ISI_DEF_PIXEL_FORMAT V4L2_PIX_FMT_YUYV +#define MXC_ISI_DEF_COLOR_SPACE V4L2_COLORSPACE_SRGB +#define MXC_ISI_DEF_YCBCR_ENC V4L2_YCBCR_ENC_601 +#define MXC_ISI_DEF_QUANTIZATION V4L2_QUANTIZATION_LIM_RANGE +#define MXC_ISI_DEF_XFER_FUNC V4L2_XFER_FUNC_SRGB + +#define MXC_ISI_DRIVER_NAME "mxc-isi" +#define MXC_ISI_CAPTURE "mxc-isi-cap" +#define MXC_ISI_M2M "mxc-isi-m2m" +#define MXC_MAX_PLANES 3 + +struct mxc_isi_dev; +struct mxc_isi_m2m_ctx; + +enum mxc_isi_buf_id { + MXC_ISI_BUF1 = 0x0, + MXC_ISI_BUF2, +}; + +enum mxc_isi_encoding { + MXC_ISI_ENC_RAW, + MXC_ISI_ENC_RGB, + MXC_ISI_ENC_YUV, +}; + +enum mxc_isi_input_id { + /* Inputs from the crossbar switch range from 0 to 15 */ + MXC_ISI_INPUT_MEM = 16, +}; + +enum mxc_isi_video_type { + MXC_ISI_VIDEO_CAP = BIT(0), + MXC_ISI_VIDEO_M2M_OUT = BIT(1), + MXC_ISI_VIDEO_M2M_CAP = BIT(2), +}; + +struct mxc_isi_format_info { + u32 mbus_code; + u32 fourcc; + enum mxc_isi_video_type type; + u32 isi_in_format; + u32 isi_out_format; + u8 mem_planes; + u8 color_planes; + u8 depth[MXC_MAX_PLANES]; + u8 hsub; + u8 vsub; + enum mxc_isi_encoding encoding; +}; + +struct mxc_isi_bus_format_info { + u32 mbus_code; + u32 output; + u32 pads; + enum mxc_isi_encoding encoding; +}; + +struct mxc_isi_buffer { + struct vb2_v4l2_buffer v4l2_buf; + struct list_head list; + dma_addr_t dma_addrs[3]; + enum mxc_isi_buf_id id; + bool discard; +}; + +struct mxc_isi_reg { + u32 offset; + u32 mask; +}; + +struct mxc_isi_ier_reg { + /* Overflow Y/U/V trigger enable*/ + struct mxc_isi_reg oflw_y_buf_en; + struct mxc_isi_reg oflw_u_buf_en; + struct mxc_isi_reg oflw_v_buf_en; + + /* Excess overflow Y/U/V trigger enable*/ + struct mxc_isi_reg excs_oflw_y_buf_en; + struct mxc_isi_reg excs_oflw_u_buf_en; + struct mxc_isi_reg excs_oflw_v_buf_en; + + /* Panic Y/U/V trigger enable*/ + struct mxc_isi_reg panic_y_buf_en; + struct mxc_isi_reg panic_v_buf_en; + struct mxc_isi_reg panic_u_buf_en; +}; + +struct mxc_isi_panic_thd { + u32 mask; + u32 offset; + u32 threshold; +}; + +struct mxc_isi_set_thd { + struct mxc_isi_panic_thd panic_set_thd_y; + struct mxc_isi_panic_thd panic_set_thd_u; + struct mxc_isi_panic_thd panic_set_thd_v; +}; + +struct mxc_gasket_ops { + void (*enable)(struct mxc_isi_dev *isi, + const struct v4l2_mbus_frame_desc *fd, + const struct v4l2_mbus_framefmt *fmt, + const unsigned int port); + void (*disable)(struct mxc_isi_dev *isi, const unsigned int port); +}; + +enum model { + MXC_ISI_IMX8MN, + MXC_ISI_IMX8MP, + MXC_ISI_IMX93, +}; + +struct mxc_isi_plat_data { + enum model model; + unsigned int num_ports; + unsigned int num_channels; + unsigned int reg_offset; + const struct mxc_isi_ier_reg *ier_reg; + const struct mxc_isi_set_thd *set_thd; + const struct mxc_gasket_ops *gasket_ops; + const struct clk_bulk_data *clks; + unsigned int num_clks; + bool buf_active_reverse; + bool has_36bit_dma; +}; + +struct mxc_isi_dma_buffer { + size_t size; + void *addr; + dma_addr_t dma; +}; + +struct mxc_isi_input { + unsigned int enable_count; +}; + +struct mxc_isi_crossbar { + struct mxc_isi_dev *isi; + + unsigned int num_sinks; + unsigned int num_sources; + struct mxc_isi_input *inputs; + + struct v4l2_subdev sd; + struct media_pad *pads; +}; + +struct mxc_isi_video { + struct mxc_isi_pipe *pipe; + + struct video_device vdev; + struct media_pad pad; + + /* Protects is_streaming, and the vdev and vb2_q operations */ + struct mutex lock; + bool is_streaming; + + struct v4l2_pix_format_mplane pix; + const struct mxc_isi_format_info *fmtinfo; + + struct { + struct v4l2_ctrl_handler handler; + unsigned int alpha; + bool hflip; + bool vflip; + } ctrls; + + struct vb2_queue vb2_q; + struct mxc_isi_buffer buf_discard[3]; + struct list_head out_pending; + struct list_head out_active; + struct list_head out_discard; + u32 frame_count; + /* Protects out_pending, out_active, out_discard and frame_count */ + spinlock_t buf_lock; + + struct mxc_isi_dma_buffer discard_buffer[MXC_MAX_PLANES]; +}; + +typedef void(*mxc_isi_pipe_irq_t)(struct mxc_isi_pipe *, u32); + +struct mxc_isi_pipe { + struct mxc_isi_dev *isi; + u32 id; + void __iomem *regs; + + struct media_pipeline pipe; + + struct v4l2_subdev sd; + struct media_pad pads[MXC_ISI_PIPE_PADS_NUM]; + + struct mxc_isi_video video; + + /* + * Protects use_count, irq_handler, res_available, res_acquired, + * chained_res, and the CHNL_CTRL register. + */ + struct mutex lock; + unsigned int use_count; + mxc_isi_pipe_irq_t irq_handler; + +#define MXC_ISI_CHANNEL_RES_LINE_BUF BIT(0) +#define MXC_ISI_CHANNEL_RES_OUTPUT_BUF BIT(1) + u8 available_res; + u8 acquired_res; + u8 chained_res; + bool chained; +}; + +struct mxc_isi_m2m { + struct mxc_isi_dev *isi; + struct mxc_isi_pipe *pipe; + + struct media_pad pad; + struct video_device vdev; + struct media_intf_devnode *intf; + struct v4l2_m2m_dev *m2m_dev; + + /* Protects last_ctx, usage_count and chained_count */ + struct mutex lock; + + struct mxc_isi_m2m_ctx *last_ctx; + int usage_count; + int chained_count; +}; + +struct mxc_isi_dev { + struct device *dev; + + const struct mxc_isi_plat_data *pdata; + + void __iomem *regs; + struct clk_bulk_data *clks; + struct regmap *gasket; + + struct mxc_isi_crossbar crossbar; + struct mxc_isi_pipe *pipes; + struct mxc_isi_m2m m2m; + + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + + struct dentry *debugfs_root; +}; + +extern const struct mxc_gasket_ops mxc_imx8_gasket_ops; +extern const struct mxc_gasket_ops mxc_imx93_gasket_ops; + +int mxc_isi_crossbar_init(struct mxc_isi_dev *isi); +void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar); +int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar); +void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar); + +const struct mxc_isi_bus_format_info * +mxc_isi_bus_format_by_code(u32 code, unsigned int pad); +const struct mxc_isi_bus_format_info * +mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad); +const struct mxc_isi_format_info * +mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type); +const struct mxc_isi_format_info * +mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type); +const struct mxc_isi_format_info * +mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix, + enum mxc_isi_video_type type); + +int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id); +void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe); +int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe, + mxc_isi_pipe_irq_t irq_handler); +void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe); +int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe); +void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe); + +int mxc_isi_video_register(struct mxc_isi_pipe *pipe, + struct v4l2_device *v4l2_dev); +void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe); +void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe); +int mxc_isi_video_resume(struct mxc_isi_pipe *pipe); +int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format, + const struct mxc_isi_format_info *info, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[]); +void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3], + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *pix); +int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2, + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *pix); + +#ifdef CONFIG_VIDEO_IMX8_ISI_M2M +int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev); +int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi); +#else +static inline int mxc_isi_m2m_register(struct mxc_isi_dev *isi, + struct v4l2_device *v4l2_dev) +{ + return 0; +} +static inline int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi) +{ + return 0; +} +#endif + +int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe, + mxc_isi_pipe_irq_t irq_handler, bool bypass); +void mxc_isi_channel_release(struct mxc_isi_pipe *pipe); +void mxc_isi_channel_get(struct mxc_isi_pipe *pipe); +void mxc_isi_channel_put(struct mxc_isi_pipe *pipe); +void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe); +void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe); +int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass); +void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe); + +void mxc_isi_channel_config(struct mxc_isi_pipe *pipe, + enum mxc_isi_input_id input, + const struct v4l2_area *in_size, + const struct v4l2_area *scale, + const struct v4l2_rect *crop, + enum mxc_isi_encoding in_encoding, + enum mxc_isi_encoding out_encoding); + +void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe, + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *format); +void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe, + const struct mxc_isi_format_info *info, + struct v4l2_pix_format_mplane *format); +void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe); + +void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha); +void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip); + +void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr); +void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe, + const dma_addr_t dma_addrs[3], + enum mxc_isi_buf_id buf_id); + +u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear); +void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe); + +#if IS_ENABLED(CONFIG_DEBUG_FS) +void mxc_isi_debug_init(struct mxc_isi_dev *isi); +void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi); +#else +static inline void mxc_isi_debug_init(struct mxc_isi_dev *isi) +{ +} +static inline void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi) +{ +} +#endif + +#endif /* __MXC_ISI_CORE_H__ */ diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c new file mode 100644 index 0000000000..792f031e03 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i.MX8 ISI - Input crossbar switch + * + * Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/minmax.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "imx8-isi-core.h" + +static inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mxc_isi_crossbar, sd); +} + +static int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar *xbar, + struct v4l2_subdev_state *state, + struct v4l2_subdev *remote_sd, + u32 remote_pad, unsigned int port) +{ + struct mxc_isi_dev *isi = xbar->isi; + const struct mxc_gasket_ops *gasket_ops = isi->pdata->gasket_ops; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_mbus_frame_desc fd; + int ret; + + if (!gasket_ops) + return 0; + + /* + * Configure and enable the gasket with the frame size and CSI-2 data + * type. For YUV422 8-bit, enable dual component mode unconditionally, + * to match the configuration of the CSIS. + */ + + ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd); + if (ret) { + dev_err(isi->dev, + "failed to get frame descriptor from '%s':%u: %d\n", + remote_sd->name, remote_pad, ret); + return ret; + } + + if (fd.num_entries != 1) { + dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n", + remote_sd->name, remote_pad); + return -EINVAL; + } + + fmt = v4l2_subdev_state_get_stream_format(state, port, 0); + if (!fmt) + return -EINVAL; + + gasket_ops->enable(isi, &fd, fmt, port); + return 0; +} + +static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar, + unsigned int port) +{ + struct mxc_isi_dev *isi = xbar->isi; + const struct mxc_gasket_ops *gasket_ops = isi->pdata->gasket_ops; + + if (!gasket_ops) + return; + + gasket_ops->disable(isi, port); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format = { + .code = MXC_ISI_DEF_MBUS_CODE_SINK, + .width = MXC_ISI_DEF_WIDTH, + .height = MXC_ISI_DEF_HEIGHT, + .field = V4L2_FIELD_NONE, + .colorspace = MXC_ISI_DEF_COLOR_SPACE, + .ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC, + .quantization = MXC_ISI_DEF_QUANTIZATION, + .xfer_func = MXC_ISI_DEF_XFER_FUNC, +}; + +static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + struct v4l2_subdev_route *route; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_NO_N_TO_1); + if (ret) + return ret; + + /* The memory input can be routed to the first pipeline only. */ + for_each_active_route(&state->routing, route) { + if (route->sink_pad == xbar->num_sinks - 1 && + route->source_pad != xbar->num_sinks) { + dev_dbg(xbar->isi->dev, + "invalid route from memory input (%u) to pipe %u\n", + route->sink_pad, + route->source_pad - xbar->num_sinks); + return -EINVAL; + } + } + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &mxc_isi_crossbar_default_format); +} + +static struct v4l2_subdev * +mxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar *xbar, + struct v4l2_subdev_state *state, + u32 source_pad, u64 source_streams, + u32 *__sink_pad, u64 *__sink_streams, + u32 *remote_pad) +{ + struct v4l2_subdev_route *route; + struct v4l2_subdev *sd; + struct media_pad *pad; + u64 sink_streams = 0; + int sink_pad = -1; + + /* + * Translate the source pad and streams to the sink side. The routing + * validation forbids stream merging, so all matching entries in the + * routing table are guaranteed to have the same sink pad. + * + * TODO: This is likely worth a helper function, it could perhaps be + * supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1. + */ + for_each_active_route(&state->routing, route) { + if (route->source_pad != source_pad || + !(source_streams & BIT(route->source_stream))) + continue; + + sink_streams |= BIT(route->sink_stream); + sink_pad = route->sink_pad; + } + + if (sink_pad < 0) { + dev_dbg(xbar->isi->dev, + "no stream connected to pipeline %u\n", + source_pad - xbar->num_sinks); + return ERR_PTR(-EPIPE); + } + + pad = media_pad_remote_pad_first(&xbar->pads[sink_pad]); + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (!sd) { + dev_dbg(xbar->isi->dev, + "no entity connected to crossbar input %u\n", + sink_pad); + return ERR_PTR(-EPIPE); + } + + *__sink_pad = sink_pad; + *__sink_streams = sink_streams; + *remote_pad = pad->index; + + return sd; +} + +static int mxc_isi_crossbar_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + struct v4l2_subdev_krouting routing = { }; + struct v4l2_subdev_route *routes; + unsigned int i; + int ret; + + /* + * Create a 1:1 mapping between pixel link inputs and outputs to + * pipelines by default. + */ + routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL); + if (!routes) + return -ENOMEM; + + for (i = 0; i < xbar->num_sources; ++i) { + struct v4l2_subdev_route *route = &routes[i]; + + route->sink_pad = i; + route->source_pad = i + xbar->num_sinks; + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + } + + routing.num_routes = xbar->num_sources; + routing.routes = routes; + + ret = __mxc_isi_crossbar_set_routing(sd, state, &routing); + + kfree(routes); + + return ret; +} + +static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + const struct mxc_isi_bus_format_info *info; + + if (code->pad >= xbar->num_sinks) { + const struct v4l2_mbus_framefmt *format; + + /* + * The media bus code on source pads is identical to the + * connected sink pad. + */ + if (code->index > 0) + return -EINVAL; + + format = v4l2_subdev_state_get_opposite_stream_format(state, + code->pad, + code->stream); + if (!format) + return -EINVAL; + + code->code = format->code; + + return 0; + } + + info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK); + if (!info) + return -EINVAL; + + code->code = info->mbus_code; + + return 0; +} + +static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_subdev_route *route; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_pad_is_streaming(&xbar->pads[fmt->pad])) + return -EBUSY; + + /* + * The source pad format is always identical to the sink pad format and + * can't be modified. + */ + if (fmt->pad >= xbar->num_sinks) + return v4l2_subdev_get_fmt(sd, state, fmt); + + /* Validate the requested format. */ + if (!mxc_isi_bus_format_by_code(fmt->format.code, MXC_ISI_PIPE_PAD_SINK)) + fmt->format.code = MXC_ISI_DEF_MBUS_CODE_SINK; + + fmt->format.width = clamp_t(unsigned int, fmt->format.width, + MXC_ISI_MIN_WIDTH, MXC_ISI_MAX_WIDTH_CHAINED); + fmt->format.height = clamp_t(unsigned int, fmt->format.height, + MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT); + fmt->format.field = V4L2_FIELD_NONE; + + /* + * Set the format on the sink stream and propagate it to the source + * streams. + */ + sink_fmt = v4l2_subdev_state_get_stream_format(state, fmt->pad, + fmt->stream); + if (!sink_fmt) + return -EINVAL; + + *sink_fmt = fmt->format; + + /* TODO: A format propagation helper would be useful. */ + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_framefmt *source_fmt; + + if (route->sink_pad != fmt->pad || + route->sink_stream != fmt->stream) + continue; + + source_fmt = v4l2_subdev_state_get_stream_format(state, route->source_pad, + route->source_stream); + if (!source_fmt) + return -EINVAL; + + *source_fmt = fmt->format; + } + + return 0; +} + +static int mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + return __mxc_isi_crossbar_set_routing(sd, state, routing); +} + +static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + struct v4l2_subdev *remote_sd; + struct mxc_isi_input *input; + u64 sink_streams; + u32 sink_pad; + u32 remote_pad; + int ret; + + remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask, + &sink_pad, &sink_streams, + &remote_pad); + if (IS_ERR(remote_sd)) + return PTR_ERR(remote_sd); + + input = &xbar->inputs[sink_pad]; + + /* + * TODO: Track per-stream enable counts to support multiplexed + * streams. + */ + if (!input->enable_count) { + ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd, + remote_pad, sink_pad); + if (ret) + return ret; + + ret = v4l2_subdev_enable_streams(remote_sd, remote_pad, + sink_streams); + if (ret) { + dev_err(xbar->isi->dev, + "failed to %s streams 0x%llx on '%s':%u: %d\n", + "enable", sink_streams, remote_sd->name, + remote_pad, ret); + mxc_isi_crossbar_gasket_disable(xbar, sink_pad); + return ret; + } + } + + input->enable_count++; + + return 0; +} + +static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd); + struct v4l2_subdev *remote_sd; + struct mxc_isi_input *input; + u64 sink_streams; + u32 sink_pad; + u32 remote_pad; + int ret = 0; + + remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask, + &sink_pad, &sink_streams, + &remote_pad); + if (IS_ERR(remote_sd)) + return PTR_ERR(remote_sd); + + input = &xbar->inputs[sink_pad]; + + input->enable_count--; + + if (!input->enable_count) { + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad, + sink_streams); + if (ret) + dev_err(xbar->isi->dev, + "failed to %s streams 0x%llx on '%s':%u: %d\n", + "disable", sink_streams, remote_sd->name, + remote_pad, ret); + + mxc_isi_crossbar_gasket_disable(xbar, sink_pad); + } + + return ret; +} + +static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = { + .init_cfg = mxc_isi_crossbar_init_cfg, + .enum_mbus_code = mxc_isi_crossbar_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mxc_isi_crossbar_set_fmt, + .set_routing = mxc_isi_crossbar_set_routing, + .enable_streams = mxc_isi_crossbar_enable_streams, + .disable_streams = mxc_isi_crossbar_disable_streams, +}; + +static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = { + .pad = &mxc_isi_crossbar_subdev_pad_ops, +}; + +static const struct media_entity_operations mxc_isi_cross_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +/* ----------------------------------------------------------------------------- + * Init & cleanup + */ + +int mxc_isi_crossbar_init(struct mxc_isi_dev *isi) +{ + struct mxc_isi_crossbar *xbar = &isi->crossbar; + struct v4l2_subdev *sd = &xbar->sd; + unsigned int num_pads; + unsigned int i; + int ret; + + xbar->isi = isi; + + v4l2_subdev_init(sd, &mxc_isi_crossbar_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + strscpy(sd->name, "crossbar", sizeof(sd->name)); + sd->dev = isi->dev; + + sd->entity.function = MEDIA_ENT_F_VID_MUX; + sd->entity.ops = &mxc_isi_cross_entity_ops; + + /* + * The subdev has one sink and one source per port, plus one sink for + * the memory input. + */ + xbar->num_sinks = isi->pdata->num_ports + 1; + xbar->num_sources = isi->pdata->num_ports; + num_pads = xbar->num_sinks + xbar->num_sources; + + xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL); + if (!xbar->pads) + return -ENOMEM; + + xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs), + GFP_KERNEL); + if (!xbar->inputs) { + ret = -ENOMEM; + goto err_free; + } + + for (i = 0; i < xbar->num_sinks; ++i) + xbar->pads[i].flags = MEDIA_PAD_FL_SINK; + for (i = 0; i < xbar->num_sources; ++i) + xbar->pads[i + xbar->num_sinks].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads); + if (ret) + goto err_free; + + ret = v4l2_subdev_init_finalize(sd); + if (ret < 0) + goto err_entity; + + return 0; + +err_entity: + media_entity_cleanup(&sd->entity); +err_free: + kfree(xbar->pads); + kfree(xbar->inputs); + + return ret; +} + +void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar) +{ + media_entity_cleanup(&xbar->sd.entity); + kfree(xbar->pads); + kfree(xbar->inputs); +} + +int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar) +{ + return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd); +} + +void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar) +{ +} diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c new file mode 100644 index 0000000000..6709ab7ea1 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019-2020 NXP + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/pm_runtime.h> +#include <linux/seq_file.h> +#include <linux/types.h> + +#include "imx8-isi-core.h" +#include "imx8-isi-regs.h" + +static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg) +{ + return readl(pipe->regs + reg); +} + +static int mxc_isi_debug_dump_regs_show(struct seq_file *m, void *p) +{ +#define MXC_ISI_DEBUG_REG(name) { name, #name } + static const struct { + u32 offset; + const char * const name; + } registers[] = { + MXC_ISI_DEBUG_REG(CHNL_CTRL), + MXC_ISI_DEBUG_REG(CHNL_IMG_CTRL), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_CTRL), + MXC_ISI_DEBUG_REG(CHNL_IMG_CFG), + MXC_ISI_DEBUG_REG(CHNL_IER), + MXC_ISI_DEBUG_REG(CHNL_STS), + MXC_ISI_DEBUG_REG(CHNL_SCALE_FACTOR), + MXC_ISI_DEBUG_REG(CHNL_SCALE_OFFSET), + MXC_ISI_DEBUG_REG(CHNL_CROP_ULC), + MXC_ISI_DEBUG_REG(CHNL_CROP_LRC), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF0), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF1), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF2), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF3), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF4), + MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF5), + MXC_ISI_DEBUG_REG(CHNL_ROI_0_ALPHA), + MXC_ISI_DEBUG_REG(CHNL_ROI_0_ULC), + MXC_ISI_DEBUG_REG(CHNL_ROI_0_LRC), + MXC_ISI_DEBUG_REG(CHNL_ROI_1_ALPHA), + MXC_ISI_DEBUG_REG(CHNL_ROI_1_ULC), + MXC_ISI_DEBUG_REG(CHNL_ROI_1_LRC), + MXC_ISI_DEBUG_REG(CHNL_ROI_2_ALPHA), + MXC_ISI_DEBUG_REG(CHNL_ROI_2_ULC), + MXC_ISI_DEBUG_REG(CHNL_ROI_2_LRC), + MXC_ISI_DEBUG_REG(CHNL_ROI_3_ALPHA), + MXC_ISI_DEBUG_REG(CHNL_ROI_3_ULC), + MXC_ISI_DEBUG_REG(CHNL_ROI_3_LRC), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_Y), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_U), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_V), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_PITCH), + MXC_ISI_DEBUG_REG(CHNL_IN_BUF_ADDR), + MXC_ISI_DEBUG_REG(CHNL_IN_BUF_PITCH), + MXC_ISI_DEBUG_REG(CHNL_MEM_RD_CTRL), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_Y), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_U), + MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_V), + MXC_ISI_DEBUG_REG(CHNL_SCL_IMG_CFG), + MXC_ISI_DEBUG_REG(CHNL_FLOW_CTRL), + }; + + struct mxc_isi_pipe *pipe = m->private; + unsigned int i; + + if (!pm_runtime_get_if_in_use(pipe->isi->dev)) + return 0; + + seq_printf(m, "--- ISI pipe %u registers ---\n", pipe->id); + + for (i = 0; i < ARRAY_SIZE(registers); ++i) + seq_printf(m, "%20s[0x%02x]: 0x%08x\n", + registers[i].name, registers[i].offset, + mxc_isi_read(pipe, registers[i].offset)); + + pm_runtime_put(pipe->isi->dev); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mxc_isi_debug_dump_regs); + +void mxc_isi_debug_init(struct mxc_isi_dev *isi) +{ + unsigned int i; + + isi->debugfs_root = debugfs_create_dir(dev_name(isi->dev), NULL); + + for (i = 0; i < isi->pdata->num_channels; ++i) { + struct mxc_isi_pipe *pipe = &isi->pipes[i]; + char name[8]; + + sprintf(name, "pipe%u", pipe->id); + debugfs_create_file(name, 0444, isi->debugfs_root, pipe, + &mxc_isi_debug_dump_regs_fops); + } +} + +void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi) +{ + debugfs_remove_recursive(isi->debugfs_root); +} diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-gasket.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-gasket.c new file mode 100644 index 0000000000..f69c3b5d47 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-gasket.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019-2023 NXP + */ + +#include <linux/regmap.h> + +#include <media/mipi-csi2.h> + +#include "imx8-isi-core.h" + +/* ----------------------------------------------------------------------------- + * i.MX8MN and i.MX8MP gasket + */ + +#define GASKET_BASE(n) (0x0060 + (n) * 0x30) + +#define GASKET_CTRL 0x0000 +#define GASKET_CTRL_DATA_TYPE(dt) ((dt) << 8) +#define GASKET_CTRL_DATA_TYPE_MASK (0x3f << 8) +#define GASKET_CTRL_DUAL_COMP_ENABLE BIT(1) +#define GASKET_CTRL_ENABLE BIT(0) + +#define GASKET_HSIZE 0x0004 +#define GASKET_VSIZE 0x0008 + +static void mxc_imx8_gasket_enable(struct mxc_isi_dev *isi, + const struct v4l2_mbus_frame_desc *fd, + const struct v4l2_mbus_framefmt *fmt, + const unsigned int port) +{ + u32 val; + + regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_HSIZE, fmt->width); + regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_VSIZE, fmt->height); + + val = GASKET_CTRL_DATA_TYPE(fd->entry[0].bus.csi2.dt); + if (fd->entry[0].bus.csi2.dt == MIPI_CSI2_DT_YUV422_8B) + val |= GASKET_CTRL_DUAL_COMP_ENABLE; + + val |= GASKET_CTRL_ENABLE; + regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, val); +} + +static void mxc_imx8_gasket_disable(struct mxc_isi_dev *isi, + const unsigned int port) +{ + regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, 0); +} + +const struct mxc_gasket_ops mxc_imx8_gasket_ops = { + .enable = mxc_imx8_gasket_enable, + .disable = mxc_imx8_gasket_disable, +}; + +/* ----------------------------------------------------------------------------- + * i.MX93 gasket + */ + +#define DISP_MIX_CAMERA_MUX 0x30 +#define DISP_MIX_CAMERA_MUX_DATA_TYPE(x) (((x) & 0x3f) << 3) +#define DISP_MIX_CAMERA_MUX_GASKET_ENABLE BIT(16) + +static void mxc_imx93_gasket_enable(struct mxc_isi_dev *isi, + const struct v4l2_mbus_frame_desc *fd, + const struct v4l2_mbus_framefmt *fmt, + const unsigned int port) +{ + u32 val; + + val = DISP_MIX_CAMERA_MUX_DATA_TYPE(fd->entry[0].bus.csi2.dt); + val |= DISP_MIX_CAMERA_MUX_GASKET_ENABLE; + regmap_write(isi->gasket, DISP_MIX_CAMERA_MUX, val); +} + +static void mxc_imx93_gasket_disable(struct mxc_isi_dev *isi, + unsigned int port) +{ + regmap_write(isi->gasket, DISP_MIX_CAMERA_MUX, 0); +} + +const struct mxc_gasket_ops mxc_imx93_gasket_ops = { + .enable = mxc_imx93_gasket_enable, + .disable = mxc_imx93_gasket_disable, +}; diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c new file mode 100644 index 0000000000..19e80b95ff --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019-2020 NXP + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/types.h> + +#include "imx8-isi-core.h" +#include "imx8-isi-regs.h" + +#define ISI_DOWNSCALE_THRESHOLD 0x4000 + +static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg) +{ + return readl(pipe->regs + reg); +} + +static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val) +{ + writel(val, pipe->regs + reg); +} + +/* ----------------------------------------------------------------------------- + * Buffers & M2M operation + */ + +void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr) +{ + mxc_isi_write(pipe, CHNL_IN_BUF_ADDR, lower_32_bits(dma_addr)); + if (pipe->isi->pdata->has_36bit_dma) + mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR, + upper_32_bits(dma_addr)); +} + +void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe, + const dma_addr_t dma_addrs[3], + enum mxc_isi_buf_id buf_id) +{ + int val; + + val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL); + + if (buf_id == MXC_ISI_BUF1) { + mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_Y, + lower_32_bits(dma_addrs[0])); + mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U, + lower_32_bits(dma_addrs[1])); + mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V, + lower_32_bits(dma_addrs[2])); + if (pipe->isi->pdata->has_36bit_dma) { + mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR, + upper_32_bits(dma_addrs[0])); + mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR, + upper_32_bits(dma_addrs[1])); + mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR, + upper_32_bits(dma_addrs[2])); + } + val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR; + } else { + mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y, + lower_32_bits(dma_addrs[0])); + mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U, + lower_32_bits(dma_addrs[1])); + mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V, + lower_32_bits(dma_addrs[2])); + if (pipe->isi->pdata->has_36bit_dma) { + mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR, + upper_32_bits(dma_addrs[0])); + mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR, + upper_32_bits(dma_addrs[1])); + mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR, + upper_32_bits(dma_addrs[2])); + } + val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR; + } + + mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val); +} + +void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe) +{ + u32 val; + + val = mxc_isi_read(pipe, CHNL_MEM_RD_CTRL); + val &= ~CHNL_MEM_RD_CTRL_READ_MEM; + mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val); + + fsleep(300); + + val |= CHNL_MEM_RD_CTRL_READ_MEM; + mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val); +} + +/* ----------------------------------------------------------------------------- + * Pipeline configuration + */ + +static u32 mxc_isi_channel_scaling_ratio(unsigned int from, unsigned int to, + u32 *dec) +{ + unsigned int ratio = from / to; + + if (ratio < 2) + *dec = 1; + else if (ratio < 4) + *dec = 2; + else if (ratio < 8) + *dec = 4; + else + *dec = 8; + + return min_t(u32, from * 0x1000 / (to * *dec), ISI_DOWNSCALE_THRESHOLD); +} + +static void mxc_isi_channel_set_scaling(struct mxc_isi_pipe *pipe, + enum mxc_isi_encoding encoding, + const struct v4l2_area *in_size, + const struct v4l2_area *out_size, + bool *bypass) +{ + u32 xscale, yscale; + u32 decx, decy; + u32 val; + + dev_dbg(pipe->isi->dev, "input %ux%u, output %ux%u\n", + in_size->width, in_size->height, + out_size->width, out_size->height); + + xscale = mxc_isi_channel_scaling_ratio(in_size->width, out_size->width, + &decx); + yscale = mxc_isi_channel_scaling_ratio(in_size->height, out_size->height, + &decy); + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK | + CHNL_IMG_CTRL_YCBCR_MODE); + + val |= CHNL_IMG_CTRL_DEC_X(ilog2(decx)) + | CHNL_IMG_CTRL_DEC_Y(ilog2(decy)); + + /* + * Contrary to what the documentation states, YCBCR_MODE does not + * control conversion between YCbCr and RGB, but whether the scaler + * operates in YUV mode or in RGB mode. It must be set when the scaler + * input is YUV. + */ + if (encoding == MXC_ISI_ENC_YUV) + val |= CHNL_IMG_CTRL_YCBCR_MODE; + + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); + + mxc_isi_write(pipe, CHNL_SCALE_FACTOR, + CHNL_SCALE_FACTOR_Y_SCALE(yscale) | + CHNL_SCALE_FACTOR_X_SCALE(xscale)); + + mxc_isi_write(pipe, CHNL_SCALE_OFFSET, 0); + + mxc_isi_write(pipe, CHNL_SCL_IMG_CFG, + CHNL_SCL_IMG_CFG_HEIGHT(out_size->height) | + CHNL_SCL_IMG_CFG_WIDTH(out_size->width)); + + *bypass = in_size->height == out_size->height && + in_size->width == out_size->width; +} + +static void mxc_isi_channel_set_crop(struct mxc_isi_pipe *pipe, + const struct v4l2_area *src, + const struct v4l2_rect *dst) +{ + u32 val, val0, val1; + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_CROP_EN; + + if (src->height == dst->height && src->width == dst->width) { + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); + return; + } + + val |= CHNL_IMG_CTRL_CROP_EN; + val0 = CHNL_CROP_ULC_X(dst->left) | CHNL_CROP_ULC_Y(dst->top); + val1 = CHNL_CROP_LRC_X(dst->width) | CHNL_CROP_LRC_Y(dst->height); + + mxc_isi_write(pipe, CHNL_CROP_ULC, val0); + mxc_isi_write(pipe, CHNL_CROP_LRC, val1 + val0); + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); +} + +/* + * A2,A1, B1, A3, B3, B2, + * C2, C1, D1, C3, D3, D2 + */ +static const u32 mxc_isi_yuv2rgb_coeffs[6] = { + /* YUV -> RGB */ + 0x0000012a, 0x012a0198, 0x0730079c, + 0x0204012a, 0x01f00000, 0x01800180 +}; + +static const u32 mxc_isi_rgb2yuv_coeffs[6] = { + /* RGB->YUV */ + 0x00810041, 0x07db0019, 0x007007b6, + 0x07a20070, 0x001007ee, 0x00800080 +}; + +static void mxc_isi_channel_set_csc(struct mxc_isi_pipe *pipe, + enum mxc_isi_encoding in_encoding, + enum mxc_isi_encoding out_encoding, + bool *bypass) +{ + static const char * const encodings[] = { + [MXC_ISI_ENC_RAW] = "RAW", + [MXC_ISI_ENC_RGB] = "RGB", + [MXC_ISI_ENC_YUV] = "YUV", + }; + const u32 *coeffs; + bool cscen = true; + u32 val; + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_CSC_BYPASS | CHNL_IMG_CTRL_CSC_MODE_MASK); + + if (in_encoding == MXC_ISI_ENC_YUV && + out_encoding == MXC_ISI_ENC_RGB) { + /* YUV2RGB */ + coeffs = mxc_isi_yuv2rgb_coeffs; + /* YCbCr enable??? */ + val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB); + } else if (in_encoding == MXC_ISI_ENC_RGB && + out_encoding == MXC_ISI_ENC_YUV) { + /* RGB2YUV */ + coeffs = mxc_isi_rgb2yuv_coeffs; + val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR); + } else { + /* Bypass CSC */ + cscen = false; + val |= CHNL_IMG_CTRL_CSC_BYPASS; + } + + dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n", + encodings[in_encoding], encodings[out_encoding]); + + if (cscen) { + mxc_isi_write(pipe, CHNL_CSC_COEFF0, coeffs[0]); + mxc_isi_write(pipe, CHNL_CSC_COEFF1, coeffs[1]); + mxc_isi_write(pipe, CHNL_CSC_COEFF2, coeffs[2]); + mxc_isi_write(pipe, CHNL_CSC_COEFF3, coeffs[3]); + mxc_isi_write(pipe, CHNL_CSC_COEFF4, coeffs[4]); + mxc_isi_write(pipe, CHNL_CSC_COEFF5, coeffs[5]); + } + + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); + + *bypass = !cscen; +} + +void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha) +{ + u32 val; + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK; + val |= CHNL_IMG_CTRL_GBL_ALPHA_VAL(alpha) | + CHNL_IMG_CTRL_GBL_ALPHA_EN; + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); +} + +void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip) +{ + u32 val; + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~(CHNL_IMG_CTRL_VFLIP_EN | CHNL_IMG_CTRL_HFLIP_EN); + + if (vflip) + val |= CHNL_IMG_CTRL_VFLIP_EN; + if (hflip) + val |= CHNL_IMG_CTRL_HFLIP_EN; + + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); +} + +static void mxc_isi_channel_set_panic_threshold(struct mxc_isi_pipe *pipe) +{ + const struct mxc_isi_set_thd *set_thd = pipe->isi->pdata->set_thd; + u32 val; + + val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL); + + val &= ~(set_thd->panic_set_thd_y.mask); + val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset; + + val &= ~(set_thd->panic_set_thd_u.mask); + val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset; + + val &= ~(set_thd->panic_set_thd_v.mask); + val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset; + + mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val); +} + +static void mxc_isi_channel_set_control(struct mxc_isi_pipe *pipe, + enum mxc_isi_input_id input, + bool bypass) +{ + u32 val; + + mutex_lock(&pipe->lock); + + val = mxc_isi_read(pipe, CHNL_CTRL); + val &= ~(CHNL_CTRL_CHNL_BYPASS | CHNL_CTRL_CHAIN_BUF_MASK | + CHNL_CTRL_BLANK_PXL_MASK | CHNL_CTRL_SRC_TYPE_MASK | + CHNL_CTRL_MIPI_VC_ID_MASK | CHNL_CTRL_SRC_INPUT_MASK); + + /* + * If no scaling or color space conversion is needed, bypass the + * channel. + */ + if (bypass) + val |= CHNL_CTRL_CHNL_BYPASS; + + /* Chain line buffers if needed. */ + if (pipe->chained) + val |= CHNL_CTRL_CHAIN_BUF(CHNL_CTRL_CHAIN_BUF_2_CHAIN); + + val |= CHNL_CTRL_BLANK_PXL(0xff); + + /* Input source (including VC configuration for CSI-2) */ + if (input == MXC_ISI_INPUT_MEM) { + /* + * The memory input is connected to the last port of the + * crossbar switch, after all pixel link inputs. The SRC_INPUT + * field controls the input selection and must be set + * accordingly, despite being documented as ignored when using + * the memory input in the i.MX8MP reference manual, and + * reserved in the i.MX8MN reference manual. + */ + val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_MEMORY); + val |= CHNL_CTRL_SRC_INPUT(pipe->isi->pdata->num_ports); + } else { + val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_DEVICE); + val |= CHNL_CTRL_SRC_INPUT(input); + val |= CHNL_CTRL_MIPI_VC_ID(0); /* FIXME: For CSI-2 only */ + } + + mxc_isi_write(pipe, CHNL_CTRL, val); + + mutex_unlock(&pipe->lock); +} + +void mxc_isi_channel_config(struct mxc_isi_pipe *pipe, + enum mxc_isi_input_id input, + const struct v4l2_area *in_size, + const struct v4l2_area *scale, + const struct v4l2_rect *crop, + enum mxc_isi_encoding in_encoding, + enum mxc_isi_encoding out_encoding) +{ + bool csc_bypass; + bool scaler_bypass; + + /* Input frame size */ + mxc_isi_write(pipe, CHNL_IMG_CFG, + CHNL_IMG_CFG_HEIGHT(in_size->height) | + CHNL_IMG_CFG_WIDTH(in_size->width)); + + /* Scaling */ + mxc_isi_channel_set_scaling(pipe, in_encoding, in_size, scale, + &scaler_bypass); + mxc_isi_channel_set_crop(pipe, scale, crop); + + /* CSC */ + mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, &csc_bypass); + + /* Output buffer management */ + mxc_isi_channel_set_panic_threshold(pipe); + + /* Channel control */ + mxc_isi_channel_set_control(pipe, input, csc_bypass && scaler_bypass); +} + +void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe, + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *format) +{ + unsigned int bpl = format->plane_fmt[0].bytesperline; + + mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, + CHNL_MEM_RD_CTRL_IMG_TYPE(info->isi_in_format)); + mxc_isi_write(pipe, CHNL_IN_BUF_PITCH, + CHNL_IN_BUF_PITCH_LINE_PITCH(bpl)); +} + +void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe, + const struct mxc_isi_format_info *info, + struct v4l2_pix_format_mplane *format) +{ + u32 val; + + /* set outbuf format */ + dev_dbg(pipe->isi->dev, "output format %p4cc", &format->pixelformat); + + val = mxc_isi_read(pipe, CHNL_IMG_CTRL); + val &= ~CHNL_IMG_CTRL_FORMAT_MASK; + val |= CHNL_IMG_CTRL_FORMAT(info->isi_out_format); + mxc_isi_write(pipe, CHNL_IMG_CTRL, val); + + /* line pitch */ + mxc_isi_write(pipe, CHNL_OUT_BUF_PITCH, + format->plane_fmt[0].bytesperline); +} + +/* ----------------------------------------------------------------------------- + * IRQ + */ + +u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear) +{ + u32 status; + + status = mxc_isi_read(pipe, CHNL_STS); + if (clear) + mxc_isi_write(pipe, CHNL_STS, status); + + return status; +} + +void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe) +{ + mxc_isi_write(pipe, CHNL_STS, 0xffffffff); +} + +static void mxc_isi_channel_irq_enable(struct mxc_isi_pipe *pipe) +{ + const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg; + u32 val; + + val = CHNL_IER_FRM_RCVD_EN | + CHNL_IER_AXI_WR_ERR_U_EN | + CHNL_IER_AXI_WR_ERR_V_EN | + CHNL_IER_AXI_WR_ERR_Y_EN; + + /* Y/U/V overflow enable */ + val |= ier_reg->oflw_y_buf_en.mask | + ier_reg->oflw_u_buf_en.mask | + ier_reg->oflw_v_buf_en.mask; + + /* Y/U/V excess overflow enable */ + val |= ier_reg->excs_oflw_y_buf_en.mask | + ier_reg->excs_oflw_u_buf_en.mask | + ier_reg->excs_oflw_v_buf_en.mask; + + /* Y/U/V panic enable */ + val |= ier_reg->panic_y_buf_en.mask | + ier_reg->panic_u_buf_en.mask | + ier_reg->panic_v_buf_en.mask; + + mxc_isi_channel_irq_clear(pipe); + mxc_isi_write(pipe, CHNL_IER, val); +} + +static void mxc_isi_channel_irq_disable(struct mxc_isi_pipe *pipe) +{ + mxc_isi_write(pipe, CHNL_IER, 0); +} + +/* ----------------------------------------------------------------------------- + * Init, deinit, enable, disable + */ + +static void mxc_isi_channel_sw_reset(struct mxc_isi_pipe *pipe, bool enable_clk) +{ + mxc_isi_write(pipe, CHNL_CTRL, CHNL_CTRL_SW_RST); + mdelay(5); + mxc_isi_write(pipe, CHNL_CTRL, enable_clk ? CHNL_CTRL_CLK_EN : 0); +} + +static void __mxc_isi_channel_get(struct mxc_isi_pipe *pipe) +{ + if (!pipe->use_count++) + mxc_isi_channel_sw_reset(pipe, true); +} + +void mxc_isi_channel_get(struct mxc_isi_pipe *pipe) +{ + mutex_lock(&pipe->lock); + __mxc_isi_channel_get(pipe); + mutex_unlock(&pipe->lock); +} + +static void __mxc_isi_channel_put(struct mxc_isi_pipe *pipe) +{ + if (!--pipe->use_count) + mxc_isi_channel_sw_reset(pipe, false); +} + +void mxc_isi_channel_put(struct mxc_isi_pipe *pipe) +{ + mutex_lock(&pipe->lock); + __mxc_isi_channel_put(pipe); + mutex_unlock(&pipe->lock); +} + +void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe) +{ + u32 val; + + mxc_isi_channel_irq_enable(pipe); + + mutex_lock(&pipe->lock); + + val = mxc_isi_read(pipe, CHNL_CTRL); + val |= CHNL_CTRL_CHNL_EN; + mxc_isi_write(pipe, CHNL_CTRL, val); + + mutex_unlock(&pipe->lock); +} + +void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe) +{ + u32 val; + + mxc_isi_channel_irq_disable(pipe); + + mutex_lock(&pipe->lock); + + val = mxc_isi_read(pipe, CHNL_CTRL); + val &= ~CHNL_CTRL_CHNL_EN; + mxc_isi_write(pipe, CHNL_CTRL, val); + + mutex_unlock(&pipe->lock); +} + +/* ----------------------------------------------------------------------------- + * Resource management & chaining + */ +int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe, + mxc_isi_pipe_irq_t irq_handler, bool bypass) +{ + u8 resources; + int ret = 0; + + mutex_lock(&pipe->lock); + + if (pipe->irq_handler) { + ret = -EBUSY; + goto unlock; + } + + /* + * Make sure the resources we need are available. The output buffer is + * always needed to operate the channel, the line buffer is needed only + * when the channel isn't in bypass mode. + */ + resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF + | (!bypass ? MXC_ISI_CHANNEL_RES_LINE_BUF : 0); + if ((pipe->available_res & resources) != resources) { + ret = -EBUSY; + goto unlock; + } + + /* Acquire the channel resources. */ + pipe->acquired_res = resources; + pipe->available_res &= ~resources; + pipe->irq_handler = irq_handler; + +unlock: + mutex_unlock(&pipe->lock); + + return ret; +} + +void mxc_isi_channel_release(struct mxc_isi_pipe *pipe) +{ + mutex_lock(&pipe->lock); + + pipe->irq_handler = NULL; + pipe->available_res |= pipe->acquired_res; + pipe->acquired_res = 0; + + mutex_unlock(&pipe->lock); +} + +/* + * We currently support line buffer chaining only, for handling images with a + * width larger than 2048 pixels. + * + * TODO: Support secondary line buffer for downscaling YUV420 images. + */ +int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass) +{ + /* Channel chaining requires both line and output buffer. */ + const u8 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF + | MXC_ISI_CHANNEL_RES_LINE_BUF; + struct mxc_isi_pipe *chained_pipe = pipe + 1; + int ret = 0; + + /* + * If buffer chaining is required, make sure this channel is not the + * last one, otherwise there's no 'next' channel to chain with. This + * should be prevented by checks in the set format handlers, but let's + * be defensive. + */ + if (WARN_ON(pipe->id == pipe->isi->pdata->num_channels - 1)) + return -EINVAL; + + mutex_lock(&chained_pipe->lock); + + /* Safety checks. */ + if (WARN_ON(pipe->chained || chained_pipe->chained_res)) { + ret = -EINVAL; + goto unlock; + } + + if ((chained_pipe->available_res & resources) != resources) { + ret = -EBUSY; + goto unlock; + } + + pipe->chained = true; + chained_pipe->chained_res |= resources; + chained_pipe->available_res &= ~resources; + + __mxc_isi_channel_get(chained_pipe); + +unlock: + mutex_unlock(&chained_pipe->lock); + + return ret; +} + +void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_pipe *chained_pipe = pipe + 1; + + if (!pipe->chained) + return; + + pipe->chained = false; + + mutex_lock(&chained_pipe->lock); + + chained_pipe->available_res |= chained_pipe->chained_res; + chained_pipe->chained_res = 0; + + __mxc_isi_channel_put(chained_pipe); + + mutex_unlock(&chained_pipe->lock); +} diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c new file mode 100644 index 0000000000..9745d6219a --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor or memory to memory or DC + * + * Copyright (c) 2019 NXP Semiconductor + */ + +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/minmax.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#include "imx8-isi-core.h" + +struct mxc_isi_m2m_buffer { + struct v4l2_m2m_buffer buf; + dma_addr_t dma_addrs[3]; +}; + +struct mxc_isi_m2m_ctx_queue_data { + struct v4l2_pix_format_mplane format; + const struct mxc_isi_format_info *info; + u32 sequence; +}; + +struct mxc_isi_m2m_ctx { + struct v4l2_fh fh; + struct mxc_isi_m2m *m2m; + + /* Protects the m2m vb2 queues */ + struct mutex vb2_lock; + + struct { + struct mxc_isi_m2m_ctx_queue_data out; + struct mxc_isi_m2m_ctx_queue_data cap; + } queues; + + struct { + struct v4l2_ctrl_handler handler; + unsigned int alpha; + bool hflip; + bool vflip; + } ctrls; + + bool chained; +}; + +static inline struct mxc_isi_m2m_buffer * +to_isi_m2m_buffer(struct vb2_v4l2_buffer *buf) +{ + return container_of(buf, struct mxc_isi_m2m_buffer, buf.vb); +} + +static inline struct mxc_isi_m2m_ctx *to_isi_m2m_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mxc_isi_m2m_ctx, fh); +} + +static inline struct mxc_isi_m2m_ctx_queue_data * +mxc_isi_m2m_ctx_qdata(struct mxc_isi_m2m_ctx *ctx, enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->queues.out; + else + return &ctx->queues.cap; +} + +/* ----------------------------------------------------------------------------- + * V4L2 M2M device operations + */ + +static void mxc_isi_m2m_frame_write_done(struct mxc_isi_pipe *pipe, u32 status) +{ + struct mxc_isi_m2m *m2m = &pipe->isi->m2m; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + struct mxc_isi_m2m_ctx *ctx; + + ctx = v4l2_m2m_get_curr_priv(m2m->m2m_dev); + if (!ctx) { + dev_err(m2m->isi->dev, + "Instance released before the end of transaction\n"); + return; + } + + src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, false); + + src_vbuf->sequence = ctx->queues.out.sequence++; + dst_vbuf->sequence = ctx->queues.cap.sequence++; + + v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE); + + v4l2_m2m_job_finish(m2m->m2m_dev, ctx->fh.m2m_ctx); +} + +static void mxc_isi_m2m_device_run(void *priv) +{ + struct mxc_isi_m2m_ctx *ctx = priv; + struct mxc_isi_m2m *m2m = ctx->m2m; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + struct mxc_isi_m2m_buffer *src_buf, *dst_buf; + + mxc_isi_channel_disable(m2m->pipe); + + mutex_lock(&m2m->lock); + + /* If the context has changed, reconfigure the channel. */ + if (m2m->last_ctx != ctx) { + const struct v4l2_area in_size = { + .width = ctx->queues.out.format.width, + .height = ctx->queues.out.format.height, + }; + const struct v4l2_area scale = { + .width = ctx->queues.cap.format.width, + .height = ctx->queues.cap.format.height, + }; + const struct v4l2_rect crop = { + .width = ctx->queues.cap.format.width, + .height = ctx->queues.cap.format.height, + }; + + mxc_isi_channel_config(m2m->pipe, MXC_ISI_INPUT_MEM, + &in_size, &scale, &crop, + ctx->queues.out.info->encoding, + ctx->queues.cap.info->encoding); + mxc_isi_channel_set_input_format(m2m->pipe, + ctx->queues.out.info, + &ctx->queues.out.format); + mxc_isi_channel_set_output_format(m2m->pipe, + ctx->queues.cap.info, + &ctx->queues.cap.format); + + m2m->last_ctx = ctx; + } + + mutex_unlock(&m2m->lock); + + mutex_lock(ctx->ctrls.handler.lock); + mxc_isi_channel_set_alpha(m2m->pipe, ctx->ctrls.alpha); + mxc_isi_channel_set_flip(m2m->pipe, ctx->ctrls.hflip, ctx->ctrls.vflip); + mutex_unlock(ctx->ctrls.handler.lock); + + src_vbuf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_vbuf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + src_buf = to_isi_m2m_buffer(src_vbuf); + dst_buf = to_isi_m2m_buffer(dst_vbuf); + + mxc_isi_channel_set_inbuf(m2m->pipe, src_buf->dma_addrs[0]); + mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF1); + mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF2); + + mxc_isi_channel_enable(m2m->pipe); + + mxc_isi_channel_m2m_start(m2m->pipe); +} + +static const struct v4l2_m2m_ops mxc_isi_m2m_ops = { + .device_run = mxc_isi_m2m_device_run, +}; + +/* ----------------------------------------------------------------------------- + * videobuf2 queue operations + */ + +static int mxc_isi_m2m_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); + const struct mxc_isi_m2m_ctx_queue_data *qdata = + mxc_isi_m2m_ctx_qdata(ctx, q->type); + + return mxc_isi_video_queue_setup(&qdata->format, qdata->info, + num_buffers, num_planes, sizes); +} + +static int mxc_isi_m2m_vb2_buffer_init(struct vb2_buffer *vb2) +{ + struct vb2_queue *vq = vb2->vb2_queue; + struct mxc_isi_m2m_buffer *buf = to_isi_m2m_buffer(to_vb2_v4l2_buffer(vb2)); + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue); + const struct mxc_isi_m2m_ctx_queue_data *qdata = + mxc_isi_m2m_ctx_qdata(ctx, vq->type); + + mxc_isi_video_buffer_init(vb2, buf->dma_addrs, qdata->info, + &qdata->format); + + return 0; +} + +static int mxc_isi_m2m_vb2_buffer_prepare(struct vb2_buffer *vb2) +{ + struct vb2_queue *vq = vb2->vb2_queue; + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vq); + const struct mxc_isi_m2m_ctx_queue_data *qdata = + mxc_isi_m2m_ctx_qdata(ctx, vq->type); + + return mxc_isi_video_buffer_prepare(ctx->m2m->isi, vb2, qdata->info, + &qdata->format); +} + +static void mxc_isi_m2m_vb2_buffer_queue(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int mxc_isi_m2m_vb2_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_isi_m2m_ctx_queue_data *qdata = + mxc_isi_m2m_ctx_qdata(ctx, q->type); + + qdata->sequence = 0; + + return 0; +} + +static void mxc_isi_m2m_vb2_stop_streaming(struct vb2_queue *q) +{ + struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + 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, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops mxc_isi_m2m_vb2_qops = { + .queue_setup = mxc_isi_m2m_vb2_queue_setup, + .buf_init = mxc_isi_m2m_vb2_buffer_init, + .buf_prepare = mxc_isi_m2m_vb2_buffer_prepare, + .buf_queue = mxc_isi_m2m_vb2_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = mxc_isi_m2m_vb2_start_streaming, + .stop_streaming = mxc_isi_m2m_vb2_stop_streaming, +}; + +static int mxc_isi_m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mxc_isi_m2m_ctx *ctx = priv; + struct mxc_isi_m2m *m2m = ctx->m2m; + 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->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer); + src_vq->ops = &mxc_isi_m2m_vb2_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->vb2_lock; + src_vq->dev = m2m->isi->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->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer); + dst_vq->ops = &mxc_isi_m2m_vb2_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->vb2_lock; + dst_vq->dev = m2m->isi->dev; + + return vb2_queue_init(dst_vq); +} + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static inline struct mxc_isi_m2m_ctx * +ctrl_to_mxc_isi_m2m_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mxc_isi_m2m_ctx, ctrls.handler); +} + +static int mxc_isi_m2m_ctx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_isi_m2m_ctx *ctx = ctrl_to_mxc_isi_m2m_ctx(ctrl); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->ctrls.hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + ctx->ctrls.vflip = ctrl->val; + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->ctrls.alpha = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mxc_isi_m2m_ctx_ctrl_ops = { + .s_ctrl = mxc_isi_m2m_ctx_s_ctrl, +}; + +static int mxc_isi_m2m_ctx_ctrls_create(struct mxc_isi_m2m_ctx *ctx) +{ + struct v4l2_ctrl_handler *handler = &ctx->ctrls.handler; + int ret; + + v4l2_ctrl_handler_init(handler, 3); + + v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); + v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (handler->error) { + ret = handler->error; + v4l2_ctrl_handler_free(handler); + return ret; + } + + ctx->fh.ctrl_handler = handler; + + return 0; +} + +static void mxc_isi_m2m_ctx_ctrls_delete(struct mxc_isi_m2m_ctx *ctx) +{ + v4l2_ctrl_handler_free(&ctx->ctrls.handler); +} + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int mxc_isi_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, MXC_ISI_M2M, sizeof(cap->card)); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_isi_m2m_enum_fmt_vid(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + const enum mxc_isi_video_type type = + f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? + MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; + const struct mxc_isi_format_info *info; + + info = mxc_isi_format_enum(f->index, type); + if (!info) + return -EINVAL; + + f->pixelformat = info->fourcc; + f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC + | V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC; + + return 0; +} + +static const struct mxc_isi_format_info * +__mxc_isi_m2m_try_fmt_vid(struct mxc_isi_m2m_ctx *ctx, + struct v4l2_pix_format_mplane *pix, + const enum mxc_isi_video_type type) +{ + if (type == MXC_ISI_VIDEO_M2M_CAP) { + /* Downscaling only */ + pix->width = min(pix->width, ctx->queues.out.format.width); + pix->height = min(pix->height, ctx->queues.out.format.height); + } + + return mxc_isi_format_try(ctx->m2m->pipe, pix, type); +} + +static int mxc_isi_m2m_try_fmt_vid(struct file *file, void *fh, + struct v4l2_format *f) +{ + const enum mxc_isi_video_type type = + f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? + MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); + + __mxc_isi_m2m_try_fmt_vid(ctx, &f->fmt.pix_mp, type); + + return 0; +} + +static int mxc_isi_m2m_g_fmt_vid(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); + const struct mxc_isi_m2m_ctx_queue_data *qdata = + mxc_isi_m2m_ctx_qdata(ctx, f->type); + + f->fmt.pix_mp = qdata->format; + + return 0; +} + +static int mxc_isi_m2m_s_fmt_vid(struct file *file, void *fh, + struct v4l2_format *f) +{ + const enum mxc_isi_video_type type = + f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? + MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + const struct mxc_isi_format_info *info; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) + return -EBUSY; + + info = __mxc_isi_m2m_try_fmt_vid(ctx, pix, type); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ctx->queues.out.format = *pix; + ctx->queues.out.info = info; + } + + /* + * Always set the format on the capture side, due to either format + * propagation or direct setting. + */ + ctx->queues.cap.format = *pix; + ctx->queues.cap.info = info; + + return 0; +} + +static int mxc_isi_m2m_streamon(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); + const struct v4l2_pix_format_mplane *out_pix = &ctx->queues.out.format; + const struct v4l2_pix_format_mplane *cap_pix = &ctx->queues.cap.format; + const struct mxc_isi_format_info *cap_info = ctx->queues.cap.info; + const struct mxc_isi_format_info *out_info = ctx->queues.out.info; + struct mxc_isi_m2m *m2m = ctx->m2m; + bool bypass; + + int ret; + + mutex_lock(&m2m->lock); + + if (m2m->usage_count == INT_MAX) { + ret = -EOVERFLOW; + goto unlock; + } + + bypass = cap_pix->width == out_pix->width && + cap_pix->height == out_pix->height && + cap_info->encoding == out_info->encoding; + + /* + * Acquire the pipe and initialize the channel with the first user of + * the M2M device. + */ + if (m2m->usage_count == 0) { + ret = mxc_isi_channel_acquire(m2m->pipe, + &mxc_isi_m2m_frame_write_done, + bypass); + if (ret) + goto unlock; + + mxc_isi_channel_get(m2m->pipe); + } + + m2m->usage_count++; + + /* + * Allocate resources for the channel, counting how many users require + * buffer chaining. + */ + if (!ctx->chained && out_pix->width > MXC_ISI_MAX_WIDTH_UNCHAINED) { + ret = mxc_isi_channel_chain(m2m->pipe, bypass); + if (ret) + goto deinit; + + m2m->chained_count++; + ctx->chained = true; + } + + /* + * Drop the lock to start the stream, as the .device_run() operation + * needs to acquire it. + */ + mutex_unlock(&m2m->lock); + ret = v4l2_m2m_ioctl_streamon(file, fh, type); + if (ret) { + /* Reacquire the lock for the cleanup path. */ + mutex_lock(&m2m->lock); + goto unchain; + } + + return 0; + +unchain: + if (ctx->chained && --m2m->chained_count == 0) + mxc_isi_channel_unchain(m2m->pipe); + ctx->chained = false; + +deinit: + if (--m2m->usage_count == 0) { + mxc_isi_channel_put(m2m->pipe); + mxc_isi_channel_release(m2m->pipe); + } + +unlock: + mutex_unlock(&m2m->lock); + return ret; +} + +static int mxc_isi_m2m_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); + struct mxc_isi_m2m *m2m = ctx->m2m; + + v4l2_m2m_ioctl_streamoff(file, fh, type); + + mutex_lock(&m2m->lock); + + /* + * If the last context is this one, reset it to make sure the device + * will be reconfigured when streaming is restarted. + */ + if (m2m->last_ctx == ctx) + m2m->last_ctx = NULL; + + /* Free the channel resources if this is the last chained context. */ + if (ctx->chained && --m2m->chained_count == 0) + mxc_isi_channel_unchain(m2m->pipe); + ctx->chained = false; + + /* Turn off the light with the last user. */ + if (--m2m->usage_count == 0) { + mxc_isi_channel_disable(m2m->pipe); + mxc_isi_channel_put(m2m->pipe); + mxc_isi_channel_release(m2m->pipe); + } + + WARN_ON(m2m->usage_count < 0); + + mutex_unlock(&m2m->lock); + + return 0; +} + +static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = { + .vidioc_querycap = mxc_isi_m2m_querycap, + + .vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid, + .vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid, + .vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid, + .vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid, + .vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid, + .vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid, + .vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid, + + .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_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + + .vidioc_streamon = mxc_isi_m2m_streamon, + .vidioc_streamoff = mxc_isi_m2m_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * Video device file operations + */ + +static void mxc_isi_m2m_init_format(struct mxc_isi_m2m_ctx *ctx, + struct mxc_isi_m2m_ctx_queue_data *qdata, + enum mxc_isi_video_type type) +{ + qdata->format.width = MXC_ISI_DEF_WIDTH; + qdata->format.height = MXC_ISI_DEF_HEIGHT; + qdata->format.pixelformat = MXC_ISI_DEF_PIXEL_FORMAT; + + qdata->info = mxc_isi_format_try(ctx->m2m->pipe, &qdata->format, type); +} + +static int mxc_isi_m2m_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct mxc_isi_m2m *m2m = video_drvdata(file); + struct mxc_isi_m2m_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->m2m = m2m; + mutex_init(&ctx->vb2_lock); + + v4l2_fh_init(&ctx->fh, vdev); + file->private_data = &ctx->fh; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(m2m->m2m_dev, ctx, + &mxc_isi_m2m_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + ctx->fh.m2m_ctx = NULL; + goto err_fh; + } + + mxc_isi_m2m_init_format(ctx, &ctx->queues.out, MXC_ISI_VIDEO_M2M_OUT); + mxc_isi_m2m_init_format(ctx, &ctx->queues.cap, MXC_ISI_VIDEO_M2M_CAP); + + ret = mxc_isi_m2m_ctx_ctrls_create(ctx); + if (ret) + goto err_ctx; + + ret = pm_runtime_resume_and_get(m2m->isi->dev); + if (ret) + goto err_ctrls; + + v4l2_fh_add(&ctx->fh); + + return 0; + +err_ctrls: + mxc_isi_m2m_ctx_ctrls_delete(ctx); +err_ctx: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +err_fh: + v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->vb2_lock); + kfree(ctx); + return ret; +} + +static int mxc_isi_m2m_release(struct file *file) +{ + struct mxc_isi_m2m *m2m = video_drvdata(file); + struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(file->private_data); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mxc_isi_m2m_ctx_ctrls_delete(ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + mutex_destroy(&ctx->vb2_lock); + kfree(ctx); + + pm_runtime_put(m2m->isi->dev); + + return 0; +} + +static const struct v4l2_file_operations mxc_isi_m2m_fops = { + .owner = THIS_MODULE, + .open = mxc_isi_m2m_open, + .release = mxc_isi_m2m_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Registration + */ + +int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev) +{ + struct mxc_isi_m2m *m2m = &isi->m2m; + struct video_device *vdev = &m2m->vdev; + struct media_link *link; + int ret; + + m2m->isi = isi; + m2m->pipe = &isi->pipes[0]; + + mutex_init(&m2m->lock); + + /* Initialize the video device and create controls. */ + snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.m2m"); + + vdev->fops = &mxc_isi_m2m_fops; + vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->vfl_dir = VFL_DIR_M2M; + + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + video_set_drvdata(vdev, m2m); + + /* Create the M2M device. */ + m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops); + if (IS_ERR(m2m->m2m_dev)) { + dev_err(isi->dev, "failed to initialize m2m device\n"); + ret = PTR_ERR(m2m->m2m_dev); + goto err_mutex; + } + + /* Register the video device. */ + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(isi->dev, "failed to register m2m device\n"); + goto err_m2m; + } + + /* + * Populate the media graph. We can't use the mem2mem helper + * v4l2_m2m_register_media_controller() as the M2M interface needs to + * be connected to the existing entities in the graph, so we have to + * wire things up manually: + * + * - The entity in the video_device, which isn't touched by the V4L2 + * core for M2M devices, is used as the source I/O entity in the + * graph, connected to the crossbar switch. + * + * - The video device at the end of the pipeline provides the sink + * entity, and is already wired up in the graph. + * + * - A new interface is created, pointing at both entities. The sink + * entity will thus have two interfaces pointing to it. + */ + m2m->pad.flags = MEDIA_PAD_FL_SOURCE; + vdev->entity.name = "mxc_isi.output"; + vdev->entity.function = MEDIA_ENT_F_IO_V4L; + ret = media_entity_pads_init(&vdev->entity, 1, &m2m->pad); + if (ret) + goto err_video; + + ret = media_device_register_entity(v4l2_dev->mdev, &vdev->entity); + if (ret) + goto err_entity_cleanup; + + ret = media_create_pad_link(&vdev->entity, 0, + &m2m->isi->crossbar.sd.entity, + m2m->isi->crossbar.num_sinks - 1, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + goto err_entity_unreg; + + m2m->intf = media_devnode_create(v4l2_dev->mdev, MEDIA_INTF_T_V4L_VIDEO, + 0, VIDEO_MAJOR, vdev->minor); + if (!m2m->intf) { + ret = -ENOMEM; + goto err_entity_unreg; + } + + link = media_create_intf_link(&vdev->entity, &m2m->intf->intf, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (!link) { + ret = -ENOMEM; + goto err_devnode; + } + + link = media_create_intf_link(&m2m->pipe->video.vdev.entity, + &m2m->intf->intf, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (!link) { + ret = -ENOMEM; + goto err_devnode; + } + + return 0; + +err_devnode: + media_devnode_remove(m2m->intf); +err_entity_unreg: + media_device_unregister_entity(&vdev->entity); +err_entity_cleanup: + media_entity_cleanup(&vdev->entity); +err_video: + video_unregister_device(vdev); +err_m2m: + v4l2_m2m_release(m2m->m2m_dev); +err_mutex: + mutex_destroy(&m2m->lock); + return ret; +} + +int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi) +{ + struct mxc_isi_m2m *m2m = &isi->m2m; + struct video_device *vdev = &m2m->vdev; + + video_unregister_device(vdev); + + v4l2_m2m_release(m2m->m2m_dev); + media_devnode_remove(m2m->intf); + media_entity_cleanup(&vdev->entity); + mutex_destroy(&m2m->lock); + + return 0; +} diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c new file mode 100644 index 0000000000..65d20e9bae --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c @@ -0,0 +1,866 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor to memory or DC + * + * Copyright (c) 2019 NXP Semiconductor + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/minmax.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-v4l2.h> + +#include "imx8-isi-core.h" +#include "imx8-isi-regs.h" + +/* + * While the ISI receives data from the gasket on a 3x12-bit bus, the pipeline + * subdev conceptually includes the gasket in order to avoid exposing an extra + * subdev between the CSIS and the ISI. We thus need to expose media bus codes + * corresponding to the CSIS output, which is narrower. + */ +static const struct mxc_isi_bus_format_info mxc_isi_bus_formats[] = { + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .output = MEDIA_BUS_FMT_YUV8_1X24, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK), + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .output = MEDIA_BUS_FMT_YUV8_1X24, + .pads = BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_YUV, + }, + /* RGB formats */ + { + .mbus_code = MEDIA_BUS_FMT_RGB565_1X16, + .output = MEDIA_BUS_FMT_RGB888_1X24, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK), + .encoding = MXC_ISI_ENC_RGB, + }, { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .output = MEDIA_BUS_FMT_RGB888_1X24, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RGB, + }, + /* RAW formats */ + { + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .output = MEDIA_BUS_FMT_Y8_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .output = MEDIA_BUS_FMT_Y10_1X10, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y12_1X12, + .output = MEDIA_BUS_FMT_Y12_1X12, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y14_1X14, + .output = MEDIA_BUS_FMT_Y14_1X14, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .output = MEDIA_BUS_FMT_SBGGR8_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .output = MEDIA_BUS_FMT_SGBRG8_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .output = MEDIA_BUS_FMT_SGRBG8_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .output = MEDIA_BUS_FMT_SRGGB8_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .output = MEDIA_BUS_FMT_SBGGR10_1X10, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .output = MEDIA_BUS_FMT_SGBRG10_1X10, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .output = MEDIA_BUS_FMT_SGRBG10_1X10, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .output = MEDIA_BUS_FMT_SRGGB10_1X10, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .output = MEDIA_BUS_FMT_SBGGR12_1X12, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .output = MEDIA_BUS_FMT_SGBRG12_1X12, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .output = MEDIA_BUS_FMT_SGRBG12_1X12, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .output = MEDIA_BUS_FMT_SRGGB12_1X12, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14, + .output = MEDIA_BUS_FMT_SBGGR14_1X14, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14, + .output = MEDIA_BUS_FMT_SGBRG14_1X14, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14, + .output = MEDIA_BUS_FMT_SGRBG14_1X14, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14, + .output = MEDIA_BUS_FMT_SRGGB14_1X14, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + }, + /* JPEG */ + { + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .output = MEDIA_BUS_FMT_JPEG_1X8, + .pads = BIT(MXC_ISI_PIPE_PAD_SINK) + | BIT(MXC_ISI_PIPE_PAD_SOURCE), + .encoding = MXC_ISI_ENC_RAW, + } +}; + +const struct mxc_isi_bus_format_info * +mxc_isi_bus_format_by_code(u32 code, unsigned int pad) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) { + const struct mxc_isi_bus_format_info *info = + &mxc_isi_bus_formats[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +const struct mxc_isi_bus_format_info * +mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) { + const struct mxc_isi_bus_format_info *info = + &mxc_isi_bus_formats[i]; + + if (!(info->pads & BIT(pad))) + continue; + + if (!index) + return info; + + index--; + } + + return NULL; +} + +static inline struct mxc_isi_pipe *to_isi_pipe(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mxc_isi_pipe, sd); +} + +int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar; + const struct mxc_isi_bus_format_info *sink_info; + const struct mxc_isi_bus_format_info *src_info; + const struct v4l2_mbus_framefmt *sink_fmt; + const struct v4l2_mbus_framefmt *src_fmt; + const struct v4l2_rect *compose; + struct v4l2_subdev_state *state; + struct v4l2_subdev *sd = &pipe->sd; + struct v4l2_area in_size, scale; + struct v4l2_rect crop; + u32 input; + int ret; + + /* + * Find the connected input by inspecting the crossbar switch routing + * table. + */ + state = v4l2_subdev_lock_and_get_active_state(&xbar->sd); + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + xbar->num_sinks + pipe->id, + 0, &input, NULL); + v4l2_subdev_unlock_state(state); + + if (ret) + return -EPIPE; + + /* Configure the pipeline. */ + state = v4l2_subdev_lock_and_get_active_state(sd); + + sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK); + src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE); + compose = v4l2_subdev_get_try_compose(sd, state, MXC_ISI_PIPE_PAD_SINK); + crop = *v4l2_subdev_get_try_crop(sd, state, MXC_ISI_PIPE_PAD_SOURCE); + + sink_info = mxc_isi_bus_format_by_code(sink_fmt->code, + MXC_ISI_PIPE_PAD_SINK); + src_info = mxc_isi_bus_format_by_code(src_fmt->code, + MXC_ISI_PIPE_PAD_SOURCE); + + in_size.width = sink_fmt->width; + in_size.height = sink_fmt->height; + scale.width = compose->width; + scale.height = compose->height; + + v4l2_subdev_unlock_state(state); + + /* Configure the ISI channel. */ + mxc_isi_channel_config(pipe, input, &in_size, &scale, &crop, + sink_info->encoding, src_info->encoding); + + mxc_isi_channel_enable(pipe); + + /* Enable streams on the crossbar switch. */ + ret = v4l2_subdev_enable_streams(&xbar->sd, xbar->num_sinks + pipe->id, + BIT(0)); + if (ret) { + mxc_isi_channel_disable(pipe); + dev_err(pipe->isi->dev, "Failed to enable pipe %u\n", + pipe->id); + return ret; + } + + return 0; +} + +void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar; + int ret; + + ret = v4l2_subdev_disable_streams(&xbar->sd, xbar->num_sinks + pipe->id, + BIT(0)); + if (ret) + dev_err(pipe->isi->dev, "Failed to disable pipe %u\n", + pipe->id); + + mxc_isi_channel_disable(pipe); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct v4l2_mbus_framefmt * +mxc_isi_pipe_get_pad_format(struct mxc_isi_pipe *pipe, + struct v4l2_subdev_state *state, + unsigned int pad) +{ + return v4l2_subdev_get_try_format(&pipe->sd, state, pad); +} + +static struct v4l2_rect * +mxc_isi_pipe_get_pad_crop(struct mxc_isi_pipe *pipe, + struct v4l2_subdev_state *state, + unsigned int pad) +{ + return v4l2_subdev_get_try_crop(&pipe->sd, state, pad); +} + +static struct v4l2_rect * +mxc_isi_pipe_get_pad_compose(struct mxc_isi_pipe *pipe, + struct v4l2_subdev_state *state, + unsigned int pad) +{ + return v4l2_subdev_get_try_compose(&pipe->sd, state, pad); +} + +static int mxc_isi_pipe_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct mxc_isi_pipe *pipe = to_isi_pipe(sd); + struct v4l2_mbus_framefmt *fmt_source; + struct v4l2_mbus_framefmt *fmt_sink; + struct v4l2_rect *compose; + struct v4l2_rect *crop; + + fmt_sink = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + fmt_source = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + + fmt_sink->width = MXC_ISI_DEF_WIDTH; + fmt_sink->height = MXC_ISI_DEF_HEIGHT; + fmt_sink->code = MXC_ISI_DEF_MBUS_CODE_SINK; + fmt_sink->field = V4L2_FIELD_NONE; + fmt_sink->colorspace = V4L2_COLORSPACE_JPEG; + fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); + fmt_sink->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, + fmt_sink->ycbcr_enc); + fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); + + *fmt_source = *fmt_sink; + fmt_source->code = MXC_ISI_DEF_MBUS_CODE_SOURCE; + + compose = mxc_isi_pipe_get_pad_compose(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + crop = mxc_isi_pipe_get_pad_crop(pipe, state, MXC_ISI_PIPE_PAD_SOURCE); + + compose->left = 0; + compose->top = 0; + compose->width = MXC_ISI_DEF_WIDTH; + compose->height = MXC_ISI_DEF_HEIGHT; + + *crop = *compose; + + return 0; +} + +static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const u32 output_codes[] = { + MEDIA_BUS_FMT_YUV8_1X24, + MEDIA_BUS_FMT_RGB888_1X24, + }; + struct mxc_isi_pipe *pipe = to_isi_pipe(sd); + const struct mxc_isi_bus_format_info *info; + unsigned int index; + unsigned int i; + + if (code->pad == MXC_ISI_PIPE_PAD_SOURCE) { + const struct v4l2_mbus_framefmt *format; + + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + info = mxc_isi_bus_format_by_code(format->code, + MXC_ISI_PIPE_PAD_SINK); + + if (info->encoding == MXC_ISI_ENC_RAW) { + /* + * For RAW formats, the sink and source media bus codes + * must match. + */ + if (code->index) + return -EINVAL; + + code->code = info->output; + } else { + /* + * For RGB or YUV formats, the ISI supports format + * conversion. Either of the two output formats can be + * used regardless of the input. + */ + if (code->index > 1) + return -EINVAL; + + code->code = output_codes[code->index]; + } + + return 0; + } + + index = code->index; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); ++i) { + info = &mxc_isi_bus_formats[i]; + + if (!(info->pads & BIT(MXC_ISI_PIPE_PAD_SINK))) + continue; + + if (index == 0) { + code->code = info->mbus_code; + return 0; + } + + index--; + } + + return -EINVAL; +} + +static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct mxc_isi_pipe *pipe = to_isi_pipe(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + const struct mxc_isi_bus_format_info *info; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *rect; + + if (vb2_is_busy(&pipe->video.vb2_q)) + return -EBUSY; + + if (fmt->pad == MXC_ISI_PIPE_PAD_SINK) { + unsigned int max_width; + + info = mxc_isi_bus_format_by_code(mf->code, + MXC_ISI_PIPE_PAD_SINK); + if (!info) + info = mxc_isi_bus_format_by_code(MXC_ISI_DEF_MBUS_CODE_SINK, + MXC_ISI_PIPE_PAD_SINK); + + /* + * Limit the max line length if there's no adjacent pipe to + * chain with. + */ + max_width = pipe->id == pipe->isi->pdata->num_channels - 1 + ? MXC_ISI_MAX_WIDTH_UNCHAINED + : MXC_ISI_MAX_WIDTH_CHAINED; + + mf->code = info->mbus_code; + mf->width = clamp(mf->width, MXC_ISI_MIN_WIDTH, max_width); + mf->height = clamp(mf->height, MXC_ISI_MIN_HEIGHT, + MXC_ISI_MAX_HEIGHT); + + /* Propagate the format to the source pad. */ + rect = mxc_isi_pipe_get_pad_compose(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + rect->width = mf->width; + rect->height = mf->height; + + rect = mxc_isi_pipe_get_pad_crop(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + rect->left = 0; + rect->top = 0; + rect->width = mf->width; + rect->height = mf->height; + + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + format->code = info->output; + format->width = mf->width; + format->height = mf->height; + } else { + /* + * For RGB or YUV formats, the ISI supports RGB <-> YUV format + * conversion. For RAW formats, the sink and source media bus + * codes must match. + */ + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + info = mxc_isi_bus_format_by_code(format->code, + MXC_ISI_PIPE_PAD_SINK); + + if (info->encoding != MXC_ISI_ENC_RAW) { + if (mf->code != MEDIA_BUS_FMT_YUV8_1X24 && + mf->code != MEDIA_BUS_FMT_RGB888_1X24) + mf->code = info->output; + + info = mxc_isi_bus_format_by_code(mf->code, + MXC_ISI_PIPE_PAD_SOURCE); + } + + mf->code = info->output; + + /* + * The width and height on the source can't be changed, they + * must match the crop rectangle size. + */ + rect = mxc_isi_pipe_get_pad_crop(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + + mf->width = rect->width; + mf->height = rect->height; + } + + format = mxc_isi_pipe_get_pad_format(pipe, state, fmt->pad); + *format = *mf; + + dev_dbg(pipe->isi->dev, "pad%u: code: 0x%04x, %ux%u", + fmt->pad, mf->code, mf->width, mf->height); + + return 0; +} + +static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mxc_isi_pipe *pipe = to_isi_pipe(sd); + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *rect; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (sel->pad != MXC_ISI_PIPE_PAD_SINK) + /* No compose rectangle on source pad. */ + return -EINVAL; + + /* The sink compose is bound by the sink format. */ + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE) + /* No crop rectangle on sink pad. */ + return -EINVAL; + + /* The source crop is bound by the sink compose. */ + rect = mxc_isi_pipe_get_pad_compose(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + sel->r = *rect; + break; + + case V4L2_SEL_TGT_CROP: + if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE) + /* No crop rectangle on sink pad. */ + return -EINVAL; + + rect = mxc_isi_pipe_get_pad_crop(pipe, state, sel->pad); + sel->r = *rect; + break; + + case V4L2_SEL_TGT_COMPOSE: + if (sel->pad != MXC_ISI_PIPE_PAD_SINK) + /* No compose rectangle on source pad. */ + return -EINVAL; + + rect = mxc_isi_pipe_get_pad_compose(pipe, state, sel->pad); + sel->r = *rect; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mxc_isi_pipe *pipe = to_isi_pipe(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *rect; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE) + /* The pipeline support cropping on the source only. */ + return -EINVAL; + + /* The source crop is bound by the sink compose. */ + rect = mxc_isi_pipe_get_pad_compose(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + sel->r.left = clamp_t(s32, sel->r.left, 0, rect->width - 1); + sel->r.top = clamp_t(s32, sel->r.top, 0, rect->height - 1); + sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH, + rect->width - sel->r.left); + sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT, + rect->height - sel->r.top); + + rect = mxc_isi_pipe_get_pad_crop(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + *rect = sel->r; + + /* Propagate the crop rectangle to the source pad. */ + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + format->width = sel->r.width; + format->height = sel->r.height; + break; + + case V4L2_SEL_TGT_COMPOSE: + if (sel->pad != MXC_ISI_PIPE_PAD_SINK) + /* Composing is supported on the sink only. */ + return -EINVAL; + + /* The sink crop is bound by the sink format downscaling only). */ + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH, + format->width); + sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT, + format->height); + + rect = mxc_isi_pipe_get_pad_compose(pipe, state, + MXC_ISI_PIPE_PAD_SINK); + *rect = sel->r; + + /* Propagate the compose rectangle to the source pad. */ + rect = mxc_isi_pipe_get_pad_crop(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + rect->left = 0; + rect->top = 0; + rect->width = sel->r.width; + rect->height = sel->r.height; + + format = mxc_isi_pipe_get_pad_format(pipe, state, + MXC_ISI_PIPE_PAD_SOURCE); + format->width = sel->r.width; + format->height = sel->r.height; + break; + + default: + return -EINVAL; + } + + dev_dbg(pipe->isi->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__, + sel->target, sel->r.left, sel->r.top, sel->r.width, + sel->r.height); + + return 0; +} + +static const struct v4l2_subdev_pad_ops mxc_isi_pipe_subdev_pad_ops = { + .init_cfg = mxc_isi_pipe_init_cfg, + .enum_mbus_code = mxc_isi_pipe_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mxc_isi_pipe_set_fmt, + .get_selection = mxc_isi_pipe_get_selection, + .set_selection = mxc_isi_pipe_set_selection, +}; + +static const struct v4l2_subdev_ops mxc_isi_pipe_subdev_ops = { + .pad = &mxc_isi_pipe_subdev_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * IRQ handling + */ + +static irqreturn_t mxc_isi_pipe_irq_handler(int irq, void *priv) +{ + struct mxc_isi_pipe *pipe = priv; + const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg; + u32 status; + + status = mxc_isi_channel_irq_status(pipe, true); + + if (status & CHNL_STS_FRM_STRD) { + if (!WARN_ON(!pipe->irq_handler)) + pipe->irq_handler(pipe, status); + } + + if (status & (CHNL_STS_AXI_WR_ERR_Y | + CHNL_STS_AXI_WR_ERR_U | + CHNL_STS_AXI_WR_ERR_V)) + dev_dbg(pipe->isi->dev, "%s: IRQ AXI Error stat=0x%X\n", + __func__, status); + + if (status & (ier_reg->panic_y_buf_en.mask | + ier_reg->panic_u_buf_en.mask | + ier_reg->panic_v_buf_en.mask)) + dev_dbg(pipe->isi->dev, "%s: IRQ Panic OFLW Error stat=0x%X\n", + __func__, status); + + if (status & (ier_reg->oflw_y_buf_en.mask | + ier_reg->oflw_u_buf_en.mask | + ier_reg->oflw_v_buf_en.mask)) + dev_dbg(pipe->isi->dev, "%s: IRQ OFLW Error stat=0x%X\n", + __func__, status); + + if (status & (ier_reg->excs_oflw_y_buf_en.mask | + ier_reg->excs_oflw_u_buf_en.mask | + ier_reg->excs_oflw_v_buf_en.mask)) + dev_dbg(pipe->isi->dev, "%s: IRQ EXCS OFLW Error stat=0x%X\n", + __func__, status); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Init & cleanup + */ + +static const struct media_entity_operations mxc_isi_pipe_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id) +{ + struct mxc_isi_pipe *pipe = &isi->pipes[id]; + struct v4l2_subdev *sd; + int irq; + int ret; + + pipe->id = id; + pipe->isi = isi; + pipe->regs = isi->regs + id * isi->pdata->reg_offset; + + mutex_init(&pipe->lock); + + pipe->available_res = MXC_ISI_CHANNEL_RES_LINE_BUF + | MXC_ISI_CHANNEL_RES_OUTPUT_BUF; + pipe->acquired_res = 0; + pipe->chained_res = 0; + pipe->chained = false; + + sd = &pipe->sd; + v4l2_subdev_init(sd, &mxc_isi_pipe_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", pipe->id); + sd->dev = isi->dev; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &mxc_isi_pipe_entity_ops; + + pipe->pads[MXC_ISI_PIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pipe->pads[MXC_ISI_PIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, MXC_ISI_PIPE_PADS_NUM, + pipe->pads); + if (ret) + goto error; + + ret = v4l2_subdev_init_finalize(sd); + if (ret < 0) + goto error; + + /* Register IRQ handler. */ + mxc_isi_channel_irq_clear(pipe); + + irq = platform_get_irq(to_platform_device(isi->dev), id); + if (irq < 0) { + ret = irq; + goto error; + } + + ret = devm_request_irq(isi->dev, irq, mxc_isi_pipe_irq_handler, + 0, dev_name(isi->dev), pipe); + if (ret < 0) { + dev_err(isi->dev, "failed to request IRQ (%d)\n", ret); + goto error; + } + + return 0; + +error: + media_entity_cleanup(&sd->entity); + mutex_destroy(&pipe->lock); + + return ret; +} + +void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe) +{ + struct v4l2_subdev *sd = &pipe->sd; + + media_entity_cleanup(&sd->entity); + mutex_destroy(&pipe->lock); +} + +int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe, + mxc_isi_pipe_irq_t irq_handler) +{ + const struct mxc_isi_bus_format_info *sink_info; + const struct mxc_isi_bus_format_info *src_info; + struct v4l2_mbus_framefmt *sink_fmt; + const struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_subdev *sd = &pipe->sd; + struct v4l2_subdev_state *state; + bool bypass; + int ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK); + src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE); + v4l2_subdev_unlock_state(state); + + sink_info = mxc_isi_bus_format_by_code(sink_fmt->code, + MXC_ISI_PIPE_PAD_SINK); + src_info = mxc_isi_bus_format_by_code(src_fmt->code, + MXC_ISI_PIPE_PAD_SOURCE); + + bypass = sink_fmt->width == src_fmt->width && + sink_fmt->height == src_fmt->height && + sink_info->encoding == src_info->encoding; + + ret = mxc_isi_channel_acquire(pipe, irq_handler, bypass); + if (ret) + return ret; + + /* Chain the channel if needed for wide resolutions. */ + if (sink_fmt->width > MXC_ISI_MAX_WIDTH_UNCHAINED) { + ret = mxc_isi_channel_chain(pipe, bypass); + if (ret) + mxc_isi_channel_release(pipe); + } + + return ret; +} + +void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe) +{ + mxc_isi_channel_release(pipe); + mxc_isi_channel_unchain(pipe); +} diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h new file mode 100644 index 0000000000..1b65eccdf0 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2019-2020 NXP + */ + +#ifndef __IMX8_ISI_REGS_H__ +#define __IMX8_ISI_REGS_H__ + +#include <linux/bits.h> + +/* ISI Registers Define */ +/* Channel Control Register */ +#define CHNL_CTRL 0x0000 +#define CHNL_CTRL_CHNL_EN BIT(31) +#define CHNL_CTRL_CLK_EN BIT(30) +#define CHNL_CTRL_CHNL_BYPASS BIT(29) +#define CHNL_CTRL_CHAIN_BUF(n) ((n) << 25) +#define CHNL_CTRL_CHAIN_BUF_MASK GENMASK(26, 25) +#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN 0 +#define CHNL_CTRL_CHAIN_BUF_2_CHAIN 1 +#define CHNL_CTRL_SW_RST BIT(24) +#define CHNL_CTRL_BLANK_PXL(n) ((n) << 16) +#define CHNL_CTRL_BLANK_PXL_MASK GENMASK(23, 16) +#define CHNL_CTRL_MIPI_VC_ID(n) ((n) << 6) +#define CHNL_CTRL_MIPI_VC_ID_MASK GENMASK(7, 6) +#define CHNL_CTRL_SRC_TYPE(n) ((n) << 4) +#define CHNL_CTRL_SRC_TYPE_MASK BIT(4) +#define CHNL_CTRL_SRC_TYPE_DEVICE 0 +#define CHNL_CTRL_SRC_TYPE_MEMORY 1 +#define CHNL_CTRL_SRC_INPUT(n) ((n) << 0) +#define CHNL_CTRL_SRC_INPUT_MASK GENMASK(2, 0) + +/* Channel Image Control Register */ +#define CHNL_IMG_CTRL 0x0004 +#define CHNL_IMG_CTRL_FORMAT(n) ((n) << 24) +#define CHNL_IMG_CTRL_FORMAT_MASK GENMASK(29, 24) +#define CHNL_IMG_CTRL_FORMAT_RGBA8888 0x00 +#define CHNL_IMG_CTRL_FORMAT_ABGR8888 0x01 +#define CHNL_IMG_CTRL_FORMAT_ARGB8888 0x02 +#define CHNL_IMG_CTRL_FORMAT_RGBX888 0x03 +#define CHNL_IMG_CTRL_FORMAT_XBGR888 0x04 +#define CHNL_IMG_CTRL_FORMAT_XRGB888 0x05 +#define CHNL_IMG_CTRL_FORMAT_RGB888P 0x06 +#define CHNL_IMG_CTRL_FORMAT_BGR888P 0x07 +#define CHNL_IMG_CTRL_FORMAT_A2BGR10 0x08 +#define CHNL_IMG_CTRL_FORMAT_A2RGB10 0x09 +#define CHNL_IMG_CTRL_FORMAT_RGB565 0x0a +#define CHNL_IMG_CTRL_FORMAT_RAW8 0x0b +#define CHNL_IMG_CTRL_FORMAT_RAW10 0x0c +#define CHNL_IMG_CTRL_FORMAT_RAW10P 0x0d +#define CHNL_IMG_CTRL_FORMAT_RAW12 0x0e +#define CHNL_IMG_CTRL_FORMAT_RAW16 0x0f +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8P 0x10 +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P8P 0x11 +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P8P 0x12 +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8 0x13 +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10 0x14 +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10 0x15 +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10 0x16 +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10P 0x18 +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10P 0x19 +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10P 0x1a +#define CHNL_IMG_CTRL_FORMAT_YUV444_1P12 0x1c +#define CHNL_IMG_CTRL_FORMAT_YUV444_2P12 0x1d +#define CHNL_IMG_CTRL_FORMAT_YUV444_3P12 0x1e +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P8P 0x20 +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P8P 0x21 +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P8P 0x22 +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10 0x24 +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10 0x25 +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10 0x26 +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10P 0x28 +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10P 0x29 +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10P 0x2a +#define CHNL_IMG_CTRL_FORMAT_YUV422_1P12 0x2c +#define CHNL_IMG_CTRL_FORMAT_YUV422_2P12 0x2d +#define CHNL_IMG_CTRL_FORMAT_YUV422_3P12 0x2e +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P8P 0x31 +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P8P 0x32 +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10 0x35 +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10 0x36 +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10P 0x39 +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10P 0x3a +#define CHNL_IMG_CTRL_FORMAT_YUV420_2P12 0x3d +#define CHNL_IMG_CTRL_FORMAT_YUV420_3P12 0x3e +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL(n) ((n) << 16) +#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK GENMASK(23, 16) +#define CHNL_IMG_CTRL_GBL_ALPHA_EN BIT(15) +#define CHNL_IMG_CTRL_DEINT(n) ((n) << 12) +#define CHNL_IMG_CTRL_DEINT_MASK GENMASK(14, 12) +#define CHNL_IMG_CTRL_DEINT_WEAVE_ODD_EVEN 2 +#define CHNL_IMG_CTRL_DEINT_WEAVE_EVEN_ODD 3 +#define CHNL_IMG_CTRL_DEINT_BLEND_ODD_EVEN 4 +#define CHNL_IMG_CTRL_DEINT_BLEND_EVEN_ODD 5 +#define CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN 6 +#define CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD 7 +#define CHNL_IMG_CTRL_DEC_X(n) ((n) << 10) +#define CHNL_IMG_CTRL_DEC_X_MASK GENMASK(11, 10) +#define CHNL_IMG_CTRL_DEC_Y(n) ((n) << 8) +#define CHNL_IMG_CTRL_DEC_Y_MASK GENMASK(9, 8) +#define CHNL_IMG_CTRL_CROP_EN BIT(7) +#define CHNL_IMG_CTRL_VFLIP_EN BIT(6) +#define CHNL_IMG_CTRL_HFLIP_EN BIT(5) +#define CHNL_IMG_CTRL_YCBCR_MODE BIT(3) +#define CHNL_IMG_CTRL_CSC_MODE(n) ((n) << 1) +#define CHNL_IMG_CTRL_CSC_MODE_MASK GENMASK(2, 1) +#define CHNL_IMG_CTRL_CSC_MODE_YUV2RGB 0 +#define CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB 1 +#define CHNL_IMG_CTRL_CSC_MODE_RGB2YUV 2 +#define CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR 3 +#define CHNL_IMG_CTRL_CSC_BYPASS BIT(0) + +/* Channel Output Buffer Control Register */ +#define CHNL_OUT_BUF_CTRL 0x0008 +#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR BIT(15) +#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR BIT(14) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V(n) ((n) << 6) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK GENMASK(7, 6) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_75 3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U(n) ((n) << 3) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK GENMASK(4, 3) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_75 3 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y(n) ((n) << 0) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK GENMASK(1, 0) +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_NO_PANIC 0 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_25 1 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_50 2 +#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_75 3 + +/* Channel Image Configuration */ +#define CHNL_IMG_CFG 0x000c +#define CHNL_IMG_CFG_HEIGHT(n) ((n) << 16) +#define CHNL_IMG_CFG_HEIGHT_MASK GENMASK(28, 16) +#define CHNL_IMG_CFG_WIDTH(n) ((n) << 0) +#define CHNL_IMG_CFG_WIDTH_MASK GENMASK(12, 0) + +/* Channel Interrupt Enable Register */ +#define CHNL_IER 0x0010 +#define CHNL_IER_MEM_RD_DONE_EN BIT(31) +#define CHNL_IER_LINE_RCVD_EN BIT(30) +#define CHNL_IER_FRM_RCVD_EN BIT(29) +#define CHNL_IER_AXI_WR_ERR_V_EN BIT(28) +#define CHNL_IER_AXI_WR_ERR_U_EN BIT(27) +#define CHNL_IER_AXI_WR_ERR_Y_EN BIT(26) +#define CHNL_IER_AXI_RD_ERR_EN BIT(25) + +/* Channel Status Register */ +#define CHNL_STS 0x0014 +#define CHNL_STS_MEM_RD_DONE BIT(31) +#define CHNL_STS_LINE_STRD BIT(30) +#define CHNL_STS_FRM_STRD BIT(29) +#define CHNL_STS_AXI_WR_ERR_V BIT(28) +#define CHNL_STS_AXI_WR_ERR_U BIT(27) +#define CHNL_STS_AXI_WR_ERR_Y BIT(26) +#define CHNL_STS_AXI_RD_ERR BIT(25) +#define CHNL_STS_OFLW_PANIC_V_BUF BIT(24) +#define CHNL_STS_EXCS_OFLW_V_BUF BIT(23) +#define CHNL_STS_OFLW_V_BUF BIT(22) +#define CHNL_STS_OFLW_PANIC_U_BUF BIT(21) +#define CHNL_STS_EXCS_OFLW_U_BUF BIT(20) +#define CHNL_STS_OFLW_U_BUF BIT(19) +#define CHNL_STS_OFLW_PANIC_Y_BUF BIT(18) +#define CHNL_STS_EXCS_OFLW_Y_BUF BIT(17) +#define CHNL_STS_OFLW_Y_BUF BIT(16) +#define CHNL_STS_EARLY_VSYNC_ERR BIT(15) +#define CHNL_STS_LATE_VSYNC_ERR BIT(14) +#define CHNL_STS_MEM_RD_OFLOW BIT(10) +#define CHNL_STS_BUF2_ACTIVE BIT(9) +#define CHNL_STS_BUF1_ACTIVE BIT(8) +#define CHNL_STS_OFLW_BYTES(n) ((n) << 0) +#define CHNL_STS_OFLW_BYTES_MASK GENMASK(7, 0) + +/* Channel Scale Factor Register */ +#define CHNL_SCALE_FACTOR 0x0018 +#define CHNL_SCALE_FACTOR_Y_SCALE(n) ((n) << 16) +#define CHNL_SCALE_FACTOR_Y_SCALE_MASK GENMASK(29, 16) +#define CHNL_SCALE_FACTOR_X_SCALE(n) ((n) << 0) +#define CHNL_SCALE_FACTOR_X_SCALE_MASK GENMASK(13, 0) + +/* Channel Scale Offset Register */ +#define CHNL_SCALE_OFFSET 0x001c +#define CHNL_SCALE_OFFSET_Y_SCALE(n) ((n) << 16) +#define CHNL_SCALE_OFFSET_Y_SCALE_MASK GENMASK(27, 16) +#define CHNL_SCALE_OFFSET_X_SCALE(n) ((n) << 0) +#define CHNL_SCALE_OFFSET_X_SCALE_MASK GENMASK(11, 0) + +/* Channel Crop Upper Left Corner Coordinate Register */ +#define CHNL_CROP_ULC 0x0020 +#define CHNL_CROP_ULC_X(n) ((n) << 16) +#define CHNL_CROP_ULC_X_MASK GENMASK(27, 16) +#define CHNL_CROP_ULC_Y(n) ((n) << 0) +#define CHNL_CROP_ULC_Y_MASK GENMASK(11, 0) + +/* Channel Crop Lower Right Corner Coordinate Register */ +#define CHNL_CROP_LRC 0x0024 +#define CHNL_CROP_LRC_X(n) ((n) << 16) +#define CHNL_CROP_LRC_X_MASK GENMASK(27, 16) +#define CHNL_CROP_LRC_Y(n) ((n) << 0) +#define CHNL_CROP_LRC_Y_MASK GENMASK(11, 0) + +/* Channel Color Space Conversion Coefficient Register 0 */ +#define CHNL_CSC_COEFF0 0x0028 +#define CHNL_CSC_COEFF0_A2(n) ((n) << 16) +#define CHNL_CSC_COEFF0_A2_MASK GENMASK(26, 16) +#define CHNL_CSC_COEFF0_A1(n) ((n) << 0) +#define CHNL_CSC_COEFF0_A1_MASK GENMASK(10, 0) + +/* Channel Color Space Conversion Coefficient Register 1 */ +#define CHNL_CSC_COEFF1 0x002c +#define CHNL_CSC_COEFF1_B1(n) ((n) << 16) +#define CHNL_CSC_COEFF1_B1_MASK GENMASK(26, 16) +#define CHNL_CSC_COEFF1_A3(n) ((n) << 0) +#define CHNL_CSC_COEFF1_A3_MASK GENMASK(10, 0) + +/* Channel Color Space Conversion Coefficient Register 2 */ +#define CHNL_CSC_COEFF2 0x0030 +#define CHNL_CSC_COEFF2_B3(n) ((n) << 16) +#define CHNL_CSC_COEFF2_B3_MASK GENMASK(26, 16) +#define CHNL_CSC_COEFF2_B2(n) ((n) << 0) +#define CHNL_CSC_COEFF2_B2_MASK GENMASK(10, 0) + +/* Channel Color Space Conversion Coefficient Register 3 */ +#define CHNL_CSC_COEFF3 0x0034 +#define CHNL_CSC_COEFF3_C2(n) ((n) << 16) +#define CHNL_CSC_COEFF3_C2_MASK GENMASK(26, 16) +#define CHNL_CSC_COEFF3_C1(n) ((n) << 0) +#define CHNL_CSC_COEFF3_C1_MASK GENMASK(10, 0) + +/* Channel Color Space Conversion Coefficient Register 4 */ +#define CHNL_CSC_COEFF4 0x0038 +#define CHNL_CSC_COEFF4_D1(n) ((n) << 16) +#define CHNL_CSC_COEFF4_D1_MASK GENMASK(24, 16) +#define CHNL_CSC_COEFF4_C3(n) ((n) << 0) +#define CHNL_CSC_COEFF4_C3_MASK GENMASK(10, 0) + +/* Channel Color Space Conversion Coefficient Register 5 */ +#define CHNL_CSC_COEFF5 0x003c +#define CHNL_CSC_COEFF5_D3(n) ((n) << 16) +#define CHNL_CSC_COEFF5_D3_MASK GENMASK(24, 16) +#define CHNL_CSC_COEFF5_D2(n) ((n) << 0) +#define CHNL_CSC_COEFF5_D2_MASK GENMASK(8, 0) + +/* Channel Alpha Value Register for ROI 0 */ +#define CHNL_ROI_0_ALPHA 0x0040 +#define CHNL_ROI_0_ALPHA_VAL(n) ((n) << 24) +#define CHNL_ROI_0_ALPHA_MASK GENMASK(31, 24) +#define CHNL_ROI_0_ALPHA_EN BIT(16) + +/* Channel Upper Left Coordinate Register for ROI 0 */ +#define CHNL_ROI_0_ULC 0x0044 +#define CHNL_ROI_0_ULC_X(n) ((n) << 16) +#define CHNL_ROI_0_ULC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_0_ULC_Y(n) ((n) << 0) +#define CHNL_ROI_0_ULC_Y_MASK GENMASK(11, 0) + +/* Channel Lower Right Coordinate Register for ROI 0 */ +#define CHNL_ROI_0_LRC 0x0048 +#define CHNL_ROI_0_LRC_X(n) ((n) << 16) +#define CHNL_ROI_0_LRC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_0_LRC_Y(n) ((n) << 0) +#define CHNL_ROI_0_LRC_Y_MASK GENMASK(11, 0) + +/* Channel Alpha Value Register for ROI 1 */ +#define CHNL_ROI_1_ALPHA 0x004c +#define CHNL_ROI_1_ALPHA_VAL(n) ((n) << 24) +#define CHNL_ROI_1_ALPHA_MASK GENMASK(31, 24) +#define CHNL_ROI_1_ALPHA_EN BIT(16) + +/* Channel Upper Left Coordinate Register for ROI 1 */ +#define CHNL_ROI_1_ULC 0x0050 +#define CHNL_ROI_1_ULC_X(n) ((n) << 16) +#define CHNL_ROI_1_ULC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_1_ULC_Y(n) ((n) << 0) +#define CHNL_ROI_1_ULC_Y_MASK GENMASK(11, 0) + +/* Channel Lower Right Coordinate Register for ROI 1 */ +#define CHNL_ROI_1_LRC 0x0054 +#define CHNL_ROI_1_LRC_X(n) ((n) << 16) +#define CHNL_ROI_1_LRC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_1_LRC_Y(n) ((n) << 0) +#define CHNL_ROI_1_LRC_Y_MASK GENMASK(11, 0) + +/* Channel Alpha Value Register for ROI 2 */ +#define CHNL_ROI_2_ALPHA 0x0058 +#define CHNL_ROI_2_ALPHA_VAL(n) ((n) << 24) +#define CHNL_ROI_2_ALPHA_MASK GENMASK(31, 24) +#define CHNL_ROI_2_ALPHA_EN BIT(16) + +/* Channel Upper Left Coordinate Register for ROI 2 */ +#define CHNL_ROI_2_ULC 0x005c +#define CHNL_ROI_2_ULC_X(n) ((n) << 16) +#define CHNL_ROI_2_ULC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_2_ULC_Y(n) ((n) << 0) +#define CHNL_ROI_2_ULC_Y_MASK GENMASK(11, 0) + +/* Channel Lower Right Coordinate Register for ROI 2 */ +#define CHNL_ROI_2_LRC 0x0060 +#define CHNL_ROI_2_LRC_X(n) ((n) << 16) +#define CHNL_ROI_2_LRC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_2_LRC_Y(n) ((n) << 0) +#define CHNL_ROI_2_LRC_Y_MASK GENMASK(11, 0) + +/* Channel Alpha Value Register for ROI 3 */ +#define CHNL_ROI_3_ALPHA 0x0064 +#define CHNL_ROI_3_ALPHA_VAL(n) ((n) << 24) +#define CHNL_ROI_3_ALPHA_MASK GENMASK(31, 24) +#define CHNL_ROI_3_ALPHA_EN BIT(16) + +/* Channel Upper Left Coordinate Register for ROI 3 */ +#define CHNL_ROI_3_ULC 0x0068 +#define CHNL_ROI_3_ULC_X(n) ((n) << 16) +#define CHNL_ROI_3_ULC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_3_ULC_Y(n) ((n) << 0) +#define CHNL_ROI_3_ULC_Y_MASK GENMASK(11, 0) + +/* Channel Lower Right Coordinate Register for ROI 3 */ +#define CHNL_ROI_3_LRC 0x006c +#define CHNL_ROI_3_LRC_X(n) ((n) << 16) +#define CHNL_ROI_3_LRC_X_MASK GENMASK(27, 16) +#define CHNL_ROI_3_LRC_Y(n) ((n) << 0) +#define CHNL_ROI_3_LRC_Y_MASK GENMASK(11, 0) +/* Channel RGB or Luma (Y) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_Y 0x0070 + +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_U 0x0074 + +/* Channel Chroma (V/Cr) Output Buffer 1 Address */ +#define CHNL_OUT_BUF1_ADDR_V 0x0078 + +/* Channel Output Buffer Pitch */ +#define CHNL_OUT_BUF_PITCH 0x007c +#define CHNL_OUT_BUF_PITCH_LINE_PITCH(n) ((n) << 0) +#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK GENMASK(15, 0) + +/* Channel Input Buffer Address */ +#define CHNL_IN_BUF_ADDR 0x0080 + +/* Channel Input Buffer Pitch */ +#define CHNL_IN_BUF_PITCH 0x0084 +#define CHNL_IN_BUF_PITCH_FRM_PITCH(n) ((n) << 16) +#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK GENMASK(31, 16) +#define CHNL_IN_BUF_PITCH_LINE_PITCH(n) ((n) << 0) +#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK GENMASK(15, 0) + +/* Channel Memory Read Control */ +#define CHNL_MEM_RD_CTRL 0x0088 +#define CHNL_MEM_RD_CTRL_IMG_TYPE(n) ((n) << 28) +#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK GENMASK(31, 28) +#define CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P 0x00 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P 0x01 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_XRGB8 0x02 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGBX8 0x03 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8 0x04 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565 0x05 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2BGR10 0x06 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2RGB10 0x07 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8P 0x08 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10 0x09 +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10P 0x0a +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P12 0x0b +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8 0x0c +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P 0x0d +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P10 0x0e +#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P12 0x0f +#define CHNL_MEM_RD_CTRL_READ_MEM BIT(0) + +/* Channel RGB or Luma (Y) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_Y 0x008c + +/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_U 0x0090 + +/* Channel Chroma (V/Cr) Output Buffer 2 Address */ +#define CHNL_OUT_BUF2_ADDR_V 0x0094 + +/* Channel scale image config */ +#define CHNL_SCL_IMG_CFG 0x0098 +#define CHNL_SCL_IMG_CFG_HEIGHT(n) ((n) << 16) +#define CHNL_SCL_IMG_CFG_HEIGHT_MASK GENMASK(28, 16) +#define CHNL_SCL_IMG_CFG_WIDTH(n) ((n) << 0) +#define CHNL_SCL_IMG_CFG_WIDTH_MASK GENMASK(12, 0) + +/* Channel Flow Control Register */ +#define CHNL_FLOW_CTRL 0x009c +#define CHNL_FLOW_CTRL_FC_DENOM_MASK GENMASK(7, 0) +#define CHNL_FLOW_CTRL_FC_DENOM(n) ((n) << 0) +#define CHNL_FLOW_CTRL_FC_NUMER_MASK GENMASK(23, 16) +#define CHNL_FLOW_CTRL_FC_NUMER(n) ((n) << 0) + +/* Channel Output Y-Buffer 1 Extended Address Bits */ +#define CHNL_Y_BUF1_XTND_ADDR 0x00a0 + +/* Channel Output U-Buffer 1 Extended Address Bits */ +#define CHNL_U_BUF1_XTND_ADDR 0x00a4 + +/* Channel Output V-Buffer 1 Extended Address Bits */ +#define CHNL_V_BUF1_XTND_ADDR 0x00a8 + +/* Channel Output Y-Buffer 2 Extended Address Bits */ +#define CHNL_Y_BUF2_XTND_ADDR 0x00ac + +/* Channel Output U-Buffer 2 Extended Address Bits */ +#define CHNL_U_BUF2_XTND_ADDR 0x00b0 + +/* Channel Output V-Buffer 2 Extended Address Bits */ +#define CHNL_V_BUF2_XTND_ADDR 0x00b4 + +/* Channel Input Buffer Extended Address Bits */ +#define CHNL_IN_BUF_XTND_ADDR 0x00b8 + +#endif /* __IMX8_ISI_REGS_H__ */ diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c new file mode 100644 index 0000000000..10840c9a09 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c @@ -0,0 +1,1512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform + * + * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which + * used to process image from camera sensor to memory or DC + * + * Copyright (c) 2019 NXP Semiconductor + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/minmax.h> +#include <linux/pm_runtime.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> + +#include "imx8-isi-core.h" +#include "imx8-isi-regs.h" + +/* Keep the first entry matching MXC_ISI_DEF_PIXEL_FORMAT */ +static const struct mxc_isi_format_info mxc_isi_formats[] = { + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_YUYV, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT + | MXC_ISI_VIDEO_M2M_CAP, + .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_1P8P, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_YUVA32, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_1P8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 32 }, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV12, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P, + .color_planes = 2, + .mem_planes = 1, + .depth = { 8, 16 }, + .hsub = 2, + .vsub = 2, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV12M, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P, + .mem_planes = 2, + .color_planes = 2, + .depth = { 8, 16 }, + .hsub = 2, + .vsub = 2, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV16, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P, + .color_planes = 2, + .mem_planes = 1, + .depth = { 8, 16 }, + .hsub = 2, + .vsub = 1, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV16M, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P, + .mem_planes = 2, + .color_planes = 2, + .depth = { 8, 16 }, + .hsub = 2, + .vsub = 1, + .encoding = MXC_ISI_ENC_YUV, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_YUV444M, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_3P8P, + .mem_planes = 3, + .color_planes = 3, + .depth = { 8, 8, 8 }, + .hsub = 1, + .vsub = 1, + .encoding = MXC_ISI_ENC_YUV, + }, + /* RGB formats */ + { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .fourcc = V4L2_PIX_FMT_RGB565, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT + | MXC_ISI_VIDEO_M2M_CAP, + .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB565, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RGB, + }, { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .fourcc = V4L2_PIX_FMT_RGB24, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT + | MXC_ISI_VIDEO_M2M_CAP, + .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_BGR888P, + .mem_planes = 1, + .color_planes = 1, + .depth = { 24 }, + .encoding = MXC_ISI_ENC_RGB, + }, { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .fourcc = V4L2_PIX_FMT_BGR24, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT + | MXC_ISI_VIDEO_M2M_CAP, + .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB888P, + .mem_planes = 1, + .color_planes = 1, + .depth = { 24 }, + .encoding = MXC_ISI_ENC_RGB, + }, { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .fourcc = V4L2_PIX_FMT_XBGR32, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT + | MXC_ISI_VIDEO_M2M_CAP, + .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_XRGB888, + .mem_planes = 1, + .color_planes = 1, + .depth = { 32 }, + .encoding = MXC_ISI_ENC_RGB, + }, { + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .fourcc = V4L2_PIX_FMT_ABGR32, + .type = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_ARGB8888, + .mem_planes = 1, + .color_planes = 1, + .depth = { 32 }, + .encoding = MXC_ISI_ENC_RGB, + }, + /* + * RAW formats + * + * The ISI shifts the 10-bit and 12-bit formats left by 6 and 4 bits + * when using CHNL_IMG_CTRL_FORMAT_RAW10 or MXC_ISI_OUT_FMT_RAW12 + * respectively, to align the bits to the left and pad with zeros in + * the LSBs. The corresponding V4L2 formats are however right-aligned, + * we have to use CHNL_IMG_CTRL_FORMAT_RAW16 to avoid the left shift. + */ + { + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .fourcc = V4L2_PIX_FMT_GREY, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .fourcc = V4L2_PIX_FMT_Y10, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y12_1X12, + .fourcc = V4L2_PIX_FMT_Y12, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_Y14_1X14, + .fourcc = V4L2_PIX_FMT_Y14, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .fourcc = V4L2_PIX_FMT_SBGGR8, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .fourcc = V4L2_PIX_FMT_SGBRG8, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .fourcc = V4L2_PIX_FMT_SGRBG8, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .fourcc = V4L2_PIX_FMT_SRGGB8, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .fourcc = V4L2_PIX_FMT_SBGGR10, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .fourcc = V4L2_PIX_FMT_SGBRG10, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .fourcc = V4L2_PIX_FMT_SGRBG10, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .fourcc = V4L2_PIX_FMT_SRGGB10, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .fourcc = V4L2_PIX_FMT_SBGGR12, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .fourcc = V4L2_PIX_FMT_SGBRG12, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .fourcc = V4L2_PIX_FMT_SGRBG12, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .fourcc = V4L2_PIX_FMT_SRGGB12, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14, + .fourcc = V4L2_PIX_FMT_SBGGR14, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14, + .fourcc = V4L2_PIX_FMT_SGBRG14, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14, + .fourcc = V4L2_PIX_FMT_SGRBG14, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14, + .fourcc = V4L2_PIX_FMT_SRGGB14, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16, + .mem_planes = 1, + .color_planes = 1, + .depth = { 16 }, + .encoding = MXC_ISI_ENC_RAW, + }, + /* JPEG */ + { + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .fourcc = V4L2_PIX_FMT_MJPEG, + .type = MXC_ISI_VIDEO_CAP, + .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8, + .mem_planes = 1, + .color_planes = 1, + .depth = { 8 }, + .encoding = MXC_ISI_ENC_RAW, + } +}; + +const struct mxc_isi_format_info * +mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { + const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i]; + + if (fmt->fourcc == fourcc && fmt->type & type) + return fmt; + } + + return NULL; +} + +const struct mxc_isi_format_info * +mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { + const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i]; + + if (!(fmt->type & type)) + continue; + + if (!index) + return fmt; + + index--; + } + + return NULL; +} + +const struct mxc_isi_format_info * +mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix, + enum mxc_isi_video_type type) +{ + const struct mxc_isi_format_info *fmt; + unsigned int max_width; + unsigned int i; + + max_width = pipe->id == pipe->isi->pdata->num_channels - 1 + ? MXC_ISI_MAX_WIDTH_UNCHAINED + : MXC_ISI_MAX_WIDTH_CHAINED; + + fmt = mxc_isi_format_by_fourcc(pix->pixelformat, type); + if (!fmt) + fmt = &mxc_isi_formats[0]; + + pix->width = clamp(pix->width, MXC_ISI_MIN_WIDTH, max_width); + pix->height = clamp(pix->height, MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT); + pix->pixelformat = fmt->fourcc; + pix->field = V4L2_FIELD_NONE; + + if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) { + pix->colorspace = MXC_ISI_DEF_COLOR_SPACE; + pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC; + pix->quantization = MXC_ISI_DEF_QUANTIZATION; + pix->xfer_func = MXC_ISI_DEF_XFER_FUNC; + } + + if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) { + bool is_rgb = fmt->encoding == MXC_ISI_ENC_RGB; + + pix->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, pix->colorspace, + pix->ycbcr_enc); + } + if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT) + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + + pix->num_planes = fmt->mem_planes; + + for (i = 0; i < fmt->color_planes; ++i) { + struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i]; + unsigned int bpl; + + /* The pitch must be identical for all planes. */ + if (i == 0) + bpl = clamp(plane->bytesperline, + pix->width * fmt->depth[0] / 8, + 65535U); + else + bpl = pix->plane_fmt[0].bytesperline; + + plane->bytesperline = bpl; + + plane->sizeimage = plane->bytesperline * pix->height; + if (i >= 1) + plane->sizeimage /= fmt->vsub; + } + + /* + * For single-planar pixel formats with multiple color planes, + * concatenate the size of all planes and clear all planes but the + * first one. + */ + if (fmt->color_planes != fmt->mem_planes) { + for (i = 1; i < fmt->color_planes; ++i) { + struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i]; + + pix->plane_fmt[0].sizeimage += plane->sizeimage; + plane->bytesperline = 0; + plane->sizeimage = 0; + } + } + + return fmt; +} + +/* ----------------------------------------------------------------------------- + * videobuf2 queue operations + */ + +static void mxc_isi_video_frame_write_done(struct mxc_isi_pipe *pipe, + u32 status) +{ + struct mxc_isi_video *video = &pipe->video; + struct device *dev = pipe->isi->dev; + struct mxc_isi_buffer *next_buf; + struct mxc_isi_buffer *buf; + enum mxc_isi_buf_id buf_id; + + spin_lock(&video->buf_lock); + + /* + * The ISI hardware handles buffers using a ping-pong mechanism with + * two sets of destination addresses (with shadow registers to allow + * programming addresses for all planes atomically) named BUF1 and + * BUF2. Addresses can be loaded and copied to shadow registers at any + * at any time. + * + * The hardware keeps track of which buffer is being written to and + * automatically switches to the other buffer at frame end, copying the + * corresponding address to another set of shadow registers that track + * the address being written to. The active buffer tracking bits are + * accessible through the CHNL_STS register. + * + * BUF1 BUF2 | Event | Action + * | | + * | | Program initial buffers + * | | B0 in BUF1, B1 in BUF2 + * | Start ISI | + * +----+ | | + * | B0 | | | + * +----+ | | + * +----+ | FRM IRQ 0 | B0 complete, BUF2 now active + * | B1 | | | Program B2 in BUF1 + * +----+ | | + * +----+ | FRM IRQ 1 | B1 complete, BUF1 now active + * | B2 | | | Program B3 in BUF2 + * +----+ | | + * +----+ | FRM IRQ 2 | B2 complete, BUF2 now active + * | B3 | | | Program B4 in BUF1 + * +----+ | | + * +----+ | FRM IRQ 3 | B3 complete, BUF1 now active + * | B4 | | | Program B5 in BUF2 + * +----+ | | + * ... | | + * + * Races between address programming and buffer switching can be + * detected by checking if a frame end interrupt occurred after + * programming the addresses. + * + * As none of the shadow registers are accessible, races can occur + * between address programming and buffer switching. It is possible to + * detect the race condition by checking if a frame end interrupt + * occurred after programming the addresses, but impossible to + * determine if the race has been won or lost. + * + * In addition to this, we need to use discard buffers if no pending + * buffers are available. To simplify handling of discard buffer, we + * need to allocate three of them, as two can be active concurrently + * and we need to still be able to get hold of a next buffer. The logic + * could be improved to use two buffers only, but as all discard + * buffers share the same memory, an additional buffer is cheap. + */ + + /* Check which buffer has just completed. */ + buf_id = pipe->isi->pdata->buf_active_reverse + ? (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF2 : MXC_ISI_BUF1) + : (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF1 : MXC_ISI_BUF2); + + buf = list_first_entry_or_null(&video->out_active, + struct mxc_isi_buffer, list); + + /* Safety check, this should really never happen. */ + if (!buf) { + dev_warn(dev, "trying to access empty active list\n"); + goto done; + } + + /* + * If the buffer that has completed doesn't match the buffer on the + * front of the active list, it means we have lost one frame end + * interrupt (or possibly a large odd number of interrupts, although + * quite unlikely). + * + * For instance, if IRQ1 is lost and we handle IRQ2, both B1 and B2 + * have been completed, but B3 hasn't been programmed, BUF2 still + * addresses B1 and the ISI is now writing in B1 instead of B3. We + * can't complete B2 as that would result in out-of-order completion. + * + * The only option is to ignore this interrupt and try again. When IRQ3 + * will be handled, we will complete B1 and be in sync again. + */ + if (buf->id != buf_id) { + dev_dbg(dev, "buffer ID mismatch (expected %u, got %u), skipping\n", + buf->id, buf_id); + + /* + * Increment the frame count by two to account for the missed + * and the ignored interrupts. + */ + video->frame_count += 2; + goto done; + } + + /* Pick the next buffer and queue it to the hardware. */ + next_buf = list_first_entry_or_null(&video->out_pending, + struct mxc_isi_buffer, list); + if (!next_buf) { + next_buf = list_first_entry_or_null(&video->out_discard, + struct mxc_isi_buffer, list); + + /* Safety check, this should never happen. */ + if (!next_buf) { + dev_warn(dev, "trying to access empty discard list\n"); + goto done; + } + } + + mxc_isi_channel_set_outbuf(pipe, next_buf->dma_addrs, buf_id); + next_buf->id = buf_id; + + /* + * Check if we have raced with the end of frame interrupt. If so, we + * can't tell if the ISI has recorded the new address, or is still + * using the previous buffer. We must assume the latter as that is the + * worst case. + * + * For instance, if we are handling IRQ1 and now detect the FRM + * interrupt, assume B2 has completed and the ISI has switched to BUF2 + * using B1 just before we programmed B3. Unlike in the previous race + * condition, B3 has been programmed and will be written to the next + * time the ISI switches to BUF2. We can however handle this exactly as + * the first race condition, as we'll program B3 (still at the head of + * the pending list) when handling IRQ3. + */ + status = mxc_isi_channel_irq_status(pipe, false); + if (status & CHNL_STS_FRM_STRD) { + dev_dbg(dev, "raced with frame end interrupt\n"); + video->frame_count += 2; + goto done; + } + + /* + * The next buffer has been queued successfully, move it to the active + * list, and complete the current buffer. + */ + list_move_tail(&next_buf->list, &video->out_active); + + if (!buf->discard) { + list_del_init(&buf->list); + buf->v4l2_buf.sequence = video->frame_count; + buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); + } else { + list_move_tail(&buf->list, &video->out_discard); + } + + video->frame_count++; + +done: + spin_unlock(&video->buf_lock); +} + +static void mxc_isi_video_free_discard_buffers(struct mxc_isi_video *video) +{ + unsigned int i; + + for (i = 0; i < video->pix.num_planes; i++) { + struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i]; + + if (!buf->addr) + continue; + + dma_free_coherent(video->pipe->isi->dev, buf->size, buf->addr, + buf->dma); + buf->addr = NULL; + } +} + +static int mxc_isi_video_alloc_discard_buffers(struct mxc_isi_video *video) +{ + unsigned int i, j; + + /* Allocate memory for each plane. */ + for (i = 0; i < video->pix.num_planes; i++) { + struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i]; + + buf->size = PAGE_ALIGN(video->pix.plane_fmt[i].sizeimage); + buf->addr = dma_alloc_coherent(video->pipe->isi->dev, buf->size, + &buf->dma, GFP_DMA | GFP_KERNEL); + if (!buf->addr) { + mxc_isi_video_free_discard_buffers(video); + return -ENOMEM; + } + + dev_dbg(video->pipe->isi->dev, + "discard buffer plane %u: %zu bytes @%pad (CPU address %p)\n", + i, buf->size, &buf->dma, buf->addr); + } + + /* Fill the DMA addresses in the discard buffers. */ + for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) { + struct mxc_isi_buffer *buf = &video->buf_discard[i]; + + buf->discard = true; + + for (j = 0; j < video->pix.num_planes; ++j) + buf->dma_addrs[j] = video->discard_buffer[j].dma; + } + + return 0; +} + +static int mxc_isi_video_validate_format(struct mxc_isi_video *video) +{ + const struct v4l2_mbus_framefmt *format; + const struct mxc_isi_format_info *info; + struct v4l2_subdev_state *state; + struct v4l2_subdev *sd = &video->pipe->sd; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + info = mxc_isi_format_by_fourcc(video->pix.pixelformat, + MXC_ISI_VIDEO_CAP); + format = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE); + + if (format->code != info->mbus_code || + format->width != video->pix.width || + format->height != video->pix.height) { + dev_dbg(video->pipe->isi->dev, + "%s: configuration mismatch, 0x%04x/%ux%u != 0x%04x/%ux%u\n", + __func__, format->code, format->width, format->height, + info->mbus_code, video->pix.width, video->pix.height); + ret = -EINVAL; + } + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static void mxc_isi_video_return_buffers(struct mxc_isi_video *video, + enum vb2_buffer_state state) +{ + struct mxc_isi_buffer *buf; + + spin_lock_irq(&video->buf_lock); + + while (!list_empty(&video->out_active)) { + buf = list_first_entry(&video->out_active, + struct mxc_isi_buffer, list); + list_del_init(&buf->list); + if (buf->discard) + continue; + + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state); + } + + while (!list_empty(&video->out_pending)) { + buf = list_first_entry(&video->out_pending, + struct mxc_isi_buffer, list); + list_del_init(&buf->list); + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state); + } + + while (!list_empty(&video->out_discard)) { + buf = list_first_entry(&video->out_discard, + struct mxc_isi_buffer, list); + list_del_init(&buf->list); + } + + INIT_LIST_HEAD(&video->out_active); + INIT_LIST_HEAD(&video->out_pending); + INIT_LIST_HEAD(&video->out_discard); + + spin_unlock_irq(&video->buf_lock); +} + +static void mxc_isi_video_queue_first_buffers(struct mxc_isi_video *video) +{ + unsigned int discard; + unsigned int i; + + lockdep_assert_held(&video->buf_lock); + + /* + * Queue two ISI channel output buffers. We are not guaranteed to have + * any buffer in the pending list when this function is called from the + * system resume handler. Use pending buffers as much as possible, and + * use discard buffers to fill the remaining slots. + */ + + /* How many discard buffers do we need to queue first ? */ + discard = list_empty(&video->out_pending) ? 2 + : list_is_singular(&video->out_pending) ? 1 + : 0; + + for (i = 0; i < 2; ++i) { + enum mxc_isi_buf_id buf_id = i == 0 ? MXC_ISI_BUF1 + : MXC_ISI_BUF2; + struct mxc_isi_buffer *buf; + struct list_head *list; + + list = i < discard ? &video->out_discard : &video->out_pending; + buf = list_first_entry(list, struct mxc_isi_buffer, list); + + mxc_isi_channel_set_outbuf(video->pipe, buf->dma_addrs, buf_id); + buf->id = buf_id; + list_move_tail(&buf->list, &video->out_active); + } +} + +static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf) +{ + return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf); +} + +int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format, + const struct mxc_isi_format_info *info, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[]) +{ + unsigned int i; + + if (*num_planes) { + if (*num_planes != info->mem_planes) + return -EINVAL; + + for (i = 0; i < info->mem_planes; ++i) { + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + return 0; + } + + *num_planes = info->mem_planes; + + for (i = 0; i < info->mem_planes; ++i) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3], + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *pix) +{ + unsigned int i; + + for (i = 0; i < info->mem_planes; ++i) + dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb2, i); + + /* + * For single-planar pixel formats with multiple color planes, split + * the buffer into color planes. + */ + if (info->color_planes != info->mem_planes) { + unsigned int size = pix->plane_fmt[0].bytesperline * pix->height; + + for (i = 1; i < info->color_planes; ++i) { + unsigned int vsub = i > 1 ? info->vsub : 1; + + dma_addrs[i] = dma_addrs[i - 1] + size / vsub; + } + } +} + +int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2, + const struct mxc_isi_format_info *info, + const struct v4l2_pix_format_mplane *pix) +{ + unsigned int i; + + for (i = 0; i < info->mem_planes; i++) { + unsigned long size = pix->plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb2, i) < size) { + dev_err(isi->dev, "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb2, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb2, i, size); + } + + return 0; +} + +static int mxc_isi_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mxc_isi_video *video = vb2_get_drv_priv(q); + + return mxc_isi_video_queue_setup(&video->pix, video->fmtinfo, + num_buffers, num_planes, sizes); +} + +static int mxc_isi_vb2_buffer_init(struct vb2_buffer *vb2) +{ + struct mxc_isi_buffer *buf = to_isi_buffer(to_vb2_v4l2_buffer(vb2)); + struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); + + mxc_isi_video_buffer_init(vb2, buf->dma_addrs, video->fmtinfo, + &video->pix); + + return 0; +} + +static int mxc_isi_vb2_buffer_prepare(struct vb2_buffer *vb2) +{ + struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); + + return mxc_isi_video_buffer_prepare(video->pipe->isi, vb2, + video->fmtinfo, &video->pix); +} + +static void mxc_isi_vb2_buffer_queue(struct vb2_buffer *vb2) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2); + struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf); + struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue); + + spin_lock_irq(&video->buf_lock); + list_add_tail(&buf->list, &video->out_pending); + spin_unlock_irq(&video->buf_lock); +} + +static void mxc_isi_video_init_channel(struct mxc_isi_video *video) +{ + struct mxc_isi_pipe *pipe = video->pipe; + + mxc_isi_channel_get(pipe); + + mutex_lock(video->ctrls.handler.lock); + mxc_isi_channel_set_alpha(pipe, video->ctrls.alpha); + mxc_isi_channel_set_flip(pipe, video->ctrls.hflip, video->ctrls.vflip); + mutex_unlock(video->ctrls.handler.lock); + + mxc_isi_channel_set_output_format(pipe, video->fmtinfo, &video->pix); +} + +static int mxc_isi_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_isi_video *video = vb2_get_drv_priv(q); + unsigned int i; + int ret; + + /* Initialize the ISI channel. */ + mxc_isi_video_init_channel(video); + + spin_lock_irq(&video->buf_lock); + + /* Add the discard buffers to the out_discard list. */ + for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) { + struct mxc_isi_buffer *buf = &video->buf_discard[i]; + + list_add_tail(&buf->list, &video->out_discard); + } + + /* Queue the first buffers. */ + mxc_isi_video_queue_first_buffers(video); + + /* Clear frame count */ + video->frame_count = 0; + + spin_unlock_irq(&video->buf_lock); + + ret = mxc_isi_pipe_enable(video->pipe); + if (ret) + goto error; + + return 0; + +error: + mxc_isi_channel_put(video->pipe); + mxc_isi_video_return_buffers(video, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void mxc_isi_vb2_stop_streaming(struct vb2_queue *q) +{ + struct mxc_isi_video *video = vb2_get_drv_priv(q); + + mxc_isi_pipe_disable(video->pipe); + mxc_isi_channel_put(video->pipe); + + mxc_isi_video_return_buffers(video, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops mxc_isi_vb2_qops = { + .queue_setup = mxc_isi_vb2_queue_setup, + .buf_init = mxc_isi_vb2_buffer_init, + .buf_prepare = mxc_isi_vb2_buffer_prepare, + .buf_queue = mxc_isi_vb2_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = mxc_isi_vb2_start_streaming, + .stop_streaming = mxc_isi_vb2_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static inline struct mxc_isi_video *ctrl_to_isi_video(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mxc_isi_video, ctrls.handler); +} + +static int mxc_isi_video_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_isi_video *video = ctrl_to_isi_video(ctrl); + + switch (ctrl->id) { + case V4L2_CID_ALPHA_COMPONENT: + video->ctrls.alpha = ctrl->val; + break; + case V4L2_CID_VFLIP: + video->ctrls.vflip = ctrl->val; + break; + case V4L2_CID_HFLIP: + video->ctrls.hflip = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mxc_isi_video_ctrl_ops = { + .s_ctrl = mxc_isi_video_s_ctrl, +}; + +static int mxc_isi_video_ctrls_create(struct mxc_isi_video *video) +{ + struct v4l2_ctrl_handler *handler = &video->ctrls.handler; + int ret; + + v4l2_ctrl_handler_init(handler, 3); + + v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); + + v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + if (handler->error) { + ret = handler->error; + v4l2_ctrl_handler_free(handler); + return ret; + } + + video->vdev.ctrl_handler = handler; + + return 0; +} + +static void mxc_isi_video_ctrls_delete(struct mxc_isi_video *video) +{ + v4l2_ctrl_handler_free(&video->ctrls.handler); +} + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int mxc_isi_video_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card)); + + return 0; +} + +static int mxc_isi_video_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct mxc_isi_format_info *fmt; + unsigned int index = f->index; + unsigned int i; + + if (f->mbus_code) { + /* + * If a media bus code is specified, only enumerate formats + * compatible with it. + */ + for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) { + fmt = &mxc_isi_formats[i]; + if (fmt->mbus_code != f->mbus_code) + continue; + + if (index == 0) + break; + + index--; + } + + if (i == ARRAY_SIZE(mxc_isi_formats)) + return -EINVAL; + } else { + /* Otherwise, enumerate all formatS. */ + if (f->index >= ARRAY_SIZE(mxc_isi_formats)) + return -EINVAL; + + fmt = &mxc_isi_formats[f->index]; + } + + f->pixelformat = fmt->fourcc; + f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC + | V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC; + + return 0; +} + +static int mxc_isi_video_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_video *video = video_drvdata(file); + + f->fmt.pix_mp = video->pix; + + return 0; +} + +static int mxc_isi_video_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mxc_isi_video *video = video_drvdata(file); + + mxc_isi_format_try(video->pipe, &f->fmt.pix_mp, MXC_ISI_VIDEO_CAP); + return 0; +} + +static int mxc_isi_video_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_isi_video *video = video_drvdata(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + + if (vb2_is_busy(&video->vb2_q)) + return -EBUSY; + + video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP); + video->pix = *pix; + + return 0; +} + +static int mxc_isi_video_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_video *video = video_drvdata(file); + struct media_device *mdev = &video->pipe->isi->media_dev; + struct media_pipeline *pipe; + int ret; + + if (vb2_queue_is_busy(&video->vb2_q, file)) + return -EBUSY; + + /* + * Get a pipeline for the video node and start it. This must be done + * here and not in the queue .start_streaming() handler, so that + * pipeline start errors can be reported from VIDIOC_STREAMON and not + * delayed until subsequent VIDIOC_QBUF calls. + */ + mutex_lock(&mdev->graph_mutex); + + ret = mxc_isi_pipe_acquire(video->pipe, &mxc_isi_video_frame_write_done); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + return ret; + } + + pipe = media_entity_pipeline(&video->vdev.entity) ? : &video->pipe->pipe; + + ret = __video_device_pipeline_start(&video->vdev, pipe); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + goto err_release; + } + + mutex_unlock(&mdev->graph_mutex); + + /* Verify that the video format matches the output of the subdev. */ + ret = mxc_isi_video_validate_format(video); + if (ret) + goto err_stop; + + /* Allocate buffers for discard operation. */ + ret = mxc_isi_video_alloc_discard_buffers(video); + if (ret) + goto err_stop; + + ret = vb2_streamon(&video->vb2_q, type); + if (ret) + goto err_free; + + video->is_streaming = true; + + return 0; + +err_free: + mxc_isi_video_free_discard_buffers(video); +err_stop: + video_device_pipeline_stop(&video->vdev); +err_release: + mxc_isi_pipe_release(video->pipe); + return ret; +} + +static void mxc_isi_video_cleanup_streaming(struct mxc_isi_video *video) +{ + lockdep_assert_held(&video->lock); + + if (!video->is_streaming) + return; + + mxc_isi_video_free_discard_buffers(video); + video_device_pipeline_stop(&video->vdev); + mxc_isi_pipe_release(video->pipe); + + video->is_streaming = false; +} + +static int mxc_isi_video_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_video *video = video_drvdata(file); + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret) + return ret; + + mxc_isi_video_cleanup_streaming(video); + + return 0; +} + +static int mxc_isi_video_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct mxc_isi_video *video = video_drvdata(file); + const struct mxc_isi_format_info *info; + unsigned int max_width; + unsigned int h_align; + unsigned int v_align; + + if (fsize->index) + return -EINVAL; + + info = mxc_isi_format_by_fourcc(fsize->pixel_format, MXC_ISI_VIDEO_CAP); + if (!info) + return -EINVAL; + + h_align = max_t(unsigned int, info->hsub, 1); + v_align = max_t(unsigned int, info->vsub, 1); + + max_width = video->pipe->id == video->pipe->isi->pdata->num_channels - 1 + ? MXC_ISI_MAX_WIDTH_UNCHAINED + : MXC_ISI_MAX_WIDTH_CHAINED; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = ALIGN(MXC_ISI_MIN_WIDTH, h_align); + fsize->stepwise.min_height = ALIGN(MXC_ISI_MIN_HEIGHT, v_align); + fsize->stepwise.max_width = ALIGN_DOWN(max_width, h_align); + fsize->stepwise.max_height = ALIGN_DOWN(MXC_ISI_MAX_HEIGHT, v_align); + fsize->stepwise.step_width = h_align; + fsize->stepwise.step_height = v_align; + + /* + * The width can be further restricted due to line buffer sharing + * between pipelines when scaling, but we have no way to know here if + * the scaler will be used. + */ + + return 0; +} + +static const struct v4l2_ioctl_ops mxc_isi_video_ioctl_ops = { + .vidioc_querycap = mxc_isi_video_querycap, + + .vidioc_enum_fmt_vid_cap = mxc_isi_video_enum_fmt, + .vidioc_try_fmt_vid_cap_mplane = mxc_isi_video_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = mxc_isi_video_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = mxc_isi_video_g_fmt, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + + .vidioc_streamon = mxc_isi_video_streamon, + .vidioc_streamoff = mxc_isi_video_streamoff, + + .vidioc_enum_framesizes = mxc_isi_video_enum_framesizes, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * Video device file operations + */ + +static int mxc_isi_video_open(struct file *file) +{ + struct mxc_isi_video *video = video_drvdata(file); + int ret; + + ret = v4l2_fh_open(file); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(video->pipe->isi->dev); + if (ret) { + v4l2_fh_release(file); + return ret; + } + + return 0; +} + +static int mxc_isi_video_release(struct file *file) +{ + struct mxc_isi_video *video = video_drvdata(file); + int ret; + + ret = vb2_fop_release(file); + if (ret) + dev_err(video->pipe->isi->dev, "%s fail\n", __func__); + + mutex_lock(&video->lock); + mxc_isi_video_cleanup_streaming(video); + mutex_unlock(&video->lock); + + pm_runtime_put(video->pipe->isi->dev); + return ret; +} + +static const struct v4l2_file_operations mxc_isi_video_fops = { + .owner = THIS_MODULE, + .open = mxc_isi_video_open, + .release = mxc_isi_video_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Suspend & resume + */ + +void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_video *video = &pipe->video; + + if (!video->is_streaming) + return; + + mxc_isi_pipe_disable(pipe); + mxc_isi_channel_put(pipe); + + spin_lock_irq(&video->buf_lock); + + /* + * Move the active buffers back to the pending or discard list. We must + * iterate the active list backward and move the buffers to the head of + * the pending list to preserve the buffer queueing order. + */ + while (!list_empty(&video->out_active)) { + struct mxc_isi_buffer *buf = + list_last_entry(&video->out_active, + struct mxc_isi_buffer, list); + + if (buf->discard) + list_move(&buf->list, &video->out_discard); + else + list_move(&buf->list, &video->out_pending); + } + + spin_unlock_irq(&video->buf_lock); +} + +int mxc_isi_video_resume(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_video *video = &pipe->video; + + if (!video->is_streaming) + return 0; + + mxc_isi_video_init_channel(video); + + spin_lock_irq(&video->buf_lock); + mxc_isi_video_queue_first_buffers(video); + spin_unlock_irq(&video->buf_lock); + + return mxc_isi_pipe_enable(pipe); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +int mxc_isi_video_register(struct mxc_isi_pipe *pipe, + struct v4l2_device *v4l2_dev) +{ + struct mxc_isi_video *video = &pipe->video; + struct v4l2_pix_format_mplane *pix = &video->pix; + struct video_device *vdev = &video->vdev; + struct vb2_queue *q = &video->vb2_q; + int ret = -ENOMEM; + + video->pipe = pipe; + + mutex_init(&video->lock); + spin_lock_init(&video->buf_lock); + + pix->width = MXC_ISI_DEF_WIDTH; + pix->height = MXC_ISI_DEF_HEIGHT; + pix->pixelformat = MXC_ISI_DEF_PIXEL_FORMAT; + pix->colorspace = MXC_ISI_DEF_COLOR_SPACE; + pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC; + pix->quantization = MXC_ISI_DEF_QUANTIZATION; + pix->xfer_func = MXC_ISI_DEF_XFER_FUNC; + video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP); + + memset(vdev, 0, sizeof(*vdev)); + snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", pipe->id); + + vdev->fops = &mxc_isi_video_fops; + vdev->ioctl_ops = &mxc_isi_video_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->queue = q; + vdev->lock = &video->lock; + + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_IO_MC; + video_set_drvdata(vdev, video); + + INIT_LIST_HEAD(&video->out_pending); + INIT_LIST_HEAD(&video->out_active); + INIT_LIST_HEAD(&video->out_discard); + + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = video; + q->ops = &mxc_isi_vb2_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct mxc_isi_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->lock = &video->lock; + q->dev = pipe->isi->dev; + + ret = vb2_queue_init(q); + if (ret) + goto err_free_ctx; + + video->pad.flags = MEDIA_PAD_FL_SINK; + vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + ret = media_entity_pads_init(&vdev->entity, 1, &video->pad); + if (ret) + goto err_free_ctx; + + ret = mxc_isi_video_ctrls_create(video); + if (ret) + goto err_me_cleanup; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_ctrl_free; + + ret = media_create_pad_link(&pipe->sd.entity, + MXC_ISI_PIPE_PAD_SOURCE, + &vdev->entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + goto err_video_unreg; + + return 0; + +err_video_unreg: + video_unregister_device(vdev); +err_ctrl_free: + mxc_isi_video_ctrls_delete(video); +err_me_cleanup: + media_entity_cleanup(&vdev->entity); +err_free_ctx: + return ret; +} + +void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe) +{ + struct mxc_isi_video *video = &pipe->video; + struct video_device *vdev = &video->vdev; + + mutex_lock(&video->lock); + + if (video_is_registered(vdev)) { + video_unregister_device(vdev); + mxc_isi_video_ctrls_delete(video); + media_entity_cleanup(&vdev->entity); + } + + mutex_unlock(&video->lock); +} diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c new file mode 100644 index 0000000000..ed048f73c9 --- /dev/null +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -0,0 +1,965 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP i.MX8MQ SoC series MIPI-CSI2 receiver driver + * + * Copyright (C) 2021 Purism SPC + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interconnect.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +#define MIPI_CSI2_DRIVER_NAME "imx8mq-mipi-csi2" +#define MIPI_CSI2_SUBDEV_NAME MIPI_CSI2_DRIVER_NAME + +#define MIPI_CSI2_PAD_SINK 0 +#define MIPI_CSI2_PAD_SOURCE 1 +#define MIPI_CSI2_PADS_NUM 2 + +#define MIPI_CSI2_DEF_PIX_WIDTH 640 +#define MIPI_CSI2_DEF_PIX_HEIGHT 480 + +/* Register map definition */ + +/* i.MX8MQ CSI-2 controller CSR */ +#define CSI2RX_CFG_NUM_LANES 0x100 +#define CSI2RX_CFG_DISABLE_DATA_LANES 0x104 +#define CSI2RX_BIT_ERR 0x108 +#define CSI2RX_IRQ_STATUS 0x10c +#define CSI2RX_IRQ_MASK 0x110 +#define CSI2RX_IRQ_MASK_ALL 0x1ff +#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE 0x8 +#define CSI2RX_ULPS_STATUS 0x114 +#define CSI2RX_PPI_ERRSOT_HS 0x118 +#define CSI2RX_PPI_ERRSOTSYNC_HS 0x11c +#define CSI2RX_PPI_ERRESC 0x120 +#define CSI2RX_PPI_ERRSYNCESC 0x124 +#define CSI2RX_PPI_ERRCONTROL 0x128 +#define CSI2RX_CFG_DISABLE_PAYLOAD_0 0x12c +#define CSI2RX_CFG_VID_VC_IGNORE 0x180 +#define CSI2RX_CFG_VID_VC 0x184 +#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL 0x188 +#define CSI2RX_CFG_DISABLE_PAYLOAD_1 0x130 + +enum { + ST_POWERED = 1, + ST_STREAMING = 2, + ST_SUSPENDED = 4, +}; + +enum imx8mq_mipi_csi_clk { + CSI2_CLK_CORE, + CSI2_CLK_ESC, + CSI2_CLK_UI, + CSI2_NUM_CLKS, +}; + +static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = { + [CSI2_CLK_CORE] = "core", + [CSI2_CLK_ESC] = "esc", + [CSI2_CLK_UI] = "ui", +}; + +#define CSI2_NUM_CLKS ARRAY_SIZE(imx8mq_mipi_csi_clk_id) + +#define GPR_CSI2_1_RX_ENABLE BIT(13) +#define GPR_CSI2_1_VID_INTFC_ENB BIT(12) +#define GPR_CSI2_1_HSEL BIT(10) +#define GPR_CSI2_1_CONT_CLK_MODE BIT(8) +#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2) + +/* + * The send level configures the number of entries that must accumulate in + * the Pixel FIFO before the data will be transferred to the video output. + * The exact value needed for this configuration is dependent on the rate at + * which the sensor transfers data to the CSI-2 Controller and the user + * video clock. + * + * The calculation is the classical rate-in rate-out type of problem: If the + * video bandwidth is 10% faster than the incoming mipi data and the video + * line length is 500 pixels, then the fifo should be allowed to fill + * 10% of the line length or 50 pixels. If the gap data is ok, then the level + * can be set to 16 and ignored. + */ +#define CSI2RX_SEND_LEVEL 64 + +struct csi_state { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data clks[CSI2_NUM_CLKS]; + struct reset_control *rst; + struct regulator *mipi_phy_regulator; + + struct v4l2_subdev sd; + struct media_pad pads[MIPI_CSI2_PADS_NUM]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + + struct v4l2_mbus_config_mipi_csi2 bus; + + struct mutex lock; /* Protect state */ + u32 state; + + struct regmap *phy_gpr; + u8 phy_gpr_reg; + + struct icc_path *icc_path; + s32 icc_path_bw; +}; + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct csi2_pix_format { + u32 code; + u8 width; +}; + +static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = { + /* RAW (Bayer and greyscale) formats. */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .width = 14, + }, + /* YUV formats */ + { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .width = 16, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .width = 16, + } +}; + +static const struct csi2_pix_format *find_csi2_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++) + if (code == imx8mq_mipi_csi_formats[i].code) + return &imx8mq_mipi_csi_formats[i]; + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val) +{ + writel(val, state->regs + reg); +} + +static int imx8mq_mipi_csi_sw_reset(struct csi_state *state) +{ + int ret; + + /* + * these are most likely self-clearing reset bits. to make it + * more clear, the reset-imx7 driver should implement the + * .reset() operation. + */ + ret = reset_control_assert(state->rst); + if (ret < 0) { + dev_err(state->dev, "Failed to assert resets: %d\n", ret); + return ret; + } + + return 0; +} + +static void imx8mq_mipi_csi_set_params(struct csi_state *state) +{ + int lanes = state->bus.num_data_lanes; + + imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1); + imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, + (0xf << lanes) & 0xf); + imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL); + /* + * 0x180 bit 0 controls the Virtual Channel behaviour: when set the + * interface ignores the Virtual Channel (VC) field in received packets; + * when cleared it causes the interface to only accept packets whose VC + * matches the value to which VC is set at offset 0x184. + */ + imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_VC_IGNORE, 1); + imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL, + CSI2RX_SEND_LEVEL); +} + +static int imx8mq_mipi_csi_clk_enable(struct csi_state *state) +{ + return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks); +} + +static void imx8mq_mipi_csi_clk_disable(struct csi_state *state) +{ + clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks); +} + +static int imx8mq_mipi_csi_clk_get(struct csi_state *state) +{ + unsigned int i; + + for (i = 0; i < CSI2_NUM_CLKS; i++) + state->clks[i].id = imx8mq_mipi_csi_clk_id[i]; + + return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state->clks); +} + +static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state, + struct v4l2_subdev_state *sd_state, + u32 *hs_settle) +{ + s64 link_freq; + u32 lane_rate; + unsigned long esc_clk_rate; + u32 min_ths_settle, max_ths_settle, ths_settle_ns, esc_clk_period_ns; + const struct v4l2_mbus_framefmt *fmt; + const struct csi2_pix_format *csi2_fmt; + + /* Calculate the line rate from the pixel rate. */ + + fmt = v4l2_subdev_get_pad_format(&state->sd, sd_state, MIPI_CSI2_PAD_SINK); + csi2_fmt = find_csi2_format(fmt->code); + + link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler, + csi2_fmt->width, + state->bus.num_data_lanes * 2); + if (link_freq < 0) { + dev_err(state->dev, "Unable to obtain link frequency: %d\n", + (int)link_freq); + return link_freq; + } + + lane_rate = link_freq * 2; + if (lane_rate < 80000000 || lane_rate > 1500000000) { + dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate); + return -EINVAL; + } + + /* + * The D-PHY specification requires Ths-settle to be in the range + * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half + * the clock period. + * + * The Ths-settle value is expressed in the hardware as a multiple of + * the Esc clock period: + * + * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc + * + * Due to the one cycle inaccuracy introduced by rounding, the + * documentation recommends picking a value away from the boundaries. + * Let's pick the average. + */ + esc_clk_rate = clk_get_rate(state->clks[CSI2_CLK_ESC].clk); + if (!esc_clk_rate) { + dev_err(state->dev, "Could not get esc clock rate.\n"); + return -EINVAL; + } + + dev_dbg(state->dev, "esc clk rate: %lu\n", esc_clk_rate); + esc_clk_period_ns = 1000000000 / esc_clk_rate; + + min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000); + max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000); + ths_settle_ns = (min_ths_settle + max_ths_settle) / 2; + + *hs_settle = ths_settle_ns / esc_clk_period_ns - 1; + + dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle %u\n", + lane_rate, ths_settle_ns, *hs_settle); + + return 0; +} + +static int imx8mq_mipi_csi_start_stream(struct csi_state *state, + struct v4l2_subdev_state *sd_state) +{ + int ret; + u32 hs_settle = 0; + + ret = imx8mq_mipi_csi_sw_reset(state); + if (ret) + return ret; + + imx8mq_mipi_csi_set_params(state); + ret = imx8mq_mipi_csi_calc_hs_settle(state, sd_state, &hs_settle); + if (ret) + return ret; + + regmap_update_bits(state->phy_gpr, + state->phy_gpr_reg, + 0x3fff, + GPR_CSI2_1_RX_ENABLE | + GPR_CSI2_1_VID_INTFC_ENB | + GPR_CSI2_1_HSEL | + GPR_CSI2_1_CONT_CLK_MODE | + GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle)); + + return 0; +} + +static void imx8mq_mipi_csi_stop_stream(struct csi_state *state) +{ + imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_state, sd); +} + +static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct v4l2_subdev_state *sd_state; + int ret = 0; + + if (enable) { + ret = pm_runtime_resume_and_get(state->dev); + if (ret < 0) + return ret; + } + + mutex_lock(&state->lock); + + if (enable) { + if (state->state & ST_SUSPENDED) { + ret = -EBUSY; + goto unlock; + } + + sd_state = v4l2_subdev_lock_and_get_active_state(sd); + ret = imx8mq_mipi_csi_start_stream(state, sd_state); + v4l2_subdev_unlock_state(sd_state); + + if (ret < 0) + goto unlock; + + ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1); + if (ret < 0) + goto unlock; + + state->state |= ST_STREAMING; + } else { + v4l2_subdev_call(state->src_sd, video, s_stream, 0); + imx8mq_mipi_csi_stop_stream(state); + state->state &= ~ST_STREAMING; + } + +unlock: + mutex_unlock(&state->lock); + + if (!enable || ret < 0) + pm_runtime_put(state->dev); + + return ret; +} + +static int imx8mq_mipi_csi_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *fmt_sink; + struct v4l2_mbus_framefmt *fmt_source; + + fmt_sink = v4l2_subdev_get_pad_format(sd, sd_state, MIPI_CSI2_PAD_SINK); + fmt_source = v4l2_subdev_get_pad_format(sd, sd_state, MIPI_CSI2_PAD_SOURCE); + + fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10; + fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH; + fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT; + fmt_sink->field = V4L2_FIELD_NONE; + + fmt_sink->colorspace = V4L2_COLORSPACE_RAW; + fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); + fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); + fmt_sink->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, + fmt_sink->ycbcr_enc); + + *fmt_source = *fmt_sink; + + return 0; +} + +static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* + * We can't transcode in any way, the source format is identical + * to the sink format. + */ + if (code->pad == MIPI_CSI2_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_get_pad_format(sd, sd_state, code->pad); + code->code = fmt->code; + return 0; + } + + if (code->pad != MIPI_CSI2_PAD_SINK) + return -EINVAL; + + if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats)) + return -EINVAL; + + code->code = imx8mq_mipi_csi_formats[code->index].code; + + return 0; +} + +static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + const struct csi2_pix_format *csi2_fmt; + struct v4l2_mbus_framefmt *fmt; + + /* + * The device can't transcode in any way, the source format can't be + * modified. + */ + if (sdformat->pad == MIPI_CSI2_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, sdformat); + + if (sdformat->pad != MIPI_CSI2_PAD_SINK) + return -EINVAL; + + csi2_fmt = find_csi2_format(sdformat->format.code); + if (!csi2_fmt) + csi2_fmt = &imx8mq_mipi_csi_formats[0]; + + fmt = v4l2_subdev_get_pad_format(sd, sd_state, sdformat->pad); + + fmt->code = csi2_fmt->code; + fmt->width = sdformat->format.width; + fmt->height = sdformat->format.height; + + sdformat->format = *fmt; + + /* Propagate the format from sink to source. */ + fmt = v4l2_subdev_get_pad_format(sd, sd_state, MIPI_CSI2_PAD_SOURCE); + *fmt = sdformat->format; + + return 0; +} + +static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = { + .s_stream = imx8mq_mipi_csi_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = { + .init_cfg = imx8mq_mipi_csi_init_cfg, + .enum_mbus_code = imx8mq_mipi_csi_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx8mq_mipi_csi_set_fmt, +}; + +static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = { + .video = &imx8mq_mipi_csi_video_ops, + .pad = &imx8mq_mipi_csi_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Async subdev notifier + */ + +static struct csi_state * +mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n) +{ + return container_of(n, struct csi_state, notifier); +} + +static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct csi_state *state = mipi_notifier_to_csi2_state(notifier); + struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK]; + + state->src_sd = sd; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = { + .bound = imx8mq_mipi_csi_notify_bound, +}; + +static int imx8mq_mipi_csi_async_register(struct csi_state *state) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_connection *asd; + struct fwnode_handle *ep; + unsigned int i; + int ret; + + v4l2_async_subdev_nf_init(&state->notifier, &state->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { + dev_err(state->dev, + "data lanes reordering is not supported"); + ret = -EINVAL; + goto err_parse; + } + } + + state->bus = vep.bus.mipi_csi2; + + dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n", + state->bus.num_data_lanes, + state->bus.flags); + + asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_parse; + } + + fwnode_handle_put(ep); + + state->notifier.ops = &imx8mq_mipi_csi_notify_ops; + + ret = v4l2_async_nf_register(&state->notifier); + if (ret) + return ret; + + return v4l2_async_register_subdev(&state->sd); + +err_parse: + fwnode_handle_put(ep); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend/resume + */ + +static void imx8mq_mipi_csi_pm_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + mutex_lock(&state->lock); + + if (state->state & ST_POWERED) { + imx8mq_mipi_csi_stop_stream(state); + imx8mq_mipi_csi_clk_disable(state); + state->state &= ~ST_POWERED; + } + + mutex_unlock(&state->lock); +} + +static int imx8mq_mipi_csi_pm_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct v4l2_subdev_state *sd_state; + int ret = 0; + + mutex_lock(&state->lock); + + if (!(state->state & ST_POWERED)) { + state->state |= ST_POWERED; + ret = imx8mq_mipi_csi_clk_enable(state); + } + if (state->state & ST_STREAMING) { + sd_state = v4l2_subdev_lock_and_get_active_state(sd); + ret = imx8mq_mipi_csi_start_stream(state, sd_state); + v4l2_subdev_unlock_state(sd_state); + if (ret) + goto unlock; + } + + state->state &= ~ST_SUSPENDED; + +unlock: + mutex_unlock(&state->lock); + + return ret ? -EAGAIN : 0; +} + +static int __maybe_unused imx8mq_mipi_csi_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + imx8mq_mipi_csi_pm_suspend(dev); + + state->state |= ST_SUSPENDED; + + return 0; +} + +static int __maybe_unused imx8mq_mipi_csi_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + if (!(state->state & ST_SUSPENDED)) + return 0; + + return imx8mq_mipi_csi_pm_resume(dev); +} + +static int __maybe_unused imx8mq_mipi_csi_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret; + + imx8mq_mipi_csi_pm_suspend(dev); + + ret = icc_set_bw(state->icc_path, 0, 0); + if (ret) + dev_err(dev, "icc_set_bw failed with %d\n", ret); + + return ret; +} + +static int __maybe_unused imx8mq_mipi_csi_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret; + + ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw); + if (ret) { + dev_err(dev, "icc_set_bw failed with %d\n", ret); + return ret; + } + + return imx8mq_mipi_csi_pm_resume(dev); +} + +static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = { + SET_RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend, + imx8mq_mipi_csi_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume) +}; + +/* ----------------------------------------------------------------------------- + * Probe/remove & platform driver + */ + +static int imx8mq_mipi_csi_subdev_init(struct csi_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + int ret; + + v4l2_subdev_init(sd, &imx8mq_mipi_csi_subdev_ops); + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "%s %s", + MIPI_CSI2_SUBDEV_NAME, dev_name(state->dev)); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &imx8mq_mipi_csi_entity_ops; + + sd->dev = state->dev; + + state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM, + state->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; +} + +static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + icc_put(state->icc_path); +} + +static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + /* Optional interconnect request */ + state->icc_path = of_icc_get(&pdev->dev, "dram"); + if (IS_ERR_OR_NULL(state->icc_path)) + return PTR_ERR_OR_ZERO(state->icc_path); + + state->icc_path_bw = MBps_to_icc(700); + + return 0; +} + +static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) +{ + struct device *dev = state->dev; + struct device_node *np = state->dev->of_node; + struct device_node *node; + phandle ph; + u32 out_val[2]; + int ret = 0; + + state->rst = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(state->rst)) { + dev_err(dev, "Failed to get reset: %pe\n", state->rst); + return PTR_ERR(state->rst); + } + + ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val, + ARRAY_SIZE(out_val)); + if (ret) { + dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret); + return ret; + } + + ph = *out_val; + + node = of_find_node_by_phandle(ph); + if (!node) { + dev_err(dev, "Error finding node by phandle\n"); + return -ENODEV; + } + state->phy_gpr = syscon_node_to_regmap(node); + of_node_put(node); + if (IS_ERR(state->phy_gpr)) { + dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr); + return PTR_ERR(state->phy_gpr); + } + + state->phy_gpr_reg = out_val[1]; + dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg); + + return ret; +} + +static int imx8mq_mipi_csi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi_state *state; + int ret; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->dev = dev; + + ret = imx8mq_mipi_csi_parse_dt(state); + if (ret < 0) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + /* Acquire resources. */ + state->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + ret = imx8mq_mipi_csi_clk_get(state); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, &state->sd); + + mutex_init(&state->lock); + + ret = imx8mq_mipi_csi_subdev_init(state); + if (ret < 0) + goto mutex; + + ret = imx8mq_mipi_csi_init_icc(pdev); + if (ret) + goto mutex; + + /* Enable runtime PM. */ + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = imx8mq_mipi_csi_runtime_resume(dev); + if (ret < 0) + goto icc; + } + + ret = imx8mq_mipi_csi_async_register(state); + if (ret < 0) + goto cleanup; + + return 0; + +cleanup: + pm_runtime_disable(&pdev->dev); + imx8mq_mipi_csi_runtime_suspend(&pdev->dev); + + media_entity_cleanup(&state->sd.entity); + v4l2_subdev_cleanup(&state->sd); + v4l2_async_nf_unregister(&state->notifier); + v4l2_async_nf_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); +icc: + imx8mq_mipi_csi_release_icc(pdev); +mutex: + mutex_destroy(&state->lock); + + return ret; +} + +static void imx8mq_mipi_csi_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + v4l2_async_nf_unregister(&state->notifier); + v4l2_async_nf_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); + + pm_runtime_disable(&pdev->dev); + imx8mq_mipi_csi_runtime_suspend(&pdev->dev); + media_entity_cleanup(&state->sd.entity); + v4l2_subdev_cleanup(&state->sd); + mutex_destroy(&state->lock); + pm_runtime_set_suspended(&pdev->dev); + imx8mq_mipi_csi_release_icc(pdev); +} + +static const struct of_device_id imx8mq_mipi_csi_of_match[] = { + { .compatible = "fsl,imx8mq-mipi-csi2", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match); + +static struct platform_driver imx8mq_mipi_csi_driver = { + .probe = imx8mq_mipi_csi_probe, + .remove_new = imx8mq_mipi_csi_remove, + .driver = { + .of_match_table = imx8mq_mipi_csi_of_match, + .name = MIPI_CSI2_DRIVER_NAME, + .pm = &imx8mq_mipi_csi_pm_ops, + }, +}; + +module_platform_driver(imx8mq_mipi_csi_driver); + +MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver"); +MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@puri.sm>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx8mq-mipi-csi2"); diff --git a/drivers/media/platform/nxp/mx2_emmaprp.c b/drivers/media/platform/nxp/mx2_emmaprp.c new file mode 100644 index 0000000000..023ed40c6b --- /dev/null +++ b/drivers/media/platform/nxp/mx2_emmaprp.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support eMMa-PrP through mem2mem framework. + * + * eMMa-PrP is a piece of HW that allows fetching buffers + * from one memory location and do several operations on + * them such as scaling or format conversion giving, as a result + * a new processed buffer in another memory location. + * + * Based on mem2mem_testdev.c by Pawel Osciak. + * + * Copyright (c) 2011 Vista Silicon S.L. + * Javier Martin <javier.martin@vista-silicon.com> + */ +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <linux/platform_device.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <linux/sizes.h> + +#define EMMAPRP_MODULE_NAME "mem2mem-emmaprp" + +MODULE_DESCRIPTION("Mem-to-mem device which supports eMMa-PrP present in mx2 SoCs"); +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.0.1"); + +static bool debug; +module_param(debug, bool, 0644); + +#define MIN_W 32 +#define MIN_H 32 +#define MAX_W 2040 +#define MAX_H 2046 + +#define S_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN_YUV420 3 /* multiple of 8 */ +#define W_ALIGN_OTHERS 2 /* multiple of 4 */ +#define H_ALIGN 1 /* multiple of 2 */ + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "m2m-emmaprp" + +/* In bytes, per queue */ +#define MEM2MEM_VID_MEM_LIMIT SZ_16M + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1UL << 31) + +#define PRP_SIZE_HEIGHT(x) (x) +#define PRP_SIZE_WIDTH(x) ((x) << 16) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define PRP_INTR_ST_RDERR (1 << 0) +#define PRP_INTR_ST_CH1WERR (1 << 1) +#define PRP_INTR_ST_CH2WERR (1 << 2) +#define PRP_INTR_ST_CH2B2CI (1 << 3) +#define PRP_INTR_ST_CH2B1CI (1 << 4) +#define PRP_INTR_ST_CH1B2CI (1 << 5) +#define PRP_INTR_ST_CH1B1CI (1 << 6) +#define PRP_INTR_ST_LBOVF (1 << 7) +#define PRP_INTR_ST_CH2OVF (1 << 8) + +struct emmaprp_fmt { + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct emmaprp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_OUTPUT, + }, +}; + +/* Per-queue, driver-specific private data */ +struct emmaprp_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct emmaprp_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct emmaprp_fmt *find_format(struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct emmaprp_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + + void __iomem *base_emma; + struct clk *clk_emma_ahb, *clk_emma_ipg; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct emmaprp_ctx { + struct v4l2_fh fh; + struct emmaprp_dev *dev; + /* Abort requested by m2m */ + int aborting; + struct emmaprp_q_data q_data[2]; +}; + +static struct emmaprp_q_data *get_q_data(struct emmaprp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * mem2mem callbacks + */ +static void emmaprp_job_abort(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->fh.m2m_ctx); +} + +static inline void emmaprp_dump_regs(struct emmaprp_dev *pcdev) +{ + dprintk(pcdev, + "eMMa-PrP Registers:\n" + " SOURCE_Y_PTR = 0x%08X\n" + " SRC_FRAME_SIZE = 0x%08X\n" + " DEST_Y_PTR = 0x%08X\n" + " DEST_CR_PTR = 0x%08X\n" + " DEST_CB_PTR = 0x%08X\n" + " CH2_OUT_IMAGE_SIZE = 0x%08X\n" + " CNTL = 0x%08X\n", + readl(pcdev->base_emma + PRP_SOURCE_Y_PTR), + readl(pcdev->base_emma + PRP_SRC_FRAME_SIZE), + readl(pcdev->base_emma + PRP_DEST_Y_PTR), + readl(pcdev->base_emma + PRP_DEST_CR_PTR), + readl(pcdev->base_emma + PRP_DEST_CB_PTR), + readl(pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE), + readl(pcdev->base_emma + PRP_CNTL)); +} + +static void emmaprp_device_run(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_q_data *s_q_data, *d_q_data; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct emmaprp_dev *pcdev = ctx->dev; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size; + dma_addr_t p_in, p_out; + u32 tmp; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + + d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + /* Input frame parameters */ + writel(p_in, pcdev->base_emma + PRP_SOURCE_Y_PTR); + writel(PRP_SIZE_WIDTH(s_width) | PRP_SIZE_HEIGHT(s_height), + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + + /* Output frame parameters */ + writel(p_out, pcdev->base_emma + PRP_DEST_Y_PTR); + writel(p_out + d_size, pcdev->base_emma + PRP_DEST_CB_PTR); + writel(p_out + d_size + (d_size >> 2), + pcdev->base_emma + PRP_DEST_CR_PTR); + writel(PRP_SIZE_WIDTH(d_width) | PRP_SIZE_HEIGHT(d_height), + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + + /* IRQ configuration */ + tmp = readl(pcdev->base_emma + PRP_INTR_CNTL); + writel(tmp | PRP_INTR_RDERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH2FC, + pcdev->base_emma + PRP_INTR_CNTL); + + emmaprp_dump_regs(pcdev); + + /* Enable transfer */ + tmp = readl(pcdev->base_emma + PRP_CNTL); + writel(tmp | PRP_CNTL_CH2_OUT_YUV420 | + PRP_CNTL_DATA_IN_YUV422 | + PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); +} + +static irqreturn_t emmaprp_irq(int irq_emma, void *data) +{ + struct emmaprp_dev *pcdev = data; + struct emmaprp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + u32 irqst; + + /* Check irq flags and clear irq */ + irqst = readl(pcdev->base_emma + PRP_INTRSTATUS); + writel(irqst, pcdev->base_emma + PRP_INTRSTATUS); + dprintk(pcdev, "irqst = 0x%08x\n", irqst); + + curr_ctx = v4l2_m2m_get_curr_priv(pcdev->m2m_dev); + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (!curr_ctx->aborting) { + if ((irqst & PRP_INTR_ST_RDERR) || + (irqst & PRP_INTR_ST_CH2WERR)) { + pr_err("PrP bus error occurred, this transfer is probably corrupted\n"); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + } else if (irqst & PRP_INTR_ST_CH2B1CI) { /* buffer ready */ + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->flags &= + ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags + & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->timecode = src_vb->timecode; + + spin_lock_irqsave(&pcdev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&pcdev->irqlock, flags); + } + } + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct emmaprp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct emmaprp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + else /* YUYV */ + f->fmt.pix.bytesperline = q_data->width * 2; + f->fmt.pix.sizeimage = q_data->sizeimage; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f) +{ + enum v4l2_field field; + + + if (!find_format(f)) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_YUV420, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + } else { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_OTHERS, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_s_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct emmaprp_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(f); + if (ret) + return ret; + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + q_data->sizeimage = q_data->width * q_data->height * 3 / 2; + else /* YUYV */ + q_data->sizeimage = q_data->width * q_data->height * 2; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static const struct v4l2_ioctl_ops emmaprp_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .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_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + + +/* + * Queue operations + */ +static int emmaprp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vq); + struct emmaprp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + size = q_data->width * q_data->height * 3 / 2; + else + size = q_data->width * q_data->height * 2; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int emmaprp_buf_prepare(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct emmaprp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, + "%s data will not fit into plane(%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void emmaprp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops emmaprp_qops = { + .queue_setup = emmaprp_queue_setup, + .buf_prepare = emmaprp_buf_prepare, + .buf_queue = emmaprp_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct emmaprp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &emmaprp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = ctx->dev->v4l2_dev.dev; + src_vq->lock = &ctx->dev->dev_mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &emmaprp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + dst_vq->lock = &ctx->dev->dev_mutex; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int emmaprp_open(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = pcdev; + + if (mutex_lock_interruptible(&pcdev->dev_mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + int ret = PTR_ERR(ctx->fh.m2m_ctx); + + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + return ret; + } + + clk_prepare_enable(pcdev->clk_emma_ipg); + clk_prepare_enable(pcdev->clk_emma_ahb); + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[1]; + ctx->q_data[V4L2_M2M_DST].fmt = &formats[0]; + v4l2_fh_add(&ctx->fh); + mutex_unlock(&pcdev->dev_mutex); + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->fh.m2m_ctx); + + return 0; +} + +static int emmaprp_release(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + mutex_lock(&pcdev->dev_mutex); + clk_disable_unprepare(pcdev->clk_emma_ahb); + clk_disable_unprepare(pcdev->clk_emma_ipg); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations emmaprp_fops = { + .owner = THIS_MODULE, + .open = emmaprp_open, + .release = emmaprp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device emmaprp_videodev = { + .name = MEM2MEM_NAME, + .fops = &emmaprp_fops, + .ioctl_ops = &emmaprp_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = emmaprp_device_run, + .job_abort = emmaprp_job_abort, +}; + +static int emmaprp_probe(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev; + struct video_device *vfd; + int irq, ret; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + pcdev->clk_emma_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(pcdev->clk_emma_ipg)) { + return PTR_ERR(pcdev->clk_emma_ipg); + } + + pcdev->clk_emma_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(pcdev->clk_emma_ahb)) + return PTR_ERR(pcdev->clk_emma_ahb); + + pcdev->base_emma = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pcdev->base_emma)) + return PTR_ERR(pcdev->base_emma); + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + return ret; + + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = emmaprp_videodev; + vfd->lock = &pcdev->dev_mutex; + vfd->v4l2_dev = &pcdev->v4l2_dev; + + video_set_drvdata(vfd, pcdev); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, EMMAPRP_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto rel_vdev; + } + + ret = devm_request_irq(&pdev->dev, irq, emmaprp_irq, 0, + dev_name(&pdev->dev), pcdev); + if (ret) + goto rel_vdev; + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto rel_vdev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + return 0; + + +rel_m2m: + v4l2_m2m_release(pcdev->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); + + mutex_destroy(&pcdev->dev_mutex); + + return ret; +} + +static void emmaprp_remove(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " EMMAPRP_MODULE_NAME); + + video_unregister_device(pcdev->vfd); + v4l2_m2m_release(pcdev->m2m_dev); + v4l2_device_unregister(&pcdev->v4l2_dev); + mutex_destroy(&pcdev->dev_mutex); +} + +static struct platform_driver emmaprp_pdrv = { + .probe = emmaprp_probe, + .remove_new = emmaprp_remove, + .driver = { + .name = MEM2MEM_NAME, + }, +}; +module_platform_driver(emmaprp_pdrv); |