From nobody Fri Jul 24 23:31:00 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 8E436372EF0; Wed, 22 Jul 2026 07:20:13 +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=1784704813; cv=none; b=Sus2Zs/wwm2UyrmwGoELy99EQOkUgKiBDif3L1Lzyg8ek10/FS2rA4NqpkaKt4Vr+BBShwIZ4aFTZpHXD3t4GvUYxMzIl+8Oobg0L7kORg+144UU5Lx9QY9ChkorCOOEjN5+bSvqEczBOckEUdiDbg1c+q+yZf4tjB/v7bkH67U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784704813; c=relaxed/simple; bh=my8+t8NDa1StfthZ96aoDEWf6pTvKBoyT1WvZNLDNM8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=n6UV1gYvkvVaJ/7V89Z33PR3etBLnuYp17KliJ/vwf2gJHslxdRLY3iP6mPfSJowRLRg6nim3EAVz3TaHtW3Q45LWOmZCCfPo3QAB/DNG7MrCQz71owJnft5fxXOqMo0wUcLA7U+Z53pOxMljjN7ZJ9cNtwZz1uPE8oVv1ojte0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NFaP+9Ak; 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="NFaP+9Ak" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4763CC19425; Wed, 22 Jul 2026 07:20:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784704813; bh=my8+t8NDa1StfthZ96aoDEWf6pTvKBoyT1WvZNLDNM8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=NFaP+9Akir4PCt8yQ3rwDV/oLemXp2aiy5HwtZLQI3NnQSq2GiCOt3NUcdshy1orL wFWxwn+uZ6t+OZvI2elOP1CQ6wSixV0Hz/KtVmRIfpca5zwCgr3RB9QEZ3Q9IplaZX b03MU4EDx75r6ljmW0RUSA7o2kubwYO1bjQdyBGUB25f5JkziCOtUmdXEexI6BjPqO ZDDQyDqTqen+OYCoAlO3NoFsDdvJGsthcKP5Vr4pxBh6PHOwEM6KuDDgSORsZhtygR hMV7TKJISW2NzNPhp4srvQ3ZFLVtKy1ja5qkNZj+0NkvSPtUi5xuYp/89SoFdhd0uw X/M8YJ7wskzQA== 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 29825C4451C; Wed, 22 Jul 2026 07:20:13 +0000 (UTC) From: Hermes Wu via B4 Relay Date: Wed, 22 Jul 2026 15:20:11 +0800 Subject: [PATCH v4 1/2] dt-bindings: media: add ITE IT6625/IT6626 HDMI bridge binding 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: <20260722-upstream-it6625-it6626-v4-1-c03362eb55ca@ite.com.tw> References: <20260722-upstream-it6625-it6626-v4-0-c03362eb55ca@ite.com.tw> In-Reply-To: <20260722-upstream-it6625-it6626-v4-0-c03362eb55ca@ite.com.tw> To: Hermes Wu , Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Hermes Wu X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784704811; l=6978; i=Hermes.wu@ite.com.tw; s=20241230; h=from:subject:message-id; bh=kvmWSJ1bCMezEyl+3kGjHtMHI6toPCQNPCBbtrclasU=; b=Ai9dVwxy46NM9/BNy6+KY90vVkWf0djmUpd3ekqG1spxr+0rMF3E3np1BA8nYMkZDucZrP4KP zj5cVCs4h3HBqmoMa8ZoVnvT4wkmVqGTwXst+tjfPjl8nC2gp+fsqhA X-Developer-Key: i=Hermes.wu@ite.com.tw; a=ed25519; pk=qho5Dawp2WWj9CGyjtJ6/Y10xH8odjRdS6SXDaDAerU= X-Endpoint-Received: by B4 Relay for Hermes.wu@ite.com.tw/20241230 with auth_id=310 X-Original-From: Hermes Wu Reply-To: Hermes.wu@ite.com.tw From: Hermes Wu Document the devicetree binding for the ITE IT6625/IT6626 HDMI to MIPI CSI-2 bridge. The device exposes two graph ports: port@0 (MIPI0) and port@1 (MIPI1), the two selectable CSI-2 D-PHY/C-PHY outputs. Only port@0 is required, since a board only needs to wire up as many of the bridge's outputs as it actually uses. Signed-off-by: Hermes Wu --- Changes in v4: - Drop port@2 (the HDMI connector input graph port) entirely: it added no constraints beyond graph.yaml's generic endpoint-base, and the driver doesn't use this graph port for anything schema-relevant, so there was nothing for a consumer devicetree to usefully validate against it. Found by the automated review of v3, which suggested adding it to `required` instead -- dropping it entirely was judged the better fix since the port carried no real constraints to begin with. - Require bus-type on port@0/port@1 endpoints when compatible contains "ite,it6626": the driver defaults to D-PHY when bus-type is omitted, which is always correct for it6625 (D-PHY only) but would silently misconfigure an it6626 board actually wired for C-PHY. Also found by the automated review of v3. One other v3 review finding was checked and is not being acted on: - The plain `properties: { endpoint: {...} }` override used for each port technically doesn't reach graph.yaml's separate `patternProperties` matcher, so an `endpoint@0`-style node could in principle bypass the local data-lanes/bus-type constraints. This is the same convention already used by other single-endpoint-per-port bindings in-tree (e.g. thine,thp7312.yaml), not a defect unique to this binding. Changes in v2: - Restrict port@0/port@1 endpoint bus-type to D-PHY (4) only when compatible is "ite,it6625", via an allOf/if/then constraint. IT6625 only supports MIPI CSI-2 D-PHY output; the schema previously allowed both C-PHY (1) and D-PHY (4) unconditionally, so a devicetree claiming "ite,it6625" with bus-type =3D C-PHY passed schema validation despite being invalid on that hardware. IT6626, which supports both PHY types, is unaffected. Found by the sashiko.dev automated review of v1. --- .../devicetree/bindings/media/i2c/ite,it6625.yaml | 169 +++++++++++++++++= ++++ 1 file changed, 169 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml b/= Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fed732e4428ba0098b0a5580cfa= f4b704af21a8e --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml @@ -0,0 +1,169 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ite,it6625.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ITE IT6625/IT6626 HDMI to dual MIPI CSI-2 bridge + +maintainers: + - Hermes Wu + +description: |- + The ITE IT6625 and IT6626 are HDMI to MIPI CSI-2 bridge devices. + IT6625 supports an HDMI 2.0 input and converts it to one or two D-PHY CSI + outputs, while IT6626 supports an HDMI 2.1 input and converts it to one = or + two C/D-PHY CSI outputs. The bridges are programmable through I2C and + expose two selectable CSI-2 output ports. The bridge can operate in + split mode or clone mode. + +properties: + compatible: + enum: + - ite,it6625 + - ite,it6626 + + reg: + maxItems: 1 + + reset-gpios: + description: + GPIO connected to the active-low reset line. + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + clock-noncontinuous: true + link-frequencies: true + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + clock-noncontinuous: true + link-frequencies: true + + required: + - data-lanes + + required: + - port@0 +required: + - compatible + - reg + - ports + +allOf: + - if: + properties: + compatible: + contains: + const: ite,it6625 + then: + properties: + ports: + properties: + port@0: + properties: + endpoint: + properties: + bus-type: + const: 4 + port@1: + properties: + endpoint: + properties: + bus-type: + const: 4 + + - if: + properties: + compatible: + contains: + const: ite,it6626 + then: + properties: + ports: + properties: + port@0: + properties: + endpoint: + required: + - bus-type + port@1: + properties: + endpoint: + required: + - bus-type + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + hdmi-bridge@4c { + compatible =3D "ite,it6625"; + reg =3D <0x4c>; + + reset-gpios =3D <&gpio 2 GPIO_ACTIVE_LOW>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + csi_out0: endpoint { + remote-endpoint =3D <&csi2_rx0>; + bus-type =3D <4>; /* MEDIA_BUS_TYPE_CSI2_DPHY */ + data-lanes =3D <1 2 3 4>; + }; + }; + }; + }; + }; --=20 2.34.1 From nobody Fri Jul 24 23:31:00 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 9ECF3386C31; Wed, 22 Jul 2026 07:20:13 +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=1784704813; cv=none; b=hNRBYd32y01hLFxillC60uAp27f3ct94ae86bUmQ3xpw7sDsBryA4K1VL/I0toYsJ5AHWTUhMobxiKOlitAIUlk/788Vf9X056w/Us7zug5ZOcj8ZOan7FCLXAlCoKm+zgdNckNXfS+xxDwM05ZvEtq0XLRtMANLdfj/D/7gOj8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784704813; c=relaxed/simple; bh=C500LN7v0gqJttFdgc+bdlpypDuw/46b2FCA1G6sR7k=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FCD1elgu7/zvb6ioOC7a+41Vttrkp5tEqDviUE922/JEKmeFGbfWKARQ4o5lRU76N+rqdapb+T163o9GiHwOI/jRW+mlYd6QLV8u/f2DS/X/vDupDpc3k+x906kwYflwnp9N4RHGC9GZ+K9XwgWnf5Ct06WxS06ynRHtYDcZ51A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TRWKglt6; 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="TRWKglt6" Received: by smtp.kernel.org (Postfix) with ESMTPS id 619A7C2BCFD; Wed, 22 Jul 2026 07:20:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784704813; bh=C500LN7v0gqJttFdgc+bdlpypDuw/46b2FCA1G6sR7k=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=TRWKglt6BGwvEA5lRVcs5fCb/Uj1zb33UHsm9Q+LKIBCdmo0gdc8ksXjlD7b4MpXd GiufT+nQoS1BXcOOkAlTx/IdOmqWKt9fODnDUWIz4M5/T+GjOkysqGh7WtULZZSeYt /kvNrWt7hmQ4G0ePKq38ApsHXdh9W1ZxmEnYrJk0RpYFffJOda4s9vE+wTvCL8xFOs tOxn64X0pjTXlp7ZovheUJUcWhYQaRhlHaj3bGXo9kxx34TLPA3rxRkG1iJHC3Pt8s Iv3TYWrLdGkrAu63Oj0bcStCzBaYcSRDhKW+ltyx40Fi7ucE2MuqRov1r/2jzyq6Ov AK+XVTy+914cg== 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 42C17C44539; Wed, 22 Jul 2026 07:20:13 +0000 (UTC) From: Hermes Wu via B4 Relay Date: Wed, 22 Jul 2026 15:20:12 +0800 Subject: [PATCH v4 2/2] media: i2c: add driver for ITE IT6625/IT6626 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: <20260722-upstream-it6625-it6626-v4-2-c03362eb55ca@ite.com.tw> References: <20260722-upstream-it6625-it6626-v4-0-c03362eb55ca@ite.com.tw> In-Reply-To: <20260722-upstream-it6625-it6626-v4-0-c03362eb55ca@ite.com.tw> To: Hermes Wu , Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Hermes Wu X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784704811; l=66259; i=Hermes.wu@ite.com.tw; s=20241230; h=from:subject:message-id; bh=AEHdFlg1cNWbFFyLhiu7AfZg8sqXHRHqVV1fKVcPyEE=; b=iU9408h9oHmvT895kBBphdV9+/emuGc3NKYX3j1hSLXL/lHlFz4YUkxOLT1+ihgGnKOaGFLzM NKut44I2k8HCbeVLESN9GXs6PQ0bym0+UKXLh2+G5GNFakk5gr53TBV X-Developer-Key: i=Hermes.wu@ite.com.tw; a=ed25519; pk=qho5Dawp2WWj9CGyjtJ6/Y10xH8odjRdS6SXDaDAerU= X-Endpoint-Received: by B4 Relay for Hermes.wu@ite.com.tw/20241230 with auth_id=310 X-Original-From: Hermes Wu Reply-To: Hermes.wu@ite.com.tw From: Hermes Wu Add a V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI-to-MIPI CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and IT6626 an HDMI 2.1 input, converting it to a D-PHY (or C/D-PHY on IT6626) MIPI CSI-2 output. The bridge is configured over I2C, exposes an HDMI CEC adapter, and supports EDID read/write, DV timings detection/configuration, and HPD control via the standard V4L2 subdevice pad and video ops. Signed-off-by: Hermes Wu --- Changes in v4: - Always acknowledge the CEC RX length register once the RX interrupt branch is entered, even when the reported length is invalid -- previously the ack write only happened on valid lengths, so an invalid length left the interrupt asserted, which could storm the IRQ line. - Fall back to a default format instead of returning -EINVAL from it6625_set_fmt() when the requested mbus code isn't recognized, matching the V4L2 subdev set_fmt convention (cf. adv7604.c). Take it6625_lock around it6625_get_fmt()'s ACTIVE-path reads of csi_format/mbus_fmt_code, which could otherwise observe a torn update from a concurrent set_fmt(). - Fix EDID ioctl spec conformance: it6625_g_edid() now clamps the requested block count and returns success instead of propagating an error when the request exceeds capacity; it6625_s_edid() now returns -E2BIG (not -EINVAL) with blocks set to the maximum, without attempting the write, when the request exceeds capacity; and it6625_read_edid()/it6625_write_edid()'s low-level bounds check now tests start_block and num_blocks individually before summing them, since the sum could otherwise overflow given the unsigned __u32 fields of struct v4l2_edid. - Harden the mipi_reg debugfs handlers: it6625_mipi_reg_write() now always parses from a local offset that starts at 0 instead of the real file offset, since this is a stateless one-write-one-command interface, not a seekable stream; it6625_mipi_reg_show() now takes it6625_lock around its raw register-bank read, matching every other banked-register access in the driver (the dumped range also includes REG_CEC_RX_DATA, which the CEC handler treats as clear-on-read, so an unlocked read could silently steal a pending CEC RX message). - Mark the HDMI info control (IT6625_CID_HDMI_INFO) volatile: it6625_g_volatile_ctrl() already had a live case for it, but the control's v4l2_ctrl_config was missing V4L2_CTRL_FLAG_VOLATILE, so that case was never actually reached and the control's value was never refreshed. All found by the sashiko.dev automated review of v3. Separately, not a review finding: remove a redundant FW-start register trigger from it6625_initial_setup() -- the chip's firmware starts automatically on power-on, so the explicit software trigger was dead weight. Changes in v3: - Finish the banked-register locking from v2: it6625_irq_infoframe_latch() and get_audio_sampling_rate() also read banked registers without it6625_lock; take the lock inside the CEC RX branch across the length read, data read and length-clear together (closing a TOCTOU where the length could go stale under lock contention), and stop the RX branch from using an early return that skipped a simultaneous CEC TX interrupt. - Fix it6625_s_edid() incorrectly treating a successful EDID write as a failure: it6625_write_edid() returns a positive block count on success, not 0, so `if (err)` skipped the CEC phys-addr update and HPD re-enable on every successful write. Pre-existing since v1, not introduced by the v2 blocks=3D=3D0 handling. - Add real V4L2_SUBDEV_FORMAT_TRY support in get_fmt/set_fmt via sd_state, matching the pattern used by adv7604.c, and lock the csi_format/mbus_fmt_code commit in the ACTIVE path against concurrent S_FMT calls. - Read the chip's real porch/sync-width registers (REG_H_FP_1..REG_V_BP_0) in it6625_get_detected_timings() instead of lumping the whole blanking interval into hsync/vsync, so detected timings can actually match the standard CEA/VESA tables. All found by the sashiko.dev automated review of v2. Four other v2 review findings were checked against the code and are not being acted on: - "timer_container_of() doesn't exist" -- it does (include/linux/timer.h), the driver already builds clean with it. - "devm_kzalloc'd state + subdev devnode risks a UAF on unbind" -- true in the abstract, but it's the exact pattern used by every comparable in-tree driver (tc358743.c, adv7604.c, adv7842.c all devm_kzalloc their main state struct despite setting V4L2_SUBDEV_FL_HAS_DEVNODE too). - "unregistering the subdev before disabling IRQ/work in remove() leaves sd->devnode dangling" -- checked the core: v4l2_device_unregister_subdev() only marks sd->devnode unregistered via video_unregister_device(), it does not free it; the struct is freed later via kref on last close, so the pointer stays valid. - the residual "ioctl reschedules hpd_delayed_work concurrently with remove()" race -- real in the abstract (requires an ioctl already in flight at the exact moment of unbind), but no peer driver adds extra synchronization for this either (none set sd->devnode->lock), and the v2 fix (unregister first in remove()) already narrowed this down to the same residual risk level every peer driver carries. Changes in v2: - Fix premature v4l2_async_register_subdev(): move it to the end of probe() (after ctrl handler, CEC and initial hardware setup), and mirror the unregister order at the top of remove(), closing a use-after-free window where a concurrent ioctl could reschedule hpd_delayed_work during teardown. - Take it6625_lock around the banked register reads in it6625_get_detected_timings() and the CEC RX path in it6625_cec_handler(), which could otherwise race with an in-progress EDID read/write and observe the wrong bank. - Fix it6625_wait_for_status()'s poll interval exceeding the timeout budget, split the CEC RX/TX interrupt handling out of an else-if so simultaneous events aren't dropped, and wait for hardware to latch the CEC transmit trigger bit before releasing the lock. - Stop it6625_set_fmt() from mutating active state on V4L2_SUBDEV_FORMAT_TRY, and handle edid->blocks =3D=3D 0 in g_edid/s_edid per the V4L2 spec (capacity query / EDID clear). - Zero-initialize the CEC rxmsg and debugfs mipi_reg write buffer to avoid leaking uninitialized stack bytes, and use designated initializers in the i2c_device_id table. All found by the sashiko.dev automated review of v1. --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 18 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/it6625.c | 2155 ++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 2181 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a994cf1b354f490d6c4e411588df88..2d5b0ee107f9e3255a5365ec0a2= 5167a716b1e6e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13847,6 +13847,13 @@ T: git https://gitlab.freedesktop.org/drm/misc/ker= nel.git F: Documentation/devicetree/bindings/display/bridge/ite,it66121.yaml F: drivers/gpu/drm/bridge/ite-it66121.c =20 +ITE IT6625 HDMI to MIPI MEDIA DRIVER +M: Hermes Wu +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml +F: drivers/media/i2c/it6625.c + IVTV VIDEO4LINUX DRIVER M: Andy Walls L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5d173e0ecf424f2f204f8d426be818e44357f8e4..fb4b24684f7710f90eb399976d1= 1f57701488d26 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1278,6 +1278,24 @@ config VIDEO_ISL7998X Support for Intersil ISL7998x analog to MIPI-CSI2 or BT.656 decoder. =20 +config VIDEO_IT6625 + tristate "IT6625 HDMI to MIPI CSI bridge" + depends on VIDEO_DEV && I2C + depends on OF + select CEC_CORE + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI to MIPI + CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and + IT6626 an HDMI 2.1 input, converting it to a MIPI CSI-2 + output. The driver also exposes an HDMI CEC adapter. + + To compile this driver as a module, choose M here: the + module will be called it6625. + config VIDEO_LT6911UXE tristate "Lontium LT6911UXE decoder" depends on ACPI && VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index e45359efe0e41e13e3c0869e5ead7d6cf4aca3a7..9bbceaa3d07573958edb8a36bc7= f0942f1410a39 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_VIDEO_IMX412) +=3D imx412.o obj-$(CONFIG_VIDEO_IMX415) +=3D imx415.o obj-$(CONFIG_VIDEO_IR_I2C) +=3D ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) +=3D isl7998x.o +obj-$(CONFIG_VIDEO_IT6625) +=3D it6625.o obj-$(CONFIG_VIDEO_KS0127) +=3D ks0127.o obj-$(CONFIG_VIDEO_LM3560) +=3D lm3560.o obj-$(CONFIG_VIDEO_LM3646) +=3D lm3646.o diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c new file mode 100644 index 0000000000000000000000000000000000000000..ea4c84c83abbb2dc950b2df0c93= 4dc4fb898ee20 --- /dev/null +++ b/drivers/media/i2c/it6625.c @@ -0,0 +1,2155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * it6625 - ite HDMI to MIPI bridge + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int debug =3D 3; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define REG_CHIP_ID_0 0x00 +#define REG_CHIP_ID_1 0x01 +#define REG_FW_VER_MAJOR 0x03 +#define REG_FW_VER_MINOR 0x04 +#define REG_PROTOCOL_VERSION 0x05 +#define B_PVER_MINOR BIT(0) +#define B_PVER_MAJOR BIT(4) + +#define REG_CMD_SET 0x10 +#define CMD_SET_CEC_LA 0xC0 +#define CMD_SET_CEC_ENABLE 0xC1 + +#define REG_EDID_START 0x20 +#define REG_CEC_RX_DATA 0x20 +#define REG_CEC_TX_DATA 0x30 + +#define REG_H_ACTIVE_1 0x50 +#define REG_H_ACTIVE_0 0x51 +#define REG_V_ACTIVE_1 0x52 +#define REG_V_ACTIVE_0 0x53 +#define REG_H_TOTAL_1 0x54 +#define REG_H_TOTAL_0 0x55 +#define REG_V_TOTAL_1 0x56 +#define REG_V_TOTAL_0 0x57 +#define REG_VID_PCLK 0x58 +#define REG_H_FP_1 0x5C +#define REG_H_FP_0 0x5D +#define REG_H_SW_1 0x5E +#define REG_H_SW_0 0x5F +#define REG_H_BP_1 0x60 +#define REG_H_BP_0 0x61 +#define REG_V_FP_1 0x62 +#define REG_V_FP_0 0x63 +#define REG_V_SW_1 0x64 +#define REG_V_SW_0 0x65 +#define REG_V_BP_1 0x66 +#define REG_V_BP_0 0x67 +#define REG_VID_INFO 0x68 +#define B_INTERLACE BIT(0) +#define B_HSWPOL BIT(1) +#define B_VSWPOL BIT(2) +#define B_PIXEL_REP BIT(4) + +#define REG_VIC 0x69 +#define REG_HDMI_VIDEO_INFO 0x6A +#define B_COLOR_MODE BIT(0) +#define B_COLOR_DEPTH BIT(4) +#define REG_HDMI_AUDIO_INFO1 0x6B +#define B_AUD_FS BIT(0) + +#define REG_HDMI_AUDIO_INFO2 0x6C +#define B_AUD_CH BIT(0) +#define B_AUD_WL BIT(4) + +#define REG_HDMI_AUDIO_INFO3 0x6D +#define B_AUD_TYPE BIT(0) +#define B_AUD_MS BIT(2) +#define B_AUD_3D BIT(3) + +#define REG_AUDIO_FMT 0x6E +#define B_I2S_WL BIT(0) +#define B_I2S_ALN BIT(2) +#define B_I2S_DLY BIT(3) +#define B_I2S_LR BIT(4) +#define B_I2S_SFT BIT(5) +#define B_AUD_OUT_IF BIT(6) + +#define REG_IF_LATCH_HB 0x6F +#define REG_IF_DATA 0x70 +#define REG_EMP_DATA 0xA0 +#define REG_AVI_DATA 0xAE + +#define REG_TX_STATUS 0xE0 + +#define REG_RX_STATUS 0xE2 +#define B_RX_5V BIT(0) +#define B_RX_HPD BIT(1) +#define B_RX_STABLE BIT(2) +#define B_RX_HDMI BIT(3) +#define B_RX_AVMUTE BIT(4) +#define B_RX_AUD_ON BIT(5) + +#define REG_RX_HDCP_STS 0xE3 +#define B_HDCP1_AUTH_START BIT(0) +#define B_HDCP2_AUTH_START BIT(1) +#define B_HDCP_AUTH_DONE BIT(2) +#define B_HDCP_ENC BIT(3) + +#define REG_CEC_STATUS 0xE4 +#define B_CEC_TX_DONE BIT(0) +#define B_CEC_TX_NACK BIT(1) + +#define REG_CEC_RX_DATA_LEN 0xE5 +#define REG_CEC_TX_DATA_LEN 0xE6 + +#define REG_SYS_MIPI_INT 0xEB +#define B_MIPI_OUTPUT_ENABLE BIT(0) +#define B_MIPI_VIDEO_UNSTABLE BIT(1) + +#define REG_RX_INT_STATUS1 0xEC +#define B_HDMI_5V_CHG BIT(0) +#define B_HDMI_VID_CHG BIT(1) +#define B_HDMI_AUD_CHG BIT(2) +#define B_HDMI_CP_CHG BIT(3) +#define B_HDMI_IF_LATCH BIT(4) +#define B_HDMI_NO_IF_LATCH BIT(5) +#define B_HDMI_EMP BIT(6) +#define B_HDMI_NO_EMP BIT(7) + +#define REG_RX_INT_STATUS2 0xED +#define B_HDMI_AVI BIT(0) +#define B_HDMI_NO_AVI BIT(1) +#define B_HDMI_VSIF BIT(2) +#define B_HDMI_NO_VSIF BIT(3) +#define B_HDMI_AVMUTE_CHG BIT(4) + +#define REG_CHIP_CONTROL 0xF0 +#define B_HDMI_RESET BIT(5) +#define B_FW_START BIT(6) + +#define REG_MIPI_CFG 0xF1 +#define M_MIPI_LANE 0x03 +#define B_MIPI_SPLIT BIT(2) +#define B_MIPI_SPLIT_CFG BIT(3) +#define B_MIPI_DPHY BIT(4) +#define B_MIPI_USE_DSI BIT(5) +#define B_MIPI_CONTINU_CLK BIT(6) + +#define REG_MIPI_DATA_TYPE 0xF2 +#define CSI_RGB444 0x20 +#define CSI_RGB555 0x21 +#define CSI_RGB565 0x22 +#define CSI_RGB666 0x23 +#define CSI_RGB888 0x24 +#define CSI_YUV420_8b_L 0x1A +#define CSI_YUV420_8b 0x1C +#define CSI_YUV420_10b 0x1D +#define CSI_YUV422_8b 0x1E +#define CSI_YUV422_10b 0x1F +#define CSI_RGB_10b 0x30 +#define CSI_RGB_12b 0x31 +#define CSI_YUV422_12b 0x32 +#define CSI_YUV420_10b_L 0x33 +#define CSI_YUV420_12b 0x34 +#define CSI_YUV444_8b 0x35 +#define CSI_YUV444_10b 0x36 +#define CSI_YUV444_12b 0x37 + +#define REG_MIPI_CONTROL 0xF3 +#define B_MIPI_OUTPUT BIT(0) + +#define REG_RX_CFG 0xF4 +#define B_MANUAL_HPD BIT(0) +#define B_HPD_HIGH BIT(1) +#define B_HPD_TOGGLE BIT(3) + +#define REG_CSC_CFG 0xF5 +#define B_DYNAMIC_RANGE BIT(0) + +#define REG_MISC_CFG 0xF6 +#define B_BAUD_RATE BIT(0) +#define B_DEBUG_MSG BIT(1) + +#define REG_HPD_DELAY 0xF7 +#define B_DELAY_COUNT BIT(0) +#define B_DELAY_UNIT BIT(7) + +#define REG_INFO_BANK_SEL 0xFD +#define CTL_BANK_EDID_READ 1 +#define CTL_BANK_EDID_WRITE 5 + +#define REG_HOST_CTRL_INT 0xFE +#define B_CMD_SET BIT(4) +#define B_CEC_SEND_DATA BIT(5) +#define B_CONFIG_UPDATE BIT(6) +#define B_IF_BANK BIT(7) + +#define REG_MCU_INTERRUPT 0xFF +#define B_SYS_INT_ACTIVE BIT(0) +#define B_CEC_RX_RECEIVED BIT(1) +#define B_CEC_TX_UPDATE BIT(2) + +#define EDID_NUM_BLOCKS_MAX 4 +#define EDID_BLOCK_SIZE 128 + +#define I2C_MAX_XFER_SIZE 8 +#define POLL_INTERVAL_CEC_MS 10 +#define POLL_INTERVAL_MS 40 + +#define AUD32K 0x03 +#define AUD44K 0x00 +#define AUD48K 0x02 +#define AUD64K 0x0B +#define AUD88K 0x08 +#define AUD96K 0x0A +#define AUD128K 0x2B +#define AUD176K 0x0C +#define AUD192K 0x0E +#define AUD256K 0x1B +#define AUD352K 0x0D +#define AUD384K 0x05 +#define AUD512K 0x3B +#define AUD705K 0x2D +#define AUD768K 0x09 +#define AUD1024K 0x35 +#define AUD1411K 0x1D +#define AUD1536K 0x15 + +enum it6625_chip_type { + IT6625_CHIP =3D 0, + IT6626_CHIP =3D 1, +}; + +struct it6625 { + struct device *dev; + struct i2c_client *i2c_client; + struct regmap *it6625_regmap; + enum it6625_chip_type chip_type; + + /* protects concurrent access to the chip's registers and state */ + struct mutex it6625_lock; + + struct v4l2_subdev sd; + struct v4l2_mbus_config_mipi_csi2 bus; + struct video_device *vdev; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + + /* controls */ + struct v4l2_ctrl *ctrl_5v_detect; + struct v4l2_ctrl *ctrl_audio_sampling_rate; + struct v4l2_ctrl *ctrl_audio_present; + struct v4l2_ctrl *ctrl_link_freq; + struct v4l2_ctrl *ctrl_get_hdmi_info; + struct v4l2_ctrl *ctrl_mipi_config; + + struct delayed_work hpd_delayed_work; + + struct timer_list timer; + struct work_struct polling_work; + + struct v4l2_dv_timings timings; + + u8 csi_lanes; + u8 port_num; + enum v4l2_mbus_type bus_type; + u8 csi_format; + u32 mbus_fmt_code; + + struct gpio_desc *reset_gpio; + + struct cec_adapter *cec_adap; + + struct dentry *debugfs_dir; +}; + +static const s64 it6625_link_freq[] =3D { 445500000 }; + +static const struct v4l2_dv_timings_cap it6625_timings_cap =3D { + .type =3D V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved =3D { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 300000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct it6625_format_info { + u8 csi_format; + u32 mbus_fmt_code; +} it6625_formats[] =3D { + { CSI_YUV422_8b, MEDIA_BUS_FMT_UYVY8_1X16 }, + { CSI_RGB888, MEDIA_BUS_FMT_RGB888_1X24 }, + { CSI_YUV444_8b, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +static inline int it6625_csi_format_idx(u8 csi_format) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].csi_format =3D=3D csi_format) + return i; + } + + return -EINVAL; +} + +static inline int it6625_csi_mbus_code_idx(u32 mbus_fmt_code) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].mbus_fmt_code =3D=3D mbus_fmt_code) + return i; + } + + return -EINVAL; +} + +static inline struct it6625 *sd_to_6625(struct v4l2_subdev *sd) +{ + return container_of(sd, struct it6625, sd); +} + +static inline struct it6625 *hdl_to_6625(struct v4l2_ctrl_handler *hdl) +{ + return container_of(hdl, struct it6625, hdl); +} + +static const struct regmap_config it6625_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D 0xff, + .cache_type =3D REGCACHE_NONE, + .max_raw_read =3D I2C_MAX_XFER_SIZE, + .max_raw_write =3D I2C_MAX_XFER_SIZE, +}; + +static int it6625_regmap_i2c_init(struct i2c_client *client, + struct it6625 *it6625) +{ + it6625->i2c_client =3D client; + it6625->dev =3D &client->dev; + + it6625->it6625_regmap =3D devm_regmap_init_i2c(it6625->i2c_client, + &it6625_regmap_config); + if (IS_ERR(it6625->it6625_regmap)) + return PTR_ERR(it6625->it6625_regmap); + + return 0; +} + +static int it6625_read_byte(struct it6625 *it6625, u8 reg) +{ + unsigned int val; + int err; + struct device *dev =3D it6625->dev; + + err =3D regmap_read(it6625->it6625_regmap, reg, &val); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return val; +} + +static int it6625_write_byte(struct it6625 *it6625, u8 reg, u8 val) +{ + int err; + struct device *dev =3D it6625->dev; + + err =3D regmap_write(it6625->it6625_regmap, reg, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_set_bits(struct it6625 *it6625, u8 reg, u8 mask, u8 val) +{ + int err; + struct device *dev =3D it6625->dev; + + err =3D regmap_update_bits(it6625->it6625_regmap, reg, mask, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] set bits failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_read_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int l= en) +{ + int err; + struct device *dev =3D it6625->dev; + + err =3D regmap_bulk_read(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_write_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int = len) +{ + int err; + struct device *dev =3D it6625->dev; + + err =3D regmap_bulk_write(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_wait_for_status(struct it6625 *it6625, u8 reg, u8 val, + int timeout_ms) +{ + struct device *dev =3D it6625->dev; + int status; + int rval; + int sleep_ms =3D 10; + int timeout_round_ms =3D DIV_ROUND_UP(timeout_ms, sleep_ms) * sleep_ms; + + status =3D read_poll_timeout(it6625_read_byte, rval, rval =3D=3D val, + sleep_ms * 1000, + timeout_round_ms * 1000, + false, it6625, reg); + + dev_info(dev, "%s status =3D %d %d", __func__, status, (int)rval); + if (status < 0) { + dev_err(dev, "%s err status =3D %d", __func__, status); + return -ETIMEDOUT; + } + + return 0; +} + +static void it6625_write_command(struct it6625 *it6625, u8 *cmds, int cmd_= len) +{ + it6625_write_bytes(it6625, REG_CMD_SET, cmds, cmd_len); + it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_CMD_SET); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_update_config(struct it6625 *it6625) +{ + int err; + + err =3D it6625_set_bits(it6625, REG_HOST_CTRL_INT, + B_CONFIG_UPDATE, B_CONFIG_UPDATE); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_set_bank(struct it6625 *it6625, u8 bank) +{ + int err; + + err =3D it6625_write_byte(it6625, REG_INFO_BANK_SEL, bank); + if (err < 0) + return err; + + err =3D it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_IF_BANK); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static inline bool is_hdmi(struct it6625 *it6625) +{ + int val; + + val =3D it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_HDMI); +} + +static inline bool hdmi_5v_power_present(struct it6625 *it6625) +{ + int val; + + val =3D it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_5V); +} + +static inline bool no_signal(struct it6625 *it6625) +{ + int val; + + val =3D it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? true : !(val & B_RX_STABLE); +} + +static inline bool audio_present(struct it6625 *it6625) +{ + int val; + + val =3D it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_AUD_ON); +} + +static int get_audio_sampling_rate(struct it6625 *it6625) +{ + u8 fs_id; + int i, freq =3D 0; + const struct fs_id_map { + u8 fs_id; + u16 freq; + } s_fsid_map[] =3D { + { AUD32K, 32 }, + { AUD44K, 44 }, + { AUD48K, 48 }, + { AUD64K, 64 }, + { AUD88K, 88 }, + { AUD96K, 96 }, + + { AUD128K, 128 }, + { AUD176K, 176 }, + { AUD192K, 192 }, + { AUD256K, 256 }, + { AUD352K, 352 }, + { AUD384K, 384 }, + + { AUD512K, 512 }, + { AUD705K, 705 }, + { AUD768K, 768 }, + { AUD1024K, 1024 }, + { AUD1411K, 1411 }, + { AUD1536K, 1536 }, + }; + + if (no_signal(it6625) || !audio_present(it6625)) + return 0; + + guard(mutex)(&it6625->it6625_lock); + + fs_id =3D it6625_read_byte(it6625, REG_HDMI_AUDIO_INFO1); + + for (i =3D 0; i < ARRAY_SIZE(s_fsid_map); i++) { + if (s_fsid_map[i].fs_id =3D=3D fs_id) { + freq =3D s_fsid_map[i].freq * 1000; + break; + } + } + + return freq; +} + +static u64 it6625_get_pclk(struct it6625 *it6625) +{ + u32 pclk; + u8 ck[4]; + int ret; + + ret =3D it6625_read_bytes(it6625, REG_VID_PCLK, ck, 4); + if (ret < 0) { + dev_err(it6625->dev, "failed to read pixel clock"); + return 0; + } + + pclk =3D ck[0]; + pclk <<=3D 8; + pclk |=3D ck[1]; + pclk <<=3D 8; + pclk |=3D ck[2]; + pclk <<=3D 8; + pclk |=3D ck[3]; + + v4l2_dbg(1, debug, &it6625->sd, "%s: pclk=3D%u (%08x)", + __func__, pclk, pclk); + + return (u64)pclk * 1000; +} + +static int it6625_read_edid(struct it6625 *it6625, u8 *edid, int start_blo= ck, + int num_blocks) +{ + int i, bank_ctrl, err =3D 0; + struct device *dev =3D it6625->dev; + + if (!edid) { + dev_err(dev, "edid buffer is NULL"); + return -EINVAL; + } + + if (start_block < 0 || num_blocks <=3D 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=3D%d, num_blocks=3D%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i =3D 0; i < num_blocks; i++) { + bank_ctrl =3D CTL_BANK_EDID_READ + start_block + i; + err =3D it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err =3D it6625_read_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static int it6625_write_edid(struct it6625 *it6625, u8 *edid, int start_bl= ock, + int num_blocks) +{ + int i, bank_ctrl, err =3D 0; + struct device *dev =3D it6625->dev; + + if (start_block < 0 || num_blocks <=3D 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=3D%d, num_blocks=3D%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i =3D 0; i < num_blocks; i++) { + bank_ctrl =3D CTL_BANK_EDID_WRITE + start_block + i; + err =3D it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err =3D it6625_write_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + + err =3D it6625_update_config(it6625); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static void it6625_enable_auto_hpd(struct it6625 *it6625) +{ + dev_dbg(it6625->dev, "%s: auto HPD", __func__); + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x00); + it6625_update_config(it6625); +} + +static void it6625_disable_hpd(struct it6625 *it6625) +{ + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x01); + it6625_update_config(it6625); +} + +static void it6625_enable_hpd(struct it6625 *it6625) +{ + schedule_delayed_work(&it6625->hpd_delayed_work, + msecs_to_jiffies(100)); +} + +static void it6625_hpd_delayed_work(struct work_struct *work) +{ + struct it6625 *it6625 =3D container_of(work, + struct it6625, hpd_delayed_work.work); + int val =3D 0; + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, val); + it6625_update_config(it6625); +} + +static int it6625_get_detected_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt =3D &timings->bt; + int val; + unsigned int width, height; + u8 buffer[4]; + u8 buffer2[12]; + + if (no_signal(it6625)) { + dev_err(it6625->dev, "no signal detected"); + return -ENOLINK; + } + + guard(mutex)(&it6625->it6625_lock); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + timings->type =3D V4L2_DV_BT_656_1120; + val =3D it6625_read_byte(it6625, REG_VID_INFO); + if (val < 0) { + dev_err(it6625->dev, "failed to read video info"); + return -EIO; + } + + bt->interlaced =3D val & B_INTERLACE ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (it6625_read_bytes(it6625, REG_H_ACTIVE_1, buffer, 4) < 0) + return -EIO; + + width =3D ((buffer[0] & 0xff) << 8) + buffer[1]; + height =3D ((buffer[2] & 0xff) << 8) + buffer[3]; + + bt->width =3D width; + bt->height =3D height; + + if (it6625_read_bytes(it6625, REG_H_FP_1, buffer2, 12) < 0) + return -EIO; + + bt->hfrontporch =3D ((buffer2[0] & 0xff) << 8) + buffer2[1]; + bt->hsync =3D ((buffer2[2] & 0xff) << 8) + buffer2[3]; + bt->hbackporch =3D ((buffer2[4] & 0xff) << 8) + buffer2[5]; + bt->vfrontporch =3D ((buffer2[6] & 0xff) << 8) + buffer2[7]; + bt->vsync =3D ((buffer2[8] & 0xff) << 8) + buffer2[9]; + bt->vbackporch =3D ((buffer2[10] & 0xff) << 8) + buffer2[11]; + + bt->pixelclock =3D it6625_get_pclk(it6625); + if (bt->interlaced =3D=3D V4L2_DV_INTERLACED) { + bt->height *=3D 2; + bt->il_vsync =3D bt->vsync + 1; + bt->pixelclock /=3D 2; + } + + return 0; +} + +static void it6625_show_avi_infoframe(struct it6625 *it6625) +{ + struct device *dev =3D it6625->dev; + union hdmi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + u8 ver, len; + int ret; + + if (!is_hdmi(it6625)) { + dev_err(dev, "not HDMI signal, skip AVI infoframe log"); + return; + } + + ret =3D it6625_read_bytes(it6625, REG_AVI_DATA, buffer + 1, + HDMI_INFOFRAME_SIZE(AVI) - 1); + if (ret < 0) { + dev_err(dev, "failed to read AVI infoframe data"); + return; + } + + len =3D buffer[1]; + ver =3D buffer[2]; + + buffer[0] =3D HDMI_INFOFRAME_TYPE_AVI; + buffer[1] =3D ver; + buffer[2] =3D len; + + ret =3D hdmi_infoframe_unpack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(dev, "unpack of AVI infoframe failed"); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_5v_detect, + hdmi_5v_power_present(it6625)); +} + +static int it6625_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate, + get_audio_sampling_rate(it6625)); +} + +static int it6625_s_ctrl_audio_present(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, + audio_present(it6625)); +} + +static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd) +{ + int ret =3D 0; + + ret |=3D it6625_s_ctrl_detect_hdmi_5v(sd); + ret |=3D it6625_s_ctrl_audio_sampling_rate(sd); + ret |=3D it6625_s_ctrl_audio_present(sd); + + return 0; +} + +static void it6625_enable_stream(struct it6625 *it6625, bool enable) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + int val; + + v4l2_dbg(3, debug, sd, "%s: %sable", + __func__, enable ? "en" : "dis"); + + val =3D enable ? B_MIPI_OUTPUT : 0; + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val); + it6625_update_config(it6625); +} + +static void it6625_set_mipi_config(struct it6625 *it6625, u32 cfg_val) +{ + u8 mipi_data_type; + + dev_dbg(it6625->dev, "mipi_data_type =3D 0x%x", cfg_val); + + mipi_data_type =3D cfg_val & 0xFF; + guard(mutex)(&it6625->it6625_lock); + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type); + it6625_update_config(it6625); +} + +static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timing= s *t) +{ + if (!V4L2_DV_BT_FRAME_HEIGHT(t) || !V4L2_DV_BT_FRAME_WIDTH(t)) + return 0; + + return DIV_ROUND_CLOSEST((unsigned int)t->pixelclock, + V4L2_DV_BT_FRAME_HEIGHT(t) * + V4L2_DV_BT_FRAME_WIDTH(t)); +} + +static void it6625_initial_setup(struct it6625 *it6625) +{ + int val =3D 0; + + /* + * REG_MIPI_CFG[0:2] lane count field: 1 lane -> 0, 2 lanes -> 1, + * 3 lanes (C-PHY only) -> 3, 4 lanes (D-PHY only) -> 3. + */ + switch (it6625->csi_lanes) { + case 1: + val =3D FIELD_PREP(M_MIPI_LANE, 0); + break; + case 2: + val =3D FIELD_PREP(M_MIPI_LANE, 1); + break; + default: + val =3D FIELD_PREP(M_MIPI_LANE, 3); + break; + } + + if (it6625->bus_type =3D=3D V4L2_MBUS_CSI2_DPHY) + val |=3D FIELD_PREP(B_MIPI_DPHY, 1); + + if (it6625->port_num =3D=3D 2) + val |=3D FIELD_PREP(B_MIPI_SPLIT, 1); + + it6625_write_byte(it6625, REG_MIPI_CFG, val); + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625->csi_format); + it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00); + it6625_write_byte(it6625, REG_RX_CFG, 0x00); + + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CONFIG_UPDATE, B_CONFIG_UPDA= TE); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static void dump_cec_msg(struct it6625 *it6625, struct cec_msg *msg, char = *str) +{ + int i; + + dev_dbg(it6625->dev, "%s: from %d to %d len=3D%d", str, + msg->msg[0] >> 4, msg->msg[0] & 0x0f, msg->len); + for (i =3D 1; i < msg->len; i++) + dev_dbg(it6625->dev, " %02x", msg->msg[i]); +} + +static int it6625_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct it6625 *it6625 =3D adap->priv; + u8 cmds[2]; + + cmds[0] =3D CMD_SET_CEC_ENABLE; + cmds[1] =3D enable ? 1 : 0; + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static int it6625_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct it6625 *it6625 =3D adap->priv; + + dev_dbg(it6625->dev, "%s: la=3D%d", __func__, log_addr); + + if (log_addr !=3D CEC_LOG_ADDR_INVALID) { + u8 cmds[2] =3D {CMD_SET_CEC_LA, log_addr}; + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + } + + return 0; +} + +static int it6625_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct it6625 *it6625 =3D adap->priv; + + dump_cec_msg(it6625, msg, "CEC_TX"); + + guard(mutex)(&it6625->it6625_lock); + it6625_write_bytes(it6625, REG_CEC_TX_DATA, msg->msg, msg->len); + it6625_write_byte(it6625, REG_CEC_TX_DATA_LEN, msg->len); + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CEC_SEND_DATA, B_CEC_SEND_DA= TA); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); + + return 0; +} + +static const struct cec_adap_ops it6625_cec_adap_ops =3D { + .adap_enable =3D it6625_cec_adap_enable, + .adap_log_addr =3D it6625_cec_adap_log_addr, + .adap_transmit =3D it6625_cec_adap_transmit, +}; + +static void it6625_cec_handler(struct it6625 *it6625, u8 intstatus) +{ + struct cec_msg rxmsg =3D {}; + int val =3D 0; + + if (intstatus & B_CEC_RX_RECEIVED) { + scoped_guard(mutex, &it6625->it6625_lock) { + val =3D it6625_read_byte(it6625, REG_CEC_RX_DATA_LEN); + if (val > 0 && val <=3D 16) + it6625_read_bytes(it6625, REG_CEC_RX_DATA, &rxmsg.msg[0], val); + it6625_write_byte(it6625, REG_CEC_RX_DATA_LEN, 0); + } + + if (val > 0 && val <=3D 16) { + rxmsg.len =3D val; + cec_received_msg(it6625->cec_adap, &rxmsg); + dump_cec_msg(it6625, &rxmsg, "CEC_RX"); + } else { + dev_err(it6625->dev, "invalid CEC RX length %d", val); + } + } + + if (intstatus & B_CEC_TX_UPDATE) { + val =3D it6625_read_byte(it6625, REG_CEC_STATUS); + if (val < 0) { + dev_err(it6625->dev, "read CEC status failed"); + return; + } + + if (val & BIT(0)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_OK); + } else if (val & BIT(1)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } else { + dev_err(it6625->dev, "unknown CEC status %02X", val); + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } + } +} + +static void it6625_irq_format_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + struct v4l2_dv_timings timings; + const struct v4l2_event it6625_ev_fmt =3D { + .type =3D V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes =3D V4L2_EVENT_SRC_CH_RESOLUTION, + }; + int ret; + + if (no_signal(it6625)) + return; + + ret =3D it6625_get_detected_timings(it6625, &timings); + if (ret < 0) { + v4l2_dbg(1, debug, sd, "Failed to get detected timings"); + return; + } + + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); +} + +static void it6625_irq_hdmi_audio_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_get_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + guard(mutex)(&it6625->it6625_lock); + *timings =3D it6625->timings; +} + +static void it6625_clear_timings(struct it6625 *it6625) +{ + guard(mutex)(&it6625->it6625_lock); + memset(&it6625->timings, 0, sizeof(it6625->timings)); +} + +static void it6625_irq_hdmi_5v_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + + it6625_clear_timings(it6625); + it6625_v4l2_sd_ctrl_update(sd); +} + +static void it6625_irq_hdcp_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + u8 cp_sts; + + cp_sts =3D it6625_read_byte(it6625, REG_RX_HDCP_STS); + v4l2_info(sd, "HDCP change to %02X", cp_sts); +} + +static void it6625_irq_infoframe_latch(struct it6625 *it6625) +{ + u8 info_frame[32]; + + guard(mutex)(&it6625->it6625_lock); + + it6625_read_bytes(it6625, REG_IF_DATA, info_frame, 31); +} + +static void it6625_irq_emata_packet_latch(struct it6625 *it6625) +{ + u8 extend_packet[14]; + + it6625_read_bytes(it6625, REG_EMP_DATA, extend_packet, 13); +} + +static void it6625_irq_avmute_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + int val; + u8 avmute; + + val =3D it6625_read_byte(it6625, REG_RX_STATUS); + if (val < 0) + return; + + avmute =3D (val & B_RX_AVMUTE) ? 1 : 0; + v4l2_info(sd, "AVMute change to %d", avmute); +} + +static int it6625_interrupt_handler(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + int val, int_sts1 =3D 0, int_sts2 =3D 0; + + val =3D it6625_read_byte(it6625, REG_MCU_INTERRUPT); + if (val <=3D 0) + return 0; + + it6625_write_byte(it6625, REG_MCU_INTERRUPT, val); + + v4l2_dbg(1, debug, sd, "%s: INT =3D 0x%02X", __func__, val); + + if (val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE) && it6625->cec_adap) + it6625_cec_handler(it6625, val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE)); + + if (val & B_SYS_INT_ACTIVE) { + int_sts1 =3D it6625_read_byte(it6625, REG_RX_INT_STATUS1); + int_sts2 =3D it6625_read_byte(it6625, REG_RX_INT_STATUS2); + v4l2_dbg(1, debug, sd, "%s: hdmi_int =3D 0x%02X 0x%02X", + __func__, int_sts1, int_sts2); + + if (int_sts1 >=3D 0) { + it6625_write_byte(it6625, REG_RX_INT_STATUS1, 0x00); + if (int_sts1 & B_HDMI_5V_CHG) + it6625_irq_hdmi_5v_change(it6625); + + if (int_sts1 & B_HDMI_VID_CHG) + it6625_irq_format_change(it6625); + + if (int_sts1 & B_HDMI_AUD_CHG) + it6625_irq_hdmi_audio_change(it6625); + + if (int_sts1 & B_HDMI_CP_CHG) + it6625_irq_hdcp_change(it6625); + + if (int_sts1 & B_HDMI_IF_LATCH) + it6625_irq_infoframe_latch(it6625); + + if (int_sts1 & B_HDMI_EMP) + it6625_irq_emata_packet_latch(it6625); + } + + if (int_sts2 >=3D 0) { + it6625_write_byte(it6625, REG_RX_INT_STATUS2, 0x00); + + if (int_sts2 & B_HDMI_AVI) + it6625_show_avi_infoframe(it6625); + + if (int_sts2 & B_HDMI_NO_AVI) + v4l2_info(sd, "AVI-info stopped"); + + if (int_sts2 & B_HDMI_AVMUTE_CHG) + it6625_irq_avmute_change(it6625); + } + } + + return 0; +} + +static irqreturn_t it6625_irq_handler(int unused, void *data) +{ + struct it6625 *it6625 =3D data; + + it6625_interrupt_handler(it6625); + return IRQ_HANDLED; +} + +static void it6625_irq_poll_timer(struct timer_list *t) +{ + struct it6625 *it6625 =3D timer_container_of(it6625, t, timer); + unsigned int msecs; + + schedule_work(&it6625->polling_work); + /* + * If CEC is present, then we need to poll more frequently, + * otherwise we will miss CEC messages. + */ + msecs =3D it6625->cec_adap ? POLL_INTERVAL_CEC_MS : POLL_INTERVAL_MS; + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(msecs)); +} + +static void it6625_polling_work(struct work_struct *work) +{ + struct it6625 *it6625 =3D container_of(work, struct it6625, + polling_work); + + it6625_interrupt_handler(it6625); +} + +static int it6625_log_status(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + struct v4l2_dv_timings timings, configured_timings; + + if (it6625_get_detected_timings(it6625, &timings)) + v4l2_info(sd, "No video detected"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", &timings, + true); + + it6625_get_timings(it6625, &configured_timings); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &configured_timings, true); + + it6625_show_avi_infoframe(it6625); + + return 0; +} + +static int it6625_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + schedule_work(&it6625->polling_work); + *handled =3D true; + + return 0; +} + +static int it6625_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *= fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int it6625_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + bool val; + + val =3D no_signal(it6625); + *status =3D 0; + *status |=3D val ? V4L2_IN_ST_NO_SIGNAL : 0; + *status |=3D val ? V4L2_IN_ST_NO_SYNC : 0; + + v4l2_dbg(1, debug, sd, "%s: status =3D 0x%x", __func__, *status); + + return 0; +} + +static int +it6625_update_timings_if_changed(struct it6625 *it6625, + const struct v4l2_dv_timings *timings) +{ + int ret; + + guard(mutex)(&it6625->it6625_lock); + if (v4l2_match_dv_timings(&it6625->timings, timings, 0, false)) { + ret =3D 0; + } else if (!v4l2_valid_dv_timings(timings, &it6625_timings_cap, + NULL, NULL)) { + ret =3D -ERANGE; + } else { + it6625->timings =3D *timings; + ret =3D 1; + } + + return ret; +} + +static int it6625_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + int ret; + + if (!timings) + return -EINVAL; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + ret =3D it6625_update_timings_if_changed(it6625, timings); + if (ret =3D=3D -ERANGE) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return ret; + } + + if (ret =3D=3D 0) { + v4l2_dbg(1, debug, sd, "%s: no change", __func__); + return 0; + } + + it6625_enable_stream(it6625, false); + + return 0; +} + +static int it6625_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + it6625_get_timings(it6625, timings); + + return 0; +} + +static int it6625_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + if (timings->pad !=3D 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + &it6625_timings_cap, NULL, NULL); +} + +static int it6625_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + int ret; + + ret =3D it6625_get_detected_timings(it6625, timings); + if (ret) + return ret; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + if (!v4l2_valid_dv_timings(timings, &it6625_timings_cap, NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return -ERANGE; + } + + return 0; +} + +static int it6625_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad !=3D 0) + return -EINVAL; + + *cap =3D it6625_timings_cap; + + return 0; +} + +static int it6625_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + it6625_enable_stream(it6625, enable); + return 0; +} + +static int it6625_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + dev_dbg(sd->dev, "%s: index=3D%d", __func__, code->index); + + if (code->index >=3D ARRAY_SIZE(it6625_formats)) + return -EINVAL; + + dev_dbg(sd->dev, "%s: code=3D0x%08x", __func__, + it6625_formats[code->index].mbus_fmt_code); + code->code =3D it6625_formats[code->index].mbus_fmt_code; + + return 0; +} + +static int it6625_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + + if (pad !=3D 0) + return -EINVAL; + + cfg->type =3D it6625->bus_type; + cfg->bus.mipi_csi2.flags =3D 0; + cfg->bus.mipi_csi2.num_data_lanes =3D it6625->csi_lanes; + + return 0; +} + +static int it6625_pad_s_dv_timings(struct v4l2_subdev *sd, unsigned int pa= d, + struct v4l2_dv_timings *timings) +{ + if (pad !=3D 0) + return -EINVAL; + + return it6625_s_dv_timings(sd, timings); +} + +static int it6625_pad_g_dv_timings(struct v4l2_subdev *sd, unsigned int pa= d, + struct v4l2_dv_timings *timings) +{ + if (pad !=3D 0) + return -EINVAL; + + return it6625_g_dv_timings(sd, timings); +} + +static int it6625_pad_query_dv_timings(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_dv_timings *timings) +{ + if (pad !=3D 0) + return -EINVAL; + + return it6625_query_dv_timings(sd, timings); +} + +static inline u32 format_to_colorspace(u8 csi_format) +{ + switch (csi_format) { + case CSI_RGB444: + case CSI_RGB555: + case CSI_RGB565: + case CSI_RGB666: + case CSI_RGB888: + case CSI_RGB_10b: + case CSI_RGB_12b: + return V4L2_COLORSPACE_SRGB; + case CSI_YUV420_8b_L: + case CSI_YUV420_8b: + case CSI_YUV420_10b: + case CSI_YUV422_8b: + case CSI_YUV422_10b: + case CSI_YUV422_12b: + case CSI_YUV420_10b_L: + case CSI_YUV420_12b: + case CSI_YUV444_8b: + case CSI_YUV444_10b: + case CSI_YUV444_12b: + return V4L2_COLORSPACE_REC709; + default: + return 0; + } +} + +static int it6625_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + struct v4l2_dv_timings timings; + + if (format->pad !=3D 0) + return -EINVAL; + + it6625_get_timings(it6625, &timings); + format->format.width =3D timings.bt.width; + format->format.height =3D timings.bt.height; + format->format.field =3D timings.bt.interlaced =3D=3D V4L2_DV_INTERLACED ? + V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; + + if (format->which =3D=3D V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + format->format.colorspace =3D format_to_colorspace(it6625->csi_format); + fmt =3D v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code =3D fmt->code; + } else { + scoped_guard(mutex, &it6625->it6625_lock) { + format->format.colorspace =3D + format_to_colorspace(it6625->csi_format); + format->format.code =3D it6625->mbus_fmt_code; + } + } + + return 0; +} + +static int it6625_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + int ret; + u32 mbus_fmt_code =3D format->format.code; + + ret =3D it6625_get_fmt(sd, sd_state, format); + format->format.code =3D mbus_fmt_code; + + if (ret) + return ret; + + ret =3D it6625_csi_mbus_code_idx(mbus_fmt_code); + + if (ret < 0) { + v4l2_dbg(1, debug, sd, + "%s: unsupported format code 0x%x, falling back to default", + __func__, mbus_fmt_code); + ret =3D 0; + mbus_fmt_code =3D it6625_formats[ret].mbus_fmt_code; + format->format.code =3D mbus_fmt_code; + } + + if (format->which =3D=3D V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt =3D v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code =3D format->format.code; + v4l2_dbg(1, debug, sd, "%s: try format code =3D 0x%x", + __func__, format->format.code); + return 0; + } + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->csi_format =3D it6625_formats[ret].csi_format; + it6625->mbus_fmt_code =3D format->format.code; + } + it6625_enable_stream(it6625, false); + it6625_set_mipi_config(it6625, it6625->csi_format); + + return 0; +} + +static int it6625_g_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + int err; + + if (edid->pad !=3D 0) + return -EINVAL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->blocks =3D=3D 0) { + edid->blocks =3D EDID_NUM_BLOCKS_MAX; + return 0; + } + + if (edid->start_block >=3D EDID_NUM_BLOCKS_MAX) { + edid->blocks =3D 0; + return 0; + } + + if (edid->blocks > EDID_NUM_BLOCKS_MAX - edid->start_block) + edid->blocks =3D EDID_NUM_BLOCKS_MAX - edid->start_block; + + err =3D it6625_read_edid(it6625, edid->edid, edid->start_block, + edid->blocks); + + return (err < 0) ? err : 0; +} + +static int it6625_s_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + int err; + u16 pa; + + if (edid->pad !=3D 0) { + v4l2_err(sd, "invalid pad %d", edid->pad); + return -EINVAL; + } + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->blocks > EDID_NUM_BLOCKS_MAX) { + v4l2_err(sd, "too many edid blocks: %d", edid->blocks); + edid->blocks =3D EDID_NUM_BLOCKS_MAX; + return -E2BIG; + } + + it6625_disable_hpd(it6625); + + if (edid->blocks !=3D 0) { + err =3D it6625_write_edid(it6625, edid->edid, + edid->start_block, edid->blocks); + if (err < 0) { + v4l2_err(sd, "write edid failed"); + return err; + } + } + + pa =3D cec_get_edid_phys_addr(edid->edid, edid->blocks * 128, NULL); + if (pa =3D=3D CEC_PHYS_ADDR_INVALID) { + v4l2_warn(sd, "invalid CEC physical address in EDID"); + cec_phys_addr_invalidate(it6625->cec_adap); + } else { + v4l2_phys_addr_validate(pa, NULL, NULL); + cec_s_phys_addr(it6625->cec_adap, pa, false); + } + + if (hdmi_5v_power_present(it6625)) { + it6625_enable_hpd(it6625); + it6625_s_ctrl_detect_hdmi_5v(sd); + } else { + it6625_enable_auto_hpd(it6625); + } + + return 0; +} + +#define V4L2_CID_USER_IT6625_BASE (V4L2_CID_USER_BASE + 0x11b0) +#define IT6625_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_IT6625_BASE + 0) +#define IT6625_CID_AUDIO_PRESENT (V4L2_CID_USER_IT6625_BASE + 1) +#define IT6625_CID_HDMI_INFO (V4L2_CID_USER_IT6625_BASE + 2) +#define IT6625_CID_MIPI_CONFIG (V4L2_CID_USER_IT6625_BASE + 3) +static int it6625_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct it6625 *it6625 =3D hdl_to_6625(ctrl->handler); + struct v4l2_subdev *sd =3D &it6625->sd; + + v4l2_dbg(1, debug, sd, "%s, id=3D0x%x, val=3D%d", + __func__, ctrl->id, ctrl->val); + + switch (ctrl->id) { + case IT6625_CID_HDMI_INFO: + snprintf(ctrl->p_new.p_char, ctrl->maximum, + "RGB444 10bit @ 60Hz"); + break; + case IT6625_CID_MIPI_CONFIG: + ctrl->val =3D it6625_read_byte(it6625, REG_MIPI_DATA_TYPE); + break; + default: + break; + } + + return 0; +} + +static int it6625_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct it6625 *it6625 =3D hdl_to_6625(ctrl->handler); + struct v4l2_subdev *sd =3D &it6625->sd; + + v4l2_dbg(1, debug, sd, "%s, id=3D0x%x, val=3D%d", + __func__, ctrl->id, ctrl->val); + + switch (ctrl->id) { + case IT6625_CID_MIPI_CONFIG: + it6625_set_mipi_config(it6625, ctrl->val); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops it6625_core_ops =3D { + .log_status =3D it6625_log_status, + .interrupt_service_routine =3D it6625_isr, + .subscribe_event =3D it6625_subscribe_event, + .unsubscribe_event =3D v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops it6625_video_ops =3D { + .g_input_status =3D it6625_g_input_status, + .s_stream =3D it6625_s_stream, +}; + +static const struct v4l2_subdev_pad_ops it6625_pad_ops =3D { + .enum_mbus_code =3D it6625_enum_mbus_code, + .set_fmt =3D it6625_set_fmt, + .get_fmt =3D it6625_get_fmt, + .get_edid =3D it6625_g_edid, + .set_edid =3D it6625_s_edid, + .enum_dv_timings =3D it6625_enum_dv_timings, + .dv_timings_cap =3D it6625_dv_timings_cap, + .get_mbus_config =3D it6625_get_mbus_config, + .s_dv_timings =3D it6625_pad_s_dv_timings, + .g_dv_timings =3D it6625_pad_g_dv_timings, + .query_dv_timings =3D it6625_pad_query_dv_timings, +}; + +static const struct v4l2_subdev_ops it6625_ops =3D { + .core =3D &it6625_core_ops, + .video =3D &it6625_video_ops, + .pad =3D &it6625_pad_ops, +}; + +static const struct v4l2_ctrl_ops it6625_ctrl_ops =3D { + .g_volatile_ctrl =3D it6625_g_volatile_ctrl, + .s_ctrl =3D it6625_s_ctrl, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_sampling_rate =3D { + .id =3D IT6625_CID_AUDIO_SAMPLING_RATE, + .name =3D "Audio sampling rate", + .type =3D V4L2_CTRL_TYPE_INTEGER, + .min =3D 0, + .max =3D 768000, + .step =3D 1, + .def =3D 0, + .flags =3D V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_present =3D { + .id =3D IT6625_CID_AUDIO_PRESENT, + .name =3D "Audio present", + .type =3D V4L2_CTRL_TYPE_BOOLEAN, + .min =3D 0, + .max =3D 1, + .step =3D 1, + .def =3D 0, + .flags =3D V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_get_hdmi_info =3D { + .ops =3D &it6625_ctrl_ops, + .id =3D IT6625_CID_HDMI_INFO, + .name =3D "HDMI Info", + .type =3D V4L2_CTRL_TYPE_STRING, + .max =3D 128, + .step =3D 1, + .flags =3D V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_mipi_config =3D { + .ops =3D &it6625_ctrl_ops, + .id =3D IT6625_CID_MIPI_CONFIG, + .name =3D "MIPI config", + .type =3D V4L2_CTRL_TYPE_INTEGER, + .min =3D 0, + .max =3D 0x7FFFFFFF, + .step =3D 1, + .def =3D 0x3E, + .flags =3D V4L2_CTRL_FLAG_VOLATILE, +}; + +static int it6625_v4l2_init_controls(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 =3D sd_to_6625(sd); + struct v4l2_ctrl_handler *hdl =3D &it6625->hdl; + + v4l2_ctrl_handler_init(hdl, 4); + it6625->ctrl_5v_detect =3D + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT, + 0, 1, 0, 0); + + it6625->ctrl_audio_sampling_rate =3D + v4l2_ctrl_new_custom(hdl, + &it6625_ctrl_audio_sampling_rate, + NULL); + it6625->ctrl_audio_present =3D + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_present, NULL); + it6625->ctrl_link_freq =3D + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(it6625_link_freq) - 1, + 0, it6625_link_freq); + it6625->ctrl_get_hdmi_info =3D + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_get_hdmi_info, NULL); + it6625->ctrl_mipi_config =3D + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_mipi_config, NULL); + if (hdl->error) { + v4l2_err(sd, "Failed to initialize controls"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + sd->ctrl_handler =3D hdl; + + return 0; +} + +static void it6625_regdump_print(struct seq_file *s, const u8 *reg_buf) +{ + int i; + + seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A = 0x0B 0x0C 0x0D 0x0E 0x0F\n"); + + for (i =3D 0; i < 256; i++) { + if (i % 16 =3D=3D 0) + seq_printf(s, "[%02X] ", i & 0xF0); + seq_printf(s, "0x%02X ", reg_buf[i]); + if (i % 16 =3D=3D 15) + seq_putc(s, '\n'); + } +} + +static int it6625_edid_ram_show(struct seq_file *s, void *data) +{ + struct it6625 *it6625 =3D s->private; + u8 reg_buf[256]; + + it6625_read_edid(it6625, reg_buf, 0, 2); + it6625_regdump_print(s, reg_buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(it6625_edid_ram); + +static int it6625_mipi_reg_show(struct seq_file *s, void *data) +{ + struct it6625 *it6625 =3D s->private; + u8 reg_buf[256]; + + scoped_guard(mutex, &it6625->it6625_lock) + it6625_read_bytes(it6625, 0x00, reg_buf, sizeof(reg_buf)); + it6625_regdump_print(s, reg_buf); + + return 0; +} + +static int it6625_mipi_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, it6625_mipi_reg_show, inode->i_private); +} + +static ssize_t it6625_mipi_reg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct it6625 *it6625 =3D file_inode(file)->i_private; + char buf[32] =3D {}; + unsigned int addr, val; + ssize_t len; + loff_t pos =3D 0; + + len =3D simple_write_to_buffer(buf, sizeof(buf) - 1, &pos, user_buf, coun= t); + if (len < 0) + return len; + buf[len] =3D '\0'; + + if (sscanf(buf, "%X %X", &addr, &val) !=3D 2) + return -EINVAL; + + it6625_write_byte(it6625, addr, val); + + return count; +} + +static const struct file_operations it6625_mipi_reg_fops =3D { + .owner =3D THIS_MODULE, + .open =3D it6625_mipi_reg_open, + .read =3D seq_read, + .write =3D it6625_mipi_reg_write, + .llseek =3D seq_lseek, + .release =3D single_release, +}; + +static void it6625_debugfs_init(struct it6625 *it6625, struct i2c_client *= client) +{ + it6625->debugfs_dir =3D debugfs_create_dir(dev_name(&client->dev), NULL); + + debugfs_create_file("mipi_reg", 0600, it6625->debugfs_dir, it6625, + &it6625_mipi_reg_fops); + debugfs_create_file("edid_ram", 0400, it6625->debugfs_dir, it6625, + &it6625_edid_ram_fops); +} + +static void it6625_init_data(struct it6625 *it6625) +{ + static struct v4l2_dv_timings default_timing =3D + V4L2_DV_BT_CEA_1920X1080P60; + + it6625->csi_lanes =3D 4; + it6625->port_num =3D 1; + it6625->bus_type =3D V4L2_MBUS_CSI2_DPHY; + it6625->csi_format =3D it6625_formats[0].csi_format; + it6625->mbus_fmt_code =3D it6625_formats[0].mbus_fmt_code; + it6625->timings =3D default_timing; +} + +static int it6625_parse_endpoint(struct it6625 *it6625) +{ + struct device *dev =3D it6625->dev; + struct v4l2_fwnode_endpoint endpoint =3D { .bus_type =3D V4L2_MBUS_CSI2_D= PHY }; + struct device_node *ep =3D NULL; + unsigned int max_lanes; + unsigned int port; + int ret; + + /* + * port@0 and port@1 are the two CSI-2 output ports MIPI0/MIPI1 + * (port@2 is the HDMI input). This chip series can drive both + * simultaneously in split or mirror mode, so port_num counts how + * many of MIPI0/MIPI1 have an endpoint wired up. This driver only + * wires up a single source pad, so lane/bus-type configuration is + * parsed from whichever of the two is found first. + */ + it6625->port_num =3D 0; + for (port =3D 0; port < 2; port++) { + struct device_node *port_ep =3D + of_graph_get_endpoint_by_regs(dev->of_node, port, -1); + + if (!port_ep) + continue; + + it6625->port_num++; + if (!ep) + ep =3D port_ep; + else + of_node_put(port_ep); + } + + if (!ep) { + it6625->port_num =3D 1; + dev_dbg(dev, "no CSI-2 endpoint node found, using default %u CSI lanes", + it6625->csi_lanes); + return 0; + } + + ret =3D v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + of_node_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint: %d", ret); + return ret; + } + + if (endpoint.bus_type =3D=3D V4L2_MBUS_CSI2_CPHY && + it6625->chip_type !=3D IT6626_CHIP) { + dev_err(dev, "IT6625 does not support C-PHY, only IT6626 does"); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + max_lanes =3D (endpoint.bus_type =3D=3D V4L2_MBUS_CSI2_CPHY) ? 3 : 4; + + if (endpoint.bus.mipi_csi2.num_data_lanes =3D=3D 0 || + endpoint.bus.mipi_csi2.num_data_lanes > max_lanes) { + dev_err(dev, + "invalid number of CSI data lanes: %u (max %u for this bus type)", + endpoint.bus.mipi_csi2.num_data_lanes, max_lanes); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + it6625->csi_lanes =3D endpoint.bus.mipi_csi2.num_data_lanes; + it6625->bus_type =3D endpoint.bus_type; + v4l2_fwnode_endpoint_free(&endpoint); + + return 0; +} + +static int it6625_parse_dt(struct it6625 *it6625) +{ + return it6625_parse_endpoint(it6625); +} + +static int it6625_init_v4l2_subdev(struct it6625 *it6625) +{ + struct v4l2_subdev *sd =3D &it6625->sd; + int err; + + sd->dev =3D it6625->dev; + + v4l2_i2c_subdev_init(sd, it6625->i2c_client, &it6625_ops); + sd->flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + if (it6625_v4l2_init_controls(sd)) { + dev_err(it6625->dev, "Failed to initialize v4l2 controls"); + return -ENOMEM; + } + + it6625->pad.flags =3D MEDIA_PAD_FL_SOURCE; + sd->entity.function =3D MEDIA_ENT_F_CAM_SENSOR; + err =3D media_entity_pads_init(&sd->entity, 1, &it6625->pad); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=3D%d", __func__, __LINE__, err); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return err; + } + + return 0; +} + +static int it6625_check_device(struct it6625 *it6625) +{ + static const u8 chip_ids[][2] =3D { + { 0x66, 0x25 }, + { 0x66, 0x26 }, + }; + int chip_id0, chip_id1; + + chip_id0 =3D it6625_read_byte(it6625, REG_CHIP_ID_0); + chip_id1 =3D it6625_read_byte(it6625, REG_CHIP_ID_1); + if (chip_id0 !=3D chip_ids[it6625->chip_type][0] || + chip_id1 !=3D chip_ids[it6625->chip_type][1]) { + dev_err(it6625->dev, + "chip ID mismatch: got 0x%02x%02x, expected 0x%02x%02x", + chip_id0, chip_id1, + chip_ids[it6625->chip_type][0], + chip_ids[it6625->chip_type][1]); + return -ENODEV; + } + + return 0; +} + +static int it6625_probe(struct i2c_client *client) +{ + struct it6625 *it6625; + struct v4l2_subdev *sd; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + it6625 =3D devm_kzalloc(&client->dev, sizeof(struct it6625), GFP_KERNEL); + if (!it6625) + return -ENOMEM; + + it6625->chip_type =3D (uintptr_t)i2c_get_match_data(client); + + err =3D it6625_regmap_i2c_init(client, it6625); + if (err) + return err; + + err =3D it6625_check_device(it6625); + if (err) + return err; + + it6625_init_data(it6625); + + err =3D it6625_parse_dt(it6625); + if (err) + return err; + + mutex_init(&it6625->it6625_lock); + INIT_DELAYED_WORK(&it6625->hpd_delayed_work, it6625_hpd_delayed_work); + INIT_WORK(&it6625->polling_work, it6625_polling_work); + + if (client->irq) { + err =3D devm_request_threaded_irq(&client->dev, client->irq, + NULL, it6625_irq_handler, + IRQF_TRIGGER_LOW | + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6625", it6625); + if (err) + return err; + } else { + dev_info(it6625->dev, "no IRQ, falling back to polling"); + timer_setup(&it6625->timer, it6625_irq_poll_timer, 0); + } + + sd =3D &it6625->sd; + err =3D it6625_init_v4l2_subdev(it6625); + if (err) + goto err_clean_work_queues; + + err =3D v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_clean_hdl; + + it6625->cec_adap =3D cec_allocate_adapter(&it6625_cec_adap_ops, + it6625, dev_name(it6625->dev), + CEC_CAP_DEFAULTS | + CEC_CAP_MONITOR_ALL | + CEC_CAP_PHYS_ADDR, + CEC_MAX_LOG_ADDRS); + if (IS_ERR(it6625->cec_adap)) { + err =3D PTR_ERR(it6625->cec_adap); + dev_err(it6625->dev, "%s %d", __func__, __LINE__); + goto err_clean_hdl; + } + + err =3D cec_register_adapter(it6625->cec_adap, &client->dev); + if (err < 0) { + dev_err(it6625->dev, "%s: failed to register the cec device", __func__); + cec_delete_adapter(it6625->cec_adap); + it6625->cec_adap =3D NULL; + goto err_clean_hdl; + } + + it6625_debugfs_init(it6625, client); + + it6625_initial_setup(it6625); + it6625_v4l2_sd_ctrl_update(sd); + + err =3D v4l2_async_register_subdev(sd); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=3D%d", __func__, __LINE__, err); + goto err_clean_debugfs; + } + + if (client->irq) + enable_irq(client->irq); + else + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); + + return 0; + +err_clean_debugfs: + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); +err_clean_hdl: + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); + +err_clean_work_queues: + if (!client->irq) + timer_shutdown_sync(&it6625->timer); + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + mutex_destroy(&it6625->it6625_lock); + return err; +} + +static void it6625_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct it6625 *it6625 =3D sd_to_6625(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_device_unregister_subdev(sd); + + if (client->irq) + disable_irq(client->irq); + else + timer_shutdown_sync(&it6625->timer); + + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); + mutex_destroy(&it6625->it6625_lock); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); +} + +static const struct i2c_device_id it6625_id[] =3D { + { .name =3D "it6625", .driver_data =3D IT6625_CHIP }, + { .name =3D "it6626", .driver_data =3D IT6626_CHIP }, + {} +}; +MODULE_DEVICE_TABLE(i2c, it6625_id); + +static const struct of_device_id it6625_of_match[] =3D { + { .compatible =3D "ite,it6625", .data =3D (void *)IT6625_CHIP }, + { .compatible =3D "ite,it6626", .data =3D (void *)IT6626_CHIP }, + {}, +}; +MODULE_DEVICE_TABLE(of, it6625_of_match); + +static struct i2c_driver it6625_driver =3D { + .driver =3D { + .name =3D "it6625", + .of_match_table =3D of_match_ptr(it6625_of_match), + }, + .probe =3D it6625_probe, + .remove =3D it6625_remove, + .id_table =3D it6625_id, +}; +module_i2c_driver(it6625_driver); + +MODULE_DESCRIPTION("iTE it6625/it6626 HDMI to MIPI CSI bridge driver"); +MODULE_AUTHOR("Hermes Wu "); +MODULE_LICENSE("GPL"); --=20 2.34.1