* [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S
@ 2026-11-03 6:50 Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 1/5] dt-bindings: media: ite,it6625: add audio DAI properties Hermes Wu via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
IT6625 and IT6626 decode the audio of their HDMI input and output it on
an I2S interface, driving the bit and frame clocks themselves. The audio
format is not configurable by the host; the bridge reports the stream it
passes through in read-only status registers.
This series adds an ASoC capture DAI to the IT6625 V4L2 driver, so that
this audio can be recorded through a SoC I2S controller, for example
with simple-audio-card.
Patch 1 adds the '#sound-dai-cells' and 'ite,i2s-data-lanes' properties
to the binding.
Patch 2 documents the existing driver-specific audio controls.
Patch 3 decodes the audio status registers with FIELD_GET() and reads
them in one place.
Patch 4 adds the I2S capture DAI for LPCM.
Patch 5 extends the DAI to compressed (IEC 61937) and HBR streams, and
adds a read-only audio type control.
The series is based on media next and does not depend on the pending
IT6625 reset timing [1] and DV timings [2] series; either applies before
or after this one.
Tested by capturing HDMI audio from a signal generator, a PC and a
second HDMI output looped back to the bridge, through an SoC I2S
controller with simple-audio-card:
- LPCM at 32 kHz to 192 kHz with 2 channels on one data line and 8
channels on four data lines, from 16-bit and 24-bit sources. 128 kHz
was not captured, as the I2S controller used does not support it.
- AC-3, DTS-HD and TrueHD bitstreams, captured bit-exact, including
high bit rate streams on four data lines.
- Rate, channel and coding changes, unplug, AV mute and pause/resume
while capturing, and the audio controls with no capture open.
One-bit, multi-stream and 3D audio could not be tested; the driver
rejects them.
[1] https://lore.kernel.org/r/20261007-upstream-it6625-reset-delay-v2-0-c5d6b2522bbe@ite.com.tw
[2] https://lore.kernel.org/r/20261103-upstream-it6625-dv-timings-fix-v2-0-127b879da6d3@ite.com.tw
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Hermes Wu (5):
dt-bindings: media: ite,it6625: add audio DAI properties
media: Documentation: it6625: document the audio controls
media: i2c: it6625: decode audio status fields with FIELD_GET()
media: i2c: it6625: add I2S audio capture DAI
media: i2c: it6625: capture compressed and HBR audio, not just LPCM
.../devicetree/bindings/media/i2c/ite,it6625.yaml | 23 +-
.../userspace-api/media/drivers/index.rst | 1 +
.../userspace-api/media/drivers/it6625.rst | 48 ++
MAINTAINERS | 1 +
drivers/media/i2c/Kconfig | 1 +
drivers/media/i2c/it6625.c | 879 +++++++++++++++++++--
include/uapi/linux/it6625.h | 25 +
7 files changed, 899 insertions(+), 79 deletions(-)
---
base-commit: d7e7bca7169873b46e857c025c10f3336c1258d8
change-id: 20261019-upstream-it6625-alsa-i2s-9a8e34878dbc
Best regards,
--
Hermes Wu <Hermes.wu@ite.com.tw>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/5] dt-bindings: media: ite,it6625: add audio DAI properties
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
@ 2026-11-03 6:50 ` Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 2/5] media: Documentation: it6625: document the audio controls Hermes Wu via B4 Relay
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
From: Hermes Wu <Hermes.wu@ite.com.tw>
IT6625 and IT6626 decode the HDMI audio stream and output it on an I2S
interface, driving the bit and frame clocks themselves. The bridge has up
to eight I2S data pins.
Add '#sound-dai-cells' so the bridge can be referenced as an audio codec
DAI, and 'ite,i2s-data-lanes' to describe which data pins are connected
to the audio receiver, in HDMI channel-pair order. Without the property
only SD0 is connected.
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
.../devicetree/bindings/media/i2c/ite,it6625.yaml | 23 +++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml
index 9b621f726249189255692da02049ebcbf1d5b43c..9304a0507903e7eaee44fa565e49c6938e9eb7d8 100644
--- a/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml
@@ -43,6 +43,23 @@ properties:
ovdd-supply:
description: I/O supply voltage
+ '#sound-dai-cells':
+ const: 0
+
+ ite,i2s-data-lanes:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 8
+ uniqueItems: true
+ items:
+ enum: [ 0, 1, 2, 3, 4, 5, 6, 7 ]
+ description:
+ I2S audio data pins (SD0 - SD7) that are connected to the audio
+ receiver, in HDMI channel-pair order. The first entry is the pin
+ carrying HDMI channels 1 and 2, the second the pin carrying channels 3
+ and 4, and so on. The bridge drives the I2S bit and frame clocks. If
+ the property is absent, only SD0 is connected.
+
ports:
$ref: /schemas/graph.yaml#/properties/ports
properties:
@@ -111,6 +128,7 @@ required:
- ovdd-supply
allOf:
+ - $ref: /schemas/sound/dai-common.yaml#
- if:
properties:
compatible:
@@ -131,7 +149,7 @@ allOf:
required:
- bus-type
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
@@ -153,6 +171,9 @@ examples:
vdd33-supply = <&vdd33_reg>;
ovdd-supply = <&ovdd_reg>;
+ #sound-dai-cells = <0>;
+ ite,i2s-data-lanes = <0 1 2 3>;
+
ports {
#address-cells = <1>;
#size-cells = <0>;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/5] media: Documentation: it6625: document the audio controls
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 1/5] dt-bindings: media: ite,it6625: add audio DAI properties Hermes Wu via B4 Relay
@ 2026-11-03 6:50 ` Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 3/5] media: i2c: it6625: decode audio status fields with FIELD_GET() Hermes Wu via B4 Relay
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
From: Hermes Wu <Hermes.wu@ite.com.tw>
The IT6625 driver exposes two read-only driver-specific controls,
V4L2_CID_IT6625_AUDIO_SAMPLING_RATE and V4L2_CID_IT6625_AUDIO_PRESENT,
which so far are described only in the uAPI header.
Add a page for the driver to the V4L2 driver-specific documentation that
describes both controls, and list it in MAINTAINERS.
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
Documentation/userspace-api/media/drivers/index.rst | 1 +
Documentation/userspace-api/media/drivers/it6625.rst | 18 ++++++++++++++++++
MAINTAINERS | 1 +
3 files changed, 20 insertions(+)
diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst
index 8d2c489b8453e11b8e15ef9d9c1c0312f3da06af..57be8dd5749d7f5aa6e0e21c7e3d89d377a209d0 100644
--- a/Documentation/userspace-api/media/drivers/index.rst
+++ b/Documentation/userspace-api/media/drivers/index.rst
@@ -33,6 +33,7 @@ For more details see the file COPYING in the source distribution of Linux.
dcmipp
dw100
imx-uapi
+ it6625
mali-c55
max2175
npcm-video
diff --git a/Documentation/userspace-api/media/drivers/it6625.rst b/Documentation/userspace-api/media/drivers/it6625.rst
new file mode 100644
index 0000000000000000000000000000000000000000..efd970e8124979abffb6f9ba3ea1e1bc193820b3
--- /dev/null
+++ b/Documentation/userspace-api/media/drivers/it6625.rst
@@ -0,0 +1,18 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+ITE IT6625/IT6626 HDMI to MIPI bridge driver
+============================================
+
+The IT6625 driver implements the following driver-specific controls. All of
+them are read-only and describe the audio of the HDMI input.
+
+``V4L2_CID_IT6625_AUDIO_SAMPLING_RATE (integer)``
+-------------------------------------------------
+ The sampling rate of the HDMI audio stream, in Hz. 0 means that the rate
+ is not available: there is no stable input signal or no audio, the bridge
+ reported a rate code that the driver does not recognize, or reading the
+ audio status failed.
+
+``V4L2_CID_IT6625_AUDIO_PRESENT (boolean)``
+-------------------------------------------
+ Whether the bridge currently receives HDMI audio.
diff --git a/MAINTAINERS b/MAINTAINERS
index 760791e3635f3c306e7fed7240b948d29b45ae66..1d6de374d86d3046d993bf220195b1a75049fa8a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13972,6 +13972,7 @@ M: Hermes Wu <Hermes.Wu@ite.com.tw>
S: Maintained
T: git git://linuxtv.org/media.git
F: Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml
+F: Documentation/userspace-api/media/drivers/it6625.rst
F: drivers/media/i2c/it6625.c
F: include/uapi/linux/it6625.h
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/5] media: i2c: it6625: decode audio status fields with FIELD_GET()
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 1/5] dt-bindings: media: ite,it6625: add audio DAI properties Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 2/5] media: Documentation: it6625: document the audio controls Hermes Wu via B4 Relay
@ 2026-11-03 6:50 ` Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 5/5] media: i2c: it6625: capture compressed and HBR audio, not just LPCM Hermes Wu via B4 Relay
4 siblings, 0 replies; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
From: Hermes Wu <Hermes.wu@ite.com.tw>
The sampling-rate control compares the whole HDMI_AUDIO_INFO1 byte
against the AUD_FS code table, although AUD_FS occupies only bits
[5:0]. Any set upper bit makes every rate read back as 0.
Describe the audio registers with GENMASK() fields and decode them with
FIELD_GET(). Collect the audio status in one helper that runs under
it6625_lock: it reads RX_STATUS once, for both the video-stable and the
audio-present bits, and reads the read-only stream description in
0x6B-0x6E as a single 4-byte transfer only while audio is present. Both
audio controls are then fed from that one sample.
Drop the 0x6E alignment/delay/polarity/shift defines; they were unused,
and the audio output framing is fixed by the firmware.
Apart from masking AUD_FS, the controls behave as before: the rate
reads 0 when there is no stable signal, no audio, an unknown code or a
read error, and the present control follows the audio-present bit.
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
drivers/media/i2c/it6625.c | 206 +++++++++++++++++++++++++++++----------------
1 file changed, 132 insertions(+), 74 deletions(-)
diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c
index b74df9dbf8c2cab7461c69bc0f926141ed7a506c..6f12a0e7601784e5b4ce2b395ad58a0dee335f27 100644
--- a/drivers/media/i2c/it6625.c
+++ b/drivers/media/i2c/it6625.c
@@ -83,25 +83,34 @@ MODULE_PARM_DESC(debug, "debug level (0-3)");
#define REG_HDMI_VIDEO_INFO 0x6A
#define B_COLOR_MODE BIT(0)
#define B_COLOR_DEPTH BIT(4)
+/*
+ * 0x6B-0x6E describe the decoded HDMI audio stream that the firmware passes
+ * through to the audio output. They are read-only and only meaningful while
+ * REG_RX_STATUS reports audio present.
+ */
#define REG_HDMI_AUDIO_INFO1 0x6B
-#define B_AUD_FS BIT(0)
+#define M_AUD_FS GENMASK(5, 0)
#define REG_HDMI_AUDIO_INFO2 0x6C
-#define B_AUD_CH BIT(0)
-#define B_AUD_WL BIT(4)
+#define M_AUD_CH GENMASK(3, 0)
+#define M_AUD_WL GENMASK(7, 4)
#define REG_HDMI_AUDIO_INFO3 0x6D
-#define B_AUD_TYPE BIT(0)
+#define M_AUD_TYPE GENMASK(1, 0)
+#define AUD_TYPE_LPCM 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 M_I2S_WL GENMASK(1, 0)
+#define I2S_WL_16BIT 0
+#define I2S_WL_18BIT 1
+#define I2S_WL_20BIT 2
+#define I2S_WL_24BIT 3
+#define M_AUD_OUT_IF GENMASK(7, 6)
+#define AUD_OUT_IF_I2S 0
+
+#define AUDIO_INFO_LEN 4
#define REG_IF_LATCH_HB 0x6F
#define REG_IF_DATA 0x70
@@ -249,6 +258,28 @@ enum it6625_chip_type {
IT6626_CHIP = 1,
};
+/* decoded 0x6B-0x6E: the HDMI audio stream passed through to the output */
+struct it6625_audio_stream {
+ u8 fs_code;
+ u8 channels;
+ u8 type;
+ bool multi_stream;
+ bool is_3d;
+ u8 i2s_wl;
+ u8 out_if;
+};
+
+struct it6625_audio_info {
+ /* false if any status read failed */
+ bool read_ok;
+ /* REG_RX_STATUS */
+ bool vid_stable;
+ bool present;
+ bool avmute;
+ /* only refreshed while present */
+ struct it6625_audio_stream stream;
+};
+
struct it6625 {
struct device *dev;
struct i2c_client *i2c_client;
@@ -584,61 +615,94 @@ static inline bool no_signal(struct it6625 *it6625)
return val < 0 ? true : !(val & B_RX_STABLE);
}
-static inline bool audio_present(struct it6625 *it6625)
+static const struct it6625_audio_rate {
+ u8 fs_code;
+ u32 rate;
+} it6625_audio_rates[] = {
+ { AUD32K, 32000 },
+ { AUD44K, 44100 },
+ { AUD48K, 48000 },
+ { AUD64K, 64000 },
+ { AUD88K, 88200 },
+ { AUD96K, 96000 },
+
+ { AUD128K, 128000 },
+ { AUD176K, 176400 },
+ { AUD192K, 192000 },
+ { AUD256K, 256000 },
+ { AUD352K, 352800 },
+ { AUD384K, 384000 },
+
+ { AUD512K, 512000 },
+ { AUD705K, 705600 },
+ { AUD768K, 768000 },
+ { AUD1024K, 1024000 },
+ { AUD1411K, 1411200 },
+ { AUD1536K, 1536000 },
+};
+
+/* returns 0 for an unknown sampling-frequency code */
+static u32 it6625_audio_fs_to_rate(u8 fs_code)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(it6625_audio_rates); i++) {
+ if (it6625_audio_rates[i].fs_code == fs_code)
+ return it6625_audio_rates[i].rate;
+ }
+
+ return 0;
+}
+
+/*
+ * Sample the audio status. REG_RX_STATUS is always read; the stream
+ * description in 0x6B-0x6E is only read while audio is present, otherwise
+ * info->stream is left untouched. read_ok is false if any read failed.
+ */
+static void it6625_read_audio_info(struct it6625 *it6625,
+ struct it6625_audio_info *info)
{
+ u8 buf[AUDIO_INFO_LEN];
int val;
- val = 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)
-{
- static const struct fs_id_map {
- u8 fs_id;
- u32 freq;
- } s_fsid_map[] = {
- { AUD32K, 32000 },
- { AUD44K, 44100 },
- { AUD48K, 48000 },
- { AUD64K, 64000 },
- { AUD88K, 88200 },
- { AUD96K, 96000 },
-
- { AUD128K, 128000 },
- { AUD176K, 176400 },
- { AUD192K, 192000 },
- { AUD256K, 256000 },
- { AUD352K, 352800 },
- { AUD384K, 384000 },
-
- { AUD512K, 512000 },
- { AUD705K, 705600 },
- { AUD768K, 768000 },
- { AUD1024K, 1024000 },
- { AUD1411K, 1411200 },
- { AUD1536K, 1536000 },
- };
- int fs_id;
- int i, freq = 0;
+ lockdep_assert_held(&it6625->it6625_lock);
- if (no_signal(it6625) || !audio_present(it6625))
- return 0;
+ val = it6625_read_byte(it6625, REG_RX_STATUS);
+ if (val < 0) {
+ info->read_ok = false;
+ info->vid_stable = false;
+ info->present = false;
+ info->avmute = false;
+ return;
+ }
- guard(mutex)(&it6625->it6625_lock);
+ info->read_ok = true;
+ info->vid_stable = val & B_RX_STABLE;
+ info->present = val & B_RX_AUD_ON;
+ info->avmute = val & B_RX_AVMUTE;
- fs_id = it6625_read_byte(it6625, REG_HDMI_AUDIO_INFO1);
- if (fs_id < 0)
- return 0;
+ if (!info->present)
+ return;
- for (i = 0; i < ARRAY_SIZE(s_fsid_map); i++) {
- if (s_fsid_map[i].fs_id == fs_id) {
- freq = s_fsid_map[i].freq;
- break;
- }
+ if (it6625_read_bytes(it6625, REG_HDMI_AUDIO_INFO1, buf, sizeof(buf))) {
+ info->read_ok = false;
+ return;
}
- return freq;
+ info->stream.fs_code = FIELD_GET(M_AUD_FS, buf[0]);
+ info->stream.channels = FIELD_GET(M_AUD_CH, buf[1]);
+ info->stream.type = FIELD_GET(M_AUD_TYPE, buf[2]);
+ info->stream.multi_stream = buf[2] & B_AUD_MS;
+ info->stream.is_3d = buf[2] & B_AUD_3D;
+ info->stream.i2s_wl = FIELD_GET(M_I2S_WL, buf[3]);
+ info->stream.out_if = FIELD_GET(M_AUD_OUT_IF, buf[3]);
+}
+
+/* sampling rate for the V4L2 control: 0 means unavailable */
+static u32 it6625_audio_info_rate(const struct it6625_audio_info *info)
+{
+ if (!info->read_ok || !info->vid_stable || !info->present)
+ return 0;
+
+ return it6625_audio_fs_to_rate(info->stream.fs_code);
}
static u64 it6625_get_pclk(struct it6625 *it6625)
@@ -888,20 +952,21 @@ static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd)
hdmi_5v_power_present(it6625));
}
-static int it6625_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd)
+static int it6625_s_ctrl_audio(struct v4l2_subdev *sd)
{
struct it6625 *it6625 = sd_to_6625(sd);
+ struct it6625_audio_info info = {};
+ int ret;
- return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate,
- get_audio_sampling_rate(it6625));
-}
+ scoped_guard(mutex, &it6625->it6625_lock)
+ it6625_read_audio_info(it6625, &info);
-static int it6625_s_ctrl_audio_present(struct v4l2_subdev *sd)
-{
- struct it6625 *it6625 = sd_to_6625(sd);
+ ret = v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate,
+ it6625_audio_info_rate(&info));
+ if (ret)
+ return ret;
- return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present,
- audio_present(it6625));
+ return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, info.present);
}
static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd)
@@ -912,11 +977,7 @@ static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd)
if (ret)
return ret;
- ret = it6625_s_ctrl_audio_sampling_rate(sd);
- if (ret)
- return ret;
-
- return it6625_s_ctrl_audio_present(sd);
+ return it6625_s_ctrl_audio(sd);
}
static int it6625_enable_stream_locked(struct it6625 *it6625, bool enable)
@@ -1165,10 +1226,7 @@ static void it6625_irq_format_change(struct it6625 *it6625)
static void it6625_irq_hdmi_audio_change(struct it6625 *it6625)
{
- struct v4l2_subdev *sd = &it6625->sd;
-
- it6625_s_ctrl_audio_sampling_rate(sd);
- it6625_s_ctrl_audio_present(sd);
+ it6625_s_ctrl_audio(&it6625->sd);
}
static void it6625_get_timings(struct it6625 *it6625,
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
` (2 preceding siblings ...)
2026-11-03 6:50 ` [PATCH 3/5] media: i2c: it6625: decode audio status fields with FIELD_GET() Hermes Wu via B4 Relay
@ 2026-11-03 6:50 ` Hermes Wu via B4 Relay
2026-11-03 7:05 ` sashiko-bot
2026-11-03 6:50 ` [PATCH 5/5] media: i2c: it6625: capture compressed and HBR audio, not just LPCM Hermes Wu via B4 Relay
4 siblings, 1 reply; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
From: Hermes Wu <Hermes.wu@ite.com.tw>
IT6625 and IT6626 decode the HDMI audio stream and pass it through to an
I2S output. The bridge drives the bit and frame clocks and uses standard
I2S framing with 16, 20 or 24 valid bits in 32-bit slots. None of this
is host-configurable: 0x6B-0x6E only report the stream being output.
Register an ASoC component with one capture DAI when the device node has
'#sound-dai-cells'. The DAI accepts only what the bridge currently
outputs: single-stream, non-3D LPCM at 32 kHz to 192 kHz, S32_LE with the
valid bits reported through msbits, and 2 channels per connected I2S
data pin (from 'ite,i2s-data-lanes'), up to 8 channels. startup pins
these values, and hw_params, prepare and the start and resume triggers
check them again against the latest observed status.
The I2S clocks are not guaranteed to keep running at a constant rate
when audio is lost, muted or changes format. Any observed change of the
audio status, and any status read failure, therefore stops a running
capture with an XRUN. A paused stream whose audio changed is still
released, and then stopped with an XRUN: refusing the release would
leave it paused, where a blocking read waits indefinitely. Changes are
observed on the audio-change, video-change, 5V and AV-mute events, and
every 200 ms while a capture stream is open, to catch changes that raise
no event.
The V4L2 audio controls are updated from the same status sample. The
bridge can report audio before the video signal is stable or the
sampling-frequency code is valid, and may raise no further event once
they are. After an event that leaves audio present without a valid rate,
the status is therefore checked again every 200 ms, up to 15 times, also
when no capture stream is open.
The component is registered without devm and unregistered first in
remove. Unregistering waits for open capture streams to be closed, so no
DAI callback can run while the rest of the driver is torn down. Unbind
therefore blocks while a capture stream is held open.
The driver still builds without ASoC: the audio code is compiled out
when SND_SOC is disabled.
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
drivers/media/i2c/Kconfig | 1 +
drivers/media/i2c/it6625.c | 609 +++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 595 insertions(+), 15 deletions(-)
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 7de2d095c96960070a3eaed859925fc97c0e12ca..6c97bccd6794e9c0821c903d2aa89a48badef482 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1354,6 +1354,7 @@ config VIDEO_IT6625
tristate "IT6625 HDMI to MIPI CSI bridge"
depends on VIDEO_DEV && I2C
depends on OF
+ depends on SND_SOC || !SND_SOC
select CEC_CORE
select HDMI
select MEDIA_CONTROLLER
diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c
index 6f12a0e7601784e5b4ce2b395ad58a0dee335f27..f1bd42ad15eda1a2bd22dac00ba11a5b1c790e71 100644
--- a/drivers/media/i2c/it6625.c
+++ b/drivers/media/i2c/it6625.c
@@ -30,6 +30,9 @@
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
#include <uapi/linux/it6625.h>
static int debug;
@@ -112,6 +115,15 @@ MODULE_PARM_DESC(debug, "debug level (0-3)");
#define AUDIO_INFO_LEN 4
+/* I2S capture DAI */
+#define IT6625_AUDIO_MAX_LANES 8
+#define IT6625_AUDIO_MAX_CHANNELS 8
+#define IT6625_AUDIO_MAX_RATE 192000
+/* status re-check period while a capture stream is open */
+#define IT6625_AUDIO_REFRESH_MS 200
+/* re-checks after an event that left present audio without a valid rate */
+#define IT6625_AUDIO_SETTLE_POLLS 15
+
#define REG_IF_LATCH_HB 0x6F
#define REG_IF_DATA 0x70
#define REG_EMP_DATA 0xA0
@@ -343,6 +355,35 @@ struct it6625 {
int if_snapshot_err;
/* set just before complete(), to disambiguate timeout vs. capture */
bool if_snapshot_done;
+
+ /*
+ * ASoC capture DAI state. audio is written with both it6625_lock and
+ * audio_lock held, so either one is enough to read it. Everything
+ * else below is protected by audio_lock, which is taken with
+ * spin_lock_irqsave() because .trigger runs in atomic context. It is
+ * never held across I2C, V4L2 control, ALSA core or work-sync calls.
+ */
+ spinlock_t audio_lock;
+ /* last published audio status sample */
+ struct it6625_audio_info audio;
+ /* bumped on any observed audio status change and on read failures */
+ u32 audio_gen;
+ /* the open capture stream, or NULL */
+ struct snd_pcm_substream *audio_substream;
+ /* audio_gen and stream accepted by the open capture stream */
+ u32 audio_accepted_gen;
+ struct it6625_audio_stream audio_accepted;
+ bool audio_running;
+ /* stream shutdown in progress: queue no new work */
+ bool audio_block;
+ /* refresh work runs left to follow a rate that is still settling */
+ unsigned int audio_settle_polls;
+ /* component registered by probe, unregistered first in remove */
+ bool audio_registered;
+ /* number of connected I2S data pins, from ite,i2s-data-lanes */
+ u8 audio_lanes;
+ struct work_struct audio_xrun_work;
+ struct delayed_work audio_refresh_work;
};
/*
@@ -952,21 +993,132 @@ static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd)
hdmi_5v_power_present(it6625));
}
+static bool it6625_audio_stream_equal(const struct it6625_audio_stream *a,
+ const struct it6625_audio_stream *b)
+{
+ return a->fs_code == b->fs_code &&
+ a->channels == b->channels &&
+ a->type == b->type &&
+ a->multi_stream == b->multi_stream &&
+ a->is_3d == b->is_3d &&
+ a->i2s_wl == b->i2s_wl &&
+ a->out_if == b->out_if;
+}
+
+/*
+ * Any change of the status bits or of the stream description, and every
+ * read failure, invalidates an accepted capture stream: the I2S clocks are
+ * not guaranteed to keep running at one rate across such a change.
+ */
+static bool it6625_audio_changed(const struct it6625_audio_info *old,
+ const struct it6625_audio_info *new)
+{
+ return !new->read_ok ||
+ new->read_ok != old->read_ok ||
+ new->vid_stable != old->vid_stable ||
+ new->present != old->present ||
+ new->avmute != old->avmute ||
+ !it6625_audio_stream_equal(&new->stream, &old->stream);
+}
+
+/*
+ * Sample the audio status and publish it. The read and the publication
+ * both happen under it6625_lock, so concurrent refreshes cannot publish
+ * out of order.
+ */
+static void it6625_audio_refresh(struct it6625 *it6625)
+{
+ struct it6625_audio_info info;
+ unsigned long flags;
+
+ guard(mutex)(&it6625->it6625_lock);
+
+ /* keep the last stream description if audio is absent */
+ info = it6625->audio;
+ it6625_read_audio_info(it6625, &info);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ if (it6625_audio_changed(&it6625->audio, &info)) {
+ it6625->audio_gen++;
+ if (it6625->audio_substream && it6625->audio_running &&
+ !it6625->audio_block)
+ queue_work(system_wq, &it6625->audio_xrun_work);
+ }
+ it6625->audio = info;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+}
+
+/*
+ * Selecting the published sample and writing both controls happen under
+ * the control handler lock, so a stale sample never overwrites a newer one.
+ */
+static int it6625_audio_update_ctrls(struct it6625 *it6625)
+{
+ struct it6625_audio_info info;
+ unsigned long flags;
+ int ret;
+
+ v4l2_ctrl_lock(it6625->ctrl_audio_present);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ info = it6625->audio;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ ret = __v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate,
+ it6625_audio_info_rate(&info));
+ if (!ret)
+ ret = __v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present,
+ info.present);
+
+ v4l2_ctrl_unlock(it6625->ctrl_audio_present);
+
+ return ret;
+}
+
static int it6625_s_ctrl_audio(struct v4l2_subdev *sd)
{
struct it6625 *it6625 = sd_to_6625(sd);
- struct it6625_audio_info info = {};
+
+ it6625_audio_refresh(it6625);
+
+ return it6625_audio_update_ctrls(it6625);
+}
+
+/* audio is present but its rate is not valid yet. Caller holds audio_lock. */
+static bool it6625_audio_settling(struct it6625 *it6625)
+{
+ lockdep_assert_held(&it6625->audio_lock);
+
+ return it6625->audio.read_ok && it6625->audio.present &&
+ !it6625_audio_info_rate(&it6625->audio);
+}
+
+/*
+ * Update the audio controls on a status event. The chip can report audio
+ * present before the video is stable or the sampling-frequency code is
+ * valid, and may raise no further interrupt once they settle, so keep
+ * re-checking for a while in that case.
+ */
+static int it6625_audio_event(struct it6625 *it6625)
+{
+ unsigned long flags;
int ret;
- scoped_guard(mutex, &it6625->it6625_lock)
- it6625_read_audio_info(it6625, &info);
+ ret = it6625_s_ctrl_audio(&it6625->sd);
- ret = v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate,
- it6625_audio_info_rate(&info));
- if (ret)
- return ret;
+ /*
+ * Load the budget and queue the work in one section, so the work
+ * can't be queued again after it has used up the budget.
+ */
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ if (it6625_audio_settling(it6625)) {
+ it6625->audio_settle_polls = IT6625_AUDIO_SETTLE_POLLS;
+ schedule_delayed_work(&it6625->audio_refresh_work,
+ msecs_to_jiffies(IT6625_AUDIO_REFRESH_MS));
+ }
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
- return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, info.present);
+ return ret;
}
static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd)
@@ -977,7 +1129,7 @@ static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd)
if (ret)
return ret;
- return it6625_s_ctrl_audio(sd);
+ return it6625_audio_event(sd_to_6625(sd));
}
static int it6625_enable_stream_locked(struct it6625 *it6625, bool enable)
@@ -1208,6 +1360,9 @@ static void it6625_irq_format_change(struct it6625 *it6625)
};
int ret;
+ /* the audio rate is only valid while the video is stable */
+ it6625_audio_event(it6625);
+
if (no_signal(it6625)) {
if (sd->devnode)
v4l2_subdev_notify_event(sd, &it6625_ev_fmt);
@@ -1226,7 +1381,7 @@ static void it6625_irq_format_change(struct it6625 *it6625)
static void it6625_irq_hdmi_audio_change(struct it6625 *it6625)
{
- it6625_s_ctrl_audio(&it6625->sd);
+ it6625_audio_event(it6625);
}
static void it6625_get_timings(struct it6625 *it6625,
@@ -1314,11 +1469,13 @@ static void it6625_irq_avmute_change(struct it6625 *it6625)
u8 avmute;
val = it6625_read_byte(it6625, REG_RX_STATUS);
- if (val < 0)
- return;
+ if (val >= 0) {
+ avmute = (val & B_RX_AVMUTE) ? 1 : 0;
+ v4l2_info(sd, "AVMute change to %d", avmute);
+ }
- avmute = (val & B_RX_AVMUTE) ? 1 : 0;
- v4l2_info(sd, "AVMute change to %d", avmute);
+ /* AV mute makes the audio unusable for capture */
+ it6625_audio_event(it6625);
}
static void it6625_dispatch_int_status1(struct it6625 *it6625, int int_sts1)
@@ -2001,6 +2158,369 @@ static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
return 0;
}
+/*
+ * I2S capture DAI. The firmware decodes the HDMI audio stream and passes it
+ * through to the I2S output, which it drives as bit and frame clock
+ * provider: standard I2S framing, 16/20/24-bit data in 32-bit slots. None of
+ * this is configurable, so the DAI only accepts streams matching the
+ * currently observed audio and stops a running capture when that changes.
+ */
+
+/* valid bits per 32-bit slot, 0 if unsupported */
+static unsigned int it6625_audio_wl_bits(u8 i2s_wl)
+{
+ switch (i2s_wl) {
+ case I2S_WL_16BIT:
+ return 16;
+ case I2S_WL_20BIT:
+ return 20;
+ case I2S_WL_24BIT:
+ return 24;
+ default:
+ return 0;
+ }
+}
+
+static unsigned int it6625_audio_max_channels(const struct it6625 *it6625)
+{
+ return min(2U * it6625->audio_lanes, IT6625_AUDIO_MAX_CHANNELS);
+}
+
+/* can this audio status be captured? Pure check, callable in atomic context. */
+static int it6625_audio_check(const struct it6625 *it6625,
+ const struct it6625_audio_info *info)
+{
+ const struct it6625_audio_stream *s = &info->stream;
+ u32 rate = it6625_audio_fs_to_rate(s->fs_code);
+
+ if (!info->read_ok || !info->vid_stable || !info->present ||
+ info->avmute)
+ return -ENODEV;
+
+ if (s->out_if != AUD_OUT_IF_I2S || s->type != AUD_TYPE_LPCM ||
+ s->multi_stream || s->is_3d)
+ return -ENODEV;
+
+ if (!rate || rate > IT6625_AUDIO_MAX_RATE)
+ return -ENODEV;
+
+ if (s->channels < 2 || s->channels % 2 ||
+ s->channels > it6625_audio_max_channels(it6625))
+ return -ENODEV;
+
+ if (!it6625_audio_wl_bits(s->i2s_wl))
+ return -ENODEV;
+
+ return 0;
+}
+
+/* is the accepted stream still current? Caller holds audio_lock. */
+static int it6625_audio_validate_locked(struct it6625 *it6625)
+{
+ lockdep_assert_held(&it6625->audio_lock);
+
+ if (it6625->audio_gen != it6625->audio_accepted_gen)
+ return -EPIPE;
+
+ return it6625_audio_check(it6625, &it6625->audio);
+}
+
+static void it6625_audio_xrun_work(struct work_struct *work)
+{
+ struct it6625 *it6625 = container_of(work, struct it6625,
+ audio_xrun_work);
+ struct snd_pcm_substream *substream = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ if (!it6625->audio_block)
+ substream = it6625->audio_substream;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ /* .shutdown drains this work before the substream goes away */
+ if (IS_ENABLED(CONFIG_SND_SOC) && substream)
+ snd_pcm_stop_xrun(substream);
+}
+
+/*
+ * Catch persistent audio changes that raise no interrupt while a capture
+ * stream is open, and follow a rate that is still settling after an event
+ * for at most IT6625_AUDIO_SETTLE_POLLS runs.
+ */
+static void it6625_audio_refresh_work(struct work_struct *work)
+{
+ struct it6625 *it6625 = container_of(to_delayed_work(work),
+ struct it6625, audio_refresh_work);
+ unsigned long flags;
+ bool rearm;
+
+ it6625_s_ctrl_audio(&it6625->sd);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ /* every run uses up one re-check of a settle budget */
+ if (it6625->audio_settle_polls)
+ it6625->audio_settle_polls--;
+ rearm = (it6625->audio_substream && !it6625->audio_block) ||
+ (it6625->audio_settle_polls && it6625_audio_settling(it6625));
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ if (rearm)
+ schedule_delayed_work(&it6625->audio_refresh_work,
+ msecs_to_jiffies(IT6625_AUDIO_REFRESH_MS));
+}
+
+static struct it6625 *dai_to_6625(struct snd_soc_dai *dai)
+{
+ /* the I2C client data is the V4L2 subdev */
+ return sd_to_6625(snd_soc_dai_get_drvdata(dai));
+}
+
+static int it6625_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct it6625 *it6625 = dai_to_6625(dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct it6625_audio_info info;
+ unsigned long flags;
+ u32 gen;
+ int ret;
+
+ it6625_audio_refresh(it6625);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ if (it6625->audio_substream || it6625->audio_block)
+ ret = -EBUSY;
+ else
+ ret = it6625_audio_check(it6625, &it6625->audio);
+ info = it6625->audio;
+ gen = it6625->audio_gen;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_RATE,
+ it6625_audio_fs_to_rate(info.stream.fs_code));
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+ info.stream.channels);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_FMTBIT_S32_LE);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_msbits(runtime, 0, 32,
+ it6625_audio_wl_bits(info.stream.i2s_wl));
+ if (ret < 0)
+ return ret;
+
+ /* recheck: the status may have changed while installing constraints */
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ if (it6625->audio_substream || it6625->audio_block)
+ ret = -EBUSY;
+ else if (it6625->audio_gen != gen)
+ ret = -EPIPE;
+ else
+ ret = it6625_audio_check(it6625, &it6625->audio);
+ if (!ret) {
+ it6625->audio_substream = substream;
+ it6625->audio_accepted = info.stream;
+ it6625->audio_accepted_gen = gen;
+ it6625->audio_running = false;
+ }
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+ if (ret)
+ return ret;
+
+ schedule_delayed_work(&it6625->audio_refresh_work,
+ msecs_to_jiffies(IT6625_AUDIO_REFRESH_MS));
+
+ return 0;
+}
+
+/* also called on rollback after a successful .startup */
+static void it6625_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct it6625 *it6625 = dai_to_6625(dai);
+ unsigned long flags;
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ it6625->audio_block = true;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ cancel_delayed_work_sync(&it6625->audio_refresh_work);
+ cancel_work_sync(&it6625->audio_xrun_work);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ it6625->audio_substream = NULL;
+ it6625->audio_running = false;
+ it6625->audio_block = false;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+}
+
+static int it6625_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBP_CFP ||
+ (fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S ||
+ (fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int it6625_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct it6625 *it6625 = dai_to_6625(dai);
+ const struct it6625_audio_stream *s;
+ unsigned long flags;
+ int ret;
+
+ it6625_audio_refresh(it6625);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ ret = it6625_audio_validate_locked(it6625);
+ s = &it6625->audio_accepted;
+ if (!ret &&
+ (params_rate(params) != it6625_audio_fs_to_rate(s->fs_code) ||
+ params_channels(params) != s->channels ||
+ params_format(params) != SNDRV_PCM_FORMAT_S32_LE))
+ ret = -EINVAL;
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ return ret;
+}
+
+static int it6625_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct it6625 *it6625 = dai_to_6625(dai);
+ unsigned long flags;
+ int ret;
+
+ it6625_audio_refresh(it6625);
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ ret = it6625_audio_validate_locked(it6625);
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ return ret;
+}
+
+/*
+ * Atomic: only the published status is consulted. A loss that no refresh
+ * has observed yet is handled by the refresh that observes it.
+ */
+static int it6625_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct it6625 *it6625 = dai_to_6625(dai);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&it6625->audio_lock, flags);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = it6625_audio_validate_locked(it6625);
+ if (!ret)
+ it6625->audio_running = true;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /*
+ * A refused release would leave the stream paused, and a
+ * blocking read on a paused stream never returns. If the
+ * stream became stale while paused, release it and stop it
+ * with an XRUN instead.
+ */
+ it6625->audio_running = true;
+ if (it6625_audio_validate_locked(it6625) &&
+ it6625->audio_substream && !it6625->audio_block)
+ queue_work(system_wq, &it6625->audio_xrun_work);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ it6625->audio_running = false;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ spin_unlock_irqrestore(&it6625->audio_lock, flags);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops it6625_dai_ops = {
+ .startup = it6625_dai_startup,
+ .shutdown = it6625_dai_shutdown,
+ .set_fmt = it6625_dai_set_fmt,
+ .hw_params = it6625_dai_hw_params,
+ .prepare = it6625_dai_prepare,
+ .trigger = it6625_dai_trigger,
+};
+
+static struct snd_soc_dai_driver it6625_dai = {
+ .name = "it6625-i2s",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = IT6625_AUDIO_MAX_CHANNELS,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 |
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_128000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &it6625_dai_ops,
+};
+
+static const struct snd_soc_component_driver it6625_component = {
+ .name = "it6625",
+ .endianness = 1,
+};
+
+/*
+ * Registered without devm so that remove can unregister it first: that
+ * waits until every open capture stream is closed, so no DAI callback
+ * runs once the driver starts tearing down.
+ */
+static int it6625_audio_register(struct it6625 *it6625)
+{
+ int err;
+
+ if (!IS_ENABLED(CONFIG_SND_SOC) ||
+ !of_property_present(it6625->dev->of_node, "#sound-dai-cells"))
+ return 0;
+
+ err = snd_soc_register_component(it6625->dev, &it6625_component,
+ &it6625_dai, 1);
+ if (err)
+ return dev_err_probe(it6625->dev, err,
+ "failed to register audio DAI");
+
+ it6625->audio_registered = true;
+
+ return 0;
+}
+
+static void it6625_audio_unregister(struct it6625 *it6625)
+{
+ if (IS_ENABLED(CONFIG_SND_SOC) && it6625->audio_registered)
+ snd_soc_unregister_component(it6625->dev);
+
+ disable_delayed_work_sync(&it6625->audio_refresh_work);
+ disable_work_sync(&it6625->audio_xrun_work);
+}
+
static void it6625_regdump_print(struct seq_file *s, const u8 *reg_buf)
{
seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F\n");
@@ -2267,9 +2787,49 @@ static int it6625_parse_endpoint(struct it6625 *it6625)
return ret;
}
+static int it6625_parse_audio(struct it6625 *it6625)
+{
+ struct device *dev = it6625->dev;
+ struct device_node *np = dev->of_node;
+ u32 lanes[IT6625_AUDIO_MAX_LANES];
+ int count, ret;
+
+ it6625->audio_lanes = 1;
+
+ if (!of_property_present(np, "ite,i2s-data-lanes"))
+ return 0;
+
+ count = of_property_count_u32_elems(np, "ite,i2s-data-lanes");
+ if (count < 1 || count > IT6625_AUDIO_MAX_LANES)
+ return dev_err_probe(dev, -EINVAL,
+ "ite,i2s-data-lanes needs 1 to %u entries",
+ IT6625_AUDIO_MAX_LANES);
+
+ ret = of_property_read_u32_array(np, "ite,i2s-data-lanes", lanes, count);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read ite,i2s-data-lanes");
+
+ /* channel-pair to data-pin mapping is not supported yet */
+ for (unsigned int i = 0; i < count; i++) {
+ if (lanes[i] != i)
+ return dev_err_probe(dev, -EINVAL,
+ "ite,i2s-data-lanes must be <0 1 ... n-1>");
+ }
+
+ it6625->audio_lanes = count;
+
+ return 0;
+}
+
static int it6625_parse_dt(struct it6625 *it6625)
{
- return it6625_parse_endpoint(it6625);
+ int ret;
+
+ ret = it6625_parse_endpoint(it6625);
+ if (ret)
+ return ret;
+
+ return it6625_parse_audio(it6625);
}
static int it6625_check_device(struct it6625 *it6625)
@@ -2342,6 +2902,9 @@ static int it6625_probe(struct i2c_client *client)
init_completion(&it6625->if_latched);
INIT_DELAYED_WORK(&it6625->hpd_delayed_work, it6625_hpd_delayed_work);
INIT_WORK(&it6625->polling_work, it6625_polling_work);
+ spin_lock_init(&it6625->audio_lock);
+ INIT_WORK(&it6625->audio_xrun_work, it6625_audio_xrun_work);
+ INIT_DELAYED_WORK(&it6625->audio_refresh_work, it6625_audio_refresh_work);
if (client->irq) {
err = devm_request_threaded_irq(&client->dev, client->irq,
@@ -2426,6 +2989,13 @@ static int it6625_probe(struct i2c_client *client)
goto err_clean_debugfs;
}
+ /* may bind a waiting sound card, so everything must be set up */
+ err = it6625_audio_register(it6625);
+ if (err) {
+ v4l2_async_unregister_subdev(sd);
+ goto err_clean_debugfs;
+ }
+
if (client->irq)
enable_irq(client->irq);
else
@@ -2434,6 +3004,12 @@ static int it6625_probe(struct i2c_client *client)
return 0;
err_clean_debugfs:
+ /*
+ * Events dispatched by the probe control update or a debugfs reader
+ * may have queued it. Disable rather than cancel it: a reader still
+ * running until the debugfs removal below must not queue it again.
+ */
+ disable_delayed_work_sync(&it6625->audio_refresh_work);
v4l2_debugfs_if_free(it6625->infoframes);
debugfs_remove_recursive(it6625->debugfs_dir);
cec_unregister_adapter(it6625->cec_adap);
@@ -2461,6 +3037,9 @@ static void it6625_remove(struct i2c_client *client)
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct it6625 *it6625 = sd_to_6625(sd);
+ /* first: waits for open capture streams to close */
+ it6625_audio_unregister(it6625);
+
v4l2_debugfs_if_free(it6625->infoframes);
if (client->irq)
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 5/5] media: i2c: it6625: capture compressed and HBR audio, not just LPCM
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
` (3 preceding siblings ...)
2026-11-03 6:50 ` [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI Hermes Wu via B4 Relay
@ 2026-11-03 6:50 ` Hermes Wu via B4 Relay
4 siblings, 0 replies; 7+ messages in thread
From: Hermes Wu via B4 Relay @ 2026-11-03 6:50 UTC (permalink / raw)
To: Hermes Wu, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hans Verkuil
Cc: linux-media, devicetree, linux-kernel, Mark Brown, Liam Girdwood,
linux-sound, linux-api, Hermes Wu
From: Hermes Wu <Hermes.wu@ite.com.tw>
The capture DAI accepts only LPCM. The bridge also outputs compressed
IEC 61937 streams (AC-3, DTS, ...) and high bit rate (HBR) streams
(Dolby TrueHD/MAT, DTS-HD Master Audio) on the same I2S interface, but
these are rejected.
Add it6625_audio_xport(), which derives the I2S frame rate and slot
count from the stream's coding type and sampling rate. LPCM is
unchanged. A compressed stream is carried in 2 slots at its own
sampling rate, except at the HBR rates of 768 kHz and 705.6 kHz, where
one IEC 61937 stream is spread over 8 slots on 4 data lines at a quarter
of that rate. The bridge firmware also reports the HBR coding type for
some compressed streams at ordinary rates, such as 48 kHz DTS, so the
sampling rate, not the coding type, selects the layout. A compressed
stream at 768 kHz or 705.6 kHz is rejected when fewer than 4 data lines
are connected.
Use the derived rate and channel count in the eligibility check, the
startup constraints and the hw_params comparison. DSD is still
rejected. The driver does not parse the compressed data: userspace
receives the IEC 61937 words as they are.
Add a read-only "Audio Type" menu control, V4L2_CID_IT6625_AUDIO_TYPE,
so that userspace can tell LPCM, compressed, HBR and DSD streams apart
without parsing the audio InfoFrame, and document it.
Signed-off-by: Hermes Wu <Hermes.wu@ite.com.tw>
---
.../userspace-api/media/drivers/it6625.rst | 30 ++++++
drivers/media/i2c/it6625.c | 118 ++++++++++++++++++---
include/uapi/linux/it6625.h | 25 +++++
3 files changed, 157 insertions(+), 16 deletions(-)
diff --git a/Documentation/userspace-api/media/drivers/it6625.rst b/Documentation/userspace-api/media/drivers/it6625.rst
index efd970e8124979abffb6f9ba3ea1e1bc193820b3..1c4d319f88946ef79e0d847ba0f8adf5b2132210 100644
--- a/Documentation/userspace-api/media/drivers/it6625.rst
+++ b/Documentation/userspace-api/media/drivers/it6625.rst
@@ -16,3 +16,33 @@ them are read-only and describe the audio of the HDMI input.
``V4L2_CID_IT6625_AUDIO_PRESENT (boolean)``
-------------------------------------------
Whether the bridge currently receives HDMI audio.
+
+``V4L2_CID_IT6625_AUDIO_TYPE (menu)``
+-------------------------------------
+ The coding type of the HDMI audio stream, as last read successfully from
+ the bridge. The value is kept when audio goes away or reading the audio
+ status fails, so it describes the current stream only while
+ ``V4L2_CID_IT6625_AUDIO_PRESENT`` is set and
+ ``V4L2_CID_IT6625_AUDIO_SAMPLING_RATE`` is not 0.
+
+.. flat-table::
+ :header-rows: 0
+ :stub-columns: 0
+ :widths: 1 4
+
+ * - ``V4L2_IT6625_AUDIO_TYPE_LPCM``
+ - Linear PCM.
+ * - ``V4L2_IT6625_AUDIO_TYPE_NLPCM``
+ - Compressed audio in IEC 61937 format.
+ * - ``V4L2_IT6625_AUDIO_TYPE_HBR``
+ - High bit rate compressed audio in IEC 61937 format. The bridge also
+ reports this type for some compressed streams at ordinary sampling
+ rates.
+ * - ``V4L2_IT6625_AUDIO_TYPE_DSD``
+ - One-bit audio. It cannot be captured.
+
+Compressed and high bit rate streams are captured as raw IEC 61937 data. The
+capture layout follows the sampling rate, not the coding type: at 768 kHz and
+705.6 kHz the stream is captured as 8 channels at a quarter of that rate,
+which needs 4 connected I2S data lines; at other rates it is captured as 2
+channels at the sampling rate.
diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c
index f1bd42ad15eda1a2bd22dac00ba11a5b1c790e71..f9cd15dc24960cf2caf98576ecccd3f803dbfd86 100644
--- a/drivers/media/i2c/it6625.c
+++ b/drivers/media/i2c/it6625.c
@@ -101,6 +101,9 @@ MODULE_PARM_DESC(debug, "debug level (0-3)");
#define REG_HDMI_AUDIO_INFO3 0x6D
#define M_AUD_TYPE GENMASK(1, 0)
#define AUD_TYPE_LPCM 0
+#define AUD_TYPE_NLPCM 1
+#define AUD_TYPE_HBR 2
+#define AUD_TYPE_DSD 3
#define B_AUD_MS BIT(2)
#define B_AUD_3D BIT(3)
@@ -119,6 +122,9 @@ MODULE_PARM_DESC(debug, "debug level (0-3)");
#define IT6625_AUDIO_MAX_LANES 8
#define IT6625_AUDIO_MAX_CHANNELS 8
#define IT6625_AUDIO_MAX_RATE 192000
+/* HBR: one IEC 61937 stream spread over 4 lanes (8 slots) per frame */
+#define IT6625_AUDIO_HBR_LANES 4
+#define IT6625_AUDIO_HBR_CHANNELS 8
/* status re-check period while a capture stream is open */
#define IT6625_AUDIO_REFRESH_MS 200
/* re-checks after an event that left present audio without a valid rate */
@@ -322,6 +328,7 @@ struct it6625 {
struct v4l2_ctrl *ctrl_5v_detect;
struct v4l2_ctrl *ctrl_audio_sampling_rate;
struct v4l2_ctrl *ctrl_audio_present;
+ struct v4l2_ctrl *ctrl_audio_type;
struct v4l2_ctrl *ctrl_link_freq;
struct delayed_work hpd_delayed_work;
@@ -1069,6 +1076,9 @@ static int it6625_audio_update_ctrls(struct it6625 *it6625)
if (!ret)
ret = __v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present,
info.present);
+ if (!ret)
+ ret = __v4l2_ctrl_s_ctrl(it6625->ctrl_audio_type,
+ info.stream.type);
v4l2_ctrl_unlock(it6625->ctrl_audio_present);
@@ -2124,6 +2134,25 @@ static const struct v4l2_ctrl_config it6625_ctrl_audio_present = {
.flags = V4L2_CTRL_FLAG_READ_ONLY,
};
+static const char * const it6625_audio_type_menu[] = {
+ [V4L2_IT6625_AUDIO_TYPE_LPCM] = "LPCM",
+ [V4L2_IT6625_AUDIO_TYPE_NLPCM] = "NLPCM",
+ [V4L2_IT6625_AUDIO_TYPE_HBR] = "HBR",
+ [V4L2_IT6625_AUDIO_TYPE_DSD] = "DSD",
+ NULL,
+};
+
+static const struct v4l2_ctrl_config it6625_ctrl_audio_type = {
+ .id = V4L2_CID_IT6625_AUDIO_TYPE,
+ .name = "Audio Type",
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_IT6625_AUDIO_TYPE_LPCM,
+ .max = V4L2_IT6625_AUDIO_TYPE_DSD,
+ .def = V4L2_IT6625_AUDIO_TYPE_LPCM,
+ .qmenu = it6625_audio_type_menu,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY,
+};
+
static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
{
struct it6625 *it6625 = sd_to_6625(sd);
@@ -2131,7 +2160,7 @@ static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
bool cphy_3trio = it6625->bus_type == V4L2_MBUS_CSI2_CPHY &&
it6625->csi_lanes == 3;
- v4l2_ctrl_handler_init(hdl, 4);
+ v4l2_ctrl_handler_init(hdl, 5);
it6625->ctrl_5v_detect =
v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT,
@@ -2143,6 +2172,8 @@ static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
NULL);
it6625->ctrl_audio_present =
v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_present, NULL);
+ it6625->ctrl_audio_type =
+ v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_type, NULL);
it6625->ctrl_link_freq =
v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, 0, 0,
cphy_3trio ? it6626_cphy_3trio_link_freq :
@@ -2186,26 +2217,74 @@ static unsigned int it6625_audio_max_channels(const struct it6625 *it6625)
return min(2U * it6625->audio_lanes, IT6625_AUDIO_MAX_CHANNELS);
}
+/*
+ * I2S frame rate and slot count that carry this stream. Pure, callable in
+ * atomic context; returns -ENODEV for a stream the I2S output can't carry.
+ *
+ * LPCM goes out as is. A compressed IEC 61937 stream at up to 192 kHz
+ * takes 2 slots at its own sampling rate, whatever channel count the
+ * source signals. An HBR stream (AUD_FS 768 or 705.6 kHz) is one IEC 61937
+ * stream at 4x the frame rate spread over 4 lanes: 8 slots at a quarter of
+ * that rate.
+ *
+ * The firmware doesn't keep AUD_TYPE to its documented meaning: it reports
+ * HBR for a plain 48 kHz DTS stream too. So the rate, not NLPCM vs. HBR,
+ * selects the layout of a compressed stream.
+ */
+static int it6625_audio_xport(const struct it6625 *it6625,
+ const struct it6625_audio_stream *s,
+ u32 *rate, unsigned int *channels)
+{
+ u32 fs = it6625_audio_fs_to_rate(s->fs_code);
+
+ if (!fs)
+ return -ENODEV;
+
+ switch (s->type) {
+ case AUD_TYPE_LPCM:
+ *rate = fs;
+ *channels = s->channels;
+ break;
+ case AUD_TYPE_NLPCM:
+ case AUD_TYPE_HBR:
+ if (fs == 768000 || fs == 705600) {
+ *rate = fs / IT6625_AUDIO_HBR_LANES;
+ *channels = IT6625_AUDIO_HBR_CHANNELS;
+ } else {
+ *rate = fs;
+ *channels = 2;
+ }
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ if (*rate > IT6625_AUDIO_MAX_RATE)
+ return -ENODEV;
+
+ if (*channels < 2 || *channels % 2 ||
+ *channels > it6625_audio_max_channels(it6625))
+ return -ENODEV;
+
+ return 0;
+}
+
/* can this audio status be captured? Pure check, callable in atomic context. */
static int it6625_audio_check(const struct it6625 *it6625,
const struct it6625_audio_info *info)
{
const struct it6625_audio_stream *s = &info->stream;
- u32 rate = it6625_audio_fs_to_rate(s->fs_code);
+ unsigned int channels;
+ u32 rate;
if (!info->read_ok || !info->vid_stable || !info->present ||
info->avmute)
return -ENODEV;
- if (s->out_if != AUD_OUT_IF_I2S || s->type != AUD_TYPE_LPCM ||
- s->multi_stream || s->is_3d)
- return -ENODEV;
-
- if (!rate || rate > IT6625_AUDIO_MAX_RATE)
+ if (s->out_if != AUD_OUT_IF_I2S || s->multi_stream || s->is_3d)
return -ENODEV;
- if (s->channels < 2 || s->channels % 2 ||
- s->channels > it6625_audio_max_channels(it6625))
+ if (it6625_audio_xport(it6625, s, &rate, &channels))
return -ENODEV;
if (!it6625_audio_wl_bits(s->i2s_wl))
@@ -2281,8 +2360,9 @@ static int it6625_dai_startup(struct snd_pcm_substream *substream,
struct it6625 *it6625 = dai_to_6625(dai);
struct snd_pcm_runtime *runtime = substream->runtime;
struct it6625_audio_info info;
+ unsigned int channels;
unsigned long flags;
- u32 gen;
+ u32 gen, rate;
int ret;
it6625_audio_refresh(it6625);
@@ -2298,13 +2378,16 @@ static int it6625_dai_startup(struct snd_pcm_substream *substream,
if (ret)
return ret;
+ /* the check above passed, so this can't fail */
+ it6625_audio_xport(it6625, &info.stream, &rate, &channels);
+
ret = snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_RATE,
- it6625_audio_fs_to_rate(info.stream.fs_code));
+ rate);
if (ret < 0)
return ret;
ret = snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
- info.stream.channels);
+ channels);
if (ret < 0)
return ret;
@@ -2378,18 +2461,21 @@ static int it6625_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct it6625 *it6625 = dai_to_6625(dai);
- const struct it6625_audio_stream *s;
+ unsigned int channels;
unsigned long flags;
+ u32 rate;
int ret;
it6625_audio_refresh(it6625);
spin_lock_irqsave(&it6625->audio_lock, flags);
ret = it6625_audio_validate_locked(it6625);
- s = &it6625->audio_accepted;
+ if (!ret)
+ ret = it6625_audio_xport(it6625, &it6625->audio_accepted,
+ &rate, &channels);
if (!ret &&
- (params_rate(params) != it6625_audio_fs_to_rate(s->fs_code) ||
- params_channels(params) != s->channels ||
+ (params_rate(params) != rate ||
+ params_channels(params) != channels ||
params_format(params) != SNDRV_PCM_FORMAT_S32_LE))
ret = -EINVAL;
spin_unlock_irqrestore(&it6625->audio_lock, flags);
diff --git a/include/uapi/linux/it6625.h b/include/uapi/linux/it6625.h
index a895658aaf6a80d3fb624cdece33c5135161c72d..9ab85378ac77d1e06bc87964c1066d35ad177c15 100644
--- a/include/uapi/linux/it6625.h
+++ b/include/uapi/linux/it6625.h
@@ -22,4 +22,29 @@
*/
#define V4L2_CID_IT6625_AUDIO_PRESENT (V4L2_CID_USER_IT6625_BASE + 1)
+/*
+ * Coding type of the HDMI audio stream, as last successfully read from the
+ * bridge. Read-only menu. It is kept, not cleared, when audio goes away or
+ * a status read fails, so it describes the current stream only while
+ * V4L2_CID_IT6625_AUDIO_PRESENT is set and
+ * V4L2_CID_IT6625_AUDIO_SAMPLING_RATE is non-zero.
+ *
+ * LPCM: linear PCM, captured as is.
+ * NLPCM, HBR: IEC 61937 compressed stream, as reported by the bridge
+ * firmware (which also reports HBR for plain 48 kHz DTS). The
+ * capture layout follows V4L2_CID_IT6625_AUDIO_SAMPLING_RATE: at
+ * 768/705.6 kHz (TrueHD/MAT, DTS-HD MA) 8 channels on 4 I2S lanes
+ * at a quarter of that rate, otherwise (AC-3, DTS, ...) 2 channels
+ * at that rate.
+ * DSD: one-bit audio; not capturable.
+ */
+#define V4L2_CID_IT6625_AUDIO_TYPE (V4L2_CID_USER_IT6625_BASE + 2)
+
+enum v4l2_it6625_audio_type {
+ V4L2_IT6625_AUDIO_TYPE_LPCM = 0,
+ V4L2_IT6625_AUDIO_TYPE_NLPCM = 1,
+ V4L2_IT6625_AUDIO_TYPE_HBR = 2,
+ V4L2_IT6625_AUDIO_TYPE_DSD = 3,
+};
+
#endif /* _UAPI_LINUX_IT6625_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI
2026-11-03 6:50 ` [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI Hermes Wu via B4 Relay
@ 2026-11-03 7:05 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-03 7:05 UTC (permalink / raw)
To: Hermes Wu; +Cc: devicetree, linux-sound, robh, conor+dt, media-ci
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] media: i2c: it6625: it6625_dai_shutdown() cancels active rate-settle polling
- [Medium] media: i2c: it6625: it6625_audio_refresh() in DAI ops skips V4L2 ctrl update
--
commit 1dfd81c0e8bfaba0b855fe94b881d239f6f65287
Author: Hermes Wu <Hermes.wu@ite.com.tw>
media: i2c: it6625: add I2S audio capture DAI
This commit registers an ASoC component and I2S capture DAI for the IT6625
and IT6626 HDMI-to-MIPI bridges when #sound-dai-cells is present in the
device tree. It constrains capture streams to the currently observed HDMI
audio status, stops active streams on status changes, and polls for audio
status updates and rate settling.
> diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c
> index 6f12a0e760178..f1bd42ad15eda 100644
> --- a/drivers/media/i2c/it6625.c
> +++ b/drivers/media/i2c/it6625.c
[ ... ]
> @@ -2001,6 +2158,369 @@ static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
> return 0;
> }
>
[ ... ]
> +static void it6625_audio_refresh_work(struct work_struct *work)
> +{
> + struct it6625 *it6625 = container_of(to_delayed_work(work),
> + struct it6625, audio_refresh_work);
> + unsigned long flags;
> + bool rearm;
> +
> + it6625_s_ctrl_audio(&it6625->sd);
> +
> + spin_lock_irqsave(&it6625->audio_lock, flags);
> + /* every run uses up one re-check of a settle budget */
> + if (it6625->audio_settle_polls)
> + it6625->audio_settle_polls--;
> + rearm = (it6625->audio_substream && !it6625->audio_block) ||
> + (it6625->audio_settle_polls && it6625_audio_settling(it6625));
> + spin_unlock_irqrestore(&it6625->audio_lock, flags);
> +
> + if (rearm)
> + schedule_delayed_work(&it6625->audio_refresh_work,
> + msecs_to_jiffies(IT6625_AUDIO_REFRESH_MS));
> +}
> +
> +static struct it6625 *dai_to_6625(struct snd_soc_dai *dai)
> +{
> + /* the I2C client data is the V4L2 subdev */
> + return sd_to_6625(snd_soc_dai_get_drvdata(dai));
> +}
> +
> +static int it6625_dai_startup(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
> + struct it6625 *it6625 = dai_to_6625(dai);
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + struct it6625_audio_info info;
> + unsigned long flags;
> + u32 gen;
> + int ret;
> +
> + it6625_audio_refresh(it6625);
[ ... ]
> +/* also called on rollback after a successful .startup */
> +static void it6625_dai_shutdown(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
> + struct it6625 *it6625 = dai_to_6625(dai);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&it6625->audio_lock, flags);
> + it6625->audio_block = true;
> + spin_unlock_irqrestore(&it6625->audio_lock, flags);
> +
> + cancel_delayed_work_sync(&it6625->audio_refresh_work);
> + cancel_work_sync(&it6625->audio_xrun_work);
> +
> + spin_lock_irqsave(&it6625->audio_lock, flags);
> + it6625->audio_substream = NULL;
> + it6625->audio_running = false;
> + it6625->audio_block = false;
> + spin_unlock_irqrestore(&it6625->audio_lock, flags);
> +}
[Severity: Medium]
Can it6625_dai_shutdown() prematurely stop rate-settle polling started by
it6625_audio_event()?
If an HDMI format or audio event occurs while a capture stream is running and
leaves audio present without a valid sampling rate, it6625_audio_event()
performs:
it6625_audio_event()
it6625_s_ctrl_audio()
it6625_audio_refresh()
it6625->audio_gen++;
queue_work(system_wq, &it6625->audio_xrun_work);
if (it6625_audio_settling(it6625)) {
it6625->audio_settle_polls = IT6625_AUDIO_SETTLE_POLLS;
schedule_delayed_work(&it6625->audio_refresh_work, ...);
}
Because audio_gen was incremented, subsequent calls to it6625_dai_prepare() or
it6625_dai_hw_params() return -EPIPE, so userspace has to close the capture
stream to recover.
When userspace closes the stream, it6625_dai_shutdown() runs
cancel_delayed_work_sync(&it6625->audio_refresh_work) and does not re-schedule
audio_refresh_work after clearing audio_block even when
it6625->audio_settle_polls > 0 and it6625_audio_settling(it6625) is still
true. If the bridge raises no further interrupt once the rate settles, won't
ctrl_audio_sampling_rate and it6625->audio remain stuck at rate 0?
> +
> +static int it6625_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
> +{
> + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBP_CFP ||
> + (fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S ||
> + (fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int it6625_dai_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + struct it6625 *it6625 = dai_to_6625(dai);
> + const struct it6625_audio_stream *s;
> + unsigned long flags;
> + int ret;
> +
> + it6625_audio_refresh(it6625);
> +
> + spin_lock_irqsave(&it6625->audio_lock, flags);
> + ret = it6625_audio_validate_locked(it6625);
> + s = &it6625->audio_accepted;
> + if (!ret &&
> + (params_rate(params) != it6625_audio_fs_to_rate(s->fs_code) ||
> + params_channels(params) != s->channels ||
> + params_format(params) != SNDRV_PCM_FORMAT_S32_LE))
> + ret = -EINVAL;
> + spin_unlock_irqrestore(&it6625->audio_lock, flags);
> +
> + return ret;
> +}
> +
> +static int it6625_dai_prepare(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
> + struct it6625 *it6625 = dai_to_6625(dai);
> + unsigned long flags;
> + int ret;
> +
> + it6625_audio_refresh(it6625);
> +
> + spin_lock_irqsave(&it6625->audio_lock, flags);
> + ret = it6625_audio_validate_locked(it6625);
> + spin_unlock_irqrestore(&it6625->audio_lock, flags);
> +
> + return ret;
> +}
[Severity: Medium]
Should it6625_dai_startup(), it6625_dai_hw_params(), and it6625_dai_prepare()
call it6625_s_ctrl_audio(&it6625->sd) instead of calling
it6625_audio_refresh(it6625) directly?
Calling it6625_audio_refresh() updates it6625->audio and increments
it6625->audio_gen when a status change is observed, but skips
it6625_audio_update_ctrls().
If an audio change that raised no interrupt is observed by
it6625_dai_startup() and causes it6625_audio_check() to fail, startup returns
an error without scheduling audio_refresh_work and without updating
ctrl_audio_sampling_rate or ctrl_audio_present. Similarly, if
it6625_dai_hw_params() or it6625_dai_prepare() observes a change and returns
-EPIPE, and userspace closes the substream before audio_refresh_work runs,
it6625_dai_shutdown() cancels audio_refresh_work and leaves the v4l2 controls
out of sync with the sample stored in it6625->audio.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103-upstream-it6625-alsa-i2s-v1-0-bb91e2879b01@ite.com.tw?part=4
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-03 7:05 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 6:50 [PATCH 0/5] media: i2c: it6625: add HDMI audio capture over I2S Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 1/5] dt-bindings: media: ite,it6625: add audio DAI properties Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 2/5] media: Documentation: it6625: document the audio controls Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 3/5] media: i2c: it6625: decode audio status fields with FIELD_GET() Hermes Wu via B4 Relay
2026-11-03 6:50 ` [PATCH 4/5] media: i2c: it6625: add I2S audio capture DAI Hermes Wu via B4 Relay
2026-11-03 7:05 ` sashiko-bot
2026-11-03 6:50 ` [PATCH 5/5] media: i2c: it6625: capture compressed and HBR audio, not just LPCM Hermes Wu via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox