From nobody Mon Jun 8 06:38:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D261045349A; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780642981; cv=none; b=RAjGzPSg6mrTqJtvbiDBd6IjzleCglZWJvUI5F/3/J9f+MeOOFWKzSlgvorwqm+pgjE3ootZM3M+q0/t1Z9SHoOsilJm2KbNpv3xKw4ocqIMh0aMb/BiFQ2vVE8ay+e9tuXI/kc5UR4bGfpGVfhftQzgbVLnUjRtzo9Hw74XEXs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780642981; c=relaxed/simple; bh=T7I5aR+M0fh+TjrqCl1kxRrcHiJV7tk6Z6TRI0rImkw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=g8IeAeIPHjeUnDzaecx7e3ePjIqnSnDmM8Pgq8vp3BuwYddUGrAPqZVK/1GosApcthr6QG7kEoMI+xwIRY7ckkqXGU+pbIadzfkmYZXWoOv2Z8+63X98cEZVaZulxHRwoUKB4fegv8obyyPjkFl5ZFHB+NQsgXfOx+9V90lcSng= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=njRrDiHZ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="njRrDiHZ" Received: by smtp.kernel.org (Postfix) with ESMTPS id 711ADC2BCC7; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1780642981; bh=T7I5aR+M0fh+TjrqCl1kxRrcHiJV7tk6Z6TRI0rImkw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=njRrDiHZI2n+lq8jeZ2dT8sjH/ZfsgPfgqeeiHB6mMUout/riI1tGyrH/S6OFd3m8 sAs1D67iv2bCyJpAIvyaIxFcUeIFhVqK1FGH2eFAMxArlKmxnA6UsJYVDbMElr5u6W AuX6JfJZM7/mqrscBfFCfuPlP+sZwoUDcVKF/FtRjBOlsvXAIUUyknxRj4mrKB3kwg uw7BkcyzGT+HYD0zx2W5urZuK4u/h2cZ1jx5dJiCGHGqFRhLFZypz2OAKOFyHLFuDx 88oh14hSK6InIBgK6kTF+1uwRiU8A875DaZdG1DdIkeAQHxwj40NSI+27Rj4IBUaVI qK7K4yZgi5XGw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 57994CD6E6B; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) From: Herman van Hazendonk via B4 Relay Date: Fri, 05 Jun 2026 09:03:00 +0200 Subject: [PATCH v3 1/2] dt-bindings: media: i2c: add aptina,mt9m113 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260605-submit-media-mt9m113-v3-1-dc3e07d5d70b@herrie.org> References: <20260605-submit-media-mt9m113-v3-0-dc3e07d5d70b@herrie.org> In-Reply-To: <20260605-submit-media-mt9m113-v3-0-dc3e07d5d70b@herrie.org> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Laurent Pinchart , Hans Verkuil , Konrad Dybcio X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780642979; l=4223; i=github.com@herrie.org; s=20240417; h=from:subject:message-id; bh=bMSc8WQ6ospWKq8ni5duh9EVZEe6lh8wHLgaiFTHJpU=; b=8S4h62JsAMORDY/fWfIt7iseiWdVry4ScO3sOJyJ2n4Br8hHGr5kS1wPBBDrd+KxHJK8UduKq OKSwzjXvMJMDfm6CvjCuKYgJ4hfrwAYe7WoqHDhxAwfMaT6TMuVWPDU X-Developer-Key: i=github.com@herrie.org; a=ed25519; pk=YYxdq8fb5O9vhkW3n2dCH044FPZZO5718v/du7fRhFw= X-Endpoint-Received: by B4 Relay for github.com@herrie.org/20240417 with auth_id=809 X-Original-From: Herman van Hazendonk Reply-To: github.com@herrie.org From: Herman van Hazendonk Add the binding for the Aptina (now ON Semiconductor) MT9M113 1.3 megapixel SoC image sensor with on-die ISP. The chip is used as the front (user-facing) camera on the HP TouchPad tablet and connects to the host SoC over MIPI CSI-2. The binding describes the chip's i2c address, optional reset and standby GPIOs, the per-supply regulators (VDD, VDD_IO, VDDA), the external clock input, and the CSI-2 endpoint pads exposed via the "port" subnode. Signed-off-by: Herman van Hazendonk --- .../bindings/media/i2c/aptina,mt9m113.yaml | 127 +++++++++++++++++= ++++ 1 file changed, 127 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/aptina,mt9m113.yam= l b/Documentation/devicetree/bindings/media/i2c/aptina,mt9m113.yaml new file mode 100644 index 000000000000..7fa7cb2fedf9 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/aptina,mt9m113.yaml @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/aptina,mt9m113.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Aptina MT9M113 1.3 Megapixel CMOS Digital Image Sensor + +maintainers: + - Herman van Hazendonk + +description: |- + The Aptina MT9M113 is a 1.3 Megapixel CMOS digital image sensor with + 1280x1024 active pixels. It supports dual context operation: + Context A (640x480 preview mode with binning) and Context B (1280x1024 + capture mode). It is programmable through an I2C interface and outputs + image data over a 1-lane MIPI CSI-2 connection. + +properties: + compatible: + const: aptina,mt9m113 + + reg: + description: I2C device address + enum: + - 0x3c + - 0x48 + - 0x5d + + clocks: + description: EXTCLK clock signal (24 MHz typical) + maxItems: 1 + + vdd-supply: + description: + Core digital voltage supply, 1.8V + + vddio-supply: + description: + I/O digital voltage supply, 1.8V or 2.8V + + vaa-supply: + description: + Analog voltage supply, 2.8V + + reset-gpios: + description: |- + Reference to the GPIO connected to the RESET_BAR pin, if any (active + low). + + powerdown-gpios: + description: |- + Reference to the GPIO connected to the STANDBY/PWDN pin, if any (act= ive + high). When asserted, the sensor enters low-power standby mode. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + const: 4 + description: MIPI CSI-2 D-PHY + + link-frequencies: true + remote-endpoint: true + data-lanes: + minItems: 1 + maxItems: 1 + + required: + - bus-type + - link-frequencies + - data-lanes + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - vdd-supply + - vddio-supply + - vaa-supply + - port + +additionalProperties: false + +examples: + - | + #include + #include + + i2c0 { + #address-cells =3D <1>; + #size-cells =3D <0>; + + camera@3c { + compatible =3D "aptina,mt9m113"; + reg =3D <0x3c>; + + clocks =3D <&mmcc 48>; + + reset-gpios =3D <&pm8058_gpio 29 GPIO_ACTIVE_LOW>; + powerdown-gpios =3D <&pm8058_gpio 30 GPIO_ACTIVE_HIGH>; + + vddio-supply =3D <&pm8058_l15>; + vdd-supply =3D <&pm8058_l15>; + vaa-supply =3D <&pm8058_l14>; + + port { + mt9m113_ep: endpoint { + bus-type =3D ; + link-frequencies =3D /bits/ 64 <384000000>; + data-lanes =3D <1>; + remote-endpoint =3D <&csiphy1_ep>; + }; + }; + }; + }; +... --=20 2.43.0 From nobody Mon Jun 8 06:38:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D26AC4534A3; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780642981; cv=none; b=Lijuua5RdmRq8XvU97jk7MPD/3/NATeVkO9PADx+xhT1CdS8zztGrzQqhQ6D815qKBbUO6A3bNmfgO9X/eunwcV2QQnhnC3aEjes+4lZOFLinzbY5c0XZ8bHf6dRlPM9ES8y9FzIdYMahDVpo5894TeznGUSa2dBRFUKlG9GzCI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780642981; c=relaxed/simple; bh=nXi6bprzKVNV0ygWrLs2Ag8usz1JdPz36YgO4WS6OzM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ZIlWwaPcU7O0uDsxK2S4i1v9TmvIfon8arUee8+dvfK2kmuRKeE9DCilPoHOawMuVxiepyRdvCG/clJcC6DOjmCuSn1UyKUToow0oPw66QQfRlld0FBBMk/xlQmIQct6iLyV6h6vMiwgBFiC6GW1arfGxg8+1GVmpI6eJcd2kAo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ecTkgai1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ecTkgai1" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8682EC2BCC9; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1780642981; bh=nXi6bprzKVNV0ygWrLs2Ag8usz1JdPz36YgO4WS6OzM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ecTkgai17/+cjhILiT+5sDIvsoDCapyYaWg+W3BmYh1ANZkPUERy4/zKnPlCE8ntt EMVB3SqR7nbC5NMmIrgME+y6FZslD/rPE27WP2FDc4QB+QATR40iz5MeDjQlX8zySi msH4sRylvT795ABWDErdLENpE+6LaDIaBoUnt0GGI1OZbDpwNYFmHydzGM4nZyaQcS LnM37x0ZpRZSZKeuPc3Yt9f1i+h6hHl5daC083yEZcM6dEs232e/aHAvyldX1aA69J rCPi9jifrPn0URFlCSlMtxBT+8Ct+FqLaQ7YM9tMIwgSJ5qWSqQJDl8QW2826w1/a8 sdymum8XJ7nCQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6EEC6CD6E7B; Fri, 5 Jun 2026 07:03:01 +0000 (UTC) From: Herman van Hazendonk via B4 Relay Date: Fri, 05 Jun 2026 09:03:01 +0200 Subject: [PATCH v3 2/2] media: i2c: add Aptina MT9M113 1.3 Mpx SoC sensor driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260605-submit-media-mt9m113-v3-2-dc3e07d5d70b@herrie.org> References: <20260605-submit-media-mt9m113-v3-0-dc3e07d5d70b@herrie.org> In-Reply-To: <20260605-submit-media-mt9m113-v3-0-dc3e07d5d70b@herrie.org> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Laurent Pinchart , Hans Verkuil , Konrad Dybcio X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780642979; l=95349; i=github.com@herrie.org; s=20240417; h=from:subject:message-id; bh=x/xChfjFt2gTsar2qCW9SKY/L7VW69D0nY+BzLMBJUc=; b=fH0CCp7tmD69o9Gyw74Ce+9QcMkQbsfROBayuovF5dIV/5VxP0q7972nwhvS8apjyU+T++zRD jTib6lxXKZhBumPlBy6CnCPUWIfXW6/cd62rvzp8eHxhHg1p5FQz6FG X-Developer-Key: i=github.com@herrie.org; a=ed25519; pk=YYxdq8fb5O9vhkW3n2dCH044FPZZO5718v/du7fRhFw= X-Endpoint-Received: by B4 Relay for github.com@herrie.org/20240417 with auth_id=809 X-Original-From: Herman van Hazendonk Reply-To: github.com@herrie.org From: Herman van Hazendonk Add a V4L2 subdev driver for the Aptina MT9M113 1.3 Megapixel SoC image sensor (1280x1024 active pixel array) with on-chip ISP. The sensor exposes: - dual context operation: Context A (640x480 preview, binned) and Context B (1280x1024 capture) selectable at runtime; - single-lane MIPI CSI-2 output, YUV422 in UYVY or YUYV byte order (the byte order is selected via the chroma/luma swap bit in MODE_OUTPUT_FORMAT_{A,B}); - I2C MCU-indirect register access through the 0x098C/0x0990 page indirection pair, in addition to the directly addressable register space; - V4L2 controls: H/V flip, color effects (none / mono / sepia / negative / solarization), power-line frequency, saturation, manual or auto exposure, analog gain, auto white balance, and a four-entry test pattern selector; - a 24 MHz EXTCLK and 1.8/2.8 V supplies. The MT9M113 MCU intermittently wedges on stream start (SEQ_CMD stuck, SEQ_STATE never reaching preview/capture; only a full power cycle recovers it). The driver works around this in two layers: runtime-PM resume always performs a full power-cycle plus init-table replay - matching the legacy vendor kernel's per-open behaviour - so a wedged MCU is recovered each session; and the s_stream(1) path retries a bounded number of times with a runtime-PM power cycle between attempts as fallback insurance for the residual failure rate. Found on the HP TouchPad (Tenderloin) as the front-facing camera and on a number of other 1280x1024 mobile/embedded boards. Signed-off-by: Herman van Hazendonk --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 12 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/mt9m113.c | 2971 +++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 2992 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..c1ca196180b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18165,6 +18165,14 @@ L: linux-mtd@lists.infradead.org S: Maintained F: drivers/mtd/devices/docg3* =20 +MT9M113 APTINA SENSOR DRIVER +M: Herman van Hazendonk +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/aptina,mt9m113.yaml +F: drivers/media/i2c/mt9m113.c + MT9M114 ONSEMI SENSOR DRIVER M: Laurent Pinchart L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 8f2ba4121586..77c2e432578f 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -302,6 +302,18 @@ config VIDEO_MT9M111 This driver supports MT9M111, MT9M112 and MT9M131 cameras from Micron/Aptina =20 +config VIDEO_MT9M113 + tristate "Aptina MT9M113 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Aptina MT9M113 + 1.3 Megapixel CMOS camera sensor. The MT9M113 has dual context + support: Context A (640x480 preview) and Context B (1280x1024 + capture). + + To compile this driver as a module, choose M here: the + module will be called mt9m113. + config VIDEO_MT9M114 tristate "onsemi MT9M114 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 90b276a7417a..f2ba184dd0e4 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_VIDEO_ML86V7667) +=3D ml86v7667.o obj-$(CONFIG_VIDEO_MSP3400) +=3D msp3400.o obj-$(CONFIG_VIDEO_MT9M001) +=3D mt9m001.o obj-$(CONFIG_VIDEO_MT9M111) +=3D mt9m111.o +obj-$(CONFIG_VIDEO_MT9M113) +=3D mt9m113.o obj-$(CONFIG_VIDEO_MT9M114) +=3D mt9m114.o obj-$(CONFIG_VIDEO_MT9P031) +=3D mt9p031.o obj-$(CONFIG_VIDEO_MT9T112) +=3D mt9t112.o diff --git a/drivers/media/i2c/mt9m113.c b/drivers/media/i2c/mt9m113.c new file mode 100644 index 000000000000..7a4a8a50822d --- /dev/null +++ b/drivers/media/i2c/mt9m113.c @@ -0,0 +1,2971 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mt9m113.c Aptina MT9M113 sensor driver + * + * Copyright (C) 2024 Herman van Hazendonk + * + * MT9M113 is a 1.3MP SOC sensor with dual context support: + * Context A: 640x480 preview mode (binned) + * Context B: 1280x1024 capture mode (full resolution) + * + * Ported from legacy vendor kernel mt9m113.c/mt9m113_reg.c. + * Uses MCU indirect access (0x098C/0x0990) for configuration. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Delay before enabling MIPI output, allowing CSIPHY to stabilize. */ +#define MT9M113_PRE_MIPI_DELAY_MS 10 + +/* + * The MT9M113 MCU intermittently wedges on stream start (SEQ_CMD stuck, + * 0xa103 timeout, SEQ_STATE never reaching preview 0x04 / capture 0x07). + * The failures cluster at the start of a run and then clear, so a bounded + * retry that power-cycles the sensor between attempts reliably gets the + * stream up. 3 attempts is enough in practice (with the per-session + * runtime-PM re-init as the primary fix); a larger cap would stack + * power-cycle + ~600-entry init-table replay into a STREAMON latency + * userspace clients treat as a hang. Total attempts (1 initial + retries= ). + */ +#define MT9M113_STREAM_START_RETRIES 3 + +/* MT9M113 chip ID */ +#define MT9M113_CHIP_ID CCI_REG16(0x0000) +#define MT9M113_CHIP_ID_VALUE 0x2480 + +/* Sysctl registers */ +#define MT9M113_PLL_DIVIDERS CCI_REG16(0x0010) +#define MT9M113_PLL_P_DIVIDERS CCI_REG16(0x0012) +#define MT9M113_PLL_CONTROL CCI_REG16(0x0014) +#define MT9M113_CLOCKS_CONTROL CCI_REG16(0x0016) +#define MT9M113_STANDBY_CONTROL CCI_REG16(0x0018) +#define MT9M113_STANDBY_CONTROL_ACTIVE 0x0028 /* MCU running, not in stan= dby */ +#define MT9M113_RESET_AND_MISC_CONTROL CCI_REG16(0x001a) +#define MT9M113_RESET_SOC BIT(0) +#define MT9M113_MCU_BOOT_MODE CCI_REG16(0x001c) + +/* MCU indirect access registers */ +#define MT9M113_MCU_ADDRESS CCI_REG16(0x098c) +#define MT9M113_MCU_DATA CCI_REG16(0x0990) + +/* MCU variable addresses */ +#define MT9M113_SEQ_CMD 0xa103 +#define MT9M113_SEQ_CMD_RUN 0x0001 +#define MT9M113_SEQ_CMD_CAPTURE 0x0002 +#define MT9M113_SEQ_CMD_REFRESH 0x0005 +#define MT9M113_SEQ_CMD_REFRESH_MODE 0x0006 +#define MT9M113_SEQ_STATE 0xa104 +#define MT9M113_SEQ_STATE_PREVIEW 0x04 /* stable Context A preview */ +#define MT9M113_SEQ_STATE_CAPTURE 0x07 /* Context B capture */ +#define MT9M113_SEQ_CAP_MODE 0xa115 +#define MT9M113_SEQ_CAP_MODE_PREVIEW 0x0030 /* continuous preview */ +#define MT9M113_SEQ_CAP_MODE_VIDEO 0x0002 /* stay in Context B */ + +/* Mode Output dimension/format MCU variables (Driver ID 7) */ +#define MT9M113_MODE_OUTPUT_WIDTH_A 0x2703 +#define MT9M113_MODE_OUTPUT_HEIGHT_A 0x2705 +#define MT9M113_MODE_OUTPUT_WIDTH_B 0x2707 +#define MT9M113_MODE_OUTPUT_HEIGHT_B 0x2709 +#define MT9M113_MODE_OUTPUT_FORMAT_A 0x2755 +#define MT9M113_MODE_OUTPUT_FORMAT_B 0x2757 +#define MT9M113_MODE_OUTPUT_FORMAT_YUV 0x0000 +/* + * YUV byte ordering (datasheet table 31 "mode_output_format_a/b"): + * default (0x0000) =3D Cb Y0 Cr Y1 =3D UYVY + * SWAP_LUMA_CHROMA =3D Y0 Cb Y1 Cr =3D YUYV + */ +#define MT9M113_MODE_OUTPUT_FORMAT_SWAP_LUMA_CHROMA BIT(1) + +/* Special effects MCU variables */ +#define MT9M113_MODE_SPEC_EFFECTS_A 0x2759 +#define MT9M113_MODE_SPEC_EFFECTS_B 0x275b +#define MT9M113_SPEC_EFFECTS_DEFAULT 0x6440 +#define MT9M113_SPEC_EFFECTS_MASK 0x0007 +#define MT9M113_SPEC_EFFECTS_NONE 0x0000 +#define MT9M113_SPEC_EFFECTS_MONOCHROME 0x0001 +#define MT9M113_SPEC_EFFECTS_SEPIA 0x0002 +#define MT9M113_SPEC_EFFECTS_NEGATIVE 0x0003 +#define MT9M113_SPEC_EFFECTS_SOLARIZE 0x0004 + +/* Sensor Read Mode MCU variables (for flip/mirror control) */ +#define MT9M113_SENSOR_READ_MODE_A 0x2717 +#define MT9M113_SENSOR_READ_MODE_B 0x272d +#define MT9M113_SENSOR_READ_MODE_HMIRROR BIT(0) +#define MT9M113_SENSOR_READ_MODE_VMIRROR BIT(1) + +/* + * Context A/B sensor configuration MCU variables + * These control the sensor readout window and must be re-applied when + * switching contexts to ensure correct line counts. + */ +#define MT9M113_MODE_SENSOR_ROW_START_A 0x270D +#define MT9M113_MODE_SENSOR_COL_START_A 0x270F +#define MT9M113_MODE_SENSOR_ROW_END_A 0x2711 +#define MT9M113_MODE_SENSOR_COL_END_A 0x2713 +#define MT9M113_MODE_SENSOR_ROW_SPEED_A 0x2715 +#define MT9M113_MODE_SENSOR_FRAME_LENGTH_A 0x271F +#define MT9M113_MODE_SENSOR_LINE_LENGTH_PCK_A 0x2721 + +#define MT9M113_MODE_SENSOR_ROW_START_B 0x2723 +#define MT9M113_MODE_SENSOR_COL_START_B 0x2725 +#define MT9M113_MODE_SENSOR_ROW_END_B 0x2727 +#define MT9M113_MODE_SENSOR_COL_END_B 0x2729 +#define MT9M113_MODE_SENSOR_ROW_SPEED_B 0x272B +#define MT9M113_MODE_SENSOR_FRAME_LENGTH_B 0x2735 +#define MT9M113_MODE_SENSOR_LINE_LENGTH_PCK_B 0x2737 + +/* Context A sensor config values (from legacy vendor driver, 640x480 binn= ed) */ +#define MT9M113_CONTEXT_A_ROW_START 0x0000 +#define MT9M113_CONTEXT_A_COL_START 0x0000 +#define MT9M113_CONTEXT_A_ROW_END 0x03CD /* 973 */ +#define MT9M113_CONTEXT_A_COL_END 0x050D /* 1293 */ +#define MT9M113_CONTEXT_A_ROW_SPEED 0x2111 +#define MT9M113_CONTEXT_A_READ_MODE 0x046C /* Binning enabled */ +#define MT9M113_CONTEXT_A_FRAME_LENGTH 0x032E /* 814 lines (incl vblank) = */ +#define MT9M113_CONTEXT_A_LINE_LENGTH_PCK 0x04CC /* 1228 pixclks/line */ + +/* Context B sensor config values (from legacy vendor driver, 1280x1024 fu= ll res) */ +#define MT9M113_CONTEXT_B_ROW_START 0x0004 +#define MT9M113_CONTEXT_B_COL_START 0x0004 +#define MT9M113_CONTEXT_B_ROW_END 0x040B /* 1035 */ +#define MT9M113_CONTEXT_B_COL_END 0x050B /* 1291 */ +#define MT9M113_CONTEXT_B_ROW_SPEED 0x2111 +#define MT9M113_CONTEXT_B_READ_MODE 0x0024 /* Full resolution */ +#define MT9M113_CONTEXT_B_FRAME_LENGTH 0x0559 /* 1369 lines (incl vblank)= */ +#define MT9M113_CONTEXT_B_LINE_LENGTH_PCK 0x0722 /* 1826 pixclks/line */ + +/* Auto Exposure MCU variables (for preview vs snapshot optimization) */ +#define MT9M113_AE_MAX_INDEX 0xa20c +#define MT9M113_AE_MAX_VIRTGAIN 0xa20e +#define MT9M113_AE_MAX_DGAIN_AE1 0xa21a +#define MT9M113_AE_JUMP_DIVISOR 0xa21c +#define MT9M113_AE_SKIP_FRAMES 0xa21e + +/* Flicker detection MCU variable */ +#define MT9M113_FD_MODE 0xa404 +#define MT9M113_FD_MODE_DISABLED 0x00 +#define MT9M113_FD_MODE_50HZ 0x01 +#define MT9M113_FD_MODE_60HZ 0x02 +#define MT9M113_FD_MODE_AUTO 0x03 + +/* AWB/Color MCU variables */ +#define MT9M113_AWB_SATURATION 0xa354 +#define MT9M113_AWB_MODE 0xa34a + +/* Test pattern MCU variables (mode_common_mode_settings) */ +#define MT9M113_CAM_MODE_SELECT 0xc84c +#define MT9M113_CAM_MODE_SELECT_NORMAL 0x00 +#define MT9M113_CAM_MODE_SELECT_TEST_PATTERN 0x02 +#define MT9M113_CAM_MODE_TEST_PATTERN_SELECT 0xc84d +#define MT9M113_TEST_PATTERN_SOLID_COLOR 0x01 +#define MT9M113_TEST_PATTERN_COLOR_BARS 0x04 +#define MT9M113_TEST_PATTERN_FADE_TO_GRAY 0x08 + +/* Double buffer control register */ +#define MT9M113_DOUBLE_BUFFER_CONTROL CCI_REG16(0x0248) +#define MT9M113_DOUBLE_BUFFER_SUSPEND BIT(15) + +/* Auto Exposure MCU variables */ +#define MT9M113_AE_GATE 0xa207 +#define MT9M113_AE_GATE_ENABLE 0x0000 +#define MT9M113_AE_GATE_DISABLE 0x00FF + +/* Sensor core exposure/gain registers */ +#define MT9M113_COARSE_IT_TIME_A CCI_REG16(0x3012) +#define MT9M113_COARSE_IT_TIME_B CCI_REG16(0x3014) +#define MT9M113_ANALOG_GAIN CCI_REG16(0x3028) + +/* Sensor core registers */ +#define MT9M113_RESET_REGISTER CCI_REG16(0x301a) +#define MT9M113_RESET_REG_STREAMING 0x120C +#define MT9M113_OFIFO_CONTROL_STATUS CCI_REG16(0x321c) +#define MT9M113_OFIFO_BYPASS 0x0003 /* FIFO bypass (YUV path) */ + +/* Color pipeline (ISP) control */ +#define MT9M113_COLOR_PIPELINE_CONTROL CCI_REG16(0x3210) +#define MT9M113_COLOR_PIPELINE_DEFAULT 0x01b8 /* scaler+gamma+ap+shading = */ + +/* OUTPUT_CONTROL register (0x3400) - MIPI control */ +#define MT9M113_OUTPUT_CONTROL CCI_REG16(0x3400) +#define MT9M113_OUTPUT_CONTROL_MIPI_ENABLE 0x7A08 /* YUV422 dt=3D0x1E */ + +/* CUSTOM_SHORT_PKT register */ +#define MT9M113_CUSTOM_SHORT_PKT CCI_REG16(0x3404) +#define MT9M113_CUSTOM_SHORT_PKT_FRAME_CNT_EN 0x0080 + +/* Pixel array dimensions */ +#define MT9M113_PIXEL_ARRAY_WIDTH 1296U +#define MT9M113_PIXEL_ARRAY_HEIGHT 1040U + +/* -----------------------------------------------------------------------= ------ + * Data Structures + */ + +struct mt9m113_format_info { + u32 code; +}; + +struct mt9m113 { + struct i2c_client *client; + struct regmap *regmap; + + struct clk *clk; + struct gpio_desc *reset; + struct gpio_desc *powerdown; + struct regulator_bulk_data supplies[3]; + struct v4l2_fwnode_endpoint bus_cfg; + + unsigned int pixrate; + s64 link_freq; + bool streaming; + bool test_pattern_active; + + /* Pixel Array sub-device */ + struct { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + } pa; + + /* Image Flow Processor sub-device */ + struct { + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_ctrl_handler hdl; + } ifp; +}; + +/* -----------------------------------------------------------------------= ------ + * Formats + */ + +/* + * Source-pad output formats. Only YUV422 is exposed: MT9M113_OUTPUT_CONT= ROL + * advertises the YUV422 CSI-2 data type (0x1E) unconditionally on the wir= e, + * and adding RGB565 would require also driving the matching CSI-2 dt (0x2= 2) + * out of OUTPUT_CONTROL =E2=80=94 verifying the exact bit layout for that= needs the + * sensor datasheet and a hardware re-test, so RGB is deferred to a follow= -up. + */ +static const struct mt9m113_format_info mt9m113_format_infos[] =3D { + { .code =3D MEDIA_BUS_FMT_UYVY8_1X16 }, + { .code =3D MEDIA_BUS_FMT_YUYV8_1X16 }, +}; + +static const struct mt9m113_format_info * +mt9m113_format_info(u32 code) +{ + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(mt9m113_format_infos); i++) { + if (mt9m113_format_infos[i].code =3D=3D code) + return &mt9m113_format_infos[i]; + } + + return &mt9m113_format_infos[0]; +} + +/* + * Map a V4L2 mbus code to the value programmed into MODE_OUTPUT_FORMAT_{A= ,B}. + * Driving the chroma/luma swap bit is what differentiates UYVY from YUYV; + * without it both codes would emit the same bytes on the MIPI bus. + */ +static u16 mt9m113_format_val(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + return MT9M113_MODE_OUTPUT_FORMAT_YUV | + MT9M113_MODE_OUTPUT_FORMAT_SWAP_LUMA_CHROMA; + case MEDIA_BUS_FMT_UYVY8_1X16: + default: + return MT9M113_MODE_OUTPUT_FORMAT_YUV; + } +} + +/* -----------------------------------------------------------------------= ------ + * MCU Variable Access Helpers + */ + +static int mt9m113_write_mcu_var(struct mt9m113 *sensor, u16 addr, u16 val= ue) +{ + int ret =3D 0; + + cci_write(sensor->regmap, MT9M113_MCU_ADDRESS, addr, &ret); + cci_write(sensor->regmap, MT9M113_MCU_DATA, value, &ret); + return ret; +} + +static int mt9m113_read_mcu_var(struct mt9m113 *sensor, u16 addr, u64 *val= ue) +{ + int ret; + + ret =3D cci_write(sensor->regmap, MT9M113_MCU_ADDRESS, addr, NULL); + if (ret) + return ret; + return cci_read(sensor->regmap, MT9M113_MCU_DATA, value, NULL); +} + +static int mt9m113_poll_mcu_var(struct mt9m113 *sensor, u16 addr, + u16 expected, unsigned int timeout_ms) +{ + unsigned int i; + u64 value =3D 0; + int ret; + + for (i =3D 0; i < timeout_ms / 10; i++) { + ret =3D mt9m113_read_mcu_var(sensor, addr, &value); + if (ret < 0) + return ret; + if (value =3D=3D expected) + return 0; + usleep_range(10000, 11000); + } + + dev_err(&sensor->client->dev, + "MCU var 0x%04x timeout (got 0x%llx, expected 0x%04x)\n", + addr, value, expected); + return -ETIMEDOUT; +} + +/* + * Ensure MCU sequencer is idle before issuing a new command. + * This prevents cascading failures where a timed-out command + * causes subsequent commands to also fail. + */ +static int mt9m113_seq_cmd_ready(struct mt9m113 *sensor) +{ + u64 seq_cmd; + int ret; + + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SEQ_CMD, &seq_cmd); + if (ret < 0) + return ret; + + if (seq_cmd =3D=3D 0) + return 0; /* Already idle */ + + /* + * A previous command is still pending. This can happen if: + * 1. Previous streaming start failed mid-way + * 2. The MCU is slow to process a command + * + * Wait up to 2 seconds for it to complete. This is longer than + * normal command timeouts to handle recovery scenarios. + */ + dev_dbg(&sensor->client->dev, + "MT9M113: SEQ_CMD=3D0x%llx, waiting for idle\n", seq_cmd); + + return mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 2000); +} + +/* + * mt9m113_refresh - Issue REFRESH_MODE + REFRESH sequence + * + * Per datasheet: "It is recommended that refresh and refresh mode commands + * always be run together, and that refresh mode should be issued BEFORE + * the refresh command." + */ +static int mt9m113_refresh(struct mt9m113 *sensor) +{ + int ret; + + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, + MT9M113_SEQ_CMD_REFRESH_MODE); + if (ret) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 500); + if (ret < 0) + dev_warn(&sensor->client->dev, "MT9M113: REFRESH_MODE timeout\n"); + + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, + MT9M113_SEQ_CMD_REFRESH); + if (ret) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 500); + if (ret < 0) + dev_warn(&sensor->client->dev, "MT9M113: REFRESH timeout\n"); + + return 0; +} + +/* + * Re-program SENSOR_READ_MODE_{A,B} for the context geometry while keeping + * the live HFLIP/VFLIP bits, which the V4L2 control handler has already + * applied via s_ctrl(). Writing CONTEXT_X_READ_MODE blindly would clear + * mirror bits 0/1 and silently undo the user's flip on first stream-start. + */ +static int mt9m113_apply_read_mode(struct mt9m113 *sensor, u16 reg, u16 ba= se) +{ + const u16 mirror_mask =3D MT9M113_SENSOR_READ_MODE_HMIRROR | + MT9M113_SENSOR_READ_MODE_VMIRROR; + u64 cur =3D 0; + int ret; + + ret =3D mt9m113_read_mcu_var(sensor, reg, &cur); + if (ret) + return ret; + + return mt9m113_write_mcu_var(sensor, reg, + (base & ~mirror_mask) | + ((u16)cur & mirror_mask)); +} + +/** + * mt9m113_configure_sensor_context - Configure sensor readout parameters + * @sensor: MT9M113 sensor device + * @use_context_b: true for Context B (1280x1024), false for Context A (64= 0x480) + * + * Re-applies the sensor configuration registers for the selected context. + * This is necessary because the MT9M113's MCU variables can become corrup= ted + * or stale after switching between contexts. Without explicitly re-writing + * these values, Context A may use incorrect row_end and read_mode values + * after running Context B, resulting in wrong line counts and corrupted i= mages. + * + * Must be called before mt9m113_refresh() to ensure the new values take e= ffect. + */ +static int mt9m113_configure_sensor_context(struct mt9m113 *sensor, + bool use_context_b) +{ + struct device *dev =3D &sensor->client->dev; + int ret; + + if (use_context_b) { + /* Context B: 1280x1024 full resolution */ + dev_dbg(dev, "MT9M113: Configuring Context B sensor params\n"); + + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_START_B, + MT9M113_CONTEXT_B_ROW_START); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_COL_START_B, + MT9M113_CONTEXT_B_COL_START); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_END_B, + MT9M113_CONTEXT_B_ROW_END); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_COL_END_B, + MT9M113_CONTEXT_B_COL_END); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_SPEED_B, + MT9M113_CONTEXT_B_ROW_SPEED); + if (ret) + return ret; + ret =3D mt9m113_apply_read_mode(sensor, + MT9M113_SENSOR_READ_MODE_B, + MT9M113_CONTEXT_B_READ_MODE); + if (ret) + return ret; + /* + * Frame/line length define the total frame period (active + + * blanking). They must be re-applied with the rest of the + * context geometry: the VFE CAMIF counts lines to find frame + * boundaries (no MIPI FE short packet), so a stale period makes + * the captured image walk vertically frame-over-frame. + */ + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_FRAME_LENGTH_B, + MT9M113_CONTEXT_B_FRAME_LENGTH); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_LINE_LENGTH_PCK_B, + MT9M113_CONTEXT_B_LINE_LENGTH_PCK); + if (ret) + return ret; + } else { + /* Context A: 640x480 binned preview */ + dev_dbg(dev, "MT9M113: Configuring Context A sensor params\n"); + + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_START_A, + MT9M113_CONTEXT_A_ROW_START); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_COL_START_A, + MT9M113_CONTEXT_A_COL_START); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_END_A, + MT9M113_CONTEXT_A_ROW_END); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_COL_END_A, + MT9M113_CONTEXT_A_COL_END); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_ROW_SPEED_A, + MT9M113_CONTEXT_A_ROW_SPEED); + if (ret) + return ret; + ret =3D mt9m113_apply_read_mode(sensor, + MT9M113_SENSOR_READ_MODE_A, + MT9M113_CONTEXT_A_READ_MODE); + if (ret) + return ret; + /* See Context B above: frame period must track the geometry. */ + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_FRAME_LENGTH_A, + MT9M113_CONTEXT_A_FRAME_LENGTH); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SENSOR_LINE_LENGTH_PCK_A, + MT9M113_CONTEXT_A_LINE_LENGTH_PCK); + if (ret) + return ret; + } + + return 0; +} + +/* -----------------------------------------------------------------------= ------ + * Double Buffer Control + * + * The MT9M113 uses double buffering for context parameters. Setting bit 15 + * of register 0x0248 suspends updates from shadow to active registers, + * allowing atomic multi-register configuration changes. + */ + +static int mt9m113_double_buffer_suspend(struct mt9m113 *sensor) +{ + u64 value; + int ret; + + ret =3D cci_read(sensor->regmap, MT9M113_DOUBLE_BUFFER_CONTROL, &value, N= ULL); + if (ret) + return ret; + + value |=3D MT9M113_DOUBLE_BUFFER_SUSPEND; + return cci_write(sensor->regmap, MT9M113_DOUBLE_BUFFER_CONTROL, value, NU= LL); +} + +static int mt9m113_double_buffer_resume(struct mt9m113 *sensor) +{ + u64 value; + int ret; + + ret =3D cci_read(sensor->regmap, MT9M113_DOUBLE_BUFFER_CONTROL, &value, N= ULL); + if (ret) + return ret; + + value &=3D ~MT9M113_DOUBLE_BUFFER_SUSPEND; + return cci_write(sensor->regmap, MT9M113_DOUBLE_BUFFER_CONTROL, value, NU= LL); +} + +/* -----------------------------------------------------------------------= ------ + * Initialization Table (from legacy vendor kernel mt9m113_reg.c) + */ + +struct mt9m113_reg_entry { + u16 reg; + u16 value; + u16 delay_ms; +}; + +static const struct mt9m113_reg_entry mt9m113_init_table[] =3D { + /* OFIFO control */ + { 0x321C, 0x0003, 0 }, + + /* Context A output (640x480 preview) */ + { 0x098C, 0x2703, 0 }, + { 0x0990, 0x0280, 0 }, + { 0x098C, 0x2705, 0 }, + { 0x0990, 0x01E0, 0 }, + + /* Context B output (1280x1024 capture) */ + { 0x098C, 0x2707, 0 }, + { 0x0990, 0x0500, 0 }, + { 0x098C, 0x2709, 0 }, + { 0x0990, 0x0400, 0 }, + + /* Context A sensor configuration */ + { 0x098C, 0x270D, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x270F, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x2711, 0 }, /* MODE_SENSOR_ROW_END_A */ + { 0x0990, 0x03CD, 0 }, /* 973 (from legacy vendor driver) */ + { 0x098C, 0x2713, 0 }, + { 0x0990, 0x050D, 0 }, + { 0x098C, 0x2715, 0 }, + { 0x0990, 0x2111, 0 }, + { 0x098C, 0x2717, 0 }, + { 0x0990, 0x046C, 0 }, + { 0x098C, 0x2719, 0 }, + { 0x0990, 0x00AC, 0 }, + { 0x098C, 0x271B, 0 }, + { 0x0990, 0x01F1, 0 }, + { 0x098C, 0x271D, 0 }, + { 0x0990, 0x013F, 0 }, + { 0x098C, 0x271F, 0 }, + { 0x0990, 0x032E, 0 }, + { 0x098C, 0x2721, 0 }, + { 0x0990, 0x04CC, 0 }, + + /* Context B sensor configuration */ + { 0x098C, 0x2723, 0 }, + { 0x0990, 0x0004, 0 }, + { 0x098C, 0x2725, 0 }, + { 0x0990, 0x0004, 0 }, + { 0x098C, 0x2727, 0 }, + { 0x0990, 0x040B, 0 }, + { 0x098C, 0x2729, 0 }, + { 0x0990, 0x050B, 0 }, + { 0x098C, 0x272B, 0 }, + { 0x0990, 0x2111, 0 }, + { 0x098C, 0x272D, 0 }, + { 0x0990, 0x0024, 0 }, + { 0x098C, 0x272F, 0 }, + { 0x0990, 0x004C, 0 }, + { 0x098C, 0x2731, 0 }, + { 0x0990, 0x00F9, 0 }, + { 0x098C, 0x2733, 0 }, + { 0x0990, 0x00A7, 0 }, + { 0x098C, 0x2735, 0 }, + { 0x0990, 0x0559, 0 }, + { 0x098C, 0x2737, 0 }, + { 0x0990, 0x0722, 0 }, + + /* Crop configuration - Context A */ + { 0x098C, 0x2739, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x273B, 0 }, + { 0x0990, 0x027F, 0 }, + { 0x098C, 0x273D, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x273F, 0 }, + { 0x0990, 0x01DF, 0 }, + + /* Crop configuration - Context B */ + { 0x098C, 0x2747, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x2749, 0 }, + { 0x0990, 0x04FF, 0 }, + { 0x098C, 0x274B, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0x274D, 0 }, + { 0x0990, 0x03FF, 0 }, + + /* Flicker detection */ + { 0x098C, 0x222D, 0 }, + { 0x0990, 0x00CC, 0 }, + { 0x098C, 0xA404, 0 }, + { 0x0990, 0x0010, 0 }, + { 0x098C, 0xA408, 0 }, + { 0x0990, 0x0032, 0 }, + { 0x098C, 0xA409, 0 }, + { 0x0990, 0x0034, 0 }, + { 0x098C, 0xA40A, 0 }, + { 0x0990, 0x003C, 0 }, + { 0x098C, 0xA40B, 0 }, + { 0x0990, 0x003E, 0 }, + { 0x098C, 0x2411, 0 }, + { 0x0990, 0x00CC, 0 }, + { 0x098C, 0x2413, 0 }, + { 0x0990, 0x00F4, 0 }, + { 0x098C, 0x2415, 0 }, + { 0x0990, 0x0089, 0 }, + { 0x098C, 0x2417, 0 }, + { 0x0990, 0x00A4, 0 }, + { 0x098C, 0xA40D, 0 }, + { 0x0990, 0x0002, 0 }, + { 0x098C, 0xA40E, 0 }, + { 0x0990, 0x0003, 0 }, + { 0x098C, 0xA410, 0 }, + { 0x0990, 0x000A, 0 }, + + /* Sensor core reserved registers */ + { 0x3044, 0x0504, 0 }, + { 0x3086, 0x24F7, 0 }, + { 0x3088, 0xF059, 0 }, + { 0x3090, 0x0716, 0 }, + { 0x3092, 0xAB1F, 0 }, + { 0x30D4, 0x9020, 0 }, + { 0x30E2, 0x6645, 0 }, + { 0x30E4, 0x7A66, 0 }, + { 0x30E6, 0x6652, 0 }, + { 0x30E8, 0x7766, 0 }, + { 0x30EA, 0x2E03, 0 }, + { 0x30EC, 0x452E, 0 }, + { 0x30EE, 0x2E17, 0 }, + { 0x30F0, 0x452E, 0 }, + { 0x30F6, 0x0501, 0 }, + { 0x30F8, 0x0501, 0 }, + { 0x30FA, 0x0401, 0 }, + { 0x30FC, 0x0401, 0 }, + { 0x30FE, 0x5145, 0 }, + { 0x3100, 0x4F45, 0 }, + { 0x3102, 0x652E, 0 }, + { 0x3104, 0x7552, 0 }, + { 0x3106, 0x2D05, 0 }, + { 0x3108, 0x4405, 0 }, + { 0x311A, 0x5045, 0 }, + { 0x311E, 0x0601, 0 }, + { 0x3122, 0x0601, 0 }, + { 0x316C, 0x8406, 0 }, + + /* Noise reduction */ + { 0x098C, 0xAB2D, 0 }, + { 0x0990, 0x002A, 0 }, + { 0x098C, 0xAB31, 0 }, + { 0x0990, 0x002E, 0 }, + + /* Low-light enhancement */ + { 0x098C, 0x2B28, 0 }, + { 0x0990, 0x1F40, 0 }, + { 0x098C, 0x2B2A, 0 }, + { 0x0990, 0x3A98, 0 }, + { 0x098C, 0x2B38, 0 }, + { 0x0990, 0x1F40, 0 }, + { 0x098C, 0x2B3A, 0 }, + { 0x0990, 0x3A98, 0 }, + + /* AE settings */ + { 0x098C, 0x2257, 0 }, + { 0x0990, 0x2710, 0 }, + { 0x098C, 0x2250, 0 }, + { 0x0990, 0x1B58, 0 }, + { 0x098C, 0x2252, 0 }, + { 0x0990, 0x32C8, 0 }, + { 0x098C, 0xA24B, 0 }, + { 0x0990, 0x0082, 0 }, + + /* Aperture */ + { 0x326C, 0x0C00, 0 }, + + /* More Context A settings */ + { 0x098C, 0x2717, 0 }, + { 0x0990, 0x046C, 0 }, + { 0x098C, 0x2719, 0 }, + { 0x0990, 0x00AC, 0 }, + { 0x098C, 0x271B, 0 }, + { 0x0990, 0x01F1, 0 }, + { 0x098C, 0x271D, 0 }, + { 0x0990, 0x013F, 0 }, + { 0x098C, 0x271F, 0 }, + { 0x0990, 0x032E, 0 }, + { 0x098C, 0x2721, 0 }, + { 0x0990, 0x04CC, 0 }, + { 0x098C, 0x275F, 0 }, + { 0x0990, 0x0596, 0 }, + { 0x098C, 0x2761, 0 }, + { 0x0990, 0x0094, 0 }, + + /* Lens shading correction */ + { 0x364E, 0x07B0, 0 }, + { 0x3650, 0x7E0E, 0 }, + { 0x3652, 0x3D31, 0 }, + { 0x3654, 0x80AE, 0 }, + { 0x3656, 0xE131, 0 }, + { 0x3658, 0x01B0, 0 }, + { 0x365A, 0x878D, 0 }, + { 0x365C, 0x2671, 0 }, + { 0x365E, 0x7D2D, 0 }, + { 0x3660, 0xA5D1, 0 }, + { 0x3662, 0x03B0, 0 }, + { 0x3664, 0x5A0E, 0 }, + { 0x3666, 0x0E71, 0 }, + { 0x3668, 0x99EE, 0 }, + { 0x366A, 0xA671, 0 }, + { 0x366C, 0x0170, 0 }, + { 0x366E, 0xF44D, 0 }, + { 0x3670, 0x2971, 0 }, + { 0x3672, 0x2D4A, 0 }, + { 0x3674, 0xD671, 0 }, + { 0x3676, 0x674C, 0 }, + { 0x3678, 0x748D, 0 }, + { 0x367A, 0x3FEE, 0 }, + { 0x367C, 0x89AE, 0 }, + { 0x367E, 0xB410, 0 }, + { 0x3680, 0x168C, 0 }, + { 0x3682, 0xC56D, 0 }, + { 0x3684, 0x7CAC, 0 }, + { 0x3686, 0x038F, 0 }, + { 0x3688, 0xA86F, 0 }, + { 0x368A, 0xDB6B, 0 }, + { 0x368C, 0xA2AE, 0 }, + { 0x368E, 0xFA8D, 0 }, + { 0x3690, 0x5C8E, 0 }, + { 0x3692, 0x740C, 0 }, + { 0x3694, 0x9F4B, 0 }, + { 0x3696, 0x1C4D, 0 }, + { 0x3698, 0x978D, 0 }, + { 0x369A, 0x21EC, 0 }, + { 0x369C, 0xF5AD, 0 }, + { 0x369E, 0x7D10, 0 }, + { 0x36A0, 0x3E2E, 0 }, + { 0x36A2, 0x8953, 0 }, + { 0x36A4, 0xD910, 0 }, + { 0x36A6, 0x3033, 0 }, + { 0x36A8, 0x06D1, 0 }, + { 0x36AA, 0xAD4E, 0 }, + { 0x36AC, 0xD2D2, 0 }, + { 0x36AE, 0x5CCE, 0 }, + { 0x36B0, 0x3B93, 0 }, + { 0x36B2, 0x50D0, 0 }, + { 0x36B4, 0x79AD, 0 }, + { 0x36B6, 0xDFF2, 0 }, + { 0x36B8, 0x88AF, 0 }, + { 0x36BA, 0x2453, 0 }, + { 0x36BC, 0x0051, 0 }, + { 0x36BE, 0x81CF, 0 }, + { 0x36C0, 0x8313, 0 }, + { 0x36C2, 0x2250, 0 }, + { 0x36C4, 0x4A53, 0 }, + { 0x36C6, 0x0C8D, 0 }, + { 0x36C8, 0x362B, 0 }, + { 0x36CA, 0xAD51, 0 }, + { 0x36CC, 0xA470, 0 }, + { 0x36CE, 0x3DD2, 0 }, + { 0x36D0, 0x174C, 0 }, + { 0x36D2, 0x152F, 0 }, + { 0x36D4, 0x82F1, 0 }, + { 0x36D6, 0xDED0, 0 }, + { 0x36D8, 0x6F12, 0 }, + { 0x36DA, 0xD36C, 0 }, + { 0x36DC, 0x51AE, 0 }, + { 0x36DE, 0xD0AE, 0 }, + { 0x36E0, 0x274E, 0 }, + { 0x36E2, 0x25F2, 0 }, + { 0x36E4, 0xDCCA, 0 }, + { 0x36E6, 0x438E, 0 }, + { 0x36E8, 0xD64E, 0 }, + { 0x36EA, 0x8A71, 0 }, + { 0x36EC, 0x1492, 0 }, + { 0x36EE, 0xD5B1, 0 }, + { 0x36F0, 0xEBF0, 0 }, + { 0x36F2, 0x53F3, 0 }, + { 0x36F4, 0x3492, 0 }, + { 0x36F6, 0x9AF4, 0 }, + { 0x36F8, 0x8BF1, 0 }, + { 0x36FA, 0x204F, 0 }, + { 0x36FC, 0x3A93, 0 }, + { 0x36FE, 0xB551, 0 }, + { 0x3700, 0xE214, 0 }, + { 0x3702, 0xF2B0, 0 }, + { 0x3704, 0x8C30, 0 }, + { 0x3706, 0x3053, 0 }, + { 0x3708, 0x64F0, 0 }, + { 0x370A, 0xFC73, 0 }, + { 0x370C, 0xD311, 0 }, + { 0x370E, 0x336F, 0 }, + { 0x3710, 0x5AF3, 0 }, + { 0x3712, 0x4EAF, 0 }, + { 0x3714, 0xDBD4, 0 }, + + /* Lens shading origin */ + { 0x3644, 0x02A0, 0 }, + { 0x3642, 0x01FC, 0 }, + { 0x3210, 0x01B8, 0 }, + + /* Color correction matrix - Low light */ + { 0x098C, 0x2306, 0 }, + { 0x0990, 0x0233, 0 }, + { 0x098C, 0x2308, 0 }, + { 0x0990, 0xFF0B, 0 }, + { 0x098C, 0x230A, 0 }, + { 0x0990, 0x0024, 0 }, + { 0x098C, 0x230C, 0 }, + { 0x0990, 0xFFC8, 0 }, + { 0x098C, 0x230E, 0 }, + { 0x0990, 0x01DE, 0 }, + { 0x098C, 0x2310, 0 }, + { 0x0990, 0xFFBD, 0 }, + { 0x098C, 0x2312, 0 }, + { 0x0990, 0x0019, 0 }, + { 0x098C, 0x2314, 0 }, + { 0x0990, 0xFF2B, 0 }, + { 0x098C, 0x2316, 0 }, + { 0x0990, 0x01E8, 0 }, + { 0x098C, 0x2318, 0 }, + { 0x0990, 0x0024, 0 }, + { 0x098C, 0x231A, 0 }, + { 0x0990, 0x0030, 0 }, + + /* Color correction matrix - RL (delta) */ + { 0x098C, 0x231C, 0 }, + { 0x0990, 0xFF7D, 0 }, + { 0x098C, 0x231E, 0 }, + { 0x0990, 0x002C, 0 }, + { 0x098C, 0x2320, 0 }, + { 0x0990, 0x002C, 0 }, + { 0x098C, 0x2322, 0 }, + { 0x0990, 0x0006, 0 }, + { 0x098C, 0x2324, 0 }, + { 0x0990, 0x00A3, 0 }, + { 0x098C, 0x2326, 0 }, + { 0x0990, 0xFF75, 0 }, + { 0x098C, 0x2328, 0 }, + { 0x0990, 0xFFF4, 0 }, + { 0x098C, 0x232A, 0 }, + { 0x0990, 0x00AC, 0 }, + { 0x098C, 0x232C, 0 }, + { 0x0990, 0xFF75, 0 }, + { 0x098C, 0x232E, 0 }, + { 0x0990, 0x0010, 0 }, + { 0x098C, 0x2330, 0 }, + { 0x0990, 0xFFF4, 0 }, + + /* AWB settings */ + { 0x098C, 0xA348, 0 }, + { 0x0990, 0x0008, 0 }, + { 0x098C, 0xA349, 0 }, + { 0x0990, 0x0002, 0 }, + { 0x098C, 0xA34A, 0 }, + { 0x0990, 0x0059, 0 }, + { 0x098C, 0xA34B, 0 }, + { 0x0990, 0x00A6, 0 }, + { 0x098C, 0xA351, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0xA352, 0 }, + { 0x0990, 0x007F, 0 }, + { 0x098C, 0xA35D, 0 }, + { 0x0990, 0x0078, 0 }, + { 0x098C, 0xA35E, 0 }, + { 0x0990, 0x0086, 0 }, + { 0x098C, 0xA35F, 0 }, + { 0x0990, 0x007E, 0 }, + { 0x098C, 0xA360, 0 }, + { 0x0990, 0x0082, 0 }, + + /* Cold color adjustment */ + { 0x098C, 0xA369, 0 }, + { 0x0990, 0x0097, 0 }, + { 0x098C, 0xA36A, 0 }, + { 0x0990, 0x008C, 0 }, + { 0x098C, 0xA36B, 0 }, + { 0x0990, 0x0080, 0 }, + + /* AWB window */ + { 0x098C, 0xA302, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0xA303, 0 }, + { 0x0990, 0x00FF, 0 }, + + /* AE preview settings */ + { 0x098C, 0xA11D, 0 }, + { 0x0990, 0x0002, 0 }, + { 0x098C, 0x271F, 0 }, + { 0x0990, 0x032E, 0 }, + { 0x098C, 0x2721, 0 }, + { 0x0990, 0x04CC, 0 }, + + /* AE gain settings */ + { 0x098C, 0xA216, 0 }, + { 0x0990, 0x0060, 0 }, + { 0x098C, 0xA215, 0 }, + { 0x0990, 0x000A, 0 }, + { 0x098C, 0xA20C, 0 }, + { 0x0990, 0x0028, 0 }, + { 0x098C, 0xA24F, 0 }, + { 0x0990, 0x0042, 0 }, + { 0x098C, 0xA20E, 0 }, + { 0x0990, 0x0060, 0 }, + + /* AE window */ + { 0x098C, 0xA202, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0xA203, 0 }, + { 0x0990, 0x00FF, 0 }, + { 0x098C, 0xA207, 0 }, + { 0x0990, 0x0004, 0 }, + + /* Gamma morph control */ + { 0x098C, 0xAB37, 0 }, + { 0x0990, 0x0003, 0 }, + { 0x098C, 0x2B38, 0 }, + { 0x0990, 0x3A98, 0 }, + { 0x098C, 0x2B3A, 0 }, + { 0x0990, 0x5000, 0 }, + + /* Saturation */ + { 0x098C, 0xAB20, 0 }, + { 0x0990, 0x0023, 0 }, + { 0x098C, 0xAB24, 0 }, + { 0x0990, 0x0010, 0 }, + + /* AE speed */ + { 0x098C, 0xA109, 0 }, + { 0x0990, 0x0020, 0 }, + { 0x098C, 0xA10A, 0 }, + { 0x0990, 0x0002, 0 }, + + /* Gamma table A */ + { 0x098C, 0xAB3C, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0xAB3D, 0 }, + { 0x0990, 0x0006, 0 }, + { 0x098C, 0xAB3E, 0 }, + { 0x0990, 0x0014, 0 }, + { 0x098C, 0xAB3F, 0 }, + { 0x0990, 0x0038, 0 }, + { 0x098C, 0xAB40, 0 }, + { 0x0990, 0x005F, 0 }, + { 0x098C, 0xAB41, 0 }, + { 0x0990, 0x0079, 0 }, + { 0x098C, 0xAB42, 0 }, + { 0x0990, 0x008D, 0 }, + { 0x098C, 0xAB43, 0 }, + { 0x0990, 0x009E, 0 }, + { 0x098C, 0xAB44, 0 }, + { 0x0990, 0x00AC, 0 }, + { 0x098C, 0xAB45, 0 }, + { 0x0990, 0x00B8, 0 }, + { 0x098C, 0xAB46, 0 }, + { 0x0990, 0x00C3, 0 }, + { 0x098C, 0xAB47, 0 }, + { 0x0990, 0x00CD, 0 }, + { 0x098C, 0xAB48, 0 }, + { 0x0990, 0x00D5, 0 }, + { 0x098C, 0xAB49, 0 }, + { 0x0990, 0x00DE, 0 }, + { 0x098C, 0xAB4A, 0 }, + { 0x0990, 0x00E5, 0 }, + { 0x098C, 0xAB4B, 0 }, + { 0x0990, 0x00EC, 0 }, + { 0x098C, 0xAB4C, 0 }, + { 0x0990, 0x00F3, 0 }, + { 0x098C, 0xAB4D, 0 }, + { 0x0990, 0x00F9, 0 }, + { 0x098C, 0xAB4E, 0 }, + { 0x0990, 0x00FF, 0 }, + + /* Noise reduction RGB */ + { 0x098C, 0xAB2C, 0 }, + { 0x0990, 0x0010, 0 }, + { 0x098C, 0xAB2D, 0 }, + { 0x0990, 0x002A, 0 }, + { 0x098C, 0xAB2E, 0 }, + { 0x0990, 0x0010, 0 }, + { 0x098C, 0xAB2F, 0 }, + { 0x0990, 0x0010, 0 }, + + /* Gamma table B */ + { 0x098C, 0xAB4F, 0 }, + { 0x0990, 0x0000, 0 }, + { 0x098C, 0xAB50, 0 }, + { 0x0990, 0x0004, 0 }, + { 0x098C, 0xAB51, 0 }, + { 0x0990, 0x000D, 0 }, + { 0x098C, 0xAB52, 0 }, + { 0x0990, 0x0028, 0 }, + { 0x098C, 0xAB53, 0 }, + { 0x0990, 0x0053, 0 }, + { 0x098C, 0xAB54, 0 }, + { 0x0990, 0x0075, 0 }, + { 0x098C, 0xAB55, 0 }, + { 0x0990, 0x0092, 0 }, + { 0x098C, 0xAB56, 0 }, + { 0x0990, 0x00A7, 0 }, + { 0x098C, 0xAB57, 0 }, + { 0x0990, 0x00B7, 0 }, + { 0x098C, 0xAB58, 0 }, + { 0x0990, 0x00C4, 0 }, + { 0x098C, 0xAB59, 0 }, + { 0x0990, 0x00CF, 0 }, + { 0x098C, 0xAB5A, 0 }, + { 0x0990, 0x00D8, 0 }, + { 0x098C, 0xAB5B, 0 }, + { 0x0990, 0x00DF, 0 }, + { 0x098C, 0xAB5C, 0 }, + { 0x0990, 0x00E6, 0 }, + { 0x098C, 0xAB5D, 0 }, + { 0x0990, 0x00EC, 0 }, + { 0x098C, 0xAB5E, 0 }, + { 0x0990, 0x00F2, 0 }, + { 0x098C, 0xAB5F, 0 }, + { 0x0990, 0x00F6, 0 }, + { 0x098C, 0xAB60, 0 }, + { 0x0990, 0x00FB, 0 }, + { 0x098C, 0xAB61, 0 }, + { 0x0990, 0x00FF, 0 }, + + /* Read mode - no mirror/flip */ + { 0x098C, 0x2717, 0 }, + { 0x0990, 0x046C, 0 }, + { 0x098C, 0x272D, 0 }, + { 0x0990, 0x0024, 0 }, + + /* Reset command before sequencer */ + { 0x001A, 0x021C, 0 }, + + /* Issue refresh command */ + { 0x098C, 0xA103, 0 }, + { 0x0990, 0x0006, 0 }, +}; + +/* Apply the MT9M113 initialization table */ +static int mt9m113_sensor_init(struct mt9m113 *sensor) +{ + struct device *dev =3D &sensor->client->dev; + int ret =3D 0; + unsigned int i; + + /* MCU is about to be fully re-initialised, so any prior test-pattern + * override is gone. Clear the bookkeeping so a subsequent + * V4L2_CID_TEST_PATTERN=3D0 does not re-run the disable sequence. + */ + sensor->test_pattern_active =3D false; + + dev_dbg(dev, "MT9M113: applying init table (%zu entries)\n", + ARRAY_SIZE(mt9m113_init_table)); + + /* + * Suspend double buffer updates so all init table writes take effect + * atomically at frame start. Per datasheet, R0x0248[15] inhibits + * transfers from pending to live registers. + */ + ret =3D mt9m113_double_buffer_suspend(sensor); + if (ret) { + dev_warn(dev, "Failed to suspend double buffer: %d\n", ret); + /* Continue anyway - not fatal */ + } + + for (i =3D 0; i < ARRAY_SIZE(mt9m113_init_table); i++) { + const struct mt9m113_reg_entry *entry =3D &mt9m113_init_table[i]; + + ret =3D cci_write(sensor->regmap, CCI_REG16(entry->reg), + entry->value, NULL); + if (ret < 0) { + dev_err(dev, "MT9M113: reg 0x%04x write failed: %d\n", + entry->reg, ret); + mt9m113_double_buffer_resume(sensor); + return ret; + } + + if (entry->delay_ms > 0) + msleep(entry->delay_ms); + } + + /* Resume double buffer updates - changes take effect at next frame start= */ + ret =3D mt9m113_double_buffer_resume(sensor); + if (ret) { + dev_warn(dev, "Failed to resume double buffer: %d\n", ret); + /* Continue anyway - not fatal */ + } + + /* Wait for MCU to complete refresh */ + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 1000); + if (ret < 0) { + dev_err(dev, "MT9M113: MCU refresh timeout\n"); + return ret; + } + + /* Issue sequencer refresh */ + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, + MT9M113_SEQ_CMD_REFRESH); + if (ret < 0) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 1000); + if (ret < 0) { + dev_err(dev, "MT9M113: SEQ refresh timeout\n"); + return ret; + } + + /* + * Enable MIPI Frame-Start/End short packets. Without them the VFE CAMIF + * has no per-frame boundary marker and runs in line-counting (APS) mode, + * which slips against the sensor's free-running readout and makes the + * captured image walk vertically (~tens of lines/frame, resolution + * dependent). Emitting the frame-sync short packets locks every frame. + */ + ret =3D cci_write(sensor->regmap, MT9M113_CUSTOM_SHORT_PKT, + MT9M113_CUSTOM_SHORT_PKT_FRAME_CNT_EN, NULL); + if (ret < 0) + return ret; + + /* + * Do NOT enable MIPI output here. It is enabled in start_streaming once + * the output config is programmed, followed by REFRESH_MODE + REFRESH. + * Enabling MIPI before the CSIPHY is configured causes problems. + */ + + dev_dbg(dev, "MT9M113: init complete\n"); + return 0; +} + +/* -----------------------------------------------------------------------= ------ + * Streaming + */ + +/* + * Configure AE (Auto Exposure) parameters for preview vs snapshot/video m= ode. + * From legacy vendor driver: snapshot mode allows longer exposure for bet= ter quality, + * while preview mode optimizes for higher frame rate and lower power. + */ +static int mt9m113_configure_ae_mode(struct mt9m113 *sensor, bool snapshot= _mode) +{ + struct device *dev =3D &sensor->client->dev; + int ret; + + if (snapshot_mode) { + /* Snapshot/Capture mode: allow longer exposure for quality */ + dev_dbg(dev, "MT9M113: configuring AE for snapshot mode\n"); + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_INDEX, 0x0028); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_VIRTGAIN, 0x0060); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_DGAIN_AE1, 0x00C8); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_JUMP_DIVISOR, 0x0002); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_SKIP_FRAMES, 0x0002); + if (ret) + return ret; + } else { + /* Preview mode: optimize for frame rate and responsiveness */ + dev_dbg(dev, "MT9M113: configuring AE for preview mode\n"); + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_INDEX, 0x0008); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_VIRTGAIN, 0x00A0); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_MAX_DGAIN_AE1, 0x0150); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_JUMP_DIVISOR, 0x0001); + if (ret) + return ret; + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_SKIP_FRAMES, 0x0001); + if (ret) + return ret; + } + + return 0; +} + +/* + * Program the per-context output dimensions and pixel format, then re-app= ly the + * sensor readout geometry. Output configuration is double-buffered so it + * latches atomically at the next frame start. + */ +static int mt9m113_configure_output(struct mt9m113 *sensor, bool use_conte= xt_b, + u32 code) +{ + struct device *dev =3D &sensor->client->dev; + u16 width_reg, height_reg, format_reg; + u16 width_val, height_val, format_val; + int ret; + + if (use_context_b) { + width_reg =3D MT9M113_MODE_OUTPUT_WIDTH_B; + height_reg =3D MT9M113_MODE_OUTPUT_HEIGHT_B; + format_reg =3D MT9M113_MODE_OUTPUT_FORMAT_B; + width_val =3D 1280; + height_val =3D 1024; + } else { + width_reg =3D MT9M113_MODE_OUTPUT_WIDTH_A; + height_reg =3D MT9M113_MODE_OUTPUT_HEIGHT_A; + format_reg =3D MT9M113_MODE_OUTPUT_FORMAT_A; + width_val =3D 640; + height_val =3D 480; + } + + format_val =3D mt9m113_format_val(code); + + ret =3D mt9m113_double_buffer_suspend(sensor); + if (ret) + dev_warn(dev, "Failed to suspend double buffer: %d\n", ret); + + ret =3D mt9m113_write_mcu_var(sensor, width_reg, width_val); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, height_reg, height_val); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, format_reg, format_val); + + { + int resume_ret =3D mt9m113_double_buffer_resume(sensor); + + if (!ret) + ret =3D resume_ret; + } + if (ret) + return ret; + + dev_dbg(dev, "MT9M113: Context %c: %ux%u, format=3D0x%04x\n", + use_context_b ? 'B' : 'A', width_val, height_val, format_val); + + /* + * Re-apply the sensor readout geometry. The MCU variables for + * row_end/read_mode can be left stale after switching from Context B to + * Context A, producing wrong line counts and a corrupted image. + */ + ret =3D mt9m113_configure_sensor_context(sensor, use_context_b); + if (ret) + dev_err(dev, "Failed to configure sensor context: %d\n", ret); + + return ret; +} + +/* + * The MCU resets OUTPUT_CONTROL and MODE_OUTPUT_FORMAT to their defaults = during + * sequencer state transitions, so re-apply them once the stream is runnin= g. + * Best-effort: a failure is logged but does not abort the stream. + */ +static void mt9m113_reassert_output(struct mt9m113 *sensor, u16 output_ctr= l_val, + u16 format_reg, u32 code) +{ + struct device *dev =3D &sensor->client->dev; + u16 format_val =3D mt9m113_format_val(code); + + if (cci_write(sensor->regmap, MT9M113_OUTPUT_CONTROL, output_ctrl_val, + NULL)) + dev_warn(dev, "MT9M113: OUTPUT_CONTROL re-write failed\n"); + if (mt9m113_write_mcu_var(sensor, format_reg, format_val)) + dev_warn(dev, "MT9M113: MODE_OUTPUT_FORMAT re-write failed\n"); +} + +/* Context A (640x480 preview): enable output and run the preview sequence= r. */ +static int mt9m113_stream_context_a(struct mt9m113 *sensor, u16 output_ctr= l_val, + u32 code) +{ + struct device *dev =3D &sensor->client->dev; + int ret; + + ret =3D cci_write(sensor->regmap, MT9M113_OUTPUT_CONTROL, output_ctrl_val, + NULL); + if (ret) + return ret; + dev_dbg(dev, "MT9M113: OUTPUT_CONTROL=3D0x%04x enabled\n", output_ctrl_va= l); + + ret =3D cci_write(sensor->regmap, MT9M113_RESET_REGISTER, + MT9M113_RESET_REG_STREAMING, NULL); + if (ret) + return ret; + + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CAP_MODE, + MT9M113_SEQ_CAP_MODE_PREVIEW); + if (ret) + return ret; + msleep(40); + + dev_dbg(dev, "MT9M113: Writing SEQ_CMD_RUN\n"); + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, MT9M113_SEQ_CMD_RU= N); + if (ret) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 500); + if (ret < 0) + dev_warn(dev, "MT9M113: SEQ_CMD_RUN did not complete\n"); + + mt9m113_reassert_output(sensor, output_ctrl_val, + MT9M113_MODE_OUTPUT_FORMAT_A, code); + msleep(20); + return 0; +} + +/* + * Context B (1280x1024 capture): the MCU only accepts CAPTURE from stable + * preview, so enter preview first, wait for SEQ_STATE=3DPREVIEW, then swi= tch. + */ +static int mt9m113_stream_context_b(struct mt9m113 *sensor, u16 output_ctr= l_val, + u32 code) +{ + struct device *dev =3D &sensor->client->dev; + u64 seq_state =3D 0; + unsigned int i; + int ret; + + ret =3D cci_write(sensor->regmap, MT9M113_OUTPUT_CONTROL, output_ctrl_val, + NULL); + if (ret) + return ret; + dev_dbg(dev, "MT9M113: OUTPUT_CONTROL=3D0x%04x enabled\n", output_ctrl_va= l); + + ret =3D cci_write(sensor->regmap, MT9M113_RESET_REGISTER, + MT9M113_RESET_REG_STREAMING, NULL); + if (ret) + return ret; + + /* Enter preview first. */ + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CAP_MODE, + MT9M113_SEQ_CAP_MODE_PREVIEW); + if (ret) + return ret; + msleep(40); + + dev_dbg(dev, "MT9M113: Entering preview mode first (SEQ_CMD_RUN)\n"); + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, MT9M113_SEQ_CMD_RU= N); + if (ret) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 500); + if (ret < 0) + dev_warn(dev, "MT9M113: SEQ_CMD_RUN did not complete\n"); + + for (i =3D 0; i < 100; i++) { + mt9m113_read_mcu_var(sensor, MT9M113_SEQ_STATE, &seq_state); + if (seq_state =3D=3D MT9M113_SEQ_STATE_PREVIEW) + break; + usleep_range(10000, 11000); + } + if (seq_state !=3D MT9M113_SEQ_STATE_PREVIEW) + dev_warn(dev, + "MT9M113: preview state not reached (SEQ_STATE=3D0x%llx)\n", + seq_state); + + /* Switch to video mode (stays in Context B). */ + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CAP_MODE, + MT9M113_SEQ_CAP_MODE_VIDEO); + if (ret) + return ret; + msleep(40); + + ret =3D mt9m113_seq_cmd_ready(sensor); + if (ret < 0) { + dev_err(dev, "MT9M113: MCU not ready for CAPTURE command\n"); + return ret; + } + + dev_dbg(dev, "MT9M113: switching to Context B (SEQ_CMD_CAPTURE)\n"); + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, + MT9M113_SEQ_CMD_CAPTURE); + if (ret) + return ret; + + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 500); + if (ret < 0) + dev_warn(dev, "MT9M113: SEQ_CMD_CAPTURE did not complete\n"); + + for (i =3D 0; i < 100; i++) { + mt9m113_read_mcu_var(sensor, MT9M113_SEQ_STATE, &seq_state); + if (seq_state =3D=3D MT9M113_SEQ_STATE_CAPTURE) + break; + usleep_range(10000, 11000); + } + if (seq_state !=3D MT9M113_SEQ_STATE_CAPTURE) { + dev_err(dev, "MT9M113: Context B switch failed (SEQ_STATE=3D0x%llx)\n", + seq_state); + return -ETIMEDOUT; + } + dev_dbg(dev, "MT9M113: reached capture state\n"); + + mt9m113_reassert_output(sensor, output_ctrl_val, + MT9M113_MODE_OUTPUT_FORMAT_B, code); + + /* The sensor pipeline needs time to reconfigure for 1280x1024. */ + msleep(200); + return 0; +} + +static int mt9m113_stream_on(struct mt9m113 *sensor, + struct v4l2_subdev_state *state) +{ + struct device *dev =3D &sensor->client->dev; + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *compose; + u64 health_check, seq_cmd; + bool use_context_b; + u16 output_ctrl_val; + int ret; + + /* + * Wake the MCU out of standby and give it time to settle (50ms matches + * the legacy vendor driver). + */ + dev_dbg(dev, "MT9M113: start_streaming\n"); + cci_write(sensor->regmap, MT9M113_STANDBY_CONTROL, + MT9M113_STANDBY_CONTROL_ACTIVE, NULL); + msleep(50); + + /* + * MCU health check: MODE_OUTPUT_WIDTH_A reads back non-zero once the + * MCU is running. If it is unresponsive, re-apply the init table. + */ + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_MODE_OUTPUT_WIDTH_A, + &health_check); + if (ret < 0 || health_check =3D=3D 0) { + dev_warn(dev, "MT9M113: MCU unresponsive, re-init\n"); + ret =3D mt9m113_sensor_init(sensor); + if (ret < 0) + return ret; + } + + /* + * Clear stale SEQ_CMD. The init table ends with REFRESH (SEQ_CMD=3D0x05) + * which may not complete before the sensor enters standby/suspend. On + * resume, SEQ_CMD is stuck at 0x05 and all subsequent commands fail. + * Force-clear it. + */ + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SEQ_CMD, &seq_cmd); + if (ret =3D=3D 0 && seq_cmd !=3D 0) { + dev_warn(dev, "MT9M113: Clearing stale SEQ_CMD=3D0x%llx\n", seq_cmd); + mt9m113_write_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000); + msleep(50); + } + + /* + * Re-assert the MIPI Frame-Start/End short packets on every stream + * start. The MCU clears CUSTOM_SHORT_PKT across standby/refresh + * cycles, and mt9m113_sensor_init() (which also sets it) only runs on + * the MCU-recovery path - so without this the bit is lost after the + * first stream. Without the per-frame boundary marker the VFE CAMIF + * falls back to line-counting and the image walks vertically. + */ + ret =3D cci_write(sensor->regmap, MT9M113_CUSTOM_SHORT_PKT, + MT9M113_CUSTOM_SHORT_PKT_FRAME_CNT_EN, NULL); + if (ret < 0) + return ret; + + /* Apply all V4L2 controls (color effects, etc.) before streaming */ + ret =3D __v4l2_ctrl_handler_setup(&sensor->ifp.hdl); + if (ret) { + dev_err(dev, "Failed to setup controls: %d\n", ret); + return ret; + } + + compose =3D v4l2_subdev_state_get_compose(state, 0); + /* Source pad (pad 1) carries the MIPI output format. */ + format =3D v4l2_subdev_state_get_format(state, 1); + + use_context_b =3D (compose->width > 640 || compose->height > 480); + dev_dbg(dev, "MT9M113: %ux%u -> Context %c\n", + compose->width, compose->height, use_context_b ? 'B' : 'A'); + + /* + * Configure AE for the selected context: Context B (capture) allows a + * longer exposure for quality, Context A (preview) optimises for frame + * rate. + */ + ret =3D mt9m113_configure_ae_mode(sensor, use_context_b); + if (ret) { + dev_err(dev, "Failed to configure AE mode: %d\n", ret); + return ret; + } + + ret =3D mt9m113_configure_output(sensor, use_context_b, format->code); + if (ret) + return ret; + + /* Wait for CSIPHY stabilization */ + msleep(MT9M113_PRE_MIPI_DELAY_MS); + + /* + * YUV/RGB output: enable MIPI CSI-2 in FIFO-bypass mode and restore the + * default ISP colour pipeline. + */ + output_ctrl_val =3D MT9M113_OUTPUT_CONTROL_MIPI_ENABLE; + ret =3D cci_write(sensor->regmap, MT9M113_OFIFO_CONTROL_STATUS, + MT9M113_OFIFO_BYPASS, NULL); + if (ret) + return ret; + ret =3D cci_write(sensor->regmap, MT9M113_COLOR_PIPELINE_CONTROL, + MT9M113_COLOR_PIPELINE_DEFAULT, NULL); + if (ret) + return ret; + + /* The MCU must be idle before it will accept a sequencer command. */ + ret =3D mt9m113_seq_cmd_ready(sensor); + if (ret < 0) { + dev_err(dev, "MT9M113: MCU not ready for streaming command\n"); + return ret; + } + + /* + * One REFRESH applies the per-session output dimensions/format; without + * it SEQ_CMD_RUN hangs (the MCU never clears SEQ_CMD). OFIFO and the + * colour pipeline are already programmed above - they must not be + * written while a REFRESH is pending or it times out. + */ + ret =3D mt9m113_refresh(sensor); + if (ret) + dev_warn(dev, "MT9M113: REFRESH failed, continuing\n"); + + /* + * For Context B the MCU must reach stable preview (SEQ_STATE=3D0x04) + * before it will accept the CAPTURE command, so enter preview first and + * then switch. Context A simply runs preview. + */ + if (use_context_b) + ret =3D mt9m113_stream_context_b(sensor, output_ctrl_val, + format->code); + else + ret =3D mt9m113_stream_context_a(sensor, output_ctrl_val, + format->code); + if (ret) + return ret; + + dev_dbg(dev, "MT9M113: streaming started\n"); + return 0; +} + +/* + * The MT9M113 MCU intermittently wedges on stream start; the failures clu= ster + * at the start of a run and then clear. Retry a bounded number of times, = fully + * power-cycling the sensor (runtime-PM suspend -> resume =3D power_off + = power_on + * + sensor_init) between attempts. Exactly one pm_runtime get/put pair ru= ns per + * attempt; on success the reference is held for the streaming session and + * released by mt9m113_stop_streaming(). + */ +static int mt9m113_start_streaming(struct mt9m113 *sensor, + struct v4l2_subdev_state *state) +{ + struct device *dev =3D &sensor->client->dev; + unsigned int attempt; + int ret =3D 0; + + /* + * Guard against a redundant s_stream(1): a second + * pm_runtime_resume_and_get() here would leak a PM reference and pin + * the device awake forever, because mt9m113_stop_streaming() only + * drops one reference per stop. + */ + if (sensor->streaming) + return 0; + + for (attempt =3D 0; attempt < MT9M113_STREAM_START_RETRIES; attempt++) { + ret =3D pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret =3D mt9m113_stream_on(sensor, state); + if (!ret) { + sensor->streaming =3D true; + return 0; + } + + pm_runtime_put_sync_suspend(dev); + if (attempt + 1 < MT9M113_STREAM_START_RETRIES) + dev_warn(dev, + "MT9M113: stream start failed (%d), power-cycle + retry %u/%u\n", + ret, attempt + 1, MT9M113_STREAM_START_RETRIES - 1); + } + + return ret; +} + +static int mt9m113_stop_streaming(struct mt9m113 *sensor) +{ + struct device *dev =3D &sensor->client->dev; + int ret; + + if (!sensor->streaming) + return 0; + + sensor->streaming =3D false; + + /* Disable MIPI output */ + ret =3D cci_write(sensor->regmap, MT9M113_OUTPUT_CONTROL, 0x0000, NULL); + if (ret < 0) + dev_warn(dev, + "MT9M113: MIPI output disable failed (%d), receiver may still see traf= fic\n", + ret); + + /* + * Wait briefly for any pending SEQ_CMD to complete. + * Don't issue REFRESH here - it can leave MCU stuck if it times out. + * Start_streaming will handle full re-initialization. + */ + ret =3D mt9m113_poll_mcu_var(sensor, MT9M113_SEQ_CMD, 0x0000, 100); + if (ret < 0) + dev_dbg(dev, "MT9M113: SEQ_CMD did not complete before stop\n"); + + dev_dbg(dev, "MT9M113: streaming stopped\n"); + + pm_runtime_put_autosuspend(dev); + return 0; +} + +/* -----------------------------------------------------------------------= ------ + * V4L2 Subdev Operations + */ + +static inline struct mt9m113 *ifp_to_mt9m113(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mt9m113, ifp.sd); +} + +static int mt9m113_ifp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + struct v4l2_subdev_state *state; + int ret; + + state =3D v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) + ret =3D mt9m113_start_streaming(sensor, state); + else + ret =3D mt9m113_stop_streaming(sensor); + + v4l2_subdev_unlock_state(state); + return ret; +} + +/* -----------------------------------------------------------------------= ------ + * Pixel Array Subdev Operations + */ + +static int mt9m113_pa_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + crop =3D v4l2_subdev_state_get_crop(state, 0); + crop->left =3D 0; + crop->top =3D 0; + crop->width =3D MT9M113_PIXEL_ARRAY_WIDTH; + crop->height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + + format =3D v4l2_subdev_state_get_format(state, 0); + format->width =3D MT9M113_PIXEL_ARRAY_WIDTH; + format->height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + format->code =3D MEDIA_BUS_FMT_SGRBG10_1X10; + format->field =3D V4L2_FIELD_NONE; + format->colorspace =3D V4L2_COLORSPACE_RAW; + + return 0; +} + +static int mt9m113_pa_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code =3D MEDIA_BUS_FMT_SGRBG10_1X10; + return 0; +} + +static int mt9m113_pa_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index > 0) + return -EINVAL; + + if (fse->code !=3D MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width =3D MT9M113_PIXEL_ARRAY_WIDTH; + fse->max_width =3D MT9M113_PIXEL_ARRAY_WIDTH; + fse->min_height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + fse->max_height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + + return 0; +} + +static int mt9m113_pa_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 =3D *v4l2_subdev_state_get_crop(state, sel->pad); + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left =3D 0; + sel->r.top =3D 0; + sel->r.width =3D MT9M113_PIXEL_ARRAY_WIDTH; + sel->r.height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + return 0; + + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_pad_ops mt9m113_pa_pad_ops =3D { + .enum_mbus_code =3D mt9m113_pa_enum_mbus_code, + .enum_frame_size =3D mt9m113_pa_enum_framesizes, + .get_fmt =3D v4l2_subdev_get_fmt, + .get_selection =3D mt9m113_pa_get_selection, +}; + +static const struct v4l2_subdev_ops mt9m113_pa_ops =3D { + .pad =3D &mt9m113_pa_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9m113_pa_internal_ops =3D { + .init_state =3D mt9m113_pa_init_state, +}; + +/* -----------------------------------------------------------------------= ------ + * IFP Subdev Operations + */ + +static int mt9m113_ifp_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect *compose; + + /* Sink pad - receives raw data from PA */ + format =3D v4l2_subdev_state_get_format(state, 0); + format->width =3D MT9M113_PIXEL_ARRAY_WIDTH; + format->height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + format->code =3D MEDIA_BUS_FMT_SGRBG10_1X10; + format->field =3D V4L2_FIELD_NONE; + format->colorspace =3D V4L2_COLORSPACE_RAW; + + crop =3D v4l2_subdev_state_get_crop(state, 0); + crop->left =3D 0; + crop->top =3D 0; + crop->width =3D MT9M113_PIXEL_ARRAY_WIDTH; + crop->height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + + compose =3D v4l2_subdev_state_get_compose(state, 0); + compose->left =3D 0; + compose->top =3D 0; + compose->width =3D 640; + compose->height =3D 480; + + /* Source pad - outputs processed data to host */ + format =3D v4l2_subdev_state_get_format(state, 1); + format->width =3D 640; + format->height =3D 480; + format->code =3D MEDIA_BUS_FMT_UYVY8_1X16; + format->field =3D V4L2_FIELD_NONE; + format->colorspace =3D V4L2_COLORSPACE_SRGB; + + return 0; +} + +static int mt9m113_ifp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Sink pad only accepts raw from PA */ + if (code->pad =3D=3D 0) { + if (code->index > 0) + return -EINVAL; + code->code =3D MEDIA_BUS_FMT_SGRBG10_1X10; + return 0; + } + + /* Source pad supports multiple output formats */ + if (code->index >=3D ARRAY_SIZE(mt9m113_format_infos)) + return -EINVAL; + + code->code =3D mt9m113_format_infos[code->index].code; + return 0; +} + +static int mt9m113_ifp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + /* Sink pad - fixed raw input size */ + if (fse->pad =3D=3D 0) { + if (fse->index > 0) + return -EINVAL; + if (fse->code !=3D MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + fse->min_width =3D MT9M113_PIXEL_ARRAY_WIDTH; + fse->max_width =3D MT9M113_PIXEL_ARRAY_WIDTH; + fse->min_height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + fse->max_height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + return 0; + } + + /* Source pad - Context A (640x480) and Context B (1280x1024) */ + if (fse->index > 1) + return -EINVAL; + if (mt9m113_format_info(fse->code)->code !=3D fse->code) + return -EINVAL; + + if (fse->index =3D=3D 0) { + fse->min_width =3D 640; + fse->max_width =3D 640; + fse->min_height =3D 480; + fse->max_height =3D 480; + } else { + fse->min_width =3D 1280; + fse->max_width =3D 1280; + fse->min_height =3D 1024; + fse->max_height =3D 1024; + } + + return 0; +} + +static int mt9m113_ifp_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + /* Only source pad (pad 1) supports frame interval enumeration */ + if (fie->pad !=3D 1) + return -EINVAL; + + /* One interval per resolution */ + if (fie->index > 0) + return -EINVAL; + + /* Context A: 640x480 @ 30fps, Context B: 1280x1024 @ 15fps */ + if (fie->width =3D=3D 640 && fie->height =3D=3D 480) { + fie->interval.numerator =3D 1; + fie->interval.denominator =3D 30; + } else if (fie->width =3D=3D 1280 && fie->height =3D=3D 1024) { + fie->interval.numerator =3D 1; + fie->interval.denominator =3D 15; + } else { + return -EINVAL; + } + + return 0; +} + +static int mt9m113_ifp_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_interval *fi) +{ + struct v4l2_mbus_framefmt *format; + + /* Frame intervals are only defined on the source pad. */ + if (fi->pad !=3D 1) + return -EINVAL; + + format =3D v4l2_subdev_state_get_format(state, 1); + + /* Return interval based on current resolution */ + if (format->width <=3D 640 && format->height <=3D 480) { + /* Context A: 30fps */ + fi->interval.numerator =3D 1; + fi->interval.denominator =3D 30; + } else { + /* Context B: 15fps */ + fi->interval.numerator =3D 1; + fi->interval.denominator =3D 15; + } + + return 0; +} + +static int mt9m113_ifp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *compose; + const struct mt9m113_format_info *info; + + /* + * Reject geometry/format changes while the pipeline is live. + * The sensor is only programmed in mt9m113_stream_on(); silently + * updating the active state would let userspace see one format while + * the wire still carries the previous one. + */ + if (sensor->streaming) + return -EBUSY; + + /* Sink pad format is fixed */ + if (fmt->pad =3D=3D 0) { + format =3D v4l2_subdev_state_get_format(state, 0); + fmt->format =3D *format; + return 0; + } + + /* Source pad */ + info =3D mt9m113_format_info(fmt->format.code); + + /* Clamp to supported sizes (Context A or Context B) */ + if (fmt->format.width <=3D 640) { + fmt->format.width =3D 640; + fmt->format.height =3D 480; + } else { + fmt->format.width =3D 1280; + fmt->format.height =3D 1024; + } + + format =3D v4l2_subdev_state_get_format(state, 1); + format->width =3D fmt->format.width; + format->height =3D fmt->format.height; + format->code =3D info->code; + format->field =3D V4L2_FIELD_NONE; + format->colorspace =3D V4L2_COLORSPACE_SRGB; + + compose =3D v4l2_subdev_state_get_compose(state, 0); + compose->width =3D format->width; + compose->height =3D format->height; + + fmt->format =3D *format; + return 0; +} + +static int mt9m113_ifp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + if (sel->pad !=3D 0) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r =3D *v4l2_subdev_state_get_crop(state, 0); + return 0; + + case V4L2_SEL_TGT_COMPOSE: + sel->r =3D *v4l2_subdev_state_get_compose(state, 0); + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left =3D 0; + sel->r.top =3D 0; + sel->r.width =3D MT9M113_PIXEL_ARRAY_WIDTH; + sel->r.height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + return 0; + + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.left =3D 0; + sel->r.top =3D 0; + sel->r.width =3D MT9M113_PIXEL_ARRAY_WIDTH; + sel->r.height =3D MT9M113_PIXEL_ARRAY_HEIGHT; + return 0; + + default: + return -EINVAL; + } +} + +/* IFP registered callback - registers PA and creates link */ +static void mt9m113_ifp_unregistered(struct v4l2_subdev *sd) +{ + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + + v4l2_device_unregister_subdev(&sensor->pa.sd); +} + +static int mt9m113_ifp_registered(struct v4l2_subdev *sd) +{ + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + int ret; + + ret =3D v4l2_device_register_subdev(sd->v4l2_dev, &sensor->pa.sd); + if (ret < 0) { + dev_err(&sensor->client->dev, + "Failed to register pixel array subdev\n"); + return ret; + } + + ret =3D media_create_pad_link(&sensor->pa.sd.entity, 0, + &sensor->ifp.sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + dev_err(&sensor->client->dev, + "Failed to link pixel array to ifp\n"); + v4l2_device_unregister_subdev(&sensor->pa.sd); + return ret; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops mt9m113_ifp_video_ops =3D { + .s_stream =3D mt9m113_ifp_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mt9m113_ifp_pad_ops =3D { + .enum_mbus_code =3D mt9m113_ifp_enum_mbus_code, + .enum_frame_size =3D mt9m113_ifp_enum_frame_size, + .enum_frame_interval =3D mt9m113_ifp_enum_frame_interval, + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D mt9m113_ifp_set_fmt, + .get_frame_interval =3D mt9m113_ifp_g_frame_interval, + .get_selection =3D mt9m113_ifp_get_selection, +}; + +static const struct v4l2_subdev_ops mt9m113_ifp_ops =3D { + .video =3D &mt9m113_ifp_video_ops, + .pad =3D &mt9m113_ifp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops mt9m113_ifp_internal_ops =3D { + .init_state =3D mt9m113_ifp_init_state, + .registered =3D mt9m113_ifp_registered, + .unregistered =3D mt9m113_ifp_unregistered, +}; + +/* -----------------------------------------------------------------------= ------ + * Controls + */ + +static const char * const mt9m113_test_pattern_menu[] =3D { + "Disabled", + "Solid Color", + "Color Bars", + "Fade to Gray", +}; + +static const u8 mt9m113_test_pattern_value[] =3D { + /* Values written to CAM_MODE_TEST_PATTERN_SELECT (indexed by menu - 1) */ + MT9M113_TEST_PATTERN_SOLID_COLOR, + MT9M113_TEST_PATTERN_COLOR_BARS, + MT9M113_TEST_PATTERN_FADE_TO_GRAY, +}; + +static int mt9m113_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m113 *sensor =3D container_of(ctrl->handler, + struct mt9m113, ifp.hdl); + int ret =3D 0; + + /* + * pm_runtime_get_if_in_use() returns >0 on success, 0 if not active, + * and -EINVAL if runtime PM is disabled. Both 0 and -EINVAL mean we + * must not touch the hardware (and must not pair with a put), so the + * defensive comparison is "<=3D 0" rather than the naive "!ret". + */ + if (pm_runtime_get_if_in_use(&sensor->client->dev) <=3D 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: { + u64 mode_a, mode_b; + + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_A, + &mode_a); + if (ret) + break; + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_B, + &mode_b); + if (ret) + break; + + if (ctrl->val) { + mode_a |=3D MT9M113_SENSOR_READ_MODE_HMIRROR; + mode_b |=3D MT9M113_SENSOR_READ_MODE_HMIRROR; + } else { + mode_a &=3D ~MT9M113_SENSOR_READ_MODE_HMIRROR; + mode_b &=3D ~MT9M113_SENSOR_READ_MODE_HMIRROR; + } + + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_A, + mode_a); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_SENSOR_READ_MODE_B, + mode_b); + /* Only refresh if streaming - otherwise MCU may not be ready */ + if (!ret && sensor->streaming) + mt9m113_refresh(sensor); + break; + } + + case V4L2_CID_VFLIP: { + u64 mode_a, mode_b; + + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_A, + &mode_a); + if (ret) + break; + ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_B, + &mode_b); + if (ret) + break; + + if (ctrl->val) { + mode_a |=3D MT9M113_SENSOR_READ_MODE_VMIRROR; + mode_b |=3D MT9M113_SENSOR_READ_MODE_VMIRROR; + } else { + mode_a &=3D ~MT9M113_SENSOR_READ_MODE_VMIRROR; + mode_b &=3D ~MT9M113_SENSOR_READ_MODE_VMIRROR; + } + + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_SENSOR_READ_MODE_A, + mode_a); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_SENSOR_READ_MODE_B, + mode_b); + /* Only refresh if streaming - otherwise MCU may not be ready */ + if (!ret && sensor->streaming) + mt9m113_refresh(sensor); + break; + } + + case V4L2_CID_COLORFX: { + u16 effect; + + switch (ctrl->val) { + case V4L2_COLORFX_NONE: + effect =3D MT9M113_SPEC_EFFECTS_NONE; + break; + case V4L2_COLORFX_BW: + effect =3D MT9M113_SPEC_EFFECTS_MONOCHROME; + break; + case V4L2_COLORFX_SEPIA: + effect =3D MT9M113_SPEC_EFFECTS_SEPIA; + break; + case V4L2_COLORFX_NEGATIVE: + effect =3D MT9M113_SPEC_EFFECTS_NEGATIVE; + break; + case V4L2_COLORFX_SOLARIZATION: + effect =3D MT9M113_SPEC_EFFECTS_SOLARIZE; + break; + default: + ret =3D -EINVAL; + break; + } + + if (!ret) { + effect |=3D (MT9M113_SPEC_EFFECTS_DEFAULT & + ~MT9M113_SPEC_EFFECTS_MASK); + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SPEC_EFFECTS_A, + effect); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_MODE_SPEC_EFFECTS_B, + effect); + + /* Only refresh if streaming - otherwise MCU may not be ready */ + if (!ret && sensor->streaming) + mt9m113_refresh(sensor); + } + break; + } + + case V4L2_CID_POWER_LINE_FREQUENCY: { + u8 fd_mode; + + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + fd_mode =3D MT9M113_FD_MODE_DISABLED; + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + fd_mode =3D MT9M113_FD_MODE_50HZ; + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + fd_mode =3D MT9M113_FD_MODE_60HZ; + break; + case V4L2_CID_POWER_LINE_FREQUENCY_AUTO: + fd_mode =3D MT9M113_FD_MODE_AUTO; + break; + default: + ret =3D -EINVAL; + break; + } + + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_FD_MODE, + fd_mode); + break; + } + + case V4L2_CID_SATURATION: + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AWB_SATURATION, + ctrl->val); + /* Only refresh if streaming - otherwise MCU may not be ready */ + if (!ret && sensor->streaming) + mt9m113_refresh(sensor); + break; + + case V4L2_CID_EXPOSURE_AUTO: + /* Enable/disable internal AE algorithm via AE_GATE */ + if (ctrl->val =3D=3D V4L2_EXPOSURE_AUTO) + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_GATE, + MT9M113_AE_GATE_ENABLE); + else + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AE_GATE, + MT9M113_AE_GATE_DISABLE); + break; + + case V4L2_CID_EXPOSURE: + /* Write coarse integration time to both contexts */ + ret =3D cci_write(sensor->regmap, MT9M113_COARSE_IT_TIME_A, + ctrl->val, NULL); + if (!ret) + ret =3D cci_write(sensor->regmap, MT9M113_COARSE_IT_TIME_B, + ctrl->val, NULL); + break; + + case V4L2_CID_ANALOGUE_GAIN: + /* Write analog gain register */ + ret =3D cci_write(sensor->regmap, MT9M113_ANALOG_GAIN, + ctrl->val, NULL); + break; + + case V4L2_CID_AUTO_WHITE_BALANCE: + /* Enable/disable internal AWB via awb_mode */ + ret =3D mt9m113_write_mcu_var(sensor, MT9M113_AWB_MODE, + ctrl->val ? 0x02 : 0x00); + break; + + case V4L2_CID_TEST_PATTERN: + if (ctrl->val =3D=3D 0) { + /* + * Disable test pattern. Only restart MCU if test + * pattern was previously active. On first call + * (ctrl_handler_setup default), the MCU is already + * in normal mode - restarting it here would disrupt + * the MCU state and cause REFRESH timeouts. + * + * Keep test_pattern_active set until both writes + * succeed: a half-applied disable leaves the MCU + * halted, and a subsequent CID=3D0 must be allowed to + * retry the full sequence. + */ + if (sensor->test_pattern_active) { + dev_dbg(&sensor->client->dev, + "MT9M113: Disabling test pattern, restarting MCU\n"); + cci_write(sensor->regmap, MT9M113_MCU_BOOT_MODE, + 0x0000, &ret); + if (!ret) { + usleep_range(10000, 15000); + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_CAM_MODE_SELECT, + MT9M113_CAM_MODE_SELECT_NORMAL); + } + if (!ret) + sensor->test_pattern_active =3D false; + } + } else { + /* + * Enable test pattern mode. + * Per datasheet: "Disabling the MCU is recommended + * before enabling test patterns." + * + * Sequence: + * 1. Configure test pattern via MCU variables (MCU running) + * 2. Issue refresh to apply settings + * 3. Hold MCU in boot mode to prevent override - but only + * while streaming. If the MCU is halted outside of an + * active session, a STREAMON inside the autosuspend + * window finds a wedged MCU and the in-flight REFRESH + * / sequencer commands time out; the next runtime + * resume's power_on + sensor_init clears it instead. + */ + sensor->test_pattern_active =3D true; + dev_dbg(&sensor->client->dev, + "MT9M113: Enabling test pattern %d\n", ctrl->val); + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_CAM_MODE_TEST_PATTERN_SELECT, + mt9m113_test_pattern_value[ctrl->val - 1]); + if (!ret) + ret =3D mt9m113_write_mcu_var(sensor, + MT9M113_CAM_MODE_SELECT, + MT9M113_CAM_MODE_SELECT_TEST_PATTERN); + if (!ret && sensor->streaming) { + mt9m113_refresh(sensor); + dev_dbg(&sensor->client->dev, + "MT9M113: Stopping MCU for test pattern\n"); + cci_write(sensor->regmap, MT9M113_MCU_BOOT_MODE, + 0x0001, &ret); + } + } + break; + + default: + ret =3D -EINVAL; + break; + } + + pm_runtime_put_autosuspend(&sensor->client->dev); + return ret; +} + +static const struct v4l2_ctrl_ops mt9m113_ctrl_ops =3D { + .s_ctrl =3D mt9m113_s_ctrl, +}; + +/* -----------------------------------------------------------------------= ------ + * Power Management + */ + +static int mt9m113_power_on(struct mt9m113 *sensor) +{ + struct device *dev =3D &sensor->client->dev; + int ret; + + ret =3D regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) + return ret; + + /* + * Aptina/onsemi SOC sensors require EXTCLK to be running before + * STANDBY (powerdown) is released; otherwise the internal + * clock-domain state machines come out of reset non-deterministically + * and the MCU can wedge on SEQ_CMD. Enable the clock first, let the + * supplies and clock settle, then deassert powerdown. + */ + ret =3D clk_prepare_enable(sensor->clk); + if (ret < 0) + goto error_regulator; + + usleep_range(20000, 25000); + + if (sensor->powerdown) + gpiod_set_value_cansleep(sensor->powerdown, 0); + + msleep(20); + + if (sensor->reset) { + gpiod_set_value_cansleep(sensor->reset, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(sensor->reset, 0); + usleep_range(44500, 50000); + } else if (sensor->powerdown) { + usleep_range(44500, 50000); + } + + /* MT9M113 MCU boot sequence */ + { + u64 clocks_val =3D 0; + + cci_read(sensor->regmap, MT9M113_CLOCKS_CONTROL, &clocks_val, NULL); + if (clocks_val !=3D 0) { + u64 seq_cmd =3D 0; + int read_ret; + + /* + * Sensor already has clocks running (warm reboot or + * resume). Check if MCU is responsive by reading + * SEQ_CMD. If stuck (non-zero) or if the read itself + * failed (I2C error), force the soft-reset path instead + * of silently returning success on an uninitialised + * sensor. + */ + read_ret =3D mt9m113_read_mcu_var(sensor, MT9M113_SEQ_CMD, + &seq_cmd); + if (read_ret =3D=3D 0 && seq_cmd =3D=3D 0) { + dev_dbg(dev, "MT9M113 already initialized, MCU OK\n"); + msleep(50); + return 0; + } + if (read_ret < 0) + dev_warn(dev, + "MT9M113: SEQ_CMD read failed (%d), forcing soft reset\n", + read_ret); + else + dev_warn(dev, + "MT9M113: MCU stuck (SEQ_CMD=3D0x%llx), forcing soft reset\n", + seq_cmd); + } + + /* Soft reset */ + cci_write(sensor->regmap, MT9M113_RESET_AND_MISC_CONTROL, + MT9M113_RESET_SOC, &ret); + cci_write(sensor->regmap, MT9M113_RESET_AND_MISC_CONTROL, + 0, &ret); + if (ret < 0) + goto error_clock; + msleep(200); + + /* Boot MCU */ + cci_write(sensor->regmap, MT9M113_MCU_BOOT_MODE, 0x0001, &ret); + usleep_range(1000, 2000); + cci_write(sensor->regmap, MT9M113_MCU_BOOT_MODE, 0x0000, &ret); + if (ret < 0) + goto error_clock; + msleep(200); + + /* Configure PLL */ + cci_write(sensor->regmap, MT9M113_CLOCKS_CONTROL, 0x00FF, &ret); + cci_write(sensor->regmap, MT9M113_STANDBY_CONTROL, + MT9M113_STANDBY_CONTROL_ACTIVE, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x2145, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x2145, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x2145, &ret); + cci_write(sensor->regmap, MT9M113_PLL_DIVIDERS, 0x0114, &ret); + cci_write(sensor->regmap, MT9M113_PLL_P_DIVIDERS, 0x00F1, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x2545, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x2547, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x3447, &ret); + if (ret < 0) + goto error_clock; + msleep(20); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x3047, &ret); + cci_write(sensor->regmap, MT9M113_PLL_CONTROL, 0x3046, &ret); + cci_write(sensor->regmap, MT9M113_RESET_AND_MISC_CONTROL, 0x0218, &ret); + /* Keep the MCU active (do not enter standby 0x002A) after power-on. */ + cci_write(sensor->regmap, MT9M113_STANDBY_CONTROL, + MT9M113_STANDBY_CONTROL_ACTIVE, &ret); + if (ret < 0) + goto error_clock; + msleep(50); + + /* Configure OFIFO */ + cci_write(sensor->regmap, MT9M113_OFIFO_CONTROL_STATUS, 0x0003, &ret); + if (ret < 0) + goto error_clock; + } + + return 0; + +error_clock: + clk_disable_unprepare(sensor->clk); +error_regulator: + if (sensor->powerdown) + gpiod_set_value_cansleep(sensor->powerdown, 1); + else if (sensor->reset) + gpiod_set_value_cansleep(sensor->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); + return ret; +} + +static void mt9m113_power_off(struct mt9m113 *sensor) +{ + /* + * Symmetric with mt9m113_power_on(): hold the chip in reset, then + * gate the clock and drop the supplies so the enable/disable refcount + * stays balanced across runtime-PM cycles and the MCU is fully reset. + * + * Prefer the powerdown GPIO when available; fall back to driving + * RESET_BAR active so it is not left floating after VDD drops on + * boards that only wire reset-gpios. Mirror the settling gaps from + * power_on so the MCU sees a clean down-then-up sequence across + * runtime-PM cycles. + */ + if (sensor->powerdown) + gpiod_set_value_cansleep(sensor->powerdown, 1); + else if (sensor->reset) + gpiod_set_value_cansleep(sensor->reset, 1); + usleep_range(1000, 2000); + clk_disable_unprepare(sensor->clk); + usleep_range(1000, 2000); + regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), + sensor->supplies); +} + +static int mt9m113_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd =3D dev_get_drvdata(dev); + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + int ret; + + /* + * Always power-cycle and re-initialise on resume, including when a + * powerdown GPIO is present. The MT9M113 MCU must be hardware-reset + * (powerdown + clock) and re-initialised on each session, otherwise a + * wedged MCU (SEQ_CMD/0xA103 stuck) is never recovered and only a + * physical power cycle helps - matching the legacy vendor kernel per-ope= n behaviour. + */ + ret =3D mt9m113_power_on(sensor); + if (ret) + return ret; + + ret =3D mt9m113_sensor_init(sensor); + if (ret) { + /* + * sensor_init() can fail (-ETIMEDOUT on an MCU lockup, I2C + * errors); on that path the PM core reverts runtime_status to + * SUSPENDED without touching driver-side state, so the + * clk/regulator refcounts taken in power_on() would otherwise + * leak and the next resume would double-enable them. + */ + mt9m113_power_off(sensor); + return ret; + } + + return 0; +} + +static int mt9m113_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd =3D dev_get_drvdata(dev); + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + + /* Power down (powerdown GPIO + clock off) so the MCU is reset next resum= e. */ + mt9m113_power_off(sensor); + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(mt9m113_pm_ops, + mt9m113_runtime_suspend, + mt9m113_runtime_resume, + NULL); + +/* -----------------------------------------------------------------------= ------ + * Probe & Remove + */ + +static int mt9m113_identify(struct mt9m113 *sensor) +{ + u64 value; + int ret; + + ret =3D cci_read(sensor->regmap, MT9M113_CHIP_ID, &value, NULL); + if (ret) { + dev_err(&sensor->client->dev, "Failed to read chip ID\n"); + return -ENXIO; + } + + if (value !=3D MT9M113_CHIP_ID_VALUE) { + dev_err(&sensor->client->dev, + "Invalid chip ID 0x%04llx (expected 0x%04x)\n", + value, MT9M113_CHIP_ID_VALUE); + return -ENXIO; + } + + dev_dbg(&sensor->client->dev, "MT9M113 detected (ID 0x%04llx)\n", + value); + return 0; +} + +static int mt9m113_parse_dt(struct mt9m113 *sensor) +{ + struct fwnode_handle *fwnode =3D dev_fwnode(&sensor->client->dev); + struct fwnode_handle *ep; + int ret; + + ep =3D fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -EINVAL; + + sensor->bus_cfg.bus_type =3D V4L2_MBUS_UNKNOWN; + ret =3D v4l2_fwnode_endpoint_alloc_parse(ep, &sensor->bus_cfg); + fwnode_handle_put(ep); + if (ret < 0) + return ret; + + if (sensor->bus_cfg.bus_type !=3D V4L2_MBUS_CSI2_DPHY) { + dev_err(&sensor->client->dev, "Unsupported bus type %d\n", + sensor->bus_cfg.bus_type); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return -EINVAL; + } + + if (sensor->bus_cfg.nr_of_link_frequencies < 1) { + dev_err(&sensor->client->dev, "no link-frequencies specified\n"); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return -EINVAL; + } + + /* + * data-lanes is required by the binding, but defend against a DTS that + * passes dt_binding_check yet leaves num_data_lanes at zero: the pixel + * rate is derived from it and a zero would be programmed into + * V4L2_CID_PIXEL_RATE, which receivers like camss use to size the + * CSI-2 link budget. + */ + if (sensor->bus_cfg.bus.mipi_csi2.num_data_lanes < 1) { + dev_err(&sensor->client->dev, "data-lanes missing or zero\n"); + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return -EINVAL; + } + + return 0; +} + +static int mt9m113_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct mt9m113 *sensor; + int ret; + + sensor =3D devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->client =3D client; + + sensor->regmap =3D devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) + return PTR_ERR(sensor->regmap); + + ret =3D mt9m113_parse_dt(sensor); + if (ret < 0) + return ret; + + sensor->clk =3D devm_clk_get(dev, NULL); + if (IS_ERR(sensor->clk)) { + ret =3D PTR_ERR(sensor->clk); + goto error_ep_free; + } + + sensor->reset =3D devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(sensor->reset)) { + ret =3D PTR_ERR(sensor->reset); + goto error_ep_free; + } + + sensor->powerdown =3D devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->powerdown)) { + ret =3D PTR_ERR(sensor->powerdown); + goto error_ep_free; + } + + sensor->supplies[0].supply =3D "vddio"; + sensor->supplies[1].supply =3D "vdd"; + sensor->supplies[2].supply =3D "vaa"; + + ret =3D devm_regulator_bulk_get(dev, ARRAY_SIZE(sensor->supplies), + sensor->supplies); + if (ret < 0) + goto error_ep_free; + + /* Power on and identify */ + ret =3D mt9m113_power_on(sensor); + if (ret < 0) + goto error_ep_free; + + ret =3D mt9m113_identify(sensor); + if (ret < 0) + goto error_power_off; + + ret =3D mt9m113_sensor_init(sensor); + if (ret < 0) + goto error_power_off; + + /* + * The CSI-2 link frequency is mandatory (enforced by the binding and + * mt9m113_parse_dt()). Derive the pixel rate from it: every supported + * source format is 16 bits/pixel on the bus and the D-PHY clocks data + * DDR, so pixel_rate =3D link_freq * 2 * lanes / bpp. + */ + sensor->link_freq =3D sensor->bus_cfg.link_frequencies[0]; + sensor->pixrate =3D div_u64(2ULL * sensor->link_freq * + sensor->bus_cfg.bus.mipi_csi2.num_data_lanes, + 16); + + dev_dbg(dev, "MT9M113: link freq %lld Hz, pixel rate %u Hz\n", + sensor->link_freq, sensor->pixrate); + + /* Initialize Pixel Array subdev */ + v4l2_subdev_init(&sensor->pa.sd, &mt9m113_pa_ops); + sensor->pa.sd.internal_ops =3D &mt9m113_pa_internal_ops; + v4l2_i2c_subdev_set_name(&sensor->pa.sd, client, NULL, " pixel array"); + sensor->pa.sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pa.sd.owner =3D THIS_MODULE; + sensor->pa.sd.dev =3D dev; + v4l2_set_subdevdata(&sensor->pa.sd, client); + sensor->pa.sd.entity.function =3D MEDIA_ENT_F_CAM_SENSOR; + sensor->pa.pad.flags =3D MEDIA_PAD_FL_SOURCE; + ret =3D media_entity_pads_init(&sensor->pa.sd.entity, 1, &sensor->pa.pad); + if (ret < 0) + goto error_pa_entity; + + /* + * LINK_FREQ and PIXEL_RATE live on the pixel-array subdev because + * receivers walk the media graph upstream until they reach the + * MEDIA_ENT_F_CAM_SENSOR entity and read these controls from that + * subdev's handler (see camss_find_sensor_pad() / v4l2_get_link_freq() + * in drivers/media/platform/qcom/camss/camss.c). The IFP is a + * MEDIA_ENT_F_PROC_VIDEO_ISP and the walk does not stop there, so + * controls on the IFP alone are invisible to the receiver and the + * pipeline fails at CSIPHY stream-on with -EINVAL. + */ + v4l2_ctrl_handler_init(&sensor->pa.hdl, 2); + { + struct v4l2_ctrl *link_freq_ctrl; + + link_freq_ctrl =3D v4l2_ctrl_new_int_menu(&sensor->pa.hdl, NULL, + V4L2_CID_LINK_FREQ, + sensor->bus_cfg.nr_of_link_frequencies - 1, 0, + sensor->bus_cfg.link_frequencies); + if (link_freq_ctrl) + link_freq_ctrl->flags |=3D V4L2_CTRL_FLAG_READ_ONLY; + } + v4l2_ctrl_new_std(&sensor->pa.hdl, NULL, V4L2_CID_PIXEL_RATE, + sensor->pixrate, sensor->pixrate, 1, sensor->pixrate); + if (sensor->pa.hdl.error) { + ret =3D sensor->pa.hdl.error; + goto error_pa_handler; + } + sensor->pa.sd.ctrl_handler =3D &sensor->pa.hdl; + sensor->pa.sd.state_lock =3D sensor->pa.hdl.lock; + + ret =3D v4l2_subdev_init_finalize(&sensor->pa.sd); + if (ret < 0) + goto error_pa_handler; + + /* Initialize IFP subdev */ + v4l2_i2c_subdev_init(&sensor->ifp.sd, client, &mt9m113_ifp_ops); + v4l2_i2c_subdev_set_name(&sensor->ifp.sd, client, NULL, " ifp"); + sensor->ifp.sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->ifp.sd.internal_ops =3D &mt9m113_ifp_internal_ops; + sensor->ifp.sd.entity.function =3D MEDIA_ENT_F_PROC_VIDEO_ISP; + sensor->ifp.pads[0].flags =3D MEDIA_PAD_FL_SINK; + sensor->ifp.pads[1].flags =3D MEDIA_PAD_FL_SOURCE; + ret =3D media_entity_pads_init(&sensor->ifp.sd.entity, 2, sensor->ifp.pad= s); + if (ret < 0) + goto error_ifp_entity; + + /* Initialize controls on IFP */ + v4l2_ctrl_handler_init(&sensor->ifp.hdl, 10); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + /* COLORFX: mask out unsupported effects 4-12 (EMBOSS through SILHOUETTE)= */ + v4l2_ctrl_new_std_menu(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_COLORFX, + V4L2_COLORFX_SOLARIZATION, 0x1ff0, + V4L2_COLORFX_NONE); + v4l2_ctrl_new_std_menu(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std_menu(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 1000, 1, 100); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 127, 1, 32); + v4l2_ctrl_new_std(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + v4l2_ctrl_new_std_menu_items(&sensor->ifp.hdl, &mt9m113_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(mt9m113_test_pattern_menu) - 1, + 0, 0, mt9m113_test_pattern_menu); + + if (sensor->ifp.hdl.error) { + ret =3D sensor->ifp.hdl.error; + goto error_ifp_handler; + } + + sensor->ifp.sd.ctrl_handler =3D &sensor->ifp.hdl; + sensor->ifp.sd.state_lock =3D sensor->ifp.hdl.lock; + + ret =3D v4l2_subdev_init_finalize(&sensor->ifp.sd); + if (ret < 0) + goto error_ifp_handler; + + /* + * Enable runtime PM. Configure autosuspend before enabling the + * runtime PM core so a put issued between enable and use_autosuspend + * cannot race with autosuspend being unconfigured. The 2 s autosuspend + * delay amortises the unconditional power-cycle + full init-table + * replay in runtime_resume over typical preview/snapshot sequences: + * the cold init is load-bearing for MCU-wedge recovery so it cannot + * be conditionalised on health, only amortised. + */ + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + /* Register only the IFP - PA will be registered in ifp_registered callba= ck */ + ret =3D v4l2_async_register_subdev(&sensor->ifp.sd); + if (ret < 0) + goto error_pm; + + pm_runtime_put_autosuspend(dev); + + dev_dbg(dev, "MT9M113 driver with IFP sub-device initialized\n"); + return 0; + +error_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_noidle(dev); +error_ifp_handler: + v4l2_subdev_cleanup(&sensor->ifp.sd); + v4l2_ctrl_handler_free(&sensor->ifp.hdl); +error_ifp_entity: + media_entity_cleanup(&sensor->ifp.sd.entity); + v4l2_subdev_cleanup(&sensor->pa.sd); +error_pa_handler: + v4l2_ctrl_handler_free(&sensor->pa.hdl); +error_pa_entity: + media_entity_cleanup(&sensor->pa.sd.entity); +error_power_off: + mt9m113_power_off(sensor); +error_ep_free: + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + return ret; +} + +static void mt9m113_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct mt9m113 *sensor =3D ifp_to_mt9m113(sd); + struct device *dev =3D &client->dev; + + v4l2_async_unregister_subdev(&sensor->ifp.sd); + v4l2_subdev_cleanup(&sensor->ifp.sd); + v4l2_ctrl_handler_free(&sensor->ifp.hdl); + media_entity_cleanup(&sensor->ifp.sd.entity); + + v4l2_subdev_cleanup(&sensor->pa.sd); + v4l2_ctrl_handler_free(&sensor->pa.hdl); + media_entity_cleanup(&sensor->pa.sd.entity); + + v4l2_fwnode_endpoint_free(&sensor->bus_cfg); + + pm_runtime_disable(dev); + pm_runtime_dont_use_autosuspend(dev); + if (!pm_runtime_status_suspended(dev)) + mt9m113_power_off(sensor); + pm_runtime_set_suspended(dev); +} + +static const struct of_device_id mt9m113_of_ids[] =3D { + { .compatible =3D "aptina,mt9m113" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mt9m113_of_ids); + +static const struct i2c_device_id mt9m113_id[] =3D { + { "mt9m113" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, mt9m113_id); + +static struct i2c_driver mt9m113_driver =3D { + .driver =3D { + .name =3D "mt9m113", + .pm =3D pm_ptr(&mt9m113_pm_ops), + .of_match_table =3D mt9m113_of_ids, + }, + .probe =3D mt9m113_probe, + .remove =3D mt9m113_remove, + .id_table =3D mt9m113_id, +}; + +module_i2c_driver(mt9m113_driver); + +MODULE_AUTHOR("Herman van Hazendonk "); +MODULE_DESCRIPTION("Aptina MT9M113 Sensor Driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0