From patchwork Thu Feb 27 07:27:12 2025 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: 870385 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 4088813777E; Thu, 27 Feb 2025 07:27:17 +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=1740641238; cv=none; b=S07ckwOby0Abkzf5jSrAKFn6wTv0O74VFVTNMXneIbrCzpyK2RKa3RqvGVkkVlwVfSb6T/PmWrPdNBfCuP3qR6bUBdE1eloXAZKpDmz60Dw2JyG/l0Kzt7WkHPC8J+zoiyxXDN7EMwpB6wHV8yF7Nk9aabbbAUtFFRJtYTa8nLE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740641238; c=relaxed/simple; bh=mUEnnIBt0E4X0McB7uSdLl/EHQ1pwdGxfllLmAMjVY8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Y2tM3UADcEK8GmHvnXhe6kLuKZ06L0nAdBChNW6ZIXhkRAvGpvjDvnVQc3nVVeI2v9w6RevwK1EHdrmusQ4Cjwbaxplj8tAk5XsfONZC/HdlS6iUAaMQ4Q8Qr0Pw0yrb9DKLgDkXrBGyR/z58jRP6b8bF9hoe94KQjoFhd4M71I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Jpnq962c; 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="Jpnq962c" Received: by smtp.kernel.org (Postfix) with ESMTPS id C2430C4CEE7; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1740641237; bh=mUEnnIBt0E4X0McB7uSdLl/EHQ1pwdGxfllLmAMjVY8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Jpnq962cNzdx5hRcYYBQB/NR80P87j7p7LtkyMZU/svdij+4TWCnYmg6fz2LMAFmM NHFELxa7LA+KpFEJRTAcn9cFNlB2heablks36DMEC9A/QvPr48JFpeaJZ4a+BgBrPW Rgg+apC9qoTxsngqlsNjaZa10sjaxzjW4G9pTo4m2AWfsVg0GYb4i0za5dNMhIPloe Nqx595lPK9+pW7BbvEHQ8PoK+ZiHw0pBBVrjv8UWcKk98jjzfSL5UP8RzcKM/PXOQN jIKus2yXn2kKuhh2OiROGSon7/jzk2u0MQakjKdknFyKqruQwHlgqOYupmitbdcwXY SnIBUGf5aLMzw== 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 9DBD5C1B0D9; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) From: Keke Li via B4 Relay Date: Thu, 27 Feb 2025 15:27:12 +0800 Subject: [PATCH v6 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: <20250227-c3isp-v6-1-f72e19084d0d@amlogic.com> References: <20250227-c3isp-v6-0-f72e19084d0d@amlogic.com> In-Reply-To: <20250227-c3isp-v6-0-f72e19084d0d@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=1740641234; l=4416; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=qlW2s7Pbdw+YMgHyq38D0qj4KtoDydNxuyBolXZJNVQ=; b=5ScUV5SPcfUazhwr5Dz1PXpiOeElB0e1Ig4ipSC+2O4fb9uFu8K3iH2QHSlumOtQbQqJtgU9K rMrTUr1eaMVBz3i+tzhMfOrqhr0O+Zu5f/QAXE2uFRhq2e1IP58DDtT 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. Signed-off-by: Keke Li --- .../bindings/media/amlogic,c3-mipi-csi2.yaml | 127 +++++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 133 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..b0129beab0c3 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml @@ -0,0 +1,127 @@ +# 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 + + 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 bd15d86a3ad7..d11a15ad5f62 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1252,6 +1252,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 Thu Feb 27 07:27:13 2025 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: 870384 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 408E621C9F4; Thu, 27 Feb 2025 07:27:18 +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=1740641238; cv=none; b=s3jpBmB9mkH3IfN6gKQ3v6sMMjt9UyAy659gE5G2pWewFZcaZT0NCIn1EfCP0epexZUtN4jRgTqWzy/rq/rcXp/i2QQ57a/nteNdvrvovEvKwZ3VnKAMkB54ld2CfdkvXZZ1gnDi3dUusMgAZ6Jdv0KcKKrMKRrJ7+2+fmdpUrc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740641238; c=relaxed/simple; bh=zBa597WMQlsUehkTCivmA8hMtfS2BzA9VKnqR7RxPxM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=D0b4AOYWXJO33CNHVpzPQmFL+c+UF5q3AVqd8k88kT1yAlw4TJIk3DrxjSAhzAk26cbZlkrLHdScaeFJWQdMO3AYWf5ato/umcW7Z1xWx+S9T6guvDqfjwAVXyncgje4rA6IzrAd2NgxEf7yb26Pxy4IG/XEDtQxgNZRnNk3yFc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=s4+mehsv; 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="s4+mehsv" Received: by smtp.kernel.org (Postfix) with ESMTPS id CE73BC4AF0C; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1740641237; bh=zBa597WMQlsUehkTCivmA8hMtfS2BzA9VKnqR7RxPxM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=s4+mehsvRqq/72qwGQMSu9Xph7eBCYDeuVWmsADzrYg8cwcByKxWidf/iqqCCJAZ0 dSfFiaRuXmAFeoOOwyDDPwOGflZ+0PpTxKjOHZQE9qjIIvOXzS4AXgOBFvq5F4jC81 ITv8zveQKuBmh2KVDGIVjS/sLYQVqaZ9GTGykYPaLxlKXHuoIbBPXecKrhLW0Yt015 +AXX5t0zVEPiN3Xa4XpVlwJb7KiBiIJj9OBJISbgtnySAazeoo+fFSxByTCyhIHKsn 12ssFg8UBY0ZEdaWbFjjeWji8GDTTL16TQDp31KVWjuPQxT+4nPYkt7KrBzZBKWVGu Z+AMY+SztJvIw== 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 B3724C19F2E; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) From: Keke Li via B4 Relay Date: Thu, 27 Feb 2025 15:27:13 +0800 Subject: [PATCH v6 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: <20250227-c3isp-v6-2-f72e19084d0d@amlogic.com> References: <20250227-c3isp-v6-0-f72e19084d0d@amlogic.com> In-Reply-To: <20250227-c3isp-v6-0-f72e19084d0d@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=1740641235; l=30652; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=+45pUewNRFe2o+ZPinDbo7ohQnpYyrz6joDCh5aovkg=; b=HFo4bdkSRQZOFH23kj/RZhFNAEnv12tP91NKfSGPMrc1Gst5s4FQWThQ0WBqSzpkXwZK2Cloh CiDVeWJPnE9DqNQGdHVKzmgEfSbNZdQYg4XAlAnVLo9lW4KU3cSYErN 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 Amlogic 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 Reviewed-by: Jacopo Mondi 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 | 827 +++++++++++++++++++++ 8 files changed, 856 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d11a15ad5f62..b12a25ad9db2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1257,6 +1257,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..f92815ffa4ae --- /dev/null +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c @@ -0,0 +1,827 @@ +// 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) +{ + struct v4l2_mbus_framefmt *fmt; + + 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: + 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; + + 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 Thu Feb 27 07:27:16 2025 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: 870382 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 7343C2222A0; Thu, 27 Feb 2025 07:27:18 +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=1740641238; cv=none; b=nE4i/CR5aGNxD2yWacA4AAoUwfcv3+P+rEMnIyzFRsjMKnOuvFvMzQZciPSLRf3K52NXjrSKYXRJBsxlZNN1mn/LRxP7QN1Cm2uovpn9PW1zLPw6Izbuism38a5V0wtyfrAsP1JCQG2L554tQvFqJlnSM5dqb9B149ompxFMlVo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740641238; c=relaxed/simple; bh=HSAclOGYTrpuo8R9UEi8hg1vrkJIVKjDam+A5llp4FI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=D9ZpkwLkcQBf8MKF7y+mvScHxjtg3esx6fdFP/+j2vMOYuLNzxJjH6HaJMijSOaWESrQOmWvce2LaDosXqqY3L8cqMYU5rDc7BBpkx98rvuWaQlIJdKFlwf176LwtCv9/GC9riJm8J9mVtML93VKsnr3conYnTzQ3zOQ6FbcBDg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AOqUSk/U; 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="AOqUSk/U" Received: by smtp.kernel.org (Postfix) with ESMTPS id F30E8C4CEED; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1740641238; bh=HSAclOGYTrpuo8R9UEi8hg1vrkJIVKjDam+A5llp4FI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=AOqUSk/UcduFcUmJ/lCIettZub50nYRTLwhAtRWH29ZUofpB94R3JXDsF2jHZDBv7 k1uLkoQZo4tbD3wfsSPm2AgJlcJ9Q6ykbQkU0OjYI6HBa9LNgXDRalb8RHlQbuk4P4 c5xXZl/47wwe25/sjw7i3wik5/IpmFJ2U0ZBCneyeNuj+PFWKJu/YqkjkqnUQNslZ7 79kM0Cr7XOmEUd2ATmcpLODuDylGGMUvOU2SiTeYwRbEpPBiseioY695F5g+kH3SWX t10kEiakKeIGP+oaAotgDeFI0Higf1YfQk9Hf3R6MwtEwTm01pqY+zotJymUf0IX5z BMKkAjXkwliwQ== 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 EB665C1B0D9; Thu, 27 Feb 2025 07:27:17 +0000 (UTC) From: Keke Li via B4 Relay Date: Thu, 27 Feb 2025 15:27:16 +0800 Subject: [PATCH v6 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: <20250227-c3isp-v6-5-f72e19084d0d@amlogic.com> References: <20250227-c3isp-v6-0-f72e19084d0d@amlogic.com> In-Reply-To: <20250227-c3isp-v6-0-f72e19084d0d@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=1740641235; l=3207; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=EHs/phh4vXvzEuo2rEKTjxu0DSB+uSkdhIq6EoyKz08=; b=wJ7RN+ohGF2yDCvF4W3MAsE3yCA8DxHAAqeEkOw1yEKyNMMfGbLj9odlHosvveKWA5h8FeG+F D9k+hhVchXmDYr2qD7R6sN8QISM80z2LDW5BnnYlf3hK6B/8U6ib3Sd 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. Signed-off-by: Keke Li --- .../devicetree/bindings/media/amlogic,c3-isp.yaml | 88 ++++++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 94 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..123bf462f098 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml @@ -0,0 +1,88 @@ +# 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 + + 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 98ae971936a0..72f403904df4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1252,6 +1252,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 Thu Feb 27 07:27:19 2025 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: 870380 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 752BE2222A3; Thu, 27 Feb 2025 07:27:18 +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=1740641238; cv=none; b=f9/Kcd5sIu/QBTKMc2AfnWl+ZvVTjzewXhta/B7mrAQ/aTtdcWHu+REqCRLvPPreBXlA4x74bWbn9Zi1Qg2Wc+xCUdpMHGni+MsTHdXOHQ7nfRoLmydI1vfdSMz1wV+L/o8ZmKNvekWzAv7qmXBbzw/dmguDA99I2eF12f0S6PY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740641238; c=relaxed/simple; bh=GtKsttGiOjBXlxlG9s2MJAuBtrGTO3pcyt9DuKZeMiE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=lvuRQT6Dzd8/RbIlX2jW7fX+BYErcGju19oAYD+xyXLqFfDIAbstZQ7tgWCjsLrto7Z8sYcGI+tByT5QGaLVlzmIC8m+VQ5X8rzcFiIbaW89HfjYyfkplIHwrqAIBcetsAeU7F4X9s+hRKUzWW6vzABIUCBYSiRrtrEJKPbFKzA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gzolTJaW; 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="gzolTJaW" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4A0E9C4CEF7; Thu, 27 Feb 2025 07:27:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1740641238; bh=GtKsttGiOjBXlxlG9s2MJAuBtrGTO3pcyt9DuKZeMiE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=gzolTJaWpV6QoR9VMxuf14IooPrt1PwIX0RizCihCiXCWoABS5Mz9yNWRa8Ky/wEF fXGS8uIJRRmmxMRb032LP/24bvYVLcxwgQ/fsGQOMEIUSVv9a2C5e3tzXf+38hl36r OdJIEiG65u13JmA/nafc/MeEx2YQPKXg0OmvBNBkF73/kba5In/g2WIFnHiTtoCRWn jUBvfSRz3W5u+WQOOq7X4IhZWknoLzkNBDUR+BCrWGUCOAdZ5oO8Ej2Wp3yNO4sczp ay+Iwy8aSITzhsb7ajHlHkm78NVOSmopaRPdyHch2bbVrPp9A4g2/TTOlmaVAvASIH rUo0tY7MFgfFg== 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 40A18C1B087; Thu, 27 Feb 2025 07:27:18 +0000 (UTC) From: Keke Li via B4 Relay Date: Thu, 27 Feb 2025 15:27:19 +0800 Subject: [PATCH v6 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: <20250227-c3isp-v6-8-f72e19084d0d@amlogic.com> References: <20250227-c3isp-v6-0-f72e19084d0d@amlogic.com> In-Reply-To: <20250227-c3isp-v6-0-f72e19084d0d@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=1740641235; l=171222; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=edK0ZhnAfbHgtU77kr0Uiyb6skkEe8juY3UezJWHmSQ=; b=asjodYM2F00SSvuSSHSyReA4JmLP1qQAQZ4YQrmLee6C2rP0/5iZ9iNVW323AKNcbP/uiq+bE si4YVS5P09uAF+8cEVXICAa1lc7e/ODVVJbKmaRmxrM3/ZiAEk1orkR 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. Reviewed-by: Jacopo Mondi 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 | 806 ++++++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-common.h | 340 +++++++ .../media/platform/amlogic/c3/isp/c3-isp-core.c | 641 +++++++++++++ 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 | 618 ++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-resizer.c | 892 +++++++++++++++++ .../media/platform/amlogic/c3/isp/c3-isp-stats.c | 328 +++++++ 13 files changed, 5087 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 26b94a1e90eb..3cedf8d29be4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1257,6 +1257,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..f419faf8c485 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-capture.c @@ -0,0 +1,806 @@ +// 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[] = { + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .fourcc = V4L2_PIX_FMT_GREY, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_Y_ONLY, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT, + .hdiv = 1, + .vdiv = 1, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .fourcc = V4L2_PIX_FMT_NV12M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT, + .hdiv = 2, + .vdiv = 2, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .fourcc = V4L2_PIX_FMT_NV21M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV420, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT, + .hdiv = 2, + .vdiv = 2, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .fourcc = V4L2_PIX_FMT_NV16M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_UV, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT, + .hdiv = 1, + .vdiv = 2 + }, { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .fourcc = V4L2_PIX_FMT_NV61M, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_YUV422, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X2, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_8BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_8BIT, + .hdiv = 1, + .vdiv = 2, + }, + /* RAW formats */ + { + .mbus_code = MEDIA_BUS_FMT_SRGGB16_1X16, + .fourcc = V4L2_PIX_FMT_SRGGB12, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_RAW, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT, + .hdiv = 1, + .hdiv = 1, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16, + .fourcc = V4L2_PIX_FMT_SBGGR12, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_RAW, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT, + .hdiv = 1, + .hdiv = 1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG16_1X16, + .fourcc = V4L2_PIX_FMT_SGRBG12, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_RAW, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT, + .hdiv = 1, + .hdiv = 1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG16_1X16, + .fourcc = V4L2_PIX_FMT_SGBRG12, + .format = ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_RAW, + .planes = ISP_WRMIFX3_0_FMT_CTRL_MTX_PLANE_X1, + .ch0_pix_bits = ISP_WRMIFX3_0_CH0_CTRL1_PIX_BITS_16BITS, + .uv_swap = ISP_WRMIFX3_0_FMT_CTRL_MTX_UV_SWAP_VU, + .in_bits = ISP_WRMIFX3_0_FMT_CTRL_MTX_IBITS_16BIT, + .hdiv = 1, + .hdiv = 1, + }, +}; + +/* 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, + info->in_bits); + + 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->planes); + + 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, + info->ch0_pix_bits); + + 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_subdev_format src_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index, + }; + int ret; + + ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt); + if (ret) + return ret; + + if (src_fmt.format.width != cap->format.pix_mp.width || + src_fmt.format.height != cap->format.pix_mp.height || + src_fmt.format.code != cap->format.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, cap->format.info->mbus_code, + cap->format.pix_mp.width, cap->format.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_NV12M; + cap->format.pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + cap->format.info = + c3_cap_find_fmt(cap->format.pix_mp.pixelformat); + + 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..cb470802e61e --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-common.h @@ -0,0 +1,340 @@ +/* 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_0, + C3_ISP_CORE_PAD_SOURCE_VIDEO_1, + C3_ISP_CORE_PAD_SOURCE_VIDEO_2, + 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 + * @planes: defines the mutil plane of hardware + * @ch0_pix_bits: defines the channel 0 pixel bits mode of hardware + * @uv_swap: defines the uv swap flag of hardware + * @in_bits: defines the input bits 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 planes; + u32 ch0_pix_bits; + u8 uv_swap; + u8 in_bits; + 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 + * @src_pad: the pad of source sub-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; + u32 src_pad; +}; + +/* + * 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 + * @resizers: 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..ff6413fff889 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c @@ -0,0 +1,641 @@ +// 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_YUV10_1X30 + +/* + * struct c3_isp_core_format_info - ISP core 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 + * @is_raw: the raw format flag of mbus code + */ +struct c3_isp_core_format_info { + u32 mbus_code; + u32 pads; + u8 xofst; + u8 yofst; + bool is_raw; +}; + +static const struct c3_isp_core_format_info c3_isp_core_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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .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, + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB16_1X16, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_0) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_1) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_2), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_0) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_1) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_2), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG16_1X16, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_0) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_1) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_2), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG16_1X16, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_0) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_1) + | BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_2), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + .is_raw = true, + }, + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_0) | + BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_1) | + BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO_2), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + .is_raw = false, + }, +}; + +static const struct c3_isp_core_format_info +*core_find_format_by_code(u32 code, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_fmts); i++) { + const struct c3_isp_core_format_info *info = + &c3_isp_core_fmts[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +static const struct c3_isp_core_format_info +*core_find_format_by_index(u32 index, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_core_fmts); i++) { + const struct c3_isp_core_format_info *info = + &c3_isp_core_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_core_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 bool c3_isp_core_streams_ready(struct c3_isp_core *core) +{ + unsigned int n_links = 0; + struct media_link *link; + + for_each_media_entity_data_link(&core->sd.entity, link) { + if ((link->source->index == C3_ISP_CORE_PAD_SOURCE_VIDEO_0 || + link->source->index == C3_ISP_CORE_PAD_SOURCE_VIDEO_1 || + link->source->index == C3_ISP_CORE_PAD_SOURCE_VIDEO_2) && + link->flags == MEDIA_LNK_FL_ENABLED) + n_links++; + } + + return n_links == core->isp->pipe.start_count; +} + +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; + + if (!c3_isp_core_streams_ready(core)) + return 0; + + 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->isp->pipe.start_count != 1) + return 0; + + 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_core_format_info *info; + + switch (code->pad) { + case C3_ISP_CORE_PAD_SINK_VIDEO: + case C3_ISP_CORE_PAD_SOURCE_VIDEO_0: + case C3_ISP_CORE_PAD_SOURCE_VIDEO_1: + case C3_ISP_CORE_PAD_SOURCE_VIDEO_2: + 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_core_format_info *isp_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, format->pad); + + 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; + + for (unsigned int i = C3_ISP_CORE_PAD_SOURCE_VIDEO_0; + i < C3_ISP_CORE_PAD_MAX; i++) { + src_fmt = v4l2_subdev_state_get_format(state, i); + + 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_core_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->ycbcr_enc = V4L2_YCBCR_ENC_601; + + if (isp_fmt && isp_fmt->is_raw) { + src_fmt->colorspace = V4L2_COLORSPACE_RAW; + src_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + src_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } else { + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + 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_0 || + format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO_1 || + format->pad == C3_ISP_CORE_PAD_SOURCE_VIDEO_2) + 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 */ + for (unsigned int i = C3_ISP_CORE_PAD_SOURCE_VIDEO_0; + i < C3_ISP_CORE_PAD_MAX; i++) { + fmt = v4l2_subdev_state_get_format(state, i); + 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_0].flags = MEDIA_PAD_FL_SOURCE; + core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO_1].flags = MEDIA_PAD_FL_SOURCE; + core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO_2].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..c3b779f63088 --- /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_0 + i, + &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..0e0b5d61654a --- /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_coord[i]) | + ISP_AWB_IDX_DATA_VIDX_DATA(cfg->vert_coord[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_coord[i]) | + ISP_AE_IDX_DATA_VIDX_DATA(cfg->vert_coord[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_coord[i]) | + ISP_AF_IDX_DATA_VIDX_DATA(cfg->vert_coord[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..fa249985a771 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-regs.h @@ -0,0 +1,618 @@ +/* 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_DISPIN_SEL 0x0018 +#define ISP_TOP_DISPIN_SEL_DISP0_MASK GENMASK(3, 0) +#define ISP_TOP_DISPIN_SEL_DISP0_CORE_OUT (0 << 0) +#define ISP_TOP_DISPIN_SEL_DISP0_MIPI_OUT (2 << 0) +#define ISP_TOP_DISPIN_SEL_DISP1_MASK GENMASK(7, 4) +#define ISP_TOP_DISPIN_SEL_DISP1_CORE_OUT (0 << 4) +#define ISP_TOP_DISPIN_SEL_DISP1_MIPI_OUT (2 << 4) +#define ISP_TOP_DISPIN_SEL_DISP2_MASK GENMASK(11, 8) +#define ISP_TOP_DISPIN_SEL_DISP2_CORE_OUT (0 << 8) +#define ISP_TOP_DISPIN_SEL_DISP2_MIPI_OUT (2 << 8) + +#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 ISP_WRMIFX3_0_FMT_CTRL_MODE_OUT_RAW (4 << 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..453a889e0b27 --- /dev/null +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c @@ -0,0 +1,892 @@ +// 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_YUV10_1X30 +#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_rsz_format_info - ISP resizer format information + * + * @mbus_code: the mbus code + * @pads: bitmask detailing valid pads for this mbus_code + * @is_raw: the raw format flag of mbus code + */ +struct c3_isp_rsz_format_info { + u32 mbus_code; + u32 pads; + bool is_raw; +}; + +static const struct c3_isp_rsz_format_info c3_isp_rsz_fmts[] = { + /* RAW formats */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16, + .pads = BIT(C3_ISP_RSZ_PAD_SINK) + | BIT(C3_ISP_RSZ_PAD_SOURCE), + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG16_1X16, + .pads = BIT(C3_ISP_RSZ_PAD_SINK) + | BIT(C3_ISP_RSZ_PAD_SOURCE), + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG16_1X16, + .pads = BIT(C3_ISP_RSZ_PAD_SINK) + | BIT(C3_ISP_RSZ_PAD_SOURCE), + .is_raw = true, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB16_1X16, + .pads = BIT(C3_ISP_RSZ_PAD_SINK) + | BIT(C3_ISP_RSZ_PAD_SOURCE), + .is_raw = true, + }, + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .pads = BIT(C3_ISP_RSZ_PAD_SINK) + | BIT(C3_ISP_RSZ_PAD_SOURCE), + .is_raw = false, + }, +}; + +/* + * 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 const struct c3_isp_rsz_format_info +*rsz_find_format_by_code(u32 code, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_rsz_fmts); i++) { + const struct c3_isp_rsz_format_info *info = &c3_isp_rsz_fmts[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +static const struct c3_isp_rsz_format_info +*rsz_find_format_by_index(u32 index, u32 pad) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(c3_isp_rsz_fmts); i++) { + const struct c3_isp_rsz_format_info *info = &c3_isp_rsz_fmts[i]; + + if (!(info->pads & BIT(pad))) + continue; + + if (!index) + return info; + + index--; + } + + return NULL; +} + +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; + + /* 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; + const struct c3_isp_rsz_format_info *rsz_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); + rsz_fmt = rsz_find_format_by_code(sink_fmt->code, C3_ISP_RSZ_PAD_SINK); + + if (rsz->id == C3_ISP_RSZ_0) { + mask = ISP_TOP_DISPIN_SEL_DISP0_MASK; + val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP0_MIPI_OUT + : ISP_TOP_DISPIN_SEL_DISP0_CORE_OUT; + } else if (rsz->id == C3_ISP_RSZ_1) { + mask = ISP_TOP_DISPIN_SEL_DISP1_MASK; + val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP1_MIPI_OUT + : ISP_TOP_DISPIN_SEL_DISP1_CORE_OUT; + } else { + mask = ISP_TOP_DISPIN_SEL_DISP2_MASK; + val = rsz_fmt->is_raw ? ISP_TOP_DISPIN_SEL_DISP2_MIPI_OUT + : ISP_TOP_DISPIN_SEL_DISP2_CORE_OUT; + } + + c3_isp_update_bits(rsz->isp, ISP_TOP_DISPIN_SEL, mask, val); + + 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); + + if (!rsz_fmt->is_raw) + 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 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); + + c3_isp_params_pre_cfg(rsz->isp); + c3_isp_stats_pre_cfg(rsz->isp); + + return v4l2_subdev_enable_streams(rsz->src_sd, rsz->src_pad, 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); + + return v4l2_subdev_disable_streams(rsz->src_sd, rsz->src_pad, 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) +{ + const struct c3_isp_rsz_format_info *info; + + info = rsz_find_format_by_index(code->index, code->pad); + if (!info) + return -EINVAL; + + code->code = info->mbus_code; + + 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; + const struct c3_isp_rsz_format_info *rsz_fmt; + + 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); + + rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad); + if (rsz_fmt) + sink_fmt->code = format->format.code; + else + sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT; + + 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->ycbcr_enc = V4L2_YCBCR_ENC_601; + + if (rsz_fmt && rsz_fmt->is_raw) { + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } else { + sink_fmt->colorspace = V4L2_COLORSPACE_SRGB; + sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + 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; + const struct c3_isp_rsz_format_info *rsz_fmt; + + 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->ycbcr_enc = V4L2_YCBCR_ENC_601; + + rsz_fmt = rsz_find_format_by_code(src_fmt->code, format->pad); + if (rsz_fmt->is_raw) { + src_fmt->colorspace = V4L2_COLORSPACE_RAW; + src_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + src_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } else { + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + 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; + rsz->src_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO_0 + i; + + 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 Thu Feb 27 07:27:21 2025 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: 870381 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 A5BB32236F7; Thu, 27 Feb 2025 07:27:18 +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=1740641238; cv=none; b=HBPB+MwUivyvgTBzK4KNK2KQxf6ZBAftdN8M5K+5at1AuKfvg2HRK6Diws21dSzrMfbnoNuLeK9WLqQf9NwCZYO/vSZbdjLqWu4FhsgOfvTxS01lF5lzpY88HmXs3DTUG2ehdRdGKPj7heIS6Zbrx7Wi4PKnWVsEAzRLG3icubc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740641238; c=relaxed/simple; bh=d32okfY48AdP5onR7d4GoJDusegmSjxz19/DzrOVxCw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MvJScmj9iO4boEKQTsK1dkrLIcrLNat+ROhEuI/vw1ETXWf++0y/uyYYpE1jA9i+y6JYm01B2gadeY+Xxsodnwq5KzEzWEBDN80Xsji+6BH+CwHgGHWS9tLiCxL8k/MxDXr0BlQ5BOIF5h3tw8Tl2vsCMFaqjJC+IKqvqQfppHQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ji+Ssq5R; 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="ji+Ssq5R" Received: by smtp.kernel.org (Postfix) with ESMTPS id 69E31C4CEFD; Thu, 27 Feb 2025 07:27:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1740641238; bh=d32okfY48AdP5onR7d4GoJDusegmSjxz19/DzrOVxCw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ji+Ssq5RG1sI+xITly+YirCC/yFW2M/5sHdYbOLJWUVLmk0IUFAuCgctFJAcnUjiV 04EowNoinWTt+r9bzTKW8DZWiV7DajRLTfQiu+nP/1yjrwPpUblGsPCqQ4Z/jEZTJg 09N1vjjDLE/irU+rv/HToYsqDhzjsWNsYGE0q4yVGMStH+f5BBi+KoIF6bV2TodEBW /ftxUnGfGXtFjB/xTItr996uvagwbtg5+74g8t5fFRSgiOcvlEkPlW8jCpCo0ouPkj FPwviVeuW7gGvv8ZNtFkPRS3KUx7efAstIKzjkq0AzUWzKimB9T0T8nLZpxrNgKh7o 7GKiVQg+HkrCA== 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 5F8BAC1B0FF; Thu, 27 Feb 2025 07:27:18 +0000 (UTC) From: Keke Li via B4 Relay Date: Thu, 27 Feb 2025 15:27:21 +0800 Subject: [PATCH v6 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: <20250227-c3isp-v6-10-f72e19084d0d@amlogic.com> References: <20250227-c3isp-v6-0-f72e19084d0d@amlogic.com> In-Reply-To: <20250227-c3isp-v6-0-f72e19084d0d@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=1740641235; l=8134; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=TjlqiBcQ0rs6eWQ9C0wNEtdDuotwwwwL0LT9Qczu0Yg=; b=hhqnSGAWvWp4O4AiVdKCE7AY5cKTTVs/ZO572LL8JxgI8wNCr4TCZIIKpX+XHau9nEcZFEKqu Pxy+RzpIEESA8vU/sKXdy5VftRu20PTjzS6FXlkLFX6S826U6IjApSJ 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..42dc931ee84a --- /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 | 4 | 5}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port3 -> n00000008:port0 + n00000001:port4 -> n0000000b:port0 + n00000001:port5 -> n0000000e:port0 + n00000001:port2 -> n00000027 + n00000008 [label="{{ 0} | c3-isp-resizer0\n/dev/v4l-subdev1 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000008:port1 -> n00000016 [style=bold] + n0000000b [label="{{ 0} | c3-isp-resizer1\n/dev/v4l-subdev2 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000b:port1 -> n0000001a [style=bold] + n0000000e [label="{{ 0} | c3-isp-resizer2\n/dev/v4l-subdev3 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000e:port1 -> n00000023 [style=bold] + n00000011 [label="{{ 0} | c3-mipi-adapter\n/dev/v4l-subdev4 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000011:port1 -> n00000001:port0 [style=bold] + n00000016 [label="c3-isp-cap0\n/dev/video0", shape=box, style=filled, fillcolor=yellow] + n0000001a [label="c3-isp-cap1\n/dev/video1", shape=box, style=filled, fillcolor=yellow] + n0000001e [label="{{ 0} | c3-mipi-csi2\n/dev/v4l-subdev5 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000001e:port1 -> n00000011:port0 [style=bold] + n00000023 [label="c3-isp-cap2\n/dev/video2", shape=box, style=filled, fillcolor=yellow] + n00000027 [label="c3-isp-stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow] + n0000002b [label="c3-isp-params\n/dev/video4", shape=box, style=filled, fillcolor=yellow] + n0000002b -> n00000001:port1 + n0000003f [label="{{} | imx290 2-001a\n/dev/v4l-subdev6 | { 0}}", shape=Mrecord, style=filled, fillcolor=green] + n0000003f:port0 -> n0000001e: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 b277304ff3e8..35af62270fcb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1256,6 +1256,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/