From 4e4e3fe650246c9ab7c7a5ef56379c20924d1fef Mon Sep 17 00:00:00 2001 From: Alex Bee Date: Tue, 16 Feb 2021 15:36:58 +0100 Subject: [PATCH] Rockchip: add IEP driver I wrote the driver some month ago and have been testing it on everday basis since then (by just using it). I will have to add some more functionality before upstreaming it, since the IP supports way more then just deinterlacing - however: thats the only use-case for kodi now. --- .../RK3288/linux/default/linux.arm.conf | 1 + .../RK3328/linux/default/linux.aarch64.conf | 1 + .../RK3399/linux/default/linux.aarch64.conf | 1 + .../linux-2002-v4l-wip-iep-driver.patch | 1798 +++++++++++++++++ 4 files changed, 1801 insertions(+) create mode 100644 projects/Rockchip/patches/linux/default/linux-2002-v4l-wip-iep-driver.patch diff --git a/projects/Rockchip/devices/RK3288/linux/default/linux.arm.conf b/projects/Rockchip/devices/RK3288/linux/default/linux.arm.conf index 8f5746419d..1bebcccdc8 100644 --- a/projects/Rockchip/devices/RK3288/linux/default/linux.arm.conf +++ b/projects/Rockchip/devices/RK3288/linux/default/linux.arm.conf @@ -2929,6 +2929,7 @@ CONFIG_SMS_SIANO_RC=y # CONFIG_V4L_PLATFORM_DRIVERS is not set CONFIG_V4L_MEM2MEM_DRIVERS=y # CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set +CONFIG_VIDEO_ROCKCHIP_IEP=m CONFIG_VIDEO_ROCKCHIP_RGA=m # CONFIG_DVB_PLATFORM_DRIVERS is not set diff --git a/projects/Rockchip/devices/RK3328/linux/default/linux.aarch64.conf b/projects/Rockchip/devices/RK3328/linux/default/linux.aarch64.conf index 2fa064f441..bc764d6f7f 100644 --- a/projects/Rockchip/devices/RK3328/linux/default/linux.aarch64.conf +++ b/projects/Rockchip/devices/RK3328/linux/default/linux.aarch64.conf @@ -3019,6 +3019,7 @@ CONFIG_SMS_SIANO_RC=y # CONFIG_V4L_PLATFORM_DRIVERS is not set CONFIG_V4L_MEM2MEM_DRIVERS=y # CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set +CONFIG_VIDEO_ROCKCHIP_IEP=m CONFIG_VIDEO_ROCKCHIP_RGA=m # CONFIG_DVB_PLATFORM_DRIVERS is not set diff --git a/projects/Rockchip/devices/RK3399/linux/default/linux.aarch64.conf b/projects/Rockchip/devices/RK3399/linux/default/linux.aarch64.conf index 8f0ec8aed0..a5966422ec 100644 --- a/projects/Rockchip/devices/RK3399/linux/default/linux.aarch64.conf +++ b/projects/Rockchip/devices/RK3399/linux/default/linux.aarch64.conf @@ -3499,6 +3499,7 @@ CONFIG_SMS_SIANO_RC=y # CONFIG_V4L_PLATFORM_DRIVERS is not set CONFIG_V4L_MEM2MEM_DRIVERS=y # CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set +CONFIG_VIDEO_ROCKCHIP_IEP=m CONFIG_VIDEO_ROCKCHIP_RGA=m # CONFIG_DVB_PLATFORM_DRIVERS is not set diff --git a/projects/Rockchip/patches/linux/default/linux-2002-v4l-wip-iep-driver.patch b/projects/Rockchip/patches/linux/default/linux-2002-v4l-wip-iep-driver.patch new file mode 100644 index 0000000000..ea86031d48 --- /dev/null +++ b/projects/Rockchip/patches/linux/default/linux-2002-v4l-wip-iep-driver.patch @@ -0,0 +1,1798 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Sun, 11 Oct 2020 17:03:12 +0200 +Subject: [PATCH] dt-bindings: media: Add Rockchip IEP binding + +Signed-off-by: Alex Bee +--- + .../bindings/media/rockchip-iep.yaml | 73 +++++++++++++++++++ + 1 file changed, 73 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/rockchip-iep.yaml + +diff --git a/Documentation/devicetree/bindings/media/rockchip-iep.yaml b/Documentation/devicetree/bindings/media/rockchip-iep.yaml +new file mode 100644 +index 000000000000..a9efcda13fc1 +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/rockchip-iep.yaml +@@ -0,0 +1,73 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/rockchip-iep.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Rockchip Image Enhancement Processor (IEP) ++ ++description: ++ Rockchip IEP supports various image enhancement operations for YUV and RGB domains. ++ Deinterlacing, spatial and temporal sampling noise reduction are supported by the ++ YUV block. Gamma adjustment, edge enhancement, detail enhancement are supported in ++ the RGB block. Brightness, Saturation, Contrast, Hue adjustment is supported for ++ both domains. Furthermore it supports converting RGB to YUV / YUV to RGB. ++ ++maintainers: ++ - Heiko Stuebner ++ ++properties: ++ compatible: ++ oneOf: ++ - const: rockchip,rk3228-iep ++ - items: ++ - enum: ++ - rockchip,rk3288-iep ++ - rockchip,rk3328-iep ++ - rockchip,rk3368-iep ++ - rockchip,rk3399-iep ++ - const: rockchip,rk3228-iep ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ clocks: ++ maxItems: 2 ++ ++ clock-names: ++ items: ++ - const: axi ++ - const: ahb ++ ++ power-domains: ++ maxItems: 1 ++ ++ iommus: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ iep: iep@20070000 { ++ compatible = "rockchip,rk3228-iep"; ++ reg = <0x20070000 0x800>; ++ interrupts = ; ++ clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; ++ clock-names = "axi", "ahb"; ++ iommus = <&iep_mmu>; ++ power-domains = <&power RK3228_PD_VIO>; ++ }; + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Sun, 11 Oct 2020 21:24:10 +0200 +Subject: [PATCH] media: rockchip: Add Rockchip IEP driver + +Signed-off-by: Alex Bee +--- + drivers/media/platform/Kconfig | 14 + + drivers/media/platform/Makefile | 1 + + drivers/media/platform/rockchip/iep/Makefile | 5 + + .../media/platform/rockchip/iep/iep-regs.h | 291 +++++ + drivers/media/platform/rockchip/iep/iep.c | 1089 +++++++++++++++++ + drivers/media/platform/rockchip/iep/iep.h | 112 ++ + 6 files changed, 1512 insertions(+) + create mode 100644 drivers/media/platform/rockchip/iep/Makefile + create mode 100644 drivers/media/platform/rockchip/iep/iep-regs.h + create mode 100644 drivers/media/platform/rockchip/iep/iep.c + create mode 100644 drivers/media/platform/rockchip/iep/iep.h + +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index 7e152bbb4fa6..eee78b20c791 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -462,6 +462,20 @@ config VIDEO_RENESAS_VSP1 + To compile this driver as a module, choose M here: the module + will be called vsp1. + ++config VIDEO_ROCKCHIP_IEP ++ tristate "Rockchip Image Enhancement Processor" ++ depends on VIDEO_DEV && VIDEO_V4L2 ++ depends on ARCH_ROCKCHIP || COMPILE_TEST ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_MEM2MEM_DEV ++ help ++ This is a v4l2 driver for Rockchip Image Enhancement Processor (IEP) ++ found in most Rockchip RK3xxx SoCs. ++ Rockchip IEP supports various enhancement operations for RGB and YUV ++ images. The driver currently implements YUV deinterlacing only. ++ To compile this driver as a module, choose M here: the module ++ will be called rockchip-iep ++ + config VIDEO_ROCKCHIP_RGA + tristate "Rockchip Raster 2d Graphic Acceleration Unit" + depends on VIDEO_DEV && VIDEO_V4L2 +diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile +index 62b6cdc8c730..f99a873818d5 100644 +--- a/drivers/media/platform/Makefile ++++ b/drivers/media/platform/Makefile +@@ -52,6 +52,7 @@ obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o + obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o + obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ + ++obj-$(CONFIG_VIDEO_ROCKCHIP_IEP) += rockchip/iep/ + obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/ + + obj-y += omap/ +diff --git a/drivers/media/platform/rockchip/iep/Makefile b/drivers/media/platform/rockchip/iep/Makefile +new file mode 100644 +index 000000000000..5c89b3277469 +--- /dev/null ++++ b/drivers/media/platform/rockchip/iep/Makefile +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++rockchip-iep-objs := iep.o ++ ++obj-$(CONFIG_VIDEO_ROCKCHIP_IEP) += rockchip-iep.o +diff --git a/drivers/media/platform/rockchip/iep/iep-regs.h b/drivers/media/platform/rockchip/iep/iep-regs.h +new file mode 100644 +index 000000000000..a68685ef3604 +--- /dev/null ++++ b/drivers/media/platform/rockchip/iep/iep-regs.h +@@ -0,0 +1,291 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Rockchip Image Enhancement Processor (IEP) driver ++ * ++ * Copyright (C) 2020 Alex Bee ++ * ++ */ ++ ++#ifndef __IEP_REGS_H__ ++#define __IEP_REGS_H__ ++ ++/* IEP Registers addresses */ ++#define IEP_CONFIG0 0x000 /* Configuration register0 */ ++#define IEP_VOP_DIRECT_PATH BIT(0) ++#define IEP_DEIN_HIGH_FREQ_SHFT 1 ++#define IEP_DEIN_HIGH_FREQ_MASK (0x7f << IEP_DEIN_HIGH_FREQ_SHFT) ++#define IEP_DEIN_MODE_SHFT 8 ++#define IEP_DEIN_MODE_MASK (7 << IEP_DEIN_MODE_SHFT) ++#define IEP_DEIN_HIGH_FREQ_EN BIT(11) ++#define IEP_DEIN_EDGE_INTPOL_EN BIT(12) ++#define IEP_YUV_DENOISE_EN BIT(13) ++#define IEP_YUV_ENHNC_EN BIT(14) ++#define IEP_DEIN_EDGE_INTPOL_SMTH_EN BIT(15) ++#define IEP_RGB_CLR_ENHNC_EN BIT(16) ++#define IEP_RGB_CNTRST_ENHNC_EN BIT(17) ++#define IEP_RGB_ENHNC_MODE_BYPASS (0 << 18) ++#define IEP_RGB_ENHNC_MODE_DNS BIT(18) ++#define IEP_RGB_ENHNC_MODE_DTL (2 << 18) ++#define IEP_RGB_ENHNC_MODE_EDG (3 << 18) ++#define IEP_RGB_ENHNC_MODE_MASK (3 << 18) ++#define IEP_RGB_CNTRST_ENHNC_DDE_FRST BIT(20) ++#define IEP_DEIN_EDGE_INTPOL_RADIUS_SHFT 21 ++#define IEP_DEIN_EDGE_INTPOL_RADIUS_MASK (3 << IEP_DEIN_EDGE_INTPOL_RADIUS_SHFT) ++#define IEP_DEIN_EDGE_INTPOL_SELECT BIT(23) ++ ++#define IEP_CONFIG1 0x004 /* Configuration register1 */ ++#define IEP_SRC_FMT_SHFT 0 ++#define IEP_SRC_FMT_MASK (3 << IEP_SRC_FMT_SHFT) ++#define IEP_SRC_RGB_SWP_SHFT 2 ++#define IEP_SRC_RGB_SWP_MASK (2 << IEP_SRC_RGB_SWP_SHFT) ++#define IEP_SRC_YUV_SWP_SHFT 4 ++#define IEP_SRC_YUV_SWP_MASK (3 << IEP_SRC_YUV_SWP_SHFT) ++#define IEP_DST_FMT_SHFT 8 ++#define IEP_DST_FMT_MASK (3 << IEP_DST_FMT_SHFT) ++#define IEP_DST_RGB_SWP_SHFT 10 ++#define IEP_DST_RGB_SWP_MASK (2 << IEP_DST_RGB_SWP_SHFT) ++#define IEP_DST_YUV_SWP_SHFT 12 ++#define IEP_DST_YUV_SWP_MASK (3 << IEP_DST_YUV_SWP_SHFT) ++#define IEP_DTH_UP_EN BIT(14) ++#define IEP_DTH_DWN_EN BIT(15) ++#define IEP_YUV2RGB_COE_BT601_1 (0 << 16) ++#define IEP_YUV2RGB_COE_BT601_F BIT(16) ++#define IEP_YUV2RGB_COE_BT709_1 (2 << 16) ++#define IEP_YUV2RGB_COE_BT709_F (3 << 16) ++#define IEP_YUV2RGB_COE_MASK (3 << 16) ++#define IEP_RGB2YUV_COE_BT601_1 (0 << 18) ++#define IEP_RGB2YUV_COE_BT601_F BIT(18) ++#define IEP_RGB2YUV_COE_BT709_1 (2 << 18) ++#define IEP_RGB2YUV_COE_BT709_F (3 << 18) ++#define IEP_RGB2YUV_COE_MASK (3 << 18) ++#define IEP_YUV2RGB_EN BIT(20) ++#define IEP_RGB2YUV_EN BIT(21) ++#define IEP_YUV2RGB_CLIP_EN BIT(22) ++#define IEP_RGB2YUV_CLIP_EN BIT(23) ++#define IEP_GLB_ALPHA_SHFT 24 ++#define IEP_GLB_ALPHA_MASK (0x7f << IEP_GLB_ALPHA_SHFT) ++ ++#define IEP_STATUS 0x008 /* Status register */ ++#define IEP_STATUS_YUV_DNS BIT(0) ++#define IEP_STATUS_SCL BIT(1) ++#define IEP_STATUS_DIL BIT(2) ++#define IEP_STATUS_DDE BIT(3) ++#define IEP_STATUS_DMA_WR_YUV BIT(4) ++#define IEP_STATUS_DMA_RE_YUV BIT(5) ++#define IEP_STATUS_DMA_WR_RGB BIT(6) ++#define IEP_STATUS_DMA_RE_RGB BIT(7) ++#define IEP_STATUS_VOP_DIRECT_PATH BIT(8) ++#define IEP_STATUS_DMA_IA_WR_YUV BIT(16) ++#define IEP_STATUS_DMA_IA_RE_YUV BIT(17) ++#define IEP_STATUS_DMA_IA_WR_RGB BIT(18) ++#define IEP_STATUS_DMA_IA_RE_RGB BIT(19) ++ ++#define IEP_INT 0x00c /* Interrupt register*/ ++#define IEP_INT_FRAME_DONE BIT(0) /* Frame process done interrupt */ ++#define IEP_INT_FRAME_DONE_EN BIT(8) /* Frame process done interrupt enable */ ++#define IEP_INT_FRAME_DONE_CLR BIT(16) /* Frame process done interrupt clear */ ++ ++#define IEP_FRM_START 0x010 /* Frame start */ ++#define IEP_SRST 0x014 /* Soft reset */ ++#define IEP_CONFIG_DONE 0x018 /* Configuration done */ ++#define IEP_FRM_CNT 0x01c /* Frame counter */ ++ ++#define IEP_VIR_IMG_WIDTH 0x020 /* Image virtual width */ ++#define IEP_IMG_SCL_FCT 0x024 /* Scaling factor */ ++#define IEP_SRC_IMG_SIZE 0x028 /* src image width/height */ ++#define IEP_DST_IMG_SIZE 0x02c /* dst image width/height */ ++#define IEP_DST_IMG_WIDTH_TILE0 0x030 /* dst image tile0 width */ ++#define IEP_DST_IMG_WIDTH_TILE1 0x034 /* dst image tile1 width */ ++#define IEP_DST_IMG_WIDTH_TILE2 0x038 /* dst image tile2 width */ ++#define IEP_DST_IMG_WIDTH_TILE3 0x03c /* dst image tile3 width */ ++ ++#define IEP_ENH_YUV_CNFG_0 0x040 /* Brightness, contrast, saturation adjustment */ ++#define IEP_YUV_BRIGHTNESS_SHFT 0 ++#define IEP_YUV_BRIGHTNESS_MASK (0x3f << IEP_YUV_BRIGHTNESS_SHFT) ++#define IEP_YUV_CONTRAST_SHFT 8 ++#define IEP_YUV_CONTRAST_MASK (0xff << IEP_YUV_CONTRAST_SHFT) ++#define IEP_YUV_SATURATION_SHFT 16 ++#define IEP_YUV_SATURATION_MASK (0x1ff << IEP_YUV_SATURATION_SHFT) ++ ++#define IEP_ENH_YUV_CNFG_1 0x044 /* Hue configuration */ ++#define IEP_YUV_COS_HUE_SHFT 0 ++#define IEP_YUV_COS_HUE_MASK (0xff << IEP_YUV_COS_HUE_SHFT) ++#define IEP_YUV_SIN_HUE_SHFT 8 ++#define IEP_YUV_SIN_HUE_MASK (0xff << IEP_YUV_SIN_HUE_SHFT) ++ ++#define IEP_ENH_YUV_CNFG_2 0x048 /* Color bar configuration */ ++#define IEP_YUV_COLOR_BAR_Y_SHFT 0 ++#define IEP_YUV_COLOR_BAR_Y_MASK (0xff << IEP_YUV_COLOR_BAR_Y_SHFT) ++#define IEP_YUV_COLOR_BAR_U_SHFT 8 ++#define IEP_YUV_COLOR_BAR_U_MASK (0xff << IEP_YUV_COLOR_BAR_U_SHFT) ++#define IEP_YUV_COLOR_BAR_V_SHFT 16 ++#define IEP_YUV_COLOR_BAR_V_MASK (0xff << IEP_YUV_COLOR_BAR_V_SHFT) ++#define IEP_YUV_VIDEO_MODE_SHFT 24 ++#define IEP_YUV_VIDEO_MODE_MASK (3 << IEP_YUV_VIDEO_MODE_SHFT) ++ ++#define IEP_ENH_RGB_CNFG 0x04c /* RGB enhancement configuration */ ++#define IEP_ENH_RGB_C_COE 0x050 /* RGB color enhancement coefficient */ ++ ++#define IEP_RAW_CONFIG0 0x058 /* Raw configuration register0 */ ++#define IEP_RAW_CONFIG1 0x05c /* Raw configuration register1 */ ++#define IEP_RAW_VIR_IMG_WIDTH 0x060 /* Raw image virtual width */ ++#define IEP_RAW_IMG_SCL_FCT 0x064 /* Raw scaling factor */ ++#define IEP_RAW_SRC_IMG_SIZE 0x068 /* Raw src image width/height */ ++#define IEP_RAW_DST_IMG_SIZE 0x06c /* Raw src image width/height */ ++#define IEP_RAW_ENH_YUV_CNFG_0 0x070 /* Raw brightness,contrast,saturation adjustment */ ++#define IEP_RAW_ENH_YUV_CNFG_1 0x074 /* Raw hue configuration */ ++#define IEP_RAW_ENH_YUV_CNFG_2 0x078 /* Raw color bar configuration */ ++#define IEP_RAW_ENH_RGB_CNFG 0x07c /* Raw RGB enhancement configuration */ ++ ++#define IEP_SRC_ADDR_Y_RGB 0x080 /* Start addr. of src image 0 (Y/RGB) */ ++#define IEP_SRC_ADDR_CBCR 0x084 /* Start addr. of src image 0 (Cb/Cr) */ ++#define IEP_SRC_ADDR_CR 0x088 /* Start addr. of src image 0 (Cr) */ ++#define IEP_SRC_ADDR_Y1 0x08c /* Start addr. of src image 1 (Y) */ ++#define IEP_SRC_ADDR_CBCR1 0x090 /* Start addr. of src image 1 (Cb/Cr) */ ++#define IEP_SRC_ADDR_CR1 0x094 /* Start addr. of src image 1 (Cr) */ ++#define IEP_SRC_ADDR_Y_ITEMP 0x098 /* Start addr. of src image(Y int part) */ ++#define IEP_SRC_ADDR_CBCR_ITEMP 0x09c /* Start addr. of src image(CBCR int part) */ ++#define IEP_SRC_ADDR_CR_ITEMP 0x0a0 /* Start addr. of src image(CR int part) */ ++#define IEP_SRC_ADDR_Y_FTEMP 0x0a4 /* Start addr. of src image(Y frac part) */ ++#define IEP_SRC_ADDR_CBCR_FTEMP 0x0a8 /* Start addr. of src image(CBCR frac part) */ ++#define IEP_SRC_ADDR_CR_FTEMP 0x0ac /* Start addr. of src image(CR frac part) */ ++ ++#define IEP_DST_ADDR_Y_RGB 0x0b0 /* Start addr. of dst image 0 (Y/RGB) */ ++#define IEP_DST_ADDR_CBCR 0x0b4 /* Start addr. of dst image 0 (Cb/Cr) */ ++#define IEP_DST_ADDR_CR 0x0b8 /* Start addr. of dst image 0 (Cr) */ ++#define IEP_DST_ADDR_Y1 0x0bc /* Start addr. of dst image 1 (Y) */ ++#define IEP_DST_ADDR_CBCR1 0x0c0 /* Start addr. of dst image 1 (Cb/Cr) */ ++#define IEP_DST_ADDR_CR1 0x0c4 /* Start addr. of dst image 1 (Cr) */ ++#define IEP_DST_ADDR_Y_ITEMP 0x0c8 /* Start addr. of dst image(Y int part) */ ++#define IEP_DST_ADDR_CBCR_ITEMP 0x0cc /* Start addr. of dst image(CBCR int part)*/ ++#define IEP_DST_ADDR_CR_ITEMP 0x0d0 /* Start addr. of dst image(CR int part) */ ++#define IEP_DST_ADDR_Y_FTEMP 0x0d4 /* Start addr. of dst image(Y frac part) */ ++#define IEP_DST_ADDR_CBCR_FTEMP 0x0d8 /* Start addr. of dst image(CBCR frac part) */ ++#define IEP_DST_ADDR_CR_FTEMP 0x0dc /* Start addr. of dst image(CR frac part)*/ ++ ++#define IEP_DEIN_MTN_TAB0 0x0e0 /* Deinterlace motion table0 */ ++#define IEP_DEIN_MTN_TAB1 0x0e4 /* Deinterlace motion table1 */ ++#define IEP_DEIN_MTN_TAB2 0x0e8 /* Deinterlace motion table2 */ ++#define IEP_DEIN_MTN_TAB3 0x0ec /* Deinterlace motion table3 */ ++#define IEP_DEIN_MTN_TAB4 0x0f0 /* Deinterlace motion table4 */ ++#define IEP_DEIN_MTN_TAB5 0x0f4 /* Deinterlace motion table5 */ ++#define IEP_DEIN_MTN_TAB6 0x0f8 /* Deinterlace motion table6 */ ++#define IEP_DEIN_MTN_TAB7 0x0fc /* Deinterlace motion table7 */ ++ ++#define IEP_ENH_CG_TAB 0x100 /* Contrast and gamma enhancement table */ ++#define IEP_ENH_DDE_COE0 0x400 /* Denoise,detail and edge enhancement coefficient */ ++#define IEP_ENH_DDE_COE1 0x500 /* Denoise,detail and edge enhancement coefficient1 */ ++ ++#define IEP_INT_MASK (IEP_INT_FRAME_DONE) ++ ++/* IEP colorformats */ ++#define IEP_COLOR_FMT_XRGB 0U ++#define IEP_COLOR_FMT_RGB565 1U ++#define IEP_COLOR_FMT_YUV422 2U ++#define IEP_COLOR_FMT_YUV420 3U ++ ++/* IEP YUV color swaps */ ++#define IEP_YUV_SWP_SP_UV 0U ++#define IEP_YUV_SWP_SP_VU 1U ++#define IEP_YUV_SWP_P 2U ++ ++/* IEP XRGB color swaps */ ++#define XRGB_SWP_XRGB 0U ++#define XRGB_SWP_XBGR 1U ++#define XRGB_SWP_BGRX 2U ++ ++/* IEP RGB565 color swaps */ ++#define RGB565_SWP_RGB 0U ++#define RGB565_SWP_BGR 1U ++ ++#define FMT_IS_YUV(fmt) (fmt == IEP_COLOR_FMT_XRGB || fmt == IEP_COLOR_FMT_RGB565 ? 0 : 1) ++ ++#define IEP_IMG_SIZE(w, h) (((w - 1) & 0x1fff) << 0 | \ ++ ((h - 1) & 0x1fff) << 16) ++ ++#define IEP_VIR_WIDTH(src_w, dst_w) (((src_w / 4) & 0x1fff) << 0 | \ ++ ((dst_w / 4) & 0x1fff) << 16) ++ ++#define IEP_Y_STRIDE(w, h) (w * h) ++#define IEP_UV_STRIDE(w, h, fac) (w * h + w * h / fac) ++ ++#define IEP_SRC_FMT_SWP_MASK(f) (FMT_IS_YUV(f) ? IEP_SRC_YUV_SWP_MASK : IEP_SRC_RGB_SWP_MASK) ++#define IEP_DST_FMT_SWP_MASK(f) (FMT_IS_YUV(f) ? IEP_DST_YUV_SWP_MASK : IEP_DST_RGB_SWP_MASK) ++ ++#define IEP_SRC_FMT(f, swp) (f << IEP_SRC_FMT_SHFT | \ ++ (swp << (FMT_IS_YUV(f) ? IEP_SRC_YUV_SWP_SHFT : IEP_SRC_RGB_SWP_SHFT))) ++#define IEP_DST_FMT(f, swp) (f << IEP_DST_FMT_SHFT | \ ++ (swp << (FMT_IS_YUV(f) ? IEP_DST_YUV_SWP_SHFT : IEP_DST_RGB_SWP_SHFT))) ++ ++/* IEP DEINTERLACE MODES */ ++#define IEP_DEIN_MODE_YUV 0U ++#define IEP_DEIN_MODE_I4O2 1U ++#define IEP_DEIN_MODE_I4O1B 2U ++#define IEP_DEIN_MODE_I4O1T 3U ++#define IEP_DEIN_MODE_I2O1B 4U ++#define IEP_DEIN_MODE_I2O1T 5U ++#define IEP_DEIN_MODE_BYPASS 6U ++ ++#define IEP_DEIN_IN_FIELDS_2 2U ++#define IEP_DEIN_IN_FIELDS_4 4U ++ ++#define IEP_DEIN_OUT_FRAMES_1 1U ++#define IEP_DEIN_OUT_FRAMES_2 2U ++ ++/* values taken from BSP driver */ ++static const u32 default_dein_motion_tbl[][2] = { ++ { IEP_DEIN_MTN_TAB0, 0x40404040 }, ++ { IEP_DEIN_MTN_TAB1, 0x3c3e3f3f }, ++ { IEP_DEIN_MTN_TAB2, 0x3336393b }, ++ { IEP_DEIN_MTN_TAB3, 0x272a2d31 }, ++ { IEP_DEIN_MTN_TAB4, 0x181c2023 }, ++ { IEP_DEIN_MTN_TAB5, 0x0c0e1215 }, ++ { IEP_DEIN_MTN_TAB6, 0x03040609 }, ++ { IEP_DEIN_MTN_TAB7, 0x00000001 }, ++ ++}; ++ ++#define IEP_DEIN_IN_IMG0_Y(bff) (bff ? IEP_SRC_ADDR_Y_RGB : IEP_SRC_ADDR_Y1) ++#define IEP_DEIN_IN_IMG0_CBCR(bff) (bff ? IEP_SRC_ADDR_CBCR : IEP_SRC_ADDR_CBCR1) ++#define IEP_DEIN_IN_IMG0_CR(bff) (bff ? IEP_SRC_ADDR_CR : IEP_SRC_ADDR_CR1) ++#define IEP_DEIN_IN_IMG1_Y(bff) (IEP_DEIN_IN_IMG0_Y(!bff)) ++#define IEP_DEIN_IN_IMG1_CBCR(bff) (IEP_DEIN_IN_IMG0_CBCR(!bff)) ++#define IEP_DEIN_IN_IMG1_CR(bff) (IEP_DEIN_IN_IMG0_CR(!bff)) ++ ++#define IEP_DEIN_OUT_IMG0_Y(bff) (bff ? IEP_DST_ADDR_Y1 : IEP_DST_ADDR_Y_RGB) ++#define IEP_DEIN_OUT_IMG0_CBCR(bff) (bff ? IEP_DST_ADDR_CBCR1 : IEP_DST_ADDR_CBCR) ++#define IEP_DEIN_OUT_IMG0_CR(bff) (bff ? IEP_DST_ADDR_CR1 : IEP_DST_ADDR_CR) ++#define IEP_DEIN_OUT_IMG1_Y(bff) (IEP_DEIN_OUT_IMG0_Y(!bff)) ++#define IEP_DEIN_OUT_IMG1_CBCR(bff) (IEP_DEIN_OUT_IMG0_CBCR(!bff)) ++#define IEP_DEIN_OUT_IMG1_CR(bff) (IEP_DEIN_OUT_IMG0_CR(!bff)) ++ ++#define IEP_DEIN_MODE(m) (m << IEP_DEIN_MODE_SHFT) ++ ++#define IEP_DEIN_IN_MODE_FIELDS(m) ((m == IEP_DEIN_MODE_I4O1T || m == IEP_DEIN_MODE_I4O1B \ ++ || m == IEP_DEIN_MODE_I4O2) \ ++ ? IEP_DEIN_IN_FIELDS_4 : IEP_DEIN_IN_FIELDS_2) ++ ++#define IEP_DEIN_OUT_MODE_FRAMES(m) (m == IEP_DEIN_MODE_I4O2 \ ++ ? IEP_DEIN_OUT_FRAMES_2 : IEP_DEIN_OUT_FRAMES_1) ++ ++#define IEP_DEIN_OUT_MODE_1FRM_TOP_FIELD(m) (m == IEP_DEIN_MODE_I4O1T || IEP_DEIN_MODE_I2O1T \ ++ ? 1 : 0) ++ ++#define IEP_DEIN_EDGE_INTPOL_RADIUS(r) (r << IEP_DEIN_EDGE_INTPOL_RADIUS_SHFT) ++ ++#define IEP_DEIN_HIGH_FREQ(f) (f << IEP_DEIN_HIGH_FREQ_SHFT) ++ ++/* YUV Enhance video modes */ ++#define VIDEO_MODE_BLACK_SCREEN 0U ++#define VIDEO_MODE_BLUE_SCREEN 1U ++#define VIDEO_MODE_COLOR_BARS 2U ++#define VIDEO_MODE_NORMAL_VIDEO 3U ++ ++#define YUV_VIDEO_MODE(m) ((m << IEP_YUV_VIDEO_MODE_SHFT) & IEP_YUV_VIDEO_MODE_MASK) ++#define YUV_BRIGHTNESS(v) ((v << IEP_YUV_BRIGHTNESS_SHFT) & IEP_YUV_BRIGHTNESS_MASK) ++#define YUV_CONTRAST(v) ((v << IEP_YUV_CONTRAST_SHFT) & IEP_YUV_CONTRAST_MASK) ++#define YUV_SATURATION(v) ((v << IEP_YUV_SATURATION_SHFT) & IEP_YUV_SATURATION_MASK) ++#define YUV_COS_HUE(v) ((v << IEP_YUV_COS_HUE_SHFT) & IEP_YUV_COS_HUE_MASK) ++#define YUV_SIN_HUE(v) ((v << IEP_YUV_SIN_HUE_SHFT) & IEP_YUV_SIN_HUE_MASK) ++ ++#endif +diff --git a/drivers/media/platform/rockchip/iep/iep.c b/drivers/media/platform/rockchip/iep/iep.c +new file mode 100644 +index 000000000000..f4b9320733be +--- /dev/null ++++ b/drivers/media/platform/rockchip/iep/iep.c +@@ -0,0 +1,1089 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Rockchip Image Enhancement Processor (IEP) driver ++ * ++ * Copyright (C) 2020 Alex Bee ++ * ++ * Based on Allwinner sun8i deinterlacer with scaler driver ++ * Copyright (C) 2019 Jernej Skrabec ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "iep-regs.h" ++#include "iep.h" ++ ++static struct iep_fmt formats[] = { ++ { ++ .fourcc = V4L2_PIX_FMT_NV12, ++ .color_swap = IEP_YUV_SWP_SP_UV, ++ .hw_format = IEP_COLOR_FMT_YUV420, ++ .depth = 12, ++ .uv_factor = 4, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV21, ++ .color_swap = IEP_YUV_SWP_SP_VU, ++ .hw_format = IEP_COLOR_FMT_YUV420, ++ .depth = 12, ++ .uv_factor = 4, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV16, ++ .color_swap = IEP_YUV_SWP_SP_UV, ++ .hw_format = IEP_COLOR_FMT_YUV422, ++ .depth = 16, ++ .uv_factor = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV61, ++ .color_swap = IEP_YUV_SWP_SP_VU, ++ .hw_format = IEP_COLOR_FMT_YUV422, ++ .depth = 16, ++ .uv_factor = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_YUV420, ++ .color_swap = IEP_YUV_SWP_P, ++ .hw_format = IEP_COLOR_FMT_YUV420, ++ .depth = 12, ++ .uv_factor = 4, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_YUV422P, ++ .color_swap = IEP_YUV_SWP_P, ++ .hw_format = IEP_COLOR_FMT_YUV422, ++ .depth = 16, ++ .uv_factor = 2, ++ }, ++}; ++ ++static struct iep_fmt *iep_fmt_find(struct v4l2_pix_format *pix_fmt) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(formats); i++) { ++ if (formats[i].fourcc == pix_fmt->pixelformat) ++ return &formats[i]; ++ } ++ ++ return NULL; ++} ++ ++static bool iep_check_pix_format(u32 pixelformat) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(formats); i++) ++ if (formats[i].fourcc == pixelformat) ++ return true; ++ ++ return false; ++} ++ ++static struct vb2_v4l2_buffer *iep_m2m_next_dst_buf(struct iep_ctx *ctx) ++{ ++ struct vb2_v4l2_buffer *dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); ++ ++ /* application has set a dst sequence: take it as start point */ ++ if (ctx->dst_sequence == 0 && dst_buf->sequence > 0) ++ ctx->dst_sequence = dst_buf->sequence; ++ ++ dst_buf->sequence = ctx->dst_sequence++; ++ ++ return dst_buf; ++} ++ ++static void iep_m2m_dst_bufs_done(struct iep_ctx *ctx, enum vb2_buffer_state state) ++{ ++ if (ctx->dst0_buf) { ++ v4l2_m2m_buf_done(ctx->dst0_buf, state); ++ ctx->dst_buffs_done++; ++ ctx->dst0_buf = NULL; ++ } ++ ++ if (ctx->dst1_buf) { ++ v4l2_m2m_buf_done(ctx->dst1_buf, state); ++ ctx->dst_buffs_done++; ++ ctx->dst1_buf = NULL; ++ } ++} ++ ++static void iep_setup_formats(struct iep_ctx *ctx) ++{ ++ /* setup src dimensions */ ++ iep_write(ctx->iep, IEP_SRC_IMG_SIZE, ++ IEP_IMG_SIZE(ctx->src_fmt.pix.width, ctx->src_fmt.pix.height)); ++ ++ /* setup dst dimensions */ ++ iep_write(ctx->iep, IEP_DST_IMG_SIZE, ++ IEP_IMG_SIZE(ctx->dst_fmt.pix.width, ctx->dst_fmt.pix.height)); ++ ++ /* setup virtual width */ ++ iep_write(ctx->iep, IEP_VIR_IMG_WIDTH, ++ IEP_VIR_WIDTH(ctx->src_fmt.pix.width, ctx->dst_fmt.pix.width)); ++ ++ /* setup src format */ ++ iep_shadow_mod(ctx->iep, IEP_CONFIG1, IEP_RAW_CONFIG1, ++ IEP_SRC_FMT_MASK | IEP_SRC_FMT_SWP_MASK(ctx->src_fmt.hw_fmt->hw_format), ++ IEP_SRC_FMT(ctx->src_fmt.hw_fmt->hw_format, ++ ctx->src_fmt.hw_fmt->color_swap)); ++ /* setup dst format */ ++ iep_shadow_mod(ctx->iep, IEP_CONFIG1, IEP_RAW_CONFIG1, ++ IEP_DST_FMT_MASK | IEP_DST_FMT_SWP_MASK(ctx->dst_fmt.hw_fmt->hw_format), ++ IEP_DST_FMT(ctx->dst_fmt.hw_fmt->hw_format, ++ ctx->dst_fmt.hw_fmt->color_swap)); ++ ++ ctx->fmt_changed = false; ++} ++ ++static void iep_dein_init(struct rockchip_iep *iep) ++{ ++ unsigned int i; ++ ++ /* values taken from BSP driver */ ++ iep_shadow_mod(iep, IEP_CONFIG0, IEP_RAW_CONFIG0, ++ (IEP_DEIN_EDGE_INTPOL_SMTH_EN | ++ IEP_DEIN_EDGE_INTPOL_RADIUS_MASK | ++ IEP_DEIN_HIGH_FREQ_EN | ++ IEP_DEIN_HIGH_FREQ_MASK), ++ (IEP_DEIN_EDGE_INTPOL_SMTH_EN | ++ IEP_DEIN_EDGE_INTPOL_RADIUS(3) | ++ IEP_DEIN_HIGH_FREQ_EN | ++ IEP_DEIN_HIGH_FREQ(64))); ++ ++ for (i = 0; i < ARRAY_SIZE(default_dein_motion_tbl); i++) ++ iep_write(iep, default_dein_motion_tbl[i][0], ++ default_dein_motion_tbl[i][1]); ++} ++ ++static void iep_init(struct rockchip_iep *iep) ++{ ++ iep_write(iep, IEP_CONFIG0, ++ IEP_DEIN_MODE(IEP_DEIN_MODE_BYPASS) // | ++ //IEP_YUV_ENHNC_EN ++ ); ++ ++ /* TODO: B/S/C/H works ++ * only in 1-frame-out modes ++ iep_write(iep, IEP_ENH_YUV_CNFG_0, ++ YUV_BRIGHTNESS(0) | ++ YUV_CONTRAST(128) | ++ YUV_SATURATION(128)); ++ ++ iep_write(iep, IEP_ENH_YUV_CNFG_1, ++ YUV_COS_HUE(255) | ++ YUV_SIN_HUE(255)); ++ ++ iep_write(iep, IEP_ENH_YUV_CNFG_2, ++ YUV_VIDEO_MODE(VIDEO_MODE_NORMAL_VIDEO)); ++ ++ */ ++ ++ /* reset frame counter */ ++ iep_write(iep, IEP_FRM_CNT, 0); ++} ++ ++static void iep_device_run(void *priv) ++{ ++ struct iep_ctx *ctx = priv; ++ struct rockchip_iep *iep = ctx->iep; ++ struct vb2_v4l2_buffer *src, *dst; ++ unsigned int dein_mode; ++ dma_addr_t addr; ++ ++ if (ctx->fmt_changed) ++ iep_setup_formats(ctx); ++ ++ if (ctx->prev_src_buf) ++ dein_mode = IEP_DEIN_MODE_I4O2; ++ else ++ dein_mode = ctx->field_bff ? IEP_DEIN_MODE_I2O1B : IEP_DEIN_MODE_I2O1T; ++ ++ iep_shadow_mod(iep, IEP_CONFIG0, IEP_RAW_CONFIG0, ++ IEP_DEIN_MODE_MASK, IEP_DEIN_MODE(dein_mode)); ++ ++ /* sync RAW_xxx registers with actual used */ ++ iep_write(iep, IEP_CONFIG_DONE, 1); ++ ++ /* setup src buff(s)/addresses */ ++ src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); ++ addr = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG0_Y(ctx->field_bff), addr); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG0_CBCR(ctx->field_bff), ++ addr + ctx->src_fmt.y_stride); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG0_CR(ctx->field_bff), ++ addr + ctx->src_fmt.uv_stride); ++ ++ if (IEP_DEIN_IN_MODE_FIELDS(dein_mode) == IEP_DEIN_IN_FIELDS_4) ++ addr = vb2_dma_contig_plane_dma_addr(&ctx->prev_src_buf->vb2_buf, 0); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG1_Y(ctx->field_bff), addr); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG1_CBCR(ctx->field_bff), ++ addr + ctx->src_fmt.y_stride); ++ ++ iep_write(iep, IEP_DEIN_IN_IMG1_CR(ctx->field_bff), ++ addr + ctx->src_fmt.uv_stride); ++ ++ /* setup dst buff(s)/addresses */ ++ dst = iep_m2m_next_dst_buf(ctx); ++ addr = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0); ++ ++ if (IEP_DEIN_OUT_MODE_FRAMES(dein_mode) == IEP_DEIN_OUT_FRAMES_2) { ++ v4l2_m2m_buf_copy_metadata(ctx->prev_src_buf, dst, true); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG0_Y(ctx->field_bff), addr); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG0_CBCR(ctx->field_bff), ++ addr + ctx->dst_fmt.y_stride); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG0_CR(ctx->field_bff), ++ addr + ctx->dst_fmt.uv_stride); ++ ++ ctx->dst0_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); ++ ++ dst = iep_m2m_next_dst_buf(ctx); ++ addr = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0); ++ } ++ ++ v4l2_m2m_buf_copy_metadata(src, dst, true); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG1_Y(ctx->field_bff), addr); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG1_CBCR(ctx->field_bff), ++ addr + ctx->dst_fmt.y_stride); ++ ++ iep_write(iep, IEP_DEIN_OUT_IMG1_CR(ctx->field_bff), ++ addr + ctx->dst_fmt.uv_stride); ++ ++ ctx->dst1_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); ++ ++ iep_mod(ctx->iep, IEP_INT, IEP_INT_FRAME_DONE_EN, ++ IEP_INT_FRAME_DONE_EN); ++ ++ /* start HW */ ++ iep_write(iep, IEP_FRM_START, 1); ++} ++ ++static int iep_job_ready(void *priv) ++{ ++ struct iep_ctx *ctx = priv; ++ ++ return v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) >= 2 && ++ v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) >= 1; ++} ++ ++static void iep_job_abort(void *priv) ++{ ++ struct iep_ctx *ctx = priv; ++ ++ /* Will cancel the transaction in the next interrupt handler */ ++ ctx->job_abort = true; ++} ++ ++static const struct v4l2_m2m_ops iep_m2m_ops = { ++ .device_run = iep_device_run, ++ .job_ready = iep_job_ready, ++ .job_abort = iep_job_abort, ++}; ++ ++static int iep_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, ++ unsigned int *nplanes, unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct iep_ctx *ctx = vb2_get_drv_priv(vq); ++ struct v4l2_pix_format *pix_fmt; ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) ++ pix_fmt = &ctx->src_fmt.pix; ++ else ++ pix_fmt = &ctx->dst_fmt.pix; ++ ++ if (*nplanes) { ++ if (sizes[0] < pix_fmt->sizeimage) ++ return -EINVAL; ++ } else { ++ sizes[0] = pix_fmt->sizeimage; ++ *nplanes = 1; ++ } ++ ++ return 0; ++} ++ ++static int iep_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct vb2_queue *vq = vb->vb2_queue; ++ struct iep_ctx *ctx = vb2_get_drv_priv(vq); ++ struct v4l2_pix_format *pix_fmt; ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) ++ pix_fmt = &ctx->src_fmt.pix; ++ else ++ pix_fmt = &ctx->dst_fmt.pix; ++ ++ if (vb2_plane_size(vb, 0) < pix_fmt->sizeimage) ++ return -EINVAL; ++ ++ /* set bytesused for capture buffers */ ++ if (!V4L2_TYPE_IS_OUTPUT(vq->type)) ++ vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); ++ ++ return 0; ++} ++ ++static void iep_buf_queue(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct iep_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ ++ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); ++} ++ ++static void iep_queue_cleanup(struct vb2_queue *vq, u32 state) ++{ ++ struct iep_ctx *ctx = vb2_get_drv_priv(vq); ++ struct vb2_v4l2_buffer *vbuf; ++ ++ do { ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) ++ vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); ++ else ++ vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); ++ ++ if (vbuf) ++ v4l2_m2m_buf_done(vbuf, state); ++ } while (vbuf); ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type) && ctx->prev_src_buf) ++ v4l2_m2m_buf_done(ctx->prev_src_buf, state); ++ else ++ iep_m2m_dst_bufs_done(ctx, state); ++} ++ ++static int iep_start_streaming(struct vb2_queue *vq, unsigned int count) ++{ ++ struct iep_ctx *ctx = vb2_get_drv_priv(vq); ++ struct device *dev = ctx->iep->dev; ++ int ret; ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ ret = pm_runtime_get_sync(dev); ++ if (ret < 0) { ++ dev_err(dev, "Failed to enable module\n"); ++ goto err_runtime_get; ++ } ++ ++ ctx->field_order_bff = ++ ctx->src_fmt.pix.field == V4L2_FIELD_INTERLACED_BT; ++ ctx->field_bff = ctx->field_order_bff; ++ ++ ctx->src_sequence = 0; ++ ctx->dst_sequence = 0; ++ ++ ctx->prev_src_buf = NULL; ++ ++ ctx->dst0_buf = NULL; ++ ctx->dst1_buf = NULL; ++ ctx->dst_buffs_done = 0; ++ ++ ctx->job_abort = false; ++ ++ iep_init(ctx->iep); ++ //if (ctx->src_fmt.pix.field != ctx->dst_fmt.pix.field) ++ iep_dein_init(ctx->iep); ++ } ++ ++ return 0; ++ ++err_runtime_get: ++ iep_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); ++ ++ return ret; ++} ++ ++static void iep_stop_streaming(struct vb2_queue *vq) ++{ ++ struct iep_ctx *ctx = vb2_get_drv_priv(vq); ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ pm_runtime_mark_last_busy(ctx->iep->dev); ++ pm_runtime_put_autosuspend(ctx->iep->dev); ++ } ++ ++ iep_queue_cleanup(vq, VB2_BUF_STATE_ERROR); ++} ++ ++static const struct vb2_ops iep_qops = { ++ .queue_setup = iep_queue_setup, ++ .buf_prepare = iep_buf_prepare, ++ .buf_queue = iep_buf_queue, ++ .start_streaming = iep_start_streaming, ++ .stop_streaming = iep_stop_streaming, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++}; ++ ++static int iep_queue_init(void *priv, struct vb2_queue *src_vq, ++ struct vb2_queue *dst_vq) ++{ ++ struct iep_ctx *ctx = priv; ++ int ret; ++ ++ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ++ src_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES | ++ DMA_ATTR_NO_KERNEL_MAPPING; ++ src_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ src_vq->drv_priv = ctx; ++ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); ++ src_vq->min_buffers_needed = 1; ++ src_vq->ops = &iep_qops; ++ src_vq->mem_ops = &vb2_dma_contig_memops; ++ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ src_vq->lock = &ctx->iep->mutex; ++ src_vq->dev = ctx->iep->v4l2_dev.dev; ++ ++ ret = vb2_queue_init(src_vq); ++ if (ret) ++ return ret; ++ ++ dst_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES | ++ DMA_ATTR_NO_KERNEL_MAPPING; ++ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ dst_vq->drv_priv = ctx; ++ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); ++ dst_vq->min_buffers_needed = 2; ++ dst_vq->ops = &iep_qops; ++ dst_vq->mem_ops = &vb2_dma_contig_memops; ++ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ dst_vq->lock = &ctx->iep->mutex; ++ dst_vq->dev = ctx->iep->v4l2_dev.dev; ++ ++ ret = vb2_queue_init(dst_vq); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void iep_prepare_format(struct v4l2_pix_format *pix_fmt) ++{ ++ unsigned int height = pix_fmt->height; ++ unsigned int width = pix_fmt->width; ++ unsigned int sizeimage, bytesperline; ++ ++ struct iep_fmt *hw_fmt = iep_fmt_find(pix_fmt); ++ ++ if (!hw_fmt) { ++ hw_fmt = &formats[0]; ++ pix_fmt->pixelformat = hw_fmt->fourcc; ++ } ++ ++ width = ALIGN(clamp(width, IEP_MIN_WIDTH, ++ IEP_MAX_WIDTH), 16); ++ height = ALIGN(clamp(height, IEP_MIN_HEIGHT, ++ IEP_MAX_HEIGHT), 16); ++ ++ bytesperline = FMT_IS_YUV(hw_fmt->hw_format) ++ ? width : (width * hw_fmt->depth) >> 3; ++ ++ sizeimage = height * (width * hw_fmt->depth) >> 3; ++ ++ pix_fmt->width = width; ++ pix_fmt->height = height; ++ pix_fmt->bytesperline = bytesperline; ++ pix_fmt->sizeimage = sizeimage; ++} ++ ++static int iep_open(struct file *file) ++{ ++ struct rockchip_iep *iep = video_drvdata(file); ++ struct iep_ctx *ctx = NULL; ++ ++ int ret; ++ ++ if (mutex_lock_interruptible(&iep->mutex)) ++ return -ERESTARTSYS; ++ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) { ++ mutex_unlock(&iep->mutex); ++ return -ENOMEM; ++ } ++ ++ /* default output format */ ++ ctx->src_fmt.pix.pixelformat = formats[0].fourcc; ++ ctx->src_fmt.pix.field = V4L2_FIELD_INTERLACED; ++ ctx->src_fmt.pix.width = IEP_DEFAULT_WIDTH; ++ ctx->src_fmt.pix.height = IEP_DEFAULT_HEIGHT; ++ iep_prepare_format(&ctx->src_fmt.pix); ++ ctx->src_fmt.hw_fmt = &formats[0]; ++ ctx->dst_fmt.y_stride = IEP_Y_STRIDE(ctx->src_fmt.pix.width, ctx->src_fmt.pix.height); ++ ctx->dst_fmt.uv_stride = IEP_UV_STRIDE(ctx->src_fmt.pix.width, ctx->src_fmt.pix.height, ++ ctx->src_fmt.hw_fmt->uv_factor); ++ ++ /* default capture format */ ++ ctx->dst_fmt.pix.pixelformat = formats[0].fourcc; ++ ctx->dst_fmt.pix.field = V4L2_FIELD_NONE; ++ ctx->dst_fmt.pix.width = IEP_DEFAULT_WIDTH; ++ ctx->dst_fmt.pix.height = IEP_DEFAULT_HEIGHT; ++ iep_prepare_format(&ctx->dst_fmt.pix); ++ ctx->dst_fmt.hw_fmt = &formats[0]; ++ ctx->dst_fmt.y_stride = IEP_Y_STRIDE(ctx->dst_fmt.pix.width, ctx->dst_fmt.pix.height); ++ ctx->dst_fmt.uv_stride = IEP_UV_STRIDE(ctx->dst_fmt.pix.width, ctx->dst_fmt.pix.height, ++ ctx->dst_fmt.hw_fmt->uv_factor); ++ /* ensure fmts are written to HW */ ++ ctx->fmt_changed = true; ++ ++ v4l2_fh_init(&ctx->fh, video_devdata(file)); ++ file->private_data = &ctx->fh; ++ ctx->iep = iep; ++ ++ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(iep->m2m_dev, ctx, ++ &iep_queue_init); ++ ++ if (IS_ERR(ctx->fh.m2m_ctx)) { ++ ret = PTR_ERR(ctx->fh.m2m_ctx); ++ goto err_free; ++ } ++ ++ v4l2_fh_add(&ctx->fh); ++ ++ mutex_unlock(&iep->mutex); ++ ++ return 0; ++ ++err_free: ++ kfree(ctx); ++ mutex_unlock(&iep->mutex); ++ ++ return ret; ++} ++ ++static int iep_release(struct file *file) ++{ ++ struct rockchip_iep *iep = video_drvdata(file); ++ struct iep_ctx *ctx = container_of(file->private_data, ++ struct iep_ctx, fh); ++ ++ mutex_lock(&iep->mutex); ++ ++ v4l2_fh_del(&ctx->fh); ++ v4l2_fh_exit(&ctx->fh); ++ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); ++ kfree(ctx); ++ ++ mutex_unlock(&iep->mutex); ++ return 0; ++} ++ ++static const struct v4l2_file_operations iep_fops = { ++ .owner = THIS_MODULE, ++ .open = iep_open, ++ .release = iep_release, ++ .poll = v4l2_m2m_fop_poll, ++ .unlocked_ioctl = video_ioctl2, ++ .mmap = v4l2_m2m_fop_mmap, ++}; ++ ++static int iep_querycap(struct file *file, void *priv, ++ struct v4l2_capability *cap) ++{ ++ strscpy(cap->driver, IEP_NAME, sizeof(cap->driver)); ++ strscpy(cap->card, IEP_NAME, sizeof(cap->card)); ++ snprintf(cap->bus_info, sizeof(cap->bus_info), ++ "platform:%s", IEP_NAME); ++ ++ return 0; ++} ++ ++static int iep_enum_fmt(struct file *file, void *priv, ++ struct v4l2_fmtdesc *f) ++{ ++ struct iep_fmt *fmt; ++ ++ if (f->index < ARRAY_SIZE(formats)) { ++ fmt = &formats[f->index]; ++ f->pixelformat = fmt->fourcc; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int iep_enum_framesizes(struct file *file, void *priv, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ if (fsize->index != 0) ++ return -EINVAL; ++ ++ if (!iep_check_pix_format(fsize->pixel_format)) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; ++ ++ fsize->stepwise.min_width = IEP_MIN_WIDTH; ++ fsize->stepwise.max_width = IEP_MAX_WIDTH; ++ fsize->stepwise.step_width = 16; ++ ++ fsize->stepwise.min_height = IEP_MIN_HEIGHT; ++ fsize->stepwise.max_height = IEP_MAX_HEIGHT; ++ fsize->stepwise.step_height = 16; ++ ++ return 0; ++} ++ ++static inline struct iep_ctx *iep_file2ctx(struct file *file) ++{ ++ return container_of(file->private_data, struct iep_ctx, fh); ++} ++ ++static int iep_g_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct iep_ctx *ctx = iep_file2ctx(file); ++ ++ f->fmt.pix = ctx->dst_fmt.pix; ++ ++ return 0; ++} ++ ++static int iep_g_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct iep_ctx *ctx = iep_file2ctx(file); ++ ++ f->fmt.pix = ctx->src_fmt.pix; ++ ++ return 0; ++} ++ ++static int iep_try_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ f->fmt.pix.field = V4L2_FIELD_NONE; ++ iep_prepare_format(&f->fmt.pix); ++ ++ return 0; ++} ++ ++static int iep_try_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ if (f->fmt.pix.field != V4L2_FIELD_INTERLACED_TB && ++ f->fmt.pix.field != V4L2_FIELD_INTERLACED_BT && ++ f->fmt.pix.field != V4L2_FIELD_INTERLACED) ++ f->fmt.pix.field = V4L2_FIELD_INTERLACED; ++ ++ iep_prepare_format(&f->fmt.pix); ++ ++ return 0; ++} ++ ++static int iep_s_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct iep_ctx *ctx = iep_file2ctx(file); ++ struct vb2_queue *vq; ++ ++ int ret; ++ ++ ret = iep_try_fmt_vid_out(file, priv, f); ++ if (ret) ++ return ret; ++ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ ctx->src_fmt.pix = f->fmt.pix; ++ ctx->src_fmt.hw_fmt = iep_fmt_find(&f->fmt.pix); ++ ctx->src_fmt.y_stride = IEP_Y_STRIDE(f->fmt.pix.width, f->fmt.pix.height); ++ ctx->src_fmt.uv_stride = IEP_UV_STRIDE(f->fmt.pix.width, f->fmt.pix.height, ++ ctx->src_fmt.hw_fmt->uv_factor); ++ ++ /* Propagate colorspace information to capture. */ ++ ctx->dst_fmt.pix.colorspace = f->fmt.pix.colorspace; ++ ctx->dst_fmt.pix.xfer_func = f->fmt.pix.xfer_func; ++ ctx->dst_fmt.pix.ycbcr_enc = f->fmt.pix.ycbcr_enc; ++ ctx->dst_fmt.pix.quantization = f->fmt.pix.quantization; ++ ++ /* scaling is not supported */ ++ ctx->dst_fmt.pix.width = f->fmt.pix.width; ++ ctx->dst_fmt.pix.height = f->fmt.pix.height; ++ ctx->dst_fmt.y_stride = IEP_Y_STRIDE(f->fmt.pix.width, f->fmt.pix.height); ++ ctx->dst_fmt.uv_stride = IEP_UV_STRIDE(f->fmt.pix.width, f->fmt.pix.height, ++ ctx->dst_fmt.hw_fmt->uv_factor); ++ ++ ctx->fmt_changed = true; ++ ++ return 0; ++} ++ ++static int iep_s_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct iep_ctx *ctx = iep_file2ctx(file); ++ struct vb2_queue *vq; ++ int ret; ++ ++ ret = iep_try_fmt_vid_cap(file, priv, f); ++ if (ret) ++ return ret; ++ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ /* scaling is not supported */ ++ f->fmt.pix.width = ctx->src_fmt.pix.width; ++ f->fmt.pix.height = ctx->src_fmt.pix.height; ++ ++ ctx->dst_fmt.pix = f->fmt.pix; ++ ctx->dst_fmt.hw_fmt = iep_fmt_find(&f->fmt.pix); ++ ++ ctx->dst_fmt.y_stride = IEP_Y_STRIDE(f->fmt.pix.width, f->fmt.pix.height); ++ ctx->dst_fmt.uv_stride = IEP_UV_STRIDE(f->fmt.pix.width, f->fmt.pix.height, ++ ctx->dst_fmt.hw_fmt->uv_factor); ++ ++ ctx->fmt_changed = true; ++ ++ return 0; ++} ++ ++static const struct v4l2_ioctl_ops iep_ioctl_ops = { ++ .vidioc_querycap = iep_querycap, ++ ++ .vidioc_enum_framesizes = iep_enum_framesizes, ++ ++ .vidioc_enum_fmt_vid_cap = iep_enum_fmt, ++ .vidioc_g_fmt_vid_cap = iep_g_fmt_vid_cap, ++ .vidioc_try_fmt_vid_cap = iep_try_fmt_vid_cap, ++ .vidioc_s_fmt_vid_cap = iep_s_fmt_vid_cap, ++ ++ .vidioc_enum_fmt_vid_out = iep_enum_fmt, ++ .vidioc_g_fmt_vid_out = iep_g_fmt_vid_out, ++ .vidioc_try_fmt_vid_out = iep_try_fmt_vid_out, ++ .vidioc_s_fmt_vid_out = iep_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_create_bufs = v4l2_m2m_ioctl_create_bufs, ++ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, ++ ++ .vidioc_streamon = v4l2_m2m_ioctl_streamon, ++ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, ++}; ++ ++static const struct video_device iep_video_device = { ++ .name = IEP_NAME, ++ .vfl_dir = VFL_DIR_M2M, ++ .fops = &iep_fops, ++ .ioctl_ops = &iep_ioctl_ops, ++ .minor = -1, ++ .release = video_device_release_empty, ++ .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, ++}; ++ ++static int iep_parse_dt(struct rockchip_iep *iep) ++{ ++ int ret = 0; ++ ++ iep->axi_clk = devm_clk_get(iep->dev, "axi"); ++ if (IS_ERR(iep->axi_clk)) { ++ dev_err(iep->dev, "failed to get aclk clock\n"); ++ return PTR_ERR(iep->axi_clk); ++ } ++ ++ iep->ahb_clk = devm_clk_get(iep->dev, "ahb"); ++ if (IS_ERR(iep->ahb_clk)) { ++ dev_err(iep->dev, "failed to get hclk clock\n"); ++ return PTR_ERR(iep->ahb_clk); ++ } ++ ++ ret = clk_set_rate(iep->axi_clk, 300000000); ++ ++ if (ret) ++ dev_err(iep->dev, "failed to set axi clock rate to 300 MHz\n"); ++ ++ return ret; ++} ++ ++static irqreturn_t iep_isr(int irq, void *prv) ++{ ++ struct rockchip_iep *iep = prv; ++ struct iep_ctx *ctx; ++ u32 val; ++ enum vb2_buffer_state state = VB2_BUF_STATE_DONE; ++ ++ ctx = v4l2_m2m_get_curr_priv(iep->m2m_dev); ++ if (!ctx) { ++ v4l2_err(&iep->v4l2_dev, ++ "Instance released before the end of transaction\n"); ++ return IRQ_NONE; ++ } ++ ++ /* ++ * The irq is shared with the iommu. If the runtime-pm state of the ++ * iep-device is disabled or the interrupt status doesn't match the ++ * expeceted mask the irq has been targeted to the iommu. ++ */ ++ ++ if (!pm_runtime_active(iep->dev) || ++ !(iep_read(iep, IEP_INT) & IEP_INT_MASK)) ++ return IRQ_NONE; ++ ++ /* disable interrupt - will be re-enabled at next iep_device_run */ ++ iep_mod(ctx->iep, IEP_INT, ++ IEP_INT_FRAME_DONE_EN, 0); ++ ++ iep_mod(iep, IEP_INT, IEP_INT_FRAME_DONE_CLR, ++ IEP_INT_FRAME_DONE_CLR); ++ ++ /* wait for all status regs to show "idle" */ ++ val = readl_poll_timeout(iep->regs + IEP_STATUS, val, ++ (val == 0), 100, IEP_TIMEOUT); ++ ++ if (val) { ++ dev_err(iep->dev, ++ "Failed to wait for job to finish: status: %u\n", val); ++ state = VB2_BUF_STATE_ERROR; ++ ctx->job_abort = true; ++ } ++ ++ iep_m2m_dst_bufs_done(ctx, state); ++ ++ ctx->field_bff = (ctx->dst_buffs_done % 2 == 0) ++ ? ctx->field_order_bff : !ctx->field_order_bff; ++ ++ if (ctx->dst_buffs_done == 2 || ctx->job_abort) { ++ if (ctx->prev_src_buf) ++ v4l2_m2m_buf_done(ctx->prev_src_buf, state); ++ ++ /* current src buff will be next prev */ ++ ctx->prev_src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); ++ ++ v4l2_m2m_job_finish(ctx->iep->m2m_dev, ctx->fh.m2m_ctx); ++ ctx->dst_buffs_done = 0; ++ ++ } else { ++ iep_device_run(ctx); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int iep_probe(struct platform_device *pdev) ++{ ++ struct rockchip_iep *iep; ++ struct video_device *vfd; ++ struct resource *res; ++ int ret = 0; ++ int irq; ++ ++ if (!pdev->dev.of_node) ++ return -ENODEV; ++ ++ iep = devm_kzalloc(&pdev->dev, sizeof(*iep), GFP_KERNEL); ++ if (!iep) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, iep); ++ iep->dev = &pdev->dev; ++ iep->vfd = iep_video_device; ++ ++ ret = iep_parse_dt(iep); ++ if (ret) ++ dev_err(&pdev->dev, "Unable to parse OF data\n"); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ iep->regs = devm_ioremap_resource(iep->dev, res); ++ if (IS_ERR(iep->regs)) { ++ ret = PTR_ERR(iep->regs); ++ goto err_put_clk; ++ } ++ ++ ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); ++ if (ret) { ++ dev_err(&pdev->dev, "Could not set DMA coherent mask.\n"); ++ goto err_put_clk; ++ } ++ ++ vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ ret = irq; ++ goto err_put_clk; ++ } ++ ++ /* IRQ is shared with IOMMU */ ++ ret = devm_request_irq(iep->dev, irq, iep_isr, IRQF_SHARED, ++ dev_name(iep->dev), iep); ++ if (ret < 0) { ++ dev_err(iep->dev, "failed to request irq\n"); ++ goto err_put_clk; ++ } ++ ++ mutex_init(&iep->mutex); ++ ++ ret = v4l2_device_register(&pdev->dev, &iep->v4l2_dev); ++ if (ret) { ++ dev_err(iep->dev, "Failed to register V4L2 device\n"); ++ ++ return ret; ++ } ++ ++ vfd = &iep->vfd; ++ vfd->lock = &iep->mutex; ++ vfd->v4l2_dev = &iep->v4l2_dev; ++ ++ snprintf(vfd->name, sizeof(vfd->name), "%s", ++ iep_video_device.name); ++ ++ video_set_drvdata(vfd, iep); ++ ++ ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); ++ if (ret) { ++ v4l2_err(&iep->v4l2_dev, "Failed to register video device\n"); ++ ++ goto err_v4l2; ++ } ++ ++ v4l2_info(&iep->v4l2_dev, ++ "Device %s registered as /dev/video%d\n", vfd->name, vfd->num); ++ ++ iep->m2m_dev = v4l2_m2m_init(&iep_m2m_ops); ++ if (IS_ERR(iep->m2m_dev)) { ++ v4l2_err(&iep->v4l2_dev, ++ "Failed to initialize V4L2 M2M device\n"); ++ ret = PTR_ERR(iep->m2m_dev); ++ ++ goto err_video; ++ } ++ ++ pm_runtime_set_autosuspend_delay(iep->dev, 100); ++ pm_runtime_use_autosuspend(iep->dev); ++ pm_runtime_enable(iep->dev); ++ ++ return ret; ++ ++err_video: ++ video_unregister_device(&iep->vfd); ++err_v4l2: ++ v4l2_device_unregister(&iep->v4l2_dev); ++err_put_clk: ++ pm_runtime_dont_use_autosuspend(iep->dev); ++ pm_runtime_disable(iep->dev); ++ ++return ret; ++} ++ ++static int iep_remove(struct platform_device *pdev) ++{ ++ struct rockchip_iep *iep = platform_get_drvdata(pdev); ++ ++ pm_runtime_dont_use_autosuspend(iep->dev); ++ pm_runtime_disable(iep->dev); ++ ++ v4l2_m2m_release(iep->m2m_dev); ++ video_unregister_device(&iep->vfd); ++ v4l2_device_unregister(&iep->v4l2_dev); ++ ++ return 0; ++} ++ ++static int __maybe_unused iep_runtime_suspend(struct device *dev) ++{ ++ struct rockchip_iep *iep = dev_get_drvdata(dev); ++ ++ clk_disable_unprepare(iep->ahb_clk); ++ clk_disable_unprepare(iep->axi_clk); ++ ++ return 0; ++} ++ ++static int __maybe_unused iep_runtime_resume(struct device *dev) ++{ ++ struct rockchip_iep *iep; ++ int ret = 0; ++ ++ iep = dev_get_drvdata(dev); ++ ++ ret = clk_prepare_enable(iep->axi_clk); ++ if (ret) { ++ dev_err(iep->dev, "Cannot enable axi clock: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(iep->ahb_clk); ++ if (ret) { ++ dev_err(iep->dev, "Cannot enable ahb clock: %d\n", ret); ++ goto err_disable_axi_clk; ++ } ++ ++ return ret; ++ ++err_disable_axi_clk: ++ clk_disable_unprepare(iep->axi_clk); ++ return ret; ++} ++ ++static const struct dev_pm_ops iep_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, ++ pm_runtime_force_resume) ++ SET_RUNTIME_PM_OPS(iep_runtime_suspend, ++ iep_runtime_resume, NULL) ++}; ++ ++static const struct of_device_id rockchip_iep_match[] = { ++ { ++ .compatible = "rockchip,rk3228-iep", ++ }, ++ {}, ++}; ++ ++MODULE_DEVICE_TABLE(of, rockchip_iep_match); ++ ++static struct platform_driver iep_pdrv = { ++ .probe = iep_probe, ++ .remove = iep_remove, ++ .driver = { ++ .name = IEP_NAME, ++ .pm = &iep_pm_ops, ++ .of_match_table = rockchip_iep_match, ++ }, ++}; ++ ++module_platform_driver(iep_pdrv); ++ ++MODULE_AUTHOR("Alex Bee "); ++MODULE_DESCRIPTION("Rockchip Image Enhancement Processor"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/media/platform/rockchip/iep/iep.h b/drivers/media/platform/rockchip/iep/iep.h +new file mode 100644 +index 000000000000..7d9fc61624b6 +--- /dev/null ++++ b/drivers/media/platform/rockchip/iep/iep.h +@@ -0,0 +1,112 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Rockchip Image Enhancement Processor (IEP) driver ++ * ++ * Copyright (C) 2020 Alex Bee ++ * ++ */ ++#ifndef __IEP_H__ ++#define __IEP_H__ ++ ++#include ++#include ++#include ++#include ++ ++#define IEP_NAME "rockchip-iep" ++ ++/* Hardware limits */ ++#define IEP_MIN_WIDTH 320U ++#define IEP_MAX_WIDTH 1920U ++ ++#define IEP_MIN_HEIGHT 240U ++#define IEP_MAX_HEIGHT 1088U ++ ++/* Hardware defaults */ ++#define IEP_DEFAULT_WIDTH 320U ++#define IEP_DEFAULT_HEIGHT 240U ++ ++//ns ++#define IEP_TIMEOUT 250000 ++ ++struct iep_fmt { ++ u32 fourcc; ++ u8 depth; ++ u8 uv_factor; ++ u8 color_swap; ++ u8 hw_format; ++}; ++ ++struct iep_frm_fmt { ++ struct iep_fmt *hw_fmt; ++ struct v4l2_pix_format pix; ++ ++ unsigned int y_stride; ++ unsigned int uv_stride; ++}; ++ ++struct iep_ctx { ++ struct v4l2_fh fh; ++ struct rockchip_iep *iep; ++ ++ struct iep_frm_fmt src_fmt; ++ struct iep_frm_fmt dst_fmt; ++ ++ struct vb2_v4l2_buffer *prev_src_buf; ++ struct vb2_v4l2_buffer *dst0_buf; ++ struct vb2_v4l2_buffer *dst1_buf; ++ ++ u32 dst_sequence; ++ u32 src_sequence; ++ ++ /* bff = bottom field first */ ++ bool field_order_bff; ++ bool field_bff; ++ ++ unsigned int dst_buffs_done; ++ ++ bool fmt_changed; ++ bool job_abort; ++}; ++ ++struct rockchip_iep { ++ struct v4l2_device v4l2_dev; ++ struct v4l2_m2m_dev *m2m_dev; ++ struct video_device vfd; ++ ++ struct device *dev; ++ ++ void __iomem *regs; ++ ++ struct clk *axi_clk; ++ struct clk *ahb_clk; ++ ++ /* vfd lock */ ++ struct mutex mutex; ++}; ++ ++static inline void iep_write(struct rockchip_iep *iep, u32 reg, u32 value) ++{ ++ writel(value, iep->regs + reg); ++}; ++ ++static inline u32 iep_read(struct rockchip_iep *iep, u32 reg) ++{ ++ return readl(iep->regs + reg); ++}; ++ ++static inline void iep_shadow_mod(struct rockchip_iep *iep, u32 reg, ++ u32 shadow_reg, u32 mask, u32 val) ++{ ++ u32 temp = iep_read(iep, shadow_reg) & ~(mask); ++ ++ temp |= val & mask; ++ iep_write(iep, reg, temp); ++}; ++ ++static inline void iep_mod(struct rockchip_iep *iep, u32 reg, u32 mask, u32 val) ++{ ++ iep_shadow_mod(iep, reg, reg, mask, val); ++}; ++ ++#endif + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Wed, 14 Oct 2020 20:22:38 +0200 +Subject: [PATCH] ARM64: dts: rockchip: Add IEP node for RK3328 + +while at that also add the mmu required + +Signed-off-by: Alex Bee +--- + arch/arm64/boot/dts/rockchip/rk3328.dtsi | 25 +++++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/rockchip/rk3328.dtsi b/arch/arm64/boot/dts/rockchip/rk3328.dtsi +index bd1e5edfbf95..b91c0bbe5ad1 100644 +@@ -767,6 +768,28 @@ vop_mmu: iommu@ff373f00 { + status = "disabled"; + }; + ++ iep: iep@ff3a0000 { ++ compatible = "rockchip,rk3328-iep", "rockchip,rk3228-iep"; ++ reg = <0x0 0xff3a0000 0x0 0x800>; ++ interrupts = ; ++ interrupt-names = "iep"; ++ clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; ++ clock-names = "axi", "ahb"; ++ power-domains = <&power RK3328_PD_VIDEO>; ++ iommus = <&iep_mmu>; ++ }; ++ ++ iep_mmu: iommu@ff3a0800 { ++ compatible = "rockchip,iommu"; ++ reg = <0x0 0xff3a0800 0x0 0x40>; ++ interrupts = ; ++ interrupt-names = "iep_mmu"; ++ clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; ++ clock-names = "aclk", "iface"; ++ power-domains = <&power RK3328_PD_VIDEO>; ++ #iommu-cells = <0>; ++ }; ++ + hdmi: hdmi@ff3c0000 { + compatible = "rockchip,rk3328-dw-hdmi"; + reg = <0x0 0xff3c0000 0x0 0x20000>; + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Wed, 14 Oct 2020 20:43:12 +0200 +Subject: [PATCH] ARM64: dts: rockchip: Add IEP node for RK3399 + +Signed-off-by: Alex Bee +--- + arch/arm64/boot/dts/rockchip/rk3399.dtsi | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/rockchip/rk3399.dtsi b/arch/arm64/boot/dts/rockchip/rk3399.dtsi +index 4d5004c9c778..7969aeec0bd0 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3399.dtsi ++++ b/arch/arm64/boot/dts/rockchip/rk3399.dtsi +@@ -1297,6 +1297,17 @@ vdec_mmu: iommu@ff660480 { + #iommu-cells = <0>; + }; + ++ iep: iep@ff670000 { ++ compatible = "rockchip,rk3399-iep", "rockchip,rk3228-iep"; ++ reg = <0x0 0xff670000 0x0 0x800>; ++ interrupts = ; ++ interrupt-names = "iep"; ++ clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; ++ clock-names = "axi", "ahb"; ++ power-domains = <&power RK3399_PD_IEP>; ++ iommus = <&iep_mmu>; ++ }; ++ + iep_mmu: iommu@ff670800 { + compatible = "rockchip,iommu"; + reg = <0x0 0xff670800 0x0 0x40>; +@@ -1304,8 +1315,8 @@ iep_mmu: iommu@ff670800 { + interrupt-names = "iep_mmu"; + clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; + clock-names = "aclk", "iface"; ++ power-domains = <&power RK3399_PD_IEP>; + #iommu-cells = <0>; +- status = "disabled"; + }; + + rga: rga@ff680000 { + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Wed, 14 Oct 2020 20:53:56 +0200 +Subject: [PATCH] ARM: dts: rockchip: Add IEP node for RK3288 + +Signed-off-by: Alex Bee +--- + arch/arm/boot/dts/rk3288.dtsi | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/boot/dts/rk3288.dtsi b/arch/arm/boot/dts/rk3288.dtsi +index ba43ee6b91e8..e9bd68cd926e 100644 +--- a/arch/arm/boot/dts/rk3288.dtsi ++++ b/arch/arm/boot/dts/rk3288.dtsi +@@ -1002,6 +1002,17 @@ crypto: cypto-controller@ff8a0000 { + status = "okay"; + }; + ++ iep: iep@ff90000 { ++ compatible = "rockchip,rk3288-iep", "rockchip,rk3228-iep"; ++ reg = <0x0 0xff900000 0x0 0x800>; ++ interrupts = ; ++ interrupt-names = "iep"; ++ clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; ++ clock-names = "axi", "ahb"; ++ power-domains = <&power RK3288_PD_VIO>; ++ iommus = <&iep_mmu>; ++ }; ++ + iep_mmu: iommu@ff900800 { + compatible = "rockchip,iommu"; + reg = <0x0 0xff900800 0x0 0x40>; +@@ -1009,8 +1020,8 @@ iep_mmu: iommu@ff900800 { + interrupt-names = "iep_mmu"; + clocks = <&cru ACLK_IEP>, <&cru HCLK_IEP>; + clock-names = "aclk", "iface"; ++ power-domains = <&power RK3288_PD_VIO>; + #iommu-cells = <0>; +- status = "disabled"; + }; + + isp_mmu: iommu@ff914000 {