--- /dev/null
+From d29786f03ced61e908a6770ba6d1a010db8428b8 Mon Sep 17 00:00:00 2001
+From: Annaliese McDermond <nh6z@nh6z.net>
+Date: Wed, 1 Jul 2026 12:30:31 -0700
+Subject: [PATCH] overlays: draws: Fix reset-gpios polarity for gpiod
+ conversion
+
+The DRAWS overlay declared the codec/SC16IS752 shared reset line as
+reset-gpios = <&gpio 13 0> (GPIO_ACTIVE_HIGH). The tlv320aic32x4 driver
+originally toggled reset with the legacy integer GPIO API
+(gpio_set_value()), which writes raw physical levels and ignores the DT
+polarity flag, so the codec was released from reset regardless.
+
+Commit 790d5f8ee6f2 ("ASoC: codec: tlv320aic32x4: Convert to GPIO
+descriptors") switched the driver to gpiod_set_value(), which honours
+the DT polarity. With the active-high flag the driver now drives the
+line low on release, holding the codec -- and the SC16IS752 sharing the
+reset -- permanently in reset, so the codec fails to probe:
+
+ tlv320aic32x4 1-0018: probe with driver tlv320aic32x4 failed with error -121
+
+The reset is active low, so declare it GPIO_ACTIVE_LOW. This is a no-op
+on kernels with the old integer-GPIO driver and restores correct
+behaviour on kernels with the descriptor-based driver.
+
+Fixes: 790d5f8ee6f2 ("ASoC: codec: tlv320aic32x4: Convert to GPIO descriptors")
+Signed-off-by: Annaliese McDermond <nh6z@nh6z.net>
+Assisted-by: Claude:claude-opus-4-8
+---
+ arch/arm/boot/dts/overlays/draws-overlay.dts | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/draws-overlay.dts
++++ b/arch/arm/boot/dts/overlays/draws-overlay.dts
+@@ -76,7 +76,8 @@
+ pinctrl-names = "default";
+ pinctrl-0 = <&gpclk0_pin &aic3204_reset>;
+
+- reset-gpios = <&gpio 13 0>;
++ /* Shared active-low reset for the codec and the SC16IS752 */
++ reset-gpios = <&gpio 13 1>;
+
+ iov-supply = <&udrc0_ldoin>;
+ ldoin-supply = <&udrc0_ldoin>;
--- /dev/null
+From 836fa33251ba570d12b09e5ac16e1890c3c8d851 Mon Sep 17 00:00:00 2001
+From: Annaliese McDermond <nh6z@nh6z.net>
+Date: Wed, 1 Jul 2026 12:30:31 -0700
+Subject: [PATCH] overlays: Add draws-pi5 overlay for Raspberry Pi 5
+
+The DRAWS HAT clocks its TLV320AIC3204 codec from GPCLK0 on GPIO4. On
+BCM2835/BCM2711 this comes from the SoC clock manager
+(BCM2835_CLOCK_GP0) and the pin uses the legacy brcm,pins/brcm,function
+pinctrl binding. On BCM2712 the 40-pin header GPIOs and their clocks
+belong to the RP1, so neither the clock provider nor the pinctrl binding
+of the existing draws overlay applies and the codec cannot be clocked.
+
+Add a Pi 5 variant that sources the codec MCLK from the RP1 GP0 clock
+(RP1_CLK_GP0) at 25 MHz, muxed onto GPIO4 via the in-tree
+rp1_gpclksrc0_gpio4 pin group, and expresses the reset/IRQ/PPS pins with
+the RP1 string-based pinctrl binding. Everything else (regulators, PPS,
+SC16IS752, TLA2024 ADC, sound card and parameters) is carried over
+unchanged.
+
+Register the overlay in the Makefile and README, and add an overlay_map
+entry so "dtoverlay=draws" transparently loads draws-pi5 on BCM2712.
+
+Signed-off-by: Annaliese McDermond <nh6z@nh6z.net>
+Assisted-by: Claude:claude-opus-4-8
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 4 +
+ .../boot/dts/overlays/draws-pi5-overlay.dts | 210 ++++++++++++++++++
+ arch/arm/boot/dts/overlays/overlay_map.dts | 10 +
+ 4 files changed, 225 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/draws-pi5-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -60,6 +60,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ dpi18cpadhi.dtbo \
+ dpi24.dtbo \
+ draws.dtbo \
++ draws-pi5.dtbo \
+ dwc-otg-deprecated.dtbo \
+ dwc2.dtbo \
+ edt-ft5406.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -1253,6 +1253,10 @@ Params: draws_adc_ch4_gain Sets the
+ alsaname Name of the ALSA audio device (default "draws")
+
+
++Name: draws-pi5
++Info: See draws (this is the Pi 5 version)
++
++
+ Name: dwc-otg
+ Info: The dwc-otg has been deprecated.
+ Load: <Deprecated>
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/draws-pi5-overlay.dts
+@@ -0,0 +1,210 @@
++#include <dt-bindings/clock/rp1.h>
++/*
++ * Device tree overlay for the DRAWS Hardware on Raspberry Pi 5 / RP1
++ *
++ * The codec's master clock is supplied by the RP1 GP0 general-purpose clock
++ * on GPIO4 (the "gpclk0" pin function), rather than the BCM2835 GP0 clock
++ * used by the Pi 4 and earlier variant (draws-overlay.dts).
++ */
++
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2712";
++
++ fragment@0 {
++ target = <&i2s_clk_producer>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ fragment@1 {
++ target-path = "/";
++ __overlay__ {
++ regulators {
++ compatible = "simple-bus";
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ udrc0_ldoin: udrc0_ldoin {
++ compatible = "regulator-fixed";
++ regulator-name = "ldoin";
++ regulator-min-microvolt = <3300000>;
++ regulator-max-microvolt = <3300000>;
++ regulator-always-on;
++ };
++
++ sc16is752_clk: sc16is752_draws_clk {
++ compatible = "fixed-clock";
++ #clock-cells = <0>;
++ clock-frequency = <1843200>;
++ };
++ };
++
++ pps: pps {
++ compatible = "pps-gpio";
++ pinctrl-names = "default";
++ pinctrl-0 = <&pps_pins>;
++ gpios = <&gpio 7 0>;
++ status = "okay";
++ };
++
++ iio-hwmon {
++ compatible = "iio-hwmon";
++ status = "okay";
++ io-channels = <&tla2024 4>, <&tla2024 5>, <&tla2024 6>,
++ <&tla2024 7>;
++ };
++ };
++ };
++
++ fragment@2 {
++ target = <&i2c_arm>;
++ __overlay__ {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++
++ tlv320aic32x4: tlv320aic32x4@18 {
++ compatible = "ti,tlv320aic32x4";
++ reg = <0x18>;
++ #sound-dai-cells = <0>;
++ status = "okay";
++
++ clocks = <&rp1_clocks RP1_CLK_GP0>;
++ clock-names = "mclk";
++ assigned-clocks = <&rp1_clocks RP1_CLK_GP0>;
++ assigned-clock-rates = <25000000>;
++
++ pinctrl-names = "default";
++ pinctrl-0 = <&rp1_gpclksrc0_gpio4 &aic3204_reset>;
++
++ /*
++ * GPIO13 is a shared, active-low reset for the codec and the
++ * SC16IS752. The aic32x4 driver deasserts by driving the line
++ * to its logical-low (released) state, so it must be declared
++ * GPIO_ACTIVE_LOW (1) for the RP1 to leave it high on release.
++ */
++ reset-gpios = <&gpio 13 1>;
++
++ iov-supply = <&udrc0_ldoin>;
++ ldoin-supply = <&udrc0_ldoin>;
++ };
++
++ sc16is752: sc16is752@50 {
++ compatible = "nxp,sc16is752";
++ reg = <0x50>;
++ clocks = <&sc16is752_clk>;
++ interrupt-parent = <&gpio>;
++ interrupts = <17 2>; /* IRQ_TYPE_EDGE_FALLING */
++
++ pinctrl-names = "default";
++ pinctrl-0 = <&sc16is752_irq>;
++ };
++
++ tla2024: tla2024@48 {
++ compatible = "ti,ads1015";
++ reg = <0x48>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ #io-channel-cells = <1>;
++
++ adc_ch4: channel@4 {
++ reg = <4>;
++ ti,gain = <1>;
++ ti,datarate = <4>;
++ };
++
++ adc_ch5: channel@5 {
++ reg = <5>;
++ ti,gain = <1>;
++ ti,datarate = <4>;
++ };
++
++ adc_ch6: channel@6 {
++ reg = <6>;
++ ti,gain = <2>;
++ ti,datarate = <4>;
++ };
++
++ adc_ch7: channel@7 {
++ reg = <7>;
++ ti,gain = <2>;
++ ti,datarate = <4>;
++ };
++ };
++ };
++ };
++
++ fragment@3 {
++ target = <&sound>;
++ snd: __overlay__ {
++ compatible = "simple-audio-card";
++ i2s-controller = <&i2s_clk_producer>;
++ status = "okay";
++
++ simple-audio-card,name = "draws";
++ simple-audio-card,format = "i2s";
++
++ simple-audio-card,bitclock-master = <&dailink0_master>;
++ simple-audio-card,frame-master = <&dailink0_master>;
++
++ simple-audio-card,widgets =
++ "Line", "Line In",
++ "Line", "Line Out";
++
++ simple-audio-card,routing =
++ "IN1_R", "Line In",
++ "IN1_L", "Line In",
++ "CM_L", "Line In",
++ "CM_R", "Line In",
++ "Line Out", "LOR",
++ "Line Out", "LOL";
++
++ dailink0_master: simple-audio-card,cpu {
++ sound-dai = <&i2s_clk_producer>;
++ };
++
++ simple-audio-card,codec {
++ sound-dai = <&tlv320aic32x4>;
++ };
++ };
++ };
++
++ fragment@4 {
++ target = <&gpio>;
++ __overlay__ {
++ aic3204_reset: aic3204_reset {
++ function = "gpio";
++ pins = "gpio13";
++ bias-pull-down;
++ };
++
++ sc16is752_irq: sc16is752_irq {
++ function = "gpio";
++ pins = "gpio17";
++ bias-pull-up;
++ };
++
++ pps_pins: pps_pins {
++ function = "gpio";
++ pins = "gpio7";
++ bias-disable;
++ };
++ };
++ };
++
++ __overrides__ {
++ draws_adc_ch4_gain = <&adc_ch4>,"ti,gain:0";
++ draws_adc_ch4_datarate = <&adc_ch4>,"ti,datarate:0";
++ draws_adc_ch5_gain = <&adc_ch5>,"ti,gain:0";
++ draws_adc_ch5_datarate = <&adc_ch5>,"ti,datarate:0";
++ draws_adc_ch6_gain = <&adc_ch6>,"ti,gain:0";
++ draws_adc_ch6_datarate = <&adc_ch6>,"ti,datarate:0";
++ draws_adc_ch7_gain = <&adc_ch7>,"ti,gain:0";
++ draws_adc_ch7_datarate = <&adc_ch7>,"ti,datarate:0";
++ alsaname = <&snd>, "simple-audio-card,name";
++ };
++};
+--- a/arch/arm/boot/dts/overlays/overlay_map.dts
++++ b/arch/arm/boot/dts/overlays/overlay_map.dts
+@@ -53,6 +53,16 @@
+ bcm2712;
+ };
+
++ draws {
++ bcm2835;
++ bcm2711;
++ bcm2712 = "draws-pi5";
++ };
++
++ draws-pi5 {
++ bcm2712;
++ };
++
+ dwc-otg {
+ renamed = "dwc-otg-deprecated";
+ };
--- /dev/null
+From 6bd47b7487eaf0ac2dd5877eb92ab6d048d69b27 Mon Sep 17 00:00:00 2001
+From: Annaliese McDermond <nh6z@nh6z.net>
+Date: Wed, 1 Jul 2026 12:38:45 -0700
+Subject: [PATCH] overlays: draws: Use GPIO flag constants for the reset and
+ PPS lines
+
+Replace the bare integer GPIO polarity flags in the DRAWS overlays with
+the GPIO_ACTIVE_HIGH / GPIO_ACTIVE_LOW constants from
+<dt-bindings/gpio/gpio.h>, so the reset and PPS line polarities are
+self-documenting.
+
+This is a readability-only change: GPIO_ACTIVE_HIGH is 0 and
+GPIO_ACTIVE_LOW is 1, so the compiled overlays are byte-identical.
+
+Signed-off-by: Annaliese McDermond <nh6z@nh6z.net>
+Assisted-by: Claude:claude-opus-4-8
+---
+ arch/arm/boot/dts/overlays/draws-overlay.dts | 5 +++--
+ arch/arm/boot/dts/overlays/draws-pi5-overlay.dts | 7 ++++---
+ 2 files changed, 7 insertions(+), 5 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/draws-overlay.dts
++++ b/arch/arm/boot/dts/overlays/draws-overlay.dts
+@@ -1,4 +1,5 @@
+ #include <dt-bindings/clock/bcm2835.h>
++#include <dt-bindings/gpio/gpio.h>
+ /*
+ * Device tree overlay for the DRAWS Hardware
+ */
+@@ -42,7 +43,7 @@
+ compatible = "pps-gpio";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pps_pins>;
+- gpios = <&gpio 7 0>;
++ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+ };
+
+@@ -77,7 +78,7 @@
+ pinctrl-0 = <&gpclk0_pin &aic3204_reset>;
+
+ /* Shared active-low reset for the codec and the SC16IS752 */
+- reset-gpios = <&gpio 13 1>;
++ reset-gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+
+ iov-supply = <&udrc0_ldoin>;
+ ldoin-supply = <&udrc0_ldoin>;
+--- a/arch/arm/boot/dts/overlays/draws-pi5-overlay.dts
++++ b/arch/arm/boot/dts/overlays/draws-pi5-overlay.dts
+@@ -1,4 +1,5 @@
+ #include <dt-bindings/clock/rp1.h>
++#include <dt-bindings/gpio/gpio.h>
+ /*
+ * Device tree overlay for the DRAWS Hardware on Raspberry Pi 5 / RP1
+ *
+@@ -47,7 +48,7 @@
+ compatible = "pps-gpio";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pps_pins>;
+- gpios = <&gpio 7 0>;
++ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+ };
+
+@@ -85,9 +86,9 @@
+ * GPIO13 is a shared, active-low reset for the codec and the
+ * SC16IS752. The aic32x4 driver deasserts by driving the line
+ * to its logical-low (released) state, so it must be declared
+- * GPIO_ACTIVE_LOW (1) for the RP1 to leave it high on release.
++ * GPIO_ACTIVE_LOW for the RP1 to leave it high on release.
+ */
+- reset-gpios = <&gpio 13 1>;
++ reset-gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+
+ iov-supply = <&udrc0_ldoin>;
+ ldoin-supply = <&udrc0_ldoin>;
--- /dev/null
+From 63598c83153e19b1f99067ab6df7409de2c111f8 Mon Sep 17 00:00:00 2001
+From: zjzhao <zjzhao@edatec.cn>
+Date: Wed, 1 Jul 2026 18:23:21 +0800
+Subject: [PATCH] overlays: sdhost: Add gpios_22_27 parameter
+
+Add a new optional parameter to the sdhost overlay:
+- gpios_22_27: Routes the SD0 interface to GPIO 22-27 (CLK, CMD,
+ DAT0-3) using ALT0 function, with appropriate pull configuration
+ (CLK: no pull, CMD/DAT: pull-up). This allows custom CM4 carrier
+ boards to use a secondary SD card slot wired to these pins.
+
+The parameter is implemented as dormant fragments and has no effect
+unless explicitly activated, preserving full backward compatibility
+with existing configurations.
+
+Tested on CM4 carrier board (EDATEC IPC2100) with:
+ dtoverlay=sdhost,gpios_22_27=on,pio_limit=1
+
+Signed-off-by: zjzhao <zjzhao@edatec.cn>
+---
+ arch/arm/boot/dts/overlays/README | 5 +++++
+ arch/arm/boot/dts/overlays/sdhost-overlay.dts | 20 +++++++++++++++++++
+ 2 files changed, 25 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -4741,6 +4741,11 @@ Params: overclock_50 Clock (i
+
+ debug Enable debug output (default off)
+
++ gpios_22_27 Route SD0 to GPIO 22-27 (CLK, CMD, DAT0-3)
++ for use with a secondary SD card slot on
++ custom carrier boards (e.g. CM4 baseboard).
++ Applies ALT0 function and appropriate pulls.
++
+
+ Name: sdio
+ Info: Selects the bcm2835-sdhost SD/MMC driver, optionally with overclock,
+--- a/arch/arm/boot/dts/overlays/sdhost-overlay.dts
++++ b/arch/arm/boot/dts/overlays/sdhost-overlay.dts
+@@ -29,10 +29,30 @@
+ };
+ };
+
++ fragment@3 {
++ target = <&gpio>;
++ __dormant__ {
++ sdhost_gpio_22_27: sdhost_gpio_22_27 {
++ brcm,pins = <22 23 24 25 26 27>;
++ brcm,function = <4>;
++ brcm,pull = <0 2 2 2 2 2>;
++ };
++ };
++ };
++
++ fragment@4 {
++ target = <&sdhost>;
++ __dormant__ {
++ pinctrl-names = "default";
++ pinctrl-0 = <&sdhost_gpio_22_27>;
++ };
++ };
++
+ __overrides__ {
+ overclock_50 = <&frag0>,"brcm,overclock-50:0";
+ force_pio = <&frag0>,"brcm,force-pio?";
+ pio_limit = <&frag0>,"brcm,pio-limit:0";
+ debug = <&frag0>,"brcm,debug?";
++ gpios_22_27 = <0>,"+3+4";
+ };
+ };
--- /dev/null
+From 05cd2866fcc6acda569f7f05fe283826b2b74d37 Mon Sep 17 00:00:00 2001
+From: Bogdan Nicolae <bogdan.nicolae@acm.org>
+Date: Fri, 3 Jul 2026 16:51:52 -0500
+Subject: [PATCH] brcmfmac-cyw: clean up PMKID and cookie code
+
+Do not touch the cookie. Value 0 means any follow-up should be discarded
+downstream. Instead, set the packet_id directly.cAlso, zero out the whole
+auth_status data structure instead of just auth_status.pmkid, which is
+safer: other bytes than pmkid may leak from stack if ssid_len is less than
+32, although the firmware is likely to ignore them. Do not rely on
+userspace to set params->pmkid only if authentication is successful (true
+for hostapd). Guard against it anyway.
+
+Signed-off-by: Bogdan Nicolae <bogdan.nicolae@acm.org>
+---
+ .../wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c
++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c
+@@ -127,7 +127,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiph
+ if (!ieee80211_is_auth(mgmt->frame_control))
+ return brcmf_cfg80211_mgmt_tx(wiphy, wdev, params, cookie);
+
+- *cookie = (u32)atomic_inc_return(&brcmf_cyw_mgmt_tx_id);
++ *cookie = 0;
+ vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev);
+
+ reinit_completion(&vif->mgmt_tx);
+@@ -159,7 +159,7 @@ int brcmf_cyw_mgmt_tx(struct wiphy *wiph
+
+ memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN);
+ memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN);
+- mf_params->packet_id = cpu_to_le32(*cookie);
++ mf_params->packet_id = cpu_to_le32(atomic_inc_return(&brcmf_cyw_mgmt_tx_id));
+ memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN],
+ le16_to_cpu(mf_params->len));
+
+@@ -204,7 +204,7 @@ brcmf_cyw_external_auth(struct wiphy *wi
+ {
+ struct brcmf_if *ifp;
+ struct brcmf_pub *drvr;
+- struct brcmf_auth_req_status_le auth_status;
++ struct brcmf_auth_req_status_le auth_status = {};
+ int ret = 0;
+
+ brcmf_dbg(TRACE, "Enter\n");
+@@ -212,6 +212,8 @@ brcmf_cyw_external_auth(struct wiphy *wi
+ ifp = netdev_priv(dev);
+ drvr = ifp->drvr;
+ if (params->status == WLAN_STATUS_SUCCESS) {
++ if (params->pmkid)
++ memcpy(auth_status.pmkid, params->pmkid, WLAN_PMKID_LEN);
+ auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS);
+ } else {
+ bphy_err(drvr, "External authentication failed: status=%d\n",
+@@ -224,9 +226,6 @@ brcmf_cyw_external_auth(struct wiphy *wi
+ IEEE80211_MAX_SSID_LEN);
+ auth_status.ssid_len = cpu_to_le32(params->ssid.ssid_len);
+ memcpy(auth_status.ssid, params->ssid.ssid, params->ssid.ssid_len);
+- memset(auth_status.pmkid, 0, WLAN_PMKID_LEN);
+- if (params->pmkid)
+- memcpy(auth_status.pmkid, params->pmkid, WLAN_PMKID_LEN);
+
+ ret = brcmf_fil_iovar_data_set(ifp, "auth_status", &auth_status,
+ sizeof(auth_status));
--- /dev/null
+From 3dcf42f6a581cc4e0c0221b4f55488bb8c50e5e4 Mon Sep 17 00:00:00 2001
+From: Richard Acayan <mailingradian@gmail.com>
+Date: Mon, 23 Mar 2026 22:01:27 -0400
+Subject: [PATCH] dt-bindings: media: i2c: Add Sony IMX355
+
+The IMX355 camera sensor is a camera sensor that can be found as the
+front camera in some smartphones, such as the Pixel 3, Pixel 3 XL, Pixel
+3a, and Pixel 3a XL. It already has a driver, but needs support for
+device tree. Document the IMX355 to support defining it in device tree.
+
+Reviewed-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Signed-off-by: Richard Acayan <mailingradian@gmail.com>
+---
+ .../bindings/media/i2c/sony,imx355.yaml | 111 ++++++++++++++++++
+ 1 file changed, 111 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml
+@@ -0,0 +1,111 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/media/i2c/sony,imx355.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Sony IMX355 Sensor
++
++maintainers:
++ - Richard Acayan <mailingradian@gmail.com>
++
++description:
++ The IMX355 sensor is a 3280x2464 image sensor, commonly found as the front
++ camera in smartphones.
++
++allOf:
++ - $ref: /schemas/media/video-interface-devices.yaml#
++
++properties:
++ compatible:
++ const: sony,imx355
++
++ reg:
++ maxItems: 1
++
++ clocks:
++ maxItems: 1
++
++ avdd-supply:
++ description: Analog power supply.
++
++ dvdd-supply:
++ description: Digital power supply.
++
++ dovdd-supply:
++ description: Interface power supply.
++
++ reset-gpios:
++ description: Reset GPIO (active low).
++ maxItems: 1
++
++ port:
++ $ref: /schemas/graph.yaml#/$defs/port-base
++ additionalProperties: false
++
++ properties:
++ endpoint:
++ $ref: /schemas/media/video-interfaces.yaml
++ unevaluatedProperties: false
++
++ properties:
++ data-lanes:
++ minItems: 4
++ maxItems: 4
++
++ required:
++ - link-frequencies
++
++ required:
++ - endpoint
++
++required:
++ - compatible
++ - reg
++ - clocks
++ - avdd-supply
++ - dvdd-supply
++ - dovdd-supply
++ - port
++
++unevaluatedProperties: false
++
++examples:
++ - |
++ #include <dt-bindings/clock/qcom,camcc-sdm845.h>
++ #include <dt-bindings/gpio/gpio.h>
++
++ i2c {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ camera@1a {
++ compatible = "sony,imx355";
++ reg = <0x1a>;
++
++ clocks = <&camcc CAM_CC_MCLK2_CLK>;
++
++ assigned-clocks = <&camcc CAM_CC_MCLK2_CLK>;
++ assigned-clock-rates = <24000000>;
++
++ reset-gpios = <&tlmm 9 GPIO_ACTIVE_LOW>;
++
++ avdd-supply = <&cam_front_ldo>;
++ dvdd-supply = <&cam_front_ldo>;
++ dovdd-supply = <&cam_vio_ldo>;
++
++ pinctrl-names = "default";
++ pinctrl-0 = <&cam_front_default>;
++
++ rotation = <270>;
++ orientation = <0>;
++
++ port {
++ cam_front_endpoint: endpoint {
++ data-lanes = <1 2 3 4>;
++ link-frequencies = /bits/ 64 <360000000>;
++ remote-endpoint = <&camss_endpoint1>;
++ };
++ };
++ };
++ };
--- /dev/null
+From 3ca16b7d50e94b66c495c0e1787e459dccb37738 Mon Sep 17 00:00:00 2001
+From: Richard Acayan <mailingradian@gmail.com>
+Date: Mon, 23 Mar 2026 22:01:28 -0400
+Subject: [PATCH] media: i2c: imx355: Support devicetree and power management
+
+A device tree compatible makes it possible for this driver to be used on
+Open Firmware devices. Initialization of power-managed resources such as
+the reset GPIO and voltage regulators can be specified in the device
+tree and handled by the driver. Add support for this so the Pixel 3a can
+use the driver.
+
+Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
+Reviewed-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
+Signed-off-by: Richard Acayan <mailingradian@gmail.com>
+---
+ drivers/media/i2c/imx355.c | 111 ++++++++++++++++++++++++++++++++++---
+ 1 file changed, 103 insertions(+), 8 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -3,9 +3,13 @@
+
+ #include <linux/acpi.h>
+ #include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/gpio/consumer.h>
+ #include <linux/i2c.h>
+ #include <linux/module.h>
++#include <linux/of.h>
+ #include <linux/pm_runtime.h>
++#include <linux/regulator/consumer.h>
+ #include <linux/unaligned.h>
+
+ #include <media/v4l2-ctrls.h>
+@@ -125,6 +129,15 @@ struct imx355 {
+ * Protect access to sensor v4l2 controls.
+ */
+ struct mutex mutex;
++
++ struct gpio_desc *reset_gpio;
++ struct regulator_bulk_data *supplies;
++};
++
++static const struct regulator_bulk_data imx355_supplies[] = {
++ { .supply = "avdd" },
++ { .supply = "dvdd" },
++ { .supply = "dovdd" },
+ };
+
+ static const struct imx355_reg imx355_global_regs[] = {
+@@ -1515,6 +1528,52 @@ static const struct v4l2_subdev_internal
+ .open = imx355_open,
+ };
+
++static int imx355_power_off(struct device *dev)
++{
++ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
++ struct v4l2_subdev *sd = i2c_get_clientdata(client);
++ struct imx355 *imx355 = to_imx355(sd);
++
++ gpiod_set_value_cansleep(imx355->reset_gpio, 1);
++
++ regulator_bulk_disable(ARRAY_SIZE(imx355_supplies), imx355->supplies);
++ clk_disable_unprepare(imx355->clk);
++
++ return 0;
++}
++
++static int imx355_power_on(struct device *dev)
++{
++ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
++ struct v4l2_subdev *sd = i2c_get_clientdata(client);
++ struct imx355 *imx355 = to_imx355(sd);
++ int ret;
++
++ ret = clk_prepare_enable(imx355->clk);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to enable clocks");
++
++ ret = regulator_bulk_enable(ARRAY_SIZE(imx355_supplies),
++ imx355->supplies);
++ if (ret) {
++ dev_err_probe(dev, ret, "failed to enable regulators");
++ goto error_disable_clocks;
++ }
++
++ usleep_range(1000, 2000);
++ gpiod_set_value_cansleep(imx355->reset_gpio, 0);
++ usleep_range(10000, 11000);
++
++ return 0;
++
++error_disable_clocks:
++ clk_disable_unprepare(imx355->clk);
++ return ret;
++}
++
++static DEFINE_RUNTIME_DEV_PM_OPS(imx355_pm_ops, imx355_power_off,
++ imx355_power_on, NULL);
++
+ /* Initialize control handlers */
+ static int imx355_init_controls(struct imx355 *imx355)
+ {
+@@ -1689,16 +1748,26 @@ static int imx355_probe(struct i2c_clien
+ "external clock %lu is not supported\n",
+ freq);
+
+- /* Initialize subdev */
+- v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops);
+-
+- /* Check module identity */
+- ret = imx355_identify_module(imx355);
++ ret = devm_regulator_bulk_get_const(imx355->dev,
++ ARRAY_SIZE(imx355_supplies),
++ imx355_supplies,
++ &imx355->supplies);
+ if (ret) {
+- dev_err(imx355->dev, "failed to find sensor: %d", ret);
++ dev_err_probe(imx355->dev, ret, "could not get regulators");
+ goto error_probe;
+ }
+
++ imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset",
++ GPIOD_OUT_HIGH);
++ if (IS_ERR(imx355->reset_gpio)) {
++ ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio),
++ "failed to get gpios");
++ goto error_probe;
++ }
++
++ /* Initialize subdev */
++ v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops);
++
+ imx355->hwcfg = imx355_get_hwcfg(imx355->dev);
+ if (!imx355->hwcfg) {
+ dev_err(imx355->dev, "failed to get hwcfg");
+@@ -1706,13 +1775,24 @@ static int imx355_probe(struct i2c_clien
+ goto error_probe;
+ }
+
++ ret = imx355_power_on(imx355->dev);
++ if (ret)
++ goto error_probe;
++
++ /* Check module identity */
++ ret = imx355_identify_module(imx355);
++ if (ret) {
++ dev_err(imx355->dev, "failed to find sensor: %d", ret);
++ goto error_power_off;
++ }
++
+ /* Set default mode to max resolution */
+ imx355->cur_mode = &supported_modes[0];
+
+ ret = imx355_init_controls(imx355);
+ if (ret) {
+ dev_err(imx355->dev, "failed to init controls: %d", ret);
+- goto error_probe;
++ goto error_power_off;
+ }
+
+ /* Initialize subdev */
+@@ -1752,6 +1832,9 @@ error_media_entity_runtime_pm:
+ error_handler_free:
+ v4l2_ctrl_handler_free(imx355->sd.ctrl_handler);
+
++error_power_off:
++ imx355_power_off(imx355->dev);
++
+ error_probe:
+ mutex_destroy(&imx355->mutex);
+
+@@ -1768,7 +1851,11 @@ static void imx355_remove(struct i2c_cli
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+ pm_runtime_disable(imx355->dev);
+- pm_runtime_set_suspended(imx355->dev);
++
++ if (!pm_runtime_status_suspended(imx355->dev)) {
++ imx355_power_off(imx355->dev);
++ pm_runtime_set_suspended(imx355->dev);
++ }
+
+ mutex_destroy(&imx355->mutex);
+ }
+@@ -1779,10 +1866,18 @@ static const struct acpi_device_id imx35
+ };
+ MODULE_DEVICE_TABLE(acpi, imx355_acpi_ids);
+
++static const struct of_device_id imx355_match_table[] = {
++ { .compatible = "sony,imx355", },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, imx355_match_table);
++
+ static struct i2c_driver imx355_i2c_driver = {
+ .driver = {
+ .name = "imx355",
+ .acpi_match_table = ACPI_PTR(imx355_acpi_ids),
++ .of_match_table = imx355_match_table,
++ .pm = &imx355_pm_ops,
+ },
+ .probe = imx355_probe,
+ .remove = imx355_remove,
--- /dev/null
+From 26f960af3458d96950e9bcdd872515677e2ba018 Mon Sep 17 00:00:00 2001
+From: Richard Acayan <mailingradian@gmail.com>
+Date: Mon, 23 Mar 2026 22:01:29 -0400
+Subject: [PATCH] media: i2c: imx355: Restrict data lanes to 4
+
+The IMX355 sensor driver currently supports having 4 data lanes. There
+can't be more or less, so check if the firmware specifies 4 lanes.
+
+Suggested-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+Link: https://lore.kernel.org/r/aW3uFcT1zmiF4GUP@kekkonen.localdomain
+Signed-off-by: Richard Acayan <mailingradian@gmail.com>
+---
+ drivers/media/i2c/imx355.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -66,6 +66,9 @@
+ #define IMX355_EXT_CLK 19200000
+ #define IMX355_LINK_FREQ_INDEX 0
+
++/* number of data lanes */
++#define IMX355_DATA_LANES 4
++
+ struct imx355_reg {
+ u16 address;
+ u8 val;
+@@ -1705,6 +1708,9 @@ static struct imx355_hwcfg *imx355_get_h
+ if (!cfg)
+ goto out_err;
+
++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES)
++ goto out_err;
++
+ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
+ bus_cfg.nr_of_link_frequencies,
+ link_freq_menu_items,
--- /dev/null
+From a54bb3088f003309d7fb23cbcd899dea20b85053 Mon Sep 17 00:00:00 2001
+From: David Heidelberg <david@ixit.cz>
+Date: Tue, 14 Apr 2026 12:12:10 +0200
+Subject: [PATCH] media: i2c: imx355: Add support for 24 MHz external clock
+
+The IMX355 sensor supports multiple external clock frequencies,
+including 19.2 MHz and 24 MHz. The driver currently supports only
+fixed 19.2 MHz input clock.
+
+Refactor the clock handling to make the PLL configuration dependent
+on the external clock frequency and add support for 24 MHz. Introduce
+a table of clock parameter sets and program the corresponding EXTCLK
+frequency and PLL multipliers to maintain consistent internal VCO
+frequencies across supported inputs.
+
+The PLL settings are adjusted so that OP VCO remains at 720 MHz
+
+This preserves existing timing characteristics while allowing systems
+using a 24 MHz clock to operate correctly.
+
+No functional change for existing 19.2 MHz users.
+
+Assisted-by: Claude:claude-opus-4-6
+Signed-off-by: David Heidelberg <david@ixit.cz>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 77 ++++++++++++++++++++++----------------
+ 1 file changed, 45 insertions(+), 32 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -25,6 +25,10 @@
+ #define IMX355_REG_CHIP_ID 0x0016
+ #define IMX355_CHIP_ID 0x0355
+
++/* PLL registers that depend on the external clock frequency */
++#define IMX355_REG_EXTCLK_FREQ 0x0136
++#define IMX355_REG_PLL_OP_MUL 0x030e
++
+ /* V_TIMING internal */
+ #define IMX355_REG_FLL 0x0340
+ #define IMX355_FLL_MAX 0xffff
+@@ -63,7 +67,6 @@
+
+ /* default link frequency and external clock */
+ #define IMX355_LINK_FREQ_DEFAULT 360000000LL
+-#define IMX355_EXT_CLK 19200000
+ #define IMX355_LINK_FREQ_INDEX 0
+
+ /* number of data lanes */
+@@ -100,6 +103,29 @@ struct imx355_mode {
+ struct imx355_reg_list reg_list;
+ };
+
++struct imx355_clk_params {
++ u32 ext_clk;
++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */
++ u16 pll_op_mpy; /* OP system PLL multiplier */
++};
++
++/*
++ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing.
++ * All modes use the same PLL setup for OP, with IOPCK being 720MHz.
++ */
++static const struct imx355_clk_params imx355_clk_params[] = {
++ {
++ .ext_clk = 19200000,
++ .extclk_freq = 0x1333, /* 19.2 MHz */
++ .pll_op_mpy = 75, /* 19.2 / 2 * 75 = 720 MHz */
++ },
++ {
++ .ext_clk = 24000000,
++ .extclk_freq = 0x1800, /* 24.0 MHz */
++ .pll_op_mpy = 60, /* 24.0 / 2 * 60 = 720 MHz */
++ },
++};
++
+ struct imx355_hwcfg {
+ unsigned long link_freq_bitmap;
+ };
+@@ -125,6 +151,7 @@ struct imx355 {
+ const struct imx355_mode *cur_mode;
+
+ struct imx355_hwcfg *hwcfg;
++ const struct imx355_clk_params *clk_params;
+
+ /*
+ * Mutex for serialized access:
+@@ -144,8 +171,6 @@ static const struct regulator_bulk_data
+ };
+
+ static const struct imx355_reg imx355_global_regs[] = {
+- { 0x0136, 0x13 },
+- { 0x0137, 0x33 },
+ { 0x304e, 0x03 },
+ { 0x4348, 0x16 },
+ { 0x4350, 0x19 },
+@@ -235,8 +260,6 @@ static const struct imx355_reg mode_3268
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -284,8 +307,6 @@ static const struct imx355_reg mode_3264
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -333,8 +354,6 @@ static const struct imx355_reg mode_3280
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -382,8 +401,6 @@ static const struct imx355_reg mode_1940
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -431,8 +448,6 @@ static const struct imx355_reg mode_1936
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -480,8 +495,6 @@ static const struct imx355_reg mode_1924
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -529,8 +542,6 @@ static const struct imx355_reg mode_1920
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -578,8 +589,6 @@ static const struct imx355_reg mode_1640
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -627,8 +636,6 @@ static const struct imx355_reg mode_1640
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -676,8 +683,6 @@ static const struct imx355_reg mode_1300
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -725,8 +730,6 @@ static const struct imx355_reg mode_1296
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -774,8 +777,6 @@ static const struct imx355_reg mode_1284
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -823,8 +824,6 @@ static const struct imx355_reg mode_1280
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+@@ -872,8 +871,6 @@ static const struct imx355_reg mode_820x
+ { 0x0307, 0x78 },
+ { 0x030b, 0x01 },
+ { 0x030d, 0x02 },
+- { 0x030e, 0x00 },
+- { 0x030f, 0x4b },
+ { 0x0310, 0x00 },
+ { 0x0700, 0x02 },
+ { 0x0701, 0x78 },
+@@ -1422,6 +1419,16 @@ static int imx355_start_streaming(struct
+ return ret;
+ }
+
++ /* Set PLL registers for the external clock frequency */
++ ret = imx355_write_reg(imx355, IMX355_REG_EXTCLK_FREQ, 2,
++ imx355->clk_params->extclk_freq);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_PLL_OP_MUL, 2,
++ imx355->clk_params->pll_op_mpy);
++ if (ret)
++ return ret;
++
+ /* set digital gain control to all color mode */
+ ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1);
+ if (ret)
+@@ -1749,7 +1756,13 @@ static int imx355_probe(struct i2c_clien
+ "failed to get clock\n");
+
+ freq = clk_get_rate(imx355->clk);
+- if (freq != IMX355_EXT_CLK)
++ for (unsigned int i = 0; i < ARRAY_SIZE(imx355_clk_params); i++) {
++ if (freq == imx355_clk_params[i].ext_clk) {
++ imx355->clk_params = &imx355_clk_params[i];
++ break;
++ }
++ }
++ if (!imx355->clk_params)
+ return dev_err_probe(imx355->dev, -EINVAL,
+ "external clock %lu is not supported\n",
+ freq);
--- /dev/null
+From fcfd82a467057ba21ff5c5ab885489a9d4652486 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Thu, 14 May 2026 11:34:32 +0100
+Subject: [PATCH] dt-bindings: media: imx355: Allow 2 CSI2 data lane output
+
+The sensor supports output over either 2 or 4 CSI2 data
+lanes. The binding only permitted the 4 lane configuration.
+
+Add the 2 lane configuration as an option.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ .../devicetree/bindings/media/i2c/sony,imx355.yaml | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+--- a/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml
++++ b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml
+@@ -50,8 +50,15 @@ properties:
+
+ properties:
+ data-lanes:
+- minItems: 4
+- maxItems: 4
++ oneOf:
++ - items:
++ - const: 1
++ - const: 2
++ - const: 3
++ - const: 4
++ - items:
++ - const: 1
++ - const: 2
+
+ required:
+ - link-frequencies
--- /dev/null
+From 34375d4e51697a1f41af9929044bc47bffe2df12 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 15:07:59 +0100
+Subject: [PATCH] media: imx355: Remove duplicated registers from the mode
+ tables
+
+A large number of registers are identical within all the modes.
+Move those to imx355_global_regs.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 344 +++----------------------------------
+ 1 file changed, 22 insertions(+), 322 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -221,6 +221,28 @@ static const struct imx355_reg imx355_gl
+ { 0x68b0, 0x00 },
+ { 0x3058, 0x00 },
+ { 0x305a, 0x00 },
++ { 0x0112, 0x0a },
++ { 0x0113, 0x0a },
++ { 0x0114, 0x03 },
++ { 0x0301, 0x05 },
++ { 0x0303, 0x01 },
++ { 0x0305, 0x02 },
++ { 0x0306, 0x00 },
++ { 0x0307, 0x78 },
++ { 0x030d, 0x02 },
++ { 0x0310, 0x00 },
++ { 0x0220, 0x00 },
++ { 0x0222, 0x01 },
++ { 0x0820, 0x0b },
++ { 0x0821, 0x40 },
++ { 0x3088, 0x04 },
++ { 0x6813, 0x02 },
++ { 0x6835, 0x07 },
++ { 0x6836, 0x01 },
++ { 0x6837, 0x04 },
++ { 0x684d, 0x07 },
++ { 0x684e, 0x01 },
++ { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg_list imx355_global_setting = {
+@@ -229,9 +251,6 @@ static const struct imx355_reg_list imx3
+ };
+
+ static const struct imx355_reg mode_3268x2448_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x0a },
+@@ -244,8 +263,6 @@ static const struct imx355_reg mode_3268
+ { 0x0349, 0xcb },
+ { 0x034a, 0x09 },
+ { 0x034b, 0x97 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -253,32 +270,11 @@ static const struct imx355_reg mode_3268
+ { 0x034d, 0xc4 },
+ { 0x034e, 0x09 },
+ { 0x034f, 0x90 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_3264x2448_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x0a },
+@@ -291,8 +287,6 @@ static const struct imx355_reg mode_3264
+ { 0x0349, 0xc7 },
+ { 0x034a, 0x09 },
+ { 0x034b, 0x97 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -300,32 +294,11 @@ static const struct imx355_reg mode_3264
+ { 0x034d, 0xc0 },
+ { 0x034e, 0x09 },
+ { 0x034f, 0x90 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_3280x2464_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x0a },
+@@ -338,8 +311,6 @@ static const struct imx355_reg mode_3280
+ { 0x0349, 0xcf },
+ { 0x034a, 0x09 },
+ { 0x034b, 0x9f },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -347,32 +318,11 @@ static const struct imx355_reg mode_3280
+ { 0x034d, 0xd0 },
+ { 0x034e, 0x09 },
+ { 0x034f, 0xa0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1940x1096_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x05 },
+@@ -385,8 +335,6 @@ static const struct imx355_reg mode_1940
+ { 0x0349, 0x33 },
+ { 0x034a, 0x06 },
+ { 0x034b, 0xf3 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -394,32 +342,11 @@ static const struct imx355_reg mode_1940
+ { 0x034d, 0x94 },
+ { 0x034e, 0x04 },
+ { 0x034f, 0x48 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1936x1096_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x05 },
+@@ -432,8 +359,6 @@ static const struct imx355_reg mode_1936
+ { 0x0349, 0x2f },
+ { 0x034a, 0x06 },
+ { 0x034b, 0xf3 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -441,32 +366,11 @@ static const struct imx355_reg mode_1936
+ { 0x034d, 0x90 },
+ { 0x034e, 0x04 },
+ { 0x034f, 0x48 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1924x1080_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x05 },
+@@ -479,8 +383,6 @@ static const struct imx355_reg mode_1924
+ { 0x0349, 0x2b },
+ { 0x034a, 0x06 },
+ { 0x034b, 0xeb },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -488,32 +390,11 @@ static const struct imx355_reg mode_1924
+ { 0x034d, 0x84 },
+ { 0x034e, 0x04 },
+ { 0x034f, 0x38 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1920x1080_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x05 },
+@@ -526,8 +407,6 @@ static const struct imx355_reg mode_1920
+ { 0x0349, 0x27 },
+ { 0x034a, 0x06 },
+ { 0x034b, 0xeb },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -535,32 +414,11 @@ static const struct imx355_reg mode_1920
+ { 0x034d, 0x80 },
+ { 0x034e, 0x04 },
+ { 0x034f, 0x38 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1640x1232_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -573,8 +431,6 @@ static const struct imx355_reg mode_1640
+ { 0x0349, 0xcf },
+ { 0x034a, 0x09 },
+ { 0x034b, 0x9f },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -582,32 +438,11 @@ static const struct imx355_reg mode_1640
+ { 0x034d, 0x68 },
+ { 0x034e, 0x04 },
+ { 0x034f, 0xd0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1640x922_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -620,8 +455,6 @@ static const struct imx355_reg mode_1640
+ { 0x0349, 0xcf },
+ { 0x034a, 0x08 },
+ { 0x034b, 0x63 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -629,32 +462,11 @@ static const struct imx355_reg mode_1640
+ { 0x034d, 0x68 },
+ { 0x034e, 0x03 },
+ { 0x034f, 0x9a },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1300x736_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -667,8 +479,6 @@ static const struct imx355_reg mode_1300
+ { 0x0349, 0x7f },
+ { 0x034a, 0x07 },
+ { 0x034b, 0xaf },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -676,32 +486,11 @@ static const struct imx355_reg mode_1300
+ { 0x034d, 0x14 },
+ { 0x034e, 0x02 },
+ { 0x034f, 0xe0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1296x736_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -714,8 +503,6 @@ static const struct imx355_reg mode_1296
+ { 0x0349, 0x77 },
+ { 0x034a, 0x07 },
+ { 0x034b, 0xaf },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -723,32 +510,11 @@ static const struct imx355_reg mode_1296
+ { 0x034d, 0x10 },
+ { 0x034e, 0x02 },
+ { 0x034f, 0xe0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1284x720_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -761,8 +527,6 @@ static const struct imx355_reg mode_1284
+ { 0x0349, 0x6f },
+ { 0x034a, 0x07 },
+ { 0x034b, 0x9f },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -770,32 +534,11 @@ static const struct imx355_reg mode_1284
+ { 0x034d, 0x04 },
+ { 0x034e, 0x02 },
+ { 0x034f, 0xd0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_1280x720_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+ { 0x0340, 0x05 },
+@@ -808,8 +551,6 @@ static const struct imx355_reg mode_1280
+ { 0x0349, 0x67 },
+ { 0x034a, 0x07 },
+ { 0x034b, 0x9f },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -817,32 +558,11 @@ static const struct imx355_reg mode_1280
+ { 0x034d, 0x00 },
+ { 0x034e, 0x02 },
+ { 0x034f, 0xd0 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const struct imx355_reg mode_820x616_regs[] = {
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+ { 0x0340, 0x02 },
+@@ -855,8 +575,6 @@ static const struct imx355_reg mode_820x
+ { 0x0349, 0xcf },
+ { 0x034a, 0x09 },
+ { 0x034b, 0x9f },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x44 },
+ { 0x0902, 0x00 },
+@@ -864,26 +582,8 @@ static const struct imx355_reg mode_820x
+ { 0x034d, 0x34 },
+ { 0x034e, 0x02 },
+ { 0x034f, 0x68 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030b, 0x01 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+ { 0x0700, 0x02 },
+ { 0x0701, 0x78 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+ };
+
+ static const char * const imx355_test_pattern_menu[] = {
--- /dev/null
+From 122d3e8240d6d7093c7340abfc6123531f53206e Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 15:27:07 +0100
+Subject: [PATCH] media: imx355: Remove setting FRM_LENGTH_LINES in the mode
+ regs
+
+Registers 0x0340 and 0x0341 (FRM_LENGTH_LINES) are already written
+from the set_ctrl(V4L2_CID_VBLANK) handler, so don't write them
+from the mode register list.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 28 ----------------------------
+ 1 file changed, 28 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -253,8 +253,6 @@ static const struct imx355_reg_list imx3
+ static const struct imx355_reg mode_3268x2448_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x0a },
+- { 0x0341, 0x37 },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x08 },
+ { 0x0346, 0x00 },
+@@ -277,8 +275,6 @@ static const struct imx355_reg mode_3268
+ static const struct imx355_reg mode_3264x2448_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x0a },
+- { 0x0341, 0x37 },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x08 },
+ { 0x0346, 0x00 },
+@@ -301,8 +297,6 @@ static const struct imx355_reg mode_3264
+ static const struct imx355_reg mode_3280x2464_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x0a },
+- { 0x0341, 0x37 },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x00 },
+ { 0x0346, 0x00 },
+@@ -325,8 +319,6 @@ static const struct imx355_reg mode_3280
+ static const struct imx355_reg mode_1940x1096_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x02 },
+ { 0x0345, 0xa0 },
+ { 0x0346, 0x02 },
+@@ -349,8 +341,6 @@ static const struct imx355_reg mode_1940
+ static const struct imx355_reg mode_1936x1096_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x02 },
+ { 0x0345, 0xa0 },
+ { 0x0346, 0x02 },
+@@ -373,8 +363,6 @@ static const struct imx355_reg mode_1936
+ static const struct imx355_reg mode_1924x1080_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x02 },
+ { 0x0345, 0xa8 },
+ { 0x0346, 0x02 },
+@@ -397,8 +385,6 @@ static const struct imx355_reg mode_1924
+ static const struct imx355_reg mode_1920x1080_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x02 },
+ { 0x0345, 0xa8 },
+ { 0x0346, 0x02 },
+@@ -421,8 +407,6 @@ static const struct imx355_reg mode_1920
+ static const struct imx355_reg mode_1640x1232_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x00 },
+ { 0x0346, 0x00 },
+@@ -445,8 +429,6 @@ static const struct imx355_reg mode_1640
+ static const struct imx355_reg mode_1640x922_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x00 },
+ { 0x0346, 0x01 },
+@@ -469,8 +451,6 @@ static const struct imx355_reg mode_1640
+ static const struct imx355_reg mode_1300x736_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x01 },
+ { 0x0345, 0x58 },
+ { 0x0346, 0x01 },
+@@ -493,8 +473,6 @@ static const struct imx355_reg mode_1300
+ static const struct imx355_reg mode_1296x736_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x01 },
+ { 0x0345, 0x58 },
+ { 0x0346, 0x01 },
+@@ -517,8 +495,6 @@ static const struct imx355_reg mode_1296
+ static const struct imx355_reg mode_1284x720_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x01 },
+ { 0x0345, 0x68 },
+ { 0x0346, 0x02 },
+@@ -541,8 +517,6 @@ static const struct imx355_reg mode_1284
+ static const struct imx355_reg mode_1280x720_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0340, 0x05 },
+- { 0x0341, 0x1a },
+ { 0x0344, 0x01 },
+ { 0x0345, 0x68 },
+ { 0x0346, 0x02 },
+@@ -565,8 +539,6 @@ static const struct imx355_reg mode_1280
+ static const struct imx355_reg mode_820x616_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0340, 0x02 },
+- { 0x0341, 0x8c },
+ { 0x0344, 0x00 },
+ { 0x0345, 0x00 },
+ { 0x0346, 0x00 },
--- /dev/null
+From 305b8900f5e9c4ab6b496f12cec05e26925be6e4 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 15:53:10 +0100
+Subject: [PATCH] media: imx355: Programmatically set the crop parameters for
+ each mode
+
+Currently the cropping is set via register entries in the per mode
+register lists. Add the crop information to the mode structure as
+a v4l2_rect, and set the registers programmatically.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 290 ++++++++++++++++---------------------
+ 1 file changed, 121 insertions(+), 169 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -33,6 +33,13 @@
+ #define IMX355_REG_FLL 0x0340
+ #define IMX355_FLL_MAX 0xffff
+
++#define IMX355_REG_X_ADD_START 0x0344
++#define IMX355_REG_Y_ADD_START 0x0346
++#define IMX355_REG_X_ADD_END 0x0348
++#define IMX355_REG_Y_ADD_END 0x034a
++#define IMX355_REG_X_OUT_SIZE 0x034c
++#define IMX355_REG_Y_OUT_SIZE 0x034e
++
+ /* Exposure control */
+ #define IMX355_REG_EXPOSURE 0x0202
+ #define IMX355_EXPOSURE_MIN 1
+@@ -88,6 +95,7 @@ struct imx355_mode {
+ u32 width;
+ /* Frame height */
+ u32 height;
++ struct v4l2_rect crop;
+
+ /* V-timing */
+ u32 fll_def;
+@@ -253,21 +261,9 @@ static const struct imx355_reg_list imx3
+ static const struct imx355_reg mode_3268x2448_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x08 },
+- { 0x0346, 0x00 },
+- { 0x0347, 0x08 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xcb },
+- { 0x034a, 0x09 },
+- { 0x034b, 0x97 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x0c },
+- { 0x034d, 0xc4 },
+- { 0x034e, 0x09 },
+- { 0x034f, 0x90 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -275,21 +271,9 @@ static const struct imx355_reg mode_3268
+ static const struct imx355_reg mode_3264x2448_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x08 },
+- { 0x0346, 0x00 },
+- { 0x0347, 0x08 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xc7 },
+- { 0x034a, 0x09 },
+- { 0x034b, 0x97 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x0c },
+- { 0x034d, 0xc0 },
+- { 0x034e, 0x09 },
+- { 0x034f, 0x90 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -297,21 +281,9 @@ static const struct imx355_reg mode_3264
+ static const struct imx355_reg mode_3280x2464_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x00 },
+- { 0x0346, 0x00 },
+- { 0x0347, 0x00 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xcf },
+- { 0x034a, 0x09 },
+- { 0x034b, 0x9f },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x0c },
+- { 0x034d, 0xd0 },
+- { 0x034e, 0x09 },
+- { 0x034f, 0xa0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -319,21 +291,9 @@ static const struct imx355_reg mode_3280
+ static const struct imx355_reg mode_1940x1096_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x02 },
+- { 0x0345, 0xa0 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0xac },
+- { 0x0348, 0x0a },
+- { 0x0349, 0x33 },
+- { 0x034a, 0x06 },
+- { 0x034b, 0xf3 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x07 },
+- { 0x034d, 0x94 },
+- { 0x034e, 0x04 },
+- { 0x034f, 0x48 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -341,21 +301,9 @@ static const struct imx355_reg mode_1940
+ static const struct imx355_reg mode_1936x1096_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x02 },
+- { 0x0345, 0xa0 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0xac },
+- { 0x0348, 0x0a },
+- { 0x0349, 0x2f },
+- { 0x034a, 0x06 },
+- { 0x034b, 0xf3 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x07 },
+- { 0x034d, 0x90 },
+- { 0x034e, 0x04 },
+- { 0x034f, 0x48 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -363,21 +311,9 @@ static const struct imx355_reg mode_1936
+ static const struct imx355_reg mode_1924x1080_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x02 },
+- { 0x0345, 0xa8 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0xb4 },
+- { 0x0348, 0x0a },
+- { 0x0349, 0x2b },
+- { 0x034a, 0x06 },
+- { 0x034b, 0xeb },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x07 },
+- { 0x034d, 0x84 },
+- { 0x034e, 0x04 },
+- { 0x034f, 0x38 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -385,21 +321,9 @@ static const struct imx355_reg mode_1924
+ static const struct imx355_reg mode_1920x1080_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x02 },
+- { 0x0345, 0xa8 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0xb4 },
+- { 0x0348, 0x0a },
+- { 0x0349, 0x27 },
+- { 0x034a, 0x06 },
+- { 0x034b, 0xeb },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x07 },
+- { 0x034d, 0x80 },
+- { 0x034e, 0x04 },
+- { 0x034f, 0x38 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -407,21 +331,9 @@ static const struct imx355_reg mode_1920
+ static const struct imx355_reg mode_1640x1232_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x00 },
+- { 0x0346, 0x00 },
+- { 0x0347, 0x00 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xcf },
+- { 0x034a, 0x09 },
+- { 0x034b, 0x9f },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x06 },
+- { 0x034d, 0x68 },
+- { 0x034e, 0x04 },
+- { 0x034f, 0xd0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -429,21 +341,9 @@ static const struct imx355_reg mode_1640
+ static const struct imx355_reg mode_1640x922_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x00 },
+- { 0x0346, 0x01 },
+- { 0x0347, 0x30 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xcf },
+- { 0x034a, 0x08 },
+- { 0x034b, 0x63 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x06 },
+- { 0x034d, 0x68 },
+- { 0x034e, 0x03 },
+- { 0x034f, 0x9a },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -451,21 +351,9 @@ static const struct imx355_reg mode_1640
+ static const struct imx355_reg mode_1300x736_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x01 },
+- { 0x0345, 0x58 },
+- { 0x0346, 0x01 },
+- { 0x0347, 0xf0 },
+- { 0x0348, 0x0b },
+- { 0x0349, 0x7f },
+- { 0x034a, 0x07 },
+- { 0x034b, 0xaf },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x05 },
+- { 0x034d, 0x14 },
+- { 0x034e, 0x02 },
+- { 0x034f, 0xe0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -473,21 +361,9 @@ static const struct imx355_reg mode_1300
+ static const struct imx355_reg mode_1296x736_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x01 },
+- { 0x0345, 0x58 },
+- { 0x0346, 0x01 },
+- { 0x0347, 0xf0 },
+- { 0x0348, 0x0b },
+- { 0x0349, 0x77 },
+- { 0x034a, 0x07 },
+- { 0x034b, 0xaf },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x05 },
+- { 0x034d, 0x10 },
+- { 0x034e, 0x02 },
+- { 0x034f, 0xe0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -495,21 +371,9 @@ static const struct imx355_reg mode_1296
+ static const struct imx355_reg mode_1284x720_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x01 },
+- { 0x0345, 0x68 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0x00 },
+- { 0x0348, 0x0b },
+- { 0x0349, 0x6f },
+- { 0x034a, 0x07 },
+- { 0x034b, 0x9f },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x05 },
+- { 0x034d, 0x04 },
+- { 0x034e, 0x02 },
+- { 0x034f, 0xd0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -517,21 +381,9 @@ static const struct imx355_reg mode_1284
+ static const struct imx355_reg mode_1280x720_regs[] = {
+ { 0x0342, 0x07 },
+ { 0x0343, 0x2c },
+- { 0x0344, 0x01 },
+- { 0x0345, 0x68 },
+- { 0x0346, 0x02 },
+- { 0x0347, 0x00 },
+- { 0x0348, 0x0b },
+- { 0x0349, 0x67 },
+- { 0x034a, 0x07 },
+- { 0x034b, 0x9f },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x05 },
+- { 0x034d, 0x00 },
+- { 0x034e, 0x02 },
+- { 0x034f, 0xd0 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+@@ -539,21 +391,9 @@ static const struct imx355_reg mode_1280
+ static const struct imx355_reg mode_820x616_regs[] = {
+ { 0x0342, 0x0e },
+ { 0x0343, 0x58 },
+- { 0x0344, 0x00 },
+- { 0x0345, 0x00 },
+- { 0x0346, 0x00 },
+- { 0x0347, 0x00 },
+- { 0x0348, 0x0c },
+- { 0x0349, 0xcf },
+- { 0x034a, 0x09 },
+- { 0x034b, 0x9f },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x44 },
+ { 0x0902, 0x00 },
+- { 0x034c, 0x03 },
+- { 0x034d, 0x34 },
+- { 0x034e, 0x02 },
+- { 0x034f, 0x68 },
+ { 0x0700, 0x02 },
+ { 0x0701, 0x78 },
+ };
+@@ -579,6 +419,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 3280,
+ .height = 2464,
++ .crop = {
++ .width = 3280,
++ .height = 2464,
++ .left = 0,
++ .top = 0,
++ },
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+@@ -591,6 +437,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 3268,
+ .height = 2448,
++ .crop = {
++ .width = 3268,
++ .height = 2448,
++ .left = 8,
++ .top = 8,
++ },
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+@@ -603,6 +455,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 3264,
+ .height = 2448,
++ .crop = {
++ .width = 3264,
++ .height = 2448,
++ .left = 8,
++ .top = 8,
++ },
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+@@ -615,6 +473,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1940,
+ .height = 1096,
++ .crop = {
++ .width = 1940,
++ .height = 1096,
++ .left = 672,
++ .top = 684,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+@@ -627,6 +491,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1936,
+ .height = 1096,
++ .crop = {
++ .width = 1936,
++ .height = 1096,
++ .left = 672,
++ .top = 684,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+@@ -639,6 +509,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1924,
+ .height = 1080,
++ .crop = {
++ .width = 1924,
++ .height = 1080,
++ .left = 680,
++ .top = 692,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+@@ -651,6 +527,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1920,
+ .height = 1080,
++ .crop = {
++ .width = 1920,
++ .height = 1080,
++ .left = 680,
++ .top = 692,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+@@ -663,6 +545,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1640,
+ .height = 1232,
++ .crop = {
++ .width = 3280,
++ .height = 2464,
++ .left = 0,
++ .top = 0,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -675,6 +563,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1640,
+ .height = 922,
++ .crop = {
++ .width = 3280,
++ .height = 1844,
++ .left = 0,
++ .top = 304,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -687,6 +581,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1300,
+ .height = 736,
++ .crop = {
++ .width = 2600,
++ .height = 1472,
++ .left = 344,
++ .top = 496,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -699,6 +599,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1296,
+ .height = 736,
++ .crop = {
++ .width = 2592,
++ .height = 1472,
++ .left = 344,
++ .top = 496,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -711,6 +617,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1284,
+ .height = 720,
++ .crop = {
++ .width = 2568,
++ .height = 1440,
++ .left = 360,
++ .top = 512,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -723,6 +635,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 1280,
+ .height = 720,
++ .crop = {
++ .width = 2560,
++ .height = 1440,
++ .left = 360,
++ .top = 512,
++ },
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+@@ -735,6 +653,12 @@ static const struct imx355_mode supporte
+ {
+ .width = 820,
+ .height = 616,
++ .crop = {
++ .width = 3280,
++ .height = 2464,
++ .left = 0,
++ .top = 0,
++ },
+ .fll_def = 652,
+ .fll_min = 652,
+ .llp = 3672,
+@@ -1073,6 +997,7 @@ imx355_set_pad_format(struct v4l2_subdev
+ static int imx355_start_streaming(struct imx355 *imx355)
+ {
+ const struct imx355_reg_list *reg_list;
++ const struct imx355_mode *mode;
+ int ret;
+
+ /* Global Setting */
+@@ -1084,13 +1009,40 @@ static int imx355_start_streaming(struct
+ }
+
+ /* Apply default values of current mode */
+- reg_list = &imx355->cur_mode->reg_list;
++ mode = imx355->cur_mode;
++ reg_list = &mode->reg_list;
+ ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs);
+ if (ret) {
+ dev_err(imx355->dev, "failed to set mode");
+ return ret;
+ }
+
++ /* Set readout crop and size registers */
++ ret = imx355_write_reg(imx355, IMX355_REG_X_ADD_START, 2,
++ mode->crop.left);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_Y_ADD_START, 2,
++ mode->crop.top);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_X_ADD_END, 2,
++ mode->crop.width + mode->crop.left - 1);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_Y_ADD_END, 2,
++ mode->crop.height + mode->crop.top - 1);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_X_OUT_SIZE, 2,
++ mode->width);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_Y_OUT_SIZE, 2,
++ mode->height);
++ if (ret)
++ return ret;
++
+ /* Set PLL registers for the external clock frequency */
+ ret = imx355_write_reg(imx355, IMX355_REG_EXTCLK_FREQ, 2,
+ imx355->clk_params->extclk_freq);
--- /dev/null
+From e63f2fe1091312857604a331f0ef240fae1acab4 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 16:06:21 +0100
+Subject: [PATCH] media: imx355: Set register LINE_LENGTH_PCK programmatically
+
+The driver already has the LLP value stored in the mode structure,
+but also had the same value set via register writes in the mode's
+register list. Remove this duplication.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+---
+ drivers/media/i2c/imx355.c | 38 ++++++++++----------------------------
+ 1 file changed, 10 insertions(+), 28 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -33,6 +33,9 @@
+ #define IMX355_REG_FLL 0x0340
+ #define IMX355_FLL_MAX 0xffff
+
++#define IMX355_REG_LLP 0x0342
++#define IMX355_LLP_MAX 0xffff
++
+ #define IMX355_REG_X_ADD_START 0x0344
+ #define IMX355_REG_Y_ADD_START 0x0346
+ #define IMX355_REG_X_ADD_END 0x0348
+@@ -259,8 +262,6 @@ static const struct imx355_reg_list imx3
+ };
+
+ static const struct imx355_reg mode_3268x2448_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -269,8 +270,6 @@ static const struct imx355_reg mode_3268
+ };
+
+ static const struct imx355_reg mode_3264x2448_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -279,8 +278,6 @@ static const struct imx355_reg mode_3264
+ };
+
+ static const struct imx355_reg mode_3280x2464_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -289,8 +286,6 @@ static const struct imx355_reg mode_3280
+ };
+
+ static const struct imx355_reg mode_1940x1096_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -299,8 +294,6 @@ static const struct imx355_reg mode_1940
+ };
+
+ static const struct imx355_reg mode_1936x1096_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -309,8 +302,6 @@ static const struct imx355_reg mode_1936
+ };
+
+ static const struct imx355_reg mode_1924x1080_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -319,8 +310,6 @@ static const struct imx355_reg mode_1924
+ };
+
+ static const struct imx355_reg mode_1920x1080_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x00 },
+ { 0x0901, 0x11 },
+ { 0x0902, 0x00 },
+@@ -329,8 +318,6 @@ static const struct imx355_reg mode_1920
+ };
+
+ static const struct imx355_reg mode_1640x1232_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -339,8 +326,6 @@ static const struct imx355_reg mode_1640
+ };
+
+ static const struct imx355_reg mode_1640x922_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -349,8 +334,6 @@ static const struct imx355_reg mode_1640
+ };
+
+ static const struct imx355_reg mode_1300x736_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -359,8 +342,6 @@ static const struct imx355_reg mode_1300
+ };
+
+ static const struct imx355_reg mode_1296x736_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -369,8 +350,6 @@ static const struct imx355_reg mode_1296
+ };
+
+ static const struct imx355_reg mode_1284x720_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -379,8 +358,6 @@ static const struct imx355_reg mode_1284
+ };
+
+ static const struct imx355_reg mode_1280x720_regs[] = {
+- { 0x0342, 0x07 },
+- { 0x0343, 0x2c },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x22 },
+ { 0x0902, 0x00 },
+@@ -389,8 +366,6 @@ static const struct imx355_reg mode_1280
+ };
+
+ static const struct imx355_reg mode_820x616_regs[] = {
+- { 0x0342, 0x0e },
+- { 0x0343, 0x58 },
+ { 0x0900, 0x01 },
+ { 0x0901, 0x44 },
+ { 0x0902, 0x00 },
+@@ -1058,6 +1033,13 @@ static int imx355_start_streaming(struct
+ if (ret)
+ return ret;
+
++ /* set line length */
++ ret = imx355_write_reg(imx355, IMX355_REG_LLP,
++ imx355->hblank->val + imx355->cur_mode->width,
++ 2);
++ if (ret)
++ return ret;
++
+ /* Apply customized values from user */
+ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler);
+ if (ret)
--- /dev/null
+From 89221a7aab3b55cb758d3057041a97452b35ae93 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 16:13:53 +0100
+Subject: [PATCH] media: imx355: Set binning mode registers programmatically
+
+Compute the binning registers based on the difference between
+the mode width/height vs the crop width/height.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 61 ++++++++++++--------------------------
+ 1 file changed, 19 insertions(+), 42 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -72,6 +72,10 @@
+ #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3
+ #define IMX355_TEST_PATTERN_PN9 4
+
++#define IMX355_REG_BINNING_MODE 0x0900
++#define IMX355_REG_BINNING_TYPE 0x0901
++#define IMX355_REG_BINNING_WEIGHTING 0x0902
++
+ /* Flip Control */
+ #define IMX355_REG_ORIENTATION 0x0101
+
+@@ -262,113 +266,71 @@ static const struct imx355_reg_list imx3
+ };
+
+ static const struct imx355_reg mode_3268x2448_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_3264x2448_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_3280x2464_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1940x1096_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1936x1096_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1924x1080_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1920x1080_regs[] = {
+- { 0x0900, 0x00 },
+- { 0x0901, 0x11 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1640x1232_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1640x922_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1300x736_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1296x736_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1284x720_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_1280x720_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x22 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x00 },
+ { 0x0701, 0x10 },
+ };
+
+ static const struct imx355_reg mode_820x616_regs[] = {
+- { 0x0900, 0x01 },
+- { 0x0901, 0x44 },
+- { 0x0902, 0x00 },
+ { 0x0700, 0x02 },
+ { 0x0701, 0x78 },
+ };
+@@ -973,6 +935,7 @@ static int imx355_start_streaming(struct
+ {
+ const struct imx355_reg_list *reg_list;
+ const struct imx355_mode *mode;
++ u8 binning_mode;
+ int ret;
+
+ /* Global Setting */
+@@ -1018,6 +981,20 @@ static int imx355_start_streaming(struct
+ if (ret)
+ return ret;
+
++ binning_mode = ((mode->crop.width / mode->width) << 4) |
++ (mode->crop.height / mode->height);
++ ret = imx355_write_reg(imx355, IMX355_REG_BINNING_MODE, 1,
++ binning_mode == 0x11 ? 0x00 : 0x01);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_BINNING_TYPE, 1,
++ binning_mode);
++ if (ret)
++ return ret;
++ ret = imx355_write_reg(imx355, IMX355_REG_BINNING_WEIGHTING, 1, 0x00);
++ if (ret)
++ return ret;
++
+ /* Set PLL registers for the external clock frequency */
+ ret = imx355_write_reg(imx355, IMX355_REG_EXTCLK_FREQ, 2,
+ imx355->clk_params->extclk_freq);
--- /dev/null
+From 372407d326de01e1c0b66bc30bc3334a555b0366 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 29 Apr 2026 14:46:06 +0100
+Subject: [PATCH] media: imx355: Remove link_freq_index from each mode as
+ ununsed
+
+The link_freq_index value in imx355_mode is unused, so remove it.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 18 ------------------
+ 1 file changed, 18 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -81,7 +81,6 @@
+
+ /* default link frequency and external clock */
+ #define IMX355_LINK_FREQ_DEFAULT 360000000LL
+-#define IMX355_LINK_FREQ_INDEX 0
+
+ /* number of data lanes */
+ #define IMX355_DATA_LANES 4
+@@ -111,9 +110,6 @@ struct imx355_mode {
+ /* H-timing */
+ u32 llp;
+
+- /* index of link frequency */
+- u32 link_freq_index;
+-
+ /* Default register values */
+ struct imx355_reg_list reg_list;
+ };
+@@ -365,7 +361,6 @@ static const struct imx355_mode supporte
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs),
+ .regs = mode_3280x2464_regs,
+@@ -383,7 +378,6 @@ static const struct imx355_mode supporte
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs),
+ .regs = mode_3268x2448_regs,
+@@ -401,7 +395,6 @@ static const struct imx355_mode supporte
+ .fll_def = 2615,
+ .fll_min = 2615,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs),
+ .regs = mode_3264x2448_regs,
+@@ -419,7 +412,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs),
+ .regs = mode_1940x1096_regs,
+@@ -437,7 +429,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs),
+ .regs = mode_1936x1096_regs,
+@@ -455,7 +446,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs),
+ .regs = mode_1924x1080_regs,
+@@ -473,7 +463,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs),
+ .regs = mode_1920x1080_regs,
+@@ -491,7 +480,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs),
+ .regs = mode_1640x1232_regs,
+@@ -509,7 +497,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1640x922_regs),
+ .regs = mode_1640x922_regs,
+@@ -527,7 +514,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1300x736_regs),
+ .regs = mode_1300x736_regs,
+@@ -545,7 +531,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1296x736_regs),
+ .regs = mode_1296x736_regs,
+@@ -563,7 +548,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1284x720_regs),
+ .regs = mode_1284x720_regs,
+@@ -581,7 +565,6 @@ static const struct imx355_mode supporte
+ .fll_def = 1306,
+ .fll_min = 1306,
+ .llp = 1836,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1280x720_regs),
+ .regs = mode_1280x720_regs,
+@@ -599,7 +582,6 @@ static const struct imx355_mode supporte
+ .fll_def = 652,
+ .fll_min = 652,
+ .llp = 3672,
+- .link_freq_index = IMX355_LINK_FREQ_INDEX,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_820x616_regs),
+ .regs = mode_820x616_regs,
--- /dev/null
+From 288ed1383e559ba8cf208a521dd798a9c158fa2f Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 29 Apr 2026 14:49:19 +0100
+Subject: [PATCH] media: imx355: pixel_rate never changes, so don't recompute
+
+The pixel rate is always the same, so there is no need to try and
+recompute it in imx355_set_pad_format, and then no need to have the
+pointer to it stored.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 10 ++--------
+ 1 file changed, 2 insertions(+), 8 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -151,7 +151,6 @@ struct imx355 {
+ struct v4l2_ctrl_handler ctrl_handler;
+ /* V4L2 Controls */
+ struct v4l2_ctrl *link_freq;
+- struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *hblank;
+ struct v4l2_ctrl *exposure;
+@@ -866,7 +865,6 @@ imx355_set_pad_format(struct v4l2_subdev
+ s32 vblank_def;
+ s32 vblank_min;
+ s64 h_blank;
+- u64 pixel_rate;
+ u32 height;
+
+ mutex_lock(&imx355->mutex);
+@@ -887,9 +885,6 @@ imx355_set_pad_format(struct v4l2_subdev
+ *framefmt = fmt->format;
+ } else {
+ imx355->cur_mode = mode;
+- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4;
+- do_div(pixel_rate, 10);
+- __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate);
+ /* Update limits and set FPS to default */
+ height = imx355->cur_mode->height;
+ vblank_def = imx355->cur_mode->fll_def - height;
+@@ -1180,9 +1175,8 @@ static int imx355_init_controls(struct i
+ pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4;
+ do_div(pixel_rate, 10);
+ /* By default, PIXEL_RATE is read only */
+- imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
+- V4L2_CID_PIXEL_RATE, pixel_rate,
+- pixel_rate, 1, pixel_rate);
++ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE,
++ pixel_rate, pixel_rate, 1, pixel_rate);
+
+ /* Initialize vblank/hblank/exposure parameters based on current mode */
+ mode = imx355->cur_mode;
--- /dev/null
+From 0aae4c0b2d05cf52bfd0c30ea2df15b58b67870c Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 29 Apr 2026 15:23:04 +0100
+Subject: [PATCH] media: imx355: Remove redundant fll_min, and implement fixed
+ offset
+
+fll_min (Frame Length Lines) is set to the same value as fll_def
+for all modes, which makes it redundant.
+
+The actual value is also erroneous as the sensor works in all
+the defined modes with FLL set at the mode height + 20 lines,
+so set the vblank control minimum to 20 rather than varying it.
+This also improves the maximum frame rate achievable.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 26 ++++----------------------
+ 1 file changed, 4 insertions(+), 22 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -32,6 +32,7 @@
+ /* V_TIMING internal */
+ #define IMX355_REG_FLL 0x0340
+ #define IMX355_FLL_MAX 0xffff
++#define IMX355_VBLANK_MIN 20
+
+ #define IMX355_REG_LLP 0x0342
+ #define IMX355_LLP_MAX 0xffff
+@@ -105,7 +106,6 @@ struct imx355_mode {
+
+ /* V-timing */
+ u32 fll_def;
+- u32 fll_min;
+
+ /* H-timing */
+ u32 llp;
+@@ -358,7 +358,6 @@ static const struct imx355_mode supporte
+ .top = 0,
+ },
+ .fll_def = 2615,
+- .fll_min = 2615,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs),
+@@ -375,7 +374,6 @@ static const struct imx355_mode supporte
+ .top = 8,
+ },
+ .fll_def = 2615,
+- .fll_min = 2615,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs),
+@@ -392,7 +390,6 @@ static const struct imx355_mode supporte
+ .top = 8,
+ },
+ .fll_def = 2615,
+- .fll_min = 2615,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs),
+@@ -409,7 +406,6 @@ static const struct imx355_mode supporte
+ .top = 684,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs),
+@@ -426,7 +422,6 @@ static const struct imx355_mode supporte
+ .top = 684,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs),
+@@ -443,7 +438,6 @@ static const struct imx355_mode supporte
+ .top = 692,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs),
+@@ -460,7 +454,6 @@ static const struct imx355_mode supporte
+ .top = 692,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs),
+@@ -477,7 +470,6 @@ static const struct imx355_mode supporte
+ .top = 0,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs),
+@@ -494,7 +486,6 @@ static const struct imx355_mode supporte
+ .top = 304,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1640x922_regs),
+@@ -511,7 +502,6 @@ static const struct imx355_mode supporte
+ .top = 496,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1300x736_regs),
+@@ -528,7 +518,6 @@ static const struct imx355_mode supporte
+ .top = 496,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1296x736_regs),
+@@ -545,7 +534,6 @@ static const struct imx355_mode supporte
+ .top = 512,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1284x720_regs),
+@@ -562,7 +550,6 @@ static const struct imx355_mode supporte
+ .top = 512,
+ },
+ .fll_def = 1306,
+- .fll_min = 1306,
+ .llp = 1836,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_1280x720_regs),
+@@ -579,7 +566,6 @@ static const struct imx355_mode supporte
+ .top = 0,
+ },
+ .fll_def = 652,
+- .fll_min = 652,
+ .llp = 3672,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_820x616_regs),
+@@ -863,7 +849,6 @@ imx355_set_pad_format(struct v4l2_subdev
+ const struct imx355_mode *mode;
+ struct v4l2_mbus_framefmt *framefmt;
+ s32 vblank_def;
+- s32 vblank_min;
+ s64 h_blank;
+ u32 height;
+
+@@ -888,10 +873,9 @@ imx355_set_pad_format(struct v4l2_subdev
+ /* Update limits and set FPS to default */
+ height = imx355->cur_mode->height;
+ vblank_def = imx355->cur_mode->fll_def - height;
+- vblank_min = imx355->cur_mode->fll_min - height;
+ height = IMX355_FLL_MAX - height;
+- __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1,
+- vblank_def);
++ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN,
++ height, 1, vblank_def);
+ __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def);
+ h_blank = mode->llp - imx355->cur_mode->width;
+ /*
+@@ -1151,7 +1135,6 @@ static int imx355_init_controls(struct i
+ struct v4l2_ctrl_handler *ctrl_hdlr;
+ s64 exposure_max;
+ s64 vblank_def;
+- s64 vblank_min;
+ s64 hblank;
+ u64 pixel_rate;
+ const struct imx355_mode *mode;
+@@ -1181,9 +1164,8 @@ static int imx355_init_controls(struct i
+ /* Initialize vblank/hblank/exposure parameters based on current mode */
+ mode = imx355->cur_mode;
+ vblank_def = mode->fll_def - mode->height;
+- vblank_min = mode->fll_min - mode->height;
+ imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
+- V4L2_CID_VBLANK, vblank_min,
++ V4L2_CID_VBLANK, IMX355_VBLANK_MIN,
+ IMX355_FLL_MAX - mode->height,
+ 1, vblank_def);
+
--- /dev/null
+From b1ddfd26254c4872c0d1d65e14255210346fc229 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 19:05:29 +0100
+Subject: [PATCH] media: imx355: Add support for get_selection
+
+Provide all the cropping information via get_selection.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 53 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 53 insertions(+)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -86,6 +86,11 @@
+ /* number of data lanes */
+ #define IMX355_DATA_LANES 4
+
++#define IMX355_PIXEL_ARRAY_TOP 0
++#define IMX355_PIXEL_ARRAY_LEFT 0
++#define IMX355_PIXEL_ARRAY_WIDTH 3280
++#define IMX355_PIXEL_ARRAY_HEIGHT 2464
++
+ struct imx355_reg {
+ u16 address;
+ u8 val;
+@@ -676,6 +681,7 @@ static int imx355_open(struct v4l2_subde
+ struct imx355 *imx355 = to_imx355(sd);
+ struct v4l2_mbus_framefmt *try_fmt =
+ v4l2_subdev_state_get_format(fh->state, 0);
++ struct v4l2_rect *crop = v4l2_subdev_state_get_crop(fh->state, 0);
+
+ mutex_lock(&imx355->mutex);
+
+@@ -685,6 +691,8 @@ static int imx355_open(struct v4l2_subde
+ try_fmt->code = imx355_get_format_code(imx355);
+ try_fmt->field = V4L2_FIELD_NONE;
+
++ *crop = imx355->cur_mode->crop;
++
+ mutex_unlock(&imx355->mutex);
+
+ return 0;
+@@ -891,6 +899,50 @@ imx355_set_pad_format(struct v4l2_subdev
+ return 0;
+ }
+
++static void
++__imx355_get_pad_crop(struct imx355 *imx355,
++ struct v4l2_subdev_state *sd_state, unsigned int pad,
++ enum v4l2_subdev_format_whence which, struct v4l2_rect *r)
++{
++ switch (which) {
++ case V4L2_SUBDEV_FORMAT_TRY:
++ *r = *v4l2_subdev_state_get_crop(sd_state, pad);
++ break;
++ case V4L2_SUBDEV_FORMAT_ACTIVE:
++ *r = imx355->cur_mode->crop;
++ break;
++ }
++}
++
++static int imx355_get_selection(struct v4l2_subdev *sd,
++ struct v4l2_subdev_state *sd_state,
++ struct v4l2_subdev_selection *sel)
++{
++ switch (sel->target) {
++ case V4L2_SEL_TGT_CROP: {
++ struct imx355 *imx355 = to_imx355(sd);
++
++ mutex_lock(&imx355->mutex);
++ __imx355_get_pad_crop(imx355, sd_state, sel->pad, sel->which,
++ &sel->r);
++ mutex_unlock(&imx355->mutex);
++
++ return 0;
++ }
++ case V4L2_SEL_TGT_CROP_DEFAULT:
++ case V4L2_SEL_TGT_CROP_BOUNDS:
++ case V4L2_SEL_TGT_NATIVE_SIZE:
++ sel->r.top = IMX355_PIXEL_ARRAY_TOP;
++ sel->r.left = IMX355_PIXEL_ARRAY_LEFT;
++ sel->r.width = IMX355_PIXEL_ARRAY_WIDTH;
++ sel->r.height = IMX355_PIXEL_ARRAY_HEIGHT;
++
++ return 0;
++ }
++
++ return -EINVAL;
++}
++
+ /* Start streaming */
+ static int imx355_start_streaming(struct imx355 *imx355)
+ {
+@@ -1066,6 +1118,7 @@ static const struct v4l2_subdev_pad_ops
+ .get_fmt = imx355_get_pad_format,
+ .set_fmt = imx355_set_pad_format,
+ .enum_frame_size = imx355_enum_frame_size,
++ .get_selection = imx355_get_selection,
+ };
+
+ static const struct v4l2_subdev_ops imx355_subdev_ops = {
--- /dev/null
+From 7d8ba6e7ed032a9c2b7e7e2b0c812b02a439a2db Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 19:06:56 +0100
+Subject: [PATCH] media: imx355: Use pm_runtime autosuspend_delay
+
+Avoid powering the sensor up and down unnecessarily by using
+pm_runtime's autosuspend_delay feature.
+
+Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -1067,7 +1067,7 @@ static int imx355_set_stream(struct v4l2
+ goto err_rpm_put;
+ } else {
+ imx355_stop_streaming(imx355);
+- pm_runtime_put(imx355->dev);
++ pm_runtime_put_autosuspend(imx355->dev);
+ }
+
+ /* vflip and hflip cannot change during streaming */
+@@ -1079,7 +1079,7 @@ static int imx355_set_stream(struct v4l2
+ return ret;
+
+ err_rpm_put:
+- pm_runtime_put(imx355->dev);
++ pm_runtime_put_autosuspend(imx355->dev);
+ err_unlock:
+ mutex_unlock(&imx355->mutex);
+
+@@ -1430,6 +1430,8 @@ static int imx355_probe(struct i2c_clien
+ pm_runtime_set_active(imx355->dev);
+ pm_runtime_enable(imx355->dev);
+ pm_runtime_idle(imx355->dev);
++ pm_runtime_set_autosuspend_delay(imx355->dev, 1000);
++ pm_runtime_use_autosuspend(imx355->dev);
+
+ ret = v4l2_async_register_subdev_sensor(&imx355->sd);
+ if (ret < 0)
--- /dev/null
+From a7a8620fe287ad5f9c9a8c1ad6c20657191e4f22 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 29 Apr 2026 12:26:21 +0100
+Subject: [PATCH] media: imx355: Convert to new CCI register access helpers
+
+Use the new comon CCI register access helpers to replace the private
+register access helpers in the imx355 driver.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/Kconfig | 1 +
+ drivers/media/i2c/imx355.c | 500 +++++++++++++++----------------------
+ 2 files changed, 196 insertions(+), 305 deletions(-)
+
+--- a/drivers/media/i2c/Kconfig
++++ b/drivers/media/i2c/Kconfig
+@@ -271,6 +271,7 @@ config VIDEO_IMX335
+
+ config VIDEO_IMX355
+ tristate "Sony IMX355 sensor support"
++ select V4L2_CCI_I2C
+ help
+ This is a Video4Linux2 sensor driver for the Sony
+ IMX355 camera.
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -9,76 +9,78 @@
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/pm_runtime.h>
++#include <linux/regmap.h>
+ #include <linux/regulator/consumer.h>
+ #include <linux/unaligned.h>
+
++#include <media/v4l2-cci.h>
+ #include <media/v4l2-ctrls.h>
+ #include <media/v4l2-device.h>
+ #include <media/v4l2-event.h>
+ #include <media/v4l2-fwnode.h>
+
+-#define IMX355_REG_MODE_SELECT 0x0100
++#define IMX355_REG_MODE_SELECT CCI_REG8(0x0100)
+ #define IMX355_MODE_STANDBY 0x00
+ #define IMX355_MODE_STREAMING 0x01
+
+ /* Chip ID */
+-#define IMX355_REG_CHIP_ID 0x0016
++#define IMX355_REG_CHIP_ID CCI_REG16(0x0016)
+ #define IMX355_CHIP_ID 0x0355
+
+ /* PLL registers that depend on the external clock frequency */
+-#define IMX355_REG_EXTCLK_FREQ 0x0136
+-#define IMX355_REG_PLL_OP_MUL 0x030e
++#define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136)
++#define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e)
+
+ /* V_TIMING internal */
+-#define IMX355_REG_FLL 0x0340
++#define IMX355_REG_FLL CCI_REG16(0x0340)
+ #define IMX355_FLL_MAX 0xffff
+ #define IMX355_VBLANK_MIN 20
+
+-#define IMX355_REG_LLP 0x0342
++#define IMX355_REG_LLP CCI_REG16(0x0342)
+ #define IMX355_LLP_MAX 0xffff
+
+-#define IMX355_REG_X_ADD_START 0x0344
+-#define IMX355_REG_Y_ADD_START 0x0346
+-#define IMX355_REG_X_ADD_END 0x0348
+-#define IMX355_REG_Y_ADD_END 0x034a
+-#define IMX355_REG_X_OUT_SIZE 0x034c
+-#define IMX355_REG_Y_OUT_SIZE 0x034e
++#define IMX355_REG_X_ADD_START CCI_REG16(0x0344)
++#define IMX355_REG_Y_ADD_START CCI_REG16(0x0346)
++#define IMX355_REG_X_ADD_END CCI_REG16(0x0348)
++#define IMX355_REG_Y_ADD_END CCI_REG16(0x034a)
++#define IMX355_REG_X_OUT_SIZE CCI_REG16(0x034c)
++#define IMX355_REG_Y_OUT_SIZE CCI_REG16(0x034e)
+
+ /* Exposure control */
+-#define IMX355_REG_EXPOSURE 0x0202
++#define IMX355_REG_EXPOSURE CCI_REG16(0x0202)
+ #define IMX355_EXPOSURE_MIN 1
+ #define IMX355_EXPOSURE_STEP 1
+ #define IMX355_EXPOSURE_DEFAULT 0x0282
+
+ /* Analog gain control */
+-#define IMX355_REG_ANALOG_GAIN 0x0204
++#define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204)
+ #define IMX355_ANA_GAIN_MIN 0
+ #define IMX355_ANA_GAIN_MAX 960
+ #define IMX355_ANA_GAIN_STEP 1
+ #define IMX355_ANA_GAIN_DEFAULT 0
+
+ /* Digital gain control */
+-#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070
+-#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e
++#define IMX355_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3070)
++#define IMX355_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e)
+ #define IMX355_DGTL_GAIN_MIN 256
+ #define IMX355_DGTL_GAIN_MAX 4095
+ #define IMX355_DGTL_GAIN_STEP 1
+ #define IMX355_DGTL_GAIN_DEFAULT 256
+
+ /* Test Pattern Control */
+-#define IMX355_REG_TEST_PATTERN 0x0600
++#define IMX355_REG_TEST_PATTERN CCI_REG8(0x0600)
+ #define IMX355_TEST_PATTERN_DISABLED 0
+ #define IMX355_TEST_PATTERN_SOLID_COLOR 1
+ #define IMX355_TEST_PATTERN_COLOR_BARS 2
+ #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3
+ #define IMX355_TEST_PATTERN_PN9 4
+
+-#define IMX355_REG_BINNING_MODE 0x0900
+-#define IMX355_REG_BINNING_TYPE 0x0901
+-#define IMX355_REG_BINNING_WEIGHTING 0x0902
++#define IMX355_REG_BINNING_MODE CCI_REG8(0x0900)
++#define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901)
++#define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902)
+
+ /* Flip Control */
+-#define IMX355_REG_ORIENTATION 0x0101
++#define IMX355_REG_ORIENTATION CCI_REG8(0x0101)
+
+ /* default link frequency and external clock */
+ #define IMX355_LINK_FREQ_DEFAULT 360000000LL
+@@ -91,14 +93,9 @@
+ #define IMX355_PIXEL_ARRAY_WIDTH 3280
+ #define IMX355_PIXEL_ARRAY_HEIGHT 2464
+
+-struct imx355_reg {
+- u16 address;
+- u8 val;
+-};
+-
+ struct imx355_reg_list {
+ u32 num_of_regs;
+- const struct imx355_reg *regs;
++ const struct cci_reg_sequence *regs;
+ };
+
+ /* Mode : resolution and related config&values */
+@@ -149,6 +146,7 @@ struct imx355_hwcfg {
+ struct imx355 {
+ struct device *dev;
+ struct clk *clk;
++ struct regmap *regmap;
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+@@ -185,154 +183,149 @@ static const struct regulator_bulk_data
+ { .supply = "dovdd" },
+ };
+
+-static const struct imx355_reg imx355_global_regs[] = {
+- { 0x304e, 0x03 },
+- { 0x4348, 0x16 },
+- { 0x4350, 0x19 },
+- { 0x4408, 0x0a },
+- { 0x440c, 0x0b },
+- { 0x4411, 0x5f },
+- { 0x4412, 0x2c },
+- { 0x4623, 0x00 },
+- { 0x462c, 0x0f },
+- { 0x462d, 0x00 },
+- { 0x462e, 0x00 },
+- { 0x4684, 0x54 },
+- { 0x480a, 0x07 },
+- { 0x4908, 0x07 },
+- { 0x4909, 0x07 },
+- { 0x490d, 0x0a },
+- { 0x491e, 0x0f },
+- { 0x4921, 0x06 },
+- { 0x4923, 0x28 },
+- { 0x4924, 0x28 },
+- { 0x4925, 0x29 },
+- { 0x4926, 0x29 },
+- { 0x4927, 0x1f },
+- { 0x4928, 0x20 },
+- { 0x4929, 0x20 },
+- { 0x492a, 0x20 },
+- { 0x492c, 0x05 },
+- { 0x492d, 0x06 },
+- { 0x492e, 0x06 },
+- { 0x492f, 0x06 },
+- { 0x4930, 0x03 },
+- { 0x4931, 0x04 },
+- { 0x4932, 0x04 },
+- { 0x4933, 0x05 },
+- { 0x595e, 0x01 },
+- { 0x5963, 0x01 },
+- { 0x3030, 0x01 },
+- { 0x3031, 0x01 },
+- { 0x3045, 0x01 },
+- { 0x4010, 0x00 },
+- { 0x4011, 0x00 },
+- { 0x4012, 0x00 },
+- { 0x4013, 0x01 },
+- { 0x68a8, 0xfe },
+- { 0x68a9, 0xff },
+- { 0x6888, 0x00 },
+- { 0x6889, 0x00 },
+- { 0x68b0, 0x00 },
+- { 0x3058, 0x00 },
+- { 0x305a, 0x00 },
+- { 0x0112, 0x0a },
+- { 0x0113, 0x0a },
+- { 0x0114, 0x03 },
+- { 0x0301, 0x05 },
+- { 0x0303, 0x01 },
+- { 0x0305, 0x02 },
+- { 0x0306, 0x00 },
+- { 0x0307, 0x78 },
+- { 0x030d, 0x02 },
+- { 0x0310, 0x00 },
+- { 0x0220, 0x00 },
+- { 0x0222, 0x01 },
+- { 0x0820, 0x0b },
+- { 0x0821, 0x40 },
+- { 0x3088, 0x04 },
+- { 0x6813, 0x02 },
+- { 0x6835, 0x07 },
+- { 0x6836, 0x01 },
+- { 0x6837, 0x04 },
+- { 0x684d, 0x07 },
+- { 0x684e, 0x01 },
+- { 0x684f, 0x04 },
+-};
+-
+-static const struct imx355_reg_list imx355_global_setting = {
+- .num_of_regs = ARRAY_SIZE(imx355_global_regs),
+- .regs = imx355_global_regs,
+-};
+-
+-static const struct imx355_reg mode_3268x2448_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_3264x2448_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_3280x2464_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1940x1096_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1936x1096_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1924x1080_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1920x1080_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1640x1232_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1640x922_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1300x736_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1296x736_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1284x720_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_1280x720_regs[] = {
+- { 0x0700, 0x00 },
+- { 0x0701, 0x10 },
+-};
+-
+-static const struct imx355_reg mode_820x616_regs[] = {
+- { 0x0700, 0x02 },
+- { 0x0701, 0x78 },
++static const struct cci_reg_sequence imx355_global_regs[] = {
++ { CCI_REG8(0x304e), 0x03 },
++ { CCI_REG8(0x4348), 0x16 },
++ { CCI_REG8(0x4350), 0x19 },
++ { CCI_REG8(0x4408), 0x0a },
++ { CCI_REG8(0x440c), 0x0b },
++ { CCI_REG8(0x4411), 0x5f },
++ { CCI_REG8(0x4412), 0x2c },
++ { CCI_REG8(0x4623), 0x00 },
++ { CCI_REG8(0x462c), 0x0f },
++ { CCI_REG8(0x462d), 0x00 },
++ { CCI_REG8(0x462e), 0x00 },
++ { CCI_REG8(0x4684), 0x54 },
++ { CCI_REG8(0x480a), 0x07 },
++ { CCI_REG8(0x4908), 0x07 },
++ { CCI_REG8(0x4909), 0x07 },
++ { CCI_REG8(0x490d), 0x0a },
++ { CCI_REG8(0x491e), 0x0f },
++ { CCI_REG8(0x4921), 0x06 },
++ { CCI_REG8(0x4923), 0x28 },
++ { CCI_REG8(0x4924), 0x28 },
++ { CCI_REG8(0x4925), 0x29 },
++ { CCI_REG8(0x4926), 0x29 },
++ { CCI_REG8(0x4927), 0x1f },
++ { CCI_REG8(0x4928), 0x20 },
++ { CCI_REG8(0x4929), 0x20 },
++ { CCI_REG8(0x492a), 0x20 },
++ { CCI_REG8(0x492c), 0x05 },
++ { CCI_REG8(0x492d), 0x06 },
++ { CCI_REG8(0x492e), 0x06 },
++ { CCI_REG8(0x492f), 0x06 },
++ { CCI_REG8(0x4930), 0x03 },
++ { CCI_REG8(0x4931), 0x04 },
++ { CCI_REG8(0x4932), 0x04 },
++ { CCI_REG8(0x4933), 0x05 },
++ { CCI_REG8(0x595e), 0x01 },
++ { CCI_REG8(0x5963), 0x01 },
++ { CCI_REG8(0x3030), 0x01 },
++ { CCI_REG8(0x3031), 0x01 },
++ { CCI_REG8(0x3045), 0x01 },
++ { CCI_REG8(0x4010), 0x00 },
++ { CCI_REG8(0x4011), 0x00 },
++ { CCI_REG8(0x4012), 0x00 },
++ { CCI_REG8(0x4013), 0x01 },
++ { CCI_REG8(0x68a8), 0xfe },
++ { CCI_REG8(0x68a9), 0xff },
++ { CCI_REG8(0x6888), 0x00 },
++ { CCI_REG8(0x6889), 0x00 },
++ { CCI_REG8(0x68b0), 0x00 },
++ { CCI_REG8(0x3058), 0x00 },
++ { CCI_REG8(0x305a), 0x00 },
++ { CCI_REG8(0x0112), 0x0a },
++ { CCI_REG8(0x0113), 0x0a },
++ { CCI_REG8(0x0114), 0x03 },
++ { CCI_REG8(0x0301), 0x05 },
++ { CCI_REG8(0x0303), 0x01 },
++ { CCI_REG8(0x0305), 0x00 },
++ { CCI_REG8(0x0306), 0x78 },
++ { CCI_REG8(0x0307), 0x02 },
++ { CCI_REG8(0x030d), 0x02 },
++ { CCI_REG8(0x0310), 0x00 },
++ { CCI_REG8(0x0220), 0x00 },
++ { CCI_REG8(0x0222), 0x01 },
++ { CCI_REG8(0x0820), 0x0b },
++ { CCI_REG8(0x0821), 0x40 },
++ { CCI_REG8(0x3088), 0x04 },
++ { CCI_REG8(0x6813), 0x02 },
++ { CCI_REG8(0x6835), 0x07 },
++ { CCI_REG8(0x6836), 0x01 },
++ { CCI_REG8(0x6837), 0x04 },
++ { CCI_REG8(0x684d), 0x07 },
++ { CCI_REG8(0x684e), 0x01 },
++ { CCI_REG8(0x684f), 0x04 },
++};
++
++static const struct cci_reg_sequence mode_3268x2448_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_3264x2448_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_3280x2464_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1940x1096_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1936x1096_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1924x1080_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1920x1080_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1640x1232_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1640x922_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1300x736_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1296x736_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1284x720_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_1280x720_regs[] = {
++ { CCI_REG8(0x0700), 0x00 },
++ { CCI_REG8(0x0701), 0x10 },
++};
++
++static const struct cci_reg_sequence mode_820x616_regs[] = {
++ { CCI_REG8(0x0700), 0x02 },
++ { CCI_REG8(0x0701), 0x78 },
+ };
+
+ static const char * const imx355_test_pattern_menu[] = {
+@@ -603,78 +596,6 @@ static u32 imx355_get_format_code(struct
+ return code;
+ }
+
+-/* Read registers up to 4 at a time */
+-static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val)
+-{
+- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd);
+- struct i2c_msg msgs[2];
+- u8 addr_buf[2];
+- u8 data_buf[4] = { 0 };
+- int ret;
+-
+- if (len > 4)
+- return -EINVAL;
+-
+- put_unaligned_be16(reg, addr_buf);
+- /* Write register address */
+- msgs[0].addr = client->addr;
+- msgs[0].flags = 0;
+- msgs[0].len = ARRAY_SIZE(addr_buf);
+- msgs[0].buf = addr_buf;
+-
+- /* Read data from register */
+- msgs[1].addr = client->addr;
+- msgs[1].flags = I2C_M_RD;
+- msgs[1].len = len;
+- msgs[1].buf = &data_buf[4 - len];
+-
+- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+- if (ret != ARRAY_SIZE(msgs))
+- return -EIO;
+-
+- *val = get_unaligned_be32(data_buf);
+-
+- return 0;
+-}
+-
+-/* Write registers up to 4 at a time */
+-static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val)
+-{
+- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd);
+- u8 buf[6];
+-
+- if (len > 4)
+- return -EINVAL;
+-
+- put_unaligned_be16(reg, buf);
+- put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
+- if (i2c_master_send(client, buf, len + 2) != len + 2)
+- return -EIO;
+-
+- return 0;
+-}
+-
+-/* Write a list of registers */
+-static int imx355_write_regs(struct imx355 *imx355,
+- const struct imx355_reg *regs, u32 len)
+-{
+- int ret;
+- u32 i;
+-
+- for (i = 0; i < len; i++) {
+- ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val);
+- if (ret) {
+- dev_err_ratelimited(imx355->dev,
+- "write reg 0x%4.4x return err %d",
+- regs[i].address, ret);
+-
+- return ret;
+- }
+- }
+-
+- return 0;
+-}
+-
+ /* Open sub-device */
+ static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+ {
+@@ -726,31 +647,31 @@ static int imx355_set_ctrl(struct v4l2_c
+ switch (ctrl->id) {
+ case V4L2_CID_ANALOGUE_GAIN:
+ /* Analog gain = 1024/(1024 - ctrl->val) times */
+- ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2,
+- ctrl->val);
++ ret = cci_write(imx355->regmap, IMX355_REG_ANALOG_GAIN,
++ ctrl->val, NULL);
+ break;
+ case V4L2_CID_DIGITAL_GAIN:
+- ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2,
+- ctrl->val);
++ ret = cci_write(imx355->regmap, IMX355_REG_DIG_GAIN_GLOBAL,
++ ctrl->val, NULL);
+ break;
+ case V4L2_CID_EXPOSURE:
+- ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2,
+- ctrl->val);
++ ret = cci_write(imx355->regmap, IMX355_REG_EXPOSURE,
++ ctrl->val, NULL);
+ break;
+ case V4L2_CID_VBLANK:
+ /* Update FLL that meets expected vertical blanking */
+- ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2,
+- imx355->cur_mode->height + ctrl->val);
++ ret = cci_write(imx355->regmap, IMX355_REG_FLL,
++ imx355->cur_mode->height + ctrl->val, NULL);
+ break;
+ case V4L2_CID_TEST_PATTERN:
+- ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN,
+- 2, ctrl->val);
++ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN,
++ ctrl->val, NULL);
+ break;
+ case V4L2_CID_HFLIP:
+ case V4L2_CID_VFLIP:
+- ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1,
+- imx355->hflip->val |
+- imx355->vflip->val << 1);
++ ret = cci_write(imx355->regmap, IMX355_REG_ORIENTATION,
++ imx355->hflip->val | imx355->vflip->val << 1,
++ NULL);
+ break;
+ default:
+ ret = -EINVAL;
+@@ -949,101 +870,64 @@ static int imx355_start_streaming(struct
+ const struct imx355_reg_list *reg_list;
+ const struct imx355_mode *mode;
+ u8 binning_mode;
+- int ret;
++ int ret = 0;
+
+ /* Global Setting */
+- reg_list = &imx355_global_setting;
+- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs);
+- if (ret) {
+- dev_err(imx355->dev, "failed to set global settings");
+- return ret;
+- }
++ cci_multi_reg_write(imx355->regmap, imx355_global_regs,
++ ARRAY_SIZE(imx355_global_regs), &ret);
+
+ /* Apply default values of current mode */
+ mode = imx355->cur_mode;
+ reg_list = &mode->reg_list;
+- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs);
+- if (ret) {
+- dev_err(imx355->dev, "failed to set mode");
+- return ret;
+- }
++ cci_multi_reg_write(imx355->regmap, reg_list->regs,
++ reg_list->num_of_regs, &ret);
+
+ /* Set readout crop and size registers */
+- ret = imx355_write_reg(imx355, IMX355_REG_X_ADD_START, 2,
+- mode->crop.left);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_Y_ADD_START, 2,
+- mode->crop.top);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_X_ADD_END, 2,
+- mode->crop.width + mode->crop.left - 1);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_Y_ADD_END, 2,
+- mode->crop.height + mode->crop.top - 1);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_X_OUT_SIZE, 2,
+- mode->width);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_Y_OUT_SIZE, 2,
+- mode->height);
+- if (ret)
+- return ret;
++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, mode->crop.left,
++ &ret);
++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, mode->crop.top, &ret);
++ cci_write(imx355->regmap, IMX355_REG_X_ADD_END,
++ mode->crop.width + mode->crop.left - 1, &ret);
++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END,
++ mode->crop.height + mode->crop.top - 1, &ret);
++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, mode->width, &ret);
++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, mode->height, &ret);
+
+ binning_mode = ((mode->crop.width / mode->width) << 4) |
+ (mode->crop.height / mode->height);
+- ret = imx355_write_reg(imx355, IMX355_REG_BINNING_MODE, 1,
+- binning_mode == 0x11 ? 0x00 : 0x01);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_BINNING_TYPE, 1,
+- binning_mode);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_BINNING_WEIGHTING, 1, 0x00);
+- if (ret)
+- return ret;
++ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE,
++ binning_mode == 0x11 ? 0x00 : 0x01, &ret);
++ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret);
++ cci_write(imx355->regmap, IMX355_REG_BINNING_WEIGHTING, 0x00, &ret);
+
+ /* Set PLL registers for the external clock frequency */
+- ret = imx355_write_reg(imx355, IMX355_REG_EXTCLK_FREQ, 2,
+- imx355->clk_params->extclk_freq);
+- if (ret)
+- return ret;
+- ret = imx355_write_reg(imx355, IMX355_REG_PLL_OP_MUL, 2,
+- imx355->clk_params->pll_op_mpy);
+- if (ret)
+- return ret;
++ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ,
++ imx355->clk_params->extclk_freq, &ret);
++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL,
++ imx355->clk_params->pll_op_mpy, &ret);
+
+ /* set digital gain control to all color mode */
+- ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1);
+- if (ret)
+- return ret;
++ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret);
+
+ /* set line length */
+- ret = imx355_write_reg(imx355, IMX355_REG_LLP,
+- imx355->hblank->val + imx355->cur_mode->width,
+- 2);
+- if (ret)
+- return ret;
++ cci_write(imx355->regmap, IMX355_REG_LLP,
++ imx355->hblank->val + imx355->cur_mode->width, &ret);
+
+ /* Apply customized values from user */
+- ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler);
+- if (ret)
+- return ret;
++ if (!ret)
++ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler);
+
+- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT,
+- 1, IMX355_MODE_STREAMING);
++ cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, IMX355_MODE_STREAMING,
++ &ret);
++
++ return ret;
+ }
+
+ /* Stop streaming */
+ static int imx355_stop_streaming(struct imx355 *imx355)
+ {
+- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT,
+- 1, IMX355_MODE_STANDBY);
++ return cci_write(imx355->regmap, IMX355_REG_MODE_SELECT,
++ IMX355_MODE_STANDBY, NULL);
+ }
+
+ static int imx355_set_stream(struct v4l2_subdev *sd, int enable)
+@@ -1090,14 +974,14 @@ err_unlock:
+ static int imx355_identify_module(struct imx355 *imx355)
+ {
+ int ret;
+- u32 val;
++ u64 val;
+
+- ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val);
++ ret = cci_read(imx355->regmap, IMX355_REG_CHIP_ID, &val, NULL);
+ if (ret)
+ return ret;
+
+ if (val != IMX355_CHIP_ID) {
+- dev_err(imx355->dev, "chip id mismatch: %x!=%x",
++ dev_err(imx355->dev, "chip id mismatch: %x!=%llx",
+ IMX355_CHIP_ID, val);
+ return -EIO;
+ }
+@@ -1344,6 +1228,12 @@ static int imx355_probe(struct i2c_clien
+
+ mutex_init(&imx355->mutex);
+
++ imx355->regmap = devm_cci_regmap_init_i2c(client, 16);
++ if (IS_ERR(imx355->regmap)) {
++ dev_err(imx355->dev, "Unable to initialize I2C\n");
++ return -ENODEV;
++ }
++
+ imx355->clk = devm_v4l2_sensor_clk_get(imx355->dev, NULL);
+ if (IS_ERR(imx355->clk))
+ return dev_err_probe(imx355->dev, PTR_ERR(imx355->clk),
--- /dev/null
+From bae633e431b226499d495c3655b91bcd6d5fcb8c Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 11 May 2026 19:01:59 +0100
+Subject: [PATCH] media: imx355: Set the colorspace in the format
+
+The colorspace fields were left set to 0 when they should be
+set appropriately for a raw image sensor. Add those values.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -737,6 +737,10 @@ static void imx355_update_pad_format(str
+ fmt->format.height = mode->height;
+ fmt->format.code = imx355_get_format_code(imx355);
+ fmt->format.field = V4L2_FIELD_NONE;
++ fmt->format.colorspace = V4L2_COLORSPACE_RAW;
++ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601;
++ fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE;
++ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE;
+ }
+
+ static int imx355_do_get_pad_format(struct imx355 *imx355,
--- /dev/null
+From 2b7901d08bc50d37acdef1c0101b62c7bb351bf9 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 12 May 2026 17:00:22 +0100
+Subject: [PATCH] media: imx355: Define the exposure offset, and use that
+ define
+
+The difference between frame length and max exposure time in
+lines is 10. That had been used as a bare value in calculations.
+
+Move it to a define, and use that define.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -51,6 +51,7 @@
+ #define IMX355_EXPOSURE_MIN 1
+ #define IMX355_EXPOSURE_STEP 1
+ #define IMX355_EXPOSURE_DEFAULT 0x0282
++#define IMX355_EXPOSURE_OFFSET 10
+
+ /* Analog gain control */
+ #define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204)
+@@ -630,7 +631,7 @@ static int imx355_set_ctrl(struct v4l2_c
+ switch (ctrl->id) {
+ case V4L2_CID_VBLANK:
+ /* Update max exposure while meeting expected vblanking */
+- max = imx355->cur_mode->height + ctrl->val - 10;
++ max = imx355->cur_mode->height + ctrl->val - IMX355_EXPOSURE_OFFSET;
+ __v4l2_ctrl_modify_range(imx355->exposure,
+ imx355->exposure->minimum,
+ max, imx355->exposure->step, max);
+@@ -1118,7 +1119,7 @@ static int imx355_init_controls(struct i
+ imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ /* fll >= exposure time + adjust parameter (default value is 10) */
+- exposure_max = mode->fll_def - 10;
++ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET;
+ imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
+ V4L2_CID_EXPOSURE,
+ IMX355_EXPOSURE_MIN, exposure_max,
--- /dev/null
+From 892ac5e6cd1f23a79be8dc914c6c61e37cec6598 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 17 Jun 2026 12:17:50 +0100
+Subject: [PATCH] media: imx355: Use NULL ctrl_ops for HBLANK as it is a
+ read-only control
+
+This avoids the set_ctrl handler being called under any
+circumstances, as it will return an error for the unhandled
+ctrl.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -1112,9 +1112,8 @@ static int imx355_init_controls(struct i
+ 1, vblank_def);
+
+ hblank = mode->llp - mode->width;
+- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
+- V4L2_CID_HBLANK, hblank, hblank,
+- 1, hblank);
++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK,
++ hblank, hblank, 1, hblank);
+ if (imx355->hblank)
+ imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
--- /dev/null
+From 7f3dda0cffae8934919d166d860378a6acf4549a Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 30 Jun 2026 17:17:04 +0100
+Subject: [PATCH] media: imx355: Compute link frequency from PLL setup
+
+In preparation for additional options in the PLL setup, compute
+the link frequency rather than using a hardcoded value.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 41 +++++++++++++++++---------------------
+ 1 file changed, 18 insertions(+), 23 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -29,7 +29,11 @@
+
+ /* PLL registers that depend on the external clock frequency */
+ #define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136)
++#define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d)
+ #define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e)
++#define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301)
++#define IMX355_PLL_OP_PREDIV 2
++#define IMX355_PLL_IVT_PCK_DIV 5
+
+ /* V_TIMING internal */
+ #define IMX355_REG_FLL CCI_REG16(0x0340)
+@@ -83,9 +87,6 @@
+ /* Flip Control */
+ #define IMX355_REG_ORIENTATION CCI_REG8(0x0101)
+
+-/* default link frequency and external clock */
+-#define IMX355_LINK_FREQ_DEFAULT 360000000LL
+-
+ /* number of data lanes */
+ #define IMX355_DATA_LANES 4
+
+@@ -141,6 +142,7 @@ static const struct imx355_clk_params im
+ };
+
+ struct imx355_hwcfg {
++ s64 link_freq_menu;
+ unsigned long link_freq_bitmap;
+ };
+
+@@ -238,12 +240,12 @@ static const struct cci_reg_sequence imx
+ { CCI_REG8(0x0112), 0x0a },
+ { CCI_REG8(0x0113), 0x0a },
+ { CCI_REG8(0x0114), 0x03 },
+- { CCI_REG8(0x0301), 0x05 },
++ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV },
+ { CCI_REG8(0x0303), 0x01 },
+- { CCI_REG8(0x0305), 0x00 },
++ { CCI_REG8(0x0305), 0x02 },
+ { CCI_REG8(0x0306), 0x78 },
+ { CCI_REG8(0x0307), 0x02 },
+- { CCI_REG8(0x030d), 0x02 },
++ { IMX355_REG_PLL_OP_PREDIV, IMX355_PLL_OP_PREDIV },
+ { CCI_REG8(0x0310), 0x00 },
+ { CCI_REG8(0x0220), 0x00 },
+ { CCI_REG8(0x0222), 0x01 },
+@@ -337,14 +339,6 @@ static const char * const imx355_test_pa
+ "Pseudorandom Sequence (PN9)",
+ };
+
+-/*
+- * When adding more than the one below, make sure the disallowed ones will
+- * actually be disabled in the LINK_FREQ control.
+- */
+-static const s64 link_freq_menu_items[] = {
+- IMX355_LINK_FREQ_DEFAULT,
+-};
+-
+ /* Mode configs */
+ static const struct imx355_mode supported_modes[] = {
+ {
+@@ -1080,7 +1074,6 @@ static int imx355_init_controls(struct i
+ s64 hblank;
+ u64 pixel_rate;
+ const struct imx355_mode *mode;
+- u32 max;
+ int ret;
+
+ ctrl_hdlr = &imx355->ctrl_handler;
+@@ -1089,15 +1082,14 @@ static int imx355_init_controls(struct i
+ return ret;
+
+ ctrl_hdlr->lock = &imx355->mutex;
+- max = ARRAY_SIZE(link_freq_menu_items) - 1;
+ imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops,
+- V4L2_CID_LINK_FREQ, max, 0,
+- link_freq_menu_items);
++ V4L2_CID_LINK_FREQ, 0, 0,
++ &imx355->hwcfg->link_freq_menu);
+ if (imx355->link_freq)
+ imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */
+- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4;
++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * 4;
+ do_div(pixel_rate, 10);
+ /* By default, PIXEL_RATE is read only */
+ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE,
+@@ -1172,12 +1164,14 @@ error:
+ return ret;
+ }
+
+-static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev)
++static struct imx355_hwcfg *imx355_get_hwcfg(struct imx355 *imx355)
+ {
++ struct device *dev = imx355->dev;
+ struct imx355_hwcfg *cfg;
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
++ const struct imx355_clk_params *clk = imx355->clk_params;
+ struct fwnode_handle *ep;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ int ret;
+@@ -1200,10 +1194,11 @@ static struct imx355_hwcfg *imx355_get_h
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES)
+ goto out_err;
+
++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy) /
++ (IMX355_PLL_OP_PREDIV * 2);
+ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
+ bus_cfg.nr_of_link_frequencies,
+- link_freq_menu_items,
+- ARRAY_SIZE(link_freq_menu_items),
++ &cfg->link_freq_menu, 1,
+ &cfg->link_freq_bitmap);
+ if (ret)
+ goto out_err;
+@@ -1275,7 +1270,7 @@ static int imx355_probe(struct i2c_clien
+ /* Initialize subdev */
+ v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops);
+
+- imx355->hwcfg = imx355_get_hwcfg(imx355->dev);
++ imx355->hwcfg = imx355_get_hwcfg(imx355);
+ if (!imx355->hwcfg) {
+ dev_err(imx355->dev, "failed to get hwcfg");
+ ret = -ENODEV;
--- /dev/null
+From 99f59a309e185efab42f3a03e38b3d18d99f9b67 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 16:22:08 +0100
+Subject: [PATCH] media: imx355: Support 2 lane readout.
+
+The sensor supports 2 or 4 lane readout, but the driver only allowed
+for 4 lanes. Add 2 lane support.
+
+The clock tree only supports single PLL mode to feed both IOP (MIPI)
+and IVT (Pixel array).
+2 lane mode supports a MIPI link frequency of up to 445MHz (890Mbit/s)
+cf 360MHz (720Mbit/s) for 4lane. Update clock setup and pixel rates to
+match.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 67 +++++++++++++++++++++++++++-----------
+ 1 file changed, 48 insertions(+), 19 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -27,11 +27,14 @@
+ #define IMX355_REG_CHIP_ID CCI_REG16(0x0016)
+ #define IMX355_CHIP_ID 0x0355
+
++#define IMX355_REG_LANE_SEL CCI_REG8(0x0114)
++
+ /* PLL registers that depend on the external clock frequency */
+ #define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136)
+ #define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d)
+ #define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e)
+ #define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301)
++#define IMX355_REG_PLL_IVT_SYSCK_DIV CCI_REG8(0x0303)
+ #define IMX355_PLL_OP_PREDIV 2
+ #define IMX355_PLL_IVT_PCK_DIV 5
+
+@@ -80,6 +83,8 @@
+ #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3
+ #define IMX355_TEST_PATTERN_PN9 4
+
++#define IMX355_REG_REQ_LINK_BIT_RATE CCI_REG16(0x0820)
++
+ #define IMX355_REG_BINNING_MODE CCI_REG8(0x0900)
+ #define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901)
+ #define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902)
+@@ -87,9 +92,6 @@
+ /* Flip Control */
+ #define IMX355_REG_ORIENTATION CCI_REG8(0x0101)
+
+-/* number of data lanes */
+-#define IMX355_DATA_LANES 4
+-
+ #define IMX355_PIXEL_ARRAY_TOP 0
+ #define IMX355_PIXEL_ARRAY_LEFT 0
+ #define IMX355_PIXEL_ARRAY_WIDTH 3280
+@@ -120,30 +122,38 @@ struct imx355_mode {
+
+ struct imx355_clk_params {
+ u32 ext_clk;
+- u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */
+- u16 pll_op_mpy; /* OP system PLL multiplier */
++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */
++ u16 pll_op_mpy[2]; /* OP system PLL multiplier */
++ u8 pll_op_prediv[2]; /* OP system pre PLL d */
+ };
+
+ /*
+ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing.
+- * All modes use the same PLL setup for OP, with IOPCK being 720MHz.
++ * In 4 lane mode the MIPI rate is 360Mhz (720Mbit/s) and pixel rate is
++ * 288MPix/s.
++ * In 2 lane mode the MIPI rate is 444MHz (888Mbit/s) and pixel rate
++ * 177.6MPix/s with a 24MHz clock, and 441.6MHz (883.2Mbit/s) and 176.6MPix/s
++ * with a 19.2MHz clock.
+ */
+ static const struct imx355_clk_params imx355_clk_params[] = {
+ {
+ .ext_clk = 19200000,
+- .extclk_freq = 0x1333, /* 19.2 MHz */
+- .pll_op_mpy = 75, /* 19.2 / 2 * 75 = 720 MHz */
++ .extclk_freq = 0x1333,
++ .pll_op_mpy = { 75, 92 },
++ .pll_op_prediv = { 2, 2 }
+ },
+ {
+ .ext_clk = 24000000,
+- .extclk_freq = 0x1800, /* 24.0 MHz */
+- .pll_op_mpy = 60, /* 24.0 / 2 * 60 = 720 MHz */
++ .extclk_freq = 0x1800,
++ .pll_op_mpy = { 60, 111 },
++ .pll_op_prediv = { 2, 3 }
+ },
+ };
+
+ struct imx355_hwcfg {
+ s64 link_freq_menu;
+ unsigned long link_freq_bitmap;
++ unsigned int num_lanes;
+ };
+
+ struct imx355 {
+@@ -239,7 +249,6 @@ static const struct cci_reg_sequence imx
+ { CCI_REG8(0x305a), 0x00 },
+ { CCI_REG8(0x0112), 0x0a },
+ { CCI_REG8(0x0113), 0x0a },
+- { CCI_REG8(0x0114), 0x03 },
+ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV },
+ { CCI_REG8(0x0303), 0x01 },
+ { CCI_REG8(0x0305), 0x02 },
+@@ -249,8 +258,6 @@ static const struct cci_reg_sequence imx
+ { CCI_REG8(0x0310), 0x00 },
+ { CCI_REG8(0x0220), 0x00 },
+ { CCI_REG8(0x0222), 0x01 },
+- { CCI_REG8(0x0820), 0x0b },
+- { CCI_REG8(0x0821), 0x40 },
+ { CCI_REG8(0x3088), 0x04 },
+ { CCI_REG8(0x6813), 0x02 },
+ { CCI_REG8(0x6835), 0x07 },
+@@ -805,6 +812,7 @@ imx355_set_pad_format(struct v4l2_subdev
+ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN,
+ height, 1, vblank_def);
+ __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def);
++
+ h_blank = mode->llp - imx355->cur_mode->width;
+ /*
+ * Currently hblank is not changeable.
+@@ -868,6 +876,8 @@ static int imx355_start_streaming(struct
+ {
+ const struct imx355_reg_list *reg_list;
+ const struct imx355_mode *mode;
++ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1;
++ u64 link_bitrate;
+ u8 binning_mode;
+ int ret = 0;
+
+@@ -903,7 +913,21 @@ static int imx355_start_streaming(struct
+ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ,
+ imx355->clk_params->extclk_freq, &ret);
+ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL,
+- imx355->clk_params->pll_op_mpy, &ret);
++ imx355->clk_params->pll_op_mpy[lane_idx], &ret);
++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_PREDIV,
++ imx355->clk_params->pll_op_prediv[lane_idx], &ret);
++ cci_write(imx355->regmap, IMX355_REG_PLL_IVT_SYSCK_DIV,
++ lane_idx ? 2 : 1, &ret);
++
++ /* Set MIPI configuration */
++ cci_write(imx355->regmap, IMX355_REG_LANE_SEL,
++ imx355->hwcfg->num_lanes - 1, &ret);
++
++ link_bitrate = imx355->link_freq->qmenu_int[imx355->link_freq->val] *
++ imx355->hwcfg->num_lanes * 2;
++ do_div(link_bitrate, 1000000);
++ cci_write(imx355->regmap, IMX355_REG_REQ_LINK_BIT_RATE, link_bitrate,
++ &ret);
+
+ /* set digital gain control to all color mode */
+ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret);
+@@ -1089,9 +1113,9 @@ static int imx355_init_controls(struct i
+ imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */
+- pixel_rate = imx355->hwcfg->link_freq_menu * 2 * 4;
++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * imx355->hwcfg->num_lanes;
+ do_div(pixel_rate, 10);
+- /* By default, PIXEL_RATE is read only */
++
+ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE,
+ pixel_rate, pixel_rate, 1, pixel_rate);
+
+@@ -1174,6 +1198,7 @@ static struct imx355_hwcfg *imx355_get_h
+ const struct imx355_clk_params *clk = imx355->clk_params;
+ struct fwnode_handle *ep;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
++ int lane_idx;
+ int ret;
+
+ if (!fwnode)
+@@ -1191,11 +1216,15 @@ static struct imx355_hwcfg *imx355_get_h
+ if (!cfg)
+ goto out_err;
+
+- if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES)
++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 &&
++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4)
+ goto out_err;
+
+- cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy) /
+- (IMX355_PLL_OP_PREDIV * 2);
++ cfg->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
++
++ lane_idx = cfg->num_lanes == 4 ? 0 : 1;
++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy[lane_idx]) /
++ (clk->pll_op_prediv[lane_idx] * 2);
+ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies,
+ bus_cfg.nr_of_link_frequencies,
+ &cfg->link_freq_menu, 1,
--- /dev/null
+From 85efb034b8e3fa95dbfa022b53e7db1dae38bed6 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 11 May 2026 18:13:20 +0100
+Subject: [PATCH] media: imx355: Switch to using the subdev state.
+
+The subdev state is now preferred rather than handling crop
+and format within the driver state, so switch the driver to
+using it.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 169 +++++++++++--------------------------
+ 1 file changed, 49 insertions(+), 120 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -179,13 +179,6 @@ struct imx355 {
+ struct imx355_hwcfg *hwcfg;
+ const struct imx355_clk_params *clk_params;
+
+- /*
+- * Mutex for serialized access:
+- * Protect sensor set pad format and start/stop streaming safely.
+- * Protect access to sensor v4l2 controls.
+- */
+- struct mutex mutex;
+-
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data *supplies;
+ };
+@@ -592,35 +585,11 @@ static u32 imx355_get_format_code(struct
+ { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, },
+ };
+
+- lockdep_assert_held(&imx355->mutex);
+ code = codes[imx355->vflip->val][imx355->hflip->val];
+
+ return code;
+ }
+
+-/* Open sub-device */
+-static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+-{
+- struct imx355 *imx355 = to_imx355(sd);
+- struct v4l2_mbus_framefmt *try_fmt =
+- v4l2_subdev_state_get_format(fh->state, 0);
+- struct v4l2_rect *crop = v4l2_subdev_state_get_crop(fh->state, 0);
+-
+- mutex_lock(&imx355->mutex);
+-
+- /* Initialize try_fmt */
+- try_fmt->width = imx355->cur_mode->width;
+- try_fmt->height = imx355->cur_mode->height;
+- try_fmt->code = imx355_get_format_code(imx355);
+- try_fmt->field = V4L2_FIELD_NONE;
+-
+- *crop = imx355->cur_mode->crop;
+-
+- mutex_unlock(&imx355->mutex);
+-
+- return 0;
+-}
+-
+ static int imx355_set_ctrl(struct v4l2_ctrl *ctrl)
+ {
+ struct imx355 *imx355 = container_of(ctrl->handler,
+@@ -628,6 +597,9 @@ static int imx355_set_ctrl(struct v4l2_c
+ s64 max;
+ int ret;
+
++ state = v4l2_subdev_get_locked_active_state(&imx355->sd);
++ format = v4l2_subdev_state_get_format(state, 0);
++
+ /* Propagate change of current control to all related controls */
+ switch (ctrl->id) {
+ case V4L2_CID_VBLANK:
+@@ -700,9 +672,7 @@ static int imx355_enum_mbus_code(struct
+ if (code->index > 0)
+ return -EINVAL;
+
+- mutex_lock(&imx355->mutex);
+ code->code = imx355_get_format_code(imx355);
+- mutex_unlock(&imx355->mutex);
+
+ return 0;
+ }
+@@ -716,12 +686,9 @@ static int imx355_enum_frame_size(struct
+ if (fse->index >= ARRAY_SIZE(supported_modes))
+ return -EINVAL;
+
+- mutex_lock(&imx355->mutex);
+ if (fse->code != imx355_get_format_code(imx355)) {
+- mutex_unlock(&imx355->mutex);
+ return -EINVAL;
+ }
+- mutex_unlock(&imx355->mutex);
+
+ fse->min_width = supported_modes[fse->index].width;
+ fse->max_width = fse->min_width;
+@@ -745,36 +712,6 @@ static void imx355_update_pad_format(str
+ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE;
+ }
+
+-static int imx355_do_get_pad_format(struct imx355 *imx355,
+- struct v4l2_subdev_state *sd_state,
+- struct v4l2_subdev_format *fmt)
+-{
+- struct v4l2_mbus_framefmt *framefmt;
+-
+- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+- fmt->format = *framefmt;
+- } else {
+- imx355_update_pad_format(imx355, imx355->cur_mode, fmt);
+- }
+-
+- return 0;
+-}
+-
+-static int imx355_get_pad_format(struct v4l2_subdev *sd,
+- struct v4l2_subdev_state *sd_state,
+- struct v4l2_subdev_format *fmt)
+-{
+- struct imx355 *imx355 = to_imx355(sd);
+- int ret;
+-
+- mutex_lock(&imx355->mutex);
+- ret = imx355_do_get_pad_format(imx355, sd_state, fmt);
+- mutex_unlock(&imx355->mutex);
+-
+- return ret;
+-}
+-
+ static int
+ imx355_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+@@ -783,12 +720,11 @@ imx355_set_pad_format(struct v4l2_subdev
+ struct imx355 *imx355 = to_imx355(sd);
+ const struct imx355_mode *mode;
+ struct v4l2_mbus_framefmt *framefmt;
++ struct v4l2_rect *crop;
+ s32 vblank_def;
+ s64 h_blank;
+ u32 height;
+
+- mutex_lock(&imx355->mutex);
+-
+ /*
+ * Only one bayer order is supported.
+ * It depends on the flip settings.
+@@ -800,10 +736,18 @@ imx355_set_pad_format(struct v4l2_subdev
+ width, height,
+ fmt->format.width, fmt->format.height);
+ imx355_update_pad_format(imx355, mode, fmt);
+- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+- *framefmt = fmt->format;
+- } else {
++
++ framefmt = v4l2_subdev_state_get_format(sd_state, 0);
++
++ *framefmt = fmt->format;
++
++ crop = v4l2_subdev_state_get_crop(sd_state, 0);
++ crop->width = mode->crop.width;
++ crop->height = mode->crop.height;
++ crop->left = mode->crop.left;
++ crop->top = mode->crop.top;
++
++ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ imx355->cur_mode = mode;
+ /* Update limits and set FPS to default */
+ height = imx355->cur_mode->height;
+@@ -822,41 +766,17 @@ imx355_set_pad_format(struct v4l2_subdev
+ h_blank, 1, h_blank);
+ }
+
+- mutex_unlock(&imx355->mutex);
+-
+ return 0;
+ }
+
+-static void
+-__imx355_get_pad_crop(struct imx355 *imx355,
+- struct v4l2_subdev_state *sd_state, unsigned int pad,
+- enum v4l2_subdev_format_whence which, struct v4l2_rect *r)
+-{
+- switch (which) {
+- case V4L2_SUBDEV_FORMAT_TRY:
+- *r = *v4l2_subdev_state_get_crop(sd_state, pad);
+- break;
+- case V4L2_SUBDEV_FORMAT_ACTIVE:
+- *r = imx355->cur_mode->crop;
+- break;
+- }
+-}
+-
+ static int imx355_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+ {
+ switch (sel->target) {
+- case V4L2_SEL_TGT_CROP: {
+- struct imx355 *imx355 = to_imx355(sd);
+-
+- mutex_lock(&imx355->mutex);
+- __imx355_get_pad_crop(imx355, sd_state, sel->pad, sel->which,
+- &sel->r);
+- mutex_unlock(&imx355->mutex);
+-
++ case V4L2_SEL_TGT_CROP:
++ sel->r = *v4l2_subdev_state_get_crop(sd_state, 0);
+ return 0;
+- }
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+@@ -871,6 +791,21 @@ static int imx355_get_selection(struct v
+ return -EINVAL;
+ }
+
++static int imx355_entity_init_state(struct v4l2_subdev *subdev,
++ struct v4l2_subdev_state *sd_state)
++{
++ struct v4l2_subdev_format fmt = { };
++
++ fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
++ fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
++ fmt.format.width = supported_modes[0].width;
++ fmt.format.height = supported_modes[0].height;
++
++ imx355_set_pad_format(subdev, sd_state, &fmt);
++
++ return 0;
++}
++
+ /* Start streaming */
+ static int imx355_start_streaming(struct imx355 *imx355)
+ {
+@@ -958,12 +893,10 @@ static int imx355_set_stream(struct v4l2
+ struct imx355 *imx355 = to_imx355(sd);
+ int ret = 0;
+
+- mutex_lock(&imx355->mutex);
+-
+ if (enable) {
+ ret = pm_runtime_resume_and_get(imx355->dev);
+ if (ret < 0)
+- goto err_unlock;
++ return ret;
+
+ /*
+ * Apply default & customized values
+@@ -981,14 +914,10 @@ static int imx355_set_stream(struct v4l2
+ __v4l2_ctrl_grab(imx355->vflip, enable);
+ __v4l2_ctrl_grab(imx355->hflip, enable);
+
+- mutex_unlock(&imx355->mutex);
+-
+ return ret;
+
+ err_rpm_put:
+ pm_runtime_put_autosuspend(imx355->dev);
+-err_unlock:
+- mutex_unlock(&imx355->mutex);
+
+ return ret;
+ }
+@@ -1022,7 +951,7 @@ static const struct v4l2_subdev_video_op
+
+ static const struct v4l2_subdev_pad_ops imx355_pad_ops = {
+ .enum_mbus_code = imx355_enum_mbus_code,
+- .get_fmt = imx355_get_pad_format,
++ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = imx355_set_pad_format,
+ .enum_frame_size = imx355_enum_frame_size,
+ .get_selection = imx355_get_selection,
+@@ -1039,7 +968,7 @@ static const struct media_entity_operati
+ };
+
+ static const struct v4l2_subdev_internal_ops imx355_internal_ops = {
+- .open = imx355_open,
++ .init_state = imx355_entity_init_state,
+ };
+
+ static int imx355_power_off(struct device *dev)
+@@ -1105,7 +1034,6 @@ static int imx355_init_controls(struct i
+ if (ret)
+ return ret;
+
+- ctrl_hdlr->lock = &imx355->mutex;
+ imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops,
+ V4L2_CID_LINK_FREQ, 0, 0,
+ &imx355->hwcfg->link_freq_menu);
+@@ -1254,8 +1182,6 @@ static int imx355_probe(struct i2c_clien
+
+ imx355->dev = &client->dev;
+
+- mutex_init(&imx355->mutex);
+-
+ imx355->regmap = devm_cci_regmap_init_i2c(client, 16);
+ if (IS_ERR(imx355->regmap)) {
+ dev_err(imx355->dev, "Unable to initialize I2C\n");
+@@ -1285,7 +1211,7 @@ static int imx355_probe(struct i2c_clien
+ &imx355->supplies);
+ if (ret) {
+ dev_err_probe(imx355->dev, ret, "could not get regulators");
+- goto error_probe;
++ return ret;
+ }
+
+ imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset",
+@@ -1293,7 +1219,7 @@ static int imx355_probe(struct i2c_clien
+ if (IS_ERR(imx355->reset_gpio)) {
+ ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio),
+ "failed to get gpios");
+- goto error_probe;
++ return ret;
+ }
+
+ /* Initialize subdev */
+@@ -1302,13 +1228,12 @@ static int imx355_probe(struct i2c_clien
+ imx355->hwcfg = imx355_get_hwcfg(imx355);
+ if (!imx355->hwcfg) {
+ dev_err(imx355->dev, "failed to get hwcfg");
+- ret = -ENODEV;
+- goto error_probe;
++ return -ENODEV;
+ }
+
+ ret = imx355_power_on(imx355->dev);
+ if (ret)
+- goto error_probe;
++ return ret;
+
+ /* Check module identity */
+ ret = imx355_identify_module(imx355);
+@@ -1341,6 +1266,13 @@ static int imx355_probe(struct i2c_clien
+ goto error_handler_free;
+ }
+
++ imx355->sd.state_lock = imx355->ctrl_handler.lock;
++ ret = v4l2_subdev_init_finalize(&imx355->sd);
++ if (ret < 0) {
++ dev_err_probe(imx355->dev, ret, "subdev init error\n");
++ goto error_handler_free;
++ }
++
+ /*
+ * Device is already turned on by i2c-core with ACPI domain PM.
+ * Enable runtime PM and turn off the device.
+@@ -1360,6 +1292,7 @@ static int imx355_probe(struct i2c_clien
+ error_media_entity_runtime_pm:
+ pm_runtime_disable(imx355->dev);
+ pm_runtime_set_suspended(imx355->dev);
++ v4l2_subdev_cleanup(&imx355->sd);
+ media_entity_cleanup(&imx355->sd.entity);
+
+ error_handler_free:
+@@ -1368,9 +1301,6 @@ error_handler_free:
+ error_power_off:
+ imx355_power_off(imx355->dev);
+
+-error_probe:
+- mutex_destroy(&imx355->mutex);
+-
+ return ret;
+ }
+
+@@ -1380,6 +1310,7 @@ static void imx355_remove(struct i2c_cli
+ struct imx355 *imx355 = to_imx355(sd);
+
+ v4l2_async_unregister_subdev(sd);
++ v4l2_subdev_cleanup(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+@@ -1389,8 +1320,6 @@ static void imx355_remove(struct i2c_cli
+ imx355_power_off(imx355->dev);
+ pm_runtime_set_suspended(imx355->dev);
+ }
+-
+- mutex_destroy(&imx355->mutex);
+ }
+
+ static const struct acpi_device_id imx355_acpi_ids[] __maybe_unused = {
--- /dev/null
+From 8f936ca8788a51b6ca75a8025de0d684e5841159 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Mon, 11 May 2026 18:58:48 +0100
+Subject: [PATCH] media: imx355: Remove storing cur_mode in the state
+
+All the information for the mode is now stored within
+the subdev state, so configure the sensor based on that.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 65 ++++++++++++++++++--------------------
+ 1 file changed, 31 insertions(+), 34 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -173,9 +173,6 @@ struct imx355 {
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *hflip;
+
+- /* Current mode */
+- const struct imx355_mode *cur_mode;
+-
+ struct imx355_hwcfg *hwcfg;
+ const struct imx355_clk_params *clk_params;
+
+@@ -594,6 +591,8 @@ static int imx355_set_ctrl(struct v4l2_c
+ {
+ struct imx355 *imx355 = container_of(ctrl->handler,
+ struct imx355, ctrl_handler);
++ const struct v4l2_mbus_framefmt *format = NULL;
++ struct v4l2_subdev_state *state;
+ s64 max;
+ int ret;
+
+@@ -604,7 +603,7 @@ static int imx355_set_ctrl(struct v4l2_c
+ switch (ctrl->id) {
+ case V4L2_CID_VBLANK:
+ /* Update max exposure while meeting expected vblanking */
+- max = imx355->cur_mode->height + ctrl->val - IMX355_EXPOSURE_OFFSET;
++ max = format->height + ctrl->val - IMX355_EXPOSURE_OFFSET;
+ __v4l2_ctrl_modify_range(imx355->exposure,
+ imx355->exposure->minimum,
+ max, imx355->exposure->step, max);
+@@ -635,7 +634,7 @@ static int imx355_set_ctrl(struct v4l2_c
+ case V4L2_CID_VBLANK:
+ /* Update FLL that meets expected vertical blanking */
+ ret = cci_write(imx355->regmap, IMX355_REG_FLL,
+- imx355->cur_mode->height + ctrl->val, NULL);
++ format->height + ctrl->val, NULL);
+ break;
+ case V4L2_CID_TEST_PATTERN:
+ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN,
+@@ -721,9 +720,7 @@ imx355_set_pad_format(struct v4l2_subdev
+ const struct imx355_mode *mode;
+ struct v4l2_mbus_framefmt *framefmt;
+ struct v4l2_rect *crop;
+- s32 vblank_def;
+ s64 h_blank;
+- u32 height;
+
+ /*
+ * Only one bayer order is supported.
+@@ -748,16 +745,14 @@ imx355_set_pad_format(struct v4l2_subdev
+ crop->top = mode->crop.top;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+- imx355->cur_mode = mode;
+ /* Update limits and set FPS to default */
+- height = imx355->cur_mode->height;
+- vblank_def = imx355->cur_mode->fll_def - height;
+- height = IMX355_FLL_MAX - height;
+ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN,
+- height, 1, vblank_def);
+- __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def);
++ IMX355_FLL_MAX - mode->height, 1,
++ mode->fll_def - mode->height);
++ __v4l2_ctrl_s_ctrl(imx355->vblank, mode->fll_def - mode->height);
++
++ h_blank = mode->llp - mode->width;
+
+- h_blank = mode->llp - imx355->cur_mode->width;
+ /*
+ * Currently hblank is not changeable.
+ * So FPS control is done only by vblank.
+@@ -809,9 +804,11 @@ static int imx355_entity_init_state(stru
+ /* Start streaming */
+ static int imx355_start_streaming(struct imx355 *imx355)
+ {
+- const struct imx355_reg_list *reg_list;
++ const struct v4l2_mbus_framefmt *fmt;
++ struct v4l2_subdev_state *state;
+ const struct imx355_mode *mode;
+ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1;
++ struct v4l2_rect *crop;
+ u64 link_bitrate;
+ u8 binning_mode;
+ int ret = 0;
+@@ -820,25 +817,29 @@ static int imx355_start_streaming(struct
+ cci_multi_reg_write(imx355->regmap, imx355_global_regs,
+ ARRAY_SIZE(imx355_global_regs), &ret);
+
+- /* Apply default values of current mode */
+- mode = imx355->cur_mode;
+- reg_list = &mode->reg_list;
+- cci_multi_reg_write(imx355->regmap, reg_list->regs,
+- reg_list->num_of_regs, &ret);
++ /* Apply values of current mode */
++ state = v4l2_subdev_get_locked_active_state(&imx355->sd);
++ fmt = v4l2_subdev_state_get_format(state, 0);
++ crop = v4l2_subdev_state_get_crop(state, 0);
++ mode = v4l2_find_nearest_size(supported_modes,
++ ARRAY_SIZE(supported_modes),
++ width, height, fmt->width, fmt->height);
++ cci_multi_reg_write(imx355->regmap, mode->reg_list.regs,
++ mode->reg_list.num_of_regs, &ret);
+
+ /* Set readout crop and size registers */
+- cci_write(imx355->regmap, IMX355_REG_X_ADD_START, mode->crop.left,
++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, crop->left,
+ &ret);
+- cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, mode->crop.top, &ret);
++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, crop->top, &ret);
+ cci_write(imx355->regmap, IMX355_REG_X_ADD_END,
+- mode->crop.width + mode->crop.left - 1, &ret);
++ crop->width + crop->left - 1, &ret);
+ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END,
+- mode->crop.height + mode->crop.top - 1, &ret);
+- cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, mode->width, &ret);
+- cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, mode->height, &ret);
++ crop->height + crop->top - 1, &ret);
++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, fmt->width, &ret);
++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, fmt->height, &ret);
+
+- binning_mode = ((mode->crop.width / mode->width) << 4) |
+- (mode->crop.height / mode->height);
++ binning_mode = ((crop->width / fmt->width) << 4) |
++ (crop->height / fmt->height);
+ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE,
+ binning_mode == 0x11 ? 0x00 : 0x01, &ret);
+ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret);
+@@ -869,7 +870,7 @@ static int imx355_start_streaming(struct
+
+ /* set line length */
+ cci_write(imx355->regmap, IMX355_REG_LLP,
+- imx355->hblank->val + imx355->cur_mode->width, &ret);
++ imx355->hblank->val + fmt->width, &ret);
+
+ /* Apply customized values from user */
+ if (!ret)
+@@ -1022,11 +1023,11 @@ static int imx355_init_controls(struct i
+ {
+ struct v4l2_fwnode_device_properties props;
+ struct v4l2_ctrl_handler *ctrl_hdlr;
++ const struct imx355_mode *mode = &supported_modes[0];
+ s64 exposure_max;
+ s64 vblank_def;
+ s64 hblank;
+ u64 pixel_rate;
+- const struct imx355_mode *mode;
+ int ret;
+
+ ctrl_hdlr = &imx355->ctrl_handler;
+@@ -1048,7 +1049,6 @@ static int imx355_init_controls(struct i
+ pixel_rate, pixel_rate, 1, pixel_rate);
+
+ /* Initialize vblank/hblank/exposure parameters based on current mode */
+- mode = imx355->cur_mode;
+ vblank_def = mode->fll_def - mode->height;
+ imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
+ V4L2_CID_VBLANK, IMX355_VBLANK_MIN,
+@@ -1242,9 +1242,6 @@ static int imx355_probe(struct i2c_clien
+ goto error_power_off;
+ }
+
+- /* Set default mode to max resolution */
+- imx355->cur_mode = &supported_modes[0];
+-
+ ret = imx355_init_controls(imx355);
+ if (ret) {
+ dev_err(imx355->dev, "failed to init controls: %d", ret);
--- /dev/null
+From 8a61264722a08914e2037a04bbd7b3fbcff77894 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 12 May 2026 16:47:32 +0100
+Subject: [PATCH] media: imx355: Make V4L2_CID_HBLANK controllable
+
+V4L2_CID_HBLANK / LLP was a read-only control that set min/
+max/default all to the same value.
+
+The datasheet does state "H-sync timing is fixed in order to
+avoid the sensor internal interference (FPN) in IMX355 by
+line_length". No interference has currently been observed,
+and the default is always to leave it at the minimum (and
+approved) value. Allowing it to be controllable therefore
+has no effect unless a frame period longer than can be
+achieved just through VBLANK is requested.
+
+Exposure times up to 14.9s/frame can be achieved when combined
+with the max FLL / VBLANK value.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/media/i2c/imx355.c | 25 ++++++++++++-------------
+ 1 file changed, 12 insertions(+), 13 deletions(-)
+
+--- a/drivers/media/i2c/imx355.c
++++ b/drivers/media/i2c/imx355.c
+@@ -636,6 +636,11 @@ static int imx355_set_ctrl(struct v4l2_c
+ ret = cci_write(imx355->regmap, IMX355_REG_FLL,
+ format->height + ctrl->val, NULL);
+ break;
++ case V4L2_CID_HBLANK:
++ ret = cci_write(imx355->regmap, IMX355_REG_LLP,
++ format->width + ctrl->val,
++ NULL);
++ break;
+ case V4L2_CID_TEST_PATTERN:
+ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN,
+ ctrl->val, NULL);
+@@ -753,12 +758,10 @@ imx355_set_pad_format(struct v4l2_subdev
+
+ h_blank = mode->llp - mode->width;
+
+- /*
+- * Currently hblank is not changeable.
+- * So FPS control is done only by vblank.
+- */
+ __v4l2_ctrl_modify_range(imx355->hblank, h_blank,
+- h_blank, 1, h_blank);
++ IMX355_LLP_MAX - mode->width, 1,
++ h_blank);
++ __v4l2_ctrl_s_ctrl(imx355->hblank, h_blank);
+ }
+
+ return 0;
+@@ -868,10 +871,6 @@ static int imx355_start_streaming(struct
+ /* set digital gain control to all color mode */
+ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret);
+
+- /* set line length */
+- cci_write(imx355->regmap, IMX355_REG_LLP,
+- imx355->hblank->val + fmt->width, &ret);
+-
+ /* Apply customized values from user */
+ if (!ret)
+ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler);
+@@ -1056,10 +1055,10 @@ static int imx355_init_controls(struct i
+ 1, vblank_def);
+
+ hblank = mode->llp - mode->width;
+- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK,
+- hblank, hblank, 1, hblank);
+- if (imx355->hblank)
+- imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops,
++ V4L2_CID_HBLANK, hblank,
++ IMX355_LLP_MAX - mode->width, 1,
++ hblank);
+
+ /* fll >= exposure time + adjust parameter (default value is 10) */
+ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET;
--- /dev/null
+From a96eaa637db7cbd4be8173024f7d75ead22c6882 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 15 Apr 2026 15:01:20 +0100
+Subject: [PATCH] dtoverlays: Add overlay for imx355 image sensor
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 17 +++
+ arch/arm/boot/dts/overlays/imx355-overlay.dts | 101 ++++++++++++++++++
+ arch/arm/boot/dts/overlays/imx355.dtsi | 26 +++++
+ 4 files changed, 145 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/imx355-overlay.dts
+ create mode 100644 arch/arm/boot/dts/overlays/imx355.dtsi
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -143,6 +143,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ imx296.dtbo \
+ imx327.dtbo \
+ imx335.dtbo \
++ imx355.dtbo \
+ imx378.dtbo \
+ imx415.dtbo \
+ imx462.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -2981,6 +2981,23 @@ Params: orientation Sensor o
+ Module (1, 3, 4, or 5) or Pi 5.
+
+
++Name: imx355
++Info: Sony IMX355 camera module.
++ Uses Unicam 1, which is the standard camera connector on most Pi
++ variants.
++Load: dtoverlay=imx355,<param>
++Params: orientation Sensor orientation (0 = front, 1 = rear,
++ 2 = external, default external)
++ rotation Mounting rotation of the camera sensor (0 or
++ 180, default 0)
++ media-controller Configure use of Media Controller API for
++ configuring the sensor (default on)
++ cam0 Adopt the default configuration for CAM0 on a
++ Compute Module (CSI0, i2c_vc, and cam0_reg).
++ 4lane Enable 4 CSI2 lanes. This requires a Compute
++ Module (1, 3, 4, or 5) or Pi 5.
++
++
+ Name: imx378
+ Info: Sony IMX378 camera module.
+ Uses Unicam 1, which is the standard camera connector on most Pi
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/imx355-overlay.dts
+@@ -0,0 +1,101 @@
++// SPDX-License-Identifier: GPL-2.0-only
++// Definitions for IMX355 camera module on VC I2C bus
++/dts-v1/;
++/plugin/;
++
++#include <dt-bindings/gpio/gpio.h>
++
++/{
++ compatible = "brcm,bcm2835";
++
++ fragment@0 {
++ target = <&i2c0if>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ clk_frag: fragment@1 {
++ target = <&cam1_clk>;
++ __overlay__ {
++ status = "okay";
++ clock-frequency = <24000000>;
++ };
++ };
++
++ fragment@2 {
++ target = <&i2c0mux>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ i2c_frag: fragment@100 {
++ target = <&i2c_csi_dsi>;
++ __overlay__ {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++
++ #include "imx355.dtsi"
++ };
++ };
++
++ csi_frag: fragment@101 {
++ target = <&csi1>;
++ csi: __overlay__ {
++ status = "okay";
++
++ port {
++ csi_ep: endpoint {
++ remote-endpoint = <&cam_endpoint>;
++ clock-lanes = <0>;
++ data-lanes = <1 2>;
++ };
++ };
++ };
++ };
++
++ fragment@102 {
++ target = <&csi1>;
++ __dormant__ {
++ compatible = "brcm,bcm2835-unicam-legacy";
++ };
++ };
++
++ fragment@201 {
++ target = <&csi_ep>;
++ __dormant__ {
++ data-lanes = <1 2 3 4>;
++ };
++ };
++
++ fragment@202 {
++ target = <&cam_endpoint>;
++ __dormant__ {
++ data-lanes = <1 2 3 4>;
++ link-frequencies =
++ /bits/ 64 <360000000>;
++ };
++ };
++
++ __overrides__ {
++ rotation = <&cam_node>,"rotation:0";
++ orientation = <&cam_node>,"orientation:0";
++ media-controller = <0>,"!102";
++ cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
++ <&csi_frag>, "target:0=",<&csi0>,
++ <&clk_frag>, "target:0=",<&cam0_clk>,
++ <&cam_node>, "clocks:0=",<&cam0_clk>,
++ <&cam_node>, "avdd-supply:0=",<&cam0_reg>;
++ 4lane = <0>, "+201+202";
++ };
++};
++
++&cam_node {
++ status = "okay";
++};
++
++&cam_endpoint {
++ remote-endpoint = <&csi_ep>;
++};
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/imx355.dtsi
+@@ -0,0 +1,26 @@
++// Fragment that configures an imx355
++
++cam_node: imx355@1a {
++ compatible = "sony,imx355";
++ reg = <0x1a>;
++ status = "disabled";
++
++ clocks = <&cam1_clk>;
++ clock-names = "xclk";
++
++ avdd-supply = <&cam1_reg>; /* 2.8v */
++ dvdd-supply = <&cam_dummy_reg>; /* 1.8v */
++ dovdd-supply = <&cam_dummy_reg>;/* 1.2v */
++
++ rotation = <180>;
++ orientation = <2>;
++
++ port {
++ cam_endpoint: endpoint {
++ clock-lanes = <0>;
++ data-lanes = <1 2>;
++ link-frequencies =
++ /bits/ 64 <444000000>;
++ };
++ };
++};
--- /dev/null
+From 256d6b4bc33527fae9967773b2a0d3b92e1bd000 Mon Sep 17 00:00:00 2001
+From: zjzhao <zjzhao@edatec.cn>
+Date: Wed, 8 Jul 2026 15:05:45 +0800
+Subject: [PATCH] overlays: ed-ipc: Add EDATEC IPC/EXP unified board overlay
+
+Add a new overlay for EDATEC IPC/EXP series industrial PC carrier
+boards. This overlay configures PCA9535 GPIO expanders on both
+i2c_csi_dsi and i2c_arm buses, with support for multiple board
+profiles (IPC2100, IPC2300, IPC3100, IPC3300, PLC2010, etc.) and
+optional expansion modules (exp8x8y, exp4x4y).
+
+Signed-off-by: zjzhao <zjzhao@edatec.cn>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 53 ++++
+ arch/arm/boot/dts/overlays/ed-ipc-overlay.dts | 279 ++++++++++++++++++
+ 3 files changed, 333 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/ed-ipc-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -63,6 +63,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ draws-pi5.dtbo \
+ dwc-otg-deprecated.dtbo \
+ dwc2.dtbo \
++ ed-ipc.dtbo \
+ edt-ft5406.dtbo \
+ enc28j60.dtbo \
+ enc28j60-spi2.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -1278,6 +1278,59 @@ Params: dr_mode Dual rol
+ mode
+
+
++Name: ed-ipc
++Info: Configures the EDATEC IPC/EXP series hardware peripherals.
++ This overlay provides support for PCA9535 GPIO expanders on
++ the EDATEC industrial PC carrier boards, including interrupt
++ handling, GPIO line names, and optional secondary expander
++ on i2c_arm.
++Load: dtoverlay=ed-ipc,<param>=<val>
++Params: addr I2C address for primary PCA9535 GPIO expander
++ (default 0x20)
++
++ exp_addr I2C address for secondary PCA9535 GPIO
++ expander on i2c_arm (default 0x27)
++
++ ipc3100 Enable configuration for IPC3100 board
++ (CM5, i2c_csi_dsi0, PCA int GPIO6)
++
++ ipc2100 Enable configuration for IPC2100 board
++ (CM4, i2c_csi_dsi, PCA int GPIO6)
++
++ ipc2210 Enable configuration for IPC2210 board
++ (CM4, i2c_csi_dsi, PCA int GPIO6)
++
++ ipc3210 Enable configuration for IPC3210 board
++ (CM5, i2c_csi_dsi0, PCA int GPIO6)
++
++ ipc1200 Enable configuration for IPC1200 board
++ (CM4, i2c_csi_dsi, PCA int GPIO15)
++
++ ipc3300 Enable configuration for IPC3300 board
++ (CM5, i2c_arm, PCA int GPIO38)
++
++ ipc2300 Enable configuration for IPC2300 board
++ (CM4, i2c_arm, PCA int GPIO44)
++
++ plc2010 Enable configuration for PLC2010 board
++ (i2c_arm, PCA int GPIO7, addr 0x21)
++
++ exp8x8y Enable 8-input/8-output expansion module
++ (CM5, i2c_arm)
++
++ exp4x4y Enable 4-input/4-output expansion module
++ (CM5, i2c_arm, exp at addr 0x21)
++
++ ipc3600 Enable combined configuration for IPC3600
++ (IPC3100 + exp8x8y)
++
++ ipc2600 Enable combined configuration for IPC2600
++ (IPC2100 + exp8x8y)
++
++ ipc1220 Enable combined configuration for IPC1220
++ (IPC1200 + exp4x4y)
++
++
+ [ The ds1307-rtc overlay has been deleted. See i2c-rtc. ]
+
+
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/ed-ipc-overlay.dts
+@@ -0,0 +1,279 @@
++/*
++ * EDATEC IPC/EXP unified board overlay
++ */
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2835";
++ i2c_frag: fragment@0 {
++ target = <&i2c_csi_dsi0>;
++ __dormant__ {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++
++ pca_base: pca@20 {
++ compatible = "nxp,pca9535";
++ reg = <0x20>;
++ gpio-controller;
++ #gpio-cells = <2>;
++ status = "okay";
++ };
++ };
++ };
++
++ fragment@100 {
++ target = <&i2c0if>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ fragment@101 {
++ target = <&i2c0mux>;
++ __overlay__ {
++ status = "okay";
++ };
++ };
++
++ fragment@200 {
++ target = <&pca_base>;
++ __dormant__ {
++ gpio-line-names = "5V_GOOD", "LVD", "BUZZER_EN",
++ "4G_RST", "4G_LED", "USER_LED";
++ };
++ };
++
++ fragment@201 {
++ target = <&pca_base>;
++ __dormant__ {
++ gpio-line-names = "5V_GOOD", "LVD", "BUZZER_EN",
++ "4G_RST", "4G_LED", "5G_RST",
++ "5G_PWR_ON", "USER_LED",
++ "MSATA_PRSNT";
++ };
++ };
++
++ fragment@203 {
++ target = <&pca_base>;
++ __dormant__ {
++ gpio-line-names = "BL_EN_L", "BL_BTN_EN", "BL_BTN_UP",
++ "BL_BTN_DOWN", "BUZZER_EN",
++ "4G_RST", "MSATA_PRSNT", "FAN_EN",
++ "NC", "NC", "NC", "NC",
++ "NC", "NC", "NC", "PANEL_ON_L";
++ };
++ };
++
++ fragment@205 {
++ target = <&pca_base>;
++ __dormant__ {
++ gpio-line-names = "USB_RST", "WK_RST", "4G_PWR",
++ "4G_RST", "4G_BOOT",
++ "4G_LED_REG_RED", "4G_LED_REG_GED",
++ "4G_LED_SIG_RED", "4G_LED_SIG_GED",
++ "USER_LED_RED", "USER_LED_GED",
++ "LCD_RST", "4G_PRESENT",
++ "IOB1_PRESENT", "IOB2_PRESENT";
++ };
++ };
++
++ fragment@206 {
++ target = <&pca_base>;
++ __dormant__ {
++ gpio-line-names = "4G_RST", "LED_ERR", "LED_UPDATE",
++ "LED_UDISK_BUSY", "LED_4G_EXP",
++ "LED_UDISK_DET",
++ "SW_FACTORY_RST", "SW_IP_ADDR_RST",
++ "SW_START_STOP", "SW_APP_IMPORT",
++ "SW_UDISK_REMOVE";
++ };
++ };
++
++ fragment@210 {
++ target = <&pca_base>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <6 2>; /* IRQ_TYPE_EDGE_FALLING */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq6>;
++ };
++ };
++
++ fragment@212 {
++ target = <&pca_base>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <38 2>; /* IPC2300 CM5 */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq38>;
++ };
++ };
++
++ fragment@213 {
++ target = <&pca_base>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <44 2>; /* IPC2300 CM4 */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq44>;
++ };
++ };
++
++ fragment@214 {
++ target = <&pca_base>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <7 2>; /* PLC2010 */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq7>;
++ };
++ };
++
++ fragment@310 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq6: pca_irq6 {
++ brcm,pins = <6>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ fragment@312 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq38: pca_irq38 {
++ brcm,pins = <38>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ fragment@313 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq44: pca_irq44 {
++ brcm,pins = <44>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ fragment@314 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq7: pca_irq7 {
++ brcm,pins = <7>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ fragment@400 {
++ target = <&i2c_arm>;
++ __dormant__ {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ status = "okay";
++
++ pca_exp: pca@27 {
++ compatible = "nxp,pca9535";
++ reg = <0x27>;
++ gpio-controller;
++ #gpio-cells = <2>;
++ status = "okay";
++ };
++ };
++ };
++
++ fragment@500 {
++ target = <&pca_exp>;
++ __dormant__ {
++ gpio-line-names = "DI0", "DI1", "DI2", "DI3",
++ "DI4", "DI5", "DI6", "DI7",
++ "DO0", "DO1", "DO2", "DO3",
++ "DO4", "DO5", "DO6", "DO7";
++ };
++ };
++
++ fragment@501 {
++ target = <&pca_exp>;
++ __dormant__ {
++ gpio-line-names = "DI4", "DI5", "DI6", "DI7",
++ "DO4", "DO5", "DO6", "DO7",
++ "NC", "NC", "NC", "NC",
++ "NC", "NC", "NC", "NC";
++ };
++ };
++
++ fragment@410 {
++ target = <&pca_exp>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <10 2>; /* EXP8X8Y */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq10>;
++ };
++ };
++
++ fragment@411 {
++ target = <&pca_exp>;
++ __dormant__ {
++ interrupt-parent = <&gpio>;
++ interrupts = <4 2>; /* EXP4X4Y */
++ pinctrl-names = "default";
++ pinctrl-0 = <&pca_irq4>;
++ };
++ };
++
++ fragment@510 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq10: pca_irq10 {
++ brcm,pins = <10>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ fragment@511 {
++ target = <&gpio>;
++ __dormant__ {
++ pca_irq4: pca_irq4 {
++ brcm,pins = <4>;
++ brcm,function = <0>; /* input */
++ };
++ };
++ };
++
++ __overrides__ {
++ addr = <&pca_base>, "reg:0";
++ exp_addr = <&pca_exp>, "reg:0";
++ ipc3100 = <0>, "+0+200+210+310";
++ ipc2100 = <0>, "+0+200+210+310",
++ <&i2c_frag>, "target:0=", <&i2c_csi_dsi>;
++ ipc2210 = <0>, "+0+201+210+310",
++ <&i2c_frag>, "target:0=", <&i2c_csi_dsi>;
++ ipc3210 = <0>, "+0+201+210+310";
++ ipc1200 = <0>, "+0+205",
++ <&i2c_frag>, "target:0=", <&i2c_csi_dsi>;
++ ipc3300 = <0>, "+0+203+212+312",
++ <&i2c_frag>, "target:0=", <&i2c_arm>;
++ ipc2300 = <0>, "+0+203+213+313",
++ <&i2c_frag>, "target:0=", <&i2c_arm>;
++ plc2010 = <0>, "+0+206+214+314",
++ <&i2c_frag>, "target:0=", <&i2c_arm>,
++ <&pca_base>, "reg:0=", <0x21>;
++ exp8x8y = <0>, "+400+500+410+510"; /* default CM5 */
++ exp4x4y = <0>, "+400+501+411+511",
++ <&pca_exp>, "reg:0=", <0x21>; /* default CM5 */
++ ipc3600 = <0>, "+0+200+210+310+400+500+410+510";
++ ipc2600 = <0>, "+0+200+210+310+400+500+410+510",
++ <&i2c_frag>, "target:0=", <&i2c_csi_dsi>;
++ ipc1220 = <0>, "+0+205+400+501+411+511",
++ <&i2c_frag>, "target:0=", <&i2c_csi_dsi>,
++ <&pca_exp>, "reg:0=", <0x21>;
++ };
++};
--- /dev/null
+From dc800a5027d2c4c52314396f1d1fd83cc455644d Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 8 Jul 2026 17:38:06 +0100
+Subject: [PATCH] Revert "media/hevc_d: Fix height rounding for old sand
+ formats"
+
+This reverts commit 970a68f63bf77fe173006ecc8bfdd7eef676f00e.
+---
+ drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
+@@ -144,7 +144,7 @@ static void hevc_d_prepare_dst_format(st
+ case V4L2_PIX_FMT_NV12_COL128:
+ /* Width rounds up to columns */
+ width = ALIGN(width, 128);
+- height = ALIGN(height, 16);
++ height = ALIGN(height, 8);
+
+ /* column height
+ * Accept suggested shape if at least min & < 2 * min
+@@ -158,7 +158,7 @@ static void hevc_d_prepare_dst_format(st
+ * columns
+ */
+ width = ALIGN(((width + 2) / 3), 32) * 3;
+- height = ALIGN(height, 16);
++ height = ALIGN(height, 8);
+
+ /* column height
+ * Accept suggested shape if at least min & < 2 * min
--- /dev/null
+From 33c5a591e4ea7e62aadab871a905e428cacea07f Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 8 Jul 2026 17:38:08 +0100
+Subject: [PATCH] Revert "media/hevc_d: Fix old-style bit_size handling
+ support"
+
+This reverts commit 7a41407d9a255b4e36b2e7710bbe7f3e1092fa2e.
+---
+ .../raspberrypi/hevc_dec/hevc_d_h265.c | 18 +++---------------
+ 1 file changed, 3 insertions(+), 15 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
+@@ -1578,8 +1578,6 @@ static int hevc_d_h265_setup(struct hevc
+ bool slice_temporal_mvp;
+ unsigned int ctb_size_y;
+ bool sps_changed = false;
+- /* Old (downstream only) bit size meanings */
+- bool old_bits = false;
+
+ de = dec_env_new(ctx);
+ if (!de) {
+@@ -1648,7 +1646,6 @@ static int hevc_d_h265_setup(struct hevc
+ de->chroma_stride = de->luma_stride;
+ de->frame_chroma_addr = de->frame_luma_addr +
+ (ctx->dst_fmt.height * 128);
+- old_bits = true;
+ break;
+ }
+
+@@ -1779,24 +1776,15 @@ static int hevc_d_h265_setup(struct hevc
+ for (i = 0; i != run->h265.slice_ents; ++i) {
+ const struct v4l2_ctrl_hevc_slice_params *const sh = sh0 + i;
+ const bool last_slice = i + 1 == run->h265.slice_ents;
+- unsigned int bit_size = old_bits ? sh->bit_size - 8 * sh->data_byte_offset :
+- sh->bit_size;
+- const u32 byte_size = DIV_ROUND_UP(bit_size, 8);
++ const u32 byte_size = DIV_ROUND_UP(sh->bit_size, 8);
+ unsigned int j;
+
+ s->sh = sh;
+
+- if (old_bits && sh->bit_size <= 8 * sh->data_byte_offset) {
+- v4l2_warn(&dev->v4l2_dev,
+- "data_byte_offset %d * 8 >= bits %d\n",
+- sh->data_byte_offset, sh->bit_size);
+- goto fail;
+- }
+-
+ if (sh->data_byte_offset + byte_size > run->src->planes[0].bytesused) {
+ v4l2_warn(&dev->v4l2_dev,
+ "data_byte_offset %d + bits %d (= %d bytes) > bytesused %d\n",
+- sh->data_byte_offset, bit_size, byte_size,
++ sh->data_byte_offset, sh->bit_size, byte_size,
+ run->src->planes[0].bytesused);
+ goto fail;
+ }
+@@ -1806,7 +1794,7 @@ static int hevc_d_h265_setup(struct hevc
+ * actual size of the buffer (which may well be what is used to set
+ * bit_size if the caller isn't being very pedantic).
+ */
+- s->data_len = min(bit_size / 8 + 1,
++ s->data_len = min(sh->bit_size / 8 + 1,
+ run->src->planes[0].bytesused - sh->data_byte_offset);
+
+ s->slice_qp = 26 + s->pps.init_qp_minus26 + sh->slice_qp_delta;
--- /dev/null
+From b54d5f9068406d4544b7a7fba9ef3c533b795a79 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 8 Jul 2026 17:38:10 +0100
+Subject: [PATCH] Revert "media: hevc_dec: Add in downstream single planar SAND
+ variant"
+
+This reverts commit a9731ff75336f472eca7a94e661d5bbcb1382ae2.
+---
+ .../raspberrypi/hevc_dec/hevc_d_h265.c | 79 ++------------
+ .../raspberrypi/hevc_dec/hevc_d_video.c | 101 ++++--------------
+ 2 files changed, 31 insertions(+), 149 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
+@@ -1628,40 +1628,24 @@ static int hevc_d_h265_setup(struct hevc
+ de->cmd_len = 0;
+ de->dpbno_col = ~0U;
+
+- switch (ctx->dst_fmt.pixelformat) {
+- case V4L2_PIX_FMT_NV12MT_COL128:
+- case V4L2_PIX_FMT_NV12MT_10_COL128:
+- de->luma_stride = ctx->dst_fmt.height * 128;
+- de->frame_luma_addr =
+- vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
+- de->chroma_stride = de->luma_stride / 2;
+- de->frame_chroma_addr =
+- vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
+- break;
+- case V4L2_PIX_FMT_NV12_COL128:
+- case V4L2_PIX_FMT_NV12_10_COL128:
+- de->luma_stride = ctx->dst_fmt.plane_fmt[0].bytesperline * 128;
+- de->frame_luma_addr =
+- vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
+- de->chroma_stride = de->luma_stride;
+- de->frame_chroma_addr = de->frame_luma_addr +
+- (ctx->dst_fmt.height * 128);
+- break;
+- }
+-
++ de->luma_stride = ctx->dst_fmt.height * 128;
++ de->frame_luma_addr =
++ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
++ de->chroma_stride = de->luma_stride / 2;
++ de->frame_chroma_addr =
++ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
+ de->frame_aux = NULL;
+
+ if (s->sps.bit_depth_luma_minus8 == 0) {
+- if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128 &&
+- ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_COL128) {
++ if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128) {
+ v4l2_err(&dev->v4l2_dev,
+ "Pixel format %#x != NV12MT_COL128 for 8-bit output",
+ ctx->dst_fmt.pixelformat);
+ goto fail;
+ }
+ } else {
+- if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_10_COL128 &&
+- ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_10_COL128) {
++ if (ctx->dst_fmt.pixelformat !=
++ V4L2_PIX_FMT_NV12MT_10_COL128) {
+ v4l2_err(&dev->v4l2_dev,
+ "Pixel format %#x != NV12MT_10_COL128 for 10-bit output",
+ ctx->dst_fmt.pixelformat);
+@@ -1680,42 +1664,6 @@ static int hevc_d_h265_setup(struct hevc
+ goto fail;
+ }
+
+- switch (ctx->dst_fmt.pixelformat) {
+- case V4L2_PIX_FMT_NV12MT_COL128:
+- case V4L2_PIX_FMT_NV12MT_10_COL128:
+- if (run->dst->vb2_buf.num_planes != 2) {
+- v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 2\n",
+- run->dst->vb2_buf.num_planes);
+- goto fail;
+- }
+- if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage ||
+- run->dst->planes[1].length < ctx->dst_fmt.plane_fmt[1].sizeimage) {
+- v4l2_warn(&dev->v4l2_dev,
+- "Capture planes length (%d/%d) < sizeimage (%d/%d)\n",
+- run->dst->planes[0].length,
+- run->dst->planes[1].length,
+- ctx->dst_fmt.plane_fmt[0].sizeimage,
+- ctx->dst_fmt.plane_fmt[1].sizeimage);
+- goto fail;
+- }
+- break;
+- case V4L2_PIX_FMT_NV12_COL128:
+- case V4L2_PIX_FMT_NV12_10_COL128:
+- if (run->dst->vb2_buf.num_planes != 1) {
+- v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 1\n",
+- run->dst->vb2_buf.num_planes);
+- goto fail;
+- }
+- if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage) {
+- v4l2_warn(&dev->v4l2_dev,
+- "Capture planes length (%d) < sizeimage (%d)\n",
+- run->dst->planes[0].length,
+- ctx->dst_fmt.plane_fmt[0].sizeimage);
+- goto fail;
+- }
+- break;
+- }
+-
+ /*
+ * Fill in ref planes with our address s.t. if we mess up refs
+ * somehow then we still have a valid address entry
+@@ -1885,13 +1833,8 @@ static int hevc_d_h265_setup(struct hevc
+
+ de->ref_addrs[i][0] =
+ vb2_dma_contig_plane_dma_addr(buf, 0);
+- if (ctx->dst_fmt.pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+- ctx->dst_fmt.pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128)
+- de->ref_addrs[i][1] =
+- vb2_dma_contig_plane_dma_addr(buf, 1);
+- else
+- de->ref_addrs[i][1] = de->ref_addrs[i][0] +
+- (ctx->dst_fmt.height * 128);
++ de->ref_addrs[i][1] =
++ vb2_dma_contig_plane_dma_addr(buf, 1);
+ }
+
+ /* Move DPB from temp */
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
+@@ -140,55 +140,17 @@ static void hevc_d_prepare_dst_format(st
+ bytesperline = width * 4 / 3;
+ sizeimage = bytesperline * height;
+ break;
+-
+- case V4L2_PIX_FMT_NV12_COL128:
+- /* Width rounds up to columns */
+- width = ALIGN(width, 128);
+- height = ALIGN(height, 8);
+-
+- /* column height
+- * Accept suggested shape if at least min & < 2 * min
+- */
+- bytesperline = constrain2x(bytesperline, height * 3 / 2);
+- sizeimage = bytesperline * width;
+- break;
+-
+- case V4L2_PIX_FMT_NV12_10_COL128:
+- /* width in pixels (3 pels = 4 bytes) rounded to 128 byte
+- * columns
+- */
+- width = ALIGN(((width + 2) / 3), 32) * 3;
+- height = ALIGN(height, 8);
+-
+- /* column height
+- * Accept suggested shape if at least min & < 2 * min
+- */
+- bytesperline = constrain2x(bytesperline, height * 3 / 2);
+- sizeimage = bytesperline * width * 4 / 3;
+- break;
+ }
+
+ pix_fmt->width = width;
+ pix_fmt->height = height;
+
+ pix_fmt->field = V4L2_FIELD_NONE;
+- switch (pix_fmt->pixelformat) {
+- default:
+- case V4L2_PIX_FMT_NV12MT_COL128:
+- case V4L2_PIX_FMT_NV12MT_10_COL128:
+- pix_fmt->plane_fmt[0].bytesperline = bytesperline;
+- pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+- pix_fmt->plane_fmt[1].bytesperline = bytesperline;
+- pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
+- pix_fmt->num_planes = 2;
+- break;
+- case V4L2_PIX_FMT_NV12_COL128:
+- case V4L2_PIX_FMT_NV12_10_COL128:
+- pix_fmt->plane_fmt[0].bytesperline = bytesperline;
+- pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+- pix_fmt->num_planes = 1;
+- break;
+- }
++ pix_fmt->plane_fmt[0].bytesperline = bytesperline;
++ pix_fmt->plane_fmt[0].sizeimage = sizeimage;
++ pix_fmt->plane_fmt[1].bytesperline = bytesperline;
++ pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
++ pix_fmt->num_planes = 2;
+ }
+
+ static int hevc_d_querycap(struct file *file, void *priv,
+@@ -273,31 +235,19 @@ static int hevc_d_hevc_validate_sps(cons
+ static u32 pixelformat_from_sps(const struct v4l2_ctrl_hevc_sps * const sps,
+ const int index)
+ {
+- static const u32 all_formats[] = {
+- V4L2_PIX_FMT_NV12MT_COL128,
+- V4L2_PIX_FMT_NV12MT_10_COL128,
+- V4L2_PIX_FMT_NV12_COL128,
+- V4L2_PIX_FMT_NV12_10_COL128,
+- };
+ u32 pf = 0;
+
+ if (!is_sps_set(sps) || !hevc_d_hevc_validate_sps(sps)) {
+ /* Treat this as an error? For now return both */
+-
+- if (index < ARRAY_SIZE(all_formats))
+- pf = all_formats[index];
+- } else {
+- if (index == 0) {
+- if (sps->bit_depth_luma_minus8 == 0)
+- pf = V4L2_PIX_FMT_NV12MT_COL128;
+- else if (sps->bit_depth_luma_minus8 == 2)
+- pf = V4L2_PIX_FMT_NV12MT_10_COL128;
+- } else if (index == 1) {
+- if (sps->bit_depth_luma_minus8 == 0)
+- pf = V4L2_PIX_FMT_NV12_COL128;
+- else if (sps->bit_depth_luma_minus8 == 2)
+- pf = V4L2_PIX_FMT_NV12_10_COL128;
+- }
++ if (index == 0)
++ pf = V4L2_PIX_FMT_NV12MT_COL128;
++ else if (index == 1)
++ pf = V4L2_PIX_FMT_NV12MT_10_COL128;
++ } else if (index == 0) {
++ if (sps->bit_depth_luma_minus8 == 0)
++ pf = V4L2_PIX_FMT_NV12MT_COL128;
++ else if (sps->bit_depth_luma_minus8 == 2)
++ pf = V4L2_PIX_FMT_NV12MT_10_COL128;
+ }
+
+ return pf;
+@@ -509,28 +459,17 @@ static int hevc_d_queue_setup(struct vb2
+ }
+
+ if (*nplanes) {
+- if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+- pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
+- if (*nplanes != expected_nplanes ||
+- sizes[0] < pix_fmt->plane_fmt[0].sizeimage ||
+- sizes[1] < pix_fmt->plane_fmt[1].sizeimage)
+- return -EINVAL;
+- } else {
+- if (sizes[0] < pix_fmt->plane_fmt[0].sizeimage)
+- return -EINVAL;
+- }
++ if (*nplanes != expected_nplanes ||
++ sizes[0] < pix_fmt->plane_fmt[0].sizeimage ||
++ sizes[1] < pix_fmt->plane_fmt[1].sizeimage)
++ return -EINVAL;
+ } else {
+ sizes[0] = pix_fmt->plane_fmt[0].sizeimage;
+ if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+ *nplanes = 1;
+ } else {
+- if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+- pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
+- sizes[1] = pix_fmt->plane_fmt[1].sizeimage;
+- *nplanes = 2;
+- } else {
+- *nplanes = 1;
+- }
++ sizes[1] = pix_fmt->plane_fmt[1].sizeimage;
++ *nplanes = 2;
+ }
+ }
+
--- /dev/null
+From d384034e313ec945b5a612942b7f3f868254bbd6 Mon Sep 17 00:00:00 2001
+From: John Cox <jc@kynesim.co.uk>
+Date: Mon, 9 Mar 2026 15:44:14 +0000
+Subject: [PATCH] hevc_d: Pick the fixes from the latest upstreaming reviews
+
+Does NOT include the clock and IRQ changes.
+
+Signed-off-by: John Cox <jc@kynesim.co.uk>
+---
+ .../platform/raspberrypi/hevc_dec/hevc_d.c | 8 +-
+ .../platform/raspberrypi/hevc_dec/hevc_d.h | 11 +-
+ .../raspberrypi/hevc_dec/hevc_d_h265.c | 236 +++++++++---------
+ .../platform/raspberrypi/hevc_dec/hevc_d_hw.c | 10 +-
+ .../platform/raspberrypi/hevc_dec/hevc_d_hw.h | 8 +-
+ .../raspberrypi/hevc_dec/hevc_d_video.c | 94 +++----
+ .../raspberrypi/hevc_dec/hevc_d_video.h | 1 -
+ 7 files changed, 172 insertions(+), 196 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
+@@ -11,9 +11,9 @@
+ * Copyright (C) 2018 Bootlin
+ */
+
++#include <linux/dma-mapping.h>
+ #include <linux/platform_device.h>
+ #include <linux/module.h>
+-#include <linux/of.h>
+
+ #include <media/v4l2-device.h>
+ #include <media/v4l2-ioctl.h>
+@@ -217,7 +217,7 @@ static int hevc_d_probe(struct platform_
+
+ ret = hevc_d_hw_probe(dev);
+ if (ret) {
+- dev_err(&pdev->dev, "Failed to probe hardware - %d\n", ret);
++ dev_err_probe(&pdev->dev, ret, "Failed to probe hardware\n");
+ return ret;
+ }
+
+@@ -225,7 +225,7 @@ static int hevc_d_probe(struct platform_
+
+ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+ if (ret) {
+- dev_err(&pdev->dev, "Failed to register V4L2 device\n");
++ dev_err_probe(&pdev->dev, ret, "Failed to register V4L2 device\n");
+ return ret;
+ }
+
+@@ -327,7 +327,7 @@ static struct platform_driver hevc_d_dri
+ .remove = hevc_d_remove,
+ .driver = {
+ .name = HEVC_D_NAME,
+- .of_match_table = of_match_ptr(hevc_d_dt_match),
++ .of_match_table = hevc_d_dt_match,
+ },
+ };
+ module_platform_driver(hevc_d_driver);
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
+@@ -134,12 +134,17 @@ struct hevc_d_ctx {
+ struct hevc_d_hwbuf pu_bufs[HEVC_D_P2BUF_COUNT];
+ struct hevc_d_hwbuf coeff_bufs[HEVC_D_P2BUF_COUNT];
+
+- /* Spinlock protecting aux_free */
+- spinlock_t aux_lock;
++ /* Aux structures only used in setup so no locking needed */
+ struct hevc_d_q_aux *aux_free;
+-
+ struct hevc_d_q_aux *aux_ents[HEVC_D_AUX_ENT_COUNT];
+
++ struct hevc_d_slot {
++ u32 refybase;
++ u32 refcbase;
++ u32 colbase;
++ u32 poc;
++ } slots[HEVC_D_AUX_ENT_COUNT];
++
+ unsigned int colmv_stride;
+ unsigned int colmv_picsize;
+ };
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
+@@ -11,7 +11,8 @@
+ * Copyright (C) 2018 Bootlin
+ */
+
+-#include <linux/delay.h>
++//#include <linux/delay.h>
++#include <linux/math.h>
+ #include <linux/types.h>
+
+ #include <media/videobuf2-dma-contig.h>
+@@ -136,6 +137,8 @@ static size_t next_size(const size_t x)
+ struct hevc_d_q_aux {
+ unsigned int refcount;
+ unsigned int q_index;
++ /* Is this aux entry set correctly? Only set on release or in P2 */
++ bool good;
+ struct hevc_d_q_aux *next;
+ struct hevc_d_hwbuf col;
+ };
+@@ -191,12 +194,13 @@ struct hevc_d_dec_env {
+ unsigned int luma_stride;
+ dma_addr_t frame_chroma_addr;
+ unsigned int chroma_stride;
+- dma_addr_t ref_addrs[16][2];
+- struct hevc_d_q_aux *frame_aux;
+- struct hevc_d_q_aux *col_aux;
++ u32 mvbase;
++
++ unsigned int frame_slot;
++ unsigned int ref_slots[16];
+
+- dma_addr_t pu_base_vc;
+- dma_addr_t coeff_base_vc;
++ dma_addr_t pu_base_addr;
++ dma_addr_t coeff_base_addr;
+ u32 pu_stride;
+ u32 coeff_stride;
+
+@@ -337,10 +341,6 @@ static struct hevc_d_q_aux *aux_q_alloc(
+ DMA_ATTR_FORCE_CONTIGUOUS | DMA_ATTR_NO_KERNEL_MAPPING))
+ goto fail;
+
+- /*
+- * Spinlock not required as called in P0 only and
+- * aux checks done by _new
+- */
+ aq->refcount = 1;
+ aq->q_index = q_index;
+ ctx->aux_ents[q_index] = aq;
+@@ -355,9 +355,7 @@ static struct hevc_d_q_aux *aux_q_new(st
+ const unsigned int q_index)
+ {
+ struct hevc_d_q_aux *aq;
+- unsigned long lockflags;
+
+- spin_lock_irqsave(&ctx->aux_lock, lockflags);
+ /*
+ * If we already have this allocated to a slot then use that
+ * and assume that it will all work itself out in the pipeline
+@@ -375,7 +373,6 @@ static struct hevc_d_q_aux *aux_q_new(st
+ ctx->aux_ents[q_index] = aq;
+ }
+ }
+- spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+
+ if (!aq)
+ aq = aux_q_alloc(ctx, q_index);
+@@ -384,30 +381,14 @@ static struct hevc_d_q_aux *aux_q_new(st
+ }
+
+ static struct hevc_d_q_aux *aux_q_ref_idx(struct hevc_d_ctx *const ctx,
+- const int q_index)
++ const unsigned int q_index)
+ {
+- unsigned long lockflags;
+ struct hevc_d_q_aux *aq;
+
+- spin_lock_irqsave(&ctx->aux_lock, lockflags);
+ aq = ctx->aux_ents[q_index];
+ if (aq)
+ ++aq->refcount;
+- spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+-
+- return aq;
+-}
+-
+-static struct hevc_d_q_aux *aux_q_ref(struct hevc_d_ctx *const ctx,
+- struct hevc_d_q_aux *const aq)
+-{
+- unsigned long lockflags;
+
+- if (aq) {
+- spin_lock_irqsave(&ctx->aux_lock, lockflags);
+- ++aq->refcount;
+- spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+- }
+ return aq;
+ }
+
+@@ -415,26 +396,23 @@ static void aux_q_release(struct hevc_d_
+ struct hevc_d_q_aux **const paq)
+ {
+ struct hevc_d_q_aux *const aq = *paq;
+- unsigned long lockflags;
+
+ if (!aq)
+ return;
+
+ *paq = NULL;
+
+- spin_lock_irqsave(&ctx->aux_lock, lockflags);
+ if (--aq->refcount == 0) {
+ aq->next = ctx->aux_free;
+ ctx->aux_free = aq;
+ ctx->aux_ents[aq->q_index] = NULL;
+ aq->q_index = ~0U;
++ aq->good = false;
+ }
+- spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+ }
+
+ static void aux_q_init(struct hevc_d_ctx *const ctx)
+ {
+- spin_lock_init(&ctx->aux_lock);
+ ctx->aux_free = NULL;
+ }
+
+@@ -761,9 +739,12 @@ static void pre_slice_decode(struct hevc
+ msg_slice(de, cmd_slice);
+
+ if (s->slice_temporal_mvp) {
+- const __u8 *const rpl = collocated_from_l0_flag ?
++ const u8 *const rpl = collocated_from_l0_flag ?
+ sh->ref_idx_l0 : sh->ref_idx_l1;
+- de->dpbno_col = rpl[sh->collocated_ref_idx];
++ if (sh->collocated_ref_idx >= dec->num_active_dpb_entries)
++ de->dpbno_col = rpl[0];
++ else
++ de->dpbno_col = rpl[sh->collocated_ref_idx];
+ }
+
+ /* Write reference picture descriptions */
+@@ -1453,9 +1434,6 @@ static void dec_env_delete(struct hevc_d
+ struct hevc_d_ctx * const ctx = de->ctx;
+ unsigned long lock_flags;
+
+- aux_q_release(ctx, &de->frame_aux);
+- aux_q_release(ctx, &de->col_aux);
+-
+ spin_lock_irqsave(&ctx->dec_lock, lock_flags);
+
+ de->state = HEVC_D_DECODE_END;
+@@ -1487,7 +1465,7 @@ static int dec_env_init(struct hevc_d_ct
+ ctx->dec_pool = kzalloc(sizeof(*ctx->dec_pool) * HEVC_D_DEC_ENV_COUNT,
+ GFP_KERNEL);
+ if (!ctx->dec_pool)
+- return -1;
++ return -ENOMEM;
+
+ spin_lock_init(&ctx->dec_lock);
+
+@@ -1510,7 +1488,7 @@ static int dec_env_init(struct hevc_d_ct
+
+ fail:
+ dec_env_uninit(ctx);
+- return -1;
++ return -ENOMEM;
+ }
+
+ /*
+@@ -1575,9 +1553,9 @@ static int hevc_d_h265_setup(struct hevc
+ unsigned int prev_rs;
+ unsigned int i;
+ int rv;
+- bool slice_temporal_mvp;
+ unsigned int ctb_size_y;
+ bool sps_changed = false;
++ unsigned int lkg_slot;
+
+ de = dec_env_new(ctx);
+ if (!de) {
+@@ -1588,7 +1566,7 @@ static int hevc_d_h265_setup(struct hevc
+
+ s->sh = NULL; /* Avoid use until in the slice loop */
+
+- slice_temporal_mvp = (sh0->flags &
++ s->slice_temporal_mvp = (sh0->flags &
+ V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED);
+
+ /* Frame start */
+@@ -1634,7 +1612,8 @@ static int hevc_d_h265_setup(struct hevc
+ de->chroma_stride = de->luma_stride / 2;
+ de->frame_chroma_addr =
+ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
+- de->frame_aux = NULL;
++ de->mvbase = 0;
++ de->frame_slot = run->dst->vb2_buf.index;
+
+ if (s->sps.bit_depth_luma_minus8 == 0) {
+ if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128) {
+@@ -1665,21 +1644,6 @@ static int hevc_d_h265_setup(struct hevc
+ }
+
+ /*
+- * Fill in ref planes with our address s.t. if we mess up refs
+- * somehow then we still have a valid address entry
+- */
+- for (i = 0; i != 16; ++i) {
+- de->ref_addrs[i][0] = de->frame_luma_addr;
+- de->ref_addrs[i][1] = de->frame_chroma_addr;
+- }
+-
+- /*
+- * Stash initial temporal_mvp flag
+- * This must be the same for all pic slices (7.4.7.1)
+- */
+- s->slice_temporal_mvp = slice_temporal_mvp;
+-
+- /*
+ * Need Aux ents for all (ref) DPB ents if temporal MV could
+ * be enabled for any pic
+ */
+@@ -1738,7 +1702,7 @@ static int hevc_d_h265_setup(struct hevc
+ }
+ /* BFNUM (data_len) includes the byte with rbsp_stop_one_bit which is not
+ * part of slice_segment_data but is all but certain to be in the input
+- * stream so add that when calulating the value we need, but limit to the
++ * stream so add that when calculating the value we need, but limit to the
+ * actual size of the buffer (which may well be what is used to set
+ * bit_size if the caller isn't being very pedantic).
+ */
+@@ -1805,6 +1769,13 @@ static int hevc_d_h265_setup(struct hevc
+ goto fail;
+ }
+
++ /*
++ * Find all the references. If any are missing the fill with the
++ * nearest valid entry. If nothing valid then use the buffer we
++ * are decoding into as a last resort. Ensure everything contains
++ * something valid so no checking is required in P2
++ */
++ lkg_slot = de->frame_slot;
+ for (i = 0; i < dec->num_active_dpb_entries; ++i) {
+ struct vb2_buffer *buf = vb2_find_buffer(vq, dec->dpb[i].timestamp);
+
+@@ -1817,25 +1788,30 @@ static int hevc_d_h265_setup(struct hevc
+ v4l2_warn(&dev->v4l2_dev,
+ "Missing inuse DPB ent %d, timestamp=%lld\n",
+ i, (long long)dec->dpb[i].timestamp);
++ de->ref_slots[i] = lkg_slot;
+ continue;
+ }
+
+ if (s->use_aux) {
+- int buffer_index = buf->index;
+-
+- dpb_q_aux[i] = aux_q_ref_idx(ctx, buffer_index);
++ dpb_q_aux[i] = aux_q_ref_idx(ctx, buf->index);
+ if (!dpb_q_aux[i])
+ v4l2_warn(&dev->v4l2_dev,
+ "Missing DPB AUX ent %d, timestamp=%lld, index=%d\n",
+ i, (long long)dec->dpb[i].timestamp,
+- buffer_index);
++ buf->index);
+ }
+
+- de->ref_addrs[i][0] =
+- vb2_dma_contig_plane_dma_addr(buf, 0);
+- de->ref_addrs[i][1] =
+- vb2_dma_contig_plane_dma_addr(buf, 1);
++ de->ref_slots[i] = buf->index;
++ if (lkg_slot == de->frame_slot) {
++ unsigned int j;
++
++ for (j = 0; j != i; ++j)
++ de->ref_slots[j] = buf->index;
++ }
++ lkg_slot = buf->index;
+ }
++ for (; i != 16; ++i)
++ de->ref_slots[i] = lkg_slot;
+
+ /* Move DPB from temp */
+ for (i = 0; i != V4L2_HEVC_DPB_ENTRIES_NUM_MAX; ++i) {
+@@ -1855,34 +1831,7 @@ static int hevc_d_h265_setup(struct hevc
+ goto fail;
+ }
+
+- de->frame_aux = aux_q_ref(ctx, s->frame_aux);
+- }
+-
+- if (de->dpbno_col != ~0U) {
+- if (de->dpbno_col >= dec->num_active_dpb_entries) {
+- v4l2_err(&dev->v4l2_dev,
+- "Col ref index %d >= %d\n",
+- de->dpbno_col,
+- dec->num_active_dpb_entries);
+- } else {
+- /* Standard requires that the col pic is constant for
+- * the duration of the pic (text of collocated_ref_idx
+- * in H265-2 2018 7.4.7.1)
+- */
+-
+- /* Spot the collocated ref in passing */
+- de->col_aux = aux_q_ref(ctx,
+- dpb_q_aux[de->dpbno_col]);
+-
+- if (!de->col_aux) {
+- v4l2_warn(&dev->v4l2_dev,
+- "Missing DPB ent for col\n");
+- /* Need to abort if this fails as P2 may
+- * explode on bad data
+- */
+- goto fail;
+- }
+- }
++ de->mvbase = VC_ADDR(s->frame_aux->col.addr);
+ }
+
+ de->state = HEVC_D_DECODE_PHASE1;
+@@ -1948,31 +1897,82 @@ static void phase2_cb(struct hevc_d_dev
+ phase2_done(dev, v, VB2_BUF_STATE_DONE);
+ }
+
++static void phase2_err_claimed(struct hevc_d_dev *const dev, void *v)
++{
++ struct hevc_d_dec_env *const de = v;
++ struct hevc_d_ctx *const ctx = de->ctx;
++ struct hevc_d_slot *const slot = ctx->slots + de->frame_slot;
++
++ slot->refybase = 0;
++ slot->refcbase = 0;
++ slot->colbase = 0;
++ slot->poc = de->rpi_currpoc;
++
++ phase2_done(dev, de, VB2_BUF_STATE_ERROR);
++}
++
++static const struct hevc_d_slot *find_slot(struct hevc_d_dec_env *const de,
++ const struct hevc_d_ctx *ctx,
++ unsigned int dpb_no)
++{
++ const struct hevc_d_slot *slot = ctx->slots + de->ref_slots[dpb_no];
++ const u32 poc = slot->poc;
++ u32 pocdiff = 0xffffffff;
++ unsigned int i;
++
++ if (likely(slot->refybase))
++ return slot;
++
++ slot = NULL;
++ for (i = 0; i != 16; ++i) {
++ const struct hevc_d_slot *t = ctx->slots + de->ref_slots[i];
++ u32 d = abs((s32)(poc - t->poc));
++
++ if (t->refybase && d < pocdiff) {
++ slot = t;
++ pocdiff = d;
++ }
++ }
++ return slot;
++}
++
+ static void phase2_claimed(struct hevc_d_dev *const dev, void *v)
+ {
+ struct hevc_d_dec_env *const de = v;
++ struct hevc_d_ctx *const ctx = de->ctx;
++ struct hevc_d_slot *const slot = ctx->slots + de->frame_slot;
++ u32 colbase = 0;
+ unsigned int i;
+
+- apb_write_vc_addr(dev, RPI_PURBASE, de->pu_base_vc);
++ slot->refybase = VC_ADDR(de->frame_luma_addr);
++ slot->refcbase = VC_ADDR(de->frame_chroma_addr);
++ slot->colbase = de->mvbase;
++ slot->poc = de->rpi_currpoc;
++
++ apb_write_vc_addr(dev, RPI_PURBASE, de->pu_base_addr);
+ apb_write_vc_len(dev, RPI_PURSTRIDE, de->pu_stride);
+- apb_write_vc_addr(dev, RPI_COEFFRBASE, de->coeff_base_vc);
++ apb_write_vc_addr(dev, RPI_COEFFRBASE, de->coeff_base_addr);
+ apb_write_vc_len(dev, RPI_COEFFRSTRIDE, de->coeff_stride);
+
+ apb_write_vc_addr(dev, RPI_OUTYBASE, de->frame_luma_addr);
+- apb_write_vc_addr(dev, RPI_OUTCBASE, de->frame_chroma_addr);
+ apb_write_vc_len(dev, RPI_OUTYSTRIDE, de->luma_stride);
++ apb_write_vc_addr(dev, RPI_OUTCBASE, de->frame_chroma_addr);
+ apb_write_vc_len(dev, RPI_OUTCSTRIDE, de->chroma_stride);
+
+ for (i = 0; i < 16; i++) {
+ /* Strides are in fact unused but fill in anyway */
+ unsigned int roff = i * RPI_REFREGS_SIZE;
++ const struct hevc_d_slot *ref = find_slot(de, ctx, i);
+
+- apb_write_vc_addr(dev, RPI_REFYBASE0 + roff,
+- de->ref_addrs[i][0]);
++ if (!ref)
++ goto fail;
++ if (i == de->dpbno_col)
++ colbase = ref->colbase;
++
++ apb_write(dev, RPI_REFYBASE0 + roff, ref->refybase);
+ apb_write_vc_len(dev, RPI_REFYSTRIDE0 + roff,
+ de->luma_stride);
+- apb_write_vc_addr(dev, RPI_REFCBASE0 + roff,
+- de->ref_addrs[i][1]);
++ apb_write(dev, RPI_REFCBASE0 + roff, ref->refcbase);
+ apb_write_vc_len(dev, RPI_REFCSTRIDE0 + roff,
+ de->chroma_stride);
+ }
+@@ -1982,23 +1982,21 @@ static void phase2_claimed(struct hevc_d
+ apb_write(dev, RPI_CURRPOC, de->rpi_currpoc);
+
+ /* collocated reads/writes */
+- apb_write_vc_len(dev, RPI_COLSTRIDE,
+- de->ctx->colmv_stride);
+- apb_write_vc_len(dev, RPI_MVSTRIDE,
+- de->ctx->colmv_stride);
+- apb_write_vc_addr(dev, RPI_MVBASE,
+- !de->frame_aux ? 0 : de->frame_aux->col.addr);
+- apb_write_vc_addr(dev, RPI_COLBASE,
+- !de->col_aux ? 0 : de->col_aux->col.addr);
++ if (!colbase && de->dpbno_col != ~0U)
++ goto fail;
++
++ apb_write_vc_len(dev, RPI_COLSTRIDE, ctx->colmv_stride);
++ apb_write(dev, RPI_COLBASE, colbase);
++ apb_write_vc_len(dev, RPI_MVSTRIDE, ctx->colmv_stride);
++ apb_write(dev, RPI_MVBASE, de->mvbase);
+
+ hevc_d_hw_irq_active2_irq(dev, &de->irq_ent, phase2_cb, de);
+
+ apb_write_final(dev, RPI_NUMROWS, de->pic_height_in_ctbs_y);
+-}
++ return;
+
+-static void phase2_err_claimed(struct hevc_d_dev *const dev, void *v)
+-{
+- phase2_done(dev, v, VB2_BUF_STATE_ERROR);
++fail:
++ phase2_err_claimed(dev, de);
+ }
+
+ static void phase1_claimed(struct hevc_d_dev *const dev, void *v);
+@@ -2108,11 +2106,11 @@ static void phase1_claimed(struct hevc_d
+ if (ctx->fatal_err)
+ goto fail;
+
+- de->pu_base_vc = pu_hwbuf->addr;
++ de->pu_base_addr = pu_hwbuf->addr;
+ de->pu_stride =
+ ALIGN_DOWN(pu_hwbuf->size / de->pic_height_in_ctbs_y, 64);
+
+- de->coeff_base_vc = coeff_hwbuf->addr;
++ de->coeff_base_addr = coeff_hwbuf->addr;
+ de->coeff_stride =
+ ALIGN_DOWN(coeff_hwbuf->size / de->pic_height_in_ctbs_y, 64);
+
+@@ -2120,9 +2118,9 @@ static void phase1_claimed(struct hevc_d
+ * in cb_phase1 after error detection
+ */
+
+- apb_write_vc_addr(dev, RPI_PUWBASE, de->pu_base_vc);
++ apb_write_vc_addr(dev, RPI_PUWBASE, de->pu_base_addr);
+ apb_write_vc_len(dev, RPI_PUWSTRIDE, de->pu_stride);
+- apb_write_vc_addr(dev, RPI_COEFFWBASE, de->coeff_base_vc);
++ apb_write_vc_addr(dev, RPI_COEFFWBASE, de->coeff_base_addr);
+ apb_write_vc_len(dev, RPI_COEFFWSTRIDE, de->coeff_stride);
+
+ /* Trigger command FIFO */
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
+@@ -11,16 +11,8 @@
+ * Copyright (C) 2018 Bootlin
+ */
+ #include <linux/clk.h>
+-#include <linux/component.h>
+-#include <linux/dma-mapping.h>
+ #include <linux/interrupt.h>
+-#include <linux/io.h>
+-#include <linux/of_reserved_mem.h>
+-#include <linux/of_device.h>
+-#include <linux/of_platform.h>
+ #include <linux/platform_device.h>
+-#include <linux/regmap.h>
+-#include <linux/reset.h>
+
+ #include <media/videobuf2-core.h>
+ #include <media/v4l2-mem2mem.h>
+@@ -405,7 +397,7 @@ int hevc_d_hw_probe(struct hevc_d_dev *d
+ return ret;
+
+ irq_dec = platform_get_irq(dev->pdev, 0);
+- if (irq_dec <= 0)
++ if (irq_dec < 0)
+ return irq_dec;
+ ret = devm_request_threaded_irq(dev->dev, irq_dec,
+ hevc_d_irq_irq,
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
+@@ -14,6 +14,8 @@
+ #ifndef _HEVC_D_HW_H_
+ #define _HEVC_D_HW_H_
+
++#include <linux/io.h>
++
+ struct hevc_d_hw_irq_ent {
+ struct hevc_d_hw_irq_ent *next;
+ hevc_d_irq_callback cb;
+@@ -84,6 +86,8 @@ struct hevc_d_hw_irq_ent {
+ /* Offset to get from REFYBASEn to REFYBASEn+1 */
+ #define RPI_REFREGS_SIZE 16
+
++#define VC_ADDR(x) ((u32)((x) >> 6))
++
+ /*
+ * Write a general register value
+ * Order is unimportant
+@@ -123,14 +127,14 @@ static inline void apb_write_vc_addr(con
+ const unsigned int offset,
+ const dma_addr_t a)
+ {
+- apb_write(dev, offset, (u32)(a >> 6));
++ apb_write(dev, offset, VC_ADDR(a));
+ }
+
+ static inline void apb_write_vc_addr_final(const struct hevc_d_dev * const dev,
+ const unsigned int offset,
+ const dma_addr_t a)
+ {
+- apb_write_final(dev, offset, (u32)(a >> 6));
++ apb_write_final(dev, offset, VC_ADDR(a));
+ }
+
+ static inline void apb_write_vc_len(const struct hevc_d_dev * const dev,
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
+@@ -11,7 +11,6 @@
+ * Copyright (C) 2018 Bootlin
+ */
+
+-#include <media/videobuf2-dma-contig.h>
+ #include <media/v4l2-device.h>
+ #include <media/v4l2-ioctl.h>
+ #include <media/v4l2-event.h>
+@@ -27,14 +26,6 @@ static inline struct hevc_d_ctx *hevc_d_
+ return container_of(file->private_data, struct hevc_d_ctx, fh);
+ }
+
+-/* constrain x to y,y*2 */
+-static inline unsigned int constrain2x(unsigned int x, unsigned int y)
+-{
+- return (x < y) ?
+- y :
+- (x > y * 2) ? y : x;
+-}
+-
+ size_t hevc_d_round_up_size(const size_t x)
+ {
+ /* Admit no size < 256 */
+@@ -43,7 +34,7 @@ size_t hevc_d_round_up_size(const size_t
+ return x >= (3 << n) ? 4 << n : (3 << n);
+ }
+
+-size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8)
++static u32 bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8)
+ {
+ const size_t wxh = w * h;
+ size_t bits_alloc;
+@@ -59,42 +50,39 @@ size_t hevc_d_bit_buf_size(unsigned int
+ wxh * 3 / 8;
+ /* Allow for bit depth */
+ bits_alloc += (bits_alloc * bits_minus8) / 8;
+- return hevc_d_round_up_size(bits_alloc);
++ return (u32)hevc_d_round_up_size(bits_alloc);
+ }
+
+ void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt)
+ {
+- size_t size;
+- u32 w;
+- u32 h;
+-
+- w = pix_fmt->width;
+- h = pix_fmt->height;
+- if (!w || !h) {
+- w = HEVC_D_DEFAULT_WIDTH;
+- h = HEVC_D_DEFAULT_HEIGHT;
+- }
+- if (w > HEVC_D_MAX_WIDTH)
+- w = HEVC_D_MAX_WIDTH;
+- if (h > HEVC_D_MAX_HEIGHT)
+- h = HEVC_D_MAX_HEIGHT;
+-
+- if (!pix_fmt->plane_fmt[0].sizeimage ||
+- pix_fmt->plane_fmt[0].sizeimage > SZ_32M) {
+- /* Unspecified or way too big - pick max for size */
+- size = hevc_d_bit_buf_size(w, h, 2);
+- }
++ unsigned int width = pix_fmt->width;
++ unsigned int height = pix_fmt->height;
++ unsigned int sizeimage = pix_fmt->plane_fmt[0].sizeimage;
++
++ if (!width)
++ width = HEVC_D_DEFAULT_WIDTH;
++ else
++ width = clamp(width, HEVC_D_MIN_WIDTH, HEVC_D_MAX_WIDTH);
++ if (!height)
++ height = HEVC_D_DEFAULT_HEIGHT;
++ else
++ height = clamp(height, HEVC_D_MIN_HEIGHT, HEVC_D_MAX_HEIGHT);
++
++ /* If unspecified or way too big - pick max for size */
++ if (!sizeimage || sizeimage > SZ_32M)
++ sizeimage = bit_buf_size(width, height, 2);
++
+ /* Set a minimum */
+- size = max_t(u32, SZ_4K, pix_fmt->plane_fmt[0].sizeimage);
++ sizeimage = max(SZ_4K, sizeimage);
+
+ pix_fmt->pixelformat = V4L2_PIX_FMT_HEVC_SLICE;
+- pix_fmt->width = w;
+- pix_fmt->height = h;
++ pix_fmt->width = width;
++ pix_fmt->height = height;
+ pix_fmt->num_planes = 1;
+ pix_fmt->field = V4L2_FIELD_NONE;
+ /* Zero bytes per line for encoded source. */
+ pix_fmt->plane_fmt[0].bytesperline = 0;
+- pix_fmt->plane_fmt[0].sizeimage = size;
++ pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+ }
+
+ /* Take any pix_format and make it valid */
+@@ -447,30 +435,21 @@ static int hevc_d_queue_setup(struct vb2
+ struct device *alloc_devs[])
+ {
+ struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+- struct v4l2_pix_format_mplane *pix_fmt;
+- int expected_nplanes;
++ const struct v4l2_pix_format_mplane *pix_fmt;
++ unsigned int i;
+
+- if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+- pix_fmt = &ctx->src_fmt;
+- expected_nplanes = 1;
+- } else {
+- pix_fmt = get_dst_fmt(ctx);
+- expected_nplanes = 2;
+- }
++ pix_fmt = V4L2_TYPE_IS_OUTPUT(vq->type) ? &ctx->src_fmt : get_dst_fmt(ctx);
+
+ if (*nplanes) {
+- if (*nplanes != expected_nplanes ||
+- sizes[0] < pix_fmt->plane_fmt[0].sizeimage ||
+- sizes[1] < pix_fmt->plane_fmt[1].sizeimage)
++ if (*nplanes != pix_fmt->num_planes)
+ return -EINVAL;
++ for (i = 0; i != pix_fmt->num_planes; ++i)
++ if (sizes[i] < pix_fmt->plane_fmt[i].sizeimage)
++ return -EINVAL;
+ } else {
+- sizes[0] = pix_fmt->plane_fmt[0].sizeimage;
+- if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+- *nplanes = 1;
+- } else {
+- sizes[1] = pix_fmt->plane_fmt[1].sizeimage;
+- *nplanes = 2;
+- }
++ *nplanes = pix_fmt->num_planes;
++ for (i = 0; i != pix_fmt->num_planes; ++i)
++ sizes[i] = pix_fmt->plane_fmt[i].sizeimage;
+ }
+
+ return 0;
+@@ -508,12 +487,11 @@ static int hevc_d_buf_prepare(struct vb2
+ {
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+- struct v4l2_pix_format_mplane *pix_fmt;
++ struct v4l2_pix_format_mplane *pix_fmt = &ctx->dst_fmt;
+
++ /* If OUTPUT then we don't care about actual buffer size */
+ if (V4L2_TYPE_IS_OUTPUT(vq->type))
+- pix_fmt = &ctx->src_fmt;
+- else
+- pix_fmt = &ctx->dst_fmt;
++ return 0;
+
+ if (vb2_plane_size(vb, 0) < pix_fmt->plane_fmt[0].sizeimage ||
+ vb2_plane_size(vb, 1) < pix_fmt->plane_fmt[1].sizeimage)
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
+@@ -30,7 +30,6 @@ extern const struct v4l2_ioctl_ops hevc_
+ int hevc_d_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq);
+
+-size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8);
+ size_t hevc_d_round_up_size(const size_t x);
+
+ void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt);
--- /dev/null
+From 7136f2b7245f380ac9ae2b29fd556ca9567c6e1b Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Tue, 11 Feb 2025 16:09:27 +0000
+Subject: [PATCH] media: hevc_dec: Add in downstream single planar SAND variant
+
+Upstream will take the multi-planar SAND format, but add back
+in the downstream single planar variant for backwards compatibility
+
+This includes the fixups that were present as separate patches in
+earlier versions.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ .../raspberrypi/hevc_dec/hevc_d_h265.c | 93 ++++++++++++++++---
+ .../raspberrypi/hevc_dec/hevc_d_video.c | 72 +++++++++++---
+ 2 files changed, 137 insertions(+), 28 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
+@@ -1556,6 +1556,8 @@ static int hevc_d_h265_setup(struct hevc
+ unsigned int ctb_size_y;
+ bool sps_changed = false;
+ unsigned int lkg_slot;
++ /* Old (downstream only) bit size meanings */
++ bool old_bits = false;
+
+ de = dec_env_new(ctx);
+ if (!de) {
+@@ -1606,25 +1608,43 @@ static int hevc_d_h265_setup(struct hevc
+ de->cmd_len = 0;
+ de->dpbno_col = ~0U;
+
+- de->luma_stride = ctx->dst_fmt.height * 128;
+- de->frame_luma_addr =
+- vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
+- de->chroma_stride = de->luma_stride / 2;
+- de->frame_chroma_addr =
+- vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
+- de->mvbase = 0;
+- de->frame_slot = run->dst->vb2_buf.index;
++ switch (ctx->dst_fmt.pixelformat) {
++ case V4L2_PIX_FMT_NV12MT_COL128:
++ case V4L2_PIX_FMT_NV12MT_10_COL128:
++ de->luma_stride = ctx->dst_fmt.height * 128;
++ de->frame_luma_addr =
++ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
++ de->chroma_stride = de->luma_stride / 2;
++ de->frame_chroma_addr =
++ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
++ de->mvbase = 0;
++ de->frame_slot = run->dst->vb2_buf.index;
++ break;
++ case V4L2_PIX_FMT_NV12_COL128:
++ case V4L2_PIX_FMT_NV12_10_COL128:
++ de->luma_stride = ctx->dst_fmt.plane_fmt[0].bytesperline * 128;
++ de->frame_luma_addr =
++ vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
++ de->chroma_stride = de->luma_stride;
++ de->frame_chroma_addr = de->frame_luma_addr +
++ (ctx->dst_fmt.height * 128);
++ de->mvbase = 0;
++ de->frame_slot = run->dst->vb2_buf.index;
++ old_bits = true;
++ break;
++ }
+
+ if (s->sps.bit_depth_luma_minus8 == 0) {
+- if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128) {
++ if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128 &&
++ ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_COL128) {
+ v4l2_err(&dev->v4l2_dev,
+ "Pixel format %#x != NV12MT_COL128 for 8-bit output",
+ ctx->dst_fmt.pixelformat);
+ goto fail;
+ }
+ } else {
+- if (ctx->dst_fmt.pixelformat !=
+- V4L2_PIX_FMT_NV12MT_10_COL128) {
++ if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_10_COL128 &&
++ ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_10_COL128) {
+ v4l2_err(&dev->v4l2_dev,
+ "Pixel format %#x != NV12MT_10_COL128 for 10-bit output",
+ ctx->dst_fmt.pixelformat);
+@@ -1643,6 +1663,42 @@ static int hevc_d_h265_setup(struct hevc
+ goto fail;
+ }
+
++ switch (ctx->dst_fmt.pixelformat) {
++ case V4L2_PIX_FMT_NV12MT_COL128:
++ case V4L2_PIX_FMT_NV12MT_10_COL128:
++ if (run->dst->vb2_buf.num_planes != 2) {
++ v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 2\n",
++ run->dst->vb2_buf.num_planes);
++ goto fail;
++ }
++ if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage ||
++ run->dst->planes[1].length < ctx->dst_fmt.plane_fmt[1].sizeimage) {
++ v4l2_warn(&dev->v4l2_dev,
++ "Capture planes length (%d/%d) < sizeimage (%d/%d)\n",
++ run->dst->planes[0].length,
++ run->dst->planes[1].length,
++ ctx->dst_fmt.plane_fmt[0].sizeimage,
++ ctx->dst_fmt.plane_fmt[1].sizeimage);
++ goto fail;
++ }
++ break;
++ case V4L2_PIX_FMT_NV12_COL128:
++ case V4L2_PIX_FMT_NV12_10_COL128:
++ if (run->dst->vb2_buf.num_planes != 1) {
++ v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 1\n",
++ run->dst->vb2_buf.num_planes);
++ goto fail;
++ }
++ if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage) {
++ v4l2_warn(&dev->v4l2_dev,
++ "Capture planes length (%d) < sizeimage (%d)\n",
++ run->dst->planes[0].length,
++ ctx->dst_fmt.plane_fmt[0].sizeimage);
++ goto fail;
++ }
++ break;
++ }
++
+ /*
+ * Need Aux ents for all (ref) DPB ents if temporal MV could
+ * be enabled for any pic
+@@ -1688,15 +1744,24 @@ static int hevc_d_h265_setup(struct hevc
+ for (i = 0; i != run->h265.slice_ents; ++i) {
+ const struct v4l2_ctrl_hevc_slice_params *const sh = sh0 + i;
+ const bool last_slice = i + 1 == run->h265.slice_ents;
+- const u32 byte_size = DIV_ROUND_UP(sh->bit_size, 8);
++ unsigned int bit_size = old_bits ? sh->bit_size - 8 * sh->data_byte_offset :
++ sh->bit_size;
++ const u32 byte_size = DIV_ROUND_UP(bit_size, 8);
+ unsigned int j;
+
+ s->sh = sh;
+
++ if (old_bits && sh->bit_size <= 8 * sh->data_byte_offset) {
++ v4l2_warn(&dev->v4l2_dev,
++ "data_byte_offset %d * 8 >= bits %d\n",
++ sh->data_byte_offset, sh->bit_size);
++ goto fail;
++ }
++
+ if (sh->data_byte_offset + byte_size > run->src->planes[0].bytesused) {
+ v4l2_warn(&dev->v4l2_dev,
+ "data_byte_offset %d + bits %d (= %d bytes) > bytesused %d\n",
+- sh->data_byte_offset, sh->bit_size, byte_size,
++ sh->data_byte_offset, bit_size, byte_size,
+ run->src->planes[0].bytesused);
+ goto fail;
+ }
+@@ -1706,7 +1771,7 @@ static int hevc_d_h265_setup(struct hevc
+ * actual size of the buffer (which may well be what is used to set
+ * bit_size if the caller isn't being very pedantic).
+ */
+- s->data_len = min(sh->bit_size / 8 + 1,
++ s->data_len = min(bit_size / 8 + 1,
+ run->src->planes[0].bytesused - sh->data_byte_offset);
+
+ s->slice_qp = 26 + s->pps.init_qp_minus26 + sh->slice_qp_delta;
+--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
++++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
+@@ -128,17 +128,49 @@ static void hevc_d_prepare_dst_format(st
+ bytesperline = width * 4 / 3;
+ sizeimage = bytesperline * height;
+ break;
++
++ case V4L2_PIX_FMT_NV12_COL128:
++ /* Width rounds up to columns */
++ width = ALIGN(width, 128);
++ height = ALIGN(height, 16);
++
++ bytesperline = height * 3 / 2;
++ sizeimage = bytesperline * width;
++ break;
++
++ case V4L2_PIX_FMT_NV12_10_COL128:
++ /* width in pixels (3 pels = 4 bytes) rounded to 128 byte
++ * columns
++ */
++ width = ALIGN(((width + 2) / 3), 32) * 3;
++ height = ALIGN(height, 16);
++
++ bytesperline = height * 3 / 2;
++ sizeimage = bytesperline * width * 4 / 3;
++ break;
+ }
+
+ pix_fmt->width = width;
+ pix_fmt->height = height;
+
+ pix_fmt->field = V4L2_FIELD_NONE;
+- pix_fmt->plane_fmt[0].bytesperline = bytesperline;
+- pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+- pix_fmt->plane_fmt[1].bytesperline = bytesperline;
+- pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
+- pix_fmt->num_planes = 2;
++ switch (pix_fmt->pixelformat) {
++ default:
++ case V4L2_PIX_FMT_NV12MT_COL128:
++ case V4L2_PIX_FMT_NV12MT_10_COL128:
++ pix_fmt->plane_fmt[0].bytesperline = bytesperline;
++ pix_fmt->plane_fmt[0].sizeimage = sizeimage;
++ pix_fmt->plane_fmt[1].bytesperline = bytesperline;
++ pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
++ pix_fmt->num_planes = 2;
++ break;
++ case V4L2_PIX_FMT_NV12_COL128:
++ case V4L2_PIX_FMT_NV12_10_COL128:
++ pix_fmt->plane_fmt[0].bytesperline = bytesperline;
++ pix_fmt->plane_fmt[0].sizeimage = sizeimage;
++ pix_fmt->num_planes = 1;
++ break;
++ }
+ }
+
+ static int hevc_d_querycap(struct file *file, void *priv,
+@@ -223,19 +255,31 @@ static int hevc_d_hevc_validate_sps(cons
+ static u32 pixelformat_from_sps(const struct v4l2_ctrl_hevc_sps * const sps,
+ const int index)
+ {
++ static const u32 all_formats[] = {
++ V4L2_PIX_FMT_NV12MT_COL128,
++ V4L2_PIX_FMT_NV12MT_10_COL128,
++ V4L2_PIX_FMT_NV12_COL128,
++ V4L2_PIX_FMT_NV12_10_COL128,
++ };
+ u32 pf = 0;
+
+ if (!is_sps_set(sps) || !hevc_d_hevc_validate_sps(sps)) {
+ /* Treat this as an error? For now return both */
+- if (index == 0)
+- pf = V4L2_PIX_FMT_NV12MT_COL128;
+- else if (index == 1)
+- pf = V4L2_PIX_FMT_NV12MT_10_COL128;
+- } else if (index == 0) {
+- if (sps->bit_depth_luma_minus8 == 0)
+- pf = V4L2_PIX_FMT_NV12MT_COL128;
+- else if (sps->bit_depth_luma_minus8 == 2)
+- pf = V4L2_PIX_FMT_NV12MT_10_COL128;
++
++ if (index < ARRAY_SIZE(all_formats))
++ pf = all_formats[index];
++ } else {
++ if (index == 0) {
++ if (sps->bit_depth_luma_minus8 == 0)
++ pf = V4L2_PIX_FMT_NV12MT_COL128;
++ else if (sps->bit_depth_luma_minus8 == 2)
++ pf = V4L2_PIX_FMT_NV12MT_10_COL128;
++ } else if (index == 1) {
++ if (sps->bit_depth_luma_minus8 == 0)
++ pf = V4L2_PIX_FMT_NV12_COL128;
++ else if (sps->bit_depth_luma_minus8 == 2)
++ pf = V4L2_PIX_FMT_NV12_10_COL128;
++ }
+ }
+
+ return pf;
--- /dev/null
+From 5cd170a0dfac8bce14b0259cb0bde50d846d34ae Mon Sep 17 00:00:00 2001
+From: Peter Robinson <pbrobinson@gmail.com>
+Date: Sat, 11 Jul 2026 23:26:36 +0100
+Subject: [PATCH] overlays: i2c-fan: add compatible for upstream compatibility
+
+The way upstream deals with compatibles is to add emc2305 and
+then deal with the variations in the driver as it's detectable
+so add emc2305 to the compat so we work with the upstream driver.
+
+Signed-off-by: Peter Robinson <pbrobinson@gmail.com>
+---
+ arch/arm/boot/dts/overlays/i2c-fan-overlay.dts | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
++++ b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
+@@ -17,7 +17,7 @@
+ status = "okay";
+
+ emc2301: emc2301@2f {
+- compatible = "microchip,emc2301";
++ compatible = "microchip,emc2301", "microchip,emc2305";
+ reg = <0x2f>;
+ #cooling-cells = <0x02>;
+ };
--- /dev/null
+From 10214efa1a114fc61131ff1e1240fd5a0f170803 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Sun, 12 Jul 2026 17:30:50 +0100
+Subject: [PATCH] dmaengine: dw-axi-dmac: Wait for runtime resume...
+
+...before using a channel
+
+dma_chan_alloc_chan_resources() called pm_runtime_get(), which only
+schedules the clock-enabling resume work asynchronously. If callers go
+on to configure and use the channel's registers immediately, the clocks
+may not yet be running, causing early register writes (e.g. CH_CFG) to
+be silently dropped. Later accesses succeed once the clock has come up,
+leaving the channel enabled but with an invalid configuration, and its
+completion interrupt never fires.
+
+Use pm_runtime_resume_and_get() so the clocks are guaranteed to be
+enabled before the channel is handed back to the caller.
+
+Fixes: 1fe20f1b8454 ("dmaengine: Introduce DW AXI DMAC driver")
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
++++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+@@ -603,6 +603,7 @@ static void dw_axi_dma_synchronize(struc
+ static int dma_chan_alloc_chan_resources(struct dma_chan *dchan)
+ {
+ struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
++ int ret;
+
+ /* ASSERT: channel is idle */
+ if (axi_chan_is_hw_enable(chan)) {
+@@ -622,7 +623,17 @@ static int dma_chan_alloc_chan_resources
+ }
+ dev_vdbg(dchan2dev(dchan), "%s: allocating\n", axi_chan_name(chan));
+
+- pm_runtime_get(chan->chip->dev);
++ /*
++ * Callers configure and use the channel's registers as soon as this
++ * returns, so the chip's clocks must already be running - a plain
++ * pm_runtime_get() only schedules the resume asynchronously.
++ */
++ ret = pm_runtime_resume_and_get(chan->chip->dev);
++ if (ret < 0) {
++ dma_pool_destroy(chan->desc_pool);
++ chan->desc_pool = NULL;
++ return ret;
++ }
+
+ return 0;
+ }
--- /dev/null
+From 66d46640d0a54ea21c4fffd0575e2b071333348a Mon Sep 17 00:00:00 2001
+From: Yufeng Gao <yufeng.gao@canonical.com>
+Date: Sun, 12 Jul 2026 22:03:57 +1000
+Subject: [PATCH] mmc: don't reference requests after issuing them
+
+Posted write tracking introduced in the commit below referenced the
+request in the submission path after it had been issued, racing with
+re-use of the request and potentially causing underflow of the pending
+write count. The count is signed but was compared against the unsigned
+posted write limit, so such an underflow would wrap to a large value
+and wrongly throttle further writes (hang entire system).
+
+Fixes: e6c1e862b2b8 ("mmc: restrict posted write counts for SD cards in CQ mode")
+
+Co-developed-by: Jonathan Bell <jonathan@raspberrypi.com>
+Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
+Signed-off-by: Yufeng Gao <yufeng.gao@canonical.com>
+---
+ drivers/mmc/core/queue.c | 9 ++++++---
+ include/linux/mmc/card.h | 2 +-
+ 2 files changed, 7 insertions(+), 4 deletions(-)
+
+--- a/drivers/mmc/core/queue.c
++++ b/drivers/mmc/core/queue.c
+@@ -243,6 +243,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ enum mmc_issued issued;
+ bool get_card, cqe_retune_ok;
+ blk_status_t ret;
++ bool write;
+
+ if (mmc_card_removed(mq->card)) {
+ req->rq_flags |= RQF_QUIET;
+@@ -250,6 +251,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ }
+
+ issue_type = mmc_issue_type(mq, req);
++ write = req_op(req) == REQ_OP_WRITE;
+
+ spin_lock_irq(&mq->lock);
+
+@@ -271,7 +273,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ spin_unlock_irq(&mq->lock);
+ return BLK_STS_RESOURCE;
+ }
+- if (!host->hsq_enabled && host->cqe_enabled && req_op(req) == REQ_OP_WRITE &&
++ if (!host->hsq_enabled && host->cqe_enabled && write &&
+ mq->pending_writes >= card->max_posted_writes) {
+ spin_unlock_irq(&mq->lock);
+ return BLK_STS_RESOURCE;
+@@ -292,7 +294,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ /* Parallel dispatch of requests is not supported at the moment */
+ mq->busy = true;
+
+- if (req_op(req) == REQ_OP_WRITE)
++ if (write)
+ mq->pending_writes++;
+ mq->in_flight[issue_type] += 1;
+ get_card = (mmc_tot_in_flight(mq) == 1);
+@@ -333,7 +335,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ bool put_card = false;
+
+ spin_lock_irq(&mq->lock);
+- if (req_op(req) == REQ_OP_WRITE)
++ if (write)
+ mq->pending_writes--;
+ mq->in_flight[issue_type] -= 1;
+ if (mmc_tot_in_flight(mq) == 0)
+@@ -345,6 +347,7 @@ static blk_status_t mmc_mq_queue_rq(stru
+ } else {
+ WRITE_ONCE(mq->busy, false);
+ }
++ WARN_ON_ONCE(mq->pending_writes < 0);
+
+ return ret;
+ }
+--- a/include/linux/mmc/card.h
++++ b/include/linux/mmc/card.h
+@@ -384,7 +384,7 @@ struct mmc_card {
+
+ struct workqueue_struct *complete_wq; /* Private workqueue */
+
+- unsigned int max_posted_writes; /* command queue posted write limit */
++ int max_posted_writes; /* command queue posted write limit */
+ };
+
+ static inline bool mmc_large_sector(struct mmc_card *card)
--- /dev/null
+From d57db18eddb0398652b95e7bfbffdef55ce0be71 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Fri, 3 Jul 2026 13:03:14 +0200
+Subject: [PATCH] Revert "net: macb: gate TX stall watchdog on
+ netif_carrier_ok, use warn_ratelimited"
+
+This reverts commit b2f7eec2bfff1a7f03e9c2ad81cb4d654079ba79.
+
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/net/ethernet/cadence/macb_main.c | 19 ++++---------------
+ 1 file changed, 4 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -2075,16 +2075,6 @@ static void macb_tx_stall_watchdog(struc
+ if (!netif_running(bp->dev))
+ return;
+
+- /* No carrier => no completion is possible. Skip the stall
+- * check (otherwise queue->tx_head can advance from kernel-
+- * queued packets between macb_open() and link autoneg
+- * completion while tx_tail stays unchanged, tripping a false
+- * positive), but keep the watchdog ticking so it picks up
+- * once carrier comes up.
+- */
+- if (!netif_carrier_ok(bp->dev))
+- goto reschedule;
+-
+ spin_lock_irqsave(&queue->tx_ptr_lock, flags);
+ cur_tail = queue->tx_tail;
+ cur_head = queue->tx_head;
+@@ -2094,14 +2084,13 @@ static void macb_tx_stall_watchdog(struc
+ spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
+
+ if (stalled) {
+- pr_warn_ratelimited("%s: TX stall detected on queue %u (tail=%u head=%u); re-kicking TSTART\n",
+- netdev_name(bp->dev),
+- (unsigned int)(queue - bp->queues),
+- cur_tail, cur_head);
++ netdev_warn_once(bp->dev,
++ "TX stall detected on queue %u (tail=%u head=%u); re-kicking TSTART\n",
++ (unsigned int)(queue - bp->queues),
++ cur_tail, cur_head);
+ macb_tx_restart(queue);
+ }
+
+-reschedule:
+ schedule_delayed_work(&queue->tx_stall_watchdog_work,
+ msecs_to_jiffies(MACB_TX_STALL_INTERVAL_MS));
+ }
--- /dev/null
+From 86a1889a96337a1b601e5600f3f85e21d7f13ac9 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Fri, 3 Jul 2026 13:03:14 +0200
+Subject: [PATCH] Revert "net: macb: drop destructive ISR read, use IMR barrier
+ in macb_tx_poll"
+
+This reverts commit 60fc80b89d134d720fbab7ef504f80a4ae7a998c.
+
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/net/ethernet/cadence/macb_main.c | 37 ++++++++++--------------
+ 1 file changed, 15 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -2002,31 +2002,24 @@ static int macb_tx_poll(struct napi_stru
+ if (work_done < budget && napi_complete_done(napi, work_done)) {
+ queue_writel(queue, IER, MACB_BIT(TCOMP));
+
+- /* TCOMP events that fire while masked don't re-fire when
+- * IER is re-enabled (HW errata), so check in software.
+- * macb_tx_complete_pending() inspects the descriptor at
+- * tx_tail; the rmb() in there orders prior CPU writes but
+- * does not retire in-flight peripheral DMA writes that
+- * may still be racing back to memory on PCIe-attached
+- * parts.
++ /* TCOMP events that fire while the interrupt is masked do
++ * not re-fire when IER is re-enabled. Catch this two ways
++ * to avoid losing a wakeup:
+ *
+- * Read a side-effect-free MMIO register (IMR, the
+- * read-only mask mirror) to act as a PCIe read barrier
+- * for prior peripheral DMA writes. After this read, any
+- * in-flight TX_USED descriptor update has retired and
+- * macb_tx_complete_pending() will observe it.
++ * (1) Read ISR -- catches completions the hardware flagged
++ * but that we did not see as an interrupt. The MMIO
++ * read doubles as a PCIe read barrier, flushing any
++ * in-flight descriptor TX_USED DMA writes into memory.
++ * (2) macb_tx_complete_pending() inspects the ring after
++ * that flush, catching a descriptor whose TX_USED is
++ * now visible as a result of the barrier.
+ *
+- * Note: an earlier form of this block read ISR directly
+- * to also sample a latched TCOMP bit, but that is
+- * destructive on silicon where MACB_CAPS_ISR_CLEAR_ON_WRITE
+- * is not set (raspberrypi_rp1_config among others): the
+- * read clears every set bit, and a masked check silently
+- * consumes RCOMP / ROVR / TXUBR bits the IRQ handler is
+- * expected to process in one pass. IMR is non-destructive
+- * on both read-clear and W1C silicon.
++ * This can race with the interrupt handler taking the same
++ * path if an interrupt fires just after the IER write;
++ * rescheduling NAPI in that case is harmless.
+ */
+- (void)queue_readl(queue, IMR);
+- if (macb_tx_complete_pending(queue)) {
++ if ((queue_readl(queue, ISR) & MACB_BIT(TCOMP)) ||
++ macb_tx_complete_pending(queue)) {
+ queue_writel(queue, IDR, MACB_BIT(TCOMP));
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(TCOMP));
--- /dev/null
+From 7f49766f02bbf12ff6459a775a9ccbb1e2087cc7 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Fri, 3 Jul 2026 13:03:14 +0200
+Subject: [PATCH] Revert "net: macb: add TX stall watchdog as defence-in-depth
+ safety net"
+
+This reverts commit 79dc190b12f9504d6a1bda1e497f912f24cf54d8.
+
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/net/ethernet/cadence/macb.h | 11 ----
+ drivers/net/ethernet/cadence/macb_main.c | 65 ------------------------
+ 2 files changed, 76 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb.h
++++ b/drivers/net/ethernet/cadence/macb.h
+@@ -1298,17 +1298,6 @@ struct macb_queue {
+ struct work_struct tx_error_task;
+ bool txubr_pending;
+ bool tx_pending;
+-
+- /* TX stall watchdog -- see macb_tx_stall_watchdog() in macb_main.c.
+- * tx_stall_tail_moved is set by macb_tx_complete() under tx_ptr_lock
+- * whenever tx_tail advances, and cleared by the watchdog tick on the
+- * same lock. A bool avoids the index-aliasing false-positive that a
+- * snapshot-of-tx_tail comparison would have when the ring index space
+- * happens to wrap to the same value between two ticks.
+- */
+- struct delayed_work tx_stall_watchdog_work;
+- bool tx_stall_tail_moved;
+-
+ struct napi_struct napi_tx;
+
+ dma_addr_t rx_ring_dma;
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -1505,8 +1505,6 @@ static int macb_tx_complete(struct macb_
+ packets, bytes);
+
+ queue->tx_tail = tail;
+- if (packets)
+- queue->tx_stall_tail_moved = true;
+ if (__netif_subqueue_stopped(bp->dev, queue_index) &&
+ CIRC_CNT(queue->tx_head, queue->tx_tail,
+ bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp))
+@@ -2031,63 +2029,6 @@ static int macb_tx_poll(struct napi_stru
+ return work_done;
+ }
+
+-#define MACB_TX_STALL_INTERVAL_MS 1000
+-
+-/* TX stall watchdog.
+- *
+- * Defence-in-depth against lost TCOMP interrupts. macb already has a
+- * recovery chain (tx_pending -> txubr_pending -> macb_tx_restart())
+- * that fires on TCOMP; if TCOMP itself is lost the TX ring stalls
+- * silently until something else kicks TSTART. This watchdog runs
+- * once per second per queue and calls macb_tx_restart() if the ring
+- * is non-empty and tx_tail has not advanced since the previous tick.
+- *
+- * Movement is tracked via the tx_stall_tail_moved boolean rather
+- * than by snapshotting tx_tail. Per-queue ring indices are bounded
+- * (and reused), so a snapshot comparison can false-positive when the
+- * index happens to land on the same value between two ticks under
+- * sustained load. The boolean is set by macb_tx_complete() whenever
+- * tx_tail advances and cleared by this watchdog after each tick;
+- * both writes are under tx_ptr_lock, so no atomic is required.
+- *
+- * macb_tx_restart() already checks the hardware's TBQP against the
+- * driver's head index before re-asserting TSTART, so on a healthy
+- * ring this is a no-op at the hardware level. The watchdog only
+- * adds the missing trigger.
+- */
+-static void macb_tx_stall_watchdog(struct work_struct *work)
+-{
+- struct macb_queue *queue = container_of(to_delayed_work(work),
+- struct macb_queue,
+- tx_stall_watchdog_work);
+- struct macb *bp = queue->bp;
+- unsigned int cur_tail, cur_head;
+- bool stalled = false;
+- unsigned long flags;
+-
+- if (!netif_running(bp->dev))
+- return;
+-
+- spin_lock_irqsave(&queue->tx_ptr_lock, flags);
+- cur_tail = queue->tx_tail;
+- cur_head = queue->tx_head;
+- if (cur_head != cur_tail && !queue->tx_stall_tail_moved)
+- stalled = true;
+- queue->tx_stall_tail_moved = false;
+- spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
+-
+- if (stalled) {
+- netdev_warn_once(bp->dev,
+- "TX stall detected on queue %u (tail=%u head=%u); re-kicking TSTART\n",
+- (unsigned int)(queue - bp->queues),
+- cur_tail, cur_head);
+- macb_tx_restart(queue);
+- }
+-
+- schedule_delayed_work(&queue->tx_stall_watchdog_work,
+- msecs_to_jiffies(MACB_TX_STALL_INTERVAL_MS));
+-}
+-
+ static void macb_hresp_error_task(struct work_struct *work)
+ {
+ struct macb *bp = from_work(bp, work, hresp_err_bh_work);
+@@ -3373,9 +3314,6 @@ static int macb_open(struct net_device *
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ napi_enable(&queue->napi_rx);
+ napi_enable(&queue->napi_tx);
+- queue->tx_stall_tail_moved = true;
+- schedule_delayed_work(&queue->tx_stall_watchdog_work,
+- msecs_to_jiffies(MACB_TX_STALL_INTERVAL_MS));
+ }
+
+ macb_init_hw(bp);
+@@ -3422,7 +3360,6 @@ static int macb_close(struct net_device
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ napi_disable(&queue->napi_rx);
+ napi_disable(&queue->napi_tx);
+- cancel_delayed_work_sync(&queue->tx_stall_watchdog_work);
+ netdev_tx_reset_queue(netdev_get_tx_queue(dev, q));
+ }
+
+@@ -5029,8 +4966,6 @@ static int macb_init(struct platform_dev
+ }
+
+ INIT_WORK(&queue->tx_error_task, macb_tx_error_task);
+- INIT_DELAYED_WORK(&queue->tx_stall_watchdog_work,
+- macb_tx_stall_watchdog);
+ q++;
+ }
+
--- /dev/null
+From 560624d8e8947ad3bc6c0ade6f655535ab83df71 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Fri, 3 Jul 2026 13:03:14 +0200
+Subject: [PATCH] Revert "net: macb: re-check ISR after IER re-enable in
+ macb_tx_poll"
+
+This reverts commit ff6914e97386f9a7d37468c79996dc6c34172cfb.
+
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/net/ethernet/cadence/macb_main.c | 27 +++++++++---------------
+ 1 file changed, 10 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -2000,24 +2000,17 @@ static int macb_tx_poll(struct napi_stru
+ if (work_done < budget && napi_complete_done(napi, work_done)) {
+ queue_writel(queue, IER, MACB_BIT(TCOMP));
+
+- /* TCOMP events that fire while the interrupt is masked do
+- * not re-fire when IER is re-enabled. Catch this two ways
+- * to avoid losing a wakeup:
+- *
+- * (1) Read ISR -- catches completions the hardware flagged
+- * but that we did not see as an interrupt. The MMIO
+- * read doubles as a PCIe read barrier, flushing any
+- * in-flight descriptor TX_USED DMA writes into memory.
+- * (2) macb_tx_complete_pending() inspects the ring after
+- * that flush, catching a descriptor whose TX_USED is
+- * now visible as a result of the barrier.
+- *
+- * This can race with the interrupt handler taking the same
+- * path if an interrupt fires just after the IER write;
+- * rescheduling NAPI in that case is harmless.
++ /* Packet completions only seem to propagate to raise
++ * interrupts when interrupts are enabled at the time, so if
++ * packets were sent while interrupts were disabled,
++ * they will not cause another interrupt to be generated when
++ * interrupts are re-enabled.
++ * Check for this case here to avoid losing a wakeup. This can
++ * potentially race with the interrupt handler doing the same
++ * actions if an interrupt is raised just after enabling them,
++ * but this should be harmless.
+ */
+- if ((queue_readl(queue, ISR) & MACB_BIT(TCOMP)) ||
+- macb_tx_complete_pending(queue)) {
++ if (macb_tx_complete_pending(queue)) {
+ queue_writel(queue, IDR, MACB_BIT(TCOMP));
+ if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE)
+ queue_writel(queue, ISR, MACB_BIT(TCOMP));
--- /dev/null
+From 75f7c2949bec170779266c94c72290f4218acbc3 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 13 Jul 2026 11:43:23 +0100
+Subject: [PATCH] drm/vc4: hdmi: Remove duplicate hotplug helper call
+
+vc4_hdmi_handle_hotplug() calls drm_atomic_helper_connector_hdmi_hotplug()
+twice: once from the downstream commit 7a761a6d884d ("vc4: Add jack
+detection to HDMI audio driver") and once from the upstream code it was
+rebased onto, which gained the same call in commit 34f051accedb
+("drm/vc4: hdmi: Call HDMI hotplug helper on disconnect").
+
+The helper reads the EDID over DDC, so every detect currently performs
+two full EDID reads and signals the audio jack state twice. Drop the
+duplicate and keep the call where upstream has it, after the locking
+comment.
+
+Fixes: 7a761a6d884d ("vc4: Add jack detection to HDMI audio driver")
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/vc4/vc4_hdmi.c | 6 ------
+ 1 file changed, 6 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
+@@ -366,12 +366,6 @@ static void vc4_hdmi_handle_hotplug(stru
+ int ret;
+
+ /*
+- * Needs to be called for both connects and disconnects for HDMI
+- * audio hotplug to work correctly.
+- */
+- drm_atomic_helper_connector_hdmi_hotplug(connector, status);
+-
+- /*
+ * NOTE: This function should really be called with vc4_hdmi->mutex
+ * held, but doing so results in reentrancy issues since
+ * cec_s_phys_addr() might call .adap_enable, which leads to that
--- /dev/null
+From 99c9dcd72062c9d29b171004187cf8eaacbab4b8 Mon Sep 17 00:00:00 2001
+From: j-schambacher <joerg@hifiberry.com>
+Date: Mon, 13 Jul 2026 09:11:37 +0200
+Subject: [PATCH] ASoC: rename hifiberry_studio_dac8x.c to hifiberry_studio.c
+
+The driver is going to add support for other HiFiBerry Studio-family
+cards (e.g. AES/SPDIF variants) that share the same onboard
+controller/firmware and I2C register protocol as the DAC8x cards. Rename
+the file and its DAC8x-specific identifiers to the more generic
+"hifiberry_studio" name in preparation for that, without changing any
+behaviour.
+
+No functional change.
+
+Signed-off-by: j-schambacher <joerg@hifiberry.com>
+---
+ sound/soc/bcm/Kconfig | 2 +-
+ sound/soc/bcm/Makefile | 4 +-
+ ...erry_studio_dac8x.c => hifiberry_studio.c} | 66 +++++++++----------
+ 3 files changed, 36 insertions(+), 36 deletions(-)
+ rename sound/soc/bcm/{hifiberry_studio_dac8x.c => hifiberry_studio.c} (93%)
+
+--- a/sound/soc/bcm/Kconfig
++++ b/sound/soc/bcm/Kconfig
+@@ -102,7 +102,7 @@ config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
+ help
+ Say Y or M if you want to add support for HifiBerry DSP-DAC.
+
+-config SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X
++config SND_BCM2708_SOC_HIFIBERRY_STUDIO
+ tristate "Support for HifiBerry Studio DAC8x soundcards"
+ help
+ Say Y or M if you want to add support for
+--- a/sound/soc/bcm/Makefile
++++ b/sound/soc/bcm/Makefile
+@@ -24,7 +24,7 @@ snd-soc-hifiberry-dacplushd-objs := hifi
+ snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o
+ snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o
+ snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o
+-snd-soc-hifiberry-studio-dac8x-objs := hifiberry_studio_dac8x.o
++snd-soc-hifiberry-studio-objs := hifiberry_studio.o
+ snd-soc-justboom-both-objs := justboom-both.o
+ snd-soc-justboom-dac-objs := justboom-dac.o
+ snd-soc-rpi-cirrus-objs := rpi-cirrus.o
+@@ -59,7 +59,7 @@ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_D
+ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o
+ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o
+ obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o
+-obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X) += snd-soc-hifiberry-studio-dac8x.o
++obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO) += snd-soc-hifiberry-studio.o
+ obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o
+ obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o
+ obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o
+--- a/sound/soc/bcm/hifiberry_studio_dac8x.c
++++ /dev/null
+@@ -1,938 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/*
+- * hifiberry_studio_dac8x.c -- driver for more complex
+- * multichannel soundcards with own onboard firmware.
+- *
+- * Copyright (C) 2026 HiFiBerry
+- *
+- * Author: Joerg Schambacher <joerg@hifiberry.com>
+- *
+- * This program is free software; you can redistribute it and/or
+- * modify it under the terms of the GNU General Public License
+- * version 2 as published by the Free Software Foundation.
+- *
+- * This program is distributed in the hope that it will be useful, but
+- * WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+- * General Public License for more details.
+- */
+-
+-#include <linux/module.h>
+-#include <linux/i2c.h>
+-#include <linux/regmap.h>
+-#include <linux/of_device.h>
+-#include <linux/platform_device.h>
+-#include <sound/pcm.h>
+-#include <sound/pcm_params.h>
+-#include <sound/soc.h>
+-#include <sound/tlv.h>
+-#include <linux/uuid.h>
+-#include <linux/slab.h>
+-
+-/* register definitions and firmware settings */
+-#define FIRMWARE_MAJOR 0x00
+-#define FIRMWARE_MINOR 0x01
+-#define FIRMWARE_SUBVERSION 0x02
+-#define HARDWARE_MAJOR 0x03
+-#define HARDWARE_MINOR 0x04
+-#define HARDWARE_SUBVERSION 0x05
+-#define UUID 0x10
+-#define SUPPORTED_RATES_0 0x20
+-#define SUPPORTED_RATES_1 0x21
+-#define SUPPORTED_RATES_2 0x22
+-#define SUPPORTED_RATES_3 0x23
+-#define SUPPORTED_FORMATS_0 0x24
+-#define SUPPORTED_FORMATS_1 0x25
+-#define SUPPORTED_FORMATS_2 0x26
+-#define SUPPORTED_FORMATS_3 0x27
+-#define NUM_OF_INPUT_CH 0x28
+-#define NUM_OF_OUTPUT_CH 0x29
+-#define CARD_RESET 0x2A
+-#define CURRENT_RATE 0x2B
+-#define CURRENT_FORMAT 0x2C
+-#define MAX_VOLUME 0x2D
+-#define MIN_VOLUME 0x2E
+-#define VOLUME_STP 0x2F
+-#define MAX_GAIN 0x30
+-#define MIN_GAIN 0x31
+-#define GAIN_STP 0x32
+-#define CARD_BUSY 0x33
+-#define CARD_NOERR 0x34
+-#define CARD_CLK_OPTIONS 0x35
+-#define CARD_CLOCK_MODE 0x36
+-#define DAC_STATE 0x40
+-#define DAC_CLOCK_SOURCE 0x41
+-#define DAC_SYS_CLK 0x42
+-#define DAC_SAMPLE_FORMAT 0x43
+-#define DAC_FILTER_SETTING_0 0x44
+-#define DAC_FILTER_SETTING_1 0x45
+-#define DAC_FILTER_SETTING_2 0x46
+-#define DAC_FILTER_SETTING_3 0x47
+-#define DAC_OUTPUT_MODE 0x48
+-#define MASTER_VOL 0x50
+-#define VOL_CH0 0x51
+-#define VOL_CH1 0x52
+-#define VOL_CH2 0x53
+-#define VOL_CH3 0x54
+-#define VOL_CH4 0x55
+-#define VOL_CH5 0x56
+-#define VOL_CH6 0x57
+-#define VOL_CH7 0x58
+-#define MUTE_OUTPUTS 0x59
+-#define ADC_INPUT_MODE 0x70
+-#define ADC_STATE 0x70
+-#define ADC_CLOCK_SOURCE 0x71
+-#define ADC_SYS_CLK 0x72
+-#define ADC_SAMPLE_FORMAT 0x73
+-#define ADC_FILTER_SETTING_0 0x74
+-#define ADC_FILTER_SETTING_1 0x75
+-#define ADC_FILTER_SETTING_2 0x76
+-#define ADC_FILTER_SETTING_3 0x77
+-#define ADC_CLIPPING_ATT 0x78
+-#define GAIN_CH0 0x81
+-#define GAIN_CH1 0x82
+-#define GAIN_CH2 0x83
+-#define GAIN_CH3 0x84
+-#define GAIN_CH4 0x85
+-#define GAIN_CH5 0x86
+-#define GAIN_CH6 0x87
+-#define GAIN_CH7 0x88
+-#define MUTE_INPUTS 0x89
+-#define CLOCK_CONSUMER_MODE 0x00
+-#define CLOCK_PROVIDER_MODE 0x01
+-
+-/* Mask encoding for provider frequency settings */
+-#define MASK_5512 0x00
+-#define MASK_8000 0x01
+-#define MASK_11025 0x02
+-#define MASK_16000 0x03
+-#define MASK_22050 0x04
+-#define MASK_32000 0x05
+-#define MASK_44100 0x06
+-#define MASK_48000 0x07
+-#define MASK_64000 0x08
+-#define MASK_88200 0x09
+-#define MASK_96000 0x0A
+-#define MASK_176400 0x0B
+-#define MASK_192000 0x0C
+-#define MASK_352800 0x0D
+-#define MASK_384000 0x0E
+-
+-/* Mask encoding for sample formats */
+-#define MASK_16_BIT_SF 0x01
+-#define MASK_24_BIT_SF 0x02
+-#define MASK_32_BIT_SF 0x03
+-
+-/* struct definition for easier access to firmware registers */
+-struct hb_studio_dac8x_regs_t {
+- unsigned char firmware_major;
+- unsigned char firmware_minor;
+- unsigned char firmware_subversion;
+- unsigned char hardware_major;
+- unsigned char hardware_minor;
+- unsigned char hardware_subversion;
+- unsigned char res1[10];
+- uuid_t uuid;
+- unsigned int supported_rates; // 0x20
+- unsigned int supported_formats; // 0x24
+- unsigned char num_of_input_ch; // 0x28
+- unsigned char num_of_output_ch; // 0x29
+- unsigned char card_reset; // 0x2a
+- unsigned char current_rate; // 0x2b
+- unsigned char current_format; // 0x2c
+- unsigned char max_volume; // 0x2d
+- unsigned char min_volume; // 0x2e
+- unsigned char volume_stp; // 0x2f
+- unsigned char max_gain; // 0x30
+- unsigned char min_gain; // 0x31
+- unsigned char gain_stp; // 0x32
+- unsigned char card_busy; // 0x33
+- unsigned char card_noerr; // 0x34
+- unsigned char card_clk_options; // 0x35
+- unsigned char card_clk_mode; // 0x36
+- unsigned char res3[9]; // 0x37
+- unsigned char dac_state; // 0x40
+- unsigned char dac_clock_source; // 0x41
+- unsigned char dac_sys_clk; // 0x42
+- unsigned char dac_sample_format;
+- unsigned char dac_filter_setting_0;
+- unsigned char dac_filter_setting_1;
+- unsigned char dac_filter_setting_2;
+- unsigned char dac_filter_setting_3;
+- unsigned char dac_output_mode; // 0x48
+- unsigned char res4[7]; // 0x49 - 0x4f
+- unsigned char master_vol; // 0x50
+- unsigned char vol_ch0; // 0x51
+- unsigned char vol_ch1; // 0x52
+- unsigned char vol_ch2; // 0x53
+- unsigned char vol_ch3; // 0x54
+- unsigned char vol_ch4; // 0x55
+- unsigned char vol_ch5; // 0x56
+- unsigned char vol_ch6; // 0x57
+- unsigned char vol_ch7; // 0x58
+- unsigned char mute_outputs; // 0x59
+- unsigned char res5[22]; // 0x5a- 0x6f
+- unsigned char adc_input_mode; // 0x70
+- unsigned char adc_state; // 0x70
+- unsigned char adc_clock_source; // 0x71
+- unsigned char adc_sys_clk; // 0x72
+- unsigned char adc_sample_format; // 0x73
+- unsigned char adc_filter_setting_0; // 0x74
+- unsigned char adc_filter_setting_1; // 0x75
+- unsigned char adc_filter_setting_2; // 0x76
+- unsigned char adc_filter_setting_3; // 0x77
+- unsigned char adc_clipping_att; // 0x78
+- unsigned char res6[7]; // 0x79- 0x7f
+- unsigned char res[1]; // 0x80
+- unsigned char gain_ch0; // 0x81
+- unsigned char gain_ch1; // 0x82
+- unsigned char gain_ch2; // 0x83
+- unsigned char gain_ch3; // 0x84
+- unsigned char gain_ch4; // 0x85
+- unsigned char gain_ch5; // 0x86
+- unsigned char gain_ch6; // 0x87
+- unsigned char gain_ch7; // 0x88
+- unsigned char mute_inputs; // 0x89
+- };
+-
+-
+-static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x;
+-static struct i2c_client *hb_uni_i2c_client;
+-struct hb_uni_private {
+- struct regmap *regmap;
+- uuid_t uuid;
+- unsigned int sample_bits;
+- unsigned int current_rate;
+- struct hb_studio_dac8x_regs_t card_info;
+-};
+-
+-static struct hb_uni_private *priv;
+-static bool card_is_clk_provider;
+-
+-static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg)
+-{
+- switch (reg) {
+- case CARD_BUSY:
+- case CARD_RESET:
+- case DAC_STATE:
+- case DAC_CLOCK_SOURCE:
+- case DAC_SYS_CLK:
+- case MASTER_VOL:
+- case VOL_CH0:
+- case VOL_CH1:
+- case VOL_CH2:
+- case VOL_CH3:
+- case VOL_CH4:
+- case VOL_CH5:
+- case VOL_CH6:
+- case VOL_CH7:
+- case GAIN_CH0:
+- case GAIN_CH1:
+- case GAIN_CH2:
+- case GAIN_CH3:
+- case GAIN_CH4:
+- case GAIN_CH5:
+- case GAIN_CH6:
+- case GAIN_CH7:
+- return true;
+- default:
+- return false;
+- }
+-}
+-
+-static bool hb_uni_readable_reg(struct device *dev, unsigned int reg)
+-{
+- switch (reg) {
+- case FIRMWARE_MAJOR:
+- case FIRMWARE_MINOR:
+- case FIRMWARE_SUBVERSION:
+- case HARDWARE_MAJOR:
+- case HARDWARE_MINOR:
+- case HARDWARE_SUBVERSION:
+- case NUM_OF_INPUT_CH:
+- case NUM_OF_OUTPUT_CH:
+- case SUPPORTED_RATES_0:
+- case SUPPORTED_RATES_1:
+- case SUPPORTED_RATES_2:
+- case SUPPORTED_RATES_3:
+- case SUPPORTED_FORMATS_0:
+- case SUPPORTED_FORMATS_1:
+- case SUPPORTED_FORMATS_2:
+- case SUPPORTED_FORMATS_3:
+- case UUID:
+- case DAC_STATE:
+- case CARD_BUSY:
+- case CARD_RESET:
+- case CARD_CLOCK_MODE:
+- case DAC_CLOCK_SOURCE:
+- case DAC_SYS_CLK:
+- case DAC_SAMPLE_FORMAT:
+- case DAC_FILTER_SETTING_0:
+- case DAC_FILTER_SETTING_1:
+- case DAC_FILTER_SETTING_2:
+- case DAC_FILTER_SETTING_3:
+- case DAC_OUTPUT_MODE:
+- case GAIN_CH0:
+- case GAIN_CH1:
+- case GAIN_CH2:
+- case GAIN_CH3:
+- case GAIN_CH4:
+- case GAIN_CH5:
+- case GAIN_CH6:
+- case GAIN_CH7:
+- case ADC_CLIPPING_ATT:
+- return true;
+- default:
+- return reg < 0xff;
+- }
+-}
+-
+-static const struct regmap_config hb_uni_regmap = {
+- .reg_bits = 8,
+- .val_bits = 8,
+- .max_register = 0xff,
+- .cache_type = REGCACHE_RBTREE,
+- .volatile_reg = hb_uni_volatile_reg,
+- .readable_reg = hb_uni_readable_reg,
+-};
+-
+-static const DECLARE_TLV_DB_MINMAX(adc_att_tlv, -600, -300);
+-static const DECLARE_TLV_DB_MINMAX(gain_tlv, -1200, 4000);
+-static const DECLARE_TLV_DB_MINMAX(volume_tlv, -10300, 2400);
+-static const DECLARE_TLV_DB_MINMAX(spkr_tlv, -10300, 0);
+-static const char * const pll_lock_texts[] = {"unlocked", "locked"};
+-static const char * const mute_texts[] = {"unmuted", "muted"};
+-static const char * const dac_filter_texts[] = {
+- "FIR w/ De-Emph.",
+- "Low Latency IIR w/ De-Emph.",
+- "High Att. w/ De-Emph.",
+- "Ringingless Low Latency FIR w/o Deemph.",
+- };
+-static const char * const adc_att_texts[] = {
+- "Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB",
+- };
+-
+-struct hb_uni_vol_control_single {
+- unsigned int reg;
+- unsigned int shift;
+- int min;
+- int max;
+- bool invert;
+- const unsigned int *tlv;
+-};
+-
+-static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_info *uinfo)
+-{
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+-
+- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+- uinfo->count = 1; // mono
+- uinfo->value.integer.min = ctl->min;
+- uinfo->value.integer.max = ctl->max;
+- uinfo->value.integer.step = 1;
+- return 0;
+-}
+-
+-static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
+-{
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+- unsigned int val;
+-
+- regmap_read(priv->regmap, ctl->reg, &val);
+- val = (val >> ctl->shift) & 0xff;
+- if (ctl->invert)
+- val = ctl->max - val;
+- ucontrol->value.integer.value[0] = val;
+- return 0;
+-}
+-
+-static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
+-{
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+- unsigned int val = ucontrol->value.integer.value[0];
+- unsigned int new;
+-
+- if (ctl->invert)
+- val = ctl->max - val;
+- new = (val & 0xff) << ctl->shift;
+- regmap_write(priv->regmap, ctl->reg, new);
+- return 0;
+-}
+-
+-struct hb_uni_enum_control {
+- unsigned int reg;
+- unsigned int shift;
+- unsigned int mask;
+- const char * const *texts;
+- unsigned int items;
+-};
+-
+-static int hb_uni_enum_info(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_info *uinfo)
+-{
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+-
+- uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+- uinfo->count = 1;
+- uinfo->value.enumerated.items = ctl->items;
+-
+- if (uinfo->value.enumerated.item >= ctl->items)
+- uinfo->value.enumerated.item = ctl->items - 1;
+-
+- strscpy(uinfo->value.enumerated.name,
+- ctl->texts[uinfo->value.enumerated.item],
+- sizeof(uinfo->value.enumerated.name) - 1);
+-
+- return 0;
+-}
+-
+-static int hb_uni_enum_get(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
+-{
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+- unsigned int val;
+-
+- regmap_read(priv->regmap, ctl->reg, &val);
+-
+- val = (val >> ctl->shift) & ctl->mask;
+- if (val >= ctl->items)
+- val = 0;
+-
+- ucontrol->value.enumerated.item[0] = val;
+-
+- return 0;
+-}
+-
+-static int hb_uni_enum_put(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
+-{
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+- unsigned int val = ucontrol->value.enumerated.item[0];
+-
+- if (val >= ctl->items)
+- return -EINVAL;
+-
+- regmap_update_bits(priv->regmap, ctl->reg,
+- ctl->mask << ctl->shift,
+- (val & ctl->mask) << ctl->shift);
+-
+- return 0;
+-}
+-
+-#define VOL_CTL_SINGLE(kname, controls, ktlv) {\
+- .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+- .name = kname, \
+- .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+- SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+- .tlv.p = ktlv, \
+- .info = hb_uni_vol_info_single, \
+- .get = hb_uni_vol_get_single, \
+- .put = hb_uni_vol_put_single, \
+- .private_value = (unsigned long)&controls, }
+-
+-#define ENUM_CTL_SINGLE(kname, controls) {\
+- .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+- .name = kname, \
+- .info = hb_uni_enum_info, \
+- .get = hb_uni_enum_get, \
+- .put = hb_uni_enum_put, \
+- .private_value = (unsigned long)&controls, }
+-
+-#define ENUM_CTL_SINGLE_RO(kname, controls) {\
+- .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+- .name = kname, \
+- .info = hb_uni_enum_info, \
+- .get = hb_uni_enum_get, \
+- .put = NULL, \
+- .private_value = (unsigned long)&controls, }
+-
+-static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = {
+- { MASTER_VOL, 0, 0, 254, true, volume_tlv },
+- { VOL_CH0, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH1, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH2, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH3, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH4, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH5, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH6, 0, 0, 206, true, spkr_tlv },
+- { VOL_CH7, 0, 0, 206, true, spkr_tlv },
+-};
+-
+-static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = {
+- { GAIN_CH0, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH1, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH2, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH3, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH4, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH5, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH6, 0, 0, 104, false, gain_tlv },
+- { GAIN_CH7, 0, 0, 104, false, gain_tlv },
+-};
+-
+-static const struct snd_kcontrol_new hb_uni_play_controls_single[] = {
+- VOL_CTL_SINGLE("Master Playback Volume", hb_uni_vol_ctls_single[0], volume_tlv),
+- VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_uni_vol_ctls_single[1], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_uni_vol_ctls_single[2], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_uni_vol_ctls_single[3], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_uni_vol_ctls_single[4], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_uni_vol_ctls_single[5], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_uni_vol_ctls_single[6], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_uni_vol_ctls_single[7], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_uni_vol_ctls_single[8], spkr_tlv),
+-};
+-
+-static const struct snd_kcontrol_new hb_uni_rec_controls_single[] = {
+- VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_uni_gain_ctls_single[0], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_uni_gain_ctls_single[1], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_uni_gain_ctls_single[2], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_uni_gain_ctls_single[3], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_uni_gain_ctls_single[4], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_uni_gain_ctls_single[5], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_uni_gain_ctls_single[6], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_uni_gain_ctls_single[7], gain_tlv),
+-};
+-
+-static const struct hb_uni_enum_control hb_uni_play_enum_ctls[] = {
+- { DAC_STATE, 0, 0x1, pll_lock_texts, ARRAY_SIZE(pll_lock_texts) },
+- { DAC_FILTER_SETTING_0, 0, 0x03, dac_filter_texts, ARRAY_SIZE(dac_filter_texts) },
+- { MUTE_OUTPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) },
+-};
+-
+-static const struct hb_uni_enum_control hb_uni_rec_enum_ctls[] = {
+- { ADC_CLIPPING_ATT, 0, 0x7, adc_att_texts, ARRAY_SIZE(adc_att_texts) },
+-};
+-
+-static const struct snd_kcontrol_new hb_uni_gen_controls_single[] = {
+- ENUM_CTL_SINGLE("DAC Filter", hb_uni_play_enum_ctls[1]),
+- ENUM_CTL_SINGLE("DAC Mute", hb_uni_play_enum_ctls[2]),
+-};
+-
+-static const struct snd_kcontrol_new adc_controls_single[] = {
+- ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]),
+-};
+-
+-static int snd_rpi_hifiberry_studio_dac8x_hw_params(
+- struct snd_pcm_substream *substream,
+- struct snd_pcm_hw_params *params)
+-{
+- struct snd_soc_pcm_runtime *rtd = substream->private_data;
+- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+- struct device *dev = rtd->dev;
+- unsigned char tmp;
+- int trials;
+- int busy;
+- int err;
+-
+- priv->sample_bits = snd_pcm_format_width(params_format(params));
+- priv->sample_bits = priv->sample_bits <= 16 ? 16 : 32;
+-
+- priv->current_rate = params_rate(params);
+- dev_info(dev, "using %ibits @ %isps\n",
+- priv->sample_bits, priv->current_rate);
+-
+- /* write requested samplerate and word length back to card */
+- switch (priv->current_rate) {
+- case 5512:
+- tmp = MASK_5512;
+- break;
+- case 8000:
+- tmp = MASK_8000;
+- break;
+- case 11025:
+- tmp = MASK_11025;
+- break;
+- case 16000:
+- tmp = MASK_16000;
+- break;
+- case 22050:
+- tmp = MASK_22050;
+- break;
+- case 32000:
+- tmp = MASK_32000;
+- break;
+- case 44100:
+- tmp = MASK_44100;
+- break;
+- case 88200:
+- tmp = MASK_88200;
+- break;
+- case 176400:
+- tmp = MASK_176400;
+- break;
+- case 352800:
+- tmp = MASK_352800;
+- break;
+- case 48000:
+- tmp = MASK_48000;
+- break;
+- case 96000:
+- tmp = MASK_96000;
+- break;
+- case 192000:
+- tmp = MASK_192000;
+- break;
+- case 384000:
+- tmp = MASK_384000;
+- break;
+- default:
+- dev_info(dev, "rate not supported (%u)\n", priv->current_rate);
+- return -EINVAL;
+- }
+-
+- err = regmap_write(priv->regmap, CURRENT_RATE, tmp);
+- if (err < 0)
+- return err;
+-
+- switch (priv->sample_bits) {
+- case 16:
+- tmp = MASK_16_BIT_SF;
+- break;
+- case 24:
+- tmp = MASK_24_BIT_SF;
+- break;
+- case 32:
+- tmp = MASK_32_BIT_SF;
+- break;
+- default:
+- dev_info(dev, "word length not supported (%u)\n",
+- priv->sample_bits);
+- return -EINVAL;
+- }
+- err = regmap_write(priv->regmap, CURRENT_FORMAT, tmp);
+- if (err < 0)
+- return err;
+-
+- /* If card provides clocks wait max. ~40ms for PLL */
+- if (card_is_clk_provider) {
+- /* trigger card to set new rate and format */
+- err = regmap_write(priv->regmap, CARD_CLOCK_MODE, 0x02);
+- if (err < 0)
+- return err;
+- trials = 10;
+- do {
+- usleep_range(3000, 4000);
+- regmap_read(priv->regmap, CARD_BUSY, &busy);
+- } while (busy && --trials);
+- if (!trials) {
+- dev_err(dev, "Card is unable to set clocks\n");
+- return -EINVAL;
+- }
+- }
+- /* always run with 64bit frames */
+- return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+-}
+-
+-static const struct snd_soc_ops snd_rpi_hifiberry_studio_dac8x_ops = {
+- .hw_params = snd_rpi_hifiberry_studio_dac8x_hw_params,
+-};
+-
+-SND_SOC_DAILINK_DEFS(hifiberry_studio_dac8x,
+- DAILINK_COMP_ARRAY(COMP_EMPTY()),
+- DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+- DAILINK_COMP_ARRAY(COMP_EMPTY()));
+-
+-static int hifiberry_studio_dac8x_init(struct snd_soc_pcm_runtime *rtd)
+-{
+- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+- struct snd_soc_card *card = rtd->card;
+-
+- /* Configure playback */
+- codec_dai->driver->playback.channels_max =
+- priv->card_info.num_of_output_ch;
+- codec_dai->driver->playback.rates = priv->card_info.supported_rates;
+- codec_dai->driver->playback.formats = priv->card_info.supported_formats;
+-
+- if (priv->card_info.num_of_input_ch) {
+- struct snd_soc_dai_link *dai = rtd->dai_link;
+-
+- dev_info(card->dev, "inputs detected: capture enabled\n");
+- codec_dai->driver->symmetric_rate = 1;
+- codec_dai->driver->symmetric_sample_bits = 1;
+- codec_dai->driver->capture.formats =
+- priv->card_info.supported_formats;
+- codec_dai->driver->capture.rates =
+- priv->card_info.supported_rates;
+- codec_dai->driver->capture.channels_max =
+- priv->card_info.num_of_input_ch;
+- dai->name = "HiFiBerry Studio DAC8x-ADC8x";
+- dai->stream_name = "HiFiBerry Studio HiFi";
+- } else {
+- rtd->dai_link->playback_only = 1; // Disable capture
+- }
+-
+- if ((priv->card_info.card_clk_options & 0x02) && card_is_clk_provider) {
+- struct snd_soc_dai_link *dai = rtd->dai_link;
+-
+- dai->stream_name = "HiFiBerry Studio Pro HiFi";
+- dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+- | SND_SOC_DAIFMT_CBP_CFP;
+- }
+- dev_info(card->dev,
+- "HiFiBerry Studio DAC8x successfully initialized\n");
+-
+- return 0;
+-}
+-
+-static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dac8x_dai[] = {
+- {
+- .name = "HiFiBerry Studio DAC8x",
+- .stream_name = "HifiBerry Studio HiFi",
+- .dai_fmt = SND_SOC_DAIFMT_I2S |
+- SND_SOC_DAIFMT_NB_NF |
+- SND_SOC_DAIFMT_CBC_CFC,
+- .init = hifiberry_studio_dac8x_init,
+- .ops = &snd_rpi_hifiberry_studio_dac8x_ops,
+- SND_SOC_DAILINK_REG(hifiberry_studio_dac8x),
+- },
+-};
+-
+-/* audio machine driver */
+-static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x = {
+- .name = "Hifiberry Studio DAC8x",
+- .driver_name = "HifiberryStudio",
+- .owner = THIS_MODULE,
+- .dai_link = snd_rpi_hifiberry_studio_dac8x_dai,
+- .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dac8x_dai),
+-};
+-
+-static int hb_uni_read_card_info(struct platform_device *pdev)
+-{
+- int ret;
+-
+- /* read basic card info */
+- ret = regmap_bulk_read(priv->regmap, 0x00, &priv->card_info, 0x06);
+- if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+- return ret;
+- }
+-
+- dev_info(&pdev->dev, "hardware V%d.%d.%d\n",
+- priv->card_info.hardware_major,
+- priv->card_info.hardware_minor,
+- priv->card_info.hardware_subversion
+- );
+-
+- dev_info(&pdev->dev, "firmware V%d.%d.%d\n",
+- priv->card_info.firmware_major,
+- priv->card_info.firmware_minor,
+- priv->card_info.firmware_subversion
+- );
+-
+- /* read card capabilities */
+- ret = regmap_bulk_read(priv->regmap, UUID, &priv->card_info.uuid, 0x20);
+- if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+- return ret;
+- }
+-
+- dev_info(&pdev->dev, "UUID: %*phN\n",
+- (int)sizeof(priv->card_info.uuid.b), priv->card_info.uuid.b);
+- dev_info(&pdev->dev, "%i output channels reported\n",
+- priv->card_info.num_of_output_ch);
+- dev_dbg(&pdev->dev, "supported rates %08x\n",
+- priv->card_info.supported_rates);
+- dev_dbg(&pdev->dev, "supported formats %08x\n",
+- priv->card_info.supported_formats);
+-
+- if (priv->card_info.num_of_output_ch > 8 ||
+- priv->card_info.num_of_input_ch > 8) {
+- dev_err(&pdev->dev, "Maximum of 8 channels exceeded!\n");
+- return -EINVAL;
+- }
+-
+- if (priv->card_info.num_of_input_ch > 0) {
+- dev_info(&pdev->dev,
+- "Inputs detected: %u channels\n",
+- priv->card_info.num_of_input_ch);
+- } else {
+- dev_info(&pdev->dev, "No inputs present, playback only\n");
+- }
+-
+- ret = regmap_bulk_read(priv->regmap, CARD_BUSY,
+- &priv->card_info.card_busy, 20);
+- if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+- return ret;
+- }
+-
+- if (card_is_clk_provider) {
+- if (priv->card_info.card_clk_options & 0x02) {
+- dev_info(&pdev->dev, "Card provides i2s clocks\n");
+- } else {
+- dev_err(&pdev->dev,
+- "Card cannot provide i2s clocks\n");
+- return -EINVAL;
+- }
+- } else {
+- if (priv->card_info.card_clk_options == 0x02) {
+- dev_err(&pdev->dev,
+- "Card cannot run as i2s clock consumer\n");
+- return -EINVAL;
+- }
+- }
+-
+- switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) {
+- case 0x74e7ae95:
+- if (card_is_clk_provider)
+- snd_rpi_hifiberry_studio_dac8x.name =
+- "HiFiBerry Studio DAC8x Pro";
+- else
+- snd_rpi_hifiberry_studio_dac8x.name =
+- "HiFiBerry Studio DAC8x";
+- break;
+- default:
+- break;
+- }
+-
+-
+- regcache_cache_only(priv->regmap, true);
+- ret = regmap_bulk_read(priv->regmap, MASTER_VOL,
+- &priv->card_info.master_vol, MUTE_OUTPUTS - MASTER_VOL);
+- regcache_cache_only(priv->regmap, false);
+-
+- return 0;
+-}
+-
+-static int hb_uni_add_card_controls(struct platform_device *pdev)
+-{
+- int ret;
+-
+- ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+- hb_uni_gen_controls_single,
+- ARRAY_SIZE(hb_uni_gen_controls_single));
+- if (ret < 0) {
+- dev_err(&pdev->dev,
+- "snd_soc_add_card_controls() failed: %d\n", ret);
+- return ret;
+- }
+- ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+- hb_uni_play_controls_single,
+- ARRAY_SIZE(hb_uni_play_controls_single) / 9 *
+- (priv->card_info.num_of_output_ch + 1));
+- if (ret < 0) {
+- dev_err(&pdev->dev,
+- "snd_soc_add_card_controls() failed: %d\n", ret);
+- return ret;
+- }
+-
+- /* add optional ADC controls if inputs detected */
+- if (priv->card_info.num_of_input_ch > 0) {
+- ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+- hb_uni_rec_controls_single,
+- ARRAY_SIZE(hb_uni_rec_controls_single) / 8 *
+- priv->card_info.num_of_input_ch);
+- if (ret < 0) {
+- dev_err(&pdev->dev,
+- "snd_soc_add_card_controls() failed: %d\n", ret);
+- }
+- ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+- adc_controls_single,
+- ARRAY_SIZE(adc_controls_single));
+- if (ret < 0) {
+- dev_err(&pdev->dev,
+- "snd_soc_add_card_controls() failed: %d\n", ret);
+- }
+- }
+- return ret;
+-}
+-
+-static int hb_controller_probe(struct platform_device *pdev)
+-{
+- struct i2c_adapter *adap = i2c_get_adapter(1);
+- struct device_node *np = pdev->dev.of_node;
+- int ret;
+-
+- if (!adap)
+- return -EPROBE_DEFER; /* I2C module not yet available */
+-
+- struct i2c_board_info info = {
+- I2C_BOARD_INFO("hb_controller", 0x10),
+- };
+-
+- hb_uni_i2c_client = i2c_new_client_device(adap, &info);
+- if (IS_ERR(hb_uni_i2c_client))
+- return PTR_ERR(hb_uni_i2c_client);
+-
+- priv = devm_kzalloc(&hb_uni_i2c_client->dev, sizeof(*priv), GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+-
+- priv->regmap = devm_regmap_init_i2c(hb_uni_i2c_client, &hb_uni_regmap);
+- if (IS_ERR(priv->regmap))
+- return dev_err_probe(&hb_uni_i2c_client->dev,
+- PTR_ERR(priv->regmap), "Failed to init regmap\n");
+-
+- if (np && of_property_read_bool(np, "clk-provider"))
+- card_is_clk_provider = true;
+-
+- ret = hb_uni_read_card_info(pdev);
+- if (ret < 0) {
+- dev_err(&hb_uni_i2c_client->dev,
+- "Failed to read card info or wrong configuration!\n");
+- }
+-
+- return ret;
+-};
+-
+-static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev)
+-{
+- int ret = 0;
+-
+- /* probe for controller */
+- ret = hb_controller_probe(pdev);
+- if (ret < 0)
+- return ret;
+-
+- snd_rpi_hifiberry_studio_dac8x.dev = &pdev->dev;
+-
+- if (pdev->dev.of_node) {
+- struct device_node *i2s_node;
+- struct snd_soc_dai_link *dai;
+-
+- dai = &snd_rpi_hifiberry_studio_dac8x_dai[0];
+- i2s_node = of_parse_phandle(pdev->dev.of_node,
+- "i2s-controller", 0);
+-
+- if (i2s_node) {
+- dai->cpus->dai_name = NULL;
+- dai->cpus->of_node = i2s_node;
+- dai->platforms->name = NULL;
+- dai->platforms->of_node = i2s_node;
+- }
+- }
+-
+- ret = devm_snd_soc_register_card(&pdev->dev,
+- &snd_rpi_hifiberry_studio_dac8x);
+- if (ret && ret != -EPROBE_DEFER)
+- dev_err(&pdev->dev,
+- "devm_snd_soc_register_card() failed: %d\n", ret);
+-
+- /* as we do not have components use card-controls */
+- ret = hb_uni_add_card_controls(pdev);
+-
+- return ret;
+-}
+-
+-static const struct of_device_id snd_rpi_hifiberry_studio_dac8x_of_match[] = {
+- { .compatible = "hifiberry,hifiberry-studio-dac8x", },
+- {},
+-};
+-MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_dac8x_of_match);
+-
+-static struct platform_driver snd_rpi_hifiberry_studio_dac8x_driver = {
+- .driver = {
+- .name = "snd-rpi-hifiberry-studio-dac8x",
+- .owner = THIS_MODULE,
+- .of_match_table = snd_rpi_hifiberry_studio_dac8x_of_match,
+- },
+- .probe = snd_rpi_hifiberry_studio_dac8x_probe,
+-};
+-
+-module_platform_driver(snd_rpi_hifiberry_studio_dac8x_driver);
+-
+-MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+-MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver");
+-MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/sound/soc/bcm/hifiberry_studio.c
+@@ -0,0 +1,938 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * hifiberry_studio.c -- driver for more complex
++ * multichannel soundcards with own onboard firmware.
++ *
++ * Copyright (C) 2026 HiFiBerry
++ *
++ * Author: Joerg Schambacher <joerg@hifiberry.com>
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful, but
++ * WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/i2c.h>
++#include <linux/regmap.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include <sound/pcm.h>
++#include <sound/pcm_params.h>
++#include <sound/soc.h>
++#include <sound/tlv.h>
++#include <linux/uuid.h>
++#include <linux/slab.h>
++
++/* register definitions and firmware settings */
++#define FIRMWARE_MAJOR 0x00
++#define FIRMWARE_MINOR 0x01
++#define FIRMWARE_SUBVERSION 0x02
++#define HARDWARE_MAJOR 0x03
++#define HARDWARE_MINOR 0x04
++#define HARDWARE_SUBVERSION 0x05
++#define UUID 0x10
++#define SUPPORTED_RATES_0 0x20
++#define SUPPORTED_RATES_1 0x21
++#define SUPPORTED_RATES_2 0x22
++#define SUPPORTED_RATES_3 0x23
++#define SUPPORTED_FORMATS_0 0x24
++#define SUPPORTED_FORMATS_1 0x25
++#define SUPPORTED_FORMATS_2 0x26
++#define SUPPORTED_FORMATS_3 0x27
++#define NUM_OF_INPUT_CH 0x28
++#define NUM_OF_OUTPUT_CH 0x29
++#define CARD_RESET 0x2A
++#define CURRENT_RATE 0x2B
++#define CURRENT_FORMAT 0x2C
++#define MAX_VOLUME 0x2D
++#define MIN_VOLUME 0x2E
++#define VOLUME_STP 0x2F
++#define MAX_GAIN 0x30
++#define MIN_GAIN 0x31
++#define GAIN_STP 0x32
++#define CARD_BUSY 0x33
++#define CARD_NOERR 0x34
++#define CARD_CLK_OPTIONS 0x35
++#define CARD_CLOCK_MODE 0x36
++#define DAC_STATE 0x40
++#define DAC_CLOCK_SOURCE 0x41
++#define DAC_SYS_CLK 0x42
++#define DAC_SAMPLE_FORMAT 0x43
++#define DAC_FILTER_SETTING_0 0x44
++#define DAC_FILTER_SETTING_1 0x45
++#define DAC_FILTER_SETTING_2 0x46
++#define DAC_FILTER_SETTING_3 0x47
++#define DAC_OUTPUT_MODE 0x48
++#define MASTER_VOL 0x50
++#define VOL_CH0 0x51
++#define VOL_CH1 0x52
++#define VOL_CH2 0x53
++#define VOL_CH3 0x54
++#define VOL_CH4 0x55
++#define VOL_CH5 0x56
++#define VOL_CH6 0x57
++#define VOL_CH7 0x58
++#define MUTE_OUTPUTS 0x59
++#define ADC_INPUT_MODE 0x70
++#define ADC_STATE 0x70
++#define ADC_CLOCK_SOURCE 0x71
++#define ADC_SYS_CLK 0x72
++#define ADC_SAMPLE_FORMAT 0x73
++#define ADC_FILTER_SETTING_0 0x74
++#define ADC_FILTER_SETTING_1 0x75
++#define ADC_FILTER_SETTING_2 0x76
++#define ADC_FILTER_SETTING_3 0x77
++#define ADC_CLIPPING_ATT 0x78
++#define GAIN_CH0 0x81
++#define GAIN_CH1 0x82
++#define GAIN_CH2 0x83
++#define GAIN_CH3 0x84
++#define GAIN_CH4 0x85
++#define GAIN_CH5 0x86
++#define GAIN_CH6 0x87
++#define GAIN_CH7 0x88
++#define MUTE_INPUTS 0x89
++#define CLOCK_CONSUMER_MODE 0x00
++#define CLOCK_PROVIDER_MODE 0x01
++
++/* Mask encoding for provider frequency settings */
++#define MASK_5512 0x00
++#define MASK_8000 0x01
++#define MASK_11025 0x02
++#define MASK_16000 0x03
++#define MASK_22050 0x04
++#define MASK_32000 0x05
++#define MASK_44100 0x06
++#define MASK_48000 0x07
++#define MASK_64000 0x08
++#define MASK_88200 0x09
++#define MASK_96000 0x0A
++#define MASK_176400 0x0B
++#define MASK_192000 0x0C
++#define MASK_352800 0x0D
++#define MASK_384000 0x0E
++
++/* Mask encoding for sample formats */
++#define MASK_16_BIT_SF 0x01
++#define MASK_24_BIT_SF 0x02
++#define MASK_32_BIT_SF 0x03
++
++/* struct definition for easier access to firmware registers */
++struct hb_studio_regs_t {
++ unsigned char firmware_major;
++ unsigned char firmware_minor;
++ unsigned char firmware_subversion;
++ unsigned char hardware_major;
++ unsigned char hardware_minor;
++ unsigned char hardware_subversion;
++ unsigned char res1[10];
++ uuid_t uuid;
++ unsigned int supported_rates; // 0x20
++ unsigned int supported_formats; // 0x24
++ unsigned char num_of_input_ch; // 0x28
++ unsigned char num_of_output_ch; // 0x29
++ unsigned char card_reset; // 0x2a
++ unsigned char current_rate; // 0x2b
++ unsigned char current_format; // 0x2c
++ unsigned char max_volume; // 0x2d
++ unsigned char min_volume; // 0x2e
++ unsigned char volume_stp; // 0x2f
++ unsigned char max_gain; // 0x30
++ unsigned char min_gain; // 0x31
++ unsigned char gain_stp; // 0x32
++ unsigned char card_busy; // 0x33
++ unsigned char card_noerr; // 0x34
++ unsigned char card_clk_options; // 0x35
++ unsigned char card_clk_mode; // 0x36
++ unsigned char res3[9]; // 0x37
++ unsigned char dac_state; // 0x40
++ unsigned char dac_clock_source; // 0x41
++ unsigned char dac_sys_clk; // 0x42
++ unsigned char dac_sample_format;
++ unsigned char dac_filter_setting_0;
++ unsigned char dac_filter_setting_1;
++ unsigned char dac_filter_setting_2;
++ unsigned char dac_filter_setting_3;
++ unsigned char dac_output_mode; // 0x48
++ unsigned char res4[7]; // 0x49 - 0x4f
++ unsigned char master_vol; // 0x50
++ unsigned char vol_ch0; // 0x51
++ unsigned char vol_ch1; // 0x52
++ unsigned char vol_ch2; // 0x53
++ unsigned char vol_ch3; // 0x54
++ unsigned char vol_ch4; // 0x55
++ unsigned char vol_ch5; // 0x56
++ unsigned char vol_ch6; // 0x57
++ unsigned char vol_ch7; // 0x58
++ unsigned char mute_outputs; // 0x59
++ unsigned char res5[22]; // 0x5a- 0x6f
++ unsigned char adc_input_mode; // 0x70
++ unsigned char adc_state; // 0x70
++ unsigned char adc_clock_source; // 0x71
++ unsigned char adc_sys_clk; // 0x72
++ unsigned char adc_sample_format; // 0x73
++ unsigned char adc_filter_setting_0; // 0x74
++ unsigned char adc_filter_setting_1; // 0x75
++ unsigned char adc_filter_setting_2; // 0x76
++ unsigned char adc_filter_setting_3; // 0x77
++ unsigned char adc_clipping_att; // 0x78
++ unsigned char res6[7]; // 0x79- 0x7f
++ unsigned char res[1]; // 0x80
++ unsigned char gain_ch0; // 0x81
++ unsigned char gain_ch1; // 0x82
++ unsigned char gain_ch2; // 0x83
++ unsigned char gain_ch3; // 0x84
++ unsigned char gain_ch4; // 0x85
++ unsigned char gain_ch5; // 0x86
++ unsigned char gain_ch6; // 0x87
++ unsigned char gain_ch7; // 0x88
++ unsigned char mute_inputs; // 0x89
++ };
++
++
++static struct snd_soc_card snd_rpi_hifiberry_studio;
++static struct i2c_client *hb_uni_i2c_client;
++struct hb_uni_private {
++ struct regmap *regmap;
++ uuid_t uuid;
++ unsigned int sample_bits;
++ unsigned int current_rate;
++ struct hb_studio_regs_t card_info;
++};
++
++static struct hb_uni_private *priv;
++static bool card_is_clk_provider;
++
++static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg)
++{
++ switch (reg) {
++ case CARD_BUSY:
++ case CARD_RESET:
++ case DAC_STATE:
++ case DAC_CLOCK_SOURCE:
++ case DAC_SYS_CLK:
++ case MASTER_VOL:
++ case VOL_CH0:
++ case VOL_CH1:
++ case VOL_CH2:
++ case VOL_CH3:
++ case VOL_CH4:
++ case VOL_CH5:
++ case VOL_CH6:
++ case VOL_CH7:
++ case GAIN_CH0:
++ case GAIN_CH1:
++ case GAIN_CH2:
++ case GAIN_CH3:
++ case GAIN_CH4:
++ case GAIN_CH5:
++ case GAIN_CH6:
++ case GAIN_CH7:
++ return true;
++ default:
++ return false;
++ }
++}
++
++static bool hb_uni_readable_reg(struct device *dev, unsigned int reg)
++{
++ switch (reg) {
++ case FIRMWARE_MAJOR:
++ case FIRMWARE_MINOR:
++ case FIRMWARE_SUBVERSION:
++ case HARDWARE_MAJOR:
++ case HARDWARE_MINOR:
++ case HARDWARE_SUBVERSION:
++ case NUM_OF_INPUT_CH:
++ case NUM_OF_OUTPUT_CH:
++ case SUPPORTED_RATES_0:
++ case SUPPORTED_RATES_1:
++ case SUPPORTED_RATES_2:
++ case SUPPORTED_RATES_3:
++ case SUPPORTED_FORMATS_0:
++ case SUPPORTED_FORMATS_1:
++ case SUPPORTED_FORMATS_2:
++ case SUPPORTED_FORMATS_3:
++ case UUID:
++ case DAC_STATE:
++ case CARD_BUSY:
++ case CARD_RESET:
++ case CARD_CLOCK_MODE:
++ case DAC_CLOCK_SOURCE:
++ case DAC_SYS_CLK:
++ case DAC_SAMPLE_FORMAT:
++ case DAC_FILTER_SETTING_0:
++ case DAC_FILTER_SETTING_1:
++ case DAC_FILTER_SETTING_2:
++ case DAC_FILTER_SETTING_3:
++ case DAC_OUTPUT_MODE:
++ case GAIN_CH0:
++ case GAIN_CH1:
++ case GAIN_CH2:
++ case GAIN_CH3:
++ case GAIN_CH4:
++ case GAIN_CH5:
++ case GAIN_CH6:
++ case GAIN_CH7:
++ case ADC_CLIPPING_ATT:
++ return true;
++ default:
++ return reg < 0xff;
++ }
++}
++
++static const struct regmap_config hb_uni_regmap = {
++ .reg_bits = 8,
++ .val_bits = 8,
++ .max_register = 0xff,
++ .cache_type = REGCACHE_RBTREE,
++ .volatile_reg = hb_uni_volatile_reg,
++ .readable_reg = hb_uni_readable_reg,
++};
++
++static const DECLARE_TLV_DB_MINMAX(adc_att_tlv, -600, -300);
++static const DECLARE_TLV_DB_MINMAX(gain_tlv, -1200, 4000);
++static const DECLARE_TLV_DB_MINMAX(volume_tlv, -10300, 2400);
++static const DECLARE_TLV_DB_MINMAX(spkr_tlv, -10300, 0);
++static const char * const pll_lock_texts[] = {"unlocked", "locked"};
++static const char * const mute_texts[] = {"unmuted", "muted"};
++static const char * const dac_filter_texts[] = {
++ "FIR w/ De-Emph.",
++ "Low Latency IIR w/ De-Emph.",
++ "High Att. w/ De-Emph.",
++ "Ringingless Low Latency FIR w/o Deemph.",
++ };
++static const char * const adc_att_texts[] = {
++ "Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB",
++ };
++
++struct hb_uni_vol_control_single {
++ unsigned int reg;
++ unsigned int shift;
++ int min;
++ int max;
++ bool invert;
++ const unsigned int *tlv;
++};
++
++static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
++ uinfo->count = 1; // mono
++ uinfo->value.integer.min = ctl->min;
++ uinfo->value.integer.max = ctl->max;
++ uinfo->value.integer.step = 1;
++ return 0;
++}
++
++static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++ unsigned int val;
++
++ regmap_read(priv->regmap, ctl->reg, &val);
++ val = (val >> ctl->shift) & 0xff;
++ if (ctl->invert)
++ val = ctl->max - val;
++ ucontrol->value.integer.value[0] = val;
++ return 0;
++}
++
++static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++ unsigned int val = ucontrol->value.integer.value[0];
++ unsigned int new;
++
++ if (ctl->invert)
++ val = ctl->max - val;
++ new = (val & 0xff) << ctl->shift;
++ regmap_write(priv->regmap, ctl->reg, new);
++ return 0;
++}
++
++struct hb_uni_enum_control {
++ unsigned int reg;
++ unsigned int shift;
++ unsigned int mask;
++ const char * const *texts;
++ unsigned int items;
++};
++
++static int hb_uni_enum_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
++{
++ struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++
++ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
++ uinfo->count = 1;
++ uinfo->value.enumerated.items = ctl->items;
++
++ if (uinfo->value.enumerated.item >= ctl->items)
++ uinfo->value.enumerated.item = ctl->items - 1;
++
++ strscpy(uinfo->value.enumerated.name,
++ ctl->texts[uinfo->value.enumerated.item],
++ sizeof(uinfo->value.enumerated.name) - 1);
++
++ return 0;
++}
++
++static int hb_uni_enum_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++ unsigned int val;
++
++ regmap_read(priv->regmap, ctl->reg, &val);
++
++ val = (val >> ctl->shift) & ctl->mask;
++ if (val >= ctl->items)
++ val = 0;
++
++ ucontrol->value.enumerated.item[0] = val;
++
++ return 0;
++}
++
++static int hb_uni_enum_put(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++ unsigned int val = ucontrol->value.enumerated.item[0];
++
++ if (val >= ctl->items)
++ return -EINVAL;
++
++ regmap_update_bits(priv->regmap, ctl->reg,
++ ctl->mask << ctl->shift,
++ (val & ctl->mask) << ctl->shift);
++
++ return 0;
++}
++
++#define VOL_CTL_SINGLE(kname, controls, ktlv) {\
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
++ .name = kname, \
++ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
++ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
++ .tlv.p = ktlv, \
++ .info = hb_uni_vol_info_single, \
++ .get = hb_uni_vol_get_single, \
++ .put = hb_uni_vol_put_single, \
++ .private_value = (unsigned long)&controls, }
++
++#define ENUM_CTL_SINGLE(kname, controls) {\
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
++ .name = kname, \
++ .info = hb_uni_enum_info, \
++ .get = hb_uni_enum_get, \
++ .put = hb_uni_enum_put, \
++ .private_value = (unsigned long)&controls, }
++
++#define ENUM_CTL_SINGLE_RO(kname, controls) {\
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
++ .name = kname, \
++ .info = hb_uni_enum_info, \
++ .get = hb_uni_enum_get, \
++ .put = NULL, \
++ .private_value = (unsigned long)&controls, }
++
++static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = {
++ { MASTER_VOL, 0, 0, 254, true, volume_tlv },
++ { VOL_CH0, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH1, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH2, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH3, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH4, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH5, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH6, 0, 0, 206, true, spkr_tlv },
++ { VOL_CH7, 0, 0, 206, true, spkr_tlv },
++};
++
++static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = {
++ { GAIN_CH0, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH1, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH2, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH3, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH4, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH5, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH6, 0, 0, 104, false, gain_tlv },
++ { GAIN_CH7, 0, 0, 104, false, gain_tlv },
++};
++
++static const struct snd_kcontrol_new hb_uni_play_controls_single[] = {
++ VOL_CTL_SINGLE("Master Playback Volume", hb_uni_vol_ctls_single[0], volume_tlv),
++ VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_uni_vol_ctls_single[1], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_uni_vol_ctls_single[2], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_uni_vol_ctls_single[3], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_uni_vol_ctls_single[4], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_uni_vol_ctls_single[5], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_uni_vol_ctls_single[6], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_uni_vol_ctls_single[7], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_uni_vol_ctls_single[8], spkr_tlv),
++};
++
++static const struct snd_kcontrol_new hb_uni_rec_controls_single[] = {
++ VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_uni_gain_ctls_single[0], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_uni_gain_ctls_single[1], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_uni_gain_ctls_single[2], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_uni_gain_ctls_single[3], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_uni_gain_ctls_single[4], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_uni_gain_ctls_single[5], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_uni_gain_ctls_single[6], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_uni_gain_ctls_single[7], gain_tlv),
++};
++
++static const struct hb_uni_enum_control hb_uni_play_enum_ctls[] = {
++ { DAC_STATE, 0, 0x1, pll_lock_texts, ARRAY_SIZE(pll_lock_texts) },
++ { DAC_FILTER_SETTING_0, 0, 0x03, dac_filter_texts, ARRAY_SIZE(dac_filter_texts) },
++ { MUTE_OUTPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) },
++};
++
++static const struct hb_uni_enum_control hb_uni_rec_enum_ctls[] = {
++ { ADC_CLIPPING_ATT, 0, 0x7, adc_att_texts, ARRAY_SIZE(adc_att_texts) },
++};
++
++static const struct snd_kcontrol_new hb_uni_gen_controls_single[] = {
++ ENUM_CTL_SINGLE("DAC Filter", hb_uni_play_enum_ctls[1]),
++ ENUM_CTL_SINGLE("DAC Mute", hb_uni_play_enum_ctls[2]),
++};
++
++static const struct snd_kcontrol_new adc_controls_single[] = {
++ ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]),
++};
++
++static int snd_rpi_hifiberry_studio_hw_params(
++ struct snd_pcm_substream *substream,
++ struct snd_pcm_hw_params *params)
++{
++ struct snd_soc_pcm_runtime *rtd = substream->private_data;
++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
++ struct device *dev = rtd->dev;
++ unsigned char tmp;
++ int trials;
++ int busy;
++ int err;
++
++ priv->sample_bits = snd_pcm_format_width(params_format(params));
++ priv->sample_bits = priv->sample_bits <= 16 ? 16 : 32;
++
++ priv->current_rate = params_rate(params);
++ dev_info(dev, "using %ibits @ %isps\n",
++ priv->sample_bits, priv->current_rate);
++
++ /* write requested samplerate and word length back to card */
++ switch (priv->current_rate) {
++ case 5512:
++ tmp = MASK_5512;
++ break;
++ case 8000:
++ tmp = MASK_8000;
++ break;
++ case 11025:
++ tmp = MASK_11025;
++ break;
++ case 16000:
++ tmp = MASK_16000;
++ break;
++ case 22050:
++ tmp = MASK_22050;
++ break;
++ case 32000:
++ tmp = MASK_32000;
++ break;
++ case 44100:
++ tmp = MASK_44100;
++ break;
++ case 88200:
++ tmp = MASK_88200;
++ break;
++ case 176400:
++ tmp = MASK_176400;
++ break;
++ case 352800:
++ tmp = MASK_352800;
++ break;
++ case 48000:
++ tmp = MASK_48000;
++ break;
++ case 96000:
++ tmp = MASK_96000;
++ break;
++ case 192000:
++ tmp = MASK_192000;
++ break;
++ case 384000:
++ tmp = MASK_384000;
++ break;
++ default:
++ dev_info(dev, "rate not supported (%u)\n", priv->current_rate);
++ return -EINVAL;
++ }
++
++ err = regmap_write(priv->regmap, CURRENT_RATE, tmp);
++ if (err < 0)
++ return err;
++
++ switch (priv->sample_bits) {
++ case 16:
++ tmp = MASK_16_BIT_SF;
++ break;
++ case 24:
++ tmp = MASK_24_BIT_SF;
++ break;
++ case 32:
++ tmp = MASK_32_BIT_SF;
++ break;
++ default:
++ dev_info(dev, "word length not supported (%u)\n",
++ priv->sample_bits);
++ return -EINVAL;
++ }
++ err = regmap_write(priv->regmap, CURRENT_FORMAT, tmp);
++ if (err < 0)
++ return err;
++
++ /* If card provides clocks wait max. ~40ms for PLL */
++ if (card_is_clk_provider) {
++ /* trigger card to set new rate and format */
++ err = regmap_write(priv->regmap, CARD_CLOCK_MODE, 0x02);
++ if (err < 0)
++ return err;
++ trials = 10;
++ do {
++ usleep_range(3000, 4000);
++ regmap_read(priv->regmap, CARD_BUSY, &busy);
++ } while (busy && --trials);
++ if (!trials) {
++ dev_err(dev, "Card is unable to set clocks\n");
++ return -EINVAL;
++ }
++ }
++ /* always run with 64bit frames */
++ return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
++}
++
++static const struct snd_soc_ops snd_rpi_hifiberry_studio_ops = {
++ .hw_params = snd_rpi_hifiberry_studio_hw_params,
++};
++
++SND_SOC_DAILINK_DEFS(hifiberry_studio,
++ DAILINK_COMP_ARRAY(COMP_EMPTY()),
++ DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
++ DAILINK_COMP_ARRAY(COMP_EMPTY()));
++
++static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd)
++{
++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
++ struct snd_soc_card *card = rtd->card;
++
++ /* Configure playback */
++ codec_dai->driver->playback.channels_max =
++ priv->card_info.num_of_output_ch;
++ codec_dai->driver->playback.rates = priv->card_info.supported_rates;
++ codec_dai->driver->playback.formats = priv->card_info.supported_formats;
++
++ if (priv->card_info.num_of_input_ch) {
++ struct snd_soc_dai_link *dai = rtd->dai_link;
++
++ dev_info(card->dev, "inputs detected: capture enabled\n");
++ codec_dai->driver->symmetric_rate = 1;
++ codec_dai->driver->symmetric_sample_bits = 1;
++ codec_dai->driver->capture.formats =
++ priv->card_info.supported_formats;
++ codec_dai->driver->capture.rates =
++ priv->card_info.supported_rates;
++ codec_dai->driver->capture.channels_max =
++ priv->card_info.num_of_input_ch;
++ dai->name = "HiFiBerry Studio DAC8x-ADC8x";
++ dai->stream_name = "HiFiBerry Studio HiFi";
++ } else {
++ rtd->dai_link->playback_only = 1; // Disable capture
++ }
++
++ if ((priv->card_info.card_clk_options & 0x02) && card_is_clk_provider) {
++ struct snd_soc_dai_link *dai = rtd->dai_link;
++
++ dai->stream_name = "HiFiBerry Studio Pro HiFi";
++ dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
++ | SND_SOC_DAIFMT_CBP_CFP;
++ }
++ dev_info(card->dev,
++ "HiFiBerry Studio DAC8x successfully initialized\n");
++
++ return 0;
++}
++
++static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dai[] = {
++ {
++ .name = "HiFiBerry Studio DAC8x",
++ .stream_name = "HifiBerry Studio HiFi",
++ .dai_fmt = SND_SOC_DAIFMT_I2S |
++ SND_SOC_DAIFMT_NB_NF |
++ SND_SOC_DAIFMT_CBC_CFC,
++ .init = hifiberry_studio_init,
++ .ops = &snd_rpi_hifiberry_studio_ops,
++ SND_SOC_DAILINK_REG(hifiberry_studio),
++ },
++};
++
++/* audio machine driver */
++static struct snd_soc_card snd_rpi_hifiberry_studio = {
++ .name = "Hifiberry Studio DAC8x",
++ .driver_name = "HifiberryStudio",
++ .owner = THIS_MODULE,
++ .dai_link = snd_rpi_hifiberry_studio_dai,
++ .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dai),
++};
++
++static int hb_uni_read_card_info(struct platform_device *pdev)
++{
++ int ret;
++
++ /* read basic card info */
++ ret = regmap_bulk_read(priv->regmap, 0x00, &priv->card_info, 0x06);
++ if (ret) {
++ dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
++ return ret;
++ }
++
++ dev_info(&pdev->dev, "hardware V%d.%d.%d\n",
++ priv->card_info.hardware_major,
++ priv->card_info.hardware_minor,
++ priv->card_info.hardware_subversion
++ );
++
++ dev_info(&pdev->dev, "firmware V%d.%d.%d\n",
++ priv->card_info.firmware_major,
++ priv->card_info.firmware_minor,
++ priv->card_info.firmware_subversion
++ );
++
++ /* read card capabilities */
++ ret = regmap_bulk_read(priv->regmap, UUID, &priv->card_info.uuid, 0x20);
++ if (ret) {
++ dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
++ return ret;
++ }
++
++ dev_info(&pdev->dev, "UUID: %*phN\n",
++ (int)sizeof(priv->card_info.uuid.b), priv->card_info.uuid.b);
++ dev_info(&pdev->dev, "%i output channels reported\n",
++ priv->card_info.num_of_output_ch);
++ dev_dbg(&pdev->dev, "supported rates %08x\n",
++ priv->card_info.supported_rates);
++ dev_dbg(&pdev->dev, "supported formats %08x\n",
++ priv->card_info.supported_formats);
++
++ if (priv->card_info.num_of_output_ch > 8 ||
++ priv->card_info.num_of_input_ch > 8) {
++ dev_err(&pdev->dev, "Maximum of 8 channels exceeded!\n");
++ return -EINVAL;
++ }
++
++ if (priv->card_info.num_of_input_ch > 0) {
++ dev_info(&pdev->dev,
++ "Inputs detected: %u channels\n",
++ priv->card_info.num_of_input_ch);
++ } else {
++ dev_info(&pdev->dev, "No inputs present, playback only\n");
++ }
++
++ ret = regmap_bulk_read(priv->regmap, CARD_BUSY,
++ &priv->card_info.card_busy, 20);
++ if (ret) {
++ dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
++ return ret;
++ }
++
++ if (card_is_clk_provider) {
++ if (priv->card_info.card_clk_options & 0x02) {
++ dev_info(&pdev->dev, "Card provides i2s clocks\n");
++ } else {
++ dev_err(&pdev->dev,
++ "Card cannot provide i2s clocks\n");
++ return -EINVAL;
++ }
++ } else {
++ if (priv->card_info.card_clk_options == 0x02) {
++ dev_err(&pdev->dev,
++ "Card cannot run as i2s clock consumer\n");
++ return -EINVAL;
++ }
++ }
++
++ switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) {
++ case 0x74e7ae95:
++ if (card_is_clk_provider)
++ snd_rpi_hifiberry_studio.name =
++ "HiFiBerry Studio DAC8x Pro";
++ else
++ snd_rpi_hifiberry_studio.name =
++ "HiFiBerry Studio DAC8x";
++ break;
++ default:
++ break;
++ }
++
++
++ regcache_cache_only(priv->regmap, true);
++ ret = regmap_bulk_read(priv->regmap, MASTER_VOL,
++ &priv->card_info.master_vol, MUTE_OUTPUTS - MASTER_VOL);
++ regcache_cache_only(priv->regmap, false);
++
++ return 0;
++}
++
++static int hb_uni_add_card_controls(struct platform_device *pdev)
++{
++ int ret;
++
++ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
++ hb_uni_gen_controls_single,
++ ARRAY_SIZE(hb_uni_gen_controls_single));
++ if (ret < 0) {
++ dev_err(&pdev->dev,
++ "snd_soc_add_card_controls() failed: %d\n", ret);
++ return ret;
++ }
++ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
++ hb_uni_play_controls_single,
++ ARRAY_SIZE(hb_uni_play_controls_single) / 9 *
++ (priv->card_info.num_of_output_ch + 1));
++ if (ret < 0) {
++ dev_err(&pdev->dev,
++ "snd_soc_add_card_controls() failed: %d\n", ret);
++ return ret;
++ }
++
++ /* add optional ADC controls if inputs detected */
++ if (priv->card_info.num_of_input_ch > 0) {
++ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
++ hb_uni_rec_controls_single,
++ ARRAY_SIZE(hb_uni_rec_controls_single) / 8 *
++ priv->card_info.num_of_input_ch);
++ if (ret < 0) {
++ dev_err(&pdev->dev,
++ "snd_soc_add_card_controls() failed: %d\n", ret);
++ }
++ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
++ adc_controls_single,
++ ARRAY_SIZE(adc_controls_single));
++ if (ret < 0) {
++ dev_err(&pdev->dev,
++ "snd_soc_add_card_controls() failed: %d\n", ret);
++ }
++ }
++ return ret;
++}
++
++static int hb_controller_probe(struct platform_device *pdev)
++{
++ struct i2c_adapter *adap = i2c_get_adapter(1);
++ struct device_node *np = pdev->dev.of_node;
++ int ret;
++
++ if (!adap)
++ return -EPROBE_DEFER; /* I2C module not yet available */
++
++ struct i2c_board_info info = {
++ I2C_BOARD_INFO("hb_controller", 0x10),
++ };
++
++ hb_uni_i2c_client = i2c_new_client_device(adap, &info);
++ if (IS_ERR(hb_uni_i2c_client))
++ return PTR_ERR(hb_uni_i2c_client);
++
++ priv = devm_kzalloc(&hb_uni_i2c_client->dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->regmap = devm_regmap_init_i2c(hb_uni_i2c_client, &hb_uni_regmap);
++ if (IS_ERR(priv->regmap))
++ return dev_err_probe(&hb_uni_i2c_client->dev,
++ PTR_ERR(priv->regmap), "Failed to init regmap\n");
++
++ if (np && of_property_read_bool(np, "clk-provider"))
++ card_is_clk_provider = true;
++
++ ret = hb_uni_read_card_info(pdev);
++ if (ret < 0) {
++ dev_err(&hb_uni_i2c_client->dev,
++ "Failed to read card info or wrong configuration!\n");
++ }
++
++ return ret;
++};
++
++static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev)
++{
++ int ret = 0;
++
++ /* probe for controller */
++ ret = hb_controller_probe(pdev);
++ if (ret < 0)
++ return ret;
++
++ snd_rpi_hifiberry_studio.dev = &pdev->dev;
++
++ if (pdev->dev.of_node) {
++ struct device_node *i2s_node;
++ struct snd_soc_dai_link *dai;
++
++ dai = &snd_rpi_hifiberry_studio_dai[0];
++ i2s_node = of_parse_phandle(pdev->dev.of_node,
++ "i2s-controller", 0);
++
++ if (i2s_node) {
++ dai->cpus->dai_name = NULL;
++ dai->cpus->of_node = i2s_node;
++ dai->platforms->name = NULL;
++ dai->platforms->of_node = i2s_node;
++ }
++ }
++
++ ret = devm_snd_soc_register_card(&pdev->dev,
++ &snd_rpi_hifiberry_studio);
++ if (ret && ret != -EPROBE_DEFER)
++ dev_err(&pdev->dev,
++ "devm_snd_soc_register_card() failed: %d\n", ret);
++
++ /* as we do not have components use card-controls */
++ ret = hb_uni_add_card_controls(pdev);
++
++ return ret;
++}
++
++static const struct of_device_id snd_rpi_hifiberry_studio_of_match[] = {
++ { .compatible = "hifiberry,hifiberry-studio-dac8x", },
++ {},
++};
++MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_of_match);
++
++static struct platform_driver snd_rpi_hifiberry_studio_driver = {
++ .driver = {
++ .name = "snd-rpi-hifiberry-studio",
++ .owner = THIS_MODULE,
++ .of_match_table = snd_rpi_hifiberry_studio_of_match,
++ },
++ .probe = snd_rpi_hifiberry_studio_probe,
++};
++
++module_platform_driver(snd_rpi_hifiberry_studio_driver);
++
++MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
++MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver");
++MODULE_LICENSE("GPL");
--- /dev/null
+From 8905174a971fe0f51efc9d39bb312c2d09874bf0 Mon Sep 17 00:00:00 2001
+From: j-schambacher <joerg@hifiberry.com>
+Date: Mon, 13 Jul 2026 09:14:22 +0200
+Subject: [PATCH] ASoC: hifiberry_studio: add multi-card and Digi/AES support
+
+Generalize the driver to support different cards that share the
+same onboard controller/firmware and I2C register protocol:
+the existing DAC8x/ADC8x analog cards and the new Studio Digi/AES card.
+
+- Split the onboard controller handling into a separate I2C client
+ driver (compatible "hifiberry,hb-studio-ctrl") that creates a shared
+ regmap and reads firmware/hardware capabilities at probe time.
+ The card type is decoded from the last 4 bytes of the controller's
+ UUID and used by the platform/machine driver.
+- It now registers different ALSA controls depending on the
+ detected card.
+- Add AES input-rate detection and locking: capture on an AES card
+ requires DIR/RX mode as the Pi's I2S interface can only run at the
+ same sample rate for either direction.
+- Add GPIO7 interrupt handling (FS-change / PLL-lost) for Digi/AES
+ cards: the IRQ stops any active playback/capture substreams via
+ snd_pcm_stop() under a spinlock shared with the hw_params/startup/
+ shutdown paths. The IRQ is only requested for AES-type cards, decided
+ automatically from the detected card type.
+- Add the "hifiberry,hifiberry-studio" compatible string alongside the
+ existing "hifiberry,hifiberry-studio-dac8x" one, used by card
+ variants that don't need the DAC8x-specific compatible string for
+ backwards compatibility.
+
+Signed-off-by: j-schambacher <joerg@hifiberry.com>
+---
+ sound/soc/bcm/hifiberry_studio.c | 747 +++++++++++++++++++++++--------
+ 1 file changed, 570 insertions(+), 177 deletions(-)
+
+--- a/sound/soc/bcm/hifiberry_studio.c
++++ b/sound/soc/bcm/hifiberry_studio.c
+@@ -28,6 +28,11 @@
+ #include <sound/tlv.h>
+ #include <linux/uuid.h>
+ #include <linux/slab.h>
++#include <linux/gpio.h>
++#include <linux/interrupt.h>
++#include <linux/of_gpio.h>
++#include <linux/of_irq.h>
++#include <linux/irq.h>
+
+ /* register definitions and firmware settings */
+ #define FIRMWARE_MAJOR 0x00
+@@ -60,6 +65,10 @@
+ #define CARD_NOERR 0x34
+ #define CARD_CLK_OPTIONS 0x35
+ #define CARD_CLOCK_MODE 0x36
++#define CARD_CLK_ACT 0x37
++#define CARD_CLK_OVRWR 0x38
++#define CARD_STREAM_STATUS 0x39
++#define CARD_DIR_FS 0x3A
+ #define DAC_STATE 0x40
+ #define DAC_CLOCK_SOURCE 0x41
+ #define DAC_SYS_CLK 0x42
+@@ -79,7 +88,6 @@
+ #define VOL_CH6 0x57
+ #define VOL_CH7 0x58
+ #define MUTE_OUTPUTS 0x59
+-#define ADC_INPUT_MODE 0x70
+ #define ADC_STATE 0x70
+ #define ADC_CLOCK_SOURCE 0x71
+ #define ADC_SYS_CLK 0x72
+@@ -123,6 +131,15 @@
+ #define MASK_24_BIT_SF 0x02
+ #define MASK_32_BIT_SF 0x03
+
++/* Card types */
++#define DACADC 0x00
++#define AES 0x01
++#define AMP 0x02
++
++/* Stream status */
++#define CAPTURE 0x01
++#define PLAY 0x10
++
+ /* struct definition for easier access to firmware registers */
+ struct hb_studio_regs_t {
+ unsigned char firmware_major;
+@@ -150,7 +167,10 @@ struct hb_studio_regs_t {
+ unsigned char card_noerr; // 0x34
+ unsigned char card_clk_options; // 0x35
+ unsigned char card_clk_mode; // 0x36
+- unsigned char res3[9]; // 0x37
++ unsigned char card_clk_act; // 0x37
++ unsigned char card_clk_ovrwr; // 0x38
++ unsigned char card_stream_status; // 0x39
++ unsigned char res3[6]; // 0x3a
+ unsigned char dac_state; // 0x40
+ unsigned char dac_clock_source; // 0x41
+ unsigned char dac_sys_clk; // 0x42
+@@ -172,7 +192,6 @@ struct hb_studio_regs_t {
+ unsigned char vol_ch7; // 0x58
+ unsigned char mute_outputs; // 0x59
+ unsigned char res5[22]; // 0x5a- 0x6f
+- unsigned char adc_input_mode; // 0x70
+ unsigned char adc_state; // 0x70
+ unsigned char adc_clock_source; // 0x71
+ unsigned char adc_sys_clk; // 0x72
+@@ -195,21 +214,27 @@ struct hb_studio_regs_t {
+ unsigned char mute_inputs; // 0x89
+ };
+
+-
+ static struct snd_soc_card snd_rpi_hifiberry_studio;
+-static struct i2c_client *hb_uni_i2c_client;
+-struct hb_uni_private {
++static struct i2c_client *hb_studio_i2c_client;
++struct hb_studio_private {
+ struct regmap *regmap;
+ uuid_t uuid;
+ unsigned int sample_bits;
+ unsigned int current_rate;
++ unsigned int allowed_rate;
++ unsigned int clk_ovrwr;
+ struct hb_studio_regs_t card_info;
++ struct snd_pcm_substream *playback_substream;
++ struct snd_pcm_substream *capture_substream;
++ spinlock_t stream_lock;
++ struct work_struct error_work;
++ int card_type;
+ };
+
+-static struct hb_uni_private *priv;
++static struct hb_studio_private *priv;
+ static bool card_is_clk_provider;
+
+-static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg)
++static bool hb_studio_volatile_reg(struct device *dev, unsigned int reg)
+ {
+ switch (reg) {
+ case CARD_BUSY:
+@@ -217,6 +242,8 @@ static bool hb_uni_volatile_reg(struct d
+ case DAC_STATE:
+ case DAC_CLOCK_SOURCE:
+ case DAC_SYS_CLK:
++ case CARD_CLK_ACT:
++ case CARD_DIR_FS:
+ case MASTER_VOL:
+ case VOL_CH0:
+ case VOL_CH1:
+@@ -240,7 +267,7 @@ static bool hb_uni_volatile_reg(struct d
+ }
+ }
+
+-static bool hb_uni_readable_reg(struct device *dev, unsigned int reg)
++static bool hb_studio_readable_reg(struct device *dev, unsigned int reg)
+ {
+ switch (reg) {
+ case FIRMWARE_MAJOR:
+@@ -264,6 +291,8 @@ static bool hb_uni_readable_reg(struct d
+ case CARD_BUSY:
+ case CARD_RESET:
+ case CARD_CLOCK_MODE:
++ case CARD_CLK_ACT:
++ case CARD_DIR_FS:
+ case DAC_CLOCK_SOURCE:
+ case DAC_SYS_CLK:
+ case DAC_SAMPLE_FORMAT:
+@@ -287,13 +316,13 @@ static bool hb_uni_readable_reg(struct d
+ }
+ }
+
+-static const struct regmap_config hb_uni_regmap = {
++static const struct regmap_config hb_studio_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .cache_type = REGCACHE_RBTREE,
+- .volatile_reg = hb_uni_volatile_reg,
+- .readable_reg = hb_uni_readable_reg,
++ .volatile_reg = hb_studio_volatile_reg,
++ .readable_reg = hb_studio_readable_reg,
+ };
+
+ static const DECLARE_TLV_DB_MINMAX(adc_att_tlv, -600, -300);
+@@ -311,8 +340,9 @@ static const char * const dac_filter_tex
+ static const char * const adc_att_texts[] = {
+ "Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB",
+ };
++static const char * const dix_clk_texts[] = {"TX", "RX"};
+
+-struct hb_uni_vol_control_single {
++struct hb_studio_vol_control_single {
+ unsigned int reg;
+ unsigned int shift;
+ int min;
+@@ -321,10 +351,17 @@ struct hb_uni_vol_control_single {
+ const unsigned int *tlv;
+ };
+
+-static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_info *uinfo)
++static const char * const samplerate_texts[] = {
++ "5512Hz", "8kHz", "11.025kHz", "16kHz",
++ "22.050kHz", "32kHz", "44.1kHz", "48kHz", "64kHz",
++ "88.2kHz", "96kHz", "176.4kHz", "192kHz", "352.8kHz", "384kHz",
++ "na"
++};
++
++static int hb_studio_vol_info_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
+ {
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1; // mono
+@@ -334,10 +371,10 @@ static int hb_uni_vol_info_single(struct
+ return 0;
+ }
+
+-static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
++static int hb_studio_vol_get_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
+ {
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value;
+ unsigned int val;
+
+ regmap_read(priv->regmap, ctl->reg, &val);
+@@ -348,10 +385,10 @@ static int hb_uni_vol_get_single(struct
+ return 0;
+ }
+
+-static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
++static int hb_studio_vol_put_single(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
+ {
+- struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_vol_control_single *ctl = (void *)kcontrol->private_value;
+ unsigned int val = ucontrol->value.integer.value[0];
+ unsigned int new;
+
+@@ -362,7 +399,7 @@ static int hb_uni_vol_put_single(struct
+ return 0;
+ }
+
+-struct hb_uni_enum_control {
++struct hb_studio_enum_control {
+ unsigned int reg;
+ unsigned int shift;
+ unsigned int mask;
+@@ -370,10 +407,10 @@ struct hb_uni_enum_control {
+ unsigned int items;
+ };
+
+-static int hb_uni_enum_info(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_info *uinfo)
++static int hb_studio_enum_info(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_info *uinfo)
+ {
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+@@ -389,10 +426,10 @@ static int hb_uni_enum_info(struct snd_k
+ return 0;
+ }
+
+-static int hb_uni_enum_get(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
++static int hb_studio_enum_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
+ {
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value;
+ unsigned int val;
+
+ regmap_read(priv->regmap, ctl->reg, &val);
+@@ -406,10 +443,10 @@ static int hb_uni_enum_get(struct snd_kc
+ return 0;
+ }
+
+-static int hb_uni_enum_put(struct snd_kcontrol *kcontrol,
+- struct snd_ctl_elem_value *ucontrol)
++static int hb_studio_enum_put(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
+ {
+- struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
++ struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+
+ if (val >= ctl->items)
+@@ -422,34 +459,103 @@ static int hb_uni_enum_put(struct snd_kc
+ return 0;
+ }
+
++/*
++ * Read the AES input rate.
++ * Returns the rate in Hz, or 0 if no valid input is present.
++ * Works in TX and RX clock modes.
++ */
++static int hb_studio_input_rate_hz(struct hb_studio_private *p)
++{
++ unsigned int raw = 0;
++ int trials = 5;
++
++ regmap_write(p->regmap, CARD_DIR_FS, 0x00);
++ do {
++ usleep_range(1000, 2000);
++ regmap_read(p->regmap, CARD_DIR_FS, &raw);
++ } while ((raw & 0x80) && --trials);
++
++ switch (raw & 0x0f) {
++ case 0x08: return 44100;
++ case 0x09: return 48000;
++ case 0x0b: return 88200;
++ case 0x0c: return 96000;
++ case 0x0e: return 176400;
++ case 0x0f: return 192000;
++ default: return 0;
++ }
++}
++
++static int hb_studio_samplerate_get(struct snd_kcontrol *kcontrol,
++ struct snd_ctl_elem_value *ucontrol)
++{
++ struct hb_studio_enum_control *ctl = (void *)kcontrol->private_value;
++ unsigned int idx;
++
++ switch (hb_studio_input_rate_hz(priv)) {
++ case 44100:
++ idx = 6;
++ break;
++ case 48000:
++ idx = 7;
++ break;
++ case 88200:
++ idx = 9;
++ break;
++ case 96000:
++ idx = 10;
++ break;
++ case 176400:
++ idx = 11;
++ break;
++ case 192000:
++ idx = 12;
++ break;
++ default:
++ idx = ctl->items - 1; /* na */
++ break;
++ }
++
++ ucontrol->value.enumerated.item[0] = idx;
++ return 0;
++}
++
+ #define VOL_CTL_SINGLE(kname, controls, ktlv) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = kname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = ktlv, \
+- .info = hb_uni_vol_info_single, \
+- .get = hb_uni_vol_get_single, \
+- .put = hb_uni_vol_put_single, \
++ .info = hb_studio_vol_info_single, \
++ .get = hb_studio_vol_get_single, \
++ .put = hb_studio_vol_put_single, \
+ .private_value = (unsigned long)&controls, }
+
+ #define ENUM_CTL_SINGLE(kname, controls) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = kname, \
+- .info = hb_uni_enum_info, \
+- .get = hb_uni_enum_get, \
+- .put = hb_uni_enum_put, \
++ .info = hb_studio_enum_info, \
++ .get = hb_studio_enum_get, \
++ .put = hb_studio_enum_put, \
+ .private_value = (unsigned long)&controls, }
+
+ #define ENUM_CTL_SINGLE_RO(kname, controls) {\
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = kname, \
+- .info = hb_uni_enum_info, \
+- .get = hb_uni_enum_get, \
++ .info = hb_studio_enum_info, \
++ .get = hb_studio_enum_get, \
+ .put = NULL, \
+ .private_value = (unsigned long)&controls, }
+
+-static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = {
++#define ENUM_CTL_SINGLE_RO_GET(kname, controls, getfn) {\
++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
++ .name = kname, \
++ .info = hb_studio_enum_info, \
++ .get = getfn, \
++ .put = NULL, \
++ .private_value = (unsigned long)&controls, }
++
++static const struct hb_studio_vol_control_single hb_studio_vol_ctls_single[] = {
+ { MASTER_VOL, 0, 0, 254, true, volume_tlv },
+ { VOL_CH0, 0, 0, 206, true, spkr_tlv },
+ { VOL_CH1, 0, 0, 206, true, spkr_tlv },
+@@ -461,7 +567,7 @@ static const struct hb_uni_vol_control_s
+ { VOL_CH7, 0, 0, 206, true, spkr_tlv },
+ };
+
+-static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = {
++static const struct hb_studio_vol_control_single hb_studio_gain_ctls_single[] = {
+ { GAIN_CH0, 0, 0, 104, false, gain_tlv },
+ { GAIN_CH1, 0, 0, 104, false, gain_tlv },
+ { GAIN_CH2, 0, 0, 104, false, gain_tlv },
+@@ -472,46 +578,69 @@ static const struct hb_uni_vol_control_s
+ { GAIN_CH7, 0, 0, 104, false, gain_tlv },
+ };
+
+-static const struct snd_kcontrol_new hb_uni_play_controls_single[] = {
+- VOL_CTL_SINGLE("Master Playback Volume", hb_uni_vol_ctls_single[0], volume_tlv),
+- VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_uni_vol_ctls_single[1], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_uni_vol_ctls_single[2], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_uni_vol_ctls_single[3], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_uni_vol_ctls_single[4], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_uni_vol_ctls_single[5], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_uni_vol_ctls_single[6], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_uni_vol_ctls_single[7], spkr_tlv),
+- VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_uni_vol_ctls_single[8], spkr_tlv),
+-};
+-
+-static const struct snd_kcontrol_new hb_uni_rec_controls_single[] = {
+- VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_uni_gain_ctls_single[0], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_uni_gain_ctls_single[1], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_uni_gain_ctls_single[2], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_uni_gain_ctls_single[3], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_uni_gain_ctls_single[4], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_uni_gain_ctls_single[5], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_uni_gain_ctls_single[6], gain_tlv),
+- VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_uni_gain_ctls_single[7], gain_tlv),
++static const struct snd_kcontrol_new hb_studio_play_controls_single[] = {
++ VOL_CTL_SINGLE("Master Playback Volume", hb_studio_vol_ctls_single[0], volume_tlv),
++ VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_studio_vol_ctls_single[1], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_studio_vol_ctls_single[2], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_studio_vol_ctls_single[3], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_studio_vol_ctls_single[4], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_studio_vol_ctls_single[5], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_studio_vol_ctls_single[6], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_studio_vol_ctls_single[7], spkr_tlv),
++ VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_studio_vol_ctls_single[8], spkr_tlv),
+ };
+
+-static const struct hb_uni_enum_control hb_uni_play_enum_ctls[] = {
++static const struct snd_kcontrol_new hb_studio_rec_controls_single[] = {
++ VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_studio_gain_ctls_single[0], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_studio_gain_ctls_single[1], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_studio_gain_ctls_single[2], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_studio_gain_ctls_single[3], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_studio_gain_ctls_single[4], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_studio_gain_ctls_single[5], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_studio_gain_ctls_single[6], gain_tlv),
++ VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_studio_gain_ctls_single[7], gain_tlv),
++};
++
++static const struct hb_studio_enum_control hb_studio_play_enum_ctls[] = {
+ { DAC_STATE, 0, 0x1, pll_lock_texts, ARRAY_SIZE(pll_lock_texts) },
+ { DAC_FILTER_SETTING_0, 0, 0x03, dac_filter_texts, ARRAY_SIZE(dac_filter_texts) },
+ { MUTE_OUTPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) },
+ };
+
+-static const struct hb_uni_enum_control hb_uni_rec_enum_ctls[] = {
++static const struct hb_studio_enum_control hb_studio_rec_enum_ctls[] = {
+ { ADC_CLIPPING_ATT, 0, 0x7, adc_att_texts, ARRAY_SIZE(adc_att_texts) },
++ { MUTE_INPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) },
++};
++
++/* ---- Studio Digi / AES (card_type AES) controls ---- */
++static const struct hb_studio_enum_control hb_studio_dix_clk_enum_ctls[] = {
++ { CARD_CLK_OVRWR, 0, 0x01, dix_clk_texts, ARRAY_SIZE(dix_clk_texts) },
+ };
+
+-static const struct snd_kcontrol_new hb_uni_gen_controls_single[] = {
+- ENUM_CTL_SINGLE("DAC Filter", hb_uni_play_enum_ctls[1]),
+- ENUM_CTL_SINGLE("DAC Mute", hb_uni_play_enum_ctls[2]),
++static const struct snd_kcontrol_new hb_studio_gen_controls_single[] = {
++ ENUM_CTL_SINGLE("DAC Filter", hb_studio_play_enum_ctls[1]),
++ ENUM_CTL_SINGLE("Output Mute", hb_studio_play_enum_ctls[2]),
+ };
+
+ static const struct snd_kcontrol_new adc_controls_single[] = {
+- ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]),
++ ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_studio_rec_enum_ctls[0]),
++};
++
++static const struct hb_studio_enum_control hb_studio_samplerate_ctl = {
++ /* DIR FS calculator (0x3A): real input rate, any clock mode */
++ .reg = CARD_DIR_FS,
++ .shift = 0,
++ .mask = 0x0F, /* rate is stored in lower 4 bits */
++ .texts = samplerate_texts,
++ .items = ARRAY_SIZE(samplerate_texts),
++};
++
++static const struct snd_kcontrol_new dix_controls_single[] = {
++ ENUM_CTL_SINGLE("Clock mode", hb_studio_dix_clk_enum_ctls[0]),
++ ENUM_CTL_SINGLE("Output Mute", hb_studio_play_enum_ctls[2]),
++ ENUM_CTL_SINGLE("Input Mute", hb_studio_rec_enum_ctls[1]),
++ ENUM_CTL_SINGLE_RO_GET("Current Sample Rate", hb_studio_samplerate_ctl,
++ hb_studio_samplerate_get),
+ };
+
+ static int snd_rpi_hifiberry_studio_hw_params(
+@@ -528,10 +657,10 @@ static int snd_rpi_hifiberry_studio_hw_p
+
+ priv->sample_bits = snd_pcm_format_width(params_format(params));
+ priv->sample_bits = priv->sample_bits <= 16 ? 16 : 32;
+-
+ priv->current_rate = params_rate(params);
+- dev_info(dev, "using %ibits @ %isps\n",
+- priv->sample_bits, priv->current_rate);
++
++ dev_info(dev, "requesting %ibits @ %isps\n",
++ priv->sample_bits, priv->current_rate);
+
+ /* write requested samplerate and word length back to card */
+ switch (priv->current_rate) {
+@@ -582,6 +711,44 @@ static int snd_rpi_hifiberry_studio_hw_p
+ return -EINVAL;
+ }
+
++ /*
++ * AES clock selection & rate locking:
++ * - in RX-clock mode every stream (capture or playback) must run at
++ * the detected (AES) input rate;
++ * - playback in TX-clock mode is unconstrained (the card is
++ * the clock master).
++ */
++ if (priv->card_type == AES) {
++ bool capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
++ unsigned int mode = 0;
++ int in_rate;
++
++ if (capture) {
++ in_rate = hb_studio_input_rate_hz(priv);
++ if (!in_rate) {
++ dev_err(dev, "no AES input detected, cannot capture\n");
++ return -EINVAL;
++ }
++ if (in_rate != (int)priv->current_rate) {
++ dev_err(dev, "capture rate %u does not match AES input %d Hz\n",
++ priv->current_rate, in_rate);
++ return -EINVAL;
++ }
++ /* valid input and matching rate: lock the card to the input */
++ regmap_write(priv->regmap, CARD_CLK_OVRWR, 0x01);
++ } else {
++ regmap_read(priv->regmap, CARD_CLK_OVRWR, &mode);
++ if (mode == 0x01) {
++ in_rate = hb_studio_input_rate_hz(priv);
++ if (!in_rate || in_rate != (int)priv->current_rate) {
++ dev_err(dev, "playback rate %u does not match AES input %d Hz\n",
++ priv->current_rate, in_rate);
++ return -EINVAL;
++ }
++ }
++ }
++ }
++
+ err = regmap_write(priv->regmap, CURRENT_RATE, tmp);
+ if (err < 0)
+ return err;
+@@ -598,7 +765,7 @@ static int snd_rpi_hifiberry_studio_hw_p
+ break;
+ default:
+ dev_info(dev, "word length not supported (%u)\n",
+- priv->sample_bits);
++ priv->sample_bits);
+ return -EINVAL;
+ }
+ err = regmap_write(priv->regmap, CURRENT_FORMAT, tmp);
+@@ -607,7 +774,7 @@ static int snd_rpi_hifiberry_studio_hw_p
+
+ /* If card provides clocks wait max. ~40ms for PLL */
+ if (card_is_clk_provider) {
+- /* trigger card to set new rate and format */
++ /* trigger card to set new rate and format */
+ err = regmap_write(priv->regmap, CARD_CLOCK_MODE, 0x02);
+ if (err < 0)
+ return err;
+@@ -625,8 +792,40 @@ static int snd_rpi_hifiberry_studio_hw_p
+ return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+ }
+
++static int snd_rpi_hifiberry_studio_startup(
++ struct snd_pcm_substream *substream)
++{
++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
++ priv->playback_substream = substream;
++ regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x10,
++ 0x10);
++ } else {
++ priv->capture_substream = substream;
++ regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x01,
++ 0x01);
++ }
++
++ return 0;
++}
++
++static void snd_rpi_hifiberry_studio_shutdown(
++ struct snd_pcm_substream *substream)
++{
++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
++ regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x10,
++ 0x00);
++ priv->playback_substream = NULL;
++ } else {
++ regmap_update_bits(priv->regmap, CARD_STREAM_STATUS, 0x01,
++ 0x00);
++ priv->capture_substream = NULL;
++ }
++}
++
+ static const struct snd_soc_ops snd_rpi_hifiberry_studio_ops = {
++ .startup = snd_rpi_hifiberry_studio_startup,
+ .hw_params = snd_rpi_hifiberry_studio_hw_params,
++ .shutdown = snd_rpi_hifiberry_studio_shutdown,
+ };
+
+ SND_SOC_DAILINK_DEFS(hifiberry_studio,
+@@ -634,6 +833,8 @@ SND_SOC_DAILINK_DEFS(hifiberry_studio,
+ DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
++static void hb_studio_error_work(struct work_struct *work);
++
+ static int hifiberry_studio_init(struct snd_soc_pcm_runtime *rtd)
+ {
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+@@ -657,7 +858,6 @@ static int hifiberry_studio_init(struct
+ priv->card_info.supported_rates;
+ codec_dai->driver->capture.channels_max =
+ priv->card_info.num_of_input_ch;
+- dai->name = "HiFiBerry Studio DAC8x-ADC8x";
+ dai->stream_name = "HiFiBerry Studio HiFi";
+ } else {
+ rtd->dai_link->playback_only = 1; // Disable capture
+@@ -671,14 +871,16 @@ static int hifiberry_studio_init(struct
+ | SND_SOC_DAIFMT_CBP_CFP;
+ }
+ dev_info(card->dev,
+- "HiFiBerry Studio DAC8x successfully initialized\n");
++ "HiFiBerry Studio Soundcard successfully initialized\n");
+
++ spin_lock_init(&priv->stream_lock);
++ INIT_WORK(&priv->error_work, hb_studio_error_work);
+ return 0;
+ }
+
+ static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dai[] = {
+ {
+- .name = "HiFiBerry Studio DAC8x",
++ .name = "HiFiBerry Studio Soundcard",
+ .stream_name = "HifiBerry Studio HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+@@ -691,73 +893,111 @@ static struct snd_soc_dai_link snd_rpi_h
+
+ /* audio machine driver */
+ static struct snd_soc_card snd_rpi_hifiberry_studio = {
+- .name = "Hifiberry Studio DAC8x",
++ .name = "Hifiberry Studio Soundcard",
+ .driver_name = "HifiberryStudio",
+ .owner = THIS_MODULE,
+ .dai_link = snd_rpi_hifiberry_studio_dai,
+ .num_links = ARRAY_SIZE(snd_rpi_hifiberry_studio_dai),
+ };
+
+-static int hb_uni_read_card_info(struct platform_device *pdev)
++/*
++ * Read the fixed hardware/firmware identity out of the controller: versions,
++ * UUID (-> card_type), channel counts, supported rates/formats and the
++ * CARD_BUSY.. capability block.
++ */
++static int hb_studio_ctrl_read_info(struct i2c_client *client,
++ struct hb_studio_private *p)
+ {
++ u32 uuid_end;
+ int ret;
+
+ /* read basic card info */
+- ret = regmap_bulk_read(priv->regmap, 0x00, &priv->card_info, 0x06);
++ ret = regmap_bulk_read(p->regmap, 0x00, &p->card_info, 0x06);
+ if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+- return ret;
++ dev_err(&client->dev, "Failed to read card info: %d\n", ret);
++ return ret;
+ }
+
+- dev_info(&pdev->dev, "hardware V%d.%d.%d\n",
+- priv->card_info.hardware_major,
+- priv->card_info.hardware_minor,
+- priv->card_info.hardware_subversion
+- );
+-
+- dev_info(&pdev->dev, "firmware V%d.%d.%d\n",
+- priv->card_info.firmware_major,
+- priv->card_info.firmware_minor,
+- priv->card_info.firmware_subversion
+- );
++ dev_info(&client->dev, "hardware V%d.%d.%d\n",
++ p->card_info.hardware_major,
++ p->card_info.hardware_minor,
++ p->card_info.hardware_subversion);
++
++ dev_info(&client->dev, "firmware V%d.%d.%d\n",
++ p->card_info.firmware_major,
++ p->card_info.firmware_minor,
++ p->card_info.firmware_subversion);
+
+ /* read card capabilities */
+- ret = regmap_bulk_read(priv->regmap, UUID, &priv->card_info.uuid, 0x20);
++ ret = regmap_bulk_read(p->regmap, UUID, &p->card_info.uuid, 0x20);
+ if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
++ dev_err(&client->dev, "Failed to read card info: %d\n", ret);
+ return ret;
+ }
+
+- dev_info(&pdev->dev, "UUID: %*phN\n",
+- (int)sizeof(priv->card_info.uuid.b), priv->card_info.uuid.b);
+- dev_info(&pdev->dev, "%i output channels reported\n",
+- priv->card_info.num_of_output_ch);
+- dev_dbg(&pdev->dev, "supported rates %08x\n",
+- priv->card_info.supported_rates);
+- dev_dbg(&pdev->dev, "supported formats %08x\n",
+- priv->card_info.supported_formats);
+-
+- if (priv->card_info.num_of_output_ch > 8 ||
+- priv->card_info.num_of_input_ch > 8) {
+- dev_err(&pdev->dev, "Maximum of 8 channels exceeded!\n");
++ dev_info(&client->dev, "UUID: %*phN\n",
++ (int)sizeof(p->card_info.uuid.b), p->card_info.uuid.b);
++ dev_info(&client->dev, "%i output channels reported\n",
++ p->card_info.num_of_output_ch);
++ dev_dbg(&client->dev, "supported rates %08x\n",
++ p->card_info.supported_rates);
++ dev_dbg(&client->dev, "supported formats %08x\n",
++ p->card_info.supported_formats);
++
++ if (p->card_info.num_of_output_ch > 8 ||
++ p->card_info.num_of_input_ch > 8) {
++ dev_err(&client->dev, "Maximum of 8 channels exceeded!\n");
+ return -EINVAL;
+ }
+
+- if (priv->card_info.num_of_input_ch > 0) {
+- dev_info(&pdev->dev,
+- "Inputs detected: %u channels\n",
+- priv->card_info.num_of_input_ch);
++ if (p->card_info.num_of_input_ch > 0) {
++ dev_info(&client->dev,
++ "Inputs detected: %u channels\n",
++ p->card_info.num_of_input_ch);
+ } else {
+- dev_info(&pdev->dev, "No inputs present, playback only\n");
++ dev_info(&client->dev, "No inputs present, playback only\n");
+ }
+
+- ret = regmap_bulk_read(priv->regmap, CARD_BUSY,
+- &priv->card_info.card_busy, 20);
++ ret = regmap_bulk_read(p->regmap, CARD_BUSY,
++ &p->card_info.card_busy, 20);
+ if (ret) {
+- dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
++ dev_err(&client->dev, "Failed to read card info: %d\n", ret);
+ return ret;
+ }
+
++ uuid_end = cpu_to_be32(*(u32 *)((u8 *)&p->card_info.uuid + 12));
++ dev_info(&client->dev, "Card UUID end %08x\n", uuid_end);
++
++ switch (uuid_end) {
++ case 0x7c641980:
++ dev_info(&client->dev, "Card type Analog\n");
++ p->card_type = DACADC;
++ break;
++ case 0x0eb0104d:
++ dev_info(&client->dev, "Card type Digital/AES\n");
++ p->card_type = AES;
++ break;
++ default:
++ dev_info(&client->dev, "No card type detected, assuming Analog\n");
++ p->card_type = DACADC;
++ break;
++ }
++
++ return 0;
++}
++
++/*
++ * Check the clock capability (CARD_CLK_OPTIONS, already read
++ * by hb_studio_ctrl_read_info()) and verfiy vs. the "clk-provider"
++ * DT property
++ */
++static int hb_studio_validate_clk_config(struct platform_device *pdev)
++{
++ struct device_node *np = pdev->dev.of_node;
++
++ if (np && of_property_read_bool(np, "clk-provider"))
++ card_is_clk_provider = true;
++
+ if (card_is_clk_provider) {
+ if (priv->card_info.card_clk_options & 0x02) {
+ dev_info(&pdev->dev, "Card provides i2s clocks\n");
+@@ -774,43 +1014,30 @@ static int hb_uni_read_card_info(struct
+ }
+ }
+
+- switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) {
+- case 0x74e7ae95:
+- if (card_is_clk_provider)
+- snd_rpi_hifiberry_studio.name =
+- "HiFiBerry Studio DAC8x Pro";
+- else
+- snd_rpi_hifiberry_studio.name =
+- "HiFiBerry Studio DAC8x";
+- break;
+- default:
+- break;
+- }
+-
+-
+- regcache_cache_only(priv->regmap, true);
+- ret = regmap_bulk_read(priv->regmap, MASTER_VOL,
+- &priv->card_info.master_vol, MUTE_OUTPUTS - MASTER_VOL);
+- regcache_cache_only(priv->regmap, false);
+-
+ return 0;
+ }
+
+-static int hb_uni_add_card_controls(struct platform_device *pdev)
++/*
++ * Register the ALSA controls for an analog Studio (DAC/ADC) cards (card_type
++ * DACADC): DAC filter + output mute, the output volumes (sized to the actual
++ * output-channel count), and if inputs are present the ADC gains and the
++ * clipping-attenuation control.
++ */
++static int hb_studio_add_dacadc_controls(struct platform_device *pdev)
+ {
+ int ret;
+
+ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
+- hb_uni_gen_controls_single,
+- ARRAY_SIZE(hb_uni_gen_controls_single));
++ hb_studio_gen_controls_single,
++ ARRAY_SIZE(hb_studio_gen_controls_single));
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "snd_soc_add_card_controls() failed: %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
+- hb_uni_play_controls_single,
+- ARRAY_SIZE(hb_uni_play_controls_single) / 9 *
++ hb_studio_play_controls_single,
++ ARRAY_SIZE(hb_studio_play_controls_single) / 9 *
+ (priv->card_info.num_of_output_ch + 1));
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+@@ -821,16 +1048,16 @@ static int hb_uni_add_card_controls(stru
+ /* add optional ADC controls if inputs detected */
+ if (priv->card_info.num_of_input_ch > 0) {
+ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
+- hb_uni_rec_controls_single,
+- ARRAY_SIZE(hb_uni_rec_controls_single) / 8 *
+- priv->card_info.num_of_input_ch);
++ hb_studio_rec_controls_single,
++ ARRAY_SIZE(hb_studio_rec_controls_single) / 8 *
++ priv->card_info.num_of_input_ch);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "snd_soc_add_card_controls() failed: %d\n", ret);
+ }
+ ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
+- adc_controls_single,
+- ARRAY_SIZE(adc_controls_single));
++ adc_controls_single,
++ ARRAY_SIZE(adc_controls_single));
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "snd_soc_add_card_controls() failed: %d\n", ret);
+@@ -839,53 +1066,189 @@ static int hb_uni_add_card_controls(stru
+ return ret;
+ }
+
+-static int hb_controller_probe(struct platform_device *pdev)
++/*
++ * Register the ALSA controls for a Studio Digi/AES card (card_type AES):
++ * the controls for Clock mode, Current Sample Rate, Input/Output Mute.
++ */
++static int hb_studio_add_dix_controls(struct platform_device *pdev)
+ {
+- struct i2c_adapter *adap = i2c_get_adapter(1);
+- struct device_node *np = pdev->dev.of_node;
+- int ret;
+-
+- if (!adap)
+- return -EPROBE_DEFER; /* I2C module not yet available */
++ int ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio,
++ dix_controls_single, ARRAY_SIZE(dix_controls_single));
++ if (ret < 0)
++ dev_err(&pdev->dev,
++ "snd_soc_add_card_controls() failed: %d\n", ret);
++ return ret;
++}
+
+- struct i2c_board_info info = {
+- I2C_BOARD_INFO("hb_controller", 0x10),
+- };
++/*
++ * The DAC8x and Digi cards share one controller and this driver; the card
++ * type is auto-detected from the controller UUID (hb_studio_ctrl_read_info):
++ * DACADC -> DAC8x DAC/ADC controls (hb_studio_add_dacadc_controls)
++ * AES -> Digi DIX controls (hb_studio_add_dix_controls)
++ * Any other/unknown type registers no extra card controls.
++ */
++static int hb_studio_add_card_controls(struct platform_device *pdev)
++{
++ switch (priv->card_type) {
++ case DACADC:
++ return hb_studio_add_dacadc_controls(pdev);
++ case AES:
++ return hb_studio_add_dix_controls(pdev);
++ default:
++ return 0;
++ }
++}
+
+- hb_uni_i2c_client = i2c_new_client_device(adap, &info);
+- if (IS_ERR(hb_uni_i2c_client))
+- return PTR_ERR(hb_uni_i2c_client);
++/*
++ * I2C client driver for the onboard controller (hb-studio-ctrl @ 0x10). It is
++ * instantiated from the "hifiberry,hb-studio-ctrl" child node under &i2c1 in
++ * the DT overlay
++ */
++static int hb_studio_ctrl_probe(struct i2c_client *client)
++{
++ struct hb_studio_private *p;
++ int ret;
+
+- priv = devm_kzalloc(&hb_uni_i2c_client->dev, sizeof(*priv), GFP_KERNEL);
+- if (!priv)
++ p = devm_kzalloc(&client->dev, sizeof(*p), GFP_KERNEL);
++ if (!p)
+ return -ENOMEM;
+
+- priv->regmap = devm_regmap_init_i2c(hb_uni_i2c_client, &hb_uni_regmap);
+- if (IS_ERR(priv->regmap))
+- return dev_err_probe(&hb_uni_i2c_client->dev,
+- PTR_ERR(priv->regmap), "Failed to init regmap\n");
++ p->regmap = devm_regmap_init_i2c(client, &hb_studio_regmap);
++ if (IS_ERR(p->regmap))
++ return dev_err_probe(&client->dev, PTR_ERR(p->regmap),
++ "Failed to init regmap\n");
++
++ i2c_set_clientdata(client, p);
++
++ /*
++ * Read into the not-yet-published 'p' first. The machine driver polls
++ * the shared 'priv' pointer and treats non-NULL as "fully ready", so
++ * 'priv'/'hb_studio_i2c_client' must only be assigned once the read
++ * below has actually succeeded - probing the two drivers can and does
++ * happen concurrently, and publishing early let the machine driver
++ * see a card_info that was still all zeroes.
++ */
++ ret = hb_studio_ctrl_read_info(client, p);
++ if (ret) {
++ dev_err(&client->dev,
++ "Failed to read card info or wrong configuration!\n");
++ return ret;
++ }
+
+- if (np && of_property_read_bool(np, "clk-provider"))
+- card_is_clk_provider = true;
++ /* fully populated: now safe for the machine driver to consume */
++ priv = p;
++ hb_studio_i2c_client = client;
+
+- ret = hb_uni_read_card_info(pdev);
+- if (ret < 0) {
+- dev_err(&hb_uni_i2c_client->dev,
+- "Failed to read card info or wrong configuration!\n");
++ return 0;
++}
++
++static void hb_studio_ctrl_remove(struct i2c_client *client)
++{
++ if (hb_studio_i2c_client == client) {
++ priv = NULL;
++ hb_studio_i2c_client = NULL;
+ }
++}
+
+- return ret;
++static const struct i2c_device_id hb_studio_ctrl_id[] = {
++ { "hb-studio-ctrl", 0 },
++ { }
++};
++MODULE_DEVICE_TABLE(i2c, hb_studio_ctrl_id);
++
++static const struct of_device_id hb_studio_ctrl_of_match[] = {
++ { .compatible = "hifiberry,hb-studio-ctrl" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, hb_studio_ctrl_of_match);
++
++static struct i2c_driver hb_studio_ctrl_driver = {
++ .driver = {
++ .name = "hb-studio-ctrl",
++ .of_match_table = hb_studio_ctrl_of_match,
++ .suppress_bind_attrs = true,
++ },
++ .probe = hb_studio_ctrl_probe,
++ .remove = hb_studio_ctrl_remove,
++ .id_table = hb_studio_ctrl_id,
+ };
+
++static void hb_studio_error_work(struct work_struct *work)
++{
++ struct hb_studio_private *p =
++ container_of(work, struct hb_studio_private, error_work);
++ unsigned long flags;
++ struct snd_pcm_substream *play, *capt;
++
++ dev_err(&hb_studio_i2c_client->dev, "PLL lock lost, stopping streams\n");
++
++ spin_lock_irqsave(&p->stream_lock, flags);
++ play = p->playback_substream;
++ capt = p->capture_substream;
++
++ if (play)
++ snd_pcm_stop(play, SNDRV_PCM_STATE_SUSPENDED);
++ if (capt)
++ snd_pcm_stop(capt, SNDRV_PCM_STATE_SUSPENDED);
++ spin_unlock_irqrestore(&p->stream_lock, flags);
++}
++
++/* FS-change / PLL-lost interrupt: hand off to the error work queue. */
++static irqreturn_t hb_studio_irq_handler(int irq, void *dev_id)
++{
++ struct hb_studio_private *p = dev_id;
++
++ schedule_work(&p->error_work);
++ return IRQ_HANDLED;
++}
++
+ static int snd_rpi_hifiberry_studio_probe(struct platform_device *pdev)
+ {
++ bool no_controls;
++ int gpio, irq;
+ int ret = 0;
+
+- /* probe for controller */
+- ret = hb_controller_probe(pdev);
++ /* wait for the I2C controller driver to have probed and populated priv */
++ if (!priv)
++ return -EPROBE_DEFER;
++
++ ret = hb_studio_validate_clk_config(pdev);
+ if (ret < 0)
+ return ret;
+
++ no_controls = of_property_read_bool(pdev->dev.of_node, "no-controls");
++
++ /*
++ * The FS-change / PLL-lost interrupt only exists on the Digi/AES
++ * board's overlay (gpios/interrupts wired to GPIO7); the DAC8x
++ * overlays don't route it at all, and there's nothing for a purely
++ * analog card to report here, so only look for it for AES cards.
++ */
++ if (priv->card_type == AES) {
++ gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0);
++ if (!gpio_is_valid(gpio))
++ return dev_err_probe(&pdev->dev, gpio, "Invalid GPIO\n");
++
++ ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_IN,
++ "hifiberry-studio-fs-change");
++ if (ret)
++ return dev_err_probe(&pdev->dev, ret, "Failed to request GPIO\n");
++
++ irq = gpio_to_irq(gpio);
++ if (irq < 0)
++ return irq;
++
++ ret = devm_request_threaded_irq(&pdev->dev, irq,
++ hb_studio_irq_handler, NULL,
++ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
++ "hifiberry-studio-fs-change", priv);
++ if (ret)
++ return dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n");
++
++ dev_info(&pdev->dev, "GPIO interrupt registered on GPIO %d (IRQ %d)\n",
++ gpio, irq);
++ }
++
+ snd_rpi_hifiberry_studio.dev = &pdev->dev;
+
+ if (pdev->dev.of_node) {
+@@ -894,7 +1257,7 @@ static int snd_rpi_hifiberry_studio_prob
+
+ dai = &snd_rpi_hifiberry_studio_dai[0];
+ i2s_node = of_parse_phandle(pdev->dev.of_node,
+- "i2s-controller", 0);
++ "i2s-controller", 0);
+
+ if (i2s_node) {
+ dai->cpus->dai_name = NULL;
+@@ -905,19 +1268,28 @@ static int snd_rpi_hifiberry_studio_prob
+ }
+
+ ret = devm_snd_soc_register_card(&pdev->dev,
+- &snd_rpi_hifiberry_studio);
+- if (ret && ret != -EPROBE_DEFER)
+- dev_err(&pdev->dev,
+- "devm_snd_soc_register_card() failed: %d\n", ret);
++ &snd_rpi_hifiberry_studio);
++ if (ret) {
++ if (ret != -EPROBE_DEFER)
++ dev_err(&pdev->dev,
++ "devm_snd_soc_register_card() failed: %d\n", ret);
++ return ret;
++ }
++
++ if (no_controls) {
++ dev_info(&pdev->dev, "ALSA controls disabled (no-controls)\n");
++ return 0;
++ }
+
+ /* as we do not have components use card-controls */
+- ret = hb_uni_add_card_controls(pdev);
++ ret = hb_studio_add_card_controls(pdev);
+
+ return ret;
+ }
+
+ static const struct of_device_id snd_rpi_hifiberry_studio_of_match[] = {
+ { .compatible = "hifiberry,hifiberry-studio-dac8x", },
++ { .compatible = "hifiberry,hifiberry-studio", },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_of_match);
+@@ -931,8 +1303,29 @@ static struct platform_driver snd_rpi_hi
+ .probe = snd_rpi_hifiberry_studio_probe,
+ };
+
+-module_platform_driver(snd_rpi_hifiberry_studio_driver);
++static int __init hb_studio_driver_init(void)
++{
++ int ret;
++
++ ret = i2c_add_driver(&hb_studio_ctrl_driver);
++ if (ret)
++ return ret;
++
++ ret = platform_driver_register(&snd_rpi_hifiberry_studio_driver);
++ if (ret)
++ i2c_del_driver(&hb_studio_ctrl_driver);
++
++ return ret;
++}
++module_init(hb_studio_driver_init);
++
++static void __exit hb_studio_driver_exit(void)
++{
++ platform_driver_unregister(&snd_rpi_hifiberry_studio_driver);
++ i2c_del_driver(&hb_studio_ctrl_driver);
++}
++module_exit(hb_studio_driver_exit);
+
+ MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+-MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver");
++MODULE_DESCRIPTION("HiFiBerry Studio soundcard driver (DAC8x, Digi)");
+ MODULE_LICENSE("GPL");
--- /dev/null
+From b97835145ed1bf30b5c27fa5686956f70d915f55 Mon Sep 17 00:00:00 2001
+From: j-schambacher <joerg@hifiberry.com>
+Date: Mon, 13 Jul 2026 09:16:54 +0200
+Subject: [PATCH] overlays: hifiberry-studio: add Digi/AES overlay, register
+ controller
+
+Add hifiberry-studio-digi-overlay.dts for the Studio Digi/AES card,
+wired the same way as the DAC8x PRO overlay (i2s1, clk-provider), plus
+the GPIO7 FS-change/PLL-lost interrupt line the driver now requests
+automatically for AES-type cards.
+
+Both existing DAC8x overlays use the "hb_studio_ctrl" I2C client node
+(compatible "hifiberry,hb-studio-ctrl") so the driver's separate
+controller probe can be attached.
+A "no_controls" can be used to skip registering the ALSA controls for
+a card. The PRO overlay also switches to the new
+"hifiberry,hifiberry-studio" compatible string and
+adds the informational "card-type" property; the base overlay keeps
+"hifiberry,hifiberry-studio-dac8x" for backwards compatibility.
+
+Signed-off-by: j-schambacher <joerg@hifiberry.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 17 +++-
+ .../hifiberry-studio-dac8x-overlay.dts | 12 ++-
+ .../hifiberry-studio-dac8x-pro-overlay.dts | 14 +++-
+ .../hifiberry-studio-digi-overlay.dts | 83 +++++++++++++++++++
+ 5 files changed, 120 insertions(+), 7 deletions(-)
+ create mode 100644 arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -109,6 +109,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ hifiberry-digi-pro.dtbo \
+ hifiberry-studio-dac8x.dtbo \
+ hifiberry-studio-dac8x-pro.dtbo \
++ hifiberry-studio-digi.dtbo \
+ highperi.dtbo \
+ hy28a.dtbo \
+ hy28b.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -2127,14 +2127,23 @@ Params: <None>
+
+ Name: hifiberry-studio-dac8x
+ Info: Configures the HifiBerry Studio DAC8x audio card
+-Load: dtoverlay=hifiberry-studio-dac8x
+-Params: <None>
++Load: dtoverlay=hifiberry-studio-dac8x,<param>=<val>
++Params: no_controls Do not register the ALSA controls for
++ this card
+
+
+ Name: hifiberry-studio-dac8x-pro
+ Info: Configures the HifiBerry Studio DAC8x PRO audio card
+-Load: dtoverlay=hifiberry-studio-dac8x-pro
+-Params: <None>
++Load: dtoverlay=hifiberry-studio-dac8x-pro,<param>=<val>
++Params: no_controls Do not register the ALSA controls for
++ this card
++
++
++Name: hifiberry-studio-digi
++Info: Configures the HifiBerry Studio Digi/AES audio card
++Load: dtoverlay=hifiberry-studio-digi,<param>=<val>
++Params: no_controls Do not register the ALSA controls for
++ this card
+
+
+ Name: highperi
+--- a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts
++++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts
+@@ -10,6 +10,13 @@
+ target = <&i2c1>;
+ __overlay__ {
+ status = "okay";
++ #address-cells = <1>;
++ #size-cells = <0>;
++ hb_studio_ctrl: hb_studio_ctrl@10 {
++ compatible = "hifiberry,hb-studio-ctrl";
++ reg = <0x10>;
++ status = "okay";
++ };
+ };
+ };
+
+@@ -49,11 +56,14 @@
+
+ fragment@4 {
+ target = <&sound>;
+- __overlay__ {
++ hifiberry_studio: __overlay__ {
+ compatible = "hifiberry,hifiberry-studio-dac8x";
+ i2s-controller = <&i2s_clk_producer>;
+ status = "okay";
+ };
+ };
+
++ __overrides__ {
++ no_controls = <&hifiberry_studio>,"no-controls?";
++ };
+ };
+--- a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts
++++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts
+@@ -10,6 +10,13 @@
+ target = <&i2c1>;
+ __overlay__ {
+ status = "okay";
++ #address-cells = <1>;
++ #size-cells = <0>;
++ hb_studio_ctrl: hb_studio_ctrl@10 {
++ compatible = "hifiberry,hb-studio-ctrl";
++ reg = <0x10>;
++ status = "okay";
++ };
+ };
+ };
+
+@@ -49,12 +56,15 @@
+
+ fragment@4 {
+ target = <&sound>;
+- __overlay__ {
+- compatible = "hifiberry,hifiberry-studio-dac8x";
++ hifiberry_studio: __overlay__ {
++ compatible = "hifiberry,hifiberry-studio";
+ i2s-controller = <&i2s_clk_consumer>;
+ clk-provider;
+ status = "okay";
+ };
+ };
+
++ __overrides__ {
++ no_controls = <&hifiberry_studio>,"no-controls?";
++ };
+ };
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/hifiberry-studio-digi-overlay.dts
+@@ -0,0 +1,83 @@
++// SPDX-License-Identifier: GPL-2.0
++// Definitions for HiFiBerry Studio Digi/AES soundcard
++/dts-v1/;
++/plugin/;
++
++#include <dt-bindings/gpio/gpio.h>
++#include <dt-bindings/interrupt-controller/irq.h>
++#include <dt-bindings/pinctrl/bcm2835.h>
++
++/ {
++ compatible = "brcm,bcm2712";
++
++ fragment@0 {
++ target = <&i2c1>;
++ __overlay__ {
++ status = "okay";
++ #address-cells = <1>;
++ #size-cells = <0>;
++ hb_studio_ctrl: hb_studio_ctrl@10 {
++ compatible = "hifiberry,hb-studio-ctrl";
++ reg = <0x10>;
++ status = "okay";
++ };
++ };
++ };
++
++ fragment@1 {
++ target = <&gpio>;
++ __overlay__ {
++ rp1_i2s1_digi: rp1_i2s1_digi {
++ function = "i2s1";
++ pins = "gpio18", "gpio19", "gpio20",
++ "gpio21", "gpio22", "gpio23",
++ "gpio24", "gpio25", "gpio26",
++ "gpio27";
++ bias-disable;
++ };
++ digi_int_pin: digi_int_pin {
++ pins = "gpio7";
++ bias-pull-up;
++ };
++ };
++ };
++
++ fragment@2 {
++ target = <&i2s_clk_consumer>;
++ __overlay__ {
++ pinctrl-names = "default";
++ pinctrl-0 = <&rp1_i2s1_digi>;
++ status = "okay";
++ };
++ };
++
++ fragment@3 {
++ target-path = "/";
++ __overlay__ {
++ dummy-codec {
++ #sound-dai-cells = <0>;
++ compatible = "snd-soc-dummy";
++ status = "okay";
++ };
++ };
++ };
++
++ fragment@4 {
++ target = <&sound>;
++ hifiberry_studio: __overlay__ {
++ compatible = "hifiberry,hifiberry-studio";
++ i2s-controller = <&i2s_clk_consumer>;
++ clk-provider;
++ pinctrl-names = "default";
++ pinctrl-0 = <&digi_int_pin>;
++ interrupt-parent = <&gpio>;
++ interrupts = <7 IRQ_TYPE_EDGE_BOTH>;
++ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
++ status = "okay";
++ };
++ };
++
++ __overrides__ {
++ no_controls = <&hifiberry_studio>,"no-controls?";
++ };
++};
--- /dev/null
+From 33cc967e07b0a5439fdcfdf942c904e00f4e794e Mon Sep 17 00:00:00 2001
+From: Rachmat Aditiya <raditiya@me.com>
+Date: Tue, 14 Jul 2026 02:57:43 +0700
+Subject: [PATCH] staging: vchiq_arm: use one DMA device to map and unmap bulk
+ pagelists
+
+create_pagelist() maps the bulk scatterlist with instance->state->dev
+(the vchiq platform device) while free_pagelist() and
+cleanup_pagelistinfo() unmap it with g_dma_dev - on BCM2711 the
+brcm,bcm2711-dma device resolved for use_36bit_addrs, whose dma-ranges
+differ from the vchiq node's. The coherent pagelist buffer is likewise
+allocated on instance->state->dev but freed on g_dma_dev.
+
+Mapping and unmapping with different devices breaks every userspace
+bulk transfer on Pi 4 arm64:
+
+ - the 36-bit addrs[] entries handed to the VPU are computed from
+ sg_dma_address() under the wrong device's dma-ranges, so the VPU
+ reads/writes the wrong bus addresses (bulk WRITE payloads arrive
+ as garbage; under sustained load the VPU stops responding to
+ mailbox requests entirely);
+
+ - the first bulk READ oopses in free_pagelist(): the DMA_FROM_DEVICE
+ dma_unmap_sg() cache invalidate mis-translates the dma address back
+ to phys under the other device's dma-ranges (0xf41e8e80 = phys
+ 0x341e8e80 still carrying the vchiq device's 0xc0000000 alias):
+
+ Unable to handle kernel paging request at virtual address
+ ffffff80f41e8e80
+ CPU: 0 PID: 80 Comm: vchiq-slot/0
+ Call trace:
+ dcache_inval_poc+0x28/0x58 (P)
+ dma_direct_unmap_sg+0x224/0x260
+ dma_unmap_sg_attrs+0x60/0x148
+ free_pagelist.isra.0+0x54/0x1f8
+ slot_handler_func+0x4b8/0xdf0
+
+rpi-6.12.y paired these consistently: dma_map_sg/dma_unmap_sg both on
+g_dma_dev, dma_alloc_coherent/dma_free_coherent both on
+instance->state->dev. Restore that pairing.
+
+Verified on a Pi 4B Rev 1.5: on an unpatched 6.18 kernel a
+vc.ril.video_splitter payload loopback from a 64-bit userland oopses
+as above and vc.ril.video_decode consumes garbage; with this change
+the loopback round-trips byte-identical and decode runs at full rate.
+Nothing in-tree exercises userspace vchiq bulk on arm64 (the kernel
+MMAL clients use vc-sm-cma zero-copy imports), which is why this went
+unnoticed.
+
+Fixes: 35ef14168c34 ("staging: vchiq_arm: Set up dma ranges on child devices")
+Link: https://github.com/raspberrypi/linux/issues/7493
+Signed-off-by: Rachmat Aditiya <raditiya@me.com>
+---
+ .../staging/vc04_services/interface/vchiq_arm/vchiq_core.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c
++++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c
+@@ -1474,8 +1474,8 @@ cleanup_pagelistinfo(struct vchiq_instan
+ dma_pool_free(g_dma_pool, pagelistinfo->pagelist,
+ pagelistinfo->dma_addr);
+ } else {
+- dma_free_coherent(g_dma_dev, pagelistinfo->pagelist_buffer_size,
+- pagelistinfo->pagelist, pagelistinfo->dma_addr);
++ dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
++ pagelistinfo->pagelist, pagelistinfo->dma_addr);
+ }
+ }
+
+@@ -1641,7 +1641,7 @@ create_pagelist(struct vchiq_instance *i
+ count -= len;
+ }
+
+- dma_buffers = dma_map_sg(instance->state->dev,
++ dma_buffers = dma_map_sg(g_dma_dev,
+ scatterlist,
+ num_pages,
+ pagelistinfo->dma_dir);
--- /dev/null
+From ead0a299761ea17229f93ef009d238a6c2779ad3 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 15 Jul 2026 11:32:13 +0100
+Subject: [PATCH] mailbox: rp1: Don't poll for TX completion
+
+The RP1 mailbox + firmware interface is currently synchronous, with
+only one outstanding message at a time and the same buffer used for the
+reply. It is therefore of no use to know when the transmit has
+completed, only that the reply is ready. This is TXDONE_BY_ACK mode,
+which is automatically selected if neither txdone_irq nor txdone_poll
+are provided.
+
+This change increases performance and reduces hrtimer usage
+significantly, but it requires the mailbox client to call
+mbox_client_txdone after each transmission.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/mailbox/rp1-mailbox.c | 14 --------------
+ 1 file changed, 14 deletions(-)
+
+--- a/drivers/mailbox/rp1-mailbox.c
++++ b/drivers/mailbox/rp1-mailbox.c
+@@ -101,22 +101,10 @@ static void rp1_shutdown(struct mbox_cha
+ writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
+ }
+
+-static bool rp1_last_tx_done(struct mbox_chan *chan)
+-{
+- struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+- unsigned int event = rp1_chan_event(chan);
+- unsigned int evs;
+-
+- evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
+-
+- return !(evs & event);
+-}
+-
+ static const struct mbox_chan_ops rp1_mbox_chan_ops = {
+ .send_data = rp1_send_data,
+ .startup = rp1_startup,
+ .shutdown = rp1_shutdown,
+- .last_tx_done = rp1_last_tx_done
+ };
+
+ static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
+@@ -168,8 +156,6 @@ static int rp1_mbox_probe(struct platfor
+ if (!chans)
+ return -ENOMEM;
+
+- mbox->controller.txdone_poll = true;
+- mbox->controller.txpoll_period = 5;
+ mbox->controller.ops = &rp1_mbox_chan_ops;
+ mbox->controller.of_xlate = &rp1_mbox_xlate;
+ mbox->controller.dev = dev;
--- /dev/null
+From 636d6cb815a0b1fa272c6115b54003066fca8361 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 15 Jul 2026 11:40:33 +0100
+Subject: [PATCH] firmware: rp1-fw: Call mbox_client_txdone after TX
+
+Now that TX polling has been disabled and the mailbox is running in
+TXDONE_BY_ACK mode, call mbox_client_txdone after each transmission to
+tell the mailbox framework that the transmission has completed.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/firmware/rp1-fw.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/drivers/firmware/rp1-fw.c
++++ b/drivers/firmware/rp1-fw.c
+@@ -80,6 +80,13 @@ int rp1_firmware_message(struct rp1_firm
+ reinit_completion(&fw->c);
+ ret = mbox_send_message(fw->chan, NULL);
+ if (ret >= 0) {
++ /*
++ * The doorbell write in rp1_send_data() is already complete by
++ * the time mbox_send_message() returns, so tell the mailbox
++ * core immediately rather than have it poll for tx-done.
++ */
++ mbox_client_txdone(fw->chan, 0);
++
+ if (wait_for_completion_timeout(&fw->c, HZ))
+ ret = 0;
+ else
--- /dev/null
+From 9467e93508f005932e5b039ed33969cc82c9bbdb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ma=C3=ADra=20Canal?= <mcanal@igalia.com>
+Date: Wed, 15 Jul 2026 19:08:39 -0300
+Subject: [PATCH] drm/v3d: Reach the GMP through the hub registers on V3D 7.x
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+v3d_idle_axi() drains the GPU's memory interface for a safe powerdown by
+using the V3D_GMP_CFG register. It reached both registers with V3D_CORE_READ
+and V3D_CORE_WRITE.
+
+On V3D 7.x the GMP is no longer a per-core block; it lives in the hub
+register region. Reaching it through the per-core window there addresses
+the wrong region.
+
+Select the hub accessors (V3D_{READ,WRITE}) for the GMP on V3D 7.x and
+keep the per-core path for earlier generations.
+
+Cc: stable@vger.kernel.org
+Fixes: 0ad5bc1ce463 ("drm/v3d: fix up register addresses for V3D 7.x")
+Signed-off-by: MaÃra Canal <mcanal@igalia.com>
+---
+ drivers/gpu/drm/v3d/v3d_gem.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/gpu/drm/v3d/v3d_gem.c
++++ b/drivers/gpu/drm/v3d/v3d_gem.c
+@@ -39,6 +39,18 @@ v3d_init_core(struct v3d_dev *v3d, int c
+ static void
+ v3d_idle_axi(struct v3d_dev *v3d, int core)
+ {
++ if (v3d->ver >= V3D_GEN_71) {
++ V3D_WRITE(V3D_GMP_CFG(v3d->ver), V3D_GMP_CFG_STOP_REQ);
++
++ if (wait_for((V3D_READ(V3D_GMP_STATUS(v3d->ver)) &
++ (V3D_GMP_STATUS_RD_COUNT_MASK |
++ V3D_GMP_STATUS_WR_COUNT_MASK |
++ V3D_GMP_STATUS_CFG_BUSY)) == 0, 100)) {
++ DRM_ERROR("Failed to wait for safe GMP shutdown\n");
++ }
++ return;
++ }
++
+ V3D_CORE_WRITE(core, V3D_GMP_CFG(v3d->ver), V3D_GMP_CFG_STOP_REQ);
+
+ if (wait_for((V3D_CORE_READ(core, V3D_GMP_STATUS(v3d->ver)) &
--- /dev/null
+From fcb324363479844c8bed1649122b3bd01a657f24 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ma=C3=ADra=20Canal?= <mcanal@igalia.com>
+Date: Wed, 15 Jul 2026 08:59:28 -0300
+Subject: [PATCH] drm/v3d: Idle AXI transactions before disabling the clock on
+ suspend
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Currently, v3d_power_suspend() removes the GPU clock without first
+quiescing the GPU's memory interface (AXI). If the clock is cut while the
+core still has outstanding AXI transactions in flight, the hardware is
+frozen mid-transaction. That corrupted state survives the power cycle, and
+the first job submitted after the next resume will cause a GPU hang
+accompanied by an L2T "pte invalid" MMU fault.
+
+The hardware already provides a safe-powerdown sequence for this: request
+the GMP to stop and wait for outstanding reads/writes to drain
+(v3d_idle_axi()), plus the GCA safe shutdown on pre-4.1 cores
+(v3d_idle_gca()). The driver implements both, but the runtime PM support
+added later never invoked them when powering the GPU down.
+
+Perform the safe-powerdown sequence in v3d_power_suspend() before
+disabling the clock, while the core is still powered.
+
+Cc: stable@vger.kernel.org
+Link: https://github.com/raspberrypi/linux/issues/7443
+Link: https://github.com/raspberrypi/linux/issues/7488
+Fixes: 17af1d14deaf ("drm/v3d: Introduce Runtime Power Management")
+Signed-off-by: MaÃra Canal <mcanal@igalia.com>
+---
+ drivers/gpu/drm/v3d/v3d_drv.h | 2 ++
+ drivers/gpu/drm/v3d/v3d_gem.c | 4 ++--
+ drivers/gpu/drm/v3d/v3d_power.c | 7 +++++++
+ 3 files changed, 11 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/v3d/v3d_drv.h
++++ b/drivers/gpu/drm/v3d/v3d_drv.h
+@@ -578,6 +578,8 @@ int v3d_gem_init(struct drm_device *dev)
+ void v3d_gem_destroy(struct drm_device *dev);
+ void v3d_reset_sms(struct v3d_dev *v3d);
+ void v3d_reset(struct v3d_dev *v3d);
++void v3d_idle_axi(struct v3d_dev *v3d, int core);
++void v3d_idle_gca(struct v3d_dev *v3d);
+ void v3d_invalidate_caches(struct v3d_dev *v3d);
+ void v3d_clean_caches(struct v3d_dev *v3d);
+
+--- a/drivers/gpu/drm/v3d/v3d_gem.c
++++ b/drivers/gpu/drm/v3d/v3d_gem.c
+@@ -36,7 +36,7 @@ v3d_init_core(struct v3d_dev *v3d, int c
+ V3D_CORE_WRITE(core, V3D_CTL_L2TFLEND, ~0);
+ }
+
+-static void
++void
+ v3d_idle_axi(struct v3d_dev *v3d, int core)
+ {
+ if (v3d->ver >= V3D_GEN_71) {
+@@ -61,7 +61,7 @@ v3d_idle_axi(struct v3d_dev *v3d, int co
+ }
+ }
+
+-static void
++void
+ v3d_idle_gca(struct v3d_dev *v3d)
+ {
+ if (v3d->ver >= V3D_GEN_41)
+--- a/drivers/gpu/drm/v3d/v3d_power.c
++++ b/drivers/gpu/drm/v3d/v3d_power.c
+@@ -55,8 +55,15 @@ int v3d_power_suspend(struct device *dev
+ /* Always clean V3D caches on shutdown. */
+ v3d_clean_caches(v3d);
+
++ /* Wait until V3D has no active or pending AXI transactions. */
++ v3d_idle_axi(v3d, 0);
++ v3d_idle_gca(v3d);
++
+ ret = v3d_suspend_sms(v3d);
+ if (ret) {
++ /* Staying active: undo the GMP STOP_REQ from v3d_idle_axi(). */
++ V3D_WRITE(V3D_GMP_CFG(v3d->ver),
++ V3D_READ(V3D_GMP_CFG(v3d->ver)) & ~V3D_GMP_CFG_STOP_REQ);
+ v3d_irq_enable(v3d);
+ return ret;
+ }
--- /dev/null
+From eacf886641f744bb0ba4afdb26bccb2de86fb7e7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ma=C3=ADra=20Canal?= <mcanal@igalia.com>
+Date: Thu, 16 Jul 2026 10:04:16 -0300
+Subject: [PATCH] mmc: block: Fix pending_writes underflow on non-CQE hosts
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The posted-write accounting mirrors in_flight[], but on the plain blk-mq
+completion path (hosts with neither CQE nor HSQ) pending_writes is
+decremented twice per write: once in mmc_blk_mq_complete_rq() and again
+in mmc_blk_mq_dec_in_flight(). Both run for a single request, since
+mmc_blk_mq_post_req() first triggers the .complete callback via
+blk_mq_complete_request() and then calls mmc_blk_mq_dec_in_flight().
+As in_flight[] is only decremented in the latter, pending_writes drifts
+one below in_flight[] for every completed write and underflows.
+
+This stayed hidden on the CQE/HSQ hosts the feature targets, where
+completion goes solely through mmc_blk_cqe_complete_rq() and decrements
+once. It surfaces on plain-mq hosts such as the bcm2835-sdhost, tripping
+the WARN_ON_ONCE() on the first write once the rootfs is remounted
+read-write:
+
+ ------------[ cut here ]------------
+ WARNING: CPU: 0 PID: 84 at drivers/mmc/core/queue.c:350 mmc_mq_queue_rq+0x2a0/0x2f8
+ Modules linked in: sch_fq_codel uinput i2c_dev zram lz4_compress fuse drm drm_panel_orientation_quirks backlight nfnetlink ipv6 libsha1
+ CPU: 0 UID: 0 PID: 84 Comm: kworker/0:1H Not tainted 6.18.38-v8-tile-alloc-256-32+ #15 PREEMPT
+ Hardware name: Raspberry Pi 3 Model B Plus Rev 1.3 (DT)
+ Workqueue: kblockd blk_mq_requeue_work
+ pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
+ pc : mmc_mq_queue_rq+0x2a0/0x2f8
+ lr : mmc_mq_queue_rq+0x284/0x2f8
+ Call trace:
+ mmc_mq_queue_rq+0x2a0/0x2f8 (P)
+ blk_mq_dispatch_rq_list+0x2c8/0x718
+ __blk_mq_sched_dispatch_requests+0xec/0x570
+ blk_mq_sched_dispatch_requests+0x3c/0x88
+ blk_mq_run_hw_queue+0xf4/0x128
+ blk_mq_run_hw_queues+0xc4/0x140
+ blk_mq_requeue_work+0x188/0x1c0
+ process_scheduled_works+0x180/0x3d0
+ worker_thread+0x268/0x3e8
+ kthread+0x140/0x250
+ ret_from_fork+0x10/0x20
+ ---[ end trace 0000000000000000 ]---
+
+Decrement pending_writes only where in_flight[] is decremented, dropping
+the redundant decrement in mmc_blk_mq_complete_rq() so the counter tracks
+in_flight[] exactly. The CQE path is unaffected.
+
+Fixes: e6c1e862b2b8 ("mmc: restrict posted write counts for SD cards in CQ mode")
+Signed-off-by: MaÃra Canal <mcanal@igalia.com>
+---
+ drivers/mmc/core/block.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/mmc/core/block.c
++++ b/drivers/mmc/core/block.c
+@@ -2129,8 +2129,6 @@ static void mmc_blk_mq_complete_rq(struc
+ struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req);
+ unsigned int nr_bytes = mqrq->brq.data.bytes_xfered;
+
+- if (req_op(req) == REQ_OP_WRITE)
+- mq->pending_writes--;
+ if (nr_bytes) {
+ if (blk_update_request(req, BLK_STS_OK, nr_bytes))
+ blk_mq_requeue_request(req, true);
--- /dev/null
+From dd42592103ea9dc5ced6fd761bf476820e1977af Mon Sep 17 00:00:00 2001
+From: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Date: Sat, 5 Jul 2025 13:05:13 +0300
+Subject: [PATCH] drm/vc4: hdmi: switch to generic CEC helpers
+
+[ Upstream commit cf207ea2c39d2809eb6e579279178dfdc89fa906 ]
+
+Switch VC4 driver to using CEC helpers code, simplifying hotplug and
+registration / cleanup. The existing vc4_hdmi_cec_release() is kept for
+now.
+
+Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Reviewed-by: Maxime Ripard <mripard@kernel.org>
+Tested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Link: https://lore.kernel.org/r/20250705-drm-hdmi-connector-cec-v7-1-d14fa0c31b74@oss.qualcomm.com
+Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+---
+ drivers/gpu/drm/vc4/Kconfig | 1 +
+ drivers/gpu/drm/vc4/vc4_hdmi.c | 137 +++++++++++++--------------------
+ drivers/gpu/drm/vc4/vc4_hdmi.h | 1 -
+ 3 files changed, 55 insertions(+), 84 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/Kconfig
++++ b/drivers/gpu/drm/vc4/Kconfig
+@@ -35,6 +35,7 @@ config DRM_VC4_HDMI_CEC
+ bool "Broadcom VC4 HDMI CEC Support"
+ depends on DRM_VC4
+ select CEC_CORE
++ select DRM_DISPLAY_HDMI_CEC_HELPER
+ help
+ Choose this option if you have a Broadcom VC4 GPU
+ and want to use CEC.
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
+@@ -32,6 +32,7 @@
+ */
+
+ #include <drm/display/drm_hdmi_audio_helper.h>
++#include <drm/display/drm_hdmi_cec_helper.h>
+ #include <drm/display/drm_hdmi_helper.h>
+ #include <drm/display/drm_hdmi_state_helper.h>
+ #include <drm/display/drm_scdc_helper.h>
+@@ -381,14 +382,6 @@ static void vc4_hdmi_handle_hotplug(stru
+
+ drm_atomic_helper_connector_hdmi_hotplug(connector, status);
+
+- if (status == connector_status_disconnected) {
+- cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
+- return;
+- }
+-
+- cec_s_phys_addr(vc4_hdmi->cec_adap,
+- connector->display_info.source_physical_address, false);
+-
+ if (status != connector_status_connected)
+ return;
+
+@@ -2410,8 +2403,8 @@ static irqreturn_t vc4_cec_irq_handler_r
+ struct vc4_hdmi *vc4_hdmi = priv;
+
+ if (vc4_hdmi->cec_rx_msg.len)
+- cec_received_msg(vc4_hdmi->cec_adap,
+- &vc4_hdmi->cec_rx_msg);
++ drm_connector_hdmi_cec_received_msg(&vc4_hdmi->connector,
++ &vc4_hdmi->cec_rx_msg);
+
+ return IRQ_HANDLED;
+ }
+@@ -2421,15 +2414,17 @@ static irqreturn_t vc4_cec_irq_handler_t
+ struct vc4_hdmi *vc4_hdmi = priv;
+
+ if (vc4_hdmi->cec_tx_ok) {
+- cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_OK,
+- 0, 0, 0, 0);
++ drm_connector_hdmi_cec_transmit_done(&vc4_hdmi->connector,
++ CEC_TX_STATUS_OK,
++ 0, 0, 0, 0);
+ } else {
+ /*
+ * This CEC implementation makes 1 retry, so if we
+ * get a NACK, then that means it made 2 attempts.
+ */
+- cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_NACK,
+- 0, 2, 0, 0);
++ drm_connector_hdmi_cec_transmit_done(&vc4_hdmi->connector,
++ CEC_TX_STATUS_NACK,
++ 0, 2, 0, 0);
+ }
+ return IRQ_HANDLED;
+ }
+@@ -2586,9 +2581,9 @@ static irqreturn_t vc4_cec_irq_handler(i
+ return ret;
+ }
+
+-static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
++static int vc4_hdmi_cec_enable(struct drm_connector *connector)
+ {
+- struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
++ struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+ struct drm_device *drm = vc4_hdmi->connector.dev;
+ /* clock period in microseconds */
+ const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
+@@ -2653,9 +2648,9 @@ static int vc4_hdmi_cec_enable(struct ce
+ return 0;
+ }
+
+-static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
++static int vc4_hdmi_cec_disable(struct drm_connector *connector)
+ {
+- struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
++ struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+ struct drm_device *drm = vc4_hdmi->connector.dev;
+ unsigned long flags;
+ int idx;
+@@ -2689,17 +2684,17 @@ static int vc4_hdmi_cec_disable(struct c
+ return 0;
+ }
+
+-static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
++static int vc4_hdmi_cec_adap_enable(struct drm_connector *connector, bool enable)
+ {
+ if (enable)
+- return vc4_hdmi_cec_enable(adap);
++ return vc4_hdmi_cec_enable(connector);
+ else
+- return vc4_hdmi_cec_disable(adap);
++ return vc4_hdmi_cec_disable(connector);
+ }
+
+-static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
++static int vc4_hdmi_cec_adap_log_addr(struct drm_connector *connector, u8 log_addr)
+ {
+- struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
++ struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+ struct drm_device *drm = vc4_hdmi->connector.dev;
+ unsigned long flags;
+ int idx;
+@@ -2725,10 +2720,10 @@ static int vc4_hdmi_cec_adap_log_addr(st
+ return 0;
+ }
+
+-static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
++static int vc4_hdmi_cec_adap_transmit(struct drm_connector *connector, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+ {
+- struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
++ struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+ struct drm_device *dev = vc4_hdmi->connector.dev;
+ unsigned long flags;
+ u32 val;
+@@ -2771,84 +2766,65 @@ static int vc4_hdmi_cec_adap_transmit(st
+ return 0;
+ }
+
+-static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = {
+- .adap_enable = vc4_hdmi_cec_adap_enable,
+- .adap_log_addr = vc4_hdmi_cec_adap_log_addr,
+- .adap_transmit = vc4_hdmi_cec_adap_transmit,
+-};
+-
+-static void vc4_hdmi_cec_release(void *ptr)
+-{
+- struct vc4_hdmi *vc4_hdmi = ptr;
+-
+- cec_unregister_adapter(vc4_hdmi->cec_adap);
+- vc4_hdmi->cec_adap = NULL;
+-}
+-
+-static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
++static int vc4_hdmi_cec_init(struct drm_connector *connector)
+ {
+- struct cec_connector_info conn_info;
++ struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+ struct platform_device *pdev = vc4_hdmi->pdev;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+- if (!of_property_present(dev->of_node, "interrupts")) {
+- dev_warn(dev, "'interrupts' DT property is missing, no CEC\n");
+- return 0;
+- }
+-
+- vc4_hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops,
+- vc4_hdmi,
+- vc4_hdmi->variant->card_name,
+- CEC_CAP_DEFAULTS |
+- CEC_CAP_CONNECTOR_INFO, 1);
+- ret = PTR_ERR_OR_ZERO(vc4_hdmi->cec_adap);
+- if (ret < 0)
+- return ret;
+-
+- cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector);
+- cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info);
+-
+ if (vc4_hdmi->variant->external_irq_controller) {
+ ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-rx"),
+ vc4_cec_irq_handler_rx_bare,
+ vc4_cec_irq_handler_rx_thread, 0,
+ "vc4 hdmi cec rx", vc4_hdmi);
+ if (ret)
+- goto err_delete_cec_adap;
++ return ret;
+
+ ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-tx"),
+ vc4_cec_irq_handler_tx_bare,
+ vc4_cec_irq_handler_tx_thread, 0,
+ "vc4 hdmi cec tx", vc4_hdmi);
+ if (ret)
+- goto err_delete_cec_adap;
++ return ret;
+ } else {
+ ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
+ vc4_cec_irq_handler,
+ vc4_cec_irq_handler_thread, 0,
+ "vc4 hdmi cec", vc4_hdmi);
+ if (ret)
+- goto err_delete_cec_adap;
++ return ret;
+ }
+
+- ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev);
+- if (ret < 0)
+- goto err_delete_cec_adap;
++ return 0;
++}
++
++static const struct drm_connector_hdmi_cec_funcs vc4_hdmi_cec_funcs = {
++ .init = vc4_hdmi_cec_init,
++ .enable = vc4_hdmi_cec_adap_enable,
++ .log_addr = vc4_hdmi_cec_adap_log_addr,
++ .transmit = vc4_hdmi_cec_adap_transmit,
++};
++
++static int vc4_hdmi_cec_register(struct vc4_hdmi *vc4_hdmi)
++{
++ struct platform_device *pdev = vc4_hdmi->pdev;
++ struct device *dev = &pdev->dev;
++
++ if (!of_property_present(dev->of_node, "interrupts")) {
++ dev_warn(dev, "'interrupts' DT property is missing, no CEC\n");
++ return 0;
++ }
+
+ /*
+- * NOTE: Strictly speaking, we should probably use a DRM-managed
+- * registration there to avoid removing the CEC adapter by the
+- * time the DRM driver doesn't have any user anymore.
++ * NOTE: the CEC adapter will be unregistered by drmm cleanup from
++ * drm_managed_release(), which is called from drm_dev_release()
++ * during device unbind.
+ *
+ * However, the CEC framework already cleans up the CEC adapter
+ * only when the last user has closed its file descriptor, so we
+ * don't need to handle it in DRM.
+ *
+- * By the time the device-managed hook is executed, we will give
+- * up our reference to the CEC adapter and therefore don't
+- * really care when it's actually freed.
+- *
+ * There's still a problematic sequence: if we unregister our
+ * CEC adapter, but the userspace keeps a handle on the CEC
+ * adapter but not the DRM device for some reason. In such a
+@@ -2859,19 +2835,14 @@ static int vc4_hdmi_cec_init(struct vc4_
+ * the CEC framework already handles this too, by calling
+ * cec_is_registered() in cec_ioctl() and cec_poll().
+ */
+- ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi);
+- if (ret)
+- return ret;
+-
+- return 0;
+-
+-err_delete_cec_adap:
+- cec_delete_adapter(vc4_hdmi->cec_adap);
+-
+- return ret;
++ return drmm_connector_hdmi_cec_register(&vc4_hdmi->connector,
++ &vc4_hdmi_cec_funcs,
++ vc4_hdmi->variant->card_name,
++ 1,
++ &pdev->dev);
+ }
+ #else
+-static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi)
++static int vc4_hdmi_cec_register(struct vc4_hdmi *vc4_hdmi)
+ {
+ return 0;
+ }
+@@ -3289,7 +3260,7 @@ static int vc4_hdmi_bind(struct device *
+ if (ret)
+ goto err_put_runtime_pm;
+
+- ret = vc4_hdmi_cec_init(vc4_hdmi);
++ ret = vc4_hdmi_cec_register(vc4_hdmi);
+ if (ret)
+ goto err_put_runtime_pm;
+
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.h
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.h
+@@ -151,7 +151,6 @@ struct vc4_hdmi {
+ */
+ bool disable_wifi_frequencies;
+
+- struct cec_adapter *cec_adap;
+ struct cec_msg cec_rx_msg;
+ bool cec_tx_ok;
+ bool cec_irq_was_rx;
--- /dev/null
+From 77f88e9e508a7dea34d6f5a8e1cf9034a9bae5a9 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Tue, 21 Jul 2026 19:51:03 +0200
+Subject: [PATCH] misc: rp1-pio: release DMA channel outside pio->lock
+
+rp1_pio_sm_dma_free() ends in dma_release_channel(), which sleeps while
+removing the sysfs links. Running it under pio->lock in SM_CONFIG_XFER
+triggers "BUG: scheduling while atomic" and hangs the machine when a
+client keeps reconfiguring a transfer.
+
+The claim bit stays set for this client across the unlock, so the
+channel remains reserved against other clients while it is released.
+
+Fixes: 0b7a604d73f3 ("misc: Add RP1 PIO driver")
+Link: https://github.com/raspberrypi/linux/issues/7512
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/misc/rp1-pio.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -618,6 +618,7 @@ static int rp1_pio_sm_config_xfer_intern
+ struct dma_info *dma = NULL;
+ uint32_t dma_mask;
+ char chan_name[4];
++ bool reconfigure = false;
+ int ret = 0;
+
+ if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
+@@ -633,7 +634,7 @@ static int rp1_pio_sm_config_xfer_intern
+ if (!(pio->claimed_dmas & dma_mask & ~client->claimed_dmas)) {
+ dma = &pio->dma_configs[sm][dir];
+ if (client->claimed_dmas & dma_mask)
+- rp1_pio_sm_dma_free(dev, dma);
++ reconfigure = true;
+ pio->claimed_dmas |= dma_mask;
+ client->claimed_dmas |= dma_mask;
+ }
+@@ -641,6 +642,10 @@ static int rp1_pio_sm_config_xfer_intern
+ if (!dma)
+ return -EBUSY;
+
++ /* dma_release_channel() sleeps, so free the old channel outside the lock. */
++ if (reconfigure)
++ rp1_pio_sm_dma_free(dev, dma);
++
+ dma->buf_size = buf_size;
+ /* Round up the allocations */
+ buf_size = ROUND_UP(buf_size, PAGE_SIZE);
--- /dev/null
+From 6f6c00a7b67bd0f45cc8631618b9e9606b0bace6 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 22 Jul 2026 09:31:32 +0100
+Subject: [PATCH] misc: rp1-pio: Reverse christmas tree declarations
+
+Linux devs prefer local variable declarations to be in order of
+decreasing length, known as "reverse christmas tree" and it is easier
+on the eye. Sort the declarations in rp1_pio_sm_config_xfer_internal
+accordingly.
+
+No functional change.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -611,14 +611,13 @@ static int rp1_pio_sm_config_xfer_intern
+ struct rp1_pio_sm_set_dmactrl_args set_dmactrl_args;
+ struct rp1_pio_device *pio = client->pio;
+ struct platform_device *pdev = pio->pdev;
+- struct device *dev = &pdev->dev;
+ struct dma_slave_config config = {};
++ struct device *dev = &pdev->dev;
+ struct dma_slave_caps dma_caps;
++ struct dma_info *dma = NULL;
+ phys_addr_t fifo_addr;
+- struct dma_info *dma = NULL;
+ uint32_t dma_mask;
+ char chan_name[4];
+- bool reconfigure = false;
+ int ret = 0;
+
+ if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
--- /dev/null
+From bb81a9b70e0a34350dd1b1d94e61e8368539344d Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 22 Jul 2026 10:28:29 +0100
+Subject: [PATCH] fixup! misc: rp1-pio: Reverse christmas tree declarations
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -614,7 +614,8 @@ static int rp1_pio_sm_config_xfer_intern
+ struct dma_slave_config config = {};
+ struct device *dev = &pdev->dev;
+ struct dma_slave_caps dma_caps;
+- struct dma_info *dma = NULL;
++ struct dma_info *dma = NULL;
++ bool reconfigure = false;
+ phys_addr_t fifo_addr;
+ uint32_t dma_mask;
+ char chan_name[4];
--- /dev/null
+From bcca3ae088c51f39b8e1adcdbfbeedaa24d4e097 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 13 Jul 2026 15:50:22 +0100
+Subject: [PATCH] misc: rpi-pio: In-kernel API fixes
+
+Fix a few issues discovered after testing the kernel API with a port of
+the userspace apitest app.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 23 ++++++++++++++++++++---
+ include/linux/pio_rp1.h | 4 ++--
+ 2 files changed, 22 insertions(+), 5 deletions(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -67,6 +67,9 @@ struct dma_xfer_state {
+ struct dma_info *dma;
+ void (*callback)(void *param);
+ void *callback_param;
++ struct dma_buf_info *dbi;
++ void *data;
++ size_t data_bytes;
+ };
+
+ struct dma_buf_info {
+@@ -225,7 +228,7 @@ int rp1_pio_can_add_program(struct rp1_p
+ offset = rp1_pio_find_program(pio, args);
+ mutex_unlock(&pio->instr_mutex);
+ if (offset >= 0)
+- return offset;
++ return 1;
+
+ /* Don't send the instructions, just the header */
+ return rp1_pio_message(pio, PIO_CAN_ADD_PROGRAM, args,
+@@ -584,6 +587,9 @@ static void rp1_pio_sm_kernel_dma_callba
+ {
+ struct dma_xfer_state *dxs = param;
+
++ if (dxs->dbi)
++ memcpy(dxs->data, dxs->dbi->buf, dxs->data_bytes);
++
+ dxs->dma->tail_idx++;
+ up(&dxs->dma->buf_sem);
+
+@@ -949,19 +955,25 @@ int rp1_pio_sm_xfer_data(struct rp1_pio_
+
+ if (!dma_addr) {
+ dxs = kmalloc(sizeof(*dxs), GFP_KERNEL);
++ if (!dxs)
++ return -ENOMEM;
+ dxs->dma = dma;
+ dxs->callback = callback;
+ dxs->callback_param = param;
++ dxs->dbi = NULL;
+ callback = rp1_pio_sm_kernel_dma_callback;
+ param = dxs;
+
+- if (!dma->buf_count || data_bytes > dma->buf_size)
++ if (!dma->buf_count || data_bytes > dma->buf_size) {
++ kfree(dxs);
+ return -EINVAL;
++ }
+
+ /* Grab a dma buffer */
+ if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+ if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+ dev_err(dev, "DMA wait timed out\n");
++ kfree(dxs);
+ return -ETIMEDOUT;
+ }
+ }
+@@ -969,8 +981,13 @@ int rp1_pio_sm_xfer_data(struct rp1_pio_
+ dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+ dma_addr = dbi->dma_addr;
+
+- if (dir == PIO_DIR_TO_SM)
++ if (dir == PIO_DIR_TO_SM) {
+ memcpy(dbi->buf, data, data_bytes);
++ } else {
++ dxs->dbi = dbi;
++ dxs->data = data;
++ dxs->data_bytes = data_bytes;
++ }
+ }
+
+ sg_init_table(&sg, 1);
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -300,7 +300,7 @@ static inline bool pio_can_add_program_a
+ args.num_instrs = program->length;
+
+ memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+- return !rp1_pio_can_add_program(client, &args);
++ return rp1_pio_can_add_program(client, &args);
+ }
+
+ static inline uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
+@@ -517,7 +517,7 @@ static inline int pio_sm_set_pindirs_wit
+
+ if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+ (pin_dirs & GPIOS_MASK) != pin_dirs ||
+- (pin_mask & pin_mask) != pin_mask))
++ (pin_mask & GPIOS_MASK) != pin_mask))
+ return -EINVAL;
+ return rp1_pio_sm_set_pindirs(client, &args);
+ }
--- /dev/null
+From 9c9fe968ff32089dad1ce3fac372fc1bf16ea08d Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 16 Jun 2026 20:39:26 +0100
+Subject: [PATCH] misc: rp1-pio: Add sm_get_dmactrl and sm_get_flags
+
+pio_sm_get_dmactrl is the reverse of pio_sm_set_dmactrl.
+pio_sm_get_flags allows the FIFO error flags - txoverflow, rxunderflow,
+txstall and rxstall - to be polled, cleared, and optionally waited for.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-fw-pio.h | 13 ++++++++-
+ drivers/misc/rp1-pio.c | 40 +++++++++++++++++++++++++++
+ include/linux/pio_rp1.h | 49 ++++++++++++++++++++++++++++++++++
+ include/uapi/misc/rp1_pio_if.h | 20 +++++++++++++-
+ 4 files changed, 120 insertions(+), 2 deletions(-)
+
+--- a/drivers/misc/rp1-fw-pio.h
++++ b/drivers/misc/rp1-fw-pio.h
+@@ -1,6 +1,6 @@
+ /* SPDX-License-Identifier: GPL-2.0 */
+ /*
+- * Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
++ * Copyright (C) 2023-2026 Raspberry Pi Ltd.
+ */
+
+ #ifndef __SOC_RP1_FIRMWARE_OPS_H__
+@@ -49,8 +49,19 @@ enum rp1_pio_ops {
+
+ PIO_SM_FIFO_STATE, // u16 sm, u8 tx -> u16 level, u8 empty, u8 full
+ PIO_SM_DRAIN_TX, // u16 sm
++ PIO_SM_GET_FLAGS, // u16 sm, u8 clear, u8 rsvd, u32 flags, u32 wait -> u32 level
++ PIO_SM_GET_DMACTRL, // u16 sm, u16 is_tx -> u32 ctrl
+
+ PIO_COUNT
+ };
+
++// Don't include this here to avoid multiple definitions
++//
++// enum pio_sm_flags {
++// PIO_SM_FLAG_TXSTALL = 0x0001,
++// PIO_SM_FLAG_RXSTALL = 0x0002,
++// PIO_SM_FLAG_TXOVER = 0x0004,
++// PIO_SM_FLAG_RXUNDER = 0x0008,
++// };
++
+ #endif
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -490,6 +490,30 @@ int rp1_pio_sm_set_dmactrl(struct rp1_pi
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_sm_set_dmactrl);
+
++int rp1_pio_sm_get_dmactrl(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_device *pio = client->pio;
++ struct rp1_pio_sm_get_dmactrl_args *args = param;
++ int ret;
++
++ if (pio->fw_pio_count >= PIO_SM_GET_DMACTRL) {
++ ret = rp1_pio_message_resp(pio, PIO_SM_GET_DMACTRL, args, sizeof(*args),
++ &args->ctrl, NULL, 4);
++ } else {
++ struct rp1_access_hw_args hwargs;
++
++ hwargs.addr = 0xf00000cc + args->sm * 0x20 + (args->is_tx ? 0x18 : 0x1c);
++ hwargs.len = 0x4;
++ hwargs.data = &args->ctrl;
++ ret = rp1_pio_message_resp(pio, READ_HW, &hwargs, 8,
++ &args->ctrl, NULL, 4);
++ }
++ if (ret >= 0)
++ return offsetof(struct rp1_pio_sm_set_dmactrl_args, ctrl) + ret;
++ return ret;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_sm_get_dmactrl);
++
+ int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_sm_fifo_state_args *args = param;
+@@ -512,6 +536,20 @@ int rp1_pio_sm_drain_tx(struct rp1_pio_c
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_sm_drain_tx);
+
++int rp1_pio_sm_get_flags(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_sm_get_flags_args *args = param;
++ int ret;
++
++ ret = rp1_pio_message_resp(client->pio, PIO_SM_GET_FLAGS, args, sizeof(*args),
++ &args->flags, NULL, 4);
++ if (ret >= 0)
++ return offsetof(struct rp1_pio_sm_get_flags_args, flags) + ret;
++ args->flags = ~0;
++ return ret;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_sm_get_flags);
++
+ int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_gpio_init_args *args = param;
+@@ -1054,6 +1092,8 @@ struct handler_info {
+ HANDLER(SM_SET_DMACTRL, sm_set_dmactrl),
+ HANDLER(SM_FIFO_STATE, sm_fifo_state),
+ HANDLER(SM_DRAIN_TX, sm_drain_tx),
++ HANDLER(SM_GET_FLAGS, sm_get_flags),
++ HANDLER(SM_GET_DMACTRL, sm_get_dmactrl),
+
+ HANDLER(GPIO_INIT, gpio_init),
+ HANDLER(GPIO_SET_FUNCTION, gpio_set_function),
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -172,6 +172,13 @@ enum gpio_drive_strength {
+ GPIO_DRIVE_STRENGTH_12MA = 3
+ };
+
++enum pio_sm_flags {
++ PIO_SM_FLAG_TXSTALL = 0x0001,
++ PIO_SM_FLAG_RXSTALL = 0x0002,
++ PIO_SM_FLAG_TXOVER = 0x0004,
++ PIO_SM_FLAG_RXUNDER = 0x0008,
++};
++
+ struct fp24_8 {
+ uint32_t val;
+ };
+@@ -213,8 +220,10 @@ int rp1_pio_sm_enable_sync(struct rp1_pi
+ int rp1_pio_sm_put(struct rp1_pio_client *client, void *param);
+ int rp1_pio_sm_get(struct rp1_pio_client *client, void *param);
+ int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param);
++int rp1_pio_sm_get_dmactrl(struct rp1_pio_client *client, void *param);
+ int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param);
+ int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param);
++int rp1_pio_sm_get_flags(struct rp1_pio_client *client, void *param);
+ int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param);
+ int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param);
+ int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param);
+@@ -611,6 +620,15 @@ static inline int pio_sm_set_dmactrl(str
+ return rp1_pio_sm_set_dmactrl(client, &args);
+ };
+
++static inline uint32_t pio_sm_get_dmactrl(struct rp1_pio_client *client, uint sm, bool is_tx)
++{
++ struct rp1_pio_sm_get_dmactrl_args args = { .sm = sm, .is_tx = is_tx };
++
++ if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
++ rp1_pio_sm_get_dmactrl(client, &args);
++ return args.ctrl;
++};
++
+ static inline int pio_sm_drain_tx_fifo(struct rp1_pio_client *client, uint sm)
+ {
+ struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+@@ -620,6 +638,37 @@ static inline int pio_sm_drain_tx_fifo(s
+ return rp1_pio_sm_drain_tx(client, &args);
+ };
+
++static inline uint32_t pio_sm_wait_flags(struct rp1_pio_client *client, uint sm, uint32_t flags,
++ bool clear, uint32_t timeout)
++{
++ struct rp1_pio_sm_get_flags_args args = { .sm = sm, .flags = flags, .clear = clear,
++ .timeout = timeout };
++
++ if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
++ rp1_pio_sm_get_flags(client, &args);
++ return args.flags;
++};
++
++static inline uint32_t pio_sm_get_flags(struct rp1_pio_client *client, uint sm, uint32_t flags,
++ bool clear)
++{
++ struct rp1_pio_sm_get_flags_args args = { .sm = sm, .flags = flags, .clear = clear,
++ .timeout = 0 };
++
++ if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
++ rp1_pio_sm_get_flags(client, &args);
++ return args.flags;
++}
++
++static inline void pio_sm_clear_flags(struct rp1_pio_client *client, uint sm, uint32_t flags)
++{
++ struct rp1_pio_sm_get_flags_args args = { .sm = sm, .flags = flags, .clear = true,
++ .timeout = 0 };
++
++ if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
++ rp1_pio_sm_get_flags(client, &args);
++}
++
+ static inline int pio_sm_put(struct rp1_pio_client *client, uint sm, uint32_t data)
+ {
+ struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = false, .data = data };
+--- a/include/uapi/misc/rp1_pio_if.h
++++ b/include/uapi/misc/rp1_pio_if.h
+@@ -124,6 +124,13 @@ struct rp1_pio_sm_set_dmactrl_args {
+ uint32_t ctrl;
+ };
+
++struct rp1_pio_sm_get_dmactrl_args {
++ uint16_t sm;
++ uint8_t is_tx;
++ uint8_t rsvd;
++ uint32_t ctrl; /* OUT */
++};
++
+ struct rp1_pio_sm_fifo_state_args {
+ uint16_t sm;
+ uint8_t tx;
+@@ -133,6 +140,14 @@ struct rp1_pio_sm_fifo_state_args {
+ uint8_t full; /* OUT */
+ };
+
++struct rp1_pio_sm_get_flags_args {
++ uint16_t sm;
++ uint8_t clear;
++ uint8_t rsvd;
++ uint32_t flags; /* IN/OUT */
++ uint32_t timeout;
++};
++
+ struct rp1_gpio_init_args {
+ uint16_t gpio;
+ };
+@@ -171,6 +186,7 @@ struct rp1_pio_sm_xfer_data_args {
+ uint16_t sm;
+ uint16_t dir;
+ uint16_t data_bytes;
++ uint16_t rsvd;
+ void *data;
+ };
+
+@@ -220,8 +236,10 @@ struct rp1_access_hw_args {
+ #define PIO_IOC_SM_PUT _IOW(PIO_IOC_MAGIC, 41, struct rp1_pio_sm_put_args)
+ #define PIO_IOC_SM_GET _IOWR(PIO_IOC_MAGIC, 42, struct rp1_pio_sm_get_args)
+ #define PIO_IOC_SM_SET_DMACTRL _IOW(PIO_IOC_MAGIC, 43, struct rp1_pio_sm_set_dmactrl_args)
+-#define PIO_IOC_SM_FIFO_STATE _IOW(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args)
++#define PIO_IOC_SM_FIFO_STATE _IOWR(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args)
+ #define PIO_IOC_SM_DRAIN_TX _IOW(PIO_IOC_MAGIC, 45, struct rp1_pio_sm_clear_fifos_args)
++#define PIO_IOC_SM_GET_FLAGS _IOWR(PIO_IOC_MAGIC, 46, struct rp1_pio_sm_get_flags_args)
++#define PIO_IOC_SM_GET_DMACTRL _IOWR(PIO_IOC_MAGIC, 47, struct rp1_pio_sm_get_dmactrl_args)
+
+ #define PIO_IOC_GPIO_INIT _IOW(PIO_IOC_MAGIC, 50, struct rp1_gpio_init_args)
+ #define PIO_IOC_GPIO_SET_FUNCTION _IOW(PIO_IOC_MAGIC, 51, struct rp1_gpio_set_function_args)
--- /dev/null
+From ee9c2a4007e551c86ec55379b01af5abd5f7c1d7 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 2 Jul 2026 17:15:56 +0100
+Subject: [PATCH] misc: rp1-pio: Add support for PIO interrupts
+
+The PIO block exposes 2 assignable interrupts. Make them available to
+kernel and userspace PIO applications.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-fw-pio.h | 5 +
+ drivers/misc/rp1-pio.c | 537 +++++++++++++++++++++++++++------
+ include/linux/pio_rp1.h | 171 ++++++++++-
+ include/uapi/misc/rp1_pio_if.h | 60 +++-
+ 4 files changed, 678 insertions(+), 95 deletions(-)
+
+--- a/drivers/misc/rp1-fw-pio.h
++++ b/drivers/misc/rp1-fw-pio.h
+@@ -52,6 +52,11 @@ enum rp1_pio_ops {
+ PIO_SM_GET_FLAGS, // u16 sm, u8 clear, u8 rsvd, u32 flags, u32 wait -> u32 level
+ PIO_SM_GET_DMACTRL, // u16 sm, u16 is_tx -> u32 ctrl
+
++ PIO_SET_IRQN_SOURCE_MASK_ENABLED, // u16 irq_index, u8 enabled, u8 rsvd,
++ // uint32_t source_mask
++ PIO_INTERRUPT_GET, // u16 pio_interrupt_num -> u8 active
++ PIO_INTERRUPT_CLEAR, // u16 pio_interrupt_num
++
+ PIO_COUNT
+ };
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -2,7 +2,7 @@
+ /*
+ * PIO driver for RP1
+ *
+- * Copyright (C) 2023-2024 Raspberry Pi Ltd.
++ * Copyright (C) 2023-2026 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ * - vcio.c, by Noralf Trønnes
+@@ -20,9 +20,11 @@
+ #include <linux/dma-mapping.h>
+ #include <linux/fs.h>
+ #include <linux/init.h>
++#include <linux/interrupt.h>
+ #include <linux/ioctl.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
++#include <linux/of_irq.h>
+ #include <linux/pio_rp1.h>
+ #include <linux/platform_device.h>
+ #include <linux/rp1-firmware.h>
+@@ -88,6 +90,16 @@ struct dma_info {
+ struct dma_buf_info bufs[DMA_BOUNCE_BUFFER_COUNT];
+ };
+
++struct rp1_pio_client;
++
++struct irq_info {
++ int irq;
++ bool enabled;
++ struct rp1_pio_client *owner;
++ pio_irq_handler_t handler;
++ void *context;
++};
++
+ struct rp1_pio_device {
+ struct platform_device *pdev;
+ struct rp1_firmware *fw;
+@@ -97,6 +109,8 @@ struct rp1_pio_device {
+ struct class *dev_class;
+ struct cdev cdev;
+ phys_addr_t phys_addr;
++ uint irq_count;
++ struct irq_info irqs[RP1_PIO_IRQ_COUNT];
+ uint32_t claimed_sms;
+ uint32_t claimed_dmas;
+ spinlock_t lock;
+@@ -110,10 +124,14 @@ struct rp1_pio_device {
+
+ struct rp1_pio_client {
+ struct rp1_pio_device *pio;
++ spinlock_t lock;
++ struct completion completion;
++ uint32_t wake_reason;
+ uint32_t claimed_sms;
+ uint32_t claimed_instrs;
+ uint32_t claimed_dmas;
+ int error;
++ int irqs[RP1_PIO_IRQ_COUNT];
+ };
+
+ static struct rp1_pio_device *g_pio;
+@@ -300,7 +318,7 @@ int rp1_pio_remove_program(struct rp1_pi
+ {
+ struct rp1_pio_remove_program_args *args = param;
+ uint32_t used_mask;
+- int ret = -ENOENT;
++ int ret = -ENODEV;
+
+ if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+ args->origin >= RP1_PIO_INSTR_COUNT ||
+@@ -542,7 +560,7 @@ int rp1_pio_sm_get_flags(struct rp1_pio_
+ int ret;
+
+ ret = rp1_pio_message_resp(client->pio, PIO_SM_GET_FLAGS, args, sizeof(*args),
+- &args->flags, NULL, 4);
++ &args->flags, NULL, sizeof(args->flags));
+ if (ret >= 0)
+ return offsetof(struct rp1_pio_sm_get_flags_args, flags) + ret;
+ args->flags = ~0;
+@@ -614,6 +632,253 @@ int rp1_pio_gpio_set_drive_strength(stru
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_drive_strength);
+
++static void rp1_pio_irq_handler_userspace(void *context)
++{
++ struct irq_info *irqi = context;
++ struct rp1_pio_client *client = irqi->owner;
++ struct rp1_pio_device *pio = client->pio;
++ uint32_t intmask = 1 << (irqi - pio->irqs);
++ unsigned long flags;
++
++ spin_lock_irqsave(&client->lock, flags);
++ client->wake_reason |= intmask;
++ spin_unlock_irqrestore(&client->lock, flags);
++
++ complete(&client->completion);
++}
++
++int rp1_pio_irq_claim(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_irq_claim_args *args = param;
++ struct rp1_pio_device *pio = client->pio;
++ struct irq_info *irqi;
++ int ret = -1;
++ int i;
++
++ // Try to claim one of the PIO interrupts
++
++ args->irq_index = -ENODEV;
++
++ spin_lock(&pio->lock);
++ for (i = 0; i < pio->irq_count; i++) {
++ irqi = &pio->irqs[i];
++ if (!irqi->owner) {
++ irqi->owner = client;
++ irqi->handler = rp1_pio_irq_handler_userspace;
++ irqi->context = irqi;
++ args->irq_index = i;
++ ret = sizeof(*args);
++ break;
++ }
++ args->irq_index = -EBUSY;
++ }
++ spin_unlock(&pio->lock);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_claim);
++
++int rp1_pio_irq_wait(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_irq_wait_args *args = param;
++ unsigned long timeout;
++ unsigned long flags;
++ long rem;
++
++ if (args->timeout_ms)
++ timeout = msecs_to_jiffies(args->timeout_ms);
++ else
++ timeout = MAX_SCHEDULE_TIMEOUT;
++ rem = wait_for_completion_interruptible_timeout(&client->completion, timeout);
++ if (rem < 0)
++ return (int)rem;
++
++ spin_lock_irqsave(&client->lock, flags);
++ args->active_mask = rem ? client->wake_reason : 0;
++ client->wake_reason = 0;
++ spin_unlock_irqrestore(&client->lock, flags);
++ return sizeof(*args);
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_wait);
++
++int rp1_pio_set_irqn_source_mask_enabled(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_set_irqn_source_mask_enabled_args *args = param;
++
++ return rp1_pio_message(client->pio, PIO_SET_IRQN_SOURCE_MASK_ENABLED,
++ args, sizeof(*args));
++}
++EXPORT_SYMBOL_GPL(rp1_pio_set_irqn_source_mask_enabled);
++
++int rp1_pio_interrupt_get(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_interrupt_get_args *args = param;
++ int ret;
++
++ ret = rp1_pio_message_resp(client->pio, PIO_INTERRUPT_GET, args, sizeof(*args),
++ &args->active, NULL, sizeof(args->active));
++ if (ret >= 0)
++ return offsetof(struct rp1_pio_interrupt_get_args, active) + ret;
++ return ret;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_interrupt_get);
++
++int rp1_pio_interrupt_clear(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_interrupt_clear_args *args = param;
++
++ return rp1_pio_message(client->pio, PIO_INTERRUPT_CLEAR, args, sizeof(*args));
++}
++EXPORT_SYMBOL_GPL(rp1_pio_interrupt_clear);
++
++int rp1_pio_irq_set_enabled(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_irq_set_enabled_args *args = param;
++ struct rp1_pio_device *pio = client->pio;
++ uint irq_index = args->irq_index;
++ struct irq_info *irqi;
++
++ if (irq_index >= ARRAY_SIZE(pio->irqs))
++ return -EINVAL;
++ if (irq_index >= pio->irq_count)
++ return -EINVAL;
++ irqi = &pio->irqs[irq_index];
++ if (irqi->owner != client)
++ return -EBUSY;
++
++ spin_lock(&pio->lock);
++ if (args->enabled && !irqi->enabled) {
++ enable_irq(irqi->irq);
++ irqi->enabled = true;
++ } else if (!args->enabled && irqi->enabled) {
++ disable_irq_nosync(irqi->irq);
++ irqi->enabled = false;
++ }
++ spin_unlock(&pio->lock);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_set_enabled);
++
++int rp1_pio_irq_is_enabled(struct rp1_pio_client *client, void *param)
++{
++ struct rp1_pio_irq_set_enabled_args *args = param;
++ struct rp1_pio_device *pio = client->pio;
++ uint irq_index = args->irq_index;
++ struct irq_info *irqi;
++
++ if (irq_index >= ARRAY_SIZE(pio->irqs))
++ return -EINVAL;
++ if (irq_index >= pio->irq_count)
++ return -EINVAL;
++ irqi = &pio->irqs[irq_index];
++ if (irqi->owner != client)
++ return -EBUSY;
++
++ args->enabled = irqi->enabled;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_is_enabled);
++
++void rp1_pio_irq_add_handler(struct rp1_pio_client *client, uint num,
++ pio_irq_handler_t handler, void *context)
++{
++ struct rp1_pio_device *pio = client->pio;
++ struct irq_info *irqi;
++
++ if (num >= pio->irq_count) {
++ dev_err(&pio->pdev->dev, "%s: bad irq index %u\n", __func__, num);
++ return;
++ }
++
++ irqi = &pio->irqs[num];
++
++ spin_lock(&pio->lock);
++ if (irqi->owner && irqi->owner != client) {
++ spin_unlock(&pio->lock);
++ dev_err(&pio->pdev->dev, "%s: irq %u already claimed\n", __func__, num);
++ return;
++ }
++ if (irqi->enabled) {
++ /*
++ * The hard-irq handler reads ->handler/->context without
++ * taking pio->lock, so they must not change while the line
++ * can fire. Callers must disable the irq before swapping
++ * its handler.
++ */
++ spin_unlock(&pio->lock);
++ dev_err(&pio->pdev->dev, "%s: irq %u is enabled\n", __func__, num);
++ return;
++ }
++
++ irqi->owner = client;
++ irqi->handler = handler;
++ irqi->context = context;
++ spin_unlock(&pio->lock);
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_add_handler);
++
++void rp1_pio_irq_remove_handler(struct rp1_pio_client *client,
++ uint num, pio_irq_handler_t handler)
++{
++ struct rp1_pio_device *pio = client->pio;
++ struct irq_info *irqi;
++
++ if (num >= pio->irq_count)
++ return;
++
++ irqi = &pio->irqs[num];
++
++ spin_lock(&pio->lock);
++ if (irqi->owner == client && irqi->handler == handler) {
++ irqi->handler = NULL;
++ irqi->context = NULL;
++ irqi->owner = NULL;
++ }
++ spin_unlock(&pio->lock);
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_remove_handler);
++
++pio_irq_handler_t rp1_pio_irq_get_handler(struct rp1_pio_client *client, uint num)
++{
++ struct rp1_pio_device *pio = client->pio;
++ struct irq_info *irqi;
++ pio_irq_handler_t handler;
++
++ if (num >= pio->irq_count)
++ return false;
++
++ irqi = &pio->irqs[num];
++
++ spin_lock(&pio->lock);
++ handler = irqi->handler;
++ spin_unlock(&pio->lock);
++
++ return handler;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_get_handler);
++
++uint rp1_pio_irq_map(struct rp1_pio_client *client, uint irqn)
++{
++ unsigned long flags;
++ int pirq;
++
++ spin_lock_irqsave(&client->lock, flags);
++ pirq = client->irqs[irqn];
++ if (pirq < 0) {
++ struct rp1_pio_irq_claim_args args = { .irq_index = -1 };
++
++ rp1_pio_irq_claim(client, &args);
++ pirq = args.irq_index;
++ if (pirq >= 0)
++ client->irqs[irqn] = pirq;
++ }
++ spin_unlock_irqrestore(&client->lock, flags);
++ return (uint)pirq;
++}
++EXPORT_SYMBOL_GPL(rp1_pio_irq_map);
++
+ static void rp1_pio_sm_dma_callback(void *param)
+ {
+ struct dma_info *dma = param;
+@@ -645,8 +910,8 @@ static void rp1_pio_sm_dma_free(struct d
+ dma->bufs[dma->buf_count].buf,
+ dma->bufs[dma->buf_count].dma_addr);
+ }
+-
+ dma_release_channel(dma->chan);
++ dma->chan = NULL;
+ }
+
+ static int rp1_pio_sm_config_xfer_internal(struct rp1_pio_client *client, uint sm, uint dir,
+@@ -722,6 +987,9 @@ static int rp1_pio_sm_config_xfer_intern
+ sg_dma_address(&dbi->sgl) = dbi->dma_addr;
+ }
+
++ dma->head_idx = 0;
++ dma->tail_idx = 0;
++
+ fifo_addr = pio->phys_addr;
+ fifo_addr += sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0);
+ fifo_addr += (dir == RP1_PIO_DIR_TO_SM) ? RP1_PIO_FIFO_TX0 : RP1_PIO_FIFO_RX0;
+@@ -794,19 +1062,14 @@ static int rp1_pio_sm_tx_user(struct rp1
+ struct device *dev = &pdev->dev;
+ int ret = 0;
+
+- /* Clean the slate - we're running synchronously */
+- dma->head_idx = 0;
+- dma->tail_idx = 0;
+-
+ while (bytes > 0) {
+ size_t copy_bytes = min(bytes, dma->buf_size);
+ struct dma_buf_info *dbi;
+
+ /* grab the next free buffer, waiting if they're all full */
+ if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+- if (down_timeout(&dma->buf_sem,
+- msecs_to_jiffies(1000))) {
+- dev_err(dev, "DMA bounce timed out\n");
++ if (down_interruptible(&dma->buf_sem)) {
++ dev_err(dev, "DMA bounce interrupted\n");
+ break;
+ }
+ dma->tail_idx++;
+@@ -847,84 +1110,97 @@ static int rp1_pio_sm_tx_user(struct rp1
+ bytes -= copy_bytes;
+ }
+
+- /* Block for completion */
+- while (dma->tail_idx != dma->head_idx) {
+- if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+- dev_err(dev, "DMA wait timed out\n");
+- ret = -ETIMEDOUT;
+- break;
+- }
+- dma->tail_idx++;
+- }
+-
+ return ret;
+ }
+
+-static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma,
+- void __user *userbuf, size_t bytes)
++static int rp1_pio_sm_rx_submit(struct rp1_pio_device *pio, struct dma_info *dma, size_t len)
+ {
+ struct platform_device *pdev = pio->pdev;
+ struct dma_async_tx_descriptor *desc;
+ struct device *dev = &pdev->dev;
+- int ret = 0;
++ struct dma_buf_info *dbi;
++ int ret;
+
+- /* Clean the slate - we're running synchronously */
+- dma->head_idx = 0;
+- dma->tail_idx = 0;
++ if (len > dma->buf_size)
++ return -EINVAL;
+
+- while (bytes || dma->tail_idx != dma->head_idx) {
+- size_t copy_bytes = min(bytes, dma->buf_size);
+- struct dma_buf_info *dbi;
++ dbi = &dma->bufs[dma->head_idx % dma->buf_count];
++ sg_dma_len(&dbi->sgl) = len;
++ desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
++ DMA_DEV_TO_MEM,
++ DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
++ DMA_PREP_FENCE);
++ if (!desc) {
++ dev_err(dev, "DMA preparation failed\n");
++ return -EIO;
++ }
+
+- /*
+- * wait for the next RX to complete if all the buffers are
+- * outstanding or we're finishing up.
+- */
+- if (!bytes || dma->head_idx - dma->tail_idx == dma->buf_count) {
+- if (down_timeout(&dma->buf_sem,
+- msecs_to_jiffies(1000))) {
+- dev_err(dev, "DMA wait timed out\n");
+- ret = -ETIMEDOUT;
+- break;
+- }
++ desc->callback = rp1_pio_sm_dma_callback;
++ desc->callback_param = dma;
+
+- dbi = &dma->bufs[dma->tail_idx++ % dma->buf_count];
+- ret = copy_to_user(userbuf, dbi->buf, sg_dma_len(&dbi->sgl));
+- if (ret < 0)
+- break;
+- userbuf += sg_dma_len(&dbi->sgl);
++ /* Submit the buffer - the callback will kick the semaphore */
++ ret = dmaengine_submit(desc);
++ if (ret < 0)
++ return ret;
+
+- if (!bytes)
+- continue;
+- }
++ dma->head_idx++;
++ dma_async_issue_pending(dma->chan);
+
+- dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+- sg_dma_len(&dbi->sgl) = copy_bytes;
+- desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
+- DMA_DEV_TO_MEM,
+- DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+- DMA_PREP_FENCE);
+- if (!desc) {
+- dev_err(dev, "DMA preparation failed\n");
+- ret = -EIO;
+- break;
+- }
++ return 0;
++}
+
+- desc->callback = rp1_pio_sm_dma_callback;
+- desc->callback_param = dma;
++/*
++ * A NULL userbuf queues a receive of exactly "bytes" and returns without
++ * waiting: it puts the app in control of its own read-ahead depth and
++ * chunk sizes, rather than us assuming every read is a full dma->buf_size
++ * chunk (reads can be any size up to buf_size, and need not match it).
++ */
++static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma,
++ void __user *userbuf, size_t bytes)
++{
++ struct device *dev = &pio->pdev->dev;
++ int ret;
+
+- /* Submit the buffer - the callback will kick the semaphore */
+- ret = dmaengine_submit(desc);
+- if (ret < 0)
+- break;
++ if (!bytes)
++ return -EINVAL;
+
+- dma->head_idx++;
+- dma_async_issue_pending(dma->chan);
++ if (!userbuf) {
++ if (dma->head_idx - dma->tail_idx == dma->buf_count)
++ return -EBUSY;
+
+- bytes -= copy_bytes;
++ return rp1_pio_sm_rx_submit(pio, dma, bytes);
+ }
+
+- return ret;
++ while (bytes) {
++ struct dma_buf_info *dbi;
++ size_t len;
++
++ if (dma->head_idx == dma->tail_idx) {
++ /* Nothing already queued: arm exactly what's needed */
++ len = min(bytes, dma->buf_size);
++ ret = rp1_pio_sm_rx_submit(pio, dma, len);
++ if (ret)
++ return ret;
++ }
++
++ dbi = &dma->bufs[dma->tail_idx % dma->buf_count];
++ len = sg_dma_len(&dbi->sgl);
++ if (len > bytes)
++ return -EINVAL;
++
++ if (down_interruptible(&dma->buf_sem)) {
++ dev_err(dev, "DMA wait interrupted\n");
++ return -ETIMEDOUT;
++ }
++ dma->tail_idx++;
++
++ if (copy_to_user(userbuf, dbi->buf, len))
++ return -EFAULT;
++ userbuf += len;
++ bytes -= len;
++ }
++
++ return 0;
+ }
+
+ static int rp1_pio_sm_xfer_data32_user(struct rp1_pio_client *client, void *param)
+@@ -935,7 +1211,8 @@ static int rp1_pio_sm_xfer_data32_user(s
+ uint32_t dma_mask;
+
+ if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT ||
+- !args->data_bytes || !args->data)
++ !args->data_bytes ||
++ (!args->data && args->dir != RP1_PIO_DIR_FROM_SM))
+ return -EINVAL;
+
+ dma_mask = 1 << (args->sm * 2 + args->dir);
+@@ -1007,13 +1284,13 @@ int rp1_pio_sm_xfer_data(struct rp1_pio_
+ return -EINVAL;
+ }
+
+- /* Grab a dma buffer */
++ /*
++ * Never block: the caller manages its own outstanding count
++ * and is told about freed slots via its callback.
++ */
+ if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+- if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+- dev_err(dev, "DMA wait timed out\n");
+- kfree(dxs);
+- return -ETIMEDOUT;
+- }
++ kfree(dxs);
++ return -EBUSY;
+ }
+
+ dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+@@ -1106,11 +1383,20 @@ struct handler_info {
+
+ HANDLER(READ_HW, read_hw),
+ HANDLER(WRITE_HW, write_hw),
++
++ HANDLER(IRQ_CLAIM, irq_claim),
++ HANDLER(IRQ_WAIT, irq_wait),
++ HANDLER(IRQ_SET_ENABLED, irq_set_enabled),
++ HANDLER(IRQ_IS_ENABLED, irq_is_enabled),
++ HANDLER(SET_IRQN_SOURCE_MASK_ENABLED, set_irqn_source_mask_enabled),
++ HANDLER(INTERRUPT_GET, interrupt_get),
++ HANDLER(INTERRUPT_CLEAR, interrupt_clear),
+ };
+
+ struct rp1_pio_client *rp1_pio_open(void)
+ {
+ struct rp1_pio_client *client;
++ int i;
+
+ if (!g_pio)
+ return ERR_PTR(-EPROBE_DEFER);
+@@ -1121,7 +1407,12 @@ struct rp1_pio_client *rp1_pio_open(void
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
++ for (i = 0; i < RP1_PIO_IRQ_COUNT; i++)
++ client->irqs[i] = -1;
++
+ client->pio = g_pio;
++ spin_lock_init(&client->lock);
++ init_completion(&client->completion);
+
+ return client;
+ }
+@@ -1130,18 +1421,49 @@ EXPORT_SYMBOL_GPL(rp1_pio_open);
+ void rp1_pio_close(struct rp1_pio_client *client)
+ {
+ struct rp1_pio_device *pio = client->pio;
+- uint claimed_dmas = client->claimed_dmas;
++ uint32_t claimed;
++ struct irq_info *irqi;
++ unsigned long flags;
+ int i;
+
++ /* Wake any waiter */
++ spin_lock_irqsave(&client->lock, flags);
++ client->wake_reason = 0;
++ spin_unlock_irqrestore(&client->lock, flags);
++ complete(&client->completion);
++
+ /* Free any allocated resources */
+
+- for (i = 0; claimed_dmas; i++) {
++ spin_lock(&pio->lock);
++ for (i = 0; i < pio->irq_count; i++) {
++ irqi = &pio->irqs[i];
++ if (irqi->owner == client) {
++ /*
++ * Disable directly rather than via irq_set_enabled():
++ * that helper takes pio->lock itself, and it is
++ * already held here.
++ */
++ if (irqi->enabled) {
++ disable_irq(irqi->irq);
++ irqi->enabled = false;
++ }
++
++ irqi->owner = NULL;
++ irqi->handler = NULL;
++ irqi->context = NULL;
++ }
++ }
++ spin_unlock(&pio->lock);
++
++ claimed = client->claimed_dmas;
++
++ for (i = 0; claimed; i++) {
+ uint mask = (1 << i);
+
+- if (claimed_dmas & mask) {
++ if (claimed & mask) {
+ struct dma_info *dma = &pio->dma_configs[i >> 1][i & 1];
+
+- claimed_dmas &= ~mask;
++ claimed &= ~mask;
+ rp1_pio_sm_dma_free(&pio->pdev->dev, dma);
+ }
+ }
+@@ -1333,6 +1655,30 @@ static long rp1_pio_compat_ioctl(struct
+ #define rp1_pio_compat_ioctl NULL
+ #endif
+
++static irqreturn_t rp1_pio_irq_handler(int irq, void *dev_id)
++{
++ struct rp1_pio_device *pio = dev_id;
++ struct irq_info *irqi = NULL;
++ int i;
++
++ for (i = 0; i < pio->irq_count; i++) {
++ if (irq == pio->irqs[i].irq) {
++ irqi = &pio->irqs[i];
++ break;
++ }
++ }
++
++ if (!irqi || !irqi->handler) {
++ pr_err("disabling unwanted interrupt\n");
++ disable_irq_nosync(irq);
++ return IRQ_NONE;
++ }
++
++ irqi->handler(irqi->context);
++
++ return IRQ_HANDLED;
++}
++
+ const struct file_operations rp1_pio_fops = {
+ .owner = THIS_MODULE,
+ .open = rp1_pio_file_open,
+@@ -1352,6 +1698,7 @@ static int rp1_pio_probe(struct platform
+ struct device *cdev;
+ char dev_name[16];
+ void *p;
++ int irq;
+ int ret;
+ int i;
+
+@@ -1363,7 +1710,7 @@ static int rp1_pio_probe(struct platform
+ return -EINVAL;
+ }
+
+- pdev->id = of_alias_get_id(pdev->dev.of_node, "pio");
++ pdev->id = of_alias_get_id(dev->of_node, "pio");
+ if (pdev->id < 0)
+ return dev_err_probe(dev, pdev->id, "alias is missing\n");
+
+@@ -1379,7 +1726,7 @@ static int rp1_pio_probe(struct platform
+ if (ret < 0)
+ goto out_err;
+
+- pio = devm_kzalloc(&pdev->dev, sizeof(*pio), GFP_KERNEL);
++ pio = devm_kzalloc(dev, sizeof(*pio), GFP_KERNEL);
+ if (!pio) {
+ ret = -ENOMEM;
+ goto out_err;
+@@ -1399,6 +1746,25 @@ static int rp1_pio_probe(struct platform
+ goto out_err;
+ }
+
++ if (pio->fw_pio_count > PIO_INTERRUPT_CLEAR) {
++ for (i = 0; i < ARRAY_SIZE(pio->irqs); i++) {
++ irq = of_irq_get(dev->of_node, i);
++ if (irq < 0)
++ break;
++ ret = devm_request_irq(dev, irq, rp1_pio_irq_handler,
++ 0, pdev->name, pio);
++ if (ret < 0) {
++ dev_err(dev, "failed to request an irq (rc=%d)\n", ret);
++ goto out_err;
++ }
++
++ disable_irq(irq);
++ pio->irqs[i].irq = irq;
++ }
++
++ pio->irq_count = i;
++ }
++
+ pio->phys_addr = ioresource->start;
+
+ ret = alloc_chrdev_region(&pio->dev_num, 0, 1, DRIVER_NAME);
+@@ -1431,7 +1797,8 @@ static int rp1_pio_probe(struct platform
+
+ g_pio = pio;
+
+- dev_info(dev, "Created instance as %s\n", dev_name);
++ dev_info(dev, "Created instance as %s (op count %d, %d interrupts)\n",
++ dev_name, pio->fw_pio_count, pio->irq_count);
+ return 0;
+
+ out_cdev_del:
+@@ -1475,7 +1842,7 @@ static struct platform_driver rp1_pio_dr
+ .of_match_table = of_match_ptr(rp1_pio_ids),
+ },
+ .probe = rp1_pio_probe,
+- .remove = rp1_pio_remove,
++ .remove = rp1_pio_remove,
+ .shutdown = rp1_pio_remove,
+ };
+
+--- a/include/linux/pio_rp1.h
++++ b/include/linux/pio_rp1.h
+@@ -46,6 +46,7 @@
+ #define GPIOS_MASK ((1 << RP1_PIO_GPIO_COUNT) - 1)
+
+ #define PICO_NO_HARDWARE 0
++#define PICO_PIO_VERSION 0
+
+ #define pio0 pio_open_helper(0)
+
+@@ -102,6 +103,18 @@
+ #define PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS 0x0000001f
+ #define PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB 0
+
++#define irq_add_shared_handler(num, handler, ...) \
++ irq_add_handler(pio, num, handler, NULL)
++
++#define irq_has_shared_handler(num, handler, ...) \
++ (irq_get_handler(pio, num, handler, NULL) != NULL)
++
++#define irq_set_exclusive_handler(num, handler) \
++ irq_add_handler(pio, num, handler, NULL)
++
++#define irq_get_exclusive_handler(num) \
++ ((void *)irq_get_handler(pio, num))
++
+ enum pio_fifo_join {
+ PIO_FIFO_JOIN_NONE = 0,
+ PIO_FIFO_JOIN_TX = 1,
+@@ -179,14 +192,36 @@ enum pio_sm_flags {
+ PIO_SM_FLAG_RXUNDER = 0x0008,
+ };
+
++
++enum pio_interrupt_source {
++ PIS_SM0_RX_FIFO_NOT_EMPTY,
++ PIS_SM1_RX_FIFO_NOT_EMPTY,
++ PIS_SM2_RX_FIFO_NOT_EMPTY,
++ PIS_SM3_RX_FIFO_NOT_EMPTY,
++ PIS_SM0_TX_FIFO_NOT_FULL,
++ PIS_SM1_TX_FIFO_NOT_FULL,
++ PIS_SM2_TX_FIFO_NOT_FULL,
++ PIS_SM3_TX_FIFO_NOT_FULL,
++ PIS_INTERRUPT0,
++ PIS_INTERRUPT1,
++ PIS_INTERRUPT2,
++ PIS_INTERRUPT3,
++
++ PIS_MAX
++};
++
+ struct fp24_8 {
+ uint32_t val;
+ };
+
+ typedef rp1_pio_sm_config pio_sm_config;
+
++typedef void (*pio_irq_handler_t)(void *context);
++
+ typedef struct rp1_pio_client *PIO;
+
++static struct rp1_pio_client *g_client;
++
+ int rp1_pio_init(void);
+ PIO rp1_pio_open(void);
+ void rp1_pio_close(struct rp1_pio_client *client);
+@@ -233,6 +268,22 @@ int rp1_pio_gpio_set_oeover(struct rp1_p
+ int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param);
+ int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param);
+
++int rp1_pio_irq_claim(struct rp1_pio_client *client, void *param);
++int rp1_pio_irq_wait(struct rp1_pio_client *client, void *param);
++int rp1_pio_set_irqn_source_mask_enabled(struct rp1_pio_client *client, void *param);
++int rp1_pio_interrupt_get(struct rp1_pio_client *client, void *param);
++int rp1_pio_interrupt_clear(struct rp1_pio_client *client, void *param);
++int rp1_pio_irq_set_enabled(struct rp1_pio_client *client, void *param);
++int rp1_pio_irq_is_enabled(struct rp1_pio_client *client, void *param);
++
++void rp1_pio_irq_add_handler(struct rp1_pio_client *client, uint num,
++ pio_irq_handler_t handler, void *context);
++void rp1_pio_irq_remove_handler(struct rp1_pio_client *client,
++ uint num, pio_irq_handler_t handler);
++pio_irq_handler_t rp1_pio_irq_get_handler(struct rp1_pio_client *client, uint num);
++uint rp1_pio_irq_map(struct rp1_pio_client *client, uint irqn);
++
++
+ static inline int pio_init(void)
+ {
+ return rp1_pio_init();
+@@ -240,11 +291,14 @@ static inline int pio_init(void)
+
+ static inline struct rp1_pio_client *pio_open(void)
+ {
+- return rp1_pio_open();
++ g_client = rp1_pio_open();
++ return g_client;
+ }
+
+ static inline void pio_close(struct rp1_pio_client *client)
+ {
++ if (g_client == client)
++ g_client = NULL;
+ rp1_pio_close(client);
+ }
+
+@@ -1066,4 +1120,119 @@ static inline int pio_gpio_disable_pulls
+ return pio_gpio_set_pulls(client, gpio, false, false);
+ }
+
++static inline int pio_get_irq_num(struct rp1_pio_client *client, uint irqn)
++{
++ return rp1_pio_irq_map(client, irqn);
++}
++
++static inline void pio_set_irqn_source_mask_enabled(struct rp1_pio_client *client, uint irq_index,
++ uint32_t source_mask, bool enabled)
++{
++ struct rp1_pio_set_irqn_source_mask_enabled_args args = {
++ .irq_index = rp1_pio_irq_map(client, irq_index),
++ .source_mask = source_mask, .enabled = enabled,
++ };
++
++ rp1_pio_set_irqn_source_mask_enabled(client, &args);
++}
++
++static inline void pio_set_irq0_source_enabled(struct rp1_pio_client *client,
++ enum pio_interrupt_source source,
++ bool enabled)
++{
++ pio_set_irqn_source_mask_enabled(client, 0, 1 << source, enabled);
++}
++
++static inline void pio_set_irq1_source_enabled(struct rp1_pio_client *client,
++ enum pio_interrupt_source source,
++ bool enabled)
++{
++ pio_set_irqn_source_mask_enabled(client, 1, 1 << source, enabled);
++}
++
++static inline void pio_set_irq0_source_mask_enabled(struct rp1_pio_client *client,
++ uint32_t source_mask,
++ bool enabled)
++{
++ pio_set_irqn_source_mask_enabled(client, 0, source_mask, enabled);
++}
++
++static inline void pio_set_irq1_source_mask_enabled(struct rp1_pio_client *client,
++ uint32_t source_mask,
++ bool enabled)
++{
++ pio_set_irqn_source_mask_enabled(client, 1, source_mask, enabled);
++}
++
++static inline void pio_set_irqn_source_enabled(struct rp1_pio_client *client, uint irq_index,
++ enum pio_interrupt_source source, bool enabled)
++{
++ pio_set_irqn_source_mask_enabled(client, irq_index, 1 << source, enabled);
++}
++
++static inline uint pio_interrupt_rel(uint sm, uint pio_interrupt_num)
++{
++ return (pio_interrupt_num & 0x1c) | ((pio_interrupt_num + sm) & 0x3);
++}
++
++static inline void irq_add_handler(struct rp1_pio_client *client, uint num,
++ pio_irq_handler_t handler,
++ void *context)
++{
++ return rp1_pio_irq_add_handler(client, num, handler, context);
++}
++
++static inline pio_irq_handler_t irq_get_handler(struct rp1_pio_client *client, uint num)
++{
++ return rp1_pio_irq_get_handler(client, num);
++}
++
++static inline bool irq_has_handler(struct rp1_pio_client *client, uint num)
++{
++ return rp1_pio_irq_get_handler(client, num) != NULL;
++}
++
++static inline void irq_set_enabled(struct rp1_pio_client *client, uint num, bool enabled)
++{
++ struct rp1_pio_irq_set_enabled_args args = { .irq_index = num, .enabled = enabled };
++
++ if (bad_params_if(client, num >= RP1_PIO_IRQ_COUNT))
++ return;
++
++ rp1_pio_irq_set_enabled(client, &args);
++};
++
++static inline bool irq_is_enabled(struct rp1_pio_client *client, uint num)
++{
++ struct rp1_pio_irq_set_enabled_args args = { .irq_index = num };
++
++ if (bad_params_if(client, num >= RP1_PIO_IRQ_COUNT))
++ return false;
++
++ (void)rp1_pio_irq_is_enabled(client, &args);
++
++ return !!args.enabled;
++}
++
++static inline bool pio_interrupt_get(struct rp1_pio_client *client, uint pio_interrupt_num)
++{
++ struct rp1_pio_interrupt_get_args args = { .pio_interrupt_num = pio_interrupt_num };
++
++ if (bad_params_if(client, pio_interrupt_num >= PIS_MAX))
++ return false;
++
++ (void)rp1_pio_interrupt_get(client, &args);
++
++ return !!args.active;
++}
++
++static inline void pio_interrupt_clear(struct rp1_pio_client *client, uint pio_interrupt_num)
++{
++ struct rp1_pio_interrupt_get_args args = { .pio_interrupt_num = pio_interrupt_num };
++
++ if (bad_params_if(client, pio_interrupt_num >= PIS_MAX))
++ return;
++ (void)rp1_pio_interrupt_clear(client, &args);
++}
++
+ #endif
+--- a/include/uapi/misc/rp1_pio_if.h
++++ b/include/uapi/misc/rp1_pio_if.h
+@@ -1,6 +1,6 @@
+ /* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
+ /*
+- * Copyright (c) 2023-24 Raspberry Pi Ltd.
++ * Copyright (c) 2023-26 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+ #ifndef _PIO_RP1_IF_H
+@@ -8,16 +8,17 @@
+
+ #include <linux/ioctl.h>
+
+-#define RP1_PIO_INSTRUCTION_COUNT 32
+-#define RP1_PIO_SM_COUNT 4
+-#define RP1_PIO_GPIO_COUNT 28
+-#define RP1_GPIO_FUNC_PIO 7
+-
+-#define RP1_PIO_ORIGIN_ANY ((uint16_t)(~0))
+-
+-#define RP1_PIO_DIR_TO_SM 0
+-#define RP1_PIO_DIR_FROM_SM 1
+-#define RP1_PIO_DIR_COUNT 2
++#define RP1_PIO_INSTRUCTION_COUNT 32
++#define RP1_PIO_SM_COUNT 4
++#define RP1_PIO_GPIO_COUNT 28
++#define RP1_GPIO_FUNC_PIO 7
++#define RP1_PIO_IRQ_COUNT 2
++
++#define RP1_PIO_ORIGIN_ANY ((uint16_t)(~0))
++
++#define RP1_PIO_DIR_TO_SM 0
++#define RP1_PIO_DIR_FROM_SM 1
++#define RP1_PIO_DIR_COUNT 2
+
+ typedef struct {
+ uint32_t clkdiv;
+@@ -203,6 +204,38 @@ struct rp1_access_hw_args {
+ void *data;
+ };
+
++struct rp1_pio_irq_claim_args {
++ int irq_index; /* OUT */
++};
++
++struct rp1_pio_irq_wait_args {
++ uint32_t timeout_ms;
++ uint32_t active_mask; /* OUT */
++};
++
++struct rp1_pio_irq_set_enabled_args {
++ uint16_t irq_index;
++ uint8_t enabled;
++ uint8_t rsvd;
++};
++
++struct rp1_pio_set_irqn_source_mask_enabled_args {
++ uint16_t irq_index;
++ uint8_t enabled;
++ uint8_t rsvd;
++ uint32_t source_mask;
++};
++
++struct rp1_pio_interrupt_get_args {
++ uint16_t pio_interrupt_num;
++ uint16_t rsvd;
++ uint8_t active; /* OUT */
++};
++
++struct rp1_pio_interrupt_clear_args {
++ uint16_t pio_interrupt_num;
++};
++
+ #define PIO_IOC_MAGIC 102
+
+ #define PIO_IOC_SM_CONFIG_XFER _IOW(PIO_IOC_MAGIC, 0, struct rp1_pio_sm_config_xfer_args)
+@@ -250,4 +283,13 @@ struct rp1_access_hw_args {
+ #define PIO_IOC_GPIO_SET_INPUT_ENABLED _IOW(PIO_IOC_MAGIC, 56, struct rp1_gpio_set_args)
+ #define PIO_IOC_GPIO_SET_DRIVE_STRENGTH _IOW(PIO_IOC_MAGIC, 57, struct rp1_gpio_set_args)
+
++#define PIO_IOC_IRQ_CLAIM _IOWR(PIO_IOC_MAGIC, 60, struct rp1_pio_irq_claim_args)
++#define PIO_IOC_IRQ_WAIT _IOWR(PIO_IOC_MAGIC, 61, struct rp1_pio_irq_wait_args)
++#define PIO_IOC_IRQ_SET_ENABLED _IOW(PIO_IOC_MAGIC, 62, struct rp1_pio_irq_set_enabled_args)
++#define PIO_IOC_IRQ_IS_ENABLED _IOWR(PIO_IOC_MAGIC, 63, struct rp1_pio_irq_set_enabled_args)
++#define PIO_IOC_SET_IRQN_SOURCE_MASK_ENABLED \
++ _IOW(PIO_IOC_MAGIC, 64, struct rp1_pio_set_irqn_source_mask_enabled_args)
++#define PIO_IOC_INTERRUPT_GET _IOWR(PIO_IOC_MAGIC, 65, struct rp1_pio_interrupt_get_args)
++#define PIO_IOC_INTERRUPT_CLEAR _IOW(PIO_IOC_MAGIC, 66, struct rp1_pio_interrupt_clear_args)
++
+ #endif
--- /dev/null
+From 202d26a6587cdd40814ef99d855e93f5ee2f0d3b Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Thu, 25 Jun 2026 12:06:11 +0100
+Subject: [PATCH] arm64: dts: rp1: Declare the PIO interrupts
+
+The PIO hardware drives 2 interrupt lines - declare them.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm64/boot/dts/broadcom/rp1.dtsi | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
+@@ -1057,6 +1057,8 @@
+ reg = <0xc0 0x40178000 0x0 0x20>;
+ compatible = "raspberrypi,rp1-pio";
+ firmware = <&rp1_firmware>;
++ interrupts = <RP1_INT_PIO_0 IRQ_TYPE_EDGE_RISING>,
++ <RP1_INT_PIO_1 IRQ_TYPE_EDGE_RISING>;
+ dmas = <&rp1_dma RP1_DMA_PIO_CH0_TX>, <&rp1_dma RP1_DMA_PIO_CH0_RX>,
+ <&rp1_dma RP1_DMA_PIO_CH1_TX>, <&rp1_dma RP1_DMA_PIO_CH1_RX>,
+ <&rp1_dma RP1_DMA_PIO_CH2_TX>, <&rp1_dma RP1_DMA_PIO_CH2_RX>,
--- /dev/null
+From bacfab828e6aa981fca1200c0bff311961764076 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 21 Jul 2026 08:02:07 +0100
+Subject: [PATCH] misc: rp1-pio: Non-blocking blocking operations
+
+The piolib API has three blocking operations:
+* pio_sm_get_blocking
+* pio_sm_put_blocking
+* pio_sm_exec_wait_blocking
+
+Rather than blocking in the firmware, stalling the whole API, allow
+the firmware to return EAGAIN to indicate that it has not completed.
+With a small change on each side it then becomes possible to poll for
+completion, allowing other operations to continue in the meantime.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/misc/rp1-pio.c | 69 ++++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 63 insertions(+), 6 deletions(-)
+
+--- a/drivers/misc/rp1-pio.c
++++ b/drivers/misc/rp1-pio.c
+@@ -15,6 +15,7 @@
+
+ #include <linux/cdev.h>
+ #include <linux/compat.h>
++#include <linux/delay.h>
+ #include <linux/device.h>
+ #include <linux/dmaengine.h>
+ #include <linux/dma-mapping.h>
+@@ -28,6 +29,7 @@
+ #include <linux/pio_rp1.h>
+ #include <linux/platform_device.h>
+ #include <linux/rp1-firmware.h>
++#include <linux/sched/signal.h>
+ #include <linux/semaphore.h>
+ #include <linux/slab.h>
+ #include <linux/spinlock.h>
+@@ -56,6 +58,14 @@
+
+ #define RP1_PIO_DMACTRL_DEFAULT 0x80000100
+
++#define POLL_INTERVAL_MIN (100)
++#define POLL_INTERVAL_MAX (100 * 1000)
++#define POLL_INTERVAL_NEXT(ival) \
++ do { \
++ if (ival < POLL_INTERVAL_MAX) \
++ ival = min(ival + (ival >> 1), POLL_INTERVAL_MAX); \
++ } while (0)
++
+ #define HANDLER(_n, _f) \
+ [_IOC_NR(PIO_IOC_ ## _n)] = { #_n, rp1_pio_ ## _f, _IOC_SIZE(PIO_IOC_ ## _n) }
+
+@@ -174,6 +184,10 @@ static int rp1_pio_message_resp(struct r
+ memcpy(resp, &resp_buf[1], ret);
+ else if (copy_to_user(userbuf, &resp_buf[1], ret))
+ ret = -EFAULT;
++ } else if (ret == 4) {
++ ret = (int)resp_buf[0];
++ if (ret == -1)
++ ret = -EIO;
+ } else if (ret >= 0) {
+ ret = -EIO;
+ }
+@@ -410,8 +424,23 @@ EXPORT_SYMBOL_GPL(rp1_pio_sm_set_config)
+ int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_sm_exec_args *args = param;
++ unsigned long interval = POLL_INTERVAL_MIN;
++ int ret;
++
++ if (args->blocking)
++ args->blocking = 2;
++
++ ret = rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args));
++ while (ret == -EAGAIN) {
++ if (signal_pending(current))
++ return -ERESTARTSYS;
++ fsleep(interval);
++ POLL_INTERVAL_NEXT(interval);
++ args->blocking = 3;
++ ret = rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args));
++ }
+
+- return rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args));
++ return ret;
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_sm_exec);
+
+@@ -482,20 +511,48 @@ EXPORT_SYMBOL_GPL(rp1_pio_sm_enable_sync
+ int rp1_pio_sm_put(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_sm_put_args *args = param;
++ unsigned long interval = POLL_INTERVAL_MIN;
++ int ret;
++
++ if (args->blocking)
++ args->blocking = 2;
+
+- return rp1_pio_message(client->pio, PIO_SM_PUT, args, sizeof(*args));
++ while (1) {
++ ret = rp1_pio_message(client->pio, PIO_SM_PUT, args, sizeof(*args));
++ if (ret != -EAGAIN)
++ break;
++ if (signal_pending(current))
++ return -ERESTARTSYS;
++ fsleep(interval);
++ POLL_INTERVAL_NEXT(interval);
++ }
++
++ return ret;
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_sm_put);
+
+ int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
+ {
+ struct rp1_pio_sm_get_args *args = param;
++ unsigned long interval = POLL_INTERVAL_MIN;
+ int ret;
+
+- ret = rp1_pio_message_resp(client->pio, PIO_SM_GET, args, sizeof(*args),
+- &args->data, NULL, sizeof(args->data));
+- if (ret >= 0)
+- return offsetof(struct rp1_pio_sm_get_args, data) + ret;
++ if (args->blocking)
++ args->blocking = 2;
++
++ while (1) {
++ ret = rp1_pio_message_resp(client->pio, PIO_SM_GET, args, sizeof(*args),
++ &args->data, NULL, sizeof(args->data));
++ if (ret >= 0)
++ return offsetof(struct rp1_pio_sm_get_args, data) + ret;
++ if (ret != -EAGAIN)
++ break;
++ if (signal_pending(current))
++ return -ERESTARTSYS;
++ fsleep(interval);
++ POLL_INTERVAL_NEXT(interval);
++ }
++
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(rp1_pio_sm_get);
--- /dev/null
+From abf7b07bc88b384342402eecc2d59febe0e79ebf Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 15 Jul 2026 16:13:59 +0100
+Subject: [PATCH] serial: Add rp1-pio-uart - a PIO-based UART
+
+This is a tty driver that implements a UART using RP1's PIO block,
+ported from pico-examples/pio/uart_dma. Like that example, it is
+limited to 8N1, with no hardware flow control.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/tty/serial/Kconfig | 13 +
+ drivers/tty/serial/Makefile | 2 +
+ drivers/tty/serial/rp1-pio-uart.c | 809 ++++++++++++++++++++++++++++++
+ include/uapi/linux/serial_core.h | 3 +
+ 4 files changed, 827 insertions(+)
+ create mode 100644 drivers/tty/serial/rp1-pio-uart.c
+
+--- a/drivers/tty/serial/Kconfig
++++ b/drivers/tty/serial/Kconfig
+@@ -1630,6 +1630,19 @@ config SERIAL_RPI_FW
+
+ If unsure, say N.
+
++config SERIAL_RP1_PIO_UART
++ tristate "Raspberry Pi RP1 PIO UART support"
++ depends on RP1_PIO || COMPILE_TEST
++ select SERIAL_CORE
++ help
++ This selects a software UART implemented using the RP1 PIO block,
++ with DMA moving data in and out of the PIO FIFOs and a PIO
++ interrupt used for break detection. Only 8N1 with no hardware
++ flow control is supported. Only useful on Raspberry Pi 5 and
++ newer platforms that have an RP1 southbridge.
++
++ If unsure, say N.
++
+ endmenu
+
+ config SERIAL_MCTRL_GPIO
+--- a/drivers/tty/serial/Makefile
++++ b/drivers/tty/serial/Makefile
+@@ -11,6 +11,7 @@ obj-$(CONFIG_SERIAL_EARLYCON_SEMIHOST) +
+ obj-$(CONFIG_SERIAL_EARLYCON_RISCV_SBI) += earlycon-riscv-sbi.o
+
+ obj-$(CONFIG_SERIAL_RPI_FW) += rpi-fw-uart.o
++obj-$(CONFIG_SERIAL_RP1_PIO_UART) += rp1-pio-uart.o
+ # These Sparc drivers have to appear before others such as 8250
+ # which share ttySx minor node space. Otherwise console device
+ # names change and other unplesantries.
+@@ -73,6 +74,7 @@ obj-$(CONFIG_SERIAL_QE) += ucc_uart.o
+ obj-$(CONFIG_SERIAL_RDA) += rda-uart.o
+ obj-$(CONFIG_SERIAL_RP2) += rp2.o
+ obj-$(CONFIG_SERIAL_RPI_FW) += rpi-fw-uart.o
++obj-$(CONFIG_SERIAL_RP1_PIO_UART) += rp1-pio-uart.o
+ obj-$(CONFIG_SERIAL_RSCI) += rsci.o
+ obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
+ obj-$(CONFIG_SERIAL_SAMSUNG) += samsung_tty.o
+--- /dev/null
++++ b/drivers/tty/serial/rp1-pio-uart.c
+@@ -0,0 +1,809 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * RP1 PIO-based UART driver
++ *
++ * Copyright (c) 2026, Raspberry Pi Ltd. All rights reserved.
++ *
++ * Implements a UART entirely in the RP1 PIO block: one state machine
++ * bit-bangs TX, another bit-bangs RX, and DMA moves the data in and out of
++ * their FIFOs. Ported from the pico-examples "pio/uart_dma" demo (BSD
++ * 3-Clause licensed), turning its one-shot bare-metal transfer into a
++ * continuous tty stream.
++ *
++ * Every per-SM register access exposed by rp1-pio (put/get/fifo_state/
++ * interrupt_clear/...) is a synchronous RP1 firmware mailbox round trip, not
++ * a register read/write, so unlike the demo this driver can *only* move
++ * data via DMA: there is no affordable way to poll or push/pop the PIO
++ * FIFOs a byte at a time from the CPU. The one primitive that *is* cheap
++ * and hardirq-capable is the PIO interrupt itself, so that is reserved for
++ * something DMA cannot express: break detection.
++ *
++ * The PIO TX/RX FIFOs are 32 bits wide and rp1-pio's DMA path always moves
++ * data 32 bits at a time, but each of our PIO programs only cares about one
++ * byte per FIFO word (the TX program pulls a fresh word per start bit; the
++ * RX program pushes a fresh word per stop bit). So bytes are expanded to
++ * 32-bit words before transmission and picked back out of 32-bit words on
++ * reception -- see rp1_pio_uart_pack_tx()/rp1_pio_uart_unpack_rx().
++ */
++
++#include <linux/bits.h>
++#include <linux/gpio/consumer.h>
++#include <linux/jiffies.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/pio_rp1.h>
++#include <linux/platform_device.h>
++#include <linux/serial.h>
++#include <linux/serial_core.h>
++#include <linux/slab.h>
++#include <linux/timer.h>
++#include <linux/tty.h>
++#include <linux/tty_flip.h>
++#include <linux/workqueue.h>
++
++#define DRIVER_NAME "rp1-pio-uart"
++
++/* ---- PIO programs, translated from pico-examples/pio/uart_tx and uart_rx ---- */
++
++#define UART_TX_WRAP_TARGET 0
++#define UART_TX_WRAP 3
++
++static const u16 uart_tx_program_instructions[] = {
++ 0x9fa0, // 0: pull block side 1 [7]
++ 0xf727, // 1: set x, 7 side 0 [7]
++ 0x6001, // 2: out pins, 1
++ 0x0642, // 3: jmp x--, 2 [6]
++};
++
++/*
++ * The full uart_rx program, not the 4-instruction "mini" variant the demo
++ * uses for its DMA path: the extra instructions detect a break or framing
++ * error and raise a PIO interrupt instead of silently pushing a corrupt
++ * byte.
++ */
++#define UART_RX_WRAP_TARGET 0
++#define UART_RX_WRAP 8
++
++static const u16 uart_rx_program_instructions[] = {
++ 0x2020, // 0: wait 0 pin, 0
++ 0xea27, // 1: set x, 7 [10]
++ 0x4001, // 2: in pins, 1
++ 0x0642, // 3: jmp x--, 2 [6]
++ 0x00c8, // 4: jmp pin, 8
++ 0xc010, // 5: irq nowait 0 rel
++ 0x20a0, // 6: wait 1 pin, 0
++ 0x0000, // 7: jmp 0
++ 0x8020, // 8: push block
++};
++
++static const struct pio_program uart_tx_program = {
++ .instructions = uart_tx_program_instructions,
++ .length = ARRAY_SIZE(uart_tx_program_instructions),
++ .origin = -1,
++};
++
++static const struct pio_program uart_rx_program = {
++ .instructions = uart_rx_program_instructions,
++ .length = ARRAY_SIZE(uart_rx_program_instructions),
++ .origin = -1,
++};
++
++/* Only 8N1 is implemented by the PIO programs above */
++#define RP1_PIO_UART_MIN_BAUD 400
++#define RP1_PIO_UART_MAX_BAUD 3000000
++#define RP1_PIO_UART_DEF_BAUD 115200
++
++/*
++ * TX: one 64-char chunk in flight at a time. RX: several small chunks kept
++ * pipelined so the PIO RX FIFO is always being drained. RX_CHUNK_CHARS
++ * bounds how long a burst of received bytes waits before being handed to
++ * the tty layer (a DMA chunk only "completes" once full) -- 8 chars is a
++ * throughput/latency compromise; lower it (down to 1) for lower latency at
++ * the cost of one DMA transaction per byte.
++ */
++#define RP1_PIO_UART_TX_CHUNK_CHARS 64
++#define RP1_PIO_UART_TX_CHUNK_BYTES (RP1_PIO_UART_TX_CHUNK_CHARS * 4)
++
++#define RP1_PIO_UART_RX_CHUNK_CHARS 8
++#define RP1_PIO_UART_RX_CHUNK_BYTES (RP1_PIO_UART_RX_CHUNK_CHARS * 4)
++#define RP1_PIO_UART_RX_SLOTS 4
++
++struct rp1_pio_uart;
++
++struct rp1_pio_uart_rx_slot {
++ struct rp1_pio_uart *rfu;
++ u8 *buf; /* RP1_PIO_UART_RX_CHUNK_BYTES, x4-packed */
++};
++
++struct rp1_pio_uart {
++ struct uart_driver driver;
++ struct uart_port port;
++
++ struct gpio_desc *tx_gpiod;
++ struct gpio_desc *rx_gpiod;
++ unsigned int tx_hwnum;
++ unsigned int rx_hwnum;
++
++ PIO pio;
++ unsigned int sm_tx;
++ unsigned int sm_rx;
++ unsigned int offset_tx;
++ unsigned int offset_rx;
++ int irq_index;
++
++ bool shutting_down;
++
++ /* TX: one chunk in flight at a time */
++ bool tx_busy;
++ u8 *tx_buf; /* RP1_PIO_UART_TX_CHUNK_BYTES, x4-packed */
++ struct work_struct tx_work;
++
++ /* RX: several chunks pipelined */
++ struct rp1_pio_uart_rx_slot rx_slots[RP1_PIO_UART_RX_SLOTS];
++ unsigned long rx_rearm_pending;
++ bool rx_stopped;
++ struct work_struct rx_rearm_work;
++
++ /* RX: CPU fallback for characters stuck below the DMA threshold */
++ struct timer_list rx_timeout;
++ struct work_struct rx_timeout_work;
++
++ struct work_struct break_work;
++};
++
++static inline struct rp1_pio_uart *port_to_rfu(struct uart_port *port)
++{
++ return container_of(port, struct rp1_pio_uart, port);
++}
++
++static void rp1_pio_uart_pack_tx(u8 *dst, const u8 *src, unsigned int count)
++{
++ unsigned int i;
++
++ for (i = 0; i < count; i++) {
++ dst[4 * i + 0] = src[i];
++ dst[4 * i + 1] = 0;
++ dst[4 * i + 2] = 0;
++ dst[4 * i + 3] = 0;
++ }
++}
++
++static void rp1_pio_uart_unpack_rx(const u8 *src, unsigned int count, u8 *dst)
++{
++ unsigned int i;
++
++ for (i = 0; i < count; i++)
++ dst[i] = src[4 * i + 3];
++}
++
++/* ---------------------------------- RX ---------------------------------- */
++
++static void rp1_pio_uart_rx_arm(struct rp1_pio_uart_rx_slot *slot);
++
++/*
++ * rp1_pio_sm_config_xfer_internal() (drivers/misc/rp1-pio.c) programs the RX
++ * SM's DREQ threshold to RP1_PIO_UART_RX_CHUNK_CHARS words, i.e. the joined,
++ * 8-deep RX FIFO is only drained by DMA once a full chunk has piled up in
++ * it. So if the sender goes quiet mid-chunk, the last few characters just
++ * sit in the FIFO forever -- DMA has nothing to trigger on and never comes
++ * back for them. rx_timeout notices the quiet spell and, since it runs in
++ * process context (see rp1_pio_uart_rx_timeout_work()), can afford to pop
++ * them out with pio_sm_get() instead.
++ */
++static unsigned long rp1_pio_uart_rx_timeout_jiffies(struct uart_port *port)
++{
++ u64 ns = (u64)READ_ONCE(port->frame_time) * RP1_PIO_UART_RX_CHUNK_CHARS;
++
++ /* Slop for scheduling/mailbox latency, and a floor for silly baud rates */
++ ns += 20 * NSEC_PER_MSEC;
++
++ return max(nsecs_to_jiffies(ns), 1UL);
++}
++
++/* DMA completion callback: context is whatever rp1-pio's dmaengine backend
++ * uses (tasklet/softirq in practice) -- do the context-safe part (deliver
++ * to the tty flip buffer) directly, defer the re-arm (which can allocate)
++ * to a workqueue.
++ */
++static void rp1_pio_uart_rx_complete(void *param)
++{
++ struct rp1_pio_uart_rx_slot *slot = param;
++ struct rp1_pio_uart *rfu = slot->rfu;
++ u8 chars[RP1_PIO_UART_RX_CHUNK_CHARS];
++ unsigned long flags;
++ bool stopped;
++
++ rp1_pio_uart_unpack_rx(slot->buf, RP1_PIO_UART_RX_CHUNK_CHARS, chars);
++ tty_insert_flip_string(&rfu->port.state->port, chars, RP1_PIO_UART_RX_CHUNK_CHARS);
++ tty_flip_buffer_push(&rfu->port.state->port);
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ rfu->port.icount.rx += RP1_PIO_UART_RX_CHUNK_CHARS;
++ stopped = rfu->shutting_down || rfu->rx_stopped;
++ if (!stopped)
++ set_bit(slot - rfu->rx_slots, &rfu->rx_rearm_pending);
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++
++ if (!stopped) {
++ queue_work(system_highpri_wq, &rfu->rx_rearm_work);
++
++ /* A chunk just completed, so the FIFO isn't backed up: push the
++ * deadline for the CPU fallback back out.
++ */
++ mod_timer(&rfu->rx_timeout, jiffies + rp1_pio_uart_rx_timeout_jiffies(&rfu->port));
++ }
++}
++
++static void rp1_pio_uart_rx_arm(struct rp1_pio_uart_rx_slot *slot)
++{
++ struct rp1_pio_uart *rfu = slot->rfu;
++ int ret;
++
++ ret = pio_sm_xfer_data(rfu->pio, rfu->sm_rx, PIO_DIR_FROM_SM,
++ RP1_PIO_UART_RX_CHUNK_BYTES, slot->buf, 0,
++ rp1_pio_uart_rx_complete, slot);
++ if (ret)
++ dev_err_ratelimited(rfu->port.dev, "RX DMA submit failed (%d)\n", ret);
++}
++
++static void rp1_pio_uart_rx_rearm_work(struct work_struct *work)
++{
++ struct rp1_pio_uart *rfu = container_of(work, struct rp1_pio_uart, rx_rearm_work);
++ unsigned long flags;
++ int i;
++
++ for (i = 0; i < RP1_PIO_UART_RX_SLOTS; i++) {
++ bool go;
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ go = test_and_clear_bit(i, &rfu->rx_rearm_pending) &&
++ !rfu->shutting_down && !rfu->rx_stopped;
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++
++ if (go)
++ rp1_pio_uart_rx_arm(&rfu->rx_slots[i]);
++ }
++}
++
++/* Runs in process context so it can make the mailbox calls that pio_sm_get()
++ * and pio_sm_rx_fifo_level() require -- unlike the DMA completion path,
++ * there is no way to do this straight out of the timer callback.
++ */
++static void rp1_pio_uart_rx_timeout_work(struct work_struct *work)
++{
++ struct rp1_pio_uart *rfu = container_of(work, struct rp1_pio_uart, rx_timeout_work);
++ u8 chars[RP1_PIO_UART_RX_CHUNK_CHARS];
++ unsigned long flags;
++ bool stopped;
++ int level;
++ int i;
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ stopped = rfu->shutting_down || rfu->rx_stopped;
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++ if (stopped)
++ return;
++
++ if (pio_sm_rx_fifo_level(rfu->pio, rfu->sm_rx) > 0) {
++ /*
++ * Stop the RX SM so it cannot push any more words into the
++ * FIFO while we drain it by hand: otherwise a fresh arrival
++ * could push the level past the DMA threshold mid-drain and
++ * race the DMA engine for the same words. A byte that is
++ * mid-reception at this exact instant can be lost, but that
++ * is the cost of doing this from a quiet-line timeout at all.
++ */
++ pio_sm_set_enabled(rfu->pio, rfu->sm_rx, false);
++
++ level = pio_sm_rx_fifo_level(rfu->pio, rfu->sm_rx);
++ /* Every FIFO word carries one byte of data in its top 8 bits,
++ * the rest is padding -- see rp1_pio_uart_unpack_rx().
++ */
++ for (i = 0; i < level; i++)
++ chars[i] = pio_sm_get(rfu->pio, rfu->sm_rx) >> 24;
++
++ pio_sm_set_enabled(rfu->pio, rfu->sm_rx, true);
++
++ if (level > 0) {
++ tty_insert_flip_string(&rfu->port.state->port, chars, level);
++ tty_flip_buffer_push(&rfu->port.state->port);
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ rfu->port.icount.rx += level;
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++ }
++ }
++
++ mod_timer(&rfu->rx_timeout, jiffies + rp1_pio_uart_rx_timeout_jiffies(&rfu->port));
++}
++
++/* Timer callback: softirq context, so just hand off to the workqueue */
++static void rp1_pio_uart_rx_timeout(struct timer_list *t)
++{
++ struct rp1_pio_uart *rfu = timer_container_of(rfu, t, rx_timeout);
++
++ queue_work(system_highpri_wq, &rfu->rx_timeout_work);
++}
++
++/* ---------------------------------- TX ---------------------------------- */
++
++static void rp1_pio_uart_tx_complete(void *param)
++{
++ struct rp1_pio_uart *rfu = param;
++ unsigned long flags;
++ bool more;
++
++ spin_lock_irqsave(&rfu->port.lock, flags);
++ rfu->tx_busy = false;
++ more = !rfu->shutting_down && !kfifo_is_empty(&rfu->port.state->port.xmit_fifo);
++ spin_unlock_irqrestore(&rfu->port.lock, flags);
++
++ if (more)
++ queue_work(system_highpri_wq, &rfu->tx_work);
++ else
++ uart_write_wakeup(&rfu->port);
++}
++
++static void rp1_pio_uart_tx_work(struct work_struct *work)
++{
++ struct rp1_pio_uart *rfu = container_of(work, struct rp1_pio_uart, tx_work);
++ struct uart_port *port = &rfu->port;
++ struct tty_port *tport = &port->state->port;
++ unsigned long flags;
++ unsigned char *tail;
++ unsigned int count;
++ int ret;
++
++ spin_lock_irqsave(&port->lock, flags);
++ if (rfu->tx_busy || rfu->shutting_down) {
++ spin_unlock_irqrestore(&port->lock, flags);
++ return;
++ }
++
++ count = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, RP1_PIO_UART_TX_CHUNK_CHARS);
++ if (!count) {
++ spin_unlock_irqrestore(&port->lock, flags);
++ return;
++ }
++
++ rp1_pio_uart_pack_tx(rfu->tx_buf, tail, count);
++ uart_xmit_advance(port, count);
++ rfu->tx_busy = true;
++ spin_unlock_irqrestore(&port->lock, flags);
++
++ ret = pio_sm_xfer_data(rfu->pio, rfu->sm_tx, PIO_DIR_TO_SM, count * 4, rfu->tx_buf, 0,
++ rp1_pio_uart_tx_complete, rfu);
++ if (ret) {
++ dev_err_ratelimited(port->dev, "TX DMA submit failed (%d)\n", ret);
++ spin_lock_irqsave(&port->lock, flags);
++ rfu->tx_busy = false;
++ spin_unlock_irqrestore(&port->lock, flags);
++ }
++
++ uart_write_wakeup(port);
++}
++
++/* Called with port->lock held */
++static void rp1_pio_uart_start_tx(struct uart_port *port)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++
++ if (rfu->tx_busy || rfu->shutting_down)
++ return;
++
++ queue_work(system_highpri_wq, &rfu->tx_work);
++}
++
++static void rp1_pio_uart_stop_tx(struct uart_port *port)
++{
++ /*
++ * Nothing exposed by pio_rp1.h can abort a DMA transfer already
++ * submitted to the PIO FIFO -- any in-flight chunk simply finishes.
++ */
++}
++
++static void rp1_pio_uart_stop_rx(struct uart_port *port)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++
++ rfu->rx_stopped = true;
++}
++
++/* -------------------------------- Break IRQ ------------------------------ */
++
++static void rp1_pio_uart_break_work(struct work_struct *work)
++{
++ struct rp1_pio_uart *rfu = container_of(work, struct rp1_pio_uart, break_work);
++ struct uart_port *port = &rfu->port;
++ unsigned long flags;
++ bool down;
++ uint intn;
++
++ intn = pio_interrupt_rel(rfu->sm_rx, 0);
++ pio_interrupt_clear(rfu->pio, intn);
++
++ spin_lock_irqsave(&port->lock, flags);
++ down = rfu->shutting_down;
++ if (!down)
++ port->icount.brk++;
++ spin_unlock_irqrestore(&port->lock, flags);
++
++ if (down)
++ return;
++
++ if (!uart_handle_break(port)) {
++ tty_insert_flip_char(&port->state->port, 0, TTY_BREAK);
++ tty_flip_buffer_push(&port->state->port);
++ }
++}
++
++/* Hardirq context: called directly from rp1-pio's own interrupt handler.
++ * pio_interrupt_clear() is a firmware mailbox call and must not run here.
++ */
++static void rp1_pio_uart_break_irq(void *context)
++{
++ struct rp1_pio_uart *rfu = context;
++
++ queue_work(system_highpri_wq, &rfu->break_work);
++}
++
++/* ------------------------------- uart_ops -------------------------------- */
++
++static unsigned int rp1_pio_uart_tx_empty(struct uart_port *port)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++ unsigned long flags;
++ bool empty;
++
++ spin_lock_irqsave(&port->lock, flags);
++ empty = !rfu->tx_busy && kfifo_is_empty(&port->state->port.xmit_fifo);
++ spin_unlock_irqrestore(&port->lock, flags);
++
++ return empty ? TIOCSER_TEMT : 0;
++}
++
++static void rp1_pio_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
++{
++ /* No hardware flow control */
++}
++
++static unsigned int rp1_pio_uart_get_mctrl(struct uart_port *port)
++{
++ return TIOCM_CTS;
++}
++
++static void rp1_pio_uart_break_ctl(struct uart_port *port, int ctl)
++{
++}
++
++static void rp1_pio_uart_set_baud(struct rp1_pio_uart *rfu, unsigned int baud)
++{
++ struct fp24_8 div = make_fp24_8(clock_get_hz(clk_sys), 8 * baud);
++
++ pio_sm_set_clkdiv(rfu->pio, rfu->sm_tx, div);
++ pio_sm_set_clkdiv(rfu->pio, rfu->sm_rx, div);
++}
++
++static void rp1_pio_uart_set_termios(struct uart_port *port, struct ktermios *new,
++ const struct ktermios *old)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++ unsigned long flags;
++ unsigned int baud;
++
++ baud = uart_get_baud_rate(port, new, old, RP1_PIO_UART_MIN_BAUD, RP1_PIO_UART_MAX_BAUD);
++
++ /* Only 8N1, no flow control: that is all the PIO programs implement */
++ new->c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS);
++ new->c_cflag |= CS8;
++
++ spin_lock_irqsave(&port->lock, flags);
++ uart_update_timeout(port, new->c_cflag, baud);
++ spin_unlock_irqrestore(&port->lock, flags);
++
++ rp1_pio_uart_set_baud(rfu, baud);
++}
++
++static int rp1_pio_uart_startup(struct uart_port *port)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++ pio_sm_config cfg;
++ uint intn;
++ int ret, i;
++
++ rfu->pio = pio_open();
++ if (IS_ERR(rfu->pio)) {
++ ret = PTR_ERR(rfu->pio);
++ rfu->pio = NULL;
++ dev_err(port->dev, "Could not open PIO (%d)\n", ret);
++ return ret;
++ }
++
++ ret = pio_claim_unused_sm(rfu->pio, false);
++ if (ret < 0) {
++ ret = -EBUSY;
++ goto err_close;
++ }
++ rfu->sm_tx = ret;
++
++ ret = pio_claim_unused_sm(rfu->pio, false);
++ if (ret < 0) {
++ ret = -EBUSY;
++ goto err_close;
++ }
++ rfu->sm_rx = ret;
++
++ ret = pio_add_program(rfu->pio, &uart_tx_program);
++ if (ret == PIO_ORIGIN_INVALID) {
++ ret = -EBUSY;
++ goto err_close;
++ }
++ rfu->offset_tx = ret;
++
++ ret = pio_add_program(rfu->pio, &uart_rx_program);
++ if (ret == PIO_ORIGIN_INVALID) {
++ ret = -EBUSY;
++ goto err_close;
++ }
++ rfu->offset_rx = ret;
++
++ /* TX SM: side-set drives the start/stop bits, OUT drives data bits */
++ pio_sm_set_pins_with_mask(rfu->pio, rfu->sm_tx, BIT(rfu->tx_hwnum), BIT(rfu->tx_hwnum));
++ pio_sm_set_consecutive_pindirs(rfu->pio, rfu->sm_tx, rfu->tx_hwnum, 1, true);
++ pio_gpio_init(rfu->pio, rfu->tx_hwnum);
++
++ cfg = pio_get_default_sm_config();
++ sm_config_set_wrap(&cfg, rfu->offset_tx + UART_TX_WRAP_TARGET,
++ rfu->offset_tx + UART_TX_WRAP);
++ sm_config_set_sideset(&cfg, 2, true, false);
++ sm_config_set_out_shift(&cfg, true, false, 32);
++ sm_config_set_out_pins(&cfg, rfu->tx_hwnum, 1);
++ sm_config_set_sideset_pins(&cfg, rfu->tx_hwnum);
++ sm_config_set_fifo_join(&cfg, PIO_FIFO_JOIN_TX);
++ pio_sm_init(rfu->pio, rfu->sm_tx, rfu->offset_tx, &cfg);
++
++ /* RX SM */
++ pio_sm_set_consecutive_pindirs(rfu->pio, rfu->sm_rx, rfu->rx_hwnum, 1, false);
++ pio_gpio_init(rfu->pio, rfu->rx_hwnum);
++ pio_gpio_pull_up(rfu->pio, rfu->rx_hwnum);
++
++ cfg = pio_get_default_sm_config();
++ sm_config_set_wrap(&cfg, rfu->offset_rx + UART_RX_WRAP_TARGET,
++ rfu->offset_rx + UART_RX_WRAP);
++ sm_config_set_in_pins(&cfg, rfu->rx_hwnum);
++ sm_config_set_jmp_pin(&cfg, rfu->rx_hwnum);
++ sm_config_set_in_shift(&cfg, true, false, 32);
++ sm_config_set_fifo_join(&cfg, PIO_FIFO_JOIN_RX);
++ pio_sm_init(rfu->pio, rfu->sm_rx, rfu->offset_rx, &cfg);
++
++ rp1_pio_uart_set_baud(rfu, RP1_PIO_UART_DEF_BAUD);
++
++ /* Break detection: hooked to PIO IRQ 0, resolved relative to sm_rx */
++ ret = pio_get_irq_num(rfu->pio, 0);
++ if (ret < 0) {
++ if (ret == -ENODEV)
++ dev_err(port->dev, "No PIO interrupts - old EEPROM?\n");
++ else if (ret == -EBUSY)
++ dev_err(port->dev, "No free PIO interrupts\n");
++ goto err_close;
++ }
++ rfu->irq_index = ret;
++ irq_add_handler(rfu->pio, rfu->irq_index, rp1_pio_uart_break_irq, rfu);
++ intn = pio_interrupt_rel(rfu->sm_rx, 0);
++ pio_set_irqn_source_enabled(rfu->pio, 0, PIS_INTERRUPT0 + intn, true);
++ irq_set_enabled(rfu->pio, rfu->irq_index, true);
++
++ pio_interrupt_clear(rfu->pio, intn);
++
++ pio_sm_set_enabled(rfu->pio, rfu->sm_tx, true);
++ pio_sm_set_enabled(rfu->pio, rfu->sm_rx, true);
++
++ /* Request rp1-pio's internal DMA bounce buffers -- see the file
++ * comment on why a client can't just dma_alloc_coherent its own.
++ */
++ ret = pio_sm_config_xfer(rfu->pio, rfu->sm_tx, PIO_DIR_TO_SM,
++ RP1_PIO_UART_TX_CHUNK_BYTES, 1);
++ if (ret)
++ goto err_close;
++
++ ret = pio_sm_config_xfer(rfu->pio, rfu->sm_rx, PIO_DIR_FROM_SM,
++ RP1_PIO_UART_RX_CHUNK_BYTES, RP1_PIO_UART_RX_SLOTS);
++ if (ret)
++ goto err_close;
++
++ rfu->shutting_down = false;
++ rfu->tx_busy = false;
++ rfu->rx_stopped = false;
++ rfu->rx_rearm_pending = 0;
++
++ /* Keep every RX slot's DMA request outstanding at all times */
++ for (i = 0; i < RP1_PIO_UART_RX_SLOTS; i++)
++ rp1_pio_uart_rx_arm(&rfu->rx_slots[i]);
++
++ mod_timer(&rfu->rx_timeout, jiffies + rp1_pio_uart_rx_timeout_jiffies(&rfu->port));
++
++ return 0;
++
++err_close:
++ /* pio_close() unclaims any SMs/instructions/DMA this client claimed */
++ pio_close(rfu->pio);
++ rfu->pio = NULL;
++ return ret;
++}
++
++static void rp1_pio_uart_shutdown(struct uart_port *port)
++{
++ struct rp1_pio_uart *rfu = port_to_rfu(port);
++ unsigned long flags;
++
++ spin_lock_irqsave(&port->lock, flags);
++ rfu->shutting_down = true;
++ spin_unlock_irqrestore(&port->lock, flags);
++
++ if (rfu->irq_index >= 0) {
++ uint intn = pio_interrupt_rel(rfu->sm_rx, 0);
++
++ irq_set_enabled(rfu->pio, rfu->irq_index, false);
++ pio_set_irqn_source_enabled(rfu->pio, 0, PIS_INTERRUPT0 + intn, false);
++ pio_interrupt_clear(rfu->pio, intn);
++ rfu->irq_index = -1;
++ }
++
++ cancel_work_sync(&rfu->tx_work);
++ cancel_work_sync(&rfu->rx_rearm_work);
++ timer_delete_sync(&rfu->rx_timeout);
++ cancel_work_sync(&rfu->rx_timeout_work);
++ cancel_work_sync(&rfu->break_work);
++
++ pio_sm_set_enabled(rfu->pio, rfu->sm_tx, false);
++ pio_sm_set_enabled(rfu->pio, rfu->sm_rx, false);
++ pio_remove_program(rfu->pio, &uart_tx_program, rfu->offset_tx);
++ pio_remove_program(rfu->pio, &uart_rx_program, rfu->offset_rx);
++ pio_sm_unclaim(rfu->pio, rfu->sm_tx);
++ pio_sm_unclaim(rfu->pio, rfu->sm_rx);
++ pio_close(rfu->pio);
++ rfu->pio = NULL;
++}
++
++static const struct uart_ops rp1_pio_uart_ops = {
++ .tx_empty = rp1_pio_uart_tx_empty,
++ .set_mctrl = rp1_pio_uart_set_mctrl,
++ .get_mctrl = rp1_pio_uart_get_mctrl,
++ .stop_tx = rp1_pio_uart_stop_tx,
++ .start_tx = rp1_pio_uart_start_tx,
++ .stop_rx = rp1_pio_uart_stop_rx,
++ .break_ctl = rp1_pio_uart_break_ctl,
++ .startup = rp1_pio_uart_startup,
++ .shutdown = rp1_pio_uart_shutdown,
++ .set_termios = rp1_pio_uart_set_termios,
++};
++
++/* ------------------------------- Device setup ---------------------------- */
++
++static int rp1_pio_uart_get_gpio_hwnum(struct device *dev, const char *name)
++{
++ struct of_phandle_args of_args = { 0 };
++ char prop[16];
++ bool is_rp1_gpio;
++ int ret;
++
++ snprintf(prop, sizeof(prop), "%s-gpios", name);
++
++ ret = of_parse_phandle_with_args(dev->of_node, prop, "#gpio-cells", 0, &of_args);
++ if (ret)
++ return dev_err_probe(dev, ret, "can't parse %s\n", prop);
++
++ is_rp1_gpio = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
++ of_node_put(of_args.np);
++ if (!is_rp1_gpio || of_args.args_count != 2)
++ return dev_err_probe(dev, -EINVAL, "%s is not an RP1 gpio\n", prop);
++
++ return of_args.args[0];
++}
++
++static int rp1_pio_uart_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct rp1_pio_uart *rfu;
++ int ret, i;
++
++ rfu = devm_kzalloc(dev, sizeof(*rfu), GFP_KERNEL);
++ if (!rfu)
++ return -ENOMEM;
++
++ rfu->tx_gpiod = devm_gpiod_get(dev, "tx", GPIOD_OUT_HIGH);
++ if (IS_ERR(rfu->tx_gpiod))
++ return PTR_ERR(rfu->tx_gpiod);
++
++ rfu->rx_gpiod = devm_gpiod_get(dev, "rx", GPIOD_IN);
++ if (IS_ERR(rfu->rx_gpiod))
++ return PTR_ERR(rfu->rx_gpiod);
++
++ ret = rp1_pio_uart_get_gpio_hwnum(dev, "tx");
++ if (ret < 0)
++ return ret;
++ rfu->tx_hwnum = ret;
++
++ ret = rp1_pio_uart_get_gpio_hwnum(dev, "rx");
++ if (ret < 0)
++ return ret;
++ rfu->rx_hwnum = ret;
++
++ rfu->tx_buf = devm_kzalloc(dev, RP1_PIO_UART_TX_CHUNK_BYTES, GFP_KERNEL);
++ if (!rfu->tx_buf)
++ return -ENOMEM;
++
++ for (i = 0; i < RP1_PIO_UART_RX_SLOTS; i++) {
++ rfu->rx_slots[i].rfu = rfu;
++ rfu->rx_slots[i].buf = devm_kzalloc(dev, RP1_PIO_UART_RX_CHUNK_BYTES, GFP_KERNEL);
++ if (!rfu->rx_slots[i].buf)
++ return -ENOMEM;
++ }
++
++ INIT_WORK(&rfu->tx_work, rp1_pio_uart_tx_work);
++ INIT_WORK(&rfu->rx_rearm_work, rp1_pio_uart_rx_rearm_work);
++ INIT_WORK(&rfu->rx_timeout_work, rp1_pio_uart_rx_timeout_work);
++ timer_setup(&rfu->rx_timeout, rp1_pio_uart_rx_timeout, 0);
++ INIT_WORK(&rfu->break_work, rp1_pio_uart_break_work);
++ rfu->irq_index = -1;
++
++ rfu->driver.owner = THIS_MODULE;
++ rfu->driver.driver_name = DRIVER_NAME;
++ rfu->driver.dev_name = "ttyPIO";
++ rfu->driver.nr = 1;
++
++ ret = uart_register_driver(&rfu->driver);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to register UART driver\n");
++
++ spin_lock_init(&rfu->port.lock);
++ rfu->port.dev = dev;
++ rfu->port.type = PORT_RP1_PIO;
++ rfu->port.ops = &rp1_pio_uart_ops;
++ rfu->port.fifosize = RP1_PIO_UART_TX_CHUNK_CHARS;
++ rfu->port.iotype = UPIO_MEM;
++ rfu->port.flags = UPF_BOOT_AUTOCONF;
++ rfu->port.private_data = rfu;
++
++ ret = uart_add_one_port(&rfu->driver, &rfu->port);
++ if (ret) {
++ dev_err(dev, "failed to add UART port: %d\n", ret);
++ goto err_unregister;
++ }
++
++ platform_set_drvdata(pdev, rfu);
++ dev_info(dev, "PIO UART on GPIOs tx %u rx %u\n", rfu->tx_hwnum, rfu->rx_hwnum);
++ return 0;
++
++err_unregister:
++ uart_unregister_driver(&rfu->driver);
++ return ret;
++}
++
++static void rp1_pio_uart_remove(struct platform_device *pdev)
++{
++ struct rp1_pio_uart *rfu = platform_get_drvdata(pdev);
++
++ uart_remove_one_port(&rfu->driver, &rfu->port);
++ uart_unregister_driver(&rfu->driver);
++}
++
++static const struct of_device_id rp1_pio_uart_match[] = {
++ { .compatible = "raspberrypi,rp1-pio-uart" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, rp1_pio_uart_match);
++
++static struct platform_driver rp1_pio_uart_driver = {
++ .driver = {
++ .name = DRIVER_NAME,
++ .of_match_table = rp1_pio_uart_match,
++ },
++ .probe = rp1_pio_uart_probe,
++ .remove = rp1_pio_uart_remove,
++};
++module_platform_driver(rp1_pio_uart_driver);
++
++MODULE_AUTHOR("Raspberry Pi Ltd.");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("RP1 PIO-based UART driver");
+--- a/include/uapi/linux/serial_core.h
++++ b/include/uapi/linux/serial_core.h
+@@ -234,6 +234,9 @@
+ /* RPi firmware UART */
+ #define PORT_RPI_FW 124
+
++/* RP1 PIO UART */
++#define PORT_RP1_PIO 125
++
+ /* Generic type identifier for ports which type is not important to userspace. */
+ #define PORT_GENERIC (-1)
+
--- /dev/null
+From 60ea684a8ace97bb0db1a16e20753bdd6ab371ff Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 15 Jul 2026 16:16:46 +0100
+Subject: [PATCH] overlays: Add the rp1-pio-uart overlay
+
+An overlay to enable the PIO-based UART.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 11 ++++++++
+ .../dts/overlays/rp1-pio-uart-overlay.dts | 25 +++++++++++++++++++
+ 3 files changed, 37 insertions(+)
+ create mode 100644 arch/arm/boot/dts/overlays/rp1-pio-uart-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -247,6 +247,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ ramoops-pi4.dtbo \
+ rootmaster.dtbo \
+ rotary-encoder.dtbo \
++ rp1-pio-uart.dtbo \
+ rpi-backlight.dtbo \
+ rpi-codeczero.dtbo \
+ rpi-dacplus.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -4527,6 +4527,17 @@ Params: pin_a GPIO con
+ common) and "binary".
+
+
++Name: rp1-pio-uart
++Info: Configures a software UART implemented using the RP1 PIO block, with
++ DMA moving data and a PIO interrupt used for break detection. Only
++ 8N1 with no hardware flow control is supported. Only usable on
++ Raspberry Pi 5 and other platforms with an RP1 southbridge.
++Load: dtoverlay=rp1-pio-uart,<param>=<val>
++Params: tx_pin GPIO used for TXD (any free - default 14)
++
++ rx_pin GPIO used for RXD (any free - default 15)
++
++
+ Name: rpi-backlight
+ Info: Raspberry Pi official display backlight driver
+ Load: dtoverlay=rpi-backlight
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/rp1-pio-uart-overlay.dts
+@@ -0,0 +1,25 @@
++// SPDX-License-Identifier: GPL-2.0
++// Device tree overlay for the RP1 PIO-based UART driver.
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2712";
++
++ fragment@0 {
++ target-path = "/";
++ __overlay__ {
++ rp1_pio_uart: rp1_pio_uart@e {
++ compatible = "raspberrypi,rp1-pio-uart";
++ tx-gpios = <&gpio 14 0>;
++ rx-gpios = <&gpio 15 0>;
++ status = "okay";
++ };
++ };
++ };
++
++ __overrides__ {
++ tx_pin = <&rp1_pio_uart>, "tx-gpios:4";
++ rx_pin = <&rp1_pio_uart>, "rx-gpios:4";
++ };
++};
--- /dev/null
+From 33bb14b06b3fb5a682d4a7a3db3963fe558fc6f9 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 24 Jul 2026 14:34:52 +0100
+Subject: [PATCH] overlays: Add ramoops-pi5
+
+The Pi 5 dts uses 64-bit sizes in some places, and the ramoops overlay
+needs to be updated accordingly.
+
+See: https://github.com/raspberrypi/linux/issues/7517
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/Makefile | 1 +
+ arch/arm/boot/dts/overlays/README | 16 ++++--------
+ arch/arm/boot/dts/overlays/overlay_map.dts | 5 +++-
+ .../boot/dts/overlays/ramoops-pi5-overlay.dts | 25 +++++++++++++++++++
+ 4 files changed, 35 insertions(+), 12 deletions(-)
+ create mode 100644 arch/arm/boot/dts/overlays/ramoops-pi5-overlay.dts
+
+--- a/arch/arm/boot/dts/overlays/Makefile
++++ b/arch/arm/boot/dts/overlays/Makefile
+@@ -245,6 +245,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
+ qca7000-uart0.dtbo \
+ ramoops.dtbo \
+ ramoops-pi4.dtbo \
++ ramoops-pi5.dtbo \
+ rootmaster.dtbo \
+ rotary-encoder.dtbo \
+ rp1-pio-uart.dtbo \
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -4475,17 +4475,11 @@ Params: base-addr Where to
+
+
+ Name: ramoops-pi4
+-Info: The version of the ramoops overlay for the Pi 4 family. It should be
+- loaded automatically if dtoverlay=ramoops is specified on a Pi 4.
+-Load: dtoverlay=ramoops-pi4,<param>=<val>
+-Params: base-addr Where to place the capture buffer (default
+- 0x0b000000)
+- total-size How much memory to allocate altogether (in
+- bytes - default 64kB)
+- record-size How much space to use for each capture, i.e.
+- total-size / record-size = number of captures
+- (default 16kB)
+- console-size Size of non-panic dmesg captures (default 0)
++Info: See ramoops
++
++
++Name: ramoops-pi5
++Info: See ramoops
+
+
+ Name: rootmaster
+--- a/arch/arm/boot/dts/overlays/overlay_map.dts
++++ b/arch/arm/boot/dts/overlays/overlay_map.dts
+@@ -274,11 +274,14 @@
+ ramoops {
+ bcm2835;
+ bcm2711 = "ramoops-pi4";
+- bcm2712 = "ramoops-pi4";
++ bcm2712 = "ramoops-pi5";
+ };
+
+ ramoops-pi4 {
+ bcm2711;
++ };
++
++ ramoops-pi5 {
+ bcm2712;
+ };
+
+--- /dev/null
++++ b/arch/arm/boot/dts/overlays/ramoops-pi5-overlay.dts
+@@ -0,0 +1,25 @@
++/dts-v1/;
++/plugin/;
++
++/ {
++ compatible = "brcm,bcm2712";
++
++ fragment@0 {
++ target = <&rmem>;
++ __overlay__ {
++ ramoops: ramoops@b000000 {
++ compatible = "ramoops";
++ reg = <0x0 0x0b000000 0x0 0x10000>; /* 64kB */
++ record-size = <0x4000>; /* 16kB */
++ console-size = <0>; /* disabled by default */
++ };
++ };
++ };
++
++ __overrides__ {
++ base-addr = <&ramoops>,"reg#0";
++ total-size = <&ramoops>,"reg#8";
++ record-size = <&ramoops>,"record-size:0";
++ console-size = <&ramoops>,"console-size:0";
++ };
++};
--- /dev/null
+From 43de7f5e9c900cf9e2464d2e8bdd9bce12c5b576 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Mon, 27 Jul 2026 16:22:08 +0100
+Subject: [PATCH] overlays: apds9960: Add missing pinctrl reference
+
+The apds9960 overlays declares its pin usage (one input for interrupts),
+but doesn't include a reference from the driver node. This seems to be
+harmless - declaring inputs and outputs for GPIOs is almost certainly
+unnecessary - but it is flagged as an error by an improved version of
+the overlaycheck utility.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/apds9960-overlay.dts | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/arm/boot/dts/overlays/apds9960-overlay.dts
++++ b/arch/arm/boot/dts/overlays/apds9960-overlay.dts
+@@ -41,6 +41,8 @@
+ apds9960: apds@39 {
+ compatible = "avago,apds9960";
+ reg = <0x39>;
++ pinctrl-names = "default";
++ pinctrl-0 = <&apds9960_pins>;
+ status = "okay";
+ };
+ };
--- /dev/null
+From 8307cc8d4cf84a24cf12f5b71c11f1da87a55688 Mon Sep 17 00:00:00 2001
+From: Dave Stevenson <dave.stevenson@raspberrypi.com>
+Date: Wed, 8 Nov 2023 16:20:27 +0000
+Subject: [PATCH] input: goodix: Include I2C details in names for the devices
+
+libinput uses the input device name alone. If you have two
+identical input devices, then there is no way to differentiate
+between them, and in the case of touchscreens that means no
+way to associate them with the appropriate display device.
+
+Add the I2C bus and address to the start of the input device
+name so that the name is always unique within the system.
+
+Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
+---
+ drivers/input/touchscreen/goodix.c | 5 ++++-
+ drivers/input/touchscreen/goodix.h | 3 +++
+ 2 files changed, 7 insertions(+), 1 deletion(-)
+
+--- a/drivers/input/touchscreen/goodix.c
++++ b/drivers/input/touchscreen/goodix.c
+@@ -1151,7 +1151,10 @@ static int goodix_configure_dev(struct g
+ return -ENOMEM;
+ }
+
+- ts->input_dev->name = "Goodix Capacitive TouchScreen";
++ snprintf(ts->name, GOODIX_NAME_MAX_LEN, "%s Goodix Capacitive TouchScreen",
++ dev_name(&ts->client->dev));
++
++ ts->input_dev->name = ts->name;
+ ts->input_dev->phys = "input/ts";
+ ts->input_dev->id.bustype = BUS_I2C;
+ ts->input_dev->id.vendor = 0x0416;
+--- a/drivers/input/touchscreen/goodix.h
++++ b/drivers/input/touchscreen/goodix.h
+@@ -57,6 +57,8 @@
+ #define GOODIX_CONFIG_MAX_LENGTH 240
+ #define GOODIX_MAX_KEYS 7
+
++#define GOODIX_NAME_MAX_LEN 38
++
+ enum goodix_irq_pin_access_method {
+ IRQ_PIN_ACCESS_NONE,
+ IRQ_PIN_ACCESS_GPIO,
+@@ -90,6 +92,7 @@ struct goodix_ts_data {
+ int gpio_int_idx;
+ char id[GOODIX_ID_MAX_LEN + 1];
+ char cfg_name[64];
++ char name[GOODIX_NAME_MAX_LEN];
+ u16 version;
+ bool reset_controller_at_probe;
+ bool load_cfg_from_disk;
--- /dev/null
+From 32d8cce2dd2094e8bfefa982744695742617f2c2 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 17:11:29 +0100
+Subject: [PATCH] nvme/host/pci: Revert to upstream
+
+---
+ drivers/nvme/host/pci.c | 102 +---------------------------------------
+ 1 file changed, 2 insertions(+), 100 deletions(-)
+
+--- a/drivers/nvme/host/pci.c
++++ b/drivers/nvme/host/pci.c
+@@ -183,11 +183,9 @@ struct nvme_dev {
+ /* host memory buffer support: */
+ u64 host_mem_size;
+ u32 nr_host_mem_descs;
+- u32 nr_sgl_ents;
+ u32 host_mem_descs_size;
+ dma_addr_t host_mem_descs_dma;
+ struct nvme_host_mem_buf_desc *host_mem_descs;
+- struct scatterlist *host_mem_sgl;
+ void **host_mem_desc_bufs;
+ unsigned int nr_allocated_queues;
+ unsigned int nr_write_queues;
+@@ -768,8 +766,8 @@ static void nvme_unmap_data(struct reque
+
+ if (!blk_rq_dma_unmap(req, dma_dev, &iod->dma_state, iod->total_len)) {
+ if (nvme_pci_cmd_use_sgl(&iod->cmd))
+- nvme_free_sgls(req, &iod->cmd.common.dptr.sgl,
+- iod->descriptors[0]);
++ nvme_free_sgls(req, iod->descriptors[0],
++ &iod->cmd.common.dptr.sgl);
+ else
+ nvme_free_prps(req);
+ }
+@@ -2308,13 +2306,6 @@ static int nvme_set_host_mem(struct nvme
+ return ret;
+ }
+
+-#if IS_ENABLED(CONFIG_ARM64)
+-static void nvme_free_host_mem_multi(struct nvme_dev *dev)
+-{
+- dma_unmap_sg(dev->dev, dev->host_mem_sgl, dev->nr_host_mem_descs, DMA_FROM_DEVICE);
+- sgl_free(dev->host_mem_sgl);
+-}
+-#else
+ static void nvme_free_host_mem_multi(struct nvme_dev *dev)
+ {
+ int i;
+@@ -2331,7 +2322,6 @@ static void nvme_free_host_mem_multi(str
+ kfree(dev->host_mem_desc_bufs);
+ dev->host_mem_desc_bufs = NULL;
+ }
+-#endif
+
+ static void nvme_free_host_mem(struct nvme_dev *dev)
+ {
+@@ -2376,93 +2366,6 @@ static int nvme_alloc_host_mem_single(st
+ return 0;
+ }
+
+-#if IS_ENABLED(CONFIG_ARM64)
+-static int nvme_alloc_host_mem_multi(struct nvme_dev *dev, u64 preferred,
+- u32 chunk_size)
+-{
+- struct nvme_host_mem_buf_desc *descs;
+- u32 max_entries, len, descs_size;
+- dma_addr_t descs_dma;
+- struct scatterlist *slist;
+- struct page *page;
+- int i = 0, mapped_nents;
+- u64 size, tmp;
+-
+- tmp = (preferred + chunk_size - 1);
+- do_div(tmp, chunk_size);
+- max_entries = tmp;
+-
+- if (dev->ctrl.hmmaxd && dev->ctrl.hmmaxd < max_entries)
+- max_entries = dev->ctrl.hmmaxd;
+-
+- descs_size = max_entries * sizeof(*descs);
+- /*
+- * Allocate the descriptor table from coherent memory -
+- * usually occupies less than/up to a single page.
+- */
+- descs = dma_alloc_coherent(dev->dev, descs_size, &descs_dma,
+- GFP_KERNEL);
+- if (!descs)
+- goto out;
+-
+- slist = kcalloc(max_entries, sizeof(struct scatterlist), GFP_KERNEL);
+- if (!slist)
+- goto out_free_descs;
+-
+- sg_init_table(slist, max_entries);
+-
+- dev_dbg(dev->dev, "Allocating HMB pref = %llu max_entries = %u\n",
+- preferred, max_entries);
+-
+- for (size = 0; size < preferred && i < max_entries; size += len) {
+- int order;
+-
+- len = min_t(u64, chunk_size, preferred - size);
+- order = get_order(len);
+- page = alloc_pages(GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN, order);
+- if (!page)
+- break;
+- sg_set_page(&slist[i], page, len, 0);
+- i++;
+- }
+- if (size < preferred)
+- goto out_free_sgl;
+-
+- mapped_nents = dma_map_sg(dev->dev, slist, i, DMA_FROM_DEVICE);
+- if (mapped_nents <= 0)
+- goto out_free_pages;
+-
+- /* Flush in case the CPU has cached any parts of the DMA buffers */
+- dma_sync_sg_for_device(dev->dev, slist, i, DMA_FROM_DEVICE);
+-
+- i = dev->nr_host_mem_descs = mapped_nents;
+-
+- while (--i >= 0) {
+- descs[i].addr = cpu_to_le64(sg_dma_address(&slist[i]));
+- WARN_ON_ONCE(sg_dma_len(&slist[i]) & (NVME_CTRL_PAGE_SIZE - 1));
+- descs[i].size = cpu_to_le32(sg_dma_len(&slist[i]) / NVME_CTRL_PAGE_SIZE);
+- }
+-
+- dev->host_mem_size = size;
+- dev->host_mem_descs = descs;
+- dev->host_mem_descs_dma = descs_dma;
+- dev->host_mem_descs_size = descs_size;
+- dev->host_mem_sgl = slist;
+- return 0;
+-
+-out_free_pages:
+- /* Don't use mapped_nents here as it could be incomplete */
+- while (--i >= 0)
+- __free_pages(sg_page(&slist[i]), get_order(slist[i].length));
+-out_free_sgl:
+- kfree(slist);
+-out_free_descs:
+- dma_free_coherent(dev->dev, descs_size, descs, descs_dma);
+-out:
+- dev->host_mem_descs = NULL;
+- return -ENOMEM;
+-}
+-#else
+ static int nvme_alloc_host_mem_multi(struct nvme_dev *dev, u64 preferred,
+ u32 chunk_size)
+ {
+@@ -2523,7 +2426,6 @@ out:
+ dev->host_mem_descs = NULL;
+ return -ENOMEM;
+ }
+-#endif
+
+ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
+ {
--- /dev/null
+From c6759b17fdcca5d08b5ae4c17ca5d1df0316d686 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 17:10:37 +0100
+Subject: [PATCH] mmc/host/sdhci-of-dwcmshc: Revert to upstream
+
+---
+ drivers/mmc/host/sdhci-of-dwcmshc.c | 62 ++---------------------------
+ 1 file changed, 4 insertions(+), 58 deletions(-)
+
+--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
++++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
+@@ -221,7 +221,6 @@ struct rk35xx_priv {
+
+ struct dwcmshc_priv {
+ struct clk *bus_clk;
+- struct clk *sdio_clk;
+ int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */
+ int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */
+
+@@ -303,17 +302,6 @@ static void dwcmshc_reset(struct sdhci_h
+ sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS);
+ }
+
+-static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock)
+-{
+- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+- struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
+-
+- if (priv->sdio_clk)
+- clk_set_rate(priv->sdio_clk, clock);
+-
+- sdhci_set_clock(host, clock);
+-}
+-
+ static unsigned int dwcmshc_get_max_clock(struct sdhci_host *host)
+ {
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+@@ -1133,11 +1121,10 @@ static int sg2042_init(struct device *de
+ }
+
+ static const struct sdhci_ops sdhci_dwcmshc_ops = {
+- .set_clock = dwcmshc_set_clock,
++ .set_clock = sdhci_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .get_max_clock = dwcmshc_get_max_clock,
+- .get_timeout_clock = sdhci_pltfm_clk_get_timeout_clock,
+ .reset = dwcmshc_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+ .irq = dwcmshc_cqe_irq_handler,
+@@ -1210,10 +1197,8 @@ static const struct sdhci_ops sdhci_dwcm
+ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_ops,
+- .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+- SDHCI_QUIRK_BROKEN_CARD_DETECTION,
+- .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+- SDHCI_QUIRK2_BROKEN_HS200,
++ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
++ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
+ },
+ };
+
+@@ -1228,28 +1213,13 @@ static const struct dwcmshc_pltfm_data s
+ };
+ #endif
+
+-static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rp1_pdata = {
+- .pdata = {
+- .ops = &sdhci_dwcmshc_ops,
+- .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+- SDHCI_QUIRK_BROKEN_CARD_DETECTION,
+- .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+- SDHCI_QUIRK2_BROKEN_HS200 |
+- SDHCI_QUIRK2_SPURIOUS_INT_RESP,
+- }
+-};
+-
+ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_rk35xx_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+- SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN |
+- SDHCI_QUIRK2_NO_SDR50 |
+- SDHCI_QUIRK2_NO_SDR104 |
+- SDHCI_QUIRK2_NO_SDR25,
+-
++ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
+ },
+ .init = dwcmshc_rk35xx_init,
+ .postinit = dwcmshc_rk35xx_postinit,
+@@ -1362,10 +1332,6 @@ dsbl_cqe_caps:
+
+ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
+ {
+- .compatible = "raspberrypi,rp1-dwcmshc",
+- .data = &sdhci_dwcmshc_rp1_pdata,
+- },
+- {
+ .compatible = "rockchip,rk3588-dwcmshc",
+ .data = &sdhci_dwcmshc_rk35xx_pdata,
+ },
+@@ -1455,37 +1421,15 @@ static int dwcmshc_probe(struct platform
+ return err;
+
+ priv->bus_clk = devm_clk_get(dev, "bus");
+- if (!IS_ERR(priv->bus_clk)) {
+- err = clk_prepare_enable(priv->bus_clk);
+- if (err)
+- goto err_clk;
+- }
+-
+- pltfm_host->timeout_clk = devm_clk_get(dev, "timeout");
+- if (!IS_ERR(pltfm_host->timeout_clk))
+- err = clk_prepare_enable(pltfm_host->timeout_clk);
+- if (err)
+- goto err_bus_clk;
+-
+- priv->sdio_clk = devm_clk_get_optional(&pdev->dev, "sdio");
++ if (!IS_ERR(priv->bus_clk))
++ clk_prepare_enable(priv->bus_clk);
+ }
+
+- pltfm_host->timeout_clk = devm_clk_get(&pdev->dev, "timeout");
+- if (IS_ERR(pltfm_host->timeout_clk)) {
+- err = PTR_ERR(pltfm_host->timeout_clk);
+- dev_err(&pdev->dev, "failed to get timeout clk: %d\n", err);
+- goto err_bus_clk;
+- }
+- err = clk_prepare_enable(pltfm_host->timeout_clk);
+- if (err)
+- goto err_bus_clk;
+-
+ err = mmc_of_parse(host->mmc);
+ if (err)
+ goto err_timeout_clk;
+
+ sdhci_get_of_property(pdev);
+- sdhci_enable_v4_mode(host);
+
+ priv->vendor_specific_area1 =
+ sdhci_readl(host, DWCMSHC_P_VENDOR_AREA1) & DWCMSHC_AREA1_MASK;
--- /dev/null
+From 2cb0c4b8f629710c05ed47fcce8c3f6841240c47 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 17:12:25 +0100
+Subject: [PATCH] Revert "nvme/host/pci: Revert to upstream"
+
+This reverts commit 7838cff31a7091c0a9c6b35ca4a961cace361c8b.
+---
+ drivers/nvme/host/pci.c | 98 +++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 98 insertions(+)
+
+--- a/drivers/nvme/host/pci.c
++++ b/drivers/nvme/host/pci.c
+@@ -183,9 +183,11 @@ struct nvme_dev {
+ /* host memory buffer support: */
+ u64 host_mem_size;
+ u32 nr_host_mem_descs;
++ u32 nr_sgl_ents;
+ u32 host_mem_descs_size;
+ dma_addr_t host_mem_descs_dma;
+ struct nvme_host_mem_buf_desc *host_mem_descs;
++ struct scatterlist *host_mem_sgl;
+ void **host_mem_desc_bufs;
+ unsigned int nr_allocated_queues;
+ unsigned int nr_write_queues;
+@@ -2306,6 +2308,13 @@ static int nvme_set_host_mem(struct nvme
+ return ret;
+ }
+
++#if IS_ENABLED(CONFIG_ARM64)
++static void nvme_free_host_mem_multi(struct nvme_dev *dev)
++{
++ dma_unmap_sg(dev->dev, dev->host_mem_sgl, dev->nr_host_mem_descs, DMA_FROM_DEVICE);
++ sgl_free(dev->host_mem_sgl);
++}
++#else
+ static void nvme_free_host_mem_multi(struct nvme_dev *dev)
+ {
+ int i;
+@@ -2322,6 +2331,7 @@ static void nvme_free_host_mem_multi(str
+ kfree(dev->host_mem_desc_bufs);
+ dev->host_mem_desc_bufs = NULL;
+ }
++#endif
+
+ static void nvme_free_host_mem(struct nvme_dev *dev)
+ {
+@@ -2366,6 +2376,93 @@ static int nvme_alloc_host_mem_single(st
+ return 0;
+ }
+
++#if IS_ENABLED(CONFIG_ARM64)
++static int nvme_alloc_host_mem_multi(struct nvme_dev *dev, u64 preferred,
++ u32 chunk_size)
++{
++ struct nvme_host_mem_buf_desc *descs;
++ u32 max_entries, len, descs_size;
++ dma_addr_t descs_dma;
++ struct scatterlist *slist;
++ struct page *page;
++ int i = 0, mapped_nents;
++ u64 size, tmp;
++
++ tmp = (preferred + chunk_size - 1);
++ do_div(tmp, chunk_size);
++ max_entries = tmp;
++
++ if (dev->ctrl.hmmaxd && dev->ctrl.hmmaxd < max_entries)
++ max_entries = dev->ctrl.hmmaxd;
++
++ descs_size = max_entries * sizeof(*descs);
++ /*
++ * Allocate the descriptor table from coherent memory -
++ * usually occupies less than/up to a single page.
++ */
++ descs = dma_alloc_coherent(dev->dev, descs_size, &descs_dma,
++ GFP_KERNEL);
++ if (!descs)
++ goto out;
++
++ slist = kcalloc(max_entries, sizeof(struct scatterlist), GFP_KERNEL);
++ if (!slist)
++ goto out_free_descs;
++
++ sg_init_table(slist, max_entries);
++
++ dev_dbg(dev->dev, "Allocating HMB pref = %llu max_entries = %u\n",
++ preferred, max_entries);
++
++ for (size = 0; size < preferred && i < max_entries; size += len) {
++ int order;
++
++ len = min_t(u64, chunk_size, preferred - size);
++ order = get_order(len);
++ page = alloc_pages(GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN, order);
++ if (!page)
++ break;
++ sg_set_page(&slist[i], page, len, 0);
++ i++;
++ }
++ if (size < preferred)
++ goto out_free_sgl;
++
++ mapped_nents = dma_map_sg(dev->dev, slist, i, DMA_FROM_DEVICE);
++ if (mapped_nents <= 0)
++ goto out_free_pages;
++
++ /* Flush in case the CPU has cached any parts of the DMA buffers */
++ dma_sync_sg_for_device(dev->dev, slist, i, DMA_FROM_DEVICE);
++
++ i = dev->nr_host_mem_descs = mapped_nents;
++
++ while (--i >= 0) {
++ descs[i].addr = cpu_to_le64(sg_dma_address(&slist[i]));
++ WARN_ON_ONCE(sg_dma_len(&slist[i]) & (NVME_CTRL_PAGE_SIZE - 1));
++ descs[i].size = cpu_to_le32(sg_dma_len(&slist[i]) / NVME_CTRL_PAGE_SIZE);
++ }
++
++ dev->host_mem_size = size;
++ dev->host_mem_descs = descs;
++ dev->host_mem_descs_dma = descs_dma;
++ dev->host_mem_descs_size = descs_size;
++ dev->host_mem_sgl = slist;
++ return 0;
++
++out_free_pages:
++ /* Don't use mapped_nents here as it could be incomplete */
++ while (--i >= 0)
++ __free_pages(sg_page(&slist[i]), get_order(slist[i].length));
++out_free_sgl:
++ kfree(slist);
++out_free_descs:
++ dma_free_coherent(dev->dev, descs_size, descs, descs_dma);
++out:
++ dev->host_mem_descs = NULL;
++ return -ENOMEM;
++}
++#else
+ static int nvme_alloc_host_mem_multi(struct nvme_dev *dev, u64 preferred,
+ u32 chunk_size)
+ {
+@@ -2426,6 +2523,7 @@ out:
+ dev->host_mem_descs = NULL;
+ return -ENOMEM;
+ }
++#endif
+
+ static int nvme_alloc_host_mem(struct nvme_dev *dev, u64 min, u64 preferred)
+ {
--- /dev/null
+From 53a153146333c85f5f1ffb67578c293198fdf771 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 17:13:42 +0100
+Subject: [PATCH] Revert "mmc/host/sdhci-of-dwcmshc: Revert to upstream"
+
+This reverts commit 861299359645d8e26a526db8de9ce3dcd904f5a8.
+---
+ drivers/mmc/host/sdhci-of-dwcmshc.c | 58 ++++++++++++++++++++++++++---
+ 1 file changed, 52 insertions(+), 6 deletions(-)
+
+--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
++++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
+@@ -221,6 +221,7 @@ struct rk35xx_priv {
+
+ struct dwcmshc_priv {
+ struct clk *bus_clk;
++ struct clk *sdio_clk;
+ int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */
+ int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */
+
+@@ -302,6 +303,17 @@ static void dwcmshc_reset(struct sdhci_h
+ sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS);
+ }
+
++static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock)
++{
++ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
++ struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
++
++ if (priv->sdio_clk)
++ clk_set_rate(priv->sdio_clk, clock);
++
++ sdhci_set_clock(host, clock);
++}
++
+ static unsigned int dwcmshc_get_max_clock(struct sdhci_host *host)
+ {
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+@@ -1121,10 +1133,11 @@ static int sg2042_init(struct device *de
+ }
+
+ static const struct sdhci_ops sdhci_dwcmshc_ops = {
+- .set_clock = sdhci_set_clock,
++ .set_clock = dwcmshc_set_clock,
+ .set_bus_width = sdhci_set_bus_width,
+ .set_uhs_signaling = dwcmshc_set_uhs_signaling,
+ .get_max_clock = dwcmshc_get_max_clock,
++ .get_timeout_clock = sdhci_pltfm_clk_get_timeout_clock,
+ .reset = dwcmshc_reset,
+ .adma_write_desc = dwcmshc_adma_write_desc,
+ .irq = dwcmshc_cqe_irq_handler,
+@@ -1197,8 +1210,10 @@ static const struct sdhci_ops sdhci_dwcm
+ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_ops,
+- .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+- .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
++ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
++ SDHCI_QUIRK_BROKEN_CARD_DETECTION,
++ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
++ SDHCI_QUIRK2_BROKEN_HS200,
+ },
+ };
+
+@@ -1213,13 +1228,28 @@ static const struct dwcmshc_pltfm_data s
+ };
+ #endif
+
++static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rp1_pdata = {
++ .pdata = {
++ .ops = &sdhci_dwcmshc_ops,
++ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
++ SDHCI_QUIRK_BROKEN_CARD_DETECTION,
++ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
++ SDHCI_QUIRK2_BROKEN_HS200 |
++ SDHCI_QUIRK2_SPURIOUS_INT_RESP,
++ }
++};
++
+ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_rk35xx_ops,
+ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+- SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
++ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN |
++ SDHCI_QUIRK2_NO_SDR50 |
++ SDHCI_QUIRK2_NO_SDR104 |
++ SDHCI_QUIRK2_NO_SDR25,
++
+ },
+ .init = dwcmshc_rk35xx_init,
+ .postinit = dwcmshc_rk35xx_postinit,
+@@ -1332,6 +1362,10 @@ dsbl_cqe_caps:
+
+ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
+ {
++ .compatible = "raspberrypi,rp1-dwcmshc",
++ .data = &sdhci_dwcmshc_rp1_pdata,
++ },
++ {
+ .compatible = "rockchip,rk3588-dwcmshc",
+ .data = &sdhci_dwcmshc_rk35xx_pdata,
+ },
+@@ -1421,8 +1455,20 @@ static int dwcmshc_probe(struct platform
+ return err;
+
+ priv->bus_clk = devm_clk_get(dev, "bus");
+- if (!IS_ERR(priv->bus_clk))
+- clk_prepare_enable(priv->bus_clk);
++ if (!IS_ERR(priv->bus_clk)) {
++ err = clk_prepare_enable(priv->bus_clk);
++ if (err)
++ goto err_clk;
++ }
++
++ pltfm_host->timeout_clk = devm_clk_get(dev, "timeout");
++ if (!IS_ERR(pltfm_host->timeout_clk)) {
++ err = clk_prepare_enable(pltfm_host->timeout_clk);
++ if (err)
++ goto err_bus_clk;
++ }
++
++ priv->sdio_clk = devm_clk_get_optional(&pdev->dev, "sdio");
+ }
+
+ err = mmc_of_parse(host->mmc);
+@@ -1430,6 +1476,7 @@ static int dwcmshc_probe(struct platform
+ goto err_timeout_clk;
+
+ sdhci_get_of_property(pdev);
++ sdhci_enable_v4_mode(host);
+
+ priv->vendor_specific_area1 =
+ sdhci_readl(host, DWCMSHC_P_VENDOR_AREA1) & DWCMSHC_AREA1_MASK;
--- /dev/null
+From 58792fcece9eeaaedcb25a9816ddbbfad4f386b3 Mon Sep 17 00:00:00 2001
+From: Daniel Matuschek <info@hifiberry.com>
+Date: Wed, 29 Jul 2026 10:46:56 +0200
+Subject: [PATCH] ASoC: hifiberry-studio: add AES CS Mode control (channel
+ status format)
+
+Add an enum control on AES cards to select the channel status format
+transmitted by the DIT, backed by card register 0x90
+(DIGI_CS_FORMAT in the card firmware register map):
+
+ Consumer - IEC 60958-3 channel status (no CRCC defined)
+ Professional - AES3, byte 5 = 0 (spec-literal; the CRCC in byte
+ 23 is invalid since the DIX9211 can only program
+ channel status bytes 0-5 and transmits 6-23 as 0)
+ Professional CRC - AES3, byte 5 carries the CRC fixup value that
+ makes the CRCC over bytes 0-22 come out 0x00,
+ matching the zero byte 23 the chip transmits
+ (firmware default)
+
+Verified on a Studio Digi I/O: channel status decode and CRC validity
+checked on an Audio Precision analyzer in all three modes.
+
+Signed-off-by: Daniel Matuschek <info@hifiberry.com>
+---
+ sound/soc/bcm/hifiberry_studio.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/sound/soc/bcm/hifiberry_studio.c
++++ b/sound/soc/bcm/hifiberry_studio.c
+@@ -106,6 +106,7 @@
+ #define GAIN_CH6 0x87
+ #define GAIN_CH7 0x88
+ #define MUTE_INPUTS 0x89
++#define DIGI_CS_FORMAT 0x90 /* AES channel status format */
+ #define CLOCK_CONSUMER_MODE 0x00
+ #define CLOCK_PROVIDER_MODE 0x01
+
+@@ -289,6 +290,7 @@ static bool hb_studio_readable_reg(struc
+ case UUID:
+ case DAC_STATE:
+ case CARD_BUSY:
++ case DIGI_CS_FORMAT:
+ case CARD_RESET:
+ case CARD_CLOCK_MODE:
+ case CARD_CLK_ACT:
+@@ -341,6 +343,8 @@ static const char * const adc_att_texts[
+ "Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB",
+ };
+ static const char * const dix_clk_texts[] = {"TX", "RX"};
++static const char * const cs_mode_texts[] = {"Consumer", "Professional",
++ "Professional CRC"};
+
+ struct hb_studio_vol_control_single {
+ unsigned int reg;
+@@ -617,6 +621,10 @@ static const struct hb_studio_enum_contr
+ { CARD_CLK_OVRWR, 0, 0x01, dix_clk_texts, ARRAY_SIZE(dix_clk_texts) },
+ };
+
++static const struct hb_studio_enum_control hb_studio_cs_mode_enum_ctls[] = {
++ { DIGI_CS_FORMAT, 0, 0x03, cs_mode_texts, ARRAY_SIZE(cs_mode_texts) },
++};
++
+ static const struct snd_kcontrol_new hb_studio_gen_controls_single[] = {
+ ENUM_CTL_SINGLE("DAC Filter", hb_studio_play_enum_ctls[1]),
+ ENUM_CTL_SINGLE("Output Mute", hb_studio_play_enum_ctls[2]),
+@@ -641,6 +649,7 @@ static const struct snd_kcontrol_new dix
+ ENUM_CTL_SINGLE("Input Mute", hb_studio_rec_enum_ctls[1]),
+ ENUM_CTL_SINGLE_RO_GET("Current Sample Rate", hb_studio_samplerate_ctl,
+ hb_studio_samplerate_get),
++ ENUM_CTL_SINGLE("AES CS Mode", hb_studio_cs_mode_enum_ctls[0]),
+ };
+
+ static int snd_rpi_hifiberry_studio_hw_params(
--- /dev/null
+From 1f128d5a97120f7fa011250ec30227da020c89da Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Tue, 28 Jul 2026 17:06:09 +0100
+Subject: [PATCH] pinctrl: rp1: Make IRQ usage set a pin to an input
+
+This is a less invasive solution to the problem that requesting that
+a GPIO be used as an interrupt from Device Tree (not going via
+gpio descriptors) does not automatically make it an input. This can lead
+to interrupt storms.
+
+See: https://github.com/raspberrypi/linux/issues/7520
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+Co-authored-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/pinctrl/pinctrl-rp1.c | 30 ++++++++++++++++++++++++++----
+ 1 file changed, 26 insertions(+), 4 deletions(-)
+
+--- a/drivers/pinctrl/pinctrl-rp1.c
++++ b/drivers/pinctrl/pinctrl-rp1.c
+@@ -742,13 +742,9 @@ static int rp1_gpio_set(struct gpio_chip
+ static int rp1_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+ {
+ struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+- u32 fsel;
+
+ if (!pin)
+ return -EINVAL;
+- fsel = rp1_get_fsel(pin);
+- if (fsel != RP1_FSEL_GPIO)
+- return -EINVAL;
+ return (rp1_get_dir(pin) == RP1_DIR_OUTPUT) ?
+ GPIO_LINE_DIRECTION_OUT :
+ GPIO_LINE_DIRECTION_IN;
+@@ -971,6 +967,23 @@ static int rp1_gpio_irq_set_affinity(str
+ return -EINVAL;
+ }
+
++static int rp1_gpio_irq_reqres(struct irq_data *d)
++{
++ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
++ int ret;
++
++ ret = gpiochip_irq_reqres(d);
++ if (!ret)
++ ret = rp1_gpio_direction_input(gc, d->hwirq);
++
++ return ret;
++}
++
++static void rp1_gpio_irq_relres(struct irq_data *d)
++{
++ return gpiochip_irq_relres(d);
++}
++
+ static struct irq_chip rp1_gpio_irq_chip = {
+ .name = MODULE_NAME,
+ .irq_enable = rp1_gpio_irq_enable,
+@@ -980,6 +993,8 @@ static struct irq_chip rp1_gpio_irq_chip
+ .irq_mask = rp1_gpio_irq_disable,
+ .irq_unmask = rp1_gpio_irq_enable,
+ .irq_set_affinity = rp1_gpio_irq_set_affinity,
++ .irq_request_resources = rp1_gpio_irq_reqres,
++ .irq_release_resources = rp1_gpio_irq_relres,
+ .flags = IRQCHIP_IMMUTABLE,
+ };
+
+@@ -1333,6 +1348,12 @@ static int rp1_pmx_gpio_set_direction(st
+ return 0;
+ }
+
++static bool rp1_pmx_function_is_gpio(struct pinctrl_dev *pctldev,
++ unsigned int selector)
++{
++ return selector == func_gpio;
++}
++
+ static const struct pinmux_ops rp1_pmx_ops = {
+ .free = rp1_pmx_free,
+ .get_functions_count = rp1_pmx_get_functions_count,
+@@ -1341,6 +1362,7 @@ static const struct pinmux_ops rp1_pmx_o
+ .set_mux = rp1_pmx_set,
+ .gpio_disable_free = rp1_pmx_gpio_disable_free,
+ .gpio_set_direction = rp1_pmx_gpio_set_direction,
++ .function_is_gpio = rp1_pmx_function_is_gpio,
+ };
+
+ static void rp1_pull_config_set(struct rp1_pin_info *pin, unsigned int arg)
--- /dev/null
+From 76dafcc81a158892910b8ca14fa7148e0034a5a3 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Sat, 25 Jul 2026 20:55:15 +0100
+Subject: [PATCH] drm/edid: Accept 3D_Detail_X of 0000 for side-by-side (half)
+ modes
+
+When parsing the per-VIC 3D_Structure_X entries of the HDMI VSDB, a
+side-by-side (half) entry is only accepted if the accompanying
+3D_Detail_X field is 0001, "horizontal sub-sampling". HDMI 1.4b
+Table H-8 also defines 0000, "all of the horizontal sub-sampling and
+Quincunx matrix methods", which is a superset of 0001. The remaining
+defined values, 0110 to 1001, each select one specific Quincunx matrix
+variant, and everything else is reserved.
+
+DRM never populates hdmi_vendor_infoframe.s3d_ext_data, so the
+3D_Ext_Data it transmits for a side-by-side (half) mode is 0000,
+"horizontal sub-sampling, odd left and odd right picture". A sink
+advertising 3D_Detail_X of 0000 supports that, so the mode should be
+offered. Instead the entry is silently discarded and such a sink ends
+up with no side-by-side modes at all.
+
+Seen on a Panasonic TX-P50VT30B, which reports 3D_Detail_X = 0000 for all
+of its side-by-side entries:
+
+ 3D VIC indices with specific capabilities:
+ VIC 16: 1920x1080 60.000000 Hz 16:9 (side-by-side, any subsampling)
+ ...
+
+Fixes: 0e5083aa9d47 ("drm/edid: parse the list of additional 3D modes")
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/drm_edid.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/drm_edid.c
++++ b/drivers/gpu/drm/drm_edid.c
+@@ -4920,8 +4920,8 @@ do_hdmi_vsdb_modes(struct drm_connector
+ newflag = DRM_MODE_FLAG_3D_TOP_AND_BOTTOM;
+ break;
+ case 8:
+- /* 3D_Detail_X */
+- if ((db[9 + offset + i] >> 4) == 1)
++ /* 3D_Detail_X: 0 and 1 both include horizontal sub-sampling */
++ if ((db[9 + offset + i] >> 4) <= 1)
+ newflag = DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF;
+ break;
+ }
--- /dev/null
+From 9732b3393d1fc0990a6a5b5417cdba9a5dd6c5d6 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 14:31:17 +0100
+Subject: [PATCH] drm/display/hdmi: Account for frame packing in the TMDS
+ character rate
+
+An HDMI frame packing 3D mode transmits the left and right eye views,
+separated by an active space gap, within a single frame period. Per
+HDMI 1.4b section 8.2.3.2 the resulting raster has twice the vertical
+total of the base 2D mode, so the pixel clock - and with it the TMDS
+character rate - is doubled.
+
+drm_hdmi_compute_mode_clock() derives the rate from mode->clock, which
+for a stereo mode still describes the base 2D timing; the doubling only
+appears in the crtc_* timings, and only once a driver asks for it with
+CRTC_STEREO_DOUBLE. The computed rate was therefore half the real one,
+so the sink's max_tmds_clock was checked against the wrong value and
+drivers programmed half the pixel clock the mode needs.
+
+Only a driver that both advertises stereo modes and uses the HDMI
+connector helpers can see a change here, and vc4 is currently the only
+one. i915 and nouveau set connector->stereo_allowed but compute their
+TMDS rates themselves. The remaining callers of
+drm_hdmi_compute_mode_clock() - ite-it6263, dw_hdmi_qp-rockchip,
+inno_hdmi and sun4i_hdmi - leave stereo_allowed clear, so
+drm_mode_validate_flag() prunes 3D modes from their connectors and none
+ever reaches this function. Should userspace hand such a driver a
+3D-flagged mode directly, it is now rejected as exceeding the sink's
+TMDS limit rather than being driven at half rate.
+
+Non-stereo modes, and the 3D layouts that fit within the 2D timing
+(side-by-side, top-and-bottom, and friends), are unaffected.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/display/drm_hdmi_helper.c | 8 ++++++++
+ drivers/gpu/drm/tests/drm_connector_test.c | 22 ++++++++++++++++++++++
+ 2 files changed, 30 insertions(+)
+
+--- a/drivers/gpu/drm/display/drm_hdmi_helper.c
++++ b/drivers/gpu/drm/display/drm_hdmi_helper.c
+@@ -254,6 +254,14 @@ drm_hdmi_compute_mode_clock(const struct
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ clock = clock * 2;
+
++ /*
++ * HDMI 1.4b Spec, Section 8.2.3.2 - Frame Packing Structure
++ * transmits both eyes within a single frame, at twice the vertical
++ * total, and thus twice the pixel clock, of the base 2D mode.
++ */
++ if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
++ clock = clock * 2;
++
+ return DIV_ROUND_CLOSEST_ULL(clock * bpc, 8);
+ }
+ EXPORT_SYMBOL(drm_hdmi_compute_mode_clock);
+--- a/drivers/gpu/drm/tests/drm_connector_test.c
++++ b/drivers/gpu/drm/tests/drm_connector_test.c
+@@ -1626,6 +1626,27 @@ static void drm_test_drm_hdmi_compute_mo
+ }
+
+ /*
++ * Test that for a frame packing stereo mode, the TMDS character rate is
++ * indeed double the mode pixel clock.
++ */
++static void drm_test_drm_hdmi_compute_mode_clock_stereo_frame_packing(struct kunit *test)
++{
++ struct drm_connector_init_priv *priv = test->priv;
++ struct drm_display_mode *mode;
++ unsigned long long rate;
++ struct drm_device *drm = &priv->drm;
++
++ mode = drm_kunit_display_mode_from_cea_vic(test, drm, 32);
++ KUNIT_ASSERT_NOT_NULL(test, mode);
++
++ mode->flags |= DRM_MODE_FLAG_3D_FRAME_PACKING;
++
++ rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB);
++ KUNIT_ASSERT_GT(test, rate, 0);
++ KUNIT_EXPECT_EQ(test, (mode->clock * 1000ULL) * 2, rate);
++}
++
++/*
+ * Test that the TMDS character rate computation for the VIC modes
+ * explicitly listed in the spec as supporting YUV420 succeed and return
+ * half the mode pixel clock.
+@@ -1784,6 +1805,7 @@ static struct kunit_case drm_hdmi_comput
+ KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc),
+ KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc_vic_1),
+ KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_rgb_double),
++ KUNIT_CASE(drm_test_drm_hdmi_compute_mode_clock_stereo_frame_packing),
+ KUNIT_CASE_PARAM(drm_test_connector_hdmi_compute_mode_clock_yuv420_valid,
+ drm_hdmi_compute_mode_clock_yuv420_valid_gen_params),
+ KUNIT_CASE(drm_test_connector_hdmi_compute_mode_clock_yuv420_10_bpc),
--- /dev/null
+From ebd2b267e575b26d135b8b3f0d7ee37e215d625e Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 14:31:35 +0100
+Subject: [PATCH] drm/vc4: Derive scanout rate and frame size from the CRTC
+ timings
+
+The vertical timings written to the pixelvalve already come from the
+crtc_* fields of the adjusted mode, but three places that describe the
+same raster still read the mode->* fields directly:
+
+ - the HVS bandwidth estimate in vc4_crtc_atomic_check()
+ - the audio CTS divisor in vc4_hdmi_set_n_cts()
+ - the HVS display FIFO width and height in {vc4,vc6}_hvs_init_channel()
+
+The two sets of fields differ only for stereo modes that pack more than
+one eye into a frame, which vc4 does not yet handle, so switch them over
+to mode->crtc_clock and drm_mode_get_hv_timing() ahead of adding that
+support. drm_mode_get_hv_timing() is used rather than crtc_vdisplay
+because the HVS is fed a whole frame even for interlaced modes, whose
+crtc_vdisplay describes a single field.
+
+No functional change.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/vc4/vc4_crtc.c | 7 ++++---
+ drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +-
+ drivers/gpu/drm/vc4/vc4_hvs.c | 20 ++++++++++++++------
+ 3 files changed, 19 insertions(+), 10 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_crtc.c
++++ b/drivers/gpu/drm/vc4/vc4_crtc.c
+@@ -768,10 +768,11 @@ int vc4_crtc_atomic_check(struct drm_crt
+ struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
+
+ if (vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0) {
+- vc4_state->hvs_load = max(mode->clock * mode->hdisplay / mode->htotal + 8000,
+- mode->clock * 9 / 10) * 1000;
++ vc4_state->hvs_load =
++ max(mode->crtc_clock * mode->hdisplay / mode->htotal + 8000,
++ mode->crtc_clock * 9 / 10) * 1000;
+ } else {
+- vc4_state->hvs_load = mode->clock * 1000;
++ vc4_state->hvs_load = mode->crtc_clock * 1000;
+ }
+ }
+
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
+@@ -1853,7 +1853,7 @@ static void vc4_hdmi_set_n_cts(struct vc
+ lockdep_assert_held(&vc4_hdmi->hw_lock);
+
+ n = 128 * samplerate / 1000;
+- tmp = (u64)(mode->clock * 1000) * n;
++ tmp = (u64)(mode->crtc_clock * 1000) * n;
+ do_div(tmp, 128 * samplerate);
+ cts = tmp;
+
+--- a/drivers/gpu/drm/vc4/vc4_hvs.c
++++ b/drivers/gpu/drm/vc4/vc4_hvs.c
+@@ -945,6 +945,7 @@ static int vc4_hvs_init_channel(struct v
+ struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
+ unsigned int chan = vc4_crtc_state->assigned_channel;
+ bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
++ int hdisplay, vdisplay;
+ u32 dispbkgndx;
+ u32 dispctrl;
+ int idx;
+@@ -954,6 +955,9 @@ static int vc4_hvs_init_channel(struct v
+ if (!drm_dev_enter(drm, &idx))
+ return -ENODEV;
+
++ /* Doubled for frame packing stereo modes */
++ drm_mode_get_hv_timing(mode, &hdisplay, &vdisplay);
++
+ HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
+ HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
+ HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
+@@ -967,16 +971,16 @@ static int vc4_hvs_init_channel(struct v
+ dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan));
+
+ if (vc4->gen == VC4_GEN_4) {
+- dispctrl |= VC4_SET_FIELD(mode->hdisplay,
++ dispctrl |= VC4_SET_FIELD(hdisplay,
+ SCALER_DISPCTRLX_WIDTH) |
+- VC4_SET_FIELD(mode->vdisplay,
++ VC4_SET_FIELD(vdisplay,
+ SCALER_DISPCTRLX_HEIGHT) |
+ (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0);
+ dispbkgndx |= SCALER_DISPBKGND_AUTOHS;
+ } else {
+- dispctrl |= VC4_SET_FIELD(mode->hdisplay,
++ dispctrl |= VC4_SET_FIELD(hdisplay,
+ SCALER5_DISPCTRLX_WIDTH) |
+- VC4_SET_FIELD(mode->vdisplay,
++ VC4_SET_FIELD(vdisplay,
+ SCALER5_DISPCTRLX_HEIGHT) |
+ (oneshot ? SCALER5_DISPCTRLX_ONESHOT : 0);
+ dispbkgndx &= ~SCALER5_DISPBKGND_BCK2BCK;
+@@ -1009,6 +1013,7 @@ static int vc6_hvs_init_channel(struct v
+ struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
+ unsigned int chan = vc4_crtc_state->assigned_channel;
+ bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
++ int hdisplay, vdisplay;
+ u32 disp_ctrl1;
+ int idx;
+
+@@ -1017,6 +1022,9 @@ static int vc6_hvs_init_channel(struct v
+ if (!drm_dev_enter(drm, &idx))
+ return -ENODEV;
+
++ /* Doubled for frame packing stereo modes */
++ drm_mode_get_hv_timing(mode, &hdisplay, &vdisplay);
++
+ HVS_WRITE(SCALER6_DISPX_CTRL0(chan), SCALER6_DISPX_CTRL0_RESET);
+
+ disp_ctrl1 = HVS_READ(SCALER6_DISPX_CTRL1(chan));
+@@ -1026,10 +1034,10 @@ static int vc6_hvs_init_channel(struct v
+
+ HVS_WRITE(SCALER6_DISPX_CTRL0(chan),
+ SCALER6_DISPX_CTRL0_ENB |
+- VC4_SET_FIELD(mode->hdisplay - 1,
++ VC4_SET_FIELD(hdisplay - 1,
+ SCALER6_DISPX_CTRL0_FWIDTH) |
+ (oneshot ? SCALER6_DISPX_CTRL0_ONESHOT : 0) |
+- VC4_SET_FIELD(mode->vdisplay - 1,
++ VC4_SET_FIELD(vdisplay - 1,
+ SCALER6_DISPX_CTRL0_LINES));
+
+ drm_dev_exit(idx);
--- /dev/null
+From 741426f4bc591beb37116a6d97d422a2d4f988b6 Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 27 Jul 2026 14:32:14 +0100
+Subject: [PATCH] drm/vc4: Support frame packed stereo modes
+
+vc4 has always set connector->stereo_allowed, so a client with
+DRM_CLIENT_CAP_STEREO_3D is offered the frame packing modes advertised
+by the sink, but nothing ever expanded the timings for them. The
+pixelvalve was programmed with the base 2D raster while the sink
+expected two eyes and an active space gap, so the picture was wrong.
+
+Ask for CRTC_STEREO_DOUBLE when recomputing the adjusted mode's crtc_*
+fields. drm_mode_set_crtcinfo() then doubles crtc_vdisplay,
+crtc_vsync_start, crtc_vsync_end, crtc_vtotal and crtc_clock, which is
+everything the pixelvalve, the HVS and the audio CTS calculation need.
+The matching TMDS character rate comes from the HDMI connector helpers
+and the 3D_Structure vendor infoframe from
+drm_hdmi_vendor_infoframe_from_display_mode(), so nothing else has to
+change.
+
+CRTC_INTERLACE_HALVE_V has to be passed as well: drm_mode_set_crtcinfo()
+rebuilds every crtc_* field from scratch, and without it the halved
+field timings the core derived earlier for interlaced modes would be
+lost.
+
+Non-stereo modes are unaffected, as the CRTC_STEREO_DOUBLE adjustment
+applies only to modes carrying DRM_MODE_FLAG_3D_FRAME_PACKING.
+
+The WiFi interference workaround just above deliberately keeps keying
+off mode->clock, i.e. the 2D pixel clock. Frame packing modes landing in
+the affected range are not known to exist, and reworking that test would
+change behaviour for existing 2D modes.
+
+Tested on a Raspberry Pi 5.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/vc4/vc4_hdmi.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
++++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
+@@ -1730,6 +1730,9 @@ static int vc4_hdmi_encoder_atomic_check
+ tmds_char_rate = mode->clock * 1000;
+ }
+
++ /* Rebuilds every crtc_* field, so CRTC_INTERLACE_HALVE_V is needed too */
++ drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE);
++
+ return 0;
+ }
+
--- /dev/null
+From 9f851ff5267aaf19d0979c6b46dece5aaa27cb2d Mon Sep 17 00:00:00 2001
+From: Waveshare_Team <support@waveshare.com>
+Date: Mon, 27 Jul 2026 21:25:50 +0800
+Subject: [PATCH] drm/panel: waveshare: Add 6.25-DSI-TOUCH-A panel support
+
+Add the initialization sequence and display mode for the Waveshare
+6.25-DSI-TOUCH-A panel.
+
+The 720x1560 panel runs at 60 Hz over two MIPI DSI lanes.
+
+Signed-off-by: Waveshare_Team <support@waveshare.com>
+---
+ .../gpu/drm/panel/panel-waveshare-dsi-v2.c | 92 +++++++++++++++++++
+ 1 file changed, 92 insertions(+)
+
+--- a/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c
++++ b/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c
+@@ -1393,6 +1393,73 @@ static const struct panel_init_cmd ws_pa
+ {},
+ };
+
++static const struct panel_init_cmd ws_panel_6_25_a_init[] = {
++ _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa2),
++ _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa3),
++ _INIT_DCS_CMD(0x60, 0x71, 0x23, 0xa4),
++ _INIT_DCS_CMD(0xB0, 0x22, 0x61, 0x11, 0x6B, 0x25, 0x39, 0x39),
++ _INIT_DCS_CMD(0xB7, 0x00, 0x00, 0x64, 0x64),
++ _INIT_DCS_CMD(0xBF, 0x95, 0x95),
++ _INIT_DCS_CMD(0xA4, 0x31),
++ _INIT_DCS_CMD(0xC8, 0x00, 0x00, 0x10, 0x1F, 0x3A, 0x00, 0x66, 0x03,
++ 0xA8, 0x04, 0x11, 0x02, 0x06, 0x73, 0x01, 0x21, 0xC1,
++ 0x02, 0x01, 0x02, 0x22, 0x3E, 0x00, 0x84, 0x09, 0x32,
++ 0xE9, 0x0B, 0x3B, 0x0D, 0xF3, 0x7E, 0x0D, 0xB2, 0xE6,
++ 0x03, 0xFC),
++ _INIT_DCS_CMD(0xC9, 0x00, 0x00, 0x10, 0x1F, 0x3A, 0x00, 0x66, 0x03,
++ 0xA8, 0x04, 0x11, 0x02, 0x06, 0x73, 0x01, 0x21, 0xC1,
++ 0x02, 0x01, 0x02, 0x22, 0x3E, 0x00, 0x84, 0x09, 0x32,
++ 0xE9, 0x0B, 0x3B, 0x0D, 0xF3, 0x7E, 0x0D, 0xB2, 0xE6,
++ 0x03, 0xFC),
++ _INIT_DCS_CMD(0xD7, 0x10, 0x12, 0x49, 0x3B, 0x0C, 0x0C),
++ _INIT_DCS_CMD(0x90, 0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09),
++ _INIT_DCS_CMD(0xA3, 0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80,
++ 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
++ 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58,
++ 0x00, 0x00, 0x00, 0xFF),
++ _INIT_DCS_CMD(0xA6, 0x50, 0x00, 0x24, 0x55, 0x3A, 0x00, 0x00, 0x01,
++ 0x54, 0x54, 0x91, 0xFF, 0x00, 0x24, 0x55, 0x3B, 0x00,
++ 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x00, 0x24, 0x11,
++ 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x00,
++ 0xAC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0xFF,
++ 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00,
++ 0x00, 0x00),
++ _INIT_DCS_CMD(0xA7, 0x1D, 0x1D, 0x80, 0x64, 0x40, 0x07, 0x16, 0x41,
++ 0x02, 0x48, 0x07, 0x54, 0x54, 0x91, 0xFF, 0x08, 0x80,
++ 0x64, 0x40, 0x25, 0x34, 0x43, 0x04, 0x06, 0x05, 0x54,
++ 0x54, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00,
++ 0x40, 0x00, 0x00, 0x00, 0x54, 0x54, 0x91, 0xFF, 0x08,
++ 0x80, 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
++ 0x54, 0x54, 0x84, 0xFF, 0x08, 0x80, 0x44),
++ _INIT_DCS_CMD(0xAC, 0x1C, 0x02, 0x09, 0x0B, 0x12, 0x10, 0x18, 0x19,
++ 0x1C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x08, 0x0A,
++ 0x13, 0x11, 0x18, 0x19, 0x1C, 0x03, 0x00, 0x00, 0x00,
++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
++ _INIT_DCS_CMD(0xAD, 0xF0, 0x00, 0x46, 0x00, 0x05, 0x50, 0x50, 0xFF,
++ 0xFF, 0xF0, 0x40, 0x06, 0x01, 0x07, 0x42, 0x42, 0xFF,
++ 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF),
++ _INIT_DCS_CMD(0xAE, 0xFF, 0x03, 0x00, 0xFF, 0x03, 0x00, 0x01),
++ _INIT_DCS_CMD(0xB2, 0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E,
++ 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, 0xC0, 0x00),
++ _INIT_DCS_CMD(0xE8, 0x20, 0x6F, 0x04, 0x7C, 0x7C, 0x3E, 0x04, 0xDC,
++ 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E),
++ _INIT_DCS_CMD(0x75, 0x03, 0x04),
++ _INIT_DCS_CMD(0xE7, 0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A,
++ 0xF0, 0xB1, 0x50, 0x00, 0x50, 0xB1, 0x50, 0xB1, 0x50,
++ 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45, 0xC9, 0x6A,
++ 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01,
++ 0x77),
++ _INIT_DCS_CMD(0xEA, 0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C),
++ _INIT_DCS_CMD(0x60, 0x71, 0x23, 0x00),
++ _INIT_DCS_CMD(0x11),
++ _INIT_DELAY_CMD(120),
++ _INIT_DCS_CMD(0x29),
++ _INIT_DELAY_CMD(50),
++ {},
++};
++
+ static const struct panel_init_cmd ws_panel_5_5_a_init[] = {
+ _INIT_DCS_CMD(0xB9, 0xFF, 0x83, 0x94),
+ _INIT_DCS_CMD(0xBA, 0x61, 0x03, 0x68, 0x6B, 0xB2, 0xC0),
+@@ -2032,6 +2099,20 @@ static const struct drm_display_mode ws_
+ .height_mm = 85,
+ };
+
++static const struct drm_display_mode ws_panel_6_25_a_mode = {
++ .clock = 83000,
++ .hdisplay = 720,
++ .hsync_start = 720 + 20,
++ .hsync_end = 720 + 20 + 20,
++ .htotal = 720 + 20 + 20 + 20,
++ .vdisplay = 1560,
++ .vsync_start = 1560 + 200,
++ .vsync_end = 1560 + 200 + 8,
++ .vtotal = 1560 + 200 + 8 + 10,
++ .width_mm = 62,
++ .height_mm = 110,
++};
++
+ static const struct drm_display_mode ws_panel_5_5_a_mode = {
+ .clock = 65000,
+ .hdisplay = 720,
+@@ -2368,6 +2449,15 @@ static const struct ws_panel_desc ws_pan
+ .format = MIPI_DSI_FMT_RGB888,
+ };
+
++static const struct ws_panel_desc ws_panel_6_25_inch_a_desc = {
++ .init = ws_panel_6_25_a_init,
++ .mode = &ws_panel_6_25_a_mode,
++ .mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
++ MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
++ .lanes = 2,
++ .format = MIPI_DSI_FMT_RGB888,
++};
++
+ static const struct ws_panel_desc ws_panel_5_5_inch_a_desc = {
+ .init = ws_panel_5_5_a_init,
+ .mode = &ws_panel_5_5_a_mode,
+@@ -2444,6 +2534,8 @@ static const struct of_device_id ws_pane
+ { .compatible = "waveshare,7.0-dsi-touch-a", &ws_panel_7_inch_a_desc },
+ { .compatible = "waveshare,7.0-dsi-touch-b", &ws_panel_7_inch_a_desc },
+ { .compatible = "waveshare,7.0-dsi-touch-c", &ws_panel_7_inch_c_desc },
++ { .compatible = "waveshare,6.25-dsi-touch-a",
++ &ws_panel_6_25_inch_a_desc },
+ { .compatible = "waveshare,5.5-dsi-touch-a",
+ &ws_panel_5_5_inch_a_desc },
+ { .compatible = "waveshare,5.0-dsi-touch-a", &ws_panel_5_inch_a_desc },
--- /dev/null
+From 6d4adb6bfea64581dff1c670a17333b63b8ba05d Mon Sep 17 00:00:00 2001
+From: Waveshare_Team <support@waveshare.com>
+Date: Thu, 16 Jul 2026 10:01:44 +0800
+Subject: [PATCH] input: touchscreen: Add Sitronix ST7123 support
+
+Add a minimal I2C touchscreen driver for the Sitronix ST7123 controller
+used by the Waveshare 6.25-DSI-TOUCH-A panel.
+
+The driver implements the functionality needed for normal input support:
+reset handling, coordinate reads, multitouch reporting, optional IRQ
+operation, input-core polling fallback, and system suspend/resume.
+
+Firmware update, factory-test, rawdata, monitor-thread, and vendor debug
+interfaces are left out because they are not required for panel
+enablement.
+
+Keep the driver as a single source file under drivers/input/touchscreen/,
+matching the pattern used by other small touchscreen drivers.
+
+Signed-off-by: Waveshare_Team <support@waveshare.com>
+---
+ MAINTAINERS | 6 +
+ drivers/input/touchscreen/Kconfig | 12 +
+ drivers/input/touchscreen/Makefile | 1 +
+ drivers/input/touchscreen/st7123.c | 495 +++++++++++++++++++++++++++++
+ 4 files changed, 514 insertions(+)
+ create mode 100644 drivers/input/touchscreen/st7123.c
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -23715,6 +23715,12 @@ S: Maintained
+ W: http://www.winischhofer.at/linuxsisusbvga.shtml
+ F: drivers/usb/misc/sisusbvga/
+
++SITRONIX ST7123 TOUCHSCREEN DRIVER
++M: Waveshare Team <support@waveshare.com>
++L: linux-input@vger.kernel.org
++S: Maintained
++F: drivers/input/touchscreen/st7123.c
++
+ SL28 CPLD MFD DRIVER
+ M: Michael Walle <mwalle@kernel.org>
+ S: Maintained
+--- a/drivers/input/touchscreen/Kconfig
++++ b/drivers/input/touchscreen/Kconfig
+@@ -1251,6 +1251,18 @@ config TOUCHSCREEN_SIS_I2C
+ To compile this driver as a module, choose M here: the
+ module will be called sis_i2c.
+
++config TOUCHSCREEN_ST7123
++ tristate "Sitronix ST7123 touchscreen controllers"
++ depends on I2C
++ help
++ Say Y here if you want to support the Sitronix ST7123
++ touchscreen controller.
++
++ If unsure, say N.
++
++ To compile this driver as a module, choose M here: the
++ module will be called st7123.
++
+ config TOUCHSCREEN_ST1232
+ tristate "Sitronix ST1232 or ST1633 touchscreen controllers"
+ depends on I2C
+--- a/drivers/input/touchscreen/Makefile
++++ b/drivers/input/touchscreen/Makefile
+@@ -83,6 +83,7 @@ obj-$(CONFIG_TOUCHSCREEN_RM_TS) += rayd
+ obj-$(CONFIG_TOUCHSCREEN_S6SY761) += s6sy761.o
+ obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o
+ obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o
++obj-$(CONFIG_TOUCHSCREEN_ST7123) += st7123.o
+ obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
+ obj-$(CONFIG_TOUCHSCREEN_STMFTS) += stmfts.o
+ obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
+--- /dev/null
++++ b/drivers/input/touchscreen/st7123.c
+@@ -0,0 +1,495 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Sitronix ST7123 touchscreen controller driver
++ *
++ * Copyright (C) 2018 Sitronix Technology Co., Ltd.
++ * Copyright (C) 2026 Waveshare International Limited
++ */
++
++#include <linux/delay.h>
++#include <linux/gpio/consumer.h>
++#include <linux/i2c.h>
++#include <linux/input.h>
++#include <linux/input/mt.h>
++#include <linux/input/touchscreen.h>
++#include <linux/interrupt.h>
++#include <linux/module.h>
++#include <linux/slab.h>
++
++#define ST7123_TS_NAME "st7123"
++
++#define ST7123_STATUS_REG 0x0001
++#define ST7123_DEVICE_CONTROL_REG 0x0002
++#define ST7123_X_RESOLUTION_HIGH 0x0005
++#define ST7123_MAX_NUM_TOUCHES 0x0009
++#define ST7123_TOUCH_INFO 0x0010
++#define ST7123_MISC_INFO 0x00f0
++#define ST7123_CHIP_ID 0x00f4
++
++#define ST7123_CHIP_ID_VALUE 0x83
++#define ST7123_MISC_COORD_CHKSUM BIT(4)
++#define ST7123_MAX_SUPPORTED_TOUCHES 10
++#define ST7123_DEFAULT_MAX_TOUCHES 10
++#define ST7123_DEFAULT_X_RES 720
++#define ST7123_DEFAULT_Y_RES 1560
++#define ST7123_POLL_INTERVAL_MS 17
++#define ST7123_TOUCH_STRIDE 7
++#define ST7123_TOUCH_HEADER_LEN 4
++#define ST7123_CHECKSUM_SEED 0x5a
++
++struct st7123_ts_data {
++ struct i2c_client *client;
++ struct input_dev *input;
++ struct gpio_desc *reset_gpio;
++ struct touchscreen_properties prop;
++ /* Serializes register access from IRQ/polling and PM paths. */
++ struct mutex lock;
++ u8 max_touches;
++ bool has_coord_checksum;
++ bool suspended;
++ u8 coord_buf[ST7123_TOUCH_HEADER_LEN +
++ ST7123_MAX_SUPPORTED_TOUCHES * ST7123_TOUCH_STRIDE + 1];
++};
++
++static int st7123_read(struct st7123_ts_data *ts, u16 reg, void *buf, u16 len)
++{
++ struct i2c_client *client = ts->client;
++ u8 addr[2] = { reg >> 8, reg & 0xff };
++ struct i2c_msg msg[] = {
++ {
++ .addr = client->addr,
++ .len = sizeof(addr),
++ .buf = addr,
++ },
++ {
++ .addr = client->addr,
++ .flags = I2C_M_RD,
++ .len = len,
++ .buf = buf,
++ },
++ };
++ int ret;
++
++ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
++ if (ret != ARRAY_SIZE(msg))
++ return ret < 0 ? ret : -EIO;
++
++ return 0;
++}
++
++static int st7123_write(struct st7123_ts_data *ts, u16 reg, const void *buf,
++ u16 len)
++{
++ struct i2c_client *client = ts->client;
++ u8 *data;
++ int ret;
++
++ data = kmalloc(len + 2, GFP_KERNEL);
++ if (!data)
++ return -ENOMEM;
++
++ data[0] = reg >> 8;
++ data[1] = reg & 0xff;
++ memcpy(&data[2], buf, len);
++
++ ret = i2c_master_send(client, data, len + 2);
++ kfree(data);
++
++ if (ret != len + 2)
++ return ret < 0 ? ret : -EIO;
++
++ return 0;
++}
++
++static u8 st7123_checksum(const u8 *buf, int len)
++{
++ u16 checksum = ST7123_CHECKSUM_SEED;
++ u8 low;
++ int i;
++
++ for (i = 0; i < len; i++) {
++ checksum += buf[i];
++ low = checksum & 0xff;
++ low = (low >> 7) | (low << 1);
++ checksum = (checksum & 0xff00) | low;
++ }
++
++ return checksum & 0xff;
++}
++
++static void st7123_reset(struct st7123_ts_data *ts)
++{
++ if (!ts->reset_gpio)
++ return;
++
++ gpiod_set_value_cansleep(ts->reset_gpio, 1);
++ fsleep(10000);
++ gpiod_set_value_cansleep(ts->reset_gpio, 0);
++ msleep(150);
++}
++
++static int st7123_read_resolution(struct st7123_ts_data *ts, u16 *x, u16 *y)
++{
++ u8 buf[4];
++ int ret;
++
++ ret = st7123_read(ts, ST7123_X_RESOLUTION_HIGH, buf, sizeof(buf));
++ if (ret)
++ return ret;
++
++ *x = ((buf[0] & 0x3f) << 8) | buf[1];
++ *y = ((buf[2] & 0x3f) << 8) | buf[3];
++
++ if (!*x || !*y)
++ return -EINVAL;
++
++ return 0;
++}
++
++static int st7123_read_max_touches(struct st7123_ts_data *ts)
++{
++ u8 max_touches;
++ int ret;
++
++ ret = st7123_read(ts, ST7123_MAX_NUM_TOUCHES, &max_touches,
++ sizeof(max_touches));
++ if (ret)
++ return ret;
++
++ if (!max_touches || max_touches > ST7123_MAX_SUPPORTED_TOUCHES)
++ max_touches = ST7123_DEFAULT_MAX_TOUCHES;
++
++ ts->max_touches = max_touches;
++
++ return 0;
++}
++
++static int st7123_read_misc_info(struct st7123_ts_data *ts)
++{
++ u8 misc;
++ int ret;
++
++ ret = st7123_read(ts, ST7123_MISC_INFO, &misc, sizeof(misc));
++ if (ret)
++ return ret;
++
++ ts->has_coord_checksum = misc & ST7123_MISC_COORD_CHKSUM;
++
++ return 0;
++}
++
++static int st7123_check_id(struct st7123_ts_data *ts)
++{
++ struct device *dev = &ts->client->dev;
++ u8 chip_id;
++ int ret;
++
++ ret = st7123_read(ts, ST7123_CHIP_ID, &chip_id, sizeof(chip_id));
++ if (ret)
++ return ret;
++
++ if (chip_id != ST7123_CHIP_ID_VALUE)
++ dev_warn(dev, "unexpected chip id 0x%02x\n", chip_id);
++
++ return 0;
++}
++
++static int st7123_wait_ready(struct st7123_ts_data *ts)
++{
++ u8 status;
++ int ret;
++ int i;
++
++ for (i = 0; i < 100; i++) {
++ ret = st7123_read(ts, ST7123_STATUS_REG, &status,
++ sizeof(status));
++ if (!ret && ((status & 0x0f) == 0x00 || (status & 0x0f) == 0x04))
++ return 0;
++
++ usleep_range(1000, 2000);
++ }
++
++ return ret ?: -ETIMEDOUT;
++}
++
++static void st7123_report_all_up(struct st7123_ts_data *ts)
++{
++ int i;
++
++ for (i = 0; i < ts->max_touches; i++) {
++ input_mt_slot(ts->input, i);
++ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false);
++ }
++
++ input_mt_sync_frame(ts->input);
++ input_sync(ts->input);
++}
++
++static int st7123_report_touch_data(struct st7123_ts_data *ts)
++{
++ struct device *dev = &ts->client->dev;
++ int read_len;
++ int ret;
++ int i;
++
++ read_len = ST7123_TOUCH_HEADER_LEN +
++ ts->max_touches * ST7123_TOUCH_STRIDE;
++ if (ts->has_coord_checksum)
++ read_len++;
++
++ ret = st7123_read(ts, ST7123_TOUCH_INFO, ts->coord_buf, read_len);
++ if (ret) {
++ dev_warn(dev, "failed to read touch data: %d\n", ret);
++ return ret;
++ }
++
++ if (ts->coord_buf[0] & BIT(7)) {
++ dev_warn(dev, "controller requested reset\n");
++ st7123_report_all_up(ts);
++ st7123_reset(ts);
++ return -EIO;
++ }
++
++ if (ts->has_coord_checksum) {
++ u8 checksum;
++
++ checksum = st7123_checksum(ts->coord_buf, read_len - 1);
++ if (checksum != ts->coord_buf[read_len - 1]) {
++ dev_warn(dev, "touch checksum mismatch\n");
++ return -EIO;
++ }
++ }
++
++ for (i = 0; i < ts->max_touches; i++) {
++ u8 *p = &ts->coord_buf[ST7123_TOUCH_HEADER_LEN +
++ i * ST7123_TOUCH_STRIDE];
++ u16 x;
++ u16 y;
++
++ input_mt_slot(ts->input, i);
++
++ if (!(p[0] & BIT(7))) {
++ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER,
++ false);
++ continue;
++ }
++
++ x = ((p[0] & 0x3f) << 8) | p[1];
++ y = ((p[2] & 0x3f) << 8) | p[3];
++
++ input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true);
++ touchscreen_report_pos(ts->input, &ts->prop, x, y, true);
++ input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, max_t(u8, p[4], 1));
++ }
++
++ input_mt_sync_frame(ts->input);
++ input_sync(ts->input);
++
++ return 0;
++}
++
++static void st7123_poll(struct input_dev *input)
++{
++ struct st7123_ts_data *ts = input_get_drvdata(input);
++
++ mutex_lock(&ts->lock);
++ if (!ts->suspended)
++ st7123_report_touch_data(ts);
++ mutex_unlock(&ts->lock);
++}
++
++static irqreturn_t st7123_irq_thread(int irq, void *dev_id)
++{
++ struct st7123_ts_data *ts = dev_id;
++
++ mutex_lock(&ts->lock);
++ if (!ts->suspended)
++ st7123_report_touch_data(ts);
++ mutex_unlock(&ts->lock);
++
++ return IRQ_HANDLED;
++}
++
++static int st7123_powerdown(struct st7123_ts_data *ts, bool powerdown)
++{
++ u8 ctrl;
++ int ret;
++
++ ret = st7123_read(ts, ST7123_DEVICE_CONTROL_REG, &ctrl, sizeof(ctrl));
++ if (ret)
++ return ret;
++
++ if (powerdown)
++ ctrl |= BIT(1);
++ else
++ ctrl &= ~BIT(1);
++
++ return st7123_write(ts, ST7123_DEVICE_CONTROL_REG, &ctrl, sizeof(ctrl));
++}
++
++static int st7123_probe(struct i2c_client *client)
++{
++ struct device *dev = &client->dev;
++ struct st7123_ts_data *ts;
++ struct input_dev *input;
++ u16 max_x = ST7123_DEFAULT_X_RES;
++ u16 max_y = ST7123_DEFAULT_Y_RES;
++ int ret;
++
++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
++ return dev_err_probe(dev, -EIO, "I2C transfers not supported\n");
++
++ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
++ if (!ts)
++ return -ENOMEM;
++
++ input = devm_input_allocate_device(dev);
++ if (!input)
++ return -ENOMEM;
++
++ ts->client = client;
++ ts->input = input;
++ ts->max_touches = ST7123_DEFAULT_MAX_TOUCHES;
++ mutex_init(&ts->lock);
++ i2c_set_clientdata(client, ts);
++
++ ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
++ if (IS_ERR(ts->reset_gpio))
++ return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
++ "failed to request reset GPIO\n");
++
++ st7123_reset(ts);
++
++ ret = st7123_wait_ready(ts);
++ if (ret)
++ return dev_err_probe(dev, ret, "controller not ready\n");
++
++ ret = st7123_check_id(ts);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to read chip id\n");
++
++ ret = st7123_read_resolution(ts, &max_x, &max_y);
++ if (ret)
++ dev_warn(dev, "using default resolution: %d\n", ret);
++
++ ret = st7123_read_max_touches(ts);
++ if (ret)
++ dev_warn(dev, "using default touch count: %d\n", ret);
++
++ ret = st7123_read_misc_info(ts);
++ if (ret)
++ dev_warn(dev, "failed to read misc info: %d\n", ret);
++
++ input->name = "Sitronix ST7123 Touchscreen";
++ input->id.bustype = BUS_I2C;
++ input->dev.parent = dev;
++ input_set_drvdata(input, ts);
++
++ input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_x - 1, 0, 0);
++ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_y - 1, 0, 0);
++ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
++ touchscreen_parse_properties(input, true, &ts->prop);
++
++ ret = input_mt_init_slots(input, ts->max_touches,
++ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to initialize MT slots\n");
++
++ if (client->irq) {
++ ret = devm_request_threaded_irq(dev, client->irq, NULL,
++ st7123_irq_thread,
++ IRQF_ONESHOT,
++ client->name, ts);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to request IRQ\n");
++ } else {
++ ret = input_setup_polling(input, st7123_poll);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to set up polling\n");
++
++ input_set_poll_interval(input, ST7123_POLL_INTERVAL_MS);
++ }
++
++ ret = input_register_device(input);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to register input\n");
++
++ return 0;
++}
++
++static int st7123_suspend(struct device *dev)
++{
++ struct i2c_client *client = to_i2c_client(dev);
++ struct st7123_ts_data *ts = i2c_get_clientdata(client);
++ bool wakeup = device_may_wakeup(dev);
++
++ if (client->irq) {
++ if (wakeup)
++ enable_irq_wake(client->irq);
++ else
++ disable_irq(client->irq);
++ }
++
++ mutex_lock(&ts->lock);
++ ts->suspended = true;
++ st7123_report_all_up(ts);
++ if (!wakeup)
++ st7123_powerdown(ts, true);
++ mutex_unlock(&ts->lock);
++
++ return 0;
++}
++
++static int st7123_resume(struct device *dev)
++{
++ struct i2c_client *client = to_i2c_client(dev);
++ struct st7123_ts_data *ts = i2c_get_clientdata(client);
++ bool wakeup = device_may_wakeup(dev);
++
++ mutex_lock(&ts->lock);
++ if (!wakeup) {
++ st7123_powerdown(ts, false);
++ st7123_reset(ts);
++ }
++ ts->suspended = false;
++ mutex_unlock(&ts->lock);
++
++ if (client->irq) {
++ if (wakeup)
++ disable_irq_wake(client->irq);
++ else
++ enable_irq(client->irq);
++ }
++
++ return 0;
++}
++
++static DEFINE_SIMPLE_DEV_PM_OPS(st7123_pm_ops, st7123_suspend, st7123_resume);
++
++static const struct i2c_device_id st7123_id[] = {
++ { "st7123" },
++ { }
++};
++MODULE_DEVICE_TABLE(i2c, st7123_id);
++
++static const struct of_device_id st7123_of_match[] = {
++ { .compatible = "sitronix,st7123" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, st7123_of_match);
++
++static struct i2c_driver st7123_driver = {
++ .probe = st7123_probe,
++ .id_table = st7123_id,
++ .driver = {
++ .name = ST7123_TS_NAME,
++ .of_match_table = st7123_of_match,
++ .pm = pm_sleep_ptr(&st7123_pm_ops),
++ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
++ },
++};
++module_i2c_driver(st7123_driver);
++
++MODULE_AUTHOR("Sitronix Technology Co., Ltd.");
++MODULE_DESCRIPTION("Sitronix ST7123 touchscreen controller driver");
++MODULE_LICENSE("GPL");
--- /dev/null
+From 97761fa3b27cc6cfb8638b0a88f5e5b14d2403fa Mon Sep 17 00:00:00 2001
+From: Waveshare_Team <support@waveshare.com>
+Date: Mon, 27 Jul 2026 21:26:09 +0800
+Subject: [PATCH] ARM: dts: overlays: waveshare-panel-v2: Add 6.25-DSI-TOUCH-A
+
+Add an overlay option for the Waveshare 6.25-DSI-TOUCH-A panel and
+touchscreen.
+
+The 6_25_inch_a option selects the two-lane panel and the Sitronix
+ST7123 touch controller at I2C address 0x55.
+
+Signed-off-by: Waveshare_Team <support@waveshare.com>
+---
+ arch/arm/boot/dts/overlays/README | 1 +
+ .../dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts | 5 ++++-
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+--- a/arch/arm/boot/dts/overlays/README
++++ b/arch/arm/boot/dts/overlays/README
+@@ -6089,6 +6089,7 @@ Params: 3_4_inch_c 3.4" 800
+ 4_3_inch_a 4.3" 480x800 2lane
+ 5_0_inch_a 5.0" 720x1280 2lane
+ 5_5_inch_a 5.5" 720x1280 2lane
++ 6_25_inch_a 6.25" 720x1560 2lane
+ 7_0_inch_a 7.0" A 720x1280 2lane
+ 7_0_inch_b 7.0" B 720x1280 2lane
+ 7_0_inch_c 7.0" C 1024x600 2lane
+--- a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts
++++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts
+@@ -25,7 +25,7 @@
+ enable-gpio = <&display_mcu 2 GPIO_ACTIVE_HIGH>;
+ };
+
+- touch: goodix@5d {
++ touch: touch@5d {
+ compatible = "goodix,gt9271";
+ reg = <0x5d>;
+ reset-gpio = <&display_mcu 9 GPIO_ACTIVE_HIGH>;
+@@ -84,6 +84,9 @@
+ 4_3_inch_a = <&dsi_panel>, "compatible=waveshare,4.3-dsi-touch-a";
+ 5_0_inch_a = <&dsi_panel>, "compatible=waveshare,5.0-dsi-touch-a";
+ 5_5_inch_a = <&dsi_panel>, "compatible=waveshare,5.5-dsi-touch-a";
++ 6_25_inch_a = <&dsi_panel>, "compatible=waveshare,6.25-dsi-touch-a",
++ <&touch>, "compatible=sitronix,st7123",
++ <&touch>, "reg:0=0x55";
+ 7_0_inch_a = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-a";
+ 7_0_inch_b = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-b";
+ 7_0_inch_c = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-c";
--- /dev/null
+From 887ceda573d90ddd4c1eb1f5c746a91a669e071f Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Tue, 28 Jul 2026 22:20:21 +0200
+Subject: [PATCH] arm64: dts: rp1: Mux SPI chip-select pins as GPIO
+
+The spi0 and spi8 CS groups mux their pins to the peripheral function
+while the pins are actually driven via cs-gpios. Under strict pinmux the
+overlapping function and GPIO claims are rejected and the SPI host fails
+to probe. Mux them as GPIO to match how they are used.
+
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ arch/arm64/boot/dts/broadcom/rp1.dtsi | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/broadcom/rp1.dtsi
++++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
+@@ -930,7 +930,7 @@
+ };
+
+ rp1_spi0_cs_gpio7: rp1_spi0_cs_gpio7 {
+- function = "spi0";
++ function = "gpio";
+ pins = "gpio7", "gpio8";
+ bias-pull-up;
+ };
+@@ -984,7 +984,7 @@
+ };
+
+ rp1_spi8_cs_gpio52: rp1_spi8_cs_gpio52 {
+- function = "spi0";
++ function = "gpio";
+ pins = "gpio52", "gpio53";
+ bias-pull-up;
+ };
--- /dev/null
+From aa16cd82538befcb1230239728a8dc1ce9593c93 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Wed, 29 Jul 2026 22:52:56 +0100
+Subject: [PATCH] overlays: Don't configure PIO pins as such
+
+Although it seems sensible to select the PIO function for pins to be
+used for PIO, the pio-rp1 driver takes care of that. With strict pinmux
+mode enabled, allowing pinctrl to claim it for PIO on behalf of the
+driver will interfere with the client drivers, which use gpiod to
+claim the pins. By not setting the pin function, or by setting it to
+"gpio" (*), there is no contention.
+
+(*) In fact, unless some other pinconf change is required, such as
+setting or disabling a pull, no pinctrl declaration is required.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ arch/arm/boot/dts/overlays/pwm-pio-overlay.dts | 1 -
+ arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts | 1 -
+ 2 files changed, 2 deletions(-)
+
+--- a/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
++++ b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
+@@ -11,7 +11,6 @@
+ __overlay__ {
+ pwm_pio_pins: pwm_pio_pins@4 {
+ brcm,pins = <4>; /* gpio 4 */
+- function = "pio";
+ bias-disable;
+ };
+ };
+--- a/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts
++++ b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts
+@@ -11,7 +11,6 @@
+ __overlay__ {
+ ws2812_pio_pins: ws2812_pio_pins@4 {
+ brcm,pins = <4>; /* gpio 4 */
+- function = "pio";
+ bias-disable;
+ };
+ };
--- /dev/null
+From 11c58b576f5f8b1f1bdd562eb159ed5212e29414 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Sun, 26 Jul 2026 00:33:55 +0200
+Subject: [PATCH] pinctrl: rp1: enable strict pinmux mode
+
+Pins claimed for a peripheral function can be taken over through the
+GPIO chardev, which rewrites FUNCSEL. On Pi 5 reading the USB
+overcurrent pin this way trips the overcurrent latch and disables all
+USB ports, and requesting GPIO 42 as output cuts VBUS entirely.
+
+Enable strict pinmux so GPIO requests on function pins are rejected.
+Implement function_is_gpio so pins muxed as GPIO, including hogs and
+legacy brcm,function maps, stay requestable. This matches the BCM2712
+pinctrl-brcmstb driver.
+
+Link: https://github.com/raspberrypi/linux/issues/5870
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/pinctrl/pinctrl-rp1.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/pinctrl/pinctrl-rp1.c
++++ b/drivers/pinctrl/pinctrl-rp1.c
+@@ -1363,6 +1363,7 @@ static const struct pinmux_ops rp1_pmx_o
+ .gpio_disable_free = rp1_pmx_gpio_disable_free,
+ .gpio_set_direction = rp1_pmx_gpio_set_direction,
+ .function_is_gpio = rp1_pmx_function_is_gpio,
++ .strict = true,
+ };
+
+ static void rp1_pull_config_set(struct rp1_pin_info *pin, unsigned int arg)
--- /dev/null
+From 95f42ede8c6ce3a552c20872d7bb4fab57298629 Mon Sep 17 00:00:00 2001
+From: Nicolai Buchwitz <nb@tipi-net.de>
+Date: Sat, 25 Jul 2026 23:46:32 +0200
+Subject: [PATCH] pinctrl: rp1: Fix node leak and mapping check in probe
+
+Drop the leaked rp1_node reference and test of_iomap() for NULL instead
+of IS_ERR(), which never matches its return value.
+
+Fixes: df868dcd8b11 ("pinctrl: Reinstate the downstream pinctrl-rp1 driver")
+Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
+---
+ drivers/pinctrl/pinctrl-rp1.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/pinctrl/pinctrl-rp1.c
++++ b/drivers/pinctrl/pinctrl-rp1.c
+@@ -1627,11 +1627,10 @@ static int rp1_pinctrl_probe(struct plat
+ else if (pace_pin_updates &&
+ of_device_is_compatible(rp1_node->parent, "brcm,bcm2712-pcie")) {
+ pc->dummy_base = of_iomap(rp1_node->parent, 0);
+- if (IS_ERR(pc->dummy_base)) {
++ if (!pc->dummy_base)
+ dev_warn(&pdev->dev, "could not map bcm2712 root complex registers\n");
+- pc->dummy_base = NULL;
+- }
+ }
++ of_node_put(rp1_node);
+
+ for (i = 0; i < RP1_NUM_BANKS; i++) {
+ const struct rp1_iobank_desc *bank = &rp1_iobanks[i];
--- /dev/null
+From b0f70212b2fe409082cc0046f117d7116b2a66bf Mon Sep 17 00:00:00 2001
+From: Dom Cobley <popcornmix@gmail.com>
+Date: Mon, 13 Jul 2026 16:46:32 +0100
+Subject: [PATCH] drm/vc4: kms: Reduce firmware clock requests during atomic
+ commits
+
+On Raspberry Pi the core clock is managed by the firmware, so every
+clk_set_min_rate() call on it results in at least one blocking
+mailbox round-trip to the VPU: the clock is registered with
+CLK_GET_RATE_NOCACHE, so clk_set_rate_range() always queries the
+current rate via GET_CLOCK_RATE, and issues a SET_CLOCK_RATE on top
+if the aggregated rate changed. All of this happens under the global
+clk prepare_lock.
+
+vc4_atomic_commit_tail() currently requests a boost of the core clock
+to at least 500MHz at the start of *every* commit and settles back to
+the load-derived rate at the end, even though the boost is only needed
+to avoid stalling the pipeline during a full modeset. In addition the
+final drm_dbg() evaluated clk_get_rate() unconditionally, hiding
+another firmware query. A compositor page-flipping at 60Hz therefore
+generated several mailbox transactions per frame, all of them no-ops.
+
+Fix this in two ways:
+
+ - Only apply the 500MHz floor when one of the CRTCs in the commit
+ actually needs a modeset. Plane-only updates still raise the clock
+ to the maximum of the old and new state requirements before the
+ HVS is reprogrammed, since the load can change without a modeset.
+
+ - Cache the last requested minimum rate in struct vc4_hvs and skip
+ requests that wouldn't change it. The core and display clocks are
+ always requested at the same rate, so a single cached value is
+ enough, and the actual-rate debug query now only happens when the
+ request really changes.
+
+With this, steady-state page flips no longer generate any firmware
+mailbox traffic, while modesets and genuine load changes behave as
+before.
+
+Signed-off-by: Dom Cobley <popcornmix@gmail.com>
+---
+ drivers/gpu/drm/vc4/vc4_drv.h | 6 ++++
+ drivers/gpu/drm/vc4/vc4_kms.c | 58 ++++++++++++++++++++++++++---------
+ 2 files changed, 50 insertions(+), 14 deletions(-)
+
+--- a/drivers/gpu/drm/vc4/vc4_drv.h
++++ b/drivers/gpu/drm/vc4/vc4_drv.h
+@@ -339,6 +339,12 @@ struct vc4_hvs {
+ struct clk *core_clk;
+ struct clk *disp_clk;
+
++ /* Last min rate requested on core_clk and disp_clk. Each
++ * clk_set_min_rate() on a firmware clock costs at least one
++ * mailbox round-trip, so skip requests that wouldn't change it.
++ */
++ unsigned long core_clk_min_rate;
++
+ struct {
+ unsigned int desc;
+ unsigned int enabled: 1;
+--- a/drivers/gpu/drm/vc4/vc4_kms.c
++++ b/drivers/gpu/drm/vc4/vc4_kms.c
+@@ -385,6 +385,40 @@ static void vc6_hvs_pv_muxing_commit(str
+ }
+ }
+
++/* Each clk_set_min_rate() on a firmware clock costs at least one
++ * mailbox round-trip to the VPU, so don't repeat requests that
++ * wouldn't change the rate.
++ */
++static void vc4_hvs_set_min_core_rate(struct vc4_hvs *hvs,
++ unsigned long core_rate)
++{
++ struct drm_device *dev = &hvs->vc4->base;
++
++ if (core_rate == hvs->core_clk_min_rate)
++ return;
++
++ hvs->core_clk_min_rate = core_rate;
++
++ WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
++ WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate));
++
++ drm_dbg(dev, "Core clock min rate %lu Hz, actual rate: %lu Hz\n",
++ core_rate, clk_get_rate(hvs->core_clk));
++}
++
++static bool vc4_atomic_needs_modeset(struct drm_atomic_state *state)
++{
++ struct drm_crtc_state *new_crtc_state;
++ struct drm_crtc *crtc;
++ int i;
++
++ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i)
++ if (drm_atomic_crtc_needs_modeset(new_crtc_state))
++ return true;
++
++ return false;
++}
++
+ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
+ {
+ struct drm_device *dev = state->dev;
+@@ -441,17 +475,19 @@ static void vc4_atomic_commit_tail(struc
+ if (vc4->gen == VC4_GEN_5 && !vc4->firmware_kms) {
+ unsigned long state_rate = max(old_hvs_state->core_clock_rate,
+ new_hvs_state->core_clock_rate);
+- unsigned long core_rate = clamp_t(unsigned long, state_rate,
+- 500000000, hvs->max_core_rate);
+-
+- drm_dbg(dev, "Raising the core clock at %lu Hz\n", core_rate);
++ unsigned long core_rate = min_t(unsigned long, state_rate,
++ hvs->max_core_rate);
+
+ /*
+ * Do a temporary request on the core clock during the
+- * modeset.
++ * modeset. Plane-only updates only need the rate the new
++ * state asked for, not the 500MHz boost.
+ */
+- WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+- WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate));
++ if (vc4_atomic_needs_modeset(state))
++ core_rate = clamp_t(unsigned long, state_rate,
++ 500000000, hvs->max_core_rate);
++
++ vc4_hvs_set_min_core_rate(hvs, core_rate);
+ }
+
+ drm_atomic_helper_commit_modeset_disables(dev, state);
+@@ -498,17 +534,11 @@ static void vc4_atomic_commit_tail(struc
+ hvs->max_core_rate,
+ new_hvs_state->core_clock_rate);
+
+- drm_dbg(dev, "Running the core clock at %lu Hz\n", core_rate);
+-
+ /*
+ * Request a clock rate based on the current HVS
+ * requirements.
+ */
+- WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+- WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate));
+-
+- drm_dbg(dev, "Core clock actual rate: %lu Hz\n",
+- clk_get_rate(hvs->core_clk));
++ vc4_hvs_set_min_core_rate(hvs, core_rate);
+ }
+ }
+
--- /dev/null
+From a9b26ac27e210dc851c57befc9537c65de6eac6b Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 31 Jul 2026 09:18:44 +0100
+Subject: [PATCH] pinctrl: bcm2835: Make IRQ usage set pin to input
+
+If a GPIO is being used as a source of interrupts, it must be configured
+as an input otherwise one risks an interrupt storm.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/pinctrl/bcm/pinctrl-bcm2835.c | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+
+--- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c
++++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
+@@ -724,6 +724,23 @@ static int bcm2835_gpio_irq_set_wake(str
+ return ret;
+ }
+
++static int bcm2835_gpio_irq_reqres(struct irq_data *d)
++{
++ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
++ int ret;
++
++ ret = gpiochip_irq_reqres(d);
++ if (!ret)
++ ret = bcm2835_gpio_direction_input(gc, d->hwirq);
++
++ return ret;
++}
++
++static void bcm2835_gpio_irq_relres(struct irq_data *d)
++{
++ return gpiochip_irq_relres(d);
++}
++
+ static const struct irq_chip bcm2835_gpio_irq_chip = {
+ .name = MODULE_NAME,
+ .irq_set_type = bcm2835_gpio_irq_set_type,
+@@ -731,6 +748,8 @@ static const struct irq_chip bcm2835_gpi
+ .irq_mask = bcm2835_gpio_irq_mask,
+ .irq_unmask = bcm2835_gpio_irq_unmask,
+ .irq_set_wake = bcm2835_gpio_irq_set_wake,
++ .irq_request_resources = bcm2835_gpio_irq_reqres,
++ .irq_release_resources = bcm2835_gpio_irq_relres,
+ .flags = (IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE),
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+ };
--- /dev/null
+From 825dba6c63eeb40a62699d1f8a4aa3f02b0eaf49 Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.com>
+Date: Fri, 31 Jul 2026 09:27:38 +0100
+Subject: [PATCH] pinctrl: bcm2835: Enable strict pinmux mode
+
+Enable strict pinmux so GPIO requests on function pins are rejected.
+Implement function_is_gpio so pins muxed as GPIO, including hogs and
+legacy brcm,function maps, stay requestable.
+
+Signed-off-by: Phil Elwell <phil@raspberrypi.com>
+---
+ drivers/pinctrl/bcm/pinctrl-bcm2835.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c
++++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
+@@ -1034,6 +1034,13 @@ static int bcm2835_pmx_gpio_set_directio
+ return 0;
+ }
+
++static bool bcm2835_pmx_function_is_gpio(struct pinctrl_dev *pctldev,
++ unsigned int selector)
++{
++ return selector == BCM2835_FSEL_GPIO_IN ||
++ selector == BCM2835_FSEL_GPIO_OUT;
++}
++
+ static const struct pinmux_ops bcm2835_pmx_ops = {
+ .free = bcm2835_pmx_free,
+ .get_functions_count = bcm2835_pmx_get_functions_count,
+@@ -1042,6 +1049,8 @@ static const struct pinmux_ops bcm2835_p
+ .set_mux = bcm2835_pmx_set,
+ .gpio_disable_free = bcm2835_pmx_gpio_disable_free,
+ .gpio_set_direction = bcm2835_pmx_gpio_set_direction,
++ .function_is_gpio = bcm2835_pmx_function_is_gpio,
++ .strict = true,
+ };
+
+ static int bcm2835_pinconf_get(struct pinctrl_dev *pctldev,
From 532a3c77ce735fae7f7155cae5fbda768120fc1e Mon Sep 17 00:00:00 2001
From: Joshua Covington <joshuacov@gmail.com>
-Date: Wed, 1 Jul 2026 20:54:52 +0000
+Date: Wed, 1 Aug 2026 20:54:52 +0000
Subject: [PATCH] Post-Merge Fixup - up to
- 9df439fbf76c0cb9f1a9282a7bf44b3405d51690
+ 825dba6c63eeb40a62699d1f8a4aa3f02b0eaf49
The man page for 'git format-patch' states that it is not suitable for dealing
with merge commits, which are omitted by default. There is no way to avoid
arch/arm64/kernel/cpu_errata.c | 2 +-
drivers/gpu/drm/v3d/v3d_submit.c | 2 +-
drivers/mmc/core/card.h | 10 +-
- 3 files changed, 7 insertions(+), 7 deletions(-)
+ drivers/nvme/host/pci.c | 4 +-
+ 4 files changed, 9 insertions(+), 9 deletions(-)
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
-}
-
#endif
+--- a/drivers/nvme/host/pci.c
++++ b/drivers/nvme/host/pci.c
+@@ -768,8 +768,8 @@ static void nvme_unmap_data(struct reque
+
+ if (!blk_rq_dma_unmap(req, dma_dev, &iod->dma_state, iod->total_len)) {
+ if (nvme_pci_cmd_use_sgl(&iod->cmd))
+- nvme_free_sgls(req, iod->descriptors[0],
+- &iod->cmd.common.dptr.sgl);
++ nvme_free_sgls(req, &iod->cmd.common.dptr.sgl,
++ iod->descriptors[0]);
+ else
+ nvme_free_prps(req);
+ }