From patchwork Wed Sep 18 06:07:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806580 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 663291487C8; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=kBrIQOCNG4H6CFOLVmQi70yW4YRdXmhERY18amqbJKPW9xR97FuqtgoyFo9AI4CJrNPb9WTeVK9cjbw+ctosuzkI/Tkzwd0mWmp4E6K7CyC5Agmmi3V+QVCvTAxiArfzfU8h4Ko60wA76w8XfuKyVPtiehWNBXrKPhCDXAmDHQI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=S7H8RteIZLQMtGDDVuduQq47vIng75mdmxX/0yNcgZA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GGXaDX82Xw2FU64IAxPWnMZCWmlQw10UJQ0XSVveCOQqFAg+1Wa55151Tc8ssa2JP7g7FxSSQyKjmLR7cxMKTmEYBdeMEe26upPcRsetjvJY0/5ldY83/Q17V86RDEBEWcaZd/VOOx2Hnh0fC5rGt/COLtWMAna8UERS0Wbait8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Naa6iSto; 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="Naa6iSto" Received: by smtp.kernel.org (Postfix) with ESMTPS id 08FA7C4CECE; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=S7H8RteIZLQMtGDDVuduQq47vIng75mdmxX/0yNcgZA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Naa6iStowYIEXclhPhmKll1v8awfT8EYhZp1+h+949KMRI54NW3QzDzvEdtEpPhzu DG96fJb6HqHZXfK4d4E3OyMGRHNHZii/T+yAOuD/oPJz+olWHsF5mTrhBWx22hwl9e OJYJ+KBr574hJ2QnW7GlpAjBug735eV7aKwbZFCJ+HpGoRaQwf+DUjRi86zQpm8kAw 5NFVmP3eacKldAKy6rm8LYE00cf4W0YOZTENRGHSYdSIVYemSlEpMjvQSj5oFxqZFs jMetaPKX/K2fdGPYN8cFwOXOJs2TdqztH3cCaIg6rNS3iTUdo2fzTjLI+UPlzfe+U0 X0UQmNPuGCbXg== 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 DF854C35FE7; Wed, 18 Sep 2024 06:07:35 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:12 +0800 Subject: [PATCH v3 1/9] 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: <20240918-c3isp-v3-1-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li , Krzysztof Kozlowski X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=3682; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=iPGUT9TFuGPln+AVXqn/S4ILdZCHK59x4w+NVJkmgHQ=; b=/LTKu2iVPuQ7csNYYJYeKk5R/LGoTpbv3AxrJKpwcUfsRxDVzkEotR/CRGMHITED3M9G2QRPh +mjp+0faCrWBQbQMhCFYOmCrlOoTB+KtQL/eucYwS7iHTIx6dolwU1Q X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li c3-mipi-csi2 is used to receive mipi data from image sensor. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Keke Li --- .../bindings/media/amlogic,c3-mipi-csi2.yaml | 124 +++++++++++++++++++++ 1 file changed, 124 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..5cea06595205 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-csi2.yaml @@ -0,0 +1,124 @@ +# 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"; + + 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>; + }; + }; + }; + }; + }; +... From patchwork Wed Sep 18 06:07:13 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806581 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 662A0139CF2; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=dhx7CbDDdrckjaPBxgDcIDTpE5lGUTS5G02wrTzleeOrcPCtrx7oVuAbkKS0lpxXsvlyA7ry6BtDPx+gCyhPmiN5JJjZiMzenDWXEeGJymeQparZmfPaoBAlILs/3ROS2ROFEbnRADt8lftfugDr+OVbXit46RxIVIA5Yz4bb2I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=5G3tFYa+6MIeIeexdomWf1dX4L71+IKzbJiB94oHHJQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=rzY9wvYJOddtxofM3IHrNOHas9CncbE5Fuzi35I/+4Yr0bnpQgMzWbI2I+5k0NVolUi3m0Hx/G/4J+BpyWfrKe+Wzz+dOofgFWn8CCFX86dSmbFhadRCot56yoh5T7my1E2gSU5VmsPekhynHVn8qn2G3lN0uDvP5UYxVe1tJE4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=jy1nGEA3; 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="jy1nGEA3" Received: by smtp.kernel.org (Postfix) with ESMTPS id 118BBC4CECF; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=5G3tFYa+6MIeIeexdomWf1dX4L71+IKzbJiB94oHHJQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=jy1nGEA3CgnZHjf+zrQhG+IHHW2zpmuC8sbkySxa6CT6pBDUS7pi5d1Tuca/AbJk3 Gs+454ZQ6d4A0JqAfVP7EnLWOGJ/TkBZCG4LeF2v+wEdE2zgqnIHEgC2wJsNgHBurB OAEtwz/bu9AQmxd8Atvs6DvKzCranueWyJIBYXn+4ONeRcy28NK60cwX2mofKrSGln kv2jcpwLdRKgr9qUl+J7EevZEdzOXLq+5kCY91Dg+hbyMHMAjgjCbjpbQ9TKnKGWAw AR3h4SCeIpW3b2o8/2X85XgCDwaZrH0zyhqcRBTcO7fpGziJnzdQZ3IUm4td2YZZv7 08Q1MhDGXMlOA== 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 017F7C36015; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:13 +0800 Subject: [PATCH v3 2/9] media: platform: Add c3 mipi csi2 driver Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240918-c3isp-v3-2-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=31281; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=Rkpt9jYclZTOnqLyCUkuArwhELTibph441BaW8wpR0A=; b=Owwx+B+BcPgrxFYHrijSRDRzPh1MWF/2o4ihgvEcSbmK4uSoaLbi/M74HrhBJtzfuKqEkZKtB VGAZeboHfroCHZqOIXDVvbE0hA7p41RBQmmO2sI0wg4SrtLLEtm1LFJ 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 This driver is used to receive mipi data from image sensor. Reviewed-by: Daniel Scally Signed-off-by: Keke Li --- MAINTAINERS | 7 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 2 + .../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 | 910 +++++++++++++++++++++ 6 files changed, 939 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2cdd7cacec86..9e75874a6e69 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1209,6 +1209,13 @@ 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 +F: drivers/media/platform/amlogic/c3-mipi-csi2/ + AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER M: Javier Carrasco L: linux-hwmon@vger.kernel.org diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index 5014957404e9..b7c2de14848b 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-mipi-csi2/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..4f571ce5d13e 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-mipi-csi2/ obj-y += meson-ge2d/ 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..6ac60d5b26a8 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-mipi-csi2/c3-mipi-csi2.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* 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 "mipi-csi2" + +/* C3 CSI-2 APHY register */ +#define MIPI_CSI_2M_PHY2_CNTL1 CSI2_REG_A(0x44) +#define MIPI_APHY_NORMAL_CNTL1 0x3f425C00 + +#define MIPI_CSI_2M_PHY2_CNTL2 CSI2_REG_A(0x48) +#define MIPI_APHY_4LANES_CNTL2 0x033a0000 +#define MIPI_APHY_NORMAL_CNTL2 0x333a0000 + +#define MIPI_CSI_2M_PHY2_CNTL3 CSI2_REG_A(0x4c) +#define MIPI_APHY_2LANES_CNTL3 0x03800000 + +/* C3 CSI-2 DPHY register */ +#define MIPI_PHY_CTRL CSI2_REG_D(0x00) +#define MIPI_DPHY_LANES_ENABLE 0x0 + +#define MIPI_PHY_CLK_LANE_CTRL CSI2_REG_D(0x04) +#define MIPI_DPHY_CLK_CONTINUE_MODE 0x3d8 + +#define MIPI_PHY_DATA_LANE_CTRL CSI2_REG_D(0x08) +#define MIPI_DPHY_LANE_CTRL_DISABLE 0x0 + +#define MIPI_PHY_DATA_LANE_CTRL1 CSI2_REG_D(0x0c) +#define MIPI_DPHY_INSERT_ERRESC BIT(0) +#define MIPI_DPHY_HS_SYNC_CHECK BIT(1) +#define MIPI_DPHY_FIVE_HS_PIPE GENMASK(6, 2) +#define MIPI_DPHY_FIVE_HS_PIPE_SHIFT 2 +#define MIPI_DPHY_DATA_PIPE_SELECT GENMASK(9, 7) +#define MIPI_DPHY_DATA_PIPE_SELECT_SHIFT 7 + +#define MIPI_PHY_TCLK_MISS CSI2_REG_D(0x10) +#define MIPI_DPHY_CLK_MISS 0x9 + +#define MIPI_PHY_TCLK_SETTLE CSI2_REG_D(0x14) +#define MIPI_DPHY_CLK_SETTLE 0x1F + +#define MIPI_PHY_THS_EXIT CSI2_REG_D(0x18) +#define MIPI_DPHY_HS_EXIT 0x8 + +#define MIPI_PHY_THS_SKIP CSI2_REG_D(0x1c) +#define MIPI_DPHY_HS_SKIP 0xa + +#define MIPI_PHY_THS_SETTLE CSI2_REG_D(0x20) +#define MIPI_PHY_TINIT CSI2_REG_D(0x24) +#define MIPI_DPHY_INIT_CYCLES 0x4e20 + +#define MIPI_PHY_TULPS_C CSI2_REG_D(0x28) +#define MIPI_DPHY_ULPS_CHECK_CYCLES 0x1000 + +#define MIPI_PHY_TULPS_S CSI2_REG_D(0x2c) +#define MIPI_DPHY_ULPS_START_CYCLES 0x100 + +#define MIPI_PHY_TMBIAS CSI2_REG_D(0x30) +#define MIPI_DPHY_MBIAS_CYCLES 0x100 + +#define MIPI_PHY_TLP_EN_W CSI2_REG_D(0x34) +#define MIPI_DPHY_ULPS_STOP_CYCLES 0xC + +#define MIPI_PHY_TLPOK CSI2_REG_D(0x38) +#define MIPI_DPHY_POWER_UP_CYCLES 0x100 + +#define MIPI_PHY_TWD_INIT CSI2_REG_D(0x3c) +#define MIPI_DPHY_INIT_WATCH_DOG 0x400000 + +#define MIPI_PHY_TWD_HS CSI2_REG_D(0x40) +#define MIPI_DPHY_HS_WATCH_DOG 0x400000 + +#define MIPI_PHY_MUX_CTRL0 CSI2_REG_D(0x284) +#define MIPI_DPHY_LANE3_SELECT GENMASK(3, 0) +#define MIPI_DPHY_LANE2_SELECT GENMASK(7, 4) +#define MIPI_DPHY_LANE2_SELECT_SHIFT 4 +#define MIPI_DPHY_LANE1_SELECT GENMASK(11, 8) +#define MIPI_DPHY_LANE1_SELECT_SHIFT 8 +#define MIPI_DPHY_LANE0_SELECT GENMASK(14, 12) + +#define MIPI_PHY_MUX_CTRL1 CSI2_REG_D(0x288) +#define MIPI_DPHY_LANE3_CTRL_SIGNAL GENMASK(3, 0) +#define MIPI_DPHY_LANE2_CTRL_SIGNAL GENMASK(7, 4) +#define MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT 4 +#define MIPI_DPHY_LANE1_CTRL_SIGNAL GENMASK(11, 8) +#define MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT 8 +#define MIPI_DPHY_LANE0_CTRL_SIGNAL GENMASK(14, 12) +#define MIPI_DPHY_CLK_SELECT BIT(17) + +/* C3 CSI-2 HOST register */ +#define CSI2_HOST_N_LANES CSI2_REG_H(0x04) +#define CSI2_HOST_CSI2_RESETN CSI2_REG_H(0x10) +#define CSI2_HOST_RESETN_DEFAULT 0x0 +#define CSI2_HOST_RESETN_RST_VALUE BIT(0) + +#define CSI2_HOST_MASK1 CSI2_REG_H(0x28) +#define CSI2_HOST_ERROR_MASK1 GENMASK(28, 0) + +#define MIPI_CSI2_MAX_WIDTH 2888 +#define MIPI_CSI2_MIN_WIDTH 160 +#define MIPI_CSI2_MAX_HEIGHT 2240 +#define MIPI_CSI2_MIN_HEIGHT 120 +#define MIPI_CSI2_DEFAULT_WIDTH 1920 +#define MIPI_CSI2_DEFAULT_HEIGHT 1080 +#define MIPI_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_SRGGB10_1X10 + +/* C3 CSI-2 pad list */ +enum { + MIPI_CSI2_PAD_SINK, + MIPI_CSI2_PAD_SRC, + MIPI_CSI2_PAD_MAX +}; + +/** + * struct csi_info - MIPI CSI2 information + * + * @clocks: array of MIPI CSI2 clock names + * @clock_rates: array of MIPI CSI2 clock rate + * @clock_num: actual clock number + */ +struct csi_info { + char *clocks[MIPI_CSI2_CLOCK_NUM_MAX]; + u32 clock_rates[MIPI_CSI2_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/** + * struct 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_sd: source sub-device + * @bus: MIPI CSI2 bus information + * @src_sd_pad: source sub-device pad + * @lock: protect MIPI CSI2 device + * @info: version-specific MIPI CSI2 information + */ +struct 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[MIPI_CSI2_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + struct v4l2_mbus_config_mipi_csi2 bus; + + u16 src_sd_pad; + struct mutex lock; /* Protect csi device */ + const struct 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 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_update_bits(struct csi_device *csi, u32 reg, + u32 mask, u32 val) +{ + void __iomem *addr; + u32 orig, tmp; + + 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; + } + + orig = readl(addr); + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + writel(tmp, addr); +} + +static void c3_mipi_csi_cfg_aphy(struct csi_device *csi, u32 lanes) +{ + c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL1, MIPI_APHY_NORMAL_CNTL1); + + if (lanes == 4) + c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_4LANES_CNTL2); + else + c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL2, MIPI_APHY_NORMAL_CNTL2); + + if (lanes == 2) + c3_mipi_csi_write(csi, MIPI_CSI_2M_PHY2_CNTL3, MIPI_APHY_2LANES_CNTL3); +} + +static void c3_mipi_csi_2lanes_setting(struct csi_device *csi) +{ + /* Disable lane 2 and lane 3 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0xf); + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT, + 0xf << MIPI_DPHY_LANE2_SELECT_SHIFT); + /* Select analog data lane 1 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT, + 0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT); + /* Select analog data lane 0 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0); + + /* Disable lane 2 and lane 3 control signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0xf); + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL, + 0xf << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT); + /* Select lane 1 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL, + 0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT); + /* Select lane 0 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0); + /* Select input 0 as clock */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT, + MIPI_DPHY_CLK_SELECT); +} + +static void c3_mipi_csi_4lanes_setting(struct csi_device *csi) +{ + /* Select analog data lane 3 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE3_SELECT, 0x3); + /* Select analog data lane 2 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE2_SELECT, + 0x2 << MIPI_DPHY_LANE2_SELECT_SHIFT); + /* Select analog data lane 1 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE1_SELECT, + 0x1 << MIPI_DPHY_LANE1_SELECT_SHIFT); + /* Select analog data lane 0 */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL0, MIPI_DPHY_LANE0_SELECT, 0x0); + + /* Select lane 3 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE3_CTRL_SIGNAL, 0x3); + /* Select lane 2 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE2_CTRL_SIGNAL, + 0x2 << MIPI_DPHY_LANE2_CTRL_SIGNAL_SHIFT); + /* Select lane 1 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE1_CTRL_SIGNAL, + 0x1 << MIPI_DPHY_LANE1_CTRL_SIGNAL_SHIFT); + /* Select lane 0 signal */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_LANE0_CTRL_SIGNAL, 0x0); + /* Select input 0 as clock */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_MUX_CTRL1, MIPI_DPHY_CLK_SELECT, + MIPI_DPHY_CLK_SELECT); +} + +static void c3_mipi_csi_cfg_dphy(struct csi_device *csi, u32 lanes, 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_DPHY_CLK_CONTINUE_MODE); + c3_mipi_csi_write(csi, MIPI_PHY_TCLK_MISS, MIPI_DPHY_CLK_MISS); + c3_mipi_csi_write(csi, MIPI_PHY_TCLK_SETTLE, MIPI_DPHY_CLK_SETTLE); + c3_mipi_csi_write(csi, MIPI_PHY_THS_EXIT, MIPI_DPHY_HS_EXIT); + c3_mipi_csi_write(csi, MIPI_PHY_THS_SKIP, MIPI_DPHY_HS_SKIP); + c3_mipi_csi_write(csi, MIPI_PHY_THS_SETTLE, settle); + c3_mipi_csi_write(csi, MIPI_PHY_TINIT, MIPI_DPHY_INIT_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TMBIAS, MIPI_DPHY_MBIAS_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TULPS_C, MIPI_DPHY_ULPS_CHECK_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TULPS_S, MIPI_DPHY_ULPS_START_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TLP_EN_W, MIPI_DPHY_ULPS_STOP_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TLPOK, MIPI_DPHY_POWER_UP_CYCLES); + c3_mipi_csi_write(csi, MIPI_PHY_TWD_INIT, MIPI_DPHY_INIT_WATCH_DOG); + c3_mipi_csi_write(csi, MIPI_PHY_TWD_HS, MIPI_DPHY_HS_WATCH_DOG); + c3_mipi_csi_write(csi, MIPI_PHY_DATA_LANE_CTRL, MIPI_DPHY_LANE_CTRL_DISABLE); + + c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_INSERT_ERRESC, + MIPI_DPHY_INSERT_ERRESC); + c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_HS_SYNC_CHECK, + MIPI_DPHY_HS_SYNC_CHECK); + /* + * Set 5 pipe lines to the same high speed. + * Each bit for one pipe line. + */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_FIVE_HS_PIPE, + 0x1f << MIPI_DPHY_FIVE_HS_PIPE_SHIFT); + + /* Output data with pipe line data. */ + c3_mipi_csi_update_bits(csi, MIPI_PHY_DATA_LANE_CTRL1, MIPI_DPHY_DATA_PIPE_SELECT, + 0x3 << MIPI_DPHY_DATA_PIPE_SELECT_SHIFT); + if (lanes == 2) + c3_mipi_csi_2lanes_setting(csi); + else + c3_mipi_csi_4lanes_setting(csi); + + /* Enable digital data and clock lanes */ + c3_mipi_csi_write(csi, MIPI_PHY_CTRL, MIPI_DPHY_LANES_ENABLE); +} + +static void c3_mipi_csi_cfg_host(struct csi_device *csi, u32 lanes) +{ + /* Reset CSI-2 controller output */ + c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_DEFAULT); + c3_mipi_csi_write(csi, CSI2_HOST_CSI2_RESETN, CSI2_HOST_RESETN_RST_VALUE); + + /* Set data lane number */ + c3_mipi_csi_write(csi, CSI2_HOST_N_LANES, lanes - 1); + + /* Enable error mask */ + c3_mipi_csi_write(csi, CSI2_HOST_MASK1, CSI2_HOST_ERROR_MASK1); +} + +static int c3_mipi_csi_start_stream(struct csi_device *csi) +{ + s64 link_freq; + s64 lane_rate; + + link_freq = v4l2_get_link_freq(csi->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) + return -EINVAL; + + c3_mipi_csi_cfg_aphy(csi, csi->bus.num_data_lanes); + c3_mipi_csi_cfg_dphy(csi, csi->bus.num_data_lanes, lane_rate); + c3_mipi_csi_cfg_host(csi, csi->bus.num_data_lanes); + + return 0; +} + +static int c3_mipi_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct csi_device *csi = v4l2_get_subdevdata(sd); + u64 sink_streams; + int ret; + + guard(mutex)(&csi->lock); + + pm_runtime_resume_and_get(csi->dev); + + c3_mipi_csi_start_stream(csi); + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + MIPI_CSI2_PAD_SINK, + &streams_mask); + ret = v4l2_subdev_enable_streams(csi->src_sd, + csi->src_sd_pad, + sink_streams); + 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 csi_device *csi = v4l2_get_subdevdata(sd); + u64 sink_streams; + int ret; + + guard(mutex)(&csi->lock); + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + MIPI_CSI2_PAD_SINK, + &streams_mask); + ret = v4l2_subdev_disable_streams(csi->src_sd, + csi->src_sd_pad, + sink_streams); + if (ret) + dev_err(csi->dev, "Failed to disable %s\n", csi->src_sd->name); + + pm_runtime_put(csi->dev); + + return ret; +} + +static int c3_mipi_csi_cfg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = MIPI_CSI2_DEFAULT_WIDTH, + .height = MIPI_CSI2_DEFAULT_HEIGHT, + .code = MIPI_CSI2_DEFAULT_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_NONE, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int c3_mipi_csi_init_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes; + struct v4l2_subdev_krouting routing; + + routes.sink_pad = MIPI_CSI2_PAD_SINK; + routes.sink_stream = 0; + routes.source_pad = MIPI_CSI2_PAD_SRC; + routes.source_stream = 0; + routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routing.num_routes = 1; + routing.routes = &routes; + + return c3_mipi_csi_cfg_routing(sd, state, &routing); +} + +static int c3_mipi_csi_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + bool is_streaming = v4l2_subdev_is_streaming(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming) + return -EBUSY; + + return c3_mipi_csi_cfg_routing(sd, state, routing); +} + +static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case 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 MIPI_CSI2_PAD_SRC: + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + 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 != 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]) + break; + + if (i == ARRAY_SIZE(c3_mipi_csi_formats)) + fmt->code = c3_mipi_csi_formats[0]; + else + fmt->code = c3_mipi_csi_formats[i]; + + fmt->width = clamp_t(u32, format->format.width, + MIPI_CSI2_MIN_WIDTH, MIPI_CSI2_MAX_WIDTH); + fmt->height = clamp_t(u32, format->format.height, + MIPI_CSI2_MIN_HEIGHT, MIPI_CSI2_MAX_HEIGHT); + + format->format = *fmt; + + /* Synchronize the format to source pad */ + fmt = v4l2_subdev_state_get_format(state, 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, MIPI_CSI2_PAD_SINK); + src_fmt = v4l2_subdev_state_get_format(state, MIPI_CSI2_PAD_SRC); + + sink_fmt->width = MIPI_CSI2_DEFAULT_WIDTH; + sink_fmt->height = MIPI_CSI2_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MIPI_CSI2_DEFAULT_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + sink_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + *src_fmt = *sink_fmt; + + return c3_mipi_csi_init_routing(sd, state); +} + +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, + .set_routing = c3_mipi_csi_set_routing, + .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 __maybe_unused c3_mipi_csi_runtime_suspend(struct device *dev) +{ + struct csi_device *csi = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(csi->info->clock_num, csi->clks); + + return 0; +} + +static int __maybe_unused c3_mipi_csi_runtime_resume(struct device *dev) +{ + struct 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 = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_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 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[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + csi->pads[MIPI_CSI2_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 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 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 csi_device *csi = v4l2_get_subdevdata(notifier->sd); + struct media_pad *sink = &csi->sd.entity.pads[MIPI_CSI2_PAD_SINK]; + int ret; + + ret = media_entity_get_fwnode_pad(&sd->entity, + sd->fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(csi->dev, "Failed to find pad for %s\n", sd->name); + return ret; + } + + csi->src_sd = sd; + csi->src_sd_pad = ret; + + 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 csi_device *csi) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + int ret; + + v4l2_async_subdev_nf_init(&csi->notifier, &csi->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_put_handle; + + csi->bus = vep.bus.mipi_csi2; + if (csi->bus.num_data_lanes != 2 && csi->bus.num_data_lanes != 4) + goto err_put_handle; + + asc = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_put_handle; + } + + csi->notifier.ops = &c3_mipi_csi_notify_ops; + ret = v4l2_async_nf_register(&csi->notifier); + if (ret) + goto err_cleanup_nf; + + ret = v4l2_async_register_subdev(&csi->sd); + if (ret) + goto err_unregister_nf; + + fwnode_handle_put(ep); + + return 0; + +err_unregister_nf: + v4l2_async_nf_unregister(&csi->notifier); +err_cleanup_nf: + v4l2_async_nf_cleanup(&csi->notifier); +err_put_handle: + fwnode_handle_put(ep); + return ret; +} + +static void c3_mipi_csi_async_unregister(struct 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 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_configure_clocks(struct csi_device *csi) +{ + const struct csi_info *info = csi->info; + int ret; + u32 i; + + for (i = 0; i < info->clock_num; i++) + csi->clks[i].id = info->clocks[i]; + + ret = devm_clk_bulk_get(csi->dev, info->clock_num, csi->clks); + if (ret) + return ret; + + for (i = 0; i < info->clock_num; i++) { + if (!info->clock_rates[i]) + continue; + ret = clk_set_rate(csi->clks[i].clk, info->clock_rates[i]); + if (ret) { + dev_err(csi->dev, "Failed to set %s rate %u\n", info->clocks[i], + info->clock_rates[i]); + return ret; + } + } + + return 0; +} + +static int c3_mipi_csi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct 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_configure_clocks(csi); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure clocks\n"); + + platform_set_drvdata(pdev, csi); + + mutex_init(&csi->lock); + 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); + mutex_destroy(&csi->lock); + return ret; +}; + +static void c3_mipi_csi_remove(struct platform_device *pdev) +{ + struct csi_device *csi = platform_get_drvdata(pdev); + + c3_mipi_csi_async_unregister(csi); + c3_mipi_csi_subdev_deinit(csi); + + pm_runtime_disable(&pdev->dev); + mutex_destroy(&csi->lock); +}; + +static const struct csi_info c3_mipi_csi_info = { + .clocks = {"vapb", "phy0"}, + .clock_rates = {0, 200000000}, + .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 = &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 Wed Sep 18 06:07:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806583 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 861F214A0B8; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=u4lEWaM6gzgbSbaT4iV7KBUPCbcA/DQQCtOyzN1rBbhxmq6EeZ/vX2XozZFi/Ek8jwTy214FDxTLKHYGZerWyJcURWQV+unGkh+mTbNQZzk1VP5uZdSmLPLK+8AkiOzrz43WNe5Rfv9LvrMOV+x0IMT1/luMAXHwDgakw10B3no= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=taiPwo/5rp14b+pe9AH9/ntG2kGPWEXKzxmnSHfAD10=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PI4tUPJST5k/MiaWOAXP6QYOh1CwVOdQDtKtG1MQOGw9LEhnYwygwjTVfhUOyzghTfxZ2k8iL2EPTpbvYEsYifmRj+I8FN2JEsfr1wsqoe8EFU/5NEVXk4FVf8EDp9o349ZJ7JCFcigU5gu+a0WApZhrAfDB8pOldOIl1hTOhGo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=KoON7lRh; 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="KoON7lRh" Received: by smtp.kernel.org (Postfix) with ESMTPS id 26959C4CECD; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=taiPwo/5rp14b+pe9AH9/ntG2kGPWEXKzxmnSHfAD10=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=KoON7lRh/WcLVzkQX+X+vSpQyGGvvfusyHMjjiLKPAoawXrFFwSLXJrj6/mdt0hqw WOuaouw+91SHUjJb1uRqWVLo58MB8y8bgx9ViR3j3unaYOg3IvSMa7Gvd56iDIRdsO YU8FqcgzAtK9JacFwLLJk9Yle58gdqlINT+KYVFlrY1+A6MsmjzPh77OK/CM+o6Lqc gnWkTyKwfXpTwKfzDIvA3nCWLRoJUjxn2I7ypWXTDGefReSPcPEUNlhweauUtmYGgy EdweEw6bMIRoUtl8uZAefM5ynSDD0C+ebo8e/N1uxj6oBEF+TZAPrEMLEn6kF+zvjT 9vNudJvpGlt1w== 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 1A3AAC3600F; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:14 +0800 Subject: [PATCH v3 3/9] dt-bindings: media: Add amlogic,c3-mipi-adapter.yaml Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240918-c3isp-v3-3-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li , Krzysztof Kozlowski X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=3217; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=M15y/KY2ciR/aOCQ6H8VpsEywY52oYEbCP67gjniwV8=; b=Sd7to8PxEgmFhTyjyyxPG8fpsz5H7faHdofkN/6d2iFgI3DRWZ1Q5UBUccqptRnLGal6Rm8es RAuw2lGK8t8AGRwLiG5YEejWaNYdEB12fCTzUhV1tOIO31tBNO+Gbp8 X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li c3-mipi-adapter is used to organize mipi data and send raw data to ISP module. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Keke Li --- .../bindings/media/amlogic,c3-mipi-adapter.yaml | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml new file mode 100644 index 000000000000..d900ff9df61b --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,c3-mipi-adapter.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic C3 MIPI adapter receiver + +maintainers: + - Keke Li + +description: + MIPI adapter is used to convert the MIPI CSI-2 data + into an ISP supported data format. + +properties: + compatible: + enum: + - amlogic,c3-mipi-adapter + + reg: + maxItems: 3 + + reg-names: + items: + - const: top + - const: fd + - const: rd + + power-domains: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: vapb + - const: isp0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: input port node. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: output port node. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reg-names + - power-domains + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + adap: adap@ff010000 { + compatible = "amlogic,c3-mipi-adapter"; + reg = <0x0 0xff010000 0x0 0x100>, + <0x0 0xff01b000 0x0 0x100>, + <0x0 0xff01d000 0x0 0x200>; + reg-names = "top", "fd", "rd"; + power-domains = <&pwrc PWRC_C3_ISP_TOP_ID>; + clocks = <&clkc_periphs CLKID_VAPB>, + <&clkc_periphs CLKID_ISP0>; + clock-names = "vapb", "isp0"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + c3_adap_in: endpoint { + remote-endpoint = <&c3_mipi_csi_out>; + }; + }; + + port@1 { + reg = <1>; + c3_adap_out: endpoint { + remote-endpoint = <&c3_isp_in>; + }; + }; + }; + }; + }; +... From patchwork Wed Sep 18 06:07:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806584 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 8619F14A086; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=hwjoju8x0bsqKch7+SXiM4Xia87Jtw64NFAzFwMFxALOx1lOeov+zDZoWvHq0DLlgMUB2MmBUMVqxPOs+tKmfM7Ci0T1IodXUqAtghKxjhC3Wa37Fd6HPiFlqR06EyJ10f7Hggn1z85CL/om7WQlbmnb8r6P5xaEIIS+3AkU6zE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=U9xfY+pHgxJ/pW9ev6ZTZoVWdrYt0qhm9U/a+dFuQJk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Uti2iCn6tNGzOEsZY1IqbFG+l+sHwNSUHTL1kLrG+TYyBo4MWKDYes6fGlR1IsYDaEd8rAXJGOozbZvnfsZqdxs567u1bNJqBjSF2lW6OtF1CzQCO/jL7hharCzF4sR2GknfpG23ofjzWNmZXz+fgShlWQFKFaLCqkKdMJX/6tU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=HoEvptY8; 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="HoEvptY8" Received: by smtp.kernel.org (Postfix) with ESMTPS id 3B679C4CED6; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=U9xfY+pHgxJ/pW9ev6ZTZoVWdrYt0qhm9U/a+dFuQJk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=HoEvptY86ol38C41C43Y5iIJTCKpigY6aScarQDf+ol2o4+ezm0lhT19BHBK5AyUu UVn2fKrtRHqSHS5cj5U4fInb5YgsqDfRK/4ieSYMkxSqnplZZdp7oshnVKpoRd1L/o F8PxbaNdd81OKc1uP/nRngSqQG2noPZ5v7/KFMk9TEbE70QrNLeS0LyLra+Ov5vJrt eaa4Gwyb97mLZJe+L5t4vDnCB12ySmc9LMOGJGgHIvXgBjXweBFWV0eRE+FehPlIoD eWZjILPc6qMxn/BQ4CSaJ0EWtoiRyiHqXgfwibeMbbTMM3vJt2BPXna+EQjYZkj2Gm wiYb70dHDSWkA== 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 31477C35FE7; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:15 +0800 Subject: [PATCH v3 4/9] media: platform: Add c3 mipi adapter driver Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240918-c3isp-v3-4-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=32423; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=OXjklpUIzAgm4+5R6wH+/u1X2MNgMrvWThvy1pQ4wuA=; b=MVhhMzf7m45UIPRENWbPZqNb5Si0SE5kqLcwipH+vCDFUdqn7HZGOZ5ulJjr5U5zjs9zILmRg 7Qx2ejTj6BkCEAHlVACLFeKhcK/zYoHwUMynLfDOptpaTVzfwqHRNjv 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 This driver mainly responsible for organizing MIPI data and sending raw data to ISP pipeline. Reviewed-by: Daniel Scally Signed-off-by: Keke Li --- MAINTAINERS | 7 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + .../media/platform/amlogic/c3-mipi-adapter/Kconfig | 16 + .../platform/amlogic/c3-mipi-adapter/Makefile | 3 + .../amlogic/c3-mipi-adapter/c3-mipi-adap.c | 913 +++++++++++++++++++++ 6 files changed, 941 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9e75874a6e69..31168c05f304 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1209,6 +1209,13 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC MIPI ADAPTER DRIVER +M: Keke Li +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,c3-mipi-adapter.yaml +F: drivers/media/platform/amlogic/c3-mipi-adapter/ + AMLOGIC MIPI CSI2 DRIVER M: Keke Li L: linux-media@vger.kernel.org diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index b7c2de14848b..df09717b28d0 100644 --- a/drivers/media/platform/amlogic/Kconfig +++ b/drivers/media/platform/amlogic/Kconfig @@ -2,5 +2,6 @@ comment "Amlogic media platform drivers" +source "drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig" source "drivers/media/platform/amlogic/c3-mipi-csi2/Kconfig" source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile index 4f571ce5d13e..b370154b090c 100644 --- a/drivers/media/platform/amlogic/Makefile +++ b/drivers/media/platform/amlogic/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-y += c3-mipi-adapter/ obj-y += c3-mipi-csi2/ obj-y += meson-ge2d/ diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig new file mode 100644 index 000000000000..bf19059b3543 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_C3_MIPI_ADAPTER + tristate "Amlogic C3 MIPI adapter" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on OF + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 driver for Amlogic C3 MIPI adapter. + C3 MIPI adapter mainly responsible for organizing + MIPI data and sending raw data to ISP pipeline. + + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile new file mode 100644 index 000000000000..216fc310c5b4 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_VIDEO_C3_MIPI_ADAPTER) += c3-mipi-adap.o diff --git a/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c new file mode 100644 index 000000000000..b64eb417c2e2 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-mipi-adapter/c3-mipi-adap.c @@ -0,0 +1,913 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* C3 adapter submodule definition */ +enum { + SUBMD_TOP, + SUBMD_FD, + SUBMD_RD, +}; + +#define ADAP_SUBMD_MASK GENMASK(17, 16) +#define ADAP_SUBMD_SHIFT 16 +#define ADAP_SUBMD(x) (((x) & (ADAP_SUBMD_MASK)) >> (ADAP_SUBMD_SHIFT)) +#define ADAP_REG_ADDR_MASK GENMASK(15, 0) +#define ADAP_REG_ADDR(x) ((x) & (ADAP_REG_ADDR_MASK)) +#define ADAP_REG_T(x) ((SUBMD_TOP << ADAP_SUBMD_SHIFT) | (x)) +#define ADAP_REG_F(x) ((SUBMD_FD << ADAP_SUBMD_SHIFT) | (x)) +#define ADAP_REG_R(x) ((SUBMD_RD << ADAP_SUBMD_SHIFT) | (x)) + +#define MIPI_ADAP_CLOCK_NUM_MAX 3 +#define MIPI_ADAP_SUBDEV_NAME "mipi-adapter" + +/* C3 MIPI adapter TOP register */ +#define MIPI_ADAPT_DE_CTRL0 ADAP_REG_T(0x40) +#define ADAP_DE_READ_BYPASS BIT(3) +#define ADAP_DE_WRITE_BYPASS BIT(7) + +/* C3 MIPI adapter FRONTEND register */ +#define CSI2_CLK_RESET ADAP_REG_F(0x00) +#define ADAP_FD_APPLY_RESET BIT(0) +#define ADAP_FD_ENABLE BIT(1) + +#define CSI2_GEN_CTRL0 ADAP_REG_F(0x04) +#define ADAP_FD_VIRTUAL_CHN0_EN BIT(0) +#define ADAP_FD_VIRTUAL_CHN1_EN BIT(1) +#define ADAP_FD_VIRTUAL_CHN2_EN BIT(2) +#define ADAP_FD_VIRTUAL_CHN3_EN BIT(3) +#define ADAP_FD_ENABLE_PACKETS GENMASK(20, 16) +#define ADAP_FD_ENABLE_RAW BIT(16) + +#define CSI2_X_START_END_ISP ADAP_REG_F(0x0c) +#define ADAP_FD_X_END_MASK GENMASK(31, 16) +#define ADAP_FD_X_END_SHIFT 16 +#define ADAP_FD_X_END(x) ((x) - 1) + +#define CSI2_Y_START_END_ISP ADAP_REG_F(0x10) +#define ADAP_FD_Y_END_MASK GENMASK(31, 16) +#define ADAP_FD_Y_END_SHIFT 16 +#define ADAP_FD_Y_END(x) ((x) - 1) + +#define CSI2_VC_MODE ADAP_REG_F(0x1c) +#define ADAP_FD_VS_SEL_VC_MASK GENMASK(19, 16) +#define ADAP_FD_VS_DIRECT_PATH BIT(16) +#define ADAP_FD_HS_SEL_VC_MASK GENMASK(23, 20) +#define ADAP_FD_HS_DIRECT_PATH BIT(20) + +/* C3 MIPI adapter READER register */ +#define MIPI_ADAPT_DDR_RD0_CNTL0 ADAP_REG_R(0x00) +#define ADAP_RD0_MODULE_ENABLE BIT(0) +#define ADAP_RD0_LINE_STRIDE_MASK GENMASK(13, 4) +#define ADAP_RD0_LINE_STRIDE_SHIFT 4 +#define ADAP_RD0_SAMPLE_SEL_MASK GENMASK(27, 26) +#define ADAP_RD0_DATA_IN_VSYNC BIT(26) +#define ADAP_RD0_BURST_TYPE_MASK GENMASK(29, 28) +#define ADAP_RD0_BURST_TYPE_SHIFT 28 +#define ADAP_RD0_BURST_TYPE_INRC8 3 +#define ADAP_RD0_FRAME_RD_START BIT(31) + +#define MIPI_ADAPT_DDR_RD0_CNTL1 ADAP_REG_R(0x04) +#define ADAP_RD0_LINE_SIZE_MASK GENMASK(9, 0) +#define ADAP_RD0_LINE_NUM_MASK GENMASK(28, 16) +#define ADAP_RD0_LINE_NUM_SHIFT 16 + +#define MIPI_ADAPT_PIXEL0_CNTL0 ADAP_REG_R(0x80) +#define ADAP_PIXEL0_WORK_MODE_MASK GENMASK(17, 16) +#define ADAP_PIXEL0_DIRECT_PATH BIT(16) +#define ADAP_PIXEL0_DATA_TYPE_MASK GENMASK(25, 20) +#define ADAP_PIXEL0_DATA_TYPE_SHIFT 20 +#define ADAP_PIXEL0_DATA_TYPE_10BITS 0x2b +#define ADAP_PIXEL0_DATA_TYPE_12BITS 0x2c +#define ADAP_PIXEL0_START_ENABLE BIT(31) + +#define MIPI_ADAPT_PIXEL0_CNTL1 ADAP_REG_R(0x84) +#define ADAP_PIXEL0_X_END_MASK GENMASK(15, 0) +#define ADAP_PIXEL0_X_END(x) ((x) - 1) + +#define MIPI_ADAPT_PIXEL0_CNTL2 ADAP_REG_R(0x88) +#define ADAP_PIXEL0_FIFO_SIZE_MASK GENMASK(9, 0) +#define ADAP_PIXEL0_PIXEL_NUM_MASK GENMASK(27, 15) +#define ADAP_PIXEL0_PIXEL_NUM_SHIFT 15 + +#define MIPI_ADAPT_ALIG_CNTL0 ADAP_REG_R(0x100) +#define ADAP_ALIG_V_TOTAL_NUM_MASK GENMASK(15, 0) +/* Need to add a default blank */ +#define ADAP_ALIG_V_TOTAL_NUM(x) ((x) + 64) +#define ADAP_ALIG_H_TOTAL_NUM_MASK GENMASK(31, 16) +#define ADAP_ALIG_H_TOTAL_NUM_SHIFT 16 +/* Need to add a default blank */ +#define ADAP_ALIG_H_TOTAL_NUM(x) ((x) + 64) + +#define MIPI_ADAPT_ALIG_CNTL1 ADAP_REG_R(0x104) +#define ADAP_ALIG_HPE_NUM_MASK GENMASK(31, 16) +#define ADAP_ALIG_HPE_NUM_SHIFT 16 + +#define MIPI_ADAPT_ALIG_CNTL2 ADAP_REG_R(0x108) +#define ADAP_ALIG_VPE_NUM_MASK GENMASK(31, 16) +#define ADAP_ALIG_VPE_NUM_SHIFT 16 + +#define MIPI_ADAPT_ALIG_CNTL3 ADAP_REG_R(0x10c) +#define ADAP_ALIG_FRM_ST_PIXEL_MASK GENMASK(15, 0) + +#define MIPI_ADAPT_ALIG_CNTL6 ADAP_REG_R(0x118) +#define ADAP_ALIG_LANE0_ENABLE BIT(0) +#define ADAP_ALIG_DATA_MODE0_MASK BIT(4) +#define ADAP_ALIG_DIRECT_MODE BIT(4) +#define ADAP_ALIG_VDATA0_ENABLE BIT(12) +#define ADAP_ALIG_VDATA1_ENABLE BIT(13) +#define ADAP_ALIG_VDATA2_ENABLE BIT(14) +#define ADAP_ALIG_VDATA3_ENABLE BIT(15) + +#define MIPI_ADAPT_ALIG_CNTL8 ADAP_REG_R(0x120) +#define ADAP_ALIG_FRAME_CONTINUE BIT(5) +#define ADAP_ALIG_EXC_MASK_DIS BIT(12) +#define ADAP_ALIG_START_ENABLE BIT(31) + +#define MIPI_ADAP_MAX_WIDTH 2888 +#define MIPI_ADAP_MIN_WIDTH 160 +#define MIPI_ADAP_MAX_HEIGHT 2240 +#define MIPI_ADAP_MIN_HEIGHT 120 +#define MIPI_ADAP_DEFAULT_WIDTH 1920 +#define MIPI_ADAP_DEFAULT_HEIGHT 1080 +#define MIPI_ADAP_DEFAULT_FMT MEDIA_BUS_FMT_SRGGB10_1X10 + +/* C3 MIPI adapter pad list */ +enum { + MIPI_ADAP_PAD_SINK, + MIPI_ADAP_PAD_SRC, + MIPI_ADAP_PAD_MAX +}; + +/* + * struct adap_info - mipi adapter information + * + * @clocks: array of mipi adapter clock names + * @clock_rates: array of mipi adapter clock rate + * @clock_num: actual clock number + */ +struct adap_info { + char *clocks[MIPI_ADAP_CLOCK_NUM_MAX]; + u32 clock_rates[MIPI_ADAP_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/** + * struct adap_device - mipi adapter platform device + * + * @dev: pointer to the struct device + * @top: mipi adapter top register address + * @fd: mipi adapter frontend register address + * @rd: mipi adapter reader register address + * @clks: array of MIPI adapter clocks + * @sd: mipi adapter sub-device + * @pads: mipi adapter sub-device pads + * @notifier: notifier to register on the v4l2-async API + * @format: save sub-device format + * @src_sd: source sub-device + * @src_sd_pad: source sub-device pad + * @lock: protect mipi adapter device + * @info: version-specific MIPI adapter information + */ +struct adap_device { + struct device *dev; + void __iomem *top; + void __iomem *fd; + void __iomem *rd; + struct clk_bulk_data clks[MIPI_ADAP_CLOCK_NUM_MAX]; + + struct v4l2_subdev sd; + struct media_pad pads[MIPI_ADAP_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev_format format; + struct v4l2_subdev *src_sd; + + u16 src_sd_pad; + struct mutex lock; /* Protect adapter device */ + const struct adap_info *info; +}; + +/* Format helpers */ + +struct adap_pix_format { + u32 code; + u8 width; +}; + +static const struct adap_pix_format c3_mipi_adap_formats[] = { + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, +}; + +static const struct adap_pix_format +*c3_mipi_adap_find_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(c3_mipi_adap_formats); i++) + if (code == c3_mipi_adap_formats[i].code) + return &c3_mipi_adap_formats[i]; + + return NULL; +} + +/* Hardware configuration */ + +static void c3_mipi_adap_update_bits(struct adap_device *adap, u32 reg, + u32 mask, u32 val) +{ + void __iomem *addr; + u32 orig, tmp; + + switch (ADAP_SUBMD(reg)) { + case SUBMD_TOP: + addr = adap->top + ADAP_REG_ADDR(reg); + break; + case SUBMD_FD: + addr = adap->fd + ADAP_REG_ADDR(reg); + break; + case SUBMD_RD: + addr = adap->rd + ADAP_REG_ADDR(reg); + break; + default: + dev_err(adap->dev, "Invalid sub-module: %lu\n", ADAP_SUBMD(reg)); + return; + } + + orig = readl(addr); + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + writel(tmp, addr); +} + +/* Configure adapter top sub module */ +static void c3_mipi_adap_cfg_top(struct adap_device *adap) +{ + /* Bypass decompress */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0, + ADAP_DE_READ_BYPASS, ADAP_DE_READ_BYPASS); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DE_CTRL0, + ADAP_DE_WRITE_BYPASS, ADAP_DE_WRITE_BYPASS); +} + +/* Configure adapter frontend sub module */ +static void c3_mipi_adap_cfg_frontend(struct adap_device *adap, + struct v4l2_mbus_framefmt *fmt) +{ + /* The default value of BIT_0 is 1, so need release reset firstly. */ + c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, ADAP_FD_APPLY_RESET, 0); + + c3_mipi_adap_update_bits(adap, CSI2_X_START_END_ISP, ADAP_FD_X_END_MASK, + ADAP_FD_X_END(fmt->width) << ADAP_FD_X_END_SHIFT); + c3_mipi_adap_update_bits(adap, CSI2_Y_START_END_ISP, ADAP_FD_Y_END_MASK, + ADAP_FD_Y_END(fmt->height) << ADAP_FD_Y_END_SHIFT); + + /* Select VS and HS signal to direct path */ + c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_VS_SEL_VC_MASK, + ADAP_FD_VS_DIRECT_PATH); + c3_mipi_adap_update_bits(adap, CSI2_VC_MODE, ADAP_FD_HS_SEL_VC_MASK, + ADAP_FD_HS_DIRECT_PATH); + + /* Enable to receive RAW image */ + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_ENABLE_PACKETS, + ADAP_FD_ENABLE_RAW); + + /* Enable virtual channel 0~3 */ + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN0_EN, + ADAP_FD_VIRTUAL_CHN0_EN); + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN1_EN, + ADAP_FD_VIRTUAL_CHN1_EN); + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN2_EN, + ADAP_FD_VIRTUAL_CHN2_EN); + c3_mipi_adap_update_bits(adap, CSI2_GEN_CTRL0, ADAP_FD_VIRTUAL_CHN3_EN, + ADAP_FD_VIRTUAL_CHN3_EN); +} + +/* Configure adapter reader sub module */ +static void c3_mipi_adap_cfg_reader(struct adap_device *adap, + struct v4l2_mbus_framefmt *fmt) +{ + const struct adap_pix_format *pix_format; + u32 line_size; + u8 data_type; + + /* Data size for a line, unit: 128 bits */ + pix_format = c3_mipi_adap_find_format(fmt->code); + line_size = fmt->width * pix_format->width; + line_size = DIV_ROUND_UP(line_size, 128); + + if (pix_format->width == 10) { + data_type = ADAP_PIXEL0_DATA_TYPE_10BITS; + } else if (pix_format->width == 12) { + data_type = ADAP_PIXEL0_DATA_TYPE_12BITS; + } else { + dev_err(adap->dev, "Invalid raw format width: %u\n", pix_format->width); + return; + } + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, + ADAP_RD0_LINE_SIZE_MASK, line_size); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL1, ADAP_RD0_LINE_NUM_MASK, + fmt->height << ADAP_RD0_LINE_NUM_SHIFT); + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_MODULE_ENABLE, + ADAP_RD0_MODULE_ENABLE); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_LINE_STRIDE_MASK, + line_size << ADAP_RD0_LINE_STRIDE_SHIFT); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_SAMPLE_SEL_MASK, + ADAP_RD0_DATA_IN_VSYNC); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_DDR_RD0_CNTL0, ADAP_RD0_BURST_TYPE_MASK, + ADAP_RD0_BURST_TYPE_INRC8 << ADAP_RD0_BURST_TYPE_SHIFT); + + /* Set data type and work mode */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, + ADAP_PIXEL0_WORK_MODE_MASK, ADAP_PIXEL0_DIRECT_PATH); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, ADAP_PIXEL0_DATA_TYPE_MASK, + data_type << ADAP_PIXEL0_DATA_TYPE_SHIFT); + + /* Set pixel end number */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL1, ADAP_PIXEL0_X_END_MASK, + ADAP_PIXEL0_X_END(fmt->width)); + + /* Set line pixel number and reader fifo size */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, + ADAP_PIXEL0_FIFO_SIZE_MASK, line_size); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL2, ADAP_PIXEL0_PIXEL_NUM_MASK, + fmt->width << ADAP_PIXEL0_PIXEL_NUM_SHIFT); + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_V_TOTAL_NUM_MASK, + ADAP_ALIG_V_TOTAL_NUM(fmt->width)); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL0, ADAP_ALIG_H_TOTAL_NUM_MASK, + ADAP_ALIG_H_TOTAL_NUM(fmt->height) + << ADAP_ALIG_H_TOTAL_NUM_SHIFT); + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL1, ADAP_ALIG_HPE_NUM_MASK, + fmt->width << ADAP_ALIG_HPE_NUM_SHIFT); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL2, ADAP_ALIG_VPE_NUM_MASK, + fmt->height << ADAP_ALIG_VPE_NUM_SHIFT); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL3, + ADAP_ALIG_FRM_ST_PIXEL_MASK, fmt->width); + + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_LANE0_ENABLE, ADAP_ALIG_LANE0_ENABLE); + + /* Select direct mode */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_DATA_MODE0_MASK, ADAP_ALIG_DIRECT_MODE); + + /* Enable vdata 0~3 */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_VDATA0_ENABLE, ADAP_ALIG_VDATA0_ENABLE); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_VDATA1_ENABLE, ADAP_ALIG_VDATA1_ENABLE); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_VDATA2_ENABLE, ADAP_ALIG_VDATA2_ENABLE); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL6, + ADAP_ALIG_VDATA3_ENABLE, ADAP_ALIG_VDATA2_ENABLE); + + /* continue mode and disable hold counter */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + ADAP_ALIG_FRAME_CONTINUE, ADAP_ALIG_FRAME_CONTINUE); + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + ADAP_ALIG_EXC_MASK_DIS, ADAP_ALIG_EXC_MASK_DIS); +} + +static void c3_mipi_adap_start_stream(struct adap_device *adap) +{ + /* Enable to start and will auto clear to 0 */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_ALIG_CNTL8, + ADAP_ALIG_START_ENABLE, ADAP_ALIG_START_ENABLE); + + /* Enable to start and will auto clear to 0 */ + c3_mipi_adap_update_bits(adap, MIPI_ADAPT_PIXEL0_CNTL0, + ADAP_PIXEL0_START_ENABLE, ADAP_PIXEL0_START_ENABLE); + + /* Enable frontend */ + c3_mipi_adap_update_bits(adap, CSI2_CLK_RESET, + ADAP_FD_ENABLE, ADAP_FD_ENABLE); +} + +static void c3_mipi_adap_cfg_format(struct adap_device *adap) +{ + struct v4l2_subdev_format *format = &adap->format; + + c3_mipi_adap_cfg_top(adap); + c3_mipi_adap_cfg_frontend(adap, &format->format); + c3_mipi_adap_cfg_reader(adap, &format->format); +} + +/* V4L2 subdev operations */ + +static int c3_mipi_adap_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct adap_device *adap = v4l2_get_subdevdata(sd); + u64 sink_streams; + int ret; + + guard(mutex)(&adap->lock); + + pm_runtime_resume_and_get(adap->dev); + + c3_mipi_adap_cfg_format(adap); + c3_mipi_adap_start_stream(adap); + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + MIPI_ADAP_PAD_SINK, + &streams_mask); + ret = v4l2_subdev_enable_streams(adap->src_sd, + adap->src_sd_pad, + sink_streams); + if (ret) { + pm_runtime_put(adap->dev); + return ret; + } + + return 0; +} + +static int c3_mipi_adap_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct adap_device *adap = v4l2_get_subdevdata(sd); + u64 sink_streams; + int ret; + + guard(mutex)(&adap->lock); + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + MIPI_ADAP_PAD_SINK, + &streams_mask); + ret = v4l2_subdev_disable_streams(adap->src_sd, + adap->src_sd_pad, + sink_streams); + if (ret) + dev_err(adap->dev, "Failed to disable %s\n", adap->src_sd->name); + + pm_runtime_put(adap->dev); + + return ret; +} + +static int c3_mipi_adap_cfg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = MIPI_ADAP_DEFAULT_WIDTH, + .height = MIPI_ADAP_DEFAULT_HEIGHT, + .code = MIPI_ADAP_DEFAULT_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_NONE, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int c3_mipi_adap_init_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes; + struct v4l2_subdev_krouting routing; + + routes.sink_pad = MIPI_ADAP_PAD_SINK; + routes.sink_stream = 0; + routes.source_pad = MIPI_ADAP_PAD_SRC; + routes.source_stream = 0; + routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routing.num_routes = 1; + routing.routes = &routes; + + return c3_mipi_adap_cfg_routing(sd, state, &routing); +} + +static int c3_mipi_adap_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + bool is_streaming = v4l2_subdev_is_streaming(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming) + return -EBUSY; + + return c3_mipi_adap_cfg_routing(sd, state, routing); +} + +static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case MIPI_ADAP_PAD_SINK: + if (code->index >= ARRAY_SIZE(c3_mipi_adap_formats)) + return -EINVAL; + + code->code = c3_mipi_adap_formats[code->index].code; + break; + case MIPI_ADAP_PAD_SRC: + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, code->pad); + code->code = fmt->code; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct adap_device *adap = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *fmt; + const struct adap_pix_format *pix_format; + + if (format->pad != MIPI_ADAP_PAD_SINK) + return v4l2_subdev_get_fmt(sd, state, format); + + pix_format = c3_mipi_adap_find_format(format->format.code); + if (!pix_format) + pix_format = &c3_mipi_adap_formats[0]; + + fmt = v4l2_subdev_state_get_format(state, format->pad); + fmt->code = pix_format->code; + fmt->width = clamp_t(u32, format->format.width, + MIPI_ADAP_MIN_WIDTH, MIPI_ADAP_MAX_WIDTH); + fmt->height = clamp_t(u32, format->format.height, + MIPI_ADAP_MIN_HEIGHT, MIPI_ADAP_MAX_HEIGHT); + + format->format = *fmt; + + /* Synchronize the format to source pad */ + fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC); + *fmt = format->format; + + adap->format = *format; + + return 0; +} + +static int c3_mipi_adap_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + + sink_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SINK); + src_fmt = v4l2_subdev_state_get_format(state, MIPI_ADAP_PAD_SRC); + + sink_fmt->width = MIPI_ADAP_DEFAULT_WIDTH; + sink_fmt->height = MIPI_ADAP_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MIPI_ADAP_DEFAULT_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + sink_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + *src_fmt = *sink_fmt; + + return c3_mipi_adap_init_routing(sd, state); +} + +static const struct v4l2_subdev_pad_ops c3_mipi_adap_pad_ops = { + .enum_mbus_code = c3_mipi_adap_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = c3_mipi_adap_set_fmt, + .set_routing = c3_mipi_adap_set_routing, + .enable_streams = c3_mipi_adap_enable_streams, + .disable_streams = c3_mipi_adap_disable_streams, +}; + +static const struct v4l2_subdev_ops c3_mipi_adap_subdev_ops = { + .pad = &c3_mipi_adap_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops c3_mipi_adap_internal_ops = { + .init_state = c3_mipi_adap_init_state, +}; + +/* Media entity operations */ +static const struct media_entity_operations c3_mipi_adap_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* PM runtime */ + +static int __maybe_unused c3_mipi_adap_runtime_suspend(struct device *dev) +{ + struct adap_device *adap = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(adap->info->clock_num, adap->clks); + + return 0; +} + +static int __maybe_unused c3_mipi_adap_runtime_resume(struct device *dev) +{ + struct adap_device *adap = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(adap->info->clock_num, adap->clks); +} + +static const struct dev_pm_ops c3_mipi_adap_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(c3_mipi_adap_runtime_suspend, + c3_mipi_adap_runtime_resume, NULL) +}; + +/* Probe/remove & platform driver */ + +static int c3_mipi_adap_subdev_init(struct adap_device *adap) +{ + struct v4l2_subdev *sd = &adap->sd; + int ret; + + v4l2_subdev_init(sd, &c3_mipi_adap_subdev_ops); + sd->owner = THIS_MODULE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &c3_mipi_adap_internal_ops; + snprintf(sd->name, sizeof(sd->name), "%s", MIPI_ADAP_SUBDEV_NAME); + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &c3_mipi_adap_entity_ops; + + sd->dev = adap->dev; + v4l2_set_subdevdata(sd, adap); + + adap->pads[MIPI_ADAP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + adap->pads[MIPI_ADAP_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, MIPI_ADAP_PAD_MAX, adap->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + media_entity_cleanup(&sd->entity); + return ret; + } + + return 0; +} + +static void c3_mipi_adap_subdev_deinit(struct adap_device *adap) +{ + v4l2_subdev_cleanup(&adap->sd); + media_entity_cleanup(&adap->sd.entity); +} + +/* Subdev notifier register */ +static int c3_mipi_adap_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct adap_device *adap = v4l2_get_subdevdata(notifier->sd); + struct media_pad *sink = &adap->sd.entity.pads[MIPI_ADAP_PAD_SINK]; + int ret; + + ret = media_entity_get_fwnode_pad(&sd->entity, + sd->fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(adap->dev, "Failed to find pad for %s\n", sd->name); + return ret; + } + + adap->src_sd = sd; + adap->src_sd_pad = ret; + + return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations c3_mipi_adap_notify_ops = { + .bound = c3_mipi_adap_notify_bound, +}; + +static int c3_mipi_adap_async_register(struct adap_device *adap) +{ + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + int ret; + + v4l2_async_subdev_nf_init(&adap->notifier, &adap->sd); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(adap->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + asc = v4l2_async_nf_add_fwnode_remote(&adap->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_put_handle; + } + + adap->notifier.ops = &c3_mipi_adap_notify_ops; + ret = v4l2_async_nf_register(&adap->notifier); + if (ret) + goto err_cleanup_nf; + + ret = v4l2_async_register_subdev(&adap->sd); + if (ret) + goto err_unregister_nf; + + fwnode_handle_put(ep); + + return 0; + +err_unregister_nf: + v4l2_async_nf_unregister(&adap->notifier); +err_cleanup_nf: + v4l2_async_nf_cleanup(&adap->notifier); +err_put_handle: + fwnode_handle_put(ep); + return ret; +} + +static void c3_mipi_adap_async_unregister(struct adap_device *adap) +{ + v4l2_async_unregister_subdev(&adap->sd); + v4l2_async_nf_unregister(&adap->notifier); + v4l2_async_nf_cleanup(&adap->notifier); +} + +static int c3_mipi_adap_ioremap_resource(struct adap_device *adap) +{ + struct device *dev = adap->dev; + struct platform_device *pdev = to_platform_device(dev); + + adap->top = devm_platform_ioremap_resource_byname(pdev, "top"); + if (IS_ERR(adap->top)) + return PTR_ERR(adap->top); + + adap->fd = devm_platform_ioremap_resource_byname(pdev, "fd"); + if (IS_ERR(adap->fd)) + return PTR_ERR(adap->fd); + + adap->rd = devm_platform_ioremap_resource_byname(pdev, "rd"); + if (IS_ERR(adap->rd)) + return PTR_ERR(adap->rd); + + return 0; +} + +static int c3_mipi_adap_configure_clocks(struct adap_device *adap) +{ + const struct adap_info *info = adap->info; + int ret; + u32 i; + + for (i = 0; i < info->clock_num; i++) + adap->clks[i].id = info->clocks[i]; + + ret = devm_clk_bulk_get(adap->dev, info->clock_num, adap->clks); + if (ret) + return ret; + + for (i = 0; i < info->clock_num; i++) { + if (!info->clock_rates[i]) + continue; + ret = clk_set_rate(adap->clks[i].clk, info->clock_rates[i]); + if (ret) { + dev_err(adap->dev, "Failed to set %s rate %u\n", info->clocks[i], + info->clock_rates[i]); + return ret; + } + } + + return 0; +} + +static int c3_mipi_adap_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adap_device *adap; + int ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + adap->info = of_device_get_match_data(dev); + adap->dev = dev; + + ret = c3_mipi_adap_ioremap_resource(adap); + if (ret) + return dev_err_probe(dev, ret, "Failed to ioremap resource\n"); + + ret = c3_mipi_adap_configure_clocks(adap); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure clocks\n"); + + platform_set_drvdata(pdev, adap); + + mutex_init(&adap->lock); + pm_runtime_enable(dev); + + ret = c3_mipi_adap_subdev_init(adap); + if (ret < 0) + goto err_disable_runtime_pm; + + ret = c3_mipi_adap_async_register(adap); + if (ret < 0) + goto err_deinit_subdev; + + return 0; + +err_deinit_subdev: + c3_mipi_adap_subdev_deinit(adap); +err_disable_runtime_pm: + pm_runtime_disable(dev); + mutex_destroy(&adap->lock); + return ret; +}; + +static void c3_mipi_adap_remove(struct platform_device *pdev) +{ + struct adap_device *adap = platform_get_drvdata(pdev); + + c3_mipi_adap_async_unregister(adap); + c3_mipi_adap_subdev_deinit(adap); + + pm_runtime_disable(&pdev->dev); + mutex_destroy(&adap->lock); +}; + +static const struct adap_info c3_mipi_adap_info = { + .clocks = {"vapb", "isp0"}, + .clock_rates = {0, 400000000}, + .clock_num = 2 +}; + +static const struct of_device_id c3_mipi_adap_of_match[] = { + { .compatible = "amlogic,c3-mipi-adapter", + .data = &c3_mipi_adap_info }, + { }, +}; +MODULE_DEVICE_TABLE(of, c3_mipi_adap_of_match); + +static struct platform_driver c3_mipi_adap_driver = { + .probe = c3_mipi_adap_probe, + .remove = c3_mipi_adap_remove, + .driver = { + .name = "c3-mipi-adapter", + .of_match_table = c3_mipi_adap_of_match, + .pm = &c3_mipi_adap_pm_ops, + }, +}; + +module_platform_driver(c3_mipi_adap_driver); + +MODULE_AUTHOR("Keke Li "); +MODULE_DESCRIPTION("Amlogic C3 MIPI adapter"); +MODULE_LICENSE("GPL"); From patchwork Wed Sep 18 06:07:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806585 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 B898115C14F; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=o9OLDxz9IqN2YlVnFN8dLMvKSUe3THrXPq843LzC00qxFj1iB4Vq49kYz4DNIPpSNHWacJQ16+pCU6XkB+f4JYagOOoCn/6L+d/Nex8bJu3MHObgPz9wSZSMt0iLxYRmflViVacntQ+kPXCr2MUoLNsmQyswazwFCqJ2QI1Tu2k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=kppESZfBsUInWnhNoLdnkM7msjpMLeO5b9hJTo5bkqU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DhLbmOlUkcNiIDwf3a97hG3lqqQLGJiPUKxoncCMqGkXfLfXxr3kyXG42gmID+RBFdnWCJi87Gf79QIzT8u4BJbTQ9RY38MUW3eNwGnd7KGuIyka7D02kEWRZPqT2W/qtKGVmy18O1IN4TIO7Te9IKx20oQGniB58hTWoY0NjO8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=teAU02WU; 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="teAU02WU" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4B13FC4CED7; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=kppESZfBsUInWnhNoLdnkM7msjpMLeO5b9hJTo5bkqU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=teAU02WU9fnBG73bfofxI0okOyiYAF4V2qqMePLs12XS/RzAO8jQvpYX0JqnnpXVi F5gBBY5kf9h0z+LntN9fF22tRKYZMdMP/qNA/nQhy6I4aweqWz4Pv6GIMG4OTm5WNl XVIiFYhs3mUJIpgfHV0aGR3eWewj/PqXEjiHqPLRDEfQVfXUNrbsfQX8Bh7BoPhY2x nhhsMSRGrP39rmO7gwLloT+uKVEn4AOZYAz92TT7ls6J2L8E87megjsZUnpXVSnH31 8S0hwU1syS+3d6K7k+D6gLL7mw1w9cq1BhrDSg3vofwf/QGauw1NvKIMN59CqBCo5V ayxSNSW9vEJFg== 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 414FFC3ABB2; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:16 +0800 Subject: [PATCH v3 5/9] 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: <20240918-c3isp-v3-5-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=2433; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=6t6d3ae/MzUOlqTRvCXtGy3RhMNxCwUEKczMDIQ1AZ0=; b=CsPlkOkXaYTnTgX6WPD25QYJKBXFf9nR4yEosBWioBt4ePjHsB+JLdHb5/n8tnSjqKtW08uBN e1snn/ItgUVCYbjRhNt7Mh87Bud2axjJy/PElmRcxUvBTFesdTa+6ph 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 Reviewed-by: Rob Herring (Arm) --- .../devicetree/bindings/media/amlogic,c3-isp.yaml | 85 ++++++++++++++++++++++ 1 file changed, 85 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..64ae1946b99b --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,c3-isp.yaml @@ -0,0 +1,85 @@ +# 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"; + interrupts = ; + + port { + c3_isp_in: endpoint { + remote-endpoint = <&c3_adap_out>; + }; + }; + }; + }; +... From patchwork Wed Sep 18 06:07:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806586 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 B7CD215C14E; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=qV2+quf7/AB9hxN+Rt+xf357nyL9GYN/LGfNv5IPArtDaBoZRs6w4f7KfxcU9SDLNf8z0jVPDV490IuuJBgTOOLtFCpMwMhWDFNJXWTRyLss11SBWtNkyaE4Sc83uPjVPvZKL0SwMu6d0LR5E9M2jPaNGeRgnNsQWYvwE8R7GI8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=eU1HQMFCxErjcvWaDadkLK5YgaXUhPL5khZzjM98BSs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GfI+kjicQEi/OeD5AnmHm3MdYb4jM6Am/bIwZPmjhkGvh3TG7l/bozcvjTv2QKNv8SobK14T5xnpE3wou3P4jMBAURKxBt3+EGNRQwuTaJXQQf3HeuavpyKfJsbeCuLIRkAwpTM/Tkf1KgBogwptmLo88cOb8WiK0mCsFUrOL9Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=dGnD5sm8; 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="dGnD5sm8" Received: by smtp.kernel.org (Postfix) with ESMTPS id 5EAFDC4CEDD; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=eU1HQMFCxErjcvWaDadkLK5YgaXUhPL5khZzjM98BSs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=dGnD5sm8zQJ6YbhSZqxx3uRcqiP24nmZpSmfzUpt9jdT7BzahJtlATGsrB3P3hcrO cGTrMM8K4fIlARAO/qvou8q1sWs0fgFuOOVQD/3KRUTRnB5f9MyXM8IQrzucYUezvg JBBHQ5A0Dh8eiw9Wb4fThCzjHxG8c2vM4+WJW3ASMSeqpvqw6wLO9/7AxjvPd9sz7q RFBSxa4M6wemGetQ6tA1jDNIxM7eAjLsW+Oz3yAq/bUEePm2RqglfyW2Ae7iogLT3S d51drtoZpPir1eN6RoOuT4nU6K1xBOiOMnd5Im5Al2v61OTF1LHd1DwqHU1C4aIauq CK+nB674yUUrQ== 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 56C6CC3ABCD; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:17 +0800 Subject: [PATCH v3 6/9] media: Add C3ISP_PARAMS and C3ISP_STATS meta formats Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240918-c3isp-v3-6-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=2162; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=1oRu2lG8MHrXLJnw+JUMY2ovLWxxdjfkC+35WOnDZsM=; b=Xp08y3/+60VjbWI0SB2B61yV6fl2Btz4PdvaL31h966ruee+1lOBKzsixe9ys/hdLbbrT3fsA fgOV8G+pgEDAVrLTE/+xHOtWwOvGtm/VJ9F5fZ0TbJnVqR4A0snDtcN X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li C3ISP_PARAMS is the C3 ISP Parameters format. C3ISP_STATS is the C3 ISP Statistics format. Reviewed-by: Daniel Scally Signed-off-by: Keke Li --- drivers/media/v4l2-core/v4l2-ioctl.c | 2 ++ include/uapi/linux/videodev2.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index e14db67be97c..d0e346f301c1 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1459,6 +1459,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; case V4L2_META_FMT_RK_ISP1_STAT_3A: descr = "Rockchip ISP1 3A Statistics"; break; case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; + case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; + case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 27239cb64065..7d0a3e502a47 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -857,6 +857,10 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ #define V4L2_META_FMT_RK_ISP1_EXT_PARAMS v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */ +/* Vendor specific - used for C3_ISP */ +#define V4L2_META_FMT_C3ISP_PARAMS v4l2_fourcc('C', 'P', 'R', 'M') /* Amlogic C3 ISP Parameters */ +#define V4L2_META_FMT_C3ISP_STATS v4l2_fourcc('C', 'S', 'T', 'S') /* Amlogic C3 ISP Statistics */ + /* Vendor specific - used for RaspberryPi PiSP */ #define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */ From patchwork Wed Sep 18 06:07:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806589 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 B3CFB15C138; Wed, 18 Sep 2024 06:07:36 +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=1726639656; cv=none; b=WA+3OhfoON2kxFXh0NT8tTrWc9MuswVxBcWPjMYOzU3alCR6LoNOxA+yDaZvQBd9jvcRVApV4VpWgj4UnRtQS87kkB7sEJ3DZZoWkQsD6KLFUEfliZ1HgxeBtMIh5UR4Ff9KEA7b2yPom4nHiO+BWPjqZ4hlIOCa9zI++3hnDkc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639656; c=relaxed/simple; bh=pxSswRCg1/Hdb5nFIJQ8/4cnGCDyiSO9neVVFi+pWro=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=gZeRrIhBkjyBPO0kOM80BHafONzjH/zneLkK7NyuFHRj17C2jWZAV8ZtBHONG9VmsjSVqqeg4AsJbOrfnR7nGpyKiQrVqxBHLzJVwaRcvJUhxdaaFRVCF0XZPdIo8F6AJOyRIHBmKBkMQa//II44a93jZTN43Qi/W4yIK5Q8Y/I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=k/fCcarn; 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="k/fCcarn" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8DFCEC4CEE0; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=pxSswRCg1/Hdb5nFIJQ8/4cnGCDyiSO9neVVFi+pWro=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=k/fCcarnIBokWk5OjYkvqZM2CKSFVqK+UHuPMf1m1QCOIQzNqckQonD/u2qsjeLdj 5Own/EAKuYp1hVLYrUZF8zxKRosJmuVkHLd4ulKb4ZWs9R+loYxbIvurhFEm5Aj94C OL8RO9gFYl3wzGwi3F1y0hq2CCvpzEsOXlfN+s17P4mJ2R3qtZO/IbhYObrvyB66rF Zgb3zwYT5EjKqwabObPnCoHHUH9tJlxjSysQq8HK+JTnbaSIECdot/f5OdzXlAeZ6d F6FT44ZcI61dU7ldSoqc/ex5YIjgvjeHxbWoN6OuOc65zAqB/eVjm/0x699ZZQE4eq vdJhvzXYMmwsg== 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 83D11C35FE7; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:18 +0800 Subject: [PATCH v3 7/9] 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: <20240918-c3isp-v3-7-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=193416; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=6Lq8bboj6uGETGQuaZt2/zWSJY25zNm3eVRV6k3FRvY=; b=ruTrb/DmxOYplsFYb3LUJ6gF0PJdZNq5Viu9A8/VwLexT2aEOmc5x2mhOevf5NbQU/aZkQ18E k+YUpiWVeopA/mAB1QqOHTpai9bwlRIuUoBnzmbYCizSd5ESZs9P1dD X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li The C3 ISP supports multi-camera and muti-exposure high dynamic range (HDR). It brings together some advanced imaging technologies to provide good image quality. This driver mainly responsible for driving ISP pipeline to process raw image. Signed-off-by: Keke Li --- drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + drivers/media/platform/amlogic/c3-isp/Kconfig | 17 + drivers/media/platform/amlogic/c3-isp/Makefile | 10 + .../media/platform/amlogic/c3-isp/c3-isp-capture.c | 759 ++++++++++++++++++ .../media/platform/amlogic/c3-isp/c3-isp-common.h | 327 ++++++++ .../media/platform/amlogic/c3-isp/c3-isp-core.c | 675 ++++++++++++++++ drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c | 486 ++++++++++++ .../media/platform/amlogic/c3-isp/c3-isp-params.c | 857 +++++++++++++++++++++ .../media/platform/amlogic/c3-isp/c3-isp-regs.h | 683 ++++++++++++++++ .../media/platform/amlogic/c3-isp/c3-isp-resizer.c | 768 ++++++++++++++++++ .../media/platform/amlogic/c3-isp/c3-isp-stats.c | 488 ++++++++++++ .../amlogic/c3-isp/include/uapi/c3-isp-config.h | 537 +++++++++++++ 13 files changed, 5609 insertions(+) diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index df09717b28d0..ebda6b7edc2d 100644 --- a/drivers/media/platform/amlogic/Kconfig +++ b/drivers/media/platform/amlogic/Kconfig @@ -2,6 +2,7 @@ comment "Amlogic media platform drivers" +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" source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile index b370154b090c..d0d9363d4d8d 100644 --- a/drivers/media/platform/amlogic/Makefile +++ b/drivers/media/platform/amlogic/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-y += c3-isp/ obj-y += c3-mipi-adapter/ obj-y += c3-mipi-csi2/ obj-y += meson-ge2d/ diff --git a/drivers/media/platform/amlogic/c3-isp/Kconfig b/drivers/media/platform/amlogic/c3-isp/Kconfig new file mode 100644 index 000000000000..e317c1e81750 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/Kconfig @@ -0,0 +1,17 @@ +# 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 + help + Video4Linux2 driver for Amlogic C3 ISP pipeline. + C3 ISP pipeline mainly for processing raw image + and output result 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..ee9a7a17a203 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-capture.c @@ -0,0 +1,759 @@ +// 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" + +static const struct c3_isp_capture_format cap_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8, + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8, + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + }, +}; + +/* Hardware configuration */ + +/* Set the address of wrmifx3(write memory interface) */ +static void c3_isp_cap_wrmifx3_buff(struct c3_isp_capture *cap) +{ + struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix; + struct c3_isp_vb2_buffer *buff = cap->buff; + u32 offset; + + c3_isp_write(cap->isp, + C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_BADDR, cap->id), + WRMIFX3_CH0_BADDR(buff->paddr)); + + if (pix->pixelformat == V4L2_PIX_FMT_NV12 || + pix->pixelformat == V4L2_PIX_FMT_NV21 || + pix->pixelformat == V4L2_PIX_FMT_NV16 || + pix->pixelformat == V4L2_PIX_FMT_NV61) { + offset = pix->width * pix->height; + c3_isp_write(cap->isp, + C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_BADDR, cap->id), + WRMIFX3_CH1_BADDR(buff->paddr + offset)); + } +} + +static void c3_isp_cap_output_size(struct c3_isp_capture *cap) +{ + struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix; + + c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id), + DISP_OUT_VSIZE_MASK, pix->height); + c3_isp_update_bits(cap->isp, C3_DISP_REG(DISP0_TOP_OUT_SIZE, cap->id), + DISP_OUT_HSIZE_MASK, pix->width << DISP_OUT_HSIZE_SHIFT); +} + +static void c3_isp_cap_wrmifx3_grey(struct c3_isp_capture *cap) +{ + struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix; + u32 stride; + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT); + + /* Grey has 1 plane*/ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_PLANE_MASK, + WRMIFX3_FMT_MTX_PLANE_X1 << WRMIFX3_FMT_MTX_PLANE_SHIFT); + + /* Set Y only as output format */ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MODE_OUT_MASK, + WRMIFX3_FMT_MODE_OUT_Y_ONLY << WRMIFX3_FMT_MODE_OUT_SHIFT); + + /* The unit of stride is 128 bits */ + stride = DIV_ROUND_UP(fmt->width * 8, 128); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id), + WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id), + WRMIFX3_CH0_PIX_BITS_MODE_MASK, + WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT); +} + +static void c3_isp_cap_wrmifx3_yuv420(struct c3_isp_capture *cap, u32 swap_uv) +{ + struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix; + u32 stride; + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_8BIT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_UV_SWAP_MASK, + swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT); + + /* NV12 or NV21 has 2 planes*/ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_PLANE_MASK, + WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT); + + /* Set YUV420 as output format */ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MODE_OUT_MASK, + WRMIFX3_FMT_MODE_OUT_YUV420 << WRMIFX3_FMT_MODE_OUT_SHIFT); + + /* The unit of stride is 128 bits */ + stride = DIV_ROUND_UP(fmt->width * 8, 128); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id), + WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id), + WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id), + WRMIFX3_CH0_PIX_BITS_MODE_MASK, + WRMIFX3_CH0_PIX_BITS_8BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id), + WRMIFX3_CH1_PIX_BITS_MODE_MASK, + WRMIFX3_CH1_PIX_BITS_16BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT); +} + +static void c3_isp_cap_wrmifx3_yuv422(struct c3_isp_capture *cap, u32 swap_uv) +{ + struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix; + u32 stride; + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_IBITS_MASK, WRMIFX3_FMT_MTX_IBITS_16BIT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_UV_SWAP_MASK, + swap_uv << WRMIFX3_FMT_MTX_UV_SWAP_SHIFT); + + /* NV16 or NV61 has 2 planes*/ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MTX_PLANE_MASK, + WRMIFX3_FMT_MTX_PLANE_X2 << WRMIFX3_FMT_MTX_PLANE_SHIFT); + + /* Set YUV422 as output format */ + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_CTRL, cap->id), + WRMIFX3_FMT_MODE_OUT_MASK, + WRMIFX3_FMT_MODE_OUT_YUV422 << WRMIFX3_FMT_MODE_OUT_SHIFT); + + /* The unit of stride is 128 bits */ + stride = DIV_ROUND_UP(fmt->width * 16, 128); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL0, cap->id), + WRMIFX3_CH0_STRIDE_MASK, stride << WRMIFX3_CH0_STRIDE_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL0, cap->id), + WRMIFX3_CH1_STRIDE_MASK, stride << WRMIFX3_CH1_STRIDE_SHIFT); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH0_CTRL1, cap->id), + WRMIFX3_CH0_PIX_BITS_MODE_MASK, + WRMIFX3_CH0_PIX_BITS_16BITS << WRMIFX3_CH0_PIX_BITS_MODE_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CH1_CTRL1, cap->id), + WRMIFX3_CH1_PIX_BITS_MODE_MASK, + WRMIFX3_CH1_PIX_BITS_32BITS << WRMIFX3_CH1_PIX_BITS_MODE_SHIFT); +} + +static void c3_isp_cap_wrmifx3_size(struct c3_isp_capture *cap) +{ + struct v4l2_pix_format *fmt = &cap->vfmt.fmt.pix; + + if (fmt->pixelformat == V4L2_PIX_FMT_GREY) { + c3_isp_cap_wrmifx3_grey(cap); + } else if (fmt->pixelformat == V4L2_PIX_FMT_NV12) { + c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV); + } else if (fmt->pixelformat == V4L2_PIX_FMT_NV21) { + c3_isp_cap_wrmifx3_yuv420(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU); + } else if (fmt->pixelformat == V4L2_PIX_FMT_NV16) { + c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_UV); + } else if (fmt->pixelformat == V4L2_PIX_FMT_NV61) { + c3_isp_cap_wrmifx3_yuv422(cap, WRMIFX3_FMT_MTX_UV_SWAP_VU); + } else { + dev_err(cap->isp->dev, "Invalid pixel format: 0x%x\n", fmt->pixelformat); + return; + } + + c3_isp_write(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_FMT_SIZE, cap->id), + WRMIFX3_FMT_HSIZE(fmt->width) | WRMIFX3_FMT_VSIZE(fmt->height)); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_H, cap->id), + WRMIFX3_WIN_LUMA_HEND_MASK, + WRMIFX3_WIN_LUMA_HEND(fmt->width) << WRMIFX3_WIN_LUMA_HEND_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_LUMA_V, cap->id), + WRMIFX3_WIN_LUMA_VEND_MASK, + WRMIFX3_WIN_LUMA_VEND(fmt->height) << WRMIFX3_WIN_LUMA_VEND_SHIFT); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_HSIZE, cap->id), + WRMIFX3_CROP_HEND_MASK, + WRMIFX3_CROP_HEND(fmt->width) << WRMIFX3_CROP_HEND_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_CRP_VSIZE, cap->id), + WRMIFX3_CROP_VEND_MASK, + WRMIFX3_CROP_VEND(fmt->height) << WRMIFX3_CROP_VEND_SHIFT); + + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_H, cap->id), + WRMIFX3_WIN_CHROM_HEND_MASK, + WRMIFX3_WIN_CHROM_HEND(fmt->width) << WRMIFX3_WIN_CHROM_HEND_SHIFT); + c3_isp_update_bits(cap->isp, C3_WRMIFX3_REG(ISP_WRMIFX3_0_WIN_CHROM_V, cap->id), + WRMIFX3_WIN_CHROM_VEND_MASK, + WRMIFX3_WIN_CHROM_VEND(fmt->height) << WRMIFX3_WIN_CHROM_VEND_SHIFT); +} + +static void c3_isp_cap_cfg_buff(struct c3_isp_capture *cap) +{ + cap->buff = list_first_entry_or_null(&cap->pending, + struct c3_isp_vb2_buffer, list); + if (cap->buff) { + c3_isp_cap_wrmifx3_buff(cap); + list_del(&cap->buff->list); + } +} + +static void c3_isp_cap_start(struct c3_isp_capture *cap) +{ + c3_isp_cap_cfg_buff(cap); + + c3_isp_cap_output_size(cap); + c3_isp_cap_wrmifx3_size(cap); + + c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, + TOP_WRMIF_EN(cap->id), TOP_WRMIF_EN(cap->id)); + + cap->is_streaming = true; +} + +static void c3_isp_cap_stop(struct c3_isp_capture *cap) +{ + cap->is_streaming = false; + + c3_isp_update_bits(cap->isp, ISP_TOP_PATH_EN, TOP_WRMIF_EN(cap->id), 0); +} + +static int c3_isp_cap_done(struct c3_isp_capture *cap) +{ + struct c3_isp_vb2_buffer *buff = cap->buff; + unsigned long flags; + + if (!cap->is_streaming) + return -EINVAL; + + spin_lock_irqsave(&cap->buff_lock, flags); + + 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); + + spin_unlock_irqrestore(&cap->buff_lock, flags); + + return 0; +} + +/* V4L2 video operations */ + +static const struct c3_isp_capture_format +*c3_cap_find_fmt(u32 fourcc) +{ + unsigned int i; + + for (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 c3_isp_capture *cap, + struct v4l2_pix_format *pix) +{ + const struct c3_isp_capture_format *fmt; + + fmt = c3_cap_find_fmt(pix->pixelformat); + if (!fmt) + fmt = &cap_formats[0]; + + pix->width = clamp(pix->width, C3_ISP_MIN_WIDTH, C3_ISP_MAX_WIDTH); + pix->height = clamp(pix->height, C3_ISP_MIN_HEIGHT, C3_ISP_MAX_HEIGHT); + pix->pixelformat = fmt->fourcc; + pix->field = V4L2_FIELD_NONE; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + /* ISP hardware requires 16 bytes alignment */ + pix->bytesperline = ALIGN(pix->width, 16); + pix->sizeimage = pix->bytesperline * pix->height * fmt->depth / 8; +} + +static void c3_isp_cap_return_buffers(struct c3_isp_capture *cap, + enum vb2_buffer_state state) +{ + unsigned long flags; + struct c3_isp_vb2_buffer *buff; + + spin_lock_irqsave(&cap->buff_lock, flags); + + 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_vb2_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, state); + } + + spin_unlock_irqrestore(&cap->buff_lock, flags); +} + +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_capture_format *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(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_capture *cap = video_drvdata(file); + + f->fmt.pix = cap->vfmt.fmt.pix; + + return 0; +} + +static int c3_isp_cap_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_capture *cap = video_drvdata(file); + + c3_cap_try_fmt(cap, &f->fmt.pix); + cap->vfmt = *f; + + return 0; +} + +static int c3_isp_cap_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct c3_isp_capture *cap = video_drvdata(file); + + c3_cap_try_fmt(cap, &f->fmt.pix); + + return 0; +} + +static int c3_isp_cap_enum_frmsize(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct c3_isp_capture_format *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 = c3_isp_cap_g_fmt, + .vidioc_s_fmt_vid_cap = c3_isp_cap_s_fmt, + .vidioc_try_fmt_vid_cap = c3_isp_cap_try_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, + .vidioc_enum_framesizes = c3_isp_cap_enum_frmsize, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations isp_cap_v4l2_fops = { + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static int c3_isp_cap_link_validate(struct media_link *link) +{ + struct video_device *vdev = + media_entity_to_video_device(link->sink->entity); + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(link->source->entity); + struct c3_isp_capture *cap = video_get_drvdata(vdev); + struct v4l2_pix_format *pix_fmt = &cap->vfmt.fmt.pix; + struct v4l2_subdev_format src_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index, + }; + const struct c3_isp_capture_format *cap_fmt = + c3_cap_find_fmt(pix_fmt->pixelformat); + int ret; + + ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &src_fmt); + if (ret) + return ret; + + if (src_fmt.format.width != pix_fmt->width || + src_fmt.format.height != pix_fmt->height || + src_fmt.format.code != cap_fmt->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_fmt->mbus_code, + pix_fmt->width, pix_fmt->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); + struct v4l2_pix_format *pix = &cap->vfmt.fmt.pix; + + if (*num_planes) { + if (*num_planes != 1) + return -EINVAL; + + if (sizes[0] < pix->sizeimage) + return -EINVAL; + } else { + *num_planes = 1; + sizes[0] = pix->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_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&cap->buff_lock, flags); + + list_add_tail(&buf->list, &cap->pending); + + spin_unlock_irqrestore(&cap->buff_lock, flags); +} + +static int c3_isp_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = cap->vfmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(cap->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_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_capture *cap = vb2_get_drv_priv(vb->vb2_queue); + + buf->vaddr = vb2_plane_vaddr(vb, 0); + buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + memset(buf->vaddr, 0, cap->vfmt.fmt.pix.sizeimage); + + 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; + + guard(mutex)(&cap->isp->lock); + + ret = pm_runtime_resume_and_get(cap->isp->dev); + if (ret) + return 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_pm_put; + } + + if (c3_isp_pipeline_ready(cap->isp)) { + ret = v4l2_subdev_enable_streams(&cap->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + BIT(0)); + if (ret) + goto err_pipeline_stop; + } + + c3_isp_rsz_start(cap->rsz); + c3_isp_cap_start(cap); + + return 0; + +err_pipeline_stop: + video_device_pipeline_stop(&cap->vdev); +err_pm_put: + pm_runtime_put(cap->isp->dev); + 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); + + guard(mutex)(&cap->isp->lock); + + c3_isp_cap_stop(cap); + c3_isp_rsz_stop(cap->rsz); + c3_isp_cap_return_buffers(cap, VB2_BUF_STATE_ERROR); + + if (cap->isp->pipe.start_count == 1) + v4l2_subdev_disable_streams(&cap->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + BIT(0)); + + video_device_pipeline_stop(&cap->vdev); + pm_runtime_put(cap->isp->dev); +} + +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), "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 | 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; + 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_vb2_buffer); + vb2_q->dev = cap->isp->dev; + vb2_q->lock = &cap->lock; + vb2_q->min_queued_buffers = 2; + + ret = vb2_queue_init(vb2_q); + if (ret < 0) + goto err_destroy; + + cap->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &cap->pad); + if (ret < 0) + goto err_queue_release; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + 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->vfmt.fmt.pix.width = C3_ISP_DEFAULT_WIDTH; + cap->vfmt.fmt.pix.height = C3_ISP_DEFAULT_HEIGHT; + cap->vfmt.fmt.pix.field = V4L2_FIELD_NONE; + cap->vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21; + cap->vfmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + + c3_cap_try_fmt(cap, &cap->vfmt.fmt.pix); + + cap->id = i; + if (cap->id == C3_ISP_CAP_DEV_0) + cap->rsz = &isp->resizers[C3_ISP_RSZ_0]; + else if (cap->id == C3_ISP_CAP_DEV_1) + cap->rsz = &isp->resizers[C3_ISP_RSZ_1]; + else + cap->rsz = &isp->resizers[C3_ISP_RSZ_2]; + + 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_done(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..19f2a3bc29c9 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-common.h @@ -0,0 +1,327 @@ +/* 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_CORE_DEF_SINK_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10 +#define C3_ISP_CORE_DEF_SRC_PAD_FMT MEDIA_BUS_FMT_YUYV8_1_5X8 +#define C3_ISP_RSZ_DEF_PAD_FMT MEDIA_BUS_FMT_YUYV8_1_5X8 +#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_DISP_INTER 0x400 +#define C3_DISP_REG(base, id) ((base) + (id) * C3_DISP_INTER) +#define C3_WRMIFX3_INTER 0x100 +#define C3_WRMIFX3_REG(base, id) ((base) + (id) * C3_WRMIFX3_INTER) +#define C3_PPS_TAP4_S11_H_NUM 33 +#define C3_PPS_LUT_CTYPE_0 0 +#define C3_PPS_LUT_CTYPE_2 2 +#define C3_SCALE_EN 1 +#define C3_SCALE_DIS 0 + +#define C3_ISP_PHASE_OFFSET_0 0 +#define C3_ISP_PHASE_OFFSET_1 1 +#define C3_ISP_PHASE_OFFSET_NONE 0xff + +enum c3_isp_core_pads { + C3_ISP_CORE_PAD_SINK_VIDEO, + C3_ISP_CORE_PAD_SINK_PARAMS, + C3_ISP_CORE_PAD_SOURCE_STATS, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + C3_ISP_CORE_PAD_MAX +}; + +enum c3_isp_resizer_ids { + C3_ISP_RSZ_0, + C3_ISP_RSZ_1, + C3_ISP_RSZ_2, + C3_ISP_NUM_RSZ +}; + +enum c3_isp_resizer_pads { + C3_ISP_RESIZER_PAD_SINK, + C3_ISP_RESIZER_PAD_SOURCE, + C3_ISP_RESIZER_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 +}; + +/** + * 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; +}; + +/** + * @mbus_code: the mbus code + * @pads: save the pad flag of this mbus_code + * @xofst: horizontal phase offset of hardware + * @yofst: vertical phase offset of hardware + */ +struct c3_isp_mbus_format_info { + u32 mbus_code; + u32 pads; + u8 xofst; + u8 yofst; +}; + +/** + * @mbus_code: the mbus code + * @fourcc: pixel format + * @depth: pixel width + */ +struct c3_isp_capture_format { + u32 mbus_code; + u32 fourcc; + u8 depth; +}; + +/** + * struct c3_isp_vb2_buffer - A container of vb2 buffer + * + * @vb: vb2 buffer + * @vaddr: buffer virtual address + * @paddr: buffer physical address + * @list: entry of the buffer in the queue + */ +struct c3_isp_vb2_buffer { + struct vb2_v4l2_buffer vb; + void *vaddr; + dma_addr_t paddr; + struct list_head list; +}; + +/** + * struct c3_isp_core - ISP core subdev + * + * @sd: ISP sub-device + * @pads: ISP sub-device pads + * @src_sd: source sub-device + * @isp: pointer to c3_isp_device + * @src_sd_pad: source sub-device pad + */ +struct c3_isp_core { + struct v4l2_subdev sd; + struct media_pad pads[C3_ISP_CORE_PAD_MAX]; + struct v4l2_subdev *src_sd; + u16 src_sd_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 + * @isp: pointer to c3_isp_device + * @cap: pointer to c3_isp_capture + */ +struct c3_isp_resizer { + enum c3_isp_resizer_ids id; + struct v4l2_subdev sd; + struct media_pad pads[C3_ISP_RESIZER_PAD_MAX]; + struct c3_isp_device *isp; + struct c3_isp_capture *cap; +}; + +/** + * 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 + * @is_streaming: stats status + * @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 */ + bool is_streaming; + struct c3_isp_device *isp; + + struct c3_isp_vb2_buffer *buff; + spinlock_t buff_lock; /* Protects stream 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_vb2_buffer *buff; + spinlock_t buff_lock; /* Protects stream buffer */ + struct list_head pending; +}; + +/** + * struct c3_isp_capture - ISP capture device + * + * @id: capture device ID + * @vb2_q: vb2 buffer queue + * @vdev: video node + * @vfmt: v4l2_format of the capture format + * @pad: media pad + * @lock: protects vb2_q, vdev + * @is_streaming: capture device status + * @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 + */ +struct c3_isp_capture { + enum c3_isp_cap_devs id; + struct vb2_queue vb2_q; + struct video_device vdev; + struct v4l2_format vfmt; + struct media_pad pad; + + struct mutex lock; /* Protects vb2_q, vdev */ + bool is_streaming; + struct c3_isp_device *isp; + struct c3_isp_resizer *rsz; + + struct c3_isp_vb2_buffer *buff; + spinlock_t buff_lock; /* Protects stream buffer */ + struct list_head pending; +}; + +/** + * struct c3_isp_info - ISP information + * + * @clocks: array of ISP clock names + * @clock_rates: array of ISP clock rate + * @clock_num: actual clock number + */ +struct c3_isp_info { + char *clocks[C3_ISP_CLOCK_NUM_MAX]; + u32 clock_rates[C3_ISP_CLOCK_NUM_MAX]; + u32 clock_num; +}; + +/** + * struct c3_isp_device - ISP platform device + * + * @dev: pointer to the struct device + * @base: base register address + * @clks: array of clocks + * @notifier: notifier to register on the v4l2-async API + * @v4l2_dev: v4l2_device variable + * @media_dev: media device variable + * @pipe: media pipeline + * @core: ISP core subdev + * @resizer: ISP resizer subdev + * @stats: ISP stats device + * @params: ISP params device + * @caps: array of ISP capture device + * @frm_sequence: used to record frame id + * @lock: protect ISP device + * @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; + struct mutex lock; /* Protect ISP device */ + 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); +bool c3_isp_pipeline_ready(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); +void c3_isp_rsz_start(struct c3_isp_resizer *rsz); +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz); +int c3_isp_captures_register(struct c3_isp_device *isp); +void c3_isp_captures_unregister(struct c3_isp_device *isp); +void c3_isp_captures_done(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); +int c3_isp_stats_done(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); +int c3_isp_params_done(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..d3672aff9fd2 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-core.c @@ -0,0 +1,675 @@ +// 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" +#include "include/uapi/c3-isp-config.h" + +#define C3_ISP_CORE_SUBDEV_NAME "isp-core" + +static const struct c3_isp_mbus_format_info c3_isp_core_mbus_formats[] = { + /* RAW formats */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_1, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_0, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pads = BIT(C3_ISP_CORE_PAD_SINK_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_1, + .yofst = C3_ISP_PHASE_OFFSET_0, + }, + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pads = BIT(C3_ISP_CORE_PAD_SOURCE_VIDEO), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, +}; + +static const struct c3_isp_mbus_format_info +*core_find_format_by_code(u32 code, u32 pad) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) { + const struct c3_isp_mbus_format_info *info = + &c3_isp_core_mbus_formats[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +static const struct c3_isp_mbus_format_info +*core_find_format_by_index(u32 index, u32 pad) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(c3_isp_core_mbus_formats); i++) { + const struct c3_isp_mbus_format_info *info = + &c3_isp_core_mbus_formats[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) +{ + /* Select the line sync signal */ + c3_isp_update_bits(isp, ISP_TOP_IRQ_LINE_THRD, + TOP_IRQ_DIN_HSYNC, TOP_IRQ_DIN_HSYNC); + + /* Enable frame done and stats error irq */ + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, + TOP_IRQ_FRAME_DONE, TOP_IRQ_FRAME_DONE); + c3_isp_update_bits(isp, ISP_TOP_IRQ_EN, + TOP_IRQ_STATS_ERR, TOP_IRQ_STATS_ERR); + + /* Enable image data to ISP core */ + c3_isp_update_bits(isp, ISP_TOP_PATH_SEL, + TOP_DATA_PATH_MASK, TOP_PATH_MIPI_TO_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, + TOP_DATA_PATH_MASK, 0x0); + + /* Disable all irq */ + c3_isp_write(isp, ISP_TOP_IRQ_EN, 0x0); +} + +/* 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, + LSWB_BLC_XPHS_OFST_MASK, + xofst << LSWB_BLC_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_LSWB_BLC_PHSOFST, + LSWB_BLC_YPHS_OFST_MASK, yofst); + + c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, + LSWB_WB_XPHS_OFST_MASK, + xofst << LSWB_WB_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_LSWB_WB_PHSOFST, + LSWB_WB_YPHS_OFST_MASK, yofst); + + c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, + LSWB_LNS_XPHS_OFST_MASK, + xofst << LSWB_LNS_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_LSWB_LNS_PHSOFST, + LSWB_LNS_YPHS_OFST_MASK, 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, AF_CTRL_XPHS_OFST_MASK, + xofst << AF_CTRL_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_AF_CTRL, AF_CTRL_YPHS_OFST_MASK, + yofst << AF_CTRL_YPHS_OFST_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_XPHS_OFST_MASK, + xofst << AE_CTRL_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_AE_CTRL, AE_CTRL_YPHS_OFST_MASK, + yofst << AE_CTRL_YPHS_OFST_SHIFT); + + c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_XPHS_OFST_MASK, + xofst << AWB_CTRL_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_AWB_CTRL, AWB_CTRL_YPHS_OFST_MASK, 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, DMS_COMMON_XPHS_OFST_MASK, + xofst << DMS_COMMON_XPHS_OFST_SHIFT); + c3_isp_update_bits(isp, ISP_DMS_COMMON_PARAM0, DMS_COMMON_YPHS_OFST_MASK, yofst); +} + +static void c3_isp_core_cfg_ofst(struct c3_isp_device *isp, + struct v4l2_mbus_framefmt *fmt) +{ + const struct c3_isp_mbus_format_info *isp_fmt = + core_find_format_by_code(fmt->code, C3_ISP_CORE_PAD_SINK_VIDEO); + + 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); +} + +/* Set format of the hardware control module */ +static void c3_isp_core_top_fmt(struct c3_isp_device *isp, + struct v4l2_mbus_framefmt *fmt) +{ + c3_isp_write(isp, ISP_TOP_INPUT_SIZE, + TOP_INPUT_HSIZE(fmt->width) | TOP_INPUT_VSIZE(fmt->height)); + + c3_isp_write(isp, ISP_TOP_FRM_SIZE, + TOP_FRM_CORE_IHSIZE(fmt->width) | TOP_FRM_CORE_IVSIZE(fmt->height)); + + c3_isp_update_bits(isp, ISP_TOP_HOLD_SIZE, TOP_HOLD_HSIZE_MASK, + fmt->width << TOP_HOLD_HSIZE_SHIFT); +} + +static void c3_isp_core_af_fmt(struct c3_isp_device *isp, + struct v4l2_mbus_framefmt *fmt) +{ + u32 hidx; + u32 vidx; + int i; + + c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, AF_HV_STAT_HBLK_NUM_MASK, + AF_STAT_BLKH_NUM << AF_HV_STAT_HBLK_NUM_SHIFT); + + c3_isp_update_bits(isp, ISP_AF_HV_BLKNUM, + AF_HV_STAT_VBLK_NUM_MASK, AF_STAT_BLKV_NUM); + + c3_isp_write(isp, ISP_AF_HV_SIZE, + AF_HV_STAT_HSIZE(fmt->width) | AF_HV_STAT_VSIZE(fmt->height)); + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AF_IDX_ADDR, 0); + /* + * Calculate and set the coordinates of points in the grid. + * hidx and vidx need to be aligned with 2. + */ + for (i = 0; i <= AF_STAT_BLKH_NUM; i++) { + hidx = i * fmt->width / AF_STAT_BLKH_NUM; + hidx = ALIGN_DOWN(hidx, 2); + + vidx = i * fmt->height / AF_STAT_BLKV_NUM; + vidx = min(vidx, fmt->height); + vidx = ALIGN_DOWN(vidx, 2); + c3_isp_write(isp, ISP_AF_IDX_DATA, + AF_IDX_HIDX_DATA(hidx) | AF_IDX_VIDX_DATA(vidx)); + } +} + +static void c3_isp_core_ae_fmt(struct c3_isp_device *isp, + struct v4l2_mbus_framefmt *fmt) +{ + u32 hidx; + u32 vidx; + int i; + + c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, AE_HV_STAT_HBLK_NUM_MASK, + AE_STAT_BLKH_NUM << AE_HV_STAT_HBLK_NUM_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_HV_BLKNUM, + AE_HV_STAT_VBLK_NUM_MASK, AE_STAT_BLKV_NUM); + + c3_isp_write(isp, ISP_AE_HV_SIZE, + AE_HV_STAT_HSIZE(fmt->width) | AE_HV_STAT_VSIZE(fmt->height)); + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AE_IDX_ADDR, 0); + /* + * Calculate and set the coordinates of points in the grid. + * hidx and vidx need to be aligned with 2. + */ + for (i = 0; i <= AE_STAT_BLKH_NUM; i++) { + hidx = i * fmt->width / AE_STAT_BLKH_NUM; + hidx = ALIGN_DOWN(hidx, 2); + + vidx = i * fmt->height / AE_STAT_BLKV_NUM; + vidx = min(vidx, fmt->height); + vidx = ALIGN_DOWN(vidx, 2); + + c3_isp_write(isp, ISP_AE_IDX_DATA, + AE_IDX_HIDX_DATA(hidx) | AE_IDX_VIDX_DATA(vidx)); + } +} + +static void c3_isp_core_awb_fmt(struct c3_isp_device *isp, + struct v4l2_mbus_framefmt *fmt) +{ + u32 hidx; + u32 vidx; + int i; + + c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, AWB_HV_STAT_HBLK_NUM_MASK, + AWB_STAT_BLKH_NUM << AWB_HV_STAT_HBLK_NUM_SHIFT); + c3_isp_update_bits(isp, ISP_AWB_HV_BLKNUM, + AWB_HV_STAT_VBLK_NUM_MASK, AWB_STAT_BLKV_NUM); + + c3_isp_write(isp, ISP_AWB_HV_SIZE, + AWB_HV_STAT_HSIZE(fmt->width) | AWB_HV_STAT_VSIZE(fmt->height)); + + /* Set the index address to 0 position */ + c3_isp_write(isp, ISP_AWB_IDX_ADDR, 0); + /* + * Calculate and set the coordinates of points in the grid. + * hidx and vidx need to be aligned with 2. + */ + for (i = 0; i <= AWB_STAT_BLKH_NUM; i++) { + hidx = i * fmt->width / AWB_STAT_BLKH_NUM; + hidx = ALIGN_DOWN(hidx, 2); + + vidx = i * fmt->height / AWB_STAT_BLKV_NUM; + vidx = min(vidx, fmt->height); + vidx = ALIGN_DOWN(vidx, 2); + + c3_isp_write(isp, ISP_AWB_IDX_DATA, + AWB_IDX_HIDX_DATA(hidx) | AWB_IDX_VIDX_DATA(vidx)); + } +} + +static void c3_isp_core_cfg_format(struct c3_isp_device *isp, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO); + + c3_isp_core_cfg_ofst(isp, fmt); + c3_isp_core_top_fmt(isp, fmt); + c3_isp_core_af_fmt(isp, fmt); + c3_isp_core_ae_fmt(isp, fmt); + c3_isp_core_awb_fmt(isp, fmt); +} + +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); + u64 sink_streams; + int ret; + + core->isp->frm_sequence = 0; + c3_isp_core_cfg_format(core->isp, state); + c3_isp_core_enable(core->isp); + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + C3_ISP_CORE_PAD_SINK_VIDEO, + &streams_mask); + ret = v4l2_subdev_enable_streams(core->src_sd, + core->src_sd_pad, sink_streams); + 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); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, pad, + C3_ISP_CORE_PAD_SINK_VIDEO, + &streams_mask); + ret = v4l2_subdev_disable_streams(core->src_sd, + core->src_sd_pad, sink_streams); + if (ret) + return ret; + + c3_isp_core_disable(core->isp); + + return 0; +} + +static int c3_isp_core_cfg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = C3_ISP_DEFAULT_WIDTH, + .height = C3_ISP_DEFAULT_HEIGHT, + .code = C3_ISP_CORE_DEF_SRC_PAD_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_NO_SOURCE_STREAM_MIX); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int c3_isp_core_init_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[2]; + struct v4l2_subdev_krouting routing; + + routes[0].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO; + routes[0].sink_stream = 0; + routes[0].source_pad = C3_ISP_CORE_PAD_SOURCE_STATS; + routes[0].source_stream = 0; + routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routes[1].sink_pad = C3_ISP_CORE_PAD_SINK_VIDEO; + routes[1].sink_stream = 0; + routes[1].source_pad = C3_ISP_CORE_PAD_SOURCE_VIDEO; + routes[1].source_stream = 0; + routes[1].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routing.num_routes = ARRAY_SIZE(routes); + routing.routes = routes; + + return c3_isp_core_cfg_routing(sd, state, &routing); +} + +static int c3_isp_core_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + bool is_streaming = v4l2_subdev_is_streaming(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming) + return -EBUSY; + + return c3_isp_core_cfg_routing(sd, state, routing); +} + +static int c3_isp_core_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct c3_isp_mbus_format_info *info; + int ret = 0; + + switch (code->pad) { + case C3_ISP_CORE_PAD_SINK_VIDEO: + case C3_ISP_CORE_PAD_SOURCE_VIDEO: + info = core_find_format_by_index(code->index, code->pad); + if (!info) + ret = -EINVAL; + else + code->code = info->mbus_code; + + break; + case C3_ISP_CORE_PAD_SINK_PARAMS: + case C3_ISP_CORE_PAD_SOURCE_STATS: + if (code->index) + ret = -EINVAL; + else + code->code = MEDIA_BUS_FMT_METADATA_FIXED; + + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void c3_isp_core_set_sink_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *sink_fmt; + const struct c3_isp_mbus_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); + + format->format = *sink_fmt; +} + +static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + const struct c3_isp_mbus_format_info *isp_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_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; + + /* The source size must be same with the sink size. */ + src_fmt->width = sink_fmt->width; + src_fmt->height = sink_fmt->height; + + 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) +{ + struct v4l2_mbus_framefmt *fmt; + + switch (format->pad) { + case C3_ISP_CORE_PAD_SINK_VIDEO: + c3_isp_core_set_sink_fmt(state, format); + break; + case C3_ISP_CORE_PAD_SINK_PARAMS: + case C3_ISP_CORE_PAD_SOURCE_STATS: + fmt = v4l2_subdev_state_get_format(state, format->pad); + format->format = *fmt; + break; + case C3_ISP_CORE_PAD_SOURCE_VIDEO: + c3_isp_core_set_source_fmt(state, format); + break; + default: + dev_err(sd->dev, "Invalid pad: %u\n", format->pad); + return -ENOTTY; + } + + return 0; +} + +static int c3_isp_core_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + + /* Video sink pad */ + sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_VIDEO); + sink_fmt->width = C3_ISP_DEFAULT_WIDTH; + sink_fmt->height = C3_ISP_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = C3_ISP_CORE_DEF_SINK_PAD_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; + + /* Video source pad */ + src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_VIDEO); + src_fmt->width = C3_ISP_DEFAULT_WIDTH; + src_fmt->height = C3_ISP_DEFAULT_HEIGHT; + src_fmt->field = V4L2_FIELD_NONE; + src_fmt->code = C3_ISP_CORE_DEF_SRC_PAD_FMT; + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + /* Parameters pad */ + sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SINK_PARAMS); + sink_fmt->width = 0; + sink_fmt->height = 0; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + /* Statistics pad */ + src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_CORE_PAD_SOURCE_STATS); + src_fmt->width = 0; + src_fmt->height = 0; + src_fmt->field = V4L2_FIELD_NONE; + src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + return c3_isp_core_init_routing(sd, state); +} + +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, + .set_routing = c3_isp_core_set_routing, +}; + +static const struct v4l2_subdev_ops c3_isp_core_subdev_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, +}; + +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; + sd->internal_ops = &c3_isp_core_internal_ops; + snprintf(sd->name, sizeof(sd->name), "%s", C3_ISP_CORE_SUBDEV_NAME); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &c3_isp_core_entity_ops; + + core->isp = isp; + sd->dev = isp->dev; + v4l2_set_subdevdata(sd, core); + + core->pads[C3_ISP_CORE_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK; + core->pads[C3_ISP_CORE_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK; + core->pads[C3_ISP_CORE_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE; + core->pads[C3_ISP_CORE_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_ISP_CORE_PAD_MAX, core->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_device_register_subdev(&isp->v4l2_dev, sd); + if (ret) + goto err_subdev_cleanup; + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(sd); +err_entity_cleanup: + media_entity_cleanup(&sd->entity); + return ret; +} + +void c3_isp_core_unregister(struct c3_isp_device *isp) +{ + struct c3_isp_core *core = &isp->core; + struct v4l2_subdev *sd = &core->sd; + + v4l2_device_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); +} diff --git a/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c new file mode 100644 index 000000000000..a57b9f8dbc3c --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-dev.c @@ -0,0 +1,486 @@ +// 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); +} + +bool c3_isp_pipeline_ready(struct c3_isp_device *isp) +{ + struct media_pipeline_entity_iter iter; + unsigned int n_video_devices = 0; + struct media_entity *entity; + int ret; + + ret = media_pipeline_entity_iter_init(&isp->pipe, &iter); + if (ret) + return ret; + + media_pipeline_for_each_entity(&isp->pipe, &iter, entity) { + if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE) + n_video_devices++; + } + + media_pipeline_entity_iter_cleanup(&iter); + + return n_video_devices == isp->pipe.start_count; +} + +/* PM runtime suspend */ +static int __maybe_unused 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 __maybe_unused 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 = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_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 & TOP_IRQ_FRAME_DONE) { + c3_isp_stats_done(isp); + c3_isp_params_done(isp); + c3_isp_captures_done(isp); + isp->frm_sequence++; + } + + if (status & TOP_IRQ_STATS_ERR) + dev_dbg(isp->dev, "ISP IRQ Stats DMA Error\n"); + + 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 c3_isp_core *core = &isp->core; + struct media_pad *sink = &core->sd.entity.pads[C3_ISP_CORE_PAD_SINK_VIDEO]; + int ret; + + ret = media_entity_get_fwnode_pad(&sd->entity, + sd->fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(isp->dev, "Failed to find pad for %s\n", sd->name); + return ret; + } + + core->src_sd = sd; + core->src_sd_pad = ret; + + 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); + int ret; + + ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev); + if (ret < 0) { + dev_err(isp->dev, + "Failed to register subdev nodes: %d\n", ret); + return ret; + } + + dev_info(isp->dev, "notify complete\n"); + + return media_device_register(&isp->media_dev); +} + +static void c3_isp_notify_destroy(struct v4l2_async_connection *asc) +{ + struct c3_isp_device *isp = + container_of(asc->notifier, struct c3_isp_device, notifier); + + media_device_unregister(&isp->media_dev); +} + +static const struct v4l2_async_notifier_operations c3_isp_notify_ops = { + .bound = c3_isp_notify_bound, + .complete = c3_isp_notify_complete, + .destroy = c3_isp_notify_destroy, +}; + +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); + if (IS_ERR(asc)) { + fwnode_handle_put(ep); + return PTR_ERR(asc); + } + + fwnode_handle_put(ep); + + 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_v4l2_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) { + media_device_cleanup(media_dev); + dev_err(isp->dev, + "Failed to register V4L2 device: %d\n", ret); + } + + return ret; +} + +static void c3_isp_v4l2_unregister(struct c3_isp_device *isp) +{ + 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_RESIZER_PAD_SOURCE, + &isp->resizers[i].cap->vdev.entity, + 0, MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(isp->dev, "Failed to link resizer %u and capture %u\n", i, i); + goto err_remove_links; + } + + ret = media_create_pad_link(&isp->core.sd.entity, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + &isp->resizers[i].sd.entity, + C3_ISP_RESIZER_PAD_SINK, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(isp->dev, "Failed to link core and resizer %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_cfg_clocks(struct c3_isp_device *isp) +{ + const struct c3_isp_info *info = isp->info; + int ret; + u32 i; + + for (i = 0; i < info->clock_num; i++) + isp->clks[i].id = info->clocks[i]; + + ret = devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks); + if (ret) + return ret; + + for (i = 0; i < info->clock_num; i++) { + if (!info->clock_rates[i]) + continue; + ret = clk_set_rate(isp->clks[i].clk, info->clock_rates[i]); + if (ret) { + dev_err(isp->dev, "Failed to set %s rate %u\n", info->clocks[i], + info->clock_rates[i]); + return ret; + } + } + + return 0; +} + +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_cfg_clocks(isp); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure clocks\n"); + + platform_set_drvdata(pdev, isp); + + pm_runtime_enable(dev); + + ret = c3_isp_v4l2_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 = c3_isp_videos_register(isp); + if (ret) + goto err_nf_unregister; + + ret = devm_request_irq(dev, irq, + c3_isp_irq_handler, IRQF_SHARED, + dev_driver_string(dev), isp); + if (ret) + goto err_streams_unregister; + + mutex_init(&isp->lock); + + return 0; + +err_streams_unregister: + c3_isp_videos_unregister(isp); +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_v4l2_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); + + mutex_destroy(&isp->lock); + c3_isp_videos_unregister(isp); + c3_isp_async_nf_unregister(isp); + c3_isp_core_unregister(isp); + c3_isp_resizers_unregister(isp); + c3_isp_v4l2_unregister(isp); + pm_runtime_disable(isp->dev); +}; + +static const struct c3_isp_info isp_info = { + .clocks = {"vapb", "isp0"}, + .clock_rates = {0, 400000000}, + .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 = &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..8a6b7ce86eaf --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-params.c @@ -0,0 +1,857 @@ +// 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" +#include "include/uapi/c3-isp-config.h" + +typedef void (*block_handler)(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block); + +struct c3_isp_block_handler { + size_t size; + block_handler handler; +}; + +/* Hardware configuration */ + +static void c3_isp_params_cfg_wb_change(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct wb_change_cfg *wb = (struct wb_change_cfg *)block; + + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + TOP_BEO_CTRL_WB_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, + TOP_BEO_CTRL_WB_EN, TOP_BEO_CTRL_WB_EN); + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, LSWB_WB_GAIN0_MASK, + wb->wb_gain[0] << LSWB_WB_GAIN0_SHIFT); + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN0, + LSWB_WB_GAIN1_MASK, wb->wb_gain[1]); + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, LSWB_WB_GAIN2_MASK, + wb->wb_gain[2] << LSWB_WB_GAIN2_SHIFT); + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN1, + LSWB_WB_GAIN3_MASK, wb->wb_gain[3]); + + c3_isp_update_bits(isp, ISP_LSWB_WB_GAIN2, + LSWB_WB_GAIN4_MASK, wb->wb_gain[4]); + + c3_isp_write(isp, ISP_LSWB_WB_LIMIT0, + LSWB_WB_LIMIT0(wb->wb_limit[0]) | LSWB_WB_LIMIT1(wb->wb_limit[1])); + + c3_isp_write(isp, ISP_LSWB_WB_LIMIT1, + LSWB_WB_LIMIT2(wb->wb_limit[2]) | LSWB_WB_LIMIT3(wb->wb_limit[3])); + + c3_isp_update_bits(isp, ISP_LSWB_WB_LIMIT2, LSWB_WB_LIMIT4_MASK, + wb->wb_limit[4]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_BL12_GRBGI0_MASK, + wb->ae_bl12_grbgi[0]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_0, AE_CRTL2_GAIN_GRBGI0_MASK, + wb->ae_gain_grbgi[0] << AE_CRTL2_GAIN_GRBGI0_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_BL12_GRBGI1_MASK, + wb->ae_bl12_grbgi[1]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_1, AE_CRTL2_GAIN_GRBGI1_MASK, + wb->ae_gain_grbgi[1] << AE_CRTL2_GAIN_GRBGI1_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_BL12_GRBGI2_MASK, + wb->ae_bl12_grbgi[2]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_2, AE_CRTL2_GAIN_GRBGI2_MASK, + wb->ae_gain_grbgi[2] << AE_CRTL2_GAIN_GRBGI2_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_BL12_GRBGI3_MASK, + wb->ae_bl12_grbgi[3]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_3, AE_CRTL2_GAIN_GRBGI3_MASK, + wb->ae_gain_grbgi[3] << AE_CRTL2_GAIN_GRBGI3_SHIFT); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_BL12_GRBGI4_MASK, + wb->ae_bl12_grbgi[4]); + + c3_isp_update_bits(isp, ISP_AE_CRTL2_4, AE_CRTL2_GAIN_GRBGI4_MASK, + wb->ae_gain_grbgi[4] << AE_CRTL2_GAIN_GRBGI4_SHIFT); +} + +static void c3_isp_params_cfg_wb_luma(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct wb_luma_cfg *wb_luma = (struct wb_luma_cfg *)block; + + if (!block->enabled) + return; + + c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_0, + AWB_STAT_BLC20_GR_MASK, wb_luma->awb_stat_blc20[0]); + c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_1, + AWB_STAT_BLC20_R_MASK, wb_luma->awb_stat_blc20[1]); + c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_2, + AWB_STAT_BLC20_B_MASK, wb_luma->awb_stat_blc20[2]); + c3_isp_update_bits(isp, ISP_AWB_STAT_BLC20_3, + AWB_STAT_BLC20_GB_MASK, wb_luma->awb_stat_blc20[3]); + + c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_0, + AWB_STAT_GAIN10_GR_MASK, wb_luma->awb_stat_gain10[0]); + c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_1, + AWB_STAT_GAIN10_R_MASK, wb_luma->awb_stat_gain10[1]); + c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_2, + AWB_STAT_GAIN10_B_MASK, wb_luma->awb_stat_gain10[2]); + c3_isp_update_bits(isp, ISP_AWB_STAT_GAIN10_3, + AWB_STAT_GAIN10_GB_MASK, wb_luma->awb_stat_gain10[3]); + + c3_isp_write(isp, ISP_AWB_STAT_SATUR_CTRL, + AWB_STAT_SATUR_LOW(wb_luma->awb_stat_satur_low) | + AWB_STAT_SATUR_HIGH(wb_luma->awb_stat_satur_high)); +} + +static void c3_isp_params_cfg_wb_triangle(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct wb_triangle_cfg *wb = (struct wb_triangle_cfg *)block; + + if (!block->enabled) + return; + + c3_isp_update_bits(isp, ISP_AWB_STAT_CTRL2, AWB_STAT_SATUR_VALID_MASK, + wb->awb_stat_satur_vald); + + c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MIN_MASK, + wb->awb_stat_rg_min); + c3_isp_update_bits(isp, ISP_AWB_STAT_RG, AWB_STAT_RG_MAX_MASK, + wb->awb_stat_rg_max << AWB_STAT_RG_MAX_SHIFT); + + c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MIN_MASK, + wb->awb_stat_bg_min); + c3_isp_update_bits(isp, ISP_AWB_STAT_BG, AWB_STAT_BG_MAX_MASK, + wb->awb_stat_bg_max << AWB_STAT_BG_MAX_SHIFT); + + c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_LOW_MASK, + wb->awb_stat_rg_low); + c3_isp_update_bits(isp, ISP_AWB_STAT_RG_HL, AWB_STAT_RG_HIGH_MASK, + wb->awb_stat_rg_high << AWB_STAT_RG_HIGH_SHIFT); + + c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_LOW_MASK, + wb->awb_stat_bg_low); + c3_isp_update_bits(isp, ISP_AWB_STAT_BG_HL, AWB_STAT_BG_HIGH_MASK, + wb->awb_stat_bg_high << AWB_STAT_BG_HIGH_SHIFT); +} + +static void c3_isp_params_cfg_awb_stats(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct awb_stats_cfg *awb_stats = (struct awb_stats_cfg *)block; + u32 *weight = awb_stats->awb_stat_blk_weight; + int idx_base; + int group; + int i; + + if (!block->enabled) + return; + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AWB_STAT_SWITCH_MASK, + awb_stats->awb_stat_switch << TOP_3A_AWB_STAT_SWITCH_SHIFT); + + /* Calculate the group number */ + group = AWB_BLOCK_WT_NUM / AWB_BLK_WT_DATA_NUM_OF_GROUP; + + /* Set the weight address to 0 position */ + c3_isp_write(isp, ISP_AWB_BLK_WT_ADDR, 0); + for (i = 0; i < group; i++) { + idx_base = i * AWB_BLK_WT_DATA_NUM_OF_GROUP; + c3_isp_write(isp, ISP_AWB_BLK_WT_DATA, + AWB_BLK_WT_DATA0(weight[idx_base + 0]) | + AWB_BLK_WT_DATA1(weight[idx_base + 1]) | + AWB_BLK_WT_DATA2(weight[idx_base + 2]) | + AWB_BLK_WT_DATA3(weight[idx_base + 3]) | + AWB_BLK_WT_DATA4(weight[idx_base + 4]) | + AWB_BLK_WT_DATA5(weight[idx_base + 5]) | + AWB_BLK_WT_DATA6(weight[idx_base + 6]) | + AWB_BLK_WT_DATA7(weight[idx_base + 7])); + } +} + +static void c3_isp_params_cfg_ae_stats(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct ae_stats_cfg *ae_stats = (struct ae_stats_cfg *)block; + u32 *weight = ae_stats->ae_stat_blk_weight; + int idx_base; + int group; + int i; + + if (!block->enabled) + return; + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AE_STAT_SWITCH_MASK, + ae_stats->ae_stat_switch << TOP_3A_AE_STAT_SWITCH_SHIFT); + + /* Calculate the group number */ + group = AE_BLOCK_WT_NUM / AE_BLK_WT_DATA_NUM_OF_GROUP; + + /* Set the weight address to 0 position */ + c3_isp_write(isp, ISP_AE_BLK_WT_ADDR, 0); + for (i = 0; i < group; i++) { + idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP; + c3_isp_write(isp, ISP_AE_BLK_WT_DATA, + AE_BLK_WT_DATA0(weight[idx_base + 0]) | + AE_BLK_WT_DATA1(weight[idx_base + 1]) | + AE_BLK_WT_DATA2(weight[idx_base + 2]) | + AE_BLK_WT_DATA3(weight[idx_base + 3]) | + AE_BLK_WT_DATA4(weight[idx_base + 4]) | + AE_BLK_WT_DATA5(weight[idx_base + 5]) | + AE_BLK_WT_DATA6(weight[idx_base + 6]) | + AE_BLK_WT_DATA7(weight[idx_base + 7])); + } + + /* Write the last weight data */ + idx_base = i * AE_BLK_WT_DATA_NUM_OF_GROUP; + c3_isp_write(isp, ISP_AE_BLK_WT_DATA, + AE_BLK_WT_DATA0(weight[idx_base + 0]) | + AE_BLK_WT_DATA1(weight[idx_base + 1]) | + AE_BLK_WT_DATA2(weight[idx_base + 2]) | + AE_BLK_WT_DATA3(weight[idx_base + 3]) | + AE_BLK_WT_DATA4(weight[idx_base + 4]) | + AE_BLK_WT_DATA5(weight[idx_base + 5]) | + AE_BLK_WT_DATA6(weight[idx_base + 6])); +} + +static void c3_isp_params_cfg_af_stats(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct af_stats_cfg *af_stats = (struct af_stats_cfg *)block; + + if (!block->enabled) + return; + + c3_isp_update_bits(isp, ISP_TOP_3A_STAT_CRTL, TOP_3A_AF_STAT_SWITCH_MASK, + af_stats->af_stat_switch << TOP_3A_AF_STAT_SWITCH_SHIFT); +} + +static void c3_isp_params_cfg_pst_gamma(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct pst_gamma_cfg *gamma = (struct pst_gamma_cfg *)block; + int idx_base; + int i, j; + + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_GAMMA_EN, TOP_BED_GAMMA_EN); + + for (j = 0; j < GAMMA_LUT_GROUP_NUM; j++) { + c3_isp_write(isp, ISP_PST_GAMMA_LUT_ADDR, PST_GAMMA_LUT_ADDR(j)); + + /* Calculate the block number */ + for (i = 0; i < GAMMA_LUT_POINT_NUM / GAMMA_LUT_DATA_NUM_OF_GROUP; i++) { + idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP; + c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base]) | + PST_GAMMA_LUT_DATA1(gamma->pst_gamma_lut[j][idx_base + 1])); + } + + /* Write the last one lut data of group j */ + idx_base = i * GAMMA_LUT_DATA_NUM_OF_GROUP; + c3_isp_write(isp, ISP_PST_GAMMA_LUT_DATA, + PST_GAMMA_LUT_DATA0(gamma->pst_gamma_lut[j][idx_base])); + } +} + +static void c3_isp_params_cfg_dmsc(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_DMSC_EN, TOP_BED_DMSC_EN); +} + +/* Configure 4 x 3 ccm matrix */ +static void c3_isp_params_cfg_ccm(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct ccm_cfg *ccm = (struct ccm_cfg *)block; + + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CCM_EN, TOP_BED_CCM_EN); + + c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_00_MASK, + ccm->ccm_4x3matrix[0][0]); + c3_isp_update_bits(isp, ISP_CCM_MTX_00_01, CCM_MTX_01_MASK, + ccm->ccm_4x3matrix[0][1] << CCM_MTX_01_SHIFT); + + c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_02_MASK, + ccm->ccm_4x3matrix[0][2]); + c3_isp_update_bits(isp, ISP_CCM_MTX_02_03, CCM_MTX_03_MASK, + ccm->ccm_4x3matrix[0][3] << CCM_MTX_03_SHIFT); + + c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_10_MASK, + ccm->ccm_4x3matrix[1][0]); + c3_isp_update_bits(isp, ISP_CCM_MTX_10_11, CCM_MTX_11_MASK, + ccm->ccm_4x3matrix[1][1] << CCM_MTX_11_SHIFT); + + c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_12_MASK, + ccm->ccm_4x3matrix[1][2]); + c3_isp_update_bits(isp, ISP_CCM_MTX_12_13, CCM_MTX_13_MASK, + ccm->ccm_4x3matrix[1][3] << CCM_MTX_13_SHIFT); + + c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_20_MASK, + ccm->ccm_4x3matrix[2][0]); + c3_isp_update_bits(isp, ISP_CCM_MTX_20_21, CCM_MTX_21_MASK, + ccm->ccm_4x3matrix[2][1] << CCM_MTX_21_SHIFT); + + c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_22_MASK, + ccm->ccm_4x3matrix[2][2]); + c3_isp_update_bits(isp, ISP_CCM_MTX_22_23_RS, CCM_MTX_23_MASK, + ccm->ccm_4x3matrix[2][3] << CCM_MTX_23_SHIFT); +} + +/* Configure color space conversion matrix parameters */ +static void c3_isp_params_cfg_csc(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct csc_cfg *csc = (struct csc_cfg *)block; + + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BED_CTRL, TOP_BED_CM0_EN, TOP_BED_CM0_EN); + + c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST0_MASK, + csc->cm0_offset_inp[0]); + c3_isp_update_bits(isp, ISP_CM0_INP_OFST01, CM0_INP_OFST1_MASK, + csc->cm0_offset_inp[1] << CM0_INP_OFST1_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_INP_OFST2, CM0_INP_OFST2_MASK, + csc->cm0_offset_inp[2]); + + c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_00_MASK, + csc->cm0_3x3matrix[0][0]); + c3_isp_update_bits(isp, ISP_CM0_COEF00_01, CM0_MTX_01_MASK, + csc->cm0_3x3matrix[0][1] << CM0_MTX_01_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_02_MASK, + csc->cm0_3x3matrix[0][2]); + c3_isp_update_bits(isp, ISP_CM0_COEF02_10, CM0_MTX_10_MASK, + csc->cm0_3x3matrix[1][0] << CM0_MTX_10_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_11_MASK, + csc->cm0_3x3matrix[1][1]); + c3_isp_update_bits(isp, ISP_CM0_COEF11_12, CM0_MTX_12_MASK, + csc->cm0_3x3matrix[1][2] << CM0_MTX_12_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_20_MASK, + csc->cm0_3x3matrix[2][0]); + c3_isp_update_bits(isp, ISP_CM0_COEF20_21, CM0_MTX_21_MASK, + csc->cm0_3x3matrix[2][1] << CM0_MTX_21_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_MTX_22_MASK, + csc->cm0_3x3matrix[2][2]); + c3_isp_update_bits(isp, ISP_CM0_COEF22_OUP_OFST0, CM0_OFST_OUP0_MASK, + csc->cm0_offset_oup[0] << CM0_OFST_OUP0_SHIFT); + + c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP1_MASK, + csc->cm0_offset_oup[1]); + c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_OFST_OUP2_MASK, + csc->cm0_offset_oup[2] << CM0_OFST_OUP2_SHIFT); + c3_isp_update_bits(isp, ISP_CM0_OUP_OFST12_RS, CM0_MTX_RS_MASK, + csc->cm0_3x3mtrx_rs << CM0_MTX_RS_SHIFT); +} + +/* Set blc offset of each color channel */ +static void c3_isp_params_cfg_blc(struct c3_isp_device *isp, + struct c3_isp_param_block_header *block) +{ + struct blc_cfg *blc = (struct blc_cfg *)block; + + if (!block->enabled) { + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, false); + return; + } + + c3_isp_update_bits(isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_BLC_EN, + TOP_BEO_CTRL_BLC_EN); + + c3_isp_write(isp, ISP_FED_BL_OFST_GR, FED_BL_GR_OFST(blc->fe_bl_ofst[0])); + c3_isp_write(isp, ISP_FED_BL_OFST_R, FED_BL_R_OFST(blc->fe_bl_ofst[1])); + c3_isp_write(isp, ISP_FED_BL_OFST_B, FED_BL_B_OFST(blc->fe_bl_ofst[2])); + c3_isp_write(isp, ISP_FED_BL_OFST_GB, FED_BL_GB_OFST(blc->fe_bl_ofst[3])); + c3_isp_write(isp, ISP_FED_BL_OFST_IR, FED_BL_IR_OFST(blc->fe_bl_ofst[4])); + + c3_isp_write(isp, ISP_LSWB_BLC_OFST0, + LSWB_BLC_OFST0(blc->blc_ofst[0]) | LSWB_BLC_OFST1(blc->blc_ofst[1])); + c3_isp_write(isp, ISP_LSWB_BLC_OFST1, + LSWB_BLC_OFST2(blc->blc_ofst[2]) | LSWB_BLC_OFST3(blc->blc_ofst[3])); + c3_isp_write(isp, ISP_LSWB_BLC_OFST2, LSWB_BLC_OFST4(blc->blc_ofst[4])); +} + +static const struct c3_isp_block_handler c3_isp_block_handlers[] = { + [C3_ISP_PARAM_BLOCK_WB_CHANGE] = { + .size = sizeof(struct wb_change_cfg), + .handler = c3_isp_params_cfg_wb_change, + }, + [C3_ISP_PARAM_BLOCK_WB_LUMA] = { + .size = sizeof(struct wb_luma_cfg), + .handler = c3_isp_params_cfg_wb_luma, + }, + [C3_ISP_PARAM_BLOCK_WB_TRIANGLE] = { + .size = sizeof(struct wb_triangle_cfg), + .handler = c3_isp_params_cfg_wb_triangle, + }, + [C3_ISP_PARAM_BLOCK_AWB_STATS] = { + .size = sizeof(struct awb_stats_cfg), + .handler = c3_isp_params_cfg_awb_stats, + }, + [C3_ISP_PARAM_BLOCK_AE_STATS] = { + .size = sizeof(struct ae_stats_cfg), + .handler = c3_isp_params_cfg_ae_stats, + }, + [C3_ISP_PARAM_BLOCK_AF_STATS] = { + .size = sizeof(struct af_stats_cfg), + .handler = c3_isp_params_cfg_af_stats, + }, + [C3_ISP_PARAM_BLOCK_PST_GAMMA] = { + .size = sizeof(struct pst_gamma_cfg), + .handler = c3_isp_params_cfg_pst_gamma, + }, + [C3_ISP_PARAM_BLOCK_DMSC] = { + .size = sizeof(struct dmsc_cfg), + .handler = c3_isp_params_cfg_dmsc, + }, + [C3_ISP_PARAM_BLOCK_CCM] = { + .size = sizeof(struct ccm_cfg), + .handler = c3_isp_params_cfg_ccm, + }, + [C3_ISP_PARAM_BLOCK_CSC] = { + .size = sizeof(struct csc_cfg), + .handler = c3_isp_params_cfg_csc, + }, + [C3_ISP_PARAM_BLOCK_BLC] = { + .size = sizeof(struct blc_cfg), + .handler = c3_isp_params_cfg_blc, + }, +}; + +static enum vb2_buffer_state +c3_isp_params_cfg_blocks(struct c3_isp_params *params) +{ + struct c3_isp_params_buffer *config = params->buff->vaddr; + enum vb2_buffer_state state = VB2_BUF_STATE_DONE; + size_t block_offset = 0; + size_t max_offset = 0; + + if (config->total_size > C3_ISP_PARAMS_MAX_SIZE) { + dev_dbg(params->isp->dev, "Invalid parameters buffer size %lu\n", + config->total_size); + state = VB2_BUF_STATE_ERROR; + goto err_return_state; + } + + /* Ensure config->data has a full struct c3_isp_param_block_header */ + max_offset = config->total_size - sizeof(struct c3_isp_param_block_header); + + while (block_offset <= max_offset) { + const struct c3_isp_block_handler *block_handler; + struct c3_isp_param_block_header *block; + + block = (struct c3_isp_param_block_header *) + &config->data[block_offset]; + + if (block->type >= C3_ISP_PARAM_BLOCK_SENTINEL) { + dev_dbg(params->isp->dev, "Invalid parameters block type\n"); + state = VB2_BUF_STATE_ERROR; + goto err_return_state; + } + + block_handler = &c3_isp_block_handlers[block->type]; + if (block->size != block_handler->size) { + dev_dbg(params->isp->dev, "Invalid parameters block size\n"); + state = VB2_BUF_STATE_ERROR; + goto err_return_state; + } + + block_handler->handler(params->isp, block); + + block_offset += block->size; + } + +err_return_state: + return state; +} + +/* Initialize ISP pipeline */ +static int c3_isp_params_start(struct c3_isp_params *params) +{ + enum vb2_buffer_state state; + unsigned long flags; + + /* Reset these controllers */ + c3_isp_write(params->isp, ISP_TOP_FEO_CTRL0, TOP_FEO_CTRL0_ALL_DIS); + c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_0, TOP_FEO_CTRL1_0_ALL_DIS); + c3_isp_write(params->isp, ISP_TOP_FEO_CTRL1_1, TOP_FEO_CTRL1_1_ALL_DIS); + c3_isp_write(params->isp, ISP_TOP_FED_CTRL, TOP_FED_CTRL_ALL_DIS); + c3_isp_write(params->isp, ISP_TOP_BEO_CTRL, TOP_BEO_CTRL_ALL_DIS); + c3_isp_write(params->isp, ISP_TOP_BED_CTRL, TOP_BED_CTRL_ALL_DIS); + + spin_lock_irqsave(¶ms->buff_lock, flags); + + /* Only use the first buffer to initialize ISP */ + params->buff = list_first_entry_or_null(¶ms->pending, + struct c3_isp_vb2_buffer, list); + if (!params->buff) { + spin_unlock_irqrestore(¶ms->buff_lock, flags); + return -EINVAL; + } + + state = c3_isp_params_cfg_blocks(params); + + spin_unlock_irqrestore(¶ms->buff_lock, flags); + + return 0; +} + +/* V4L2 video operations */ + +static void c3_isp_params_return_buffers(struct c3_isp_params *params, + enum vb2_buffer_state state) +{ + unsigned long flags; + struct c3_isp_vb2_buffer *buff; + + spin_lock_irqsave(¶ms->buff_lock, flags); + + while (!list_empty(¶ms->pending)) { + buff = list_first_entry(¶ms->pending, + struct c3_isp_vb2_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, state); + } + + spin_unlock_irqrestore(¶ms->buff_lock, flags); +} + +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, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +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_buffer)) + return -EINVAL; + } else { + *num_planes = 1; + sizes[0] = sizeof(struct c3_isp_params_buffer); + } + + 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_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(¶ms->buff_lock, flags); + + list_add_tail(&buf->list, ¶ms->pending); + + spin_unlock_irqrestore(¶ms->buff_lock, flags); +} + +static int c3_isp_params_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + unsigned int size = params->vfmt.fmt.meta.buffersize; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(params->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_params_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + struct c3_isp_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_params *params = vb2_get_drv_priv(vb->vb2_queue); + + buf->vaddr = vb2_plane_vaddr(vb, 0); + buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + memset(buf->vaddr, 0, params->vfmt.fmt.meta.buffersize); + + return 0; +} + +static int c3_isp_params_vb2_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct c3_isp_params *params = vb2_get_drv_priv(q); + int ret; + + guard(mutex)(¶ms->isp->lock); + + ret = pm_runtime_resume_and_get(params->isp->dev); + if (ret) + return ret; + + ret = video_device_pipeline_start(¶ms->vdev, ¶ms->isp->pipe); + if (ret) { + dev_err(params->isp->dev, + "Failed to start params pipeline: %d\n", ret); + goto err_pm_put; + } + + if (c3_isp_pipeline_ready(params->isp)) { + ret = v4l2_subdev_enable_streams(¶ms->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + BIT(0)); + if (ret) + goto err_pipeline_stop; + } + + c3_isp_params_start(params); + + return 0; + +err_pipeline_stop: + video_device_pipeline_stop(¶ms->vdev); +err_pm_put: + pm_runtime_put(params->isp->dev); + c3_isp_params_return_buffers(params, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void c3_isp_params_vb2_stop_streaming(struct vb2_queue *q) +{ + struct c3_isp_params *params = vb2_get_drv_priv(q); + + guard(mutex)(¶ms->isp->lock); + + c3_isp_params_return_buffers(params, VB2_BUF_STATE_ERROR); + + if (params->isp->pipe.start_count == 1) + v4l2_subdev_disable_streams(¶ms->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_VIDEO, + BIT(0)); + + video_device_pipeline_stop(¶ms->vdev); + pm_runtime_put(params->isp->dev); +} + +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, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = c3_isp_params_vb2_start_streaming, + .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_buffer); + params->isp = isp; + INIT_LIST_HEAD(¶ms->pending); + spin_lock_init(¶ms->buff_lock); + mutex_init(¶ms->lock); + + snprintf(vdev->name, sizeof(vdev->name), "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_dma_contig_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_vb2_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); +} + +int c3_isp_params_done(struct c3_isp_device *isp) +{ + struct c3_isp_params *params = &isp->params; + enum vb2_buffer_state state; + unsigned long flags; + + spin_lock_irqsave(¶ms->buff_lock, flags); + + params->buff = list_first_entry_or_null(¶ms->pending, + struct c3_isp_vb2_buffer, list); + if (!params->buff) { + spin_unlock_irqrestore(¶ms->buff_lock, flags); + return -EINVAL; + } + + list_del(¶ms->buff->list); + + state = 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, state); + + spin_unlock_irqrestore(¶ms->buff_lock, flags); + + return 0; +} 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..de1938f7c354 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-regs.h @@ -0,0 +1,683 @@ +/* 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 TOP_INPUT_HSIZE(x) (((x) & 0xffff) << 16) +#define TOP_INPUT_VSIZE(x) ((x) & 0xffff) + +#define ISP_TOP_FRM_SIZE 0x0004 +#define TOP_FRM_CORE_IHSIZE(x) (((x) & 0xffff) << 16) +#define TOP_FRM_CORE_IVSIZE(x) ((x) & 0xffff) + +#define ISP_TOP_HOLD_SIZE 0x0008 +#define TOP_HOLD_HSIZE_MASK GENMASK(31, 16) +#define TOP_HOLD_HSIZE_SHIFT 16 + +#define ISP_TOP_PATH_EN 0x0010 +#define TOP_DISP_EN(x) BIT((x)) +#define TOP_WRMIF_EN(x) BIT((x) + 8) + +#define ISP_TOP_PATH_SEL 0x0014 +#define TOP_DATA_PATH_MASK GENMASK(18, 16) +#define TOP_PATH_MIPI_TO_CORE BIT(16) + +#define ISP_TOP_IRQ_EN 0x0080 +#define TOP_IRQ_FRAME_DONE BIT(0) +#define TOP_IRQ_STATS_ERR BIT(5) + +#define ISP_TOP_IRQ_CLR 0x0084 +#define ISP_TOP_IRQ_LINE_THRD 0x0088 +#define TOP_IRQ_DIN_HSYNC BIT(16) + +#define ISP_TOP_RO_IRQ_STAT 0x01c4 +#define ISP_TOP_MODE_CTRL 0x0400 +#define ISP_TOP_FEO_CTRL0 0x040c +#define TOP_FEO_CTRL0_ALL_DIS 0 +#define TOP_FEO_INPUT_FMT_EN BIT(8) + +#define ISP_TOP_FEO_CTRL1_0 0x0410 +#define TOP_FEO_CTRL1_0_ALL_DIS 0 + +#define ISP_TOP_FEO_CTRL1_1 0x0414 +#define TOP_FEO_CTRL1_1_ALL_DIS 0 + +#define ISP_TOP_FED_CTRL 0x0418 +#define TOP_FED_CTRL_ALL_DIS 0 + +#define ISP_TOP_BEO_CTRL 0x041c +#define TOP_BEO_CTRL_ALL_DIS 0 +#define TOP_BEO_CTRL_GTM_EN BIT(2) +#define TOP_BEO_CTRL_WB_EN BIT(6) +#define TOP_BEO_CTRL_BLC_EN BIT(7) +#define TOP_BEO_CTRL_IDGAIN_EN BIT(8) +#define TOP_BEO_CTRL_EOTF_EN BIT(9) + +#define ISP_TOP_BED_CTRL 0x0420 +#define TOP_BED_CTRL_ALL_DIS 0 +#define TOP_BED_CM0_EN BIT(14) +#define TOP_BED_GAMMA_EN BIT(16) +#define TOP_BED_CCM_EN BIT(18) +#define TOP_BED_DMSC_EN BIT(19) + +#define ISP_TOP_3A_STAT_CRTL 0x0424 +#define TOP_3A_AE_STAT_EN BIT(0) +#define TOP_3A_AWB_STAT_EN BIT(1) +#define TOP_3A_AF_STAT_EN BIT(2) +#define TOP_3A_AWB_STAT_SWITCH_MASK GENMASK(6, 4) +#define TOP_3A_AWB_STAT_SWITCH_SHIFT 4 +#define TOP_3A_AWB_STAT_SWITCH_BEFORE_WB 2 + +#define TOP_3A_AE_STAT_SWITCH_MASK GENMASK(9, 8) +#define TOP_3A_AE_STAT_SWITCH_SHIFT 8 +#define TOP_3A_AE_STAT_SWITCH_FROM_MLS 1 +#define TOP_3A_AF_STAT_SWITCH_MASK GENMASK(13, 12) +#define TOP_3A_AF_STAT_SWITCH_SHIFT 12 +#define TOP_3A_AF_STAT_SWITCH_FROM_SNR 0 + +#define ISP_FED_BL_OFST_GR 0x2018 +#define FED_BL_GR_OFST(x) ((x) & 0x1fffff) + +#define ISP_FED_BL_OFST_R 0x201c +#define FED_BL_R_OFST(x) ((x) & 0x1fffff) + +#define ISP_FED_BL_OFST_B 0x2020 +#define FED_BL_B_OFST(x) ((x) & 0x1fffff) + +#define ISP_FED_BL_OFST_GB 0x2024 +#define FED_BL_GB_OFST(x) ((x) & 0x1fffff) + +#define ISP_FED_BL_OFST_IR 0x2028 +#define FED_BL_IR_OFST(x) ((x) & 0x1fffff) + +#define ISP_LSWB_BLC_OFST0 0x4028 +#define LSWB_BLC_OFST1_MASK GENMASK(15, 0) +#define LSWB_BLC_OFST1(x) ((x) & 0xffff) +#define LSWB_BLC_OFST1_EXT(x) ((x) & 0xffff) +#define LSWB_BLC_OFST0_MASK GENMASK(31, 16) +#define LSWB_BLC_OFST0(x) (((x) & 0xffff) << 16) +#define LSWB_BLC_OFST0_EXT(x) (((x) >> 16) & 0xffff) + +#define ISP_LSWB_BLC_OFST1 0x402c +#define LSWB_BLC_OFST3_MASK GENMASK(15, 0) +#define LSWB_BLC_OFST3(x) ((x) & 0xffff) +#define LSWB_BLC_OFST3_EXT(x) ((x) & 0xffff) +#define LSWB_BLC_OFST2_MASK GENMASK(31, 16) +#define LSWB_BLC_OFST2(x) (((x) & 0xffff) << 16) +#define LSWB_BLC_OFST2_EXT(x) (((x) >> 16) & 0xffff) + +#define ISP_LSWB_BLC_OFST2 0x4030 +#define LSWB_BLC_OFST4_MASK GENMASK(15, 0) +#define LSWB_BLC_OFST4(x) ((x) & 0xffff) +#define LSWB_BLC_OFST4_EXT(x) ((x) & 0xffff) + +#define ISP_LSWB_BLC_PHSOFST 0x4034 +#define LSWB_BLC_YPHS_OFST_MASK GENMASK(1, 0) +#define LSWB_BLC_XPHS_OFST_MASK GENMASK(3, 2) +#define LSWB_BLC_XPHS_OFST_SHIFT 2 + +#define ISP_LSWB_WB_GAIN0 0x4038 +#define LSWB_WB_GAIN1_MASK GENMASK(11, 0) +#define LSWB_WB_GAIN0_MASK GENMASK(27, 16) +#define LSWB_WB_GAIN0_SHIFT 16 + +#define ISP_LSWB_WB_GAIN1 0x403c +#define LSWB_WB_GAIN3_MASK GENMASK(11, 0) +#define LSWB_WB_GAIN2_MASK GENMASK(27, 16) +#define LSWB_WB_GAIN2_SHIFT 16 + +#define ISP_LSWB_WB_GAIN2 0x4040 +#define LSWB_WB_GAIN4_MASK GENMASK(11, 0) + +#define ISP_LSWB_WB_LIMIT0 0x4044 +#define LSWB_WB_LIMIT1(x) ((x) & 0xffff) +#define LSWB_WB_LIMIT0(x) (((x) & 0xffff) << 16) + +#define ISP_LSWB_WB_LIMIT1 0x4048 +#define LSWB_WB_LIMIT3(x) ((x) & 0xffff) +#define LSWB_WB_LIMIT2(x) (((x) & 0xffff) << 16) + +#define ISP_LSWB_WB_LIMIT2 0x404c +#define LSWB_WB_LIMIT4_MASK GENMASK(15, 0) + +#define ISP_LSWB_WB_PHSOFST 0x4050 +#define LSWB_WB_YPHS_OFST_MASK GENMASK(1, 0) +#define LSWB_WB_XPHS_OFST_MASK GENMASK(3, 2) +#define LSWB_WB_XPHS_OFST_SHIFT 2 + +#define ISP_LSWB_LNS_PHSOFST 0x4054 +#define LSWB_LNS_YPHS_OFST_MASK GENMASK(1, 0) +#define LSWB_LNS_XPHS_OFST_MASK GENMASK(3, 2) +#define LSWB_LNS_XPHS_OFST_SHIFT 2 + +#define ISP_DMS_COMMON_PARAM0 0x5000 +#define DMS_COMMON_YPHS_OFST_MASK GENMASK(1, 0) +#define DMS_COMMON_XPHS_OFST_MASK GENMASK(3, 2) +#define DMS_COMMON_XPHS_OFST_SHIFT 2 + +#define ISP_CM0_INP_OFST01 0x6040 +#define CM0_INP_OFST0_MASK GENMASK(12, 0) +#define CM0_INP_OFST1_MASK GENMASK(28, 16) +#define CM0_INP_OFST1_SHIFT 16 + +#define ISP_CM0_INP_OFST2 0x6044 +#define CM0_INP_OFST2_MASK GENMASK(12, 0) + +#define ISP_CM0_COEF00_01 0x6048 +#define CM0_MTX_00_MASK GENMASK(12, 0) +#define CM0_MTX_01_MASK GENMASK(28, 16) +#define CM0_MTX_01_SHIFT 16 + +#define ISP_CM0_COEF02_10 0x604c +#define CM0_MTX_02_MASK GENMASK(12, 0) +#define CM0_MTX_10_MASK GENMASK(28, 16) +#define CM0_MTX_10_SHIFT 16 + +#define ISP_CM0_COEF11_12 0x6050 +#define CM0_MTX_11_MASK GENMASK(12, 0) +#define CM0_MTX_12_MASK GENMASK(28, 16) +#define CM0_MTX_12_SHIFT 16 + +#define ISP_CM0_COEF20_21 0x6054 +#define CM0_MTX_20_MASK GENMASK(12, 0) +#define CM0_MTX_21_MASK GENMASK(28, 16) +#define CM0_MTX_21_SHIFT 16 + +#define ISP_CM0_COEF22_OUP_OFST0 0x6058 +#define CM0_MTX_22_MASK GENMASK(12, 0) +#define CM0_OFST_OUP0_MASK GENMASK(28, 16) +#define CM0_OFST_OUP0_SHIFT 16 + +#define ISP_CM0_OUP_OFST12_RS 0x605c +#define CM0_OFST_OUP1_MASK GENMASK(12, 0) +#define CM0_OFST_OUP2_MASK GENMASK(28, 16) +#define CM0_OFST_OUP2_SHIFT 16 +#define CM0_MTX_RS_MASK GENMASK(31, 30) +#define CM0_MTX_RS_SHIFT 30 + +#define ISP_CCM_MTX_00_01 0x6098 +#define CCM_MTX_00_MASK GENMASK(12, 0) +#define CCM_MTX_01_MASK GENMASK(28, 16) +#define CCM_MTX_01_SHIFT 16 + +#define ISP_CCM_MTX_02_03 0x609c +#define CCM_MTX_02_MASK GENMASK(12, 0) +#define CCM_MTX_03_MASK GENMASK(28, 16) +#define CCM_MTX_03_SHIFT 16 + +#define ISP_CCM_MTX_10_11 0x60A0 +#define CCM_MTX_10_MASK GENMASK(12, 0) +#define CCM_MTX_11_MASK GENMASK(28, 16) +#define CCM_MTX_11_SHIFT 16 + +#define ISP_CCM_MTX_12_13 0x60A4 +#define CCM_MTX_12_MASK GENMASK(12, 0) +#define CCM_MTX_13_MASK GENMASK(28, 16) +#define CCM_MTX_13_SHIFT 16 + +#define ISP_CCM_MTX_20_21 0x60A8 +#define CCM_MTX_20_MASK GENMASK(12, 0) +#define CCM_MTX_21_MASK GENMASK(28, 16) +#define CCM_MTX_21_SHIFT 16 + +#define ISP_CCM_MTX_22_23_RS 0x60Ac +#define CCM_MTX_22_MASK GENMASK(12, 0) +#define CCM_MTX_23_MASK GENMASK(28, 16) +#define CCM_MTX_23_SHIFT 16 + +#define ISP_PST_GAMMA_MODE 0x60C0 +#define PST_GAMMA_MODE BIT(0) +#define PST_GAMMA_MODE_EXT(x) ((x) & 0x1) + +#define ISP_PST_GAMMA_LUT_ADDR 0x60cc +#define PST_GAMMA_LUT_ADDR(x) ((x) << 7) + +#define ISP_PST_GAMMA_LUT_DATA 0x60d0 +#define PST_GAMMA_LUT_DATA0(x) ((x) & 0xffff) +#define PST_GAMMA_LUT_DATA1(x) (((x) & 0xffff) << 16) + +#define DISP0_TOP_TOP_CTRL 0x8000 +#define DISP_CRP2_EN BIT(5) + +#define DISP0_TOP_CRP2_START 0x8004 +#define DISP_CRP2_VSTART_MASK GENMASK(15, 0) +#define DISP_CRP2_VSTART(x) ((x) & 0xffff) +#define DISP_CRP2_HSTART_MASK GENMASK(31, 16) +#define DISP_CRP2_HSTART(x) (((x) & 0xffff) << 16) + +#define DISP0_TOP_CRP2_SIZE 0x8008 +#define DISP_CRP2_VSIZE_MASK GENMASK(15, 0) +#define DISP_CRP2_VSIZE(x) ((x) & 0xffff) +#define DISP_CRP2_HSIZE_MASK GENMASK(31, 16) +#define DISP_CRP2_HSIZE(x) (((x) & 0xffff) << 16) + +#define DISP0_TOP_OUT_SIZE 0x800c +#define DISP_OUT_VSIZE_MASK GENMASK(12, 0) +#define DISP_OUT_HSIZE_MASK GENMASK(28, 16) +#define DISP_OUT_HSIZE_SHIFT 16 + +#define ISP_DISP0_TOP_IN_SIZE 0x804c +#define DISP_TOP_IN_VSIZE(x) ((x) & 0x1fff) +#define DISP_TOP_IN_HSIZE(x) (((x) & 0x1fff) << 16) + +#define DISP0_PPS_SCALE_EN 0x8200 +#define PPS_VSC_TAP_NUM_MASK GENMASK(3, 0) +#define PPS_HSC_TAP_NUM_MASK GENMASK(7, 4) +#define PPS_HSC_TAP_NUM_SHIFT 4 +#define PPS_HSC_TAP_NUM_INIT 4 +#define PPS_PREVSC_FLT_NUM_MASK GENMASK(11, 8) +#define PPS_PREVSC_FLT_NUM_SHIFT 8 +#define PPS_PREHSC_FLT_NUM_MASK GENMASK(15, 12) +#define PPS_PREHSC_FLT_NUM_SHIFT 12 +#define PPS_PREHSC_FLT_NUM_INIT 8 +#define PPS_PREVSC_RATE_MASK GENMASK(17, 16) +#define PPS_PREVSC_RATE_SHIFT 16 +#define PPS_PREHSC_RATE_MASK GENMASK(19, 18) +#define PPS_PREHSC_RATE_SHIFT 18 +#define PPS_HSC_EN_MASK BIT(20) +#define PPS_HSC_EN_SHIFT 20 +#define PPS_VSC_EN_MASK BIT(21) +#define PPS_VSC_EN_SHIFT 21 +#define PPS_PREVSC_EN_MASK BIT(22) +#define PPS_PREVSC_EN_SHIFT 22 +#define PPS_PREHSC_EN_MASK BIT(23) +#define PPS_PREHSC_EN_SHIFT 23 +#define PPS_HSC_NOR_RS_BITS_MASK GENMASK(27, 24) +#define PPS_HSC_NOR_RS_BITS_SHIFT 24 +#define PPS_HSC_NOR_RS_BITS_INIT 9 +#define PPS_VSC_NOR_RS_BITS_MASK GENMASK(31, 28) +#define PPS_VSC_NOR_RS_BITS_SHIFT 28 +#define PPS_VSC_NOR_RS_BITS_INIT 9 + +#define DISP0_PPS_PRE_HSCALE_COEF_0 0x8204 +#define PPS_PREHSC_LUMA_COEF0_MASK GENMASK(9, 0) +#define PPS_PREHSC_LUMA_COEF0_INIT 128 +#define PPS_PREHSC_LUMA_COEF1_MASK GENMASK(25, 16) +#define PPS_PREHSC_LUMA_COEF1_SHIFT 16 +#define PPS_PREHSC_LUMA_COEF1_INIT 128 + +#define DISP0_PPS_PRE_HSCALE_COEF_1 0x8208 +#define PPS_PREHSC_LUMA_COEF2_MASK GENMASK(9, 0) +#define PPS_PREHSC_LUMA_COEF2_INIT 32 +#define PPS_PREHSC_LUMA_COEF3_MASK GENMASK(25, 16) +#define PPS_PREHSC_LUMA_COEF3_SHIFT 16 +#define PPS_PREHSC_LUMA_COEF3_INIT 32 + +#define DISP0_PPS_VSC_START_PHASE_STEP 0x8224 +#define PPS_VSC_FRACTION_PART_MASK GENMASK(23, 0) +#define PPS_VSC_INTEGER_PART_MASK GENMASK(27, 24) +#define PPS_VSC_INTEGER_PART_SHIFT 24 + +#define DISP0_PPS_HSC_START_PHASE_STEP 0x8230 +#define PPS_HSC_FRACTION_PART_MASK GENMASK(23, 0) +#define PPS_HSC_INTEGER_PART_MASK GENMASK(27, 24) +#define PPS_HSC_INTEGER_PART_SHIFT 24 + +#define DISP0_PPS_444TO422 0x823c +#define PPS_444TO422_EN_MASK BIT(0) + +#define ISP_SCALE0_COEF_IDX_LUMA 0x8240 +#define SCALE_LUMA_COEF_S11_MODE BIT(9) +#define SCALE_LUMA_CTYPE(x) (((x) & 0x7) << 10) + +#define ISP_SCALE0_COEF_LUMA 0x8244 +#define SCALE_COEF_LUMA_DATA1(x) ((x) & 0x7ff) +#define SCALE_COEF_LUMA_DATA0(x) (((x) & 0x7ff) << 16) + +#define ISP_SCALE0_COEF_IDX_CHRO 0x8248 +#define SCALE_CHRO_COEF_S11_MODE BIT(9) +#define SCALE_CHRO_CTYPE(x) (((x) & 0x7) << 10) + +#define ISP_SCALE0_COEF_CHRO 0x824c +#define SCALE_COEF_CHRO_DATA1(x) ((x) & 0x7ff) +#define SCALE_COEF_CHRO_DATA0(x) (((x) & 0x7ff) << 16) + +#define ISP_AF_ROI0_WIN01 0xa00c +#define AF_ROI_XYWIN_00_INIT (50 & 0xffff) +#define AF_ROI_XYWIN_01_INIT ((50 & 0xffff) << 16) + +#define ISP_AF_ROI1_WIN01 0xa010 +#define AF_ROI_XYWIN_10_INIT (100 & 0xffff) +#define AF_ROI_XYWIN_11_INIT ((50 & 0xffff) << 16) + +#define ISP_AF_ROI0_WIN23 0xa014 +#define AF_ROI_XYWIN_02_INIT (80 & 0xffff) +#define AF_ROI_XYWIN_03_INIT ((40 & 0xffff) << 16) + +#define ISP_AF_ROI1_WIN23 0xa018 +#define AF_ROI_XYWIN_12_INIT (80 & 0xffff) +#define AF_ROI_XYWIN_13_INIT ((40 & 0xffff) << 16) + +#define ISP_AF_CTRL 0xa044 +#define AF_CTRL_YPHS_OFST_MASK GENMASK(15, 14) +#define AF_CTRL_YPHS_OFST_SHIFT 14 +#define AF_CTRL_XPHS_OFST_MASK GENMASK(17, 16) +#define AF_CTRL_XPHS_OFST_SHIFT 16 + +#define ISP_AF_HV_SIZE 0xa04c +#define AF_HV_STAT_VSIZE(x) ((x) & 0xffff) +#define AF_HV_STAT_HSIZE(x) (((x) & 0xffff) << 16) + +#define ISP_AF_HV_BLKNUM 0xa050 +#define AF_HV_STAT_VBLK_NUM_MASK GENMASK(5, 0) +#define AF_HV_STAT_VBLK_NUM_EXT(x) ((x) & 0x3f) +#define AF_HV_STAT_HBLK_NUM_MASK GENMASK(21, 16) +#define AF_HV_STAT_HBLK_NUM_SHIFT 16 + +#define ISP_AF_EN_CTRL 0xa054 +#define AF_STAT_SELECT BIT(21) +#define AF_STAT_SELECT_SHIFT 21 + +#define ISP_RO_AF_GLB_STAT_PCK0 0xa0e0 +#define ISP_RO_AF_GLB_STAT_PCK1 0xa0e4 +#define ISP_RO_AF_GLB_STAT_PCK2 0xa0e8 +#define ISP_RO_AF_GLB_STAT_PCK3 0xa0eC +#define ISP_AF_IDX_ADDR 0xa1c0 +#define ISP_AF_IDX_DATA 0xa1c4 +#define AF_IDX_VIDX_DATA(x) ((x) & 0xffff) +#define AF_IDX_HIDX_DATA(x) (((x) & 0xffff) << 16) + +#define ISP_AE_ROI0_WIN01 0xa40c +#define AE_ROI_XYWIN_00_INIT (50 & 0xffff) +#define AE_ROI_XYWIN_01_INIT ((50 & 0xffff) << 16) + +#define ISP_AE_ROI1_WIN01 0xa410 +#define AE_ROI_XYWIN_10_INIT (100 & 0xffff) +#define AE_ROI_XYWIN_11_INIT ((50 & 0xffff) << 16) + +#define ISP_AE_ROI0_WIN23 0xa414 +#define AE_ROI_XYWIN_02_INIT (80 & 0xffff) +#define AE_ROI_XYWIN_03_INIT ((40 & 0xffff) << 16) + +#define ISP_AE_ROI1_WIN23 0xa418 +#define AE_ROI_XYWIN_12_INIT (80 & 0xffff) +#define AE_ROI_XYWIN_13_INIT ((40 & 0xffff) << 16) + +#define ISP_RO_AE_ROI_STAT_PCK0_0 0xa424 +#define ISP_RO_AE_ROI_STAT_PCK1_0 0xa428 +#define ISP_RO_AE_ROI_STAT_PCK0_1 0xa42c +#define ISP_RO_AE_ROI_STAT_PCK1_1 0xa430 +#define ISP_AE_CTRL 0xa448 +#define AE_CTRL_AE_STAT_LOCAL_MODE GENMASK(3, 2) +#define AE_CTRL_AE_STAT_LOCAL_MODE_EXT(x) (((x) >> 2) & 0x3) +#define AE_CTRL_INPUT_2LINE_TOGETHER BIT(7) +#define AE_CTRL_LUMA_MODE_MASK GENMASK(9, 8) +#define AE_CTRL_LUMA_MODE_SHIFT 8 +#define AE_CTRL_LUMA_MODE_FILTER 2 +#define AE_CTRL_YPHS_OFST_MASK GENMASK(25, 24) +#define AE_CTRL_YPHS_OFST_SHIFT 24 +#define AE_CTRL_XPHS_OFST_MASK GENMASK(27, 26) +#define AE_CTRL_XPHS_OFST_SHIFT 26 + +#define ISP_AE_CRTL2_0 0xa44c +#define AE_CRTL2_BL12_GRBGI0_MASK GENMASK(11, 0) +#define AE_CRTL2_GAIN_GRBGI0_MASK GENMASK(23, 16) +#define AE_CRTL2_GAIN_GRBGI0_SHIFT 16 + +#define ISP_AE_CRTL2_1 0xa450 +#define AE_CRTL2_BL12_GRBGI1_MASK GENMASK(11, 0) +#define AE_CRTL2_GAIN_GRBGI1_MASK GENMASK(23, 16) +#define AE_CRTL2_GAIN_GRBGI1_SHIFT 16 + +#define ISP_AE_CRTL2_2 0xa454 +#define AE_CRTL2_BL12_GRBGI2_MASK GENMASK(11, 0) +#define AE_CRTL2_GAIN_GRBGI2_MASK GENMASK(23, 16) +#define AE_CRTL2_GAIN_GRBGI2_SHIFT 16 + +#define ISP_AE_CRTL2_3 0xa458 +#define AE_CRTL2_BL12_GRBGI3_MASK GENMASK(11, 0) +#define AE_CRTL2_GAIN_GRBGI3_MASK GENMASK(23, 16) +#define AE_CRTL2_GAIN_GRBGI3_SHIFT 16 + +#define ISP_AE_CRTL2_4 0xa45C +#define AE_CRTL2_BL12_GRBGI4_MASK GENMASK(11, 0) +#define AE_CRTL2_GAIN_GRBGI4_MASK GENMASK(23, 16) +#define AE_CRTL2_GAIN_GRBGI4_SHIFT 16 + +#define ISP_AE_HV_SIZE 0xa464 +#define AE_HV_STAT_VSIZE(x) ((x) & 0xffff) +#define AE_HV_STAT_HSIZE(x) (((x) & 0xffff) << 16) + +#define ISP_AE_HV_BLKNUM 0xa468 +#define AE_HV_STAT_VBLK_NUM_MASK GENMASK(6, 0) +#define AE_HV_STAT_VBLK_NUM_EXT(x) ((x) & 0x7f) +#define AE_HV_STAT_HBLK_NUM_MASK GENMASK(22, 16) +#define AE_HV_STAT_HBLK_NUM_SHIFT 16 + +#define ISP_AE_STAT_THD01 0xa46c +#define AE_STAT_THRD0_INIT (13107 & 0xffff) +#define AE_STAT_THRD1_INIT ((26214 & 0xffff) << 16) + +#define ISP_AE_STAT_THD23 0xa470 +#define AE_STAT_THRD2_INIT (39321 & 0xffff) +#define AE_STAT_THRD3_INIT ((52428 & 0xffff) << 16) + +#define ISP_RO_AE_STAT_GPNUM 0xa49c +#define ISP_AE_IDX_ADDR 0xa600 +#define ISP_AE_IDX_DATA 0xa604 +#define AE_IDX_VIDX_DATA(x) ((x) & 0xffff) +#define AE_IDX_HIDX_DATA(x) (((x) & 0xffff) << 16) + +#define ISP_AE_BLK_WT_ADDR 0xa608 +#define ISP_AE_BLK_WT_DATA 0xa60c +#define AE_BLK_WT_DATA0(x) ((x) & 0xf) +#define AE_BLK_WT_DATA1(x) (((x) & 0xf) << 4) +#define AE_BLK_WT_DATA2(x) (((x) & 0xf) << 8) +#define AE_BLK_WT_DATA3(x) (((x) & 0xf) << 12) +#define AE_BLK_WT_DATA4(x) (((x) & 0xf) << 16) +#define AE_BLK_WT_DATA5(x) (((x) & 0xf) << 20) +#define AE_BLK_WT_DATA6(x) (((x) & 0xf) << 24) +#define AE_BLK_WT_DATA7(x) (((x) & 0xf) << 28) + +#define ISP_AWB_CTRL 0xa834 +#define AWB_CTRL_YPHS_OFST_MASK GENMASK(1, 0) +#define AWB_CTRL_XPHS_OFST_MASK GENMASK(3, 2) +#define AWB_CTRL_XPHS_OFST_SHIFT 2 +#define AWB_CTRL_STAT_RATIO_MODE BIT(4) + +#define ISP_AWB_HV_SIZE 0xa83c +#define AWB_HV_STAT_VSIZE(x) ((x) & 0xffff) +#define AWB_HV_STAT_HSIZE(x) (((x) & 0xffff) << 16) + +#define ISP_AWB_STAT_ROI0_WIN01 0xa80c +#define AWB_ROI_XYWIN_00_INIT (32 & 0xffff) +#define AWB_ROI_XYWIN_01_INIT ((62 & 0xffff) << 16) + +#define ISP_AWB_STAT_ROI1_WIN01 0xa810 +#define AWB_ROI_XYWIN_10_INIT (32 & 0xffff) +#define AWB_ROI_XYWIN_11_INIT ((82 & 0xffff) << 16) + +#define ISP_AWB_STAT_ROI0_WIN23 0xa814 +#define AWB_ROI_XYWIN_02_INIT (32 & 0xffff) +#define AWB_ROI_XYWIN_03_INIT ((42 & 0xffff) << 16) + +#define ISP_AWB_STAT_ROI1_WIN23 0xa818 +#define AWB_ROI_XYWIN_12_INIT (64 & 0xffff) +#define AWB_ROI_XYWIN_13_INIT ((52 & 0xffff) << 16) + +#define ISP_AWB_HV_BLKNUM 0xa840 +#define AWB_HV_STAT_VBLK_NUM_MASK GENMASK(5, 0) +#define AWB_HV_STAT_VBLK_NUM_EXT(x) ((x) & 0x3f) +#define AWB_HV_STAT_HBLK_NUM_MASK GENMASK(21, 16) +#define AWB_HV_STAT_HBLK_NUM_SHIFT 16 + +#define ISP_AWB_STAT_RG 0xa848 +#define AWB_STAT_RG_MIN_MASK GENMASK(11, 0) +#define AWB_STAT_RG_MAX_MASK GENMASK(27, 16) +#define AWB_STAT_RG_MAX_SHIFT 16 + +#define ISP_AWB_STAT_BG 0xa84c +#define AWB_STAT_BG_MIN_MASK GENMASK(11, 0) +#define AWB_STAT_BG_MAX_MASK GENMASK(27, 16) +#define AWB_STAT_BG_MAX_SHIFT 16 + +#define ISP_AWB_STAT_RG_HL 0xa850 +#define AWB_STAT_RG_LOW_MASK GENMASK(11, 0) +#define AWB_STAT_RG_HIGH_MASK GENMASK(27, 16) +#define AWB_STAT_RG_HIGH_SHIFT 16 + +#define ISP_AWB_STAT_BG_HL 0xa854 +#define AWB_STAT_BG_LOW_MASK GENMASK(11, 0) +#define AWB_STAT_BG_HIGH_MASK GENMASK(27, 16) +#define AWB_STAT_BG_HIGH_SHIFT 16 + +#define ISP_AWB_STAT_CTRL2 0xa858 +#define AWB_STAT_SATUR_VALID_MASK BIT(0) +#define AWB_STAT_LOCAL_MODE BIT(2) +#define AWB_STAT_LOCAL_MODE_EXT(x) (((x) >> 2) & 0x1) +#define AWB_STAT_LUMA_DIV_MODE GENMASK(4, 3) +#define AWB_STAT_LUMA_DIV_MODE_EXT(x) (((x) >> 3) & 0x3) + +#define ISP_AWB_STAT_BLC20_0 0xa85c +#define AWB_STAT_BLC20_GR_MASK GENMASK(19, 0) + +#define ISP_AWB_STAT_BLC20_1 0xa860 +#define AWB_STAT_BLC20_R_MASK GENMASK(19, 0) + +#define ISP_AWB_STAT_BLC20_2 0xa864 +#define AWB_STAT_BLC20_B_MASK GENMASK(19, 0) + +#define ISP_AWB_STAT_BLC20_3 0xa868 +#define AWB_STAT_BLC20_GB_MASK GENMASK(19, 0) + +#define ISP_AWB_STAT_GAIN10_0 0xa86c +#define AWB_STAT_GAIN10_GR_MASK GENMASK(9, 0) + +#define ISP_AWB_STAT_GAIN10_1 0xa870 +#define AWB_STAT_GAIN10_R_MASK GENMASK(9, 0) + +#define ISP_AWB_STAT_GAIN10_2 0xa874 +#define AWB_STAT_GAIN10_B_MASK GENMASK(9, 0) + +#define ISP_AWB_STAT_GAIN10_3 0xa878 +#define AWB_STAT_GAIN10_GB_MASK GENMASK(9, 0) + +#define ISP_AWB_STAT_SATUR_CTRL 0xa884 +#define AWB_STAT_SATUR_LOW(x) ((x) & 0xffff) +#define AWB_STAT_SATUR_HIGH(x) (((x) & 0xffff) << 16) + +#define ISP_AWB_IDX_ADDR 0xaa00 +#define ISP_AWB_IDX_DATA 0xaa04 +#define AWB_IDX_VIDX_DATA(x) ((x) & 0xffff) +#define AWB_IDX_HIDX_DATA(x) (((x) & 0xffff) << 16) + +#define ISP_AWB_BLK_WT_ADDR 0xaa08 +#define ISP_AWB_BLK_WT_DATA 0xaa0c +#define AWB_BLK_WT_DATA0(x) ((x) & 0xf) +#define AWB_BLK_WT_DATA1(x) (((x) & 0xf) << 4) +#define AWB_BLK_WT_DATA2(x) (((x) & 0xf) << 8) +#define AWB_BLK_WT_DATA3(x) (((x) & 0xf) << 12) +#define AWB_BLK_WT_DATA4(x) (((x) & 0xf) << 16) +#define AWB_BLK_WT_DATA5(x) (((x) & 0xf) << 20) +#define AWB_BLK_WT_DATA6(x) (((x) & 0xf) << 24) +#define AWB_BLK_WT_DATA7(x) (((x) & 0xf) << 28) + +#define ISP_WRMIFX3_0_CH0_CTRL0 0xc400 +#define WRMIFX3_CH0_STRIDE_MASK GENMASK(28, 16) +#define WRMIFX3_CH0_STRIDE_SHIFT 16 + +#define ISP_WRMIFX3_0_CH0_CTRL1 0xc404 +#define WRMIFX3_CH0_PIX_BITS_MODE_MASK GENMASK(30, 27) +#define WRMIFX3_CH0_PIX_BITS_MODE_SHIFT 27 +#define WRMIFX3_CH0_PIX_BITS_8BITS 1 +#define WRMIFX3_CH0_PIX_BITS_16BITS 2 + +#define ISP_WRMIFX3_0_CH1_CTRL0 0xc408 +#define WRMIFX3_CH1_STRIDE_MASK GENMASK(28, 16) +#define WRMIFX3_CH1_STRIDE_SHIFT 16 + +#define ISP_WRMIFX3_0_CH1_CTRL1 0xc40c +#define WRMIFX3_CH1_PIX_BITS_MODE_MASK GENMASK(30, 27) +#define WRMIFX3_CH1_PIX_BITS_MODE_SHIFT 27 +#define WRMIFX3_CH1_PIX_BITS_16BITS 2 +#define WRMIFX3_CH1_PIX_BITS_32BITS 3 + +#define ISP_WRMIFX3_0_CH0_BADDR 0xc440 +/* WRMIF base address need 16 bits alignment */ +#define WRMIFX3_CH0_BADDR(x) (((x) >> 4) & 0xffffffff) + +#define ISP_WRMIFX3_0_CH1_BADDR 0xc444 +#define WRMIFX3_CH1_BADDR(x) (((x) >> 4) & 0xffffffff) + +#define ISP_WRMIFX3_0_FMT_SIZE 0xc464 +#define WRMIFX3_FMT_HSIZE(x) ((x) & 0xffff) +#define WRMIFX3_FMT_VSIZE(x) (((x) & 0xffff) << 16) + +#define ISP_WRMIFX3_0_FMT_CTRL 0xc468 +#define WRMIFX3_FMT_MTX_IBITS_MASK GENMASK(1, 0) +#define WRMIFX3_FMT_MTX_IBITS_8BIT 0 +#define WRMIFX3_FMT_MTX_IBITS_10BIT 1 +#define WRMIFX3_FMT_MTX_IBITS_12BIT 2 +#define WRMIFX3_FMT_MTX_IBITS_16BIT 3 +#define WRMIFX3_FMT_MTX_UV_SWAP_MASK BIT(2) +#define WRMIFX3_FMT_MTX_UV_SWAP_SHIFT 2 +#define WRMIFX3_FMT_MTX_UV_SWAP_VU 0 +#define WRMIFX3_FMT_MTX_UV_SWAP_UV 1 +#define WRMIFX3_FMT_MTX_PLANE_MASK GENMASK(5, 4) +#define WRMIFX3_FMT_MTX_PLANE_SHIFT 4 +#define WRMIFX3_FMT_MTX_PLANE_X1 0 +#define WRMIFX3_FMT_MTX_PLANE_X2 1 +#define WRMIFX3_FMT_MODE_OUT_MASK GENMASK(18, 16) +#define WRMIFX3_FMT_MODE_OUT_SHIFT 16 +#define WRMIFX3_FMT_MODE_OUT_YUV422 1 +#define WRMIFX3_FMT_MODE_OUT_YUV420 2 +#define WRMIFX3_FMT_MODE_OUT_Y_ONLY 3 + +#define ISP_WRMIFX3_0_WIN_LUMA_H 0xc420 +#define WRMIFX3_WIN_LUMA_HEND_MASK GENMASK(28, 16) +#define WRMIFX3_WIN_LUMA_HEND_SHIFT 16 +#define WRMIFX3_WIN_LUMA_HEND(x) ((x) - 1) + +#define ISP_WRMIFX3_0_WIN_LUMA_V 0xc424 +#define WRMIFX3_WIN_LUMA_VEND_MASK GENMASK(28, 16) +#define WRMIFX3_WIN_LUMA_VEND_SHIFT 16 +#define WRMIFX3_WIN_LUMA_VEND(x) ((x) - 1) + +#define ISP_WRMIFX3_0_WIN_CHROM_H 0xc428 +#define WRMIFX3_WIN_CHROM_HEND_MASK GENMASK(28, 16) +#define WRMIFX3_WIN_CHROM_HEND_SHIFT 16 +#define WRMIFX3_WIN_CHROM_HEND(x) (((x) >> 1) - 1) + +#define ISP_WRMIFX3_0_WIN_CHROM_V 0xc42c +#define WRMIFX3_WIN_CHROM_VEND_MASK GENMASK(28, 16) +#define WRMIFX3_WIN_CHROM_VEND_SHIFT 16 +#define WRMIFX3_WIN_CHROM_VEND(x) (((x) >> 1) - 1) + +#define ISP_WRMIFX3_0_CRP_HSIZE 0xc48c +#define WRMIFX3_CROP_HEND_MASK GENMASK(31, 16) +#define WRMIFX3_CROP_HEND_SHIFT 16 +#define WRMIFX3_CROP_HEND(x) ((x) - 1) + +#define ISP_WRMIFX3_0_CRP_VSIZE 0xc490 +#define WRMIFX3_CROP_VEND_MASK GENMASK(31, 16) +#define WRMIFX3_CROP_VEND_SHIFT 16 +#define WRMIFX3_CROP_VEND(x) ((x) - 1) + +#define VIU_DMAWR_BADDR0 0xc840 +#define VIU_DMAWR_AF_BADDR_MASK GENMASK(27, 0) +/* AF base address need 16 bits alignment */ +#define VIU_DMAWR_AF_BADDR(x) ((x) >> 4) + +#define VIU_DMAWR_BADDR1 0xc844 +#define VIU_DMAWR_AWB_BADDR_MASK GENMASK(27, 0) +/* AWB base address need 16 bits alignment */ +#define VIU_DMAWR_AWB_BADDR(x) ((x) >> 4) + +#define VIU_DMAWR_BADDR2 0xc848 +#define VIU_DMAWR_AE_BADDR_MASK GENMASK(27, 0) +/* AE base address need 16 bits alignment */ +#define VIU_DMAWR_AE_BADDR(x) ((x) >> 4) + +#define VIU_DMAWR_SIZE0 0xc854 +#define VIU_DMAWR_SIZE_AF_MASK GENMASK(15, 0) +#define VIU_DMAWR_SIZE_AWB_MASK GENMASK(31, 16) +#define VIU_DMAWR_SIZE_AWB_SHIFT 16 + +#define VIU_DMAWR_SIZE1 0xc858 +#define VIU_DMAWR_SIZE_AE_MASK GENMASK(15, 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..01d99b66cb32 --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-resizer.c @@ -0,0 +1,768 @@ +// 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" + +static const struct c3_isp_mbus_format_info c3_isp_rsz_mbus_formats[] = { + /* YUV formats */ + { + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pads = BIT(C3_ISP_RESIZER_PAD_SINK) + | BIT(C3_ISP_RESIZER_PAD_SOURCE), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8, + .pads = BIT(C3_ISP_RESIZER_PAD_SINK) + | BIT(C3_ISP_RESIZER_PAD_SOURCE), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pads = BIT(C3_ISP_RESIZER_PAD_SINK) + | BIT(C3_ISP_RESIZER_PAD_SOURCE), + .xofst = C3_ISP_PHASE_OFFSET_NONE, + .yofst = C3_ISP_PHASE_OFFSET_NONE, + }, +}; + +/* The normal parameters of pps module */ +static const int pps_lut_tap4_s11[C3_PPS_TAP4_S11_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_mbus_format_info +*rsz_find_format_by_code(u32 code, u32 pad) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(c3_isp_rsz_mbus_formats); i++) { + const struct c3_isp_mbus_format_info *info = + &c3_isp_rsz_mbus_formats[i]; + + if (info->mbus_code == code && info->pads & BIT(pad)) + return info; + } + + return NULL; +} + +static void c3_isp_rsz_cfg_fmt(struct c3_isp_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK); + + c3_isp_write(rsz->isp, C3_DISP_REG(ISP_DISP0_TOP_IN_SIZE, rsz->id), + DISP_TOP_IN_HSIZE(fmt->width) | DISP_TOP_IN_VSIZE(fmt->height)); +} + +static void c3_isp_rsz_crop_enable(struct c3_isp_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct v4l2_rect *crop; + + crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK); + + c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_START, rsz->id), + DISP_CRP2_VSTART(crop->top) | DISP_CRP2_HSTART(crop->left)); + c3_isp_write(rsz->isp, C3_DISP_REG(DISP0_TOP_CRP2_SIZE, rsz->id), + DISP_CRP2_VSIZE(crop->height) | DISP_CRP2_HSIZE(crop->width)); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id), + DISP_CRP2_EN, DISP_CRP2_EN); +} + +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 step_h_integer, step_v_integer; + int step_h_fraction, step_v_fraction; + int yuv444to422_en; + + /* Calculate the integer part of horizonal scaler step */ + step_h_integer = thsize / ohsize; + + /* Calculate the vertical part of horizonal scaler step */ + step_v_integer = 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. + */ + step_h_fraction = ((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. + */ + step_v_fraction = ((tvsize << 12) / ovsize) << 12; + + yuv444to422_en = ihsize > (max_hsize / 2) ? 1 : 0; + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_444TO422, rsz->id), + PPS_444TO422_EN_MASK, yuv444to422_en); + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id), + PPS_VSC_FRACTION_PART_MASK, step_v_fraction); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_VSC_START_PHASE_STEP, rsz->id), + PPS_VSC_INTEGER_PART_MASK, + step_v_integer << PPS_VSC_INTEGER_PART_SHIFT); + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id), + PPS_HSC_FRACTION_PART_MASK, step_h_fraction); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_HSC_START_PHASE_STEP, rsz->id), + PPS_HSC_INTEGER_PART_MASK, + step_h_integer << PPS_HSC_INTEGER_PART_SHIFT); + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id), + PPS_PREHSC_LUMA_COEF0_MASK, PPS_PREHSC_LUMA_COEF0_INIT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_0, rsz->id), + PPS_PREHSC_LUMA_COEF1_MASK, + PPS_PREHSC_LUMA_COEF1_INIT << PPS_PREHSC_LUMA_COEF1_SHIFT); + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id), + PPS_PREHSC_LUMA_COEF2_MASK, PPS_PREHSC_LUMA_COEF2_INIT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_PRE_HSCALE_COEF_1, rsz->id), + PPS_PREHSC_LUMA_COEF3_MASK, + PPS_PREHSC_LUMA_COEF3_INIT << PPS_PREHSC_LUMA_COEF3_SHIFT); +} + +static void c3_isp_rsz_pps_lut(struct c3_isp_resizer *rsz, u32 ctype) +{ + 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_DISP_REG(ISP_SCALE0_COEF_IDX_LUMA, rsz->id), + SCALE_LUMA_COEF_S11_MODE | SCALE_LUMA_CTYPE(ctype)); + + for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) { + c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id), + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][0]) | + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[i][1])); + c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_LUMA, rsz->id), + SCALE_COEF_LUMA_DATA0(pps_lut_tap4_s11[i][2]) | + SCALE_COEF_LUMA_DATA1(pps_lut_tap4_s11[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_DISP_REG(ISP_SCALE0_COEF_IDX_CHRO, rsz->id), + SCALE_CHRO_COEF_S11_MODE | SCALE_CHRO_CTYPE(ctype)); + + for (i = 0; i < C3_PPS_TAP4_S11_H_NUM; i++) { + c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id), + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][0]) | + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][1])); + c3_isp_write(rsz->isp, C3_DISP_REG(ISP_SCALE0_COEF_CHRO, rsz->id), + SCALE_COEF_CHRO_DATA0(pps_lut_tap4_s11[i][2]) | + SCALE_COEF_CHRO_DATA1(pps_lut_tap4_s11[i][3])); + } +} + +static void c3_isp_rsz_pps_disable(struct c3_isp_resizer *rsz) +{ + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_HSC_EN_MASK, 0); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_VSC_EN_MASK, 0); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREVSC_EN_MASK, 0); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREHSC_EN_MASK, 0); +} + +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 reg_prehsc_rate; + u32 reg_prevsc_flt_num; + int pre_vscale_max_hsize; + u32 ihsize_after_pre_hsc; + u32 ihsize_after_pre_hsc_alt; + u32 reg_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_RESIZER_PAD_SINK); + cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK); + + ihsize = crop->width; + ivsize = crop->height; + + hsc_en = (ihsize == cmps->width) ? C3_SCALE_DIS : C3_SCALE_EN; + vsc_en = (ivsize == cmps->height) ? C3_SCALE_DIS : C3_SCALE_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_SCALE_EN : C3_SCALE_DIS; + prev_en = (ivsize > cmps->height * 4) ? C3_SCALE_EN : C3_SCALE_DIS; + + if (rsz->id == C3_ISP_RSZ_2) { + max_hsize = C3_ISP_MAX_WIDTH; + /* + * Set vertical tap number and + * the max hsize of pre-vertical scale. + */ + reg_prevsc_flt_num = 4; + pre_vscale_max_hsize = max_hsize / 2; + } else { + max_hsize = C3_ISP_DEFAULT_WIDTH; + preh_en = (ihsize > max_hsize) ? C3_SCALE_EN : C3_SCALE_DIS; + /* + * Set vertical tap number and + * the max hsize of pre-vertical scale. + */ + if (ihsize > (max_hsize / 2) && + ihsize <= max_hsize && prev_en) { + reg_prevsc_flt_num = 2; + pre_vscale_max_hsize = max_hsize; + } else { + reg_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) { + reg_prehsc_rate = 1; + ihsize_after_pre_hsc = DIV_ROUND_UP(ihsize, 2); + } else { + reg_prehsc_rate = 0; + ihsize_after_pre_hsc = ihsize; + } + + /* Change pre-horizonal scale rate */ + if (prev_en && ihsize_after_pre_hsc >= pre_vscale_max_hsize) + reg_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 << reg_prehsc_rate); + else + ihsize_after_pre_hsc_alt = ihsize; + + /* Set vertical scaler bank length */ + if (ihsize_after_pre_hsc_alt <= (max_hsize / 2)) + reg_vsc_tap_num_alt = 4; + else if (ihsize_after_pre_hsc_alt <= max_hsize) + reg_vsc_tap_num_alt = prev_en ? 2 : 4; + else + reg_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_PPS_LUT_CTYPE_0); + c3_isp_rsz_pps_lut(rsz, C3_PPS_LUT_CTYPE_2); + + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_VSC_TAP_NUM_MASK, reg_vsc_tap_num_alt); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_HSC_TAP_NUM_MASK, + PPS_HSC_TAP_NUM_INIT << PPS_HSC_TAP_NUM_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREVSC_FLT_NUM_MASK, + reg_prevsc_flt_num << PPS_PREVSC_FLT_NUM_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREHSC_FLT_NUM_MASK, + PPS_PREHSC_FLT_NUM_INIT << PPS_PREHSC_FLT_NUM_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREVSC_RATE_MASK, prev_en << PPS_PREVSC_RATE_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREHSC_RATE_MASK, reg_prehsc_rate << PPS_PREHSC_RATE_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_HSC_EN_MASK, hsc_en << PPS_HSC_EN_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_VSC_EN_MASK, vsc_en << PPS_VSC_EN_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREVSC_EN_MASK, prev_en << PPS_PREVSC_EN_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_PREHSC_EN_MASK, preh_en << PPS_PREHSC_EN_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_HSC_NOR_RS_BITS_MASK, + PPS_HSC_NOR_RS_BITS_INIT << PPS_HSC_NOR_RS_BITS_SHIFT); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_PPS_SCALE_EN, rsz->id), + PPS_VSC_NOR_RS_BITS_MASK, + PPS_VSC_NOR_RS_BITS_INIT << PPS_VSC_NOR_RS_BITS_SHIFT); + + return 0; +} + +void c3_isp_rsz_start(struct c3_isp_resizer *rsz) +{ + struct v4l2_subdev_state *state; + + state = v4l2_subdev_lock_and_get_active_state(&rsz->sd); + + c3_isp_rsz_cfg_fmt(rsz, state); + c3_isp_rsz_crop_enable(rsz, state); + c3_isp_rsz_pps_enable(rsz, state); + + c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, + TOP_DISP_EN(rsz->id), TOP_DISP_EN(rsz->id)); + + v4l2_subdev_unlock_state(state); +} + +void c3_isp_rsz_stop(struct c3_isp_resizer *rsz) +{ + c3_isp_update_bits(rsz->isp, ISP_TOP_PATH_EN, TOP_DISP_EN(rsz->id), 0); + c3_isp_update_bits(rsz->isp, C3_DISP_REG(DISP0_TOP_TOP_CTRL, rsz->id), + DISP_CRP2_EN, 0x0); + + c3_isp_rsz_pps_disable(rsz); +} + +static int c3_isp_rsz_cfg_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = C3_ISP_DEFAULT_WIDTH, + .height = C3_ISP_DEFAULT_HEIGHT, + .code = C3_ISP_RSZ_DEF_PAD_FMT, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int c3_isp_rsz_init_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes; + struct v4l2_subdev_krouting routing; + + routes.sink_pad = C3_ISP_RESIZER_PAD_SINK; + routes.sink_stream = 0; + routes.source_pad = C3_ISP_RESIZER_PAD_SOURCE; + routes.source_stream = 0; + routes.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routing.num_routes = 1; + routing.routes = &routes; + + return c3_isp_rsz_cfg_routing(sd, state, &routing); +} + +static int c3_isp_rsz_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + bool is_streaming = v4l2_subdev_is_streaming(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && is_streaming) + return -EBUSY; + + return c3_isp_rsz_cfg_routing(sd, state, routing); +} + +static int c3_isp_rsz_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(c3_isp_rsz_mbus_formats)) + return -EINVAL; + + code->code = c3_isp_rsz_mbus_formats[code->index].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; + const struct c3_isp_mbus_format_info *isp_fmt; + struct v4l2_rect *sink_crop; + struct v4l2_rect *sink_cmps; + + sink_fmt = v4l2_subdev_state_get_format(state, format->pad); + sink_crop = v4l2_subdev_state_get_crop(state, format->pad); + sink_cmps = v4l2_subdev_state_get_compose(state, format->pad); + + isp_fmt = rsz_find_format_by_code(format->format.code, format->pad); + if (!isp_fmt) + sink_fmt->code = C3_ISP_RSZ_DEF_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_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; + + format->format = *sink_fmt; +} + +static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + const struct c3_isp_mbus_format_info *rsz_fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *sink_crop; + struct v4l2_rect *sink_cmps; + + src_fmt = v4l2_subdev_state_get_format(state, format->pad); + sink_cmps = v4l2_subdev_state_get_compose(state, C3_ISP_RESIZER_PAD_SINK); + sink_crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_PAD_SINK); + + rsz_fmt = rsz_find_format_by_code(format->format.code, format->pad); + if (!rsz_fmt) + src_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT; + else + src_fmt->code = format->format.code; + + src_fmt->width = clamp_t(u32, format->format.width, + C3_ISP_MIN_WIDTH, sink_crop->width); + src_fmt->height = clamp_t(u32, format->format.height, + C3_ISP_MIN_HEIGHT, sink_crop->height); + + /* The sink compose size must be same with the source size. */ + sink_cmps->width = src_fmt->width; + sink_cmps->height = src_fmt->height; + + 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_RESIZER_PAD_SINK) { + c3_isp_rsz_set_sink_fmt(state, format); + } else if (format->pad == C3_ISP_RESIZER_PAD_SOURCE) { + c3_isp_rsz_set_source_fmt(state, format); + } else { + dev_err(sd->dev, "Invalid pad: %u\n", format->pad); + return -ENOTTY; + } + + 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_RESIZER_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_rect *crop; + struct v4l2_rect *cmps; + + if (sel->pad == C3_ISP_RESIZER_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); + + 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; + + 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_RESIZER_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 *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *crop; + struct v4l2_rect *cmps; + + sink_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SINK); + sink_fmt->width = C3_ISP_DEFAULT_WIDTH; + sink_fmt->height = C3_ISP_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = C3_ISP_RSZ_DEF_PAD_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_SRGB; + sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + crop = v4l2_subdev_state_get_crop(state, C3_ISP_RESIZER_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_RESIZER_PAD_SINK); + cmps->width = C3_ISP_DEFAULT_WIDTH; + cmps->height = C3_ISP_DEFAULT_HEIGHT; + cmps->left = 0; + cmps->top = 0; + + src_fmt = v4l2_subdev_state_get_format(state, C3_ISP_RESIZER_PAD_SOURCE); + *src_fmt = *sink_fmt; + + return c3_isp_rsz_init_routing(sd, state); +} + +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, + .set_routing = c3_isp_rsz_set_routing, +}; + +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), "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_RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + rsz->pads[C3_ISP_RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, C3_ISP_RESIZER_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) +{ + u32 i; + int ret; + + for (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; + + if (rsz->id == C3_ISP_RSZ_0) + rsz->cap = &isp->caps[C3_ISP_CAP_DEV_0]; + else if (rsz->id == C3_ISP_RSZ_1) + rsz->cap = &isp->caps[C3_ISP_CAP_DEV_1]; + else + rsz->cap = &isp->caps[C3_ISP_CAP_DEV_2]; + + 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) +{ + u32 i; + + for (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..72024442d48f --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/c3-isp-stats.c @@ -0,0 +1,488 @@ +// 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" +#include "include/uapi/c3-isp-config.h" + +/* Hardware configuration */ + +static void c3_isp_stats_af_init(struct c3_isp_stats *stats) +{ + c3_isp_write(stats->isp, ISP_AF_ROI0_WIN01, + AF_ROI_XYWIN_00_INIT | AF_ROI_XYWIN_01_INIT); + + c3_isp_write(stats->isp, ISP_AF_ROI0_WIN23, + AF_ROI_XYWIN_02_INIT | AF_ROI_XYWIN_03_INIT); + + c3_isp_write(stats->isp, ISP_AF_ROI1_WIN01, + AF_ROI_XYWIN_10_INIT | AF_ROI_XYWIN_11_INIT); + + c3_isp_write(stats->isp, ISP_AF_ROI1_WIN23, + AF_ROI_XYWIN_12_INIT | AF_ROI_XYWIN_13_INIT); + + /* 0: old statistics output, 1: new statistics output. */ + c3_isp_update_bits(stats->isp, ISP_AF_EN_CTRL, + AF_STAT_SELECT, 0x1 << AF_STAT_SELECT_SHIFT); +} + +static void c3_isp_stats_ae_init(struct c3_isp_stats *stats) +{ + c3_isp_write(stats->isp, ISP_AE_ROI0_WIN01, + AE_ROI_XYWIN_00_INIT | AE_ROI_XYWIN_01_INIT); + + c3_isp_write(stats->isp, ISP_AE_ROI0_WIN23, + AE_ROI_XYWIN_02_INIT | AE_ROI_XYWIN_03_INIT); + + c3_isp_write(stats->isp, ISP_AE_ROI1_WIN01, + AE_ROI_XYWIN_10_INIT | AE_ROI_XYWIN_11_INIT); + + c3_isp_write(stats->isp, ISP_AE_ROI1_WIN23, + AE_ROI_XYWIN_12_INIT | AE_ROI_XYWIN_13_INIT); + + c3_isp_write(stats->isp, ISP_AE_STAT_THD01, + AE_STAT_THRD0_INIT | AE_STAT_THRD1_INIT); + + c3_isp_write(stats->isp, ISP_AE_STAT_THD23, + AE_STAT_THRD2_INIT | AE_STAT_THRD3_INIT); + + /* Set 0 when ae_stat_switch is not 0 */ + c3_isp_update_bits(stats->isp, ISP_AE_CTRL, + AE_CTRL_INPUT_2LINE_TOGETHER, 0); + + /* Configure ae luma mode */ + c3_isp_update_bits(stats->isp, ISP_AE_CTRL, AE_CTRL_LUMA_MODE_MASK, + AE_CTRL_LUMA_MODE_FILTER << AE_CTRL_LUMA_MODE_SHIFT); +} + +static void c3_isp_stats_awb_init(struct c3_isp_stats *stats) +{ + /* Initialize the awb statistics rectangle of image */ + c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN01, + AWB_ROI_XYWIN_00_INIT | AWB_ROI_XYWIN_01_INIT); + c3_isp_write(stats->isp, ISP_AWB_STAT_ROI0_WIN23, + AWB_ROI_XYWIN_02_INIT | AWB_ROI_XYWIN_03_INIT); + + c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN01, + AWB_ROI_XYWIN_10_INIT | AWB_ROI_XYWIN_11_INIT); + c3_isp_write(stats->isp, ISP_AWB_STAT_ROI1_WIN23, + AWB_ROI_XYWIN_12_INIT | AWB_ROI_XYWIN_13_INIT); +} + +static void c3_isp_stats_cfg_dmawr_addr(struct c3_isp_stats *stats) +{ + struct c3_isp_device *isp = stats->isp; + struct c3_isp_stats_info *stats_info = stats->buff->vaddr; + u32 awb_dma_size = sizeof(stats_info->awb_stats); + u32 ae_dma_size = sizeof(stats_info->ae_stats); + u32 awb_dma_addr = stats->buff->paddr; + 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(isp, VIU_DMAWR_BADDR0, VIU_DMAWR_AF_BADDR_MASK, + VIU_DMAWR_AF_BADDR(af_dma_addr)); + c3_isp_update_bits(isp, VIU_DMAWR_BADDR1, VIU_DMAWR_AWB_BADDR_MASK, + VIU_DMAWR_AWB_BADDR(awb_dma_addr)); + c3_isp_update_bits(isp, VIU_DMAWR_BADDR2, VIU_DMAWR_AE_BADDR_MASK, + VIU_DMAWR_AE_BADDR(ae_dma_addr)); +} + +static void c3_isp_stats_enable(struct c3_isp_stats *stats) +{ + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AE_STAT_EN, TOP_3A_AE_STAT_EN); + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AWB_STAT_EN, TOP_3A_AWB_STAT_EN); + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AF_STAT_EN, TOP_3A_AF_STAT_EN); +} + +static void c3_isp_stats_disable(struct c3_isp_stats *stats) +{ + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AE_STAT_EN, 0); + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AWB_STAT_EN, 0); + c3_isp_update_bits(stats->isp, ISP_TOP_3A_STAT_CRTL, + TOP_3A_AF_STAT_EN, 0); +} + +/* The unit of dma_size is 16 bytes */ +static void c3_isp_stats_cfg_dmawr_size(struct c3_isp_stats *stats) +{ + u32 dma_size; + + dma_size = sizeof(struct af_stats_info) / 16; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, + VIU_DMAWR_SIZE_AF_MASK, dma_size); + + dma_size = sizeof(struct awb_stats_info) / 16; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE0, VIU_DMAWR_SIZE_AWB_MASK, + dma_size << VIU_DMAWR_SIZE_AWB_SHIFT); + + dma_size = sizeof(struct ae_stats_info) / 16; + c3_isp_update_bits(stats->isp, VIU_DMAWR_SIZE1, + VIU_DMAWR_SIZE_AE_MASK, dma_size); +} + +static void c3_isp_stats_cfg_buff(struct c3_isp_stats *stats) +{ + stats->buff = list_first_entry_or_null(&stats->pending, + struct c3_isp_vb2_buffer, list); + if (stats->buff) { + c3_isp_stats_cfg_dmawr_addr(stats); + list_del(&stats->buff->list); + } +} + +static void c3_isp_stats_start(struct c3_isp_stats *stats) +{ + c3_isp_stats_af_init(stats); + c3_isp_stats_ae_init(stats); + c3_isp_stats_awb_init(stats); + + c3_isp_stats_cfg_dmawr_size(stats); + c3_isp_stats_cfg_buff(stats); + c3_isp_stats_enable(stats); + + stats->is_streaming = true; +} + +static void c3_isp_stats_stop(struct c3_isp_stats *stats) +{ + stats->is_streaming = false; + + c3_isp_stats_disable(stats); +} + +static void c3_isp_stats_return_buffers(struct c3_isp_stats *stats, + enum vb2_buffer_state state) +{ + unsigned long flags; + struct c3_isp_vb2_buffer *buff; + + spin_lock_irqsave(&stats->buff_lock, flags); + + if (stats->buff) { + vb2_buffer_done(&stats->buff->vb.vb2_buf, state); + stats->buff = NULL; + } + + while (!list_empty(&stats->pending)) { + buff = list_first_entry(&stats->pending, + struct c3_isp_vb2_buffer, list); + list_del(&buff->list); + vb2_buffer_done(&buff->vb.vb2_buf, state); + } + + spin_unlock_irqrestore(&stats->buff_lock, flags); +} + +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, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +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; + } else { + *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_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&stats->buff_lock, flags); + + list_add_tail(&buf->list, &stats->pending); + + spin_unlock_irqrestore(&stats->buff_lock, flags); +} + +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_vb2_buffer *buf = + container_of(v4l2_buf, struct c3_isp_vb2_buffer, vb); + struct c3_isp_stats *stats = vb2_get_drv_priv(vb->vb2_queue); + + buf->vaddr = vb2_plane_vaddr(vb, 0); + buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + memset(buf->vaddr, 0, stats->vfmt.fmt.meta.buffersize); + + return 0; +} + +static int c3_isp_stats_vb2_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct c3_isp_stats *stats = vb2_get_drv_priv(q); + int ret; + + guard(mutex)(&stats->isp->lock); + + ret = pm_runtime_resume_and_get(stats->isp->dev); + if (ret) + return ret; + + ret = video_device_pipeline_start(&stats->vdev, &stats->isp->pipe); + if (ret) { + dev_err(stats->isp->dev, + "Failed to start stats pipeline: %d\n", ret); + goto err_pm_put; + } + + if (c3_isp_pipeline_ready(stats->isp)) { + ret = v4l2_subdev_enable_streams(&stats->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_STATS, + BIT(0)); + if (ret) + goto err_pipeline_stop; + } + + c3_isp_stats_start(stats); + + return 0; + +err_pipeline_stop: + video_device_pipeline_stop(&stats->vdev); +err_pm_put: + pm_runtime_put(stats->isp->dev); + c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void c3_isp_stats_vb2_stop_streaming(struct vb2_queue *q) +{ + struct c3_isp_stats *stats = vb2_get_drv_priv(q); + + guard(mutex)(&stats->isp->lock); + + c3_isp_stats_stop(stats); + c3_isp_stats_return_buffers(stats, VB2_BUF_STATE_ERROR); + + if (stats->isp->pipe.start_count == 1) + v4l2_subdev_disable_streams(&stats->isp->core.sd, + C3_ISP_CORE_PAD_SOURCE_STATS, + BIT(0)); + + video_device_pipeline_stop(&stats->vdev); + pm_runtime_put(stats->isp->dev); +} + +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, + .start_streaming = c3_isp_stats_vb2_start_streaming, + .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), "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_vb2_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); +} + +int c3_isp_stats_done(struct c3_isp_device *isp) +{ + struct c3_isp_stats *stats = &isp->stats; + struct c3_isp_vb2_buffer *buff = stats->buff; + unsigned long flags; + + if (!stats->is_streaming) + return -EINVAL; + + spin_lock_irqsave(&stats->buff_lock, flags); + + if (buff) { + buff->vb.sequence = stats->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_stats_cfg_buff(stats); + + spin_unlock_irqrestore(&stats->buff_lock, flags); + + return 0; +} diff --git a/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h new file mode 100644 index 000000000000..84ff5741357a --- /dev/null +++ b/drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h @@ -0,0 +1,537 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2024 Amlogic, Inc. All rights reserved + */ + +#ifndef __C3_ISP_CONFIG_H__ +#define __C3_ISP_CONFIG_H__ + +#define AF_STAT_BLKH_NUM 17 +#define AF_STAT_BLKV_NUM 15 +#define AF_STAT_BLK_NUM (AF_STAT_BLKH_NUM * AF_STAT_BLKV_NUM) +/* AF stats block size need to be aligned with 2 */ +#define AF_STAT_BLK_SIZE ALIGN(AF_STAT_BLK_NUM, 2) +#define AE_HISTOGRAM_SIZE 1024 +#define AE_STAT_BLKH_NUM 17 +#define AE_STAT_BLKV_NUM 15 +#define AE_STAT_BLK_NUM (AE_STAT_BLKH_NUM * AE_STAT_BLKV_NUM) +/* AE stats block size need to be aligned with 2 */ +#define AE_STAT_BLK_SIZE ALIGN(AE_STAT_BLK_NUM, 2) +#define AE_BLOCK_WT_NUM 255 +#define AE_BLK_WT_DATA_NUM_OF_GROUP 8 +#define AWB_STAT_BLKH_NUM 32 +#define AWB_STAT_BLKV_NUM 24 +#define AWB_STAT_BLK_NUM (AWB_STAT_BLKH_NUM * AWB_STAT_BLKV_NUM) +/* AWB stats block size need to be aligned with 2 */ +#define AWB_STAT_BLK_SIZE ALIGN(AWB_STAT_BLK_NUM, 2) +#define AWB_BLOCK_WT_NUM 768 +#define AWB_BLK_WT_DATA_NUM_OF_GROUP 8 +#define AWB_STAT_BLC20_NUM 4 +#define AWB_STAT_GAIN10_NUM 4 +#define BLC_OFFSET_NUM 5 +#define GAMMA_LUT_GROUP_NUM 4 +#define GAMMA_LUT_POINT_NUM 129 +#define GAMMA_LUT_DATA_NUM_OF_GROUP 2 + +/** + * struct awb_zone_stats - AWB statistics of a block + * + * AWB zone stats is aligned with 8 bytes + * + * @rg: the ratio of R / G in a zone + * @bg: the ratio of B / G in a zone + * @pixel_sum: the total number of pixels used in a zone + */ +struct awb_zone_stats { + u16 rg; + u16 bg; + u32 pixel_sum; +}; + +/** + * struct awb_stats_info - Auto white balance statistics information. + * + * AWB statistical information of all blocks. + * + * @awb_stats: array of auto white balance statistics + */ +struct awb_stats_info { + struct awb_zone_stats awb_stats[AWB_STAT_BLK_SIZE]; +}; + +/** + * struct ae_zone_stats - AE statistics of a block + * + * AE zone stats is aligned with 8 bytes. + * This is a 5-bin histogram and the total sum is + * normalized to 0xffff. + * So hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4) + * + * @hist0: the global normalized pixel count for bin 0 + * @hist1: the global normalized pixel count for bin 1 + * @hist3: the global normalized pixel count for bin 3 + * @hist4: the global normalized pixel count for bin 4 + */ +struct ae_zone_stats { + u16 hist0; + u16 hist1; + u16 hist3; + u16 hist4; +}; + +/** + * struct ae_stats_info - Exposure statistics information + * + * AE statistical information consists of + * all blocks information and a 1024-bin histogram. + * + * @ae_stats: array of auto exposure block statistics + * @hist: a 1024-bin histogram for the entire image + */ +struct ae_stats_info { + struct ae_zone_stats ae_stats[AE_STAT_BLK_SIZE]; + u32 hist[AE_HISTOGRAM_SIZE]; +}; + +/** + * struct af_zone_stats - AF statistics of a block + * + * AF block stats is aligned with 8 bytes. + * The zonal accumulated contrast metrics are stored + * in floating point format with 16 bits mantissa and + * 5 or 6 bits exponent. + * Apart from contrast metrics we accumulate squared image and + * quartic image data over the zone. + * + * @i2_mat: the mantissa of zonal squared image pixel sum + * @i4_mat: the mantissa of zonal quartic image pixel sum + * @e4_mat: the mantissa of zonal multi-directional quartic edge sum + * @i2_exp: the exponent of zonal squared image pixel sum + * @i4_exp: the exponent of zonal quartic image pixel sum + * @e4_exp: the exponent of zonal multi-directional quartic edge sum + */ +struct af_zone_stats { + u16 i2_mat; + u16 i4_mat; + u16 e4_mat; + u16 i2_exp: 5; + u16 i4_exp: 6; + u16 e4_exp: 5; +}; + +/** + * struct af_stats_info - Auto Focus statistics information + * + * AF statistical information of each block + * + * @af_stats: array of auto focus block statistics + */ +struct af_stats_info { + struct af_zone_stats af_stats[AF_STAT_BLK_SIZE]; +}; + +/** + * struct c3_isp_stats_info - V4L2_META_FMT_C3ISP_STATS + * + * Contains ISP statistics + * + * @awb_stats: auto white balance stats + * @ae_stats: auto exposure stats + * @af_stats: auto focus stats + */ +struct c3_isp_stats_info { + struct awb_stats_info awb_stats; + struct ae_stats_info ae_stats; + struct af_stats_info af_stats; +}; + +/** + * enum c3_isp_param_buffer_version - C3 ISP parameters block versioning + * + * @C3_ISP_PARAM_BUFFER_V0: First version of C3 ISP parameters block + */ +enum c3_isp_param_buffer_version { + C3_ISP_PARAM_BUFFER_V0, +}; + +/** + * enum c3_isp_param_block_type - Enumeration of C3 ISP parameter blocks + * + * Each block configures a specific processing block of the C3 ISP. + * The block type allows the driver to correctly interpret + * the parameters block data. + * + * @C3_ISP_PARAM_BLOCK_WB_CHANGE: White balance change parameters + * @C3_ISP_PARAM_BLOCK_WB_LUMA: White balance Luma-based parameters + * @C3_ISP_PARAM_BLOCK_WB_TRIANGLE: White balance triangle parameters + * @C3_ISP_PARAM_BLOCK_AWB_STATS: AWB statistics parameters + * @C3_ISP_PARAM_BLOCK_AE_STATS: AE statistics parameters + * @C3_ISP_PARAM_BLOCK_AF_STATS: AF statistics parameters + * @C3_ISP_PARAM_BLOCK_PST_GAMMA: post gamma parameters + * @C3_ISP_PARAM_BLOCK_DMSC: demosaic parameters + * @C3_ISP_PARAM_BLOCK_CCM: Color correction matrix parameters + * @C3_ISP_PARAM_BLOCK_CSC: Color space conversion parameters + * @C3_ISP_PARAM_BLOCK_BLC: Black level correction parameters + * @C3_ISP_PARAM_BLOCK_SENTINEL: First non-valid block index + */ +enum c3_isp_param_block_type { + C3_ISP_PARAM_BLOCK_WB_CHANGE, + C3_ISP_PARAM_BLOCK_WB_LUMA, + C3_ISP_PARAM_BLOCK_WB_TRIANGLE, + C3_ISP_PARAM_BLOCK_AWB_STATS, + C3_ISP_PARAM_BLOCK_AE_STATS, + C3_ISP_PARAM_BLOCK_AF_STATS, + C3_ISP_PARAM_BLOCK_PST_GAMMA, + C3_ISP_PARAM_BLOCK_DMSC, + C3_ISP_PARAM_BLOCK_CCM, + C3_ISP_PARAM_BLOCK_CSC, + C3_ISP_PARAM_BLOCK_BLC, + C3_ISP_PARAM_BLOCK_SENTINEL +}; + +/** + * struct c3_isp_param_block_header - C3 ISP parameter block header + * + * This structure represents the common part of all the ISP configuration + * blocks. Each parameters block shall embed an instance of this structure type + * as its first member, followed by the block-specific configuration data. The + * driver inspects this common header to discern the block type and its size and + * properly handle the block content by casting it to the correct block-specific + * type. + * + * @type: The parameters block type (enum c3_isp_param_block_type) + * @enabled: Block enabled/disabled flag + * @size: Size (in bytes) of parameters block + */ + +struct c3_isp_param_block_header { + enum c3_isp_param_block_type type; + bool enabled; + size_t size; +}; + +/** + * struct wb_change_cfg - White Balance configuration + * + * @header: The C3 ISP parameters block header + * @wb_gain: white balance gain of each color + * wb_gain[0]: Gr gain, range 0~0xfff + * wb_gain[1]: R gain, range 0~0xfff + * wb_gain[2]: B gain, range 0~0xfff + * wb_gain[3]: Gb gain, range 0~0xfff + * wb_gain[4]: Ir gain, range 0~0xfff + * @wb_limit: white balance limit of each color + * wb_limit[0]: Gr limit, 16 bits float + * wb_limit[1]: R limit, 16 bits float + * wb_limit[2]: B limit, 16 bits float + * wb_limit[3]: Gb limit, 16 bits float + * wb_limit[4]: Ir limit, 16 bits float + * @ae_gain_grbgi: Gain of each color before blending to luma + * ae_gain_grbgi[0]: Gr gain, range 0~255 + * ae_gain_grbgi[1]: R gain, range 0~255 + * ae_gain_grbgi[2]: B gain, range 0~255 + * ae_gain_grbgi[3]: Gb gain, range 0~255 + * ae_gain_grbgi[4]: Ir gain, range 0~255 + * @ae_bl12_grbgi: Offset of each color before ae_gain_grbgi + * ae_bl12_grbgi[0]: Gr offset, range 0~4095 + * ae_bl12_grbgi[1]: R offset, range 0~4095 + * ae_bl12_grbgi[2]: B offset, range 0~4095 + * ae_bl12_grbgi[3]: Gb offset, range 0~4095 + * ae_bl12_grbgi[4]: Ir offset, range 0~4095 + */ +struct wb_change_cfg { + struct c3_isp_param_block_header header; + u32 wb_gain[5]; + u32 wb_limit[5]; + u32 ae_gain_grbgi[5]; + u32 ae_bl12_grbgi[5]; +}; + +/** + * struct wb_luma_cfg - White Balance Luma-based configuration + * + * @header: The C3 ISP parameters block header + * @awb_stat_blc20: BLC in AWB statistic + * awb_stat_blc20[0]: Gr blc, range 0~0xfffff + * awb_stat_blc20[1]: R blc, range 0~0xfffff + * awb_stat_blc20[2]: B blc, range 0~0xfffff + * awb_stat_blc20[3]: Gb blc, range 0~0xfffff + * @awb_stat_gain10: Gain in AWB statistic + * awb_stat_gain10[0]: Gr gain, range 0~1023 + * awb_stat_gain10[1]: R gain, range 0~1023 + * awb_stat_gain10[2]: B gain, range 0~1023 + * awb_stat_gain10[3]: Gb gain, range 0~1023 + * @awb_stat_satur_low: AWB statistic under-saturation threshold + * value: range 0~65535 + * @awb_stat_satur_high: AWB statistic over-saturation threshold + * value: range 0~65535 + */ +struct wb_luma_cfg { + struct c3_isp_param_block_header header; + u32 awb_stat_blc20[AWB_STAT_BLC20_NUM]; + u32 awb_stat_gain10[AWB_STAT_GAIN10_NUM]; + u32 awb_stat_satur_low; + u32 awb_stat_satur_high; +}; + +/** + * struct wb_triangle_cfg - White Balance Triangle + * + * @header: The C3 ISP parameters block header + * @awb_stat_satur_vald: AWB statistic over saturation control + * value: 0: disable, 1: enable + * @awb_stat_rg_min: min value of r/g + * value: 0~4095 + * @awb_stat_rg_max: max value of r/g + * value: 0~4095 + * @awb_stat_bg_min: min value of b/g + * value: 0~4095 + * @awb_stat_bg_max: max value of b/g + * value: 0~4095 + * @awb_stat_rg_low: low value of r/g + * value: 0~4095 + * @awb_stat_rg_high: high value of r/g + * value: 0~4095 + * @awb_stat_bg_low: low value of b/g + * value: 0~4095 + * @awb_stat_bg_high: high value of b/g + * value: 0~4095 + */ +struct wb_triangle_cfg { + struct c3_isp_param_block_header header; + u32 awb_stat_satur_vald; + u32 awb_stat_rg_min; + u32 awb_stat_rg_max; + u32 awb_stat_bg_min; + u32 awb_stat_bg_max; + u32 awb_stat_rg_low; + u32 awb_stat_rg_high; + u32 awb_stat_bg_low; + u32 awb_stat_bg_high; +}; + +/** + * struct awb_stats_cfg - AWB statistics configuration + * + * This structure contains AWB statistics control information. + * + * @header: The C3 ISP parameters block header + * @awb_stat_switch: the switch of AWB statistics + * value: 0~7 + * @awb_stat_blk_weight: Array of weights for AWB statistics blocks + * value: 0~15 + */ +struct awb_stats_cfg { + struct c3_isp_param_block_header header; + u8 awb_stat_switch; + u32 awb_stat_blk_weight[AWB_BLOCK_WT_NUM]; +}; + +/** + * struct ae_stats_cfg - AE statistics configuration + * + * This structure contains AE statistics control information. + * + * @header: The C3 ISP parameters block header + * @ae_stat_switch: the switch of AE statistics + * value: 0~3 + * @ae_stat_blk_weight: Array of weights for AE statistics blocks + * value: 0~15 + */ +struct ae_stats_cfg { + struct c3_isp_param_block_header header; + u8 ae_stat_switch; + u32 ae_stat_blk_weight[AE_BLOCK_WT_NUM]; +}; + +/** + * struct af_stats_cfg - AF statistics configuration + * + * This structure contains AF statistics control information. + * + * @header: The C3 ISP parameters block header + * @af_stat_switch: the switch of AF statistics + * value: 0~3 + */ +struct af_stats_cfg { + struct c3_isp_param_block_header header; + u8 af_stat_switch; +}; + +/** + * struct pst_gamma_cfg - Post gamma configuration + * + * This structure contains post gamma parameters + * + * @header: The C3 ISP parameters block header + * @pst_gamma_lut: LUT for P-Stitch gamma + * value: 0~65535 + */ +struct pst_gamma_cfg { + struct c3_isp_param_block_header header; + u32 pst_gamma_lut[GAMMA_LUT_GROUP_NUM][GAMMA_LUT_POINT_NUM]; +}; + +/** + * struct dmsc_cfg - Demosaic configuration + * + * This structure contains demosaic parameters + * + * @header: The C3 ISP parameters block header + */ +struct dmsc_cfg { + struct c3_isp_param_block_header header; +}; + +/** + * struct ccm_cfg - ISP CCM configuration + * + * This structure holds the parameters for configuring the CCM, + * which is used for color correction. + * + * @header: The C3 ISP parameters block header + * @ccm_4x3matrix: A 3x4 matrix used for color correction + * value: 0~8191 + */ +struct ccm_cfg { + struct c3_isp_param_block_header header; + u32 ccm_4x3matrix[3][4]; +}; + +/** + * struct csc_cfg - ISP Color Space Conversion configuration + * + * This structure contains settings for color space conversion. + * + * @header: The C3 ISP parameters block header + * @cm0_offset_inp: Input offset values for the 0-order color matrix + * value: 0~8191 + * @cm0_offset_oup: Output offset values for the 0-order color matrix + * value: 0~8191 + * @cm0_3x3mtrx_rs: matrix right shift for cm0 + * value: 0~3 + * @cm0_3x3matrix: A 3x3 matrix used for the color matrix + * value: 0~8191 + */ +struct csc_cfg { + struct c3_isp_param_block_header header; + u32 cm0_offset_inp[3]; + u32 cm0_offset_oup[3]; + u32 cm0_3x3mtrx_rs; + u32 cm0_3x3matrix[3][3]; +}; + +/** + * struct blc_cfg - ISP Black Level Correction (BLC) configuration + * + * This structure holds the parameters for BLC in image processing. + * + * @header: The C3 ISP parameters block header + * @fe_bl_ofst: Array of front-end BLC offsets for each color each channels + * fe_bl_ofst[0]: Gr blc offset, range 0~0x1fffff + * fe_bl_ofst[1]: R blc offset, range 0~0x1fffff + * fe_bl_ofst[2]: B blc offset, range 0~0x1fffff + * fe_bl_ofst[3]: Gb blc offset, range 0~0x1fffff + * fe_bl_ofst[4]: Ir blc offset, range 0~0x1fffff + * @blc_ofst: Array of LSWB BLC offsets + * blc_ofst[0]: Gr blc offset, 16 bits float + * blc_ofst[1]: R blc offset, 16 bits float + * blc_ofst[2]: B blc offset, 16 bits float + * blc_ofst[3]: Gb blc offset, 16 bits float + * blc_ofst[4]: Ir blc offset, 16 bits float + */ +struct blc_cfg { + struct c3_isp_param_block_header header; + u32 fe_bl_ofst[BLC_OFFSET_NUM]; + u32 blc_ofst[BLC_OFFSET_NUM]; +}; + +/** + * define C3_ISP_PARAMS_MAX_SIZE - Maximum size of all C3 ISP Parameters + * + * Though the parameters for the C3 ISP are passed as optional blocks, the + * driver still needs to know the absolute maximum size so that it can allocate + * a buffer sized appropriately to accommodate userspace attempting to set all + * possible parameters in a single frame. + */ +#define C3_ISP_PARAMS_MAX_SIZE \ + (sizeof(struct wb_change_cfg) + \ + sizeof(struct wb_luma_cfg) + \ + sizeof(struct wb_triangle_cfg) + \ + sizeof(struct awb_stats_cfg) + \ + sizeof(struct ae_stats_cfg) + \ + sizeof(struct af_stats_cfg) + \ + sizeof(struct pst_gamma_cfg) + \ + sizeof(struct dmsc_cfg) + \ + sizeof(struct ccm_cfg) + \ + sizeof(struct csc_cfg) + \ + sizeof(struct blc_cfg)) + +/** + * struct c3_isp_params_buffer - C3 ISP configuration parameters + * + * This struct contains the configuration parameters of the C3 ISP + * algorithms, serialized by userspace into an opaque data buffer. Each + * configuration parameter block is represented by a block-specific structure + * which contains a :c:type:`c3_isp_param_block_header` entry as first + * member. Userspace populates the @data buffer with configuration parameters + * for the blocks that it intends to configure. As a consequence, the data + * buffer effective size changes according to the number of ISP blocks that + * userspace intends to configure. + * + * The parameters buffer is versioned by the @version field to allow modifying + * and extending its definition. Userspace should populate the @version field to + * inform the driver about the version it intends to use. The driver will parse + * and handle the @data buffer according to the data layout specific to the + * indicated revision and return an error if the desired revision is not + * supported. + * + * For each ISP block that userspace wants to configure, a block-specific + * structure is appended to the @data buffer, one after the other without gaps + * in between nor overlaps. Userspace shall populate the @total_size field with + * the effective size, in bytes, of the @data buffer. + * + * The expected memory layout of the parameters buffer is:: + * + * +-------------------- struct c3_isp_params_buffer ------------------+ + * | version = C3_ISP_PARAM_BUFFER_V0; | + * | total_size = sizeof(sizeof(struct wb_change_cfg)) | + * | sizeof(sizeof(struct wb_luma_cfg)); | + * | +------------------------- data ---------------------------------+ | + * | | +------------------ struct wb_change_cfg) --------------------+ | | + * | | | +--------- struct c3_isp_param_block_header header -----+ | | | + * | | | | type = C3_ISP_PARAM_BLOCK_WB_CHANGE; | | | | + * | | | | enabled = true; | | | | + * | | | | size = | | | | + * | | | | sizeof(struct c3_isp_param_block_header header); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | wb_gain[5] = ...; | | | + * | | | wb_limit[5] = ...; | | | + * | | | ae_gain_grbgi[5] = ...; | | | + * | | | ae_bl12_grbgi[5] = ...; | | | + * | | +------------------ struct wb_luma_cfg -----------------------+ | | + * | | | +---------- struct c3_isp_param_block_header header ------+ | | | + * | | | | type = C3_ISP_PARAM_BLOCK_WB_LUMA; | | | | + * | | | | enabled = true; | | | | + * | | | | size = sizeof(struct wb_luma_cfg); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | awb_stat_blc20[4] = ...; | | | + * | | | awb_stat_gain10[4] = ...; | | | + * | | | awb_stat_satur_low = ...; | | | + * | | | awb_stat_satur_high = ...; | | | + * | | +-------------------------------------------------------------+ | | + * | +-----------------------------------------------------------------+ | + * +---------------------------------------------------------------------+ + * + * @version: The C3 ISP parameters buffer version + * @total_size: The C3 ISP configuration data effective size, + * excluding this header + * @data: The C3 ISP configuration blocks data + */ +struct c3_isp_params_buffer { + enum c3_isp_param_buffer_version version; + size_t total_size; + u8 data[C3_ISP_PARAMS_MAX_SIZE]; +}; + +#endif From patchwork Wed Sep 18 06:07:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806587 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 E9FED1662E4; Wed, 18 Sep 2024 06:07:36 +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=1726639657; cv=none; b=O3ms/5o2+SgoyHq/JFub5NJBI6dR9xAHeljRTnv5mycIyQS1rYYjjaG28qy8qNVyOxsJeNxP3KyLQR52bNSUwYXTo+ze0HCATXekeOCd51fyxk9fs7Ef8/+C6NWeobqIHKdsb1BpA87GTE8kT8nqhFObfkbVkoLezZwuu9u59tE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639657; c=relaxed/simple; bh=4+Kjm45EumF1BZkBC+vch/qxAQMK2SWvZ+EcU8j+cTA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aWalo9dl1+XcKbFs0CXBKwQ0Ybp8AVLQA4Io0O0+2zS+rB/zlIBcgjRS6NwK1VP625XCbJjiQvWStULq27HTJV4Z9qIjEqXuJqzfNqWYsbvfggw1kHzvYcWSzQDsAT2SXO+5rBIkfgt9jkiWcZvTqGVHSnUZUY95dXzSP5Pgmn0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ccu5jhtP; 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="ccu5jhtP" Received: by smtp.kernel.org (Postfix) with ESMTPS id A3C5EC4CEE2; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=4+Kjm45EumF1BZkBC+vch/qxAQMK2SWvZ+EcU8j+cTA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ccu5jhtP1wFFhLTJ2Jxp1o+q714FG0m0mRoJ68ybmQHTRIKDrRhFD/oXVBk+/sn3x uCKyk+LUDsUbWVwaOTbn35235JeH0pD4Mvl1qh4IbthZALkp5OCtt/Qb6250sj0A4v oxcnk0A4q5gsMlHAAZwZEsLiNFHKM7sILNcIJAxZ9gMDwgLbKGT9HylRKCns+ZJMOq MipM6h4acrvLce7X74OjBgF4xwdFkHVWsK0q/0Yc1hq5Wsd7d+dI1cZCyh4xHboyDO sNfMQ7b02ArPX7cMszuECn5zrXX26W4VngPT48r/LE6P51e8T+d1pfutwFvuPA2+6H 6pN6Iy1krN6EA== 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 986ABCAC589; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:19 +0800 Subject: [PATCH v3 8/9] Documentation: media: add documentation file metafmt-c3-isp.rst Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240918-c3isp-v3-8-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=2506; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=rUBd8+yUBOxLjKjvST1rZmnj73w+JEufrqFVM56kfIk=; b=umhZtXyWm5l3d16imxC3hRyCvlhDSs+brlI2w+6I5pg2d79B0fSj2Kor1GABxoWgSCO6axCwJ TNdX7IB2i1hCjo8HjSm3D9L0mZngrJQUAI95+jptQ3Dua/Jpu8xNMn1 X-Developer-Key: i=keke.li@amlogic.com; a=ed25519; pk=XxNPTsQ0YqMJLLekV456eoKV5gbSlxnViB1k1DhfRmU= X-Endpoint-Received: by B4 Relay for keke.li@amlogic.com/20240902 with auth_id=204 X-Original-From: Keke Li Reply-To: keke.li@amlogic.com From: Keke Li Add the file 'metafmt-c3-isp.rst' that documents the meta format of c3-isp. Signed-off-by: Keke Li --- .../userspace-api/media/v4l/meta-formats.rst | 1 + .../userspace-api/media/v4l/metafmt-c3-isp.rst | 39 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst index c6e56b5888bc..3a420747f6e7 100644 --- a/Documentation/userspace-api/media/v4l/meta-formats.rst +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst @@ -12,6 +12,7 @@ These formats are used for the :ref:`metadata` interface only. .. toctree:: :maxdepth: 1 + metafmt-c3-isp metafmt-d4xx metafmt-generic metafmt-intel-ipu3 diff --git a/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst new file mode 100644 index 000000000000..bbaf3542c1ca --- /dev/null +++ b/Documentation/userspace-api/media/v4l/metafmt-c3-isp.rst @@ -0,0 +1,39 @@ +.. SPDX-License-Identifier: (GPL-2.0-only OR MIT) + +.. _v4l2-meta-fmt-c3isp-stats: +.. _v4l2-meta-fmt-c3isp-params: + +*********************************************************************** +V4L2_META_FMT_C3ISP_STATS ('CSTS'), V4L2_META_FMT_C3ISP_PARAMS ('CPRM') +*********************************************************************** + +.. c3_isp_stats_info + +3A statistics +============= + +The C3 ISP can collect different statistics over an input Bayer frame. +Those statistics are obtained from the "isp-stats" metadata capture video nodes, +using the :c:type:`v4l2_meta_format` interface. +They are formatted as described by the :c:type:`c3_isp_stats_info` structure. + +The statistics collected are Auto-white balance, +Auto-exposure and Auto-focus information. + +.. c3_isp_params_buffer + +Pipeline parameters +=================== + +The pipeline parameters are passed to the "isp-params" metadata +output video nodes, using the :c:type:`v4l2_meta_format` interface. They are +formatted as described by the :c:type:`c3_isp_params_buffer` structure. + +The structure c3_isp_params_buffer includes the necessary parameters +required by the ISP. +These parameters are written to the ISP hardware. + +Amlogic C3 ISP uAPI data types +=============================== + +.. kernel-doc:: drivers/media/platform/amlogic/c3-isp/include/uapi/c3-isp-config.h From patchwork Wed Sep 18 06:07:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Keke Li via B4 Relay X-Patchwork-Id: 13806588 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 E4528165EE8; Wed, 18 Sep 2024 06:07:36 +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=1726639657; cv=none; b=Yk92qhTzviB4q8N7/pzqvtqIohV0mkG6bDz+qBH0V5z97wq/nsDHLeV/oDMRthQWi1UuFTcvSovNtvp/Nng2/I+bF3YJWMTy1pP8KM/GqJYLzHsiipyd1+h95MQ4L//zfe4wcadjnj2SXNwTdbIe8egI8atO8Uq8WL5OxW3+etY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1726639657; c=relaxed/simple; bh=ZgEEH+wRMBm1vMdsV77DX+s0PoxJgWCyCun3sZdgxmc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=lRbMyWjhkOlHWJn9dwqC4TV9t3MFV+qChrbyD7XP+6S+SFbfZhT6vhwak48GD3t+HjuRaBv6TUdOnPhOr7un/M0WNMNpkl2kZBr8idY3+5PR30Fhq12vEq6Kd1HxqdweQGTfWXBjV4H1nkFYpNMSX1wO2i1wa2KszfXsQyTZtX0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=tzJ2P9se; 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="tzJ2P9se" Received: by smtp.kernel.org (Postfix) with ESMTPS id B1CFDC4CEC3; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1726639656; bh=ZgEEH+wRMBm1vMdsV77DX+s0PoxJgWCyCun3sZdgxmc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=tzJ2P9seTpwIL5kQD5bCrzHiuHe82Bd70wxsFormQM3s+gE6x9Ty4DMFvJlCF1FmW aWlo7z/LGtYpVUu5HGiW9NsZiySFzO6WS7QbWlB7YzXW4RJnwolCh0GRrkCmtzsney yiAZIkM1VfC6OMTD8JMeWQ9io+m8RMPt1tBvXOigH1BHCsZY6W/DMvVpW47om4vpsF 4PDkaXsSpL94joDc5cSxRNRUPpDRaFbNsZ4UOug7EWAXzLbSaw2pnqnZFd12Jd8ion 4Q+4v0nkFtz4NH3tx2oUFbDh1xs9U/42uN1WQ1HNa2rfwZukMwKOsv0gxPKiQeIf+c tjOD5lziHljRg== 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 A9CAEC3ABB2; Wed, 18 Sep 2024 06:07:36 +0000 (UTC) From: Keke Li via B4 Relay Date: Wed, 18 Sep 2024 14:07:20 +0800 Subject: [PATCH v3 9/9] 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: <20240918-c3isp-v3-9-f774a39e6774@amlogic.com> References: <20240918-c3isp-v3-0-f774a39e6774@amlogic.com> In-Reply-To: <20240918-c3isp-v3-0-f774a39e6774@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, Keke Li X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1726639635; l=7366; i=keke.li@amlogic.com; s=20240902; h=from:subject:message-id; bh=P5dC7l+ZbCbctqEJAFTW5TGDWTvTCyS+gW8/VP6XmsY=; b=ZVGKUMBHYRHxghyZ9wRt0VfbUi6jDWihrl46xLKcM/f/G8ZzTFGxeNkj/u41dEmo48GWtV5s1 BDmDuJtW4bPDiIDNBTJ+608gm1fTNiOQ4NXYTSFmpyjQcD+l4FxqFs1 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 | 96 +++++++++++++++++++++++++ Documentation/admin-guide/media/v4l-drivers.rst | 1 + MAINTAINERS | 10 +++ 4 files changed, 133 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..0cc1b8b96404 --- /dev/null +++ b/Documentation/admin-guide/media/c3-isp.dot @@ -0,0 +1,26 @@ +digraph board { + rankdir=TB + n00000001 [label="{{ 0 | 1} | isp-core\n/dev/v4l-subdev0 | { 2 | 3}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port3 -> n00000006:port0 [style=bold] + n00000001:port3 -> n00000009:port0 [style=bold] + n00000001:port3 -> n0000000c:port0 [style=bold] + n00000001:port2 -> n00000020 [style=bold] + n00000006 [label="{{ 0} | isp-resizer0\n/dev/v4l-subdev1 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000006:port1 -> n00000014 [style=bold] + n00000009 [label="{{ 0} | isp-resizer1\n/dev/v4l-subdev2 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000009:port1 -> n00000018 [style=bold] + n0000000c [label="{{ 0} | isp-resizer2\n/dev/v4l-subdev3 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000c:port1 -> n0000001c [style=bold] + n0000000f [label="{{ 0} | mipi-adapter\n/dev/v4l-subdev4 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000f:port1 -> n00000001:port0 [style=bold] + n00000014 [label="isp-video0\n/dev/video0", shape=box, style=filled, fillcolor=yellow] + n00000018 [label="isp-video1\n/dev/video1", shape=box, style=filled, fillcolor=yellow] + n0000001c [label="isp-video2\n/dev/video2", shape=box, style=filled, fillcolor=yellow] + n00000020 [label="isp-stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow] + n00000024 [label="isp-params\n/dev/video4", shape=box, style=filled, fillcolor=yellow] + n00000024 -> n00000001:port1 [style=bold] + n00000038 [label="{{ 0} | mipi-csi2\n/dev/v4l-subdev5 | { 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000038:port1 -> n0000000f:port0 [style=bold] + n0000003d [label="{{} | imx290 2-001a\n/dev/v4l-subdev6 | { 0}}", shape=Mrecord, style=filled, fillcolor=green] + n0000003d:port0 -> n00000038: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..fab10c962465 --- /dev/null +++ b/Documentation/admin-guide/media/c3-isp.rst @@ -0,0 +1,96 @@ +.. 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. + +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 mipi-csi2 sub-device - mipi-csi2 is represented by a single sub-device. +- 1 mipi-adapter sub-device - mipi-adapter is represented by a single sub-devices. +- 1 isp-core sub-device - isp-core is represented by a single sub-devices. +- 3 isp-resizer sub-devices - isp-resizer is represented by a number of sub-devices + equal to the number of capture device. + +isp-core sub-device is linked to 2 separate video device nodes and +3 isp-resizer sub-devices nodes. + +- 1 capture statistics video device node. +- 1 output parameters video device node. +- 3 isp-resizer sub-device nodes. + +isp-resizer sub-device is linked to capture video device node. + +- isp-resizer0 is linked to isp-cap0 +- isp-resizer1 is linked to isp-cap1 +- isp-resizer2 is linked to 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 'isp-params' video device node. +Acquiring statistics of ISP hardware via 'isp-stats' video device node. +Acquiring output image of ISP hardware via 'isp-video[0, 2]' video device node. + +The output size of the scaler module in the ISP is configured with +the pixel format of 'isp-video[0, 2]' video device node. diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index b6af448b9fe9..be0a8a860f39 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 31168c05f304..954dd9bdf77e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1209,6 +1209,16 @@ 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/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/ + AMLOGIC MIPI ADAPTER DRIVER M: Keke Li L: linux-media@vger.kernel.org