]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
bcm27xx: update patches up to Git HEAD (2026-08-02)
authorJoshua Covington <joshuacov@gmail.com>
Sun, 2 Aug 2026 12:03:42 +0000 (12:03 +0000)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Fri, 7 Aug 2026 10:24:30 +0000 (12:24 +0200)
These patches were generated from:
https://github.com/raspberrypi/linux/commits/rpi-6.18.y
Patches were generated from the diff between linux kernel branch linux-6.18.y
and rpi-6.18.y from raspberry pi kernel source:

git format-patch -N linux-6.18.y..rpi-6.18.y (HEAD)
(HEAD -> 825dba6c63eeb40a62699d1f8a4aa3f02b0eaf49) as of 20260802
"Commit: pinctrl: bcm2835: Enable strict pinmux mode"

Exceptions:
- github workflow patches
- applied and reverted patches
- README patches
- (def)configs
- patches merged upstream

Patches refreshed against current OpenWrt-kernel.

Signed-off-by: Joshua Covington <joshuacov@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/24275
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
88 files changed:
target/linux/bcm27xx/patches-6.18/0808-overlays-draws-Fix-reset-gpios-polarity-for-gpiod-co.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0809-overlays-Add-draws-pi5-overlay-for-Raspberry-Pi-5.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0810-overlays-draws-Use-GPIO-flag-constants-for-the-reset.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0811-overlays-sdhost-Add-gpios_22_27-parameter.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0812-brcmfmac-cyw-clean-up-PMKID-and-cookie-code.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0814-dt-bindings-media-i2c-Add-Sony-IMX355.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0815-media-i2c-imx355-Support-devicetree-and-power-manage.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0816-media-i2c-imx355-Restrict-data-lanes-to-4.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0817-media-i2c-imx355-Add-support-for-24-MHz-external-clo.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0818-dt-bindings-media-imx355-Allow-2-CSI2-data-lane-outp.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0819-media-imx355-Remove-duplicated-registers-from-the-mo.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0820-media-imx355-Remove-setting-FRM_LENGTH_LINES-in-the-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0821-media-imx355-Programmatically-set-the-crop-parameter.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0822-media-imx355-Set-register-LINE_LENGTH_PCK-programmat.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0823-media-imx355-Set-binning-mode-registers-programmatic.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0824-media-imx355-Remove-link_freq_index-from-each-mode-a.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0825-media-imx355-pixel_rate-never-changes-so-don-t-recom.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0826-media-imx355-Remove-redundant-fll_min-and-implement-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0827-media-imx355-Add-support-for-get_selection.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0828-media-imx355-Use-pm_runtime-autosuspend_delay.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0829-media-imx355-Convert-to-new-CCI-register-access-help.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0830-media-imx355-Set-the-colorspace-in-the-format.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0831-media-imx355-Define-the-exposure-offset-and-use-that.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0832-media-imx355-Use-NULL-ctrl_ops-for-HBLANK-as-it-is-a.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0833-media-imx355-Compute-link-frequency-from-PLL-setup.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0834-media-imx355-Support-2-lane-readout.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0835-media-imx355-Switch-to-using-the-subdev-state.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0836-media-imx355-Remove-storing-cur_mode-in-the-state.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0837-media-imx355-Make-V4L2_CID_HBLANK-controllable.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0840-dtoverlays-Add-overlay-for-imx355-image-sensor.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0841-overlays-ed-ipc-Add-EDATEC-IPC-EXP-unified-board-ove.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0842-Revert-media-hevc_d-Fix-height-rounding-for-old-sand.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0843-Revert-media-hevc_d-Fix-old-style-bit_size-handling-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0845-Revert-media-hevc_dec-Add-in-downstream-single-plana.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0846-hevc_d-Pick-the-fixes-from-the-latest-upstreaming-re.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0848-media-hevc_dec-Add-in-downstream-single-planar-SAND-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0849-overlays-i2c-fan-add-compatible-for-upstream-compati.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0850-dmaengine-dw-axi-dmac-Wait-for-runtime-resume.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0851-mmc-don-t-reference-requests-after-issuing-them.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0852-Revert-net-macb-gate-TX-stall-watchdog-on-netif_carr.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0853-Revert-net-macb-drop-destructive-ISR-read-use-IMR-ba.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0854-Revert-net-macb-add-TX-stall-watchdog-as-defence-in-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0855-Revert-net-macb-re-check-ISR-after-IER-re-enable-in-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0857-drm-vc4-hdmi-Remove-duplicate-hotplug-helper-call.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0858-ASoC-rename-hifiberry_studio_dac8x.c-to-hifiberry_st.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0859-ASoC-hifiberry_studio-add-multi-card-and-Digi-AES-su.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0860-overlays-hifiberry-studio-add-Digi-AES-overlay-regis.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0862-staging-vchiq_arm-use-one-DMA-device-to-map-and-unma.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0864-mailbox-rp1-Don-t-poll-for-TX-completion.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0865-firmware-rp1-fw-Call-mbox_client_txdone-after-TX.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0866-drm-v3d-Reach-the-GMP-through-the-hub-registers-on-V.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0867-drm-v3d-Idle-AXI-transactions-before-disabling-the-c.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0868-mmc-block-Fix-pending_writes-underflow-on-non-CQE-ho.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0869-drm-vc4-hdmi-switch-to-generic-CEC-helpers.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0870-misc-rp1-pio-release-DMA-channel-outside-pio-lock.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0871-misc-rp1-pio-Reverse-christmas-tree-declarations.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0872-fixup-misc-rp1-pio-Reverse-christmas-tree-declaratio.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0873-misc-rpi-pio-In-kernel-API-fixes.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0874-misc-rp1-pio-Add-sm_get_dmactrl-and-sm_get_flags.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0875-misc-rp1-pio-Add-support-for-PIO-interrupts.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0876-arm64-dts-rp1-Declare-the-PIO-interrupts.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0877-misc-rp1-pio-Non-blocking-blocking-operations.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0878-serial-Add-rp1-pio-uart-a-PIO-based-UART.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0880-overlays-Add-the-rp1-pio-uart-overlay.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0881-overlays-Add-ramoops-pi5.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0882-overlays-apds9960-Add-missing-pinctrl-reference.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0883-input-goodix-Include-I2C-details-in-names-for-the-de.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0884-nvme-host-pci-Revert-to-upstream.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0885-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0886-Revert-nvme-host-pci-Revert-to-upstream.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0887-Revert-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0888-ASoC-hifiberry-studio-add-AES-CS-Mode-control-channe.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0889-pinctrl-rp1-Make-IRQ-usage-set-a-pin-to-an-input.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0890-drm-edid-Accept-3D_Detail_X-of-0000-for-side-by-side.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0891-drm-display-hdmi-Account-for-frame-packing-in-the-TM.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0892-drm-vc4-Derive-scanout-rate-and-frame-size-from-the-.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0893-drm-vc4-Support-frame-packed-stereo-modes.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0894-drm-panel-waveshare-Add-6.25-DSI-TOUCH-A-panel-suppo.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0895-input-touchscreen-Add-Sitronix-ST7123-support.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0896-ARM-dts-overlays-waveshare-panel-v2-Add-6.25-DSI-TOU.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0898-arm64-dts-rp1-Mux-SPI-chip-select-pins-as-GPIO.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0899-overlays-Don-t-configure-PIO-pins-as-such.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0900-pinctrl-rp1-enable-strict-pinmux-mode.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0901-pinctrl-rp1-Fix-node-leak-and-mapping-check-in-probe.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0902-drm-vc4-kms-Reduce-firmware-clock-requests-during-at.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0903-pinctrl-bcm2835-Make-IRQ-usage-set-pin-to-input.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0904-pinctrl-bcm2835-Enable-strict-pinmux-mode.patch [new file with mode: 0644]
target/linux/bcm27xx/patches-6.18/0905-Post-Merge-Fixup-up-to-825dba6c63eeb40a62699d1f8a4aa.patch [moved from target/linux/bcm27xx/patches-6.18/0808-Post-Merge-Fixup-up-to-9df439fbf76c0cb9f1a9282a7bf44.patch with 81% similarity]

diff --git a/target/linux/bcm27xx/patches-6.18/0808-overlays-draws-Fix-reset-gpios-polarity-for-gpiod-co.patch b/target/linux/bcm27xx/patches-6.18/0808-overlays-draws-Fix-reset-gpios-polarity-for-gpiod-co.patch
new file mode 100644 (file)
index 0000000..ad1e811
--- /dev/null
@@ -0,0 +1,43 @@
+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>;
diff --git a/target/linux/bcm27xx/patches-6.18/0809-overlays-Add-draws-pi5-overlay-for-Raspberry-Pi-5.patch b/target/linux/bcm27xx/patches-6.18/0809-overlays-Add-draws-pi5-overlay-for-Raspberry-Pi-5.patch
new file mode 100644 (file)
index 0000000..7b1c191
--- /dev/null
@@ -0,0 +1,287 @@
+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";
+       };
diff --git a/target/linux/bcm27xx/patches-6.18/0810-overlays-draws-Use-GPIO-flag-constants-for-the-reset.patch b/target/linux/bcm27xx/patches-6.18/0810-overlays-draws-Use-GPIO-flag-constants-for-the-reset.patch
new file mode 100644 (file)
index 0000000..4c93bdb
--- /dev/null
@@ -0,0 +1,76 @@
+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>;
diff --git a/target/linux/bcm27xx/patches-6.18/0811-overlays-sdhost-Add-gpios_22_27-parameter.patch b/target/linux/bcm27xx/patches-6.18/0811-overlays-sdhost-Add-gpios_22_27-parameter.patch
new file mode 100644 (file)
index 0000000..c650a04
--- /dev/null
@@ -0,0 +1,71 @@
+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";
+       };
+ };
diff --git a/target/linux/bcm27xx/patches-6.18/0812-brcmfmac-cyw-clean-up-PMKID-and-cookie-code.patch b/target/linux/bcm27xx/patches-6.18/0812-brcmfmac-cyw-clean-up-PMKID-and-cookie-code.patch
new file mode 100644 (file)
index 0000000..9eccc77
--- /dev/null
@@ -0,0 +1,66 @@
+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));
diff --git a/target/linux/bcm27xx/patches-6.18/0814-dt-bindings-media-i2c-Add-Sony-IMX355.patch b/target/linux/bcm27xx/patches-6.18/0814-dt-bindings-media-i2c-Add-Sony-IMX355.patch
new file mode 100644 (file)
index 0000000..3139c92
--- /dev/null
@@ -0,0 +1,132 @@
+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>;
++                };
++            };
++        };
++    };
diff --git a/target/linux/bcm27xx/patches-6.18/0815-media-i2c-imx355-Support-devicetree-and-power-manage.patch b/target/linux/bcm27xx/patches-6.18/0815-media-i2c-imx355-Support-devicetree-and-power-manage.patch
new file mode 100644 (file)
index 0000000..872ccd4
--- /dev/null
@@ -0,0 +1,204 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0816-media-i2c-imx355-Restrict-data-lanes-to-4.patch b/target/linux/bcm27xx/patches-6.18/0816-media-i2c-imx355-Restrict-data-lanes-to-4.patch
new file mode 100644 (file)
index 0000000..6eb5d67
--- /dev/null
@@ -0,0 +1,37 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0817-media-i2c-imx355-Add-support-for-24-MHz-external-clo.patch b/target/linux/bcm27xx/patches-6.18/0817-media-i2c-imx355-Add-support-for-24-MHz-external-clo.patch
new file mode 100644 (file)
index 0000000..66b6608
--- /dev/null
@@ -0,0 +1,255 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0818-dt-bindings-media-imx355-Allow-2-CSI2-data-lane-outp.patch b/target/linux/bcm27xx/patches-6.18/0818-dt-bindings-media-imx355-Allow-2-CSI2-data-lane-outp.patch
new file mode 100644 (file)
index 0000000..ae01d2d
--- /dev/null
@@ -0,0 +1,35 @@
+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
diff --git a/target/linux/bcm27xx/patches-6.18/0819-media-imx355-Remove-duplicated-registers-from-the-mo.patch b/target/linux/bcm27xx/patches-6.18/0819-media-imx355-Remove-duplicated-registers-from-the-mo.patch
new file mode 100644 (file)
index 0000000..231ac46
--- /dev/null
@@ -0,0 +1,638 @@
+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[] = {
diff --git a/target/linux/bcm27xx/patches-6.18/0820-media-imx355-Remove-setting-FRM_LENGTH_LINES-in-the-.patch b/target/linux/bcm27xx/patches-6.18/0820-media-imx355-Remove-setting-FRM_LENGTH_LINES-in-the-.patch
new file mode 100644 (file)
index 0000000..82808b9
--- /dev/null
@@ -0,0 +1,144 @@
+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 },
diff --git a/target/linux/bcm27xx/patches-6.18/0821-media-imx355-Programmatically-set-the-crop-parameter.patch b/target/linux/bcm27xx/patches-6.18/0821-media-imx355-Programmatically-set-the-crop-parameter.patch
new file mode 100644 (file)
index 0000000..7e3bf09
--- /dev/null
@@ -0,0 +1,579 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0822-media-imx355-Set-register-LINE_LENGTH_PCK-programmat.patch b/target/linux/bcm27xx/patches-6.18/0822-media-imx355-Set-register-LINE_LENGTH_PCK-programmat.patch
new file mode 100644 (file)
index 0000000..64e3ec2
--- /dev/null
@@ -0,0 +1,168 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0823-media-imx355-Set-binning-mode-registers-programmatic.patch b/target/linux/bcm27xx/patches-6.18/0823-media-imx355-Set-binning-mode-registers-programmatic.patch
new file mode 100644 (file)
index 0000000..715cd36
--- /dev/null
@@ -0,0 +1,169 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0824-media-imx355-Remove-link_freq_index-from-each-mode-a.patch b/target/linux/bcm27xx/patches-6.18/0824-media-imx355-Remove-link_freq_index-from-each-mode-a.patch
new file mode 100644 (file)
index 0000000..a16c8d9
--- /dev/null
@@ -0,0 +1,146 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0825-media-imx355-pixel_rate-never-changes-so-don-t-recom.patch b/target/linux/bcm27xx/patches-6.18/0825-media-imx355-pixel_rate-never-changes-so-don-t-recom.patch
new file mode 100644 (file)
index 0000000..f7e5539
--- /dev/null
@@ -0,0 +1,55 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0826-media-imx355-Remove-redundant-fll_min-and-implement-.patch b/target/linux/bcm27xx/patches-6.18/0826-media-imx355-Remove-redundant-fll_min-and-implement-.patch
new file mode 100644 (file)
index 0000000..ba06e9a
--- /dev/null
@@ -0,0 +1,190 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0827-media-imx355-Add-support-for-get_selection.patch b/target/linux/bcm27xx/patches-6.18/0827-media-imx355-Add-support-for-get_selection.patch
new file mode 100644 (file)
index 0000000..7b74165
--- /dev/null
@@ -0,0 +1,102 @@
+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 = {
diff --git a/target/linux/bcm27xx/patches-6.18/0828-media-imx355-Use-pm_runtime-autosuspend_delay.patch b/target/linux/bcm27xx/patches-6.18/0828-media-imx355-Use-pm_runtime-autosuspend_delay.patch
new file mode 100644 (file)
index 0000000..86f678b
--- /dev/null
@@ -0,0 +1,43 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0829-media-imx355-Convert-to-new-CCI-register-access-help.patch b/target/linux/bcm27xx/patches-6.18/0829-media-imx355-Convert-to-new-CCI-register-access-help.patch
new file mode 100644 (file)
index 0000000..5ae2e49
--- /dev/null
@@ -0,0 +1,738 @@
+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),
diff --git a/target/linux/bcm27xx/patches-6.18/0830-media-imx355-Set-the-colorspace-in-the-format.patch b/target/linux/bcm27xx/patches-6.18/0830-media-imx355-Set-the-colorspace-in-the-format.patch
new file mode 100644 (file)
index 0000000..9ac0815
--- /dev/null
@@ -0,0 +1,26 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0831-media-imx355-Define-the-exposure-offset-and-use-that.patch b/target/linux/bcm27xx/patches-6.18/0831-media-imx355-Define-the-exposure-offset-and-use-that.patch
new file mode 100644 (file)
index 0000000..f395449
--- /dev/null
@@ -0,0 +1,44 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0832-media-imx355-Use-NULL-ctrl_ops-for-HBLANK-as-it-is-a.patch b/target/linux/bcm27xx/patches-6.18/0832-media-imx355-Use-NULL-ctrl_ops-for-HBLANK-as-it-is-a.patch
new file mode 100644 (file)
index 0000000..b648739
--- /dev/null
@@ -0,0 +1,29 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0833-media-imx355-Compute-link-frequency-from-PLL-setup.patch b/target/linux/bcm27xx/patches-6.18/0833-media-imx355-Compute-link-frequency-from-PLL-setup.patch
new file mode 100644 (file)
index 0000000..349098c
--- /dev/null
@@ -0,0 +1,142 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0834-media-imx355-Support-2-lane-readout.patch b/target/linux/bcm27xx/patches-6.18/0834-media-imx355-Support-2-lane-readout.patch
new file mode 100644 (file)
index 0000000..7ea1c37
--- /dev/null
@@ -0,0 +1,197 @@
+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,
diff --git a/target/linux/bcm27xx/patches-6.18/0835-media-imx355-Switch-to-using-the-subdev-state.patch b/target/linux/bcm27xx/patches-6.18/0835-media-imx355-Switch-to-using-the-subdev-state.patch
new file mode 100644 (file)
index 0000000..1ae2708
--- /dev/null
@@ -0,0 +1,386 @@
+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 = {
diff --git a/target/linux/bcm27xx/patches-6.18/0836-media-imx355-Remove-storing-cur_mode-in-the-state.patch b/target/linux/bcm27xx/patches-6.18/0836-media-imx355-Remove-storing-cur_mode-in-the-state.patch
new file mode 100644 (file)
index 0000000..0f08cee
--- /dev/null
@@ -0,0 +1,180 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0837-media-imx355-Make-V4L2_CID_HBLANK-controllable.patch b/target/linux/bcm27xx/patches-6.18/0837-media-imx355-Make-V4L2_CID_HBLANK-controllable.patch
new file mode 100644 (file)
index 0000000..fa71a7d
--- /dev/null
@@ -0,0 +1,80 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0840-dtoverlays-Add-overlay-for-imx355-image-sensor.patch b/target/linux/bcm27xx/patches-6.18/0840-dtoverlays-Add-overlay-for-imx355-image-sensor.patch
new file mode 100644 (file)
index 0000000..eeeca54
--- /dev/null
@@ -0,0 +1,184 @@
+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>;
++              };
++      };
++};
diff --git a/target/linux/bcm27xx/patches-6.18/0841-overlays-ed-ipc-Add-EDATEC-IPC-EXP-unified-board-ove.patch b/target/linux/bcm27xx/patches-6.18/0841-overlays-ed-ipc-Add-EDATEC-IPC-EXP-unified-board-ove.patch
new file mode 100644 (file)
index 0000000..e7dbc35
--- /dev/null
@@ -0,0 +1,373 @@
+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>;
++      };
++};
diff --git a/target/linux/bcm27xx/patches-6.18/0842-Revert-media-hevc_d-Fix-height-rounding-for-old-sand.patch b/target/linux/bcm27xx/patches-6.18/0842-Revert-media-hevc_d-Fix-height-rounding-for-old-sand.patch
new file mode 100644 (file)
index 0000000..4b444cc
--- /dev/null
@@ -0,0 +1,31 @@
+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
diff --git a/target/linux/bcm27xx/patches-6.18/0843-Revert-media-hevc_d-Fix-old-style-bit_size-handling-.patch b/target/linux/bcm27xx/patches-6.18/0843-Revert-media-hevc_d-Fix-old-style-bit_size-handling-.patch
new file mode 100644 (file)
index 0000000..5c3d67c
--- /dev/null
@@ -0,0 +1,66 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0845-Revert-media-hevc_dec-Add-in-downstream-single-plana.patch b/target/linux/bcm27xx/patches-6.18/0845-Revert-media-hevc_dec-Add-in-downstream-single-plana.patch
new file mode 100644 (file)
index 0000000..95aa6b7
--- /dev/null
@@ -0,0 +1,262 @@
+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;
+               }
+       }
diff --git a/target/linux/bcm27xx/patches-6.18/0846-hevc_d-Pick-the-fixes-from-the-latest-upstreaming-re.patch b/target/linux/bcm27xx/patches-6.18/0846-hevc_d-Pick-the-fixes-from-the-latest-upstreaming-re.patch
new file mode 100644 (file)
index 0000000..b91e4ab
--- /dev/null
@@ -0,0 +1,784 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0848-media-hevc_dec-Add-in-downstream-single-planar-SAND-.patch b/target/linux/bcm27xx/patches-6.18/0848-media-hevc_dec-Add-in-downstream-single-planar-SAND-.patch
new file mode 100644 (file)
index 0000000..7da6e60
--- /dev/null
@@ -0,0 +1,260 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0849-overlays-i2c-fan-add-compatible-for-upstream-compati.patch b/target/linux/bcm27xx/patches-6.18/0849-overlays-i2c-fan-add-compatible-for-upstream-compati.patch
new file mode 100644 (file)
index 0000000..3cc4f64
--- /dev/null
@@ -0,0 +1,25 @@
+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>;
+                       };
diff --git a/target/linux/bcm27xx/patches-6.18/0850-dmaengine-dw-axi-dmac-Wait-for-runtime-resume.patch b/target/linux/bcm27xx/patches-6.18/0850-dmaengine-dw-axi-dmac-Wait-for-runtime-resume.patch
new file mode 100644 (file)
index 0000000..98a1242
--- /dev/null
@@ -0,0 +1,53 @@
+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;
+ }
diff --git a/target/linux/bcm27xx/patches-6.18/0851-mmc-don-t-reference-requests-after-issuing-them.patch b/target/linux/bcm27xx/patches-6.18/0851-mmc-don-t-reference-requests-after-issuing-them.patch
new file mode 100644 (file)
index 0000000..952d837
--- /dev/null
@@ -0,0 +1,86 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0852-Revert-net-macb-gate-TX-stall-watchdog-on-netif_carr.patch b/target/linux/bcm27xx/patches-6.18/0852-Revert-net-macb-gate-TX-stall-watchdog-on-netif_carr.patch
new file mode 100644 (file)
index 0000000..3c847c6
--- /dev/null
@@ -0,0 +1,51 @@
+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));
+ }
diff --git a/target/linux/bcm27xx/patches-6.18/0853-Revert-net-macb-drop-destructive-ISR-read-use-IMR-ba.patch b/target/linux/bcm27xx/patches-6.18/0853-Revert-net-macb-drop-destructive-ISR-read-use-IMR-ba.patch
new file mode 100644 (file)
index 0000000..ccca036
--- /dev/null
@@ -0,0 +1,62 @@
+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));
diff --git a/target/linux/bcm27xx/patches-6.18/0854-Revert-net-macb-add-TX-stall-watchdog-as-defence-in-.patch b/target/linux/bcm27xx/patches-6.18/0854-Revert-net-macb-add-TX-stall-watchdog-as-defence-in-.patch
new file mode 100644 (file)
index 0000000..a3b090b
--- /dev/null
@@ -0,0 +1,136 @@
+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++;
+       }
diff --git a/target/linux/bcm27xx/patches-6.18/0855-Revert-net-macb-re-check-ISR-after-IER-re-enable-in-.patch b/target/linux/bcm27xx/patches-6.18/0855-Revert-net-macb-re-check-ISR-after-IER-re-enable-in-.patch
new file mode 100644 (file)
index 0000000..9bf20ff
--- /dev/null
@@ -0,0 +1,50 @@
+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));
diff --git a/target/linux/bcm27xx/patches-6.18/0857-drm-vc4-hdmi-Remove-duplicate-hotplug-helper-call.patch b/target/linux/bcm27xx/patches-6.18/0857-drm-vc4-hdmi-Remove-duplicate-hotplug-helper-call.patch
new file mode 100644 (file)
index 0000000..603cc31
--- /dev/null
@@ -0,0 +1,37 @@
+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
diff --git a/target/linux/bcm27xx/patches-6.18/0858-ASoC-rename-hifiberry_studio_dac8x.c-to-hifiberry_st.patch b/target/linux/bcm27xx/patches-6.18/0858-ASoC-rename-hifiberry_studio_dac8x.c-to-hifiberry_st.patch
new file mode 100644 (file)
index 0000000..1b11c59
--- /dev/null
@@ -0,0 +1,1935 @@
+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");
diff --git a/target/linux/bcm27xx/patches-6.18/0859-ASoC-hifiberry_studio-add-multi-card-and-Digi-AES-su.patch b/target/linux/bcm27xx/patches-6.18/0859-ASoC-hifiberry_studio-add-multi-card-and-Digi-AES-su.patch
new file mode 100644 (file)
index 0000000..0830003
--- /dev/null
@@ -0,0 +1,1184 @@
+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");
diff --git a/target/linux/bcm27xx/patches-6.18/0860-overlays-hifiberry-studio-add-Digi-AES-overlay-regis.patch b/target/linux/bcm27xx/patches-6.18/0860-overlays-hifiberry-studio-add-Digi-AES-overlay-regis.patch
new file mode 100644 (file)
index 0000000..15ce290
--- /dev/null
@@ -0,0 +1,222 @@
+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?";
++      };
++};
diff --git a/target/linux/bcm27xx/patches-6.18/0862-staging-vchiq_arm-use-one-DMA-device-to-map-and-unma.patch b/target/linux/bcm27xx/patches-6.18/0862-staging-vchiq_arm-use-one-DMA-device-to-map-and-unma.patch
new file mode 100644 (file)
index 0000000..a1a46fb
--- /dev/null
@@ -0,0 +1,78 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0864-mailbox-rp1-Don-t-poll-for-TX-completion.patch b/target/linux/bcm27xx/patches-6.18/0864-mailbox-rp1-Don-t-poll-for-TX-completion.patch
new file mode 100644 (file)
index 0000000..e1e69bf
--- /dev/null
@@ -0,0 +1,55 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0865-firmware-rp1-fw-Call-mbox_client_txdone-after-TX.patch b/target/linux/bcm27xx/patches-6.18/0865-firmware-rp1-fw-Call-mbox_client_txdone-after-TX.patch
new file mode 100644 (file)
index 0000000..15a6398
--- /dev/null
@@ -0,0 +1,30 @@
+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
diff --git a/target/linux/bcm27xx/patches-6.18/0866-drm-v3d-Reach-the-GMP-through-the-hub-registers-on-V.patch b/target/linux/bcm27xx/patches-6.18/0866-drm-v3d-Reach-the-GMP-through-the-hub-registers-on-V.patch
new file mode 100644 (file)
index 0000000..c10bf63
--- /dev/null
@@ -0,0 +1,47 @@
+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)) &
diff --git a/target/linux/bcm27xx/patches-6.18/0867-drm-v3d-Idle-AXI-transactions-before-disabling-the-c.patch b/target/linux/bcm27xx/patches-6.18/0867-drm-v3d-Idle-AXI-transactions-before-disabling-the-c.patch
new file mode 100644 (file)
index 0000000..2e28918
--- /dev/null
@@ -0,0 +1,85 @@
+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;
+       }
diff --git a/target/linux/bcm27xx/patches-6.18/0868-mmc-block-Fix-pending_writes-underflow-on-non-CQE-ho.patch b/target/linux/bcm27xx/patches-6.18/0868-mmc-block-Fix-pending_writes-underflow-on-non-CQE-ho.patch
new file mode 100644 (file)
index 0000000..1557108
--- /dev/null
@@ -0,0 +1,67 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0869-drm-vc4-hdmi-switch-to-generic-CEC-helpers.patch b/target/linux/bcm27xx/patches-6.18/0869-drm-vc4-hdmi-switch-to-generic-CEC-helpers.patch
new file mode 100644 (file)
index 0000000..3463125
--- /dev/null
@@ -0,0 +1,307 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0870-misc-rp1-pio-release-DMA-channel-outside-pio-lock.patch b/target/linux/bcm27xx/patches-6.18/0870-misc-rp1-pio-release-DMA-channel-outside-pio-lock.patch
new file mode 100644 (file)
index 0000000..17b69e6
--- /dev/null
@@ -0,0 +1,50 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0871-misc-rp1-pio-Reverse-christmas-tree-declarations.patch b/target/linux/bcm27xx/patches-6.18/0871-misc-rp1-pio-Reverse-christmas-tree-declarations.patch
new file mode 100644 (file)
index 0000000..2dd412d
--- /dev/null
@@ -0,0 +1,36 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0872-fixup-misc-rp1-pio-Reverse-christmas-tree-declaratio.patch b/target/linux/bcm27xx/patches-6.18/0872-fixup-misc-rp1-pio-Reverse-christmas-tree-declaratio.patch
new file mode 100644 (file)
index 0000000..50600dd
--- /dev/null
@@ -0,0 +1,22 @@
+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];
diff --git a/target/linux/bcm27xx/patches-6.18/0873-misc-rpi-pio-In-kernel-API-fixes.patch b/target/linux/bcm27xx/patches-6.18/0873-misc-rpi-pio-In-kernel-API-fixes.patch
new file mode 100644 (file)
index 0000000..79cc515
--- /dev/null
@@ -0,0 +1,107 @@
+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);
+ }
diff --git a/target/linux/bcm27xx/patches-6.18/0874-misc-rp1-pio-Add-sm_get_dmactrl-and-sm_get_flags.patch b/target/linux/bcm27xx/patches-6.18/0874-misc-rp1-pio-Add-sm_get_dmactrl-and-sm_get_flags.patch
new file mode 100644 (file)
index 0000000..62887a0
--- /dev/null
@@ -0,0 +1,242 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0875-misc-rp1-pio-Add-support-for-PIO-interrupts.patch b/target/linux/bcm27xx/patches-6.18/0875-misc-rp1-pio-Add-support-for-PIO-interrupts.patch
new file mode 100644 (file)
index 0000000..dc66003
--- /dev/null
@@ -0,0 +1,1101 @@
+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
diff --git a/target/linux/bcm27xx/patches-6.18/0876-arm64-dts-rp1-Declare-the-PIO-interrupts.patch b/target/linux/bcm27xx/patches-6.18/0876-arm64-dts-rp1-Declare-the-PIO-interrupts.patch
new file mode 100644 (file)
index 0000000..bb2db42
--- /dev/null
@@ -0,0 +1,23 @@
+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>,
diff --git a/target/linux/bcm27xx/patches-6.18/0877-misc-rp1-pio-Non-blocking-blocking-operations.patch b/target/linux/bcm27xx/patches-6.18/0877-misc-rp1-pio-Non-blocking-blocking-operations.patch
new file mode 100644 (file)
index 0000000..9094534
--- /dev/null
@@ -0,0 +1,143 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0878-serial-Add-rp1-pio-uart-a-PIO-based-UART.patch b/target/linux/bcm27xx/patches-6.18/0878-serial-Add-rp1-pio-uart-a-PIO-based-UART.patch
new file mode 100644 (file)
index 0000000..0435ee7
--- /dev/null
@@ -0,0 +1,882 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0880-overlays-Add-the-rp1-pio-uart-overlay.patch b/target/linux/bcm27xx/patches-6.18/0880-overlays-Add-the-rp1-pio-uart-overlay.patch
new file mode 100644 (file)
index 0000000..3ff19db
--- /dev/null
@@ -0,0 +1,73 @@
+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";
++      };
++};
diff --git a/target/linux/bcm27xx/patches-6.18/0881-overlays-Add-ramoops-pi5.patch b/target/linux/bcm27xx/patches-6.18/0881-overlays-Add-ramoops-pi5.patch
new file mode 100644 (file)
index 0000000..6948b5b
--- /dev/null
@@ -0,0 +1,100 @@
+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";
++      };
++};
diff --git a/target/linux/bcm27xx/patches-6.18/0882-overlays-apds9960-Add-missing-pinctrl-reference.patch b/target/linux/bcm27xx/patches-6.18/0882-overlays-apds9960-Add-missing-pinctrl-reference.patch
new file mode 100644 (file)
index 0000000..dfadaed
--- /dev/null
@@ -0,0 +1,27 @@
+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";
+                       };
+               };
diff --git a/target/linux/bcm27xx/patches-6.18/0883-input-goodix-Include-I2C-details-in-names-for-the-de.patch b/target/linux/bcm27xx/patches-6.18/0883-input-goodix-Include-I2C-details-in-names-for-the-de.patch
new file mode 100644 (file)
index 0000000..df65422
--- /dev/null
@@ -0,0 +1,52 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0884-nvme-host-pci-Revert-to-upstream.patch b/target/linux/bcm27xx/patches-6.18/0884-nvme-host-pci-Revert-to-upstream.patch
new file mode 100644 (file)
index 0000000..481bcb0
--- /dev/null
@@ -0,0 +1,158 @@
+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)
+ {
diff --git a/target/linux/bcm27xx/patches-6.18/0885-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch b/target/linux/bcm27xx/patches-6.18/0885-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch
new file mode 100644 (file)
index 0000000..0e84a0b
--- /dev/null
@@ -0,0 +1,144 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0886-Revert-nvme-host-pci-Revert-to-upstream.patch b/target/linux/bcm27xx/patches-6.18/0886-Revert-nvme-host-pci-Revert-to-upstream.patch
new file mode 100644 (file)
index 0000000..03bda9f
--- /dev/null
@@ -0,0 +1,148 @@
+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)
+ {
diff --git a/target/linux/bcm27xx/patches-6.18/0887-Revert-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch b/target/linux/bcm27xx/patches-6.18/0887-Revert-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch
new file mode 100644 (file)
index 0000000..0a8a6ae
--- /dev/null
@@ -0,0 +1,136 @@
+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;
diff --git a/target/linux/bcm27xx/patches-6.18/0888-ASoC-hifiberry-studio-add-AES-CS-Mode-control-channe.patch b/target/linux/bcm27xx/patches-6.18/0888-ASoC-hifiberry-studio-add-AES-CS-Mode-control-channe.patch
new file mode 100644 (file)
index 0000000..a6c481c
--- /dev/null
@@ -0,0 +1,73 @@
+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(
diff --git a/target/linux/bcm27xx/patches-6.18/0889-pinctrl-rp1-Make-IRQ-usage-set-a-pin-to-an-input.patch b/target/linux/bcm27xx/patches-6.18/0889-pinctrl-rp1-Make-IRQ-usage-set-a-pin-to-an-input.patch
new file mode 100644 (file)
index 0000000..4c82311
--- /dev/null
@@ -0,0 +1,88 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0890-drm-edid-Accept-3D_Detail_X-of-0000-for-side-by-side.patch b/target/linux/bcm27xx/patches-6.18/0890-drm-edid-Accept-3D_Detail_X-of-0000-for-side-by-side.patch
new file mode 100644 (file)
index 0000000..233bfaa
--- /dev/null
@@ -0,0 +1,47 @@
+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;
+               }
diff --git a/target/linux/bcm27xx/patches-6.18/0891-drm-display-hdmi-Account-for-frame-packing-in-the-TM.patch b/target/linux/bcm27xx/patches-6.18/0891-drm-display-hdmi-Account-for-frame-packing-in-the-TM.patch
new file mode 100644 (file)
index 0000000..1c393e7
--- /dev/null
@@ -0,0 +1,94 @@
+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),
diff --git a/target/linux/bcm27xx/patches-6.18/0892-drm-vc4-Derive-scanout-rate-and-frame-size-from-the-.patch b/target/linux/bcm27xx/patches-6.18/0892-drm-vc4-Derive-scanout-rate-and-frame-size-from-the-.patch
new file mode 100644 (file)
index 0000000..6036508
--- /dev/null
@@ -0,0 +1,130 @@
+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);
diff --git a/target/linux/bcm27xx/patches-6.18/0893-drm-vc4-Support-frame-packed-stereo-modes.patch b/target/linux/bcm27xx/patches-6.18/0893-drm-vc4-Support-frame-packed-stereo-modes.patch
new file mode 100644 (file)
index 0000000..f1b7dc3
--- /dev/null
@@ -0,0 +1,52 @@
+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;
+ }
diff --git a/target/linux/bcm27xx/patches-6.18/0894-drm-panel-waveshare-Add-6.25-DSI-TOUCH-A-panel-suppo.patch b/target/linux/bcm27xx/patches-6.18/0894-drm-panel-waveshare-Add-6.25-DSI-TOUCH-A-panel-suppo.patch
new file mode 100644 (file)
index 0000000..e9f5221
--- /dev/null
@@ -0,0 +1,137 @@
+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 },
diff --git a/target/linux/bcm27xx/patches-6.18/0895-input-touchscreen-Add-Sitronix-ST7123-support.patch b/target/linux/bcm27xx/patches-6.18/0895-input-touchscreen-Add-Sitronix-ST7123-support.patch
new file mode 100644 (file)
index 0000000..abfdd96
--- /dev/null
@@ -0,0 +1,572 @@
+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");
diff --git a/target/linux/bcm27xx/patches-6.18/0896-ARM-dts-overlays-waveshare-panel-v2-Add-6.25-DSI-TOU.patch b/target/linux/bcm27xx/patches-6.18/0896-ARM-dts-overlays-waveshare-panel-v2-Add-6.25-DSI-TOU.patch
new file mode 100644 (file)
index 0000000..67fc7f4
--- /dev/null
@@ -0,0 +1,48 @@
+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";
diff --git a/target/linux/bcm27xx/patches-6.18/0898-arm64-dts-rp1-Mux-SPI-chip-select-pins-as-GPIO.patch b/target/linux/bcm27xx/patches-6.18/0898-arm64-dts-rp1-Mux-SPI-chip-select-pins-as-GPIO.patch
new file mode 100644 (file)
index 0000000..6894f71
--- /dev/null
@@ -0,0 +1,35 @@
+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;
+                       };
diff --git a/target/linux/bcm27xx/patches-6.18/0899-overlays-Don-t-configure-PIO-pins-as-such.patch b/target/linux/bcm27xx/patches-6.18/0899-overlays-Don-t-configure-PIO-pins-as-such.patch
new file mode 100644 (file)
index 0000000..02812f2
--- /dev/null
@@ -0,0 +1,41 @@
+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;
+                       };
+               };
diff --git a/target/linux/bcm27xx/patches-6.18/0900-pinctrl-rp1-enable-strict-pinmux-mode.patch b/target/linux/bcm27xx/patches-6.18/0900-pinctrl-rp1-enable-strict-pinmux-mode.patch
new file mode 100644 (file)
index 0000000..135771f
--- /dev/null
@@ -0,0 +1,31 @@
+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)
diff --git a/target/linux/bcm27xx/patches-6.18/0901-pinctrl-rp1-Fix-node-leak-and-mapping-check-in-probe.patch b/target/linux/bcm27xx/patches-6.18/0901-pinctrl-rp1-Fix-node-leak-and-mapping-check-in-probe.patch
new file mode 100644 (file)
index 0000000..f9b865d
--- /dev/null
@@ -0,0 +1,30 @@
+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];
diff --git a/target/linux/bcm27xx/patches-6.18/0902-drm-vc4-kms-Reduce-firmware-clock-requests-during-at.patch b/target/linux/bcm27xx/patches-6.18/0902-drm-vc4-kms-Reduce-firmware-clock-requests-during-at.patch
new file mode 100644 (file)
index 0000000..843a377
--- /dev/null
@@ -0,0 +1,149 @@
+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);
+       }
+ }
diff --git a/target/linux/bcm27xx/patches-6.18/0903-pinctrl-bcm2835-Make-IRQ-usage-set-pin-to-input.patch b/target/linux/bcm27xx/patches-6.18/0903-pinctrl-bcm2835-Make-IRQ-usage-set-pin-to-input.patch
new file mode 100644 (file)
index 0000000..6fb13ce
--- /dev/null
@@ -0,0 +1,48 @@
+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,
+ };
diff --git a/target/linux/bcm27xx/patches-6.18/0904-pinctrl-bcm2835-Enable-strict-pinmux-mode.patch b/target/linux/bcm27xx/patches-6.18/0904-pinctrl-bcm2835-Enable-strict-pinmux-mode.patch
new file mode 100644 (file)
index 0000000..7726a41
--- /dev/null
@@ -0,0 +1,39 @@
+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,
similarity index 81%
rename from target/linux/bcm27xx/patches-6.18/0808-Post-Merge-Fixup-up-to-9df439fbf76c0cb9f1a9282a7bf44.patch
rename to target/linux/bcm27xx/patches-6.18/0905-Post-Merge-Fixup-up-to-825dba6c63eeb40a62699d1f8a4aa.patch
index 4aba84ac028a46525c6a3a48fe04bdae4781b695..e580a61b15be80248646208936560ca56289d84f 100644 (file)
@@ -1,8 +1,8 @@
 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
@@ -24,7 +24,8 @@ Signed-off-by: Joshua Covington <joshuacov@gmail.com>
  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
@@ -72,3 +73,16 @@ Signed-off-by: Joshua Covington <joshuacov@gmail.com>
 -}
 -
  #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);
+       }