From: Joshua Covington Date: Sun, 2 Aug 2026 12:03:42 +0000 (+0000) Subject: bcm27xx: update patches up to Git HEAD (2026-08-02) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f6973b130236bb2d57073f7c4b53ccea78649105;p=thirdparty%2Fopenwrt.git bcm27xx: update patches up to Git HEAD (2026-08-02) 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 Link: https://github.com/openwrt/openwrt/pull/24275 Signed-off-by: Jonas Jelonek --- 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 index 00000000000..ad1e8111b0f --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0808-overlays-draws-Fix-reset-gpios-polarity-for-gpiod-co.patch @@ -0,0 +1,43 @@ +From d29786f03ced61e908a6770ba6d1a010db8428b8 Mon Sep 17 00:00:00 2001 +From: Annaliese McDermond +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 +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 index 00000000000..7b1c191288a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0809-overlays-Add-draws-pi5-overlay-for-Raspberry-Pi-5.patch @@ -0,0 +1,287 @@ +From 836fa33251ba570d12b09e5ac16e1890c3c8d851 Mon Sep 17 00:00:00 2001 +From: Annaliese McDermond +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 +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: +--- /dev/null ++++ b/arch/arm/boot/dts/overlays/draws-pi5-overlay.dts +@@ -0,0 +1,210 @@ ++#include ++/* ++ * 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 index 00000000000..4c93bdb9a7a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0810-overlays-draws-Use-GPIO-flag-constants-for-the-reset.patch @@ -0,0 +1,76 @@ +From 6bd47b7487eaf0ac2dd5877eb92ab6d048d69b27 Mon Sep 17 00:00:00 2001 +From: Annaliese McDermond +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 +, 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 +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 ++#include + /* + * 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 ++#include + /* + * 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 index 00000000000..c650a049f0b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0811-overlays-sdhost-Add-gpios_22_27-parameter.patch @@ -0,0 +1,71 @@ +From 63598c83153e19b1f99067ab6df7409de2c111f8 Mon Sep 17 00:00:00 2001 +From: zjzhao +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 +--- + 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 index 00000000000..9eccc77fde6 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0812-brcmfmac-cyw-clean-up-PMKID-and-cookie-code.patch @@ -0,0 +1,66 @@ +From 05cd2866fcc6acda569f7f05fe283826b2b74d37 Mon Sep 17 00:00:00 2001 +From: Bogdan Nicolae +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 +--- + .../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 index 00000000000..3139c92793e --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0814-dt-bindings-media-i2c-Add-Sony-IMX355.patch @@ -0,0 +1,132 @@ +From 3dcf42f6a581cc4e0c0221b4f55488bb8c50e5e4 Mon Sep 17 00:00:00 2001 +From: Richard Acayan +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 +Reviewed-by: Krzysztof Kozlowski +Signed-off-by: Richard Acayan +--- + .../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 ++ ++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 ++ #include ++ ++ 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 index 00000000000..872ccd48526 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0815-media-i2c-imx355-Support-devicetree-and-power-manage.patch @@ -0,0 +1,204 @@ +From 3ca16b7d50e94b66c495c0e1787e459dccb37738 Mon Sep 17 00:00:00 2001 +From: Richard Acayan +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 +Reviewed-by: Vladimir Zapolskiy +Signed-off-by: Richard Acayan +--- + 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 + #include ++#include ++#include + #include + #include ++#include + #include ++#include + #include + + #include +@@ -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 index 00000000000..6eb5d67ef11 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0816-media-i2c-imx355-Restrict-data-lanes-to-4.patch @@ -0,0 +1,37 @@ +From 26f960af3458d96950e9bcdd872515677e2ba018 Mon Sep 17 00:00:00 2001 +From: Richard Acayan +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 +Link: https://lore.kernel.org/r/aW3uFcT1zmiF4GUP@kekkonen.localdomain +Signed-off-by: Richard Acayan +--- + 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 index 00000000000..66b66086ad1 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0817-media-i2c-imx355-Add-support-for-24-MHz-external-clo.patch @@ -0,0 +1,255 @@ +From a54bb3088f003309d7fb23cbcd899dea20b85053 Mon Sep 17 00:00:00 2001 +From: David Heidelberg +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..ae01d2d447a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0818-dt-bindings-media-imx355-Allow-2-CSI2-data-lane-outp.patch @@ -0,0 +1,35 @@ +From fcfd82a467057ba21ff5c5ab885489a9d4652486 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + .../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 index 00000000000..231ac461c21 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0819-media-imx355-Remove-duplicated-registers-from-the-mo.patch @@ -0,0 +1,638 @@ +From 34375d4e51697a1f41af9929044bc47bffe2df12 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..82808b9126a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0820-media-imx355-Remove-setting-FRM_LENGTH_LINES-in-the-.patch @@ -0,0 +1,144 @@ +From 122d3e8240d6d7093c7340abfc6123531f53206e Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..7e3bf0983ed --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0821-media-imx355-Programmatically-set-the-crop-parameter.patch @@ -0,0 +1,579 @@ +From 305b8900f5e9c4ab6b496f12cec05e26925be6e4 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..64e3ec2f301 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0822-media-imx355-Set-register-LINE_LENGTH_PCK-programmat.patch @@ -0,0 +1,168 @@ +From e63f2fe1091312857604a331f0ef240fae1acab4 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- +--- + 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 index 00000000000..715cd367d2d --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0823-media-imx355-Set-binning-mode-registers-programmatic.patch @@ -0,0 +1,169 @@ +From 89221a7aab3b55cb758d3057041a97452b35ae93 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..a16c8d9f341 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0824-media-imx355-Remove-link_freq_index-from-each-mode-a.patch @@ -0,0 +1,146 @@ +From 372407d326de01e1c0b66bc30bc3334a555b0366 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..f7e55396ac0 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0825-media-imx355-pixel_rate-never-changes-so-don-t-recom.patch @@ -0,0 +1,55 @@ +From 288ed1383e559ba8cf208a521dd798a9c158fa2f Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..ba06e9a7673 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0826-media-imx355-Remove-redundant-fll_min-and-implement-.patch @@ -0,0 +1,190 @@ +From 0aae4c0b2d05cf52bfd0c30ea2df15b58b67870c Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..7b7416579f4 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0827-media-imx355-Add-support-for-get_selection.patch @@ -0,0 +1,102 @@ +From b1ddfd26254c4872c0d1d65e14255210346fc229 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..86f678bcd24 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0828-media-imx355-Use-pm_runtime-autosuspend_delay.patch @@ -0,0 +1,43 @@ +From 7d8ba6e7ed032a9c2b7e7e2b0c812b02a439a2db Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +Signed-off-by: Dave Stevenson +--- + 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 index 00000000000..5ae2e49f521 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0829-media-imx355-Convert-to-new-CCI-register-access-help.patch @@ -0,0 +1,738 @@ +From a7a8620fe287ad5f9c9a8c1ad6c20657191e4f22 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 + #include + #include ++#include + #include + #include + ++#include + #include + #include + #include + #include + +-#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 index 00000000000..9ac081543fe --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0830-media-imx355-Set-the-colorspace-in-the-format.patch @@ -0,0 +1,26 @@ +From bae633e431b226499d495c3655b91bcd6d5fcb8c Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..f395449a902 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0831-media-imx355-Define-the-exposure-offset-and-use-that.patch @@ -0,0 +1,44 @@ +From 2b7901d08bc50d37acdef1c0101b62c7bb351bf9 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..b64873963d8 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0832-media-imx355-Use-NULL-ctrl_ops-for-HBLANK-as-it-is-a.patch @@ -0,0 +1,29 @@ +From 892ac5e6cd1f23a79be8dc914c6c61e37cec6598 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..349098c423f --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0833-media-imx355-Compute-link-frequency-from-PLL-setup.patch @@ -0,0 +1,142 @@ +From 7f3dda0cffae8934919d166d860378a6acf4549a Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..7ea1c370e70 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0834-media-imx355-Support-2-lane-readout.patch @@ -0,0 +1,197 @@ +From 99f59a309e185efab42f3a03e38b3d18d99f9b67 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..1ae27089d33 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0835-media-imx355-Switch-to-using-the-subdev-state.patch @@ -0,0 +1,386 @@ +From 85efb034b8e3fa95dbfa022b53e7db1dae38bed6 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..0f08ceeaed9 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0836-media-imx355-Remove-storing-cur_mode-in-the-state.patch @@ -0,0 +1,180 @@ +From 8f936ca8788a51b6ca75a8025de0d684e5841159 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..fa71a7db04b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0837-media-imx355-Make-V4L2_CID_HBLANK-controllable.patch @@ -0,0 +1,80 @@ +From 8a61264722a08914e2037a04bbd7b3fbcff77894 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..eeeca54ab2d --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0840-dtoverlays-Add-overlay-for-imx355-image-sensor.patch @@ -0,0 +1,184 @@ +From a96eaa637db7cbd4be8173024f7d75ead22c6882 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +Date: Wed, 15 Apr 2026 15:01:20 +0100 +Subject: [PATCH] dtoverlays: Add overlay for imx355 image sensor + +Signed-off-by: Dave Stevenson +--- + 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, ++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 ++ ++/{ ++ 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 index 00000000000..e7dbc351521 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0841-overlays-ed-ipc-Add-EDATEC-IPC-EXP-unified-board-ove.patch @@ -0,0 +1,373 @@ +From 256d6b4bc33527fae9967773b2a0d3b92e1bd000 Mon Sep 17 00:00:00 2001 +From: zjzhao +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 +--- + 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,= ++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 index 00000000000..4b444cccd49 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0842-Revert-media-hevc_d-Fix-height-rounding-for-old-sand.patch @@ -0,0 +1,31 @@ +From dc800a5027d2c4c52314396f1d1fd83cc455644d Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 index 00000000000..5c3d67c201c --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0843-Revert-media-hevc_d-Fix-old-style-bit_size-handling-.patch @@ -0,0 +1,66 @@ +From 33c5a591e4ea7e62aadab871a905e428cacea07f Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 index 00000000000..95aa6b7b7df --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0845-Revert-media-hevc_dec-Add-in-downstream-single-plana.patch @@ -0,0 +1,262 @@ +From b54d5f9068406d4544b7a7fba9ef3c533b795a79 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 index 00000000000..b91e4ab8170 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0846-hevc_d-Pick-the-fixes-from-the-latest-upstreaming-re.patch @@ -0,0 +1,784 @@ +From d384034e313ec945b5a612942b7f3f868254bbd6 Mon Sep 17 00:00:00 2001 +From: John Cox +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 +--- + .../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 + #include + #include +-#include + + #include + #include +@@ -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 ++//#include ++#include + #include + + #include +@@ -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 +-#include +-#include + #include +-#include +-#include +-#include +-#include + #include +-#include +-#include + + #include + #include +@@ -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 ++ + 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 + #include + #include + #include +@@ -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 index 00000000000..7da6e6061c2 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0848-media-hevc_dec-Add-in-downstream-single-planar-SAND-.patch @@ -0,0 +1,260 @@ +From 7136f2b7245f380ac9ae2b29fd556ca9567c6e1b Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + .../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 index 00000000000..3cc4f64bb31 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0849-overlays-i2c-fan-add-compatible-for-upstream-compati.patch @@ -0,0 +1,25 @@ +From 5cd170a0dfac8bce14b0259cb0bde50d846d34ae Mon Sep 17 00:00:00 2001 +From: Peter Robinson +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 +--- + 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 index 00000000000..98a12424719 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0850-dmaengine-dw-axi-dmac-Wait-for-runtime-resume.patch @@ -0,0 +1,53 @@ +From 10214efa1a114fc61131ff1e1240fd5a0f170803 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..952d837e204 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0851-mmc-don-t-reference-requests-after-issuing-them.patch @@ -0,0 +1,86 @@ +From 66d46640d0a54ea21c4fffd0575e2b071333348a Mon Sep 17 00:00:00 2001 +From: Yufeng Gao +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 +Signed-off-by: Jonathan Bell +Signed-off-by: Yufeng Gao +--- + 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 index 00000000000..3c847c6dc78 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0852-Revert-net-macb-gate-TX-stall-watchdog-on-netif_carr.patch @@ -0,0 +1,51 @@ +From d57db18eddb0398652b95e7bfbffdef55ce0be71 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..ccca036ad8b --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0853-Revert-net-macb-drop-destructive-ISR-read-use-IMR-ba.patch @@ -0,0 +1,62 @@ +From 86a1889a96337a1b601e5600f3f85e21d7f13ac9 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..a3b090bc6e6 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0854-Revert-net-macb-add-TX-stall-watchdog-as-defence-in-.patch @@ -0,0 +1,136 @@ +From 7f49766f02bbf12ff6459a775a9ccbb1e2087cc7 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..9bf20ff12ad --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0855-Revert-net-macb-re-check-ISR-after-IER-re-enable-in-.patch @@ -0,0 +1,50 @@ +From 560624d8e8947ad3bc6c0ade6f655535ab83df71 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..603cc31af3a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0857-drm-vc4-hdmi-Remove-duplicate-hotplug-helper-call.patch @@ -0,0 +1,37 @@ +From 75f7c2949bec170779266c94c72290f4218acbc3 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..1b11c590ece --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0858-ASoC-rename-hifiberry_studio_dac8x.c-to-hifiberry_st.patch @@ -0,0 +1,1935 @@ +From 99c9dcd72062c9d29b171004187cf8eaacbab4b8 Mon Sep 17 00:00:00 2001 +From: j-schambacher +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 +--- + 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 +- * +- * 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 +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-/* 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 "); +-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 ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 "); ++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 index 00000000000..0830003489e --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0859-ASoC-hifiberry_studio-add-multi-card-and-Digi-AES-su.patch @@ -0,0 +1,1184 @@ +From 8905174a971fe0f51efc9d39bb312c2d09874bf0 Mon Sep 17 00:00:00 2001 +From: j-schambacher +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 +--- + 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 + #include + #include ++#include ++#include ++#include ++#include ++#include + + /* 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 "); +-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 index 00000000000..15ce290cfb8 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0860-overlays-hifiberry-studio-add-Digi-AES-overlay-regis.patch @@ -0,0 +1,222 @@ +From b97835145ed1bf30b5c27fa5686956f70d915f55 Mon Sep 17 00:00:00 2001 +From: j-schambacher +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 +--- + 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: + + Name: hifiberry-studio-dac8x + Info: Configures the HifiBerry Studio DAC8x audio card +-Load: dtoverlay=hifiberry-studio-dac8x +-Params: ++Load: dtoverlay=hifiberry-studio-dac8x,= ++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: ++Load: dtoverlay=hifiberry-studio-dac8x-pro,= ++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,= ++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 ++#include ++#include ++ ++/ { ++ 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 index 00000000000..a1a46fb4dfd --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0862-staging-vchiq_arm-use-one-DMA-device-to-map-and-unma.patch @@ -0,0 +1,78 @@ +From 33cc967e07b0a5439fdcfdf942c904e00f4e794e Mon Sep 17 00:00:00 2001 +From: Rachmat Aditiya +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 +--- + .../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 index 00000000000..e1e69bfe895 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0864-mailbox-rp1-Don-t-poll-for-TX-completion.patch @@ -0,0 +1,55 @@ +From ead0a299761ea17229f93ef009d238a6c2779ad3 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..15a639856c9 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0865-firmware-rp1-fw-Call-mbox_client_txdone-after-TX.patch @@ -0,0 +1,30 @@ +From 636d6cb815a0b1fa272c6115b54003066fca8361 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..c10bf63fe82 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0866-drm-v3d-Reach-the-GMP-through-the-hub-registers-on-V.patch @@ -0,0 +1,47 @@ +From 9467e93508f005932e5b039ed33969cc82c9bbdb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ma=C3=ADra=20Canal?= +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 +--- + 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 index 00000000000..2e28918515a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0867-drm-v3d-Idle-AXI-transactions-before-disabling-the-c.patch @@ -0,0 +1,85 @@ +From fcb324363479844c8bed1649122b3bd01a657f24 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ma=C3=ADra=20Canal?= +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 +--- + 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 index 00000000000..1557108cd97 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0868-mmc-block-Fix-pending_writes-underflow-on-non-CQE-ho.patch @@ -0,0 +1,67 @@ +From eacf886641f744bb0ba4afdb26bccb2de86fb7e7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ma=C3=ADra=20Canal?= +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 +--- + 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 index 00000000000..34631255360 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0869-drm-vc4-hdmi-switch-to-generic-CEC-helpers.patch @@ -0,0 +1,307 @@ +From dd42592103ea9dc5ced6fd761bf476820e1977af Mon Sep 17 00:00:00 2001 +From: Dmitry Baryshkov +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 +Reviewed-by: Maxime Ripard +Tested-by: Dave Stevenson +Link: https://lore.kernel.org/r/20250705-drm-hdmi-connector-cec-v7-1-d14fa0c31b74@oss.qualcomm.com +Signed-off-by: Dmitry Baryshkov +--- + 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 ++#include + #include + #include + #include +@@ -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 index 00000000000..17b69e665ce --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0870-misc-rp1-pio-release-DMA-channel-outside-pio-lock.patch @@ -0,0 +1,50 @@ +From 77f88e9e508a7dea34d6f5a8e1cf9034a9bae5a9 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..2dd412dc1f2 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0871-misc-rp1-pio-Reverse-christmas-tree-declarations.patch @@ -0,0 +1,36 @@ +From 6f6c00a7b67bd0f45cc8631618b9e9606b0bace6 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..50600dd0053 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0872-fixup-misc-rp1-pio-Reverse-christmas-tree-declaratio.patch @@ -0,0 +1,22 @@ +From bb81a9b70e0a34350dd1b1d94e61e8368539344d Mon Sep 17 00:00:00 2001 +From: Phil Elwell +Date: Wed, 22 Jul 2026 10:28:29 +0100 +Subject: [PATCH] fixup! misc: rp1-pio: Reverse christmas tree declarations + +Signed-off-by: Phil Elwell +--- + 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 index 00000000000..79cc515f6fe --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0873-misc-rpi-pio-In-kernel-API-fixes.patch @@ -0,0 +1,107 @@ +From bcca3ae088c51f39b8e1adcdbfbeedaa24d4e097 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..62887a06d30 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0874-misc-rp1-pio-Add-sm_get_dmactrl-and-sm_get_flags.patch @@ -0,0 +1,242 @@ +From 9c9fe968ff32089dad1ce3fac372fc1bf16ea08d Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..dc66003e3e2 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0875-misc-rp1-pio-Add-support-for-PIO-interrupts.patch @@ -0,0 +1,1101 @@ +From ee9c2a4007e551c86ec55379b01af5abd5f7c1d7 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -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 + +-#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 index 00000000000..bb2db42ce2f --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0876-arm64-dts-rp1-Declare-the-PIO-interrupts.patch @@ -0,0 +1,23 @@ +From 202d26a6587cdd40814ef99d855e93f5ee2f0d3b Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 = , ++ ; + 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 index 00000000000..9094534ad93 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0877-misc-rp1-pio-Non-blocking-blocking-operations.patch @@ -0,0 +1,143 @@ +From bacfab828e6aa981fca1200c0bff311961764076 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 + #include ++#include + #include + #include + #include +@@ -28,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -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 index 00000000000..0435ee7100a --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0878-serial-Add-rp1-pio-uart-a-PIO-based-UART.patch @@ -0,0 +1,882 @@ +From abf7b07bc88b384342402eecc2d59febe0e79ebf Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 index 00000000000..3ff19dbbf18 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0880-overlays-Add-the-rp1-pio-uart-overlay.patch @@ -0,0 +1,73 @@ +From 60ea684a8ace97bb0db1a16e20753bdd6ab371ff Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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,= ++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 index 00000000000..6948b5bbc16 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0881-overlays-Add-ramoops-pi5.patch @@ -0,0 +1,100 @@ +From 33bb14b06b3fb5a682d4a7a3db3963fe558fc6f9 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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,= +-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 index 00000000000..dfadaed0c05 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0882-overlays-apds9960-Add-missing-pinctrl-reference.patch @@ -0,0 +1,27 @@ +From 43de7f5e9c900cf9e2464d2e8bdd9bce12c5b576 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..df654225de1 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0883-input-goodix-Include-I2C-details-in-names-for-the-de.patch @@ -0,0 +1,52 @@ +From 8307cc8d4cf84a24cf12f5b71c11f1da87a55688 Mon Sep 17 00:00:00 2001 +From: Dave Stevenson +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 +--- + 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 index 00000000000..481bcb06f39 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0884-nvme-host-pci-Revert-to-upstream.patch @@ -0,0 +1,158 @@ +From 32d8cce2dd2094e8bfefa982744695742617f2c2 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 index 00000000000..0e84a0b8598 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0885-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch @@ -0,0 +1,144 @@ +From c6759b17fdcca5d08b5ae4c17ca5d1df0316d686 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 index 00000000000..03bda9f8e69 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0886-Revert-nvme-host-pci-Revert-to-upstream.patch @@ -0,0 +1,148 @@ +From 2cb0c4b8f629710c05ed47fcce8c3f6841240c47 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 index 00000000000..0a8a6aec8fb --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0887-Revert-mmc-host-sdhci-of-dwcmshc-Revert-to-upstream.patch @@ -0,0 +1,136 @@ +From 53a153146333c85f5f1ffb67578c293198fdf771 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 index 00000000000..a6c481c93c3 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0888-ASoC-hifiberry-studio-add-AES-CS-Mode-control-channe.patch @@ -0,0 +1,73 @@ +From 58792fcece9eeaaedcb25a9816ddbbfad4f386b3 Mon Sep 17 00:00:00 2001 +From: Daniel Matuschek +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 +--- + 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 index 00000000000..4c82311d0fb --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0889-pinctrl-rp1-Make-IRQ-usage-set-a-pin-to-an-input.patch @@ -0,0 +1,88 @@ +From 1f128d5a97120f7fa011250ec30227da020c89da Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +Co-authored-by: Nicolai Buchwitz +--- + 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 index 00000000000..233bfaab06c --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0890-drm-edid-Accept-3D_Detail_X-of-0000-for-side-by-side.patch @@ -0,0 +1,47 @@ +From 76dafcc81a158892910b8ca14fa7148e0034a5a3 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..1c393e7e7b0 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0891-drm-display-hdmi-Account-for-frame-packing-in-the-TM.patch @@ -0,0 +1,94 @@ +From 9732b3393d1fc0990a6a5b5417cdba9a5dd6c5d6 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..60365080403 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0892-drm-vc4-Derive-scanout-rate-and-frame-size-from-the-.patch @@ -0,0 +1,130 @@ +From ebd2b267e575b26d135b8b3f0d7ee37e215d625e Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..f1b7dc3986c --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0893-drm-vc4-Support-frame-packed-stereo-modes.patch @@ -0,0 +1,52 @@ +From 741426f4bc591beb37116a6d97d422a2d4f988b6 Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..e9f52212e69 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0894-drm-panel-waveshare-Add-6.25-DSI-TOUCH-A-panel-suppo.patch @@ -0,0 +1,137 @@ +From 9f851ff5267aaf19d0979c6b46dece5aaa27cb2d Mon Sep 17 00:00:00 2001 +From: Waveshare_Team +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 +--- + .../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 index 00000000000..abfdd96ec74 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0895-input-touchscreen-Add-Sitronix-ST7123-support.patch @@ -0,0 +1,572 @@ +From 6d4adb6bfea64581dff1c670a17333b63b8ba05d Mon Sep 17 00:00:00 2001 +From: Waveshare_Team +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 +--- + 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 ++L: linux-input@vger.kernel.org ++S: Maintained ++F: drivers/input/touchscreen/st7123.c ++ + SL28 CPLD MFD DRIVER + M: Michael Walle + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 index 00000000000..67fc7f47987 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0896-ARM-dts-overlays-waveshare-panel-v2-Add-6.25-DSI-TOU.patch @@ -0,0 +1,48 @@ +From 97761fa3b27cc6cfb8638b0a88f5e5b14d2403fa Mon Sep 17 00:00:00 2001 +From: Waveshare_Team +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 +--- + 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 index 00000000000..6894f711cbc --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0898-arm64-dts-rp1-Mux-SPI-chip-select-pins-as-GPIO.patch @@ -0,0 +1,35 @@ +From 887ceda573d90ddd4c1eb1f5c746a91a669e071f Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..02812f25312 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0899-overlays-Don-t-configure-PIO-pins-as-such.patch @@ -0,0 +1,41 @@ +From aa16cd82538befcb1230239728a8dc1ce9593c93 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..135771f22c8 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0900-pinctrl-rp1-enable-strict-pinmux-mode.patch @@ -0,0 +1,31 @@ +From 11c58b576f5f8b1f1bdd562eb159ed5212e29414 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..f9b865d0ac6 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0901-pinctrl-rp1-Fix-node-leak-and-mapping-check-in-probe.patch @@ -0,0 +1,30 @@ +From 95f42ede8c6ce3a552c20872d7bb4fab57298629 Mon Sep 17 00:00:00 2001 +From: Nicolai Buchwitz +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 +--- + 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 index 00000000000..843a3770673 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0902-drm-vc4-kms-Reduce-firmware-clock-requests-during-at.patch @@ -0,0 +1,149 @@ +From b0f70212b2fe409082cc0046f117d7116b2a66bf Mon Sep 17 00:00:00 2001 +From: Dom Cobley +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 +--- + 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 index 00000000000..6fb13ce2f8f --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0903-pinctrl-bcm2835-Make-IRQ-usage-set-pin-to-input.patch @@ -0,0 +1,48 @@ +From a9b26ac27e210dc851c57befc9537c65de6eac6b Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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 index 00000000000..7726a412063 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.18/0904-pinctrl-bcm2835-Enable-strict-pinmux-mode.patch @@ -0,0 +1,39 @@ +From 825dba6c63eeb40a62699d1f8a4aa3f02b0eaf49 Mon Sep 17 00:00:00 2001 +From: Phil Elwell +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 +--- + 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, diff --git a/target/linux/bcm27xx/patches-6.18/0808-Post-Merge-Fixup-up-to-9df439fbf76c0cb9f1a9282a7bf44.patch b/target/linux/bcm27xx/patches-6.18/0905-Post-Merge-Fixup-up-to-825dba6c63eeb40a62699d1f8a4aa.patch 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 4aba84ac028..e580a61b15b 100644 --- a/target/linux/bcm27xx/patches-6.18/0808-Post-Merge-Fixup-up-to-9df439fbf76c0cb9f1a9282a7bf44.patch +++ b/target/linux/bcm27xx/patches-6.18/0905-Post-Merge-Fixup-up-to-825dba6c63eeb40a62699d1f8a4aa.patch @@ -1,8 +1,8 @@ From 532a3c77ce735fae7f7155cae5fbda768120fc1e Mon Sep 17 00:00:00 2001 From: Joshua Covington -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 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 -} - #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); + }