From patchwork Thu Sep 5 22:52:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 826142 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 C02CF1482E9; Thu, 5 Sep 2024 22:53:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576809; cv=none; b=Uf9KVUmdlnDcEEm25Hvhdl3JsS20+djvByZ0m36DN6UoVnHTqGtXrOe45gPCsJ3y74qLpJQSXMGmkLk1FvmsUL4JHMhG24fprjPVxb0QKLAaW/HVcZYXt+Sh9Ii8l1y+JzpBnybbAdOxOodKVhg+UzJXilxOyk/giWrvbHudFs8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576809; c=relaxed/simple; bh=uHE6wk+VfJ2HylWD36/7bT3qyYq8QnInc44rK8lUwcM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=aGshzlmsYA2gqnkUxGimqgN9X5CmLbKN9TqlAn9tNF7HXa2A+KZfz4cRpaXcD4EC9s4GXw3DqolNgecSWJOOFlkrvsKGGegSNg43JAquqK3LliMLVb6gafwE4wHySEHHqhwVHlKo7ulSY1Jn6wf/uTBxdsVD60jPX+TGLLTFmbo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=QQcI1qIr; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QQcI1qIr" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5DF628BF; Fri, 6 Sep 2024 00:52:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576722; bh=uHE6wk+VfJ2HylWD36/7bT3qyYq8QnInc44rK8lUwcM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QQcI1qIr1oUN+oZVF/gcBaSTQufLZzwr+oKY8iZ+JOCsiL5CW+KSBZPuXLgaJMOxJ xqAJcKIWEn7Q0/abRRFZMp9keTTbCv2PHS4jV9naTwtyOnxIPqGurDQm9NFJDTi//t XYUrirtO5/SNf97VaF2ERwIhT1Vl4ZCeQEkZugFU= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 1/9] media: dt-bindings: media: i2c: Add AR0144 camera sensor Date: Fri, 6 Sep 2024 01:52:59 +0300 Message-ID: <20240905225308.11267-2-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add device tree binding for the onsemi AR0144 CMOS camera sensor. The binding supports both the monochrome and color sensor versions. Signed-off-by: Laurent Pinchart --- Changes since v1: - Drop clock-names - Rename vdd_io-supply to vddio supply - Drop unnecessary | --- .../bindings/media/i2c/onnn,ar0144.yaml | 162 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 168 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml b/Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml new file mode 100644 index 000000000000..50e0908b6473 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/onnn,ar0144.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ON Semiconductor AR0144 MIPI CSI-2 sensor + +maintainers: + - Krzysztof Hałasa + +description: | + The AR0144 is a 1/4" 1MP raw CMOS image sensor with MIPI CSI-2 and parallel + outputs, and an I2C-compatible control interface. + + The sensor comes in a regular CMOS version (AR0144CS) and an automotive + version (AR0144AT). The former is further declined in a colour version + (AR0144CSSC) and a monochrome version (AR0144CSSM), while the later exists + only in monochrome (AR0144ATSM) + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + enum: + - onnn,ar0144c # Color version + - onnn,ar0144m # Monochrome version + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + vaa-supply: + description: + Supply for the VAA and VAA_PIX analog power rails (2.8V). + + vdd-supply: + description: + Supply for the VDD and VDD_PHY digital power rails (1.2V). + + vddio-supply: + description: + Supply for the VDD_IO digital I/O power rail (1.8V or 2.8V). + + reset-gpios: + description: Reset GPIO, active low. + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Video output port. + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + enum: + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + - 5 # MEDIA_BUS_TYPE_PARALLEL + + data-lanes: + items: + - const: 1 + - const: 2 + + bus-width: + enum: [8, 10, 12] + default: 12 + + data-shift: + enum: [0, 2, 4] + default: 0 + + pclk-sample: + enum: [0, 1] + default: 0 + + allOf: + - if: + properties: + bus-type: + const: 4 # MEDIA_BUS_TYPE_CSI2_DPHY + then: + properties: + bus-width: false + data-shift: false + pclk-sample: false + required: + - data-lanes + else: + properties: + data-lanes: false + - if: + properties: + bus-width: + const: 10 + then: + properties: + data-shift: + enum: [0, 2] + - if: + properties: + bus-width: + const: 12 + then: + properties: + data-shift: + const: 0 + + required: + - bus-type + +required: + - compatible + - reg + - clocks + - clock-names + - vaa-supply + - vdd-supply + - vddio-supply + - port + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@10 { + compatible = "onnn,ar0144c"; + reg = <0x10>; + + clocks = <&cam_clk>; + + reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; + + vaa-supply = <®_2v8>; + vdd-supply = <®_1v2>; + vddio-supply = <®_1v8>; + + port { + ar0144_out: endpoint { + remote-endpoint = <&mipi_csi2_in>; + bus-type = ; + data-lanes = <1 2>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index d7c9fced9db1..93f758624830 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1650,6 +1650,12 @@ S: Supported W: http://www.aquantia.com F: drivers/net/ethernet/aquantia/atlantic/aq_ptp* +AR0144 ONSEMI CAMERA SENSOR DRIVER +M: Laurent Pinchart +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml + AR0521 ON SEMICONDUCTOR CAMERA SENSOR DRIVER M: Krzysztof Hałasa L: linux-media@vger.kernel.org From patchwork Thu Sep 5 22:53:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 825749 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 993421B12CD; Thu, 5 Sep 2024 22:53:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576813; cv=none; b=f9mW9CXC56+HU6rCQJzgm10bfeO/3VZctLr6mQM8z60hnAL9YPLbdXt7TsNT9kt2lkNCGbDFNBODj9E3HYSjNs0j2YhD3FLJ3H7X0/qjk57UuTKSY2xTF8Ct57TDvd1DnjcgSxUN2T7RF9T2Kwll1LZA8F8Zcm8eIqUOwuDpnrE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576813; c=relaxed/simple; bh=k+yKoKldHKcAKCqk2qswLRo5AucHDhxyId7IGr6zF24=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=LqyGIt9IfoeUGiQvyDMWck6j/JlH1RXfaWOhwChz7G98HKugOFEbq0/v/vd8SHbv+MbBmLuONdIEHnj57jLrBOXuSiSy/hUyGrhwq9Y/trFeXknXtpdi4dg8auXufjh7NFHlWYqO7+RELzcGO+aekGvMIgpCorPenpNPt3Y2mCs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=k4otQwwr; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="k4otQwwr" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E5E2EFEF; Fri, 6 Sep 2024 00:52:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576725; bh=k+yKoKldHKcAKCqk2qswLRo5AucHDhxyId7IGr6zF24=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k4otQwwr/bdH8nUNlenhqWDpcQ0lPP2ZOtycbCz3AXDCnpopHbtqgr2Env34wCM6w slLQa4btJsZjmw3DQaIscmRSHdWxeoCt222Ry8hSyIP9XFflyCBYQffQNP+LU78+8n YrIfcp4HE0F8/gx7kx76+HWIN4zKSLd1PH5l1hbo= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 2/9] media: i2c: Add a driver for the onsemi AR0144 camera sensor Date: Fri, 6 Sep 2024 01:53:00 +0300 Message-ID: <20240905225308.11267-3-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The AR0144 is a 1/4" 1MP CMOS camera sensor from onsemi. The driver supports both the monochrome and color versions, and both the parallel and MIPI CSI-2 interfaces. Due to limitations of the test platform, only the CSI-2 output has been tested. The driver supports 8-, 10- and 12-bit output formats, analog crop and skipping. It exposes controls that cover the usual use cases for camera sensors. Signed-off-by: Laurent Pinchart --- Changes since v1: - Add binning support to todo list - Support AT version in chip ID check - Fix operator precedence when configuring MIPI clock - Grab LINK_FREQ control during streaming - Rename vdd_io supply to vddio - Fix reset_bits when stopping streaming --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ar0144.c | 1842 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1855 insertions(+) create mode 100644 drivers/media/i2c/ar0144.c diff --git a/MAINTAINERS b/MAINTAINERS index 93f758624830..919d4f5675bf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1655,6 +1655,7 @@ M: Laurent Pinchart L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml +F: drivers/media/i2c/ar0144.c AR0521 ON SEMICONDUCTOR CAMERA SENSOR DRIVER M: Krzysztof Hałasa diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index d149e4e31660..62746f83891a 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -51,6 +51,17 @@ config VIDEO_ALVIUM_CSI2 To compile this driver as a module, choose M here: the module will be called alvium-csi2. +config VIDEO_AR0144 + tristate "onsemi AR0144 sensor support" + select V4L2_CCI_I2C + select VIDEO_CCS_PLL + help + This is a Video4Linux2 sensor driver for the onsemi AR0144 camera + sensor. + + To compile this driver as a module, choose M here: the + module will be called ar0144. + config VIDEO_AR0521 tristate "ON Semiconductor AR0521 sensor support" help diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 6e5bc08ae874..71507863f770 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o obj-$(CONFIG_VIDEO_AK881X) += ak881x.o obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o +obj-$(CONFIG_VIDEO_AR0144) += ar0144.o obj-$(CONFIG_VIDEO_AR0521) += ar0521.o obj-$(CONFIG_VIDEO_BT819) += bt819.o obj-$(CONFIG_VIDEO_BT856) += bt856.o diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c new file mode 100644 index 000000000000..f9224fd2bb9f --- /dev/null +++ b/drivers/media/i2c/ar0144.c @@ -0,0 +1,1842 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Ideas on Board Oy + * Author: Laurent Pinchart + */ + +/* + * Possible improvements: + * + * - Use grouped parameter hold to update controls atomically + * - Expose debug information through debugfs (FRAME_COUNT, FRAME_STATUS, ...) + * - Make MIPI D-PHY timings configurable + * - Support the parallel interface + * - Expose additional controls (in particular the temperature sensor and the + * on-chip black level correction) + * - Make binning configurable + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ccs-pll.h" + +#define AR0144_CHIP_VERSION_REG CCI_REG16(0x3000) +#define AR0144_CHIP_VERSION_CS 0x0356 +#define AR0144_CHIP_VERSION_AT 0x1356 +#define AR0144_Y_ADDR_START CCI_REG16(0x3002) +#define AR0144_X_ADDR_START CCI_REG16(0x3004) +#define AR0144_Y_ADDR_END CCI_REG16(0x3006) +#define AR0144_X_ADDR_END CCI_REG16(0x3008) +#define AR0144_FRAME_LENGTH_LINES CCI_REG16(0x300a) +#define AR0144_MIN_FRAME_LENGTH_LINES 29 +#define AR0144_MAX_FRAME_LENGTH_LINES 65535 +#define AR0144_LINE_LENGTH_PCK CCI_REG16(0x300c) +#define AR0144_MIN_LINE_LENGTH_PCK 1488 +#define AR0144_MAX_LINE_LENGTH_PCK 65534 +#define AR0144_REVISION_NUMBER CCI_REG16(0x300e) +#define AR0144_REVISION_NUMBER_CREV(n) (((n) >> 12) & 0xf) +#define AR0144_REVISION_NUMBER_SILICON(n) (((n) >> 4) & 0xf) +#define AR0144_REVISION_NUMBER_OTPM(n) (((n) >> 0) & 0xf) +#define AR0144_LOCK_CONTROL CCI_REG16(0x3010) +#define AR0144_LOCK_CONTROL_VALUE 0xbeef +#define AR0144_COARSE_INTEGRATION_TIME CCI_REG16(0x3012) +#define AR0144_FINE_INTEGRATION_TIME CCI_REG16(0x3014) +#define AR0144_COARSE_INTEGRATION_TIME_CB CCI_REG16(0x3016) +#define AR0144_FINE_INTEGRATION_TIME_CB CCI_REG16(0x3018) +#define AR0144_RESET_REGISTER CCI_REG16(0x301a) +#define AR0144_GROUPED_PARAMETER_HOLD BIT(15) +#define AR0144_SMIA_SERIALIZER_DIS BIT(12) +#define AR0144_FORCED_PLL_ON BIT(11) +#define AR0144_RESTART_BAD BIT(10) +#define AR0144_MASK_BAD BIT(9) +#define AR0144_GPI_EN BIT(8) +#define AR0144_PARALLEL_EN BIT(7) +#define AR0144_DRIVE_PINS BIT(6) +#define AR0144_LOCK_REG BIT(3) +#define AR0144_STREAM BIT(2) +#define AR0144_RESTART BIT(1) +#define AR0144_RESET BIT(0) +#define AR0144_MODE_SELECT CCI_REG8(0x301c) +#define AR0144_MODE_STREAM BIT(0) +#define AR0144_IMAGE_ORIENTATION CCI_REG8(0x301d) +#define AR0144_ORIENTATION_VERT_FLIP BIT(1) +#define AR0144_ORIENTATION_HORIZ_MIRROR BIT(0) +#define AR0144_DATA_PEDESTAL CCI_REG16(0x301e) +#define AR0144_SOFTWARE_RESET CCI_REG8(0x3021) +#define AR0144_GROUPED_PARAMETER_HOLD_ CCI_REG8(0x3022) +#define AR0144_MASK_CORRUPTED_FRAMES CCI_REG8(0x3023) +#define AR0144_PIXEL_ORDER CCI_REG8(0x3024) +#define AR0144_GPI_STATUS CCI_REG16(0x3026) +#define AR0144_TRIGGER BIT(2) +#define AR0144_OUTPUT_ENABLE_N BIT(1) +#define AR0144_SADDR BIT(0) +#define AR0144_ROW_SPEED CCI_REG16(0x3028) +#define AR0144_ROW_SPEED_HALF_CLK BIT(4) +#define AR0144_VT_PIX_CLK_DIV CCI_REG16(0x302a) +#define AR0144_VT_SYS_CLK_DIV CCI_REG16(0x302c) +#define AR0144_PRE_PLL_CLK_DIV CCI_REG16(0x302e) +#define AR0144_PLL_MULTIPLIER CCI_REG16(0x3030) +#define AR0144_CTX_CONTROL_REG CCI_REG16(0x3034) +#define AR0144_OP_PIX_CLK_DIV CCI_REG16(0x3036) +#define AR0144_OP_SYS_CLK_DIV CCI_REG16(0x3038) +#define AR0144_FRAME_COUNT CCI_REG16(0x303a) +#define AR0144_FRAME_STATUS CCI_REG16(0x303c) +#define AR0144_PLL_LOCKED BIT(3) +#define AR0144_FRAME_START_DURING_GPH BIT(2) +#define AR0144_STANDBY_STATUS BIT(1) +#define AR0144_FRAMESYNC BIT(0) +#define AR0144_LINE_LENGTH_PCK_CB CCI_REG16(0x303e) +#define AR0144_READ_MODE CCI_REG16(0x3040) +#define AR0144_VERT_FLIP BIT(15) +#define AR0144_HORIZ_MIRROR BIT(14) +#define AR0144_READ_MODE_COL_BIN BIT(13) +#define AR0144_READ_MODE_ROW_BIN BIT(12) +#define AR0144_READ_MODE_COL_BIN_CB BIT(11) +#define AR0144_READ_MODE_ROW_BIN_CB BIT(10) +#define AR0144_READ_MODE_COL_SF_BIN_EN BIT(9) +#define AR0144_READ_MODE_COL_SF_BIN_EN_CB BIT(8) +#define AR0144_READ_MODE_COL_SF_BIN_MONO_EN BIT(7) +#define AR0144_READ_MODE_COL_SF_BIN_MONO_EN_CB BIT(6) +#define AR0144_READ_MODE_COL_SUM BIT(5) +#define AR0144_EXTRA_DELAY CCI_REG16(0x3042) +#define AR0144_GREEN1_GAIN CCI_REG16(0x3056) +#define AR0144_BLUE_GAIN CCI_REG16(0x3058) +#define AR0144_RED_GAIN CCI_REG16(0x305a) +#define AR0144_GREEN2_GAIN CCI_REG16(0x305c) +#define AR0144_GLOBAL_GAIN CCI_REG16(0x305e) +#define AR0144_ANALOG_GAIN CCI_REG16(0x3060) +#define AR0144_COARSE_GAIN_CB(n) ((n) << 12) +#define AR0144_FINE_GAIN_CB(n) ((n) << 8) +#define AR0144_COARSE_GAIN(n) ((n) << 4) +#define AR0144_FINE_GAIN(n) ((n) << 0) +#define AR0144_SMIA_TEST CCI_REG16(0x3064) +#define AR0144_EMBEDDED_DATA BIT(8) +#define AR0144_STATS_EN BIT(7) +#define AR0144_CTX_WR_DATA_REG CCI_REG16(0x3066) +#define AR0144_CTX_RD_DATA_REG CCI_REG16(0x3068) +#define AR0144_DATAPATH_SELECT CCI_REG16(0x306e) +#define AR0144_SLEW_RATE_CTRL_PARALLEL(n) (((n) & 7) << 13) +#define AR0144_SLEW_RATE_CTRL_PARALLEL_MASK GENMASK(15, 13) +#define AR0144_SLEW_RATE_CTRL_PIXCLK(n) (((n) & 7) << 10) +#define AR0144_SLOW_RATE_CTRL_PIXCLK_MASK GENMASK(12, 10) +#define AR0144_SPECIAL_LINE_VALID_NORMAL (0U << 0) +#define AR0144_SPECIAL_LINE_VALID_CONTINUOUS (1U << 0) +#define AR0144_SPECIAL_LINE_VALID_CONTINUOUS_XOR (2U << 0) +#define AR0144_TEST_PATTERN_MODE CCI_REG16(0x3070) +#define AR0144_TEST_PATTERN_NONE 0 +#define AR0144_TEST_PATTERN_SOLID 1 +#define AR0144_TEST_PATTERN_BARS 2 +#define AR0144_TEST_PATTERN_BARS_FADE 3 +#define AR0144_TEST_PATTERN_WALKING_1S 256 +#define AR0144_TEST_DATA_RED CCI_REG16(0x3072) +#define AR0144_TEST_DATA_GREENR CCI_REG16(0x3074) +#define AR0144_TEST_DATA_BLUE CCI_REG16(0x3076) +#define AR0144_TEST_DATA_GREENB CCI_REG16(0x3078) +#define AR0144_OPERATION_MODE_CTRL CCI_REG16(0x3082) +#define AR0144_OPERATION_MODE_CTRL_CB CCI_REG16(0x3084) +#define AR0144_SEQ_DATA_PORT CCI_REG16(0x3086) +#define AR0144_SEQ_CTRL_PORT CCI_REG16(0x3088) +#define AR0144_SEQUENCER_STOPPED BIT(15) +#define AR0144_AUTO_INC_ON_READ BIT(14) +#define AR0144_ACCESS_ADDRESS(n) ((n) & 0x3ff) +#define AR0144_X_ADDR_START_CB CCI_REG16(0x308a) +#define AR0144_Y_ADDR_START_CB CCI_REG16(0x308c) +#define AR0144_X_ADDR_END_CB CCI_REG16(0x308e) +#define AR0144_Y_ADDR_END_CB CCI_REG16(0x3090) +#define AR0144_X_EVEN_INC CCI_REG16(0x30a0) +#define AR0144_X_ODD_INC CCI_REG16(0x30a2) +#define AR0144_X_ODD_INC_SKIP(n) (((n) << 1) - 1) +#define AR0144_Y_EVEN_INC CCI_REG16(0x30a4) +#define AR0144_Y_ODD_INC CCI_REG16(0x30a6) +#define AR0144_Y_ODD_INC_SKIP(n) (((n) << 1) - 1) +#define AR0144_Y_ODD_INC_CB CCI_REG16(0x30a8) +#define AR0144_FRAME_LINE_LENGTH_CB CCI_REG16(0x30aa) +#define AR0144_X_ODD_INC_CB CCI_REG16(0x30ae) +#define AR0144_DIGITAL_TEST CCI_REG16(0x30b0) +#define AR0144_PLL_COMPLETE_BYPASS BIT(14) +#define AR0144_PIXCLK_ON BIT(8) +#define AR0144_MONO_CHROME_OPERATION BIT(7) +#define AR0144_TEMPSENS_DATA_REG CCI_REG16(0x30b2) +#define AR0144_TEMPSENS_CTRL_REG CCI_REG16(0x30b4) +#define AR0144_RETRIGGER_THRESHOLD(n) ((n) << 6) +#define AR0144_RETRIGGER_THRESHOLD_MASK GENMASK(15, 6) +#define AR0144_TEMP_CLEAR_VALUE BIT(5) +#define AR0144_TEMP_START_CONVERSION BIT(4) +#define AR0144_TEMPSENS_POWER_ON BIT(0) +#define AR0144_GREEN1_GAIN_CB CCI_REG16(0x30bc) +#define AR0144_BLUE_GAIN_CB CCI_REG16(0x30be) +#define AR0144_RED_GAIN_CB CCI_REG16(0x30c0) +#define AR0144_GREEN2_GAIN_CB CCI_REG16(0x30c2) +#define AR0144_GLOBAL_GAIN_CB CCI_REG16(0x30c4) +#define AR0144_TEMPSENS_CALIB1 CCI_REG16(0x30c6) +#define AR0144_TEMPSENS_CALIB2 CCI_REG16(0x30c8) +#define AR0144_GRR_CONTROL1 CCI_REG16(0x30ce) +#define AR0144_NOISE_PEDESTAL CCI_REG16(0x30fe) +#define AR0144_AECTRLREG CCI_REG16(0x3100) +#define AR0144_MIN_ANA_GAIN(n) ((n) << 5) +#define AR0144_MIN_ANA_GAIN_MASK GENMASK(6, 5) +#define AR0144_AUTO_DG_EN BIT(4) +#define AR0144_AUTO_AG_EN BIT(1) +#define AR0144_AE_ENABLE BIT(0) +#define AR0144_AE_LUMA_TARGET_REG CCI_REG16(0x3102) +#define AR0144_AE_MIN_EV_STEP_REG CCI_REG16(0x3108) +#define AR0144_AE_MAX_EV_STEP_REG CCI_REG16(0x310a) +#define AR0144_AE_DAMP_OFFSET_REG CCI_REG16(0x310c) +#define AR0144_AE_DAMP_GAIN_REG CCI_REG16(0x310e) +#define AR0144_AE_DAMP_MAX_REG CCI_REG16(0x3110) +#define AR0144_AE_MAX_EXPOSURE_REG CCI_REG16(0x311c) +#define AR0144_AE_MIN_EXPOSURE_REG CCI_REG16(0x311e) +#define AR0144_AE_COARSE_INTEGRATION_TIME CCI_REG16(0x3164) +#define AR0144_DELTA_DK_CONTROL CCI_REG16(0x3180) +#define AR0144_DELTA_DK_SUB_EN BIT(15) +#define AR0144_DELTA_DK_EVERY_FRAME BIT(14) +#define AR0144_DELTA_DK_RECALC BIT(13) +#define AR0144_DELTA_DK_GRADIENT_REMOVAL BIT(10) +#define AR0144_DATA_FORMAT_BITS CCI_REG16(0x31ac) +#define AR0144_DATA_FORMAT_IN(n) ((n) << 8) +#define AR0144_DATA_FORMAT_OUT(n) ((n) << 0) +#define AR0144_SERIAL_FORMAT CCI_REG16(0x31ae) +#define AR0144_NUM_LANES(n) (n) +#define AR0144_NUM_LANES_MASK GENMASK(1, 0) +#define AR0144_FRAME_PREAMBLE CCI_REG16(0x31b0) +#define AR0144_LINE_PREAMBLE CCI_REG16(0x31b2) +#define AR0144_MIPI_TIMING_0 CCI_REG16(0x31b4) +#define AR0144_T_HS_PREPARE(n) ((n) << 12) +#define AR0144_T_HS_ZERO(n) ((n) << 8) +#define AR0144_T_HS_TRAIL(n) ((n) << 4) +#define AR0144_T_CLK_TRAIL(n) ((n) << 0) +#define AR0144_MIPI_TIMING_1 CCI_REG16(0x31b6) +#define AR0144_T_CLK_PREPARE(n) ((n) << 12) +#define AR0144_T_HS_EXIT(n) ((n) << 6) +#define AR0144_T_CLK_ZERO(n) ((n) << 0) +#define AR0144_MIPI_TIMING_2 CCI_REG16(0x31b8) +#define AR0144_T_BGAP(n) ((n) << 12) +#define AR0144_T_CLK_PRE(n) ((n) << 6) +#define AR0144_T_CLK_POST(n) ((n) << 0) +#define AR0144_MIPI_TIMING_3 CCI_REG16(0x31ba) +#define AR0144_T_LPX(n) ((n) << 7) +#define AR0144_T_WAKE_UP(n) ((n) << 0) +#define AR0144_MIPI_TIMING_4 CCI_REG16(0x31bc) +#define AR0144_CONT_TX_CLK BIT(15) +#define AR0144_HEAVY_LP_LOAD BIT(14) +#define AR0144_T_INIT(n) ((n) << 0) +#define AR0144_SERIAL_CONFIG_STATUS CCI_REG16(0x31be) +#define AR0144_SERIAL_CONTROL_STATUS CCI_REG16(0x31c6) +#define AR0144_FRAMER_TEST_MODE BIT(7) +#define AR0144_SERIAL_CRC_0 CCI_REG16(0x31c8) +#define AR0144_COMPANDING CCI_REG16(0x31d0) +#define AR0144_COMPAND_EN BIT(0) +#define AR0144_STAT_FRAME_ID CCI_REG16(0x31d2) +#define AR0144_I2C_WRT_CHEKCSUM CCI_REG16(0x31d6) +#define AR0144_SERIAL_TEST CCI_REG16(0x31d8) +#define AR0144_TEST_LANE_EN(n) ((n) << 8) +#define AR0144_TEST_LANE_EN_MASK GENMASK(11, 8) +#define AR0144_TEST_MODE_LP00 (0 << 4) +#define AR0144_TEST_MODE_LP11 (1 << 4) +#define AR0144_TEST_MODE_HS0 (2 << 4) +#define AR0144_TEST_MODE_HS1 (3 << 4) +#define AR0144_TEST_MODE_SQUARE_HALF (4 << 4) +#define AR0144_TEST_MODE_SQUARE_FULL (5 << 4) +#define AR0144_TEST_MODE_SQUARE_LP (6 << 4) +#define AR0144_TEST_MODE_PRBS31 (7 << 4) +#define AR0144_TEST_MODE_PRBS9 (8 << 4) +#define AR0144_TEST_MODE_MASK GENMASK(7, 0) +#define AR0144_PIX_DEF_1D CCI_REG16(0x31e0) +#define AR0144_PIX_DEF_1D_DDC_EN BIT(3) +#define AR0144_PIX_DEF_CORRECTION_MODE BIT(1) +#define AR0144_PIX_DEF_ENABLE BIT(0) +#define AR0144_HORIZONTAL_CURSOR_POSITION CCI_REG16(0x31e8) +#define AR0144_VERTICAL_CURSOR_POSITION CCI_REG16(0x31ea) +#define AR0144_HORIZONTAL_CURSOR_WIDTH CCI_REG16(0x31ec) +#define AR0144_VERTICAL_CURSOR_WIDTH CCI_REG16(0x31ee) +#define AR0144_CCI_IDS CCI_REG16(0x31fc) +#define AR0144_CUSTOMER_REV CCI_REG16(0x31fe) +#define AR0144_CUSTOMER_REV_CRA(n) (((n) & GENMASK(9, 7)) >> 7) +#define AR0144_CUSTOMER_REV_CFA(n) (((n) & GENMASK(6, 4)) >> 4) +#define AR0144_CUSTOMER_REV_CFA_COLOR 1 +#define AR0144_CUSTOMER_REV_CFA_MONO 2 +#define AR0144_LED_FLASH_CONTROL CCI_REG16(0x3270) +#define AR0144_LED_FLASH_EN BIT(8) +#define AR0144_LED_DELAY(n) ((n) << 0) +#define AR0144_LED_DELAY_MASK GENMASK(7, 0) +#define AR0144_MIPI_TEST_CNTRL CCI_REG16(0x3338) +#define AR0144_MIPI_COMPRESS_8_DATA_TYPE CCI_REG16(0x333a) +#define AR0144_MIPI_COMPRESS_7_DATA_TYPE CCI_REG16(0x333c) +#define AR0144_MIPI_COMPRESS_6_DATA_TYPE CCI_REG16(0x333e) +#define AR0144_MIPI_JPEG_PN9_DATA_TYPE CCI_REG16(0x3340) +#define AR0144_MIPI_CNTRL CCI_REG16(0x3354) +#define AR0144_CHAN_NUM(n) ((n) << 6) +#define AR0144_DATA_TYPE_XMIT(n) ((n) << 0) +#define AR0144_MIPI_TEST_PATTERN_CNTRL CCI_REG16(0x3356) +#define AR0144_MIPI_TEST_PATTERN_STATUS CCI_REG16(0x3358) +#define AR0144_DIGITAL_CTRL_1 CCI_REG16(0x3786) +#define AR0144_LSB_ALIGN_PARA_OUT BIT(5) +#define AR0144_USE_1FRAME_SYNCED BIT(4) +#define AR0144_PLL_TEST_MODE BIT(3) +#define AR0144_DRIVE_PIX_CLK BIT(0) + +#define AR0144_MIN_CSI2_LINK_FREQ 150000000U +#define AR0144_MAX_CSI2_LINK_FREQ 384000000U + +/* + * The pixel array contains 1300x820 optically transparent pixels, with 6 dummy + * pixels on each side that can't be read out. The active size is 1288x808. + */ +#define AR0144_PIXEL_ARRAY_WIDTH 1300U +#define AR0144_PIXEL_ARRAY_HEIGHT 820U +#define AR0144_PIXEL_ARRAY_ACTIVE_LEFT 6U +#define AR0144_PIXEL_ARRAY_ACTIVE_TOP 6U +#define AR0144_PIXEL_ARRAY_ACTIVE_WIDTH 1288U +#define AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT 808U + +/* + * Documentation indicates minimum horizontal and vertical blanking of 208 + * pixels and 27 lines respectively, which matches the default values for the + * LINE_LENGTH_PCK and FRAME_LENGTH_LINES registers. Out-of-tree drivers are + * known to successfully use 22 or 25 lines of vertical blanking. This is + * likely related to the fact that the total number of rows per frame is equal + * to FRAME_LENGTH_LINES plus a 5 lines overhead, as indicated in at least one + * place in the documentation. + */ +#define AR0144_MIN_HBLANK 208U +#define AR0144_MIN_VBLANK 27U + +#define AR0144_FRAME_LENGTH_OVERHEAD 5U + +/* The minimum values are not documented, pick reasonable minimums. */ +#define AR0144_MIN_WIDTH 32U +#define AR0144_MIN_HEIGHT 32U +#define AR0144_DEF_WIDTH 1280U +#define AR0144_DEF_HEIGHT 800U + +#define AR0144_NUM_SUPPLIES 3 + +struct ar0144_model { + bool mono; +}; + +struct ar0144_format_info { + u32 colour; + u32 mono; + u16 bpp; + u16 dt; +}; + +static const struct ar0144_model ar0144_model_color = { + .mono = false, +}; + +static const struct ar0144_model ar0144_model_mono = { + .mono = true, +}; + +static const struct ar0144_format_info ar0144_formats[] = { + { + .colour = MEDIA_BUS_FMT_SGRBG12_1X12, + .mono = MEDIA_BUS_FMT_Y12_1X12, + .bpp = 12, + .dt = MIPI_CSI2_DT_RAW12, + }, { + .colour = MEDIA_BUS_FMT_SGRBG10_1X10, + .mono = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 10, + .dt = MIPI_CSI2_DT_RAW10, + }, { + .colour = MEDIA_BUS_FMT_SGRBG8_1X8, + .mono = MEDIA_BUS_FMT_Y8_1X8, + .bpp = 8, + .dt = MIPI_CSI2_DT_RAW8, + }, +}; + +struct ar0144 { + struct device *dev; + + struct regmap *regmap; + struct clk *clk; + struct gpio_desc *reset; + struct regulator_bulk_data supplies[AR0144_NUM_SUPPLIES]; + + ktime_t off_time; + + struct ccs_pll pll; + + struct v4l2_fwnode_endpoint bus_cfg; + u64 valid_link_freqs[ARRAY_SIZE(ar0144_formats)]; + u32 valid_formats; + + struct v4l2_subdev sd; + struct media_pad pad; + + const struct ar0144_model *model; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *test_data[4]; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; +}; + +static inline struct ar0144 *to_ar0144(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ar0144, sd); +} + +static u32 ar0144_format_code(struct ar0144 *sensor, + const struct ar0144_format_info *info) +{ + return sensor->model->mono ? info->mono : info->colour; +} + +static const struct ar0144_format_info * +ar0144_format_info(struct ar0144 *sensor, u32 code, bool use_def) +{ + const struct ar0144_format_info *def = NULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) { + const struct ar0144_format_info *info = &ar0144_formats[i]; + u32 info_code = ar0144_format_code(sensor, info); + + if (!(sensor->valid_formats & BIT(i))) + continue; + + if (info_code == code) + return info; + + if (!def) + def = info; + } + + return use_def ? def : NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static int ar0144_configure_pll(struct ar0144 *sensor) +{ + int ret = 0; + + cci_write(sensor->regmap, AR0144_PRE_PLL_CLK_DIV, + sensor->pll.vt_fr.pre_pll_clk_div, &ret); + cci_write(sensor->regmap, AR0144_PLL_MULTIPLIER, + sensor->pll.vt_fr.pll_multiplier, &ret); + cci_write(sensor->regmap, AR0144_VT_PIX_CLK_DIV, + sensor->pll.vt_bk.pix_clk_div, &ret); + cci_write(sensor->regmap, AR0144_VT_SYS_CLK_DIV, + sensor->pll.vt_bk.sys_clk_div, &ret); + cci_write(sensor->regmap, AR0144_OP_PIX_CLK_DIV, + sensor->pll.op_bk.pix_clk_div, &ret); + cci_write(sensor->regmap, AR0144_OP_SYS_CLK_DIV, + sensor->pll.op_bk.sys_clk_div, &ret); + + /* Wait 1ms for the PLL to lock. */ + fsleep(1000); + + return ret; +} + +static int ar0144_configure_mipi(struct ar0144 *sensor, + const struct ar0144_format_info *info) +{ + unsigned int num_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes; + bool cont_clk = !(sensor->bus_cfg.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK); + int ret = 0; + + cci_write(sensor->regmap, AR0144_SERIAL_FORMAT, num_lanes | 0x0200, + &ret); + + cci_write(sensor->regmap, AR0144_MIPI_CNTRL, + AR0144_CHAN_NUM(0) | AR0144_DATA_TYPE_XMIT(info->dt), &ret); + + cci_write(sensor->regmap, AR0144_MIPI_TIMING_0, + AR0144_T_HS_PREPARE(2) | AR0144_T_HS_ZERO(6) | + AR0144_T_HS_TRAIL(6) | AR0144_T_CLK_TRAIL(5), &ret); + cci_write(sensor->regmap, AR0144_MIPI_TIMING_1, + AR0144_T_CLK_PREPARE(1) | AR0144_T_HS_EXIT(4) | + AR0144_T_CLK_ZERO(14), &ret); + cci_write(sensor->regmap, AR0144_MIPI_TIMING_2, + AR0144_T_BGAP(2) | AR0144_T_CLK_PRE(1) | AR0144_T_CLK_POST(7), + &ret); + cci_write(sensor->regmap, AR0144_MIPI_TIMING_3, + AR0144_T_LPX(2) | AR0144_T_WAKE_UP(5), &ret); + cci_write(sensor->regmap, AR0144_MIPI_TIMING_4, + AR0144_T_INIT(4) | (cont_clk ? AR0144_CONT_TX_CLK : 0), + &ret); + + return ret; +} + +static int ar0144_start_streaming(struct ar0144 *sensor, + const struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + const struct ar0144_format_info *info; + const struct v4l2_rect *crop; + unsigned int bin_x, bin_y; + int ret = 0; + u16 val; + + /* + * The link frequency cannot change during streaming. Grab it first, so + * that it won't be set by the __v4l2_ctrl_handler_setup() call below. + */ + __v4l2_ctrl_grab(sensor->link_freq, true); + + format = v4l2_subdev_state_get_format(state, 0); + crop = v4l2_subdev_state_get_crop(state, 0); + info = ar0144_format_info(sensor, format->code, true); + + ret = ar0144_configure_pll(sensor); + if (ret) + goto error; + + cci_write(sensor->regmap, AR0144_DIGITAL_TEST, + (sensor->model->mono ? AR0144_MONO_CHROME_OPERATION : 0) | + 0x0038, &ret); + + cci_write(sensor->regmap, AR0144_DATA_FORMAT_BITS, + AR0144_DATA_FORMAT_IN(info->bpp) | + AR0144_DATA_FORMAT_OUT(info->bpp), &ret); + + /* Analog crop rectangle, binning/skipping. */ + cci_write(sensor->regmap, AR0144_X_ADDR_START, crop->left, &ret); + cci_write(sensor->regmap, AR0144_Y_ADDR_START, crop->top, &ret); + cci_write(sensor->regmap, AR0144_X_ADDR_END, + crop->left + crop->width - 1, &ret); + cci_write(sensor->regmap, AR0144_Y_ADDR_END, + crop->top + crop->height - 1, &ret); + + bin_x = crop->width / format->width; + bin_y = crop->height / format->height; + + cci_write(sensor->regmap, AR0144_X_ODD_INC, (bin_x << 1) - 1, &ret); + cci_write(sensor->regmap, AR0144_Y_ODD_INC, (bin_y << 1) - 1, &ret); + + /* + * Enable generation of embedded statistics, required for the on-chip + * auto-exposure. There is no downside in enabling it unconditionally. + */ + cci_write(sensor->regmap, AR0144_SMIA_TEST, AR0144_STATS_EN | 0x1802, + &ret); + + if (ret) + goto error; + + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret) + goto error; + + ret = ar0144_configure_mipi(sensor, info); + + /* + * We're all set, start streaming. Mask bad frames and keep read-only + * registers locked. + */ + val = AR0144_MASK_BAD | AR0144_LOCK_REG | AR0144_STREAM | 0x2010; + cci_write(sensor->regmap, AR0144_RESET_REGISTER, val, &ret); + + if (ret) + goto error; + + return 0; + +error: + __v4l2_ctrl_grab(sensor->link_freq, false); + return ret; +} + +#define ar0144_read_poll_timeout(sensor, addr, val, cond, sleep_us, timeout_us) \ +({ \ + int __ret, __err; \ + __ret = read_poll_timeout(cci_read, __err, __err || (cond), sleep_us, \ + timeout_us, false, (sensor)->regmap, addr, \ + &(val), NULL); \ + __ret ? : __err; \ +}) + +static int ar0144_stop_streaming(struct ar0144 *sensor) +{ + u16 reset_bits = AR0144_MASK_BAD | AR0144_LOCK_REG | 0x2010; + int ret; + u64 val; + + __v4l2_ctrl_grab(sensor->link_freq, false); + + /* + * Initiate the transition to standby by clearing the STREAM bit. Don't + * clear the bits that affect the output interface yet. + */ + ret = cci_write(sensor->regmap, AR0144_RESET_REGISTER, reset_bits, NULL); + if (ret) + return ret; + + ret = ar0144_read_poll_timeout(sensor, AR0144_FRAME_STATUS, val, + val & AR0144_STANDBY_STATUS, 2000, + 2000000); + if (ret) + dev_warn(sensor->dev, "%s while trying to enter standby (%d)\n", + ret == -ETIMEDOUT ? "timeout" : "error", ret); + + /* Standby state reached, disable the output interface. */ + reset_bits |= AR0144_SMIA_SERIALIZER_DIS; + reset_bits &= ~(AR0144_PARALLEL_EN | AR0144_DRIVE_PINS); + + return cci_write(sensor->regmap, AR0144_RESET_REGISTER, reset_bits, NULL); +} + +/* ----------------------------------------------------------------------------- + * PLL + */ + +static int ar0144_pll_calculate(struct ar0144 *sensor, struct ccs_pll *pll, + unsigned int link_freq, unsigned int bpp) +{ + struct ccs_pll_limits limits = { + .min_ext_clk_freq_hz = 6000000, + .max_ext_clk_freq_hz = 48000000, + + .vt_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 63, + .min_pll_ip_clk_freq_hz = 1000000, /* min_pll_op_clk_freq_hz / max_pll_multiplier */ + .max_pll_ip_clk_freq_hz = 24000000, /* max_pll_op_clk_freq_hz / min_pll_multiplier */ + .min_pll_multiplier = 32, + .max_pll_multiplier = 384, + .min_pll_op_clk_freq_hz = 384000000, + .max_pll_op_clk_freq_hz = 768000000, + }, + .vt_bk = { + .min_sys_clk_div = 1, + .max_sys_clk_div = 16, + .min_pix_clk_div = 4, + .max_pix_clk_div = 16, + .min_pix_clk_freq_hz = 6000000, /* No documented limit */ + .max_pix_clk_freq_hz = 74250000, + }, + .op_bk = { + .min_sys_clk_div = 1, + .max_sys_clk_div = 16, + .min_pix_clk_div = 8, + .max_pix_clk_div = 12, + .min_pix_clk_freq_hz = 6000000, /* No documented limit */ + .max_pix_clk_freq_hz = 74250000, + }, + + .min_line_length_pck_bin = 1200 + AR0144_MIN_HBLANK, /* To be checked */ + .min_line_length_pck = 1200 + AR0144_MIN_HBLANK, + }; + unsigned int num_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes; + int ret; + + /* + * The OP pixel clock limits depends on the number of lanes, which the + * PLL calculator doesn't take into account despite specifying the + * CCS_PLL_FLAG_LANE_SPEED_MODEL flag. Adjust them manually. + */ + limits.op_bk.min_pix_clk_freq_hz /= num_lanes; + limits.op_bk.max_pix_clk_freq_hz /= num_lanes; + + /* + * There are no documented constraints on the sys clock frequency, for + * either branch. Recover them based on the PLL output clock frequency + * and sys_clk_div limits on one hand, and the pix clock frequency and + * the pix_clk_div limits on the other hand. + */ + limits.vt_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.vt_bk.max_sys_clk_div, + limits.vt_bk.min_pix_clk_freq_hz * limits.vt_bk.min_pix_clk_div); + limits.vt_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.vt_bk.min_sys_clk_div, + limits.vt_bk.max_pix_clk_freq_hz * limits.vt_bk.max_pix_clk_div); + + limits.op_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.op_bk.max_sys_clk_div, + limits.op_bk.min_pix_clk_freq_hz * limits.op_bk.min_pix_clk_div); + limits.op_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.op_bk.min_sys_clk_div, + limits.op_bk.max_pix_clk_freq_hz * limits.op_bk.max_pix_clk_div); + + memset(pll, 0, sizeof(*pll)); + + pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + pll->op_lanes = num_lanes; + pll->vt_lanes = 1; + pll->csi2.lanes = num_lanes; + /* + * As the sensor doesn't support FIFO derating, binning doesn't + * influence the PLL configuration. Hardcode the binning factors. + */ + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = 1; + pll->scale_n = 1; + pll->bits_per_pixel = bpp; + pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL; + pll->link_freq = link_freq; + pll->ext_clk_freq_hz = clk_get_rate(sensor->clk); + + ret = ccs_pll_calculate(sensor->dev, &limits, pll); + if (ret) + return ret; + + /* + * The sensor ignores the LSB of the PLL multiplier. If the multiplier + * is an odd value, as a workaround to avoid precision loss, multiply + * both the pre-divider and the multiplier by 2 if this doesn't bring + * them beyond their maximum values. This doesn't necessarily guarantee + * optimum PLL parameters. Ideally the PLL calculator should handle + * this constraint. + */ + if ((pll->vt_fr.pll_multiplier & 1) && + pll->vt_fr.pll_multiplier * 2 <= limits.vt_fr.max_pll_multiplier && + pll->vt_fr.pre_pll_clk_div * 2 <= limits.vt_fr.max_pre_pll_clk_div) { + pll->vt_fr.pll_multiplier *= 2; + pll->vt_fr.pre_pll_clk_div *= 2; + } + + if (pll->vt_fr.pll_multiplier & 1) + dev_warn(sensor->dev, + "Odd PLL multiplier, link frequency %u will not be exact\n", + pll->link_freq); + + return 0; +} + +static int ar0144_pll_update(struct ar0144 *sensor, + const struct ar0144_format_info *info) +{ + u64 link_freq; + int ret; + + link_freq = sensor->bus_cfg.link_frequencies[sensor->link_freq->val]; + ret = ar0144_pll_calculate(sensor, &sensor->pll, link_freq, info->bpp); + if (ret) { + dev_err(sensor->dev, "PLL calculations failed: %d\n", ret); + return ret; + } + + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, + sensor->pll.pixel_rate_pixel_array); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 controls + */ + +static const char * const ar0144_test_pattern_menu[] = { + "Disabled", + "Solid color", + "Full Color Bars", + "Fade to Gray Color Bars", + "Walking 1", +}; + +static const u32 ar0144_test_pattern_values[] = { + AR0144_TEST_PATTERN_NONE, + AR0144_TEST_PATTERN_SOLID, + AR0144_TEST_PATTERN_BARS, + AR0144_TEST_PATTERN_BARS_FADE, + AR0144_TEST_PATTERN_WALKING_1S, +}; + +static void ar0144_update_link_freqs(struct ar0144 *sensor, + const struct ar0144_format_info *info) +{ + u64 valid_link_freqs; + unsigned int index; + unsigned int min; + unsigned int max; + + index = info - ar0144_formats; + valid_link_freqs = sensor->valid_link_freqs[index]; + + min = __ffs(valid_link_freqs); + max = __fls(valid_link_freqs); + + __v4l2_ctrl_modify_range(sensor->link_freq, min, max, ~valid_link_freqs, + min); +} + +static int ar0144_update_exposure(struct ar0144 *sensor, + const struct v4l2_rect *crop) +{ + unsigned int max; + + /* The coarse integration time is limited to FRAME_LENGTH_LINES - 1. */ + max = crop->height + sensor->vblank->val - 1 + - AR0144_FRAME_LENGTH_OVERHEAD; + return __v4l2_ctrl_modify_range(sensor->exposure, 1, max, 1, max); +} + +static void ar0144_update_blanking(struct ar0144 *sensor, + const struct v4l2_subdev_state *state) +{ + const struct v4l2_rect *crop; + unsigned int min; + unsigned int max; + + crop = v4l2_subdev_state_get_crop(state, 0); + + /* + * Horizontally, the line length (in pixel clocks), programmed in the + * LINE_LENGTH_PCK register, is equal to the sum of the crop width and + * the horizontal blanking. The sensor has lower and upper bounds for + * the LINE_LENGTH_PCK value, as well as a lower bound for the + * horizontal blanking. + */ + min = max_t(int, AR0144_MIN_LINE_LENGTH_PCK - crop->width, + AR0144_MIN_HBLANK); + max = AR0144_MAX_LINE_LENGTH_PCK - crop->width; + __v4l2_ctrl_modify_range(sensor->hblank, min, max, 2, min); + + /* + * Vertically, the situation is more complicated. The frame length (in + * lines) is equal to the FRAME_LENGTH_LINES register value plus 5 + * lines of overhead. This needs to be taken into account for the + * VBLANK calculation. + */ + min = max_t(int, AR0144_MIN_FRAME_LENGTH_LINES - crop->height + + AR0144_FRAME_LENGTH_OVERHEAD, AR0144_MIN_VBLANK); + max = AR0144_MAX_FRAME_LENGTH_LINES - crop->height + + AR0144_FRAME_LENGTH_OVERHEAD; + __v4l2_ctrl_modify_range(sensor->vblank, min, max, 1, min); + + ar0144_update_exposure(sensor, crop); +} + +static int ar0144_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ar0144 *sensor = container_of(ctrl->handler, struct ar0144, ctrls); + const struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + const struct ar0144_format_info *info; + const struct v4l2_rect *crop; + int ret = 0; + + /* + * Return immediately for controls that don't need to be applied to the + * device. + */ + if (ctrl->flags & (V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_GRABBED)) + return 0; + + /* + * First process controls that update other parts of the device + * configuration. + */ + state = v4l2_subdev_get_locked_active_state(&sensor->sd); + format = v4l2_subdev_state_get_format(state, 0); + info = ar0144_format_info(sensor, format->code, true); + crop = v4l2_subdev_state_get_crop(state, 0); + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + ret = ar0144_update_exposure(sensor, crop); + break; + + case V4L2_CID_LINK_FREQ: + ret = ar0144_pll_update(sensor, info); + break; + + default: + break; + } + + if (ret) + return ret; + + /* + * Apply controls to the hardware. If power is down, they controls will + * be applied when starting streaming. + */ + if (!pm_runtime_get_if_in_use(sensor->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + cci_write(sensor->regmap, AR0144_ANALOG_GAIN, ctrl->val, &ret); + break; + + case V4L2_CID_DIGITAL_GAIN: + cci_write(sensor->regmap, AR0144_GLOBAL_GAIN, ctrl->val, &ret); + break; + + case V4L2_CID_EXPOSURE: + cci_write(sensor->regmap, AR0144_COARSE_INTEGRATION_TIME, + ctrl->val, &ret); + break; + + case V4L2_CID_HBLANK: + cci_write(sensor->regmap, AR0144_LINE_LENGTH_PCK, + crop->width + ctrl->val, &ret); + break; + + case V4L2_CID_VBLANK: + cci_write(sensor->regmap, AR0144_FRAME_LENGTH_LINES, + crop->height + ctrl->val - AR0144_FRAME_LENGTH_OVERHEAD, + &ret); + break; + + case V4L2_CID_TEST_PATTERN: + cci_write(sensor->regmap, AR0144_TEST_PATTERN_MODE, + ar0144_test_pattern_values[ctrl->val], &ret); + + /* + * Register 0x3044 is not documented, but mentioned in the test + * pattern configuration. Bits [5:4] should be set to 0 to + * avoid clipping pixel values to 0xf70. + */ + cci_write(sensor->regmap, CCI_REG16(0x3044), + ctrl->val ? 0x0400 : 0x0410, &ret); + break; + + case V4L2_CID_TEST_PATTERN_RED: + case V4L2_CID_TEST_PATTERN_GREENR: + case V4L2_CID_TEST_PATTERN_BLUE: + case V4L2_CID_TEST_PATTERN_GREENB: + cci_write(sensor->regmap, AR0144_GROUPED_PARAMETER_HOLD_, + 1, &ret); + + cci_write(sensor->regmap, AR0144_TEST_DATA_RED, + sensor->test_data[0]->val, &ret); + cci_write(sensor->regmap, AR0144_TEST_DATA_GREENR, + sensor->test_data[1]->val, &ret); + cci_write(sensor->regmap, AR0144_TEST_DATA_BLUE, + sensor->test_data[2]->val, &ret); + cci_write(sensor->regmap, AR0144_TEST_DATA_GREENB, + sensor->test_data[3]->val, &ret); + + cci_write(sensor->regmap, AR0144_GROUPED_PARAMETER_HOLD_, + 0, &ret); + break; + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + { + u16 reg = 0; + + if (sensor->hflip->val) + reg |= AR0144_ORIENTATION_HORIZ_MIRROR; + if (sensor->vflip->val) + reg |= AR0144_ORIENTATION_VERT_FLIP; + + cci_write(sensor->regmap, AR0144_IMAGE_ORIENTATION, reg, &ret); + break; + } + + case V4L2_CID_EXPOSURE_AUTO: + cci_write(sensor->regmap, AR0144_AECTRLREG, + ctrl->val == V4L2_EXPOSURE_AUTO ? + AR0144_AUTO_AG_EN | AR0144_AE_ENABLE : 0, &ret); + break; + + case V4L2_CID_LINK_FREQ: + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_mark_last_busy(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ar0144_ctrl_ops = { + .s_ctrl = ar0144_s_ctrl, +}; + +static int ar0144_init_ctrls(struct ar0144 *sensor) +{ + static const u32 test_pattern_ctrls[] = { + V4L2_CID_TEST_PATTERN_RED, + V4L2_CID_TEST_PATTERN_GREENR, + V4L2_CID_TEST_PATTERN_BLUE, + V4L2_CID_TEST_PATTERN_GREENB, + }; + struct v4l2_fwnode_device_properties props; + unsigned int i; + int ret; + + ret = v4l2_fwnode_device_parse(sensor->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&sensor->ctrls, 17); + + v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ar0144_ctrl_ops, + &props); + + /* + * Set the link frequency, pixel rate, horizontal blanking and vertical + * blanking and exposure to hardcoded values. They will be updated by + * ar0144_update_link_freqs(), ar0144_pll_update() and + * ar0144_update_blanking(). + */ + sensor->pixel_rate = + v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); + sensor->link_freq = + v4l2_ctrl_new_int_menu(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_LINK_FREQ, + sensor->bus_cfg.nr_of_link_frequencies - 1, 0, + sensor->bus_cfg.link_frequencies); + + sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_HBLANK, AR0144_MIN_HBLANK, + AR0144_MIN_HBLANK, 2, AR0144_MIN_HBLANK); + sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_VBLANK, AR0144_MIN_VBLANK, + AR0144_MIN_VBLANK, 1, AR0144_MIN_VBLANK); + + sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_EXPOSURE, 1, + AR0144_MAX_FRAME_LENGTH_LINES - 1, + 1, 16); + + /* + * The sensor analogue gain is split in an exponential coarse gain and + * a fine gain. The minimum recommended gain is 1.6842, which maps to a + * gain code of 13. Set the minimum to 0 to expose the whole range of + * possible values, and the default to the recommended minimum. + */ + v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 79, 1, 13); + + v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, 0, 0x7ff, 1, 0x080); + + v4l2_ctrl_new_std_menu(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, + ~(BIT(V4L2_EXPOSURE_AUTO) | BIT(V4L2_EXPOSURE_MANUAL)), + V4L2_EXPOSURE_MANUAL); + + v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ar0144_test_pattern_menu) - 1, + 0, 0, ar0144_test_pattern_menu); + + for (i = 0; i < ARRAY_SIZE(test_pattern_ctrls); ++i) + sensor->test_data[i] = + v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + test_pattern_ctrls[i], 0, 4095, 1, 0); + + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (sensor->ctrls.error) { + ret = sensor->ctrls.error; + v4l2_ctrl_handler_free(&sensor->ctrls); + return ret; + } + + v4l2_ctrl_cluster(4, sensor->test_data); + v4l2_ctrl_cluster(2, &sensor->hflip); + + sensor->sd.ctrl_handler = &sensor->ctrls; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static int ar0144_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ar0144 *sensor = to_ar0144(sd); + unsigned int index = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) { + const struct ar0144_format_info *info = &ar0144_formats[i]; + + if (!(sensor->valid_formats & BIT(i))) + continue; + + if (code->index == index) { + code->code = ar0144_format_code(sensor, info); + return 0; + } + + index++; + } + + return -EINVAL; +} + +static int ar0144_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ar0144 *sensor = to_ar0144(sd); + const struct ar0144_format_info *info; + const struct v4l2_rect *crop; + + info = ar0144_format_info(sensor, fse->code, false); + if (!info) + return -EINVAL; + + /* + * Enumerate binning/skipping. Supported factors are powers of two from + * /1 to /16. + */ + + if (fse->index >= 5) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, fse->pad); + + fse->min_width = crop->width / (1 << fse->index); + fse->max_width = fse->min_width; + fse->min_height = crop->height / (1 << fse->index); + fse->max_height = fse->min_height; + + return 0; +} + +static int ar0144_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ar0144 *sensor = to_ar0144(sd); + const struct ar0144_format_info *info; + struct v4l2_mbus_framefmt *fmt; + const struct v4l2_rect *crop; + unsigned int bin_x, bin_y; + + if (v4l2_subdev_is_streaming(sd) && + format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return -EBUSY; + + fmt = v4l2_subdev_state_get_format(state, format->pad); + crop = v4l2_subdev_state_get_crop(state, format->pad); + + info = ar0144_format_info(sensor, format->format.code, true); + fmt->code = ar0144_format_code(sensor, info); + + /* + * The output size results from the binning/skipping applied to the + * crop rectangle. The x/y increments must be powers of 2. Clamp the + * width/height first, to avoid both divisions by 0 and the undefined + * behaviour of roundup_pow_of_two(0). + */ + fmt->width = clamp(format->format.width, 1U, crop->width); + fmt->height = clamp(format->format.height, 1U, crop->height); + bin_x = clamp(roundup_pow_of_two(crop->width / fmt->width), 1, 16); + bin_y = clamp(roundup_pow_of_two(crop->height / fmt->height), 1, 16); + fmt->width = crop->width / bin_x; + fmt->height = crop->height / bin_y; + + format->format = *fmt; + + if (format->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return 0; + + ar0144_update_link_freqs(sensor, info); + ar0144_pll_update(sensor, info); + + return 0; +} + +static int ar0144_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, sel->pad); + break; + + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.left = (AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_DEF_WIDTH) / 2; + sel->r.top = (AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_DEF_HEIGHT) / 2; + sel->r.width = AR0144_DEF_WIDTH; + sel->r.height = AR0144_DEF_HEIGHT; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = AR0144_PIXEL_ARRAY_ACTIVE_WIDTH; + sel->r.height = AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int ar0144_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; + + if (v4l2_subdev_is_streaming(sd) && + sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return -EBUSY; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, 0); + fmt = v4l2_subdev_state_get_format(state, 0); + + crop->left = min_t(unsigned int, ALIGN(sel->r.left, 2), + AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_MIN_WIDTH); + crop->top = min_t(unsigned int, ALIGN(sel->r.top, 2), + AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_MIN_HEIGHT); + crop->width = clamp(sel->r.width, AR0144_MIN_WIDTH, + AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - crop->left); + crop->height = clamp(sel->r.height, AR0144_MIN_HEIGHT, + AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - crop->top); + + sel->r = *crop; + + fmt->width = crop->width; + fmt->height = crop->height; + + return 0; +} + +static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ar0144 *sensor = to_ar0144(sd); + const struct ar0144_format_info *info; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_subdev_state *state; + u32 code; + + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_state_get_format(state, 0); + code = fmt->code; + v4l2_subdev_unlock_state(state); + + info = ar0144_format_info(sensor, code, true); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->num_entries = 1; + + fd->entry[0].pixelcode = code; + fd->entry[0].stream = 0; + fd->entry[0].bus.csi2.vc = 0; + fd->entry[0].bus.csi2.dt = info->dt; + + return 0; +} + +static int ar0144_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct ar0144 *sensor = to_ar0144(sd); + + cfg->type = sensor->bus_cfg.bus_type; + + switch (sensor->bus_cfg.bus_type) { + case V4L2_MBUS_CSI2_DPHY: + default: + cfg->bus.mipi_csi2 = sensor->bus_cfg.bus.mipi_csi2; + break; + } + + return 0; +} + +static int ar0144_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ar0144 *sensor = to_ar0144(sd); + int ret; + + ret = pm_runtime_resume_and_get(sensor->dev); + if (ret < 0) + return ret; + + ret = ar0144_start_streaming(sensor, state); + if (ret) { + dev_err(sensor->dev, "Failed to start streaming: %d\n", + ret); + pm_runtime_put_sync(sensor->dev); + return ret; + } + + return 0; +} + +static int ar0144_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ar0144 *sensor = to_ar0144(sd); + + ar0144_stop_streaming(sensor); + pm_runtime_mark_last_busy(sensor->dev); + pm_runtime_put_autosuspend(sensor->dev); + + return 0; +} + +static int ar0144_entity_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + const struct ar0144_format_info *info; + struct ar0144 *sensor = to_ar0144(sd); + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + + info = ar0144_format_info(sensor, 0, true); + + fmt = v4l2_subdev_state_get_format(state, 0); + fmt->width = AR0144_DEF_WIDTH; + fmt->height = AR0144_DEF_HEIGHT; + fmt->code = ar0144_format_code(sensor, info); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + + crop = v4l2_subdev_state_get_crop(state, 0); + crop->left = 4; + crop->top = 4; + crop->width = AR0144_DEF_WIDTH; + crop->height = AR0144_DEF_HEIGHT; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ar0144_subdev_pad_ops = { + .enum_mbus_code = ar0144_enum_mbus_code, + .enum_frame_size = ar0144_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ar0144_set_fmt, + .get_selection = ar0144_get_selection, + .set_selection = ar0144_set_selection, + .get_frame_desc = ar0144_get_frame_desc, + .get_mbus_config = ar0144_get_mbus_config, + .enable_streams = ar0144_enable_streams, + .disable_streams = ar0144_disable_streams, +}; + +static const struct v4l2_subdev_ops ar0144_subdev_ops = { + .pad = &ar0144_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ar0144_subdev_internal_ops = { + .init_state = ar0144_entity_init_state, +}; + +static const struct media_entity_operations ar0144_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +static int ar0144_init_subdev(struct ar0144 *sensor) +{ + struct i2c_client *client = to_i2c_client(sensor->dev); + struct v4l2_subdev *sd = &sensor->sd; + const struct v4l2_mbus_framefmt *format; + const struct ar0144_format_info *info; + struct v4l2_subdev_state *state; + int ret; + + v4l2_i2c_subdev_init(sd, client, &ar0144_subdev_ops); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->internal_ops = &ar0144_subdev_internal_ops; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + sd->entity.ops = &ar0144_entity_ops; + + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad); + if (ret) + return ret; + + ret = ar0144_init_ctrls(sensor); + if (ret) + goto err_entity; + + sensor->sd.state_lock = sensor->ctrls.lock; + ret = v4l2_subdev_init_finalize(&sensor->sd); + if (ret) + goto err_ctrls; + + /* + * Update the link frequency, PLL configuration (including the pixel + * rate) and blanking controls. + */ + state = v4l2_subdev_lock_and_get_active_state(sd); + format = v4l2_subdev_state_get_format(state, 0); + info = ar0144_format_info(sensor, format->code, true); + + ar0144_update_link_freqs(sensor, info); + ar0144_pll_update(sensor, info); + ar0144_update_blanking(sensor, state); + + v4l2_subdev_unlock_state(state); + + return 0; + +err_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrls); +err_entity: + media_entity_cleanup(&sd->entity); + return ret; +} + +static void ar0144_cleanup_subdev(struct ar0144 *sensor) +{ + v4l2_subdev_cleanup(&sensor->sd); + v4l2_ctrl_handler_free(&sensor->ctrls); + media_entity_cleanup(&sensor->sd.entity); +} + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int ar0144_power_on(struct ar0144 *sensor) +{ + u64 reset_delay; + long rate; + int ret; + + /* + * The sensor must be powered off for at least 100ms before being + * powered on again. + */ + if (sensor->off_time) { + u64 off_duration; + + off_duration = ktime_us_delta(ktime_get_boottime(), + sensor->off_time); + if (off_duration < 100000) + fsleep(100000 - off_duration); + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret) { + dev_err(sensor->dev, "Failed to enable regulators\n"); + return ret; + } + + ret = clk_prepare_enable(sensor->clk); + if (ret) { + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + dev_err(sensor->dev, "Failed to enable clock\n"); + return ret; + } + + /* + * The internal initialization time after hard reset is 160000 EXTCLK + * cycles. + */ + rate = clk_get_rate(sensor->clk); + reset_delay = DIV_ROUND_UP_ULL(160000ULL * USEC_PER_SEC, rate); + + if (sensor->reset) { + gpiod_set_value_cansleep(sensor->reset, 1); + fsleep(1000); + gpiod_set_value_cansleep(sensor->reset, 0); + } else { + cci_write(sensor->regmap, AR0144_RESET_REGISTER, AR0144_RESET, + NULL); + } + + fsleep(reset_delay); + + return 0; +} + +static void ar0144_power_off(struct ar0144 *sensor) +{ + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); + sensor->off_time = ktime_get_boottime(); + + clk_disable_unprepare(sensor->clk); + gpiod_set_value_cansleep(sensor->reset, 1); +} + +static int ar0144_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ar0144 *sensor = to_ar0144(sd); + + return ar0144_power_on(sensor); +} + +static int ar0144_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ar0144 *sensor = to_ar0144(sd); + + ar0144_power_off(sensor); + + return 0; +} + +static const struct dev_pm_ops ar0144_pm_ops = { + RUNTIME_PM_OPS(ar0144_runtime_suspend, ar0144_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe & remove + */ + +static int ar0144_identify_device(struct ar0144 *sensor) +{ + const struct ar0144_model *model; + u64 customer_rev; + u64 chip_id; + int ret = 0; + + cci_read(sensor->regmap, AR0144_CHIP_VERSION_REG, &chip_id, &ret); + cci_read(sensor->regmap, AR0144_CUSTOMER_REV, &customer_rev, &ret); + + if (ret) { + dev_err(sensor->dev, "Failed to read chip ID: %d\n", ret); + return ret; + } + + if (chip_id != AR0144_CHIP_VERSION_CS && + chip_id != AR0144_CHIP_VERSION_AT) { + dev_err(sensor->dev, + "Wrong chip version 0x%04x (expected 0x%04x or 0x%04x)\n", + (u32)chip_id, AR0144_CHIP_VERSION_CS, + AR0144_CHIP_VERSION_AT); + return -ENODEV; + } + + switch (AR0144_CUSTOMER_REV_CFA(customer_rev)) { + default: + dev_warn(sensor->dev, + "Unknown CFA %u, defaulting to color\n", + (u32)AR0144_CUSTOMER_REV_CFA(customer_rev)); + fallthrough; + case AR0144_CUSTOMER_REV_CFA_COLOR: + model = &ar0144_model_color; + break; + case AR0144_CUSTOMER_REV_CFA_MONO: + model = &ar0144_model_mono; + break; + } + + if (sensor->model != model) + dev_warn(sensor->dev, + "Customer rev CFA doesn't match compatible string\n"); + + sensor->model = model; + + if (IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || IS_ENABLED(DEBUG)) { + cci_read(sensor->regmap, AR0144_REVISION_NUMBER, &chip_id, NULL); + dev_dbg(sensor->dev, + "Sensor detected, CFA %s, CRA %u, OTPM r%u, silicon r%u, CREV r%u\n", + sensor->model->mono ? "mono" : "color", + (u32)AR0144_CUSTOMER_REV_CRA(customer_rev), + (u32)AR0144_REVISION_NUMBER_OTPM(chip_id), + (u32)AR0144_REVISION_NUMBER_SILICON(chip_id), + (u32)AR0144_REVISION_NUMBER_CREV(chip_id)); + } + + return 0; +} + +static int ar0144_parse_dt(struct ar0144 *sensor) +{ + struct v4l2_fwnode_endpoint *ep = &sensor->bus_cfg; + struct fwnode_handle *endpoint; + u64 valid_link_freqs = 0; + unsigned int nlanes; + unsigned int i, j; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), NULL); + if (!endpoint) { + dev_err(sensor->dev, "Endpoint node not found\n"); + return -EINVAL; + } + + ep->bus_type = V4L2_MBUS_UNKNOWN; + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, ep); + fwnode_handle_put(endpoint); + if (ret) { + dev_err(sensor->dev, "Parsing endpoint node failed\n"); + goto error; + } + + switch (ep->bus_type) { + case V4L2_MBUS_CSI2_DPHY: + /* Validate the number of data lanes. */ + nlanes = ep->bus.mipi_csi2.num_data_lanes; + if (nlanes != 1 && nlanes != 2) { + dev_err(sensor->dev, "Invalid data lanes: %d\n", nlanes); + ret = -EINVAL; + goto error; + } + break; + + default: + dev_err(sensor->dev, "Unsupported bus type %u\n", ep->bus_type); + goto error; + } + + /* Get and validate the link frequencies. */ + if (!ep->nr_of_link_frequencies) { + dev_err(sensor->dev, "link-frequency property not found in DT\n"); + ret = -EINVAL; + goto error; + } + + if (ep->nr_of_link_frequencies > 64) { + dev_err(sensor->dev, "Too many link-frequency entries\n"); + ret = -EINVAL; + goto error; + } + + for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) { + const struct ar0144_format_info *info = &ar0144_formats[i]; + + for (j = 0; j < ep->nr_of_link_frequencies; ++j) { + u64 freq = ep->link_frequencies[j]; + struct ccs_pll pll; + + ret = ar0144_pll_calculate(sensor, &pll, freq, + info->bpp); + if (ret) + continue; + + sensor->valid_link_freqs[i] |= BIT(j); + valid_link_freqs |= BIT(j); + } + + if (!sensor->valid_link_freqs[i]) { + dev_warn(sensor->dev, + "No valid link frequency for %u bpp\n", + info->bpp); + continue; + } + + sensor->valid_formats |= BIT(i); + } + + if (!sensor->valid_formats) { + dev_err(sensor->dev, + "No valid link frequency found for any format\n"); + ret = -EINVAL; + goto error; + } + + for (i = 0; i < ep->nr_of_link_frequencies; ++i) { + if (!(valid_link_freqs & BIT(i))) + dev_warn(sensor->dev, + "Link frequency %llu not valid for any format\n", + ep->link_frequencies[i]); + } + + dev_dbg(sensor->dev, "Using %u data lanes and %u link frequencies\n", + nlanes, ep->nr_of_link_frequencies); + + return 0; + +error: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static const char * const ar0144_supply_name[AR0144_NUM_SUPPLIES] = { + "vaa", + "vddio", + "vdd", +}; + +static int ar0144_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ar0144 *sensor; + unsigned int i; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->dev = dev; + sensor->model = device_get_match_data(sensor->dev); + + /* Acquire resources. */ + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) + return dev_err_probe(dev, PTR_ERR(sensor->regmap), + "Unable to initialize I2C\n"); + + sensor->clk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->clk)) + return dev_err_probe(dev, PTR_ERR(sensor->clk), + "Cannot get clock\n"); + + sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) + return dev_err_probe(dev, PTR_ERR(sensor->reset), + "Cannot get reset gpio\n"); + + for (i = 0; i < ARRAY_SIZE(sensor->supplies); i++) + sensor->supplies[i].supply = ar0144_supply_name[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret) + return dev_err_probe(dev, ret, "Cannot get supplies\n"); + + /* Parse the DT properties. */ + ret = ar0144_parse_dt(sensor); + if (ret) + return ret; + + /* + * Enable power management. The driver supports runtime PM, but needs to + * work when runtime PM is disabled in the kernel. To that end, power + * the sensor on manually here, identify it, and fully initialize it. + */ + ret = ar0144_power_on(sensor); + if (ret < 0) { + dev_err_probe(dev, ret, "Could not power on the device\n"); + goto err_dt; + } + + ret = ar0144_identify_device(sensor); + if (ret) + goto err_power; + + /* + * Enable runtime PM with autosuspend. As the device has been powered + * manually, mark it as active, and increase the usage count without + * resuming the device. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + /* Initialize and register the subdev. */ + ret = ar0144_init_subdev(sensor); + if (ret) { + dev_err(dev, "Subdev initialization error %d\n", ret); + goto err_pm; + } + + ret = v4l2_async_register_subdev_sensor(&sensor->sd); + if (ret) { + dev_err(dev, "Could not register V4L2 subdevice\n"); + goto err_subdev; + } + + /* + * Decrease the PM usage count. The device will get suspended after the + * autosuspend delay, turning the power off. + */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; + +err_subdev: + ar0144_cleanup_subdev(sensor); +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +err_power: + ar0144_power_off(sensor); +err_dt: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static void ar0144_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ar0144 *sensor = to_ar0144(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + ar0144_cleanup_subdev(sensor); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + + /* + * Disable runtime PM. In case runtime PM is disabled in the kernel, + * make sure to turn power off manually. + */ + pm_runtime_disable(sensor->dev); + if (!pm_runtime_status_suspended(sensor->dev)) + ar0144_power_off(sensor); + pm_runtime_set_suspended(sensor->dev); +} + +static const struct of_device_id ar0144_of_match[] = { + { .compatible = "onnn,ar0144c", &ar0144_model_color }, + { .compatible = "onnn,ar0144m", &ar0144_model_mono }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ar0144_of_match); + +static struct i2c_driver ar0144_i2c_driver = { + .driver = { + .name = "ar0144", + .of_match_table = ar0144_of_match, + .pm = pm_ptr(&ar0144_pm_ops), + }, + .probe = ar0144_probe, + .remove = ar0144_remove, +}; + +module_i2c_driver(ar0144_i2c_driver); + +MODULE_DESCRIPTION("onsemi AR0144 Camera Sensor Driver"); +MODULE_LICENSE("GPL"); From patchwork Thu Sep 5 22:53:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 826141 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 76F3B1482E9; Thu, 5 Sep 2024 22:53:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576814; cv=none; b=uRz/ZLJ50tcpg7IZ9pqq+htUcN9clQldET2fl4redeC5AQniY1eqn2I+p61v9FWiCPboDqiSOYoqgLpJtikDjFur63nBIUC8wuK9FDhrwOvsWLpCChhIjYr3Rn9slr0GNJ+K1zsqUrUrFtDB0ZCVg4Hfmfx8mYUUhUEzLTn4CA0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576814; c=relaxed/simple; bh=KqQ+djuT8kGjmHI7AJhiC4YINbWMttDUbU+2ULLO/Xo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oQqq2qH1DB6pc1q5wdhxzWeN+spqOgC5lHnpWhIKD/i2w51GV6/ADzn/kk1YWEciPDf7hxPmbw1eL3jPOv0ZB2bcVjprIkb6/DNpjMlkYEVTHclR6RG9MDt0gXUv5tKXLTHHJf6R5x28E1G1ehUrcptjfV+r1iJs5Lqf4vj4pzM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=kVWuU4cd; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kVWuU4cd" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E22611574; Fri, 6 Sep 2024 00:52:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576727; bh=KqQ+djuT8kGjmHI7AJhiC4YINbWMttDUbU+2ULLO/Xo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kVWuU4cdh/7wmYIre+K6SuM0lavabzSlVZnmb3wZRe9G4hM4GIMAaKHLbxVf1AOOL sxAhTicSSlcgsCleC0LET0CR+emGO8B7EcZeN8L/hozbrcrxaQXxSxz+71gMxSqDwg e7eQY3u9fm5s3MNuH8OYEKUoCj86wY1JPa0d2sk4= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 3/9] media: i2c: ar0144: Add support for the parallel interface Date: Fri, 6 Sep 2024 01:53:01 +0300 Message-ID: <20240905225308.11267-4-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add basic support for the parallel interface. This hasn't been tested due to the lack of a test platform, and will thus likely not work out of the box. Signed-off-by: Laurent Pinchart --- Changes since v1: - Fix reset_bits when stopping streaming --- drivers/media/i2c/ar0144.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index f9224fd2bb9f..e03806ed12f4 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -10,7 +10,8 @@ * - Use grouped parameter hold to update controls atomically * - Expose debug information through debugfs (FRAME_COUNT, FRAME_STATUS, ...) * - Make MIPI D-PHY timings configurable - * - Support the parallel interface + * - Program additional parallel interface parameters (slew rates, polarities, + * ...) * - Expose additional controls (in particular the temperature sensor and the * on-chip black level correction) * - Make binning configurable @@ -300,6 +301,8 @@ #define AR0144_MIN_CSI2_LINK_FREQ 150000000U #define AR0144_MAX_CSI2_LINK_FREQ 384000000U +#define AR0144_MAX_PARALLEL_LINK_FREQ 74250000U + /* * The pixel array contains 1300x820 optically transparent pixels, with 6 dummy * pixels on each side that can't be read out. The active size is 1288x808. @@ -466,6 +469,12 @@ static int ar0144_configure_pll(struct ar0144 *sensor) return ret; } +static int ar0144_configure_parallel(struct ar0144 *sensor, + const struct ar0144_format_info *info) +{ + return cci_write(sensor->regmap, AR0144_SERIAL_FORMAT, 0x0200, NULL); +} + static int ar0144_configure_mipi(struct ar0144 *sensor, const struct ar0144_format_info *info) { @@ -558,13 +567,19 @@ static int ar0144_start_streaming(struct ar0144 *sensor, if (ret) goto error; - ret = ar0144_configure_mipi(sensor, info); + if (sensor->bus_cfg.bus_type == V4L2_MBUS_PARALLEL) + ret = ar0144_configure_parallel(sensor, info); + else + ret = ar0144_configure_mipi(sensor, info); /* * We're all set, start streaming. Mask bad frames and keep read-only * registers locked. */ val = AR0144_MASK_BAD | AR0144_LOCK_REG | AR0144_STREAM | 0x2010; + if (sensor->bus_cfg.bus_type == V4L2_MBUS_PARALLEL) + val |= AR0144_SMIA_SERIALIZER_DIS | AR0144_PARALLEL_EN + | AR0144_DRIVE_PINS; cci_write(sensor->regmap, AR0144_RESET_REGISTER, val, &ret); if (ret) @@ -598,6 +613,10 @@ static int ar0144_stop_streaming(struct ar0144 *sensor) * Initiate the transition to standby by clearing the STREAM bit. Don't * clear the bits that affect the output interface yet. */ + if (sensor->bus_cfg.bus_type == V4L2_MBUS_PARALLEL) + reset_bits |= AR0144_SMIA_SERIALIZER_DIS | AR0144_PARALLEL_EN + | AR0144_DRIVE_PINS; + ret = cci_write(sensor->regmap, AR0144_RESET_REGISTER, reset_bits, NULL); if (ret) return ret; @@ -1282,6 +1301,10 @@ static int ar0144_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, default: cfg->bus.mipi_csi2 = sensor->bus_cfg.bus.mipi_csi2; break; + + case V4L2_MBUS_PARALLEL: + cfg->bus.parallel = sensor->bus_cfg.bus.parallel; + break; } return 0; @@ -1623,6 +1646,10 @@ static int ar0144_parse_dt(struct ar0144 *sensor) } break; + case V4L2_MBUS_PARALLEL: + nlanes = ep->bus.parallel.bus_width ? : 12; + break; + default: dev_err(sensor->dev, "Unsupported bus type %u\n", ep->bus_type); goto error; From patchwork Thu Sep 5 22:53:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 825748 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 EB6C01482E9; Thu, 5 Sep 2024 22:53:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576817; cv=none; b=n5yTBRBWrBJJyEyp0tJW67vYokrObJls2RyS70SkIjeTQLgo7ecIa5WBPJ9E+DeUHOZbzUVdwf+Hjzwl2rIQYL1aHKpTApu2V6UP7EuZKbn2reB5Kk7EKmxYObpjlg4nOCLYwKyh6PLA+EL5OMd32/KZLmu7qnpABbtC2ZWB6TA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576817; c=relaxed/simple; bh=w2HLc5cm945AqT0Ttwnw5iNHWQ4D2e6bPp9krFMMDsI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AuwmZfneFRKamFwvl6e/MX6nkaLg32vJmQSLg6s9PD8NvZrPRFENjBxbZdakkEiOOYMfYDpxRt3EtRsn9s5hIhTNjLGTKyzZLeAcyko0fYLGYoq0ybGwdm6WQAnTjAAGkEidCry0C9shTleeh+RthYSwHRZhiD/l+JQMADXcZx8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=QSNnCPQx; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QSNnCPQx" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4B70B157F; Fri, 6 Sep 2024 00:52:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576728; bh=w2HLc5cm945AqT0Ttwnw5iNHWQ4D2e6bPp9krFMMDsI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QSNnCPQxuUoixRx28nvHI729u7lYJLS5rmxBxw5AZkt/WXlW099HDaPl+rc7q3Gfj bzugRKyBSa5t23WeqVFfCETQceKDCOvZ6U3+TPPnDa3iRgXXcHbSFpfpyf5udbITKT yl3af37Ucl5xwCvqdr2vxlF1IvYO9/b2FGHrJdAk= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 4/9] media: i2c: ar0144: Add internal image sink pad Date: Fri, 6 Sep 2024 01:53:02 +0300 Message-ID: <20240905225308.11267-5-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Use the in-development internal pad API to expose the internal configuration of the sensor to userspace in a standard manner. This also paves the way for adding support for embedded data with an additional internal pad. Signed-off-by: Laurent Pinchart --- drivers/media/i2c/ar0144.c | 277 ++++++++++++++++++++++++++----------- 1 file changed, 193 insertions(+), 84 deletions(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index e03806ed12f4..ea76d484e6d1 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -336,6 +336,12 @@ #define AR0144_NUM_SUPPLIES 3 +enum ar0144_pad_ids { + AR0144_PAD_SOURCE = 0, + AR0144_PAD_IMAGE, + AR0144_NUM_PADS, +}; + struct ar0144_model { bool mono; }; @@ -355,6 +361,10 @@ static const struct ar0144_model ar0144_model_mono = { .mono = true, }; +/* + * Keep the entries sorted by descending bit depth. The top entry is used as + * the default media bus code, as well as the native code for the image pad. + */ static const struct ar0144_format_info ar0144_formats[] = { { .colour = MEDIA_BUS_FMT_SGRBG12_1X12, @@ -391,7 +401,7 @@ struct ar0144 { u32 valid_formats; struct v4l2_subdev sd; - struct media_pad pad; + struct media_pad pads[AR0144_NUM_PADS]; const struct ar0144_model *model; @@ -513,6 +523,7 @@ static int ar0144_start_streaming(struct ar0144 *sensor, const struct v4l2_mbus_framefmt *format; const struct ar0144_format_info *info; const struct v4l2_rect *crop; + const struct v4l2_rect *compose; unsigned int bin_x, bin_y; int ret = 0; u16 val; @@ -523,8 +534,9 @@ static int ar0144_start_streaming(struct ar0144 *sensor, */ __v4l2_ctrl_grab(sensor->link_freq, true); - format = v4l2_subdev_state_get_format(state, 0); - crop = v4l2_subdev_state_get_crop(state, 0); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); + compose = v4l2_subdev_state_get_compose(state, AR0144_PAD_IMAGE); info = ar0144_format_info(sensor, format->code, true); ret = ar0144_configure_pll(sensor); @@ -547,8 +559,8 @@ static int ar0144_start_streaming(struct ar0144 *sensor, cci_write(sensor->regmap, AR0144_Y_ADDR_END, crop->top + crop->height - 1, &ret); - bin_x = crop->width / format->width; - bin_y = crop->height / format->height; + bin_x = crop->width / compose->width; + bin_y = crop->height / compose->height; cci_write(sensor->regmap, AR0144_X_ODD_INC, (bin_x << 1) - 1, &ret); cci_write(sensor->regmap, AR0144_Y_ODD_INC, (bin_y << 1) - 1, &ret); @@ -828,7 +840,7 @@ static void ar0144_update_blanking(struct ar0144 *sensor, unsigned int min; unsigned int max; - crop = v4l2_subdev_state_get_crop(state, 0); + crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); /* * Horizontally, the line length (in pixel clocks), programmed in the @@ -878,9 +890,9 @@ static int ar0144_s_ctrl(struct v4l2_ctrl *ctrl) * configuration. */ state = v4l2_subdev_get_locked_active_state(&sensor->sd); - format = v4l2_subdev_state_get_format(state, 0); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); info = ar0144_format_info(sensor, format->code, true); - crop = v4l2_subdev_state_get_crop(state, 0); + crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); switch (ctrl->id) { case V4L2_CID_VBLANK: @@ -1104,24 +1116,44 @@ static int ar0144_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_mbus_code_enum *code) { struct ar0144 *sensor = to_ar0144(sd); - unsigned int index = 0; - unsigned int i; - for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) { - const struct ar0144_format_info *info = &ar0144_formats[i]; + switch (code->pad) { + case AR0144_PAD_IMAGE: { + const struct ar0144_format_info *info; - if (!(sensor->valid_formats & BIT(i))) - continue; + /* The internal image pad is hardwired to the native format. */ + if (code->index > 0) + return -EINVAL; - if (code->index == index) { - code->code = ar0144_format_code(sensor, info); - return 0; - } - - index++; + info = &ar0144_formats[0]; + code->code = ar0144_format_code(sensor, info); + return 0; } - return -EINVAL; + case AR0144_PAD_SOURCE: { + unsigned int index = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) { + const struct ar0144_format_info *info = &ar0144_formats[i]; + + if (!(sensor->valid_formats & BIT(i))) + continue; + + if (code->index == index) { + code->code = ar0144_format_code(sensor, info); + return 0; + } + + index++; + } + + return -EINVAL; + } + + default: + return -EINVAL; + } } static int ar0144_enum_frame_size(struct v4l2_subdev *sd, @@ -1130,26 +1162,35 @@ static int ar0144_enum_frame_size(struct v4l2_subdev *sd, { struct ar0144 *sensor = to_ar0144(sd); const struct ar0144_format_info *info; - const struct v4l2_rect *crop; + const struct v4l2_mbus_framefmt *fmt; - info = ar0144_format_info(sensor, fse->code, false); - if (!info) + if (fse->index > 0) return -EINVAL; - /* - * Enumerate binning/skipping. Supported factors are powers of two from - * /1 to /16. - */ + switch (fse->pad) { + case AR0144_PAD_IMAGE: + if (fse->code != ar0144_format_code(sensor, &ar0144_formats[0])) + return -EINVAL; - if (fse->index >= 5) + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_IMAGE); + break; + + case AR0144_PAD_SOURCE: + info = ar0144_format_info(sensor, fse->code, false); + if (!info) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + break; + + default: return -EINVAL; + } - crop = v4l2_subdev_state_get_crop(state, fse->pad); - - fse->min_width = crop->width / (1 << fse->index); - fse->max_width = fse->min_width; - fse->min_height = crop->height / (1 << fse->index); - fse->max_height = fse->min_height; + fse->min_width = fmt->width; + fse->max_width = fmt->width; + fse->min_height = fmt->height; + fse->max_height = fmt->height; return 0; } @@ -1161,31 +1202,22 @@ static int ar0144_set_fmt(struct v4l2_subdev *sd, struct ar0144 *sensor = to_ar0144(sd); const struct ar0144_format_info *info; struct v4l2_mbus_framefmt *fmt; - const struct v4l2_rect *crop; - unsigned int bin_x, bin_y; if (v4l2_subdev_is_streaming(sd) && format->which == V4L2_SUBDEV_FORMAT_ACTIVE) return -EBUSY; - fmt = v4l2_subdev_state_get_format(state, format->pad); - crop = v4l2_subdev_state_get_crop(state, format->pad); - - info = ar0144_format_info(sensor, format->format.code, true); - fmt->code = ar0144_format_code(sensor, info); + /* The format can only be set on the source pad. */ + if (format->pad != AR0144_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); /* - * The output size results from the binning/skipping applied to the - * crop rectangle. The x/y increments must be powers of 2. Clamp the - * width/height first, to avoid both divisions by 0 and the undefined - * behaviour of roundup_pow_of_two(0). + * Only the media bus code can be updated on the source pad, dimensions + * are set by the compose on the image pad rectangle. */ - fmt->width = clamp(format->format.width, 1U, crop->width); - fmt->height = clamp(format->format.height, 1U, crop->height); - bin_x = clamp(roundup_pow_of_two(crop->width / fmt->width), 1, 16); - bin_y = clamp(roundup_pow_of_two(crop->height / fmt->height), 1, 16); - fmt->width = crop->width / bin_x; - fmt->height = crop->height / bin_y; + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + info = ar0144_format_info(sensor, format->format.code, true); + fmt->code = ar0144_format_code(sensor, info); format->format = *fmt; @@ -1202,11 +1234,14 @@ static int ar0144_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - switch (sel->target) { - case V4L2_SEL_TGT_CROP: - sel->r = *v4l2_subdev_state_get_crop(state, sel->pad); - break; + /* + * The sensor doesn't support output crop, selection rectangles are + * supported on the internal image pad only. + */ + if (sel->pad != AR0144_PAD_IMAGE) + return -EINVAL; + switch (sel->target) { case V4L2_SEL_TGT_CROP_DEFAULT: sel->r.left = (AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_DEF_WIDTH) / 2; sel->r.top = (AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_DEF_HEIGHT) / 2; @@ -1221,6 +1256,14 @@ static int ar0144_get_selection(struct v4l2_subdev *sd, sel->r.height = AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT; break; + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); + break; + + case V4L2_SEL_TGT_COMPOSE: + sel->r = *v4l2_subdev_state_get_compose(state, AR0144_PAD_IMAGE); + break; + default: return -EINVAL; } @@ -1233,31 +1276,72 @@ static int ar0144_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_selection *sel) { struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *compose; struct v4l2_rect *crop; + unsigned int bin_x, bin_y; + + /* + * The sensor doesn't support output crop, selection rectangles are + * supported on the internal image pad only. + */ + if (sel->pad != AR0144_PAD_IMAGE) + return -EINVAL; if (v4l2_subdev_is_streaming(sd) && sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) return -EBUSY; - if (sel->target != V4L2_SEL_TGT_CROP) + crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); + compose = v4l2_subdev_state_get_compose(state, AR0144_PAD_IMAGE); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + crop->left = min_t(unsigned int, ALIGN(sel->r.left, 2), + AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - + AR0144_MIN_WIDTH); + crop->top = min_t(unsigned int, ALIGN(sel->r.top, 2), + AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - + AR0144_MIN_HEIGHT); + crop->width = clamp(sel->r.width, AR0144_MIN_WIDTH, + AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - + crop->left); + crop->height = clamp(sel->r.height, AR0144_MIN_HEIGHT, + AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - + crop->top); + + sel->r = *crop; + + compose->width = crop->width; + compose->height = crop->height; + break; + + case V4L2_SEL_TGT_COMPOSE: + /* + * The output size results from the binning/skipping applied to + * the crop rectangle. The x/y increments must be powers of 2. + * Clamp the width/height first, to avoid both divisions by 0 + * and the undefined behaviour of roundup_pow_of_two(0). + */ + compose->width = clamp(sel->r.width, 1U, crop->width); + compose->height = clamp(sel->r.height, 1U, crop->height); + bin_x = clamp(roundup_pow_of_two(crop->width / compose->width), + 1, 16); + bin_y = clamp(roundup_pow_of_two(crop->height / compose->height), + 1, 16); + compose->width = crop->width / bin_x; + compose->height = crop->height / bin_y; + + sel->r = *compose; + break; + + default: return -EINVAL; + } - crop = v4l2_subdev_state_get_crop(state, 0); - fmt = v4l2_subdev_state_get_format(state, 0); - - crop->left = min_t(unsigned int, ALIGN(sel->r.left, 2), - AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_MIN_WIDTH); - crop->top = min_t(unsigned int, ALIGN(sel->r.top, 2), - AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_MIN_HEIGHT); - crop->width = clamp(sel->r.width, AR0144_MIN_WIDTH, - AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - crop->left); - crop->height = clamp(sel->r.height, AR0144_MIN_HEIGHT, - AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - crop->top); - - sel->r = *crop; - - fmt->width = crop->width; - fmt->height = crop->height; + /* Propagate the compose rectangle to the output format. */ + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt->width = compose->width; + fmt->height = compose->height; return 0; } @@ -1271,8 +1355,11 @@ static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_subdev_state *state; u32 code; + if (pad != AR0144_PAD_SOURCE) + return -EINVAL; + state = v4l2_subdev_lock_and_get_active_state(sd); - fmt = v4l2_subdev_state_get_format(state, 0); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); code = fmt->code; v4l2_subdev_unlock_state(state); @@ -1352,10 +1439,35 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, struct ar0144 *sensor = to_ar0144(sd); struct v4l2_mbus_framefmt *fmt; struct v4l2_rect *crop; + struct v4l2_rect *compose; + + info = &ar0144_formats[0]; + + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_IMAGE); + fmt->width = AR0144_PIXEL_ARRAY_ACTIVE_WIDTH; + fmt->height = AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT; + fmt->code = ar0144_format_code(sensor, info); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; + + crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); + crop->left = 4; + crop->top = 4; + crop->width = AR0144_DEF_WIDTH; + crop->height = AR0144_DEF_HEIGHT; + + compose = v4l2_subdev_state_get_compose(state, AR0144_PAD_IMAGE); + compose->left = 0; + compose->top = 0; + compose->width = AR0144_DEF_WIDTH; + compose->height = AR0144_DEF_HEIGHT; info = ar0144_format_info(sensor, 0, true); - fmt = v4l2_subdev_state_get_format(state, 0); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); fmt->width = AR0144_DEF_WIDTH; fmt->height = AR0144_DEF_HEIGHT; fmt->code = ar0144_format_code(sensor, info); @@ -1365,12 +1477,6 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; fmt->xfer_func = V4L2_XFER_FUNC_NONE; - crop = v4l2_subdev_state_get_crop(state, 0); - crop->left = 4; - crop->top = 4; - crop->width = AR0144_DEF_WIDTH; - crop->height = AR0144_DEF_HEIGHT; - return 0; } @@ -1415,9 +1521,12 @@ static int ar0144_init_subdev(struct ar0144 *sensor) sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; sd->entity.ops = &ar0144_entity_ops; - sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->pads[AR0144_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + sensor->pads[AR0144_PAD_IMAGE].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_INTERNAL; - ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad); + ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(sensor->pads), + sensor->pads); if (ret) return ret; @@ -1435,7 +1544,7 @@ static int ar0144_init_subdev(struct ar0144 *sensor) * rate) and blanking controls. */ state = v4l2_subdev_lock_and_get_active_state(sd); - format = v4l2_subdev_state_get_format(state, 0); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); info = ar0144_format_info(sensor, format->code, true); ar0144_update_link_freqs(sensor, info); From patchwork Thu Sep 5 22:53:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 826140 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 965E01B1425; Thu, 5 Sep 2024 22:53:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576817; cv=none; b=DinpOCbDcQIogq2t/7qraCeBcpZsbD+YJuhzOOu9BTsxADAA6zTlYccZd6VFl6VW3fpX4y4U+pK58/zSZgxTnlzZAGC8EaGmLKpUQEuxGRI32kV4dQ6N1qgcGi04b84aLLcXYwB05Og2uIQyXCwlPUan8bdkLgZYyLQk+tA9nPQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576817; c=relaxed/simple; bh=nEdyS6y7cAZfLXda/uyC5EEe/rOzj2G8C6Rro+ttGn4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JqYWxZUH5XM9PAJqBqlw/plX/mZZKPMDlRB3R0WtR7KvqkJ/NVCU4fgGYP+euXTpYGTr6f/CR6ttRb4CLnEWGxAuQN1eCdB4jc8pzZC09PHN96k9/UA7pCP+NHcktgWjr8FBd/UKGdzZkG5f0xNXAqb7v12ofv0ZVD4AdUse6/8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=L5FHN/nE; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="L5FHN/nE" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2331718B9; Fri, 6 Sep 2024 00:52:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576730; bh=nEdyS6y7cAZfLXda/uyC5EEe/rOzj2G8C6Rro+ttGn4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=L5FHN/nEAHcmljSKJC0Ptz6rWlker/4HDwSorw/pEFK1mXNyqoFcvYXIh/7A86PZo /K6EZD3e8rOMyCr9+MXe7ac6moIEN8qUbtsHqjchjvLlnzubBYEqOa6tUsqAGZufL4 SSaHQWfewzw5tWM8NKqwVcw8dybZGYDMxawuDeWE= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 5/9] media: i2c: ar0144: Add image stream Date: Fri, 6 Sep 2024 01:53:03 +0300 Message-ID: <20240905225308.11267-6-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 In preparation for embedded data stream support, introduce a new ar0144_stream_ids enumeration for stream IDs, with a single value, AR0144_STREAM_IMAGE for the image data stream. Use it when accessing the formats, crop and compose rectangles on the source pad. This is meant to reduce the size of further commits, and doesn't introduce any functional change. Signed-off-by: Laurent Pinchart --- drivers/media/i2c/ar0144.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index ea76d484e6d1..432f2dba58d2 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -342,6 +342,10 @@ enum ar0144_pad_ids { AR0144_NUM_PADS, }; +enum ar0144_stream_ids { + AR0144_STREAM_IMAGE, +}; + struct ar0144_model { bool mono; }; @@ -534,7 +538,8 @@ static int ar0144_start_streaming(struct ar0144 *sensor, */ __v4l2_ctrl_grab(sensor->link_freq, true); - format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); compose = v4l2_subdev_state_get_compose(state, AR0144_PAD_IMAGE); info = ar0144_format_info(sensor, format->code, true); @@ -890,7 +895,8 @@ static int ar0144_s_ctrl(struct v4l2_ctrl *ctrl) * configuration. */ state = v4l2_subdev_get_locked_active_state(&sensor->sd); - format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); info = ar0144_format_info(sensor, format->code, true); crop = v4l2_subdev_state_get_crop(state, AR0144_PAD_IMAGE); @@ -1180,7 +1186,8 @@ static int ar0144_enum_frame_size(struct v4l2_subdev *sd, if (!info) return -EINVAL; - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); break; default: @@ -1207,15 +1214,17 @@ static int ar0144_set_fmt(struct v4l2_subdev *sd, format->which == V4L2_SUBDEV_FORMAT_ACTIVE) return -EBUSY; - /* The format can only be set on the source pad. */ - if (format->pad != AR0144_PAD_SOURCE) + /* The format can only be set for the image stream on the source pad. */ + if (format->pad != AR0144_PAD_SOURCE || + format->stream != AR0144_STREAM_IMAGE) return v4l2_subdev_get_fmt(sd, state, format); /* * Only the media bus code can be updated on the source pad, dimensions * are set by the compose on the image pad rectangle. */ - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); info = ar0144_format_info(sensor, format->format.code, true); fmt->code = ar0144_format_code(sensor, info); @@ -1339,7 +1348,8 @@ static int ar0144_set_selection(struct v4l2_subdev *sd, } /* Propagate the compose rectangle to the output format. */ - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); fmt->width = compose->width; fmt->height = compose->height; @@ -1359,7 +1369,8 @@ static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, return -EINVAL; state = v4l2_subdev_lock_and_get_active_state(sd); - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); code = fmt->code; v4l2_subdev_unlock_state(state); @@ -1369,7 +1380,7 @@ static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, fd->num_entries = 1; fd->entry[0].pixelcode = code; - fd->entry[0].stream = 0; + fd->entry[0].stream = AR0144_STREAM_IMAGE; fd->entry[0].bus.csi2.vc = 0; fd->entry[0].bus.csi2.dt = info->dt; @@ -1467,7 +1478,8 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, info = ar0144_format_info(sensor, 0, true); - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); fmt->width = AR0144_DEF_WIDTH; fmt->height = AR0144_DEF_HEIGHT; fmt->code = ar0144_format_code(sensor, info); @@ -1544,7 +1556,8 @@ static int ar0144_init_subdev(struct ar0144 *sensor) * rate) and blanking controls. */ state = v4l2_subdev_lock_and_get_active_state(sd); - format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE); + format = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE); info = ar0144_format_info(sensor, format->code, true); ar0144_update_link_freqs(sensor, info); From patchwork Thu Sep 5 22:53:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 825747 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 A06C61AB514; Thu, 5 Sep 2024 22:53:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576820; cv=none; b=licTeIyBQKHClQRD6hzNZbTT5zwL/S05yIlREpnw1EFEe1tpEt7p8DpMkAV0x9ffJpKriYZow6bbUbzGquUZ71iGXN9vng5KiyR3lvFxzqUrGmgQqHrfBZ+Ip4QztY+LPCTU6RI50kXFBbZchnRFivyGa5Z9nQW65Pdpeq8NFXU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576820; c=relaxed/simple; bh=SJs3uGkQLdSxJPqrkaRewpnSWVHVRy+oUPRnsCpSTSc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WN2nJ+sfJMNdbtBJtMiHdaGtyfC//jY9JuN4uRCditMAFo77/8jAR2oYyVH3aHD7QrIbI72rrcpVNy//pERNMMR1J+thzMSav6UnoYOJXzOK6Z1nEiWl5x60orvVPZOk9MjsZTak1vSzsrLliSk8PR7srP0DuwFVoEeCug1lkY0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=vDGhZoaT; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vDGhZoaT" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E28CF1AC8; Fri, 6 Sep 2024 00:52:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576732; bh=SJs3uGkQLdSxJPqrkaRewpnSWVHVRy+oUPRnsCpSTSc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vDGhZoaTo0HB/fzPWpbSDGX8i+iPQz8Xniu5fTtczSQZa2zIKRvGZzBo34z1Hv9If kK6RVPo5oo1C2I3uqBV3cSSkYrY9ch0DEQ5pFXDS25W7rDcL72jXx8q4AYREUj5n7w aD3nd5e9n7c8SL9JO+md9+kFCx7lGtHiE1fwvIvA= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 6/9] media: i2c: ar0144: Report internal routes to userspace Date: Fri, 6 Sep 2024 01:53:04 +0300 Message-ID: <20240905225308.11267-7-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Usage of internal pads creates a route internal to the subdev, and the V4L2 camera sensor API requires such routes to be reported to userspace. Create the route in the .init_state() operation. Internal routing support requires stream support, so set the V4L2_SUBDEV_FL_STREAMS flag. As the route is immutable, there's no need to implement the .set_routing() operation. Signed-off-by: Laurent Pinchart --- drivers/media/i2c/ar0144.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index 432f2dba58d2..a784d806b189 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -1446,11 +1446,31 @@ static int ar0144_disable_streams(struct v4l2_subdev *sd, static int ar0144_entity_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { + struct v4l2_subdev_route routes[] = { + { + .sink_pad = AR0144_PAD_IMAGE, + .sink_stream = 0, + .source_pad = AR0144_PAD_SOURCE, + .source_stream = AR0144_STREAM_IMAGE, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE | + V4L2_SUBDEV_ROUTE_FL_IMMUTABLE, + }, + }; + struct v4l2_subdev_krouting routing = { + .len_routes = ARRAY_SIZE(routes), + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; const struct ar0144_format_info *info; struct ar0144 *sensor = to_ar0144(sd); struct v4l2_mbus_framefmt *fmt; struct v4l2_rect *crop; struct v4l2_rect *compose; + int ret; + + ret = v4l2_subdev_set_routing(sd, state, &routing); + if (ret) + return ret; info = &ar0144_formats[0]; @@ -1528,7 +1548,8 @@ static int ar0144_init_subdev(struct ar0144 *sensor) v4l2_i2c_subdev_init(sd, client, &ar0144_subdev_ops); - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; sd->internal_ops = &ar0144_subdev_internal_ops; sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; sd->entity.ops = &ar0144_entity_ops; From patchwork Thu Sep 5 22:53:05 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 826139 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 79C881482E9; Thu, 5 Sep 2024 22:53:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576820; cv=none; b=gvE/+HBdJDl0W8KJTfD4ocBlimIsHr+z1OFrio6dl8uBfBYoiAQaX0kqDzmzisyMT3TDltvXDNlA+83d4MVuOS72fOC6uwu9LZ/4B/KCWFwGQFW9lE5ytSL89ytGisPBEOw/5af7jbbvpH1dI23l8wT/6qLMlkm6vtJz0rApPq0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576820; c=relaxed/simple; bh=8C/tkDNF52pieEQGdPKerb0YP5g0XAbTP6AkTqtR3jU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FAoXvgG0rd9FyliXDOg7XGUiYX0vJE0MyZQVfkqNdZFyl17Mid38QKvymV77gnSVinHVLieRT268HRzVPcG7Ilm2ODfc/1Zds01p6b7VXcaNE7z9LMw8VSnAVPxQ3yIfOVMTy3zY7DOS8lkw+DUxe8XDLY/zH76y6qH052+5LLY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=OYiiE6MW; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OYiiE6MW" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7961C1BA8; Fri, 6 Sep 2024 00:52:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576733; bh=8C/tkDNF52pieEQGdPKerb0YP5g0XAbTP6AkTqtR3jU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OYiiE6MWn62uN4QclQgK9A98zR++0Q5zGV8oKtEpZSLxmzXWjrCXmjPLacTZmDulH qypTVlH391NMeN1pB50uKLeSLkz3dAhne0YB3+FoxSXvq8XXamzO7KRoxwVHhyowAA hMl2MTzjxJzitDxCmWppw7eQiyNZhPzb+vyz3wnk= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 7/9] media: i2c: ar0144: Add embedded data support Date: Fri, 6 Sep 2024 01:53:05 +0300 Message-ID: <20240905225308.11267-8-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The AR0144 can generate embedded data. Report it as an additional stream, with a new internal embedded data pad, and update subdev operations accordingly. Make the embedded data generation unconditional for now to simplify the implementation. It can be made configurable later if desired. Signed-off-by: Laurent Pinchart --- Changes since v1: - Fix frame size enumeration --- drivers/media/i2c/ar0144.c | 147 +++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 29 deletions(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index a784d806b189..c2ef8b26a2bc 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -314,6 +314,9 @@ #define AR0144_PIXEL_ARRAY_ACTIVE_WIDTH 1288U #define AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT 808U +/* Embedded metadata stream height */ +#define AR0144_EMBEDDED_DATA_HEIGHT 2U + /* * Documentation indicates minimum horizontal and vertical blanking of 208 * pixels and 27 lines respectively, which matches the default values for the @@ -339,11 +342,13 @@ enum ar0144_pad_ids { AR0144_PAD_SOURCE = 0, AR0144_PAD_IMAGE, + AR0144_PAD_EDATA, AR0144_NUM_PADS, }; enum ar0144_stream_ids { AR0144_STREAM_IMAGE, + AR0144_STREAM_EDATA, }; struct ar0144_model { @@ -353,6 +358,7 @@ struct ar0144_model { struct ar0144_format_info { u32 colour; u32 mono; + u32 edata; u16 bpp; u16 dt; }; @@ -373,16 +379,19 @@ static const struct ar0144_format_info ar0144_formats[] = { { .colour = MEDIA_BUS_FMT_SGRBG12_1X12, .mono = MEDIA_BUS_FMT_Y12_1X12, + .edata = MEDIA_BUS_FMT_META_12, .bpp = 12, .dt = MIPI_CSI2_DT_RAW12, }, { .colour = MEDIA_BUS_FMT_SGRBG10_1X10, .mono = MEDIA_BUS_FMT_Y10_1X10, + .edata = MEDIA_BUS_FMT_META_10, .bpp = 10, .dt = MIPI_CSI2_DT_RAW10, }, { .colour = MEDIA_BUS_FMT_SGRBG8_1X8, .mono = MEDIA_BUS_FMT_Y8_1X8, + .edata = MEDIA_BUS_FMT_META_8, .bpp = 8, .dt = MIPI_CSI2_DT_RAW8, }, @@ -574,8 +583,8 @@ static int ar0144_start_streaming(struct ar0144 *sensor, * Enable generation of embedded statistics, required for the on-chip * auto-exposure. There is no downside in enabling it unconditionally. */ - cci_write(sensor->regmap, AR0144_SMIA_TEST, AR0144_STATS_EN | 0x1802, - &ret); + cci_write(sensor->regmap, AR0144_SMIA_TEST, AR0144_EMBEDDED_DATA | + AR0144_STATS_EN | 0x1802, &ret); if (ret) goto error; @@ -1136,7 +1145,25 @@ static int ar0144_enum_mbus_code(struct v4l2_subdev *sd, return 0; } - case AR0144_PAD_SOURCE: { + case AR0144_PAD_EDATA: { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_CCS_EMBEDDED; + return 0; + } + + case AR0144_PAD_SOURCE: + default: + break; + } + + /* + * On the source pad, the sensor supports multiple image raw formats + * with different bit depths. The embedded data format bit depth + * follows the image stream. + */ + if (code->stream == AR0144_STREAM_IMAGE) { unsigned int index = 0; unsigned int i; @@ -1155,11 +1182,18 @@ static int ar0144_enum_mbus_code(struct v4l2_subdev *sd, } return -EINVAL; + } else { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_EDATA); + code->code = fmt->code; } - default: - return -EINVAL; - } + return 0; } static int ar0144_enum_frame_size(struct v4l2_subdev *sd, @@ -1173,25 +1207,21 @@ static int ar0144_enum_frame_size(struct v4l2_subdev *sd, if (fse->index > 0) return -EINVAL; - switch (fse->pad) { - case AR0144_PAD_IMAGE: - if (fse->code != ar0144_format_code(sensor, &ar0144_formats[0])) - return -EINVAL; + fmt = v4l2_subdev_state_get_format(state, fse->pad, fse->stream); - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_IMAGE); - break; - - case AR0144_PAD_SOURCE: + /* + * Verify the media bus code. On the source image stream multiple + * options are supported, while on all other streams the requested code + * must match the current format. + */ + if (fse->pad == AR0144_PAD_SOURCE && + fse->stream == AR0144_STREAM_IMAGE) { info = ar0144_format_info(sensor, fse->code, false); if (!info) return -EINVAL; - - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, - AR0144_STREAM_IMAGE); - break; - - default: - return -EINVAL; + } else { + if (fse->code != fmt->code) + return -EINVAL; } fse->min_width = fmt->width; @@ -1230,6 +1260,11 @@ static int ar0144_set_fmt(struct v4l2_subdev *sd, format->format = *fmt; + /* Update the format on the source side of the embedded data stream. */ + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_EDATA); + fmt->code = info->edata; + if (format->which != V4L2_SUBDEV_FORMAT_ACTIVE) return 0; @@ -1353,6 +1388,14 @@ static int ar0144_set_selection(struct v4l2_subdev *sd, fmt->width = compose->width; fmt->height = compose->height; + /* Update the embedded data stream width. */ + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_EDATA); + fmt->width = compose->width; + + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_EDATA); + fmt->width = compose->width; + return 0; } @@ -1361,29 +1404,35 @@ static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, { struct ar0144 *sensor = to_ar0144(sd); const struct ar0144_format_info *info; - const struct v4l2_mbus_framefmt *fmt; struct v4l2_subdev_state *state; - u32 code; + u32 img_code; + u32 ed_code; if (pad != AR0144_PAD_SOURCE) return -EINVAL; state = v4l2_subdev_lock_and_get_active_state(sd); - fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, - AR0144_STREAM_IMAGE); - code = fmt->code; + img_code = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_IMAGE)->code; + ed_code = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_EDATA)->code; v4l2_subdev_unlock_state(state); - info = ar0144_format_info(sensor, code, true); + info = ar0144_format_info(sensor, img_code, true); fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; - fd->num_entries = 1; + fd->num_entries = 2; - fd->entry[0].pixelcode = code; + fd->entry[0].pixelcode = img_code; fd->entry[0].stream = AR0144_STREAM_IMAGE; fd->entry[0].bus.csi2.vc = 0; fd->entry[0].bus.csi2.dt = info->dt; + fd->entry[1].pixelcode = ed_code; + fd->entry[1].stream = AR0144_STREAM_EDATA; + fd->entry[1].bus.csi2.vc = 0; + fd->entry[1].bus.csi2.dt = MIPI_CSI2_DT_EMBEDDED_8B; + return 0; } @@ -1415,6 +1464,13 @@ static int ar0144_enable_streams(struct v4l2_subdev *sd, struct ar0144 *sensor = to_ar0144(sd); int ret; + /* + * The image stream controls sensor streaming, as embedded data isn't + * controllable independently. + */ + if (!(streams_mask & BIT(AR0144_STREAM_IMAGE))) + return 0; + ret = pm_runtime_resume_and_get(sensor->dev); if (ret < 0) return ret; @@ -1436,6 +1492,13 @@ static int ar0144_disable_streams(struct v4l2_subdev *sd, { struct ar0144 *sensor = to_ar0144(sd); + /* + * The image stream controls sensor streaming, as embedded data isn't + * controllable independently. + */ + if (!(streams_mask & BIT(AR0144_STREAM_IMAGE))) + return 0; + ar0144_stop_streaming(sensor); pm_runtime_mark_last_busy(sensor->dev); pm_runtime_put_autosuspend(sensor->dev); @@ -1454,6 +1517,13 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, .source_stream = AR0144_STREAM_IMAGE, .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE | V4L2_SUBDEV_ROUTE_FL_IMMUTABLE, + }, { + .sink_pad = AR0144_PAD_EDATA, + .sink_stream = 0, + .source_pad = AR0144_PAD_SOURCE, + .source_stream = AR0144_STREAM_EDATA, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE | + V4L2_SUBDEV_ROUTE_FL_IMMUTABLE, }, }; struct v4l2_subdev_krouting routing = { @@ -1472,6 +1542,7 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, if (ret) return ret; + /* Image pad. */ info = &ar0144_formats[0]; fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_IMAGE); @@ -1496,6 +1567,14 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, compose->width = AR0144_DEF_WIDTH; compose->height = AR0144_DEF_HEIGHT; + /* Embedded data pad. */ + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_EDATA); + fmt->width = AR0144_DEF_WIDTH; + fmt->height = AR0144_EMBEDDED_DATA_HEIGHT; + fmt->code = MEDIA_BUS_FMT_CCS_EMBEDDED; + fmt->field = V4L2_FIELD_NONE; + + /* Source pad, image stream. */ info = ar0144_format_info(sensor, 0, true); fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, @@ -1509,6 +1588,14 @@ static int ar0144_entity_init_state(struct v4l2_subdev *sd, fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; fmt->xfer_func = V4L2_XFER_FUNC_NONE; + /* Source pad, embedded data stream. */ + fmt = v4l2_subdev_state_get_format(state, AR0144_PAD_SOURCE, + AR0144_STREAM_EDATA); + fmt->width = AR0144_DEF_WIDTH; + fmt->height = AR0144_EMBEDDED_DATA_HEIGHT; + fmt->code = info->edata; + fmt->field = V4L2_FIELD_NONE; + return 0; } @@ -1557,6 +1644,8 @@ static int ar0144_init_subdev(struct ar0144 *sensor) sensor->pads[AR0144_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; sensor->pads[AR0144_PAD_IMAGE].flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL; + sensor->pads[AR0144_PAD_EDATA].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_INTERNAL; ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(sensor->pads), sensor->pads); From patchwork Thu Sep 5 22:53:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 825746 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 72C451482E9; Thu, 5 Sep 2024 22:53:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576822; cv=none; b=LkhkXylVG/OhxM/tCQQCkj6BEAWkAG9Hs74LA/KFSttbqKWrBUwUEGhBkN7iVYy/B05ag42bLRHJPRAokelLiZdtRc/6hjZSfZUxjCjRh7222weBQ6tcAf1sfgkkk5XGQcoYvXqm9VRwWxbJa9TQmuwSgMRf7s3msfzLarNo7hU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576822; c=relaxed/simple; bh=vj3KhOD/5YRFjJ8FWlZ9KBorRcLXXcJfS5/189OR5l4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BE3tRmNd9muhKExqvh/B5TyPkK/Izxd9iHDRZO/Y5JqDsyC3OhZNJ6Sa/enX5xBEcg29hQApCubsySvtNxt3kk+wGVjPQ+yqhszrQJjpi/Plp9+TSr9GZbLUXrfsMCOv7Tp7X4WX6UJH8kJ1xE3QhAL68CobkYK98Z3vmHmxO2U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=Z66bvVOn; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Z66bvVOn" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 08EE71BC1; Fri, 6 Sep 2024 00:52:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576735; bh=vj3KhOD/5YRFjJ8FWlZ9KBorRcLXXcJfS5/189OR5l4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Z66bvVOnwx4xr2E4EhSto8ruSOXEOx8IAEuB4NnquKIT0QGhPrLQLK34XKvpDrvSB ivkjcI5eqvySaAA8qywZpIXOkTyRCX+ne1w1iJKQabNYlYbz/N8lThcRnL8patMwYG n/PtXM4/Oxq9130e+eHLTIr0Q2UqkI4F7MZ6D1rY= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 8/9] media: v4l: ctrls: Add a control for companding Date: Fri, 6 Sep 2024 01:53:06 +0300 Message-ID: <20240905225308.11267-9-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Companding refers to compression and expansion of pixel data to fit in a smaller range. The control is named V4L2_CID_COMPANDING, and only supports compression initially. Signed-off-by: Laurent Pinchart --- Changes since v1: - Rename V4L2_COMPANDING_ALAW to V4L2_COMPANDING_COMPRESS - Document V4L2_COMPANDING_LINEAR and V4L2_COMPANDING_COMPRESS --- .../media/v4l/ext-ctrls-image-process.rst | 16 ++++++++++++++++ drivers/media/v4l2-core/v4l2-ctrls-defs.c | 9 +++++++++ include/uapi/linux/v4l2-controls.h | 5 +++++ 3 files changed, 30 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-image-process.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-image-process.rst index b1c2ab2854af..d529b0a8cd41 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-image-process.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-image-process.rst @@ -55,3 +55,19 @@ Image Process Control IDs control value divided by e.g. 0x100, meaning that to get no digital gain the control value needs to be 0x100. The no-gain configuration is also typically the default. + +``V4L2_CID_COMPANDING (menu)`` + Companding refers to compression and expansion of pixel data to fit in a + smaller range. + + +.. tabularcolumns:: |p{5.7cm}|p{11.8cm}| + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + + * - ``V4L2_COMPANDING_LINEAR`` + - No companding. + * - ``V4L2_COMPANDING_COMPRESS`` + - Compress the data using a device-specific curve. diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 1ea52011247a..841e415a88ae 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -611,6 +611,11 @@ const char * const *v4l2_ctrl_get_menu(u32 id) "Cyclic", NULL, }; + static const char * const companding[] = { + "Linear", + "A-Law", + NULL + }; switch (id) { case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: @@ -750,6 +755,8 @@ const char * const *v4l2_ctrl_get_menu(u32 id) return camera_orientation; case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: return intra_refresh_period_type; + case V4L2_CID_COMPANDING: + return companding; default: return NULL; } @@ -1164,6 +1171,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_TEST_PATTERN: return "Test Pattern"; case V4L2_CID_DEINTERLACING_MODE: return "Deinterlacing Mode"; case V4L2_CID_DIGITAL_GAIN: return "Digital Gain"; + case V4L2_CID_COMPANDING: return "Companding"; /* DV controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1421,6 +1429,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_CAMERA_ORIENTATION: case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: case V4L2_CID_HDR_SENSOR_MODE: + case V4L2_CID_COMPANDING: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_LINK_FREQ: diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 974fd254e573..ca9e25f72886 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1225,6 +1225,11 @@ enum v4l2_jpeg_chroma_subsampling { #define V4L2_CID_TEST_PATTERN (V4L2_CID_IMAGE_PROC_CLASS_BASE + 3) #define V4L2_CID_DEINTERLACING_MODE (V4L2_CID_IMAGE_PROC_CLASS_BASE + 4) #define V4L2_CID_DIGITAL_GAIN (V4L2_CID_IMAGE_PROC_CLASS_BASE + 5) +#define V4L2_CID_COMPANDING (V4L2_CID_IMAGE_PROC_CLASS_BASE + 6) +enum v4l2_companding { + V4L2_COMPANDING_LINEAR = 0, + V4L2_COMPANDING_COMPRESS = 1, +}; /* DV-class control IDs defined by V4L2 */ #define V4L2_CID_DV_CLASS_BASE (V4L2_CTRL_CLASS_DV | 0x900) From patchwork Thu Sep 5 22:53:07 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 826138 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (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 C105D1AB514; Thu, 5 Sep 2024 22:53:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576823; cv=none; b=iT3iCOCnStj1juZPTorIAyFfI91cXEGYJm4LfmMVy8ewpCUUMPaFS7tDJLTzLZFnDBxg41RJ1OfgWYXwJ86qKlUEA6oe+K9/EsNPVWMvZPLxaIy5R3qmAcdRqkbvRG4CrRNYOQ6mgdv0/Sq3ucsM3RiT/Gap3m5sQBVNwoxkDxU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725576823; c=relaxed/simple; bh=pw7XGS4OO8Fu31h0Rowj9Qv+w+cgSylPnYD9HTV1WSw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gKhgnaiJ5azLY0szDAjNvSexRzrTSz4Um1ogfPycYnzA1py2fTQ7BNfl5Z/DDIIrY6THQJL8W+fXL8ewlc7ndoC7dvPijovUaA7XKhWynzYKxa5lohjbvng8wdVXDczbX6imIo47e2scOzmojkBlA7BqBCyRhNXkSVjT07+RtE4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=K8pAaQPU; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="K8pAaQPU" Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 742CF1BD0; Fri, 6 Sep 2024 00:52:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1725576736; bh=pw7XGS4OO8Fu31h0Rowj9Qv+w+cgSylPnYD9HTV1WSw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=K8pAaQPUU7wpRah/h9HCfSeVPlmoKpTPsHtfxhEujH4B7TC+xS72jWN1WA7nyybJf cWk7p8fXN89B5Dckz2o/Ag46W3UMKSG5ugcommlReKiHwOH8ERtABj5viHQki06sbq IQKZ5jYWbQJXY96PZa5XB05Ot9R1/oKUFHFkS3Qs= From: Laurent Pinchart To: linux-media@vger.kernel.org, devicetree@vger.kernel.org Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus , Hans Verkuil , Dave Stevenson Subject: [PATCH/RFC v2 9/9] media: i2c: ar0144: Add support for companding Date: Fri, 6 Sep 2024 01:53:07 +0300 Message-ID: <20240905225308.11267-10-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> References: <20240905225308.11267-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add the V4L2_CID_COMPANDING control to enable control of companding. The sensor supports compression only, enable the linear (no companding) and compression menu entries. Signed-off-by: Laurent Pinchart --- drivers/media/i2c/ar0144.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c index c2ef8b26a2bc..8789ba0d2ea8 100644 --- a/drivers/media/i2c/ar0144.c +++ b/drivers/media/i2c/ar0144.c @@ -1010,6 +1010,12 @@ static int ar0144_s_ctrl(struct v4l2_ctrl *ctrl) AR0144_AUTO_AG_EN | AR0144_AE_ENABLE : 0, &ret); break; + case V4L2_CID_COMPANDING: + cci_write(sensor->regmap, AR0144_COMPANDING, + ctrl->val == V4L2_COMPANDING_COMPRESS ? + AR0144_COMPAND_EN : 0, &ret); + break; + case V4L2_CID_LINK_FREQ: break; @@ -1044,7 +1050,7 @@ static int ar0144_init_ctrls(struct ar0144 *sensor) if (ret < 0) return ret; - v4l2_ctrl_handler_init(&sensor->ctrls, 17); + v4l2_ctrl_handler_init(&sensor->ctrls, 18); v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ar0144_ctrl_ops, &props); @@ -1108,6 +1114,10 @@ static int ar0144_init_ctrls(struct ar0144 *sensor) sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std_menu(&sensor->ctrls, &ar0144_ctrl_ops, + V4L2_CID_COMPANDING, V4L2_COMPANDING_COMPRESS, 0, + V4L2_COMPANDING_LINEAR); + if (sensor->ctrls.error) { ret = sensor->ctrls.error; v4l2_ctrl_handler_free(&sensor->ctrls);