From 4144c08a176cc32de0103da4cd5f470707d008b1 Mon Sep 17 00:00:00 2001 From: Paolo Sabatino Date: Thu, 21 Oct 2021 16:20:07 +0000 Subject: [PATCH] add rockchip IEP driver --- .../bindings/media/rockchip-iep.yaml | 73 ++ arch/arm/boot/dts/rk3288.dtsi | 13 +- arch/arm64/boot/dts/rockchip/rk3328.dtsi | 22 + arch/arm64/boot/dts/rockchip/rk3399.dtsi | 13 + 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 ++ 10 files changed, 1632 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/media/rockchip-iep.yaml 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/Documentation/devicetree/bindings/media/rockchip-iep.yaml b/Documentation/devicetree/bindings/media/rockchip-iep.yaml new file mode 100644 index 000000000..a9efcda13 --- /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>; + }; diff --git a/arch/arm/boot/dts/rk3288.dtsi b/arch/arm/boot/dts/rk3288.dtsi index 9c5a7791a..13db2ac6b 100644 --- a/arch/arm/boot/dts/rk3288.dtsi +++ b/arch/arm/boot/dts/rk3288.dtsi @@ -983,6 +983,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>; @@ -990,8 +1001,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 { diff --git a/arch/arm64/boot/dts/rockchip/rk3328.dtsi b/arch/arm64/boot/dts/rockchip/rk3328.dtsi index 8c0bca75e..162e57936 100644 --- a/arch/arm64/boot/dts/rockchip/rk3328.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3328.dtsi @@ -829,6 +829,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>; diff --git a/arch/arm64/boot/dts/rockchip/rk3399.dtsi b/arch/arm64/boot/dts/rockchip/rk3399.dtsi index c39408ccc..f35211810 100644 --- a/arch/arm64/boot/dts/rockchip/rk3399.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3399.dtsi @@ -1283,6 +1283,18 @@ 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>; @@ -1290,6 +1302,7 @@ 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"; }; diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 157c92468..d77056060 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -527,6 +527,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 73ce083c2..d1cf1cf99 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -54,6 +54,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_ISP1) += rockchip/rkisp1/ obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/ diff --git a/drivers/media/platform/rockchip/iep/Makefile b/drivers/media/platform/rockchip/iep/Makefile new file mode 100644 index 000000000..5c89b3277 --- /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 000000000..a68685ef3 --- /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 000000000..f4b932073 --- /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 000000000..7d9fc6162 --- /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 -- 2.30.2