From patchwork Fri Dec 27 07:09:10 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 853964 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 550D613D244; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=E4s2lHvmyndqf55P+og5h6iiigLvnDCUmr+knfgQzOiuPtGufmnBRGDazmWub+xu2nmGVLa7llJfTmZh3N2G1L6GGMfGQMN0iVt+T0XWPrgbT1MVtl/uWj5R2awq6j1kS2H6EMjobAgUqY0+zc0b8XN6vd948oC9Q5vCB/Km7fM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=dB+v4vZ77CITUInmv0lV+wncrB3byu1tfpTr95BjbGk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uft0LfagHeA28cWAyXqUC+Y0HKNLrhuKKQ14EWhLN1AaatzCLjLNnXPjBkNH5dC1XckUljwYiHnXZG7/WKNfGKEKmUCjRGVt/ce2OOeHKfDPRABuI3qzw6d+W2kmnfRbNsdwQ8TkPRpift1Fb6+bjp0ns+PcxE8C2cEcByLoxtI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QXnamMr2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QXnamMr2" Received: by smtp.kernel.org (Postfix) with ESMTPS id D8E29C4CED6; Fri, 27 Dec 2024 07:09:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283376; bh=dB+v4vZ77CITUInmv0lV+wncrB3byu1tfpTr95BjbGk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=QXnamMr2XtKWSp7RRN77i9xpcy8XSowqgUBiHlf8l9JXz6ffnaSdXK+ahnMwclce/ HmiDDbFyDc8zlxhDKdWmYH2wiQgt8T5OuKa2T8AK+kKnJoQphsbWAsflGZeMOAvDKY 5a3VXQqcwfdJ209iLwTPFtiIzCt38QJ0rtwg4FVUGJLLgDhfkQqxZQirzmuNJ6H9JY M3zBB4oBxQng5sVfFaJwSWWZCtg+zV+l/hUlnkpIxD5VyFaJOUBC+jC9ITeztK8JFH u7iRQPiDz0RJ01Bx3R6RcWDJRv5Y/IwMSrDeF86UYT9FMYm+zi8/mEnsCtqajuYvw8 UjUcCBVWcmnyQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C4774E7718F; Fri, 27 Dec 2024 07:09:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:10 +0800 Subject: [PATCH v5 01/10] dt-bindings: media: Add amlogic,c3-mipi-csi2.yaml Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-1-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li , Krzysztof Kozlowski X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=4546; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=UR+F4DAIF45sMVWIQ9QMPW/rSAXK8QvBGZOSXJAHlgQ=; b=k1PCDLEiKFcfeVE4XytUwNcjRST8aX6Gt5ffLfVSlkVJnBav31xbrELEdb1vBxbHLGpsS49Ae AD7c4+ET1HpCpi4wgX4a7bQF3l0ziSmWEaAF95sle1YT6dLYf8qkWuz X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li c3-mipi-csi2 is used to receive mipi data from image sensor. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Keke Li --- .../bindings/media/amlogic,c3-mipi-csi2.yaml | 131 +++++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 137 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml new file mode 100644 index 000000000000..76b68d1e7316 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,c3-mipi-csi2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic C3 MIPI CSI-2 receiver + +maintainers: + - Keke Li + +description: + MIPI CSI-2 receiver contains CSI-2 RX PHY and host controller. + It receives the MIPI data from the image sensor and sends MIPI data + to MIPI adapter. + +properties: + compatible: + enum: + - amlogic,c3-mipi-csi2 + + reg: + maxItems: 3 + + reg-names: + items: + - const: aphy + - const: dphy + - const: host + + power-domains: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: vapb + - const: phy0 + + assigned-clocks: true + + assigned-clock-rates: true + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: input port node, connected to sensor. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: output port node + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reg-names + - power-domains + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + csi: csi@ff018000 { + compatible = "amlogic,c3-mipi-csi2"; + reg = <0x0 0xff018000 0x0 0x400>, + <0x0 0xff019000 0x0 0x300>, + <0x0 0xff01a000 0x0 0x100>; + reg-names = "aphy", "dphy", "host"; + power-domains = <&pwrc PWRC_C3_MIPI_ISP_WRAP_ID>; + clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_CSI_PHY0>; + clock-names = "vapb", "phy0"; + assigned-clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_CSI_PHY0>; + assigned-clock-rates = <0>, <200000000>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + c3_mipi_csi_in: endpoint { + remote-endpoint = <&imx290_out>; + data-lanes = <1 2 3 4>; + }; + }; + + port@1 { + reg = <1>; + c3_mipi_csi_out: endpoint { + remote-endpoint = <&c3_adap_in>; + }; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index d2ab799a0659..2b06962db506 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1243,6 +1243,12 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC MIPI CSI2 DRIVER +M: Keke Li +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml + AMLOGIC RTC DRIVER M: Yiting Deng M: Xianwei Zhao From patchwork Fri Dec 27 07:09:11 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 854092 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 55089524B4; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=RR1usvMOS6oR/COvKrqkEAuf5NSMh2IJyyfhwXzMcXyAeZ6zwoIUzHqefrO/RzniQvBuphpfwnZAODIejazpYWvl/IdCLRSTiWDUu6v4dy2nqEv2ACITtOxaV3vbDPcAOUZfHYGxEkHdrsBax8Du8hrGH2qubWhJDQq7BzSYM7Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=DViKbk8TNHxGc2ssruZCL2vyYKL1fAP14ldlXTcIls0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KmrgdZ6TUm7Q3lzG38XFhPfGFrZXBhG4mFNVFJ6gaNaOKyLbjEVXqvXsfat7pQlf8swsILF0KmkT+C5dgG0OSW5gK6UGYBjne+G1Yjdg6HKcEwbUmtbiKmr+vwu0lDHFQpQlPjxZ0VZIrrZV7L+EQzIEbw1tJ6sJnSx0Osh1qZk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VbzevhGp; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VbzevhGp" Received: by smtp.kernel.org (Postfix) with ESMTPS id EDACDC4CEDC; Fri, 27 Dec 2024 07:09:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=DViKbk8TNHxGc2ssruZCL2vyYKL1fAP14ldlXTcIls0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=VbzevhGpGajw0p11lFxYpanQ69OYAZlIOhFjyqNqGRLYavu47IUW6MsFfNTknnG5h aR1WgxEi8RMG1720IEWyiS0UlNb/ec14pKK7xdgU0atPzb1+HtsHSzt77vKCRd+wa7 XqBU0tbFDEG4HWvM6CrMcac6xdj6/RejPhqBmy3yYZkTh9INOgMTHHOM/ER+uzquAg 0uq6WuXZ2coyRn4b4D7qDU4brt5ztmkQFj29LwWuOvFTtGqH8VMekQv2qUAQFydC+M kxkAH+dxmbg6LAVHkQlFbYaJ1JOImGnSatB5JLz3B/bAfGzaYzUU91NQ5BW4Lqh4qE S4HNmwkvaV1fQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id E4E50E7718B; Fri, 27 Dec 2024 07:09:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:11 +0800 Subject: [PATCH v5 02/10] media: platform: Add C3 MIPI CSI-2 driver Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-2-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=30751; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=d4ElrpaXExQJyRdywVMTcQUl4RYEWxWEg4UwYORABXo=; b=cJgxTIn1MCTL0yyRcmZBVZM7EtVscCgc7zAOQ37QX3Ct70bst6MEYmeGqOVg49oVJPhxvopaO yMXiVC5cjYjBZyTXQHAxizBYaFUhB+JtSHq2904PcrG70U9/D1SraxK X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add a driver for the CSI-2 receiver uinit found on the Amlofic C3 SoC. Create a drivers/media/platform/amlogic/c3/ directory to host the driver and the forthcoming support for the Amlogic C3 MIPI adapter and C3 ISP. This driver is used to receive the MIPI data from image sensor. Reviewed-by: Daniel Scally Signed-off-by: Keke Li --- MAINTAINERS | 1 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 2 + drivers/media/platform/amlogic/c3/Kconfig | 3 + drivers/media/platform/amlogic/c3/Makefile | 3 + .../media/platform/amlogic/c3/mipi-csi2/Kconfig | 16 + .../media/platform/amlogic/c3/mipi-csi2/Makefile | 3 + .../platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c | 831 +++++++++++++++++++++ 8 files changed, 860 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2b06962db506..1d1416b15570 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,6 +1248,7 @@ M: Keke Li L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml +F: drivers/media/platform/amlogic/c3/mipi-csi2/ AMLOGIC RTC DRIVER M: Yiting Deng diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index 5014957404e9..458acf3d5fa8 100644 --- a/drivers/media/platform/amlogic/Kconfig +++ b/drivers/media/platform/amlogic/Kconfig @@ -2,4 +2,5 @@ comment "Amlogic media platform drivers" +source "drivers/media/platform/amlogic/c3/Kconfig" source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile index d3cdb8fa4ddb..c744afcd1b9e 100644 --- a/drivers/media/platform/amlogic/Makefile +++ b/drivers/media/platform/amlogic/Makefile @@ -1,2 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only + +obj-y += c3/ obj-y += meson-ge2d/ diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig new file mode 100644 index 000000000000..098d458747b8 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig" diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile new file mode 100644 index 000000000000..a468fb782f94 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += mipi-csi2/ diff --git a/drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig b/drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig new file mode 100644 index 000000000000..0d7b2e203273 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_C3_MIPI_CSI2 + tristate "Amlogic C3 MIPI CSI-2 receiver" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 driver for Amlogic C3 MIPI CSI-2 receiver. + C3 MIPI CSI-2 receiver is used to receive MIPI data from + image sensor. + + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/c3/mipi-csi2/Makefile b/drivers/media/platform/amlogic/c3/mipi-csi2/Makefile new file mode 100644 index 000000000000..cc08fc722bfd --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_VIDEO_C3_MIPI_CSI2) += c3-mipi-csi2.o diff --git a/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c new file mode 100644 index 000000000000..7343e7003d13 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* C3 CSI-2 submodule definition */ +enum { + SUBMD_APHY, + SUBMD_DPHY, + SUBMD_HOST, +}; + +#define CSI2_SUBMD_MASK GENMASK(17, 16) +#define CSI2_SUBMD_SHIFT 16 +#define CSI2_SUBMD(x) (((x) & (CSI2_SUBMD_MASK)) >> (CSI2_SUBMD_SHIFT)) +#define CSI2_REG_ADDR_MASK GENMASK(15, 0) +#define CSI2_REG_ADDR(x) ((x) & (CSI2_REG_ADDR_MASK)) +#define CSI2_REG_A(x) ((SUBMD_APHY << CSI2_SUBMD_SHIFT) | (x)) +#define CSI2_REG_D(x) ((SUBMD_DPHY << CSI2_SUBMD_SHIFT) | (x)) +#define CSI2_REG_H(x) ((SUBMD_HOST << CSI2_SUBMD_SHIFT) | (x)) + +#define MIPI_CSI2_CLOCK_NUM_MAX 3 +#define MIPI_CSI2_SUBDEV_NAME "c3-mipi-csi2" + +/* C3 CSI-2 APHY register */ +#define CSI_PHY_CNTL0 CSI2_REG_A(0x44) +#define CSI_PHY_CNTL0_HS_LP_BIAS_EN BIT(10) +#define CSI_PHY_CNTL0_HS_RX_TRIM_11 (11 << 11) +#define CSI_PHY_CNTL0_LP_LOW_VTH_2 (2 << 16) +#define CSI_PHY_CNTL0_LP_HIGH_VTH_4 (4 << 20) +#define CSI_PHY_CNTL0_DATA_LANE0_HS_DIG_EN BIT(24) +#define CSI_PHY_CNTL0_DATA_LANE1_HS_DIG_EN BIT(25) +#define CSI_PHY_CNTL0_CLK0_LANE_HS_DIG_EN BIT(26) +#define CSI_PHY_CNTL0_DATA_LANE2_HS_DIG_EN BIT(27) +#define CSI_PHY_CNTL0_DATA_LANE3_HS_DIG_EN BIT(28) + +#define CSI_PHY_CNTL1 CSI2_REG_A(0x48) +#define CSI_PHY_CNTL1_HS_EQ_CAP_SMALL (2 << 16) +#define CSI_PHY_CNTL1_HS_EQ_CAP_BIG (3 << 16) +#define CSI_PHY_CNTL1_HS_EQ_RES_MIN (3 << 18) +#define CSI_PHY_CNTL1_HS_EQ_RES_MED (2 << 18) +#define CSI_PHY_CNTL1_HS_EQ_RES_MAX BIT(18) +#define CSI_PHY_CNTL1_CLK_CHN_EQ_MAX_GAIN BIT(20) +#define CSI_PHY_CNTL1_DATA_CHN_EQ_MAX_GAIN BIT(21) +#define CSI_PHY_CNTL1_COM_BG_EN BIT(24) +#define CSI_PHY_CNTL1_HS_SYNC_EN BIT(25) + +/* C3 CSI-2 DPHY register */ +#define MIPI_PHY_CTRL CSI2_REG_D(0x00) +#define MIPI_PHY_CTRL_DATA_LANE0_EN (0 << 0) +#define MIPI_PHY_CTRL_DATA_LANE0_DIS BIT(0) +#define MIPI_PHY_CTRL_DATA_LANE1_EN (0 << 1) +#define MIPI_PHY_CTRL_DATA_LANE1_DIS BIT(1) +#define MIPI_PHY_CTRL_DATA_LANE2_EN (0 << 2) +#define MIPI_PHY_CTRL_DATA_LANE2_DIS BIT(2) +#define MIPI_PHY_CTRL_DATA_LANE3_EN (0 << 3) +#define MIPI_PHY_CTRL_DATA_LANE3_DIS BIT(3) +#define MIPI_PHY_CTRL_CLOCK_LANE_EN (0 << 4) +#define MIPI_PHY_CTRL_CLOCK_LANE_DIS BIT(4) + +#define MIPI_PHY_CLK_LANE_CTRL CSI2_REG_D(0x04) +#define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_ENTER BIT(0) +#define MIPI_PHY_CLK_LANE_CTRL_FORCE_ULPS_EXIT BIT(1) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS (0 << 3) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_2 BIT(3) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_4 (2 << 3) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_8 (3 << 3) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_16 (4 << 3) +#define MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN BIT(6) +#define MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS BIT(7) +#define MIPI_PHY_CLK_LANE_CTRL_END_EN BIT(8) +#define MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN BIT(9) + +#define MIPI_PHY_DATA_LANE_CTRL1 CSI2_REG_D(0x0c) +#define MIPI_PHY_DATA_LANE_CTRL1_INSERT_ERRESC BIT(0) +#define MIPI_PHY_DATA_LANE_CTRL1_HS_SYNC_CHK_EN BIT(1) +#define MIPI_PHY_DATA_LANE_CTRL1_PIPE_MASK GENMASK(6, 2) +#define MIPI_PHY_DATA_LANE_CTRL1_PIPE_ALL_EN (0x1f << 2) +#define MIPI_PHY_DATA_LANE_CTRL1_PIPE_DELAY_MASK GENMASK(9, 7) +#define MIPI_PHY_DATA_LANE_CTRL1_PIPE_DELAY_3 (3 << 7) + +#define MIPI_PHY_TCLK_MISS CSI2_REG_D(0x10) +#define MIPI_PHY_TCLK_MISS_CYCLES_MASK GENMASK(7, 0) +#define MIPI_PHY_TCLK_MISS_CYCLES_9 (9 << 0) + +#define MIPI_PHY_TCLK_SETTLE CSI2_REG_D(0x14) +#define MIPI_PHY_TCLK_SETTLE_CYCLES_MASK GENMASK(7, 0) +#define MIPI_PHY_TCLK_SETTLE_CYCLES_31 (31 << 0) + +#define MIPI_PHY_THS_EXIT CSI2_REG_D(0x18) +#define MIPI_PHY_THS_EXIT_CYCLES_MASK GENMASK(7, 0) +#define MIPI_PHY_THS_EXIT_CYCLES_8 (8 << 0) + +#define MIPI_PHY_THS_SKIP CSI2_REG_D(0x1c) +#define MIPI_PHY_THS_SKIP_CYCLES_MASK GENMASK(7, 0) +#define MIPI_PHY_THS_SKIP_CYCLES_10 (10 << 0) + +#define MIPI_PHY_THS_SETTLE CSI2_REG_D(0x20) +#define MIPI_PHY_THS_SETTLE_CYCLES_MASK GENMASK(7, 0) + +#define MIPI_PHY_TINIT CSI2_REG_D(0x24) +#define MIPI_PHY_TINIT_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TINIT_CYCLES_20000 (20000 << 0) + +#define MIPI_PHY_TULPS_C CSI2_REG_D(0x28) +#define MIPI_PHY_TULPS_C_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TULPS_C_CYCLES_4096 (4096 << 0) + +#define MIPI_PHY_TULPS_S CSI2_REG_D(0x2c) +#define MIPI_PHY_TULPS_S_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TULPS_S_CYCLES_256 (256 << 0) + +#define MIPI_PHY_TMBIAS CSI2_REG_D(0x30) +#define MIPI_PHY_TMBIAS_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TMBIAS_CYCLES_256 (256 << 0) + +#define MIPI_PHY_TLP_EN_W CSI2_REG_D(0x34) +#define MIPI_PHY_TLP_EN_W_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TLP_EN_W_CYCLES_12 (12 << 0) + +#define MIPI_PHY_TLPOK CSI2_REG_D(0x38) +#define MIPI_PHY_TLPOK_CYCLES_MASK GENMASK(31, 0) +#define MIPI_PHY_TLPOK_CYCLES_256 (256 << 0) + +#define MIPI_PHY_TWD_INIT CSI2_REG_D(0x3c) +#define MIPI_PHY_TWD_INIT_DOG_MASK GENMASK(31, 0) +#define MIPI_PHY_TWD_INIT_DOG_0X400000 (0x400000 << 0) + +#define MIPI_PHY_TWD_HS CSI2_REG_D(0x40) +#define MIPI_PHY_TWD_HS_DOG_MASK GENMASK(31, 0) +#define MIPI_PHY_TWD_HS_DOG_0X400000 (0x400000 << 0) + +#define MIPI_PHY_MUX_CTRL0 CSI2_REG_D(0x284) +#define MIPI_PHY_MUX_CTRL0_SFEN3_SRC_MASK GENMASK(3, 0) +#define MIPI_PHY_MUX_CTRL0_SFEN3_SRC_LANE0 (0 << 0) +#define MIPI_PHY_MUX_CTRL0_SFEN3_SRC_LANE1 BIT(0) +#define MIPI_PHY_MUX_CTRL0_SFEN3_SRC_LANE2 (2 << 0) +#define MIPI_PHY_MUX_CTRL0_SFEN3_SRC_LANE3 (3 << 0) +#define MIPI_PHY_MUX_CTRL0_SFEN2_SRC_MASK GENMASK(7, 4) +#define MIPI_PHY_MUX_CTRL0_SFEN2_SRC_LANE0 (0 << 4) +#define MIPI_PHY_MUX_CTRL0_SFEN2_SRC_LANE1 BIT(4) +#define MIPI_PHY_MUX_CTRL0_SFEN2_SRC_LANE2 (2 << 4) +#define MIPI_PHY_MUX_CTRL0_SFEN2_SRC_LANE3 (3 << 4) +#define MIPI_PHY_MUX_CTRL0_SFEN1_SRC_MASK GENMASK(11, 8) +#define MIPI_PHY_MUX_CTRL0_SFEN1_SRC_LANE0 (0 << 8) +#define MIPI_PHY_MUX_CTRL0_SFEN1_SRC_LANE1 BIT(8) +#define MIPI_PHY_MUX_CTRL0_SFEN1_SRC_LANE2 (2 << 8) +#define MIPI_PHY_MUX_CTRL0_SFEN1_SRC_LANE3 (3 << 8) +#define MIPI_PHY_MUX_CTRL0_SFEN0_SRC_MASK GENMASK(14, 12) +#define MIPI_PHY_MUX_CTRL0_SFEN0_SRC_LANE0 (0 << 12) +#define MIPI_PHY_MUX_CTRL0_SFEN0_SRC_LANE1 BIT(12) +#define MIPI_PHY_MUX_CTRL0_SFEN0_SRC_LANE2 (2 << 12) +#define MIPI_PHY_MUX_CTRL0_SFEN0_SRC_LANE3 (3 << 12) + +#define MIPI_PHY_MUX_CTRL1 CSI2_REG_D(0x288) +#define MIPI_PHY_MUX_CTRL1_LANE3_SRC_MASK GENMASK(3, 0) +#define MIPI_PHY_MUX_CTRL1_LANE3_SRC_SFEN0 (0 << 0) +#define MIPI_PHY_MUX_CTRL1_LANE3_SRC_SFEN1 BIT(0) +#define MIPI_PHY_MUX_CTRL1_LANE3_SRC_SFEN2 (2 << 0) +#define MIPI_PHY_MUX_CTRL1_LANE3_SRC_SFEN3 (3 << 0) +#define MIPI_PHY_MUX_CTRL1_LANE2_SRC_MASK GENMASK(7, 4) +#define MIPI_PHY_MUX_CTRL1_LANE2_SRC_SFEN0 (0 << 4) +#define MIPI_PHY_MUX_CTRL1_LANE2_SRC_SFEN1 BIT(4) +#define MIPI_PHY_MUX_CTRL1_LANE2_SRC_SFEN2 (2 << 4) +#define MIPI_PHY_MUX_CTRL1_LANE2_SRC_SFEN3 (3 << 4) +#define MIPI_PHY_MUX_CTRL1_LANE1_SRC_MASK GENMASK(11, 8) +#define MIPI_PHY_MUX_CTRL1_LANE1_SRC_SFEN0 (0 << 8) +#define MIPI_PHY_MUX_CTRL1_LANE1_SRC_SFEN1 BIT(8) +#define MIPI_PHY_MUX_CTRL1_LANE1_SRC_SFEN2 (2 << 8) +#define MIPI_PHY_MUX_CTRL1_LANE1_SRC_SFEN3 (3 << 8) +#define MIPI_PHY_MUX_CTRL1_LANE0_SRC_MASK GENMASK(14, 12) +#define MIPI_PHY_MUX_CTRL1_LANE0_SRC_SFEN0 (0 << 12) +#define MIPI_PHY_MUX_CTRL1_LANE0_SRC_SFEN1 BIT(12) +#define MIPI_PHY_MUX_CTRL1_LANE0_SRC_SFEN2 (2 << 12) +#define MIPI_PHY_MUX_CTRL1_LANE0_SRC_SFEN3 (3 << 12) + +/* C3 CSI-2 HOST register */ +#define CSI2_HOST_N_LANES CSI2_REG_H(0x04) +#define CSI2_HOST_N_LANES_MASK GENMASK(1, 0) +#define CSI2_HOST_N_LANES_1 (0 << 0) +#define CSI2_HOST_N_LANES_2 BIT(0) +#define CSI2_HOST_N_LANES_3 (2 << 0) +#define CSI2_HOST_N_LANES_4 (3 << 0) + +#define CSI2_HOST_CSI2_RESETN CSI2_REG_H(0x10) +#define CSI2_HOST_CSI2_RESETN_MASK BIT(0) +#define CSI2_HOST_CSI2_RESETN_ACTIVE (0 << 0) +#define CSI2_HOST_CSI2_RESETN_EXIT BIT(0) + +#define C3_MIPI_CSI2_MAX_WIDTH 2888 +#define C3_MIPI_CSI2_MIN_WIDTH 160 +#define C3_MIPI_CSI2_MAX_HEIGHT 2240 +#define C3_MIPI_CSI2_MIN_HEIGHT 120 +#define C3_MIPI_CSI2_DEFAULT_WIDTH 1920 +#define C3_MIPI_CSI2_DEFAULT_HEIGHT 1080 +#define C3_MIPI_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_SRGGB10_1X10 + +/* C3 CSI-2 pad list */ +enum { + C3_MIPI_CSI2_PAD_SINK, + C3_MIPI_CSI2_PAD_SRC, + C3_MIPI_CSI2_PAD_MAX +}; + +/* + * struct c3_csi_info - MIPI CSI2 information + * + * @clocks: array of MIPI CSI2 clock names + * @clock_num: actual clock number + */ +struct c3_csi_info { + char *clocks[MIPI_CSI2_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/* + * struct c3_csi_device - MIPI CSI2 platform device + * + * @dev: pointer to the struct device + * @aphy: MIPI CSI2 aphy register address + * @dphy: MIPI CSI2 dphy register address + * @host: MIPI CSI2 host register address + * @clks: array of MIPI CSI2 clocks + * @sd: MIPI CSI2 sub-device + * @pads: MIPI CSI2 sub-device pads + * @notifier: notifier to register on the v4l2-async API + * @src_pad: source sub-device pad + * @bus: MIPI CSI2 bus information + * @info: version-specific MIPI CSI2 information + */ +struct c3_csi_device { + struct device *dev; + void __iomem *aphy; + void __iomem *dphy; + void __iomem *host; + struct clk_bulk_data clks[MIPI_CSI2_CLOCK_NUM_MAX]; + + struct v4l2_subdev sd; + struct media_pad pads[C3_MIPI_CSI2_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct media_pad *src_pad; + struct v4l2_mbus_config_mipi_csi2 bus; + + const struct c3_csi_info *info; +}; + +static const u32 c3_mipi_csi_formats[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, +}; + +/* Hardware configuration */ + +static void c3_mipi_csi_write(struct c3_csi_device *csi, u32 reg, u32 val) +{ + void __iomem *addr; + + switch (CSI2_SUBMD(reg)) { + case SUBMD_APHY: + addr = csi->aphy + CSI2_REG_ADDR(reg); + break; + case SUBMD_DPHY: + addr = csi->dphy + CSI2_REG_ADDR(reg); + break; + case SUBMD_HOST: + addr = csi->host + CSI2_REG_ADDR(reg); + break; + default: + dev_err(csi->dev, "Invalid sub-module: %lu\n", CSI2_SUBMD(reg)); + return; + } + + writel(val, addr); +} + +static void c3_mipi_csi_cfg_aphy(struct c3_csi_device *csi) +{ + c3_mipi_csi_write(csi, CSI_PHY_CNTL0, + CSI_PHY_CNTL0_HS_LP_BIAS_EN | + CSI_PHY_CNTL0_HS_RX_TRIM_11 | + CSI_PHY_CNTL0_LP_LOW_VTH_2 | + CSI_PHY_CNTL0_LP_HIGH_VTH_4 | + CSI_PHY_CNTL0_DATA_LANE0_HS_DIG_EN | + CSI_PHY_CNTL0_DATA_LANE1_HS_DIG_EN | + CSI_PHY_CNTL0_CLK0_LANE_HS_DIG_EN | + CSI_PHY_CNTL0_DATA_LANE2_HS_DIG_EN | + CSI_PHY_CNTL0_DATA_LANE3_HS_DIG_EN); + + c3_mipi_csi_write(csi, CSI_PHY_CNTL1, + CSI_PHY_CNTL1_HS_EQ_CAP_SMALL | + CSI_PHY_CNTL1_HS_EQ_RES_MED | + CSI_PHY_CNTL1_CLK_CHN_EQ_MAX_GAIN | + CSI_PHY_CNTL1_DATA_CHN_EQ_MAX_GAIN | + CSI_PHY_CNTL1_COM_BG_EN | + CSI_PHY_CNTL1_HS_SYNC_EN); +} + +static void c3_mipi_csi_cfg_dphy(struct c3_csi_device *csi, s64 rate) +{ + u32 val; + u32 settle; + + /* Calculate the high speed settle */ + val = DIV_ROUND_UP(1000000000, rate); + settle = (16 * val + 230) / 10; + + c3_mipi_csi_write(csi, MIPI_PHY_CLK_LANE_CTRL, + MIPI_PHY_CLK_LANE_CTRL_HS_RX_EN | + MIPI_PHY_CLK_LANE_CTRL_END_EN | + MIPI_PHY_CLK_LANE_CTRL_LPEN_DIS | + MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_EN | + MIPI_PHY_CLK_LANE_CTRL_TCLK_ZERO_HS_8); + + c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_PHY_TCLK_MISS_CYCLES_9); + c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, + MIPI_PHY_TCLK_SETTLE_CYCLES_31); + c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_PHY_THS_EXIT_CYCLES_8); + c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_PHY_THS_SKIP_CYCLES_10); + c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle); + c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_PHY_TINIT_CYCLES_20000); + c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_PHY_TMBIAS_CYCLES_256); + c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_PHY_TULPS_C_CYCLES_4096); + c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_PHY_TULPS_S_CYCLES_256); + c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_PHY_TLP_EN_W_CYCLES_12); + c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_PHY_TLPOK_CYCLES_256); + c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, + MIPI_PHY_TWD_INIT_DOG_0X400000); + c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_PHY_TWD_HS_DOG_0X400000); + + c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL1, + MIPI_PHY_DATA_LANE_CTRL1_INSERT_ERRESC | + MIPI_PHY_DATA_LANE_CTRL1_HS_SYNC_CHK_EN | + MIPI_PHY_DATA_LANE_CTRL1_PIPE_ALL_EN | + MIPI_PHY_DATA_LANE_CTRL1_PIPE_DELAY_3); + + /* Set the order of lanes */ + c3_mipi_csi_write(csi, MIPI_PHY_MUX_CTRL0, + MIPI_PHY_MUX_CTRL0_SFEN3_SRC_LANE3 | + MIPI_PHY_MUX_CTRL0_SFEN2_SRC_LANE2 | + MIPI_PHY_MUX_CTRL0_SFEN1_SRC_LANE1 | + MIPI_PHY_MUX_CTRL0_SFEN0_SRC_LANE0); + + c3_mipi_csi_write(csi, MIPI_PHY_MUX_CTRL1, + MIPI_PHY_MUX_CTRL1_LANE3_SRC_SFEN3 | + MIPI_PHY_MUX_CTRL1_LANE2_SRC_SFEN2 | + MIPI_PHY_MUX_CTRL1_LANE1_SRC_SFEN1 | + MIPI_PHY_MUX_CTRL1_LANE0_SRC_SFEN0); + + /* Enable digital data and clock lanes */ + c3_mipi_csi_write(csi, MIPI_PHY_CTRL, + MIPI_PHY_CTRL_DATA_LANE0_EN | + MIPI_PHY_CTRL_DATA_LANE1_EN | + MIPI_PHY_CTRL_DATA_LANE2_EN | + MIPI_PHY_CTRL_DATA_LANE3_EN | + MIPI_PHY_CTRL_CLOCK_LANE_EN); +} + +static void c3_mipi_csi_cfg_host(struct c3_csi_device *csi) +{ + /* Reset CSI-2 controller output */ + c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, + CSI2_HOST_CSI2_RESETN_ACTIVE); + c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, + CSI2_HOST_CSI2_RESETN_EXIT); + + /* Set data lane number */ + c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, csi->bus.num_data_lanes - 1); +} + +static int c3_mipi_csi_start_stream(struct c3_csi_device *csi, + struct v4l2_subdev *src_sd) +{ + s64 link_freq; + s64 lane_rate; + + link_freq = v4l2_get_link_freq(src_sd->ctrl_handler, 0, 0); + if (link_freq < 0) { + dev_err(csi->dev, + "Unable to obtain link frequency: %lld\n", link_freq); + return link_freq; + } + + lane_rate = link_freq * 2; + if (lane_rate > 1500000000) { + dev_err(csi->dev, "Invalid lane rate: %lld\n", lane_rate); + return -EINVAL; + } + + c3_mipi_csi_cfg_aphy(csi); + c3_mipi_csi_cfg_dphy(csi, lane_rate); + c3_mipi_csi_cfg_host(csi); + + return 0; +} + +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_csi_device *csi = v4l2_get_subdevdata(sd); + struct media_pad *sink_pad; + struct v4l2_subdev *src_sd; + int ret; + + sink_pad = &csi->pads[C3_MIPI_CSI2_PAD_SINK]; + csi->src_pad = media_pad_remote_pad_unique(sink_pad); + if (IS_ERR(csi->src_pad)) { + dev_dbg(csi->dev, "Failed to get source pad for MIPI CSI-2\n"); + return -EPIPE; + } + + src_sd = media_entity_to_v4l2_subdev(csi->src_pad->entity); + + pm_runtime_resume_and_get(csi->dev); + + c3_mipi_csi_start_stream(csi, src_sd); + + ret = v4l2_subdev_enable_streams(src_sd, csi->src_pad->index, BIT(0)); + if (ret) { + pm_runtime_put(csi->dev); + return ret; + } + + return 0; +} + +static int c3_mipi_csi_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_csi_device *csi = v4l2_get_subdevdata(sd); + struct v4l2_subdev *src_sd; + + if (csi->src_pad) { + src_sd = media_entity_to_v4l2_subdev(csi->src_pad->entity); + v4l2_subdev_disable_streams(src_sd, csi->src_pad->index, + BIT(0)); + } + csi->src_pad = NULL; + + pm_runtime_put(csi->dev); + + return 0; +} + +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case C3_MIPI_CSI2_PAD_SINK: + if (code->index >= ARRAY_SIZE(c3_mipi_csi_formats)) + return -EINVAL; + + code->code = c3_mipi_csi_formats[code->index]; + break; + case C3_MIPI_CSI2_PAD_SRC: + struct v4l2_mbus_framefmt *fmt; + + if (code->index) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, code->pad); + code->code = fmt->code; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt; + unsigned int i; + + if (format->pad != C3_MIPI_CSI2_PAD_SINK) + return v4l2_subdev_get_fmt(sd, state, format); + + fmt = v4l2_subdev_state_get_format(state, format->pad); + + for (i = 0; i < ARRAY_SIZE(c3_mipi_csi_formats); i++) { + if (format->format.code == c3_mipi_csi_formats[i]) { + fmt->code = c3_mipi_csi_formats[i]; + break; + } + } + + if (i == ARRAY_SIZE(c3_mipi_csi_formats)) + fmt->code = c3_mipi_csi_formats[0]; + + fmt->width = clamp_t(u32, format->format.width, + C3_MIPI_CSI2_MIN_WIDTH, C3_MIPI_CSI2_MAX_WIDTH); + fmt->height = clamp_t(u32, format->format.height, + C3_MIPI_CSI2_MIN_HEIGHT, C3_MIPI_CSI2_MAX_HEIGHT); + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + format->format = *fmt; + + /* Synchronize the format to source pad */ + fmt = v4l2_subdev_state_get_format(state, C3_MIPI_CSI2_PAD_SRC); + *fmt = format->format; + + return 0; +} + +static int c3_mipi_csi_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, C3_MIPI_CSI2_PAD_SINK); + src_fmt = v4l2_subdev_state_get_format(state, C3_MIPI_CSI2_PAD_SRC); + + sink_fmt->width = C3_MIPI_CSI2_DEFAULT_WIDTH; + sink_fmt->height = C3_MIPI_CSI2_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = C3_MIPI_CSI2_DEFAULT_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + *src_fmt = *sink_fmt; + + return 0; +} + +static const struct v4l2_subdev_pad_ops c3_mipi_csi_pad_ops = { + .enum_mbus_code = c3_mipi_csi_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = c3_mipi_csi_set_fmt, + .enable_streams = c3_mipi_csi_enable_streams, + .disable_streams = c3_mipi_csi_disable_streams, +}; + +static const struct v4l2_subdev_ops c3_mipi_csi_subdev_ops = { + .pad = &c3_mipi_csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops c3_mipi_csi_internal_ops = { + .init_state = c3_mipi_csi_init_state, +}; + +/* Media entity operations */ +static const struct media_entity_operations c3_mipi_csi_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* PM runtime */ + +static int c3_mipi_csi_runtime_suspend(struct device *dev) +{ + struct c3_csi_device *csi = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks); + + return 0; +} + +static int c3_mipi_csi_runtime_resume(struct device *dev) +{ + struct c3_csi_device *csi = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(csi->info->clock_num, csi->clks); +} + +static const struct dev_pm_ops c3_mipi_csi_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + RUNTIME_PM_OPS(c3_mipi_csi_runtime_suspend, + c3_mipi_csi_runtime_resume, NULL) +}; + +/* Probe/remove & platform driver */ + +static int c3_mipi_csi_subdev_init(struct c3_csi_device *csi) +{ + struct v4l2_subdev *sd = &csi->sd; + int ret; + + v4l2_subdev_init(sd, &c3_mipi_csi_subdev_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &c3_mipi_csi_internal_ops; + snprintf(sd->name, sizeof(sd->name), "%s", MIPI_CSI2_SUBDEV_NAME); + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &c3_mipi_csi_entity_ops; + + sd->dev = csi->dev; + v4l2_set_subdevdata(sd, csi); + + csi->pads[C3_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + csi->pads[C3_MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_MIPI_CSI2_PAD_MAX, + csi->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; +} + +static void c3_mipi_csi_subdev_deinit(struct c3_csi_device *csi) +{ + v4l2_subdev_cleanup(&csi->sd); + media_entity_cleanup(&csi->sd.entity); +} + +/* Subdev notifier register */ +static int c3_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct c3_csi_device *csi = v4l2_get_subdevdata(notifier->sd); + struct media_pad *sink = &csi->sd.entity.pads[C3_MIPI_CSI2_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations c3_mipi_csi_notify_ops = { + .bound = c3_mipi_csi_notify_bound, +}; + +static int c3_mipi_csi_async_register(struct c3_csi_device *csi) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + int ret; + + v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_put_handle; + + csi->bus = vep.bus.mipi_csi2; + if (csi->bus.num_data_lanes < 1 || csi->bus.num_data_lanes > 4) { + dev_err(csi->dev, "Unsupported data lanes number\n"); + goto err_put_handle; + } + + asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_put_handle; + } + + csi->notifier.ops = &c3_mipi_csi_notify_ops; + ret = v4l2_async_nf_register(&csi->notifier); + if (ret) + goto err_cleanup_nf; + + ret = v4l2_async_register_subdev(&csi->sd); + if (ret) + goto err_unregister_nf; + + fwnode_handle_put(ep); + + return 0; + +err_unregister_nf: + v4l2_async_nf_unregister(&csi->notifier); +err_cleanup_nf: + v4l2_async_nf_cleanup(&csi->notifier); +err_put_handle: + fwnode_handle_put(ep); + return ret; +} + +static void c3_mipi_csi_async_unregister(struct c3_csi_device *csi) +{ + v4l2_async_unregister_subdev(&csi->sd); + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); +} + +static int c3_mipi_csi_ioremap_resource(struct c3_csi_device *csi) +{ + struct device *dev = csi->dev; + struct platform_device *pdev = to_platform_device(dev); + + csi->aphy = devm_platform_ioremap_resource_byname(pdev, "aphy"); + if (IS_ERR(csi->aphy)) + return PTR_ERR(csi->aphy); + + csi->dphy = devm_platform_ioremap_resource_byname(pdev, "dphy"); + if (IS_ERR(csi->dphy)) + return PTR_ERR(csi->dphy); + + csi->host = devm_platform_ioremap_resource_byname(pdev, "host"); + if (IS_ERR(csi->host)) + return PTR_ERR(csi->host); + + return 0; +} + +static int c3_mipi_csi_get_clocks(struct c3_csi_device *csi) +{ + const struct c3_csi_info *info = csi->info; + + for (unsigned int i = 0; i < info->clock_num; i++) + csi->clks[i].id = info->clocks[i]; + + return devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks); +} + +static int c3_mipi_csi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct c3_csi_device *csi; + int ret; + + csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + + csi->info = of_device_get_match_data(dev); + csi->dev = dev; + + ret = c3_mipi_csi_ioremap_resource(csi); + if (ret) + return dev_err_probe(dev, ret, "Failed to ioremap resource\n"); + + ret = c3_mipi_csi_get_clocks(csi); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + platform_set_drvdata(pdev, csi); + + pm_runtime_enable(dev); + + ret = c3_mipi_csi_subdev_init(csi); + if (ret) + goto err_disable_runtime_pm; + + ret = c3_mipi_csi_async_register(csi); + if (ret) + goto err_deinit_subdev; + + return 0; + +err_deinit_subdev: + c3_mipi_csi_subdev_deinit(csi); +err_disable_runtime_pm: + pm_runtime_disable(dev); + return ret; +}; + +static void c3_mipi_csi_remove(struct platform_device *pdev) +{ + struct c3_csi_device *csi = platform_get_drvdata(pdev); + + c3_mipi_csi_async_unregister(csi); + c3_mipi_csi_subdev_deinit(csi); + + pm_runtime_disable(&pdev->dev); +}; + +static const struct c3_csi_info c3_mipi_csi_info = { + .clocks = {"vapb", "phy0"}, + .clock_num = 2 +}; + +static const struct of_device_id c3_mipi_csi_of_match[] = { + { + .compatible = "amlogic,c3-mipi-csi2", + .data = &c3_mipi_csi_info, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, c3_mipi_csi_of_match); + +static struct platform_driver c3_mipi_csi_driver = { + .probe = c3_mipi_csi_probe, + .remove = c3_mipi_csi_remove, + .driver = { + .name = "c3-mipi-csi2", + .of_match_table = c3_mipi_csi_of_match, + .pm = pm_ptr(&c3_mipi_csi_pm_ops), + }, +}; + +module_platform_driver(c3_mipi_csi_driver); + +MODULE_AUTHOR("Keke Li "); +MODULE_DESCRIPTION("Amlogic C3 MIPI CSI-2 receiver"); +MODULE_LICENSE("GPL"); From patchwork Fri Dec 27 07:09:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 854091 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E45713DDD3; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=hnkUyexchG4fv4oRC7J971D13QCIGRdT0kg8VnPyVzB4M2fg9DxY9O8jBIoc4Bx15QdQMWCGTWyV75f9yQsQBb5Qc4HgIDyObuNkzMclUQrtpd7m0Nh5nte0Oeyb2VIs1/cejwghgeyWwsY+KZA4iasNPjY/+7v0Ch2bEA2VZ58= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=WVyXGXDLn1agM04aAKCvM95ighMkwQ+w3r3BgWh/q20=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=XMBaNIM0cIWR/8SGRD5krC3X/ymI5uJcChygHB3gRt3L+5bbowIuRzMDotkAp+kyQmAPcOC/3e/k0rPTECkAxWNx2pZbPnVSTRfJbbi5kbcRsVfHuWQy9RR6FwyAFFzlGadnjfC/zlgz0n0w1jv7eGyQT3jdG2+/eGci3cVChZc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=A/iVW3Rg; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="A/iVW3Rg" Received: by smtp.kernel.org (Postfix) with ESMTPS id 10BB0C4CEE0; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=WVyXGXDLn1agM04aAKCvM95ighMkwQ+w3r3BgWh/q20=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=A/iVW3RgK+v089uXeJh5QayaYkPJMhhDSvJPIZjQQR9B/2GXOBBuIGcubG6mLOTmD BL3QSKfPdumxOfvAyVI9fi7ASZKNVxMaRrLt7/tbpvyjm7ykuwQkhTOQB/NCyEs/oD ZuKicQVHMkAYeGdD8U3rYC+R1HLSJ3fwYS4iYv0G7d8OLFIAG04514ErPofZ8+p5Ps QaXpKvKCb7io5B9Xt4ZB3oXtcVEQvJZuR824FFClsGxesqrypNzbox7cshhB0X5+/k RrxDrBx1DeGFkdqCAXat88ViXguejCTygudp/tXrDDCn4jmyDtV6qxzTQ4nWxa39ws rv27mDfd93Uuw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0695CE7718F; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:12 +0800 Subject: [PATCH v5 03/10] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-3-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li , Krzysztof Kozlowski X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=4070; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=o6Q1zuPq4y0enxVfoRdd9v721QVLXCRWjLLI5Pa3BnE=; b=zRJP4N49WZCSmXx7+8JnJ3u/fiteMZz6eT8suUZiULry8J6plAJ/7k7iWj94LpedtQtyCqZVq x89YGlhcCvHAPRd0X9uuNIX7z0/y5wskr3QP86gKd8sBKDmLUsflF70 X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li c3-mipi-adapter is used to organize mipi data and send raw data to ISP module. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Keke Li --- .../bindings/media/amlogic,c3-mipi-adapter.yaml | 115 +++++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 121 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml new file mode 100644 index 000000000000..1105fee2d7a8 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,c3-mipi-adapter.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic C3 MIPI adapter receiver + +maintainers: + - Keke Li + +description: + MIPI adapter is used to convert the MIPI CSI-2 data + into an ISP supported data format. + +properties: + compatible: + enum: + - amlogic,c3-mipi-adapter + + reg: + maxItems: 3 + + reg-names: + items: + - const: top + - const: fd + - const: rd + + power-domains: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: vapb + - const: isp0 + + assigned-clocks: true + + assigned-clock-rates: true + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: input port node. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: output port node. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reg-names + - power-domains + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + adap: adap@ff010000 { + compatible = "amlogic,c3-mipi-adapter"; + reg = <0x0 0xff010000 0x0 0x100>, + <0x0 0xff01b000 0x0 0x100>, + <0x0 0xff01d000 0x0 0x200>; + reg-names = "top", "fd", "rd"; + power-domains = <&pwrc PWRC_C3_ISP_TOP_ID>; + clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_ISP0>; + clock-names = "vapb", "isp0"; + assigned-clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_ISP0>; + assigned-clock-rates = <0>, <400000000>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + c3_adap_in: endpoint { + remote-endpoint = <&c3_mipi_csi_out>; + }; + }; + + port@1 { + reg = <1>; + c3_adap_out: endpoint { + remote-endpoint = <&c3_isp_in>; + }; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 1d1416b15570..af4239f2f3fc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1243,6 +1243,12 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC MIPI ADAPTER DRIVER +M: Keke Li +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml + AMLOGIC MIPI CSI2 DRIVER M: Keke Li L: linux-media@vger.kernel.org From patchwork Fri Dec 27 07:09:13 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 853962 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E4A013E02D; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=Z2RaYuPUAq6MzRPG9qTOQxAUPsBGAgOP7Du/ajT09fZuQfjRzZvsibNECwOqY2HdbC8FbrQze0xT9k4C7CXdiPM0hjKTJZtNihQ9jqHTY3rhA481J5c9lQdcKfkac9cuxBTk4D6zg4XuTTJarn3QsiLMRuIQ4ckKRwEk6ummIMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=URl5T14kJtAG7gkdvxzpTmTb0KtReOamQ8Lr/8OZ8XE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=iMRqPppqxMXl8bMw5cUhm5EpL9Ty0ZE99AbfNERbPMOoeUySekF0hIB/03F3OM2Gt9iUXyrkfQnWMNjO43c5yGV8RdKGCsHsMUK1tc1WM993kjV5YclXibckuzBonsdLXZUfrH9AYRHQ1yCcA4TBIwTSwOh6dN/iTxvq5gVKuv0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=qvY2QbsP; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="qvY2QbsP" Received: by smtp.kernel.org (Postfix) with ESMTPS id 2EC5AC4CEE4; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=URl5T14kJtAG7gkdvxzpTmTb0KtReOamQ8Lr/8OZ8XE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=qvY2QbsPOFduUEA10Gp/7OU+JkyB1Fmo4xFDsPGPIBWABWFkmvZT31eSBmOUYRF1o 4VekWbn7FTjwjWq9rFQCWVJ/XcKJEFnryC90w1cnw178RpfqM+AqeOguAp2eVPx+An giPbUQjIYY8qApHUZz07xc9e7W95lG2MQzQlDHgjFiBvQfFlxsQJKuaRXFZDFRlZG/ veTMUGqYLaW+gdQhrbk8XzFVVxTqoibgHEhGqjawQ0cp6e+0H2Wh9MvLwK3jp0l2+X mA4BQYeZCAkl19OOnnA50HiZb8GH2iidGDsp9d2a/XoU6MNOF5I21KopP/FZ11RYq+ 1rrTYGJ9DfG+A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 24AD4E7718B; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:13 +0800 Subject: [PATCH v5 04/10] media: platform: Add C3 MIPI adapter driver Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-4-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=30152; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=tZSuxaVUhW57c19pk6NzNOmWejmfK8vKSJaj5NDbQXM=; b=6j8RJW3Hx3C+LDNMlXp5eTrr5mT0ea7nYdIHuXehv/CGuSyNgBiiSvAAVP8US3v/f5t0jZ7Ez nk27FOzE6huAA/XZM6Ypdt/Z9gsm7TqAMytpKd2+kjcnpHB1cmmO7Td X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add a driver for the MIPI adapter unit found on the Amlogic C3 SoC. This driver is used to align the MIPI data from the MIPI CSI-2 receiver unit and send the aligned data to the ISP unit. Reviewed-by: Daniel Scally Signed-off-by: Keke Li --- MAINTAINERS | 1 + drivers/media/platform/amlogic/c3/Kconfig | 1 + drivers/media/platform/amlogic/c3/Makefile | 1 + .../media/platform/amlogic/c3/mipi-adapter/Kconfig | 16 + .../platform/amlogic/c3/mipi-adapter/Makefile | 3 + .../amlogic/c3/mipi-adapter/c3-mipi-adap.c | 829 +++++++++++++++++++++ 6 files changed, 851 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index af4239f2f3fc..2ef79d99e088 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,6 +1248,7 @@ M: Keke Li L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml +F: drivers/media/platform/amlogic/c3/mipi-adapter/ AMLOGIC MIPI CSI2 DRIVER M: Keke Li diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig index 098d458747b8..a504a1eb22e6 100644 --- a/drivers/media/platform/amlogic/c3/Kconfig +++ b/drivers/media/platform/amlogic/c3/Kconfig @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only +source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig" source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig" diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile index a468fb782f94..770b2a2903ad 100644 --- a/drivers/media/platform/amlogic/c3/Makefile +++ b/drivers/media/platform/amlogic/c3/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-y += mipi-adapter/ obj-y += mipi-csi2/ diff --git a/drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig new file mode 100644 index 000000000000..bf19059b3543 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_C3_MIPI_ADAPTER + tristate "Amlogic C3 MIPI adapter" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 driver for Amlogic C3 MIPI adapter. + C3 MIPI adapter mainly responsible for organizing + MIPI data and sending raw data to ISP pipeline. + + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/c3/mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3/mipi-adapter/Makefile new file mode 100644 index 000000000000..216fc310c5b4 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-adapter/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o diff --git a/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c new file mode 100644 index 000000000000..4ae6e2e76138 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* + * Adapter Block Diagram + * --------------------- + * + * +--------------------------------------------+ + * | Adapter | + * |--------------------------------------------| + * +------------+ | | | | | +-----+ + * | MIPI CSI-2 |--->| Frontend -> DDR_RD0 -> PIXEL0 -> ALIGNMENT |--->| ISP | + * +------------+ | | | | | +-----+ + * +--------------------------------------------+ + * + */ + +/* C3 adapter submodule definition */ +enum { + SUBMD_TOP, + SUBMD_FD, + SUBMD_RD, +}; + +#define ADAP_SUBMD_MASK GENMASK(17, 16) +#define ADAP_SUBMD_SHIFT 16 +#define ADAP_SUBMD(x) (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT)) +#define ADAP_REG_ADDR_MASK GENMASK(15, 0) +#define ADAP_REG_ADDR(x) ((x) & (ADAP_REG_ADDR_MASK)) +#define ADAP_REG_T(x) ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x)) +#define ADAP_REG_F(x) ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x)) +#define ADAP_REG_R(x) ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x)) + +#define MIPI_ADAP_CLOCK_NUM_MAX 3 +#define MIPI_ADAP_SUBDEV_NAME "c3-mipi-adapter" + +/* C3 MIPI adapter TOP register */ +#define MIPI_ADAPT_DE_CTRL0 ADAP_REG_T(0x40) +#define MIPI_ADAPT_DE_CTRL0_RD_BUS_BYPASS_MASK BIT(3) +#define MIPI_ADAPT_DE_CTRL0_RD_BUS_BYPASS_EN BIT(3) +#define MIPI_ADAPT_DE_CTRL0_RD_BUS_BYPASS_DIS (0 << 3) +#define MIPI_ADAPT_DE_CTRL0_WR_BUS_BYPASS_MASK BIT(7) +#define MIPI_ADAPT_DE_CTRL0_WR_BUS_BYPASS_EN BIT(7) +#define MIPI_ADAPT_DE_CTRL0_WR_BUS_BYPASS_DIS (0 << 7) + +/* C3 MIPI adapter FRONTEND register */ +#define CSI2_CLK_RESET ADAP_REG_F(0x00) +#define CSI2_CLK_RESET_SW_RESET_MASK BIT(0) +#define CSI2_CLK_RESET_SW_RESET_APPLY BIT(0) +#define CSI2_CLK_RESET_SW_RESET_RELEASE (0 << 0) +#define CSI2_CLK_RESET_CLK_ENABLE_MASK BIT(1) +#define CSI2_CLK_RESET_CLK_ENABLE_EN BIT(1) +#define CSI2_CLK_RESET_CLK_ENABLE_DIS (0 << 1) + +#define CSI2_GEN_CTRL0 ADAP_REG_F(0x04) +#define CSI2_GEN_CTRL0_VC0_MASK BIT(0) +#define CSI2_GEN_CTRL0_VC0_EN BIT(0) +#define CSI2_GEN_CTRL0_VC0_DIS (0 << 0) +#define CSI2_GEN_CTRL0_ENABLE_PACKETS_MASK GENMASK(20, 16) +#define CSI2_GEN_CTRL0_ENABLE_PACKETS_RAW BIT(16) +#define CSI2_GEN_CTRL0_ENABLE_PACKETS_YUV (2 << 16) + +#define CSI2_X_START_END_ISP ADAP_REG_F(0x0c) +#define CSI2_X_START_END_ISP_X_START_MASK GENMASK(15, 0) +#define CSI2_X_START_END_ISP_X_START(x) ((x) << 0) +#define CSI2_X_START_END_ISP_X_END_MASK GENMASK(31, 16) +#define CSI2_X_START_END_ISP_X_END(x) (((x) - 1) << 16) + +#define CSI2_Y_START_END_ISP ADAP_REG_F(0x10) +#define CSI2_Y_START_END_ISP_Y_START_MASK GENMASK(15, 0) +#define CSI2_Y_START_END_ISP_Y_START(x) ((x) << 0) +#define CSI2_Y_START_END_ISP_Y_END_MASK GENMASK(31, 16) +#define CSI2_Y_START_END_ISP_Y_END(x) (((x) - 1) << 16) + +#define CSI2_VC_MODE ADAP_REG_F(0x1c) +#define CSI2_VC_MODE_VS_ISP_SEL_VC_MASK GENMASK(19, 16) +#define CSI2_VC_MODE_VS_ISP_SEL_VC_0 BIT(16) +#define CSI2_VC_MODE_VS_ISP_SEL_VC_1 (2 << 16) +#define CSI2_VC_MODE_VS_ISP_SEL_VC_2 (4 << 16) +#define CSI2_VC_MODE_VS_ISP_SEL_VC_3 (8 << 16) +#define CSI2_VC_MODE_HS_ISP_SEL_VC_MASK GENMASK(23, 20) +#define CSI2_VC_MODE_HS_ISP_SEL_VC_0 BIT(20) +#define CSI2_VC_MODE_HS_ISP_SEL_VC_1 (2 << 20) +#define CSI2_VC_MODE_HS_ISP_SEL_VC_2 (4 << 20) +#define CSI2_VC_MODE_HS_ISP_SEL_VC_3 (8 << 20) + +/* C3 MIPI adapter READER register */ +#define MIPI_ADAPT_DDR_RD0_CNTL0 ADAP_REG_R(0x00) +#define MIPI_ADAPT_DDR_RD0_CNTL0_MODULE_EN_MASK BIT(0) +#define MIPI_ADAPT_DDR_RD0_CNTL0_MODULE_EN BIT(0) +#define MIPI_ADAPT_DDR_RD0_CNTL0_MODULE_DIS (0 << 0) + +#define MIPI_ADAPT_DDR_RD0_CNTL1 ADAP_REG_R(0x04) +#define MIPI_ADAPT_DDR_RD0_CNTL1_PORT_SEL_MASK GENMASK(31, 30) +#define MIPI_ADAPT_DDR_RD0_CNTL1_PORT_SEL_DIRECT_MODE (0 << 30) +#define MIPI_ADAPT_DDR_RD0_CNTL1_PORT_SEL_DDR_MODE BIT(30) + +#define MIPI_ADAPT_PIXEL0_CNTL0 ADAP_REG_R(0x80) +#define MIPI_ADAPT_PIXEL0_CNTL0_WORK_MODE_MASK GENMASK(17, 16) +#define MIPI_ADAPT_PIXEL0_CNTL0_WORK_MODE_RAW_DDR (0 << 16) +#define MIPI_ADAPT_PIXEL0_CNTL0_WORK_MODE_RAW_DIRECT BIT(16) +#define MIPI_ADAPT_PIXEL0_CNTL0_DATA_TYPE_MASK GENMASK(25, 20) +#define MIPI_ADAPT_PIXEL0_CNTL0_DATA_TYPE(x) ((x) << 20) +#define MIPI_ADAPT_PIXEL0_CNTL0_START_EN_MASK BIT(31) +#define MIPI_ADAPT_PIXEL0_CNTL0_START_EN BIT(31) + +#define MIPI_ADAPT_ALIG_CNTL0 ADAP_REG_R(0x100) +#define MIPI_ADAPT_ALIG_CNTL0_H_NUM_MASK GENMASK(15, 0) +#define MIPI_ADAPT_ALIG_CNTL0_H_NUM(x) ((x) << 0) +#define MIPI_ADAPT_ALIG_CNTL0_V_NUM_MASK GENMASK(31, 16) +#define MIPI_ADAPT_ALIG_CNTL0_V_NUM(x) ((x) << 16) + +#define MIPI_ADAPT_ALIG_CNTL1 ADAP_REG_R(0x104) +#define MIPI_ADAPT_ALIG_CNTL1_HPE_NUM_MASK GENMASK(31, 16) +#define MIPI_ADAPT_ALIG_CNTL1_HPE_NUM(x) ((x) << 16) + +#define MIPI_ADAPT_ALIG_CNTL2 ADAP_REG_R(0x108) +#define MIPI_ADAPT_ALIG_CNTL2_VPE_NUM_MASK GENMASK(31, 16) +#define MIPI_ADAPT_ALIG_CNTL2_VPE_NUM(x) ((x) << 16) + +#define MIPI_ADAPT_ALIG_CNTL6 ADAP_REG_R(0x118) +#define MIPI_ADAPT_ALIG_CNTL6_PATH0_EN_MASK BIT(0) +#define MIPI_ADAPT_ALIG_CNTL6_PATH0_EN BIT(0) +#define MIPI_ADAPT_ALIG_CNTL6_PATH0_DIS (0 << 0) +#define MIPI_ADAPT_ALIG_CNTL6_PIX0_DATA_MODE_MASK BIT(4) +#define MIPI_ADAPT_ALIG_CNTL6_PIX0_DATA_MODE_DDR (0 << 4) +#define MIPI_ADAPT_ALIG_CNTL6_PIX0_DATA_MODE_DIRECT BIT(4) +#define MIPI_ADAPT_ALIG_CNTL6_DATA0_EN_MASK BIT(12) +#define MIPI_ADAPT_ALIG_CNTL6_DATA0_EN BIT(12) +#define MIPI_ADAPT_ALIG_CNTL6_DATA0_DIS (0 << 12) + +#define MIPI_ADAPT_ALIG_CNTL8 ADAP_REG_R(0x120) +#define MIPI_ADAPT_ALIG_CNTL8_FRMAE_CONTINUE_MASK BIT(5) +#define MIPI_ADAPT_ALIG_CNTL8_FRMAE_CONTINUE_EN BIT(5) +#define MIPI_ADAPT_ALIG_CNTL8_FRMAE_CONTINUE_DIS (0 << 5) +#define MIPI_ADAPT_ALIG_CNTL8_EXCEED_DIS_MASK BIT(12) +#define MIPI_ADAPT_ALIG_CNTL8_EXCEED_HOLD (0 << 12) +#define MIPI_ADAPT_ALIG_CNTL8_EXCEED_NOT_HOLD BIT(12) +#define MIPI_ADAPT_ALIG_CNTL8_START_EN_MASK BIT(31) +#define MIPI_ADAPT_ALIG_CNTL8_START_EN BIT(31) + +#define MIPI_ADAP_MAX_WIDTH 2888 +#define MIPI_ADAP_MIN_WIDTH 160 +#define MIPI_ADAP_MAX_HEIGHT 2240 +#define MIPI_ADAP_MIN_HEIGHT 120 +#define MIPI_ADAP_DEFAULT_WIDTH 1920 +#define MIPI_ADAP_DEFAULT_HEIGHT 1080 +#define MIPI_ADAP_DEFAULT_FMT MEDIA_BUS_FMT_SRGGB10_1X10 + +/* C3 MIPI adapter pad list */ +enum { + C3_MIPI_ADAP_PAD_SINK, + C3_MIPI_ADAP_PAD_SRC, + C3_MIPI_ADAP_PAD_MAX +}; + +/* + * struct c3_adap_info - mipi adapter information + * + * @clocks: array of mipi adapter clock names + * @clock_num: actual clock number + */ +struct c3_adap_info { + char *clocks[MIPI_ADAP_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/* + * struct c3_adap_device - mipi adapter platform device + * + * @dev: pointer to the struct device + * @top: mipi adapter top register address + * @fd: mipi adapter frontend register address + * @rd: mipi adapter reader register address + * @clks: array of MIPI adapter clocks + * @sd: mipi adapter sub-device + * @pads: mipi adapter sub-device pads + * @notifier: notifier to register on the v4l2-async API + * @src_sd: source sub-device pad + * @info: version-specific MIPI adapter information + */ +struct c3_adap_device { + struct device *dev; + void __iomem *top; + void __iomem *fd; + void __iomem *rd; + struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX]; + + struct v4l2_subdev sd; + struct media_pad pads[C3_MIPI_ADAP_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct media_pad *src_pad; + + const struct c3_adap_info *info; +}; + +/* Format helpers */ + +struct c3_adap_pix_format { + u32 code; + u8 type; +}; + +static const struct c3_adap_pix_format c3_mipi_adap_formats[] = { + { MEDIA_BUS_FMT_SBGGR10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, MIPI_CSI2_DT_RAW12 }, +}; + +static const struct c3_adap_pix_format *c3_mipi_adap_find_format(u32 code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++) + if (code == c3_mipi_adap_formats[i].code) + return &c3_mipi_adap_formats[i]; + + return NULL; +} + +/* Hardware configuration */ + +static void c3_mipi_adap_update_bits(struct c3_adap_device *adap, u32 reg, + u32 mask, u32 val) +{ + void __iomem *addr; + u32 orig, tmp; + + switch (ADAP_SUBMD(reg)) { + case SUBMD_TOP: + addr = adap->top + ADAP_REG_ADDR(reg); + break; + case SUBMD_FD: + addr = adap->fd + ADAP_REG_ADDR(reg); + break; + case SUBMD_RD: + addr = adap->rd + ADAP_REG_ADDR(reg); + break; + default: + dev_err(adap->dev, + "Invalid sub-module: %lu\n", ADAP_SUBMD(reg)); + return; + } + + orig = readl(addr); + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + writel(tmp, addr); +} + +/* Configure adapter top sub module */ +static void c3_mipi_adap_cfg_top(struct c3_adap_device *adap) +{ + /* Bypass decompress */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0, + MIPI_ADAPT_DE_CTRL0_RD_BUS_BYPASS_MASK, + MIPI_ADAPT_DE_CTRL0_RD_BUS_BYPASS_EN); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0, + MIPI_ADAPT_DE_CTRL0_WR_BUS_BYPASS_MASK, + MIPI_ADAPT_DE_CTRL0_WR_BUS_BYPASS_EN); +} + +/* Configure adapter frontend sub module */ +static void c3_mipi_adap_cfg_frontend(struct c3_adap_device *adap, + struct v4l2_mbus_framefmt *fmt) +{ + /* Reset frontend module */ + c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, + CSI2_CLK_RESET_SW_RESET_MASK, + CSI2_CLK_RESET_SW_RESET_APPLY); + c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, + CSI2_CLK_RESET_SW_RESET_MASK, + CSI2_CLK_RESET_SW_RESET_RELEASE); + c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, + CSI2_CLK_RESET_CLK_ENABLE_MASK, + CSI2_CLK_RESET_CLK_ENABLE_EN); + + c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, + CSI2_X_START_END_ISP_X_START_MASK, + CSI2_X_START_END_ISP_X_START(0)); + c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, + CSI2_X_START_END_ISP_X_END_MASK, + CSI2_X_START_END_ISP_X_END(fmt->width)); + + c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, + CSI2_Y_START_END_ISP_Y_START_MASK, + CSI2_Y_START_END_ISP_Y_START(0)); + c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, + CSI2_Y_START_END_ISP_Y_END_MASK, + CSI2_Y_START_END_ISP_Y_END(fmt->height)); + + /* Select VS and HS signal for direct path */ + c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, + CSI2_VC_MODE_VS_ISP_SEL_VC_MASK, + CSI2_VC_MODE_VS_ISP_SEL_VC_0); + c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, + CSI2_VC_MODE_HS_ISP_SEL_VC_MASK, + CSI2_VC_MODE_HS_ISP_SEL_VC_0); + + /* Enable to receive RAW packet */ + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, + CSI2_GEN_CTRL0_ENABLE_PACKETS_MASK, + CSI2_GEN_CTRL0_ENABLE_PACKETS_RAW); + + /* Enable virtual channel 0 */ + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, + CSI2_GEN_CTRL0_VC0_MASK, + CSI2_GEN_CTRL0_VC0_EN); +} + +static void c3_mipi_adap_cfg_rd0(struct c3_adap_device *adap) +{ + /* Select direct mode for DDR_RD0 mode */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, + MIPI_ADAPT_DDR_RD0_CNTL1_PORT_SEL_MASK, + MIPI_ADAPT_DDR_RD0_CNTL1_PORT_SEL_DIRECT_MODE); + + /* Data can't bypass DDR_RD0 in direct mode, so enable DDR_RD0 here */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, + MIPI_ADAPT_DDR_RD0_CNTL0_MODULE_EN_MASK, + MIPI_ADAPT_DDR_RD0_CNTL0_MODULE_EN); +} + +static void c3_mipi_adap_cfg_pixel0(struct c3_adap_device *adap, + struct v4l2_mbus_framefmt *fmt) +{ + const struct c3_adap_pix_format *pix; + + pix = c3_mipi_adap_find_format(fmt->code); + + /* Set work mode and data type for PIXEL0 module */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, + MIPI_ADAPT_PIXEL0_CNTL0_WORK_MODE_MASK, + MIPI_ADAPT_PIXEL0_CNTL0_WORK_MODE_RAW_DIRECT); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, + MIPI_ADAPT_PIXEL0_CNTL0_DATA_TYPE_MASK, + MIPI_ADAPT_PIXEL0_CNTL0_DATA_TYPE(pix->type)); + + /* Start PIXEL0 module */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, + MIPI_ADAPT_PIXEL0_CNTL0_START_EN_MASK, + MIPI_ADAPT_PIXEL0_CNTL0_START_EN); +} + +static void c3_mipi_adap_cfg_alig(struct c3_adap_device *adap, + struct v4l2_mbus_framefmt *fmt) +{ + /* + * ISP hardware requires the number of horizonal blanks greater than + * 64 cycles, so adding 64 here. + */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, + MIPI_ADAPT_ALIG_CNTL0_H_NUM_MASK, + MIPI_ADAPT_ALIG_CNTL0_H_NUM(fmt->width + 64)); + + /* + * ISP hardware requires the number of vertical blanks greater than + * 40 lines, so adding 40 here. + */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, + MIPI_ADAPT_ALIG_CNTL0_V_NUM_MASK, + MIPI_ADAPT_ALIG_CNTL0_V_NUM(fmt->height + 40)); + + /* End pixel in a line */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, + MIPI_ADAPT_ALIG_CNTL1_HPE_NUM_MASK, + MIPI_ADAPT_ALIG_CNTL1_HPE_NUM(fmt->width)); + + /* End line in a frame */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, + MIPI_ADAPT_ALIG_CNTL2_VPE_NUM_MASK, + MIPI_ADAPT_ALIG_CNTL2_VPE_NUM(fmt->height)); + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + MIPI_ADAPT_ALIG_CNTL6_PATH0_EN_MASK, + MIPI_ADAPT_ALIG_CNTL6_PATH0_EN); + + /* Select direct mode for ALIG module */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + MIPI_ADAPT_ALIG_CNTL6_PIX0_DATA_MODE_MASK, + MIPI_ADAPT_ALIG_CNTL6_PIX0_DATA_MODE_DIRECT); + + /* Enable to send raw data */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + MIPI_ADAPT_ALIG_CNTL6_DATA0_EN_MASK, + MIPI_ADAPT_ALIG_CNTL6_DATA0_EN); + + /* Set continue mode and disable hold counter */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + MIPI_ADAPT_ALIG_CNTL8_FRMAE_CONTINUE_MASK, + MIPI_ADAPT_ALIG_CNTL8_FRMAE_CONTINUE_EN); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + MIPI_ADAPT_ALIG_CNTL8_EXCEED_DIS_MASK, + MIPI_ADAPT_ALIG_CNTL8_EXCEED_NOT_HOLD); + + /* Start ALIG module */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + MIPI_ADAPT_ALIG_CNTL8_START_EN_MASK, + MIPI_ADAPT_ALIG_CNTL8_START_EN); +} + +/* V4L2 subdev operations */ + +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_adap_device *adap = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *fmt; + struct media_pad *sink_pad; + struct v4l2_subdev *src_sd; + int ret; + + sink_pad = &adap->pads[C3_MIPI_ADAP_PAD_SINK]; + adap->src_pad = media_pad_remote_pad_unique(sink_pad); + if (IS_ERR(adap->src_pad)) { + dev_dbg(adap->dev, "Failed to get source pad for MIPI adap\n"); + return -EPIPE; + } + + src_sd = media_entity_to_v4l2_subdev(adap->src_pad->entity); + + pm_runtime_resume_and_get(adap->dev); + + fmt = v4l2_subdev_state_get_format(state, C3_MIPI_ADAP_PAD_SINK); + + c3_mipi_adap_cfg_top(adap); + c3_mipi_adap_cfg_frontend(adap, fmt); + c3_mipi_adap_cfg_rd0(adap); + c3_mipi_adap_cfg_pixel0(adap, fmt); + c3_mipi_adap_cfg_alig(adap, fmt); + + ret = v4l2_subdev_enable_streams(src_sd, adap->src_pad->index, BIT(0)); + if (ret) { + pm_runtime_put(adap->dev); + return ret; + } + + return 0; +} + +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_adap_device *adap = v4l2_get_subdevdata(sd); + struct v4l2_subdev *src_sd; + + if (adap->src_pad) { + src_sd = media_entity_to_v4l2_subdev(adap->src_pad->entity); + v4l2_subdev_disable_streams(src_sd, adap->src_pad->index, + BIT(0)); + } + adap->src_pad = NULL; + + pm_runtime_put(adap->dev); + + return 0; +} + +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case C3_MIPI_ADAP_PAD_SINK: + if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats)) + return -EINVAL; + + code->code = c3_mipi_adap_formats[code->index].code; + break; + case C3_MIPI_ADAP_PAD_SRC: + struct v4l2_mbus_framefmt *fmt; + + if (code->index) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, code->pad); + code->code = fmt->code; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt; + const struct c3_adap_pix_format *pix_format; + + if (format->pad != C3_MIPI_ADAP_PAD_SINK) + return v4l2_subdev_get_fmt(sd, state, format); + + pix_format = c3_mipi_adap_find_format(format->format.code); + if (!pix_format) + pix_format = &c3_mipi_adap_formats[0]; + + fmt = v4l2_subdev_state_get_format(state, format->pad); + fmt->code = pix_format->code; + fmt->width = clamp_t(u32, format->format.width, + MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH); + fmt->height = clamp_t(u32, format->format.height, + MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT); + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + format->format = *fmt; + + /* Synchronize the format to source pad */ + fmt = v4l2_subdev_state_get_format(state, C3_MIPI_ADAP_PAD_SRC); + *fmt = format->format; + + return 0; +} + +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, C3_MIPI_ADAP_PAD_SINK); + src_fmt = v4l2_subdev_state_get_format(state, C3_MIPI_ADAP_PAD_SRC); + + sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH; + sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MIPI_ADAP_DEFAULT_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + *src_fmt = *sink_fmt; + + return 0; +} + +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = { + .enum_mbus_code = c3_mipi_adap_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = c3_mipi_adap_set_fmt, + .enable_streams = c3_mipi_adap_enable_streams, + .disable_streams = c3_mipi_adap_disable_streams, +}; + +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = { + .pad = &c3_mipi_adap_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = { + .init_state = c3_mipi_adap_init_state, +}; + +/* Media entity operations */ +static const struct media_entity_operations c3_mipi_adap_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* PM runtime */ + +static int c3_mipi_adap_runtime_suspend(struct device *dev) +{ + struct c3_adap_device *adap = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks); + + return 0; +} + +static int c3_mipi_adap_runtime_resume(struct device *dev) +{ + struct c3_adap_device *adap = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks); +} + +static const struct dev_pm_ops c3_mipi_adap_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend, + c3_mipi_adap_runtime_resume, NULL) +}; + +/* Probe/remove & platform driver */ + +static int c3_mipi_adap_subdev_init(struct c3_adap_device *adap) +{ + struct v4l2_subdev *sd = &adap->sd; + int ret; + + v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &c3_mipi_adap_internal_ops; + snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME); + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &c3_mipi_adap_entity_ops; + + sd->dev = adap->dev; + v4l2_set_subdevdata(sd, adap); + + adap->pads[C3_MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + adap->pads[C3_MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_MIPI_ADAP_PAD_MAX, + adap->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; +} + +static void c3_mipi_adap_subdev_deinit(struct c3_adap_device *adap) +{ + v4l2_subdev_cleanup(&adap->sd); + media_entity_cleanup(&adap->sd.entity); +} + +/* Subdev notifier register */ +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct c3_adap_device *adap = v4l2_get_subdevdata(notifier->sd); + struct media_pad *sink = &adap->sd.entity.pads[C3_MIPI_ADAP_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = { + .bound = c3_mipi_adap_notify_bound, +}; + +static int c3_mipi_adap_async_register(struct c3_adap_device *adap) +{ + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + int ret; + + v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_put_handle; + } + + adap->notifier.ops = &c3_mipi_adap_notify_ops; + ret = v4l2_async_nf_register(&adap->notifier); + if (ret) + goto err_cleanup_nf; + + ret = v4l2_async_register_subdev(&adap->sd); + if (ret) + goto err_unregister_nf; + + fwnode_handle_put(ep); + + return 0; + +err_unregister_nf: + v4l2_async_nf_unregister(&adap->notifier); +err_cleanup_nf: + v4l2_async_nf_cleanup(&adap->notifier); +err_put_handle: + fwnode_handle_put(ep); + return ret; +} + +static void c3_mipi_adap_async_unregister(struct c3_adap_device *adap) +{ + v4l2_async_unregister_subdev(&adap->sd); + v4l2_async_nf_unregister(&adap->notifier); + v4l2_async_nf_cleanup(&adap->notifier); +} + +static int c3_mipi_adap_ioremap_resource(struct c3_adap_device *adap) +{ + struct device *dev = adap->dev; + struct platform_device *pdev = to_platform_device(dev); + + adap->top = devm_platform_ioremap_resource_byname(pdev, "top"); + if (IS_ERR(adap->top)) + return PTR_ERR(adap->top); + + adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd"); + if (IS_ERR(adap->fd)) + return PTR_ERR(adap->fd); + + adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd"); + if (IS_ERR(adap->rd)) + return PTR_ERR(adap->rd); + + return 0; +} + +static int c3_mipi_adap_get_clocks(struct c3_adap_device *adap) +{ + const struct c3_adap_info *info = adap->info; + + for (unsigned int i = 0; i < info->clock_num; i++) + adap->clks[i].id = info->clocks[i]; + + return devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks); +} + +static int c3_mipi_adap_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct c3_adap_device *adap; + int ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + adap->info = of_device_get_match_data(dev); + adap->dev = dev; + + ret = c3_mipi_adap_ioremap_resource(adap); + if (ret) + return dev_err_probe(dev, ret, "Failed to ioremap resource\n"); + + ret = c3_mipi_adap_get_clocks(adap); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + platform_set_drvdata(pdev, adap); + + pm_runtime_enable(dev); + + ret = c3_mipi_adap_subdev_init(adap); + if (ret) + goto err_disable_runtime_pm; + + ret = c3_mipi_adap_async_register(adap); + if (ret) + goto err_deinit_subdev; + + return 0; + +err_deinit_subdev: + c3_mipi_adap_subdev_deinit(adap); +err_disable_runtime_pm: + pm_runtime_disable(dev); + return ret; +}; + +static void c3_mipi_adap_remove(struct platform_device *pdev) +{ + struct c3_adap_device *adap = platform_get_drvdata(pdev); + + c3_mipi_adap_async_unregister(adap); + c3_mipi_adap_subdev_deinit(adap); + + pm_runtime_disable(&pdev->dev); +}; + +static const struct c3_adap_info c3_mipi_adap_info = { + .clocks = {"vapb", "isp0"}, + .clock_num = 2 +}; + +static const struct of_device_id c3_mipi_adap_of_match[] = { + { + .compatible = "amlogic,c3-mipi-adapter", + .data = &c3_mipi_adap_info + }, + { }, +}; +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match); + +static struct platform_driver c3_mipi_adap_driver = { + .probe = c3_mipi_adap_probe, + .remove = c3_mipi_adap_remove, + .driver = { + .name = "c3-mipi-adapter", + .of_match_table = c3_mipi_adap_of_match, + .pm = pm_ptr(&c3_mipi_adap_pm_ops), + }, +}; + +module_platform_driver(c3_mipi_adap_driver); + +MODULE_AUTHOR("Keke Li "); +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter"); +MODULE_LICENSE("GPL"); From patchwork Fri Dec 27 07:09:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 854090 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 88D7C140E34; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=dwVzO5J3hP669WgYC0SrFQxLb5NRFsudnNMH3O/evXjTVKCyygzCvcwEcXuHQIA/Xf5uqOhLbBPN1NTN4Un0D6VfU9ZCA9wR/sxGgxNhaDQCE2zBudMw1pa7Qsiv3MKzlCnMYbSGrt3E2GDAdrueDkh292fYJfBLjWW9MEClexE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=ZTDwxv6a7xPLzWJjlbJt9zSsFwF2+xHo3ETbX5m/pI0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=oObqHwCLaUc2dGSSx4riHEYNQ2Bb06Hc4ADyt9yMuFPo224C52rWnRjtJ1LfzFlAf0inQO2YcEp36wvrrZsdF5o65I5k6CuPOiQSlX/sp6hxnsQ+efQmJufslhiVkVx1nZjp+5OIM8xxj8/39TNezqUhlQG3SEJnRvnpRe7YbT8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=V0WiVn0e; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="V0WiVn0e" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4073BC4CEE6; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=ZTDwxv6a7xPLzWJjlbJt9zSsFwF2+xHo3ETbX5m/pI0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=V0WiVn0eGH2yT07rDa5CUrebqODTRzlbLJGYfTl+grqkd4+0JdlbkXbuGjlu4p9p1 Pzos8uE3ykWat41i2TK2nKgxcPG7WBX9V6b378eggrZ+gNGLU9rdYj73c1JjRB6COV aVzIeOECLdIZvnXUssjwj7ovDHM5VX+IHEhKNGhdaysPZuU5dv8G1/hVSLc6Dxkjm1 Qn6AZyP0BVmTTbubAmW4O6rbFXopXfw9qUsFtic4uwcCRpOBFbyfGJZtfGPNv4wiNX FROPvgF3AMB5IFoA5bWfkRVHv5iElBBSp0kePPQQq4zR5j14WZH5XP/nEaWNJSU2lK iM5CKuUv5e8qw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 398F5E77191; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:14 +0800 Subject: [PATCH v5 05/10] dt-bindings: media: Add amlogic,c3-isp.yaml Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-5-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=3320; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=2D4MMs+teJbgYEczXUrF7n2IOGGJmdnvrDImmAbZybo=; b=MUWJoveP5laDJ6CvNhJUdoIrFCPdeIORTTvVXA8b10rxpgjv8jA+0j/KfBNm+4RkA7pEEjq7Z 2gUL6UhojaoAeJ9J8UxgSv58sVHMZLi7ufKvPpHMTlYv9xQiaGr9TVs X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li c3-isp is used to process raw image. Reviewed-by: Rob Herring (Arm) Signed-off-by: Keke Li --- .../devicetree/bindings/media/amlogic,c3-isp.yaml | 92 ++++++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 98 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml new file mode 100644 index 000000000000..a33060ac47cb --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,c3-isp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic C3 Image Signal Processing Unit + +maintainers: + - Keke Li + +description: + Amlogic ISP is the RAW image processing module + and supports three channels image output. + +properties: + compatible: + enum: + - amlogic,c3-isp + + reg: + maxItems: 1 + + reg-names: + items: + - const: isp + + power-domains: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: vapb + - const: isp0 + + assigned-clocks: true + + assigned-clock-rates: true + + interrupts: + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/properties/port + description: input port node. + +required: + - compatible + - reg + - reg-names + - power-domains + - clocks + - clock-names + - interrupts + - port + +additionalProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + isp: isp@ff000000 { + compatible = "amlogic,c3-isp"; + reg = <0x0 0xff000000 0x0 0xf000>; + reg-names = "isp"; + power-domains = <&pwrc PWRC_C3_ISP_TOP_ID>; + clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_ISP0>; + clock-names = "vapb", "isp0"; + assigned-clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_ISP0>; + assigned-clock-rates = <0>, <400000000>; + interrupts = ; + + port { + c3_isp_in: endpoint { + remote-endpoint = <&c3_adap_out>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 2ef79d99e088..bdf8b24c773f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1243,6 +1243,12 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC ISP DRIVER +M: Keke Li +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml + AMLOGIC MIPI ADAPTER DRIVER M: Keke Li L: linux-media@vger.kernel.org From patchwork Fri Dec 27 07:09:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 853960 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AFEB31448DC; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=E0zDNs+yhVaAeQTWG0gTtWN4tLotTIFxnTdRwacb6YwG5KreVv4roTT2336l4q5gMNSBB6x2Pa46EOec9ukxpYx9yPDTCNcGKerONoQMoDavY1PVElN3sEpSi3MXNzuoEZJDHKzqNMPlDP1oa3dFGbaLUnp2QAOYLH2yeXxSNno= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=HDphpUsmRZtsWH8FkarXiJDsgLNr+DgOnO7OwJIYx7Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=a2jXBcBEww9hnZHbXQdvfLau87KPe9ZTCpazd1I0y4TK0hFrqnbUrizAa6iifmyd/DOkeAwseYM+QqBGWcmQhb5iD0HSm0phadSIHK4VdKeH85ec0w176nTEVq0GoIzbD9IdFeMeV+NQ6iUHxStsNg2H+vrP6prUsp2J15sXtqo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gsgaJVqC; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="gsgaJVqC" Received: by smtp.kernel.org (Postfix) with ESMTPS id 55BA5C4CEE9; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=HDphpUsmRZtsWH8FkarXiJDsgLNr+DgOnO7OwJIYx7Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=gsgaJVqCE4GmUvVe1NmHWOzrQpQrD+cYnFMoREuQM8gc0qounJ/Y+6YFihKK5jupL NjbVHIW46z/vVCJuPQ4Pbbsc8mNZF79I3Am36M6tb3XEbOwm6Gi8XWnYLeaLR2wJgd xqQ0c4kDYMPHAf45I9DZ6CJL7wt08hQdIjoJwiaVvU/StMRwvhPHeoqb8j5reccodb SFFxoUnASjAgGeORJ3LXIO1mAHQuOR9qKVUUZiyydhxHk4LqFj7aDbmuary3PY3XKg qdcT47hdQg/vlCU/2VsqSp9PakOWph7je+CU6f+/0XIfhBJ15+5TASxBwg7Wc6pkiL yEwHkkEm5XQcQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4EEFFE7718B; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:15 +0800 Subject: [PATCH v5 06/10] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-6-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=2313; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=47Wr/QvwP+uFAvDY6Bi59wxrfL1TNanjthR6Z36FYT0=; b=djiN1eqL2kd5fCyWYmx/pavg3/pFi2vrtqmGx+at2Aiy+4wKHKSXN4bDM3Lg4yPx70R4Gz8Zz GLWN24avFlqDkiUimzhJJAH2usXi8wdZzd+1aWrqsBKGly5ngPKAo40 X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li C3ISP_PARAMS is the C3 ISP Parameters format. C3ISP_STATS is the C3 ISP Statistics format. Reviewed-by: Daniel Scally Reviewed-by: Jacopo Mondi Signed-off-by: Keke Li --- drivers/media/v4l2-core/v4l2-ioctl.c | 2 ++ include/uapi/linux/videodev2.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 0304daa8471d..dae34b1170d7 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1460,6 +1460,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; + case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; + case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index e7c4dce39007..75e990e9d8b7 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -858,6 +858,10 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */ +/* Vendor specific - used for C3_ISP */ +#define V4L2_META_FMT_C3ISP_PARAMS v4l2_fourcc('C', '3', 'P', 'M') /* Amlogic C3 ISP Parameters */ +#define V4L2_META_FMT_C3ISP_STATS v4l2_fourcc('C', '3', 'S', 'T') /* Amlogic C3 ISP Statistics */ + /* Vendor specific - used for RaspberryPi PiSP */ #define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */ #define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C') /* PiSP FE configuration */ From patchwork Fri Dec 27 07:09:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 853961 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9646C142633; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; cv=none; b=X0ZeZ+o8VlqwvP+QQOqdMZcvG1hPfaiKuxfTRxYzGlwED0t07ZDs8yX7NbYtsx35Rvk+XAGkjPCXtQLcIqcOmhEXdmO3BxBpyV3Upi2OBTuNRIp61TDUvCIwTRYk9VFrSnyyL0mPA8lJ9WEFu43YKVXSffw6wW+3au6ZxM8F9B0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283377; c=relaxed/simple; bh=ZYWU8pIy76BqaTqDtwKN1PQ1ZPaDfo3bzNi/jcTxOAA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=esYTO7MtsZn3cNSe6stIOSNXBhJmB4eocLyvQf2hKRLsTY8eJO95N+JyRVMpavaguSRyPq2F5zUclEbS3mbIqhKkQfg9QJNBaE4fdpihYelxMPsDgE+zF3PZ1n6+zm2GHj5IaeTklycdRZh4fJN2B66E4yC7MsrxCLyLrYX2hQ0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gqBIMUDN; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="gqBIMUDN" Received: by smtp.kernel.org (Postfix) with ESMTPS id 6CCD9C4CEEB; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=ZYWU8pIy76BqaTqDtwKN1PQ1ZPaDfo3bzNi/jcTxOAA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=gqBIMUDNnhaJ4E0h0MdDawCZ+2ckwz/gRdorQJW8DQRi7qhciwZqbZ80OOUcgWlED k5XFuwHHKNq4nigJTFncVM3v1ho6xrDROKoWrzdjtqqIBU2TRbXxG0qbMGFhGl/N+N XmvFZarAuQd8icKhPryfd5ILBW4xPA4g5oYbnLEs9GXdZNu28PyTJAmdXvrr64VXO1 ApONkZ+7H4qk20VaAKtR+m3oPDdWrdjP/c5gX9Ntk+yjvpk/w+SdEcefgAN9jQ8V+s iROpgV49IYRsD3Ik41+HsFWcmLPp0y8TCfdtevwqKgJf70P1STh5IT/tRdKU3S81Wj 3YscqcF1z4WCQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 64BB9E7718F; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:16 +0800 Subject: [PATCH v5 07/10] media: uapi: Add stats info and parameters buffer for c3 ISP Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-7-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=22703; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=obrkAgI7uHmC5/ACXQJwq75rDbQbwWp49WHdXd9c2Bs=; b=2uDpGKJqoTpxiEtMigv5qGLStR/Ljng3gz+Ery1hnm1HhQbBtvIZWNoH7FMTL7igu8EkNJ6fY /RwgBy8+HQgCUKLGUBTPHBFgYwMS0C4EpKsDr6pVRiFkOFTDc7+jh47 X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add a header that describes the 3A statistics buffer and the parameters buffer for c3 ISP Signed-off-by: Keke Li --- MAINTAINERS | 1 + include/uapi/linux/media/amlogic/c3-isp-config.h | 564 +++++++++++++++++++++++ 2 files changed, 565 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index bdf8b24c773f..274f72c31d9a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,6 +1248,7 @@ M: Keke Li L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml +F: include/uapi/linux/media/amlogic/ AMLOGIC MIPI ADAPTER DRIVER M: Keke Li diff --git a/include/uapi/linux/media/amlogic/c3-isp-config.h b/include/uapi/linux/media/amlogic/c3-isp-config.h new file mode 100644 index 000000000000..ee673ed022c0 --- /dev/null +++ b/include/uapi/linux/media/amlogic/c3-isp-config.h @@ -0,0 +1,564 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#ifndef _UAPI_C3_ISP_CONFIG_H_ +#define _UAPI_C3_ISP_CONFIG_H_ + +#include + +/* + * Frames are split into zones of almost equal width and height - a zone is a + * rectangular tile of a frame. The metering blocks within the ISP collect + * aggregated statistics per zone. + */ +#define C3_ISP_AE_MAX_ZONES (17 * 15) +#define C3_ISP_AF_MAX_ZONES (17 * 15) +#define C3_ISP_AWB_MAX_ZONES (32 * 24) + +/* The maximum number of point on the diagonal of the frame for statistics */ +#define C3_ISP_AE_MAX_PT_NUM 18 +#define C3_ISP_AF_MAX_PT_NUM 18 +#define C3_ISP_AWB_MAX_PT_NUM 33 + +/** + * struct c3_isp_awb_zone_stats - AWB statistics of a zone + * + * AWB zone stats is aligned with 8 bytes + * + * @rg: the ratio of R / G in a zone + * @bg: the ratio of B / G in a zone + * @pixel_sum: the total number of pixels used in a zone + */ +struct c3_isp_awb_zone_stats { + __u16 rg; + __u16 bg; + __u32 pixel_sum; +}; + +/** + * struct c3_isp_awb_stats - Auto white balance statistics information. + * + * AWB statistical information of all zones. + * + * @stats: array of auto white balance statistics + */ +struct c3_isp_awb_stats { + struct c3_isp_awb_zone_stats stats[C3_ISP_AWB_MAX_ZONES]; +} __attribute__((aligned(16))); + +/** + * struct c3_isp_ae_zone_stats - AE statistics of a zone + * + * AE zone stats is aligned with 8 bytes. + * This is a 5-bin histogram and the total sum is normalized to 0xffff. + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4) + * + * @hist0: the global normalized pixel count for bin 0 + * @hist1: the global normalized pixel count for bin 1 + * @hist3: the global normalized pixel count for bin 3 + * @hist4: the global normalized pixel count for bin 4 + */ +struct c3_isp_ae_zone_stats { + __u16 hist0; + __u16 hist1; + __u16 hist3; + __u16 hist4; +}; + +/** + * struct c3_isp_ae_stats - Exposure statistics information + * + * AE statistical information consists of all blocks information and a 1024-bin + * histogram. + * + * @stats: array of auto exposure block statistics + * @reserved: undefined buffer space + * @hist: a 1024-bin histogram for the entire image + */ +struct c3_isp_ae_stats { + struct c3_isp_ae_zone_stats stats[C3_ISP_AE_MAX_ZONES]; + __u32 reserved[2]; + __u32 hist[1024]; +} __attribute__((aligned(16))); + +/** + * struct c3_isp_af_zone_stats - AF statistics of a zone + * + * AF zone stats is aligned with 8 bytes. + * The zonal accumulated contrast metrics are stored in floating point format + * with 16 bits mantissa and 5 or 6 bits exponent. Apart from contrast metrics + * we accumulate squared image and quartic image data over the zone. + * + * @i2_mat: the mantissa of zonal squared image pixel sum + * @i4_mat: the mantissa of zonal quartic image pixel sum + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum + * @e4_exp: the exponent of zonal multi-directional quartic edge sum + * @i2_exp: the exponent of zonal squared image pixel sum + * @i4_exp: the exponent of zonal quartic image pixel sum + */ +struct c3_isp_af_zone_stats { + __u16 i2_mat; + __u16 i4_mat; + __u16 e4_mat; + __u16 e4_exp : 5; + __u16 i2_exp : 5; + __u16 i4_exp : 6; +}; + +/** + * struct c3_isp_af_stats - Auto Focus statistics information + * + * AF statistical information of each zone + * + * @stats: array of auto focus block statistics + * @reserved: undefined buffer space + */ +struct c3_isp_af_stats { + struct c3_isp_af_zone_stats stats[C3_ISP_AF_MAX_ZONES]; + __u32 reserved[2]; +} __attribute__((aligned(16))); + +/** + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS + * + * Contains ISP statistics + * + * @awb: auto white balance stats + * @ae: auto exposure stats + * @af: auto focus stats + */ +struct c3_isp_stats_info { + struct c3_isp_awb_stats awb; + struct c3_isp_ae_stats ae; + struct c3_isp_af_stats af; +}; + +/** + * enum c3_isp_params_buffer_version - C3 ISP parameters block versioning + * + * @C3_ISP_PARAMS_BUFFER_V0: First version of C3 ISP parameters block + */ +enum c3_isp_params_buffer_version { + C3_ISP_PARAMS_BUFFER_V0, +}; + +/** + * enum c3_isp_params_block_type - Enumeration of C3 ISP parameter blocks + * + * Each block configures a specific processing block of the C3 ISP. + * The block type allows the driver to correctly interpret the parameters block + * data. + * + * @C3_ISP_PARAMS_BLOCK_AWB_GAINS: White balance gains + * @C3_ISP_PARAMS_BLOCK_AWB_CONFIG: AWB statistic format configuration for all + * blocks that control how stats are generated + * @C3_ISP_PARAMS_BLOCK_AE_CONFIG: AE statistic format configuration for all + * blocks that control how stats are generated + * @C3_ISP_PARAMS_BLOCK_AF_CONFIG: AF statistic format configuration for all + * blocks that control how stats are generated + * @C3_ISP_PARAMS_BLOCK_PST_GAMMA: post gamma parameters + * @C3_ISP_PARAMS_BLOCK_CCM: Color correction matrix parameters + * @C3_ISP_PARAMS_BLOCK_CSC: Color space conversion parameters + * @C3_ISP_PARAMS_BLOCK_BLC: Black level correction parameters + * @C3_ISP_PARAMS_BLOCK_SENTINEL: First non-valid block index + */ +enum c3_isp_params_block_type { + C3_ISP_PARAMS_BLOCK_AWB_GAINS, + C3_ISP_PARAMS_BLOCK_AWB_CONFIG, + C3_ISP_PARAMS_BLOCK_AE_CONFIG, + C3_ISP_PARAMS_BLOCK_AF_CONFIG, + C3_ISP_PARAMS_BLOCK_PST_GAMMA, + C3_ISP_PARAMS_BLOCK_CCM, + C3_ISP_PARAMS_BLOCK_CSC, + C3_ISP_PARAMS_BLOCK_BLC, + C3_ISP_PARAMS_BLOCK_SENTINEL +}; + +#define C3_ISP_PARAMS_BLOCK_FL_DISABLE (1U << 0) +#define C3_ISP_PARAMS_BLOCK_FL_ENABLE (1U << 1) + +/** + * struct c3_isp_params_block_header - C3 ISP parameter block header + * + * This structure represents the common part of all the ISP configuration + * blocks. Each parameters block shall embed an instance of this structure type + * as its first member, followed by the block-specific configuration data. The + * driver inspects this common header to discern the block type and its size and + * properly handle the block content by casting it to the correct block-specific + * type. + * + * The @type field is one of the values enumerated by + * :c:type:`c3_isp_params_block_type` and specifies how the data should be + * interpreted by the driver. The @size field specifies the size of the + * parameters block and is used by the driver for validation purposes. The + * @flags field is a bitmask of per-block flags C3_ISP_PARAMS_FL*. + * + * When userspace wants to disable an ISP block the + * C3_ISP_PARAMS_BLOCK_FL_DISABLED bit should be set in the @flags field. In + * this case userspace may optionally omit the remainder of the configuration + * block, which will be ignored by the driver. + * + * When a new configuration of an ISP block needs to be applied userspace + * shall fully populate the ISP block and omit setting the + * C3_ISP_PARAMS_BLOCK_FL_DISABLED bit in the @flags field. + * + * Userspace is responsible for correctly populating the parameters block header + * fields (@type, @flags and @size) and the block-specific parameters. + * + * For example: + * + * .. code-block:: c + * + * void populate_pst_gamma(struct c3_isp_params_block_header *block) { + * struct c3_isp_params_pst_gamma *gamma = + * (struct c3_isp_params_pst_gamma *)block; + * + * gamma->header.type = C3_ISP_PARAMS_BLOCK_PST_GAMMA; + * gamma->header.flags = C3_ISP_PARAMS_BLOCK_FL_ENABLE; + * gamma->header.size = sizeof(*gamma); + * + * for (unsigned int i = 0; i < 129; i++) + * gamma->pst_gamma_lut[i] = i; + * } + * + * @type: The parameters block type from :c:type:`c3_isp_params_block_type` + * @flags: A bitmask of block flags + * @size: Size (in bytes) of the parameters block, including this header + */ +struct c3_isp_params_block_header { + __u16 type; + __u16 flags; + __u32 size; +}; + +/** + * struct c3_isp_params_awb_gains - Gains for auto-white balance + * + * This struct allows users to configure the gains for white balance. + * There are four gain settings corresponding to each colour channel in + * the bayer domain. All of the gains are stored in Q4.8 format. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_AWB_GAINS + * from :c:type:`c3_isp_params_block_type` + * + * @header: The C3 ISP parameters block header + * @gr_gain: Multiplier for Gr channel (Q4.8 format) + * @r_gain: Multiplier for R channel (Q4.8 format) + * @b_gain: Multiplier for B channel (Q4.8 format) + * @gb_gain: Multiplier for Gb channel (Q4.8 format) + */ +struct c3_isp_params_awb_gains { + struct c3_isp_params_block_header header; + __u16 gr_gain; + __u16 r_gain; + __u16 b_gain; + __u16 gb_gain; +} __attribute__((aligned(8))); + +/** + * enum c3_isp_params_awb_tap_points - Tap points for the AWB statistics + * @C3_ISP_AWB_STATS_TAP_FE: immediately after the optical frontend block + * @C3_ISP_AWB_STATS_TAP_GE: immediately after the green equal block + * @C3_ISP_AWB_STATS_TAP_BEFORE_WB: immediately before the white balance block + * @C3_ISP_AWB_STATS_TAP_AFTER_WB: immediately after the white balance block + */ +enum c3_isp_params_awb_tap_point { + C3_ISP_AWB_STATS_TAP_OFE = 0, + C3_ISP_AWB_STATS_TAP_GE, + C3_ISP_AWB_STATS_TAP_BEFORE_WB, + C3_ISP_AWB_STATS_TAP_AFTER_WB, +}; + +/** + * struct c3_isp_params_awb_config - Stats settings for auto-white balance + * + * This struct allows the configuration of the statistics generated for auto + * white balance. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_AWB_CONFIG + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @tap_point: the tap point from enum c3_isp_params_awb_tap_point + * @satur_vald: AWB statistic over saturation control + * value: 0: disable, 1: enable + * @horiz_zones_num: active number of hotizontal zones [0..32] + * @vert_zones_num: active number of vertical zones [0..24] + * @rg_min: minimum R/G ratio (Q4.8 format) + * @rg_max: maximum R/G ratio (Q4.8 format) + * @bg_min: minimum B/G ratio (Q4.8 format) + * @bg_max: maximum B/G ratio (Q4.8 format) + * @rg_low: R/G ratio trim low (Q4.8 format) + * @rg_high: R/G ratio trim hight (Q4.8 format) + * @bg_low: B/G ratio trim low (Q4.8 format) + * @bg_high: B/G ratio trim high (Q4.8 format) + * @zone_weight: array of weights for AWB statistics zones [0..15] + * @horiz_cood: the horizontal coordinate of points on the diagonal [0..2888] + * @vert_cood: the vertical coordinate of points on the diagonal [0..2240] + */ +struct c3_isp_params_awb_config { + struct c3_isp_params_block_header header; + __u8 tap_point; + __u8 satur_vald; + __u8 horiz_zones_num; + __u8 vert_zones_num; + __u16 rg_min; + __u16 rg_max; + __u16 bg_min; + __u16 bg_max; + __u16 rg_low; + __u16 rg_high; + __u16 bg_low; + __u16 bg_high; + __u8 zone_weight[C3_ISP_AWB_MAX_ZONES]; + __u16 horiz_cood[C3_ISP_AWB_MAX_PT_NUM]; + __u16 vert_cood[C3_ISP_AWB_MAX_PT_NUM]; +} __attribute__((aligned(8))); + +/** + * enum c3_isp_params_ae_tap_points - Tap points for the AE statistics + * @C3_ISP_AE_STATS_TAP_GE: immediately after the green equal block + * @C3_ISP_AE_STATS_TAP_MLS: immediately after the mesh lens shading block + */ +enum c3_isp_params_ae_tap_point { + C3_ISP_AE_STATS_TAP_GE = 0, + C3_ISP_AE_STATS_TAP_MLS, +}; + +/** + * struct c3_isp_params_ae_config - Stats settings for auto-exposure + * + * This struct allows the configuration of the statistics generated for + * auto exposure. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_AE_CONFIG + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @horiz_zones_num: active number of horizontal zones [0..17] + * @vert_zones_num: active number of vertical zones [0..15] + * @tap_point: the tap point from enum c3_isp_params_ae_tap_point + * @zone_weight: array of weights for AE statistics zones [0..15] + * @horiz_cood: the horizontal coordinate of points on the diagonal [0..2888] + * @vert_cood: the vertical coordinate of points on the diagonal [0..2240] + * @reserved: applications must zero this array + */ +struct c3_isp_params_ae_config { + struct c3_isp_params_block_header header; + __u8 tap_point; + __u8 horiz_zones_num; + __u8 vert_zones_num; + __u8 zone_weight[C3_ISP_AE_MAX_ZONES]; + __u16 horiz_cood[C3_ISP_AE_MAX_PT_NUM]; + __u16 vert_cood[C3_ISP_AE_MAX_PT_NUM]; + __u16 reserved[3]; +} __attribute__((aligned(8))); + +/** + * enum c3_isp_params_af_tap_points - Tap points for the AF statistics + * @C3_ISP_AF_STATS_TAP_SNR: immediately after the spatial noise reduce block + * @C3_ISP_AF_STATS_TAP_DMS: immediately after the demosaic block + */ +enum c3_isp_params_af_tap_point { + C3_ISP_AF_STATS_TAP_SNR = 0, + C3_ISP_AF_STATS_TAP_DMS, +}; + +/** + * struct c3_isp_params_af_config - Stats settings for auto-focus + * + * This struct allows the configuration of the statistics generated for + * auto focus. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_AF_CONFIG + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @tap_point: the tap point from enum c3_isp_params_af_tap_point + * @horiz_zones_num: active number of hotizontal zones [0..17] + * @vert_zones_num: active number of vertical zones [0..15] + * @reserved: applications must zero this array + * @horiz_cood: the horizontal coordinate of points on the diagonal [0..2888] + * @vert_cood: the vertical coordinate of points on the diagonal [0..2240] + */ +struct c3_isp_params_af_config { + struct c3_isp_params_block_header header; + __u8 tap_point; + __u8 horiz_zones_num; + __u8 vert_zones_num; + __u8 reserved[5]; + __u16 horiz_cood[C3_ISP_AF_MAX_PT_NUM]; + __u16 vert_cood[C3_ISP_AF_MAX_PT_NUM]; +} __attribute__((aligned(8))); + +/** + * struct c3_isp_params_pst_gamma - Post gamma configuration + * + * This struct allows the configuration of the look up table for + * post gamma. The gamma curve consists of 129 points, so need to + * set lut[129]. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_PST_GAMMA + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @lut: lookup table for P-Stitch gamma [0..1023] + * @reserved: applications must zero this array + */ +struct c3_isp_params_pst_gamma { + struct c3_isp_params_block_header header; + __u16 lut[129]; + __u16 reserved[3]; +} __attribute__((aligned(8))); + +/** + * struct c3_isp_params_ccm - ISP CCM configuration + * + * This struct allows the configuration of the matrix for + * color correction. The matrix consists of 3 x 3 points, + * so need to set matrix[3][3]. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_CCM + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @matrix: a 3 x 3 matrix used for color correction, + * the value of matrix[x][y] is orig_value x 256. [-4096..4095] + * @reserved: applications must zero this array + */ +struct c3_isp_params_ccm { + struct c3_isp_params_block_header header; + __s16 matrix[3][3]; + __u16 reserved[3]; +} __attribute__((aligned(8))); + +/** + * struct c3_isp_params_csc - ISP Color Space Conversion configuration + * + * This struct allows the configuration of the matrix for color space + * conversion. The matrix consists of 3 x 3 points, so need to set matrix[3][3]. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_CSC + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @matrix: a 3x3 matrix used for the color space conversion, + * the value of matrix[x][y] is orig_value x 256. [-4096..4095] + * @reserved: applications must zero this array + */ +struct c3_isp_params_csc { + struct c3_isp_params_block_header header; + __s16 matrix[3][3]; + __u16 reserved[3]; +} __attribute__((aligned(8))); + +/** + * struct c3_isp_params_blc - ISP Black Level Correction configuration + * + * This struct allows the configuration of the block level offset for each + * color channel. + * + * header.type should be set to C3_ISP_PARAMS_BLOCK_BLC + * from :c:type:`c3_isp_params_block_type` + * + * @header: the C3 ISP parameters block header + * @gr_ofst: Gr blc offset (Q4.8 format) + * @r_ofst: R blc offset (Q4.8 format) + * @b_ofst: B blc offset (Q4.8 format) + * @gb_ofst: Gb blc offset(Q4.8 format) + */ +struct c3_isp_params_blc { + struct c3_isp_params_block_header header; + __u16 gr_ofst; + __u16 r_ofst; + __u16 b_ofst; + __u16 gb_ofst; +}; + +/** + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters + * + * Though the parameters for the C3 ISP are passed as optional blocks, the + * driver still needs to know the absolute maximum size so that it can allocate + * a buffer sized appropriately to accommodate userspace attempting to set all + * possible parameters in a single frame. + */ +#define C3_ISP_PARAMS_MAX_SIZE \ + (sizeof(struct c3_isp_params_awb_gains) + \ + sizeof(struct c3_isp_params_awb_config) + \ + sizeof(struct c3_isp_params_ae_config) + \ + sizeof(struct c3_isp_params_af_config) + \ + sizeof(struct c3_isp_params_pst_gamma) + \ + sizeof(struct c3_isp_params_ccm) + \ + sizeof(struct c3_isp_params_csc) + \ + sizeof(struct c3_isp_params_blc)) + +/** + * struct c3_isp_params_cfg - C3 ISP configuration parameters + * + * This struct contains the configuration parameters of the C3 ISP + * algorithms, serialized by userspace into an opaque data buffer. Each + * configuration parameter block is represented by a block-specific structure + * which contains a :c:type:`c3_isp_param_block_header` entry as first + * member. Userspace populates the @data buffer with configuration parameters + * for the blocks that it intends to configure. As a consequence, the data + * buffer effective size changes according to the number of ISP blocks that + * userspace intends to configure. + * + * The parameters buffer is versioned by the @version field to allow modifying + * and extending its definition. Userspace should populate the @version field to + * inform the driver about the version it intends to use. The driver will parse + * and handle the @data buffer according to the data layout specific to the + * indicated revision and return an error if the desired revision is not + * supported. + * + * For each ISP block that userspace wants to configure, a block-specific + * structure is appended to the @data buffer, one after the other without gaps + * in between nor overlaps. Userspace shall populate the @total_size field with + * the effective size, in bytes, of the @data buffer. + * + * The expected memory layout of the parameters buffer is:: + * + * +-------------------- struct c3_isp_params_cfg ---- ------------------+ + * | version = C3_ISP_PARAM_BUFFER_V0; | + * | data_size = sizeof(struct c3_isp_params_awb_gains) + | + * | sizeof(struct c3_isp_params_awb_config); | + * | +------------------------- data ---------------------------------+ | + * | | +------------ struct c3_isp_params_awb_gains) ------------------+ | + * | | | +--------- struct c3_isp_params_block_header header -----+ | | | + * | | | | type = C3_ISP_PARAMS_BLOCK_AWB_GAINS; | | | | + * | | | | flags = C3_ISP_PARAMS_BLOCK_FL_NONE; | | | | + * | | | | size = sizeof(struct c3_isp_params_awb_gains); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | gr_gain = ...; | | | + * | | | r_gain = ...; | | | + * | | | b_gain = ...; | | | + * | | | gb_gain = ...; | | | + * | | +------------------ struct c3_isp_params_awb_config ----------+ | | + * | | | +---------- struct c3_isp_param_block_header header ------+ | | | + * | | | | type = C3_ISP_PARAMS_BLOCK_AWB_CONFIG; | | | | + * | | | | flags = C3_ISP_PARAMS_BLOCK_FL_NONE; | | | | + * | | | | size = sizeof(struct c3_isp_params_awb_config) | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | tap_point = ...; | | | + * | | | satur_vald = ...; | | | + * | | | horiz_zones_num = ...; | | | + * | | | vert_zones_num = ...; | | | + * | | +-------------------------------------------------------------+ | | + * | +-----------------------------------------------------------------+ | + * +---------------------------------------------------------------------+ + * + * @version: The C3 ISP parameters buffer version + * @data_size: The C3 ISP configuration data effective size, excluding this + * header + * @data: The C3 ISP configuration blocks data + */ +struct c3_isp_params_cfg { + __u32 version; + __u32 data_size; + __u8 data[C3_ISP_PARAMS_MAX_SIZE]; +}; + +#endif From patchwork Fri Dec 27 07:09:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 853959 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8B62145B3E; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; cv=none; b=iIG2EsCsGjKG9Pm/ndhPHdt0Jv5yl7VYAo3xHln/HrSwNYo0dQZBmMH9ECNALGc+ZUgboRdLPzobt+223H9EmC8g1zBNIkSNiq4SNobgVuqdTye8V0nuo2P7Fh7BnVR30P6ezfSO3nY6NQNW7n/98ENBdWEsXu6wfEuGCltgHV8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; c=relaxed/simple; bh=Xv9XUfK2bMta6/9NsOaRAaIMRWcGB+CX45Qkjkum75E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=owqEHfJaQ87vfPOGfpH/7NEVyOyN6w3d0hmsBF4q8MPqyrtmPnlxp9dBCHjeZjKpCvgimI9DywDbi1FiaCjLTIbabA51dcflwbLZ9XGHGgZa+wKeIEaJrKtjJV3gfsjyxkAdGwv7USYxSx1cbC4rD+Mx/T28spOjfmbfRjDnSCU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=CkJMxFfW; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="CkJMxFfW" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8CC12C4CEF2; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=Xv9XUfK2bMta6/9NsOaRAaIMRWcGB+CX45Qkjkum75E=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=CkJMxFfWRhT48cqHKJyASI1aKPls+Fewqn6BSJ1g4Iy9I3Ox3AwJT0S9Qfyd3LLNn pDKj7jkLiDBG1Af12a2+frgAFtXFEdQNdPCZigptn/5MuPXUdotiYovEcilGgFyO+X Yoqk1almfUHgymmee9GF2EZauXA8DFw7W27Ci8f/6PFn0m7CAIj+2C7Mk9CjzE/o6r 0/2x0WIAykCh6L9b075Dy+haTzkzYQv+eo/lDGnP54zqTjwmB0mjwqmT5rJ//AB+gU d7bFit9tSYdQL0IhU9lcRlIkxwP/DsZzLX7/PJKHD1ng2hotYEsHjmBR6cIPm/m+Fk GSduG4zoWUg3A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 85CB0E77191; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:17 +0800 Subject: [PATCH v5 08/10] media: platform: Add c3 ISP driver Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-8-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=162386; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=16fcIAjDJa4yidyd4dkdWum+hvNMrlsZFDnADByPs4c=; b=JY2Pdh9x4eiY3rVG+NFZGqfWwXXUoywtxlAZkjUgNtU+m3GVex+yODSDIoLJK70x80ED4pu62 xjFNxPN9F71B6wSPk8egwEbAKpj1aXV7nLD/uNf020YInovZKC1O6HF X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li The C3 ISP supports multi-camera and muti-exposure high dynamic range (HDR). It brings together some advanced imaging technologies to provide good image quality. This driver mainly responsible for driving ISP pipeline to process raw image. Signed-off-by: Keke Li --- MAINTAINERS | 1 + drivers/media/platform/amlogic/c3/Kconfig | 1 + drivers/media/platform/amlogic/c3/Makefile | 1 + drivers/media/platform/amlogic/c3/isp/Kconfig | 18 + drivers/media/platform/amlogic/c3/isp/Makefile | 10 + .../media/platform/amlogic/c3/isp/c3-isp-capture.c | 753 +++++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-common.h | 332 +++++++ .../media/platform/amlogic/c3/isp/c3-isp-core.c | 555 +++++++++++ drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c | 421 ++++++++ .../media/platform/amlogic/c3/isp/c3-isp-params.c | 1010 ++++++++++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-regs.h | 606 ++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 796 +++++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-stats.c | 328 +++++++ 13 files changed, 4832 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 274f72c31d9a..d92427630bfa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,6 +1248,7 @@ M: Keke Li L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml +F: drivers/media/platform/amlogic/c3/isp/ F: include/uapi/linux/media/amlogic/ AMLOGIC MIPI ADAPTER DRIVER diff --git a/drivers/media/platform/amlogic/c3/Kconfig b/drivers/media/platform/amlogic/c3/Kconfig index a504a1eb22e6..d355d3a9358d 100644 --- a/drivers/media/platform/amlogic/c3/Kconfig +++ b/drivers/media/platform/amlogic/c3/Kconfig @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +source "drivers/media/platform/amlogic/c3/isp/Kconfig" source "drivers/media/platform/amlogic/c3/mipi-adapter/Kconfig" source "drivers/media/platform/amlogic/c3/mipi-csi2/Kconfig" diff --git a/drivers/media/platform/amlogic/c3/Makefile b/drivers/media/platform/amlogic/c3/Makefile index 770b2a2903ad..14f305a493d2 100644 --- a/drivers/media/platform/amlogic/c3/Makefile +++ b/drivers/media/platform/amlogic/c3/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-y += isp/ obj-y += mipi-adapter/ obj-y += mipi-csi2/ diff --git a/drivers/media/platform/amlogic/c3/isp/Kconfig b/drivers/media/platform/amlogic/c3/isp/Kconfig new file mode 100644 index 000000000000..02c62a50a5e8 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_C3_ISP + tristate "Amlogic C3 Image Signal Processor (ISP) driver" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + help + Video4Linux2 driver for Amlogic C3 ISP pipeline. + The C3 ISP is used for processing raw images and + outputing results to memory. + + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/c3/isp/Makefile b/drivers/media/platform/amlogic/c3/isp/Makefile new file mode 100644 index 000000000000..b1b064170b57 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +c3-isp-objs := c3-isp-dev.o \ + c3-isp-params.o \ + c3-isp-stats.o \ + c3-isp-capture.o \ + c3-isp-core.o \ + c3-isp-resizer.o + +obj-$(CONFIG_VIDEO_C3_ISP) += c3-isp.o diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c new file mode 100644 index 000000000000..a3180e4d4221 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +#define C3_ISP_WRMIFX3_REG(addr, id) ((addr) + (id) * 0x100) + +static const struct c3_isp_cap_format_info cap_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_GREY, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .plane = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .hdiv = 1, + .vdiv = 1, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV12M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV, + .plane = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .hdiv = 2, + .vdiv = 2, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV21M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .plane = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .hdiv = 2, + .vdiv = 2, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV16M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV, + .plane = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .hdiv = 1, + .vdiv = 2 + }, { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .fourcc = V4L2_PIX_FMT_NV61M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .plane = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .hdiv = 1, + .vdiv = 2, + }, +}; + +/* Hardware configuration */ + +/* Set the address of wrmifx3(write memory interface) */ +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap) +{ + dma_addr_t y_dma_addr; + dma_addr_t uv_dma_addr; + + if (cap->buff) { + y_dma_addr = cap->buff->dma_addr[C3_ISP_PLANE_Y]; + uv_dma_addr = cap->buff->dma_addr[C3_ISP_PLANE_UV]; + } else { + y_dma_addr = cap->dummy_buff.dma_addr; + uv_dma_addr = cap->dummy_buff.dma_addr; + } + + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id), + ISP_WRMIFX3_0_CH0_BASE_ADDR(y_dma_addr)); + + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id), + ISP_WRMIFX3_0_CH1_BASE_ADDR(uv_dma_addr)); +} + +static void c3_isp_cap_wrmifx3_format(struct c3_isp_capture *cap) +{ + struct v4l2_pix_format_mplane *pix_mp = &cap->format.pix_mp; + const struct c3_isp_cap_format_info *info = cap->format.info; + u32 stride; + u32 chrom_h; + u32 chrom_v; + + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id), + ISP_WRMIFX3_0_FMT_SIZE_HSIZE(pix_mp->width) | + ISP_WRMIFX3_0_FMT_SIZE_VSIZE(pix_mp->height)); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK, info->format); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK, + ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK, info->plane); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK, + info->uv_swap); + + stride = DIV_ROUND_UP(pix_mp->plane_fmt[C3_ISP_PLANE_Y].bytesperline, + C3_ISP_DMA_SIZE_ALIGN_BYTES); + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id), + ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK, + ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(stride)); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id), + ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK, + ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS); + + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id), + ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(pix_mp->width)); + + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id), + ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(pix_mp->height)); + + stride = DIV_ROUND_UP(pix_mp->plane_fmt[C3_ISP_PLANE_UV].bytesperline, + C3_ISP_DMA_SIZE_ALIGN_BYTES); + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id), + ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK, + ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(stride)); + + c3_isp_update_bits(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id), + ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK, + ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS); + + chrom_h = DIV_ROUND_UP(pix_mp->width, info->hdiv); + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id), + ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(chrom_h)); + + chrom_v = DIV_ROUND_UP(pix_mp->height, info->vdiv); + c3_isp_write(cap->isp, + C3_ISP_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id), + ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(chrom_v)); +} + +static int c3_isp_cap_dummy_buff_create(struct c3_isp_capture *cap) +{ + struct c3_isp_dummy_buffer *dummy_buff = &cap->dummy_buff; + struct v4l2_pix_format_mplane *pix_mp = &cap->format.pix_mp; + + if (pix_mp->num_planes == 1) + dummy_buff->size = pix_mp->plane_fmt[C3_ISP_PLANE_Y].sizeimage; + else + dummy_buff->size = + max(pix_mp->plane_fmt[C3_ISP_PLANE_Y].sizeimage, + pix_mp->plane_fmt[C3_ISP_PLANE_UV].sizeimage); + + /* The driver never access vaddr, no mapping is required */ + dummy_buff->vaddr = dma_alloc_attrs(cap->isp->dev, dummy_buff->size, + &dummy_buff->dma_addr, GFP_KERNEL, + DMA_ATTR_NO_KERNEL_MAPPING); + if (!dummy_buff->vaddr) + return -ENOMEM; + + return 0; +} + +static void c3_isp_cap_dummy_buff_destroy(struct c3_isp_capture *cap) +{ + dma_free_attrs(cap->isp->dev, cap->dummy_buff.size, + cap->dummy_buff.vaddr, cap->dummy_buff.dma_addr, + DMA_ATTR_NO_KERNEL_MAPPING); +} + +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap) +{ + cap->buff = list_first_entry_or_null(&cap->pending, + struct c3_isp_cap_buffer, list); + + c3_isp_cap_wrmifx3_buff(cap); + + if (cap->buff) + list_del(&cap->buff->list); +} + +static void c3_isp_cap_start(struct c3_isp_capture *cap) +{ + u32 mask; + u32 val; + + scoped_guard(spinlock_irqsave, &cap->buff_lock) + c3_isp_cap_cfg_buff(cap); + + c3_isp_cap_wrmifx3_format(cap); + + if (cap->id == C3_ISP_CAP_DEV_0) { + mask = ISP_TOP_PATH_EN_WRMIF0_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF0_EN; + } else if (cap->id == C3_ISP_CAP_DEV_1) { + mask = ISP_TOP_PATH_EN_WRMIF1_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF1_EN; + } else { + mask = ISP_TOP_PATH_EN_WRMIF2_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF2_EN; + } + + c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, mask, val); +} + +static void c3_isp_cap_stop(struct c3_isp_capture *cap) +{ + u32 mask; + u32 val; + + if (cap->id == C3_ISP_CAP_DEV_0) { + mask = ISP_TOP_PATH_EN_WRMIF0_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF0_DIS; + } else if (cap->id == C3_ISP_CAP_DEV_1) { + mask = ISP_TOP_PATH_EN_WRMIF1_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF1_DIS; + } else { + mask = ISP_TOP_PATH_EN_WRMIF2_EN_MASK; + val = ISP_TOP_PATH_EN_WRMIF2_DIS; + } + + c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, mask, val); +} + +static void c3_isp_cap_done(struct c3_isp_capture *cap) +{ + struct c3_isp_cap_buffer *buff = cap->buff; + + guard(spinlock_irqsave)(&cap->buff_lock); + + if (buff) { + buff->vb.sequence = cap->isp->frm_sequence; + buff->vb.vb2_buf.timestamp = ktime_get(); + buff->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + c3_isp_cap_cfg_buff(cap); +} + +/* V4L2 video operations */ + +static const struct c3_isp_cap_format_info *c3_cap_find_fmt(u32 fourcc) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(cap_formats); i++) { + if (cap_formats[i].fourcc == fourcc) + return &cap_formats[i]; + } + + return NULL; +} + +static void c3_cap_try_fmt(struct v4l2_pix_format_mplane *pix_mp) +{ + const struct c3_isp_cap_format_info *fmt; + const struct v4l2_format_info *info; + struct v4l2_plane_pix_format *plane; + + fmt = c3_cap_find_fmt(pix_mp->pixelformat); + if (!fmt) + fmt = &cap_formats[0]; + + pix_mp->width = clamp(pix_mp->width, C3_ISP_MIN_WIDTH, + C3_ISP_MAX_WIDTH); + pix_mp->height = clamp(pix_mp->height, C3_ISP_MIN_HEIGHT, + C3_ISP_MAX_HEIGHT); + pix_mp->pixelformat = fmt->fourcc; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + info = v4l2_format_info(fmt->fourcc); + pix_mp->num_planes = info->mem_planes; + memset(pix_mp->plane_fmt, 0, sizeof(pix_mp->plane_fmt)); + + for (unsigned int i = 0; i < info->comp_planes; i++) { + unsigned int hdiv = (i == 0) ? 1 : info->hdiv; + unsigned int vdiv = (i == 0) ? 1 : info->vdiv; + + plane = &pix_mp->plane_fmt[i]; + + plane->bytesperline = DIV_ROUND_UP(pix_mp->width, hdiv) * + info->bpp[i] / info->bpp_div[i]; + plane->bytesperline = ALIGN(plane->bytesperline, + C3_ISP_DMA_SIZE_ALIGN_BYTES); + plane->sizeimage = plane->bytesperline * + DIV_ROUND_UP(pix_mp->height, vdiv); + } +} + +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap, + enum vb2_buffer_state state) +{ + struct c3_isp_cap_buffer *buff; + + guard(spinlock_irqsave)(&cap->buff_lock); + + if (cap->buff) { + vb2_buffer_done(&cap->buff->vb.vb2_buf, state); + cap->buff = NULL; + } + + while (!list_empty(&cap->pending)) { + buff = list_first_entry(&cap->pending, + struct c3_isp_cap_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, state); + } +} + +static int c3_isp_cap_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, "AML C3 ISP", sizeof(cap->card)); + + return 0; +} + +static int c3_isp_cap_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + const struct c3_isp_cap_format_info *fmt; + unsigned int index = 0; + unsigned int i; + + if (!f->mbus_code) { + if (f->index >= ARRAY_SIZE(cap_formats)) + return -EINVAL; + + fmt = &cap_formats[f->index]; + f->pixelformat = fmt->fourcc; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(cap_formats); i++) { + fmt = &cap_formats[i]; + if (f->mbus_code != fmt->mbus_code) + continue; + + if (index++ == f->index) { + f->pixelformat = cap_formats[i].fourcc; + return 0; + } + } + + return -EINVAL; +} + +static int c3_isp_cap_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_capture *cap = video_drvdata(file); + + f->fmt.pix_mp = cap->format.pix_mp; + + return 0; +} + +static int c3_isp_cap_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_capture *cap = video_drvdata(file); + + c3_cap_try_fmt(&f->fmt.pix_mp); + + cap->format.pix_mp = f->fmt.pix_mp; + cap->format.info = c3_cap_find_fmt(f->fmt.pix_mp.pixelformat); + + return 0; +} + +static int c3_isp_cap_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + c3_cap_try_fmt(&f->fmt.pix_mp); + + return 0; +} + +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct c3_isp_cap_format_info *fmt; + + if (fsize->index) + return -EINVAL; + + fmt = c3_cap_find_fmt(fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = C3_ISP_MIN_WIDTH; + fsize->stepwise.min_height = C3_ISP_MIN_HEIGHT; + fsize->stepwise.max_width = C3_ISP_MAX_WIDTH; + fsize->stepwise.max_height = C3_ISP_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; + + return 0; +} + +static const struct v4l2_ioctl_ops isp_cap_v4l2_ioctl_ops = { + .vidioc_querycap = c3_isp_cap_querycap, + .vidioc_enum_fmt_vid_cap = c3_isp_cap_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = c3_isp_cap_g_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = c3_isp_cap_s_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = c3_isp_cap_try_fmt_mplane, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_framesizes = c3_isp_cap_enum_frmsize, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations isp_cap_v4l2_fops = { + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int c3_isp_cap_link_validate(struct media_link *link) +{ + struct video_device *vdev = + media_entity_to_video_device(link->sink->entity); + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(link->source->entity); + struct c3_isp_capture *cap = video_get_drvdata(vdev); + struct v4l2_pix_format_mplane *pix_mp = &cap->format.pix_mp; + struct v4l2_subdev_format src_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index, + }; + const struct c3_isp_cap_format_info *info = cap->format.info; + int ret; + + ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt); + if (ret) + return ret; + + if (src_fmt.format.width != pix_mp->width || + src_fmt.format.height != pix_mp->height || + src_fmt.format.code != info->mbus_code) { + dev_err(cap->isp->dev, + "link %s: %u -> %s: %u not valid: 0x%04x/%ux%u not match 0x%04x/%ux%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index, + src_fmt.format.code, src_fmt.format.width, + src_fmt.format.height, info->mbus_code, + pix_mp->width, pix_mp->height); + + return -EPIPE; + }; + + return 0; +} + +static const struct media_entity_operations isp_cap_entity_ops = { + .link_validate = c3_isp_cap_link_validate, +}; + +static int c3_isp_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(q); + const struct v4l2_pix_format_mplane *pix_mp = &cap->format.pix_mp; + unsigned int i; + + if (*num_planes) { + if (*num_planes != pix_mp->num_planes) + return -EINVAL; + + for (i = 0; i < pix_mp->num_planes; i++) + if (sizes[i] < pix_mp->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + *num_planes = pix_mp->num_planes; + for (i = 0; i < pix_mp->num_planes; i++) + sizes[i] = pix_mp->plane_fmt[i].sizeimage; + + return 0; +} + +static void c3_isp_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_cap_buffer *buf = + container_of(v4l2_buf, struct c3_isp_cap_buffer, vb); + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + + guard(spinlock_irqsave)(&cap->buff_lock); + + list_add_tail(&buf->list, &cap->pending); +} + +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size; + + for (unsigned int i = 0; i < cap->format.pix_mp.num_planes; i++) { + size = cap->format.pix_mp.plane_fmt[i].sizeimage; + if (vb2_plane_size(vb, i) < size) { + dev_err(cap->isp->dev, + "User buffer too small (%ld < %lu)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static int c3_isp_vb2_buf_init(struct vb2_buffer *vb) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_cap_buffer *buf = + container_of(v4l2_buf, struct c3_isp_cap_buffer, vb); + + for (unsigned int i = 0; i < cap->format.pix_mp.num_planes; i++) + buf->dma_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + + return 0; +} + +static int c3_isp_vb2_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(q); + int ret; + + ret = video_device_pipeline_start(&cap->vdev, &cap->isp->pipe); + if (ret) { + dev_err(cap->isp->dev, + "Failed to start cap%u pipeline: %d\n", cap->id, ret); + goto err_return_buffers; + } + + ret = c3_isp_cap_dummy_buff_create(cap); + if (ret) + goto err_pipeline_stop; + + ret = pm_runtime_resume_and_get(cap->isp->dev); + if (ret) + goto err_dummy_destroy; + + c3_isp_cap_start(cap); + + ret = v4l2_subdev_enable_streams(&cap->rsz->sd, C3_ISP_RSZ_PAD_SOURCE, + BIT(0)); + if (ret) + goto err_pm_put; + + return 0; + +err_pm_put: + pm_runtime_put(cap->isp->dev); +err_dummy_destroy: + c3_isp_cap_dummy_buff_destroy(cap); +err_pipeline_stop: + video_device_pipeline_stop(&cap->vdev); +err_return_buffers: + c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void c3_isp_vb2_stop_streaming(struct vb2_queue *q) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(q); + + c3_isp_cap_stop(cap); + + c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR); + + v4l2_subdev_disable_streams(&cap->rsz->sd, C3_ISP_RSZ_PAD_SOURCE, + BIT(0)); + + pm_runtime_put(cap->isp->dev); + + c3_isp_cap_dummy_buff_destroy(cap); + + video_device_pipeline_stop(&cap->vdev); +} + +static const struct vb2_ops isp_video_vb2_ops = { + .queue_setup = c3_isp_vb2_queue_setup, + .buf_queue = c3_isp_vb2_buf_queue, + .buf_prepare = c3_isp_vb2_buf_prepare, + .buf_init = c3_isp_vb2_buf_init, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = c3_isp_vb2_start_streaming, + .stop_streaming = c3_isp_vb2_stop_streaming, +}; + +static int c3_isp_register_capture(struct c3_isp_capture *cap) +{ + struct video_device *vdev = &cap->vdev; + struct vb2_queue *vb2_q = &cap->vb2_q; + int ret; + + snprintf(vdev->name, sizeof(vdev->name), "c3-isp-cap%u", cap->id); + vdev->fops = &isp_cap_v4l2_fops; + vdev->ioctl_ops = &isp_cap_v4l2_ioctl_ops; + vdev->v4l2_dev = &cap->isp->v4l2_dev; + vdev->entity.ops = &isp_cap_entity_ops; + vdev->lock = &cap->lock; + vdev->minor = -1; + vdev->queue = vb2_q; + vdev->release = video_device_release_empty; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_RX; + video_set_drvdata(vdev, cap); + + vb2_q->drv_priv = cap; + vb2_q->mem_ops = &vb2_dma_contig_memops; + vb2_q->ops = &isp_video_vb2_ops; + vb2_q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + vb2_q->io_modes = VB2_DMABUF | VB2_MMAP; + vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb2_q->buf_struct_size = sizeof(struct c3_isp_cap_buffer); + vb2_q->dev = cap->isp->dev; + vb2_q->lock = &cap->lock; + + ret = vb2_queue_init(vb2_q); + if (ret) + goto err_destroy; + + cap->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad); + if (ret) + goto err_queue_release; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(cap->isp->dev, + "Failed to register %s: %d\n", vdev->name, ret); + goto err_entity_cleanup; + } + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&vdev->entity); +err_queue_release: + vb2_queue_release(vb2_q); +err_destroy: + mutex_destroy(&cap->lock); + return ret; +} + +int c3_isp_captures_register(struct c3_isp_device *isp) +{ + int ret; + unsigned int i; + struct c3_isp_capture *cap; + + for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) { + cap = &isp->caps[i]; + memset(cap, 0, sizeof(*cap)); + + cap->format.pix_mp.width = C3_ISP_DEFAULT_WIDTH; + cap->format.pix_mp.height = C3_ISP_DEFAULT_HEIGHT; + cap->format.pix_mp.field = V4L2_FIELD_NONE; + cap->format.pix_mp.pixelformat = V4L2_PIX_FMT_NV21; + cap->format.pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + + c3_cap_try_fmt(&cap->format.pix_mp); + + cap->id = i; + cap->rsz = &isp->resizers[i]; + cap->isp = isp; + INIT_LIST_HEAD(&cap->pending); + spin_lock_init(&cap->buff_lock); + mutex_init(&cap->lock); + + ret = c3_isp_register_capture(cap); + if (ret) { + cap->isp = NULL; + mutex_destroy(&cap->lock); + c3_isp_captures_unregister(isp); + return ret; + } + } + + return 0; +} + +void c3_isp_captures_unregister(struct c3_isp_device *isp) +{ + unsigned int i; + struct c3_isp_capture *cap; + + for (i = C3_ISP_CAP_DEV_0; i < C3_ISP_NUM_CAP_DEVS; i++) { + cap = &isp->caps[i]; + + if (!cap->isp) + continue; + vb2_queue_release(&cap->vb2_q); + media_entity_cleanup(&cap->vdev.entity); + video_unregister_device(&cap->vdev); + mutex_destroy(&cap->lock); + } +} + +void c3_isp_captures_isr(struct c3_isp_device *isp) +{ + c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_0]); + c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_1]); + c3_isp_cap_done(&isp->caps[C3_ISP_CAP_DEV_2]); +} diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h new file mode 100644 index 000000000000..c6932232d15d --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#ifndef __C3_ISP_COMMON_H__ +#define __C3_ISP_COMMON_H__ + +#include + +#include +#include +#include +#include +#include + +#define C3_ISP_DRIVER_NAME "c3-isp" +#define C3_ISP_CLOCK_NUM_MAX 3 + +#define C3_ISP_DEFAULT_WIDTH 1920 +#define C3_ISP_DEFAULT_HEIGHT 1080 +#define C3_ISP_MAX_WIDTH 2888 +#define C3_ISP_MAX_HEIGHT 2240 +#define C3_ISP_MIN_WIDTH 160 +#define C3_ISP_MIN_HEIGHT 120 + +#define C3_ISP_DMA_SIZE_ALIGN_BYTES 16 + +enum c3_isp_core_pads { + C3_ISP_CORE_PAD_SINK_VIDEO, + C3_ISP_CORE_PAD_SINK_PARAMS, + C3_ISP_CORE_PAD_SOURCE_STATS, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + C3_ISP_CORE_PAD_MAX +}; + +enum c3_isp_resizer_ids { + C3_ISP_RSZ_0, + C3_ISP_RSZ_1, + C3_ISP_RSZ_2, + C3_ISP_NUM_RSZ +}; + +enum c3_isp_resizer_pads { + C3_ISP_RSZ_PAD_SINK, + C3_ISP_RSZ_PAD_SOURCE, + C3_ISP_RSZ_PAD_MAX +}; + +enum c3_isp_cap_devs { + C3_ISP_CAP_DEV_0, + C3_ISP_CAP_DEV_1, + C3_ISP_CAP_DEV_2, + C3_ISP_NUM_CAP_DEVS +}; + +enum c3_isp_planes { + C3_ISP_PLANE_Y, + C3_ISP_PLANE_UV, + C3_ISP_NUM_PLANES +}; + +/* + * struct c3_isp_cap_format_info - The image format of capture device + * + * @mbus_code: the mbus code + * @fourcc: the pixel format + * @format: defines the output format of hardware + * @uv_swap: defines the uv swap flag of hardware + * @plane: defines the mutil plane of hardware + * @hdiv: horizontal chroma subsampling factor of hardware + * @vdiv: vertical chroma subsampling factor of hardware + */ +struct c3_isp_cap_format_info { + u32 mbus_code; + u32 fourcc; + u32 format; + u32 uv_swap; + u32 plane; + u8 hdiv; + u8 vdiv; +}; + +/* + * struct c3_isp_cap_buffer - A container of vb2 buffer used by the video + * devices: capture video devices + * + * @vb: vb2 buffer + * @dma_addr: buffer physical address + * @list: entry of the buffer in the queue + */ +struct c3_isp_cap_buffer { + struct vb2_v4l2_buffer vb; + dma_addr_t dma_addr[C3_ISP_NUM_PLANES]; + struct list_head list; +}; + +/* + * struct c3_isp_stats_dma_buffer - A container of vb2 buffer used by the video + * devices: stats video devices + * + * @vb: vb2 buffer + * @dma_addr: buffer physical address + * @list: entry of the buffer in the queue + */ +struct c3_isp_stats_buffer { + struct vb2_v4l2_buffer vb; + dma_addr_t dma_addr; + struct list_head list; +}; + +/* + * struct c3_isp_params_buffer - A container of vb2 buffer used by the + * params video device + * + * @vb: vb2 buffer + * @cfg: scratch buffer used for caching the ISP configuration parameters + * @list: entry of the buffer in the queue + */ +struct c3_isp_params_buffer { + struct vb2_v4l2_buffer vb; + void *cfg; + struct list_head list; +}; + +/* + * struct c3_isp_dummy_buffer - A buffer to write the next frame to in case + * there are no vb2 buffers available. + * + * @vaddr: return value of call to dma_alloc_attrs + * @dma_addr: dma address of the buffer + * @size: size of the buffer + */ +struct c3_isp_dummy_buffer { + void *vaddr; + dma_addr_t dma_addr; + u32 size; +}; + +/* + * struct c3_isp_core - ISP core subdev + * + * @sd: ISP sub-device + * @pads: ISP sub-device pads + * @src_pad: source sub-device pad + * @isp: pointer to c3_isp_device + */ +struct c3_isp_core { + struct v4l2_subdev sd; + struct media_pad pads[C3_ISP_CORE_PAD_MAX]; + struct media_pad *src_pad; + struct c3_isp_device *isp; +}; + +/* + * struct c3_isp_resizer - ISP resizer subdev + * + * @id: resizer id + * @sd: resizer sub-device + * @pads: resizer sub-device pads + * @src_sd: source sub-device + * @isp: pointer to c3_isp_device + */ +struct c3_isp_resizer { + enum c3_isp_resizer_ids id; + struct v4l2_subdev sd; + struct media_pad pads[C3_ISP_RSZ_PAD_MAX]; + struct v4l2_subdev *src_sd; + struct c3_isp_device *isp; +}; + +/* + * struct c3_isp_stats - ISP statistics device + * + * @vb2_q: vb2 buffer queue + * @vdev: video node + * @vfmt: v4l2_format of the metadata format + * @pad: media pad + * @lock: protects vb2_q, vdev + * @isp: pointer to c3_isp_device + * @buff: in use buffer + * @buff_lock: protects stats buffer + * @pending: stats buffer list head + */ +struct c3_isp_stats { + struct vb2_queue vb2_q; + struct video_device vdev; + struct v4l2_format vfmt; + struct media_pad pad; + + struct mutex lock; /* Protects vb2_q, vdev */ + struct c3_isp_device *isp; + + struct c3_isp_stats_buffer *buff; + spinlock_t buff_lock; /* Protects stats buffer */ + struct list_head pending; +}; + +/* + * struct c3_isp_params - ISP parameters device + * + * @vb2_q: vb2 buffer queue + * @vdev: video node + * @vfmt: v4l2_format of the metadata format + * @pad: media pad + * @lock: protects vb2_q, vdev + * @isp: pointer to c3_isp_device + * @buff: in use buffer + * @buff_lock: protects stats buffer + * @pending: stats buffer list head + */ +struct c3_isp_params { + struct vb2_queue vb2_q; + struct video_device vdev; + struct v4l2_format vfmt; + struct media_pad pad; + + struct mutex lock; /* Protects vb2_q, vdev */ + struct c3_isp_device *isp; + + struct c3_isp_params_buffer *buff; + spinlock_t buff_lock; /* Protects params buffer */ + struct list_head pending; +}; + +/* + * struct c3_isp_capture - ISP capture device + * + * @id: capture device ID + * @vb2_q: vb2 buffer queue + * @vdev: video node + * @pad: media pad + * @lock: protects vb2_q, vdev + * @isp: pointer to c3_isp_device + * @rsz: pointer to c3_isp_resizer + * @buff: in use buffer + * @buff_lock: protects capture buffer + * @pending: capture buffer list head + * @format.info: a pointer to the c3_isp_capture_format of the pixel format + * @format.fmt: buffer format + */ +struct c3_isp_capture { + enum c3_isp_cap_devs id; + struct vb2_queue vb2_q; + struct video_device vdev; + struct media_pad pad; + + struct mutex lock; /* Protects vb2_q, vdev */ + struct c3_isp_device *isp; + struct c3_isp_resizer *rsz; + + struct c3_isp_dummy_buffer dummy_buff; + struct c3_isp_cap_buffer *buff; + spinlock_t buff_lock; /* Protects stream buffer */ + struct list_head pending; + struct { + const struct c3_isp_cap_format_info *info; + struct v4l2_pix_format_mplane pix_mp; + } format; +}; + +/** + * struct c3_isp_info - ISP information + * + * @clocks: array of ISP clock names + * @clock_num: actual clock number + */ +struct c3_isp_info { + char *clocks[C3_ISP_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/** + * struct c3_isp_device - ISP platform device + * + * @dev: pointer to the struct device + * @base: base register address + * @clks: array of clocks + * @notifier: notifier to register on the v4l2-async API + * @v4l2_dev: v4l2_device variable + * @media_dev: media device variable + * @pipe: media pipeline + * @core: ISP core subdev + * @resizer: ISP resizer subdev + * @stats: ISP stats device + * @params: ISP params device + * @caps: array of ISP capture device + * @frm_sequence: used to record frame id + * @info: version-specific ISP information + */ +struct c3_isp_device { + struct device *dev; + void __iomem *base; + struct clk_bulk_data clks[C3_ISP_CLOCK_NUM_MAX]; + + struct v4l2_async_notifier notifier; + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct media_pipeline pipe; + + struct c3_isp_core core; + struct c3_isp_resizer resizers[C3_ISP_NUM_RSZ]; + struct c3_isp_stats stats; + struct c3_isp_params params; + struct c3_isp_capture caps[C3_ISP_NUM_CAP_DEVS]; + + u32 frm_sequence; + const struct c3_isp_info *info; +}; + +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg); +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val); +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val); + +void c3_isp_core_queue_sof(struct c3_isp_device *isp); +int c3_isp_core_register(struct c3_isp_device *isp); +void c3_isp_core_unregister(struct c3_isp_device *isp); +int c3_isp_resizers_register(struct c3_isp_device *isp); +void c3_isp_resizers_unregister(struct c3_isp_device *isp); +int c3_isp_captures_register(struct c3_isp_device *isp); +void c3_isp_captures_unregister(struct c3_isp_device *isp); +void c3_isp_captures_isr(struct c3_isp_device *isp); +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp); +int c3_isp_stats_register(struct c3_isp_device *isp); +void c3_isp_stats_unregister(struct c3_isp_device *isp); +void c3_isp_stats_isr(struct c3_isp_device *isp); +void c3_isp_params_pre_cfg(struct c3_isp_device *isp); +int c3_isp_params_register(struct c3_isp_device *isp); +void c3_isp_params_unregister(struct c3_isp_device *isp); +void c3_isp_params_isr(struct c3_isp_device *isp); + +#endif diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c new file mode 100644 index 000000000000..cdd1983c4418 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include + +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +#define C3_ISP_CORE_SUBDEV_NAME "c3-isp-core" + +#define C3_ISP_PHASE_OFFSET_0 0 +#define C3_ISP_PHASE_OFFSET_1 1 +#define C3_ISP_PHASE_OFFSET_NONE 0xff + +#define C3_ISP_CORE_DEF_SINK_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10 +#define C3_ISP_CORE_DEF_SRC_PAD_FMT MEDIA_BUS_FMT_YUV8_1X24 + +/* + * struct c3_isp_mbus_format_info - MBUS format information + * + * @mbus_code: the mbus code + * @pads: bitmask detailing valid pads for this mbus_code + * @xofst: horizontal phase offset of hardware + * @yofst: vertical phase offset of hardware + */ +struct c3_isp_mbus_format_info { + u32 mbus_code; + u32 pads; + u8 xofst; + u8 yofst; +}; + +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_fmts[] = { + /* RAW formats */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_YUV8_1X24, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, +}; + +static const struct c3_isp_mbus_format_info +*core_find_format_by_code(u32 code, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_fmts); i++) { + const struct c3_isp_mbus_format_info *info = + &c3_isp_core_mbus_fmts[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +static const struct c3_isp_mbus_format_info +*core_find_format_by_index(u32 index, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_fmts); i++) { + const struct c3_isp_mbus_format_info *info = + &c3_isp_core_mbus_fmts[i]; + + if (!(info->pads & BIT(pad))) + continue; + + if (!index) + return info; + + index--; + } + + return NULL; +} + +static void c3_isp_core_enable(struct c3_isp_device *isp) +{ + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRM_END_MASK, + ISP_TOP_IRQ_EN_FRM_END_EN); + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRM_RST_MASK, + ISP_TOP_IRQ_EN_FRM_RST_EN); + + /* Enable image data to ISP core */ + c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK, + ISP_TOP_PATH_SEL_CORE_MIPI_CORE); +} + +static void c3_isp_core_disable(struct c3_isp_device *isp) +{ + /* Disable image data to ISP core */ + c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, ISP_TOP_PATH_SEL_CORE_MASK, + ISP_TOP_PATH_SEL_CORE_CORE_DIS); + + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRM_END_MASK, + ISP_TOP_IRQ_EN_FRM_END_DIS); + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, ISP_TOP_IRQ_EN_FRM_RST_MASK, + ISP_TOP_IRQ_EN_FRM_RST_DIS); +} + +/* Set the phase offset of blc, wb and lns */ +static void c3_isp_core_lswb_ofst(struct c3_isp_device *isp, + u8 xofst, u8 yofst) +{ + c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, + ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK, + ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, + ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK, + ISP_LSWB_BLC_PHSOFST_VERT_OFST(yofst)); + + c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, + ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK, + ISP_LSWB_WB_PHSOFST_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, + ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK, + ISP_LSWB_WB_PHSOFST_VERT_OFST(yofst)); + + c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, + ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK, + ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, + ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK, + ISP_LSWB_LNS_PHSOFST_VERT_OFST(yofst)); +} + +/* Set the phase offset of af, ae and awb */ +static void c3_isp_core_3a_ofst(struct c3_isp_device *isp, + u8 xofst, u8 yofst) +{ + c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_HORIZ_OFST_MASK, + ISP_AF_CTRL_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_AF_CTRL, ISP_AF_CTRL_VERT_OFST_MASK, + ISP_AF_CTRL_VERT_OFST(yofst)); + + c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_HORIZ_OFST_MASK, + ISP_AE_CTRL_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_AE_CTRL, ISP_AE_CTRL_VERT_OFST_MASK, + ISP_AE_CTRL_VERT_OFST(yofst)); + + c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_HORIZ_OFST_MASK, + ISP_AWB_CTRL_HORIZ_OFST(xofst)); + c3_isp_update_bits(isp, ISP_AWB_CTRL, ISP_AWB_CTRL_VERT_OFST_MASK, + ISP_AWB_CTRL_VERT_OFST(yofst)); +} + +/* Set the phase offset of demosaic */ +static void c3_isp_core_dms_ofst(struct c3_isp_device *isp, + u8 xofst, u8 yofst) +{ + c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, + ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK, + ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(xofst)); + c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, + ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK, + ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(yofst)); +} + +static void c3_isp_core_cfg_format(struct c3_isp_device *isp, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + const struct c3_isp_mbus_format_info *isp_fmt; + + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO); + isp_fmt = core_find_format_by_code(fmt->code, + C3_ISP_CORE_PAD_SINK_VIDEO); + + c3_isp_write(isp, ISP_TOP_INPUT_SIZE, + ISP_TOP_INPUT_SIZE_HORIZ_SIZE(fmt->width) | + ISP_TOP_INPUT_SIZE_VERT_SIZE(fmt->height)); + c3_isp_write(isp, ISP_TOP_FRM_SIZE, + ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(fmt->width) | + ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(fmt->height)); + + c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, + ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK, + ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(fmt->width)); + + c3_isp_write(isp, ISP_AF_HV_SIZE, + ISP_AF_HV_SIZE_GLB_WIN_XSIZE(fmt->width) | + ISP_AF_HV_SIZE_GLB_WIN_YSIZE(fmt->height)); + c3_isp_write(isp, ISP_AE_HV_SIZE, + ISP_AE_HV_SIZE_HORIZ_SIZE(fmt->width) | + ISP_AE_HV_SIZE_VERT_SIZE(fmt->height)); + c3_isp_write(isp, ISP_AWB_HV_SIZE, + ISP_AWB_HV_SIZE_HORIZ_SIZE(fmt->width) | + ISP_AWB_HV_SIZE_VERT_SIZE(fmt->height)); + + c3_isp_core_lswb_ofst(isp, isp_fmt->xofst, isp_fmt->yofst); + c3_isp_core_3a_ofst(isp, isp_fmt->xofst, isp_fmt->yofst); + c3_isp_core_dms_ofst(isp, isp_fmt->xofst, isp_fmt->yofst); +} + +static int c3_isp_core_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_isp_core *core = v4l2_get_subdevdata(sd); + struct media_pad *sink_pad; + struct v4l2_subdev *src_sd; + int ret; + + core->isp->frm_sequence = 0; + c3_isp_core_cfg_format(core->isp, state); + c3_isp_core_enable(core->isp); + + sink_pad = &core->pads[C3_ISP_CORE_PAD_SINK_VIDEO]; + core->src_pad = media_pad_remote_pad_unique(sink_pad); + if (IS_ERR(core->src_pad)) { + dev_dbg(core->isp->dev, + "Failed to get source pad for ISP core\n"); + return -EPIPE; + } + + src_sd = media_entity_to_v4l2_subdev(core->src_pad->entity); + + ret = v4l2_subdev_enable_streams(src_sd, core->src_pad->index, BIT(0)); + if (ret) { + c3_isp_core_disable(core->isp); + return ret; + } + + return 0; +} + +static int c3_isp_core_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_isp_core *core = v4l2_get_subdevdata(sd); + struct v4l2_subdev *src_sd; + + if (core->src_pad) { + src_sd = media_entity_to_v4l2_subdev(core->src_pad->entity); + v4l2_subdev_disable_streams(src_sd, core->src_pad->index, + BIT(0)); + } + core->src_pad = NULL; + + c3_isp_core_disable(core->isp); + + return 0; +} + +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct c3_isp_mbus_format_info *info; + + switch (code->pad) { + case C3_ISP_CORE_PAD_SINK_VIDEO: + case C3_ISP_CORE_PAD_SOURCE_VIDEO: + info = core_find_format_by_index(code->index, code->pad); + if (!info) + return -EINVAL; + + code->code = info->mbus_code; + + break; + case C3_ISP_CORE_PAD_SINK_PARAMS: + case C3_ISP_CORE_PAD_SOURCE_STATS: + if (code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_METADATA_FIXED; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + const struct c3_isp_mbus_format_info *isp_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, format->pad); + src_fmt = v4l2_subdev_state_get_format(state, + C3_ISP_CORE_PAD_SOURCE_VIDEO); + + isp_fmt = core_find_format_by_code(format->format.code, format->pad); + if (!isp_fmt) + sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT; + else + sink_fmt->code = format->format.code; + + sink_fmt->width = clamp_t(u32, format->format.width, + C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH); + sink_fmt->height = clamp_t(u32, format->format.height, + C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT); + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + src_fmt->width = sink_fmt->width; + src_fmt->height = sink_fmt->height; + + format->format = *sink_fmt; +} + +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + const struct c3_isp_mbus_format_info *isp_fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, + C3_ISP_CORE_PAD_SINK_VIDEO); + src_fmt = v4l2_subdev_state_get_format(state, format->pad); + + isp_fmt = core_find_format_by_code(format->format.code, format->pad); + if (!isp_fmt) + src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT; + else + src_fmt->code = format->format.code; + + src_fmt->width = sink_fmt->width; + src_fmt->height = sink_fmt->height; + src_fmt->field = V4L2_FIELD_NONE; + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + format->format = *src_fmt; +} + +static int c3_isp_core_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + if (format->pad == C3_ISP_CORE_PAD_SINK_VIDEO) + c3_isp_core_set_sink_fmt(state, format); + else if (format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO) + c3_isp_core_set_source_fmt(state, format); + else + format->format = + *v4l2_subdev_state_get_format(state, format->pad); + + return 0; +} + +static int c3_isp_core_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + /* Video sink pad */ + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO); + fmt->width = C3_ISP_DEFAULT_WIDTH; + fmt->height = C3_ISP_DEFAULT_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->code = C3_ISP_CORE_DEF_SINK_PAD_FMT; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + /* Video source pad */ + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO); + fmt->width = C3_ISP_DEFAULT_WIDTH; + fmt->height = C3_ISP_DEFAULT_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + /* Parameters pad */ + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS); + fmt->width = 0; + fmt->height = 0; + fmt->field = V4L2_FIELD_NONE; + fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + /* Statistics pad */ + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS); + fmt->width = 0; + fmt->height = 0; + fmt->field = V4L2_FIELD_NONE; + fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + return 0; +} + +static int c3_isp_core_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type != V4L2_EVENT_FRAME_SYNC) + return -EINVAL; + + /* V4L2_EVENT_FRAME_SYNC doesn't need id, so should set 0 */ + if (sub->id != 0) + return -EINVAL; + + return v4l2_event_subscribe(fh, sub, 0, NULL); +} + +static const struct v4l2_subdev_pad_ops c3_isp_core_pad_ops = { + .enum_mbus_code = c3_isp_core_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = c3_isp_core_set_fmt, + .enable_streams = c3_isp_core_enable_streams, + .disable_streams = c3_isp_core_disable_streams, +}; + +static const struct v4l2_subdev_core_ops c3_isp_core_core_ops = { + .subscribe_event = c3_isp_core_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops c3_isp_core_subdev_ops = { + .core = &c3_isp_core_core_ops, + .pad = &c3_isp_core_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops c3_isp_core_internal_ops = { + .init_state = c3_isp_core_init_state, +}; + +static int c3_isp_core_link_validate(struct media_link *link) +{ + if (link->sink->index == C3_ISP_CORE_PAD_SINK_PARAMS) + return 0; + + return v4l2_subdev_link_validate(link); +} + +/* Media entity operations */ +static const struct media_entity_operations c3_isp_core_entity_ops = { + .link_validate = c3_isp_core_link_validate, +}; + +void c3_isp_core_queue_sof(struct c3_isp_device *isp) +{ + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + }; + + event.u.frame_sync.frame_sequence = isp->frm_sequence; + v4l2_event_queue(isp->core.sd.devnode, &event); +} + +int c3_isp_core_register(struct c3_isp_device *isp) +{ + struct c3_isp_core *core = &isp->core; + struct v4l2_subdev *sd = &core->sd; + int ret; + + v4l2_subdev_init(sd, &c3_isp_core_subdev_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &c3_isp_core_internal_ops; + snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &c3_isp_core_entity_ops; + + core->isp = isp; + sd->dev = isp->dev; + v4l2_set_subdevdata(sd, core); + + core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK; + core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK; + core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE; + core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, + core->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd); + if (ret) + goto err_subdev_cleanup; + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(sd); +err_entity_cleanup: + media_entity_cleanup(&sd->entity); + return ret; +} + +void c3_isp_core_unregister(struct c3_isp_device *isp) +{ + struct c3_isp_core *core = &isp->core; + struct v4l2_subdev *sd = &core->sd; + + v4l2_device_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); +} diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c new file mode 100644 index 000000000000..185b34a321a4 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-dev.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +u32 c3_isp_read(struct c3_isp_device *isp, u32 reg) +{ + return readl(isp->base + reg); +} + +void c3_isp_write(struct c3_isp_device *isp, u32 reg, u32 val) +{ + writel(val, isp->base + reg); +} + +void c3_isp_update_bits(struct c3_isp_device *isp, u32 reg, u32 mask, u32 val) +{ + u32 orig, tmp; + + orig = c3_isp_read(isp, reg); + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + c3_isp_write(isp, reg, tmp); +} + +/* PM runtime suspend */ +static int c3_isp_runtime_suspend(struct device *dev) +{ + struct c3_isp_device *isp = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks); + + return 0; +} + +/* PM runtime resume */ +static int c3_isp_runtime_resume(struct device *dev) +{ + struct c3_isp_device *isp = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks); +} + +static const struct dev_pm_ops c3_isp_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + RUNTIME_PM_OPS(c3_isp_runtime_suspend, + c3_isp_runtime_resume, NULL) +}; + +/* IRQ handling */ +static irqreturn_t c3_isp_irq_handler(int irq, void *dev) +{ + struct c3_isp_device *isp = dev; + u32 status; + + /* Get irq status and clear irq status */ + status = c3_isp_read(isp, ISP_TOP_RO_IRQ_STAT); + c3_isp_write(isp, ISP_TOP_IRQ_CLR, status); + + if (status & ISP_TOP_RO_IRQ_STAT_FRM_END_MASK) { + c3_isp_stats_isr(isp); + c3_isp_params_isr(isp); + c3_isp_captures_isr(isp); + isp->frm_sequence++; + } + + if (status & ISP_TOP_RO_IRQ_STAT_FRM_RST_MASK) + c3_isp_core_queue_sof(isp); + + return IRQ_HANDLED; +} + +/* Subdev notifier register */ +static int c3_isp_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct c3_isp_device *isp = + container_of(notifier, struct c3_isp_device, notifier); + struct media_pad *sink = + &isp->core.sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static int c3_isp_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct c3_isp_device *isp = + container_of(notifier, struct c3_isp_device, notifier); + + return v4l2_device_register_subdev_nodes(&isp->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = { + .bound = c3_isp_notify_bound, + .complete = c3_isp_notify_complete, +}; + +static int c3_isp_async_nf_register(struct c3_isp_device *isp) +{ + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + int ret; + + v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep, + struct v4l2_async_connection); + fwnode_handle_put(ep); + + if (IS_ERR(asc)) + return PTR_ERR(asc); + + isp->notifier.ops = &c3_isp_notify_ops; + ret = v4l2_async_nf_register(&isp->notifier); + if (ret) + v4l2_async_nf_cleanup(&isp->notifier); + + return ret; +} + +static void c3_isp_async_nf_unregister(struct c3_isp_device *isp) +{ + v4l2_async_nf_unregister(&isp->notifier); + v4l2_async_nf_cleanup(&isp->notifier); +} + +static int c3_isp_media_register(struct c3_isp_device *isp) +{ + struct media_device *media_dev = &isp->media_dev; + struct v4l2_device *v4l2_dev = &isp->v4l2_dev; + int ret; + + /* Initialize media device */ + strscpy(media_dev->model, C3_ISP_DRIVER_NAME, sizeof(media_dev->model)); + media_dev->dev = isp->dev; + + media_device_init(media_dev); + + /* Initialize v4l2 device */ + v4l2_dev->mdev = media_dev; + strscpy(v4l2_dev->name, C3_ISP_DRIVER_NAME, sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(isp->dev, v4l2_dev); + if (ret) + goto err_media_dev_cleanup; + + ret = media_device_register(&isp->media_dev); + if (ret) { + dev_err(isp->dev, "Failed to register media device: %d\n", ret); + goto err_unreg_v4l2_dev; + } + + return 0; + +err_unreg_v4l2_dev: + v4l2_device_unregister(&isp->v4l2_dev); +err_media_dev_cleanup: + media_device_cleanup(media_dev); + return ret; +} + +static void c3_isp_media_unregister(struct c3_isp_device *isp) +{ + media_device_unregister(&isp->media_dev); + v4l2_device_unregister(&isp->v4l2_dev); + media_device_cleanup(&isp->media_dev); +} + +static void c3_isp_remove_links(struct c3_isp_device *isp) +{ + unsigned int i; + + media_entity_remove_links(&isp->core.sd.entity); + + for (i = 0; i < C3_ISP_NUM_RSZ; i++) + media_entity_remove_links(&isp->resizers[i].sd.entity); + + for (i = 0; i < C3_ISP_NUM_CAP_DEVS; i++) + media_entity_remove_links(&isp->caps[i].vdev.entity); +} + +static int c3_isp_create_links(struct c3_isp_device *isp) +{ + unsigned int i; + int ret; + + for (i = 0; i < C3_ISP_NUM_RSZ; i++) { + ret = media_create_pad_link(&isp->resizers[i].sd.entity, + C3_ISP_RSZ_PAD_SOURCE, + &isp->caps[i].vdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(isp->dev, + "Failed to link rsz %u and cap %u\n", i, i); + goto err_remove_links; + } + + ret = media_create_pad_link(&isp->core.sd.entity, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + &isp->resizers[i].sd.entity, + C3_ISP_RSZ_PAD_SINK, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(isp->dev, + "Failed to link core and rsz %u\n", i); + goto err_remove_links; + } + } + + ret = media_create_pad_link(&isp->core.sd.entity, + C3_ISP_CORE_PAD_SOURCE_STATS, + &isp->stats.vdev.entity, + 0, MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(isp->dev, "Failed to link core and stats\n"); + goto err_remove_links; + } + + ret = media_create_pad_link(&isp->params.vdev.entity, 0, + &isp->core.sd.entity, + C3_ISP_CORE_PAD_SINK_PARAMS, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(isp->dev, "Failed to link params and core\n"); + goto err_remove_links; + } + + return 0; + +err_remove_links: + c3_isp_remove_links(isp); + return ret; +} + +static int c3_isp_videos_register(struct c3_isp_device *isp) +{ + int ret; + + ret = c3_isp_captures_register(isp); + if (ret) + return ret; + + ret = c3_isp_stats_register(isp); + if (ret) + goto err_captures_unregister; + + ret = c3_isp_params_register(isp); + if (ret) + goto err_stats_unregister; + + ret = c3_isp_create_links(isp); + if (ret) + goto err_params_unregister; + + return 0; + +err_params_unregister: + c3_isp_params_unregister(isp); +err_stats_unregister: + c3_isp_stats_unregister(isp); +err_captures_unregister: + c3_isp_captures_unregister(isp); + return ret; +} + +static void c3_isp_videos_unregister(struct c3_isp_device *isp) +{ + c3_isp_remove_links(isp); + c3_isp_params_unregister(isp); + c3_isp_stats_unregister(isp); + c3_isp_captures_unregister(isp); +} + +static int c3_isp_get_clocks(struct c3_isp_device *isp) +{ + const struct c3_isp_info *info = isp->info; + + for (unsigned int i = 0; i < info->clock_num; i++) + isp->clks[i].id = info->clocks[i]; + + return devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks); +} + +static int c3_isp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct c3_isp_device *isp; + int irq; + int ret; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->info = of_device_get_match_data(dev); + isp->dev = dev; + + isp->base = devm_platform_ioremap_resource_byname(pdev, "isp"); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to ioremap resource\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = c3_isp_get_clocks(isp); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + platform_set_drvdata(pdev, isp); + + pm_runtime_enable(dev); + + ret = c3_isp_media_register(isp); + if (ret) + goto err_runtime_disable; + + ret = c3_isp_core_register(isp); + if (ret) + goto err_v4l2_unregister; + + ret = c3_isp_resizers_register(isp); + if (ret) + goto err_core_unregister; + + ret = c3_isp_async_nf_register(isp); + if (ret) + goto err_resizers_unregister; + + ret = devm_request_irq(dev, irq, + c3_isp_irq_handler, IRQF_SHARED, + dev_driver_string(dev), isp); + if (ret) + goto err_nf_unregister; + + ret = c3_isp_videos_register(isp); + if (ret) + goto err_nf_unregister; + + return 0; + +err_nf_unregister: + c3_isp_async_nf_unregister(isp); +err_resizers_unregister: + c3_isp_resizers_unregister(isp); +err_core_unregister: + c3_isp_core_unregister(isp); +err_v4l2_unregister: + c3_isp_media_unregister(isp); +err_runtime_disable: + pm_runtime_disable(dev); + return ret; +}; + +static void c3_isp_remove(struct platform_device *pdev) +{ + struct c3_isp_device *isp = platform_get_drvdata(pdev); + + c3_isp_videos_unregister(isp); + c3_isp_async_nf_unregister(isp); + c3_isp_core_unregister(isp); + c3_isp_resizers_unregister(isp); + c3_isp_media_unregister(isp); + pm_runtime_disable(isp->dev); +}; + +static const struct c3_isp_info isp_info = { + .clocks = {"vapb", "isp0"}, + .clock_num = 2 +}; + +static const struct of_device_id c3_isp_of_match[] = { + { .compatible = "amlogic,c3-isp", + .data = &isp_info }, + { }, +}; +MODULE_DEVICE_TABLE(of, c3_isp_of_match); + +static struct platform_driver c3_isp_driver = { + .probe = c3_isp_probe, + .remove = c3_isp_remove, + .driver = { + .name = "c3-isp", + .of_match_table = c3_isp_of_match, + .pm = pm_ptr(&c3_isp_pm_ops), + }, +}; + +module_platform_driver(c3_isp_driver); + +MODULE_AUTHOR("Keke Li "); +MODULE_DESCRIPTION("Amlogic C3 ISP pipeline"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c new file mode 100644 index 000000000000..f41fcfdc92f4 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-params.c @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include + +#include +#include +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +/* + * union c3_isp_params_block - Generalisation of a parameter block + * + * This union allows the driver to treat a block as a generic struct to this + * union and safely access the header and block-specific struct without having + * to resort to casting. The header member is accessed first, and the type field + * checked which allows the driver to determine which of the other members + * should be used. + * + * @header: The shared header struct embedded as the first member + * of all the possible other members. This member would be + * accessed first and the type field checked to determine + * which of the other members should be accessed. + * @awb_gains: For header.type == C3_ISP_PARAMS_BLOCK_AWB_GAINS + * @awb_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AWB_CONFIG + * @ae_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AE_CONFIG + * @af_cfg: For header.type == C3_ISP_PARAMS_BLOCK_AF_CONFIG + * @pst_gamma: For header.type == C3_ISP_PARAMS_BLOCK_PST_GAMMA + * @ccm: For header.type == C3_ISP_PARAMS_BLOCK_CCM + * @csc: For header.type == C3_ISP_PARAMS_BLOCK_CSC + * @blc: For header.type == C3_ISP_PARAMS_BLOCK_BLC + */ +union c3_isp_params_block { + struct c3_isp_params_block_header header; + struct c3_isp_params_awb_gains awb_gains; + struct c3_isp_params_awb_config awb_cfg; + struct c3_isp_params_ae_config ae_cfg; + struct c3_isp_params_af_config af_cfg; + struct c3_isp_params_pst_gamma pst_gamma; + struct c3_isp_params_ccm ccm; + struct c3_isp_params_csc csc; + struct c3_isp_params_blc blc; +}; + +typedef void (*c3_isp_block_handler)(struct c3_isp_device *isp, + const union c3_isp_params_block *block); + +struct c3_isp_params_handler { + size_t size; + c3_isp_block_handler handler; +}; + +#define to_c3_isp_params_buffer(vbuf) \ + container_of(vbuf, struct c3_isp_params_buffer, vb) + +/* Hardware configuration */ + +static void c3_isp_params_cfg_awb_gains(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_awb_gains *awb_gains = &block->awb_gains; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + ISP_TOP_BEO_CTRL_WB_EN_MASK, + ISP_TOP_BEO_CTRL_WB_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, + ISP_LSWB_WB_GAIN0_GR_GAIN_MASK, + ISP_LSWB_WB_GAIN0_GR_GAIN(awb_gains->gr_gain)); + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, + ISP_LSWB_WB_GAIN0_R_GAIN_MASK, + ISP_LSWB_WB_GAIN0_R_GAIN(awb_gains->r_gain)); + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, + ISP_LSWB_WB_GAIN1_B_GAIN_MASK, + ISP_LSWB_WB_GAIN1_B_GAIN(awb_gains->b_gain)); + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, + ISP_LSWB_WB_GAIN1_GB_GAIN_MASK, + ISP_LSWB_WB_GAIN1_GB_GAIN(awb_gains->gb_gain)); + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2, + ISP_LSWB_WB_GAIN2_IR_GAIN_MASK, + ISP_LSWB_WB_GAIN2_IR_GAIN(awb_gains->gb_gain)); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + ISP_TOP_BEO_CTRL_WB_EN_MASK, + ISP_TOP_BEO_CTRL_WB_EN); +} + +static void c3_isp_params_awb_wt(struct c3_isp_device *isp, + const struct c3_isp_params_awb_config *cfg) +{ + unsigned int zones_num; + unsigned int base; + unsigned int data; + unsigned int i; + + /* Set the weight address to 0 position */ + c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0); + + zones_num = cfg->horiz_zones_num * cfg->vert_zones_num; + + /* Need to write 8 weights at once */ + for (i = 0; i < zones_num / 8; i++) { + base = i * 8; + data = ISP_AWB_BLK_WT_DATA_WT(0, cfg->zone_weight[base + 0]) | + ISP_AWB_BLK_WT_DATA_WT(1, cfg->zone_weight[base + 1]) | + ISP_AWB_BLK_WT_DATA_WT(2, cfg->zone_weight[base + 2]) | + ISP_AWB_BLK_WT_DATA_WT(3, cfg->zone_weight[base + 3]) | + ISP_AWB_BLK_WT_DATA_WT(4, cfg->zone_weight[base + 4]) | + ISP_AWB_BLK_WT_DATA_WT(5, cfg->zone_weight[base + 5]) | + ISP_AWB_BLK_WT_DATA_WT(6, cfg->zone_weight[base + 6]) | + ISP_AWB_BLK_WT_DATA_WT(7, cfg->zone_weight[base + 7]); + c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data); + } + + if (zones_num % 8 == 0) + return; + + data = 0; + base = i * 8; + + for (i = 0; i < zones_num % 8; i++) + data |= ISP_AWB_BLK_WT_DATA_WT(i, cfg->zone_weight[base + i]); + + c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, data); +} + +static void c3_isp_params_awb_cood(struct c3_isp_device *isp, + const struct c3_isp_params_awb_config *cfg) +{ + unsigned int max_point_num; + + /* The number of points is one more than the number of edges */ + max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0); + + for (unsigned int i = 0; i < max_point_num; i++) + c3_isp_write(isp, ISP_AWB_IDX_DATA, + ISP_AWB_IDX_DATA_HIDX_DATA(cfg->horiz_cood[i]) | + ISP_AWB_IDX_DATA_VIDX_DATA(cfg->vert_cood[i])); +} + +static void c3_isp_params_cfg_awb_config(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_awb_config *awb_cfg = &block->awb_cfg; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AWB_POINT_MASK, + ISP_TOP_3A_STAT_CRTL_AWB_POINT(awb_cfg->tap_point)); + + c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, + ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK, + ISP_AWB_STAT_CTRL2_SATUR_CTRL(awb_cfg->satur_vald)); + + c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, + ISP_AWB_HV_BLKNUM_H_NUM_MASK, + ISP_AWB_HV_BLKNUM_H_NUM(awb_cfg->horiz_zones_num)); + c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, + ISP_AWB_HV_BLKNUM_V_NUM_MASK, + ISP_AWB_HV_BLKNUM_V_NUM(awb_cfg->vert_zones_num)); + + c3_isp_update_bits(isp, ISP_AWB_STAT_RG, ISP_AWB_STAT_RG_MIN_VALUE_MASK, + ISP_AWB_STAT_RG_MIN_VALUE(awb_cfg->rg_min)); + c3_isp_update_bits(isp, ISP_AWB_STAT_RG, ISP_AWB_STAT_RG_MAX_VALUE_MASK, + ISP_AWB_STAT_RG_MAX_VALUE(awb_cfg->rg_max)); + + c3_isp_update_bits(isp, ISP_AWB_STAT_BG, ISP_AWB_STAT_BG_MIN_VALUE_MASK, + ISP_AWB_STAT_BG_MIN_VALUE(awb_cfg->bg_min)); + c3_isp_update_bits(isp, ISP_AWB_STAT_BG, ISP_AWB_STAT_BG_MAX_VALUE_MASK, + ISP_AWB_STAT_BG_MAX_VALUE(awb_cfg->bg_max)); + + c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, + ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK, + ISP_AWB_STAT_RG_HL_LOW_VALUE(awb_cfg->rg_low)); + c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, + ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK, + ISP_AWB_STAT_RG_HL_HIGH_VALUE(awb_cfg->rg_high)); + + c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, + ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK, + ISP_AWB_STAT_BG_HL_LOW_VALUE(awb_cfg->bg_low)); + c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, + ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK, + ISP_AWB_STAT_BG_HL_HIGH_VALUE(awb_cfg->bg_high)); + + c3_isp_params_awb_wt(isp, awb_cfg); + c3_isp_params_awb_cood(isp, awb_cfg); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN); +} + +static void c3_isp_params_ae_wt(struct c3_isp_device *isp, + const struct c3_isp_params_ae_config *cfg) +{ + unsigned int zones_num; + unsigned int base; + unsigned int data; + unsigned int i; + + /* Set the weight address to 0 position */ + c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0); + + zones_num = cfg->horiz_zones_num * cfg->vert_zones_num; + + /* Need to write 8 weights at once */ + for (i = 0; i < zones_num / 8; i++) { + base = i * 8; + data = ISP_AE_BLK_WT_DATA_WT(0, cfg->zone_weight[base + 0]) | + ISP_AE_BLK_WT_DATA_WT(1, cfg->zone_weight[base + 1]) | + ISP_AE_BLK_WT_DATA_WT(2, cfg->zone_weight[base + 2]) | + ISP_AE_BLK_WT_DATA_WT(3, cfg->zone_weight[base + 3]) | + ISP_AE_BLK_WT_DATA_WT(4, cfg->zone_weight[base + 4]) | + ISP_AE_BLK_WT_DATA_WT(5, cfg->zone_weight[base + 5]) | + ISP_AE_BLK_WT_DATA_WT(6, cfg->zone_weight[base + 6]) | + ISP_AE_BLK_WT_DATA_WT(7, cfg->zone_weight[base + 7]); + c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data); + } + + if (zones_num % 8 == 0) + return; + + data = 0; + base = i * 8; + + /* Write the last weights data */ + for (i = 0; i < zones_num % 8; i++) + data |= ISP_AE_BLK_WT_DATA_WT(i, cfg->zone_weight[base + i]); + + c3_isp_write(isp, ISP_AE_BLK_WT_DATA, data); +} + +static void c3_isp_params_ae_cood(struct c3_isp_device *isp, + const struct c3_isp_params_ae_config *cfg) +{ + unsigned int max_point_num; + + /* The number of points is one more than the number of edges */ + max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AE_IDX_ADDR, 0); + + for (unsigned int i = 0; i < max_point_num; i++) + c3_isp_write(isp, ISP_AE_IDX_DATA, + ISP_AE_IDX_DATA_HIDX_DATA(cfg->horiz_cood[i]) | + ISP_AE_IDX_DATA_VIDX_DATA(cfg->vert_cood[i])); +} + +static void c3_isp_params_cfg_ae_config(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_ae_config *ae_cfg = &block->ae_cfg; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AE_POINT_MASK, + ISP_TOP_3A_STAT_CRTL_AE_POINT(ae_cfg->tap_point)); + + if (ae_cfg->tap_point == C3_ISP_AE_STATS_TAP_GE) + c3_isp_update_bits(isp, ISP_AE_CTRL, + ISP_AE_CTRL_INPUT_2LINE_MASK, + ISP_AE_CTRL_INPUT_2LINE_EN); + else + c3_isp_update_bits(isp, ISP_AE_CTRL, + ISP_AE_CTRL_INPUT_2LINE_MASK, + ISP_AE_CTRL_INPUT_2LINE_DIS); + + c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, + ISP_AE_HV_BLKNUM_H_NUM_MASK, + ISP_AE_HV_BLKNUM_H_NUM(ae_cfg->horiz_zones_num)); + c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, + ISP_AE_HV_BLKNUM_V_NUM_MASK, + ISP_AE_HV_BLKNUM_V_NUM(ae_cfg->vert_zones_num)); + + c3_isp_params_ae_wt(isp, ae_cfg); + c3_isp_params_ae_cood(isp, ae_cfg); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AE_STAT_EN); +} + +static void c3_isp_params_af_cood(struct c3_isp_device *isp, + const struct c3_isp_params_af_config *cfg) +{ + unsigned int max_point_num; + + /* The number of points is one more than the number of edges */ + max_point_num = max(cfg->horiz_zones_num, cfg->vert_zones_num) + 1; + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AF_IDX_ADDR, 0); + + for (unsigned int i = 0; i < max_point_num; i++) + c3_isp_write(isp, ISP_AF_IDX_DATA, + ISP_AF_IDX_DATA_HIDX_DATA(cfg->horiz_cood[i]) | + ISP_AF_IDX_DATA_VIDX_DATA(cfg->vert_cood[i])); +} + +static void c3_isp_params_cfg_af_config(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_af_config *af_cfg = &block->af_cfg; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AF_POINT_MASK, + ISP_TOP_3A_STAT_CRTL_AF_POINT(af_cfg->tap_point)); + + c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, + ISP_AF_HV_BLKNUM_H_NUM_MASK, + ISP_AF_HV_BLKNUM_H_NUM(af_cfg->horiz_zones_num)); + c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, + ISP_AF_HV_BLKNUM_V_NUM_MASK, + ISP_AF_HV_BLKNUM_V_NUM(af_cfg->vert_zones_num)); + + c3_isp_params_af_cood(isp, af_cfg); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AF_STAT_EN); +} + +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_pst_gamma *gm = &block->pst_gamma; + unsigned int base; + unsigned int i; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK, + ISP_TOP_BED_CTRL_PST_GAMMA_DIS); + return; + } + + /* R, G and B channels use the same gamma lut */ + for (unsigned int j = 0; j < 3; j++) { + /* Set the channel lut address */ + c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, + ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(j)); + + /* Need to write 2 lut values at once */ + for (i = 0; i < ARRAY_SIZE(gm->lut) / 2; i++) { + base = i * 2; + c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, + ISP_PST_GM_LUT_DATA0(gm->lut[base]) | + ISP_PST_GM_LUT_DATA1(gm->lut[base + 1])); + } + + /* Write the last one */ + if (ARRAY_SIZE(gm->lut) % 2) { + base = i * 2; + c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, + ISP_PST_GM_LUT_DATA0(gm->lut[base])); + } + } + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK, + ISP_TOP_BED_CTRL_PST_GAMMA_EN); +} + +/* Configure 3 x 3 ccm matrix */ +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_ccm *ccm = &block->ccm; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_CCM_EN_MASK, + ISP_TOP_BED_CTRL_CCM_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, + ISP_CCM_MTX_00_01_MTX_00_MASK, + ISP_CCM_MTX_00_01_MTX_00(ccm->matrix[0][0])); + c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, + ISP_CCM_MTX_00_01_MTX_01_MASK, + ISP_CCM_MTX_00_01_MTX_01(ccm->matrix[0][1])); + c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, + ISP_CCM_MTX_02_03_MTX_02_MASK, + ISP_CCM_MTX_02_03_MTX_02(ccm->matrix[0][2])); + + c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, + ISP_CCM_MTX_10_11_MTX_10_MASK, + ISP_CCM_MTX_10_11_MTX_10(ccm->matrix[1][0])); + c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, + ISP_CCM_MTX_10_11_MTX_11_MASK, + ISP_CCM_MTX_10_11_MTX_11(ccm->matrix[1][1])); + c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, + ISP_CCM_MTX_12_13_MTX_12_MASK, + ISP_CCM_MTX_12_13_MTX_12(ccm->matrix[1][2])); + + c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, + ISP_CCM_MTX_20_21_MTX_20_MASK, + ISP_CCM_MTX_20_21_MTX_20(ccm->matrix[2][0])); + c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, + ISP_CCM_MTX_20_21_MTX_21_MASK, + ISP_CCM_MTX_20_21_MTX_21(ccm->matrix[2][1])); + c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, + ISP_CCM_MTX_22_23_RS_MTX_22_MASK, + ISP_CCM_MTX_22_23_RS_MTX_22(ccm->matrix[2][2])); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_CCM_EN_MASK, + ISP_TOP_BED_CTRL_CCM_EN); +} + +/* Configure color space conversion matrix parameters */ +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_csc *csc = &block->csc; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_CM0_EN_MASK, + ISP_TOP_BED_CTRL_CM0_DIS); + return; + } + + c3_isp_update_bits(isp, ISP_CM0_COEF00_01, + ISP_CM0_COEF00_01_MTX_00_MASK, + ISP_CM0_COEF00_01_MTX_00(csc->matrix[0][0])); + c3_isp_update_bits(isp, ISP_CM0_COEF00_01, + ISP_CM0_COEF00_01_MTX_01_MASK, + ISP_CM0_COEF00_01_MTX_01(csc->matrix[0][1])); + c3_isp_update_bits(isp, ISP_CM0_COEF02_10, + ISP_CM0_COEF02_10_MTX_02_MASK, + ISP_CM0_COEF02_10_MTX_02(csc->matrix[0][2])); + + c3_isp_update_bits(isp, ISP_CM0_COEF02_10, + ISP_CM0_COEF02_10_MTX_10_MASK, + ISP_CM0_COEF02_10_MTX_10(csc->matrix[1][0])); + c3_isp_update_bits(isp, ISP_CM0_COEF11_12, + ISP_CM0_COEF11_12_MTX_11_MASK, + ISP_CM0_COEF11_12_MTX_11(csc->matrix[1][1])); + c3_isp_update_bits(isp, ISP_CM0_COEF11_12, + ISP_CM0_COEF11_12_MTX_12_MASK, + ISP_CM0_COEF11_12_MTX_12(csc->matrix[1][2])); + + c3_isp_update_bits(isp, ISP_CM0_COEF20_21, + ISP_CM0_COEF20_21_MTX_20_MASK, + ISP_CM0_COEF20_21_MTX_20(csc->matrix[2][0])); + c3_isp_update_bits(isp, ISP_CM0_COEF20_21, + ISP_CM0_COEF20_21_MTX_21_MASK, + ISP_CM0_COEF20_21_MTX_21(csc->matrix[2][1])); + c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, + ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK, + ISP_CM0_COEF22_OUP_OFST0_MTX_22(csc->matrix[2][2])); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_CM0_EN_MASK, + ISP_TOP_BED_CTRL_CM0_EN); +} + +/* Set blc offset of each color channel */ +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp, + const union c3_isp_params_block *block) +{ + const struct c3_isp_params_blc *blc = &block->blc; + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_DISABLE) { + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + ISP_TOP_BEO_CTRL_BLC_EN_MASK, + ISP_TOP_BEO_CTRL_BLC_DIS); + return; + } + + c3_isp_write(isp, ISP_LSWB_BLC_OFST0, + ISP_LSWB_BLC_OFST0_R_OFST(blc->r_ofst) | + ISP_LSWB_BLC_OFST0_GR_OFST(blc->gr_ofst)); + c3_isp_write(isp, ISP_LSWB_BLC_OFST1, + ISP_LSWB_BLC_OFST1_GB_OFST(blc->gb_ofst) | + ISP_LSWB_BLC_OFST1_B_OFST(blc->b_ofst)); + + if (block->header.flags & C3_ISP_PARAMS_BLOCK_FL_ENABLE) + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + ISP_TOP_BEO_CTRL_BLC_EN_MASK, + ISP_TOP_BEO_CTRL_BLC_EN); +} + +static const struct c3_isp_params_handler c3_isp_params_handlers[] = { + [C3_ISP_PARAMS_BLOCK_AWB_GAINS] = { + .size = sizeof(struct c3_isp_params_awb_gains), + .handler = c3_isp_params_cfg_awb_gains, + }, + [C3_ISP_PARAMS_BLOCK_AWB_CONFIG] = { + .size = sizeof(struct c3_isp_params_awb_config), + .handler = c3_isp_params_cfg_awb_config, + }, + [C3_ISP_PARAMS_BLOCK_AE_CONFIG] = { + .size = sizeof(struct c3_isp_params_ae_config), + .handler = c3_isp_params_cfg_ae_config, + }, + [C3_ISP_PARAMS_BLOCK_AF_CONFIG] = { + .size = sizeof(struct c3_isp_params_af_config), + .handler = c3_isp_params_cfg_af_config, + }, + [C3_ISP_PARAMS_BLOCK_PST_GAMMA] = { + .size = sizeof(struct c3_isp_params_pst_gamma), + .handler = c3_isp_params_cfg_pst_gamma, + }, + [C3_ISP_PARAMS_BLOCK_CCM] = { + .size = sizeof(struct c3_isp_params_ccm), + .handler = c3_isp_params_cfg_ccm, + }, + [C3_ISP_PARAMS_BLOCK_CSC] = { + .size = sizeof(struct c3_isp_params_csc), + .handler = c3_isp_params_cfg_csc, + }, + [C3_ISP_PARAMS_BLOCK_BLC] = { + .size = sizeof(struct c3_isp_params_blc), + .handler = c3_isp_params_cfg_blc, + }, +}; + +static void c3_isp_params_cfg_blocks(struct c3_isp_params *params) +{ + struct c3_isp_params_cfg *config = params->buff->cfg; + size_t block_offset = 0; + + if (WARN_ON(!config)) + return; + + /* Walk the list of parameter blocks and process them */ + while (block_offset < config->data_size) { + const struct c3_isp_params_handler *block_handler; + const union c3_isp_params_block *block; + + block = (const union c3_isp_params_block *) + &config->data[block_offset]; + + block_handler = &c3_isp_params_handlers[block->header.type]; + block_handler->handler(params->isp, block); + + block_offset += block->header.size; + } +} + +void c3_isp_params_pre_cfg(struct c3_isp_device *isp) +{ + struct c3_isp_params *params = &isp->params; + + /* Disable some unused modules */ + c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL0, + ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK, + ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS); + + c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL1_0, + ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK, + ISP_TOP_FEO_CTRL1_0_DPC_DIS); + c3_isp_update_bits(isp, ISP_TOP_FEO_CTRL1_0, + ISP_TOP_FEO_CTRL1_0_OG_EN_MASK, + ISP_TOP_FEO_CTRL1_0_OG_DIS); + + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_PDPC_EN_MASK, + ISP_TOP_FED_CTRL_PDPC_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, + ISP_TOP_FED_CTRL_RAWCNR_EN_MASK, + ISP_TOP_FED_CTRL_RAWCNR_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_SNR1_EN_MASK, + ISP_TOP_FED_CTRL_SNR1_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_TNR0_EN_MASK, + ISP_TOP_FED_CTRL_TNR0_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, + ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK, + ISP_TOP_FED_CTRL_CUBIC_CS_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, ISP_TOP_FED_CTRL_SQRT_EN_MASK, + ISP_TOP_FED_CTRL_SQRT_DIS); + c3_isp_update_bits(isp, ISP_TOP_FED_CTRL, + ISP_TOP_FED_CTRL_DGAIN_EN_MASK, + ISP_TOP_FED_CTRL_DGAIN_DIS); + + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK, + ISP_TOP_BEO_CTRL_INV_DGAIN_DIS); + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, ISP_TOP_BEO_CTRL_EOTF_EN_MASK, + ISP_TOP_BEO_CTRL_EOTF_DIS); + + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK, + ISP_TOP_BED_CTRL_YHS_STAT_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK, + ISP_TOP_BED_CTRL_GRPH_STAT_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_FMETER_EN_MASK, + ISP_TOP_BED_CTRL_FMETER_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_BSC_EN_MASK, + ISP_TOP_BED_CTRL_BSC_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_CNR2_EN_MASK, + ISP_TOP_BED_CTRL_CNR2_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_CM1_EN_MASK, + ISP_TOP_BED_CTRL_CM1_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_LUT3D_EN_MASK, + ISP_TOP_BED_CTRL_LUT3D_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, + ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK, + ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS); + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, ISP_TOP_BED_CTRL_AMCM_EN_MASK, + ISP_TOP_BED_CTRL_AMCM_DIS); + + /* + * Disable AE, AF and AWB stat module. Please configure the parameters + * in userspace algorithm if need to enable these switch. + */ + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS); + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS); + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, + ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK, + ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS); + + c3_isp_write(isp, ISP_LSWB_WB_LIMIT0, + ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX | + ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX); + c3_isp_write(isp, ISP_LSWB_WB_LIMIT1, + ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX | + ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX); + + guard(spinlock_irqsave)(¶ms->buff_lock); + + /* Only use the first buffer to initialize ISP */ + params->buff = + list_first_entry_or_null(¶ms->pending, + struct c3_isp_params_buffer, list); + if (params->buff) + c3_isp_params_cfg_blocks(params); +} + +/* V4L2 video operations */ + +static int c3_isp_params_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, "AML C3 ISP", sizeof(cap->card)); + + return 0; +} + +static int c3_isp_params_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_META_FMT_C3ISP_PARAMS; + + return 0; +} + +static int c3_isp_params_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_params *params = video_drvdata(file); + + f->fmt.meta = params->vfmt.fmt.meta; + + return 0; +} + +static const struct v4l2_ioctl_ops isp_params_v4l2_ioctl_ops = { + .vidioc_querycap = c3_isp_params_querycap, + .vidioc_enum_fmt_meta_out = c3_isp_params_enum_fmt, + .vidioc_g_fmt_meta_out = c3_isp_params_g_fmt, + .vidioc_s_fmt_meta_out = c3_isp_params_g_fmt, + .vidioc_try_fmt_meta_out = c3_isp_params_g_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations isp_params_v4l2_fops = { + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int c3_isp_params_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + if (*num_planes) { + if (*num_planes != 1) + return -EINVAL; + + if (sizes[0] < sizeof(struct c3_isp_params_cfg)) + return -EINVAL; + + return 0; + } + + *num_planes = 1; + sizes[0] = sizeof(struct c3_isp_params_cfg); + + return 0; +} + +static void c3_isp_params_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + + guard(spinlock_irqsave)(¶ms->buff_lock); + + list_add_tail(&buf->list, ¶ms->pending); +} + +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(vbuf); + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + struct c3_isp_params_cfg *cfg = buf->cfg; + struct c3_isp_params_cfg *usr_cfg = vb2_plane_vaddr(vb, 0); + size_t payload_size = vb2_get_plane_payload(vb, 0); + size_t header_size = offsetof(struct c3_isp_params_cfg, data); + size_t block_offset = 0; + size_t cfg_size; + + /* Payload size can't be greater than the destination buffer size */ + if (payload_size > params->vfmt.fmt.meta.buffersize) { + dev_dbg(params->isp->dev, + "Payload size is too large: %zu\n", payload_size); + return -EINVAL; + } + + /* Payload size can't be smaller than the header size */ + if (payload_size < header_size) { + dev_dbg(params->isp->dev, + "Payload size is too small: %zu\n", payload_size); + return -EINVAL; + } + + /* + * Use the internal scratch buffer to avoid userspace modifying + * the buffer content while the driver is processing it. + */ + memcpy(cfg, usr_cfg, payload_size); + + /* Only v0 is supported at the moment */ + if (cfg->version != C3_ISP_PARAMS_BUFFER_V0) { + dev_dbg(params->isp->dev, + "Invalid params buffer version: %u\n", cfg->version); + return -EINVAL; + } + + /* Validate the size reported in the parameter buffer header */ + cfg_size = header_size + cfg->data_size; + if (cfg_size != payload_size) { + dev_dbg(params->isp->dev, + "Data size %zu and payload size %zu are different\n", + cfg_size, payload_size); + return -EINVAL; + } + + /* Walk the list of parameter blocks and validate them */ + cfg_size = cfg->data_size; + while (cfg_size >= sizeof(struct c3_isp_params_block_header)) { + const struct c3_isp_params_block_header *block; + const struct c3_isp_params_handler *handler; + + block = (struct c3_isp_params_block_header *) + &cfg->data[block_offset]; + + if (block->type >= ARRAY_SIZE(c3_isp_params_handlers)) { + dev_dbg(params->isp->dev, + "Invalid params block type\n"); + return -EINVAL; + } + + if (block->size > cfg_size) { + dev_dbg(params->isp->dev, + "Block size is greater than cfg size\n"); + return -EINVAL; + } + + if ((block->flags & (C3_ISP_PARAMS_BLOCK_FL_ENABLE | + C3_ISP_PARAMS_BLOCK_FL_DISABLE)) == + (C3_ISP_PARAMS_BLOCK_FL_ENABLE | + C3_ISP_PARAMS_BLOCK_FL_DISABLE)) { + dev_dbg(params->isp->dev, + "Invalid parameters block flags\n"); + return -EINVAL; + } + + handler = &c3_isp_params_handlers[block->type]; + if (block->size != handler->size) { + dev_dbg(params->isp->dev, + "Invalid params block size\n"); + return -EINVAL; + } + + block_offset += block->size; + cfg_size -= block->size; + } + + if (cfg_size) { + dev_dbg(params->isp->dev, + "Unexpected data after the params buffer end\n"); + return -EINVAL; + } + + return 0; +} + +static int c3_isp_params_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); + + buf->cfg = kvmalloc(params->vfmt.fmt.meta.buffersize, GFP_KERNEL); + if (!buf->cfg) + return -ENOMEM; + + return 0; +} + +static void c3_isp_params_vb2_buf_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_params_buffer *buf = to_c3_isp_params_buffer(v4l2_buf); + + kvfree(buf->cfg); + buf->cfg = NULL; +} + +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q) +{ + struct c3_isp_params *params = vb2_get_drv_priv(q); + struct c3_isp_params_buffer *buff; + + guard(spinlock_irqsave)(¶ms->buff_lock); + + while (!list_empty(¶ms->pending)) { + buff = list_first_entry(¶ms->pending, + struct c3_isp_params_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops isp_params_vb2_ops = { + .queue_setup = c3_isp_params_vb2_queue_setup, + .buf_queue = c3_isp_params_vb2_buf_queue, + .buf_prepare = c3_isp_params_vb2_buf_prepare, + .buf_init = c3_isp_params_vb2_buf_init, + .buf_cleanup = c3_isp_params_vb2_buf_cleanup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = c3_isp_params_vb2_stop_streaming, +}; + +int c3_isp_params_register(struct c3_isp_device *isp) +{ + struct c3_isp_params *params = &isp->params; + struct video_device *vdev = ¶ms->vdev; + struct vb2_queue *vb2_q = ¶ms->vb2_q; + int ret; + + memset(params, 0, sizeof(*params)); + params->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_PARAMS; + params->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_params_cfg); + params->isp = isp; + INIT_LIST_HEAD(¶ms->pending); + spin_lock_init(¶ms->buff_lock); + mutex_init(¶ms->lock); + + snprintf(vdev->name, sizeof(vdev->name), "c3-isp-params"); + vdev->fops = &isp_params_v4l2_fops; + vdev->ioctl_ops = &isp_params_v4l2_ioctl_ops; + vdev->v4l2_dev = &isp->v4l2_dev; + vdev->lock = ¶ms->lock; + vdev->minor = -1; + vdev->queue = vb2_q; + vdev->release = video_device_release_empty; + vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_TX; + video_set_drvdata(vdev, params); + + vb2_q->drv_priv = params; + vb2_q->mem_ops = &vb2_vmalloc_memops; + vb2_q->ops = &isp_params_vb2_ops; + vb2_q->type = V4L2_BUF_TYPE_META_OUTPUT; + vb2_q->io_modes = VB2_DMABUF | VB2_MMAP; + vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb2_q->buf_struct_size = sizeof(struct c3_isp_params_buffer); + vb2_q->dev = isp->dev; + vb2_q->lock = ¶ms->lock; + vb2_q->min_queued_buffers = 1; + + ret = vb2_queue_init(vb2_q); + if (ret) + goto err_detroy; + + params->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&vdev->entity, 1, ¶ms->pad); + if (ret) + goto err_queue_release; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(isp->dev, + "Failed to register %s: %d\n", vdev->name, ret); + goto err_entity_cleanup; + } + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&vdev->entity); +err_queue_release: + vb2_queue_release(vb2_q); +err_detroy: + mutex_destroy(¶ms->lock); + return ret; +} + +void c3_isp_params_unregister(struct c3_isp_device *isp) +{ + struct c3_isp_params *params = &isp->params; + + vb2_queue_release(¶ms->vb2_q); + media_entity_cleanup(¶ms->vdev.entity); + video_unregister_device(¶ms->vdev); + mutex_destroy(¶ms->lock); +} + +void c3_isp_params_isr(struct c3_isp_device *isp) +{ + struct c3_isp_params *params = &isp->params; + + guard(spinlock_irqsave)(¶ms->buff_lock); + + params->buff = + list_first_entry_or_null(¶ms->pending, + struct c3_isp_params_buffer, list); + if (!params->buff) + return; + + list_del(¶ms->buff->list); + + c3_isp_params_cfg_blocks(params); + + params->buff->vb.sequence = params->isp->frm_sequence; + params->buff->vb.vb2_buf.timestamp = ktime_get(); + params->buff->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(¶ms->buff->vb.vb2_buf, VB2_BUF_STATE_DONE); +} diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h new file mode 100644 index 000000000000..d16c2bec6342 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h @@ -0,0 +1,606 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#ifndef __C3_ISP_REGS_H__ +#define __C3_ISP_REGS_H__ + +#define ISP_TOP_INPUT_SIZE 0x0000 +#define ISP_TOP_INPUT_SIZE_VERT_SIZE_MASK GENMASK(15, 0) +#define ISP_TOP_INPUT_SIZE_VERT_SIZE(x) ((x) << 0) +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE_MASK GENMASK(31, 16) +#define ISP_TOP_INPUT_SIZE_HORIZ_SIZE(x) ((x) << 16) + +#define ISP_TOP_FRM_SIZE 0x0004 +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE_MASK GENMASK(15, 0) +#define ISP_TOP_FRM_SIZE_CORE_VERT_SIZE(x) ((x) << 0) +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE_MASK GENMASK(31, 16) +#define ISP_TOP_FRM_SIZE_CORE_HORIZ_SIZE(x) ((x) << 16) + +#define ISP_TOP_HOLD_SIZE 0x0008 +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE_MASK GENMASK(31, 16) +#define ISP_TOP_HOLD_SIZE_CORE_HORIZ_SIZE(x) ((x) << 16) + +#define ISP_TOP_PATH_EN 0x0010 +#define ISP_TOP_PATH_EN_DISP0_EN_MASK BIT(0) +#define ISP_TOP_PATH_EN_DISP0_EN BIT(0) +#define ISP_TOP_PATH_EN_DISP0_DIS (0 << 0) +#define ISP_TOP_PATH_EN_DISP1_EN_MASK BIT(1) +#define ISP_TOP_PATH_EN_DISP1_EN BIT(1) +#define ISP_TOP_PATH_EN_DISP1_DIS (0 << 1) +#define ISP_TOP_PATH_EN_DISP2_EN_MASK BIT(2) +#define ISP_TOP_PATH_EN_DISP2_EN BIT(2) +#define ISP_TOP_PATH_EN_DISP2_DIS (0 << 2) +#define ISP_TOP_PATH_EN_WRMIF0_EN_MASK BIT(8) +#define ISP_TOP_PATH_EN_WRMIF0_EN BIT(8) +#define ISP_TOP_PATH_EN_WRMIF0_DIS (0 << 8) +#define ISP_TOP_PATH_EN_WRMIF1_EN_MASK BIT(9) +#define ISP_TOP_PATH_EN_WRMIF1_EN BIT(9) +#define ISP_TOP_PATH_EN_WRMIF1_DIS (0 << 9) +#define ISP_TOP_PATH_EN_WRMIF2_EN_MASK BIT(10) +#define ISP_TOP_PATH_EN_WRMIF2_EN BIT(10) +#define ISP_TOP_PATH_EN_WRMIF2_DIS (0 << 10) + +#define ISP_TOP_PATH_SEL 0x0014 +#define ISP_TOP_PATH_SEL_CORE_MASK GENMASK(18, 16) +#define ISP_TOP_PATH_SEL_CORE_CORE_DIS (0 << 16) +#define ISP_TOP_PATH_SEL_CORE_MIPI_CORE BIT(16) + +#define ISP_TOP_IRQ_EN 0x0080 +#define ISP_TOP_IRQ_EN_FRM_END_MASK BIT(0) +#define ISP_TOP_IRQ_EN_FRM_END_EN BIT(0) +#define ISP_TOP_IRQ_EN_FRM_END_DIS (0 << 0) +#define ISP_TOP_IRQ_EN_FRM_RST_MASK BIT(1) +#define ISP_TOP_IRQ_EN_FRM_RST_EN BIT(1) +#define ISP_TOP_IRQ_EN_FRM_RST_DIS (0 << 1) +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_MASK BIT(5) +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_EN BIT(5) +#define ISP_TOP_IRQ_EN_3A_DMA_ERR_DIS (0 << 5) + +#define ISP_TOP_IRQ_CLR 0x0084 +#define ISP_TOP_RO_IRQ_STAT 0x01c4 +#define ISP_TOP_RO_IRQ_STAT_FRM_END_MASK BIT(0) +#define ISP_TOP_RO_IRQ_STAT_FRM_RST_MASK BIT(1) +#define ISP_TOP_RO_IRQ_STAT_3A_DMA_ERR_MASK BIT(5) + +#define ISP_TOP_MODE_CTRL 0x0400 +#define ISP_TOP_FEO_CTRL0 0x040c +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN_MASK BIT(8) +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_DIS (0 << 8) +#define ISP_TOP_FEO_CTRL0_INPUT_FMT_EN BIT(8) + +#define ISP_TOP_FEO_CTRL1_0 0x0410 +#define ISP_TOP_FEO_CTRL1_0_DPC_EN_MASK BIT(3) +#define ISP_TOP_FEO_CTRL1_0_DPC_DIS (0 << 3) +#define ISP_TOP_FEO_CTRL1_0_DPC_EN BIT(3) +#define ISP_TOP_FEO_CTRL1_0_OG_EN_MASK BIT(5) +#define ISP_TOP_FEO_CTRL1_0_OG_DIS (0 << 5) +#define ISP_TOP_FEO_CTRL1_0_OG_EN BIT(5) + +#define ISP_TOP_FED_CTRL 0x0418 +#define ISP_TOP_FED_CTRL_PDPC_EN_MASK BIT(1) +#define ISP_TOP_FED_CTRL_PDPC_DIS (0 << 1) +#define ISP_TOP_FED_CTRL_PDPC_EN BIT(1) +#define ISP_TOP_FED_CTRL_RAWCNR_EN_MASK GENMASK(6, 5) +#define ISP_TOP_FED_CTRL_RAWCNR_DIS (0 << 5) +#define ISP_TOP_FED_CTRL_RAWCNR_EN BIT(5) +#define ISP_TOP_FED_CTRL_SNR1_EN_MASK BIT(9) +#define ISP_TOP_FED_CTRL_SNR1_DIS (0 << 9) +#define ISP_TOP_FED_CTRL_SNR1_EN BIT(9) +#define ISP_TOP_FED_CTRL_TNR0_EN_MASK BIT(11) +#define ISP_TOP_FED_CTRL_TNR0_DIS (0 << 11) +#define ISP_TOP_FED_CTRL_TNR0_EN BIT(11) +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN_MASK BIT(12) +#define ISP_TOP_FED_CTRL_CUBIC_CS_DIS (0 << 12) +#define ISP_TOP_FED_CTRL_CUBIC_CS_EN BIT(12) +#define ISP_TOP_FED_CTRL_SQRT_EN_MASK BIT(14) +#define ISP_TOP_FED_CTRL_SQRT_DIS (0 << 14) +#define ISP_TOP_FED_CTRL_SQRT_EN BIT(14) +#define ISP_TOP_FED_CTRL_DGAIN_EN_MASK BIT(16) +#define ISP_TOP_FED_CTRL_DGAIN_DIS (0 << 16) +#define ISP_TOP_FED_CTRL_DGAIN_EN BIT(16) + +#define ISP_TOP_BEO_CTRL 0x041c +#define ISP_TOP_BEO_CTRL_WB_EN_MASK BIT(6) +#define ISP_TOP_BEO_CTRL_WB_DIS (0 << 6) +#define ISP_TOP_BEO_CTRL_WB_EN BIT(6) +#define ISP_TOP_BEO_CTRL_BLC_EN_MASK BIT(7) +#define ISP_TOP_BEO_CTRL_BLC_DIS (0 << 7) +#define ISP_TOP_BEO_CTRL_BLC_EN BIT(7) +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN_MASK BIT(8) +#define ISP_TOP_BEO_CTRL_INV_DGAIN_DIS (0 << 8) +#define ISP_TOP_BEO_CTRL_INV_DGAIN_EN BIT(8) +#define ISP_TOP_BEO_CTRL_EOTF_EN_MASK BIT(9) +#define ISP_TOP_BEO_CTRL_EOTF_DIS (0 << 9) +#define ISP_TOP_BEO_CTRL_EOTF_EN BIT(9) + +#define ISP_TOP_BED_CTRL 0x0420 +#define ISP_TOP_BED_CTRL_YHS_STAT_EN_MASK GENMASK(1, 0) +#define ISP_TOP_BED_CTRL_YHS_STAT_DIS (0 << 0) +#define ISP_TOP_BED_CTRL_YHS_STAT_EN BIT(0) +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN_MASK BIT(2) +#define ISP_TOP_BED_CTRL_GRPH_STAT_DIS (0 << 2) +#define ISP_TOP_BED_CTRL_GRPH_STAT_EN BIT(2) +#define ISP_TOP_BED_CTRL_FMETER_EN_MASK BIT(3) +#define ISP_TOP_BED_CTRL_FMETER_DIS (0 << 3) +#define ISP_TOP_BED_CTRL_FMETER_EN BIT(3) +#define ISP_TOP_BED_CTRL_BSC_EN_MASK BIT(10) +#define ISP_TOP_BED_CTRL_BSC_DIS (0 << 10) +#define ISP_TOP_BED_CTRL_BSC_EN BIT(10) +#define ISP_TOP_BED_CTRL_CNR2_EN_MASK BIT(11) +#define ISP_TOP_BED_CTRL_CNR2_DIS (0 << 11) +#define ISP_TOP_BED_CTRL_CNR2_EN BIT(11) +#define ISP_TOP_BED_CTRL_CM1_EN_MASK BIT(13) +#define ISP_TOP_BED_CTRL_CM1_DIS (0 << 13) +#define ISP_TOP_BED_CTRL_CM1_EN BIT(13) +#define ISP_TOP_BED_CTRL_CM0_EN_MASK BIT(14) +#define ISP_TOP_BED_CTRL_CM0_DIS (0 << 14) +#define ISP_TOP_BED_CTRL_CM0_EN BIT(14) +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN_MASK BIT(16) +#define ISP_TOP_BED_CTRL_PST_GAMMA_DIS (0 << 16) +#define ISP_TOP_BED_CTRL_PST_GAMMA_EN BIT(16) +#define ISP_TOP_BED_CTRL_LUT3D_EN_MASK BIT(17) +#define ISP_TOP_BED_CTRL_LUT3D_DIS (0 << 17) +#define ISP_TOP_BED_CTRL_LUT3D_EN BIT(17) +#define ISP_TOP_BED_CTRL_CCM_EN_MASK BIT(18) +#define ISP_TOP_BED_CTRL_CCM_DIS (0 << 18) +#define ISP_TOP_BED_CTRL_CCM_EN BIT(18) +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN_MASK BIT(21) +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_DIS (0 << 21) +#define ISP_TOP_BED_CTRL_PST_TNR_LITE_EN BIT(21) +#define ISP_TOP_BED_CTRL_AMCM_EN_MASK BIT(25) +#define ISP_TOP_BED_CTRL_AMCM_DIS (0 << 25) +#define ISP_TOP_BED_CTRL_AMCM_EN BIT(25) + +#define ISP_TOP_3A_STAT_CRTL 0x0424 +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN_MASK BIT(0) +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_DIS (0 << 0) +#define ISP_TOP_3A_STAT_CRTL_AE_STAT_EN BIT(0) +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN_MASK BIT(1) +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_DIS (0 << 1) +#define ISP_TOP_3A_STAT_CRTL_AWB_STAT_EN BIT(1) +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN_MASK BIT(2) +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_DIS (0 << 2) +#define ISP_TOP_3A_STAT_CRTL_AF_STAT_EN BIT(2) +#define ISP_TOP_3A_STAT_CRTL_AWB_POINT_MASK GENMASK(6, 4) +#define ISP_TOP_3A_STAT_CRTL_AWB_POINT(x) ((x) << 4) +#define ISP_TOP_3A_STAT_CRTL_AE_POINT_MASK GENMASK(9, 8) +#define ISP_TOP_3A_STAT_CRTL_AE_POINT(x) ((x) << 8) +#define ISP_TOP_3A_STAT_CRTL_AF_POINT_MASK GENMASK(13, 12) +#define ISP_TOP_3A_STAT_CRTL_AF_POINT(x) ((x) << 12) + +#define ISP_LSWB_BLC_OFST0 0x4028 +#define ISP_LSWB_BLC_OFST0_R_OFST_MASK GENMASK(15, 0) +#define ISP_LSWB_BLC_OFST0_R_OFST(x) ((x) << 0) +#define ISP_LSWB_BLC_OFST0_GR_OFST_MASK GENMASK(31, 16) +#define ISP_LSWB_BLC_OFST0_GR_OFST(x) ((x) << 16) + +#define ISP_LSWB_BLC_OFST1 0x402c +#define ISP_LSWB_BLC_OFST1_GB_OFST_MASK GENMASK(15, 0) +#define ISP_LSWB_BLC_OFST1_GB_OFST(x) ((x) << 0) +#define ISP_LSWB_BLC_OFST1_B_OFST_MASK GENMASK(31, 16) +#define ISP_LSWB_BLC_OFST1_B_OFST(x) ((x) << 16) + +#define ISP_LSWB_BLC_PHSOFST 0x4034 +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST_MASK GENMASK(1, 0) +#define ISP_LSWB_BLC_PHSOFST_VERT_OFST(x) ((x) << 0) +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST_MASK GENMASK(3, 2) +#define ISP_LSWB_BLC_PHSOFST_HORIZ_OFST(x) ((x) << 2) + +#define ISP_LSWB_WB_GAIN0 0x4038 +#define ISP_LSWB_WB_GAIN0_R_GAIN_MASK GENMASK(11, 0) +#define ISP_LSWB_WB_GAIN0_R_GAIN(x) ((x) << 0) +#define ISP_LSWB_WB_GAIN0_GR_GAIN_MASK GENMASK(27, 16) +#define ISP_LSWB_WB_GAIN0_GR_GAIN(x) ((x) << 16) + +#define ISP_LSWB_WB_GAIN1 0x403c +#define ISP_LSWB_WB_GAIN1_GB_GAIN_MASK GENMASK(11, 0) +#define ISP_LSWB_WB_GAIN1_GB_GAIN(x) ((x) << 0) +#define ISP_LSWB_WB_GAIN1_B_GAIN_MASK GENMASK(27, 16) +#define ISP_LSWB_WB_GAIN1_B_GAIN(x) ((x) << 16) + +#define ISP_LSWB_WB_GAIN2 0x4040 +#define ISP_LSWB_WB_GAIN2_IR_GAIN_MASK GENMASK(11, 0) +#define ISP_LSWB_WB_GAIN2_IR_GAIN(x) ((x) << 0) + +#define ISP_LSWB_WB_LIMIT0 0x4044 +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MASK GENMASK(15, 0) +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R(x) ((x) << 0) +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_R_MAX (0x8fff << 0) +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MASK GENMASK(31, 16) +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR(x) ((x) << 16) +#define ISP_LSWB_WB_LIMIT0_WB_LIMIT_GR_MAX (0x8fff << 16) + +#define ISP_LSWB_WB_LIMIT1 0x4048 +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MASK GENMASK(15, 0) +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB(x) ((x) << 0) +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_GB_MAX (0x8fff << 0) +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MASK GENMASK(31, 16) +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B(x) ((x) << 16) +#define ISP_LSWB_WB_LIMIT1_WB_LIMIT_B_MAX (0x8fff << 16) + +#define ISP_LSWB_WB_PHSOFST 0x4050 +#define ISP_LSWB_WB_PHSOFST_VERT_OFST_MASK GENMASK(1, 0) +#define ISP_LSWB_WB_PHSOFST_VERT_OFST(x) ((x) << 0) +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST_MASK GENMASK(3, 2) +#define ISP_LSWB_WB_PHSOFST_HORIZ_OFST(x) ((x) << 2) + +#define ISP_LSWB_LNS_PHSOFST 0x4054 +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST_MASK GENMASK(1, 0) +#define ISP_LSWB_LNS_PHSOFST_VERT_OFST(x) ((x) << 0) +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST_MASK GENMASK(3, 2) +#define ISP_LSWB_LNS_PHSOFST_HORIZ_OFST(x) ((x) << 2) + +#define ISP_DMS_COMMON_PARAM0 0x5000 +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST_MASK GENMASK(1, 0) +#define ISP_DMS_COMMON_PARAM0_VERT_PHS_OFST(x) ((x) << 0) +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST_MASK GENMASK(3, 2) +#define ISP_DMS_COMMON_PARAM0_HORIZ_PHS_OFST(x) ((x) << 2) + +#define ISP_CM0_COEF00_01 0x6048 +#define ISP_CM0_COEF00_01_MTX_00_MASK GENMASK(12, 0) +#define ISP_CM0_COEF00_01_MTX_00(x) ((x) << 0) +#define ISP_CM0_COEF00_01_MTX_01_MASK GENMASK(28, 16) +#define ISP_CM0_COEF00_01_MTX_01(x) ((x) << 16) + +#define ISP_CM0_COEF02_10 0x604c +#define ISP_CM0_COEF02_10_MTX_02_MASK GENMASK(12, 0) +#define ISP_CM0_COEF02_10_MTX_02(x) ((x) << 0) +#define ISP_CM0_COEF02_10_MTX_10_MASK GENMASK(28, 16) +#define ISP_CM0_COEF02_10_MTX_10(x) ((x) << 16) + +#define ISP_CM0_COEF11_12 0x6050 +#define ISP_CM0_COEF11_12_MTX_11_MASK GENMASK(12, 0) +#define ISP_CM0_COEF11_12_MTX_11(x) ((x) << 0) +#define ISP_CM0_COEF11_12_MTX_12_MASK GENMASK(28, 16) +#define ISP_CM0_COEF11_12_MTX_12(x) ((x) << 16) + +#define ISP_CM0_COEF20_21 0x6054 +#define ISP_CM0_COEF20_21_MTX_20_MASK GENMASK(12, 0) +#define ISP_CM0_COEF20_21_MTX_20(x) ((x) << 0) +#define ISP_CM0_COEF20_21_MTX_21_MASK GENMASK(28, 16) +#define ISP_CM0_COEF20_21_MTX_21(x) ((x) << 16) + +#define ISP_CM0_COEF22_OUP_OFST0 0x6058 +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22_MASK GENMASK(12, 0) +#define ISP_CM0_COEF22_OUP_OFST0_MTX_22(x) ((x) << 0) + +#define ISP_CCM_MTX_00_01 0x6098 +#define ISP_CCM_MTX_00_01_MTX_00_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_00_01_MTX_00(x) ((x) << 0) +#define ISP_CCM_MTX_00_01_MTX_01_MASK GENMASK(28, 16) +#define ISP_CCM_MTX_00_01_MTX_01(x) ((x) << 16) + +#define ISP_CCM_MTX_02_03 0x609c +#define ISP_CCM_MTX_02_03_MTX_02_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_02_03_MTX_02(x) ((x) << 0) + +#define ISP_CCM_MTX_10_11 0x60A0 +#define ISP_CCM_MTX_10_11_MTX_10_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_10_11_MTX_10(x) ((x) << 0) +#define ISP_CCM_MTX_10_11_MTX_11_MASK GENMASK(28, 16) +#define ISP_CCM_MTX_10_11_MTX_11(x) ((x) << 16) + +#define ISP_CCM_MTX_12_13 0x60A4 +#define ISP_CCM_MTX_12_13_MTX_12_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_12_13_MTX_12(x) ((x) << 0) + +#define ISP_CCM_MTX_20_21 0x60A8 +#define ISP_CCM_MTX_20_21_MTX_20_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_20_21_MTX_20(x) ((x) << 0) +#define ISP_CCM_MTX_20_21_MTX_21_MASK GENMASK(28, 16) +#define ISP_CCM_MTX_20_21_MTX_21(x) ((x) << 16) + +#define ISP_CCM_MTX_22_23_RS 0x60Ac +#define ISP_CCM_MTX_22_23_RS_MTX_22_MASK GENMASK(12, 0) +#define ISP_CCM_MTX_22_23_RS_MTX_22(x) ((x) << 0) + +#define ISP_PST_GAMMA_LUT_ADDR 0x60cc +#define ISP_PST_GAMMA_LUT_ADDR_IDX_ADDR(x) ((x) << 7) + +#define ISP_PST_GAMMA_LUT_DATA 0x60d0 +#define ISP_PST_GM_LUT_DATA0(x) (((x) & GENMASK(15, 0)) << 0) +#define ISP_PST_GM_LUT_DATA1(x) (((x) & GENMASK(15, 0)) << 16) + +#define DISP0_TOP_TOP_CTRL 0x8000 +#define DISP0_TOP_TOP_CTRL_CROP2_EN_MASK BIT(5) +#define DISP0_TOP_TOP_CTRL_CROP2_EN BIT(5) +#define DISP0_TOP_TOP_CTRL_CROP2_DIS (0 << 5) + +#define DISP0_TOP_CRP2_START 0x8004 +#define DISP0_TOP_CRP2_START_V_START_MASK GENMASK(15, 0) +#define DISP0_TOP_CRP2_START_V_START(x) ((x) << 0) +#define DISP0_TOP_CRP2_START_H_START_MASK GENMASK(31, 16) +#define DISP0_TOP_CRP2_START_H_START(x) ((x) << 16) + +#define DISP0_TOP_CRP2_SIZE 0x8008 +#define DISP0_TOP_CRP2_SIZE_V_SIZE_MASK GENMASK(15, 0) +#define DISP0_TOP_CRP2_SIZE_V_SIZE(x) ((x) << 0) +#define DISP0_TOP_CRP2_SIZE_H_SIZE_MASK GENMASK(31, 16) +#define DISP0_TOP_CRP2_SIZE_H_SIZE(x) ((x) << 16) + +#define DISP0_TOP_OUT_SIZE 0x800c +#define DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT_MASK GENMASK(12, 0) +#define DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT(x) ((x) << 0) +#define DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH_MASK GENMASK(28, 16) +#define DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH(x) ((x) << 16) + +#define ISP_DISP0_TOP_IN_SIZE 0x804c +#define ISP_DISP0_TOP_IN_SIZE_VSIZE_MASK GENMASK(12, 0) +#define ISP_DISP0_TOP_IN_SIZE_VSIZE(x) ((x) << 0) +#define ISP_DISP0_TOP_IN_SIZE_HSIZE_MASK GENMASK(28, 16) +#define ISP_DISP0_TOP_IN_SIZE_HSIZE(x) ((x) << 16) + +#define DISP0_PPS_SCALE_EN 0x8200 +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK GENMASK(3, 0) +#define DISP0_PPS_SCALE_EN_VSC_TAP_NUM(x) ((x) << 0) +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM_MASK GENMASK(7, 4) +#define DISP0_PPS_SCALE_EN_HSC_TAP_NUM(x) ((x) << 4) +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK GENMASK(11, 8) +#define DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(x) ((x) << 8) +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM_MASK GENMASK(15, 12) +#define DISP0_PPS_SCALE_EN_PREHSC_FLT_NUM(x) ((x) << 12) +#define DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK GENMASK(17, 16) +#define DISP0_PPS_SCALE_EN_PREVSC_RATE(x) ((x) << 16) +#define DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK GENMASK(19, 18) +#define DISP0_PPS_SCALE_EN_PREHSC_RATE(x) ((x) << 18) +#define DISP0_PPS_SCALE_EN_HSC_EN_MASK BIT(20) +#define DISP0_PPS_SCALE_EN_HSC_EN(x) ((x) << 20) +#define DISP0_PPS_SCALE_EN_HSC_DIS (0 << 20) +#define DISP0_PPS_SCALE_EN_VSC_EN_MASK BIT(21) +#define DISP0_PPS_SCALE_EN_VSC_EN(x) ((x) << 21) +#define DISP0_PPS_SCALE_EN_VSC_DIS (0 << 21) +#define DISP0_PPS_SCALE_EN_PREVSC_EN_MASK BIT(22) +#define DISP0_PPS_SCALE_EN_PREVSC_EN(x) ((x) << 22) +#define DISP0_PPS_SCALE_EN_PREVSC_DIS (0 << 22) +#define DISP0_PPS_SCALE_EN_PREHSC_EN_MASK BIT(23) +#define DISP0_PPS_SCALE_EN_PREHSC_EN(x) ((x) << 23) +#define DISP0_PPS_SCALE_EN_PREHSC_DIS (0 << 23) +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK GENMASK(27, 24) +#define DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(x) ((x) << 24) +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK GENMASK(31, 28) +#define DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(x) ((x) << 28) + +#define DISP0_PPS_VSC_START_PHASE_STEP 0x8224 +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC_MASK GENMASK(23, 0) +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(x) ((x) << 0) +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE_MASK GENMASK(27, 24) +#define DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(x) ((x) << 24) + +#define DISP0_PPS_HSC_START_PHASE_STEP 0x8230 +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC_MASK GENMASK(23, 0) +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(x) ((x) << 0) +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE_MASK GENMASK(27, 24) +#define DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(x) ((x) << 24) + +#define DISP0_PPS_444TO422 0x823c +#define DISP0_PPS_444TO422_EN_MASK BIT(0) +#define DISP0_PPS_444TO422_EN(x) ((x) << 0) + +#define ISP_SCALE0_COEF_IDX_LUMA 0x8240 +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_MASK BIT(9) +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN BIT(9) +#define ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_DIS (0 << 9) +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE_MASK GENMASK(12, 10) +#define ISP_SCALE0_COEF_IDX_LUMA_CTYPE(x) ((x) << 10) + +#define ISP_SCALE0_COEF_LUMA 0x8244 +#define ISP_SCALE0_COEF_LUMA_DATA1(x) (((x) & GENMASK(10, 0)) << 0) +#define ISP_SCALE0_COEF_LUMA_DATA0(x) (((x) & GENMASK(10, 0)) << 16) + +#define ISP_SCALE0_COEF_IDX_CHRO 0x8248 +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_MASK BIT(9) +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN BIT(9) +#define ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_DIS (0 << 9) +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE_MASK GENMASK(12, 10) +#define ISP_SCALE0_COEF_IDX_CHRO_CTYPE(x) ((x) << 10) + +#define ISP_SCALE0_COEF_CHRO 0x824c +#define ISP_SCALE0_COEF_CHRO_DATA1(x) (((x) & GENMASK(10, 0)) << 0) +#define ISP_SCALE0_COEF_CHRO_DATA0(x) (((x) & GENMASK(10, 0)) << 16) + +#define ISP_AF_CTRL 0xa044 +#define ISP_AF_CTRL_VERT_OFST_MASK GENMASK(15, 14) +#define ISP_AF_CTRL_VERT_OFST(x) ((x) << 14) +#define ISP_AF_CTRL_HORIZ_OFST_MASK GENMASK(17, 16) +#define ISP_AF_CTRL_HORIZ_OFST(x) ((x) << 16) + +#define ISP_AF_HV_SIZE 0xa04c +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE_MASK GENMASK(15, 0) +#define ISP_AF_HV_SIZE_GLB_WIN_YSIZE(x) ((x) << 0) +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE_MASK GENMASK(31, 16) +#define ISP_AF_HV_SIZE_GLB_WIN_XSIZE(x) ((x) << 16) + +#define ISP_AF_HV_BLKNUM 0xa050 +#define ISP_AF_HV_BLKNUM_V_NUM_MASK GENMASK(5, 0) +#define ISP_AF_HV_BLKNUM_V_NUM(x) ((x) << 0) +#define ISP_AF_HV_BLKNUM_H_NUM_MASK GENMASK(21, 16) +#define ISP_AF_HV_BLKNUM_H_NUM(x) ((x) << 16) + +#define ISP_AF_EN_CTRL 0xa054 +#define ISP_AF_EN_CTRL_STAT_SEL_MASK BIT(21) +#define ISP_AF_EN_CTRL_STAT_SEL_OLD (0 << 21) +#define ISP_AF_EN_CTRL_STAT_SEL_NEW BIT(21) + +#define ISP_AF_IDX_ADDR 0xa1c0 +#define ISP_AF_IDX_DATA 0xa1c4 +#define ISP_AF_IDX_DATA_VIDX_DATA(x) (((x) & GENMASK(15, 0)) << 0) +#define ISP_AF_IDX_DATA_HIDX_DATA(x) (((x) & GENMASK(15, 0)) << 16) + +#define ISP_AE_CTRL 0xa448 +#define ISP_AE_CTRL_INPUT_2LINE_MASK BIT(7) +#define ISP_AE_CTRL_INPUT_2LINE_EN BIT(7) +#define ISP_AE_CTRL_INPUT_2LINE_DIS (0 << 7) +#define ISP_AE_CTRL_LUMA_MODE_MASK GENMASK(9, 8) +#define ISP_AE_CTRL_LUMA_MODE_CUR (0 << 8) +#define ISP_AE_CTRL_LUMA_MODE_MAX BIT(8) +#define ISP_AE_CTRL_LUMA_MODE_FILTER (2 << 8) +#define ISP_AE_CTRL_VERT_OFST_MASK GENMASK(25, 24) +#define ISP_AE_CTRL_VERT_OFST(x) ((x) << 24) +#define ISP_AE_CTRL_HORIZ_OFST_MASK GENMASK(27, 26) +#define ISP_AE_CTRL_HORIZ_OFST(x) ((x) << 26) + +#define ISP_AE_HV_SIZE 0xa464 +#define ISP_AE_HV_SIZE_VERT_SIZE_MASK GENMASK(15, 0) +#define ISP_AE_HV_SIZE_VERT_SIZE(x) ((x) << 0) +#define ISP_AE_HV_SIZE_HORIZ_SIZE_MASK GENMASK(31, 16) +#define ISP_AE_HV_SIZE_HORIZ_SIZE(x) ((x) << 16) + +#define ISP_AE_HV_BLKNUM 0xa468 +#define ISP_AE_HV_BLKNUM_V_NUM_MASK GENMASK(6, 0) +#define ISP_AE_HV_BLKNUM_V_NUM(x) ((x) << 0) +#define ISP_AE_HV_BLKNUM_H_NUM_MASK GENMASK(22, 16) +#define ISP_AE_HV_BLKNUM_H_NUM(x) ((x) << 16) + +#define ISP_AE_IDX_ADDR 0xa600 +#define ISP_AE_IDX_DATA 0xa604 +#define ISP_AE_IDX_DATA_VIDX_DATA(x) (((x) & GENMASK(15, 0)) << 0) +#define ISP_AE_IDX_DATA_HIDX_DATA(x) (((x) & GENMASK(15, 0)) << 16) + +#define ISP_AE_BLK_WT_ADDR 0xa608 +#define ISP_AE_BLK_WT_DATA 0xa60c +#define ISP_AE_BLK_WT_DATA_WT(i, x) (((x) & GENMASK(3, 0)) << ((i) * 4)) + +#define ISP_AWB_CTRL 0xa834 +#define ISP_AWB_CTRL_VERT_OFST_MASK GENMASK(1, 0) +#define ISP_AWB_CTRL_VERT_OFST(x) ((x) << 0) +#define ISP_AWB_CTRL_HORIZ_OFST_MASK GENMASK(3, 2) +#define ISP_AWB_CTRL_HORIZ_OFST(x) ((x) << 2) + +#define ISP_AWB_HV_SIZE 0xa83c +#define ISP_AWB_HV_SIZE_VERT_SIZE_MASK GENMASK(15, 0) +#define ISP_AWB_HV_SIZE_VERT_SIZE(x) ((x) << 0) +#define ISP_AWB_HV_SIZE_HORIZ_SIZE_MASK GENMASK(31, 16) +#define ISP_AWB_HV_SIZE_HORIZ_SIZE(x) ((x) << 16) + +#define ISP_AWB_HV_BLKNUM 0xa840 +#define ISP_AWB_HV_BLKNUM_V_NUM_MASK GENMASK(5, 0) +#define ISP_AWB_HV_BLKNUM_V_NUM(x) ((x) << 0) +#define ISP_AWB_HV_BLKNUM_H_NUM_MASK GENMASK(21, 16) +#define ISP_AWB_HV_BLKNUM_H_NUM(x) ((x) << 16) + +#define ISP_AWB_STAT_RG 0xa848 +#define ISP_AWB_STAT_RG_MIN_VALUE_MASK GENMASK(11, 0) +#define ISP_AWB_STAT_RG_MIN_VALUE(x) ((x) << 0) +#define ISP_AWB_STAT_RG_MAX_VALUE_MASK GENMASK(27, 16) +#define ISP_AWB_STAT_RG_MAX_VALUE(x) ((x) << 16) + +#define ISP_AWB_STAT_BG 0xa84c +#define ISP_AWB_STAT_BG_MIN_VALUE_MASK GENMASK(11, 0) +#define ISP_AWB_STAT_BG_MIN_VALUE(x) ((x) << 0) +#define ISP_AWB_STAT_BG_MAX_VALUE_MASK GENMASK(27, 16) +#define ISP_AWB_STAT_BG_MAX_VALUE(x) ((x) << 16) + +#define ISP_AWB_STAT_RG_HL 0xa850 +#define ISP_AWB_STAT_RG_HL_LOW_VALUE_MASK GENMASK(11, 0) +#define ISP_AWB_STAT_RG_HL_LOW_VALUE(x) ((x) << 0) +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE_MASK GENMASK(27, 16) +#define ISP_AWB_STAT_RG_HL_HIGH_VALUE(x) ((x) << 16) + +#define ISP_AWB_STAT_BG_HL 0xa854 +#define ISP_AWB_STAT_BG_HL_LOW_VALUE_MASK GENMASK(11, 0) +#define ISP_AWB_STAT_BG_HL_LOW_VALUE(x) ((x) << 0) +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE_MASK GENMASK(27, 16) +#define ISP_AWB_STAT_BG_HL_HIGH_VALUE(x) ((x) << 16) + +#define ISP_AWB_STAT_CTRL2 0xa858 +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL_MASK BIT(0) +#define ISP_AWB_STAT_CTRL2_SATUR_CTRL(x) ((x) << 0) + +#define ISP_AWB_IDX_ADDR 0xaa00 +#define ISP_AWB_IDX_DATA 0xaa04 +#define ISP_AWB_IDX_DATA_VIDX_DATA(x) (((x) & GENMASK(15, 0)) << 0) +#define ISP_AWB_IDX_DATA_HIDX_DATA(x) (((x) & GENMASK(15, 0)) << 16) + +#define ISP_AWB_BLK_WT_ADDR 0xaa08 +#define ISP_AWB_BLK_WT_DATA 0xaa0c +#define ISP_AWB_BLK_WT_DATA_WT(i, x) (((x) & GENMASK(3, 0)) << ((i) * 4)) + +#define ISP_WRMIFX3_0_CH0_CTRL0 0xc400 +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_CH0_CTRL0_STRIDE(x) ((x) << 16) + +#define ISP_WRMIFX3_0_CH0_CTRL1 0xc404 +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_MODE_MASK GENMASK(30, 27) +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS BIT(27) +#define ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS (2 << 27) + +#define ISP_WRMIFX3_0_CH1_CTRL0 0xc408 +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_CH1_CTRL0_STRIDE(x) ((x) << 16) + +#define ISP_WRMIFX3_0_CH1_CTRL1 0xc40c +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_MODE_MASK GENMASK(30, 27) +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_8BITS BIT(27) +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_16BITS (2 << 27) +#define ISP_WRMIFX3_0_CH1_CTRL1_PIX_BITS_32BITS (3 << 27) + +#define ISP_WRMIFX3_0_WIN_LUMA_H 0xc420 +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_WIN_LUMA_H_LUMA_HEND(x) (((x) - 1) << 16) + +#define ISP_WRMIFX3_0_WIN_LUMA_V 0xc424 +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_WIN_LUMA_V_LUMA_VEND(x) (((x) - 1) << 16) + +#define ISP_WRMIFX3_0_WIN_CHROM_H 0xc428 +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_WIN_CHROM_H_CHROM_HEND(x) (((x) - 1) << 16) + +#define ISP_WRMIFX3_0_WIN_CHROM_V 0xc42c +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND_MASK GENMASK(28, 16) +#define ISP_WRMIFX3_0_WIN_CHROM_V_CHROM_VEND(x) (((x) - 1) << 16) + +#define ISP_WRMIFX3_0_CH0_BADDR 0xc440 +#define ISP_WRMIFX3_0_CH0_BASE_ADDR(x) ((x) >> 4) + +#define ISP_WRMIFX3_0_CH1_BADDR 0xc444 +#define ISP_WRMIFX3_0_CH1_BASE_ADDR(x) ((x) >> 4) + +#define ISP_WRMIFX3_0_FMT_SIZE 0xc464 +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE_MASK GENMASK(15, 0) +#define ISP_WRMIFX3_0_FMT_SIZE_HSIZE(x) ((x) << 0) +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE_MASK GENMASK(31, 16) +#define ISP_WRMIFX3_0_FMT_SIZE_VSIZE(x) ((x) << 16) + +#define ISP_WRMIFX3_0_FMT_CTRL 0xc468 +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_MASK GENMASK(1, 0) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT (0 << 0) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_10BIT BIT(0) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_12BIT (2 << 0) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT (3 << 0) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_MASK BIT(2) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU (0 << 2) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV BIT(2) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_MASK GENMASK(5, 4) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1 (0 << 4) +#define ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2 BIT(4) +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_MASK GENMASK(18, 16) +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422 BIT(16) +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420 (2 << 16) +#define ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY (3 << 16) + +#define VIU_DMAWR_BADDR0 0xc840 +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK GENMASK(27, 0) +#define VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(x) ((x) >> 4) + +#define VIU_DMAWR_BADDR1 0xc844 +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK GENMASK(27, 0) +#define VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(x) ((x) >> 4) + +#define VIU_DMAWR_BADDR2 0xc848 +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK GENMASK(27, 0) +#define VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(x) ((x) >> 4) + +#define VIU_DMAWR_SIZE0 0xc854 +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK GENMASK(15, 0) +#define VIU_DMAWR_SIZE0_AF_STATS_SIZE(x) ((x) << 0) +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK GENMASK(31, 16) +#define VIU_DMAWR_SIZE0_AWB_STATS_SIZE(x) ((x) << 16) + +#define VIU_DMAWR_SIZE1 0xc858 +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK GENMASK(15, 0) +#define VIU_DMAWR_SIZE1_AE_STATS_SIZE(x) ((x) << 0) + +#endif diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c new file mode 100644 index 000000000000..eaae90cf0d9e --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +#define C3_ISP_RSZ_DEF_PAD_FMT MEDIA_BUS_FMT_YUV8_1X24 +#define C3_ISP_DISP_REG(base, id) ((base) + (id) * 0x400) +#define C3_ISP_PPS_LUT_H_NUM 33 +#define C3_ISP_PPS_LUT_CTYPE_0 0 +#define C3_ISP_PPS_LUT_CTYPE_2 2 +#define C3_ISP_SCL_EN 1 +#define C3_ISP_SCL_DIS 0 + +/* + * struct c3_isp_pps_io_size - ISP scaler input and output size + * + * @thsize: input horizontal size of after preprocessing + * @tvsize: input vertical size of after preprocessing + * @ohsize: output horizontal size + * @ovsize: output vertical size + * @ihsize: input horizontal size + * @max_hsize: maximum horizontal size + */ +struct c3_isp_pps_io_size { + u32 thsize; + u32 tvsize; + u32 ohsize; + u32 ovsize; + u32 ihsize; + u32 max_hsize; +}; + +/* The normal parameters of pps module */ +static const int c3_isp_pps_lut[C3_ISP_PPS_LUT_H_NUM][4] = { + { 0, 511, 0, 0}, { -5, 511, 5, 0}, {-10, 511, 11, 0}, + {-14, 510, 17, -1}, {-18, 508, 23, -1}, {-22, 506, 29, -1}, + {-25, 503, 36, -2}, {-28, 500, 43, -3}, {-32, 496, 51, -3}, + {-34, 491, 59, -4}, {-37, 487, 67, -5}, {-39, 482, 75, -6}, + {-41, 476, 84, -7}, {-42, 470, 92, -8}, {-44, 463, 102, -9}, + {-45, 456, 111, -10}, {-45, 449, 120, -12}, {-47, 442, 130, -13}, + {-47, 434, 140, -15}, {-47, 425, 151, -17}, {-47, 416, 161, -18}, + {-47, 407, 172, -20}, {-47, 398, 182, -21}, {-47, 389, 193, -23}, + {-46, 379, 204, -25}, {-45, 369, 215, -27}, {-44, 358, 226, -28}, + {-43, 348, 237, -30}, {-43, 337, 249, -31}, {-41, 326, 260, -33}, + {-40, 316, 271, -35}, {-39, 305, 282, -36}, {-37, 293, 293, -37} +}; + +static void c3_isp_rsz_pps_size(struct c3_isp_resizer *rsz, + struct c3_isp_pps_io_size *io_size) +{ + int thsize = io_size->thsize; + int tvsize = io_size->tvsize; + u32 ohsize = io_size->ohsize; + u32 ovsize = io_size->ovsize; + u32 ihsize = io_size->ihsize; + u32 max_hsize = io_size->max_hsize; + int h_int; + int v_int; + int h_fract; + int v_fract; + int yuv444to422_en; + + /* Calculate the integer part of horizonal scaler step */ + h_int = thsize / ohsize; + + /* Calculate the vertical part of horizonal scaler step */ + v_int = tvsize / ovsize; + + /* + * Calculate the fraction part of horizonal scaler step. + * step_h_fraction = (source / dest) * 2^24, + * so step_h_fraction = ((source << 12) / dest) << 12. + */ + h_fract = ((thsize << 12) / ohsize) << 12; + + /* + * Calculate the fraction part of vertical scaler step + * step_v_fraction = (source / dest) * 2^24, + * so step_v_fraction = ((source << 12) / dest) << 12. + */ + v_fract = ((tvsize << 12) / ovsize) << 12; + + yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0; + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_444TO422, rsz->id), + DISP0_PPS_444TO422_EN_MASK, + DISP0_PPS_444TO422_EN(yuv444to422_en)); + + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id), + DISP0_PPS_VSC_START_PHASE_STEP_VERT_FRAC(v_fract) | + DISP0_PPS_VSC_START_PHASE_STEP_VERT_INTE(v_int)); + + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id), + DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_FRAC(h_fract) | + DISP0_PPS_HSC_START_PHASE_STEP_HORIZ_INTE(h_int)); +} + +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype) +{ + unsigned int i; + + /* + * Default value of this register is 0, so only need to set + * SCALE_LUMA_COEF_S11_MODE and SCALE_LUMA_CTYPE. This register needs + * to be written in one time. + */ + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id), + ISP_SCALE0_COEF_IDX_LUMA_COEF_S11_MODE_EN | + ISP_SCALE0_COEF_IDX_LUMA_CTYPE(ctype)); + + for (i = 0; i < C3_ISP_PPS_LUT_H_NUM; i++) { + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id), + ISP_SCALE0_COEF_LUMA_DATA0(c3_isp_pps_lut[i][0]) | + ISP_SCALE0_COEF_LUMA_DATA1(c3_isp_pps_lut[i][1])); + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id), + ISP_SCALE0_COEF_LUMA_DATA0(c3_isp_pps_lut[i][2]) | + ISP_SCALE0_COEF_LUMA_DATA1(c3_isp_pps_lut[i][3])); + } + + /* + * Default value of this register is 0, so only need to set + * SCALE_CHRO_COEF_S11_MODE and SCALE_CHRO_CTYPE. This register needs + * to be written in one time. + */ + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id), + ISP_SCALE0_COEF_IDX_CHRO_COEF_S11_MODE_EN | + ISP_SCALE0_COEF_IDX_CHRO_CTYPE(ctype)); + + for (i = 0; i < C3_ISP_PPS_LUT_H_NUM; i++) { + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id), + ISP_SCALE0_COEF_CHRO_DATA0(c3_isp_pps_lut[i][0]) | + ISP_SCALE0_COEF_CHRO_DATA1(c3_isp_pps_lut[i][1])); + c3_isp_write(rsz->isp, + C3_ISP_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id), + ISP_SCALE0_COEF_CHRO_DATA0(c3_isp_pps_lut[i][2]) | + ISP_SCALE0_COEF_CHRO_DATA1(c3_isp_pps_lut[i][3])); + } +} + +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz) +{ + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_HSC_EN_MASK, + DISP0_PPS_SCALE_EN_HSC_DIS); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_VSC_EN_MASK, + DISP0_PPS_SCALE_EN_VSC_DIS); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREVSC_EN_MASK, + DISP0_PPS_SCALE_EN_PREVSC_DIS); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREHSC_EN_MASK, + DISP0_PPS_SCALE_EN_PREHSC_DIS); +} + +static int c3_isp_rsz_pps_enable(struct c3_isp_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct v4l2_rect *crop; + struct v4l2_rect *cmps; + int max_hsize; + int hsc_en, vsc_en; + int preh_en, prev_en; + u32 prehsc_rate; + u32 prevsc_flt_num; + int pre_vscale_max_hsize; + u32 ihsize_after_pre_hsc; + u32 ihsize_after_pre_hsc_alt; + u32 vsc_tap_num_alt; + u32 ihsize; + u32 ivsize; + struct c3_isp_pps_io_size io_size; + + crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK); + cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK); + + ihsize = crop->width; + ivsize = crop->height; + + hsc_en = (ihsize == cmps->width) ? C3_ISP_SCL_DIS : C3_ISP_SCL_EN; + vsc_en = (ivsize == cmps->height) ? C3_ISP_SCL_DIS : C3_ISP_SCL_EN; + + /* Disable pps when there no need to use pps */ + if (!hsc_en && !vsc_en) { + c3_isp_rsz_pps_disable(rsz); + return 0; + } + + /* Pre-scale needs to be enable if the down scaling factor exceeds 4 */ + preh_en = (ihsize > cmps->width * 4) ? C3_ISP_SCL_EN : C3_ISP_SCL_DIS; + prev_en = (ivsize > cmps->height * 4) ? C3_ISP_SCL_EN : C3_ISP_SCL_DIS; + + if (rsz->id == C3_ISP_RSZ_2) { + max_hsize = C3_ISP_MAX_WIDTH; + + /* Set vertical tap number */ + prevsc_flt_num = 4; + + /* Set the max hsize of pre-vertical scale */ + pre_vscale_max_hsize = max_hsize / 2; + } else { + max_hsize = C3_ISP_DEFAULT_WIDTH; + preh_en = (ihsize > max_hsize) ? C3_ISP_SCL_EN : + C3_ISP_SCL_DIS; + + /* Set vertical tap number and the max hsize of pre-vertical */ + if (ihsize > (max_hsize / 2) && + ihsize <= max_hsize && prev_en) { + prevsc_flt_num = 2; + pre_vscale_max_hsize = max_hsize; + } else { + prevsc_flt_num = 4; + pre_vscale_max_hsize = max_hsize / 2; + } + } + + /* + * Set pre-horizonal scale rate and the hsize of after + * pre-horizonal scale. + */ + if (preh_en) { + prehsc_rate = 1; + ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2); + } else { + prehsc_rate = 0; + ihsize_after_pre_hsc = ihsize; + } + + /* Change pre-horizonal scale rate */ + if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize) + prehsc_rate += 1; + + /* Set the actual hsize of after pre-horizonal scale */ + if (preh_en) + ihsize_after_pre_hsc_alt = + DIV_ROUND_UP(ihsize, 1 << prehsc_rate); + else + ihsize_after_pre_hsc_alt = ihsize; + + /* Set vertical scaler bank length */ + if (ihsize_after_pre_hsc_alt <= (max_hsize / 2)) + vsc_tap_num_alt = 4; + else if (ihsize_after_pre_hsc_alt <= max_hsize) + vsc_tap_num_alt = prev_en ? 2 : 4; + else + vsc_tap_num_alt = prev_en ? 4 : 2; + + io_size.thsize = ihsize_after_pre_hsc_alt; + io_size.tvsize = prev_en ? DIV_ROUND_UP(ivsize, 2) : ivsize; + io_size.ohsize = cmps->width; + io_size.ovsize = cmps->height; + io_size.ihsize = ihsize; + io_size.max_hsize = max_hsize; + + c3_isp_rsz_pps_size(rsz, &io_size); + c3_isp_rsz_pps_lut(rsz, C3_ISP_PPS_LUT_CTYPE_0); + c3_isp_rsz_pps_lut(rsz, C3_ISP_PPS_LUT_CTYPE_2); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_VSC_TAP_NUM_MASK, + DISP0_PPS_SCALE_EN_VSC_TAP_NUM(vsc_tap_num_alt)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM_MASK, + DISP0_PPS_SCALE_EN_PREVSC_FLT_NUM(prevsc_flt_num)); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREVSC_RATE_MASK, + DISP0_PPS_SCALE_EN_PREVSC_RATE(prev_en)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREHSC_RATE_MASK, + DISP0_PPS_SCALE_EN_PREHSC_RATE(prehsc_rate)); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_HSC_EN_MASK, + DISP0_PPS_SCALE_EN_HSC_EN(hsc_en)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_VSC_EN_MASK, + DISP0_PPS_SCALE_EN_VSC_EN(vsc_en)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREVSC_EN_MASK, + DISP0_PPS_SCALE_EN_PREVSC_EN(prev_en)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_PREHSC_EN_MASK, + DISP0_PPS_SCALE_EN_PREHSC_EN(preh_en)); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS_MASK, + DISP0_PPS_SCALE_EN_HSC_NOR_RS_BITS(9)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS_MASK, + DISP0_PPS_SCALE_EN_VSC_NOR_RS_BITS(9)); + + return 0; +} + +static void c3_isp_rsz_start(struct c3_isp_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *sink_crop; + u32 mask; + u32 val; + + sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK); + sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK); + src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE); + + c3_isp_write(rsz->isp, C3_ISP_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id), + ISP_DISP0_TOP_IN_SIZE_HSIZE(sink_fmt->width) | + ISP_DISP0_TOP_IN_SIZE_VSIZE(sink_fmt->height)); + + c3_isp_write(rsz->isp, C3_ISP_DISP_REG(DISP0_TOP_CRP2_START, rsz->id), + DISP0_TOP_CRP2_START_V_START(sink_crop->top) | + DISP0_TOP_CRP2_START_H_START(sink_crop->left)); + + c3_isp_write(rsz->isp, C3_ISP_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id), + DISP0_TOP_CRP2_SIZE_V_SIZE(sink_crop->height) | + DISP0_TOP_CRP2_SIZE_H_SIZE(sink_crop->width)); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id), + DISP0_TOP_TOP_CTRL_CROP2_EN_MASK, + DISP0_TOP_TOP_CTRL_CROP2_EN); + + c3_isp_rsz_pps_enable(rsz, state); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_TOP_OUT_SIZE, rsz->id), + DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT_MASK, + DISP0_TOP_OUT_SIZE_SCL_OUT_HEIGHT(src_fmt->height)); + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_TOP_OUT_SIZE, rsz->id), + DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH_MASK, + DISP0_TOP_OUT_SIZE_SCL_OUT_WIDTH(src_fmt->width)); + + if (rsz->id == C3_ISP_RSZ_0) { + mask = ISP_TOP_PATH_EN_DISP0_EN_MASK; + val = ISP_TOP_PATH_EN_DISP0_EN; + } else if (rsz->id == C3_ISP_RSZ_1) { + mask = ISP_TOP_PATH_EN_DISP1_EN_MASK; + val = ISP_TOP_PATH_EN_DISP1_EN; + } else { + mask = ISP_TOP_PATH_EN_DISP2_EN_MASK; + val = ISP_TOP_PATH_EN_DISP2_EN; + } + + c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, mask, val); +} + +static void c3_isp_rsz_stop(struct c3_isp_resizer *rsz) +{ + u32 mask; + u32 val; + + if (rsz->id == C3_ISP_RSZ_0) { + mask = ISP_TOP_PATH_EN_DISP0_EN_MASK; + val = ISP_TOP_PATH_EN_DISP0_DIS; + } else if (rsz->id == C3_ISP_RSZ_1) { + mask = ISP_TOP_PATH_EN_DISP1_EN_MASK; + val = ISP_TOP_PATH_EN_DISP1_DIS; + } else { + mask = ISP_TOP_PATH_EN_DISP2_EN_MASK; + val = ISP_TOP_PATH_EN_DISP2_DIS; + } + + c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, mask, val); + + c3_isp_update_bits(rsz->isp, + C3_ISP_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id), + DISP0_TOP_TOP_CTRL_CROP2_EN_MASK, + DISP0_TOP_TOP_CTRL_CROP2_DIS); + + c3_isp_rsz_pps_disable(rsz); +} + +static bool c3_isp_rsz_streams_ready(struct c3_isp_resizer *rsz) +{ + unsigned int n_links = 0; + struct media_link *link; + + for_each_media_entity_data_link(&rsz->src_sd->entity, link) { + if (link->source->index == C3_ISP_CORE_PAD_SOURCE_VIDEO && + link->flags == MEDIA_LNK_FL_ENABLED) + n_links++; + } + + return n_links == rsz->isp->pipe.start_count; +} + +static int c3_isp_rsz_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_isp_resizer *rsz = v4l2_get_subdevdata(sd); + + c3_isp_rsz_start(rsz, state); + + if (!c3_isp_rsz_streams_ready(rsz)) + return 0; + + c3_isp_params_pre_cfg(rsz->isp); + c3_isp_stats_pre_cfg(rsz->isp); + + return v4l2_subdev_enable_streams(rsz->src_sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, BIT(0)); +} + +static int c3_isp_rsz_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct c3_isp_resizer *rsz = v4l2_get_subdevdata(sd); + + c3_isp_rsz_stop(rsz); + + if (rsz->isp->pipe.start_count != 1) + return 0; + + return v4l2_subdev_disable_streams(rsz->src_sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + BIT(0)); +} + +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUV8_1X24; + + return 0; +} + +static void c3_isp_rsz_set_sink_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *sink_crop; + struct v4l2_rect *sink_cmps; + + sink_fmt = v4l2_subdev_state_get_format(state, format->pad); + sink_crop = v4l2_subdev_state_get_crop(state, format->pad); + sink_cmps = v4l2_subdev_state_get_compose(state, format->pad); + src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE); + + sink_fmt->code = MEDIA_BUS_FMT_YUV8_1X24; + sink_fmt->width = clamp_t(u32, format->format.width, + C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH); + sink_fmt->height = clamp_t(u32, format->format.height, + C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT); + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->colorspace = V4L2_COLORSPACE_SRGB; + sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + sink_crop->width = sink_fmt->width; + sink_crop->height = sink_fmt->height; + sink_crop->left = 0; + sink_crop->top = 0; + + sink_cmps->width = sink_crop->width; + sink_cmps->height = sink_crop->height; + sink_cmps->left = 0; + sink_cmps->top = 0; + + src_fmt->code = sink_fmt->code; + src_fmt->width = sink_cmps->width; + src_fmt->height = sink_cmps->height; + + format->format = *sink_fmt; +} + +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *sink_cmps; + + src_fmt = v4l2_subdev_state_get_format(state, format->pad); + sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK); + sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK); + + src_fmt->code = sink_fmt->code; + src_fmt->width = sink_cmps->width; + src_fmt->height = sink_cmps->height; + src_fmt->field = V4L2_FIELD_NONE; + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + format->format = *src_fmt; +} + +static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + if (format->pad == C3_ISP_RSZ_PAD_SINK) + c3_isp_rsz_set_sink_fmt(state, format); + else + c3_isp_rsz_set_source_fmt(state, format); + + return 0; +} + +static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + struct v4l2_rect *cmps; + + if (sel->pad == C3_ISP_RSZ_PAD_SOURCE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + fmt = v4l2_subdev_state_get_format(state, sel->pad); + sel->r.width = fmt->width; + sel->r.height = fmt->height; + sel->r.left = 0; + sel->r.top = 0; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + crop = v4l2_subdev_state_get_crop(state, sel->pad); + sel->r.width = crop->width; + sel->r.height = crop->height; + sel->r.left = 0; + sel->r.top = 0; + break; + case V4L2_SEL_TGT_CROP: + crop = v4l2_subdev_state_get_crop(state, sel->pad); + sel->r = *crop; + break; + case V4L2_SEL_TGT_COMPOSE: + cmps = v4l2_subdev_state_get_compose(state, sel->pad); + sel->r = *cmps; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *crop; + struct v4l2_rect *cmps; + + if (sel->pad == C3_ISP_RSZ_PAD_SOURCE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + fmt = v4l2_subdev_state_get_format(state, sel->pad); + crop = v4l2_subdev_state_get_crop(state, sel->pad); + cmps = v4l2_subdev_state_get_compose(state, sel->pad); + src_fmt = v4l2_subdev_state_get_format(state, + C3_ISP_RSZ_PAD_SOURCE); + + sel->r.left = clamp_t(s32, sel->r.left, 0, fmt->width - 1); + sel->r.top = clamp_t(s32, sel->r.top, 0, fmt->height - 1); + sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, + fmt->width - sel->r.left); + sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, + fmt->height - sel->r.top); + + crop->width = ALIGN(sel->r.width, 2); + crop->height = ALIGN(sel->r.height, 2); + crop->left = sel->r.left; + crop->top = sel->r.top; + + *cmps = *crop; + + src_fmt->code = fmt->code; + src_fmt->width = cmps->width; + src_fmt->height = cmps->height; + + sel->r = *crop; + break; + case V4L2_SEL_TGT_COMPOSE: + crop = v4l2_subdev_state_get_crop(state, sel->pad); + cmps = v4l2_subdev_state_get_compose(state, sel->pad); + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = clamp(sel->r.width, C3_ISP_MIN_WIDTH, + crop->width); + sel->r.height = clamp(sel->r.height, C3_ISP_MIN_HEIGHT, + crop->height); + + cmps->width = ALIGN(sel->r.width, 2); + cmps->height = ALIGN(sel->r.height, 2); + cmps->left = sel->r.left; + cmps->top = sel->r.top; + + sel->r = *cmps; + + fmt = v4l2_subdev_state_get_format(state, + C3_ISP_RSZ_PAD_SOURCE); + fmt->width = cmps->width; + fmt->height = cmps->height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int c3_isp_rsz_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + struct v4l2_rect *cmps; + + fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SINK); + fmt->width = C3_ISP_DEFAULT_WIDTH; + fmt->height = C3_ISP_DEFAULT_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->code = C3_ISP_RSZ_DEF_PAD_FMT; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + crop = v4l2_subdev_state_get_crop(state, C3_ISP_RSZ_PAD_SINK); + crop->width = C3_ISP_DEFAULT_WIDTH; + crop->height = C3_ISP_DEFAULT_HEIGHT; + crop->left = 0; + crop->top = 0; + + cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RSZ_PAD_SINK); + cmps->width = C3_ISP_DEFAULT_WIDTH; + cmps->height = C3_ISP_DEFAULT_HEIGHT; + cmps->left = 0; + cmps->top = 0; + + fmt = v4l2_subdev_state_get_format(state, C3_ISP_RSZ_PAD_SOURCE); + fmt->width = cmps->width; + fmt->height = cmps->height; + fmt->field = V4L2_FIELD_NONE; + fmt->code = C3_ISP_RSZ_DEF_PAD_FMT; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + return 0; +} + +static const struct v4l2_subdev_pad_ops c3_isp_rsz_pad_ops = { + .enum_mbus_code = c3_isp_rsz_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = c3_isp_rsz_set_fmt, + .get_selection = c3_isp_rsz_get_selection, + .set_selection = c3_isp_rsz_set_selection, + .enable_streams = c3_isp_rsz_enable_streams, + .disable_streams = c3_isp_rsz_disable_streams, +}; + +static const struct v4l2_subdev_ops c3_isp_rsz_subdev_ops = { + .pad = &c3_isp_rsz_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops c3_isp_rsz_internal_ops = { + .init_state = c3_isp_rsz_init_state, +}; + +/* Media entity operations */ +static const struct media_entity_operations c3_isp_rsz_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int c3_isp_rsz_register(struct c3_isp_resizer *rsz) +{ + struct v4l2_subdev *sd = &rsz->sd; + int ret; + + v4l2_subdev_init(sd, &c3_isp_rsz_subdev_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &c3_isp_rsz_internal_ops; + snprintf(sd->name, sizeof(sd->name), "c3-isp-resizer%u", rsz->id); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->entity.ops = &c3_isp_rsz_entity_ops; + + sd->dev = rsz->isp->dev; + v4l2_set_subdevdata(sd, rsz); + + rsz->pads[C3_ISP_RSZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + rsz->pads[C3_ISP_RSZ_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_ISP_RSZ_PAD_MAX, + rsz->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_device_register_subdev(&rsz->isp->v4l2_dev, sd); + if (ret) + goto err_subdev_cleanup; + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(sd); +err_entity_cleanup: + media_entity_cleanup(&sd->entity); + return ret; +} + +static void c3_isp_rsz_unregister(struct c3_isp_resizer *rsz) +{ + struct v4l2_subdev *sd = &rsz->sd; + + v4l2_device_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); +} + +int c3_isp_resizers_register(struct c3_isp_device *isp) +{ + int ret; + + for (unsigned int i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) { + struct c3_isp_resizer *rsz = &isp->resizers[i]; + + rsz->id = i; + rsz->isp = isp; + rsz->src_sd = &isp->core.sd; + + ret = c3_isp_rsz_register(rsz); + if (ret) { + rsz->isp = NULL; + c3_isp_resizers_unregister(isp); + return ret; + } + } + + return 0; +} + +void c3_isp_resizers_unregister(struct c3_isp_device *isp) +{ + for (unsigned int i = C3_ISP_RSZ_0; i < C3_ISP_NUM_RSZ; i++) { + struct c3_isp_resizer *rsz = &isp->resizers[i]; + + if (rsz->isp) + c3_isp_rsz_unregister(rsz); + }; +} diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c new file mode 100644 index 000000000000..21c8567def78 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-stats.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include + +#include +#include +#include + +#include "c3-isp-common.h" +#include "c3-isp-regs.h" + +/* Hardware configuration */ + +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats) +{ + u32 awb_dma_size = sizeof(struct c3_isp_awb_stats); + u32 ae_dma_size = sizeof(struct c3_isp_ae_stats); + u32 awb_dma_addr = stats->buff->dma_addr; + u32 af_dma_addr; + u32 ae_dma_addr; + + ae_dma_addr = awb_dma_addr + awb_dma_size; + af_dma_addr = ae_dma_addr + ae_dma_size; + + c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR0, + VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR_MASK, + VIU_DMAWR_BADDR0_AF_STATS_BASE_ADDR(af_dma_addr)); + + c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR1, + VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR_MASK, + VIU_DMAWR_BADDR1_AWB_STATS_BASE_ADDR(awb_dma_addr)); + + c3_isp_update_bits(stats->isp, VIU_DMAWR_BADDR2, + VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR_MASK, + VIU_DMAWR_BADDR2_AE_STATS_BASE_ADDR(ae_dma_addr)); +} + +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats) +{ + stats->buff = + list_first_entry_or_null(&stats->pending, + struct c3_isp_stats_buffer, list); + if (stats->buff) { + c3_isp_stats_cfg_dmawr_addr(stats); + list_del(&stats->buff->list); + } +} + +void c3_isp_stats_pre_cfg(struct c3_isp_device *isp) +{ + struct c3_isp_stats *stats = &isp->stats; + u32 dma_size; + + c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, + ISP_AF_EN_CTRL_STAT_SEL_MASK, + ISP_AF_EN_CTRL_STAT_SEL_NEW); + c3_isp_update_bits(stats->isp, ISP_AE_CTRL, + ISP_AE_CTRL_LUMA_MODE_MASK, + ISP_AE_CTRL_LUMA_MODE_FILTER); + + /* The unit of dma_size is 16 bytes */ + dma_size = sizeof(struct c3_isp_af_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, + VIU_DMAWR_SIZE0_AF_STATS_SIZE_MASK, + VIU_DMAWR_SIZE0_AF_STATS_SIZE(dma_size)); + + dma_size = sizeof(struct c3_isp_awb_stats) / + C3_ISP_DMA_SIZE_ALIGN_BYTES; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, + VIU_DMAWR_SIZE0_AWB_STATS_SIZE_MASK, + VIU_DMAWR_SIZE0_AWB_STATS_SIZE(dma_size)); + + dma_size = sizeof(struct c3_isp_ae_stats) / C3_ISP_DMA_SIZE_ALIGN_BYTES; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, + VIU_DMAWR_SIZE1_AE_STATS_SIZE_MASK, + VIU_DMAWR_SIZE1_AE_STATS_SIZE(dma_size)); + + guard(spinlock_irqsave)(&stats->buff_lock); + + c3_isp_stats_cfg_buff(stats); +} + +static int c3_isp_stats_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, C3_ISP_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, "AML C3 ISP", sizeof(cap->card)); + + return 0; +} + +static int c3_isp_stats_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct c3_isp_stats *stats = video_drvdata(file); + + if (f->index > 0 || f->type != stats->vb2_q.type) + return -EINVAL; + + f->pixelformat = V4L2_META_FMT_C3ISP_STATS; + + return 0; +} + +static int c3_isp_stats_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_stats *stats = video_drvdata(file); + + f->fmt.meta = stats->vfmt.fmt.meta; + + return 0; +} + +static const struct v4l2_ioctl_ops isp_stats_v4l2_ioctl_ops = { + .vidioc_querycap = c3_isp_stats_querycap, + .vidioc_enum_fmt_meta_cap = c3_isp_stats_enum_fmt, + .vidioc_g_fmt_meta_cap = c3_isp_stats_g_fmt, + .vidioc_s_fmt_meta_cap = c3_isp_stats_g_fmt, + .vidioc_try_fmt_meta_cap = c3_isp_stats_g_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations isp_stats_v4l2_fops = { + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int c3_isp_stats_vb2_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + if (*num_planes) { + if (*num_planes != 1) + return -EINVAL; + + if (sizes[0] < sizeof(struct c3_isp_stats_info)) + return -EINVAL; + + return 0; + } + + *num_planes = 1; + sizes[0] = sizeof(struct c3_isp_stats_info); + + return 0; +} + +static void c3_isp_stats_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_stats_buffer *buf = + container_of(v4l2_buf, struct c3_isp_stats_buffer, vb); + struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue); + + guard(spinlock_irqsave)(&stats->buff_lock); + + list_add_tail(&buf->list, &stats->pending); +} + +static int c3_isp_stats_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = stats->vfmt.fmt.meta.buffersize; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(stats->isp->dev, + "User buffer too small (%ld < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static int c3_isp_stats_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_stats_buffer *buf = + container_of(v4l2_buf, struct c3_isp_stats_buffer, vb); + + buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + return 0; +} + +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q) +{ + struct c3_isp_stats *stats = vb2_get_drv_priv(q); + + guard(spinlock_irqsave)(&stats->buff_lock); + + if (stats->buff) { + vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_ERROR); + stats->buff = NULL; + } + + while (!list_empty(&stats->pending)) { + struct c3_isp_stats_buffer *buff; + + buff = list_first_entry(&stats->pending, + struct c3_isp_stats_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops isp_stats_vb2_ops = { + .queue_setup = c3_isp_stats_vb2_queue_setup, + .buf_queue = c3_isp_stats_vb2_buf_queue, + .buf_prepare = c3_isp_stats_vb2_buf_prepare, + .buf_init = c3_isp_stats_vb2_buf_init, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = c3_isp_stats_vb2_stop_streaming, +}; + +int c3_isp_stats_register(struct c3_isp_device *isp) +{ + struct c3_isp_stats *stats = &isp->stats; + struct video_device *vdev = &stats->vdev; + struct vb2_queue *vb2_q = &stats->vb2_q; + int ret; + + memset(stats, 0, sizeof(*stats)); + stats->vfmt.fmt.meta.dataformat = V4L2_META_FMT_C3ISP_STATS; + stats->vfmt.fmt.meta.buffersize = sizeof(struct c3_isp_stats_info); + stats->isp = isp; + INIT_LIST_HEAD(&stats->pending); + spin_lock_init(&stats->buff_lock); + + mutex_init(&stats->lock); + + snprintf(vdev->name, sizeof(vdev->name), "c3-isp-stats"); + vdev->fops = &isp_stats_v4l2_fops; + vdev->ioctl_ops = &isp_stats_v4l2_ioctl_ops; + vdev->v4l2_dev = &isp->v4l2_dev; + vdev->lock = &stats->lock; + vdev->minor = -1; + vdev->queue = vb2_q; + vdev->release = video_device_release_empty; + vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_RX; + video_set_drvdata(vdev, stats); + + vb2_q->drv_priv = stats; + vb2_q->mem_ops = &vb2_dma_contig_memops; + vb2_q->ops = &isp_stats_vb2_ops; + vb2_q->type = V4L2_BUF_TYPE_META_CAPTURE; + vb2_q->io_modes = VB2_DMABUF | VB2_MMAP; + vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb2_q->buf_struct_size = sizeof(struct c3_isp_stats_buffer); + vb2_q->dev = isp->dev; + vb2_q->lock = &stats->lock; + vb2_q->min_queued_buffers = 2; + + ret = vb2_queue_init(vb2_q); + if (ret) + goto err_destroy; + + stats->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &stats->pad); + if (ret) + goto err_queue_release; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(isp->dev, + "Failed to register %s: %d\n", vdev->name, ret); + goto err_entity_cleanup; + } + + return 0; + +err_entity_cleanup: + media_entity_cleanup(&vdev->entity); +err_queue_release: + vb2_queue_release(vb2_q); +err_destroy: + mutex_destroy(&stats->lock); + return ret; +} + +void c3_isp_stats_unregister(struct c3_isp_device *isp) +{ + struct c3_isp_stats *stats = &isp->stats; + + vb2_queue_release(&stats->vb2_q); + media_entity_cleanup(&stats->vdev.entity); + video_unregister_device(&stats->vdev); + mutex_destroy(&stats->lock); +} + +void c3_isp_stats_isr(struct c3_isp_device *isp) +{ + struct c3_isp_stats *stats = &isp->stats; + + guard(spinlock_irqsave)(&stats->buff_lock); + + if (stats->buff) { + stats->buff->vb.sequence = stats->isp->frm_sequence; + stats->buff->vb.vb2_buf.timestamp = ktime_get(); + stats->buff->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&stats->buff->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + c3_isp_stats_cfg_buff(stats); +} From patchwork Fri Dec 27 07:09:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 854089 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E43B2146593; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; cv=none; b=RDEtgF0o5mN328gGeQHK8O8+7kCCZTK4GEReMjzenXrJfyrLOSHR+wufyO89EhDlM3IDcEllzbPNAX04p/tOiZEug0aECRca7eelYZe0rilhicYUCbuMhLsSjYBfX3XpKM/cgROpX/zbQN8ckusStkT+TXOMVUtHmFqZXhvHWjg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; c=relaxed/simple; bh=PaJQ+pNFMh56pWt9uj1SZSt5Jk6tuzM1yeS1XIuLvVs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=U2Kr8UHIAhLzPFdG1oaSLEsti9EcD1gOy+C/YNm7zCmpsb6/YuzGIrvdKoU7PjgEQBu3hecLmyeIoq0kNsQQIwtu1n/HWTnHwcrsXbGzGAGX3KWKAL10FELE5R6VzBMFlGSiAlPkiKtnKjckOQSarrmpg8ijzO80h7B0h8ULXQM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=W88cPUUj; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="W88cPUUj" Received: by smtp.kernel.org (Postfix) with ESMTPS id C0DF6C4AF0C; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=PaJQ+pNFMh56pWt9uj1SZSt5Jk6tuzM1yeS1XIuLvVs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=W88cPUUjJ6jx7aFGs3WTxxWlMEhYt4E5z0Fcgvnpkh+31Ia+rSa3s6QsN1uex6rXp a03fi6MS7jbeyOGWoisglvf34K+C86Qe55Sa5A9fJ24ZSCR3LMsrxiLywMlNbDutU3 zV5OIm1SD5B5mHPrZbjjAr9SNr6uvtP/cN2tgJ50osovCQh0Wl3G5C+oGGapL29Thj +3GLq2Bl3Y2P2WJZYYJihLKt10n9HuIg3ncBdbPQLpz2vAXMlgEZ9kVZ+uFn9Xvvl6 f/TyyT9XHrXaNQQA2e+isrhtp5q4+mr6NFPQM2mnws/bC5wrX4MU+6Cg6nH639zSbc oNpKJflw4F0Ug== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B9F6FE7718B; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:18 +0800 Subject: [PATCH v5 09/10] Documentation: media: add documentation file metafmt-c3-isp.rst Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-9-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=4958; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=w4dz2vA7GDZeLpb0UA1sUBtNURUTWWS+GdXKL9y1Wec=; b=rIgXwVLTkcbMXuJd3DuyQzXsvzVkiXlqtXba5+hrjOmVWViYDDOWnc0yQdU7C4uK4hyK62mQa 99smmSFNCU5B4arWNVBecbZHSgZbF++SpVovAs88SrbsVcYGur9u61f X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add the file 'metafmt-c3-isp.rst' that documents the meta format of c3-isp. Signed-off-by: Keke Li --- .../userspace-api/media/v4l/meta-formats.rst | 1 + .../userspace-api/media/v4l/metafmt-c3-isp.rst | 86 ++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 88 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst index 86ffb3bc8ade..bb6876cfc271 100644 --- a/Documentation/userspace-api/media/v4l/meta-formats.rst +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst @@ -12,6 +12,7 @@ These formats are used for the :ref:`metadata` interface only. .. toctree:: :maxdepth: 1 + metafmt-c3-isp metafmt-d4xx metafmt-generic metafmt-intel-ipu3 diff --git a/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst new file mode 100644 index 000000000000..b15c40841266 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst @@ -0,0 +1,86 @@ +.. SPDX-License-Identifier: (GPL-2.0-only OR MIT) + +.. _v4l2-meta-fmt-c3isp-stats: +.. _v4l2-meta-fmt-c3isp-params: + +*********************************************************************** +V4L2_META_FMT_C3ISP_STATS ('CSTS'), V4L2_META_FMT_C3ISP_PARAMS ('CPRM') +*********************************************************************** + +.. c3_isp_stats_info + +3A Statistics +============= + +The C3 ISP can collect different statistics over an input Bayer frame. +Those statistics are obtained from the "c3-isp-stats" metadata capture video nodes, +using the :c:type:`v4l2_meta_format` interface. +They are formatted as described by the :c:type:`c3_isp_stats_info` structure. + +The statistics collected are Auto-white balance, +Auto-exposure and Auto-focus information. + +.. c3_isp_params_cfg + +Configuration Parameters +======================== + +The configuration parameters are passed to the c3-isp-params metadata output video node, +using the :c:type:`v4l2_meta_format` interface. Rather than a single struct containing +sub-structs for each configurable area of the ISP, parameters for the C3-ISP +are defined as distinct structs or "blocks" which may be added to the data +member of :c:type:`c3_isp_params_cfg`. Userspace is responsible for +populating the data member with the blocks that need to be configured by the driver, but +need not populate it with **all** the blocks, or indeed with any at all if there +are no configuration changes to make. Populated blocks **must** be consecutive +in the buffer. To assist both userspace and the driver in identifying the +blocks each block-specific struct embeds +:c:type:`c3_isp_params_block_header` as its first member and userspace +must populate the type member with a value from +:c:type:`c3_isp_params_block_type`. Once the blocks have been populated +into the data buffer, the combined size of all populated blocks shall be set in +the data_size member of :c:type:`c3_isp_params_cfg`. For example: + +.. code-block:: c + + struct c3_isp_params_cfg *params = + (struct c3_isp_params_cfg *)buffer; + + params->version = C3_ISP_PARAM_BUFFER_V0; + params->data_size = 0; + + void *data = (void *)params->data; + + struct c3_isp_params_awb_gains *gains = + (struct c3_isp_params_awb_gains *)data; + + gains->header.type = C3_ISP_PARAMS_BLOCK_AWB_GAINS; + gains->header.flags = C3_ISP_PARAMS_BLOCK_FL_ENABLE; + gains->header.size = sizeof(struct c3_isp_params_awb_gains); + + gains->gr_gain = 256; + gains->r_gain = 256; + gains->b_gain = 256; + gains->gb_gain = 256; + + data += sizeof(struct c3_isp__params_awb_gains); + params->data_size += sizeof(struct c3_isp_params_awb_gains); + + struct c3_isp_params_awb_config *awb_cfg = + (struct c3_isp_params_awb_config *)data; + + awb_cfg->header.type = C3_ISP_PARAMS_BLOCK_AWB_CONFIG; + awb_cfg->header.flags = C3_ISP_PARAMS_BLOCK_FL_ENABLE; + awb_cfg->header.size = sizeof(struct c3_isp_params_awb_config); + + awb_cfg->tap_point = C3_ISP_AWB_STATS_TAP_BEFORE_WB; + awb_cfg->satur = 1; + awb_cfg->horiz_zones_num = 32; + awb_cfg->vert_zones_num = 24; + + params->data_size += sizeof(struct c3_isp_params_awb_config); + +Amlogic C3 ISP uAPI data types +=============================== + +.. kernel-doc:: include/uapi/linux/media/amlogic/c3-isp-config.h diff --git a/MAINTAINERS b/MAINTAINERS index d92427630bfa..b2626a370cc0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1248,6 +1248,7 @@ M: Keke Li L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml +F: Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst F: drivers/media/platform/amlogic/c3/isp/ F: include/uapi/linux/media/amlogic/ From patchwork Fri Dec 27 07:09:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 854088 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6D76E14A4DF; Fri, 27 Dec 2024 07:09:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; cv=none; b=mfPwtwNR6P9QLPfE34iIcKdoY9SnvZP07YoD6VtviZQSwyfXVvptDWri8lJYCYYO+Z2UaQrvWZtv8O3TgpEQIoa14t+KpEHSa+hN+NC6uwX8f9tXp3bM43LvVTeBJbj6tJw8bNB91z9gk06/TcJjFM3ZzT2PKTkSXfi1F+MDyh4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1735283378; c=relaxed/simple; bh=i1J0mhvW9a4DClwnnWZIzVLDzkCwppZXfkTj/GxfWcA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ub+wNDny3Isg4T2xvj5xpyrH+ErdANgHRYdNll5iCt++S6hvVjMLC3j5yWd6+3TPLXGnoLRxMxGJUrghkd+SY7HEvU33QejW+1gLU/phbqFAplQl1AneBX92wnWG3Tobkel7I/HN3NHE0ZWjd8zH9zvgvME8DVMyA/8wDnRhr0Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=p0QlHb10; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="p0QlHb10" Received: by smtp.kernel.org (Postfix) with ESMTPS id D14B4C4CEDD; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1735283377; bh=i1J0mhvW9a4DClwnnWZIzVLDzkCwppZXfkTj/GxfWcA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=p0QlHb102oi2Bihigwq07IGH2EEoKkPEKRDSo3q0DoNiJRSauyswwLegaJOlotKJ4 7eP2C0sYuYT5z1LhmIcLuda1wzKVWE2GKTH/45IRKKHlj8KU0cWMgslhd8QeLntqw6 Zx8FlTRrXwAlTZ8A/8qbNhN6ABdtj0R5FLNSSnBWD2ua+WRlz5H7uGwQRTtor8ddL9 A8tMB9LeB0zzJ7U2Mt2//a4l4oXvERhUJy8OFIV4Ayb08803K4Y2mEDX4oQTGb3tPX Xe9t0QbYG0WxD0TfZeYDJl5uQLgk16xLso/mrCGDycWhl3WYErMpnTShwifj2YuqUK C/lorILhE+Rxw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CA64EE7718F; Fri, 27 Dec 2024 07:09:37 +0000 (UTC) From: Keke Li via B4 Relay Date: Fri, 27 Dec 2024 15:09:19 +0800 Subject: [PATCH v5 10/10] Documentation: media: add documentation file c3-isp.rst Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241227-c3isp-v5-10-c7124e762ff6@amlogic.com> References: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> In-Reply-To: <20241227-c3isp-v5-0-c7124e762ff6@amlogic.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, kieran.bingham@ideasonboard.com, laurent.pinchart@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1735283374; l=8110; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=1vRdKPzff/46+mgERVzXgPBajqANliFbOSvtsnU0Dac=; b=pQFTz6lKs9HNUk8OO+Rveftc6BgTH9PSDY0J38OCC6EeZ0wHvmJFORz9HdTq+g2PxBid9tWqU UmJlFKySxloA6V3JDZg6rLRoH4cXLkk54GcEddwPnZ/1r3tMjIokPpO X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add the file 'c3-isp.rst' that documents the c3-isp driver. Signed-off-by: Keke Li --- Documentation/admin-guide/media/c3-isp.dot | 26 ++++++ Documentation/admin-guide/media/c3-isp.rst | 109 ++++++++++++++++++++++++ Documentation/admin-guide/media/v4l-drivers.rst | 1 + MAINTAINERS | 2 + 4 files changed, 138 insertions(+) diff --git a/Documentation/admin-guide/media/c3-isp.dot b/Documentation/admin-guide/media/c3-isp.dot new file mode 100644 index 000000000000..f96217dbcebf --- /dev/null +++ b/Documentation/admin-guide/media/c3-isp.dot @@ -0,0 +1,26 @@ +digraph board { + rankdir=TB + n00000001 [label="{{ 0 | 1} | c3-isp-core\n/dev/v4l-subdev0 | { 2 | 3}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port3 -> n00000006:port0 + n00000001:port3 -> n00000009:port0 + n00000001:port3 -> n0000000c:port0 + n00000001:port2 -> n00000025 + n00000006 [label="{{ 0} | c3-isp-resizer0\n/dev/v4l-subdev1 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000006:port1 -> n00000019 [style=bold] + n00000009 [label="{{ 0} | c3-isp-resizer1\n/dev/v4l-subdev2 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000009:port1 -> n0000001d [style=bold] + n0000000c [label="{{ 0} | c3-isp-resizer2\n/dev/v4l-subdev3 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000c:port1 -> n00000021 [style=bold] + n0000000f [label="{{ 0} | c3-mipi-adapter\n/dev/v4l-subdev4 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000f:port1 -> n00000001:port0 [style=bold] + n00000014 [label="{{ 0} | c3-mipi-csi2\n/dev/v4l-subdev5 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000014:port1 -> n0000000f:port0 [style=bold] + n00000019 [label="c3-isp-cap0\n/dev/video0", shape=box, style=filled, fillcolor=yellow] + n0000001d [label="c3-isp-cap1\n/dev/video1", shape=box, style=filled, fillcolor=yellow] + n00000021 [label="c3-isp-cap2\n/dev/video2", shape=box, style=filled, fillcolor=yellow] + n00000025 [label="c3-isp-stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow] + n00000029 [label="c3-isp-params\n/dev/video4", shape=box, style=filled, fillcolor=yellow] + n00000029 -> n00000001:port1 + n0000003d [label="{{} | imx290 2-001a\n/dev/v4l-subdev6 | { 0}}", shape=Mrecord, style=filled, fillcolor=green] + n0000003d:port0 -> n00000014:port0 [style=bold] +} diff --git a/Documentation/admin-guide/media/c3-isp.rst b/Documentation/admin-guide/media/c3-isp.rst new file mode 100644 index 000000000000..8adac4605a6a --- /dev/null +++ b/Documentation/admin-guide/media/c3-isp.rst @@ -0,0 +1,109 @@ +.. SPDX-License-Identifier: (GPL-2.0-only OR MIT) + +.. include:: + +================================================= +Amlogic C3 Image Signal Processing (C3ISP) driver +================================================= + +Introduction +============ + +This file documents the Amlogic C3ISP driver located under +drivers/media/platform/amlogic/c3/isp. + +The current version of the driver supports the C3ISP found on +Amlogic C308L processor. + +The driver implements V4L2, Media controller and V4L2 subdev interfaces. +Camera sensor using V4L2 subdev interface in the kernel is supported. + +The driver has been tested on AW419-C308L-Socket platform. + +Anlogic Camera hardware +======================= + +The Camera hardware found on C308L processors and supported by +the driver consists of: + +- 1 MIPI-CSI2 module. It handle the Physical layer of the CSI2 receivers and + receive MIPI data. + A separate camera sensor can be connected to MIPI-CSi2 module. +- 1 MIPI-ADAPTER module. Organize MIPI data to meet ISP input requirements and + send MIPI data to ISP +- 1 ISP (Image Signal Processing) module. Contain a pipeline of image processing + hardware blocks. + The ISP pipeline contains three scalers at the end. + The ISP also contains the DMA interface which writes the output data to memory. + +A high level functional view of the C3 ISP is presented below. The ISP +takes input from the sensor.:: + + +---------+ +-------+ + | Scaler |--->| WRMIF | + +---------+ +------------+ +--------------+ +-------+ |---------+ +-------+ + | Sensor |--->| MIPI CSI-2 |--->| MIPI ADAPTER |--->| ISP |---|---------+ +-------+ + +---------+ +------------+ +--------------+ +-------+ | Scaler |--->| WRMIF | + +---------+ +-------+ + |---------+ +-------+ + | Scaler |--->| WRMIF | + +---------+ +-------+ + +Supported functionality +======================= + +The current version of the driver supports: + +- Input from camera sensor via MIPI-CSI2; + +- Pixel output interface of ISP + + - Scaling support. Configuration of the scaler module + for downscalling with ratio up to 8x. + +Driver Architecture and Design +============================== + +The driver implements the V4L2 subdev interface. With the goal to model the +hardware links between the modules and to expose a clean, logical and usable +interface, the driver is split into V4L2 sub-devices as follows: + +- 1 c3-mipi-csi2 sub-device - mipi-csi2 is represented by a single sub-device. +- 1 c3-mipi-adapter sub-device - mipi-adapter is represented by a single sub-devices. +- 1 c3-isp-core sub-device - isp-core is represented by a single sub-devices. +- 3 c3-isp-resizer sub-devices - isp-resizer is represented by a number of sub-devices + equal to the number of capture device. + +c3-isp-core sub-device is linked to 2 separate video device nodes and +3 c3-isp-resizer sub-devices nodes. + +- 1 capture statistics video device node. +- 1 output parameters video device node. +- 3 c3-isp-resizer sub-device nodes. + +c3-isp-resizer sub-device is linked to capture video device node. + +- c3-isp-resizer0 is linked to c3-isp-cap0 +- c3-isp-resizer1 is linked to c3-isp-cap1 +- c3-isp-resizer2 is linked to c3-isp-cap2 + +The media controller pipeline graph is as follows (with connected a +IMX290 camera sensor): + +.. _isp_topology_graph: + +.. kernel-figure:: c3-isp.dot + :alt: c3-isp.dot + :align: center + + Media pipeline topology + +Implementation +============== + +Runtime configuration of the hardware via 'c3-isp-params' video device node. +Acquiring statistics of ISP hardware via 'c3-isp-stats' video device node. +Acquiring output image of ISP hardware via 'c3-isp-cap[0, 2]' video device node. + +The output size of the scaler module in the ISP is configured with +the pixel format of 'c3-isp-cap[0, 2]' video device node. diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index e8761561b2fe..3bac5165b134 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -10,6 +10,7 @@ Video4Linux (V4L) driver-specific documentation :maxdepth: 2 bttv + c3-isp cafe_ccic cx88 fimc diff --git a/MAINTAINERS b/MAINTAINERS index b2626a370cc0..0dd171521d6e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1247,6 +1247,8 @@ AMLOGIC ISP DRIVER M: Keke Li L: linux-media@vger.kernel.org S: Maintained +F: Documentation/admin-guide/media/c3-isp.dot +F: Documentation/admin-guide/media/c3-isp.rst F: Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml F: Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst F: drivers/media/platform/amlogic/c3/isp/