Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,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"
@@ -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
@@ -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.
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0+
obj-$(CONFIG_VIDEO_DW100) += dw100.o
File diff suppressed because it is too large Load Diff
@@ -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_ */
@@ -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.
@@ -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
@@ -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));
}
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver
*
* Copyright 2018-2019 NXP
*/
#include <linux/bitfield.h>
#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))
#define GLB_CTRL_CUR_VERSION(r) FIELD_GET(GENMASK_U32(19, 16), r)
/* 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)
#define SLOT_STATUS_ONGOING (0x1 << 31)
/* 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 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);
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);
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);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,225 @@
/* 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_LINE 0x8000
#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
#define MXC_JPEG_PATTERN_WIDTH 128
#define MXC_JPEG_PATTERN_HEIGHT 64
#define MXC_JPEG_ADDR_ALIGNMENT 16
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;
/* below parameters are valid for v1 */
u32 mode;
u32 cfg_mode;
u32 quality;
u32 rc_regs_sel;
u32 lumth;
u32 chrth;
u32 nomfrsize_lo;
u32 nomfrsize_hi;
u32 ofbsize_lo;
u32 ofbsize_hi;
} __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;
bool extseq;
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
dma_addr_t cfg_dec_size;
void *cfg_dec_vaddr;
dma_addr_t cfg_dec_daddr;
};
struct mxc_jpeg_enc_ops {
/* Manual configuration (v0 hardware) - two-phase process */
void (*enter_config_mode)(struct mxc_jpeg_ctx *ctx);
void (*exit_config_mode)(struct mxc_jpeg_ctx *ctx);
/* Descriptor-based configuration (v1 hardware) - single-phase */
void (*setup_desc)(struct mxc_jpeg_ctx *ctx);
};
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 gen_pool *sram_pool;
const struct mxc_jpeg_enc_ops *enc_cfg_ops;
};
/**
* 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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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.
@@ -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
@@ -0,0 +1,591 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019-2020 NXP
*/
#include <linux/bits.h>
#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.MX8MN ISI IER version */
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = {
.oflw_y_buf_en = { .mask = BIT(19) },
.oflw_u_buf_en = { .mask = BIT(21) },
.oflw_v_buf_en = { .mask = BIT(23) },
.panic_y_buf_en = { .mask = BIT(20) },
.panic_u_buf_en = { .mask = BIT(22) },
.panic_v_buf_en = { .mask = BIT(24) },
};
/* For i.MX8QXP C0 and i.MX8MP ISI IER version */
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = {
.oflw_y_buf_en = { .mask = BIT(18) },
.oflw_u_buf_en = { .mask = BIT(20) },
.oflw_v_buf_en = { .mask = BIT(22) },
.panic_y_buf_en = { .mask = BIT(19) },
.panic_u_buf_en = { .mask = BIT(21) },
.panic_v_buf_en = { .mask = BIT(23) },
};
/* For i.MX8QM ISI IER version */
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_qm = {
.oflw_y_buf_en = { .mask = BIT(16) },
.oflw_u_buf_en = { .mask = BIT(19) },
.oflw_v_buf_en = { .mask = BIT(22) },
.excs_oflw_y_buf_en = { .mask = BIT(17) },
.excs_oflw_u_buf_en = { .mask = BIT(20) },
.excs_oflw_v_buf_en = { .mask = BIT(23) },
.panic_y_buf_en = { .mask = BIT(18) },
.panic_u_buf_en = { .mask = BIT(21) },
.panic_v_buf_en = { .mask = BIT(24) },
};
/* 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 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,
.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,
.buf_active_reverse = true,
.gasket_ops = &mxc_imx8_gasket_ops,
.has_36bit_dma = true,
};
static const struct mxc_isi_plat_data mxc_imx95_data = {
.model = MXC_ISI_IMX95,
.num_ports = 4,
.num_channels = 8,
.reg_offset = 0x10000,
.ier_reg = &mxc_imx8_isi_ier_v2,
.set_thd = &mxc_imx8_isi_thd_v1,
.buf_active_reverse = true,
.has_36bit_dma = true,
};
static const struct mxc_isi_plat_data mxc_imx8qm_data = {
.model = MXC_ISI_IMX8QM,
.num_ports = 5,
.num_channels = 8,
.reg_offset = 0x10000,
.ier_reg = &mxc_imx8_isi_ier_qm,
.set_thd = &mxc_imx8_isi_thd_v1,
.buf_active_reverse = true,
.has_36bit_dma = false,
};
static const struct mxc_isi_plat_data mxc_imx8qxp_data = {
.model = MXC_ISI_IMX8QXP,
.num_ports = 5,
.num_channels = 6,
.reg_offset = 0x10000,
.ier_reg = &mxc_imx8_isi_ier_v2,
.set_thd = &mxc_imx8_isi_thd_v1,
.buf_active_reverse = true,
.has_36bit_dma = false,
};
static const struct mxc_isi_plat_data mxc_imx8ulp_data = {
.model = MXC_ISI_IMX8ULP,
.num_ports = 1,
.num_channels = 1,
.reg_offset = 0x0,
.ier_reg = &mxc_imx8_isi_ier_v2,
.set_thd = &mxc_imx8_isi_thd_v1,
.buf_active_reverse = true,
.has_36bit_dma = false,
};
static const struct mxc_isi_plat_data mxc_imx91_data = {
.model = MXC_ISI_IMX91,
.num_ports = 1,
.num_channels = 1,
.reg_offset = 0,
.ier_reg = &mxc_imx8_isi_ier_v2,
.set_thd = &mxc_imx8_isi_thd_v1,
.buf_active_reverse = true,
.has_36bit_dma = false,
};
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,
.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);
}
mxc_isi_m2m_suspend(&isi->m2m);
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;
}
}
ret = mxc_isi_m2m_resume(&isi->m2m);
if (ret) {
dev_err(dev, "Failed to resume ISI (%d) for m2m\n", ret);
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->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->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_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 = kzalloc_objs(isi->pipes[0], isi->pdata->num_channels);
if (!isi->pipes)
return -ENOMEM;
isi->num_clks = devm_clk_bulk_get_all(dev, &isi->clks);
if (isi->num_clks < 0)
return dev_err_probe(dev, isi->num_clks, "Failed to get clocks\n");
isi->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(isi->regs))
return dev_err_probe(dev, PTR_ERR(isi->regs),
"Failed to get ISI register map\n");
if (isi->pdata->gasket_ops) {
isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node,
"fsl,blk-ctrl");
if (IS_ERR(isi->gasket))
return dev_err_probe(dev, PTR_ERR(isi->gasket),
"failed to get gasket\n");
}
dma_size = isi->pdata->has_36bit_dma ? 36 : 32;
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size));
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
ret = mxc_isi_crossbar_init(isi);
if (ret)
return dev_err_probe(dev, ret,
"Failed to initialize crossbar\n");
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:
while (i--)
mxc_isi_pipe_cleanup(&isi->pipes[i]);
mxc_isi_crossbar_cleanup(&isi->crossbar);
return ret;
}
static void 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_v4l2_cleanup(isi);
mxc_isi_crossbar_cleanup(&isi->crossbar);
}
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,imx8qm-isi", .data = &mxc_imx8qm_data },
{ .compatible = "fsl,imx8qxp-isi", .data = &mxc_imx8qxp_data },
{ .compatible = "fsl,imx8ulp-isi", .data = &mxc_imx8ulp_data },
{ .compatible = "fsl,imx91-isi", .data = &mxc_imx91_data },
{ .compatible = "fsl,imx93-isi", .data = &mxc_imx93_data },
{ .compatible = "fsl,imx95-isi", .data = &mxc_imx95_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_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,433 @@
/* 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/math.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 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_IMX8QM,
MXC_ISI_IMX8QXP,
MXC_ISI_IMX8ULP,
MXC_ISI_IMX91,
MXC_ISI_IMX93,
MXC_ISI_IMX95,
};
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;
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 the vdev and vb2_q operations */
struct mutex lock;
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;
int num_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);
void mxc_isi_m2m_suspend(struct mxc_isi_m2m *m2m);
int mxc_isi_m2m_resume(struct mxc_isi_m2m *m2m);
#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;
}
static inline void mxc_isi_m2m_suspend(struct mxc_isi_m2m *m2m)
{
}
static inline int mxc_isi_m2m_resume(struct mxc_isi_m2m *m2m)
{
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);
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
/*
* ISI scaling engine works in two parts: it performs pre-decimation of
* the image followed by bilinear filtering to achieve the desired
* downscaling factor.
*
* The decimation filter provides a maximum downscaling factor of 8, and
* the subsequent bilinear filter provides a maximum downscaling factor
* of 2. Combined, the maximum scaling factor can be up to 16.
*/
static inline unsigned int
mxc_isi_clamp_downscale_16(unsigned int val, unsigned int max_val)
{
return clamp(val, max(1U, DIV_ROUND_UP(max_val, 16)), max_val);
}
#endif /* __MXC_ISI_CORE_H__ */
@@ -0,0 +1,507 @@
// 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_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 -ENXIO;
}
}
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_state(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.
*/
routing.num_routes = min(xbar->num_sinks - 1, xbar->num_sources);
routes = kzalloc_objs(*routes, routing.num_routes);
if (!routes)
return -ENOMEM;
for (i = 0; i < routing.num_routes; ++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.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_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_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 enable streams 0x%llx on '%s':%u: %d\n",
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 disable streams 0x%llx on '%s':%u: %d\n",
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 = {
.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 v4l2_subdev_internal_ops mxc_isi_crossbar_internal_ops = {
.init_state = mxc_isi_crossbar_init_state,
};
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->internal_ops = &mxc_isi_crossbar_internal_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_channels;
num_pads = xbar->num_sinks + xbar->num_sources;
xbar->pads = kzalloc_objs(*xbar->pads, num_pads);
if (!xbar->pads)
return -ENOMEM;
xbar->inputs = kzalloc_objs(*xbar->inputs, xbar->num_sinks);
if (!xbar->inputs) {
ret = -ENOMEM;
goto err_free;
}
for (i = 0; i < xbar->num_sinks; ++i)
xbar->pads[i].flags = MEDIA_PAD_FL_SINK
| MEDIA_PAD_FL_MUST_CONNECT;
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)
{
v4l2_subdev_cleanup(&xbar->sd);
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)
{
}
@@ -0,0 +1,130 @@
// 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 }
struct debug_regs {
u32 offset;
const char * const name;
};
static const struct debug_regs 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),
};
/* These registers contain the upper 4 bits of 36-bit DMA addresses. */
static const struct debug_regs registers_36bit_dma[] = {
MXC_ISI_DEBUG_REG(CHNL_Y_BUF1_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_U_BUF1_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_V_BUF1_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_Y_BUF2_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_U_BUF2_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_V_BUF2_XTND_ADDR),
MXC_ISI_DEBUG_REG(CHNL_IN_BUF_XTND_ADDR),
};
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, "%21s[0x%02x]: 0x%08x\n",
registers[i].name, registers[i].offset,
mxc_isi_read(pipe, registers[i].offset));
if (pipe->isi->pdata->has_36bit_dma) {
for (i = 0; i < ARRAY_SIZE(registers_36bit_dma); ++i) {
const struct debug_regs *reg = &registers_36bit_dma[i];
seq_printf(m, "%21s[0x%02x]: 0x%08x\n",
reg->name, reg->offset,
mxc_isi_read(pipe, reg->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);
}
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019-2023 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#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) FIELD_PREP(GENMASK(13, 8), dt)
#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) FIELD_PREP(GENMASK(8, 3), x)
#define DISP_MIX_CAMERA_MUX_GASKET_ENABLE BIT(16)
#define DISP_MIX_CAMERA_MUX_GASKET_SOURCE_TYPE BIT(17)
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;
/*
* CAMERA MUX
* - [17]: Selects source input to gasket
* 0: Data from MIPI CSI
* 1: Data from parallel camera
*/
if (fd->type == V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL)
val |= DISP_MIX_CAMERA_MUX_GASKET_SOURCE_TYPE;
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,
};
@@ -0,0 +1,654 @@
// 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;
/*
* The ISI rounds output dimensions up to the next integer (i.MX93 RM
* section 57.7.8). Calculate the scale factor such that the theoretical
* output (input / scale_factor) rounds up to exactly the desired
* output.
*/
return min_t(u32, DIV_ROUND_UP(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 = NULL;
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 */
val |= CHNL_IMG_CTRL_CSC_BYPASS;
}
dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n",
encodings[in_encoding], encodings[out_encoding]);
if (coeffs) {
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 = !coeffs;
}
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_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);
/* 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)
{
/* 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);
}
@@ -0,0 +1,861 @@
// 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 *file_to_isi_m2m_ctx(struct file *filp)
{
return container_of(file_to_v4l2_fh(filp), 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);
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_prepare_streaming(struct vb2_queue *q)
{
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
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;
int ret;
guard(mutex)(&m2m->lock);
if (m2m->usage_count == INT_MAX)
return -EOVERFLOW;
ret = pm_runtime_resume_and_get(m2m->isi->dev);
if (ret)
return ret;
/*
* Acquire the pipe and initialize the channel with the first user of
* the M2M device.
*/
if (m2m->usage_count == 0) {
bool bypass = cap_pix->width == out_pix->width &&
cap_pix->height == out_pix->height &&
cap_info->encoding == out_info->encoding;
ret = mxc_isi_channel_acquire(m2m->pipe,
&mxc_isi_m2m_frame_write_done,
bypass);
if (ret)
goto err_pm;
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);
if (ret)
goto err_deinit;
m2m->chained_count++;
ctx->chained = true;
}
return 0;
err_deinit:
if (--m2m->usage_count == 0) {
mxc_isi_channel_put(m2m->pipe);
mxc_isi_channel_release(m2m->pipe);
}
err_pm:
pm_runtime_put(m2m->isi->dev);
return ret;
}
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 void mxc_isi_m2m_vb2_unprepare_streaming(struct vb2_queue *q)
{
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
struct mxc_isi_m2m *m2m = ctx->m2m;
guard(mutex)(&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);
pm_runtime_put(m2m->isi->dev);
}
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,
.prepare_streaming = mxc_isi_m2m_vb2_prepare_streaming,
.start_streaming = mxc_isi_m2m_vb2_start_streaming,
.stop_streaming = mxc_isi_m2m_vb2_stop_streaming,
.unprepare_streaming = mxc_isi_m2m_vb2_unprepare_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) {
const struct v4l2_pix_format_mplane *format =
&ctx->queues.out.format;
/* Downscaling only, by up to 16. */
pix->width = mxc_isi_clamp_downscale_16(pix->width,
format->width);
pix->height = mxc_isi_clamp_downscale_16(pix->height,
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 = file_to_isi_m2m_ctx(file);
__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 = file_to_isi_m2m_ctx(file);
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 = file_to_isi_m2m_ctx(file);
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 (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 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 = v4l2_m2m_ioctl_streamon,
.vidioc_streamoff = v4l2_m2m_ioctl_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_obj(*ctx);
if (!ctx)
return -ENOMEM;
ctx->m2m = m2m;
mutex_init(&ctx->vb2_lock);
v4l2_fh_init(&ctx->fh, vdev);
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;
v4l2_fh_add(&ctx->fh, file);
return 0;
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_ctx *ctx = file_to_isi_m2m_ctx(file);
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
mxc_isi_m2m_ctx_ctrls_delete(ctx);
v4l2_fh_del(&ctx->fh, file);
v4l2_fh_exit(&ctx->fh);
mutex_destroy(&ctx->vb2_lock);
kfree(ctx);
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,
};
/* -----------------------------------------------------------------------------
* Suspend & resume
*/
void mxc_isi_m2m_suspend(struct mxc_isi_m2m *m2m)
{
if (m2m->usage_count == 0)
return;
v4l2_m2m_suspend(m2m->m2m_dev);
if (m2m->chained_count > 0)
mxc_isi_channel_unchain(m2m->pipe);
mxc_isi_channel_disable(m2m->pipe);
mxc_isi_channel_put(m2m->pipe);
}
int mxc_isi_m2m_resume(struct mxc_isi_m2m *m2m)
{
if (m2m->usage_count == 0)
return 0;
mxc_isi_channel_get(m2m->pipe);
if (m2m->chained_count > 0)
mxc_isi_channel_chain(m2m->pipe);
m2m->last_ctx = NULL;
v4l2_m2m_resume(m2m->m2m_dev);
return 0;
}
/* -----------------------------------------------------------------------------
* 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;
}
@@ -0,0 +1,876 @@
// 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_state_get_format(state, MXC_ISI_PIPE_PAD_SINK);
src_fmt = v4l2_subdev_state_get_format(state, MXC_ISI_PIPE_PAD_SOURCE);
compose = v4l2_subdev_state_get_compose(state, MXC_ISI_PIPE_PAD_SINK);
crop = *v4l2_subdev_state_get_crop(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_state_get_format(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_state_get_crop(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_state_get_compose(state, pad);
}
static int mxc_isi_pipe_init_state(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 ISI supports downscaling only, with a factor up to 16.
* Clamp the compose rectangle size accordingly.
*/
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 = mxc_isi_clamp_downscale_16(sel->r.width,
format->width);
sel->r.height = mxc_isi_clamp_downscale_16(sel->r.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 = {
.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,
};
static const struct v4l2_subdev_internal_ops mxc_isi_pipe_internal_ops = {
.init_state = mxc_isi_pipe_init_state,
};
/* -----------------------------------------------------------------------------
* 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->internal_ops = &mxc_isi_pipe_internal_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_subdev;
}
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_subdev;
}
return 0;
error_subdev:
v4l2_subdev_cleanup(sd);
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;
v4l2_subdev_cleanup(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_state_get_format(state, MXC_ISI_PIPE_PAD_SINK);
src_fmt = v4l2_subdev_state_get_format(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);
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);
}
@@ -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__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,905 @@
// 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 inline struct emmaprp_ctx *file_to_emmaprp_ctx(struct file *filp)
{
return container_of(file_to_v4l2_fh(filp), struct emmaprp_ctx, fh);
}
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 emmaprp_q_data *q_data;
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(file_to_emmaprp_ctx(file), f);
}
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
return vidioc_g_fmt(file_to_emmaprp_ctx(file), 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_ctx *ctx = file_to_emmaprp_ctx(file);
struct emmaprp_fmt *fmt;
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_ctx *ctx = file_to_emmaprp_ctx(file);
struct emmaprp_fmt *fmt;
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);
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(file_to_emmaprp_ctx(file), 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(file_to_emmaprp_ctx(file), 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,
};
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_obj(*ctx);
if (!ctx)
return -ENOMEM;
v4l2_fh_init(&ctx->fh, video_devdata(file));
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, file);
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_to_emmaprp_ctx(file);
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, file);
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 = emmaprp_remove,
.driver = {
.name = MEM2MEM_NAME,
},
};
module_platform_driver(emmaprp_pdrv);