From nobody Sat Oct 4 12:46:57 2025 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 351BD1F4615; Sat, 16 Aug 2025 05:55:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755323717; cv=none; b=kAx0+dgcNkvqek2RFcBVatiiqlcq6kXOIr9MpxFggibb4XPzF+2oBISyGFMbKlB3k1JtWhvDoP4InA7z2rKlULludPOjcmsyDaNpHBWweRL1T+TVrMkRITtv3I517ME5QwufEtIxYi54ASOC30Er2t6bQz28swPQ68iAPJoH47I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755323717; c=relaxed/simple; bh=1V+8kS6GZTnk9SvLNX5osVSVF7aOGssqIZgBwqR08io=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=XPFdol7bCGUcfnQRx3miKnYTmo35nYNZDxbkFGVJuA01drx/eODcnoiauiERCLFR3jHfAxxmvEITF7kwZGd97wDCMczLVvtpItwFQVCx61iJ7bBkiAQCc56bCEZ3v0B0ScK5DFzMv1OPc6z554ZtNNFCzAcbHJGYJ+CncS6H53M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=willwhang.com; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.210.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=willwhang.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-76e2e60433eso2019198b3a.0; Fri, 15 Aug 2025 22:55:15 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755323715; x=1755928515; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=1DxrGkBUPFLX3W6oqWKXYIoIc1TSctUbLJzEW+JwYuM=; b=sRoRm1acZAtIUkZHlCqDgQOChycw/YvFmm80xcZfWCTclJhqsss9DS5NTM/57UEFSS 4H+xTR4/xisDY4bgQ/fMD1fx6o8StS3f8nUl/0jzirATWpG9DYuNp+fKK2WsvpOUoO4U CxTuNaeBTAjVtOqOYn7zUMWL/5Wc8h6RXCxpVhBidYysX/i9j8uYEkdQk6TfCfmlcQrf /RNCmak9zIXlXRPX7glg31gbAaib6Uf2dYrKBF/8idJyKOFIOI3If4ZGfE7fq0Gz9u6r RHpscsHklES+EqwIl0AdQWKLlbi4J69oaIPB2E74yohUNy81yCWaTEYYvKfOtx+qG56a vHAA== X-Forwarded-Encrypted: i=1; AJvYcCUmF7lTZJ3JnHcpKg3yh2eVsARLCBhV7xvPhVrjTayo0EAVnOnW//3vby961wBqqmDVLBxeuGpDY5KM@vger.kernel.org, AJvYcCWUxTh8AuOf9mqsDCreH9n9HfGRF1nSyQ0C0+MbI506S1UfVAlConUolCQb/PlrgqH1pbxzXdErLX7uGbdH@vger.kernel.org X-Gm-Message-State: AOJu0YyhOXEhdO2MZeLSd7AzFJ4eXCrQC4B1WVzYxzF4/qz/0LHyNHo3 9srUHwLx15OkhgNyQggqRLY9K4e7zryaBSJ1/f+ml6gB+US5cZSBl1Tx X-Gm-Gg: ASbGnctOCQJ9CHySAeG9sWMTnwNaRoP2XllH1/6UPvwBgq4wmFMAKSkBzig10fyBeAR RHsZnX03phgbc8nr/DajTk9+xe/Dm0d/NbflgoIFuuYBn8VhFo4ZXUINdG8pFBo7DWnuE14QeKC 8y+A1glmDmauyoxRUy+ksALAXGR9UzYY6OFqyqt1kHpkNGv5PQFxxDSbPDP4KdDQKKBFurmm1mo wzJLzh5jVY6tTJMqSHAuwhqFyfz4opvvCHRhjM4Q3TIqH+8emX8HIBB4pr17bj42j9UYN47cE7I fBcOF44p1+/GcjyGAFPriggpa5NYVIdsKcEx7Ppn4rnGw7D7U8iIqxRbAIQwsu2BhnxO9dKbE95 4gv47OA5slPo6h9ENf9rgvGIoGtpNIiXaJWmVGqWcMd/ahSKZ4JJc+6xmKVz+bj0hAnlF09QVmX UpfQ== X-Google-Smtp-Source: AGHT+IFyHiS49/D/iPWxilJTBcbUDkiSVDAHtOu1z/F4AU+9dvHfFydYg4BN9Zvvq7VBtTgF8Ak0aQ== X-Received: by 2002:a05:6a00:1893:b0:76b:e805:30e4 with SMTP id d2e1a72fcca58-76e44838e26mr7106437b3a.24.1755323715430; Fri, 15 Aug 2025 22:55:15 -0700 (PDT) Received: from localhost.localdomain (c-24-4-34-163.hsd1.ca.comcast.net. [24.4.34.163]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-76e452663f0sm2402730b3a.21.2025.08.15.22.55.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 Aug 2025 22:55:15 -0700 (PDT) From: Will Whang To: Will Whang , 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 Subject: [PATCH v3 1/2] dt-bindings: media: Add Sony IMX585 CMOS image sensor Date: Sat, 16 Aug 2025 06:54:31 +0100 Message-Id: <20250816055432.131912-2-will@willwhang.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250816055432.131912-1-will@willwhang.com> References: <20250816055432.131912-1-will@willwhang.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Document the devicetree binding for the Sony IMX585. The schema covers the CSI-2 data-lanes, and the synchronization mode properties used by the driver. Compatible strings are now using the full parts number given the review comments from patch v2. Signed-off-by: Will Whang --- .../bindings/media/i2c/sony,imx585.yaml | 114 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx585= .yaml diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx585.yaml b= /Documentation/devicetree/bindings/media/i2c/sony,imx585.yaml new file mode 100644 index 000000000..b1a4f447f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx585.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx585.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX585 CMOS image sensor + +maintainers: + - Will Whang + +description: + IMX585 sensor is a Sony CMOS sensor with 4K and FHD outputs. + The driver supports both imx585-aaqj1 and imx585-aamj1. + +properties: + compatible: + enum: + - sony,imx585-aamj1 + - sony,imx585-aaqj1 + + reg: + maxItems: 1 + + clocks: + description: Clock frequency 74.25MHz, 37.125MHz, 72MHz, 27MHz, 24MHz + maxItems: 1 + + vana-supply: + description: Analog power supply (3.3V) + + vddl-supply: + description: Interface power supply (1.8V) + + vdig-supply: + description: Digital power supply (1.1V) + + reset-gpios: + description: Sensor reset (XCLR) GPIO + maxItems: 1 + + sony,sync-mode: + description: | + Select the sensor synchronisation mode. + - internal-leader (sensor drives XVS/XHS) [default] + - internal-follower (internal clock, external XVS input) + - external (sensor follows external XVS/XHS) + enum: + - internal-leader + - internal-follower + - external + default: internal-leader + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - clocks + - port + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + imx585@1a { + compatible =3D "sony,imx585-aaqj1"; + reg =3D <0x1a>; + clocks =3D <&imx585_clk>; + + assigned-clocks =3D <&imx585_clk>; + assigned-clock-rates =3D <24000000>; + + vana-supply =3D <&camera_vadd_3v3>; + vdig-supply =3D <&camera_vdd1_1v8>; + vddl-supply =3D <&camera_vdd2_1v1>; + + port { + imx585: endpoint { + remote-endpoint =3D <&cam>; + data-lanes =3D <1 2 3 4>; + link-frequencies =3D /bits/ 64 <720000000>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index fe168477c..ec7a6e29a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23535,6 +23535,12 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml F: drivers/media/i2c/imx415.c =20 +SONY IMX585 SENSOR DRIVER +M: Will Whang +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/sony,imx585.yaml + SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky M: Alex Dubov --=20 2.39.5 From nobody Sat Oct 4 12:46:57 2025 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DEC351FE470; Sat, 16 Aug 2025 05:55:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755323721; cv=none; b=IVs3lG3FPXt1uTBb0E13Pk64M39+DK6VnFV33wSTG7KOOEh+BKi+PQw1IYAtMp8n678fTq2F+1urPE5FA3F/nHllntDZx+pgvZfLHOvPbT08hfPEo65GJ0ODLPcGCm84aOrbjgGiwKyYNmNYa5HKVLteGSsy74tdY6GQt0RudmU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755323721; c=relaxed/simple; bh=M5TJ7TlbGTUShk8IBFa1+On7LqYSeZWfRAYPDg4DLa8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Gvs0eFarQlJP/bQIOJSQKAZs/0RudzK2s7uLCT3vuOxA51kkk2bvw0s2mVkzmAAJiHYuGznDAPwlTdv9HfFAHnWKYQ/k7LY3OrPoqxj9cJJnq+ymTvgF1GwSwxHuqTFGomqBFLVPM5jqK//Dip88mAzkfYMFWH0+ppN/8O38kWk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=willwhang.com; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.210.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=willwhang.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-76e43ee62b8so795391b3a.2; Fri, 15 Aug 2025 22:55:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755323717; x=1755928517; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=kJYm3Cr3reoMXzrES29WNhLis9WF72q6EDoBjLj3Tg8=; b=r9gKBbkCfQIAjTSUAiuq5x/04xEwv2nX4ONypIGdmg5i+Ok12IZLCIROw+mdG46T5k clLbJO4cEgw6zHnGB6X/G4tQzdY6xi0ieHcIstGlt++U1IVUt0ar3CDgoFsjC33Bqj4Y u+rdRvx+KBnXgMqWWbBjvC7nngTi4Zv3dOOLqnqg/LsM5SDygrOyZR9Z1np7yOjtS70a kEV74uNVM0s+lVhoBHlGLShPrkdwu99a537SLSSXU6Wrii96p27oEAc9KhALzT4C2Is3 xddX/E1EWzHQVr4PdhLAVhuAF/L95c83ATPUoOyG2Iee1Prnp6v3WqP8xln08N4lzlMg Bq9g== X-Forwarded-Encrypted: i=1; AJvYcCVSeZgx0FgC28UL4K8AhqAprETTAbMFtk1imswSl9bsZPKOmvzf4ESxfd+qCxt4qPNiKoerlbyz1fTz/unA@vger.kernel.org, AJvYcCXvDiTttTu2+S+0BYyHmo/MPcRKjPal0u3oLm2t5uNthYTEwFZVGCu/0VVBimkMKhi20GMVYFE5y+S2@vger.kernel.org X-Gm-Message-State: AOJu0YyivBYe5I+VBS46TD7kZ1OLfE7oniVBNbDc0vklAGPMskRno7nZ /s+wSK+XhVIgMEvINgfJnmzvi+x5+Z/YKfh0GmtVUBi3Q908dYnPw0pq X-Gm-Gg: ASbGncsOnS1cLu3BII4dO8n+TvX/pvOmpsmNl7p0Bm5yh0xhReGNXGPlmFhHxBWITbk 6fa4pSutedtPpaWwaiyaJaLwRF8Kco4oTSCOLgtZqhAmNFGsjOXxkrf2b3nla2n7G6Nb9P2gDL2 3MCUJT5Ni2WYuXwYFJWR+wVUCD6vjsjhva1Lft1dIuMdmmcYXi1sSMNgKQYdNzfcpCuYVQrEP74 6l/Kvy0/9Y8hX42IikKxNnEDqToZQerjH7D22tE77Xlo6k39IUtNQVDCbPQ9sfUJDgCy6kbwcg1 aoVYi40yQOemyHSNmV3eWhPibv3W/P1pTn5Iz29N5b+do6wTYqte3ISPFb2KeGg45XI0eg74YeL eVWEg3SYn3nROPvT9nyRKzxXgkBkQr6aKDvAAfCrKzKOBPRo86910aVrxli65uURJ6qQ= X-Google-Smtp-Source: AGHT+IH1W7Z+sTqqDiAqMJNpOSiDHFjnM3umpNCiMJBo0BZiEpKX3PSyn8zwm2gMx53Nn0wRWRGMfg== X-Received: by 2002:a05:6a20:244f:b0:240:2234:6860 with SMTP id adf61e73a8af0-240d2fed925mr7312025637.32.1755323716869; Fri, 15 Aug 2025 22:55:16 -0700 (PDT) Received: from localhost.localdomain (c-24-4-34-163.hsd1.ca.comcast.net. [24.4.34.163]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-76e452663f0sm2402730b3a.21.2025.08.15.22.55.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 15 Aug 2025 22:55:16 -0700 (PDT) From: Will Whang To: Will Whang , 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 Subject: [PATCH v3 2/2] media: i2c: imx585: Add Sony IMX585 image-sensor driver Date: Sat, 16 Aug 2025 06:54:32 +0100 Message-Id: <20250816055432.131912-3-will@willwhang.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250816055432.131912-1-will@willwhang.com> References: <20250816055432.131912-1-will@willwhang.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Implements support for: * 4-lane / 2-lane CSI-2 * 12-bit linear, 4K/FHD mode. * Monochrome variant. * Tested on Raspberry Pi 4/5 with 24 MHz clock. Signed-off-by: Will Whang --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 9 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx585.c | 1358 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1369 insertions(+) create mode 100644 drivers/media/i2c/imx585.c diff --git a/MAINTAINERS b/MAINTAINERS index ec7a6e29a..1abf317cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23540,6 +23540,7 @@ M: Will Whang L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/sony,imx585.yaml +F: drivers/media/i2c/imx585.c =20 SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 6237fe804..3dc3eea36 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -267,6 +267,15 @@ config VIDEO_IMX415 To compile this driver as a module, choose M here: the module will be called imx415. =20 +config VIDEO_IMX585 + tristate "Sony IMX585 sensor support" + help + This is a Video4Linux2 sensor driver for the Sony + IMX585 camera. + + To compile this driver as a module, choose M here: the + module will be called imx585. + config VIDEO_MAX9271_LIB tristate =20 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 5873d2943..887d19ca7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_VIDEO_IMX335) +=3D imx335.o obj-$(CONFIG_VIDEO_IMX355) +=3D imx355.o obj-$(CONFIG_VIDEO_IMX412) +=3D imx412.o obj-$(CONFIG_VIDEO_IMX415) +=3D imx415.o +obj-$(CONFIG_VIDEO_IMX585) +=3D imx585.o obj-$(CONFIG_VIDEO_IR_I2C) +=3D ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) +=3D isl7998x.o obj-$(CONFIG_VIDEO_KS0127) +=3D ks0127.o diff --git a/drivers/media/i2c/imx585.c b/drivers/media/i2c/imx585.c new file mode 100644 index 000000000..fb2241fa1 --- /dev/null +++ b/drivers/media/i2c/imx585.c @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX585 camera. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* -----------------------------------------------------------------------= --- + * Registers / limits + * -----------------------------------------------------------------------= --- + */ + +/* Standby or streaming mode */ +#define IMX585_REG_MODE_SELECT CCI_REG8(0x3000) +#define IMX585_MODE_STANDBY 0x01 +#define IMX585_MODE_STREAMING 0x00 +#define IMX585_STREAM_DELAY_US 25000 +#define IMX585_STREAM_DELAY_RANGE_US 1000 + +/* Initialisation delay between XCLR low->high and the moment sensor is re= ady */ +#define IMX585_XCLR_MIN_DELAY_US 500000 +#define IMX585_XCLR_DELAY_RANGE_US 1000 + +/* Leader mode and XVS/XHS direction */ +#define IMX585_REG_XMSTA CCI_REG8(0x3002) +#define IMX585_REG_XXS_DRV CCI_REG8(0x30a6) +#define IMX585_REG_EXTMODE CCI_REG8(0x30ce) +#define IMX585_REG_XXS_OUTSEL CCI_REG8(0x30a4) + +/* XVS pulse length, 2^n H with n=3D0~3 */ +#define IMX585_REG_XVSLNG CCI_REG8(0x30cc) +/* XHS pulse length, 16*(2^n) Clock with n=3D0~3 */ +#define IMX585_REG_XHSLNG CCI_REG8(0x30cd) + +/* Clock selection */ +#define IMX585_INCK_SEL CCI_REG8(0x3014) + +/* Link speed selector */ +#define IMX585_DATARATE_SEL CCI_REG8(0x3015) + +/* BIN mode: 0x01 mono bin, 0x00 color */ +#define IMX585_BIN_MODE CCI_REG8(0x3019) + +/* Lane Count */ +#define IMX585_LANEMODE CCI_REG8(0x3040) + +/* VMAX internal VBLANK */ +#define IMX585_REG_VMAX CCI_REG24_LE(0x3028) +#define IMX585_VMAX_MAX 0xfffff +#define IMX585_VMAX_DEFAULT 2250 + +/* HMAX internal HBLANK */ +#define IMX585_REG_HMAX CCI_REG16_LE(0x302c) +#define IMX585_HMAX_MAX 0xffff + +/* SHR internal (coarse exposure) */ +#define IMX585_REG_SHR CCI_REG24_LE(0x3050) +#define IMX585_SHR_MIN 8 +#define IMX585_SHR_MIN_HDR 10 +#define IMX585_SHR_MAX 0xfffff + +/* Exposure control (lines) */ +#define IMX585_EXPOSURE_MIN 2 +#define IMX585_EXPOSURE_STEP 1 +#define IMX585_EXPOSURE_DEFAULT 1000 +#define IMX585_EXPOSURE_MAX 49865 + +/* Black level control */ +#define IMX585_REG_BLKLEVEL CCI_REG16_LE(0x30dc) +#define IMX585_BLKLEVEL_DEFAULT 50 + +/* Digital Clamp */ +#define IMX585_REG_DIGITAL_CLAMP CCI_REG8(0x3458) + +/* Analog gain control */ +#define IMX585_REG_ANALOG_GAIN CCI_REG16_LE(0x306c) +#define IMX585_REG_FDG_SEL0 CCI_REG8(0x3030) +#define IMX585_ANA_GAIN_MIN 0 +#define IMX585_ANA_GAIN_MAX 240 +#define IMX585_ANA_GAIN_STEP 1 +#define IMX585_ANA_GAIN_DEFAULT 0 + +/* Flip */ +#define IMX585_FLIP_WINMODEH CCI_REG8(0x3020) +#define IMX585_FLIP_WINMODEV CCI_REG8(0x3021) + +/* Pixel rate helper (sensor line clock proxy used below) */ +#define IMX585_PIXEL_RATE 74250000U + +/* Native and active array */ +#define IMX585_NATIVE_WIDTH 3856U +#define IMX585_NATIVE_HEIGHT 2180U +#define IMX585_PIXEL_ARRAY_LEFT 8U +#define IMX585_PIXEL_ARRAY_TOP 8U +#define IMX585_PIXEL_ARRAY_WIDTH 3840U +#define IMX585_PIXEL_ARRAY_HEIGHT 2160U + +/* Link frequency setup */ +enum { + IMX585_LINK_FREQ_297MHZ, /* 594 Mbps/lane */ + IMX585_LINK_FREQ_360MHZ, /* 720 Mbps/lane */ + IMX585_LINK_FREQ_445MHZ, /* 891 Mbps/lane */ + IMX585_LINK_FREQ_594MHZ, /* 1188 Mbps/lane */ + IMX585_LINK_FREQ_720MHZ, /* 1440 Mbps/lane */ + IMX585_LINK_FREQ_891MHZ, /* 1782 Mbps/lane */ + IMX585_LINK_FREQ_1039MHZ, /* 2079 Mbps/lane */ + IMX585_LINK_FREQ_1188MHZ, /* 2376 Mbps/lane */ +}; + +static const u8 link_freqs_reg_value[] =3D { + [IMX585_LINK_FREQ_297MHZ] =3D 0x07, + [IMX585_LINK_FREQ_360MHZ] =3D 0x06, + [IMX585_LINK_FREQ_445MHZ] =3D 0x05, + [IMX585_LINK_FREQ_594MHZ] =3D 0x04, + [IMX585_LINK_FREQ_720MHZ] =3D 0x03, + [IMX585_LINK_FREQ_891MHZ] =3D 0x02, + [IMX585_LINK_FREQ_1039MHZ] =3D 0x01, + [IMX585_LINK_FREQ_1188MHZ] =3D 0x00, +}; + +static const u64 link_freqs[] =3D { + [IMX585_LINK_FREQ_297MHZ] =3D 297000000ULL, + [IMX585_LINK_FREQ_360MHZ] =3D 360000000ULL, + [IMX585_LINK_FREQ_445MHZ] =3D 445500000ULL, + [IMX585_LINK_FREQ_594MHZ] =3D 594000000ULL, + [IMX585_LINK_FREQ_720MHZ] =3D 720000000ULL, + [IMX585_LINK_FREQ_891MHZ] =3D 891000000ULL, + [IMX585_LINK_FREQ_1039MHZ] =3D 1039500000ULL, + [IMX585_LINK_FREQ_1188MHZ] =3D 1188000000ULL, +}; + +/* min HMAX for 4-lane 4K full res mode, x2 for 2-lane, /2 for FHD */ +static const u16 HMAX_table_4lane_4K[] =3D { + [IMX585_LINK_FREQ_297MHZ] =3D 1584, + [IMX585_LINK_FREQ_360MHZ] =3D 1320, + [IMX585_LINK_FREQ_445MHZ] =3D 1100, + [IMX585_LINK_FREQ_594MHZ] =3D 792, + [IMX585_LINK_FREQ_720MHZ] =3D 660, + [IMX585_LINK_FREQ_891MHZ] =3D 550, + [IMX585_LINK_FREQ_1039MHZ] =3D 440, + [IMX585_LINK_FREQ_1188MHZ] =3D 396, +}; + +struct imx585_inck_cfg { + u32 xclk_hz; + u8 inck_sel; +}; + +static const struct imx585_inck_cfg imx585_inck_table[] =3D { + { 74250000, 0x00 }, + { 37125000, 0x01 }, + { 72000000, 0x02 }, + { 27000000, 0x03 }, + { 24000000, 0x04 }, +}; + +enum { + SYNC_INT_LEADER, + SYNC_INT_FOLLOWER, + SYNC_EXTERNAL, +}; + +static const char * const sync_mode_menu[] =3D { + "Internal Sync Leader Mode", + "External Sync Leader Mode", + "Follower Mode", +}; + +/* Mode description */ +struct imx585_mode { + unsigned int width; + unsigned int height; + + u8 hmax_div; /* per-mode scaling of min HMAX */ + u16 min_hmax; /* computed at runtime */ + u32 min_vmax; /* computed at runtime (fits 20-bit) */ + + struct v4l2_rect crop; + + struct { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; + } reg_list; +}; + +/* -----------------------------------------------------------------------= --- + * Register tables + * -----------------------------------------------------------------------= --- + */ + +static const struct cci_reg_sequence common_regs[] =3D { + { CCI_REG8(0x3002), 0x01 }, + { CCI_REG8(0x3069), 0x00 }, + { CCI_REG8(0x3074), 0x64 }, + { CCI_REG8(0x30d5), 0x04 }, /* DIG_CLP_VSTART */ + { CCI_REG8(0x3030), 0x00 }, /* FDG_SEL0 LCG (HCG=3D0x01) */ + { CCI_REG8(0x30a6), 0x00 }, /* XVS_DRV [1:0] Hi-Z */ + { CCI_REG8(0x3081), 0x00 }, /* EXP_GAIN reset */ + { CCI_REG8(0x303a), 0x03 }, /* Disable embedded data */ + + /* The remaining blocks are datasheet-recommended settings */ + { CCI_REG8(0x3460), 0x21 }, { CCI_REG8(0x3478), 0xa1 }, + { CCI_REG8(0x347c), 0x01 }, { CCI_REG8(0x3480), 0x01 }, + { CCI_REG8(0x3a4e), 0x14 }, { CCI_REG8(0x3a52), 0x14 }, + { CCI_REG8(0x3a56), 0x00 }, { CCI_REG8(0x3a5a), 0x00 }, + { CCI_REG8(0x3a5e), 0x00 }, { CCI_REG8(0x3a62), 0x00 }, + { CCI_REG8(0x3a6a), 0x20 }, { CCI_REG8(0x3a6c), 0x42 }, + { CCI_REG8(0x3a6e), 0xa0 }, { CCI_REG8(0x3b2c), 0x0c }, + { CCI_REG8(0x3b30), 0x1c }, { CCI_REG8(0x3b34), 0x0c }, + { CCI_REG8(0x3b38), 0x1c }, { CCI_REG8(0x3ba0), 0x0c }, + { CCI_REG8(0x3ba4), 0x1c }, { CCI_REG8(0x3ba8), 0x0c }, + { CCI_REG8(0x3bac), 0x1c }, { CCI_REG8(0x3d3c), 0x11 }, + { CCI_REG8(0x3d46), 0x0b }, { CCI_REG8(0x3de0), 0x3f }, + { CCI_REG8(0x3de1), 0x08 }, { CCI_REG8(0x3e14), 0x87 }, + { CCI_REG8(0x3e16), 0x91 }, { CCI_REG8(0x3e18), 0x91 }, + { CCI_REG8(0x3e1a), 0x87 }, { CCI_REG8(0x3e1c), 0x78 }, + { CCI_REG8(0x3e1e), 0x50 }, { CCI_REG8(0x3e20), 0x50 }, + { CCI_REG8(0x3e22), 0x50 }, { CCI_REG8(0x3e24), 0x87 }, + { CCI_REG8(0x3e26), 0x91 }, { CCI_REG8(0x3e28), 0x91 }, + { CCI_REG8(0x3e2a), 0x87 }, { CCI_REG8(0x3e2c), 0x78 }, + { CCI_REG8(0x3e2e), 0x50 }, { CCI_REG8(0x3e30), 0x50 }, + { CCI_REG8(0x3e32), 0x50 }, { CCI_REG8(0x3e34), 0x87 }, + { CCI_REG8(0x3e36), 0x91 }, { CCI_REG8(0x3e38), 0x91 }, + { CCI_REG8(0x3e3a), 0x87 }, { CCI_REG8(0x3e3c), 0x78 }, + { CCI_REG8(0x3e3e), 0x50 }, { CCI_REG8(0x3e40), 0x50 }, + { CCI_REG8(0x3e42), 0x50 }, { CCI_REG8(0x4054), 0x64 }, + { CCI_REG8(0x4148), 0xfe }, { CCI_REG8(0x4149), 0x05 }, + { CCI_REG8(0x414a), 0xff }, { CCI_REG8(0x414b), 0x05 }, + { CCI_REG8(0x420a), 0x03 }, { CCI_REG8(0x4231), 0x08 }, + { CCI_REG8(0x423d), 0x9c }, { CCI_REG8(0x4242), 0xb4 }, + { CCI_REG8(0x4246), 0xb4 }, { CCI_REG8(0x424e), 0xb4 }, + { CCI_REG8(0x425c), 0xb4 }, { CCI_REG8(0x425e), 0xb6 }, + { CCI_REG8(0x426c), 0xb4 }, { CCI_REG8(0x426e), 0xb6 }, + { CCI_REG8(0x428c), 0xb4 }, { CCI_REG8(0x428e), 0xb6 }, + { CCI_REG8(0x4708), 0x00 }, { CCI_REG8(0x4709), 0x00 }, + { CCI_REG8(0x470a), 0xff }, { CCI_REG8(0x470b), 0x03 }, + { CCI_REG8(0x470c), 0x00 }, { CCI_REG8(0x470d), 0x00 }, + { CCI_REG8(0x470e), 0xff }, { CCI_REG8(0x470f), 0x03 }, + { CCI_REG8(0x47eb), 0x1c }, { CCI_REG8(0x47f0), 0xa6 }, + { CCI_REG8(0x47f2), 0xa6 }, { CCI_REG8(0x47f4), 0xa0 }, + { CCI_REG8(0x47f6), 0x96 }, { CCI_REG8(0x4808), 0xa6 }, + { CCI_REG8(0x480a), 0xa6 }, { CCI_REG8(0x480c), 0xa0 }, + { CCI_REG8(0x480e), 0x96 }, { CCI_REG8(0x492c), 0xb2 }, + { CCI_REG8(0x4930), 0x03 }, { CCI_REG8(0x4932), 0x03 }, + { CCI_REG8(0x4936), 0x5b }, { CCI_REG8(0x4938), 0x82 }, + { CCI_REG8(0x493e), 0x23 }, { CCI_REG8(0x4ba8), 0x1c }, + { CCI_REG8(0x4ba9), 0x03 }, { CCI_REG8(0x4bac), 0x1c }, + { CCI_REG8(0x4bad), 0x1c }, { CCI_REG8(0x4bae), 0x1c }, + { CCI_REG8(0x4baf), 0x1c }, { CCI_REG8(0x4bb0), 0x1c }, + { CCI_REG8(0x4bb1), 0x1c }, { CCI_REG8(0x4bb2), 0x1c }, + { CCI_REG8(0x4bb3), 0x1c }, { CCI_REG8(0x4bb4), 0x1c }, + { CCI_REG8(0x4bb8), 0x03 }, { CCI_REG8(0x4bb9), 0x03 }, + { CCI_REG8(0x4bba), 0x03 }, { CCI_REG8(0x4bbb), 0x03 }, + { CCI_REG8(0x4bbc), 0x03 }, { CCI_REG8(0x4bbd), 0x03 }, + { CCI_REG8(0x4bbe), 0x03 }, { CCI_REG8(0x4bbf), 0x03 }, + { CCI_REG8(0x4bc0), 0x03 }, { CCI_REG8(0x4c14), 0x87 }, + { CCI_REG8(0x4c16), 0x91 }, { CCI_REG8(0x4c18), 0x91 }, + { CCI_REG8(0x4c1a), 0x87 }, { CCI_REG8(0x4c1c), 0x78 }, + { CCI_REG8(0x4c1e), 0x50 }, { CCI_REG8(0x4c20), 0x50 }, + { CCI_REG8(0x4c22), 0x50 }, { CCI_REG8(0x4c24), 0x87 }, + { CCI_REG8(0x4c26), 0x91 }, { CCI_REG8(0x4c28), 0x91 }, + { CCI_REG8(0x4c2a), 0x87 }, { CCI_REG8(0x4c2c), 0x78 }, + { CCI_REG8(0x4c2e), 0x50 }, { CCI_REG8(0x4c30), 0x50 }, + { CCI_REG8(0x4c32), 0x50 }, { CCI_REG8(0x4c34), 0x87 }, + { CCI_REG8(0x4c36), 0x91 }, { CCI_REG8(0x4c38), 0x91 }, + { CCI_REG8(0x4c3a), 0x87 }, { CCI_REG8(0x4c3c), 0x78 }, + { CCI_REG8(0x4c3e), 0x50 }, { CCI_REG8(0x4c40), 0x50 }, + { CCI_REG8(0x4c42), 0x50 }, { CCI_REG8(0x4d12), 0x1f }, + { CCI_REG8(0x4d13), 0x1e }, { CCI_REG8(0x4d26), 0x33 }, + { CCI_REG8(0x4e0e), 0x59 }, { CCI_REG8(0x4e14), 0x55 }, + { CCI_REG8(0x4e16), 0x59 }, { CCI_REG8(0x4e1e), 0x3b }, + { CCI_REG8(0x4e20), 0x47 }, { CCI_REG8(0x4e22), 0x54 }, + { CCI_REG8(0x4e26), 0x81 }, { CCI_REG8(0x4e2c), 0x7d }, + { CCI_REG8(0x4e2e), 0x81 }, { CCI_REG8(0x4e36), 0x63 }, + { CCI_REG8(0x4e38), 0x6f }, { CCI_REG8(0x4e3a), 0x7c }, + { CCI_REG8(0x4f3a), 0x3c }, { CCI_REG8(0x4f3c), 0x46 }, + { CCI_REG8(0x4f3e), 0x59 }, { CCI_REG8(0x4f42), 0x64 }, + { CCI_REG8(0x4f44), 0x6e }, { CCI_REG8(0x4f46), 0x81 }, + { CCI_REG8(0x4f4a), 0x82 }, { CCI_REG8(0x4f5a), 0x81 }, + { CCI_REG8(0x4f62), 0xaa }, { CCI_REG8(0x4f72), 0xa9 }, + { CCI_REG8(0x4f78), 0x36 }, { CCI_REG8(0x4f7a), 0x41 }, + { CCI_REG8(0x4f7c), 0x61 }, { CCI_REG8(0x4f7d), 0x01 }, + { CCI_REG8(0x4f7e), 0x7c }, { CCI_REG8(0x4f7f), 0x01 }, + { CCI_REG8(0x4f80), 0x77 }, { CCI_REG8(0x4f82), 0x7b }, + { CCI_REG8(0x4f88), 0x37 }, { CCI_REG8(0x4f8a), 0x40 }, + { CCI_REG8(0x4f8c), 0x62 }, { CCI_REG8(0x4f8d), 0x01 }, + { CCI_REG8(0x4f8e), 0x76 }, { CCI_REG8(0x4f8f), 0x01 }, + { CCI_REG8(0x4f90), 0x5e }, { CCI_REG8(0x4f91), 0x02 }, + { CCI_REG8(0x4f92), 0x69 }, { CCI_REG8(0x4f93), 0x02 }, + { CCI_REG8(0x4f94), 0x89 }, { CCI_REG8(0x4f95), 0x02 }, + { CCI_REG8(0x4f96), 0xa4 }, { CCI_REG8(0x4f97), 0x02 }, + { CCI_REG8(0x4f98), 0x9f }, { CCI_REG8(0x4f99), 0x02 }, + { CCI_REG8(0x4f9a), 0xa3 }, { CCI_REG8(0x4f9b), 0x02 }, + { CCI_REG8(0x4fa0), 0x5f }, { CCI_REG8(0x4fa1), 0x02 }, + { CCI_REG8(0x4fa2), 0x68 }, { CCI_REG8(0x4fa3), 0x02 }, + { CCI_REG8(0x4fa4), 0x8a }, { CCI_REG8(0x4fa5), 0x02 }, + { CCI_REG8(0x4fa6), 0x9e }, { CCI_REG8(0x4fa7), 0x02 }, + { CCI_REG8(0x519e), 0x79 }, { CCI_REG8(0x51a6), 0xa1 }, + { CCI_REG8(0x51f0), 0xac }, { CCI_REG8(0x51f2), 0xaa }, + { CCI_REG8(0x51f4), 0xa5 }, { CCI_REG8(0x51f6), 0xa0 }, + { CCI_REG8(0x5200), 0x9b }, { CCI_REG8(0x5202), 0x91 }, + { CCI_REG8(0x5204), 0x87 }, { CCI_REG8(0x5206), 0x82 }, + { CCI_REG8(0x5208), 0xac }, { CCI_REG8(0x520a), 0xaa }, + { CCI_REG8(0x520c), 0xa5 }, { CCI_REG8(0x520e), 0xa0 }, + { CCI_REG8(0x5210), 0x9b }, { CCI_REG8(0x5212), 0x91 }, + { CCI_REG8(0x5214), 0x87 }, { CCI_REG8(0x5216), 0x82 }, + { CCI_REG8(0x5218), 0xac }, { CCI_REG8(0x521a), 0xaa }, + { CCI_REG8(0x521c), 0xa5 }, { CCI_REG8(0x521e), 0xa0 }, + { CCI_REG8(0x5220), 0x9b }, { CCI_REG8(0x5222), 0x91 }, + { CCI_REG8(0x5224), 0x87 }, { CCI_REG8(0x5226), 0x82 }, + { CCI_REG8(0x301a), 0x00 }, /* WDMODE: Normal */ + { CCI_REG8(0x3024), 0x00 }, /* COMBI_EN */ + { CCI_REG8(0x3069), 0x00 }, + { CCI_REG8(0x3074), 0x64 }, + { CCI_REG8(0x3930), 0x0c }, /* DUR[15:8] (12-bit) */ + { CCI_REG8(0x3931), 0x01 }, /* DUR[7:0] (12-bit) */ + { CCI_REG8(0x3a4c), 0x39 }, { CCI_REG8(0x3a4d), 0x01 }, + { CCI_REG8(0x3a50), 0x48 }, { CCI_REG8(0x3a51), 0x01 }, + { CCI_REG8(0x3e10), 0x10 }, /* ADTHEN */ + { CCI_REG8(0x493c), 0x23 }, /* 10-bit Normal */ + { CCI_REG8(0x4940), 0x23 }, /* 12-bit Normal */ +}; + +/* All-pixel 4K, 12-bit */ +static const struct cci_reg_sequence mode_4k_regs_12bit[] =3D { + { CCI_REG8(0x301b), 0x00 }, /* ADDMODE non-binning */ + { CCI_REG8(0x3022), 0x02 }, /* ADBIT 12-bit */ + { CCI_REG8(0x3023), 0x01 }, /* MDBIT 12-bit */ + { CCI_REG8(0x30d5), 0x04 }, /* DIG_CLP_VSTART non-binning */ +}; + +/* 2x2 binned 1080p, 12-bit */ +static const struct cci_reg_sequence mode_1080_regs_12bit[] =3D { + { CCI_REG8(0x301b), 0x01 }, /* ADDMODE binning */ + { CCI_REG8(0x3022), 0x02 }, /* ADBIT 12-bit */ + { CCI_REG8(0x3023), 0x01 }, /* MDBIT 12-bit */ + { CCI_REG8(0x30d5), 0x02 }, /* DIG_CLP_VSTART binning */ +}; + +/* -----------------------------------------------------------------------= --- + * Mode list + * -----------------------------------------------------------------------= --- + */ + +static struct imx585_mode supported_modes[] =3D { + { + /* 1080p60 2x2 binning */ + .width =3D 1928, + .height =3D 1090, + .hmax_div =3D 1, + .min_hmax =3D 366, /* overwritten at runtime */ + .min_vmax =3D IMX585_VMAX_DEFAULT, + .crop =3D { + .left =3D IMX585_PIXEL_ARRAY_LEFT, + .top =3D IMX585_PIXEL_ARRAY_TOP, + .width =3D IMX585_PIXEL_ARRAY_WIDTH, + .height =3D IMX585_PIXEL_ARRAY_HEIGHT, + }, + .reg_list =3D { + .num_of_regs =3D ARRAY_SIZE(mode_1080_regs_12bit), + .regs =3D mode_1080_regs_12bit, + }, + }, + { + /* 4K60 All pixel */ + .width =3D 3856, + .height =3D 2180, + .hmax_div =3D 1, + .min_hmax =3D 550, /* overwritten at runtime */ + .min_vmax =3D IMX585_VMAX_DEFAULT, + .crop =3D { + .left =3D IMX585_PIXEL_ARRAY_LEFT, + .top =3D IMX585_PIXEL_ARRAY_TOP, + .width =3D IMX585_PIXEL_ARRAY_WIDTH, + .height =3D IMX585_PIXEL_ARRAY_HEIGHT, + }, + .reg_list =3D { + .num_of_regs =3D ARRAY_SIZE(mode_4k_regs_12bit), + .regs =3D mode_4k_regs_12bit, + }, + }, +}; + +/* Formats exposed per mode/bit depth */ +static const u32 color_codes[] =3D { + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SBGGR12_1X12, +}; + +static const u32 mono_codes[] =3D { + MEDIA_BUS_FMT_Y12_1X12, +}; + +/* Regulators */ +static const char * const imx585_supply_name[] =3D { + "vana", /* 3.3V analog */ + "vdig", /* 1.1V core */ + "vddl", /* 1.8V I/O */ +}; + +#define IMX585_NUM_SUPPLIES ARRAY_SIZE(imx585_supply_name) + +/* -----------------------------------------------------------------------= --- + * State + * -----------------------------------------------------------------------= --- + */ + +struct imx585 { + struct v4l2_subdev sd; + struct media_pad pad; + struct device *clientdev; + struct regmap *regmap; + + struct clk *xclk; + u32 xclk_freq; + u8 inck_sel_val; + + unsigned int lane_count; + unsigned int link_freq_idx; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX585_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + + /* Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *hcg_ctrl; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *blacklevel; + + /* Flags/params */ + bool hcg; + bool mono; + + /* + * Sync Mode + * 0 =3D Internal Sync Leader Mode + * 1 =3D External Sync Leader Mode + * 2 =3D Follower Mode + * The datasheet wording is very confusing but basically: + * Leader Mode =3D Sensor using internal clock to drive the sensor + * But with external sync mode you can send a XVS input so the sensor + * will try to align with it. + * For Follower mode it is purely driven by external clock. + * In this case you need to drive both XVS and XHS. + */ + u8 sync_mode; + + u16 hmax; + u32 vmax; + + bool streaming; + bool common_regs_written; +}; + +/* Helpers */ + +static inline struct imx585 *to_imx585(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx585, sd); +} + +static inline void get_mode_table(struct imx585 *imx585, unsigned int code, + const struct imx585_mode **mode_list, + unsigned int *num_modes) +{ + *mode_list =3D NULL; + *num_modes =3D 0; + + if (imx585->mono && code =3D=3D MEDIA_BUS_FMT_Y12_1X12) { + *mode_list =3D supported_modes; + *num_modes =3D ARRAY_SIZE(supported_modes); + } else { + /* --- Color paths --- */ + switch (code) { + case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SBGGR12_1X12: + *mode_list =3D supported_modes; + *num_modes =3D ARRAY_SIZE(supported_modes); + break; + default: + *mode_list =3D NULL; + *num_modes =3D 0; + } + } +} + +static u32 imx585_get_format_code(struct imx585 *imx585, u32 code) +{ + unsigned int i; + + if (imx585->mono) + return mono_codes[0]; + + for (i =3D 0; i < ARRAY_SIZE(color_codes); i++) + if (color_codes[i] =3D=3D code) + return color_codes[i]; + return color_codes[0]; +} + +/* Recompute per-mode timing limits (HMAX/VMAX) from link/lanes/HDR */ +static void imx585_update_hmax(struct imx585 *imx585) +{ + const u32 base_4lane =3D HMAX_table_4lane_4K[imx585->link_freq_idx]; + const u32 lane_scale =3D (imx585->lane_count =3D=3D 2) ? 2 : 1; + const u32 factor =3D base_4lane * lane_scale; + unsigned int i; + + dev_dbg(imx585->clientdev, "Update minimum HMAX: base=3D%u lane_scale=3D%= u\n", + base_4lane, lane_scale); + + for (i =3D 0; i < ARRAY_SIZE(supported_modes); ++i) { + u32 h =3D factor / supported_modes[i].hmax_div; + + supported_modes[i].min_hmax =3D h; + + dev_dbg(imx585->clientdev, " mode %ux%u -> HMAX=3D%u\n", + supported_modes[i].width, supported_modes[i].height, h); + } +} + +static void imx585_set_framing_limits(struct imx585 *imx585, + const struct imx585_mode *mode) +{ + u64 pixel_rate; + u64 max_hblank; + + imx585_update_hmax(imx585); + + imx585->vmax =3D mode->min_vmax; + imx585->hmax =3D mode->min_hmax; + + /* Pixel rate proxy: width * clock / min_hmax */ + pixel_rate =3D (u64)mode->width * IMX585_PIXEL_RATE; + do_div(pixel_rate, mode->min_hmax); + __v4l2_ctrl_modify_range(imx585->pixel_rate, pixel_rate, pixel_rate, 1, + pixel_rate); + + max_hblank =3D (u64)IMX585_HMAX_MAX * pixel_rate; + do_div(max_hblank, IMX585_PIXEL_RATE); + max_hblank -=3D mode->width; + + __v4l2_ctrl_modify_range(imx585->hblank, 0, max_hblank, 1, 0); + __v4l2_ctrl_s_ctrl(imx585->hblank, 0); + + __v4l2_ctrl_modify_range(imx585->vblank, + mode->min_vmax - mode->height, + IMX585_VMAX_MAX - mode->height, + 1, mode->min_vmax - mode->height); + __v4l2_ctrl_s_ctrl(imx585->vblank, mode->min_vmax - mode->height); + + __v4l2_ctrl_modify_range(imx585->exposure, IMX585_EXPOSURE_MIN, + imx585->vmax - IMX585_SHR_MIN_HDR, 1, + IMX585_EXPOSURE_DEFAULT); + + dev_dbg(imx585->clientdev, "Framing: VMAX=3D%u HMAX=3D%u pixel_rate=3D%ll= u\n", + imx585->vmax, imx585->hmax, pixel_rate); +} + +/* -----------------------------------------------------------------------= --- + * Controls + * -----------------------------------------------------------------------= --- + */ + +static int imx585_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx585 *imx585 =3D container_of(ctrl->handler, struct imx585, ctrl= _handler); + const struct imx585_mode *mode, *mode_list; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + unsigned int num_modes; + int ret =3D 0; + + state =3D v4l2_subdev_get_locked_active_state(&imx585->sd); + fmt =3D v4l2_subdev_state_get_format(state, 0); + + get_mode_table(imx585, fmt->code, &mode_list, &num_modes); + mode =3D v4l2_find_nearest_size(mode_list, num_modes, width, height, + fmt->width, fmt->height); + + /* Apply control only when powered (runtime active). */ + if (!pm_runtime_get_if_active(imx585->clientdev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: { + u32 shr =3D (imx585->vmax - ctrl->val) & ~1U; /* SHR always a multiple o= f 2 */ + + dev_dbg(imx585->clientdev, "EXPOSURE=3D%u -> SHR=3D%u (VMAX=3D%u HMAX=3D= %u)\n", + ctrl->val, shr, imx585->vmax, imx585->hmax); + + ret =3D cci_write(imx585->regmap, IMX585_REG_SHR, shr, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "SHR write failed (%d)\n", ret); + break; + } + case V4L2_CID_ANALOGUE_GAIN: + dev_dbg(imx585->clientdev, "ANALOG_GAIN=3D%u\n", ctrl->val); + ret =3D cci_write(imx585->regmap, IMX585_REG_ANALOG_GAIN, ctrl->val, NUL= L); + if (ret) + dev_err_ratelimited(imx585->clientdev, "Gain write failed (%d)\n", ret); + break; + case V4L2_CID_VBLANK: { + u32 current_exposure =3D imx585->exposure->cur.val; + + imx585->vmax =3D (mode->height + ctrl->val) & ~1U; + + current_exposure =3D clamp_t(u32, current_exposure, + IMX585_EXPOSURE_MIN, imx585->vmax - IMX585_SHR_MIN); + __v4l2_ctrl_modify_range(imx585->exposure, + IMX585_EXPOSURE_MIN, imx585->vmax - IMX585_SHR_MIN, 1, + current_exposure); + + dev_dbg(imx585->clientdev, "VBLANK=3D%u -> VMAX=3D%u\n", ctrl->val, imx5= 85->vmax); + + ret =3D cci_write(imx585->regmap, IMX585_REG_VMAX, imx585->vmax, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "VMAX write failed (%d)\n", ret); + break; + } + case V4L2_CID_HBLANK: { + u64 pixel_rate =3D (u64)mode->width * IMX585_PIXEL_RATE; + u64 hmax; + + do_div(pixel_rate, mode->min_hmax); + hmax =3D (u64)(mode->width + ctrl->val) * IMX585_PIXEL_RATE; + do_div(hmax, pixel_rate); + imx585->hmax =3D (u32)hmax; + + dev_dbg(imx585->clientdev, "HBLANK=3D%u -> HMAX=3D%u\n", ctrl->val, imx5= 85->hmax); + + ret =3D cci_write(imx585->regmap, IMX585_REG_HMAX, imx585->hmax, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "HMAX write failed (%d)\n", ret); + break; + } + case V4L2_CID_HFLIP: + ret =3D cci_write(imx585->regmap, IMX585_FLIP_WINMODEH, ctrl->val, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "HFLIP write failed (%d)\n", ret= ); + break; + case V4L2_CID_VFLIP: + ret =3D cci_write(imx585->regmap, IMX585_FLIP_WINMODEV, ctrl->val, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "VFLIP write failed (%d)\n", ret= ); + break; + case V4L2_CID_BRIGHTNESS: { + u16 blacklevel =3D min_t(u32, ctrl->val, 4095); + + ret =3D cci_write(imx585->regmap, IMX585_REG_BLKLEVEL, blacklevel, NULL); + if (ret) + dev_err_ratelimited(imx585->clientdev, "BLKLEVEL write failed (%d)\n", = ret); + break; + } + default: + dev_dbg(imx585->clientdev, "Unhandled ctrl %s: id=3D0x%x, val=3D0x%x\n", + ctrl->name, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(imx585->clientdev); + return ret; +} + +static const struct v4l2_ctrl_ops imx585_ctrl_ops =3D { + .s_ctrl =3D imx585_set_ctrl, +}; + +static int imx585_init_controls(struct imx585 *imx585) +{ + struct v4l2_ctrl_handler *hdl =3D &imx585->ctrl_handler; + struct v4l2_fwnode_device_properties props; + int ret; + + ret =3D v4l2_ctrl_handler_init(hdl, 16); + + /* Read-only, updated per mode */ + imx585->pixel_rate =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, UINT_MAX, 1, 1); + + imx585->link_freq =3D + v4l2_ctrl_new_int_menu(hdl, &imx585_ctrl_ops, V4L2_CID_LINK_FREQ, + 0, 0, &link_freqs[imx585->link_freq_idx]); + if (imx585->link_freq) + imx585->link_freq->flags |=3D V4L2_CTRL_FLAG_READ_ONLY; + + imx585->vblank =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xFFFFF, 1, 0); + imx585->hblank =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xFFFF, 1, 0); + imx585->blacklevel =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xFFFF, 1, + IMX585_BLKLEVEL_DEFAULT); + + imx585->exposure =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX585_EXPOSURE_MIN, IMX585_EXPOSURE_MAX, + IMX585_EXPOSURE_STEP, IMX585_EXPOSURE_DEFAULT); + + imx585->gain =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, V4L2_CID_ANALOG= UE_GAIN, + IMX585_ANA_GAIN_MIN, IMX585_ANA_GAIN_MAX, + IMX585_ANA_GAIN_STEP, IMX585_ANA_GAIN_DEFAULT); + + imx585->hflip =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + imx585->vflip =3D v4l2_ctrl_new_std(hdl, &imx585_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (hdl->error) { + ret =3D hdl->error; + dev_err(imx585->clientdev, "control init failed (%d)\n", ret); + goto err_free; + } + + ret =3D v4l2_fwnode_device_parse(imx585->clientdev, &props); + if (ret) + goto err_free; + + ret =3D v4l2_ctrl_new_fwnode_properties(hdl, &imx585_ctrl_ops, &props); + if (ret) + goto err_free; + + imx585->sd.ctrl_handler =3D hdl; + return 0; + +err_free: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static void imx585_free_controls(struct imx585 *imx585) +{ + v4l2_ctrl_handler_free(imx585->sd.ctrl_handler); +} + +/* -----------------------------------------------------------------------= --- + * Pad ops / formats + * -----------------------------------------------------------------------= --- + */ + +static int imx585_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx585 *imx585 =3D to_imx585(sd); + unsigned int entries; + const u32 *tbl; + + if (imx585->mono) { + if (code->index) + return -EINVAL; + code->code =3D MEDIA_BUS_FMT_Y12_1X12; + return 0; + } + + tbl =3D color_codes; + entries =3D ARRAY_SIZE(color_codes) / 4; + + if (code->index >=3D entries) + return -EINVAL; + + code->code =3D imx585_get_format_code(imx585, tbl[code->index * 4]); + return 0; +} + +static int imx585_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx585 *imx585 =3D to_imx585(sd); + const struct imx585_mode *mode_list; + unsigned int num_modes; + + get_mode_table(imx585, fse->code, &mode_list, &num_modes); + if (fse->index >=3D num_modes) + return -EINVAL; + if (fse->code !=3D imx585_get_format_code(imx585, fse->code)) + return -EINVAL; + + fse->min_width =3D mode_list[fse->index].width; + fse->max_width =3D fse->min_width; + fse->min_height =3D mode_list[fse->index].height; + fse->max_height =3D fse->min_height; + + return 0; +} + +static int imx585_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx585 *imx585 =3D to_imx585(sd); + const struct imx585_mode *mode_list, *mode; + unsigned int num_modes; + struct v4l2_mbus_framefmt *format; + + get_mode_table(imx585, fmt->format.code, &mode_list, &num_modes); + mode =3D v4l2_find_nearest_size(mode_list, num_modes, width, height, + fmt->format.width, fmt->format.height); + + fmt->format.width =3D mode->width; + fmt->format.height =3D mode->height; + fmt->format.field =3D V4L2_FIELD_NONE; + fmt->format.colorspace =3D V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc =3D V4L2_YCBCR_ENC_601; + fmt->format.quantization =3D V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func =3D V4L2_XFER_FUNC_NONE; + + format =3D v4l2_subdev_state_get_format(sd_state, 0); + + if (fmt->which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE) + imx585_set_framing_limits(imx585, mode); + + *format =3D fmt->format; + return 0; +} + +/* -----------------------------------------------------------------------= --- + * Stream on/off + * -----------------------------------------------------------------------= --- + */ + +static int imx585_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct imx585 *imx585 =3D to_imx585(sd); + const struct imx585_mode *mode_list, *mode; + struct v4l2_subdev_state *st; + struct v4l2_mbus_framefmt *fmt; + unsigned int n_modes; + int ret; + + ret =3D pm_runtime_get_sync(imx585->clientdev); + if (ret < 0) { + pm_runtime_put_noidle(imx585->clientdev); + return ret; + } + + ret =3D cci_multi_reg_write(imx585->regmap, common_regs, + ARRAY_SIZE(common_regs), NULL); + if (ret) { + dev_err(imx585->clientdev, "Failed to write common settings\n"); + goto err_rpm_put; + } + + ret =3D cci_write(imx585->regmap, IMX585_INCK_SEL, imx585->inck_sel_val, = NULL); + if (!ret) + ret =3D cci_write(imx585->regmap, IMX585_REG_BLKLEVEL, IMX585_BLKLEVEL_D= EFAULT, NULL); + if (!ret) + ret =3D cci_write(imx585->regmap, IMX585_DATARATE_SEL, + link_freqs_reg_value[imx585->link_freq_idx], NULL); + if (ret) + goto err_rpm_put; + + ret =3D cci_write(imx585->regmap, IMX585_LANEMODE, + (imx585->lane_count =3D=3D 2) ? 0x01 : 0x03, NULL); + if (ret) + goto err_rpm_put; + + /* Mono bin flag (datasheet: 0x01 mono, 0x00 color) */ + ret =3D cci_write(imx585->regmap, IMX585_BIN_MODE, imx585->mono ? 0x01 : = 0x00, NULL); + if (ret) + goto err_rpm_put; + + /* Sync configuration */ + if (imx585->sync_mode =3D=3D SYNC_INT_FOLLOWER) { + dev_dbg(imx585->clientdev, "Internal sync follower: XVS input\n"); + cci_write(imx585->regmap, IMX585_REG_EXTMODE, 0x01, NULL); + cci_write(imx585->regmap, IMX585_REG_XXS_DRV, 0x03, NULL); /* XHS out, X= VS in */ + cci_write(imx585->regmap, IMX585_REG_XXS_OUTSEL, 0x08, NULL); /* disable= XVS OUT */ + } else if (imx585->sync_mode =3D=3D SYNC_INT_LEADER) { + dev_dbg(imx585->clientdev, "Internal sync leader: XVS/XHS output\n"); + cci_write(imx585->regmap, IMX585_REG_EXTMODE, 0x00, NULL); + cci_write(imx585->regmap, IMX585_REG_XXS_DRV, 0x00, NULL); /* XHS/XVS ou= t */ + cci_write(imx585->regmap, IMX585_REG_XXS_OUTSEL, 0x0A, NULL); + } else { + dev_dbg(imx585->clientdev, "Follower: XVS/XHS input\n"); + cci_write(imx585->regmap, IMX585_REG_XXS_DRV, 0x0F, NULL); /* inputs */ + cci_write(imx585->regmap, IMX585_REG_XXS_OUTSEL, 0x00, NULL); + } + + imx585->common_regs_written =3D true; + + /* Select mode */ + st =3D v4l2_subdev_get_locked_active_state(&imx585->sd); + fmt =3D v4l2_subdev_state_get_format(st, 0); + + get_mode_table(imx585, fmt->code, &mode_list, &n_modes); + mode =3D v4l2_find_nearest_size(mode_list, n_modes, width, height, + fmt->width, fmt->height); + + ret =3D cci_multi_reg_write(imx585->regmap, mode->reg_list.regs, + mode->reg_list.num_of_regs, NULL); + if (ret) { + dev_err(imx585->clientdev, "Failed to write mode registers\n"); + goto err_rpm_put; + } + + /* Disable digital clamp */ + cci_write(imx585->regmap, IMX585_REG_DIGITAL_CLAMP, 0x00, NULL); + + /* Apply user controls after writing the base tables */ + ret =3D __v4l2_ctrl_handler_setup(imx585->sd.ctrl_handler); + if (ret) { + dev_err(imx585->clientdev, "Control handler setup failed\n"); + goto err_rpm_put; + } + + if (imx585->sync_mode !=3D SYNC_EXTERNAL) + cci_write(imx585->regmap, IMX585_REG_XMSTA, 0x00, NULL); + + ret =3D cci_write(imx585->regmap, IMX585_REG_MODE_SELECT, IMX585_MODE_STR= EAMING, NULL); + if (ret) + goto err_rpm_put; + + dev_dbg(imx585->clientdev, "Streaming started\n"); + usleep_range(IMX585_STREAM_DELAY_US, + IMX585_STREAM_DELAY_US + IMX585_STREAM_DELAY_RANGE_US); + + /* vflip, hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx585->vflip, true); + __v4l2_ctrl_grab(imx585->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put_autosuspend(imx585->clientdev); + return ret; +} + +static int imx585_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct imx585 *imx585 =3D to_imx585(sd); + int ret; + + ret =3D cci_write(imx585->regmap, IMX585_REG_MODE_SELECT, IMX585_MODE_STA= NDBY, NULL); + if (ret) + dev_err(imx585->clientdev, "Failed to stop streaming\n"); + + __v4l2_ctrl_grab(imx585->vflip, false); + __v4l2_ctrl_grab(imx585->hflip, false); + + pm_runtime_put_autosuspend(imx585->clientdev); + + return ret; +} + +/* -----------------------------------------------------------------------= --- + * Power / runtime PM + * -----------------------------------------------------------------------= --- + */ + +static int imx585_power_on(struct device *dev) +{ + struct v4l2_subdev *sd =3D dev_get_drvdata(dev); + struct imx585 *imx585 =3D to_imx585(sd); + int ret; + + dev_dbg(imx585->clientdev, "power_on\n"); + + ret =3D regulator_bulk_enable(IMX585_NUM_SUPPLIES, imx585->supplies); + if (ret) { + dev_err(imx585->clientdev, "Failed to enable regulators\n"); + return ret; + } + + ret =3D clk_prepare_enable(imx585->xclk); + if (ret) { + dev_err(imx585->clientdev, "Failed to enable clock\n"); + goto reg_off; + } + + gpiod_set_value_cansleep(imx585->reset_gpio, 1); + usleep_range(IMX585_XCLR_MIN_DELAY_US, + IMX585_XCLR_MIN_DELAY_US + IMX585_XCLR_DELAY_RANGE_US); + return 0; + +reg_off: + regulator_bulk_disable(IMX585_NUM_SUPPLIES, imx585->supplies); + return ret; +} + +static int imx585_power_off(struct device *dev) +{ + struct v4l2_subdev *sd =3D dev_get_drvdata(dev); + struct imx585 *imx585 =3D to_imx585(sd); + + dev_dbg(imx585->clientdev, "power_off\n"); + + gpiod_set_value_cansleep(imx585->reset_gpio, 0); + regulator_bulk_disable(IMX585_NUM_SUPPLIES, imx585->supplies); + clk_disable_unprepare(imx585->xclk); + + return 0; +} + +/* -----------------------------------------------------------------------= --- + * Selection / state + * -----------------------------------------------------------------------= --- + */ + +static int imx585_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r =3D *v4l2_subdev_state_get_crop(sd_state, 0); + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left =3D 0; + sel->r.top =3D 0; + sel->r.width =3D IMX585_NATIVE_WIDTH; + sel->r.height =3D IMX585_NATIVE_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left =3D IMX585_PIXEL_ARRAY_LEFT; + sel->r.top =3D IMX585_PIXEL_ARRAY_TOP; + sel->r.width =3D IMX585_PIXEL_ARRAY_WIDTH; + sel->r.height =3D IMX585_PIXEL_ARRAY_HEIGHT; + return 0; + default: + return -EINVAL; + } +} + +static int imx585_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_rect *crop; + struct imx585 *imx585 =3D to_imx585(sd); + struct v4l2_subdev_format fmt =3D { + .which =3D V4L2_SUBDEV_FORMAT_TRY, + .pad =3D 0, + .format =3D { + .code =3D imx585->mono ? MEDIA_BUS_FMT_Y12_1X12 + : MEDIA_BUS_FMT_SRGGB12_1X12, + .width =3D supported_modes[0].width, + .height =3D supported_modes[0].height, + }, + }; + + imx585_set_pad_format(sd, state, &fmt); + + crop =3D v4l2_subdev_state_get_crop(state, 0); + *crop =3D supported_modes[0].crop; + + return 0; +} + +/* -----------------------------------------------------------------------= --- + * Subdev ops + * -----------------------------------------------------------------------= --- + */ + +static const struct v4l2_subdev_video_ops imx585_video_ops =3D { + .s_stream =3D v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops imx585_pad_ops =3D { + .enum_mbus_code =3D imx585_enum_mbus_code, + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D imx585_set_pad_format, + .get_selection =3D imx585_get_selection, + .enum_frame_size =3D imx585_enum_frame_size, + .enable_streams =3D imx585_enable_streams, + .disable_streams =3D imx585_disable_streams, +}; + +static const struct v4l2_subdev_internal_ops imx585_internal_ops =3D { + .init_state =3D imx585_init_state, +}; + +static const struct v4l2_subdev_ops imx585_subdev_ops =3D { + .video =3D &imx585_video_ops, + .pad =3D &imx585_pad_ops, +}; + +/* -----------------------------------------------------------------------= --- + * Probe / remove + * -----------------------------------------------------------------------= --- + */ + +static int imx585_check_hwcfg(struct device *dev, struct imx585 *imx585) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep =3D { + .bus_type =3D V4L2_MBUS_CSI2_DPHY, + }; + int ret =3D -EINVAL; + int i; + + endpoint =3D fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep)) { + dev_err(dev, "could not parse endpoint\n"); + goto out_put; + } + + if (ep.bus.mipi_csi2.num_data_lanes !=3D 2 && + ep.bus.mipi_csi2.num_data_lanes !=3D 4) { + dev_err(dev, "only 2 or 4 data lanes supported\n"); + goto out_free; + } + imx585->lane_count =3D ep.bus.mipi_csi2.num_data_lanes; + dev_dbg(dev, "Data lanes: %u\n", imx585->lane_count); + + if (!ep.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property missing\n"); + goto out_free; + } + + for (i =3D 0; i < ARRAY_SIZE(link_freqs); i++) { + if (link_freqs[i] =3D=3D ep.link_frequencies[0]) { + imx585->link_freq_idx =3D i; + break; + } + } + if (i =3D=3D ARRAY_SIZE(link_freqs)) { + dev_err(dev, "unsupported link frequency: %llu\n", + (unsigned long long)ep.link_frequencies[0]); + goto out_free; + } + + dev_dbg(dev, "Link speed: %llu Hz\n", + (unsigned long long)ep.link_frequencies[0]); + + ret =3D 0; + +out_free: + v4l2_fwnode_endpoint_free(&ep); +out_put: + fwnode_handle_put(endpoint); + return ret; +} + +static int imx585_get_regulators(struct imx585 *imx585) +{ + unsigned int i; + + for (i =3D 0; i < IMX585_NUM_SUPPLIES; i++) + imx585->supplies[i].supply =3D imx585_supply_name[i]; + + return devm_regulator_bulk_get(imx585->clientdev, + IMX585_NUM_SUPPLIES, imx585->supplies); +} + +static int imx585_check_module_exists(struct imx585 *imx585) +{ + int ret; + u64 val; + + /* No chip-id register; read a known register as a presence test */ + ret =3D cci_read(imx585->regmap, IMX585_REG_BLKLEVEL, &val, NULL); + if (ret) { + dev_err(imx585->clientdev, "register read failed (%d)\n", ret); + return ret; + } + + dev_dbg(imx585->clientdev, "Sensor detected\n"); + return 0; +} + +static int imx585_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct imx585 *imx585; + const char *sync_mode; + int ret, i; + + imx585 =3D devm_kzalloc(dev, sizeof(*imx585), GFP_KERNEL); + if (!imx585) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx585->sd, client, &imx585_subdev_ops); + imx585->clientdev =3D dev; + + imx585->mono =3D of_device_is_compatible(dev->of_node, "sony,imx585-aamj1= "); + dev_dbg(dev, "mono=3D%d\n", imx585->mono); + + imx585->sync_mode =3D SYNC_INT_LEADER; + if (!device_property_read_string(dev, "sony,sync-mode", &sync_mode)) { + if (!strcmp(sync_mode, "internal-follower")) + imx585->sync_mode =3D SYNC_INT_FOLLOWER; + else if (!strcmp(sync_mode, "external")) + imx585->sync_mode =3D SYNC_EXTERNAL; + } + dev_dbg(dev, "sync-mode: %s\n", sync_mode_menu[imx585->sync_mode]); + + ret =3D imx585_check_hwcfg(dev, imx585); + if (ret) + return ret; + + imx585->regmap =3D devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx585->regmap)) + return dev_err_probe(dev, PTR_ERR(imx585->regmap), "CCI init failed\n"); + + imx585->xclk =3D devm_clk_get(dev, NULL); + if (IS_ERR(imx585->xclk)) + return dev_err_probe(dev, PTR_ERR(imx585->xclk), "xclk missing\n"); + + imx585->xclk_freq =3D clk_get_rate(imx585->xclk); + for (i =3D 0; i < ARRAY_SIZE(imx585_inck_table); ++i) { + if (imx585_inck_table[i].xclk_hz =3D=3D imx585->xclk_freq) { + imx585->inck_sel_val =3D imx585_inck_table[i].inck_sel; + break; + } + } + if (i =3D=3D ARRAY_SIZE(imx585_inck_table)) + return dev_err_probe(dev, -EINVAL, "unsupported XCLK %u Hz\n", imx585->x= clk_freq); + + dev_dbg(dev, "XCLK %u Hz -> INCK_SEL 0x%02x\n", + imx585->xclk_freq, imx585->inck_sel_val); + + ret =3D imx585_get_regulators(imx585); + if (ret) + return dev_err_probe(dev, ret, "regulators\n"); + + imx585->reset_gpio =3D devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HI= GH); + + /* Power on to probe the device */ + ret =3D imx585_power_on(dev); + if (ret) + return ret; + + ret =3D imx585_check_module_exists(imx585); + if (ret) + goto err_power_off; + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret =3D imx585_init_controls(imx585); + if (ret) + goto err_pm; + + imx585->sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE; + imx585->sd.entity.function =3D MEDIA_ENT_F_CAM_SENSOR; + imx585->sd.internal_ops =3D &imx585_internal_ops; + + imx585->pad.flags =3D MEDIA_PAD_FL_SOURCE; + + ret =3D media_entity_pads_init(&imx585->sd.entity, 1, &imx585->pad); + if (ret) { + dev_err(dev, "entity pads init failed: %d\n", ret); + goto err_ctrls; + } + + imx585->sd.state_lock =3D imx585->ctrl_handler.lock; + ret =3D v4l2_subdev_init_finalize(&imx585->sd); + if (ret) { + dev_err_probe(dev, ret, "subdev init\n"); + goto err_entity; + } + + ret =3D v4l2_async_register_subdev_sensor(&imx585->sd); + if (ret) { + dev_err(dev, "sensor subdev register failed: %d\n", ret); + goto err_entity; + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return 0; + +err_entity: + media_entity_cleanup(&imx585->sd.entity); +err_ctrls: + imx585_free_controls(imx585); +err_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); +err_power_off: + imx585_power_off(dev); + return ret; +} + +static void imx585_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct imx585 *imx585 =3D to_imx585(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + imx585_free_controls(imx585); + + pm_runtime_disable(imx585->clientdev); + if (!pm_runtime_status_suspended(imx585->clientdev)) + imx585_power_off(imx585->clientdev); + pm_runtime_set_suspended(imx585->clientdev); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx585_pm_ops, imx585_power_off, + imx585_power_on, NULL); + +static const struct of_device_id imx585_of_match[] =3D { + { .compatible =3D "sony,imx585-aaqj1" }, + { .compatible =3D "sony,imx585-aamj1" }, /* monochrome variant */ + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx585_of_match); + +static struct i2c_driver imx585_i2c_driver =3D { + .driver =3D { + .name =3D "imx585", + .pm =3D pm_ptr(&imx585_pm_ops), + .of_match_table =3D imx585_of_match, + }, + .probe =3D imx585_probe, + .remove =3D imx585_remove, +}; +module_i2c_driver(imx585_i2c_driver); + +MODULE_AUTHOR("Will Whang "); +MODULE_AUTHOR("Tetsuya Nomura "); +MODULE_DESCRIPTION("Sony IMX585 sensor driver"); +MODULE_LICENSE("GPL"); --=20 2.39.5