diff --git a/arch/arm/boot/dts/samsung/Makefile b/arch/arm/boot/dts/samsung/Makefile index 31eb090c63f2c8..83b5c1e22f3a7c 100644 --- a/arch/arm/boot/dts/samsung/Makefile +++ b/arch/arm/boot/dts/samsung/Makefile @@ -50,7 +50,8 @@ dtb-$(CONFIG_ARCH_S3C64XX) += \ dtb-$(CONFIG_ARCH_S5L87XX) += \ s5l8702-n46.dtb \ s5l8723-n20.dtb \ - s5l8740-n31.dtb + s5l8740-n31.dtb \ + s5l8740-n31-nodrm.dtb dtb-$(CONFIG_ARCH_S5PV210) += \ s5pv210-aquila.dtb \ s5pv210-fascinate4g.dtb \ diff --git a/arch/arm/boot/dts/samsung/s5l8702-n46.dts b/arch/arm/boot/dts/samsung/s5l8702-n46.dts index 1e1a38e7efca8e..be304daf434a85 100644 --- a/arch/arm/boot/dts/samsung/s5l8702-n46.dts +++ b/arch/arm/boot/dts/samsung/s5l8702-n46.dts @@ -91,7 +91,7 @@ }; usbotg: usb@38400000 { - compatible = "apple,s5l87xx-usb"; + compatible = "apple,s5l8702-usb", "apple,s5l87xx-usb"; reg = <0x38400000 0x40000>; interrupt-parent = <&vic0>; interrupts = <19>; diff --git a/arch/arm/boot/dts/samsung/s5l8740-n31-nodrm.dts b/arch/arm/boot/dts/samsung/s5l8740-n31-nodrm.dts new file mode 100755 index 00000000000000..6d026f9159ed31 --- /dev/null +++ b/arch/arm/boot/dts/samsung/s5l8740-n31-nodrm.dts @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Apple S5L8740 SoC + * Board name: N31 + * Product name: iPod nano (7th generation) + */ +/dts-v1/; + +#include +#include +#include +#include + +/ { + #address-cells = <1>; + #size-cells = <1>; + model = "Apple iPod nano (7th generation)"; + compatible = "apple,n31", "samsung,s5l8740"; + + aliases { + serial0 = &uart3; + console = &uart3; + }; + + chosen { + /* quiet: suppress non-critical boot spam; grape uses explicit prints */ + bootargs = "panic=-1 loglevel=8 nohlt"; + stdout-path = "serial0"; + }; + + nclk: external_clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "nclk"; + }; + + // pclk: clock-ref { + // compatible = "fixed-clock"; + // #clock-cells = <0>; + // clock-frequency = <6000000>; + // clock-output-names = "pclk"; + // }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + reg = <0>; + compatible = "arm,cortex-a5"; + }; + }; + + memory@8000000 { + device_type = "memory"; + reg = <0x08000000 0x04000000>; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + vic0: interrupt-controller@38e00000 { + compatible = "arm,pl192-vic"; + interrupt-controller; + reg = <0x38e00000 0x1000>; + #interrupt-cells = <1>; + }; + + vic1: interrupt-controller@38e01000 { + compatible = "arm,pl192-vic"; + interrupt-controller; + reg = <0x38e01000 0x1000>; + #interrupt-cells = <1>; + }; + + clkctrl: clock-controller@3c500000 { + compatible = "samsung,s5l8740-clock", "samsung,s5l8702-clock"; + reg = <0x3c500000 0x100>; + #clock-cells = <1>; + /* boot-minimal: WTF already ungated clocks — skip SEC mass reprogram */ + apple,skip-clkcon-ensure; + apple,skip-clkcon-bringup; + status = "okay"; + }; + eic: interrupt-controller@39700000 { + compatible = "apple,s5l8740-eic", "samsung,s5l8740-eic"; + reg = <0x39700000 0x1000>; + interrupt-controller; + #interrupt-cells = <2>; + /* + * Chain ONLY VIC EXT1 (group1 / GPIOs 32-63): Vol 40/41, Grape 38. + * Chaining all EXT0-6 hung boot previously. + */ + interrupt-parent = <&vic0>; + interrupts = <1>; /* EXT1 */ + /* boot-minimal v2: no EIC chain (IRQ storm risk without Grape) */ + status = "disabled"; + }; + + timer: timer@3c700000 { + compatible = "samsung,s5l8720-timer"; + reg = <0x3c700000 0x20000>; + interrupt-parent = <&vic0>; + interrupts = <7>; + }; + + /* Probe lcdif early — before SPI/Grape (boot-minimal bisect) */ + lcdif: lcdif@38300000 { + compatible = "samsung,s5l8740-lcdif"; + reg = <0x38300000 0x10000>; + status = "disabled"; + }; + + backlight: backlight@3e000000 { + compatible = "apple,s5l8740-backlight", "samsung,s5l8740-backlight"; + reg = <0x3e000000 0x100>; + default-brightness = <62>; + status = "disabled"; + }; + + /* if this one is not first, pointers get overwritten + in the driver and it fails to initialize */ + uart3: serial@3dd00000 { + compatible = "apple,s5l-uart"; + reg = <0x3dd00000 0x3c>; + clocks = <&nclk>, <&nclk>; + clock-names = "uart", "clk_uart_baud0"; + reg-io-width = <4>; + interrupt-parent = <&vic0>; + interrupts = <27>; + status = "okay"; + }; + + uart0: serial@3cc00000 { + compatible = "apple,s5l-uart"; + reg = <0x3cc00000 0x3c>; + clocks = <&nclk>, <&nclk>; + clock-names = "uart", "clk_uart_baud0"; + reg-io-width = <4>; + interrupt-parent = <&vic0>; + interrupts = <24>; + status = "okay"; + }; + + uart1: serial@3db00000 { + compatible = "apple,s5l-uart"; + reg = <0x3db00000 0x3c>; + clocks = <&nclk>, <&nclk>; + clock-names = "uart", "clk_uart_baud0"; + reg-io-width = <4>; + interrupt-parent = <&vic0>; + interrupts = <25>; + status = "okay"; + + /* BCM2078KUBG on UART1 @115200 — Vincent CodePatches → BCM2076B1.hcd */ + bluetooth { + compatible = "brcm,bcm2078", "brcm,bcm4329-bt"; + max-speed = <115200>; + shutdown-gpios = <&gpio 97 GPIO_ACTIVE_LOW>; + device-wakeup-gpios = <&gpio 98 GPIO_ACTIVE_HIGH>; + host-wakeup-gpios = <&gpio 119 GPIO_ACTIVE_HIGH>; + firmware-name = "brcm/BCM2076B1.hcd"; + /* Boot-safe: defer BCM until init up — see bcm2078-bt.c */ + status = "disabled"; + }; + }; + + uart2: serial@3dc00000 { + compatible = "apple,s5l-uart"; + reg = <0x3dc00000 0x3c>; + clocks = <&nclk>, <&nclk>; + clock-names = "uart", "clk_uart_baud0"; + reg-io-width = <4>; + interrupt-parent = <&vic0>; + interrupts = <26>; + status = "okay"; + + mikeybus { + compatible = "apple,mikeybus", "apple,n31-mikeybus"; + current-speed = <115200>; + }; + }; + + usbphy: usbphy@3c400000 { + /* N31 uses s5l87xx PHY sequence (NOT Nano3 8702 ramp) */ + compatible = "apple,s5l8740-otgphy", "apple,s5l87xx-otgphy"; + reg = <0x3c400000 0x100>; + status = "disabled"; + #phy-cells = <0>; + }; + + usbotg_hs: usb@38400000 { + compatible = "apple,s5l8740-usb", "apple,s5l87xx-usb"; + reg = <0x38400000 0x40000>; + interrupt-parent = <&vic0>; + interrupts = <19>; + phys = <&usbphy>; + phy-names = "usb2-phy"; + clocks = <&clkctrl CLK_USBOTG>, <&clkctrl CLK_USBPHY>, <&nclk>; + clock-names = "otg", "phy", "ref"; + /* rx=256 + np-tx=256 leaves room for periodic TX FIFOs (536+256 overflowed) */ + g-rx-fifo-size = <256>; + g-np-tx-fifo-size = <256>; + status = "disabled"; + dr_mode = "peripheral"; + }; + + + spi2: spi@3d200000 { + /* N31 Grape on SPI2 — CLK_SPI2 (+ alt/secondary) + pinmux in driver */ + compatible = "apple,s5l8702-spi", "samsung,s5l8740-spi", "samsung,s5l8702-spi"; + reg = <0x3d200000 0x100>; + clocks = <&clkctrl CLK_SPI2>, <&clkctrl CLK_SPI2_2>, <&clkctrl CLK_SPI2_ALT>; + clock-names = "spi", "spi-2", "spi-alt"; + /* SPI DMA peri IDs OPEN in N31 RE — Grape uses proven PIO path */ + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + grape: touchscreen@0 { + compatible = "apple,grape"; + reg = <0>; + spi-max-frequency = <1000000>; + enable-gpios = <&gpio 14 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio 39 GPIO_ACTIVE_LOW>; + attn-gpios = <&gpio 38 GPIO_ACTIVE_LOW>; + /* GPIO38 → EIC group1 → VIC EXT1 */ + interrupts-extended = <&eic 38 IRQ_TYPE_LEVEL_LOW>; + status = "disabled"; + }; + }; + + /* + * SPI0 @0x3C300000: panel in IpodSec, CS42 in RetailOS. + * Keep disabled so LCD SPI ownership is undisturbed; enable only + * after panel/CS42 mux is sequenced. + */ + /* SPI0: CS42 control. Panel pixels are LCDIF@383 — not this bus. */ + spi0: spi@3c300000 { + compatible = "apple,s5l8702-spi", "samsung,s5l8740-spi", "samsung,s5l8702-spi"; + reg = <0x3c300000 0x100>; + clocks = <&clkctrl CLK_SPI0>, <&clkctrl CLK_SPI0_2>; + clock-names = "spi", "spi-2"; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + cs42l81: codec@0 { + compatible = "cirrus,cs42l81", "apple,338s1146"; + reg = <0>; + spi-max-frequency = <1000000>; + status = "okay"; + }; + }; + + i2s0: i2s@3ca00000 { + compatible = "apple,s5l8740-i2s", "samsung,s5l8740-i2s"; + reg = <0x3ca00000 0x1000>; + clocks = <&clkctrl CLK_I2S0>, <&clkctrl CLK_CG16_9>; + clock-names = "i2s", "cg16"; + dmas = <&dmac 12 0>, <&dmac 13 0>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; + status = "disabled"; + }; + + i2s2: i2s@3d400000 { + compatible = "apple,s5l8740-iis2"; + reg = <0x3d400000 0x1000>; + clocks = <&clkctrl CLK_I2S2>, <&clkctrl CLK_CG16_11>; + clock-names = "i2s", "cg16"; + dmas = <&dmac 16 0>, <&dmac 17 0>; + dma-names = "tx", "rx"; + status = "disabled"; + }; + + nano7_audio: audio { + compatible = "apple,n31-audio"; + apple,cpu = <&i2s0>; + status = "disabled"; + }; + + wdt: watchdog@3c800000 { + compatible = "apple,s5l8740-syscon", "syscon", "simple-mfd"; + reg = <0x3c800000 0x8>; + + reboot: syscon-reboot@3c800000 { + compatible = "syscon-reboot"; + offset = <0x0>; + value = <0x100000>; + }; + }; + + /* + * Full S5L8740 banked GPIO (was: 2-line bcm6345 hack at 0x3cf000a4). + * Keep label gpio5 disabled so old phandles fail closed if any remain. + */ + gpio5: gpio-hack@3cf000a4 { + compatible = "brcm,bcm6345-gpio"; + reg-names = "dat"; + reg = <0x3cf000a4 0x4>; + #gpio-cells = <2>; + gpio-controller; + ngpios = <2>; + status = "disabled"; + }; + + gpio: gpio@3cf00000 { + compatible = "apple,s5l8740-gpio", "samsung,s5l8740-gpio"; + reg = <0x3cf00000 0x400>; + clocks = <&clkctrl CLK_GPIO>; + clock-names = "gpio"; + apple,eic = <&eic>; + /* boot-minimal: skip SEC sub_223C mass pinmux (WTF handoff) */ + apple,skip-sec-pinmux; + #gpio-cells = <2>; + gpio-controller; + ngpios = <128>; /* BT host-wake GPIO 119 */ + status = "disabled"; + }; + + i2c0: i2c@3c600000 { + compatible = "samsung,s5l8702-i2c"; + samsung,write-busy-poll; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x3c600000 0x100>; + clocks = <&clkctrl CLK_I2C0>, <&clkctrl CLK_I2C0_2>; + clock-names = "i2c", "i2c-2"; + clock-frequency = <100000>; + interrupt-parent = <&vic0>; + interrupts = <21>; + status = "disabled"; + + /* + * Tristar CBTL1609A1 — public "0x34 write / 0x35 read" is 8-bit; + * Linux DT 7-bit address is 0x1a. + * RetailOS RE: zero Dx/mux register writes observed — dump stays + * flat until accessory; only apple,init-sequence may write. + */ + tristar: lightning-mux@1a { + compatible = "apple,tristar-cbtl1609", "nxp,cbtl1609a1", + "apple,n31-tristar"; + reg = <0x1a>; + /* Parent i2c0 is disabled in this DTB; child cannot probe. */ + status = "okay"; + }; + }; + + i2c1: i2c@3c900000 { + compatible = "samsung,s5l8702-i2c"; + samsung,write-busy-poll; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x3c900000 0x100>; + clocks = <&clkctrl CLK_I2C1>, <&clkctrl CLK_I2C1_2>; + clock-names = "i2c", "i2c-2"; + clock-frequency = <100000>; /* UPDATE ME */ + interrupt-parent = <&vic0>; + interrupts = <22>; + status = "disabled"; + + /* ST lis3lv02d binding; mount-matrix optional (skip until board orient known) */ + lis3dc: lis331dlh@18 { + compatible = "st,lis3lv02d"; + reg = <0x18>; + /* Identity until board orientation proven on HW */ + mount-matrix = "1", "0", "0", + "0", "1", "0", + "0", "0", "1"; + status = "okay"; + }; + + /* Duplicate of i2c0 tristar@1a — keep disabled; primary is i2c0 only */ + tristar_i2c1: lightning-mux@1a { + compatible = "apple,tristar-cbtl1609", "nxp,cbtl1609a1"; + reg = <0x1a>; + status = "disabled"; + }; + /* Also try literal 7-bit 0x34 in case public notes meant that */ + /* 0x34 is 8-bit write addr form — NOT a Linux 7-bit address */ + tristar_i2c1_34: lightning-mux@34 { + compatible = "apple,tristar-cbtl1609", "nxp,cbtl1609a1"; + reg = <0x34>; + status = "disabled"; + }; + + d1830: pmic@73 { + compatible = "dlg,d1830-gpio"; + reg = <0x73>; + gpio-controller; + #gpio-cells = <2>; + /* N31: no Home/Play. Sleep/Wake is PMIC — map TBD. */ + dlg,gpio-map = <0x07 5>; /* SEC sub_27F4: Sleep/Wake status bit5 */ + }; + }; + + sha1: sha1@38000000 { + compatible = "samsung,s5l8702-sha1"; + reg = <0x38000000 0x100>; + clocks = <&clkctrl CLK_SHA1>; + clock-names = "sha1"; + status = "disabled"; + }; + + /* + * PL080 DMAC0/1 — OSOS pairs 0x38200000 + 0x38700000. + * NOT 0x384 (DWC OTG). Peri IDs: N31 RE IIS0 12/13, IIS2 16/17. + */ + dmac: dma-controller@38200000 { + compatible = "apple,s5l8740-pl080", "arm,pl080"; + reg = <0x38200000 0x1000>, <0x38700000 0x1000>; + clocks = <&clkctrl CLK_DMAC0>, <&clkctrl CLK_DMAC1>; + clock-names = "dmac0", "dmac1"; + interrupt-parent = <&vic0>; + interrupts = <16>, <17>; + #dma-cells = <2>; + status = "disabled"; + }; + + aes: aes@38c00000 { + compatible = "samsung,s5l8702-aes"; + reg = <0x38c00000 0x100>; + clocks = <&clkctrl CLK_AES>; + clock-names = "aes"; + status = "disabled"; + }; + + prng: prng@3c100000 { + compatible = "samsung,s5l8702-prng"; + reg = <0x3c100000 0x100>; + clocks = <&clkctrl CLK_PRNG>; + clock-names = "prng"; + status = "disabled"; + }; + }; + gpio-keys { + compatible = "gpio-keys"; + status = "disabled"; + + button-power { + label = "Sleep/Wake"; + gpios = <&d1830 0 GPIO_ACTIVE_LOW>; + linux,code = ; + debounce-interval = <50>; + wakeup-source; + status = "okay"; + }; + + /* Active-low CONFIRMED; Vol+ = GPIO40 provisional (swap with 41 if inverted) */ + button-volup { + label = "Volume Up"; + gpios = <&gpio 40 GPIO_ACTIVE_LOW>; + linux,code = ; + debounce-interval = <30>; + status = "okay"; + }; + + button-voldown { + label = "Volume Down"; + gpios = <&gpio 41 GPIO_ACTIVE_LOW>; + linux,code = ; + debounce-interval = <30>; + status = "okay"; + }; + }; +}; diff --git a/arch/arm/boot/dts/samsung/s5l8740-n31.dts b/arch/arm/boot/dts/samsung/s5l8740-n31.dts old mode 100644 new mode 100755 index 54d0e34aab25d8..9f5836a6d8e9c1 --- a/arch/arm/boot/dts/samsung/s5l8740-n31.dts +++ b/arch/arm/boot/dts/samsung/s5l8740-n31.dts @@ -9,6 +9,7 @@ #include #include #include +#include / { #address-cells = <1>; @@ -22,7 +23,14 @@ }; chosen { + /* U-Boot CONFIG_BOOTARGS overrides this. g_ether is built-in (0525:a4a2). */ + bootargs = "console=tty0 fbcon=font:MINI4x6 earlyprintk nohlt panic=0 clk_ignore_unused init=/init netconsole=6666@192.168.7.2/,6666@192.168.7.1/"; stdout-path = "serial0"; + /* + * U-Boot ft_board_setup adds apple,n31-touch_cal-addr / apple,n31-touch_cal-size + * pointing at a reserved DRAM copy of the A34 touch calibration object (0x560). + * Do not consume the original 0x2202FE1C pointer from Linux. + */ }; nclk: external_clock { @@ -55,6 +63,89 @@ reg = <0x08000000 0x04000000>; }; + /* + * U-Boot copies the 0x560 A34 touch calibration object here (gap below DFU load, + * above the 16 MiB bootm window). Chosen apple,n31-touch_cal-* is filled + * at ft_board_setup; this node keeps Linux from recycling the page. + */ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + n31-touch_cal@9dff000 { + compatible = "apple,n31-touch_cal"; + reg = <0x09DFF000 0x1000>; + no-map; + }; + + /* + * Audio DMA buffer in SRAM, where stock keeps it. + * + * Measured on RetailOS with music playing: PL080 ch2 runs + * src=0x220025d0 -> dst=0x3ca00010, i.e. the source is SRAM, not + * DRAM. Ours was in DRAM at 0x095c0020. + * + * A second read, taken with playback PAUSED and the screen off, + * showed src at 0x22002868 -- still inside SRAM, and with + * EnbldChns still 4, Config still 1 and ctl/cfg unchanged. The + * two reads bracket a stretch of playback, so they establish that + * the source advances through SRAM; they do NOT show it moving at + * the instant of the second read, because it was paused by then. + * What the paused read does show is that stock leaves the channel + * ENABLED across a pause, which is consistent with the + * self-linked LLI never terminating. + * + * SRAM is 0x22000000..0x2202FFFF (192 KiB; the bootloader + * descriptor at file offset 0x4BDC gives 0x00030000). + * + * This pool sits at 0x22002000, i.e. where stock puts its buffer + * (measured src 0x220025d0 and 0x22002868 across two reads). It + * was briefly at 0x22020000 -- the TOP of SRAM -- chosen to avoid + * the resident code seen in the RetailOS SRAM dump. That was a + * guess dressed as caution: the resident code is RetailOS's, and + * with the buffer up there the PL080 moved ZERO bytes (c2 stayed + * 0x800, src advanced only the 16-byte prefetch), where the same + * channel from DRAM had at least moved one 32-byte burst. Whether + * the top of SRAM is even reachable by the DMA is not + * established, and there is no reason to find out: stock uses low + * SRAM, so we use low SRAM. + */ + i2s_sram: audio-dma@22002000 { + compatible = "shared-dma-pool"; + reg = <0x22002000 0x10000>; + no-map; + }; + + /* + * IIS2 (BCM2078 FM/PCM capture) buffer, immediately above the + * playback pool. + * + * In SRAM for the same reason the playback buffer is: the one + * DRAM-sourced playback attempt moved a single 32-byte burst and + * then stalled, and there is no reason to find out the hard way + * whether the receive side behaves differently. Better to have + * both ports in the memory stock uses. + * + * Placed as LOW as it can go without overlapping the playback + * pool, which ends at 0x22012000. That is deliberate: the top of + * SRAM has already been tried for playback and moved ZERO bytes + * (c2 stayed 0x800, src advanced only the 16-byte prefetch), so + * whether the DMA can even reach up there is unestablished. Low + * SRAM is the part that is known to work. + * + * 32 KiB, not the 256 KiB the generic dmaengine-pcm config would + * preallocate: SRAM is 192 KiB in total and 64 KiB of it is + * already the playback buffer. A capture buffer does not need to + * be large -- at 48 kHz stereo this is still ~85 ms. + */ + i2s2_sram: audio-dma@22012000 { + compatible = "shared-dma-pool"; + reg = <0x22012000 0x8000>; + no-map; + }; + }; + soc { compatible = "simple-bus"; #address-cells = <1>; @@ -75,6 +166,29 @@ #interrupt-cells = <1>; }; + /* + * GPIO -> EIC -> VIC. The two groups do not share a controller, + * which is why this needs interrupts-extended: + * + * group 1 (GPIO 32-63, volume keys 40/41) -> VIC1 line 0 + * group 2 (GPIO 64-95, PMIC nIRQ 86) -> VIC0 line 31 + * + * Measured on the device by toggling each group's INTEN and + * watching which VIC RAWINTR bit followed. The previous entry + * put both on vic0 lines 1 and 3; neither line exists for the + * EIC, so no GPIO interrupt was ever delivered -- buttons and + * the touch nIRQ alike latched pending in the EIC forever. + */ + eic: interrupt-controller@39700000 { + compatible = "apple,s5l8740-eic", "samsung,s5l8740-eic"; + reg = <0x39700000 0x1000>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts-extended = <&vic1 0>, <&vic0 31>; + apple,eic-groups = <1>, <2>; + status = "okay"; + }; + timer: timer@3c700000 { compatible = "samsung,s5l8720-timer"; reg = <0x3c700000 0x20000>; @@ -115,6 +229,61 @@ interrupt-parent = <&vic0>; interrupts = <25>; status = "okay"; + + /* BCM2078KUBG on UART1 @115200 — serdev → hci_bcm → hci0. + * Companion GPIO/FM lives under apple,n31-bcm2078-companion. */ + bluetooth { + compatible = "brcm,bcm4329-bt", "brcm,bcm2078"; + /* + * sphwBluetooth_Init opens the port and immediately + * sends 01 18 FC 06 00 00 00 9F 24 00 -- vendor command + * 0xFC18, baud rate 0x00249F00 = 2400000. hci_bcm sends + * the same command from this property, so leaving it at + * 115200 would have run the link eight times slower than + * stock once the controller started answering. + */ + /* + * A dependency, not a power role. + * + * The chip driver owns the sequence -- rails, REG_ON, the + * wake pins -- but it can only run after gpio-d1830 loads, + * at about t=7.4s. hci_bcm probes the serdev at t=4.8s and + * sent 0xFC18 into a part that had no supply yet, timing + * out before the chip driver ever got its turn. + * + * Naming the same rail here makes the bridge defer until + * the provider exists, which is the only ordering hook a + * serdev node has. Enabling an already-enabled regulator is + * a refcount, so this does not take power management away + * from the chip driver. + */ + vbat-supply = <&bt_rail>; + max-speed = <2400000>; + /* + * 97/98/119 were once listed as shutdown/device-wakeup/ + * host-wakeup and must not go here: sub_15DD5C claims + * those three at function 2 when FM powers on and + * releases them when it powers off -- they are the IIS2 + * PCM pads, and handing them to hci_bcm made it drive + * the capture bus as GPIOs. + * + * GPIO 70 is a different pin and is decomp-backed. + * sphwBluetooth_Init (sub_570054, reached from + * BluetoothOSBridgeInit) opens with + * + * sub_43D38C(0x46u, 1, 1); + * + * pad 0x46 = 70, mode 1 = output, value 1 = high, before + * any HCI traffic. That is the power-on control the + * controller needs; without it hci0 binds and then + * HCI_Reset (0x0c03) times out, which is exactly what we + * were seeing. hci_bcm drives shutdown-gpios high on + * power-on, which matches. + */ + shutdown-gpios = <&gpio 70 GPIO_ACTIVE_HIGH>; + firmware-name = "brcm/BCM2076B1.hcd"; + status = "okay"; + }; }; uart2: serial@3dc00000 { @@ -125,18 +294,31 @@ reg-io-width = <4>; interrupt-parent = <&vic0>; interrupts = <26>; + /* + * MikeyBus on UART2. GPIO 66/67 = pad mux ONLY (not DIN detect). + * Resistor/model = OSOS cmd 3/0x8D/ch3 (backend TBD). + * force_plugged until resistor backend; RX = raw+osos rings. + */ status = "okay"; + + mikeybus { + compatible = "apple,mikeybus", "apple,n31-mikeybus"; + current-speed = <115200>; + apple,force-plugged; + status = "okay"; + }; }; usbphy: usbphy@3c400000 { - compatible = "apple,s5l8720-otgphy"; + /* N31 = s5l87xx PHY. Do NOT use apple,s5l8702-otgphy (Nano3 ramp). */ + compatible = "apple,s5l8740-otgphy", "apple,s5l87xx-otgphy"; reg = <0x3c400000 0x100>; status = "okay"; #phy-cells = <0>; }; usbotg_hs: usb@38400000 { - compatible = "apple,s5l87xx-usb"; + compatible = "apple,s5l8740-usb", "apple,s5l87xx-usb"; reg = <0x38400000 0x40000>; interrupt-parent = <&vic0>; interrupts = <19>; @@ -144,16 +326,34 @@ phy-names = "usb2-phy"; clocks = <&nclk>; clock-names = "otg"; + /* Glass: GHWCFG3 DFIFO=2080 words, ded=1, in_eps=6. + * SoC setter applies RetailOS sub_1B543A (NP=32, first + * IN cap 512). These DT values are overwritten. */ g-rx-fifo-size = <256>; - g-np-tx-fifo-size = <32>; - g-tx-fifo-size = <256 256 512 512 512 768 768>; + g-np-tx-fifo-size = <256>; status = "okay"; dr_mode = "peripheral"; + /* DMA on (g_dma=true) — HS was an IRQ storm only in slave PIO. */ + maximum-speed = "high-speed"; }; lcdif: lcdif@38300000 { compatible = "samsung,s5l8740-lcdif"; - reg = <0x38300000 0x10000>; + /* LCDIF, the clock controller (an LCDIF reset has to cycle + * two gates in it), then the MIPI DSI host. The panel is a + * DCS command-mode panel: the window is set through the DSI + * host and only the pixels go through LCDIF. */ + reg = <0x38300000 0x10000>, <0x3c500000 0x100>, + <0x3d800000 0x1000>; + }; + + /* MMIO backlight only. Never LCDIF CON/PHTIME. U-Boot already + * wrote 62 @ +0x08; this class device owns brightness after probe. */ + backlight: backlight@3e000000 { + compatible = "apple,s5l8740-backlight", "samsung,s5l8740-backlight"; + reg = <0x3e000000 0x100>; + default-brightness = <62>; + status = "okay"; }; wdt: watchdog@3c800000 { @@ -164,29 +364,78 @@ compatible = "syscon-reboot"; offset = <0x0>; value = <0x100000>; + /* 0x100000 is the WDT arm/reset poke — do not probe until poweroff is proven. */ + status = "disabled"; }; }; - gpio5: gpio@3cf00000 { + /* 2-line hack kept disabled; Vol± use full banked GPIO + EIC. */ + gpio5: gpio-hack@3cf000a4 { compatible = "brcm,bcm6345-gpio"; reg-names = "dat"; reg = <0x3cf000a4 0x4>; #gpio-cells = <2>; gpio-controller; ngpios = <2>; + status = "disabled"; + }; + + gpio: gpio@3cf00000 { + compatible = "apple,s5l8740-gpio", "samsung,s5l8740-gpio"; + reg = <0x3cf00000 0x400>; + apple,eic = <&eic>; + apple,skip-sec-pinmux; + #gpio-cells = <2>; + gpio-controller; + /* + * The block is mapped 0x400, which is 32 banks of 8, so 256 + * pads exist. 128 was enough for BT host-wake at 119 but put + * pad 0xC8 = 200 out of reach, and sub_17D4DC drives that one + * as the Bluetooth power control on board variants 1 and 2. + */ + ngpios = <256>; + status = "okay"; + }; + + /* Thin N31 helper: RetailOS GPIO mode-2 + FM 0xFC15 debug via hci0. + * Does not own UART1 — that is bluetooth { } under uart1 → hci_bcm. */ + bcm2078_companion: bcm2078-companion { + compatible = "apple,n31-bcm2078-companion"; + /* + * The chip driver owns power. The UART-to-HCI bridge is a dumb + * transport and takes no supply: hci_bcm carries power handling + * upstream only because it doubles as the chip driver on most + * boards, which is not the case here. + */ + bt-supply = <&bt_rail>; + status = "okay"; }; i2c0: i2c@3c600000 { compatible = "samsung,s5l8702-i2c"; + /* sub_5714EE bus 0. Nothing muxed these before, which is + * why this bus reads as noise while i2c1 works: the + * bootloader leaves i2c1 configured and this one not. */ + apple,pads = <4 5>; + #address-cells = <1>; + #size-cells = <0>; reg = <0x3c600000 0x100>; clock-frequency = <100000>; /* UPDATE ME */ interrupt-parent = <&vic0>; interrupts = <21>; + /* + * Glass 2026-08-27: IIC0 emcore-TX never completes (VIC21 irq + * count stays 0). Keep the controller for RE; no children until + * pinmux/clock path is proven. + */ status = "okay"; }; i2c1: i2c@3c900000 { compatible = "samsung,s5l8702-i2c"; + /* sub_5714EE bus 1. Already live at boot; re-applying the + * stock mux is a no-op rather than a change. */ + apple,pads = <78 79>; #address-cells = <1>; #size-cells = <0>; reg = <0x3c900000 0x100>; @@ -195,19 +444,61 @@ interrupts = <22>; status = "okay"; + /* + * CBTL1609A1 — public 0x34/0x35 is 8-bit; Linux 7-bit is 0x1a. + * Glass: lives on IIC1 (3c900000), NOT IIC0. Non-flat dump with + * IDBUS 0x75 proven via i2cdump. RetailOS writes no Dx mux map. + */ + tristar: lightning-mux@1a { + compatible = "apple,tristar-cbtl1609", "nxp,cbtl1609a1", + "apple,n31-tristar"; + reg = <0x1a>; + status = "okay"; + }; + lis3dc: lis331dlh@18 { compatible = "st,lis3lv02d"; + /* Linux DT 7-bit. Wire 8-bit is 0x30 write / 0x31 read. */ reg = <0x18>; + status = "okay"; }; d1830: pmic@73 { compatible = "dlg,d1830-gpio"; + /* Linux DT 7-bit. SEC sub_1C8C(115). Wire 8-bit 0xE6/0xE7. */ reg = <0x73>; gpio-controller; #gpio-cells = <2>; - dlg,gpio-map = <0x07 4>, /* Home button */ - <0x07 5>, /* Power button */ - <0x08 1>; /* Play/pause button */ + /* OSOS sub_26520 packs PMIC regs 5-8; sub_3B3100 + sub_FFA*: + * ID 1 Home = reg7 bit4 (status bit 16) + * ID 9 Sleep = reg7 bit5 (status bit 17) + * ID 6 Play = reg8 bit1 (status bit 19) + * Key path is active-low: sub_4195D8(id, bit==0). + * GPIOButtonManager has only GPIO 40/41 — not these. + */ + dlg,gpio-map = <0x07 4>, /* Home OSOS ID 1 */ + <0x07 5>, /* Sleep OSOS ID 9 */ + <0x08 1>; /* Play OSOS ID 6 (side, between Vol±) */ + /* SoC GPIO 86 → EIC group2 → VIC EXT3 */ + interrupt-parent = <&eic>; + interrupts = <86 IRQ_TYPE_LEVEL_LOW>; + monitored-battery = <&battery>; + + /* + * The Bluetooth rail, named so a consumer can reference it. + * + * bcm2078-bt is built into the kernel and probes long before + * this driver, which is a module. Asking for power through a + * direct hook in that window just fails. Asking for it as a + * regulator makes the kernel defer the consumer's probe until + * this node exists, which is the ordering guarantee that was + * missing. + */ + regulators { + bt_rail: bt { + regulator-name = "bt"; + }; + }; }; }; @@ -223,6 +514,7 @@ reg = <0x38000000 0x100>; clocks = <&clkctrl CLK_SHA1>; clock-names = "sha1"; + status = "okay"; }; aes: aes@38c00000 { @@ -230,6 +522,7 @@ reg = <0x38c00000 0x100>; clocks = <&clkctrl CLK_AES>; clock-names = "aes"; + status = "okay"; }; prng: prng@3c100000 { @@ -237,15 +530,150 @@ reg = <0x3c100000 0x100>; clocks = <&clkctrl CLK_PRNG>; clock-names = "prng"; + status = "okay"; + }; + + /* + * SPI0 @0x3C300000 — CS42L81 / 338S1146 control (RetailOS). + * Panel pixels are LCDIF@383, not this bus. Pads 0–3 are the + * SEC/gpio-s5l8740 SPI0 group (PCON 0x2222 in the SPI driver). + */ + spi0: spi@3c300000 { + compatible = "apple,s5l8702-spi", "samsung,s5l8740-spi", + "samsung,s5l8702-spi"; + reg = <0x3c300000 0x100>; + /* CLKCON already ungate-all; SPI driver also clears PWRCON1 SPI0. */ + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cs42l81: codec@0 { + compatible = "cirrus,cs42l81", "apple,338s1146"; + reg = <0>; + spi-max-frequency = <1000000>; + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + + /* + * SPI2 @0x3D200000 — TI 343S0538 Grape. Pads 87/5, 88/3, 89/3, + * 90/3 (OSOS 20690). EN 14, RST 39, IRQ 38. No clocks= — CCF + * only publishes SHA/AES/PRNG; ungate-all + SPI PWRCON poke. + */ + spi2: spi@3d200000 { + compatible = "apple,s5l8702-spi", "samsung,s5l8740-spi", + "samsung,s5l8702-spi"; + reg = <0x3d200000 0x100>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + grape: touchscreen@0 { + compatible = "apple,grape"; + reg = <0>; + /* Stock runs this bus at 12 MHz (divider 2 from 24 MHz). + * The controller programs its own divider, so this is + * documentation rather than a control. */ + spi-max-frequency = <12000000>; + enable-gpios = <&gpio 14 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio 39 GPIO_ACTIVE_LOW>; + attn-gpios = <&gpio 38 GPIO_ACTIVE_LOW>; + /* GPIO38 → EIC group1 → VIC EXT1 */ + interrupts-extended = <&eic 38 IRQ_TYPE_LEVEL_LOW>; + status = "okay"; + }; + }; + + /* + * PL080 pair from OSOS. Not 0x384 (DWC2). + * RetailOS oracle: IIS0 TX peri 10 (DST 0x3ca00010); IIS0 RX 11 assumed; + * IIS2 FM RX peri 13 (SRC 0x3d400038). PL080_1 unused in snaps. + */ + dmac: dma-controller@38200000 { + compatible = "apple,s5l8740-pl080", "arm,pl080"; + reg = <0x38200000 0x1000>, <0x38700000 0x1000>; + interrupt-parent = <&vic0>; + interrupts = <16>, <17>; + #dma-cells = <2>; + status = "okay"; + }; + + i2s0: i2s@3ca00000 { + compatible = "apple,s5l8740-i2s", "samsung,s5l8740-i2s"; + reg = <0x3ca00000 0x1000>; + /* + * This node had NO clocks property, so + * devm_clk_bulk_get_all() returned zero clocks and nothing + * ever ungated IIS0. PWRCON1 bit 7 is CLK_I2S0 in + * clk-s5l8702.c, and stock clears it via sub_345D70(0x26,0,1) + * from sub_C09AC before writing I2SCLKCON -- measured on + * RetailOS as PWRCON1 0xef226fe3 idle -> 0xef226f23 playing. + * CLK_CG16_9 is the codec gate, CLKCON+0x0C bit 15 + * (sub_41CBD8 case 9). The nodrm variant already had both. + */ + clocks = <&clkctrl CLK_I2S0>, <&clkctrl CLK_CG16_9>; + clock-names = "i2s", "cg16"; + memory-region = <&i2s_sram>; + /* + * SRAM is uncached, so the CPU and the PL080 already see the + * same bytes and no cache maintenance is needed. Without this, + * the DMA API tries to clean a no-map region that has no + * cacheable kernel mapping and oopses in v7_dma_clean_range. + */ + dma-coherent; + /* RetailOS music: TX peri 10 confirmed. RX 11 still assumed. */ + dmas = <&dmac 10 0>, <&dmac 11 0>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; + status = "okay"; + }; + + /* + * BCM2078 digital PCM (SoC IIS2 @ 0x3D400000). + * RX FIFO @ +0x38, PL080 peri 13 — FM + module→SoC PCM in. + * IIS1 @ 0x3CD00000 is unused (XSP); BT A2DP out = UART1 HCI. + * RetailOS: bt-*-scsi-live/, fm-*-scsi-live/ + */ + i2s2: i2s@3d400000 { + compatible = "apple,s5l8740-bcm2078-pcm", "apple,s5l8740-iis2"; + reg = <0x3d400000 0x1000>; + dmas = <&dmac 13 0>; + dma-names = "rx"; + /* Capture buffer in low SRAM; see the i2s2_sram node. */ + memory-region = <&i2s2_sram>; + dma-coherent; + #sound-dai-cells = <0>; + status = "okay"; + }; + + nano7_audio: audio { + compatible = "apple,n31-audio"; + apple,cpu = <&i2s0>; + apple,fm-cpu = <&i2s2>; + apple,codec = <&cs42l81>; + status = "okay"; }; }; + /* Design pack only. Voltage comes from D1830 OSOS ADC, not this node. */ + battery: battery { + compatible = "simple-battery"; + voltage-min-design-microvolt = <3300000>; + voltage-max-design-microvolt = <4200000>; + energy-full-design-microwatt-hours = <740000>; + charge-full-design-microamp-hours = <200000>; + }; + + /* + * Keep gpio-keys disabled (p5-keys-alsa): Home/Sleep/Play come from + * gpio-d1830 I2C poll + nIRQ; Vol± from gpio-s5l8740 DIN poll. + * Enabling this node would double-report and risk 0xFFFE on Vol pads. + */ gpio-keys { - compatible = "gpio-keys-polled"; - poll-interval = <50>; + compatible = "gpio-keys"; + status = "disabled"; - /* these hammer the i2c bus and the driver breaks - at some point, disable them for now */ button-home { label = "Home Button"; gpios = <&d1830 0 GPIO_ACTIVE_LOW>; @@ -266,16 +694,28 @@ linux,code = ; status = "disabled"; }; + }; + + /* Disabled: gpio-keys-polled must not direction_input/0xFFFE Vol pads. + * gpio-s5l8740 polls DIN and reports KEY_VOLUMEUP/DOWN; cs42l81-spi + * input_handler steps Master Playback Volume. + */ + gpio-keys-vol { + compatible = "gpio-keys-polled"; + status = "disabled"; + poll-interval = <50>; button-volup { + /* OSOS id 8, GPIO 0x28. Glass: Vol+. */ label = "Volume Up"; - gpios = <&gpio5 0 GPIO_ACTIVE_LOW>; + gpios = <&gpio 40 GPIO_ACTIVE_LOW>; linux,code = ; }; button-voldown { + /* OSOS id 7, GPIO 0x29. Glass: Vol-. */ label = "Volume Down"; - gpios = <&gpio5 1 GPIO_ACTIVE_LOW>; + gpios = <&gpio 41 GPIO_ACTIVE_LOW>; linux,code = ; }; }; diff --git a/arch/arm/configs/apple_n31_bootminimal_defconfig b/arch/arm/configs/apple_n31_bootminimal_defconfig new file mode 100755 index 00000000000000..53377f4837839f --- /dev/null +++ b/arch/arm/configs/apple_n31_bootminimal_defconfig @@ -0,0 +1,41 @@ +# N31 boot-minimal — LCD + timer + clk only (bisect ~500ms OSOS reset) +CONFIG_ARCH_S5L87XX=y +CONFIG_CPU_S5L8740=y +CONFIG_CMDLINE="console=tty0 fbcon=font:MINI4x6 vt.global_cursor_default=0 loglevel=8 panic=-1 nohlt" +CONFIG_CMDLINE_FORCE=y +CONFIG_PRINTK_TIME=y +CONFIG_S5L8720_TIMER=y +CONFIG_SERIAL_SAMSUNG=y +# CONFIG_SERIAL_SAMSUNG_CONSOLE is not set +CONFIG_VFP=y +CONFIG_TMPFS=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DRM=y +CONFIG_TINYDRM_S5L8740=y +CONFIG_FB=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_FONTS=y +CONFIG_FONT_8x8=y +CONFIG_FONT_MINI_4x6=y +CONFIG_CLK_S5L8702=y +CONFIG_ARM_VIC=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_S5L8740=y +CONFIG_BLK_DEV_INITRD=y +# No panic→reboot (was masking oops before fbcon ready) +# CONFIG_POWER_RESET is not set +# CONFIG_POWER_RESET_SYSCON is not set +# Peripheral drivers compiled out — DT disabled alone was not enough +# CONFIG_USB is not set +# CONFIG_NET is not set +# CONFIG_I2C is not set +# CONFIG_SPI is not set +# CONFIG_CRYPTO is not set +# CONFIG_GPIOLIB is not set +# CONFIG_GPIO_S5L8740 is not set +# CONFIG_S5L8740_EIC is not set +# CONFIG_INPUT is not set +# CONFIG_SND is not set +# CONFIG_DMA_ENGINE is not set diff --git a/arch/arm/configs/apple_n31_defconfig b/arch/arm/configs/apple_n31_defconfig index 2d3160bd4812e9..13125825232a99 100644 --- a/arch/arm/configs/apple_n31_defconfig +++ b/arch/arm/configs/apple_n31_defconfig @@ -1,6 +1,7 @@ CONFIG_ARCH_S5L87XX=y CONFIG_CPU_S5L8740=y -CONFIG_CMDLINE="console=tty0 console=ttySAC0 earlyprintk nohlt" +CONFIG_CMDLINE="console=tty0 fbcon=font:MINI4x6 earlyprintk nohlt panic=-1 clk_ignore_unused init=/init" +CONFIG_DEBUG_USER=y CONFIG_EARLY_PRINTK=y CONFIG_PRINTK_TIME=y CONFIG_DEBUG_KERNEL=y @@ -9,16 +10,40 @@ CONFIG_DEBUG_S3C_UART3=y CONFIG_S5L8720_TIMER=y CONFIG_SERIAL_SAMSUNG=y CONFIG_SERIAL_SAMSUNG_CONSOLE=y -CONFIG_PHY_S5L8720_USB2=y -CONFIG_USB_DWC2=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_COMPRESS is not set +CONFIG_DEBUG_FS=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_USB_DWC2=m +CONFIG_USB_DWC2_PERIPHERAL=y CONFIG_USB_GADGET=y -CONFIG_USB_SNP_UDC_PLAT=y -CONFIG_USB_ETH=y -CONFIG_USB_ETH_RNDIS=n +# CONFIG_USB_SNP_UDC_PLAT is not set +# CONFIG_USB_ETH is not set +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_U_SERIAL=y +CONFIG_USB_F_ACM=y +CONFIG_USB_F_RNDIS=y +CONFIG_USB_LIBCOMPOSITE=y +CONFIG_CONFIGFS_FS=y +CONFIG_PHY_S5L8702_USB2=y +CONFIG_APPLE_TRISTAR_CBTL1609=m +CONFIG_APPLE_MIKEYBUS=m +CONFIG_UNIX98_PTYS=y +CONFIG_DEVPTS_FS=y CONFIG_NET=y +CONFIG_NETDEVICES=y +CONFIG_NET_CORE=y +# CONFIG_ETHERNET is not set CONFIG_UNIX=y CONFIG_PACKET=y CONFIG_INET=y +# CONFIG_IPV6 is not set CONFIG_IP_PNP=y # CONFIG_NFS_FS=y # CONFIG_NFS_V4=y @@ -33,27 +58,34 @@ CONFIG_TINYDRM_S5L8740=y CONFIG_FB=y CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_DRM_FBDEV_EMULATION=y -CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y -CONFIG_LOGO=y +CONFIG_FONTS=y +CONFIG_FONT_8x8=y +CONFIG_FONT_MINI_4x6=y +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_LOGO is not set CONFIG_BLK_DEV_INITRD=y CONFIG_REGMAP=y CONFIG_REGMAP_MMIO=y CONFIG_MFD_SYSCON=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_SYSCON=y +CONFIG_POWER_SUPPLY=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_S5L8740=y CONFIG_GPIOLIB=y CONFIG_GPIOLIB_FASTPATH_LIMIT=512 CONFIG_OF_GPIO=y CONFIG_GPIO_CDEV=y CONFIG_GPIO_CDEV_V1=y -CONFIG_GPIO_D1830=y -CONFIG_GPIO_GENERIC=y -CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_GPIO_D1830=m +CONFIG_S5L8740_EIC=y +CONFIG_GPIO_S5L8740=y +CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_GPIO_POLLED=y CONFIG_INPUT_EVDEV=y -CONFIG_I2C_S5L8702=y +CONFIG_I2C_S5L8702=m CONFIG_I2C_CHARDEV=y -CONFIG_SENSORS_LIS3_I2C=y +CONFIG_SENSORS_LIS3_I2C=m CONFIG_CRYPTO=y CONFIG_CRYPTO_DEV_S5L8702_AES=y CONFIG_CRYPTO_USER_API_SKCIPHER=y @@ -62,3 +94,60 @@ CONFIG_CRYPTO_USER_API_HASH=y CONFIG_CRYPTO_DEV_S5L8702_PRNG=y CONFIG_CRYPTO_USER_API_RNG=y CONFIG_CLK_S5L8702=y +CONFIG_SPI=y +CONFIG_SPI_S5L8702=m +CONFIG_INPUT=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_APPLE_NIMBUS=m +CONFIG_FW_LOADER=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y +CONFIG_SND_DMAENGINE_PCM=y +CONFIG_SND_SOC_APPLE_CS42L81_SPI=m +CONFIG_SND_SOC_APPLE_S5L8740_I2S=m +CONFIG_SND_SOC_APPLE_S5L8740_IIS2=m +CONFIG_SND_SOC_APPLE_NANO7=m +# V4L2 FM radio (/dev/radio0) over BCM 0xFC15 +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_RADIO_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_MEDIA_TUNER is not set +# CONFIG_MEDIA_DIGITAL_TV_SUPPORT is not set +# CONFIG_MEDIA_CAMERA_SUPPORT is not set +# CONFIG_MEDIA_USB_SUPPORT is not set +# No stock V4L radio tuners — N31 FM is bcm2078 /dev/radio0 only. +CONFIG_DMADEVICES=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_S5L8740_PL080=m +# Block + buffer_head must be built into vmlinux so FAT/VFAT modules +# can resolve mark_buffer_dirty / __bread / etc. (FAT selects BUFFER_HEAD; +# a stale O-tree with CONFIG_BUFFER_HEAD=y but no fs/buffer.o breaks vfat.ko.) +CONFIG_BLOCK=y +CONFIG_BUFFER_HEAD=y +CONFIG_NLS=y +CONFIG_FAT_FS=m +CONFIG_VFAT_FS=m +CONFIG_MSDOS_FS=m +CONFIG_NLS_CODEPAGE_437=m +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_UTF8=m +CONFIG_MSDOS_PARTITION=y +CONFIG_EFI_PARTITION=y +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +# Bluetooth: UART1 serdev → hci_bcm → hci0 (+ thin N31 GPIO companion) +CONFIG_BT=y +CONFIG_BT_BREDR=y +CONFIG_BT_LE=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_BCM=y +CONFIG_BT_BCM2078_N31=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_SERIAL_DEV_CTRL_TTYPORT=y diff --git a/arch/arm/configs/apple_n31_nodrm_defconfig b/arch/arm/configs/apple_n31_nodrm_defconfig new file mode 100755 index 00000000000000..39831181bbf2cd --- /dev/null +++ b/arch/arm/configs/apple_n31_nodrm_defconfig @@ -0,0 +1,19 @@ +# N31 nodrm bisect — no LCD drivers, no initramfs, BL-off witness +CONFIG_ARCH_S5L87XX=y +CONFIG_CPU_S5L8740=y +CONFIG_CMDLINE="panic=-1 loglevel=8 nohlt" +CONFIG_CMDLINE_FORCE=y +CONFIG_PRINTK_TIME=y +CONFIG_S5L8720_TIMER=y +CONFIG_VFP=y +CONFIG_TMPFS=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CLK_S5L8702=y +CONFIG_ARM_VIC=y +CONFIG_N31_EARLY_BL_OFF=y +# CONFIG_DRM is not set +# CONFIG_FB is not set +# CONFIG_BLK_DEV_INITRD is not set +# CONFIG_SERIAL_SAMSUNG is not set +# CONFIG_POWER_RESET is not set diff --git a/arch/arm/mach-s5l87xx/s5l87xx.c b/arch/arm/mach-s5l87xx/s5l87xx.c index cb9cf8120a71d1..dfa612a7a43325 100644 --- a/arch/arm/mach-s5l87xx/s5l87xx.c +++ b/arch/arm/mach-s5l87xx/s5l87xx.c @@ -6,10 +6,56 @@ * S5L8740 (Cortex-A5, iPod nano 7g) */ +#include #include +#include +#include +#include +#include #include #include +#define S5L8740_WDT_PHYS 0x3c800000ul + +/* + * WTF/U-Boot leave the SEC watchdog armed. A bigger zImage loses the race. + * Stage0 sequence only — never CLKCON+0x50 (fatal latch). + */ +static int __init s5l87xx_wdt_disarm(void) +{ + void __iomem *wdt = ioremap(S5L8740_WDT_PHYS, 8); + + if (!wdt) + return 0; + writel(0, wdt); + writel(0, wdt + 4); + writel(0, wdt); + writel(0, wdt + 4); + pr_info("s5l87xx: WDT disarmed con=%08x cnt=%08x\n", + readl(wdt), readl(wdt + 4)); + iounmap(wdt); + return 0; +} +early_initcall(s5l87xx_wdt_disarm); + +/* Print before Run /init so glass shows whether rootfs actually has PID 1. */ +static int __init n31_init_witness(void) +{ + struct path path; + int err = kern_path("/init", LOOKUP_FOLLOW, &path); + + if (err) { + pr_err("n31: /init missing (%d) — initramfs not in rootfs\n", err); + return 0; + } + pr_info("n31: /init present mode=%o size=%lld\n", + path.dentry->d_inode->i_mode, + (long long)i_size_read(path.dentry->d_inode)); + path_put(&path); + return 0; +} +late_initcall(n31_init_witness); + /* * Map the debug UART into a fixed virtual address so earlyprintk works * across the MMU transition. The physical base is 0x3CC00000; UART3 diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 7771edf54fb3f1..1941a202451015 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -518,3 +518,13 @@ config BT_INTEL_PCIE Say Y here to compiler support for Intel Bluetooth PCIe device into the kernel or say M to compile it as module (btintel_pcie) endmenu + +config BT_BCM2078_N31 + tristate "Broadcom BCM2078 GPIO/FM companion (N31)" + depends on BT && OF && HAS_IOMEM + depends on VIDEO_DEV + help + Thin N31 helper: RetailOS GPIO mode-2, FM 0xFC15 via hci0, and + V4L2 /dev/radio0. Sysfs fm_* is debug. UART1 HCI is hci_bcm. + No FM->A2DP path — local IIS2 capture + IIS0 play only. + diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 81856512ddd030..2c6727dd91b8fc 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -53,3 +53,4 @@ hci_uart-$(CONFIG_BT_HCIUART_AG6XX) += hci_ag6xx.o hci_uart-$(CONFIG_BT_HCIUART_MRVL) += hci_mrvl.o hci_uart-$(CONFIG_BT_HCIUART_AML) += hci_aml.o hci_uart-objs := $(hci_uart-y) +obj-$(CONFIG_BT_BCM2078_N31) += bcm2078-bt.o diff --git a/drivers/bluetooth/bcm2078-bt.c b/drivers/bluetooth/bcm2078-bt.c new file mode 100755 index 00000000000000..bb0e1f538a70a8 --- /dev/null +++ b/drivers/bluetooth/bcm2078-bt.c @@ -0,0 +1,1599 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BCM2078 N31 companion — RetailOS GPIO mode-2 + FM 0xFC15. + * + * UART1 HCI / patchram belong to apple,s5l-uart + serdev + hci_bcm/btbcm + * (hci0). This driver must NOT ioremap UART1 or of_platform_device_create the + * bluetooth child — that steals the port from hci_bcm. + * + * GPIO 97/98/119 are not this driver's business either, but for a different + * reason than the UART. They were taken for BCM shutdown / device-wakeup / + * host-wakeup, and this driver muxed them at function 2 under that name. + * sub_15DD5C shows what they really are: FM power-on claims exactly these + * three at function 2 next to programming audio device 2 (0x3D400000) and + * kicking RXCOM, so they are the IIS2 PCM pads. The mux is right, the owner + * was wrong -- s5l8740-i2s claims them with the rest of the capture setup, + * which also means the capture PCM works without the tuner being on. + * gpio_poke=1 restores the old local poking for bring-up comparisons. + * + * FM vendor opcode 0xFC15 is sent via __hci_cmd_sync on hci0 when HCI_UP. + * Phase 3: thin V4L2 radio (/dev/radio0). Sysfs fm_* remains debug. + * Audio is IIS2 ALSA capture → userspace → IIS0 play. No FM→A2DP path. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +/* + * The PMIC driver is a module and this file is built in, so the link + * can only go the other way: expose a hook and let gpio-d1830 register + * its rail control when it probes. Everything here still works with + * nothing registered -- the rails simply stay as the bootloader left + * them, which is the behaviour we had before. + */ +static int (*bcm_bt_rails_fn)(bool on); + +/* + * What we asked for while nobody was listening. + * + * This driver is built in and probes at about t=2.4s. gpio-d1830, which + * owns the PMIC rails, is a module that userspace loads later -- it + * registered here at t=7.1s on the boot that exposed this. In between, + * every bcm_bt_rails() call returned -ENODEV and the rails were simply + * never switched on, so the controller had no supply. hci_bcm sent + * 0xFC18 into a dead part at t=4.8s, timed out, and gave up two seconds + * before the rails became available. + * + * The hook was always the right shape for a built-in talking to a module; + * it just had no memory. Record the last request and apply it when the + * provider finally shows up. + */ +static bool bcm_bt_rails_want; +static bool bcm_bt_rails_pending; + +void bcm2078_register_bt_rails(int (*fn)(bool on)) +{ + bcm_bt_rails_fn = fn; + + if (fn && bcm_bt_rails_pending) { + int ret = fn(bcm_bt_rails_want); + + bcm_bt_rails_pending = false; + pr_info("bcm2078-bt: rails provider arrived late; applied %s: %d\n", + bcm_bt_rails_want ? "on" : "off", ret); + } +} +EXPORT_SYMBOL_GPL(bcm2078_register_bt_rails); + +/* + * The rail, taken as a regulator. + * + * This is the ordering fix. The hook below can only report that no provider + * exists yet; a regulator makes the kernel wait for one. devm_regulator_get() + * returns -EPROBE_DEFER while gpio-d1830 is still unloaded, probe is retried + * once it registers, and by the time this driver runs the rail is reachable. + * + * Power belongs here, in the chip driver, and not in the UART-to-HCI bridge: + * the bridge should move bytes and nothing else. hci_bcm carries supplies and + * shutdown-gpios upstream because on most boards it is also the chip driver, + * which is not true on this one. + */ +static struct regulator *bcm_bt_vreg; + +/* + * Whether WE hold an enable on bcm_bt_vreg. + * + * regulator_enable/disable are refcounted per consumer handle, and calling + * disable without a matching enable is not merely ignored: _regulator_disable + * WARNs with a full backtrace and returns -EIO. That is exactly what an + * unbind produced -- + * + * _regulator_disable from regulator_disable + * regulator_disable from bcm_power_off + * bcm2078-bt: bt rail disable failed: -5 + * + * -- because the enable had gone down the legacy-hook path (the regulator + * was not acquired yet at the time) while the disable found the pointer set + * and went to the regulator. Track it here rather than inferring it, so the + * two paths cannot disagree. + */ +static bool bcm_bt_vreg_on; + +static int bcm_bt_rails(bool on) +{ + if (bcm_bt_vreg) { + int ret; + + if (on == bcm_bt_vreg_on) + return 0; + + ret = on ? regulator_enable(bcm_bt_vreg) + : regulator_disable(bcm_bt_vreg); + + if (ret) + pr_warn("bcm2078-bt: bt rail %s failed: %d\n", + on ? "enable" : "disable", ret); + else + bcm_bt_vreg_on = on; + return ret; + } + + if (!bcm_bt_rails_fn) { + /* Remember it; the provider may still be loading. */ + bcm_bt_rails_want = on; + bcm_bt_rails_pending = true; + pr_info("bcm2078-bt: rails %s deferred, no provider yet\n", + on ? "on" : "off"); + return -ENODEV; + } + return bcm_bt_rails_fn(on); +} + +#define BCM_GPIO_PHYS 0x3cf00000UL +#define BCM_GPIOCMD_OFF 0x1e0 + +#define BCM_GPIO_A 0x61 /* 97 — shutdown / REG_ON */ +#define BCM_GPIO_B 0x62 /* 98 — device-wakeup */ +#define BCM_GPIO_C 0x77 /* 119 — host-wakeup */ +/* + * 0xC8 = 200 is not a no-op. sub_17D4DC selects the Bluetooth power + * control by board variant: + * + * variant 1 or 2: sub_428F70(0xC8, 1); sub_43D38C(0xC8, 1, 1); + * variant 5: sub_43D38C(0x46, 1, 1); sub_428F70(0x46, 1); + * + * and sphwBluetooth_Init then drives 0x46 = 70 high unconditionally. + * Both orders pair the pad write with sub_428F70, which sets the pad's + * +0x0C bit -- something a gpiod output cannot express, so hci_bcm + * driving shutdown-gpios does not cover it. + */ +#define BCM_GPIO_NOP 0xC8 +#define BCM_GPIO_PWR 0x46 /* 70 — power control, all variants */ +#define BCM_GPIO_UART 0x50 /* 80 — function 2 while the part is on */ +#define BCM_GPIO_PWR_ALT 0xC8 /* 200 — variants 1 and 2 */ +#define BCM_MODE_POWER 2 +#define BCM_MODE_CLEAR 0xFFFE + +#define HCI_OP_FC15 0xFC15 + +/* + * FM_RDS_Command register map, from the BlueTool hcidef (COMMAND + * "FM_RDS_Command" 0x015). The opcode carries an I2C-style register + * transaction: address, read/write, then either the write data or, for a + * read, the byte count. Register widths below are the read lengths that + * same definition encodes. + */ +#define FM_REG_RDS_SYSTEM 0x00 /* 1: FM_ON | RDS_ON */ +#define FM_REG_FM_CTRL 0x01 /* 1: band, stereo, injection */ +#define FM_REG_RDS_CTRL 0x02 /* 1 */ +#define FM_REG_AUDIO_PAUSE 0x04 /* 1 */ +#define FM_REG_AUDIO_CTRL 0x05 /* 2: mute/route/de-emphasis */ +#define FM_REG_SEARCH_CTRL 0x07 /* 1: direction + RSSI threshold */ +#define FM_REG_SEARCH_CTRL1 0x08 /* 1 */ +#define FM_REG_SEARCH_TUNE 0x09 /* 1: 1 = tune, 2 = search */ +#define FM_REG_FREQ 0x0a /* 2 */ +#define FM_REG_AF_FREQ 0x0c /* 2 */ +#define FM_REG_CARRIER 0x0e /* 1 */ +#define FM_REG_RSSI 0x0f /* 1 */ +#define FM_REG_RDS_MASK 0x10 /* 2 */ +#define FM_REG_RDS_FLAG 0x12 /* 2 */ +#define FM_REG_RDS_WLINE 0x14 /* 1: RDS FIFO watermark */ +#define FM_REG_RDS_BLKB_MATCH 0x16 /* 2 */ +#define FM_REG_RDS_BLKB_MASK 0x18 /* 2 */ +#define FM_REG_RDS_PI_MATCH 0x1a /* 2 */ +#define FM_REG_RDS_PI_MASK 0x1c /* 2 */ +#define FM_REG_RDS_BOOT 0x1e /* 1 */ +#define FM_REG_RDS_TEST 0x1f /* 1 */ +#define FM_REG_SLAVE_CONFIG 0x29 +#define FM_REG_ROUTE_PCM 0x4d /* 1: tuner audio onto the PCM port */ +#define FM_REG_RDS_DATA 0x80 /* n: RDS FIFO, caller-sized */ +#define FM_REG_BEST_TUNE 0x90 /* 1 */ +#define FM_REG_SMUTE_V3 0xda /* 5 */ +#define FM_REG_FEATURES 0xdb /* 4 */ +#define FM_REG_PRESCAN_QUALITY 0xde /* 1 */ +#define FM_REG_SNR 0xdf /* 1 */ +#define FM_REG_EXTRA_AUDIO 0xf5 /* 1: FMRX_2_AFIFO_ENABLE */ +#define FM_REG_ANT_MATCHING 0xf6 /* 1 */ +#define FM_REG_VOLUME_CTRL 0xf8 /* 2 */ +#define FM_REG_BLEND_SMUTE 0xf9 /* 8: stereo blend + soft mute */ +#define FM_REG_ANT_SELECT 0xfa /* 1 */ +#define FM_REG_SEARCH_BOUND 0xfb /* 4: band edges */ +#define FM_REG_SEARCH_METHOD 0xfc /* 1 */ +#define FM_REG_SEARCH_STEP 0xfd /* 2 */ +#define FM_REG_PRESET_MAX 0xfe /* 1 */ +#define FM_REG_PRESET_CHAN 0xff /* n */ + +#define FM_MODE_WRITE 0 +#define FM_MODE_READ 1 + +/* SEARCH_TUNE_MODE values. */ +#define FM_TUNE_MODE_IDLE 0 +#define FM_TUNE_MODE_PRESET 1 +#define FM_TUNE_MODE_SEARCH 2 + +/* RDS_SYSTEM bits. */ +#define FM_SYSTEM_FM_ON 0x01 +#define FM_SYSTEM_RDS_ON 0x02 + +/* FM_CTRL bits. Band select 0 = 87.5-108 MHz, 1 = 76-90 MHz. */ +#define FM_CTRL_BAND_JAPAN 0x01 +#define FM_CTRL_STEREO_AUTO 0x02 +#define FM_CTRL_STEREO_MANUAL 0x04 +#define FM_CTRL_STEREO_BLEND 0x08 +#define FM_CTRL_INJECTION 0x10 + +/* AUDIO_CTRL bits 6:0; 15:7 is the audio bandwidth select. */ +#define FM_AUDIO_RF_MUTE 0x0001 +#define FM_AUDIO_MANUAL_MUTE 0x0002 +#define FM_AUDIO_Z_MUTE_LEFT 0x0004 +#define FM_AUDIO_Z_MUTE_RIGHT 0x0008 +#define FM_AUDIO_ROUTE_DAC 0x0010 +#define FM_AUDIO_ROUTE_I2S 0x0020 +#define FM_AUDIO_DEEMPH_75US 0x0040 + +/* Largest RDS read we will ask for in one go. */ +#define FM_RDS_READ_MAX 60 +#define FM_RDS_TEXT_MAX 64 +#define FM_RDS_PS_MAX 8 + +/* Bounds for the raw HCI passthrough. */ +#define BCM_HCI_PARAM_MAX 255 +#define BCM_HCI_RSP_MAX 64 + +/* V4L2_TUNER_CAP_LOW: 62.5 Hz units → kHz * 16 */ +#define BCM_FM_FREQ_TO_V4L(khz) ((khz) * 16u) +#define BCM_FM_V4L_TO_FREQ(f) ((f) / 16u) +#define BCM_FM_KHZ_MIN 87500u +#define BCM_FM_KHZ_MAX 108000u +#define BCM_FM_KHZ_DEFAULT 94700u /* Canada test station */ + +/* + * RF_MUTE squelches the output as C/N falls, de-emphasis is 75 us for + * North America (clear the bit for the 50 us regions), and ROUTE_I2S is + * what puts tuner audio on the PCM port that IIS2 captures. + */ +#define BCM_FM_AUDIO_CTRL0_DEFAULT (FM_AUDIO_RF_MUTE | \ + FM_AUDIO_DEEMPH_75US | \ + FM_AUDIO_ROUTE_I2S) + +/* + * Off by default: these pins belong to hci_bcm. See the file header. + */ +/* + * On. Without it the BCM part is never actually powered. + * + * hci_bcm drives shutdown-gpios, which the device tree points at GPIO 70 -- + * the power control pin. REG_ON is GPIO 97, and device-wake and host-wake + * are 98 and 119. Stock sets all three to mode 2 as part of its power + * sequence: + * + * sub_43D38C(0x61u, 2, 0) 97 REG_ON + * sub_43D38C(0x62u, 2, 0) 98 device-wake + * sub_43D38C(0x77u, 2, 0) 119 host-wake + * sub_43D38C(0x46u, 1, 1) 70 power control, paired with 428F70 + * + * With this off we did the rails and the +0x0C pad gate and then stopped, + * so REG_ON was never asserted and the controller stayed in reset. The + * symptom is unambiguous: vendor command 0xFC18 times out, hci_bcm reports + * "failed to write update baudrate (-110)", and /proc/interrupts shows + * zero interrupts on 3db00000.serial -- the chip has never sent a byte. + * + * It defaulted off because driving these pads early once correlated with a + * reset back to RetailOS. That is a single observation against a sequence + * the stock firmware performs on every power-on, and the cost of honouring + * it is that Bluetooth cannot work at all. If the reset returns, the thing + * to investigate is ordering against the rails, not whether to power the + * part. + */ +static bool gpio_poke = true; +module_param(gpio_poke, bool, 0644); +MODULE_PARM_DESC(gpio_poke, + "Drive the BCM control pins directly (default N; hci_bcm owns them)"); + +static u8 rds_wline = 12; +module_param(rds_wline, byte, 0644); +MODULE_PARM_DESC(rds_wline, "RDS FIFO watermark in blocks (default 12)"); + +static bool fm_route_pcm = true; +module_param(fm_route_pcm, bool, 0644); +MODULE_PARM_DESC(fm_route_pcm, + "Write ROUTE_PCM on FM power-on so IIS2 receives audio"); + +static u16 fm_audio_ctrl0 = BCM_FM_AUDIO_CTRL0_DEFAULT; +module_param(fm_audio_ctrl0, ushort, 0644); +MODULE_PARM_DESC(fm_audio_ctrl0, + "FC15 reg0x05 audio ctrl (default 0x61 = route+I2S+75us deemph)"); + +struct bcm2078_bt { + struct device *dev; + void __iomem *gpio; + void __iomem *gpiocmd; + bool powered; + bool fm_on; + bool rds_on; + unsigned int fm_khz; + /* Last decoded RDS. Guarded by lock along with everything else. */ + char rds_ps[FM_RDS_PS_MAX + 1]; + char rds_rt[FM_RDS_TEXT_MAX + 1]; + char rds_ps_build[FM_RDS_PS_MAX]; + char rds_rt_build[FM_RDS_TEXT_MAX]; + u16 rds_pi; + u8 rds_pty; + u8 rds_rt_ab; + unsigned int rds_groups; + u8 reg_addr; + u8 reg_len; + u8 reg_data[FM_RDS_READ_MAX]; + u16 hci_opcode; + bool hci_valid; + u8 hci_rsp_len; + u8 hci_rsp[BCM_HCI_RSP_MAX]; + struct mutex lock; + struct v4l2_device v4l2_dev; + struct video_device vdev; + bool radio_registered; +}; + +/* ---------- GPIO (RetailOS sub_43D38C mode-2) ---------- */ + +static void bcm_43D38C(struct bcm2078_bt *bt, unsigned int gpio, u16 mode, int val) +{ + void __iomem *bank; + u32 pin, dir; + u8 cmd; + + if (!bt->gpio || !bt->gpiocmd || gpio == BCM_GPIO_NOP) + return; + bank = bt->gpio + 32 * (gpio >> 3); + pin = gpio & 7; + if (mode == 1) + cmd = val ? 15 : 14; + else if (mode == BCM_MODE_CLEAR) + cmd = 0; + else + cmd = (u8)mode; + + /* + * sub_43D38C sets DIR for every mode but 0xFFFE, mode 1 included: + * the a2 == 1 arm picks command 14 or 15 and falls through to the + * shared "DIR |= bit". Skipping it here left pad 70 an input, so + * the level never reached the pin. + */ + dir = readl(bank + 0x14); + if (mode == BCM_MODE_CLEAR) + dir &= ~BIT(pin); + else + dir |= BIT(pin); + writel(dir, bank + 0x14); + writel(((gpio >> 3) << 16) | (pin << 8) | cmd, bt->gpiocmd); +} + +/* sub_428F70(gpio, on): the pad's +0x0C bit, paired with every + * sub_43D38C power write in sub_17D4DC. */ +static void bcm_428F70(struct bcm2078_bt *bt, unsigned int gpio, int on) +{ + void __iomem *bank; + u32 pin, v; + + if (!bt->gpio) + return; + bank = bt->gpio + 32 * (gpio >> 3); + pin = gpio & 7; + v = readl(bank + 0x0c); + if (on) + v |= BIT(pin); + else + v &= ~BIT(pin); + writel(v, bank + 0x0c); +} + +/* + * The +0x0C half of the power sequence, kept separate from the mode-2 + * level pokes below. Those drive pads 97/98/119, which are the IIS2 PCM + * bus, and doing that early once correlated with resets back to + * RetailOS -- hence gpio_poke defaulting off. This is only an input + * enable on the power pad, so it is safe to run whenever the caller + * asks for power, and hci_bcm cannot do it through gpiod. + */ +static void bcm_power_pad_gate(struct bcm2078_bt *bt, int on) +{ + bcm_428F70(bt, BCM_GPIO_PWR, on); + dev_dbg(bt->dev, "pad %#x +0x0C -> %d\n", BCM_GPIO_PWR, on); +} + +static void bcm_power_pins_on(struct bcm2078_bt *bt) +{ + bcm_43D38C(bt, BCM_GPIO_NOP, 0, 0); + bcm_43D38C(bt, BCM_GPIO_A, BCM_MODE_POWER, 0); + bcm_43D38C(bt, BCM_GPIO_B, BCM_MODE_POWER, 0); + bcm_43D38C(bt, BCM_GPIO_C, BCM_MODE_POWER, 0); +} + +static void bcm_power_pins_off(struct bcm2078_bt *bt) +{ + bcm_43D38C(bt, BCM_GPIO_NOP, BCM_MODE_CLEAR, 0); + bcm_43D38C(bt, BCM_GPIO_A, BCM_MODE_CLEAR, 0); + bcm_43D38C(bt, BCM_GPIO_B, BCM_MODE_CLEAR, 0); + bcm_43D38C(bt, BCM_GPIO_C, BCM_MODE_CLEAR, 0); +} + +/* ---------- FM 0xFC15 via hci0 (not raw UART) ---------- */ + +static int bcm_fc15(struct bcm2078_bt *bt, const u8 *payload, u8 plen) +{ + struct hci_dev *hdev; + struct sk_buff *skb; + + hdev = hci_dev_get(0); + if (!hdev) { + dev_warn_ratelimited(bt->dev, + "FC15: no hci0 — bring up hci_bcm first\n"); + return -ENODEV; + } + if (!test_bit(HCI_UP, &hdev->flags)) { + hci_dev_put(hdev); + dev_warn_ratelimited(bt->dev, + "FC15: hci0 down — run n31-bt-up / HCIDEVUP\n"); + return -ENETDOWN; + } + + skb = __hci_cmd_sync(hdev, HCI_OP_FC15, plen, payload, HCI_CMD_TIMEOUT); + hci_dev_put(hdev); + if (IS_ERR(skb)) { + dev_dbg(bt->dev, "FC15 plen=%u → %ld\n", plen, PTR_ERR(skb)); + return PTR_ERR(skb); + } + dev_dbg(bt->dev, "FC15 plen=%u → OK len=%u\n", plen, skb->len); + kfree_skb(skb); + return 0; +} + +static int bcm_fm_w8(struct bcm2078_bt *bt, u8 reg, u8 val) +{ + u8 p[3] = { reg, 0x00, val }; + + return bcm_fc15(bt, p, 3); +} + +static int bcm_fm_w16(struct bcm2078_bt *bt, u8 reg, u16 val) +{ + u8 p[4] = { reg, 0x00, val & 0xff, val >> 8 }; + + return bcm_fc15(bt, p, 4); +} + +/* + * A read transaction is address + mode + byte count; the count is fixed + * per register except for the RDS FIFO, where the caller chooses it. + * + * The command-complete parameters begin with the status byte. Some + * firmware revisions echo the address and mode ahead of the payload and + * some do not, so accept either rather than assuming: the payload is + * whatever trails a 1- or 3-byte header of the expected total length. + */ +static int bcm_fm_read(struct bcm2078_bt *bt, u8 reg, u8 *out, u8 len) +{ + u8 req[3] = { reg, FM_MODE_READ, len }; + struct hci_dev *hdev; + struct sk_buff *skb; + unsigned int hdr; + int ret = 0; + + if (!len) + return -EINVAL; + + hdev = hci_dev_get(0); + if (!hdev) + return -ENODEV; + if (!test_bit(HCI_UP, &hdev->flags)) { + hci_dev_put(hdev); + return -ENETDOWN; + } + + skb = __hci_cmd_sync(hdev, HCI_OP_FC15, sizeof(req), req, + HCI_CMD_TIMEOUT); + hci_dev_put(hdev); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + if (skb->len == 1u + len) + hdr = 1; + else if (skb->len == 3u + len) + hdr = 3; + else { + dev_warn_ratelimited(bt->dev, + "FC15 read reg 0x%02x: %u bytes for len %u: %*ph\n", + reg, skb->len, len, + min_t(int, skb->len, 16), skb->data); + ret = -EPROTO; + goto out; + } + if (skb->data[0]) { + dev_dbg(bt->dev, "FC15 read reg 0x%02x status 0x%02x\n", + reg, skb->data[0]); + ret = -EIO; + goto out; + } + memcpy(out, skb->data + hdr, len); +out: + kfree_skb(skb); + return ret; +} + +static int bcm_fm_r8(struct bcm2078_bt *bt, u8 reg, u8 *val) +{ + return bcm_fm_read(bt, reg, val, 1); +} + +static int bcm_fm_r16(struct bcm2078_bt *bt, u8 reg, u16 *val) +{ + u8 b[2]; + int ret = bcm_fm_read(bt, reg, b, 2); + + if (!ret) + *val = (u16)b[0] | ((u16)b[1] << 8); + return ret; +} + +/* ---------- RDS ---------- */ + +/* + * The FIFO hands back RDS blocks as 3-byte records: two data bytes and a + * status byte whose low bits carry the block offset (A, B, C, C', D) and + * whose upper bits flag correction/error. Four blocks make a group, and + * only the well-formed ones are worth decoding. + */ +#define FM_RDS_REC_LEN 3 +#define FM_RDS_BLK_MASK 0x07 +#define FM_RDS_BLK_A 0 +#define FM_RDS_BLK_B 1 +#define FM_RDS_BLK_C 2 +#define FM_RDS_BLK_CP 3 +#define FM_RDS_BLK_D 4 +#define FM_RDS_ERR_MASK 0x80 + +/* Printable-ASCII guard: RDS pads with 0x20 and terminates RT with 0x0D. */ +static char bcm_rds_char(u8 c) +{ + return (c >= 0x20 && c < 0x7f) ? (char)c : ' '; +} + +static void bcm_rds_group(struct bcm2078_bt *bt, const u16 blk[4]) +{ + unsigned int type = blk[1] >> 12; + unsigned int ver = (blk[1] >> 11) & 1; + unsigned int i; + + bt->rds_pi = blk[0]; + bt->rds_pty = (blk[1] >> 5) & 0x1f; + bt->rds_groups++; + + if (type == 0) { + /* 0A/0B: two Program Service characters at offset 2*seg. */ + unsigned int seg = blk[1] & 0x03; + + bt->rds_ps_build[seg * 2] = bcm_rds_char(blk[3] >> 8); + bt->rds_ps_build[seg * 2 + 1] = bcm_rds_char(blk[3] & 0xff); + if (seg == 3) { + memcpy(bt->rds_ps, bt->rds_ps_build, FM_RDS_PS_MAX); + bt->rds_ps[FM_RDS_PS_MAX] = 0; + } + } else if (type == 2) { + /* + * 2A carries four RadioText characters per group, 2B two. The + * A/B flag toggles when the station starts a new message, so + * clear the buffer rather than blending two texts together. + */ + unsigned int seg = blk[1] & 0x0f; + unsigned int ab = (blk[1] >> 4) & 1; + unsigned int n = ver ? 2 : 4; + unsigned int base = seg * n; + + if (ab != bt->rds_rt_ab) { + bt->rds_rt_ab = ab; + memset(bt->rds_rt_build, ' ', FM_RDS_TEXT_MAX); + } + if (base + n <= FM_RDS_TEXT_MAX) { + if (ver) { + bt->rds_rt_build[base] = bcm_rds_char(blk[3] >> 8); + bt->rds_rt_build[base + 1] = + bcm_rds_char(blk[3] & 0xff); + } else { + bt->rds_rt_build[base] = bcm_rds_char(blk[2] >> 8); + bt->rds_rt_build[base + 1] = + bcm_rds_char(blk[2] & 0xff); + bt->rds_rt_build[base + 2] = + bcm_rds_char(blk[3] >> 8); + bt->rds_rt_build[base + 3] = + bcm_rds_char(blk[3] & 0xff); + } + memcpy(bt->rds_rt, bt->rds_rt_build, FM_RDS_TEXT_MAX); + bt->rds_rt[FM_RDS_TEXT_MAX] = 0; + for (i = FM_RDS_TEXT_MAX; i > 0; i--) { + if (bt->rds_rt[i - 1] != ' ') + break; + bt->rds_rt[i - 1] = 0; + } + } + } +} + +/* + * Drain the FIFO once and feed whole groups to the decoder. Blocks are + * accumulated by their offset code so a partial group at either end of + * the read is discarded rather than shifting everything that follows. + */ +static int bcm_rds_poll(struct bcm2078_bt *bt) +{ + u8 buf[FM_RDS_READ_MAX]; + u16 blk[4]; + bool have[4] = { false, false, false, false }; + unsigned int i; + int ret; + + ret = bcm_fm_read(bt, FM_REG_RDS_DATA, buf, sizeof(buf)); + if (ret) + return ret; + + for (i = 0; i + FM_RDS_REC_LEN <= sizeof(buf); i += FM_RDS_REC_LEN) { + u8 st = buf[i + 2]; + unsigned int off = st & FM_RDS_BLK_MASK; + u16 val = ((u16)buf[i] << 8) | buf[i + 1]; + + if (st & FM_RDS_ERR_MASK) + continue; + if (off == FM_RDS_BLK_CP) + off = FM_RDS_BLK_C; + if (off > FM_RDS_BLK_D) + continue; + if (off == FM_RDS_BLK_A) { + memset(have, 0, sizeof(have)); + blk[0] = val; + have[0] = true; + continue; + } + if (!have[0]) + continue; + blk[off > FM_RDS_BLK_C ? 3 : off] = val; + have[off > FM_RDS_BLK_C ? 3 : off] = true; + if (have[0] && have[1] && have[2] && have[3]) { + bcm_rds_group(bt, blk); + memset(have, 0, sizeof(have)); + } + } + return 0; +} + +static int bcm_rds_enable(struct bcm2078_bt *bt, bool on) +{ + int ret; + + ret = bcm_fm_w8(bt, FM_REG_RDS_SYSTEM, + FM_SYSTEM_FM_ON | (on ? FM_SYSTEM_RDS_ON : 0)); + if (ret) + return ret; + if (on) { + /* Interrupt once the FIFO holds this many blocks. */ + ret = bcm_fm_w8(bt, FM_REG_RDS_WLINE, rds_wline); + if (ret) + return ret; + } + bt->rds_on = on; + dev_info(bt->dev, "RDS %s\n", on ? "on" : "off"); + return 0; +} + +static int bcm_fm_power_on(struct bcm2078_bt *bt) +{ + int ret; + + /* Tuner and RDS decoder on together; RDS costs nothing when idle. */ + ret = bcm_fm_w8(bt, FM_REG_RDS_SYSTEM, + FM_SYSTEM_FM_ON | FM_SYSTEM_RDS_ON); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_RDS_WLINE, rds_wline); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_RDS_CTRL, 0x02); + if (ret) + return ret; + ret = bcm_fm_w16(bt, FM_REG_AUDIO_CTRL, fm_audio_ctrl0); + if (ret) + return ret; + + /* + * ROUTE_PCM is what actually puts tuner audio on the port IIS2 + * captures. The old code only read this register back and never + * wrote it, leaving the route wherever the last owner left it. + */ + if (fm_route_pcm) { + ret = bcm_fm_w8(bt, FM_REG_ROUTE_PCM, 0x01); + if (ret) + return ret; + } + + /* Stereo blend and soft-mute curve; 8 data bytes per the register map. */ + { + u8 p[11] = { + FM_REG_BLEND_SMUTE, FM_MODE_WRITE, + 0x21, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00 + }; + + ret = bcm_fc15(bt, p, sizeof(p)); + } + bt->fm_on = !ret; + bt->rds_on = !ret; + memset(bt->rds_ps, 0, sizeof(bt->rds_ps)); + memset(bt->rds_rt, 0, sizeof(bt->rds_rt)); + memset(bt->rds_ps_build, ' ', sizeof(bt->rds_ps_build)); + memset(bt->rds_rt_build, ' ', sizeof(bt->rds_rt_build)); + bt->rds_groups = 0; + dev_info(bt->dev, + "FM power ON audio_ctrl=0x%04x route_pcm=%d wline=%u%s\n", + fm_audio_ctrl0, fm_route_pcm, rds_wline, + ret ? " FAIL" : ""); + return ret; +} + +static int bcm_fm_power_off(struct bcm2078_bt *bt) +{ + int ret = bcm_fm_w8(bt, FM_REG_RDS_SYSTEM, 0x00); + + bt->fm_on = false; + bt->rds_on = false; + dev_info(bt->dev, "FM power OFF%s\n", ret ? " FAIL" : ""); + return ret; +} + +static int bcm_fm_tune_khz(struct bcm2078_bt *bt, unsigned int khz) +{ + u16 enc; + int ret; + + if (!bt->fm_on) { + ret = bcm_fm_power_on(bt); + if (ret) + return ret; + } + /* Band select is inverted: the Japan band is the one with the bit. */ + ret = bcm_fm_w8(bt, FM_REG_FM_CTRL, + FM_CTRL_STEREO_AUTO | + (khz >= 87500 ? 0 : FM_CTRL_BAND_JAPAN)); + if (ret) + return ret; + /* Arm the tune/search-complete and RDS flags we care about. */ + ret = bcm_fm_w16(bt, FM_REG_RDS_MASK, 0x1203); + if (ret) + return ret; + enc = (u16)((khz + 1536) & 0xffff); + ret = bcm_fm_w16(bt, FM_REG_FREQ, enc); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_SEARCH_TUNE, FM_TUNE_MODE_PRESET); + if (!ret) + bt->fm_khz = khz; + dev_info(bt->dev, "FM tune %u kHz enc=%04x%s\n", + khz, enc, ret ? " FAIL" : ""); + return ret; +} + +static int bcm_fm_seek(struct bcm2078_bt *bt, int up, u8 rssi) +{ + u8 flags = 0x70 | (up ? 0x80 : 0); + int ret; + + ret = bcm_fm_w8(bt, FM_REG_SEARCH_CTRL, flags); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_SEARCH_CTRL1, rssi ? rssi : 33); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_PRESCAN_QUALITY, 0x01); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_SEARCH_METHOD, 0x00); + if (ret) + return ret; + ret = bcm_fm_w8(bt, FM_REG_SEARCH_TUNE, FM_TUNE_MODE_SEARCH); + dev_info(bt->dev, "FM seek %s rssi=%u%s\n", + up ? "up" : "down", rssi ? rssi : 33, ret ? " FAIL" : ""); + return ret; +} + +/* + * The power-on stock actually performs, in stock's order. + * + * sub_57045C(1) is Bluetooth power on and is exactly three pad writes: + * + * sub_17D0(1) -> sub_43D38C(0x50, 2, 0) pad 80 function 2 + * sub_43D38C(0x46, 1, 1) pad 70 output high + * sub_428F70(0x46, 1) pad 70 +0x0C + * + * and sub_570054 only then opens the port and speaks at 115200. Power off + * is the same three with the argument inverted, which is what says pad 70 + * high means on. + * + * Deliberately NOT the mode-2 trio 97/98/119 that bcm_power_pins_on() + * drives. Those are claimed by sub_15DD5C at function 2 when FM powers up + * -- they are the IIS2 PCM pads -- and driving them here once put the + * device back into RetailOS. They are not part of sub_57045C. + * + * This has to run before hci_bcm opens the port, so it is called from + * probe rather than from the FM tuning path, which was the only caller + * bcm_power_on() ever had. + */ +/* + * WARNING: this is almost certainly NOT the power-on sequence. + * + * It transliterates sub_57045C, and sub_57045C is not what this file has + * been assuming. Its two callers name it, in stock's own log strings: + * + * sub_58B8F4: "Allow BT Chip to enter low power mode." -> sub_57045C(0) + * sub_58B930: "Prevent BT Chip from entering low power mode." -> sub_57045C(1) + * + * So sub_57045C is the low-power-mode inhibit -- the BT_WAKE line -- and + * not a supply or reset at all. The body agrees: sub_17D0(1) is + * sub_43D38C(0x50, 2, 0) and the rest is sub_43D38C(0x46, 1, 1) plus its + * gate. Two pins, toggled, nothing else. Our transliteration of it is + * faithful; what it was labelled is wrong. + * + * Which fits what the hardware says. Measured with this running: the UART + * transmits cleanly (UTRSTAT 0x06, tx and shift register both empty, zero + * errors), the rx FIFO stays empty, and UMSTAT reads 0x00 -- CTS never + * asserts. hci_bcm gets -110 on 0xfc18. That is a part which is not + * executing, and asserting its wake pin would not change that. + * + * The real power-on has not been found yet. The trail runs from + * "BTLocalDeviceSetModulePower" through sub_429538(handle, 3, -1, ...), + * which is a BT stack call several layers above any GPIO. + * + * Left in place because it is a correct transliteration of a real stock + * function and removing it would lose that, and because it is harmless. + * It is just not the thing that turns the chip on. + */ +static void bcm_bt_power_up(struct bcm2078_bt *bt) +{ + bcm_43D38C(bt, BCM_GPIO_UART, BCM_MODE_POWER, 0); + bcm_43D38C(bt, BCM_GPIO_PWR, 1, 1); + bcm_428F70(bt, BCM_GPIO_PWR, 1); + /* + * The part needs to come out of reset before it will answer. Stock + * waits here through a thunk into 0x2200xxxx, which is not in the + * image, so the length is not recoverable; this is the same + * conservative stand-in bcm_power_on() already uses. + */ + msleep(150); + dev_info(bt->dev, "BT power-on: pad 80 fn2, pad 70 high + gate\n"); +} + +static int bcm_power_on(struct bcm2078_bt *bt) +{ + int ret; + + bt->powered = true; + /* + * Rails first, then the pad gate, then the level: powering a pin + * before its supply is the wrong order and is what the de-init + * sequence unwinds. + */ + ret = bcm_bt_rails(true); + if (ret && ret != -ENODEV) + dev_warn(bt->dev, "bt rails on: %d\n", ret); + bcm_power_pad_gate(bt, 1); + if (!gpio_poke) { + dev_dbg(bt->dev, + "control pins left to hci_bcm (gpio_poke=0)\n"); + return 0; + } + bcm_power_pins_on(bt); + msleep(150); + dev_info(bt->dev, + "RetailOS mode-2 GPIOs forced on (gpio_poke=1)\n"); + return 0; +} + +/* + * sub_51688C: two delays, then sub_158C82 zeroing entries 3 and 5. + * Unwound in the reverse order of power-on -- levels, then the pad + * gate, then the rails -- so the part is not left driving a pin whose + * supply has already gone. Doing the rails is also what makes this a + * real off rather than an idle: without it the companion keeps drawing + * even with the control pin low. + */ +static void bcm_power_off(struct bcm2078_bt *bt) +{ + int ret; + + bt->powered = false; + if (gpio_poke) + bcm_power_pins_off(bt); + msleep(2); + bcm_power_pad_gate(bt, 0); + ret = bcm_bt_rails(false); + if (ret && ret != -ENODEV) + dev_warn(bt->dev, "bt rails off: %d\n", ret); +} + +/* ---------- V4L2 radio (tuner control only; PCM is ALSA IIS2) ---------- */ + +static int bcm_radio_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "bcm2078-fm", sizeof(cap->driver)); + strscpy(cap->card, "N31 BCM2078 FM", sizeof(cap->card)); + strscpy(cap->bus_info, "hci0:0xFC15", sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER | + V4L2_CAP_HW_FREQ_SEEK; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int bcm_radio_g_tuner(struct file *file, void *fh, struct v4l2_tuner *t) +{ + struct bcm2078_bt *bt = video_drvdata(file); + u8 rssi = 0; + + if (t->index > 0) + return -EINVAL; + strscpy(t->name, "FM", sizeof(t->name)); + t->type = V4L2_TUNER_RADIO; + t->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS; + t->rangelow = BCM_FM_FREQ_TO_V4L(BCM_FM_KHZ_MIN); + t->rangehigh = BCM_FM_FREQ_TO_V4L(BCM_FM_KHZ_MAX); + t->rxsubchans = V4L2_TUNER_SUB_STEREO; + t->audmode = V4L2_TUNER_MODE_STEREO; + t->afc = 0; + + /* + * Report what the tuner actually sees. RSSI is a single byte on the + * chip's own scale, spread over the 16-bit field V4L2 expects; a + * failed read is reported as no signal rather than as an error, so + * that polling a powered-down tuner stays harmless. + */ + mutex_lock(&bt->lock); + if (bt->fm_on && !bcm_fm_r8(bt, FM_REG_RSSI, &rssi)) + t->signal = (u16)rssi * 257; + else + t->signal = 0; + mutex_unlock(&bt->lock); + return 0; +} + +static int bcm_radio_s_tuner(struct file *file, void *fh, + const struct v4l2_tuner *t) +{ + if (t->index > 0) + return -EINVAL; + return 0; +} + +static int bcm_radio_g_frequency(struct file *file, void *fh, + struct v4l2_frequency *f) +{ + struct bcm2078_bt *bt = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = BCM_FM_FREQ_TO_V4L(bt->fm_khz ? bt->fm_khz : + BCM_FM_KHZ_DEFAULT); + return 0; +} + +static int bcm_radio_s_frequency(struct file *file, void *fh, + const struct v4l2_frequency *f) +{ + struct bcm2078_bt *bt = video_drvdata(file); + unsigned int khz; + int ret; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + khz = BCM_FM_V4L_TO_FREQ(f->frequency); + if (khz < BCM_FM_KHZ_MIN) + khz = BCM_FM_KHZ_MIN; + if (khz > BCM_FM_KHZ_MAX) + khz = BCM_FM_KHZ_MAX; + + mutex_lock(&bt->lock); + if (!bt->powered) + bcm_power_on(bt); + if (!bt->fm_on) { + ret = bcm_fm_power_on(bt); + if (ret) + goto out; + } + ret = bcm_fm_tune_khz(bt, khz); +out: + mutex_unlock(&bt->lock); + return ret; +} + +static int bcm_radio_s_hw_freq_seek(struct file *file, void *fh, + const struct v4l2_hw_freq_seek *a) +{ + struct bcm2078_bt *bt = video_drvdata(file); + int ret; + + if (a->tuner != 0 || a->type != V4L2_TUNER_RADIO) + return -EINVAL; + + mutex_lock(&bt->lock); + if (!bt->powered) + bcm_power_on(bt); + if (!bt->fm_on) { + ret = bcm_fm_power_on(bt); + if (ret) + goto out; + } + ret = bcm_fm_seek(bt, a->seek_upward ? 1 : 0, 33); +out: + mutex_unlock(&bt->lock); + return ret; +} + +static int bcm_radio_enum_freq_bands(struct file *file, void *fh, + struct v4l2_frequency_band *band) +{ + if (band->tuner != 0 || band->index > 0) + return -EINVAL; + band->type = V4L2_TUNER_RADIO; + band->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + band->rangelow = BCM_FM_FREQ_TO_V4L(BCM_FM_KHZ_MIN); + band->rangehigh = BCM_FM_FREQ_TO_V4L(BCM_FM_KHZ_MAX); + band->modulation = V4L2_BAND_MODULATION_FM; + return 0; +} + +static const struct v4l2_ioctl_ops bcm_radio_ioctl_ops = { + .vidioc_querycap = bcm_radio_querycap, + .vidioc_g_tuner = bcm_radio_g_tuner, + .vidioc_s_tuner = bcm_radio_s_tuner, + .vidioc_g_frequency = bcm_radio_g_frequency, + .vidioc_s_frequency = bcm_radio_s_frequency, + .vidioc_s_hw_freq_seek = bcm_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = bcm_radio_enum_freq_bands, +}; + +static const struct v4l2_file_operations bcm_radio_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .unlocked_ioctl = video_ioctl2, +}; + +static int bcm_radio_register(struct bcm2078_bt *bt) +{ + int ret; + + ret = v4l2_device_register(bt->dev, &bt->v4l2_dev); + if (ret) + return ret; + + strscpy(bt->v4l2_dev.name, "bcm2078-fm", sizeof(bt->v4l2_dev.name)); + bt->vdev = (struct video_device){ + .name = "N31 FM Radio", + .v4l2_dev = &bt->v4l2_dev, + .fops = &bcm_radio_fops, + .ioctl_ops = &bcm_radio_ioctl_ops, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER | + V4L2_CAP_HW_FREQ_SEEK, + }; + video_set_drvdata(&bt->vdev, bt); + + ret = video_register_device(&bt->vdev, VFL_TYPE_RADIO, -1); + if (ret) { + v4l2_device_unregister(&bt->v4l2_dev); + return ret; + } + bt->radio_registered = true; + bt->fm_khz = BCM_FM_KHZ_DEFAULT; + dev_info(bt->dev, "V4L2 radio %s (0xFC15; headphones required; no FM→A2DP)\n", + video_device_node_name(&bt->vdev)); + return 0; +} + +static void bcm_radio_unregister(struct bcm2078_bt *bt) +{ + if (!bt->radio_registered) + return; + video_unregister_device(&bt->vdev); + v4l2_device_unregister(&bt->v4l2_dev); + bt->radio_registered = false; +} + +/* ---------- sysfs (debug FM + GPIO helper) ---------- */ + +static ssize_t power_on_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", bt->powered ? 1 : 0); +} + +static ssize_t power_on_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + unsigned int on; + + if (kstrtouint(buf, 0, &on)) + return -EINVAL; + mutex_lock(&bt->lock); + if (on) + bcm_power_on(bt); + else + bcm_power_off(bt); + mutex_unlock(&bt->lock); + return count; +} +static DEVICE_ATTR_RW(power_on); + +static ssize_t patchram_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct hci_dev *hdev = hci_dev_get(0); + int up = 0; + + if (hdev) { + up = test_bit(HCI_UP, &hdev->flags); + hci_dev_put(hdev); + } + return sysfs_emit(buf, "%d\n", up); +} + +static ssize_t patchram_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + /* Interim: patchram is owned by hci_bcm/btbcm — do not steal UART. */ + dev_info(dev, + "patchram retired — use hci0 (hci_bcm + brcm/BCM2076B1.hcd)\n"); + return count; +} +static DEVICE_ATTR_RW(patchram); + +static ssize_t fm_power_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + unsigned int on; + int ret; + + if (kstrtouint(buf, 0, &on)) + return -EINVAL; + mutex_lock(&bt->lock); + if (!bt->powered) + bcm_power_on(bt); + ret = on ? bcm_fm_power_on(bt) : bcm_fm_power_off(bt); + mutex_unlock(&bt->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(fm_power); + +static ssize_t fm_tune_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + unsigned int khz; + int ret; + + if (kstrtouint(buf, 0, &khz)) + return -EINVAL; + if (khz < 1000) + khz *= 100; + mutex_lock(&bt->lock); + if (!bt->powered) + bcm_power_on(bt); + if (!bt->fm_on) { + ret = bcm_fm_power_on(bt); + if (ret) + goto out; + } + ret = bcm_fm_tune_khz(bt, khz); +out: + mutex_unlock(&bt->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(fm_tune); + +static ssize_t fm_seek_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + int up = 1; + int ret; + + if (buf[0] == 'd' || buf[0] == '0' || buf[0] == '-') + up = 0; + mutex_lock(&bt->lock); + if (!bt->powered) + bcm_power_on(bt); + ret = bcm_fm_seek(bt, up, 33); + mutex_unlock(&bt->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(fm_seek); + +static ssize_t patchram_info_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + return sysfs_emit(buf, + "hci=hci_bcm/serdev on uart1 (not this companion)\n" + "hcd=/lib/firmware/brcm/BCM2076B1.hcd\n" + "bringup=/bin/n31-bt-up → hci0 + HCIDEVUP\n" + "gpio=97/98/119 owned by hci_bcm (gpio_poke=1 to override)\n" + "radio=/dev/radio0 V4L2 (tune/seek/signal); sysfs fm_* = debug\n" + "metrics=fm_rssi, fm_snr\n" + "rds=fm_rds (read drains the FIFO; PS/RT/PI/PTY decoded)\n" + "raw=fm_reg \"r|w \" per BlueTool FM_RDS_Command map\n" + "hci=hci_cmd \" [bytes]\" for any command incl. vendor\n" + "audio_bcm=FC15 reg0x05 bit0x20 routes tuner audio to the PCM port\n" + "audio_soc=IIS2 is only clocked while the capture PCM is open:\n" + " arecord -D hw:0,1 -f S16_LE -r 44100 -c 2 | aplay -D hw:0,0 -\n" + "fm_default=94700 kHz deemph=75us (Canada)\n" + "no_fm_a2dp=1 (local speakers/HP only)\n"); +} +static DEVICE_ATTR_RO(patchram_info); + +/* Signal quality. Both are one byte; RSSI is the tuner's own scale. */ +static ssize_t fm_rssi_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + u8 v = 0; + int ret; + + mutex_lock(&bt->lock); + ret = bcm_fm_r8(bt, FM_REG_RSSI, &v); + mutex_unlock(&bt->lock); + return ret ? ret : sysfs_emit(buf, "%u\n", v); +} +static DEVICE_ATTR_RO(fm_rssi); + +static ssize_t fm_snr_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + u8 v = 0; + int ret; + + mutex_lock(&bt->lock); + ret = bcm_fm_r8(bt, FM_REG_SNR, &v); + mutex_unlock(&bt->lock); + return ret ? ret : sysfs_emit(buf, "%u\n", v); +} +static DEVICE_ATTR_RO(fm_snr); + +/* Drain the FIFO, then report whatever has been decoded so far. */ +static ssize_t fm_rds_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + u16 flag = 0; + int ret; + + mutex_lock(&bt->lock); + ret = bt->fm_on ? bcm_rds_poll(bt) : -ENODEV; + if (bt->fm_on && bcm_fm_r16(bt, FM_REG_RDS_FLAG, &flag)) + flag = 0; + ret = sysfs_emit(buf, + "on=%d poll=%d groups=%u flag=0x%04x\n" + "pi=0x%04x pty=%u\n" + "ps=%s\n" + "rt=%s\n", + bt->rds_on, ret, bt->rds_groups, flag, + bt->rds_pi, bt->rds_pty, + bt->rds_ps, bt->rds_rt); + mutex_unlock(&bt->lock); + return ret; +} + +/* "1" / "0" toggles the decoder without disturbing the tuner. */ +static ssize_t fm_rds_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + unsigned int on; + int ret; + + if (kstrtouint(buf, 0, &on)) + return -EINVAL; + mutex_lock(&bt->lock); + ret = bcm_rds_enable(bt, on); + mutex_unlock(&bt->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(fm_rds); + +/* + * Raw register access, for walking the map in the BlueTool hcidef without + * a driver change: + * echo "w 05 0061" > fm_reg write (1 or 2 bytes by value width) + * echo "r 0f 1" > fm_reg read, result appears on the next read + */ +static ssize_t fm_reg_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + + if (!bt->reg_len) + return sysfs_emit(buf, "no read pending\n"); + return sysfs_emit(buf, "0x%02x: %*ph\n", + bt->reg_addr, bt->reg_len, bt->reg_data); +} + +static ssize_t fm_reg_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + unsigned int reg, val; + char op; + int ret, n; + + n = sscanf(buf, " %c %x %x", &op, ®, &val); + if (n < 3 || reg > 0xff) + return -EINVAL; + + mutex_lock(&bt->lock); + if (op == 'r' || op == 'R') { + if (!val || val > sizeof(bt->reg_data)) + ret = -EINVAL; + else + ret = bcm_fm_read(bt, reg, bt->reg_data, val); + bt->reg_addr = reg; + bt->reg_len = ret ? 0 : val; + } else if (op == 'w' || op == 'W') { + ret = (val > 0xff) ? bcm_fm_w16(bt, reg, val) : + bcm_fm_w8(bt, reg, val); + } else { + ret = -EINVAL; + } + mutex_unlock(&bt->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(fm_reg); + +/* + * Arbitrary HCI command, so the whole vendor surface is reachable from a + * shell without BlueZ tools present: + * echo "fc15 0f 01 01" > hci_cmd opcode then parameter bytes + * cat hci_cmd status and command-complete payload + * The kernel owns hci0; this only borrows it for one synchronous command. + */ +static ssize_t hci_cmd_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + + if (!bt->hci_valid) + return sysfs_emit(buf, "no command run\n"); + if (!bt->hci_rsp_len) + return sysfs_emit(buf, "opcode 0x%04x: no payload\n", + bt->hci_opcode); + return sysfs_emit(buf, "opcode 0x%04x: %*ph\n", + bt->hci_opcode, bt->hci_rsp_len, bt->hci_rsp); +} + +static ssize_t hci_cmd_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + struct bcm2078_bt *bt = dev_get_drvdata(dev); + u8 params[BCM_HCI_PARAM_MAX]; + unsigned int opcode, v, plen = 0; + struct hci_dev *hdev; + struct sk_buff *skb; + const char *p = buf; + int used, ret = 0; + + if (sscanf(p, " %x%n", &opcode, &used) != 1 || opcode > 0xffff) + return -EINVAL; + p += used; + while (plen < sizeof(params) && sscanf(p, " %x%n", &v, &used) == 1) { + if (v > 0xff) + return -EINVAL; + params[plen++] = (u8)v; + p += used; + } + + hdev = hci_dev_get(0); + if (!hdev) + return -ENODEV; + if (!test_bit(HCI_UP, &hdev->flags)) { + hci_dev_put(hdev); + return -ENETDOWN; + } + + mutex_lock(&bt->lock); + skb = __hci_cmd_sync(hdev, opcode, plen, plen ? params : NULL, + HCI_CMD_TIMEOUT); + bt->hci_opcode = opcode; + bt->hci_valid = true; + if (IS_ERR(skb)) { + bt->hci_rsp_len = 0; + ret = PTR_ERR(skb); + } else { + bt->hci_rsp_len = min_t(unsigned int, skb->len, + sizeof(bt->hci_rsp)); + memcpy(bt->hci_rsp, skb->data, bt->hci_rsp_len); + kfree_skb(skb); + } + mutex_unlock(&bt->lock); + hci_dev_put(hdev); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(hci_cmd); + +static struct attribute *bcm_attrs[] = { + &dev_attr_power_on.attr, + &dev_attr_patchram.attr, + &dev_attr_patchram_info.attr, + &dev_attr_fm_power.attr, + &dev_attr_fm_tune.attr, + &dev_attr_fm_seek.attr, + &dev_attr_fm_rssi.attr, + &dev_attr_fm_snr.attr, + &dev_attr_fm_rds.attr, + &dev_attr_fm_reg.attr, + &dev_attr_hci_cmd.attr, + NULL, +}; +ATTRIBUTE_GROUPS(bcm); + +static int bcm2078_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm2078_bt *bt; + struct regulator *bt_vreg; + int ret; + + /* + * Take the rail first, and let the kernel handle the ordering. + * + * This driver is built in and probes at about t=2.4s; gpio-d1830, + * which owns the PMIC, is a module userspace loads at about t=7.1s. + * Everything in between used to fail with -ENODEV and there was no + * way to wait, so the controller was never powered and hci_bcm timed + * out talking to a dead part. Deferring is the whole point: the + * kernel re-probes this driver once the provider registers. + * + * Optional on purpose. A device tree without bt-supply keeps the old + * hook path rather than refusing to probe at all. + */ + bt_vreg = devm_regulator_get_optional(dev, "bt"); + if (IS_ERR(bt_vreg)) { + ret = PTR_ERR(bt_vreg); + bt_vreg = NULL; + if (ret == -EPROBE_DEFER) { + dev_info(dev, "waiting for the bt rail provider\n"); + return ret; + } + dev_info(dev, "no bt-supply (%d); using the legacy rails hook\n", + ret); + } else { + bcm_bt_vreg = bt_vreg; + dev_info(dev, "bt rail acquired as a regulator\n"); + } + + bt = devm_kzalloc(dev, sizeof(*bt), GFP_KERNEL); + if (!bt) + return -ENOMEM; + bt->dev = dev; + bt->fm_khz = BCM_FM_KHZ_DEFAULT; + mutex_init(&bt->lock); + platform_set_drvdata(pdev, bt); + + bt->gpio = devm_ioremap(dev, BCM_GPIO_PHYS, 0x400); + if (!bt->gpio) + return -ENOMEM; + bt->gpiocmd = bt->gpio + BCM_GPIOCMD_OFF; + + if (sysfs_create_groups(&dev->kobj, bcm_groups)) + dev_warn(dev, "sysfs groups failed\n"); + + ret = bcm_radio_register(bt); + if (ret) + dev_warn(dev, "V4L2 radio register: %d (sysfs FM still OK)\n", + ret); + + /* + * Power the controller up here, before hci_bcm opens the port. + * + * Nothing used to do this at probe: bcm_power_on() hangs off the V4L2 + * FM tuning path and is never reached on a plain boot, so hci_bcm bound + * to a part that had never been powered and every command timed out. + * + * The older note here said not to touch GPIOs at probe because early + * mode-2 correlated with a reset back to RetailOS. That observation + * stands and is why bcm_bt_power_up() deliberately leaves 97/98/119 + * alone -- those are the IIS2 PCM pads, claimed by sub_15DD5C when FM + * powers on, and driving them is what reproduces the reset. They are + * not part of sub_57045C. What is left is pad 80 to function 2 and + * pad 70 high, which is the whole of stock Bluetooth power-on. + */ + bcm_bt_power_up(bt); + dev_info(dev, + "BCM2078 companion (GPIO+FM+V4L2) — UART1 owned by hci_bcm\n"); + return 0; +} + +/* + * sub_51688C is the de-init OSOS runs when Bluetooth goes away, and + * bcm_power_off now implements it. Reaching it only from remove() meant + * the companion kept its rails across a reboot, so the part came up in + * whatever state the previous kernel left it rather than from reset. + */ +static void bcm2078_shutdown(struct platform_device *pdev) +{ + struct bcm2078_bt *bt = platform_get_drvdata(pdev); + + if (!bt) + return; + mutex_lock(&bt->lock); + bcm_power_off(bt); + mutex_unlock(&bt->lock); +} + +static void bcm2078_remove(struct platform_device *pdev) +{ + struct bcm2078_bt *bt = platform_get_drvdata(pdev); + + bcm_radio_unregister(bt); + sysfs_remove_groups(&pdev->dev.kobj, bcm_groups); + /* + * Turn the tuner off, but leave the control pins alone unless this + * driver was the one driving them -- otherwise unloading it drops + * REG_ON and takes hci0 down with it. + */ + if (bt->fm_on) + bcm_fm_power_off(bt); + if (gpio_poke) + bcm_power_off(bt); + + /* + * bcm_bt_vreg is a file-scope pointer to a devm_regulator_get_optional() + * handle, so devm frees it the moment this returns. Leaving the pointer + * set means the next bcm_bt_rails() -- from a rebind, or from the FM + * side, which shares these statics -- dereferences freed memory. Drop + * it here, along with the enable state it tracks. + */ + bcm_bt_vreg = NULL; + bcm_bt_vreg_on = false; +} + +static const struct of_device_id bcm2078_of_match[] = { + { .compatible = "apple,n31-bcm2078-companion" }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm2078_of_match); + +static struct platform_driver bcm2078_driver = { + .probe = bcm2078_probe, + .remove = bcm2078_remove, + .shutdown = bcm2078_shutdown, + .driver = { + .name = "bcm2078-bt", + .of_match_table = bcm2078_of_match, + }, +}; + +module_platform_driver(bcm2078_driver); + +MODULE_DESCRIPTION("N31 BCM2078 GPIO companion + V4L2 FM (0xFC15 via hci0)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/clk-s5l8702.c b/drivers/clk/clk-s5l8702.c old mode 100644 new mode 100755 index e003018011e972..7257863b05af12 --- a/drivers/clk/clk-s5l8702.c +++ b/drivers/clk/clk-s5l8702.c @@ -1,16 +1,36 @@ // SPDX-License-Identifier: GPL-2.0 /* - * S5L8702 Clockgates driver + * S5L8702 / S5L8740 Clockgates + * + * Bring-up policy (Phases 0–4): ungate-all documented PWRCON banks so + * peripherals stay alive without a Linux consumer. CCF also marks the + * published gates CLK_IS_CRITICAL | CLK_IGNORE_UNUSED so + * clk_disable_unused cannot write those bits later. + * + * Selective IIS/UART/CG16 CCF consumers are DEFERRED to Phase 5 + * (p5-ccf-pm). Rationale: HCI/ALSA/FM glass proof still needs a stable + * clock baseline; early selective gating caused false leads (timer poke, + * SYS remux). Keep absolute CLKCON+0x30 play (0x32190-class) / + * idle (0x1c20) in the IIS drivers until audio/BT prove out. + * + * Never remux SYS PLL (+0x00/+0x04) — that kills live DRAM. + * Never write CLKCON+0x50 — that is the fatal/WDT latch (0xA5). + * Never poke the TIMER MMIO block @0x3C700000 from here. */ #include +#include #include #include -#include #include #include #include +#define CLKCON_PWRCON0 0x48 +#define CLKCON_PWRCON1 0x4c +#define CLKCON_PWRCON2 0x58 +#define CLKCON_PWRCON4 0x6c + struct s5l8702_clk_data { void __iomem *regs; struct clk_hw_onecell_data *hw_data; @@ -39,6 +59,36 @@ static const struct s5l8702_clk_gate s5l8702_gates[] = { [CLK_PRNG] = GATE("prng", NULL, 0x4c, 0), }; +/* + * SET_TO_DISABLE: bit clear = clock running. Write 0 to the known PWRCON + * banks so every AHB/APB gate is on even without a driver. CG16 enable + * bits (RetailOS 41CBD8) are the high halves of the divider regs — clear + * those bits only; leave the divider fields WTF/U-Boot programmed. + */ +static void s5l8740_ungate_all(struct device *dev, void __iomem *regs) +{ + dev_info(dev, + "PWRCON before: +48=%08x +4c=%08x +58=%08x +6c=%08x SYS+00=%08x\n", + readl(regs + CLKCON_PWRCON0), readl(regs + CLKCON_PWRCON1), + readl(regs + CLKCON_PWRCON2), readl(regs + CLKCON_PWRCON4), + readl(regs + 0x00)); + + writel(0, regs + CLKCON_PWRCON0); + writel(0, regs + CLKCON_PWRCON1); + writel(0, regs + CLKCON_PWRCON2); + writel(0, regs + CLKCON_PWRCON4); + + writel(readl(regs + 0x08) & ~0x80008000u, regs + 0x08); + writel(readl(regs + 0x0c) & ~0x80008000u, regs + 0x0c); + writel(readl(regs + 0x10) & ~0x8000u, regs + 0x10); + writel(readl(regs + 0x14) & ~0x80008000u, regs + 0x14); + + dev_info(dev, + "PWRCON after ungate-all: +48=%08x +4c=%08x +58=%08x +6c=%08x\n", + readl(regs + CLKCON_PWRCON0), readl(regs + CLKCON_PWRCON1), + readl(regs + CLKCON_PWRCON2), readl(regs + CLKCON_PWRCON4)); +} + static int s5l8702_clk_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -46,23 +96,24 @@ static int s5l8702_clk_probe(struct platform_device *pdev) struct clk_hw_onecell_data *hw_data; int i, ret; size_t num_clks; + const unsigned long gate_flags = + CLK_IGNORE_UNUSED | CLK_IS_CRITICAL; num_clks = ARRAY_SIZE(s5l8702_gates); clk_data = devm_kzalloc(dev, sizeof(*clk_data), GFP_KERNEL); - if (!clk_data) { + if (!clk_data) return -ENOMEM; - } clk_data->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(clk_data->regs)) { + if (IS_ERR(clk_data->regs)) return PTR_ERR(clk_data->regs); - } + + s5l8740_ungate_all(dev, clk_data->regs); hw_data = devm_kzalloc(dev, struct_size(hw_data, hws, num_clks), GFP_KERNEL); - if (!hw_data) { + if (!hw_data) return -ENOMEM; - } hw_data->num = num_clks; @@ -71,22 +122,23 @@ static int s5l8702_clk_probe(struct platform_device *pdev) for (i = 0; i < num_clks; i++) { const struct s5l8702_clk_gate *clk_gate = &s5l8702_gates[i]; - hw_data->hws[i] = devm_clk_hw_register_gate(dev, clk_gate->name, clk_gate->parent_name, 0, - clk_data->regs + clk_gate->reg, clk_gate->bit, CLK_GATE_SET_TO_DISABLE, &clk_data->lock); + hw_data->hws[i] = devm_clk_hw_register_gate(dev, clk_gate->name, + clk_gate->parent_name, gate_flags, + clk_data->regs + clk_gate->reg, clk_gate->bit, + CLK_GATE_SET_TO_DISABLE, &clk_data->lock); - if (IS_ERR(hw_data->hws[i])) { + if (IS_ERR(hw_data->hws[i])) return PTR_ERR(hw_data->hws[i]); - } } clk_data->hw_data = hw_data; ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, hw_data); - if (ret) { + if (ret) return ret; - } - dev_info(dev, "Registered %d clockgate(s)", num_clks); + dev_info(dev, "Registered %zu clockgate(s), unused left enabled\n", + num_clks); return 0; } diff --git a/drivers/clk/n31-early-bringup.c b/drivers/clk/n31-early-bringup.c new file mode 100755 index 00000000000000..729062132d7deb --- /dev/null +++ b/drivers/clk/n31-early-bringup.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * N31 earliest bring-up hooks (before any platform drivers): + * 1) SEC WDT @0x3C800000 disarm (stage0 sequence + clear enable bits) + * 2) CLKCON+0x50 fatal latch clear + * 3) Optional glass witness: backlight OFF @0x3E000000 (U-Boot leaves BL on) + */ +#include +#include +#include + +#define N31_WDT_PHYS 0x3c800000ul +#define N31_BL_PHYS 0x3e000000ul +#define N31_CLKCON_PHYS 0x3c500000ul + +static void n31_wdt_disarm_full(void) +{ + void __iomem *wdt, *fatal; + u32 wcon, wcnt; + + wdt = ioremap(N31_WDT_PHYS, 8); + if (!wdt) + return; + + wcon = readl(wdt); + wcnt = readl(wdt + 4); + writel(0, wdt + 4); + writel(0, wdt); + writel(0, wdt + 4); + writel(0, wdt); + + pr_alert("N31>> WDT disarm con=%08x cnt=%08x now=%08x/%08x\n", + wcon, wcnt, readl(wdt), readl(wdt + 4)); + iounmap(wdt); + + fatal = ioremap(N31_CLKCON_PHYS + 0x50, 4); + if (fatal) { + u32 v = readl(fatal); + + if (v) + pr_alert("N31>> CLKCON+0x50 was %08x — clearing\n", v); + writel(0, fatal); + iounmap(fatal); + } +} + +static void n31_bl_off_witness(void) +{ + void __iomem *aux; + + aux = ioremap(N31_BL_PHYS, 0x10); + if (!aux) + return; + writel(readl(aux + 0x04) & ~1u, aux + 0x04); + writel(readl(aux + 0x08) & ~1u, aux + 0x08); + pr_alert("N31>> BL OFF witness (expect screen black)\n"); + iounmap(aux); +} + +static int __init n31_early_bringup(void) +{ + n31_wdt_disarm_full(); + n31_bl_off_witness(); + return 0; +} +early_initcall(n31_early_bringup); diff --git a/drivers/crypto/s5l8702-aes.c b/drivers/crypto/s5l8702-aes.c index 23463966c19ff3..adfb411b55bc40 100644 --- a/drivers/crypto/s5l8702-aes.c +++ b/drivers/crypto/s5l8702-aes.c @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 /* * S5L8702 AES Accelerator Driver + * + * Used on N31 (S5L8740) via compatible "samsung,s5l8702-aes". + * Fused UID/GID CFG follows N31 RetailOS (0x0F enc / 0x0E dec), not + * Rockbox S5L8702 hwkeyaes 0x09/0x08 — see s5l8702_aes_hw_init(). */ #include @@ -71,7 +75,8 @@ #define S5L8702_AES_CMD_CONTINUE 3 #define S5L8702_AES_CFG_KEYSIZE GENMASK(5, 4) -#define S5L8702_AES_CFG_PAUSE GENMASK(2, 1) +/* Bits 2:1 — semantics unproven ("pause" was a guess). N31 fused GID uses them. */ +#define S5L8702_AES_CFG_UNK_2_1 GENMASK(2, 1) #define S5L8702_AES_IRQ_ALL GENMASK(3, 0) #define S5L8702_AES_IRQ_XFR_DONE BIT(0) @@ -259,6 +264,8 @@ static inline int s5l8702_aes_check_fused_key_length(struct crypto_skcipher *tfm return -EINVAL; } + /* Fused keys ignore key bytes; still record length for ctx consistency. */ + ctx->keylen = keylen; return 0; } @@ -270,7 +277,7 @@ static void s5l8702_aes_hw_exit(struct s5l8702_aes_dev *aes_dev) clk_disable_unprepare(aes_dev->clk); } -static int s5l8702_aes_hw_init(struct s5l8702_aes_ctx *ctx, const u8 *iv, bool encrypt) +static int s5l8702_aes_hw_init(struct s5l8702_aes_ctx *ctx, bool encrypt) { struct s5l8702_aes_dev *aes_dev = ctx->aes_dev; struct device *dev = aes_dev->dev; @@ -310,50 +317,61 @@ static int s5l8702_aes_hw_init(struct s5l8702_aes_ctx *ctx, const u8 *iv, bool e } } - // unknown register + /* Unknown register; Rockbox/OSOS write 0 for fused and software paths. */ s5l8702_aes_writel(aes_dev, S5L8702_AES_UNK8C, 0); - // config - cfg = s5l8702_aes_readl(aes_dev, S5L8702_AES_CFG); - - // encrypt/decrypt - if (encrypt) - cfg |= BIT(0); - else - cfg &= ~BIT(0); - - // pause engine - cfg |= S5L8702_AES_CFG_PAUSE; - - // chaining mode - if (ctx->cbc) { - // CBC - cfg |= BIT(3); - - // IV - s5l8702_aes_write_iv(aes_dev, iv); + /* + * UID/GID fused CFG: N31/S5L8740 RetailOS (OSOS sub_422FFA / nimbus + * 422FFA MMIO) writes exactly (encrypt ? 1 : 0) | 0xE → 0x0F / 0x0E + * (CBC + bits 2:1 + direction; no software key-size field). + * + * Rockbox S5L8702 hwkeyaes() uses 0x09 encrypt / 0x08 decrypt — do + * NOT transplant that encoding onto this N31 driver without separate + * proof; the SoCs are related but CFG fused encoding differs here. + */ + if (hw_key_type == S5L8702_AES_KEY_TYPE_GLOBAL_ID || + hw_key_type == S5L8702_AES_KEY_TYPE_USER_ID) { + cfg = (encrypt ? BIT(0) : 0) | 0xeu; } else { - // ECB - cfg &= ~BIT(3); - } - - // key size - cfg &= ~S5L8702_AES_CFG_KEYSIZE; - - switch (ctx->keylen) { - case AES_KEYSIZE_128: - cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, S5L8702_AES_KEY_SIZE_128); - break; - case AES_KEYSIZE_192: - cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, S5L8702_AES_KEY_SIZE_192); - break; - case AES_KEYSIZE_256: - cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, S5L8702_AES_KEY_SIZE_256); - break; - default: - dev_err(dev, "Invalid key length: %u\n", ctx->keylen); - ret = -EINVAL; - goto err_hw; + /* Software / zero-key ECB/CBC — keep existing RMW path. */ + cfg = s5l8702_aes_readl(aes_dev, S5L8702_AES_CFG); + + if (encrypt) + cfg |= BIT(0); + else + cfg &= ~BIT(0); + + cfg |= S5L8702_AES_CFG_UNK_2_1; + + if (ctx->cbc) + cfg |= BIT(3); /* CBC */ + else + cfg &= ~BIT(3); /* ECB */ + + cfg &= ~S5L8702_AES_CFG_KEYSIZE; + + /* Zero-key selector: leave KEYSIZE cleared (same as before). */ + if (hw_key_type == S5L8702_AES_KEY_TYPE_USER_DEFINE) { + switch (ctx->keylen) { + case AES_KEYSIZE_128: + cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, + S5L8702_AES_KEY_SIZE_128); + break; + case AES_KEYSIZE_192: + cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, + S5L8702_AES_KEY_SIZE_192); + break; + case AES_KEYSIZE_256: + cfg |= FIELD_PREP(S5L8702_AES_CFG_KEYSIZE, + S5L8702_AES_KEY_SIZE_256); + break; + default: + dev_err(dev, "Invalid key length: %u\n", + ctx->keylen); + ret = -EINVAL; + goto err_hw; + } + } } s5l8702_aes_writel(aes_dev, S5L8702_AES_CFG, cfg); @@ -371,10 +389,15 @@ static int s5l8702_aes_hw_crypt(struct s5l8702_aes_dev *aes_dev, dma_addr_t src, u32 irq; int ret; - // set src/dst buffer addresses and size + /* set src/dst buffer addresses and size */ s5l8702_aes_write_buf(aes_dev, src, dst, len); - // go! + dev_dbg(dev, + "AES pre-START CIPHERKEY_SEL=0x%x COMPLIMENT=0x%x CFG=0x%x len=%u\n", + s5l8702_aes_readl(aes_dev, S5L8702_AES_CIPHERKEY_SEL), + s5l8702_aes_readl(aes_dev, S5L8702_AES_COMPLIMENT), + s5l8702_aes_readl(aes_dev, S5L8702_AES_CFG), len); + s5l8702_aes_writel(aes_dev, S5L8702_AES_COMMAND, S5L8702_AES_CMD_START); // wait for completion @@ -416,14 +439,33 @@ static int s5l8702_aes_crypt(struct skcipher_request *req, bool encrypt) mutex_lock(&aes_dev->lock); - ret = s5l8702_aes_hw_init(ctx, walk.iv, encrypt); + ret = s5l8702_aes_hw_init(ctx, encrypt); if (ret) goto out_unlock; while (walk.nbytes) { dma_addr_t src, dst; + u8 next_iv[AES_BLOCK_SIZE]; + + /* set IV for the current operation if needed */ + if (ctx->cbc) + s5l8702_aes_write_iv(aes_dev, walk.iv); + + /* + * CBC decrypt next-IV must be the last *ciphertext* block. + * Save it before DMA: in-place decrypt overwrites src. + */ + if (ctx->cbc && !encrypt) { + if (walk.nbytes < AES_BLOCK_SIZE) { + ret = -EINVAL; + break; + } + memcpy(next_iv, + walk.src.virt.addr + walk.nbytes - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + } - // map addresses + /* map addresses */ src = dma_map_single(dev, walk.src.virt.addr, walk.nbytes, DMA_TO_DEVICE); if (dma_mapping_error(dev, src)) { ret = -ENOMEM; @@ -439,14 +481,24 @@ static int s5l8702_aes_crypt(struct skcipher_request *req, bool encrypt) ret = s5l8702_aes_hw_crypt(aes_dev, src, dst, walk.nbytes); - // unmap addresses + /* unmap addresses */ dma_unmap_single(dev, dst, walk.nbytes, DMA_FROM_DEVICE); dma_unmap_single(dev, src, walk.nbytes, DMA_TO_DEVICE); if (ret) break; - // update remaining bytes and process next chunk + /* prepare IV for the next walk chunk if needed */ + if (ctx->cbc) { + if (encrypt) + memcpy(walk.iv, + walk.dst.virt.addr + walk.nbytes - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + else + memcpy(walk.iv, next_iv, AES_BLOCK_SIZE); + } + + /* update remaining bytes and process next chunk */ ret = skcipher_walk_done(&walk, 0); if (ret) break; diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 8afea2e2336027..8e76a88c6f1aa1 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -787,3 +787,11 @@ config DMA_ENGINE_RAID bool endif + +config S5L8740_PL080 + tristate "S5L8740 PL080 DMAC probe (N31)" + depends on ARM + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + PL080 dmaengine at 0x38200000 / 0x38700000 for iPod nano 7G. diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 19ba465011a6d5..bb14d5472c6d27 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -90,3 +90,4 @@ obj-y += qcom/ obj-y += stm32/ obj-y += ti/ obj-y += xilinx/ +obj-$(CONFIG_S5L8740_PL080) += dma-s5l8740-pl080.o diff --git a/drivers/dma/dma-s5l8740-pl080.c b/drivers/dma/dma-s5l8740-pl080.c new file mode 100755 index 00000000000000..ceae31eef1f8d8 --- /dev/null +++ b/drivers/dma/dma-s5l8740-pl080.c @@ -0,0 +1,2063 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 dual PL080 DMA (N31) + * + * Bases: 0x38200000 / 0x38700000 (OSOS pair). Not 0x384 (DWC OTG). + * + * DT #dma-cells = <2>: + * Quirks (PL080 + I²S on S5L8740/N31): + * Burst: M2P dest=1 beat (fixed IIS FIFO @+0x10); src≈4 beats (half FIFO). + * Peri: RetailOS oracle 2026-08-25 — IIS0 TX **10** (→0x3CA00010), IIS2 RX **13** + * (←0x3D400038); BT A2DP uses UART1 only (no PL080). Glass: peri 12 stuck; + * Rockbox IIS0 TX=0xA. See artifacts/retailos-mmio/README.md. + * Cache: PL080 not coherent — dma_sync in start(); no CTL_PROT_CACHE on slave. + * LLI: OSOS B424C uses **5×u32 / 20-byte** nodes (src,dst,lli,ctl,count). + * Count lives in LLI[4] and is programmed to CONTROL2 @+0x114. + * terminate_all: CFG disable + bounded ENBLD poll — never spin on BUSY (amba-pl08x). + * SG: multi-element builds LLI chain; contiguous buffers preferred (CMA). + * AHB: M2P src=mem on AHB2 (ahb_s=1), dst=FIFO on AHB1 (ahb_d=0). + * FIFO: S3C64xx-style ~64 deep — src burst 8 (m2p_src_burst=2), dst=1. + * PL080S/S5L: CTL @+0x10c, CFG @+0x110, CONTROL2 count @+0x114 (OSOS B424C). + * Not the mainline Samsung map (CONTROL2@+0x10 / CFG@+0x14). + * Cache: ARM1176 32-byte lines — buffer 32-byte aligned; sync in start(). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "virt-dma.h" + +#define PL080_INT_STATUS 0x00 +#define PL080_INT_TC_STATUS 0x04 +#define PL080_INT_TC_CLEAR 0x08 +#define PL080_INT_ERR_STATUS 0x0c +#define PL080_INT_ERR_CLEAR 0x10 +#define PL080_RAW_TC 0x14 +#define PL080_RAW_ERR 0x18 +#define PL080_ENBLD_CHNS 0x1c +#define PL080_SOFT_BREQ 0x20 +#define PL080_SOFT_SREQ 0x24 +#define PL080_CONFIG 0x30 +#define PL080_CONFIG_EN BIT(0) +#define PL080_SYNC 0x34 + +#define PL080_Cx_SRC(i) (0x100 + (i) * 0x20) +#define PL080_Cx_DST(i) (0x104 + (i) * 0x20) +#define PL080_Cx_LLI(i) (0x108 + (i) * 0x20) +#define PL080_Cx_CTL(i) (0x10c + (i) * 0x20) +#define PL080_Cx_CFG(i) (0x110 + (i) * 0x20) +/* OSOS B424C writes transfer count here (PL080S CONTROL2). Not in DDI0196. */ +#define PL080S_Cx_CONTROL2(i) (0x114 + (i) * 0x20) + +#define PL080_CH_COUNT 8 +#define PL080_MAX_XFER_WORDS 0xfff + +#define CTL_SRC_AI BIT(26) +#define CTL_DST_AI BIT(27) +#define CTL_TC_IRQ BIT(31) +#define CTL_WIDTH_SHIFT 18 +#define CTL_DBSIZE_SHIFT 15 +#define CTL_SBSIZE_SHIFT 12 + +#define CFG_ENABLE BIT(0) +#define CFG_SRC_PERI_SHIFT 1 +#define CFG_DST_PERI_SHIFT 6 +#define CFG_FLOW_SHIFT 11 +#define CFG_IE BIT(14) /* unmask error IRQ */ +#define CFG_ITC BIT(15) /* unmask TC IRQ */ +#define FLOW_M2P 0x1 +#define FLOW_P2M 0x2 +#define CTL_PROT_PRIV BIT(28) +#define CTL_PROT_BUFF BIT(29) +#define CTL_PROT_CACHE BIT(30) + +/* Glass: peri 12 Active+c2 stuck. peri 10 SRC walks. Rockbox IIS0 TX=0xA. */ +static int force_peri = -1; +module_param(force_peri, int, 0644); +MODULE_PARM_DESC(force_peri, "override DT DMA peri id (-1 = use DT)"); +/* + * Put the transfer count in CxControl[11:0] as well as CONTROL2. + * + * Stock writes the count to the channel's +0x14 (CONTROL2) and to the 5th + * dword of the LLI -- sub_B424C does "*v25 = v27 & 0x1FFFFFFF" and + * "v21[4] = v27" -- which is the Samsung PL080S layout and is what this + * driver does. But stock also *preserves* CxControl's low bits: it only + * rewrites SI/DI, via "*v10 & 0xF3FFFFFF | v13 | v17". Whatever sits in + * CxControl[11:0] was put there by something else and stock never clears + * it. This driver rebuilds the word from scratch each time and leaves the + * field zero. + * + * Measured: with force_mem=1 the channel waits on no peripheral request at + * all and still stops after exactly 32 bytes, which rules out the I2S and + * points at the descriptor. + */ +/* + * DMACSync: leave the synchronisation logic ENABLED. + * + * On the PL080 a SET bit in DMACSync *disables* the synchronisation logic + * for that DMA request line. That logic exists for peripherals whose + * request signal is in a different clock domain from the DMA controller, + * which IIS0 is: it runs off the audio clock, not the AHB clock. With + * synchronisation off the request is sampled unreliably, and the channel + * takes one burst and then never sees another request -- no error, no + * terminal count. + * + * OSOS writes 0x38200034 zero times in the whole image, leaving it at its + * reset value of 0 with synchronisation enabled for every line. This driver + * does the same. sync_mask restores the old value for comparison. + */ +static unsigned int sync_mask; +module_param(sync_mask, uint, 0644); +MODULE_PARM_DESC(sync_mask, + "DMACSync value; 0 = sync enabled (stock). Old behaviour was ~0."); + +static int ctl_count; +module_param(ctl_count, int, 0644); +MODULE_PARM_DESC(ctl_count, + "1 = also place the transfer count in CxControl[11:0]"); + +static int force_mem; +module_param(force_mem, int, 0644); +MODULE_PARM_DESC(force_mem, "1 = M2M flow + soft req, dest still FIFO"); +/* + * RetailOS music CFG = 0x28a81 (Active RO bit17 set mid-play → base 0x8a81): + * DstPeri=10, FlowCntrl=1 (M2P DMA), ITC=1, IE=0. + * Earlier misread of Active as Flow=5; keep Flow=1 per PL080-DECODE.md. + */ +static int force_flow = 1; +module_param(force_flow, int, 0644); +MODULE_PARM_DESC(force_flow, "PL080 FlowCntrl -1=auto M2P, 0=M2M+soft, 1=M2P (RetailOS), 5=M2P-peri"); +/* DDI0196 CxControl bits 24/25: 0=AHB1, 1=AHB2. Kitra memcpy uses AHB1. */ +/* + * RetailOS music-playing CTL = 0x84249000: + * Prot=0, SI=1, width=16, SB=1, DB=1, AHB_S=0, AHB_D=0, TC_IRQ=1. + * Prior Linux defaults (ahb_s=1, SB=8, DB=1, Prot=PRIV|BUFF) yielded + * CTL 0xb5242000 and STATUS stuck in 0x2A0 class vs retail 0x320. + */ +static int ahb_s; +module_param(ahb_s, int, 0644); +MODULE_PARM_DESC(ahb_s, "source AHB master (0=AHB1 RetailOS music, 1=AHB2)"); +static int ahb_d; +module_param(ahb_d, int, 0644); +MODULE_PARM_DESC(ahb_d, "dest AHB master (0=AHB1/periph, 1=AHB2/mem)"); +/* 1=16-bit S16 LE (Rockbox pcm / OSOS BCB60 16-bit). 2=32-bit packed LR. */ +static int xfer_width = 1; +module_param(xfer_width, int, 0644); +MODULE_PARM_DESC(xfer_width, "PL080 src/dst width 0=8 1=16 2=32"); +/* RetailOS music SBSIZE/DBSIZE enc = 1 (4-beat? enc1) — CTL 0x84249000. */ +static bool start_verbose = true; +module_param(start_verbose, bool, 0644); +MODULE_PARM_DESC(start_verbose, + "Log channel CFG write and read-back at every start"); + +static bool xfer_width_override; +module_param(xfer_width_override, bool, 0644); +MODULE_PARM_DESC(xfer_width_override, + "Force xfer_width on every channel, ignoring dma_slave_config"); + +static int m2p_src_burst = 1; +module_param(m2p_src_burst, int, 0644); +MODULE_PARM_DESC(m2p_src_burst, "M2P SBSIZE enc (default 1 = RetailOS music)"); +static int m2p_dst_burst = 1; +module_param(m2p_dst_burst, int, 0644); +MODULE_PARM_DESC(m2p_dst_burst, "M2P DBSIZE enc (default 1 = RetailOS music)"); +/* 1=match RetailOS Prot=0 on slave; 0=PRIV|BUFF (old Linux). */ +static int retail_prot = 1; +module_param(retail_prot, int, 0644); +MODULE_PARM_DESC(retail_prot, "1=Prot=0 on M2P/P2M (RetailOS music CTL)"); +static int force_eng; +module_param(force_eng, int, 0644); +MODULE_PARM_DESC(force_eng, "PL080 engine 0/1 for xlate (-1 = either; default 0 = PL080_0)"); +/* Prefer physical channel (RetailOS music uses ch2). -1 = first free. */ +static int force_ch = 2; +module_param(force_ch, int, 0644); +MODULE_PARM_DESC(force_ch, "prefer PL080 channel id 0..7 (-1=any; default 2=RetailOS)"); + +/* Verbose transfer/xlate spam off by default; use verbose=1 or dyndbg. */ +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Verbose PL080 DMA bring-up logs (default N)"); + +#define pl080_vinfo(dev, fmt, ...) \ + do { \ + if (verbose) \ + dev_info((dev), fmt, ##__VA_ARGS__); \ + else \ + dev_dbg((dev), fmt, ##__VA_ARGS__); \ + } while (0) + +/* OSOS B424C descriptor stride is 20 bytes; keep pool 32-byte aligned. */ +#define PL080_LLI_ALIGN 32 +#define PL080_TERM_POLL_US 10 +#define PL080_TERM_POLL_MAX 10 +#define PL080S_XFER_COUNT_MASK 0x1fffffffu + +struct pl080_lli { + __le32 src; + __le32 dst; + __le32 lli; + __le32 ctrl; + __le32 ctrl2; /* transfer count → CONTROL2 (OSOS v27) */ +}; + +static size_t s5l_pl080_lli_size(unsigned int nlli) +{ + return ALIGN(nlli * sizeof(struct pl080_lli), PL080_LLI_ALIGN); +} + +static dma_addr_t s5l_pl080_lli_pa(dma_addr_t base, unsigned int idx) +{ + return base + idx * sizeof(struct pl080_lli); +} + +#define PL080_LLI_POOL_NODES 64 + +struct s5l_pl080_chan { + struct virt_dma_chan vc; + struct s5l_pl080 *host; + void __iomem *base; + u8 id; + u8 peri; + u8 src_burst; + u8 dst_burst; + /* + * Encoded PL080 width (0 = 8-bit, 1 = 16, 2 = 32) taken from the + * slave config, or -1 when the client never set one. + */ + s8 src_wid; + s8 dst_wid; + enum dma_transfer_direction dir; + dma_addr_t fifo_addr; + struct s5l_pl080_desc *running; + /* + * DMA errors seen on this channel, and whether the driver has stopped + * trying. Bounded because an error that re-arms itself is an + * interrupt storm, and a storm on this SoC is a dead device rather + * than a slow one. + */ + unsigned int err_count; + bool err_stuck; + /* + * Cyclic period callbacks are delivered from a workqueue, not from + * virt-dma's tasklet. See s5l_pl080_cyc_workfn(). + */ + struct work_struct cyc_work; + atomic_t cyc_pending; + dma_async_tx_callback cyc_cb; + void *cyc_cb_param; + bool cyc_active; +}; + +struct s5l_pl080_desc { + struct virt_dma_desc vd; + struct pl080_lli *lli; + dma_addr_t lli_phys; + unsigned int nlli; + unsigned int lli_off; + bool lli_from_pool; + u32 cfg; + bool cyclic; + dma_addr_t buf_addr; + size_t buf_len; + size_t period_len; + unsigned int periods; + unsigned int periods_done; + /* + * Ring mode for the single self-linked node: the driver advances + * lli[0].src itself, from the terminal-count interrupt, instead of + * having a consumer post each period from process context. + * See s5l_pl080_rearm_set_ring(). + */ + bool ring; + u32 ring_base; + u32 ring_bytes; + u32 ring_period; + u32 ring_off; + struct llist_node free_node; +}; + +struct s5l_pl080 { + struct device *dev; + void __iomem *base[2]; + struct clk *clk[2]; + struct dma_device ddev; + struct s5l_pl080_chan chans[PL080_CH_COUNT * 2]; + spinlock_t lock; + void *dummy_cpu; + dma_addr_t dummy_dma; + struct pl080_lli *lli_pool; + dma_addr_t lli_pool_phys; + DECLARE_BITMAP(lli_busy, PL080_LLI_POOL_NODES); + struct task_struct *pump; + /* Descriptors whose coherent LLI block must be freed outside + * atomic context; see s5l_pl080_desc_free(). + */ + struct llist_head free_list; + struct work_struct free_work; + struct workqueue_struct *cyc_wq; +}; + +static struct pl080_lli *s5l_pl080_lli_alloc(struct s5l_pl080 *pl, + unsigned int nlli, + dma_addr_t *phys, + unsigned int *off, + bool *from_pool) +{ + unsigned long flags; + unsigned int i, j; + struct pl080_lli *lli; + + if (!pl || !nlli || !phys || !off || !from_pool) + return NULL; + + if (pl->lli_pool && nlli <= PL080_LLI_POOL_NODES) { + spin_lock_irqsave(&pl->lock, flags); + for (i = 0; i + nlli <= PL080_LLI_POOL_NODES; i++) { + for (j = 0; j < nlli; j++) { + if (test_bit(i + j, pl->lli_busy)) + break; + } + if (j != nlli) + continue; + for (j = 0; j < nlli; j++) + set_bit(i + j, pl->lli_busy); + *phys = pl->lli_pool_phys + + i * sizeof(struct pl080_lli); + *off = i; + *from_pool = true; + spin_unlock_irqrestore(&pl->lock, flags); + return pl->lli_pool + i; + } + spin_unlock_irqrestore(&pl->lock, flags); + } + + lli = dma_alloc_coherent(pl->dev, s5l_pl080_lli_size(nlli), phys, + GFP_NOWAIT); + if (!lli) { + dev_warn_ratelimited(pl->dev, + "LLI alloc nlli=%u ENOMEM\n", nlli); + return NULL; + } + *off = 0; + *from_pool = false; + return lli; +} + +/* Returning pool slots is just clearing bits, so any context will do. */ +static void s5l_pl080_lli_pool_release(struct s5l_pl080 *pl, + struct s5l_pl080_desc *d) +{ + unsigned long flags; + unsigned int j; + + spin_lock_irqsave(&pl->lock, flags); + for (j = 0; j < d->nlli && d->lli_off + j < PL080_LLI_POOL_NODES; j++) + clear_bit(d->lli_off + j, pl->lli_busy); + spin_unlock_irqrestore(&pl->lock, flags); + d->lli = NULL; +} + +/* Drains descriptors parked by s5l_pl080_desc_free(). */ +static void s5l_pl080_free_work(struct work_struct *work) +{ + struct s5l_pl080 *pl = container_of(work, struct s5l_pl080, + free_work); + struct s5l_pl080_desc *d, *tmp; + struct llist_node *pending; + + pending = llist_del_all(&pl->free_list); + llist_for_each_entry_safe(d, tmp, pending, free_node) { + dma_free_coherent(pl->dev, s5l_pl080_lli_size(d->nlli), + d->lli, d->lli_phys); + kfree(d); + } +} + +static int s5l_pl080_need_soft(void) +{ + /* Flow 0/4: M2M or M2P under DMA control — drive with SOFT_BREQ. */ + return force_mem || force_flow == 0 || force_flow == 4; +} + +static unsigned int s5l_pl080_burst_enc(unsigned int maxburst) +{ + if (maxburst <= 1) + return 0; + if (maxburst <= 4) + return 1; + if (maxburst <= 8) + return 2; + if (maxburst <= 16) + return 3; + if (maxburst <= 32) + return 4; + if (maxburst <= 64) + return 5; + if (maxburst <= 128) + return 6; + return 7; +} + +static int s5l_pl080_pump(void *data) +{ + struct s5l_pl080 *pl = data; + unsigned int i, n; + + while (!kthread_should_stop()) { + if (!s5l_pl080_need_soft()) { + usleep_range(20000, 40000); + continue; + } + n = 0; + for (i = 0; i < PL080_CH_COUNT * 2; i++) { + struct s5l_pl080_chan *ch = &pl->chans[i]; + + if (!ch->base || !ch->running) + continue; + writel(BIT(ch->id % PL080_CH_COUNT), + ch->base + PL080_SOFT_BREQ); + n++; + } + if (!n) + usleep_range(2000, 4000); + else + cond_resched(); + } + return 0; +} + +static struct s5l_pl080_chan *to_s5l_chan(struct dma_chan *c) +{ + return container_of(c, struct s5l_pl080_chan, vc.chan); +} + +static struct s5l_pl080_desc *to_s5l_desc(struct virt_dma_desc *vd) +{ + return container_of(vd, struct s5l_pl080_desc, vd); +} + +/* + * PL080 encodes transfer width as log2(bytes): 0 = 8-bit, 1 = 16-bit, + * 2 = 32-bit. Anything wider than 32-bit has no encoding here. + */ +static int s5l_pl080_width_enc(enum dma_slave_buswidth w) +{ + switch (w) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + return 0; + case DMA_SLAVE_BUSWIDTH_2_BYTES: + return 1; + case DMA_SLAVE_BUSWIDTH_4_BYTES: + return 2; + default: + return -1; + } +} + +/* + * Width actually used for a channel. A client's dma_slave_config wins; + * xfer_width is the fallback and the bring-up override. Ignoring the slave + * config here silently narrows a client that asked for 32-bit FIFO writes. + */ +static unsigned int s5l_pl080_chan_width(struct s5l_pl080_chan *ch, + bool dst) +{ + unsigned int w = xfer_width & 7; + s8 cfgw = -1; + + if (ch) + cfgw = dst ? ch->dst_wid : ch->src_wid; + if (!xfer_width_override && cfgw >= 0) + w = (unsigned int)cfgw; + if (w > 2) + w = 1; + return w; +} + +static unsigned int s5l_pl080_unit(void) +{ + unsigned int w = xfer_width & 7; + + if (w > 2) + w = 1; + return 1u << w; +} + +/* + * CTL template only — transfer count is NOT in CTL[11:0] on this SoC. + * OSOS B424C / RetailOS music CTL (e.g. 0x84249000) keep size bits clear; + * count is written to CONTROL2 and LLI ctrl2. + */ +static u32 s5l_pl080_build_ctl(struct s5l_pl080_chan *ch, u32 words, + bool src_inc, bool dst_inc, bool irq) +{ + unsigned int sw = s5l_pl080_chan_width(ch, false); + unsigned int dw = s5l_pl080_chan_width(ch, true); + unsigned int sb, db; + u32 ctl; + + if (ch && (ch->dir == DMA_MEM_TO_DEV || ch->dir == DMA_DEV_TO_MEM)) { + sb = ch->src_burst; + db = ch->dst_burst; + ctl = retail_prot ? 0 : (CTL_PROT_PRIV | CTL_PROT_BUFF); + } else { + /* M2M selftest / memcpy: Rockbox pcm-s5l8702 8/4 */ + sb = 2; + db = 1; + ctl = CTL_PROT_PRIV | CTL_PROT_BUFF | CTL_PROT_CACHE; + } + /* SWIDTH is the low field, DWIDTH sits three bits above it. */ + ctl |= (sw << CTL_WIDTH_SHIFT) | (dw << (CTL_WIDTH_SHIFT + 3)) | + (sb << CTL_SBSIZE_SHIFT) | (db << CTL_DBSIZE_SHIFT); + if (ahb_s) + ctl |= BIT(24); + if (ahb_d) + ctl |= BIT(25); + + if (src_inc) + ctl |= CTL_SRC_AI; + if (dst_inc) + ctl |= CTL_DST_AI; + if (irq) + ctl |= CTL_TC_IRQ; + if (ctl_count) + ctl |= words & 0xfff; + return ctl; +} + +static void s5l_pl080_chan_disable(struct s5l_pl080_chan *ch) +{ + u8 id = ch->id % PL080_CH_COUNT; + void __iomem *b = ch->base; + unsigned int i; + u32 en; + + if (!b) + return; + writel(0, b + PL080_Cx_CFG(id)); + /* Never spin forever on BUSY — bounded poll then force-clear IRQs. */ + for (i = 0; i < PL080_TERM_POLL_MAX; i++) { + en = readl(b + PL080_ENBLD_CHNS); + if (!(en & BIT(id))) + break; + udelay(PL080_TERM_POLL_US); + } + if (en & BIT(id)) + dev_warn(ch->host->dev, + "ch%u still enabled en=0x%x after disable (no BUSY wait)\n", + ch->id, en); + writel(BIT(id), b + PL080_INT_TC_CLEAR); + writel(BIT(id), b + PL080_INT_ERR_CLEAR); +} + +/* SRAM window: 0x22000000..0x2202FFFF. */ +/* + * Stock programs Cx_LLI with bit 31 set. + * + * Measured on RetailOS with music playing: ch2 lli = 0x8b353be0. DRAM on + * this board is 0x08000000..0x0BFFFFFF (memory@8000000, 64 MiB), so that is + * 0x0b353be0 with bit 31 set -- an alias of a perfectly ordinary DRAM + * address, not a different region. A bare 0x09588000 is not equivalent. + * + * Note stock keeps the audio BUFFER in SRAM but the LLI descriptor in DRAM, + * through this alias. + * + * What bit 31 selects is NOT established -- an uncached view, or a + * different AHB master, are both plausible and neither is proven. It is + * applied because stock applies it. One define to revert. + */ +#define PL080_LLI_ALIAS 0x80000000u + + +#define S5L8740_SRAM_BASE 0x22000000u +#define S5L8740_SRAM_END 0x22030000u + +static void s5l_pl080_sync_buffer(struct s5l_pl080_chan *ch, + struct s5l_pl080_desc *d) +{ + struct device *dev = ch->host->dev; + + if (!d->buf_len || !dev) + return; + + /* + * Never do cache maintenance on an SRAM buffer. + * + * The audio PCM buffer now lives in SRAM, where stock keeps it + * (measured PL080 ch2 src=0x220025d0 on RetailOS while playing). That + * region is declared no-map, so it is not in the kernel linear map and + * dma_sync_single_for_device() reaches v7_dma_clean_range() on an + * address that has no cacheable mapping: + * + * Unable to handle kernel paging request at virtual address da020000 + * PC is at v7_dma_clean_range+0x1c/0x34 + * LR is at arch_sync_dma_for_device+0x54/0xa8 + * arch_sync_dma_for_device from s5l_pl080_start + * + * There is nothing to maintain in any case: SRAM is not cached, so + * writes from the CPU are already visible to the controller. The sync + * exists for DRAM buffers, where this engine is not coherent. + */ + if (d->buf_addr >= S5L8740_SRAM_BASE && d->buf_addr < S5L8740_SRAM_END) + return; + if (ch->dir == DMA_MEM_TO_DEV) + dma_sync_single_for_device(dev, d->buf_addr, d->buf_len, + DMA_TO_DEVICE); + else if (ch->dir == DMA_DEV_TO_MEM) + dma_sync_single_for_device(dev, d->buf_addr, d->buf_len, + DMA_FROM_DEVICE); +} + +/* + * Cyclic period callbacks, delivered from process context. + * + * virt-dma hands descriptor callbacks to a tasklet. For a cyclic audio + * stream that callback is dmaengine_pcm_dma_complete(), which calls + * snd_pcm_period_elapsed(), which takes the PCM stream lock -- and both DAI + * links on this board are nonatomic, so that lock is a mutex, not a + * spinlock. + * + * A mutex_lock() in a tasklet is not reliably fatal, which is what made + * this so slow to find. Uncontended it takes the cmpxchg fast path and + * returns without sleeping, so short playback looks fine. It is only when + * the writer thread is actually holding the stream lock -- that is, during + * sustained playback -- that the tasklet tries to sleep, calls schedule() + * from atomic context, and takes the machine down. Playback that runs for + * a moment and then hangs the whole device is exactly that shape. + * + * So the callback is queued to a workqueue instead. The pending count is + * kept exactly, never coalesced: dmaengine_pcm_dma_complete() advances its + * own position by one period per call, so swallowing a call would desync + * the ALSA pointer from the hardware just as surely as never calling it. + */ +static void s5l_pl080_destroy_wq(void *wq) +{ + destroy_workqueue(wq); +} + +static void s5l_pl080_cyc_workfn(struct work_struct *w) +{ + struct s5l_pl080_chan *ch = container_of(w, struct s5l_pl080_chan, + cyc_work); + unsigned long flags; + + while (atomic_dec_if_positive(&ch->cyc_pending) >= 0) { + dma_async_tx_callback cb; + void *param; + + spin_lock_irqsave(&ch->vc.lock, flags); + cb = ch->cyc_active ? ch->cyc_cb : NULL; + param = ch->cyc_cb_param; + spin_unlock_irqrestore(&ch->vc.lock, flags); + + if (!cb) + break; + cb(param); + } +} + +/* + * Drop any callback still owed for this channel. + * + * Callers hold vc.lock, so this must not wait for a callback already in + * flight. Clearing cyc_active is what stops the work function dead: it + * re-reads the flag under the lock before every single invocation, so once + * this returns no further callback can start. One already past that check + * may still run, which is what device_synchronize() is for. + */ +static void s5l_pl080_cyc_drop(struct s5l_pl080_chan *ch) +{ + ch->cyc_active = false; + ch->cyc_cb = NULL; + ch->cyc_cb_param = NULL; + atomic_set(&ch->cyc_pending, 0); +} + +static void s5l_pl080_start(struct s5l_pl080_chan *ch, struct s5l_pl080_desc *d) +{ + void __iomem *b = ch->base; + + /* A channel the error path gave up on stays down until it is + * reconfigured; restarting it just resumes the storm. + */ + if (ch->err_stuck) { + dev_warn_ratelimited(ch->host->dev, + "ch%u start refused: %u DMA errors\n", + ch->id, ch->err_count); + return; + } + u8 id = ch->id % PL080_CH_COUNT; + struct pl080_lli *first = d->lli; + + s5l_pl080_sync_buffer(ch, d); + s5l_pl080_chan_disable(ch); + s5l_pl080_cyc_drop(ch); + if (d->cyclic) { + ch->cyc_cb = d->vd.tx.callback; + ch->cyc_cb_param = d->vd.tx.callback_param; + ch->cyc_active = true; + } + writel(le32_to_cpu(first->src), b + PL080_Cx_SRC(id)); + writel(le32_to_cpu(first->dst), b + PL080_Cx_DST(id)); + /* Next LLI, not the first (already loaded into SRC/DST/CTL/C2). */ + writel(le32_to_cpu(first->lli) | PL080_LLI_ALIAS, + b + PL080_Cx_LLI(id)); + writel(le32_to_cpu(first->ctrl), b + PL080_Cx_CTL(id)); + /* B424C: *v25 = v27 & 0x1FFFFFFF — count only, never the CTL word. */ + writel(le32_to_cpu(first->ctrl2) & PL080S_XFER_COUNT_MASK, + b + PL080S_Cx_CONTROL2(id)); + writel(d->cfg | CFG_ENABLE, b + PL080_Cx_CFG(id)); + /* + * Read CFG straight back. If the enable does not stick, the channel + * never starts and every later symptom is downstream noise, so this + * distinguishes "never programmed" from "programmed then halted". + */ + if (start_verbose) { + u32 rb = readl(b + PL080_Cx_CFG(id)); + + dev_info(ch->host->dev, + "ch%u START peri=%u dir=%d cfg_want=0x%08x cfg_read=0x%08x en=0x%x ctl=0x%08x c2=0x%08x src=0x%08x dst=0x%08x\n", + ch->id, ch->peri, (int)ch->dir, + (u32)(d->cfg | CFG_ENABLE), rb, + readl(b + PL080_ENBLD_CHNS), + le32_to_cpu(first->ctrl), + le32_to_cpu(first->ctrl2), + le32_to_cpu(first->src), + le32_to_cpu(first->dst)); + } + /* M2M / force_flow 0|4: software request. M2P peri waits for IIS DRQ. */ + if (s5l_pl080_need_soft()) { + writel(BIT(id), b + PL080_SOFT_BREQ); + if (force_mem || force_flow == 0) + writel(BIT(id), b + PL080_SOFT_SREQ); + } + ch->running = d; + dev_dbg(ch->host->dev, + "ch%u start peri=%u cfg=0x%x nlli=%u src=0x%x dst=0x%x ctl=0x%x c2=0x%x\n", + ch->id, ch->peri, (u32)(d->cfg | CFG_ENABLE), d->nlli, + le32_to_cpu(first->src), le32_to_cpu(first->dst), + le32_to_cpu(first->ctrl), le32_to_cpu(first->ctrl2)); +} + +static void s5l_pl080_issue(struct dma_chan *c) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + struct virt_dma_desc *vd; + unsigned long flags; + + spin_lock_irqsave(&ch->vc.lock, flags); + if (vchan_issue_pending(&ch->vc) && !ch->running) { + vd = vchan_next_desc(&ch->vc); + if (vd) { + list_del(&vd->node); + s5l_pl080_start(ch, to_s5l_desc(vd)); + } + } + spin_unlock_irqrestore(&ch->vc.lock, flags); +} + +static enum dma_status s5l_pl080_tx_status(struct dma_chan *c, + dma_cookie_t cookie, + struct dma_tx_state *state) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + enum dma_status st = dma_cookie_status(c, cookie, state); + struct s5l_pl080_desc *d; + unsigned long flags; + u32 cur, start, end; + + if (!state || st == DMA_COMPLETE) + return st; + + spin_lock_irqsave(&ch->vc.lock, flags); + d = ch->running; + if (d && d->buf_len) { + u8 id = ch->id % PL080_CH_COUNT; + u32 link, cnt, link2, cnt2; + + /* + * Derive the position from LINK and COUNT, never from SRCADDR. + * + * Rockbox's PL080 driver for this controller family documents + * the behaviour of reading the channel registers while the + * hardware is updating them: SRCADDR may return corrupted + * data, while LINK and COUNT always read back valid, and the + * pair is updated atomically. So read LINK, COUNT, LINK, COUNT + * and take the second pair when the link moved underneath the read. + * + * An earlier version of this function read SRCADDR, which is + * exactly the register that is not safe to trust here. + */ + link = readl(ch->base + PL080_Cx_LLI(id)) & ~PL080_LLI_ALIAS; + cnt = readl(ch->base + PL080S_Cx_CONTROL2(id)) & + PL080S_XFER_COUNT_MASK; + link2 = readl(ch->base + PL080_Cx_LLI(id)) & ~PL080_LLI_ALIAS; + cnt2 = readl(ch->base + PL080S_Cx_CONTROL2(id)) & + PL080S_XFER_COUNT_MASK; + if (link != link2) { + link = link2; + cnt = cnt2; + } + + if (d->cyclic && d->nlli && d->lli && d->lli_phys && + link >= lower_32_bits(d->lli_phys)) { + u32 nodes = d->nlli; + u32 next = (link - lower_32_bits(d->lli_phys)) / + sizeof(struct pl080_lli); + u32 cur = (next + nodes - 1) % nodes; + u32 per_node = d->buf_len / nodes; + u32 words = le32_to_cpu(d->lli[cur].ctrl2) & + PL080S_XFER_COUNT_MASK; + u32 pos = cur * per_node; + + /* + * LINK points at the descriptor the hardware will load + * next, so the one in flight is the previous node. + */ + if (words && cnt <= words) + pos += ((words - cnt) * per_node) / words; + if (pos < d->buf_len) + state->residue = d->buf_len - pos; + else + state->residue = d->buf_len; + } else { + cur = readl(ch->base + + ((ch->dir == DMA_DEV_TO_MEM) ? + PL080_Cx_DST(id) : PL080_Cx_SRC(id))); + start = lower_32_bits(d->buf_addr); + end = start + d->buf_len; + if (cur >= start && cur < end) + state->residue = end - cur; + else + state->residue = d->buf_len; + } + } + spin_unlock_irqrestore(&ch->vc.lock, flags); + return st; +} + +static int s5l_pl080_alloc(struct dma_chan *c) +{ + return 0; +} + +/* + * Wait for a callback that was already past the cyc_active check when + * terminate ran. ALSA calls this via snd_pcm_sync_stop(), which is the + * point at which it is safe to sleep. + * + * The current_work() test is not defensive programming, it is the whole + * reason this function has a guard. The period callback is + * snd_pcm_period_elapsed(), and an xrun detected inside it walks straight + * back down into the driver: + * + * s5l_pl080_cyc_workfn + * snd_pcm_period_elapsed -> snd_pcm_update_state -> snd_pcm_do_stop + * s5l8740_i2s_trigger(STOP) -> s5l8740_i2s_hw_stop + * dmaengine_synchronize -> cancel_work_sync(&ch->cyc_work) + * + * which is this work item waiting for itself to finish. It hangs the + * worker, and then everything that touches the PCM -- the player, the + * launcher reading /proc/asound, anything reading a status file -- piles + * up behind the stream mutex the dead worker still holds. The device looks + * exactly as bricked as a real lockup. + * + * Inside the callback there is nothing to wait for: the caller is the + * thing synchronize would have waited on. + */ +static void s5l_pl080_synchronize(struct dma_chan *c) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + + if (current_work() != &ch->cyc_work) + cancel_work_sync(&ch->cyc_work); + vchan_synchronize(&ch->vc); +} + +static void s5l_pl080_free(struct dma_chan *c) +{ + vchan_free_chan_resources(&to_s5l_chan(c)->vc); +} + +/* + * virt-dma calls this from its tasklet, and callers may hold the channel + * lock, so it must not sleep. dma_free_coherent() can, so descriptors + * holding a coherent LLI block are queued for s5l_pl080_free_work() + * instead. Pool-backed descriptors are released inline. + */ +static void s5l_pl080_desc_free(struct virt_dma_desc *vd) +{ + struct s5l_pl080_desc *d = to_s5l_desc(vd); + struct s5l_pl080 *pl = to_s5l_chan(vd->tx.chan)->host; + + if (!pl || !d->lli) { + kfree(d); + return; + } + if (d->lli_from_pool) { + s5l_pl080_lli_pool_release(pl, d); + kfree(d); + return; + } + llist_add(&d->free_node, &pl->free_list); + schedule_work(&pl->free_work); +} + +static struct dma_async_tx_descriptor * +s5l_pl080_prep_slave_sg(struct dma_chan *c, struct scatterlist *sgl, + unsigned int sg_len, + enum dma_transfer_direction dir, + unsigned long flags, void *context) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + struct s5l_pl080_desc *d; + struct scatterlist *sg; + struct pl080_lli *lli; + dma_addr_t lli_phys, dev_addr; + unsigned int i, nlli = 0, total = 0; + u32 cfg; + + for_each_sg(sgl, sg, sg_len, i) + total += sg_dma_len(sg); + if (!total) + return NULL; + if (sg_len > 1) + pl080_vinfo(ch->host->dev, "prep_slave_sg sg_len=%u (LLI chain)\n", + sg_len); + + /* One LLI node per <= PL080_MAX_XFER_WORDS transfer units */ + nlli = DIV_ROUND_UP(total, PL080_MAX_XFER_WORDS * s5l_pl080_unit()); + if (nlli == 0) + return NULL; + + d = kzalloc(sizeof(*d), GFP_NOWAIT); + if (!d) + return NULL; + + lli = s5l_pl080_lli_alloc(ch->host, nlli, &lli_phys, &d->lli_off, + &d->lli_from_pool); + if (!lli) { + kfree(d); + return NULL; + } + + cfg = 0; + dev_addr = ch->fifo_addr; + if (force_flow >= 0) { + /* Retail music Flow=1 → CFG 0x8a81 (ITC only; Active RO adds 0x20000). */ + cfg |= ((force_flow & 7) << CFG_FLOW_SHIFT) | CFG_ITC; + if (force_flow != 1 && force_flow != 5) + cfg |= CFG_IE; + if (dir == DMA_MEM_TO_DEV) + cfg |= (ch->peri & 0x1f) << CFG_DST_PERI_SHIFT; + else + cfg |= (ch->peri & 0x1f) << CFG_SRC_PERI_SHIFT; + } else if (force_mem) { + cfg |= CFG_IE | CFG_ITC; + } else if (dir == DMA_MEM_TO_DEV) { + cfg |= (FLOW_M2P << CFG_FLOW_SHIFT) | + ((ch->peri & 0x1f) << CFG_DST_PERI_SHIFT) | + CFG_IE | CFG_ITC; + } else { + cfg |= (FLOW_P2M << CFG_FLOW_SHIFT) | + ((ch->peri & 0x1f) << CFG_SRC_PERI_SHIFT) | + CFG_IE | CFG_ITC; + } + + { + unsigned int idx = 0; + size_t remaining = total; + + for_each_sg(sgl, sg, sg_len, i) { + dma_addr_t addr = sg_dma_address(sg); + size_t sg_left = sg_dma_len(sg); + size_t sg_off = 0; + + while (sg_left && idx < nlli) { + size_t unit = s5l_pl080_unit(); + size_t chunk = min_t(size_t, sg_left, + PL080_MAX_XFER_WORDS * unit); + u32 words = chunk / unit; + + if (!words) + words = 1; + chunk = words * unit; + + if (dir == DMA_MEM_TO_DEV) { + lli[idx].src = cpu_to_le32( + lower_32_bits(addr + sg_off)); + lli[idx].dst = cpu_to_le32( + lower_32_bits(dev_addr)); + } else { + lli[idx].src = cpu_to_le32( + lower_32_bits(dev_addr)); + lli[idx].dst = cpu_to_le32( + lower_32_bits(addr + sg_off)); + } + + lli[idx].ctrl = cpu_to_le32(s5l_pl080_build_ctl( + ch, words, + dir == DMA_MEM_TO_DEV, + dir == DMA_DEV_TO_MEM, + idx == nlli - 1)); + lli[idx].ctrl2 = cpu_to_le32(words & + PL080S_XFER_COUNT_MASK); + + if (idx < nlli - 1) + lli[idx].lli = cpu_to_le32(PL080_LLI_ALIAS | + lower_32_bits(s5l_pl080_lli_pa( + lli_phys, idx + 1))); + else + lli[idx].lli = cpu_to_le32(0); + + sg_off += chunk; + sg_left -= chunk; + remaining -= chunk; + idx++; + } + } + } + + d->lli = lli; + d->lli_phys = lli_phys; + d->nlli = nlli; + d->cfg = cfg; + d->cyclic = false; + d->buf_addr = sg_dma_address(sgl); + d->buf_len = total; + return vchan_tx_prep(&ch->vc, &d->vd, flags); +} + +static struct dma_async_tx_descriptor * +s5l_pl080_prep_dma_cyclic(struct dma_chan *c, dma_addr_t buf_addr, + size_t buf_len, size_t period_len, + enum dma_transfer_direction dir, + unsigned long flags) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + struct s5l_pl080_desc *d; + struct pl080_lli *lli; + dma_addr_t lli_phys, dev_addr; + unsigned int periods, per_period, nlli, p, idx; + u32 cfg; + + if (!buf_len || !period_len || buf_len % period_len) { + pl080_vinfo(ch->host->dev, + "cyclic reject len=%zu period=%zu\n", buf_len, period_len); + return NULL; + } + + periods = buf_len / period_len; + per_period = DIV_ROUND_UP(period_len, PL080_MAX_XFER_WORDS * s5l_pl080_unit()); + if (!per_period) + return NULL; + nlli = periods * per_period; + + d = kzalloc(sizeof(*d), GFP_NOWAIT); + if (!d) + return NULL; + + lli = s5l_pl080_lli_alloc(ch->host, nlli, &lli_phys, &d->lli_off, + &d->lli_from_pool); + if (!lli) { + kfree(d); + return NULL; + } + + cfg = 0; + dev_addr = ch->fifo_addr; + if (force_flow >= 0) { + /* Retail music Flow=1 → CFG 0x8a81 (ITC only; Active RO adds 0x20000). */ + cfg |= ((force_flow & 7) << CFG_FLOW_SHIFT) | CFG_ITC; + if (force_flow != 1 && force_flow != 5) + cfg |= CFG_IE; + if (dir == DMA_MEM_TO_DEV) + cfg |= (ch->peri & 0x1f) << CFG_DST_PERI_SHIFT; + else + cfg |= (ch->peri & 0x1f) << CFG_SRC_PERI_SHIFT; + } else if (force_mem) { + cfg |= CFG_IE | CFG_ITC; + } else if (dir == DMA_MEM_TO_DEV) { + cfg |= (FLOW_M2P << CFG_FLOW_SHIFT) | + ((ch->peri & 0x1f) << CFG_DST_PERI_SHIFT) | + CFG_IE | CFG_ITC; + } else { + cfg |= (FLOW_P2M << CFG_FLOW_SHIFT) | + ((ch->peri & 0x1f) << CFG_SRC_PERI_SHIFT) | + CFG_IE | CFG_ITC; + } + + idx = 0; + for (p = 0; p < periods; p++) { + size_t sg_off = 0; + size_t sg_left = period_len; + unsigned int chunk_i; + + for (chunk_i = 0; chunk_i < per_period && sg_left; chunk_i++) { + size_t unit = s5l_pl080_unit(); + size_t chunk = min_t(size_t, sg_left, + PL080_MAX_XFER_WORDS * unit); + u32 words = chunk / unit; + bool period_last; + + if (!words) + words = 1; + chunk = words * unit; + period_last = (chunk_i == per_period - 1) || + (sg_left <= chunk); + + if (dir == DMA_MEM_TO_DEV) { + lli[idx].src = cpu_to_le32(lower_32_bits( + buf_addr + p * period_len + sg_off)); + lli[idx].dst = cpu_to_le32( + lower_32_bits(dev_addr)); + } else { + lli[idx].src = cpu_to_le32( + lower_32_bits(dev_addr)); + lli[idx].dst = cpu_to_le32(lower_32_bits( + buf_addr + p * period_len + sg_off)); + } + + lli[idx].ctrl = cpu_to_le32(s5l_pl080_build_ctl( + ch, words, + dir == DMA_MEM_TO_DEV, + dir == DMA_DEV_TO_MEM, + period_last)); + lli[idx].ctrl2 = cpu_to_le32(words & + PL080S_XFER_COUNT_MASK); + + if (idx + 1 < nlli) + lli[idx].lli = cpu_to_le32(PL080_LLI_ALIAS | + lower_32_bits( + s5l_pl080_lli_pa(lli_phys, idx + 1))); + else + lli[idx].lli = cpu_to_le32(PL080_LLI_ALIAS | + lower_32_bits(lli_phys)); + + sg_off += chunk; + sg_left -= chunk; + idx++; + } + } + if (idx) + lli[idx - 1].lli = cpu_to_le32(PL080_LLI_ALIAS | + lower_32_bits(lli_phys)); + + d->lli = lli; + d->lli_phys = lli_phys; + d->nlli = nlli; + d->cfg = cfg; + d->cyclic = true; + d->buf_addr = buf_addr; + d->buf_len = buf_len; + d->period_len = period_len; + d->periods = periods; + d->periods_done = 0; + pl080_vinfo(ch->host->dev, + "cyclic ok peri=%u nlli=%u periods=%u period=%zu fifo=0x%x\n", + ch->peri, nlli, periods, period_len, + (u32)lower_32_bits(dev_addr)); + return vchan_tx_prep(&ch->vc, &d->vd, flags); +} + +static int s5l_pl080_config(struct dma_chan *c, + struct dma_slave_config *cfg) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + + ch->src_wid = s5l_pl080_width_enc(cfg->src_addr_width); + ch->dst_wid = s5l_pl080_width_enc(cfg->dst_addr_width); + + if (cfg->direction == DMA_MEM_TO_DEV) { + ch->fifo_addr = cfg->dst_addr; + /* + * Memory side is read linearly, so if the client only + * described the device side, match it rather than leaving + * the source at the module default. + */ + if (ch->src_wid < 0) + ch->src_wid = ch->dst_wid; + /* + * ALSA/dma_tone often pass maxburst=1. burst_enc(1)=0, but + * RetailOS music CTL 0x84249000 needs SB/DB enc=1. Prefer + * module params (oracle) over a 1-beat slave hint. + */ + if (cfg->src_maxburst > 1) + ch->src_burst = s5l_pl080_burst_enc(cfg->src_maxburst); + else + ch->src_burst = clamp(m2p_src_burst, 0, 7); + if (cfg->dst_maxburst > 1) + ch->dst_burst = s5l_pl080_burst_enc(cfg->dst_maxburst); + else + ch->dst_burst = clamp(m2p_dst_burst, 0, 7); + } else { + ch->fifo_addr = cfg->src_addr; + if (ch->dst_wid < 0) + ch->dst_wid = ch->src_wid; + ch->src_burst = cfg->src_maxburst ? + s5l_pl080_burst_enc(cfg->src_maxburst) : 0; + ch->dst_burst = cfg->dst_maxburst ? + s5l_pl080_burst_enc(cfg->dst_maxburst) : 1; + } + ch->dir = cfg->direction; + return 0; +} + +static int s5l_pl080_terminate(struct dma_chan *c) +{ + struct s5l_pl080_chan *ch = to_s5l_chan(c); + u8 id = ch->id % PL080_CH_COUNT; + unsigned long flags; + struct virt_dma_desc *vd; + + dev_dbg(ch->host->dev, + "term ch%u en=0x%x src=0x%x dst=0x%x lli=0x%x ctl=0x%x cfg=0x%x rawtc=0x%x rawerr=0x%x\n", + ch->id, readl(ch->base + PL080_ENBLD_CHNS), + readl(ch->base + PL080_Cx_SRC(id)), + readl(ch->base + PL080_Cx_DST(id)), + readl(ch->base + PL080_Cx_LLI(id)), + readl(ch->base + PL080_Cx_CTL(id)), + readl(ch->base + PL080_Cx_CFG(id)), + readl(ch->base + PL080_RAW_TC), + readl(ch->base + PL080_RAW_ERR)); + s5l_pl080_chan_disable(ch); + spin_lock_irqsave(&ch->vc.lock, flags); + /* Before the descriptor is freed -- cyc_cb points into it. */ + s5l_pl080_cyc_drop(ch); + if (ch->running) { + s5l_pl080_desc_free(&ch->running->vd); + ch->running = NULL; + } + while (!list_empty(&ch->vc.desc_submitted)) { + vd = list_first_entry(&ch->vc.desc_submitted, + struct virt_dma_desc, node); + list_del(&vd->node); + s5l_pl080_desc_free(vd); + } + spin_unlock_irqrestore(&ch->vc.lock, flags); + return 0; +} + +/* + * The DMA interrupt. + * + * Two rules here decide whether a fault stops the audio or hangs the + * device. + * + * An error must disable the channel and end its transfer, and a channel + * that keeps erroring must be shut down for good after a bounded number of + * tries. Clearing the error latch alone leaves the channel enabled, so + * whatever raised the error raises it again immediately -- an interrupt + * storm on a single core with the watchdog disarmed, indistinguishable + * from a lockup from outside. + * + * And the handler must claim only what it actually serviced. Returning + * IRQ_HANDLED unconditionally, including when neither engine had anything + * pending, tells the kernel every interrupt on the line was handled, which + * disables the spurious-interrupt protection that would otherwise notice a + * line stuck active and mask it. Claiming accurately lets that protection + * work: the audio still dies, but the device stays up and says why. + */ +#define PL080_MAX_CH_ERRS 8 + +static irqreturn_t s5l_pl080_irq(int irq, void *data) +{ + struct s5l_pl080 *pl = data; + unsigned int eng, i; + u32 tc, err; + bool serviced = false; + bool pending; + + /* + * Drain every pending source before returning. + * + * The VIC line for this controller is configured edge-triggered, and + * a PL080 holds its interrupt asserted until the matching status bit + * is cleared. Servicing exactly one terminal count per invocation is + * therefore not enough: if another period completes while the handler + * is still running, the line never falls, no fresh edge is produced, + * and the channel goes silent for the rest of the stream. Servicing + * one source per invocation yields one DMA interrupt per playback + * where a 6 s file at 1024-frame periods needs a few hundred, so ALSA + * receives no period callbacks and every stream runs to XRUN. + */ + do { + pending = false; + for (eng = 0; eng < 2; eng++) { + void __iomem *b = pl->base[eng]; + + if (!b) + continue; + tc = readl(b + PL080_INT_TC_STATUS); + err = readl(b + PL080_INT_ERR_STATUS); + if (!tc && !err) + continue; + serviced = true; + pending = true; + dev_dbg(pl->dev, "irq eng%u tc=0x%x err=0x%x\n", + eng, tc, err); + if (tc) + writel(tc, b + PL080_INT_TC_CLEAR); + if (err) + writel(err, b + PL080_INT_ERR_CLEAR); + + for (i = 0; i < PL080_CH_COUNT; i++) { + struct s5l_pl080_chan *ech = + &pl->chans[eng * PL080_CH_COUNT + i]; + struct s5l_pl080_desc *ed; + unsigned long eflags; + + if (!(err & BIT(i))) + continue; + + /* + * Stop the channel before anything else. Leaving it + * enabled is what turns one error into a storm. + */ + s5l_pl080_chan_disable(ech); + + spin_lock_irqsave(&ech->vc.lock, eflags); + ed = ech->running; + ech->running = NULL; + if (++ech->err_count >= PL080_MAX_CH_ERRS) + ech->err_stuck = true; + spin_unlock_irqrestore(&ech->vc.lock, eflags); + + dev_err_ratelimited(pl->dev, + "ch%u DMA error (%u so far)%s -- channel stopped\n", + ech->id, ech->err_count, + ech->err_stuck ? ", giving up on it" : ""); + + if (ed) { + spin_lock_irqsave(&ech->vc.lock, eflags); + vchan_cookie_complete(&ed->vd); + spin_unlock_irqrestore(&ech->vc.lock, eflags); + } + } + + for (i = 0; i < PL080_CH_COUNT; i++) { + if (!(tc & BIT(i))) + continue; + { + struct s5l_pl080_chan *ch = + &pl->chans[eng * PL080_CH_COUNT + i]; + struct s5l_pl080_desc *d; + unsigned long flags; + + spin_lock_irqsave(&ch->vc.lock, flags); + d = ch->running; + if (d && d->cyclic) { + d->periods_done++; + if (d->periods) + d->periods_done %= d->periods; + /* + * Advance the self-linked node HERE, + * not from the consumer's workqueue. + * + * The terminal count and the LLI + * reload are one hardware event: by + * the time this handler runs the + * PL080S has already latched lli[0] + * and started the next period. The + * window to write the period after + * that is exactly one period long, + * and a process-context consumer + * loses that race whenever the box + * is busy -- the hardware then + * replays the stale source and the + * stream stutters with no FIFO + * underrun to show for it. + * + * Two stores to coherent memory is + * all it takes, so there is no + * reason for it to be anywhere but + * in the interrupt. + */ + if (d->ring && d->lli) { + d->ring_off += d->ring_period; + if (d->ring_off >= d->ring_bytes) + d->ring_off = 0; + d->lli[0].src = cpu_to_le32( + d->ring_base + + d->ring_off); + } + /* + * Not vchan_cyclic_callback(): that + * runs the callback in a tasklet, and + * this one sleeps. See + * s5l_pl080_cyc_workfn(). + */ + if (ch->cyc_active) { + atomic_inc(&ch->cyc_pending); + queue_work(pl->cyc_wq, + &ch->cyc_work); + } + spin_unlock_irqrestore(&ch->vc.lock, + flags); + continue; + } + ch->running = NULL; + if (d) + vchan_cookie_complete(&d->vd); + { + struct virt_dma_desc *vd = + vchan_next_desc(&ch->vc); + if (vd) { + list_del(&vd->node); + s5l_pl080_start(ch, + to_s5l_desc(vd)); + } + } + spin_unlock_irqrestore(&ch->vc.lock, flags); + } + } + } + } while (pending); + return serviced ? IRQ_HANDLED : IRQ_NONE; +} + +static struct dma_chan *s5l_pl080_xlate_args(struct s5l_pl080 *pl, + struct of_phandle_args *spec) +{ + struct s5l_pl080_chan *ch; + unsigned int i, peri, eng_lo, eng_hi; + + if (!pl || !spec || spec->args_count < 1) + return NULL; + peri = spec->args[0] & 0x1f; + if (force_peri >= 0) + peri = force_peri & 0x1f; + if (force_eng >= 0) { + eng_lo = force_eng ? PL080_CH_COUNT : 0; + eng_hi = eng_lo + PL080_CH_COUNT; + } else { + eng_lo = 0; + eng_hi = PL080_CH_COUNT * 2; + } + /* + * RetailOS music: peri 10 on physical ch2 (EnbldChns=0x4). Prefer it. + * (ASoC often already holds ch2 — dma_tone must reuse via lookup_peri, + * not allocate a second peri-10 channel on ch3.) + */ + if (force_ch >= 0 && force_ch < PL080_CH_COUNT && peri == 10) { + unsigned int prefer = eng_lo + force_ch; + + if (prefer < eng_hi) { + ch = &pl->chans[prefer]; + if (ch->base && !ch->vc.chan.client_count) { + ch->peri = peri; + ch->src_burst = clamp(m2p_src_burst, 0, 7); + ch->dst_burst = clamp(m2p_dst_burst, 0, 7); + pl080_vinfo(pl->dev, + "xlate DT peri=%u -> ch%u (forced) peri=%u\n", + spec->args[0] & 0x1f, prefer, ch->peri); + return dma_get_slave_channel(&ch->vc.chan); + } + } + } + for (i = eng_lo; i < eng_hi; i++) { + ch = &pl->chans[i]; + if (!ch->base || ch->vc.chan.client_count) + continue; + ch->peri = peri; + ch->src_burst = clamp(m2p_src_burst, 0, 7); + ch->dst_burst = clamp(m2p_dst_burst, 0, 7); + pl080_vinfo(pl->dev, "xlate DT peri=%u -> ch%u (eng%u) peri=%u\n", + spec->args[0] & 0x1f, i, i / PL080_CH_COUNT, ch->peri); + return dma_get_slave_channel(&ch->vc.chan); + } + return NULL; +} + +static struct dma_chan *s5l_pl080_xlate(struct of_phandle_args *spec, + struct of_dma *ofdma) +{ + return s5l_pl080_xlate_args(ofdma->of_dma_data, spec); +} + +/* + * Request PL080 slave channel from consumer DT dmas[] without creating + * the consumer-side dma:tx sysfs symlink (avoids sysfs_warn_dup spam). + */ +struct dma_chan *s5l_pl080_request_slave(struct device *consumer, + unsigned int idx) +{ + struct of_phandle_args spec; + struct platform_device *pdev; + struct s5l_pl080 *pl; + struct dma_chan *chan; + + if (!consumer || !consumer->of_node) + return ERR_PTR(-ENODEV); + if (of_parse_phandle_with_args(consumer->of_node, "dmas", "#dma-cells", + idx, &spec)) + return ERR_PTR(-ENODEV); + pdev = of_find_device_by_node(spec.np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + pl = platform_get_drvdata(pdev); + if (!pl) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + chan = s5l_pl080_xlate_args(pl, &spec); + put_device(&pdev->dev); + if (!chan) + return ERR_PTR(-EBUSY); + return chan; +} +EXPORT_SYMBOL_GPL(s5l_pl080_request_slave); + +/* + * Return an already-owned channel for peri (no client_count bump). + * dma_tone uses this so it rides RetailOS ch2 held by ASoC instead of + * allocating a second peri-10 channel. + */ +struct dma_chan *s5l_pl080_lookup_peri(unsigned int peri) +{ + struct device_node *np; + struct platform_device *pdev; + struct s5l_pl080 *pl; + unsigned int i; + struct dma_chan *found = NULL; + + np = of_find_compatible_node(NULL, NULL, "apple,s5l8740-pl080"); + if (!np) + np = of_find_compatible_node(NULL, NULL, "arm,pl080"); + if (!np) + return NULL; + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) + return NULL; + pl = platform_get_drvdata(pdev); + if (!pl) { + put_device(&pdev->dev); + return NULL; + } + peri &= 0x1f; + for (i = 0; i < PL080_CH_COUNT * 2; i++) { + struct s5l_pl080_chan *ch = &pl->chans[i]; + + if (!ch->base || ch->peri != peri || !ch->vc.chan.client_count) + continue; + found = &ch->vc.chan; + dev_dbg(pl->dev, "lookup peri=%u -> ch%u clients=%u\n", + peri, ch->id, ch->vc.chan.client_count); + break; + } + put_device(&pdev->dev); + return found; +} +EXPORT_SYMBOL_GPL(s5l_pl080_lookup_peri); + +int s5l_pl080_peri_snapshot(unsigned int peri, u32 *src, u32 *dst, u32 *en) +{ + struct dma_chan *chan; + struct s5l_pl080_chan *ch; + u8 id; + + chan = s5l_pl080_lookup_peri(peri); + if (!chan) + return -ENODEV; + ch = to_s5l_chan(chan); + id = ch->id % PL080_CH_COUNT; + if (src) + *src = readl(ch->base + PL080_Cx_SRC(id)); + if (dst) + *dst = readl(ch->base + PL080_Cx_DST(id)); + if (en) + *en = readl(ch->base + PL080_ENBLD_CHNS); + return 0; +} +EXPORT_SYMBOL_GPL(s5l_pl080_peri_snapshot); + +/* + * Rewrite the source address of a running single-node cyclic transfer. + * + * This exists to reproduce how RetailOS actually drives audio playback. Stock + * builds ONE 20-byte descriptor whose next-pointer points at ITSELF, so the + * transfer never terminates: after each terminal count the PL080S reloads + * SrcAddr, DstAddr, LLI, Control and Control2 from that descriptor, and + * because LLI reloads with its own address the cycle repeats forever. The + * channel's Enable bit stays set for the entire life of the stream and no + * software touches a channel register on the data path at all -- the producer + * only rewrites the descriptor in memory and then waits for the TC interrupt. + * + * Re-arming with dmaengine_prep_slave_single() per period cannot match + * that: it terminates the channel at every period boundary and reprograms + * it from a workqueue, and the IIS TX FIFO drains in about a millisecond, + * so every period boundary is an underrun window. + * + * prep_dma_cyclic already builds stock's topology when buf_len equals + * period_len: nlli == 1 and lli[0].lli == lli_phys, a node pointing at + * itself. This function supplies the one missing piece, a way to move the + * source between terminal counts. + * + * Deliberately touches NO channel register -- not Cx_SrcAddr, Cx_Control, + * Cx_Control2, Cx_LLI or Cx_Config -- and never disables or restarts the + * channel. Writing any of those is what stock does exactly once, at start. + * The LLI block is dma_alloc_coherent, so a plain store is enough and no + * cache maintenance is needed. + */ +int s5l_pl080_rearm_set_src(struct dma_chan *c, dma_addr_t addr, size_t bytes) +{ + struct s5l_pl080_chan *ch; + struct s5l_pl080_desc *d; + unsigned long flags; + unsigned int words; + int ret = 0; + + if (!c) + return -ENODEV; + ch = to_s5l_chan(c); + + spin_lock_irqsave(&ch->vc.lock, flags); + d = ch->running; + if (!d || !d->lli) { + ret = -ENXIO; + goto out; + } + /* + * Only the stock shape is accepted. A multi-node chain is a different + * model and silently rewriting node 0 of one would corrupt it. + */ + if (!d->cyclic || d->nlli != 1) { + ret = -EINVAL; + goto out; + } + + d->lli[0].src = cpu_to_le32(lower_32_bits(addr)); + + words = (unsigned int)(bytes / s5l_pl080_unit()); + if ((le32_to_cpu(d->lli[0].ctrl2) & PL080S_XFER_COUNT_MASK) != words) + d->lli[0].ctrl2 = cpu_to_le32(words & PL080S_XFER_COUNT_MASK); +out: + spin_unlock_irqrestore(&ch->vc.lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(s5l_pl080_rearm_set_src); + +/* + * Hand the whole ring to the controller once, and let the terminal-count + * interrupt walk it. + * + * s5l_pl080_rearm_set_src() above requires a consumer to post every period + * from process context, inside a one-period window. That works and it is + * what stock does -- sub_C4960 writes the descriptor and blocks on the + * per-channel semaphore -- but stock's producer is a dedicated task on a + * system with nothing else running. This driver runs a workqueue item behind + * snd_pcm_period_elapsed() on a single core that is also servicing USB, + * and it misses the window often enough to be audible: the hardware + * reloads the stale source, replays the period it just finished, and + * nothing anywhere reports an error because the FIFO never ran dry. + * + * The source walk is entirely deterministic for a cyclic ALSA buffer -- + * base, wrap at base+bytes, step by one period -- so there is no reason + * for software scheduling to be in the loop at all. This installs the + * three numbers and the interrupt does the arithmetic. + * + * ring_off starts at one period, not zero: s5l_pl080_start() has already + * loaded period zero into Cx_SrcAddr, so what the FIRST terminal count + * reloads has to be period one. + */ +size_t s5l_pl080_max_seg_bytes(void) +{ + return (size_t)PL080_MAX_XFER_WORDS * s5l_pl080_unit(); +} +EXPORT_SYMBOL_GPL(s5l_pl080_max_seg_bytes); + +int s5l_pl080_rearm_set_ring(struct dma_chan *c, dma_addr_t base, size_t bytes, + size_t period) +{ + struct s5l_pl080_chan *ch; + struct s5l_pl080_desc *d; + unsigned long flags; + unsigned int words; + int ret = 0; + + if (!c || !bytes || !period || bytes % period) + return -EINVAL; + ch = to_s5l_chan(c); + + spin_lock_irqsave(&ch->vc.lock, flags); + d = ch->running; + if (!d || !d->lli) { + ret = -ENXIO; + goto out; + } + /* Same restriction as set_src: only the single self-linked node. */ + if (!d->cyclic || d->nlli != 1) { + ret = -EINVAL; + goto out; + } + + d->ring_base = lower_32_bits(base); + d->ring_bytes = (u32)bytes; + d->ring_period = (u32)period; + d->ring_off = (u32)period % (u32)bytes; + d->lli[0].src = cpu_to_le32(d->ring_base + d->ring_off); + + words = (unsigned int)(period / s5l_pl080_unit()); + if ((le32_to_cpu(d->lli[0].ctrl2) & PL080S_XFER_COUNT_MASK) != words) + d->lli[0].ctrl2 = cpu_to_le32(words & PL080S_XFER_COUNT_MASK); + + /* Last, so the interrupt never sees a half-installed ring. */ + d->ring = true; +out: + spin_unlock_irqrestore(&ch->vc.lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(s5l_pl080_rearm_set_ring); + +static ssize_t chregs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct s5l_pl080 *pl = dev_get_drvdata(dev); + int n = 0, eng, i; + + if (!pl) + return -ENODEV; + for (eng = 0; eng < 2; eng++) { + void __iomem *b = pl->base[eng]; + + if (!b) + continue; + n += scnprintf(buf + n, PAGE_SIZE - n, + "eng%u en=0x%x rawtc=0x%x rawerr=0x%x cfg=0x%x\n", + eng, readl(b + PL080_ENBLD_CHNS), + readl(b + PL080_RAW_TC), + readl(b + PL080_RAW_ERR), + readl(b + PL080_CONFIG)); + for (i = 0; i < PL080_CH_COUNT; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, + " e%u ch%u src=%08x dst=%08x lli=%08x ctl=%08x cfg=%08x c2=%08x\n", + eng, i, + readl(b + PL080_Cx_SRC(i)), + readl(b + PL080_Cx_DST(i)), + readl(b + PL080_Cx_LLI(i)), + readl(b + PL080_Cx_CTL(i)), + readl(b + PL080_Cx_CFG(i)), + readl(b + PL080S_Cx_CONTROL2(i))); + } + return n; +} +static DEVICE_ATTR_RO(chregs); + +static int s5l_pl080_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l_pl080 *pl; + struct resource *res; + int irq, i, ret; + u32 id0; + + pl = devm_kzalloc(dev, sizeof(*pl), GFP_KERNEL); + if (!pl) + return -ENOMEM; + pl->dev = dev; + spin_lock_init(&pl->lock); + init_llist_head(&pl->free_list); + INIT_WORK(&pl->free_work, s5l_pl080_free_work); + + for (i = 0; i < 2; i++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + continue; + pl->base[i] = devm_ioremap_resource(dev, res); + if (IS_ERR(pl->base[i])) + return PTR_ERR(pl->base[i]); + } + if (!pl->base[0]) + return -EINVAL; + + pl->clk[0] = devm_clk_get_optional(dev, "dmac0"); + pl->clk[1] = devm_clk_get_optional(dev, "dmac1"); + if (!IS_ERR_OR_NULL(pl->clk[0])) + clk_prepare_enable(pl->clk[0]); + if (!IS_ERR_OR_NULL(pl->clk[1])) + clk_prepare_enable(pl->clk[1]); + + id0 = readl(pl->base[0] + 0xfe0) & 0xff; + writel(PL080_CONFIG_EN, pl->base[0] + PL080_CONFIG); + writel(sync_mask, pl->base[0] + PL080_SYNC); + if (pl->base[1]) { + writel(PL080_CONFIG_EN, pl->base[1] + PL080_CONFIG); + writel(sync_mask, pl->base[1] + PL080_SYNC); + } + /* + * Nothing else is seeded here, and two things that briefly were have + * been removed because stock does not write them. + * + * SoftBReq: a working RetailOS reads 0x00000008 while playing, but + * the register is never written anywhere in the image -- a search of + * the whole decomp for 0x38200020 / 0x38700020 finds zero hits. That + * value is a reset default or hardware-set, not configuration. + * + * Idle-channel Control: RetailOS holds 0x80249000 in ch0/ch1 while + * playing, and it is tempting to seed it. But sub_A4F94 writes + * Cx_Control only for the channel it is ALLOCATING, and from a + * per-peripheral table at 0x0891DB94: + * + * v20 = 32 * chan + 0x3820010C; // Cx_CTL(chan) + * *(u32 *)0x38200030 = 1; // DMACConfiguration + * *v20 = *(u32 *)(4 * peri + 0x891DB94); + * + * so the value in an idle channel is residue from its last + * allocation, not state stock maintains. Cx_Control is already written + * at channel start, which is the equivalent. + * + * Reading a register on a working device says what it holds. It does + * not say the software put it there. + */ + dev_info(dev, "DMACSync=0x%08x (0 = sync enabled, as stock)\n", + sync_mask); + + /* DDI0196 M2M: try AHB1/AHB2 × 16/32-bit. dst0==pattern means the engine copies. */ + { + dma_addr_t sa, da; + u32 *s, *d; + int eng, as, ad, wid; + + s = dmam_alloc_coherent(dev, 64, &sa, GFP_KERNEL); + d = dmam_alloc_coherent(dev, 64, &da, GFP_KERNEL); + if (s && d) { + s[0] = 0xa5a5a5a5; + s[1] = 0x5a5a5a5a; + for (eng = 0; eng < 2; eng++) { + void __iomem *b = pl->base[eng]; + + if (!b) + continue; + pl080_vinfo(dev, "selftest eng%u id=%02x\n", + eng, readl(b + 0xfe0) & 0xff); + for (wid = 1; wid <= 2; wid++) { + for (as = 0; as <= 1; as++) { + for (ad = 0; ad <= 1; ad++) { + u32 ctl, words = (wid == 2) ? 4 : 8; + + d[0] = 0; + writel(0, b + PL080_Cx_CFG(0)); + writel(lower_32_bits(sa), + b + PL080_Cx_SRC(0)); + writel(lower_32_bits(da), + b + PL080_Cx_DST(0)); + writel(0, b + PL080_Cx_LLI(0)); + ctl = words | + (wid << CTL_WIDTH_SHIFT) | + (wid << (CTL_WIDTH_SHIFT + 3)) | + (2 << CTL_SBSIZE_SHIFT) | + (2 << CTL_DBSIZE_SHIFT) | + CTL_PROT_PRIV | + CTL_PROT_BUFF | + CTL_PROT_CACHE | + CTL_SRC_AI | CTL_DST_AI | + CTL_TC_IRQ; + if (as) + ctl |= BIT(24); + if (ad) + ctl |= BIT(25); + writel(ctl, b + PL080_Cx_CTL(0)); + writel(words, b + PL080S_Cx_CONTROL2(0)); + writel(CFG_ENABLE | CFG_IE | CFG_ITC, + b + PL080_Cx_CFG(0)); + writel(BIT(0), b + PL080_SOFT_BREQ); + writel(BIT(0), b + PL080_SOFT_SREQ); + udelay(50); + pl080_vinfo(dev, + "selftest e%u w%u s%d d%d tc=%x err=%x dst=%08x\n", + eng, wid, as, ad, + readl(b + PL080_RAW_TC), + readl(b + PL080_RAW_ERR), + d[0]); + writel(0, b + PL080_Cx_CFG(0)); + writel(~0u, b + PL080_INT_TC_CLEAR); + writel(~0u, b + PL080_INT_ERR_CLEAR); + } + } + } + } + } + } + + dma_cap_zero(pl->ddev.cap_mask); + dma_cap_set(DMA_SLAVE, pl->ddev.cap_mask); + dma_cap_set(DMA_CYCLIC, pl->ddev.cap_mask); + /* + * WQ_HIGHPRI because a missed period is an audible dropout, and + * WQ_MEM_RECLAIM because playback must keep draining under memory + * pressure rather than deadlock against it. + */ + pl->cyc_wq = alloc_workqueue("pl080-cyc", + WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); + if (!pl->cyc_wq) + return -ENOMEM; + ret = devm_add_action_or_reset(dev, s5l_pl080_destroy_wq, pl->cyc_wq); + if (ret) + return ret; + + dma_cap_set(DMA_PRIVATE, pl->ddev.cap_mask); + pl->ddev.dev = dev; + pl->ddev.device_alloc_chan_resources = s5l_pl080_alloc; + pl->ddev.device_free_chan_resources = s5l_pl080_free; + pl->ddev.device_tx_status = s5l_pl080_tx_status; + pl->ddev.device_issue_pending = s5l_pl080_issue; + pl->ddev.device_prep_slave_sg = s5l_pl080_prep_slave_sg; + pl->ddev.device_prep_dma_cyclic = s5l_pl080_prep_dma_cyclic; + pl->ddev.device_config = s5l_pl080_config; + pl->ddev.device_terminate_all = s5l_pl080_terminate; + pl->ddev.device_synchronize = s5l_pl080_synchronize; + pl->ddev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + pl->ddev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + pl->ddev.directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM); + /* + * The position now comes from the channel's live transfer pointer + * (see s5l_pl080_tx_status), so residue is accurate to a burst rather + * than to a whole descriptor. Declaring DESCRIPTOR made ALSA treat the + * stream as batched and mis-size its own timing. + */ + pl->ddev.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + INIT_LIST_HEAD(&pl->ddev.channels); + + for (i = 0; i < PL080_CH_COUNT * 2; i++) { + struct s5l_pl080_chan *ch = &pl->chans[i]; + + ch->host = pl; + ch->id = i; + ch->base = pl->base[i / PL080_CH_COUNT]; + if (!ch->base) + continue; + ch->vc.desc_free = s5l_pl080_desc_free; + INIT_WORK(&ch->cyc_work, s5l_pl080_cyc_workfn); + atomic_set(&ch->cyc_pending, 0); + vchan_init(&ch->vc, &pl->ddev); + } + + for (i = 0; i < 2; i++) { + irq = platform_get_irq_optional(pdev, i); + if (irq <= 0) + continue; + ret = devm_request_irq(dev, irq, s5l_pl080_irq, 0, + "s5l8740-pl080", pl); + if (ret) + dev_warn(dev, "IRQ %d: %d (poll mode)\n", irq, ret); + } + + ret = dma_async_device_register(&pl->ddev); + if (ret) + return ret; + + ret = of_dma_controller_register(dev->of_node, s5l_pl080_xlate, pl); + if (ret) + dev_warn(dev, "of_dma_controller_register: %d\n", ret); + + pl->dummy_cpu = dmam_alloc_coherent(dev, 4096, &pl->dummy_dma, + GFP_KERNEL); + if (!pl->dummy_cpu) + dev_warn(dev, "dummy DMA sink alloc failed\n"); + + pl->lli_pool = dmam_alloc_coherent(dev, + s5l_pl080_lli_size(PL080_LLI_POOL_NODES), + &pl->lli_pool_phys, GFP_KERNEL); + if (!pl->lli_pool) + dev_warn(dev, "LLI pool alloc failed — GFP_NOWAIT fallback only\n"); + else + pl080_vinfo(dev, "LLI pool %u nodes pa=%pad\n", + PL080_LLI_POOL_NODES, &pl->lli_pool_phys); + + pl->pump = kthread_run(s5l_pl080_pump, pl, "n31-pl080-pump"); + if (IS_ERR(pl->pump)) { + dev_warn(dev, "soft-req pump: %ld\n", PTR_ERR(pl->pump)); + pl->pump = NULL; + } + + platform_set_drvdata(pdev, pl); + ret = device_create_file(dev, &dev_attr_chregs); + if (ret) + dev_warn(dev, "chregs sysfs: %d\n", ret); + pl080_vinfo(dev, + "PL080 dmaengine @%pR id=%02x peri IIS0=10/11 IIS2 RX=13 (RetailOS)\n", + platform_get_resource(pdev, IORESOURCE_MEM, 0), id0); + return 0; +} + +static void s5l_pl080_remove(struct platform_device *pdev) +{ + struct s5l_pl080 *pl = platform_get_drvdata(pdev); + + if (pl->pump) + kthread_stop(pl->pump); + device_remove_file(&pdev->dev, &dev_attr_chregs); + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&pl->ddev); + /* Descriptors parked by s5l_pl080_desc_free() must not outlive the channel. */ + cancel_work_sync(&pl->free_work); + s5l_pl080_free_work(&pl->free_work); +} + +/* + * kexec hands the machine to a new kernel with the old one's memory map + * already forgotten. A channel still running writes into whatever now + * occupies its destination, so the controller has to be stopped before + * the jump -- and remove() does not do it, because unregistering a + * dma_device says nothing to the hardware. + * + * Clearing CONFIG_EN halts both controllers outright rather than + * unwinding channel by channel, which is what is wanted here: nothing + * after this point needs the engine, and a per-channel teardown has + * more ways to get stuck than to succeed. + */ +static void s5l_pl080_shutdown(struct platform_device *pdev) +{ + struct s5l_pl080 *pl = platform_get_drvdata(pdev); + unsigned int i; + + if (!pl) + return; + for (i = 0; i < ARRAY_SIZE(pl->base); i++) { + if (!pl->base[i]) + continue; + writel(readl(pl->base[i] + PL080_CONFIG) & ~PL080_CONFIG_EN, + pl->base[i] + PL080_CONFIG); + } + dev_info(&pdev->dev, "PL080 halted for shutdown/kexec\n"); +} + +static const struct of_device_id s5l_pl080_of_match[] = { + { .compatible = "apple,s5l8740-pl080" }, + { .compatible = "arm,pl080" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l_pl080_of_match); + +static struct platform_driver s5l_pl080_driver = { + .probe = s5l_pl080_probe, + .remove = s5l_pl080_remove, + .shutdown = s5l_pl080_shutdown, + .driver = { + .name = "s5l8740-pl080", + .of_match_table = s5l_pl080_of_match, + }, +}; +module_platform_driver(s5l_pl080_driver); + +MODULE_DESCRIPTION("S5L8740 PL080 dmaengine (N31)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4622176f88cd09..35dc7f47dc11c7 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1345,6 +1345,16 @@ config GPIO_D1830 together with gpio-keys-polled for physical buttons wired through the PMIC. +config GPIO_S5L8740 + tristate "Samsung/Apple S5L8740 SoC GPIO" + depends on ARCH_S5L87XX || COMPILE_TEST + depends on GPIOLIB + depends on OF + help + Banked GPIO controller at 0x3CF00000 for S5L8740 (iPod nano 7G). + DIN/DOUT only; PCON/pinmux is left to SEC/boot or a future + pinctrl driver (direction callbacks are soft no-ops). + config GPIO_DA9052 tristate "Dialog DA9052 GPIO" depends on PMIC_DA9052 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 9062f4de56f2c5..80a7a18808c986 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_GPIO_CROS_EC) += gpio-cros-ec.o obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o obj-$(CONFIG_GPIO_CS5535) += gpio-cs5535.o obj-$(CONFIG_GPIO_D1830) += gpio-d1830.o +obj-$(CONFIG_GPIO_S5L8740) += gpio-s5l8740.o obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o obj-$(CONFIG_GPIO_DA9055) += gpio-da9055.o obj-$(CONFIG_GPIO_DAVINCI) += gpio-davinci.o diff --git a/drivers/gpio/gpio-d1830.c b/drivers/gpio/gpio-d1830.c old mode 100644 new mode 100755 index d15d0ef5600bec..f116b9409d9c5c --- a/drivers/gpio/gpio-d1830.c +++ b/drivers/gpio/gpio-d1830.c @@ -1,17 +1,339 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * GPIO Driver for Dialog Semiconductor D1830 PMIC + * GPIO driver for Dialog Semiconductor D1830 PMIC * - * This driver exposes specific bits of PMIC registers as GPIO lines. - * It is read-only and intended for button polling via gpio-keys-polled. + * Exposes selected PMIC register bits as GPIO lines for gpio-keys-polled, + * implements machine power-off via SEC-observed reg 13 bit0, and registers + * a power_supply battery using the OSOS ADC path (not ACPI — this SoC + * has none; power_supply is the Linux equivalent). + * + * OSOS RetailOS 1.0.2: + * 439A98(1) → 2C778(channel 3, 5 samples) → 3477C / 347E4 / 3484C + * start: reg48 = (reg48 & 0xF0) | (ch & 0xF) | 0x10 + * data: 10-bit = (reg50 << 2) | reg49 + * 158C82(3/5) writes bitfields in 87/88 — not the ADC. Keep poweroff + * unchanged. Do not enable dlg,apply-sec-rails from here. + * + * Analog HP needs SEC sub_23EC sibling LDOs (regs 21–23 bit4) plus + * reg16 bit4. Default probe stays gpio-only. CS42 calls + * d1830_audio_rails() on prepare. Never replay hibernate cookie + * writes (regs 1/2/73/96) unless boot_mode bit7 is actually set. * * Copyright (C) 2026 Vencislav Atanasov */ - -#include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Power-off and restart are the same PMIC write. The bootloader's + * __noreturn sub_128C does + * + * sub_3F60(110, 1, 0) reg 110 = 0 + * sub_3F40(64, 4, v5) read reg 64, add 2, write reg 69 + * sub_3F60(73, 1, 1) reg 73 = 1 restart-after-cut + * sub_3F40(13, ...) reg 13 |= 1 cut power + * sub_1130() halt + * + * while its low-battery path does the reg 13 write with no reg 73 at + * all and stays off. So bit 0 of 13 always cuts power, and 73 decides + * whether the PMIC comes back. Writing 13 without clearing 73 is a + * reboot dressed as a shutdown, which is what this driver was doing. + */ +/* + * Registers are written in hex here, deliberately. + * + * Hex-Rays prints them in decimal, so sub_7484's "case 16: v7 = 40" + * means register 40 decimal, which is 0x28 -- not 0x40. Reading those + * listings as hex is what produced a whole fictional rail map with a + * codec enable at 0x40, and 0x40 turned out to be a ticking counter. + * One radix mistake is a bad afternoon; leaving the door open for a + * second is a design choice. + * + * The decimal literals this driver used were correct -- reg 16 really + * is 0x10, the rail bitfield -- but they made checking against a + * decompiler listing an exercise in mental arithmetic, which is + * exactly when radix mistakes happen. + */ +/* + * sub_1E7C, the boot low-battery decision, tests this after finding + * VBAT below 3550 mV. If the bit is set RetailOS permits Low Power Boot + * even below 3400 mV; if it is clear below 3400 mV it powers the device + * off instead. That is precisely the shape of an external-source + * indication. + * + * Confirmed on glass. Unplugging cleared the bit and replugging set + * it, observed live: + * + * 3094 external power OFF -> gadget disconnect + * 3169 external power ON -> gadget connect + * + * so this is the VBUS presence indication rather than merely some + * external source. VBUS_PRESENT is what it means; the EXT_POWER + * spelling is kept as an alias so the firmware notes that introduced + * it still read straight. + * + * Bit 2 is separate and also moved. 0x05 went 0x68 to 0x6c and 0x06 + * went 0x40 to 0x44 across the same cable cycle, so bit 2 latched in + * both. 0x05 to 0x08 are the event latches this driver already reads + * for buttons, masked at 0x09 to 0x0c, which makes bit 2 an + * attach/detach event rather than a level. + * + * Deliberately not wired to anything. One observation cannot separate + * attach from detach, and acknowledging a latch we have not + * characterised risks consuming an event something else depends on. + */ +#define D1830_REG_STATUS0 0x05 /* 5 */ +#define D1830_STATUS_VBUS_PRESENT BIT(6) +#define D1830_STATUS_EXT_POWER D1830_STATUS_VBUS_PRESENT +#define D1830_REG_STATUS1 0x06 /* 6 */ +/* Latched in both 0x05 and 0x06 by a cable event; polarity unproven. */ +#define D1830_STATUS_CABLE_EVENT BIT(2) + +#define D1830_REG_POWEROFF 0x0d /* 13 */ +#define D1830_POWEROFF_BIT BIT(0) + +/* + * Read at entry to sub_1E7C, before the battery is even measured: if + * set, RetailOS writes 1 back and goes straight to Low Power Boot. A + * persistent latch, and specifically NOT the external-power test -- + * that decision happens later from 0x05 bit 6. + */ +#define D1830_REG_LOWPOWER_LATCH 0x0e /* 14 */ +#define D1830_LOWPOWER_LATCH BIT(0) + +/* Programmed by the stock Low Power Boot path. Meanings unresolved. */ +#define D1830_REG_LOWPOWER_CFG0 0x24 /* 36 */ +#define D1830_REG_LOWPOWER_CFG1 0x29 /* 41 */ +#define D1830_LOWPOWER_CFG1_MASK 0x07 +#define D1830_LOWPOWER_CFG1_RETAIL 0x06 + +/* + * sub_1E7C thresholds, in millivolts against d1830_adc_to_mv(): + * >= 3550 boot normally + * 3400..3549 Low Power Boot + * < 3400 Low Power Boot if external power, else power off + */ +#define N31_VBAT_BOOT_NORMAL_MV 3550 +#define N31_VBAT_BOOT_MIN_MV 3400 +#define D1830_REG_RESTART 0x49 /* 73; 1 = come back after the cut */ +#define D1830_REG_CLR_ON_CUT 0x6e /* 110 */ + +/* + * SoC fallback from the tail of sub_128C, reached only if the PMIC + * write fails. 0xA5 is a magic value rather than a bit pattern. + */ +#define S5L8740_RESET_A_PHYS 0x3c800000UL +#define S5L8740_RESET_B_PHYS 0x3c500050UL +#define S5L8740_RESET_MAGIC 0xa5 +#define D1830_REG_ADC_CFG 0x30 /* 48 */ +#define D1830_REG_ADC_LOW 0x31 /* 49 */ +#define D1830_REG_ADC_HIGH 0x32 /* 50 */ +#define D1830_ADC_CH_VBAT 3 /* OSOS 439A98 case 1 */ +#define D1830_ADC_START 0x10 +#define D1830_ADC_SAMPLES 5 +/* + * VBAT conversion, from the RetailOS formula rather than a full-scale + * guess: + * + * mv = (62 * raw + 207000) / 100 + * + * which reproduces its own reference points exactly -- raw 1823 gives + * 3200 mV, 2877 gives 3853, 3435 gives 4199. + * + * Two things follow. The transfer function has a 2070 mV offset, so + * treating the ADC as linear from zero was wrong; and its slope is + * 0.62 mV per count against the 1.47 mV per count that 6000/1023 + * implied, so the old conversion multiplied every LSB of ADC noise by + * more than twice as much as it should have. That is a large part of + * why vbat looked so unstable. + * + * The formula is written for a 12-bit raw. This ADC path assembles ten + * bits -- (r50 << 2) | (r49 & 3) -- so it is shifted up by two rather + * than the constants being rescaled, which keeps the published numbers + * checkable against the source they came from. + */ +/* + * sub_4234, the boot battery helper, and this is the conversion to use. + * Its raw assembly is (reg32 << 2) | reg31, which is exactly what this + * driver already reads, and its constants are explicit in the ARM: + * + * 0x3FF = 1023, 0x7D0 = 2000, 0x9C4 = 2500 + * mv = 2500 + raw * 2000 / 1023 + * + * so 0 is 2500 mV and 1023 is 4500 mV. + * + * This replaces (62 * raw12 + 207000) / 100, which came from the later + * twelve-bit averaging API. Both are real, but they are different + * paths, and ours assembles ten bits the way the boot helper does. The + * deciding argument is that the 3550 and 3400 thresholds below are + * compared against this function's output, so any other scale makes + * them mean something the firmware never intended. + */ +#define D1830_VBAT_BASE_MV 2500 +#define D1830_VBAT_SPAN_MV 2000 +#define D1830_VBAT_FULL_SCALE 1023 + +/* RetailOS sub_8005C314 warns and acts at 3.2 V. Reported, not acted on + * here: a driver that powers the machine off the instant a noisy read + * dips below a threshold is worse than one that reports it. */ +#define D1830_VBAT_LOW_MV 3200 +#define D1830_DESIGN_UAH 200000 /* nano 7 pack, 200 mAh */ +#define D1830_DESIGN_MIN_UV 3300000 +#define D1830_DESIGN_MAX_UV 4200000 + +/* Provisional Li-ion empty/full for capacity % (OPEN scale) */ +#define D1830_MV_EMPTY 3300 +#define D1830_MV_FULL 4200 + +static bool dump_only; +module_param(dump_only, bool, 0644); +MODULE_PARM_DESC(dump_only, + "Log PMIC rail ops, do not write (docs-internal n31-pmic dummies)"); + +/* + * Chatty WR/RMW/reg dumps are off by default. Enable with verbose=1, or + * via dynamic debug (dev_dbg) if the kernel was built with DYNAMIC_DEBUG. + */ +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Verbose n31-pmic I2C/reg logging (default N; also gpio_d1830.verbose=1 on cmdline)"); + +#define d1830_vinfo(dev, fmt, ...) \ + do { \ + if (verbose) \ + dev_info((dev), fmt, ##__VA_ARGS__); \ + else \ + dev_dbg((dev), fmt, ##__VA_ARGS__); \ + } while (0) + +/* + * Off by default, and it should stay that way: this replays the + * board-wide rail trim, whose ACTIVE_1 form is (old & 0x2F) | 0x10. + * That is correct at boot, but it clears bits 6 and 7, so running it + * again once the panel is up switches the display rail off. Measured on + * glass: ACTIVE_1 goes 0x7f -> 0x3f on the first codec prepare after + * boot, and the screen turns white. + * + * This comment used to end "the codec's analog supply is always on, so + * audio does not need this at all". That claim is unverified and should + * not be relied on -- but the correction attempted here, powering the + * codec from registers 20-23 bit 4, was also wrong and locked the + * kernel. Those registers hold a bare 5-bit voltage code with no enable + * bit; see the note above d1830_audio_rails(). Where the CS42L81 analog + * supply actually comes from is still an open question, and neither + * claim in this paragraph's history should be treated as settled. + */ +/* + * On by default now. Stock's bootloader runs this sequence (sub_23EC) and + * our boot chain does not, so skipping it left the analog LDOs untrimmed -- + * a divergence from stock, not a safe default. The white-screen hazard that + * originally forced it off was the reg-16 write clearing bits 6 and 7; that + * is now guarded twice in d1830_sec_trim_seq(), by n31_pmu_rail_held_mask() + * and by preserving the live 0xc0 bits unless apply_boot_rails is set. + */ +/* + * Default N, and it now gates only the sysfs audio_rails hook. + * + * This used to default Y while its own description said N, so anyone + * reading the description believed the sequence was dormant. It was not: + * it fired from d1830_audio_rails() on every codec prepare, which is how + * the 0x14-0x17 voltage write reached a live rail. + * + * Both automatic callers are gone now -- codec prepare and pm_resume -- + * because sub_23EC lives only in the bootloader and stock has no OS path + * that replays it. What is left is a manual hook, and a manual hook that + * clears ACTIVE1 bits 6 and 7 with the panel live must be armed + * deliberately rather than by default. + */ +static bool allow_audio_rails; +module_param(allow_audio_rails, bool, 0644); +MODULE_PARM_DESC(allow_audio_rails, + "Arm the sysfs audio_rails debug hook (default N; replays the bootloader trim and clears ACTIVE1 bits 6/7 with the panel live)"); + + +/* Off by default: false Sleep during NAND CS storms was cutting power. */ +static bool sleep_poweroff; +module_param(sleep_poweroff, bool, 0644); +MODULE_PARM_DESC(sleep_poweroff, + "Hold Sleep ~500ms → pm_power_off (default N)"); + +/* + * The 100 ms r5-r8 sweep was a bring-up aid, not a product poll: Home / + * Sleep / Play arrive on the PMIC nIRQ (GPIO 86 DIN) via + * d1830_n31_din_nirq(). Polling it during a NAND CS storm is what + * produced the phantom "SLEEP PRESS r7=0x0e" that power-watch turned + * into a poweroff mid-recover. Interrupt-driven by default. + */ +/* + * Off by default. An earlier version of this comment claimed "on by + * default", which the declaration below has never matched -- requesting the + * line arms a real EIC input whose level encoding is still unconfirmed, and + * getting that wrong wedges the system rather than merely failing. Polling + * covers the buttons meanwhile. Set pmic_irq=1 to arm it deliberately. + */ +static bool pmic_irq; +module_param(pmic_irq, bool, 0444); +MODULE_PARM_DESC(pmic_irq, + "Request the PMIC nIRQ line (default N; polling is used)"); + +static bool ack_events = true; +module_param(ack_events, bool, 0644); +MODULE_PARM_DESC(ack_events, + "Write back the PMIC event latches to release nIRQ"); + +/* + * Polled by default. The PMIC nIRQ reaches the EIC, but the EIC's level + * behaviour is not yet pinned down, so this is what makes Home, Play and + * Sleep work today. 100 ms is well inside a keypress. + */ +/* + * On, and load-bearing. Do not turn this off. + * + * It was switched off once on the strength of the comment above + * d1830_trace_work() calling it "not a product poll", and Home, Play and + * Sleep stopped working immediately. The reason is in /proc/interrupts: + * there is no PMIC nIRQ line there at all. The EIC path this is nominally + * a fallback for does not deliver, so the poll is not backup -- it is the + * only thing turning a key press into an input event. + * + * The cost is real: ten I2C transactions a second for the life of the + * system. The fix is to make the PMIC interrupt work and then retire this, + * not to delete the mechanism that currently carries the buttons. + */ +static unsigned int btn_poll_ms = 100; +module_param(btn_poll_ms, uint, 0644); +MODULE_PARM_DESC(btn_poll_ms, + "Fallback button poll period in ms (0=off, interrupt-only)"); + +/* + * A single glitched I2C byte must not reach userspace either: power-watch + * turns one KEY_POWER press into reboot(RB_POWER_OFF). Re-read after + * btn_confirm_ms and only report the press if Sleep is still low. + */ +static unsigned int btn_confirm_ms = 60; +module_param(btn_confirm_ms, uint, 0644); +MODULE_PARM_DESC(btn_confirm_ms, + "Re-read delay confirming a Sleep press before KEY_POWER (0=off)"); struct d1830_gpio_map { u8 reg; @@ -23,8 +345,32 @@ struct d1830_gpio { struct gpio_chip gpio_chip; struct d1830_gpio_map *map; int num_gpios; + struct power_supply *psy; + struct power_supply *usb_psy; + struct input_dev *input; + u8 last_home, last_sleep, last_play; + u8 sleep_hold; + int last_r5, last_r6, last_r7, last_r8; + bool keys_inited; + /* Event-register reads serviced, for the input diagnostics. */ + unsigned int irq_events; + bool lsb_logged; + int mv_filtered; + int last_mv; + u16 last_adc; + u8 last_r48, last_r49, last_r50; + unsigned long last_adc_jiffies; + int psy_ticks; + struct delayed_work trace; + struct delayed_work confirm; + u8 sleep_pending; + int trace_r[12]; + int trace_din; + bool trace_inited; }; +static struct i2c_client *d1830_poweroff_client; + static int d1830_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) { return GPIO_LINE_DIRECTION_IN; @@ -36,19 +382,17 @@ static int d1830_gpio_get(struct gpio_chip *chip, unsigned int offset) struct d1830_gpio_map *entry; int ret; - if (offset >= gpio_dev->num_gpios) { + if (offset >= gpio_dev->num_gpios) return -EINVAL; - } entry = &gpio_dev->map[offset]; - ret = i2c_smbus_read_byte_data(gpio_dev->client, entry->reg); if (ret < 0) { - dev_err(&gpio_dev->client->dev, - "Failed to read reg 0x%02x: %d\n", entry->reg, ret); + dev_err_ratelimited(&gpio_dev->client->dev, + "Failed to read reg 0x%02x: %d\n", + entry->reg, ret); return 0; } - return !!(ret & BIT(entry->bit)); } @@ -57,14 +401,14 @@ static int d1830_gpio_direction_input(struct gpio_chip *chip, unsigned int offse return 0; } -static int d1830_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) +static int d1830_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, + int value) { return -ENOTSUPP; } static void d1830_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { - // read-only } static int d1830_gpio_parse_dt(struct d1830_gpio *gpio_dev) @@ -78,109 +422,4076 @@ static int d1830_gpio_parse_dt(struct d1830_gpio *gpio_dev) size = of_property_count_u32_elems(np, "dlg,gpio-map"); if (size <= 0 || size % 2) { - dev_err(dev, "Invalid or missing 'dlg,gpio-map' property " - "(size=%d)\n", size); + dev_err(dev, "Invalid or missing 'dlg,gpio-map' (size=%d)\n", size); return -EINVAL; } gpio_dev->num_gpios = size / 2; - gpio_dev->map = devm_kcalloc(dev, gpio_dev->num_gpios, - sizeof(*gpio_dev->map), GFP_KERNEL); + sizeof(*gpio_dev->map), GFP_KERNEL); if (!gpio_dev->map) return -ENOMEM; for (i = 0; i < gpio_dev->num_gpios; i++) { u32 reg, bit; - of_property_read_u32_index(np, "dlg,gpio-map", - i * 2, ®); - of_property_read_u32_index(np, "dlg,gpio-map", - i * 2 + 1, &bit); - + of_property_read_u32_index(np, "dlg,gpio-map", i * 2, ®); + of_property_read_u32_index(np, "dlg,gpio-map", i * 2 + 1, &bit); if (reg > 0xff || bit > 7) { dev_err(dev, "GPIO %d: reg=0x%x bit=%u out of range\n", i, reg, bit); return -EINVAL; } - gpio_dev->map[i].reg = (u8)reg; gpio_dev->map[i].bit = (u8)bit; + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* RTC */ +/* */ +/* The counter lives in the PMIC rather than in SoC MMIO. OSOS reads it */ +/* a byte at a time, least significant first, in sub_16517E: */ +/* */ +/* sub_41286E(a1, 124, v8); byte 0 */ +/* sub_41286E(a1, 125, (char *)v8 + 1); byte 1 */ +/* sub_41286E(a1, 126, (char *)v8 + 2); byte 2 */ +/* sub_41286E(a1, 127, (char *)v8 + 3); byte 3 */ +/* */ +/* and the bootloader's sub_FDE zeroes the same four, which is what you */ +/* would expect of a counter being reset rather than of scratch space. */ +/* */ +/* Honest limitation: OSOS passes the four bytes through sub_16CA5E */ +/* before using them, and that routine is a long bit-manipulation which */ +/* has not been decoded. This driver therefore treats the registers as */ +/* a plain little-endian seconds counter. Reads and writes are */ +/* self-consistent, so timekeeping across a reboot works; what is not */ +/* guaranteed is that the epoch agrees with RetailOS. Set the clock */ +/* once with hwclock and it will keep. */ +/* ------------------------------------------------------------------ */ + +/* + * Calendar registers, not the seconds counter this driver first used. + * + * 124 to 127 looked like a 32-bit counter because OSOS sub_16517E reads + * exactly those four LSB-first, and writing them round-tripped perfectly. + * They are MEMBYTE, 0x60 to 0x87, which is general purpose non-volatile + * scratch -- so that was our own value being read back, and OSOS keeps a + * timestamp there by convention rather than because it is the clock. + * + * The real block, confirmed against the part: R64 counts seconds and was + * observed ticking 0x23 to 0x26 across three seconds. + * + * 0x40 64 COUNT_SEC bits 5:0, bit 6 MONITOR + * 0x41 65 COUNT_MIN bits 5:0 + * 0x42 66 COUNT_HOUR bits 4:0 + * 0x43 67 COUNT_DAY bits 4:0, 1 based + * 0x44 68 COUNT_MONTH bits 3:0, 1 based + * 0x45 69 COUNT_YEAR bits 5:0, 0 is 2000 + * + * Reading COUNT_SEC latches the rest, and writing COUNT_YEAR commits + * them, so the order of access is part of the interface rather than a + * convenience. + * + * MONITOR is the part telling us whether it kept time: 0 means power was + * lost. On this unit it reads 0 with day and month at zero, which are not + * legal values -- the clock has been free-running since the factory and + * was never set. Reporting that as a date would be worse than refusing, + * so an unset clock returns -EINVAL and userspace can decide. + */ +#define D1830_RTC_SEC 0x40 +#define D1830_RTC_MIN 0x41 +#define D1830_RTC_HOUR 0x42 +#define D1830_RTC_DAY 0x43 +#define D1830_RTC_MONTH 0x44 +#define D1830_RTC_YEAR 0x45 +#define D1830_RTC_MONITOR BIT(6) +#define D1830_RTC_YEAR_BASE 100 /* tm_year for 2000 */ + +/* + * The calendar registers are not a calendar. They are one binary counter, + * and it ticks. + * + * The previous reading of this block had 0x40 as seconds and 0x41 to 0x45 as + * minutes through year, concluded that "R65 to R68 accept nothing and do not + * advance", and fell back to keeping a timestamp in MEMBYTE scratch that + * cannot tick at all. Hence a clock that read 1970 in Linux and a wrong + * time in RetailOS. + * + * Measured on this unit over thirty seconds rather than three: + * + * 0x40 0xf0 -> 0x0f 0x41 0xd4 -> 0xd5 0x42 0x28 unchanged + * + * Read little-endian across 0x40..0x42 that is 0x28d4f0 -> 0x28d50f, which + * is +31 in 30 s. It is a seconds upcounter, battery backed, currently + * around 31 days. 0x41 advances once every 256 seconds, not every 60, which + * is exactly why a three second sample saw it "not advancing" and read the + * block as a broken calendar. + * + * So the part gives a monotonic seconds count with no epoch. Wall time needs + * an anchor, and MEMBYTE is where that belongs: + * + * Wall time needs an epoch, and RetailOS keeps one -- a single 32-bit + * offset, not a pair. From the decompiled OSOS: + * + * sub_26764 read: sub_1FFFE(pmic, 64, 4, &t); t = tick at 0x40 + * off = cached ? cached : sub_20718(pmic, &off); + * *out = t + off; + * + * sub_11AA0 write: sub_422BE(pmic, &t); t = tick at 0x40 + * sub_141A2(pmic, wall - t); store at 0x74 + * + * where sub_422BE is sub_1FFFE(pmic, 64, 4, ...) and sub_141A2 is + * sub_19EE8(pmic, 116, 4, ...) -- a four-byte write at register 0x74. + * + * wall = tick + offset, offset = wall - tick + * + * This driver used to keep its own pair at 0x78 and 0x7c instead, chosen + * because 0x78..0x87 read zero on this unit and 0x68..0x77 was noted as + * holding live data. That note was right for the wrong reason: 0x74 is + * live precisely because it is this offset. The zero-reading range turned + * out not to survive a power cycle, so every boot found an empty anchor, + * fell back to the compiled-in seed, and came up roughly a day and a half + * behind whatever had last been set. + * + * Using stock's register also means RetailOS and Linux now agree about + * what time it is, since they read and write the same offset. + * + * sub_20718 substitutes 1 for a zero read, so zero means "never set". + */ +#define D1830_RTC_TICK_BASE 0x40 /* 32-bit LE seconds upcounter */ +#define D1830_RTC_TICK_COUNT 4 +#define D1830_RTC_OFFSET 0x74 /* wall = tick + this, 4 bytes LE (OSOS) */ +#define D1830_RTC_OFFSET_COUNT 4 - dev_dbg(dev, "GPIO %d -> reg 0x%02x bit %u\n", i, reg, bit); +static int d1830_rtc_read_u32(struct i2c_client *client, u8 base, u32 *out) +{ + u32 v = 0; + int i, r; + + for (i = 0; i < 4; i++) { + r = i2c_smbus_read_byte_data(client, base + i); + if (r < 0) + return r; + v |= (u32)(r & 0xff) << (8 * i); } + *out = v; + return 0; +} +static int d1830_rtc_write_u32(struct i2c_client *client, u8 base, u32 val) +{ + int i, r; + + for (i = 0; i < 4; i++) { + r = i2c_smbus_write_byte_data(client, base + i, + (val >> (8 * i)) & 0xff); + if (r < 0) + return r; + } return 0; } -static int d1830_gpio_probe(struct i2c_client *client) +/* + * Seed for the first boot on a unit whose anchor has never been written. + * + * The counter at 0x40 is monotonic but has no epoch, so with an empty + * offset the only honest answer is zero -- which is what put "setting + * system clock to 1970-01-01" in the boot log. There is no other time + * source on this device: no calendar, no network that early in boot. + * Seeding once gives a sane starting date that then advances correctly, + * and any later set_time overwrites it. + * + * This is a fallback, not the mechanism. If it is being used on a unit + * that has had its time set, the offset is not persisting and that is the + * bug -- not the seed being stale. + */ +static uint rtc_seed = 1788000000; /* 2026-08-29 */ +module_param(rtc_seed, uint, 0644); +MODULE_PARM_DESC(rtc_seed, + "wall seconds to anchor to when the RTC has never been set"); + +static int d1830_rtc_read_time(struct device *dev, struct rtc_time *tm) { - struct d1830_gpio *gpio_dev; - struct device *dev = &client->dev; + struct i2c_client *client = to_i2c_client(dev); + u32 tick, offset; int ret; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(dev, "Adapter does not support SMBUS_BYTE_DATA\n"); - return -ENODEV; + ret = d1830_rtc_read_u32(client, D1830_RTC_TICK_BASE, &tick); + if (ret) + return ret; + ret = d1830_rtc_read_u32(client, D1830_RTC_OFFSET, &offset); + if (ret) + return ret; + + /* Zero means never set, the same reading sub_20718 takes. */ + if (!offset && rtc_seed) { + dev_info(dev, "RTC never set; seeding offset from %u\n", + rtc_seed); + offset = rtc_seed - tick; + d1830_rtc_write_u32(client, D1830_RTC_OFFSET, offset); } - gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL); - if (!gpio_dev) - return -ENOMEM; + /* + * Deliberately u32 arithmetic: the offset is stored as the wrapped + * difference, so the sum wraps back to the right answer and a tick + * that has rolled over needs no special case. + */ + rtc_time64_to_tm((time64_t)(u32)(tick + offset), tm); + return 0; +} - gpio_dev->client = client; - i2c_set_clientdata(client, gpio_dev); +static int d1830_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + time64_t secs = rtc_tm_to_time64(tm); + u32 tick; + int ret; - ret = d1830_gpio_parse_dt(gpio_dev); + if (secs < 0 || secs > U32_MAX) + return -EINVAL; + + ret = d1830_rtc_read_u32(client, D1830_RTC_TICK_BASE, &tick); if (ret) return ret; + /* offset = wall - tick, exactly sub_11AA0. */ + return d1830_rtc_write_u32(client, D1830_RTC_OFFSET, + (u32)secs - tick); +} - gpio_dev->gpio_chip.label = dev_name(dev); - gpio_dev->gpio_chip.parent = dev; - gpio_dev->gpio_chip.owner = THIS_MODULE; - gpio_dev->gpio_chip.base = -1; - gpio_dev->gpio_chip.ngpio = gpio_dev->num_gpios; - gpio_dev->gpio_chip.can_sleep = true; - gpio_dev->gpio_chip.get_direction = d1830_gpio_get_direction; - gpio_dev->gpio_chip.direction_input = d1830_gpio_direction_input; - gpio_dev->gpio_chip.direction_output = d1830_gpio_direction_output; - gpio_dev->gpio_chip.get = d1830_gpio_get; - gpio_dev->gpio_chip.set = d1830_gpio_set; +static const struct rtc_class_ops d1830_rtc_ops = { + .read_time = d1830_rtc_read_time, + .set_time = d1830_rtc_set_time, +}; - ret = devm_gpiochip_add_data(dev, &gpio_dev->gpio_chip, gpio_dev); +static int d1830_rtc_register(struct i2c_client *client) +{ + struct rtc_device *rtc; + + rtc = devm_rtc_allocate_device(&client->dev); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + rtc->ops = &d1830_rtc_ops; + rtc->range_min = 0; + rtc->range_max = U32_MAX; + /* + * Four PMIC registers and nothing else: there is no alarm here. Say + * so, or registration goes looking for one -- __rtc_read_alarm walks + * forward from the current time hunting a valid match, which with a + * counter that reads zero means a long search over i2c inside probe, + * and the driver sits in Loading while it happens. + */ + clear_bit(RTC_FEATURE_ALARM, rtc->features); + + return devm_rtc_register_device(rtc); +} + +static void d1830_soc_reset_fallback(void) +{ + void __iomem *a, *b; + + a = ioremap(S5L8740_RESET_A_PHYS, 4); + b = ioremap(S5L8740_RESET_B_PHYS, 4); + if (a) + writel(S5L8740_RESET_MAGIC, a); + if (b) + writel(S5L8740_RESET_MAGIC, b); + /* No iounmap: this does not return if it works. */ +} + +static void d1830_cut_power(struct i2c_client *client, bool restart) +{ + int v, ret; + u8 out; + + if (!client) + return; + + dev_emerg(&client->dev, "PMIC %s: reg %u |= 0x%02lx\n", + restart ? "restart" : "poweroff", + D1830_REG_POWEROFF, D1830_POWEROFF_BIT); + + /* + * Order matters and this half is the whole difference between the + * two: 73 must be settled before 13 is written, because 13 is what + * actually removes power and nothing runs afterwards. + */ + i2c_smbus_write_byte_data(client, D1830_REG_CLR_ON_CUT, 0); + i2c_smbus_write_byte_data(client, D1830_REG_RESTART, restart ? 1 : 0); + + v = i2c_smbus_read_byte_data(client, D1830_REG_POWEROFF); + out = (v < 0) ? (u8)D1830_POWEROFF_BIT : (u8)(v | D1830_POWEROFF_BIT); + + ret = i2c_smbus_write_byte_data(client, D1830_REG_POWEROFF, out); if (ret) { - dev_err(dev, "Failed to add GPIO chip: %d\n", ret); - return ret; + dev_emerg(&client->dev, "PMIC poweroff write failed (%d); retry raw 1\n", + ret); + i2c_smbus_write_byte_data(client, D1830_REG_POWEROFF, + (u8)D1830_POWEROFF_BIT); } - dev_info(dev, "Registered %u read-only GPIOs using Dialog D1830 driver\n", - gpio_dev->num_gpios); - return 0; + mdelay(100); + + /* + * Still here, so the PMIC did not take it. For a restart the SoC can + * still do the job; for a power-off there is no equivalent, and + * resetting instead would be worse than stopping -- with panic=-1 on + * the command line, returning from here reboots, which is the + * "shutdown rebooted the device" symptom rather than a fix for it. + */ + if (restart) { + dev_emerg(&client->dev, + "PMIC restart did not take; SoC reset\n"); + d1830_soc_reset_fallback(); + mdelay(100); + } else { + dev_emerg(&client->dev, + "PMIC poweroff did not take; halting\n"); + } + while (1) + cpu_relax(); } -static const struct of_device_id d1830_gpio_of_match[] = { - { .compatible = "dlg,d1830-gpio" }, - { } -}; -MODULE_DEVICE_TABLE(of, d1830_gpio_of_match); +static void d1830_pm_power_off(void) +{ + d1830_cut_power(d1830_poweroff_client, false); +} -static const struct i2c_device_id d1830_gpio_id[] = { - { "d1830-gpio" }, - { } +/* + * pm_power_off is the legacy global and mainline is migrating off it. + * The sys-off handler is the current interface, is properly scoped to + * this device, and unregisters itself, so the global is only kept as a + * fallback for the in-driver callers that still reference it. + */ +static int d1830_sys_off_handler(struct sys_off_data *data) +{ + d1830_cut_power(d1830_poweroff_client, false); + return NOTIFY_DONE; +} + +/* + * Without this, reboot fell through to whatever the architecture could + * manage on its own, which on this SoC is nothing reliable. Priority is + * high because the PMIC is the only thing here that genuinely restarts + * the machine. + */ +static int d1830_restart_handler(struct notifier_block *nb, + unsigned long mode, void *cmd) +{ + d1830_cut_power(d1830_poweroff_client, true); + return NOTIFY_DONE; +} + +static struct notifier_block d1830_restart_nb = { + .notifier_call = d1830_restart_handler, + .priority = 192, }; -MODULE_DEVICE_TABLE(i2c, d1830_gpio_id); + +/* + * Register window, for identifying this part against the DA9053 + * datasheet -- the closest public Dialog device. The layouts are + * similar but not known to be identical, and several things worth + * having (the RTC monitor bit, the alarm block, the charger) are only + * usable once the offset between the two is established rather than + * assumed. + * + * echo 108 24 > regs dump 24 registers from 108 + * cat regs + */ +static unsigned int d1830_regs_first = 108, d1830_regs_count = 24; + +static ssize_t regs_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned int i; + int len = 0, v; + + for (i = 0; i < d1830_regs_count && len < PAGE_SIZE - 24; i++) { + v = i2c_smbus_read_byte_data(client, d1830_regs_first + i); + len += scnprintf(buf + len, PAGE_SIZE - len, + "R%-3u = %02x\n", + d1830_regs_first + i, v < 0 ? 0 : v); + } + return len; +} + +static ssize_t regs_store(struct device *dev, struct device_attribute *a, + const char *buf, size_t count) +{ + unsigned int first, n; + + if (sscanf(buf, "%u %u", &first, &n) != 2) + return -EINVAL; + if (first > 255 || n == 0 || n > 64 || first + n > 256) + return -EINVAL; + d1830_regs_first = first; + d1830_regs_count = n; + return count; +} +static DEVICE_ATTR_RW(regs); + +static ssize_t do_poweroff_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (buf[0] != '1' && buf[0] != 'y' && buf[0] != 'Y') + return -EINVAL; + d1830_cut_power(client, false); + return count; +} +static DEVICE_ATTR_WO(do_poweroff); + +static ssize_t audio_rails_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + if (buf[0] != '1' && buf[0] != 'y' && buf[0] != 'Y') + return -EINVAL; + ret = d1830_audio_rails(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(audio_rails); + +/* + * Chain from RE (do not invert without new ARM): + * user key → PMIC status r5-r8 (sub_26520, read-only in OSOS) + * → SoC GPIO 86 DIN 0 = asserted (sub_42BBEC; EFBB4 loops while DIN==0) + * → EIC g2 b22 INTLEVEL=0 INTTYPE=1 (40641C(86,1); 7D490 set=high/clear=low) + * → VIC EXT3 + * SEC sub_27F4 is IIC1 + rail/hibernate RMW. No PMIC MCU image, no write of + * r5-r8. OSOS 4118BC never writes 5-8. Linux must not replay 27F4 (reg13). + */ +static void d1830_dump_irq_chain(struct i2c_client *client, const char *tag) +{ + void __iomem *eic, *gpio; + u8 i; + int v[12]; + u32 din, pcon, dir, level, stat, en, itype; + + for (i = 0; i < 12; i++) + v[i] = i2c_smbus_read_byte_data(client, i + 1); + dev_dbg(&client->dev, + "n31-pmic %s r1-8=%02x %02x %02x %02x %02x %02x %02x %02x r9-12=%02x %02x %02x %02x\n", + tag, + v[0] < 0 ? 0 : v[0], v[1] < 0 ? 0 : v[1], + v[2] < 0 ? 0 : v[2], v[3] < 0 ? 0 : v[3], + v[4] < 0 ? 0 : v[4], v[5] < 0 ? 0 : v[5], + v[6] < 0 ? 0 : v[6], v[7] < 0 ? 0 : v[7], + v[8] < 0 ? 0 : v[8], v[9] < 0 ? 0 : v[9], + v[10] < 0 ? 0 : v[10], v[11] < 0 ? 0 : v[11]); + + /* GPIO 86: bank 10, pin 6. EIC group 2, bit 22. */ + gpio = ioremap(0x3cf00000ul + 32u * 10u, 32); + eic = ioremap(0x39700000ul, 0x100); + if (gpio && eic) { + din = readl(gpio + 0x04); + pcon = readl(gpio); + dir = readl(gpio + 0x14); + level = readl(eic + 0x80 + 8); + stat = readl(eic + 0xa0 + 8); + en = readl(eic + 0xc0 + 8); + itype = readl(eic + 0xe0 + 8); + dev_dbg(&client->dev, + "n31-pmic %s gpio86 din=%u dir=%u pcon=%08x eic g2 L=%08x S=%08x E=%08x T=%08x bit22 L=%u S=%u E=%u T=%u irq=%d\n", + tag, !!(din & BIT(6)), !!(dir & BIT(6)), pcon, + level, stat, en, itype, + !!(level & BIT(22)), !!(stat & BIT(22)), + !!(en & BIT(22)), !!(itype & BIT(22)), + client->irq); + } + if (gpio) + iounmap(gpio); + if (eic) + iounmap(eic); + + { + int r14 = i2c_smbus_read_byte_data(client, 14); + int r41 = i2c_smbus_read_byte_data(client, 41); + int r42 = i2c_smbus_read_byte_data(client, 42); + int r43 = i2c_smbus_read_byte_data(client, 43); + int r60 = i2c_smbus_read_byte_data(client, 60); + + dev_dbg(&client->dev, + "n31-pmic %s r14=%02x r41=%02x r42=%02x r43=%02x r60=%02x (SEC want 14=20 41=(x&ec)|10 42=(x&c0)|14 43=(x&f0)|01 60=01)\n", + tag, + r14 < 0 ? 0 : r14, r41 < 0 ? 0 : r41, + r42 < 0 ? 0 : r42, r43 < 0 ? 0 : r43, + r60 < 0 ? 0 : r60); + } +} + +/* + * OSOS BatteryLevel 0x588150 (not PCFPowerMgr, not SEC): + * 51686C → 174288(0x891DB48, 0x1c1f40ee) + * 174288 sbfx+1 per bit → write ~bytes to regs 9-12 + * then 5D308(gpio 0x56, level=0, type=1) and EFBB4 + * SEC 27F4 never writes 9-12. Linux DFU skips OSOS, so replay this mask. + * 158C82(3/5) in the same task is ADC regs 87/88 — not nIRQ. + */ +static void d1830_osos_nirq_mask(struct i2c_client *client) +{ + static const u8 regs[] = { 9, 10, 11, 12 }; + static const u8 vals[] = { 0x11, 0xbf, 0xe0, 0xe3 }; + int i, ret, before[4]; + + for (i = 0; i < 4; i++) + before[i] = i2c_smbus_read_byte_data(client, regs[i]); + for (i = 0; i < 4; i++) { + ret = i2c_smbus_write_byte_data(client, regs[i], vals[i]); + if (ret) + dev_err(&client->dev, + "n31-pmic OSOS r%u=0x%02x write %d\n", + regs[i], vals[i], ret); + } + dev_dbg(&client->dev, + "n31-pmic OSOS 174288 mask r9-12 %02x %02x %02x %02x -> 11 bf e0 e3\n", + before[0] < 0 ? 0 : before[0], before[1] < 0 ? 0 : before[1], + before[2] < 0 ? 0 : before[2], before[3] < 0 ? 0 : before[3]); +} + +static void d1830_btn_poll_once(struct d1830_gpio *gpio_dev); + +/* GPIO 86 DIN edge from n31-btn poll — I2C read even if EIC missed the line. */ +void d1830_n31_din_nirq(void) +{ + struct i2c_client *client = d1830_poweroff_client; + struct d1830_gpio *gpio_dev; + + if (!client) + return; + gpio_dev = i2c_get_clientdata(client); + if (gpio_dev) + d1830_btn_poll_once(gpio_dev); +} + +static irqreturn_t d1830_irq_thread(int irq, void *data) +{ + struct d1830_gpio *gpio_dev = data; + static unsigned int hits; + + hits++; + if (hits <= 8 || (hits & 0x3f) == 0) { + dev_dbg(&gpio_dev->client->dev, + "n31-pmic nIRQ irq=%d hit=%u (GPIO86 EIC g2 b22)\n", + irq, hits); + if (hits == 1) + d1830_dump_irq_chain(gpio_dev->client, "nirq1"); + } + d1830_btn_poll_once(gpio_dev); + return IRQ_HANDLED; +} + +/* Screen-sleep and power-button policy; defined with the PMU rail code. */ +static void n31_power_button(bool pressed); +static void n31_power_button_poll(void); +static void n31_home_button(bool pressed); + +static void d1830_key_active_low(struct d1830_gpio *gpio_dev, unsigned int code, + u8 now_bit, u8 *last, const char *name) +{ + bool pressed, was; + + if (now_bit == *last) + return; + /* OSOS sub_4195D8(id, bit==0): pressed when the status bit is clear. */ + pressed = !now_bit; + was = !(*last); + *last = now_bit; + if (pressed == was) + return; + dev_dbg(&gpio_dev->client->dev, + "n31-btn %s %s (bit=%u, 0=pressed OSOS)\n", + name, pressed ? "PRESS" : "release", now_bit); + if (code == KEY_HOMEPAGE) + n31_home_button(pressed); + if (gpio_dev->input) { + input_report_key(gpio_dev->input, code, pressed); + input_sync(gpio_dev->input); + } +} + +/* + * Registers 5-8 are the event latches and 9-12 their masks, the usual + * layout for this PMIC family -- d1830_osos_nirq_mask() programs the + * masks. Event latches are write-1-to-clear, and nothing here was + * clearing them. + * + * That is fatal rather than untidy. nIRQ is requested level-low and + * one-shot, so an uncleared latch holds the line asserted: the handler + * returns, the line is still low, the interrupt fires again, and genirq + * eventually disables it as spurious. Buttons work once and then stop + * for the rest of the boot, which is exactly the reported symptom. + * + * Only bits that actually read as set are written back, so this cannot + * disturb a latch that armed between the read and the acknowledgement. + */ +/* + * Everything needed to tell a dead nIRQ from a dead button, without + * having to reflash to add a printk: the live event and mask registers, + * how many interrupts genirq has actually delivered, and whether it has + * given up on the line. + */ +static ssize_t buttons_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct d1830_gpio *g = i2c_get_clientdata(client); + int r5, r6, r7, r8, r9, r10, r11, r12; + + if (!g) + return -ENODEV; + r5 = i2c_smbus_read_byte_data(client, 5); + r6 = i2c_smbus_read_byte_data(client, 6); + r7 = i2c_smbus_read_byte_data(client, 7); + r8 = i2c_smbus_read_byte_data(client, 8); + r9 = i2c_smbus_read_byte_data(client, 9); + r10 = i2c_smbus_read_byte_data(client, 10); + r11 = i2c_smbus_read_byte_data(client, 11); + r12 = i2c_smbus_read_byte_data(client, 12); + + return sysfs_emit(buf, + "events r5=%02x r6=%02x r7=%02x r8=%02x\n" + "masks r9=%02x r10=%02x r11=%02x r12=%02x\n" + "home=%d sleep=%d play=%d (0 = pressed)\n" + "irq=%d serviced=%u ack_events=%d btn_poll_ms=%u\n" + "input=%s\n", + r5 & 0xff, r6 & 0xff, r7 & 0xff, r8 & 0xff, + r9 & 0xff, r10 & 0xff, r11 & 0xff, r12 & 0xff, + r7 < 0 ? -1 : !!(r7 & BIT(4)), + r7 < 0 ? -1 : !!(r7 & BIT(5)), + r8 < 0 ? -1 : !!(r8 & BIT(1)), + client->irq, g->irq_events, ack_events, btn_poll_ms, + g->input ? "registered" : "absent"); +} +static DEVICE_ATTR_RO(buttons); + +static void d1830_ack_events(struct i2c_client *client, + int r5, int r6, int r7, int r8) +{ + static const u8 regs[] = { 5, 6, 7, 8 }; + int vals[4] = { r5, r6, r7, r8 }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + if (vals[i] <= 0) + continue; + if (i2c_smbus_write_byte_data(client, regs[i], (u8)vals[i])) + dev_warn_ratelimited(&client->dev, + "event ack r%u=0x%02x failed\n", + regs[i], vals[i]); + } +} + +static void d1830_btn_poll_once(struct d1830_gpio *gpio_dev) +{ + struct i2c_client *client = gpio_dev->client; + int r5, r6, r7, r8; + u8 home, sleep, play; + + r5 = i2c_smbus_read_byte_data(client, 5); + r6 = i2c_smbus_read_byte_data(client, 6); + r7 = i2c_smbus_read_byte_data(client, 7); + r8 = i2c_smbus_read_byte_data(client, 8); + if (r7 < 0) + return; + + if (ack_events) + d1830_ack_events(client, r5, r6, r7, r8); + gpio_dev->irq_events++; + + /* OSOS sub_26520: Home=r7b4, Sleep=r7b5, Play=r8b1. */ + home = !!(r7 & BIT(4)); + sleep = !!(r7 & BIT(5)); + play = (r8 >= 0) ? !!(r8 & BIT(1)) : 1; + + if (!gpio_dev->keys_inited) { + gpio_dev->last_home = home; + gpio_dev->last_sleep = sleep; + gpio_dev->last_play = play; + gpio_dev->last_r5 = r5 < 0 ? 0 : r5; + gpio_dev->last_r6 = r6 < 0 ? 0 : r6; + gpio_dev->last_r7 = r7; + gpio_dev->last_r8 = r8 < 0 ? 0 : r8; + gpio_dev->keys_inited = true; + dev_dbg(&client->dev, + "n31-pmic idle r5=0x%02x r6=0x%02x r7=0x%02x r8=0x%02x home=%u sleep=%u play=%u (OSOS active-low)\n", + r5 < 0 ? 0 : r5, r6 < 0 ? 0 : r6, r7, + r8 < 0 ? 0 : r8, home, sleep, play); + return; + } + + if (r5 >= 0 && r5 != gpio_dev->last_r5) + dev_dbg(&client->dev, "n31-pmic r5 0x%02x->0x%02x xor=0x%02x\n", + gpio_dev->last_r5, r5, gpio_dev->last_r5 ^ r5); + if (r6 >= 0 && r6 != gpio_dev->last_r6) + dev_dbg(&client->dev, "n31-pmic r6 0x%02x->0x%02x xor=0x%02x\n", + gpio_dev->last_r6, r6, gpio_dev->last_r6 ^ r6); + if (r7 != gpio_dev->last_r7) + dev_dbg(&client->dev, "n31-pmic r7 0x%02x->0x%02x xor=0x%02x\n", + gpio_dev->last_r7, r7, gpio_dev->last_r7 ^ r7); + if (r8 >= 0 && r8 != gpio_dev->last_r8) + dev_dbg(&client->dev, "n31-pmic r8 0x%02x->0x%02x xor=0x%02x\n", + gpio_dev->last_r8, r8, gpio_dev->last_r8 ^ r8); + + d1830_key_active_low(gpio_dev, KEY_HOMEPAGE, home, + &gpio_dev->last_home, "HOME"); + d1830_key_active_low(gpio_dev, KEY_PLAYPAUSE, play, + &gpio_dev->last_play, "PLAY"); + + if (!sleep) { + if (gpio_dev->last_sleep) { + /* + * First low sample only arms the press. A real press easily + * outlives btn_confirm_ms; a glitched I2C byte during a NAND + * CS storm does not, and power-watch turns one KEY_POWER into + * reboot(RB_POWER_OFF). + */ + if (btn_confirm_ms && !gpio_dev->sleep_pending) { + gpio_dev->sleep_pending = 1; + dev_dbg(&client->dev, + "n31-btn SLEEP arm r7=0x%02x (confirm in %ums)\n", + r7, btn_confirm_ms); + schedule_delayed_work(&gpio_dev->confirm, + msecs_to_jiffies(btn_confirm_ms)); + return; + } + gpio_dev->sleep_pending = 0; + d1830_vinfo(&client->dev, + "n31-btn SLEEP PRESS r7=0x%02x (bit5 1->0)\n", + r7); + n31_power_button(true); + if (gpio_dev->input) { + input_report_key(gpio_dev->input, KEY_POWER, 1); + input_sync(gpio_dev->input); + } + } + /* Hold Sleep across 5 polls (~500ms) before cutting power. + * Short press still emits KEY_POWER for power-watch / + * pm_power_off; hold is the kernel-direct fallback when + * userspace is not watching. One noisy I2C byte must not + * hibernate. + */ + n31_power_button_poll(); + if (gpio_dev->sleep_hold < 5) + gpio_dev->sleep_hold++; + if (gpio_dev->sleep_hold == 5) { + if (!sleep_poweroff) { + d1830_vinfo(&client->dev, + "n31-btn SLEEP held (poweroff disabled; sleep_poweroff=1 to enable)\n"); + } else { + dev_warn(&client->dev, + "n31-btn SLEEP held — poweroff\n"); + /* Prefer machine pm_power_off (same cut_power). */ + if (pm_power_off) + pm_power_off(); + else + d1830_cut_power(client, false); + } + } + } else { + if (!gpio_dev->last_sleep) { + dev_dbg(&client->dev, + "n31-btn SLEEP release r7=0x%02x\n", r7); + n31_power_button(false); + if (gpio_dev->input) { + input_report_key(gpio_dev->input, KEY_POWER, 0); + input_sync(gpio_dev->input); + } + } + gpio_dev->sleep_pending = 0; + gpio_dev->sleep_hold = 0; + } + gpio_dev->last_sleep = sleep; + if (r5 >= 0) + gpio_dev->last_r5 = r5; + if (r6 >= 0) + gpio_dev->last_r6 = r6; + gpio_dev->last_r7 = r7; + if (r8 >= 0) + gpio_dev->last_r8 = r8; +} + +/* Split the silent-PMIC case: do r5-r8 bits move when Home/Play/Sleep + * are pressed, and does GPIO 86 DIN follow? Not a product poll. + */ +static void d1830_trace_work(struct work_struct *work) +{ + struct d1830_gpio *gpio_dev = container_of(to_delayed_work(work), + struct d1830_gpio, trace); + unsigned int period = btn_poll_ms ? btn_poll_ms : 1000; + unsigned int psy_every = 10000 / period; + + if (btn_poll_ms) + d1830_btn_poll_once(gpio_dev); + if (gpio_dev->psy && ++gpio_dev->psy_ticks >= (psy_every ? psy_every : 1)) { + gpio_dev->psy_ticks = 0; + power_supply_changed(gpio_dev->psy); + if (gpio_dev->usb_psy) + power_supply_changed(gpio_dev->usb_psy); + } + schedule_delayed_work(&gpio_dev->trace, msecs_to_jiffies(period)); +} + +/* Second look at Sleep after btn_confirm_ms; see d1830_btn_poll_once(). */ +static void d1830_confirm_work(struct work_struct *work) +{ + struct d1830_gpio *gpio_dev = container_of(to_delayed_work(work), + struct d1830_gpio, confirm); + + d1830_btn_poll_once(gpio_dev); +} + +/* + * OSOS 3477C + 347E4 + 2C778(3, 5). Channel 3 is VBAT. 10-bit sample + * averaged 5×. Scale is 10-bit * 6 V FS / 1023 (emcore / OSOS). + * 439A98 then >>2 — logged only. RetailOS UI cache at 0x891DB18 + * is still unmapped. No writes to 87/88. Never write reg 13 here. + */ +static int d1830_adc_once_ch(struct d1830_gpio *gpio_dev, u8 channel, + int *adc, u8 *r48, u8 *r49, u8 *r50) +{ + struct i2c_client *client = gpio_dev->client; + int cfg, hi, lo, i; + + cfg = i2c_smbus_read_byte_data(client, D1830_REG_ADC_CFG); + if (cfg < 0) + return cfg; + if (cfg & D1830_ADC_START) { + usleep_range(1000, 1500); + cfg = i2c_smbus_read_byte_data(client, D1830_REG_ADC_CFG); + if (cfg < 0) + return cfg; + if (cfg & D1830_ADC_START) + return -EBUSY; + } + cfg = i2c_smbus_write_byte_data(client, D1830_REG_ADC_CFG, + (cfg & 0xF0) | (channel & 0x0f) | + D1830_ADC_START); + if (cfg) + return cfg; + + /* + * Wait for START to clear. It is the conversion-in-progress bit -- + * the busy check at the top of this function already treats it that + * way -- but this loop used to break while it was still set, so the + * data registers were read mid-conversion every time. Averaging five + * torn samples still gives a torn answer, which is why vbat swung + * 3325 to 4058 mV inside twenty seconds and capacity wandered + * between 2 and 24 percent. Nothing downstream of this could be + * trusted, including the charging state. + */ + for (i = 0; i < 10; i++) { + usleep_range(1000, 1500); + cfg = i2c_smbus_read_byte_data(client, D1830_REG_ADC_CFG); + if (cfg < 0) + return cfg; + if (!(cfg & D1830_ADC_START)) + break; + } + if (cfg & D1830_ADC_START) + return -ETIMEDOUT; + + hi = i2c_smbus_read_byte_data(client, D1830_REG_ADC_HIGH); + lo = i2c_smbus_read_byte_data(client, D1830_REG_ADC_LOW); + if (hi < 0) + return hi; + if (lo < 0) + return lo; + *r48 = (u8)cfg; + *r49 = (u8)lo; + *r50 = (u8)hi; + /* + * 347E4: (reg50 << 2) | reg49, where only the bottom two bits of + * reg49 are sample data. They were being OR'd in unmasked, so + * anything above bit 1 landed on top of bits belonging to reg50 -- + * reg49 was observed at 0x0e, which is about 60 LSBs of corruption, + * or roughly 300 mV across a 10-bit range. That is most of the swing + * that made vbat and capacity unusable. + */ + *adc = (((hi & 0xff) << 2) | (lo & 0x3)) & 0x3ff; + return 0; +} + +static int d1830_adc_to_mv(int adc) +{ + return D1830_VBAT_BASE_MV + + (adc * D1830_VBAT_SPAN_MV) / D1830_VBAT_FULL_SCALE; +} + +static int d1830_read_vbat(struct d1830_gpio *gpio_dev, int *mv) +{ + int i, ret, adc, sum = 0, n = 0; + u8 r48 = 0, r49 = 0, r50 = 0; + + if (gpio_dev->last_adc_jiffies && + time_before(jiffies, gpio_dev->last_adc_jiffies + HZ / 2) && + gpio_dev->last_mv > 0) { + *mv = gpio_dev->last_mv; + return 0; + } + + for (i = 0; i < D1830_ADC_SAMPLES; i++) { + ret = d1830_adc_once_ch(gpio_dev, D1830_ADC_CH_VBAT, &adc, + &r48, &r49, &r50); + if (ret) { + if (n) + break; + return ret; + } + sum += adc; + n++; + } + if (!n) + return -EIO; + adc = sum / n; + *mv = d1830_adc_to_mv(adc); + + /* + * Smooth across calls, not just within one. + * + * The conversions are honest -- r48 shows START clear before each + * read and r49 never exceeds 2 -- but the raw high byte still ranges + * over a7 to d3 between samples, which is real movement on VBAT + * under a switching load rather than a decoding fault. The five + * samples above are taken back to back in a few milliseconds, so + * they all land inside the same transient and averaging them + * smooths nothing. That is what made vbat swing 700 mV and capacity + * wander between 2 and 100 percent, which in turn made the charging + * state meaningless. + * + * An exponential average over successive reads spans many + * transients instead of one. Weight is 1/4 new, giving a time + * constant of a few seconds at the half-second cache interval -- + * slow enough to be steady, quick enough to follow a real charge. + * The first reading seeds it rather than ramping up from zero. + */ + if (gpio_dev->mv_filtered) + gpio_dev->mv_filtered = (gpio_dev->mv_filtered * 3 + *mv) / 4; + else + gpio_dev->mv_filtered = *mv; + *mv = gpio_dev->mv_filtered; + gpio_dev->last_adc = (u16)adc; + gpio_dev->last_mv = *mv; + gpio_dev->last_r48 = r48; + gpio_dev->last_r49 = r49; + gpio_dev->last_r50 = r50; + gpio_dev->last_adc_jiffies = jiffies; + + if (!gpio_dev->lsb_logged) { + dev_dbg(&gpio_dev->client->dev, + "n31-bat OSOS 2C778 ch3 adc=%u r48=%02x r49=%02x r50=%02x mv=%d osos>>2=%u\n", + adc, r48, r49, r50, *mv, adc >> 2); + gpio_dev->lsb_logged = true; + } + return 0; +} + +static int d1830_get_vbat_uV(struct d1830_gpio *gpio_dev, int *val) +{ + int mv, ret; + + ret = d1830_read_vbat(gpio_dev, &mv); + if (ret) + return ret; + *val = mv * 1000; + return 0; +} + +/* + * A voltage-curve estimate, not a fuel gauge. The hardware measures + * VBAT and nothing else -- no coulomb counter, no charge current, no + * temperature -- so this is an interpolation between two constants and + * should be read as one. It is exposed because a UI that shows nothing + * is worse than one showing an approximation, not because the number + * is measured. + */ +static int d1830_get_capacity(struct d1830_gpio *gpio_dev, int *val) +{ + int mv, pct, ret; + + ret = d1830_read_vbat(gpio_dev, &mv); + if (ret) + return ret; + if (mv <= D1830_MV_EMPTY) + pct = 0; + else if (mv >= D1830_MV_FULL) + pct = 100; + else + pct = ((mv - D1830_MV_EMPTY) * 100) / + (D1830_MV_FULL - D1830_MV_EMPTY); + *val = clamp_val(pct, 0, 100); + return 0; +} + +static ssize_t vbat_raw_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct d1830_gpio *gpio_dev = i2c_get_clientdata(to_i2c_client(dev)); + int mv, ret; + + ret = d1830_read_vbat(gpio_dev, &mv); + if (ret) + return ret; + return sysfs_emit(buf, + "r48=%02x r49=%02x r50=%02x adc=%u mv=%d\n", + gpio_dev->last_r48, gpio_dev->last_r49, + gpio_dev->last_r50, gpio_dev->last_adc, mv); +} +static DEVICE_ATTR_RO(vbat_raw); + +static int d1830_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct d1830_gpio *gpio_dev = power_supply_get_drvdata(psy); + int mv, pct, ret; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return d1830_get_vbat_uV(gpio_dev, &val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = D1830_DESIGN_MIN_UV; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = D1830_DESIGN_MAX_UV; + return 0; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = D1830_DESIGN_UAH; + return 0; + case POWER_SUPPLY_PROP_CAPACITY: + return d1830_get_capacity(gpio_dev, &val->intval); + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = d1830_get_capacity(gpio_dev, &pct); + if (ret) + return ret; + if (pct <= 5) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (pct <= 15) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (pct >= 95) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (pct >= 80) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + return 0; + case POWER_SUPPLY_PROP_STATUS: + /* + * UNKNOWN is the honest answer. Charge state needs a VBUS or + * charger status bit and neither is identified: TriStar's own + * VBUS task exists in OSOS but its register is not mapped, and + * apple_tristar_vbus() is still a stub. + * + * Guessing from voltage alone was actively misleading. It + * reported Full above 4150 mV, so a cell sitting high after a + * charge read as Full while unplugged, and ADC noise flipped it + * between Full and Discharging inside a few seconds. A field + * that changes meaning with noise is worse than one that admits + * it does not know. + */ + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + case POWER_SUPPLY_PROP_HEALTH: + ret = d1830_read_vbat(gpio_dev, &mv); + if (ret) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + return 0; + } + if (mv < 3000) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (mv > 4300) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + return 0; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + return 0; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + return 0; + default: + return -EINVAL; + } +} + +static enum power_supply_property d1830_psy_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_SCOPE, +}; + +/* ------------------------------------------------------------------ */ +/* Charger scaffold */ +/* */ +/* Deliberately inert. Every property here answers -ENODATA, and no */ +/* register is written, because not one charger register on this part is */ +/* proven: not VBUS presence, not enable, not input current limit, not */ +/* charge current or voltage, not termination, not fault, not battery */ +/* presence. */ +/* */ +/* What exists is the shape. When the registers are found, filling in */ +/* d1830_charger_regs below and the switch bodies is the whole job -- */ +/* the class device, the property list and the plumbing are already */ +/* here and already exercised. */ +/* */ +/* The reason this stops at the shape is asymmetry of consequence. A */ +/* wrong GPIO bit gives silence and you try the next one. A wrong */ +/* charger register pushes current into a soldered lithium cell with no */ +/* replaceable path, and the register map for this part has already been */ +/* revised three times -- a rail block at 0x40 that turned out to be a */ +/* counter, a calendar whose middle registers refuse writes, and a */ +/* seconds store that turned out to be scratch. A default-off flag */ +/* guards against accidents, not against the values being wrong. */ +/* */ +/* Enabling charger_enable does not make it write anything. It only */ +/* registers the class device so a consumer can be developed against it. */ +/* ------------------------------------------------------------------ */ + +/* + * Read-only telemetry for charger reverse engineering. + * + * Nothing here writes a charger register, because none is proven. What + * it does is make the part observable, which is the step that has to come + * first: the way to find the VBUS bit is to watch which bit moves when + * the cable state changes, not to guess an address. + * + * adc_channels sweep every ADC channel, not just VBAT + * charger_regs labelled dump of the candidate window + * charger_watch write 1 to snapshot, read to see what has changed + * + * Only channel 3 has a trustworthy interpretation today -- it is VBAT, + * via sub_8005C2C4 to sub_8005C618(3, ...). The others are read and + * reported as raw counts with no label, because plausible occupants + * include VBUS voltage, charge current and a thermistor, and writing any + * of those names into the output before correlating it would make a + * guess look like a measurement. + * + * The register window is bounded rather than a blind 0x00..0xff sweep. + * Some PMIC registers are clear-on-read or otherwise side-effecting -- + * the event latches in this same address space are write-one-to-clear -- + * so reading everything to see what happens is not free. + */ +#define D1830_ADC_CH_MAX 8 + +static ssize_t adc_channels_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct d1830_gpio *gpio_dev = dev_get_drvdata(dev); + unsigned int ch; + int len = 0; + + if (!gpio_dev) + return -ENODEV; + + for (ch = 0; ch < D1830_ADC_CH_MAX; ch++) { + u8 r48 = 0, r49 = 0, r50 = 0; + int adc = 0, ret; + + ret = d1830_adc_once_ch(gpio_dev, (u8)ch, &adc, + &r48, &r49, &r50); + if (ret) { + len += scnprintf(buf + len, PAGE_SIZE - len, + "ch%u err=%d\n", ch, ret); + continue; + } + /* mV is only meaningful for channel 3; the rest are counts. */ + if (ch == D1830_ADC_CH_VBAT) + len += scnprintf(buf + len, PAGE_SIZE - len, + "ch%u raw=%4d r48=%02x r49=%02x r50=%02x mv=%d (VBAT)\n", + ch, adc, r48, r49, r50, + d1830_adc_to_mv(adc)); + else + len += scnprintf(buf + len, PAGE_SIZE - len, + "ch%u raw=%4d r48=%02x r49=%02x r50=%02x (unidentified)\n", + ch, adc, r48, r49, r50); + } + return len; +} +static DEVICE_ATTR_RO(adc_channels); + +/* + * The window worth watching. Deliberately excludes the event latches at + * 0x05..0x08, which are write-one-to-clear and whose read semantics are + * not established, and the hibernate block, which RetailOS only touches + * during power-state transitions. + */ +static const struct { + u8 first, count; + const char *what; +} d1830_charger_window[] = { + /* Non-overlapping: a register appearing twice would be counted + * twice in the snapshot and reported twice in a diff. */ + /* + * The event latches. Previously left out because their read + * semantics were unestablished, but the buttons path reads them + * continuously without harm, and the cable event turned up here -- + * bit 2 latched in both 0x05 and 0x06 -- so excluding them meant + * excluding the interesting part. Read only; nothing acknowledges. + */ + { 0x05, 4, "event latches (0x05 bit6 = VBUS present, bit2 = cable event)" }, + { 0x09, 4, "event masks" }, + { 0x0d, 5, "0x0d poweroff, 0x10-0x11 rail enables (0x10 bit5 = Grape, proven)" }, + { 0x14, 8, "bootloader LDO trims; 0x1a takes 0xb2, charge-adjacent" }, + { 0x23, 2, "touched by the bootloader trim sequence" }, + { 0x30, 4, "ADC config and result" }, + { 0x57, 2, "BT companion rails (proven via sub_51688C)" }, + /* + * The regions RetailOS only touches during power-state changes. + * Live and stable -- two consecutive reads returned identical + * values, so nothing here is clear-on-read -- and unmapped. + * + * Worth watching rather than naming. 0xc9..0xcc reads ff 0d ff 0d, + * which is 0x0dff twice as little-endian 16-bit, or 3583. That + * sits beside RetailOS's own 3550 and 3400 millivolt thresholds, + * and a duplicated voltage constant in a power block is the shape + * of a charge or recharge threshold. One reading is not a decode, + * so it goes in the capture window and gets a name only if it + * moves when the charging state does. + */ + { 0xa4, 1, "hibernate seq, RetailOS masks 0x3f" }, + { 0xaf, 9, "hibernate config 0xaf-0xb7, unmapped" }, + { 0xc0, 19, "hibernate config 0xc0-0xd2; 0xc9-0xcc looks like 3583 twice" }, +}; + +static ssize_t charger_regs_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned int i, j; + int len = 0, v; + + for (i = 0; i < ARRAY_SIZE(d1830_charger_window) && + len < PAGE_SIZE - 96; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "# %s\n", + d1830_charger_window[i].what); + for (j = 0; j < d1830_charger_window[i].count && + len < PAGE_SIZE - 32; j++) { + u8 reg = d1830_charger_window[i].first + j; + + v = i2c_smbus_read_byte_data(client, reg); + len += scnprintf(buf + len, PAGE_SIZE - len, + "0x%02x = %02x\n", + reg, v < 0 ? 0 : v); + } + } + return len; +} +static DEVICE_ATTR_RO(charger_regs); + +/* + * Snapshot and diff. Write 1 to record the window, read to see only what + * moved since. This is the tool that actually finds a status bit: take a + * snapshot, change something in the world, read back, and the bits that + * changed are the short list. + */ +static u8 d1830_snap[64]; +static bool d1830_snap_valid; + +static unsigned int d1830_snap_fill(struct i2c_client *client, u8 *out) +{ + unsigned int i, j, n = 0; + + for (i = 0; i < ARRAY_SIZE(d1830_charger_window); i++) + for (j = 0; j < d1830_charger_window[i].count && + n < sizeof(d1830_snap); j++) { + int v = i2c_smbus_read_byte_data(client, + d1830_charger_window[i].first + j); + + out[n++] = (v < 0) ? 0 : (u8)v; + } + return n; +} + +static ssize_t charger_watch_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + u8 now[sizeof(d1830_snap)]; + unsigned int i, j, n = 0, changed = 0; + int len = 0; + + if (!d1830_snap_valid) + return sysfs_emit(buf, + "no snapshot; echo 1 > charger_watch first\n"); + + d1830_snap_fill(client, now); + for (i = 0; i < ARRAY_SIZE(d1830_charger_window); i++) + for (j = 0; j < d1830_charger_window[i].count && + n < sizeof(d1830_snap); j++, n++) { + u8 reg = d1830_charger_window[i].first + j; + + if (now[n] == d1830_snap[n]) + continue; + changed++; + if (len < PAGE_SIZE - 64) + len += scnprintf(buf + len, PAGE_SIZE - len, + "0x%02x %02x -> %02x (xor %02x) %s\n", + reg, d1830_snap[n], now[n], + d1830_snap[n] ^ now[n], + d1830_charger_window[i].what); + } + if (!changed) + len += scnprintf(buf + len, PAGE_SIZE - len, + "no change\n"); + return len; +} + +static ssize_t charger_watch_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (buf[0] != '1') + return -EINVAL; + d1830_snap_fill(client, d1830_snap); + d1830_snap_valid = true; + return count; +} +static DEVICE_ATTR_RW(charger_watch); + +/* ------------------------------------------------------------------ */ +/* Charger state and RetailOS power init */ +/* ------------------------------------------------------------------ */ + +enum d1830_input_type { + D1830_INPUT_NONE, + D1830_INPUT_USB, + D1830_INPUT_ACCESSORY, + D1830_INPUT_UNKNOWN_VBUS, +}; + +struct d1830_charger_state { + bool vbus_known; /* false until a VBUS source is identified */ + bool vbus_present; + bool charging_enabled; + bool charging; + bool full; + int vbat_mv; + enum d1830_input_type input; +}; + +static struct d1830_charger_state d1830_chg; + +/* + * VBUS presence. + * + * Returns -ENODATA rather than a value, and that is the honest answer + * today: no D1830 status bit and no TriStar register has been identified + * as the VBUS indication. The existing USB supply reported ONLINE=1 + * unconditionally, which was a bring-up shortcut, and the note is right + * that it must not become the charging policy -- a charger that believes + * the cable is always attached will happily conclude it is charging while + * running the battery flat. + * + * When the bit is found, this is the only function that needs to change. + * Everything downstream already handles the unknown case explicitly + * rather than defaulting to a convenient answer. + */ +/* + * External power present, from sub_1E7C. Reads 0x05 bit 6. + * + * This is the one function the whole charger design was waiting on, and + * everything downstream was already written to consume it. What it + * reports is "a usable external source is available" -- the firmware + * proves that much and no more, so consumers must not upgrade it to + * "charging" on their own. + */ +static int d1830_vbus_present(struct i2c_client *client, bool *present) +{ + int v; + + if (!client) + return -ENODEV; + v = i2c_smbus_read_byte_data(client, D1830_REG_STATUS0); + if (v < 0) + return v; + *present = !!(v & D1830_STATUS_EXT_POWER); + return 0; +} + +/* + * sub_23EC, the RetailOS PMIC power initialisation, in its original order: + * + * program the board trims at 0x14..0x17 + * write 0x1a = 0xb2, twice + * 0x10: clear 0xd0, set 0x10, and set 0x20 on a cold boot + * 0x11: set 0x07 + * 0x13: set 0x02 + * + * Off by default, and the reason is specific rather than caution in + * general. On this device 0x10 currently reads 0x7f, and the RetailOS + * masking takes that to 0x3f -- which clears bit 6. Bit 6 is a live rail + * in our own table. RetailOS runs this at cold boot, before anything + * depends on those rails; running it from a module probe, with the + * display already up, would drop a rail out from under a running system. + * + * The 0x14..0x17 values are computed by RetailOS from board tables that + * have not been recovered, so this reproduces the write ordering and the + * masking, and deliberately leaves those four registers alone rather than + * inventing trim values. That is why it is called an init reproduction + * and not an equivalent. + * + * 0x1a already reads 0xb2 on this unit -- the bootloader wrote it -- so + * that part is a confirmation rather than a change. + */ +/* + * Off by default, and this one is a correction rather than caution. + * + * This write was enabled at probe while 0x1a was believed to be + * charger configuration. It is not: the OSOS rail setter writes it as + * (code & 0x1f) | 0xa0 with code = (mv - 1200) / 100, so 0xb2 is a + * 3.0 V rail. Writing a live rail register during probe on the + * strength of a label that turned out to be wrong is not something + * to leave on by default, and two boots failed to come up with it + * enabled. + * + * The value is what the bootloader already leaves there, so this is + * expected to be a no-op -- but expected is not the same as + * observed, and the cost of being wrong is a device that needs + * recovering by hand. + */ +static bool ldo_1a_write; +module_param(ldo_1a_write, bool, 0444); +MODULE_PARM_DESC(ldo_1a_write, + "Re-write the 0x1a rail voltage at probe (default N)"); + +static bool charger_hw_init; +module_param(charger_hw_init, bool, 0444); +MODULE_PARM_DESC(charger_hw_init, + "Replay the sub_23EC PMIC init (clears 0x10 bit 6; boot only)"); + +/* + * 0x1a is a rail voltage register, not charger configuration. + * + * The OSOS rail setter writes it as + * + * code = (millivolts - 1200) / 100 + * reg = (code & 0x1f) | 0xa0 + * + * and 0xb2 decodes exactly: 0xb2 & 0xe0 is 0xa0, matching the mask, + * and 0xb2 & 0x1f is 18, giving 1200 + 1800 = 3000 mV. The same + * function covers 0x14 through 0x22 with per-register bases and steps, + * which is also what the bootloader is doing when it programs + * 0x14..0x17 from board tables -- those are voltages. + * + * So writing 0xb2 here sets a 3.0 V rail. It is harmless because that + * is the value already present, but calling it CHG_CFG was wrong and + * would have sent the next person looking for a charger in the rail + * block. + */ +/* + * The byte stock writes to 0x1A. What its bits mean is NOT established. + * This was annotated "3000 mV: 0xa0 | ((3000-1200)/100)", assuming a + * 100 mV step from a 0xa0 base; under the Dialog LDO shape in the DA9053 + * datasheet (bits 5:0 = voltage, 50 mV steps from 1.20 V, bit 6 = enable) + * the same byte reads as 3.70 V with the enable bit clear. We have no + * register map for the D1830 and the DA905x map is disproved for it, so + * neither reading is evidence. Written as an opaque unit. + * See docs-internal/n7g-audio/N31-PMIC-STATE-ANALYSIS.md. + */ +#define D1830_REG_LDO4 0x1a /* 26 -- LDO4, not "LDO 1A" */ +#define D1830_LDO4_STOCK 0xb2 +/* + * Dialog D18x0 register names. Validated against the firmware's own + * behaviour, not assumed from a similar part -- see + * docs-internal/n7g-audio/N31-PMIC-STATE-ANALYSIS.md section 6.1. The + * decisive checks were that reg 0x60 is read as a 4-byte magic ('Sctr') and + * that regs 0x01-0x04 are copied into 0x7C-0x7F across hibernate, both of + * which only make sense if 0x60.. is the persistent scratch block and + * 0x01-0x04 are the EVENT registers. + * + * Do NOT substitute the DA9052/DA9053 map here. It collides on addresses + * (it defines nearly every address 1..127) but contradicts the firmware on + * four separate points. + */ +#define D1830_REG_EVENT_A 0x01 /* 1 EVENT_A..EVENT_D = 0x01..0x04 */ +#define D1830_REG_SYS_CONTROL 0x0d /* 13 bit0 STANDBY, bit1 HIBERNATE */ +#define D1830_REG_ACTIVE_1 0x10 /* 16 ACTIVE1 -- rail enables */ +#define D1830_REG_ACTIVE_2 0x11 /* 17 ACTIVE2 -- rail enables */ +#define D1830_REG_HIBERNATE_1 0x13 /* 19 HIBERNATE1 */ +#define D1830_REG_BUCK1 0x14 /* 20 */ +#define D1830_REG_BUCK2 0x15 /* 21 */ +#define D1830_REG_SPECIAL 0x16 /* 22 */ +#define D1830_REG_LDO1 0x17 /* 23 */ +#define D1830_REG_BUCK_CONTROL 0x23 /* 35 */ +#define D1830_REG_MEMBYTE0 0x60 /* 96 persistent scratch, runs to 0x87 */ + +/* SYS_CONTROL bits */ +#define D1830_SYS_STANDBY BIT(0) +#define D1830_SYS_HIBERNATE BIT(1) +#define D1830_SYS_BAT_PWR_SUSPEND BIT(2) +#define D1830_SYS_SWI_EN BIT(4) +#define D1830_SYS_PRO_FET_DIS BIT(5) +#define D1830_SYS_BUS_PWR_SUSPEND BIT(6) + +/* The scratch bytes the bootloader saves EVENT_A..EVENT_D into. */ +#define D1830_REG_SAVED_EVENT_A 0x7c +#define D1830_REG_SAVED_EVENT_D 0x7f + +/* + * The charger-configuration half of sub_23EC, on its own: 0x1a takes + * 0xb2, written twice, exactly as both the bootloader and RetailOS do. + * The second write is not a different value -- the firmware reuses the + * same stack byte -- so this reproduces it rather than tidying it away. + * + * Safe to run, and enabled by default: this device already reads 0xb2 + * at 0x1a because the bootloader put it there, so this confirms the + * state rather than changing it, and it makes the driver correct on a + * path where the bootloader did not. + * + * What the individual bits of 0xb2 mean is not decoded. There is no + * bitwise read-modify-write of this register anywhere in the extracted + * firmware to give any bit an independent meaning, so the whole byte is + * written as a unit and no D1830_CHARGE_ENABLE is invented from it. + */ +static int d1830_charger_config(struct i2c_client *client) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, D1830_REG_LDO4, + D1830_LDO4_STOCK); + if (ret) + return ret; + return i2c_smbus_write_byte_data(client, D1830_REG_LDO4, + D1830_LDO4_STOCK); +} + +static int d1830_charger_hw_init(struct i2c_client *client, bool cold_boot) +{ + int v, ret; + + /* 0x14..0x17 are deliberately not written: the trim values come from + * board tables we have not recovered, and a wrong trim is worse than + * whatever the bootloader already left there. */ + + ret = d1830_charger_config(client); + if (ret) + return ret; + + v = i2c_smbus_read_byte_data(client, D1830_REG_ACTIVE_1); + if (v < 0) + return v; + v &= ~0xd0; + v |= 0x10; + if (cold_boot) + v |= 0x20; + ret = i2c_smbus_write_byte_data(client, D1830_REG_ACTIVE_1, (u8)v); + if (ret) + return ret; + + v = i2c_smbus_read_byte_data(client, D1830_REG_ACTIVE_2); + if (v < 0) + return v; + ret = i2c_smbus_write_byte_data(client, D1830_REG_ACTIVE_2, + (u8)(v | 0x07)); + if (ret) + return ret; + + v = i2c_smbus_read_byte_data(client, D1830_REG_HIBERNATE_1); + if (v < 0) + return v; + return i2c_smbus_write_byte_data(client, D1830_REG_HIBERNATE_1, + (u8)(v | 0x02)); +} + +/* + * Deliberately absent: d1830_charger_set_enabled(). + * + * 0x1a = 0xb2 is proven as configuration written during init, twice, by + * both the bootloader and RetailOS. It is not proven to be the bit that + * starts and stops charging at runtime, and nothing observed so far shows + * RetailOS toggling it dynamically. Writing a guessed enable would be the + * one mistake in this driver that damages hardware rather than annoying + * the user, so the dynamic enable stays unimplemented until the bit is + * identified. + */ + +/* + * The policy, written now so that identifying VBUS is the only remaining + * step rather than the start of a design. Note what it does not do: it + * never keys off USB enumeration. VBUS present, host detected and gadget + * configured are three different things -- a wall charger gives VBUS with + * no enumeration at all -- so gating charge on enumeration would refuse to + * charge from exactly the source most likely to be attached. + */ +static void d1830_charger_update(struct i2c_client *client) +{ + struct d1830_gpio *gpio_dev = i2c_get_clientdata(client); + bool present = false; + int mv = 0; + + if (d1830_vbus_present(client, &present)) { + d1830_chg.vbus_known = false; + d1830_chg.input = D1830_INPUT_NONE; + d1830_chg.charging = false; + d1830_chg.full = false; + } else { + d1830_chg.vbus_known = true; + d1830_chg.vbus_present = present; + /* + * UNKNOWN_VBUS, not USB. 0x05 bit 6 says an external source + * is available; it does not say what kind. Classifying the + * attachment is TriStar's job and is not wired up yet. + */ + d1830_chg.input = present ? D1830_INPUT_UNKNOWN_VBUS : + D1830_INPUT_NONE; + /* + * Charging stays false even with external power. Present and + * delivering current are different claims, and no bit + * separating input-present from charger-active, CV phase or + * complete has been isolated. Reporting CHARGING here would be + * the same category of error as the old voltage heuristic. + */ + d1830_chg.charging = false; + d1830_chg.full = false; + } + + if (gpio_dev && !d1830_read_vbat(gpio_dev, &mv)) + d1830_chg.vbat_mv = mv; +} + +static bool charger_enable; +module_param(charger_enable, bool, 0444); +MODULE_PARM_DESC(charger_enable, + "Register the charger class device (properties still unproven)"); + +/* +* Fill these in as they are established, one line per proven register, +* with the evidence in the comment. An entry that is still zero means +* nobody has proven it, and the property stays -ENODATA. +*/ +struct d1830_charger_reg { + const char *what; + u8 reg; /* 0 = not identified */ + u8 mask; +}; + +static const struct d1830_charger_reg d1830_charger_regs[] = { + { "vbus_present", 0, 0 }, + { "charge_enable", 0, 0 }, + { "input_current_limit", 0, 0 }, + { "charge_current", 0, 0 }, + { "charge_voltage", 0, 0 }, + { "term_current", 0, 0 }, + { "charge_status", 0, 0 }, + { "fault", 0, 0 }, +}; + +static int d1830_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + return 0; + case POWER_SUPPLY_PROP_ONLINE: + if (!d1830_chg.vbus_known) + return -ENODATA; + val->intval = d1830_chg.vbus_present ? 1 : 0; + return 0; + case POWER_SUPPLY_PROP_STATUS: + if (!d1830_chg.vbus_known) + return -ENODATA; + /* + * NOT_CHARGING rather than CHARGING when external power is + * present. charging and full are only ever set from a proven + * status bit, and none is identified, so this reports the one + * thing that is known: a source is or is not attached. + */ + if (!d1830_chg.vbus_present) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (d1830_chg.full) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (d1830_chg.charging) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + /* Register not identified. Saying so beats a number. */ + return -ENODATA; + default: + return -EINVAL; + } +} + +static enum power_supply_property d1830_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_SCOPE, +}; + +static const struct power_supply_desc d1830_charger_desc = { + .name = "d1830-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = d1830_charger_props, + .num_properties = ARRAY_SIZE(d1830_charger_props), + .get_property = d1830_charger_get_property, +}; + +static void d1830_charger_register(struct device *dev) +{ + struct power_supply_config cfg = { }; + struct power_supply *psy; + unsigned int i, known = 0; + + if (!charger_enable) + return; + + for (i = 0; i < ARRAY_SIZE(d1830_charger_regs); i++) + if (d1830_charger_regs[i].reg) + known++; + + psy = devm_power_supply_register(dev, &d1830_charger_desc, &cfg); + if (IS_ERR(psy)) { + dev_warn(dev, "charger scaffold not registered: %ld\n", + PTR_ERR(psy)); + return; + } + dev_info(dev, + "charger scaffold registered: %u/%zu registers proven, all properties -ENODATA\n", + known, ARRAY_SIZE(d1830_charger_regs)); +} + +static int d1830_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + /* + * Was hardcoded to 1. That was a bring-up shortcut and it + * cannot be the charging policy: a supply that claims to be + * online whether or not a cable is attached will report + * charging while the battery drains. No VBUS source is + * identified, so -ENODATA is the true answer until + * d1830_vbus_present() can give one. + */ + if (!d1830_chg.vbus_known) + return -ENODATA; + val->intval = d1830_chg.vbus_present ? 1 : 0; + return 0; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = POWER_SUPPLY_USB_TYPE_SDP; + return 0; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + return 0; + default: + return -EINVAL; + } +} + +static enum power_supply_property d1830_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_SCOPE, +}; + +static int d1830_write8(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + + d1830_vinfo(&client->dev, "n31-pmic: WR %02x <- %02x%s\n", + reg, val, dump_only ? " (suppressed)" : ""); + if (dump_only) + return 0; + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret) + dev_err(&client->dev, "n31-pmic: WR %02x failed ret=%d\n", + reg, ret); + return ret; +} + +static int d1830_rmw(struct i2c_client *client, u8 reg, u8 clear, u8 set) +{ + int v = i2c_smbus_read_byte_data(client, reg); + u8 newv; + + if (v < 0) + return v; + newv = (u8)((v & ~clear) | set); + d1830_vinfo(&client->dev, + "n31-pmic: RMW reg=%02x old=%02x clear=%02x set=%02x new=%02x%s\n", + reg, v, clear, set, newv, dump_only ? " (suppressed)" : ""); + if (dump_only) + return 0; + return i2c_smbus_write_byte_data(client, reg, newv); +} + +/* ------------------------------------------------------------------ */ +/* N31 PMU register / rail decode */ +/* */ +/* Read-only by default. The point is to make the boot rail model */ +/* visible so audio and display power can be debugged from evidence */ +/* rather than by poking registers and watching what breaks. Names are */ +/* driver-local: they describe what the boot sequence does with each */ +/* register, and carry no claim about which peripheral owns a rail */ +/* until a snapshot diff proves it. */ +/* ------------------------------------------------------------------ */ + +#define N31_PMU_VARIANT "n31_d1830_pmu_v1" +#define N31_PMU_REG_MAX 0x88 /* dump 0x00..0x87 */ +#define N31_PMU_VSEL_MASK 0x1f +#define N31_PMU_NO_VSEL 0xff + +static char *pmu_variant = N31_PMU_VARIANT; +module_param(pmu_variant, charp, 0444); +MODULE_PARM_DESC(pmu_variant, "PMU register-map variant this driver decodes"); + +static bool allow_pmu_writes; +module_param(allow_pmu_writes, bool, 0644); +MODULE_PARM_DESC(allow_pmu_writes, + "Permit PMU rail writes at all (default N — decode only)"); + +static bool apply_boot_rails; +module_param(apply_boot_rails, bool, 0644); +MODULE_PARM_DESC(apply_boot_rails, + "Replay the boot rail sequence exactly (needs allow_pmu_writes)"); + +static bool audio_rail_test; +module_param(audio_rail_test, bool, 0644); +MODULE_PARM_DESC(audio_rail_test, + "Arm the analog rail experiment (needs allow_pmu_writes)"); + +static bool restore_after_test = true; +module_param(restore_after_test, bool, 0644); +MODULE_PARM_DESC(restore_after_test, + "Restore the pre-test register values afterwards (default Y)"); + +struct n31_pmu_regname { + u8 reg; + const char *name; +}; + +static const struct n31_pmu_regname n31_pmu_names[] = { + { 0x00, "PMU_CHIP_ID" }, + { 0x01, "PMU_EVENT_A" }, + { 0x02, "PMU_EVENT_B" }, + { 0x03, "PMU_EVENT_C" }, + { 0x04, "PMU_EVENT_D" }, + { 0x05, "PMU_STATUS_A" }, + { 0x06, "PMU_STATUS_B" }, + { 0x07, "PMU_STATUS_C" }, + { 0x08, "PMU_STATUS_D" }, + { 0x09, "PMU_IRQ_MASK_A" }, + { 0x0a, "PMU_IRQ_MASK_B" }, + { 0x0b, "PMU_IRQ_MASK_C" }, + { 0x0c, "PMU_IRQ_MASK_D" }, + { 0x0d, "PMU_SYS_CONTROL" }, + { 0x0e, "PMU_FAULT_LOG" }, + { 0x10, "PMU_ACTIVE_1" }, + { 0x11, "PMU_ACTIVE_2" }, + { 0x12, "PMU_STANDBY_1" }, + { 0x13, "PMU_HIBERNATE_1" }, + { 0x14, "PMU_BUCK_1_CFG" }, + { 0x15, "PMU_BUCK_2_CFG" }, + { 0x16, "PMU_SPECIAL_CFG" }, + { 0x17, "PMU_LDO_1_CFG" }, + { 0x18, "PMU_LDO_2_CFG" }, + { 0x19, "PMU_LDO_3_CFG" }, + { 0x1a, "PMU_LDO_4_CFG" }, + { 0x1b, "PMU_LDO_5_CFG" }, + { 0x1c, "PMU_LDO_6_CFG" }, + { 0x1d, "PMU_LDO_7_CFG" }, + { 0x1e, "PMU_LDO_8_CFG" }, + { 0x1f, "PMU_LDO_9_CFG" }, + { 0x20, "PMU_LDO_10_CFG" }, + { 0x21, "PMU_LDO_11_CFG" }, + { 0x22, "PMU_LDO_CONTROL" }, + { 0x23, "PMU_BUCK_CONTROL" }, + { 0x24, "PMU_BUCK_CONTROL_2" }, + { 0x25, "PMU_WLED_ISET" }, + { 0x26, "PMU_WLED_CONTROL" }, + { 0x27, "PMU_CHARGE_BUCK_CONTROL" }, + { 0x28, "PMU_CHARGE_CONTROL_A" }, + { 0x29, "PMU_CHARGE_CONTROL_B" }, + { 0x2a, "PMU_CHARGE_TIME" }, + { 0x2b, "PMU_CHARGE_MISC" }, + { 0x30, "PMU_ADC_CONTROL" }, + { 0x31, "PMU_ADC_LSB" }, + { 0x32, "PMU_ADC_MSB" }, + { 0x35, "PMU_ICHG_AVG" }, + { 0x3c, "PMU_MISC_ENABLE" }, + { 0x5f, "PMU_SYS_CONFIG" }, + { 0x6e, "PMU_N31_STATE" }, + { 0x6f, "PMU_N31_CAL_INPUT" }, +}; + +/* Named ranges; 0x6E/0x6F are looked up before the MEMBYTE range wins. */ +static const char *n31_pmu_reg_name(u8 reg, char *buf, size_t len) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(n31_pmu_names); i++) + if (n31_pmu_names[i].reg == reg) + return n31_pmu_names[i].name; + + if (reg >= 0x40 && reg <= 0x4f) + scnprintf(buf, len, "PMU_RTC_OR_UPCOUNT_%u", reg - 0x40); + else if (reg >= 0x50 && reg <= 0x57) + scnprintf(buf, len, "PMU_GPIO_%u", reg - 0x50 + 1); + else if (reg >= 0x59 && reg <= 0x5b) + scnprintf(buf, len, "PMU_GPIO_DEBOUNCE_%u", reg - 0x59 + 1); + else if (reg >= 0x5c && reg <= 0x5e) + scnprintf(buf, len, "PMU_BUTTON_%u", reg - 0x5c + 1); + else if (reg >= 0x60 && reg <= 0x87) + scnprintf(buf, len, "PMU_MEMBYTE_%u", reg - 0x60); + else + scnprintf(buf, len, "PMU_RESERVED_%02X", reg); + return buf; +} + +/* + * Rail decode. `active` names the register whose `mask` bit gates the rail; + * `vsel` holds the 5-bit voltage code. Ownership is deliberately unassigned: + * which peripheral each rail feeds should come out of a snapshot diff. + */ +struct n31_pmu_rail { + const char *name; + u8 vsel; + u8 active_reg; + u8 active_mask; + u16 base_mv; + u16 step_mv; +}; + +static const struct n31_pmu_rail n31_pmu_rails[] = { + /* + * base/step come from sub_2D404, the stock rail-voltage setter, which + * maps a rail index 1..14 onto registers 20..33 and picks a base and + * a step per rail: + * + * idx 1-4 regs 20-23 BUCK1,BUCK2,SPECIAL,LDO1 725 + 25*n + * idx 5 reg 24 LDO2 1200 + 50*n (4-bit) + * idx 6 reg 25 LDO3 2000 + 50*n + * idx 7 reg 26 LDO4 1200 + 100*n, | 0xA0 + * idx 8,9 regs 27,28 LDO5,LDO6 1200 + 100*n + * idx 10 reg 29 LDO7 1200 + 100*n, keeps bits 7:5 + * idx 11 reg 30 LDO8 2500 + 50*n, keeps bits 7:5 + * idx 12,13 regs 31,32 LDO9,LDO10 2000 + 50*n + * idx 14 reg 33 LDO11 1650 + 5*n + * + * Every entry here was previously wrong, so every voltage this driver + * reported was wrong. Two independent cross-checks pin the encodings: + * LDO4 decodes 0xB2 -- the byte sub_23EC writes -- to exactly 3000 mV, + * and LDO8 decodes to exactly the 3300 mV that sub_2A120(6, 3300) sets. + * + * This was a *reporting* bug only. The trim writes stock's literal + * bytes, so the hardware always received stock's voltages. + * + * ACTIVE1/ACTIVE2 bit assignment comes from sub_7484, the stock rail + * enable, which takes the SAME rail index as sub_2D404 above: + * + * idx 0-4 ACTIVE1 bit 0 BUCK1,BUCK2,SPECIAL,LDO1 ganged + * idx 5-11 ACTIVE1 bits 1-7 LDO2..LDO8, one bit each + * idx 12-15 ACTIVE2 bits 0-3 LDO9..LDO12, one bit each + * idx 16-18 regs 40,46,47 bit 5 + * + * That the two functions share an index space is not an assumption: + * sub_2A120 maps a consumer id onto idx = id + 5 and hands it to + * sub_2D404, and sub_6644 maps the same consumer id onto the same + * idx = id + 5 and hands it to sub_7484. sub_2A120 also feeds that idx + * to sub_2D30E, whose idx 10 -> reg 29 and idx 11 -> reg 30 match + * sub_2D404's cases 10 and 11 exactly. + * + * The mapping here was previously an irregular guess (LDO9 on ACTIVE2 + * bit 7, LDO6 on ACTIVE2 bit 0) that had every rail on the wrong bit. + * With the real mapping, the boot state ACTIVE1=0x7f ACTIVE2=0x17 reads + * as LDO1..LDO7 and LDO9..LDO11 on, LDO8 and LDO12 off -- not the + * "LDO5/LDO9/LDO11 off" the old table reported. + * + * Enable polarity: a SET bit in ACTIVE1/ACTIVE2 means the rail is ON -- + * sub_7484 does `v & ~mask` then `| (mask * (on != 0))`. Do NOT confuse + * this with the per-rail gate in sub_2D30E (LDO7/LDO8 only), where + * config-register bit 5 SET means OFF. The two are opposite. + * + * See docs-internal/n7g-audio/N31-POWER-RAILS.md. + */ + { "PMU_LDO_1", 0x17, 0x10, 0x01, 725, 25 }, + { "PMU_LDO_2", 0x18, 0x10, 0x02, 1200, 50 }, + { "PMU_LDO_3", 0x19, 0x10, 0x04, 2000, 50 }, + { "PMU_LDO_4", 0x1a, 0x10, 0x08, 1200, 100 }, + { "PMU_LDO_5", 0x1b, 0x10, 0x10, 1200, 100 }, + { "PMU_LDO_6", 0x1c, 0x10, 0x20, 1200, 100 }, + { "PMU_LDO_7", 0x1d, 0x10, 0x40, 1200, 100 }, + { "PMU_LDO_8", 0x1e, 0x10, 0x80, 2500, 50 }, + { "PMU_LDO_9", 0x1f, 0x11, 0x01, 2000, 50 }, + { "PMU_LDO_10", 0x20, 0x11, 0x02, 2000, 50 }, + { "PMU_LDO_11", 0x21, 0x11, 0x04, 1650, 5 }, + /* + * sub_2D404 case 15: reg 34 (0x22), 1700 + 50*n. This rail was missing + * from the table entirely, and the slot it now occupies used to hold a + * "PMU_WDIG" entry with no vsel that no decomp evidence supports. + */ + { "PMU_LDO_12", 0x22, 0x11, 0x08, 1700, 50 }, +}; + +/* Registers worth watching across an audio or display state change. */ +static const u8 n31_pmu_watch[] = { + 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, + 0x23, 0x24, 0x25, 0x26, 0x29, 0x2a, 0x2b, 0x30, 0x3c, +}; + +/* + * Registers the PMU updates on its own: measurement results, counters and + * latched status. They differ between any two reads, so a diff marks them + * instead of letting them bury a real rail change. + */ +static bool n31_pmu_reg_is_live(u8 reg) +{ + if (reg >= 0x01 && reg <= 0x08) /* events and status */ + return true; + if (reg >= 0x31 && reg <= 0x35) /* ADC result, charge current */ + return true; + if (reg >= 0x3d && reg <= 0x3f) /* observed ticking with the ADC */ + return true; + if (reg >= 0x40 && reg <= 0x4f) /* RTC / up-counter */ + return true; + return false; +} + +static u8 n31_pmu_snap[N31_PMU_REG_MAX]; +static bool n31_pmu_snap_valid; +static struct dentry *n31_pmu_debugfs; + +static int n31_pmu_read(u8 reg) +{ + struct i2c_client *client = d1830_poweroff_client; + + if (!client) + return -ENODEV; + return i2c_smbus_read_byte_data(client, reg); +} + +static void n31_pmu_read_all(u8 *out, bool *ok) +{ + unsigned int r; + + for (r = 0; r < N31_PMU_REG_MAX; r++) { + int v = n31_pmu_read((u8)r); + + ok[r] = v >= 0; + out[r] = ok[r] ? (u8)v : 0; + } +} + +static int n31_pmu_variant_show(struct seq_file *s, void *unused) +{ + seq_printf(s, "%s\n", pmu_variant ? pmu_variant : N31_PMU_VARIANT); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_variant); + +static int n31_pmu_regs_raw_show(struct seq_file *s, void *unused) +{ + unsigned int r; + + for (r = 0; r < N31_PMU_REG_MAX; r += 16) { + unsigned int i; + + seq_printf(s, "%02x:", r); + for (i = 0; i < 16 && r + i < N31_PMU_REG_MAX; i++) { + int v = n31_pmu_read((u8)(r + i)); + + if (v < 0) + seq_puts(s, " --"); + else + seq_printf(s, " %02x", v); + } + seq_puts(s, "\n"); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_regs_raw); + +static int n31_pmu_regs_named_show(struct seq_file *s, void *unused) +{ + char buf[32]; + unsigned int r; + + for (r = 0; r < N31_PMU_REG_MAX; r++) { + int v = n31_pmu_read((u8)r); + + if (v < 0) + continue; + seq_printf(s, "0x%02x %-24s 0x%02x\n", r, + n31_pmu_reg_name((u8)r, buf, sizeof(buf)), v); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_regs_named); + +static int n31_pmu_rails_show(struct seq_file *s, void *unused) +{ + unsigned int i; + + seq_puts(s, "rail vsel active raw on mV\n"); + for (i = 0; i < ARRAY_SIZE(n31_pmu_rails); i++) { + const struct n31_pmu_rail *ra = &n31_pmu_rails[i]; + int act = n31_pmu_read(ra->active_reg); + int cfg = ra->vsel == N31_PMU_NO_VSEL ? -1 : + n31_pmu_read(ra->vsel); + bool on = act >= 0 && (act & ra->active_mask); + + seq_printf(s, "%-11s ", ra->name); + if (ra->vsel == N31_PMU_NO_VSEL) + seq_puts(s, "---- "); + else + seq_printf(s, "0x%02x ", ra->vsel); + seq_printf(s, "0x%02x/0x%02x ", ra->active_reg, ra->active_mask); + if (cfg < 0) + seq_puts(s, " -- "); + else + seq_printf(s, "0x%02x ", cfg); + seq_printf(s, "%-3s ", act < 0 ? "?" : (on ? "yes" : "no")); + if (cfg >= 0 && ra->step_mv) + seq_printf(s, "%u", + ra->base_mv + + (cfg & N31_PMU_VSEL_MASK) * ra->step_mv); + else + seq_puts(s, "-"); + seq_puts(s, "\n"); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_rails); + +/* + * The boot rail sequence in this driver's register names. Blind writes and + * read-modify-writes are shown as they appear, because reproducing one as the + * other would change what the hardware ends up with. + */ +static int n31_pmu_bootseq_decode_show(struct seq_file *s, void *unused) +{ + seq_puts(s, "sub_23EC — rail configuration and active state\n"); + seq_puts(s, " PMU_BUCK_CONTROL [0x23] &= 0xFC\n"); + seq_puts(s, " PMU_BUCK_1_CFG [0x14] = computed 5-bit A\n"); + seq_puts(s, " PMU_BUCK_2_CFG [0x15] = computed 5-bit B\n"); + seq_puts(s, " PMU_SPECIAL_CFG [0x16] = computed 5-bit B\n"); + seq_puts(s, " PMU_LDO_1_CFG [0x17] = computed 5-bit B\n"); + seq_puts(s, " PMU_LDO_4_CFG [0x1A] = 0xB2 (written twice)\n"); + seq_puts(s, " PMU_ACTIVE_1 [0x10] = (old & 0x2F) | 0x10\n"); + seq_puts(s, " cold boot also | 0x20\n"); + seq_puts(s, " PMU_ACTIVE_2 [0x11] |= 0x07\n"); + seq_puts(s, " PMU_HIBERNATE_1 [0x13] |= 0x02\n"); + seq_puts(s, "\n"); + seq_puts(s, "sub_27F4 — board power initialisation tail\n"); + seq_puts(s, " PMU_SYS_CONTROL [0x0D] &= 0x8F\n"); + seq_puts(s, " (apply sub_23EC)\n"); + seq_puts(s, " PMU_ADC_CONTROL [0x30] |= 0x40\n"); + seq_puts(s, " PMU_GPIO_DEBOUNCE_1[0x59] &= 0xE3\n"); + seq_puts(s, " PMU_MISC_ENABLE [0x3C] = 0x01\n"); + seq_puts(s, " PMU_CHARGE_CONTROL_B[0x29] = (old & 0xEC) | 0x10\n"); + seq_puts(s, " PMU_CHARGE_TIME [0x2A] = (old & 0xC0) | 0x14\n"); + seq_puts(s, " PMU_CHARGE_MISC [0x2B] = (old & 0xF0) | 0x01\n"); + seq_puts(s, " PMU_FAULT_LOG [0x0E] = 0x20\n"); + seq_puts(s, " PMU_BUCK_CONTROL_2 [0x24] = f(cal) >> 1\n"); + seq_puts(s, " PMU_WLED_ISET [0x25] = 4 * (f(cal) & 1)\n"); + seq_puts(s, " PMU_WLED_CONTROL [0x26] &= ~0x01\n"); + seq_puts(s, " PMU_SYS_CONTROL [0x0D] &= 0xF3\n"); + seq_puts(s, "\n"); + seq_puts(s, "sub_1E7C — low-power boot finish\n"); + seq_puts(s, " PMU_CHARGE_CONTROL_B[0x29] = (old & 0xF8) | 0x06\n"); + seq_puts(s, " PMU_BUCK_CONTROL_2 [0x24] = f(0x28) >> 1\n"); + seq_puts(s, " PMU_WLED_ISET [0x25] = 4 * (f(0x28) & 1)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_bootseq_decode); + +/* Reading this captures the current register file; diff_last compares to it. */ +static int n31_pmu_snapshot_show(struct seq_file *s, void *unused) +{ + bool ok[N31_PMU_REG_MAX]; + char buf[32]; + unsigned int r; + + n31_pmu_read_all(n31_pmu_snap, ok); + n31_pmu_snap_valid = true; + + seq_puts(s, "captured; compare with diff_last\n"); + for (r = 0; r < N31_PMU_REG_MAX; r++) { + if (!ok[r]) + continue; + seq_printf(s, "0x%02x %-24s 0x%02x\n", r, + n31_pmu_reg_name((u8)r, buf, sizeof(buf)), + n31_pmu_snap[r]); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_snapshot); + +static int n31_pmu_diff_last_show(struct seq_file *s, void *unused) +{ + char buf[32]; + unsigned int r, n = 0; + + if (!n31_pmu_snap_valid) { + seq_puts(s, "no snapshot yet; read snapshot first\n"); + return 0; + } + for (r = 0; r < N31_PMU_REG_MAX; r++) { + int v = n31_pmu_read((u8)r); + u8 old, xor; + + if (v < 0) + continue; + old = n31_pmu_snap[r]; + if (old == (u8)v) + continue; + xor = old ^ (u8)v; + seq_printf(s, "0x%02x %-24s 0x%02x -> 0x%02x xor=0x%02x set=0x%02x cleared=0x%02x%s\n", + r, n31_pmu_reg_name((u8)r, buf, sizeof(buf)), + old, v, xor, (u8)(xor & v), (u8)(xor & old), + n31_pmu_reg_is_live((u8)r) ? " [live]" : ""); + if (!n31_pmu_reg_is_live((u8)r)) + n++; + } + if (!n) + seq_puts(s, + "no change since snapshot (ignoring [live] registers)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_diff_last); + +static void n31_pmu_show_watch(struct seq_file *s, const char *tag) +{ + char buf[32]; + unsigned int i; + + seq_printf(s, "=== %s ===\n", tag); + for (i = 0; i < ARRAY_SIZE(n31_pmu_watch); i++) { + u8 reg = n31_pmu_watch[i]; + int v = n31_pmu_read(reg); + + seq_printf(s, "0x%02x %-24s ", reg, + n31_pmu_reg_name(reg, buf, sizeof(buf))); + if (v < 0) + seq_puts(s, "--\n"); + else + seq_printf(s, "0x%02x\n", v); + } +} + +static int n31_pmu_audio_snapshot_show(struct seq_file *s, void *unused) +{ + n31_pmu_show_watch(s, "audio rail watch"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_audio_snapshot); + +static int n31_pmu_display_snapshot_show(struct seq_file *s, void *unused) +{ + n31_pmu_show_watch(s, "display rail watch"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_display_snapshot); + +/* + * Show what the boot sequence would write given the registers as they stand, + * without writing any of it. The read-modify-write steps are evaluated + * against live values so the result is what would actually land. + */ +static int n31_pmu_apply_bootseq_dryrun_show(struct seq_file *s, void *unused) +{ + int r10, r11, r13, r23, r0d, r29, r2a, r2b, r26, r30, r59; + + seq_printf(s, "allow_pmu_writes=%d apply_boot_rails=%d (dry run only)\n\n", + allow_pmu_writes, apply_boot_rails); + + r23 = n31_pmu_read(0x23); + r10 = n31_pmu_read(0x10); + r11 = n31_pmu_read(0x11); + r13 = n31_pmu_read(0x13); + r0d = n31_pmu_read(0x0d); + r29 = n31_pmu_read(0x29); + r2a = n31_pmu_read(0x2a); + r2b = n31_pmu_read(0x2b); + r26 = n31_pmu_read(0x26); + r30 = n31_pmu_read(0x30); + r59 = n31_pmu_read(0x59); + if (r23 < 0 || r10 < 0 || r11 < 0 || r13 < 0) { + seq_puts(s, "PMU read failed\n"); + return 0; + } + + seq_puts(s, "sub_23EC:\n"); + seq_printf(s, " PMU_BUCK_CONTROL 0x%02x -> 0x%02x\n", + r23, r23 & 0xfc); + seq_printf(s, " PMU_LDO_4_CFG ---- -> 0xb2 (blind, twice)\n"); + seq_printf(s, " PMU_ACTIVE_1 0x%02x -> 0x%02x (cold: 0x%02x)\n", + r10, (r10 & 0x2f) | 0x10, (r10 & 0x2f) | 0x30); + seq_printf(s, " PMU_ACTIVE_2 0x%02x -> 0x%02x\n", + r11, r11 | 0x07); + seq_printf(s, " PMU_HIBERNATE_1 0x%02x -> 0x%02x\n", + r13, r13 | 0x02); + + seq_puts(s, "sub_27F4 tail:\n"); + if (r0d >= 0) + seq_printf(s, " PMU_SYS_CONTROL 0x%02x -> 0x%02x then 0x%02x\n", + r0d, r0d & 0x8f, (r0d & 0x8f) & 0xf3); + if (r30 >= 0) + seq_printf(s, " PMU_ADC_CONTROL 0x%02x -> 0x%02x\n", + r30, r30 | 0x40); + if (r59 >= 0) + seq_printf(s, " PMU_GPIO_DEBOUNCE_1 0x%02x -> 0x%02x\n", + r59, r59 & 0xe3); + seq_puts(s, " PMU_MISC_ENABLE ---- -> 0x01 (blind)\n"); + if (r29 >= 0) + seq_printf(s, " PMU_CHARGE_CONTROL_B 0x%02x -> 0x%02x\n", + r29, (r29 & 0xec) | 0x10); + if (r2a >= 0) + seq_printf(s, " PMU_CHARGE_TIME 0x%02x -> 0x%02x\n", + r2a, (r2a & 0xc0) | 0x14); + if (r2b >= 0) + seq_printf(s, " PMU_CHARGE_MISC 0x%02x -> 0x%02x\n", + r2b, (r2b & 0xf0) | 0x01); + seq_puts(s, " PMU_FAULT_LOG ---- -> 0x20 (blind)\n"); + if (r26 >= 0) + seq_printf(s, " PMU_WLED_CONTROL 0x%02x -> 0x%02x\n", + r26, r26 & ~0x01); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(n31_pmu_apply_bootseq_dryrun); + + +/* ------------------------------------------------------------------ */ +/* Consumer rail control */ +/* */ +/* A rail is enabled while at least one consumer holds it and is turned */ +/* off again once the last reference has been gone for rail_off_delay_ms.*/ +/* The delay matters for the analog rail: track changes drop and retake */ +/* it within a second, and cycling it each time both wastes power and */ +/* risks an audible pop. */ +/* */ +/* Only the rail's own enable bit in PMU_ACTIVE_* is touched, never the */ +/* whole register. */ +/* ------------------------------------------------------------------ */ + +static bool rail_control = true; +module_param(rail_control, bool, 0644); +MODULE_PARM_DESC(rail_control, + "Let drivers enable/disable their rail via n31_pmu_rail_get/put"); + +static unsigned int rail_off_delay_ms = 5000; +module_param(rail_off_delay_ms, uint, 0644); +MODULE_PARM_DESC(rail_off_delay_ms, + "Idle time before a released rail is powered down (ms)"); + +struct n31_pmu_rail_state { + int users; + bool on; + struct delayed_work off_work; +}; + +static struct n31_pmu_rail_state n31_pmu_rail_state[ARRAY_SIZE(n31_pmu_rails)]; +static DEFINE_MUTEX(n31_pmu_rail_lock); + +/* Enable bits in `active_reg` belonging to rails a consumer still holds. */ +static u8 n31_pmu_rail_held_mask(u8 active_reg) +{ + unsigned int i; + u8 mask = 0; + + mutex_lock(&n31_pmu_rail_lock); + for (i = 0; i < ARRAY_SIZE(n31_pmu_rails); i++) + if (n31_pmu_rails[i].active_reg == active_reg && + n31_pmu_rail_state[i].users) + mask |= n31_pmu_rails[i].active_mask; + mutex_unlock(&n31_pmu_rail_lock); + return mask; +} + +static int n31_pmu_rail_apply(unsigned int id, bool on) +{ + const struct n31_pmu_rail *ra = &n31_pmu_rails[id]; + struct i2c_client *client = d1830_poweroff_client; + int ret; + + if (!client) + return -ENODEV; + if (!rail_control) + return -EPERM; + + ret = d1830_rmw(client, ra->active_reg, + on ? 0 : ra->active_mask, + on ? ra->active_mask : 0); + if (ret) { + dev_warn(&client->dev, "%s %s failed: %d\n", + ra->name, on ? "enable" : "disable", ret); + return ret; + } + d1830_vinfo(&client->dev, "%s %s\n", ra->name, on ? "on" : "off"); + return 0; +} + +static void n31_pmu_rail_off_work(struct work_struct *work) +{ + struct n31_pmu_rail_state *st = container_of(to_delayed_work(work), + struct n31_pmu_rail_state, + off_work); + unsigned int id = st - n31_pmu_rail_state; + + mutex_lock(&n31_pmu_rail_lock); + if (!st->users && st->on && !n31_pmu_rail_apply(id, false)) + st->on = false; + mutex_unlock(&n31_pmu_rail_lock); +} + +/* Enable a rail and hold it. Balanced by n31_pmu_rail_put(). */ +int n31_pmu_rail_get(unsigned int id) +{ + struct n31_pmu_rail_state *st; + int ret = 0; + + if (id >= ARRAY_SIZE(n31_pmu_rails)) + return -EINVAL; + st = &n31_pmu_rail_state[id]; + + mutex_lock(&n31_pmu_rail_lock); + cancel_delayed_work(&st->off_work); + if (!st->on) { + ret = n31_pmu_rail_apply(id, true); + if (!ret) + st->on = true; + } + if (!ret) + st->users++; + mutex_unlock(&n31_pmu_rail_lock); + return ret; +} +EXPORT_SYMBOL_GPL(n31_pmu_rail_get); + +/* Drop a reference; the rail powers down once idle for rail_off_delay_ms. */ +void n31_pmu_rail_put(unsigned int id) +{ + struct n31_pmu_rail_state *st; + + if (id >= ARRAY_SIZE(n31_pmu_rails)) + return; + st = &n31_pmu_rail_state[id]; + + mutex_lock(&n31_pmu_rail_lock); + if (st->users) + st->users--; + if (!st->users && st->on) + schedule_delayed_work(&st->off_work, + msecs_to_jiffies(rail_off_delay_ms)); + mutex_unlock(&n31_pmu_rail_lock); +} +EXPORT_SYMBOL_GPL(n31_pmu_rail_put); + +static void n31_pmu_rail_init(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(n31_pmu_rail_state); i++) + INIT_DELAYED_WORK(&n31_pmu_rail_state[i].off_work, + n31_pmu_rail_off_work); +} + +static void n31_pmu_rail_exit(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(n31_pmu_rail_state); i++) { + cancel_delayed_work_sync(&n31_pmu_rail_state[i].off_work); + /* + * Leave rail state as-is on unbind: the boot configuration is + * what the rest of the system expects to find. + */ + } +} + + +/* ------------------------------------------------------------------ */ +/* Screen sleep and power-button policy */ +/* */ +/* Short press toggles screen sleep: fade the backlight out and quiesce */ +/* touch. Holding the button past power_hold_ms asks the kernel to shut */ +/* down, which reaches the PMU through the machine pm_power_off this */ +/* driver already installs. */ +/* */ +/* The panel itself is deliberately left running. Nothing in this stack */ +/* can re-initialise it, so powering it down would be a one-way trip */ +/* until the panel init sequence is recovered. Backlight and touch are */ +/* where the current draw is anyway. */ +/* */ +/* Two tiers are selected from whether the analog audio rail is held: */ +/* with playback running a press only sleeps the screen, and the deeper */ +/* path is left for when nothing is playing. */ +/* ------------------------------------------------------------------ */ + +/* + * Off by default. Twice now a power press has left the panel stuck -- + * image still on screen, no fade, and no wake from a second press -- + * needing DFU to recover. The transition has never been driven + * deliberately, only caught by accident, so arming it on every boot + * risks the display on a device whose only recovery is a reflash. + * + * The mechanism is still there and still wired: set screen_sleep_enable=1 + * to test it on purpose, with the console reachable, rather than + * discovering it by pressing a button. + */ +static bool screen_sleep_enable; +module_param(screen_sleep_enable, bool, 0644); +MODULE_PARM_DESC(screen_sleep_enable, + "Short press toggles screen sleep (default Y)"); + +static unsigned int power_hold_ms = 4000; +module_param(power_hold_ms, uint, 0644); +MODULE_PARM_DESC(power_hold_ms, + "Hold the power button this long to power off (0 = never)"); + +static unsigned int backlight_fade_ms = 400; +module_param(backlight_fade_ms, uint, 0644); +MODULE_PARM_DESC(backlight_fade_ms, "Backlight ramp duration either way (ms)"); + +static bool n31_screen_asleep; +static int n31_screen_saved_level = -1; +static unsigned long n31_power_press_jiffies; +static bool n31_power_off_pending; +static DEFINE_MUTEX(n31_screen_lock); + +/* + * True while audio is playing. The codec has no PMU rail of its own, so + * this asks the codec directly rather than inferring it from rail state. + */ +static bool n31_audio_active(void) +{ + bool (*active)(void); + bool r = false; + + active = (bool (*)(void))__symbol_get("n31_audio_playback_active"); + if (active) { + r = active(); + __symbol_put("n31_audio_playback_active"); + } + return r; +} + +/* + * Sleeping the panel is only safe if we can also bring it back. These + * are resolved through __symbol_get, so a provider that is not loaded + * silently does half the job: the panel goes off with no fade, which + * looks like a lockup, or the backlight comes up over a panel that is + * still off, which looks like a device that will not wake. Neither is + * distinguishable from a crash at the time, so refuse to start a + * transition we cannot finish and say why. + */ +static void n31_screen_set(bool asleep) +{ + int (*fade)(int, unsigned int); + int (*level)(void); + int (*touch)(void); + int (*lcd)(bool); + + mutex_lock(&n31_screen_lock); + if (asleep == n31_screen_asleep) + goto out; + + fade = (int (*)(int, unsigned int))__symbol_get("n31_backlight_fade"); + level = (int (*)(void))__symbol_get("n31_backlight_level"); + lcd = (int (*)(bool))__symbol_get("n31_lcd_power"); + + if (asleep && !lcd && !fade) { + /* + * Nothing to dim and nothing to blank. Going "asleep" here + * would only set a flag that stops the next press waking + * anything, which is worse than staying awake. + */ + if (level) + __symbol_put("n31_backlight_level"); + pr_warn("n31: screen sleep skipped -- no backlight or LCD provider\n"); + goto out; + } + + if (asleep) { + if (level) { + n31_screen_saved_level = level(); + __symbol_put("n31_backlight_level"); + } + if (fade) { + fade(0, backlight_fade_ms); + __symbol_put("n31_backlight_fade"); + } + touch = (int (*)(void))__symbol_get("n31_touch_suspend"); + if (touch) { + touch(); + __symbol_put("n31_touch_suspend"); + } + /* Panel last, once nothing is drawing to it. */ + if (lcd) + lcd(false); + } else { + /* Panel first: it has to be scanning before the light comes up. */ + if (lcd) + lcd(true); + touch = (int (*)(void))__symbol_get("n31_touch_resume"); + if (touch) { + touch(); + __symbol_put("n31_touch_resume"); + } + if (level) + __symbol_put("n31_backlight_level"); + if (fade) { + fade(n31_screen_saved_level > 0 ? + n31_screen_saved_level : 1, + backlight_fade_ms); + __symbol_put("n31_backlight_fade"); + } + } + + if (lcd) + __symbol_put("n31_lcd_power"); + + n31_screen_asleep = asleep; + pr_info("n31: screen %s (audio %s, bl=%s lcd=%s)\n", + asleep ? "asleep" : "awake", + n31_audio_active() ? "active" : "idle", + fade ? "yes" : "no", lcd ? "yes" : "no"); +out: + mutex_unlock(&n31_screen_lock); +} + +/* + * The transition takes backlight_fade_ms either way and holds + * n31_screen_lock while it does. Called straight from the button path + * that runs it, that stalls every other button for the length of the + * fade -- so a press during it appears to do nothing, which reads as a + * hang rather than a fade. Push it to a workqueue and let the poller + * carry on. + */ +static bool n31_screen_want_asleep; + +static void n31_screen_work_fn(struct work_struct *work) +{ + n31_screen_set(READ_ONCE(n31_screen_want_asleep)); +} +static DECLARE_WORK(n31_screen_worker, n31_screen_work_fn); + +static void n31_screen_request(bool asleep) +{ + WRITE_ONCE(n31_screen_want_asleep, asleep); + schedule_work(&n31_screen_worker); +} + +bool n31_screen_is_asleep(void) +{ + return n31_screen_asleep; +} +EXPORT_SYMBOL_GPL(n31_screen_is_asleep); + +static void n31_power_off_work(struct work_struct *work) +{ + pr_warn("n31: power button held — shutting down\n"); + orderly_poweroff(true); +} +static DECLARE_WORK(n31_power_off_worker, n31_power_off_work); + +/* + * Called on every observed Sleep-button edge. Press only records when it + * started; the decision happens on release, or as soon as the hold passes + * power_hold_ms while still down. + */ +static void n31_power_button(bool pressed) +{ + unsigned long held_ms; + + if (pressed) { + n31_power_press_jiffies = jiffies; + n31_power_off_pending = false; + return; + } + + if (!n31_power_press_jiffies) + return; + held_ms = jiffies_to_msecs(jiffies - n31_power_press_jiffies); + n31_power_press_jiffies = 0; + + if (power_hold_ms && held_ms >= power_hold_ms) { + if (!n31_power_off_pending) { + n31_power_off_pending = true; + schedule_work(&n31_power_off_worker); + } + return; + } + + if (!screen_sleep_enable) + return; + + /* + * A press while asleep only wakes; it should not immediately put the + * screen back down. + */ + n31_screen_request(!n31_screen_asleep); +} + +/* Long holds must act while the button is still down, not on release. */ +static void n31_power_button_poll(void) +{ + unsigned long held_ms; + + if (!n31_power_press_jiffies || !power_hold_ms) + return; + if (n31_power_off_pending) + return; + held_ms = jiffies_to_msecs(jiffies - n31_power_press_jiffies); + if (held_ms < power_hold_ms) + return; + n31_power_off_pending = true; + schedule_work(&n31_power_off_worker); +} + +/* Home wakes the screen but is otherwise left to userspace. */ +static void n31_home_button(bool pressed) +{ + if (pressed && n31_screen_asleep) + n31_screen_request(false); +} + +/* ------------------------------------------------------------------ */ +/* Regulator provider */ +/* */ +/* The rails already have a refcounted in-kernel API, but nothing gave */ +/* userspace or other drivers the conventional view of them. Exposing */ +/* them as regulators means /sys/class/regulator carries the real names */ +/* and voltages, and a consumer can be wired up in DT like any other */ +/* board. */ +/* */ +/* Enable and disable route through n31_pmu_rail_get/put rather than */ +/* touching the ACTIVE register directly, so the regulator count and */ +/* the driver-internal holders cannot disagree about who wants a rail */ +/* on -- which is exactly the confusion that let the display rail be */ +/* dropped underneath a live panel. */ +/* ------------------------------------------------------------------ */ + +struct n31_pmu_reg { + struct regulator_desc desc; + char name[16]; + unsigned int id; +}; + +static struct n31_pmu_reg n31_pmu_regs[ARRAY_SIZE(n31_pmu_rails)]; + +static int n31_pmu_reg_enable(struct regulator_dev *rdev) +{ + unsigned int id = rdev_get_id(rdev); + + if (!allow_pmu_writes) + return -EPERM; + return n31_pmu_rail_get(id); +} + +static int n31_pmu_reg_disable(struct regulator_dev *rdev) +{ + unsigned int id = rdev_get_id(rdev); + + if (!allow_pmu_writes) + return -EPERM; + n31_pmu_rail_put(id); + return 0; +} + +/* + * Report what the hardware says rather than what this driver last asked + * for: the rail may have been brought up by the bootloader, and a rail + * that is already on is the normal case at probe. + */ +static int n31_pmu_reg_is_enabled(struct regulator_dev *rdev) +{ + unsigned int id = rdev_get_id(rdev); + const struct n31_pmu_rail *r; + struct i2c_client *client = d1830_poweroff_client; + int v; + + if (id >= ARRAY_SIZE(n31_pmu_rails) || !client) + return -ENODEV; + r = &n31_pmu_rails[id]; + v = i2c_smbus_read_byte_data(client, r->active_reg); + if (v < 0) + return v; + return !!(v & r->active_mask); +} + +static int n31_pmu_reg_get_voltage(struct regulator_dev *rdev) +{ + unsigned int id = rdev_get_id(rdev); + const struct n31_pmu_rail *r; + struct i2c_client *client = d1830_poweroff_client; + int v; + + if (id >= ARRAY_SIZE(n31_pmu_rails) || !client) + return -ENODEV; + r = &n31_pmu_rails[id]; + if (r->vsel == N31_PMU_NO_VSEL || !r->step_mv) + return -EINVAL; + v = i2c_smbus_read_byte_data(client, r->vsel); + if (v < 0) + return v; + /* Low bits select the step; the upper bits are trim/control. */ + return (r->base_mv + (v & 0x1f) * r->step_mv) * 1000; +} + +static const struct regulator_ops n31_pmu_reg_ops = { + .enable = n31_pmu_reg_enable, + .disable = n31_pmu_reg_disable, + .is_enabled = n31_pmu_reg_is_enabled, + .get_voltage = n31_pmu_reg_get_voltage, +}; + +#define D1830_BT_REG_A 0x57 /* 87 */ +#define D1830_BT_REG_B 0x58 /* 88 */ +#define D1830_BT_A_MASK 0xc0 /* bits 7:6 */ +#define D1830_BT_B_MASK 0x71 /* bit 0 and bits 6:4 */ + +/* Defined below; the regulator ops need it before its definition. */ +int d1830_bt_rails(bool on); + +/* + * The Bluetooth rail, as a regulator. + * + * It does not fit n31_pmu_rails[]: that table describes single-register + * LDOs at 0x17..0x21, and this one is two registers -- 0x57 bits 7:6 and + * 0x58 bit 0 plus bits 6:4. So it gets its own descriptor whose enable and + * disable defer to d1830_bt_rails(), which already knows the sequence. + * + * The point of exposing it at all is ordering. bcm2078-bt is built into + * the kernel and probes around t=2.4s; this driver is a module userspace + * loads at about t=7.1s. A driver asking for power through a bespoke hook + * in that window gets -ENODEV and has no way to wait, so the controller + * simply never came up. Asking for it as a regulator makes the kernel do + * the waiting: devm_regulator_get() returns -EPROBE_DEFER until this + * driver registers, and the consumer is re-probed afterwards. + * + * of_match plus regulators_node is what lets a device tree node name it, + * which is the whole mechanism -- without those it is registered but + * unreachable from DT. + */ +static int n31_bt_reg_enable(struct regulator_dev *rdev) +{ + return d1830_bt_rails(true); +} + +static int n31_bt_reg_disable(struct regulator_dev *rdev) +{ + return d1830_bt_rails(false); +} + +static int n31_bt_reg_is_enabled(struct regulator_dev *rdev) +{ + struct i2c_client *client = d1830_poweroff_client; + int a; + + if (!client) + return 0; + a = i2c_smbus_read_byte_data(client, D1830_BT_REG_A); + if (a < 0) + return a; + return (a & D1830_BT_A_MASK) ? 1 : 0; +} + +static const struct regulator_ops n31_bt_reg_ops = { + .enable = n31_bt_reg_enable, + .disable = n31_bt_reg_disable, + .is_enabled = n31_bt_reg_is_enabled, +}; + +static const struct regulator_desc n31_bt_reg_desc = { + .name = "bt", + .of_match = "bt", + .regulators_node = "regulators", + .id = 0x100, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &n31_bt_reg_ops, + .n_voltages = 1, +}; + +static void n31_pmu_regulators_register(struct device *dev) +{ + struct regulator_config cfg = { }; + struct regulator_dev *rdev; + unsigned int i; + + cfg.dev = dev; + for (i = 0; i < ARRAY_SIZE(n31_pmu_rails); i++) { + struct n31_pmu_reg *pr = &n31_pmu_regs[i]; + + strscpy(pr->name, n31_pmu_rails[i].name, sizeof(pr->name)); + pr->id = i; + pr->desc.name = pr->name; + pr->desc.id = i; + pr->desc.type = REGULATOR_VOLTAGE; + pr->desc.owner = THIS_MODULE; + pr->desc.ops = &n31_pmu_reg_ops; + pr->desc.n_voltages = 1; + + cfg.driver_data = pr; + rdev = devm_regulator_register(dev, &pr->desc, &cfg); + if (IS_ERR(rdev)) + dev_warn(dev, "regulator %s: %ld\n", + pr->name, PTR_ERR(rdev)); + } + { + struct regulator_config btcfg = { }; + struct regulator_dev *btrdev; + + btcfg.dev = dev; + btrdev = devm_regulator_register(dev, &n31_bt_reg_desc, &btcfg); + if (IS_ERR(btrdev)) + dev_warn(dev, "bt regulator: %ld\n", PTR_ERR(btrdev)); + else + dev_info(dev, "bt rail exposed as a regulator\n"); + } + + dev_info(dev, "%u PMU rails exposed as regulators (writes %s)\n", + (unsigned int)ARRAY_SIZE(n31_pmu_rails), + allow_pmu_writes ? "allowed" : "blocked"); +} + +static void n31_pmu_debugfs_init(void) +{ + struct dentry *d; + + d = debugfs_create_dir("n31_pmu", NULL); + if (IS_ERR(d)) + return; + n31_pmu_debugfs = d; + + debugfs_create_file("variant", 0444, d, NULL, &n31_pmu_variant_fops); + debugfs_create_file("regs_raw", 0444, d, NULL, &n31_pmu_regs_raw_fops); + debugfs_create_file("regs_named", 0444, d, NULL, + &n31_pmu_regs_named_fops); + debugfs_create_file("rails", 0444, d, NULL, &n31_pmu_rails_fops); + debugfs_create_file("bootseq_decode", 0444, d, NULL, + &n31_pmu_bootseq_decode_fops); + debugfs_create_file("snapshot", 0444, d, NULL, &n31_pmu_snapshot_fops); + debugfs_create_file("diff_last", 0444, d, NULL, + &n31_pmu_diff_last_fops); + debugfs_create_file("audio_snapshot", 0444, d, NULL, + &n31_pmu_audio_snapshot_fops); + debugfs_create_file("display_snapshot", 0444, d, NULL, + &n31_pmu_display_snapshot_fops); + debugfs_create_file("apply_bootseq_dryrun", 0444, d, NULL, + &n31_pmu_apply_bootseq_dryrun_fops); +} + +static void n31_pmu_debugfs_exit(void) +{ + debugfs_remove_recursive(n31_pmu_debugfs); + n31_pmu_debugfs = NULL; +} + + +static void d1830_log_audio_regs(struct i2c_client *client, const char *tag) +{ + static const u8 regs[] = { + 1, 2, 3, 5, 13, 14, 16, 17, 19, 20, 21, 22, 23, 26, 35, + 36, 37, 38, 41, 42, 43, 48, 89, 96, 110, 111 + }; + char hex[8]; + int i, v; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + v = i2c_smbus_read_byte_data(client, regs[i]); + if (v < 0) + snprintf(hex, sizeof(hex), "ERR"); + else + snprintf(hex, sizeof(hex), "%02x", v); + d1830_vinfo(&client->dev, "n31-pmic: %s RD %02x -> %s\n", + tag, regs[i], hex); + } +} + +/* + * OSOS sub_20766(1) → 439B00(1) → 6644(4) → 7484(pmic, 9, on): + * RMW D1830 register 16 bit 5. Targeted Grape rail — not the SEC seq. + */ +/* + * Bluetooth companion rails. + * + * sub_51688C, the de-init half of the Bluetooth bring-up, zeroes two + * entries through sub_158C82 -- a different accessor from the sub_7484 + * rail toggler, writing multi-bit fields rather than a single enable: + * + * index 3: reg 87 bits 7:6, and reg 88 bit 0 + * index 5: reg 88 bits 6:4 + * + * Nothing in the init path sets them, so their running values come from + * the bootloader. Clearing them without keeping the originals would mean + * Bluetooth could be turned off exactly once per boot and never restored, + * so the first power-off saves what it found and power-on puts it back. + */ + +static u8 d1830_bt_saved_a, d1830_bt_saved_b; +static bool d1830_bt_saved; + +int d1830_bt_rails(bool on) +{ + struct i2c_client *client = d1830_poweroff_client; + int a, b, ret; + + if (!client) + return -ENODEV; + + if (on) { + if (!d1830_bt_saved) + return 0; /* never turned off; leave boot state alone */ + ret = d1830_rmw(client, D1830_BT_REG_A, D1830_BT_A_MASK, + d1830_bt_saved_a & D1830_BT_A_MASK); + if (!ret) + ret = d1830_rmw(client, D1830_BT_REG_B, D1830_BT_B_MASK, + d1830_bt_saved_b & D1830_BT_B_MASK); + d1830_vinfo(&client->dev, + "bt rails restored r87=%02x r88=%02x ret=%d\n", + d1830_bt_saved_a, d1830_bt_saved_b, ret); + return ret; + } + + a = i2c_smbus_read_byte_data(client, D1830_BT_REG_A); + if (a < 0) + return a; + b = i2c_smbus_read_byte_data(client, D1830_BT_REG_B); + if (b < 0) + return b; + if (!d1830_bt_saved) { + d1830_bt_saved_a = (u8)a; + d1830_bt_saved_b = (u8)b; + d1830_bt_saved = true; + } + ret = d1830_rmw(client, D1830_BT_REG_A, D1830_BT_A_MASK, 0); + if (!ret) + ret = d1830_rmw(client, D1830_BT_REG_B, D1830_BT_B_MASK, 0); + d1830_vinfo(&client->dev, + "bt rails off (was r87=%02x r88=%02x) ret=%d\n", + a, b, ret); + return ret; +} +EXPORT_SYMBOL_GPL(d1830_bt_rails); + +/* bcm2078-bt is built in and cannot link against this module, so it + * publishes a hook and we fill it in. */ +void bcm2078_register_bt_rails(int (*fn)(bool on)); + +/* + * Read PMIC register 0x51 during touch bring-up. + * + * OSOS does this in sub_20E94, via sub_26144(...,8) -> sub_41286E(81), + * between the 26494 probe and the download loop. The value it reads is + * not used afterwards, which is exactly why it was skipped here for so + * long -- and that reasoning was wrong. A register read is a bus + * transaction, and plenty of PMIC registers do something when they are + * addressed: clear a latched status, sample an input, arm a state + * machine. The decompiler can show the returned value going nowhere and + * still tell us nothing about what the read did to the part. + * + * 0x51 is inside the PMIC GPIO block (0x50..0x57). Whether this one + * latches anything is unknown; what is known is that stock performs it + * on every touch bring-up and we did not. Reproduce the sequence, + * including the parts whose purpose is not obvious yet. + */ +int d1830_touch_bringup_read(void) +{ + struct i2c_client *client = d1830_poweroff_client; + int v; + + if (!client) + return -ENODEV; + + v = i2c_smbus_read_byte_data(client, 0x51); + if (v < 0) { + dev_warn(&client->dev, + "n31-pmic: touch bring-up read of 0x51 failed: %d\n", + v); + return v; + } + + d1830_vinfo(&client->dev, + "n31-pmic: touch bring-up read 0x51 = 0x%02x\n", v); + return v; +} +EXPORT_SYMBOL_GPL(d1830_touch_bringup_read); + +int d1830_grape_rail(bool on) +{ + struct i2c_client *client = d1830_poweroff_client; + int before, ret; + + if (!client) + return -ENODEV; + before = i2c_smbus_read_byte_data(client, 16); + if (before < 0) + return before; + ret = d1830_rmw(client, 16, BIT(5), on ? BIT(5) : 0); + d1830_vinfo(&client->dev, "grape rail reg16 0x%02x -> bit5=%d ret=%d\n", + before, on, ret); + return ret; +} +EXPORT_SYMBOL_GPL(d1830_grape_rail); + +/* + * SEC sub_23EC trim — sibling LDOs used by analog HP. + * + * Bootloader writes computed 5-bit values from tables at 0x22004B70 / + * 0x22004B80 (not recovered). Live Linux (gpio-only): r20=0x1a already + * has bit4; r21=0x0a (no bit4); r22=r23=0x00. Match the group by + * setting bit4 on 20–23 and forcing 21/22/23 to the same value, then + * the documented r16/r17/r19 RMWs. Skip r26=0xB2 (charge). Never + * touch POWEROFF (reg 13). + * + * Cold-boot sub_23EC also sets r16 bit5 (same bit Grape uses). Keep + * that — OSOS 7484 will still toggle it for BT. + */ +/* + * The step stock performs immediately before the rail trim: + * + * sub_3F40(13, 1u, v133); LOBYTE(v133[0]) &= 0x8Fu; sub_3F60(13, 1u, v133); + * + * SYS_CONTROL bits 4, 5 and 6 are SWI_EN, PRO_FET_DIS and BUS_PWR_SUSPEND; + * clearing them is the state the trim expects to run against. Our boot chain + * never runs the stock bootloader, so this had never happened on this device. + */ +static int d1830_sys_control_pre_trim(struct i2c_client *client) +{ + int v = i2c_smbus_read_byte_data(client, D1830_REG_SYS_CONTROL); + + if (v < 0) + return v; + return d1830_write8(client, D1830_REG_SYS_CONTROL, + (u8)(v & ~(D1830_SYS_SWI_EN | + D1830_SYS_PRO_FET_DIS | + D1830_SYS_BUS_PWR_SUSPEND))); +} + +/* + * sub_FDE: on a normal (non-hibernate) boot stock zeroes the four scratch + * bytes that sub_45C0 uses to carry EVENT_A..EVENT_D across hibernate. If + * they are left holding a previous session's events, anything that later + * reads them sees a stale wake reason. + * + * The hibernate counterpart (sub_45C0, which *saves* the events) belongs in + * a suspend path; see d1830_pm_suspend_stub(). + */ +/* + * sub_FDE: zero the saved-EVENT scratch, regs 124-127. + * + * Deliberately has no caller. Stock runs it only on a cold boot, from the + * bootloader, before Linux exists -- see the note in d1830_gpio_probe(). + * Kept as the transliteration so the sequence is not lost. + */ +static int __maybe_unused d1830_clear_saved_events(struct i2c_client *client) +{ + unsigned int reg; + int ret; + + for (reg = D1830_REG_SAVED_EVENT_A; reg <= D1830_REG_SAVED_EVENT_D; reg++) { + ret = d1830_write8(client, reg, 0); + if (ret) + return ret; + } + return 0; +} + +/* + * The two board rail-trim tables sub_23EC reads, and the fuse word that + * indexes them. + * + * These were long recorded here as unrecoverable -- "populated from board + * data before sub_23EC runs", so the codes could not be known statically and + * the driver preserved whatever 5-bit field was already in the register. That + * was wrong, and the reason it was wrong is worth keeping: IDA loaded the + * bootloader at base 0, so VA 0x22004B70 fell outside its address space and + * rendered as MEMORY[0x22004B70] with nothing behind it. The bootloader is + * loaded flat at VA 0x22000000, so that address is simply file offset 0x4B70 + * of N31.bootloader.dec.bin -- static data, present in the image all along. + * + * Four self-describing constants in the same block confirm the load base: + * + * 0x4B9C = 0x22000000 SRAM base + * 0x4BDC = 0x00030000 SRAM size + * 0x4BD8 = 0x08000000 the OS load base, known independently + * 0x4BB8 = 24000000 UART clock + * + * The tables, read straight out of the image: + * + * 0x4B70: E8 03 x4 1000, 1000, 1000, 1000 mV + * 0x4B80: B6 03 9D 03 84 03 6B 03 950, 925, 900, 875 mV + * + * sub_23EC calls sub_6CC(mV - 725, 25) -- __udivsi3 -- and masks five bits, + * so the register code is (mV - 725) / 25. Every entry divides exactly on the + * 25 mV grid. sub_2D404 in the OS image independently gives base 725 step 25 + * for registers 20-23, from a different image. + * + * Cross-check with no free parameters: index 0 yields 0x0B for register 20 + * and 0x09 for 21-23, and the as-found dump on this unit reads + * 0x14=0x0b 0x15=0x09 0x16=0x09 0x17=0x09. Static derivation and measurement + * agree exactly, which is why this is now computed rather than preserved. + * + * Note stock writes these as bare bytes with bits 7:5 zero. It does not + * read-modify-write them, and it has no "leave it alone" fallback: on three + * consecutive I2C failures sub_23EC calls sub_128C, which does not return. + */ +static const u16 d1830_trim_code_a_mv[4] = { 1000, 1000, 1000, 1000 }; +static const u16 d1830_trim_code_b_mv[4] = { 950, 925, 900, 875 }; + +/* sub_351E(0x3D100014): SoC ID/fuse word. Bit 30 says the index field is + * valid; bits 29:28 are the index. Not set means index 0. */ +#define D1830_TRIM_FUSE_PHYS 0x3d100014ul + +static u8 d1830_trim_index(struct i2c_client *client) +{ + void __iomem *p; + u32 w; + u8 idx = 0; + + p = ioremap(D1830_TRIM_FUSE_PHYS, 4); + if (!p) { + dev_warn(&client->dev, + "n31-pmic: trim fuse ioremap failed, using index 0\n"); + return 0; + } + w = readl(p); + iounmap(p); + + if (w & BIT(30)) + idx = (u8)((w >> 28) & 3); + + dev_info(&client->dev, + "n31-pmic: trim fuse 0x%08x -> index %u (r20=0x%02x r21-23=0x%02x)\n", + w, idx, + (u8)(((d1830_trim_code_a_mv[idx] - 725) / 25) & 0x1f), + (u8)(((d1830_trim_code_b_mv[idx] - 725) / 25) & 0x1f)); + return idx; +} + +static int d1830_sec_trim_seq(struct i2c_client *client, u8 boot_mode) +{ + int v21, v35; + u8 fill, r16, idx; + + d1830_vinfo(&client->dev, + "n31-pmic: sub_23EC-equivalent begin boot_mode=0x%02x\n", + boot_mode); + + /* + * Stock order is r20 first, then r35. + * + * sub_23EC writes r20 = codeA & 0x1F and r21..r23 = codeB & 0x1F, + * both computed from the board tables above and the fuse index. It + * does not read these registers first, so neither do we: writing a + * bare byte is what the sequence does. + * + * Two earlier versions of this write are worth remembering, because + * both looked reasonable in the file and both were wrong. + * + * It first ORed 0x10 into all four registers -- already admitted in + * this file as "this driver's own invention, not stock". Bit 4 is the + * top bit of the 5-bit voltage code, so that added 16 steps, 400 mV, + * to each. Measured: reg 0x17 (LDO1) read 0x09 = 950 mV at probe and + * 0x19 = 1350 mV after playback had run this sequence. + * + * It then preserved the live 5-bit field, on the belief that stock's + * codes were unrecoverable. They are not; see the tables above. The + * preserved value happened to equal the computed one on this unit, + * which is exactly why the belief survived so long. + */ + idx = d1830_trim_index(client); + d1830_write8(client, 20, + (u8)(((d1830_trim_code_a_mv[idx] - 725) / 25) & 0x1f)); + + v35 = i2c_smbus_read_byte_data(client, 35); + if (v35 >= 0) + d1830_write8(client, 35, (u8)(v35 & 0xFC)); + + fill = (u8)(((d1830_trim_code_b_mv[idx] - 725) / 25) & 0x1f); + d1830_write8(client, 21, fill); + d1830_write8(client, 22, fill); + d1830_write8(client, 23, fill); + + /* + * Step 6 of sub_23EC: r26 (0x1A) = 0xB2, issued twice. The firmware + * reuses the same stack byte, so the repeat is reproduced rather + * than tidied away. + * + * This lived in d1830_charger_config() behind charger_hw_init and + * ldo_1a_write, both of which default to false -- so on our device + * this step of the sequence never executed at all. sub_23EC is one + * sequence in stock and belongs in one function here. + */ + d1830_write8(client, 26, D1830_LDO4_STOCK); + d1830_write8(client, 26, D1830_LDO4_STOCK); + + v21 = i2c_smbus_read_byte_data(client, 16); + if (v21 < 0) + return v21; + r16 = (u8)((v21 & 0x2f) | 0x10); + if (!boot_mode) + r16 |= 0x20; + /* + * The boot form of this write clears bits 6 and 7 -- display and + * accessory. That is correct exactly once, on a cold boot, when + * neither is up yet. Replaying it later switches off a panel that + * is already lit, which is what the white screen was. + * + * Two guards, because one is not enough. Restoring rails a driver + * holds only works if a driver actually claimed one, and the + * display is the case where none did: the panel arrives already + * running from the bootloader handoff, and the DRM driver is + * built in, so it probes long before this module exists and its + * claim cannot reach us. So also refuse to clear any of those two + * bits that the hardware currently has set. Turning a live rail + * off is never what this sequence is for. + */ + r16 |= n31_pmu_rail_held_mask(0x10); + if (!apply_boot_rails) + r16 |= (u8)(v21 & 0xc0); + d1830_write8(client, 16, r16); + + d1830_rmw(client, D1830_REG_ACTIVE_2, 0, 0x07); + + d1830_rmw(client, D1830_REG_HIBERNATE_1, 0, 0x02); + + d1830_vinfo(&client->dev, "n31-pmic: sub_23EC-equivalent complete\n"); + return 0; +} + +/* + * There is no "analog LDO enable" at registers 20-23 (0x14-0x17). + * + * A note in our Rockbox placeholder claimed the codec analog side was + * powered by "regs 21-23 bit 4", and acting on it locked the kernel on + * boot. The OSOS rail setter settles it: registers 20-23 take LABEL_25, + * which computes the code at a 25 mV step and writes (code & 0x1F) -- + * the low five bits and nothing else. Contrast case 17/18, registers 46 + * and 47, which deliberately preserve (old & 0xE0); 20-23 preserve + * nothing, because there is nothing up there to preserve. Register 0x1a + * is different again: it alone uses (code & 0x1F) | 0xA0 at a 100 mV + * step, which is where the idea of an enable pattern came from. + * + * So bit 4 in these registers is the top bit of a voltage field, not a + * switch. Setting it on 0x15-0x17, which read 0x09 on this unit, moved + * three rails by +16 steps -- 400 mV each, simultaneously -- and the + * device did not survive it. + * + * If the codec analog supply is gated somewhere, it is not here. Whoever + * picks this up next: establish which rail actually feeds the CS42L81 + * analog stage before writing anything in this range. + */ + +/* + * sub_23EC analog-adjacent trim only. Called from CS42 prepare. + * Does not run hibernate cookie / POWEROFF / charger 0xB2. + */ +int d1830_audio_rails(void) +{ + struct i2c_client *client = d1830_poweroff_client; + int r02, ret; + + if (!client) + return -ENODEV; + + if (!allow_audio_rails) { + d1830_vinfo(&client->dev, + "n31-pmic: board rail trim skipped (allow_audio_rails=0)\n"); + return 0; + } + + d1830_log_audio_regs(client, "audio-before"); + r02 = i2c_smbus_read_byte_data(client, 2); + if (r02 < 0) + return r02; + + /* Stock's order: SYS_CONTROL is cleaned up first, then the trim. */ + ret = d1830_sys_control_pre_trim(client); + if (ret) + dev_warn(&client->dev, + "n31-pmic: SYS_CONTROL pre-trim failed: %d\n", ret); + + ret = d1830_sec_trim_seq(client, (r02 & 0x80) ? 0x11 : 0x00); + /* sub_27F4 analog-adjacent: r14 fixed 0x20. Not POWEROFF. */ + if (!ret) + d1830_write8(client, 14, 0x20); + d1830_log_audio_regs(client, "audio-after"); + return ret; +} +EXPORT_SYMBOL_GPL(d1830_audio_rails); + +/* + * IpodSec PMIC rail / charge bring-up. Opt-in via dlg,apply-sec-rails. + * + * The old Linux seq always wrote the hibernate-to-standby cluster + * (reg2=0x80, reg73=0, reg1=0, cookie@96) and historically POWEROFF. + * Bootloader only does that when reg2 bit7 is set. Match the dummies + * doc: detect boot_mode, skip reset/hibernate unless that branch, + * never write reg 13 here. + */ +static int d1830_sec_rail_seq(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int r02, r01; + u8 boot_mode = 0; + + r02 = i2c_smbus_read_byte_data(client, 2); + if (r02 < 0) + return r02; + d1830_vinfo(dev, "n31-pmic: RD 02 -> %02x\n", r02); + if (r02 & 0x80) { + boot_mode = 0x11; + d1830_write8(client, 2, 0x80); + } + d1830_vinfo(dev, "n31-pmic: boot_mode=0x%02x\n", boot_mode); + + r01 = i2c_smbus_read_byte_data(client, 1); + if (r01 < 0) + return r01; + d1830_vinfo(dev, "n31-pmic: RD 01 -> %02x\n", r01); + + if (boot_mode == 0x11) { + dev_warn(dev, + "n31-pmic: hibernate-to-standby path detected, reset suppressed (reg01=%02x)\n", + r01); + /* Do not write reg 0x49/0x60/0x01/0x0d or call sub_1130. */ + } + + /* + * EA 0x29BA: RD 0x0D, clear bits 4/5/6, WR 0x0D -- unconditional, and + * immediately before the BL to sub_23EC at 0x29DC. This sequence went + * straight from the reg 1 read into the trim without it. + * + * The mask cannot reach bit 0 or bit 1, so it cannot request standby + * or hibernate. + */ + if (d1830_sys_control_pre_trim(client)) + dev_warn(dev, "n31-pmic: SYS_CONTROL pre-trim failed\n"); + + d1830_sec_trim_seq(client, boot_mode); + + /* sub_27F4 post-trim, non-fatal. NEVER POWEROFF (reg 13). */ + d1830_rmw(client, 48, 0, 0x40); + d1830_rmw(client, 89, 0x1C, 0); + d1830_write8(client, 60, 0x01); + if (boot_mode != 0x11) + d1830_rmw(client, 41, 0x13, 0x10); + d1830_rmw(client, 42, 0x3F, 0x14); + d1830_rmw(client, 43, 0x0F, 0x01); + d1830_write8(client, 14, 0x20); + d1830_rmw(client, 38, 0x01, 0); + + /* + * EA 0x2B2A, cold boot only: derive the charge current from the fuel + * gauge and program it. + * + * RD 0x6F -> v + * if (v & 0x80) { v &= 0x7F; if (v > 100) v = 40; } else v = 40; + * code = sub_B88(v) + * WR 0x24 = code >> 1 + * WR 0x25 = (code & 1) << 2 + * + * sub_B88(pct) = pct > 40 ? 475 - (91 * (100 - pct)) / 60 + * : (284 * pct) / 40 + 100 + */ + if (boot_mode != 0x11) { + int r6f = i2c_smbus_read_byte_data(client, 0x6f); + unsigned int pct = 40, code; + + if (r6f >= 0 && (r6f & 0x80)) { + pct = (unsigned int)(r6f & 0x7f); + if (pct > 100) + pct = 40; + } + code = (pct > 40) ? (475u - (91u * (100u - pct)) / 60u) + : ((284u * pct) / 40u + 100u); + d1830_write8(client, 36, (u8)(code >> 1)); + d1830_write8(client, 37, (u8)((code & 1u) << 2)); + d1830_vinfo(dev, + "n31-pmic: charge pct=%u code=%u r24=%02x r25=%02x\n", + pct, code, (u8)(code >> 1), (u8)((code & 1u) << 2)); + } + + /* + * EA 0x2BC6: RD 0x0D, clear bits 2 and 3, WR 0x0D -- the last thing + * sub_27F4 does on every boot. The mask cannot reach bit 0 or bit 1. + */ + d1830_rmw(client, D1830_REG_SYS_CONTROL, 0x0C, 0); + + /* + * Deliberately NOT transliterated, because every one of them ends in a + * non-returning spin after cutting power: + * + * EA 0x28D4 hibernate-to-standby, WR 0x0D = 0x01 + * EA 0x297C reg 0x03/0x6E/0x07 branch, WR 0x0D |= 1 or 2 + * EA 0x12EC sub_128C panic handler, WR 0x0D |= 1 + * + * Writing reg 13 bit 0 has already cut power to this device once. + * There is no non-terminal bit 0 or bit 1 write anywhere in either + * image, so a Linux driver has nothing to gain by reproducing them. + */ + + d1830_vinfo(dev, "n31-pmic: sub_27F4-equivalent complete (POWEROFF skipped)\n"); + return 0; +} + +/* + * Log every rail and every enable bit exactly as found, before this driver + * changes anything. + * + * The point is a baseline: the trim, the codec prepare and the BT/Grape rail + * helpers all move bits in ACTIVE1/ACTIVE2, so anything sampled later cannot + * be attributed. Called first thing in probe. + * + * Voltages are decoded with the encodings from stock's sub_2D404 (see + * docs-internal/n7g-audio/N31-POWER-RAILS.md). Enable polarity in + * ACTIVE1/ACTIVE2 is set = ON -- note this is the opposite of the per-rail + * gate in sub_2D30E, where config bit 5 set means OFF. + */ +static void d1830_dump_rails(struct i2c_client *client, const char *tag) +{ + static const u8 sysregs[] = { 0x0d, 0x10, 0x11, 0x12, 0x13 }; + static const char *const sysnames[] = { + "SYS_CONTROL", "ACTIVE1", "ACTIVE2", "STANDBY1", "HIBERNATE1" + }; + unsigned int i; + int act1, act2; + + for (i = 0; i < ARRAY_SIZE(sysregs); i++) { + int v = i2c_smbus_read_byte_data(client, sysregs[i]); + + dev_info(&client->dev, "pmu %s: 0x%02x %-11s = 0x%02x\n", + tag, sysregs[i], sysnames[i], v < 0 ? 0 : v); + } + + act1 = i2c_smbus_read_byte_data(client, 0x10); + act2 = i2c_smbus_read_byte_data(client, 0x11); + + /* + * ACTIVE2 reads 0x17 as found -- bits 0, 1, 2 and 4. sub_7484 sends + * rail indices 12-15 to register 17 (0x11) and uses only bits 0-3 + * there, so bit 4 belongs to no rail in the stock setter and this + * driver must not relabel it as one. It arrives already set from the + * bootloader handoff. Named here so it stops being a silent unknown. + */ + if (act2 >= 0 && (act2 & 0x10)) + dev_info(&client->dev, + "pmu %s: ACTIVE2 bit4 set (0x%02x) -- no rail index in sub_7484, unidentified\n", + tag, act2); + + /* + * sub_2D404 cases 16-18 and sub_7484 cases 16-18: three more supplies + * outside the 0x17..0x22 LDO block, at regs 0x28, 0x2E and 0x2F. All + * three decode 5000 + 50*n and are enabled by bit 5 of their own + * register rather than by ACTIVE1/ACTIVE2. This driver has never read + * or written them, so log them rather than leave a blind spot. + */ + { + static const u8 hv[] = { 0x28, 0x2e, 0x2f }; + unsigned int k; + + for (k = 0; k < ARRAY_SIZE(hv); k++) { + int raw = i2c_smbus_read_byte_data(client, hv[k]); + + dev_info(&client->dev, + "pmu %s: PMU_HV_%u vsel 0x%02x=0x%02x -> %5d mV en(own bit5)=%d\n", + tag, k, hv[k], raw < 0 ? 0 : raw, + raw < 0 ? -1 : 5000 + (raw & 0x1f) * 50, + raw < 0 ? -1 : !!(raw & 0x20)); + } + } + + for (i = 0; i < ARRAY_SIZE(n31_pmu_rails); i++) { + const struct n31_pmu_rail *r = &n31_pmu_rails[i]; + int raw, act, on, mv = -1; + + if (r->vsel == N31_PMU_NO_VSEL) { + raw = -1; + } else { + raw = i2c_smbus_read_byte_data(client, r->vsel); + if (raw >= 0) + mv = r->base_mv + + (raw & (r->vsel == 0x18 ? 0x0f : 0x1f)) * + r->step_mv; + } + + act = (r->active_reg == 0x10) ? act1 : act2; + on = (act < 0) ? -1 : !!(act & r->active_mask); + + /* + * LDO7 and LDO8 have a second gate, and it runs the other way. + * + * sub_2D30E takes rail index 10 -> register 29 (0x1D) and index 11 + * -> register 30 (0x1E) -- the same bytes this table calls the + * LDO7/LDO8 vsel -- and does + * + * v = (v & 0xDF) | (32 * (a3 != 0)) + * + * Its caller picks a3 from the requested voltage: the "off" + * sentinel 0x80000000 passes a3 = 1, and any real voltage passes + * a3 = 0 and then programs the level via sub_2D404. So for these + * two rails bit 5 SET means OFF -- the opposite of the + * ACTIVE1/ACTIVE2 convention, where a set bit is on. + * + * A rail is only actually on when its ACTIVE bit is set AND, for + * these two, bit 5 of its own register is clear. Reporting the + * ACTIVE bit alone gives the right answer while both config bits + * read clear, which is what this unit does -- so this was + * invisible rather than harmless. + */ + if ((r->vsel == 0x1d || r->vsel == 0x1e) && on > 0 && raw >= 0) + on = !(raw & 0x20); + + dev_info(&client->dev, + "pmu %s: %-11s vsel 0x%02x=0x%02x -> %5d mV en(%s bit%d)=%d%s\n", + tag, r->name, r->vsel, + raw < 0 ? 0 : raw, mv, + r->active_reg == 0x10 ? "ACTIVE1" : "ACTIVE2", + __ffs(r->active_mask), on, + (r->vsel == 0x1d || r->vsel == 0x1e) + ? (raw >= 0 && (raw & 0x20) ? " cfg-bit5=1 FORCED-OFF" + : " cfg-bit5=0") + : ""); + } +} + +static int d1830_gpio_probe(struct i2c_client *client) +{ + struct d1830_gpio *gpio_dev; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "Adapter does not support SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL); + if (!gpio_dev) + return -ENOMEM; + + gpio_dev->client = client; + i2c_set_clientdata(client, gpio_dev); + + ret = d1830_gpio_parse_dt(gpio_dev); + if (ret) + return ret; + + d1830_poweroff_client = client; + + /* Baseline, before this driver changes a single bit. */ + d1830_dump_rails(client, "as-found"); + + /* bcm2078-bt is built in; hand it our rail control now that we have + * a client to talk to. */ + bcm2078_register_bt_rails(d1830_bt_rails); + + /* Opt-in only. Default probe is GPIO + VBAT reads — no rail writes. + * The old default seq wrote reg 13 = 0x01 (POWEROFF bit) at boot. + */ + if (of_property_read_bool(dev->of_node, "dlg,apply-sec-rails")) + d1830_sec_rail_seq(client); + else + d1830_vinfo(dev, "d1830 gpio-only (rail seq off; the bootloader already ran it)\n"); + + d1830_log_audio_regs(client, "probe"); + + gpio_dev->gpio_chip.label = dev_name(dev); + gpio_dev->gpio_chip.parent = dev; + gpio_dev->gpio_chip.owner = THIS_MODULE; + gpio_dev->gpio_chip.base = -1; + gpio_dev->gpio_chip.ngpio = gpio_dev->num_gpios; + gpio_dev->gpio_chip.can_sleep = true; + gpio_dev->gpio_chip.get_direction = d1830_gpio_get_direction; + gpio_dev->gpio_chip.direction_input = d1830_gpio_direction_input; + gpio_dev->gpio_chip.direction_output = d1830_gpio_direction_output; + gpio_dev->gpio_chip.get = d1830_gpio_get; + gpio_dev->gpio_chip.set = d1830_gpio_set; + + ret = devm_gpiochip_add_data(dev, &gpio_dev->gpio_chip, gpio_dev); + if (ret) { + dev_err(dev, "Failed to add GPIO chip: %d\n", ret); + return ret; + } + + /* Every create here has a matching remove below. A leaked attribute + * outlives the module and reading it jumps into freed text, which is + * how buttons oopsed. */ + if (device_create_file(dev, &dev_attr_adc_channels)) + dev_warn(dev, "adc_channels sysfs failed\n"); + if (device_create_file(dev, &dev_attr_charger_regs)) + dev_warn(dev, "charger_regs sysfs failed\n"); + if (device_create_file(dev, &dev_attr_charger_watch)) + dev_warn(dev, "charger_watch sysfs failed\n"); + if (device_create_file(dev, &dev_attr_regs)) + dev_warn(dev, "regs sysfs failed\n"); + ret = device_create_file(dev, &dev_attr_do_poweroff); + if (ret) + dev_warn(dev, "sysfs do_poweroff unavailable: %d\n", ret); + + ret = device_create_file(dev, &dev_attr_audio_rails); + if (ret) + dev_warn(dev, "sysfs audio_rails unavailable: %d\n", ret); + + ret = device_create_file(dev, &dev_attr_vbat_raw); + if (ret) + dev_warn(dev, "sysfs vbat_raw unavailable: %d\n", ret); + + if (!pm_power_off) { + pm_power_off = d1830_pm_power_off; + if (devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + d1830_sys_off_handler, NULL)) + dev_warn(dev, "sys-off handler not registered\n"); + /* + * The charger configuration write runs by default: 0x1a already + * reads 0xb2 here because the bootloader wrote it, so this confirms + * the stock state and makes us correct on a path where it did not. + * + * The full sub_23EC replay stays behind charger_hw_init, because its + * 0x10 masking clears bit 6 -- a live rail -- and RetailOS only runs + * that at cold boot, before anything depends on those rails. + */ + if (charger_hw_init) { + int cret = d1830_charger_hw_init(client, true); + + dev_warn(dev, "sub_23EC PMIC init replayed: %d\n", cret); + } else if (ldo_1a_write) { + int cret = d1830_charger_config(client); + + if (cret) + dev_warn(dev, "0x1a rail write: %d\n", cret); + } + /* + * The saved-EVENT scratch is deliberately left alone. + * + * This used to run the sub_FDE half unconditionally at probe, zeroing + * regs 124-127. Stock only does that on a cold boot. On a hibernate + * wake it does the OPPOSITE: sub_45C0 copies regs 1-4 INTO 124-127. + * The bootloader has already run whichever half applies before Linux + * gets control -- EA 0x29B0, boot_mode == 0x11 ? sub_45C0 : sub_FDE, + * immediately before the trim. + * + * We cannot tell the two apart. boot_mode comes from reg 2 bit 7 and + * the bootloader consumes and clears that latch at EA 0x2822-0x282E, + * so by the time this driver probes it always reads 0. + * + * Guessing cold boot is not safe. On a cold boot the bootloader has + * already zeroed these registers and our write is redundant; on a + * hibernate wake it destroys what the bootloader just saved. + * Redundant in one case and destructive in the other leaves no case + * where the write is required, so it is gone. + * + * d1830_clear_saved_events() is kept below with no caller, as the + * sub_FDE transliteration. + */ + + d1830_charger_update(client); + d1830_charger_register(dev); + if (d1830_rtc_register(client)) + dev_warn(&client->dev, "RTC not registered\n"); + if (register_restart_handler(&d1830_restart_nb)) + dev_warn(&client->dev, + "restart handler not registered\n"); + d1830_vinfo(dev, "registered pm_power_off (SEC reg %u bit0)\n", + D1830_REG_POWEROFF); + } else { + dev_warn(dev, "pm_power_off already set — sysfs do_poweroff only\n"); + } + + /* OSOS 9-12 mask only. No 27F4 tail, no 1-4 writeback, no IIC1 peek. */ + d1830_dump_irq_chain(client, "sec-left"); + d1830_osos_nirq_mask(client); + d1830_dump_irq_chain(client, "osos-mask"); + + /* + * Requesting this arms a real EIC line. The level/type encoding is + * now taken from the stock configuration path, but it has not been + * confirmed on hardware, and getting it wrong wedges the system + * rather than merely failing. Polling covers the buttons meanwhile, + * so this stays opt-in until INTSTAT is observed tracking a press. + */ + if (client->irq > 0 && pmic_irq) { + struct irq_data *d = irq_get_irq_data(client->irq); + + if (d) + s5l8740_eic_enable_gpio(d->hwirq, IRQ_TYPE_LEVEL_LOW); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + d1830_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "d1830-nirq", gpio_dev); + if (ret) + dev_err(dev, "PMIC nIRQ %d failed: %d\n", + client->irq, ret); + else + d1830_vinfo(dev, + "PMIC nIRQ virq=%d hwirq=%lu LEVEL_LOW (OSOS 40641C type=1, EFBB4 DIN=0)\n", + client->irq, d ? d->hwirq : 0); + } else if (client->irq <= 0) { + dev_warn(dev, + "no PMIC nIRQ: of_irq did not map GPIO 86 -- polling\n"); + } else { + /* + * The line mapped fine. It is simply not requested, because + * pmic_irq defaults off. Saying "did not map GPIO 86" here + * blamed the device tree for a deliberate choice and sent at + * least one person looking for a DT bug that does not exist. + */ + dev_info(dev, + "PMIC nIRQ virq=%d mapped but not requested (pmic_irq=0, polling)\n", + client->irq); + } + d1830_dump_irq_chain(client, "irq-on"); + + { + int v = i2c_smbus_read_byte_data(client, 7); + + d1830_vinfo(dev, + "PMIC 7bit=0x%02x wire WR=0x%02x RD=0x%02x reg7 %s (%d)\n", + client->addr, client->addr << 1, + (client->addr << 1) | 1, + v < 0 ? "read fail" : "ok", v); + } + + { + struct power_supply_config psy_cfg = { + .drv_data = gpio_dev, + .of_node = dev->of_node, + }; + struct power_supply_desc *desc; + int mv; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (desc) { + desc->name = "d1830-battery"; + desc->type = POWER_SUPPLY_TYPE_BATTERY; + desc->properties = d1830_psy_props; + desc->num_properties = ARRAY_SIZE(d1830_psy_props); + desc->get_property = d1830_psy_get_property; + gpio_dev->psy = devm_power_supply_register(dev, desc, &psy_cfg); + if (IS_ERR(gpio_dev->psy)) { + dev_warn(dev, "battery psy unavailable: %ld\n", + PTR_ERR(gpio_dev->psy)); + gpio_dev->psy = NULL; + } else if (!d1830_read_vbat(gpio_dev, &mv)) { + d1830_vinfo(dev, + "battery psy OSOS ch3 10-bit*6 mV=%d (design %u mAh)\n", + mv, D1830_DESIGN_UAH / 1000); + } + } + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (desc) { + desc->name = "d1830-usb"; + desc->type = POWER_SUPPLY_TYPE_USB; + desc->properties = d1830_usb_props; + desc->num_properties = ARRAY_SIZE(d1830_usb_props); + desc->get_property = d1830_usb_get_property; + desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP); + gpio_dev->usb_psy = devm_power_supply_register(dev, desc, + &psy_cfg); + if (IS_ERR(gpio_dev->usb_psy)) { + dev_warn(dev, "usb psy unavailable: %ld\n", + PTR_ERR(gpio_dev->usb_psy)); + gpio_dev->usb_psy = NULL; + } + } + } + + d1830_vinfo(dev, "Registered %u read-only GPIOs using Dialog D1830 driver\n", + gpio_dev->num_gpios); + + gpio_dev->input = devm_input_allocate_device(dev); + if (gpio_dev->input) { + /* + * Home, Sleep and Play live on the PMIC status registers, so + * they are reported here and nowhere else. n31-buttons keeps + * Vol+/Vol-, which are SoC GPIOs. + */ + gpio_dev->input->name = "n31-pmic-buttons"; + gpio_dev->input->phys = "d1830/gpio"; + gpio_dev->input->dev.parent = dev; + gpio_dev->input->id.bustype = BUS_I2C; + gpio_dev->input->phys = "d1830/input0"; + gpio_dev->input->id.bustype = BUS_I2C; + input_set_capability(gpio_dev->input, EV_KEY, KEY_HOMEPAGE); + input_set_capability(gpio_dev->input, EV_KEY, KEY_POWER); + input_set_capability(gpio_dev->input, EV_KEY, KEY_PLAYPAUSE); + if (input_register_device(gpio_dev->input)) + gpio_dev->input = NULL; + } + + /* Idle snapshot only. Home / Sleep / Play arrive on the PMIC nIRQ + * (GPIO 86) via d1830_n31_din_nirq; btn_poll_ms re-enables the old + * 100ms sweep if an EIC edge is ever missed. The slow tick that + * remains is the battery power_supply refresh. + */ + d1830_btn_poll_once(gpio_dev); + INIT_DELAYED_WORK(&gpio_dev->trace, d1830_trace_work); + INIT_DELAYED_WORK(&gpio_dev->confirm, d1830_confirm_work); + schedule_delayed_work(&gpio_dev->trace, + msecs_to_jiffies(btn_poll_ms ? btn_poll_ms : 1000)); + n31_pmu_rail_init(); + n31_pmu_debugfs_init(); + n31_pmu_regulators_register(&client->dev); + if (device_create_file(dev, &dev_attr_buttons)) + dev_warn(dev, "buttons sysfs\n"); + d1830_n31_din_nirq_hook = d1830_n31_din_nirq; + return 0; +} + +static void d1830_gpio_remove(struct i2c_client *client) +{ + struct d1830_gpio *gpio_dev = i2c_get_clientdata(client); + + if (gpio_dev) { + cancel_delayed_work_sync(&gpio_dev->trace); + cancel_delayed_work_sync(&gpio_dev->confirm); + } + device_remove_file(&client->dev, &dev_attr_vbat_raw); + device_remove_file(&client->dev, &dev_attr_audio_rails); + device_remove_file(&client->dev, &dev_attr_adc_channels); + device_remove_file(&client->dev, &dev_attr_charger_regs); + device_remove_file(&client->dev, &dev_attr_charger_watch); + device_remove_file(&client->dev, &dev_attr_buttons); + device_remove_file(&client->dev, &dev_attr_regs); + device_remove_file(&client->dev, &dev_attr_do_poweroff); + if (pm_power_off == d1830_pm_power_off) + pm_power_off = NULL; + unregister_restart_handler(&d1830_restart_nb); + d1830_n31_din_nirq_hook = NULL; + n31_pmu_debugfs_exit(); + n31_pmu_rail_exit(); + bcm2078_register_bt_rails(NULL); + d1830_poweroff_client = NULL; +} + +static const struct of_device_id d1830_gpio_of_match[] = { + { .compatible = "dlg,d1830-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, d1830_gpio_of_match); + +static const struct i2c_device_id d1830_gpio_id[] = { + { "d1830-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, d1830_gpio_id); + +/* + * Suspend / resume. + * + * Stock does not do this in the OS at all -- on S3 the *bootloader* re-runs + * the rail trim (sub_23EC, unconditionally) and the codec analog power-up + * (sub_1314, on the hibernate arm) when it comes back up. Our boot chain + * never executes the stock bootloader, so on resume nothing would restore + * either. These callbacks stand in for the bootloader. + * + * INCOMPLETE -- see the placeholders below. Landing the structure now so the + * gap is visible in code rather than only in a document. + */ +static int d1830_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned int i; + int v; + + /* sub_45C0: carry EVENT_A..EVENT_D across the sleep in scratch. */ + for (i = 0; i < 4; i++) { + v = i2c_smbus_read_byte_data(client, D1830_REG_EVENT_A + i); + if (v < 0) + return v; + d1830_write8(client, D1830_REG_SAVED_EVENT_A + i, (u8)v); + } + + /* + * PLACEHOLDER: stock also writes a 4-byte 'Sctr' magic to MEMBYTE0 + * so the bootloader recognises the resume. We deliberately do NOT + * write it: it would send the *stock* bootloader down its + * hibernate arm, and our boot chain does not run that bootloader at + * all. Revisit only if we ever boot through it. + */ + return 0; +} + +static int d1830_pm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + + /* What the bootloader would have done for us. */ + ret = d1830_sys_control_pre_trim(client); + if (ret) + dev_warn(dev, "n31-pmic: resume SYS_CONTROL: %d\n", ret); + + /* + * No rail trim on resume. + * + * This ran the full sub_23EC-equivalent on every resume, which + * clears ACTIVE1 bits 6 and 7 with the panel live. Stock has no + * OS-side path that re-runs it: sub_23EC exists only in the + * bootloader and is reached only through the reset vector, so a + * stock hibernate wake gets it by re-entering the bootloader, never + * from a resume callback. + * + * Whether our S3 resume re-enters the bootloader is not established. + * Until it is, doing nothing here is the position with a stock + * analogue; running the trim is not. + */ + + /* + * PLACEHOLDER: the codec also needs its analog block brought back -- + * stock gets that from sub_1314 in the bootloader on resume. That is + * cs42l81's job, not this driver's: it must re-run from the 0x9901 + * key onward, in order. cs42l81-spi.c has no PM callbacks yet, so + * that half does not exist. Do not paper over it here by poking the + * codec from the PMIC driver. + */ + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(d1830_pm_ops, d1830_pm_suspend, d1830_pm_resume); static struct i2c_driver d1830_gpio_driver = { .driver = { .name = "gpio-d1830", .of_match_table = d1830_gpio_of_match, + .pm = pm_sleep_ptr(&d1830_pm_ops), }, .probe = d1830_gpio_probe, + .remove = d1830_gpio_remove, .id_table = d1830_gpio_id, }; module_i2c_driver(d1830_gpio_driver); MODULE_AUTHOR("Vencislav Atanasov "); -MODULE_DESCRIPTION("Dialog Semiconductor D1830 PMIC read-only GPIO driver"); +MODULE_DESCRIPTION("Dialog Semiconductor D1830 PMIC GPIO + poweroff + battery"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpio/gpio-s5l8740.c b/drivers/gpio/gpio-s5l8740.c new file mode 100755 index 00000000000000..698121da65f627 --- /dev/null +++ b/drivers/gpio/gpio-s5l8740.c @@ -0,0 +1,1037 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GPIO driver for Samsung/Apple S5L8740 (iPod nano 7G / N31) + * + * Banked MMIO @ 0x3CF00000 (RetailOS / Rockbox): + * bank_base = base + 32 * (gpio >> 3) + * DIN = bank + 0x04 + * DOUT = bank + 0x08 + * DIR = bank + 0x14 (bit set on pinmux/mode!=1; cleared on mode 0xFFFE) + * + * GPIOCMD latch @ 0x3CF001E0 (sub_43D38C): + * word = (bank << 16) | (pin << 8) | cmd + * mode==1: cmd = val ? 15 : 14 (drive high / low) + * mode==0xFFFE: clear DIR bit, cmd=0 + * else: set DIR bit, cmd=(u8)mode (pinmux / EN-enable mode 0) + * + * IRQ: gc.to_irq maps through the sibling EIC (apple,eic / apple,s5l8740-eic) + * after s5l8740_eic_enable_gpio(offset, IRQ_TYPE_LEVEL_LOW). Enough for + * gpio-keys once EIC parent chaining is proven; hierarchical irqchip optional. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define S5L8740_GPIO_BANK_STRIDE 32 +#define S5L8740_GPIO_PCON_OFF 0x00 +#define S5L8740_GPIO_DIN_OFF 0x04 +/* sub_428F70 target: input/pull enable, one bit per pad. */ +#define S5L8740_GPIO_INEN_OFF 0x0c +#define S5L8740_GPIO_DOUT_OFF 0x08 +#define S5L8740_GPIO_DIR_OFF 0x14 + +/* + * Guards every read-modify-write of a per-bank GPIO register. PCON, DIR, + * PUNB and PUNC each pack eight pads into one word, so an unlocked RMW can + * silently drop a bit another caller just set. + */ +static DEFINE_RAW_SPINLOCK(s5l8740_gpio_reg_lock); +#define S5L8740_GPIOCMD_OFF 0x1e0 +/* + * 32 banks of 8 across the 0x400 the block occupies. The old 128 cleared + * BT host-wake at 119 but not pad 200, which sub_17D4DC uses as the + * Bluetooth power control on some board variants. + */ +#define S5L8740_GPIO_DEFAULT_NGPIO 256 + +#define S5L8740_CMD_OUT_LOW 14 +#define S5L8740_CMD_OUT_HIGH 15 + +/* + * IpodSec sub_223C / sub_47CC — packed pinmux word: + * [31:24] bank, [23:16] pin, [15] pull?, [12] bit→+0x14?, [8] bit→+0x10, + * [4] bit→+0x0C, [3:0] nibble into bank+0 function field. + * Table extracted from bootloader VA 0x22004C6C (121 words). + */ +#include "pinmux_table.inc" + +static void s5l8740_pinmux_apply_word(void __iomem *gpio_base, u32 a1) +{ + unsigned int bank = (a1 >> 24) & 0xff; + unsigned int pin = (a1 >> 16) & 0xff; + void __iomem *base = gpio_base + 32u * bank; + u32 v; + + unsigned long flags; + + /* + * Four per-bank registers, each shared by eight pads. Held across + * the whole group so a concurrent pad change cannot land between the + * function nibble and the direction bit and leave the pad half + * configured. + */ + raw_spin_lock_irqsave(&s5l8740_gpio_reg_lock, flags); + v = readl(base + 0x00); + writel(((a1 & 0xfu) << (4u * pin)) | (v & ~(15u << (4u * pin))), + base + 0x00); + + v = readl(base + 0x14); + writel((((a1 >> 12) & 1u) << pin) | (v & ~BIT(pin)), base + 0x14); + + v = readl(base + 0x0c); + writel((((a1 >> 4) & 1u) << pin) | (v & ~BIT(pin)), base + 0x0c); + + v = readl(base + 0x10); + writel((((a1 >> 8) & 1u) << pin) | (v & ~BIT(pin)), base + 0x10); + raw_spin_unlock_irqrestore(&s5l8740_gpio_reg_lock, flags); +} + +static void s5l8740_pinmux_223C(struct device *dev, void __iomem *gpio_base) +{ + unsigned int i; + void __iomem *eic; + + s5l8740_pinmux_apply_word(gpio_base, 0x0C03000Fu); + for (i = 0; i < ARRAY_SIZE(k_pinmux_table); i++) + s5l8740_pinmux_apply_word(gpio_base, k_pinmux_table[i]); + + /* EIC mask-all (SEC sub_223C @0x39700080…E0) */ + eic = ioremap(0x39700000ul, 0x100); + if (eic) { + for (i = 0; i <= 6; i++) { + writel(0, eic + 0x80 + 4 * i); + writel(0xffffffffu, eic + 0xa0 + 4 * i); + writel(0, eic + 0xc0 + 4 * i); + writel(0, eic + 0xe0 + 4 * i); + } + iounmap(eic); + } + + s5l8740_pinmux_apply_word(gpio_base, 0x0C041100u); + /* SEC busy(~0x1F4); approximate with short delay */ + udelay(500); + s5l8740_pinmux_apply_word(gpio_base, 0x0C040000u); + + writel(1377685u, gpio_base + 0x380); + writel(1, gpio_base + 0x388); + writel(1, gpio_base + 0x3f4); + writel(1, gpio_base + 0x3e0); + + dev_info(dev, "SEC pinmux sub_223C applied (%u table words)\n", + (unsigned int)ARRAY_SIZE(k_pinmux_table)); +} + +#define S5L8740_NKEYS 2 +/* Re-entries before a key is judged to be misconfigured, not pressed. */ +#define S5L8740_KEY_STORM_MAX 64 + +struct s5l8740_key { + struct s5l8740_gpio *sg; + struct delayed_work release; + const char *name; + unsigned int gpio; + unsigned int code; + int irq; + bool down; + bool masked; + unsigned int storm; +}; + +struct s5l8740_gpio { + void __iomem *base; + void __iomem *gpiocmd; + struct gpio_chip gc; + struct irq_domain *eic_domain; + struct timer_list din_timer; + struct work_struct poweroff_work; + struct input_dev *input; + u8 last40, last41, last86; + bool din_inited; + struct s5l8740_key keys[S5L8740_NKEYS]; + bool keys_on_irq; +}; + +static struct s5l8740_gpio *s5l8740_n31; + +/* ------------------------------------------------------------------ */ +/* Pad-function debugfs */ +/* */ +/* gpiolib covers direction and value, but not the pad function nibble, */ +/* which is what most N31 bring-up questions are actually about: which */ +/* peripheral owns a pin right now. Reading it previously meant mapping */ +/* /dev/mem from userspace, which is an easy way to mistake a tool bug */ +/* for a hardware finding. */ +/* */ +/* pads one line per bank: PCON word then the eight nibbles */ +/* pad_set " " -- 14/15 drive an output low/high, */ +/* 0-7 select a peripheral function, 0xFFFE releases to in */ +/* ------------------------------------------------------------------ */ + +#define S5L8740_GPIO_BANKS 16 +#define S5L8740_GPIO_BANK_STRIDE 32 +#define S5L8740_GPIO_DIR 0x14 +#define S5L8740_GPIO_RELEASE 0xfffe + +static struct dentry *s5l8740_gpio_debugfs; + +static int s5l8740_pads_show(struct seq_file *s, void *unused) +{ + struct s5l8740_gpio *sg = s->private; + unsigned int bank, pin; + + if (!sg || !sg->base) + return -ENODEV; + seq_puts(s, "bank PCON dir pads 0..7 (function nibble)\n"); + for (bank = 0; bank < S5L8740_GPIO_BANKS; bank++) { + void __iomem *b = sg->base + S5L8740_GPIO_BANK_STRIDE * bank; + u32 pcon = readl(b); + u32 dir = readl(b + S5L8740_GPIO_DIR); + + seq_printf(s, "%-4u %08x %08x ", bank, pcon, dir); + for (pin = 0; pin < 8; pin++) + seq_printf(s, "%u%s", (pcon >> (4 * pin)) & 0xf, + pin == 7 ? "" : " "); + seq_printf(s, " (gpio %u-%u)\n", bank * 8, bank * 8 + 7); + } + return 0; +} +DEFINE_SHOW_ATTRIBUTE(s5l8740_pads); + +static ssize_t s5l8740_pad_set_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *ppos) +{ + struct s5l8740_gpio *sg = file_inode(file)->i_private; + char buf[32]; + unsigned int gpio, func, bank, pin; + u32 dir; + + if (!sg || !sg->base || !sg->gpiocmd) + return -ENODEV; + if (len >= sizeof(buf)) + return -EINVAL; + if (copy_from_user(buf, ubuf, len)) + return -EFAULT; + buf[len] = 0; + if (sscanf(buf, "%u %i", &gpio, &func) != 2) + return -EINVAL; + if (gpio >= S5L8740_GPIO_BANKS * 8) + return -EINVAL; + + bank = gpio >> 3; + pin = gpio & 7; + dir = readl(sg->base + S5L8740_GPIO_BANK_STRIDE * bank + + S5L8740_GPIO_DIR); + if (func == S5L8740_GPIO_RELEASE) + dir &= ~BIT(pin); + else + dir |= BIT(pin); + writel(dir, sg->base + S5L8740_GPIO_BANK_STRIDE * bank + + S5L8740_GPIO_DIR); + writel((bank << 16) | (pin << 8) | + (func == S5L8740_GPIO_RELEASE ? 0 : (func & 0xff)), + sg->gpiocmd); + + /* Echo of a debugfs pad write: tracing. */ + dev_dbg(sg->gc.parent, "pad gpio %u (bank %u pin %u) -> func %u\n", + gpio, bank, pin, func); + return len; +} + +static const struct file_operations s5l8740_pad_set_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .write = s5l8740_pad_set_write, + .llseek = noop_llseek, +}; + +static void s5l8740_gpio_debugfs_init(struct s5l8740_gpio *sg) +{ + struct dentry *d = debugfs_create_dir("s5l8740_gpio", NULL); + + if (IS_ERR(d)) + return; + s5l8740_gpio_debugfs = d; + debugfs_create_file("pads", 0444, d, sg, &s5l8740_pads_fops); + debugfs_create_file("pad_set", 0200, d, sg, &s5l8740_pad_set_fops); +} + + +void (*d1830_n31_din_nirq_hook)(void); +EXPORT_SYMBOL_GPL(d1830_n31_din_nirq_hook); + +/* + * Report a key on n31-buttons. Only for keys this driver actually sources; + * PMIC-sourced keys go out on n31-pmic-buttons instead, so that a single + * physical press produces a single event. + */ +void s5l8740_n31_report_key(unsigned int code, int pressed) +{ + if (!s5l8740_n31 || !s5l8740_n31->input) + return; + input_report_key(s5l8740_n31->input, code, pressed); + input_sync(s5l8740_n31->input); +} +EXPORT_SYMBOL_GPL(s5l8740_n31_report_key); + +static void __iomem *s5l8740_bank(struct s5l8740_gpio *sg, unsigned int offset) +{ + return sg->base + S5L8740_GPIO_BANK_STRIDE * (offset >> 3); +} + +/* RetailOS sub_43D38C(gpio, mode, val) */ +static void s5l8740_gpiocmd_mode(struct s5l8740_gpio *sg, unsigned int gpio, + u16 mode, int val) +{ + void __iomem *bank = s5l8740_bank(sg, gpio); + u32 pin = gpio & 7; + unsigned long flags; + u32 dir; + u8 cmd; + + if (gpio == 200) + return; + + if (mode == 1) + cmd = val ? S5L8740_CMD_OUT_HIGH : S5L8740_CMD_OUT_LOW; + else if (mode == 0xFFFE) + cmd = 0; + else + cmd = (u8)mode; + + /* + * DIR follows the mode for everything except 0xFFFE, mode 1 + * included. sub_43D38C picks command 14 or 15 for mode 1 and then + * falls through to the shared "DIR |= bit"; only the 0xFFFE arm + * branches away to "DIR &= ~bit": + * + * if (a2 == 1) { v8 = a3 ? 15 : 14; } + * else { ... if (a2 == 65534) { v9 = DIR & ~v7; goto L10; } } + * v9 = DIR | v7; + * L10: DIR = v9; GPIOCMD = ...; + * + * This used to skip DIR for mode 1, with a comment claiming stock + * did the same. It does not, and the cost was that every output + * this chip drove stayed an input: the Bluetooth controller's + * REG_ON read back "in lo" in debugfs and the part never powered + * up, so hci0 bound to a dead chip and every command timed out. + * + * Eight pads share one DIR word, so this is a read-modify-write + * under the same lock as every other writer to the bank. + */ + raw_spin_lock_irqsave(&s5l8740_gpio_reg_lock, flags); + dir = readl(bank + S5L8740_GPIO_DIR_OFF); + if (mode == 0xFFFE) + dir &= ~BIT(pin); + else + dir |= BIT(pin); + writel(dir, bank + S5L8740_GPIO_DIR_OFF); + writel(((gpio >> 3) << 16) | (pin << 8) | cmd, sg->gpiocmd); + raw_spin_unlock_irqrestore(&s5l8740_gpio_reg_lock, flags); +} + +static int s5l8740_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct s5l8740_gpio *sg = gpiochip_get_data(gc); + u32 din = readl(s5l8740_bank(sg, offset) + S5L8740_GPIO_DIN_OFF); + + return !!(din & BIT(offset & 7)); +} + +static void s5l8740_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct s5l8740_gpio *sg = gpiochip_get_data(gc); + + /* mode==1 — cmd 14/15 only (DIR untouched per sub_43D38C) */ + s5l8740_gpiocmd_mode(sg, offset, 1, value); +} + +static void __maybe_unused s5l8740_button_as_input(struct s5l8740_gpio *sg, + unsigned int gpio, + u32 pinmux_word) +{ + void __iomem *bank = s5l8740_bank(sg, gpio); + u32 pin = gpio & 7; + u32 v; + + /* SEC PCON/PUNB. Bit8 (PUNC/+0x10) is 0 in the table = pull-down; + * Vol± are pull-up active-low, so force +0x0c and +0x10. */ + s5l8740_pinmux_apply_word(sg->base, pinmux_word | 0x00000100u); + v = readl(bank + 0x0c); + writel(v | BIT(pin), bank + 0x0c); + v = readl(bank + 0x10); + writel(v | BIT(pin), bank + 0x10); + s5l8740_gpiocmd_mode(sg, gpio, 0xFFFE, 0); +} + +static int s5l8740_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct s5l8740_gpio *sg = gpiochip_get_data(gc); + + /* Vol± / nIRQ: do not re-pinmux or GPIOCMD 0xFFFE — last image did + * that and DIN never moved. Leave SEC / U-Boot pad state. + */ + if (offset == 40 || offset == 41 || offset == 86) + return 0; + s5l8740_gpiocmd_mode(sg, offset, 0xFFFE, 0); + return 0; +} + +static int s5l8740_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + s5l8740_gpio_set(gc, offset, value); + return 0; +} + +static int s5l8740_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct s5l8740_gpio *sg = gpiochip_get_data(gc); + u32 dir = readl(s5l8740_bank(sg, offset) + S5L8740_GPIO_DIR_OFF); + + if (dir & BIT(offset & 7)) + return GPIO_LINE_DIRECTION_OUT; + return GPIO_LINE_DIRECTION_IN; +} + +static int s5l8740_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) +{ + struct s5l8740_gpio *sg = gpiochip_get_data(gc); + int ret, virq; + + if (!sg->eic_domain) + return -ENXIO; + + /* + * Stock arms an interrupt-capable pad with sub_43D38C(gpio, 0, 1) + * followed by sub_428F70(gpio, 1), which sets the bank's +0x0C bit. + * Without that second step the pad is muxed but its input stage is + * not enabled, so the EIC has nothing to level-detect. + */ + { + void __iomem *b = s5l8740_bank(sg, offset); + unsigned int pin = offset & 7; + + writel(readl(b + S5L8740_GPIO_INEN_OFF) | BIT(pin), + b + S5L8740_GPIO_INEN_OFF); + } + + ret = s5l8740_eic_enable_gpio(offset, IRQ_TYPE_LEVEL_LOW); + if (ret) + return ret; + + virq = irq_create_mapping(sg->eic_domain, offset); + if (!virq) + return -EINVAL; + return virq; +} + +static u8 s5l8740_din_bit(struct s5l8740_gpio *sg, unsigned int gpio) +{ + u32 din = readl(s5l8740_bank(sg, gpio) + S5L8740_GPIO_DIN_OFF); + + return !!(din & BIT(gpio & 7)); +} + +/* + * Pinmux ownership (SEC 223C table + every OSOS 43D38C immediate): + * SEC nibbles are only 0 / 2 / 4 / 14. No IIC/IIS-specific nibble. + * OSOS never GPIOCMDs IIC0/IIC1 — IIC1 works from SEC/WTF leftover. + * OSOS 5714EE is UART pairs func2: (4,5)(78,79)(66,67)(83,84). + * OSOS 20690 is SPI2: 87/5, 88/3, 89/3, 90/3. + * Grape: 14 EN, 39 RST, 38 IRQ. Vol 40/41. nIRQ 86. + * IIS0: OSOS BCB60 GPIOCMD 7 and 20 only (mode 3=on, 2=off). + * IIS1/IIS2: no 43D38C. Do not treat 21-22/49-54/57-63 as IIS. + * IIC0/IIC1: no named SCL/SDA GPIO; no PUNB/PUNC. Clock IIC1 = + * PWRCON1 bit 6 (SEC 2308). Do not invent IIC pulls. + * Do not replay 20690 or 5714EE in GATE0. Do not 0xFFFE IIC/Vol/86. + */ +static void s5l8740_log_pad(struct s5l8740_gpio *sg, unsigned int gpio, + char *out, size_t n) +{ + void __iomem *b = s5l8740_bank(sg, gpio); + u32 pin = gpio & 7; + u32 pcon = readl(b), din = readl(b + 0x04); + u32 dir = readl(b + S5L8740_GPIO_DIR_OFF); + u32 punb = readl(b + 0x0c), punc = readl(b + 0x10); + + snprintf(out, n, "%u:n%x/d%u/i%u/b%u/c%u", gpio, + (pcon >> (4 * pin)) & 0xf, !!(dir & BIT(pin)), + !!(din & BIT(pin)), !!(punb & BIT(pin)), !!(punc & BIT(pin))); +} + +static void s5l8740_log_pads(struct s5l8740_gpio *sg, const char *tag, + const char *what, const unsigned int *gpios, + unsigned int n) +{ + char buf[160]; + unsigned int i, off = 0; + + off = snprintf(buf, sizeof(buf), "n31-btn pinmux %s %s", tag, what); + for (i = 0; i < n && off < sizeof(buf) - 36; i++) { + char p[36]; + + s5l8740_log_pad(sg, gpios[i], p, sizeof(p)); + off += snprintf(buf + off, sizeof(buf) - off, " %s", p); + } + /* + * Pad-state tracing, so dev_dbg rather than dev_err. + * + * This runs nine times for every IIS hw_params. At err level no + * console loglevel can hide it, so a stream start buries whatever + * else was being read at the time. Dynamic debug turns it back on + * per call site when the pinmux is what is under investigation. + */ + dev_dbg(sg->gc.parent, "%s\n", buf); +} + +static void s5l8740_log_pinmux_map(struct s5l8740_gpio *sg, const char *tag) +{ + static const unsigned int keys[] = { 40, 41, 86 }; + static const unsigned int spi2[] = { 87, 88, 89, 90 }; + static const unsigned int nim[] = { 14, 38, 39 }; + static const unsigned int spi0[] = { 0, 1, 2, 3 }; + static const unsigned int uart0[] = { 4, 5 }; + static const unsigned int uart1[] = { 78, 79 }; + static const unsigned int uart2[] = { 66, 67 }; + static const unsigned int uart3p[] = { 83, 84 }; + static const unsigned int iis0[] = { 7, 20 }; + + s5l8740_log_pads(sg, tag, "key", keys, ARRAY_SIZE(keys)); + s5l8740_log_pads(sg, tag, "spi2", spi2, ARRAY_SIZE(spi2)); + s5l8740_log_pads(sg, tag, "nim", nim, ARRAY_SIZE(nim)); + s5l8740_log_pads(sg, tag, "spi0", spi0, ARRAY_SIZE(spi0)); + s5l8740_log_pads(sg, tag, "uartA", uart0, ARRAY_SIZE(uart0)); + s5l8740_log_pads(sg, tag, "uartB", uart1, ARRAY_SIZE(uart1)); + s5l8740_log_pads(sg, tag, "uartC", uart2, ARRAY_SIZE(uart2)); + s5l8740_log_pads(sg, tag, "uartD", uart3p, ARRAY_SIZE(uart3p)); + s5l8740_log_pads(sg, tag, "iis0", iis0, ARRAY_SIZE(iis0)); +} + +static void s5l8740_sec_gpio86(struct s5l8740_gpio *sg) +{ + s5l8740_log_pinmux_map(sg, "before-SEC"); + s5l8740_pinmux_apply_word(sg->base, 0x0A061010u); + /* OSOS BCB60 IIS0 on: 43D38C(20,3) 43D38C(7,3). No IIC GPIOCMD. */ + s5l8740_gpiocmd_mode(sg, 20, 3, 0); + s5l8740_gpiocmd_mode(sg, 7, 3, 0); + s5l8740_log_pinmux_map(sg, "after-SEC-86-iis0"); +} + +/* SEC sub_223C IIS0 ASP pins: func2 + DIR (table @ 0x22004C6C). */ +static void s5l8740_iis0_pinmux_sec(struct s5l8740_gpio *sg) +{ + s5l8740_pinmux_apply_word(sg->base, 0x00061002u); /* GPIO6 BCLK? */ + s5l8740_pinmux_apply_word(sg->base, 0x00071002u); /* GPIO7 */ + s5l8740_pinmux_apply_word(sg->base, 0x02041002u); /* GPIO20 */ +} + +/* + * OSOS BCB60 IIS0 pad enable. mode 3=on, 2=off (BCB60 teardown). + * Re-applies SEC func2 on 6/7/20 then GPIOCMD. + */ +void s5l8740_iis0_pads_enable(unsigned int mode) +{ + struct s5l8740_gpio *sg = s5l8740_n31; + u16 m = mode ? mode : 3; + + if (!sg) + return; + s5l8740_iis0_pinmux_sec(sg); + s5l8740_gpiocmd_mode(sg, 20, m, 0); + s5l8740_gpiocmd_mode(sg, 7, m, 0); +} +EXPORT_SYMBOL_GPL(s5l8740_iis0_pads_enable); + +void s5l8740_iis0_pads_disable(void) +{ + s5l8740_iis0_pads_enable(2); +} +EXPORT_SYMBOL_GPL(s5l8740_iis0_pads_disable); + +void s5l8740_iis0_pad6_enable(unsigned int mode) +{ + struct s5l8740_gpio *sg = s5l8740_n31; + u16 m = mode ? mode : 3; + + if (!sg) + return; + s5l8740_pinmux_apply_word(sg->base, 0x00061002u); + s5l8740_gpiocmd_mode(sg, 6, m, 0); +} +EXPORT_SYMBOL_GPL(s5l8740_iis0_pad6_enable); + +/* + * Set one pad's function nibble and direction, and nothing else. + * + * Audio checkpoint-010 records the stock pad state while RetailOS plays: + * bank0 PCON 0x32112224 / DIR 0xFF and bank2 PCON 0x02230000 / DIR 0x70, + * which is GPIO6 func2, GPIO7 func3, GPIO20 func3, GPIO21 func2 and GPIO22 + * func2, all outputs. Linux sets 7 and 20 and leaves 6, 21 and 22 alone. + * + * That matters because everything upstream now matches the oracle exactly + * -- TXCON, TXCOM, CLKDIV 272, CLKCON +0x18 and +0x1C, IIS STATUS, the + * PL080 channel -- and the jack is still silent. Clocks and status can all + * be right while the serialiser's data pin is not muxed out of the SoC. + * + * A whole-word PCON write would take pins 0..5 with it, which is why this + * is per-pad read-modify-write. The doc is explicit that GPIO6 must not go + * through GPIOCMD, so the nibble is written directly. + */ +int s5l8740_gpio_set_pad(unsigned int gpio, unsigned int func, bool out) +{ + struct s5l8740_gpio *sg = s5l8740_n31; + void __iomem *bank; + unsigned int pin = gpio & 7; + unsigned long flags; + u32 v; + + if (!sg || !sg->base || func > 15) + return -ENODEV; + bank = sg->base + (gpio >> 3) * S5L8740_GPIO_BANK_STRIDE; + + raw_spin_lock_irqsave(&s5l8740_gpio_reg_lock, flags); + v = readl(bank + S5L8740_GPIO_PCON_OFF); + v &= ~(0xfu << (pin * 4)); + v |= (func & 0xf) << (pin * 4); + writel(v, bank + S5L8740_GPIO_PCON_OFF); + + v = readl(bank + S5L8740_GPIO_DIR_OFF); + if (out) + v |= BIT(pin); + else + v &= ~BIT(pin); + writel(v, bank + S5L8740_GPIO_DIR_OFF); + raw_spin_unlock_irqrestore(&s5l8740_gpio_reg_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(s5l8740_gpio_set_pad); + +/* + * Set one pad's direction bit, and nothing else, under the same lock. + * + * PCON, DIR, PUNB and PUNC are per-bank: eight pads share one word, so a + * read-modify-write on any of them can drop another pad's bit if two + * owners race. There were two owners -- this driver and s5l8740-i2s.c, + * which was doing its own unlocked RMW on DIR for the IIS pads. Exporting + * this gives the audio driver a way to set what it needs without becoming + * a second writer to a register it does not own. + */ +int s5l8740_gpio_dir_set(unsigned int gpio, bool out); +int s5l8740_gpio_dir_set(unsigned int gpio, bool out) +{ + struct s5l8740_gpio *sg = s5l8740_n31; + unsigned int pin = gpio & 7; + unsigned long flags; + void __iomem *bank; + u32 v; + + if (!sg || !sg->base) + return -ENODEV; + bank = sg->base + (gpio >> 3) * S5L8740_GPIO_BANK_STRIDE; + + raw_spin_lock_irqsave(&s5l8740_gpio_reg_lock, flags); + v = readl(bank + S5L8740_GPIO_DIR_OFF); + if (out) + v |= BIT(pin); + else + v &= ~BIT(pin); + writel(v, bank + S5L8740_GPIO_DIR_OFF); + raw_spin_unlock_irqrestore(&s5l8740_gpio_reg_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(s5l8740_gpio_dir_set); + +void s5l8740_gpio_log_iis0_pads(const char *tag) +{ + struct s5l8740_gpio *sg = s5l8740_n31; + + if (!sg) + return; + s5l8740_log_pinmux_map(sg, tag ? tag : "iis0"); +} +EXPORT_SYMBOL_GPL(s5l8740_gpio_log_iis0_pads); + +int s5l8740_n31_din86(void) +{ + if (!s5l8740_n31) + return -1; + return s5l8740_din_bit(s5l8740_n31, 86); +} +EXPORT_SYMBOL_GPL(s5l8740_n31_din86); + +static void s5l8740_poweroff_work(struct work_struct *work) +{ + if (pm_power_off) + pm_power_off(); +} + +static void s5l8740_key_edge(struct s5l8740_gpio *sg, unsigned int code, + u8 now, u8 *last, const char *name) +{ + if (now == *last) + return; + if (sg->input) { + /* Active-low pad: 0 = pressed */ + input_report_key(sg->input, code, now ? 0 : 1); + input_sync(sg->input); + } + /* One line per key edge: tracing, not an error. */ + dev_dbg(sg->gc.parent, "n31-btn %s %s din=%u\n", + name, now ? "release" : "PRESS", now); + *last = now; +} + +static void s5l8740_din_timer(struct timer_list *t) +{ + struct s5l8740_gpio *sg = container_of(t, struct s5l8740_gpio, din_timer); + u8 v40 = s5l8740_din_bit(sg, 40); + u8 v41 = s5l8740_din_bit(sg, 41); + u8 v86 = s5l8740_din_bit(sg, 86); + + if (!sg->din_inited) { + sg->last40 = v40; + sg->last41 = v41; + sg->last86 = v86; + sg->din_inited = true; + } else { + /* OSOS GPIOButtonManager: only GPIO 40/41. Home/Play/Sleep + * are PMIC bits; GPIO 86 is the nIRQ doorbell into d1830. + */ + if (!sg->keys_on_irq) { + s5l8740_key_edge(sg, KEY_VOLUMEUP, v40, + &sg->last40, "VOL+"); + s5l8740_key_edge(sg, KEY_VOLUMEDOWN, v41, + &sg->last41, "VOL-"); + } + if (v86 != sg->last86) { + dev_dbg(sg->gc.parent, "n31-btn NIRQ86 %u->%u\n", + sg->last86, v86); + sg->last86 = v86; + if (d1830_n31_din_nirq_hook) + d1830_n31_din_nirq_hook(); + } + } + + mod_timer(&sg->din_timer, jiffies + msecs_to_jiffies(50)); +} + +/* ------------------------------------------------------------------ */ +/* Volume keys on real interrupts */ +/* */ +/* The EIC only offers level-low, so a held key would re-assert forever */ +/* and genirq would retire the line as spurious -- the same failure the */ +/* PMIC event latches caused. The shape that works on a level-only */ +/* irqchip is: take the interrupt for the press, mask the line, then */ +/* poll only while the key is down, and unmask on release. */ +/* */ +/* So the press is interrupt-driven, which is the part latency is */ +/* visible in, and polling exists only for the tens of milliseconds a */ +/* finger is actually on the button instead of forever at 50 ms. */ +/* ------------------------------------------------------------------ */ + +/* + * Off until the EIC level semantics are understood. The VIC routing is + * now correct, so enabling this actually delivers interrupts -- and an + * idle active-low pad reads as permanently asserted, which wedges the + * system. The sweep is slower but it works. + */ +static bool btn_irq; +module_param(btn_irq, bool, 0444); +MODULE_PARM_DESC(btn_irq, + "Drive the volume keys from EIC interrupts (default Y)"); + +static unsigned int btn_release_ms = 30; +module_param(btn_release_ms, uint, 0644); +MODULE_PARM_DESC(btn_release_ms, + "Poll interval while a key is held, waiting for release"); + +static irqreturn_t s5l8740_key_isr(int irq, void *data) +{ + struct s5l8740_key *k = data; + struct s5l8740_gpio *sg = k->sg; + + /* + * Mask before reporting. The pad stays low for as long as the key is + * held, so leaving it unmasked here is an instant interrupt storm. + */ + if (!k->masked) { + disable_irq_nosync(irq); + k->masked = true; + } + /* + * If the line keeps re-asserting with nobody touching the key, the + * polarity or routing is wrong and re-enabling would spin here + * forever, taking userspace down with it. Give up instead and say + * so; the sweep still reports the key. + */ + if (++k->storm > S5L8740_KEY_STORM_MAX) { + pr_warn_once("n31-btn %s: runaway interrupt, left masked\n", + k->name); + k->sg->keys_on_irq = false; + return IRQ_HANDLED; + } + if (!k->down) { + k->down = true; + if (sg->input) { + input_report_key(sg->input, k->code, 1); + input_sync(sg->input); + } + dev_dbg(sg->gc.parent, "n31-btn %s PRESS (irq)\n", k->name); + } + schedule_delayed_work(&k->release, + msecs_to_jiffies(btn_release_ms)); + return IRQ_HANDLED; +} + +static void s5l8740_key_release_work(struct work_struct *work) +{ + struct s5l8740_key *k = container_of(to_delayed_work(work), + struct s5l8740_key, release); + struct s5l8740_gpio *sg = k->sg; + + /* Pad is active low, so a 1 here means the key came back up. */ + if (!s5l8740_din_bit(sg, k->gpio)) { + schedule_delayed_work(&k->release, + msecs_to_jiffies(btn_release_ms)); + return; + } + k->storm = 0; + if (k->down) { + k->down = false; + if (sg->input) { + input_report_key(sg->input, k->code, 0); + input_sync(sg->input); + } + dev_dbg(sg->gc.parent, "n31-btn %s release\n", k->name); + } + if (k->masked) { + k->masked = false; + enable_irq(k->irq); + } +} + +/* + * Returns the number of keys successfully wired. The caller keeps the + * legacy sweep running if this is not the full set, so a partial or + * failed setup degrades to the old behaviour instead of losing input. + */ +static unsigned int s5l8740_keys_irq_init(struct s5l8740_gpio *sg) +{ + static const struct { + unsigned int gpio, code; + const char *name; + } want[] = { + { 40, KEY_VOLUMEUP, "VOL+" }, + { 41, KEY_VOLUMEDOWN, "VOL-" }, + }; + unsigned int i, ok = 0; + + if (!btn_irq || !sg->eic_domain) + return 0; + + for (i = 0; i < ARRAY_SIZE(want) && i < S5L8740_NKEYS; i++) { + struct s5l8740_key *k = &sg->keys[i]; + int virq; + + k->sg = sg; + k->gpio = want[i].gpio; + k->code = want[i].code; + k->name = want[i].name; + INIT_DELAYED_WORK(&k->release, s5l8740_key_release_work); + + virq = s5l8740_gpio_to_irq(&sg->gc, k->gpio); + if (virq <= 0) { + dev_info(sg->gc.parent, + "key %s: no EIC irq (%d), staying on the sweep\n", + k->name, virq); + continue; + } + k->irq = virq; + if (devm_request_irq(sg->gc.parent, virq, s5l8740_key_isr, + IRQF_TRIGGER_LOW | IRQF_SHARED, + k->name, k)) { + dev_info(sg->gc.parent, + "key %s: irq %d busy, staying on the sweep\n", + k->name, virq); + k->irq = 0; + continue; + } + ok++; + dev_info(sg->gc.parent, "key %s on irq %d (EIC level-low)\n", + k->name, virq); + } + return ok; +} + +static struct irq_domain *s5l8740_gpio_find_eic_domain(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct device_node *eic_np = NULL; + struct irq_domain *domain = NULL; + + /* Preferred: DT phandle apple,eic = <&eic> on the gpio node */ + if (np) + eic_np = of_parse_phandle(np, "apple,eic", 0); + + if (!eic_np && np) + eic_np = of_parse_phandle(np, "interrupt-parent", 0); + + if (!eic_np && np) + eic_np = of_irq_find_parent(np); + + if (!eic_np) + eic_np = of_find_compatible_node(NULL, NULL, "apple,s5l8740-eic"); + + if (!eic_np) + eic_np = of_find_compatible_node(NULL, NULL, "samsung,s5l8740-eic"); + + if (eic_np) { + domain = irq_find_host(eic_np); + of_node_put(eic_np); + } + + return domain; +} + +static int s5l8740_gpio_probe(struct platform_device *pdev) +{ + struct s5l8740_gpio *sg; + struct device *dev = &pdev->dev; + struct resource *res; + u32 ngpios = S5L8740_GPIO_DEFAULT_NGPIO; + int ret; + + sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); + if (!sg) + return -ENOMEM; + + sg->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sg->base)) + return PTR_ERR(sg->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res && resource_size(res) > S5L8740_GPIOCMD_OFF) + sg->gpiocmd = sg->base + S5L8740_GPIOCMD_OFF; + else + sg->gpiocmd = devm_ioremap(dev, 0x3cf001e0, 4); + if (!sg->gpiocmd) + return -ENOMEM; + + of_property_read_u32(dev->of_node, "ngpios", &ngpios); + + sg->eic_domain = s5l8740_gpio_find_eic_domain(dev); + if (!sg->eic_domain) + dev_warn(dev, "EIC irq domain not found — to_irq unavailable\n"); + + sg->gc.label = dev_name(dev); + sg->gc.parent = dev; + sg->gc.owner = THIS_MODULE; + sg->gc.base = -1; + sg->gc.ngpio = ngpios; + sg->gc.get = s5l8740_gpio_get; + sg->gc.set = s5l8740_gpio_set; + sg->gc.direction_input = s5l8740_gpio_direction_input; + sg->gc.direction_output = s5l8740_gpio_direction_output; + sg->gc.get_direction = s5l8740_gpio_get_direction; + sg->gc.to_irq = s5l8740_gpio_to_irq; + /* Do not set gc.irq.* without a full gpio irqchip — to_irq alone. */ + + ret = devm_gpiochip_add_data(dev, &sg->gc, sg); + if (ret) { + dev_err(dev, "gpiochip_add failed: %d\n", ret); + return ret; + } + + /* Re-apply SEC pinmux so WTF/U-Boot leftovers match cold-boot */ + if (!of_property_read_bool(dev->of_node, "apple,skip-sec-pinmux")) + s5l8740_pinmux_223C(dev, sg->base); + else + s5l8740_sec_gpio86(sg); + + /* Vol± stay as SEC/U-Boot left them. GPIO 86 got its SEC word only. */ + sg->input = devm_input_allocate_device(dev); + if (sg->input) { + sg->input->name = "n31-buttons"; + sg->input->phys = "s5l8740/gpio"; + sg->input->dev.parent = dev; + sg->input->id.bustype = BUS_HOST; + /* + * Vol+/Vol- only. Home, Sleep and Play are PMIC-sourced and + * are reported on n31-pmic-buttons; declaring them here too + * made one press look like two events to userspace. + */ + input_set_capability(sg->input, EV_KEY, KEY_VOLUMEUP); + input_set_capability(sg->input, EV_KEY, KEY_VOLUMEDOWN); + if (input_register_device(sg->input)) + sg->input = NULL; + } + + INIT_WORK(&sg->poweroff_work, s5l8740_poweroff_work); + /* + * GPIO 86 is the PMIC doorbell and still needs watching, so the + * sweep runs either way; it just stops carrying the volume keys + * once they are on interrupts. + */ + sg->keys_on_irq = s5l8740_keys_irq_init(sg) == S5L8740_NKEYS; + dev_info(dev, "volume keys: %s\n", + sg->keys_on_irq ? "EIC interrupts" : "50 ms sweep"); + timer_setup(&sg->din_timer, s5l8740_din_timer, 0); + mod_timer(&sg->din_timer, jiffies + msecs_to_jiffies(50)); + s5l8740_n31 = sg; + platform_set_drvdata(pdev, sg); + s5l8740_gpio_debugfs_init(sg); + + dev_info(dev, "S5L8740 GPIO @%pR ngpios=%u (GPIOCMD @+0x1E0) eic=%s\n", + res, ngpios, sg->eic_domain ? "yes" : "no"); + return 0; +} + +static void s5l8740_gpio_remove(struct platform_device *pdev) +{ + struct s5l8740_gpio *sg = platform_get_drvdata(pdev); + + if (!sg) + return; + s5l8740_n31 = NULL; + timer_delete_sync(&sg->din_timer); + cancel_work_sync(&sg->poweroff_work); +} + +static const struct of_device_id s5l8740_gpio_of_match[] = { + { .compatible = "apple,s5l8740-gpio" }, + { .compatible = "samsung,s5l8740-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_gpio_of_match); + +static struct platform_driver s5l8740_gpio_driver = { + .probe = s5l8740_gpio_probe, + .remove = s5l8740_gpio_remove, + .driver = { + .name = "gpio-s5l8740", + .of_match_table = s5l8740_gpio_of_match, + }, +}; +module_platform_driver(s5l8740_gpio_driver); + +MODULE_DESCRIPTION("Samsung/Apple S5L8740 banked GPIO + GPIOCMD + EIC to_irq"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/pinmux_table.inc b/drivers/gpio/pinmux_table.inc new file mode 100644 index 00000000000000..da7a05477637f6 --- /dev/null +++ b/drivers/gpio/pinmux_table.inc @@ -0,0 +1,25 @@ +/* extracted from N31.bootloader.dec.bin @ VA 0x22004C6C — IpodSec sub_223C */ +static const u32 k_pinmux_table[] = { + 0x00001004u, 0x00011002u, 0x00021002u, 0x00031002u, 0x0004100Eu, 0x0005100Eu, + 0x00061002u, 0x00071002u, /* GPIO 6/7 func2 DIR — not buttons (LCD/UART pads) */ + 0x01000000u, 0x01010000u, 0x01021002u, 0x01031002u, /* GPIO 11 = last: func2 DIR */ + 0x01040000u, 0x01050000u, 0x0106000Eu, 0x0107100Eu, 0x02000000u, 0x02010000u, + 0x02020000u, 0x02030000u, 0x02041002u, 0x02051002u, 0x02061002u, 0x02070000u, + 0x03000000u, 0x03010000u, 0x03020000u, 0x03030000u, 0x03040000u, 0x03050000u, + 0x03060000u, 0x03070000u, 0x04000000u, 0x04010000u, 0x04020000u, 0x04030000u, + 0x04040000u, 0x04050000u, 0x04061010u, 0x0407100Eu, 0x05001010u, 0x05011010u, + 0x05021002u, 0x05030000u, 0x05040000u, 0x05050000u, 0x05060000u, 0x05070000u, + 0x06000000u, 0x06011002u, 0x06021002u, 0x06031002u, 0x06041002u, 0x06051002u, + 0x06061002u, 0x06070000u, 0x07000000u, 0x07011002u, 0x07021002u, 0x07031002u, + 0x07041002u, 0x07051002u, 0x07061002u, 0x07071002u, 0x08001002u, 0x08010000u, + 0x08020000u, 0x08030000u, 0x08041010u, 0x08050000u, 0x08060000u, 0x08070000u, + 0x09000000u, 0x09010000u, 0x09020000u, 0x09030000u, 0x09040000u, 0x09050000u, + 0x09060000u, 0x09070000u, 0x0A000000u, 0x0A010000u, 0x0A020000u, 0x0A031002u, + 0x0A041002u, 0x0A051010u, 0x0A061010u, 0x0A070100u, 0x0B00000Eu, 0x0B01000Eu, + 0x0B020000u, 0x0B030000u, 0x0B040000u, 0x0B050000u, 0x0B060000u, 0x0B070000u, + 0x0C000000u, 0x0C010000u, 0x0C020000u, 0x0C03000Eu, 0x0C041010u, 0x0C050000u, + 0x0C060000u, 0x0C070000u, 0x0D000000u, 0x0D010000u, 0x0D020000u, 0x0D030000u, + 0x0D040000u, 0x0D050000u, 0x0D060000u, 0x0D070000u, 0x0E000000u, 0x0E010000u, + 0x0E020000u, 0x0E030000u, 0x0E040000u, 0x0E050000u, 0x0E060000u, 0x0E070000u, + 0x00000000u, +}; diff --git a/drivers/gpu/drm/tiny/s5l8740.c b/drivers/gpu/drm/tiny/s5l8740.c old mode 100644 new mode 100755 index 696bda7ee58a65..f5d912492d287e --- a/drivers/gpu/drm/tiny/s5l8740.c +++ b/drivers/gpu/drm/tiny/s5l8740.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include +#include +#include #include #include @@ -11,7 +14,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -23,6 +29,7 @@ #include #include #include +#include #define S5L8740_LCD_CON 0x00 /* Control register. */ #define S5L8740_LCD_WCMD 0x04 /* Write command register. */ @@ -38,7 +45,35 @@ #define S5L8740_LCD_STATUS_BUSY 0x10 -#define S5L8740_LCD_TIMEOUT_US 1 +/* GATE0: 1us was too short (stride/FIFO); 100ms wait, pitch-aware blit */ +#define S5L8740_LCD_TIMEOUT_US 100000 + +/* + * Transfer framing. + * + * A transfer is not just a run of pixel writes. Stock brackets it, and + * sub_A25C0 is the whole shape of it: + * + * if (MEMORY[0x3830008C] << 30) return -1; busy: bits 0-1 set + * MEMORY[0x38300080] = 1; open + * sub_AEFAC(0, 0, w, h); window, then pixels + * do sub_345D68(); while (!v3); settle + * MEMORY[0x38300080] = 0; close + * + * and sub_A96E4() is the bare wait, "while (MEMORY[0x3830008C] << 30);" + * -- a left shift of 30 tests bits 0 and 1, so nonzero means busy. + * + * Without the bracket the panel is handed a stream that neither begins + * nor ends where it expects, so its write pointer does not come back to + * the origin and the next frame starts from wherever the last one + * stopped. Rows stay intact and the image walks up and down the screen, + * which is exactly what this driver did while every DSI window command + * was reporting success. + */ +#define S5L8740_LCD_XFER 0x80 /* 1 = transfer open */ +#define S5L8740_LCD_XSTAT 0x8c /* bits 0-1 busy */ +#define S5L8740_LCD_XSTAT_BUSY 0x3 +#define S5L8740_LCD_XFER_TIMEOUT_US 100000 #define WIDTH 240 #define HEIGHT 432 @@ -56,6 +91,13 @@ /* memory management */ void __iomem *lcdif; + void __iomem *dsi; /* MIPI DSI host; NULL = no windowing */ + void __iomem *clkcon; /* gates cycled across an LCDIF reset */ + + /* display power */ + struct mutex power_lock; + bool powered; + bool rail_held; /* modesetting */ uint32_t formats[8]; @@ -66,6 +108,39 @@ struct drm_connector connector; }; +/* + * MIPI DSI host, a separate block from the LCDIF. + * + * The panel is a DCS command-mode panel with full windowed addressing. + * Before each transfer stock sends set_column_address, set_page_address and + * write_memory_start through this host, and only then streams pixels into + * LCDIF. RetailOS never writes LCD_WDATA without doing that first. + * + * Verified in the decomp. The short and long paths genuinely wait on the + * status register differently, which is not a transcription slip: + * + * short: MEMORY[0x3D800034] = dt & 0x3F | (p0 << 8) | (p1 << 16); + * wait until (status & 0x400000) == 0 bit 22 clears + * long: MEMORY[0x3D800038] = payload words, LSB first; + * MEMORY[0x3D800034] = dt & 0x3F | (len << 8); + * wait until (status & 0x500000) == 0x500000 bits 20+22 set + */ +#define S5L8740_DSI_CTL 0x10 +#define S5L8740_DSI_CTL_LONG_GATE BIT(28) +#define S5L8740_DSI_HDR 0x34 +#define S5L8740_DSI_PAYLOAD 0x38 +#define S5L8740_DSI_STATUS 0x44 +#define S5L8740_DSI_ST_SHORT 0x400000u +#define S5L8740_DSI_ST_LONG 0x500000u +#define S5L8740_DSI_TIMEOUT_US 2000 + +#define S5L8740_DSI_DT_DCS_SHORT_0P 0x05 +#define S5L8740_DSI_DT_GEN_LONG 0x29 + +#define S5L8740_DCS_SET_COLUMN 0x2a +#define S5L8740_DCS_SET_PAGE 0x2b +#define S5L8740_DCS_WRITE_START 0x2c + static inline void s5l8740_lcd_writel(struct s5l8740_device *lcd_dev, u32 reg, u32 val) { @@ -92,44 +167,322 @@ static int s5l8740_primary_plane_helper_atomic_check(struct drm_plane *plane, st false, false); } -static void s5l8740_primary_plane_helper_atomic_update(struct drm_plane *plane, - struct drm_atomic_state *state) +/* + * The stock sequence waits between enabling the rail and touching the + * LCDIF. That wait is a thunk into ROM, so its length is not recoverable; + * this is a conservative stand-in, tunable if the panel proves fussy. + */ +/* + * Send the whole panel on every update, the way stock does, rather than + * only the rectangles DRM reports as damaged. + * + * Off is faster and was the default while the drift was being chased. It + * is kept because it is the one knob that reproduces the fault, and a + * regression here is otherwise hard to tell from a slow frame. + */ +static bool lcd_partial; +module_param(lcd_partial, bool, 0644); +MODULE_PARM_DESC(lcd_partial, + "Send only damaged rectangles instead of the whole panel"); + +/* + * Bracket each update with the LCDIF transfer gate. Off reproduces the + * unframed streaming this driver did before, for comparison. + */ +static bool lcd_frame_xfer = true; +module_param(lcd_frame_xfer, bool, 0644); +MODULE_PARM_DESC(lcd_frame_xfer, + "Open and close the LCDIF transfer around each update (default Y)"); + +/* Wait for the interface to leave a transfer. */ +static int s5l8740_xfer_idle(struct s5l8740_device *sdev) { - struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); - struct drm_framebuffer *fb = plane_state->fb; - struct drm_device *dev = plane->dev; - struct s5l8740_device *sdev = s5l8740_device_of_dev(dev); - int idx; + u32 st; + + return readl_poll_timeout_atomic(sdev->lcdif + S5L8740_LCD_XSTAT, st, + !(st & S5L8740_LCD_XSTAT_BUSY), 0, + S5L8740_LCD_XFER_TIMEOUT_US); +} + +static int s5l8740_xfer_open(struct s5l8740_device *sdev) +{ + int ret; + + if (!lcd_frame_xfer) + return 0; + /* + * Opening on top of a transfer still running is what stock refuses + * outright, returning -1 rather than queueing behind it. + */ + ret = s5l8740_xfer_idle(sdev); + if (ret) + return ret; + writel(1, sdev->lcdif + S5L8740_LCD_XFER); + return 0; +} - if (!fb || drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) - return; +static void s5l8740_xfer_close(struct s5l8740_device *sdev) +{ + if (!lcd_frame_xfer) + return; + /* + * Let the interface drain before dropping the gate. Closing under a + * transfer truncates it, which leaves the panel mid-row and is the + * failure this framing exists to prevent. + */ + s5l8740_xfer_idle(sdev); + writel(0, sdev->lcdif + S5L8740_LCD_XFER); +} - if (!drm_dev_enter(dev, &idx)) - goto out_drm_gem_fb_end_cpu_access; +/* Short DCS write, no parameters. */ +static int s5l8740_dsi_short(struct s5l8740_device *sdev, u8 dt, u8 cmd) +{ + u32 st; + + if (!sdev->dsi) + return -ENODEV; + writel((dt & 0x3f) | (cmd << 8), sdev->dsi + S5L8740_DSI_HDR); + /* + * Flush the header write and let the host react before polling. + * + * The done bits are set while the host is idle -- measured + * 0x0155541D with nothing in flight -- so they only mean anything + * after issuing a command has cleared them. Polling straight after a + * posted write reads the stale idle value, returns success + * immediately, and the next command then pushes its payload over one + * still in flight. + * + * That is what put the panel window wrong in one axis only: the + * column write survived because setting up the page write gave it + * time, and the page write was clobbered by write_memory_start + * arriving on its heels. Horizontal placement correct, vertical + * placement wherever the previous command happened to leave it. + */ + readl(sdev->dsi + S5L8740_DSI_STATUS); + udelay(1); + /* + * Wait for the bit to SET, not clear. + * + * The two waits stock uses are opposites and it is easy to read one + * as the other: + * + * sub_3D11B0(r, m, v) while ((*r & m) == v) -- until NOT equal + * sub_714C (r, m, v) while ((*r & m) != v) -- until equal + * + * The short path calls sub_3D11B0(status, 0x400000, 0), so it waits + * until bit 22 is no longer zero. Waiting for it to clear instead + * hangs forever: the bit reads 1 at idle -- measured 0x0155541D on + * the glass -- and only drops while a command is in flight. + */ + return readl_poll_timeout_atomic(sdev->dsi + S5L8740_DSI_STATUS, st, + st & S5L8740_DSI_ST_SHORT, 0, + S5L8740_DSI_TIMEOUT_US); +} - unsigned int count = fb->width * fb->height; - int *src = shadow_plane_state->data[0].vaddr; +/* Long write: payload first, LSB-first into the FIFO, then the header. */ +static int s5l8740_dsi_long(struct s5l8740_device *sdev, u8 dt, + const u8 *buf, unsigned int len) +{ + unsigned int i; + u32 w = 0, st; + + if (!sdev->dsi) + return -ENODEV; + writel(readl(sdev->dsi + S5L8740_DSI_CTL) & ~S5L8740_DSI_CTL_LONG_GATE, + sdev->dsi + S5L8740_DSI_CTL); + + for (i = 0; i < len; i++) { + w |= (u32)buf[i] << (8 * (i & 3)); + if ((i & 3) == 3) { + writel(w, sdev->dsi + S5L8740_DSI_PAYLOAD); + w = 0; + } + } + if (len & 3) + writel(w, sdev->dsi + S5L8740_DSI_PAYLOAD); + + writel((dt & 0x3f) | (len << 8), sdev->dsi + S5L8740_DSI_HDR); + /* + * Flush the header write and let the host react before polling. + * + * The done bits are set while the host is idle -- measured + * 0x0155541D with nothing in flight -- so they only mean anything + * after issuing a command has cleared them. Polling straight after a + * posted write reads the stale idle value, returns success + * immediately, and the next command then pushes its payload over one + * still in flight. + * + * That is what put the panel window wrong in one axis only: the + * column write survived because setting up the page write gave it + * time, and the page write was clobbered by write_memory_start + * arriving on its heels. Horizontal placement correct, vertical + * placement wherever the previous command happened to leave it. + */ + readl(sdev->dsi + S5L8740_DSI_STATUS); + udelay(1); + return readl_poll_timeout_atomic(sdev->dsi + S5L8740_DSI_STATUS, st, + (st & S5L8740_DSI_ST_LONG) == + S5L8740_DSI_ST_LONG, 0, + S5L8740_DSI_TIMEOUT_US); +} - for (int i = 0; i < count; i++) { - int ret; - u32 status; +/* Point the panel at one rectangle and rewind its write pointer. */ +static int s5l8740_dsi_window(struct s5l8740_device *sdev, + u32 x, u32 y, u32 w, u32 h) +{ + u8 col[5] = { S5L8740_DCS_SET_COLUMN, (x >> 8) & 0xff, x & 0xff, + ((x + w - 1) >> 8) & 0xff, (x + w - 1) & 0xff }; + u8 row[5] = { S5L8740_DCS_SET_PAGE, (y >> 8) & 0xff, y & 0xff, + ((y + h - 1) >> 8) & 0xff, (y + h - 1) & 0xff }; + int ret; - ret = readl_poll_timeout_atomic(sdev->lcdif + S5L8740_LCD_STATUS, status, - !(status & S5L8740_LCD_STATUS_BUSY), 0, S5L8740_LCD_TIMEOUT_US); + ret = s5l8740_dsi_long(sdev, S5L8740_DSI_DT_GEN_LONG, col, sizeof(col)); + if (ret) + return ret; + ret = s5l8740_dsi_long(sdev, S5L8740_DSI_DT_GEN_LONG, row, sizeof(row)); + if (ret) + return ret; - if (unlikely(ret)) { - drm_warn(dev, "S5L8740_LCD_STATUS_BUSY timeout\n"); - goto out_drm_dev_exit; - } + return s5l8740_dsi_short(sdev, S5L8740_DSI_DT_DCS_SHORT_0P, + S5L8740_DCS_WRITE_START); +} - s5l8740_lcd_writel(sdev, S5L8740_LCD_WDATA, src[i]); - } +/* + * Point the panel at one rectangle and stream it. + * + * Returns nonzero only when the interface stopped answering, which costs + * the rest of the frame; a window command that will not take is not fatal + * and falls through to the pixels, since the panel keeps whatever window + * it had and one crooked frame beats a frozen display. + */ +static int s5l8740_blit(struct s5l8740_device *sdev, const u32 *src, + unsigned int pitch_px, const struct drm_rect *clip) +{ + unsigned int x, y; + + if (sdev->dsi && + s5l8740_dsi_window(sdev, clip->x1, clip->y1, + drm_rect_width(clip), drm_rect_height(clip))) + drm_warn_once(&sdev->dev, "DSI window timeout\n"); + + for (y = clip->y1; y < (unsigned int)clip->y2; y++) { + const u32 *row = src + y * pitch_px; + + for (x = clip->x1; x < (unsigned int)clip->x2; x++) { + u32 status; + + if (readl_poll_timeout_atomic(sdev->lcdif + + S5L8740_LCD_STATUS, + status, + !(status & S5L8740_LCD_STATUS_BUSY), + 0, S5L8740_LCD_TIMEOUT_US)) { + drm_warn_once(&sdev->dev, + "LCDIF stayed busy; frame dropped\n"); + return -ETIMEDOUT; + } + + s5l8740_lcd_writel(sdev, S5L8740_LCD_WDATA, row[x]); + } + } + + return 0; +} -out_drm_dev_exit: - drm_dev_exit(idx); -out_drm_gem_fb_end_cpu_access: - drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +static void s5l8740_primary_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_shadow_plane_state *shadow = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_device *dev = plane->dev; + struct s5l8740_device *sdev = s5l8740_device_of_dev(dev); + unsigned int pitch_px; + const u32 *src; + int idx; + + if (!fb || drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) + return; + + /* + * Never push pixels at a stopped interface. Each write waits on the + * status register, so a full frame against a powered-down LCDIF would + * stall for the timeout on every one of them. The panel is repainted + * by n31_lcd_power() once it is running again. + */ + if (!READ_ONCE(sdev->powered)) + goto out_end_cpu_access; + + if (!drm_dev_enter(dev, &idx)) + goto out_end_cpu_access; + + src = shadow->data[0].vaddr; + pitch_px = fb->pitches[0] / 4; + if (!pitch_px) + pitch_px = fb->width; + + /* + * Open the transfer before any command or pixel goes out, and close it + * once, after the last rectangle. The bracket belongs around the whole + * update rather than around each rectangle: stock opens once, blits, + * and closes. + */ + if (s5l8740_xfer_open(sdev)) { + drm_warn_once(dev, "LCDIF busy; update skipped\n"); + goto out_dev_exit; + } + + /* + * Stock refreshes the panel exactly one way. From sub_A25C0: + * + * if (MEMORY[0x3830008C] << 30) return -1; busy + * MEMORY[0x38300080] = 1; open + * sub_AEFAC(0, 0, w, h); window + * do sub_345D68(); while (!v3); settle + * MEMORY[0x38300080] = 0; close + * + * and sub_AEFAC is one instruction, "b.w sub_75D44" -- a tail jump + * into the DCS window setter. So the window is (0, 0, w, h), the whole + * panel, on every refresh. The firmware has no partial-window path at + * all. + * + * That is why damage-clipped updates walked up and down the screen. + * The LCDIF is programmed once at init with the panel geometry, + * 0x38300074 = h | (w << 16), and never again; it frames what it sends + * by that register, so a narrower rectangle is still framed to the + * full panel width and each row lands further from where it belongs. + * The error grows with (panel width - rect width), which is why it + * looked as though it depended on what was being drawn. Full-screen + * repaints have no discrepancy and landed correctly -- exactly the + * split that showed on the glass, fresh screens right and menu deltas + * adrift. + * + * That init is not ours to change, so send full frames like stock. + */ + if (lcd_partial) { + struct drm_atomic_helper_damage_iter iter; + struct drm_rect clip; + + drm_atomic_helper_damage_iter_init(&iter, old_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &clip) + if (s5l8740_blit(sdev, src, pitch_px, &clip)) + break; + } else { + struct drm_rect full = { + .x1 = 0, + .y1 = 0, + .x2 = fb->width, + .y2 = fb->height, + }; + + s5l8740_blit(sdev, src, pitch_px, &full); + } + + s5l8740_xfer_close(sdev); +out_dev_exit: + drm_dev_exit(idx); +out_end_cpu_access: + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); } static const struct drm_plane_helper_funcs s5l8740_primary_plane_helper_funcs = { @@ -164,6 +517,295 @@ static const struct drm_connector_funcs s5l8740_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; + +/* ------------------------------------------------------------------ */ +/* Display power sequence */ +/* */ +/* Reconstructed from the stock firmware, which does the whole thing in */ +/* three steps and never sends the panel a command: */ +/* */ +/* on: enable the display rail, reset the LCDIF, reprogram it, run */ +/* off: stop the LCDIF, drop the rail */ +/* */ +/* The panel needs no init sequence of its own — the LCDIF is the whole */ +/* story — which is what makes a real off/on cycle possible here. */ +/* ------------------------------------------------------------------ */ + +#define S5L8740_LCD_RESET 0x30 /* reset command / ack */ +#define S5L8740_LCD_UNK2C 0x2c +#define S5L8740_LCD_UNK68 0x68 +#define S5L8740_LCD_UNK70 0x70 +#define S5L8740_LCD_SIZE 0x74 /* height | (width << 16) */ +#define S5L8740_LCD_UNK78 0x78 +#define S5L8740_LCD_UNK7C 0x7c +#define S5L8740_LCD_UNK84 0x84 +#define S5L8740_LCD_UNKA4 0xa4 + +#define S5L8740_CON_HOLD BIT(10) /* 0x400: interface held in reset */ +#define S5L8740_CON_RUN BIT(11) /* 0x800: interface running */ +#define S5L8740_CON_BASE 0x00100ab0 +#define S5L8740_CON_MODE_MASK 0xc0000000 +#define S5L8740_CON_FMT_MASK 0x00000007 +#define S5L8740_STATUS_RESETTING 0x1000 + +/* CLKCON gates that have to be dropped across an LCDIF reset. */ +#define S5L8740_CLKCON_08 0x08 +#define S5L8740_CLKCON_18 0x18 +#define S5L8740_CLKCON_08_MASK 0x7fff7fff +#define S5L8740_CLKCON_18_MASK 0xffff3fff + +#define S5L8740_LCD_RESET_TRIES 5 + +static unsigned int lcd_power_tries = S5L8740_LCD_RESET_TRIES; +module_param(lcd_power_tries, uint, 0644); +MODULE_PARM_DESC(lcd_power_tries, "Attempts to bring the display up (default 5)"); + +static unsigned int lcd_rail_settle_us = 3000; +module_param(lcd_rail_settle_us, uint, 0644); +MODULE_PARM_DESC(lcd_rail_settle_us, + "Settle time after enabling the display rail (us)"); + +static bool lcd_manage_rail = true; +module_param(lcd_manage_rail, bool, 0644); +MODULE_PARM_DESC(lcd_manage_rail, + "Hold the PMU display rail while the panel is on (default Y)"); + +static struct s5l8740_device *s5l8740_lcd_dev; + +static void s5l8740_lcd_rail(struct s5l8740_device *sdev, bool on) +{ + int (*get)(unsigned int); + void (*put)(unsigned int); + + if (!lcd_manage_rail) + return; + if (on) { + get = (int (*)(unsigned int))__symbol_get("n31_pmu_rail_get"); + if (!get) + return; + if (get(N31_PMU_RAIL_DISPLAY)) + drm_warn(&sdev->dev, "display rail enable failed\n"); + __symbol_put("n31_pmu_rail_get"); + } else { + put = (void (*)(unsigned int))__symbol_get("n31_pmu_rail_put"); + if (!put) + return; + put(N31_PMU_RAIL_DISPLAY); + __symbol_put("n31_pmu_rail_put"); + } +} + +/* + * LCDIF reset. `light` skips the parts that wait on hardware, which the + * stock code uses when it only needs the clocks cycled. + */ +static int s5l8740_lcdif_reset(struct s5l8740_device *sdev, bool light) +{ + void __iomem *b = sdev->lcdif; + u32 clk08 = 0, clk18 = 0, val; + int ret = 0; + + if (sdev->clkcon) { + clk08 = readl(sdev->clkcon + S5L8740_CLKCON_08); + clk18 = readl(sdev->clkcon + S5L8740_CLKCON_18); + writel(clk08 & S5L8740_CLKCON_08_MASK, + sdev->clkcon + S5L8740_CLKCON_08); + writel(clk18 & S5L8740_CLKCON_18_MASK, + sdev->clkcon + S5L8740_CLKCON_18); + } + + writel(readl(b + S5L8740_LCD_CON) & ~S5L8740_CON_HOLD, + b + S5L8740_LCD_CON); + + if (!light && + readl_poll_timeout(b + S5L8740_LCD_STATUS, val, + !(val & S5L8740_STATUS_RESETTING), 100, + 500 * USEC_PER_MSEC)) + drm_warn(&sdev->dev, "LCDIF busy before reset (status %08x)\n", + readl(b + S5L8740_LCD_STATUS)); + + writel(1, b + S5L8740_LCD_RESET); + if (readl_poll_timeout(b + S5L8740_LCD_RESET, val, !val, 100, + 500 * USEC_PER_MSEC)) { + drm_warn(&sdev->dev, "LCDIF reset did not ack\n"); + ret = -ETIMEDOUT; + } + + if (!light) { + /* + * Poke the hold bit until the interface admits it is held; + * the stock code loops on this for up to half a second. + */ + ktime_t end = ktime_add_ms(ktime_get(), 500); + + while (!(readl(b + S5L8740_LCD_CON) & S5L8740_CON_HOLD)) { + writel(readl(b + S5L8740_LCD_CON) | S5L8740_CON_HOLD, + b + S5L8740_LCD_CON); + if (ktime_after(ktime_get(), end)) { + drm_warn(&sdev->dev, "LCDIF hold ack timeout\n"); + ret = -ETIMEDOUT; + break; + } + usleep_range(100, 200); + } + } + + writel(readl(b + S5L8740_LCD_CON) & ~S5L8740_CON_HOLD, + b + S5L8740_LCD_CON); + + if (sdev->clkcon) { + writel(clk08, sdev->clkcon + S5L8740_CLKCON_08); + writel(clk18, sdev->clkcon + S5L8740_CLKCON_18); + } + return ret; +} + +/* Reprogram the LCDIF for the current mode. Leaves it stopped. */ +static void s5l8740_lcdif_program(struct s5l8740_device *sdev) +{ + void __iomem *b = sdev->lcdif; + u32 con = readl(b + S5L8740_LCD_CON); + u32 keep = con & (S5L8740_CON_MODE_MASK | S5L8740_CON_FMT_MASK); + + writel(0x000a000a, b + S5L8740_LCD_UNK78); + writel(keep | S5L8740_CON_BASE, b + S5L8740_LCD_CON); + writel(1, b + S5L8740_LCD_UNK2C); + writel(0, b + S5L8740_LCD_UNK68); + writel(0, b + S5L8740_LCD_UNK70); + writel(sdev->mode.vdisplay | (sdev->mode.hdisplay << 16), + b + S5L8740_LCD_SIZE); + writel(0, b + S5L8740_LCD_PHTIME); + writel(770, b + S5L8740_LCD_UNK7C); + writel(100, b + S5L8740_LCD_UNK84); + writel(1, b + S5L8740_LCD_UNKA4); +} + +/* Start the interface, matching the stock enable path. */ +static int s5l8740_lcdif_run(struct s5l8740_device *sdev) +{ + void __iomem *b = sdev->lcdif; + u32 con = readl(b + S5L8740_LCD_CON); + u32 val; + + if ((con & (S5L8740_CON_MODE_MASK | S5L8740_CON_FMT_MASK)) != + (S5L8740_CON_BASE & (S5L8740_CON_MODE_MASK | S5L8740_CON_FMT_MASK))) { + if (readl_poll_timeout(b + S5L8740_LCD_STATUS, val, + !(val & S5L8740_STATUS_RESETTING), 100, + 500 * USEC_PER_MSEC)) + drm_warn(&sdev->dev, "LCDIF busy before run\n"); + writel((con & 0x3ffffff8) | S5L8740_CON_BASE, + b + S5L8740_LCD_CON); + } + writel(readl(b + S5L8740_LCD_CON) | S5L8740_CON_RUN, + b + S5L8740_LCD_CON); + return 0; +} + +static int s5l8740_lcd_power_on_locked(struct s5l8740_device *sdev) +{ + unsigned int try; + int ret = -ETIMEDOUT; + + if (sdev->powered) + return 0; + + s5l8740_lcd_rail(sdev, true); + if (lcd_manage_rail && lcd_rail_settle_us) + usleep_range(lcd_rail_settle_us, lcd_rail_settle_us + 500); + + for (try = 0; try < (lcd_power_tries ? lcd_power_tries : 1); try++) { + ret = s5l8740_lcdif_reset(sdev, try == 0); + if (ret && try == 0) + ret = s5l8740_lcdif_reset(sdev, false); + + s5l8740_lcdif_program(sdev); + s5l8740_lcdif_run(sdev); + + if (readl(sdev->lcdif + S5L8740_LCD_CON) & S5L8740_CON_RUN) { + ret = 0; + break; + } + drm_warn(&sdev->dev, "display did not start (attempt %u)\n", + try + 1); + ret = -EIO; + msleep(2); + } + + if (ret) { + drm_err(&sdev->dev, "failed to turn on display after %u tries\n", + lcd_power_tries); + s5l8740_lcd_rail(sdev, false); + return ret; + } + + sdev->powered = true; + drm_info(&sdev->dev, "display on (CON=%08x STATUS=%08x)\n", + readl(sdev->lcdif + S5L8740_LCD_CON), + readl(sdev->lcdif + S5L8740_LCD_STATUS)); + return 0; +} + +static void s5l8740_lcd_power_off_locked(struct s5l8740_device *sdev) +{ + if (!sdev->powered) + return; + + writel(readl(sdev->lcdif + S5L8740_LCD_CON) & ~S5L8740_CON_RUN, + sdev->lcdif + S5L8740_LCD_CON); + s5l8740_lcd_rail(sdev, false); + sdev->powered = false; + drm_info(&sdev->dev, "display off (CON=%08x)\n", + readl(sdev->lcdif + S5L8740_LCD_CON)); +} + +/* + * Entry points for the screen-sleep policy. Powering the panel down is the + * point of the exercise: with only the backlight off the panel keeps drawing. + */ +int n31_lcd_power(bool on) +{ + struct s5l8740_device *sdev = s5l8740_lcd_dev; + int ret; + + if (!sdev) + return -ENODEV; + + /* + * Suspend the in-kernel clients before the panel goes down so fbcon + * stops drawing into a stopped interface; its damage would otherwise + * be dropped and the cursor would keep queueing work for nothing. + */ + if (!on) + drm_client_dev_suspend(&sdev->dev, false); + + mutex_lock(&sdev->power_lock); + if (on) { + ret = s5l8740_lcd_power_on_locked(sdev); + } else { + s5l8740_lcd_power_off_locked(sdev); + ret = 0; + } + mutex_unlock(&sdev->power_lock); + + if (on) { + if (ret) { + /* Left suspended on failure; nothing to draw to. */ + return ret; + } + /* Buffer survived, interface state did not: resume and repaint. */ + drm_client_dev_resume(&sdev->dev, false); + drm_client_dev_restore(&sdev->dev); + } + return ret; +} +EXPORT_SYMBOL_GPL(n31_lcd_power); + +bool n31_lcd_is_on(void) +{ + return s5l8740_lcd_dev && s5l8740_lcd_dev->powered; +} +EXPORT_SYMBOL_GPL(n31_lcd_is_on); + static const struct drm_crtc_helper_funcs s5l8740_crtc_helper_funcs = { .atomic_check = drm_crtc_helper_atomic_check, }; @@ -212,6 +854,58 @@ static struct drm_driver s5l8740_driver = { * Platform driver */ +/* + * Power control from userspace. The panel has no command interface, so + * an off/on cycle here is the whole recovery path after anything glitches + * the display rail: rail on, settle, LCDIF reset, reprogram, run, repaint. + * + * cat lcd_power 1 while the interface is running + * echo 0 > ... stop the interface and drop the rail + * echo 1 > ... full bring-up + */ +static ssize_t lcd_power_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s5l8740_device *sdev = s5l8740_lcd_dev; + + if (!sdev) + return -ENODEV; + return sysfs_emit(buf, "%d\n", sdev->powered ? 1 : 0); +} + +static ssize_t lcd_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int on; + int ret; + + if (kstrtouint(buf, 0, &on)) + return -EINVAL; + ret = n31_lcd_power(on != 0); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(lcd_power); + +static ssize_t lcd_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s5l8740_device *sdev = s5l8740_lcd_dev; + + if (!sdev || !sdev->lcdif) + return -ENODEV; + return sysfs_emit(buf, + "powered=%d rail_held=%d\n" + "CON=%08x STATUS=%08x SIZE=%08x\n" + "clkcon_window=%s\n", + sdev->powered, sdev->rail_held, + readl(sdev->lcdif + S5L8740_LCD_CON), + readl(sdev->lcdif + S5L8740_LCD_STATUS), + readl(sdev->lcdif + S5L8740_LCD_SIZE), + sdev->clkcon ? "mapped" : "absent"); +} +static DEVICE_ATTR_RO(lcd_state); + static int s5l8740_probe(struct platform_device *pdev) { struct s5l8740_device *sdev; @@ -242,6 +936,97 @@ static int s5l8740_probe(struct platform_device *pdev) drm_dbg(dev, "using I/O memory framebuffer at %pr\n", res); sdev->lcdif = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(sdev->lcdif)) + return PTR_ERR(sdev->lcdif); + + mutex_init(&sdev->power_lock); + + /* + * Third window is the MIPI DSI host. Optional: without it the panel + * window cannot be set and every update repaints the whole frame. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (res) { + sdev->dsi = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(sdev->dsi)) + sdev->dsi = NULL; + } + + /* + * Second window is the clock controller. Only two gates are touched, + * and only to cycle them across an LCDIF reset. Optional: without it + * the reset still works, it just does not gate the clocks first. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + /* + * Map without claiming. This window is the clock controller, which + * the clock-controller node already owns and the IIS driver also + * maps, so an exclusive devm_ioremap_resource() always lost the + * race and returned -EBUSY. The driver then carried on with + * clkcon = NULL and quietly stopped gating the clocks across an + * LCDIF reset -- a real behaviour change reported only as an error + * line nobody acted on. CLKCON is a shared block; sharing it is + * correct, claiming it is not. + */ + sdev->clkcon = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + } + if (!sdev->clkcon) + drm_info(dev, + "no clkcon window; LCDIF reset will not gate clocks\n"); + + /* The panel is already running from the boot loader handoff. */ + sdev->powered = true; + s5l8740_lcd_dev = sdev; + + /* + * Take the display rail for the panel we inherited. Without this + * nobody holds LDO_4, so the PMU's global rail repair -- which + * clears bits 6 and 7 by design -- has nothing to preserve and + * switches the panel off underneath us. That is the white screen: + * the first audio bring-up after boot replays that sequence. + */ + if (lcd_manage_rail) { + int (*get)(unsigned int) = + (int (*)(unsigned int))__symbol_get("n31_pmu_rail_get"); + + if (get) { + if (get(N31_PMU_RAIL_DISPLAY)) + drm_warn(dev, "display rail claim failed\n"); + else + sdev->rail_held = true; + __symbol_put("n31_pmu_rail_get"); + } else { + drm_info(dev, + "PMIC absent; display rail unprotected\n"); + } + } + + if (device_create_file(&pdev->dev, &dev_attr_lcd_power)) + drm_warn(dev, "lcd_power sysfs\n"); + if (device_create_file(&pdev->dev, &dev_attr_lcd_state)) + drm_warn(dev, "lcd_state sysfs\n"); + + /* GATE0: log WTF handoff, never rewrite CON/PHTIME */ + drm_info(dev, "LCDIF handoff CON=%08x PHTIME=%08x (untouched)\n", + readl(sdev->lcdif + S5L8740_LCD_CON), + readl(sdev->lcdif + S5L8740_LCD_PHTIME)); + + /* CON first (stage0). Print so glass shows whether WDT is still live. */ + { + void __iomem *wdt = ioremap(0x3c800000, 8); + + if (wdt) { + writel(0, wdt); + writel(0, wdt + 4); + writel(0, wdt); + writel(0, wdt + 4); + drm_info(dev, "WDT CON=%08x CNT=%08x (disarmed)\n", + readl(wdt), readl(wdt + 4)); + iounmap(wdt); + } + } /* * Modesetting @@ -315,6 +1100,18 @@ static int s5l8740_probe(struct platform_device *pdev) return 0; } +/* + * The LCDIF scans out of the framebuffer by DMA. Across a kexec that + * memory belongs to the next kernel, so the controller would keep + * fetching whatever landed there and the panel would show it. Powering + * down stops the fetch; n31_lcd_power suspends the DRM clients on the + * way, so nothing is left drawing into a stopped interface. + */ +static void s5l8740_shutdown(struct platform_device *pdev) +{ + n31_lcd_power(false); +} + static void s5l8740_remove(struct platform_device *pdev) { struct s5l8740_device *sdev = platform_get_drvdata(pdev); @@ -336,6 +1133,7 @@ static struct platform_driver s5l8740_platform_driver = { }, .probe = s5l8740_probe, .remove = s5l8740_remove, + .shutdown = s5l8740_shutdown, }; module_platform_driver(s5l8740_platform_driver); diff --git a/drivers/i2c/busses/i2c-s5l8702.c b/drivers/i2c/busses/i2c-s5l8702.c old mode 100644 new mode 100755 index 1c8000aa839dfd..0f3e04a01a2944 --- a/drivers/i2c/busses/i2c-s5l8702.c +++ b/drivers/i2c/busses/i2c-s5l8702.c @@ -1,83 +1,120 @@ // SPDX-License-Identifier: GPL-2.0 /* - * S5L8702 I2C controller driver + * S5L8702 / S5L8740 I2C — IRQ master (S3C2410-compatible CON/STAT) + * + * Glass 2026-08-22: "IRQ timeout INT=0 STAT=30 CON=181 or 1B1" + * + * +0x20 INT is always 0. Completion is IICCON bit4 IRQPEND (S3C INTPEND). + * CON=0x1B1 = 0x1A1|IRQPEND — byte done, ISR used to read +0x20, return + * IRQ_NONE, leave SCL stretched. Old "BUSHOLD" *set* bit4, which is the + * hold bit — inverted. Clear IRQPEND to resume; set IRQEN (bit5) for VIC. + * + * Glass 2026-08-22: VIC IRQ works (virq 38 = hwirq 22). STAT bit0 as + * S3C LASTBIT/NAK aborted every xfer with -EIO (-5) including PMIC@0x73. + * Ignore bit0; writes still complete on IRQPEND. + * + * Reads: address IRQPEND is not a data byte (DS still holds addr8). + * clock_rx_byte() held IRQPEND *clear* and wiped the next byte-done, + * so DS stayed 0x31/0xe7. One IRQPEND per RX byte, then read DS. + * SEC 4AC4 / Rockbox also treat INT 0x100 as byte-ready. Not PIO. */ - +#include +#include +#include +#include +#include #include -#include #include +#include +#include #include #include #include #include #include +#include + +#define S5L8702_I2C_CON 0x0 +#define S5L8702_I2C_STAT 0x4 +#define S5L8702_I2C_ADD 0x8 +#define S5L8702_I2C_DS 0xc +#define S5L8702_I2C_BUSY 0x10 +#define S5L8702_I2C_UNK14 0x14 +#define S5L8702_I2C_UNK18 0x18 +#define S5L8702_I2C_INT 0x20 +#define S5L8702_I2C_UNK28 0x28 + +/* S3C2410 IICCON low byte + SEC bit8 */ +#define S5L8702_I2C_CON_SCALE(x) ((x) & 0xf) +#define S5L8702_I2C_CON_IRQPEND BIT(4) /* W0C to resume SCL */ +#define S5L8702_I2C_CON_IRQEN BIT(5) +#define S5L8702_I2C_CON_TXDIV_512 BIT(6) +#define S5L8702_I2C_CON_ACKEN BIT(7) +#define S5L8702_I2C_CON_SEC_BIT8 BIT(8) + +#define S5L8702_I2C_CON_IDLE (S5L8702_I2C_CON_SEC_BIT8 | \ + S5L8702_I2C_CON_ACKEN | \ + S5L8702_I2C_CON_IRQEN | \ + S5L8702_I2C_CON_SCALE(1)) /* 0x1A1 */ + +#define S5L8702_I2C_STAT_LASTBIT BIT(0) +#define S5L8702_I2C_STAT_TXRXEN BIT(4) +#define S5L8702_I2C_STAT_START BIT(5) +#define S5L8702_I2C_STAT_TX BIT(6) +#define S5L8702_I2C_STAT_MASTER BIT(7) +#define S5L8702_I2C_STAT_MASTER_TX (S5L8702_I2C_STAT_MASTER | \ + S5L8702_I2C_STAT_TX | \ + S5L8702_I2C_STAT_TXRXEN) /* 0xD0 */ +#define S5L8702_I2C_STAT_MASTER_RX (S5L8702_I2C_STAT_MASTER | \ + S5L8702_I2C_STAT_TXRXEN) /* 0x90 */ + +#define S5L8702_I2C_INT_ALL 0x3f00 +#define S5L8702_I2C_INT_BYTE BIT(8) /* SEC 4AC4 / Rockbox STA2 */ +#define S5L8702_I2C_INT_STOP BIT(13) + +/* SEC 1C8C canned STAT: v4=0x80 read / 0xC0 write, then |0x10 / |0x30 */ +#define S5L8702_I2C_STAT_SEC_RX 0x80 +#define S5L8702_I2C_STAT_SEC_TX 0xC0 +#define S5L8702_I2C_STAT_SEC_SOE 0x10 +#define S5L8702_I2C_STAT_SEC_GO 0x30 /* SOE|BB */ + +#define S5L8702_I2C_XFER_TIMEOUT (msecs_to_jiffies(200)) +#define S5L8702_I2C_BUSY_LOOPS 10000 -#define S5L8702_I2C_CON 0x0 /* Control register */ -#define S5L8702_I2C_STAT 0x4 /* Control/status register */ -#define S5L8702_I2C_ADD 0x8 /* Bus address register */ -#define S5L8702_I2C_DS 0xc /* Transmit/receive data shift register */ -#define S5L8702_I2C_BUSY 0x10 -#define S5L8702_I2C_UNK14 0x14 -#define S5L8702_I2C_UNK18 0x18 -#define S5L8702_I2C_INT 0x20 /* Interrupt status register */ -#define S5L8702_I2C_UNK28 0x28 - -#define S5L8702_I2C_CON_CK_REG(x) ((x) & 0xf) -#define S5L8702_I2C_CON_BUSHOLD BIT(4) -#define S5L8702_I2C_CON_CKSEL16 (0 << 6) -#define S5L8702_I2C_CON_CKSEL512 BIT(6) -#define S5L8702_I2C_CON_ACKGEN BIT(7) -#define S5L8702_I2C_CON_INTEN_BUSHOLD BIT(8) -#define S5L8702_I2C_CON_INTEN_TIMEOUT BIT(9) -#define S5L8702_I2C_CON_INTEN_RX BIT(10) -#define S5L8702_I2C_CON_INTEN_TX BIT(11) -#define S5L8702_I2C_CON_INTEN_START BIT(12) -#define S5L8702_I2C_CON_INTEN_STOP BIT(13) -#define S5L8702_I2C_CON_INTEN_ALL (0x3f00) - -#define S5L8702_I2C_STAT_LRB BIT(0) -// The missing bits probably match S5L8700X datasheet ADDR_ZERO, AAS, LBA -#define S5L8702_I2C_STAT_SOE BIT(4) // Serial Output Enable -#define S5L8702_I2C_STAT_BB BIT(5) -#define S5L8702_I2C_STAT_TX BIT(6) -#define S5L8702_I2C_STAT_MASTER BIT(7) - -#define S5L8702_I2C_INT_BUSHOLD BIT(8) -#define S5L8702_I2C_INT_TIMEOUT BIT(9) -#define S5L8702_I2C_INT_RX BIT(10) -#define S5L8702_I2C_INT_TX BIT(11) -#define S5L8702_I2C_INT_START BIT(12) -#define S5L8702_I2C_INT_STOP BIT(13) -#define S5L8702_I2C_INT_ALL (0x3f00) - -#define S5L8702_I2C_XFER_TIMEOUT (msecs_to_jiffies(100)) - -#define S5L8702_I2C_BUSY_LOOPS 5000 - -/* i2c controller state */ enum s5l8702_i2c_state { STATE_IDLE, STATE_START, STATE_READ, - STATE_PREPARE_READ, STATE_WRITE, - STATE_STOP }; struct s5l8702_i2c_dev { struct device *dev; void __iomem *regs; int irq; - bool write_busy_poll; + spinlock_t lock; enum s5l8702_i2c_state state; - struct i2c_msg *msg; + struct i2c_msg *msg; unsigned int msg_pos; unsigned int nmsgs; int msg_ret; unsigned int iiccon; - unsigned int pending_irq; + bool timeout_logged; + bool start_logged; + bool isr_logged; + bool polled_logged; + bool stat_logged; + bool rd_logged; + bool drop_pend; /* READ: skip address-phase IRQPEND (DS still addr8) */ + u8 rx_retry; struct completion msg_complete; struct i2c_adapter adapter; + struct clk_bulk_data *clks; + int num_clks; + void __iomem *gpio; + void __iomem *gpiocmd; + u32 pads[2]; + unsigned int npads; }; static inline u32 s5l8702_i2c_readl(struct s5l8702_i2c_dev *i2c_dev, u32 reg) @@ -85,179 +122,497 @@ static inline u32 s5l8702_i2c_readl(struct s5l8702_i2c_dev *i2c_dev, u32 reg) return readl(i2c_dev->regs + reg); } -static void s5l8702_i2c_writel(struct s5l8702_i2c_dev *i2c_dev, u32 reg, u32 val) +static void s5l8702_i2c_write_raw(struct s5l8702_i2c_dev *i2c_dev, u32 reg, + u32 val) { - if (i2c_dev->write_busy_poll) { - unsigned int n = S5L8702_I2C_BUSY_LOOPS; - - while (readl(i2c_dev->regs + S5L8702_I2C_BUSY) && --n) - cpu_relax(); - } writel(val, i2c_dev->regs + reg); } -static void s5l8702_i2c_state_machine(struct s5l8702_i2c_dev *i2c_dev) { - uint32_t stat; +static void s5l8702_i2c_wait_rdy(struct s5l8702_i2c_dev *i2c_dev) +{ + unsigned int n = S5L8702_I2C_BUSY_LOOPS; - switch ( i2c_dev->state ) - { + while (s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_BUSY) && --n) + cpu_relax(); +} - case STATE_START: - i2c_dev->pending_irq = S5L8702_I2C_INT_BUSHOLD; - if (i2c_dev->msg->flags & I2C_M_RD) { - stat = S5L8702_I2C_STAT_SOE | S5L8702_I2C_STAT_MASTER; - } else { - stat = S5L8702_I2C_STAT_SOE | S5L8702_I2C_STAT_TX | S5L8702_I2C_STAT_MASTER; - } - i2c_dev->iiccon &= ~S5L8702_I2C_CON_BUSHOLD; - i2c_dev->iiccon |= S5L8702_I2C_CON_ACKGEN; - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, stat); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_DS, i2c_8bit_addr_from_msg(i2c_dev->msg)); - stat |= S5L8702_I2C_STAT_BB; - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, stat); - if (i2c_dev->msg->flags & I2C_M_RD) { - i2c_dev->state = STATE_PREPARE_READ; - } - else { - i2c_dev->state = STATE_WRITE; - } - break; +static void s5l8702_i2c_writel(struct s5l8702_i2c_dev *i2c_dev, u32 reg, u32 val) +{ + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, reg, val); +} - case STATE_WRITE: // Write - if ( s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & S5L8702_I2C_STAT_LRB ) { // Did we receive ACK? - i2c_dev->msg_ret = -EIO; - goto generate_stop; - } - if ( i2c_dev->msg_pos == i2c_dev->msg->len ) { // is the end of the msg - goto generate_stop; - } - i2c_dev->pending_irq = S5L8702_I2C_INT_BUSHOLD; - i2c_dev->iiccon |= S5L8702_I2C_CON_BUSHOLD; - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_DS, i2c_dev->msg->buf[i2c_dev->msg_pos++]); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); - break; +/* Clear IRQPEND so SCL runs. Never set bit4 — that stretches the bus. */ +static void s5l8702_i2c_resume(struct s5l8702_i2c_dev *i2c_dev) +{ + u32 con = i2c_dev->iiccon & ~S5L8702_I2C_CON_IRQPEND; + unsigned int n = 10000; + + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, con); + /* Wait until this IRQPEND drops so the next service is a new byte. */ + while ((s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON) & + S5L8702_I2C_CON_IRQPEND) && --n) + cpu_relax(); +} - case STATE_READ: // Read - i2c_dev->msg->buf[i2c_dev->msg_pos++] = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_DS); - fallthrough; - case STATE_PREPARE_READ: // Prepare read - if ( !i2c_dev->msg_pos && (s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & S5L8702_I2C_STAT_LRB) ) { - i2c_dev->msg_ret = -EIO; - goto generate_stop; - } +static void s5l8702_i2c_state_machine(struct s5l8702_i2c_dev *i2c_dev); - if ( i2c_dev->msg_pos == i2c_dev->msg->len ) { // is the end of the msg - goto generate_stop; - } +static void s5l8702_i2c_finish(struct s5l8702_i2c_dev *i2c_dev) +{ + i2c_dev->state = STATE_IDLE; + complete(&i2c_dev->msg_complete); +} - if ( (i2c_dev->msg->len - i2c_dev->msg_pos) == 1 ) { // last byte of msg NACK - i2c_dev->iiccon &= ~S5L8702_I2C_CON_ACKGEN; - } - i2c_dev->iiccon |= S5L8702_I2C_CON_BUSHOLD; - i2c_dev->pending_irq = S5L8702_I2C_INT_BUSHOLD; - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); - i2c_dev->state = STATE_READ; - break; +static void s5l8702_i2c_stop(struct s5l8702_i2c_dev *i2c_dev) +{ + u32 mode = (i2c_dev->msg->flags & I2C_M_RD) ? + S5L8702_I2C_STAT_SEC_RX : S5L8702_I2C_STAT_SEC_TX; + + /* SEC 1C8C stop: INT=0x2000, STAT = v4|0x10 (0x90 read / 0xD0 + * write). Do not read-modify current STAT — that left 0xe1 + * (MASTER|TX|START|bit0) in DS on the next read. + */ + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_INT, 0x2000); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, + mode | S5L8702_I2C_STAT_SEC_SOE); + i2c_dev->iiccon = S5L8702_I2C_CON_IDLE; + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); + i2c_dev->nmsgs = 0; + s5l8702_i2c_finish(i2c_dev); +} - case STATE_STOP: // Generate Stop -generate_stop: - i2c_dev->pending_irq = S5L8702_I2C_INT_STOP; +static void s5l8702_i2c_state_machine(struct s5l8702_i2c_dev *i2c_dev) +{ + u32 stat; + + switch (i2c_dev->state) { + case STATE_START: + dev_dbg(i2c_dev->dev, "IIC START 7bit=0x%02x DS=0x%02x %s\n", + i2c_dev->msg->addr, + i2c_8bit_addr_from_msg(i2c_dev->msg), + (i2c_dev->msg->flags & I2C_M_RD) ? "RD" : "WR"); + /* Write: MASTER_TX|START = 0xF0. Read: MASTER_RX|START = 0xB0 + * (same 0xB0 glass saw). INT +0x20 stays 0 — IRQPEND only. + */ + if (i2c_dev->msg->flags & I2C_M_RD) + stat = S5L8702_I2C_STAT_MASTER_RX; + else + stat = S5L8702_I2C_STAT_MASTER_TX; + i2c_dev->iiccon = S5L8702_I2C_CON_IDLE; + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, stat); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, + i2c_8bit_addr_from_msg(i2c_dev->msg)); + ndelay(50); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, + stat | S5L8702_I2C_STAT_START); + i2c_dev->state = (i2c_dev->msg->flags & I2C_M_RD) ? + STATE_READ : STATE_WRITE; + /* NACK the sole RX byte before the address IRQ is dropped. */ + if ((i2c_dev->msg->flags & I2C_M_RD) && + i2c_dev->msg->len == 1) + i2c_dev->iiccon &= ~S5L8702_I2C_CON_ACKEN; + /* + * Glass #73: first RD IRQPEND is address complete, DS=addr8. + * Only skipping when IRQPEND was already set ate that IRQ + * as data (DS=31) and wedged the next xfer (-110). + */ + if (i2c_dev->msg->flags & I2C_M_RD) + i2c_dev->drop_pend = true; + break; - if (i2c_dev->msg->flags & I2C_M_RD) { - stat &= ~S5L8702_I2C_STAT_BB; + case STATE_WRITE: { + u32 st = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT); + + /* + * Do not abort on STAT bit0. S3C LASTBIT=NAK, but on this + * block it is 1 whenever IRQPEND fires — PMIC@0x73 (CONFIRMED) + * and LIS3 both returned -EIO (-5) after a working VIC IRQ. + */ + dev_dbg(i2c_dev->dev, + "IIC WR STAT=%08x CON=%08x bit0=%u\n", + st, s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON), + !!(st & S5L8702_I2C_STAT_LASTBIT)); + if (i2c_dev->msg_pos == i2c_dev->msg->len) { + s5l8702_i2c_stop(i2c_dev); + break; } - else { - stat &= ~( S5L8702_I2C_STAT_BB | S5L8702_I2C_STAT_TX ); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, + i2c_dev->msg->buf[i2c_dev->msg_pos++]); + ndelay(50); + break; + } + + case STATE_READ: { + u32 ds, st; + u8 addr8 = i2c_8bit_addr_from_msg(i2c_dev->msg); + + /* + * This IRQPEND is a completed RX byte. The address-phase + * IRQPEND is dropped in service_pend (DS still addr8). + * Do not hold IRQPEND clear — that wiped the data-ready + * flag and left DS=0x31/0xe7 on glass. + */ + if (i2c_dev->msg_pos >= i2c_dev->msg->len) { + s5l8702_i2c_stop(i2c_dev); + break; } - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, stat); - i2c_dev->iiccon &= ~S5L8702_I2C_CON_ACKGEN; - i2c_dev->iiccon |= S5L8702_I2C_CON_BUSHOLD; - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); - i2c_dev->nmsgs--; - i2c_dev->msg++; - i2c_dev->msg_pos = 0; - - // If we have an error or we processed all messages then we are done - if (i2c_dev->msg_ret || (i2c_dev->nmsgs == 0)) { - i2c_dev->state = STATE_IDLE; - } else { - i2c_dev->state = STATE_START; + ds = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_DS) & 0xff; + st = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & 0xff; + if (!i2c_dev->rd_logged) { + i2c_dev->rd_logged = true; + dev_info(i2c_dev->dev, + "IIC RD DS=%02x STAT=%02x addr8=%02x%s\n", + ds, st, addr8, + (ds == addr8) ? " (still addr)" : ""); } + i2c_dev->msg->buf[i2c_dev->msg_pos++] = (u8)ds; + if (i2c_dev->msg_pos >= i2c_dev->msg->len) + s5l8702_i2c_stop(i2c_dev); + else if (i2c_dev->msg_pos + 1 == i2c_dev->msg->len) + i2c_dev->iiccon &= ~S5L8702_I2C_CON_ACKEN; break; - - case STATE_IDLE: // We are done - i2c_dev->pending_irq = 0; - complete(&i2c_dev->msg_complete); + } + + case STATE_IDLE: break; } } -static irqreturn_t s5l8702_i2c_isr(int this_irq, void *data) -{ - struct s5l8702_i2c_dev *i2c_dev = data; - u32 val; - - val = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_INT); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_INT, val); +#define S5L8740_VIC0_PHYS 0x38E00000ul +#define S5L8740_VIC_SIZE 0x2000 - dev_dbg(i2c_dev->dev, "%s state=0x%04x msg_ret=0x%04x pending_irq=0x%04x val=0x%04x", - __func__, i2c_dev->state, i2c_dev->msg_ret, i2c_dev->pending_irq, val); +static void __iomem *s5l_vic; - i2c_dev->pending_irq &= ~val; +static void s5l8702_dump_vic(struct s5l8702_i2c_dev *i2c_dev) +{ + void __iomem *v0, *v1; + u32 r0, s0, e0, r1, s1, e1; + + if (!s5l_vic) + s5l_vic = ioremap(S5L8740_VIC0_PHYS, S5L8740_VIC_SIZE); + if (!s5l_vic) + return; + v0 = s5l_vic; + v1 = s5l_vic + 0x1000; + r0 = readl(v0 + 0x08); + s0 = readl(v0 + 0x00); + e0 = readl(v0 + 0x10); + r1 = readl(v1 + 0x08); + s1 = readl(v1 + 0x00); + e1 = readl(v1 + 0x10); + dev_err(i2c_dev->dev, + "VIC0 raw=%08x stat=%08x en=%08x VIC1 raw=%08x stat=%08x en=%08x\n", + r0, s0, e0, r1, s1, e1); + if (r0) + dev_err(i2c_dev->dev, "VIC0 pending bit %u (DT IIC is 21/22)\n", + ffs(r0) - 1); + if (r1) + dev_err(i2c_dev->dev, "VIC1 pending bit %u\n", ffs(r1) - 1); +} - // [TODO] Is this the best way due to us getting other interrupts? - if (!i2c_dev->pending_irq) { - s5l8702_i2c_state_machine(i2c_dev); +/* Caller holds i2c_dev->lock. */ +static bool s5l8702_i2c_service_pend(struct s5l8702_i2c_dev *i2c_dev) +{ + u32 con = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON); + u32 extra = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_INT); + + if (!(con & S5L8702_I2C_CON_IRQPEND) && + !(extra & (S5L8702_I2C_INT_ALL | S5L8702_I2C_INT_BYTE | + S5L8702_I2C_INT_STOP))) + return false; + if (extra & (S5L8702_I2C_INT_ALL | S5L8702_I2C_INT_BYTE | + S5L8702_I2C_INT_STOP)) + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_INT, extra); + if (i2c_dev->drop_pend) { + i2c_dev->drop_pend = false; + s5l8702_i2c_resume(i2c_dev); + return true; } + s5l8702_i2c_state_machine(i2c_dev); + /* One S3C-style out_ack per IRQ. Do not ack inside each state. */ + s5l8702_i2c_resume(i2c_dev); + return true; +} +static irqreturn_t s5l8702_i2c_isr(int this_irq, void *data) +{ + struct s5l8702_i2c_dev *i2c_dev = data; + bool handled; + + spin_lock(&i2c_dev->lock); + handled = s5l8702_i2c_service_pend(i2c_dev); + spin_unlock(&i2c_dev->lock); + if (!handled) + return IRQ_NONE; + if (!i2c_dev->isr_logged) { + struct irq_data *d = irq_get_irq_data(this_irq); + + i2c_dev->isr_logged = true; + dev_dbg(i2c_dev->dev, "IIC ISR fired irq=%d hwirq=%lu\n", + this_irq, d ? d->hwirq : 0); + } return IRQ_HANDLED; } -static int s5l8702_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], - int num) +static int s5l8702_i2c_init(struct s5l8702_i2c_dev *i2c_dev); + +/* Drop leftover IRQPEND/INT so the next START's first pend is a new byte. */ +static void s5l8702_i2c_drain_pend(struct s5l8702_i2c_dev *i2c_dev) { - unsigned long time_left; - struct s5l8702_i2c_dev *i2c_dev = i2c_get_adapdata(adap); + unsigned int n = 8; + + while (n--) { + u32 con = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON); + u32 extra = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_INT); + + if (!(con & S5L8702_I2C_CON_IRQPEND) && + !(extra & S5L8702_I2C_INT_ALL)) + break; + if (extra & S5L8702_I2C_INT_ALL) + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_INT, extra); + s5l8702_i2c_resume(i2c_dev); + } +} - dev_dbg(i2c_dev->dev, "%s start", __func__); +/* One START/STOP, like SEC 1C8C. IRQPEND completion for write and read. */ +static int s5l8702_i2c_xfer_one(struct s5l8702_i2c_dev *i2c_dev, + struct i2c_msg *msg) +{ + unsigned long flags, deadline; - // [TODO] implement clocks this is equivalent to set controller active and clear interrupts - // but we are missing clock enable and disable + reinit_completion(&i2c_dev->msg_complete); s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_UNK14, 1); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_INT, S5L8702_I2C_INT_ALL); - - int i; + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_INT, S5L8702_I2C_INT_ALL); + + spin_lock_irqsave(&i2c_dev->lock, flags); + s5l8702_i2c_drain_pend(i2c_dev); + i2c_dev->drop_pend = false; + i2c_dev->rx_retry = 0; + i2c_dev->msg = msg; + i2c_dev->nmsgs = 1; + i2c_dev->msg_ret = 0; + i2c_dev->msg_pos = 0; + i2c_dev->state = STATE_START; + s5l8702_i2c_state_machine(i2c_dev); + spin_unlock_irqrestore(&i2c_dev->lock, flags); + + /* + * Completion is IICCON IRQPEND (S3C bit4). Glass: VIC 22 can fire + * once ("IIC ISR fired") then go silent for later bytes — ISR-only + * wait became -110 on every PMIC/LIS3 xfer. Service the same + * IRQPEND bit from this thread when the ISR does not. Same lock as + * the ISR, so one IRQPEND is one step. Not the old +0x20 PIO path. + */ + deadline = jiffies + S5L8702_I2C_XFER_TIMEOUT; + while (!try_wait_for_completion(&i2c_dev->msg_complete)) { + bool serviced; + + if (time_after(jiffies, deadline)) { + if (!i2c_dev->timeout_logged) { + i2c_dev->timeout_logged = true; + dev_err(i2c_dev->dev, + "IIC IRQ timeout 7bit=0x%02x DS=0x%02x INT=%08x STAT=%08x CON=%08x state=%d isr=%d\n", + i2c_dev->msg ? i2c_dev->msg->addr : 0, + i2c_dev->msg ? i2c_8bit_addr_from_msg(i2c_dev->msg) : 0, + s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_INT), + s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT), + s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON), + i2c_dev->state, + i2c_dev->isr_logged); + s5l8702_dump_vic(i2c_dev); + } + spin_lock_irqsave(&i2c_dev->lock, flags); + i2c_dev->state = STATE_IDLE; + s5l8702_i2c_init(i2c_dev); + spin_unlock_irqrestore(&i2c_dev->lock, flags); + return -ETIMEDOUT; + } - for (i = 0; i < num; i++) { - dev_dbg(i2c_dev->dev, "%s addr=0x%04x flags=0x%04x len=%u buf=%02x", - __func__, msgs[i].addr, msgs[i].flags, msgs[i].len, msgs[i].buf[0]); + spin_lock_irqsave(&i2c_dev->lock, flags); + serviced = s5l8702_i2c_service_pend(i2c_dev); + spin_unlock_irqrestore(&i2c_dev->lock, flags); + if (serviced) { + if (!i2c_dev->polled_logged && !i2c_dev->isr_logged) { + i2c_dev->polled_logged = true; + dev_dbg(i2c_dev->dev, + "IIC IRQPEND polled (VIC irq=%d missed)\n", + i2c_dev->irq); + s5l8702_dump_vic(i2c_dev); + } + continue; + } + cpu_relax(); } + return i2c_dev->msg_ret; +} - i2c_dev->msg = msgs; - i2c_dev->nmsgs = num; - i2c_dev->msg_ret = 0; - i2c_dev->msg_pos = 0; - i2c_dev->state = STATE_START; +/* + * emcore/umsboot s5l87xx_i2c_recv_byte — the RX kick this IP needs. + * After the address IRQPEND, rewrite CON to 0xB7 (ACK) or 0x37 (NACK), + * wait for bit4, then read DS. Clearing bit4 (our write resume) does + * not clock a payload byte — glass #74 still had DS=addr8. + * Writes stay on the IRQPEND path. Not samsung,pio-mode. + */ +static int s5l8702_i2c_wait_con_pend(struct s5l8702_i2c_dev *i2c_dev) +{ + unsigned int i; - s5l8702_i2c_state_machine(i2c_dev); + for (i = 0; i < 200000; i++) { + if (s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_CON) & + S5L8702_I2C_CON_IRQPEND) + return 0; + cpu_relax(); + } + return -ETIMEDOUT; +} - time_left = wait_for_completion_timeout(&i2c_dev->msg_complete, - S5L8702_I2C_XFER_TIMEOUT); +static int s5l8702_i2c_xfer_read(struct s5l8702_i2c_dev *i2c_dev, + struct i2c_msg *msg) +{ + unsigned int i; + int ret; + u8 addr8 = i2c_8bit_addr_from_msg(msg); + static bool rd_ok_logged; + + disable_irq(i2c_dev->irq); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, addr8); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, 0xb0); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0xb7); + ret = s5l8702_i2c_wait_con_pend(i2c_dev); + if (ret) + goto out; + + for (i = 0; i < msg->len; i++) { + u8 ds; + u32 st; + bool ack = (i + 1 < msg->len); + + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, + ack ? 0xb7 : 0x37); + ret = s5l8702_i2c_wait_con_pend(i2c_dev); + if (ret) + goto out; + ds = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_DS) & 0xff; + st = s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & 0xff; + msg->buf[i] = ds; + if (!rd_ok_logged) { + rd_ok_logged = true; + dev_info(i2c_dev->dev, + "IIC RD DS=%02x STAT=%02x addr8=%02x%s\n", + ds, st, addr8, + (ds == addr8) ? " (still addr)" : ""); + } + } - dev_dbg(i2c_dev->dev, "%s done time_left=0x%04lx msg_ret=0x%04x", __func__, time_left, i2c_dev->msg_ret); - if (time_left == 0) - return -ETIMEDOUT; +out: + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, 0x90); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0xb7); + for (i = 0; i < 200000; i++) { + if (!(s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & + S5L8702_I2C_STAT_START)) + break; + cpu_relax(); + } + i2c_dev->iiccon = S5L8702_I2C_CON_IDLE; + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); + enable_irq(i2c_dev->irq); + if (ret) + dev_err_once(i2c_dev->dev, "IIC emcore-RX timeout\n"); + return ret; +} - return i2c_dev->msg_ret ? : num; +/* emcore i2c_send: DS then CON=0xB7, wait bit4. Same kick as RX. */ +static int s5l8702_i2c_xfer_write(struct s5l8702_i2c_dev *i2c_dev, + struct i2c_msg *msg) +{ + unsigned int i; + int ret; + u8 addr8 = i2c_8bit_addr_from_msg(msg); + + disable_irq(i2c_dev->irq); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, addr8); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, 0xf0); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0xb7); + ret = s5l8702_i2c_wait_con_pend(i2c_dev); + if (ret) + goto out; + for (i = 0; i < msg->len; i++) { + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, msg->buf[i]); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0xb7); + ret = s5l8702_i2c_wait_con_pend(i2c_dev); + if (ret) + goto out; + } +out: + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, 0xd0); + s5l8702_i2c_wait_rdy(i2c_dev); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0xb7); + for (i = 0; i < 200000; i++) { + if (!(s5l8702_i2c_readl(i2c_dev, S5L8702_I2C_STAT) & + S5L8702_I2C_STAT_START)) + break; + cpu_relax(); + } + i2c_dev->iiccon = S5L8702_I2C_CON_IDLE; + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); + enable_irq(i2c_dev->irq); + if (ret) + dev_err_once(i2c_dev->dev, "IIC emcore-TX timeout\n"); + return ret; } -static u32 s5l8702_i2c_func(struct i2c_adapter *adap) +static int s5l8702_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) { struct s5l8702_i2c_dev *i2c_dev = i2c_get_adapdata(adap); + int i, ret; + static bool split_logged; + + /* + * SEC 3F40 = 3F60 write of the register, STOP, then 1C8C READ. + * Linux smbus_read_byte_data is two msgs in one xfer (Sr). + * Chaining the READ START inside the WRITE's IRQ left STAT=0xe1 + * and every r1–r12 read returned that byte. + */ + if (!split_logged) { + split_logged = true; + dev_dbg(i2c_dev->dev, + "IIC one-msg xfer (SEC 1C8C STOP, IRQPEND write+read)\n"); + } + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + ret = s5l8702_i2c_xfer_read(i2c_dev, &msgs[i]); + else + ret = s5l8702_i2c_xfer_write(i2c_dev, &msgs[i]); + if (ret) + return ret; + if (i + 1 < num) + udelay(100); + } + return num; +} - dev_dbg(i2c_dev->dev, "%s", __func__); - +static u32 s5l8702_i2c_func(struct i2c_adapter *adap) +{ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } @@ -266,69 +621,217 @@ static const struct i2c_algorithm s5l8702_i2c_algo = { .functionality = s5l8702_i2c_func, }; -static int s5l8702_i2c_init(struct s5l8702_i2c_dev *i2c_dev) { - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_ADD, 0x40); // [TODO] Get slave address from DT +#define S5L87XX_CLK_BASE 0x3C500000u +#define S5L87XX_PWRCON1 (S5L87XX_CLK_BASE + 0x4C) - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_UNK14, 1); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_UNK18, 0); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, S5L8702_I2C_STAT_MASTER); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, 0); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, 0); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_DS, 0x40); // [TODO] Get slave address from DT - - // [TODO] calculate divisors from freq in DT - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_CON, - S5L8702_I2C_CON_INTEN_BUSHOLD | S5L8702_I2C_CON_ACKGEN | - S5L8702_I2C_CON_CKSEL512 | S5L8702_I2C_CON_CK_REG(0)); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_STAT, S5L8702_I2C_STAT_SOE); - s5l8702_i2c_writel(i2c_dev, S5L8702_I2C_UNK28, 0); - - i2c_dev->iiccon = S5L8702_I2C_CON_INTEN_STOP | S5L8702_I2C_CON_INTEN_BUSHOLD | - S5L8702_I2C_CON_CKSEL512 | S5L8702_I2C_CON_CK_REG(0); +static void s5l8702_i2c_ungate(struct s5l8702_i2c_dev *i2c_dev, + resource_size_t base) +{ + void __iomem *pwr; + u32 val, mask; + + if (base == 0x3C600000) + mask = BIT(4); + else if (base == 0x3C900000) + mask = BIT(6); + else + return; + + pwr = ioremap(S5L87XX_PWRCON1, 4); + if (!pwr) + return; + val = readl(pwr) & ~mask; + writel(val, pwr); + iounmap(pwr); + udelay(50); +} + +static int s5l8702_i2c_init(struct s5l8702_i2c_dev *i2c_dev) +{ + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_ADD, 0x40); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_UNK14, 1); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_UNK18, 0); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, 0); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, 0); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_DS, 0x40); + i2c_dev->iiccon = S5L8702_I2C_CON_IDLE; + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_CON, i2c_dev->iiccon); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_STAT, S5L8702_I2C_STAT_TXRXEN); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_UNK28, 0); + s5l8702_i2c_write_raw(i2c_dev, S5L8702_I2C_INT, S5L8702_I2C_INT_ALL); + return 0; +} + +/* + * Pad mux. + * + * Nothing else in this port muxes the I2C pads, which is fine for the bus + * the bootloader leaves configured but not for one it does not. Stock does + * it per bus in sub_5714EE: + * + * bus 0 (0x3C600000) GPIO 4, 5 bus 2 GPIO 66, 67 + * bus 1 (0x3C900000) GPIO 78, 79 bus 3 GPIO 83, 84 + * + * Each pad goes to function 2 via the GPIO command port, and the first of + * the pair additionally gets its bit set in the bank's +0x0C register -- + * stock applies that to one pad only, so this does the same rather than + * guessing that both want it. + * + * The pads come from DT rather than a bus-index table here: the index a + * controller has in the stock enumeration is not something the driver can + * see, and getting it wrong would mux somebody else's pins. + */ +#define S5L8702_I2C_GPIO_PHYS 0x3cf00000ul +#define S5L8702_I2C_GPIO_LEN 0x200 +#define S5L8702_I2C_GPIOCMD_PHYS 0x3cf001e0ul +#define S5L8702_I2C_PAD_FUNC 2 +#define S5L8702_I2C_PAD_RELEASE 0 +#define S5L8702_I2C_BANK_STRIDE 32 +#define S5L8702_I2C_BANK_DIR 0x14 +#define S5L8702_I2C_BANK_ENABLE 0x0c +#define S5L8702_I2C_PADS 2 + +static void s5l8702_i2c_pad_func(struct s5l8702_i2c_dev *i2c_dev, + unsigned int gpio, u8 func, bool claim) +{ + void __iomem *bank; + unsigned int pin; + u32 dir; + + if (!i2c_dev->gpio || !i2c_dev->gpiocmd) + return; + bank = i2c_dev->gpio + S5L8702_I2C_BANK_STRIDE * (gpio >> 3); + pin = gpio & 7; + + dir = readl(bank + S5L8702_I2C_BANK_DIR); + if (claim) + dir |= BIT(pin); + else + dir &= ~BIT(pin); + writel(dir, bank + S5L8702_I2C_BANK_DIR); + + writel(((gpio >> 3) << 16) | (pin << 8) | func, i2c_dev->gpiocmd); +} + +static void s5l8702_i2c_pad_enable(struct s5l8702_i2c_dev *i2c_dev, + unsigned int gpio, bool on) +{ + void __iomem *bank; + unsigned int pin; + u32 v; + + if (!i2c_dev->gpio) + return; + bank = i2c_dev->gpio + S5L8702_I2C_BANK_STRIDE * (gpio >> 3); + pin = gpio & 7; + v = readl(bank + S5L8702_I2C_BANK_ENABLE); + if (on) + v |= BIT(pin); + else + v &= ~BIT(pin); + writel(v, bank + S5L8702_I2C_BANK_ENABLE); +} + +static void s5l8702_i2c_pads(struct s5l8702_i2c_dev *i2c_dev, bool claim) +{ + unsigned int i; + + if (!i2c_dev->npads) + return; + + if (claim) { + for (i = 0; i < i2c_dev->npads; i++) + s5l8702_i2c_pad_func(i2c_dev, i2c_dev->pads[i], + S5L8702_I2C_PAD_FUNC, true); + s5l8702_i2c_pad_enable(i2c_dev, i2c_dev->pads[0], true); + dev_info(i2c_dev->dev, "pads %u/%u -> function %u\n", + i2c_dev->pads[0], i2c_dev->pads[1], + S5L8702_I2C_PAD_FUNC); + } else { + s5l8702_i2c_pad_enable(i2c_dev, i2c_dev->pads[0], false); + for (i = 0; i < i2c_dev->npads; i++) + s5l8702_i2c_pad_func(i2c_dev, i2c_dev->pads[i], + S5L8702_I2C_PAD_RELEASE, false); + } +} +static int s5l8702_i2c_pads_init(struct s5l8702_i2c_dev *i2c_dev, + struct device *dev) +{ + u32 pads[S5L8702_I2C_PADS]; + int n; + + n = of_property_count_u32_elems(dev->of_node, "apple,pads"); + if (n <= 0) + return 0; + if (n != S5L8702_I2C_PADS) { + dev_warn(dev, "apple,pads needs %d entries, got %d\n", + S5L8702_I2C_PADS, n); + return 0; + } + if (of_property_read_u32_array(dev->of_node, "apple,pads", pads, n)) + return 0; + + i2c_dev->gpio = devm_ioremap(dev, S5L8702_I2C_GPIO_PHYS, + S5L8702_I2C_GPIO_LEN); + i2c_dev->gpiocmd = devm_ioremap(dev, S5L8702_I2C_GPIOCMD_PHYS, 4); + if (!i2c_dev->gpio || !i2c_dev->gpiocmd) { + dev_warn(dev, "pad mux unavailable (ioremap)\n"); + return 0; + } + i2c_dev->pads[0] = pads[0]; + i2c_dev->pads[1] = pads[1]; + i2c_dev->npads = S5L8702_I2C_PADS; return 0; } static int s5l8702_i2c_probe(struct platform_device *pdev) { - dev_dbg(&pdev->dev, "%s", __func__); struct s5l8702_i2c_dev *i2c_dev; - int ret; struct i2c_adapter *adap; + struct resource *res; + int ret; i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL); if (!i2c_dev) return -ENOMEM; platform_set_drvdata(pdev, i2c_dev); i2c_dev->dev = &pdev->dev; + spin_lock_init(&i2c_dev->lock); i2c_dev->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); if (IS_ERR(i2c_dev->regs)) return PTR_ERR(i2c_dev->regs); - i2c_dev->write_busy_poll = of_property_read_bool(pdev->dev.of_node, - "samsung,write-busy-poll"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + s5l8702_i2c_ungate(i2c_dev, res->start); - ret = s5l8702_i2c_init(i2c_dev); - if (ret) { - dev_err(&pdev->dev, "Could initialize I2C controller\n"); - goto err; + ret = devm_clk_bulk_get_all(&pdev->dev, &i2c_dev->clks); + if (ret > 0) { + i2c_dev->num_clks = ret; + ret = clk_bulk_prepare_enable(i2c_dev->num_clks, i2c_dev->clks); + if (ret) + return ret; } + s5l8702_i2c_pads_init(i2c_dev, &pdev->dev); + s5l8702_i2c_pads(i2c_dev, true); + + s5l8702_i2c_init(i2c_dev); + i2c_dev->irq = platform_get_irq(pdev, 0); - if (i2c_dev->irq < 0) { - ret = i2c_dev->irq; - goto err; - } + if (i2c_dev->irq < 0) + return i2c_dev->irq; - ret = devm_request_irq(&pdev->dev, i2c_dev->irq, s5l8702_i2c_isr, IRQF_SHARED, - dev_name(&pdev->dev), i2c_dev); - if (ret) { - dev_err(&pdev->dev, "Could not request IRQ\n"); - goto err; - } + ret = devm_request_irq(&pdev->dev, i2c_dev->irq, s5l8702_i2c_isr, 0, + dev_name(&pdev->dev), i2c_dev); + if (ret) + return ret; init_completion(&i2c_dev->msg_complete); + dev_info(&pdev->dev, "IIC IRQ INTPEND irq=%d CON=0x%03lx\n", + i2c_dev->irq, (unsigned long)S5L8702_I2C_CON_IDLE); adap = &i2c_dev->adapter; i2c_set_adapdata(adap, i2c_dev); @@ -340,29 +843,20 @@ static int s5l8702_i2c_probe(struct platform_device *pdev) adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; - ret = devm_i2c_add_adapter(&pdev->dev, adap); - if (ret) - goto err; - - return 0; - -err: - return ret; + return devm_i2c_add_adapter(&pdev->dev, adap); } -#ifdef CONFIG_OF static const struct of_device_id s5l8702_i2c_of_match[] = { { .compatible = "samsung,s5l8702-i2c" }, - {}, + { } }; MODULE_DEVICE_TABLE(of, s5l8702_i2c_of_match); -#endif static struct platform_driver s5l8702_i2c_driver = { - .probe = s5l8702_i2c_probe, - .driver = { - .name = "i2c-s5l8702", - .of_match_table = of_match_ptr(s5l8702_i2c_of_match), + .probe = s5l8702_i2c_probe, + .driver = { + .name = "i2c-s5l8702", + .of_match_table = s5l8702_i2c_of_match, }, }; module_platform_driver(s5l8702_i2c_driver); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 1a03de7fcfa66c..17e8df0fcb6b90 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1391,3 +1391,7 @@ config TOUCHSCREEN_HIMAX_HX83112B module will be called himax_hx83112b. endif + +config TOUCHSCREEN_APPLE_NIMBUS + tristate "Apple Nimbus (iPod nano 7G) multitouch" + depends on SPI diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 82bc837ca01e2e..75ca270d9a7964 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -117,3 +117,4 @@ obj-$(CONFIG_TOUCHSCREEN_IQS5XX) += iqs5xx.o obj-$(CONFIG_TOUCHSCREEN_IQS7211) += iqs7211.o obj-$(CONFIG_TOUCHSCREEN_ZINITIX) += zinitix.o obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX83112B) += himax_hx83112b.o +obj-$(CONFIG_TOUCHSCREEN_APPLE_NIMBUS) += apple-nimbus.o diff --git a/drivers/input/touchscreen/apple-grape.c b/drivers/input/touchscreen/apple-grape.c new file mode 100755 index 00000000000000..9218cd9327d505 --- /dev/null +++ b/drivers/input/touchscreen/apple-grape.c @@ -0,0 +1,4288 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple Grape / Grape multitouch — N31 SPI2 @ 0x3D200000 + * + * RetailOS 1.0.2 sequences (osos extracts): + * bring-up sub_1A5AC / 2075A / 20766 / 20690 / 11B70 + * teardown sub_1A878: IRQ off, RST, 20690(0), rail off, EN mode 1 + * (13A20 retries: 1A878 + sleep 50 + 1A5AC, max 3) + * grape.bin IS the app. SEC bootloader has no grape/Grape path. + * 1A5AC delays 2075A(1)+5 → 20766(1)+15 → 20690(1)+5 → 11B70 → + * 20848 +15 → 2075A(0)+30 → 20E94 (no extra POR pulse) + * bootload cmd sub_20848(6593): 6593 = 0x19C1 = HBPP ENTER (not a + * firmware byte count). TX 19 C1 + (18 E1)* pad. + * + * Firmware (SPI → controller @ dest=offset, start 0) — 1A640 / 204E0 / 2D640: + * Full "8740" GrapeFirmware-style container on disk: + * 0x000..0x3ff Apple/N31 header (NOT sent over SPI) + * 0x400.. ARM app body; length = le32(file+0x0c) + * rev 3: GID-CBC IV=0 decrypt of ARM body before send + * ARM-only cut (no 8740 magic, e.g. 18 F0 9F E5…): whole file = body + * Chunks: max 0x1FF0. Upload = sub_3B9D0 envelope (see below). ACK 0x4BC1. + * Callsite 2D640: r1 = firmware offset (NOT 0x00100000). EXEC 0x00100018 + * is the bootloader-mapped app PC, not the upload destination. + * + * Calibration (SPI → controller @ 0x00400200) — 2D7A4 / 273A0: + * Per-device touch calibration comes from the A34 handoff, not a host file: + * desc @ 0x2202FE18 (sub_A34(24)): magic 0x53797349 "touch calibration", ptr @ +4 + * memcpy 0x560 from ptr (sub_564) + * cal = bytes at decimal +350, length 0x200, reverse each u32 (sub_273A0) + * then 2D7A4 that window; DATA packet still does b1b0b3b2 wire swizzle + * Callsite 2D7A4: r1 = 0x00400200 + offset. + * No grape-cal.bin, no FTL touch calibration scan, no GrapeFirmware.bin+350. + * Preferred source: U-Boot copy in reserved DRAM, advertised in /chosen + * apple,n31-touch_cal-addr / apple,n31-touch_cal-size. Never consume the original + * A34 pointer. Live A34 ioremap is fallback only. + * + * sub_3B9D0 upload frame (full SPI length = payload_len + 16): + * [0..1] 18 E1 + * [2..3] 30 01 + * [4..5] word_count = len>>2 as hi,lo (len>>10, len>>2) + * [6..9] dest swizzled BYTE1,BYTE0,BYTE3,BYTE2 + * [10..11] u16 byte-sum of [4..9], big-endian + * [12..12+len) payload u32s swizzled B1 B0 B3 B2 + * [12+len..] u32 byte-sum of swizzled payload, stored B1 B0 B3 B2 + * First FW prefix (dest=0,len=0x1FF0): 18 E1 30 01 07 FC 00 00 00 00 01 03 + * Cal prefix (dest=0x400200,len=0x200): 18 E1 30 01 00 80 02 00 00 40 00 C2 + * + * After cal: 2D5B0 RequestCal → 2D54C EXEC → 40 ms → runtime ping. + * Register input only after runtime ping. runtime_ready = ping csum. + * + * Firmware host file: request_firmware("apple/grape.bin") and/or + * FTL gpfw/8740 when fw_prefer_ftl=1. That is the ARM app, not cal. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GRAPE_MAGIC 0xEA +#define GRAPE_PING_TYPE 490 +#define GRAPE_BOOTLOAD_WORD 6593 /* 0x19C1 */ +#define GRAPE_FRAME_LEN 16 +#define GRAPE_READ_MAX 512 +#define GRAPE_SLOTS 8 +#define GRAPE_ABS_X_MAX 239 +#define GRAPE_ABS_Y_MAX 431 +#define GRAPE_SCALE_X_DIV 0x0B1D +#define GRAPE_SCALE_Y_DIV 0x1482 + +#define GRAPE_CHUNK_MAX 0x1FF0 /* 8176 — sub_2D640 */ +#define GRAPE_HDR_LEN 16 /* 2 outer + 10 body hdr + 4 payload sum */ +#define GRAPE_CAL_DEST 0x00400200u /* 2D7A4 literal 0x400200 */ +#define GRAPE_FW_HDR_OFF 350 +#define GRAPE_FW_HDR_LEN 0x200 +#define GRAPE_IMG1_BODY_OFF 1024 /* IMG1 body offset, 0x400 */ +#define GRAPE_IMG1_SIG_LEN 128 /* IMG1 body signature, 0x80 */ +#define GRAPE_IMG1_CERT_LEN 768 /* sub_204E0 guard constant */ +#define GRAPE_ARM_OFFICIAL 0xe970 /* 8740 le32(+0x0c); 204E0 2D640 size */ +#define GRAPE_TOUCH_CAL_MAGIC 0x53797349u /* 'touch calibration' — sub_564 */ +#define GRAPE_TOUCH_CAL_LEN 0x560 +#define GRAPE_A34_BASE 0x2202fe00UL /* sub_A34(idx) = 0x2202FE00+idx */ +#define GRAPE_A34_TOUCH_CAL_DESC (GRAPE_A34_BASE + 0x18) /* sub_A34(24) */ + +/* Whimory FTL (nand-s5l8740.ko) — optional cal/FW from device NAND */ +#define GRAPE_FTL_SECTOR_SIZE 4096U +#define GRAPE_GPFW_TAG 0x67706677u /* 'gpfw' LE */ + +#define GRAPE_ACK_CHUNK 0x4BC1 /* 19393 */ +#define GRAPE_ACK_34AD0 0x4AD1 /* 19153 */ +#define GRAPE_POST_POKE 0x011F /* 287 */ +#define GRAPE_WATCHDOG_MS 1000 /* IRQ-primary: this thread is a fallback */ +#define GRAPE_EXEC_SETTLE_MS 40 /* 273A0 tail: 410522(40) */ + +#define GRAPE_GPIO_EN 0x0E +#define GRAPE_GPIO_RST 0x27 +#define GRAPE_GPIO_IRQ 0x26 + +#define S5L8740_GPIO_PHYS 0x3cf00000UL +#define S5L8740_GPIOCMD_PHYS 0x3cf001e0UL +#define S5L8740_SPI2_PHYS 0x3d200000UL +#define SPI2_CTRL 0x00 +#define SPI2_SETUP 0x04 +#define SPI2_STATUS 0x08 +#define SPI2_PIN 0x0c +#define SPI2_TXDATA 0x10 +#define SPI2_RXDATA 0x20 +#define SPI2_CLKDIV 0x30 +#define SPI2_RXLIMIT 0x34 +#define SPI2_UNK4C 0x4c +#define SPI2_CTRL_FIFO_RST 0x0c +#define SPI2_CTRL_ENABLE 0x01 +#define SPI2_SETUP_11B70 0x403e /* 11B70(2, 0x1A, 0x2EE0, 1) */ +#define SPI2_CS_BIT BIT(1) + +#define GRAPE_Z2_HDR_LEN 16 +#define GRAPE_Z2_MAGIC_5A5A 0x5a5a0000u +#define GRAPE_Z2_MAGIC_C3F5 0xc3f50000u +#define GRAPE_Z2FW_MAGIC 0x5746325au /* apple_z2 "Z2FW" container */ + +#define GRAPE_CS_BEGIN BIT(0) +#define GRAPE_CS_END BIT(1) + +/* + * 0 lets the SPI controller own the engine setup, which is what the stock + * firmware does: one sub_11B70 configuration, divider 2 for a 12 MHz bus. + * This driver used to reprogram the same registers afterwards with a much + * larger divider, quietly running the bus eight times too slow. + */ +static int spi_clkdiv; +module_param(spi_clkdiv, int, 0644); +MODULE_PARM_DESC(spi_clkdiv, + "Override SPI2 CLKDIV (0 = leave it to the SPI controller)"); +/* + * The stock code retries the transfer itself three times before giving + * up; the caller's retry re-runs the whole bring-up. + */ +static unsigned int download_tries = 3; +module_param(download_tries, uint, 0644); +MODULE_PARM_DESC(download_tries, + "Firmware download attempts before re-running bring-up"); + +/* + * The stock path does nothing after a good download but wait 2 ms and + * enable the interrupt. These register pokes keep speaking the boot + * protocol at a part that should already be running its application. + */ +static int reset_hold_ms = 5; +module_param(reset_hold_ms, int, 0644); +MODULE_PARM_DESC(reset_hold_ms, "RST low ms in optional extra_por_pulse (1A5AC uses 5)"); +/* 1A5AC: 2075A(0) then sleep 30 before 20E94 — not FAMILY 15 / old 100. */ +static int reset_release_ms = 30; +module_param(reset_release_ms, int, 0644); +MODULE_PARM_DESC(reset_release_ms, "ms after RST release before probe/FW (1A5AC=30)"); +static int extra_por_pulse; +module_param(extra_por_pulse, int, 0644); +MODULE_PARM_DESC(extra_por_pulse, "1=extra RST low/high/low before 1A5AC (not in RetailOS)"); +static int go_spi_setup; +module_param(go_spi_setup, int, 0644); +MODULE_PARM_DESC(go_spi_setup, "SPI2 SETUP override for 2D54C GO (0=11B70)"); +/* 0=8-bit PIO (RetailOS HBPP default), 1=u16 TXDATA pairs, 2=spi_sync */ +/* + * Read a report whenever ATTN is asserted, without requiring the ping + * to checksum first. Off restores the ping-gated behaviour. + */ +static bool attn_read = true; +module_param(attn_read, bool, 0644); +MODULE_PARM_DESC(attn_read, + "Read a frame when ATTN asserts even if the ping fails"); + +static bool post_poke_strict; +module_param(post_poke_strict, bool, 0644); +MODULE_PARM_DESC(post_poke_strict, + "Require a known 2D5B0 status before EXEC"); + +/* + * Never park. + * + * Parking sets stopped and ends servicing, so the ATTN line stops being + * read and the interrupt stops being acted on. That makes it impossible + * to answer the one question stock actually asks after EXEC -- does the + * part raise ATTN when the panel is touched -- because the driver has + * already given up by the time anyone puts a finger on the glass. + * + * For measuring only. A part that never starts will sit here forever. + */ +/* + * Consecutive failed services before ATTN is masked. + * + * The interrupt is level-triggered on a line the application holds low + * until a report is read out of it, and IRQF_ONESHOT re-enables as soon + * as the threaded handler returns. So a read that never succeeds means + * the line is never released and the interrupt re-fires immediately, + * forever: the count climbed past twelve thousand while nothing was + * being delivered. That is real CPU burned on a part that is not + * answering. + * + * Masking after a ceiling keeps the failure visible without letting it + * spin. Write attn_rearm to try again once something has changed. + */ +static unsigned int irq_fail_max = 20; +module_param(irq_fail_max, uint, 0644); +MODULE_PARM_DESC(irq_fail_max, + "Consecutive failed services before ATTN is masked (0 = never)"); + +static bool no_park; +module_param(no_park, bool, 0644); +MODULE_PARM_DESC(no_park, + "Keep servicing after a failed bring-up instead of parking"); + +static bool park_power_down = true; +module_param(park_power_down, bool, 0644); +MODULE_PARM_DESC(park_power_down, + "Cut the rail when parking (0 keeps it up for raw_xfer)"); + +static int go_xfer; +module_param(go_xfer, int, 0644); +MODULE_PARM_DESC(go_xfer, + "2D54C EXEC xfer: 0=8-bit burst 1=u16 burst 2=spi_sync"); +/* Default 0: N31 RetailOS path has no Z2/5A5A host container. */ +static int prepend_z2_hdr; +module_param(prepend_z2_hdr, int, 0644); +MODULE_PARM_DESC(prepend_z2_hdr, "0=none (N31 default) 1=5A5A+BE len+CRC32 2=c3f5 hdr"); +static int chunk_spi; +module_param(chunk_spi, int, 0644); +MODULE_PARM_DESC(chunk_spi, "1=spi_sync chunk xfers (apple_z2-style atomic CS)"); +/* Default quiet: bring-up spam off; set quiet=0 or verbose=1 for detail. */ +static int quiet = 1; +module_param(quiet, int, 0644); +MODULE_PARM_DESC(quiet, "1=minimal logs (default); 0=verbose bring-up (or apple_grape.verbose=1)"); + +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Verbose Grape logs (overrides quiet; default N)"); +static int skip_download; +module_param(skip_download, int, 0644); +MODULE_PARM_DESC(skip_download, "1=bootload+ping only, no FW chunks"); + +static int force_gid; +module_param(force_gid, int, 0644); +MODULE_PARM_DESC(force_gid, "1=422FFA decrypt attempt even without 8740 rev3 hdr"); + +static int cal_try_dt = 1; +module_param(cal_try_dt, int, 0644); +MODULE_PARM_DESC(cal_try_dt, + "Read U-Boot touch calibration copy from /chosen apple,n31-touch_cal-* (default on)"); + +static int cal_try_a34 = 1; +module_param(cal_try_a34, int, 0644); +MODULE_PARM_DESC(cal_try_a34, + "Fallback: read live A34 descriptor at 0x2202FE18 (default on)"); + +/* + * Stock does not ping for a runtime status after EXEC. + * + * sub_2D54C returns sub_40F770(...) == 0 -- success is that the 12-byte + * EXEC transfer completed, nothing more. sub_20E94 sets the success flag + * from that, and sub_20490(1) then does exactly two things: puts pad 0x26 + * (38) in mode 0 as an input and arms its interrupt. The host waits for + * ATTN; it never asks the part to identify itself again. + * + * That matters because 0x4F81 is a bootloader-format status. A part that + * has correctly left the bootloader has no reason to answer a + * bootloader-style ping, so requiring one turns a good boot into a + * failure. Worse, the retry loop below only exited on that ping, so a + * running application was torn down and re-uploaded three times before we + * gave up and parked. + * + * Default off, which is stock. Set it to require the ping back if a build + * needs the old, stricter behaviour for comparison. + */ +static bool require_runtime; +module_param(require_runtime, bool, 0644); +MODULE_PARM_DESC(require_runtime, + "Require a runtime ping after EXEC before registering input " + "(stock does not; default N)"); + +static unsigned int exec_wait_ms = 40; +module_param(exec_wait_ms, uint, 0644); +MODULE_PARM_DESC(exec_wait_ms, + "ms after EXEC before runtime ping (OSOS 2D54C success wait = 40)"); + +static unsigned int cal_ftl_start; +module_param(cal_ftl_start, uint, 0644); +MODULE_PARM_DESC(cal_ftl_start, "FTL LBA to start gpfw/8740 firmware scan (not cal)"); + +static unsigned int cal_ftl_count = 4096; +module_param(cal_ftl_count, uint, 0644); +MODULE_PARM_DESC(cal_ftl_count, + "FTL LBAs to scan for gpfw/8740 firmware (not touch calibration cal)"); + +/* + * fw_prefer_ftl is retired -- the gpfw store does not exist on this device. + * + * Stock's tag lookup is not an FPART special block. sub_201FC('gpfw') goes + * through sub_683E0 -> sub_2F51C -> sub_38DA8 (ATABlock) -> sub_26794 / + * sub_261A4 and bottoms out in ATA READ SECTORS / READ DMA against a drive + * whose IDENTIFY model is "NAND FLASH DRIVE" -- an ATA-shaped facade over + * Whimory, reading plain FTL logical blocks of 4096 bytes, in the same LBA + * space as the FAT32 volume. + * + * It expects LBA 0 to be an MBR, takes the entry whose type byte is 0x00 or + * 0x3F (RetailOS writes the firmware partition as entry 1, type 0x3F, at LBA + * 64), then requires le32 @ 0x100 == 0x5B68695D ("[hi]" byte-reversed) and + * walks sixteen 40-byte directory entries for tag 0x67706677. + * + * Measured on this unit, LBA 0 is the FAT32 VBR, not an MBR: + * + * 00000000: eb3c 902a 554f 4b4a 4948 4300 1004 2000 .<.*UOKJIHC... . + * 000001be: 0000 0000 0000 0000 0000 0000 0000 0000 + * 000001fe: 55aa + * 00000100: 0000 0000 0000 0000 0000 0000 0000 0000 + * + * eb 3c 90 jump, OEM "*UOKJIHC", all four partition entries zero, no "[hi]" + * header, and the 5d 69 68 5b magic appears nowhere in block 0 or block 64. + * The FTL LBA space begins directly with the FAT volume, so the lookup would + * pass its 0xAA55 test on the VBR, match type 0x00 on a zeroed entry, take + * start LBA 0, fail the "[hi]" check and give up. + * + * MEMORY[0x8A8FAA4] must therefore be non-zero on this hardware and stock + * takes the filesystem branch. The option could never have found anything, + * and leaving it selectable invites someone to enable it while chasing a + * touch failure and change two variables at once. + */ + +static int fw_allow_file = 1; +module_param(fw_allow_file, int, 0644); +MODULE_PARM_DESC(fw_allow_file, "1=allow apple/grape.bin fallback"); + +/* + * 2D640 r1 = firmware offset (start 0). Do NOT default to 0x00100000 — + * that is the EXEC-mapped app window, not the upload dest. Override only + * for deliberate A/B experiments. + */ +static unsigned int fw_dest; +module_param(fw_dest, uint, 0644); +MODULE_PARM_DESC(fw_dest, + "2D640 ARM upload base dest (OSOS offset 0; cal stays 0x400200)"); + +static unsigned int exec_addr = 0x00100018; +module_param(exec_addr, uint, 0644); +MODULE_PARM_DESC(exec_addr, + "2D54C EXEC word0 (OSOS 0x00100018; bootloader-mapped PC)"); +static unsigned int exec_word1 = 0x00000100; +module_param(exec_word1, uint, 0644); +MODULE_PARM_DESC(exec_word1, "2D54C EXEC word1 (OSOS 0x00000100)"); + + +static bool grape_verbose; + +#define grape_dev_vinfo(dev, fmt, ...) \ + do { \ + if (grape_verbose) \ + dev_info((dev), fmt, ##__VA_ARGS__); \ + else \ + dev_dbg((dev), fmt, ##__VA_ARGS__); \ + } while (0) + +#define grape_vinfo(n, fmt, ...) \ + do { \ + if (!(n)->parked) \ + grape_dev_vinfo(&(n)->spi->dev, fmt, ##__VA_ARGS__); \ + } while (0) + +struct grape { + struct spi_device *spi; + struct input_dev *input; + struct gpio_desc *enable; + struct gpio_desc *reset; + struct gpio_desc *attn; + void __iomem *gpio_base; + void __iomem *gpiocmd; + void __iomem *spi2; + struct task_struct *thread; + struct mutex lock; + bool stopped; + bool fw_uploaded; /* 2D640/2D7A4 transport ACKs */ + bool cal_uploaded; + bool requestcal_done; /* 2D5B0 / 1F01 path done */ + bool exec_sent; /* 2D54C SPI xfer completed — not runtime */ + u8 *raw_rx; /* last raw_xfer response */ + unsigned int raw_n; + unsigned int attn_fails; + bool irq_masked; + unsigned int irq_fails; /* recycle must not reset this */ + /* true when the blob came from the FTL gpfw tag, false for a + * host file. Only the gpfw path inspects the "8740" magic. */ + bool fw_from_ftl; + unsigned int irq_count; + unsigned int irq_seen; + bool runtime_ready; /* valid 182590 ping checksum */ + bool fw_loaded; /* alias of runtime_ready for older call sites */ + bool fw_tried; + bool spi_ok; + bool use_irq; + bool blob16; /* S5L TXDATA is 8-bit; 16-bit writes fail 4BC1 */ + bool parked; /* give up after recycle budget — stop SPI spam */ + bool have_touch_cal; + bool have_cal; + bool touch_cal_sysfs; + u8 touch_cal[GRAPE_TOUCH_CAL_LEN]; + u8 cal_upload[GRAPE_FW_HDR_LEN]; + int irq; + unsigned int ping_fails; + unsigned int recycle_count; + int spi_fam; /* 0 auto, 1 ROS, 2 Classic */ + unsigned int tx_timeouts; + unsigned int rx_timeouts; +}; + +/* From gpio-d1830.c — OSOS 20766 / 6644(4) / reg16 bit5 */ + +static u16 grape_sum16(const u8 *buf, int len) +{ + u16 sum = 0; + + while (len-- > 0) + sum += *buf++; + return sum; +} + +static void grape_gpiocmd_mode(struct grape *n, unsigned int gpio, u16 mode, int val) +{ + void __iomem *bank; + u32 pin = gpio & 7; + u32 dir; + u8 cmd; + + if (!n->gpiocmd || !n->gpio_base) + return; + + bank = n->gpio_base + 32 * (gpio >> 3); + if (mode == 1) { + /* 43D38C: mode 1 also sets DIR, then GPIOCMD 14/15 */ + cmd = val ? 15 : 14; + dir = readl(bank + 0x14); + writel(dir | BIT(pin), bank + 0x14); + } else if (mode == 0xFFFE) { + dir = readl(bank + 0x14); + writel(dir & ~BIT(pin), bank + 0x14); + cmd = 0; + } else { + cmd = (u8)mode; + dir = readl(bank + 0x14); + writel(dir | BIT(pin), bank + 0x14); + } + writel(((gpio >> 3) << 16) | (pin << 8) | cmd, n->gpiocmd); +} + +/* sub_23CD0(gpio, on) — PUNC bit at bank+0x10 */ +static void grape_punc(struct grape *n, unsigned int gpio, bool set) +{ + void __iomem *bank; + u32 pin, punc; + + if (!n->gpio_base) + return; + bank = n->gpio_base + 32 * (gpio >> 3); + pin = gpio & 7; + punc = readl(bank + 0x10); + if (set) + punc |= BIT(pin); + else + punc &= ~BIT(pin); + writel(punc, bank + 0x10); +} + +/* sub_20690(a1) — SPI2 pads 0x57–0x5A */ +static void grape_spi2_pinmux(struct grape *n, bool on) +{ + if (on) { + grape_punc(n, 0x57, false); + grape_gpiocmd_mode(n, 0x57, 5, 0); + grape_gpiocmd_mode(n, 0x58, 3, 0); + grape_gpiocmd_mode(n, 0x59, 3, 0); + grape_gpiocmd_mode(n, 0x5A, 3, 0); + } else { + grape_gpiocmd_mode(n, 0x57, 0xFFFE, 0); + grape_punc(n, 0x57, true); + grape_gpiocmd_mode(n, 0x58, 1, 0); + grape_gpiocmd_mode(n, 0x59, 1, 0); + grape_gpiocmd_mode(n, 0x5A, 0xFFFE, 0); + } +} + +/* + * sub_1A878 — disable / retry power-cut: + * 20490(0), RST assert, 20690(0), 20766(0) rail off + EN mode 1. + */ +static void grape_power_down(struct grape *n) +{ + grape_gpiocmd_mode(n, GRAPE_GPIO_IRQ, 0xFFFE, 0); + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 0); + grape_spi2_pinmux(n, false); + d1830_grape_rail(false); + grape_gpiocmd_mode(n, GRAPE_GPIO_EN, 1, 0); + grape_vinfo(n, "1A878 power-cut (RST hold, rail off, EN mode 1)\n"); +} + +/* + * CLKCON oracle replay. + * + * Deliberately not named after any peripheral. Nothing in the extracted + * Grape boot path -- sub_13A20, sub_1A5AC and the 2075A/20766/20690/ + * 11B70/20848/20E94/20490 chain -- writes CLKCON or calls sub_41CBD8 at + * all, so there is no evidence naming any of these bits as a touch + * clock. What is established is narrower and still worth replaying: + * Linux is missing global CLKCON state that both the Apple bootloader + * and the running stock system have. + * + * The N31 bootloader sets, at 0x3C500000: + * + * +0x08 = 0x2009200A we inherit this unchanged + * +0x0C = 0x80008000 we have 0x00000000 + * +0x10 = 0x00008000 we have 0x00000000 + * +0x14 = 0x80008000 we have 0x00002200 + * +0x18 = 0x20012001 we inherit this unchanged + * + * so something between DFU, u-boot and Linux clears two of them and + * rewrites a third, while leaving their neighbours alone. + * + * Polarity is inverted -- sub_41CBD8 clears a bit to enable and sets it + * to disable -- so our zeroes mean more clocks running than stock, not + * fewer. And the low nibble of +0x10 is a divider, not a flag: the rate + * decoder reads (MEMORY[0x3C500010] & 0xF) + 1. The stock live-touch + * 0x8000 -> 0x8004 delta is therefore a divider change, not an enable. + * + * clkcon_oracle=1 replay stock's live-touch values + * clkcon_oracle=2 replay the bootloader's post-init values + * + * Values are restored on unload so a failed experiment does not leave + * the clock tree in a state the rest of the kernel did not ask for. + */ +#define N31_CLKCON_PHYS 0x3c500000UL + +static int clkcon_oracle; +module_param(clkcon_oracle, int, 0644); +MODULE_PARM_DESC(clkcon_oracle, + "Replay observed CLKCON state: 1=stock touch, 2=bootloader"); + +static const unsigned int n31_clkcon_off[] = { 0x08, 0x0c, 0x10, 0x14 }; +static const u32 n31_clkcon_touch[] = { + 0xa009200a, 0x80000001, 0x00008004, 0x80002200, +}; +static const u32 n31_clkcon_boot[] = { + 0x2009200a, 0x80008000, 0x00008000, 0x80008000, +}; +static u32 n31_clkcon_saved[ARRAY_SIZE(n31_clkcon_off)]; +static bool n31_clkcon_applied; + +static void grape_clkcon_replay(struct grape *n) +{ + const u32 *want; + void __iomem *ck; + unsigned int i; + + if (!clkcon_oracle || n31_clkcon_applied) + return; + want = (clkcon_oracle == 2) ? n31_clkcon_boot : n31_clkcon_touch; + + ck = ioremap(N31_CLKCON_PHYS, 0x80); + if (!ck) + return; + for (i = 0; i < ARRAY_SIZE(n31_clkcon_off); i++) { + n31_clkcon_saved[i] = readl(ck + n31_clkcon_off[i]); + writel(want[i], ck + n31_clkcon_off[i]); + dev_info(&n->spi->dev, + "ORACLE_REPLAY clkcon+0x%02x %08x -> %08x\n", + n31_clkcon_off[i], n31_clkcon_saved[i], + readl(ck + n31_clkcon_off[i])); + } + n31_clkcon_applied = true; + iounmap(ck); +} + +static void grape_clkcon_restore(void) +{ + void __iomem *ck; + unsigned int i; + + if (!n31_clkcon_applied) + return; + ck = ioremap(N31_CLKCON_PHYS, 0x80); + if (!ck) + return; + for (i = 0; i < ARRAY_SIZE(n31_clkcon_off); i++) + writel(n31_clkcon_saved[i], ck + n31_clkcon_off[i]); + n31_clkcon_applied = false; + iounmap(ck); +} + +/* + * sub_11B70(2, 0x1A, 0x2EE0, 1) after every 20690(1). + * 1A5AC always re-inits SPI2 here. Skipping it after 1A878 remux + * left an extra SCLK edge: 0x1f01/0x4879 came back as 0x0f80/0xa43c + * and shifted one more bit on each retry. + */ +static void grape_spi2_11b70(struct grape *n) +{ + void (*reinit)(void); + u32 setup; + + if (!n->spi2) + return; + reinit = (void (*)(void))__symbol_get("s5l8702_spi2_reinit"); + if (reinit) { + reinit(); + __symbol_put("s5l8702_spi2_reinit"); + } else { + /* Controller absent: same sequence, same numbers. */ + writel(0xf, n->spi2 + SPI2_STATUS); + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(10, n->spi2 + 0x44); + writel(24, n->spi2 + 0x38); /* 24 * a4=1 */ + writel(255, n->spi2 + 0x40); + writel(144, n->spi2 + 0x3c); /* 3 * 24 * (1+1) */ + writel(2, n->spi2 + SPI2_CLKDIV); /* 24000/12000 */ + writel(SPI2_SETUP_11B70, n->spi2 + SPI2_SETUP); + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(SPI2_CTRL_ENABLE, n->spi2 + SPI2_CTRL); + } + if (spi_clkdiv) + writel(clamp(spi_clkdiv, 1, 255), n->spi2 + SPI2_CLKDIV); + setup = readl(n->spi2 + SPI2_SETUP); + grape_vinfo(n, "11B70 SPI2 SETUP=0x%x CLKDIV=%u\n", + setup, readl(n->spi2 + SPI2_CLKDIV)); +} + +static void grape_spi2_cs(struct grape *n, bool assert) +{ + u32 pin; + + if (!n->spi2) + return; + pin = readl(n->spi2 + SPI2_PIN); + if (assert) + pin &= ~SPI2_CS_BIT; + else + pin |= SPI2_CS_BIT; + writel(pin, n->spi2 + SPI2_PIN); +} + +/* + * Tight 4043D0 PIO for 16-byte app frames. OSOS 11B70 leaves SETUP + * bit5 set so 40F770 takes the DMA path — continuous clocks. Linux + * per-byte spi_sync gaps are fine for the bootloader, not the app. + */ +static void grape_spi2_fifo_flush(struct grape *n) +{ + if (!n->spi2) + return; + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(0xf, n->spi2 + SPI2_STATUS); +} + +/* + * SPI2 status polling. + * + * Both transfer loops below used to do this: + * + * guard = 100000; + * do { st = readl(STATUS); } while (!(st & 0xf800) && --guard); + * rx[i] = readl(RXDATA); + * + * The guard running out was not treated as a failure. RXDATA was read + * either way and the function returned 0, so when the ready bit never set, + * the FIFO's previous contents were handed back as if they were a reply. + * That does not look like noise because it is not noise: it is the same + * stale word every time, which is where the repeating 4f81 came from, and + * it is why the part appeared to answer while telling us nothing. + * + * Two changes. A timeout is now an error that propagates, so a dead bus + * reports as dead instead of inventing a reply. And because this engine has + * been seen reporting readiness in either of two encodings depending on the + * init path that ran, both are polled in one loop until one genuinely + * satisfies; that result is latched, logged once, and used exclusively + * afterwards. Latching matters: alternating between interpretations is how + * a transfer gets declared complete early. + */ +#define GRAPE_SPI_GUARD 100000u + +#define GRAPE_TXBUSY_ROS 0x7c0u +#define GRAPE_TXBUSY_RESIDUE 0x40u /* stays set after 11B70 setup */ +#define GRAPE_TXLVL_CLASSIC 0x1f0u +#define GRAPE_RXRDY_ROS 0xf800u +#define GRAPE_RXLVL_CLASSIC 0x3e00u + +static int grape_spi_family; +module_param(grape_spi_family, int, 0644); +MODULE_PARM_DESC(grape_spi_family, + "SPI2 status encoding: 0=auto-latch, 1=ROS (0x7C0/0xF800), 2=Classic (0x1F0/0x3E00)"); + +/* + * Fail closed on a receive timeout. + * + * This was zero-initialised while its description said strict was the + * default. In the direct-PIO path a receive timeout was then followed by a + * read of whatever RXDATA still held, so stale FIFO contents could satisfy + * an 0x4bc1 ACK, a status read, or a runtime-ready check. A silent + * controller could look like a working one. + */ +static bool grape_strict_rx = true; +module_param(grape_strict_rx, bool, 0644); +MODULE_PARM_DESC(grape_strict_rx, + "1=receive timeout fails the transfer (default); 0=legacy, read the FIFO anyway"); + +static void grape_latch_fam(struct grape *n, int fam, u32 st) +{ + if (n->spi_fam == fam) + return; + n->spi_fam = fam; + dev_info(&n->spi->dev, + "SPI2 status encoding latched: %s (STATUS=0x%08x)\n", + fam == 1 ? "ROS(0x7C0/0xF800)" : "Classic(0x1F0/0x3E00)", st); +} + +/* Wait for the transmit side to accept another word. */ +static int grape_wait_tx(struct grape *n) +{ + unsigned int guard = GRAPE_SPI_GUARD; + u32 st = 0; + + while (guard--) { + st = readl(n->spi2 + SPI2_STATUS); + + if (n->spi_fam != 2) { + u32 b = st & GRAPE_TXBUSY_ROS; + + if (b == 0 || b == GRAPE_TXBUSY_RESIDUE) { + grape_latch_fam(n, 1, st); + return 0; + } + } + if (n->spi_fam != 1 && (st & GRAPE_TXLVL_CLASSIC) == 0) { + grape_latch_fam(n, 2, st); + return 0; + } + cpu_relax(); + } + + if (!n->tx_timeouts++) + dev_warn(&n->spi->dev, + "SPI2 transmit never went idle (STATUS=0x%08x)\n", st); + return -ETIMEDOUT; +} + +/* Wait for a received word to actually be available. */ +static int grape_wait_rx(struct grape *n) +{ + unsigned int guard = GRAPE_SPI_GUARD; + u32 st = 0; + + while (guard--) { + st = readl(n->spi2 + SPI2_STATUS); + + if (n->spi_fam != 2 && (st & GRAPE_RXRDY_ROS)) { + grape_latch_fam(n, 1, st); + return 0; + } + if (n->spi_fam != 1 && (st & GRAPE_RXLVL_CLASSIC)) { + grape_latch_fam(n, 2, st); + return 0; + } + cpu_relax(); + } + + if (!n->rx_timeouts++) + dev_warn(&n->spi->dev, + "SPI2 receive never became ready (STATUS=0x%08x), refusing to report stale FIFO\n", + st); + return -ETIMEDOUT; +} + +/* + * Transfers go through the SPI core. + * + * This driver is bound as an spi_device yet drove the SPI2 registers itself: + * its own CS, its own FIFO reset, its own TXDATA/RXDATA polling. That meant + * none of the usual guarantees applied -- no bus locking against another + * client, no controller-side clock or mode setup, no error propagation -- + * and it duplicated the controller driver's completion logic well enough to + * drift from it. Two copies of a tricky wait loop is one copy too many. + * + * The only thing the register path really offered was holding CS down across + * several calls, which the core expresses with cs_change on the final + * transfer of a message. The controller now honours that, so the whole thing + * is reachable through spi_sync(). + * + * The legacy path is kept behind grape_use_spi=0 purely so the two can be + * compared on hardware; it is not the supported route. + */ +static bool grape_use_spi; /* opt-in until proven on glass */ +module_param(grape_use_spi, bool, 0644); +MODULE_PARM_DESC(grape_use_spi, + "1=transfer via the SPI core (default); 0=legacy direct SPI2 register PIO"); + +/* + * One HBPP burst as a single spi_message. + * + * @hold_cs: leave the part selected when the message ends, for a frame that + * spans more than one call. Expressed as cs_change on the last + * transfer, which is exactly what that flag means there. + * + * A TX-only caller passes rx == NULL straight through, so spi-s5l8702 takes + * its SPIRXLIMIT = 0 branch and never waits for a receive byte. + * + * This used to allocate a throwaway drain buffer, on the reasoning that the + * part clocks a reply out regardless and an undrained 8 KiB chunk would + * overrun the RX FIFO. Stock does not do that: sub_2D640 sends every chunk + * as sub_40F770(buf, len + 16, 0, 0) with rxlen zero, and sub_4043D0 then + * writes SPIRXLIMIT = 0 and never reads SPIRXDATA. Supplying a drain buffer + * forced full duplex on exactly the frames stock sends one-way. + */ +static int grape_spi_burst(struct grape *n, const u8 *tx, u8 *rx, + unsigned int len, bool hold_cs, unsigned int bpw) +{ + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + .cs_change = hold_cs, + .bits_per_word = bpw, + }; + struct spi_message m; + int ret; + + if (!n->spi) + return -ENODEV; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(n->spi, &m); + + if (ret && !n->tx_timeouts++) + dev_warn(&n->spi->dev, "spi_sync failed: %d\n", ret); + return ret; +} + +static int grape_burst_ex(struct grape *n, const u8 *tx, u8 *rx, + unsigned int len, unsigned int cs_flags) +{ + unsigned int i; + int ret = 0; + + if (grape_use_spi) + return grape_spi_burst(n, tx, rx, len, + !(cs_flags & GRAPE_CS_END), 8); + + if (!n->spi2) + return -ENODEV; + if (cs_flags & GRAPE_CS_BEGIN) { + grape_spi2_cs(n, true); + /* No delay: sub_40F770 clocks the first byte straight + * after sub_4045D4(2, 0). */ + /* + * Read CS back rather than trusting the write. A MISO that + * floats for a whole frame looks identical to a part that + * never saw the transaction; the difference is whether + * SPIPIN bit 1 is actually low while we clock. + */ + dev_info_once(&n->spi->dev, + "CS during xfer: PIN=0x%08x (%s) SETUP=0x%08x STATUS=0x%08x\n", + readl(n->spi2 + SPI2_PIN), + (readl(n->spi2 + SPI2_PIN) & SPI2_CS_BIT) ? + "HIGH, NOT asserted" : "low, asserted", + readl(n->spi2 + SPI2_SETUP), + readl(n->spi2 + SPI2_STATUS)); + grape_spi2_fifo_flush(n); + writel(readl(n->spi2 + SPI2_SETUP) & ~BIT(0), n->spi2 + SPI2_SETUP); + writel(readl(n->spi2 + SPI2_STATUS) | 0x400000u, n->spi2 + SPI2_STATUS); + } + /* + * A TX-only frame must not touch the receive side at all. + * + * sub_4043D0 writes SPIRXLIMIT = rx ? 1 : 0 and only waits for and + * reads SPIRXDATA when there is an rx buffer. Every firmware and + * calibration DATA frame goes out as sub_40F770(buf, len + 16, 0, 0) + * -- rxlen zero -- so on stock those chunks never wait for a receive + * byte. + * + * We wrote SPIRXLIMIT = 1 unconditionally and then waited for and + * drained a receive byte for every byte of every chunk, which is a + * per-byte wait on a FIFO stock never asks to fill. + */ + for (i = 0; i < len; i++) { + writel(rx ? 1 : 0, n->spi2 + SPI2_RXLIMIT); + + ret = grape_wait_tx(n); + if (ret) + goto out; + + writel(tx[i], n->spi2 + SPI2_TXDATA); + writel(1, n->spi2 + SPI2_UNK4C); + + if (rx) { + ret = grape_wait_rx(n); + if (ret && grape_strict_rx) + goto out; + ret = 0; + rx[i] = (u8)readl(n->spi2 + SPI2_RXDATA); + } + } + +out: + if (cs_flags & GRAPE_CS_END) { + writel(readl(n->spi2 + SPI2_SETUP) & ~0x400001u, n->spi2 + SPI2_SETUP); + grape_spi2_cs(n, false); + } + return ret; +} + +static int grape_burst(struct grape *n, const u8 *tx, u8 *rx, unsigned int len) +{ + return grape_burst_ex(n, tx, rx, len, GRAPE_CS_BEGIN | GRAPE_CS_END); +} + +/* + * The 16-bit burst variants are gone. + * + * They existed to drive the SPISETUP word-size field so each pair would go + * out high byte first, matching upstream apple_z2. sub_11B70 ORs the literal + * 0x4000 into SPISETUP once per port and never touches those bits again, and + * both of its call sites pass the same mode 0x1A, so the two ports cannot + * even differ. Bit 13 is never set anywhere in either image. + * + * The pair swap the Grape needs is real, but stock does it in the CPU while + * packing the buffer -- grape_build_upload_frame() already matches sub_3B9D0 + * byte for byte -- not by changing the controller word width. + */ +static int grape_burst_u16_ex(struct grape *n, const u8 *tx, u8 *rx, + unsigned int len, unsigned int cs_flags) +{ + return grape_burst_ex(n, tx, rx, len, cs_flags); +} + +static int grape_burst_u16(struct grape *n, const u8 *tx, u8 *rx, + unsigned int len) +{ + return grape_burst_u16_ex(n, tx, rx, len, + GRAPE_CS_BEGIN | GRAPE_CS_END); +} + +static int grape_burst16(struct grape *n, const u8 *tx, u8 *rx) +{ + return grape_burst(n, tx, rx, GRAPE_FRAME_LEN); +} + +/* + * One transport for every Grape frame. + * + * Stock has exactly one: sub_40F770 takes the mutex, asserts CS via + * sub_4045D4(2, 0), calls sub_4043D0(..., port 2), releases CS, unlocks. + * Probe handshake, firmware chunks, calibration, register writes, EXEC, + * ping and report reads all go through it. + * + * This driver had two. grape_probe_26494, grape_status_poll, + * grape_cmd_34ad0, the 0x011F poke, grape_bootload_cmd and + * grape_read_reports went through spi_sync, while grape_send_chunk_ex, + * grape_cmd_2d54c and grape_ping went through direct SPI2 register PIO -- + * so a single bring-up sequence alternated between two transports with + * different CS handling, different FIFO handling and different error + * paths. grape_xfer also allocated a drain buffer, forcing full duplex on + * frames stock sends one-way. + * + * Everything now funnels through grape_burst_ex(), which dispatches on + * grape_use_spi so the PIO-versus-SPI-core comparison stays a single + * one-variable experiment instead of a property of which function you + * happened to call. + */ +static int grape_xfer(struct grape *n, const u8 *tx, u8 *rx, unsigned int len) +{ + return grape_burst_ex(n, tx, rx, len, GRAPE_CS_BEGIN | GRAPE_CS_END); +} + +/* sub_2C87E — bootloader opcode whitelist */ +static bool grape_opcode_known(u16 w); +static bool grape_looks_like_arm(const u8 *p, size_t n); +static void grape_bswap32_words(u8 *p, unsigned int len); +static u32 grape_sum32(const u8 *p, unsigned int len); + +/* + * The eight words sub_2C87E accepts, and only those. + * + * 0x4F81 used to be in this list. It is not in stock's: + * + * return a1 == 6369 || a1 == 6817 || a1 == 7937 || a1 == 6593 + * || a1 == 18553 || a1 == 19393 || a1 == 18793 || a1 == 19153; + * + * which is 0x18E1 0x1AA1 0x1F01 0x19C1 0x4879 0x4BC1 0x4969 0x4AD1. + * So 0x4F81 is not a bootloader status the firmware recognises at + * all -- it is a word stock never expects to see. + * + * That matters at sub_26494, which probes with 1A A1 + 18 E1 padding + * and requires BOTH returned halfwords to pass this test before + * sub_20E94 will download anything. Accepting 0x4F81 here let our + * probe succeed where stock's would have refused, so we uploaded + * firmware into a part stock would never have downloaded to. + * + * grape_bootloader_word() below still lists 0x4F81, because naming + * what came back is useful even when it is not a legal status. + */ +static bool grape_opcode_known(u16 w) +{ + return w == 0x18e1 || w == 0x1aa1 || w == 0x1f01 || w == 0x19c1 || + w == 0x4879 || w == 0x4bc1 || w == 0x4969 || w == 0x4ad1; +} + +/* + * Words the bootloader answers with. + * + * 0x4F81 says less than it looks like it says. + * + * It is not in sub_2C87E, the firmware own list of legal protocol + * words, and it appears nowhere in the OSOS image. What it is not is + * evidence about the application. + * + * The ping that provokes it is genuine. sub_182590 sends 490 as a + * little-endian u32, then zeros, with a 16-bit checksum over the + * first 14 bytes placed at offset 14, and reads 16 bytes back; a + * reply is good when the same checksum over rx[0..13] equals + * rx[14..15], and the pending length is then rx[1..2]. That is + * exactly grape_ping, and sub_188FFC drives it with five retries. + * + * So a failed ping here means the checksum did not verify, not that + * the part is in the bootloader. Where stock differs is what it does + * with the result: sub_188FFC only reads a report when the pending + * length is non-zero, and nothing gates registering the input device + * on a ping at all -- sub_20490(1) simply arms ATTN on pad 38. + */ +static bool grape_status_is_bootloader(u16 w) +{ + return w == 0x4f81 || w == 0x4879 || w == 0x4bc1 || + w == 0x4ad1 || w == 0x4969; +} + +/* sub_26494 — 16↔16 1A A1 + 18 E1 pad; two rev16 words must be known */ +static int grape_probe_26494(struct grape *n, const char *tag) +{ + u8 tx[GRAPE_FRAME_LEN]; + u8 rx[GRAPE_FRAME_LEN] = { 0 }; + unsigned int i; + u16 w0, w1; + int ret; + + tx[0] = 0x1a; + tx[1] = 0xa1; + for (i = 2; i < GRAPE_FRAME_LEN; i += 2) { + tx[i] = 0x18; + tx[i + 1] = 0xe1; + } + ret = grape_xfer(n, tx, rx, GRAPE_FRAME_LEN); + w0 = (u16)((rx[0] << 8) | rx[1]); + w1 = (u16)((rx[2] << 8) | rx[3]); + grape_vinfo(n, + "26494 %s ret=%d words 0x%04x 0x%04x known=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + tag, ret, w0, w1, grape_opcode_known(w0) && grape_opcode_known(w1), + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + /* + * The stock probe only rejects the part when the transfer worked and + * came back with words it does not recognise. A failed transfer is not + * a verdict, and treating it as one meant a single soft SPI error + * skipped the firmware download entirely. + */ + if (ret) { + dev_warn(&n->spi->dev, + "26494 %s transfer %d; continuing to download\n", + tag, ret); + return 0; + } + if (!grape_opcode_known(w0) || !grape_opcode_known(w1)) + return -EIO; + return 0; +} + +/* sub_3D5706 — TX 1A A1, RX 2, byteswap */ +static int grape_status_poll(struct grape *n, u16 *status) +{ + u8 tx[2] = { 0x1a, 0xa1 }; + u8 rx[2] = { 0 }; + int ret; + + ret = grape_xfer(n, tx, rx, 2); + if (ret) + return ret; + if (status) + *status = (u16)((rx[0] << 8) | rx[1]); /* __rev16 of LE word */ + return 0; +} + +static bool grape_fw_has_8740_hdr(const u8 *data, size_t size) +{ + return size >= 8 && data[0] == '8' && data[1] == '7' && + data[2] == '4' && data[3] == '0'; +} + +static bool grape_fw_has_z2fw_hdr(const u8 *data, size_t size) +{ + u32 magic; + + if (size < 8) + return false; + magic = get_unaligned_le32(data); + return magic == GRAPE_Z2FW_MAGIC; +} + +/* Classify host grape file: full 8740 container vs ARM-only cut vs Z2FW. */ +static void grape_fwfile_classify(struct grape *n, const u8 *data, size_t size) +{ + bool h8740 = grape_fw_has_8740_hdr(data, size); + bool hz2 = grape_fw_has_z2fw_hdr(data, size); + bool arm0 = size >= 4 && grape_looks_like_arm(data, size); + bool arm400 = size >= 0x410 && grape_looks_like_arm(data + 0x400, 16); + u32 le0c = (h8740 && size >= 0x10) ? get_unaligned_le32(data + 0xc) : 0; + u8 rev = (h8740 && size >= 5) ? data[4] : 0; + + grape_vinfo(n, + "FWFILE size=%zu first16=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x has_8740=%d rev=%u le32(+0xc)=0x%x arm@0=%d arm@0x400=%d Z2FW=%d\n", + size, + size > 0 ? data[0] : 0, size > 1 ? data[1] : 0, + size > 2 ? data[2] : 0, size > 3 ? data[3] : 0, + size > 4 ? data[4] : 0, size > 5 ? data[5] : 0, + size > 6 ? data[6] : 0, size > 7 ? data[7] : 0, + size > 8 ? data[8] : 0, size > 9 ? data[9] : 0, + size > 10 ? data[10] : 0, size > 11 ? data[11] : 0, + size > 12 ? data[12] : 0, size > 13 ? data[13] : 0, + size > 14 ? data[14] : 0, size > 15 ? data[15] : 0, + h8740, rev, le0c, arm0, arm400, hz2); + if (!h8740 && arm0) + dev_warn(&n->spi->dev, + "FWFILE is ARM-only cut — grape file +350 is not touch calibration cal\n"); +} + +static void __maybe_unused grape_log_calcand(struct grape *n, const char *name, + const u8 *data, size_t size, unsigned int off) +{ + u8 tmp[GRAPE_FW_HDR_LEN]; + u32 s; + + if (size < off + GRAPE_FW_HDR_LEN) { + grape_vinfo(n, "CALCAND %s off=%u OOB (file=%zu)\n", + name, off, size); + return; + } + memcpy(tmp, data + off, GRAPE_FW_HDR_LEN); + grape_bswap32_words(tmp, GRAPE_FW_HDR_LEN); + s = grape_sum32(tmp, GRAPE_FW_HDR_LEN); + grape_vinfo(n, + "CALCAND %s off=%u sum32=0x%08x first16=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x (post-bswap)\n", + name, off, s, + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], + tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], + tmp[14], tmp[15]); +} + +static void __maybe_unused grape_dump_calcands(struct grape *n, const u8 *data, + size_t size) +{ + /* Diagnostic only — does not select a candidate for upload. */ + grape_log_calcand(n, "dec350", data, size, 350); + grape_log_calcand(n, "hex350", data, size, 0x350); + grape_log_calcand(n, "arm_plus_dec350", data, size, 0x400 + 350); + grape_log_calcand(n, "arm_plus_hex350", data, size, 0x400 + 0x350); +} + +static u32 grape_crc32_payload(const u8 *p, size_t len) +{ + return crc32_le(~0U, p, len) ^ ~0U; +} + +static void grape_fw_audit(struct grape *n, const u8 *body, size_t body_len, + const char *tag) +{ + u32 crc; + size_t pad; + + if (!body_len) + return; + crc = grape_crc32_payload(body, body_len); + pad = body_len & 3u; + grape_vinfo(n, + "FW audit %s %zuB arm=%d crc32=0x%08x pad=%zu\n", + tag, body_len, grape_looks_like_arm(body, body_len), crc, pad); + if (body_len >= 16) { + u32 m = get_unaligned_le32(body); + u32 ln_le = get_unaligned_le32(body + 4); + u32 ln_be = get_unaligned_be32(body + 4); + u32 c_le = get_unaligned_le32(body + 8); + + if (m == GRAPE_Z2_MAGIC_5A5A || m == GRAPE_Z2_MAGIC_C3F5) + grape_vinfo(n, + " z2-dl hdr magic=0x%08x len_le=%u len_be=%u crc=0x%08x\n", + m, ln_le, ln_be, c_le); + } +} + +static int grape_build_z2_dl_hdr(u8 *hdr, const u8 *payload, size_t len, + u32 magic) +{ + u32 crc = grape_crc32_payload(payload, len); + + put_unaligned_le32(magic, hdr); + put_unaligned_be32(len, hdr + 4); + put_unaligned_le32(crc, hdr + 8); + put_unaligned_le32(0, hdr + 12); + return 0; +} + +static u8 *grape_maybe_prepend_z2_hdr(struct grape *n, const u8 *body, + size_t body_len, size_t *out_len) +{ + u8 *buf; + u32 magic; + + if (!prepend_z2_hdr || body_len < 4) + return NULL; + magic = prepend_z2_hdr == 2 ? GRAPE_Z2_MAGIC_C3F5 : GRAPE_Z2_MAGIC_5A5A; + buf = kmalloc(GRAPE_Z2_HDR_LEN + body_len + 3, GFP_KERNEL); + if (!buf) + return NULL; + grape_build_z2_dl_hdr(buf, body, body_len, magic); + memcpy(buf + GRAPE_Z2_HDR_LEN, body, body_len); + *out_len = GRAPE_Z2_HDR_LEN + body_len; + if (*out_len & 3) { + memset(buf + *out_len, 0, 4 - (*out_len & 3)); + *out_len = round_up(*out_len, 4); + } + grape_vinfo(n, + "prepended Z2 dl hdr magic=0x%08x total=%zu\n", magic, *out_len); + return buf; +} + +static int grape_wait_ack(struct grape *n, u16 expect, int retries) +{ + int i; + u16 st = 0; + + for (i = 0; i < retries; i++) { + if (grape_status_poll(n, &st) == 0 && st == expect) + return 0; + msleep(2); + } + dev_warn(&n->spi->dev, "ACK wait fail (want 0x%04x got 0x%04x)\n", + expect, st); + return -ETIMEDOUT; +} + +/* + * S5LBox §6.5 / iOS AppleMultitouchZ2SPI MemRead: + * 1C 73 + addr middle-endian + sum16(addr bytes) + * then 8-byte ATN 1A A1 18 E1×3; value at rx[2..5] middle-endian. + */ +static int grape_rdreg(struct grape *n, u32 addr, u32 *val) +{ + u8 tx[8] = { 0x1c, 0x73 }; + u8 rx[8] = { 0 }; + u8 atn_tx[8] = { 0x1a, 0xa1, 0x18, 0xe1, 0x18, 0xe1, 0x18, 0xe1 }; + u8 atn_rx[8] = { 0 }; + u16 csum; + int ret; + + tx[2] = (addr >> 8) & 0xff; + tx[3] = addr & 0xff; + tx[4] = (addr >> 24) & 0xff; + tx[5] = (addr >> 16) & 0xff; + csum = grape_sum16(tx + 2, 4); + tx[6] = (csum >> 8) & 0xff; + tx[7] = csum & 0xff; + + ret = grape_xfer(n, tx, rx, 8); + if (ret) + return ret; + ret = grape_xfer(n, atn_tx, atn_rx, 8); + if (ret) + return ret; + if (val) + *val = ((u32)atn_rx[2] << 8) | atn_rx[3] | + ((((u32)atn_rx[4] << 8) | atn_rx[5]) << 16); + dev_dbg(&n->spi->dev, + "RDREG 0x%08x = 0x%08x atn %02x %02x %02x %02x %02x %02x %02x %02x\n", + addr, val ? *val : 0, atn_rx[0], atn_rx[1], atn_rx[2], + atn_rx[3], atn_rx[4], atn_rx[5], atn_rx[6], atn_rx[7]); + return 0; +} + + +static void grape_peek(struct grape *n, const char *tag) +{ + static const u32 addrs[] = { + 0x00000000, 0x0000d208, 0x0000e970, 0x00400200, + 0x0040f7f4, 0x0040fffc, 0x1000300c, 0x10008ffc, + }; + unsigned int i; + + if (!grape_verbose) + return; + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 v = 0; + + if (grape_rdreg(n, addrs[i], &v) == 0) + grape_vinfo(n, "peek %s %08x=%08x\n", tag, addrs[i], v); + } +} + +/* HBPP MemRead, dest packing is B1,B0,B3,B2 — same as DATA offset. */ +static int grape_rdmem(struct grape *n, u32 addr, u8 *buf, unsigned int len) +{ + unsigned int i; + + if (len & 3) + return -EINVAL; + for (i = 0; i < len; i += 4) { + u32 v = 0; + + if (grape_rdreg(n, addr + i, &v)) + return -EIO; + put_unaligned_le32(v, buf + i); + } + return 0; +} + +/* + * Prove whether 2D640 landed the ARM image at dest 0 or at the EXEC + * word 0x00100018. Cal dest 0x400200 is a separate window. + */ +static void grape_fw_readback(struct grape *n, const char *tag) +{ + static const u32 addrs[] = { + 0x00000000, 0x00000018, 0x00100000, 0x00100018, + 0x00400000, 0x00400200, + }; + u8 *buf; + unsigned int i; + + /* + * Six 4 KB reads in the middle of the download sequence. Harmless to + * look at, but not something to put on the bus by default. + */ + if (!verbose) + return; + + buf = kmalloc(0x1000, GFP_KERNEL); + if (!buf) + return; + grape_vinfo(n, "GRAPE FW_READBACK %s:\n", tag); + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 crc100, crc1000; + + memset(buf, 0xa5, 0x1000); + if (grape_rdmem(n, addrs[i], buf, 0x1000)) { + dev_warn(&n->spi->dev, + " addr=%08x RDREG fail\n", addrs[i]); + continue; + } + crc100 = grape_crc32_payload(buf, 0x100); + crc1000 = grape_crc32_payload(buf, 0x1000); + grape_vinfo(n, + " addr=%08x first32=%32ph crc100=0x%08x crc1000=0x%08x\n", + addrs[i], buf, crc100, crc1000); + } + kfree(buf); +} + +static void grape_cal_readback(struct grape *n, const u8 *upload) +{ + u8 *buf; + u32 crc_chip, crc_host; + + if (!verbose) + return; + + buf = kmalloc(GRAPE_FW_HDR_LEN, GFP_KERNEL); + if (!buf) + return; + if (grape_rdmem(n, GRAPE_CAL_DEST, buf, GRAPE_FW_HDR_LEN)) { + dev_warn(&n->spi->dev, "cal readback RDREG fail @0x%08x\n", + GRAPE_CAL_DEST); + kfree(buf); + return; + } + crc_chip = grape_crc32_payload(buf, GRAPE_FW_HDR_LEN); + crc_host = grape_crc32_payload(upload, GRAPE_FW_HDR_LEN); + grape_vinfo(n, + "GRAPE CAL_READBACK @%08x first64=%32ph %32ph crc200=0x%08x host_crc=0x%08x match=%d\n", + GRAPE_CAL_DEST, buf, buf + 32, crc_chip, crc_host, + crc_chip == crc_host && !memcmp(buf, upload, GRAPE_FW_HDR_LEN)); + kfree(buf); +} + +/* sub_20848(6593) */ +static int grape_bootload_cmd(struct grape *n) +{ + u8 tx[GRAPE_FRAME_LEN]; + u8 rx[GRAPE_FRAME_LEN]; + unsigned int i; + int ret; + + tx[0] = (GRAPE_BOOTLOAD_WORD >> 8) & 0xff; + tx[1] = GRAPE_BOOTLOAD_WORD & 0xff; + for (i = 2; i < GRAPE_FRAME_LEN; i += 2) { + tx[i] = 0x18; + tx[i + 1] = 0xe1; + } + memset(rx, 0, sizeof(rx)); + ret = grape_xfer(n, tx, rx, GRAPE_FRAME_LEN); + grape_vinfo(n, + "bootload 6593 ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + return ret; +} + +/* + * sub_3B9D0 dword swizzle into chunk payload: b0 b1 b2 b3 → b1 b0 b3 b2 + * (distinct from the full u32 byte-reverse used at FW+350). + */ +static void grape_grape_swizzle32(u8 *dst, const u8 *src, unsigned int len) +{ + unsigned int i; + + for (i = 0; i + 3 < len; i += 4) { + dst[i] = src[i + 1]; + dst[i + 1] = src[i]; + dst[i + 2] = src[i + 3]; + dst[i + 3] = src[i + 2]; + } +} + +/* 273A0: full u32 reverse of the 512-byte cal window before 2D7A4. */ +static void grape_bswap32_words(u8 *p, unsigned int len) +{ + unsigned int i; + + for (i = 0; i + 3 < len; i += 4) { + u8 t0 = p[i], t1 = p[i + 1]; + + p[i] = p[i + 3]; + p[i + 1] = p[i + 2]; + p[i + 2] = t1; + p[i + 3] = t0; + } +} + +static u32 grape_sum32(const u8 *p, unsigned int len) +{ + u32 s = 0; + + while (len--) + s += *p++; + return s; +} + +/* + * Family clue (iPhone 4S AppleMultitouchN1SPI): cal is a separate + * multi-touch-calibration property starting "NI" (4e 49 …), not FW+350. + * N31 RetailOS window has been observed as 4e 49 02 01 (vs 4S 4e 49 01 01). + * Host/touch calibration order is checked BEFORE the RetailOS u32-reverse into win[]. + */ +static bool grape_cal_looks_ni(const u8 *p) +{ + return p && p[0] == 0x4e && p[1] == 0x49; +} + +/* + * OSOS sub_273A0: copy touch calibration[350 : 350+0x200], reverse each u32 in that + * copy only (do not mutate the 0x560 object). + */ +static int grape_prepare_cal_from_touch_cal(struct grape *n, const u8 *touch_cal, + size_t touch_cal_len) +{ + const u8 *raw; + u32 sum; + + if (touch_cal_len != GRAPE_TOUCH_CAL_LEN) { + dev_err(&n->spi->dev, "touch calibration bad size: got=%zu want=0x%x\n", + touch_cal_len, GRAPE_TOUCH_CAL_LEN); + return -EINVAL; + } + + memcpy(n->touch_cal, touch_cal, GRAPE_TOUCH_CAL_LEN); + n->have_touch_cal = true; + + raw = n->touch_cal + GRAPE_FW_HDR_OFF; + memcpy(n->cal_upload, raw, GRAPE_FW_HDR_LEN); + grape_vinfo(n, + "cal +350 raw head %02x %02x %02x %02x%s\n", + raw[0], raw[1], raw[2], raw[3], + grape_cal_looks_ni(raw) ? " (NI family — good)" : + " (not NI — suspect vs 4S/IOReg cal)"); + grape_bswap32_words(n->cal_upload, GRAPE_FW_HDR_LEN); + sum = grape_sum32(n->cal_upload, GRAPE_FW_HDR_LEN); + grape_vinfo(n, + "Grape touch calibration cal prepared: off=%u len=0x%x sum32=0x%08x upload_first32=%32ph\n", + GRAPE_FW_HDR_OFF, GRAPE_FW_HDR_LEN, sum, n->cal_upload); + if (!sum) { + dev_err(&n->spi->dev, + "touch calibration +350 window is all zeros — not a usable cal\n"); + n->have_cal = false; + return -EINVAL; + } + n->have_cal = true; + return 0; +} + +/* + * OSOS sub_564: desc = sub_A34(24) = 0x2202FE18 + * desc[0] == 0x53797349 + * memcpy(0x08A8B510, desc[1], 0x560) + * Linux reads the live descriptor if boot preserved that SRAM. + */ +static int grape_load_touch_cal_from_a34(struct grape *n) +{ + void __iomem *desc_io; + void __iomem *src_io; + u32 magic; + u32 ptr; + u8 *tmp; + int ret; + + if (!cal_try_a34) + return -ENOENT; + + desc_io = ioremap(GRAPE_A34_TOUCH_CAL_DESC, 8); + if (!desc_io) + return -ENOMEM; + + magic = readl(desc_io); + ptr = readl(desc_io + 4); + iounmap(desc_io); + + grape_vinfo(n, + "A34 touch calibration descriptor: magic=0x%08x ptr=0x%08x\n", + magic, ptr); + + if (magic != GRAPE_TOUCH_CAL_MAGIC) { + dev_warn(&n->spi->dev, + "A34 touch calibration missing: magic=0x%08x want=0x%08x\n", + magic, GRAPE_TOUCH_CAL_MAGIC); + return -ENOENT; + } + if (!ptr) { + dev_warn(&n->spi->dev, "A34 touch calibration pointer is NULL\n"); + return -ENOENT; + } + + src_io = ioremap(ptr, GRAPE_TOUCH_CAL_LEN); + if (!src_io) + return -ENOMEM; + + tmp = kmalloc(GRAPE_TOUCH_CAL_LEN, GFP_KERNEL); + if (!tmp) { + iounmap(src_io); + return -ENOMEM; + } + + memcpy_fromio(tmp, src_io, GRAPE_TOUCH_CAL_LEN); + iounmap(src_io); + + grape_vinfo(n, + "A34 touch calibration read: ptr=0x%08x len=0x%x first32=%32ph calraw_first16=%16ph\n", + ptr, GRAPE_TOUCH_CAL_LEN, tmp, tmp + GRAPE_FW_HDR_OFF); + + ret = grape_prepare_cal_from_touch_cal(n, tmp, GRAPE_TOUCH_CAL_LEN); + kfree(tmp); + return ret; +} + +/* + * U-Boot copies the 0x560 touch calibration object to reserved DRAM and publishes + * apple,n31-touch_cal-addr / apple,n31-touch_cal-size on /chosen. That copy is the + * safe address — never the original A34 pointer. + */ +static int grape_load_touch_cal_from_dt(struct grape *n) +{ + struct device_node *chosen; + u32 addr; + u32 size; + void *p; + u8 *tmp; + int ret; + + if (!cal_try_dt) + return -ENOENT; + + chosen = of_find_node_by_path("/chosen"); + if (!chosen) + return -ENOENT; + + ret = of_property_read_u32(chosen, "apple,n31-touch_cal-addr", &addr); + if (ret) + goto out; + + ret = of_property_read_u32(chosen, "apple,n31-touch_cal-size", &size); + if (ret) + goto out; + + grape_vinfo(n, "DT touch_cal: addr=0x%08x size=0x%x\n", addr, size); + + if (!addr || size != GRAPE_TOUCH_CAL_LEN) { + ret = -EINVAL; + goto out; + } + + p = memremap(addr, size, MEMREMAP_WB); + if (!p) { + ret = -ENOMEM; + goto out; + } + + tmp = kmemdup(p, size, GFP_KERNEL); + memunmap(p); + if (!tmp) { + ret = -ENOMEM; + goto out; + } + + grape_vinfo(n, + "DT touch_cal read: addr=0x%08x len=0x%x first32=%32ph calraw_first16=%16ph\n", + addr, size, tmp, tmp + GRAPE_FW_HDR_OFF); + + ret = grape_prepare_cal_from_touch_cal(n, tmp, size); + kfree(tmp); + +out: + of_node_put(chosen); + return ret; +} + +static int grape_acquire_touch_cal_cal(struct grape *n) +{ + int ret; + + if (n->have_cal) + return 0; + + ret = grape_load_touch_cal_from_dt(n); + if (!ret) + return 0; + + grape_vinfo(n, "DT touch_cal unavailable: %d; trying A34 live\n", + ret); + + ret = grape_load_touch_cal_from_a34(n); + if (!ret) + return 0; + + dev_err(&n->spi->dev, + "No touch calibration calibration from DT or A34; not registering input\n"); + return ret; +} + +/* Optional fmss FTL export — grape firmware only, not touch calibration cal. */ +static bool (*grape_ftl_present_fn)(void); +static int (*grape_ftl_read_fn)(u64 logical_sector, void *buf); +static bool grape_ftl_inited; + +static void grape_ftl_init_once(void) +{ + if (grape_ftl_inited) + return; + grape_ftl_inited = true; + grape_ftl_present_fn = symbol_get(nand_ftl_present); + grape_ftl_read_fn = symbol_get(nand_ftl_read_sector); +} + +static bool grape_ftl_ready(void) +{ + grape_ftl_init_once(); + return grape_ftl_present_fn && grape_ftl_read_fn && + grape_ftl_present_fn(); +} + +/* + * Walk FTL for Apple 8740 / gpfw IMG1. Returns kmalloc'd buffer + size. + * Caller kfree() on success. This is the ARM app, not touch calibration cal. + */ +/* + * Kept with no caller: the transliteration of the gpfw read, retained so the + * shape is not lost if a unit ever turns up with an MBR at LBA 0 and an + * APPLE_MDFW partition. On this device LBA 0 is the FAT32 VBR, so it can + * only fail. See the note on fw_prefer_ftl. + */ +static u8 __maybe_unused *grape_try_gpfw_from_ftl(struct device *dev, + size_t *out_len) +{ + u8 *sec, *buf = NULL; + u64 lba, end; + unsigned int off; + size_t need, got; + u32 body_sz; + + if (!grape_ftl_ready()) + return NULL; + + sec = kmalloc(GRAPE_FTL_SECTOR_SIZE, GFP_KERNEL); + if (!sec) + return NULL; + + end = min_t(u64, cal_ftl_start + cal_ftl_count, 256ULL); + for (lba = 0; lba < end; lba++) { + if (grape_ftl_read_fn(lba, sec)) + continue; + for (off = 0; off + 0x410 <= GRAPE_FTL_SECTOR_SIZE; off += 4) { + if (memcmp(sec + off, "8740", 4)) + continue; + body_sz = get_unaligned_le32(sec + off + 0x0c); + if (!body_sz || body_sz > 1024 * 1024) + continue; + need = 0x400 + round_up(body_sz, 16); + buf = kmalloc(need, GFP_KERNEL); + if (!buf) + goto out; + memcpy(buf, sec + off, min_t(size_t, need, + GRAPE_FTL_SECTOR_SIZE - off)); + got = min_t(size_t, need, GRAPE_FTL_SECTOR_SIZE - off); + while (got < need && lba + 1 < end) { + lba++; + if (grape_ftl_read_fn(lba, sec)) { + kfree(buf); + buf = NULL; + goto out; + } + memcpy(buf + got, sec, + min_t(size_t, need - got, + GRAPE_FTL_SECTOR_SIZE)); + got += min_t(size_t, need - got, + GRAPE_FTL_SECTOR_SIZE); + } + if (got >= 0x410) { + grape_dev_vinfo(dev, + "gpfw/8740 from FTL lba=%llu off=%u need=%zu got=%zu rev=%u\n", + lba, off, need, got, buf[7]); + *out_len = got; + goto out; + } + kfree(buf); + buf = NULL; + } + } + +out: + kfree(sec); + return buf; +} + +static int grape_acquire_fw(struct device *dev, const u8 **data, + size_t *size, const struct firmware **fw_out, + u8 **kbuf_out, bool *from_ftl) +{ + *fw_out = NULL; + *kbuf_out = NULL; + *from_ftl = false; + + /* + * No gpfw attempt: see the note on fw_prefer_ftl above. The store is + * not present on this device, so the host file is the only source. + */ + if (!fw_allow_file) + return -ENOENT; + if (request_firmware(fw_out, "apple/grape.bin", dev) || + !*fw_out) + return -ENOENT; + *from_ftl = false; + *data = (*fw_out)->data; + *size = (*fw_out)->size; + + /* + * The firmware blob is an IMG1 file, and "8740" is its SoC code. + * + * sub_1A640 tests the magic with sub_440CD0, which is memcmp and + * returns 0 on a match: + * + * if (sub_440CD0(buf, "8740", 4)) { ptr = buf; len = size; } + * else if (sub_204E0(buf, size, &len, &ptr)) { ... } + * + * so the whole-blob arm is taken when the magic is ABSENT, and a blob + * that does start with "8740" goes to sub_204E0. That function is an + * IMG1 parser, which the freemyipod IMG1 notes and wInd3x pkg/image + * describe independently: + * + * +0 magic[4] SoC code, "8740" here + * +4 version[3] "2.0" + * +7 format 4 = X509_SIGNED + * +8 entrypoint + * +12 body_length + * +16 data_length + * +20 cert_offset + * +24 cert_length + * + * with the body at 0x400 for this device. sub_204E0 returns exactly + * that: ptr = buf + 1024, len = *(u32 *)(buf + 12). Its guards are + * IMG1 sanity checks -- its 768 is the certificate length and its 128 + * is the 0x80 body signature. + * + * This blob checks out field for field: body 59760 at 0x400, then a + * 0x80 signature, then 768 of certificate, which is 61680 exactly. + * data_length equals body + signature + certificate, and cert_offset + * equals body + signature. + * + * body_length is rounded up to the AES block size before use. It is + * already 16-aligned here so nothing moves, but an image whose body + * is not would otherwise be uploaded short by up to 15 bytes. + * + * Earlier versions uploaded from offset 0 -- once truncated to + * body_length, once whole -- so the part was handed the IMG1 header + * where it expected the vector table. Chunks still acked 0x4BC1 and + * EXEC still completed, which is why it looked like a good download + * that would not start. + */ + if (*size > GRAPE_IMG1_BODY_OFF && !memcmp(*data, "8740", 4)) { + u32 entry = get_unaligned_le32(*data + 8); + u32 body = get_unaligned_le32(*data + 12); + u32 data_len = get_unaligned_le32(*data + 16); + u32 cert_off = get_unaligned_le32(*data + 20); + u32 aligned; + + /* IMG1 body_length rounds up to the AES block size. */ + if (body & 0xf) { + dev_info(dev, "IMG1 body %u rounded up to %u\n", + body, (body + 0xf) & ~0xfu); + body = (body + 0xf) & ~0xfu; + } + aligned = (body + 0xf) & ~0xfu; + + /* sub_204E0 guards, in its order. */ + if (body && data_len >= aligned + GRAPE_IMG1_CERT_LEN && + cert_off >= aligned + GRAPE_IMG1_SIG_LEN && + !(cert_off & 0xf) && entry <= aligned && + *size >= GRAPE_IMG1_BODY_OFF + body) { + dev_info(dev, + "IMG1 8740 v%c.%c fmt %u: body %u at +%u, entry 0x%08x, of %zu\n", + (*data)[4], (*data)[6], (*data)[7], body, + GRAPE_IMG1_BODY_OFF, entry, *size); + *data += GRAPE_IMG1_BODY_OFF; + *size = body; + } else { + dev_warn(dev, + "IMG1 header fails sub_204E0 guards (body=%u data=%u cert_off=%u entry=0x%08x); using the whole blob\n", + body, data_len, cert_off, entry); + } + } else { + dev_info(dev, "fw has no 8740 IMG1 magic; uploading all %zu bytes\n", + *size); + } + return 0; +} + +static void grape_release_fw(const struct firmware *fw, u8 *kbuf) +{ + /* Exactly one of the decoded copy and the firmware blob is live. */ + if (!kbuf && fw) + release_firmware(fw); + kfree(kbuf); +} + +/* + * 2D7A4 payload → controller @ 0x00400200. + * Cal is the transformed A34 touch calibration window only. + */ +static int grape_load_cal_window(struct grape *n, u8 *win) +{ + int ret; + + ret = grape_acquire_touch_cal_cal(n); + if (ret) + return ret; + memcpy(win, n->cal_upload, GRAPE_FW_HDR_LEN); + return 0; +} + +/* + * sub_2D640 / 2D7A4 + trampoline sub_35C1C → sub_3B9D0: + * frame[0..1] 18 E1 + * body @ +2: + * 30 01 + * word_count hi/lo = (len>>10),(len>>2) + * dest B1 B0 B3 B2 + * u16 byte-sum of previous 6 body bytes (words+dest), BE + * payload u32s swizzled B1 B0 B3 B2 + * u32 byte-sum of swizzled payload, stored B1 B0 B3 B2 + * SPI len = payload_len + 16; max payload 0x1FF0; ACK 0x4BC1 (retry ≤5). + * + * Expected prefixes (exact glass check): + * FW chunk0 dest=0 len=0x1FF0: + * 18 E1 30 01 07 FC 00 00 00 00 01 03 + * CAL chunk0 dest=0x00400200 len=0x200: + * 18 E1 30 01 00 80 02 00 00 40 00 C2 + */ +static unsigned int grape_build_upload_frame(u8 *buf, u32 dest, + const u8 *src, unsigned int len) +{ + u16 hdr_sum; + u32 payload_sum; + + buf[0] = 0x18; + buf[1] = 0xe1; + buf[2] = 0x30; + buf[3] = 0x01; + /* word_count = len/4 as big-endian u16 via (len>>10),(len>>2) */ + buf[4] = (len >> 10) & 0xff; + buf[5] = (len >> 2) & 0xff; + /* dest swizzle B1 B0 B3 B2 */ + buf[6] = (dest >> 8) & 0xff; + buf[7] = dest & 0xff; + buf[8] = (dest >> 24) & 0xff; + buf[9] = (dest >> 16) & 0xff; + hdr_sum = grape_sum16(buf + 4, 6); + buf[10] = (hdr_sum >> 8) & 0xff; + buf[11] = hdr_sum & 0xff; + + grape_grape_swizzle32(buf + 12, src, len); + + payload_sum = grape_sum32(buf + 12, len); + buf[12 + len] = (payload_sum >> 8) & 0xff; + buf[12 + len + 1] = payload_sum & 0xff; + buf[12 + len + 2] = (payload_sum >> 24) & 0xff; + buf[12 + len + 3] = (payload_sum >> 16) & 0xff; + + return len + GRAPE_HDR_LEN; +} + +/* Glass/oracle prefixes from RetailOS 2D640 / 2D7A4 — fail loud if wrong. */ +static void grape_check_upload_prefix(struct grape *n, u32 dest, + unsigned int len, const u8 *tx) +{ + static const u8 fw0[12] = { + 0x18, 0xe1, 0x30, 0x01, 0x07, 0xfc, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03 + }; + static const u8 cal0[12] = { + 0x18, 0xe1, 0x30, 0x01, 0x00, 0x80, 0x02, 0x00, + 0x00, 0x40, 0x00, 0xc2 + }; + + if (dest == 0 && len == GRAPE_CHUNK_MAX && memcmp(tx, fw0, 12)) { + dev_err(&n->spi->dev, + "FW_UPLOAD prefix MISMATCH want 18 e1 30 01 07 fc 00 00 00 00 01 03 got %12ph\n", + tx); + } + if (dest == GRAPE_CAL_DEST && len == GRAPE_FW_HDR_LEN && + memcmp(tx, cal0, 12)) { + dev_err(&n->spi->dev, + "CAL_UPLOAD prefix MISMATCH want 18 e1 30 01 00 80 02 00 00 40 00 c2 got %12ph\n", + tx); + } +} + +static void grape_log_upload_prefix(struct grape *n, const char *tag, + unsigned int chunk_idx, u32 dest, + unsigned int len, const u8 *tx, + unsigned int xfer_len, u16 ack, int ack_ret) +{ + grape_vinfo(n, + "GRAPE %s chunk=%u dest=%08x len=%04x xfer=%u ACK=0x%04x ret=%d\n", + tag, chunk_idx, dest, len, xfer_len, ack, ack_ret); + grape_vinfo(n, + " tx[0:16] = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], tx[6], tx[7], + tx[8], tx[9], tx[10], tx[11], + xfer_len > 12 ? tx[12] : 0, xfer_len > 13 ? tx[13] : 0, + xfer_len > 14 ? tx[14] : 0, xfer_len > 15 ? tx[15] : 0); +} + +static void grape_dump_hbpp_tx(struct grape *n, const char *tag, + const u8 *raw, unsigned int chunk_idx, + unsigned int dest, unsigned int chunk_len, + const u8 *tx, unsigned int xfer_len, u16 ack, + int ack_ret) +{ + unsigned int last_off; + + grape_log_upload_prefix(n, tag, chunk_idx, dest, chunk_len, + tx, xfer_len, ack, ack_ret); + if (raw && chunk_len >= 64) + grape_vinfo(n, " raw first64=%32ph %32ph\n", + raw, raw + 32); + else if (raw) + grape_vinfo(n, " raw first%u=%*ph\n", + chunk_len, chunk_len, raw); + if (xfer_len >= 96) + grape_vinfo(n, + " tx first96=%32ph %32ph %32ph\n", + tx, tx + 32, tx + 64); + else if (xfer_len > 16) + grape_vinfo(n, " tx first%u=%*ph\n", + xfer_len, xfer_len, tx); + if (xfer_len >= 32) { + last_off = xfer_len - 32; + grape_vinfo(n, " tx last32=%32ph\n", tx + last_off); + } +} + +static int grape_send_chunk_ex(struct grape *n, const u8 *data, + unsigned int dest, unsigned int len, + unsigned int cs_flags, bool dump, + const char *tag, unsigned int chunk_idx, + unsigned int file_off) +{ + u8 *buf; + unsigned int xfer_len; + int ret = -EIO, try, ack_ret = -ETIMEDOUT; + u16 ack = 0; + + if (!len || len > GRAPE_CHUNK_MAX || (len & 3)) + return -EINVAL; + + xfer_len = len + GRAPE_HDR_LEN; + buf = kzalloc(xfer_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (grape_build_upload_frame(buf, dest, data, len) != xfer_len) { + kfree(buf); + return -EINVAL; + } + grape_check_upload_prefix(n, dest, len, buf); + + /* Z2 SEND_BLOB: spi_sync keeps CS down for whole HBPP frame. */ + for (try = 0; try < 5; try++) { + if (chunk_spi) + ret = grape_xfer(n, buf, NULL, xfer_len); + else if (n->blob16) + ret = grape_burst_u16_ex(n, buf, NULL, + xfer_len, cs_flags); + else + ret = grape_burst_ex(n, buf, NULL, xfer_len, cs_flags); + if (ret) + continue; + /* 1A A1 → 2 bytes → rev16; expect 0x4BC1 */ + ack_ret = grape_wait_ack(n, GRAPE_ACK_CHUNK, 8); + if (ack_ret == 0) { + ack = GRAPE_ACK_CHUNK; + if (dump) + grape_dump_hbpp_tx(n, tag, data, chunk_idx, + dest, len, buf, xfer_len, + ack, ack_ret); + else if (!chunk_idx) + grape_log_upload_prefix(n, tag, chunk_idx, + dest, len, buf, + xfer_len, ack, + ack_ret); + kfree(buf); + return 0; + } + if (n->blob16 && chunk_idx && try == 0) { + n->blob16 = false; + grape_vinfo(n, + "16-bit DATA no 4BC1 — falling back to 8-bit PIO\n"); + } + } + if (dump || !chunk_idx) + grape_dump_hbpp_tx(n, tag, data, chunk_idx, dest, len, buf, + xfer_len, ack, ack_ret); + kfree(buf); + return -EIO; +} + +static int grape_send_chunk(struct grape *n, const u8 *data, + unsigned int dest, unsigned int len, bool dump, + unsigned int file_off, const char *tag, + unsigned int chunk_idx) +{ + return grape_send_chunk_ex(n, data, dest, len, + GRAPE_CS_BEGIN | GRAPE_CS_END, dump, + tag, chunk_idx, file_off); +} + +static int grape_send_blob(struct grape *n, const u8 *data, unsigned int len, + unsigned int dest_off) +{ + unsigned int off = 0; + unsigned int chunk_idx = 0; + u8 pad[4]; + const char *tag; + bool is_cal = (dest_off == GRAPE_CAL_DEST); + + tag = is_cal ? "CAL_UPLOAD" : "FW_UPLOAD"; + + while (off < len) { + unsigned int chunk = min_t(unsigned int, len - off, GRAPE_CHUNK_MAX); + int ret; + bool last, dump; + + /* RetailOS always transfers whole words */ + if (chunk & 3) + chunk &= ~3u; + if (!chunk) { + memset(pad, 0, sizeof(pad)); + memcpy(pad, data + off, len - off); + return grape_send_chunk(n, pad, dest_off + off, 4, + true, off, tag, chunk_idx); + } + last = (off + chunk >= len); + /* Always dump first + last FW chunk and the sole cal chunk. */ + dump = (off == 0) || last || is_cal; + ret = grape_send_chunk(n, data + off, dest_off + off, chunk, + dump, off, tag, chunk_idx); + if (ret) + return ret; + off += chunk; + chunk_idx++; + } + return 0; +} + +/* sub_34AD0(a1,a2,a3) — TX 1E 33 + 12-byte pack + sum16, expect ACK 0x4AD1 */ +static int grape_cmd_34ad0(struct grape *n, u32 a1, u32 a2, u32 a3) +{ + u8 tx[16]; + u8 rx[16]; + u8 body[12]; + u16 csum; + int ret; + + tx[0] = 0x1e; + tx[1] = 0x33; + + /* Packing from Hex-Rays sub_34AD0 */ + body[0] = (a1 >> 8) & 0xff; + body[1] = a1 & 0xff; + body[2] = (a1 >> 24) & 0xff; + body[3] = (a1 >> 16) & 0xff; + body[4] = (a3 >> 8) & 0xff; + body[5] = a3 & 0xff; + body[6] = (a3 >> 24) & 0xff; + body[7] = (a3 >> 16) & 0xff; + body[8] = (a2 >> 8) & 0xff; + body[9] = a2 & 0xff; + body[10] = (a2 >> 24) & 0xff; + body[11] = (a2 >> 16) & 0xff; + csum = grape_sum16(body, 12); + memcpy(tx + 2, body, 12); + tx[14] = (csum >> 8) & 0xff; + tx[15] = csum & 0xff; + + /* 34AD0: 40F770 16↔16 then 3D5706 == 0x4AD1 */ + ret = grape_xfer(n, tx, rx, 16); + if (ret) + return ret; + return grape_wait_ack(n, GRAPE_ACK_34AD0, 8); +} + +/* sub_2D5B0 post-download */ +static int grape_post_download(struct grape *n) +{ + u8 tx[2], rx[2]; + u16 st = 0; + int ret, i; + + static const struct { + u32 a1, a2, a3; + } pokes[] = { + /* 2D5B0: ldr 0x1000300C, then +0x50 / +0x4C / -0x0C */ + { 0x1000300c, 5859, (u32)-1 }, + { 0x1000305c, 32, (u32)-1 }, + { 0x10003058, 6, (u32)-1 }, + { 0x10003000, 3, (u32)-1 }, + }; + + for (i = 0; i < ARRAY_SIZE(pokes); i++) { + u32 rb = 0; + + ret = grape_cmd_34ad0(n, pokes[i].a1, pokes[i].a2, pokes[i].a3); + grape_vinfo(n, "34AD0[%d] %d\n", i, ret); + if (ret) + return ret; + /* + * 4AD1 acknowledges the write without echoing it, so the only + * way to see the value is a separate RDREG. Stock sub_2D5B0 + * does not do this, and it inserts a transfer between pokes + * that the part was not told to expect. Verbose runs only. + */ + if (verbose && grape_rdreg(n, pokes[i].a1, &rb) == 0) + grape_vinfo(n, + "34AD0[%d] RDREG 0x%08x -> 0x%08x (wrote %u)\n", + i, pokes[i].a1, rb, pokes[i].a2); + } + + /* + * Stock gates everything after this point on the reply: + * + * if (!sub_40F770(&v8, 2, &v10, 2)) { sub_410522(65); + * if (!sub_3D5706(&v9, ...)) return 1; } + * return 0; + * + * We were throwing both answers away. grape_xfer returning 0 + * only says the SPI transfer completed, and grape_status_poll + * below returns 0 for any status at all, so post_download always + * reported success and we went on to EXEC no matter what the part + * said. This is the last handshake before the application is + * supposed to start, and its reply has never been looked at. + */ + put_unaligned_le16(GRAPE_POST_POKE, tx); + ret = grape_xfer(n, tx, rx, 2); + if (ret) + return ret; + st = (u16)((rx[0] << 8) | rx[1]); + dev_info(&n->spi->dev, + "011F reply %02x %02x (0x%04x)%s\n", + rx[0], rx[1], st, + grape_opcode_known(st) ? " known" : " UNKNOWN"); + st = 0; + msleep(65); + /* 2D5B0: 3D5706 success only — does not require 0x4BC1 */ + if (grape_status_poll(n, &st) != 0) + return -EIO; + + dev_info(&n->spi->dev, "post-poke 1AA1 status 0x%04x%s\n", + st, grape_opcode_known(st) ? " known" : " UNKNOWN"); + + /* + * Refuse to EXEC on an unrecognised status when post_poke_strict + * is set. Off by default so a run still reaches EXEC and we can + * see both halves before deciding which status stock treats as a + * pass. + */ + if (post_poke_strict && !grape_opcode_known(st)) { + dev_warn(&n->spi->dev, + "post-poke status 0x%04x rejected; not running EXEC\n", + st); + return -EIO; + } + n->requestcal_done = true; + return 0; +} + +/* sub_2D54C — 12↔12: 1D 53 + two LE u32 + sum16 */ +/* + * Neither implementation reconfigures SPI2 for EXEC -- go_spi_setup + * defaults to 0 here, and stock's sub_2D54C goes through sub_40F770 + * like every other command, same channel-2 bracket and all. But the + * part stops driving MISO from EXEC onwards, so the question is + * whether the controller's own state moves underneath us. Measure it + * either side of the transfer rather than reasoning about it. + */ +static void grape_spi2_dump(struct grape *n, const char *when) +{ + if (!n->spi2) + return; + dev_info(&n->spi->dev, + "SPI2 %-6s ctrl=%08x setup=%08x status=%08x pin=%08x clkdiv=%08x\n", + when, + readl(n->spi2 + SPI2_CTRL), + readl(n->spi2 + SPI2_SETUP), + readl(n->spi2 + SPI2_STATUS), + readl(n->spi2 + SPI2_PIN), + readl(n->spi2 + SPI2_CLKDIV)); +} + +static int grape_cmd_2d54c_raw(struct grape *n, u32 word0, u32 word1) +{ + u8 tx[12] = { 0x1d, 0x53 }; + u8 rx[12] = { 0 }; + u16 csum; + u32 saved_setup = 0; + int ret; + + put_unaligned_le32(word0, tx + 2); + put_unaligned_le32(word1, tx + 6); + csum = grape_sum16(tx + 2, 8); + tx[10] = (csum >> 8) & 0xff; + tx[11] = csum & 0xff; + if (n->spi2) { + grape_spi2_fifo_flush(n); + if (go_spi_setup > 0) { + saved_setup = readl(n->spi2 + SPI2_SETUP); + writel((u32)go_spi_setup, n->spi2 + SPI2_SETUP); + grape_vinfo(n, "2D54C GO SETUP 0x%x (was 0x%x)\n", + go_spi_setup, saved_setup); + } + } + grape_spi2_dump(n, "pre"); + if (go_xfer == 2) + ret = grape_xfer(n, tx, rx, 12); + else if (go_xfer == 1) + ret = grape_burst_u16(n, tx, rx, 12); + else + ret = grape_burst(n, tx, rx, 12); + grape_spi2_dump(n, "post"); + if (saved_setup) + writel(saved_setup, n->spi2 + SPI2_SETUP); + /* + * No settle here. sub_273A0 waits ONCE, 40 ms, after a successful + * EXEC. This helper used to sleep, its caller slept again, and probe + * slept a third time before the runtime ping -- 120 ms in normal + * operation for a 40 ms wait. The transfer helper transfers; the + * state-transition owner owns the delay. + */ + grape_spi2_dump(n, "settle"); + grape_vinfo(n, + "2D54C %08x %08x ret=%d xfer=%d rx %02x %02x %02x %02x %02x %02x\n", + word0, word1, ret, go_xfer, rx[0], rx[1], rx[2], rx[3], + rx[4], rx[5]); + return ret; +} + +static void __maybe_unused grape_drain(struct grape *n, unsigned int bytes) +{ + u8 tx[GRAPE_FRAME_LEN] = { 0 }; + u8 rx[GRAPE_FRAME_LEN] = { 0 }; + unsigned int nxf = bytes < GRAPE_FRAME_LEN ? bytes : GRAPE_FRAME_LEN; + + /* Optional post-fail diagnostics only — never call on EXEC path. */ + grape_burst(n, tx, rx, nxf); +} + +static void grape_pre_exec_verify(struct grape *n) +{ + static const u32 addrs[] = { + 0x00000000, 0x00000004, 0x00000008, 0x00000020, + 0x00400200, 0x00400204, 0x004003fc, + }; + unsigned int i; + + if (!verbose) + return; + + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 v = 0; + + if (grape_rdreg(n, addrs[i], &v) == 0) + grape_vinfo(n, "pre-EXEC RDREG 0x%08x=0x%08x\n", + addrs[i], v); + } +} + +/* + * sub_2D54C — one-shot EXEC packet only. + * Do NOT poll 1A A1 / drain after EXEC: that keeps speaking HBPP across the + * bootloader→runtime boundary. Success is proven only by 182590 ping csum. + */ +static int grape_cmd_2d54c(struct grape *n) +{ + int ret; + + grape_pre_exec_verify(n); + ret = grape_cmd_2d54c_raw(n, exec_addr, exec_word1); + if (!ret) { + n->exec_sent = true; + /* Stock waits 40 ms here before touching the part again. */ + msleep(GRAPE_EXEC_SETTLE_MS); + } + return ret; +} + + + + +#define S5L8740_AES_PHYS 0x38c00000UL + +/* + * Touch FW GID decrypt (OSOS sub_422FFA / sub_204E0): + * MMIO @ 0x38C00000 AES, keysel=1 (GID), CBC IV=0, CFG=0xE|enc. + * 26CCC verifies with 16-byte encrypt; 204E0 decrypts full ARM @ +0x400 + * for 8740 rev 3 only. grape.bin on DFU is usually pre-decrypted-cut. + * force_gid=1 tries 422FFA even when loading plaintext blob (bring-up). + */ +static int grape_422ffa_mmio(struct device *dev, u8 *buf, unsigned int len, + bool encrypt) +{ + struct device *aes_dev; + void __iomem *aes; + struct clk *clk = NULL; + dma_addr_t phys; + u32 irq = 0; + int ret; + + if (!len || (len & 15)) + return -EINVAL; + + aes_dev = bus_find_device_by_name(&platform_bus_type, NULL, + "38c00000.aes"); + if (!aes_dev) + aes_dev = dev; + + clk = clk_get(aes_dev, "aes"); + if (IS_ERR(clk)) + clk = NULL; + if (clk) { + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + if (aes_dev != dev) + put_device(aes_dev); + return ret; + } + } + + aes = ioremap(S5L8740_AES_PHYS, 0x100); + if (!aes) { + ret = -ENOMEM; + goto out_clk; + } + + phys = dma_map_single(aes_dev, buf, len, DMA_BIDIRECTIONAL); + if (dma_mapping_error(aes_dev, phys)) { + ret = -ENOMEM; + goto out_io; + } + + writel(1, aes + 0x08); + { + unsigned int guard = 100000; + + while (readl(aes + 0x08) && --guard) + ; + } + writel(1, aes + 0x70); + writel(1, aes + 0x6c); + writel(~1u, aes + 0x88); + writel(1, aes + 0x00); + writel((encrypt ? 1u : 0u) | 0xeu, aes + 0x14); + writel(len, aes + 0x18); + writel(phys, aes + 0x28); + writel(len, aes + 0x2c); + writel(phys, aes + 0x20); + writel(len, aes + 0x24); + writel(phys, aes + 0x30); + writel(len, aes + 0x34); + writel(0, aes + 0x74); + writel(0, aes + 0x78); + writel(0, aes + 0x7c); + writel(0, aes + 0x80); + writel(7, aes + 0x0c); + writel(1, aes + 0x04); + ret = readl_poll_timeout(aes + 0x0c, irq, irq & 1, 2, 500000); + writel(0, aes + 0x00); + dma_unmap_single(aes_dev, phys, len, DMA_BIDIRECTIONAL); + if (ret) + dev_err(dev, "422FFA MMIO timeout IRQ=0x%x\n", irq); + +out_io: + iounmap(aes); +out_clk: + if (clk) { + clk_disable_unprepare(clk); + clk_put(clk); + } + if (aes_dev != dev) + put_device(aes_dev); + return ret; +} + +static int grape_gid_crypt(struct device *dev, u8 *buf, unsigned int len, + bool encrypt) +{ + struct crypto_skcipher *tfm; + struct skcipher_request *req; + struct scatterlist sg; + u8 key[AES_KEYSIZE_128] = { 0 }; + u8 iv[AES_BLOCK_SIZE] = { 0 }; + int ret; + + if (!len || (len & 15)) + return -EINVAL; + + tfm = crypto_alloc_skcipher("cbc(aes-gid)", 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + ret = crypto_skcipher_setkey(tfm, key, sizeof(key)); + if (ret) + goto out_tfm; + req = skcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out_tfm; + } + sg_init_one(&sg, buf, len); + skcipher_request_set_callback(req, 0, NULL, NULL); + skcipher_request_set_crypt(req, &sg, &sg, len, iv); + ret = encrypt ? crypto_skcipher_encrypt(req) : crypto_skcipher_decrypt(req); + skcipher_request_free(req); +out_tfm: + crypto_free_skcipher(tfm); + if (ret) + dev_warn(dev, "cbc(aes-gid) %s %d\n", + encrypt ? "enc" : "dec", ret); + return ret; +} + +static bool grape_looks_like_arm(const u8 *p, size_t n) +{ + return n >= 4 && p[0] == 0x18 && p[1] == 0xf0 && + p[2] == 0x9f && p[3] == 0xe5; +} + +/* + * v6 RE (2026-08-25): post-GID plaintext may already be a preconstructed + * HBPP DATA object (18 E1 30 01 …) — Corellium GEN_1 sends Constructed + * Firmware unchanged. Detect that and SPI-send as-is (ACK 0x4BC1). + */ +static bool grape_looks_like_hbpp_data(const u8 *p, size_t n) +{ + if (n >= 4 && p[0] == 0x18 && p[1] == 0xe1 && + p[2] == 0x30 && p[3] == 0x01) + return true; + if (n >= 2 && p[0] == 0x30 && p[1] == 0x01) + return true; + return false; +} + +/** + * grape_send_preconstructed_hbpp - SPI the whole HBPP frame, expect 0x4BC1 + * (DATA ACK). Do not re-wrap or swizzle — bytes are already HBPP. + */ +static int grape_send_preconstructed_hbpp(struct grape *n, const u8 *data, + size_t len) +{ + int try, ret; + + if (len < 16) + return -EINVAL; + + for (try = 0; try < 5; try++) { + if (chunk_spi) + ret = grape_xfer(n, data, NULL, len); + else if (n->blob16) + ret = grape_burst_u16_ex(n, data, NULL, len, 0); + else + ret = grape_burst_ex(n, data, NULL, len, 0); + if (ret) + continue; + if (grape_wait_ack(n, GRAPE_ACK_CHUNK, 8) == 0) { + grape_vinfo(n, + "preconstructed HBPP %zuB ACK 0x4BC1 try=%d\n", + len, try); + return 0; + } + if (n->blob16 && try == 0) { + n->blob16 = false; + grape_vinfo(n, + "preconstructed HBPP: fall back to 8-bit\n"); + } + } + return -EIO; +} + +/* + * 204E0 sends le32(8740+0x0c)=0xe970. The decrypted cut is 0xecf0 and the + * extra 896 bytes are 0x53/0x43 fill. Downloading that fill to dest 0xe970 + * stomps SRAM just past the official image (likely BSS / bootloader workspace). + */ +static size_t grape_official_arm_len(const u8 *body, size_t len) +{ + size_t i; + + if (len <= GRAPE_ARM_OFFICIAL || !grape_looks_like_arm(body, len)) + return len; + for (i = GRAPE_ARM_OFFICIAL; i < len; i++) { + if (body[i] != 0x53 && body[i] != 0x43) + return len; + } + return GRAPE_ARM_OFFICIAL; +} + +static int grape_download_fw(struct grape *n, const u8 *data, size_t size, + bool arm_at_zero) +{ + u8 *dec = NULL; + int ret; + const u8 *body = data; + size_t body_len = size; + /* + * Only a gpfw-sourced blob gets the container treatment. + * + * sub_1A640 has two mutually exclusive sources, chosen on + * MEMORY[0x8A8FAA4]. The FILESYSTEM path -- iPod_Control/Device/ + * GrapeFirmware.bin -- reads the file, sets MEMORY[0x8925F88] to the + * buffer itself and MEMORY[0x8925F8C] to the whole file size, and + * never looks at the magic at all. There is no +0x400, no length + * field and no decrypt on that path. + * + * Only the gpfw path checks: + * + * if (sub_440CD0(buf, "8740", 4)) // memcmp, non-zero = differs + * send the whole blob; + * else + * sub_204E0(); // +0x400, le32(+0x0c), GID + * + * request_firmware("apple/grape.bin") is the filesystem analogue, so + * applying the container treatment to it skipped 0x400 bytes and + * decrypted a payload stock would have sent verbatim. + */ + bool apple_hdr = n->fw_from_ftl && + grape_fw_has_8740_hdr(data, size); + + (void)arm_at_zero; + grape_fwfile_classify(n, data, size); + + /* + * 1A640 NOR 8740 → 204E0. ARM at +0x400, size le32(+0x0c). + * Rev 3: 422FFA GID-CBC IV=0 decrypt in place (NOR gpfw). + * Rev 4: no decrypt; 204E0 then returns 33 so NOR drops it. + */ + if (apple_hdr) { + u32 hdr_sz; + u8 rev; + + if (size < 0x400 + 4) { + dev_warn(&n->spi->dev, + "FW too small (%zu) — skip download\n", size); + return -EINVAL; + } + hdr_sz = get_unaligned_le32(data + 0x0c); + /* Short NOR slice: take the ARM bytes we have. Never expand. */ + if (!hdr_sz || hdr_sz > size - 0x400) + hdr_sz = size - 0x400; + /* 204E0: size rounded up to 16 for 422FFA; keep ≤ available. */ + hdr_sz = round_up(hdr_sz, 16); + if (hdr_sz > size - 0x400) + hdr_sz = (size - 0x400) & ~15u; + body = data + 0x400; + body_len = hdr_sz; + rev = data[7]; + /* + * Disk path (1A640) sends the raw file, no 204E0. + * NOR 204E0 rev≠3 returns 33 after writing outputs and + * the NOR path frees the buffer. Rev 4 plaintext is the + * disk-shaped image — send the whole 8740+ARM at dest 0. + */ + if (rev != 3) { + grape_vinfo(n, + "204E0 ARM-at-0 %zuB dest 0 (rev=%u hdr+0x0c=0x%x file=%zu)\n", + body_len, rev, get_unaligned_le32(data + 0x0c), + size); + if (force_gid && size >= 0x400 + 16) { + u8 *try = kmemdup(data + 0x400, min_t(size_t, body_len, size - 0x400), + GFP_KERNEL); + if (try && grape_422ffa_mmio(&n->spi->dev, try, + round_up(min_t(size_t, body_len, size - 0x400) & ~15u, 16), + false) == 0 && + grape_looks_like_arm(try, min_t(size_t, 16, body_len))) { + grape_vinfo(n, "force_gid 422FFA ARM ok\n"); + body = try; + body_len = min_t(size_t, body_len, size - 0x400); + dec = try; + } else { + kfree(try); + } + } + goto send; + } + grape_vinfo(n, + "204E0 ARM-at-0 %zu bytes rev=%u (hdr+0x0c=0x%x file=%zu)\n", + body_len, rev, get_unaligned_le32(data + 0x0c), size); + + if (rev == 3) { + u8 *probe; + bool verified = false; + + probe = kmemdup(data, 16, GFP_KERNEL); + if (!probe) + return -ENOMEM; + if (grape_gid_crypt(&n->spi->dev, probe, 16, true) == 0 && + !memcmp(probe, data + 0x40, 16)) { + verified = true; + grape_vinfo(n, "26CCC GID verify OK\n"); + } else { + memcpy(probe, data, 16); + if (grape_422ffa_mmio(&n->spi->dev, probe, 16, + true) == 0 && + !memcmp(probe, data + 0x40, 16)) { + verified = true; + grape_vinfo(n, + "26CCC 422FFA verify OK\n"); + } else { + dev_warn(&n->spi->dev, + "26CCC GID verify fail (sig %02x%02x%02x%02x got %02x%02x%02x%02x)\n", + data[0x40], data[0x41], + data[0x42], data[0x43], + probe[0], probe[1], + probe[2], probe[3]); + } + } + kfree(probe); + + dec = kmemdup(body, body_len, GFP_KERNEL); + if (!dec) + return -ENOMEM; + /* 204E0: one-shot 422FFA, not the Linux AES walk. */ + ret = grape_422ffa_mmio(&n->spi->dev, dec, body_len, + false); + if (ret || !grape_looks_like_arm(dec, body_len)) { + memcpy(dec, body, body_len); + ret = grape_gid_crypt(&n->spi->dev, dec, + body_len, false); + } + grape_vinfo(n, + "204E0 GID decrypt ret=%d arm=%d head %02x %02x %02x %02x ver=%d\n", + ret, grape_looks_like_arm(dec, body_len), + dec[0], dec[1], dec[2], dec[3], verified); + if (!ret && body_len > 0xd210) + grape_vinfo(n, + "ARM +0x54 %02x%02x%02x%02x +0x100 %02x%02x%02x%02x +0x1000 %02x%02x%02x%02x +0xD208 %02x%02x%02x%02x +0x20=%08x\n", + dec[0x54], dec[0x55], dec[0x56], dec[0x57], + dec[0x100], dec[0x101], dec[0x102], dec[0x103], + dec[0x1000], dec[0x1001], dec[0x1002], + dec[0x1003], + dec[0xd208], dec[0xd209], dec[0xd20a], + dec[0xd20b], + get_unaligned_le32(dec + 0x20)); + if (!ret) { + body = dec; + } else { + kfree(dec); + dec = NULL; + } + } + } else if (size < 4) { + dev_warn(&n->spi->dev, "FW empty (%zu) — skip download\n", size); + return -EINVAL; + } else { + grape_vinfo(n, "Grape FW download %zu bytes (no 8740)\n", + size); + } + +send: + /* + * v6: if body is already 18 E1 30 01… (post-GID constructed FW), + * send once and run post-download. Do not re-packetize ARM. + */ + if (grape_looks_like_hbpp_data(body, body_len)) { + grape_vinfo(n, + "preconstructed HBPP DATA %zuB — direct SPI (no ARM wrap)\n", + body_len); + ret = grape_send_preconstructed_hbpp(n, body, body_len); + if (!ret) { + ret = grape_post_download(n); + if (!ret) + ret = grape_cmd_2d54c(n); + } + kfree(dec); + return ret; + } + + { + size_t official = grape_official_arm_len(body, body_len); + size_t dl_len = body_len; + const u8 *dl_body = body; + u8 *z2_prep = NULL; + u8 *pad_buf = NULL; + u8 *win; + int try, cal; + + if (official < body_len) { + grape_vinfo(n, + "cap ARM %zu -> %zu (204E0 +0x0c; strip S/C fill)\n", + body_len, official); + body_len = official; + dl_len = body_len; + } + + z2_prep = grape_maybe_prepend_z2_hdr(n, body, body_len, &dl_len); + if (z2_prep) + dl_body = z2_prep; + else if (dl_len & 3) { + pad_buf = kmalloc(round_up(dl_len, 4), GFP_KERNEL); + if (!pad_buf) { + kfree(dec); + return -ENOMEM; + } + memcpy(pad_buf, dl_body, dl_len); + memset(pad_buf + dl_len, 0, round_up(dl_len, 4) - dl_len); + dl_len = round_up(dl_len, 4); + dl_body = pad_buf; + grape_vinfo(n, "FW padded to %zu (4-byte align)\n", + dl_len); + } + grape_fw_audit(n, dl_body, dl_len, "2D640"); + + win = kzalloc(GRAPE_FW_HDR_LEN, GFP_KERNEL); + if (!win) { + kfree(z2_prep); + kfree(pad_buf); + kfree(dec); + return -ENOMEM; + } + /* + * Host cal source ≠ controller address. + * 2D640 ARM → dest = fw_dest + offset (OSOS fw_dest=0). + * 2D7A4 cal → dest = 0x00400200 + offset. + * EXEC 0x00100018 is mapped app PC — not upload dest. + */ + cal = grape_load_cal_window(n, win); + if (cal < 0) { + dev_err(&n->spi->dev, "2D7A4 aborted: no device cal\n"); + kfree(win); + kfree(z2_prep); + kfree(pad_buf); + kfree(dec); + return cal; + } + + grape_vinfo(n, + "2D640 ARM dest_base=0x%08x EXEC=0x%08x cal=0x%08x\n", + fw_dest, exec_addr, GRAPE_CAL_DEST); + if (fw_dest == 0) + grape_vinfo(n, + "expect FW prefix: 18 e1 30 01 07 fc 00 00 00 00 01 03 (len=0x1ff0)\n"); + else + dev_warn(&n->spi->dev, + "fw_dest override 0x%08x — OSOS uses 0 (A/B only)\n", + fw_dest); + grape_vinfo(n, + "expect CAL prefix: 18 e1 30 01 00 80 02 00 00 40 00 c2\n"); + + /* 20E94: 273A0 up to 3 times, no 1A878 between. */ + for (try = 0; try < 3; try++) { + ret = grape_send_blob(n, dl_body, dl_len, fw_dest); + if (ret) { + dev_err(&n->spi->dev, + "2D640 ARM@0x%08x try %d: %d\n", + fw_dest, try, ret); + continue; + } + n->fw_uploaded = true; + grape_fw_readback(n, "post-2D640"); + ret = grape_send_blob(n, win, GRAPE_FW_HDR_LEN, + GRAPE_CAL_DEST); + if (ret) { + dev_err(&n->spi->dev, + "2D7A4 cal@0x%08x try %d: %d\n", + GRAPE_CAL_DEST, try, ret); + continue; + } + n->cal_uploaded = true; + grape_vinfo(n, + "2D7A4 512B cal @0x%08x ACK (transport only)\n", + GRAPE_CAL_DEST); + grape_cal_readback(n, win); + ret = grape_post_download(n); + if (ret) { + dev_warn(&n->spi->dev, + "2D5B0 try %d: %d\n", try, ret); + continue; + } + ret = grape_cmd_2d54c(n); + if (!ret) { + grape_vinfo(n, + "2D54C EXEC sent (try %d) — await runtime ping\n", + try); + break; + } + dev_warn(&n->spi->dev, "2D54C try %d: %d\n", try, ret); + } + kfree(win); + kfree(z2_prep); + kfree(pad_buf); + } + kfree(dec); + /* EXEC transport success is NOT runtime_ready / fw_loaded. */ + return ret; +} + +static int grape_ping(struct grape *n, u16 *status_out) +{ + u8 tx[GRAPE_FRAME_LEN] = { 0 }; + u8 rx[GRAPE_FRAME_LEN] = { 0 }; + u16 csum, rx_csum; + int ret, tries; + + put_unaligned_le32(GRAPE_PING_TYPE, tx); + csum = grape_sum16(tx, 14); + put_unaligned_le16(csum, tx + 14); + + /* 182590: up to 5 retries, sleep 1 between. After EXEC, 16-bit + * pairs match the app SPI width; 8-bit PIO is bootloader-only. + */ + for (tries = 0; tries < 6; tries++) { + if (n->exec_sent && go_xfer) + ret = grape_burst_u16(n, tx, rx, GRAPE_FRAME_LEN); + else + ret = grape_burst16(n, tx, rx); + if (ret) + return ret; + + rx_csum = get_unaligned_le16(rx + 14); + if (!rx_csum && !grape_sum16(rx, 14)) { + dev_warn(&n->spi->dev, "ping rx all-zero (MISO dead)\n"); + return -EIO; + } + if (grape_sum16(rx, 14) == rx_csum) + break; + /* One dump per call; MultitouchTask rate-limits via ping_fails. */ + if (tries == 0 && n->ping_fails == 0) + dev_warn(&n->spi->dev, + "ping csum fail rx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + rx[0], rx[1], rx[2], rx[3], rx[4], + rx[5], rx[6], rx[7], rx[8], rx[9], + rx[10], rx[11], rx[12], rx[13], + rx[14], rx[15]); + { + u16 w0 = get_unaligned_be16(rx); + + if (grape_status_is_bootloader(w0)) + dev_warn(&n->spi->dev, + "bootloader status 0x%04x: answering 0x4F81, which is not a word sub_2C87E accepts\n", + w0); + } + if (tries == 5) + return -EIO; + msleep(1); + } + + if (status_out) + *status_out = get_unaligned_le16(rx + 1); + return 0; +} + +static void grape_map_coords(s16 rawx, s16 rawy, int *x, int *y) +{ + int xx = (GRAPE_ABS_X_MAX * ((int)rawx + 75)) / GRAPE_SCALE_X_DIV; + int yy = (GRAPE_ABS_Y_MAX * ((int)rawy + 75)) / GRAPE_SCALE_Y_DIV; + + if (xx < 0) + xx = 0; + if (xx > GRAPE_ABS_X_MAX) + xx = GRAPE_ABS_X_MAX; + yy = GRAPE_ABS_Y_MAX - yy; + if (yy < 0) + yy = 0; + if (yy > GRAPE_ABS_Y_MAX) + yy = GRAPE_ABS_Y_MAX; + *x = xx; + *y = yy; +} + +/* + * sub_187AB4 -- one report frame. + * + * Slot identity comes from the CONTACT ID in record byte 0, not from the + * record's position in the frame. Stock keeps eight persistent ID-keyed + * slots, reuses a slot when the same ID appears again, allocates a free one + * for a new ID, and explicitly releases any slot whose ID is absent from the + * frame. + * + * This used to bind slot i to record i and treat any non-zero byte 1 as + * down. That makes contacts jump whenever the controller reorders records, + * leaves stale contacts when a record disappears, and cannot express a + * release at all. Linux Type-B has the same requirement as stock here: + * identifiable contacts must keep slot identity, and a tracking ID that + * disappears must be released. + * + * input_mt_get_slot_by_key() does the keying, and input_mt_sync_frame() + * performs the release of every slot not touched this frame -- provided + * every present contact is reported first, which is why the loop no longer + * skips records. + * + * Byte 1 and byte 2 are both part of the state. Byte 1 == 0 is not-down. + * The finer bytes1/2 lifecycle stock runs is NOT transliterated here and is + * NOT established from this driver's evidence; what is implemented is the + * down/absent distinction plus ID keying. Do not invent pressure or width + * from bytes whose offsets are unproven. + */ +static void grape_parse_D(struct grape *n, const u8 *payload, unsigned int len) +{ + const u8 *rec; + u8 count, stride; + unsigned int off; + int i; + + if (!n->input) + return; + if (len < 18 || payload[0] != 0x44) + return; + + off = payload[2]; + count = payload[16]; + stride = payload[17]; + if (!stride || off >= len) + return; + if (count > GRAPE_SLOTS) + count = GRAPE_SLOTS; + + rec = payload + off; + for (i = 0; i < count; i++) { + s16 rawx, rawy; + int x, y, slot; + u8 id, st1, st2; + + if (rec + stride > payload + len) + break; + + id = rec[0]; + st1 = rec[1]; + st2 = rec[2]; + rawx = (s16)get_unaligned_le16(rec + 4); + rawy = (s16)get_unaligned_le16(rec + 6); + grape_map_coords(rawx, rawy, &x, &y); + + slot = input_mt_get_slot_by_key(n->input, id); + if (slot < 0) { + rec += stride; + continue; + } + + input_mt_slot(n->input, slot); + input_mt_report_slot_state(n->input, MT_TOOL_FINGER, st1 != 0); + if (st1) { + input_report_abs(n->input, ABS_MT_POSITION_X, x); + input_report_abs(n->input, ABS_MT_POSITION_Y, y); + pr_warn_ratelimited( + "grape touch slot%d id=%u s1=%u s2=%u raw=%d,%d -> %d,%d\n", + slot, id, st1, st2, rawx, rawy, x, y); + } + rec += stride; + } + /* Releases every slot no contact claimed this frame. */ + input_mt_sync_frame(n->input); + input_sync(n->input); +} + +/* + * sub_17E404 -- read one report, exactly as stock frames it. + * + * zero a 512-byte TX buffer + * tx[0] = 0xEA; tx[1] = 0x01; tx[2] = 0x01 + * *(u16 *)(tx + len - 2) = sum16(tx, 14) + * if (len > 0x200) len = 512 + * xfer(tx, len, rx, len) + * if (xfer failed || rx[0] != 0xEA) return 81 + * if ((u8)sum16(rx, 5)) return 87 + * plen = rx[2] + * if (plen && plen != 2) { + * if (*(u16 *)(rx + len - 2) == sum16(rx + 5, plen - 2)) + * deliver(rx + 5, plen - 2); + * else return 87 + * } + * + * The two checksum tests were both missing here. The header one is an + * 8-bit test -- stock takes the low byte of the 16-bit sum over rx[0..4] + * and requires zero -- and the payload one compares the trailer at + * rx[len - 2] against the sum over the report body. Without them any + * garbage that happened to start with 0xEA was handed to the parser. + * + * The old code also had a fallback that parsed from rx[5] whenever it saw + * 0x44 there, and floored the length at 16. Stock does neither: the + * length is whatever the pending count asked for, and a frame that fails + * either checksum is an error, not something to salvage. + */ +static int grape_read_reports(struct grape *n, u16 pending) +{ + unsigned int len = (unsigned int)pending + 5; + u8 *tx, *rx; + unsigned int plen; + int ret; + + if (len > GRAPE_READ_MAX) + len = GRAPE_READ_MAX; + if (len < 8) + return -EINVAL; + + tx = kzalloc(GRAPE_READ_MAX, GFP_KERNEL); + rx = kzalloc(GRAPE_READ_MAX, GFP_KERNEL); + if (!tx || !rx) { + ret = -ENOMEM; + goto out; + } + + tx[0] = GRAPE_MAGIC; + tx[1] = 0x01; + tx[2] = 0x01; + put_unaligned_le16(grape_sum16(tx, 14), tx + len - 2); + + ret = grape_xfer(n, tx, rx, len); + if (ret) + goto out; + + if (rx[0] != GRAPE_MAGIC) { + dev_info_once(&n->spi->dev, + "17E404 magic: rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], + rx[6], rx[7]); + ret = -EIO; + goto out; + } + + if ((u8)grape_sum16(rx, 5)) { + dev_info_once(&n->spi->dev, + "17E404 header sum: rx %02x %02x %02x %02x %02x\n", + rx[0], rx[1], rx[2], rx[3], rx[4]); + ret = -EIO; + goto out; + } + + plen = rx[2]; + if (plen && plen != 2) { + u16 want, got; + + if (5 + (plen - 2) > len) { + ret = -EIO; + goto out; + } + got = get_unaligned_le16(rx + len - 2); + want = grape_sum16(rx + 5, plen - 2); + if (got != want) { + dev_info_once(&n->spi->dev, + "17E404 payload sum %04x want %04x plen %u len %u\n", + got, want, plen, len); + ret = -EIO; + goto out; + } + grape_parse_D(n, rx + 5, plen - 2); + } + ret = 0; +out: + kfree(tx); + kfree(rx); + return ret; +} + +/* + * sub_188FFC -- ask how much is pending, then read exactly that. + * + * if (!powered) return 18 + * result = sub_182590(&pending, 5) + * if (result || (pending && (result = sub_17E404(pending + 5)))) + * if (retries) { sleep 1; recurse with retries - 1 } + * return result + * + * A pending count of zero is not a failure and not a reason to read: the + * part simply has nothing to hand over. Reading anyway, which this driver + * did from the ATTN path with a hardcoded zero, puts a frame on the bus + * that stock would never send. + */ +static int grape_service_once(struct grape *n, unsigned int retries) +{ + u16 pending = 0; + int ret; + + ret = grape_ping(n, &pending); + if (!ret && pending) + ret = grape_read_reports(n, pending); + if (ret && retries) { + msleep(1); + return grape_service_once(n, retries - 1); + } + return ret; +} + +static void grape_dump_pad(struct grape *n, unsigned int gpio, const char *name) +{ + void __iomem *b; + u32 pin, pcon, din, dir; + + if (!grape_verbose || !n->gpio_base) + return; + b = n->gpio_base + 32 * (gpio >> 3); + pin = gpio & 7; + pcon = readl(b); + din = readl(b + 0x04); + dir = readl(b + 0x14); + grape_vinfo(n, + "pad %s gpio%u pcon=%x din=%u dir=%u dout=%u punb=%u punc=%u\n", + name, gpio, (pcon >> (4 * pin)) & 0xf, + !!(din & BIT(pin)), !!(dir & BIT(pin)), + !!(readl(b + 0x08) & BIT(pin)), + !!(readl(b + 0x0c) & BIT(pin)), + !!(readl(b + 0x10) & BIT(pin))); +} + +/* + * Optional glass experiment only — not in OSOS sub_1A5AC. + * Default off (extra_por_pulse=0). + */ +static void grape_gpio_por_reset(struct grape *n) +{ + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 0); + msleep(reset_hold_ms); + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 1); + msleep(reset_release_ms); + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 0); + msleep(5); + grape_vinfo(n, "extra POR RST %dms low / %dms high\n", + reset_hold_ms, reset_release_ms); +} + +/* + * 1A5AC GPIO half (before 20848): + * 2075A(1) sleep5 → 20766(1) sleep15 → 20690(1) sleep5 → 11B70 + */ +static void grape_gpio_bringup(struct grape *n) +{ + int rail; + + grape_clkcon_replay(n); + + /* GPIOCMD only — gpiod set_value fights polarity on RST. */ + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 0); + msleep(5); + /* + * sub_20766(1): 439B00(1) rail first, 66A8(8)+sleep 3, then + * GPIOCMD EN mode 0 (not output-high). + */ + rail = d1830_grape_rail(true); + if (rail) + dev_warn(&n->spi->dev, "20766 PMIC rail: %d\n", rail); + else { + /* 66A8(8) is 345D40 thunk (0x220002B2), not an 8ms sleep */ + msleep(3); + } + /* 20766(1): EN mode 0, val 0. Do not cmd-15 the latch. */ + grape_gpiocmd_mode(n, GRAPE_GPIO_EN, 0, 0); + grape_dump_pad(n, GRAPE_GPIO_EN, "en-mode0"); + msleep(15); + /* 20690(1) after EN — OSOS order; required after 1A878 unmux */ + grape_spi2_pinmux(n, true); + msleep(5); + /* 1A5AC: 11B70 after remux, still in reset, before 20848 */ + grape_spi2_11b70(n); + grape_dump_pad(n, GRAPE_GPIO_EN, "en"); + grape_dump_pad(n, GRAPE_GPIO_RST, "rst"); + grape_dump_pad(n, GRAPE_GPIO_IRQ, "irq"); + grape_dump_pad(n, 87, "spi2-87"); + grape_dump_pad(n, 88, "spi2-88"); + grape_dump_pad(n, 89, "spi2-89"); + grape_dump_pad(n, 90, "spi2-90"); +} + +/* 1A5AC: 2075A(0) then sleep reset_release_ms (default 30). */ +static void grape_gpio_release_reset(struct grape *n) +{ + grape_gpiocmd_mode(n, GRAPE_GPIO_RST, 1, 1); + msleep(reset_release_ms); + grape_dump_pad(n, GRAPE_GPIO_RST, "rst-rel"); +} + +/* sub_20490(1) — GPIOCMD input + EIC enable for GPIO 38 */ +static void grape_irq_enable(struct grape *n) +{ + grape_gpiocmd_mode(n, GRAPE_GPIO_IRQ, 0, 0); + /* RetailOS: level, active-low → VIC EXT1 */ + if (s5l8740_eic_enable_gpio(GRAPE_GPIO_IRQ, IRQ_TYPE_LEVEL_LOW) == 0) + grape_vinfo(n, "EIC enabled GPIO%d level-low\n", + GRAPE_GPIO_IRQ); +} + +static int grape_1a5ac_and_download(struct grape *n, const u8 *data, + size_t size, const char *tag) +{ + unsigned int attempt; + int err; + + /* Dump path has no pre-1A5AC POR; gate for glass A/B only. */ + if (extra_por_pulse) + grape_gpio_por_reset(n); + grape_gpio_bringup(n); + err = grape_bootload_cmd(n); + if (err) + dev_warn(&n->spi->dev, "bootload %s: %d\n", tag, err); + msleep(15); /* 1A5AC: after 20848, before 2075A(0) */ + grape_gpio_release_reset(n); + if (skip_download) { + grape_vinfo(n, "skip_download — no 2D640\n"); + return 0; + } + /* 20E94: 26494 then 273A0; settle already done in release (30ms). */ + if (grape_probe_26494(n, tag)) { + dev_warn(&n->spi->dev, "26494 %s failed\n", tag); + return -EIO; + } + /* + * Retry the transfer before escalating. The caller's retry re-runs the + * whole rail/reset/HBPP bring-up, which is a lot of disruption for what + * is usually a transient bus error; the stock code retries just this. + */ + for (attempt = 0; attempt < download_tries; attempt++) { + err = grape_download_fw(n, data, size, false); + if (!err) + break; + dev_warn(&n->spi->dev, "download %s attempt %u: %d\n", + tag, attempt + 1, err); + } + n->fw_tried = true; + return err; +} + +/* + * 1703E8: 10 failed pings → 13A20(0) then 13A20(1). Cap recycles so a + * stuck bootloader does not spam forever (GO ACKs but app never runs). + */ +#define GRAPE_RECYCLE_MAX 3 + +static void grape_park(struct grape *n, const char *why) +{ + if (n->parked) + return; + if (no_park) { + dev_warn(&n->spi->dev, + "would park (%s); no_park=1, still servicing\n", why); + return; + } + n->parked = true; + n->stopped = true; + grape_verbose = false; + dev_warn(&n->spi->dev, + "grape parked (%s) — rmmod/insmod to retry\n", why); + /* + * Parking cuts the rail, which makes every post-mortem probe read + * an undriven bus and look exactly like the failure being + * investigated. touch_power_down only covers suspend, so there was + * no way to examine a part that had reached EXEC and stayed up. + */ + if (park_power_down) + grape_power_down(n); + else + dev_warn(&n->spi->dev, + "park_power_down=0: rail left on for probing\n"); +} + +/* ------------------------------------------------------------------ */ +/* Screen-sleep suspend / resume */ +/* */ +/* Two levels, because they trade power against wake latency: */ +/* */ +/* touch_power_down=0 IRQ masked and SPI2 released, controller still */ +/* powered. Resume is immediate. */ +/* touch_power_down=1 full 1A878 power cut including the PMU rail. */ +/* Resume has to re-run bring-up and download the */ +/* firmware again, so it costs a few hundred ms. */ +/* ------------------------------------------------------------------ */ + +static struct grape *grape_pm_dev; + +static bool touch_power_down = true; +module_param(touch_power_down, bool, 0644); +MODULE_PARM_DESC(touch_power_down, + "Cut the touch rail on screen sleep (default Y; N keeps it powered)"); + +static bool grape_pm_suspended; + +int n31_touch_suspend(void) +{ + struct grape *n = grape_pm_dev; + + if (!n) + return -ENODEV; + if (grape_pm_suspended) + return 0; + + if (n->irq > 0) + disable_irq(n->irq); + + if (touch_power_down) { + mutex_lock(&n->lock); + grape_power_down(n); + n->runtime_ready = false; + n->spi_ok = false; + mutex_unlock(&n->lock); + } else { + /* Stop driving the bus, leave the controller alive. */ + mutex_lock(&n->lock); + grape_spi2_pinmux(n, false); + mutex_unlock(&n->lock); + } + + grape_pm_suspended = true; + grape_vinfo(n, "touch suspended (power_down=%d)\n", touch_power_down); + return 0; +} +EXPORT_SYMBOL_GPL(n31_touch_suspend); + +int n31_touch_resume(void) +{ + struct grape *n = grape_pm_dev; + const struct firmware *fw = NULL; + const u8 *data = NULL; + u8 *kbuf = NULL; + size_t size = 0; + int ret = 0; + + if (!n) + return -ENODEV; + if (!grape_pm_suspended) + return 0; + + if (!touch_power_down) { + mutex_lock(&n->lock); + grape_spi2_pinmux(n, true); + mutex_unlock(&n->lock); + goto done; + } + + /* + * The controller lost its firmware with the rail, so this is the same + * bring-up the probe runs. Storage is back by the time a wake reaches + * here, so fetching the blob again is safe. + */ + ret = grape_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf, + &n->fw_from_ftl); + if (ret) { + dev_warn(&n->spi->dev, "touch resume: no firmware (%d)\n", ret); + goto done; + } + + mutex_lock(&n->lock); + n->fw_uploaded = false; + n->cal_uploaded = false; + n->requestcal_done = false; + n->exec_sent = false; + n->runtime_ready = false; + n->fw_loaded = false; + n->spi_ok = false; + ret = grape_1a5ac_and_download(n, data, size, "resume"); + mutex_unlock(&n->lock); + + grape_release_fw(fw, kbuf); + if (ret) + dev_warn(&n->spi->dev, "touch resume download: %d\n", ret); + +done: + if (n->irq > 0) + enable_irq(n->irq); + grape_pm_suspended = false; + grape_vinfo(n, "touch resumed (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(n31_touch_resume); + + +static void grape_recycle(struct grape *n) +{ + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + u16 st = 0; + + if (n->parked) + return; + if (n->recycle_count >= GRAPE_RECYCLE_MAX) { + grape_park(n, "recycle budget"); + return; + } + n->recycle_count++; + grape_vinfo(n, + "1703E8 10 ping fails — 13A20 recycle %u/%u\n", + n->recycle_count, GRAPE_RECYCLE_MAX); + grape_power_down(n); + n->fw_loaded = false; + n->fw_tried = false; + n->spi_ok = false; + n->ping_fails = 0; + msleep(50); + if (grape_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf, + &n->fw_from_ftl)) { + dev_warn(&n->spi->dev, "recycle: no FW (FTL or file)\n"); + grape_park(n, "no firmware"); + return; + } + grape_1a5ac_and_download(n, data, size, "recycle"); + grape_release_fw(fw, kbuf); + msleep(2); + if (!grape_ping(n, &st)) { + n->spi_ok = true; + grape_vinfo(n, "recycle ping ok, status=0x%04x\n", st); + } else if (n->recycle_count >= GRAPE_RECYCLE_MAX) { + grape_park(n, "still bootloader after GO"); + } +} + +static int grape_service(struct grape *n) +{ + int ret; + + if (n->parked) + return -ENODEV; + + /* + * sub_188FFC, with its own five retries inside grape_service_once: + * ask sub_182590 how much is pending, and read exactly that much + * with sub_17E404 only when the count is non-zero. + * + * What used to be here read a report whenever ATTN was asserted and + * the ping had failed, passing a pending count of zero. That was + * added because a failed ping was being taken as proof the part had + * moved on to the application, which it never was -- the ping is + * sub_182590 and a failure means its checksum did not verify. A read + * framed from a zero count is a frame stock never sends, so it is + * gone; if the count is zero the part has nothing to hand over and + * the right thing is to wait for the next ATTN. + */ + ret = grape_service_once(n, 5); + if (!ret) { + n->spi_ok = true; + n->ping_fails = 0; + n->attn_fails = 0; + return 0; + } + + n->ping_fails++; + if (n->ping_fails <= 3 || n->ping_fails == 10) + dev_info(&n->spi->dev, + "188FFC failed (%d), attempt %u, attn=%d\n", + ret, n->ping_fails, + n->attn ? gpiod_get_value_cansleep(n->attn) : -1); + return ret; +} + +static irqreturn_t grape_irq_thread(int irq, void *data) +{ + struct grape *n = data; + bool mask = false; + + WRITE_ONCE(n->irq_count, n->irq_count + 1); + mutex_lock(&n->lock); + if (!n->stopped) { + if (grape_service(n)) + n->irq_fails++; + else + n->irq_fails = 0; + + /* + * Re-arm ATN after every service. The stock ATN thread at + * EA 0x000F1B74 calls sub_20490(1) at 0x000F1BAE on each + * pass round its loop; this driver armed once in probe and + * never again. + */ + grape_irq_enable(n); + } + /* + * ATTN is only released when a report is actually read, so a + * service that keeps failing leaves the line asserted and this + * handler re-entered without pause. Mask rather than spin. + */ + if (irq_fail_max && !n->irq_masked && + n->irq_fails >= irq_fail_max) { + n->irq_masked = true; + mask = true; + } + mutex_unlock(&n->lock); + + if (mask) { + disable_irq_nosync(irq); + dev_warn(&n->spi->dev, + "ATTN masked after %u failed services; rearm with attn_rearm\n", + n->irq_fails); + } + return IRQ_HANDLED; +} + +static void grape_try_firmware(struct grape *n) +{ + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + + if (n->fw_tried || n->fw_loaded) + return; + /* Don't hammer SPI with 60KB download if ping already fails */ + if (!n->spi_ok) { + u16 st; + + if (grape_ping(n, &st)) + return; + n->spi_ok = true; + } + + if (grape_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf, + &n->fw_from_ftl)) + return; + + n->fw_tried = true; + grape_download_fw(n, data, size, false); + grape_release_fw(fw, kbuf); +} + +static int grape_poll_thread(void *data) +{ + struct grape *n = data; + int wait; + int fail_backoff_ms = 50; + + for (wait = 0; wait < 30 && !n->fw_loaded && !n->fw_tried && + !kthread_should_stop(); wait++) { + mutex_lock(&n->lock); + grape_try_firmware(n); + mutex_unlock(&n->lock); + if (!n->fw_loaded && !n->fw_tried) + msleep(1000); + } + if (!n->fw_loaded && !n->fw_tried) { + n->fw_tried = true; + grape_vinfo(n, + "no apple/grape.bin — bootload+ping only\n"); + } + + while (!kthread_should_stop()) { + bool do_poll = true; + int attn = -1; + + /* + * 1703E8/188FFC runs every MultitouchTask loop, not only + * on attn. Gating on attn skipped ping if the app released + * GPIO 38 after 2D54C (bootloader holds it low). + */ + if (n->attn) + attn = gpiod_get_value_cansleep(n->attn); + do_poll = true; + + mutex_lock(&n->lock); + if (n->parked || n->stopped) { + mutex_unlock(&n->lock); + /* Parked: sleep until kthread_stop; no SPI traffic. */ + msleep(500); + continue; + } + /* + * Watchdog only: with the IRQ live, service from here just + * once every GRAPE_WATCHDOG_MS, and only if the handler has + * not run since the last pass. n->irq_count is bumped by + * grape_irq_thread(). + */ + if (do_poll && n->use_irq) { + unsigned int c = READ_ONCE(n->irq_count); + + if (c != n->irq_seen) { + n->irq_seen = c; + do_poll = false; + } + } + if (do_poll) { + grape_service(n); + if (n->ping_fails >= 10) + grape_recycle(n); + fail_backoff_ms = n->spi_ok ? 50 : + min(fail_backoff_ms * 2, 2000); + } + mutex_unlock(&n->lock); + + /* + * When the IRQ is live it is the primary trigger and this + * thread is only a watchdog. + * + * Stock services on ATN: the thread at EA 0x000F1B74 blocks on + * sub_43DAD4(0x43) and runs sub_1703E8 only when the ISR + * signals event 67. It has no periodic poll at all. + * + * We polled every 50 ms regardless of ATN, so both paths + * consumed the same report queue -- the mutex kept them from + * racing on the bus but not from one path taking the frame the + * other was there to service, which hides a dead IRQ. + */ + if (!do_poll) + msleep(20); + else if (!n->spi_ok) + msleep(fail_backoff_ms); + else if (n->use_irq) + msleep(GRAPE_WATCHDOG_MS); + else + msleep(50); + } + return 0; +} + +static ssize_t touch_cal_blob_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct grape *n = dev_get_drvdata(dev); + + if (!n || !n->have_touch_cal) + return -ENODATA; + if (off >= GRAPE_TOUCH_CAL_LEN) + return 0; + if (off + count > GRAPE_TOUCH_CAL_LEN) + count = GRAPE_TOUCH_CAL_LEN - off; + memcpy(buf, n->touch_cal + off, count); + return count; +} + +static struct bin_attribute touch_cal_blob_attr = { + .attr = { + .name = "touch_cal_blob", + .mode = 0444, + }, + .size = GRAPE_TOUCH_CAL_LEN, + .read = touch_cal_blob_read, +}; + +static void grape_touch_cal_sysfs_remove(struct grape *n) +{ + if (!n || !n->touch_cal_sysfs) + return; + sysfs_remove_bin_file(&n->spi->dev.kobj, &touch_cal_blob_attr); + n->touch_cal_sysfs = false; +} + +/* + * Bring-up state. This driver carries two dozen module parameters and had + * no way to read back what any of them achieved, so a failed download and + * a controller that never booted looked identical from userspace. + * + * The flags are the download milestones in order, so the first one that + * reads 0 is where the sequence stopped. + */ +static ssize_t state_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct grape *n = spi_get_drvdata(to_spi_device(dev)); + + if (!n) + return -ENODEV; + return sysfs_emit(buf, + "spi_ok=%d fw_tried=%d fw_uploaded=%d cal_uploaded=%d\n" + "requestcal_done=%d exec_sent=%d runtime_ready=%d\n" + "irq=%d suspended=%d\n", + n->spi_ok, n->fw_tried, n->fw_uploaded, + n->cal_uploaded, n->requestcal_done, n->exec_sent, + n->runtime_ready, n->irq, grape_pm_suspended); +} +/* + * Raw transfer window. + * + * The application firmware answers the bootloader's ping with a + * repeating 16-bit word rather than a checksummed frame. A repeating + * word is not an idle bus -- the part is clocking something out -- so + * the remaining unknown is framing, not liveness. Guessing at that one + * hypothesis per rebuild is far too slow, so expose the transfer itself: + * + * echo 'ea 01 01 00 ...' > raw_xfer send these bytes + * cat raw_xfer what came back + * echo N > raw_len pad/clock out to N bytes + * raw_mode=0 8-bit PIO (bootloader width) + * raw_mode=1 16-bit PIO (NOT stock; family comparison only) + * raw_mode=2 SPI core + * + * Read-only against a part that is already running: it clocks bytes and + * reports what returns. It cannot wedge the SoC the way arming an + * unclaimed level interrupt can, which is the whole point of preferring + * it to another EIC experiment. + */ +/* + * 0 = 8-bit PIO, which is the only width stock ever uses. + * + * This defaulted to 1, the 16-bit diagnostic. sub_11B70 ORs the literal + * 0x4000 into SPISETUP once per port and never varies the word-size bits, + * and both of its call sites pass the same mode 0x1A, so no N31 transfer is + * ever wider than a byte. + */ +static int raw_mode; +module_param(raw_mode, int, 0644); +MODULE_PARM_DESC(raw_mode, + "raw_xfer transport: 0=8-bit PIO 1=16-bit PIO 2=SPI core"); + +static unsigned int raw_len; +module_param(raw_len, uint, 0644); +MODULE_PARM_DESC(raw_len, + "Clock raw_xfer out to this many bytes (0 = as written)"); + +static int grape_raw_do(struct grape *n, const u8 *tx, u8 *rx, + unsigned int len) +{ + switch (raw_mode) { + case 0: + return grape_burst(n, tx, rx, len); + case 2: + return grape_xfer(n, tx, rx, len); + default: + return grape_burst_u16(n, tx, rx, len); + } +} + +static ssize_t raw_xfer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct grape *n = dev_get_drvdata(dev); + unsigned int i; + int len = 0; + + if (!n || !n->raw_rx || !n->raw_n) + return sysfs_emit(buf, "(no transfer yet)\n"); + + mutex_lock(&n->lock); + for (i = 0; i < n->raw_n && len < PAGE_SIZE - 4; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%02x%c", + n->raw_rx[i], + ((i & 15) == 15 || i + 1 == n->raw_n) ? + '\n' : ' '); + mutex_unlock(&n->lock); + return len; +} + +static ssize_t raw_xfer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct grape *n = dev_get_drvdata(dev); + unsigned int nb = 0, want; + const char *p = buf; + u8 *tx, *rx; + int ret; + + if (!n) + return -ENODEV; + + tx = kzalloc(GRAPE_READ_MAX, GFP_KERNEL); + rx = kzalloc(GRAPE_READ_MAX, GFP_KERNEL); + if (!tx || !rx) { + ret = -ENOMEM; + goto out; + } + + while (*p && nb < GRAPE_READ_MAX) { + unsigned int v; + + while (*p == ' ' || *p == ',' || *p == '\n' || + *p == '\t') + p++; + if (!*p) + break; + if (sscanf(p, "%2x", &v) != 1) + break; + tx[nb++] = (u8)v; + while (*p && *p != ' ' && *p != ',' && *p != '\n') + p++; + } + + /* Clocking past the written bytes is how you see a reply that + * arrives after the command, so honour raw_len when it is longer. */ + want = raw_len ? raw_len : nb; + if (!want || want > GRAPE_READ_MAX) { + ret = -EINVAL; + goto out; + } + if ((raw_mode == 1) && (want & 1)) + want++; + + mutex_lock(&n->lock); + ret = grape_raw_do(n, tx, rx, want); + if (!ret) { + if (!n->raw_rx) + n->raw_rx = devm_kzalloc(&n->spi->dev, + GRAPE_READ_MAX, GFP_KERNEL); + if (n->raw_rx) { + memcpy(n->raw_rx, rx, want); + n->raw_n = want; + } + } + mutex_unlock(&n->lock); + + dev_info(&n->spi->dev, + "raw_xfer mode=%d len=%u ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + raw_mode, want, ret, rx[0], rx[1], rx[2], rx[3], + rx[4], rx[5], rx[6], rx[7]); +out: + kfree(tx); + kfree(rx); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(raw_xfer); + +/* + * attn sysfs: the line the application drives when it has a report. + * Readable straight from DIN, which is why touch does not have to wait + * for the EIC to be understood. + */ +static ssize_t attn_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct grape *n = dev_get_drvdata(dev); + + if (!n || !n->attn) + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%d\n", + gpiod_get_value_cansleep(n->attn)); +} +static DEVICE_ATTR_RO(attn); + +static DEVICE_ATTR_RO(state); + +/* Force a re-run of the bring-up without unbinding the driver. */ +static ssize_t redownload_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct grape *n = spi_get_drvdata(to_spi_device(dev)); + int ret; + + if (!n) + return -ENODEV; + if (buf[0] != '1') + return -EINVAL; + ret = n31_touch_suspend(); + if (!ret) + ret = n31_touch_resume(); + return ret ? ret : count; +} +/* + * Re-arm ATTN after it was masked. + * + * Masking is not a fix, it is a way to stop a level-triggered line the + * part never releases from spinning the CPU. Once something has actually + * changed, write here to let it try again without a module reload. + */ +static ssize_t attn_rearm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct grape *n = dev_get_drvdata(dev); + bool rearm = false; + + if (!n) + return -ENODEV; + mutex_lock(&n->lock); + if (n->irq_masked) { + n->irq_masked = false; + n->ping_fails = 0; + n->attn_fails = 0; + n->irq_fails = 0; + rearm = true; + } + mutex_unlock(&n->lock); + + if (rearm && n->irq > 0) { + enable_irq(n->irq); + dev_info(dev, "ATTN re-armed\n"); + } + return count; +} +static DEVICE_ATTR_WO(attn_rearm); + +static DEVICE_ATTR_WO(redownload); + +static struct attribute *grape_attrs[] = { + &dev_attr_attn_rearm.attr, + &dev_attr_raw_xfer.attr, + &dev_attr_attn.attr, + &dev_attr_state.attr, + &dev_attr_redownload.attr, + NULL, +}; +ATTRIBUTE_GROUPS(grape); + +static int grape_probe(struct spi_device *spi) +{ + struct grape *n; + struct input_dev *input; + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + u16 ping_st = 0; + int err; + + n = devm_kzalloc(&spi->dev, sizeof(*n), GFP_KERNEL); + if (!n) + return -ENOMEM; + n->spi = spi; + /* + * 8-bit only. This used to arm a 16-bit blob transport. + * + * Stock has exactly one SPI transport and it is byte-at-a-time: + * sub_4043D0's PIO loop is "ldrb.w r3,[ip],#1 / str r3,[SPITXDATA]" + * with +0x4C set to 1 per byte, and its IRQ engine does the same. + * The word-size bits in SPISETUP are a constant stock never varies. + * + * The Grape really is a 16-bit slave, but stock feeds it big-endian + * 16-bit words as BYTE PAIRS, doing the halfword swap in the CPU + * while packing the buffer (frame builder at EA 0x0003B9D0: each + * source u32 W is emitted as (W>>8), W, (W>>24), (W>>16)). It never + * sets a 16-bit SPI word width to achieve it. + * + * That also explains the note at the top of this file that 16-bit + * writes fail 4BC1 -- the byte order was being produced by the + * controller instead of by the packing, which is not the same thing. + */ + n->blob16 = false; + grape_verbose = verbose || !quiet; + mutex_init(&n->lock); + spi_set_drvdata(spi, n); + grape_pm_dev = n; + if (sysfs_create_groups(&spi->dev.kobj, grape_groups)) + dev_warn(&spi->dev, "sysfs groups failed\n"); + + n->gpio_base = devm_ioremap(&spi->dev, S5L8740_GPIO_PHYS, 0x400); + n->gpiocmd = devm_ioremap(&spi->dev, S5L8740_GPIOCMD_PHYS, 4); + n->spi2 = devm_ioremap(&spi->dev, S5L8740_SPI2_PHYS, 0x80); + if (!n->gpio_base || !n->gpiocmd || !n->spi2) + return -ENOMEM; + + n->enable = devm_gpiod_get_optional(&spi->dev, "enable", GPIOD_ASIS); + if (IS_ERR(n->enable)) + return PTR_ERR(n->enable); + n->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_ASIS); + if (IS_ERR(n->reset)) + return PTR_ERR(n->reset); + n->attn = devm_gpiod_get_optional(&spi->dev, "attn", GPIOD_IN); + if (IS_ERR(n->attn)) + return PTR_ERR(n->attn); + + err = grape_acquire_touch_cal_cal(n); + if (err) + return err; + if (!sysfs_create_bin_file(&spi->dev.kobj, &touch_cal_blob_attr)) + n->touch_cal_sysfs = true; + else + dev_warn(&spi->dev, "touch_cal_blob sysfs failed\n"); + + data = NULL; + size = 0; + err = grape_acquire_fw(&spi->dev, &data, &size, &fw, &kbuf, + &n->fw_from_ftl); + if (err) + dev_warn(&spi->dev, + "no grape firmware (%d) — A34 touch calibration is present but 2D640 cannot run\n", + err); + + if (!data || !size) { + grape_park(n, "no grape firmware"); + } else { + int attempt; + + /* + * 13A20(1): first try is 1A5AC only. 1A878 + sleep 50 + * only after a failed 1A5AC, max 3. remove() already + * 1A878s on reload. + */ + /* + * sub_20E94 reads PMIC 0x51 here, between the 26494 probe and + * the download loop, and discards the value. Do the same: the + * read itself may be the point. + */ + { + int (*pmic_read)(void); + + pmic_read = (int (*)(void)) + __symbol_get("d1830_touch_bringup_read"); + if (pmic_read) { + pmic_read(); + __symbol_put("d1830_touch_bringup_read"); + } + } + + for (attempt = 0; attempt < 3; attempt++) { + if (attempt) { + grape_power_down(n); + msleep(50); + } + /* 0xEE is iOS3 Z2-only; N31 wake is 19 C1 in reset. */ + mutex_lock(&n->lock); + n->fw_uploaded = false; + n->cal_uploaded = false; + n->requestcal_done = false; + n->exec_sent = false; + n->runtime_ready = false; + n->fw_loaded = false; + n->spi_ok = false; + grape_1a5ac_and_download(n, data, size, + attempt ? "retry" : "1A5AC"); + { + u16 st = 0; + + /* + * Cross EXEC boundary: short wait then runtime + * ping only. No HBPP 1A A1 until ping fails. + */ + /* + * No wait when EXEC was sent: the single 40 ms settle already + * happened in grape_exec(), which owns the state transition. + * sub_273A0 waits once, not three times. + */ + if (!n->exec_sent) + msleep(2); + err = grape_ping(n, &ping_st); + if (!err) { + n->spi_ok = true; + n->runtime_ready = true; + n->fw_loaded = true; + grape_dev_vinfo(&spi->dev, + "runtime ping ok status=0x%04x (ready)\n", + ping_st); + } else { + dev_warn(&spi->dev, + "runtime ping fail attempt %d (exec_sent=%d)\n", + attempt, n->exec_sent); + grape_peek(n, "post-go-fail"); + /* Diagnostics only after runtime fail. */ + if (grape_status_poll(n, &st) == 0) + grape_dev_vinfo(&spi->dev, + "post-fail HBPP status 0x%04x\n", + st); + err = 0; /* keep 1A878 retries going */ + } + } + mutex_unlock(&n->lock); + /* + * Stock retries sub_273A0 only when the EXEC transfer + * itself failed, never because a ping came back + * wrong. Re-entering the loop re-asserts reset and + * re-uploads, so treating a failed ping as a reason + * to retry destroys an application that had already + * started. + */ + if (n->runtime_ready || + (!require_runtime && n->exec_sent)) + break; + } + } + grape_release_fw(fw, kbuf); + grape_dev_vinfo(&spi->dev, + "grape state uploaded=%d cal=%d reqcal=%d exec=%d runtime=%d spi_ok=%d\n", + n->fw_uploaded, n->cal_uploaded, n->requestcal_done, + n->exec_sent, n->runtime_ready, n->spi_ok); + + /* + * Input device first, ATN second. + * + * The threaded level-low IRQ used to be requested before the input + * device existed. ATN is level-sensitive and may already be asserted + * at this point, so the handler could reach grape_parse_D() with + * n->input still NULL. grape_parse_D() now returns early on a NULL + * input as well, but the ordering is the actual fix. + */ + input = devm_input_allocate_device(&spi->dev); + if (!input) { + grape_touch_cal_sysfs_remove(n); + return -ENOMEM; + } + n->input = input; + input->name = "Apple Grape"; + input->phys = "grape/input0"; + input->id.bustype = BUS_SPI; + __set_bit(INPUT_PROP_DIRECT, input->propbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, GRAPE_ABS_X_MAX, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, GRAPE_ABS_Y_MAX, 0, 0); + err = input_mt_init_slots(input, GRAPE_SLOTS, INPUT_MT_DIRECT); + if (err) { + grape_touch_cal_sysfs_remove(n); + return err; + } + err = input_register_device(input); + if (err) { + grape_touch_cal_sysfs_remove(n); + return err; + } + + if (n->cal_uploaded && n->exec_sent && + (n->runtime_ready || !require_runtime)) { + /* + * sub_1A5AC arms ATN (sub_20490) only after sub_20E94 + * reports success. Done here, with the input device already + * registered, so no interrupt can outrun it. + * + * No IRQF_TRIGGER_LOW: that makes genirq call eic_set_type() + * and rewrite the INTLEVEL/INTTYPE pair grape_irq_enable() + * just programmed from sub_40641C. + */ + grape_irq_enable(n); + n->irq = spi->irq; + if (n->irq > 0) { + err = devm_request_threaded_irq(&spi->dev, n->irq, NULL, + grape_irq_thread, + IRQF_ONESHOT, + "grape", n); + if (err) { + dev_warn(&spi->dev, + "VIC IRQ %d request failed %d — attn poll\n", + n->irq, err); + n->irq = -1; + } else { + n->use_irq = true; + grape_dev_vinfo(&spi->dev, + "IRQ-driven (VIC irq %d + EIC GPIO%d)\n", + n->irq, GRAPE_GPIO_IRQ); + } + } + } else { + dev_err(&spi->dev, + "Grape boot failed: cal=%d exec=%d runtime=%d; not registering input\n", + n->cal_uploaded, n->exec_sent, n->runtime_ready); + if (!n->parked) + grape_park(n, "boot incomplete"); + return 0; + } + + + if (ping_st) { + mutex_lock(&n->lock); + if (ping_st) + grape_read_reports(n, ping_st); + mutex_unlock(&n->lock); + } + n->thread = kthread_run(grape_poll_thread, n, "grape-poll"); + if (IS_ERR(n->thread)) { + dev_warn(&spi->dev, + "grape-poll kthread %ld — poll via IRQ only\n", + PTR_ERR(n->thread)); + n->thread = NULL; + } + + dev_info(&spi->dev, "Grape up (attn=%d runtime=%d)\n", + !!n->attn, n->runtime_ready); + return 0; +} + +/* + * OSOS sub_1A878 is the disable path -- 20490(0), RST asserted, 20690(0) + * to release the SPI2 pads, rail off, EN mode 1 -- and grape_power_down + * already implements it. It was only ever reached through remove(), + * which a shutdown or kexec does not call, so the part was left powered + * and holding the bus across the handover. + */ +static void grape_shutdown(struct spi_device *spi) +{ + struct grape *n = spi_get_drvdata(spi); + + if (!n) + return; + n->stopped = true; + if (n->thread) + kthread_stop(n->thread); + n->thread = NULL; + grape_power_down(n); +} + +static void grape_remove(struct spi_device *spi) +{ + grape_pm_dev = NULL; + struct grape *n = spi_get_drvdata(spi); + + n->stopped = true; + if (n->thread) + kthread_stop(n->thread); + grape_touch_cal_sysfs_remove(n); + /* + * probe creates these but nothing removed them, so the group + * outlived the module. The next insmod then hit a duplicate + * filename, and internal_create_group rolls the whole group back + * on failure -- so a second load silently lost every attribute, + * including state and redownload. It looked like the attributes + * were never registered rather than registered twice. + */ + sysfs_remove_groups(&spi->dev.kobj, grape_groups); + grape_clkcon_restore(); + grape_power_down(n); +} + +static const struct of_device_id grape_of_match[] = { + { .compatible = "apple,grape" }, + { } +}; +MODULE_DEVICE_TABLE(of, grape_of_match); + +static struct spi_driver grape_driver = { + .driver = { + .name = "apple-grape", + .of_match_table = grape_of_match, + }, + .probe = grape_probe, + .remove = grape_remove, + .shutdown = grape_shutdown, +}; +module_spi_driver(grape_driver); + +MODULE_DESCRIPTION("Apple Grape multitouch (N31 SPI2, RetailOS-matched)"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("apple/grape.bin"); diff --git a/drivers/input/touchscreen/apple-nimbus.c b/drivers/input/touchscreen/apple-nimbus.c new file mode 100755 index 00000000000000..055f08aba0e1f6 --- /dev/null +++ b/drivers/input/touchscreen/apple-nimbus.c @@ -0,0 +1,3920 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple Nimbus / Grape multitouch — N31 SPI2 @ 0x3D200000 + * + * RetailOS 1.0.2 sequences (osos extracts): + * bring-up sub_1A5AC / 2075A / 20766 / 20690 / 11B70 + * teardown sub_1A878: IRQ off, RST, 20690(0), rail off, EN mode 1 + * (13A20 retries: 1A878 + sleep 50 + 1A5AC, max 3) + * grape.bin IS the app. SEC bootloader has no grape/Nimbus path. + * 1A5AC delays 2075A(1)+5 → 20766(1)+15 → 20690(1)+5 → 11B70 → + * 20848 +15 → 2075A(0)+30 → 20E94 (no extra POR pulse) + * bootload cmd sub_20848(6593): 6593 = 0x19C1 = HBPP ENTER (not a + * firmware byte count). TX 19 C1 + (18 E1)* pad. + * + * Firmware (SPI → controller @ dest=offset, start 0) — 1A640 / 204E0 / 2D640: + * Full "8740" GrapeFirmware-style container on disk: + * 0x000..0x3ff Apple/N31 header (NOT sent over SPI) + * 0x400.. ARM app body; length = le32(file+0x0c) + * rev 3: GID-CBC IV=0 decrypt of ARM body before send + * ARM-only cut (no 8740 magic, e.g. 18 F0 9F E5…): whole file = body + * Chunks: max 0x1FF0. Upload = sub_3B9D0 envelope (see below). ACK 0x4BC1. + * Callsite 2D640: r1 = firmware offset (NOT 0x00100000). EXEC 0x00100018 + * is the bootloader-mapped app PC, not the upload destination. + * + * Calibration (SPI → controller @ 0x00400200) — 2D7A4 / 273A0: + * Per-device IsyS comes from the A34 handoff, not a host file: + * desc @ 0x2202FE18 (sub_A34(24)): magic 0x53797349 "IsyS", ptr @ +4 + * memcpy 0x560 from ptr (sub_564) + * cal = bytes at decimal +350, length 0x200, reverse each u32 (sub_273A0) + * then 2D7A4 that window; DATA packet still does b1b0b3b2 wire swizzle + * Callsite 2D7A4: r1 = 0x00400200 + offset. + * No grape-nimbus-cal.bin, no FTL IsyS scan, no GrapeFirmware.bin+350. + * Preferred source: U-Boot copy in reserved DRAM, advertised in /chosen + * apple,n31-isys-addr / apple,n31-isys-size. Never consume the original + * A34 pointer. Live A34 ioremap is fallback only. + * + * sub_3B9D0 upload frame (full SPI length = payload_len + 16): + * [0..1] 18 E1 + * [2..3] 30 01 + * [4..5] word_count = len>>2 as hi,lo (len>>10, len>>2) + * [6..9] dest swizzled BYTE1,BYTE0,BYTE3,BYTE2 + * [10..11] u16 byte-sum of [4..9], big-endian + * [12..12+len) payload u32s swizzled B1 B0 B3 B2 + * [12+len..] u32 byte-sum of swizzled payload, stored B1 B0 B3 B2 + * First FW prefix (dest=0,len=0x1FF0): 18 E1 30 01 07 FC 00 00 00 00 01 03 + * Cal prefix (dest=0x400200,len=0x200): 18 E1 30 01 00 80 02 00 00 40 00 C2 + * + * After cal: 2D5B0 RequestCal → 2D54C EXEC → 40 ms → runtime ping. + * Register input only after runtime ping. runtime_ready = ping csum. + * + * Firmware host file: request_firmware("apple/grape-nimbus.bin") and/or + * FTL gpfw/8740 when fw_prefer_ftl=1. That is the ARM app, not cal. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NIMBUS_MAGIC 0xEA +#define NIMBUS_PING_TYPE 490 +#define NIMBUS_BOOTLOAD_WORD 6593 /* 0x19C1 */ +#define NIMBUS_FRAME_LEN 16 +#define NIMBUS_READ_MAX 512 +#define NIMBUS_SLOTS 8 +#define NIMBUS_ABS_X_MAX 239 +#define NIMBUS_ABS_Y_MAX 431 +#define NIMBUS_SCALE_X_DIV 0x0B1D +#define NIMBUS_SCALE_Y_DIV 0x1482 + +#define NIMBUS_CHUNK_MAX 0x1FF0 /* 8176 — sub_2D640 */ +#define NIMBUS_HDR_LEN 16 /* 2 outer + 10 body hdr + 4 payload sum */ +#define NIMBUS_CAL_DEST 0x00400200u /* 2D7A4 literal 0x400200 */ +#define NIMBUS_FW_HDR_OFF 350 +#define NIMBUS_FW_HDR_LEN 0x200 +#define NIMBUS_ARM_OFFICIAL 0xe970 /* 8740 le32(+0x0c); 204E0 2D640 size */ +#define NIMBUS_ISYS_MAGIC 0x53797349u /* 'IsyS' — sub_564 */ +#define NIMBUS_ISYS_LEN 0x560 +#define NIMBUS_A34_BASE 0x2202fe00UL /* sub_A34(idx) = 0x2202FE00+idx */ +#define NIMBUS_A34_ISYS_DESC (NIMBUS_A34_BASE + 0x18) /* sub_A34(24) */ + +/* Whimory FTL (nand-s5l8740.ko) — optional cal/FW from device NAND */ +#define NIMBUS_FTL_SECTOR_SIZE 4096U +#define NIMBUS_GPFW_TAG 0x67706677u /* 'gpfw' LE */ + +#define NIMBUS_ACK_CHUNK 0x4BC1 /* 19393 */ +#define NIMBUS_ACK_34AD0 0x4AD1 /* 19153 */ +#define NIMBUS_POST_POKE 0x011F /* 287 */ +#define NIMBUS_EXEC_SETTLE_MS 40 /* 273A0 tail: 410522(40) */ + +#define NIMBUS_GPIO_EN 0x0E +#define NIMBUS_GPIO_RST 0x27 +#define NIMBUS_GPIO_IRQ 0x26 + +#define S5L8740_GPIO_PHYS 0x3cf00000UL +#define S5L8740_GPIOCMD_PHYS 0x3cf001e0UL +#define S5L8740_SPI2_PHYS 0x3d200000UL +#define SPI2_CTRL 0x00 +#define SPI2_SETUP 0x04 +#define SPI2_STATUS 0x08 +#define SPI2_PIN 0x0c +#define SPI2_TXDATA 0x10 +#define SPI2_RXDATA 0x20 +#define SPI2_CLKDIV 0x30 +#define SPI2_RXLIMIT 0x34 +#define SPI2_UNK4C 0x4c +#define SPI2_CTRL_FIFO_RST 0x0c +#define SPI2_CTRL_ENABLE 0x01 +#define SPI2_SETUP_11B70 0x403e /* 11B70(2, 0x1A, 0x2EE0, 1) */ +#define SPI2_CS_BIT BIT(1) + +#define NIMBUS_Z2_HDR_LEN 16 +#define NIMBUS_Z2_MAGIC_5A5A 0x5a5a0000u +#define NIMBUS_Z2_MAGIC_C3F5 0xc3f50000u +#define NIMBUS_Z2FW_MAGIC 0x5746325au /* apple_z2 "Z2FW" container */ + +#define NIMBUS_CS_BEGIN BIT(0) +#define NIMBUS_CS_END BIT(1) + +/* + * 0 lets the SPI controller own the engine setup, which is what the stock + * firmware does: one sub_11B70 configuration, divider 2 for a 12 MHz bus. + * This driver used to reprogram the same registers afterwards with a much + * larger divider, quietly running the bus eight times too slow. + */ +static int spi_clkdiv; +module_param(spi_clkdiv, int, 0644); +MODULE_PARM_DESC(spi_clkdiv, + "Override SPI2 CLKDIV (0 = leave it to the SPI controller)"); +/* + * The stock code retries the transfer itself three times before giving + * up; the caller's retry re-runs the whole bring-up. + */ +static unsigned int download_tries = 3; +module_param(download_tries, uint, 0644); +MODULE_PARM_DESC(download_tries, + "Firmware download attempts before re-running bring-up"); + +/* + * The stock path does nothing after a good download but wait 2 ms and + * enable the interrupt. These register pokes keep speaking the boot + * protocol at a part that should already be running its application. + */ +static int reset_hold_ms = 5; +module_param(reset_hold_ms, int, 0644); +MODULE_PARM_DESC(reset_hold_ms, "RST low ms in optional extra_por_pulse (1A5AC uses 5)"); +/* 1A5AC: 2075A(0) then sleep 30 before 20E94 — not FAMILY 15 / old 100. */ +static int reset_release_ms = 30; +module_param(reset_release_ms, int, 0644); +MODULE_PARM_DESC(reset_release_ms, "ms after RST release before probe/FW (1A5AC=30)"); +static int extra_por_pulse; +module_param(extra_por_pulse, int, 0644); +MODULE_PARM_DESC(extra_por_pulse, "1=extra RST low/high/low before 1A5AC (not in RetailOS)"); +static int go_spi_setup; +module_param(go_spi_setup, int, 0644); +MODULE_PARM_DESC(go_spi_setup, "SPI2 SETUP override for 2D54C GO (0=11B70)"); +/* 0=8-bit PIO (RetailOS HBPP default), 1=u16 TXDATA pairs, 2=spi_sync */ +/* + * Read a report whenever ATTN is asserted, without requiring the ping + * to checksum first. Off restores the ping-gated behaviour. + */ +static bool attn_read = true; +module_param(attn_read, bool, 0644); +MODULE_PARM_DESC(attn_read, + "Read a frame when ATTN asserts even if the ping fails"); + +static bool post_poke_strict; +module_param(post_poke_strict, bool, 0644); +MODULE_PARM_DESC(post_poke_strict, + "Require a known 2D5B0 status before EXEC"); + +static bool park_power_down = true; +module_param(park_power_down, bool, 0644); +MODULE_PARM_DESC(park_power_down, + "Cut the rail when parking (0 keeps it up for raw_xfer)"); + +static int go_xfer; +module_param(go_xfer, int, 0644); +MODULE_PARM_DESC(go_xfer, + "2D54C EXEC xfer: 0=8-bit burst 1=u16 burst 2=spi_sync"); +/* Default 0: N31 RetailOS path has no Z2/5A5A host container. */ +static int prepend_z2_hdr; +module_param(prepend_z2_hdr, int, 0644); +MODULE_PARM_DESC(prepend_z2_hdr, "0=none (N31 default) 1=5A5A+BE len+CRC32 2=c3f5 hdr"); +static int chunk_spi; +module_param(chunk_spi, int, 0644); +MODULE_PARM_DESC(chunk_spi, "1=spi_sync chunk xfers (apple_z2-style atomic CS)"); +/* Default quiet: bring-up spam off; set quiet=0 or verbose=1 for detail. */ +static int quiet = 1; +module_param(quiet, int, 0644); +MODULE_PARM_DESC(quiet, "1=minimal logs (default); 0=verbose bring-up (or apple_nimbus.verbose=1)"); + +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Verbose Nimbus logs (overrides quiet; default N)"); +static int skip_download; +module_param(skip_download, int, 0644); +MODULE_PARM_DESC(skip_download, "1=bootload+ping only, no FW chunks"); + +static int force_gid; +module_param(force_gid, int, 0644); +MODULE_PARM_DESC(force_gid, "1=422FFA decrypt attempt even without 8740 rev3 hdr"); + +static int cal_try_dt = 1; +module_param(cal_try_dt, int, 0644); +MODULE_PARM_DESC(cal_try_dt, + "Read U-Boot IsyS copy from /chosen apple,n31-isys-* (default on)"); + +static int cal_try_a34 = 1; +module_param(cal_try_a34, int, 0644); +MODULE_PARM_DESC(cal_try_a34, + "Fallback: read live A34 descriptor at 0x2202FE18 (default on)"); + +static unsigned int exec_wait_ms = 40; +module_param(exec_wait_ms, uint, 0644); +MODULE_PARM_DESC(exec_wait_ms, + "ms after EXEC before runtime ping (OSOS 2D54C success wait = 40)"); + +static unsigned int cal_ftl_start; +module_param(cal_ftl_start, uint, 0644); +MODULE_PARM_DESC(cal_ftl_start, "FTL LBA to start gpfw/8740 firmware scan (not cal)"); + +static unsigned int cal_ftl_count = 4096; +module_param(cal_ftl_count, uint, 0644); +MODULE_PARM_DESC(cal_ftl_count, + "FTL LBAs to scan for gpfw/8740 firmware (not IsyS cal)"); + +static int fw_prefer_ftl; +module_param(fw_prefer_ftl, int, 0644); +MODULE_PARM_DESC(fw_prefer_ftl, + "1=try gpfw/8740 from FTL before grape-nimbus.bin (DFU default 0)"); + +static int fw_allow_file = 1; +module_param(fw_allow_file, int, 0644); +MODULE_PARM_DESC(fw_allow_file, "1=allow apple/grape-nimbus.bin fallback"); + +/* + * 2D640 r1 = firmware offset (start 0). Do NOT default to 0x00100000 — + * that is the EXEC-mapped app window, not the upload dest. Override only + * for deliberate A/B experiments. + */ +static unsigned int fw_dest; +module_param(fw_dest, uint, 0644); +MODULE_PARM_DESC(fw_dest, + "2D640 ARM upload base dest (OSOS offset 0; cal stays 0x400200)"); + +static unsigned int exec_addr = 0x00100018; +module_param(exec_addr, uint, 0644); +MODULE_PARM_DESC(exec_addr, + "2D54C EXEC word0 (OSOS 0x00100018; bootloader-mapped PC)"); +static unsigned int exec_word1 = 0x00000100; +module_param(exec_word1, uint, 0644); +MODULE_PARM_DESC(exec_word1, "2D54C EXEC word1 (OSOS 0x00000100)"); + + +static bool nimbus_verbose; + +#define nimbus_dev_vinfo(dev, fmt, ...) \ + do { \ + if (nimbus_verbose) \ + dev_info((dev), fmt, ##__VA_ARGS__); \ + else \ + dev_dbg((dev), fmt, ##__VA_ARGS__); \ + } while (0) + +#define nimbus_vinfo(n, fmt, ...) \ + do { \ + if (!(n)->parked) \ + nimbus_dev_vinfo(&(n)->spi->dev, fmt, ##__VA_ARGS__); \ + } while (0) + +struct nimbus { + struct spi_device *spi; + struct input_dev *input; + struct gpio_desc *enable; + struct gpio_desc *reset; + struct gpio_desc *attn; + void __iomem *gpio_base; + void __iomem *gpiocmd; + void __iomem *spi2; + struct task_struct *thread; + struct mutex lock; + bool stopped; + bool fw_uploaded; /* 2D640/2D7A4 transport ACKs */ + bool cal_uploaded; + bool requestcal_done; /* 2D5B0 / 1F01 path done */ + bool exec_sent; /* 2D54C SPI xfer completed — not runtime */ + u8 *raw_rx; /* last raw_xfer response */ + unsigned int raw_n; + unsigned int attn_fails; + bool runtime_ready; /* valid 182590 ping checksum */ + bool fw_loaded; /* alias of runtime_ready for older call sites */ + bool fw_tried; + bool spi_ok; + bool use_irq; + bool blob16; /* S5L TXDATA is 8-bit; 16-bit writes fail 4BC1 */ + bool parked; /* give up after recycle budget — stop SPI spam */ + bool have_isys; + bool have_cal; + bool isys_sysfs; + u8 isys[NIMBUS_ISYS_LEN]; + u8 cal_upload[NIMBUS_FW_HDR_LEN]; + int irq; + unsigned int ping_fails; + unsigned int recycle_count; + int spi_fam; /* 0 auto, 1 ROS, 2 Classic */ + unsigned int tx_timeouts; + unsigned int rx_timeouts; +}; + +/* From gpio-d1830.c — OSOS 20766 / 6644(4) / reg16 bit5 */ + +static u16 nimbus_sum16(const u8 *buf, int len) +{ + u16 sum = 0; + + while (len-- > 0) + sum += *buf++; + return sum; +} + +static void nimbus_gpiocmd_mode(struct nimbus *n, unsigned int gpio, u16 mode, int val) +{ + void __iomem *bank; + u32 pin = gpio & 7; + u32 dir; + u8 cmd; + + if (!n->gpiocmd || !n->gpio_base) + return; + + bank = n->gpio_base + 32 * (gpio >> 3); + if (mode == 1) { + /* 43D38C: mode 1 also sets DIR, then GPIOCMD 14/15 */ + cmd = val ? 15 : 14; + dir = readl(bank + 0x14); + writel(dir | BIT(pin), bank + 0x14); + } else if (mode == 0xFFFE) { + dir = readl(bank + 0x14); + writel(dir & ~BIT(pin), bank + 0x14); + cmd = 0; + } else { + cmd = (u8)mode; + dir = readl(bank + 0x14); + writel(dir | BIT(pin), bank + 0x14); + } + writel(((gpio >> 3) << 16) | (pin << 8) | cmd, n->gpiocmd); +} + +/* sub_23CD0(gpio, on) — PUNC bit at bank+0x10 */ +static void nimbus_punc(struct nimbus *n, unsigned int gpio, bool set) +{ + void __iomem *bank; + u32 pin, punc; + + if (!n->gpio_base) + return; + bank = n->gpio_base + 32 * (gpio >> 3); + pin = gpio & 7; + punc = readl(bank + 0x10); + if (set) + punc |= BIT(pin); + else + punc &= ~BIT(pin); + writel(punc, bank + 0x10); +} + +/* sub_20690(a1) — SPI2 pads 0x57–0x5A */ +static void nimbus_spi2_pinmux(struct nimbus *n, bool on) +{ + if (on) { + nimbus_punc(n, 0x57, false); + nimbus_gpiocmd_mode(n, 0x57, 5, 0); + nimbus_gpiocmd_mode(n, 0x58, 3, 0); + nimbus_gpiocmd_mode(n, 0x59, 3, 0); + nimbus_gpiocmd_mode(n, 0x5A, 3, 0); + } else { + nimbus_gpiocmd_mode(n, 0x57, 0xFFFE, 0); + nimbus_punc(n, 0x57, true); + nimbus_gpiocmd_mode(n, 0x58, 1, 0); + nimbus_gpiocmd_mode(n, 0x59, 1, 0); + nimbus_gpiocmd_mode(n, 0x5A, 0xFFFE, 0); + } +} + +/* + * sub_1A878 — disable / retry power-cut: + * 20490(0), RST assert, 20690(0), 20766(0) rail off + EN mode 1. + */ +static void nimbus_power_down(struct nimbus *n) +{ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_IRQ, 0xFFFE, 0); + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 0); + nimbus_spi2_pinmux(n, false); + d1830_nimbus_rail(false); + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_EN, 1, 0); + nimbus_vinfo(n, "1A878 power-cut (RST hold, rail off, EN mode 1)\n"); +} + +/* + * CLKCON oracle replay. + * + * Deliberately not named after any peripheral. Nothing in the extracted + * Nimbus boot path -- sub_13A20, sub_1A5AC and the 2075A/20766/20690/ + * 11B70/20848/20E94/20490 chain -- writes CLKCON or calls sub_41CBD8 at + * all, so there is no evidence naming any of these bits as a touch + * clock. What is established is narrower and still worth replaying: + * Linux is missing global CLKCON state that both the Apple bootloader + * and the running stock system have. + * + * The N31 bootloader sets, at 0x3C500000: + * + * +0x08 = 0x2009200A we inherit this unchanged + * +0x0C = 0x80008000 we have 0x00000000 + * +0x10 = 0x00008000 we have 0x00000000 + * +0x14 = 0x80008000 we have 0x00002200 + * +0x18 = 0x20012001 we inherit this unchanged + * + * so something between DFU, u-boot and Linux clears two of them and + * rewrites a third, while leaving their neighbours alone. + * + * Polarity is inverted -- sub_41CBD8 clears a bit to enable and sets it + * to disable -- so our zeroes mean more clocks running than stock, not + * fewer. And the low nibble of +0x10 is a divider, not a flag: the rate + * decoder reads (MEMORY[0x3C500010] & 0xF) + 1. The stock live-touch + * 0x8000 -> 0x8004 delta is therefore a divider change, not an enable. + * + * clkcon_oracle=1 replay stock's live-touch values + * clkcon_oracle=2 replay the bootloader's post-init values + * + * Values are restored on unload so a failed experiment does not leave + * the clock tree in a state the rest of the kernel did not ask for. + */ +#define N31_CLKCON_PHYS 0x3c500000UL + +static int clkcon_oracle; +module_param(clkcon_oracle, int, 0644); +MODULE_PARM_DESC(clkcon_oracle, + "Replay observed CLKCON state: 1=stock touch, 2=bootloader"); + +static const unsigned int n31_clkcon_off[] = { 0x08, 0x0c, 0x10, 0x14 }; +static const u32 n31_clkcon_touch[] = { + 0xa009200a, 0x80000001, 0x00008004, 0x80002200, +}; +static const u32 n31_clkcon_boot[] = { + 0x2009200a, 0x80008000, 0x00008000, 0x80008000, +}; +static u32 n31_clkcon_saved[ARRAY_SIZE(n31_clkcon_off)]; +static bool n31_clkcon_applied; + +static void nimbus_clkcon_replay(struct nimbus *n) +{ + const u32 *want; + void __iomem *ck; + unsigned int i; + + if (!clkcon_oracle || n31_clkcon_applied) + return; + want = (clkcon_oracle == 2) ? n31_clkcon_boot : n31_clkcon_touch; + + ck = ioremap(N31_CLKCON_PHYS, 0x80); + if (!ck) + return; + for (i = 0; i < ARRAY_SIZE(n31_clkcon_off); i++) { + n31_clkcon_saved[i] = readl(ck + n31_clkcon_off[i]); + writel(want[i], ck + n31_clkcon_off[i]); + dev_info(&n->spi->dev, + "ORACLE_REPLAY clkcon+0x%02x %08x -> %08x\n", + n31_clkcon_off[i], n31_clkcon_saved[i], + readl(ck + n31_clkcon_off[i])); + } + n31_clkcon_applied = true; + iounmap(ck); +} + +static void nimbus_clkcon_restore(void) +{ + void __iomem *ck; + unsigned int i; + + if (!n31_clkcon_applied) + return; + ck = ioremap(N31_CLKCON_PHYS, 0x80); + if (!ck) + return; + for (i = 0; i < ARRAY_SIZE(n31_clkcon_off); i++) + writel(n31_clkcon_saved[i], ck + n31_clkcon_off[i]); + n31_clkcon_applied = false; + iounmap(ck); +} + +/* + * sub_11B70(2, 0x1A, 0x2EE0, 1) after every 20690(1). + * 1A5AC always re-inits SPI2 here. Skipping it after 1A878 remux + * left an extra SCLK edge: 0x1f01/0x4879 came back as 0x0f80/0xa43c + * and shifted one more bit on each retry. + */ +static void nimbus_spi2_11b70(struct nimbus *n) +{ + void (*reinit)(void); + u32 setup; + + if (!n->spi2) + return; + reinit = (void (*)(void))__symbol_get("s5l8702_spi2_reinit"); + if (reinit) { + reinit(); + __symbol_put("s5l8702_spi2_reinit"); + } else { + /* Controller absent: same sequence, same numbers. */ + writel(0xf, n->spi2 + SPI2_STATUS); + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(10, n->spi2 + 0x44); + writel(24, n->spi2 + 0x38); /* 24 * a4=1 */ + writel(255, n->spi2 + 0x40); + writel(144, n->spi2 + 0x3c); /* 3 * 24 * (1+1) */ + writel(2, n->spi2 + SPI2_CLKDIV); /* 24000/12000 */ + writel(SPI2_SETUP_11B70, n->spi2 + SPI2_SETUP); + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(SPI2_CTRL_ENABLE, n->spi2 + SPI2_CTRL); + } + if (spi_clkdiv) + writel(clamp(spi_clkdiv, 1, 255), n->spi2 + SPI2_CLKDIV); + setup = readl(n->spi2 + SPI2_SETUP); + nimbus_vinfo(n, "11B70 SPI2 SETUP=0x%x CLKDIV=%u\n", + setup, readl(n->spi2 + SPI2_CLKDIV)); +} + +static void nimbus_spi2_cs(struct nimbus *n, bool assert) +{ + u32 pin; + + if (!n->spi2) + return; + pin = readl(n->spi2 + SPI2_PIN); + if (assert) + pin &= ~SPI2_CS_BIT; + else + pin |= SPI2_CS_BIT; + writel(pin, n->spi2 + SPI2_PIN); +} + +/* + * Tight 4043D0 PIO for 16-byte app frames. OSOS 11B70 leaves SETUP + * bit5 set so 40F770 takes the DMA path — continuous clocks. Linux + * per-byte spi_sync gaps are fine for the bootloader, not the app. + */ +static void nimbus_spi2_fifo_flush(struct nimbus *n) +{ + if (!n->spi2) + return; + writel(readl(n->spi2 + SPI2_CTRL) | SPI2_CTRL_FIFO_RST, + n->spi2 + SPI2_CTRL); + writel(0xf, n->spi2 + SPI2_STATUS); +} + +/* + * SPI2 status polling. + * + * Both transfer loops below used to do this: + * + * guard = 100000; + * do { st = readl(STATUS); } while (!(st & 0xf800) && --guard); + * rx[i] = readl(RXDATA); + * + * The guard running out was not treated as a failure. RXDATA was read + * either way and the function returned 0, so when the ready bit never set, + * the FIFO's previous contents were handed back as if they were a reply. + * That does not look like noise because it is not noise: it is the same + * stale word every time, which is where the repeating 4f81 came from, and + * it is why the part appeared to answer while telling us nothing. + * + * Two changes. A timeout is now an error that propagates, so a dead bus + * reports as dead instead of inventing a reply. And because this engine has + * been seen reporting readiness in either of two encodings depending on the + * init path that ran, both are polled in one loop until one genuinely + * satisfies; that result is latched, logged once, and used exclusively + * afterwards. Latching matters: alternating between interpretations is how + * a transfer gets declared complete early. + */ +#define NIMBUS_SPI_GUARD 100000u + +#define NIMBUS_TXBUSY_ROS 0x7c0u +#define NIMBUS_TXBUSY_RESIDUE 0x40u /* stays set after 11B70 setup */ +#define NIMBUS_TXLVL_CLASSIC 0x1f0u +#define NIMBUS_RXRDY_ROS 0xf800u +#define NIMBUS_RXLVL_CLASSIC 0x3e00u + +static int nimbus_spi_family; +module_param(nimbus_spi_family, int, 0644); +MODULE_PARM_DESC(nimbus_spi_family, + "SPI2 status encoding: 0=auto-latch, 1=ROS (0x7C0/0xF800), 2=Classic (0x1F0/0x3E00)"); + +static bool nimbus_strict_rx; /* opt-in until proven on glass */ +module_param(nimbus_strict_rx, bool, 0644); +MODULE_PARM_DESC(nimbus_strict_rx, + "1=receive timeout fails the transfer (default); 0=legacy, read the FIFO anyway"); + +static void nimbus_latch_fam(struct nimbus *n, int fam, u32 st) +{ + if (n->spi_fam == fam) + return; + n->spi_fam = fam; + dev_info(&n->spi->dev, + "SPI2 status encoding latched: %s (STATUS=0x%08x)\n", + fam == 1 ? "ROS(0x7C0/0xF800)" : "Classic(0x1F0/0x3E00)", st); +} + +/* Wait for the transmit side to accept another word. */ +static int nimbus_wait_tx(struct nimbus *n) +{ + unsigned int guard = NIMBUS_SPI_GUARD; + u32 st = 0; + + while (guard--) { + st = readl(n->spi2 + SPI2_STATUS); + + if (n->spi_fam != 2) { + u32 b = st & NIMBUS_TXBUSY_ROS; + + if (b == 0 || b == NIMBUS_TXBUSY_RESIDUE) { + nimbus_latch_fam(n, 1, st); + return 0; + } + } + if (n->spi_fam != 1 && (st & NIMBUS_TXLVL_CLASSIC) == 0) { + nimbus_latch_fam(n, 2, st); + return 0; + } + cpu_relax(); + } + + if (!n->tx_timeouts++) + dev_warn(&n->spi->dev, + "SPI2 transmit never went idle (STATUS=0x%08x)\n", st); + return -ETIMEDOUT; +} + +/* Wait for a received word to actually be available. */ +static int nimbus_wait_rx(struct nimbus *n) +{ + unsigned int guard = NIMBUS_SPI_GUARD; + u32 st = 0; + + while (guard--) { + st = readl(n->spi2 + SPI2_STATUS); + + if (n->spi_fam != 2 && (st & NIMBUS_RXRDY_ROS)) { + nimbus_latch_fam(n, 1, st); + return 0; + } + if (n->spi_fam != 1 && (st & NIMBUS_RXLVL_CLASSIC)) { + nimbus_latch_fam(n, 2, st); + return 0; + } + cpu_relax(); + } + + if (!n->rx_timeouts++) + dev_warn(&n->spi->dev, + "SPI2 receive never became ready (STATUS=0x%08x), refusing to report stale FIFO\n", + st); + return -ETIMEDOUT; +} + +/* + * Transfers go through the SPI core. + * + * This driver is bound as an spi_device yet drove the SPI2 registers itself: + * its own CS, its own FIFO reset, its own TXDATA/RXDATA polling. That meant + * none of the usual guarantees applied -- no bus locking against another + * client, no controller-side clock or mode setup, no error propagation -- + * and it duplicated the controller driver's completion logic well enough to + * drift from it. Two copies of a tricky wait loop is one copy too many. + * + * The only thing the register path really offered was holding CS down across + * several calls, which the core expresses with cs_change on the final + * transfer of a message. The controller now honours that, so the whole thing + * is reachable through spi_sync(). + * + * The legacy path is kept behind nimbus_use_spi=0 purely so the two can be + * compared on hardware; it is not the supported route. + */ +static bool nimbus_use_spi; /* opt-in until proven on glass */ +module_param(nimbus_use_spi, bool, 0644); +MODULE_PARM_DESC(nimbus_use_spi, + "1=transfer via the SPI core (default); 0=legacy direct SPI2 register PIO"); + +/* + * One HBPP burst as a single spi_message. + * + * @hold_cs: leave the part selected when the message ends, for a frame that + * spans more than one call. Expressed as cs_change on the last + * transfer, which is exactly what that flag means there. + * + * The controller skips reading RXDATA when rx is NULL, but the part clocks a + * reply out regardless; without draining it an 8 KiB chunk overruns the RX + * FIFO and quietly loses everything after the header. So a TX-only caller + * still gets a throwaway receive buffer. + */ +static int nimbus_spi_burst(struct nimbus *n, const u8 *tx, u8 *rx, + unsigned int len, bool hold_cs) +{ + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + .cs_change = hold_cs, + }; + struct spi_message m; + u8 *drain = NULL; + int ret; + + if (!n->spi) + return -ENODEV; + + if (!rx) { + drain = kzalloc(len, GFP_KERNEL); + if (!drain) + return -ENOMEM; + t.rx_buf = drain; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(n->spi, &m); + kfree(drain); + + if (ret && !n->tx_timeouts++) + dev_warn(&n->spi->dev, "spi_sync failed: %d\n", ret); + return ret; +} + +static int nimbus_burst_ex(struct nimbus *n, const u8 *tx, u8 *rx, + unsigned int len, unsigned int cs_flags) +{ + unsigned int i; + int ret = 0; + + if (nimbus_use_spi) + return nimbus_spi_burst(n, tx, rx, len, + !(cs_flags & NIMBUS_CS_END)); + + if (!n->spi2) + return -ENODEV; + if (cs_flags & NIMBUS_CS_BEGIN) { + nimbus_spi2_cs(n, true); + ndelay(2000); + nimbus_spi2_fifo_flush(n); + writel(readl(n->spi2 + SPI2_SETUP) & ~BIT(0), n->spi2 + SPI2_SETUP); + writel(readl(n->spi2 + SPI2_STATUS) | 0x400000u, n->spi2 + SPI2_STATUS); + } + for (i = 0; i < len; i++) { + writel(1, n->spi2 + SPI2_RXLIMIT); + + ret = nimbus_wait_tx(n); + if (ret) + goto out; + + writel(tx[i], n->spi2 + SPI2_TXDATA); + writel(1, n->spi2 + SPI2_UNK4C); + + ret = nimbus_wait_rx(n); + if (ret && nimbus_strict_rx) + goto out; + ret = 0; + + if (rx) + rx[i] = (u8)readl(n->spi2 + SPI2_RXDATA); + else + readl(n->spi2 + SPI2_RXDATA); + } + +out: + if (cs_flags & NIMBUS_CS_END) { + writel(readl(n->spi2 + SPI2_SETUP) & ~0x400001u, n->spi2 + SPI2_SETUP); + nimbus_spi2_cs(n, false); + } + return ret; +} + +static int nimbus_burst(struct nimbus *n, const u8 *tx, u8 *rx, unsigned int len) +{ + return nimbus_burst_ex(n, tx, rx, len, NIMBUS_CS_BEGIN | NIMBUS_CS_END); +} + +/* + * S5LBox §6.1: every 32-bit HBPP field is middle-endian because the + * part is a 16-bit SPI slave. Our packed buffer is already wire-byte + * order (18 E1 30 01 …); pair as BE u16 so TXDATA 0x18E1 clocks 18 then E1. + * If TXDATA is 8-bit-only, only the low byte leaves and 4BC1 fails — + * send_chunk then falls back to 8-bit PIO. + */ +static int nimbus_burst_u16_ex(struct nimbus *n, const u8 *tx, u8 *rx, + unsigned int len, unsigned int cs_flags) +{ + unsigned int i; + int ret = 0; + + if (!n->spi2) + return -ENODEV; + if (len & 1) + return nimbus_burst_ex(n, tx, rx, len, cs_flags); + + /* + * The controller advertises SPI_BPW_MASK(8) and TXDATA is byte wide, + * so a 16-bit write only ever put the low byte on the wire -- which is + * why this path never got its 4BC1 ack and fell back. Through the core + * the same buffer goes out as two bytes in the same order, which is + * what the part was always receiving from the working path anyway. + */ + if (nimbus_use_spi) + return nimbus_spi_burst(n, tx, rx, len, + !(cs_flags & NIMBUS_CS_END)); + if (cs_flags & NIMBUS_CS_BEGIN) { + nimbus_spi2_cs(n, true); + ndelay(2000); + nimbus_spi2_fifo_flush(n); + writel(readl(n->spi2 + SPI2_SETUP) & ~BIT(0), n->spi2 + SPI2_SETUP); + writel(readl(n->spi2 + SPI2_STATUS) | 0x400000u, n->spi2 + SPI2_STATUS); + } + for (i = 0; i < len; i += 2) { + u16 w = ((u16)tx[i] << 8) | tx[i + 1]; + u16 r; + + writel(1, n->spi2 + SPI2_RXLIMIT); + + ret = nimbus_wait_tx(n); + if (ret) + goto out; + + writel(w, n->spi2 + SPI2_TXDATA); + writel(1, n->spi2 + SPI2_UNK4C); + + ret = nimbus_wait_rx(n); + if (ret && nimbus_strict_rx) + goto out; + ret = 0; + + r = (u16)readl(n->spi2 + SPI2_RXDATA); + if (rx) { + rx[i] = (u8)(r >> 8); + rx[i + 1] = (u8)r; + } + } + +out: + if (cs_flags & NIMBUS_CS_END) { + writel(readl(n->spi2 + SPI2_SETUP) & ~0x400001u, n->spi2 + SPI2_SETUP); + nimbus_spi2_cs(n, false); + } + return ret; +} + +static int nimbus_burst_u16(struct nimbus *n, const u8 *tx, u8 *rx, + unsigned int len) +{ + return nimbus_burst_u16_ex(n, tx, rx, len, + NIMBUS_CS_BEGIN | NIMBUS_CS_END); +} + +static int nimbus_burst16(struct nimbus *n, const u8 *tx, u8 *rx) +{ + return nimbus_burst(n, tx, rx, NIMBUS_FRAME_LEN); +} + +static int nimbus_xfer(struct nimbus *n, const u8 *tx, u8 *rx, unsigned int len) +{ + u8 *drain = NULL; + int ret; + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + .len = len, + }; + struct spi_message m; + + /* + * s5l8702 pio_one skips RXDATA when rx==NULL. 2D640 is TX-only + * in OSOS (40F770 dest 0) but that path still drains the FIFO. + * Without a drain, 8 KiB chunks overflow RX and the payload + * after the 12-byte header is dropped — 4BC1 can still ACK. + */ + if (!rx) { + drain = kzalloc(len, GFP_KERNEL); + if (!drain) + return -ENOMEM; + t.rx_buf = drain; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(n->spi, &m); + kfree(drain); + return ret; +} + +/* sub_2C87E — bootloader opcode whitelist */ +static bool nimbus_opcode_known(u16 w); +static bool nimbus_looks_like_arm(const u8 *p, size_t n); +static void nimbus_bswap32_words(u8 *p, unsigned int len); +static u32 nimbus_sum32(const u8 *p, unsigned int len); + +static bool nimbus_opcode_known(u16 w) +{ + return w == 0x18e1 || w == 0x1aa1 || w == 0x1f01 || w == 0x19c1 || + w == 0x4879 || w == 0x4bc1 || w == 0x4969 || w == 0x4ad1 || + w == 0x4f81; +} + +/* + * Words the bootloader answers with. + * + * This distinction is the useful part, and it is worth stating plainly + * because the log did not: seeing 0x4f81 come back is not a failed read + * and not a bus fault. The part is alive, it is answering, and it is + * answering as the bootloader -- which means the application it was + * asked to start is not running. A checksum failure on the runtime ping + * says only that the reply was not a runtime reply; this says what it + * was instead. + */ +static bool nimbus_status_is_bootloader(u16 w) +{ + return w == 0x4f81 || w == 0x4879 || w == 0x4bc1 || + w == 0x4ad1 || w == 0x4969; +} + +/* sub_26494 — 16↔16 1A A1 + 18 E1 pad; two rev16 words must be known */ +static int nimbus_probe_26494(struct nimbus *n, const char *tag) +{ + u8 tx[NIMBUS_FRAME_LEN]; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + unsigned int i; + u16 w0, w1; + int ret; + + tx[0] = 0x1a; + tx[1] = 0xa1; + for (i = 2; i < NIMBUS_FRAME_LEN; i += 2) { + tx[i] = 0x18; + tx[i + 1] = 0xe1; + } + ret = nimbus_xfer(n, tx, rx, NIMBUS_FRAME_LEN); + w0 = (u16)((rx[0] << 8) | rx[1]); + w1 = (u16)((rx[2] << 8) | rx[3]); + nimbus_vinfo(n, + "26494 %s ret=%d words 0x%04x 0x%04x known=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + tag, ret, w0, w1, nimbus_opcode_known(w0) && nimbus_opcode_known(w1), + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + /* + * The stock probe only rejects the part when the transfer worked and + * came back with words it does not recognise. A failed transfer is not + * a verdict, and treating it as one meant a single soft SPI error + * skipped the firmware download entirely. + */ + if (ret) { + dev_warn(&n->spi->dev, + "26494 %s transfer %d; continuing to download\n", + tag, ret); + return 0; + } + if (!nimbus_opcode_known(w0) || !nimbus_opcode_known(w1)) + return -EIO; + return 0; +} + +/* sub_3D5706 — TX 1A A1, RX 2, byteswap */ +static int nimbus_status_poll(struct nimbus *n, u16 *status) +{ + u8 tx[2] = { 0x1a, 0xa1 }; + u8 rx[2] = { 0 }; + int ret; + + ret = nimbus_xfer(n, tx, rx, 2); + if (ret) + return ret; + if (status) + *status = (u16)((rx[0] << 8) | rx[1]); /* __rev16 of LE word */ + return 0; +} + +static bool nimbus_fw_has_8740_hdr(const u8 *data, size_t size) +{ + return size >= 8 && data[0] == '8' && data[1] == '7' && + data[2] == '4' && data[3] == '0'; +} + +static bool nimbus_fw_has_z2fw_hdr(const u8 *data, size_t size) +{ + u32 magic; + + if (size < 8) + return false; + magic = get_unaligned_le32(data); + return magic == NIMBUS_Z2FW_MAGIC; +} + +/* Classify host grape file: full 8740 container vs ARM-only cut vs Z2FW. */ +static void nimbus_fwfile_classify(struct nimbus *n, const u8 *data, size_t size) +{ + bool h8740 = nimbus_fw_has_8740_hdr(data, size); + bool hz2 = nimbus_fw_has_z2fw_hdr(data, size); + bool arm0 = size >= 4 && nimbus_looks_like_arm(data, size); + bool arm400 = size >= 0x410 && nimbus_looks_like_arm(data + 0x400, 16); + u32 le0c = (h8740 && size >= 0x10) ? get_unaligned_le32(data + 0xc) : 0; + u8 rev = (h8740 && size >= 5) ? data[4] : 0; + + nimbus_vinfo(n, + "FWFILE size=%zu first16=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x has_8740=%d rev=%u le32(+0xc)=0x%x arm@0=%d arm@0x400=%d Z2FW=%d\n", + size, + size > 0 ? data[0] : 0, size > 1 ? data[1] : 0, + size > 2 ? data[2] : 0, size > 3 ? data[3] : 0, + size > 4 ? data[4] : 0, size > 5 ? data[5] : 0, + size > 6 ? data[6] : 0, size > 7 ? data[7] : 0, + size > 8 ? data[8] : 0, size > 9 ? data[9] : 0, + size > 10 ? data[10] : 0, size > 11 ? data[11] : 0, + size > 12 ? data[12] : 0, size > 13 ? data[13] : 0, + size > 14 ? data[14] : 0, size > 15 ? data[15] : 0, + h8740, rev, le0c, arm0, arm400, hz2); + if (!h8740 && arm0) + dev_warn(&n->spi->dev, + "FWFILE is ARM-only cut — grape file +350 is not IsyS cal\n"); +} + +static void __maybe_unused nimbus_log_calcand(struct nimbus *n, const char *name, + const u8 *data, size_t size, unsigned int off) +{ + u8 tmp[NIMBUS_FW_HDR_LEN]; + u32 s; + + if (size < off + NIMBUS_FW_HDR_LEN) { + nimbus_vinfo(n, "CALCAND %s off=%u OOB (file=%zu)\n", + name, off, size); + return; + } + memcpy(tmp, data + off, NIMBUS_FW_HDR_LEN); + nimbus_bswap32_words(tmp, NIMBUS_FW_HDR_LEN); + s = nimbus_sum32(tmp, NIMBUS_FW_HDR_LEN); + nimbus_vinfo(n, + "CALCAND %s off=%u sum32=0x%08x first16=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x (post-bswap)\n", + name, off, s, + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], + tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], + tmp[14], tmp[15]); +} + +static void __maybe_unused nimbus_dump_calcands(struct nimbus *n, const u8 *data, + size_t size) +{ + /* Diagnostic only — does not select a candidate for upload. */ + nimbus_log_calcand(n, "dec350", data, size, 350); + nimbus_log_calcand(n, "hex350", data, size, 0x350); + nimbus_log_calcand(n, "arm_plus_dec350", data, size, 0x400 + 350); + nimbus_log_calcand(n, "arm_plus_hex350", data, size, 0x400 + 0x350); +} + +static u32 nimbus_crc32_payload(const u8 *p, size_t len) +{ + return crc32_le(~0U, p, len) ^ ~0U; +} + +static void nimbus_fw_audit(struct nimbus *n, const u8 *body, size_t body_len, + const char *tag) +{ + u32 crc; + size_t pad; + + if (!body_len) + return; + crc = nimbus_crc32_payload(body, body_len); + pad = body_len & 3u; + nimbus_vinfo(n, + "FW audit %s %zuB arm=%d crc32=0x%08x pad=%zu\n", + tag, body_len, nimbus_looks_like_arm(body, body_len), crc, pad); + if (body_len >= 16) { + u32 m = get_unaligned_le32(body); + u32 ln_le = get_unaligned_le32(body + 4); + u32 ln_be = get_unaligned_be32(body + 4); + u32 c_le = get_unaligned_le32(body + 8); + + if (m == NIMBUS_Z2_MAGIC_5A5A || m == NIMBUS_Z2_MAGIC_C3F5) + nimbus_vinfo(n, + " z2-dl hdr magic=0x%08x len_le=%u len_be=%u crc=0x%08x\n", + m, ln_le, ln_be, c_le); + } +} + +static int nimbus_build_z2_dl_hdr(u8 *hdr, const u8 *payload, size_t len, + u32 magic) +{ + u32 crc = nimbus_crc32_payload(payload, len); + + put_unaligned_le32(magic, hdr); + put_unaligned_be32(len, hdr + 4); + put_unaligned_le32(crc, hdr + 8); + put_unaligned_le32(0, hdr + 12); + return 0; +} + +static u8 *nimbus_maybe_prepend_z2_hdr(struct nimbus *n, const u8 *body, + size_t body_len, size_t *out_len) +{ + u8 *buf; + u32 magic; + + if (!prepend_z2_hdr || body_len < 4) + return NULL; + magic = prepend_z2_hdr == 2 ? NIMBUS_Z2_MAGIC_C3F5 : NIMBUS_Z2_MAGIC_5A5A; + buf = kmalloc(NIMBUS_Z2_HDR_LEN + body_len + 3, GFP_KERNEL); + if (!buf) + return NULL; + nimbus_build_z2_dl_hdr(buf, body, body_len, magic); + memcpy(buf + NIMBUS_Z2_HDR_LEN, body, body_len); + *out_len = NIMBUS_Z2_HDR_LEN + body_len; + if (*out_len & 3) { + memset(buf + *out_len, 0, 4 - (*out_len & 3)); + *out_len = round_up(*out_len, 4); + } + nimbus_vinfo(n, + "prepended Z2 dl hdr magic=0x%08x total=%zu\n", magic, *out_len); + return buf; +} + +static int nimbus_wait_ack(struct nimbus *n, u16 expect, int retries) +{ + int i; + u16 st = 0; + + for (i = 0; i < retries; i++) { + if (nimbus_status_poll(n, &st) == 0 && st == expect) + return 0; + msleep(2); + } + dev_warn(&n->spi->dev, "ACK wait fail (want 0x%04x got 0x%04x)\n", + expect, st); + return -ETIMEDOUT; +} + +/* + * S5LBox §6.5 / iOS AppleMultitouchZ2SPI MemRead: + * 1C 73 + addr middle-endian + sum16(addr bytes) + * then 8-byte ATN 1A A1 18 E1×3; value at rx[2..5] middle-endian. + */ +static int nimbus_rdreg(struct nimbus *n, u32 addr, u32 *val) +{ + u8 tx[8] = { 0x1c, 0x73 }; + u8 rx[8] = { 0 }; + u8 atn_tx[8] = { 0x1a, 0xa1, 0x18, 0xe1, 0x18, 0xe1, 0x18, 0xe1 }; + u8 atn_rx[8] = { 0 }; + u16 csum; + int ret; + + tx[2] = (addr >> 8) & 0xff; + tx[3] = addr & 0xff; + tx[4] = (addr >> 24) & 0xff; + tx[5] = (addr >> 16) & 0xff; + csum = nimbus_sum16(tx + 2, 4); + tx[6] = (csum >> 8) & 0xff; + tx[7] = csum & 0xff; + + ret = nimbus_xfer(n, tx, rx, 8); + if (ret) + return ret; + ret = nimbus_xfer(n, atn_tx, atn_rx, 8); + if (ret) + return ret; + if (val) + *val = ((u32)atn_rx[2] << 8) | atn_rx[3] | + ((((u32)atn_rx[4] << 8) | atn_rx[5]) << 16); + dev_dbg(&n->spi->dev, + "RDREG 0x%08x = 0x%08x atn %02x %02x %02x %02x %02x %02x %02x %02x\n", + addr, val ? *val : 0, atn_rx[0], atn_rx[1], atn_rx[2], + atn_rx[3], atn_rx[4], atn_rx[5], atn_rx[6], atn_rx[7]); + return 0; +} + +/* iOS3 MT_SPI_Z2_WAKE_CMD — 16-byte frame, opcode 0xEE, LE16 csum 0x00EE */ +static int nimbus_hbpp_wake_ee(struct nimbus *n, const char *tag) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0xee }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + int ret; + + tx[14] = 0xee; + ret = nimbus_burst16(n, tx, rx); + nimbus_vinfo(n, + "HBPP 0xEE wake %s ret=%d rx %02x %02x %02x %02x %02x %02x\n", + tag, ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5]); + return ret; +} + +static void nimbus_peek(struct nimbus *n, const char *tag) +{ + static const u32 addrs[] = { + 0x00000000, 0x0000d208, 0x0000e970, 0x00400200, + 0x0040f7f4, 0x0040fffc, 0x1000300c, 0x10008ffc, + }; + unsigned int i; + + if (!nimbus_verbose) + return; + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 v = 0; + + if (nimbus_rdreg(n, addrs[i], &v) == 0) + nimbus_vinfo(n, "peek %s %08x=%08x\n", tag, addrs[i], v); + } +} + +/* HBPP MemRead, dest packing is B1,B0,B3,B2 — same as DATA offset. */ +static int nimbus_rdmem(struct nimbus *n, u32 addr, u8 *buf, unsigned int len) +{ + unsigned int i; + + if (len & 3) + return -EINVAL; + for (i = 0; i < len; i += 4) { + u32 v = 0; + + if (nimbus_rdreg(n, addr + i, &v)) + return -EIO; + put_unaligned_le32(v, buf + i); + } + return 0; +} + +/* + * Prove whether 2D640 landed the ARM image at dest 0 or at the EXEC + * word 0x00100018. Cal dest 0x400200 is a separate window. + */ +static void nimbus_fw_readback(struct nimbus *n, const char *tag) +{ + static const u32 addrs[] = { + 0x00000000, 0x00000018, 0x00100000, 0x00100018, + 0x00400000, 0x00400200, + }; + u8 *buf; + unsigned int i; + + /* + * Six 4 KB reads in the middle of the download sequence. Harmless to + * look at, but not something to put on the bus by default. + */ + if (!verbose) + return; + + buf = kmalloc(0x1000, GFP_KERNEL); + if (!buf) + return; + nimbus_vinfo(n, "NIMBUS FW_READBACK %s:\n", tag); + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 crc100, crc1000; + + memset(buf, 0xa5, 0x1000); + if (nimbus_rdmem(n, addrs[i], buf, 0x1000)) { + dev_warn(&n->spi->dev, + " addr=%08x RDREG fail\n", addrs[i]); + continue; + } + crc100 = nimbus_crc32_payload(buf, 0x100); + crc1000 = nimbus_crc32_payload(buf, 0x1000); + nimbus_vinfo(n, + " addr=%08x first32=%32ph crc100=0x%08x crc1000=0x%08x\n", + addrs[i], buf, crc100, crc1000); + } + kfree(buf); +} + +static void nimbus_cal_readback(struct nimbus *n, const u8 *upload) +{ + u8 *buf; + u32 crc_chip, crc_host; + + if (!verbose) + return; + + buf = kmalloc(NIMBUS_FW_HDR_LEN, GFP_KERNEL); + if (!buf) + return; + if (nimbus_rdmem(n, NIMBUS_CAL_DEST, buf, NIMBUS_FW_HDR_LEN)) { + dev_warn(&n->spi->dev, "cal readback RDREG fail @0x%08x\n", + NIMBUS_CAL_DEST); + kfree(buf); + return; + } + crc_chip = nimbus_crc32_payload(buf, NIMBUS_FW_HDR_LEN); + crc_host = nimbus_crc32_payload(upload, NIMBUS_FW_HDR_LEN); + nimbus_vinfo(n, + "NIMBUS CAL_READBACK @%08x first64=%32ph %32ph crc200=0x%08x host_crc=0x%08x match=%d\n", + NIMBUS_CAL_DEST, buf, buf + 32, crc_chip, crc_host, + crc_chip == crc_host && !memcmp(buf, upload, NIMBUS_FW_HDR_LEN)); + kfree(buf); +} + +/* sub_20848(6593) */ +static int nimbus_bootload_cmd(struct nimbus *n) +{ + u8 tx[NIMBUS_FRAME_LEN]; + u8 rx[NIMBUS_FRAME_LEN]; + unsigned int i; + int ret; + + tx[0] = (NIMBUS_BOOTLOAD_WORD >> 8) & 0xff; + tx[1] = NIMBUS_BOOTLOAD_WORD & 0xff; + for (i = 2; i < NIMBUS_FRAME_LEN; i += 2) { + tx[i] = 0x18; + tx[i + 1] = 0xe1; + } + memset(rx, 0, sizeof(rx)); + ret = nimbus_xfer(n, tx, rx, NIMBUS_FRAME_LEN); + nimbus_vinfo(n, + "bootload 6593 ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + return ret; +} + +/* + * sub_3B9D0 dword swizzle into chunk payload: b0 b1 b2 b3 → b1 b0 b3 b2 + * (distinct from the full u32 byte-reverse used at FW+350). + */ +static void nimbus_grape_swizzle32(u8 *dst, const u8 *src, unsigned int len) +{ + unsigned int i; + + for (i = 0; i + 3 < len; i += 4) { + dst[i] = src[i + 1]; + dst[i + 1] = src[i]; + dst[i + 2] = src[i + 3]; + dst[i + 3] = src[i + 2]; + } +} + +/* 273A0: full u32 reverse of the 512-byte cal window before 2D7A4. */ +static void nimbus_bswap32_words(u8 *p, unsigned int len) +{ + unsigned int i; + + for (i = 0; i + 3 < len; i += 4) { + u8 t0 = p[i], t1 = p[i + 1]; + + p[i] = p[i + 3]; + p[i + 1] = p[i + 2]; + p[i + 2] = t1; + p[i + 3] = t0; + } +} + +static u32 nimbus_sum32(const u8 *p, unsigned int len) +{ + u32 s = 0; + + while (len--) + s += *p++; + return s; +} + +/* + * Family clue (iPhone 4S AppleMultitouchN1SPI): cal is a separate + * multi-touch-calibration property starting "NI" (4e 49 …), not FW+350. + * N31 RetailOS window has been observed as 4e 49 02 01 (vs 4S 4e 49 01 01). + * Host/IsyS order is checked BEFORE the RetailOS u32-reverse into win[]. + */ +static bool nimbus_cal_looks_ni(const u8 *p) +{ + return p && p[0] == 0x4e && p[1] == 0x49; +} + +/* + * OSOS sub_273A0: copy IsyS[350 : 350+0x200], reverse each u32 in that + * copy only (do not mutate the 0x560 object). + */ +static int nimbus_prepare_cal_from_isys(struct nimbus *n, const u8 *isys, + size_t isys_len) +{ + const u8 *raw; + u32 sum; + + if (isys_len != NIMBUS_ISYS_LEN) { + dev_err(&n->spi->dev, "IsyS bad size: got=%zu want=0x%x\n", + isys_len, NIMBUS_ISYS_LEN); + return -EINVAL; + } + + memcpy(n->isys, isys, NIMBUS_ISYS_LEN); + n->have_isys = true; + + raw = n->isys + NIMBUS_FW_HDR_OFF; + memcpy(n->cal_upload, raw, NIMBUS_FW_HDR_LEN); + nimbus_vinfo(n, + "cal +350 raw head %02x %02x %02x %02x%s\n", + raw[0], raw[1], raw[2], raw[3], + nimbus_cal_looks_ni(raw) ? " (NI family — good)" : + " (not NI — suspect vs 4S/IOReg cal)"); + nimbus_bswap32_words(n->cal_upload, NIMBUS_FW_HDR_LEN); + sum = nimbus_sum32(n->cal_upload, NIMBUS_FW_HDR_LEN); + nimbus_vinfo(n, + "Nimbus IsyS cal prepared: off=%u len=0x%x sum32=0x%08x upload_first32=%32ph\n", + NIMBUS_FW_HDR_OFF, NIMBUS_FW_HDR_LEN, sum, n->cal_upload); + if (!sum) { + dev_err(&n->spi->dev, + "IsyS +350 window is all zeros — not a usable cal\n"); + n->have_cal = false; + return -EINVAL; + } + n->have_cal = true; + return 0; +} + +/* + * OSOS sub_564: desc = sub_A34(24) = 0x2202FE18 + * desc[0] == 0x53797349 + * memcpy(0x08A8B510, desc[1], 0x560) + * Linux reads the live descriptor if boot preserved that SRAM. + */ +static int nimbus_load_isys_from_a34(struct nimbus *n) +{ + void __iomem *desc_io; + void __iomem *src_io; + u32 magic; + u32 ptr; + u8 *tmp; + int ret; + + if (!cal_try_a34) + return -ENOENT; + + desc_io = ioremap(NIMBUS_A34_ISYS_DESC, 8); + if (!desc_io) + return -ENOMEM; + + magic = readl(desc_io); + ptr = readl(desc_io + 4); + iounmap(desc_io); + + nimbus_vinfo(n, + "A34 IsyS descriptor: magic=0x%08x ptr=0x%08x\n", + magic, ptr); + + if (magic != NIMBUS_ISYS_MAGIC) { + dev_warn(&n->spi->dev, + "A34 IsyS missing: magic=0x%08x want=0x%08x\n", + magic, NIMBUS_ISYS_MAGIC); + return -ENOENT; + } + if (!ptr) { + dev_warn(&n->spi->dev, "A34 IsyS pointer is NULL\n"); + return -ENOENT; + } + + src_io = ioremap(ptr, NIMBUS_ISYS_LEN); + if (!src_io) + return -ENOMEM; + + tmp = kmalloc(NIMBUS_ISYS_LEN, GFP_KERNEL); + if (!tmp) { + iounmap(src_io); + return -ENOMEM; + } + + memcpy_fromio(tmp, src_io, NIMBUS_ISYS_LEN); + iounmap(src_io); + + nimbus_vinfo(n, + "A34 IsyS read: ptr=0x%08x len=0x%x first32=%32ph calraw_first16=%16ph\n", + ptr, NIMBUS_ISYS_LEN, tmp, tmp + NIMBUS_FW_HDR_OFF); + + ret = nimbus_prepare_cal_from_isys(n, tmp, NIMBUS_ISYS_LEN); + kfree(tmp); + return ret; +} + +/* + * U-Boot copies the 0x560 IsyS object to reserved DRAM and publishes + * apple,n31-isys-addr / apple,n31-isys-size on /chosen. That copy is the + * safe address — never the original A34 pointer. + */ +static int nimbus_load_isys_from_dt(struct nimbus *n) +{ + struct device_node *chosen; + u32 addr; + u32 size; + void *p; + u8 *tmp; + int ret; + + if (!cal_try_dt) + return -ENOENT; + + chosen = of_find_node_by_path("/chosen"); + if (!chosen) + return -ENOENT; + + ret = of_property_read_u32(chosen, "apple,n31-isys-addr", &addr); + if (ret) + goto out; + + ret = of_property_read_u32(chosen, "apple,n31-isys-size", &size); + if (ret) + goto out; + + nimbus_vinfo(n, "DT IsyS: addr=0x%08x size=0x%x\n", addr, size); + + if (!addr || size != NIMBUS_ISYS_LEN) { + ret = -EINVAL; + goto out; + } + + p = memremap(addr, size, MEMREMAP_WB); + if (!p) { + ret = -ENOMEM; + goto out; + } + + tmp = kmemdup(p, size, GFP_KERNEL); + memunmap(p); + if (!tmp) { + ret = -ENOMEM; + goto out; + } + + nimbus_vinfo(n, + "DT IsyS read: addr=0x%08x len=0x%x first32=%32ph calraw_first16=%16ph\n", + addr, size, tmp, tmp + NIMBUS_FW_HDR_OFF); + + ret = nimbus_prepare_cal_from_isys(n, tmp, size); + kfree(tmp); + +out: + of_node_put(chosen); + return ret; +} + +static int nimbus_acquire_isys_cal(struct nimbus *n) +{ + int ret; + + if (n->have_cal) + return 0; + + ret = nimbus_load_isys_from_dt(n); + if (!ret) + return 0; + + nimbus_vinfo(n, "DT IsyS unavailable: %d; trying A34 live\n", + ret); + + ret = nimbus_load_isys_from_a34(n); + if (!ret) + return 0; + + dev_err(&n->spi->dev, + "No IsyS calibration from DT or A34; not registering input\n"); + return ret; +} + +/* Optional fmss FTL export — grape firmware only, not IsyS cal. */ +static bool (*nimbus_ftl_present_fn)(void); +static int (*nimbus_ftl_read_fn)(u64 logical_sector, void *buf); +static bool nimbus_ftl_inited; + +static void nimbus_ftl_init_once(void) +{ + if (nimbus_ftl_inited) + return; + nimbus_ftl_inited = true; + nimbus_ftl_present_fn = symbol_get(nand_ftl_present); + nimbus_ftl_read_fn = symbol_get(nand_ftl_read_sector); +} + +static bool nimbus_ftl_ready(void) +{ + nimbus_ftl_init_once(); + return nimbus_ftl_present_fn && nimbus_ftl_read_fn && + nimbus_ftl_present_fn(); +} + +/* + * Walk FTL for Apple 8740 / gpfw IMG1. Returns kmalloc'd buffer + size. + * Caller kfree() on success. This is the ARM app, not IsyS cal. + */ +static u8 *nimbus_try_gpfw_from_ftl(struct device *dev, size_t *out_len) +{ + u8 *sec, *buf = NULL; + u64 lba, end; + unsigned int off; + size_t need, got; + u32 body_sz; + + if (!fw_prefer_ftl || !nimbus_ftl_ready()) + return NULL; + + sec = kmalloc(NIMBUS_FTL_SECTOR_SIZE, GFP_KERNEL); + if (!sec) + return NULL; + + end = min_t(u64, cal_ftl_start + cal_ftl_count, 256ULL); + for (lba = 0; lba < end; lba++) { + if (nimbus_ftl_read_fn(lba, sec)) + continue; + for (off = 0; off + 0x410 <= NIMBUS_FTL_SECTOR_SIZE; off += 4) { + if (memcmp(sec + off, "8740", 4)) + continue; + body_sz = get_unaligned_le32(sec + off + 0x0c); + if (!body_sz || body_sz > 1024 * 1024) + continue; + need = 0x400 + round_up(body_sz, 16); + buf = kmalloc(need, GFP_KERNEL); + if (!buf) + goto out; + memcpy(buf, sec + off, min_t(size_t, need, + NIMBUS_FTL_SECTOR_SIZE - off)); + got = min_t(size_t, need, NIMBUS_FTL_SECTOR_SIZE - off); + while (got < need && lba + 1 < end) { + lba++; + if (nimbus_ftl_read_fn(lba, sec)) { + kfree(buf); + buf = NULL; + goto out; + } + memcpy(buf + got, sec, + min_t(size_t, need - got, + NIMBUS_FTL_SECTOR_SIZE)); + got += min_t(size_t, need - got, + NIMBUS_FTL_SECTOR_SIZE); + } + if (got >= 0x410) { + nimbus_dev_vinfo(dev, + "gpfw/8740 from FTL lba=%llu off=%u need=%zu got=%zu rev=%u\n", + lba, off, need, got, buf[7]); + *out_len = got; + goto out; + } + kfree(buf); + buf = NULL; + } + } + +out: + kfree(sec); + return buf; +} + +static int nimbus_acquire_fw(struct device *dev, const u8 **data, + size_t *size, const struct firmware **fw_out, + u8 **kbuf_out) +{ + size_t flen = 0; + u8 *ftl; + + *fw_out = NULL; + *kbuf_out = NULL; + if (fw_prefer_ftl) { + ftl = nimbus_try_gpfw_from_ftl(dev, &flen); + if (ftl) { + *data = ftl; + *size = flen; + *kbuf_out = ftl; + return 0; + } + } + if (!fw_allow_file) + return -ENOENT; + if (request_firmware(fw_out, "apple/grape-nimbus.bin", dev) || + !*fw_out) + return -ENOENT; + *data = (*fw_out)->data; + *size = (*fw_out)->size; + + /* + * The image declares its own length, and ours does not match the file. + * + * Layout starts with an ASCII revision string -- "87402.0" followed by + * a revision byte -- and carries a little-endian u32 at +12. On the + * Rockbox port's blob that u32 is 0xE960 = 59744, exactly the file + * size, which is what identifies the field. The blob extracted here is + * 61680 bytes while its own header says 59760, so 1920 bytes of + * whatever follows the image were being uploaded as if they were part + * of it. + * + * That is worth being strict about: the part checksums what it + * receives, so trailing bytes do not produce a diagnostic, they produce + * an image that fails to start while every transfer reports success -- + * which is exactly the state this driver has been stuck in, with the + * bootloader still answering pings after EXEC. + * + * Trust the header when it is sane and smaller than the file. A header + * larger than the file means the file is truncated and the declared + * length is unusable, so keep the file size and say so. + */ + if (*size >= NIMBUS_FW_HDR_LEN) { + u32 declared = get_unaligned_le32(*data + 12); + + if (declared && declared < *size) { + dev_info(dev, + "fw '%c%c%c%c%c%c%c' rev %02x: file %zu, header says %u; uploading %u\n", + (*data)[0], (*data)[1], (*data)[2], (*data)[3], + (*data)[4], (*data)[5], (*data)[6], (*data)[7], + *size, declared, declared); + *size = declared; + } else if (declared > *size) { + dev_warn(dev, + "fw header declares %u but file is only %zu; using the file\n", + declared, *size); + } + } + return 0; +} + +static void nimbus_release_fw(const struct firmware *fw, u8 *kbuf) +{ + /* Exactly one of the decoded copy and the firmware blob is live. */ + if (!kbuf && fw) + release_firmware(fw); + kfree(kbuf); +} + +/* + * 2D7A4 payload → controller @ 0x00400200. + * Cal is the transformed A34 IsyS window only. + */ +static int nimbus_load_cal_window(struct nimbus *n, u8 *win) +{ + int ret; + + ret = nimbus_acquire_isys_cal(n); + if (ret) + return ret; + memcpy(win, n->cal_upload, NIMBUS_FW_HDR_LEN); + return 0; +} + +/* + * sub_2D640 / 2D7A4 + trampoline sub_35C1C → sub_3B9D0: + * frame[0..1] 18 E1 + * body @ +2: + * 30 01 + * word_count hi/lo = (len>>10),(len>>2) + * dest B1 B0 B3 B2 + * u16 byte-sum of previous 6 body bytes (words+dest), BE + * payload u32s swizzled B1 B0 B3 B2 + * u32 byte-sum of swizzled payload, stored B1 B0 B3 B2 + * SPI len = payload_len + 16; max payload 0x1FF0; ACK 0x4BC1 (retry ≤5). + * + * Expected prefixes (exact glass check): + * FW chunk0 dest=0 len=0x1FF0: + * 18 E1 30 01 07 FC 00 00 00 00 01 03 + * CAL chunk0 dest=0x00400200 len=0x200: + * 18 E1 30 01 00 80 02 00 00 40 00 C2 + */ +static unsigned int nimbus_build_upload_frame(u8 *buf, u32 dest, + const u8 *src, unsigned int len) +{ + u16 hdr_sum; + u32 payload_sum; + + buf[0] = 0x18; + buf[1] = 0xe1; + buf[2] = 0x30; + buf[3] = 0x01; + /* word_count = len/4 as big-endian u16 via (len>>10),(len>>2) */ + buf[4] = (len >> 10) & 0xff; + buf[5] = (len >> 2) & 0xff; + /* dest swizzle B1 B0 B3 B2 */ + buf[6] = (dest >> 8) & 0xff; + buf[7] = dest & 0xff; + buf[8] = (dest >> 24) & 0xff; + buf[9] = (dest >> 16) & 0xff; + hdr_sum = nimbus_sum16(buf + 4, 6); + buf[10] = (hdr_sum >> 8) & 0xff; + buf[11] = hdr_sum & 0xff; + + nimbus_grape_swizzle32(buf + 12, src, len); + + payload_sum = nimbus_sum32(buf + 12, len); + buf[12 + len] = (payload_sum >> 8) & 0xff; + buf[12 + len + 1] = payload_sum & 0xff; + buf[12 + len + 2] = (payload_sum >> 24) & 0xff; + buf[12 + len + 3] = (payload_sum >> 16) & 0xff; + + return len + NIMBUS_HDR_LEN; +} + +/* Glass/oracle prefixes from RetailOS 2D640 / 2D7A4 — fail loud if wrong. */ +static void nimbus_check_upload_prefix(struct nimbus *n, u32 dest, + unsigned int len, const u8 *tx) +{ + static const u8 fw0[12] = { + 0x18, 0xe1, 0x30, 0x01, 0x07, 0xfc, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03 + }; + static const u8 cal0[12] = { + 0x18, 0xe1, 0x30, 0x01, 0x00, 0x80, 0x02, 0x00, + 0x00, 0x40, 0x00, 0xc2 + }; + + if (dest == 0 && len == NIMBUS_CHUNK_MAX && memcmp(tx, fw0, 12)) { + dev_err(&n->spi->dev, + "FW_UPLOAD prefix MISMATCH want 18 e1 30 01 07 fc 00 00 00 00 01 03 got %12ph\n", + tx); + } + if (dest == NIMBUS_CAL_DEST && len == NIMBUS_FW_HDR_LEN && + memcmp(tx, cal0, 12)) { + dev_err(&n->spi->dev, + "CAL_UPLOAD prefix MISMATCH want 18 e1 30 01 00 80 02 00 00 40 00 c2 got %12ph\n", + tx); + } +} + +static void nimbus_log_upload_prefix(struct nimbus *n, const char *tag, + unsigned int chunk_idx, u32 dest, + unsigned int len, const u8 *tx, + unsigned int xfer_len, u16 ack, int ack_ret) +{ + nimbus_vinfo(n, + "NIMBUS %s chunk=%u dest=%08x len=%04x xfer=%u ACK=0x%04x ret=%d\n", + tag, chunk_idx, dest, len, xfer_len, ack, ack_ret); + nimbus_vinfo(n, + " tx[0:16] = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], tx[6], tx[7], + tx[8], tx[9], tx[10], tx[11], + xfer_len > 12 ? tx[12] : 0, xfer_len > 13 ? tx[13] : 0, + xfer_len > 14 ? tx[14] : 0, xfer_len > 15 ? tx[15] : 0); +} + +static void nimbus_dump_hbpp_tx(struct nimbus *n, const char *tag, + const u8 *raw, unsigned int chunk_idx, + unsigned int dest, unsigned int chunk_len, + const u8 *tx, unsigned int xfer_len, u16 ack, + int ack_ret) +{ + unsigned int last_off; + + nimbus_log_upload_prefix(n, tag, chunk_idx, dest, chunk_len, + tx, xfer_len, ack, ack_ret); + if (raw && chunk_len >= 64) + nimbus_vinfo(n, " raw first64=%32ph %32ph\n", + raw, raw + 32); + else if (raw) + nimbus_vinfo(n, " raw first%u=%*ph\n", + chunk_len, chunk_len, raw); + if (xfer_len >= 96) + nimbus_vinfo(n, + " tx first96=%32ph %32ph %32ph\n", + tx, tx + 32, tx + 64); + else if (xfer_len > 16) + nimbus_vinfo(n, " tx first%u=%*ph\n", + xfer_len, xfer_len, tx); + if (xfer_len >= 32) { + last_off = xfer_len - 32; + nimbus_vinfo(n, " tx last32=%32ph\n", tx + last_off); + } +} + +static int nimbus_send_chunk_ex(struct nimbus *n, const u8 *data, + unsigned int dest, unsigned int len, + unsigned int cs_flags, bool dump, + const char *tag, unsigned int chunk_idx, + unsigned int file_off) +{ + u8 *buf; + unsigned int xfer_len; + int ret = -EIO, try, ack_ret = -ETIMEDOUT; + u16 ack = 0; + + if (!len || len > NIMBUS_CHUNK_MAX || (len & 3)) + return -EINVAL; + + xfer_len = len + NIMBUS_HDR_LEN; + buf = kzalloc(xfer_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (nimbus_build_upload_frame(buf, dest, data, len) != xfer_len) { + kfree(buf); + return -EINVAL; + } + nimbus_check_upload_prefix(n, dest, len, buf); + + /* Z2 SEND_BLOB: spi_sync keeps CS down for whole HBPP frame. */ + for (try = 0; try < 5; try++) { + if (chunk_spi) + ret = nimbus_xfer(n, buf, NULL, xfer_len); + else if (n->blob16) + ret = nimbus_burst_u16_ex(n, buf, NULL, + xfer_len, cs_flags); + else + ret = nimbus_burst_ex(n, buf, NULL, xfer_len, cs_flags); + if (ret) + continue; + /* 1A A1 → 2 bytes → rev16; expect 0x4BC1 */ + ack_ret = nimbus_wait_ack(n, NIMBUS_ACK_CHUNK, 8); + if (ack_ret == 0) { + ack = NIMBUS_ACK_CHUNK; + if (dump) + nimbus_dump_hbpp_tx(n, tag, data, chunk_idx, + dest, len, buf, xfer_len, + ack, ack_ret); + else if (!chunk_idx) + nimbus_log_upload_prefix(n, tag, chunk_idx, + dest, len, buf, + xfer_len, ack, + ack_ret); + kfree(buf); + return 0; + } + if (n->blob16 && try == 0) { + n->blob16 = false; + nimbus_vinfo(n, + "16-bit DATA no 4BC1 — falling back to 8-bit PIO\n"); + } + } + if (dump || !chunk_idx) + nimbus_dump_hbpp_tx(n, tag, data, chunk_idx, dest, len, buf, + xfer_len, ack, ack_ret); + kfree(buf); + return -EIO; +} + +static int nimbus_send_chunk(struct nimbus *n, const u8 *data, + unsigned int dest, unsigned int len, bool dump, + unsigned int file_off, const char *tag, + unsigned int chunk_idx) +{ + return nimbus_send_chunk_ex(n, data, dest, len, + NIMBUS_CS_BEGIN | NIMBUS_CS_END, dump, + tag, chunk_idx, file_off); +} + +static int nimbus_send_blob(struct nimbus *n, const u8 *data, unsigned int len, + unsigned int dest_off) +{ + unsigned int off = 0; + unsigned int chunk_idx = 0; + u8 pad[4]; + const char *tag; + bool is_cal = (dest_off == NIMBUS_CAL_DEST); + + tag = is_cal ? "CAL_UPLOAD" : "FW_UPLOAD"; + + while (off < len) { + unsigned int chunk = min_t(unsigned int, len - off, NIMBUS_CHUNK_MAX); + int ret; + bool last, dump; + + /* RetailOS always transfers whole words */ + if (chunk & 3) + chunk &= ~3u; + if (!chunk) { + memset(pad, 0, sizeof(pad)); + memcpy(pad, data + off, len - off); + return nimbus_send_chunk(n, pad, dest_off + off, 4, + true, off, tag, chunk_idx); + } + last = (off + chunk >= len); + /* Always dump first + last FW chunk and the sole cal chunk. */ + dump = (off == 0) || last || is_cal; + ret = nimbus_send_chunk(n, data + off, dest_off + off, chunk, + dump, off, tag, chunk_idx); + if (ret) + return ret; + off += chunk; + chunk_idx++; + } + return 0; +} + +/* sub_34AD0(a1,a2,a3) — TX 1E 33 + 12-byte pack + sum16, expect ACK 0x4AD1 */ +static int nimbus_cmd_34ad0(struct nimbus *n, u32 a1, u32 a2, u32 a3) +{ + u8 tx[16]; + u8 rx[16]; + u8 body[12]; + u16 csum; + int ret; + + tx[0] = 0x1e; + tx[1] = 0x33; + + /* Packing from Hex-Rays sub_34AD0 */ + body[0] = (a1 >> 8) & 0xff; + body[1] = a1 & 0xff; + body[2] = (a1 >> 24) & 0xff; + body[3] = (a1 >> 16) & 0xff; + body[4] = (a3 >> 8) & 0xff; + body[5] = a3 & 0xff; + body[6] = (a3 >> 24) & 0xff; + body[7] = (a3 >> 16) & 0xff; + body[8] = (a2 >> 8) & 0xff; + body[9] = a2 & 0xff; + body[10] = (a2 >> 24) & 0xff; + body[11] = (a2 >> 16) & 0xff; + csum = nimbus_sum16(body, 12); + memcpy(tx + 2, body, 12); + tx[14] = (csum >> 8) & 0xff; + tx[15] = csum & 0xff; + + /* 34AD0: 40F770 16↔16 then 3D5706 == 0x4AD1 */ + ret = nimbus_xfer(n, tx, rx, 16); + if (ret) + return ret; + return nimbus_wait_ack(n, NIMBUS_ACK_34AD0, 8); +} + +/* sub_2D5B0 post-download */ +static int nimbus_post_download(struct nimbus *n) +{ + u8 tx[2], rx[2]; + u16 st = 0; + int ret, i; + + static const struct { + u32 a1, a2, a3; + } pokes[] = { + /* 2D5B0: ldr 0x1000300C, then +0x50 / +0x4C / -0x0C */ + { 0x1000300c, 5859, (u32)-1 }, + { 0x1000305c, 32, (u32)-1 }, + { 0x10003058, 6, (u32)-1 }, + { 0x10003000, 3, (u32)-1 }, + }; + + for (i = 0; i < ARRAY_SIZE(pokes); i++) { + u32 rb = 0; + + ret = nimbus_cmd_34ad0(n, pokes[i].a1, pokes[i].a2, pokes[i].a3); + nimbus_vinfo(n, "34AD0[%d] %d\n", i, ret); + if (ret) + return ret; + /* + * 4AD1 acknowledges the write without echoing it, so the only + * way to see the value is a separate RDREG. Stock sub_2D5B0 + * does not do this, and it inserts a transfer between pokes + * that the part was not told to expect. Verbose runs only. + */ + if (verbose && nimbus_rdreg(n, pokes[i].a1, &rb) == 0) + nimbus_vinfo(n, + "34AD0[%d] RDREG 0x%08x -> 0x%08x (wrote %u)\n", + i, pokes[i].a1, rb, pokes[i].a2); + } + + /* + * Stock gates everything after this point on the reply: + * + * if (!sub_40F770(&v8, 2, &v10, 2)) { sub_410522(65); + * if (!sub_3D5706(&v9, ...)) return 1; } + * return 0; + * + * We were throwing both answers away. nimbus_xfer returning 0 + * only says the SPI transfer completed, and nimbus_status_poll + * below returns 0 for any status at all, so post_download always + * reported success and we went on to EXEC no matter what the part + * said. This is the last handshake before the application is + * supposed to start, and its reply has never been looked at. + */ + put_unaligned_le16(NIMBUS_POST_POKE, tx); + ret = nimbus_xfer(n, tx, rx, 2); + if (ret) + return ret; + st = (u16)((rx[0] << 8) | rx[1]); + dev_info(&n->spi->dev, + "011F reply %02x %02x (0x%04x)%s\n", + rx[0], rx[1], st, + nimbus_opcode_known(st) ? " known" : " UNKNOWN"); + st = 0; + msleep(65); + /* 2D5B0: 3D5706 success only — does not require 0x4BC1 */ + if (nimbus_status_poll(n, &st) != 0) + return -EIO; + + dev_info(&n->spi->dev, "post-poke 1AA1 status 0x%04x%s\n", + st, nimbus_opcode_known(st) ? " known" : " UNKNOWN"); + + /* + * Refuse to EXEC on an unrecognised status when post_poke_strict + * is set. Off by default so a run still reaches EXEC and we can + * see both halves before deciding which status stock treats as a + * pass. + */ + if (post_poke_strict && !nimbus_opcode_known(st)) { + dev_warn(&n->spi->dev, + "post-poke status 0x%04x rejected; not running EXEC\n", + st); + return -EIO; + } + n->requestcal_done = true; + return 0; +} + +/* sub_2D54C — 12↔12: 1D 53 + two LE u32 + sum16 */ +/* + * Neither implementation reconfigures SPI2 for EXEC -- go_spi_setup + * defaults to 0 here, and stock's sub_2D54C goes through sub_40F770 + * like every other command, same channel-2 bracket and all. But the + * part stops driving MISO from EXEC onwards, so the question is + * whether the controller's own state moves underneath us. Measure it + * either side of the transfer rather than reasoning about it. + */ +static void nimbus_spi2_dump(struct nimbus *n, const char *when) +{ + if (!n->spi2) + return; + dev_info(&n->spi->dev, + "SPI2 %-6s ctrl=%08x setup=%08x status=%08x pin=%08x clkdiv=%08x\n", + when, + readl(n->spi2 + SPI2_CTRL), + readl(n->spi2 + SPI2_SETUP), + readl(n->spi2 + SPI2_STATUS), + readl(n->spi2 + SPI2_PIN), + readl(n->spi2 + SPI2_CLKDIV)); +} + +static int nimbus_cmd_2d54c_raw(struct nimbus *n, u32 word0, u32 word1) +{ + u8 tx[12] = { 0x1d, 0x53 }; + u8 rx[12] = { 0 }; + u16 csum; + u32 saved_setup = 0; + int ret; + + put_unaligned_le32(word0, tx + 2); + put_unaligned_le32(word1, tx + 6); + csum = nimbus_sum16(tx + 2, 8); + tx[10] = (csum >> 8) & 0xff; + tx[11] = csum & 0xff; + if (n->spi2) { + nimbus_spi2_fifo_flush(n); + if (go_spi_setup > 0) { + saved_setup = readl(n->spi2 + SPI2_SETUP); + writel((u32)go_spi_setup, n->spi2 + SPI2_SETUP); + nimbus_vinfo(n, "2D54C GO SETUP 0x%x (was 0x%x)\n", + go_spi_setup, saved_setup); + } + } + nimbus_spi2_dump(n, "pre"); + if (go_xfer == 2) + ret = nimbus_xfer(n, tx, rx, 12); + else if (go_xfer == 1) + ret = nimbus_burst_u16(n, tx, rx, 12); + else + ret = nimbus_burst(n, tx, rx, 12); + nimbus_spi2_dump(n, "post"); + if (saved_setup) + writel(saved_setup, n->spi2 + SPI2_SETUP); + msleep(NIMBUS_EXEC_SETTLE_MS); + nimbus_spi2_dump(n, "settle"); + nimbus_vinfo(n, + "2D54C %08x %08x ret=%d xfer=%d rx %02x %02x %02x %02x %02x %02x\n", + word0, word1, ret, go_xfer, rx[0], rx[1], rx[2], rx[3], + rx[4], rx[5]); + return ret; +} + +static void __maybe_unused nimbus_drain(struct nimbus *n, unsigned int bytes) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0 }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + unsigned int nxf = bytes < NIMBUS_FRAME_LEN ? bytes : NIMBUS_FRAME_LEN; + + /* Optional post-fail diagnostics only — never call on EXEC path. */ + nimbus_burst(n, tx, rx, nxf); +} + +static void nimbus_pre_exec_verify(struct nimbus *n) +{ + static const u32 addrs[] = { + 0x00000000, 0x00000004, 0x00000008, 0x00000020, + 0x00400200, 0x00400204, 0x004003fc, + }; + unsigned int i; + + if (!verbose) + return; + + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + u32 v = 0; + + if (nimbus_rdreg(n, addrs[i], &v) == 0) + nimbus_vinfo(n, "pre-EXEC RDREG 0x%08x=0x%08x\n", + addrs[i], v); + } +} + +/* + * sub_2D54C — one-shot EXEC packet only. + * Do NOT poll 1A A1 / drain after EXEC: that keeps speaking HBPP across the + * bootloader→runtime boundary. Success is proven only by 182590 ping csum. + */ +static int nimbus_cmd_2d54c(struct nimbus *n) +{ + int ret; + + nimbus_pre_exec_verify(n); + ret = nimbus_cmd_2d54c_raw(n, exec_addr, exec_word1); + if (!ret) { + n->exec_sent = true; + /* Stock waits 40 ms here before touching the part again. */ + msleep(NIMBUS_EXEC_SETTLE_MS); + } + return ret; +} + +/* + * 273A0: 2D640(204E0 ARM, hdr+0x0c) → 2D7A4(BSS+350 @ 0x400200) → 2D5B0. + */ +static int nimbus_probe_z2_eb(struct nimbus *n) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0 }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + int ret; + + tx[0] = 0xeb; + tx[1] = 0x01; + put_unaligned_le16(0xeb + 1, tx + 14); + ret = nimbus_burst16(n, tx, rx); + nimbus_vinfo(n, + "z2-EB ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + return (ret == 0 && rx[0] == 0xe1) ? 0 : -EIO; +} + +static int nimbus_probe_ea16(struct nimbus *n) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0 }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + u16 csum; + int ret; + + tx[0] = NIMBUS_MAGIC; + tx[1] = 0x01; + tx[2] = 0x01; + csum = nimbus_sum16(tx, 14); + put_unaligned_le16(csum, tx + 14); + ret = nimbus_burst16(n, tx, rx); + nimbus_vinfo(n, + "EA16 ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]); + return (ret == 0 && rx[0] == NIMBUS_MAGIC) ? 0 : -EIO; +} + +static int nimbus_probe_ping16(struct nimbus *n) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0 }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + u16 csum; + int ret; + struct spi_transfer t = { + .tx_buf = tx, + .rx_buf = rx, + .len = NIMBUS_FRAME_LEN, + .bits_per_word = 16, + }; + struct spi_message m; + + put_unaligned_le32(NIMBUS_PING_TYPE, tx); + csum = nimbus_sum16(tx, 14); + put_unaligned_le16(csum, tx + 14); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(n->spi, &m); + nimbus_vinfo(n, + "ping16 ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x csum=%d\n", + ret, rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7], + nimbus_sum16(rx, 14) == get_unaligned_le16(rx + 14)); + return ret; +} + +#define S5L8740_AES_PHYS 0x38c00000UL + +/* + * Touch FW GID decrypt (OSOS sub_422FFA / sub_204E0): + * MMIO @ 0x38C00000 AES, keysel=1 (GID), CBC IV=0, CFG=0xE|enc. + * 26CCC verifies with 16-byte encrypt; 204E0 decrypts full ARM @ +0x400 + * for 8740 rev 3 only. grape-nimbus.bin on DFU is usually pre-decrypted-cut. + * force_gid=1 tries 422FFA even when loading plaintext blob (bring-up). + */ +static int nimbus_422ffa_mmio(struct device *dev, u8 *buf, unsigned int len, + bool encrypt) +{ + struct device *aes_dev; + void __iomem *aes; + struct clk *clk = NULL; + dma_addr_t phys; + u32 irq = 0; + int ret; + + if (!len || (len & 15)) + return -EINVAL; + + aes_dev = bus_find_device_by_name(&platform_bus_type, NULL, + "38c00000.aes"); + if (!aes_dev) + aes_dev = dev; + + clk = clk_get(aes_dev, "aes"); + if (IS_ERR(clk)) + clk = NULL; + if (clk) { + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + if (aes_dev != dev) + put_device(aes_dev); + return ret; + } + } + + aes = ioremap(S5L8740_AES_PHYS, 0x100); + if (!aes) { + ret = -ENOMEM; + goto out_clk; + } + + phys = dma_map_single(aes_dev, buf, len, DMA_BIDIRECTIONAL); + if (dma_mapping_error(aes_dev, phys)) { + ret = -ENOMEM; + goto out_io; + } + + writel(1, aes + 0x08); + { + unsigned int guard = 100000; + + while (readl(aes + 0x08) && --guard) + ; + } + writel(1, aes + 0x70); + writel(1, aes + 0x6c); + writel(~1u, aes + 0x88); + writel(1, aes + 0x00); + writel((encrypt ? 1u : 0u) | 0xeu, aes + 0x14); + writel(len, aes + 0x18); + writel(phys, aes + 0x28); + writel(len, aes + 0x2c); + writel(phys, aes + 0x20); + writel(len, aes + 0x24); + writel(phys, aes + 0x30); + writel(len, aes + 0x34); + writel(0, aes + 0x74); + writel(0, aes + 0x78); + writel(0, aes + 0x7c); + writel(0, aes + 0x80); + writel(7, aes + 0x0c); + writel(1, aes + 0x04); + ret = readl_poll_timeout(aes + 0x0c, irq, irq & 1, 2, 500000); + writel(0, aes + 0x00); + dma_unmap_single(aes_dev, phys, len, DMA_BIDIRECTIONAL); + if (ret) + dev_err(dev, "422FFA MMIO timeout IRQ=0x%x\n", irq); + +out_io: + iounmap(aes); +out_clk: + if (clk) { + clk_disable_unprepare(clk); + clk_put(clk); + } + if (aes_dev != dev) + put_device(aes_dev); + return ret; +} + +static int nimbus_gid_crypt(struct device *dev, u8 *buf, unsigned int len, + bool encrypt) +{ + struct crypto_skcipher *tfm; + struct skcipher_request *req; + struct scatterlist sg; + u8 key[AES_KEYSIZE_128] = { 0 }; + u8 iv[AES_BLOCK_SIZE] = { 0 }; + int ret; + + if (!len || (len & 15)) + return -EINVAL; + + tfm = crypto_alloc_skcipher("cbc(aes-gid)", 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + ret = crypto_skcipher_setkey(tfm, key, sizeof(key)); + if (ret) + goto out_tfm; + req = skcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out_tfm; + } + sg_init_one(&sg, buf, len); + skcipher_request_set_callback(req, 0, NULL, NULL); + skcipher_request_set_crypt(req, &sg, &sg, len, iv); + ret = encrypt ? crypto_skcipher_encrypt(req) : crypto_skcipher_decrypt(req); + skcipher_request_free(req); +out_tfm: + crypto_free_skcipher(tfm); + if (ret) + dev_warn(dev, "cbc(aes-gid) %s %d\n", + encrypt ? "enc" : "dec", ret); + return ret; +} + +static bool nimbus_looks_like_arm(const u8 *p, size_t n) +{ + return n >= 4 && p[0] == 0x18 && p[1] == 0xf0 && + p[2] == 0x9f && p[3] == 0xe5; +} + +/* + * v6 RE (2026-08-25): post-GID plaintext may already be a preconstructed + * HBPP DATA object (18 E1 30 01 …) — Corellium GEN_1 sends Constructed + * Firmware unchanged. Detect that and SPI-send as-is (ACK 0x4BC1). + */ +static bool nimbus_looks_like_hbpp_data(const u8 *p, size_t n) +{ + if (n >= 4 && p[0] == 0x18 && p[1] == 0xe1 && + p[2] == 0x30 && p[3] == 0x01) + return true; + if (n >= 2 && p[0] == 0x30 && p[1] == 0x01) + return true; + return false; +} + +/** + * nimbus_send_preconstructed_hbpp - SPI the whole HBPP frame, expect 0x4BC1 + * (DATA ACK). Do not re-wrap or swizzle — bytes are already HBPP. + */ +static int nimbus_send_preconstructed_hbpp(struct nimbus *n, const u8 *data, + size_t len) +{ + int try, ret; + + if (len < 16) + return -EINVAL; + + for (try = 0; try < 5; try++) { + if (chunk_spi) + ret = nimbus_xfer(n, data, NULL, len); + else if (n->blob16) + ret = nimbus_burst_u16_ex(n, data, NULL, len, 0); + else + ret = nimbus_burst_ex(n, data, NULL, len, 0); + if (ret) + continue; + if (nimbus_wait_ack(n, NIMBUS_ACK_CHUNK, 8) == 0) { + nimbus_vinfo(n, + "preconstructed HBPP %zuB ACK 0x4BC1 try=%d\n", + len, try); + return 0; + } + if (n->blob16 && try == 0) { + n->blob16 = false; + nimbus_vinfo(n, + "preconstructed HBPP: fall back to 8-bit\n"); + } + } + return -EIO; +} + +/* + * 204E0 sends le32(8740+0x0c)=0xe970. The decrypted cut is 0xecf0 and the + * extra 896 bytes are 0x53/0x43 fill. Downloading that fill to dest 0xe970 + * stomps SRAM just past the official image (likely BSS / bootloader workspace). + */ +static size_t nimbus_official_arm_len(const u8 *body, size_t len) +{ + size_t i; + + if (len <= NIMBUS_ARM_OFFICIAL || !nimbus_looks_like_arm(body, len)) + return len; + for (i = NIMBUS_ARM_OFFICIAL; i < len; i++) { + if (body[i] != 0x53 && body[i] != 0x43) + return len; + } + return NIMBUS_ARM_OFFICIAL; +} + +static int nimbus_download_fw(struct nimbus *n, const u8 *data, size_t size, + bool arm_at_zero) +{ + u8 *dec = NULL; + int ret; + const u8 *body = data; + size_t body_len = size; + bool apple_hdr = nimbus_fw_has_8740_hdr(data, size); + + (void)arm_at_zero; + nimbus_fwfile_classify(n, data, size); + + /* + * 1A640 NOR 8740 → 204E0. ARM at +0x400, size le32(+0x0c). + * Rev 3: 422FFA GID-CBC IV=0 decrypt in place (NOR gpfw). + * Rev 4: no decrypt; 204E0 then returns 33 so NOR drops it. + */ + if (apple_hdr) { + u32 hdr_sz; + u8 rev; + + if (size < 0x400 + 4) { + dev_warn(&n->spi->dev, + "FW too small (%zu) — skip download\n", size); + return -EINVAL; + } + hdr_sz = get_unaligned_le32(data + 0x0c); + /* Short NOR slice: take the ARM bytes we have. Never expand. */ + if (!hdr_sz || hdr_sz > size - 0x400) + hdr_sz = size - 0x400; + /* 204E0: size rounded up to 16 for 422FFA; keep ≤ available. */ + hdr_sz = round_up(hdr_sz, 16); + if (hdr_sz > size - 0x400) + hdr_sz = (size - 0x400) & ~15u; + body = data + 0x400; + body_len = hdr_sz; + rev = data[7]; + /* + * Disk path (1A640) sends the raw file, no 204E0. + * NOR 204E0 rev≠3 returns 33 after writing outputs and + * the NOR path frees the buffer. Rev 4 plaintext is the + * disk-shaped image — send the whole 8740+ARM at dest 0. + */ + if (rev != 3) { + nimbus_vinfo(n, + "204E0 ARM-at-0 %zuB dest 0 (rev=%u hdr+0x0c=0x%x file=%zu)\n", + body_len, rev, get_unaligned_le32(data + 0x0c), + size); + if (force_gid && size >= 0x400 + 16) { + u8 *try = kmemdup(data + 0x400, min_t(size_t, body_len, size - 0x400), + GFP_KERNEL); + if (try && nimbus_422ffa_mmio(&n->spi->dev, try, + round_up(min_t(size_t, body_len, size - 0x400) & ~15u, 16), + false) == 0 && + nimbus_looks_like_arm(try, min_t(size_t, 16, body_len))) { + nimbus_vinfo(n, "force_gid 422FFA ARM ok\n"); + body = try; + body_len = min_t(size_t, body_len, size - 0x400); + dec = try; + } else { + kfree(try); + } + } + goto send; + } + nimbus_vinfo(n, + "204E0 ARM-at-0 %zu bytes rev=%u (hdr+0x0c=0x%x file=%zu)\n", + body_len, rev, get_unaligned_le32(data + 0x0c), size); + + if (rev == 3) { + u8 *probe; + bool verified = false; + + probe = kmemdup(data, 16, GFP_KERNEL); + if (!probe) + return -ENOMEM; + if (nimbus_gid_crypt(&n->spi->dev, probe, 16, true) == 0 && + !memcmp(probe, data + 0x40, 16)) { + verified = true; + nimbus_vinfo(n, "26CCC GID verify OK\n"); + } else { + memcpy(probe, data, 16); + if (nimbus_422ffa_mmio(&n->spi->dev, probe, 16, + true) == 0 && + !memcmp(probe, data + 0x40, 16)) { + verified = true; + nimbus_vinfo(n, + "26CCC 422FFA verify OK\n"); + } else { + dev_warn(&n->spi->dev, + "26CCC GID verify fail (sig %02x%02x%02x%02x got %02x%02x%02x%02x)\n", + data[0x40], data[0x41], + data[0x42], data[0x43], + probe[0], probe[1], + probe[2], probe[3]); + } + } + kfree(probe); + + dec = kmemdup(body, body_len, GFP_KERNEL); + if (!dec) + return -ENOMEM; + /* 204E0: one-shot 422FFA, not the Linux AES walk. */ + ret = nimbus_422ffa_mmio(&n->spi->dev, dec, body_len, + false); + if (ret || !nimbus_looks_like_arm(dec, body_len)) { + memcpy(dec, body, body_len); + ret = nimbus_gid_crypt(&n->spi->dev, dec, + body_len, false); + } + nimbus_vinfo(n, + "204E0 GID decrypt ret=%d arm=%d head %02x %02x %02x %02x ver=%d\n", + ret, nimbus_looks_like_arm(dec, body_len), + dec[0], dec[1], dec[2], dec[3], verified); + if (!ret && body_len > 0xd210) + nimbus_vinfo(n, + "ARM +0x54 %02x%02x%02x%02x +0x100 %02x%02x%02x%02x +0x1000 %02x%02x%02x%02x +0xD208 %02x%02x%02x%02x +0x20=%08x\n", + dec[0x54], dec[0x55], dec[0x56], dec[0x57], + dec[0x100], dec[0x101], dec[0x102], dec[0x103], + dec[0x1000], dec[0x1001], dec[0x1002], + dec[0x1003], + dec[0xd208], dec[0xd209], dec[0xd20a], + dec[0xd20b], + get_unaligned_le32(dec + 0x20)); + if (!ret) { + body = dec; + } else { + kfree(dec); + dec = NULL; + } + } + } else if (size < 4) { + dev_warn(&n->spi->dev, "FW empty (%zu) — skip download\n", size); + return -EINVAL; + } else { + nimbus_vinfo(n, "Grape FW download %zu bytes (no 8740)\n", + size); + } + +send: + /* + * v6: if body is already 18 E1 30 01… (post-GID constructed FW), + * send once and run post-download. Do not re-packetize ARM. + */ + if (nimbus_looks_like_hbpp_data(body, body_len)) { + nimbus_vinfo(n, + "preconstructed HBPP DATA %zuB — direct SPI (no ARM wrap)\n", + body_len); + ret = nimbus_send_preconstructed_hbpp(n, body, body_len); + if (!ret) { + ret = nimbus_post_download(n); + if (!ret) + ret = nimbus_cmd_2d54c(n); + } + kfree(dec); + return ret; + } + + { + size_t official = nimbus_official_arm_len(body, body_len); + size_t dl_len = body_len; + const u8 *dl_body = body; + u8 *z2_prep = NULL; + u8 *pad_buf = NULL; + u8 *win; + int try, cal; + + if (official < body_len) { + nimbus_vinfo(n, + "cap ARM %zu -> %zu (204E0 +0x0c; strip S/C fill)\n", + body_len, official); + body_len = official; + dl_len = body_len; + } + + z2_prep = nimbus_maybe_prepend_z2_hdr(n, body, body_len, &dl_len); + if (z2_prep) + dl_body = z2_prep; + else if (dl_len & 3) { + pad_buf = kmalloc(round_up(dl_len, 4), GFP_KERNEL); + if (!pad_buf) { + kfree(dec); + return -ENOMEM; + } + memcpy(pad_buf, dl_body, dl_len); + memset(pad_buf + dl_len, 0, round_up(dl_len, 4) - dl_len); + dl_len = round_up(dl_len, 4); + dl_body = pad_buf; + nimbus_vinfo(n, "FW padded to %zu (4-byte align)\n", + dl_len); + } + nimbus_fw_audit(n, dl_body, dl_len, "2D640"); + + win = kzalloc(NIMBUS_FW_HDR_LEN, GFP_KERNEL); + if (!win) { + kfree(z2_prep); + kfree(pad_buf); + kfree(dec); + return -ENOMEM; + } + /* + * Host cal source ≠ controller address. + * 2D640 ARM → dest = fw_dest + offset (OSOS fw_dest=0). + * 2D7A4 cal → dest = 0x00400200 + offset. + * EXEC 0x00100018 is mapped app PC — not upload dest. + */ + cal = nimbus_load_cal_window(n, win); + if (cal < 0) { + dev_err(&n->spi->dev, "2D7A4 aborted: no device cal\n"); + kfree(win); + kfree(z2_prep); + kfree(pad_buf); + kfree(dec); + return cal; + } + + nimbus_vinfo(n, + "2D640 ARM dest_base=0x%08x EXEC=0x%08x cal=0x%08x\n", + fw_dest, exec_addr, NIMBUS_CAL_DEST); + if (fw_dest == 0) + nimbus_vinfo(n, + "expect FW prefix: 18 e1 30 01 07 fc 00 00 00 00 01 03 (len=0x1ff0)\n"); + else + dev_warn(&n->spi->dev, + "fw_dest override 0x%08x — OSOS uses 0 (A/B only)\n", + fw_dest); + nimbus_vinfo(n, + "expect CAL prefix: 18 e1 30 01 00 80 02 00 00 40 00 c2\n"); + + /* 20E94: 273A0 up to 3 times, no 1A878 between. */ + for (try = 0; try < 3; try++) { + ret = nimbus_send_blob(n, dl_body, dl_len, fw_dest); + if (ret) { + dev_err(&n->spi->dev, + "2D640 ARM@0x%08x try %d: %d\n", + fw_dest, try, ret); + continue; + } + n->fw_uploaded = true; + nimbus_fw_readback(n, "post-2D640"); + ret = nimbus_send_blob(n, win, NIMBUS_FW_HDR_LEN, + NIMBUS_CAL_DEST); + if (ret) { + dev_err(&n->spi->dev, + "2D7A4 cal@0x%08x try %d: %d\n", + NIMBUS_CAL_DEST, try, ret); + continue; + } + n->cal_uploaded = true; + nimbus_vinfo(n, + "2D7A4 512B cal @0x%08x ACK (transport only)\n", + NIMBUS_CAL_DEST); + nimbus_cal_readback(n, win); + ret = nimbus_post_download(n); + if (ret) { + dev_warn(&n->spi->dev, + "2D5B0 try %d: %d\n", try, ret); + continue; + } + ret = nimbus_cmd_2d54c(n); + if (!ret) { + nimbus_vinfo(n, + "2D54C EXEC sent (try %d) — await runtime ping\n", + try); + break; + } + dev_warn(&n->spi->dev, "2D54C try %d: %d\n", try, ret); + } + kfree(win); + kfree(z2_prep); + kfree(pad_buf); + } + kfree(dec); + /* EXEC transport success is NOT runtime_ready / fw_loaded. */ + return ret; +} + +static int nimbus_ping(struct nimbus *n, u16 *status_out) +{ + u8 tx[NIMBUS_FRAME_LEN] = { 0 }; + u8 rx[NIMBUS_FRAME_LEN] = { 0 }; + u16 csum, rx_csum; + int ret, tries; + + put_unaligned_le32(NIMBUS_PING_TYPE, tx); + csum = nimbus_sum16(tx, 14); + put_unaligned_le16(csum, tx + 14); + + /* 182590: up to 5 retries, sleep 1 between. After EXEC, 16-bit + * pairs match the app SPI width; 8-bit PIO is bootloader-only. + */ + for (tries = 0; tries < 6; tries++) { + if (n->exec_sent && go_xfer) + ret = nimbus_burst_u16(n, tx, rx, NIMBUS_FRAME_LEN); + else + ret = nimbus_burst16(n, tx, rx); + if (ret) + return ret; + + rx_csum = get_unaligned_le16(rx + 14); + if (!rx_csum && !nimbus_sum16(rx, 14)) { + dev_warn(&n->spi->dev, "ping rx all-zero (MISO dead)\n"); + return -EIO; + } + if (nimbus_sum16(rx, 14) == rx_csum) + break; + /* One dump per call; MultitouchTask rate-limits via ping_fails. */ + if (tries == 0 && n->ping_fails == 0) + dev_warn(&n->spi->dev, + "ping csum fail rx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + rx[0], rx[1], rx[2], rx[3], rx[4], + rx[5], rx[6], rx[7], rx[8], rx[9], + rx[10], rx[11], rx[12], rx[13], + rx[14], rx[15]); + { + u16 w0 = get_unaligned_be16(rx); + + if (nimbus_status_is_bootloader(w0)) + dev_warn(&n->spi->dev, + "bootloader status 0x%04x: the part is answering, its application is not running\n", + w0); + } + if (tries == 5) + return -EIO; + msleep(1); + } + + if (status_out) + *status_out = get_unaligned_le16(rx + 1); + return 0; +} + +static void nimbus_map_coords(s16 rawx, s16 rawy, int *x, int *y) +{ + int xx = (NIMBUS_ABS_X_MAX * ((int)rawx + 75)) / NIMBUS_SCALE_X_DIV; + int yy = (NIMBUS_ABS_Y_MAX * ((int)rawy + 75)) / NIMBUS_SCALE_Y_DIV; + + if (xx < 0) + xx = 0; + if (xx > NIMBUS_ABS_X_MAX) + xx = NIMBUS_ABS_X_MAX; + yy = NIMBUS_ABS_Y_MAX - yy; + if (yy < 0) + yy = 0; + if (yy > NIMBUS_ABS_Y_MAX) + yy = NIMBUS_ABS_Y_MAX; + *x = xx; + *y = yy; +} + +static void nimbus_parse_D(struct nimbus *n, const u8 *payload, unsigned int len) +{ + const u8 *rec; + u8 count, stride; + unsigned int off; + int i; + + if (len < 18 || payload[0] != 0x44) + return; + + off = payload[2]; + count = payload[16]; + stride = payload[17]; + if (!stride || off >= len) + return; + if (count > NIMBUS_SLOTS) + count = NIMBUS_SLOTS; + + rec = payload + off; + for (i = 0; i < count; i++) { + s16 rawx, rawy; + int x, y; + u8 tip; + + if (rec + stride > payload + len) + break; + rawx = (s16)get_unaligned_le16(rec + 4); + rawy = (s16)get_unaligned_le16(rec + 6); + tip = rec[1]; + nimbus_map_coords(rawx, rawy, &x, &y); + + input_mt_slot(n->input, i); + input_mt_report_slot_state(n->input, MT_TOOL_FINGER, tip != 0); + if (tip) { + input_report_abs(n->input, ABS_MT_POSITION_X, x); + input_report_abs(n->input, ABS_MT_POSITION_Y, y); + /* Visible proof on quiet console (fbcon won't scroll from MT) */ + pr_warn_ratelimited("nimbus touch slot%d tip=%u raw=%d,%d -> %d,%d\n", + i, tip, rawx, rawy, x, y); + } + rec += stride; + } + input_mt_sync_frame(n->input); + input_sync(n->input); +} + +static int nimbus_read_reports(struct nimbus *n, u16 ping_st) +{ + u8 *tx, *rx; + u16 csum; + unsigned int len = ping_st + 5; + int ret; + + /* 17E404(ping_status+5), cap 512 */ + if (len < NIMBUS_FRAME_LEN) + len = NIMBUS_FRAME_LEN; + if (len > NIMBUS_READ_MAX) + len = NIMBUS_READ_MAX; + + tx = kzalloc(NIMBUS_READ_MAX, GFP_KERNEL); + rx = kzalloc(NIMBUS_READ_MAX, GFP_KERNEL); + if (!tx || !rx) { + ret = -ENOMEM; + goto out; + } + + tx[0] = NIMBUS_MAGIC; + tx[1] = 0x01; + tx[2] = 0x01; + csum = nimbus_sum16(tx, 14); + /* 17E404 writes sum16(tx,14) at TX+(len-2) */ + put_unaligned_le16(csum, tx + len - 2); + + ret = nimbus_xfer(n, tx, rx, len); + if (ret) + goto out; + + if (rx[0] != NIMBUS_MAGIC) { + dev_info_once(&n->spi->dev, + "read magic fail rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], + rx[6], rx[7]); + ret = -EIO; + goto out; + } + + if (rx[2] && rx[2] != 2) { + unsigned int plen = rx[2]; + + if (plen >= 2 && (5 + (plen - 2)) <= len) + nimbus_parse_D(n, rx + 5, plen - 2); + } else if (len > 5 && rx[5] == 0x44) { + nimbus_parse_D(n, rx + 5, len - 5); + } + ret = 0; +out: + kfree(tx); + kfree(rx); + return ret; +} + +static void nimbus_dump_pad(struct nimbus *n, unsigned int gpio, const char *name) +{ + void __iomem *b; + u32 pin, pcon, din, dir; + + if (!nimbus_verbose || !n->gpio_base) + return; + b = n->gpio_base + 32 * (gpio >> 3); + pin = gpio & 7; + pcon = readl(b); + din = readl(b + 0x04); + dir = readl(b + 0x14); + nimbus_vinfo(n, + "pad %s gpio%u pcon=%x din=%u dir=%u dout=%u punb=%u punc=%u\n", + name, gpio, (pcon >> (4 * pin)) & 0xf, + !!(din & BIT(pin)), !!(dir & BIT(pin)), + !!(readl(b + 0x08) & BIT(pin)), + !!(readl(b + 0x0c) & BIT(pin)), + !!(readl(b + 0x10) & BIT(pin))); +} + +/* + * Optional glass experiment only — not in OSOS sub_1A5AC. + * Default off (extra_por_pulse=0). + */ +static void nimbus_gpio_por_reset(struct nimbus *n) +{ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 0); + msleep(reset_hold_ms); + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 1); + msleep(reset_release_ms); + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 0); + msleep(5); + nimbus_vinfo(n, "extra POR RST %dms low / %dms high\n", + reset_hold_ms, reset_release_ms); +} + +/* + * 1A5AC GPIO half (before 20848): + * 2075A(1) sleep5 → 20766(1) sleep15 → 20690(1) sleep5 → 11B70 + */ +static void nimbus_gpio_bringup(struct nimbus *n) +{ + int rail; + + nimbus_clkcon_replay(n); + + /* GPIOCMD only — gpiod set_value fights polarity on RST. */ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 0); + msleep(5); + /* + * sub_20766(1): 439B00(1) rail first, 66A8(8)+sleep 3, then + * GPIOCMD EN mode 0 (not output-high). + */ + rail = d1830_nimbus_rail(true); + if (rail) + dev_warn(&n->spi->dev, "20766 PMIC rail: %d\n", rail); + else { + /* 66A8(8) is 345D40 thunk (0x220002B2), not an 8ms sleep */ + msleep(3); + } + /* 20766(1): EN mode 0, val 0. Do not cmd-15 the latch. */ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_EN, 0, 0); + nimbus_dump_pad(n, NIMBUS_GPIO_EN, "en-mode0"); + msleep(15); + /* 20690(1) after EN — OSOS order; required after 1A878 unmux */ + nimbus_spi2_pinmux(n, true); + msleep(5); + /* 1A5AC: 11B70 after remux, still in reset, before 20848 */ + nimbus_spi2_11b70(n); + nimbus_dump_pad(n, NIMBUS_GPIO_EN, "en"); + nimbus_dump_pad(n, NIMBUS_GPIO_RST, "rst"); + nimbus_dump_pad(n, NIMBUS_GPIO_IRQ, "irq"); + nimbus_dump_pad(n, 87, "spi2-87"); + nimbus_dump_pad(n, 88, "spi2-88"); + nimbus_dump_pad(n, 89, "spi2-89"); + nimbus_dump_pad(n, 90, "spi2-90"); +} + +/* 1A5AC: 2075A(0) then sleep reset_release_ms (default 30). */ +static void nimbus_gpio_release_reset(struct nimbus *n) +{ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_RST, 1, 1); + msleep(reset_release_ms); + nimbus_dump_pad(n, NIMBUS_GPIO_RST, "rst-rel"); +} + +/* sub_20490(1) — GPIOCMD input + EIC enable for GPIO 38 */ +static void nimbus_irq_enable(struct nimbus *n) +{ + nimbus_gpiocmd_mode(n, NIMBUS_GPIO_IRQ, 0, 0); + /* RetailOS: level, active-low → VIC EXT1 */ + if (s5l8740_eic_enable_gpio(NIMBUS_GPIO_IRQ, IRQ_TYPE_LEVEL_LOW) == 0) + nimbus_vinfo(n, "EIC enabled GPIO%d level-low\n", + NIMBUS_GPIO_IRQ); +} + +static int nimbus_1a5ac_and_download(struct nimbus *n, const u8 *data, + size_t size, const char *tag) +{ + unsigned int attempt; + int err; + + /* Dump path has no pre-1A5AC POR; gate for glass A/B only. */ + if (extra_por_pulse) + nimbus_gpio_por_reset(n); + nimbus_gpio_bringup(n); + err = nimbus_bootload_cmd(n); + if (err) + dev_warn(&n->spi->dev, "bootload %s: %d\n", tag, err); + msleep(15); /* 1A5AC: after 20848, before 2075A(0) */ + nimbus_gpio_release_reset(n); + if (skip_download) { + nimbus_vinfo(n, "skip_download — no 2D640\n"); + return 0; + } + /* 20E94: 26494 then 273A0; settle already done in release (30ms). */ + if (nimbus_probe_26494(n, tag)) { + dev_warn(&n->spi->dev, "26494 %s failed\n", tag); + return -EIO; + } + /* + * Retry the transfer before escalating. The caller's retry re-runs the + * whole rail/reset/HBPP bring-up, which is a lot of disruption for what + * is usually a transient bus error; the stock code retries just this. + */ + for (attempt = 0; attempt < download_tries; attempt++) { + err = nimbus_download_fw(n, data, size, false); + if (!err) + break; + dev_warn(&n->spi->dev, "download %s attempt %u: %d\n", + tag, attempt + 1, err); + } + n->fw_tried = true; + return err; +} + +/* + * 1703E8: 10 failed pings → 13A20(0) then 13A20(1). Cap recycles so a + * stuck bootloader does not spam forever (GO ACKs but app never runs). + */ +#define NIMBUS_RECYCLE_MAX 3 + +static void nimbus_park(struct nimbus *n, const char *why) +{ + if (n->parked) + return; + n->parked = true; + n->stopped = true; + nimbus_verbose = false; + dev_warn(&n->spi->dev, + "nimbus parked (%s) — rmmod/insmod to retry\n", why); + /* + * Parking cuts the rail, which makes every post-mortem probe read + * an undriven bus and look exactly like the failure being + * investigated. touch_power_down only covers suspend, so there was + * no way to examine a part that had reached EXEC and stayed up. + */ + if (park_power_down) + nimbus_power_down(n); + else + dev_warn(&n->spi->dev, + "park_power_down=0: rail left on for probing\n"); +} + +/* ------------------------------------------------------------------ */ +/* Screen-sleep suspend / resume */ +/* */ +/* Two levels, because they trade power against wake latency: */ +/* */ +/* touch_power_down=0 IRQ masked and SPI2 released, controller still */ +/* powered. Resume is immediate. */ +/* touch_power_down=1 full 1A878 power cut including the PMU rail. */ +/* Resume has to re-run bring-up and download the */ +/* firmware again, so it costs a few hundred ms. */ +/* ------------------------------------------------------------------ */ + +static struct nimbus *nimbus_pm_dev; + +static bool touch_power_down = true; +module_param(touch_power_down, bool, 0644); +MODULE_PARM_DESC(touch_power_down, + "Cut the touch rail on screen sleep (default Y; N keeps it powered)"); + +static bool nimbus_pm_suspended; + +int n31_touch_suspend(void) +{ + struct nimbus *n = nimbus_pm_dev; + + if (!n) + return -ENODEV; + if (nimbus_pm_suspended) + return 0; + + if (n->irq > 0) + disable_irq(n->irq); + + if (touch_power_down) { + mutex_lock(&n->lock); + nimbus_power_down(n); + n->runtime_ready = false; + n->spi_ok = false; + mutex_unlock(&n->lock); + } else { + /* Stop driving the bus, leave the controller alive. */ + mutex_lock(&n->lock); + nimbus_spi2_pinmux(n, false); + mutex_unlock(&n->lock); + } + + nimbus_pm_suspended = true; + nimbus_vinfo(n, "touch suspended (power_down=%d)\n", touch_power_down); + return 0; +} +EXPORT_SYMBOL_GPL(n31_touch_suspend); + +int n31_touch_resume(void) +{ + struct nimbus *n = nimbus_pm_dev; + const struct firmware *fw = NULL; + const u8 *data = NULL; + u8 *kbuf = NULL; + size_t size = 0; + int ret = 0; + + if (!n) + return -ENODEV; + if (!nimbus_pm_suspended) + return 0; + + if (!touch_power_down) { + mutex_lock(&n->lock); + nimbus_spi2_pinmux(n, true); + mutex_unlock(&n->lock); + goto done; + } + + /* + * The controller lost its firmware with the rail, so this is the same + * bring-up the probe runs. Storage is back by the time a wake reaches + * here, so fetching the blob again is safe. + */ + ret = nimbus_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf); + if (ret) { + dev_warn(&n->spi->dev, "touch resume: no firmware (%d)\n", ret); + goto done; + } + + mutex_lock(&n->lock); + n->fw_uploaded = false; + n->cal_uploaded = false; + n->requestcal_done = false; + n->exec_sent = false; + n->runtime_ready = false; + n->fw_loaded = false; + n->spi_ok = false; + ret = nimbus_1a5ac_and_download(n, data, size, "resume"); + mutex_unlock(&n->lock); + + nimbus_release_fw(fw, kbuf); + if (ret) + dev_warn(&n->spi->dev, "touch resume download: %d\n", ret); + +done: + if (n->irq > 0) + enable_irq(n->irq); + nimbus_pm_suspended = false; + nimbus_vinfo(n, "touch resumed (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(n31_touch_resume); + + +static void nimbus_recycle(struct nimbus *n) +{ + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + u16 st = 0; + + if (n->parked) + return; + if (n->recycle_count >= NIMBUS_RECYCLE_MAX) { + nimbus_park(n, "recycle budget"); + return; + } + n->recycle_count++; + nimbus_vinfo(n, + "1703E8 10 ping fails — 13A20 recycle %u/%u\n", + n->recycle_count, NIMBUS_RECYCLE_MAX); + nimbus_power_down(n); + n->fw_loaded = false; + n->fw_tried = false; + n->spi_ok = false; + n->ping_fails = 0; + msleep(50); + if (nimbus_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf)) { + dev_warn(&n->spi->dev, "recycle: no FW (FTL or file)\n"); + nimbus_park(n, "no firmware"); + return; + } + nimbus_1a5ac_and_download(n, data, size, "recycle"); + nimbus_release_fw(fw, kbuf); + msleep(2); + if (!nimbus_ping(n, &st)) { + n->spi_ok = true; + nimbus_vinfo(n, "recycle ping ok, status=0x%04x\n", st); + } else if (n->recycle_count >= NIMBUS_RECYCLE_MAX) { + nimbus_park(n, "still bootloader after GO"); + } +} + +static void nimbus_service(struct nimbus *n) +{ + u16 st = 0; + + if (n->parked) + return; + /* 188FFC: ping 490 (5 tries) then 17E404. Never 1A A1 after FW. */ + if (nimbus_ping(n, &st) == 0) { + n->spi_ok = true; + n->ping_fails = 0; + if (st) + nimbus_read_reports(n, st); + return; + } + /* + * The ping is a bootloader-era transaction. Once the application + * is running it answers with a repeating word instead, so a failed + * ping stopped us from ever reading a report -- even though ATTN, + * which the application drives low when a frame is waiting, was + * asserted the whole time. + * + * Measured on a fresh boot: the pad idles high at 0x40 in bank 4 + * DIN, goes low during bring-up and stays low. That is the part + * asking to be serviced. Honour it: if ATTN says there is data, + * read regardless of what the ping thought. + */ + if (attn_read && n->attn && !gpiod_get_value_cansleep(n->attn)) { + int ret = nimbus_read_reports(n, 0); + + if (!ret) { + n->spi_ok = true; + n->ping_fails = 0; + return; + } + if (n->attn_fails++ < 3) + dev_info(&n->spi->dev, + "attn asserted but read failed (%d)\n", + ret); + } + n->ping_fails++; + if (nimbus_verbose && (n->ping_fails <= 3 || n->ping_fails == 10)) + nimbus_vinfo(n, + "188FFC ping still fail (%u) attn=%d\n", + n->ping_fails, + n->attn ? gpiod_get_value_cansleep(n->attn) : -1); +} + +static irqreturn_t nimbus_irq_thread(int irq, void *data) +{ + struct nimbus *n = data; + + mutex_lock(&n->lock); + if (!n->stopped) + nimbus_service(n); + mutex_unlock(&n->lock); + return IRQ_HANDLED; +} + +static void nimbus_try_firmware(struct nimbus *n) +{ + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + + if (n->fw_tried || n->fw_loaded) + return; + /* Don't hammer SPI with 60KB download if ping already fails */ + if (!n->spi_ok) { + u16 st; + + if (nimbus_ping(n, &st)) + return; + n->spi_ok = true; + } + + if (nimbus_acquire_fw(&n->spi->dev, &data, &size, &fw, &kbuf)) + return; + + n->fw_tried = true; + nimbus_download_fw(n, data, size, false); + nimbus_release_fw(fw, kbuf); +} + +static int nimbus_poll_thread(void *data) +{ + struct nimbus *n = data; + int wait; + int fail_backoff_ms = 50; + + for (wait = 0; wait < 30 && !n->fw_loaded && !n->fw_tried && + !kthread_should_stop(); wait++) { + mutex_lock(&n->lock); + nimbus_try_firmware(n); + mutex_unlock(&n->lock); + if (!n->fw_loaded && !n->fw_tried) + msleep(1000); + } + if (!n->fw_loaded && !n->fw_tried) { + n->fw_tried = true; + nimbus_vinfo(n, + "no apple/grape-nimbus.bin — bootload+ping only\n"); + } + + while (!kthread_should_stop()) { + bool do_poll = true; + int attn = -1; + + /* + * 1703E8/188FFC runs every MultitouchTask loop, not only + * on attn. Gating on attn skipped ping if the app released + * GPIO 38 after 2D54C (bootloader holds it low). + */ + if (n->attn) + attn = gpiod_get_value_cansleep(n->attn); + do_poll = true; + + mutex_lock(&n->lock); + if (n->parked || n->stopped) { + mutex_unlock(&n->lock); + /* Parked: sleep until kthread_stop; no SPI traffic. */ + msleep(500); + continue; + } + if (do_poll) { + nimbus_service(n); + if (n->ping_fails >= 10) + nimbus_recycle(n); + fail_backoff_ms = n->spi_ok ? 50 : + min(fail_backoff_ms * 2, 2000); + } + mutex_unlock(&n->lock); + + if (!do_poll) + msleep(20); + else if (!n->spi_ok) + msleep(fail_backoff_ms); + else + msleep(50); + } + return 0; +} + +static ssize_t isys_blob_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct nimbus *n = dev_get_drvdata(dev); + + if (!n || !n->have_isys) + return -ENODATA; + if (off >= NIMBUS_ISYS_LEN) + return 0; + if (off + count > NIMBUS_ISYS_LEN) + count = NIMBUS_ISYS_LEN - off; + memcpy(buf, n->isys + off, count); + return count; +} + +static struct bin_attribute isys_blob_attr = { + .attr = { + .name = "isys_blob", + .mode = 0444, + }, + .size = NIMBUS_ISYS_LEN, + .read = isys_blob_read, +}; + +static void nimbus_isys_sysfs_remove(struct nimbus *n) +{ + if (!n || !n->isys_sysfs) + return; + sysfs_remove_bin_file(&n->spi->dev.kobj, &isys_blob_attr); + n->isys_sysfs = false; +} + +/* + * Bring-up state. This driver carries two dozen module parameters and had + * no way to read back what any of them achieved, so a failed download and + * a controller that never booted looked identical from userspace. + * + * The flags are the download milestones in order, so the first one that + * reads 0 is where the sequence stopped. + */ +static ssize_t state_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct nimbus *n = spi_get_drvdata(to_spi_device(dev)); + + if (!n) + return -ENODEV; + return sysfs_emit(buf, + "spi_ok=%d fw_tried=%d fw_uploaded=%d cal_uploaded=%d\n" + "requestcal_done=%d exec_sent=%d runtime_ready=%d\n" + "irq=%d suspended=%d\n", + n->spi_ok, n->fw_tried, n->fw_uploaded, + n->cal_uploaded, n->requestcal_done, n->exec_sent, + n->runtime_ready, n->irq, nimbus_pm_suspended); +} +/* + * Raw transfer window. + * + * The application firmware answers the bootloader's ping with a + * repeating 16-bit word rather than a checksummed frame. A repeating + * word is not an idle bus -- the part is clocking something out -- so + * the remaining unknown is framing, not liveness. Guessing at that one + * hypothesis per rebuild is far too slow, so expose the transfer itself: + * + * echo 'ea 01 01 00 ...' > raw_xfer send these bytes + * cat raw_xfer what came back + * echo N > raw_len pad/clock out to N bytes + * raw_mode=0 8-bit PIO (bootloader width) + * raw_mode=1 16-bit PIO (application width) + * raw_mode=2 SPI core + * + * Read-only against a part that is already running: it clocks bytes and + * reports what returns. It cannot wedge the SoC the way arming an + * unclaimed level interrupt can, which is the whole point of preferring + * it to another EIC experiment. + */ +static int raw_mode = 1; +module_param(raw_mode, int, 0644); +MODULE_PARM_DESC(raw_mode, + "raw_xfer transport: 0=8-bit PIO 1=16-bit PIO 2=SPI core"); + +static unsigned int raw_len; +module_param(raw_len, uint, 0644); +MODULE_PARM_DESC(raw_len, + "Clock raw_xfer out to this many bytes (0 = as written)"); + +static int nimbus_raw_do(struct nimbus *n, const u8 *tx, u8 *rx, + unsigned int len) +{ + switch (raw_mode) { + case 0: + return nimbus_burst(n, tx, rx, len); + case 2: + return nimbus_xfer(n, tx, rx, len); + default: + return nimbus_burst_u16(n, tx, rx, len); + } +} + +static ssize_t raw_xfer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nimbus *n = dev_get_drvdata(dev); + unsigned int i; + int len = 0; + + if (!n || !n->raw_rx || !n->raw_n) + return sysfs_emit(buf, "(no transfer yet)\n"); + + mutex_lock(&n->lock); + for (i = 0; i < n->raw_n && len < PAGE_SIZE - 4; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%02x%c", + n->raw_rx[i], + ((i & 15) == 15 || i + 1 == n->raw_n) ? + '\n' : ' '); + mutex_unlock(&n->lock); + return len; +} + +static ssize_t raw_xfer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nimbus *n = dev_get_drvdata(dev); + unsigned int nb = 0, want; + const char *p = buf; + u8 *tx, *rx; + int ret; + + if (!n) + return -ENODEV; + + tx = kzalloc(NIMBUS_READ_MAX, GFP_KERNEL); + rx = kzalloc(NIMBUS_READ_MAX, GFP_KERNEL); + if (!tx || !rx) { + ret = -ENOMEM; + goto out; + } + + while (*p && nb < NIMBUS_READ_MAX) { + unsigned int v; + + while (*p == ' ' || *p == ',' || *p == '\n' || + *p == '\t') + p++; + if (!*p) + break; + if (sscanf(p, "%2x", &v) != 1) + break; + tx[nb++] = (u8)v; + while (*p && *p != ' ' && *p != ',' && *p != '\n') + p++; + } + + /* Clocking past the written bytes is how you see a reply that + * arrives after the command, so honour raw_len when it is longer. */ + want = raw_len ? raw_len : nb; + if (!want || want > NIMBUS_READ_MAX) { + ret = -EINVAL; + goto out; + } + if ((raw_mode == 1) && (want & 1)) + want++; + + mutex_lock(&n->lock); + ret = nimbus_raw_do(n, tx, rx, want); + if (!ret) { + if (!n->raw_rx) + n->raw_rx = devm_kzalloc(&n->spi->dev, + NIMBUS_READ_MAX, GFP_KERNEL); + if (n->raw_rx) { + memcpy(n->raw_rx, rx, want); + n->raw_n = want; + } + } + mutex_unlock(&n->lock); + + dev_info(&n->spi->dev, + "raw_xfer mode=%d len=%u ret=%d rx %02x %02x %02x %02x %02x %02x %02x %02x\n", + raw_mode, want, ret, rx[0], rx[1], rx[2], rx[3], + rx[4], rx[5], rx[6], rx[7]); +out: + kfree(tx); + kfree(rx); + return ret ? ret : count; +} +static DEVICE_ATTR_RW(raw_xfer); + +/* + * attn sysfs: the line the application drives when it has a report. + * Readable straight from DIN, which is why touch does not have to wait + * for the EIC to be understood. + */ +static ssize_t attn_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nimbus *n = dev_get_drvdata(dev); + + if (!n || !n->attn) + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%d\n", + gpiod_get_value_cansleep(n->attn)); +} +static DEVICE_ATTR_RO(attn); + +static DEVICE_ATTR_RO(state); + +/* Force a re-run of the bring-up without unbinding the driver. */ +static ssize_t redownload_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct nimbus *n = spi_get_drvdata(to_spi_device(dev)); + int ret; + + if (!n) + return -ENODEV; + if (buf[0] != '1') + return -EINVAL; + ret = n31_touch_suspend(); + if (!ret) + ret = n31_touch_resume(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(redownload); + +static struct attribute *nimbus_attrs[] = { + &dev_attr_raw_xfer.attr, + &dev_attr_attn.attr, + &dev_attr_state.attr, + &dev_attr_redownload.attr, + NULL, +}; +ATTRIBUTE_GROUPS(nimbus); + +static int nimbus_probe(struct spi_device *spi) +{ + struct nimbus *n; + struct input_dev *input; + const struct firmware *fw = NULL; + const u8 *data; + u8 *kbuf = NULL; + size_t size; + u16 ping_st = 0; + int err; + + n = devm_kzalloc(&spi->dev, sizeof(*n), GFP_KERNEL); + if (!n) + return -ENOMEM; + n->spi = spi; + n->blob16 = false; + nimbus_verbose = verbose || !quiet; + mutex_init(&n->lock); + spi_set_drvdata(spi, n); + nimbus_pm_dev = n; + if (sysfs_create_groups(&spi->dev.kobj, nimbus_groups)) + dev_warn(&spi->dev, "sysfs groups failed\n"); + + n->gpio_base = devm_ioremap(&spi->dev, S5L8740_GPIO_PHYS, 0x400); + n->gpiocmd = devm_ioremap(&spi->dev, S5L8740_GPIOCMD_PHYS, 4); + n->spi2 = devm_ioremap(&spi->dev, S5L8740_SPI2_PHYS, 0x80); + if (!n->gpio_base || !n->gpiocmd || !n->spi2) + return -ENOMEM; + + n->enable = devm_gpiod_get_optional(&spi->dev, "enable", GPIOD_ASIS); + if (IS_ERR(n->enable)) + return PTR_ERR(n->enable); + n->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_ASIS); + if (IS_ERR(n->reset)) + return PTR_ERR(n->reset); + n->attn = devm_gpiod_get_optional(&spi->dev, "attn", GPIOD_IN); + if (IS_ERR(n->attn)) + return PTR_ERR(n->attn); + + err = nimbus_acquire_isys_cal(n); + if (err) + return err; + if (!sysfs_create_bin_file(&spi->dev.kobj, &isys_blob_attr)) + n->isys_sysfs = true; + else + dev_warn(&spi->dev, "isys_blob sysfs failed\n"); + + data = NULL; + size = 0; + err = nimbus_acquire_fw(&spi->dev, &data, &size, &fw, &kbuf); + if (err) + dev_warn(&spi->dev, + "no grape firmware (%d) — A34 IsyS is present but 2D640 cannot run\n", + err); + + if (!data || !size) { + nimbus_park(n, "no grape firmware"); + } else { + int attempt; + + /* + * 13A20(1): first try is 1A5AC only. 1A878 + sleep 50 + * only after a failed 1A5AC, max 3. remove() already + * 1A878s on reload. + */ + /* + * sub_20E94 reads PMIC 0x51 here, between the 26494 probe and + * the download loop, and discards the value. Do the same: the + * read itself may be the point. + */ + { + int (*pmic_read)(void); + + pmic_read = (int (*)(void)) + __symbol_get("d1830_touch_bringup_read"); + if (pmic_read) { + pmic_read(); + __symbol_put("d1830_touch_bringup_read"); + } + } + + for (attempt = 0; attempt < 3; attempt++) { + if (attempt) { + nimbus_power_down(n); + msleep(50); + } + /* 0xEE is iOS3 Z2-only; N31 wake is 19 C1 in reset. */ + mutex_lock(&n->lock); + n->fw_uploaded = false; + n->cal_uploaded = false; + n->requestcal_done = false; + n->exec_sent = false; + n->runtime_ready = false; + n->fw_loaded = false; + n->spi_ok = false; + nimbus_1a5ac_and_download(n, data, size, + attempt ? "retry" : "1A5AC"); + { + u16 st = 0; + + /* + * Cross EXEC boundary: short wait then runtime + * ping only. No HBPP 1A A1 until ping fails. + */ + if (n->exec_sent) + msleep(exec_wait_ms ? exec_wait_ms : 1); + else + msleep(2); + err = nimbus_ping(n, &ping_st); + if (!err) { + n->spi_ok = true; + n->runtime_ready = true; + n->fw_loaded = true; + nimbus_dev_vinfo(&spi->dev, + "runtime ping ok status=0x%04x (ready)\n", + ping_st); + } else { + dev_warn(&spi->dev, + "runtime ping fail attempt %d (exec_sent=%d)\n", + attempt, n->exec_sent); + nimbus_peek(n, "post-go-fail"); + /* Diagnostics only after runtime fail. */ + if (nimbus_status_poll(n, &st) == 0) + nimbus_dev_vinfo(&spi->dev, + "post-fail HBPP status 0x%04x\n", + st); + err = 0; /* keep 1A878 retries going */ + } + } + mutex_unlock(&n->lock); + /* Only runtime_ready ends the 1A878 retry loop. */ + if (n->runtime_ready) + break; + } + } + nimbus_release_fw(fw, kbuf); + nimbus_dev_vinfo(&spi->dev, + "nimbus state uploaded=%d cal=%d reqcal=%d exec=%d runtime=%d spi_ok=%d\n", + n->fw_uploaded, n->cal_uploaded, n->requestcal_done, + n->exec_sent, n->runtime_ready, n->spi_ok); + + if (n->cal_uploaded && n->exec_sent && n->runtime_ready) { + /* 1A5AC: 20490 after 20E94, then MultitouchTask 1703E8/188FFC. */ + nimbus_irq_enable(n); + n->irq = spi->irq; + if (n->irq > 0) { + err = devm_request_threaded_irq(&spi->dev, n->irq, NULL, + nimbus_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "nimbus", n); + if (err) { + dev_warn(&spi->dev, + "VIC IRQ %d request failed %d — attn poll\n", + n->irq, err); + n->irq = -1; + } else { + n->use_irq = true; + nimbus_dev_vinfo(&spi->dev, + "IRQ-driven (VIC irq %d + EIC GPIO%d)\n", + n->irq, NIMBUS_GPIO_IRQ); + } + } + } else { + dev_err(&spi->dev, + "Nimbus boot failed: cal=%d exec=%d runtime=%d; not registering input\n", + n->cal_uploaded, n->exec_sent, n->runtime_ready); + if (!n->parked) + nimbus_park(n, "boot incomplete"); + return 0; + } + + input = devm_input_allocate_device(&spi->dev); + if (!input) { + nimbus_isys_sysfs_remove(n); + return -ENOMEM; + } + n->input = input; + input->name = "Apple Nimbus"; + input->phys = "nimbus/input0"; + input->id.bustype = BUS_SPI; + __set_bit(INPUT_PROP_DIRECT, input->propbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, NIMBUS_ABS_X_MAX, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, NIMBUS_ABS_Y_MAX, 0, 0); + err = input_mt_init_slots(input, NIMBUS_SLOTS, INPUT_MT_DIRECT); + if (err) { + nimbus_isys_sysfs_remove(n); + return err; + } + err = input_register_device(input); + if (err) { + nimbus_isys_sysfs_remove(n); + return err; + } + + if (ping_st) { + mutex_lock(&n->lock); + nimbus_read_reports(n, ping_st); + mutex_unlock(&n->lock); + } + n->thread = kthread_run(nimbus_poll_thread, n, "nimbus-poll"); + if (IS_ERR(n->thread)) { + dev_warn(&spi->dev, + "nimbus-poll kthread %ld — poll via IRQ only\n", + PTR_ERR(n->thread)); + n->thread = NULL; + } + + dev_info(&spi->dev, "Nimbus up (attn=%d runtime=%d)\n", + !!n->attn, n->runtime_ready); + return 0; +} + +/* + * OSOS sub_1A878 is the disable path -- 20490(0), RST asserted, 20690(0) + * to release the SPI2 pads, rail off, EN mode 1 -- and nimbus_power_down + * already implements it. It was only ever reached through remove(), + * which a shutdown or kexec does not call, so the part was left powered + * and holding the bus across the handover. + */ +static void nimbus_shutdown(struct spi_device *spi) +{ + struct nimbus *n = spi_get_drvdata(spi); + + if (!n) + return; + n->stopped = true; + if (n->thread) + kthread_stop(n->thread); + n->thread = NULL; + nimbus_power_down(n); +} + +static void nimbus_remove(struct spi_device *spi) +{ + nimbus_pm_dev = NULL; + struct nimbus *n = spi_get_drvdata(spi); + + n->stopped = true; + if (n->thread) + kthread_stop(n->thread); + nimbus_isys_sysfs_remove(n); + /* + * probe creates these but nothing removed them, so the group + * outlived the module. The next insmod then hit a duplicate + * filename, and internal_create_group rolls the whole group back + * on failure -- so a second load silently lost every attribute, + * including state and redownload. It looked like the attributes + * were never registered rather than registered twice. + */ + sysfs_remove_groups(&spi->dev.kobj, nimbus_groups); + nimbus_clkcon_restore(); + nimbus_power_down(n); +} + +static const struct of_device_id nimbus_of_match[] = { + { .compatible = "apple,nimbus" }, + { } +}; +MODULE_DEVICE_TABLE(of, nimbus_of_match); + +static struct spi_driver nimbus_driver = { + .driver = { + .name = "apple-nimbus", + .of_match_table = nimbus_of_match, + }, + .probe = nimbus_probe, + .remove = nimbus_remove, + .shutdown = nimbus_shutdown, +}; +module_spi_driver(nimbus_driver); + +MODULE_DESCRIPTION("Apple Nimbus/Grape multitouch (N31 SPI2, RetailOS-matched)"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("apple/grape-nimbus.bin"); diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index c11b9965c4ad9b..215047cfce1d48 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -762,3 +762,10 @@ config SUNPLUS_SP7021_INTC the primary controller on C-Chip. endmenu + +config S5L8740_EIC + bool "Samsung/Apple S5L8740 GPIO EIC" + depends on ARM || COMPILE_TEST + select IRQ_DOMAIN + help + External interrupt controller at 0x39700000 feeding PL192 VIC EXTn. diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 25e9ad29b8c4a5..41becb0b032e73 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -129,3 +129,4 @@ obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o +obj-$(CONFIG_S5L8740_EIC) += irq-s5l8740-eic.o diff --git a/drivers/irqchip/irq-s5l8740-eic.c b/drivers/irqchip/irq-s5l8740-eic.c new file mode 100644 index 00000000000000..a791c71b4cf050 --- /dev/null +++ b/drivers/irqchip/irq-s5l8740-eic.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 GPIO External Interrupt Controller (EIC) @ 0x39700000 + * + * Topology (CONFIRMED_N31): + * GPIO → EIC → EXT line → PL192 VIC @ 0x38E00000 → CPU + * + * Registers (group g = 0..6): + * +0x80+4*g INTLEVEL 0=low, 1=high + * +0xA0+4*g INTSTAT W1C (edge) / status + * +0xC0+4*g INTEN + * +0xE0+4*g INTTYPE 0=edge, 1=level + * + * GPIO → (group, bit): group = gpio >> 5, bit = gpio & 31 + * Group g is parented to VIC EXT irq g (Nimbus GPIO38 → g=1 bit6 → VIC1). + * + * RetailOS: sub_7D490 (level), sub_40641C (type+enable+ack). + * + * #interrupt-cells = <2> via irq_domain_xlate_twocell (hwirq, flags). + * + * Chaining: only chain parents that appear in DT interrupts. The safe N31 + * config is a single parent — EXT1 (interrupts = <1>) for Nimbus GPIO38 / + * group1 — until multi-EXT chaining is HW-proven. Omitting interrupts = + * MMIO helper only (s5l8740_eic_enable_gpio / gpio_to_irq). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EIC_NGROUPS 7 +#define EIC_GPIOS (EIC_NGROUPS * 32) + +#define EIC_INTLEVEL(g) (0x80 + 4 * (g)) +#define EIC_INTSTAT(g) (0xa0 + 4 * (g)) +#define EIC_INTEN(g) (0xc0 + 4 * (g)) +#define EIC_INTTYPE(g) (0xe0 + 4 * (g)) + +struct s5l8740_eic { + void __iomem *base; + struct irq_domain *domain; + int parent_irq[EIC_NGROUPS]; +}; + +static struct s5l8740_eic *s5l8740_eic_global; + +static void eic_mask(struct irq_data *d) +{ + struct s5l8740_eic *eic = irq_data_get_irq_chip_data(d); + unsigned int gpio = irqd_to_hwirq(d); + u32 g = gpio >> 5, bit = gpio & 31; + u32 en; + + en = readl(eic->base + EIC_INTEN(g)); + writel(en & ~BIT(bit), eic->base + EIC_INTEN(g)); +} + +static void eic_unmask(struct irq_data *d) +{ + struct s5l8740_eic *eic = irq_data_get_irq_chip_data(d); + unsigned int gpio = irqd_to_hwirq(d); + u32 g = gpio >> 5, bit = gpio & 31; + u32 en; + + /* ack sticky */ + writel(BIT(bit), eic->base + EIC_INTSTAT(g)); + en = readl(eic->base + EIC_INTEN(g)); + writel(en | BIT(bit), eic->base + EIC_INTEN(g)); +} + +/* + * Hardware INTLEVEL is one polarity. gpio-keys always requests EDGE_BOTH + * (IRQF_TRIGGER_RISING|FALLING) → -EINVAL without this. Flip polarity on + * each ack so press (falling, idle-high) and release (rising) both fire. + */ +static void eic_ack(struct irq_data *d) +{ + struct s5l8740_eic *eic = irq_data_get_irq_chip_data(d); + unsigned int gpio = irqd_to_hwirq(d); + u32 g = gpio >> 5, bit = gpio & 31; + + writel(BIT(bit), eic->base + EIC_INTSTAT(g)); + + if ((irqd_get_trigger_type(d) & IRQ_TYPE_SENSE_MASK) == + IRQ_TYPE_EDGE_BOTH) { + u32 level = readl(eic->base + EIC_INTLEVEL(g)); + + writel(level ^ BIT(bit), eic->base + EIC_INTLEVEL(g)); + } +} + +static int eic_set_type(struct irq_data *d, unsigned int type) +{ + struct s5l8740_eic *eic = irq_data_get_irq_chip_data(d); + unsigned int gpio = irqd_to_hwirq(d); + u32 g = gpio >> 5, bit = gpio & 31; + u32 level, itype; + unsigned int sense = type & IRQ_TYPE_SENSE_MASK; + + level = readl(eic->base + EIC_INTLEVEL(g)); + itype = readl(eic->base + EIC_INTTYPE(g)); + + switch (sense) { + case IRQ_TYPE_LEVEL_LOW: + level &= ~BIT(bit); /* 0 = low */ + itype |= BIT(bit); /* 1 = level */ + irq_set_handler_locked(d, handle_level_irq); + break; + case IRQ_TYPE_LEVEL_HIGH: + level |= BIT(bit); + itype |= BIT(bit); + irq_set_handler_locked(d, handle_level_irq); + break; + case IRQ_TYPE_EDGE_FALLING: + level &= ~BIT(bit); + itype &= ~BIT(bit); /* 0 = edge */ + irq_set_handler_locked(d, handle_edge_irq); + break; + case IRQ_TYPE_EDGE_RISING: + level |= BIT(bit); + itype &= ~BIT(bit); + irq_set_handler_locked(d, handle_edge_irq); + break; + case IRQ_TYPE_EDGE_BOTH: + /* Idle-high active-low keys: first event is falling. */ + level &= ~BIT(bit); + itype &= ~BIT(bit); + irq_set_handler_locked(d, handle_edge_irq); + break; + default: + return -EINVAL; + } + + writel(level, eic->base + EIC_INTLEVEL(g)); + writel(itype, eic->base + EIC_INTTYPE(g)); + return 0; +} + +static struct irq_chip s5l8740_eic_chip = { + .name = "s5l8740-eic", + .irq_ack = eic_ack, + .irq_mask = eic_mask, + .irq_unmask = eic_unmask, + .irq_set_type = eic_set_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE | + IRQCHIP_SET_TYPE_MASKED, +}; + +static void eic_chained_handler(struct irq_desc *desc) +{ + struct s5l8740_eic *eic = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int g, parent = irq_desc_get_irq(desc); + u32 stat, en, pending; + unsigned int bit; + + chained_irq_enter(chip, desc); + + for (g = 0; g < EIC_NGROUPS; g++) { + if (eic->parent_irq[g] != parent) + continue; + stat = readl(eic->base + EIC_INTSTAT(g)); + en = readl(eic->base + EIC_INTEN(g)); + pending = stat & en; + for_each_set_bit(bit, (unsigned long *)&pending, 32) { + unsigned int virq = irq_find_mapping(eic->domain, + (g << 5) | bit); + static unsigned hits; + + if (hits < 8) { + hits++; + pr_debug("EIC hit g%u b%u virq=%u\n", g, bit, + virq); + } + if (virq) + generic_handle_irq(virq); + /* W1C ack */ + writel(BIT(bit), eic->base + EIC_INTSTAT(g)); + } + } + + chained_irq_exit(chip, desc); +} + +static int eic_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &s5l8740_eic_chip, handle_level_irq); + irq_set_chip_data(irq, d->host_data); + irq_set_probe(irq); + return 0; +} + +static const struct irq_domain_ops eic_domain_ops = { + .map = eic_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +/* Export for early consumers (nimbus) before domain lookup */ +int s5l8740_eic_enable_gpio(unsigned int gpio, unsigned int irq_type) +{ + struct s5l8740_eic *eic = s5l8740_eic_global; + u32 g, bit; + u32 level, itype, en; + + if (!eic || gpio >= EIC_GPIOS) + return -ENODEV; + + g = gpio >> 5; + bit = gpio & 31; + + level = readl(eic->base + EIC_INTLEVEL(g)); + itype = readl(eic->base + EIC_INTTYPE(g)); + + if (irq_type & IRQ_TYPE_LEVEL_HIGH) + level |= BIT(bit); + else + level &= ~BIT(bit); /* default active-low */ + + if (irq_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) + itype &= ~BIT(bit); + else + itype |= BIT(bit); /* level */ + + writel(level, eic->base + EIC_INTLEVEL(g)); + writel(itype, eic->base + EIC_INTTYPE(g)); + writel(BIT(bit), eic->base + EIC_INTSTAT(g)); + en = readl(eic->base + EIC_INTEN(g)); + writel(en | BIT(bit), eic->base + EIC_INTEN(g)); + return 0; +} +EXPORT_SYMBOL_GPL(s5l8740_eic_enable_gpio); + +/** + * s5l8740_eic_gpio_to_irq - create/return Linux IRQ for an SoC GPIO line + * @gpio: SoC GPIO number (group = gpio>>5, bit = gpio&31) + */ +int s5l8740_eic_gpio_to_irq(unsigned int gpio) +{ + struct s5l8740_eic *eic = s5l8740_eic_global; + int virq; + + if (!eic || !eic->domain || gpio >= EIC_GPIOS) + return -ENODEV; + + virq = irq_create_mapping(eic->domain, gpio); + if (!virq) + return -EINVAL; + return virq; +} +EXPORT_SYMBOL_GPL(s5l8740_eic_gpio_to_irq); + +static int s5l8740_eic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l8740_eic *eic; + int g, nirq, ngrp, i, ret, chained = 0; + + eic = devm_kzalloc(dev, sizeof(*eic), GFP_KERNEL); + if (!eic) + return -ENOMEM; + + eic->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(eic->base)) + return PTR_ERR(eic->base); + + /* Mask all, clear status (same as SEC pinmux_223C) */ + for (g = 0; g < EIC_NGROUPS; g++) { + writel(0, eic->base + EIC_INTEN(g)); + writel(0xffffffff, eic->base + EIC_INTSTAT(g)); + writel(0, eic->base + EIC_INTLEVEL(g)); + writel(0, eic->base + EIC_INTTYPE(g)); + eic->parent_irq[g] = -1; + } + + eic->domain = irq_domain_add_linear(dev->of_node, EIC_GPIOS, + &eic_domain_ops, eic); + if (!eic->domain) + return -ENOMEM; + + nirq = of_irq_count(dev->of_node); + if (nirq < 0) + nirq = 0; + if (nirq > EIC_NGROUPS) + nirq = EIC_NGROUPS; + + ngrp = of_property_count_u32_elems(dev->of_node, "apple,eic-groups"); + if (ngrp < 0) + ngrp = 0; + + /* + * Chain only the VIC EXTn parents listed in DT. Map each to an EIC + * group via apple,eic-groups (parallel to interrupts). GPIO 86 is + * group 2 (86>>5) on VIC EXT3 = 3 — do not chain every EXT0..6 + * (that hung boot). + */ + for (i = 0; i < nirq; i++) { + u32 group = i; + + ret = platform_get_irq(pdev, i); + if (ret < 0) + continue; + if (ngrp == nirq) + of_property_read_u32_index(dev->of_node, + "apple,eic-groups", i, + &group); + if (group >= EIC_NGROUPS) + continue; + eic->parent_irq[group] = ret; + irq_set_chained_handler_and_data(ret, eic_chained_handler, eic); + chained++; + dev_info(dev, "EIC group%u <- VIC irq %d\n", group, ret); + } + + s5l8740_eic_global = eic; + platform_set_drvdata(pdev, eic); + dev_info(dev, + "EIC @%pR groups=%d dt_irqs=%d chained_parents=%d (prefer EXT1-only)\n", + platform_get_resource(pdev, IORESOURCE_MEM, 0), EIC_NGROUPS, + nirq, chained); + return 0; +} + +static const struct of_device_id s5l8740_eic_of_match[] = { + { .compatible = "apple,s5l8740-eic" }, + { .compatible = "samsung,s5l8740-eic" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_eic_of_match); + +static struct platform_driver s5l8740_eic_driver = { + .probe = s5l8740_eic_probe, + .driver = { + .name = "s5l8740-eic", + .of_match_table = s5l8740_eic_of_match, + }, +}; +builtin_platform_driver(s5l8740_eic_driver); + +MODULE_DESCRIPTION("S5L8740 GPIO EIC (External Interrupt Controller)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index ea93e7236c4ac9..b4f707c38f47f3 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -89,9 +89,14 @@ static void vic_init2(void __iomem *base) { int i; - for (i = 0; i < 16; i++) { - void __iomem *reg = base + VIC_VECT_CNTL0 + (i * 4); - writel(VIC_VECT_CNTL_ENABLE | i, reg); + /* + * PL190 has 16 vectored slots; PL192 has 32. Linux used to program + * only 0..15, so sources 16..31 (N31 IIC is family 21/22) kept the + * bootloader VECTADDR. Zero every slot, then enable all 32. + */ + for (i = 0; i < 32; i++) { + writel(0, base + VIC_VECT_ADDR0 + (i * 4)); + writel(VIC_VECT_CNTL_ENABLE | i, base + VIC_VECT_CNTL0 + (i * 4)); } writel(32, base + VIC_PL190_DEF_VECT_ADDR); @@ -391,6 +396,9 @@ static void __init vic_clear_interrupts(void __iomem *base) value = readl(base + VIC_PL190_VECT_ADDR); writel(value, base + VIC_PL190_VECT_ADDR); } + /* PL192 EOI is +0xF00. U-Boot/SEC may have taken a vector without EOI. */ + for (i = 0; i < 32; i++) + writel(0, base + VIC_PL192_VECT_ADDR); } /* diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 56bc72c7ce4a99..7dd6112ed82468 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -650,3 +650,41 @@ source "drivers/misc/pvpanic/Kconfig" source "drivers/misc/mchp_pci1xxxx/Kconfig" source "drivers/misc/keba/Kconfig" endmenu + +config FMSS_S5L8740 + tristate "S5L8740 FMSS NAND (N31 WhimoryPPN CS path)" + depends on HAS_IOMEM + help + Flash Memory Subsystem driver for iPod nano 7G (S5L8740). + Provides CS DMA page reads, META weave/LBA scan, and sysfs + hooks used by the FTL bring-up path. + +config FTL_S5L8740 + tristate "S5L8740 FTL helper (N31)" + depends on FMSS_S5L8740 + help + Higher-level FTL helper layered on FMSS_S5L8740 for N31 + restore / LBA mapping experiments. + +config APPLE_MIKEYBUS + tristate "Apple MikeyBus headset jack (N31 UART2)" + depends on SERIAL_DEV_BUS + help + MikeyBus on UART2 (GPIO 66/67): headset model ID (open circuit / + A18/B18/…) and remote button RX. Not Tristar; not CS42 amp. + Baud/protocol OPEN until accessory snap. force_plugged for glass. + +config APPLE_TRISTAR_CBTL1609 + tristate "Apple Lightning Tristar (CBTL1609A1) mux" + depends on I2C + help + I2C driver for NXP CBTL1609A1 Lightning charge/data mux (Tristar) + on iPod nano 7G. Lives under misc because N31 is gadget-only + (CONFIG_USB host is off, so drivers/usb/misc is invisible). + +config S5L8740_IIS2_MMIO + tristate "S5L8740 IIS2 FM MMIO hook (obsolete)" + depends on HAS_IOMEM && BROKEN + help + Obsolete. Use CONFIG_SND_SOC_APPLE_S5L8740_IIS2 instead. + diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 545aad06d08856..f9162ce4841ac6 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 # # Makefile for misc devices that really don't fit anywhere else. -# obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_IBMVMC) += ibmvmc.o @@ -75,3 +74,10 @@ lan966x-pci-objs := lan966x_pci.o lan966x-pci-objs += lan966x_pci.dtbo.o obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ +obj-$(CONFIG_APPLE_MIKEYBUS) += apple-mikeybus.o +obj-$(CONFIG_APPLE_TRISTAR_CBTL1609) += apple-tristar-cbtl1609.o +obj-$(CONFIG_S5L8740_IIS2_MMIO) += s5l8740-iis2-mmio.o +obj-$(CONFIG_FTL_S5L8740) += ftl-s5l8740.o +obj-m += nand-s5l8740.o +obj-m += ftl-s5l8740.o +ftl-s5l8740-y := ftl-s5l8740-core.o ftl-s5l8740-csmap.o ftl-s5l8740-vecmap.o diff --git a/drivers/misc/apple-mikeybus.c b/drivers/misc/apple-mikeybus.c new file mode 100755 index 00000000000000..e654f3399275c6 --- /dev/null +++ b/drivers/misc/apple-mikeybus.c @@ -0,0 +1,1575 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple N31 MikeyBus, decomp-aligned Linux driver. + * + * RetailOS objects: + * CMikeyBusUartReadTask + * CMikeyBusUartResistorTask + * + * Read path: + * sub_570BA8 opens channel 4 with command 9/0x71/channel4. + * sub_500ECC handles lower packet type 0x70 and appends payload bytes + * to a 1024-byte RX ring. + * sub_2542F0 drains that RX ring and appends each byte to a parent stream. + * If byte == 0xAA, it appends an extra 0x01 to the stream. + * + * Resistor/model path: + * sub_410DB0 enables channel 3 and submits command 3/0x8D/channel3. + * It waits with timeout 100 on the backend result object. + * It reads sample byte 0x8A92444 and modifier byte 0x8A9244C. + * If sample == 15 and modifier == 0, sample is remapped to 100. + * + * Presence/model state: + * sub_587F38 consumes 0x7E / 0x8A-like presence events. + * sub_17DD6C toggles the model modifier/state byte and opens/closes + * the read path. + * + * Gate/audio route: + * sub_42D364 and sub_587E60 tie the Mikey state to the CS42/audio route. + * Do not blindly poke audio rails here. + * + * Not implemented yet: + * button input-event mapping. + * + * Never do: + * GPIO66/67 resistor detection. Those are UART pins, not model-detect GPIOs. + * + * Optional UART pad mux (GPIO 66/67 = TX/RX): + * GPIO_PHYS 0x3cf00000, GPIOCMD_PHYS 0x3cf001e0, TX=0x42 RX=0x43, + * GPIOCMD mode 2 for UART function ONLY. Never sample those pins as + * resistor DIN / model detect. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_PHYS 0x3cf00000ul +#define GPIOCMD_PHYS 0x3cf001e0ul + +/* UART2 pad mux only — NOT DIN / resistor detect. */ +#define MIKEY_GPIO_TX 0x42u /* 66 — UART TX mux */ +#define MIKEY_GPIO_RX 0x43u /* 67 — UART RX mux */ + +#define MIKEY_RX_RING_SIZE 1024 +#define MIKEY_TASK_RING_SIZE 2048 + +#define MIKEY_SAMPLE_DEFAULT 0x64 +#define MIKEY_SAMPLE_OPEN_CIRCUIT 0x0b +#define MIKEY_SAMPLE_REMAP_FROM 0x0f + +#define MIKEY_CH_RESISTOR 3 +#define MIKEY_CH_READ 4 + +#define MIKEY_PKT_RX_BYTES 0x70 +#define MIKEY_PKT_IGNORED_74 0x74 +#define MIKEY_PKT_STATUS_76 0x76 +#define MIKEY_PKT_STATUS_8A 0x8a + +#define MIKEY_INJECT_MAX 64 + +/* -------------------- module parameters -------------------- */ + +static bool force_plugged; +module_param(force_plugged, bool, 0644); +MODULE_PARM_DESC(force_plugged, "Force headset plugged state for bring-up"); + +static int force_model = -1; +module_param(force_model, int, 0644); +MODULE_PARM_DESC(force_model, "Force headset model sample, -1 disables"); + +static bool auto_report = true; +module_param(auto_report, bool, 0644); +MODULE_PARM_DESC(auto_report, "Print plug/unplug/model changes to kernel log"); + +static bool active_probe; +module_param(active_probe, bool, 0644); +MODULE_PARM_DESC(active_probe, + "Experimental: actively send model probe command if transport is implemented"); + +static int poll_ms = 500; +module_param(poll_ms, int, 0644); +MODULE_PARM_DESC(poll_ms, "Model poll interval in milliseconds"); + +/* + * Remote buttons. + * + * What the decomp proves: the event vocabulary, from the handler names -- + * HandleMikeyCenter, HandleMikeyVolumeUp, HandleMikeyVolumeDown and + * HandleMikeyAllUp. That last one is the useful inference. An "all up" + * event only makes sense if the wire carries a bitmap of the buttons + * currently held rather than discrete press and release codes: with + * discrete codes each button would report its own release and a combined + * all-up event would be redundant. So this decodes a held-button bitmap and + * derives press/release by comparing against the previous value, with an + * all-zero byte meaning everything is released. + * + * What the decomp does NOT give is which bit is which button. The names + * prove the vocabulary, not the wire encoding, and nothing in the + * disassembly pins the bit order down. Guessing it in the source would bury + * an assumption somewhere it cannot be seen, so it lives here instead: one + * keycode per bit, changeable at runtime. Read remote_raw while pressing a + * known button, see which bit moves, and set the map accordingly -- that is + * one session with the hardware rather than an argument about byte order. + * + * The default below is a placeholder ordering, NOT an attested one. + */ +#define MIKEY_BUTTON_BITS 8 + +static int button_map[MIKEY_BUTTON_BITS] = { + KEY_PLAYPAUSE, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_NEXTSONG, + KEY_PREVIOUSSONG, 0, 0, 0, +}; +static int button_map_count = MIKEY_BUTTON_BITS; +module_param_array(button_map, int, &button_map_count, 0644); +MODULE_PARM_DESC(button_map, + "keycode per remote bitmap bit 0..7, 0=unused; bit order is NOT attested, confirm with remote_raw"); + +static int baud = 115200; +module_param(baud, int, 0644); +MODULE_PARM_DESC(baud, "MikeyBus UART baud rate"); + +static bool accept_case3_model = true; +module_param(accept_case3_model, bool, 0644); +MODULE_PARM_DESC(accept_case3_model, + "Accept backend packet class 3 as model sample candidate"); + +/* -------------------- state -------------------- */ + +struct apple_mikey_ring { + u8 data[MIKEY_TASK_RING_SIZE]; + u16 head; + u16 tail; + u32 drops; +}; + +struct apple_mikeybus { + struct device *dev; + struct serdev_device *serdev; + struct mutex lock; + struct delayed_work poll_work; + + void __iomem *gpio; + void __iomem *gpiocmd; + bool pinmux_on; + bool uart_opened; + + bool auto_report; + bool active_probe; + bool resistor_backend_ready; + + bool plugged; + bool last_reported_plugged; + + u8 model; + u8 last_reported_model; + u8 model_sample; + u8 model_modifier; + + bool force_plugged; + int force_model; + + u32 decomp_channel_mask_shadow; + u8 rx_status_shadow; + + struct apple_mikey_ring rx_raw; + struct apple_mikey_ring rx_task_stream; + struct apple_mikey_ring remote_raw; /* channel 4 only */ + + struct input_dev *input; + u8 last_buttons; + u32 remote_bytes; + u32 button_events; + u32 all_up_events; + + u32 rx_bytes; + u32 lower_packets; + u32 lower_rx70_packets; + u32 lower_status_packets; + u32 presence_packets; + u32 aa_stuff_count; + u32 model_changes; + u32 plug_events; + u32 unplug_events; + u32 active_probe_count; + u32 active_probe_fail_count; + + int baud; +}; + +static struct apple_mikeybus *mikeybus_singleton; +static DEFINE_MUTEX(mikeybus_singleton_lock); +static struct platform_device *mikey_plat_pdev; +static void mikey_ensure_plat(struct work_struct *work); +static DECLARE_WORK(mikey_plat_work, mikey_ensure_plat); + +/* -------------------- model tables -------------------- */ + +static const char *mikey_model_name(u8 model) +{ + switch (model) { + case 0x01: return "A18"; + case 0x02: return "B18"; + case 0x03: return "A62"; + case 0x04: return "B15"; + case 0x05: return "A36"; + case 0x06: return "Apple noise occluding"; + case 0x07: return "mfg noise occluding"; + case 0x08: return "mfg noise occluding w/ mic"; + case 0x09: return "mfg std"; + case 0x0a: return "mfg std w/ mic"; + case 0x0b: return "open circuit"; + case 0x0d: return "B60f"; + case 0x0e: return "B60g"; + case 0x0f: return "B149"; + case 0x10: return "B187"; + case 0x64: return "default/open/unknown"; + default: return "inscrutable"; + } +} + +static bool mikey_sample_is_plugged(u8 sample) +{ + switch (sample) { + case 0x0b: /* open circuit */ + case 0x64: /* RetailOS default/open/unknown */ + return false; + default: + return true; + } +} + +/* -------------------- CS42 exports -------------------- */ + +int apple_mikeybus_jack_present(void) +{ + int ret; + + mutex_lock(&mikeybus_singleton_lock); + if (!mikeybus_singleton) { + mutex_unlock(&mikeybus_singleton_lock); + return -ENODEV; + } + mutex_lock(&mikeybus_singleton->lock); + ret = mikeybus_singleton->plugged ? 1 : 0; + mutex_unlock(&mikeybus_singleton->lock); + mutex_unlock(&mikeybus_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_mikeybus_jack_present); + +int apple_mikeybus_headset_ready(void) +{ + /* Same as jack_present for now (analog HP gate). */ + return apple_mikeybus_jack_present(); +} +EXPORT_SYMBOL_GPL(apple_mikeybus_headset_ready); + +/* -------------------- rings -------------------- */ + +static void mikey_ring_put(struct apple_mikey_ring *r, u8 b) +{ + u16 next = (r->head + 1) % sizeof(r->data); + + if (next == r->tail) { + r->drops++; + r->tail = (r->tail + 1) % sizeof(r->data); + } + + r->data[r->head] = b; + r->head = next; +} + +static size_t mikey_ring_dump_hex(struct apple_mikey_ring *r, + char *buf, size_t max) +{ + size_t n = 0; + u16 p = r->tail; + + while (p != r->head && n + 4 < max) { + n += scnprintf(buf + n, max - n, "%02x ", r->data[p]); + p = (p + 1) % sizeof(r->data); + } + + if (n && n < max) + buf[n - 1] = '\n'; + + return n; +} + +/* -------------------- UART pad mux (UART function only) -------------------- */ + +static void mikey_gpiocmd(struct apple_mikeybus *m, u8 gpio, u8 mode) +{ + u32 bank = gpio >> 3; + u32 pin = gpio & 7; + + if (!m->gpiocmd) + return; + writel((bank << 16) | (pin << 8) | mode, m->gpiocmd); +} + +static void mikey_pinmux_uart(struct apple_mikeybus *m, bool on) +{ + u32 bank, pin, dir; + void __iomem *b; + + if (!m->gpio || !m->gpiocmd) + return; + + if (on) { + bank = MIKEY_GPIO_TX >> 3; + pin = MIKEY_GPIO_TX & 7; + b = m->gpio + 32 * bank; + dir = readl(b + 0x14); + writel(dir | BIT(pin), b + 0x14); + mikey_gpiocmd(m, MIKEY_GPIO_TX, 2); + + bank = MIKEY_GPIO_RX >> 3; + pin = MIKEY_GPIO_RX & 7; + b = m->gpio + 32 * bank; + dir = readl(b + 0x14); + writel(dir | BIT(pin), b + 0x14); + mikey_gpiocmd(m, MIKEY_GPIO_RX, 2); + m->pinmux_on = true; + } else { + bank = MIKEY_GPIO_TX >> 3; + pin = MIKEY_GPIO_TX & 7; + b = m->gpio + 32 * bank; + dir = readl(b + 0x14); + writel(dir & ~BIT(pin), b + 0x14); + mikey_gpiocmd(m, MIKEY_GPIO_TX, 0); + + bank = MIKEY_GPIO_RX >> 3; + pin = MIKEY_GPIO_RX & 7; + b = m->gpio + 32 * bank; + dir = readl(b + 0x14); + writel(dir & ~BIT(pin), b + 0x14); + mikey_gpiocmd(m, MIKEY_GPIO_RX, 0); + m->pinmux_on = false; + } +} + +/* -------------------- RX / state -------------------- */ + +static void mikey_rx_byte_locked(struct apple_mikeybus *m, u8 b) +{ + mikey_ring_put(&m->rx_raw, b); + mikey_ring_put(&m->rx_task_stream, b); + + if (b == 0xaa) { + mikey_ring_put(&m->rx_task_stream, 0x01); + m->aa_stuff_count++; + } + + m->rx_bytes++; +} + +/* + * One byte of the channel 4 remote stream. + * + * Treated as a held-button bitmap: bits set now that were not set before are + * presses, bits that cleared are releases, and 0x00 releases everything -- + * the AllUp case. Reporting is edge-driven, so a repeated identical byte + * costs nothing and a dropped byte self-corrects on the next one. + */ +static void mikey_remote_byte_locked(struct apple_mikeybus *m, u8 b) +{ + u8 changed; + unsigned int bit; + + mikey_ring_put(&m->remote_raw, b); + m->remote_bytes++; + + if (!m->input) + return; + + changed = b ^ m->last_buttons; + if (!changed) + return; + + for (bit = 0; bit < MIKEY_BUTTON_BITS; bit++) { + int code = button_map[bit]; + + if (!code || !(changed & BIT(bit))) + continue; + input_report_key(m->input, code, !!(b & BIT(bit))); + m->button_events++; + } + input_sync(m->input); + + if (!b && m->last_buttons) + m->all_up_events++; + + m->last_buttons = b; +} + +static void mikey_report_state_locked(struct apple_mikeybus *m, + const char *reason) +{ + if (!m->auto_report) + return; + + if (m->plugged == m->last_reported_plugged && + m->model == m->last_reported_model) + return; + + if (m->plugged && m->last_reported_plugged && + m->model != m->last_reported_model) { + dev_info(m->dev, + "headset changed: %s sample=0x%02x modifier=%u reason=%s\n", + mikey_model_name(m->model), m->model_sample, + m->model_modifier, reason); + } else if (m->plugged) { + dev_info(m->dev, + "headset plugged: %s sample=0x%02x modifier=%u reason=%s\n", + mikey_model_name(m->model), m->model_sample, + m->model_modifier, reason); + m->plug_events++; + } else { + dev_info(m->dev, + "headset unplugged: %s sample=0x%02x modifier=%u reason=%s\n", + mikey_model_name(m->model), m->model_sample, + m->model_modifier, reason); + m->unplug_events++; + } + + m->last_reported_plugged = m->plugged; + m->last_reported_model = m->model; +} + +static void mikey_apply_model_sample_locked(struct apple_mikeybus *m, + u8 sample, + const char *reason) +{ + bool plugged; + u8 model; + + if (sample == MIKEY_SAMPLE_REMAP_FROM && m->model_modifier == 0) + sample = MIKEY_SAMPLE_DEFAULT; + + if (m->force_plugged) { + plugged = true; + model = (m->force_model >= 0) ? (u8)m->force_model : sample; + } else if (m->force_model >= 0) { + sample = (u8)m->force_model; + model = sample; + plugged = mikey_sample_is_plugged(sample); + } else { + model = sample; + plugged = mikey_sample_is_plugged(sample); + } + + if (m->model_sample != sample || m->model != model || + m->plugged != plugged) + m->model_changes++; + + m->model_sample = sample; + m->model = model; + m->plugged = plugged; + + mikey_report_state_locked(m, reason); +} + +/* -------------------- lower / backend packets -------------------- */ + +static void mikey_handle_lower_packet_locked(struct apple_mikeybus *m, + const u8 *pkt, size_t len) +{ + u8 type; + u8 v; + size_t i; + u8 count; + + if (len < 2) + return; + + m->lower_packets++; + type = pkt[1]; + + switch (type) { + case MIKEY_PKT_RX_BYTES: + if (len < 3 || pkt[0] < 3 || pkt[0] > len) + return; + + count = pkt[0] - 3; + + /* + * pkt[2] is the channel and was being thrown away, so the + * headset-model stream on channel 3 and the remote stream on + * channel 4 were interleaved into one buffer. They are + * different protocols; anything reading the mixed result is + * parsing two things at once. Keep feeding both to the raw + * ring for tracing, but route channel 4 to the remote decoder. + */ + for (i = 0; i < count; i++) { + u8 payload = pkt[3 + i]; + + mikey_rx_byte_locked(m, payload); + if (pkt[2] == MIKEY_CH_READ) + mikey_remote_byte_locked(m, payload); + } + + m->lower_rx70_packets++; + break; + + case MIKEY_PKT_IGNORED_74: + break; + + case MIKEY_PKT_STATUS_76: + case MIKEY_PKT_STATUS_8A: + if (len < 4) + return; + + v = pkt[3]; + + if (v & 0x10) + m->rx_status_shadow = 0; + else if (v & 0x20) + m->rx_status_shadow = 0x80; + + m->lower_status_packets++; + break; + + default: + dev_dbg(m->dev, "unknown lower packet type=0x%02x len=%zu\n", + type, len); + break; + } +} + +static bool model_completion_byte_plausible(u8 b) +{ + switch (b) { + case 0x01 ... 0x0b: + case 0x0d ... 0x10: + case 0x64: + return true; + default: + return false; + } +} + +static void mikey_handle_model_completion_candidate_locked( + struct apple_mikeybus *m, const u8 *pkt, size_t len) +{ + u8 sample = 0xff; + + if (!accept_case3_model) + return; + + /* + * Conservative candidate extraction: + * Try packet[3], packet[4], packet[1] in that order. + * + * Why not hard-code one forever? + * The decomp proves the sample global is written in the case-3 lane, + * but the current dump does not yet show enough local context to + * name the exact source byte with full confidence. + */ + if (len > 3 && model_completion_byte_plausible(pkt[3])) + sample = pkt[3]; + else if (len > 4 && model_completion_byte_plausible(pkt[4])) + sample = pkt[4]; + else if (len > 1 && model_completion_byte_plausible(pkt[1])) + sample = pkt[1]; + + if (sample == 0xff) { + dev_dbg(m->dev, "case3 model candidate ignored len=%zu\n", len); + return; + } + + m->resistor_backend_ready = true; + mikey_apply_model_sample_locked(m, sample, "case3"); +} + +static void mikey_handle_presence_candidate_locked(struct apple_mikeybus *m, + const u8 *pkt, size_t len) +{ + u8 code; + u8 arg = 0; + + if (len < 2) + return; + + code = pkt[1]; + if (len > 4) + arg = pkt[4]; + + m->presence_packets++; + + if (code == 0x7e) { + m->model_modifier = arg & 1; + dev_info(m->dev, + "presence event: code=0x7e arg=0x%02x modifier=%u\n", + arg, m->model_modifier); + return; + } + + if (code == 0x8a) { + dev_info(m->dev, "presence event: code=0x8a\n"); + return; + } + + dev_dbg(m->dev, "presence candidate code=0x%02x arg=0x%02x\n", + code, arg); +} + +static void mikey_handle_backend_packet_locked(struct apple_mikeybus *m, + const u8 *pkt, size_t len) +{ + u8 cls; + + if (len < 3) + return; + + cls = pkt[2]; + + switch (cls) { + case 3: + mikey_handle_model_completion_candidate_locked(m, pkt, len); + break; + + case 4: + mikey_handle_lower_packet_locked(m, pkt, len); + break; + + case 5: + dev_dbg(m->dev, "backend case5 len=%zu\n", len); + break; + + case 6: + mikey_handle_presence_candidate_locked(m, pkt, len); + break; + + case 16: + dev_dbg(m->dev, "backend case16 len=%zu\n", len); + break; + + default: + dev_dbg(m->dev, "backend packet class=%u len=%zu\n", cls, len); + break; + } +} + +/* -------------------- active probe / poll -------------------- */ + +static int mikey_send_active_probe(struct apple_mikeybus *m) +{ + /* + * Do not write this blindly unless the lower transport framing is known. + * + * RetailOS command-object shape: + * cmd[6] = 3 + * cmd[7] = 0x8D + * cmd[8] = 3 + * cmd[3] = 0xFF + * + * The Linux serial transport may not accept the command object bytes + * directly. Keep this disabled until glass capture proves framing. + */ + u8 cmd[] = { + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x03, 0x8d, 0x03 + }; + int ret; + + if (!m->active_probe) + return -EOPNOTSUPP; + + m->active_probe_count++; + + if (!m->serdev || !m->uart_opened) + return -ENODEV; + + m->decomp_channel_mask_shadow |= BIT(MIKEY_CH_RESISTOR); + + ret = serdev_device_write_buf(m->serdev, cmd, sizeof(cmd)); + if (ret < 0) + return ret; + if (ret != sizeof(cmd)) + return -EIO; + return 0; +} + +static void mikey_poll_work(struct work_struct *work) +{ + struct apple_mikeybus *m = + container_of(to_delayed_work(work), struct apple_mikeybus, + poll_work); + int ret; + int interval; + + mutex_lock(&m->lock); + + if (m->force_plugged || m->force_model >= 0) { + u8 sample = (m->force_model >= 0) ? + (u8)m->force_model : MIKEY_SAMPLE_DEFAULT; + mikey_apply_model_sample_locked(m, sample, "force"); + goto out; + } + + if (m->active_probe) { + ret = mikey_send_active_probe(m); + if (ret < 0) { + m->active_probe_fail_count++; + dev_dbg(m->dev, "active probe failed ret=%d\n", ret); + } + } + +out: + mutex_unlock(&m->lock); + + interval = poll_ms; + if (interval < 100) + interval = 100; + schedule_delayed_work(&m->poll_work, msecs_to_jiffies(interval)); +} + +/* -------------------- hex inject parser -------------------- */ + +static int mikey_parse_hex_bytes(const char *buf, size_t count, + u8 *out, size_t max, size_t *out_len) +{ + const char *p = buf; + const char *end = buf + count; + size_t n = 0; + + while (p < end) { + u8 byte; + char tok[32]; + size_t i = 0; + + while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || + *p == '\r' || *p == ',')) + p++; + if (p >= end) + break; + + while (p < end && *p != ' ' && *p != '\t' && *p != '\n' && + *p != '\r' && *p != ',' && i + 1 < sizeof(tok)) + tok[i++] = *p++; + tok[i] = '\0'; + if (!i) + break; + + /* Inject ABI is hex bytes (guide: "06 70 00 aa 55 33"). */ + if (kstrtou8(tok, 16, &byte)) + return -EINVAL; + if (n >= max) + return -EINVAL; + out[n++] = byte; + } + + *out_len = n; + return n ? 0 : -EINVAL; +} + +/* -------------------- serdev -------------------- */ + +static size_t mikey_serdev_receive(struct serdev_device *serdev, + const u8 *data, size_t count) +{ + struct apple_mikeybus *m = serdev_device_get_drvdata(serdev); + size_t i; + + if (!m) + return count; + + mutex_lock(&m->lock); + for (i = 0; i < count; i++) + mikey_rx_byte_locked(m, data[i]); + mutex_unlock(&m->lock); + + dev_dbg(&serdev->dev, "Mikey RX %zu: %*ph\n", count, + (int)min(count, (size_t)16), data); + + return count; +} + +static const struct serdev_device_ops mikey_serdev_ops = { + .receive_buf = mikey_serdev_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +/* -------------------- sysfs -------------------- */ + +static ssize_t info_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + ssize_t n; + + mutex_lock(&m->lock); + n = sysfs_emit(buf, + "N31 MikeyBus\n" + "status=YELLOW\n" + "read_path=channel4 command 9/0x71\n" + "resistor_path=channel3 command 3/0x8D\n" + "auto_report=%d\n" + "active_probe=%d\n" + "plugged=%d\n" + "model=0x%02x %s\n" + "model_sample=0x%02x\n" + "model_modifier=%u\n" + "force_plugged=%d\n" + "force_model=%d\n" + "resistor_backend_ready=%d\n" + "accept_case3_model=%d\n" + "baud=%d\n" + "serdev=%s\n" + "uart_opened=%d\n" + "pinmux_on=%d\n" + "button_decode=raw-only\n", + m->auto_report, m->active_probe, m->plugged, + m->model, mikey_model_name(m->model), + m->model_sample, m->model_modifier, + m->force_plugged, m->force_model, + m->resistor_backend_ready, accept_case3_model, + m->baud, m->serdev ? "yes" : "no", + m->uart_opened, m->pinmux_on); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(info); + +static ssize_t plugged_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + bool plugged; + + mutex_lock(&m->lock); + plugged = m->plugged; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "%d\n", plugged); +} +static DEVICE_ATTR_RO(plugged); + +static ssize_t model_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 model; + + mutex_lock(&m->lock); + model = m->model; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "0x%02x\n", model); +} +static DEVICE_ATTR_RO(model); + +static ssize_t model_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 model; + + mutex_lock(&m->lock); + model = m->model; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "%s\n", mikey_model_name(model)); +} +static DEVICE_ATTR_RO(model_name); + +static ssize_t model_sample_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 sample; + + mutex_lock(&m->lock); + sample = m->model_sample; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "0x%02x\n", sample); +} +static DEVICE_ATTR_RO(model_sample); + +static ssize_t model_sample_inject_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + if (val > 0xff) + return -EINVAL; + + mutex_lock(&m->lock); + mikey_apply_model_sample_locked(m, (u8)val, "sample_inject"); + mutex_unlock(&m->lock); + + return count; +} +static DEVICE_ATTR_WO(model_sample_inject); + +static ssize_t model_modifier_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 mod; + + mutex_lock(&m->lock); + mod = m->model_modifier; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "%u\n", mod); +} + +static ssize_t model_modifier_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + if (val > 1) + return -EINVAL; + + mutex_lock(&m->lock); + m->model_modifier = (u8)val; + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_RW(model_modifier); + +static ssize_t force_plugged_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", m->force_plugged); +} + +static ssize_t force_plugged_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + bool v; + int ret; + + ret = kstrtobool(buf, &v); + if (ret) + return ret; + + mutex_lock(&m->lock); + m->force_plugged = v; + force_plugged = v; + if (v) { + u8 sample = (m->force_model >= 0) ? + (u8)m->force_model : MIKEY_SAMPLE_DEFAULT; + mikey_apply_model_sample_locked(m, sample, "force"); + } else if (m->force_model < 0) { + mikey_apply_model_sample_locked(m, m->model_sample, "force_clear"); + } + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_RW(force_plugged); + +static ssize_t force_model_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", m->force_model); +} + +static ssize_t force_model_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + if (val < -1 || val > 0xff) + return -EINVAL; + + mutex_lock(&m->lock); + m->force_model = val; + force_model = val; + if (m->force_plugged || val >= 0) { + u8 sample = (val >= 0) ? (u8)val : MIKEY_SAMPLE_DEFAULT; + + mikey_apply_model_sample_locked(m, sample, "force"); + } + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_RW(force_model); + +static ssize_t auto_report_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", m->auto_report); +} + +static ssize_t auto_report_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + bool v; + int ret; + + ret = kstrtobool(buf, &v); + if (ret) + return ret; + + mutex_lock(&m->lock); + m->auto_report = v; + auto_report = v; + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_RW(auto_report); + +static ssize_t active_probe_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", m->active_probe); +} + +static ssize_t active_probe_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + bool v; + int ret; + + ret = kstrtobool(buf, &v); + if (ret) + return ret; + + mutex_lock(&m->lock); + m->active_probe = v; + active_probe = v; + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_RW(active_probe); + +static ssize_t resistor_backend_ready_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + bool ready; + + mutex_lock(&m->lock); + ready = m->resistor_backend_ready; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "%d\n", ready); +} +static DEVICE_ATTR_RO(resistor_backend_ready); + +static ssize_t decomp_channel_mask_shadow_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u32 mask; + + mutex_lock(&m->lock); + mask = m->decomp_channel_mask_shadow; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "0x%08x\n", mask); +} +static DEVICE_ATTR_RO(decomp_channel_mask_shadow); + +static ssize_t rx_status_shadow_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 st; + + mutex_lock(&m->lock); + st = m->rx_status_shadow; + mutex_unlock(&m->lock); + return sysfs_emit(buf, "0x%02x\n", st); +} +static DEVICE_ATTR_RO(rx_status_shadow); + +static ssize_t remote_raw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + size_t n; + + mutex_lock(&m->lock); + n = mikey_ring_dump_hex(&m->remote_raw, buf, PAGE_SIZE); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(remote_raw); + +/* + * Everything needed to pin down the bit order: the live bitmap, the map in + * force, and whether any of it is moving. Hold a button, read this. + */ +static ssize_t buttons_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + unsigned int i; + size_t n = 0; + + mutex_lock(&m->lock); + n += scnprintf(buf + n, PAGE_SIZE - n, + "held=0x%02x remote_bytes=%u events=%u all_up=%u\n", + m->last_buttons, m->remote_bytes, m->button_events, + m->all_up_events); + for (i = 0; i < MIKEY_BUTTON_BITS; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, + "bit%u keycode=%d held=%d\n", + i, button_map[i], + !!(m->last_buttons & BIT(i))); + n += scnprintf(buf + n, PAGE_SIZE - n, + "note: bit order is not attested by the decomp; confirm here\n"); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(buttons); + +static ssize_t rx_raw_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + size_t n; + + mutex_lock(&m->lock); + n = mikey_ring_dump_hex(&m->rx_raw, buf, PAGE_SIZE); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(rx_raw); + +static ssize_t rx_task_stream_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + size_t n; + + mutex_lock(&m->lock); + n = mikey_ring_dump_hex(&m->rx_task_stream, buf, PAGE_SIZE); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(rx_task_stream); + +static ssize_t rx_stats_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + ssize_t n; + + mutex_lock(&m->lock); + n = sysfs_emit(buf, + "rx_bytes=%u\n" + "aa_stuff_count=%u\n" + "lower_packets=%u\n" + "lower_rx70_packets=%u\n" + "lower_status_packets=%u\n" + "presence_packets=%u\n" + "model_changes=%u\n" + "plug_events=%u\n" + "unplug_events=%u\n" + "active_probe_count=%u\n" + "active_probe_fail_count=%u\n" + "rx_raw_drops=%u\n" + "rx_task_drops=%u\n", + m->rx_bytes, m->aa_stuff_count, + m->lower_packets, m->lower_rx70_packets, + m->lower_status_packets, m->presence_packets, + m->model_changes, m->plug_events, m->unplug_events, + m->active_probe_count, m->active_probe_fail_count, + m->rx_raw.drops, m->rx_task_stream.drops); + mutex_unlock(&m->lock); + return n; +} +static DEVICE_ATTR_RO(rx_stats); + +static ssize_t lower_packet_inject_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 pkt[MIKEY_INJECT_MAX]; + size_t len; + int ret; + + ret = mikey_parse_hex_bytes(buf, count, pkt, sizeof(pkt), &len); + if (ret) + return ret; + + mutex_lock(&m->lock); + mikey_handle_lower_packet_locked(m, pkt, len); + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_WO(lower_packet_inject); + +static ssize_t backend_packet_inject_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + u8 pkt[MIKEY_INJECT_MAX]; + size_t len; + int ret; + + ret = mikey_parse_hex_bytes(buf, count, pkt, sizeof(pkt), &len); + if (ret) + return ret; + + mutex_lock(&m->lock); + mikey_handle_backend_packet_locked(m, pkt, len); + mutex_unlock(&m->lock); + return count; +} +static DEVICE_ATTR_WO(backend_packet_inject); + +static struct attribute *mikey_attrs[] = { + &dev_attr_info.attr, + &dev_attr_plugged.attr, + &dev_attr_model.attr, + &dev_attr_model_name.attr, + &dev_attr_model_sample.attr, + &dev_attr_model_sample_inject.attr, + &dev_attr_model_modifier.attr, + &dev_attr_force_plugged.attr, + &dev_attr_force_model.attr, + &dev_attr_auto_report.attr, + &dev_attr_active_probe.attr, + &dev_attr_resistor_backend_ready.attr, + &dev_attr_decomp_channel_mask_shadow.attr, + &dev_attr_remote_raw.attr, + &dev_attr_buttons.attr, + &dev_attr_rx_status_shadow.attr, + &dev_attr_rx_raw.attr, + &dev_attr_rx_task_stream.attr, + &dev_attr_rx_stats.attr, + &dev_attr_lower_packet_inject.attr, + &dev_attr_backend_packet_inject.attr, + NULL, +}; + +static const struct attribute_group mikey_attr_group = { + .attrs = mikey_attrs, +}; + +static const struct attribute_group *mikey_groups[] = { + &mikey_attr_group, + NULL, +}; + +/* + * The remote is an input device, so publish it as one. + * + * Without this the driver decoded a button stream into nothing: no evdev + * node, so no key ever reached userspace no matter how well the packets + * parsed. devm-managed, so teardown follows the device. + */ +static int mikey_register_input(struct apple_mikeybus *m) +{ + struct input_dev *in; + unsigned int i; + int ret; + + in = devm_input_allocate_device(m->dev); + if (!in) + return -ENOMEM; + + in->name = "Apple MikeyBus Remote"; + in->phys = "mikeybus/input0"; + in->id.bustype = BUS_HOST; + + for (i = 0; i < MIKEY_BUTTON_BITS; i++) + if (button_map[i]) + input_set_capability(in, EV_KEY, button_map[i]); + + ret = input_register_device(in); + if (ret) + return ret; + + m->input = in; + return 0; +} + +static int mikey_create_sysfs(struct apple_mikeybus *m) +{ + return sysfs_create_groups(&m->dev->kobj, mikey_groups); +} + +static void mikey_remove_sysfs(struct apple_mikeybus *m) +{ + sysfs_remove_groups(&m->dev->kobj, mikey_groups); +} + +/* -------------------- bind / unbind -------------------- */ + +static int mikey_bind(struct device *dev, struct serdev_device *serdev) +{ + struct apple_mikeybus *m; + int ret; + + m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL); + if (!m) + return -ENOMEM; + + m->dev = dev; + m->serdev = serdev; + m->baud = baud; + m->auto_report = auto_report; + m->active_probe = active_probe; + /* + * The module parameter, OR the device tree. apple,force-plugged has + * been in the N31 dts since the port began and was read by nothing: + * this driver had no property reads at all, so the board said "assume + * a headset is plugged in until the resistor backend exists" and + * nothing was listening. + * + * Ported from n31/wip-local-backup-20260827 (8efa5c0e11ec). The rest + * of that commit is superseded: its codec half stopped jack detection + * returning -ENODEV, and this tree has since removed codec headset + * detection altogether. + */ + m->force_plugged = force_plugged || + device_property_read_bool(dev, "apple,force-plugged"); + m->force_model = force_model; + + mutex_init(&m->lock); + INIT_DELAYED_WORK(&m->poll_work, mikey_poll_work); + + m->model_sample = MIKEY_SAMPLE_DEFAULT; + m->model = MIKEY_SAMPLE_DEFAULT; + m->plugged = false; + m->last_reported_plugged = false; + m->last_reported_model = MIKEY_SAMPLE_DEFAULT; + + m->gpio = devm_ioremap(dev, GPIO_PHYS, 0x200); + m->gpiocmd = devm_ioremap(dev, GPIOCMD_PHYS, 4); + + dev_set_drvdata(dev, m); + + if (serdev) { + serdev_device_set_drvdata(serdev, m); + serdev_device_set_client_ops(serdev, &mikey_serdev_ops); + + ret = serdev_device_open(serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, m->baud); + serdev_device_set_flow_control(serdev, false); + m->uart_opened = true; + m->decomp_channel_mask_shadow |= BIT(MIKEY_CH_READ); + } + + mikey_pinmux_uart(m, true); + + /* + * Not fatal: the bus is still useful for headset detection and + * tracing even if the input node cannot be created. + */ + ret = mikey_register_input(m); + if (ret) + dev_warn(m->dev, "no input device: %d\n", ret); + + ret = mikey_create_sysfs(m); + if (ret) { + if (serdev && m->uart_opened) { + serdev_device_close(serdev); + m->uart_opened = false; + } + return ret; + } + + mutex_lock(&mikeybus_singleton_lock); + mikeybus_singleton = m; + mutex_unlock(&mikeybus_singleton_lock); + + dev_info(dev, + "N31 MikeyBus loaded (%s): auto_report=%d active_probe=%d force_plugged=%d force_model=%d baud=%d\n", + serdev ? "serdev" : "platform", + m->auto_report, m->active_probe, + m->force_plugged, m->force_model, m->baud); + + schedule_delayed_work(&m->poll_work, msecs_to_jiffies(100)); + return 0; +} + +static void mikey_unbind(struct device *dev) +{ + struct apple_mikeybus *m = dev_get_drvdata(dev); + + if (!m) + return; + + cancel_delayed_work_sync(&m->poll_work); + + mutex_lock(&mikeybus_singleton_lock); + if (mikeybus_singleton == m) + mikeybus_singleton = NULL; + mutex_unlock(&mikeybus_singleton_lock); + + mikey_remove_sysfs(m); + + mutex_lock(&m->lock); + if (m->uart_opened && m->serdev) { + serdev_device_close(m->serdev); + m->uart_opened = false; + } + mikey_pinmux_uart(m, false); + mutex_unlock(&m->lock); +} + +/* -------------------- platform fallback -------------------- */ + +static void mikey_ensure_plat(struct work_struct *work) +{ + struct device_node *uart_np, *mikey_np = NULL; + int ret; + + (void)work; + if (mikeybus_singleton) + return; + + /* + * Prefer serdev when uart2 is okay in DT. Only instantiate the + * platform fallback when uart2 is disabled / missing so exports + * and sysfs still exist for CS42 bring-up. + */ + uart_np = of_find_node_by_path("/soc/serial@3dc00000"); + if (uart_np && of_device_is_available(uart_np)) { + pr_info("apple-mikeybus: uart2 okay in DT — waiting on serdev\n"); + of_node_put(uart_np); + return; + } + if (uart_np) + mikey_np = of_get_child_by_name(uart_np, "mikeybus"); + + mikey_plat_pdev = platform_device_alloc("apple-mikeybus-plat", + PLATFORM_DEVID_NONE); + if (!mikey_plat_pdev) + goto out; + if (mikey_np) + mikey_plat_pdev->dev.of_node = of_node_get(mikey_np); + ret = platform_device_add(mikey_plat_pdev); + if (ret) { + pr_warn("apple-mikeybus: plat add %d\n", ret); + platform_device_put(mikey_plat_pdev); + mikey_plat_pdev = NULL; + } +out: + if (mikey_np) + of_node_put(mikey_np); + if (uart_np) + of_node_put(uart_np); +} + +static int mikey_serdev_probe(struct serdev_device *serdev) +{ + return mikey_bind(&serdev->dev, serdev); +} + +static void mikey_serdev_remove(struct serdev_device *serdev) +{ + mikey_unbind(&serdev->dev); +} + +static const struct of_device_id mikey_serdev_of_match[] = { + { .compatible = "apple,mikeybus" }, + { .compatible = "apple,n31-mikeybus" }, + { } +}; +MODULE_DEVICE_TABLE(of, mikey_serdev_of_match); + +static struct serdev_device_driver mikey_serdev_driver = { + .probe = mikey_serdev_probe, + .remove = mikey_serdev_remove, + .driver = { + .name = "apple-mikeybus", + .of_match_table = mikey_serdev_of_match, + }, +}; + +static int mikey_plat_probe(struct platform_device *pdev) +{ + return mikey_bind(&pdev->dev, NULL); +} + +static void mikey_plat_remove(struct platform_device *pdev) +{ + mikey_unbind(&pdev->dev); +} + +static struct platform_driver mikey_plat_driver = { + .probe = mikey_plat_probe, + .remove = mikey_plat_remove, + .driver = { + .name = "apple-mikeybus-plat", + }, +}; + +static int __init mikey_init(void) +{ + int ret; + + ret = serdev_device_driver_register(&mikey_serdev_driver); + if (ret) + return ret; + ret = platform_driver_register(&mikey_plat_driver); + if (ret) { + serdev_device_driver_unregister(&mikey_serdev_driver); + return ret; + } + schedule_work(&mikey_plat_work); + return 0; +} + +static void __exit mikey_exit(void) +{ + cancel_work_sync(&mikey_plat_work); + if (mikey_plat_pdev) { + platform_device_unregister(mikey_plat_pdev); + mikey_plat_pdev = NULL; + } + platform_driver_unregister(&mikey_plat_driver); + serdev_device_driver_unregister(&mikey_serdev_driver); +} + +module_init(mikey_init); +module_exit(mikey_exit); + +MODULE_DESCRIPTION("Apple N31 MikeyBus (serdev RX, model/jack state, CS42 exports)"); +MODULE_AUTHOR("FreeMyiPod"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:apple-mikeybus-plat"); diff --git a/drivers/misc/apple-tristar-cbtl1609.c b/drivers/misc/apple-tristar-cbtl1609.c new file mode 100755 index 00000000000000..e14cd7a4f38fed --- /dev/null +++ b/drivers/misc/apple-tristar-cbtl1609.c @@ -0,0 +1,1439 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Apple Lightning Tristar mux — NXP CBTL1609A1 (iPod nano 7G / N31) + * + * Transport (proven on glass 2026-08-27): I2C1 7-bit 0x1a + * (controller 3c900000). I2C0/IIC0 does not complete emcore-TX here — + * do not bind Tristar there. Public 0x34 write / 0x35 read is the + * 8-bit form of that address (nyansatan). + * + * Routing is IDBUS inside the chip, not Linux Dx register writes. + * RetailOS N31 RE observed zero Dx/mux I2C writes. The 0x75 accessory + * ID byte programs ACCx/Dx per the THS7383 tables (nyansatan; first-gen + * CBTL1608 is documented as backwards compatible with those tables). + * CBTL1609 is the nano7 first-gen part — same IDBUS decode, no invented + * I2C mux map. + * + * N31 analog 3.5 mm jack is CS42 + MikeyBus UART2 (accessoryMgr type 1, + * sub_35A4). It is not a Tristar Dx path. Lightning analog EarPods use + * ID 04 F1 00 00 00 00 (nyansatan) — a different connector. CS42 still + * refreshes Tristar on prepare so we log whether Lightning is USB, + * analog, idle, or I2C-echo; we do not mute the jack because USB is + * routed. + * + * OSOS software lane (sub_11C8C): TriStarID/VBUS/CONDET tasks wait on + * event source 13 and branch on raw bits 0x01/0x04/0x08/0x10/0x20. + * Those bits are not mapped to I2C registers (TODO RE). Linux keeps + * osos_event=0 and reports hardware observations separately. Do not + * treat dump-not-flat as OSOS bit 0x04. + * + * Register 0x11 is CBTL1610 "configuration status" (Lina/nyansatan). + * First-gen may NAK it or the bus may echo 0x35 — the read result is + * reported, never invented. + * + * accessoryMgr type 2 is TODO RE — logged, not stub-handled. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRISTAR_DUMP_LEN 0x40 +#define TRISTAR_LOG_LEN 64 + +/* OSOS sub_11C8C masks — names only. Not produced from I2C until RE maps them. */ +#define N31_TS_EVENT_01 BIT(0) +#define N31_TS_EVENT_04 BIT(2) +#define N31_TS_EVENT_08 BIT(3) +#define N31_TS_EVENT_10 BIT(4) +#define N31_TS_EVENT_20 BIT(5) + +/* nyansatan 0x75 first byte: ACCx[7:6] Dx[5:4] DATA[3:0] */ +#define TS_ID_ACCX(id0) (((id0) >> 6) & 3) +#define TS_ID_DX(id0) (((id0) >> 4) & 3) + +/* + * Control/status register map for this accessory switch. + * + * This part is the Lightning-side analogue mux: two "Dx" data lanes and two + * accessory lanes, each routed by a small field, plus an ID bus with a byte + * FIFO the accessory answers on. Until now this driver had no register + * semantics whatsoever -- it dumped 0x00..0x3F and pattern-matched the + * result -- which is why its audio-path line could only ever report + * "unmapped". + * + * Names here are ours. The field layout is what the hardware does. + */ +#define TS_REG_DX_SWITCH 0x01 /* both Dx lanes */ +#define TS_REG_ACC_SWITCH 0x02 /* both accessory lanes */ +#define TS_REG_CHARGE_DET 0x03 /* charger-detect source/sink */ +#define TS_REG_IDBUS_CTL 0x05 +#define TS_REG_DIG_ID 0x06 /* raw ID-pin levels */ +#define TS_REG_STATUS 0x0c +#define TS_REG_FIFO_TOP 0x3f /* ID FIFO; payload reads downward */ + +/* TS_REG_DX_SWITCH: lane 1 in bits 2:0, lane 2 in bits 6:4, override above each */ +#define TS_DX1_SEL GENMASK(2, 0) +#define TS_DX1_OVERRIDE BIT(3) +#define TS_DX2_SEL GENMASK(6, 4) +#define TS_DX2_OVERRIDE BIT(7) +#define TS_DX_SEL_OPEN 0 +#define TS_DX_SEL_USB 1 +#define TS_DX_SEL_UART0 2 +#define TS_DX_SEL_DIGITAL_ID 3 +#define TS_DX_SEL_BRICK_ID_P 4 +#define TS_DX_SEL_BRICK_ID_N 5 +/* 6 and 7 differ per lane: lane 1 gives a second USB and JTAG data, */ +/* lane 2 gives two further UARTs. */ +#define TS_DX1_SEL_USB_ALT 6 +#define TS_DX1_SEL_JTAG_DIO 7 +#define TS_DX2_SEL_UART2 6 +#define TS_DX2_SEL_UART1 7 + +/* TS_REG_ACC_SWITCH: lane 1 in bits 1:0, lane 2 in bits 4:3 */ +#define TS_ACC1_SEL GENMASK(1, 0) +#define TS_ACC1_OVERRIDE BIT(2) +#define TS_ACC2_SEL GENMASK(4, 3) +#define TS_ACC2_OVERRIDE BIT(5) +#define TS_ACC_SEL_OPEN 0 +#define TS_ACC_SEL_UART1 1 /* rx on lane 1, tx on lane 2 */ +#define TS_ACC_SEL_JTAG 2 /* data on lane 1, clock on lane 2 */ +#define TS_ACC_SEL_ACC_POWER 3 + +/* TS_REG_CHARGE_DET */ +#define TS_CHG_ID_SINK_EN BIT(3) +#define TS_CHG_SRC_SEL GENMASK(2, 0) +#define TS_CHG_SRC_OFF 0 +#define TS_CHG_SRC_DP1 1 +#define TS_CHG_SRC_DN1 2 +#define TS_CHG_SRC_DP2 3 +#define TS_CHG_SRC_DN2 4 + +/* TS_REG_IDBUS_CTL */ +#define TS_IDBUS_RESET BIT(3) +#define TS_IDBUS_BREAK BIT(2) +#define TS_IDBUS_REORIENT BIT(1) +#define TS_IDBUS_SINK_EN BIT(0) + +/* TS_REG_DIG_ID: raw levels on the two Dx and two accessory ID pins */ +#define TS_DIG_ID_DX1 BIT(3) +#define TS_DIG_ID_DX0 BIT(2) +#define TS_DIG_ID_ACC1 BIT(1) +#define TS_DIG_ID_ACC0 BIT(0) + +/* TS_REG_STATUS */ +#define TS_ST_IDBUS_CONNECTED BIT(7) +#define TS_ST_IDBUS_ORIENT BIT(6) +#define TS_ST_SWITCH_EN BIT(5) +#define TS_ST_HOST_RESET BIT(4) +#define TS_ST_OVP BIT(3) +#define TS_ST_CON_DET_N BIT(2) /* active low: set means NOT detected */ + +/* The accessory answers with this marker before the ID payload. */ +#define TS_ID_FIFO_MARKER 0x75 +#define TS_ID_PAYLOAD_LEN 6 + +struct tristar_id_sig { + u8 bytes[6]; + const char *name; +}; + +/* nyansatan Lightning ID table (HOSTID=1). */ +static const struct tristar_id_sig tristar_known_ids[] = { + { { 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00 }, "usb-cable" }, + { { 0x04, 0xf1, 0x00, 0x00, 0x00, 0x00 }, "lightning-analog" }, + { { 0x0b, 0xf0, 0x00, 0x00, 0x00, 0x00 }, "haywire-hdmi" }, + { { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 }, "dcsd-or-uart-charge" }, + { { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00 }, "kong-swd-idle" }, + { { 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00 }, "kong-swd-astris" }, + { { 0x20, 0x0e, 0x00, 0x00, 0x00, 0x00 }, "kanzi-swd-idle" }, + { { 0xa0, 0x0c, 0x00, 0x00, 0x00, 0x00 }, "kanzi-swd-astris" }, + { { 0x20, 0x00, 0x10, 0x00, 0x00, 0x00 }, "uart-charge" }, +}; + +struct apple_tristar { + struct i2c_client *client; + struct mutex lock; + struct delayed_work poll; + struct dentry *debug_root; + u8 last_dump[TRISTAR_DUMP_LEN]; + u8 read_reg; + u8 read_val; + bool dump_ok; + bool dump_flat; + bool i2c_echo; + int reg11_ret; + u8 reg11; + bool id_valid; + u8 id75[6]; + unsigned int id_off; + const char *id_name; + u8 accx; + u8 dx; + /* OSOS v36 — stays 0. Not synthesized from I2C. */ + u8 osos_event; + u8 prev_osos_event; + bool cf9_latch; + u8 cfa_state; + u32 seen_mask; + u32 polls; + u32 deltas; + u32 writes; + u32 i2c_fail_streak; + bool poll_disabled; + char log[TRISTAR_LOG_LEN][112]; + unsigned int log_head; + unsigned int log_count; +}; + +static struct apple_tristar *tristar_singleton; +static DEFINE_MUTEX(tristar_singleton_lock); + +int apple_tristar_refresh(void); +int apple_tristar_connected(void); +int apple_tristar_usb_routed(void); +int apple_tristar_lightning_analog(void); +int apple_tristar_vbus(void); +int apple_tristar_config_reg11(u8 *val); +void apple_tristar_log_audio_path(struct device *audio_dev); + +static bool read_only = true; +module_param(read_only, bool, 0644); +MODULE_PARM_DESC(read_only, + "Refuse I2C writes (default 1). IDBUS routing does not need them."); + +static bool unsafe_acks; +module_param(unsafe_acks, bool, 0600); +MODULE_PARM_DESC(unsafe_acks, + "Unused: OSOS event ack path not recovered. Default 0."); + +static bool unsafe_writes; +module_param(unsafe_writes, bool, 0600); +MODULE_PARM_DESC(unsafe_writes, + "Allow poke / DT init-sequence writes. Default 0."); + +/* + * Glass: I2C0 often -110 / echo 0x35. Polling reg0 every 250ms floods dmesg + * and burns the bus. Default off; enable only when I2C0 ACKs for real. + */ +static int poll_ms; +module_param(poll_ms, int, 0644); +MODULE_PARM_DESC(poll_ms, + "Status poll interval ms (0=off). Default 0 — I2C0 often times out."); + +static int tristar_read_reg(struct apple_tristar *ts, u8 reg, u8 *val) +{ + int ret = i2c_smbus_read_byte_data(ts->client, reg); + + if (ret < 0) + return ret; + *val = (u8)ret; + return 0; +} + +static int tristar_write_reg(struct apple_tristar *ts, u8 reg, u8 val) +{ + int ret; + + if (read_only && !unsafe_writes) + return -EPERM; + ret = i2c_smbus_write_byte_data(ts->client, reg, val); + if (!ret) + ts->writes++; + return ret; +} + +static bool tristar_dump_is_flat(const u8 *dump, size_t len) +{ + size_t i; + + for (i = 1; i < len; i++) { + if (dump[i] != dump[0]) + return false; + } + return true; +} + +static u8 tristar_read_addr_echo(struct apple_tristar *ts) +{ + return (u8)((ts->client->addr << 1) | 1); +} + +static const char *tristar_dx_usb_id0(u8 dx) +{ + switch (dx) { + case 0: + return "hiz"; + case 1: + return "usb0-on-dp1dn1"; + case 2: + return "usb0-on-dp1dn1+uart-on-dp2dn2"; + default: + return "hiz"; + } +} + +static const char *tristar_dx_usb_id1(u8 dx) +{ + switch (dx) { + case 0: + return "hiz"; + case 1: + return "usb0-on-dp2dn2"; + case 2: + return "usb0-on-dp1dn1+uart-on-dp2dn2"; + default: + return "hiz"; + } +} + +static const char *tristar_accx_name(u8 accx) +{ + switch (accx) { + case 0: + return "hiz-idbus"; + case 1: + return "uart1"; + case 2: + return "jtag-swd"; + default: + return "host-reset"; + } +} + +static bool tristar_usb_dp_from_dx(u8 dx) +{ + return dx == 1 || dx == 2; +} + +static bool tristar_is_lightning_analog(struct apple_tristar *ts) +{ + return ts->id_valid && ts->id_name && + !strcmp(ts->id_name, "lightning-analog"); +} + +static const char *tristar_id_label(struct apple_tristar *ts) +{ + if (ts->i2c_echo) + return "i2c-echo"; + if (ts->id_name) + return ts->id_name; + if (!ts->dump_ok) + return "unread"; + if (ts->dump_flat) + return "idle"; + return "unknown"; +} + +static void tristar_log_line(struct apple_tristar *ts, const char *why) +{ + unsigned int i = ts->log_head; + + snprintf(ts->log[i], sizeof(ts->log[i]), + "%s osos=0x%02x (unmapped) flat=%d echo=%d id=%s accx=%u dx=%u r11=%s%02x", + why, ts->osos_event, ts->dump_flat, ts->i2c_echo, + tristar_id_label(ts), ts->accx, ts->dx, + ts->reg11_ret ? "ERR" : "", + ts->reg11_ret ? 0 : ts->reg11); + ts->log_head = (i + 1) % TRISTAR_LOG_LEN; + if (ts->log_count < TRISTAR_LOG_LEN) + ts->log_count++; +} + +static void tristar_clear_id(struct apple_tristar *ts) +{ + ts->id_valid = false; + ts->id_name = NULL; + ts->id_off = 0; + memset(ts->id75, 0, sizeof(ts->id75)); + ts->accx = 0; + ts->dx = 0; +} + +static void tristar_find_id(struct apple_tristar *ts) +{ + unsigned int s, off; + const struct tristar_id_sig *sig; + + tristar_clear_id(ts); + + if (!ts->dump_ok || ts->dump_flat || ts->i2c_echo) + return; + + for (s = 0; s < ARRAY_SIZE(tristar_known_ids); s++) { + sig = &tristar_known_ids[s]; + for (off = 0; off + 6 <= TRISTAR_DUMP_LEN; off++) { + if (memcmp(ts->last_dump + off, sig->bytes, 6)) + continue; + memcpy(ts->id75, sig->bytes, 6); + ts->id_name = sig->name; + ts->id_valid = true; + ts->id_off = off; + ts->accx = TS_ID_ACCX(sig->bytes[0]); + ts->dx = TS_ID_DX(sig->bytes[0]); + dev_info(&ts->client->dev, + "tristar: IDBUS id %s @dump+0x%x accx=%u(%s) dx=%u id0=%s id1=%s\n", + sig->name, off, ts->accx, + tristar_accx_name(ts->accx), ts->dx, + tristar_dx_usb_id0(ts->dx), + tristar_dx_usb_id1(ts->dx)); + return; + } + } +} + +static int tristar_refresh_locked(struct apple_tristar *ts, const char *why) +{ + int i, ret; + u8 v; + u8 prior[TRISTAR_DUMP_LEN]; + unsigned int n = 0; + u8 echo = tristar_read_addr_echo(ts); + bool echo_now; + const char *prev_id; + + memcpy(prior, ts->last_dump, sizeof(prior)); + prev_id = tristar_id_label(ts); + + for (i = 0; i < TRISTAR_DUMP_LEN; i++) { + ret = tristar_read_reg(ts, (u8)i, &v); + if (ret) { + ts->dump_ok = false; + ts->i2c_echo = false; + tristar_clear_id(ts); + ts->reg11_ret = ret; + ts->i2c_fail_streak++; + /* + * One line per outage, not one per poll. I2C0 -110 is + * common until the mux/bus is actually live. Kill poll + * immediately so a leftover poll_ms=250 never storms. + */ + ts->poll_disabled = true; + if (ts->i2c_fail_streak == 1) + dev_warn(&ts->client->dev, + "tristar: I2C read 0x%02x failed %d (poll off until bus recovers)\n", + i, ret); + return ret; + } + ts->i2c_fail_streak = 0; + ts->last_dump[i] = v; + /* + * I2C0 often echoes the 8-bit read address (0x35). Four + * identical echo bytes means the dump is not chip SRAM — + * stop before a 64-register storm. + */ + if (i == 3 && ts->last_dump[0] == echo && + ts->last_dump[1] == echo && + ts->last_dump[2] == echo && + ts->last_dump[3] == echo) { + memset(ts->last_dump, echo, TRISTAR_DUMP_LEN); + break; + } + } + ts->dump_ok = true; + echo_now = tristar_dump_is_flat(ts->last_dump, TRISTAR_DUMP_LEN) && + ts->last_dump[0] == echo; + ts->i2c_echo = echo_now; + ts->dump_flat = tristar_dump_is_flat(ts->last_dump, TRISTAR_DUMP_LEN); + + if (echo_now) { + ts->reg11_ret = -ENOTSUPP; + ts->reg11 = echo; + tristar_clear_id(ts); + } else { + ts->reg11 = ts->last_dump[0x11]; + ts->reg11_ret = 0; + tristar_find_id(ts); + } + + ts->polls++; + for (i = 0; i < TRISTAR_DUMP_LEN; i++) { + if (prior[i] != ts->last_dump[i]) + n++; + } + ts->deltas += n; + + if (n || strcmp(prev_id, tristar_id_label(ts))) { + dev_info(&ts->client->dev, + "tristar: %s osos=0x00 (unmapped) flat=%d echo=%d deltas=%u id=%s r11=%d/%02x CONDET=%s VBUS=ENOTSUPP accmgr_type2=TODO\n", + why, ts->dump_flat, ts->i2c_echo, n, + tristar_id_label(ts), ts->reg11_ret, + ts->reg11_ret ? 0 : ts->reg11, + (!ts->dump_ok || ts->i2c_echo) ? "unknown" : + (ts->dump_flat ? "idle" : "not-flat")); + if (!ts->dump_flat && !ts->i2c_echo && !ts->id_valid) + dev_info(&ts->client->dev, + "tristar: unknown non-flat dump[0..15] %*ph\n", + 16, ts->last_dump); + tristar_log_line(ts, why); + } + return 0; +} + +static int tristar_poll_cheap_locked(struct apple_tristar *ts) +{ + u8 v, echo = tristar_read_addr_echo(ts); + int ret; + + if (ts->poll_disabled) + return -ENOTSUPP; + + ret = tristar_read_reg(ts, 0x00, &v); + if (ret) { + ts->i2c_fail_streak++; + ts->dump_ok = false; + ts->poll_disabled = true; + if (ts->i2c_fail_streak == 1) + dev_warn(&ts->client->dev, + "tristar: poll read 0x00 failed %d — disabling poll\n", + ret); + /* Do not call full 64-reg refresh on NACK — that is the storm. */ + return ret; + } + ts->i2c_fail_streak = 0; + if (ts->i2c_echo && v == echo) { + ts->polls++; + return 0; + } + if (ts->dump_ok && ts->dump_flat && !ts->i2c_echo && v == ts->last_dump[0]) { + ts->polls++; + return 0; + } + return tristar_refresh_locked(ts, "poll"); +} + +static int tristar_refresh(struct apple_tristar *ts, const char *why) +{ + int ret; + + mutex_lock(&ts->lock); + ret = tristar_refresh_locked(ts, why); + mutex_unlock(&ts->lock); + return ret; +} + +int apple_tristar_refresh(void) +{ + struct apple_tristar *ts; + int ret; + + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + ret = tristar_refresh(ts, "export"); + mutex_unlock(&tristar_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_tristar_refresh); + +int apple_tristar_connected(void) +{ + struct apple_tristar *ts; + int ret; + + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + mutex_lock(&ts->lock); + if (!ts->dump_ok) + ret = -EIO; + else if (ts->i2c_echo) + ret = -ENOTSUPP; + else + ret = ts->dump_flat ? 0 : 1; + mutex_unlock(&ts->lock); + mutex_unlock(&tristar_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_tristar_connected); + +int apple_tristar_usb_routed(void) +{ + struct apple_tristar *ts; + int ret; + + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + mutex_lock(&ts->lock); + if (!ts->dump_ok) + ret = -EIO; + else if (ts->i2c_echo) + ret = -ENOTSUPP; + else if (!ts->id_valid) + ret = 0; + else + ret = tristar_usb_dp_from_dx(ts->dx) ? 1 : 0; + mutex_unlock(&ts->lock); + mutex_unlock(&tristar_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_tristar_usb_routed); + +int apple_tristar_lightning_analog(void) +{ + struct apple_tristar *ts; + int ret; + + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + mutex_lock(&ts->lock); + if (!ts->dump_ok) + ret = -EIO; + else if (ts->i2c_echo) + ret = -ENOTSUPP; + else + ret = tristar_is_lightning_analog(ts) ? 1 : 0; + mutex_unlock(&ts->lock); + mutex_unlock(&tristar_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_tristar_lightning_analog); + +int apple_tristar_vbus(void) +{ + mutex_lock(&tristar_singleton_lock); + if (!tristar_singleton) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + mutex_unlock(&tristar_singleton_lock); + /* TriStarVBUSProcessTask exists; I2C register is TODO RE. */ + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(apple_tristar_vbus); + +int apple_tristar_config_reg11(u8 *val) +{ + struct apple_tristar *ts; + int ret; + + if (!val) + return -EINVAL; + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + return -ENODEV; + } + mutex_lock(&ts->lock); + if (!ts->dump_ok) + ret = -EIO; + else if (ts->i2c_echo) + ret = -ENOTSUPP; + else if (ts->reg11_ret) + ret = ts->reg11_ret; + else { + *val = ts->reg11; + ret = 0; + } + mutex_unlock(&ts->lock); + mutex_unlock(&tristar_singleton_lock); + return ret; +} +EXPORT_SYMBOL_GPL(apple_tristar_config_reg11); + +/* Human-readable routing for one Dx lane. Lanes 1 and 2 differ at 6 and 7. */ +static const char *tristar_dx_route(u8 sel, bool lane2) +{ + switch (sel) { + case TS_DX_SEL_OPEN: return "open"; + case TS_DX_SEL_USB: return "usb"; + case TS_DX_SEL_UART0: return "uart0"; + case TS_DX_SEL_DIGITAL_ID: return "digital-id"; + case TS_DX_SEL_BRICK_ID_P: return "brick-id+"; + case TS_DX_SEL_BRICK_ID_N: return "brick-id-"; + case 6: return lane2 ? "uart2" : "usb-alt"; + case 7: return lane2 ? "uart1" : "jtag-dio"; + } + return "?"; +} + +static const char *tristar_acc_route(u8 sel, bool lane2) +{ + switch (sel) { + case TS_ACC_SEL_OPEN: return "open"; + case TS_ACC_SEL_UART1: return lane2 ? "uart1-tx" : "uart1-rx"; + case TS_ACC_SEL_JTAG: return lane2 ? "jtag-clk" : "jtag-dio"; + case TS_ACC_SEL_ACC_POWER: return "acc-power"; + } + return "?"; +} + +/* + * Read the accessory ID off the ID bus. + * + * The accessory answers with a fixed marker byte at the top of the FIFO, + * followed by the payload in the registers *below* it, read downward. If the + * marker is not there the bus may simply be the wrong way round, and a + * re-orient plus a settle makes it answer. + * + * This driver never implemented any of that -- it dumped 0x00..0x3F and + * pattern-matched, so it could not distinguish "no accessory" from "cable in + * the other orientation". + * + * The re-orient step is a write, so it is skipped when the driver is in its + * default read-only mode; the caller is told which case it got. + */ +static int tristar_read_accessory_id(struct apple_tristar *ts, + u8 id[TS_ID_PAYLOAD_LEN]) +{ + u8 status = 0, marker = 0; + unsigned int i; + int ret; + + ret = tristar_read_reg(ts, TS_REG_STATUS, &status); + if (ret) + return ret; + + if ((status & TS_ST_CON_DET_N) || !(status & TS_ST_IDBUS_CONNECTED)) + return -ENODEV; + + ret = tristar_read_reg(ts, TS_REG_FIFO_TOP, &marker); + if (ret) + return ret; + + if (marker != TS_ID_FIFO_MARKER) { + if (read_only) { + dev_dbg(&ts->client->dev, + "id: marker 0x%02x, re-orient needs a write (read_only=1)\n", + marker); + return -EAGAIN; + } + ret = i2c_smbus_write_byte_data(ts->client, TS_REG_IDBUS_CTL, + TS_IDBUS_REORIENT); + if (ret < 0) + return ret; + msleep(100); + ret = tristar_read_reg(ts, TS_REG_FIFO_TOP, &marker); + if (ret) + return ret; + } + + if (marker != TS_ID_FIFO_MARKER) + return -EAGAIN; + + for (i = 0; i < TS_ID_PAYLOAD_LEN; i++) { + ret = tristar_read_reg(ts, TS_REG_FIFO_TOP - (i + 1), &id[i]); + if (ret) + return ret; + } + return 0; +} + +void apple_tristar_log_audio_path(struct device *audio_dev) +{ + struct apple_tristar *ts; + + if (!audio_dev) + return; + + mutex_lock(&tristar_singleton_lock); + ts = tristar_singleton; + if (!ts) { + mutex_unlock(&tristar_singleton_lock); + dev_info(audio_dev, + "tristar unbound — 3.5mm is Mikey/CS42, not Lightning Dx\n"); + return; + } + mutex_lock(&ts->lock); + tristar_refresh_locked(ts, "audio-path"); + { + u8 status = 0, dx = 0, acc = 0; + u8 id[TS_ID_PAYLOAD_LEN] = {}; + int idret; + + /* + * Report what the switches are actually doing. + * + * Decoded switch state, so dev_dbg: this whole block runs on + * every stream start and is a snapshot for diagnosis, not an + * event. Dynamic debug brings it back per call site. + */ + if (!tristar_read_reg(ts, TS_REG_STATUS, &status) && + !tristar_read_reg(ts, TS_REG_DX_SWITCH, &dx) && + !tristar_read_reg(ts, TS_REG_ACC_SWITCH, &acc)) { + dev_dbg(audio_dev, + "tristar: connected=%d orient=%d switch_en=%d con_det=%d ovp=%d\n", + !!(status & TS_ST_IDBUS_CONNECTED), + !!(status & TS_ST_IDBUS_ORIENT), + !!(status & TS_ST_SWITCH_EN), + !(status & TS_ST_CON_DET_N), + !!(status & TS_ST_OVP)); + dev_dbg(audio_dev, + "tristar: Dx1=%s%s Dx2=%s%s ACC1=%s%s ACC2=%s%s\n", + tristar_dx_route(FIELD_GET(TS_DX1_SEL, dx), false), + (dx & TS_DX1_OVERRIDE) ? "(ovrd)" : "", + tristar_dx_route(FIELD_GET(TS_DX2_SEL, dx), true), + (dx & TS_DX2_OVERRIDE) ? "(ovrd)" : "", + tristar_acc_route(FIELD_GET(TS_ACC1_SEL, acc), false), + (acc & TS_ACC1_OVERRIDE) ? "(ovrd)" : "", + tristar_acc_route(FIELD_GET(TS_ACC2_SEL, acc), true), + (acc & TS_ACC2_OVERRIDE) ? "(ovrd)" : ""); + } + + idret = tristar_read_accessory_id(ts, id); + if (!idret) + dev_dbg(audio_dev, "tristar: accessory id %*ph\n", + TS_ID_PAYLOAD_LEN, id); + else if (idret == -ENODEV) + dev_dbg(audio_dev, "tristar: nothing on the ID bus\n"); + else + dev_dbg(audio_dev, + "tristar: ID bus did not answer (%d)\n", idret); + } + + dev_dbg(audio_dev, + "tristar audio-path: lightning=%s flat=%d echo=%d usb_dx=%u accx=%u analog_lightning=%d — 3.5mm jack is CS42+Mikey not Dx; USB route does not mute jack\n", + tristar_id_label(ts), ts->dump_flat, ts->i2c_echo, ts->dx, + ts->accx, tristar_is_lightning_analog(ts)); + if (tristar_is_lightning_analog(ts)) + dev_warn(audio_dev, + "Lightning analog ID 04 F1 present — that is EarPods-on-Lightning, not the onboard 3.5mm CS42 jack\n"); + mutex_unlock(&ts->lock); + mutex_unlock(&tristar_singleton_lock); +} +EXPORT_SYMBOL_GPL(apple_tristar_log_audio_path); + +static void tristar_poll_work(struct work_struct *work) +{ + struct apple_tristar *ts = container_of(to_delayed_work(work), + struct apple_tristar, poll); + + mutex_lock(&ts->lock); + tristar_poll_cheap_locked(ts); + if (ts->poll_disabled) { + mutex_unlock(&ts->lock); + return; + } + mutex_unlock(&ts->lock); + if (poll_ms > 0) + schedule_delayed_work(&ts->poll, msecs_to_jiffies(poll_ms)); +} + +static int tristar_apply_init_sequence(struct apple_tristar *ts) +{ + struct device *dev = &ts->client->dev; + struct device_node *np = dev->of_node; + int n, i, ret; + u32 reg, val; + + if (!np) + return 0; + + n = of_property_count_u32_elems(np, "apple,init-sequence"); + if (n <= 0) + return 0; + if (!unsafe_writes && read_only) { + dev_warn(dev, + "tristar: apple,init-sequence present but read_only=1 unsafe_writes=0 — not applied\n"); + return 0; + } + if (n % 2) { + dev_err(dev, "apple,init-sequence must be reg,val pairs\n"); + return -EINVAL; + } + + for (i = 0; i < n; i += 2) { + of_property_read_u32_index(np, "apple,init-sequence", i, ®); + of_property_read_u32_index(np, "apple,init-sequence", i + 1, &val); + ret = tristar_write_reg(ts, (u8)reg, (u8)val); + if (ret) { + dev_err(dev, "init write 0x%02x=0x%02x failed: %d\n", + reg, val, ret); + return ret; + } + dev_info(dev, "init 0x%02x <= 0x%02x\n", reg, val); + udelay(100); + } + return 0; +} + +static ssize_t dump_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int i, n = 0; + + if (tristar_refresh(ts, "sysfs-dump")) + return -EIO; + mutex_lock(&ts->lock); + for (i = 0; i < TRISTAR_DUMP_LEN; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, "%02x%s", + ts->last_dump[i], + (i + 1) % 16 ? " " : "\n"); + mutex_unlock(&ts->lock); + return n; +} +static DEVICE_ATTR_RO(dump); + +static ssize_t poke_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + unsigned int reg, val; + int ret; + + if (sscanf(buf, "%x %x", ®, &val) != 2) + return -EINVAL; + if (reg > 0xff || val > 0xff) + return -EINVAL; + if (!unsafe_writes) + return -EPERM; + ret = tristar_write_reg(ts, (u8)reg, (u8)val); + if (ret) + return ret; + dev_info(dev, "poke 0x%02x <= 0x%02x\n", reg, val); + return count; +} +static DEVICE_ATTR_WO(poke); + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + const char *name; + + if (tristar_refresh(ts, "sysfs-mode")) + return sysfs_emit(buf, "unread\n"); + mutex_lock(&ts->lock); + name = tristar_id_label(ts); + mutex_unlock(&ts->lock); + return sysfs_emit(buf, "%s\n", name); +} +static DEVICE_ATTR_RO(mode); + +static ssize_t route_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int n; + + if (tristar_refresh(ts, "sysfs-route")) + return -EIO; + mutex_lock(&ts->lock); + n = sysfs_emit(buf, + "id=%s valid=%d accx=%u (%s) dx=%u id0=%s id1=%s usb=%d lightning_analog=%d echo=%d jack_3v5=cs42+mikey vbus=ENOTSUPP osos_event=0x00\n", + tristar_id_label(ts), ts->id_valid, ts->accx, + tristar_accx_name(ts->accx), ts->dx, + tristar_dx_usb_id0(ts->dx), tristar_dx_usb_id1(ts->dx), + ts->id_valid && tristar_usb_dp_from_dx(ts->dx), + tristar_is_lightning_analog(ts), ts->i2c_echo); + mutex_unlock(&ts->lock); + return n; +} +static DEVICE_ATTR_RO(route); + +static ssize_t audio_path_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int n; + + if (tristar_refresh(ts, "sysfs-audio-path")) + return -EIO; + mutex_lock(&ts->lock); + if (tristar_is_lightning_analog(ts)) + n = sysfs_emit(buf, + "selected=lightning-analog (04 F1) — not onboard 3.5mm\n"); + else + n = sysfs_emit(buf, + "selected=onboard-3.5mm cs42+mikey lightning=%s\n", + tristar_id_label(ts)); + mutex_unlock(&ts->lock); + return n; +} +static DEVICE_ATTR_RO(audio_path); + +static ssize_t vbus_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "ENOTSUPP (TriStarVBUSProcessTask I2C map TODO RE)\n"); +} +static DEVICE_ATTR_RO(vbus); + +static ssize_t read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + unsigned int reg; + u8 val; + int ret; + + if (kstrtouint(buf, 0, ®) || reg > 0xff) + return -EINVAL; + ret = tristar_read_reg(ts, (u8)reg, &val); + if (ret) + return ret; + ts->read_reg = (u8)reg; + ts->read_val = val; + return count; +} + +static ssize_t read_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "0x%02x\n", ts->read_val); +} +static DEVICE_ATTR_RW(read); + +static ssize_t value_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "0x%02x (reg 0x%02x)\n", + ts->read_val, ts->read_reg); +} +static DEVICE_ATTR_RO(value); + +static ssize_t verify_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int n; + + if (tristar_refresh(ts, "sysfs-verify")) + return sysfs_emit(buf, "FAIL read\n"); + mutex_lock(&ts->lock); + if (ts->i2c_echo) + n = sysfs_emit(buf, + "I2C_ECHO 0x%02x (8-bit read addr; chip SRAM not visible)\n", + ts->last_dump[0]); + else if (ts->dump_flat) + n = sysfs_emit(buf, "IDLE flat 0x%02x (no Lightning IDBUS accessory)\n", + ts->last_dump[0]); + else + n = sysfs_emit(buf, "STATUS_OK id=%s\n", tristar_id_label(ts)); + mutex_unlock(&ts->lock); + return n; +} +static DEVICE_ATTR_RO(verify); + +static ssize_t poll_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + if (buf[0] != '1' && buf[0] != 'y' && buf[0] != 'Y') + return -EINVAL; + ret = tristar_refresh(ts, "sysfs-poll"); + return ret ? ret : count; +} + +static ssize_t poll_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct apple_tristar *ts = i2c_get_clientdata(to_i2c_client(dev)); + int n; + + mutex_lock(&ts->lock); + n = sysfs_emit(buf, "flat=%d echo=%d last_ok=%d polls=%u id=%s\n", + ts->dump_flat, ts->i2c_echo, ts->dump_ok, ts->polls, + tristar_id_label(ts)); + mutex_unlock(&ts->lock); + return n; +} +static DEVICE_ATTR_RW(poll); + +/* + * Read-only telemetry. + * + * TriStar is not the charger -- it decides what is attached to Lightning + * and where the signal paths go, while the D1830 owns the battery and the + * power path. What it does hold is the attach/detach state, which is the + * missing input to any charging policy, so making its state observable is + * the useful contribution here rather than trying to drive anything. + * + * tristar_stats counters and decoded state in one place + * tristar_regs the dump with labels on the registers we can justify + * tristar_watch echo 1 to snapshot, read to see only what moved + * + * The watch is the one that matters for attach detection: snapshot, change + * the cable, read back, and whatever moved is the short list. + */ +static ssize_t tristar_stats_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct apple_tristar *ts = dev_get_drvdata(dev); + + if (!ts) + return -ENODEV; + + return sysfs_emit(buf, + "polls=%u deltas=%u writes=%u i2c_fail_streak=%u poll_disabled=%d\n" + "dump_ok=%d dump_flat=%d i2c_echo=%d seen_mask=%08x\n" + "id=%s id_valid=%d id_off=%u accx=%02x dx=%02x\n" + "reg11=%02x reg11_ret=%d osos_event=%02x prev=%02x cf9_latch=%d cfa=%02x\n", + ts->polls, ts->deltas, ts->writes, ts->i2c_fail_streak, + ts->poll_disabled, + ts->dump_ok, ts->dump_flat, ts->i2c_echo, ts->seen_mask, + ts->id_name ? ts->id_name : "unknown", ts->id_valid, + ts->id_off, ts->accx, ts->dx, + ts->reg11, ts->reg11_ret, ts->osos_event, ts->prev_osos_event, + ts->cf9_latch, ts->cfa_state); +} +static DEVICE_ATTR_RO(tristar_stats); + +/* + * Only 0x11 has a name we can defend -- CBTL1610 configuration status, + * and it may NAK or echo on a 1609. The rest are listed as offsets with + * their observed values so a diff has somewhere to point; naming them + * before correlation would turn guesses into apparent fact, which is + * exactly how a rail block at 0x40 got invented for the PMIC. + */ +static ssize_t tristar_regs_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct apple_tristar *ts = dev_get_drvdata(dev); + unsigned int i; + int len = 0; + + if (!ts) + return -ENODEV; + if (!ts->dump_ok) + return sysfs_emit(buf, "no valid dump (dump_ok=0)\n"); + + mutex_lock(&ts->lock); + for (i = 0; i < TRISTAR_DUMP_LEN && len < PAGE_SIZE - 48; i++) { + if (!ts->last_dump[i]) + continue; /* zeros are the overwhelming majority */ + len += scnprintf(buf + len, PAGE_SIZE - len, + "0x%02x = %02x%s\n", i, ts->last_dump[i], + i == 0x11 ? " (CBTL1610 config status)" : ""); + } + mutex_unlock(&ts->lock); + len += scnprintf(buf + len, PAGE_SIZE - len, + "# non-zero offsets only; flat=%d echo=%d\n", + ts->dump_flat, ts->i2c_echo); + return len; +} +static DEVICE_ATTR_RO(tristar_regs); + +static u8 tristar_snap[TRISTAR_DUMP_LEN]; +static bool tristar_snap_valid; + +static ssize_t tristar_watch_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct apple_tristar *ts = dev_get_drvdata(dev); + unsigned int i, changed = 0; + int len = 0; + + if (!ts) + return -ENODEV; + if (!tristar_snap_valid) + return sysfs_emit(buf, + "no snapshot; echo 1 > tristar_watch first\n"); + + mutex_lock(&ts->lock); + for (i = 0; i < TRISTAR_DUMP_LEN && len < PAGE_SIZE - 64; i++) { + if (ts->last_dump[i] == tristar_snap[i]) + continue; + changed++; + len += scnprintf(buf + len, PAGE_SIZE - len, + "0x%02x %02x -> %02x (xor %02x)\n", + i, tristar_snap[i], ts->last_dump[i], + tristar_snap[i] ^ ts->last_dump[i]); + } + mutex_unlock(&ts->lock); + if (!changed) + len += scnprintf(buf + len, PAGE_SIZE - len, + "no change\n"); + return len; +} + +static ssize_t tristar_watch_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct apple_tristar *ts = dev_get_drvdata(dev); + + if (!ts) + return -ENODEV; + if (buf[0] != '1') + return -EINVAL; + mutex_lock(&ts->lock); + memcpy(tristar_snap, ts->last_dump, TRISTAR_DUMP_LEN); + tristar_snap_valid = true; + mutex_unlock(&ts->lock); + return count; +} +static DEVICE_ATTR_RW(tristar_watch); + +static struct attribute *tristar_attrs[] = { + &dev_attr_dump.attr, + &dev_attr_poke.attr, + &dev_attr_mode.attr, + &dev_attr_route.attr, + &dev_attr_audio_path.attr, + &dev_attr_vbus.attr, + &dev_attr_read.attr, + &dev_attr_value.attr, + &dev_attr_verify.attr, + &dev_attr_poll.attr, + &dev_attr_tristar_stats.attr, + &dev_attr_tristar_regs.attr, + &dev_attr_tristar_watch.attr, + NULL, +}; +ATTRIBUTE_GROUPS(tristar); + +static int tristar_dbg_anchors_show(struct seq_file *m, void *p) +{ + seq_puts(m, + "OSOS central loop: sub_11C8C\n" + "OSOS tasks: TriStarIDProcessTask, TriStarVBUSProcessTask, TriStarCONDETProcessTask\n" + "OSOS accessory manager: accessoryMgr.cpp:716 type1=MikeyBus UART2, type2=TODO RE\n" + "OSOS MikeyBus: CMikeyBusUartReadTask, CMikeyBusUartResistorTask, mikeyTask.cpp:169\n" + "I2C: 7-bit 0x1a (8-bit WR 0x34 / RD 0x35)\n" + "Routing: IDBUS 0x74/0x75 inside CBTL1609 — Linux does not write Dx\n" + "0x75 ACCx/Dx tables: THS7383 datasheet via nyansatan\n" + "reg 0x11: CBTL1610 config status (Lina); may NAK or echo on CBTL1609\n" + "OSOS v36 bits 0x01/0x04/0x08/0x10/0x20: logged as unmapped, not synthesized\n" + "3.5mm analog: CS42 + apple-mikeybus, not Tristar Dx\n" + "Lightning analog EarPods: ID 04 F1 00 00 00 00\n" + "Bootloader PMIC sub_3F40/3F60 is not Tristar\n" + "Candidate index pmic-tristar-ida-out.txt is not a write recipe\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_anchors); + +static int tristar_dbg_status_show(struct seq_file *m, void *p) +{ + struct apple_tristar *ts = m->private; + + mutex_lock(&ts->lock); + seq_printf(m, + "osos_event=0x%02x\nprev_osos_event=0x%02x\nseen_mask=0x%02x\n" + "osos_masks=0x%02x,0x%02x,0x%02x,0x%02x,0x%02x (I2C producer TODO RE)\n" + "cf9_latch=%d\ncfa_state=%u\n" + "dump_ok=%d dump_flat=%d i2c_echo=%d\nreg11_ret=%d reg11=0x%02x\n" + "id=%s usb_routed=%d lightning_analog=%d\n" + "read_only=%d unsafe_writes=%d unsafe_acks=%d poll_ms=%d\n" + "polls=%u writes=%u\n", + ts->osos_event, ts->prev_osos_event, ts->seen_mask & 0xff, + (unsigned int)N31_TS_EVENT_01, (unsigned int)N31_TS_EVENT_04, + (unsigned int)N31_TS_EVENT_08, (unsigned int)N31_TS_EVENT_10, + (unsigned int)N31_TS_EVENT_20, + ts->cf9_latch, ts->cfa_state, + ts->dump_ok, ts->dump_flat, ts->i2c_echo, ts->reg11_ret, + ts->reg11, tristar_id_label(ts), + ts->id_valid && tristar_usb_dp_from_dx(ts->dx), + tristar_is_lightning_analog(ts), + read_only, unsafe_writes, unsafe_acks, poll_ms, ts->polls, + ts->writes); + mutex_unlock(&ts->lock); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_status); + +static int tristar_dbg_log_show(struct seq_file *m, void *p) +{ + struct apple_tristar *ts = m->private; + unsigned int i, n, idx; + + mutex_lock(&ts->lock); + n = ts->log_count; + idx = (ts->log_head + TRISTAR_LOG_LEN - n) % TRISTAR_LOG_LEN; + for (i = 0; i < n; i++) { + seq_printf(m, "%s\n", ts->log[idx]); + idx = (idx + 1) % TRISTAR_LOG_LEN; + } + mutex_unlock(&ts->lock); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_log); + +static int tristar_dbg_counters_show(struct seq_file *m, void *p) +{ + struct apple_tristar *ts = m->private; + + mutex_lock(&ts->lock); + seq_printf(m, "polls=%u\ndeltas=%u\nwrites=%u\nacks=0\n", + ts->polls, ts->deltas, ts->writes); + mutex_unlock(&ts->lock); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_counters); + +static int tristar_dbg_mode_show(struct seq_file *m, void *p) +{ + struct apple_tristar *ts = m->private; + + mutex_lock(&ts->lock); + seq_printf(m, + "read_only=%d\nunsafe_writes=%d\nunsafe_acks=%d\n" + "i2c_echo=%d\nid=%s\n", + read_only, unsafe_writes, unsafe_acks, ts->i2c_echo, + tristar_id_label(ts)); + mutex_unlock(&ts->lock); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_mode); + +static int tristar_dbg_unsafe_show(struct seq_file *m, void *p) +{ + seq_printf(m, "%d\n", unsafe_writes); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tristar_dbg_unsafe); + +static void tristar_debugfs_init(struct apple_tristar *ts) +{ + ts->debug_root = debugfs_create_dir("n31_tristar", NULL); + if (IS_ERR_OR_NULL(ts->debug_root)) { + ts->debug_root = NULL; + return; + } + debugfs_create_file("source_anchors", 0444, ts->debug_root, ts, + &tristar_dbg_anchors_fops); + debugfs_create_file("raw_status", 0444, ts->debug_root, ts, + &tristar_dbg_status_fops); + debugfs_create_file("event_log", 0444, ts->debug_root, ts, + &tristar_dbg_log_fops); + debugfs_create_file("counters", 0444, ts->debug_root, ts, + &tristar_dbg_counters_fops); + debugfs_create_file("mode", 0444, ts->debug_root, ts, + &tristar_dbg_mode_fops); + debugfs_create_file("unsafe_writes_enabled", 0444, ts->debug_root, ts, + &tristar_dbg_unsafe_fops); +} + +static int apple_tristar_probe(struct i2c_client *client) +{ + struct apple_tristar *ts; + struct device *dev = &client->dev; + int ret; + u8 id0 = 0xff; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + ts->client = client; + mutex_init(&ts->lock); + INIT_DELAYED_WORK(&ts->poll, tristar_poll_work); + i2c_set_clientdata(client, ts); + + dev_info(dev, + "N31 Tristar: OSOS sub_11C8C ID/VBUS/CONDET; I2C 7-bit 0x%02x on %s\n", + client->addr, client->adapter->name); + dev_info(dev, + "N31 Tristar: IDBUS routes USB/UART/SWD; read_only=%d unsafe_writes=%d poll_ms=%d\n", + read_only, unsafe_writes, poll_ms); + dev_info(dev, + "N31 Tristar: 3.5mm analog is CS42+MikeyBus — not a Dx write; type2 accessoryMgr TODO RE\n"); + dev_info(dev, + "N31 Tristar: OSOS v36 bits 0x01/0x04/0x08/0x10/0x20 unmapped; VBUS I2C map ENOTSUPP\n"); + + if (of_property_read_bool(dev->of_node, "apple,require-ack")) { + ret = tristar_read_reg(ts, 0x00, &id0); + if (ret) { + dev_err(dev, + "Tristar no ACK at 7-bit 0x%02x on %s (err=%d)\n", + client->addr, client->adapter->name, ret); + return -ENODEV; + } + dev_info(dev, "Lightning Tristar ACK @7bit=0x%02x reg0=0x%02x\n", + client->addr, id0); + } + + tristar_apply_init_sequence(ts); + tristar_debugfs_init(ts); + + ret = sysfs_create_groups(&dev->kobj, tristar_groups); + if (ret) + dev_warn(dev, "sysfs groups failed: %d\n", ret); + + mutex_lock(&tristar_singleton_lock); + tristar_singleton = ts; + mutex_unlock(&tristar_singleton_lock); + + /* + * Probe with a single cheap ACK. Full 64-reg refresh only if the + * chip answers — otherwise one warn and stay quiet (I2C0 often + * NACKs until Lightning/mux is up). + */ + ret = tristar_read_reg(ts, 0x00, &id0); + if (ret) { + ts->dump_ok = false; + ts->poll_disabled = true; + ts->i2c_fail_streak = 1; + dev_warn(dev, + "tristar: probe ACK failed %d — leaving unbound quiet (poll off)\n", + ret); + } else { + tristar_refresh(ts, "probe"); + if (poll_ms > 0 && !ts->poll_disabled) + schedule_delayed_work(&ts->poll, + msecs_to_jiffies(poll_ms)); + } + return 0; +} + +static void apple_tristar_remove(struct i2c_client *client) +{ + struct apple_tristar *ts = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&ts->poll); + mutex_lock(&tristar_singleton_lock); + if (tristar_singleton == ts) + tristar_singleton = NULL; + mutex_unlock(&tristar_singleton_lock); + debugfs_remove_recursive(ts->debug_root); + sysfs_remove_groups(&client->dev.kobj, tristar_groups); +} + +static const struct of_device_id apple_tristar_of_match[] = { + { .compatible = "apple,tristar-cbtl1609" }, + { .compatible = "nxp,cbtl1609a1" }, + { .compatible = "apple,n31-tristar" }, + { }, +}; +MODULE_DEVICE_TABLE(of, apple_tristar_of_match); + +static const struct i2c_device_id apple_tristar_id[] = { + { "tristar-cbtl1609" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, apple_tristar_id); + +static struct i2c_driver apple_tristar_driver = { + .driver = { + .name = "apple-tristar", + .of_match_table = apple_tristar_of_match, + }, + .probe = apple_tristar_probe, + .remove = apple_tristar_remove, + .id_table = apple_tristar_id, +}; +module_i2c_driver(apple_tristar_driver); + +MODULE_DESCRIPTION("Apple Lightning Tristar / NXP CBTL1609A1 mux"); +MODULE_AUTHOR("Hydrogenuine / FreeMyiPod N31 bring-up"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/ftl-s5l8740-core.c b/drivers/misc/ftl-s5l8740-core.c new file mode 100755 index 00000000000000..6c6ea0d35a9f1d --- /dev/null +++ b/drivers/misc/ftl-s5l8740-core.c @@ -0,0 +1,10678 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 Whimory FTL — read-only block path (N31). + * + * Layers: NAND FIL → FPart → VFL → FTL/L2V → optional /dev/s5l8740-ftl. + * The CS-map front-end in ftl-s5l8740-csmap.c is the preferred RO disk path. + * This module retains the classic Whimory open/boot helpers. + * + * The block device is registered only after FAT-critical validation succeeds. + * Empty or inconsistent maps never expose a disk. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "whimory-s5l8740.h" +#include "ftl-s5l8740-csmap.h" + +#define FTL_DISK_NAME "s5l8740-ftl" +#define FTL_IPOD_NAME "s5l8740-ipod" + +#define WHIMORY_ORACLE_SIG "apple/n31-whimory-sig.bin" +#define WHIMORY_ORACLE_ROOT "apple/n31-whimory-l2v-root.bin" +#define WHIMORY_ORACLE_NODES "apple/n31-whimory-l2v-nodes.bin" +#define WHIMORY_ORACLE_GLOBALS "apple/n31-whimory-l2v-globals.bin" + +#define WHIMORY_SPECIAL_LBA 0xFFFF0000u + +static bool import_l2v_oracle; +module_param(import_l2v_oracle, bool, 0644); +MODULE_PARM_DESC(import_l2v_oracle, + "Load L2V root/nodes/globals from /lib/firmware/apple/"); + +/* + * Rebuild every open superblock we find, up to a backstop. + * + * This was 16, which was a bring-up number and never lifted. It is not a + * tuning knob: an open superblock holds writes that happened after the + * checkpoint, so a cap on how many get rebuilt is a cap on how much recent + * data ends up in the map. With 1619 open superblocks on this volume it was + * rebuilding one percent of them and silently dropping the rest, which is a + * second and entirely separate cause of the missing-recent-files symptom + * that the page-0 weave bug caused. + * + * 4096 is above the number of blocks a CAU has, so in practice this is "all + * of them" -- it stays a finite number only so a corrupt classify cannot turn + * into an unbounded loop. The cost is real and is reported: each rebuild + * reads pages from page 0 until it finds a blank one, so a genuinely open + * block stops early and a block that is actually full reads all 127. + */ +/* + * Rebuild a closed superblock whose BTOC has no BTE array. + * + * Off, and the measurement that turned it off is worth keeping. Enabling it + * answered the question it was built for -- the 70 such superblocks report + * 127 pages/sb, so they are full sealed blocks and not empty ones, exactly + * as the decomp of sub_567E3C predicted. But the second number says the + * work is unnecessary and the third says it is harmful: + * + * btoc_fallback sbs=70 pages=8890 hits=35560 (127 pages/sb) + * mapped_lbas 949733 -> 949733 + * + * 35560 mappings applied and not one new LBA. They did not add anything; + * they replaced existing mappings one for one with different VBAs. Reads + * then came back holding the wrong logical block: + * + * sftl lba mismatch want=0x8042b meta=0x7fed0 type=01 + * sftl lba mismatch want=0x7483f meta=0x7482c type=01 + * + * with scattered deltas, which is what overwriting a correct map with a + * differently-derived one looks like. The build immediately before this + * read every file on the volume with no error. + * + * The reason is the same one that made the uncapped open rebuild + * destructive: on this volume no superblock is newer than the checkpoint + * (weave newer=0 older=2182), so per-page meta has nothing to add over the + * CXT and every override is a regression. The CXT already maps everything + * those blocks hold, which is precisely why mapped_lbas did not move. + * + * Kept as a switch rather than deleted because the reasoning is + * volume-specific: a device whose checkpoint is genuinely behind its data + * would need this, and would show it by mapped_lbas rising when it runs. + */ +/* + * Dump the raw CXT tree records covering a window of logical space. + * + * The mismatch narrowed to the logical cursor in whimory_cxt_parse_tree: + * the runs land in the right VBAs and carry the wrong LBA labels. The + * cursor is checked against each record's declared start LBA and never + * disagreed, so whatever goes wrong is internal to a record and cancels out + * by the end of it -- which cannot be reasoned about from summary counters. + * + * cxt_dump_lba names the window, cxt_dump_len its size. Every pair whose + * logical extent touches it is printed with the cursor value it was given, + * so the pair whose span does not match the run it describes is visible + * directly. + */ +/* + * Trace the first records of every CXT superblock walk. + * + * Two readings are left for the two newest checkpoints, whose records are + * eight bytes of 0xff followed by file content, and they need opposite + * fixes: + * + * the walk is landing on the wrong page -> an addressing bug here + * a record can be a header-only terminator followed by pre-erase + * contents -> accept it and keep walking + * + * The discriminator is whether the meta and the data come from the same + * place. Printing each slot's own meta beside its own data head, with the + * page's four metas alongside, settles it: a page whose slots all carry + * SFTL_CXT meta while their data is XML is a page we should not be reading, + * whereas a CXT page whose later slots hold stale content is a terminator. + */ +/* + * Replay this virtual block even if the checkpoint says it is covered. + * + * The last hypothesis for the handful of files that will not read. Their + * mapping comes from the checkpoint, is structurally sound, and points at a + * page holding much older data -- so either the checkpoint entry is stale + * and the correction lives in per-page meta that the skip threw away, or it + * is not stale and the fault is elsewhere. Forcing one block through the + * replay separates those without changing the rule for anything else. + * + * 0 disables. This is a test instrument, not a fix: if it works the fix is + * to make the skip rule stop being wrong about blocks like it, not to keep + * a hardcoded exception. + */ +static unsigned int force_replay_vblock; +module_param(force_replay_vblock, uint, 0644); +MODULE_PARM_DESC(force_replay_vblock, + "replay this vblock even when the CXT covers it (0 = off)"); + +static unsigned int cxt_trace_sb; +module_param(cxt_trace_sb, uint, 0644); +MODULE_PARM_DESC(cxt_trace_sb, "trace this many leading records of each CXT superblock"); + +static unsigned int cxt_dump_lba; +module_param(cxt_dump_lba, uint, 0644); +MODULE_PARM_DESC(cxt_dump_lba, "dump CXT tree pairs covering this LBA (0 = off)"); + +static unsigned int cxt_dump_len = 16384; +module_param(cxt_dump_len, uint, 0644); +MODULE_PARM_DESC(cxt_dump_len, "size of the cxt_dump_lba window"); + +static unsigned int cxt_dump_max = 96; +module_param(cxt_dump_max, uint, 0644); +MODULE_PARM_DESC(cxt_dump_max, "cap on dumped pairs"); + +static bool btoc_meta_fallback; +module_param(btoc_meta_fallback, bool, 0644); +MODULE_PARM_DESC(btoc_meta_fallback, + "Rebuild BTOC-less closed superblocks from per-page meta " + "(default N; overrides the CXT and corrupts the map when " + "the CXT is newer, which it is on N31)"); + +static unsigned int max_open_sbs = 4096; +module_param(max_open_sbs, uint, 0644); +MODULE_PARM_DESC(max_open_sbs, + "Max open superblocks to META-rebuild (0 = all; default 4096)"); + +/* + * Scan every block. 256 was a bring-up limit that was never lifted, and + * it quietly disabled the CXT fast path: the snapshot blocks sit above + * that mark, so classify never saw one and every boot fell back to + * replaying the whole device. Measured on this unit, 256 against 0: + * + * replayed superblocks 624 -> 0 + * skipped_by_cxt 0 -> 2274 + * cxt_seeded 0 -> 239525 + * classified_cxt 0 -> 4 + * mapped_ranges 1933 -> 200000 + * + * so it was not only slow, it was building a far less complete map. + * Classifying more blocks costs less than replaying 624 superblocks. + */ +static unsigned int scan_blocks; +module_param(scan_blocks, uint, 0644); +MODULE_PARM_DESC(scan_blocks, + "User blocks per CE/CAU to classify (0 = all, the default)"); + +/* + * BTOC meta-confirm re-reads every candidate data page over CS. Full-SB + * confirm across closed BTOCs wedges USB (~2min then reset). Cap confirms; + * open-META rebuild remains the bulk authority. 0 = unlimited (raise only + * after correctness passes; stage with scan_blocks). + */ +static bool btoc_meta_confirm = true; +module_param(btoc_meta_confirm, bool, 0644); +MODULE_PARM_DESC(btoc_meta_confirm, + "CS-read data pages to take meta_lba as L2V key (default Y)"); + +/* + * BTOC confirmation budget. + * + * This was 512, and on a full volume that is not a budget, it is a + * truncation. Measured on an N31 with the same flash contents: + * + * 512 65536 + * pages_valid 6 312 + * l2v_updates 2048 97820 + * confirm_capped 23943 0 + * mapped_lbas 7468 77553 + * mapped_ranges 1933 20596 + * + * Every one of the extra confirmations was good -- meta_mismatch stayed + * at 0 -- so the old default was discarding about nine tenths of the + * mapping and reporting success while doing it. That is the same shape + * of bug as the old max_range_nodes ceiling: the statistic sits exactly + * on the limit, which means the limit chose the answer. + * + * This was reverted to 512 once, after a single run where the richer map + * flipped BPB selection to the other candidate (49285) and the volume + * would not mount. That revert was wrong. It let one observation override + * a measurement, and the cost is not subtle: at 512 the volume mounts but + * only about a tenth of it is mapped, so most files simply are not there. + * On the boot that settled it, 65536 bound to 49279 and mounted with Apps, + * iPod_Control and n31os all present. + * + * If BPB selection ever does pick an unmountable candidate again, fix the + * selection -- it should prefer a candidate whose FAT actually reads -- + * rather than starving the map to steer it. + * + * 24455 confirm pages were needed here, so 65536 leaves headroom for a + * fuller volume without being unbounded. Raise it (or set 0) if + * btoc_confirm_capped is ever non-zero -- that value being non-zero is + * the signal that recovery is being cut short. + */ +/* + * Read page 127 only when page 0 leaves the question open. + * + * This is a large IO saving and it is why classify dropped from ~57s to + * ~26s. It is also the kind of optimisation that can quietly change what + * gets classified, so it is switchable: set btoc_page_lazy=0 to go back + * to reading page 127 for every block and compare the classify totals. + * If the two disagree, the laziness is wrong, not the flash. + */ +/* + * Probe blocks for "empty" with a one-record read before doing the full + * four-record page read. On by default: it is a strict reduction in NAND + * traffic for the majority case and changes no classification, because a + * negative probe falls through to exactly the old path. + */ +static bool fast_empty_probe = true; +module_param(fast_empty_probe, bool, 0644); + +static bool batch_classify = true; +module_param(batch_classify, bool, 0644); +MODULE_PARM_DESC(batch_classify, + "classify page-0 scan reads 16 blocks per sequencer kick (default on)"); +MODULE_PARM_DESC(fast_empty_probe, + "1=one-record empty probe before the full page read (default)"); + +static bool btoc_page_lazy = true; +module_param(btoc_page_lazy, bool, 0644); + +static unsigned int read_prefetch_pages = WHIMORY_RC_SLOTS; +module_param(read_prefetch_pages, uint, 0644); +MODULE_PARM_DESC(read_prefetch_pages, + "physical pages held in the sequential read-ahead window, 0 to disable (default 8)"); + +/* + * How far ahead to walk the VBAs when filling the window. 32 VBAs is one + * 128 KiB readahead request, which at four LBAs per page is the eight pages + * the window holds. + */ +static unsigned int read_prefetch_vbas = 32; +module_param(read_prefetch_vbas, uint, 0644); + +/* Window behaviour, read-only. Guessing at this cost a 4x regression once. */ +static unsigned int rc_fills, rc_hits, rc_misses, rc_fails, rc_pages; +module_param(rc_fills, uint, 0444); +module_param(rc_hits, uint, 0444); +module_param(rc_misses, uint, 0444); +module_param(rc_fails, uint, 0444); +module_param(rc_pages, uint, 0444); +MODULE_PARM_DESC(read_prefetch_vbas, + "VBAs to look ahead when filling the read-ahead window (default 32)"); +MODULE_PARM_DESC(btoc_page_lazy, + "1=read page 127 only when page 0 is inconclusive (default); 0=always read it"); + +static unsigned int btoc_confirm_max = 65536; +module_param(btoc_confirm_max, uint, 0644); +MODULE_PARM_DESC(btoc_confirm_max, + "Max BTOC CS page confirms per recover (default 65536; 0=unlimited). Non-zero btoc_confirm_capped means this is too low"); +/* Alias name from bring-up notes. */ +module_param_named(btoc_confirm_pages_cap, btoc_confirm_max, uint, 0644); + +/* Soft wall-clock budget for BTOC confirms (0 = ignore). */ +static unsigned int recover_budget_ms; +module_param(recover_budget_ms, uint, 0644); +MODULE_PARM_DESC(recover_budget_ms, + "Stop BTOC confirms after this many ms (0=off)"); + +/* Keep RNDIS/USB alive during long recover (default 2ms every 4 blocks). */ +/* + * No sleep by default. This existed to keep RNDIS and the watchdog alive + * during a long scan, but cond_resched() alone does that under + * CONFIG_PREEMPT, and at 2 ms every fourth block the sleeps alone cost + * several seconds of a mount that should take a few. + */ +static unsigned int recover_yield_us; +module_param(recover_yield_us, uint, 0644); +MODULE_PARM_DESC(recover_yield_us, + "usleep between classify blocks to keep USB alive (0=off; default 2000)"); + +/* + * Interval-map node budget. N31 glass has ~55 MiB of RAM total; each + * whimory_range is a kmalloc-64 slab object. Running out mid-recover is an + * OOM panic (panic=-1 → reboot to RetailOS), which costs a DFU cycle and + * loses the log. Stop adding mappings at the budget and report instead. + */ +/* + * 200000 was below what this device actually needs, and the failure is + * silent in the worst way: the map fills to exactly the ceiling and the + * rest of the volume is simply absent. + * + * Measured here, with the CXT fast path working: + * + * CXT_SEED extents=239525 + * mapped_ranges=200000 range_budget_stop=39512 + * + * so 39512 ranges were dropped on the floor. Reads past them return + * UNMAPPED, FAT cannot fetch its directory blocks, and the mount comes + * up with "invalid cluster chain" and missing folders -- which looks + * like a corrupt disk rather than a driver that stopped writing down + * where things are. L2V packing then fails with -12 and it falls back + * to the truncated interval map. + * + * A whimory_range is about 32 bytes, so this ceiling is roughly 16 MB + * if it were ever reached. It is a ceiling rather than an allocation: + * nodes are only created for extents that exist, so a device with + * fewer extents pays nothing for the headroom. Set well above this + * unit's 239525 deliberately, so a fuller or more fragmented volume + * on another device does not hit the same silent truncation. + * Set 0 for unlimited. + */ +static unsigned int max_range_nodes = 500000; +module_param(max_range_nodes, uint, 0644); +MODULE_PARM_DESC(max_range_nodes, + "Interval-map node ceiling; stop mapping past it (0=unlimited)"); + +/* + * The SFTL context is the authoritative FTL snapshot and only turns up + * once scan_blocks reaches the high blocks that hold it (~1960 on N31). + * It maps 600k+ LBAs in a few thousand coalesced ranges, but its VBAs do + * not yet resolve (sftl lba mismatch → no BPB in L2V). Off skips both the + * CXT load and the weave filter that suppresses replay of older SBs, so + * the brute-force BTOC/open rebuild still yields a mountable disk. + */ +/* + * Open-SB rebuild assigns each VBA its own weave, so adjacent LBAs landing + * in adjacent VBAs never merged and the map cost one 64-byte rb node per + * LBA — 46 MiB to replay every open SB on a 55 MiB device. Merging + * neighbours that are contiguous in both LBA and VBA and keeping the OLDER + * weave collapses sequentially written data; keeping the older weave means + * a later claim is never wrongly rejected as stale, it just splits the + * range again. 0 restores exact per-weave ranges. + */ +/* + * Closed superblocks currently cost the same 127 CS reads as open ones: + * the BTOC is used only to pick pages to re-read, and the per-slot meta is + * taken as the L2V key. Before trusting the BTOC records outright, measure + * whether they actually predict that metadata. + */ +static unsigned int btoc_verify_sbs; +module_param(btoc_verify_sbs, uint, 0644); +MODULE_PARM_DESC(btoc_verify_sbs, + "Closed SBs to probe for BTOC-vs-meta agreement (0=off)"); + +static unsigned int btoc_verify_pages = 6; +module_param(btoc_verify_pages, uint, 0644); +MODULE_PARM_DESC(btoc_verify_pages, + "Pages sampled per verified BTOC (default 6)"); + +/* + * Console logging is not free on this target: every dev_info goes out the + * serial console and measurably lengthens recover. Quiet by default; the + * deep bring-up dumps come back with diag=1. + */ +static bool ftl_diag; +module_param_named(diag, ftl_diag, bool, 0644); +MODULE_PARM_DESC(diag, + "Per-LBA/BTOC/VBA diagnostic dumps (default N)"); + +static bool ftl_progress = true; +module_param_named(progress, ftl_progress, bool, 0644); +MODULE_PARM_DESC(progress, "Periodic recover progress lines (default Y)"); + +/* + * 5 s suited a console log but is far too coarse to drive a progress + * bar, which needs to move several times a second or it reads as hung. + * This gates the sysfs counters as well as the log lines, so it is now + * a UI refresh rate rather than a logging interval. + */ +static unsigned int progress_ms = 500; +module_param(progress_ms, uint, 0644); +MODULE_PARM_DESC(progress_ms, "Minimum ms between progress updates"); + +/* Rate-limit: emit at most this many of a repeating diagnostic. */ +static unsigned int diag_max_lines = 3; +module_param(diag_max_lines, uint, 0644); + +/* + * Log the parts of each FPart system object that carry something. + * Off: this is for reverse engineering the objects, not for boots. + */ +static bool fpart_dump; +module_param(fpart_dump, bool, 0644); +MODULE_PARM_DESC(fpart_dump, + "Dump non-fill rows of the FPart system objects (default N)"); +MODULE_PARM_DESC(diag_max_lines, "Cap on repeated read-miss/winner lines"); + +/* True once per progress_ms window; keeps hot loops from flooding. */ +static bool ftl_progress_due(struct whimory *w) +{ + unsigned long now = jiffies; + + if (!ftl_progress) + return false; + if (w->progress_jiffies && + time_before(now, w->progress_jiffies + msecs_to_jiffies(progress_ms))) + return false; + w->progress_jiffies = now; + return true; +} + +static void ftl_progress_set(struct whimory *w, const char *phase, + unsigned int cur, unsigned int total) +{ + w->prog_phase = phase; + w->prog_cur = cur; + w->prog_total = total; +} + +static bool range_coalesce = true; +module_param(range_coalesce, bool, 0644); +MODULE_PARM_DESC(range_coalesce, + "Merge LBA/VBA-contiguous ranges across weaves (default Y)"); + +static bool use_cxt = true; +module_param(use_cxt, bool, 0644); + +/* + * Mount from the checkpoint alone, as RetailOS does. + * + * s_cxt_load.c inserts every (vba, span) pair the context carries unless + * the pair's superblock is set in diff->sbFilter: + * + * sb = vba_to_sb(vba); + * if (vba >= invalid_vba || !sbFilter_test(sb)) + * l2v_insert(lba, span, vba); + * + * and s_cxt_diff.c:405 allocates that filter with numRecords = 0 at init. + * Nothing populates the record array during a load -- the diff machinery + * belongs to the running FTL, where it tracks superblocks dirtied since the + * last checkpoint so the next one can be incremental. At mount the filter + * is empty, every pair is inserted, and no superblock is replayed. + * + * This driver keeps one safety net stock does not need: a superblock whose + * newest page is at or after the checkpoint weave was written after the + * checkpoint, so the checkpoint cannot describe it and it is replayed. + * On a cleanly unmounted volume that set is empty and the whole replay + * disappears. + * + * Clearing this restores the per-superblock weave test, which replays + * anything whose bound did not come from page 127. + */ +/* + * Scan superblock meta the way RetailOS does: first and last page queued + * together, 256 records per sequencer kick, page data discarded. + */ +static bool stock_scan = true; +module_param(stock_scan, bool, 0644); +MODULE_PARM_DESC(stock_scan, + "classify reads first+last page meta in 256-record batches (default Y)"); + +static bool cxt_fast = true; +module_param(cxt_fast, bool, 0644); +MODULE_PARM_DESC(cxt_fast, + "mount from the checkpoint and replay only superblocks newer than it (default Y)"); +MODULE_PARM_DESC(use_cxt, + "Load the SFTL CXT snapshot during recover (default Y)"); + +/* + * The checkpoint is not an optimisation, it is the mount. + * + * Recovering from the checkpoint takes about eight seconds on this device; + * rebuilding the same map by replaying every superblock takes about eight + * minutes and reads roughly two hundred thousand pages to arrive at the + * same answer. The full replay exists as an oracle -- it is built from + * per-page metadata, so it is what a suspect checkpoint gets checked + * against -- and as the thing to reach for when the checkpoint decode is + * under suspicion. It is not a boot path. + * + * With this set, a checkpoint that will not load is an error and the + * recover stops there. Clearing it restores the old silent fallback. + */ +static bool require_cxt = true; +module_param(require_cxt, bool, 0644); +MODULE_PARM_DESC(require_cxt, + "Fail the recover instead of full-replaying when the CXT will not load (default Y)"); + +/* + * A full rebuild used to be re-runnable at any time, so a stray write to + * ftl_sftl_recover could tear down a live map underneath a mounted disk. + */ +enum whimory_recovery_state { + RECOVERY_NONE = 0, + RECOVERY_RUNNING, + RECOVERY_VALID, + RECOVERY_FAILED, +}; + +static enum whimory_recovery_state recovery_state; +static u32 recovery_params_key; + +static bool recover_force; +module_param(recover_force, bool, 0644); +MODULE_PARM_DESC(recover_force, + "Allow rebuild when a map is already valid/bound (default N)"); + +/* + * Check each CXT extent against the page it points at before seeding it. + * Off is faster and reproduces the old behaviour, at the cost of + * trusting a snapshot that may predate the writes which moved its data. + */ +/* + * Re-reading each extent's page meta to confirm the checkpoint is not + * something RetailOS does: s_cxt_load.c inserts what the context says and + * moves on. It costs one page read per extent -- 6276 on this volume -- + * and is kept only as a diagnostic. + */ +static bool cxt_meta_confirm; +module_param(cxt_meta_confirm, bool, 0644); +MODULE_PARM_DESC(cxt_meta_confirm, + "Confirm CXT extents against page metadata before seeding (default Y)"); + +static unsigned int cxt_confirm_max; +module_param(cxt_confirm_max, uint, 0644); +MODULE_PARM_DESC(cxt_confirm_max, + "Cap on CXT confirmation page reads (0 = no cap)"); + +/* Changing any of these is a different map, so a repeat is not a no-op. */ +static u32 whimory_recover_key(void) +{ + return scan_blocks * 1000003u + max_open_sbs * 10007u + + btoc_confirm_max * 101u + (use_cxt ? 2u : 0u) + (cxt_meta_confirm ? 4u : 0u) + + (range_coalesce ? 1u : 0u) + max_range_nodes; +} + +const char *whimory_recovery_state_name(void) +{ + switch (recovery_state) { + case RECOVERY_RUNNING: + return "running"; + case RECOVERY_VALID: + return "valid"; + case RECOVERY_FAILED: + return "failed"; + default: + return "none"; + } +} +EXPORT_SYMBOL_GPL(whimory_recovery_state_name); + +static bool audit_lba_winners; +module_param(audit_lba_winners, bool, 0644); +MODULE_PARM_DESC(audit_lba_winners, + "Log duplicate-LBA winner decisions for critical fmss LBAs"); + +/* Dump every winner decision for this absolute fmss_lba (0 = off). */ +static unsigned int l2v_trace_lba; +module_param(l2v_trace_lba, uint, 0644); +MODULE_PARM_DESC(l2v_trace_lba, + "Log L2V winner old/new for this fmss_lba (0=off)"); + +/* Apply BTOC FFFF0001 LIST unmaps during recover (default Y). */ +static bool btoc_apply_list = true; +module_param(btoc_apply_list, bool, 0644); +MODULE_PARM_DESC(btoc_apply_list, + "Apply BTOC LIST (FFFF0001) unmap payloads during recover"); + +static bool vba_page_dump; +module_param(vba_page_dump, bool, 0644); +MODULE_PARM_DESC(vba_page_dump, + "Emit VBA_DIAG sibling slot dump on critical reads (default N)"); + +/* + * Off by default: 508 page reads that nothing acts on. + * + * This full-scans closed superblocks after the map is already built, page 0 + * to 126, and hands every page to whimory_note_meta0() -- which increments + * meta0_hits and, unless diag is set, does nothing else. At four + * superblocks that is 4 x 127 reads and about 0.8 seconds added to every + * mount, to produce one number in RECOVERY_STATS. + * + * It was worth having while the question was "does anything on this volume + * carry meta lba 0", and it is worth having again the next time that comes + * up. It is not worth having on the boot path. Set it to 4 to get it back. + */ +static unsigned int meta0_scan_sbs; +module_param(meta0_scan_sbs, uint, 0644); +MODULE_PARM_DESC(meta0_scan_sbs, + "Closed SBs to full-scan for META lba=0 after recover (default 0 = off)"); + +static bool allow_sigless_debug; +module_param(allow_sigless_debug, bool, 0644); +MODULE_PARM_DESC(allow_sigless_debug, + "If true, classify/recover without xrmw (default N — NAND wedge / fake META)"); + +static unsigned int sig_scan_blocks; +module_param(sig_scan_blocks, uint, 0644); +MODULE_PARM_DESC(sig_scan_blocks, + "FPart assignment scan: tail blocks (0 = vfl_tail)"); + +static unsigned int fpart_assign_pages = 1; +module_param(fpart_assign_pages, uint, 0644); +MODULE_PARM_DESC(fpart_assign_pages, + "Pages per tail block to scan for META 0x30 assignment (default 1 = page0)"); + +static bool sig_brute_scan; +module_param(sig_brute_scan, bool, 0644); +MODULE_PARM_DESC(sig_brute_scan, + "META 0x30 page0 brute (default N — PIO spare is not Sogeti)"); + +static bool payload_magic_scan; +module_param(payload_magic_scan, bool, 0644); +MODULE_PARM_DESC(payload_magic_scan, + "Debug-only data xrmw/wrmx hunt (default N — FPart uses META 0x30)"); + +/* Kept so existing insmod lines do not fail. Recovery always runs at probe. */ +static bool ftl_auto_map __maybe_unused; +module_param(ftl_auto_map, bool, 0644); +MODULE_PARM_DESC(ftl_auto_map, "ignored; Whimory always recovers at insmod"); + +static bool fpart_auto_scan __maybe_unused; +module_param(fpart_auto_scan, bool, 0644); +MODULE_PARM_DESC(fpart_auto_scan, "ignored; host slices created after LBA0"); + +static struct whimory *whimory_dev; +static struct platform_device *ftl_pdev; + +static void whimory_l2v_find_frag(struct whimory *w); +static void whimory_l2v_cache_flush(struct whimory *w); +static void whimory_l2v_free_tree(struct whimory *w, u32 node_idx, u32 root_idx); +static int whimory_l2v_update_packed(struct whimory *w, u32 ridx, u32 off, + u32 span, u32 vba); +static int n31_sftl_read_lba(struct whimory *w, u32 lba, void *buf, + bool allow_blank); +static bool whimory_audit_fmss_lba(u32 fmss_lba); +static void whimory_note_payload_strings(struct whimory *w, const u8 *data, + unsigned int len); +static void whimory_dump_vba_page(struct whimory *w, u32 vba, u32 fmss_lba); + +static u64 whimory_weave48(const u8 *m) +{ + return (u64)get_unaligned_le16(m + 2) | + ((u64)get_unaligned_le32(m + 4) << 16); +} + +/* + * CS span4/rec4112 page read — real 4× META (glass-proven). Used for + * classify / BTOC / open-SB / CXT / VBA reads when meta_dma_read=0. + */ +static int whimory_cs_read_page(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, void *data, size_t data_len, + void *meta, size_t meta_len) +{ + struct s5l8740_cs_page *csp; + unsigned int s; + int ret; + + if (!w || !data || data_len < S5L8740_NAND_PAGE_SIZE) + return -EINVAL; + csp = w->sftl.cs_page; + if (!csp) + return -ENOMEM; + + ret = s5l8740_nand_cs_phys_read((u8)ce, (u8)cau, (u16)block, (u8)page, + csp); + if (ret) + return ret; + + for (s = 0; s < S5L8740_NAND_SLOTS_PER_PAGE; s++) + memcpy((u8 *)data + s * S5L8740_NAND_SLOT_DATA, + csp->data[s], S5L8740_NAND_SLOT_DATA); + + if (meta && meta_len) { + size_t copy = min_t(size_t, meta_len, S5L8740_NAND_META_SIZE); + + memset(meta, 0xff, meta_len); + for (s = 0; s < S5L8740_NAND_SLOTS_PER_PAGE && + (s + 1) * WHIMORY_META_SIZE <= copy; s++) + memcpy((u8 *)meta + s * WHIMORY_META_SIZE, + csp->meta_raw[s], WHIMORY_META_SIZE); + } + return 0; +} + +static bool whimory_meta_any_btoc(const u8 *meta64) +{ + unsigned int s; + + if (!meta64) + return false; + for (s = 0; s < WHIMORY_VBAS_PER_PAGE; s++) { + if (meta64[s * WHIMORY_META_SIZE] == WHIMORY_META_TYPE_BTOC) + return true; + } + return false; +} + +static bool whimory_meta_slot0_or_any_cxt(const u8 *meta64) +{ + unsigned int s; + + if (!meta64) + return false; + if (meta64[0] == WHIMORY_META_TYPE_SFTL_CXT) + return true; + for (s = 1; s < WHIMORY_VBAS_PER_PAGE; s++) { + if (meta64[s * WHIMORY_META_SIZE] == WHIMORY_META_TYPE_SFTL_CXT) + return true; + } + return false; +} + +static bool whimory_page_blank(const u8 *p, unsigned int n) +{ + unsigned int i; + u8 all_ff = 0xff, all_00 = 0; + + if (!p || !n) + return true; + for (i = 0; i < n; i++) { + all_ff &= p[i]; + all_00 |= p[i]; + } + return all_ff == 0xff || all_00 == 0; +} + +static bool whimory_meta_erased(const u8 *m, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + if (m[i] != 0xff) + return false; + } + return true; +} + +/* + * Cheap "is this block empty" probe. + * + * classify visits every block and, for seventy percent of them, only needs + * to answer "empty?" -- 64 bytes of slot-0 data and slot-0's 16-byte meta. + * The full page read moves 16448 bytes to obtain those 80. This reads one + * record, 4112 bytes. + * + * Deliberately narrow: it answers empty/not-empty and nothing else. Every + * other question classify asks, CXT detection above all, reads all four + * slots' meta, so a "no" here must be followed by the full read. Returning + * a bool rather than filling meta0 makes that impossible to get wrong by + * accident. + */ +/* + * Read slot 0 of a page: 4112 bytes instead of 16448. + * + * The controller takes a column offset and length -- col_len packs the + * length in the low half and the start column in the high half -- so a + * one-record read is a genuine short transfer, not a full page quietly + * discarded. Slot 0 carries the first 4096 data bytes and its own 16-byte + * meta, which between them settle almost every question classify asks. + * + * The exception is whimory_meta_slot0_or_any_cxt(), which scans all four + * slots, so a meta this read cannot classify must escalate to the full + * page. That is rare: on this volume the meta type histogram is 5492 + * erased, 2268 data, 2 data2, 2 at 0x4b and 76 zero, so slot 0 answers for + * all but the last hundred or so. + * + * Returns 0 with data and meta filled, or negative on a read error. + */ +static int whimory_cs_read_slot0(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, const u8 **data, + u8 *meta0) +{ + struct s5l8740_cs_page *csp = w->sftl.cs_page; + unsigned int i; + int ret; + + if (!csp) + return -ENOMEM; + ret = s5l8740_nand_cs_phys_read_slot0((u8)ce, (u8)cau, (u16)block, + (u8)page, csp); + if (ret) + return ret; + for (i = 0; i < WHIMORY_META_SIZE; i++) + meta0[i] = csp->meta_raw[0][i]; + *data = csp->data[0]; + return 0; +} + +/* + * Batched page-0 prefetch for the classify scan. + * + * Classify reads page 0 of every block on the volume -- about 7840 of them + * -- and does nothing between one read and the next that depends on the + * previous answer. That makes it the one pass where the per-kick cost is + * pure waste: measured on the device at kick_us=1536 against a NAND tR of + * 60-80 us, roughly 1.45 ms of every 1.54 ms read is the sequencer being + * set up and torn down for a single page. + * + * So set it up once for sixteen. The window slides forward through the + * block range and every read the loop asks for is served from it, including + * the full 4 KiB slot the escalation path wants -- there is no point + * batching the blank test and then reading the page again for the blocks + * that are not blank. + * + * Refill failure is not fatal. batch_valid goes false and the caller falls + * back to the single-page path for that window, which is what the scan did + * before any of this existed. + */ +static void whimory_prefetch_reset(struct whimory *w) +{ + w->sftl.pf_valid = false; + w->sftl.pf_count = 0; +} + +static int whimory_prefetch_slot0(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int nscan, const u8 **data, + u8 *meta0) +{ + struct whimory_sftl *s = &w->sftl; + u16 blocks[WHIMORY_PF_SLOTS]; + unsigned int i, n, idx; + int ret; + + if (!batch_classify || !s->pf_data) + goto fallback; + + if (!(s->pf_valid && ce == s->pf_ce && cau == s->pf_cau && + block >= s->pf_first && block < s->pf_first + s->pf_count)) { + n = nscan - block; + if (n > WHIMORY_PF_SLOTS) + n = WHIMORY_PF_SLOTS; + if (n < 1) + goto fallback; + for (i = 0; i < n; i++) + blocks[i] = (u16)(block + i); + ret = s5l8740_nand_cs_read_meta_batch((u8)ce, (u8)cau, blocks, + 0, n, s->pf_meta, + s->pf_data, + S5L8740_NAND_SLOT_DATA); + if (ret) { + /* + * Say so once per mount rather than per window. A + * batch that cannot run is a speed regression, not a + * correctness one, and the fallback below is the + * path the scan used before batching existed. + */ + if (!s->pf_failed) { + s->pf_failed = true; + dev_warn(w->dev, + "SFTL batch prefetch failed (%d), classify falls back to single-page reads\n", + ret); + } + whimory_prefetch_reset(w); + goto fallback; + } + s->pf_ce = ce; + s->pf_cau = cau; + s->pf_first = block; + s->pf_count = n; + s->pf_valid = true; + s->pf_kicks++; + + /* + * Prove the descriptor walk once, on the first window of the mount. + * + * The list format is read out of the 4EDDDC decomp, and the one thing + * the decomp does not answer is whether the CS microcode blob we load + * walks a multi-descriptor list or executes the first pair and stops + * at the terminator. If it stops, every entry past index 0 is stale + * buffer rather than the block that was asked for -- and a classify + * pass that believes it would mismarks most of the volume. + * + * So the second entry gets read again the slow way and compared. One + * extra page read per mount buys the right to default this on; a + * mismatch turns batching off for the rest of the mount and the scan + * carries on down the path it used before. + */ + if (!s->pf_checked && n >= 2) { + const u8 *vd = NULL; + u8 vm[WHIMORY_META_SIZE]; + + s->pf_checked = true; + /* + * Only a read that succeeds and disagrees is evidence. A + * verification read that fails outright says nothing about + * the batch -- dma_one_shot disarms CS after a single read, + * so a bare -EPERM here is routine and it comes back as an + * all-zero meta, which is indistinguishable from a real + * disagreement if you only compare bytes. Treating that as a + * mismatch would switch batching off on every mount. + */ + if (!whimory_cs_read_slot0(w, ce, cau, block + 1, 0, &vd, vm) && + (memcmp(vm, s->pf_meta + S5L8740_NAND_BATCH_META_SIZE, + WHIMORY_META_SIZE) || + memcmp(vd, s->pf_data + S5L8740_NAND_SLOT_DATA, 64))) { + dev_warn(w->dev, + "SFTL batch prefetch does not match single-page reads at blk=%u -- the CS blob is not walking the descriptor list; batching off\n", + block + 1); + batch_classify = false; + whimory_prefetch_reset(w); + goto fallback; + } + dev_info(w->dev, + "SFTL batch prefetch verified against single-page reads, %u blocks per kick\n", + n); + } + + } + + idx = block - s->pf_first; + memcpy(meta0, s->pf_meta + idx * S5L8740_NAND_BATCH_META_SIZE, + WHIMORY_META_SIZE); + *data = s->pf_data + (size_t)idx * S5L8740_NAND_SLOT_DATA; + s->pf_hits++; + return 0; + +fallback: + return whimory_cs_read_slot0(w, ce, cau, block, 0, data, meta0); +} + + +/* + * Can slot 0 alone classify this block? + * + * The only thing classify needs the other three slots for is + * whimory_meta_slot0_or_any_cxt(), which exists to catch a partially + * written CXT page whose slot 0 never got its meta. So the question is + * narrower than it looks: does this slot-0 type rule out a CXT hiding in + * slots 1..3? + * + * Plain user data and a slot-0 CXT do. A page is written slot 0 first with + * one kind of content, so DATA in slot 0 means the page is user data, and + * SFTL_CXT in slot 0 answers the CXT question outright. + * + * Everything else escalates, deliberately -- an erased or zero or + * unrecognised slot-0 meta is exactly the partial-write shape that + * _or_any_cxt() was written for, and misreading one of those loses a + * checkpoint. On this volume that leaves about eighty blocks paying for a + * full read against roughly 2270 settling from slot 0. + */ +static bool whimory_meta0_is_conclusive(const u8 *meta0) +{ + switch (meta0[0]) { + case WHIMORY_META_TYPE_DATA: + case WHIMORY_META_TYPE_DATA2: + case WHIMORY_META_TYPE_SFTL_CXT: + return true; + default: + return false; + } +} + + +static bool whimory_meta_is_user_data(const struct whimory_meta *m) +{ + return m->type == WHIMORY_META_TYPE_DATA || + m->type == WHIMORY_META_TYPE_DATA2; +} + +static bool whimory_meta_is_cxt_base(const u8 *m, u32 vba_ofs) +{ + return m[0] == WHIMORY_META_TYPE_SFTL_CXT && + m[1] == WHIMORY_CXT_TAG_BASE && + vba_ofs == 0; +} + +static bool whimory_meta_is_data_raw(const u8 *m) +{ + return m[0] == WHIMORY_META_TYPE_DATA || + m[0] == WHIMORY_META_TYPE_DATA2; +} + +/* + * Does this metadata belong to a block that is part of a superblock? + * + * Membership from one record, for the blocks above user_blocks that the + * classify loop does not enumerate but that VBAs still name. Data, a BTOC + * and a checkpoint record all say the FTL allocated this block to a + * superblock; an erased record says it did not, and so does the all-zero + * type 00 shape that 76 blocks on this unit carry at both page 0 and page + * 127. + */ +static bool whimory_meta_is_member(const u8 *m) +{ + if (whimory_meta_erased(m, WHIMORY_META_SIZE)) + return false; + /* + * One 16-byte record, not a 64-byte page spare -- the batched plane + * scan returns a single meta per (block, page), so the four-slot + * helpers cannot be used here without reading past the end of it. + */ + return whimory_meta_is_data_raw(m) || + m[0] == WHIMORY_META_TYPE_BTOC || + m[0] == WHIMORY_META_TYPE_SFTL_CXT; +} + +static bool whimory_special_lba(u32 lba) +{ + return (lba & 0xFFFF0000u) == WHIMORY_SPECIAL_LBA || + lba == WHIMORY_LBA_BLANK || lba == WHIMORY_LBA_DELETED; +} + +static u32 whimory_vfl_phys(struct whimory *w, u32 cau, u32 virt) +{ + /* + * The N31 VFL is an identity map over blocks_per_cau: physical + * block number equals virtual block number. + * + * The u16 table at CXT +0x200 looks like a remap table but is not + * one — it is the VFL context copy journal, where each value is + * index | (generation << 15) and 0xC070 marks a free slot. A failed + * user block keeps its VBN and moves to another CAU instead, which + * the bank bitmap records. + */ + if (cau >= w->geom.num_cau || !w->vfl.remap[cau]) + return virt; + if (virt >= w->geom.blocks_per_cau) + return virt; + return w->vfl.remap[cau][virt]; +} + +/* + * Which CAUs carry this virtual block, for the CAU-substitution rule. + * + * This is not superblock bank membership, and reading it as if it were is + * what let the address decode assume four banks everywhere. Its domain is + * CAUs -- num_cau entries, no CE in it at all -- and it answers only "if + * this block failed on its own CAU, which CAU holds it now". vfl.bank_mask + * behind it is still a stub: allocated all-ones in n31_vfl_init() and never + * filled from flash, which makes whimory_vfl_bank() the identity and is why + * nothing has ever depended on it. + * + * The count that address arithmetic needs -- how many (ce, cau) banks a + * superblock spans -- is sftl.sb_bank_mask, built by classify. See + * whimory_sb_banks(). + */ +static u32 whimory_vfl_banks_in_vbn(struct whimory *w, u32 vbn, u8 *out, + u32 out_max) +{ + u8 mask; + u32 n = 0, b; + + if (w->vfl.cached_vbn == (u16)vbn && w->vfl.cached_n) { + n = min_t(u32, w->vfl.cached_n, out_max); + if (out) + memcpy(out, w->vfl.cached_banks, n); + return w->vfl.cached_n; + } + mask = 0; + if (w->vfl.bank_mask && vbn < w->geom.blocks_per_cau) { + u32 stride = w->vfl.bank_stride ? w->vfl.bank_stride : 1; + const u8 *row = w->vfl.bank_mask + stride * vbn; + u32 b; + + for (b = 0; b < w->geom.num_cau && b < 8; b++) { + u32 bi = b >> 3; + + if (bi < stride && (row[bi] & (1u << (b & 7)))) + mask |= (u8)(1u << b); + } + } + if (!mask) + mask = (1u << w->geom.num_cau) - 1; + for (b = 0; b < w->geom.num_cau && b < 8; b++) { + if (!(mask & (1u << b))) + continue; + if (out && n < out_max) + out[n] = (u8)b; + if (n < S5L8740_NAND_MAX_CAU) + w->vfl.cached_banks[n] = (u8)b; + n++; + } + w->vfl.cached_vbn = (u16)vbn; + w->vfl.cached_n = (u8)n; + return n; +} + +static u32 whimory_vfl_bank(struct whimory *w, u32 cau, u32 vblock) +{ + u8 banks[S5L8740_NAND_MAX_CAU]; + u32 n, i; + + n = whimory_vfl_banks_in_vbn(w, vblock, banks, ARRAY_SIZE(banks)); + if (!n) + return cau; + for (i = 0; i < n; i++) { + if (banks[i] == (u8)cau) + return cau; + } + return banks[0]; +} + +static u32 whimory_vfl_virt(struct whimory *w, u32 cau, u32 phys) +{ + u32 i, n; + + if (cau >= w->geom.num_cau || !w->vfl.remap[cau]) + return phys; + n = w->geom.blocks_per_cau; + for (i = 0; i < n; i++) { + if (w->vfl.remap[cau][i] == phys) + return i; + } + return phys; +} + +/* + * Removed with the move to the native VBA space: nothing builds a VBA from + * a bank-major superblock index any more. whimory_sb_ofs_to_vba() is the + * replacement and converts at the boundary instead. + */ + +/* + * Resolve a (ce, cau, vblock) triple to the hardware that holds it. + * + * Two steps, and they have to happen in this order: whimory_vfl_bank() + * answers which CAU actually carries this virtual block, and only then does + * whimory_vfl_phys() translate the block number within that CAU. + * + * n31_vfl_read_vba() and whimory_l2v_search_phys() did both. The BTOC + * confirm pass and the checkpoint walk did only the second, with the + * unremapped CAU -- so if the bank bitmap were ever populated, the confirm + * pass would key the L2V on metadata read from one plane while every later + * read fetched another, a self-inflicted lba mismatch across the whole map. + * Both helpers are the identity while vfl_remap_mode=off, which is why it + * has never bitten; one helper means it cannot start. + */ +static void whimory_vfl_resolve(struct whimory *w, u32 vblock, u32 *cau, + u32 *pblock) +{ + *cau = whimory_vfl_bank(w, *cau, vblock); + *pblock = whimory_vfl_phys(w, *cau, vblock); +} + +/* + * The number of banks a superblock may have, and the VBA stride of one + * virtual block. + * + * The stride is the *maximum* -- max_banks * pages_per_sb * vbas_per_page, + * 2048 here -- not the number a given superblock uses. s_vfl.c builds an + * address as + * + * (vbn * max_banks * pages_per_sb + page * nbanks + bank_ofs) + * * vbas_per_page + slot + * + * (sub_4EAD34 forward, sub_4EAE40 back), so every virtual block owns the + * same 2048-address window and a short superblock simply leaves the tail of + * its window unused. Confirmed on this unit: CXT extents land exactly on + * vbn * 2048 boundaries -- vba 0xa0000 is vblock 320 page 0, 0x9d800 is + * vblock 315 page 0. + */ +static u32 whimory_max_banks(const struct whimory *w) +{ + u32 n = w->geom.num_ce * w->geom.num_cau; + + return n ? n : 1; +} + +static u32 whimory_vbas_per_vblock(const struct whimory *w) +{ + return whimory_max_banks(w) * w->sftl.pages_per_sb * + w->sftl.vbas_per_page; +} + +/* + * The banks this virtual block spans, and how many. + * + * An empty mask means classify never reached this block -- the region + * above user_blocks, or a run cut short by scan_blocks. Those fall back to + * every bank, which is what this driver assumed everywhere before the map + * existed, so an unscanned region behaves exactly as it used to. + */ +static u32 whimory_sb_banks(const struct whimory *w, u32 vblock, u8 *mask_out) +{ + u32 maxb = whimory_max_banks(w); + u8 mask = 0; + + if (w->sftl.sb_bank_mask && vblock < w->sftl.sb_bank_blocks) + mask = w->sftl.sb_bank_mask[vblock]; + if (!mask) + mask = (u8)((1u << maxb) - 1); + if (mask_out) + *mask_out = mask; + return hweight8(mask); +} + +/* The bank sitting at position idx of a mask, counting from bit 0. */ +static u32 whimory_bank_at(u8 mask, u32 idx) +{ + u32 b; + + for (b = 0; b < 8; b++) { + if (!(mask & (1u << b))) + continue; + if (!idx) + return b; + idx--; + } + return 0; +} + +/* + * Record that (ce, cau) carries this virtual block. + * + * Called from classify for every block that came back as a real superblock + * member -- closed, open, or CXT. Empty blocks and blocks nothing could + * recognise deliberately do not call this: on this unit the 76 blocks whose + * page 0 and page 127 both read as type 00 are exactly the banks their + * superblocks do not span, and they are what the classify histogram has + * been reporting as "unknown" all along. + */ +static void whimory_sb_bank_note(struct whimory *w, u32 vblock, u32 ce, + u32 cau) +{ + u32 bank = ce * w->geom.num_cau + cau; + + if (!w->sftl.sb_bank_mask || vblock >= w->sftl.sb_bank_blocks || + bank >= 8) + return; + w->sftl.sb_bank_mask[vblock] |= (u8)(1u << bank); +} + +/* Where a bank sits in a mask, or -1 when it is not a member. */ +static int whimory_bank_index(u8 mask, u32 bank) +{ + u32 b, n = 0; + + for (b = 0; b < 8 && b < bank; b++) + if (mask & (1u << b)) + n++; + if (bank >= 8 || !(mask & (1u << bank))) + return -1; + return (int)n; +} + +static u32 s_g_vba_to_sb(const struct whimory *w, u32 vba) +{ + u32 per_vb = whimory_vbas_per_vblock(w); + + /* + * Divide by the per-virtual-block stride, not by sftl.vbas_per_sb. + * vbas_per_sb is 512, the per-plane count the BTOC parsers size + * their arrays with; VBAs are packed over all four banks, so using + * it here reported a superblock index four times too large in every + * VBA_DIAG line. + */ + if (!per_vb) + return 0; + return vba / per_vb; +} + +static u32 s_g_vba_to_ofs(const struct whimory *w, u32 vba) +{ + u32 per_vb = whimory_vbas_per_vblock(w); + + if (!per_vb) + return 0; + return vba % per_vb; +} + +static u32 whimory_sb_index(const struct whimory *w, u32 ce, u32 cau, + u32 vblock) +{ + return (ce * w->geom.num_cau + cau) * w->sftl.user_blocks + vblock; +} + +/* + * The VBA space is the FTL's native one. + * + * Apple treats a superblock as the same virtual block across every + * (ce, cau) plane, so the plane index sits between the page and the slot: + * + * vba = vblock * (pages_per_sb * planes * vbas_per_page) + * + page * (planes * vbas_per_page) + * + plane * vbas_per_page + * + slot + * + * This used to be bank-major -- every (ce, cau, vblock) triple got its own + * superblock index -- which meant CXT VBAs had to be translated on the way + * in, and a run of consecutive CXT VBAs was only contiguous here within one + * 4-slot group, because the next group belonged to a different plane. The + * cost of that was not subtle: the CXT seed produced 236675 ranges for + * 938395 LBAs, just under four LBAs per range, when the same data in the + * native space is a few thousand contiguous runs. + * + * Matching the native layout removes the translation entirely and lets + * extents stay whole, which is what the range budget was fighting. + */ +/* + * How many virtual blocks a VBA may name. + * + * Not user_blocks. user_blocks is blocks_per_cau minus the VFL tail -- 1960 + * of 2088 here -- and it is the right number for "how much space may be + * allocated to the user". It is the wrong number for "which blocks may a + * stored VBA refer to", and using it as the range check silently discarded + * 81 CXT records: + * + * CXT_XLATE_FAIL vba=0x003e3883 (vblk=1991 pg=8 plane=0 slot=3) + * lba=841408 span=256 user_blocks=1960 + * + * Those are not corrupt entries. They cluster in vblocks 1987..1991, their + * spans are large and ordinary (93, 116, 125, 128, 256, 384), and their VBAs + * run contiguously through the plane interleave exactly as the arithmetic + * predicts -- 0x3e3883 + 256 lands on 0x3e3983 and the next record begins at + * 0x3e3984. That is the FTL telling us, correctly, where it put roughly two + * thousand LBAs around 839k-842k. We were throwing them away. + * + * The VFL is an identity map over blocks_per_cau, so any block below that is + * addressable and a VBA naming one is legitimate. + */ +static u32 whimory_vba_blocks(const struct whimory *w) +{ + if (w->geom.blocks_per_cau) + return w->geom.blocks_per_cau; + return w->sftl.user_blocks; +} + +static u32 whimory_pack_vba(const struct whimory *w, u32 ce, u32 cau, + u32 vblock, u32 page, u32 slot) +{ + u32 bank = ce * w->geom.num_cau + cau; + u32 per_vb = whimory_vbas_per_vblock(w); + u8 mask; + u32 nbanks = whimory_sb_banks(w, vblock, &mask); + int ofs = whimory_bank_index(mask, bank); + + /* + * A bank the map does not list as a member of this superblock. + * + * That is either a block classify could not read or a genuine + * derivation error, and either way the honest answer is the layout + * this driver used before the map existed: all banks, dense. It + * keeps pack and unpack mutual inverses, which is what the replay + * paths depend on -- they store an address and read it straight + * back, so a wrong bank count cancels for them and only the + * FTL-authored addresses (CXT extents, BTOC arrays) can go wrong. + */ + if (ofs < 0) { + nbanks = whimory_max_banks(w); + ofs = (int)bank; + } + + return vblock * per_vb + + (page * nbanks + (u32)ofs) * w->sftl.vbas_per_page + slot; +} + +/* + * Build a VBA from a bank-major superblock index and an in-superblock + * offset. The replay paths still enumerate one (ce, cau, vblock) at a + * time, which is a bank-major idea; this converts at the boundary so the + * stored VBA is native. + */ +static u32 whimory_sb_ofs_to_vba(const struct whimory *w, u32 sb_idx, u32 ofs) +{ + u32 per_ce = w->geom.num_cau * w->sftl.user_blocks; + u32 ce, cau, vblock, rem; + + if (!per_ce || !w->sftl.user_blocks || !w->sftl.vbas_per_page) + return 0; + ce = sb_idx / per_ce; + rem = sb_idx % per_ce; + cau = rem / w->sftl.user_blocks; + vblock = rem % w->sftl.user_blocks; + + return whimory_pack_vba(w, ce, cau, vblock, + ofs / w->sftl.vbas_per_page, + ofs % w->sftl.vbas_per_page); +} + +/* The virtual block a bank-major superblock index names. */ +static u32 whimory_cxt_sb_vblock(const struct whimory *w, u32 sb_idx) +{ + u32 per_ce = w->geom.num_cau * w->sftl.user_blocks; + + if (!per_ce || !w->sftl.user_blocks) + return 0; + return (sb_idx % per_ce) % w->sftl.user_blocks; +} + + +static int whimory_unpack_vba(const struct whimory *w, u32 vba, + u32 *ce, u32 *cau, u32 *vblock, + u32 *page, u32 *slot) +{ + if (!w->sftl.vbas_per_sb || !w->sftl.vbas_per_page || + !w->sftl.user_blocks) + return -EINVAL; + /* Exact inverse of whimory_pack_vba(); see the layout there. */ + { + u32 per_vb = whimory_vbas_per_vblock(w); + u32 rem, unit, nbanks, bank; + u8 mask; + + if (!per_vb) + return -EINVAL; + + *vblock = vba / per_vb; + rem = vba % per_vb; + *slot = rem % w->sftl.vbas_per_page; + unit = rem / w->sftl.vbas_per_page; + + nbanks = whimory_sb_banks(w, *vblock, &mask); + *page = unit / nbanks; + bank = whimory_bank_at(mask, unit % nbanks); + *ce = bank / w->geom.num_cau; + *cau = bank % w->geom.num_cau; + } + + if (*ce >= w->geom.num_ce || *cau >= w->geom.num_cau) + return -ERANGE; + if (*vblock >= whimory_vba_blocks(w)) + return -ERANGE; + if (*page >= w->sftl.pages_per_sb) + return -ERANGE; + return 0; +} + +static void whimory_set_status(struct whimory *w, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(w->status, sizeof(w->status), fmt, ap); + va_end(ap); +} + +/* ------------------------------------------------------------------ */ +/* Interval map: weave-order LBA→VBA, then packed into the L2V tree. */ +/* ------------------------------------------------------------------ */ + +static struct whimory_range *whimory_range_find(struct rb_root *root, u32 lba) +{ + struct rb_node *n = root->rb_node; + + while (n) { + struct whimory_range *r = rb_entry(n, struct whimory_range, rb); + + if (lba < r->start) + n = n->rb_left; + else if (lba >= r->start + r->len) + n = n->rb_right; + else + return r; + } + return NULL; +} + +/* + * Leftmost range that can overlap [lba, ...) — i.e. the first with + * start + len > lba. Ranges are disjoint and keyed by start, so r_end is + * monotonic in tree order and the predicate is a valid binary search. + * + * Callers used to walk from rb_first(), which made every L2V update + * O(ranges) and the whole recover O(ranges^2). At wide scan_blocks that + * spins the CPU long enough to starve RNDIS and trip the watchdog. + */ +static struct whimory_range *whimory_range_lower(struct rb_root *root, u32 lba) +{ + struct rb_node *n = root->rb_node; + struct whimory_range *best = NULL; + + while (n) { + struct whimory_range *r = rb_entry(n, struct whimory_range, rb); + + if (r->start + r->len > lba) { + best = r; + n = n->rb_left; + } else { + n = n->rb_right; + } + } + return best; +} + +static int whimory_range_link(struct rb_root *root, struct whimory_range *n) +{ + struct rb_node **link = &root->rb_node, *parent = NULL; + + while (*link) { + struct whimory_range *r = rb_entry(*link, struct whimory_range, + rb); + + parent = *link; + if (n->start < r->start) + link = &(*link)->rb_left; + else + link = &(*link)->rb_right; + } + rb_link_node(&n->rb, parent, link); + rb_insert_color(&n->rb, root); + return 0; +} + +static int whimory_range_split(struct whimory *w, struct whimory_range *r, + u32 at) +{ + struct whimory_range *right; + u32 left_len; + + if (at <= r->start || at >= r->start + r->len) + return 0; + right = kzalloc(sizeof(*right), GFP_KERNEL); + if (!right) + return -ENOMEM; + left_len = at - r->start; + right->start = at; + right->len = r->len - left_len; + right->vba = r->vba + left_len; + right->weave = r->weave; + right->src = r->src; + r->len = left_len; + whimory_range_link(&w->ranges, right); + w->sftl.range_nodes++; + w->sftl.map_gen++; + return 0; +} + +static void whimory_range_erase(struct whimory *w, struct whimory_range *r) +{ + rb_erase(&r->rb, &w->ranges); + kfree(r); + w->sftl.map_gen++; + if (w->sftl.range_nodes) + w->sftl.range_nodes--; +} + +static int whimory_range_insert_new(struct whimory *w, u32 start, u32 len, + u32 vba) +{ + struct whimory_range *n; + + if (!len) + return 0; + /* + * Backstop. whimory_range_update() checks the budget before it + * splits or erases anything, so reaching here means a caller went + * around it. Returning 0 -- success, having inserted nothing -- is + * what turned working mappings into holes; say -ENOSPC instead so a + * new caller fails loudly rather than silently truncating the map. + */ + if (max_range_nodes && w->sftl.range_nodes >= max_range_nodes) { + w->sftl.range_budget_stop++; + return -ENOSPC; + } + n = kzalloc(sizeof(*n), GFP_KERNEL); + if (!n) + return -ENOMEM; + n->start = start; + n->len = len; + n->vba = vba; + n->weave = w->sftl.claim_weave; + n->src = w->sftl.claim_source; + whimory_range_link(&w->ranges, n); + w->sftl.range_nodes++; + w->sftl.map_gen++; + return 0; +} + +/* Merge r with either neighbour when both LBA and VBA stay contiguous. */ +static bool whimory_range_joinable(const struct whimory_range *a, + const struct whimory_range *b) +{ + if (a->start + a->len != b->start || a->vba + a->len != b->vba) + return false; + return range_coalesce || a->weave == b->weave; +} + +static void whimory_range_coalesce_at(struct whimory *w, u32 start) +{ + struct whimory_range *r, *prev, *next; + struct rb_node *p, *q; + + r = whimory_range_find(&w->ranges, start); + if (!r) + return; + p = rb_prev(&r->rb); + if (p) { + prev = rb_entry(p, struct whimory_range, rb); + if (whimory_range_joinable(prev, r)) { + prev->len += r->len; + if (r->weave < prev->weave) + prev->weave = r->weave; + whimory_range_erase(w, r); + r = prev; + } + } + q = rb_next(&r->rb); + if (q) { + next = rb_entry(q, struct whimory_range, rb); + if (whimory_range_joinable(r, next)) { + r->len += next->len; + if (next->weave < r->weave) + r->weave = next->weave; + whimory_range_erase(w, next); + } + } +} + +/* + * Apply as much of [lba, lba+span) as this claim is entitled to. + * + * Returns the number of LBAs consumed -- always at least one, so the caller + * cannot spin -- or a negative errno. *applied says whether that run was + * written to the map or passed over. + * + * TWO THINGS THIS USED TO GET WRONG + * + * It rejected the whole span the moment any part of it was covered by a + * newer weave: "return 1" from inside the scan, with the comment "stale -- + * do not touch packed L2V". A BTOC or CXT run of 256 LBAs where one LBA had + * been superseded therefore lost all 256, and the 255 with no competing + * claim kept whatever was there before, which was frequently nothing. The + * run is now split at the newer range instead: the part in front of it is + * applied, the overlap is skipped, and the caller comes back for the rest. + * + * And it erased before it knew whether it could insert. The split and erase + * ran first, then whimory_range_insert_new() returned 0 -- success -- + * without inserting anything once max_range_nodes was reached. Past the + * ceiling every update turned a working mapping into a hole and reported + * success while doing it. The budget is checked up front now, before + * anything is destroyed, and a claim that cannot be recorded is passed over + * intact rather than half-applied. + */ +static int whimory_range_update(struct whimory *w, u32 lba, u32 span, u32 vba, + bool *applied) +{ + bool is_unmap = vba >= w->l2v.invalid_vba; + struct whimory_range *blocker = NULL; + struct whimory_range *hit; + struct rb_node *node, *next; + u32 run, end; + int ret; + + *applied = false; + if (!span) + return 0; + if (whimory_special_lba(lba)) + return span; + + /* First range in the way that a newer writer already owns. */ + { + struct whimory_range *first = whimory_range_lower(&w->ranges, + lba); + struct rb_node *n = first ? &first->rb : NULL; + + while (n) { + struct whimory_range *r = rb_entry(n, + struct whimory_range, + rb); + + if (r->start >= lba + span) + break; + if (r->weave > w->sftl.claim_weave) { + blocker = r; + break; + } + n = rb_next(n); + } + } + + if (blocker && blocker->start <= lba) { + /* + * The head of the run is owned by something newer. Skip + * exactly the overlap and let the caller retry past it -- + * the tail may be entirely unclaimed. + */ + u32 blocker_end = blocker->start + blocker->len; + + w->sftl.stale_mapping_rejected++; + run = min(lba + span, blocker_end) - lba; + return (int)run; + } + + run = blocker ? blocker->start - lba : span; + end = lba + run; + if (WARN_ON_ONCE(end < lba)) /* would wrap; cannot with a + * non-special lba and + * run <= WHIMORY_L2V_ROOT_SPAN */ + return (int)run; + + /* + * Budget first, and only for claims that add a node. An unmap only + * erases, so it is net-negative on the node count and is always + * allowed through -- refusing it would strand a stale mapping. + * + * The two splits below each allocate, and they run before the + * insert. Checking for bare equality would let a claim past the gate + * with one node of headroom, split twice, and then fail the insert + * having already erased -- which is the exact hole this check exists + * to prevent, just moved to the boundary. Reserve the splits. + */ + if (!is_unmap && max_range_nodes && + w->sftl.range_nodes + 2 >= max_range_nodes) { + w->sftl.range_budget_stop++; + return (int)run; + } + + hit = whimory_range_find(&w->ranges, lba); + if (hit) { + ret = whimory_range_split(w, hit, lba); + if (ret) + return ret; + } + hit = whimory_range_find(&w->ranges, end - 1); + if (hit && hit->start < end) { + ret = whimory_range_split(w, hit, end); + if (ret) + return ret; + } + + hit = whimory_range_lower(&w->ranges, lba); + node = hit ? &hit->rb : NULL; + while (node) { + struct whimory_range *r = rb_entry(node, struct whimory_range, + rb); + + next = rb_next(node); + if (r->start >= end) + break; + if (r->start >= lba && r->start + r->len <= end) + whimory_range_erase(w, r); + node = next; + cond_resched(); + } + + /* True unmap: erase only; do not insert invalid_vba placeholders. */ + if (is_unmap) { + whimory_range_coalesce_at(w, lba); + *applied = true; + return (int)run; + } + + ret = whimory_range_insert_new(w, lba, run, vba); + if (ret) + return ret; + whimory_range_coalesce_at(w, lba); + *applied = true; + return (int)run; +} + +/* + * L2V_Update: split at 0x8000 root boundaries, then insert. + * The interval map is the RO observable of the live tree. + * Returns 0 on success (including stale-skip of a chunk), <0 on OOM/error. + */ +static int whimory_l2v_update(struct whimory *w, u32 lba, u32 span, u32 vba) +{ + bool is_unmap = vba >= w->l2v.invalid_vba; + + while (span) { + u32 chunk = WHIMORY_L2V_ROOT_SPAN - + (lba & (WHIMORY_L2V_ROOT_SPAN - 1)); + bool applied; + u32 done; + int ret; + + if (chunk > span) + chunk = span; + /* + * range_update consumes as much of the chunk as this claim + * is entitled to, which may be less than all of it when a + * newer weave owns part of the range. It always consumes at + * least one LBA, so this loop always advances. + */ + ret = whimory_range_update(w, lba, chunk, vba, &applied); + if (ret < 0) + return ret; + done = (u32)ret; + if (WARN_ON_ONCE(!done || done > chunk)) + return -EINVAL; + /* + * A chunk can now take several passes -- one per newer range + * standing in the way -- so this loop is no longer bounded by + * a couple of iterations. Up to WHIMORY_L2V_ROOT_SPAN of + * them, if the tree happens to alternate. + */ + cond_resched(); + if (!applied) { + /* Skipped: leave the packed L2V alone for this run. */ + span -= done; + lba += done; + if (!is_unmap) + vba += done; + continue; + } + chunk = done; + + w->sftl.l2v_update_calls++; + if (is_unmap) + w->sftl.l2v_unmap_calls++; + + if (w->l2v.root && w->l2v.num_roots) { + u32 ridx = lba >> 15; + u8 *rec; + u16 ver, node_idx; + + if (ridx < w->l2v.num_roots) { + rec = w->l2v.root + ridx * WHIMORY_L2V_ROOT_REC_SIZE; + ver = get_unaligned_le16(rec + 4); + if (ver == 0xffff) + ver = 0; + put_unaligned_le16(ver + 1, rec + 4); + /* + * whole-root unmap (off=0, span=0x8000, + * vba=invalid) frees the tree. + */ + if (!(lba & 0x7fff) && + chunk == WHIMORY_L2V_ROOT_SPAN && + is_unmap) { + node_idx = get_unaligned_le16(rec); + if (node_idx != WHIMORY_L2V_INVALID_ROOT) + whimory_l2v_free_tree(w, + node_idx, + ridx); + put_unaligned_le16( + WHIMORY_L2V_INVALID_ROOT, rec); + } + } + w->l2v.updates++; + w->l2v.gen++; + if (w->l2v.updates >= WHIMORY_L2V_UPDATE_REPACK) + w->l2v.updates = 0; + } + /* + * whimory_l2v_update_packed() repacks a whole root, and + * collecting a root walks the interval map. Doing that per + * update makes replay O(updates x ranges) — the tail of a full + * recover crawled to ~14 s per superblock. The interval map is + * the lookup authority; pack once at the end instead. + */ + if (w->l2v.root && w->l2v.num_roots && !w->l2v_defer_pack) { + u32 ridx = lba >> 15; + bool whole_unmap = !(lba & 0x7fff) && + chunk == WHIMORY_L2V_ROOT_SPAN && + is_unmap; + + if (ridx < w->l2v.num_roots && !whole_unmap) { + ret = whimory_l2v_update_packed(w, ridx, + lba & 0x7fff, chunk, vba); + if (ret) + dev_dbg(w->dev, + "L2V packed update r=%u %d\n", + ridx, ret); + } + } + span -= chunk; + lba += chunk; + if (vba < w->l2v.invalid_vba) + vba += chunk; + } + return 0; +} + +static void whimory_range_free(struct whimory *w) +{ + struct rb_node *n; + + while ((n = rb_first(&w->ranges))) { + struct whimory_range *r = rb_entry(n, struct whimory_range, rb); + + whimory_range_erase(w, r); + } + w->ranges = RB_ROOT; + w->sftl.range_nodes = 0; + w->sftl.map_gen++; + whimory_l2v_cache_flush(w); +} + +/* ------------------------------------------------------------------ */ +/* L2V init / lookup / tree pack , */ +/* ------------------------------------------------------------------ */ + +static void whimory_l2v_free(struct whimory *w) +{ + kvfree(w->l2v.root); + kvfree(w->l2v.nodes); + kvfree(w->l2v.leaf_scratch); + w->l2v.root = NULL; + w->l2v.nodes = NULL; + w->l2v.leaf_scratch = NULL; + w->l2v.num_roots = 0; + w->l2v.nodepool_bytes = 0; + w->l2v.free_head = WHIMORY_L2V_INVALID_ROOT; + w->l2v.free_count = 0; +} + +/* L2V_Mem.c— intrusive free list in node[0]. */ +static void whimory_l2v_mem_free(struct whimory_l2v *l2v, u32 idx) +{ + u8 *node; + u32 n = l2v->nodepool_bytes / WHIMORY_L2V_NODE_SIZE; + + if (!l2v->nodes || idx >= n) + return; + node = l2v->nodes + idx * WHIMORY_L2V_NODE_SIZE; + put_unaligned_le32(l2v->free_head, node); + l2v->free_head = idx; + l2v->free_count++; +} + +static void whimory_l2v_mem_reset(struct whimory_l2v *l2v) +{ + u32 n = l2v->nodepool_bytes / WHIMORY_L2V_NODE_SIZE; + s32 j; + + l2v->free_head = WHIMORY_L2V_INVALID_ROOT; + l2v->free_count = 0; + l2v->nodes_used = 0; + if (!l2v->nodes || !n) + return; + for (j = (s32)n - 1; j >= 0; j--) + whimory_l2v_mem_free(l2v, (u32)j); +} + +static u32 whimory_l2v_alloc_node(struct whimory_l2v *l2v) +{ + u32 idx, next; + u8 *node; + u32 n = l2v->nodepool_bytes / WHIMORY_L2V_NODE_SIZE; + + idx = l2v->free_head; + if (idx == WHIMORY_L2V_INVALID_ROOT || !l2v->free_count || idx >= n) + return WHIMORY_L2V_INVALID_ROOT; + node = l2v->nodes + idx * WHIMORY_L2V_NODE_SIZE; + next = get_unaligned_le32(node); + l2v->free_head = next; + l2v->free_count--; + memset(node, 0, WHIMORY_L2V_NODE_SIZE); + if (idx + 1 > l2v->nodes_used) + l2v->nodes_used = idx + 1; + return idx; +} + +static int whimory_l2v_init(struct whimory *w, u32 max_lba, + u32 vba_factor_a, u32 vba_factor_b, + u32 nodepool_bytes) +{ + struct whimory_l2v *l2v = &w->l2v; + u64 prod; + + whimory_l2v_free(w); + + if (nodepool_bytes < WHIMORY_MIN_NODEPOOL_BYTES) + nodepool_bytes = WHIMORY_MIN_NODEPOOL_BYTES; + + prod = 2ull * vba_factor_a * vba_factor_b; + if (prod < 2) + return -EINVAL; + l2v->bits_vba = fls64(prod - 1) - 1; + if (!l2v->bits_vba || l2v->bits_vba > 30) + return -EINVAL; + l2v->spanbits_vba = 30 - l2v->bits_vba; + + prod = 2ull * (nodepool_bytes >> 6); + if (prod < 2) + return -EINVAL; + l2v->bits_nodeidx = fls64(prod - 1) - 1; + if (!l2v->bits_nodeidx || l2v->bits_nodeidx > 30) + return -EINVAL; + l2v->spanbits_nodeidx = 30 - l2v->bits_nodeidx; + + l2v->sentinel_vba = (1u << l2v->bits_vba) - 1; + l2v->invalid_vba = l2v->sentinel_vba; + l2v->num_roots = (max_lba >> 15) + 1; + l2v->nodepool_bytes = nodepool_bytes; + l2v->nodes_used = 0; + l2v->updates = 0; + l2v->gen = 0; + l2v->frag_count = 0; + l2v->frag_max = 0; + l2v->free_head = WHIMORY_L2V_INVALID_ROOT; + l2v->free_count = 0; + + l2v->root = kvzalloc(WHIMORY_L2V_ROOT_REC_SIZE * l2v->num_roots, + GFP_KERNEL); + if (!l2v->root) + return -ENOMEM; + l2v->nodes = kvzalloc(nodepool_bytes, GFP_KERNEL); + if (!l2v->nodes) { + kvfree(l2v->root); + l2v->root = NULL; + return -ENOMEM; + } + l2v->leaf_scratch = kvcalloc(WHIMORY_L2V_ROOT_SPAN, + sizeof(*l2v->leaf_scratch), GFP_KERNEL); + if (!l2v->leaf_scratch) { + kvfree(l2v->nodes); + kvfree(l2v->root); + l2v->nodes = NULL; + l2v->root = NULL; + return -ENOMEM; + } + memset(l2v->root, 0xff, WHIMORY_L2V_ROOT_REC_SIZE * l2v->num_roots); + memset(l2v->nodes, 0, nodepool_bytes); + whimory_l2v_mem_reset(l2v); + + dev_info(w->dev, + "L2V init roots=%u nodepool=0x%x bits_vba=%u/%u bits_node=%u/%u invalid=0x%x\n", + l2v->num_roots, l2v->nodepool_bytes, + l2v->bits_vba, l2v->spanbits_vba, + l2v->bits_nodeidx, l2v->spanbits_nodeidx, + l2v->invalid_vba); + return 0; +} + +static int whimory_l2v_encode(struct whimory_l2v *l2v, u8 *node, + u32 *front, u32 *back, bool is_node, + u32 value, u32 span) +{ + u8 value_bits = is_node ? l2v->bits_nodeidx : l2v->bits_vba; + u8 span_bits = is_node ? l2v->spanbits_nodeidx : l2v->spanbits_vba; + u32 span_m1, span_mask, value_mask, e, need; + bool has_ext; + + if (!span || !value_bits) + return -EINVAL; + span_m1 = span - 1; + span_mask = span_bits ? ((1u << span_bits) - 1) : 0; + value_mask = (1u << value_bits) - 1; + if (value > value_mask) + return -EINVAL; + has_ext = span_m1 > span_mask; + if (has_ext && span_bits && (span_m1 >> span_bits) > 0xffffu) + return -E2BIG; + need = 4 + (has_ext ? 2 : 0); + if (*front + need > *back) + return -ENOSPC; + e = (is_node ? 1u : 0u) | (has_ext ? 2u : 0u); + e |= (value & value_mask) << 2; + e |= (span_m1 & span_mask) << (value_bits + 2); + put_unaligned_le32(e, node + *front); + *front += 4; + if (has_ext) { + *back -= 2; + put_unaligned_le16((u16)(span_m1 >> span_bits), + node + *back); + } + return 0; +} + +static u32 whimory_leaf_span_sum(const struct whimory_leaf *l, u32 n) +{ + u32 i, s = 0; + + for (i = 0; i < n; i++) + s += l[i].span; + return s; +} + +static int whimory_l2v_pack_leaves(struct whimory *w, + const struct whimory_leaf *leaves, u32 n, + u32 *idx_out); + +static int whimory_l2v_pack_parent(struct whimory *w, u32 left, u32 span_l, + u32 right, u32 span_r, u32 *idx_out) +{ + struct whimory_l2v *l2v = &w->l2v; + u8 *node; + u32 idx, front, back; + int ret; + + idx = whimory_l2v_alloc_node(l2v); + if (idx == WHIMORY_L2V_INVALID_ROOT) + return -ENOMEM; + node = l2v->nodes + idx * WHIMORY_L2V_NODE_SIZE; + front = 0; + back = WHIMORY_L2V_NODE_SIZE; + ret = whimory_l2v_encode(l2v, node, &front, &back, true, left, span_l); + if (!ret) + ret = whimory_l2v_encode(l2v, node, &front, &back, true, + right, span_r); + if (ret) { + memset(node, 0xff, WHIMORY_L2V_NODE_SIZE); + return ret; + } + if (front <= back - 4) + put_unaligned_le32(0xffffffff, node + front); + *idx_out = idx; + return 0; +} + +static int whimory_l2v_pack_leaves(struct whimory *w, + const struct whimory_leaf *leaves, u32 n, + u32 *idx_out) +{ + struct whimory_l2v *l2v = &w->l2v; + u8 *node; + u32 idx, front, back, i, mid, left, right, span_l, span_r; + int ret; + + if (!n) + return -EINVAL; + + idx = whimory_l2v_alloc_node(l2v); + if (idx == WHIMORY_L2V_INVALID_ROOT) + return -ENOMEM; + node = l2v->nodes + idx * WHIMORY_L2V_NODE_SIZE; + front = 0; + back = WHIMORY_L2V_NODE_SIZE; + for (i = 0; i < n; i++) { + ret = whimory_l2v_encode(l2v, node, &front, &back, false, + leaves[i].vba, leaves[i].span); + if (ret) + break; + } + if (!ret) { + if (front <= back - 4) + put_unaligned_le32(0xffffffff, node + front); + *idx_out = idx; + return 0; + } + memset(node, 0xff, WHIMORY_L2V_NODE_SIZE); + if (l2v->nodes_used == idx + 1) + l2v->nodes_used = idx; + + if (n == 1) { + struct whimory_leaf half[2]; + u32 s0, s1; + + s0 = leaves[0].span / 2; + s1 = leaves[0].span - s0; + if (!s0 || !s1) + return -EINVAL; + half[0].vba = leaves[0].vba; + half[0].span = s0; + half[1].vba = leaves[0].vba + s0; + half[1].span = s1; + ret = whimory_l2v_pack_leaves(w, half, 1, &left); + if (ret) + return ret; + ret = whimory_l2v_pack_leaves(w, half + 1, 1, &right); + if (ret) + return ret; + return whimory_l2v_pack_parent(w, left, s0, right, s1, idx_out); + } + + mid = n / 2; + if (!mid) + mid = 1; + ret = whimory_l2v_pack_leaves(w, leaves, mid, &left); + if (ret) + return ret; + ret = whimory_l2v_pack_leaves(w, leaves + mid, n - mid, &right); + if (ret) + return ret; + span_l = whimory_leaf_span_sum(leaves, mid); + span_r = whimory_leaf_span_sum(leaves + mid, n - mid); + return whimory_l2v_pack_parent(w, left, span_l, right, span_r, idx_out); +} + +static u32 whimory_l2v_collect_root(struct whimory *w, u32 ridx, + struct whimory_leaf *leaves) +{ + struct whimory_l2v *l2v = &w->l2v; + u32 base = ridx * WHIMORY_L2V_ROOT_SPAN; + u32 win_end = base + WHIMORY_L2V_ROOT_SPAN; + u32 cursor = base, nleaf = 0; + struct whimory_range *first = whimory_range_lower(&w->ranges, base); + struct rb_node *n; + + for (n = first ? &first->rb : NULL; n; n = rb_next(n)) { + struct whimory_range *rg = rb_entry(n, struct whimory_range, rb); + u32 s, e, vba, span; + + if (rg->start >= win_end) + break; + e = rg->start + rg->len; + if (e <= base) + continue; + s = max(rg->start, base); + e = min(e, win_end); + if (s >= e) + continue; + vba = rg->vba + (s - rg->start); + if (s > cursor) { + leaves[nleaf].vba = l2v->invalid_vba; + leaves[nleaf].span = s - cursor; + nleaf++; + } + span = e - s; + leaves[nleaf].vba = vba; + leaves[nleaf].span = span; + nleaf++; + cursor = e; + } + if (nleaf && cursor < win_end) { + leaves[nleaf].vba = l2v->invalid_vba; + leaves[nleaf].span = win_end - cursor; + nleaf++; + } + return nleaf; +} + +/* Discard this root's packed tree and rebuild it from the interval map. */ +static int whimory_l2v_pack_root(struct whimory *w, u32 ridx) +{ + struct whimory_l2v *l2v = &w->l2v; + struct whimory_leaf *leaves = l2v->leaf_scratch; + u8 *rec; + u16 ver, node_old; + u32 nleaf, node_idx, used0; + int ret; + + if (!leaves || ridx >= l2v->num_roots) + return -EINVAL; + rec = l2v->root + ridx * WHIMORY_L2V_ROOT_REC_SIZE; + nleaf = whimory_l2v_collect_root(w, ridx, leaves); + node_old = get_unaligned_le16(rec); + if (node_old != WHIMORY_L2V_INVALID_ROOT) + whimory_l2v_free_tree(w, node_old, ridx); + if (!nleaf) { + put_unaligned_le16(WHIMORY_L2V_INVALID_ROOT, rec); + put_unaligned_le16(0, rec + 2); + return 0; + } + w->sftl.l2v_repack_roots++; + used0 = l2v->nodes_used; + ret = whimory_l2v_pack_leaves(w, leaves, nleaf, &node_idx); + if (ret) { + put_unaligned_le16(WHIMORY_L2V_INVALID_ROOT, rec); + put_unaligned_le16(0, rec + 2); + return ret; + } + ver = get_unaligned_le16(rec + 4); + if (ver == 0xffff) + ver = 1; + put_unaligned_le16((u16)node_idx, rec); + put_unaligned_le16((u16)min_t(u32, l2v->nodes_used - used0, 0xffff), + rec + 2); + put_unaligned_le16(ver, rec + 4); + return 0; +} + +/* First insert into an empty root: one node holding up to three leaves. */ +static int whimory_l2v_grow_empty(struct whimory *w, u32 ridx, u32 off, + u32 span, u32 vba) +{ + struct whimory_l2v *l2v = &w->l2v; + u8 *node, *rec; + u32 idx, front = 0, back = WHIMORY_L2V_NODE_SIZE; + u32 old = l2v->invalid_vba; + int ret; + + if (off + span > WHIMORY_L2V_ROOT_SPAN) + return -EINVAL; + idx = whimory_l2v_alloc_node(l2v); + if (idx == WHIMORY_L2V_INVALID_ROOT) + return -ENOMEM; + node = l2v->nodes + idx * WHIMORY_L2V_NODE_SIZE; + if (off) { + ret = whimory_l2v_encode(l2v, node, &front, &back, false, old, + off); + if (ret) + goto fail; + } + ret = whimory_l2v_encode(l2v, node, &front, &back, false, vba, span); + if (ret) + goto fail; + if (off + span < WHIMORY_L2V_ROOT_SPAN) { + u32 tail = WHIMORY_L2V_ROOT_SPAN - off - span; + u32 tail_vba = old; + + if (old < l2v->invalid_vba) + tail_vba = old + off + span; + ret = whimory_l2v_encode(l2v, node, &front, &back, false, + tail_vba, tail); + if (ret) + goto fail; + } + if (front < back) + memset(node + front, 0xff, back - front); + rec = l2v->root + ridx * WHIMORY_L2V_ROOT_REC_SIZE; + put_unaligned_le16((u16)idx, rec); + put_unaligned_le16(1, rec + 2); + return 0; +fail: + whimory_l2v_mem_free(l2v, idx); + return ret; +} + +static int whimory_l2v_update_packed(struct whimory *w, u32 ridx, u32 off, + u32 span, u32 vba) +{ + u16 node_idx; + u8 *rec; + + if (!w->l2v.root || ridx >= w->l2v.num_roots || !span) + return 0; + rec = w->l2v.root + ridx * WHIMORY_L2V_ROOT_REC_SIZE; + node_idx = get_unaligned_le16(rec); + if (node_idx == WHIMORY_L2V_INVALID_ROOT) + return whimory_l2v_grow_empty(w, ridx, off, span, vba); + return whimory_l2v_pack_root(w, ridx); +} + +static int whimory_l2v_build_from_ranges(struct whimory *w) +{ + struct whimory_l2v *l2v = &w->l2v; + u32 ridx, mapped_roots = 0, mapped_lbas = 0; + int ret = 0; + struct rb_node *n; + + if (!l2v->root || !l2v->nodes || !l2v->leaf_scratch) + return -ENODEV; + + { + u16 *vers; + u32 i; + + vers = kvmalloc_array(l2v->num_roots, sizeof(u16), GFP_KERNEL); + if (!vers) + return -ENOMEM; + for (i = 0; i < l2v->num_roots; i++) + vers[i] = get_unaligned_le16( + l2v->root + i * WHIMORY_L2V_ROOT_REC_SIZE + 4); + for (i = 0; i < l2v->num_roots; i++) { + u8 *rec = l2v->root + i * WHIMORY_L2V_ROOT_REC_SIZE; + + put_unaligned_le16(WHIMORY_L2V_INVALID_ROOT, rec); + put_unaligned_le16(0, rec + 2); + put_unaligned_le16(vers[i], rec + 4); + } + kvfree(vers); + whimory_l2v_mem_reset(l2v); + } + + for (ridx = 0; ridx < l2v->num_roots; ridx++) { + ret = whimory_l2v_pack_root(w, ridx); + if (ret) + break; + if (get_unaligned_le16(l2v->root + + ridx * WHIMORY_L2V_ROOT_REC_SIZE) != + WHIMORY_L2V_INVALID_ROOT) + mapped_roots++; + } + for (n = rb_first(&w->ranges); n; n = rb_next(n)) { + struct whimory_range *rg = rb_entry(n, struct whimory_range, rb); + + if (rg->vba < l2v->invalid_vba) + mapped_lbas += rg->len; + } + w->sftl.mapped_roots = mapped_roots; + w->sftl.mapped_lbas = mapped_lbas; + if (ret) + return ret; + whimory_l2v_find_frag(w); + if (l2v->free_count < WHIMORY_L2V_MIN_FREE) + dev_warn(w->dev, "L2V free %u < %u after pack\n", + l2v->free_count, WHIMORY_L2V_MIN_FREE); + dev_info(w->dev, + "L2V recovery OK mapped_roots=%u mapped_lbas=%u nodes_used=%u range_nodes=%u frag=%u/%u free=%u\n", + mapped_roots, mapped_lbas, l2v->nodes_used, + w->sftl.range_nodes, l2v->frag_count, l2v->frag_max, + l2v->free_count); + return mapped_roots ? 0 : -ENOENT; +} + +/* L2V_FindFrag.c— walk leaves, record fragment stats. */ +static void whimory_l2v_find_frag_node(struct whimory *w, u32 node_idx, + u32 *count, u32 *maxspan, int depth) +{ + struct whimory_l2v *l2v = &w->l2v; + const u8 *node; + u32 front = 0, back = WHIMORY_L2V_NODE_SIZE; + + if (depth > WHIMORY_L2V_FINDFRAG_WIN || + (node_idx + 1) * WHIMORY_L2V_NODE_SIZE > + l2v->nodepool_bytes) + return; + node = l2v->nodes + node_idx * WHIMORY_L2V_NODE_SIZE; + while (front + 4 <= back) { + u32 e = get_unaligned_le32(node + front); + bool is_node, has_ext; + u32 value_bits, span_bits, value, span_minus1, span; + + if (e == 0xffffffff) + break; + is_node = e & 1; + has_ext = e & 2; + value_bits = is_node ? l2v->bits_nodeidx : l2v->bits_vba; + span_bits = is_node ? l2v->spanbits_nodeidx : l2v->spanbits_vba; + value = (e >> 2) & ((1u << value_bits) - 1); + span_minus1 = span_bits ? + ((e >> (value_bits + 2)) & ((1u << span_bits) - 1)) : 0; + if (has_ext) { + back -= 2; + if (front + 4 > back) + break; + span_minus1 += (u32)get_unaligned_le16(node + back) << + span_bits; + } + span = span_minus1 + 1; + if (is_node) + whimory_l2v_find_frag_node(w, value, count, maxspan, + depth + 1); + else { + (*count)++; + if (span > *maxspan) + *maxspan = span; + } + front += 4; + } +} + +static void whimory_l2v_free_tree(struct whimory *w, u32 node_idx, u32 root_idx) +{ + struct whimory_l2v *l2v = &w->l2v; + const u8 *node; + u32 front = 0, back = WHIMORY_L2V_NODE_SIZE; + u8 *rec; + + if ((node_idx + 1) * WHIMORY_L2V_NODE_SIZE > l2v->nodepool_bytes) + return; + node = l2v->nodes + node_idx * WHIMORY_L2V_NODE_SIZE; + while (front + 4 <= back) { + u32 e = get_unaligned_le32(node + front); + bool is_node, has_ext; + u32 value_bits, value; + + if (e == 0xffffffff) + break; + is_node = e & 1; + has_ext = e & 2; + value_bits = is_node ? l2v->bits_nodeidx : l2v->bits_vba; + value = (e >> 2) & ((1u << value_bits) - 1); + if (has_ext) { + back -= 2; + if (front + 4 > back) + break; + } + if (is_node) + whimory_l2v_free_tree(w, value, root_idx); + front += 4; + } + whimory_l2v_mem_free(l2v, node_idx); + if (root_idx < l2v->num_roots) { + rec = l2v->root + root_idx * WHIMORY_L2V_ROOT_REC_SIZE; + { + u16 n_nodes = get_unaligned_le16(rec + 2); + + if (n_nodes && n_nodes != 0xffff) + put_unaligned_le16(n_nodes - 1, rec + 2); + } + } +} + +static void whimory_l2v_find_frag(struct whimory *w) +{ + struct whimory_l2v *l2v = &w->l2v; + u32 ridx, count = 0, maxspan = 0; + + if (!l2v->root || !l2v->nodes) + return; + for (ridx = 0; ridx < l2v->num_roots; ridx++) { + u32 node_idx = get_unaligned_le16( + l2v->root + ridx * WHIMORY_L2V_ROOT_REC_SIZE); + + if (node_idx == WHIMORY_L2V_INVALID_ROOT) + continue; + whimory_l2v_find_frag_node(w, node_idx, &count, &maxspan, 0); + } + l2v->frag_count = count; + l2v->frag_max = maxspan; +} + +static int whimory_l2v_lookup(struct whimory *w, u32 lba, + u32 *vba_out, u32 *span_out) +{ + struct whimory_l2v *l2v = &w->l2v; + u32 root_idx = lba >> 15; + u32 target = lba & 0x7fff; + u32 consumed; + u32 node_idx; + int depth; + + if (!l2v->root || !l2v->nodes) + return -ENODEV; + if (root_idx >= l2v->num_roots) + return -ERANGE; + + node_idx = get_unaligned_le16(l2v->root + 6 * root_idx); + if (node_idx == WHIMORY_L2V_INVALID_ROOT) + return -ENOENT; + + for (depth = 0; depth < 32; depth++) { + const u8 *node; + u32 front = 0; + u32 back = WHIMORY_L2V_NODE_SIZE; + + if ((node_idx + 1) * WHIMORY_L2V_NODE_SIZE > + l2v->nodepool_bytes) + return -EINVAL; + node = l2v->nodes + node_idx * WHIMORY_L2V_NODE_SIZE; + consumed = 0; + + while (front + 4 <= back) { + u32 e = get_unaligned_le32(node + front); + bool is_node, has_ext; + u32 value_bits, span_bits, value_mask, value; + u32 span_minus1, span; + + if (e == 0xffffffff) + break; + is_node = e & 1; + has_ext = e & 2; + if (is_node) { + value_bits = l2v->bits_nodeidx; + span_bits = l2v->spanbits_nodeidx; + } else { + value_bits = l2v->bits_vba; + span_bits = l2v->spanbits_vba; + } + value_mask = value_bits ? ((1u << value_bits) - 1) : 0; + value = (e >> 2) & value_mask; + span_minus1 = e >> (value_bits + 2); + if (span_bits) + span_minus1 &= (1u << span_bits) - 1; + else + span_minus1 = 0; + if (has_ext) { + back -= 2; + if (front + 4 > back) + return -EINVAL; + span_minus1 += + (u32)get_unaligned_le16(node + back) << + span_bits; + } + span = span_minus1 + 1; + if (!span) + return -EINVAL; + if (target < consumed + span) { + u32 delta = target - consumed; + + if (is_node) { + node_idx = value; + goto next_level; + } + if (value >= l2v->invalid_vba) + return -ENOENT; + *vba_out = value + delta; + *span_out = span - delta; + return 0; + } + consumed += span; + front += 4; + } + return -EINVAL; +next_level: + continue; + } + return -ELOOP; +} + +/* + * L2V_Search keeps a sequential hint; do the same here. VFAT walks a cluster + * one 4 KiB sector at a time, so consecutive lookups land in the same extent + * and the rbtree descent is pure overhead. Invalidated by map generation. + */ +static void whimory_l2v_cache_store(struct whimory *w, + const struct whimory_range *r) +{ + w->search_start = r->start; + w->search_len = r->len; + w->search_vba = r->vba; + w->search_gen = w->sftl.map_gen; + w->search_valid = true; +} + +static void whimory_l2v_cache_flush(struct whimory *w) +{ + if (w) + w->search_valid = false; +} + +/* Prefer the interval map (L2V_Update result); packed tree is for Search. */ +static int whimory_l2v_search(struct whimory *w, u32 lba, + u32 *vba_out, u32 *span_out) +{ + struct whimory_range *r; + + if (w->search_valid && w->search_gen == w->sftl.map_gen && + lba >= w->search_start && lba - w->search_start < w->search_len) { + u32 delta = lba - w->search_start; + + *vba_out = w->search_vba + delta; + *span_out = w->search_len - delta; + w->sftl.search_cache_hits++; + return 0; + } + + r = whimory_range_find(&w->ranges, lba); + if (r) { + u32 delta = lba - r->start; + + whimory_l2v_cache_store(w, r); + w->sftl.search_cache_misses++; + *vba_out = r->vba + delta; + *span_out = r->len - delta; + return 0; + } + w->sftl.search_cache_misses++; + return whimory_l2v_lookup(w, lba, vba_out, span_out); +} + +/* ------------------------------------------------------------------ */ +/* FIL */ +/* ------------------------------------------------------------------ */ + +static int whimory_fil_init(struct whimory *w) +{ + struct s5l8740_nand_geom g; + int ret; + + ret = s5l8740_nand_hw_init(); + if (ret) + return ret; + ret = s5l8740_nand_query_geometry(&g); + if (ret) + return ret; + if (!g.dev_id) + return -ENODEV; + + w->geom.num_ce = g.num_ce; + w->geom.num_cau = g.num_cau; + w->geom.blocks_per_cau = g.blocks_per_cau; + w->geom.pages_per_block = g.pages_per_block; + w->geom.page_size = g.page_size; + w->geom.vfl_tail = g.vfl_tail; + w->geom.user_blocks = g.blocks_per_cau - g.vfl_tail; + w->geom.dev_id = s5l8740_nand_fil_get_info(101); + w->geom.geom_104 = s5l8740_nand_fil_get_info(104); + w->geom.geom_105 = s5l8740_nand_fil_get_info(105); + w->geom.geom_135 = s5l8740_nand_fil_get_info(135); + if (!w->geom.dev_id) + return -ENODEV; + if (w->geom.geom_104 && w->geom.geom_104 != w->geom.page_size) { + dev_err(w->dev, + "FIL GetInfo(104)=%u != page_size=%u\n", + w->geom.geom_104, w->geom.page_size); + return -EINVAL; + } + if (w->geom.geom_105 && w->geom.geom_105 != WHIMORY_FIL_META_BYTES) { + dev_err(w->dev, "FIL GetInfo(105)=%u != %u\n", + w->geom.geom_105, WHIMORY_FIL_META_BYTES); + return -EINVAL; + } + + dev_info(w->dev, + "FIL_Init OK dev_id=%u g104=%u g105=%u g135=%u ce=%u cau=%u blocks=%u user=%u param=%d\n", + w->geom.dev_id, w->geom.geom_104, w->geom.geom_105, + w->geom.geom_135, w->geom.num_ce, w->geom.num_cau, + w->geom.blocks_per_cau, w->geom.user_blocks, + g.from_param_page); + w->fil_ok = true; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* FPart — signature from media (or oracle firmware file) */ +/* ------------------------------------------------------------------ */ + +/* + *— FPart signature is NOT a user-page hunt. + * OSOS: memset(sig, 0xA5, 0x600) then _fpart->op80(sig, 0x600, 0xC101). + * READ ONLY — never AllocateSpecialBlock / WriteSpecial / erase. + * Validate: magic 0x776d7278, ver<=6, +0x34 == FIL GetInfo(101). + */ +static void whimory_log_sig_fields(struct whimory *w, const u8 *s, + const char *why) +{ + u32 magic = whimory_sig32(s, 0x00); + u32 ver = whimory_sig32(s, 0x08); + u32 ftl_m = whimory_sig32(s, 0x0c); + u32 ftl_n = whimory_sig32(s, 0x10); + u32 vfl_m = whimory_sig32(s, 0x18); + u32 vfl_n = whimory_sig32(s, 0x1c); + u32 vfl_arg = whimory_sig32(s, 0x20); + u32 fpt_m = whimory_sig32(s, 0x24); + u32 fpt_n = whimory_sig32(s, 0x28); + u32 fpt_a = whimory_sig32(s, 0x2c); + u32 extra = whimory_sig32(s, 0x30); + u32 geom = whimory_sig32(s, 0x34); + u32 cfg = whimory_sig32(s, 0xb8); + + dev_info(w->dev, + "WHIMORY_SIG %s magic=%08x ver=%u ftl=%u.%u vfl=%u.%u fpart=%u.%u geom=%u num_ce=%u vfl_arg=%u fpart_arg=%u extra=%u cfg_b8=%u first32=%32ph\n", + why, magic, ver, ftl_m, ftl_n, vfl_m, vfl_n, fpt_m, fpt_n, + geom, w->geom.num_ce, vfl_arg, fpt_a, extra, cfg, s); +} + +/* OSOSchecks — not the old ver>=1 / major<=16 heuristic. */ +static int whimory_validate_signature(struct whimory *w, const u8 *sig) +{ + u32 magic = whimory_sig32(sig, 0x00); + u32 ver = whimory_sig32(sig, 0x08); + u32 geom = whimory_sig32(sig, 0x34); + + whimory_log_sig_fields(w, sig, "validate"); + if (magic != WHIMORY_SIG_MAGIC) { + dev_info(w->dev, + "FPART_SIG_READ reject: magic=%08x want=776d7278\n", + magic); + return -EINVAL; + } + if (ver > 6) { + dev_info(w->dev, + "FPART_SIG_READ reject: version=%u > 6\n", ver); + return -EINVAL; + } + /* + * The +0x34 field is a device count, not a block count. + * + * This compared it against FIL GetInfo(101) and rejected the + * signature on every boot of this unit: geom=2 against + * GetInfo(101)=2088. Those are not a mismatched pair of the same + * quantity, they are two different quantities -- GetInfo(101) + * returns blocks_per_cau, and the signature field holds 2, which is + * num_ce. + * + * OSOS settles it. The validator reads, with the signature buffer at + * 0x8D0C220: + * + * if (MEMORY[0x8D0C220] != 0x776D7278) "invalid magic" + * if (MEMORY[0x8D0C244] != a1) "FPart major ver" + * if (MEMORY[0x8D0C254] != MEMORY[0x8D0CE20]) "Geometry does not match" + * if (MEMORY[0x8D0C228] > 6) "Device version unsupported" + * + * so +0x34 is compared against MEMORY[0x8D0CE20]. Earlier in the + * same function that word is the one tested as + * + * if (!MEMORY[0x8D0CE20]) "[NAND] No NAND device found" + * + * which is a count of NAND devices. It cannot be a block count: a + * zero block count is not how firmware says no chip responded. + * + * Rejecting the signature is not a harmless miss. It is what left + * fpart_sig=0 vfl_ctx_hits=0 vfl_cxt_loc=0 on every boot, and + * without the FPart signature there is no VFL context, so recovery + * fell back to scanning all 1960 user blocks three times and + * inferring by weave what the context would have stated. + */ + if (geom != w->geom.num_ce) { + dev_info(w->dev, + "FPART_SIG_READ reject: geom=%u != num_ce=%u\n", + geom, w->geom.num_ce); + return -EINVAL; + } + return 0; +} + +static void whimory_dump_sparse(struct whimory *w, const char *tag, + const u8 *p, unsigned int len); + +static int whimory_parse_signature(struct whimory *w, const u8 *s) +{ + int ret; + + ret = whimory_validate_signature(w, s); + if (ret) + return ret; + memcpy(w->sig.raw, s, WHIMORY_SIG_SIZE); + w->sig.version = whimory_sig32(s, 0x08); + w->sig.ftl_major = whimory_sig32(s, 0x0c); + w->sig.ftl_minor = whimory_sig32(s, 0x10); + w->sig.vfl_major = whimory_sig32(s, 0x18); + w->sig.vfl_minor = whimory_sig32(s, 0x1c); + w->sig.fpart_major = whimory_sig32(s, 0x24); + w->sig.fpart_minor = whimory_sig32(s, 0x28); + w->sig.sig_geom = whimory_sig32(s, 0x34); + w->sig.flags_or_open = whimory_sig32(s, 0x20); + w->sig.fpart_arg = whimory_sig32(s, 0x2c); + w->sig.extra_arg = whimory_sig32(s, 0x30); + w->sig_ok = true; + whimory_dump_sparse(w, "SIG", s, WHIMORY_SIG_SIZE); + dev_info(w->dev, + "Whimory sig OK ver=%u fpart=%u.%u vfl=%u.%u ftl=%u.%u geom=%u vfl_arg=%u fpart_arg=%u extra=%u\n", + w->sig.version, w->sig.fpart_major, w->sig.fpart_minor, + w->sig.vfl_major, w->sig.vfl_minor, + w->sig.ftl_major, w->sig.ftl_minor, w->sig.sig_geom, + w->sig.flags_or_open, w->sig.fpart_arg, w->sig.extra_arg); + return 0; +} + +static int n31_fpart_init(struct whimory *w) +{ + if (!w->fil_ok) + return -ENODEV; + memset(w->fpart_ctx.table, 0xff, sizeof(w->fpart_ctx.table)); + w->fpart_ctx.count = 0; + w->fpart_ctx.scanned = false; + return 0; +} + +static u32 n31_fpart_minor(struct whimory *w) +{ + return w->sig.fpart_minor; +} + +static u16 fpart_num_banks(const struct whimory *w) +{ + return w->geom.num_ce * w->geom.num_cau; +} + +static void fpart_bank_to_ce_cau(const struct whimory *w, u16 bank, + unsigned int *ce, unsigned int *cau) +{ + u16 ncau = w->geom.num_cau ? w->geom.num_cau : 1; + + *ce = bank / ncau; + *cau = bank % ncau; +} + +static bool fpart_type_class1(u16 type_word) +{ + return ((type_word >> 8) & FPART_SPECIAL_CLASS_MASK) == + FPART_SPECIAL_CLASS; +} + +static bool fpart_meta_special(const u8 *meta, u8 want_chunk, u16 *type_out); +static bool fpart_meta_is_assign(const u8 *meta, u16 *type_out); +static bool fpart_has_xrmw(const u8 *page); + +/* + *op=1 analogue. Special objects often live on SLC; try SLC + * then MLC. Full 16 KiB data + 64B META; special uses first 16 META bytes. + */ +/* + * Read one FPart page, trying SLC plane 1 then 0. + * + * This went through s5l8740_nand_page_read() until now, and never once + * reached the NAND. That function refuses any request carrying a meta buffer + * unless meta_dma_read is set, and meta_dma_read is deliberately off -- a + * permanent live CS kick reboots the device, so the sanctioned path is a + * temporary dma_session around cs_phys instead. Every FPart read therefore + * returned -EOPNOTSUPP: the log said reads=512 fail=512 in sixty + * milliseconds, which is far too fast to have been a NAND access at all, and + * "sig=0, not a native open" was a verdict about a region nobody had looked + * at. + * + * cs_phys is the same path classify uses, and it needs the caller to hold a + * DMA session -- fpart_scan_region() opens one. + */ +static int fpart_fil_read_page(struct whimory *w, u16 bank, u32 block, + u32 page, struct s5l8740_cs_page *csp, + void *data, u8 *meta) +{ + unsigned int ce, cau, i, sl; + int last = -EIO; + const u8 slc_order[2] = { 1, 0 }; + + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + if (ce >= w->geom.num_ce || cau >= w->geom.num_cau || + block >= w->geom.blocks_per_cau || + page >= w->geom.pages_per_block) + return -EINVAL; + + for (i = 0; i < 2; i++) { + int ret; + + ret = s5l8740_nand_cs_phys_read_slc((u8)ce, (u8)cau, (u16)block, + (u8)page, slc_order[i], + csp, 4); + if (ret) + continue; + + /* Flatten the four records back into the flat page and the + * 64-byte meta the FPart parsers expect. + */ + for (sl = 0; sl < N31_DATA_SLOTS; sl++) { + size_t doff = (size_t)sl * N31_DATA_SLOT_SIZE; + + if (doff + N31_DATA_SLOT_SIZE <= w->geom.page_size) + memcpy((u8 *)data + doff, csp->data[sl], + N31_DATA_SLOT_SIZE); + memcpy(meta + sl * WHIMORY_META_SIZE, + csp->meta_raw[sl], WHIMORY_META_SIZE); + } + + last = 0; + if (fpart_meta_special(meta, 0, NULL) || + fpart_meta_is_assign(meta, NULL)) + return 0; + if (payload_magic_scan && fpart_has_xrmw(data)) + return 0; + } + return last; +} + +/*— 16-byte META copy. LE type_word at +2 (RE). */ +static bool fpart_meta_special(const u8 *meta, u8 want_chunk, u16 *type_out) +{ + unsigned int slot; + + if (!meta) + return false; + for (slot = 0; slot < 4; slot++) { + const u8 *m = meta + slot * WHIMORY_META_SIZE; + + if (m[0] != FPART_SPECIAL_TAG) + continue; + if (m[1] != want_chunk) + continue; + if (type_out) + *type_out = get_unaligned_le16(m + 2); + return true; + } + return false; +} + +/* + * Scanner: META tag 0x30 and class 1. Chunk-0 assignment pages use m[1]==0 + * with class in type_word[15:8]. Avoid treat payload magic as a hit. + */ +static bool fpart_meta_is_assign(const u8 *meta, u16 *type_out) +{ + unsigned int slot; + + if (!meta) + return false; + for (slot = 0; slot < 4; slot++) { + const u8 *m = meta + slot * WHIMORY_META_SIZE; + u16 tw; + + if (m[0] != FPART_SPECIAL_TAG) + continue; + tw = get_unaligned_le16(m + 2); + if ((m[1] & FPART_SPECIAL_CLASS_MASK) == FPART_SPECIAL_CLASS || + (m[1] == 0 && fpart_type_class1(tw))) { + if (type_out) + *type_out = tw; + return true; + } + } + return false; +} + +static int fpart_meta_special_slot(const u8 *meta, u8 want_chunk, u16 *type_out) +{ + unsigned int slot; + + if (!meta) + return -1; + for (slot = 0; slot < 4; slot++) { + const u8 *m = meta + slot * WHIMORY_META_SIZE; + + if (m[0] != FPART_SPECIAL_TAG) + continue; + if (m[1] != want_chunk) + continue; + if (type_out) + *type_out = get_unaligned_le16(m + 2); + return (int)slot; + } + return -1; +} + +static bool fpart_meta_interesting(const u8 *m) +{ + return m && (m[0] == FPART_SPECIAL_TAG || + m[0] == WHIMORY_META_TYPE_VFL_CXT || + m[0] == WHIMORY_META_TYPE_SFTL_CXT || + m[0] == WHIMORY_META_TYPE_BTOC); +} + +static u32 fpart_word_at(const u8 *page, unsigned int off) +{ + return get_unaligned_le32(page + off); +} + +static bool fpart_has_xrmw(const u8 *page) +{ + u32 a = fpart_word_at(page, 0); + u32 b = fpart_word_at(page, FPART_SPECIAL_HDR); + + return a == WHIMORY_SIG_MAGIC || b == WHIMORY_SIG_MAGIC; +} + +static bool fpart_has_wrmx(const u8 *page) +{ + u32 a = fpart_word_at(page, 0); + u32 b = fpart_word_at(page, FPART_SPECIAL_HDR); + + return a == WHIMORY_SIG_MAGIC_WRMX || b == WHIMORY_SIG_MAGIC_WRMX; +} + +/* + * Cache insert: sorted by type_word, then bank, then block (OSOS + * fpart_special_cache_add_pairs). Table size 0x2d0 / 6 = 120. + */ +static int fpart_cache_add(struct whimory *w, u16 bank, u16 block, + u16 type_word) +{ + struct fpart_special_entry *t = w->fpart_ctx.table; + u16 n = w->fpart_ctx.count; + u16 i, j; + + if (n >= FPART_SPECIAL_MAX_ENTRIES) + return -ENOSPC; + + for (i = 0; i < n; i++) { + if (t[i].type_word == type_word && t[i].bank == bank && + t[i].block == block) + return 0; + if (t[i].type_word > type_word) + break; + if (t[i].type_word == type_word && t[i].bank > bank) + break; + if (t[i].type_word == type_word && t[i].bank == bank && + t[i].block > block) + break; + } + for (j = n; j > i; j--) + t[j] = t[j - 1]; + t[i].bank = bank; + t[i].block = block; + t[i].type_word = type_word; + w->fpart_ctx.count = n + 1; + return 1; +} + +/* Assignment page DATA: up to eight u16 bank, u16 block; bank==0xffff ends. */ +static bool fpart_data_is_assignment(const u8 *data, u16 nbanks, u32 nblk) +{ + unsigned int i, valid = 0; + + for (i = 0; i < FPART_ASSIGN_MAX_PAIRS; i++) { + u16 bank = get_unaligned_le16(data + i * 4); + u16 block = get_unaligned_le16(data + i * 4 + 2); + + if (bank == 0xffff) + return i == 0 || valid > 0; + if (bank >= nbanks || block >= nblk) + return false; + valid++; + } + return valid > 0; +} + +static unsigned int fpart_ingest_pairs(struct whimory *w, const u8 *data, + u16 type_word) +{ + unsigned int i, added = 0; + u16 nbanks = fpart_num_banks(w); + + for (i = 0; i < FPART_ASSIGN_MAX_PAIRS; i++) { + u16 bank = get_unaligned_le16(data + i * 4); + u16 block = get_unaligned_le16(data + i * 4 + 2); + + if (bank == 0xffff) + break; + if (bank >= nbanks || block >= w->geom.blocks_per_cau) { + dev_info(w->dev, + "FPART_ASSIGN skip pair bank=%u block=%u (banks=%u blocks=%u)\n", + bank, block, nbanks, w->geom.blocks_per_cau); + continue; + } + if (fpart_cache_add(w, bank, block, type_word) > 0) { + dev_info(w->dev, + "FPART_ASSIGN_ADD bank=%u block=%u type=0x%04x\n", + bank, block, type_word); + added++; + } + } + return added; +} + +static bool fpart_find_in_cache(struct whimory *w, u16 *index, u16 type) +{ + u8 low = type & 0xff; + u16 i; + + for (i = 0; i < w->fpart_ctx.count; i++) { + if ((w->fpart_ctx.table[i].type_word & 0xff) == low) { + *index = i; + return true; + } + } + return false; +} + +/* + * Say out loud every distinct FPART special type the scan actually found. + * + * We only act on three: 0xc101 SIGNATURE, 0xc104 VFL_CXT, 0xc105 CONFIG (which + * carries SysCfg). Anything else is cached and then silently ignored, so a + * type that exists on the medium has never been visible in a boot log. + * + * That matters right now because the Grape touch firmware has a second source. + * sub_1A640 falls back to sub_201FC(0x67706677, ...) -- 'gpfw' big-endian -- + * when MEMORY[0x8A8FAA4] is zero, and that lookup goes through sub_683E0 -> + * sub_26794 -> sub_261A4, a keyed record lookup whose sub_26794 carries the + * string "APPLE_MDFW". Which medium backs it is NOT established from the + * decomp yet; a NAND special area analogous to SysCfg is the working + * hypothesis and this log is how the device answers it. + * + * Printed once per scan, unconditionally, because the whole point is to turn a + * cold boot into evidence. + */ +static void fpart_log_special_types(struct whimory *w) +{ + u16 seen[16]; + unsigned int nseen = 0; + u16 i, j; + + for (i = 0; i < w->fpart_ctx.count; i++) { + u16 t = w->fpart_ctx.table[i].type_word; + + for (j = 0; j < nseen; j++) + if (seen[j] == t) + break; + if (j < nseen) + continue; + if (nseen < ARRAY_SIZE(seen)) + seen[nseen++] = t; + } + + for (j = 0; j < nseen; j++) { + const char *known = + seen[j] == FPART_TYPE_SIGNATURE ? " SIGNATURE" : + seen[j] == FPART_TYPE_VFL_CXT ? " VFL_CXT" : + seen[j] == FPART_TYPE_CONFIG ? " CONFIG/SysCfg" : + " UNCLAIMED"; + u16 n = 0; + + for (i = 0; i < w->fpart_ctx.count; i++) + if (w->fpart_ctx.table[i].type_word == seen[j]) + n++; + + dev_info(w->dev, + "FPART_SPECIAL_TYPE 0x%04x copies=%u%s\n", + seen[j], n, known); + } + dev_info(w->dev, "FPART_SPECIAL_TYPES distinct=%u of %u copies\n", + nseen, w->fpart_ctx.count); +} + +static u16 fpart_count_special_copies(struct whimory *w, u16 type_word) +{ + u8 low = type_word & 0xff; + u16 n = 0, i; + + for (i = 0; i < w->fpart_ctx.count; i++) { + if ((w->fpart_ctx.table[i].type_word & 0xff) == low) + n++; + } + return n; +} + +static int fpart_scan_region(struct whimory *w, u16 type, + u32 block_lo, u32 block_hi, + unsigned int page_lo, unsigned int page_hi, + bool *matched) +{ + u8 *page; + u8 meta[S5L8740_NAND_META_SIZE]; + struct s5l8740_cs_page *csp; + u16 bank, nbanks = fpart_num_banks(w); + u32 b, p; + int ret, reads = 0, tag30 = 0, xrmw = 0, wrmx = 0, fail = 0; + unsigned int sample = 0; + /* + * Heap, not stack. 256 u32 is a kilobyte, and with the page buffer + * pointers and the meta array around it this frame measured 1224 + * bytes -- past the 1024-byte warning and a quarter of an 8 KiB + * kernel stack, on a function that also calls into the NAND driver. + */ + u32 *hist; + int sess; + + page = kvmalloc(w->geom.page_size, GFP_KERNEL); + if (!page) + return -ENOMEM; + hist = kcalloc(256, sizeof(*hist), GFP_KERNEL); + if (!hist) { + kvfree(page); + return -ENOMEM; + } + + csp = kvmalloc(sizeof(*csp), GFP_KERNEL); + if (!csp) { + kfree(hist); + kvfree(page); + return -ENOMEM; + } + + /* + * Clamp to the SLC page count, not the MLC one. + * + * This region is SLC and an SLC block holds geom_105 pages, 16 on + * this part, against 128 in MLC. Sweeping to pages_per_block asks + * for pages 16..127, which do not exist there -- and the failures + * do not stay local: measured over blk[1960,2088), pages[0,127] + * gave 65536 reads, fail=0 and tag30=0, with 97 per cent of pages + * reading back as meta 0x00. Every object in the region vanished, + * including the five on page 0 that the same code finds every + * time when it stops at page 0. The same sweep bounded to + * pages[0,15] gives 8192 reads, fail=0, tag30=80 and entries=5: + * the five objects, each occupying all 16 SLC pages of its block. + * + * So an out-of-range SLC page does not merely fail, it takes the + * reads after it with it, and the result looks like a clean scan + * of an empty region rather than like an error. + */ + if (w->geom.geom_105 && page_hi >= w->geom.geom_105) + page_hi = w->geom.geom_105 - 1; + else if (page_hi >= w->geom.pages_per_block) + page_hi = w->geom.pages_per_block - 1; + + /* + * Arm live CS for the sweep. Sessions nest and begin() always + * succeeds, so this one owns exactly its own end; an outer session + * simply keeps CS armed past it. The failure check below is kept for + * a future begin() that can fail. + */ + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + dev_warn(w->dev, "FPART_SCAN no DMA session (%d)\n", sess); + kfree(hist); + kvfree(csp); + kvfree(page); + return sess; + } + + s5l8740_nand_reset(); + + for (bank = 0; bank < nbanks; bank++) { + for (b = block_hi; b > block_lo; b--) { + u32 blk = b - 1; + + for (p = page_lo; p <= page_hi; p++) { + u16 type_word = 0; + unsigned int ce, cau, pairs; + u32 obj_len; + bool special, magic; + + cond_resched(); + ret = fpart_fil_read_page(w, bank, blk, p, + csp, page, meta); + reads++; + if (ret) { + fail++; + continue; + } + hist[meta[0]]++; + /* + * Gated, because printing is not free here. + * + * Each of these is a 112-byte hex dump down a + * slow console, and the timestamps put one at + * 85 to 95 ms. Two dozen of them across the + * scan is about two seconds added to every + * mount, spent describing pages the scan + * already summarises in FPART_SPECIAL_TYPE + * and the meta histogram. diag=1 brings them + * back. + */ + if (ftl_diag && sample < 12) { + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + dev_info(w->dev, + "FPART_META_SAMPLE n=%u bank=%u ce=%u cau=%u blk=%u pg=%u meta=%16ph data00=%32ph data80=%32ph\n", + sample, bank, ce, cau, blk, p, + meta, page, + page + FPART_SPECIAL_HDR); + sample++; + } + { + unsigned int s, interesting = 0; + + for (s = 0; s < 4; s++) + if (fpart_meta_interesting(meta + + s * WHIMORY_META_SIZE)) + interesting++; + if (ftl_diag && interesting && + w->fpart_ctx.slot_logs < 48) { + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + for (s = 0; s < 4; s++) { + const u8 *m = meta + s * WHIMORY_META_SIZE; + const u8 *d = page + s * WHIMORY_LBA_SIZE; + + if (!fpart_meta_interesting(m) && + s != 0) + continue; + dev_info(w->dev, + "FPART_SLOTS n=%u bank=%u ce=%u cau=%u blk=%u pg=%u slot=%u type=%02x chunk=%02x tw=0x%04x meta=%16ph data=%32ph\n", + w->fpart_ctx.slot_logs, + bank, ce, cau, blk, p, s, + m[0], m[1], + get_unaligned_le16(m + 2), + m, d); + } + w->fpart_ctx.slot_logs++; + } + } + special = fpart_meta_is_assign(meta, &type_word); + if (!special) + special = fpart_meta_special(meta, 0, + &type_word); + magic = fpart_has_xrmw(page); + if (fpart_has_wrmx(page)) + wrmx++; + if (magic) + xrmw++; + if (!special) + continue; + tag30++; + if (ftl_diag) { + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + dev_info(w->dev, + "FPART_ASSIGN_SCAN bank=%u ce=%u cau=%u block=%u page=%u slot=%d type_word=0x%04x blank=%d m0=%16ph m1=%16ph m2=%16ph m3=%16ph data00=%32ph data80=%32ph\n", + bank, ce, cau, blk, p, + fpart_meta_special_slot(meta, 0, NULL), + type_word, + whimory_page_blank(page, 256), + meta, meta + 16, meta + 32, + meta + 48, + page, page + FPART_SPECIAL_HDR); + } + if (whimory_page_blank(page, 256)) { + dev_info(w->dev, + "FPART_ASSIGN skip blank data type_word=0x%04x\n", + type_word); + continue; + } + if (fpart_data_is_assignment(page, nbanks, + w->geom.blocks_per_cau)) { + pairs = fpart_ingest_pairs(w, page, + type_word); + dev_info(w->dev, + "FPART_ASSIGN_PAGE type_word=0x%04x pairs=%u count=%u\n", + type_word, pairs, + w->fpart_ctx.count); + } else { + obj_len = get_unaligned_le32(page + + FPART_SPECIAL_LEN_OFF); + if (magic || + (obj_len && obj_len != 0xffffffffu && + obj_len < 0x100000u)) { + if (fpart_cache_add(w, bank, + blk, + type_word) > 0) + dev_info(w->dev, + "FPART_ASSIGN_ADD bank=%u block=%u type=0x%04x (object chunk0)\n", + bank, blk, + type_word); + } + } + if ((type_word & 0xff) == (type & 0xff)) + *matched = true; + } + } + } + dev_info(w->dev, + "FPART_SCAN blk[%u,%u) pages[%u,%u] reads=%d fail=%d tag30=%d xrmw=%d wrmx=%d entries=%u matched=%d meta0_top=%02x/%u %02x/%u %02x/%u %02x/%u\n", + block_lo, block_hi, page_lo, page_hi, reads, fail, tag30, xrmw, + wrmx, w->fpart_ctx.count, *matched, + 0xff, hist[0xff], 0x00, hist[0], 0x30, hist[0x30], 0x20, + hist[0x20]); + fpart_log_special_types(w); + if (!sess) + s5l8740_nand_dma_session_end(); + kfree(hist); + kvfree(csp); + kvfree(page); + return 0; +} + +/* + * fpart_locate_special_4EBBDC: cache by low byte, else scan tail assignment + * pages (META 0x30 chunk 0). scanned=true after a full miss so we do not + * rescan.op=4 bitmap is not ported — every tail block is read. + */ +static bool fpart_locate_special(struct whimory *w, u16 *index, u16 type) +{ + u32 tail, start, nblk = w->geom.blocks_per_cau; + unsigned int npg = fpart_assign_pages ? fpart_assign_pages : 1; + bool matched = false; + + if (fpart_find_in_cache(w, index, type)) + return true; + if (w->fpart_ctx.scanned) + return false; + + tail = sig_scan_blocks ? sig_scan_blocks : w->geom.vfl_tail; + if (!tail) + tail = 128; + if (tail > nblk) + tail = nblk; + start = nblk - tail; + + dev_info(w->dev, + "FPART_LOCATE type=0x%04x tail=%u start=%u pages=0..%u banks=%u\n", + type, tail, start, npg - 1, fpart_num_banks(w)); + + if (fpart_scan_region(w, type, start, nblk, 0, npg - 1, &matched)) + return false; + + if (!matched && sig_brute_scan && start) { + dev_info(w->dev, + "FPART_LOCATE brute remaining blk[0,%u) (debug)\n", + start); + fpart_scan_region(w, type, 0, start, 0, 0, &matched); + } + + w->fpart_ctx.scanned = true; + if (!fpart_find_in_cache(w, index, type)) { + dev_info(w->dev, + "FPART_LOCATE type=0x%04x miss entries=%u\n", + type, w->fpart_ctx.count); + return false; + } + return true; +} + +/* + * fpart_read_special_copy_4F1420. + * Chunk 0: META 30 type_word; object_len @+0x24, gen @+0x28, + * payload @+0x80. Later chunks: payload @+0, dst off = page_size*chunk-128. + */ +static int fpart_read_special_copy(struct whimory *w, u8 *dst, u32 dst_len, + u16 entry_i, u32 *gen_out) +{ + struct fpart_special_entry *e; + struct s5l8740_cs_page *csp; + u8 *page; + u8 meta[S5L8740_NAND_META_SIZE]; + u32 page_size, chunk_count = 1, copy_slots, chunk, slot; + u32 object_len = 0, copy_len = 0, generation = 0; + int ret = -ENOENT; + int sess; + + if (entry_i >= w->fpart_ctx.count) + return -EINVAL; + e = &w->fpart_ctx.table[entry_i]; + page_size = w->geom.page_size; + copy_slots = w->geom.pages_per_block; + if (!page_size || !copy_slots) + return -EINVAL; + + page = kvmalloc(page_size, GFP_KERNEL); + if (!page) + return -ENOMEM; + + csp = kvmalloc(sizeof(*csp), GFP_KERNEL); + if (!csp) { + kvfree(page); + return -ENOMEM; + } + + /* Same as the scan: cs_phys needs live CS armed, and -EBUSY only + * means someone outside already armed it. + */ + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + dev_warn(w->dev, "FPART_COPY no DMA session (%d)\n", sess); + kvfree(csp); + kvfree(page); + return sess; + } + + for (chunk = 0; chunk < chunk_count; chunk++) { + bool got = false; + + for (slot = 0; slot < copy_slots; slot++) { + u32 pg = chunk + slot * chunk_count; + u16 meta_type = 0; + + if (pg >= w->geom.pages_per_block) + break; + cond_resched(); + ret = fpart_fil_read_page(w, e->bank, e->block, pg, + csp, page, meta); + if (ret) + continue; + if (!fpart_meta_special(meta, chunk, &meta_type)) + continue; + if (meta_type != e->type_word) + continue; + got = true; + + if (chunk == 0) { + object_len = get_unaligned_le32(page + + FPART_SPECIAL_LEN_OFF); + generation = get_unaligned_le32(page + + FPART_SPECIAL_GEN_OFF); + if (!object_len || object_len == 0xffffffffu) { + got = false; + continue; + } + copy_len = min(object_len, dst_len ? dst_len : + object_len); + chunk_count = DIV_ROUND_UP(copy_len + + FPART_SPECIAL_HDR, + page_size); + if (!chunk_count) + chunk_count = 1; + copy_slots = w->geom.pages_per_block / + chunk_count; + if (!copy_slots) + copy_slots = 1; + dev_info(w->dev, + "FPART_SPECIAL_COPY entry=%u bank=%u block=%u type_word=0x%04x chunk0 page=%u meta=%16ph object_len=0x%x gen=%u raw00=%32ph raw80=%32ph\n", + entry_i, e->bank, e->block, + e->type_word, pg, meta, object_len, + generation, page, + page + FPART_SPECIAL_HDR); + if (dst && dst_len) { + u32 n = min(page_size - FPART_SPECIAL_HDR, + copy_len); + + n = min(n, dst_len); + memcpy(dst, page + FPART_SPECIAL_HDR, n); + } + } else if (dst && dst_len) { + u32 dst_off = page_size * chunk - FPART_SPECIAL_HDR; + u32 n; + + if (dst_off >= dst_len || dst_off >= copy_len) + break; + n = min(page_size, copy_len - dst_off); + n = min(n, dst_len - dst_off); + memcpy(dst + dst_off, page, n); + } + break; + } + if (!got) { + ret = -ENOENT; + goto out; + } + } + if (gen_out) + *gen_out = generation; + if (dst && dst_len) + dev_info(w->dev, + "FPART_SPECIAL_PAYLOAD entry=%u first32=%32ph\n", + entry_i, dst); + ret = 0; +out: + if (!sess) + s5l8740_nand_dma_session_end(); + kvfree(csp); + kvfree(page); + return ret; +} + +/* Newest generation among contiguous low-byte copies (4F12DC / 4EB428). */ +static int fpart_read_special_by_index(struct whimory *w, u8 *dst, u32 len, + u16 index) +{ + u16 type_word, copies, i, best_i = 0; + bool have = false; + u32 best_gen = 0; + + if (index >= w->fpart_ctx.count) + return -EINVAL; + type_word = w->fpart_ctx.table[index].type_word; + copies = fpart_count_special_copies(w, type_word); + dev_info(w->dev, + "FPART_READ_SPECIAL type=0x%04x index=%u copies=%u class=%u\n", + type_word, index, copies, (type_word >> 8) & + FPART_SPECIAL_CLASS_MASK); + + for (i = 0; i < copies; i++) { + u16 entry_i = index + i; + u32 gen = 0; + int ok; + + if (entry_i >= w->fpart_ctx.count) + break; + if ((w->fpart_ctx.table[entry_i].type_word & 0xff) != + (type_word & 0xff)) + break; + dev_info(w->dev, + "FPART_READ_SPECIAL copy=%u bank=%u block=%u type_word=0x%04x\n", + i, w->fpart_ctx.table[entry_i].bank, + w->fpart_ctx.table[entry_i].block, + w->fpart_ctx.table[entry_i].type_word); + ok = fpart_read_special_copy(w, have ? NULL : dst, + have ? 0 : len, entry_i, &gen); + if (ok) + continue; + if (!have) { + have = true; + best_gen = gen; + best_i = entry_i; + continue; + } + if (gen > best_gen) { + if (!fpart_read_special_copy(w, dst, len, entry_i, + &gen)) { + best_gen = gen; + best_i = entry_i; + } else if (fpart_read_special_copy(w, dst, len, + best_i, NULL)) { + return -EIO; + } + } + } + if (!have) + return -ENOENT; + dev_info(w->dev, + "FPART_READ_SPECIAL selected entry=%u gen=%u\n", + best_i, best_gen); + return 0; +} + +/* + * fpart_read_special_common_4EEB68 / vtable +80. + * Do NOT expect xrmw at raw page offset 0 — payload is after the 0x80 header. + */ +static int whimory_fpart_read_special(struct whimory *w, u32 type, u8 *buf, + size_t len) +{ + u16 index = (u16)type; + u16 type_word; + + if (!buf || !len || type > 0xffff) + return -EINVAL; + + memset(buf, 0xa5, len); + if (!fpart_locate_special(w, &index, (u16)type)) + return -ENOENT; + + type_word = w->fpart_ctx.table[index].type_word; + dev_info(w->dev, + "FPART_LOCATE type=0x%04x index=%u entry bank=%u block=%u type_word=0x%04x class=%u\n", + type, index, w->fpart_ctx.table[index].bank, + w->fpart_ctx.table[index].block, type_word, + (type_word >> 8) & FPART_SPECIAL_CLASS_MASK); + if (!fpart_type_class1(type_word)) + return -EINVAL; + + return fpart_read_special_by_index(w, buf, len, index); +} + +static int n31_fpart_read_signature(struct whimory *w, u8 *buf, size_t len) +{ + return whimory_fpart_read_special(w, WHIMORY_SIG_TYPE, buf, len); +} + +static const struct whimory_fpart_ops n31_ppn_fpart_ops = { + .major = 0, + .minor = n31_fpart_minor, + .init = n31_fpart_init, + .read_special = whimory_fpart_read_special, + .read_signature = n31_fpart_read_signature, +}; + +static void whimory_dump256(struct whimory *w, const char *tag, const u8 *p) +{ + unsigned int i; + + for (i = 0; i < 256; i += 32) + dev_info(w->dev, "%s +0x%02x: %32ph\n", tag, i, p + i); +} + +static int whimory_payload_read_page(struct whimory *w, u16 bank, u32 block, + u32 page, void *data, unsigned int *slc_out) +{ + unsigned int ce, cau, i; + const unsigned int slc_order[2] = { 1, 0 }; + int last = -EIO; + + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + if (ce >= w->geom.num_ce || cau >= w->geom.num_cau || + block >= w->geom.blocks_per_cau || + page >= w->geom.pages_per_block) + return -EINVAL; + + for (i = 0; i < 2; i++) { + int ret; + + ret = s5l8740_nand_page_read(ce, cau, block, page, slc_order[i], + 16, data, w->geom.page_size, + NULL, 0); + if (ret) { + last = ret; + continue; + } + if (slc_out) + *slc_out = slc_order[i]; + /* One successful FIL read is enough — do not MLC-retry blanks. */ + return 0; + } + return last; +} + +static int whimory_payload_check_hit(struct whimory *w, u16 bank, u32 blk, + u32 pg, unsigned int slc, const u8 *page) +{ + unsigned int ce, cau, i; + const unsigned int offs[2] = { 0, FPART_SPECIAL_HDR }; + + fpart_bank_to_ce_cau(w, bank, &ce, &cau); + for (i = 0; i < 2; i++) { + unsigned int off = offs[i]; + u32 mag; + const char *kind; + + if (off + 4 > w->geom.page_size) + continue; + mag = get_unaligned_le32(page + off); + if (mag == WHIMORY_SIG_MAGIC) + kind = "xrmw"; + else if (mag == WHIMORY_SIG_MAGIC_WRMX) + kind = "wrmx"; + else + continue; + dev_info(w->dev, + "PAYLOAD_MAGIC hit kind=%s off=0x%x bank=%u ce=%u cau=%u blk=%u pg=%u slc=%u\n", + kind, off, bank, ce, cau, blk, pg, slc); + whimory_dump256(w, "PAYLOAD_MAGIC data00", page); + whimory_dump256(w, "PAYLOAD_MAGIC data80", page + FPART_SPECIAL_HDR); + if (mag == WHIMORY_SIG_MAGIC && + off + WHIMORY_SIG_SIZE <= w->geom.page_size && + !whimory_parse_signature(w, page + off)) + dev_info(w->dev, "PAYLOAD_MAGIC parsed xrmw as signature\n"); + return 1; + } + return 0; +} + +static int whimory_payload_scan_range(struct whimory *w, void *page, + u32 block_lo, u32 block_hi, + unsigned int page_lo, unsigned int page_hi, + const char *why, int *reads) +{ + u16 bank, nbanks = fpart_num_banks(w); + u32 b, p; + + /* + * Clamp to the SLC page count, not the MLC one. + * + * This region is SLC and an SLC block holds geom_105 pages, 16 on + * this part, against 128 in MLC. Sweeping to pages_per_block asks + * for pages 16..127, which do not exist there -- and the failures + * do not stay local: measured over blk[1960,2088), pages[0,127] + * gave 65536 reads, fail=0 and tag30=0, with 97 per cent of pages + * reading back as meta 0x00. Every object in the region vanished, + * including the five on page 0 that the same code finds every + * time when it stops at page 0. The same sweep bounded to + * pages[0,15] gives 8192 reads, fail=0, tag30=80 and entries=5: + * the five objects, each occupying all 16 SLC pages of its block. + * + * So an out-of-range SLC page does not merely fail, it takes the + * reads after it with it, and the result looks like a clean scan + * of an empty region rather than like an error. + */ + if (w->geom.geom_105 && page_hi >= w->geom.geom_105) + page_hi = w->geom.geom_105 - 1; + else if (page_hi >= w->geom.pages_per_block) + page_hi = w->geom.pages_per_block - 1; + if (block_hi > w->geom.blocks_per_cau) + block_hi = w->geom.blocks_per_cau; + if (block_lo >= block_hi) + return 0; + + dev_info(w->dev, + "PAYLOAD_SCAN %s blk[%u,%u) pages[%u,%u] banks=%u\n", + why, block_lo, block_hi, page_lo, page_hi, nbanks); + + for (bank = 0; bank < nbanks; bank++) { + for (b = block_hi; b > block_lo; b--) { + u32 blk = b - 1; + + for (p = page_lo; p <= page_hi; p++) { + unsigned int slc = 0; + int ret; + + cond_resched(); + ret = whimory_payload_read_page(w, bank, blk, p, + page, &slc); + (*reads)++; + if (!(*reads % 512)) + dev_info(w->dev, + "PAYLOAD_SCAN %s progress reads=%d blk=%u pg=%u\n", + why, *reads, blk, p); + if (ret) + continue; + if (whimory_payload_check_hit(w, bank, blk, p, + slc, page)) + return 1; + } + } + } + return 0; +} + +/* + * Data-only xrmw/wrmx hunt. PIO last_spare is not Sogeti META. Abort on + * first hit. No classify / L2V. + */ +static int whimory_payload_magic_scan(struct whimory *w) +{ + void *page; + int reads = 0, hit; + u32 nblk = w->geom.blocks_per_cau; + u32 user = w->geom.user_blocks; + u32 around = 1461; + + if (!user || user > nblk) + user = nblk > 128 ? nblk - 128 : nblk; + if (around >= nblk) + around = nblk / 2; + + page = kvmalloc(w->geom.page_size, GFP_KERNEL); + if (!page) + return -ENOMEM; + + s5l8740_nand_reset(); + hit = whimory_payload_scan_range(w, page, user, nblk, 0, + w->geom.pages_per_block - 1, + "tail", &reads); + if (!hit) + hit = whimory_payload_scan_range(w, page, 0, user, 0, 0, + "user-pg0", &reads); + if (!hit && around + 1 < nblk) + hit = whimory_payload_scan_range(w, page, around, around + 1, 0, + w->geom.pages_per_block - 1, + "blk1461", &reads); + dev_info(w->dev, "PAYLOAD_SCAN done hit=%d reads=%d sig=%d\n", + hit, reads, w->sig_ok); + kvfree(page); + return hit; +} + +static int whimory_read_signature(struct whimory *w) +{ + int ret; + + w->fpart = &n31_ppn_fpart_ops; + ret = w->fpart->init(w); + if (ret) + return ret; + if (payload_magic_scan) + whimory_payload_magic_scan(w); + ret = w->fpart->read_signature(w, w->sig.raw, WHIMORY_SIG_SIZE); + if (ret) { + dev_warn(w->dev, + "FPart special 0xC101 miss (%d). sig=0 is not a native open.\n", + ret); + if (!allow_sigless_debug) { + dev_err(w->dev, + "allow_sigless_debug=0: refusing VFL/FTL without signature\n"); + return ret; + } + dev_warn(w->dev, + "allow_sigless_debug=1: classify/recover anyway (research only)\n"); + return 0; + } + return whimory_parse_signature(w, w->sig.raw); +} + +static u32 n31_vfl_minor(struct whimory *w) +{ + return w->sig.vfl_minor; +} + +static u32 n31_sftl_minor(struct whimory *w) +{ + return w->sig.ftl_minor; +} + +/* ------------------------------------------------------------------ */ +/* VFL */ +/* ------------------------------------------------------------------ */ + +static int n31_vfl_init(struct whimory *w) +{ + unsigned int cau, i, n, cxt_len; + + n = w->geom.blocks_per_cau; + if (!n) + return -EINVAL; + cxt_len = 16; + w->vfl.cxt_u16_len = cxt_len; + w->vfl.bank_stride = 1; + w->vfl.bank_mask = kvmalloc(n, GFP_KERNEL); + if (!w->vfl.bank_mask) + return -ENOMEM; + memset(w->vfl.bank_mask, (1u << min_t(u32, w->geom.num_cau, 8)) - 1, n); + w->vfl.cached_vbn = 0xffff; + w->vfl.cached_n = 0; + for (cau = 0; cau < w->geom.num_cau; cau++) { + w->vfl.remap[cau] = kvmalloc_array(n, sizeof(u32), GFP_KERNEL); + w->vfl.cxt_u16[cau] = kvmalloc_array(cxt_len, sizeof(u16), + GFP_KERNEL); + if (!w->vfl.remap[cau] || !w->vfl.cxt_u16[cau]) + return -ENOMEM; + for (i = 0; i < n; i++) + w->vfl.remap[cau][i] = i; + for (i = 0; i < cxt_len; i++) + w->vfl.cxt_u16[cau][i] = 0xffff; + w->vfl.ctx_block[cau] = ~0u; + } + return 0; +} + +/* + * Ingest the VFL context object. + * + * What this device keeps in its system area, enumerated completely -- + * 128 blocks, all 16 SLC pages, 4 banks, 8192 reads, no failures -- + * and dumped past each header: + * + * 0xc101 bank 1 blk 2085 signature: magic, versions, geometry + * 0xc104 bank 1 blk 2084 block status bitmap, mirror on bank 2 + * 0xc105 bank 0 blk 2085 device identity, mirror on bank 3 + * + * 0xc104 is the bitmap and nothing else: everything past the 261 bytes + * it needs for 2088 blocks is 0xff, and all 16 pages of the block are + * byte-identical, so the object is one bitmap replicated for + * redundancy. 0xc105 is four-character tags stored reversed -- SCfg, + * SrNm with the serial, FwId, HwId, HwVr, SwVr, MLB#, Regn, BMac, + * Mod#. + * + * The FPart data was checked too, both halves of it, since that is the + * obvious place to look next. The first 0x80 bytes of each object are + * (bank, block) assignment pairs, and they are self-describing rather + * than a directory of anything else: 0xc101 lists bank 1 blk 2085, + * 0xc104 lists bank 2 blk 2085 and bank 1 blk 2084, 0xc105 lists bank + * 3 and bank 0. Five entries, which is exactly the five copies of the + * three objects above. And the signature is 0x600 bytes of which only + * the first 0x40 carry anything: magic, versions, geometry, the string + * "00230g)" at +0x40, one config byte at +0xb8, and zeros to the end. + * + * So nothing here points at the FTL checkpoint, which is what a + * context is normally wanted for. openiBoot vsvfl reaches the FTL + * through control_block[3] in its VFLCxt; this part has no equivalent + * field. The checkpoint can only be found by classifying superblocks, + * which is what recovery already does, so reading the context cannot + * remove the scan -- the context does not know where the map is. + * + * The bitmap is loaded and reported but deliberately not used to skip + * blocks. 29 blocks are marked, all inside the first 256, in a regular + * pattern of two or three low bits per byte, which reads more like + * reserved blocks than like wear scattered across the device. Skipping + * a block that actually holds data would lose it, and whether a clear + * bit means bad or reserved is not established. + * + * Four things here were wrong, and each of them alone was enough to + * make the scan find nothing. + * + * It looked for meta[0] == 0x20. Nothing on this part carries that. + * FPART_SCAN counts the whole system area and reports + * meta0_top=ff/454 00/5 30/5 20/0 -- 454 erased pages, five of type + * 0x30, and not one of 0x20. The system objects are all meta[0] 0x30, + * the FPart special type, and what separates them is a type word at + * meta+2. Enumerating all five across all four banks: + * + * bank 1 blk 2085 0xc101 payload "xrmw" the signature + * bank 0 blk 2085 0xc105 payload "SCfg".."SrNm" config, serial + * bank 3 blk 2085 0xc105 mirror of bank 0 + * bank 1 blk 2084 0xc104 fc f8 fc fc .. ff ff block status + * bank 2 blk 2085 0xc104 mirror of bank 1 + * + * 0xc105 is identity, not a map. 0xc104 is the only structural object + * of the three, it is mirrored across CEs the way context is, and a + * bitmap over 2088 blocks is what a block status table looks like. So + * the context is keyed on 0xc104. + * + * It read from 0x100 and 0x200. An FPart object payload begins at + * FPART_SPECIAL_HDR, 0x80, so those offsets land 0x80 and 0x180 bytes + * into the object rather than at its start. That is why cxt_loc was + * always 0: there was never anything at 0x100 to count. + * + * It read the bitmap as one byte per block with the bits meaning + * banks, and validated it by rejecting any byte with bits set outside + * num_cau -- mask 0x3 here. Every byte actually present is fc, f8 or + * ff, so that test rejects all of them. It is a bit per block, not a + * byte per block. + * + * And it took whichever copy was scanned last. There are two, and a + * later one silently replaced an earlier one. The first valid copy now + * wins and a disagreeing mirror is reported rather than applied. + * + * The block status table is kept separately from vfl.bank_mask. That + * one is a byte per VBN whose bits select banks, whimory_vfl_bank() + * resolves every read through it, and it is not this. Loading one into + * the other is what sent reads to the wrong die when this function was + * matching the signature block. + */ +/* + * SysCfg tags are stored byte-reversed, so "SrNm" is 6d 4e 72 53. + */ +static bool whimory_syscfg_tag(const u8 *p, const char *tag) +{ + return p[0] == tag[3] && p[1] == tag[2] && + p[2] == tag[1] && p[3] == tag[0]; +} + +static void whimory_syscfg_str(char *dst, size_t dstlen, const u8 *src, + size_t len) +{ + size_t i, n = min(len, dstlen - 1); + + for (i = 0; i < n; i++) { + /* + * Stop at the first byte that is not printable text. + * + * Padding is NUL or 0xff, but a value can also be followed + * directly by binary belonging to the next field -- MLB# is, + * and substituting a placeholder for it put a stray "?" on the + * end of the board number. Two tags can also sit adjacent with + * no value between them, which CNTB and MtCl do, and that is a + * legitimately empty string rather than garbage. + */ + if (src[i] < 0x20 || src[i] > 0x7e) + break; + dst[i] = src[i]; + } + dst[i] = 0; +} + +/* + * Parse the SysCfg object into something a person can read. + * + * The record stride is not constant -- the serial takes 16 bytes and the + * board number more -- so rather than assume one, this locates every tag + * it knows and treats each value as running to the next tag found. That + * is robust to a size we guessed wrong and to tags we have not seen. + */ +static void whimory_syscfg_parse(struct whimory *w, const u8 *p, + unsigned int len) +{ + static const char * const tags[] = { + "SrNm", "FwId", "HwId", "HwVr", "SwVr", "MLB#", + "CNTB", "MtCl", "Mod#", "Regn", "BMac", + }; + unsigned int off[ARRAY_SIZE(tags)]; + unsigned int i, o, n = 0; + + kvfree(w->syscfg.raw); + memset(&w->syscfg, 0, sizeof(w->syscfg)); + if (len < 24 || !whimory_syscfg_tag(p, "SCfg")) + return; + w->syscfg.entries = get_unaligned_le32(p + 0x14); + + /* + * Keep the section itself, and index every 4-byte-aligned printable + * group in it. + * + * The decode below understands eleven tags. Anything else in the + * section is unreachable, and this part carries records that have not + * been identified. A printable group is a candidate rather than a + * confirmed tag -- ASCII values such as the serial match the same + * test -- so the candidates are reported as such, flagged when they + * are one of the known tags, and the verbatim bytes are kept beside + * them so an unrecognised record can still be read out. + */ + w->syscfg.raw = NULL; + w->syscfg.raw_len = 0; + w->syscfg.n_cand = 0; + w->syscfg.raw = kvmalloc(len, GFP_KERNEL); + if (w->syscfg.raw) { + memcpy(w->syscfg.raw, p, len); + w->syscfg.raw_len = len; + } + + for (o = 0; o + 4 <= len && w->syscfg.n_cand < N31_SYSCFG_MAX_CAND; + o += 4) { + unsigned int k, idx; + char *t; + + for (k = 0; k < 4; k++) + if (p[o + k] < 0x20 || p[o + k] > 0x7e) + break; + if (k != 4) + continue; + + idx = w->syscfg.n_cand++; + t = w->syscfg.cand[idx].tag; + /* Tags sit byte-reversed on disk; record them forwards. */ + t[0] = p[o + 3]; + t[1] = p[o + 2]; + t[2] = p[o + 1]; + t[3] = p[o + 0]; + t[4] = 0; + w->syscfg.cand[idx].off = o; + w->syscfg.cand[idx].known = false; + for (k = 0; k < ARRAY_SIZE(tags); k++) + if (!strcmp(t, tags[k])) + w->syscfg.cand[idx].known = true; + } + /* A record runs to the next candidate, or to the end of the section. */ + for (i = 0; i < w->syscfg.n_cand; i++) + w->syscfg.cand[i].len = + ((i + 1 < w->syscfg.n_cand) ? w->syscfg.cand[i + 1].off + : len) - + w->syscfg.cand[i].off; + + for (i = 0; i < ARRAY_SIZE(tags); i++) + off[i] = 0; + + /* Locate every tag we know, on its 4-byte alignment. */ + for (o = 4; o + 4 <= len; o += 4) { + for (i = 0; i < ARRAY_SIZE(tags); i++) { + if (!off[i] && whimory_syscfg_tag(p + o, tags[i])) { + off[i] = o; + n++; + break; + } + } + } + if (!n) + return; + + for (i = 0; i < ARRAY_SIZE(tags); i++) { + unsigned int vstart, vend, j; + + if (!off[i]) + continue; + vstart = off[i] + 4; + vend = len; + /* Value runs to the next tag that was found after this one. */ + for (j = 0; j < ARRAY_SIZE(tags); j++) + if (off[j] > off[i] && off[j] < vend) + vend = off[j]; + if (vend <= vstart) + continue; + + if (!strcmp(tags[i], "SrNm")) + whimory_syscfg_str(w->syscfg.serial, + sizeof(w->syscfg.serial), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "Mod#")) + whimory_syscfg_str(w->syscfg.model, + sizeof(w->syscfg.model), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "MLB#")) + whimory_syscfg_str(w->syscfg.mlb, + sizeof(w->syscfg.mlb), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "SwVr")) + whimory_syscfg_str(w->syscfg.sw_ver, + sizeof(w->syscfg.sw_ver), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "CNTB")) + whimory_syscfg_str(w->syscfg.cnt_b, + sizeof(w->syscfg.cnt_b), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "MtCl")) + whimory_syscfg_str(w->syscfg.mt_cl, + sizeof(w->syscfg.mt_cl), + p + vstart, vend - vstart); + else if (!strcmp(tags[i], "BMac") && vend - vstart >= 6) { + memcpy(w->syscfg.mac, p + vstart, 6); + w->syscfg.mac_ok = true; + } else if (!strcmp(tags[i], "Regn") && vend - vstart >= 4) + w->syscfg.region = get_unaligned_le32(p + vstart); + else if (!strcmp(tags[i], "HwVr") && vend - vstart >= 8) + w->syscfg.hw_ver = get_unaligned_le32(p + vstart + 4); + else if (!strcmp(tags[i], "FwId") && vend - vstart >= 8) + w->syscfg.fw_id = get_unaligned_le32(p + vstart + 4); + } + + /* + * Touch calibration: the panel calibration block, kept for apple-grape. + * + * The SysCfg object carries a second region past the identity records, + * at 0x1dc0 into the payload. It is not identity and it is not map + * data: tags NI and MB, then per-node byte tables and 16-bit pairs. + * That is panel calibration, and it belongs to the touch controller + * rather than to storage -- but this is the only place it exists, so + * the FTL is where it has to be read from. + * + * Copied verbatim rather than interpreted. The layout is the touch + * driver problem, and guessing at it here would only add a second + * opinion about bytes this code has no stake in. + */ + /* + * Look for a real touch calibration container anywhere in the page. + * + * apple-grape wants the blob whose magic word is 0x53797349, and + * takes its calibration from decimal +350 for 0x200 bytes, + * byte-swapped per u32 -- so the container has to be at least 862 + * bytes long. The region this driver has been exposing as touch_cal + * lives at a fixed offset and carries no such magic, so it is some + * other SysCfg record and cannot be what the touch controller is + * asking for. Scan for the magic rather than trusting the offset, + * and report what is actually there. + */ + w->syscfg.touch_cal_magic_off = -1; + for (i = 0; i + 4 <= len; i += 4) { + if (get_unaligned_le32(p + i) == 0x53797349u) { + w->syscfg.touch_cal_magic_off = (int)i; + dev_info(w->dev, + "SysCfg touch calibration magic at +0x%x (%u bytes to end of page)\n", + i, len - i); + break; + } + } + if (w->syscfg.touch_cal_magic_off < 0) + dev_info(w->dev, + "SysCfg has no touch calibration magic; touch_cal region is a different record\n"); + + /* + * Copy the container the scan actually found, whole. + * + * Stock copies 0x560 bytes from the calibration descriptor and apple-grape + * reads its calibration at +350 for 0x200, so the consumer needs + * 862 bytes measured from the magic. Two things here used to make + * that impossible: + * + * - the copy came from N31_TOUCH_CAL_OFF, a fixed offset that the scan + * above reports carries no touch calibration magic, so it was some other + * SysCfg record entirely; + * - the length was trimmed back to the last byte that was neither + * 0x00 nor 0xff, which silently shortens any blob whose tail is + * padding -- and calibration data ending in zeros is ordinary. + * + * So: take it from the magic when the magic is present, keep the + * full length, and say so when the page cannot supply all of it. + * N31_TOUCH_CAL_OFF remains only as the fallback for a page with no + * magic at all, where there is nothing better to point at. + */ + if (w->syscfg.touch_cal_magic_off >= 0) { + unsigned int off = (unsigned int)w->syscfg.touch_cal_magic_off; + unsigned int n = min_t(unsigned int, len - off, + (unsigned int)sizeof(w->syscfg.touch_cal)); + + memcpy(w->syscfg.touch_cal, p + off, n); + w->syscfg.touch_cal_len = n; + if (n < N31_TOUCH_CAL_LEN) + dev_warn(w->dev, + "SysCfg touch calibration truncated: %u of %u bytes (page ends %u after the magic)\n", + n, N31_TOUCH_CAL_LEN, len - off); + if (n < N31_TOUCH_CAL_CAL_OFF + N31_TOUCH_CAL_CAL_LEN) + dev_warn(w->dev, + "SysCfg touch calibration too short for the touch calibration window (need %u, have %u)\n", + N31_TOUCH_CAL_CAL_OFF + N31_TOUCH_CAL_CAL_LEN, n); + } else { + /* + * No magic: this page does not carry the blob, so do not + * present anything as if it did. + * + * The authoritative copy comes from the A34 handoff, which + * U-Boot republishes in reserved DRAM and advertises as + * /chosen apple,n31-touch_cal-addr and apple,n31-touch_cal-size -- + * measured on this device as 0x560 bytes, the same length + * stock copies and the same length apple-grape demands + * exactly. Handing a consumer 1376 bytes of some other + * SysCfg record would satisfy that length check with the + * wrong data, which is worse than returning nothing. + */ + w->syscfg.touch_cal_len = 0; + dev_info(w->dev, + "SysCfg carries no touch calibration blob; the touch calibration source is the U-Boot region (/chosen apple,n31-touch_cal-*)\n"); + } + w->syscfg.valid = true; + dev_info(w->dev, + "SysCfg %u entries: serial=%s model=%s sw=%s\n", + w->syscfg.entries, w->syscfg.serial, w->syscfg.model, + w->syscfg.sw_ver); + dev_info(w->dev, "SysCfg touch calibration: %u bytes\n", + w->syscfg.touch_cal_len); +} + +/* + * Hand the raw touch calibration block to whoever owns the panel. Returns its length, + * or 0 when the SysCfg object has not been read. + */ +size_t whimory_syscfg_touch_cal(const u8 **out) +{ + struct whimory *w = whimory_dev; + + if (!w || !w->syscfg.valid || !w->syscfg.touch_cal_len) + return 0; + if (out) + *out = w->syscfg.touch_cal; + return w->syscfg.touch_cal_len; +} +EXPORT_SYMBOL_GPL(whimory_syscfg_touch_cal); + +int whimory_touch_cal_show(char *buf, size_t len) +{ + const u8 *d = NULL; + size_t n = whimory_syscfg_touch_cal(&d); + int o = 0; + size_t i; + + if (!n) + return scnprintf(buf, len, "no touch_cal block\n"); + for (i = 0; i < n && o < (int)len - 80; i += 16) + o += scnprintf(buf + o, len - o, "%04zx: %*ph\n", + i, (int)min_t(size_t, 16, n - i), d + i); + return o; +} +EXPORT_SYMBOL_GPL(whimory_touch_cal_show); + +int whimory_syscfg_show(char *buf, size_t len) +{ + struct whimory *w = whimory_dev; + int n = 0; + + if (!w || !w->syscfg.valid) + return scnprintf(buf, len, "syscfg not read\n"); + n += scnprintf(buf + n, len - n, "entries=%u\n", w->syscfg.entries); + n += scnprintf(buf + n, len - n, "serial=%s\n", w->syscfg.serial); + n += scnprintf(buf + n, len - n, "model=%s\n", w->syscfg.model); + n += scnprintf(buf + n, len - n, "mlb=%s\n", w->syscfg.mlb); + n += scnprintf(buf + n, len - n, "sw_version=%s\n", w->syscfg.sw_ver); + n += scnprintf(buf + n, len - n, "cntb=%s\n", w->syscfg.cnt_b); + n += scnprintf(buf + n, len - n, "mtcl=%s\n", w->syscfg.mt_cl); + if (w->syscfg.mac_ok) + n += scnprintf(buf + n, len - n, "bt_mac=%pM\n", + w->syscfg.mac); + n += scnprintf(buf + n, len - n, "region=0x%08x\n", w->syscfg.region); + n += scnprintf(buf + n, len - n, "hw_version=0x%08x\n", w->syscfg.hw_ver); + n += scnprintf(buf + n, len - n, "fw_id=0x%08x\n", w->syscfg.fw_id); + return n; +} +EXPORT_SYMBOL_GPL(whimory_syscfg_show); + +int whimory_fpart_objects_show(char *buf, size_t len) +{ + struct whimory *w = whimory_dev; + int n = 0; + u16 i; + + if (!w) + return scnprintf(buf, len, "no device\n"); + n += scnprintf(buf + n, len - n, + "# type_word bank ce cau block what\n"); + for (i = 0; i < w->fpart_ctx.count && n < (int)len - 96; i++) { + struct fpart_special_entry *e = &w->fpart_ctx.table[i]; + unsigned int ce, cau; + const char *what; + + fpart_bank_to_ce_cau(w, e->bank, &ce, &cau); + switch (e->type_word) { + case FPART_TYPE_SIGNATURE: + what = "signature"; + break; + case FPART_TYPE_VFL_CXT: + what = "block status bitmap"; + break; + case FPART_TYPE_CONFIG: + what = "SysCfg device identity"; + break; + default: + what = "unknown"; + break; + } + n += scnprintf(buf + n, len - n, + "%u 0x%04x %u %u %u %u %s\n", + i, e->type_word, e->bank, ce, cau, + e->block, what); + } + return n; +} +EXPORT_SYMBOL_GPL(whimory_fpart_objects_show); + +/* + * Log only the parts of an object that carry something. + * + * These objects are a few hundred bytes of content in a 4 KiB page, and + * dumping all of it buries the interesting part. This walks the page in + * 16-byte rows and prints a row only when it is neither all zero nor all + * 0xff, which is what fill looks like here. + */ +static void whimory_dump_sparse(struct whimory *w, const char *tag, + const u8 *p, unsigned int len) +{ + unsigned int o, shown = 0; + + if (!fpart_dump) + return; + + for (o = 0; o + 16 <= len && shown < 40; o += 16) { + unsigned int i; + bool zero = true, ones = true; + + for (i = 0; i < 16; i++) { + if (p[o + i] != 0x00) + zero = false; + if (p[o + i] != 0xff) + ones = false; + } + if (zero || ones) + continue; + shown++; + dev_info(w->dev, "%s +%04x: %16ph\n", tag, o, p + o); + } + dev_info(w->dev, "%s: %u non-fill rows in %u bytes\n", + tag, shown, len); +} + +static int n31_vfl_ingest_ctx(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int block, + const u8 *page, unsigned int page_len, + const u8 *meta) +{ + unsigned int nbytes, nblk, i, bad = 0; + const u8 *payload; + u16 type_word; + + if (!page || !meta) + return 0; + if (cau >= w->geom.num_cau || !w->vfl.remap[cau]) + return 0; + if (meta[0] != FPART_META_TYPE_SPECIAL) + return 0; + + type_word = get_unaligned_le16(meta + 2); + if (type_word == FPART_TYPE_CONFIG && !w->syscfg.valid) { + whimory_syscfg_parse(w, page + FPART_SPECIAL_HDR, + page_len - FPART_SPECIAL_HDR); + whimory_dump_sparse(w, "C105", page, page_len); + } + if (type_word == FPART_TYPE_VFL_CXT && !w->vfl.bitmap_loaded) + whimory_dump_sparse(w, "C104", page, page_len); + if (type_word != FPART_TYPE_VFL_CXT) + return 0; + + nblk = w->geom.blocks_per_cau; + nbytes = DIV_ROUND_UP(nblk, 8); + if (!nblk || page_len < FPART_SPECIAL_HDR + nbytes) + return 0; + payload = page + FPART_SPECIAL_HDR; + + if (!w->vfl.blk_status) { + w->vfl.blk_status = kvmalloc(nbytes, GFP_KERNEL); + if (!w->vfl.blk_status) + return 0; + memset(w->vfl.blk_status, 0xff, nbytes); + } + + for (i = 0; i < nblk; i++) + if (!(payload[i >> 3] & (1u << (i & 7)))) + bad++; + + /* + * A table claiming most of the device is unusable is not a table + * we found, it is bytes that happened to sit at this offset. + */ + if (bad > nblk / 4) { + dev_warn(w->dev, + "VFL ctx blk=%u type=0x%04x rejected: %u of %u blocks marked bad\n", + block, type_word, bad, nblk); + return 0; + } + + if (!w->vfl.bitmap_loaded) { + memcpy(w->vfl.blk_status, payload, nbytes); + w->vfl.blk_status_bad = bad; + w->vfl.blk_status_bank = ce * (w->geom.num_cau ? w->geom.num_cau : 1) + cau; + w->vfl.blk_status_block = block; + w->vfl.bitmap_loaded = 1; + w->vfl.ctx_ce[cau] = ce; + w->vfl.ctx_block[cau] = block; + dev_info(w->dev, + "VFL ctx ce=%u cau=%u blk=%u type=0x%04x accepted: %u/%u blocks bad\n", + ce, cau, block, type_word, bad, nblk); + } else if (memcmp(w->vfl.blk_status, payload, nbytes)) { + /* Mirrors should agree. Say so if they do not; keep the first. */ + w->vfl.blk_status_mismatch++; + dev_warn(w->dev, + "VFL ctx mirror ce=%u cau=%u blk=%u disagrees with blk=%u (kept)\n", + ce, cau, block, w->vfl.blk_status_block); + } + + /* VBN->PBN is identity here; replacement lives in the u16 tables. */ + w->vfl.remap_count = nblk; + return 1; +} + +static int n31_vfl_open(struct whimory *w) +{ + struct s5l8740_cs_page *csp; + u8 *page; + u8 meta[S5L8740_NAND_META_SIZE]; + unsigned int ce, cau, b; + int hits = 0; + int sess; + + page = kvmalloc(S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + if (!page) + return -ENOMEM; + csp = kvmalloc(sizeof(*csp), GFP_KERNEL); + if (!csp) { + kvfree(page); + return -ENOMEM; + } + + /* + * Arm live CS for the reads, the same way FPART_SCAN does. + * + * cs_phys_read_slc() needs the DMA session; without one it returns + * -ENODEV before issuing anything, so the whole scan completed in + * about sixty milliseconds and reported ctx_hits=0 -- the same + * instant-failure profile the meta refusal used to produce, for a + * different reason. + * + * Sessions nest and begin() always succeeds, so this owns exactly + * its own end; an outer session keeps CS armed past it. + */ + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + dev_warn(w->dev, "VFL_Open no DMA session (%d)\n", sess); + kvfree(csp); + kvfree(page); + return sess; + } + s5l8740_nand_reset(); + + /* + * Read the copies the FPart directory names, and nothing else. + * + * This used to sweep the whole system area -- every block from + * blocks_per_cau - vfl_tail up, eight pages each, on all four banks. + * That is 4096 page reads, it took seventeen seconds of a + * forty-second mount, and it found the context it wanted eight and a + * half seconds before it stopped looking, because the loop had no + * reason to end early. + * + * Stock does not do this. s_fpart keeps a table of special-object + * copies -- six bytes of {bank, block, type} per entry, indexed by + * type -- and sub_4F12DC walks only the copies of the one type it + * was asked for, taking the highest generation. fpart_locate_special() + * builds exactly that table and fpart_read_special_by_index() is a + * transcription of that walk; the scan behind them has already run + * for the signature by the time VFL_Open is called, so the lookup + * here is a cache hit and costs nothing. + * + * On this unit the table holds five entries and two of them are the + * 0xc104 context, so this is five page reads against 4096. The + * objects live at page 0 of their block -- chunk 0, payload at + * FPART_SPECIAL_HDR -- which is where the sweep found them too, and + * n31_vfl_ingest_ctx() still does the parsing and the mirror + * comparison exactly as before. + */ + { + u16 idx; + + /* + * Force the directory to exist. It normally already does -- + * n31_fpart_read_signature() ran first -- and this is then a + * cache lookup. Its return value is not the point: a miss + * here is reported by the loop below finding no context. + */ + fpart_locate_special(w, &idx, FPART_TYPE_VFL_CXT); + } + + for (b = 0; b < w->fpart_ctx.count; b++) { + const struct fpart_special_entry *e = &w->fpart_ctx.table[b]; + int got; + + cond_resched(); + fpart_bank_to_ce_cau(w, e->bank, &ce, &cau); + /* + * fpart_fil_read_page(), not s5l8740_nand_page_read(). + * page_read refuses any request carrying meta unless + * meta_dma_read is set, and that is deliberately off because + * a permanent live CS kick reboots the device -- so every + * read here used to return -EOPNOTSUPP before touching the + * NAND, and VFL_Open reported ctx_hits=0 about a region + * nobody had actually looked at. + * + * The shared helper also gets the plane right: the tail of + * each CAU is SLC, so it tries slc=1 before slc=0. Reading it + * as MLC returns a page of zeros that looks exactly like a + * successful read of an empty block. + */ + got = fpart_fil_read_page(w, e->bank, e->block, 0, csp, page, + meta); + if (got) + continue; + if (n31_vfl_ingest_ctx(w, ce, cau, e->block, page, + S5L8740_NAND_PAGE_SIZE, meta)) + hits++; + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + kvfree(csp); + kvfree(page); + w->vfl.ctx_hits = hits; + w->vfl_ok = true; + dev_info(w->dev, + "VFL_Open OK ctx_hits=%u remap_ents=%u bitmap=%u bad_blocks=%u src_bank=%u mirror_diff=%u\n", + hits, w->vfl.remap_count, w->vfl.bitmap_loaded, + w->vfl.blk_status_bad, w->vfl.blk_status_bank, + w->vfl.blk_status_mismatch); + return 0; +} + +static u32 n31_vfl_get_param(struct whimory *w, u32 selector) +{ + switch (selector) { + case WHIMORY_VFL_PARAM_NUM_SB: + return w->geom.num_ce * w->geom.num_cau * w->geom.user_blocks; + default: + return 0; + } +} + + +/* + * Read-ahead window, filled along the VBA axis. + * + * The first version of this prefetched consecutive physical pages of one + * block and made sequential reads four times SLOWER (1.6 MB/s against 6.7). + * Measured on the device, the FTL stripes consecutive LBAs across every + * (ce, cau) before advancing the page: + * + * lba+0..3 ce0/cau1 pg36 slot0..3 + * lba+4..7 ce1/cau0 pg36 slot0..3 + * lba+8..11 ce1/cau1 pg36 slot0..3 + * lba+12..15 ce0/cau0 pg37 slot0..3 + * + * so page+1 of one chip is 16 LBAs away, and a window of 8 consecutive + * pages held nothing the reader wanted next while costing eight pages of + * NAND to fill. Hence this version does not guess the stripe at all: it + * walks the VBAs the caller is about to ask for, unpacks each to its + * physical page, and fetches the distinct pages that fall out. That stays + * correct whatever the stripe turns out to be. + * + * Pages are grouped by CE because a command list is single-CE, so a lookahead + * spanning both chips costs one kick each rather than one in total. + */ +static int whimory_window_find(struct whimory_sftl *s, unsigned int ce, + unsigned int cau, unsigned int pblock, + unsigned int page) +{ + unsigned int i; + + for (i = 0; i < s->rc_count; i++) { + if (s->rc_key[i].valid && s->rc_key[i].ce == ce && + s->rc_key[i].cau == cau && + s->rc_key[i].pblock == pblock && + s->rc_key[i].page == page) + return (int)i; + } + return -1; +} + +static void whimory_window_fill(struct whimory *w, u32 vba) +{ + struct whimory_sftl *s = &w->sftl; + struct s5l8740_ppn_ref refs[WHIMORY_RC_SLOTS]; + unsigned int ref_ce[WHIMORY_RC_SLOTS]; + unsigned int want = read_prefetch_pages; + unsigned int i, n = 0, ce_i, base; + + if (want > WHIMORY_RC_SLOTS) + want = WHIMORY_RC_SLOTS; + + s->rc_count = 0; + for (i = 0; i < read_prefetch_vbas && n < want; i++) { + u32 ce, cau, vblock, page, slot, pblock; + unsigned int j; + bool dup = false; + + if (whimory_unpack_vba(w, vba + i, &ce, &cau, &vblock, &page, + &slot)) + break; + if (page >= s->pages_per_sb || slot >= s->vbas_per_page) + break; + whimory_vfl_resolve(w, vblock, &cau, &pblock); + for (j = 0; j < n; j++) { + if (ref_ce[j] == ce && refs[j].cau == cau && + refs[j].block == pblock && refs[j].page == page) { + dup = true; + break; + } + } + if (dup) + continue; + ref_ce[n] = ce; + refs[n].cau = (u8)cau; + refs[n].block = (u16)pblock; + refs[n].page = (u8)page; + n++; + } + if (!n) + return; + + /* + * One kick per CE. Entries are emitted in the order they were + * discovered so slot indices stay aligned with rc_key. + */ + base = 0; + for (ce_i = 0; ce_i < WHIMORY_NUM_CE_MAX; ce_i++) { + struct s5l8740_ppn_ref grp[WHIMORY_RC_SLOTS]; + unsigned int map[WHIMORY_RC_SLOTS]; + unsigned int g = 0; + + for (i = 0; i < n; i++) { + if (ref_ce[i] != ce_i) + continue; + map[g] = i; + grp[g++] = refs[i]; + } + if (!g) + continue; + rc_pages += g; + if (s5l8740_nand_cs_read_pages_batch((u8)ce_i, grp, g, + s->rc_stage, + s->rc_stage_meta)) { + rc_fails++; + continue; + } + for (i = 0; i < g; i++) { + unsigned int dst = base + i; + + if (dst >= WHIMORY_RC_SLOTS) + break; + memcpy(s->rc_data + (size_t)dst * S5L8740_NAND_PAGE_SIZE, + s->rc_stage + (size_t)i * S5L8740_NAND_PAGE_SIZE, + S5L8740_NAND_PAGE_SIZE); + memcpy(s->rc_meta + (size_t)dst * S5L8740_NAND_META_SIZE, + s->rc_stage_meta + (size_t)i * S5L8740_NAND_META_SIZE, + S5L8740_NAND_META_SIZE); + s->rc_key[dst].ce = ref_ce[map[i]]; + s->rc_key[dst].cau = refs[map[i]].cau; + s->rc_key[dst].pblock = refs[map[i]].block; + s->rc_key[dst].page = refs[map[i]].page; + s->rc_key[dst].valid = true; + } + base += g; + if (base > WHIMORY_RC_SLOTS) + base = WHIMORY_RC_SLOTS; + s->rc_count = base; + rc_fills++; + } +} + +static int whimory_window_read(struct whimory *w, u32 vba, unsigned int ce, + unsigned int cau, unsigned int pblock, + unsigned int page, u8 *pagebuf, u8 *spare, + size_t spare_len) +{ + struct whimory_sftl *s = &w->sftl; + int idx; + + if (!read_prefetch_pages || !s->rc_data || !s->rc_meta || !s->rc_stage) + return -ENODEV; + + idx = whimory_window_find(s, ce, cau, pblock, page); + if (idx < 0) { + whimory_window_fill(w, vba); + idx = whimory_window_find(s, ce, cau, pblock, page); + rc_misses++; + if (idx < 0) + return -ENOENT; + } else { + rc_hits++; + } + + memcpy(pagebuf, s->rc_data + (size_t)idx * S5L8740_NAND_PAGE_SIZE, + S5L8740_NAND_PAGE_SIZE); + memcpy(spare, s->rc_meta + (size_t)idx * S5L8740_NAND_META_SIZE, + min_t(size_t, spare_len, S5L8740_NAND_META_SIZE)); + return 0; +} + +static int n31_vfl_read_vba(struct whimory *w, u32 vba, u32 count, + void *data, struct whimory_meta *meta) +{ + u32 i, ce, cau, vblock, page, slot, pblock; + u32 last_ce = ~0u, last_cau = ~0u, last_pblock = ~0u, last_page = ~0u; + u8 *pagebuf; + u8 spare[S5L8740_NAND_META_SIZE]; + int ret; + + if (!count || count > WHIMORY_VBAS_PER_PAGE) + return -EINVAL; + pagebuf = w->sftl.data_page; + if (!pagebuf) + return -ENOMEM; + + for (i = 0; i < count; i++) { + u8 *dst = (u8 *)data + i * WHIMORY_LBA_SIZE; + + ret = whimory_unpack_vba(w, vba + i, &ce, &cau, &vblock, + &page, &slot); + if (ret) + return ret; + if (page >= w->sftl.pages_per_sb || + slot >= w->sftl.vbas_per_page) + return -ERANGE; + whimory_vfl_resolve(w, vblock, &cau, &pblock); + if (ce != last_ce || cau != last_cau || pblock != last_pblock || + page != last_page) { + /* + * Cross-call page cache. The loop below already avoids + * re-reading a page within one call, but the block + * layer calls this with count == 1 for every 4 KiB, so + * that only ever helped the recovery scans. Four LBAs + * share a page, so a sequential read was fetching each + * page four times. + */ + if (!whimory_window_read(w, vba + i, ce, cau, pblock, + page, pagebuf, spare, + sizeof(spare))) { + /* Served from the read-ahead window. */ + } else if (w->sftl.page_cache && + w->sftl.page_cache_valid && + ce == w->sftl.pc_ce && cau == w->sftl.pc_cau && + pblock == w->sftl.pc_pblock && + page == w->sftl.pc_page) { + memcpy(pagebuf, w->sftl.page_cache, + S5L8740_NAND_PAGE_SIZE); + memcpy(spare, w->sftl.page_cache_spare, + sizeof(spare)); + } else { + ret = whimory_cs_read_page(w, ce, cau, pblock, + page, pagebuf, + S5L8740_NAND_PAGE_SIZE, + spare, sizeof(spare)); + if (ret) { + /* + * Do not leave a stale key pointing at + * a buffer this read may have already + * partly overwritten. + */ + w->sftl.page_cache_valid = false; + return ret; + } + if (w->sftl.page_cache) { + memcpy(w->sftl.page_cache, pagebuf, + S5L8740_NAND_PAGE_SIZE); + memcpy(w->sftl.page_cache_spare, spare, + sizeof(spare)); + w->sftl.pc_ce = ce; + w->sftl.pc_cau = cau; + w->sftl.pc_pblock = pblock; + w->sftl.pc_page = page; + w->sftl.page_cache_valid = true; + } + } + last_ce = ce; + last_cau = cau; + last_pblock = pblock; + last_page = page; + } + memcpy(dst, pagebuf + slot * WHIMORY_LBA_SIZE, WHIMORY_LBA_SIZE); + if (meta && i == 0) { + memset(meta, 0xff, sizeof(*meta)); + if (sizeof(spare) >= (slot + 1) * WHIMORY_META_SIZE) + memcpy(meta, spare + slot * WHIMORY_META_SIZE, + sizeof(*meta)); + } + } + return 0; +} + +static const struct whimory_vfl_ops n31_vfl_ops = { + .major = 0, + .minor = n31_vfl_minor, + .init = n31_vfl_init, + .open = n31_vfl_open, + .get_param = n31_vfl_get_param, + .read_vba = n31_vfl_read_vba, +}; + +static int whimory_vfl_open(struct whimory *w) +{ + int ret; + + ret = w->vfl_ops->init(w); + if (ret) { + dev_err(w->dev, "VFL_Init failed: %d\n", ret); + return ret; + } + ret = w->vfl_ops->open(w); + if (ret) { + dev_err(w->dev, "VFL_Open failed: %d\n", ret); + return ret; + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* SFTL recovery — classify SBs, replay BTOC/META by weave */ +/* ------------------------------------------------------------------ */ + +/* FFFF0001 payload is {count, [lba,span]...} → unmap. */ +static int whimory_sftl_apply_list(struct whimory *w, u32 vba) +{ + u8 *buf; + u32 count, i, lba, span; + struct whimory_meta meta; + int ret; + + /* + * Must not alias data_page: n31_vfl_read_vba uses data_page as the + * page scratch. Prefer gc_data; else a stack-sized one-shot alloc. + */ + buf = w->sftl.gc_data; + if (!buf) { + buf = kmalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + if (!w->vfl_ops || !w->vfl_ops->read_vba) { + if (buf != w->sftl.gc_data) + kfree(buf); + return -ENOMEM; + } + ret = w->vfl_ops->read_vba(w, vba, 1, buf, &meta); + if (ret) { + if (buf != w->sftl.gc_data) + kfree(buf); + return ret; + } + count = get_unaligned_le32(buf); + if (!count || count > (WHIMORY_LBA_SIZE - 4) / 8) { + if (buf != w->sftl.gc_data) + kfree(buf); + return -EINVAL; + } + for (i = 0; i < count; i++) { + lba = get_unaligned_le32(buf + 4 + 8 * i); + span = get_unaligned_le32(buf + 8 + 8 * i); + /* Cap runaway garbage spans from misclassified tokens. */ + if (!span || whimory_special_lba(lba) || lba >= 0x01000000u) + break; + if (span > WHIMORY_L2V_ROOT_SPAN) + span = WHIMORY_L2V_ROOT_SPAN; + ret = whimory_l2v_update(w, lba, span, w->l2v.invalid_vba); + if (ret) { + if (buf != w->sftl.gc_data) + kfree(buf); + return ret; + } + w->sftl.token_list_applied++; + } + if (buf != w->sftl.gc_data) + kfree(buf); + return 0; +} + +static bool whimory_btoc_looks_be_lpn(const u8 *page) +{ + u32 a = get_unaligned_be32(page); + u32 b = get_unaligned_be32(page + 4); + u32 c = get_unaligned_be32(page + 8); + u32 d = get_unaligned_be32(page + 12); + unsigned int i, ok = 0; + + if (a < 0x01000000u && b == a + 1 && c == b + 1 && d == c + 1) + return true; + if (a == 0 && (b == 1 || b == WHIMORY_VBAS_PER_PAGE) && + c == b + (b == 1 ? 1 : WHIMORY_VBAS_PER_PAGE)) + return true; + for (i = 0; i < 16; i++) { + u32 v = get_unaligned_be32(page + i * 4); + + if (v != 0xffffffff && v < 0x01000000u) + ok++; + } + return ok >= 12; +} + +/* Live N31 SFTL BTOC: 16-byte BE records, span in last byte (fmss glass). */ +static bool whimory_btoc_looks_be_bte(const u8 *page) +{ + u32 weave0, lba0, lba1; + u32 span0, span1; + + if (whimory_page_blank(page, 64)) + return false; + weave0 = get_unaligned_be32(page); + lba0 = get_unaligned_be32(page + 8); + span0 = page[15]; + if (weave0 != 0 || !span0 || span0 > WHIMORY_DATA_VBAS_PER_SB || + lba0 >= 0x01000000u) + return false; + lba1 = get_unaligned_be32(page + 16 + 8); + span1 = page[16 + 15]; + if (!span1 || span1 > WHIMORY_DATA_VBAS_PER_SB || lba1 >= 0x01000000u) + return false; + if (lba1 != lba0 + span0 && lba1 + span1 != lba0 && + (lba1 < lba0 || lba1 > lba0 + span0 + 8)) + return false; + return true; +} + + +/* + * BTOC is a physical-slot index only. Per-slot CS metadata is the L2V key. + * Never L2V_Update(btoc_lpn) — zeros/holes in BTOC were poisoning L2V[0]. + */ +static int whimory_l2v_update_from_slot_meta(struct whimory *w, + unsigned int ce, unsigned int cau, + unsigned int vblock, + unsigned int page, unsigned int slot, + const u8 *meta16, + u32 btoc_hint_lba) +{ + u32 meta_lba, vba; + + if (!meta16) + return 0; + if (meta16[0] != WHIMORY_META_TYPE_DATA && + meta16[0] != WHIMORY_META_TYPE_DATA2) + return 0; + if (meta16[1] & 0x02) + return 0; + if (whimory_meta_erased(meta16, WHIMORY_META_SIZE)) + return 0; + meta_lba = get_unaligned_le32(meta16 + 8); + if (whimory_special_lba(meta_lba) || meta_lba >= 0x01000000u) + return 0; + if (btoc_hint_lba != 0xffffffffu && btoc_hint_lba != meta_lba) { + w->sftl.btoc_meta_mismatch++; + dev_dbg(w->dev, + "btoc_meta_mismatch hint=%u meta_lba=%u ce=%u cau=%u vblock=%u pg=%u slot=%u\n", + btoc_hint_lba, meta_lba, ce, cau, vblock, page, slot); + /* Still trust metadata as authority. */ + } + vba = whimory_pack_vba(w, ce, cau, vblock, page, slot); + w->sftl.claim_weave = whimory_weave48(meta16); + if (w->sftl.claim_source == 0) + w->sftl.claim_source = 1; + if ((audit_lba_winners && whimory_audit_fmss_lba(meta_lba)) || + (l2v_trace_lba && meta_lba == l2v_trace_lba)) { + struct whimory_range *prev = + whimory_range_find(&w->ranges, meta_lba); + u64 prev_weave = prev ? prev->weave : 0; + u32 prev_vba = prev ? prev->vba : ~0u; + bool win = !prev || + !(prev->weave > w->sftl.claim_weave); + const char *src = w->sftl.claim_source == 2 ? "open" : + w->sftl.claim_source == 3 ? "CXT" : + w->sftl.claim_source == 4 ? "LIST" : + "BTOC/meta"; + + dev_info(w->dev, + "LBA_WINNER fmss_lba=%u candidate vba=%u " + "ce=%u cau=%u vblk=%u pg=%u slot=%u weave=%012llx " + "prev_vba=%u prev_weave=%012llx selected=%s " + "reason=%s source=%s\n", + meta_lba, vba, ce, cau, vblock, page, slot, + (unsigned long long)w->sftl.claim_weave, + prev_vba, (unsigned long long)prev_weave, + win ? "yes" : "no", + win ? (prev ? "newer_or_equal_weave" : "first") + : "older_weave_kept", + src); + } + if (whimory_l2v_update(w, meta_lba, 1, vba)) { + w->sftl.claim_weave = 0; + return -ENOMEM; + } + w->sftl.claim_weave = 0; + /* + * Same derivation, two callers. The BTOC walk builds the map from + * scratch; the CXT repair path uses it to rebuild the part of the + * map a stale checkpoint extent had claimed. Counting both as BTOC + * would hide how much of a recovery was repair. + */ + if (w->sftl.claim_source == 3) { + w->sftl.cxt_repair_slots++; + } else { + w->sftl.btoc_meta_confirmed++; + w->sftl.btoc_l2v_updates++; + w->sftl.btoc_recs++; + } + return 1; +} + +static int whimory_btoc_confirm_page(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int vblock, + unsigned int page, u32 btoc_hint_base, + bool hint_is_page_lpn) +{ + u8 spare[S5L8740_NAND_META_SIZE]; + u8 *data = w->sftl.data_page; + unsigned int slot; + u32 pblock, rcau = cau; + int ret, hits = 0; + + if (!btoc_meta_confirm) + return 0; + if (btoc_confirm_max && + w->sftl.btoc_confirm_pages >= btoc_confirm_max) { + w->sftl.btoc_confirm_capped++; + return 0; + } + if (recover_budget_ms && w->sftl.confirm_start_jiffies && + time_after(jiffies, w->sftl.confirm_start_jiffies + + msecs_to_jiffies(recover_budget_ms))) { + w->sftl.btoc_confirm_budget_stop++; + return 0; + } + if (!data) + return -ENOMEM; + /* + * Both steps. This did only the block translation, with the CAU the + * caller passed in -- so the page confirmed here could come from a + * different plane than the one every later read of the same VBA will + * fetch. Identity today; wrong the moment the bank bitmap is loaded. + */ + whimory_vfl_resolve(w, vblock, &rcau, &pblock); + ret = whimory_cs_read_page(w, ce, rcau, pblock, page, data, + S5L8740_NAND_PAGE_SIZE, spare, + sizeof(spare)); + if (ret) + return ret; + w->sftl.btoc_confirm_pages++; + if (recover_yield_us && (w->sftl.btoc_confirm_pages & 0x0f) == 0) { + cond_resched(); + usleep_range(recover_yield_us, recover_yield_us + 500); + } + whimory_note_payload_strings(w, data, S5L8740_NAND_PAGE_SIZE); + if (whimory_page_blank(data, 64) && whimory_meta_erased(spare, 16)) + return 0; + for (slot = 0; slot < WHIMORY_VBAS_PER_PAGE; slot++) { + u32 hint = 0xffffffffu; + + if (btoc_hint_base != 0xffffffffu) { + if (hint_is_page_lpn) + hint = btoc_hint_base * WHIMORY_VBAS_PER_PAGE + + slot; + else if (slot == 0) + hint = btoc_hint_base; + } + ret = whimory_l2v_update_from_slot_meta(w, ce, cau, vblock, + page, slot, + spare + slot * + WHIMORY_META_SIZE, + hint); + if (ret < 0) + return ret; + hits += ret; + } + return hits; +} + +/* + * Turn a BTOC offset into the page it names. + * + * The offsets a BTOC carries are whole-superblock VBA offsets -- s_btoc.c + * asserts vba == s_g_addr_to_vba(wr->sb, wr->nextVbaOfs) at line 230 -- so + * the bank comes out of the offset, not from whichever plane's page 127 + * happened to carry the BTOC. The parsers used to divide by vbas_per_page + * and keep the caller's (ce, cau), which is the retired per-bank reading: + * it names a page nbanks times too early and always on one plane. + * + * That was never a mapping error, because none of these parsers maps + * anything -- the BTE only chooses which pages to visit, and + * whimory_btoc_confirm_page() then applies each slot from that slot's own + * metadata. It was a coverage error: the sweep re-read the first quarter of + * one plane instead of walking the superblock, and stopped at + * WHIMORY_DATA_VBAS_PER_SB, a quarter of the way in on four banks. + * + * Same arithmetic as everything else now: build the VBA and unpack it. + */ +static int whimory_btoc_ofs_to_page(const struct whimory *w, u32 vblock, + u32 vba_ofs, unsigned int *ce, + unsigned int *cau, unsigned int *page, + unsigned int *slot) +{ + u32 vba = vblock * whimory_vbas_per_vblock(w) + vba_ofs; + u32 c, a, vb, pg, sl; + + if (whimory_unpack_vba(w, vba, &c, &a, &vb, &pg, &sl)) + return -ERANGE; + *ce = c; + *cau = a; + *page = pg; + *slot = sl; + return 0; +} + +/* Addresses one superblock holds, and how many of them carry data. */ +static u32 whimory_sb_vbas(const struct whimory *w, u32 vblock) +{ + return whimory_sb_banks(w, vblock, NULL) * w->sftl.pages_per_sb * + w->sftl.vbas_per_page; +} + +static u32 whimory_sb_data_vbas(const struct whimory *w, u32 vblock) +{ + return whimory_sb_banks(w, vblock, NULL) * + WHIMORY_DATA_PAGES_PER_SB * w->sftl.vbas_per_page; +} + +/* One key per (bank, page), for the visit-each-page-once dedup. */ +static u32 whimory_btoc_page_key(unsigned int ce, unsigned int cau, + unsigned int page) +{ + return ((u32)ce << 24) | ((u32)cau << 16) | (u32)page; +} + +static bool whimory_btoc_parse_be_lpn(struct whimory *w, const u8 *page, + unsigned int len, unsigned int ce, + unsigned int cau, unsigned int vblock) +{ + unsigned int i, n, hit = 0, valid = 0; + bool page_gran; + + /* + * Bound by this superblock's own data-address count, not the + * per-bank one -- see whimory_btoc_ofs_to_page(). + */ + n = min_t(unsigned int, len / 4, whimory_sb_data_vbas(w, vblock)); + for (i = 0; i < n; i++) { + u32 lpn = get_unaligned_be32(page + i * 4); + + if (lpn == 0xffffffff) + break; + valid++; + } + page_gran = valid > 0 && valid <= WHIMORY_DATA_PAGES_PER_SB; + if (w->sftl.btoc_pages_valid < 5) + dev_info(w->dev, + "BTOC_BE_LPN valid=%u %s ce=%u cau=%u vblock=%u " + "(meta-validated)\n", + valid, + page_gran ? "page-granularity x4" : "slot-granularity", + ce, cau, vblock); + + if (page_gran) + n = min(valid, (unsigned int)WHIMORY_DATA_PAGES_PER_SB); + for (i = 0; i < n; i++) { + u32 lpn = get_unaligned_be32(page + i * 4); + unsigned int pg, slot; + int got; + + w->sftl.btoc_entries_seen++; + if (lpn == 0xffffffff || lpn == WHIMORY_LBA_BLANK) { + w->sftl.btoc_hole_entries++; + continue; + } + if (whimory_special_lba(lpn)) { + if (lpn == WHIMORY_LBA_HOLE) + w->sftl.btoc_hole_entries++; + else if (lpn == WHIMORY_LBA_DELETED || + lpn == WHIMORY_LBA_LIST) + w->sftl.btoc_unmap_entries++; + else + w->sftl.btoc_unknown_entries++; + w->sftl.token_hole++; + continue; + } + if (lpn >= 0x01000000u) + continue; + /* Zero BTOC slots are holes — never poison L2V[0]. */ + if (lpn == 0) { + w->sftl.btoc_skipped_zero++; + continue; + } + if (page_gran) { + /* + * One record per page, in the superblock's page + * order: record i is page i of bank i % nbanks. + */ + unsigned int bce, bcau, bpg, bsl; + + if (whimory_btoc_ofs_to_page(w, vblock, + i * w->sftl.vbas_per_page, + &bce, &bcau, &bpg, &bsl)) + break; + got = whimory_btoc_confirm_page(w, bce, bcau, vblock, + bpg, lpn, true); + } else { + unsigned int bce, bcau; + + if (whimory_btoc_ofs_to_page(w, vblock, i, &bce, &bcau, + &pg, &slot)) + break; + if (slot != 0) + continue; + got = whimory_btoc_confirm_page(w, bce, bcau, vblock, + pg, lpn, false); + } + if (got < 0) + return hit > 0; + if (got > 0) + hit += got; + } + return hit > 0; +} + +static bool whimory_btoc_parse_be_bte(struct whimory *w, const u8 *page, + unsigned int len, unsigned int ce, + unsigned int cau, unsigned int vblock) +{ + unsigned int i, recs, vba_ofs = 0, hit = 0; + unsigned int last_pg = ~0u; + /* + * Bounds from this superblock's own width. WHIMORY_VBAS_PER_SB and + * WHIMORY_DATA_VBAS_PER_SB are the per-bank counts and stopped the + * walk a quarter of the way into a four-bank superblock. + */ + u32 sb_vbas = whimory_sb_vbas(w, vblock); + u32 sb_data_vbas = whimory_sb_data_vbas(w, vblock); + + recs = len / 16; + for (i = 0; i < recs; i++) { + const u8 *r = page + i * 16; + u32 lba = get_unaligned_be32(r + 8); + u32 span = r[15]; + unsigned int s; + + w->sftl.btoc_entries_seen++; + if (!span) + break; + if (whimory_special_lba(lba)) { + if (lba == WHIMORY_LBA_LIST) { + u32 vba = whimory_pack_vba(w, ce, cau, vblock, + vba_ofs / w->sftl.vbas_per_page, + vba_ofs % w->sftl.vbas_per_page); + + w->sftl.btoc_holelist_ffff0001++; + w->sftl.btoc_unmap_entries++; + if (btoc_apply_list) { + w->sftl.claim_source = 4; + if (whimory_sftl_apply_list(w, vba)) + dev_warn(w->dev, + "BE BTE list token vba=%u failed\n", + vba); + else + w->sftl.token_list++; + w->sftl.claim_source = 1; + } + } else if (lba == WHIMORY_LBA_HOLE) { + w->sftl.btoc_token_ffff0000++; + w->sftl.btoc_hole_entries++; + } else if (lba == WHIMORY_LBA_DELETED) { + w->sftl.btoc_token_ffffff00++; + w->sftl.btoc_unmap_entries++; + } else if (lba == WHIMORY_LBA_BLANK) { + w->sftl.btoc_token_ffffffff++; + w->sftl.btoc_hole_entries++; + } else { + w->sftl.btoc_unknown_entries++; + } + w->sftl.token_hole++; + if (vba_ofs + span > sb_vbas) + break; + vba_ofs += span; + continue; + } + if (span > sb_data_vbas || lba >= 0x01000000u) + break; + if (vba_ofs + span > sb_data_vbas) + break; + if (lba == 0) { + w->sftl.btoc_skipped_zero++; + vba_ofs += span; + continue; + } + for (s = 0; s < span; s++) { + unsigned int bce, bcau, pg, sl; + u32 key; + int got; + + if (whimory_btoc_ofs_to_page(w, vblock, vba_ofs + s, + &bce, &bcau, &pg, &sl)) + break; + key = whimory_btoc_page_key(bce, bcau, pg); + if (key == last_pg) + continue; + last_pg = key; + got = whimory_btoc_confirm_page(w, bce, bcau, vblock, + pg, 0xffffffffu, false); + if (got < 0) + return hit > 0; + if (got > 0) + hit += got; + } + vba_ofs += span; + } + return hit > 0; +} + +static bool whimory_btoc_parse_bte(struct whimory *w, const u8 *page, + unsigned int len, unsigned int ce, + unsigned int cau, unsigned int vblock) +{ + unsigned int i, recs, vba_ofs = 0, hit = 0; + unsigned int last_pg = ~0u; + /* + * Bounds from this superblock's own width. WHIMORY_VBAS_PER_SB and + * WHIMORY_DATA_VBAS_PER_SB are the per-bank counts and stopped the + * walk a quarter of the way into a four-bank superblock. + */ + u32 sb_vbas = whimory_sb_vbas(w, vblock); + u32 sb_data_vbas = whimory_sb_data_vbas(w, vblock); + + if (len < sizeof(struct whimory_bte) || whimory_page_blank(page, 64)) + return false; + + recs = len / sizeof(struct whimory_bte); + if (le32_to_cpu(((const struct whimory_bte *)page)->weave_seq_add)) + dev_dbg(w->dev, "BTOC weaveSeqAdd[0] != 0\n"); + + for (i = 0; i < recs; i++) { + const struct whimory_bte *bte = + (const struct whimory_bte *)(page + i * sizeof(*bte)); + u32 lba = le32_to_cpu(bte->lba); + u32 span = le32_to_cpu(bte->span); + unsigned int s; + + w->sftl.btoc_entries_seen++; + if (!span) + break; + if (whimory_special_lba(lba)) { + if (lba == WHIMORY_LBA_LIST) { + u32 vba = whimory_pack_vba(w, ce, cau, vblock, + vba_ofs / w->sftl.vbas_per_page, + vba_ofs % w->sftl.vbas_per_page); + w->sftl.btoc_unmap_entries++; + w->sftl.btoc_holelist_ffff0001++; + if (btoc_apply_list) { + w->sftl.claim_source = 4; + if (whimory_sftl_apply_list(w, vba)) + dev_warn(w->dev, + "list token vba=%u failed\n", + vba); + else + w->sftl.token_list++; + w->sftl.claim_source = 1; + } + } else if (lba == WHIMORY_LBA_HOLE) { + w->sftl.btoc_token_ffff0000++; + w->sftl.btoc_hole_entries++; + } else if (lba == WHIMORY_LBA_DELETED) { + w->sftl.btoc_token_ffffff00++; + w->sftl.btoc_unmap_entries++; + } else if (lba == WHIMORY_LBA_BLANK) { + w->sftl.btoc_token_ffffffff++; + w->sftl.btoc_hole_entries++; + } else { + w->sftl.btoc_unknown_entries++; + } + w->sftl.token_hole++; + if (vba_ofs + span > sb_vbas) + break; + vba_ofs += span; + continue; + } + if (span > sb_data_vbas || lba >= 0x01000000u) + break; + if (vba_ofs + span > sb_data_vbas) + break; + if (lba == 0) { + w->sftl.btoc_skipped_zero++; + vba_ofs += span; + continue; + } + for (s = 0; s < span; s++) { + unsigned int bce, bcau, pg, sl; + u32 key; + int got; + + if (whimory_btoc_ofs_to_page(w, vblock, vba_ofs + s, + &bce, &bcau, &pg, &sl)) + break; + key = whimory_btoc_page_key(bce, bcau, pg); + if (key == last_pg) + continue; + last_pg = key; + got = whimory_btoc_confirm_page(w, bce, bcau, vblock, + pg, 0xffffffffu, false); + if (got < 0) + return hit > 0; + if (got > 0) + hit += got; + } + vba_ofs += span; + } + return hit > 0; +} + +/* + * Decode a BTOC record stream into a per-VBA LBA table. Records are 16 bytes; + * `be` picks the big-endian form (LBA at +8 BE, span in the last byte) over + * the little-endian struct whimory_bte. Returns how many VBAs were described. + * Special/token LBAs and holes land as WHIMORY_LBA_BLANK. + */ +static unsigned int whimory_btoc_decode_map(const u8 *page, unsigned int len, + bool be, u32 *map) +{ + unsigned int i, recs = len / 16, vba_ofs = 0; + + for (i = 0; i < recs && vba_ofs < WHIMORY_DATA_VBAS_PER_SB; i++) { + const u8 *r = page + i * 16; + u32 lba = be ? get_unaligned_be32(r + 8) : + get_unaligned_le32(r + 8); + u32 span = be ? r[15] : get_unaligned_le32(r + 12); + unsigned int s; + + if (!span || span > WHIMORY_DATA_VBAS_PER_SB) + break; + if (vba_ofs + span > WHIMORY_DATA_VBAS_PER_SB) + break; + for (s = 0; s < span; s++) + map[vba_ofs + s] = whimory_special_lba(lba) ? + WHIMORY_LBA_BLANK : lba + s; + vba_ofs += span; + } + return vba_ofs; +} + +/* + * Sample a few pages of a closed superblock and report how often the BTOC + * prediction matches the per-slot metadata. High agreement means the replay + * can apply the BTOC directly and drop from 127 reads per SB to one. + */ +static void whimory_btoc_verify(struct whimory *w, struct whimory_sb *sb, + unsigned int vblock, const u8 *btoc, + unsigned int len) +{ + static const bool forms[2] = { false, true }; + u8 meta[S5L8740_NAND_META_SIZE]; + unsigned int f; + + if (!w->sftl.btoc_map || !w->sftl.data_page) + return; + + for (f = 0; f < ARRAY_SIZE(forms); f++) { + u32 *map = w->sftl.btoc_map; + unsigned int vbas, pages, step, pg, n = 0; + unsigned int agree = 0, disagree = 0, nodata = 0; + u32 first_hint = 0, first_meta = 0; + + memset(map, 0xff, WHIMORY_DATA_VBAS_PER_SB * sizeof(*map)); + vbas = whimory_btoc_decode_map(btoc, len, forms[f], map); + if (vbas < w->sftl.vbas_per_page) + continue; + pages = vbas / w->sftl.vbas_per_page; + step = pages / (btoc_verify_pages ? btoc_verify_pages : 1); + if (!step) + step = 1; + + for (pg = 0; pg < pages && n < btoc_verify_pages; pg += step) { + unsigned int slot; + + if (whimory_cs_read_page(w, sb->ce, sb->cau, sb->block, + pg, w->sftl.data_page, + S5L8740_NAND_PAGE_SIZE, + meta, sizeof(meta))) + break; + n++; + for (slot = 0; slot < WHIMORY_VBAS_PER_PAGE; slot++) { + const u8 *m = meta + slot * WHIMORY_META_SIZE; + u32 hint = map[pg * w->sftl.vbas_per_page + slot]; + u32 mlba; + + if (!whimory_meta_is_data_raw(m) || + whimory_meta_erased(m, WHIMORY_META_SIZE)) { + nodata++; + continue; + } + mlba = get_unaligned_le32(m + 8); + if (hint == mlba) { + agree++; + } else { + if (!disagree) { + first_hint = hint; + first_meta = mlba; + } + disagree++; + } + } + } + dev_info(w->dev, + "BTOC_VERIFY ce=%u cau=%u vblk=%u form=%s vbas=%u " + "pages_probed=%u agree=%u disagree=%u nodata=%u " + "first_hint=%u first_meta=%u\n", + sb->ce, sb->cau, vblock, forms[f] ? "BE" : "LE", + vbas, n, agree, disagree, nodata, + first_hint, first_meta); + } +} + +static int whimory_ingest_btoc_page(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int vblock, + const u8 *page, unsigned int len) +{ + const char *verdict = "NONE"; + int hit = 0; + + if (whimory_page_blank(page, 64)) { + w->sftl.btoc_blank++; + return 0; + } + if (whimory_btoc_looks_be_bte(page)) { + if (whimory_btoc_parse_be_bte(w, page, len, ce, cau, vblock)) { + verdict = "BE_BTE"; + w->sftl.btoc_be_bte++; + hit = 1; + } + } + if (!hit && whimory_btoc_looks_be_lpn(page)) { + if (whimory_btoc_parse_be_lpn(w, page, len, ce, cau, vblock)) { + verdict = "BE_LPN_ARRAY"; + w->sftl.btoc_be_lpn++; + hit = 1; + } + } + if (!hit && whimory_btoc_parse_bte(w, page, len, ce, cau, vblock)) { + verdict = "LE_BTE"; + w->sftl.btoc_le_bte++; + hit = 1; + } + + /* + * Some BTOC pages carry an eight-byte header before the BTE array, + * and 255 of the 563 closed superblocks on this device use it. Every + * one of them was dropped -- 127 pages and about 2032 LBAs each -- + * which is what left directory sectors unreadable on a volume whose + * FAT mounted fine. + * + * struct whimory_bte is {weave_seq_add, aux, lba, span}, so the + * parser was reading the header's third and fourth words as lba and + * span. The dumps make the misread obvious: + * + * 00000000 00000002 0000c30d 000004f2 | 000004f2 00000002 ... + * ^header........^ ^read as lba/span^ | ^the real first record^ + * + * span came out as 1266 against a 508-VBA superblock, so the loop + * broke on record zero and the page was declared unrecognised. Shift + * eight bytes and the same parser reads lba=0x4f2 span=2, then 0x4f3, + * then 0x4f4 -- ascending LBAs with uniform spans, exactly what a + * BTOC is. + * + * Tried only after the unshifted parse yields nothing, and only when + * the first word is zero as it is in every sample, so a page that + * already parses cannot be re-read at the wrong offset. + */ + if (!hit && len > 8 && get_unaligned_le32(page) == 0 && + whimory_btoc_parse_bte(w, page + 8, len - 8, ce, cau, vblock)) { + verdict = "LE_BTE_HDR8"; + w->sftl.btoc_le_bte_hdr8++; + hit = 1; + } + + /* + * A BTOC nobody claimed is a whole closed superblock missing from the + * map -- 127 pages, about 2032 LBAs -- and 255 of 563 were going + * unclaimed with no record of it beyond a count that did not + * distinguish "erased" from "unrecognised". + * + * The read misses that follow look like this, and they are what stops + * files opening on a volume whose FAT reads fine: + * + * read miss fmss_lba=3723686 ret=-2 + * neighbor 3723682..85 MAPPED blk=1714 pg=43 slot=0..3 + * neighbor 3723686 UNMAPPED + * FAT-fs: Directory bread(block 3674407) failed + * + * So the unclaimed ones are dumped. Three parsers is three guesses at + * a format, and the bytes say whether there is a fourth shape here or + * whether one of the three is rejecting pages it should accept. + */ + if (!hit) { + w->sftl.btoc_unclaimed++; + if (w->sftl.btoc_unclaimed <= 8) + dev_info(w->dev, + "BTOC_UNCLAIMED n=%u ce=%u cau=%u vblock=%u first64=%32ph %32ph\n", + w->sftl.btoc_unclaimed, ce, cau, vblock, + page, page + 32); + } + if (ftl_diag && w->sftl.btoc_pages_read <= 8) + dev_info(w->dev, + "BTOC_VERDICT ce=%u cau=%u vblock=%u %s first32=%32ph\n", + ce, cau, vblock, verdict, page); + return hit; +} + +static int whimory_rebuild_open_sb(struct whimory *w, struct whimory_sb *sb) +{ + unsigned int pg, slot, vblock; + u8 *data = w->sftl.data_page; + u8 spare[S5L8740_NAND_META_SIZE]; + int ret, hits = 0; + + vblock = whimory_vfl_virt(w, sb->cau, sb->block); + w->sftl.claim_source = 2; + for (pg = 0; pg < WHIMORY_DATA_PAGES_PER_SB; pg++) { + ret = whimory_cs_read_page(w, sb->ce, sb->cau, sb->block, pg, + data, S5L8740_NAND_PAGE_SIZE, + spare, sizeof(spare)); + w->sftl.open_pages_read++; + if (ret) + break; + if (whimory_page_blank(data, 64) && + whimory_page_blank(spare, 16)) + break; + for (slot = 0; slot < WHIMORY_VBAS_PER_PAGE; slot++) { + const u8 *m = spare + slot * WHIMORY_META_SIZE; + u32 lba, vba; + struct whimory_range *prev; + u64 weave; + + w->sftl.open_slots_seen++; + if (m[0] != WHIMORY_META_TYPE_DATA && + m[0] != WHIMORY_META_TYPE_DATA2) + continue; + if (m[1] & 0x02) + continue; + if (whimory_meta_erased(m, WHIMORY_META_SIZE)) + continue; + lba = get_unaligned_le32(m + 8); + w->sftl.open_slots_valid_meta++; + if (whimory_special_lba(lba)) { + w->sftl.open_unmap_entries++; + continue; + } + if (lba >= 0x01000000u) { + w->sftl.btoc_unknown_entries++; + continue; + } + if (lba == 0) { + w->sftl.open_skipped_zero++; + continue; + } + vba = whimory_pack_vba(w, sb->ce, sb->cau, vblock, pg, + slot); + weave = whimory_weave48(m); + prev = whimory_range_find(&w->ranges, lba); + if (prev) { + if (weave > prev->weave) + w->sftl.open_overrides_closed++; + else if (weave < prev->weave) + w->sftl.open_rejected_stale++; + else + w->sftl.open_unknown_order++; + } + w->sftl.claim_weave = weave; + if ((l2v_trace_lba && lba == l2v_trace_lba) || + (audit_lba_winners && whimory_audit_fmss_lba(lba))) { + dev_info(w->dev, + "LBA_WINNER fmss_lba=%u candidate vba=%u " + "ce=%u cau=%u vblk=%u pg=%u slot=%u " + "weave=%012llx prev_vba=%u prev_weave=%012llx " + "source=open\n", + lba, vba, sb->ce, sb->cau, vblock, pg, + slot, (unsigned long long)weave, + prev ? prev->vba : ~0u, + prev ? (unsigned long long)prev->weave : + 0ull); + } + /* + * Propagate the real error. Flattening everything to + * -ENOMEM here told the caller to abort the entire + * recovery -- it treats any negative return that way -- + * on the strength of a guess about what went wrong. + */ + ret = whimory_l2v_update(w, lba, 1, vba); + if (ret) { + dev_err(w->dev, + "open rebuild: L2V update failed lba=%u vba=%u: %d\n", + lba, vba, ret); + w->sftl.claim_weave = 0; + w->sftl.claim_source = 0; + return ret; + } + w->sftl.claim_weave = 0; + w->sftl.open_l2v_updates++; + hits++; + } + if ((pg & 0x0f) == 0) { + cond_resched(); + if (recover_yield_us) + usleep_range(recover_yield_us, + recover_yield_us + 500); + } + } + w->sftl.claim_source = 0; + return hits; +} + +static int whimory_sb_cmp(const void *a, const void *b) +{ + const struct whimory_sb *sa = a, *sb = b; + + if (sa->weave < sb->weave) + return -1; + if (sa->weave > sb->weave) + return 1; + if (sa->ce != sb->ce) + return sa->ce < sb->ce ? -1 : 1; + if (sa->cau != sb->cau) + return sa->cau < sb->cau ? -1 : 1; + if (sa->block != sb->block) + return sa->block < sb->block ? -1 : 1; + return 0; +} + +/* + * Compact (ce, cau, block) list of the CXT superblocks found by classify. + * whimory_vba_is_cxt() is called once per CXT L2V record; scanning all + * num_sb entries there was O(num_sb) per record (7840 SBs on N31). + */ +static void whimory_cxt_index_build(struct whimory *w, unsigned int nsb) +{ + struct whimory_sftl *s = &w->sftl; + unsigned int i; + + s->n_cxt_idx = 0; + if (!s->sbs) + return; + for (i = 0; i < nsb && s->n_cxt_idx < ARRAY_SIZE(s->cxt_idx); i++) { + struct whimory_sb *sb = &s->sbs[i]; + + if (sb->kind != WHIMORY_SB_CXT) + continue; + s->cxt_idx[s->n_cxt_idx].ce = sb->ce; + s->cxt_idx[s->n_cxt_idx].cau = sb->cau; + s->cxt_idx[s->n_cxt_idx].block = sb->block; + s->n_cxt_idx++; + } +} + +/* + * VBAs belonging to a CXT superblock hold context records, not user data, + * so they must never enter the L2V map. + */ +static bool whimory_vba_is_cxt(struct whimory *w, u32 vba) +{ + u32 ce, cau, vblock, page, slot, phys; + unsigned int i; + + if (whimory_unpack_vba(w, vba, &ce, &cau, &vblock, &page, &slot)) + return false; + whimory_vfl_resolve(w, vblock, &cau, &phys); + for (i = 0; i < w->sftl.n_cxt_idx; i++) { + struct whimory_cxt_sb_id *c = &w->sftl.cxt_idx[i]; + + if (c->ce == ce && c->cau == cau && c->block == phys) + return true; + } + return false; +} + +static int whimory_cxt_add_base(struct whimory *w, u32 sb, u64 weave) +{ + int i; + + if (w->n_cxt >= WHIMORY_CXT_MAX_SB) + return -ENOSPC; + for (i = w->n_cxt; i > 0; i--) { + if (w->cxt[i - 1].weave >= weave) + break; + w->cxt[i] = w->cxt[i - 1]; + } + w->cxt[i].sb = sb; + w->cxt[i].weave = weave; + w->n_cxt++; + w->sftl.cxt_bases = w->n_cxt; + return 0; +} + +static int whimory_cxt_load_contig(struct whimory *w, const u8 *data, + unsigned int len) +{ + u32 lba, span, vba, i, n; + + if (len < 16) + return 0; + n = len / 8; + lba = get_unaligned_le32(data); + span = get_unaligned_le32(data + 4); + if (span == 0xffffffff) + return 0; + if (span != WHIMORY_CXT_CONTIG_SPAN) + return -EINVAL; + if (w->cxt_lba_valid && lba != w->cxt_next_lba) { + dev_err(w->dev, + "cxt lba not consecutive want=%u got=%u\n", + w->cxt_next_lba, lba); + return -EINVAL; + } + w->cxt_lba_valid = true; + for (i = 1; i < n; i++) { + vba = get_unaligned_le32(data + 8 * i); + span = get_unaligned_le32(data + 8 * i + 4); + if (vba == 0xffffffff || !span) + break; + w->sftl.cxt_records_seen++; + /* + * A record whose vba is at or above invalid_vba is a hole, + * and it has to be applied, not skipped. + * + * s_cxt_load.c inserts on + * + * if (invalid_vba <= vba || !sbFilter_test(sb)) + * + * so a hole is always inserted and everything else is + * inserted unless its superblock is in the checkpoint diff + * filter, which is empty at mount. whimory_l2v_update() + * already reads vba >= invalid_vba as an unmap, so applying + * the record is all that is needed. + * + * Skipping holes leaves whatever previously covered those + * LBAs in the tree. The read then lands on a page belonging + * to a different LBA and the meta check rejects it, which is + * the "sftl lba mismatch want=X meta=Y" failure. + */ + if (vba >= w->l2v.invalid_vba || + !whimory_vba_is_cxt(w, vba)) { + if (whimory_l2v_update(w, lba, span, vba)) + return -ENOMEM; + w->sftl.cxt_l2v_updates++; + } + lba += span; + } + w->cxt_next_lba = lba; + return 0; +} + +static int whimory_cxt_handle_vba(struct whimory *w, const u8 *data, + const u8 *meta) +{ + u8 tag; + + if (meta[0] != WHIMORY_META_TYPE_SFTL_CXT) + return 0; + tag = meta[1]; + if (tag == WHIMORY_CXT_TAG_END) + return 1; + if (tag != WHIMORY_CXT_TAG_L2V) + return 0; + return whimory_cxt_load_contig(w, data, WHIMORY_LBA_SIZE); +} + +static int whimory_cxt_load_sb(struct whimory *w, u32 sb_idx) +{ + struct whimory_sftl *s = &w->sftl; + u32 ce, cau, vblock, page, slot, ofs, vba, pblock; + u32 last_ce = ~0u, last_cau = ~0u, last_pblock = ~0u, last_page = ~0u; + u32 zone, n, i; + u8 *data, *gmeta; + u8 spare[S5L8740_NAND_META_SIZE]; + int ret, done = 0; + + if (sb_idx >= s->num_sb) + return -EINVAL; + w->sftl.cxt_blocks_seen++; + zone = s->gc_zone_size; + data = s->gc_data; + gmeta = s->gc_meta; + if (!zone || !data || !gmeta || zone % s->vbas_per_page) + return -ENOMEM; + + w->cxt_lba_valid = false; + w->cxt_next_lba = 0; + /* + * Read the superblock in gc_zone_size chunks, as the FTL does. + * + * Unreferenced: this and whimory_cxt_load() are kept as the literal + * transcription of s_cxt_load.c sub_5884D4, which is what + * whimory_cxt_parse_tree() was checked against. The live path is + * whimory_cxt_build_from_sb(). Note the bound below is the per-bank + * count and the walk is bank-major -- both retired -- so this is + * documentation, not something to call. + */ + for (ofs = 0; ofs < s->vbas_per_sb && !done; ofs += zone) { + n = min(zone, s->vbas_per_sb - ofs); + for (i = 0; i < n; i++) { + vba = whimory_sb_ofs_to_vba(w, sb_idx, ofs + i); + ret = whimory_unpack_vba(w, vba, &ce, &cau, &vblock, + &page, &slot); + if (ret) + return ret; + whimory_vfl_resolve(w, vblock, &cau, &pblock); + if (ce != last_ce || cau != last_cau || + pblock != last_pblock || page != last_page) { + ret = whimory_cs_read_page(w, ce, cau, pblock, + page, s->data_page, + S5L8740_NAND_PAGE_SIZE, + spare, + sizeof(spare)); + if (ret) + return ret; + last_ce = ce; + last_cau = cau; + last_pblock = pblock; + last_page = page; + } + memcpy(data + i * WHIMORY_LBA_SIZE, + s->data_page + slot * WHIMORY_LBA_SIZE, + WHIMORY_LBA_SIZE); + if (sizeof(spare) >= (slot + 1) * WHIMORY_META_SIZE) + memcpy(gmeta + i * WHIMORY_META_SIZE, + spare + slot * WHIMORY_META_SIZE, + WHIMORY_META_SIZE); + else + memset(gmeta + i * WHIMORY_META_SIZE, 0xff, + WHIMORY_META_SIZE); + } + for (i = 0; i < n; i++) { + ret = whimory_cxt_handle_vba(w, + data + i * WHIMORY_LBA_SIZE, + gmeta + i * WHIMORY_META_SIZE); + if (ret < 0) + return ret; + if (ret > 0) { + done = 1; + break; + } + } + } + return 0; +} + +static int __maybe_unused whimory_cxt_load(struct whimory *w) +{ + unsigned int i; + int ret, loaded = 0; + + for (i = 0; i < w->n_cxt; i++) { + u32 sb = w->cxt[i].sb; + + dev_info(w->dev, "s_cxt_load base sb=%u weave=%llu\n", + sb, w->cxt[i].weave); + w->sftl.claim_weave = w->cxt[i].weave; + ret = whimory_cxt_load_sb(w, sb); + w->sftl.claim_weave = 0; + if (ret) { + dev_warn(w->dev, "cxt sb=%u failed %d\n", sb, ret); + continue; + } + w->cxt_base_weave = w->cxt[i].weave; + loaded = 1; + break; + } + w->sftl.cxt_loaded = loaded; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* CXT scanner / dumper (read-only; never touches the map) */ +/* ------------------------------------------------------------------ */ + + +/* + * Every superblock classify tagged as CXT, newest weave first. + * + * whimory_cxt_add_base() only registers a superblock whose page 0 slot 0 + * carries tag BASE, which on this device is one of the four CXT blocks. The + * BASE payload itself names the others ({count, sb, sb, ...}), and the newest + * generation need not be the one holding the BASE marker, so the candidate + * search has to consider all of them. + */ +static unsigned int whimory_cxt_collect_sbs(struct whimory *w, + struct whimory_cxt_base *out, + unsigned int max) +{ + struct whimory_sftl *s = &w->sftl; + unsigned int i, n = 0; + + if (!s->sbs) + return 0; + for (i = 0; i < s->num_sb && n < max; i++) { + struct whimory_sb *sb = &s->sbs[i]; + u32 vblock, idx, j; + + if (sb->kind != WHIMORY_SB_CXT) + continue; + vblock = whimory_vfl_virt(w, sb->cau, sb->block); + idx = whimory_sb_index(w, sb->ce, sb->cau, vblock); + /* + * One candidate per virtual block, not per plane. A CXT is a + * single superblock striped across all four (ce, cau) planes; + * counting each plane separately turned one checkpoint into + * four candidates with four weaves, of which the two highest + * appeared to contribute nothing. + */ + for (j = 0; j < n; j++) + if (whimory_cxt_sb_vblock(w, out[j].sb) == vblock) + break; + if (j < n) { + if (sb->weave > out[j].weave) + out[j].weave = sb->weave; + continue; + } + out[n].sb = idx; + out[n].weave = sb->weave; + n++; + } + /* Insertion sort, newest weave first; n is at most WHIMORY_CXT_MAX_SB. */ + for (i = 1; i < n; i++) { + struct whimory_cxt_base tmp = out[i]; + int j = (int)i - 1; + + while (j >= 0 && out[j].weave < tmp.weave) { + out[j + 1] = out[j]; + j--; + } + out[j + 1] = tmp; + } + return n; +} + +static const char *whimory_cxt_tag_name(u8 tag) +{ + switch (tag) { + case WHIMORY_CXT_TAG_BASE: + return "BASE"; + case WHIMORY_CXT_TAG_STATS: + return "STATS"; + case WHIMORY_CXT_TAG_SB: + return "SB"; + case WHIMORY_CXT_TAG_L2V: + return "TREE"; + case WHIMORY_CXT_TAG_USERSEQ: + return "USERSEQ"; + case WHIMORY_CXT_TAG_READS: + return "READS"; + case WHIMORY_CXT_TAG_CLEAN: + return "CLEAN/END"; + default: + return "?"; + } +} + +/* + * Read one VBA of a CXT superblock. Returns the 4 KiB payload in `data` and + * the 16-byte record metadata in `meta`. Page reads are cached across the + * four slots of a physical page by the caller. + */ +/* + * Read one record of a checkpoint, by offset within the whole superblock. + * + * The offset is a native VBA offset, which means it walks all four planes + * in the order the FTL wrote them. That matters more than it looks. + * + * A CXT is one superblock striped across the four (ce, cau) planes, and its + * records run in VBA order: plane 0 slots 0..3 of page 0, then plane 1, + * plane 2, plane 3, then page 1 of plane 0. The weave in each record's meta + * says so directly -- 580d..5810 on plane 0, 5811..5814 on plane 1, then + * 5815, 5819, and 581d back on plane 0. + * + * This used to take a bank-major superblock index and walk one plane at a + * time, which visited the same records in the wrong order: every record of + * plane 0, then every record of plane 1, and so on. The L2V tree cannot + * survive that. Each record declares the LBA it continues from and the + * parse rejects a record whose declared start does not match the running + * cursor, so out-of-order records either abort the walk or, worse, attach a + * run to the wrong logical position. + * + * It also made one checkpoint look like four candidates with four different + * weaves, of which the two "newest" contributed nothing -- which read as a + * stale map when it was a misread one. + */ +static int whimory_cxt_read_ofs(struct whimory *w, u32 vblock, u32 ofs, + u8 *data, u8 *meta, u8 *spare, u32 *last_key) +{ + struct whimory_sftl *s = &w->sftl; + u32 ce, cau, vb, page, slot, pblock, key; + /* + * The offset is against the virtual block's full address stride -- + * every virtual block owns the same window whatever its bank count. + * How far into that window a checkpoint runs is the caller's bound; + * see whimory_cxt_build_from_sb(). + */ + u32 vba = vblock * whimory_vbas_per_vblock(w) + ofs; + int ret; + + ret = whimory_unpack_vba(w, vba, &ce, &cau, &vb, &page, &slot); + if (ret) + return ret; + whimory_vfl_resolve(w, vb, &cau, &pblock); + key = ((ce & 0xf) << 28) | ((cau & 0xf) << 24) | + ((pblock & 0xffff) << 8) | (page & 0xff); + if (key != *last_key) { + ret = whimory_cs_read_page(w, ce, cau, pblock, page, + s->data_page, + S5L8740_NAND_PAGE_SIZE, + spare, S5L8740_NAND_META_SIZE); + if (ret) + return ret; + *last_key = key; + } + memcpy(data, s->data_page + slot * WHIMORY_LBA_SIZE, WHIMORY_LBA_SIZE); + memcpy(meta, spare + slot * WHIMORY_META_SIZE, WHIMORY_META_SIZE); + return 0; +} + +/* + * whimory_cxt_read_vba() lived here: the same read keyed by a bank-major + * superblock index and a per-bank offset. Its last caller was the dump, and + * the dump was the one place still walking a checkpoint in an order nothing + * else uses. Deleted rather than left for someone to reach for, because a + * second walker that disagrees with the loader is how the bank-count bug + * stayed hidden for as long as it did. + */ + +/* + * Walk one CXT superblock and report what is actually stored in it: the tag + * of every record, the shape of the BASE header (which carries the CXT + * superblock list), and the header of each TREE record. + */ +static void whimory_cxt_dump_sb(struct whimory *w, u32 sb_idx, u64 weave, + unsigned int max_vbas) +{ + struct whimory_sftl *s = &w->sftl; + u32 counts[8] = {0}, clean = 0, other = 0, end_at = ~0u; + u32 ofs, last_key = ~0u, trees = 0, base_seen = 0; + u8 *data = s->gc_data; + u8 meta[WHIMORY_META_SIZE]; + u8 spare[S5L8740_NAND_META_SIZE]; + /* + * Walk this the way the loader does. + * + * It used to call whimory_cxt_read_vba(), the bank-major walker, + * while whimory_cxt_build_from_sb() uses whimory_cxt_read_ofs() -- + * so ftl_cxt_dump showed the records of one checkpoint in an order + * no code path ever reads them in. It also clamped to vbas_per_sb, + * the per-bank count, and so covered a quarter of the block. A + * diagnostic that disagrees with the thing it is diagnosing is worse + * than none, and this one was consulted while chasing exactly the + * bug the disagreement was hiding. + */ + u32 vblock = whimory_cxt_sb_vblock(w, sb_idx); + u32 nbanks = whimory_sb_banks(w, vblock, NULL); + u32 sb_vbas = nbanks * s->pages_per_sb * s->vbas_per_page; + + if (!data || !s->data_page) { + dev_err(w->dev, "CXT_DUMP sb=%u no scratch\n", sb_idx); + return; + } + if (!max_vbas || max_vbas > sb_vbas) + max_vbas = sb_vbas; + + dev_info(w->dev, + "CXT_DUMP_BEGIN sb=%u vblk=%u weave=%llu vbas=%u nbanks=%u\n", + sb_idx, vblock, (unsigned long long)weave, max_vbas, nbanks); + + for (ofs = 0; ofs < max_vbas; ofs++) { + u8 type, tag; + + if (whimory_cxt_read_ofs(w, vblock, ofs, data, meta, spare, + &last_key)) + break; + type = meta[0]; + tag = meta[1]; + if (whimory_meta_erased(meta, WHIMORY_META_SIZE)) { + clean++; + continue; + } + if (type != WHIMORY_META_TYPE_SFTL_CXT) { + other++; + continue; + } + if (tag == WHIMORY_CXT_TAG_CLEAN) { + clean++; + if (end_at == ~0u) + end_at = ofs; + break; + } + if (tag < ARRAY_SIZE(counts)) + counts[tag]++; + else + other++; + + /* One compact row per distinct tag, so the record layout of + * a superblock is visible without a full hex dump. + */ + if (tag < ARRAY_SIZE(counts) && counts[tag] == 1) + dev_info(w->dev, + "CXT_REC sb=%u ofs=%u pg=%u bank=%u slot=%u type=%02x tag=%02x %s\n", + sb_idx, ofs, + (ofs / s->vbas_per_page) / nbanks, + (ofs / s->vbas_per_page) % nbanks, + ofs % s->vbas_per_page, type, tag, + whimory_cxt_tag_name(tag)); + + if (tag == WHIMORY_CXT_TAG_BASE && base_seen++ < 2) { + dev_info(w->dev, + "CXT_BASE_REC sb=%u ofs=%u meta=%16ph\n", + sb_idx, ofs, meta); + dev_info(w->dev, + "CXT_BASE_REC sb=%u ofs=%u first64=%32ph %32ph\n", + sb_idx, ofs, data, data + 32); + } + if (tag == WHIMORY_CXT_TAG_L2V && trees < 1) { + unsigned int b; + + for (b = 0; b < 128; b += 32) + dev_info(w->dev, + "CXT_TREE_HEX sb=%u ofs=%u +%03u %32ph\n", + sb_idx, ofs, b, data + b); + } + if (tag == WHIMORY_CXT_TAG_L2V && trees++ < 3) { + dev_info(w->dev, + "CXT_TREE_REC sb=%u ofs=%u hdr_lba=%u " + "hdr_span=0x%08x p0=(%u,%u) p1=(%u,%u) " + "p2=(%u,%u)\n", + sb_idx, ofs, + get_unaligned_le32(data), + get_unaligned_le32(data + 4), + get_unaligned_le32(data + 8), + get_unaligned_le32(data + 12), + get_unaligned_le32(data + 16), + get_unaligned_le32(data + 20), + get_unaligned_le32(data + 24), + get_unaligned_le32(data + 28)); + } + } + + dev_info(w->dev, + "CXT_DUMP_END sb=%u scanned=%u clean=%u other=%u end_at=%d " + "base=%u stats=%u sbrec=%u tree=%u userseq=%u reads=%u\n", + sb_idx, ofs, clean, other, (int)end_at, + counts[WHIMORY_CXT_TAG_BASE], counts[WHIMORY_CXT_TAG_STATS], + counts[WHIMORY_CXT_TAG_SB], counts[WHIMORY_CXT_TAG_L2V], + counts[WHIMORY_CXT_TAG_USERSEQ], + counts[WHIMORY_CXT_TAG_READS]); +} + +/* + * Phase 2 entry point: report every CXT base candidate newest-weave first, + * then dump what each one contains. Read-only — the L2V map is untouched. + */ +int whimory_cxt_dump(unsigned int max_vbas) +{ + struct whimory *w = whimory_dev; + unsigned int i; + struct whimory_cxt_base all[WHIMORY_CXT_MAX_SB]; + unsigned int n_all; + int sess; + + if (!w) + return -ENODEV; + if (!w->sftl.sbs || !w->sftl.num_sb) + return -ENODATA; + + dev_info(w->dev, + "CXT_SCAN bases=%u cxt_sbs=%u classified_sbs=%u " + "cxt_loaded=%d base_weave=%llu\n", + w->n_cxt, w->sftl.cxt_sbs, w->sftl.num_sb, w->sftl.cxt_loaded, + (unsigned long long)w->cxt_base_weave); + + for (i = 0; i < w->n_cxt; i++) + dev_info(w->dev, "CXT_CAND i=%u sb=%u weave=%llu\n", + i, w->cxt[i].sb, + (unsigned long long)w->cxt[i].weave); + + n_all = whimory_cxt_collect_sbs(w, all, ARRAY_SIZE(all)); + for (i = 0; i < n_all; i++) + dev_info(w->dev, "CXT_SB i=%u sb=%u weave=%llu\n", + i, all[i].sb, (unsigned long long)all[i].weave); + + if (!w->n_cxt) { + dev_warn(w->dev, + "CXT_SCAN no base candidates; classify must reach the " + "high blocks that hold them (scan_blocks=0/1960)\n"); + return -ENOENT; + } + + sess = s5l8740_nand_dma_session_begin(); + for (i = 0; i < n_all; i++) + whimory_cxt_dump_sb(w, all[i].sb, all[i].weave, max_vbas); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return 0; +} +EXPORT_SYMBOL_GPL(whimory_cxt_dump); + +/* ------------------------------------------------------------------ */ +/* Phase 3: CXT TREE -> candidate map, compared against the live map */ +/* ------------------------------------------------------------------ */ + +static unsigned int cxt_max_extents = 1048576; +module_param(cxt_max_extents, uint, 0644); +MODULE_PARM_DESC(cxt_max_extents, + "Candidate-map extent ceiling for the CXT TREE parser"); + +/* + * CXT VBAs are in the FTL native superblock space, which is not the space + * whimory_pack_vba() builds. Apple counts one superblock as the same virtual + * block across every (ce, cau) plane, so a superblock holds + * pages_per_sb * planes * vbas_per_page VBAs and the plane index sits + * between the page and the slot: + * + * vba = vblock * (pages_per_sb * planes * 4) + * + page * (planes * 4) + plane * 4 + slot + * + * whimory_pack_vba() instead gives every (ce, cau, vblock) triple its own + * superblock index, so a raw CXT VBA lands on an unrelated page here. Two + * consequences: translate before use, and a run of consecutive CXT VBAs is + * only contiguous in our space within one 4-slot group, because the next + * group belongs to a different plane. + */ +/* + * Now that the VBA space is the native one, a CXT VBA is already a VBA. + * All that is left is the range check the old translation did on the way + * through -- kept because an out-of-range CXT entry is real and must not + * be turned into a mapping onto some unrelated block. + */ +static int whimory_cxt_vba_translate(struct whimory *w, u32 cxt_vba, u32 *out) +{ + u32 planes = w->geom.num_ce * w->geom.num_cau; + u32 per_sb; + + if (!planes || !w->sftl.vbas_per_page || !w->sftl.pages_per_sb) + return -EINVAL; + per_sb = w->sftl.pages_per_sb * planes * w->sftl.vbas_per_page; + if (cxt_vba / per_sb >= whimory_vba_blocks(w)) + return -ERANGE; + *out = cxt_vba; + return 0; +} + +static void whimory_cxt_ext_reset(struct whimory *w) +{ + w->n_cxt_ext = 0; + w->cxt_ext_weave = 0; + w->cxt_ext_sb = 0; + w->cxt_ext_max_span = 0; +} + +static int whimory_cxt_ext_add(struct whimory *w, u32 lba, u32 span, u32 vba, + u64 weave) +{ + struct whimory_cxt_extent *e; + + if (w->n_cxt_ext >= w->max_cxt_ext) + return -ENOSPC; + e = &w->cxt_ext[w->n_cxt_ext++]; + e->lba = lba; + e->span = span; + e->vba = vba; + e->weave = weave; + if (span > w->cxt_ext_max_span) + w->cxt_ext_max_span = span; + return 0; +} + +/* + * Spell a raw CXT VBA out in the terms it is built from. + * + * Every question about the CXT map so far -- are the holes real, is the + * translation ceiling right -- has been answered by arguing about + * arithmetic. A VBA printed as vblock/page/plane/slot settles them by + * inspection: a genuine hole sentinel is far outside the geometry, while an + * arithmetic fault lands just past a boundary. + */ +static void whimory_vba_describe(const struct whimory *w, u32 vba, + char *buf, size_t len) +{ + u32 per_vb = whimory_vbas_per_vblock(w); + u32 vblock, rem, unit, nbanks, bank; + u8 mask; + + if (!per_vb || !w->sftl.vbas_per_page || !w->sftl.pages_per_sb) { + scnprintf(buf, len, "?"); + return; + } + vblock = vba / per_vb; + rem = vba % per_vb; + unit = rem / w->sftl.vbas_per_page; + /* + * nbanks is printed too. Reading a description without it is what + * made this bug so hard to see: two superblocks with the same + * vblk/pg/slot are different physical pages when their bank counts + * differ, and nothing in the old line said so. + */ + nbanks = whimory_sb_banks(w, vblock, &mask); + bank = whimory_bank_at(mask, unit % nbanks); + scnprintf(buf, len, "vblk=%u pg=%u bank=%u slot=%u nbanks=%u", + vblock, unit / nbanks, bank, + rem % w->sftl.vbas_per_page, nbanks); +} + +/* + * A TREE record is {start_lba, CONTIG_SPAN} followed by (vba, span) pairs, + * each pair advancing the logical cursor by span. Same shape as + * whimory_cxt_load_contig(), but it appends to the candidate map instead of + * touching L2V. + */ +static int whimory_cxt_parse_tree(struct whimory *w, const u8 *data, + unsigned int len, u32 *next_lba, + bool *lba_valid, u64 sb_weave) +{ + u32 lba, span, vba, i, n = len / 8; + + if (len < 16) + return 0; + lba = get_unaligned_le32(data); + span = get_unaligned_le32(data + 4); + if (span == 0xffffffffu) { + /* + * Header says nothing follows, and until now that was + * indistinguishable from a record that parsed and contained + * nothing. They are not the same: the two newest checkpoints + * on this device contribute no extents while the map is built + * from the two older ones, leaving it a generation behind the + * volume -- which is what the bad reads are. If those + * checkpoints are being turned away here, this says so and + * shows the header that did it. + */ + w->sftl.cxt_hdr_skipped++; + if (w->sftl.cxt_hdr_skipped <= 6) + dev_info(w->dev, + "CXT_HDR_SKIP n=%u lba=%u span=0x%08x first16=%16ph\n", + w->sftl.cxt_hdr_skipped, lba, span, data); + return 0; + } + if (span != WHIMORY_CXT_CONTIG_SPAN) { + w->sftl.cxt_hdr_bad++; + if (w->sftl.cxt_hdr_bad <= 6) + dev_info(w->dev, + "CXT_HDR_BAD n=%u lba=%u span=0x%08x want=0x%08x first16=%16ph\n", + w->sftl.cxt_hdr_bad, lba, span, + (u32)WHIMORY_CXT_CONTIG_SPAN, data); + return -EINVAL; + } + /* + * A discontinuity used to abort the whole checkpoint, and with it + * every record after this one -- on a superblock whose records run + * into the thousands, and whose tail is where the newest mappings + * live. The reasoning was that the logical cursor places every + * following record, so a broken cursor puts mappings at wrong LBAs. + * + * That is true of the cursor and not of the records. Each record + * carries its own start LBA in its header, which is what the check + * compares against; when the two disagree, believing the header + * re-anchors the cursor and the records after it land correctly. + * Losing the cursor is not the same as losing the records. + * + * Counted, because a checkpoint that resynchronises repeatedly is + * saying something about the decode that a silent recovery would + * hide. + */ + if (*lba_valid && lba != *next_lba) { + w->sftl.cxt_resyncs++; + if (w->sftl.cxt_resyncs <= 8) + dev_warn(w->dev, + "CXT_TREE lba discontinuity want=%u got=%u -- resyncing on the record header\n", + *next_lba, lba); + } + *lba_valid = true; + + if (cxt_dump_lba && lba < cxt_dump_lba + cxt_dump_len) + dev_info(w->dev, + "CXT_REC header lba=%u contig=0x%x pairs<=%u\n", + lba, span, n - 1); + + for (i = 1; i < n; i++) { + vba = get_unaligned_le32(data + 8 * i); + span = get_unaligned_le32(data + 8 * i + 4); + if (vba == 0xffffffffu || !span) + break; + w->sftl.cxt_records_seen++; + + if (cxt_dump_lba && w->sftl.cxt_dumped < cxt_dump_max && + lba + span > cxt_dump_lba && + lba < cxt_dump_lba + cxt_dump_len) { + bool hole = vba >= WHIMORY_CXT_VBA_HOLE || + vba >= w->l2v.invalid_vba; + char d[64]; + + w->sftl.cxt_dumped++; + whimory_vba_describe(w, vba, d, sizeof(d)); + dev_info(w->dev, + "CXT_PAIR[%u] lba=%u..%u vba=0x%08x span=%u %s%s\n", + i, lba, lba + span - 1, vba, span, + hole ? "HOLE" : d, + hole ? "" : ""); + } + if (vba >= WHIMORY_CXT_VBA_HOLE || vba >= w->l2v.invalid_vba) { + /* + * Hole: consumes logical space, maps nothing. + * + * Sampled, because "609 holes" on its own does not say + * whether the checkpoint is describing unmapped space + * or whether the ceiling is wrong. A sentinel sits far + * outside the geometry; a ceiling fault sits just past + * it. The covered LBA count tells them apart too -- a + * volume a quarter full should have most of its + * logical space in holes. + */ + w->sftl.cxt_hole_entries++; + w->sftl.cxt_hole_lbas += span; + /* + * Gated: CXT_MAP already reports holes= and + * hole_lbas=, which is what answers "are the holes + * real". The per-hole lines answer "is the ceiling + * wrong", which is a question you ask once. + */ + if (ftl_diag && w->sftl.cxt_hole_entries <= 12) { + char d[64]; + + whimory_vba_describe(w, vba, d, sizeof(d)); + dev_info(w->dev, + "CXT_HOLE n=%u vba=0x%08x (%s) lba=%u span=%u limit=0x%x\n", + w->sftl.cxt_hole_entries, vba, d, lba, + span, w->l2v.invalid_vba); + } + lba += span; + continue; + } + while (span) { + /* + * Whole runs now. This used to break every extent at + * the next 4-slot boundary because consecutive CXT + * VBAs were not contiguous in the old bank-major + * space. They are in the native one, so a run stays a + * run and the range count drops by about four times. + */ + u32 chunk = span; + u32 tvba; + int ret; + + /* + * A free consistency check on the bank map. + * + * A checkpoint never names an address past the end of + * its superblock, so an offset at or beyond + * nbanks * pages_per_sb * vbas_per_page says our bank + * count for that virtual block is too small -- the + * classify signal missed a member. Counted here + * because it costs nothing and it is the one symptom + * that distinguishes a bad derivation from a bad + * checkpoint. + */ + { + u32 per_vb = whimory_vbas_per_vblock(w); + u32 vblk = per_vb ? vba / per_vb : 0; + u32 nb = whimory_sb_banks(w, vblk, NULL); + u32 end = nb * w->sftl.pages_per_sb * + w->sftl.vbas_per_page; + + if (per_vb && vba % per_vb >= end) { + w->sftl.sb_bank_overflow++; + if (w->sftl.sb_bank_overflow <= 8) + dev_warn(w->dev, + "CXT_BANK_SHORT vba=0x%08x vblk=%u ofs=%u >= %u (nbanks=%u) lba=%u span=%u\n", + vba, vblk, + vba % per_vb, end, nb, + lba, chunk); + } + } + + if (!whimory_cxt_vba_translate(w, vba, &tvba)) { + ret = whimory_cxt_ext_add(w, lba, chunk, tvba, + sb_weave); + if (ret) { + /* Loud: a full table is a short map, + * and a short map is lost files. + */ + w->sftl.cxt_ext_nospc++; + dev_warn(w->dev, + "CXT extent table full at %u -- map will be short\n", + w->n_cxt_ext); + return ret; + } + } else { + w->sftl.cxt_xlate_fail++; + if (w->sftl.cxt_xlate_fail <= 12) { + char d[64]; + + whimory_vba_describe(w, vba, d, + sizeof(d)); + dev_info(w->dev, + "CXT_XLATE_FAIL n=%u vba=0x%08x (%s) lba=%u span=%u vba_blocks=%u\n", + w->sftl.cxt_xlate_fail, vba, d, + lba, chunk, + whimory_vba_blocks(w)); + } + } + lba += chunk; + vba += chunk; + span -= chunk; + } + continue; + } + *next_lba = lba; + return 0; +} + +/* Walk the records of one CXT superblock and collect every TREE extent. */ +static int whimory_cxt_build_from_sb(struct whimory *w, u32 sb_idx, + u64 sb_weave) +{ + struct whimory_sftl *s = &w->sftl; + u8 *data = s->gc_data; + u8 meta[WHIMORY_META_SIZE]; + u8 spare[S5L8740_NAND_META_SIZE]; + u32 ofs, last_key = ~0u, next_lba = 0, n_l2v = 0; + /* + * Eight entries, not 256. + * + * A full byte-indexed histogram is a kilobyte of stack, which put + * this frame at 1368 bytes -- past the warning and a sixth of an + * 8 KiB kernel stack, on a function that recurses into the NAND read + * path. Every tag this walk can meet is 1..6 or CLEAN at 0xff, and + * CLEAN is counted separately because it ends the walk; anything + * else is by definition unrecognised and only needs a count. + */ + u32 tag_hist[8] = { 0 }; + u32 tag_other = 0; + u32 n_cxt_meta = 0, n_clean = 0; + u32 vblock = whimory_cxt_sb_vblock(w, sb_idx); + /* + * How many addresses this checkpoint superblock actually holds. + * + * s_cxt_load.c walks a checkpoint from 0 to the value the VFL gives + * for that superblock -- sub_4EFE0C, banks_in_vbn * pages_per_block * + * vbas_per_page -- not to a fixed per-virtual-block stride. A + * checkpoint on a short superblock has its records packed over the + * banks it has; walking the full 2048 offsets would read them in the + * wrong order, and the tree cannot survive that because each record + * declares the LBA it continues from. + * + * This one happens to be a four-bank superblock on this unit, so the + * two bounds agree today. They agree by luck. + */ + u32 nbanks = whimory_sb_banks(w, vblock, NULL); + u32 per_sb = nbanks * s->pages_per_sb * s->vbas_per_page; + u32 per_vb = whimory_vbas_per_vblock(w); + bool lba_valid = false; + int ret; + + if (!data || !s->data_page || !per_sb) + return -ENOMEM; + + /* + * The whole superblock, every bank, in the order the FTL wrote it -- + * not one bank at a time. See whimory_cxt_read_ofs(). + */ + for (ofs = 0; ofs < per_sb; ofs++) { + ret = whimory_cxt_read_ofs(w, vblock, ofs, data, meta, spare, + &last_key); + if (!ret && cxt_trace_sb && ofs < cxt_trace_sb) { + u32 tv = vblock * per_vb + ofs; + unsigned int tce, tcau, tvb, tpg, tsl; + char d[64] = "?"; + + if (!whimory_unpack_vba(w, tv, &tce, &tcau, &tvb, &tpg, + &tsl)) + scnprintf(d, sizeof(d), + "ce%u/cau%u/vblk%u/pg%u/slot%u", + tce, tcau, tvb, tpg, tsl); + dev_info(w->dev, + "CXT_TRACE sb=%u ofs=%u vba=%u %s meta=%16ph data8=%8ph\n", + sb_idx, ofs, tv, d, meta, data); + if (tsl == 0) + dev_info(w->dev, + "CXT_TRACE page metas s0=%16ph s1=%16ph\n", + spare, spare + WHIMORY_META_SIZE); + } + if (ret) { + dev_warn(w->dev, + "CXT sb=%u read failed at ofs=%u/%u (%d) -- rest of this checkpoint dropped\n", + sb_idx, ofs, per_sb, ret); + return ret; + } + if (meta[0] != WHIMORY_META_TYPE_SFTL_CXT) + continue; + n_cxt_meta++; + if (meta[1] < ARRAY_SIZE(tag_hist)) + tag_hist[meta[1]]++; + else + tag_other++; + if (meta[1] == WHIMORY_CXT_TAG_CLEAN) { + n_clean++; + break; + } + /* + * The BASE payload is {count, sb, sb, ...} -- s_cxt_save.c + * writes buf[0] = cxt->save.num_sb and then the list. That + * count is what the weave fast-forward is scaled by, so it is + * worth taking rather than guessing from how many checkpoint + * superblocks classify happened to find. + */ + if (meta[1] == WHIMORY_CXT_TAG_BASE && !w->cxt_save_num_sb) { + u32 n = get_unaligned_le32(data); + + if (n && n <= WHIMORY_CXT_MAX_SB) + w->cxt_save_num_sb = n; + } + if (meta[1] != WHIMORY_CXT_TAG_L2V) + continue; + n_l2v++; + ret = whimory_cxt_parse_tree(w, data, WHIMORY_LBA_SIZE, + &next_lba, &lba_valid, sb_weave); + if (ret) { + /* + * Stop this checkpoint, but say what stopping cost. + * + * Carrying on past a bad record is not an option: the + * logical cursor is what places every record after it, + * so a record parsed against a broken cursor lands at + * the wrong LBA, and a mapping in the wrong place is + * worse than a mapping missing. But the records after + * it were silently lost before, and a checkpoint that + * quietly stops halfway looks exactly like one that + * finished. + */ + s->cxt_records_lost += per_sb - ofs; + dev_warn(w->dev, + "CXT sb=%u parse stopped at record %u of %u (%d, %u L2V records read) -- up to %u records dropped\n", + sb_idx, ofs, per_sb, ret, n_l2v, + per_sb - ofs); + return ret; + } + } + if (!n_l2v) { + /* + * A checkpoint that contributes nothing is not necessarily + * stale, and on this device it is not: the two newest CXT + * superblocks -- weave 2049391 and 2049387 -- both land here, + * while the map is built from the two older ones at 2049383 + * and 2049379. That is a map one generation behind the + * volume, which is exactly what the bad reads look like: the + * checkpoint names a page whose contents were superseded, and + * the page still holds what it held at weave 687252. + * + * So the tags actually present are worth having. The walk + * only parses WHIMORY_CXT_TAG_L2V and stops at + * WHIMORY_CXT_TAG_CLEAN; if the newest checkpoints carry the + * tree under some other tag, or lead with a CLEAN record that + * stops the walk before the tree, this says so instead of + * leaving it as "superseded". + */ + char hb[160]; + unsigned int t, hn = 0; + + for (t = 0; t < ARRAY_SIZE(tag_hist); t++) { + if (!tag_hist[t] || hn + 14 >= sizeof(hb)) + continue; + hn += scnprintf(hb + hn, sizeof(hb) - hn, "%02x:%u ", + t, tag_hist[t]); + } + if (tag_other && hn + 16 < sizeof(hb)) + scnprintf(hb + hn, sizeof(hb) - hn, "other:%u ", + tag_other); + dev_info(w->dev, + "CXT sb=%u no L2V records: cxt_meta=%u clean=%u tags=%s(l2v=0x%02x clean=0x%02x)\n", + sb_idx, n_cxt_meta, n_clean, hb, + WHIMORY_CXT_TAG_L2V, WHIMORY_CXT_TAG_CLEAN); + } + return 0; +} + +/* + * Build the candidate map from the newest CXT base that parses cleanly. + * Never touches w->ranges. + */ +/* + * A total order, deliberately. + * + * This used to compare lba alone, which left every pair of extents with the + * same lba -- and the candidate map is the union of four checkpoint + * generations, so there are many -- in whatever order sort() happened to + * leave them. sort() is heapsort and is not stable, so that order was an + * artefact of the heap rather than of the flash: the same recovery over the + * same NAND could seed an LBA from either generation, and the map came out + * different on every boot. + * + * Ordering by weave second makes the newest extent for an lba the last one, + * every time. whimory_cxt_seed_l2v() then replays in index order and the + * newest legitimately wins, and whimory_cxt_lookup() takes the last match + * for the same reason. + */ +static int whimory_cxt_ext_cmp(const void *a, const void *b) +{ + const struct whimory_cxt_extent *x = a, *y = b; + + if (x->lba != y->lba) + return x->lba < y->lba ? -1 : 1; + if (x->weave != y->weave) + return x->weave < y->weave ? -1 : 1; + /* + * Same lba, same generation: order on vba so the result is a function + * of the media and not of the heap. Two extents this alike are a + * malformed checkpoint either way; CXT_VBA_OVERLAP reports them. + */ + if (x->vba != y->vba) + return x->vba < y->vba ? -1 : 1; + return 0; +} + +/* Total order, for the same reason whimory_cxt_ext_cmp() is one. */ +static int whimory_cxt_ext_vba_cmp(const void *a, const void *b) +{ + const struct whimory_cxt_extent *x = a, *y = b; + + if (x->vba != y->vba) + return x->vba < y->vba ? -1 : 1; + if (x->weave != y->weave) + return x->weave < y->weave ? -1 : 1; + if (x->lba != y->lba) + return x->lba < y->lba ? -1 : 1; + return 0; +} + +/* + * Do two extents claim the same physical VBA? + * + * The overlap check next to this one sorts by LBA and asks whether two + * extents claim the same logical block. That is the wrong axis for the + * failure that is left: a checkpoint mapping lba 555968 to a VBA whose page + * holds lba 564360, with both extents structurally sound. Two extents can + * name disjoint LBA ranges and still point at the same place, and nothing + * looked for it -- overlaps=0 was measuring the other axis and reading as + * "no collisions". + * + * The seed arbitrates on weave now, so a VBA claimed by two generations + * resolves the way the FTL wrote it: the newer extent wins. What ordering + * cannot fix is two extents at the SAME weave claiming overlapping VBAs -- + * one checkpoint cannot have put two logical ranges in one physical place, + * so that is a decode fault. The two are counted apart, because lumping + * them together is what made a healthy map look damaged. + */ +static void whimory_cxt_check_vba_overlaps(struct whimory *w) +{ + struct whimory_cxt_extent *by_vba; + unsigned int i, n = w->n_cxt_ext, hits = 0, shown = 0, cross_gen = 0; + size_t bytes; + + if (n < 2) + return; + bytes = (size_t)n * sizeof(*by_vba); + by_vba = kvmalloc(bytes, GFP_KERNEL); + if (!by_vba) { + dev_info(w->dev, "CXT_VBA_OVERLAP skipped (no memory)\n"); + return; + } + memcpy(by_vba, w->cxt_ext, bytes); + sort(by_vba, n, sizeof(*by_vba), whimory_cxt_ext_vba_cmp, NULL); + + for (i = 1; i < n; i++) { + const struct whimory_cxt_extent *p = &by_vba[i - 1]; + const struct whimory_cxt_extent *c = &by_vba[i]; + + if (c->vba >= p->vba + p->span) + continue; + /* + * Different generations may legitimately describe the same + * physical VBA; the seed resolves that on weave. Only a + * same-generation collision is a fault. + */ + if (c->weave != p->weave) { + cross_gen++; + continue; + } + hits++; + if (shown < 8) { + shown++; + dev_err(w->dev, + "CXT_VBA_OVERLAP lba=%u span=%u vba=%u overlaps lba=%u span=%u vba=%u by %u (weave=%llu)\n", + c->lba, c->span, c->vba, + p->lba, p->span, p->vba, + p->vba + p->span - c->vba, + (unsigned long long)c->weave); + } + } + if (hits) + dev_err(w->dev, + "CXT_VBA_OVERLAP %u extents claim a VBA another at the same weave already holds -- decode fault\n", + hits); + else + dev_info(w->dev, + "CXT_VBA_OVERLAP none within a generation (cross_gen=%u, resolved by weave)\n", + cross_gen); + kvfree(by_vba); +} + +/* + * Build the candidate map from every CXT superblock. + * + * The TREE is partitioned by logical range across the CXT blocks — on this + * device sb 1702 starts at LBA 0 and sb 3662 picks up at 613939 — so taking + * the first superblock that parses leaves most of the volume, including the + * FAT-critical sectors, unmapped. Merge them all, then sort by LBA so the + * lookup can binary search. Never touches w->ranges. + */ +static int whimory_cxt_build_candidate(struct whimory *w) +{ + struct whimory_cxt_base all[WHIMORY_CXT_MAX_SB]; + unsigned int i, n_all, ok = 0, overlaps = 0, regenerated = 0; + int ret, sess; + + /* + * Allocated on first use: translation splits every CXT run at 4-slot + * plane boundaries, so the table is roughly one entry per four LBAs + * and is only worth its megabytes when the CXT path is exercised. + */ + if (!w->cxt_ext) { + w->max_cxt_ext = cxt_max_extents; + w->cxt_ext = kvmalloc_array(w->max_cxt_ext, + sizeof(*w->cxt_ext), GFP_KERNEL); + if (!w->cxt_ext) { + w->max_cxt_ext = 0; + return -ENOMEM; + } + } + n_all = whimory_cxt_collect_sbs(w, all, ARRAY_SIZE(all)); + if (!n_all) + return -ENOENT; + + whimory_cxt_ext_reset(w); + w->sftl.cxt_records_seen = 0; + w->sftl.cxt_hole_entries = 0; + w->sftl.cxt_xlate_fail = 0; + w->sftl.cxt_hole_lbas = 0; + w->sftl.cxt_ext_nospc = 0; + w->sftl.cxt_records_lost = 0; + w->sftl.cxt_resyncs = 0; + w->sftl.cxt_sb_empty = 0; + + sess = s5l8740_nand_dma_session_begin(); + for (i = 0; i < n_all; i++) { + u32 before = w->n_cxt_ext; + + ret = whimory_cxt_build_from_sb(w, all[i].sb, all[i].weave); + if (ret) { + dev_warn(w->dev, + "CXT_CAND_MAP sb=%u parse failed %d\n", + all[i].sb, ret); + continue; + } + if (w->n_cxt_ext == before) { + /* + * Counted. sbs_used=2/4 read as "two checkpoints were + * stale", and it may well be, but nothing here had + * ever checked -- a superblock that parsed fine and + * produced nothing left exactly the same trace as one + * that was never looked at. + */ + w->sftl.cxt_sb_empty++; + dev_info(w->dev, + "CXT_CAND_MAP sb=%u weave=%llu parsed but added no extents\n", + all[i].sb, (unsigned long long)all[i].weave); + continue; + } + ok++; + if (all[i].weave > w->cxt_ext_weave) { + w->cxt_ext_weave = all[i].weave; + w->cxt_ext_sb = all[i].sb; + } + dev_info(w->dev, + "CXT_CAND_MAP sb=%u weave=%llu extents=+%u total=%u\n", + all[i].sb, (unsigned long long)all[i].weave, + w->n_cxt_ext - before, w->n_cxt_ext); + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + + if (!w->n_cxt_ext) + return -ENODATA; + + sort(w->cxt_ext, w->n_cxt_ext, sizeof(*w->cxt_ext), + whimory_cxt_ext_cmp, NULL); + for (i = 1; i < w->n_cxt_ext; i++) + /* + * Two different things used to be counted as one number. + * + * An extent that starts at the same LBA as the previous one is a + * second checkpoint generation describing the same range; that is + * expected, is now ordered by weave, and the seed resolves it. An + * extent that starts partway into the previous one is a genuine + * partial overlap and is not resolvable by ordering alone. Reporting + * them together made a healthy map look damaged and hid the case + * that matters. + */ + for (i = 1; i < w->n_cxt_ext; i++) { + const struct whimory_cxt_extent *p = &w->cxt_ext[i - 1]; + const struct whimory_cxt_extent *c = &w->cxt_ext[i]; + + if (c->lba >= p->lba + p->span) + continue; + if (c->lba == p->lba) + regenerated++; + else + overlaps++; + } + + dev_info(w->dev, + "CXT_MAP sbs_used=%u/%u extents=%u records=%u holes=%u " + "xlate_fail=%u overlaps=%u regenerated=%u base_weave=%llu\n", + ok, n_all, w->n_cxt_ext, w->sftl.cxt_records_seen, + w->sftl.cxt_hole_entries, w->sftl.cxt_xlate_fail, overlaps, + regenerated, (unsigned long long)w->cxt_ext_weave); + /* + * The accounting the previous line was missing. hole_lbas is the one + * that settles whether the holes are a fault: this volume maps about + * 938k of 3.86M sectors, so if the holes cover roughly the other + * three quarters they are describing unmapped space and are correct. + * If they cover a little and there are hundreds of them, they are + * not. + */ + dev_info(w->dev, + "CXT_MAP hole_lbas=%u empty_sbs=%u records_lost=%u nospc=%u " + "resyncs=%u hdr_bad=%u\n", + w->sftl.cxt_hole_lbas, w->sftl.cxt_sb_empty, + w->sftl.cxt_records_lost, w->sftl.cxt_ext_nospc, + w->sftl.cxt_resyncs, w->sftl.cxt_hdr_bad); + whimory_cxt_check_vba_overlaps(w); + return 0; +} + +/* + * Compare the candidate map against the brute-force interval map, which is + * ground truth because it comes from per-slot metadata. Any systematic + * decode error shows up as a repeated (vba_cxt - vba_brute) delta. + */ +#define WHIMORY_CXT_DELTA_SLOTS 8 + +static void whimory_cxt_compare(struct whimory *w) +{ + s64 delta_val[WHIMORY_CXT_DELTA_SLOTS] = {0}; + u32 delta_cnt[WHIMORY_CXT_DELTA_SLOTS] = {0}; + u32 agree = 0, disagree = 0, absent = 0, checked = 0; + u32 i, j, shown = 0; + u64 covered = 0; + + for (i = 0; i < w->n_cxt_ext; i++) { + struct whimory_cxt_extent *e = &w->cxt_ext[i]; + u32 probes[3], np = 0, k; + + covered += e->span; + probes[np++] = e->lba; + if (e->span > 2) + probes[np++] = e->lba + e->span / 2; + if (e->span > 1) + probes[np++] = e->lba + e->span - 1; + + for (k = 0; k < np; k++) { + u32 lba = probes[k]; + u32 want = e->vba + (lba - e->lba); + struct whimory_range *r; + s64 d; + + checked++; + r = whimory_range_find(&w->ranges, lba); + if (!r) { + absent++; + continue; + } + d = (s64)want - (s64)(r->vba + (lba - r->start)); + if (!d) { + agree++; + continue; + } + disagree++; + if (shown++ < diag_max_lines) { + u32 bv = r->vba + (lba - r->start); + u32 per = w->sftl.vbas_per_sb; + u32 dper = WHIMORY_DATA_VBAS_PER_SB; + + /* + * Decompose both VBAs in the 512-per-SB space + * we pack into and in the 508-per-SB data-only + * space, so a units mismatch is visible rather + * than inferred. + */ + dev_info(w->dev, + "CXT_CMP lba=%u cxt_vba=%u brute_vba=%u delta=%lld\n", + lba, want, bv, (long long)d); + dev_info(w->dev, + " cxt sb512=%u ofs512=%u sb508=%u ofs508=%u\n", + want / per, want % per, + want / dper, want % dper); + dev_info(w->dev, + " brute sb512=%u ofs512=%u sb508=%u ofs508=%u\n", + bv / per, bv % per, + bv / dper, bv % dper); + } + for (j = 0; j < WHIMORY_CXT_DELTA_SLOTS; j++) { + if (delta_cnt[j] && delta_val[j] != d) + continue; + delta_val[j] = d; + delta_cnt[j]++; + break; + } + } + } + + dev_info(w->dev, + "CXT_COMPARE extents=%u covered_lbas=%llu checked=%u " + "agree=%u disagree=%u absent_in_brute=%u\n", + w->n_cxt_ext, covered, checked, agree, disagree, absent); + for (j = 0; j < WHIMORY_CXT_DELTA_SLOTS; j++) { + if (!delta_cnt[j]) + continue; + dev_info(w->dev, "CXT_DELTA %lld x%u\n", + (long long)delta_val[j], delta_cnt[j]); + } +} + +/* + * Resolve an LBA through the candidate map only. + * + * The table is sorted (lba, weave) ascending and holds every checkpoint + * generation, so several extents can cover one LBA. Answering with the first + * one a binary search happens to land on picks a generation at random, which + * is the same fault whimory_cxt_ext_cmp() was fixed for. Find the upper + * bound on start LBA, then walk back and keep the newest weave among the + * extents that actually cover it. + * + * The walk is bounded: it stops as soon as it is further back than the + * longest extent in the table could reach. + */ +static int whimory_cxt_lookup(struct whimory *w, u32 lba, u32 *vba_out) +{ + u32 lo = 0, hi = w->n_cxt_ext, i; + u64 best_weave = 0; + bool found = false; + + /* First index whose start LBA is strictly greater than lba. */ + while (lo < hi) { + u32 mid = lo + (hi - lo) / 2; + + if (w->cxt_ext[mid].lba <= lba) + lo = mid + 1; + else + hi = mid; + } + + for (i = lo; i-- > 0; ) { + struct whimory_cxt_extent *e = &w->cxt_ext[i]; + + if (lba - e->lba >= w->cxt_ext_max_span) + break; + if (lba >= e->lba + e->span) + continue; + if (found && e->weave <= best_weave) + continue; + *vba_out = e->vba + (lba - e->lba); + best_weave = e->weave; + found = true; + } + return found ? 0 : -ENOENT; +} + +/* + * Read the BPB and the FAT-critical sectors through the candidate map and + * check that each landed on a page whose metadata claims the LBA we asked + * for. This is the gate that decides whether the CXT map is usable. + */ +static int whimory_cxt_validate(struct whimory *w, u32 fat_base) +{ + static const u32 rel[] = { 0, 1, 2, 6, 7, 8 }; + struct whimory_meta meta; + u8 *buf; + unsigned int i; + u32 ok = 0, bad = 0, miss = 0; + int ret, sess; + + if (!w->n_cxt_ext) + return -ENODATA; + buf = kmalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + sess = s5l8740_nand_dma_session_begin(); + for (i = 0; i < ARRAY_SIZE(rel); i++) { + u32 lba = fat_base + rel[i]; + u32 vba = 0, mlba; + + ret = whimory_cxt_lookup(w, lba, &vba); + if (ret) { + miss++; + dev_info(w->dev, "CXT_VALID lba=%u UNMAPPED\n", lba); + continue; + } + ret = w->vfl_ops->read_vba(w, vba, 1, buf, &meta); + if (ret) { + bad++; + dev_info(w->dev, "CXT_VALID lba=%u vba=%u read %d\n", + lba, vba, ret); + continue; + } + mlba = le32_to_cpu(meta.lba); + if (mlba == lba) { + ok++; + } else { + bad++; + dev_info(w->dev, + "CXT_VALID lba=%u vba=%u meta_lba=%u MISMATCH\n", + lba, vba, mlba); + } + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + + dev_info(w->dev, + "CXT_VALIDATE fat_base=%u ok=%u bad=%u unmapped=%u verdict=%s\n", + fat_base, ok, bad, miss, + (ok == ARRAY_SIZE(rel)) ? "USABLE" : "NOT_USABLE"); + return (ok == ARRAY_SIZE(rel)) ? 0 : -EBADMSG; +} + + +/* + * Sample extents across the candidate map, read the VBA the CXT claims, and + * print what the page metadata actually says. If the decode is merely stale + * the page still claims the LBA we asked for; if it is wrong, meta_lba is + * unrelated and the (expected, actual) pairs expose the transform. + */ +static void whimory_cxt_probe(struct whimory *w, unsigned int nsamples) +{ + struct whimory_meta meta; + u8 *buf; + u32 step, i, ok = 0, stale = 0, wrong = 0, blank = 0, zero = 0; + int ret, sess; + + if (!w->n_cxt_ext || !nsamples) + return; + buf = kmalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + if (!buf) + return; + + step = w->n_cxt_ext / nsamples; + if (!step) + step = 1; + + sess = s5l8740_nand_dma_session_begin(); + for (i = 0; i < w->n_cxt_ext; i += step) { + struct whimory_cxt_extent *e = &w->cxt_ext[i]; + u32 mlba; + + ret = w->vfl_ops->read_vba(w, e->vba, 1, buf, &meta); + if (ret) { + wrong++; + continue; + } + mlba = le32_to_cpu(meta.lba); + if (mlba == e->lba) { + ok++; + continue; + } + if (mlba == 0xffffffffu) + blank++; + else if (!mlba) + zero++; + else + wrong++; + if (stale++ < 12) + dev_info(w->dev, + "CXT_PROBE ext=%u lba=%u span=%u vba=%u " + "meta_type=%02x meta_lba=%u diff=%d\n", + i, e->lba, e->span, e->vba, meta.type, mlba, + (int)(mlba - e->lba)); + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + + dev_info(w->dev, + "CXT_PROBE_SUM sampled=%u ok=%u wrong=%u blank=%u zero_lba=%u\n", + (w->n_cxt_ext + step - 1) / step, ok, wrong, blank, zero); +} + +/* + * Seed the interval map from the CXT snapshot. + * + * Each extent is claimed at the weave of the checkpoint that produced it, + * not at the newest weave in the set. That is what lets the normal winner + * rules do their job: an older generation's extent loses to a newer one + * covering the same LBA, and the diff replay can still override either. + * + * Claiming everything at w->cxt_ext_weave -- as this used to -- made every + * seed extent look equally new, so whimory_range_update()'s stale check + * could never fire between two of them and the last one written won + * regardless of generation. Combined with an lba-only sort over an unstable + * sort(), which one that was came down to the heap. + */ +/* + * Does the page a CXT extent points at actually hold the LBA it claims? + * + * A checkpoint is a snapshot of the map at one commit. Its (lba, span, + * vba) triples were true then, and stay true only while nothing moves. + * Everything written afterwards lives in the journal, and any LBA + * rewritten since has left its old page behind -- a page the allocator + * has very likely recycled and refilled with data belonging to some + * other LBA. Replay the snapshot without checking and those stale + * triples are indistinguishable from good ones, because nothing inside + * a CXT record can be self-inconsistent. + * + * Measured on this unit after RetailOS had written: seeding from CXT + * gave mapped_lbas=950050 and a map that failed nearly every read, with + * "sftl lba mismatch want=0xc085 meta=0xcd97a" -- thousands of scattered + * low LBAs all resolving into one narrow band of pages that belong to + * LBAs around 842000. Rebuilding with use_cxt=0 produced a smaller map + * that read correctly and found the BPB. The seed was not malformed. It + * was stale, and it was winning. + * + * The BTOC path never had this problem because it does not trust its own + * index: whimory_l2v_update_from_slot_meta() reads the page and takes + * the meta LBA as the authority, the same way the YaFTL reference does + * in verifyUserSpares(), checking spare.lpn against the page it was + * asked for. This holds the CXT path to that standard, so a stale + * checkpoint costs coverage rather than correctness. + * + * Only the first slot of the extent is read. An extent is a contiguous + * run laid down by one write, so its head is representative, and + * checking every slot would multiply recovery time by the run length to + * learn nothing new in the common case. + */ +static bool whimory_cxt_extent_confirmed(struct whimory *w, + const struct whimory_cxt_extent *e) +{ + u8 spare[S5L8740_NAND_META_SIZE]; + u8 *data = w->sftl.data_page; + u32 ce, cau, vblock, page, slot; + u32 pblock, rcau, meta_lba; + const u8 *m; + int ret; + + if (!cxt_meta_confirm || !data) + return true; + if (cxt_confirm_max && w->sftl.cxt_confirm_pages >= cxt_confirm_max) + return true; + if (recover_budget_ms && w->sftl.confirm_start_jiffies && + time_after(jiffies, w->sftl.confirm_start_jiffies + + msecs_to_jiffies(recover_budget_ms))) + return true; + if (whimory_unpack_vba(w, e->vba, &ce, &cau, &vblock, &page, &slot)) + return true; + if (slot >= WHIMORY_VBAS_PER_PAGE) + return true; + + rcau = cau; + whimory_vfl_resolve(w, vblock, &rcau, &pblock); + ret = whimory_cs_read_page(w, ce, rcau, pblock, page, data, + S5L8740_NAND_PAGE_SIZE, spare, sizeof(spare)); + if (ret) { + /* + * Unreadable is not evidence of staleness. Keep the extent + * and let the per-read meta check catch it later. + */ + w->sftl.cxt_confirm_unreadable++; + return true; + } + w->sftl.cxt_confirm_pages++; + if (recover_yield_us && (w->sftl.cxt_confirm_pages & 0x0f) == 0) { + cond_resched(); + usleep_range(recover_yield_us, recover_yield_us + 500); + } + + m = spare + slot * WHIMORY_META_SIZE; + if (whimory_meta_erased(m, WHIMORY_META_SIZE)) + return true; + if (m[0] != WHIMORY_META_TYPE_DATA && m[0] != WHIMORY_META_TYPE_DATA2) + return true; + + meta_lba = get_unaligned_le32(m + 8); + if (meta_lba == e->lba) { + w->sftl.cxt_meta_confirmed++; + return true; + } + w->sftl.cxt_meta_mismatch++; + dev_dbg(w->dev, + "cxt_meta_mismatch lba=%u span=%u vba=%u meta_lba=%u pg=%u slot=%u\n", + e->lba, e->span, e->vba, meta_lba, page, slot); + return false; +} + +/* + * Rebuild the LBAs a stale checkpoint extent had claimed, from the pages + * themselves. + * + * Refusing a stale extent is necessary and on its own it is not enough. + * Dropping it leaves the range unclaimed, and the next-best claim for + * those LBAs is generally an even older one, so the map ends up wrong in + * a different way: on this unit, dropping alone moved LBA 49279 from a + * page holding someone else data to a page holding no data at all, and + * the FAT signature check failed instead of the read. + * + * So the extent is re-derived rather than deleted. Every page it covers + * is read once and each slot is applied from its own metadata, which is + * exactly how the BTOC walk builds the map and what the YaFTL reference + * does in restoreUserBlock(): the page says which logical block it holds, + * and that statement is authoritative because it was written with the + * data. A checkpoint can go stale. A page cannot lie about itself. + * + * Only extents that failed confirmation get here, so the cost is bounded + * by how wrong the checkpoint is rather than by its size -- 37 extents + * out of 3200 on this unit. + */ +static void whimory_cxt_extent_repair(struct whimory *w, + const struct whimory_cxt_extent *e) +{ + u8 spare[S5L8740_NAND_META_SIZE]; + u8 *data = w->sftl.data_page; + u32 last_ce = ~0u, last_cau = ~0u, last_vblock = ~0u, last_page = ~0u; + u32 i; + + if (!data) + return; + + for (i = 0; i < e->span; i++) { + u32 ce, cau, vblock, page, slot, pblock, rcau; + int ret; + + if (whimory_unpack_vba(w, e->vba + i, &ce, &cau, &vblock, + &page, &slot)) + return; + if (slot >= WHIMORY_VBAS_PER_PAGE) + continue; + + /* One read per page, not per slot. */ + if (ce != last_ce || cau != last_cau || + vblock != last_vblock || page != last_page) { + if (recover_budget_ms && w->sftl.confirm_start_jiffies && + time_after(jiffies, w->sftl.confirm_start_jiffies + + msecs_to_jiffies(recover_budget_ms))) + return; + rcau = cau; + whimory_vfl_resolve(w, vblock, &rcau, &pblock); + ret = whimory_cs_read_page(w, ce, rcau, pblock, page, data, + S5L8740_NAND_PAGE_SIZE, spare, + sizeof(spare)); + if (ret) + return; + last_ce = ce; + last_cau = cau; + last_vblock = vblock; + last_page = page; + w->sftl.cxt_repair_pages++; + if (recover_yield_us && + (w->sftl.cxt_repair_pages & 0x0f) == 0) { + cond_resched(); + usleep_range(recover_yield_us, + recover_yield_us + 500); + } + } + + /* + * No BTOC hint: the checkpoint already proved itself wrong + * about this run, so the metadata is the only witness left. + */ + if (whimory_l2v_update_from_slot_meta(w, ce, cau, vblock, page, + slot, + spare + slot * + WHIMORY_META_SIZE, + 0xffffffffu) < 0) + return; + } +} + +static int whimory_cxt_seed_l2v(struct whimory *w) +{ + u32 i, seeded = 0, regenerated = 0; + int ret; + + w->sftl.claim_source = 3; + for (i = 0; i < w->n_cxt_ext; i++) { + struct whimory_cxt_extent *e = &w->cxt_ext[i]; + + /* + * Everything is applied, oldest generation first. + * + * Skipping an extent because a newer one starts at the same + * LBA looks tempting and is wrong: the two need not have the + * same span. An old extent covering lba 100 for 1000 sectors + * and a new one covering lba 100 for 10 describe the same + * start and different amounts of the volume, and dropping the + * old one would lose 990 sectors outright. + * + * Applying both in ascending weave order gets it right + * without a special case: the older lands first, the newer + * overwrites exactly the part it claims, and the rest of the + * older extent stays. whimory_range_update() protects the + * newer from ever being clobbered by an older claim. + */ + if (i && e->lba == w->cxt_ext[i - 1].lba) + regenerated++; + + if (!whimory_cxt_extent_confirmed(w, e)) { + whimory_cxt_extent_repair(w, e); + cond_resched(); + continue; + } + + w->sftl.claim_weave = e->weave; + ret = whimory_l2v_update(w, e->lba, e->span, e->vba); + w->sftl.claim_weave = 0; + if (ret) { + w->sftl.claim_source = 0; + return ret; + } + seeded += e->span; + w->sftl.cxt_l2v_updates++; + if ((i & 0x3fff) == 0) + cond_resched(); + } + w->sftl.claim_source = 0; + dev_info(w->dev, + "CXT_SEED extents=%u regenerated=%u lbas=%u ranges=%u base_weave=%llu\n", + w->n_cxt_ext, regenerated, seeded, w->sftl.range_nodes, + (unsigned long long)w->cxt_ext_weave); + return 0; +} + +/* + * Fast path: seed from the CXT, then let the caller replay only the + * superblocks newer than the checkpoint. Returns 0 when the map is seeded. + */ +static int whimory_cxt_fast_load(struct whimory *w) +{ + int ret; + + ret = whimory_cxt_build_candidate(w); + if (ret) { + dev_warn(w->dev, + "CXT fast path unavailable (%d); full replay\n", + ret); + return ret; + } + ret = whimory_cxt_seed_l2v(w); + if (ret) + return ret; + w->cxt_base_weave = w->cxt_ext_weave; + /* + * The weave fast-forward, from s_cxt.c:81. + * + * Having loaded a checkpoint, stock sets the write cursor to + * + * baseWeaveSeq + save.num_sb * s_g_vbas_per_sb + 1 + * + * so that every subsequent write carries a weave at or above it. The + * weaves in between belong to the checkpoint's own pages. This + * driver has no write path and never needed the cursor, but it does + * need the boundary: the diff skip below has to separate "predates + * the checkpoint" from "written after it", and the base alone puts + * the checkpoint's own write span on the wrong side of that line. + * + * num_sb comes from the BASE payload when it parsed, and otherwise + * from the number of checkpoint superblocks classify found, which is + * the same number on a healthy volume. + */ + { + u32 nsb = w->cxt_save_num_sb ? w->cxt_save_num_sb : + (w->n_cxt ? w->n_cxt : 1); + + w->cxt_top_weave = w->cxt_base_weave + + (u64)nsb * whimory_vbas_per_vblock(w) + 1; + dev_info(w->dev, + "CXT_WEAVE base=%llu top=%llu (num_sb=%u src=%s)\n", + (unsigned long long)w->cxt_base_weave, + (unsigned long long)w->cxt_top_weave, nsb, + w->cxt_save_num_sb ? "BASE" : "classify"); + } + w->sftl.cxt_loaded = true; + /* + * The interval map now holds everything the extent table did, and the + * table is ~12 MiB on a 55 MiB device. Drop it; the Phase 3 tools + * reallocate it on demand. + */ + kvfree(w->cxt_ext); + w->cxt_ext = NULL; + w->max_cxt_ext = 0; + w->n_cxt_ext = 0; + return 0; +} + +/* Phase 3 entry point: build the candidate map, compare it, validate it. */ +int whimory_cxt_candidate(u32 fat_base) +{ + struct whimory *w = whimory_dev; + int ret; + + if (!w) + return -ENODEV; + ret = whimory_cxt_build_candidate(w); + if (ret) { + dev_warn(w->dev, "CXT candidate build failed %d\n", ret); + return ret; + } + whimory_cxt_compare(w); + whimory_cxt_probe(w, 24); + if (fat_base) + whimory_cxt_validate(w, fat_base); + return 0; +} +EXPORT_SYMBOL_GPL(whimory_cxt_candidate); + + +static void whimory_note_meta0(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, const u8 *data, const u8 *meta) +{ + unsigned int slot; + + if (!data || !meta) + return; + for (slot = 0; slot < WHIMORY_VBAS_PER_PAGE; slot++) { + const u8 *m = meta + slot * WHIMORY_META_SIZE; + const u8 *d = data + slot * WHIMORY_LBA_SIZE; + u32 lba = get_unaligned_le32(m + 8); + u32 vba, vblock; + u16 bps; + + if (lba != 0) + continue; + if (m[0] != WHIMORY_META_TYPE_DATA && + m[0] != WHIMORY_META_TYPE_DATA2) + continue; + w->sftl.meta0_hits++; + if (!ftl_diag || w->sftl.meta0_hits > 24) + continue; + vblock = whimory_vfl_virt(w, cau, block); + vba = whimory_pack_vba(w, ce, cau, vblock, page, slot); + bps = get_unaligned_le16(d + 11); + dev_info(w->dev, + "META0_HIT vba=%u ce=%u cau=%u blk=%u page=%u slot=%u type=%02x first64=%32ph %32ph bps=%u\n", + vba, ce, cau, block, page, slot, m[0], d, d + 32, bps); + } +} + +static void whimory_dump_btoc_page(struct whimory *w, const struct whimory_sb *sb, + u32 vblock, const u8 *page, const u8 *meta) +{ + u32 be0 = get_unaligned_be32(page); + u32 be1 = get_unaligned_be32(page + 4); + u32 be2 = get_unaligned_be32(page + 8); + bool lpn = whimory_btoc_looks_be_lpn(page); + + dev_info(w->dev, + "BTOC_DUMP sb_ce=%u cau=%u blk=%u vblock=%u page=%u first32=%32ph meta0=%16ph be=%u %u %u%s\n", + sb->ce, sb->cau, sb->block, vblock, WHIMORY_BTOC_PAGE, + page, meta, be0, be1, be2, + lpn ? " (BE LPN array)" : ""); +} + +static void whimory_print_recovery_stats(struct whimory *w) +{ + struct whimory_sftl *s = &w->sftl; + + dev_info(w->dev, + "RECOVERY_STATS:\n" + " scan_blocks=%u (param) user_blocks=%u\n" + " fpart_sig=%u vfl_ctx_hits=%u vfl_cxt_loc=%u vfl_bitmap=%u\n" + " classified_empty=%u classified_closed=%u classified_open=%u classified_cxt=%u classified_unknown=%u\n" + " sb_banks known=%u partial=%u overflow=%u\n" + " cxt_blocks_seen=%u cxt_records_seen=%u cxt_l2v_updates=%u\n" + " cxt_meta_confirmed=%u cxt_meta_mismatch=%u cxt_confirm_pages=%u cxt_confirm_unreadable=%u\n" + " cxt_repair_pages=%u cxt_repair_slots=%u\n" + " btoc_pages_read=%u btoc_pages_valid=%u btoc_entries_seen=%u btoc_l2v_updates=%u\n" + " btoc_meta_confirmed=%u btoc_meta_mismatch=%u btoc_skipped_zero=%u\n" + " btoc_blank=%u be_bte=%u be_lpn=%u le_bte=%u hdr8=%u unclaimed=%u\n" + " btoc_fallback sbs=%u pages=%u hits=%u (%u pages/sb)\n" + " unknown_fallback sbs=%u pages=%u hits=%u (%u pages/sb)\n" + " btoc_confirm_pages=%u btoc_confirm_capped=%u btoc_confirm_budget_stop=%u\n" + " btoc_unmap_entries=%u btoc_hole_entries=%u btoc_unknown_entries=%u\n" + " btoc_token_ffff0000=%u btoc_token_ffffff00=%u btoc_token_ffffffff=%u btoc_holelist_ffff0001=%u\n" + " open_slots_seen=%u open_slots_valid_meta=%u open_l2v_updates=%u\n" + " open_unmap_entries=%u open_skipped_zero=%u open_overrides_closed=%u " + "open_rejected_stale=%u open_unknown_order=%u\n" + " mapped_lbas=%u mapped_ranges=%u mapped_roots=%u range_budget_stop=%u\n" + " string_hits itunesdb=%u f00=%u ipod_control=%u music=%u apps=%u mp3=%u m4a=%u\n" + " l2v_update_calls=%u l2v_unmap_calls=%u stale_mapping_rejected=%u " + "l2v_repack_roots=%u meta0_hits=%u\n", + scan_blocks, s->user_blocks, + w->sig_ok, w->vfl.ctx_hits, w->vfl.cxt_loc_count, + w->vfl.bitmap_loaded, + s->empty_sbs, s->btoc_sbs, s->open_sbs, s->cxt_sbs, + s->unknown_sbs, + s->sb_bank_known, s->sb_bank_partial, s->sb_bank_overflow, + s->cxt_blocks_seen, s->cxt_records_seen, s->cxt_l2v_updates, + s->cxt_meta_confirmed, s->cxt_meta_mismatch, + s->cxt_confirm_pages, s->cxt_confirm_unreadable, + s->cxt_repair_pages, s->cxt_repair_slots, + s->btoc_pages_read, s->btoc_pages_valid, s->btoc_entries_seen, + s->btoc_l2v_updates, + s->btoc_meta_confirmed, s->btoc_meta_mismatch, + s->btoc_skipped_zero, + s->btoc_blank, s->btoc_be_bte, s->btoc_be_lpn, + s->btoc_le_bte, s->btoc_le_bte_hdr8, s->btoc_unclaimed, + s->btoc_fb_sbs, s->btoc_fb_pages, s->btoc_fb_hits, + s->btoc_fb_sbs ? s->btoc_fb_pages / s->btoc_fb_sbs : 0, + s->unk_fb_sbs, s->unk_fb_pages, s->unk_fb_hits, + s->unk_fb_sbs ? s->unk_fb_pages / s->unk_fb_sbs : 0, + s->btoc_confirm_pages, s->btoc_confirm_capped, + s->btoc_confirm_budget_stop, + s->btoc_unmap_entries, s->btoc_hole_entries, + s->btoc_unknown_entries, + s->btoc_token_ffff0000, s->btoc_token_ffffff00, + s->btoc_token_ffffffff, s->btoc_holelist_ffff0001, + s->open_slots_seen, s->open_slots_valid_meta, + s->open_l2v_updates, + s->open_unmap_entries, s->open_skipped_zero, + s->open_overrides_closed, s->open_rejected_stale, + s->open_unknown_order, + s->mapped_lbas, s->range_nodes, s->mapped_roots, + s->range_budget_stop, + s->string_hit_itunesdb, s->string_hit_f00, + s->string_hit_ipod_control, s->string_hit_music, + s->string_hit_apps, s->string_hit_mp3, s->string_hit_m4a, + s->l2v_update_calls, s->l2v_unmap_calls, + s->stale_mapping_rejected, + s->l2v_repack_roots, s->meta0_hits); +} + +static void whimory_scan_closed_meta0(struct whimory *w, unsigned int nsb) +{ + unsigned int i, pg, scanned = 0, cap; + u8 spare[S5L8740_NAND_META_SIZE]; + u8 *data = w->sftl.data_page; + + cap = meta0_scan_sbs; + if (!cap || !data) + return; + dev_info(w->dev, + "META0_SCAN deeper closed SBs=%u pages 0..%u (independent of L2V)\n", + cap, WHIMORY_DATA_PAGES_PER_SB - 1); + for (i = 0; i < nsb && scanned < cap; i++) { + struct whimory_sb *sb = &w->sftl.sbs[i]; + + if (sb->kind != WHIMORY_SB_CLOSED) + continue; + scanned++; + for (pg = 0; pg < WHIMORY_DATA_PAGES_PER_SB; pg++) { + int ret; + + ret = whimory_cs_read_page(w, sb->ce, sb->cau, sb->block, + pg, data, + S5L8740_NAND_PAGE_SIZE, + spare, sizeof(spare)); + if (ret) + break; + whimory_note_meta0(w, sb->ce, sb->cau, sb->block, pg, + data, spare); + if (!(pg & 0x1f)) + cond_resched(); + } + } +} + +/* + * Sibling slots on a page need not be contiguous LBAs. Only the selected + * map_slot is judged against requested fmss_lba. Sibling dump is VBA_DIAG. + */ +static bool whimory_audit_fmss_lba(u32 fmss_lba) +{ + /* Known BPB / critical fmss candidates + FAT-relative later. */ + switch (fmss_lba) { + case 49216u: + case 49279u: + case 49280u: + case 49285u: + case 49286u: + case 49311u: + case 49317u: /* FAT0 disk 32 @ base 49285 */ + case 51201u: /* root @ base 49285 */ + return true; + default: + if (fmss_lba >= 49279u && fmss_lba < 49279u + 2048u) + return true; + return false; + } +} + +static bool payload_string_scan; +module_param(payload_string_scan, bool, 0644); +MODULE_PARM_DESC(payload_string_scan, + "Scan confirmed pages for iTunesDB/F00/mp3 strings (default N)"); + +/* + * Seven strnstr() sweeps over 16 KiB, on the hottest path in the recover. + * + * This runs from whimory_btoc_confirm_page(), which on this volume is + * called 26653 times -- so it is 26653 passes over a 16 KiB buffer looking + * for "iTunesDB", "F00", "iPod_Control", "Music" and friends, inside the + * phase that already dominates the boot. It is pure diagnostics: nothing + * reads the counters except the RECOVERY_STATS line, and on this volume + * every one of them prints 0. + * + * payload_string_scan gates it and already defaults off, so this costs + * nothing today -- noted here only so nobody enables it during a boot-time + * measurement and wonders where the seconds went. It stays because it was + * useful once, for confirming that a rebuilt map really did point at a + * filesystem. + */ +static void whimory_note_payload_strings(struct whimory *w, const u8 *data, + unsigned int len) +{ + if (!payload_string_scan || !data || len < 8) + return; + if (memchr(data, 'i', len) && + strnstr((const char *)data, "iTunesDB", len)) + w->sftl.string_hit_itunesdb++; + if (strnstr((const char *)data, "F00", len) || + strnstr((const char *)data, "F01", len) || + strnstr((const char *)data, "F02", len)) + w->sftl.string_hit_f00++; + if (strnstr((const char *)data, "iPod_Control", len)) + w->sftl.string_hit_ipod_control++; + if (strnstr((const char *)data, "Music", len)) + w->sftl.string_hit_music++; + if (strnstr((const char *)data, "NanoApps", len) || + strnstr((const char *)data, "Apps", len)) + w->sftl.string_hit_apps++; + if (strnstr((const char *)data, ".mp3", len) || + strnstr((const char *)data, ".MP3", len) || + strnstr((const char *)data, "mp3", len)) + w->sftl.string_hit_mp3++; + if (strnstr((const char *)data, ".m4a", len) || + strnstr((const char *)data, ".M4A", len) || + strnstr((const char *)data, "m4a", len)) + w->sftl.string_hit_m4a++; +} + +static void whimory_dump_vba_page(struct whimory *w, u32 vba, u32 fmss_lba) +{ + u32 ce, cau, vblock, page, map_slot, pblock, slot; + u8 spare[S5L8740_NAND_META_SIZE]; + u8 *data = w->sftl.data_page; + const u8 *sel_m; + u32 sel_meta_lba; + u64 sel_weave; + bool sel_type_ok, sel_lba_ok, bad; + int ret; + + if (!data) + return; + if (whimory_unpack_vba(w, vba, &ce, &cau, &vblock, &page, &map_slot)) { + dev_warn(w->dev, + "BAD_VBA unpack failed fmss_lba=%u vba=%u\n", + fmss_lba, vba); + return; + } + whimory_vfl_resolve(w, vblock, &cau, &pblock); + ret = whimory_cs_read_page(w, ce, cau, pblock, page, data, + S5L8740_NAND_PAGE_SIZE, spare, + sizeof(spare)); + if (ret) { + dev_warn(w->dev, + "BAD_VBA page read fmss_lba=%u vba=%u ret=%d\n", + fmss_lba, vba, ret); + return; + } + + sel_m = spare + map_slot * WHIMORY_META_SIZE; + sel_meta_lba = get_unaligned_le32(sel_m + 8); + sel_weave = whimory_weave48(sel_m); + sel_type_ok = (sel_m[0] == WHIMORY_META_TYPE_DATA || + sel_m[0] == WHIMORY_META_TYPE_DATA2) && + !(sel_m[1] & 0x02); + sel_lba_ok = (sel_meta_lba == fmss_lba); + bad = !sel_type_ok || !sel_lba_ok; + + dev_info(w->dev, + "VBA_DIAG fmss_lba=%u vba=%u sb=%u ofs=%u " + "ppn=ce%u/cau%u/vblk%u/pbn%u/pg%u selected_slot=%u " + "selected_meta_lba=%u selected_weave=%012llx " + "type=%02x flags=%02x verdict=%s\n", + fmss_lba, vba, s_g_vba_to_sb(w, vba), s_g_vba_to_ofs(w, vba), + ce, cau, vblock, pblock, page, map_slot, sel_meta_lba, + (unsigned long long)sel_weave, sel_m[0], sel_m[1], + bad ? "BAD" : "OK"); + + if (bad) { + const u8 *d = data + map_slot * WHIMORY_LBA_SIZE; + + dev_warn(w->dev, + "BAD_VBA fmss_lba=%u selected_slot=%u type=%02x " + "flags=%02x meta_lba=%u (want %u) first64=%32ph\n", + fmss_lba, map_slot, sel_m[0], sel_m[1], sel_meta_lba, + fmss_lba, d); + } + + if (!vba_page_dump) + return; + + for (slot = 0; slot < WHIMORY_VBAS_PER_PAGE; slot++) { + const u8 *m = spare + slot * WHIMORY_META_SIZE; + const u8 *d = data + slot * WHIMORY_LBA_SIZE; + u32 meta_lba = get_unaligned_le32(m + 8); + u16 bps = get_unaligned_le16(d + 11); + + dev_info(w->dev, + "VBA_DIAG slot=%u%s type=%02x flags=%02x meta_lba=%u " + "bps=%u first64=%32ph meta=%16ph\n", + slot, slot == map_slot ? "*" : "", + m[0], m[1], meta_lba, bps, d, m); + } +} + + +/* + * Batched meta pre-pass for one plane, in stock's shape. + * + * RetailOS scans superblocks by queueing two meta reads each -- the first + * page and the last -- and polling once per 256 records + * (s_cxt_diff.c:349, S_SCAN_META_SIZE). Every read in the batch lands its + * page data in the same discarded buffer while only the 16-byte meta cursor + * advances, so a kick covers 128 superblocks and costs 4 KiB of meta and + * one sector of data. + * + * This fills meta0[] and metaN[] for blocks [0, nscan) of one (ce, cau) so + * the classify loop that follows does no I/O at all. + * + * Returns 0 with both arrays filled, or negative if the scan transport is + * unavailable, in which case the caller reads per block as before. + */ +static int whimory_scan_plane_meta(struct whimory *w, unsigned int ce, + unsigned int cau, unsigned int nscan, + u8 *meta0, u8 *metaN) +{ + struct s5l8740_ppn_ref *refs; + u8 *mbuf; + unsigned int b, i, per_kick; + unsigned int last_page = w->sftl.pages_per_sb ? + w->sftl.pages_per_sb - 1 : WHIMORY_BTOC_PAGE; + int ret = 0; + + if (!stock_scan) + return -ENODEV; + + /* Two records per superblock, so half the record cap in blocks. */ + per_kick = S5L8740_NAND_BATCH_MAX / 2; + + refs = kmalloc_array(per_kick * 2, sizeof(*refs), GFP_KERNEL); + mbuf = kmalloc_array(per_kick * 2, WHIMORY_META_SIZE, GFP_KERNEL); + if (!refs || !mbuf) { + kfree(refs); + kfree(mbuf); + return -ENOMEM; + } + + for (b = 0; b < nscan; b += per_kick) { + unsigned int n = min(per_kick, nscan - b); + + for (i = 0; i < n; i++) { + refs[2 * i].cau = (u8)cau; + refs[2 * i].block = (u16)(b + i); + refs[2 * i].page = 0; + refs[2 * i + 1].cau = (u8)cau; + refs[2 * i + 1].block = (u16)(b + i); + refs[2 * i + 1].page = (u8)last_page; + } + ret = s5l8740_nand_cs_scan_meta((u8)ce, refs, n * 2, mbuf); + if (ret) + break; + for (i = 0; i < n; i++) { + memcpy(meta0 + (size_t)(b + i) * WHIMORY_META_SIZE, + mbuf + (size_t)(2 * i) * WHIMORY_META_SIZE, + WHIMORY_META_SIZE); + memcpy(metaN + (size_t)(b + i) * WHIMORY_META_SIZE, + mbuf + (size_t)(2 * i + 1) * WHIMORY_META_SIZE, + WHIMORY_META_SIZE); + } + w->sftl.scan_kicks++; + cond_resched(); + } + kfree(refs); + kfree(mbuf); + return ret; +} + +static int whimory_sftl_recover_l2v_from_media(struct whimory *w) +{ + struct whimory_sftl *s = &w->sftl; + unsigned int ce, cau, b, nscan, nmap, nsb = 0, i, open_done = 0; + u8 meta0[S5L8740_NAND_META_SIZE]; + u8 meta127[S5L8740_NAND_META_SIZE]; + u8 *plane_m0 = NULL; + u8 *plane_mN = NULL; + u8 *p127; + u32 *meta0_hist; + u32 *meta127_hist; + int ret; + + nscan = scan_blocks ? scan_blocks : s->user_blocks; + if (nscan > s->user_blocks) + nscan = s->user_blocks; + + /* + * The bank map has to cover every block a VBA may name, which is + * blocks_per_cau -- 2088 -- and not the 1960 the classify loop + * enumerates. The FTL does put user data above user_blocks: there + * are checkpoint extents in virtual blocks 1987..1991 and 2048 on + * this unit, and vblock 2048 is a three-bank superblock. Left out of + * the map it decoded as four banks and read one page in twelve + * wrong, which was the last mismatch standing after the rest of this + * was fixed. + * + * It costs nothing: the batched pre-pass already sweeps a whole + * plane in one series of kicks, so 128 more blocks per plane is + * within the noise of a pass that takes about 1.2 seconds. The + * classify loop itself still stops at nscan -- the tail is the VFL + * and FPart region, and enumerating it as data superblocks would put + * blocks into the replay that have no business there. + */ + nmap = whimory_vba_blocks(w); + if (nmap < nscan) + nmap = nscan; + + p127 = s->btoc_page; + if (!p127) + return -ENOMEM; + + /* + * Histogram of the page 0 meta type byte over every block scanned. + * When a class comes out at zero -- cxt=0, say -- the useful question + * is whether the flash has no such block or whether the test for it + * stopped matching, and the classify counters cannot tell those apart. + * This can: WHIMORY_META_TYPE_SFTL_CXT is 0x1f, so a non-zero count at + * 0x1f with cxt=0 means the recogniser is wrong, and a zero count means + * the blocks really are not there. + */ + meta0_hist = kcalloc(256, sizeof(*meta0_hist), GFP_KERNEL); + meta127_hist = kcalloc(256, sizeof(*meta127_hist), GFP_KERNEL); + s->btoc_dumps_left = ftl_diag ? 5 : 0; + w->l2v_defer_pack = true; + s->btoc_verified = 0; + s->diff_replayed_sbs = 0; + s->diff_skipped_sbs = 0; + /* + * Cleared per run: a re-recover reclassifies every block, and a mask + * carried over from a previous scan would keep banks that this one + * did not see. + */ + if (s->sb_bank_mask) + memset(s->sb_bank_mask, 0, s->sb_bank_blocks); + s->sb_bank_known = 0; + s->sb_bank_partial = 0; + s->sb_bank_overflow = 0; + s->confirm_start_jiffies = jiffies; + s->btoc_confirm_budget_stop = 0; + s->string_hit_itunesdb = 0; + s->string_hit_f00 = 0; + s->string_hit_apps = 0; + s->string_hit_mp3 = 0; + s->string_hit_m4a = 0; + + dev_info(w->dev, + "SFTL classify scan ce=%u cau=%u blocks=%u " + "btoc_confirm_max=%u recover_budget_ms=%u audit_winners=%d\n", + w->geom.num_ce, w->geom.num_cau, nscan, + btoc_confirm_max, recover_budget_ms, audit_lba_winners); + + plane_m0 = kvmalloc_array(nmap, WHIMORY_META_SIZE, GFP_KERNEL); + plane_mN = kvmalloc_array(nmap, WHIMORY_META_SIZE, GFP_KERNEL); + if (!plane_m0 || !plane_mN) { + kvfree(plane_m0); + kvfree(plane_mN); + plane_m0 = NULL; + plane_mN = NULL; + } + + for (ce = 0; ce < w->geom.num_ce; ce++) { + for (cau = 0; cau < w->geom.num_cau; cau++) { + bool have_scan = false; + + if (plane_m0 && plane_mN) + have_scan = !whimory_scan_plane_meta(w, ce, cau, + nmap, + plane_m0, + plane_mN); + /* + * Bank membership for the blocks past nscan, which + * classify does not enumerate. Nothing else is + * derived from them -- they do not enter sbs[] and + * are never replayed -- but a VBA naming one has to + * decode against the right bank count. + */ + if (have_scan) { + for (b = nscan; b < nmap; b++) { + const u8 *pm0 = plane_m0 + + (size_t)b * WHIMORY_META_SIZE; + const u8 *pmN = plane_mN + + (size_t)b * WHIMORY_META_SIZE; + + if (whimory_meta_is_member(pm0) || + whimory_meta_is_member(pmN)) + whimory_sb_bank_note(w, + whimory_vfl_virt(w, cau, b), + ce, cau); + } + } + for (b = 0; b < nscan; b++) { + struct whimory_sb *sb; + int r0, r127; + + if (nsb >= s->num_sb) + goto classify_done; + if ((b & 0x1f) == 0 && ftl_progress_due(w)) + ftl_progress_set(w, "classify", b, nscan), + dev_info(w->dev, + "SFTL classify ce=%u cau=%u blk=%u/%u nsb=%u\n", + ce, cau, b, nscan, nsb); + if (recover_yield_us && (b & 0x3) == 0) { + cond_resched(); + usleep_range(recover_yield_us, + recover_yield_us + 500); + } + /* + * Page 127 is read lazily. It used to be fetched for + * every block alongside page 0, but meta127 is only + * ever consulted for the closed/BTOC test below -- so + * on this device 5484 empty blocks and the 4 CXT + * blocks paid for a full page read whose result was + * discarded. That is roughly 70 percent of the page + * 127 traffic and a third of all classify IO, against + * a pass that takes about 57 seconds at 7.4 ms per + * block. + * + * A blank page 0 with an erased meta is taken as an + * empty block without confirming page 127. Blocks are + * written from page 0 upwards, so blank-at-0 with data + * at 127 does not occur in normal operation. If page 0 + * cannot be read at all the old both-failed rule still + * applies and page 127 is consulted. + */ + /* + * One short read answers both questions. + * + * Empty is the common answer -- about seven + * blocks in ten on this volume -- and slot 0 + * settles it. But the same 4112 bytes also + * carry the meta that classifies a non-empty + * block, so there is no reason to throw them + * away and read the page again: the block + * either finishes here or escalates to the + * full read, and nothing reads page 0 twice. + */ + if (have_scan) { + /* + * Both metas already in hand from the + * batched pre-pass, so this block + * costs no I/O. An erased pair marks + * a free superblock, which is the + * only test stock applies here. + */ + const u8 *pm0 = plane_m0 + + (size_t)b * WHIMORY_META_SIZE; + const u8 *pmN = plane_mN + + (size_t)b * WHIMORY_META_SIZE; + + memset(meta0, 0xff, sizeof(meta0)); + memcpy(meta0, pm0, WHIMORY_META_SIZE); + memset(meta127, 0xff, sizeof(meta127)); + memcpy(meta127, pmN, WHIMORY_META_SIZE); + r0 = 0; + r127 = 0; + if (meta0_hist) + meta0_hist[meta0[0]]++; + if (whimory_meta_erased(meta0, + WHIMORY_META_SIZE) && + whimory_meta_erased(meta127, + WHIMORY_META_SIZE)) { + s->empty_sbs++; + s->fast_empty_hits++; + continue; + } + goto have_meta; + } + + r0 = -EAGAIN; + if (fast_empty_probe) { + const u8 *d0 = NULL; + u8 m0[WHIMORY_META_SIZE]; + + if (!whimory_prefetch_slot0(w, ce, cau, + b, nscan, + &d0, m0)) { + if (whimory_page_blank(d0, 64) && + whimory_meta_erased(m0, + WHIMORY_META_SIZE)) { + s->empty_sbs++; + s->fast_empty_hits++; + continue; + } + if (whimory_meta0_is_conclusive(m0)) { + /* + * Slots 1..3 stay 0xff + * so an erased-meta + * test on them tells + * the truth rather + * than repeating the + * last page's bytes. + */ + memset(meta0, 0xff, + sizeof(meta0)); + memcpy(meta0, m0, + WHIMORY_META_SIZE); + memcpy(w->sftl.data_page, + d0, + S5L8740_NAND_SLOT_DATA); + memset(w->sftl.data_page + + S5L8740_NAND_SLOT_DATA, + 0xff, + S5L8740_NAND_PAGE_SIZE - + S5L8740_NAND_SLOT_DATA); + s->fast_slot0_hits++; + r0 = 0; + } + } + } + + r127 = -EAGAIN; /* not read yet */ + + if (r0) + r0 = whimory_cs_read_page(w, ce, cau, b, 0, + w->sftl.data_page, + S5L8740_NAND_PAGE_SIZE, + meta0, sizeof(meta0)); + if (!r0) { + whimory_note_meta0(w, ce, cau, b, 0, + w->sftl.data_page, + meta0); + if (meta0_hist) + meta0_hist[meta0[0]]++; + } + + if (!btoc_page_lazy) + r127 = whimory_cs_read_page(w, ce, cau, b, + WHIMORY_BTOC_PAGE, + p127, + S5L8740_NAND_PAGE_SIZE, + meta127, + sizeof(meta127)); + + /* + * Only call a block empty when page 127 agrees, or + * when we deliberately did not look. Checking page 0 + * alone is what the lazy path relies on. + */ + if (!r0 && whimory_page_blank(w->sftl.data_page, 64) && + whimory_meta_erased(meta0, 16) && + (btoc_page_lazy || + (!r127 && whimory_page_blank(p127, 64) && + whimory_meta_erased(meta127, 16)))) { + s->empty_sbs++; + continue; + } + + /* CXT is decided from meta0 alone. */ + if (r127 == -EAGAIN && + (r0 || !whimory_meta_slot0_or_any_cxt(meta0))) { + r127 = whimory_cs_read_page(w, ce, cau, b, + WHIMORY_BTOC_PAGE, + p127, + S5L8740_NAND_PAGE_SIZE, + meta127, + sizeof(meta127)); + if (r0 && r127) + continue; + if (r0 && + whimory_page_blank(p127, 64) && + whimory_meta_erased(meta127, 16)) { + s->empty_sbs++; + continue; + } + } +have_meta: + sb = &s->sbs[nsb]; + sb->ce = ce; + sb->cau = cau; + sb->block = b; + /* + * Two weaves, because they answer different + * questions and only one of them is any use + * for the CXT diff. + * + * sb->weave is page 0: the OLDEST content in + * the superblock. A block is filled from page + * 0 upwards, so page 0 was written when the + * block was opened -- which may have been long + * before the checkpoint even if the block was + * still being appended to long after it. + * + * sb->weave_max is the newest weave we have + * actually seen. For a closed superblock that + * is page 127, written when the block was + * sealed, so it is the real answer. For an + * open one there is no page whose weave bounds + * the block, which is why the replay below + * refuses to skip open superblocks at all. + */ + sb->weave = 0; + sb->weave_max = 0; + sb->weave_max_p127 = 0; + if (!r0 && (whimory_meta_is_data_raw(meta0) || + meta0[0] == WHIMORY_META_TYPE_SFTL_CXT)) + sb->weave = whimory_weave48(meta0); + if (!r127 && (whimory_meta_is_data_raw(meta127) || + whimory_meta_any_btoc(meta127))) { + sb->weave_max = whimory_weave48(meta127); + sb->weave_max_p127 = 1; + } + if (sb->weave_max < sb->weave) { + sb->weave_max = sb->weave; + sb->weave_max_p127 = 0; + } + if (meta127_hist && !r127) + meta127_hist[meta127[0]]++; + if (!r0 && whimory_meta_is_cxt_base(meta0, 0)) { + u32 vblock = whimory_vfl_virt(w, cau, b); + u32 sb_idx = whimory_sb_index(w, ce, cau, + vblock); + + sb->kind = WHIMORY_SB_CXT; + s->cxt_sbs++; + whimory_cxt_add_base(w, sb_idx, sb->weave); + } else if (!r0 && whimory_meta_slot0_or_any_cxt(meta0)) { + sb->kind = WHIMORY_SB_CXT; + s->cxt_sbs++; + } else if (!r127 && whimory_meta_any_btoc(meta127)) { + sb->kind = WHIMORY_SB_CLOSED; + s->btoc_sbs++; + } else if ((!r0 && whimory_meta_is_data_raw(meta0)) || + (!r127 && whimory_meta_is_data_raw(meta127))) { + sb->kind = WHIMORY_SB_OPEN; + s->open_sbs++; + } else { + /* + * Nothing recognised this block, which + * is not the same as it being empty -- + * the empty test ran earlier and said + * no. 78 blocks land here on this + * volume, meta type 00 on 76 of them + * and 0x4b on two, and until now they + * were counted and then dropped before + * ever entering sbs[]. A block that + * never enters sbs[] is never + * replayed, so up to 2032 LBAs each + * were unreachable with nothing in the + * log to say so. + * + * They are kept now and rebuilt from + * per-page meta in the replay, which + * is the one method that needs no + * recognised structure at all. Cost is + * bounded the same way as the BTOC + * fallback: a blank page 0 stops it + * after one read. + */ + sb->kind = WHIMORY_SB_UNKNOWN; + s->unknown_sbs++; + } + /* + * Bank membership, which is what every VBA in + * this superblock is decoded against. Only a + * recognised superblock counts as a member: + * an unreadable or unrecognised block is + * precisely the case the FTL left out of the + * superblock, and including it would put the + * whole block's addresses one bank wide. + */ + if (sb->kind == WHIMORY_SB_CLOSED || + sb->kind == WHIMORY_SB_OPEN || + sb->kind == WHIMORY_SB_CXT) + whimory_sb_bank_note(w, + whimory_vfl_virt(w, cau, b), + ce, cau); + nsb++; + } + } + } +classify_done: + sort(s->sbs, nsb, sizeof(s->sbs[0]), whimory_sb_cmp, NULL); + dev_info(w->dev, + "SFTL classified nsb=%u closed=%u open=%u cxt=%u empty=%u unknown=%u\n", + nsb, s->btoc_sbs, s->open_sbs, s->cxt_sbs, s->empty_sbs, + s->unknown_sbs); + /* + * How wide the superblocks actually are. + * + * This is the number every address on the volume is decoded against, + * and until now it was assumed to be four everywhere. A histogram + * with anything outside the maximum column is a volume where that + * assumption was costing reads; a histogram that is entirely in the + * maximum column says the assumption happened to hold. + */ + { + u32 maxb = whimory_max_banks(w); + char hb[128]; + unsigned int t, hn = 0; + + memset(s->sb_bank_hist, 0, sizeof(s->sb_bank_hist)); + s->sb_bank_known = 0; + s->sb_bank_partial = 0; + for (i = 0; i < s->sb_bank_blocks; i++) { + u32 n = hweight8(s->sb_bank_mask[i]); + + if (!n) + continue; + s->sb_bank_known++; + if (n < maxb) + s->sb_bank_partial++; + if (n < ARRAY_SIZE(s->sb_bank_hist)) + s->sb_bank_hist[n]++; + } + for (t = 0; t < ARRAY_SIZE(s->sb_bank_hist); t++) { + if (!s->sb_bank_hist[t] || hn + 14 >= sizeof(hb)) + continue; + hn += scnprintf(hb + hn, sizeof(hb) - hn, "%u:%u ", + t, s->sb_bank_hist[t]); + } + dev_info(w->dev, + "SFTL sb banks %s(known=%u partial=%u max=%u of %u vblocks)\n", + hb, s->sb_bank_known, s->sb_bank_partial, maxb, + s->sb_bank_blocks); + } + /* + * Check the derived bank map against the VFL's own bad-block table. + * + * The bank map is built from which banks carry a record at page 0, + * which is evidence but not authority. The authority for "this block + * is unusable" is the 0xc104 object -- and it is worth being precise + * about what that object is, because it was the obvious candidate + * for a per-VBN bank table and it is not one. + * + * Dumped on this unit: object_len 0x800 at +0x24, generation 29 at + * +0x28, payload at +0x80, and every payload row is 0xff except the + * first two. Those two rows hold exactly 29 clear bits, which is the + * 29 of "accepted: 29/2088 blocks bad" -- so it is one bit per + * block over blocks_per_cau, padded, and nothing more. All 29 bad + * blocks sit in blocks 0..31, while the narrow superblocks measured + * here are vblocks 320, 1259, 1818 and 2048. The two describe + * different things: a superblock can be narrower than four banks + * without any of its blocks being factory-bad. + * + * So the FTL's per-VBN bank table (s_vfl.c sub_3D1438, one stride- + * sized row per VBN) is not in this object and has not been located + * on media. + * + * What is left to check is the object's scope, which was an + * assumption and not a finding: the bitmap is taken to describe the + * blocks of the bank it was found on, blk_status_bank. If that is + * right, a block it calls bad must not have that bank as a member -- + * the superblock can still exist on the other three. So count both: + * how many mapped vblocks it calls bad at all, and how many of those + * still list that one bank. + * + * A zero in the second column supports the scope reading. A non-zero + * one says the bitmap is not this bank's block list, and the next + * person should not assume it is. + */ + if (w->vfl.bitmap_loaded && w->vfl.blk_status && s->sb_bank_mask) { + u32 v, flagged = 0, conflicts = 0, checked = 0; + u32 bank = w->vfl.blk_status_bank; + + for (v = 0; v < s->sb_bank_blocks && + v < w->geom.blocks_per_cau; v++) { + bool bad = !(w->vfl.blk_status[v >> 3] & + (1u << (v & 7))); + + if (!s->sb_bank_mask[v]) + continue; + checked++; + if (!bad) + continue; + flagged++; + if (bank < 8 && (s->sb_bank_mask[v] & (1u << bank))) + conflicts++; + } + s->sb_bank_conflicts = conflicts; + dev_info(w->dev, + "SFTL sb banks vs VFL bitmap: checked=%u flagged_bad=%u still_list_bank%u=%u (%u bad recorded)\n", + checked, flagged, bank, conflicts, + w->vfl.blk_status_bad); + if (conflicts) + dev_info(w->dev, + "SFTL the 0xc104 bitmap is not bank %u's block list -- do not read it as one\n", + bank); + } + dev_info(w->dev, "SFTL fast-empty probe settled %u of %u blocks\n", + s->fast_empty_hits, s->empty_sbs); + dev_info(w->dev, "SFTL slot0 read settled %u non-empty blocks\n", + s->fast_slot0_hits); + dev_info(w->dev, + "SFTL batch prefetch served %u page-0 reads in %u kicks (%u per kick)\n", + s->pf_hits, s->pf_kicks, + s->pf_kicks ? s->pf_hits / s->pf_kicks : 0); + + if (meta0_hist) { + char hb[192]; + unsigned int t, hn = 0; + + for (t = 0; t < 256; t++) { + if (!meta0_hist[t] || hn + 16 >= sizeof(hb)) + continue; + hn += scnprintf(hb + hn, sizeof(hb) - hn, "%02x:%u ", + t, meta0_hist[t]); + } + dev_info(w->dev, + "SFTL meta0 types %s(cxt=0x%02x btoc=0x%02x data=0x%02x)\n", + hb, WHIMORY_META_TYPE_SFTL_CXT, WHIMORY_META_TYPE_BTOC, + WHIMORY_META_TYPE_DATA); + kfree(meta0_hist); + meta0_hist = NULL; + } + + /* + * What page 127 actually holds, and how the superblocks sit either + * side of the checkpoint. + * + * Both were guesses until now. open=1619 against closed=563 is a + * strange shape for a mostly-static volume and says either that a + * great many blocks really are mid-write, or that page 127 is not + * where this geometry keeps its BTOC -- the type histogram tells + * those apart. The weave split says how much of the volume the + * checkpoint genuinely covers, which is the number the diff replay + * is supposed to act on. + */ + if (meta127_hist) { + char hb[192]; + unsigned int t, hn = 0; + + for (t = 0; t < 256; t++) { + if (!meta127_hist[t] || hn + 16 >= sizeof(hb)) + continue; + hn += scnprintf(hb + hn, sizeof(hb) - hn, "%02x:%u ", + t, meta127_hist[t]); + } + dev_info(w->dev, "SFTL meta127 types %s(page %u)\n", + hb, WHIMORY_BTOC_PAGE); + kfree(meta127_hist); + meta127_hist = NULL; + } + + kvfree(plane_m0); + kvfree(plane_mN); + + whimory_cxt_index_build(w, nsb); + /* + * The CXT is the FTL own checkpoint: it rebuilds the bulk of the map + * from a handful of pages instead of every open superblock. Replay + * below then adopts only what is newer than its base weave. + */ + ret = use_cxt ? whimory_cxt_fast_load(w) : -ENOENT; + if (ret && use_cxt) { + /* + * Do not quietly become the full scan. + * + * When the checkpoint does not load, s->cxt_loaded stays + * false, and every skip test in the replay below is guarded + * on it -- so nothing is skipped and all 7623 superblocks are + * replayed. That is the 500-second path, and it used to be + * announced with a dev_info in the middle of a boot log + * nobody reads at the time. The visible symptom was a device + * that "sometimes takes eight minutes to mount", with the one + * line explaining it eight thousand lines up. + * + * The fast path is the supported path. If it cannot run, say + * so at error level and stop, so the failure is a failure + * rather than a stall. require_cxt=0 restores the old + * behaviour for a deliberate comparison against the + * brute-force map, and use_cxt=0 still asks for that map + * outright -- neither is something a boot should reach by + * accident. + */ + if (require_cxt) { + dev_err(w->dev, + "CXT fast path failed (%d) and require_cxt is set: refusing to full-replay %u superblocks. Set require_cxt=0 (or use_cxt=0) to rebuild from media.\n", + ret, nsb); + whimory_set_status(w, "cxt load failed (%d)", ret); + w->l2v_defer_pack = false; + return ret; + } + dev_warn(w->dev, + "CXT seed failed %d; falling back to full replay of %u superblocks -- this is the slow path\n", + ret, nsb); + } + ret = 0; + if (s->cxt_loaded) + dev_info(w->dev, "s_cxt_load OK bases=%u weave=%llu mapped=%u\n", + w->n_cxt, w->cxt_base_weave, s->range_nodes); + + for (i = 0; i < nsb; i++) { + struct whimory_sb *sb = &s->sbs[i]; + u32 vblock = whimory_vfl_virt(w, sb->cau, sb->block); + + /* + * Replay is the long pole once scan_blocks is widened. Give RNDIS + * and the watchdog air on every superblock, not just inside + * whimory_rebuild_open_sb(). + */ + cond_resched(); + if (recover_yield_us && (i & 0x3) == 0) + usleep_range(recover_yield_us, recover_yield_us + 500); + + if (sb->kind == WHIMORY_SB_CXT) + continue; + + /* + * Skip what the checkpoint already covers -- but only when we + * can prove it does. + * + * This used to test sb->weave, which is page 0: the oldest + * page in the superblock. That is the wrong end. A block being + * appended to right up to the moment of the crash still has an + * old page 0, so every superblock on the volume tested older + * than the checkpoint and the diff replay skipped all 2182 of + * them -- sbs=0 replayed. The map then reflected the volume + * exactly as of the last checkpoint and nothing written after + * it, which is precisely the missing-recent-files symptom. + * + * The fix for that was to stop skipping open superblocks at + * all, on the reasoning that an open block is still being + * written and no page in it bounds the rest. On this hardware + * that reasoning does not hold, and the measurement is + * unambiguous: + * + * SFTL meta127 types 00:76 01:1615 1c:563 ff:6 + * + * 1615 of the 1619 "open" superblocks carry user data at page + * 127. They are full blocks that were never sealed with a + * BTOC, not blocks mid-write -- the rebuild proved it by + * reading 126 pages per superblock before finding a blank one. + * A full block has a real newest page, so page 127 bounds it + * exactly as it bounds a closed one. + * + * Leaving them unskipped was not merely slow. It read 205194 + * pages in 411 seconds, had 98.6 percent of the slots rejected + * as stale, and overrode 5726 CXT mappings with older data -- + * on a volume where not one superblock is newer than the + * checkpoint (weave newer=0 older=2182). A map that had been + * mounting stopped mounting. + * + * So the test is the bound, not the kind: skip only when the + * weave came from page 127. A superblock whose page 127 gave + * no usable weave has nothing but page 0 to offer, which is + * the wrong end again, and is replayed. + */ + if (!sb->weave_max) + s->weave_none++; + else if (sb->weave_max >= w->cxt_top_weave) + s->weave_newer++; + else + s->weave_older++; + + if (force_replay_vblock && vblock == force_replay_vblock) { + dev_info(w->dev, + "SFTL forcing vblk=%u through replay (kind=%u weave=%llu max=%llu p127=%u base=%llu)\n", + vblock, sb->kind, + (unsigned long long)sb->weave, + (unsigned long long)sb->weave_max, + sb->weave_max_p127, + (unsigned long long)w->cxt_base_weave); + } else if (use_cxt && s->cxt_loaded && cxt_fast && + sb->weave_max && + sb->weave_max < w->cxt_top_weave) { + /* + * Skip only what the checkpoint provably covers. + * + * The line is cxt_top_weave, not the base. Weaves + * between the two were consumed writing the + * checkpoint itself, so a superblock bounded there is + * still one the checkpoint describes; stock reserves + * exactly that span before letting any user write + * take a weave (s_cxt.c:81). Testing against the base + * put those superblocks on the post-checkpoint side + * and replayed them for nothing. + * + * A superblock with no usable weave at all is in + * neither set: nothing bounds it, so there is no + * evidence the checkpoint describes it. Those are + * replayed. On this volume that is the 78 blocks + * classify reports as unknown, which is cheap against + * the 7541 it does skip. + */ + s->diff_skipped_sbs++; + continue; + } else if (use_cxt && s->cxt_loaded && sb->weave_max_p127 && + sb->weave_max && sb->weave_max < w->cxt_top_weave) { + s->diff_skipped_sbs++; + continue; + } + if (use_cxt && s->cxt_loaded && !sb->weave_max_p127 && + sb->weave && sb->weave < w->cxt_top_weave) + s->diff_open_kept++; + s->diff_replayed_sbs++; + if (sb->kind == WHIMORY_SB_CLOSED) { + int ingested; + + ret = whimory_cs_read_page(w, sb->ce, sb->cau, sb->block, + WHIMORY_BTOC_PAGE, + s->btoc_page, + S5L8740_NAND_PAGE_SIZE, + meta127, sizeof(meta127)); + if (ret) + continue; + s->btoc_pages_read++; + if (s->btoc_verified < btoc_verify_sbs) { + s->btoc_verified++; + whimory_btoc_verify(w, sb, vblock, s->btoc_page, + S5L8740_NAND_PAGE_SIZE); + } + if (s->btoc_dumps_left && + (s->btoc_pages_read <= 2 || + whimory_btoc_looks_be_lpn(s->btoc_page))) { + whimory_dump_btoc_page(w, sb, vblock, + s->btoc_page, meta127); + s->btoc_dumps_left--; + } + s->claim_weave = sb->weave; + s->claim_source = 1; + ingested = whimory_ingest_btoc_page(w, sb->ce, sb->cau, + vblock, s->btoc_page, + S5L8740_NAND_PAGE_SIZE); + s->claim_weave = 0; + s->claim_source = 0; + if (ingested) { + s->btoc_pages_valid++; + } else { + /* + * No BTE array here -- rebuild the superblock + * from per-page meta instead of writing it off. + * + * 70 of these remain after the header-8 parse + * and they share one shape: header, aux=0x7fc, + * then zeros where the first record would be. + * The decomp of the writer (s_btoc sub_567E3C) + * says that cannot be a record. It writes + * v11[2]=lba and v11[3]=span with the caller + * asserting span != 0, and it keeps + * nextVbaOfs += span consistent with + * s_g_addr_to_vba(sb, nextVbaOfs) afterwards -- + * a zero span would break that invariant. aux + * is not a length either; at the call site it + * is a per-write tag lifted from the stream + * context, so 0x7fc there is not the array + * declaring its own size. And 0x7fc is 2044, + * which is a superblock's 2048 VBAs less one + * 4-slot group. + * + * So these read as sealed-block trailers: a + * fill count written when the block closed, + * terminator after it, no BTE array at all. + * That is not the same as an empty superblock, + * and the difference is about 2032 LBAs each. + * + * Rather than decide which, measure. The + * rebuild costs one read against an empty + * block -- page 0 comes back blank and it + * stops -- and about 127 against a full one. + * The counters below say which happened. + */ + int fb; + + s->btoc_fb_sbs++; + if (!btoc_meta_fallback) + goto btoc_done; + fb = s->open_pages_read; + ret = whimory_rebuild_open_sb(w, sb); + s->btoc_fb_pages += s->open_pages_read - fb; + if (ret > 0) + s->btoc_fb_hits += ret; + else if (ret < 0) + return ret; +btoc_done: + ; + } + if (ftl_progress_due(w)) + ftl_progress_set(w, "replay", i, nsb), + dev_info(w->dev, + "SFTL replay progress i=%u/%u closed_valid=%u " + "open_updates=%u unmap_calls=%u stale_rej=%u " + "mapped=%u ranges=%u confirm=%u budget_stop=%u\n", + i, nsb, s->btoc_pages_valid, + s->open_l2v_updates, s->l2v_unmap_calls, + s->stale_mapping_rejected, s->mapped_lbas, + s->range_nodes, s->btoc_confirm_pages, + s->range_budget_stop); + } else if (sb->kind == WHIMORY_SB_UNKNOWN) { + /* Same rebuild, counted apart so the two unknowns -- + * BTOCs with no array, and blocks with no recognised + * meta at all -- stay distinguishable. + */ + int fb = s->open_pages_read; + + s->unk_fb_sbs++; + ret = whimory_rebuild_open_sb(w, sb); + s->unk_fb_pages += s->open_pages_read - fb; + if (ret > 0) + s->unk_fb_hits += ret; + else if (ret < 0) + return ret; + } else if (sb->kind == WHIMORY_SB_OPEN) { + if (max_open_sbs && open_done >= max_open_sbs) { + /* Counted, not silent: a cap that drops open + * superblocks drops recent writes, and a map + * that is quietly short is worse than a slow + * one. + */ + s->open_truncated++; + continue; + } + ret = whimory_rebuild_open_sb(w, sb); + if (ret > 0) + open_done++; + else if (ret < 0) + return ret; + if (ftl_progress_due(w)) + ftl_progress_set(w, "open", open_done, s->open_sbs), + dev_info(w->dev, + "SFTL open progress done=%u/%u i=%u/%u " + "open_updates=%u ranges=%u mapped=%u\n", + open_done, s->open_sbs, i, nsb, + s->open_l2v_updates, s->range_nodes, + s->mapped_lbas); + } + } + + dev_info(w->dev, + "SFTL diff replay sbs=%u skipped_by_cxt=%u cxt_seeded=%u " + "open_kept=%u open_truncated=%u\n", + s->diff_replayed_sbs, s->diff_skipped_sbs, s->cxt_l2v_updates, + s->diff_open_kept, s->open_truncated); + dev_info(w->dev, + "SFTL weave vs cxt base=%llu top=%llu: newer=%u older=%u none=%u\n", + w->cxt_base_weave, w->cxt_top_weave, s->weave_newer, + s->weave_older, s->weave_none); + /* + * How deep the open rebuilds went. A genuinely open superblock stops + * at its first blank page, so pages/sb well under 127 says these + * really are partly written; pages/sb at 127 says they are full + * blocks that classify called open because page 127 held data rather + * than a BTOC, and the fix belongs in the classification instead. + */ + if (open_done) + dev_info(w->dev, + "SFTL open rebuild sbs=%u pages=%u (%u pages/sb)\n", + open_done, s->open_pages_read, + s->open_pages_read / open_done); + if (s->open_truncated) + dev_warn(w->dev, + "SFTL %u open superblocks dropped by max_open_sbs=%u -- " + "recent writes in them are NOT in the map\n", + s->open_truncated, max_open_sbs); + w->l2v_defer_pack = false; + ret = whimory_l2v_build_from_ranges(w); + if (ret) { + /* The interval map is the lookup authority, so a failed pack + * is survivable as long as it holds something. + */ + if (!s->range_nodes) + return ret; + dev_warn(w->dev, + "L2V pack %d; using interval map (%u ranges)\n", + ret, s->range_nodes); + ret = 0; + } else { + s->packed_ok = true; + } + w->l2v_ok = true; + /* + * A map that hit the node ceiling is short, and short is not the same + * as complete. Nothing used to say so: range_budget_stop was counted + * and printed among forty other statistics, so a truncated map and a + * whole one produced the same "recover OK". The reads that follow a + * truncation are unmapped LBAs scattered across the volume, which + * reads as failing flash rather than as a driver that stopped writing + * down where things are. + */ + if (w->sftl.range_budget_stop) + dev_err(w->dev, + "SFTL MAP IS SHORT: %u claims dropped at max_range_nodes=%u " + "(ranges=%u). Reads in the dropped regions will return " + "unmapped. Raise max_range_nodes and re-run recover.\n", + w->sftl.range_budget_stop, max_range_nodes, + w->sftl.range_nodes); + whimory_scan_closed_meta0(w, nsb); + whimory_print_recovery_stats(w); + return 0; +} + +static int whimory_sftl_alloc(struct whimory *w) +{ + struct whimory_sftl *s = &w->sftl; + u32 nsb; + + s->vbas_per_page = WHIMORY_VBAS_PER_PAGE; + s->pages_per_sb = WHIMORY_PAGES_PER_SB; + s->vbas_per_sb = WHIMORY_VBAS_PER_SB; + s->user_blocks = w->geom.user_blocks; + nsb = w->geom.num_ce * w->geom.num_cau * s->user_blocks; + if (w->vfl_ops && w->vfl_ops->get_param) { + u32 p = w->vfl_ops->get_param(w, WHIMORY_VFL_PARAM_NUM_SB); + + if (p) + nsb = p; + } + s->num_sb = nsb; + /* + * Size the L2V VBA field from the space whimory_pack_vba() actually + * builds, which since the move to the native layout is + * + * blocks_per_cau x (pages_per_sb x planes x vbas_per_page) + * + * These factors were num_sb x vbas_per_sb -- 7840 x 512 -- which is + * the retired bank-major space. That gave bits_vba = 22 and an + * invalid_vba sentinel of 4194303, against a native maximum VBA of + * 2088 x 2048 - 1 = 4276223. Every VBA in virtual blocks 2048..2087 + * therefore compared >= the sentinel and was read as "unmapped" in + * three places at once: the CXT parser scored it a hole, range_update + * treated it as a true unmap and erased, and read_lba returned + * -ENOENT. + * + * Nothing lands there today -- user data stops at user_blocks = 1960, + * so real VBAs top out around 4014079 -- but it is a three percent + * margin held by luck rather than by arithmetic. The tell is that the + * correct width, 23 bits, gives sentinel 0x7FFFFF, which is exactly + * WHIMORY_CXT_VBA_HOLE. The two were meant to agree. + * + * vbas_per_sb stays as the per-block VBA count that the BTOC parsers + * and the GC zone use; it is simply not the right factor for this. + */ + { + u32 planes = w->geom.num_ce * w->geom.num_cau; + + s->vba_factor_a = w->geom.blocks_per_cau ? + w->geom.blocks_per_cau : s->user_blocks; + s->vba_factor_b = s->pages_per_sb * (planes ? planes : 1) * + s->vbas_per_page; + } + s->nodepool_bytes = WHIMORY_MIN_NODEPOOL_BYTES; + + s->btoc_page = kvmalloc(S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + /* + * Cross-call NAND page cache; see the fields in struct whimory_sftl. + * Not fatal if it fails -- n31_vfl_read_vba() falls back to reading + * the page every time, which is exactly the old behaviour. + */ + s->page_cache = kvmalloc(S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + s->page_cache_valid = false; + /* + * 64 KiB + 256 B for the classify prefetch window. Optional in the + * same sense as the batch buffers in the NAND driver: without it + * whimory_prefetch_slot0() falls through to the single-page read. + */ + /* + * Legacy per-block prefetch, used only when the batched plane scan + * is unavailable. Sized independently of S5L8740_NAND_BATCH_MAX, + * which the meta-only scan raised to 256 records -- at a full 4 KiB + * slot per entry that would be a megabyte for a fallback path. + */ + s->pf_data = kvmalloc((size_t)WHIMORY_PF_SLOTS * + S5L8740_NAND_SLOT_DATA, GFP_KERNEL); + s->pf_meta = kvmalloc((size_t)WHIMORY_PF_SLOTS * + S5L8740_NAND_BATCH_META_SIZE, GFP_KERNEL); + if (!s->pf_data || !s->pf_meta) { + kvfree(s->pf_data); + kvfree(s->pf_meta); + s->pf_data = NULL; + s->pf_meta = NULL; + } + s->pf_valid = false; + s->pf_failed = false; + s->pf_checked = false; + /* + * 128 KiB + 512 B for the read-ahead window. Optional in the same + * sense as everything else on this path: without it + * whimory_window_read() returns -ENODEV and the single-page cache + * below answers, exactly as it did before. + */ + s->rc_data = kvmalloc((size_t)WHIMORY_RC_SLOTS * + S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + s->rc_meta = kvmalloc((size_t)WHIMORY_RC_SLOTS * + S5L8740_NAND_META_SIZE, GFP_KERNEL); + if (!s->rc_data || !s->rc_meta) { + kvfree(s->rc_data); + kvfree(s->rc_meta); + s->rc_data = NULL; + s->rc_meta = NULL; + } + s->rc_stage = kvmalloc((size_t)S5L8740_NAND_PAGE_BATCH_MAX * + S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + s->rc_stage_meta = kvmalloc((size_t)S5L8740_NAND_PAGE_BATCH_MAX * + S5L8740_NAND_META_SIZE, GFP_KERNEL); + if (!s->rc_stage || !s->rc_stage_meta) { + kvfree(s->rc_stage); + kvfree(s->rc_stage_meta); + s->rc_stage = NULL; + s->rc_stage_meta = NULL; + } + memset(s->rc_key, 0, sizeof(s->rc_key)); + s->rc_count = 0; + s->rc_fills = 0; + s->rc_hits = 0; + s->rc_misses = 0; + s->rc_fails = 0; + s->pf_count = 0; + s->pf_kicks = 0; + s->pf_hits = 0; + s->data_page = kvmalloc(S5L8740_NAND_PAGE_SIZE, GFP_KERNEL); + s->meta_page = kvmalloc(WHIMORY_META_SIZE * WHIMORY_VBAS_PER_PAGE * + (WHIMORY_DATA_PAGES_PER_SB + 1), GFP_KERNEL); + s->cs_page = kvmalloc(sizeof(*s->cs_page), GFP_KERNEL); + s->btoc_map = kvmalloc_array(WHIMORY_DATA_VBAS_PER_SB, + sizeof(*s->btoc_map), GFP_KERNEL); + s->sbs = kvcalloc(nsb, sizeof(*s->sbs), GFP_KERNEL); + /* + * Sized by blocks_per_cau, not user_blocks. + * + * A VBA may name any block the VFL can address, and CXT records + * legitimately do -- there are extents in virtual blocks 1987..1991 + * on this unit, above user_blocks = 1960. Classify only reaches + * user_blocks, so those entries stay zero and decode as all banks, + * which is what this driver did everywhere before the map existed. + */ + s->sb_bank_blocks = whimory_vba_blocks(w); + s->sb_bank_mask = kvcalloc(s->sb_bank_blocks, + sizeof(*s->sb_bank_mask), GFP_KERNEL); + if (!s->btoc_page || !s->data_page || !s->meta_page || !s->cs_page || + !s->sbs || !s->btoc_map || !s->sb_bank_mask) + return -ENOMEM; + + /* + * Pages needed to hold one block table of contents: + * + * ceil(16 * vbas_per_sb / page_bytes) + 1 + * + * 16 bytes per entry x 512 VBAs fits in a single 16 KiB NAND page, + * and the stock firmware reserves one more, giving 2. + */ + { + u32 page_bytes = w->geom.page_size ? + w->geom.page_size : S5L8740_NAND_PAGE_SIZE; + u32 i; + + s->max_pages_per_btoc = + (page_bytes + 16 * s->vbas_per_sb - 1) / page_bytes + 1; + if (!s->max_pages_per_btoc) + return -EINVAL; + for (i = 0; i < WHIMORY_BTOC_OPEN; i++) { + s->btoc_lba[i] = kvmalloc_array(s->vbas_per_sb, + sizeof(u32), + GFP_KERNEL); + if (!s->btoc_lba[i]) + return -ENOMEM; + memset(s->btoc_lba[i], 0xff, + s->vbas_per_sb * sizeof(u32)); + } + } + + /* + * The garbage-collection zone size doubles until it reaches at + * least 16 VBAs and must stay a multiple of vbas_per_page, so 16 is + * both the minimum and what N31 uses. Context load reads this many + * VBAs at a time into gc_data / gc_meta. + */ + s->gc_zone_size = WHIMORY_GC_ZONE_MIN; + if (s->gc_zone_size % s->vbas_per_page) + return -EINVAL; + s->gc_data = kvmalloc((size_t)WHIMORY_LBA_SIZE * s->gc_zone_size, + GFP_KERNEL); + s->gc_meta = kvmalloc((size_t)WHIMORY_META_SIZE * s->gc_zone_size, + GFP_KERNEL); + if (!s->gc_data || !s->gc_meta) + return -ENOMEM; + /* + *full-size FTL: num_superblocks * user VBAs per SB. + * BTOC page is not host LBA space (DATA_VBAS_PER_SB). + */ + { + u64 cap = (u64)nsb * WHIMORY_DATA_VBAS_PER_SB; + + w->total_4k_sectors = cap ? cap : NAND_FTL_DEFAULT_CAPACITY; + } + return 0; +} + +static int whimory_oracle_load(struct whimory *w) +{ + const struct firmware *gfw = NULL, *rfw = NULL, *nfw = NULL, *sfw = NULL; + int ret; + + ret = request_firmware(&sfw, WHIMORY_ORACLE_SIG, w->dev); + if (!ret && sfw && sfw->size >= WHIMORY_SIG_SIZE) { + ret = whimory_parse_signature(w, sfw->data); + if (ret) + dev_err(w->dev, "oracle signature invalid: %d\n", ret); + } + if (sfw) + release_firmware(sfw); + + ret = request_firmware(&gfw, WHIMORY_ORACLE_GLOBALS, w->dev); + if (ret) { + dev_err(w->dev, "oracle globals missing: %d\n", ret); + return ret; + } + if (gfw->size < 28) { + release_firmware(gfw); + return -EINVAL; + } + { + u32 num_roots = get_unaligned_le32(gfw->data + 0); + u32 nodepool = get_unaligned_le32(gfw->data + 4); + u32 max_lba; + + if (gfw->size >= 32) + max_lba = get_unaligned_le32(gfw->data + 28); + else + max_lba = (u32)w->total_4k_sectors; + if (!num_roots || !nodepool) + ret = -EINVAL; + else + ret = whimory_l2v_init(w, max_lba ? max_lba : + (u32)w->total_4k_sectors, + w->sftl.vba_factor_a, + w->sftl.vba_factor_b, + nodepool); + if (!ret && gfw->size >= 12) { + w->l2v.bits_vba = gfw->data[8]; + w->l2v.spanbits_vba = gfw->data[9]; + w->l2v.bits_nodeidx = gfw->data[10]; + w->l2v.spanbits_nodeidx = gfw->data[11]; + if (gfw->size >= 16) + w->l2v.invalid_vba = + get_unaligned_le32(gfw->data + 12); + w->l2v.sentinel_vba = w->l2v.invalid_vba; + if (num_roots && num_roots != w->l2v.num_roots) + dev_warn(w->dev, + "oracle num_roots=%u init=%u\n", + num_roots, w->l2v.num_roots); + } + } + release_firmware(gfw); + if (ret) + return ret; + + ret = request_firmware(&rfw, WHIMORY_ORACLE_ROOT, w->dev); + if (ret) + return ret; + if (rfw->size < WHIMORY_L2V_ROOT_REC_SIZE * w->l2v.num_roots) { + release_firmware(rfw); + return -EINVAL; + } + memcpy(w->l2v.root, rfw->data, + WHIMORY_L2V_ROOT_REC_SIZE * w->l2v.num_roots); + release_firmware(rfw); + + ret = request_firmware(&nfw, WHIMORY_ORACLE_NODES, w->dev); + if (ret) + return ret; + if (nfw->size < w->l2v.nodepool_bytes) { + release_firmware(nfw); + return -EINVAL; + } + memcpy(w->l2v.nodes, nfw->data, w->l2v.nodepool_bytes); + release_firmware(nfw); + + w->oracle_used = true; + w->l2v_ok = true; + whimory_l2v_find_frag(w); + dev_info(w->dev, + "L2V oracle loaded roots=%u nodes=0x%x frag=%u/%u\n", + w->l2v.num_roots, w->l2v.nodepool_bytes, + w->l2v.frag_count, w->l2v.frag_max); + return 0; +} + +static int n31_sftl_init(struct whimory *w) +{ + if (!w->vfl_ok) + return -ENODEV; + w->sftl.vbas_per_page = WHIMORY_VBAS_PER_PAGE; + w->sftl.pages_per_sb = WHIMORY_PAGES_PER_SB; + w->sftl.vbas_per_sb = WHIMORY_VBAS_PER_SB; + w->sftl.user_blocks = w->geom.user_blocks; + if (!w->sftl.user_blocks || !w->sftl.vbas_per_sb) + return -EINVAL; + return 0; +} + +static int whimory_l2v_selftest(struct whimory *w) +{ + u32 vba = ~0u, span = 0; + int fail = 0; + + if (whimory_l2v_update(w, 0, 1, 100) || + whimory_l2v_search(w, 0, &vba, &span) || vba != 100) + fail++; + if (whimory_l2v_update(w, 1, 10, 101) || + whimory_l2v_search(w, 5, &vba, &span) || vba != 105) + fail++; + if (whimory_l2v_update(w, 0x7fff, 4, 200) || + whimory_l2v_search(w, 0x7fff, &vba, &span) || vba != 200) + fail++; + if (whimory_l2v_search(w, 0x8000, &vba, &span) || vba != 201) + fail++; + whimory_range_free(w); + if (w->l2v.root && w->l2v.num_roots) + memset(w->l2v.root, 0xff, + WHIMORY_L2V_ROOT_REC_SIZE * w->l2v.num_roots); + whimory_l2v_mem_reset(&w->l2v); + w->sftl.l2v_update_calls = 0; + w->sftl.l2v_unmap_calls = 0; + w->sftl.l2v_repack_roots = 0; + dev_info(w->dev, "L2V_SELFTEST %s\n", fail ? "FAIL" : "OK"); + return fail ? -EINVAL : 0; +} + +static int n31_sftl_open(struct whimory *w) +{ + int ret; + int sess; + + /* + * OSOS FTL_Open:BTOC (6 slots / 2 open LBA maps), + *GC zone,block tables,SB + * state, nodepool ≥ 0x80000,L2V_Init, then s_boot. + */ + ret = whimory_sftl_alloc(w); + if (ret) + return ret; + + if (import_l2v_oracle) { + ret = whimory_oracle_load(w); + if (ret) { + dev_err(w->dev, "L2V oracle load failed: %d\n", ret); + return ret; + } + return 0; + } + + ret = whimory_l2v_init(w, (u32)w->total_4k_sectors, + w->sftl.vba_factor_a, w->sftl.vba_factor_b, + w->sftl.nodepool_bytes); + if (ret) + return ret; + whimory_l2v_selftest(w); + + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + dev_warn(w->dev, "SFTL recover: DMA session %d\n", sess); + /* Continue — cs_phys_read may still one-shot arm. */ + } + ret = whimory_sftl_recover_l2v_from_media(w); + if (sess == 0) + s5l8740_nand_dma_session_end(); + if (ret) + return ret; + return 0; +} + +static const struct whimory_ftl_ops n31_sftl_ops = { + .major = 0, + .minor = n31_sftl_minor, + .init = n31_sftl_init, + .open = n31_sftl_open, + .read_lba = n31_sftl_read_lba, +}; + +static int whimory_select_ops(struct whimory *w) +{ + /* + * OSOS dispatches VFL/FTL by signature major through a table that + * is not named in the static dump. N31 media is PPN VFL + SFTL; + * those are the only ops this module implements. Log the majors + * from the signature (when present) and bind the N31 ops. + */ + w->vfl_ops = &n31_vfl_ops; + w->ftl = &n31_sftl_ops; + if (w->sig_ok) { + dev_info(w->dev, + "VFL_SELECT major=%u minor=%u arg=%u\n", + w->sig.vfl_major, w->sig.vfl_minor, + w->sig.flags_or_open); + dev_info(w->dev, + "FTL_SELECT major=%u minor=%u\n", + w->sig.ftl_major, w->sig.ftl_minor); + dev_info(w->dev, + "ops bound fpart=%u.%u vfl=%u.%u ftl=%u.%u\n", + w->sig.fpart_major, w->sig.fpart_minor, + w->sig.vfl_major, w->sig.vfl_minor, + w->sig.ftl_major, w->sig.ftl_minor); + } else { + dev_warn(w->dev, + "VFL_SELECT/FTL_SELECT skipped: sig=0, binding N31 PPN+SFTL fallback\n"); + } + return 0; +} + +static int whimory_ftl_open(struct whimory *w) +{ + int ret; + + ret = w->ftl->init(w); + if (ret) { + dev_err(w->dev, "FTL_Init failed: %d\n", ret); + return ret; + } + ret = w->ftl->open(w); + if (ret) { + dev_err(w->dev, "FTL_Open failed: %d\n", ret); + return ret; + } + w->ftl_ok = true; + dev_info(w->dev, "FTL_Open OK\n"); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Read path */ +/* ------------------------------------------------------------------ */ + +/* + * Explain a bad read from the live map. + * + * The first attempt at this read the CXT extent table, which is freed after + * seeding -- it is about 12 MiB on a 55 MiB device -- so it printed nothing + * at all. The interval map holds the same information and is still there. + * + * The question is what kind of wrong the mapping is, and the deltas already + * hint at it. One of them is exactly -16: + * + * want=0x89af4 meta=0x89ae4 delta -16 + * + * 16 is one page of VBAs -- planes * vbas_per_page -- which is the quantum + * you get wrong if the two VBA spaces disagree about a superblock stride. + * Replay builds VBAs with whimory_pack_vba over pages_per_sb = 128, while + * CXT VBAs come from the FTL. If the FTL strides over the 127 data pages + * instead, the two differ by one page per superblock and pack/unpack cancel + * it everywhere except where a CXT mapping meets a replayed one. + * + * So: look the returned LBA up as well. delta_vba tells them apart. + * + * delta_vba == delta_lba -> the run is intact and sitting at the wrong + * place; a placement error + * delta_vba == 0 -> two LBAs claim one VBA; the map is + * double-mapped and one writer overwrote the + * other + * otherwise -> the run itself is malformed + */ +static void whimory_explain_bad_map(struct whimory *w, u32 lba, u32 meta_lba, + u32 vba, u32 span, const struct whimory_meta *m) +{ + unsigned int ce, cau, vblock, page, slot; + char dw[64]; + u32 vba2 = 0, span2 = 0; + int r2; + + if (w->sftl.bad_map_logged >= 8) + return; + w->sftl.bad_map_logged++; + + whimory_vba_describe(w, vba, dw, sizeof(dw)); + r2 = whimory_l2v_search(w, meta_lba, &vba2, &span2); + + if (r2) { + dev_err(w->dev, + " lba=%u -> vba=%u span=%u (%s); meta lba=%u is NOT mapped (%d), delta_lba=%d\n", + lba, vba, span, dw, meta_lba, r2, + (int)meta_lba - (int)lba); + return; + } + + { + struct whimory_range *r = whimory_range_find(&w->ranges, lba); + + dev_err(w->dev, + " lba=%u -> vba=%u span=%u (%s) src=%s weave=%llu\n", + lba, vba, span, dw, + !r ? "?" : + r->src == 1 ? "BTOC" : + r->src == 2 ? "open" : + r->src == 3 ? "CXT" : + r->src == 4 ? "LIST" : "seed", + r ? (unsigned long long)r->weave : 0ULL); + } + whimory_vba_describe(w, vba2, dw, sizeof(dw)); + dev_err(w->dev, + " meta lba=%u -> vba=%u span=%u (%s) | delta_lba=%d delta_vba=%d%s\n", + meta_lba, vba2, span2, dw, + (int)meta_lba - (int)lba, (int)vba - (int)vba2, + ((int)vba - (int)vba2) == 0 ? " DOUBLE-MAPPED" : + (((int)meta_lba - (int)lba) == ((int)vba - (int)vba2) ? + " run intact, misplaced" : " run malformed")); + + if (!whimory_unpack_vba(w, vba, &ce, &cau, &vblock, &page, &slot)) + dev_err(w->dev, + " read from ce%u/cau%u/vblk%u/pg%u/slot%u\n", + ce, cau, vblock, page, slot); + + /* + * The one number that decides whether the skip rule is at fault. + * + * The mapping came from the CXT and the CXT was parsed correctly, so + * the checkpoint is describing a page that now holds something else: + * the FTL moved data there after the checkpoint was taken. That is + * only possible if the block was not covered by the skip, and the + * skip uses page 127 as the block's upper bound. + * + * If this page's weave is newer than cxt_base_weave, page 127 does + * not bound this block -- the block was appended to after its BTOC + * was written -- and the diff replay skipped a superblock it should + * have replayed. If it is older, the checkpoint and the NAND + * genuinely disagree and the fault is elsewhere. + */ + if (m) { + u64 pw = whimory_weave48((const u8 *)m); + + dev_err(w->dev, + " page weave=%llu vs cxt base=%llu -- %s\n", + (unsigned long long)pw, + (unsigned long long)w->cxt_base_weave, + pw > w->cxt_base_weave ? + "NEWER: page 127 does not bound this block, the skip was wrong" : + "older: written before the checkpoint"); + } + + /* + * And what is actually at the VBA the map gave the returned LBA. + * + * The two extents in the failing case are exactly adjacent in VBA + * space -- 737713 + 440 = 738153 -- and both sit in vblock 360, so + * the runs are placed contiguously and only their LBA labels are in + * question. One read settles which way round they belong: if the page + * at vba2 holds the LBA we originally wanted, the two runs simply + * have each other's labels, and the fault is in how parse_tree pairs + * a run with its starting LBA rather than in the VBA arithmetic. + * + * It costs a NAND read on a path that has already failed. + */ + { + struct whimory_meta m2; + /* + * A buffer of its own. + * + * This used to hand w->sftl.data_page to read_vba as the + * destination -- and read_vba uses that same buffer as its + * page scratch, so the copy out of it was self-overlapping + * and the scratch came back holding a slice of itself. Only + * an explainer, but an explainer that corrupts the buffer the + * next real read will use is worse than no explainer. + */ + u8 *tmp = kmalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + + if (tmp && w->vfl_ops && w->vfl_ops->read_vba && + !w->vfl_ops->read_vba(w, vba2, 1, tmp, &m2)) + dev_err(w->dev, + " vba=%u actually holds lba=%u type=%02x%s\n", + vba2, le32_to_cpu(m2.lba), m2.type, + le32_to_cpu(m2.lba) == lba ? + " <-- the LBA we wanted: the two runs have swapped labels" : ""); + + /* + * The same page on the other plane convention. + * + * A plane index packs a (ce, cau) pair, and with two of each + * there are two ways round: ce * num_cau + cau, which is what + * this driver uses, or cau * num_ce + ce. They agree on planes + * 0 and 3 and swap 1 and 2 -- and both of the failing reads + * land on plane 2 and plane 1 respectively, which is exactly + * the half a wrong convention would break. + * + * Reading the same vblock/page/slot with ce and cau exchanged + * settles it in one access: if that page holds the LBA we + * asked for, the convention is backwards. + */ + if (tmp && w->vfl_ops && w->vfl_ops->read_vba && + !whimory_unpack_vba(w, vba, &ce, &cau, &vblock, &page, + &slot) && + w->geom.num_ce == w->geom.num_cau) { + u32 swapped = whimory_pack_vba(w, cau, ce, vblock, + page, slot); + + if (swapped != vba && + !w->vfl_ops->read_vba(w, swapped, 1, tmp, &m2)) + dev_err(w->dev, + " plane-swapped vba=%u (ce%u/cau%u) holds lba=%u type=%02x%s\n", + swapped, cau, ce, + le32_to_cpu(m2.lba), m2.type, + le32_to_cpu(m2.lba) == lba ? + " <-- MATCH: the ce/cau plane order is reversed" : ""); + } + kfree(tmp); + } +} + +static int whimory_validate_meta(struct whimory *w, + const struct whimory_meta *m, + u32 expected_lba) +{ + u32 meta_lba = le32_to_cpu(m->lba); + + if (!whimory_meta_is_user_data(m)) { + dev_err(w->dev, + "sftl non-data meta want=0x%x type=%02x flags=%02x lba=0x%x\n", + expected_lba, m->type, m->flags, meta_lba); + whimory_explain_bad_map(w, expected_lba, meta_lba, + w->bad_vba, w->bad_span, m); + return -EIO; + } + + if (meta_lba != expected_lba) { + dev_err(w->dev, + "sftl lba mismatch want=0x%x meta=0x%x type=%02x flags=%02x\n", + expected_lba, meta_lba, m->type, m->flags); + whimory_explain_bad_map(w, expected_lba, meta_lba, + w->bad_vba, w->bad_span, m); + return -EIO; + } + + if (m->flags & 0x02) { + dev_err(w->dev, + "sftl uECC flag lba=0x%x type=%02x flags=%02x\n", + expected_lba, m->type, m->flags); + return -EIO; + } + + return 0; +} + +static int n31_sftl_read_lba(struct whimory *w, u32 lba, void *buf, + bool allow_blank) +{ + struct whimory_meta meta; + u32 vba = 0, span = 0; + u32 ce, cau, vblock, page, slot; + int ret; + + if (!w->l2v_ok) + return -ENODEV; + ret = whimory_l2v_search(w, lba, &vba, &span); + if (ret) { + if (allow_blank && ret == -ENOENT) { + memset(buf, 0xff, WHIMORY_LBA_SIZE); + return 0; + } + return ret; + } + if (lba == 0) + w->lba0_vba = vba; + if (vba >= w->l2v.invalid_vba) { + if (!allow_blank) + return -ENOENT; + memset(buf, 0xff, WHIMORY_LBA_SIZE); + return 0; + } + if (ftl_diag && (whimory_audit_fmss_lba(lba) || lba < 16)) { + if (!whimory_unpack_vba(w, vba, &ce, &cau, &vblock, &page, + &slot)) + dev_info(w->dev, + "L2V lookup fmss_lba=%u -> vba=%u span=%u " + "ppn=ce%u/cau%u/vblk%u/pg%u/slot%u\n", + lba, vba, span, ce, cau, vblock, page, slot); + else + dev_info(w->dev, + "L2V lookup fmss_lba=%u -> vba=%u span=%u\n", + lba, vba, span); + /* Sibling VBA_DIAG only for BPB candidates (avoid spam). */ + if (lba == 49279u || lba == 49285u || lba == 49216u || + lba < 4) + whimory_dump_vba_page(w, vba, lba); + } + ret = w->vfl_ops->read_vba(w, vba, 1, buf, &meta); + if (ret) { + /* + * A transport failure used to leave here without a word, so a + * uECC or a disarmed CS looked exactly like a map hole from + * the filesystem's side -- both arrive at vfat as a bare EIO. + * They need entirely different fixes, so say which one it is. + */ + dev_err_ratelimited(w->dev, + "sftl read failed lba=%u vba=%u: %d\n", + lba, vba, ret); + return ret; + } + /* What the map actually answered, for the explainer below. */ + w->bad_vba = vba; + w->bad_span = span; + ret = whimory_validate_meta(w, &meta, lba); + if (!ret) + dev_dbg(w->dev, + "meta OK fmss_lba=%u vba=%u type=%02x meta_lba=%u\n", + lba, vba, meta.type, le32_to_cpu(meta.lba)); + return ret; +} + +/* + * Both of these used to call n31_sftl_read_lba() directly, which walks the + * range tree and reads through w->sftl.cs_page / data_page -- shared + * scratch, under no lock and with CS unarmed. That was survivable only + * because the hook is installed by whimory_register_disk(), which cannot + * run while meta_dma_read=0. It is still reachable from + * nand_ftl_read_sector(), and apple-grape symbol_get()s that. + * + * Route both through whimory_read_fmss_lba(), which takes tree_lock and + * opens a DMA session, so the entry point does not decide whether the read + * path is safe. + */ +static int whimory_read_lba_4k(struct whimory *w, u32 lba, void *buf) +{ + int ret = whimory_read_fmss_lba(lba, buf); + + /* The block layer wants a hole to read as erased, not to fail. */ + if (ret == -ENOENT) { + memset(buf, 0xff, WHIMORY_LBA_SIZE); + return 0; + } + return ret; +} + +static int whimory_ftl_read_hook(u64 lba, void *buf) +{ + struct whimory *w = whimory_dev; + + if (!w || !w->l2v_ok) + return -ENODEV; + if (lba >= w->total_4k_sectors) + return -ERANGE; + return whimory_read_fmss_lba((u32)lba, buf); +} + +static int whimory_check_lba0(struct whimory *w) +{ + u8 *buf; + int ret; + u16 bps; + u32 total32, rootclus, serial; + + buf = kzalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = n31_sftl_read_lba(w, 0, buf, false); + if (ret) { + dev_err(w->dev, "LBA0 read failed: %d\n", ret); + goto out; + } + + dev_info(w->dev, "LBA0 first32=%32ph\n", buf); + dev_info(w->dev, "LBA0 next32=%32ph\n", buf + 32); + + bps = get_unaligned_le16(buf + 11); + total32 = get_unaligned_le32(buf + 32); + rootclus = get_unaligned_le32(buf + 44); + serial = get_unaligned_le32(buf + 67); + dev_info(w->dev, + "BPB bytes_per_sector=%u sectors_per_cluster=%u total32=%u rootclus=%u serial=%08x label=%.11s\n", + bps, buf[13], total32, rootclus, serial, buf + 71); + + if (bps != 4096) { + ret = -EINVAL; + goto out; + } + if (buf[0] != 0xeb && buf[0] != 0xe9) { + ret = -EINVAL; + goto out; + } + dev_info(w->dev, "meta OK lba=0\n"); + w->lba0_ok = true; + if (total32) + w->total_4k_sectors = total32; +out: + kfree(buf); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* Block device */ +/* ------------------------------------------------------------------ */ + +static void whimory_submit_bio_range(struct bio *bio, u64 start_4k, + u64 n_4k) +{ + struct whimory *w = whimory_dev; + struct bvec_iter iter; + struct bio_vec bvec; + sector_t sector = bio->bi_iter.bi_sector; + int ret = 0; + + if (!w || !w->lba0_ok) { + bio_io_error(bio); + return; + } + if (op_is_write(bio_op(bio))) { + bio_io_error(bio); + return; + } + + bio_for_each_segment(bvec, bio, iter) { + u8 *dst = kmap_local_page(bvec.bv_page) + bvec.bv_offset; + unsigned int done_bytes = 0; + + while (done_bytes < bvec.bv_len) { + u32 lba4k = (u32)(start_4k + (sector >> 3)); + unsigned int off = (sector & 7) * 512; + unsigned int n = min_t(unsigned int, + bvec.bv_len - done_bytes, + 4096 - off); + + if ((u64)lba4k >= start_4k + n_4k || + lba4k >= w->total_4k_sectors) { + ret = -EIO; + kunmap_local(dst); + goto done; + } + mutex_lock(&w->bounce_lock); + ret = whimory_read_lba_4k(w, lba4k, w->bounce); + if (!ret) + memcpy(dst + done_bytes, w->bounce + off, n); + mutex_unlock(&w->bounce_lock); + if (ret) { + kunmap_local(dst); + goto done; + } + done_bytes += n; + sector += n >> 9; + } + kunmap_local(dst); + } +done: + if (ret) + bio_io_error(bio); + else + bio_endio(bio); +} + +static void whimory_submit_bio(struct bio *bio) +{ + struct whimory *w = whimory_dev; + + whimory_submit_bio_range(bio, 0, w ? w->total_4k_sectors : 0); +} + +static void whimory_ipod_submit_bio(struct bio *bio) +{ + struct whimory *w = whimory_dev; + + whimory_submit_bio_range(bio, 0, w ? w->total_4k_sectors : 0); +} + +static const struct block_device_operations whimory_bd_ops = { + .owner = THIS_MODULE, + .submit_bio = whimory_submit_bio, +}; + +static const struct block_device_operations whimory_ipod_ops = { + .owner = THIS_MODULE, + .submit_bio = whimory_ipod_submit_bio, +}; + +static struct gendisk *whimory_alloc_disk(struct whimory *w, const char *name, + const struct block_device_operations *ops) +{ + struct queue_limits lim = { + .logical_block_size = WHIMORY_LBA_SIZE, + .physical_block_size = WHIMORY_LBA_SIZE, + }; + struct gendisk *gd; + int ret; + + gd = blk_alloc_disk(&lim, NUMA_NO_NODE); + if (IS_ERR(gd)) + return gd; + gd->first_minor = 0; + gd->flags = GENHD_FL_NO_PART; + gd->fops = ops; + gd->private_data = w; + snprintf(gd->disk_name, DISK_NAME_LEN, "%s", name); + set_capacity(gd, w->total_4k_sectors * (WHIMORY_LBA_SIZE / 512)); + set_disk_ro(gd, 1); + ret = add_disk(gd); + if (ret) { + put_disk(gd); + return ERR_PTR(ret); + } + return gd; +} + +static int whimory_register_disk(struct whimory *w) +{ + struct gendisk *gd; + + if (!w->sig_ok) { + dev_warn(w->dev, + "sig=0: native PASS requires FPart 0xC101 xrmw signature\n"); + if (!allow_sigless_debug) + return -ENODEV; + } + if (!w->vfl_ok || !w->ftl_ok || !w->l2v_ok || !w->lba0_ok) + return -ENODEV; + + gd = whimory_alloc_disk(w, FTL_DISK_NAME, &whimory_bd_ops); + if (IS_ERR(gd)) + return PTR_ERR(gd); + w->disk = gd; + gd = whimory_alloc_disk(w, FTL_IPOD_NAME, &whimory_ipod_ops); + if (!IS_ERR(gd)) + w->ipod_disk = gd; + s5l8740_nand_register_ftl_read(whimory_ftl_read_hook); + dev_info(w->dev, + "/dev/%s registered read-only (%llu x %uB)\n", + FTL_DISK_NAME, w->total_4k_sectors, WHIMORY_LBA_SIZE); + return 0; +} + +static void whimory_unregister_disk(struct whimory *w) +{ + s5l8740_nand_register_ftl_read(NULL); + if (w->ipod_disk) { + del_gendisk(w->ipod_disk); + put_disk(w->ipod_disk); + w->ipod_disk = NULL; + } + if (w->disk) { + del_gendisk(w->disk); + put_disk(w->disk); + w->disk = NULL; + } +} + +static ssize_t whimory_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct whimory *w = whimory_dev; + int meta_ok = s5l8740_nand_meta_transport_ok(); + + if (!w) + return sysfs_emit(buf, "no device\n"); + return sysfs_emit(buf, + "fil=%d sig=%d vfl=%d ftl=%d l2v=%d lba0=%d oracle=%d\n" + "meta_transport=%s cs_dma_safe=%d pio_meta_trusted=0 " + "disk_gate=%s\n" + "mapped_roots=%u mapped_lbas=%u btoc_sbs=%u open_sbs=%u cxt_sbs=%u empty=%u recs=%u cxt_loaded=%d packed=%d\n" + "lba0_vba=%u cap=%llu vbas_per_sb=%u hole=%u list=%u\n" + "spare_applied=%u bitmap=%u frag=%u/%u gc_zone=%u btoc_pages=%u updates=%u gen=%u free=%u list_unmapped=%u\n" + "search_cache hits=%u misses=%u recovery=%s\n%s\n", + w->fil_ok, w->sig_ok, w->vfl_ok, w->ftl_ok, + w->l2v_ok, w->lba0_ok, w->oracle_used, + meta_ok ? "enabled" : "disabled", + meta_ok ? 1 : 0, + w->disk ? "registered" : + (meta_ok ? "blocked_open" : "blocked_cs_phys_only"), + w->sftl.mapped_roots, w->sftl.mapped_lbas, + w->sftl.btoc_sbs, w->sftl.open_sbs, w->sftl.cxt_sbs, + w->sftl.empty_sbs, w->sftl.btoc_recs, + w->sftl.cxt_loaded, w->sftl.packed_ok, + w->lba0_vba, w->total_4k_sectors, w->sftl.vbas_per_sb, + w->sftl.token_hole, w->sftl.token_list, + w->vfl.spare_applied, w->vfl.bitmap_loaded, + w->l2v.frag_count, w->l2v.frag_max, + w->sftl.gc_zone_size, w->sftl.max_pages_per_btoc, + w->l2v.updates, w->l2v.gen, w->l2v.free_count, + w->sftl.token_list_applied, + w->sftl.search_cache_hits, + w->sftl.search_cache_misses, + whimory_recovery_state_name(), + w->status); +} +static DEVICE_ATTR_RO(whimory_status); + +/* + * One field per line, no prose, so a shell loop or a UI can read this + * without parsing the human log. percent is -1 rather than 0 while the + * total is unknown, so a bar can show indeterminate instead of snapping + * back to the left every time a phase begins. + */ +static ssize_t recover_progress_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct whimory *w = whimory_dev; + int pct = -1; + + if (!w) + return sysfs_emit(buf, "state=absent\n"); + if (w->prog_total) + /* 32-bit: cur is a block/SB index, so *100 cannot overflow, and + * a u64 divide would need div_u64 on arm anyway. */ + pct = min_t(unsigned int, 100, + w->prog_cur / (w->prog_total / 100 + 1)); + return sysfs_emit(buf, + "state=%s\nphase=%s\ncur=%u\ntotal=%u\n" + "percent=%d\nmapped_lbas=%u\ndisk=%s\n", + whimory_recovery_state_name(), + w->prog_phase ? w->prog_phase : "idle", + w->prog_cur, w->prog_total, pct, + w->sftl.mapped_lbas, + w->disk ? "registered" : "none"); +} +static DEVICE_ATTR_RO(recover_progress); + +/* + * The decoded identity, and an index of every record the scan located. + * + * The decode understands eleven tags; the section holds more than that. + * Listing the candidates with their offsets and lengths makes the rest + * reachable, and pairing this with syscfg_raw is enough to identify a + * record without reflashing anything. "known" marks a candidate the + * decode already consumes, so an unknown one is a genuine find rather + * than a duplicate of a field printed above it. + */ +static ssize_t syscfg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct whimory *w = whimory_dev; + unsigned int i; + int len = 0; + + if (!w) + return sysfs_emit(buf, "state=absent\n"); + if (!w->syscfg.valid && !w->syscfg.raw_len) + return sysfs_emit(buf, "state=unread\n"); + + len += sysfs_emit_at(buf, len, + "state=%s\nsection_bytes=%u\nentries=%u\n" + "records=%u\n", + w->syscfg.valid ? "valid" : "partial", + w->syscfg.raw_len, w->syscfg.entries, + w->syscfg.n_cand); + len += sysfs_emit_at(buf, len, + "serial=%s\nmodel=%s\nmlb=%s\nsw_ver=%s\n" + "cnt_b=%s\nmt_cl=%s\n", + w->syscfg.serial, w->syscfg.model, w->syscfg.mlb, + w->syscfg.sw_ver, w->syscfg.cnt_b, + w->syscfg.mt_cl); + len += sysfs_emit_at(buf, len, + "region=0x%08x\nhw_ver=0x%08x\nfw_id=0x%08x\n", + w->syscfg.region, w->syscfg.hw_ver, + w->syscfg.fw_id); + if (w->syscfg.mac_ok) + len += sysfs_emit_at(buf, len, "bt_mac=%pM\n", w->syscfg.mac); + len += sysfs_emit_at(buf, len, + "touch_cal_magic_off=%d\ntouch_cal_bytes=%u\n", + w->syscfg.touch_cal_magic_off, + w->syscfg.touch_cal_len); + + /* + * One record per line: index, tag, offset and length into + * syscfg_raw, and whether the decode above claims it. + */ + for (i = 0; i < w->syscfg.n_cand; i++) + len += sysfs_emit_at(buf, len, + "record%u=%s off=0x%04x len=%u known=%u\n", + i, w->syscfg.cand[i].tag, + w->syscfg.cand[i].off, + w->syscfg.cand[i].len, + w->syscfg.cand[i].known ? 1 : 0); + return len; +} +static DEVICE_ATTR_RO(syscfg); + +/* + * The SysCfg section verbatim, so a record this driver does not decode + * can still be extracted. Offsets here are the ones syscfg reports. + */ +static ssize_t syscfg_raw_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct whimory *w = whimory_dev; + + if (!w || !w->syscfg.raw || !w->syscfg.raw_len) + return -ENODEV; + if (off >= w->syscfg.raw_len) + return 0; + if (off + count > w->syscfg.raw_len) + count = w->syscfg.raw_len - off; + memcpy(buf, w->syscfg.raw + off, count); + return count; +} +static BIN_ATTR_RO(syscfg_raw, S5L8740_NAND_PAGE_SIZE); + +/* + * The touch calibration container, at its full length. Reads short when + * the section did not carry one, which is the case whenever + * touch_cal_bytes above is 0 -- the source is then the U-Boot region. + */ +static ssize_t touch_cal_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + const u8 *d = NULL; + size_t n = whimory_syscfg_touch_cal(&d); + + if (!n) + return -ENODEV; + if (off >= n) + return 0; + if (off + count > n) + count = n - off; + memcpy(buf, d + off, count); + return count; +} +static BIN_ATTR_RO(touch_cal, N31_TOUCH_CAL_LEN); + +static struct attribute *ftl_attrs[] = { + &dev_attr_whimory_status.attr, + &dev_attr_recover_progress.attr, + &dev_attr_syscfg.attr, + NULL, +}; + +static struct bin_attribute *ftl_bin_attrs[] = { + &bin_attr_syscfg_raw, + &bin_attr_touch_cal, + NULL, +}; +static const struct attribute_group ftl_attr_group = { + .attrs = ftl_attrs, + .bin_attrs = ftl_bin_attrs, +}; + +static void whimory_free(struct whimory *w) +{ + unsigned int cau; + + if (!w) + return; + whimory_unregister_disk(w); + whimory_range_free(w); + whimory_l2v_free(w); + w->syscfg.raw_len = 0; + w->syscfg.n_cand = 0; + kvfree(w->syscfg.raw); + w->syscfg.raw = NULL; + kvfree(w->sftl.btoc_page); + w->sftl.page_cache_valid = false; + kvfree(w->sftl.page_cache); + w->sftl.page_cache = NULL; + w->sftl.pf_valid = false; + kvfree(w->sftl.pf_data); + kvfree(w->sftl.pf_meta); + w->sftl.pf_data = NULL; + w->sftl.pf_meta = NULL; + w->sftl.rc_count = 0; + kvfree(w->sftl.rc_data); + kvfree(w->sftl.rc_meta); + kvfree(w->sftl.rc_stage); + kvfree(w->sftl.rc_stage_meta); + w->sftl.rc_data = NULL; + w->sftl.rc_meta = NULL; + w->sftl.rc_stage = NULL; + w->sftl.rc_stage_meta = NULL; + kvfree(w->sftl.data_page); + kvfree(w->sftl.meta_page); + kvfree(w->sftl.cs_page); + kvfree(w->sftl.btoc_map); + kvfree(w->cxt_ext); + kvfree(w->sftl.sbs); + kvfree(w->sftl.sb_bank_mask); + w->sftl.sb_bank_mask = NULL; + w->sftl.sb_bank_blocks = 0; + kvfree(w->sftl.gc_data); + kvfree(w->sftl.gc_meta); + kvfree(w->vfl.bank_mask); + for (cau = 0; cau < WHIMORY_BTOC_OPEN; cau++) + kvfree(w->sftl.btoc_lba[cau]); + for (cau = 0; cau < S5L8740_NAND_MAX_CAU; cau++) { + kvfree(w->vfl.remap[cau]); + kvfree(w->vfl.cxt_u16[cau]); + } + kfree(w->bounce); + kfree(w); +} + +static int whimory_open_stack(struct whimory *w) +{ + int ret; + + ret = whimory_fil_init(w); + if (ret) { + whimory_set_status(w, "FIL_Init failed %d", ret); + return ret; + } + + /* + * Without CS metadata DMA, classic Whimory open cannot validate + * META via page_read. Recover is still available via CS phys: + * echo 1 > .../ftl_sftl_recover + */ + if (!s5l8740_nand_meta_transport_ok()) { + whimory_set_status(w, + "CS metadata DMA disabled; " + "use ftl_sftl_recover (CS META path) " + "or meta_dma_read=1 dma_dry=0"); + pr_info("s5l8740-ftl: Whimory auto-open deferred " + "(meta transport off); run ftl_sftl_recover\n"); + return -EOPNOTSUPP; + } + + ret = whimory_read_signature(w); + if (ret) { + whimory_set_status(w, "signature failed %d", ret); + return ret; + } + ret = whimory_select_ops(w); + if (ret) + return ret; + ret = whimory_vfl_open(w); + if (ret) { + whimory_set_status(w, "VFL_Open failed %d", ret); + return ret; + } + ret = whimory_ftl_open(w); + if (ret) { + whimory_set_status(w, "FTL_Open failed %d", ret); + return ret; + } + ret = whimory_check_lba0(w); + if (ret) { + whimory_set_status(w, "LBA0 check failed %d", ret); + return ret; + } + ret = whimory_register_disk(w); + if (ret) { + whimory_set_status(w, "disk register failed %d", ret); + return ret; + } + whimory_set_status(w, "ready"); + return 0; +} + +bool whimory_l2v_ready(void) +{ + return whimory_dev && whimory_dev->l2v_ok; +} + +int whimory_read_fmss_lba(u32 lba, void *buf) +{ + struct whimory *w = whimory_dev; + int sess, ret; + + if (!w || !buf) + return -EINVAL; + if (!w->l2v_ok || !w->ftl || !w->ftl->read_lba) + return -ENODEV; + sess = s5l8740_nand_dma_session_begin(); + mutex_lock(&w->tree_lock); + ret = w->ftl->read_lba(w, lba, buf, false); + mutex_unlock(&w->tree_lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret; +} + +int whimory_range_walk(int (*fn)(u32 start, u32 len, u32 vba, u64 weave, + void *ctx), + void *ctx) +{ + struct whimory *w = whimory_dev; + struct rb_node *n; + struct whimory_range *snap; + unsigned int i, count = 0; + int ret = 0; + + if (!w || !fn) + return -EINVAL; + + mutex_lock(&w->tree_lock); + count = w->sftl.range_nodes; + if (!count) { + mutex_unlock(&w->tree_lock); + return 0; + } + snap = kvmalloc_array(count, sizeof(*snap), GFP_KERNEL); + if (!snap) { + mutex_unlock(&w->tree_lock); + return -ENOMEM; + } + i = 0; + for (n = rb_first(&w->ranges); n && i < count; n = rb_next(n)) { + struct whimory_range *r = rb_entry(n, struct whimory_range, rb); + + snap[i].start = r->start; + snap[i].len = r->len; + snap[i].vba = r->vba; + snap[i].weave = r->weave; + i++; + } + count = i; + mutex_unlock(&w->tree_lock); + + for (i = 0; i < count; i++) { + ret = fn(snap[i].start, snap[i].len, snap[i].vba, + snap[i].weave, ctx); + if (ret) + break; + } + kvfree(snap); + return ret; +} + +int whimory_l2v_search_phys(u32 lba, u8 *ce, u8 *cau, u16 *blk, u8 *page, + u8 *slot, u64 *weave) +{ + struct whimory *w = whimory_dev; + u32 vba = ~0u, span = 0, vce, vcau, vblock, vpage, vslot, pblock; + struct whimory_range *r; + int ret; + + if (!w || !w->l2v_ok) + return -ENODEV; + mutex_lock(&w->tree_lock); + ret = whimory_l2v_search(w, lba, &vba, &span); + if (ret || vba >= w->l2v.invalid_vba) { + mutex_unlock(&w->tree_lock); + return ret ? ret : -ENOENT; + } + r = whimory_range_find(&w->ranges, lba); + if (weave) + *weave = r ? r->weave : 0; + ret = whimory_unpack_vba(w, vba, &vce, &vcau, &vblock, &vpage, &vslot); + if (ret) { + mutex_unlock(&w->tree_lock); + return ret; + } + whimory_vfl_resolve(w, vblock, &vcau, &pblock); + mutex_unlock(&w->tree_lock); + if (ce) + *ce = (u8)vce; + if (cau) + *cau = (u8)vcau; + if (blk) + *blk = (u16)pblock; + if (page) + *page = (u8)vpage; + if (slot) + *slot = (u8)vslot; + return 0; +} + +static bool whimory_slot_has_needle(const u8 *slot, unsigned int len, + const char *needle) +{ + return !!strnstr((const char *)slot, needle, len); +} + +/* + * Physical string scanner — ignores L2V. Walks readable CS pages and prints + * hits with ce/cau/block/page/slot + meta_lba. Independent of mount. + */ +int whimory_phys_string_scan(unsigned int max_blocks) +{ + struct whimory *w = whimory_dev; + u8 *data; + u8 spare[S5L8740_NAND_META_SIZE]; + unsigned int ce, cau, b, pg, slot, nscan, hits = 0, pages = 0; + static const char *const needles[] = { + "iTunesDB", "F00", "F01", "F02", "iPod_Control", "Music", + "Apps", "NanoApps", ".mp3", ".m4a", "mp3", "m4a", + }; + int sess; + + if (!w || !w->sftl.data_page) + return -ENODEV; + nscan = max_blocks ? max_blocks : + (scan_blocks ? scan_blocks : w->sftl.user_blocks); + if (!nscan) + nscan = 256; + data = w->sftl.data_page; + sess = s5l8740_nand_dma_session_begin(); + dev_info(w->dev, + "PHYS_STRING_SCAN start blocks=%u (L2V ignored)\n", nscan); + for (ce = 0; ce < w->geom.num_ce; ce++) { + for (cau = 0; cau < w->geom.num_cau; cau++) { + for (b = 0; b < nscan; b++) { + for (pg = 0; pg < WHIMORY_DATA_PAGES_PER_SB; + pg++) { + int ret; + unsigned int ni; + + ret = whimory_cs_read_page(w, ce, cau, + b, pg, data, + S5L8740_NAND_PAGE_SIZE, spare, + sizeof(spare)); + if (ret) + break; + if (whimory_page_blank(data, 64) && + whimory_meta_erased(spare, 16)) + break; + pages++; + for (slot = 0; + slot < WHIMORY_VBAS_PER_PAGE; + slot++) { + const u8 *d = data + + slot * WHIMORY_LBA_SIZE; + const u8 *m = spare + + slot * WHIMORY_META_SIZE; + u32 meta_lba = + get_unaligned_le32(m + 8); + + for (ni = 0; ni < ARRAY_SIZE(needles); + ni++) { + if (!whimory_slot_has_needle( + d, + WHIMORY_LBA_SIZE, + needles[ni])) + continue; + hits++; + if (hits <= 64) + dev_info(w->dev, + "PHYS_STRING hit=%s " + "ce=%u cau=%u blk=%u " + "page=%u slot=%u " + "meta_lba=%u type=%02x\n", + needles[ni], + ce, cau, b, pg, + slot, meta_lba, + m[0]); + break; + } + } + if ((pages & 0x7f) == 0) + cond_resched(); + } + if ((b & 0x7f) == 0) + dev_info(w->dev, + "PHYS_STRING_SCAN progress " + "ce=%u cau=%u blk=%u/%u hits=%u\n", + ce, cau, b, nscan, hits); + } + } + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + dev_info(w->dev, + "PHYS_STRING_SCAN done pages=%u hits=%u\n", pages, hits); + return hits; +} + +static int whimory_sftl_recover_cs_locked(void) +{ + struct whimory *w = whimory_dev; + int ret, sess; + + if (!w) + return -ENODEV; + + ret = whimory_fil_init(w); + if (ret) { + whimory_set_status(w, "FIL_Init failed %d", ret); + return ret; + } + + ret = whimory_read_signature(w); + if (ret) { + dev_warn(w->dev, + "signature %d; CS recover continues (identity VFL)\n", + ret); + } + + ret = whimory_select_ops(w); + if (ret) + return ret; + + if (!w->vfl_ok) { + ret = whimory_vfl_open(w); + if (ret) { + whimory_set_status(w, "VFL_Open failed %d", ret); + return ret; + } + } + + if (!w->ftl_ok) { + ret = whimory_ftl_open(w); + if (ret) { + whimory_set_status(w, "FTL_Open/recover failed %d", + ret); + return ret; + } + } else { + /* Re-run recover on CS META (clear prior L2V). */ + whimory_range_free(w); + if (w->l2v.root && w->l2v.num_roots) + memset(w->l2v.root, 0xff, + WHIMORY_L2V_ROOT_REC_SIZE * w->l2v.num_roots); + whimory_l2v_mem_reset(&w->l2v); + w->l2v_ok = false; + w->sftl.cxt_loaded = false; + w->sftl.packed_ok = false; + w->n_cxt = 0; + w->cxt_base_weave = 0; + w->cxt_top_weave = 0; + w->cxt_save_num_sb = 0; + w->sftl.btoc_sbs = 0; + w->sftl.open_sbs = 0; + w->sftl.empty_sbs = 0; + w->sftl.cxt_sbs = 0; + w->sftl.unknown_sbs = 0; + w->sftl.btoc_pages_read = 0; + w->sftl.btoc_pages_valid = 0; + w->sftl.btoc_entries_seen = 0; + w->sftl.btoc_l2v_updates = 0; + w->sftl.btoc_meta_mismatch = 0; + w->sftl.btoc_meta_confirmed = 0; + w->sftl.btoc_skipped_zero = 0; + w->sftl.btoc_confirm_pages = 0; + w->sftl.btoc_confirm_capped = 0; + w->sftl.btoc_confirm_budget_stop = 0; + w->sftl.string_hit_itunesdb = 0; + w->sftl.string_hit_f00 = 0; + w->sftl.string_hit_apps = 0; + w->sftl.string_hit_mp3 = 0; + w->sftl.string_hit_m4a = 0; + w->sftl.open_slots_seen = 0; + w->sftl.open_slots_valid_meta = 0; + w->sftl.open_l2v_updates = 0; + w->sftl.range_nodes = 0; + w->sftl.cxt_l2v_updates = 0; + w->sftl.cxt_meta_confirmed = 0; + w->sftl.cxt_meta_mismatch = 0; + w->sftl.cxt_confirm_pages = 0; + w->sftl.cxt_confirm_unreadable = 0; + w->sftl.cxt_repair_pages = 0; + w->sftl.cxt_repair_slots = 0; + w->sftl.range_budget_stop = 0; + w->sftl.stale_mapping_rejected = 0; + w->sftl.n_cxt_idx = 0; + + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) + dev_warn(w->dev, "re-recover DMA session %d\n", sess); + ret = whimory_sftl_recover_l2v_from_media(w); + if (sess == 0) + s5l8740_nand_dma_session_end(); + if (ret) { + whimory_set_status(w, "re-recover failed %d", ret); + return ret; + } + } + + if (!w->l2v_ok) { + whimory_set_status(w, "recover OK but l2v_ok=0"); + return -EIO; + } + whimory_set_status(w, + "CS recover OK mapped_ranges=%u btoc=%u open=%u", + w->sftl.range_nodes, w->sftl.btoc_pages_valid, + w->sftl.open_l2v_updates); + dev_info(w->dev, "%s\n", w->status); + return 0; +} + +/* + * One boot should run one recovery. A repeat with the same knobs is a + * no-op; a repeat with different knobs rebuilds, but not while the map is + * already live behind a registered disk unless asked explicitly. + */ +int whimory_sftl_recover_cs(void) +{ + struct whimory *w = whimory_dev; + int ret; + + if (!w) + return -ENODEV; + if (recovery_state == RECOVERY_RUNNING) + return -EBUSY; + if (recovery_state == RECOVERY_VALID) { + if (recovery_params_key == whimory_recover_key()) { + dev_info(w->dev, + "recover: map already valid (same params); skipping\n"); + return 0; + } + if (!recover_force && n31_ftl_cs_disk_registered()) { + dev_warn(w->dev, + "recover: disk bound; set recover_force=1 to rebuild\n"); + return -EBUSY; + } + } + + recovery_state = RECOVERY_RUNNING; + ret = whimory_sftl_recover_cs_locked(); + if (ret) { + recovery_state = RECOVERY_FAILED; + return ret; + } + recovery_state = RECOVERY_VALID; + recovery_params_key = whimory_recover_key(); + return 0; +} + +static int __init ftl_init(void) +{ + struct whimory *w; + int ret; + + if (!s5l8740_nand_available()) { + pr_err("s5l8740-ftl: load nand_s5l8740 first\n"); + return -ENODEV; + } + + w = kzalloc(sizeof(*w), GFP_KERNEL); + if (!w) + return -ENOMEM; + w->dev = nand_ftl_device(); + mutex_init(&w->bounce_lock); + mutex_init(&w->tree_lock); + w->ranges = RB_ROOT; + w->bounce = kzalloc(WHIMORY_LBA_SIZE, GFP_KERNEL); + if (!w->bounce) { + kfree(w); + return -ENOMEM; + } + w->total_4k_sectors = NAND_FTL_DEFAULT_CAPACITY; + whimory_dev = w; + + ftl_pdev = platform_device_register_simple("s5l8740-ftl", -1, NULL, 0); + if (IS_ERR(ftl_pdev)) { + ret = PTR_ERR(ftl_pdev); + ftl_pdev = NULL; + whimory_free(w); + whimory_dev = NULL; + return ret; + } + w->pdev = ftl_pdev; + w->dev = &ftl_pdev->dev; + ret = sysfs_create_group(&ftl_pdev->dev.kobj, &ftl_attr_group); + if (ret) { + platform_device_unregister(ftl_pdev); + whimory_free(w); + whimory_dev = NULL; + ftl_pdev = NULL; + return ret; + } + + ret = ftl_s5l8740_csmap_init(&ftl_pdev->dev); + if (ret) + dev_warn(&ftl_pdev->dev, "CS map init failed %d\n", ret); + + ret = whimory_open_stack(w); + if (ret) { + dev_err(w->dev, + "Whimory open failed (%d) — NOT registering /dev/%s (fil=%d sig=%d vfl=%d ftl=%d l2v=%d lba0=%d)\n", + ret, FTL_DISK_NAME, w->fil_ok, w->sig_ok, w->vfl_ok, + w->ftl_ok, w->l2v_ok, w->lba0_ok); + /* + * Keep the platform device so sysfs status is visible. + * The block disk is absent until LBA0 works. + */ + return 0; + } + return 0; +} + +static void __exit ftl_exit(void) +{ + struct whimory *w = whimory_dev; + + if (ftl_pdev) { + ftl_s5l8740_csmap_exit(&ftl_pdev->dev); + sysfs_remove_group(&ftl_pdev->dev.kobj, &ftl_attr_group); + platform_device_unregister(ftl_pdev); + ftl_pdev = NULL; + } + whimory_dev = NULL; + whimory_free(w); +} + +module_init(ftl_init); +module_exit(ftl_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("S5L8740 Whimory PPN SFTL read-only block driver"); +MODULE_AUTHOR("n31"); +MODULE_SOFTDEP("pre: nand_s5l8740"); +MODULE_FIRMWARE(WHIMORY_ORACLE_SIG); +MODULE_FIRMWARE(WHIMORY_ORACLE_ROOT); +MODULE_FIRMWARE(WHIMORY_ORACLE_NODES); +MODULE_FIRMWARE(WHIMORY_ORACLE_GLOBALS); diff --git a/drivers/misc/ftl-s5l8740-csmap.c b/drivers/misc/ftl-s5l8740-csmap.c new file mode 100755 index 00000000000000..8c4c3c6b7b2056 --- /dev/null +++ b/drivers/misc/ftl-s5l8740-csmap.c @@ -0,0 +1,3290 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 FTL CS LBA map and read-only VFAT block front-end (N31). + * + * Builds a sparse LBA→physical cache from CS page metadata (4096+16 × 4 + * slots per page), compresses it into dual L2V/V2L vector tables, and + * registers read-only disks after FAT-critical validation: + * /dev/s5l8740-ipod — user FAT (disk_lba 0 @ fat_base_lba) + * /dev/s5l8740-ftl — same FAT (compat alias) + * /dev/s5l8740-firmware — pre-FAT fmss range, if mapped content found + * + * Authoritative map after `ftl_sftl_recover`: Whimory CXT→BTOC→L2V_Update + * (CS META), with reads via L2V_Search. `ftl_map_build` (blk 62–66) is a + * debug fallback only — not a journal replay. + * + * Every data return re-validates the on-media metadata LBA. Read-only: + * no program, erase, or GC. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nand-s5l8740.h" +#include "ftl-s5l8740-csmap.h" +#include "ftl-s5l8740-vecmap.h" + +#define N31_FAT_BASE_DEFAULT 49279u +#define N31_FAT_TOTAL_DEFAULT 3856968u +#define N31_FMSS_LBA_MAX (N31_FAT_BASE_DEFAULT + \ + N31_FAT_TOTAL_DEFAULT + 65536u) +#define N31_BPB_CANDIDATES_MAX 16 +/* + * Bounds for the partition-table probe; see n31_bpb_partition_lbas(). + * + * RESERVED_PROBE has to span two things at once: a volume that starts at + * the partition sector, and one that starts a track in, at the classic + * CHS-aligned offset of 63. This unit is the second kind -- the partition + * begins at 49216 with an "MSDOS5.0" BPB whose hidden count is 0, and the + * volume the driver actually mounts is 63 sectors further on at 49279, its + * own BPB recording hidden=63 and its backup at +6. So the window is 63 + * plus a full reserved area (this volume records reserved=32), rounded up. + * + * HIDDEN_MAX bounds the redirect a BPB's own hidden count can ask for, for + * the layouts where that field points forwards rather than back. + */ +#define N31_BPB_RESERVED_PROBE 96u +#define N31_BPB_HIDDEN_MAX 4096u +#define N31_BPB_LBA_SANE_MAX 0x10000000u +#define N31_BPB_PROBE_MAX (2 * 2 * N31_BPB_RESERVED_PROBE) +#define N31_MAP_HASH_BITS 12 +#define N31_EXTENT_MAX 2048 +#define N31_PAGES_PER_BLOCK 128 + +#define N31_PHYS_KEY_INVALID 0xffffffffu + +/* Sparse hash + extents for construction; L2V/V2L preferred on read. */ + +struct n31_map_entry { + u32 fmss_lba; + u32 phys_key; + u64 weave; /* full 48-bit weaveSeq — do not truncate */ + u8 type; + u8 valid; +}; + +struct n31_map_node { + struct hlist_node hnode; + struct n31_map_entry e; +}; + +struct n31_lba_extent { + u32 start_lba; + u32 len; + u32 start_phys_key; + u64 weave_first; + u8 type; +}; + +struct n31_fat_layout { + u16 bytes_per_sector; + u8 sectors_per_cluster; + u16 reserved_sectors; + u8 num_fats; + u32 fat_size_32; + u32 root_cluster; + u16 fsinfo_sector; + u16 backup_boot_sector; + u16 ext_flags; /* BPB_ExtFlags @ 0x28 */ + u32 total_sectors; + u32 fat_start; + u32 data_start; + u32 root_dir_lba; + u32 fsinfo_lba; + u32 backup_boot_lba; + bool valid; +}; + +enum n31_slice_kind { + N31_SLICE_IPOD = 0, + N31_SLICE_FTL_ALIAS, + N31_SLICE_FIRMWARE, +}; + +struct n31_ftl_cs; + +struct n31_ftl_slice { + struct n31_ftl_cs *ftl; + struct gendisk *gd; + u32 base_fmss; + u32 nsectors; + enum n31_slice_kind kind; +}; + +struct n31_ftl_cs { + struct device *dev; + struct mutex lock; + + DECLARE_HASHTABLE(map, N31_MAP_HASH_BITS); + unsigned int map_entries; + unsigned int map_updates; + unsigned int map_skips; + unsigned int map_collisions; + unsigned int map_capped; + unsigned int map_pages; + unsigned int map_data_recs; + unsigned int newer_replacements; + u32 lba_min, lba_max; + bool map_built; + + struct n31_vecmap vec; + char vec_log[384]; + + struct n31_lba_extent *extents; + unsigned int extent_count; + unsigned int extent_largest; + u32 open_ext_lba; + u32 open_ext_phys; + u32 open_ext_start_lba; + u32 open_ext_start_phys; + u64 open_ext_weave; + u8 open_ext_type; + u32 open_ext_len; + + u32 fat_base_lba; + bool fat_base_valid; + bool fat_base_autodetect; + u32 fat_total_sectors; + struct n31_fat_layout layout; + u8 bpb_sector[N31_DATA_SLOT_SIZE]; + bool bpb_cached; + + u32 bpb_candidates[N31_BPB_CANDIDATES_MAX]; + u64 bpb_cand_weave[N31_BPB_CANDIDATES_MAX]; + u32 bpb_cand_total[N31_BPB_CANDIDATES_MAX]; + char bpb_cand_oem[N31_BPB_CANDIDATES_MAX][9]; + u8 bpb_cand_sector[N31_BPB_CANDIDATES_MAX][N31_DATA_SLOT_SIZE]; + unsigned int bpb_ncand; + unsigned int fat_crit_ok_n; + unsigned int fat_crit_need_n; + + u32 fw_base_fmss; + u32 fw_nsectors; + bool fw_valid; + unsigned int fw_mapped; + unsigned int fw_magic_hits; + char fw_log[320]; + + u8 last_sector[N31_DATA_SLOT_SIZE]; + u32 last_fmss_lba; + u32 last_disk_lba; + int last_ret; + char last_log[768]; + char extents_log[512]; + char range_log[512]; + char layout_log[512]; + char bpb_log[384]; + + unsigned int range_ok; + unsigned int range_fail; + unsigned int range_miss; + unsigned int demand_scans; + unsigned int read_miss_count; + /* Rolling budget for read-miss descriptions; see n31_log_read_miss(). */ + unsigned long miss_window_start; + unsigned int miss_window_shown; + unsigned int miss_window_suppressed; + + bool disk0_ok; + bool fat_critical_ok; + bool enable_gate_ok; + bool block_enable; + bool whimory_backed; /* L2V_Search via Whimory recover */ + + /* FAT semantic validation (beyond crit sector reads). */ + u32 fat_sem_root_chain_len; + u32 fat_sem_root_entries; + u32 fat_sem_music_dirs; + u32 fat_sem_fat0_fat1_diff; + bool fat_sem_itunesdb; + bool fat_sem_apps; + bool fat_sem_nanoapps; + char fat_sem_log[512]; + char string_scan_log[384]; + + struct n31_ftl_slice ipod; + struct n31_ftl_slice ftl_alias; + struct n31_ftl_slice firmware; + u8 *bounce; +}; + +static int n31_ftl_find_bpb(struct n31_ftl_cs *ftl); +static int n31_ftl_select_bpb(struct n31_ftl_cs *ftl); +static int n31_validate_fat_critical(struct n31_ftl_cs *ftl); +static void n31_fat_semantic_validate(struct n31_ftl_cs *ftl); +static bool n31_fat_first_sector_ok(struct n31_ftl_cs *ftl); +static int n31_ftl_register_disk(struct n31_ftl_cs *ftl); +static void n31_ftl_unregister_disk(struct n31_ftl_cs *ftl); +static int n31_ftl_apply_bpb(struct n31_ftl_cs *ftl, u32 fmss_lba, + u32 total, const u8 *sector); +static bool n31_bpb_looks_valid(const u8 *d, u32 *total_out); + +static struct n31_ftl_cs *n31_ftl; + +/* + * A read miss costs nine L2V probes plus ten console lines. VFAT retries a + * failing directory cluster, so an unmapped chain can bury the log and slow + * the mount. Describe a few, then go quiet -- but go quiet for a while, not + * for the rest of the uptime. + * + * This was a budget against a counter that was never reset: not per bio, not + * per mount, not per open. Three misses at any point in the module's life + * and every read failure after it was silent, forever. On a device that had + * been up eight hours that meant `cp` failing with EIO and ten + * "FAT read failed" lines from vfat with nothing at all from the driver that + * produced them -- which is most of why the underlying map bug took so long + * to see. + * + * The budget refills on a window now, so a burst is still bounded but the + * next failure a person triggers is always explained. Set the window to 0 + * for the old latching behaviour, or the max to 0 to describe every miss. + */ +/* + * The exhaustive range walk is the fallback, not the plan. Clear this to + * force it -- useful when the partition table is suspect, or to confirm the + * two paths pick the same volume. + */ +static bool bpb_probe_partition = true; +module_param(bpb_probe_partition, bool, 0644); +MODULE_PARM_DESC(bpb_probe_partition, + "Find the BPB from the MBR partition table before walking every range (default Y)"); + +static unsigned int read_miss_diag_max = 3; +module_param(read_miss_diag_max, uint, 0644); +MODULE_PARM_DESC(read_miss_diag_max, + "Read misses to describe in full per window (0=all)"); + +static unsigned int read_miss_diag_window_ms = 5000; +module_param(read_miss_diag_window_ms, uint, 0644); +MODULE_PARM_DESC(read_miss_diag_window_ms, + "Refill the read-miss description budget after this long (0=never)"); + +static bool ftl_block_enable = true; +module_param(ftl_block_enable, bool, 0644); +MODULE_PARM_DESC(ftl_block_enable, + "Register RO ipod/firmware disks after FAT-critical gates (default Y)"); + +/* + * Ceiling on the sparse hash, which block I/O feeds. About 40 bytes an + * entry, so 65536 is roughly 2.6 MB on a 55 MB device. 0 = unbounded, + * which is only safe when nothing is reading the volume. + */ +static unsigned int map_max_entries = 65536; +module_param(map_max_entries, uint, 0644); +MODULE_PARM_DESC(map_max_entries, + "Ceiling on sparse-map entries ingested from reads (0=unbounded)"); + +static bool ftl_demand_scan; +module_param(ftl_demand_scan, bool, 0644); +MODULE_PARM_DESC(ftl_demand_scan, + "On map miss, scan a nearby block for the LBA (default N; unsafe under mount I/O)"); + +static int fw_start_lba = -1; +module_param(fw_start_lba, int, 0644); +MODULE_PARM_DESC(fw_start_lba, + "Firmware slice start fmss_lba (-1 = auto below fat_base)"); + +static int fw_nsectors = -1; +module_param(fw_nsectors, int, 0644); +MODULE_PARM_DESC(fw_nsectors, + "Firmware slice length in 4096-byte sectors (-1 = auto)"); + +static bool fw_force; +module_param(fw_force, bool, 0644); +MODULE_PARM_DESC(fw_force, + "Register firmware disk even without magic/mapped hits (default N)"); + +/* -------------------- packed physical key -------------------- */ + +static u32 n31_phys_pack(u8 ce, u8 cau, u16 blk, u8 page, u8 slot) +{ + return ((u32)(ce & 0x3) << 30) | + ((u32)(cau & 0x3) << 28) | + ((u32)(blk & 0x3fff) << 14) | + ((u32)(page & 0xff) << 6) | + ((u32)(slot & 0x3)); +} + +static void n31_phys_unpack(u32 key, u8 *ce, u8 *cau, u16 *blk, + u8 *page, u8 *slot) +{ + *ce = (key >> 30) & 0x3; + *cau = (key >> 28) & 0x3; + *blk = (key >> 14) & 0x3fff; + *page = (key >> 6) & 0xff; + *slot = key & 0x3; +} + +/* Advance one CS data slot: slot→…→3 → next page slot0 → next block. */ +static u32 n31_phys_succ(u32 key) +{ + u8 ce, cau, page, slot; + u16 blk; + + if (key == N31_PHYS_KEY_INVALID) + return N31_PHYS_KEY_INVALID; + n31_phys_unpack(key, &ce, &cau, &blk, &page, &slot); + if (slot < 3) + return n31_phys_pack(ce, cau, blk, page, slot + 1); + if (page + 1 < N31_PAGES_PER_BLOCK) + return n31_phys_pack(ce, cau, blk, page + 1, 0); + return n31_phys_pack(ce, cau, blk + 1, 0, 0); +} + +static u32 n31_phys_pred(u32 key) +{ + u8 ce, cau, page, slot; + u16 blk; + + if (key == N31_PHYS_KEY_INVALID) + return N31_PHYS_KEY_INVALID; + n31_phys_unpack(key, &ce, &cau, &blk, &page, &slot); + if (slot > 0) + return n31_phys_pack(ce, cau, blk, page, slot - 1); + if (page > 0) + return n31_phys_pack(ce, cau, blk, page - 1, 3); + if (blk > 0) + return n31_phys_pack(ce, cau, blk - 1, + N31_PAGES_PER_BLOCK - 1, 3); + return N31_PHYS_KEY_INVALID; +} + +static u32 n31_phys_advance(u32 key, u32 delta) +{ + while (delta--) { + key = n31_phys_succ(key); + if (key == N31_PHYS_KEY_INVALID) + break; + } + return key; +} + +static void n31_entry_from_phys(struct n31_map_entry *e, u32 fmss_lba, + u32 phys_key, u64 weave, u8 type) +{ + e->fmss_lba = fmss_lba; + e->phys_key = phys_key; + e->weave = weave; + e->type = type; + e->valid = 1; +} + +static void n31_entry_to_legacy(const struct n31_map_entry *e, + struct n31_lba_map_entry *out) +{ + u8 ce, cau, page, slot; + u16 blk; + + memset(out, 0, sizeof(*out)); + if (!e || !e->valid) + return; + n31_phys_unpack(e->phys_key, &ce, &cau, &blk, &page, &slot); + out->ce = ce; + out->cau = cau; + out->block = blk; + out->page = page; + out->slot = slot; + out->type = e->type; + out->weave = e->weave; + out->fmss_lba = e->fmss_lba; + out->present = true; +} + +/* -------------------- BPB / layout -------------------- */ + +static bool n31_bpb_looks_valid(const u8 *d, u32 *total_out) +{ + u16 bps, root_ents, fat16; + u32 total32, total16, fat32; + u8 spc, fats; + + if (!d) + return false; + if (d[0] != 0xeb && d[0] != 0xe9) + return false; + if (d[0] == 0xeb && d[2] != 0x90) + return false; + bps = get_unaligned_le16(d + 11); + spc = d[13]; + fats = d[16]; + root_ents = get_unaligned_le16(d + 17); + fat16 = get_unaligned_le16(d + 22); + total16 = get_unaligned_le16(d + 19); + total32 = get_unaligned_le32(d + 32); + fat32 = get_unaligned_le32(d + 36); + if (bps != 4096 || spc != 4 || fats == 0 || fats > 2) + return false; + if (!total32) + total32 = total16; + if (!total32 || total32 < 1000u || total32 > 16u * 1024u * 1024u) + return false; + /* + * FAT32: root_ents==0 and FATsz16==0 → require FATsz32. + * Reject MSDOS5.0-style stubs that advertise FAT32 in the type + * string but leave FATsz32=0 (not a mountable volume). + */ + if (root_ents == 0 && fat16 == 0 && fat32 == 0) + return false; + if (total_out) + *total_out = total32; + return true; +} + +static int n31_parse_bpb(const u8 *d, struct n31_fat_layout *L) +{ + u32 total = 0; + + memset(L, 0, sizeof(*L)); + if (!n31_bpb_looks_valid(d, &total)) + return -EINVAL; + + L->bytes_per_sector = get_unaligned_le16(d + 11); + L->sectors_per_cluster = d[13]; + L->reserved_sectors = get_unaligned_le16(d + 14); + L->num_fats = d[16]; + L->total_sectors = total; + L->fat_size_32 = get_unaligned_le32(d + 36); + L->ext_flags = get_unaligned_le16(d + 0x28); + L->root_cluster = get_unaligned_le32(d + 44); + L->fsinfo_sector = get_unaligned_le16(d + 48); + L->backup_boot_sector = get_unaligned_le16(d + 50); + + L->fat_start = L->reserved_sectors; + L->data_start = L->reserved_sectors + + (u32)L->num_fats * L->fat_size_32; + if (L->root_cluster >= 2) + L->root_dir_lba = L->data_start + + (L->root_cluster - 2) * L->sectors_per_cluster; + else + L->root_dir_lba = L->data_start; + L->fsinfo_lba = L->fsinfo_sector; + L->backup_boot_lba = L->backup_boot_sector; + L->valid = true; + return 0; +} + +/* -------------------- sparse map + extents -------------------- */ + +static void n31_extent_close(struct n31_ftl_cs *ftl) +{ + struct n31_lba_extent *ex; + + if (ftl->open_ext_len == 0) + return; + if (ftl->extent_count >= N31_EXTENT_MAX) { + ftl->open_ext_len = 0; + return; + } + if (!ftl->extents) { + ftl->extents = kcalloc(N31_EXTENT_MAX, + sizeof(*ftl->extents), GFP_KERNEL); + if (!ftl->extents) { + ftl->open_ext_len = 0; + return; + } + } + ex = &ftl->extents[ftl->extent_count++]; + ex->start_lba = ftl->open_ext_start_lba; + ex->len = ftl->open_ext_len; + ex->start_phys_key = ftl->open_ext_start_phys; + ex->weave_first = ftl->open_ext_weave; + ex->type = ftl->open_ext_type; + if (ex->len > ftl->extent_largest) + ftl->extent_largest = ex->len; + ftl->open_ext_len = 0; +} + +static void n31_extent_feed(struct n31_ftl_cs *ftl, u32 fmss_lba, + u32 phys_key, u64 weave, u8 type) +{ + if (ftl->open_ext_len == 0) { + ftl->open_ext_start_lba = fmss_lba; + ftl->open_ext_start_phys = phys_key; + ftl->open_ext_lba = fmss_lba; + ftl->open_ext_phys = phys_key; + ftl->open_ext_weave = weave; + ftl->open_ext_type = type; + ftl->open_ext_len = 1; + return; + } + + if (fmss_lba == ftl->open_ext_lba + 1 && + phys_key == n31_phys_succ(ftl->open_ext_phys) && + (type == ftl->open_ext_type || + (type <= 2 && ftl->open_ext_type <= 2))) { + ftl->open_ext_lba = fmss_lba; + ftl->open_ext_phys = phys_key; + ftl->open_ext_len++; + return; + } + + n31_extent_close(ftl); + ftl->open_ext_start_lba = fmss_lba; + ftl->open_ext_start_phys = phys_key; + ftl->open_ext_lba = fmss_lba; + ftl->open_ext_phys = phys_key; + ftl->open_ext_weave = weave; + ftl->open_ext_type = type; + ftl->open_ext_len = 1; +} + +static void n31_extents_reset(struct n31_ftl_cs *ftl) +{ + n31_extent_close(ftl); + ftl->extent_count = 0; + ftl->extent_largest = 0; + ftl->open_ext_len = 0; +} + +static int n31_extent_lookup(struct n31_ftl_cs *ftl, u32 fmss_lba, + struct n31_map_entry *out) +{ + unsigned int i; + + for (i = 0; i < ftl->extent_count; i++) { + struct n31_lba_extent *ex = &ftl->extents[i]; + u32 off; + + if (fmss_lba < ex->start_lba || + fmss_lba >= ex->start_lba + ex->len) + continue; + off = fmss_lba - ex->start_lba; + n31_entry_from_phys(out, fmss_lba, + n31_phys_advance(ex->start_phys_key, off), + ex->weave_first, ex->type); + return 0; + } + return -ENOENT; +} + +static void n31_map_free(struct n31_ftl_cs *ftl) +{ + unsigned int bkt; + struct n31_map_node *n; + struct hlist_node *tmp; + + hash_for_each_safe(ftl->map, bkt, tmp, n, hnode) { + hash_del(&n->hnode); + kfree(n); + } + ftl->map_entries = 0; + ftl->map_updates = 0; + ftl->map_skips = 0; + ftl->map_collisions = 0; + ftl->map_pages = 0; + ftl->map_data_recs = 0; + ftl->newer_replacements = 0; + ftl->lba_min = ~0u; + ftl->lba_max = 0; + ftl->map_built = false; + ftl->disk0_ok = false; + ftl->fat_critical_ok = false; + ftl->enable_gate_ok = false; + n31_extents_reset(ftl); + kfree(ftl->extents); + ftl->extents = NULL; + n31_vecmap_free(&ftl->vec); + ftl->vec_log[0] = '\0'; + ftl->fw_valid = false; + ftl->fw_mapped = 0; + ftl->fw_magic_hits = 0; + ftl->fw_nsectors = 0; + ftl->fw_log[0] = '\0'; +} + +static struct n31_map_node *n31_map_find(struct n31_ftl_cs *ftl, u32 fmss_lba) +{ + struct n31_map_node *n; + + hash_for_each_possible(ftl->map, n, hnode, fmss_lba) { + if (n->e.valid && n->e.fmss_lba == fmss_lba) + return n; + } + return NULL; +} + +static void n31_map_ingest(struct n31_ftl_cs *ftl, u8 ce, u8 cau, + u16 block, u8 page, u8 slot, + const struct s5l8740_meta_decoded *m) +{ + struct n31_map_node *n; + u32 phys; + u64 weave; + + if (!n31_meta_is_data_record(m)) { + ftl->map_skips++; + return; + } + if (m->lba >= N31_FMSS_LBA_MAX) { + ftl->map_skips++; + return; + } + + phys = n31_phys_pack(ce, cau, block, page, slot); + weave = m->weave; + ftl->map_data_recs++; + if (m->lba < ftl->lba_min) + ftl->lba_min = m->lba; + if (m->lba > ftl->lba_max) + ftl->lba_max = m->lba; + + n = n31_map_find(ftl, m->lba); + if (!n) { + /* + * Bounded, because this is now reachable from block I/O. + * + * Every fallback read ingests the page it just fetched, which + * is what makes the neighbour probe work on the next miss. + * It also means the table grows with the volume as it is + * read: this device has 3.8M sectors and each entry is about + * forty bytes, so an unbounded table is 150 MB on a machine + * with 55. The path was dead until CS was armed for it, so + * this never mattered before and would have become an OOM + * during a long copy. + * + * Refusing new entries past the cap costs only the probe + * hint; every read still resolves through Whimory L2V or + * through its own CS read. + */ + if (map_max_entries && ftl->map_entries >= map_max_entries) { + ftl->map_capped++; + return; + } + n = kzalloc(sizeof(*n), GFP_NOIO); + if (!n) { + ftl->map_skips++; + return; + } + n31_entry_from_phys(&n->e, m->lba, phys, weave, m->type); + hash_add(ftl->map, &n->hnode, m->lba); + ftl->map_entries++; + n31_extent_feed(ftl, m->lba, phys, weave, m->type); + return; + } + + if (weave == n->e.weave && n->e.phys_key != phys) { + ftl->map_collisions++; + dev_dbg(ftl->dev, + "lba=%u weave collision old_phys=%08x new_phys=%08x\n", + m->lba, n->e.phys_key, phys); + return; + } + if (!n31_weave_newer(weave, n->e.weave)) { + /* Stale or equal — never feed extents from losers. */ + ftl->map_skips++; + return; + } + + ftl->newer_replacements++; + ftl->map_updates++; + n31_entry_from_phys(&n->e, m->lba, phys, weave, m->type); + n31_extent_feed(ftl, m->lba, phys, weave, m->type); +} + +static int n31_scan_page(struct n31_ftl_cs *ftl, u8 ce, u8 cau, + u16 block, u8 page) +{ + struct s5l8740_cs_page *pg; + int ret, s; + + /* + * GFP_NOIO, and no zeroing. + * + * struct s5l8740_cs_page is about 16 KiB, so this is an order-2 + * allocation on a path the block layer can enter during writeback -- + * GFP_KERNEL there lets reclaim recurse back into the filesystem + * that is waiting on this read. It mattered less when the fallback + * path was dead; it is live now. + * + * The zeroing was redundant either way: s5l8740_nand_cs_phys_read_slc() + * memsets the whole struct before it fills it. + */ + pg = kmalloc(sizeof(*pg), GFP_NOIO); + if (!pg) + return -ENOMEM; + ret = s5l8740_nand_cs_phys_read(ce, cau, block, page, pg); + if (!ret) { + ftl->map_pages++; + for (s = 0; s < N31_DATA_SLOTS; s++) + n31_map_ingest(ftl, ce, cau, block, page, s, + &pg->meta[s]); + } + kfree(pg); + return ret; +} + +/* + * Compress newest-weave hash entries into L2V/V2L. Pair scratch is freed + * after build; the hash remains available for misses and rebuilds. + */ +static int n31_vecmap_rebuild_from_hash(struct n31_ftl_cs *ftl) +{ + struct n31_vec_pair *pairs; + struct n31_map_node *n; + unsigned int bkt, i = 0, nent; + int ret; + u8 ce, cau, page, slot; + u16 blk; + + nent = ftl->map_entries; + if (!nent) { + n31_vecmap_free(&ftl->vec); + scnprintf(ftl->vec_log, sizeof(ftl->vec_log), + "ready=0 pairs=0\n"); + return 0; + } + + pairs = vmalloc(array_size(nent, sizeof(*pairs))); + if (!pairs) { + scnprintf(ftl->vec_log, sizeof(ftl->vec_log), + "ready=0 err=-ENOMEM pairs=%u\n", nent); + return -ENOMEM; + } + + hash_for_each(ftl->map, bkt, n, hnode) { + if (i >= nent) + break; + n31_phys_unpack(n->e.phys_key, &ce, &cau, &blk, &page, &slot); + pairs[i].l = n->e.fmss_lba; + pairs[i].p = n31_phys_to_ordinal(ce, cau, blk, page, slot); + pairs[i].weave = n->e.weave; + i++; + } + + ret = n31_vecmap_build(&ftl->vec, pairs, i); + vfree(pairs); + if (ret) { + scnprintf(ftl->vec_log, sizeof(ftl->vec_log), + "ready=0 err=%d pairs=%u\n", ret, i); + return ret; + } + + scnprintf(ftl->vec_log, sizeof(ftl->vec_log), + "ready=1 pairs=%u l_base=%u l_count=%u p_base=%u p_count=%u " + "l2v_groups=%u v2l_groups=%u compact_ok=%u esc=%u miss=%u " + "l2v_esc_n=%u v2l_esc_n=%u\n", + i, ftl->vec.l_base, ftl->vec.l_count, + ftl->vec.p_base, ftl->vec.p_count, + ftl->vec.l2v_groups, ftl->vec.v2l_groups, + ftl->vec.compact_ok, ftl->vec.compact_esc, + ftl->vec.compact_miss, ftl->vec.l2v_esc_n, + ftl->vec.v2l_esc_n); + return 0; +} + +static void n31_scan_finish_stats(struct n31_ftl_cs *ftl, const char *tag, + unsigned int pages_ok, + unsigned int pages_fail, + bool rebuild_vec) +{ + int vret = 0; + + n31_extent_close(ftl); + ftl->map_built = ftl->map_entries > 0; + if (rebuild_vec) + vret = n31_vecmap_rebuild_from_hash(ftl); + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "%s pages_ok=%u pages_fail=%u records=%u " + "lba_min=%u lba_max=%u extents=%u largest=%u " + "collisions=%u replacements=%u has_fat_base=%d " + "vec_ready=%d vec_ret=%d\n", + tag, pages_ok, pages_fail, ftl->map_data_recs, + ftl->lba_min == ~0u ? 0 : ftl->lba_min, ftl->lba_max, + ftl->extent_count, ftl->extent_largest, + ftl->map_collisions, ftl->newer_replacements, + n31_map_find(ftl, ftl->fat_base_lba) ? 1 : 0, + ftl->vec.ready ? 1 : 0, vret); + scnprintf(ftl->extents_log, sizeof(ftl->extents_log), + "extent_count=%u largest=%u open_len=%u\n", + ftl->extent_count, ftl->extent_largest, ftl->open_ext_len); + dev_dbg(ftl->dev, "%s", ftl->last_log); + if (rebuild_vec && ftl->vec_log[0]) + dev_dbg(ftl->dev, "vecmap %s", ftl->vec_log); +} + +/* + * Scan CE/CAU × blk_lo..blk_hi × pages. Appends unless @reset. + * Full vector rebuild only when @rebuild_vec (not on demand I/O fills). + */ +static int n31_scan_block_window(struct n31_ftl_cs *ftl, u8 ce, u8 cau, + u16 blk_lo, u16 blk_hi, bool reset, + bool rebuild_vec) +{ + u16 blk; + u8 pg; + unsigned int pages_ok = 0, pages_fail = 0; + int ret; + + if (reset) { + n31_map_free(ftl); + hash_init(ftl->map); + ftl->lba_min = ~0u; + ftl->lba_max = 0; + } + + for (blk = blk_lo; blk <= blk_hi; blk++) { + for (pg = 0; pg < N31_PAGES_PER_BLOCK; pg++) { + ret = n31_scan_page(ftl, ce, cau, blk, pg); + if (ret) + pages_fail++; + else + pages_ok++; + cond_resched(); + } + } + n31_scan_finish_stats(ftl, "scan_window", pages_ok, pages_fail, + rebuild_vec); + return 0; +} + +/* Scan blk_lo..blk_hi on every CE/CAU; reset once; compress vectors once. */ +static int n31_scan_banks_window(struct n31_ftl_cs *ftl, u16 blk_lo, + u16 blk_hi, bool reset) +{ + u8 ce, cau; + bool do_reset = reset; + + for (ce = 0; ce < N31_VEC_NUM_CE; ce++) { + for (cau = 0; cau < N31_VEC_NUM_CAU; cau++) { + bool last = (ce == N31_VEC_NUM_CE - 1) && + (cau == N31_VEC_NUM_CAU - 1); + + n31_scan_block_window(ftl, ce, cau, blk_lo, blk_hi, + do_reset, last); + do_reset = false; + } + } + return 0; +} + +static int n31_map_lookup_hint(struct n31_ftl_cs *ftl, u32 fmss_lba, + struct n31_map_entry *out) +{ + struct n31_map_node *n; + + n = n31_map_find(ftl, fmss_lba); + if (n) { + *out = n->e; + return 0; + } + return n31_extent_lookup(ftl, fmss_lba, out); +} + +/* + * Optional nearby-block fill for map misses. Must not run under block I/O + * with a multi-block window or vector rebuild — that softlocks the system. + */ +static int n31_demand_scan_for(struct n31_ftl_cs *ftl, u32 fmss_lba) +{ + struct n31_map_entry hint; + u8 ce = 0, cau = 0; + u16 blk = 63; + u8 page = 88, slot; + int ret, sess; + u16 lo, hi; + + ftl->demand_scans++; + if (fmss_lba > 0 && + !n31_map_lookup_hint(ftl, fmss_lba - 1, &hint)) { + n31_phys_unpack(hint.phys_key, &ce, &cau, &blk, &page, &slot); + } else if (!n31_map_lookup_hint(ftl, fmss_lba + 1, &hint)) { + n31_phys_unpack(hint.phys_key, &ce, &cau, &blk, &page, &slot); + } else if (!n31_map_lookup_hint(ftl, ftl->fat_base_lba, &hint)) { + n31_phys_unpack(hint.phys_key, &ce, &cau, &blk, &page, &slot); + } + (void)slot; + + dev_dbg(ftl->dev, + "demand_scan lba=%u ce=%u cau=%u blk=%u\n", + fmss_lba, ce, cau, blk); + + lo = blk; + hi = blk; + + /* Arm CS for the scan; nests if a caller already holds a session. */ + sess = s5l8740_nand_dma_session_begin(); + + /* Home bank first, then the other CE/CAU at the same block. */ + ret = n31_scan_block_window(ftl, ce, cau, lo, hi, false, false); + if (n31_map_find(ftl, fmss_lba) || + !n31_extent_lookup(ftl, fmss_lba, &hint)) + goto found; + { + u8 tce, tcau; + + for (tce = 0; tce < N31_VEC_NUM_CE; tce++) { + for (tcau = 0; tcau < N31_VEC_NUM_CAU; tcau++) { + if (tce == ce && tcau == cau) + continue; + n31_scan_block_window(ftl, tce, tcau, lo, hi, + false, false); + if (n31_map_find(ftl, fmss_lba) || + !n31_extent_lookup(ftl, fmss_lba, &hint)) + goto found; + } + } + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret ? ret : -ENOENT; +found: + if (sess == 0) + s5l8740_nand_dma_session_end(); + return 0; +} + +static void n31_map_ingest_page(struct n31_ftl_cs *ftl, u8 ce, u8 cau, + u16 block, u8 page, + const struct s5l8740_cs_page *pg) +{ + int s; + + for (s = 0; s < N31_DATA_SLOTS; s++) + n31_map_ingest(ftl, ce, cau, block, page, s, &pg->meta[s]); +} + +/* + * One-page fill from a mapped neighbour's CS page. Safe under block I/O. + * Also tries the predicted successor/predecessor slot's page when distinct. + */ +static int n31_neighbor_probe(struct n31_ftl_cs *ftl, u32 fmss_lba, + struct n31_map_entry *out) +{ + struct n31_map_entry hint; + struct s5l8740_cs_page *page; + u32 try_key[4]; + unsigned int ntry = 0, i, j; + u8 ce, cau, pg, sl; + u16 blk; + int slot, ret, sess; + + if (fmss_lba > 0 && + !n31_map_lookup_hint(ftl, fmss_lba - 1, &hint)) { + try_key[ntry++] = hint.phys_key; + try_key[ntry++] = n31_phys_succ(hint.phys_key); + } + if (!n31_map_lookup_hint(ftl, fmss_lba + 1, &hint)) { + try_key[ntry++] = hint.phys_key; + try_key[ntry++] = n31_phys_pred(hint.phys_key); + } + if (!ntry) + return -ENOENT; + + /* + * GFP_NOIO, and no zeroing. + * + * struct s5l8740_cs_page is about 16 KiB, so this is an order-2 + * allocation on a path the block layer can enter during writeback -- + * GFP_KERNEL there lets reclaim recurse back into the filesystem + * that is waiting on this read. It mattered less when the fallback + * path was dead; it is live now. + * + * The zeroing was redundant either way: s5l8740_nand_cs_phys_read_slc() + * memsets the whole struct before it fills it. + */ + page = kmalloc(sizeof(*page), GFP_NOIO); + if (!page) + return -ENOMEM; + + /* Arm CS for the probe reads; nests if a caller already holds one. */ + sess = s5l8740_nand_dma_session_begin(); + + for (i = 0; i < ntry; i++) { + if (try_key[i] == N31_PHYS_KEY_INVALID) + continue; + /* Skip duplicate pages already tried. */ + for (j = 0; j < i; j++) { + u8 ce2, cau2, pg2, sl2; + u16 blk2; + + if (try_key[j] == N31_PHYS_KEY_INVALID) + continue; + n31_phys_unpack(try_key[i], &ce, &cau, &blk, &pg, &sl); + n31_phys_unpack(try_key[j], &ce2, &cau2, &blk2, &pg2, + &sl2); + if (ce == ce2 && cau == cau2 && blk == blk2 && + pg == pg2) + goto next; + } + n31_phys_unpack(try_key[i], &ce, &cau, &blk, &pg, &sl); + (void)sl; + ret = s5l8740_nand_cs_phys_read(ce, cau, blk, pg, page); + if (ret) + continue; + n31_map_ingest_page(ftl, ce, cau, blk, pg, page); + slot = s5l8740_nand_meta_pick_lba(page, fmss_lba); + if (slot >= 0) { + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(page); + return n31_map_lookup_hint(ftl, fmss_lba, out); + } +next: + ; + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(page); + return -ENOENT; +} + +static int n31_ftl_read_fmss_lba_flags(struct n31_ftl_cs *ftl, u32 fmss_lba, + void *dst, bool allow_demand) +{ + struct n31_map_entry e; + struct n31_lba_map_entry leg; + struct s5l8740_cs_page *page; + int slot, ret, sess; + u8 ce, cau, pg, sl; + u16 blk; + u32 p_ord; + bool have_hint = false; + + if (!ftl || !dst) + return -EINVAL; + + /* Whimory L2V is authoritative after CXT→BTOC recover. */ + if (ftl->whimory_backed && whimory_l2v_ready()) { + dev_dbg(ftl->dev, + "read disk? via L2V_Search fmss_lba=%u\n", + fmss_lba); + ret = whimory_read_fmss_lba(fmss_lba, dst); + if (!ret) + return 0; + /* Fall through to hash/vec if Search miss (sparse holes). */ + } + + if (ftl->vec.ready) { + ret = n31_vecmap_lookup(&ftl->vec, fmss_lba, &p_ord); + if (!ret) { + n31_ordinal_to_phys(p_ord, &ce, &cau, &blk, &pg, &sl); + n31_entry_from_phys(&e, fmss_lba, + n31_phys_pack(ce, cau, blk, pg, sl), + 0, 0x01); + have_hint = true; + } else if (ret == -EUCLEAN) { + /* + * L2V and V2L disagree about this LBA. That is a + * reason to distrust the vecmap, not a reason to + * abandon the read: the hash map, the neighbour probe + * and the demand scan below are independent of it and + * may well know the answer. + * + * Returning -EUCLEAN here skipped all three. And the + * disagreement is expected by construction -- + * n31_esc_add() de-duplicates escapes by key with + * last-writer-wins, so two LBAs claiming one physical + * ordinal genuinely cannot agree on both axes. + */ + dev_dbg(ftl->dev, + "vecmap cross-check failed for fmss_lba=%u; falling through\n", + fmss_lba); + } + } + + if (!have_hint) { + ret = n31_map_lookup_hint(ftl, fmss_lba, &e); + if (ret) + ret = n31_neighbor_probe(ftl, fmss_lba, &e); + if (ret && allow_demand && ftl_demand_scan) { + ret = n31_demand_scan_for(ftl, fmss_lba); + if (!ret) + ret = n31_map_lookup_hint(ftl, fmss_lba, &e); + } + if (ret) + return -ENOENT; + } + + n31_entry_to_legacy(&e, &leg); + ce = leg.ce; + cau = leg.cau; + blk = leg.block; + pg = leg.page; + + /* + * GFP_NOIO, and no zeroing. + * + * struct s5l8740_cs_page is about 16 KiB, so this is an order-2 + * allocation on a path the block layer can enter during writeback -- + * GFP_KERNEL there lets reclaim recurse back into the filesystem + * that is waiting on this read. It mattered less when the fallback + * path was dead; it is live now. + * + * The zeroing was redundant either way: s5l8740_nand_cs_phys_read_slc() + * memsets the whole struct before it fills it. + */ + page = kmalloc(sizeof(*page), GFP_NOIO); + if (!page) + return -ENOMEM; + + /* + * Arm CS for this read. + * + * Nothing did, and the disk-lifetime session that was supposed to + * cover it never actually existed -- so every read that got this far + * came back -EAGAIN from the dma_dry test inside + * s5l8740_nand_cs_phys_read(), before f->lock, in microseconds. That + * is the whole hash/vec fallback path, and it failed silently. + * + * Sessions nest, so this costs nothing when a caller above already + * holds one. + */ + sess = s5l8740_nand_dma_session_begin(); + ret = s5l8740_nand_cs_phys_read(ce, cau, blk, pg, page); + if (sess == 0) + s5l8740_nand_dma_session_end(); + if (ret) { + dev_err_ratelimited(ftl->dev, + "CS read failed fmss_lba=%u ce=%u cau=%u blk=%u pg=%u: %d\n", + fmss_lba, ce, cau, blk, pg, ret); + ret = -EIO; + goto out; + } + + n31_map_ingest_page(ftl, ce, cau, blk, pg, page); + + slot = s5l8740_nand_meta_pick_lba(page, fmss_lba); + if (slot < 0) { + ret = -EUCLEAN; + goto out; + } + n31_phys_unpack(e.phys_key, &ce, &cau, &blk, &pg, &sl); + if ((u8)slot != sl) + dev_dbg(ftl->dev, + "lba=%u map_slot=%u meta_slot=%u\n", + fmss_lba, sl, slot); + + memcpy(dst, page->data[slot], N31_DATA_SLOT_SIZE); + ret = 0; +out: + kfree(page); + return ret; +} + +int n31_ftl_read_fmss_lba(struct n31_ftl_cs *ftl, u32 fmss_lba, void *dst) +{ + return n31_ftl_read_fmss_lba_flags(ftl, fmss_lba, dst, true); +} + +static int n31_ftl_read_disk_lba_flags(struct n31_ftl_cs *ftl, u32 disk_lba, + void *dst, bool allow_demand) +{ + u32 fmss_lba; + + if (!ftl || !dst) + return -EINVAL; + if (!ftl->fat_base_valid) + return -ENODEV; + if (disk_lba >= ftl->fat_total_sectors) + return -ERANGE; + + fmss_lba = ftl->fat_base_lba + disk_lba; + dev_dbg(ftl->dev, + "disk_lba=%u -> fmss_lba=%u (fat_base=%u)\n", + disk_lba, fmss_lba, ftl->fat_base_lba); + return n31_ftl_read_fmss_lba_flags(ftl, fmss_lba, dst, allow_demand); +} + +int n31_ftl_read_disk_lba(struct n31_ftl_cs *ftl, u32 disk_lba, void *dst) +{ + return n31_ftl_read_disk_lba_flags(ftl, disk_lba, dst, true); +} + +static int n31_ftl_apply_bpb(struct n31_ftl_cs *ftl, u32 fmss_lba, + u32 total, const u8 *sector) +{ + if (!sector) + return -EINVAL; + ftl->fat_base_lba = fmss_lba; + ftl->fat_total_sectors = total; + ftl->fat_base_valid = true; + memcpy(ftl->bpb_sector, sector, N31_DATA_SLOT_SIZE); + n31_parse_bpb(sector, &ftl->layout); + ftl->bpb_cached = true; + return 0; +} + +static int n31_ftl_find_bpb(struct n31_ftl_cs *ftl) +{ + unsigned int bkt; + struct n31_map_node *n; + u8 *buf; + int ret, sess; + unsigned int found = 0; + char cand_log[512]; + unsigned int cand_n = 0; + + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + kfree(buf); + return sess; + } + + mutex_lock(&ftl->lock); + ftl->bpb_ncand = 0; + ftl->fat_base_valid = false; + ftl->bpb_cached = false; + cand_log[0] = '\0'; + + hash_for_each(ftl->map, bkt, n, hnode) { + u32 total = 0; + unsigned int i; + + if (!n->e.valid) + continue; + if (n->e.type != S5L8740_NAND_META_TYPE_DATA && + n->e.type != S5L8740_NAND_META_TYPE_DATA2) + continue; + ret = n31_ftl_read_fmss_lba(ftl, n->e.fmss_lba, buf); + if (ret) + continue; + if (!n31_bpb_looks_valid(buf, &total)) + continue; + + /* Dedup same fmss_lba (keep higher weave). */ + for (i = 0; i < ftl->bpb_ncand; i++) { + if (ftl->bpb_candidates[i] != n->e.fmss_lba) + continue; + if (n31_weave_newer(n->e.weave, ftl->bpb_cand_weave[i])) { + ftl->bpb_cand_weave[i] = n->e.weave; + ftl->bpb_cand_total[i] = total; + memcpy(ftl->bpb_cand_oem[i], buf + 3, 8); + ftl->bpb_cand_oem[i][8] = '\0'; + memcpy(ftl->bpb_cand_sector[i], buf, + N31_DATA_SLOT_SIZE); + } + goto next; + } + if (ftl->bpb_ncand < N31_BPB_CANDIDATES_MAX) { + i = ftl->bpb_ncand++; + ftl->bpb_candidates[i] = n->e.fmss_lba; + ftl->bpb_cand_weave[i] = n->e.weave; + ftl->bpb_cand_total[i] = total; + memcpy(ftl->bpb_cand_oem[i], buf + 3, 8); + ftl->bpb_cand_oem[i][8] = '\0'; + memcpy(ftl->bpb_cand_sector[i], buf, N31_DATA_SLOT_SIZE); + } + found++; +next: + ; + } + + for (bkt = 0; bkt < ftl->bpb_ncand && cand_n < sizeof(cand_log) - 96; + bkt++) + cand_n += scnprintf(cand_log + cand_n, sizeof(cand_log) - cand_n, + " cand%u fmss=%u weave=%012llx oem='%.8s' " + "total=%u\n", + bkt + 1, ftl->bpb_candidates[bkt], + (unsigned long long)ftl->bpb_cand_weave[bkt], + ftl->bpb_cand_oem[bkt], + ftl->bpb_cand_total[bkt]); + + scnprintf(ftl->bpb_log, sizeof(ftl->bpb_log), + "bpb_scan candidates=%u\n%s", ftl->bpb_ncand, cand_log); + scnprintf(ftl->last_log, sizeof(ftl->last_log), "%s", ftl->bpb_log); + dev_info(ftl->dev, "%s", ftl->last_log); + mutex_unlock(&ftl->lock); + + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + return found || ftl->bpb_ncand ? 0 : -ENOENT; +} + +/* + * Newest weave first among candidates that pass FAT-critical. A slightly + * older *UOKJIHC volume that mounts beats a newer BPB with a broken FAT. + */ +static int n31_ftl_select_bpb(struct n31_ftl_cs *ftl) +{ + unsigned int i, j; + int best = -1; + unsigned int best_ok = 0; + bool best_apple = false; + u64 best_weave = 0; + u8 *sector; + + if (!ftl->bpb_ncand) + return -ENOENT; + + /* + * One 4 KiB swap buffer for the sort below, on the heap. It used to + * be a local inside the loop, which put a 4160-byte frame on a + * kernel stack that has nothing like that to spare. + */ + sector = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!sector) + return -ENOMEM; + + /* Insertion-sort candidates by weave descending (n is tiny). */ + for (i = 1; i < ftl->bpb_ncand; i++) { + u32 fmss = ftl->bpb_candidates[i]; + u64 weave = ftl->bpb_cand_weave[i]; + u32 total = ftl->bpb_cand_total[i]; + char oem[9]; + + memcpy(oem, ftl->bpb_cand_oem[i], 9); + memcpy(sector, ftl->bpb_cand_sector[i], N31_DATA_SLOT_SIZE); + j = i; + while (j > 0 && + n31_weave_newer(weave, ftl->bpb_cand_weave[j - 1])) { + ftl->bpb_candidates[j] = ftl->bpb_candidates[j - 1]; + ftl->bpb_cand_weave[j] = ftl->bpb_cand_weave[j - 1]; + ftl->bpb_cand_total[j] = ftl->bpb_cand_total[j - 1]; + memcpy(ftl->bpb_cand_oem[j], ftl->bpb_cand_oem[j - 1], + 9); + memcpy(ftl->bpb_cand_sector[j], + ftl->bpb_cand_sector[j - 1], + N31_DATA_SLOT_SIZE); + j--; + } + ftl->bpb_candidates[j] = fmss; + ftl->bpb_cand_weave[j] = weave; + ftl->bpb_cand_total[j] = total; + memcpy(ftl->bpb_cand_oem[j], oem, 9); + memcpy(ftl->bpb_cand_sector[j], sector, N31_DATA_SLOT_SIZE); + } + + for (i = 0; i < ftl->bpb_ncand; i++) { + bool apple, fatsig; + int vret; + + n31_ftl_apply_bpb(ftl, ftl->bpb_candidates[i], + ftl->bpb_cand_total[i], + ftl->bpb_cand_sector[i]); + vret = n31_validate_fat_critical(ftl); + apple = !memcmp(ftl->bpb_cand_oem[i], "*UOKJIHC", 8); + fatsig = n31_fat_first_sector_ok(ftl); + + dev_info(ftl->dev, + "bpb_try fmss=%u weave=%012llx oem='%.8s' " + "crit=%u/%u ret=%d\n", + ftl->bpb_candidates[i], + (unsigned long long)ftl->bpb_cand_weave[i], + ftl->bpb_cand_oem[i], ftl->fat_crit_ok_n, + ftl->fat_crit_need_n, vret); + + dev_info(ftl->dev, "bpb_try fmss=%u fatsig=%d\n", + ftl->bpb_candidates[i], fatsig); + + /* + * A candidate whose first FAT sector is not a FAT is the wrong + * volume base, however well its critical sectors read. That is + * the entire failure this check exists for, so it gates both + * the early-out below and the scoring after it. + */ + if (!fatsig) + continue; + + /* Perfect critical set: newest weave wins immediately. */ + if (!vret && ftl->fat_crit_ok_n == ftl->fat_crit_need_n && + ftl->fat_crit_need_n > 0) { + best = i; + break; + } + + if (ftl->fat_crit_ok_n < 3) + continue; + if (best < 0 || ftl->fat_crit_ok_n > best_ok || + (ftl->fat_crit_ok_n == best_ok && apple && !best_apple) || + (ftl->fat_crit_ok_n == best_ok && apple == best_apple && + n31_weave_newer(ftl->bpb_cand_weave[i], best_weave))) { + best = i; + best_ok = ftl->fat_crit_ok_n; + best_apple = apple; + best_weave = ftl->bpb_cand_weave[i]; + } + } + + /* + * No candidate survived the gates, and index 0 is not a safe guess. + * + * Candidates are sorted newest weave first, so falling back to 0 + * selected exactly the candidate n31_fat_first_sector_ok() had just + * rejected -- on this unit fmss 49285, whose FAT is not a FAT and + * whose selection puts the whole volume six sectors out. That is not + * a degraded mount, it is a wrong one: every FAT read lands in the + * middle of the table and the disk looks corrupt. + * + * A disk that does not appear is something a person can diagnose. + */ + if (best < 0) { + dev_err(ftl->dev, + "bpb: no candidate passed the FAT signature check (%u tried) -- refusing to guess\n", + ftl->bpb_ncand); + ftl->fat_base_valid = false; + ftl->fat_critical_ok = false; + ftl->enable_gate_ok = false; + scnprintf(ftl->bpb_log, sizeof(ftl->bpb_log), + "bpb: no valid candidate of %u; volume base unknown\n", + ftl->bpb_ncand); + kfree(sector); + return -ENOENT; + } + + n31_ftl_apply_bpb(ftl, ftl->bpb_candidates[best], + ftl->bpb_cand_total[best], + ftl->bpb_cand_sector[best]); + n31_validate_fat_critical(ftl); + + scnprintf(ftl->bpb_log, sizeof(ftl->bpb_log), + "fat_base_lba=%u valid=%d total=%u candidates=%u " + "oem='%.8s' weave=%012llx ext_flags=0x%04x " + "active_fat=%u mirror=%s selected=%u crit=%u/%u " + "itunesdb=%d music_dirs=%u fat1_disk_lba=%u\n", + ftl->fat_base_lba, ftl->fat_base_valid, ftl->fat_total_sectors, + ftl->bpb_ncand, ftl->bpb_cand_oem[best], + (unsigned long long)ftl->bpb_cand_weave[best], + ftl->layout.ext_flags, ftl->layout.ext_flags & 0xF, + (ftl->layout.ext_flags & 0x80) ? "off" : "on", + best + 1, ftl->fat_crit_ok_n, ftl->fat_crit_need_n, + ftl->fat_sem_itunesdb, ftl->fat_sem_music_dirs, + ftl->layout.fat_start + ftl->layout.fat_size_32); + dev_info(ftl->dev, "%s", ftl->bpb_log); + for (i = 0; i < ftl->bpb_ncand; i++) + dev_info(ftl->dev, + "BPB_CAND #%u fmss_lba=%u weave=%012llx oem='%.8s' " + "total=%u selected=%s reason=%s\n", + i + 1, ftl->bpb_candidates[i], + (unsigned long long)ftl->bpb_cand_weave[i], + ftl->bpb_cand_oem[i], ftl->bpb_cand_total[i], + i == (unsigned int)best ? "yes" : "no", + i == (unsigned int)best ? + "newest_valid_high_crit" : "not_selected"); + kfree(sector); + return ftl->fat_critical_ok ? 0 : -EAGAIN; +} + +static int n31_read_disk_checked(struct n31_ftl_cs *ftl, u32 disk_lba, + u8 *buf, const char *tag) +{ + int ret = n31_ftl_read_disk_lba(ftl, disk_lba, buf); + + if (ret) + dev_warn_ratelimited(ftl->dev, + "FAT-critical %s disk_lba=%u fail %d\n", + tag, disk_lba, ret); + else + dev_dbg(ftl->dev, "FAT-critical %s disk_lba=%u OK\n", + tag, disk_lba); + return ret; +} + +/* + * Semantic FAT checks beyond fat_critical N/N sector readability. + * Walk root cluster chain, count dir entries, search for known iPod names. + * FAT1 starts at reserved + fat_size32 (e.g. 32+942=974), not disk_lba=33. + */ +/* + * Does the first FAT sector actually look like the start of a FAT? + * + * n31_validate_fat_critical() only checks that its nine critical sectors + * *read*. It never looks at what came back, so a BPB whose volume base is + * wrong still scores a perfect 9/9 as long as the shifted sectors happen to + * be mapped -- which they were: + * + * BPB_CAND #1 fmss_lba=49285 weave=..ad crit=9/9 selected + * BPB_CAND #2 fmss_lba=49279 weave=..ac + * + * Six sectors apart, and the FAT belongs to the older one. Selecting #1 put + * the whole volume six sectors out: sector 0 still read as a valid BPB and + * sector 1 as a valid FSInfo, because those are static and were found by + * content, but the FAT region was offset. Reading it back showed a perfectly + * well-formed FAT32 chain stepping 0x400 per 4096-byte sector and starting + * six sectors early -- so vfat walked into the middle of the table and got + * "invalid cluster chain". + * + * Every FAT32 begins entry 0 with the media descriptor in the low byte and + * the FAT32 12-bit-wide EOC nibbles above it: F8 FF FF 0F. That single test + * separates the two candidates, and nothing that is genuinely the first FAT + * sector can fail it. + */ +static bool n31_fat_first_sector_ok(struct n31_ftl_cs *ftl) +{ + struct n31_fat_layout *L = &ftl->layout; + u8 *buf; + bool ok = false; + + if (!ftl->fat_base_valid || !L->fat_start) + return false; + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return false; + if (!n31_ftl_read_disk_lba(ftl, L->fat_start, buf)) { + u32 e0 = get_unaligned_le32(buf); + u32 e1 = get_unaligned_le32(buf + 4); + + /* + * Entry 0 low byte is the media descriptor and matches the + * one in the BPB at offset 0x15; the rest of entry 0 and all + * of entry 1 are set. Mask + * to 28 bits -- FAT32 entries carry only the low 28. + */ + ok = (e0 & 0xff) == ftl->bpb_sector[0x15] && + (e0 & 0x0fffff00) == 0x0fffff00 && + (e1 & 0x0fffffff) == 0x0fffffff; + } + kfree(buf); + return ok; +} + +static void n31_fat_semantic_validate(struct n31_ftl_cs *ftl) +{ + struct n31_fat_layout *L = &ftl->layout; + u8 *buf, *fat0 = NULL, *fat1 = NULL; + u32 cluster, chain_len = 0, entries = 0, music_dirs = 0; + u32 fat_diff = 0, i, max_chain = 64, max_fat_cmp = 4; + bool itunesdb = false, apps = false, nanoapps = false; + int ret; + + ftl->fat_sem_root_chain_len = 0; + ftl->fat_sem_root_entries = 0; + ftl->fat_sem_music_dirs = 0; + ftl->fat_sem_fat0_fat1_diff = 0; + ftl->fat_sem_itunesdb = false; + ftl->fat_sem_apps = false; + ftl->fat_sem_nanoapps = false; + ftl->fat_sem_log[0] = '\0'; + + if (!ftl->fat_base_valid || L->sectors_per_cluster == 0 || + L->root_cluster < 2) + return; + + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return; + + /* Compare first few FAT0 vs FAT1 sectors (mirror check). */ + if (L->num_fats >= 2 && L->fat_size_32) { + fat0 = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + fat1 = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (fat0 && fat1) { + u32 n = min(max_fat_cmp, L->fat_size_32); + + for (i = 0; i < n; i++) { + u32 d0 = L->fat_start + i; + u32 d1 = L->fat_start + L->fat_size_32 + i; + + if (n31_ftl_read_disk_lba(ftl, d0, fat0) || + n31_ftl_read_disk_lba(ftl, d1, fat1)) + break; + if (memcmp(fat0, fat1, N31_DATA_SLOT_SIZE)) + fat_diff++; + } + } + kfree(fat0); + kfree(fat1); + } + + cluster = L->root_cluster; + while (cluster >= 2 && cluster < 0x0ffffff8 && chain_len < max_chain) { + u32 disk_lba = L->data_start + + (cluster - 2) * L->sectors_per_cluster; + u32 s; + + for (s = 0; s < L->sectors_per_cluster; s++) { + unsigned int off; + + ret = n31_ftl_read_disk_lba(ftl, disk_lba + s, buf); + if (ret) + goto done; + for (off = 0; off + 32 <= N31_DATA_SLOT_SIZE; off += 32) { + const u8 *ent = buf + off; + char name[13]; + unsigned int n; + + if (ent[0] == 0x00) + goto chain_done; + if (ent[0] == 0xe5 || (ent[11] & 0x0f) == 0x0f) + continue; + entries++; + for (n = 0; n < 11; n++) + name[n] = ent[n] == ' ' ? '\0' : ent[n]; + name[11] = '\0'; + if (strnstr(name, "MUSIC", 11) || + (ent[11] & 0x10)) { + if (strnstr((const char *)ent, "MUSIC", 11) || + strnstr(name, "F00", 11) || + strnstr(name, "F01", 11) || + strnstr(name, "F02", 11)) + music_dirs++; + } + } + if (strnstr((const char *)buf, "iTunesDB", + N31_DATA_SLOT_SIZE) || + strnstr((const char *)buf, "ITUNESDB", + N31_DATA_SLOT_SIZE)) + itunesdb = true; + if (strnstr((const char *)buf, "iPod_Control", + N31_DATA_SLOT_SIZE) || + strnstr((const char *)buf, "IPOD_CON", + N31_DATA_SLOT_SIZE)) + entries++; /* ensure root hit is counted */ + if (strnstr((const char *)buf, "NanoApps", + N31_DATA_SLOT_SIZE) || + strnstr((const char *)buf, "NANOAPPS", + N31_DATA_SLOT_SIZE)) + nanoapps = true; + if (strnstr((const char *)buf, "Apps", + N31_DATA_SLOT_SIZE)) + apps = true; + if (strnstr((const char *)buf, "Music", + N31_DATA_SLOT_SIZE) || + strnstr((const char *)buf, "MUSIC", + N31_DATA_SLOT_SIZE)) + music_dirs++; + if (strnstr((const char *)buf, "F00", + N31_DATA_SLOT_SIZE) || + strnstr((const char *)buf, "F01", + N31_DATA_SLOT_SIZE)) + music_dirs++; + } + + /* Next cluster from active FAT (FAT0). */ + { + u32 fat_off = cluster * 4; + u32 fat_sec = L->fat_start + (fat_off / N31_DATA_SLOT_SIZE); + u32 fat_ent_off = fat_off % N31_DATA_SLOT_SIZE; + + ret = n31_ftl_read_disk_lba(ftl, fat_sec, buf); + if (ret) + break; + cluster = get_unaligned_le32(buf + fat_ent_off) & + 0x0fffffffu; + } + chain_len++; + } +chain_done: +done: + ftl->fat_sem_root_chain_len = chain_len; + ftl->fat_sem_root_entries = entries; + ftl->fat_sem_music_dirs = music_dirs; + ftl->fat_sem_fat0_fat1_diff = fat_diff; + ftl->fat_sem_itunesdb = itunesdb; + ftl->fat_sem_apps = apps; + ftl->fat_sem_nanoapps = nanoapps; + scnprintf(ftl->fat_sem_log, sizeof(ftl->fat_sem_log), + "fat_semantic fat_base=%u root_chain_len=%u root_entries=%u " + "music_dirs=%u itunesdb=%d apps=%d nanoapps=%d " + "fat0_fat1_diff=%u fat1_start_disk_lba=%u\n", + ftl->fat_base_lba, chain_len, entries, music_dirs, + itunesdb, apps, nanoapps, fat_diff, + L->fat_start + L->fat_size_32); + dev_info(ftl->dev, "%s", ftl->fat_sem_log); + kfree(buf); +} + +/* + * Validate BPB, FSInfo, FAT, and root-directory sectors before registering + * the block device. + */ +static int n31_validate_fat_critical(struct n31_ftl_cs *ftl) +{ + u8 *buf; + u32 total = 0; + int ret, sess; + struct n31_fat_layout *L; + unsigned int ok = 0, need = 0; + + if (!ftl->fat_base_valid) + return -ENODEV; + + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + kfree(buf); + return sess; + } + + mutex_lock(&ftl->lock); + ftl->fat_critical_ok = false; + ftl->enable_gate_ok = false; + ftl->disk0_ok = false; + + ret = n31_ftl_read_disk_lba(ftl, 0, buf); + ftl->last_ret = ret; + ftl->last_disk_lba = 0; + ftl->last_fmss_lba = ftl->fat_base_lba; + if (ret || !n31_bpb_looks_valid(buf, &total)) { + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "disk_lba0 BPB fail ret=%d\n", ret); + goto out; + } + memcpy(ftl->last_sector, buf, N31_DATA_SLOT_SIZE); + memcpy(ftl->bpb_sector, buf, N31_DATA_SLOT_SIZE); + ftl->bpb_cached = true; + ftl->disk0_ok = true; + ftl->fat_total_sectors = total; + n31_parse_bpb(buf, &ftl->layout); + L = &ftl->layout; + + scnprintf(ftl->layout_log, sizeof(ftl->layout_log), + "bps=%u spc=%u reserved=%u fats=%u fat_size32=%u " + "root_cluster=%u fsinfo=%u backup=%u total=%u " + "ext_flags=0x%04x active_fat=%u mirror=%s\n" + "fat_start=%u fat1_start=%u data_start=%u root_dir_lba=%u " + "fsinfo_lba=%u backup_boot_lba=%u\n", + L->bytes_per_sector, L->sectors_per_cluster, + L->reserved_sectors, L->num_fats, L->fat_size_32, + L->root_cluster, L->fsinfo_sector, L->backup_boot_sector, + L->total_sectors, L->ext_flags, L->ext_flags & 0xF, + (L->ext_flags & 0x80) ? "off" : "on", + L->fat_start, + L->fat_start + L->fat_size_32, + L->data_start, L->root_dir_lba, L->fsinfo_lba, + L->backup_boot_lba); + scnprintf(ftl->bpb_log, sizeof(ftl->bpb_log), + "oem='%.8s' jump=%02x%02x%02x %s", + buf + 3, buf[0], buf[1], buf[2], ftl->layout_log); + + need = 0; + ok = 0; +#define CRIT(lba, tag) do { \ + need++; \ + if ((lba) < ftl->fat_total_sectors && \ + !n31_read_disk_checked(ftl, (lba), buf, (tag))) \ + ok++; \ +} while (0) + + CRIT(0, "BPB"); + if (L->fsinfo_lba) + CRIT(L->fsinfo_lba, "FSInfo"); + if (L->backup_boot_lba && L->backup_boot_lba != L->fsinfo_lba) + CRIT(L->backup_boot_lba, "backup_BPB"); + CRIT(L->fat_start, "FAT0"); + if (L->fat_start + 1 < ftl->fat_total_sectors) + CRIT(L->fat_start + 1, "FAT1"); + CRIT(L->data_start, "root0"); + if (L->sectors_per_cluster >= 2) + CRIT(L->data_start + 1, "root1"); + if (L->sectors_per_cluster >= 3) + CRIT(L->data_start + 2, "root2"); + if (L->sectors_per_cluster >= 4) + CRIT(L->data_start + 3, "root3"); +#undef CRIT + + /* Whimory-backed maps must nearly pass; 5/9 must not register. */ + if (ftl->whimory_backed) + ftl->fat_critical_ok = (ok >= 8 && ftl->disk0_ok); + else + ftl->fat_critical_ok = (ok >= 3 && ftl->disk0_ok && + n31_map_find(ftl, ftl->fat_base_lba)); + ftl->enable_gate_ok = ftl->fat_critical_ok; + ftl->fat_crit_ok_n = ok; + ftl->fat_crit_need_n = need; + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "fat_critical ok=%u/%u gate=%d fat_base=%u\n%s", + ok, need, ftl->enable_gate_ok, ftl->fat_base_lba, + ftl->layout_log); + dev_info(ftl->dev, "%s", ftl->last_log); + if (ftl->fat_critical_ok) + n31_fat_semantic_validate(ftl); + ret = ftl->fat_critical_ok ? 0 : -EAGAIN; +out: + mutex_unlock(&ftl->lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + return ret; +} + +static int n31_read_disk_range(struct n31_ftl_cs *ftl, u32 start, u32 count) +{ + u8 *buf; + u32 i; + int ret, sess; + u32 prev_phys = N31_PHYS_KEY_INVALID; + + if (!count) + return -EINVAL; + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) { + kfree(buf); + return sess; + } + + mutex_lock(&ftl->lock); + ftl->range_ok = 0; + ftl->range_fail = 0; + ftl->range_miss = 0; + for (i = 0; i < count; i++) { + u32 disk_lba = start + i; + u32 fmss_lba; + struct n31_map_entry e; + + if (!ftl->fat_base_valid) { + ftl->range_fail++; + break; + } + fmss_lba = ftl->fat_base_lba + disk_lba; + if (n31_map_lookup_hint(ftl, fmss_lba, &e)) + ftl->range_miss++; + ret = n31_ftl_read_disk_lba(ftl, disk_lba, buf); + if (ret) { + ftl->range_fail++; + dev_dbg(ftl->dev, + "range miss disk_lba=%u ret=%d\n", + disk_lba, ret); + continue; + } + ftl->range_ok++; + if (!n31_map_lookup_hint(ftl, fmss_lba, &e)) { + if (prev_phys != N31_PHYS_KEY_INVALID && + e.phys_key != n31_phys_succ(prev_phys) && + e.phys_key != prev_phys) + dev_dbg(ftl->dev, + "range gap disk_lba=%u phys=%08x prev=%08x\n", + disk_lba, e.phys_key, prev_phys); + prev_phys = e.phys_key; + } + } + scnprintf(ftl->range_log, sizeof(ftl->range_log), + "range start=%u count=%u ok=%u fail=%u pre_miss=%u " + "demand_scans=%u\n", + start, count, ftl->range_ok, ftl->range_fail, + ftl->range_miss, ftl->demand_scans); + scnprintf(ftl->last_log, sizeof(ftl->last_log), "%s", ftl->range_log); + dev_dbg(ftl->dev, "%s", ftl->last_log); + mutex_unlock(&ftl->lock); + + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + return ftl->range_fail ? -EIO : 0; +} + +/* -------------------- block device -------------------- */ + +static bool n31_looks_like_firmware(const u8 *d) +{ + u32 total; + + if (!d) + return false; + /* Classic Apple IMG1 / 8900 header */ + if (d[0] == '8' && d[1] == '9' && d[2] == '0' && d[3] == '0') + return true; + if (d[0] == 'I' && d[1] == 'm' && d[2] == 'g') + return true; + /* WinPod-style volume marker */ + if (!memcmp(d + 0x100, "[hi]", 4)) + return true; + /* Nested FAT inside the pre-user range */ + if (n31_bpb_looks_valid(d, &total) && total > 0) + return true; + return false; +} + +static void n31_firmware_probe(struct n31_ftl_cs *ftl) +{ + unsigned int bkt; + struct n31_map_node *n; + u32 min_l = ~0u, max_l = 0; + unsigned int mapped = 0, magic = 0, sampled = 0; + u8 *buf; + + ftl->fw_valid = false; + ftl->fw_mapped = 0; + ftl->fw_magic_hits = 0; + ftl->fw_base_fmss = 0; + ftl->fw_nsectors = 0; + ftl->fw_log[0] = '\0'; + + if (!ftl->fat_base_valid || !ftl->fat_base_lba) + return; + + hash_for_each(ftl->map, bkt, n, hnode) { + u32 l = n->e.fmss_lba; + + if (l >= ftl->fat_base_lba) + continue; + mapped++; + if (l < min_l) + min_l = l; + if (l > max_l) + max_l = l; + } + ftl->fw_mapped = mapped; + + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (buf) { + hash_for_each(ftl->map, bkt, n, hnode) { + u32 l = n->e.fmss_lba; + + if (l >= ftl->fat_base_lba) + continue; + if (sampled >= 24) + break; + sampled++; + if (!n31_ftl_read_fmss_lba_flags(ftl, l, buf, false) && + n31_looks_like_firmware(buf)) + magic++; + } + kfree(buf); + } + ftl->fw_magic_hits = magic; + + if (fw_start_lba >= 0) + ftl->fw_base_fmss = (u32)fw_start_lba; + else + ftl->fw_base_fmss = 0; + + if (fw_nsectors > 0) + ftl->fw_nsectors = (u32)fw_nsectors; + else if (ftl->fat_base_lba > ftl->fw_base_fmss) + ftl->fw_nsectors = ftl->fat_base_lba - ftl->fw_base_fmss; + else + ftl->fw_nsectors = 0; + + ftl->fw_valid = fw_force || magic > 0 || mapped >= 4; + if (!ftl->fw_nsectors) + ftl->fw_valid = false; + + scnprintf(ftl->fw_log, sizeof(ftl->fw_log), + "fw_valid=%d base=%u nsectors=%u mapped=%u magic=%u " + "min_lba=%u max_lba=%u force=%d\n", + ftl->fw_valid, ftl->fw_base_fmss, ftl->fw_nsectors, + mapped, magic, + mapped ? min_l : 0, mapped ? max_l : 0, fw_force); + dev_info(ftl->dev, "%s", ftl->fw_log); +} + +/* + * VFAT directory bread failures land here as L2V misses. Print address-space + * math + neighbor map presence so we can tell "not scanned yet" from corruption. + */ +static void n31_log_read_miss(struct n31_ftl_cs *ftl, struct n31_ftl_slice *sl, + u32 fmss_lba, u32 disk_lba, int ret) +{ + struct n31_fat_layout *L = &ftl->layout; + u32 cluster = 0; + int i; + u8 ce = 0, cau = 0, page = 0, slot = 0; + u16 blk = 0; + u64 weave = 0; + int phys_ret; + + ftl->read_miss_count++; + + /* + * Refill the description budget once the window has passed, and say + * how many misses went by unexplained while it was empty. A count + * that only appears in sysfs is a count nobody reads. + */ + if (read_miss_diag_window_ms && + (!ftl->miss_window_start || + time_after(jiffies, ftl->miss_window_start + + msecs_to_jiffies(read_miss_diag_window_ms)))) { + if (ftl->miss_window_suppressed) + dev_err(ftl->dev, + "read miss: %u further misses not described (%u since load)\n", + ftl->miss_window_suppressed, + ftl->read_miss_count); + ftl->miss_window_start = jiffies; + ftl->miss_window_shown = 0; + ftl->miss_window_suppressed = 0; + } + + if (read_miss_diag_max && ftl->miss_window_shown >= read_miss_diag_max) { + ftl->miss_window_suppressed++; + return; + } + ftl->miss_window_shown++; + if (L->valid && L->sectors_per_cluster && + disk_lba >= L->data_start) { + cluster = ((disk_lba - L->data_start) / + L->sectors_per_cluster) + 2; + } + + dev_err_ratelimited(ftl->dev, + "read miss %s fmss_lba=%u disk_lba=%u ret=%d " + "fat_base=%u data_start=%u spc=%u cluster~=%u miss_n=%u\n", + sl->gd ? sl->gd->disk_name : "?", + fmss_lba, disk_lba, ret, + ftl->fat_base_lba, L->data_start, L->sectors_per_cluster, + cluster, ftl->read_miss_count); + + for (i = -4; i <= 4; i++) { + u32 n = fmss_lba + i; + u8 nce = 0, ncau = 0, npg = 0, nslot = 0; + u16 nblk = 0; + u64 nw = 0; + int nr; + + if ((int)fmss_lba + i < 0) + continue; + nr = whimory_l2v_search_phys(n, &nce, &ncau, &nblk, &npg, + &nslot, &nw); + if (!nr) + dev_err_ratelimited(ftl->dev, + " neighbor fmss_lba=%u MAPPED ce=%u cau=%u " + "blk=%u pg=%u slot=%u weave=%012llx\n", + n, nce, ncau, nblk, npg, nslot, + (unsigned long long)nw); + else if (i == 0) + dev_err_ratelimited(ftl->dev, + " neighbor fmss_lba=%u UNMAPPED ret=%d\n", + n, nr); + } + + phys_ret = whimory_l2v_search_phys(fmss_lba, &ce, &cau, &blk, &page, + &slot, &weave); + if (!phys_ret) + dev_err_ratelimited(ftl->dev, + " L2V suddenly mapped after miss? ce=%u cau=%u blk=%u " + "pg=%u slot=%u\n", + ce, cau, blk, page, slot); + (void)phys_ret; +} + +static void n31_ftl_submit_bio(struct bio *bio) +{ + struct n31_ftl_slice *sl = bio->bi_bdev->bd_disk->private_data; + struct n31_ftl_cs *ftl; + struct bvec_iter iter; + struct bio_vec bvec; + sector_t sector = bio->bi_iter.bi_sector; + u64 pos = (u64)sector << 9; + int ret = 0; + + if (!sl || !sl->ftl || !sl->nsectors) { + pr_err_ratelimited("s5l8740-ftl: bio on an unbound slice\n"); + bio_io_error(bio); + return; + } + ftl = sl->ftl; + if (sl->kind != N31_SLICE_FIRMWARE && !ftl->enable_gate_ok) { + dev_err_ratelimited(ftl->dev, + "bio refused: FAT-critical gate is not open\n"); + bio_io_error(bio); + return; + } + if (op_is_write(bio_op(bio)) || bio_op(bio) == REQ_OP_DISCARD || + bio_op(bio) == REQ_OP_WRITE_ZEROES) { + /* Read-only by construction; not worth a log line per write. */ + bio->bi_status = BLK_STS_IOERR; + bio_endio(bio); + return; + } + if ((sector & 7) != 0) { + dev_err_ratelimited(ftl->dev, + "bio refused: sector %llu is not 4 KiB aligned\n", + (unsigned long long)sector); + bio_io_error(bio); + return; + } + + /* + * Track the position in bytes, not in 512-byte sectors. + * + * The copy used to come from the head of the bounce buffer + * unconditionally, and the position advanced by n/512 -- so a bvec + * that was not a whole number of 4 KiB sectors left the LBA + * arithmetic truncating back onto the sector just read, copying its + * first bytes a second time. That is silent corruption rather than an + * error, and n/512 could also round to zero and stall. Bytes cannot + * do either, and the intra-sector offset falls out of the same + * variable. The Whimory front-end has always done it this way. + */ + bio_for_each_segment(bvec, bio, iter) { + u8 *dst = kmap_local_page(bvec.bv_page) + bvec.bv_offset; + unsigned int done = 0; + + while (done < bvec.bv_len) { + u32 off = (u32)(pos >> 12); + unsigned int soff = (unsigned int)(pos & (N31_DATA_SLOT_SIZE - 1)); + u32 fmss_lba; + unsigned int n = min_t(unsigned int, + bvec.bv_len - done, + N31_DATA_SLOT_SIZE - soff); + + if (off >= sl->nsectors) { + dev_err_ratelimited(ftl->dev, + "bio past end: sector %u of %u on %s\n", + off, sl->nsectors, + sl->gd ? sl->gd->disk_name : "?"); + ret = -ERANGE; + kunmap_local(dst); + goto done; + } + fmss_lba = sl->base_fmss + off; + mutex_lock(&ftl->lock); + ret = n31_ftl_read_fmss_lba_flags(ftl, fmss_lba, + ftl->bounce, false); + if (!ret) + memcpy(dst + done, ftl->bounce + soff, n); + else + n31_log_read_miss(ftl, sl, fmss_lba, off, ret); + mutex_unlock(&ftl->lock); + if (ret) { + kunmap_local(dst); + goto done; + } + done += n; + pos += n; + } + kunmap_local(dst); + } +done: + if (ret) + bio_io_error(bio); + else + bio_endio(bio); +} + +static const struct block_device_operations n31_ftl_bd_ops = { + .owner = THIS_MODULE, + .submit_bio = n31_ftl_submit_bio, +}; + +static int n31_slice_register(struct n31_ftl_slice *sl, const char *name, + u32 base_fmss, u32 nsectors, + enum n31_slice_kind kind) +{ + struct queue_limits lim = { + .logical_block_size = N31_DATA_SLOT_SIZE, + .physical_block_size = N31_DATA_SLOT_SIZE, + .io_min = N31_DATA_SLOT_SIZE, + }; + struct gendisk *gd; + int ret; + + if (!sl || !sl->ftl || !nsectors) + return -EINVAL; + if (sl->gd) + return 0; + + gd = blk_alloc_disk(&lim, NUMA_NO_NODE); + if (IS_ERR(gd)) + return PTR_ERR(gd); + + sl->base_fmss = base_fmss; + sl->nsectors = nsectors; + sl->kind = kind; + sl->gd = gd; + + gd->first_minor = 0; + gd->flags = GENHD_FL_NO_PART; + gd->fops = &n31_ftl_bd_ops; + gd->private_data = sl; + snprintf(gd->disk_name, DISK_NAME_LEN, "%s", name); + set_capacity(gd, (sector_t)nsectors * 8); + set_disk_ro(gd, 1); + ret = add_disk(gd); + if (ret) { + put_disk(gd); + sl->gd = NULL; + return ret; + } + dev_info(sl->ftl->dev, + "/dev/%s read-only, %u × 4096-byte sectors, base_fmss=%u\n", + name, nsectors, base_fmss); + return 0; +} + +static void n31_slice_unregister(struct n31_ftl_slice *sl) +{ + if (!sl || !sl->gd) + return; + del_gendisk(sl->gd); + put_disk(sl->gd); + sl->gd = NULL; + sl->nsectors = 0; +} + +static void n31_ftl_unregister_disk(struct n31_ftl_cs *ftl); + +static int n31_ftl_register_disk(struct n31_ftl_cs *ftl) +{ + int ret; + + if (!ftl_block_enable && !ftl->block_enable) + return -EPERM; + if (!ftl->enable_gate_ok || !ftl->fat_critical_ok) + return -EAGAIN; + if (ftl->ipod.gd) + return 0; + + /* + * No session is taken here any more. + * + * This used to open one and hold it for the lifetime of the gendisk, + * which -- now that sessions nest properly -- would mean dma_dry=0 + * and dma_armed=1 permanently. That is exactly the standing live C00 + * kick the NAND driver documents as rebooting the device, and it was + * only ever harmless because the acquisition silently failed with + * -EBUSY when called from inside n31_ftl_cs_bind_whimory(). + * + * Reads arm CS for the duration of the read instead; see + * n31_ftl_read_fmss_lba_flags(). + */ + ftl->ipod.ftl = ftl; + ftl->ftl_alias.ftl = ftl; + ftl->firmware.ftl = ftl; + + n31_firmware_probe(ftl); + + ret = n31_slice_register(&ftl->ipod, N31_IPOD_DISK_NAME, + ftl->fat_base_lba, ftl->fat_total_sectors, + N31_SLICE_IPOD); + if (ret) + goto fail; + + ret = n31_slice_register(&ftl->ftl_alias, N31_FTL_DISK_NAME, + ftl->fat_base_lba, ftl->fat_total_sectors, + N31_SLICE_FTL_ALIAS); + if (ret) + goto fail; + + if (ftl->fw_valid) { + ret = n31_slice_register(&ftl->firmware, N31_FW_DISK_NAME, + ftl->fw_base_fmss, ftl->fw_nsectors, + N31_SLICE_FIRMWARE); + if (ret) + dev_warn(ftl->dev, + "firmware disk register failed %d\n", ret); + } + return 0; + +fail: + n31_ftl_unregister_disk(ftl); + return ret; +} + +static void n31_ftl_unregister_disk(struct n31_ftl_cs *ftl) +{ + if (!ftl) + return; + n31_slice_unregister(&ftl->firmware); + n31_slice_unregister(&ftl->ftl_alias); + n31_slice_unregister(&ftl->ipod); +} + +/* -------------------- sysfs -------------------- */ + +static ssize_t ftl_map_stats_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, + "built=%d entries=%u pages=%u valid_records=%u " + "lba_min=%u lba_max=%u extents=%u largest=%u " + "duplicates=%u newer=%u has_49279=%d " + "demand_scans=%u read_misses=%u capped=%u skips=%u\n%s", + ftl->map_built, ftl->map_entries, ftl->map_pages, + ftl->map_data_recs, + ftl->lba_min == ~0u ? 0 : ftl->lba_min, ftl->lba_max, + ftl->extent_count, ftl->extent_largest, + ftl->map_collisions, ftl->newer_replacements, + n31_map_find(ftl, N31_FAT_BASE_DEFAULT) ? 1 : 0, + ftl->demand_scans, ftl->read_miss_count, + ftl->map_capped, ftl->map_skips, ftl->last_log); +} +static DEVICE_ATTR_RO(ftl_map_stats); + +static ssize_t ftl_extents_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int i, n = 0; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + n = sysfs_emit(buf, "%s", ftl->extents_log); + for (i = 0; i < ftl->extent_count && n < PAGE_SIZE - 80; i++) { + struct n31_lba_extent *ex = &ftl->extents[i]; + + n += scnprintf(buf + n, PAGE_SIZE - n, + "ex%u start_lba=%u len=%u phys=%08x type=%02x\n", + i, ex->start_lba, ex->len, ex->start_phys_key, + ex->type); + } + return n; +} +static DEVICE_ATTR_RO(ftl_extents); + +/* echo "CE CAU BLK_LO BLK_HI" > ftl_scan_block_window */ +static ssize_t ftl_scan_block_window_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int ce, cau, lo, hi; + int nf, sess, ret; + + if (!ftl) + return -ENODEV; + nf = sscanf(buf, "%u %u %u %u", &ce, &cau, &lo, &hi); + if (nf < 4) + return -EINVAL; + if (hi < lo) + return -EINVAL; + if (hi - lo > 32) + return -E2BIG; /* glass safety */ + + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) + return sess; + mutex_lock(&ftl->lock); + ret = n31_scan_block_window(ftl, ce, cau, lo, hi, true, true); + mutex_unlock(&ftl->lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_scan_block_window); + +static ssize_t ftl_map_build_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int stage = 1; + int sess, ret; + u16 lo, hi; + + if (!ftl) + return -ENODEV; + if (sscanf(buf, "%u", &stage) < 1) + stage = 1; + /* stage1 = blk63; stage2 = 62..66; all CE/CAU. stage3 refused. */ + if (stage >= 3) + return -EPERM; + lo = (stage <= 1) ? 63 : 62; + hi = (stage <= 1) ? 63 : 66; + + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) + return sess; + mutex_lock(&ftl->lock); + ret = n31_scan_banks_window(ftl, lo, hi, true); + mutex_unlock(&ftl->lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_map_build); + +static ssize_t ftl_fat_base_lba_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, + "fat_base_lba=%u valid=%d total=%u disk0_ok=%d " + "fat_critical_ok=%d gate_ok=%d\n", + ftl->fat_base_lba, ftl->fat_base_valid, + ftl->fat_total_sectors, ftl->disk0_ok, + ftl->fat_critical_ok, ftl->enable_gate_ok); +} + +static ssize_t ftl_fat_base_lba_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + u32 v; + + if (!ftl || kstrtou32(buf, 0, &v)) + return -EINVAL; + mutex_lock(&ftl->lock); + ftl->fat_base_lba = v; + ftl->fat_base_valid = true; + ftl->fat_base_autodetect = false; + ftl->disk0_ok = false; + ftl->fat_critical_ok = false; + ftl->enable_gate_ok = false; + mutex_unlock(&ftl->lock); + return count; +} +static DEVICE_ATTR_RW(ftl_fat_base_lba); + +static ssize_t ftl_bpb_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", ftl->bpb_log[0] ? ftl->bpb_log : "none\n"); +} +static DEVICE_ATTR_RO(ftl_bpb); + +static ssize_t ftl_layout_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", + ftl->layout_log[0] ? ftl->layout_log : "none\n"); +} +static DEVICE_ATTR_RO(ftl_layout); + +static ssize_t ftl_find_bpb_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + int ret; + + if (!ftl) + return -ENODEV; + ftl->fat_base_autodetect = true; + ret = n31_ftl_find_bpb(ftl); + if (!ret) + ret = n31_ftl_select_bpb(ftl); + if (!ret && ftl_block_enable) { + ftl->block_enable = true; + ret = n31_ftl_register_disk(ftl); + } + return (ret && ret != -ENOENT && ret != -EAGAIN) ? ret : count; +} +static DEVICE_ATTR_WO(ftl_find_bpb); + +static ssize_t ftl_read_last_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + size_t n = N31_DATA_SLOT_SIZE; + + if (!ftl) + return -ENODEV; + if (n > PAGE_SIZE) + n = PAGE_SIZE; + memcpy(buf, ftl->last_sector, n); + return n; +} +static DEVICE_ATTR_RO(ftl_read_last); + +static ssize_t ftl_read_fmss_lba_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + u32 fmss_lba; + int ret, sess; + + if (!ftl || kstrtou32(buf, 0, &fmss_lba)) + return -EINVAL; + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) + return sess; + mutex_lock(&ftl->lock); + ret = n31_ftl_read_fmss_lba(ftl, fmss_lba, ftl->last_sector); + ftl->last_ret = ret; + ftl->last_fmss_lba = fmss_lba; + ftl->last_disk_lba = ~0u; + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "ftl_read_fmss_lba=%u ret=%d first8=%*ph\n", + fmss_lba, ret, 8, ftl->last_sector); + dev_dbg(ftl->dev, "%s", ftl->last_log); + mutex_unlock(&ftl->lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_read_fmss_lba); + +static ssize_t ftl_read_disk_lba_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + u32 disk_lba; + int ret, sess; + + if (!ftl || kstrtou32(buf, 0, &disk_lba)) + return -EINVAL; + sess = s5l8740_nand_dma_session_begin(); + if (sess && sess != -EBUSY) + return sess; + mutex_lock(&ftl->lock); + ret = n31_ftl_read_disk_lba(ftl, disk_lba, ftl->last_sector); + ftl->last_ret = ret; + ftl->last_disk_lba = disk_lba; + ftl->last_fmss_lba = ftl->fat_base_valid ? + ftl->fat_base_lba + disk_lba : 0; + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "ftl_read_disk_lba=%u fmss_lba=%u ret=%d first8=%*ph\n", + disk_lba, ftl->last_fmss_lba, ret, 8, ftl->last_sector); + dev_dbg(ftl->dev, "%s", ftl->last_log); + mutex_unlock(&ftl->lock); + if (sess == 0) + s5l8740_nand_dma_session_end(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_read_disk_lba); + +/* echo "START COUNT" > ftl_read_disk_range */ +static ssize_t ftl_read_disk_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int start, n; + int ret; + + if (!ftl) + return -ENODEV; + if (sscanf(buf, "%u %u", &start, &n) < 2) + return -EINVAL; + if (n > 512) + return -E2BIG; + ret = n31_read_disk_range(ftl, start, n); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_read_disk_range); + +static ssize_t ftl_read_range_stats_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", + ftl->range_log[0] ? ftl->range_log : "none\n"); +} +static DEVICE_ATTR_RO(ftl_read_range_stats); + +static ssize_t ftl_enable_block_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int v = 0; + int ret; + + if (!ftl || kstrtouint(buf, 0, &v)) + return -EINVAL; + if (!v) { + n31_ftl_unregister_disk(ftl); + ftl->block_enable = false; + return count; + } + ret = n31_validate_fat_critical(ftl); + if (ret) + return ret; + ftl->block_enable = true; + ret = n31_ftl_register_disk(ftl); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_enable_block); + +static ssize_t ftl_vec_stats_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + struct n31_vecmap *v; + u32 p = N31_INVALID_P; + int cross = -ENOENT; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + v = &ftl->vec; + if (v->ready) + cross = n31_vecmap_lookup(v, N31_FAT_BASE_DEFAULT, &p); + return sysfs_emit(buf, "%scross_49279_ret=%d p=%u\n", + ftl->vec_log[0] ? ftl->vec_log : "ready=0\n", + cross, p); +} +static DEVICE_ATTR_RO(ftl_vec_stats); + +/* echo 1 > ftl_vec_build — recompress from current sparse hash */ +static ssize_t ftl_vec_build_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + int ret; + + if (!ftl) + return -ENODEV; + mutex_lock(&ftl->lock); + ret = n31_vecmap_rebuild_from_hash(ftl); + mutex_unlock(&ftl->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_vec_build); + +static ssize_t ftl_firmware_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", + ftl->fw_log[0] ? ftl->fw_log : "fw_valid=0\n"); +} +static DEVICE_ATTR_RO(ftl_firmware); + +struct n31_bpb_walk_ctx { + struct n31_ftl_cs *ftl; + u8 *buf; + unsigned int tried; + unsigned int max_try; +}; + +static void n31_ftl_note_bpb_cand(struct n31_ftl_cs *ftl, u32 fmss_lba, + u64 weave, const u8 *buf, u32 total) +{ + unsigned int i; + + for (i = 0; i < ftl->bpb_ncand; i++) { + if (ftl->bpb_candidates[i] != fmss_lba) + continue; + if (n31_weave_newer(weave, ftl->bpb_cand_weave[i])) { + ftl->bpb_cand_weave[i] = weave; + ftl->bpb_cand_total[i] = total; + memcpy(ftl->bpb_cand_oem[i], buf + 3, 8); + ftl->bpb_cand_oem[i][8] = '\0'; + memcpy(ftl->bpb_cand_sector[i], buf, + N31_DATA_SLOT_SIZE); + } + return; + } + if (ftl->bpb_ncand >= N31_BPB_CANDIDATES_MAX) + return; + i = ftl->bpb_ncand++; + ftl->bpb_candidates[i] = fmss_lba; + ftl->bpb_cand_weave[i] = weave; + ftl->bpb_cand_total[i] = total; + memcpy(ftl->bpb_cand_oem[i], buf + 3, 8); + ftl->bpb_cand_oem[i][8] = '\0'; + memcpy(ftl->bpb_cand_sector[i], buf, N31_DATA_SLOT_SIZE); +} + +static int n31_bpb_walk_fn(u32 start, u32 len, u32 vba, u64 weave, void *opaque) +{ + struct n31_bpb_walk_ctx *c = opaque; + u32 try_lba[2]; + unsigned int ntry = 0, i; + u32 total = 0; + int ret; + + (void)vba; + if (!c || !c->ftl || !c->buf || !len) + return 0; + if (c->tried >= c->max_try) + return 1; /* stop walk */ + + try_lba[ntry++] = start; + if (start < N31_FAT_BASE_DEFAULT && + start + len > N31_FAT_BASE_DEFAULT) + try_lba[ntry++] = N31_FAT_BASE_DEFAULT; + + for (i = 0; i < ntry; i++) { + if (c->tried >= c->max_try) + break; + c->tried++; + ret = whimory_read_fmss_lba(try_lba[i], c->buf); + if (ret) + continue; + if (!n31_bpb_looks_valid(c->buf, &total)) + continue; + mutex_lock(&c->ftl->lock); + n31_ftl_note_bpb_cand(c->ftl, try_lba[i], weave, c->buf, + total); + mutex_unlock(&c->ftl->lock); + } + return 0; +} + +/* + * Which LBAs are worth reading to find the volume, according to the media. + * + * The bind used to probe three hardcoded LBAs and then walk the interval + * map, reading up to max_try=512 mapped LBAs and asking each whether it + * looked like a BPB. That walk is 5.3 seconds of a 15-second mount, and on + * this unit it contributes nothing: the hardcoded probes have already found + * both candidates before it starts. + * + * The media states the answer outright. fmss_lba 0 is an MBR; measured + * here: + * + * 0x1BE: 80 10 0e 03 | 0c | 26 3d f3 | 40 c0 00 00 | 8a da 3a 00 + * + * type 0x0c (FAT32 LBA), start LBA 0x0000c040 = 49216. That sector does + * hold a BPB -- "MSDOS5.0", jump eb 58 -- and its hidden-sector count at + * +0x1C is 63. 49216 + 63 = 49279, which is the volume this driver + * selects, whose own BPB puts its backup boot sector at +6 = 49285. Both + * candidates the walk was finding are reachable from the partition table in + * two steps. + * + * So: the partition start, whatever its hidden count redirects to, and the + * reserved area of each -- where FAT keeps its FSInfo and its backup boot + * sector, so a volume whose primary is damaged is still found. Around + * eighty LBAs instead of 512, and derived rather than assumed. + * + * Returns 0 when there is no usable partition table, and the caller then + * behaves exactly as it did before. + */ +static unsigned int n31_bpb_partition_lbas(struct n31_ftl_cs *ftl, u8 *buf, + u32 *out, unsigned int max) +{ + static const u32 MBR_PART_OFF = 0x1be; + unsigned int n = 0, p, k, nbases = 0; + u32 bases[2]; + + if (!bpb_probe_partition || !ftl || !buf || !out) + return 0; + if (whimory_read_fmss_lba(0, buf)) + return 0; + if (buf[0x1fe] != 0x55 || buf[0x1ff] != 0xaa) + return 0; + + for (p = 0; p < 4 && nbases < ARRAY_SIZE(bases); p++) { + const u8 *e = buf + MBR_PART_OFF + p * 16; + u8 type = e[4]; + u32 base = get_unaligned_le32(e + 8); + + /* FAT12/16/32, CHS and LBA forms. */ + if (type != 0x01 && type != 0x04 && type != 0x06 && + type != 0x0b && type != 0x0c && type != 0x0e) + continue; + if (!base || base >= N31_BPB_LBA_SANE_MAX) + continue; + bases[nbases++] = base; + } + + for (p = 0; p < nbases; p++) { + u32 base = bases[p]; + u32 hidden = 0; + + /* Reading the base is what supplies the redirect. */ + if (!whimory_read_fmss_lba(base, buf)) + hidden = get_unaligned_le32(buf + 0x1c); + if (hidden >= N31_BPB_HIDDEN_MAX) + hidden = 0; + + for (k = 0; k < N31_BPB_RESERVED_PROBE && n < max; k++) + out[n++] = base + k; + if (!hidden) + continue; + for (k = 0; k < N31_BPB_RESERVED_PROBE && n < max; k++) + out[n++] = base + hidden + k; + } + return n; +} + +bool n31_ftl_cs_whimory_backed(void) +{ + return n31_ftl && n31_ftl->whimory_backed; +} + +/* True once /dev/s5l8740-ipod is live; a rebuild under it is destructive. */ +bool n31_ftl_cs_disk_registered(void) +{ + return n31_ftl && n31_ftl->ipod.gd; +} +EXPORT_SYMBOL_GPL(n31_ftl_cs_disk_registered); + +int n31_ftl_cs_bind_whimory(void) +{ + struct n31_ftl_cs *ftl = n31_ftl; + struct n31_bpb_walk_ctx ctx; + u8 *buf; + u8 ce, cau, page, slot; + u16 blk; + u64 weave = 0; + u32 total = 0; + int ret, sess; + static const u32 probes[] = { + N31_FAT_BASE_DEFAULT, 49279u, 49285u, 0u + }; + unsigned int i; + + if (!ftl) + return -ENODEV; + if (!whimory_l2v_ready()) + return -ENODEV; + + buf = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + sess = s5l8740_nand_dma_session_begin(); + mutex_lock(&ftl->lock); + ftl->whimory_backed = true; + ftl->map_built = true; + ftl->bpb_ncand = 0; + ftl->fat_base_valid = false; + mutex_unlock(&ftl->lock); + + /* Prefer known BPB LBA probes via L2V_Search. */ + for (i = 0; i < ARRAY_SIZE(probes); i++) { + ret = whimory_read_fmss_lba(probes[i], buf); + if (ret) + continue; + if (!n31_bpb_looks_valid(buf, &total)) + continue; + weave = 0; + whimory_l2v_search_phys(probes[i], &ce, &cau, &blk, &page, + &slot, &weave); + mutex_lock(&ftl->lock); + n31_ftl_note_bpb_cand(ftl, probes[i], weave, buf, total); + mutex_unlock(&ftl->lock); + } + + /* + * Then whatever the partition table names. The hardcoded probes + * above are right for this unit and were arrived at by finding them + * the slow way; these are derived from the media, so they keep + * working on a volume laid out differently. + */ + { + u32 *plist = kmalloc_array(N31_BPB_PROBE_MAX, sizeof(*plist), + GFP_KERNEL); + unsigned int np = 0; + + if (plist) { + np = n31_bpb_partition_lbas(ftl, buf, plist, + N31_BPB_PROBE_MAX); + for (i = 0; i < np; i++) { + if (whimory_read_fmss_lba(plist[i], buf)) + continue; + if (!n31_bpb_looks_valid(buf, &total)) + continue; + weave = 0; + whimory_l2v_search_phys(plist[i], &ce, &cau, + &blk, &page, &slot, + &weave); + mutex_lock(&ftl->lock); + n31_ftl_note_bpb_cand(ftl, plist[i], weave, + buf, total); + mutex_unlock(&ftl->lock); + } + kfree(plist); + } + dev_info(ftl->dev, + "bpb_probe partition lbas=%u candidates=%u\n", + np, ftl->bpb_ncand); + } + + /* + * The walk is the fallback. It reads up to 512 mapped LBAs looking + * for a boot sector, which is 5.3 seconds here, and it has nothing + * to add once a candidate exists -- the selection rule below picks + * among candidates, and a boot sector that neither the partition + * table nor the known probes name is not one this volume boots from. + */ + if (!ftl->bpb_ncand || !bpb_probe_partition) { + ctx.ftl = ftl; + ctx.buf = buf; + ctx.tried = 0; + ctx.max_try = 512; + whimory_range_walk(n31_bpb_walk_fn, &ctx); + } else { + dev_info(ftl->dev, + "bpb_probe skipping the range walk (%u candidate(s) already)\n", + ftl->bpb_ncand); + } + + mutex_lock(&ftl->lock); + if (!ftl->bpb_ncand) { + dev_err(ftl->dev, + "whimory bind: no BPB found in L2V ranges\n"); + ftl->whimory_backed = false; + mutex_unlock(&ftl->lock); + ret = -ENOENT; + goto out_sess; + } + /* Snapshot ncand; select_bpb/validate take the lock themselves. */ + mutex_unlock(&ftl->lock); + + ret = n31_ftl_select_bpb(ftl); + if (ret) { + mutex_lock(&ftl->lock); + ftl->whimory_backed = false; + mutex_unlock(&ftl->lock); + goto out_sess; + } + + /* Self-check: Search(fat_base) must resolve. */ + ret = whimory_l2v_search_phys(ftl->fat_base_lba, &ce, &cau, &blk, + &page, &slot, &weave); + dev_info(ftl->dev, + "whimory_bind fat_base=%u search=%d phys=%u/%u/%u/%u/%u " + "weave=%012llx cand=%u\n", + ftl->fat_base_lba, ret, ce, cau, blk, page, slot, + (unsigned long long)weave, ftl->bpb_ncand); + if (ret) { + mutex_lock(&ftl->lock); + ftl->whimory_backed = false; + mutex_unlock(&ftl->lock); + goto out_sess; + } + + if (ftl->enable_gate_ok && ftl_block_enable) { + mutex_lock(&ftl->lock); + n31_ftl_unregister_disk(ftl); + ret = n31_ftl_register_disk(ftl); + mutex_unlock(&ftl->lock); + } else { + ret = ftl->enable_gate_ok ? 0 : -EAGAIN; + } + + scnprintf(ftl->last_log, sizeof(ftl->last_log), + "whimory_bind ok backed=%d fat_base=%u gate=%d disk=%d\n", + ftl->whimory_backed, ftl->fat_base_lba, ftl->enable_gate_ok, + ret); + dev_info(ftl->dev, "%s", ftl->last_log); +out_sess: + if (sess == 0) + s5l8740_nand_dma_session_end(); + kfree(buf); + return ret; +} + +/* echo 1 > ftl_sftl_recover — CXT→BTOC→L2V on CS META, then bind disks */ +static ssize_t ftl_sftl_recover_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int v = 1; + int ret; + + (void)dev; + (void)attr; + if (sscanf(buf, "%u", &v) >= 1 && !v) + return count; + + ret = whimory_sftl_recover_cs(); + if (ret) + return ret; + /* + * Re-binding a disk that is already registered clears the BPB + * candidates and leaves the gendisk at capacity 0, so a live mount + * starts failing every read. Recovery that was a no-op must not + * disturb the disk it just declined to rebuild. + */ + if (n31_ftl_cs_disk_registered()) + return count; + ret = n31_ftl_cs_bind_whimory(); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_sftl_recover); + +/* echo > ftl_cxt_dump — report CXT bases/tags; never touches L2V */ +static ssize_t ftl_cxt_dump_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int v = 0; + int ret; + + (void)dev; + (void)attr; + if (sscanf(buf, "%u", &v) < 1) + v = 0; + ret = whimory_cxt_dump(v); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_cxt_dump); + +/* + * echo [fat_base] > ftl_cxt_candidate — build the CXT TREE map into a + * separate candidate array, diff it against the live brute-force map, and + * validate the FAT-critical sectors through it. Never mutates L2V. + */ +static ssize_t ftl_cxt_candidate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + unsigned int v = 0; + int ret; + + (void)dev; + (void)attr; + if (sscanf(buf, "%u", &v) < 1 || !v) + v = ftl ? ftl->fat_base_lba : 0; + ret = whimory_cxt_candidate(v); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(ftl_cxt_candidate); + +static ssize_t ftl_finishline_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, + "map_built=%d whimory_backed=%d entries=%u extents=%u " + "vec_ready=%d fat_base_lba=%u valid=%d disk0_ok=%d " + "fat_critical_ok=%d gate_ok=%d " + "ipod=%s ftl_alias=%s firmware=%s\n" + "last_ret=%d last_fmss=%u last_disk=%u\n%s%s%s", + ftl->map_built, ftl->whimory_backed ? 1 : 0, + ftl->map_entries, ftl->extent_count, + ftl->vec.ready ? 1 : 0, + ftl->fat_base_lba, ftl->fat_base_valid, ftl->disk0_ok, + ftl->fat_critical_ok, ftl->enable_gate_ok, + ftl->ipod.gd ? ftl->ipod.gd->disk_name : "(none)", + ftl->ftl_alias.gd ? ftl->ftl_alias.gd->disk_name : + "(none)", + ftl->firmware.gd ? ftl->firmware.gd->disk_name : + "(none)", + ftl->last_ret, ftl->last_fmss_lba, ftl->last_disk_lba, + ftl->last_log, + ftl->vec_log, + ftl->fw_log); +} +static DEVICE_ATTR_RO(ftl_finishline_status); + +static ssize_t ftl_fat_semantic_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", + ftl->fat_sem_log[0] ? ftl->fat_sem_log : + "none\n"); +} +static DEVICE_ATTR_RO(ftl_fat_semantic); + +static ssize_t ftl_phys_string_scan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int blocks = 0; + int hits; + + if (kstrtouint(buf, 0, &blocks)) + blocks = 0; + hits = whimory_phys_string_scan(blocks); + if (n31_ftl) + scnprintf(n31_ftl->string_scan_log, + sizeof(n31_ftl->string_scan_log), + "phys_string_scan blocks=%u hits=%d\n", blocks, hits); + return hits < 0 ? hits : count; +} +static DEVICE_ATTR_WO(ftl_phys_string_scan); + +static ssize_t ftl_logical_string_scan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct n31_ftl_cs *ftl = n31_ftl; + u8 *sec; + u32 i, nsectors = 4096, hits = 0; + int ret, sess; + static const char *const needles[] = { + "iTunesDB", "F00", "F01", "F02", "iPod_Control", "Music", + "Apps", "NanoApps", ".mp3", ".m4a", + }; + + if (!ftl || !ftl->fat_base_valid) + return -ENODEV; + if (kstrtouint(buf, 0, &nsectors)) + nsectors = 4096; + if (nsectors > ftl->fat_total_sectors) + nsectors = ftl->fat_total_sectors; + sec = kmalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!sec) + return -ENOMEM; + sess = s5l8740_nand_dma_session_begin(); + dev_info(ftl->dev, + "LOGICAL_STRING_SCAN start disk_lbas=0..%u via L2V\n", + nsectors); + for (i = 0; i < nsectors; i++) { + unsigned int ni; + + ret = n31_ftl_read_disk_lba(ftl, i, sec); + if (ret) + continue; + for (ni = 0; ni < ARRAY_SIZE(needles); ni++) { + if (!strnstr((const char *)sec, needles[ni], + N31_DATA_SLOT_SIZE)) + continue; + hits++; + if (hits <= 64) + dev_info(ftl->dev, + "LOGICAL_STRING hit=%s disk_lba=%u " + "fmss_lba=%u\n", + needles[ni], i, + ftl->fat_base_lba + i); + break; + } + if ((i & 0xff) == 0) + cond_resched(); + } + if (sess == 0) + s5l8740_nand_dma_session_end(); + scnprintf(ftl->string_scan_log, sizeof(ftl->string_scan_log), + "logical_string_scan disk_lbas=%u hits=%u\n", nsectors, hits); + dev_info(ftl->dev, "%s", ftl->string_scan_log); + kfree(sec); + return count; +} +static DEVICE_ATTR_WO(ftl_logical_string_scan); + +static ssize_t ftl_string_scan_log_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return sysfs_emit(buf, "no ftl\n"); + return sysfs_emit(buf, "%s", + ftl->string_scan_log[0] ? ftl->string_scan_log : + "none\n"); +} +static DEVICE_ATTR_RO(ftl_string_scan_log); + +/* + * The FPart system objects, and the device identity inside one of them. + * Read-only: this is what the flash says about itself. + */ +static ssize_t fpart_objects_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return whimory_fpart_objects_show(buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(fpart_objects); + +static ssize_t device_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return whimory_syscfg_show(buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(device_id); + +/* + * Hexdump of the same bytes the touch_cal binary attribute serves. + * + * Kept for reading by eye; it is bounded by PAGE_SIZE and so stops short + * of a full N31_TOUCH_CAL_LEN container. Anything that needs the whole + * blob should read the binary attribute instead. + */ +static ssize_t touch_cal_hex_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return whimory_touch_cal_show(buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(touch_cal_hex); + +static struct attribute *n31_ftl_finish_attrs[] = { + &dev_attr_touch_cal_hex.attr, + &dev_attr_fpart_objects.attr, + &dev_attr_device_id.attr, + &dev_attr_ftl_sftl_recover.attr, + &dev_attr_ftl_cxt_dump.attr, + &dev_attr_ftl_cxt_candidate.attr, + &dev_attr_ftl_map_build.attr, + &dev_attr_ftl_scan_block_window.attr, + &dev_attr_ftl_map_stats.attr, + &dev_attr_ftl_extents.attr, + &dev_attr_ftl_vec_stats.attr, + &dev_attr_ftl_vec_build.attr, + &dev_attr_ftl_firmware.attr, + &dev_attr_ftl_find_bpb.attr, + &dev_attr_ftl_fat_base_lba.attr, + &dev_attr_ftl_bpb.attr, + &dev_attr_ftl_layout.attr, + &dev_attr_ftl_fat_semantic.attr, + &dev_attr_ftl_phys_string_scan.attr, + &dev_attr_ftl_logical_string_scan.attr, + &dev_attr_ftl_string_scan_log.attr, + &dev_attr_ftl_read_fmss_lba.attr, + &dev_attr_ftl_read_disk_lba.attr, + &dev_attr_ftl_read_disk_range.attr, + &dev_attr_ftl_read_range_stats.attr, + &dev_attr_ftl_read_last.attr, + &dev_attr_ftl_enable_block.attr, + &dev_attr_ftl_finishline_status.attr, + NULL, +}; + +static const struct attribute_group n31_ftl_finish_group = { + .attrs = n31_ftl_finish_attrs, +}; + +int ftl_s5l8740_csmap_init(struct device *dev) +{ + struct n31_ftl_cs *ftl; + int ret; + + if (!dev) + return -EINVAL; + if (n31_ftl) + return -EBUSY; + + ftl = kzalloc(sizeof(*ftl), GFP_KERNEL); + if (!ftl) + return -ENOMEM; + ftl->bounce = kzalloc(N31_DATA_SLOT_SIZE, GFP_KERNEL); + if (!ftl->bounce) { + kfree(ftl); + return -ENOMEM; + } + ftl->dev = dev; + mutex_init(&ftl->lock); + hash_init(ftl->map); + ftl->lba_min = ~0u; + ftl->fat_base_lba = N31_FAT_BASE_DEFAULT; + ftl->fat_total_sectors = N31_FAT_TOTAL_DEFAULT; + ftl->fat_base_autodetect = true; + + ret = sysfs_create_group(&dev->kobj, &n31_ftl_finish_group); + if (ret) { + kfree(ftl->bounce); + kfree(ftl); + return ret; + } + n31_ftl = ftl; + dev_info(dev, + "CS map ready (scan → find_bpb → /dev/%s + /dev/%s)\n", + N31_IPOD_DISK_NAME, N31_FW_DISK_NAME); + return 0; +} + +void ftl_s5l8740_csmap_exit(struct device *dev) +{ + struct n31_ftl_cs *ftl = n31_ftl; + + if (!ftl) + return; + n31_ftl_unregister_disk(ftl); + if (dev) + sysfs_remove_group(&dev->kobj, &n31_ftl_finish_group); + mutex_lock(&ftl->lock); + n31_map_free(ftl); + mutex_unlock(&ftl->lock); + kfree(ftl->bounce); + kfree(ftl); + n31_ftl = NULL; +} diff --git a/drivers/misc/ftl-s5l8740-csmap.h b/drivers/misc/ftl-s5l8740-csmap.h new file mode 100755 index 00000000000000..4b7a996baa9371 --- /dev/null +++ b/drivers/misc/ftl-s5l8740-csmap.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef FTL_S5L8740_CSMAP_H +#define FTL_S5L8740_CSMAP_H + +#include +#include + +#define N31_FTL_DISK_NAME "s5l8740-ftl" /* FAT alias (compat) */ +#define N31_IPOD_DISK_NAME "s5l8740-ipod" /* user FAT volume */ +#define N31_FW_DISK_NAME "s5l8740-firmware" + +struct n31_lba_map_entry { + u8 ce; + u8 cau; + u16 block; + u8 page; + u8 slot; + u8 type; + u64 weave; + u32 fmss_lba; + bool present; +}; + +struct n31_ftl_cs; + +int ftl_s5l8740_csmap_init(struct device *dev); +void ftl_s5l8740_csmap_exit(struct device *dev); + +int n31_ftl_read_fmss_lba(struct n31_ftl_cs *ftl, u32 fmss_lba, void *dst); +int n31_ftl_read_disk_lba(struct n31_ftl_cs *ftl, u32 disk_lba, void *dst); + +/* + * After Whimory CXT→BTOC→L2V recover: bind csmap disks to L2V_Search + * (no full hash import — avoids multi-million node RAM). + */ +int n31_ftl_cs_bind_whimory(void); +bool n31_ftl_cs_disk_registered(void); +bool n31_ftl_cs_whimory_backed(void); + +/* Implemented in ftl-s5l8740-core.c (same module). */ +int whimory_sftl_recover_cs(void); +int whimory_cxt_dump(unsigned int max_vbas); +/* The FPart system objects, and the SysCfg identity inside one. */ +int whimory_fpart_objects_show(char *buf, size_t len); +int whimory_syscfg_show(char *buf, size_t len); +int whimory_touch_cal_show(char *buf, size_t len); +size_t whimory_syscfg_touch_cal(const u8 **out); +int whimory_cxt_candidate(u32 fat_base); +bool whimory_l2v_ready(void); +int whimory_read_fmss_lba(u32 lba, void *buf); +int whimory_range_walk(int (*fn)(u32 start, u32 len, u32 vba, u64 weave, + void *ctx), + void *ctx); +int whimory_l2v_search_phys(u32 lba, u8 *ce, u8 *cau, u16 *blk, u8 *page, + u8 *slot, u64 *weave); + +/* Physical NAND string scan (ignores L2V). Returns hit count. */ +int whimory_phys_string_scan(unsigned int max_blocks); + +#endif /* FTL_S5L8740_CSMAP_H */ diff --git a/drivers/misc/ftl-s5l8740-vecmap.c b/drivers/misc/ftl-s5l8740-vecmap.c new file mode 100755 index 00000000000000..3f35f197b66366 --- /dev/null +++ b/drivers/misc/ftl-s5l8740-vecmap.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Dual vector LBA maps for S5L8740 FTL. + * + * L2V maps logical LBA → physical record ordinal; V2L maps the reverse. + * Each axis uses a u32 group anchor (256 entries) plus an s8 residual. + * Sentinels: 127 = missing, -128 = sparse escape. Callers must still + * validate on-media metadata after a successful cross-check. + */ +#include +#include +#include +#include + +#include "ftl-s5l8740-vecmap.h" + +void n31_vecmap_init(struct n31_vecmap *v) +{ + memset(v, 0, sizeof(*v)); +} + +void n31_vecmap_free(struct n31_vecmap *v) +{ + if (!v) + return; + vfree(v->l2v_base_p); + vfree(v->l2v_delta); + vfree(v->v2l_base_l); + vfree(v->v2l_delta); + kfree(v->l2v_esc); + kfree(v->v2l_esc); + memset(v, 0, sizeof(*v)); +} + +static int n31_s32_cmp(const void *a, const void *b) +{ + s32 da = *(const s32 *)a; + s32 db = *(const s32 *)b; + + return da < db ? -1 : da > db ? 1 : 0; +} + +static s32 n31_median_s32(s32 *tmp, unsigned int n) +{ + if (!n) + return 0; + sort(tmp, n, sizeof(*tmp), n31_s32_cmp, NULL); + return tmp[n / 2]; +} + +static int n31_esc_add(struct n31_vec_escape **arr, unsigned int *n, + unsigned int *cap, u32 key, u32 value, u32 weave) +{ + struct n31_vec_escape *e; + unsigned int i; + + for (i = 0; i < *n; i++) { + if ((*arr)[i].key == key) { + (*arr)[i].value = value; + (*arr)[i].weave = weave; + return 0; + } + } + if (*n >= *cap) { + unsigned int ncap = *cap ? *cap * 2 : 64; + struct n31_vec_escape *na = + krealloc(*arr, ncap * sizeof(*e), GFP_KERNEL); + + if (!na) + return -ENOMEM; + *arr = na; + *cap = ncap; + } + e = &(*arr)[(*n)++]; + e->key = key; + e->value = value; + e->weave = weave; + return 0; +} + +static int n31_esc_find(const struct n31_vec_escape *arr, unsigned int n, + u32 key, u32 *value) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + if (arr[i].key == key) { + *value = arr[i].value; + return 0; + } + } + return -ENOENT; +} + +static int n31_compress_axis(u32 *base_out, s8 *delta_out, u32 count, + u32 groups, bool is_l2v, + const struct n31_vec_pair *pairs, unsigned int np, + u32 key_base, + struct n31_vec_escape **esc, unsigned int *esc_n, + unsigned int *esc_cap, + unsigned int *ok, unsigned int *escapes, + unsigned int *miss) +{ + s32 *scratch; + unsigned int g; + + scratch = kmalloc_array(N31_VEC_GROUP_SIZE, sizeof(*scratch), + GFP_KERNEL); + if (!scratch) + return -ENOMEM; + + for (g = 0; g < groups; g++) { + u32 gbase = g << N31_VEC_GROUP_SHIFT; + unsigned int nobs = 0; + unsigned int i; + s32 anchor; + u32 idx; + + for (i = 0; i < np && nobs < N31_VEC_GROUP_SIZE; i++) { + u32 key = is_l2v ? pairs[i].l : pairs[i].p; + u32 val = is_l2v ? pairs[i].p : pairs[i].l; + + if (key < key_base) + continue; + idx = key - key_base; + if ((idx >> N31_VEC_GROUP_SHIFT) != g) + continue; + scratch[nobs++] = (s32)val - (s32)(idx & + (N31_VEC_GROUP_SIZE - 1)); + } + + if (!nobs) { + base_out[g] = 0; + for (idx = 0; idx < N31_VEC_GROUP_SIZE && + gbase + idx < count; idx++) { + delta_out[gbase + idx] = N31_VEC_MISS; + (*miss)++; + } + continue; + } + + anchor = n31_median_s32(scratch, nobs); + base_out[g] = (u32)anchor; + + /* Fill miss first, then overwrite observed. */ + for (idx = 0; idx < N31_VEC_GROUP_SIZE && + gbase + idx < count; idx++) + delta_out[gbase + idx] = N31_VEC_MISS; + + for (i = 0; i < np; i++) { + u32 key = is_l2v ? pairs[i].l : pairs[i].p; + u32 val = is_l2v ? pairs[i].p : pairs[i].l; + s32 expected, delta; + u32 off; + + if (key < key_base) + continue; + off = key - key_base; + if ((off >> N31_VEC_GROUP_SHIFT) != g) + continue; + if (off >= count) + continue; + + expected = (s32)anchor + (s32)(off & (N31_VEC_GROUP_SIZE - 1)); + delta = (s32)val - expected; + if (delta >= -127 && delta <= 126) { + delta_out[off] = (s8)delta; + (*ok)++; + } else { + delta_out[off] = N31_VEC_ESC; + if (n31_esc_add(esc, esc_n, esc_cap, key, val, + pairs[i].weave)) { + kfree(scratch); + return -ENOMEM; + } + (*escapes)++; + } + } + + for (idx = 0; idx < N31_VEC_GROUP_SIZE && + gbase + idx < count; idx++) { + if (delta_out[gbase + idx] == N31_VEC_MISS) + (*miss)++; + } + } + + kfree(scratch); + return 0; +} + +int n31_vecmap_build(struct n31_vecmap *v, const struct n31_vec_pair *pairs, + unsigned int n) +{ + u32 l_min = ~0u, l_max = 0, p_min = ~0u, p_max = 0; + unsigned int i; + int ret; + + n31_vecmap_free(v); + n31_vecmap_init(v); + + if (!pairs || !n) + return -EINVAL; + + for (i = 0; i < n; i++) { + if (pairs[i].l < l_min) + l_min = pairs[i].l; + if (pairs[i].l > l_max) + l_max = pairs[i].l; + if (pairs[i].p < p_min) + p_min = pairs[i].p; + if (pairs[i].p > p_max) + p_max = pairs[i].p; + } + + /* Align bases down to group boundary for clean indexing. */ + v->l_base = l_min & ~(N31_VEC_GROUP_SIZE - 1); + v->p_base = p_min & ~(N31_VEC_GROUP_SIZE - 1); + v->l_count = l_max - v->l_base + 1; + v->p_count = p_max - v->p_base + 1; + /* Pad counts to full groups. */ + v->l_count = (v->l_count + N31_VEC_GROUP_SIZE - 1) & + ~(N31_VEC_GROUP_SIZE - 1); + v->p_count = (v->p_count + N31_VEC_GROUP_SIZE - 1) & + ~(N31_VEC_GROUP_SIZE - 1); + v->l2v_groups = v->l_count >> N31_VEC_GROUP_SHIFT; + v->v2l_groups = v->p_count >> N31_VEC_GROUP_SHIFT; + + v->l2v_base_p = vmalloc(array_size(v->l2v_groups, sizeof(u32))); + v->l2v_delta = vmalloc(v->l_count); + v->v2l_base_l = vmalloc(array_size(v->v2l_groups, sizeof(u32))); + v->v2l_delta = vmalloc(v->p_count); + if (!v->l2v_base_p || !v->l2v_delta || !v->v2l_base_l || !v->v2l_delta) { + n31_vecmap_free(v); + return -ENOMEM; + } + memset(v->l2v_delta, N31_VEC_MISS, v->l_count); + memset(v->v2l_delta, N31_VEC_MISS, v->p_count); + + ret = n31_compress_axis(v->l2v_base_p, v->l2v_delta, v->l_count, + v->l2v_groups, true, pairs, n, v->l_base, + &v->l2v_esc, &v->l2v_esc_n, &v->l2v_esc_cap, + &v->compact_ok, &v->compact_esc, + &v->compact_miss); + if (ret) { + n31_vecmap_free(v); + return ret; + } + + ret = n31_compress_axis(v->v2l_base_l, v->v2l_delta, v->p_count, + v->v2l_groups, false, pairs, n, v->p_base, + &v->v2l_esc, &v->v2l_esc_n, &v->v2l_esc_cap, + &v->compact_ok, &v->compact_esc, + &v->compact_miss); + if (ret) { + n31_vecmap_free(v); + return ret; + } + + v->ready = true; + return 0; +} + +static u32 n31_vec_predict(u32 base, s8 delta, u32 idx_in_group) +{ + return (u32)((s32)base + (s32)idx_in_group + (s32)delta); +} + +int n31_vecmap_lookup(const struct n31_vecmap *v, u32 lba, u32 *p_out) +{ + u32 off, group, idx, p, back; + s8 d; + + if (!v || !v->ready || !p_out) + return -EINVAL; + if (lba < v->l_base || lba >= v->l_base + v->l_count) + return -ENOENT; + + off = lba - v->l_base; + d = v->l2v_delta[off]; + if (d == N31_VEC_MISS) + return -ENOENT; + if (d == N31_VEC_ESC) { + if (n31_esc_find(v->l2v_esc, v->l2v_esc_n, lba, &p)) + return -ENOENT; + } else { + group = off >> N31_VEC_GROUP_SHIFT; + idx = off & (N31_VEC_GROUP_SIZE - 1); + p = n31_vec_predict(v->l2v_base_p[group], d, idx); + } + + /* Cross-check V2L(P) == L */ + back = n31_vecmap_v2l(v, p); + if (back != lba) + return -EUCLEAN; + + *p_out = p; + return 0; +} + +u32 n31_vecmap_v2l(const struct n31_vecmap *v, u32 p) +{ + u32 off, group, idx, l; + s8 d; + + if (!v || !v->ready) + return N31_INVALID_L; + if (p < v->p_base || p >= v->p_base + v->p_count) + return N31_INVALID_L; + + off = p - v->p_base; + d = v->v2l_delta[off]; + if (d == N31_VEC_MISS) + return N31_INVALID_L; + if (d == N31_VEC_ESC) { + if (n31_esc_find(v->v2l_esc, v->v2l_esc_n, p, &l)) + return N31_INVALID_L; + return l; + } + group = off >> N31_VEC_GROUP_SHIFT; + idx = off & (N31_VEC_GROUP_SIZE - 1); + return n31_vec_predict(v->v2l_base_l[group], d, idx); +} diff --git a/drivers/misc/ftl-s5l8740-vecmap.h b/drivers/misc/ftl-s5l8740-vecmap.h new file mode 100755 index 00000000000000..a51621c42d8276 --- /dev/null +++ b/drivers/misc/ftl-s5l8740-vecmap.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef FTL_S5L8740_VECMAP_H +#define FTL_S5L8740_VECMAP_H + +#include + +#define N31_VEC_GROUP_SHIFT 8 /* 256 entries per group */ +#define N31_VEC_GROUP_SIZE (1u << N31_VEC_GROUP_SHIFT) +#define N31_VEC_MISS 127 /* unknown / missing */ +#define N31_VEC_ESC (-128) /* escape to full table */ +#define N31_INVALID_P 0xffffffffu +#define N31_INVALID_L 0xffffffffu + +#define N31_VEC_NUM_CE 2u +#define N31_VEC_NUM_CAU 2u +#define N31_VEC_BLOCKS_PER_CAU 2088u +#define N31_VEC_PAGES_PER_BLOCK 128u +#define N31_VEC_SLOTS_PER_PAGE 4u + +struct n31_vec_pair { + u32 l; /* fmss_lba */ + u32 p; /* physical record ordinal */ + u32 weave; +}; + +struct n31_vec_escape { + u32 key; + u32 value; + u32 weave; +}; + +struct n31_vecmap { + /* L2V: L -> P */ + u32 l_base; + u32 l_count; + u32 *l2v_base_p; /* per group */ + s8 *l2v_delta; /* per L index */ + u32 l2v_groups; + + /* V2L: P -> L */ + u32 p_base; + u32 p_count; + u32 *v2l_base_l; + s8 *v2l_delta; + u32 v2l_groups; + + struct n31_vec_escape *l2v_esc; + unsigned int l2v_esc_n; + unsigned int l2v_esc_cap; + struct n31_vec_escape *v2l_esc; + unsigned int v2l_esc_n; + unsigned int v2l_esc_cap; + + unsigned int compact_ok; + unsigned int compact_esc; + unsigned int compact_miss; + bool ready; +}; + +static inline u32 n31_phys_to_ordinal(u8 ce, u8 cau, u16 blk, u8 page, u8 slot) +{ + u32 bank = (u32)ce * N31_VEC_NUM_CAU + cau; + u32 page_i = (u32)blk * N31_VEC_PAGES_PER_BLOCK + page; + + return ((bank * N31_VEC_BLOCKS_PER_CAU * + N31_VEC_PAGES_PER_BLOCK + page_i) * + N31_VEC_SLOTS_PER_PAGE) + (slot & 3); +} + +static inline void n31_ordinal_to_phys(u32 p, u8 *ce, u8 *cau, u16 *blk, + u8 *page, u8 *slot) +{ + u32 slot_i = p % N31_VEC_SLOTS_PER_PAGE; + u32 page_i = p / N31_VEC_SLOTS_PER_PAGE; + u32 pages_per_bank = N31_VEC_BLOCKS_PER_CAU * N31_VEC_PAGES_PER_BLOCK; + u32 bank = page_i / pages_per_bank; + u32 rem = page_i % pages_per_bank; + + *slot = (u8)slot_i; + *page = (u8)(rem % N31_VEC_PAGES_PER_BLOCK); + *blk = (u16)(rem / N31_VEC_PAGES_PER_BLOCK); + *cau = (u8)(bank % N31_VEC_NUM_CAU); + *ce = (u8)(bank / N31_VEC_NUM_CAU); +} + +void n31_vecmap_init(struct n31_vecmap *v); +void n31_vecmap_free(struct n31_vecmap *v); + +/* + * Build L2V/V2L from newest-weave pairs (caller already chose weave). + * Allocates delta tables covering [l_min..l_max] and [p_min..p_max]. + */ +int n31_vecmap_build(struct n31_vecmap *v, const struct n31_vec_pair *pairs, + unsigned int n); + +int n31_vecmap_lookup(const struct n31_vecmap *v, u32 lba, u32 *p_out); +u32 n31_vecmap_v2l(const struct n31_vecmap *v, u32 p); + +#endif /* FTL_S5L8740_VECMAP_H */ diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c index 15119584473caf..57da8adf17df06 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -52,7 +52,12 @@ static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) { struct i2c_client *c = lis3->bus_priv; - *v = i2c_smbus_read_byte_data(c, reg); + s32 ret = i2c_smbus_read_byte_data(c, reg); + + /* Do not store errno in *v — (u8)(-ETIMEDOUT/-110) is 0x92. */ + if (ret < 0) + return ret; + *v = (u8)ret; return 0; } @@ -71,9 +76,15 @@ static int lis3_i2c_init(struct lis3lv02d *lis3) lis3_reg_ctrl(lis3, LIS3_REG_ON); - lis3->read(lis3, WHO_AM_I, ®); + ret = lis3->read(lis3, WHO_AM_I, ®); + if (ret < 0) { + printk(KERN_ERR "lis3: WHO_AM_I read failed %d\n", ret); + return ret; + } + printk(KERN_INFO "lis3: WHO_AM_I=0x%02x expect=0x%02x\n", + reg, lis3->whoami); if (reg != lis3->whoami) - printk(KERN_ERR "lis3: power on failure\n"); + printk(KERN_ERR "lis3: power on failure (WHO_AM_I mismatch)\n"); /* power up the device */ ret = lis3->read(lis3, CTRL_REG1, ®); @@ -157,6 +168,23 @@ static int lis3lv02d_i2c_probe(struct i2c_client *client) i2c_set_clientdata(client, &lis3_dev); + { + s32 id = i2c_smbus_read_byte_data(client, WHO_AM_I); + + if (id < 0) + dev_info(&client->dev, + "LIS3 WHO_AM_I read failed %d (7bit=0x%02x, -5=-EIO not a chip id; wire 0x31 is the 8-bit READ addr)\n", + id, client->addr); + else + dev_info(&client->dev, + "LIS3 WHO_AM_I=0x%02x 7bit=0x%02x (0x33=8-bit 3DC)\n", + id, client->addr); + if (id < 0) { + ret = id; + goto fail2; + } + } + /* Provide power over the init call */ lis3_reg_ctrl(&lis3_dev, LIS3_REG_ON); diff --git a/drivers/misc/nand-s5l8740-seq.h b/drivers/misc/nand-s5l8740-seq.h new file mode 100755 index 00000000000000..635b9843acf401 --- /dev/null +++ b/drivers/misc/nand-s5l8740-seq.h @@ -0,0 +1,185 @@ +/* Auto-extracted OSOS 1.0.2 FMSS read sequence at 0x8980EA0. Do not edit. */ +#ifndef FMSS_SEQ_READ_H +#define FMSS_SEQ_READ_H + +#define FMSS_SEQ_READ_LEN 2824u +static const u8 fmss_seq_read_blob[] = { + 0x0c, 0x0c, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x10, 0x0c, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x58, 0x0c, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x4c, 0x0c, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0d, 0x06, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x06, 0x00, 0x06, 0x0b, 0x01, 0x08, 0x00, 0x01, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x0a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0xb8, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0x78, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x14, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x06, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x20, 0x01, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x05, 0x38, 0x01, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x37, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x05, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x14, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x06, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x50, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x05, 0x68, 0x02, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x37, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x0a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0xe8, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0x20, 0x07, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0xc8, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x06, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x77, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0xca, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x0c, 0x07, 0x40, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x01, 0x04, 0xff, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x20, 0x00, 0x03, 0x00, 0x48, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, + 0x01, 0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf0, 0x0a, 0x00, 0x00, + 0x10, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x10, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x7a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x02, 0x04, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0d, 0x07, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x10, 0x00, 0x30, 0x00, 0x00, 0x01, 0x0f, 0x00, 0x00, 0x00, + 0x58, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, 0x00, + 0x18, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x18, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x10, 0x00, 0x00, 0x00, 0x18, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x01, 0xff, 0x03, 0x00, 0x00, 0x58, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x62, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x02, 0x11, 0x00, 0x00, + 0x40, 0x00, 0x01, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x60, 0x05, 0x00, 0x00, + 0x60, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x54, 0x0c, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x10, 0x00, + 0x02, 0x00, 0x02, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x88, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0e, 0x10, 0x06, 0x00, 0x00, 0x08, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x17, 0xe8, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x0d, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x17, 0x20, 0x07, 0x00, 0x00, 0x01, 0x00, 0x01, 0x0d, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x17, 0xc8, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x14, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xff, + 0x00, 0x00, 0x06, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x7d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xca, 0x00, 0x00, 0x00, 0x40, 0x00, 0x0c, 0x07, 0x40, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x01, 0x04, 0xff, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x20, 0x00, 0x03, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, 0x01, 0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0xf0, 0x0a, 0x00, 0x00, 0x10, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x10, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x02, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x08, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x0d, 0x07, 0x04, 0xff, 0xff, 0xff, 0xff, 0x30, 0x00, 0x00, 0x01, 0x0f, 0x00, 0x00, 0x00, + 0x58, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, 0x00, + 0x18, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x18, 0x0d, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x01, 0x0c, 0x10, 0x00, 0x00, 0x00, 0x18, 0x0d, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x07, 0x0d, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0xff, 0x03, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xe2, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x02, 0x11, 0x00, 0x00, + 0x40, 0x00, 0x01, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x00, 0x09, 0x00, 0x00, + 0x60, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x01, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0d, 0x07, 0x04, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, 0x54, 0x0c, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x08, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x08, 0x05, 0x10, 0x06, 0x00, 0x00, 0x14, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0xff, 0x03, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x17, 0xb8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x06, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x77, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0xca, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x0c, 0x07, 0x40, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x20, 0x00, 0x03, 0x00, 0x48, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, + 0x00, 0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf0, 0x0a, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x04, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x62, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0x68, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x01, 0x13, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xd8, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x06, 0x00, 0x06, 0x0a, 0x01, 0xfe, 0xff, 0xfe, + 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x01, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif diff --git a/drivers/misc/nand-s5l8740.c b/drivers/misc/nand-s5l8740.c new file mode 100755 index 00000000000000..3bd3ac860f5b3f --- /dev/null +++ b/drivers/misc/nand-s5l8740.c @@ -0,0 +1,7798 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 FMSS/FMC NAND controller driver (N31). + * + * Provides FIL primitives used by the FTL: READ ID, parameter page, page + * read (PIO and CS command-list paths), and geometry helpers. Array + * program and erase are not implemented. + * + * CS physical reads use span-4 / 4112-byte records with four 4096+16 slots + * per page. True metadata DMA remains optional and disabled by default. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nand-s5l8740-seq.h" +#include "nand-s5l8740.h" +#include "whimory-ftl.h" + +#define FMSS_PHYS 0x38A00000ul +#define FMSS_SIZE 0x1000 + +#define FMCTRL0 0x00 +#define FMCTRL1 0x04 +#define FMCMD 0x08 +#define FMADDR 0x0c +#define FMCE 0x14 +#define FMUNK18 0x18 +#define FMUNK24 0x24 +#define FMUNK28 0x28 +#define FMCYCLES 0x2c +#define FMLEN 0x30 +#define FMUNK38 0x38 +#define FMSTAT48 0x48 +#define NANDSTAT 0x4c +#define FMDATA 0x80 +#define FMSEQ 0xc00 +#define FMSEQBASE 0xc04 +#define FMSEQSTAT 0xc08 +#define FMSEQIRQ 0xc0c +#define FMGEN0 0xd00 +#define FMGEN1 0xd04 +#define FMGEN2 0xd08 +#define FMGEN3 0xd0c +#define FMGEN4 0xd10 +#define FMGEN5 0xd14 +#define FMUNK81C 0x81c + +#define FMSS_DMA_STATUS_LEN 512 +/* Consecutive IRQ-less-but-clean transfers before the driver stops waiting. */ +#define FMSS_IRQ_GIVEUP 8 +#define FMSS_DMA_CMDLIST_LEN 256 +#define FMSS_DMA_SPARE_LEN 256 + +/* + * Batched meta scan. + * + * The command list is a chain of 8-dword descriptor pairs (CE-select then + * transfer) closed by a 0x00010002 terminator. Nothing in that format is + * single-page. + * + * A single-page kick costs about 1.54 ms against a NAND tR of 60-80 us, so + * roughly 1.45 ms of every read is fixed sequencer overhead paid once per + * page. Batching N pages behind one kick pays it once per N. + * + * The batch is 16 entries because that is what the spare buffer holds + * (256 B at 16 B per sector). It needs 16 * 4 KiB of data buffer: the + * transfer descriptor always lands a full sector even when only the meta is + * wanted. + */ +/* + * 256 meta records per kick, which is stock's S_SCAN_META_SIZE + * (s_cxt_diff.c:349). The scan queues two reads per superblock -- first + * page and last page -- so one kick covers 128 superblocks. + */ +#define FMSS_DMA_BATCH_MAX 256 +#define FMSS_DMA_BATCH_CMDL_LEN (FMSS_DMA_BATCH_MAX * 32 + 16) + +/* + * Two shapes share these buffers, and the buffer is sized for whichever + * needs more of it: + * + * classify 16 entries x span 1 -> 64 KiB data, 256 B spare + * read ahead 8 entries x span 4 -> 128 KiB data, 512 B spare + * + * span is the descriptor's sector count. The classify scan wants one 4 KiB + * sector per block because all it reads is slot 0 meta across many blocks; + * the read path wants all four, because a 16 KiB page holds four + * consecutive LBAs and splitting it into four span-1 entries would make the + * NAND open the same page four times. + */ +#define FMSS_DMA_BATCH_SPAN_MAX 4 +#define FMSS_DMA_BATCH_DATA_LEN (128 * 1024) +#define FMSS_DMA_BATCH_SPARE_LEN (FMSS_DMA_BATCH_MAX * 16) +#define FMSS_SECTOR_LEN 4096 + +#define FMSS_MAX_CHUNKS 16 +#define FMSS_CHUNK 1024 +#define FMSS_PAGE_LEN (FMSS_MAX_CHUNKS * FMSS_CHUNK) +#define FMSS_PARAM_LEN 0x200 + +/* + * PPN address packing — OSOS 5173CA / Sogeti PPNVFL: + * page | (block << page_bits) | (cau << (page_bits+block_bits)) | (slc <<...) + * Param page: page_bits=7, block_bits=12, cau_bits=1. + * VFL context lives in the last ~5% of each CAU, SLC page 0 + * (spare type 0x20, index 0xFFFF). SFTL context spare type 0x1F. + */ +#define FMSS_PAGE_BITS 7 +#define FMSS_BLOCK_BITS 12 +#define FMSS_CAU_BITS 1 +#define FMSS_BLOCKS_PER_CAU 2088 +#define FMSS_NUM_CE 2 +#define FMSS_NUM_CAU 2 +#define FMSS_VFL_TAIL 128 +#define FMSS_VFL_HDR_LEN 512 +#define FMSS_BTOC_PAGE 127 +#define FMSS_VBAS_PER_PAGE 4 /* s_g_vbas_per_page: 16 KiB / 4096 */ +#define FMSS_LPN_INDEX_MAX 512 +#define FMSS_GREP_MAX_BLOCKS 64 +#define FMSS_VFL_MAP_MAX 512 +#define FMSS_L2V_DEFAULT_BLOCKS 256 +/* Map is keyed by page LPN (YaFTL); full LBA map preferred for block I/O. */ +#define FMSS_L2V_DEFAULT_MAX_LPN \ + ((NAND_FTL_DEFAULT_CAPACITY / NAND_FTL_SECTORS_PER_LPN) + 64) + +/* Classic Whimory mount (freemyipod) adapted for N31 PPN geometry. */ +#define WMR_PAGES_PER_BLOCK 128u +#define WMR_BLOCK_MAP_MAX 16384u +#define WMR_MOUNT_MAX_BLOCKS 64u +#define WMR_FTLCTRL_MAX 3u + +/* Packed L2V entry: + * valid|ce[1:0]|cau[1:0]|PHYS|sec[1:0]|block[11:0]|page[6:0] + * sec = 4K index within the NAND page (SFTL VBA). 0x3 = “use LBA%4”. + * PHYS: block is already physical (BTOC/BTE/META/carve) — do NOT VFL-remap. + */ +#define L2V_VALID BIT(31) +#define L2V_CE_SHIFT 29 +#define L2V_CAU_SHIFT 27 +#define L2V_PHYS BIT(26) /* already-physical block */ +#define L2V_SEC_SHIFT 19 +#define L2V_SEC_MASK 0x3u +#define L2V_SEC_FROM_LBA 0x3u /* sentinel: derive sec from lba%4 */ +#define L2V_BLOCK_SHIFT 7 +#define L2V_PAGE_MASK 0x7fu +#define L2V_BLOCK_MASK 0xfffu + +/* Claim source priority for newest-wins (higher wins on equal weave). */ +enum { + L2V_SRC_NONE = 0, + L2V_SRC_CARVE = 1, + L2V_SRC_WMR = 2, /* classic virt block-map (may need VFL) */ + L2V_SRC_BTOC = 3, + L2V_SRC_BTE = 4, + L2V_SRC_META = 5, +}; + +struct fmss_vfl_map { + unsigned int cau; + unsigned int virt; + unsigned int phys; +}; + +struct fmss_lpn_map { + unsigned int lpn; + unsigned int ce; + unsigned int cau; + unsigned int block; + unsigned int page; +}; + +static char vfl_log[PAGE_SIZE]; +static unsigned int vfl_log_len; +static char sector_log[512]; +static unsigned int sector_log_len; +static unsigned int lpn_scan_blocks = 512; +module_param(lpn_scan_blocks, uint, 0644); +MODULE_PARM_DESC(lpn_scan_blocks, "max CAU blocks to scan for lpn_read (default 512)"); + +static unsigned int l2v_scan_blocks = FMSS_L2V_DEFAULT_BLOCKS; +module_param(l2v_scan_blocks, uint, 0644); +MODULE_PARM_DESC(l2v_scan_blocks, + "default block count per CE/CAU for l2v_build (default 256)"); + +static unsigned int grep_max_blocks = 64; +module_param(grep_max_blocks, uint, 0644); +MODULE_PARM_DESC(grep_max_blocks, "max blocks per ftl_grep call (default 64)"); + +static int quiet = 1; +module_param(quiet, int, 0644); +MODULE_PARM_DESC(quiet, "1=minimal logs, skip ECC diag (faster FTL scans, default on)"); + +#define fmss_info(fmt, ...) \ + do { if (!quiet) pr_info("s5l8740-nand: " fmt, ##__VA_ARGS__); } while (0) + +#define nand_dev_info(dev, fmt, ...) \ + do { if (!quiet) dev_info(dev, fmt, ##__VA_ARGS__); } while (0) + +static unsigned int vfl_build_blocks = 32; +module_param(vfl_build_blocks, uint, 0644); +MODULE_PARM_DESC(vfl_build_blocks, "max blocks per CAU for vfl_build (default 32, tail only)"); + +/* + * VFL remap of map entries that are NOT marked L2V_PHYS. + * BTOC/BTE/carve store physical scan blocks — remapping those double-translates. + * Default off until wrmx+0x100 is proven as direct virt→phys. + */ +static char vfl_remap_mode[16] = "off"; +module_param_string(vfl_remap_mode, vfl_remap_mode, sizeof(vfl_remap_mode), 0644); +MODULE_PARM_DESC(vfl_remap_mode, + "off (default) | direct256 | tail_only — VFL remap for non-PHYS map entries"); + +static unsigned int vfl_remap_applied; +static unsigned int vfl_remap_skipped_phys; + +/* Tiny list for sysfs lpn_index (debug); dense map is authoritative. */ +static struct fmss_lpn_map lpn_index[FMSS_LPN_INDEX_MAX]; +static unsigned int lpn_index_count; + +/* Dense LPN → physical page map (vzalloc). */ +static u32 *l2v_map; +static u64 *l2v_weave; +static u8 *l2v_src; +static unsigned int l2v_map_size; +static unsigned int l2v_mapped; +static unsigned int l2v_max_lpn; +static unsigned int l2v_btoc_hits; +static unsigned int l2v_bmap_hits; +static unsigned int l2v_meta_hits; + +/* + * Full LBA → packed phys+sec map (SFTL BTE / boot carve). Preferred over LPN + * dense map for block I/O. + * + * WARNING: NAND_FTL_DEFAULT_CAPACITY is ~3.8M *sectors* (~15GB media). A dense + * map of that size is ~50MB+ RAM (u32+u64+u8) and OOMs N31 before RNDIS. + * Cap with lba_map_max (default 262144 ≈ 1GB LBA space ≈ 3.4MB RAM). + */ +#define FMSS_LBA_MAP_HARDMAX NAND_FTL_DEFAULT_CAPACITY +static unsigned int lba_map_max = 262144; +module_param(lba_map_max, uint, 0644); +MODULE_PARM_DESC(lba_map_max, + "max LBA entries for dense map (default 262144; full media is ~3.8M / ~50MB)"); +static u32 *lba_map; +static u64 *lba_weave; +static u8 *lba_src; +static unsigned int lba_mapped; + +/* Last resolution chain for sysfs resolve_log. */ +static char resolve_log[512]; +static unsigned int resolve_log_len; + +/* Carved Apple FAT boot (*UOKJIHC); sector 0 served from here. */ +static bool boot_carve_valid; +static unsigned int boot_carve_ce; +static unsigned int boot_carve_cau; +static unsigned int boot_carve_block; +static unsigned int boot_carve_page; +static unsigned int boot_carve_off; +/* From carved BPB; reserved area must not be served from a poisoned L2V[0]. */ +static unsigned int boot_reserved_sects = 32; +static unsigned int boot_data_start = 1916; +static unsigned int boot_fat_sects = 942; /* FATSz32 from live BPB */ + +/* + * Optional cached aligned boot page (page must have BPB at offset 0). + * Defaults 0 = rediscover via BTOC(0,1) + aligned BPB. Do NOT cache the + * mid-page *UOKJIHC @ blk63/pg88/off7816 — that is a file copy, not LBA0. + */ +static unsigned int boot_carve_ce_param; +static unsigned int boot_carve_cau_param; +static unsigned int boot_carve_block_param; /* 0 = scan */ +static unsigned int boot_carve_page_param; +static unsigned int boot_carve_off_param; /* must be 0 for a real boot page */ +module_param_named(boot_carve_ce, boot_carve_ce_param, uint, 0644); +module_param_named(boot_carve_cau, boot_carve_cau_param, uint, 0644); +module_param_named(boot_carve_block, boot_carve_block_param, uint, 0644); +module_param_named(boot_carve_page, boot_carve_page_param, uint, 0644); +module_param_named(boot_carve_off, boot_carve_off_param, uint, 0644); +MODULE_PARM_DESC(boot_carve_block, + "cached aligned boot block (0=BTOC hunt for LPN0)"); + +/* Default l2v_build width when sysfs omits NBLOCKS (user blocks to scan). */ +static unsigned int l2v_auto_blocks; +module_param(l2v_auto_blocks, uint, 0644); +MODULE_PARM_DESC(l2v_auto_blocks, + "default l2v_build block span per CE/CAU (0=carve/boot hunt only)"); + +/* + * Legacy dense-map META ingest. Off unless l2v_build/whimory_mount is + * running — FIL reads from the Whimory driver must not allocate the + * ~50MB LBA map as a side effect. + */ +static bool fmss_legacy_meta_ingest; + +static int (*nand_ftl_read_hook)(u64 lba, void *buf); + +/* Root-dir physical page when LPN(DataStart) is otherwise unmapped. */ +static bool root_dir_valid; +static unsigned int root_dir_ce; +static unsigned int root_dir_cau; +static unsigned int root_dir_block; +static unsigned int root_dir_page; +static unsigned int root_dir_lpn; + +static struct fmss_vfl_map vfl_map[FMSS_VFL_MAP_MAX]; +static unsigned int vfl_map_count; +static unsigned int vfl_ctx_cau[FMSS_NUM_CAU]; +static unsigned int vfl_ctx_block[FMSS_NUM_CAU]; + +/* Classic Whimory FTL block map + mount status (phase). */ +static u16 *wmr_block_map; +static unsigned int wmr_block_map_n; +static unsigned int wmr_dis_hits; +static unsigned int wmr_vfl_hits; +static unsigned int wmr_ftlctrl_hits; +static unsigned int wmr_bmap_pages; +static unsigned int wmr_l2v_filled; +static int wmr_mount_ret; +static u16 wmr_ftlctrl[WMR_FTLCTRL_MAX]; +static unsigned int wmr_ftlctrl_n; +static unsigned int wmr_dis_ce, wmr_dis_cau, wmr_dis_block, wmr_dis_page; + +static u32 fmss_ppn_addr(unsigned int cau, unsigned int block, + unsigned int page, unsigned int slc) +{ + return page + | (block << FMSS_PAGE_BITS) + | (cau << (FMSS_PAGE_BITS + FMSS_BLOCK_BITS)) + | ((slc ? 1u : 0u) << (FMSS_PAGE_BITS + FMSS_BLOCK_BITS + FMSS_CAU_BITS)); +} + +struct nand_s5l8740 { + void __iomem *base; + struct mutex lock; + u8 last_id[8]; + int last_ce; + u8 last_page[FMSS_PAGE_LEN]; + u8 last_spare[64]; + unsigned int last_spare_len; + u8 last_parity[FMSS_MAX_CHUNKS][64]; + unsigned int last_parity_len[FMSS_MAX_CHUNKS]; + u32 last_page_addr; + int last_page_ce; + int last_page_ret; + int last_page_chunk; + unsigned int last_page_len; + unsigned int last_clean_chunks; + u8 last_param[FMSS_PARAM_LEN]; + int last_param_ce; + int last_param_ret; + u32 last_stat48; + u32 last_nandstat; + unsigned int pages_since_reset; + unsigned int ecc_soft_fails; + int dma_ok; + int dma_mapped; + struct device *dev; + void *seq; + void *cmdl; + void *bcmdl; + void *bdata; + void *bspare; + bool batch_ok; + void *data; + void *spare; + void *stbuf; + dma_addr_t seq_dma; + dma_addr_t cmdl_dma; + dma_addr_t bcmdl_dma; + dma_addr_t bdata_dma; + dma_addr_t bspare_dma; + dma_addr_t data_dma; + dma_addr_t spare_dma; + dma_addr_t stbuf_dma; + u32 last_dma_c0c; + /* + * Bumped by every writer of last_dma_c0c -- preflight, ISR, poll. + * fmss_dma_page_read() samples it either side of the kick so it can + * tell a status word that belongs to this transfer from one left + * over by the previous read. + */ + u32 c0c_seq; + u32 last_dma_d00; + u32 last_dma_c00; + int irq; + /* + * Consecutive transfers whose IRQ never arrived but which then + * polled clean. Past FMSS_IRQ_GIVEUP the line is not this driver's and the + * 200 ms wait is pure cost, so stop taking it. + */ + unsigned int irq_timeouts; + bool irq_dead; + struct completion cs_irq; + u32 last_vic_raw; + u32 last_vic_en; +}; + +static struct nand_s5l8740 *nand_dev; + +/* 12ED9C: PPN >= 0x10500 uses 4 address cycles. */ +static unsigned int addr_cycles = 4; +module_param(addr_cycles, uint, 0644); +MODULE_PARM_DESC(addr_cycles, "FMSS address cycles (OSOS 8D102EC, default 4)"); + +/* 12ECCC calls 50D960(..., 1): hardware eats 53-byte parity before each 1K. */ +static bool with_parity = true; +module_param(with_parity, bool, 0644); +MODULE_PARM_DESC(with_parity, "Issue 50D960 parity transfer (default Y)"); + +static bool use_dma; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, "Use FMSS DMA page read when dma_ok (default N — DMA spare path still OPEN on N31)"); + +/* 4EC6F4: MEMORY[0x8980CA4] = 0xFF000, OR'd into FMCTRL0 for ID/param/features. */ +static unsigned int ctrl0_or = 0xFF000; +module_param(ctrl0_or, uint, 0644); +MODULE_PARM_DESC(ctrl0_or, "FMCTRL0 extra bits for ID/param/features (default 0xFF000)"); + +/* + * FFE70 timing row 171 MHz: CA4 = 0x20011000. 50D960 uses this, not 0xFF000. + * 0xFF000 for array reads returns zeros and wedges PPN. + */ +static unsigned int page_ctrl0_or = 0x20011000; +module_param(page_ctrl0_or, uint, 0644); +MODULE_PARM_DESC(page_ctrl0_or, "FMCTRL0 extra bits for 50D960 page read (171MHz timing)"); + +/* 50D960 wedges PPN after many reads; re-run 10453C+0xFF+power-state. */ +static unsigned int reset_every = 32; +module_param(reset_every, uint, 0644); +MODULE_PARM_DESC(reset_every, "nand_reset after this many page_read calls (0=off)"); + +/* 50D960 uses 3; full PPN page is 16 x 1K data (+64 spare not in this PIO). */ +/* + * The CS/command-list path bumps pages_since_reset but historically never + * acted on it — only the PIO page_read paths reset. A recover is thousands + * of back-to-back live C00 kicks with no controller reset in between, and + * the glass reboots partway through a wide scan. Reset every N CS reads. + */ +/* Total live CS kicks since insmod; heartbeat + post-mortem marker. */ +static unsigned int cs_reads_total; +/* Off by default: console writes lengthen recover measurably. */ +static bool cs_heartbeat; +module_param(cs_heartbeat, bool, 0644); +MODULE_PARM_DESC(cs_heartbeat, "Log CS read progress every 1024 pages"); +module_param(cs_reads_total, uint, 0444); + +/* Where CS read wall time actually goes; reported by the heartbeat. */ +static u64 cs_ns_kick, cs_ns_copy; +static unsigned int cs_batch_kicks; + +/* + * Must be non-zero. A recovery walks eight thousand pages of back-to-back + * live C00 kicks; with this at 0 the counter is incremented in five places + * and acted on in none, so nothing reasserts the sequencer across the whole + * run -- exactly the condition the comment above describes. + * + * 4096 costs two register writes and two 10 us delays roughly twice per + * recovery, unmeasurable against the reads between them, and it bounds how + * far the sequencer can drift before something puts it back. + */ +static unsigned int cs_reset_every = 4096; +module_param(cs_reset_every, uint, 0644); +MODULE_PARM_DESC(cs_reset_every, + "fmss_nand_reset after this many CS phys reads (0=off)"); + +static unsigned int page_chunks = 16; +module_param(page_chunks, uint, 0644); +MODULE_PARM_DESC(page_chunks, "1K PIO chunks per page_read (16=full 16KiB data)"); + +static unsigned int spare_len = 64; +module_param(spare_len, uint, 0644); +MODULE_PARM_DESC(spare_len, + "PIO META bytes after data (default 64 = 4×16B slots; was 16)"); + +/* + * 50D960 parity FIFO is 53 bytes per 1K: 16B host spare (Sogeti META) + ECC. + * Draining it before 4EB458 steals the syndrome (whitened data). A second + * 50D960 with ecc_before_drain=0 copies META and keeps the first pass's data. + * Extra style-1 FMLEN=15 beats after a style-0 page returned 0xFF and made + * the next programmed page ECC-clean. + */ +static bool meta_pass = true; +module_param(meta_pass, bool, 0644); +MODULE_PARM_DESC(meta_pass, + "Second 50D960 to stash 53-byte parity into META slots (default Y)"); + +/* + * 4EDDDC: D14 = (v40 ? 8D102F0 : 8D102EC) - 1. + * v40 = (8D10300 != 1) — PPN multi-cycle path uses 8, so D14=7. + */ +static unsigned int dma_d14 = 7; +module_param(dma_d14, uint, 0644); +MODULE_PARM_DESC(dma_d14, "FMSS D14 address-cycles-1 for DMA (default 7 = PPN v40)"); + +/* One 4096-byte host sector first (oracle G1); FTL callers override via span. */ +static unsigned int dma_nsect = 1; +module_param(dma_nsect, uint, 0644); +MODULE_PARM_DESC(dma_nsect, "DMA span (# logical LBAs) per CS read (default 1)"); + +/* + * Decomp wants command-list META. Live CS kick (C00=0xFFF5) still wedges + * the SoC in testing — keep default off until that path is safe. Callers that + * ask for meta with this off get -EOPNOTSUPP (never PIO fake spare). + */ +static bool meta_dma_read; +module_param(meta_dma_read, bool, 0644); +MODULE_PARM_DESC(meta_dma_read, + "Allow page_read META via CS span4 (default N). " + "ftl_sftl_recover uses dma_session live CS without this."); + +/* + * PIO page path uses the legacy reset/reinit sequence. + * CS command-list path is a separate sequencer path and should not assume + * hard reset immediately before kick. in testing this sequence can wedge. + */ +static bool meta_dma_reset_before; +module_param(meta_dma_reset_before, bool, 0644); +MODULE_PARM_DESC(meta_dma_reset_before, + "Reset NAND controller before command-list metadata read"); + +static bool dma_reset_before; +module_param(dma_reset_before, bool, 0644); +MODULE_PARM_DESC(dma_reset_before, + "Reset NAND controller before manual CS DMA read"); + +/* + * PPN physical page = N × (4096 DATA + 16 META) records (N=2 or 4). + * 5172A0: qword.lo = (rec*span) | ((rec*slot) << 16); qword.hi = encoded_ppn. + */ +#define FMSS_PPN_REC 4112u /* 4096 + 16 */ +static unsigned int dma_slot; +module_param(dma_slot, uint, 0644); +MODULE_PARM_DESC(dma_slot, "starting logical slot within PPN page (0..3, default 0)"); + +static unsigned int dma_rec = FMSS_PPN_REC; +module_param(dma_rec, uint, 0644); +MODULE_PARM_DESC(dma_rec, "PPN on-flash record bytes (default 4112 = 4096+16)"); + +/* + * 4EDDDC v40 packing with qword=(0,ppn): addr in cmdlist[3]. + * 1 = force addr into cmdlist[2] (experiment). Default 0. + */ +static unsigned int dma_row_in_lo; +module_param(dma_row_in_lo, uint, 0644); +MODULE_PARM_DESC(dma_row_in_lo, "1=addr in cmdlist[2]; 0=v40 high dword in [3] (default)"); + +static unsigned int dma_c6c = 16; +module_param(dma_c6c, uint, 0644); +MODULE_PARM_DESC(dma_c6c, "value written to +0xC6C before DMA kick (default 16)"); + +/* D39EC: MEMORY[C00] = 65525 = 0xFFF5 (NOT 0xFFFD). */ +static unsigned int dma_kick = 0xfff5; +module_param(dma_kick, uint, 0644); +MODULE_PARM_DESC(dma_kick, "FMSEQ (C00) kick value (OSOS D39EC = 0xFFF5)"); + +/* + * Default dry and disarmed: leaving the C00 kick permanently live reboots + * the device. ftl_sftl_recover and cs_phys arm CS for the duration of an + * operation via dma_session_begin() and restore these on end. + */ +static bool dma_dry = true; +module_param(dma_dry, bool, 0644); +MODULE_PARM_DESC(dma_dry, + "program CS regs/descriptors but do not write C00 (default Y)"); + +static bool dma_armed; +module_param(dma_armed, bool, 0644); +MODULE_PARM_DESC(dma_armed, "Allow CS DMA kick (default N — session arms for recover)"); + +static bool dma_one_shot = true; +module_param(dma_one_shot, bool, 0644); +MODULE_PARM_DESC(dma_one_shot, + "Clear dma_armed after each CS kick (default Y outside sessions)"); + +/* Canary path: one page, no FTL/lba_map ingest. */ +static bool dma_skip_ingest; + +/* + * D39EC: if 0x8982448 then C6C=0 + pulse C60; else C6C=16. + * Pulse never raised C64 in testing. Default matches the else path. + */ +static bool dma_pulse; +module_param(dma_pulse, bool, 0644); +MODULE_PARM_DESC(dma_pulse, "pulse +0xC60 before DMA kick (D39EC if-path)"); + +/* + * Seq blob[0..31] is CS ops. CPU-poking C0C=0xFF is W1C of FMSEQIRQ and + * made wait_cs see 0xFF&0xD==0xD. Leave off unless debugging C10/C58/C4C. + */ +static bool dma_preamble; +module_param(dma_preamble, bool, 0644); +MODULE_PARM_DESC(dma_preamble, "CPU-write C10/C58/C4C from seq header (not C0C)"); + +/* OSOS D39B8: interrupt 54 = VIC1 hwirq 22 = Linux irq 70 on this GATE0 map. */ +static int dma_irq = 70; +module_param(dma_irq, int, 0644); +MODULE_PARM_DESC(dma_irq, "Linux IRQ for FMSS CS (OSOS 54 / VIC1 22, default 70)"); + +static u32 fmss_ctrl0(unsigned int ce) +{ + return ctrl0_or | (2u * (1u << ce)) | 1u; +} + +static u32 fmss_page_ctrl0(unsigned int ce) +{ + return page_ctrl0_or | (2u * (1u << ce)) | 1u; +} + +/* 1858DC: poll FMSTAT48 bit(s), then W1C. */ +static int fmss_wait48_n(struct nand_s5l8740 *f, u32 mask, unsigned int loops) +{ + unsigned int i; + u32 st; + + for (i = 0; i < loops; i++) { + st = readl(f->base + FMSTAT48); + if (st & mask) { + if (mask == 0x800000u) + writel(32, f->base + FMCTRL1); + writel(mask, f->base + FMSTAT48); + return 0; + } + udelay(1); + } + f->last_stat48 = readl(f->base + FMSTAT48); + f->last_nandstat = readl(f->base + NANDSTAT); + writel(mask, f->base + FMSTAT48); + return -ETIMEDOUT; +} + +static int fmss_wait48(struct nand_s5l8740 *f, u32 mask) +{ + return fmss_wait48_n(f, mask, 20000); +} + +static int fmss_cmd(struct nand_s5l8740 *f, u8 cmd) +{ + writel(cmd, f->base + FMCMD); + return fmss_wait48(f, 2); +} + +/* D622C: drain PIO FIFO at +0x80. Must read +0x80 first (OSOS + live ID). */ +static int fmss_pio_read(struct nand_s5l8740 *f, void *dst, unsigned int len) +{ + u8 *p = dst; + unsigned int n = 0, words = len >> 2, spins = 0; + u32 word; + + writel(3, f->base + FMLEN); + writel(1u << 8, f->base + FMCE); + writel(readl(f->base + FMCTRL0) & 0xFEFFFBFF, f->base + FMCTRL0); + writel(0, f->base + FMUNK28); + writel(480, f->base + FMCTRL1); + + while (n < words && spins < 20000u * (words + 1)) { + word = readl(f->base + FMDATA); + if ((u8)readl(f->base + FMUNK28) == (u8)(4 * n + 4)) { + memcpy(p + 4 * n, &word, 4); + n++; + } + spins++; + cpu_relax(); + } + writel(0x100000, f->base + FMSTAT48); + writel(32, f->base + FMCTRL1); + return n == words ? 0 : -ETIMEDOUT; +} + +/* 41AE38: NAND→controller beat then D622C PIO. len <= M2_BYTES_PER_SECTOR. */ +static int fmss_data_in(struct nand_s5l8740 *f, void *dst, unsigned int len) +{ + if (len < 1 || len > 0x400) + return -EINVAL; + writel(len - 1, f->base + FMLEN); + writel(1, f->base + FMCE); + writel(0, f->base + FMUNK24); + writel(34, f->base + FMCTRL1); + if (fmss_wait48(f, 8)) { + pr_info("s5l8740-nand: data_in wait48(8) timeout len=%u st=%08x\n", + len, f->last_stat48); + return -ETIMEDOUT; + } + return fmss_pio_read(f, dst, len); +} + +/* 41DA70: wait NANDSTAT ready after 0x77/0x7D. */ +static int fmss_wait_status(struct nand_s5l8740 *f, unsigned int loops, u8 *nandstat) +{ + int ret; + + writel(0x800000u, f->base + FMSTAT48); + writel(234, f->base + FMCTRL1); + ret = fmss_wait48_n(f, 0x800000u, loops); + if (nandstat) + *nandstat = (u8)readl(f->base + NANDSTAT); + writel(0x1000000u, f->base + FMSTAT48); + return ret; +} + +static int fmss_addr_n(struct nand_s5l8740 *f, u32 addr, unsigned int cycles) +{ + if (cycles < 1 || cycles > 8) + cycles = 1; + writel(cycles - 1, f->base + FMCYCLES); + writel(addr, f->base + FMADDR); + writel(1, f->base + FMCTRL1); + return fmss_wait48(f, 4); +} + +static int fmss_addr1(struct nand_s5l8740 *f, u32 addr) +{ + return fmss_addr_n(f, addr, 1); +} + +/* + * D6388 + 112A7C: PIO write to +0x80 then kick. Used only for SET FEATURES + * (NAND device registers), never for array program (50DC34). + */ +static int fmss_pio_write(struct nand_s5l8740 *f, const void *src, unsigned int len) +{ + const u32 *p = src; + unsigned int i, words; + + if (len < 4 || (len & 3) || len > 0x400) + return -EINVAL; + words = len >> 2; + writel(readl(f->base + FMCTRL0) | 0x02000000, f->base + FMCTRL0); + writel(readl(f->base + FMCTRL0) & 0xFEFFFBFF, f->base + FMCTRL0); + writel(1, f->base + FMCE); + writel(0, f->base + FMUNK24); + writel(736, f->base + FMCTRL1); + for (i = 0; i < words; i++) + writel(p[i], f->base + FMDATA); + writel(0x100000, f->base + FMSTAT48); + writel(32, f->base + FMCTRL1); + writel(readl(f->base + FMCTRL0) & ~0x02000000u, f->base + FMCTRL0); + + writel(len - 1, f->base + FMLEN); + writel(256, f->base + FMCE); + writel(65764, f->base + FMCTRL1); + return fmss_wait48(f, 8); +} + +/* 1303B4: PPN SET FEATURES (cmd 0xEF). */ +static int fmss_set_feature(struct nand_s5l8740 *f, unsigned int ce, u16 feat, u32 val) +{ + u8 st = 0; + u32 feat_word = feat; + int ret = 0; + + if (ce > 7) + return -EINVAL; + writel((2u * (1u << ce)) | 0xFF001u, f->base + FMCTRL0); + if (fmss_cmd(f, 0xef) || fmss_addr_n(f, feat_word, 2) || + fmss_pio_write(f, &val, 4)) + ret = -ETIMEDOUT; + fmss_cmd(f, 0xe7); + fmss_cmd(f, 0x77); + fmss_cmd(f, 0x7d); + if (fmss_wait_status(f, 100000, &st)) + ret = -ETIMEDOUT; + fmss_cmd(f, 0x77); + writel(1, f->base + FMCTRL0); + fmss_info("set_feature ce=%u feat=0x%04x val=0x%08x ret=%d nandstat=%02x\n", + ce, feat, val, ret, st); + return ret; +} + +/* 41CEDC: PPN GET FEATURES (cmd 0xEE). */ +static u8 last_feat[16]; +static u16 last_feat_id; +static int last_feat_ce = -1; +static int last_feat_ret = -1; + +static int fmss_get_feature(struct nand_s5l8740 *f, unsigned int ce, u16 feat, + void *dst, unsigned int len) +{ + u8 st = 0; + u32 feat_word = feat; + u32 saved; + int ret = 0; + + if (ce > 7 || len < 4 || len > 0x200) + return -EINVAL; + memset(dst, 0, len); + saved = readl(f->base + FMCTRL0); + writel((2u * (1u << ce)) | 0xFF001u, f->base + FMCTRL0); + if (fmss_cmd(f, 0xee) || fmss_addr_n(f, feat_word, 2)) + ret = -ETIMEDOUT; + fmss_cmd(f, 0xe7); + fmss_cmd(f, 0x77); + fmss_cmd(f, 0x7d); + if (fmss_wait_status(f, 100000, &st)) + ret = -ETIMEDOUT; + else { + fmss_cmd(f, 0x7a); + if (fmss_data_in(f, dst, len)) + ret = -ETIMEDOUT; + fmss_cmd(f, 0x77); + } + writel(saved, f->base + FMCTRL0); + fmss_info("get_feature ce=%u feat=0x%04x ret=%d nandstat=%02x %02x %02x %02x %02x\n", + ce, feat, ret, st, + ((u8 *)dst)[0], ((u8 *)dst)[1], ((u8 *)dst)[2], ((u8 *)dst)[3]); + return ret; +} + +/*(ce, addr=0, buf): READ ID, no 10453C reset. */ +static int fmss_read_id(struct nand_s5l8740 *f, unsigned int ce) +{ + u8 *dst = f->last_id; + + if (ce > 7) + return -EINVAL; + + memset(dst, 0, 8); + /* Drop leftover ready bits so 1858DC does not return immediately. */ + writel(0x0e, f->base + FMSTAT48); + + writel(fmss_ctrl0(ce), f->base + FMCTRL0); + fmss_cmd(f, 0x90); + + if (fmss_addr1(f, 0)) + pr_info("s5l8740-nand: wait48(4) after addr timed out st=%08x\n", + readl(f->base + FMSTAT48)); + + if (fmss_data_in(f, dst, 8)) { + f->last_ce = (int)ce; + return -ETIMEDOUT; + } + writel(1, f->base + FMCTRL0); + + f->last_ce = (int)ce; + return 0; +} + +/* 4F1CE8: cmd 0x77/0x7D then wait NAND ready (STAT48 bit 0x800000). */ +static int fmss_wait_ready(struct nand_s5l8740 *f) +{ + int ret; + + fmss_cmd(f, 0x77); + fmss_cmd(f, 0x7d); + writel(1, f->base + FMCE); + writel(234, f->base + FMCTRL1); + ret = fmss_wait48_n(f, 0x800000u, 200000); + writel(32, f->base + FMCTRL1); + writel(0x800000u, f->base + FMSTAT48); + if (ret) + pr_info("s5l8740-nand: ready timeout NANDSTAT=%08x STAT48=%08x\n", + f->last_nandstat, f->last_stat48); + return ret; +} + +static bool ecc_before_drain = true; +module_param(ecc_before_drain, bool, 0644); +MODULE_PARM_DESC(ecc_before_drain, + "Run OSOS 4EB458 ECC/descramble before PIO drain (default Y)"); + +/* + * 0 = diagnostic: FMLEN=52 FMCE=16 CTRL1=34 then data FMCE=1 + * 1 = production-seq style: FMLEN=15 FMCE=0x102 CTRL1=0x1E2, data FMCE=0x201 +0x18=2 + */ +static unsigned int xfer_style; +module_param(xfer_style, uint, 0644); +MODULE_PARM_DESC(xfer_style, + "0=50D960 parity52 (default), 1=DMA-seq-like meta15/CTRL1=0x1E2"); + +static unsigned int ecc_op = 0x1e1; +module_param(ecc_op, uint, 0644); +MODULE_PARM_DESC(ecc_op, + "low bits OR'd into +0x804 for 4EB458 (default 0x1E1; encode uses 0x1E2)"); + +/* OSOS 50D960 uses 16; sweep if UECC persists. */ +static unsigned int parity_fmce = 16; +module_param(parity_fmce, uint, 0644); +MODULE_PARM_DESC(parity_fmce, "FMCE value for 52-byte parity beat (default 16)"); + +static unsigned int data_fmce = 1; +module_param(data_fmce, uint, 0644); +MODULE_PARM_DESC(data_fmce, "FMCE value for 1024-byte data beat (default 1)"); + +static unsigned int xfer_ctrl1 = 34; +module_param(xfer_ctrl1, uint, 0644); +MODULE_PARM_DESC(xfer_ctrl1, "FMCTRL1 for parity/data beats (default 34)"); + +/* + * OSOS(a1=0, len=1024): kick FMSS ECC/descramble engine. + * Must run AFTER parity+data transfer waits, BEFORE FIFO drain. + * + * Exact RetailOS order — do NOT clear +0x810 before kick (preload stays + * 0x3f1f73af). Poll bit0 of +0x810 for completion; if preload already has + * bit0, treat as synchronous after the +0x804 write (first read). + * + * Returns 0 OK, 1 clean/erased page, -ETIMEDOUT / -EIO on hard fail. + */ +static int fmss_ecc_chunk(struct nand_s5l8740 *f, unsigned int seed_a1) +{ + u32 ecc, hist, st; + unsigned int t; + + writel(1, f->base + 0x81c); + writel(0x04000400u, f->base + 0x818); + writel(1, f->base + 0x80c); + writel(0x3f1f73afu, f->base + 0x810); + writel(0x80000000u, f->base + 0x820); + writel(0x02000000u, f->base + 0x808); + writel(seed_a1 | (1024u << 16) | (ecc_op & 0xfffu), f->base + 0x804); + + /* Kick may clear bit0; poll until set again (or already set). */ + for (t = 0; t < 20000; t++) { + st = readl(f->base + 0x810); + if (st & 1u) + break; + udelay(1); + } + if (t >= 20000) { + pr_info("s5l8740-nand: 4EB458 timeout st=%08x\n", st); + return -ETIMEDOUT; + } + ecc = readl(f->base + 0x80c); + hist = readl(f->base + 0x818); + writel(1, f->base + 0x810); + if (!quiet) + fmss_info("4EB458 ecc=0x%08x hist=0x%08x t=%u st=%08x op=%x\n", + ecc, hist, t, st, ecc_op); + if (ecc & 1u) + return -EIO; + if (ecc & 2u) + return 1; + return 0; +} + +/* + *(ce, page_addr, buf, with_parity). + * Per 1KiB chunk: parity beat → data xfer wait → 4EB458 → PIO drain. + * Draining before ECC leaves DATA pages whitened. + */ +static void fmss_meta_ingest_spare(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, unsigned int slot0); + +static int fmss_page_read(struct nand_s5l8740 *f, unsigned int ce, u32 addr) +{ + int i, ret = -EIO, ecc_ret; + u8 *dst = f->last_page; + unsigned int cycles = addr_cycles; + unsigned int chunks = page_chunks; + + if (ce > 7) + return -EINVAL; + if (cycles < 1 || cycles > 8) + cycles = 4; + if (chunks < 1 || chunks > FMSS_MAX_CHUNKS) + chunks = 3; + + memset(dst, 0, FMSS_PAGE_LEN); + memset(f->last_spare, 0, sizeof(f->last_spare)); + f->last_spare_len = 0; + f->last_page_ce = (int)ce; + f->last_page_addr = addr; + f->last_page_chunk = -1; + f->last_page_len = 0; + f->last_clean_chunks = 0; + + writel(7, f->base + FMUNK38); + writel(fmss_page_ctrl0(ce), f->base + FMCTRL0); + writel(0x0FF00FFE, f->base + FMSTAT48); + writel(1, f->base + FMUNK81C); + fmss_cmd(f, 0x0a); + + writel(cycles - 1, f->base + FMCYCLES); + writel(addr, f->base + FMADDR); + writel(1, f->base + FMCTRL1); + if (fmss_wait48(f, 4)) { + pr_info("s5l8740-nand: addr cycle timeout ce=%u addr=%08x st=%08x nand=%08x\n", + ce, addr, f->last_stat48, f->last_nandstat); + goto fail_ctrl0; + } + + if (fmss_cmd(f, 0x37)) { + pr_info("s5l8740-nand: cmd 0x37 timeout ce=%u addr=%08x st=%08x\n", + ce, addr, f->last_stat48); + goto fail_ctrl0; + } + + if (fmss_wait_ready(f)) { + pr_info("s5l8740-nand: not ready after read cmd ce=%u addr=%08x\n", + ce, addr); + goto fail_ctrl0; + } + + fmss_cmd(f, 0x7a); + for (i = 0; i < (int)chunks; i++) { + f->last_page_chunk = i; + f->last_parity_len[i] = 0; + writel(0x08000000, f->base + FMSTAT48); + if (with_parity) { + if (xfer_style == 1) { + writel(15, f->base + FMLEN); + writel(0x102, f->base + FMCE); + writel(1, f->base + FMUNK18); + writel(0, f->base + FMUNK24); + writel(0x1e2, f->base + FMCTRL1); + } else { + /* OSOS 50D960 parity: FMLEN=52 FMCE=16 +0x24=0 CTRL1=34 + * — do not touch +0x18 or +0x28 here. + */ + writel(52, f->base + FMLEN); + writel(parity_fmce, f->base + FMCE); + writel(0, f->base + FMUNK24); + writel(xfer_ctrl1, f->base + FMCTRL1); + } + if (fmss_wait48(f, 8)) { + pr_info("s5l8740-nand: parity xfer timeout ce=%u addr=%08x chunk=%d st=%08x style=%u\n", + ce, addr, i, f->last_stat48, xfer_style); + goto fail_ctrl0; + } + /* + * Live: after FMCE=16 FMLEN=52, D622C can read bytes + * (head often 02 …) — parity lands on the DATA FIFO. + * Never drain it when ecc_before_drain=1 (OSOS leaves + * it for 4EB458). Optional strip only when ECC off. + */ + if (xfer_style == 0 && !ecc_before_drain) { + u8 dig[64]; + + memset(dig, 0, sizeof(dig)); + if (!fmss_pio_read(f, dig, 53)) { + memcpy(f->last_parity[i], dig, 53); + f->last_parity_len[i] = 53; + /* + * Host-visible spare is the first 16 of + * the 53-byte beat, once per 4K slot. + */ + if ((i & 3) == 0) { + unsigned int slot = (unsigned int)i / 4u; + unsigned int pick = 0; + + if (slot < 4) { + if (dig[0] != 0x30) { + if (dig[16] == 0x30) + pick = 16; + else if (dig[32] == 0x30) + pick = 32; + } + memcpy(f->last_spare + slot * 16, + dig + pick, 16); + f->last_spare_len = (slot + 1) * 16; + } + } + if (!quiet) + fmss_info("stripped parity-fifo ch=%d %02x%02x%02x%02x…\n", + i, dig[0], dig[1], dig[2], dig[3]); + } + } + } + writel(1023, f->base + FMLEN); + if (xfer_style == 1) { + writel(2, f->base + FMUNK18); + writel(0x201, f->base + FMCE); + writel(0, f->base + FMUNK24); + writel(0x1e2, f->base + FMCTRL1); + } else { + writel(1, f->base + FMUNK18); + writel(data_fmce, f->base + FMCE); + writel(0, f->base + FMUNK24); + writel(xfer_ctrl1, f->base + FMCTRL1); + } + if (fmss_wait48(f, 8)) { + pr_info("s5l8740-nand: data xfer timeout ce=%u addr=%08x chunk=%d st=%08x nand=%08x style=%u\n", + ce, addr, i, f->last_stat48, f->last_nandstat, + xfer_style); + goto fail_ctrl0; + } + /* OSOS: ECC/descramble BEFORE FIFO drain when parity path used. */ + if (with_parity && ecc_before_drain) { + ecc_ret = fmss_ecc_chunk(f, 0); + if (ecc_ret == 1) { + /* + * Chunk is erased/clean — leave zeros and + * keep going. Aborting the whole page on + * chunk0 made FPart/VFL miss SLC specials + * (glass: 4096 tail reads, tag30=0). + */ + f->last_clean_chunks++; + continue; + } + if (ecc_ret) { + /* Blank BTOC pages are normal during l2v_build — count, don't spam. */ + f->ecc_soft_fails++; + if (!quiet && i == 0) + fmss_info("ECC soft-fail ce=%u addr=%08x ret=%d (raw drain)\n", + ce, addr, ecc_ret); + } + } + if (fmss_pio_read(f, dst + i * FMSS_CHUNK, FMSS_CHUNK)) { + pr_info("s5l8740-nand: PIO drain timeout ce=%u addr=%08x chunk=%d\n", + ce, addr, i); + goto fail_ctrl0; + } + } + + /* + * Trailing fmss_data_in(64) after 16×1K is an empty FIFO (zeros) and + * must not be treated as META (that polluted lba_map with type 0x00). + * Extra style-1 FMLEN=15 beats after this path desynced the next page. + */ + + fmss_cmd(f, 0x77); + writel(0, f->base + FMCTRL0); + f->last_page_ret = 0; + f->last_page_len = chunks * FMSS_CHUNK; + /* Full-page PIO: ingest all META slots into lba_map (pass 2). */ + if (chunks >= 16 && f->last_spare_len >= 16) { + unsigned int pg = addr & L2V_PAGE_MASK; + unsigned int blk = (addr >> FMSS_PAGE_BITS) & L2V_BLOCK_MASK; + unsigned int cau = (addr >> (FMSS_PAGE_BITS + FMSS_BLOCK_BITS)) & + ((1u << FMSS_CAU_BITS) - 1u); + + fmss_meta_ingest_spare(f, ce, cau, blk, pg, 0); + } + return 0; + +fail_ctrl0: + writel(0, f->base + FMCTRL0); + f->last_page_ret = ret; + return ret; +} + +/* + * OSOS 4EDDDC / D39EC: FMSS command-list DMA page read. + * Sequence program is the OSOS blob at 0x8980EA0 (embedded). + * 16-byte PPN spare per 4K sector lands in the spare DMA buffer + * (Sogeti type/bank/weaveSeq/lpn). Status bytes go to stbuf. + * + * Treat CS as a peripheral sequencer (not PL080): clear status, prove + * idle, program pointers, read back posted MMIO, kick last. USB OTG + * already proves SoC bus-master DMA / coherent buffers work. + */ +static int fmss_wait_cs_poll(struct nand_s5l8740 *f, unsigned int loops) +{ + unsigned int i; + u32 irq; + + for (i = 0; i < loops; i++) { + irq = readl(f->base + FMSEQIRQ); + if ((irq & 0xd) == 1) { + f->last_dma_c0c = irq; + f->c0c_seq++; + return 0; + } + if (irq & 0xc) { + f->last_dma_c0c = irq; + f->c0c_seq++; + return -EIO; + } + udelay(10); + } + f->last_dma_c0c = readl(f->base + FMSEQIRQ); + f->c0c_seq++; + return -ETIMEDOUT; +} + +static int fmss_nand_reset(struct nand_s5l8740 *f); + +/* + * Try the idle gate once. Reports which half refused and what it saw. + * + * 0 = idle, 1 = interrupt status will not clear, 2 = sequencer not idle. + */ +static int fmss_cs_preflight_once(struct nand_s5l8740 *f, u32 *c00_out, + u32 *c08_out, u32 *c0c_out) +{ + u32 c08, c0c, c00; + + c00 = readl(f->base + FMSEQ); + c08 = readl(f->base + FMSEQSTAT); + c0c = readl(f->base + FMSEQIRQ); + + f->last_dma_c00 = c00; + f->last_dma_c0c = c0c; + + /* Clear stale completion/error before programming. */ + if (c0c & 0x0d) { + writel(c0c & 0x0d, f->base + FMSEQIRQ); + readl(f->base + FMSEQIRQ); + udelay(10); + c0c = readl(f->base + FMSEQIRQ); + f->last_dma_c0c = c0c; + } + + *c00_out = c00; + *c08_out = c08; + *c0c_out = c0c; + + /* + * Conservative idle gate. Adjust allowed states only after glass logs. + * Avoid kick if status already advertises completion/error/busy noise. + */ + if (c0c & 0x0d) + return 1; + if (c08 != 0 && c08 != 3) + return 2; + return 0; +} + +/* + * Reset the controller once when the gate refuses, rather than failing + * every read until reboot. + * + * This gate is the only source of -EBUSY on the read path, and it said + * nothing at all: the caller turned it into a bare -EBUSY, n31_sftl_read_lba + * turned that into an EIO, and vfat printed "FAT read failed". Three layers + * and not one of them could say that the sequencer was not idle, which is + * the entire content of the failure. + * + * Observed on hardware after a hard hang: recovery completes -- thousands of + * CS reads -- and then every subsequent read returns -EBUSY, with the map + * intact and the LBAs resolving. That is a sequencer left mid-transaction, + * not a mapping fault, and it persisted across a driver reload because + * nothing reasserted the sequencer. + * + * fmss_nand_reset() is already run periodically on this path by + * cs_reset_every, so doing it here is not a new operation, only a better + * trigger for it: a gate that refuses is exactly the condition a reset + * exists to clear. If it does not help, the read fails as before and both + * register snapshots are in the log. + */ +static bool cs_preflight_reset = true; +module_param(cs_preflight_reset, bool, 0644); +MODULE_PARM_DESC(cs_preflight_reset, + "Reset the controller when the CS idle gate refuses (default Y)"); + +static unsigned int cs_preflight_fails; +module_param(cs_preflight_fails, uint, 0444); +static unsigned int cs_preflight_recovered; +module_param(cs_preflight_recovered, uint, 0444); + +static bool fmss_cs_preflight(struct nand_s5l8740 *f) +{ + u32 c00, c08, c0c; + int why; + + why = fmss_cs_preflight_once(f, &c00, &c08, &c0c); + if (!why) + return true; + + cs_preflight_fails++; + dev_err_ratelimited(f->dev, + "CS not idle (%s): C00=%08x C08=%08x C0C=%08x since_reset=%u\n", + why == 1 ? "IRQ status will not clear" : + "sequencer busy", + c00, c08, c0c, f->pages_since_reset); + + if (!cs_preflight_reset) + return false; + + fmss_nand_reset(f); + f->pages_since_reset = 0; + + why = fmss_cs_preflight_once(f, &c00, &c08, &c0c); + if (!why) { + cs_preflight_recovered++; + dev_warn_ratelimited(f->dev, + "CS idle again after reset (recovered %u times)\n", + cs_preflight_recovered); + return true; + } + + dev_err_ratelimited(f->dev, + "CS still not idle after reset (%s): C00=%08x C08=%08x C0C=%08x\n", + why == 1 ? "IRQ status will not clear" : + "sequencer busy", + c00, c08, c0c); + return false; +} + +static void fmss_dma_teardown(struct nand_s5l8740 *f) +{ + struct device *dev = f->dev; + + if (f->irq > 0) { + free_irq(f->irq, f); + f->irq = 0; + } + if (dev) { + if (f->seq) + dma_free_coherent(dev, FMSS_SEQ_READ_LEN, f->seq, f->seq_dma); + if (f->cmdl) + dma_free_coherent(dev, FMSS_DMA_CMDLIST_LEN, f->cmdl, f->cmdl_dma); + if (f->data) + dma_free_coherent(dev, FMSS_PAGE_LEN, f->data, f->data_dma); + if (f->spare) + dma_free_coherent(dev, FMSS_DMA_SPARE_LEN, f->spare, f->spare_dma); + if (f->stbuf) + dma_free_coherent(dev, FMSS_DMA_STATUS_LEN, f->stbuf, f->stbuf_dma); + if (f->bcmdl) + dma_free_coherent(dev, FMSS_DMA_BATCH_CMDL_LEN, f->bcmdl, f->bcmdl_dma); + if (f->bdata) + dma_free_coherent(dev, FMSS_DMA_BATCH_DATA_LEN, f->bdata, f->bdata_dma); + if (f->bspare) + dma_free_coherent(dev, FMSS_DMA_BATCH_SPARE_LEN, f->bspare, f->bspare_dma); + } + f->seq = f->cmdl = f->data = f->spare = f->stbuf = NULL; + f->bcmdl = f->bdata = f->bspare = NULL; + f->batch_ok = false; + f->dma_ok = 0; + f->dma_mapped = 0; +} + +static irqreturn_t fmss_cs_irq(int irq, void *data) +{ + struct nand_s5l8740 *f = data; + u32 st; + + st = readl(f->base + FMSEQIRQ); + f->last_dma_c0c = st; + f->c0c_seq++; + + /* + * Level-style VIC source: clear peripheral before parent EOI, or it + * can retrigger/stick. Snapshot first — waiter must not require C0C + * to remain asserted after W1C. + */ + if (st & 0x0d) { + writel(st & 0x0d, f->base + FMSEQIRQ); + readl(f->base + FMSEQIRQ); + } + + complete(&f->cs_irq); + return IRQ_HANDLED; +} + +static void fmss_peek_vic1(struct nand_s5l8740 *f) +{ + void __iomem *vic1; + + vic1 = ioremap(0x38e01000ul, 0x20); + if (!vic1) + return; + f->last_vic_raw = readl(vic1 + 0x08); + f->last_vic_en = readl(vic1 + 0x10); + iounmap(vic1); +} + +static int fmss_dma_setup(struct nand_s5l8740 *f, struct device *dev) +{ + int ret; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + dev_warn(dev, "dma mask: %d\n", ret); + f->dev = dev; + init_completion(&f->cs_irq); + + f->seq = dma_alloc_coherent(dev, FMSS_SEQ_READ_LEN, &f->seq_dma, GFP_KERNEL); + f->cmdl = dma_alloc_coherent(dev, FMSS_DMA_CMDLIST_LEN, &f->cmdl_dma, GFP_KERNEL); + f->data = dma_alloc_coherent(dev, FMSS_PAGE_LEN, &f->data_dma, GFP_KERNEL); + f->spare = dma_alloc_coherent(dev, FMSS_DMA_SPARE_LEN, &f->spare_dma, GFP_KERNEL); + f->stbuf = dma_alloc_coherent(dev, FMSS_DMA_STATUS_LEN, &f->stbuf_dma, GFP_KERNEL); + if (!f->seq || !f->cmdl || !f->data || !f->spare || !f->stbuf) { + nand_dev_info(dev, "DMA coherent alloc failed, PIO only\n"); + fmss_dma_teardown(f); + return -ENOMEM; + } + /* + * Batch buffers are a speed path, not a correctness one: if the + * allocator cannot find 64 KiB of coherent memory the single-page + * transport still works, so record the failure and carry on rather + * than failing the probe over it. + */ + f->bcmdl = dma_alloc_coherent(dev, FMSS_DMA_BATCH_CMDL_LEN, + &f->bcmdl_dma, GFP_KERNEL); + f->bdata = dma_alloc_coherent(dev, FMSS_DMA_BATCH_DATA_LEN, + &f->bdata_dma, GFP_KERNEL); + f->bspare = dma_alloc_coherent(dev, FMSS_DMA_BATCH_SPARE_LEN, + &f->bspare_dma, GFP_KERNEL); + f->batch_ok = f->bcmdl && f->bdata && f->bspare; + if (!f->batch_ok) + nand_dev_info(dev, "batch DMA buffers unavailable, single-page reads only\n"); + else + memset(f->bcmdl, 0, FMSS_DMA_BATCH_CMDL_LEN); + + memcpy(f->seq, fmss_seq_read_blob, FMSS_SEQ_READ_LEN); + memset(f->cmdl, 0, FMSS_DMA_CMDLIST_LEN); + memset(f->data, 0, FMSS_PAGE_LEN); + memset(f->spare, 0, FMSS_DMA_SPARE_LEN); + memset(f->stbuf, 0, FMSS_DMA_STATUS_LEN); + f->dma_mapped = 1; + f->dma_ok = 1; + + if (dma_irq > 0) { + ret = request_irq(dma_irq, fmss_cs_irq, 0, "fmss-cs", f); + if (ret) { + dev_warn(dev, "CS IRQ %d: %d (polling C0C)\n", dma_irq, ret); + f->irq = 0; + } else { + f->irq = dma_irq; + nand_dev_info(dev, "CS IRQ %d (OSOS 54 / VIC1 22)\n", f->irq); + } + } + + nand_dev_info(dev, "DMA seq_phys=0x%08lx cmdl=0x%08lx data=0x%08lx seq0=%02x %02x %02x %02x coherent=1\n", + (unsigned long)f->seq_dma, (unsigned long)f->cmdl_dma, + (unsigned long)f->data_dma, + ((u8 *)f->seq)[0], ((u8 *)f->seq)[1], + ((u8 *)f->seq)[2], ((u8 *)f->seq)[3]); + return 0; +} + +static int fmss_dma_page_read(struct nand_s5l8740 *f, unsigned int ce, u32 addr) +{ + u32 *cl; + u32 ce_bit; + unsigned int nsect = dma_nsect; + unsigned int i, spare_bytes; + u8 *dp; + int ret = 0; + u32 dregs[32]; + u32 c0c_seq_before = 0; + + memset(dregs, 0, sizeof(dregs)); + + if (!f->dma_ok) + return -ENODEV; + if (ce > 7 || nsect < 1 || nsect > 4) + return -EINVAL; + + /* Refresh CS microcode + wipe all coherent targets (device-visible). */ + memcpy(f->seq, fmss_seq_read_blob, FMSS_SEQ_READ_LEN); + memset(f->data, 0, FMSS_PAGE_LEN); + memset(f->spare, 0, FMSS_DMA_SPARE_LEN); + memset(f->stbuf, 0, FMSS_DMA_STATUS_LEN); + memset(f->cmdl, 0, FMSS_DMA_CMDLIST_LEN); + + cl = f->cmdl; + ce_bit = 1u << (16 + ce); + + /* + * 4EDDDC one-page descriptor list: + * desc0: CE-select dword0=1<<(ce+16), later |0x80000000 if last for CE + * dword2/3 = packed physical address (v40: low in [2]) + * desc1: transfer dword0=(1<<(ce+16))|1, [1]=span, [2]=meta, [3]=data + * term: 0x00010002 + */ + /* + * 4EDDDC address qword from 5172A0 (READ, v40 / multi-LBA page): + * lo = (rec * span) | ((rec * slot) << 16) // length | column<<16 + * hi = encoded_ppn (5173CA, mode 0) + * desc[2]=lo, desc[3]=hi when dma_d14>=7 (v40). + */ + cl[0] = ce_bit; + cl[1] = 0; + { + unsigned int span = nsect; + unsigned int slot = dma_slot; + u32 rec = dma_rec ? dma_rec : FMSS_PPN_REC; + u32 col_len; + + if (slot > 3) + slot = 3; + if (span < 1) + span = 1; + if (span > 4) + span = 4; + if (slot + span > 4) + span = 4 - slot; + col_len = (rec * span) | ((rec * slot) << 16); + + if (dma_row_in_lo) { + /* experiment: put ppn in lo (broken for normal path) */ + cl[2] = addr; + cl[3] = (addr >> 31); + } else if (dma_d14 >= 7) { + cl[2] = col_len; + cl[3] = addr; + } else { + /* !v40: only HIDWORD consumed as ppn; keep col_len in lo anyway */ + cl[2] = addr; + cl[3] = 0; + } + fmss_info("dma slice slot=%u span=%u rec=%u col_len=%08x ppn=%08x\n", + slot, span, rec, col_len, addr); + } + /* Post-pass: OR bit31 onto last CE-select for each CE present. */ + cl[0] |= 0x80000000u; + cl[4] = ce_bit | 1u; + cl[5] = nsect; + cl[6] = (u32)f->spare_dma; + cl[7] = (u32)f->data_dma; + cl[8] = 0x00010002u; + + if (dma_pulse) { + unsigned int t; + + writel(0, f->base + 0xc6c); + writel(1, f->base + 0xc60); + for (t = 0; t < 200000; t++) { + if (readl(f->base + 0xc64) == 1) + break; + udelay(1); + } + writel(0, f->base + 0xc60); + } else { + writel(dma_c6c, f->base + 0xc6c); + } + + if (dma_preamble) { + writel(1, f->base + 0xc10); + writel(4, f->base + 0xc58); + writel(0x0b00, f->base + 0xc4c); + } + + /* + * Clear/idle before programming D-regs / C04 (USB/PL080 pattern: + * clear status, prove idle, program pointers, kick last). + */ + if (!fmss_cs_preflight(f)) { + ret = -EBUSY; + goto dma_done; + } + + /* 4EDDDC register skeleton — bus addresses only. */ + writel(page_ctrl0_or, f->base + FMGEN1); /* D04 timing template */ + writel((u32)f->cmdl_dma, f->base + FMGEN2); /* D08 descriptor list */ + writel(FMSS_SECTOR_LEN, f->base + FMGEN3); /* D0C = 4096 */ + writel((u32)f->stbuf_dma, f->base + FMGEN4); /* D10 status */ + writel(dma_d14, f->base + FMGEN5); /* D14 = addr_cycles-1 */ + writel((u32)f->seq_dma, f->base + FMSEQBASE); /* C04 = seq program */ + /* + * Do NOT poke +0x81C here — that is 4EB458 ECC, not in 4EDDDC/D39EC. + * A spurious 81C write before CS correlates with SoC wedges. + */ + /* D39EC: C00 = 0xFFF5. Do NOT use 0x80000 (reset) here. */ + reinit_completion(&f->cs_irq); + /* Everything after this that observes C0C bumps c0c_seq; see below. */ + c0c_seq_before = f->c0c_seq; + fmss_info("dma kick ce=%u addr=%08x seq=%08x cmdl=%08x data=%08x meta=%08x st=%08x d14=%u kick=%04x dry=%d armed=%d\n", + ce, addr, (u32)f->seq_dma, (u32)f->cmdl_dma, (u32)f->data_dma, + (u32)f->spare_dma, (u32)f->stbuf_dma, dma_d14, dma_kick, + dma_dry, dma_armed); + if (dma_dry) { + f->last_dma_c0c = readl(f->base + FMSEQIRQ); + f->last_dma_d00 = readl(f->base + FMGEN0); + f->last_dma_c00 = readl(f->base + FMSEQ); + ret = -EAGAIN; + goto dma_done; + } + if (!dma_armed) { + ret = -EPERM; + goto dma_done; + } + if (dma_one_shot) + dma_armed = false; + + /* + * Publish the descriptor, data and status buffers before the + * sequencer fetches them. Coherent allocations need no explicit + * cache maintenance, but they do need ordering. + */ + dma_wmb(); + /* Pair the DMA-visible write above with a full barrier. */ + wmb(); + + /* Flush posted MMIO programming before the sequencer kick. */ + readl(f->base + FMGEN2); + readl(f->base + FMGEN3); + readl(f->base + FMGEN4); + readl(f->base + FMGEN5); + readl(f->base + FMSEQBASE); + + writel(dma_kick, f->base + FMSEQ); + readl(f->base + FMSEQ); /* posted write flush */ + + /* + * Prefer IRQ completion snapshot (ISR already W1C'd C0C). Fall back + * to short poll only when no IRQ or completion never arrived. + */ + /* + * dma_irq is a hardcoded Linux IRQ number (this device has no DT node + * -- it is created with platform_device_register_simple), so it is + * only right as long as nothing changes the order in which VIC lines + * get mapped. Boot now loads the periph drivers before storage, which + * is exactly the kind of change that can move it. + * + * request_irq() still succeeds on the wrong line, and the symptom is + * not an error: every read waits the full 200 ms and then polls + * successfully, so the volume works and is two hundred times slower + * than it should be. Notice that instead of paying it forever. + */ + if (f->irq > 0 && !f->irq_dead) { + if (wait_for_completion_timeout(&f->cs_irq, + msecs_to_jiffies(200))) { + f->irq_timeouts = 0; + ret = 0; + } else { + ret = fmss_wait_cs_poll(f, 2000); + if (!ret && ++f->irq_timeouts >= FMSS_IRQ_GIVEUP) { + f->irq_dead = true; + dev_warn(f->dev, + "CS IRQ %d has not fired in %u transfers that then polled clean -- polling from now on\n", + f->irq, f->irq_timeouts); + } + } + } else { + ret = fmss_wait_cs_poll(f, 20000); + } + + /* + * Avoid requiring C0C to still be live — the ISR clears it for VIC + * EOI. Trust the saved snapshot from the ISR or the poll. + * + * The snapshot has to be from THIS transfer, though. last_dma_c0c is + * written by the preflight, the ISR and the poll, and the check used + * to skip itself entirely when the value was zero -- so a transfer + * whose ISR never ran passed on a stale word from the previous read, + * or on the zero the preflight left, and a failed read was reported + * as a good one with whatever happened to be in the DMA buffer. + * + * c0c_seq is stamped by every writer; if it did not move across the + * kick, nothing observed this transfer's status and the result is + * not trustworthy. + */ + if (!ret && f->c0c_seq == c0c_seq_before) { + fmss_info("dma no status observed for this transfer (c0c=%08x)\n", + f->last_dma_c0c); + ret = -EIO; + } + if (!ret && (f->last_dma_c0c & 0x0d) != 1) + ret = -EIO; + + f->last_dma_d00 = readl(f->base + FMGEN0); + f->last_dma_c00 = readl(f->base + FMSEQ); + fmss_peek_vic1(f); + + for (i = 0; i < 32; i++) + dregs[i] = readl(f->base + FMGEN0 + i * 4); + + writel(13, f->base + FMSEQIRQ); + for (i = 0; i < 10000; i++) { + if ((readl(f->base + FMSEQIRQ) & 0xd) == 0) + break; + udelay(1); + } + writel(1, f->base + FMCTRL0); + +dma_done: + if (ret == -EAGAIN) { + for (i = 0; i < 32; i++) + dregs[i] = readl(f->base + FMGEN0 + i * 4); + fmss_peek_vic1(f); + } + + memcpy(f->last_page, f->data, FMSS_PAGE_LEN); + spare_bytes = nsect * 16; + if (spare_bytes > sizeof(f->last_spare)) + spare_bytes = sizeof(f->last_spare); + memcpy(f->last_spare, f->spare, spare_bytes); + f->last_spare_len = spare_bytes; + /* 16 B Sogeti spare per 4 KiB sector → copy into last_parity[group]. */ + for (i = 0; i < nsect; i++) { + unsigned int c, idx = i * 4; + + if (idx >= FMSS_MAX_CHUNKS) + break; + memcpy(f->last_parity[idx], f->spare + i * 16, 16); + f->last_parity_len[idx] = 16; + for (c = 1; c < 4 && idx + c < FMSS_MAX_CHUNKS; c++) + f->last_parity_len[idx + c] = 0; + } + f->last_page_ce = (int)ce; + f->last_page_addr = addr; + f->last_page_len = nsect * FMSS_SECTOR_LEN; + f->last_page_chunk = (int)((u8 *)f->stbuf)[0]; + f->last_page_ret = ret; + f->last_stat48 = readl(f->base + FMSTAT48); + f->last_nandstat = readl(f->base + NANDSTAT); + + dp = f->data; + fmss_info("dma ce=%u addr=%08x ret=%d kick=%04x nsect=%u rowlo=%u d14=%u c00=%08x c04=%08x c0c=%08x c6c=%08x d00=%08x d04=%08x d08=%08x d0c=%08x d10=%08x d14=%08x st=%08x spare=%02x%02x%02x%02x data=%02x%02x%02x%02x%02x%02x%02x%02x\n", + ce, addr, ret, dma_kick, nsect, dma_row_in_lo, dma_d14, + f->last_dma_c00, readl(f->base + FMSEQBASE), f->last_dma_c0c, + readl(f->base + 0xc6c), dregs[0], dregs[1], dregs[2], dregs[3], + dregs[4], dregs[5], *(u32 *)f->stbuf, + f->last_spare[0], f->last_spare[1], f->last_spare[2], + f->last_spare[3], + dp[0], dp[1], dp[2], dp[3], dp[4], dp[5], dp[6], dp[7]); + fmss_info("dma D00..D7C: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x | %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + dregs[0], dregs[1], dregs[2], dregs[3], dregs[4], dregs[5], + dregs[6], dregs[7], dregs[8], dregs[9], dregs[10], dregs[11], + dregs[12], dregs[13], dregs[14], dregs[15], dregs[16], dregs[17], + dregs[18], dregs[19], dregs[20], dregs[21], dregs[22], dregs[23], + dregs[24], dregs[25], dregs[26], dregs[27], dregs[28], dregs[29], + dregs[30], dregs[31]); + fmss_info("dma cmdl: %08x %08x %08x %08x | %08x %08x %08x %08x | %08x seq_dma=%08x cmdl_dma=%08x data_dma=%08x meta_dma=%08x\n", + cl[0], cl[1], cl[2], cl[3], cl[4], cl[5], cl[6], cl[7], cl[8], + (u32)f->seq_dma, (u32)f->cmdl_dma, (u32)f->data_dma, + (u32)f->spare_dma); + if (!ret && !dma_skip_ingest && f->last_spare_len >= 16) { + unsigned int pg = addr & L2V_PAGE_MASK; + unsigned int blk = (addr >> FMSS_PAGE_BITS) & L2V_BLOCK_MASK; + unsigned int cau = (addr >> (FMSS_PAGE_BITS + FMSS_BLOCK_BITS)) & + ((1u << FMSS_CAU_BITS) - 1u); + + fmss_meta_ingest_spare(f, ce, cau, blk, pg, dma_slot); + } + return ret; +} + +/* + * Batched meta read: N pages, one sequencer kick. + * + * This is the transport the FTL classify scan wants. It is deliberately a + * separate function rather than a generalisation of fmss_dma_page_read(): + * that one carries the diagnostic apparatus for CS bring-up and is the path + * the block layer uses. Reworking it to loop would put every read at risk to + * speed up mount, which is the wrong trade. + * + * The list is the same 4EDDDC shape, just longer: + * + * per page i: [8i+0] CE-select 1<<(ce+16), bit31 on the last one only + * [8i+1] 0 + * [8i+2] col_len rec*span | (rec*slot)<<16 + * [8i+3] ppn + * [8i+4] transfer (1<<(ce+16))|1 + * [8i+5] span + * [8i+6] meta target + * [8i+7] data target + * terminator: [8n] 0x00010002 + * + * Every page in a batch must be on the same CE, because bit31 marks the last + * CE-select for a CE and the caller has no way to express a second one here. + * The FTL scans a CE at a time anyway. + * + * span is forced to 1: the scan wants 16 bytes of meta per page, and a + * one-sector transfer is the smallest the descriptor can express. The 4 KiB + * of data it lands anyway is not wasted -- the classifier needs the head of + * slot 0 for its blank test. + */ +static int fmss_dma_meta_read_batch(struct nand_s5l8740 *f, unsigned int ce, + const u32 *addrs, unsigned int n, + unsigned int span, bool scan_mode) +{ + u32 *cl; + u32 ce_bit, col_len, rec; + unsigned int i; + int ret = 0; + + if (!f->dma_ok || !f->batch_ok) + return -ENODEV; + if (ce > 7 || n < 1 || n > FMSS_DMA_BATCH_MAX) + return -EINVAL; + if (span < 1 || span > FMSS_DMA_BATCH_SPAN_MAX) + return -EINVAL; + if (!scan_mode && n * span * FMSS_SECTOR_LEN > FMSS_DMA_BATCH_DATA_LEN) + return -EINVAL; + if (scan_mode && span * FMSS_SECTOR_LEN > FMSS_DMA_BATCH_DATA_LEN) + return -EINVAL; + if (n * span * 16 > FMSS_DMA_BATCH_SPARE_LEN) + return -EINVAL; + + memcpy(f->seq, fmss_seq_read_blob, FMSS_SEQ_READ_LEN); + memset(f->bdata, 0, FMSS_DMA_BATCH_DATA_LEN); + memset(f->bspare, 0, FMSS_DMA_BATCH_SPARE_LEN); + memset(f->stbuf, 0, FMSS_DMA_STATUS_LEN); + memset(f->bcmdl, 0, FMSS_DMA_BATCH_CMDL_LEN); + + cl = f->bcmdl; + ce_bit = 1u << (16 + ce); + rec = dma_rec ? dma_rec : FMSS_PPN_REC; + col_len = rec * span; /* slot 0, span sectors */ + + for (i = 0; i < n; i++) { + u32 *d = cl + i * 8; + + d[0] = ce_bit; + d[1] = 0; + if (dma_d14 >= 7) { + d[2] = col_len; + d[3] = addrs[i]; + } else { + d[2] = addrs[i]; + d[3] = 0; + } + d[4] = ce_bit | 1u; + d[5] = span; + d[6] = (u32)f->bspare_dma + i * span * 16; + /* + * In scan mode every entry writes its page data to the same + * buffer and only the meta pointer advances, so a 256-entry + * list needs one sector of data buffer rather than 256. + * Stock does exactly this: the data argument to its read is a + * fixed pointer across the whole batch while the meta cursor + * steps by 16. + */ + d[7] = scan_mode ? (u32)f->bdata_dma + : (u32)f->bdata_dma + i * span * FMSS_SECTOR_LEN; + } + /* bit31 = last CE-select for this CE, so only the final descriptor. */ + cl[(n - 1) * 8] |= 0x80000000u; + cl[n * 8] = 0x00010002u; + + writel(dma_c6c, f->base + 0xc6c); + + if (!fmss_cs_preflight(f)) + return -EBUSY; + + writel(page_ctrl0_or, f->base + FMGEN1); + writel((u32)f->bcmdl_dma, f->base + FMGEN2); + writel(FMSS_SECTOR_LEN, f->base + FMGEN3); + writel((u32)f->stbuf_dma, f->base + FMGEN4); + writel(dma_d14, f->base + FMGEN5); + writel((u32)f->seq_dma, f->base + FMSEQBASE); + + reinit_completion(&f->cs_irq); + + if (dma_dry) + return -EAGAIN; + if (!dma_armed) + return -EPERM; + + readl(f->base + FMGEN2); + readl(f->base + FMGEN3); + readl(f->base + FMGEN4); + readl(f->base + FMGEN5); + readl(f->base + FMSEQBASE); + + writel(dma_kick, f->base + FMSEQ); + readl(f->base + FMSEQ); + + /* + * Same completion rule as the single-page path, with the timeout + * scaled by the batch: n pages behind one kick is n tR of NAND time + * before the sequencer can finish, and a fixed 200 ms would start + * declaring the IRQ dead on the larger batches. + */ + if (f->irq > 0 && !f->irq_dead) { + if (wait_for_completion_timeout(&f->cs_irq, + msecs_to_jiffies(200 + 20 * n))) { + f->irq_timeouts = 0; + ret = 0; + } else { + ret = fmss_wait_cs_poll(f, 2000 * n); + } + } else { + ret = fmss_wait_cs_poll(f, 20000 * n); + } + + f->last_stat48 = readl(f->base + FMSTAT48); + f->last_nandstat = readl(f->base + NANDSTAT); + return ret; +} + +/* + * Meta for n blocks on one CE, same page in each, in one kick. + * + * meta_out is n * S5L8740_NAND_BATCH_META_SIZE bytes of raw spare. + * + * data_out, when given, gets data_bytes from the head of each page, packed + * at that stride. The transfer always lands a full 4 KiB sector whatever is + * asked for, so the choice is purely how much the caller wants copied out: + * S5L8740_NAND_BATCH_DATA_HEAD (64) is enough for a blank test, and + * S5L8740_NAND_SLOT_DATA (4096) hands over the whole slot, which is what the + * classify scan needs to finish a block without reading it a second time. + * Pass 0 for meta only. + * + * Returns 0 with the outputs filled, or a negative error with neither. + */ +int s5l8740_nand_cs_read_meta_batch(u8 ce, u8 cau, const u16 *blocks, u8 page, + unsigned int n, u8 *meta_out, u8 *data_out, + unsigned int data_bytes) +{ + struct nand_s5l8740 *f = nand_dev; + u32 *addrs; + bool saved_armed; + unsigned int i; + int ret; + u64 t0, t1; + + if (!f || !blocks || !meta_out) + return -ENODEV; + if (!f->dma_ok || !f->batch_ok) + return -ENODEV; + if (ce >= FMSS_NUM_CE || cau >= FMSS_NUM_CAU || page > FMSS_BTOC_PAGE) + return -EINVAL; + if (n < 1 || n > FMSS_DMA_BATCH_MAX) + return -EINVAL; + if (data_bytes > FMSS_SECTOR_LEN) + return -EINVAL; + if (dma_dry) + return -EAGAIN; + if (!dma_armed) + return -EPERM; + + addrs = kmalloc_array(n, sizeof(*addrs), GFP_KERNEL); + if (!addrs) + return -ENOMEM; + for (i = 0; i < n; i++) { + if (blocks[i] >= FMSS_BLOCKS_PER_CAU) { + kfree(addrs); + return -EINVAL; + } + addrs[i] = fmss_ppn_addr(cau, blocks[i], page, 0); + } + + mutex_lock(&f->lock); + if (cs_reset_every && f->pages_since_reset >= cs_reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + saved_armed = dma_armed; + dma_armed = true; + dma_skip_ingest = true; + t0 = ktime_get_ns(); + ret = fmss_dma_meta_read_batch(f, ce, addrs, n, 1, true); + t1 = ktime_get_ns(); + dma_skip_ingest = false; + if (!dma_one_shot) + dma_armed = saved_armed; + f->pages_since_reset += n; + + if (!ret) { + for (i = 0; i < n; i++) { + memcpy(meta_out + i * S5L8740_NAND_BATCH_META_SIZE, + (u8 *)f->bspare + i * 16, + S5L8740_NAND_BATCH_META_SIZE); + if (data_out && data_bytes) + memcpy(data_out + i * data_bytes, + (u8 *)f->bdata + i * FMSS_SECTOR_LEN, + data_bytes); + } + cs_ns_kick += t1 - t0; + cs_reads_total += n; + cs_batch_kicks++; + if (cs_heartbeat && (cs_batch_kicks & 0x3f) == 0) + pr_info("s5l8740-nand: cs_batch kicks=%u pages=%u n=%u kick_us=%llu\n", + cs_batch_kicks, cs_reads_total, n, + div_u64(t1 - t0, 1000)); + } + mutex_unlock(&f->lock); + kfree(addrs); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_read_meta_batch); + +/* + * n consecutive whole pages of one block, in one sequencer kick. + * + * This is the read path's version of the batch. The classify scan wants one + * sector from each of many blocks; a file read wants every sector of a few + * consecutive pages, because four 4 KiB LBAs share a 16 KiB page and the + * LBAs after them are in the next page along. So the axis is transposed: + * one block, pages first_page .. first_page + n - 1, span 4. + * + * data_out gets n * S5L8740_NAND_PAGE_SIZE bytes, meta_out (optional) gets + * n * S5L8740_NAND_META_SIZE -- four 16-byte slot metas per page, laid out + * exactly as s5l8740_nand_cs_phys_read returns them, so a caller can swap + * between the two without reshaping anything. + */ +int s5l8740_nand_cs_read_pages_batch(u8 ce, const struct s5l8740_ppn_ref *refs, + unsigned int n, u8 *data_out, u8 *meta_out) +{ + struct nand_s5l8740 *f = nand_dev; + u32 *addrs; + bool saved_armed; + unsigned int i; + int ret; + u64 t0, t1; + + if (!f || !data_out || !refs) + return -ENODEV; + if (!f->dma_ok || !f->batch_ok) + return -ENODEV; + if (ce >= FMSS_NUM_CE) + return -EINVAL; + if (n < 1 || n > S5L8740_NAND_PAGE_BATCH_MAX) + return -EINVAL; + if (dma_dry) + return -EAGAIN; + if (!dma_armed) + return -EPERM; + + addrs = kmalloc_array(n, sizeof(*addrs), GFP_KERNEL); + if (!addrs) + return -ENOMEM; + for (i = 0; i < n; i++) { + if (refs[i].cau >= FMSS_NUM_CAU || + refs[i].block >= FMSS_BLOCKS_PER_CAU || + refs[i].page > FMSS_BTOC_PAGE) { + kfree(addrs); + return -EINVAL; + } + addrs[i] = fmss_ppn_addr(refs[i].cau, refs[i].block, + refs[i].page, 0); + } + + mutex_lock(&f->lock); + if (cs_reset_every && f->pages_since_reset >= cs_reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + saved_armed = dma_armed; + dma_armed = true; + dma_skip_ingest = true; + t0 = ktime_get_ns(); + ret = fmss_dma_meta_read_batch(f, ce, addrs, n, + S5L8740_NAND_SLOTS_PER_PAGE, false); + t1 = ktime_get_ns(); + dma_skip_ingest = false; + if (!dma_one_shot) + dma_armed = saved_armed; + f->pages_since_reset += n; + + if (!ret) { + memcpy(data_out, f->bdata, + (size_t)n * S5L8740_NAND_PAGE_SIZE); + if (meta_out) + memcpy(meta_out, f->bspare, + (size_t)n * S5L8740_NAND_META_SIZE); + cs_ns_kick += t1 - t0; + cs_reads_total += n; + cs_batch_kicks++; + } + mutex_unlock(&f->lock); + kfree(addrs); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_read_pages_batch); + +int s5l8740_nand_cs_scan_meta(u8 ce, const struct s5l8740_ppn_ref *refs, + unsigned int n, u8 *meta_out) +{ + struct nand_s5l8740 *f = nand_dev; + u32 *addrs; + bool saved_armed; + unsigned int i; + int ret; + + if (!f || !refs || !meta_out) + return -ENODEV; + if (!f->dma_ok || !f->batch_ok) + return -ENODEV; + if (ce >= FMSS_NUM_CE) + return -EINVAL; + if (n < 1 || n > FMSS_DMA_BATCH_MAX) + return -EINVAL; + if (dma_dry) + return -EAGAIN; + if (!dma_armed) + return -EPERM; + + addrs = kmalloc_array(n, sizeof(*addrs), GFP_KERNEL); + if (!addrs) + return -ENOMEM; + for (i = 0; i < n; i++) { + if (refs[i].cau >= FMSS_NUM_CAU || + refs[i].block >= FMSS_BLOCKS_PER_CAU || + refs[i].page > FMSS_BTOC_PAGE) { + kfree(addrs); + return -EINVAL; + } + addrs[i] = fmss_ppn_addr(refs[i].cau, refs[i].block, + refs[i].page, 0); + } + + mutex_lock(&f->lock); + if (cs_reset_every && f->pages_since_reset >= cs_reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + saved_armed = dma_armed; + dma_armed = true; + dma_skip_ingest = true; + ret = fmss_dma_meta_read_batch(f, ce, addrs, n, 1, true); + dma_skip_ingest = false; + if (!dma_one_shot) + dma_armed = saved_armed; + f->pages_since_reset += n; + if (!ret) { + memcpy(meta_out, f->bspare, (size_t)n * 16); + cs_reads_total += n; + cs_batch_kicks++; + } + mutex_unlock(&f->lock); + kfree(addrs); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_scan_meta); + + +/* + * Read the 512-byte PPN parameter page. + * + * Command sequence: 0x92, address 0, 0x97, then 0x77/0x7D to select the + * page, then 0x7A and 512 bytes of PIO. + */ +static int fmss_param_read(struct nand_s5l8740 *f, unsigned int ce) +{ + u8 st = 0; + int ret; + + if (ce > 7) + return -EINVAL; + memset(f->last_param, 0, FMSS_PARAM_LEN); + f->last_param_ce = (int)ce; + + writel(fmss_ctrl0(ce), f->base + FMCTRL0); + if (fmss_cmd(f, 0x92)) { + pr_info("s5l8740-nand: param cmd 0x92 timeout\n"); + ret = -ETIMEDOUT; + goto out_idle; + } + if (fmss_addr1(f, 0)) { + pr_info("s5l8740-nand: param addr timeout st=%08x\n", f->last_stat48); + ret = -ETIMEDOUT; + goto out_idle; + } + if (fmss_cmd(f, 0x97)) { + pr_info("s5l8740-nand: param cmd 0x97 timeout\n"); + ret = -ETIMEDOUT; + goto out_idle; + } + fmss_cmd(f, 0x77); + fmss_cmd(f, 0x7d); + if (fmss_wait_status(f, 100000, &st)) { + pr_info("s5l8740-nand: param ready timeout NANDSTAT=%02x st48=%08x\n", + st, f->last_stat48); + ret = -ETIMEDOUT; + goto out_idle; + } + fmss_cmd(f, 0x7a); + ret = fmss_data_in(f, f->last_param, FMSS_PARAM_LEN); + fmss_cmd(f, 0x77); +out_idle: + writel(1, f->base + FMCTRL0); + f->last_param_ret = ret; + fmss_info("param ce=%u ret=%d nandstat=%02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + ce, ret, st, + f->last_param[0], f->last_param[1], f->last_param[2], f->last_param[3], + f->last_param[4], f->last_param[5], f->last_param[6], f->last_param[7]); + return ret; +} + +/* + * Reset the FMSS controller only; this touches no NAND array state. + * Each CE is then issued command 0xFF, matching the stock reset path. + */ +static int fmss_ctrl_reset(struct nand_s5l8740 *f) +{ + unsigned int i; + u32 v; + + writel(1, f->base + FMCTRL0); + writel(8, f->base + FMSEQ); + v = readl(f->base + FMSEQSTAT); + fmss_info("10453C after C00=8 c00=%08x c08=%08x\n", + readl(f->base + FMSEQ), v); + for (i = 0; i < 200000; i++) { + v = readl(f->base + FMSEQSTAT); + if (v == 4) { + writel(2, f->base + FMSEQ); + for (i = 0; i < 200000; i++) { + v = readl(f->base + FMSEQSTAT); + if (v == 3 || v == 0) + break; + udelay(1); + } + break; + } + if (!v) + break; + udelay(1); + } + if (readl(f->base + 0xc6c) != 16) { + writel(1, f->base + 0xc60); + for (i = 0; i < 200000; i++) { + if (readl(f->base + 0xc64) == 1) + break; + udelay(1); + } + writel(0, f->base + 0xc60); + } + writel(0x80000, f->base + FMSEQ); + writel(2048, f->base + FMCTRL1); + for (i = 0; i < 200000; i++) { + if (readl(f->base + FMCTRL1) & 0x40000000u) + break; + udelay(1); + } + if (!(readl(f->base + FMCTRL1) & 0x40000000u)) { + pr_info("s5l8740-nand: 10453C FMCTRL1 bit30 timeout v=%08x\n", + readl(f->base + FMCTRL1)); + return -ETIMEDOUT; + } + writel(0x80000000, f->base + FMCTRL1); + writel(1, f->base + FMUNK81C); + return 0; +} + +/* 130060: 10453C, then NAND RESET (0xFF) on CE0/CE1. */ +static int fmss_nand_reset(struct nand_s5l8740 *f) +{ + unsigned int ce; + int ret; + + /* Abort a wedged 50D960 (glass: STAT48 stuck 0x080c3002, FIL -110). */ + writel(0, f->base + FMCTRL0); + writel(0x0FF00FFE, f->base + FMSTAT48); + writel(13, f->base + FMSEQIRQ); + udelay(100); + writel(1, f->base + FMCTRL0); + + ret = fmss_ctrl_reset(f); + if (ret) + return ret; + for (ce = 0; ce < 2; ce++) { + writel((2u * (1u << ce)) | 0xFF001u, f->base + FMCTRL0); + writel(2, f->base + FMSTAT48); + if (fmss_cmd(f, 0xff)) + pr_info("s5l8740-nand: cmd 0xFF timeout ce=%u st=%08x\n", + ce, f->last_stat48); + } + msleep(50); + writel(1, f->base + FMCTRL0); + /* 130544: PPN_FEATURE__POWER_STATE (384) = 2 on each CE. */ + for (ce = 0; ce < 2; ce++) + fmss_set_feature(f, ce, 384, 2); + fmss_info("nand_reset done NANDSTAT=%08x STAT48=%08x\n", + readl(f->base + NANDSTAT), readl(f->base + FMSTAT48)); + f->pages_since_reset = 0; + return 0; +} + +static u32 fmss_le32(const u8 *p, unsigned int off) +{ + u32 v; + + memcpy(&v, p + off, 4); + return le32_to_cpu(v); +} + +static ssize_t regs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + static const u32 offs[] = { + FMCTRL0, FMCTRL1, FMCMD, FMADDR, FMCE, FMUNK18, FMUNK24, FMUNK28, + FMCYCLES, FMLEN, FMUNK38, FMSTAT48, NANDSTAT, FMDATA, FMSEQ, FMSEQSTAT, + 0xc04, 0xc10, 0xc38, 0xc4c, 0xc58, 0xc60, 0xc64, 0xc6c, FMUNK81C, + }; + int i, n = 0; + + if (!f || !f->base) + return -ENODEV; + for (i = 0; i < ARRAY_SIZE(offs); i++) + n += scnprintf(buf + n, PAGE_SIZE - n, "+0x%03x: 0x%08x\n", + offs[i], readl(f->base + offs[i])); + return n; +} +static DEVICE_ATTR_RO(regs); + +static ssize_t id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + + if (!f) + return -ENODEV; + return sysfs_emit(buf, "ce=%d %02x %02x %02x %02x %02x %02x %02x %02x\n", + f->last_ce, + f->last_id[0], f->last_id[1], f->last_id[2], + f->last_id[3], f->last_id[4], f->last_id[5], + f->last_id[6], f->last_id[7]); +} +static DEVICE_ATTR_RO(id); + +static ssize_t read_id_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce; + int ret; + + if (!f) + return -ENODEV; + if (kstrtouint(buf, 0, &ce)) + return -EINVAL; + mutex_lock(&f->lock); + ret = fmss_read_id(f, ce); + mutex_unlock(&f->lock); + nand_dev_info(dev, "read_id ce=%u ret=%d id=%02x%02x%02x%02x%02x%02x%02x%02x\n", + ce, ret, + f->last_id[0], f->last_id[1], f->last_id[2], f->last_id[3], + f->last_id[4], f->last_id[5], f->last_id[6], f->last_id[7]); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(read_id); + +static ssize_t page_status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + + if (!f) + return -ENODEV; + return sysfs_emit(buf, + "ce=%d addr=0x%08x ret=%d chunk=%d len=%u parity=%d cycles=%u chunks=%u stat48=0x%08x nand=0x%08x dma=%d c0c=0x%08x d00=0x%08x c00=0x%08x\n", + f->last_page_ce, f->last_page_addr, f->last_page_ret, + f->last_page_chunk, f->last_page_len, with_parity, addr_cycles, + page_chunks, f->last_stat48, f->last_nandstat, f->dma_ok, + f->last_dma_c0c, f->last_dma_d00, f->last_dma_c00); +} +static DEVICE_ATTR_RO(page_status); + +static ssize_t page_hex_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + int i, n = 0; + + if (!f) + return -ENODEV; + for (i = 0; i < 256; i++) { + n += scnprintf(buf + n, PAGE_SIZE - n, "%02x%s", + f->last_page[i], ((i + 1) % 16) ? " " : "\n"); + } + return n; +} +static DEVICE_ATTR_RO(page_hex); + +static ssize_t spare_hex_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + int i, n = 0; + + if (!f) + return -ENODEV; + n += scnprintf(buf + n, PAGE_SIZE - n, "len=%u ", f->last_spare_len); + for (i = 0; i < (int)f->last_spare_len && i < 64; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, "%02x ", f->last_spare[i]); + n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); + return n; +} +static DEVICE_ATTR_RO(spare_hex); + +static u32 fmss_meta_lpn(const u8 *m, unsigned int len) +{ + if (len < 12) + return ~0u; + return get_unaligned_le32(m + 8); +} + +static u8 fmss_meta_type(const u8 *m, unsigned int len) +{ + if (!len) + return 0; + return m[0]; +} + +static ssize_t parity_hex_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int i, j, n = 0; + + if (!f) + return -ENODEV; + for (i = 0; i < FMSS_MAX_CHUNKS; i++) { + if (!f->last_parity_len[i]) + continue; + n += scnprintf(buf + n, PAGE_SIZE - n, "chunk%u len=%u", + i, f->last_parity_len[i]); + for (j = 0; j < f->last_parity_len[i] && j < 32; j++) + n += scnprintf(buf + n, PAGE_SIZE - n, " %02x", + f->last_parity[i][j]); + n += scnprintf(buf + n, PAGE_SIZE - n, " lpn=%u type=%02x\n", + fmss_meta_lpn(f->last_parity[i], f->last_parity_len[i]), + fmss_meta_type(f->last_parity[i], f->last_parity_len[i])); + } + if (!n) + return sysfs_emit(buf, "(no parity captured — page_read with with_parity=1)\n"); + return n; +} +static DEVICE_ATTR_RO(parity_hex); + +static ssize_t page_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, a, b, c, d, cycles; + u32 addr; + int nf, ret; + + if (!f) + return -ENODEV; + /* "CE CAU BLOCK PAGE SLC" or "CE ADDR [cycles]" */ + nf = sscanf(buf, "%u %i %u %u %u", &ce, &a, &b, &c, &d); + if (nf == 5) { + if (a > 1 || b >= FMSS_BLOCKS_PER_CAU || c > FMSS_BTOC_PAGE) + return -EINVAL; + addr = fmss_ppn_addr(a, b, c, d); + } else { + nf = sscanf(buf, "%u %i %u", &ce, &addr, &cycles); + if (nf < 2) + return -EINVAL; + if (nf >= 3 && cycles >= 1 && cycles <= 8) + addr_cycles = cycles; + } + mutex_lock(&f->lock); + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(page_read); + +static ssize_t dma_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, a, b, c, d, cycles; + u32 addr; + int nf, ret; + + if (!f) + return -ENODEV; + if (!f->dma_ok) + return -ENODEV; + nf = sscanf(buf, "%u %i %u %u %u", &ce, &a, &b, &c, &d); + if (nf == 5) { + if (a > 1 || b >= FMSS_BLOCKS_PER_CAU || c > FMSS_BTOC_PAGE) + return -EINVAL; + addr = fmss_ppn_addr(a, b, c, d); + } else { + nf = sscanf(buf, "%u %i %u", &ce, &addr, &cycles); + if (nf < 2) + return -EINVAL; + } + mutex_lock(&f->lock); + + if (dma_reset_before) + fmss_nand_reset(f); + + ret = fmss_dma_page_read(f, ce, addr); + f->pages_since_reset++; + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(dma_read); + +/* + * batch_read: prove the batched transport before anything depends on it. + * + * echo " " > batch_read + * cat batch_read + * + * It reads count consecutive blocks in one kick and prints each page's meta + * next to what the single-page path returns for the same page. The two + * columns must match. They are the whole point of this file: the descriptor + * list format is read out of the 4EDDDC decomp, not measured, and the one + * thing the decomp does not answer is whether the loaded CS microcode blob + * walks a multi-descriptor list or executes one page and stops at the + * terminator. In the latter case every row past the first comes back wrong + * or zero, and this is where that shows up -- not in the middle of a mount. + */ +static char batch_log[4096]; +static unsigned int batch_log_len; + +static ssize_t batch_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + /* + * Heap, not stack: the three of these come to about 1.3 KiB, which + * is most of a kernel stack frame budget for a debug path that has + * no business consuming one. + */ + u8 *meta, *head; + u16 blocks[S5L8740_NAND_BATCH_MAX]; + unsigned int ce, cau, first, page, n, i; + unsigned int len = 0, mismatch = 0, single_failed = 0; + u64 t0, t1, tb, ts; + int ret; + + if (!f) + return -ENODEV; + if (!f->batch_ok) + return -ENODEV; + if (sscanf(buf, "%u %u %u %u %u", &ce, &cau, &first, &page, &n) != 5) + return -EINVAL; + if (n < 1 || n > S5L8740_NAND_BATCH_MAX) + return -EINVAL; + if (first + n > FMSS_BLOCKS_PER_CAU) + return -EINVAL; + + meta = kzalloc(S5L8740_NAND_BATCH_MAX * S5L8740_NAND_BATCH_META_SIZE, + GFP_KERNEL); + head = kzalloc(S5L8740_NAND_BATCH_MAX * S5L8740_NAND_BATCH_DATA_HEAD, + GFP_KERNEL); + if (!meta || !head) { + kfree(meta); + kfree(head); + return -ENOMEM; + } + + for (i = 0; i < n; i++) + blocks[i] = (u16)(first + i); + + t0 = ktime_get_ns(); + ret = s5l8740_nand_cs_read_meta_batch((u8)ce, (u8)cau, blocks, (u8)page, + n, meta, head, + S5L8740_NAND_BATCH_DATA_HEAD); + t1 = ktime_get_ns(); + tb = t1 - t0; + if (ret) { + batch_log_len = scnprintf(batch_log, sizeof(batch_log), + "batch ce=%u cau=%u blk=%u+%u page=%u FAILED ret=%d\n", + ce, cau, first, n, page, ret); + kfree(meta); + kfree(head); + return ret; + } + + len += scnprintf(batch_log + len, sizeof(batch_log) - len, + "batch ce=%u cau=%u blk=%u+%u page=%u ok in %llu us (%llu us/page)\n", + ce, cau, first, n, page, div_u64(tb, 1000), + div_u64(tb, 1000 * n)); + + /* Same pages one at a time, for the column that has to match. */ + t0 = ktime_get_ns(); + for (i = 0; i < n; i++) { + struct s5l8740_cs_page *pg; + const u8 *bm = meta + i * S5L8740_NAND_BATCH_META_SIZE; + bool same = false; + + pg = kzalloc(sizeof(*pg), GFP_KERNEL); + if (!pg) + break; + /* + * Re-arm per read. dma_one_shot defaults on, so the first + * single-page read disarms CS and every one after it returns + * -EPERM -- which reads back as an all-zero meta and looks + * exactly like the batch inventing data. It is not; it is + * this column failing to be collected. + */ + dma_armed = true; + if (!s5l8740_nand_cs_phys_read_slot0((u8)ce, (u8)cau, + blocks[i], (u8)page, pg)) + same = !memcmp(pg->meta_raw[0], bm, + S5L8740_NAND_BATCH_META_SIZE); + else + single_failed++; + if (!same) + mismatch++; + len += scnprintf(batch_log + len, sizeof(batch_log) - len, + " blk=%-5u batch=%16phN single=%16phN %s data=%8phN\n", + blocks[i], bm, pg->meta_raw[0], + same ? "OK" : "MISMATCH", + head + i * S5L8740_NAND_BATCH_DATA_HEAD); + kfree(pg); + } + t1 = ktime_get_ns(); + ts = t1 - t0; + + len += scnprintf(batch_log + len, sizeof(batch_log) - len, + "single %llu us (%llu us/page), %u mismatch, %u single-read failures\n", + div_u64(ts, 1000), div_u64(ts, 1000 * n), + mismatch, single_failed); + batch_log_len = len; + kfree(meta); + kfree(head); + return count; +} + +static ssize_t batch_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!batch_log_len) + return scnprintf(buf, PAGE_SIZE, + "usage: echo \" \" > batch_read\n"); + return scnprintf(buf, PAGE_SIZE, "%s", batch_log); +} +static DEVICE_ATTR_RW(batch_read); + +/* + * CS descriptor ABI canary — no FTL ingest / no lba_map mutation. + * + * Physical page: ce=0 cau=0 block=63 page=88 slc=0 + * + * PIO may show *UOKJIHC at non-aligned offset 7816 — that is a raw + * positive-control artifact only. CS full-page shows sector-aligned + * boot-like payloads at 0 and 8192 (= 2×4096). Prefer rec=4112 + * (4096 data + 16 meta). Span4 is the primary trusted CS path; + * sub-slot is optional. + */ +#define CS_CANARY_MARK "*UOKJIHC" +#define CS_CANARY_MARK_LEN 8 +#define CS_CANARY_CE 0u +#define CS_CANARY_CAU 0u +#define CS_CANARY_BLOCK 63u +#define CS_CANARY_PAGE 88u +#define CS_CANARY_SLC 0u +#define CS_CANARY_PIO_OFF 7816u /* PIO-only artifact; not CS expect */ +#define CS_CANARY_SLOT2_OFF 8192u +#define CS_CANARY_OEM_DELTA 3u +#define CS_CANARY_SECTOR 4096u +#define CS_CANARY_META 16u + +static char cs_canary_log[8192]; + +static int fmss_find_bytes(const u8 *p, unsigned int n, + const char *s, unsigned int sl) +{ + unsigned int i; + + if (!sl || sl > n) + return -1; + for (i = 0; i + sl <= n; i++) { + if (!memcmp(p + i, s, sl)) + return (int)i; + } + return -1; +} + +static bool fmss_meta_nonblank(const u8 *m, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + if (m[i] != 0x00 && m[i] != 0xff) + return true; + } + return false; +} + +static bool fmss_looks_like_bpb(const u8 *p) +{ + return (p[0] == 0xeb || p[0] == 0xe9) && + (p[2] == 0x90 || p[2] == 0x00); +} + +/* Sector-aligned CS hit: BPB jump at expect_off and/or OEM at +3. */ +static bool fmss_cs_canary_hit(const u8 *dp, unsigned int data_len, + unsigned int expect_off, int *mark_off) +{ + int hit; + + hit = fmss_find_bytes(dp, data_len, CS_CANARY_MARK, CS_CANARY_MARK_LEN); + if (mark_off) + *mark_off = hit; + if (expect_off + 3 <= data_len && fmss_looks_like_bpb(dp + expect_off)) + return true; + if (hit == (int)(expect_off + CS_CANARY_OEM_DELTA)) + return true; + return false; +} + +static u64 fmss_le48(const u8 *p) +{ + return (u64)p[0] | ((u64)p[1] << 8) | ((u64)p[2] << 16) | + ((u64)p[3] << 24) | ((u64)p[4] << 32) | ((u64)p[5] << 40); +} + +static int fmss_cs_slot_report(const u8 *dp, const u8 *mp, + unsigned int slot, char *out, size_t out_sz) +{ + const u8 *d = dp + slot * CS_CANARY_SECTOR; + const u8 *m = mp + slot * CS_CANARY_META; + u16 bps = (u16)d[11] | ((u16)d[12] << 8); + u8 spc = d[13]; + u32 lba = (u32)m[8] | ((u32)m[9] << 8) | + ((u32)m[10] << 16) | ((u32)m[11] << 24); + int mark = fmss_find_bytes(d, CS_CANARY_SECTOR, + CS_CANARY_MARK, CS_CANARY_MARK_LEN); + + return scnprintf(out, out_sz, + " slot%u data[0..15]=%*ph oem=%*ph bpb=%d " + "bps=%u spc=%u mark_off=%d\n" + " slot%u meta=%*ph type=%02x flags=%02x " + "weave=%012llx lba=%u aux=%*ph nb=%d\n", + slot, 16, d, 8, d + 3, fmss_looks_like_bpb(d), + bps, spc, mark, + slot, 16, m, m[0], m[1], + (unsigned long long)fmss_le48(m + 2), lba, + 4, m + 12, fmss_meta_nonblank(m, 16)); +} + +static int fmss_cs_canary_one(struct nand_s5l8740 *f, unsigned int ce, u32 addr, + unsigned int slot, unsigned int span, + unsigned int rec, unsigned int expect_off, + bool slot_report, char *out, size_t out_sz) +{ + unsigned int saved_slot = dma_slot; + unsigned int saved_nsect = dma_nsect; + unsigned int saved_rec = dma_rec; + bool saved_armed = dma_armed; + int ret, hit, n, s; + bool pass; + u8 *dp, *sp, *st; + unsigned int data_len; + + dma_slot = slot; + dma_nsect = span; + dma_rec = rec; + + if (dma_reset_before) + fmss_nand_reset(f); + + if (!dma_dry) + dma_armed = true; + + dma_skip_ingest = true; + ret = fmss_dma_page_read(f, ce, addr); + dma_skip_ingest = false; + + dma_slot = saved_slot; + dma_nsect = saved_nsect; + dma_rec = saved_rec; + if (dma_dry) + dma_armed = saved_armed; + + dp = f->last_page; + sp = f->last_spare; + st = f->stbuf ? f->stbuf : f->last_spare; + data_len = f->last_page_len; + if (data_len > FMSS_PAGE_LEN) + data_len = FMSS_PAGE_LEN; + + pass = (expect_off != 0xffffffffu) && + fmss_cs_canary_hit(dp, data_len, expect_off, &hit); + if (expect_off == 0xffffffffu) + fmss_cs_canary_hit(dp, data_len, 0, &hit); + + n = scnprintf(out, out_sz, + "case slot=%u span=%u rec=%u ret=%d dry=%d expect_off=%u " + "mark_off=%d meta_nb=%d\n" + " c00=%08x c04=%08x c08=%08x c0c=%08x c6c=%08x\n" + " d00=%08x d04=%08x d08=%08x d0c=%08x d10=%08x d14=%08x\n" + " st[0..15]=%*ph\n" + " meta[0..15]=%*ph\n" + " data[0..15]=%*ph\n", + slot, span, rec, ret, dma_dry, + expect_off == 0xffffffffu ? 0 : expect_off, hit, + fmss_meta_nonblank(sp, 16), + f->last_dma_c00, readl(f->base + FMSEQBASE), + readl(f->base + FMSEQSTAT), f->last_dma_c0c, + readl(f->base + 0xc6c), + f->last_dma_d00, readl(f->base + FMGEN1), + readl(f->base + FMGEN2), readl(f->base + FMGEN3), + readl(f->base + FMGEN4), readl(f->base + FMGEN5), + 16, st, 16, sp, 16, dp); + + if (expect_off != 0xffffffffu && expect_off < data_len) { + n += scnprintf(out + n, out_sz - n, + " data@expect=%*ph%s\n", + 16, dp + expect_off, + pass ? " HIT" : + (hit >= 0) ? " ELSEWHERE" : " MISS"); + } + if (slot_report && span == 4 && data_len >= FMSS_PAGE_LEN) { + for (s = 0; s < 4; s++) + n += fmss_cs_slot_report(dp, sp, s, + out + n, out_sz - n); + } + if (pass) + n += scnprintf(out + n, out_sz - n, " RESULT=PASS_MARKER\n"); + else if (hit >= 0) + n += scnprintf(out + n, out_sz - n, + " RESULT=MARKER_AT_%d\n", hit); + else if (!ret || ret == -EAGAIN) + n += scnprintf(out + n, out_sz - n, " RESULT=NO_MARKER\n"); + else + n += scnprintf(out + n, out_sz - n, " RESULT=ERR\n"); + + return n; +} + +static ssize_t cs_canary_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!cs_canary_log[0]) + return sysfs_emit(buf, "no canary yet\n"); + return sysfs_emit(buf, "%s", cs_canary_log); +} + +static ssize_t cs_canary_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, a, b, c, d; + unsigned int slot = 0, span = 1, rec = FMSS_PPN_REC; + unsigned int expect_off = 0xffffffffu; + u32 addr; + int nf, n; + + if (!f) + return -ENODEV; + if (!f->dma_ok) + return -ENODEV; + + nf = sscanf(buf, "%u %i %u %u %u %u %u %u", + &ce, &a, &b, &c, &d, &slot, &span, &rec); + if (nf >= 5) { + if (a > 1 || b >= FMSS_BLOCKS_PER_CAU || c > FMSS_BTOC_PAGE) + return -EINVAL; + addr = fmss_ppn_addr(a, b, c, d); + if (nf < 6) + slot = dma_slot; + if (nf < 7) + span = 1; + if (nf < 8) + rec = dma_rec ? dma_rec : FMSS_PPN_REC; + } else { + nf = sscanf(buf, "%u %i %u %u %u", + &ce, &addr, &slot, &span, &rec); + if (nf < 2) + return -EINVAL; + if (nf < 3) + slot = dma_slot; + if (nf < 4) + span = 1; + if (nf < 5) + rec = dma_rec ? dma_rec : FMSS_PPN_REC; + } + + if (slot > 3 || span < 1 || span > 4 || slot + span > 4) + return -EINVAL; + if (rec != 4096 && rec != 4112) + return -EINVAL; + + /* Sector-aligned expects only. */ + if (span == 1) + expect_off = 0; + else if (span == 4 && slot == 0) + expect_off = CS_CANARY_SLOT2_OFF; /* Apple boot in slot2 */ + + if (!dma_dry && !dma_armed) + return -EPERM; + + mutex_lock(&f->lock); + n = scnprintf(cs_canary_log, sizeof(cs_canary_log), + "cs_canary ce=%u addr=%08x slot=%u span=%u rec=%u\n", + ce, addr, slot, span, rec); + n += fmss_cs_canary_one(f, ce, addr, slot, span, rec, expect_off, + span == 4, cs_canary_log + n, + sizeof(cs_canary_log) - n); + f->pages_since_reset++; + nand_dev_info(dev, "%s", cs_canary_log); + mutex_unlock(&f->lock); + return count; +} +static DEVICE_ATTR_RW(cs_canary_read); + +static ssize_t cs_canary_matrix_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t len; + + if (!cs_canary_log[0]) + return sysfs_emit(buf, + "usage: echo 1 > cs_canary_matrix\n" + "page ce=%u cau=%u blk=%u pg=%u\n" + "PIO artifact off=%u (not CS expect)\n" + "E slot2/span1/rec4112 expect data@0 BPB/OEM\n" + "F slot2/span1/rec4096 (meta-in-data check)\n" + "G slot0/span4/rec4112 per-slot BPB+meta\n" + "A/B slot1 legacy; C/D span4 legacy\n" + "(full log also in dmesg)\n", + CS_CANARY_CE, CS_CANARY_CAU, CS_CANARY_BLOCK, + CS_CANARY_PAGE, CS_CANARY_PIO_OFF); + len = strnlen(cs_canary_log, sizeof(cs_canary_log)); + if (len >= PAGE_SIZE) + len = PAGE_SIZE - 1; + memcpy(buf, cs_canary_log, len); + buf[len] = '\0'; + return len; +} + +static ssize_t cs_canary_matrix_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + u32 addr; + unsigned int on = 0; + int n; + struct { + unsigned int slot, span, rec, expect; + bool slot_report; + char tag; + } cases[] = { + /* Primary: sector-aligned slot2 + span4 decode. */ + { 2, 1, 4112, 0, false, 'E' }, + { 2, 1, 4096, 0, false, 'F' }, + { 0, 4, 4112, CS_CANARY_SLOT2_OFF, true, 'G' }, + }; + unsigned int i; + + if (!f) + return -ENODEV; + if (!f->dma_ok) + return -ENODEV; + if (kstrtouint(buf, 0, &on) || !on) + return -EINVAL; + if (!dma_dry && !dma_armed) + return -EPERM; + + addr = fmss_ppn_addr(CS_CANARY_CAU, CS_CANARY_BLOCK, + CS_CANARY_PAGE, CS_CANARY_SLC); + + mutex_lock(&f->lock); + n = scnprintf(cs_canary_log, sizeof(cs_canary_log), + "cs_canary_matrix ce=%u cau=%u blk=%u pg=%u addr=%08x " + "dry=%d\n" + "note: PIO off=%u is artifact; CS expect sector @0/@8192\n", + CS_CANARY_CE, CS_CANARY_CAU, CS_CANARY_BLOCK, + CS_CANARY_PAGE, addr, dma_dry, CS_CANARY_PIO_OFF); + + /* PIO control — proves page exists; do not require CS at 7816. */ + { + int pret, phit; + unsigned int saved_chunks = page_chunks; + unsigned int plen; + + page_chunks = FMSS_MAX_CHUNKS; + pret = fmss_page_read(f, CS_CANARY_CE, addr); + page_chunks = saved_chunks; + plen = f->last_page_len; + if (plen > FMSS_PAGE_LEN) + plen = FMSS_PAGE_LEN; + phit = fmss_find_bytes(f->last_page, plen, + CS_CANARY_MARK, CS_CANARY_MARK_LEN); + n += scnprintf(cs_canary_log + n, sizeof(cs_canary_log) - n, + "PIO ret=%d mark_off=%d (artifact expect~%u) %s\n", + pret, phit, CS_CANARY_PIO_OFF, + (phit >= 0) ? "PASS_CONTROL" : "FAIL_CONTROL"); + if (pret || phit < 0) { + nand_dev_info(dev, "%s", cs_canary_log); + mutex_unlock(&f->lock); + return -EIO; + } + } + + for (i = 0; i < ARRAY_SIZE(cases); i++) { + n += scnprintf(cs_canary_log + n, sizeof(cs_canary_log) - n, + "\n=== %c ===\n", cases[i].tag); + n += fmss_cs_canary_one(f, CS_CANARY_CE, addr, + cases[i].slot, cases[i].span, + cases[i].rec, cases[i].expect, + cases[i].slot_report, + cs_canary_log + n, + sizeof(cs_canary_log) - n); + f->pages_since_reset++; + } + + nand_dev_info(dev, "%s", cs_canary_log); + mutex_unlock(&f->lock); + return count; +} +static DEVICE_ATTR_RW(cs_canary_matrix); + +static char cs_phys_log[2048]; + +static ssize_t cs_phys_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!cs_phys_log[0]) + return sysfs_emit(buf, + "usage: echo CE CAU BLK PG [fmss_lba] > cs_phys_read\n" + "CS span4/rec4112 physical page; optional fmss_lba pick\n" + "requires dma_dry=0 dma_armed=1; no lba_map ingest\n"); + return sysfs_emit(buf, "%s", cs_phys_log); +} + +static ssize_t cs_phys_last_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return cs_phys_read_show(dev, attr, buf); +} +static DEVICE_ATTR_RO(cs_phys_last); + +static ssize_t cs_phys_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, block, page; + u32 want_lba = 0xffffffffu; + struct s5l8740_cs_page *pg; + int nf, ret, n, s, pick; + + if (!f || !f->dma_ok) + return -ENODEV; + /* Accept optional trailing SLC (ignored; CS phys is always MLC=0). */ + nf = sscanf(buf, "%u %u %u %u %u", + &ce, &cau, &block, &page, &want_lba); + if (nf < 4) + return -EINVAL; + /* + * Legacy: "ce cau blk pg slc [lba]" — if 5th token is 0/1 treat as + * SLC and take optional 6th as fmss_lba. + */ + if (nf == 5 && want_lba <= 1) { + unsigned int slc_ignored = want_lba; + u32 lba6 = 0xffffffffu; + int n6 = sscanf(buf, "%u %u %u %u %u %u", + &ce, &cau, &block, &page, &slc_ignored, &lba6); + (void)slc_ignored; + want_lba = (n6 >= 6) ? lba6 : 0xffffffffu; + } + + pg = kzalloc(sizeof(*pg), GFP_KERNEL); + if (!pg) + return -ENOMEM; + + ret = s5l8740_nand_cs_phys_read(ce, cau, block, page, pg); + n = scnprintf(cs_phys_log, sizeof(cs_phys_log), + "cs_phys_read ce=%u cau=%u blk=%u pg=%u ret=%d " + "rec=%u span=4\n", + ce, cau, block, page, ret, N31_CS_REC_SIZE); + if (!ret) { + for (s = 0; s < N31_DATA_SLOTS; s++) { + const struct s5l8740_meta_decoded *sm = &pg->meta[s]; + + n += scnprintf(cs_phys_log + n, sizeof(cs_phys_log) - n, + "slot%u data=%*ph bpb=%d meta type=%02x " + "weave=%012llx fmss_lba=%u valid=%d " + "data_rec=%d\n", + s, 8, pg->data[s], + (pg->data[s][0] == 0xeb || + pg->data[s][0] == 0xe9), + sm->type, (unsigned long long)sm->weave, + sm->lba, sm->valid, + n31_meta_is_data_record(sm)); + } + if (want_lba != 0xffffffffu) { + pick = s5l8740_nand_meta_pick_lba(pg, want_lba); + n += scnprintf(cs_phys_log + n, sizeof(cs_phys_log) - n, + "pick_fmss_lba=%u -> slot=%d weave=%012llx " + "type=%02x\n", + want_lba, pick, + pick >= 0 ? + (unsigned long long)pg->meta[pick].weave : + 0ULL, + pick >= 0 ? pg->meta[pick].type : 0); + } + } + nand_dev_info(dev, "%s", cs_phys_log); + kfree(pg); + return ret && ret != -EAGAIN && ret != -EPERM ? ret : count; +} +static DEVICE_ATTR_RW(cs_phys_read); + +static int fmss_page_blankish(const u8 *p, unsigned int n); + +/* + * PPN DATA spare (CS META stream, type 0x01): + * +0 type, +1 bank/flags, +2..+7 weaveSeq48, +8..+11 LBA LE, +12..+15 aux. + * Whimory chooses the newest weave claimant before FMSS; manual phys reads + * can hit stale historical LBA copies. lba_weave_scan lists them newest-first. + */ +#define FMSS_LBA_CLAIM_MAX 64 +#define FMSS_LBA_TARGETS_MAX 8 +struct fmss_lba_claim { + u64 weave; + u32 lba; + u32 ppn; + u16 block; + u8 ce; + u8 cau; + u8 page; + u8 slot; + u8 typ; +}; + +static struct fmss_lba_claim lba_claims[FMSS_LBA_CLAIM_MAX]; +static unsigned int lba_claim_count; +static unsigned int lba_claim_target; /* primary / first target */ +static unsigned int lba_claim_targets[FMSS_LBA_TARGETS_MAX]; +static unsigned int lba_claim_ntargets; +static unsigned int lba_claim_scanned; +static unsigned int lba_claim_hits; +static unsigned int lba_claim_small; /* type01 && lba < 4096 sightings */ +static unsigned int lba_claim_be_alt; /* BE@+8 matched a target */ +static char lba_claim_log[PAGE_SIZE]; +static unsigned int lba_claim_log_len; + +/* N31 META: weave[15:0]@+2 LE16 | weave[47:16]@+4 LE32 (5688C4 / 568ED4) */ +static u64 fmss_ppn_weave48(const u8 *m) +{ + return (u64)get_unaligned_le16(m + 2) | + ((u64)get_unaligned_le32(m + 4) << 16); +} + +static u32 fmss_ppn_meta_lba(const u8 *m) +{ + return get_unaligned_le32(m + 8); +} + +static bool fmss_lba_is_target(u32 lba) +{ + unsigned int i; + + for (i = 0; i < lba_claim_ntargets; i++) { + if (lba_claim_targets[i] == lba) + return true; + } + return false; +} + +static void fmss_lba_claim_insert(const struct fmss_lba_claim *c) +{ + unsigned int i, j; + + for (i = 0; i < lba_claim_count; i++) { + if (lba_claims[i].ce == c->ce && lba_claims[i].cau == c->cau && + lba_claims[i].block == c->block && + lba_claims[i].page == c->page && + lba_claims[i].slot == c->slot && + lba_claims[i].lba == c->lba) + return; + } + for (i = 0; i < lba_claim_count; i++) { + if (c->weave > lba_claims[i].weave) + break; + } + if (i >= FMSS_LBA_CLAIM_MAX) + return; + if (lba_claim_count < FMSS_LBA_CLAIM_MAX) + lba_claim_count++; + for (j = lba_claim_count - 1; j > i; j--) + lba_claims[j] = lba_claims[j - 1]; + lba_claims[i] = *c; +} + +static void fmss_lba_claim_note_page(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, u32 ppn) +{ + unsigned int s, nslots; + const u8 *meta; + + if (f->last_page_ret) + return; + nslots = f->last_spare_len / 16; + if (nslots > FMSS_VBAS_PER_PAGE) + nslots = FMSS_VBAS_PER_PAGE; + for (s = 0; s < nslots; s++) { + struct fmss_lba_claim c; + u32 lba_le, lba_be; + + meta = f->last_spare + s * 16; + /* + * Diagnostic: any type whose LE LBA matches a target. + * Production mapping still prefers type 0x01. + */ + lba_le = fmss_ppn_meta_lba(meta); + lba_be = get_unaligned_be32(meta + 8); + if (meta[0] == 0x01 && lba_le < 4096u) + lba_claim_small++; + if (fmss_lba_is_target(lba_be) && !fmss_lba_is_target(lba_le)) + lba_claim_be_alt++; + if (!fmss_lba_is_target(lba_le)) + continue; + c.weave = fmss_ppn_weave48(meta); + c.lba = lba_le; + c.ppn = ppn; + c.block = (u16)block; + c.ce = (u8)ce; + c.cau = (u8)cau; + c.page = (u8)page; + c.slot = (u8)s; + c.typ = meta[0]; + fmss_lba_claim_insert(&c); + lba_claim_hits++; + /* + * Raw META for positive-control / endian debug. One line per + * hit with no bound, so it goes through the quiet gate like + * the rest of the per-item tracing in this file. + */ + fmss_info("META hit lba=%u typ=%02x weave=%012llx ce=%u cau=%u blk=%u pg=%u slot=%u meta=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + lba_le, meta[0], (unsigned long long)c.weave, + ce, cau, block, page, s, + meta[0], meta[1], meta[2], meta[3], + meta[4], meta[5], meta[6], meta[7], + meta[8], meta[9], meta[10], meta[11], + meta[12], meta[13], meta[14], meta[15]); + } +} + +/* Erased NAND only — do not treat all-zero DMA failure residue as blank. */ +static int fmss_page_erased(const u8 *p, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + if (p[i] != 0xff) + return 0; + } + return 1; +} + +/* + * CS full-page (slot0/span4) meta hunt for one or more LBAs. + * echo "LBA [START [NBLOCKS]]" > lba_weave_scan + * echo "121,122,123 [START [NBLOCKS]]" > lba_weave_scan + * Default: START=0 NBLOCKS=256 (user area; skip VFL tail). + */ +static int fmss_lba_weave_scan(struct nand_s5l8740 *f, unsigned int start, + unsigned int nblocks) +{ + unsigned int ce, cau, b, pg; + unsigned int saved_nsect, saved_slot, user_max; + u32 addr; + int ret = 0; + + user_max = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + if (!nblocks) + nblocks = 256; + if (start >= user_max) + return -EINVAL; + if (start + nblocks > user_max) + nblocks = user_max - start; + if (!lba_claim_ntargets) + return -EINVAL; + + lba_claim_count = 0; + lba_claim_hits = 0; + lba_claim_scanned = 0; + lba_claim_small = 0; + lba_claim_be_alt = 0; + lba_claim_target = lba_claim_targets[0]; + lba_claim_log_len = 0; + + saved_nsect = dma_nsect; + saved_slot = dma_slot; + dma_nsect = FMSS_VBAS_PER_PAGE; + dma_slot = 0; + + for (unsigned int cei = 0; cei < FMSS_NUM_CE; cei++) { + /* Prefer CE1 first — known user DATA / music live there. */ + ce = (cei == 0) ? 1u : 0u; + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + nblocks; b++) { + unsigned int fail = 0; + + for (pg = 0; pg < FMSS_BTOC_PAGE; pg++) { + /* Always re-init before CS — hung C0C wedges SoC. */ + fmss_nand_reset(f); + f->pages_since_reset = 0; + addr = fmss_ppn_addr(cau, b, pg, 0); + ret = fmss_dma_page_read(f, ce, addr); + lba_claim_scanned++; + if (ret) { + fail++; + if (fail >= 2) + break; + continue; + } + fail = 0; + fmss_lba_claim_note_page(f, ce, cau, b, + pg, addr); + /* Fully erased page0 → skip rest of block. */ + if (pg == 0 && + fmss_page_erased(f->last_page, 64) && + fmss_page_erased(f->last_spare, 16)) + break; + } + if ((b & 7) == 0) { + /* Progress every 8 blocks: quiet-gated. */ + fmss_info("lba_weave_scan prog targets=%u scanned=%u hits=%u small=%u be_alt=%u ce=%u cau=%u blk=%u\n", + lba_claim_ntargets, + lba_claim_scanned, lba_claim_hits, + lba_claim_small, lba_claim_be_alt, + ce, cau, b); + lba_claim_log_len = scnprintf( + lba_claim_log, sizeof(lba_claim_log), + "INPROGRESS scanned=%u hits=%u small=%u be_alt=%u kept=%u ce=%u cau=%u blk=%u\n", + lba_claim_scanned, lba_claim_hits, + lba_claim_small, lba_claim_be_alt, + lba_claim_count, ce, cau, b); + } + } + } + } + + dma_nsect = saved_nsect; + dma_slot = saved_slot; + + lba_claim_log_len = scnprintf(lba_claim_log, sizeof(lba_claim_log), + "targets=%u scanned=%u hits=%u small=%u be_alt=%u kept=%u (newest weave first)\n", + lba_claim_ntargets, lba_claim_scanned, lba_claim_hits, + lba_claim_small, lba_claim_be_alt, lba_claim_count); + for (b = 0; b < lba_claim_count; b++) { + const struct fmss_lba_claim *c = &lba_claims[b]; + + lba_claim_log_len += scnprintf( + lba_claim_log + lba_claim_log_len, + sizeof(lba_claim_log) - lba_claim_log_len, + "CSV %u,%012llx,%u,%u,%u,%u,%u,%08x\n", + c->lba, (unsigned long long)c->weave, c->ce, c->cau, + c->block, c->page, c->slot, c->ppn); + lba_claim_log_len += scnprintf( + lba_claim_log + lba_claim_log_len, + sizeof(lba_claim_log) - lba_claim_log_len, + "%u: lba=%u weave=%012llx ce=%u cau=%u blk=%u pg=%u slot=%u ppn=%08x\n", + b, c->lba, (unsigned long long)c->weave, c->ce, c->cau, + c->block, c->page, c->slot, c->ppn); + } + pr_info("s5l8740-nand: lba_weave_scan %s", lba_claim_log); + return 0; +} + +static int fmss_lba_parse_targets(const char *s, unsigned int *start, + unsigned int *nblocks) +{ + unsigned int n = 0, a = 0, b = 256; + const char *p = s; + char *list, *cursor, *tok; + size_t len = 0; + + lba_claim_ntargets = 0; + while (p[len] && p[len] != ' ' && p[len] != '\t' && p[len] != '\n') + len++; + list = kstrndup(p, len, GFP_KERNEL); + if (!list) + return -ENOMEM; + cursor = list; + while ((tok = strsep(&cursor, ",")) != NULL) { + unsigned int v; + + if (n >= FMSS_LBA_TARGETS_MAX || kstrtouint(tok, 0, &v)) { + kfree(list); + return -EINVAL; + } + lba_claim_targets[n++] = v; + } + kfree(list); + p += len; + if (!n) + return -EINVAL; + while (*p == ' ' || *p == '\t') + p++; + if (*p) { + if (sscanf(p, "%u %u", &a, &b) < 1) + return -EINVAL; + } + *start = a; + *nblocks = b; + lba_claim_ntargets = n; + return 0; +} + +static ssize_t lba_weave_scan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int start = 0, nblocks = 256; + int ret; + + if (!f || !f->dma_ok) + return -ENODEV; + ret = fmss_lba_parse_targets(buf, &start, &nblocks); + if (ret) + return ret; + mutex_lock(&f->lock); + fmss_nand_reset(f); + ret = fmss_lba_weave_scan(f, start, nblocks); + mutex_unlock(&f->lock); + return ret ? ret : count; +} + +static ssize_t lba_weave_scan_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!lba_claim_log_len) + return sysfs_emit(buf, "no scan yet\n"); + return sysfs_emit(buf, "%s", lba_claim_log); +} +static DEVICE_ATTR_RW(lba_weave_scan); + +static ssize_t seq_kick_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int cmd; + + if (!f) + return -ENODEV; + if (kstrtouint(buf, 0, &cmd)) + return -EINVAL; + mutex_lock(&f->lock); + if (!dma_armed) { + mutex_unlock(&f->lock); + return -EPERM; + } + if (dma_one_shot) + dma_armed = false; + writel(cmd, f->base + FMSEQ); + f->last_dma_c00 = readl(f->base + FMSEQ); + f->last_dma_c0c = readl(f->base + FMSEQIRQ); + nand_dev_info(dev, "seq_kick wrote 0x%x now c00=%08x c08=%08x c0c=%08x c04=%08x c38=%08x\n", + cmd, f->last_dma_c00, readl(f->base + FMSEQSTAT), + f->last_dma_c0c, readl(f->base + FMSEQBASE), + readl(f->base + 0xc38)); + mutex_unlock(&f->lock); + return count; +} +static DEVICE_ATTR_WO(seq_kick); + +static int fmss_find(const u8 *p, unsigned int n, const char *s, unsigned int sl) +{ + unsigned int i; + + if (sl == 0 || sl > n) + return 0; + for (i = 0; i + sl <= n; i++) { + if (!memcmp(p + i, s, sl)) + return 1; + } + return 0; +} + +/* + * Match ASCII needle as UTF-16LE or UTF-16BE (FAT LFN / wide strings). + * Returns match byte offset+1, or 0 if not found. + */ +static unsigned int fmss_find_utf16(const u8 *p, unsigned int n, const char *s, + unsigned int sl, bool be) +{ + unsigned int i, j; + + if (sl == 0 || n < sl * 2) + return 0; + for (i = 0; i + sl * 2 <= n; i++) { + for (j = 0; j < sl; j++) { + u8 lo = be ? p[i + j * 2 + 1] : p[i + j * 2]; + u8 hi = be ? p[i + j * 2] : p[i + j * 2 + 1]; + + if (hi != 0 || lo != (u8)s[j]) + break; + } + if (j == sl) + return i + 1; + } + return 0; +} + +/* 16-bit byte-swap of each pair: "AB" -> "BA" stream — PIO/DMA mis-endian probe. */ +static int fmss_find_bswap16(const u8 *p, unsigned int n, const char *s, + unsigned int sl) +{ + unsigned int i, j; + + if (sl < 2 || n < sl) + return 0; + for (i = 0; i + sl <= n; i++) { + for (j = 0; j + 1 < sl; j += 2) { + if (p[i + j] != (u8)s[j + 1] || + p[i + j + 1] != (u8)s[j]) + break; + } + if (j >= sl - (sl & 1)) { + if (!(sl & 1) || p[i + j] == (u8)s[j]) + return 1; + } + } + return 0; +} + +enum { + FMSS_MATCH_ASCII = 1, + FMSS_MATCH_UTF16LE = 2, + FMSS_MATCH_UTF16BE = 4, + FMSS_MATCH_BSWAP16 = 8, +}; + +/* OR of FMSS_MATCH_* flags; also returns first hit offset via *off_out. */ +static unsigned int fmss_find_enc(const u8 *p, unsigned int n, const char *s, + unsigned int sl, unsigned int *off_out) +{ + unsigned int flags = 0, i, u; + + if (off_out) + *off_out = 0; + if (sl == 0 || sl > n) + return 0; + for (i = 0; i + sl <= n; i++) { + if (!memcmp(p + i, s, sl)) { + flags |= FMSS_MATCH_ASCII; + if (off_out && !*off_out) + *off_out = i; + break; + } + } + u = fmss_find_utf16(p, n, s, sl, false); + if (u) { + flags |= FMSS_MATCH_UTF16LE; + if (off_out && !*off_out) + *off_out = u - 1; + } + u = fmss_find_utf16(p, n, s, sl, true); + if (u) { + flags |= FMSS_MATCH_UTF16BE; + if (off_out && !*off_out) + *off_out = u - 1; + } + if (fmss_find_bswap16(p, n, s, sl)) + flags |= FMSS_MATCH_BSWAP16; + return flags; +} + +static const char *fmss_match_enc_name(unsigned int flags) +{ + if (flags & FMSS_MATCH_ASCII) + return "ascii"; + if (flags & FMSS_MATCH_UTF16LE) + return "utf16le"; + if (flags & FMSS_MATCH_UTF16BE) + return "utf16be"; + if (flags & FMSS_MATCH_BSWAP16) + return "bswap16"; + return "none"; +} + +static int fmss_page_blankish(const u8 *p, unsigned int n) +{ + unsigned int i, ff = 0, zz = 0; + + for (i = 0; i < n; i++) { + if (p[i] == 0xff) + ff++; + else if (p[i] == 0x00) + zz++; + } + return ff == n || zz == n; +} + +static u32 fmss_btoc_entry_be(const u8 *btoc_page, unsigned int idx) +{ + return get_unaligned_be32(btoc_page + idx * 4); +} + +static u32 fmss_btoc_entry_le(const u8 *btoc_page, unsigned int idx) +{ + return get_unaligned_le32(btoc_page + idx * 4); +} + +/* Default YaFTL/Sogeti BTOC is big-endian (live blk64: 00 00 00 0b …). */ +static u32 fmss_btoc_entry(const u8 *btoc_page, unsigned int idx) +{ + return fmss_btoc_entry_be(btoc_page, idx); +} + +/* Pick BE vs LE for a BTOC page: prefer the endian with more plausible LPNs. */ +static bool fmss_btoc_prefer_le(const u8 *btoc) +{ + unsigned int i, good_be = 0, good_le = 0; + u32 prev_be = 0, prev_le = 0; + unsigned int seq_be = 0, seq_le = 0; + + for (i = 0; i < 16; i++) { + u32 be = fmss_btoc_entry_be(btoc, i); + u32 le = fmss_btoc_entry_le(btoc, i); + + if (be != 0xffffffff && be < 0x01000000u) + good_be++; + if (le != 0xffffffff && le < 0x01000000u) + good_le++; + if (i && be == prev_be + 1) + seq_be++; + if (i && le == prev_le + 1) + seq_le++; + prev_be = be; + prev_le = le; + } + if (seq_le > seq_be && good_le >= good_be) + return true; + if (good_le >= 4 && good_be <= 1) + return true; + return false; +} + +/* D: probe 2026-08-24: EB 3C 90 OEM "*UOKJIHC", vol "AISPOD FAT32" + * Live copy may have 55AA at 0x1C9 rather than 510 — match OEM, patch on carve. + */ +static bool fmss_apple_fat_sig(const u8 *s) +{ + return s[0] == 0xeb && s[1] == 0x3c && s[2] == 0x90 && + s[3] == '*' && s[4] == 'U' && s[5] == 'O' && s[6] == 'K' && + s[7] == 'J' && s[8] == 'I' && s[9] == 'H' && s[10] == 'C'; +} + +static bool fmss_apple_fat_boot(const u8 *s) +{ + return fmss_apple_fat_sig(s) && s[510] == 0x55 && s[511] == 0xaa; +} + +static bool fmss_page_find_apple_bpb(const u8 *page, unsigned int len, + unsigned int *off_out) +{ + unsigned int off; + + if (len < FMSS_SECTOR_LEN) + return false; + for (off = 0; off + FMSS_SECTOR_LEN <= len; off++) { + if (fmss_apple_fat_sig(page + off)) { + if (off_out) + *off_out = off; + return true; + } + } + return false; +} + +static bool fmss_page_has_fat_boot(const u8 *page, unsigned int len, + unsigned int *off_out) +{ + unsigned int off; + + for (off = 0; off + 512 <= len; off += 512) { + if (fmss_apple_fat_boot(page + off) || + fmss_apple_fat_sig(page + off)) { + if (off_out) + *off_out = off; + return true; + } + } + return fmss_page_find_apple_bpb(page, len, off_out); +} + +/* FAT32 DataStart = reserved + nFATS * FATSz32 (logical sectors). */ +static unsigned int fmss_bpb_data_start(const u8 *bpb) +{ + u16 bps = get_unaligned_le16(bpb + 11); + u16 rsvd = get_unaligned_le16(bpb + 14); + u8 nfats = bpb[16]; + u32 fatz = get_unaligned_le32(bpb + 36); + unsigned int start; + + if (bps != 512 && bps != 4096) + return 1916; + if (!nfats || nfats > 4 || !fatz) + return 1916; + start = (unsigned int)rsvd + (unsigned int)nfats * fatz; + if (!start || start > NAND_FTL_DEFAULT_CAPACITY) + return 1916; + return start; +} + +static u32 fmss_l2v_pack_sec(unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, + unsigned int sec, bool phys) +{ + u32 e = L2V_VALID | + ((ce & 3u) << L2V_CE_SHIFT) | + ((cau & 3u) << L2V_CAU_SHIFT) | + ((sec & L2V_SEC_MASK) << L2V_SEC_SHIFT) | + ((block & L2V_BLOCK_MASK) << L2V_BLOCK_SHIFT) | + (page & L2V_PAGE_MASK); + + if (phys) + e |= L2V_PHYS; + return e; +} + +static u32 fmss_l2v_pack(unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, bool phys) +{ + return fmss_l2v_pack_sec(ce, cau, block, page, L2V_SEC_FROM_LBA, phys); +} + +static void fmss_l2v_unpack_sec(u32 e, unsigned int *ce, unsigned int *cau, + unsigned int *block, unsigned int *page, + unsigned int *sec) +{ + *ce = (e >> L2V_CE_SHIFT) & 3u; + *cau = (e >> L2V_CAU_SHIFT) & 3u; + *sec = (e >> L2V_SEC_SHIFT) & L2V_SEC_MASK; + *block = (e >> L2V_BLOCK_SHIFT) & L2V_BLOCK_MASK; + *page = e & L2V_PAGE_MASK; +} + +static void fmss_l2v_unpack(u32 e, unsigned int *ce, unsigned int *cau, + unsigned int *block, unsigned int *page) +{ + unsigned int sec; + + fmss_l2v_unpack_sec(e, ce, cau, block, page, &sec); + (void)sec; +} + +static bool fmss_claim_better(u8 old_src, u64 old_weave, u8 new_src, + u64 new_weave) +{ + if (!old_src) + return true; + if (new_weave > old_weave) + return true; + if (new_weave < old_weave) + return false; + return new_src > old_src; +} + +static void fmss_wmr_map_free(void) +{ + vfree(wmr_block_map); + wmr_block_map = NULL; + wmr_block_map_n = 0; +} + +static void fmss_lba_map_free(void) +{ + vfree(lba_map); + vfree(lba_weave); + vfree(lba_src); + lba_map = NULL; + lba_weave = NULL; + lba_src = NULL; + lba_mapped = 0; +} + +static unsigned int fmss_lba_map_cap(void) +{ + unsigned int cap = lba_map_max; + + if (!cap) + cap = 262144; + if (cap > FMSS_LBA_MAP_HARDMAX) + cap = FMSS_LBA_MAP_HARDMAX; + return cap; +} + +static int fmss_lba_map_ensure(void) +{ + unsigned int cap; + + if (lba_map) + return 0; + cap = fmss_lba_map_cap(); + lba_map = vzalloc(array_size(cap, sizeof(*lba_map))); + lba_weave = vzalloc(array_size(cap, sizeof(*lba_weave))); + lba_src = vzalloc(array_size(cap, sizeof(*lba_src))); + if (!lba_map || !lba_weave || !lba_src) { + fmss_lba_map_free(); + return -ENOMEM; + } + pr_info("nand-s5l8740: LBA dense map cap=%u (~%u KiB)\n", + cap, (cap * (4 + 8 + 1)) / 1024); + return 0; +} + +static void fmss_l2v_free(void) +{ + vfree(l2v_map); + vfree(l2v_weave); + vfree(l2v_src); + l2v_map = NULL; + l2v_weave = NULL; + l2v_src = NULL; + l2v_map_size = 0; + l2v_mapped = 0; + l2v_max_lpn = 0; + l2v_btoc_hits = 0; + l2v_bmap_hits = 0; + l2v_meta_hits = 0; + fmss_wmr_map_free(); +} + +static int fmss_l2v_ensure(unsigned int max_lpn) +{ + unsigned int need = max_lpn + 1; + u32 *n; + u64 *nw; + u8 *ns; + + if (l2v_map && l2v_map_size >= need) { + l2v_max_lpn = max_lpn; + return 0; + } + n = vzalloc(array_size(need, sizeof(*n))); + nw = vzalloc(array_size(need, sizeof(*nw))); + ns = vzalloc(array_size(need, sizeof(*ns))); + if (!n || !nw || !ns) { + vfree(n); + vfree(nw); + vfree(ns); + return -ENOMEM; + } + if (l2v_map) { + memcpy(n, l2v_map, l2v_map_size * sizeof(*n)); + if (l2v_weave) + memcpy(nw, l2v_weave, l2v_map_size * sizeof(*nw)); + if (l2v_src) + memcpy(ns, l2v_src, l2v_map_size * sizeof(*ns)); + vfree(l2v_map); + vfree(l2v_weave); + vfree(l2v_src); + } + l2v_map = n; + l2v_weave = nw; + l2v_src = ns; + l2v_map_size = need; + l2v_max_lpn = max_lpn; + return 0; +} + +static void fmss_l2v_index_note(unsigned int lpn, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page) +{ + unsigned int i; + + for (i = 0; i < lpn_index_count; i++) { + if (lpn_index[i].lpn == lpn) { + lpn_index[i].ce = ce; + lpn_index[i].cau = cau; + lpn_index[i].block = block; + lpn_index[i].page = page; + return; + } + } + if (lpn_index_count >= FMSS_LPN_INDEX_MAX) + return; + lpn_index[lpn_index_count].lpn = lpn; + lpn_index[lpn_index_count].ce = ce; + lpn_index[lpn_index_count].cau = cau; + lpn_index[lpn_index_count].block = block; + lpn_index[lpn_index_count].page = page; + lpn_index_count++; +} + +static void fmss_l2v_set_ex(unsigned int lpn, unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, bool phys, + u8 src, u64 weave) +{ + u32 prev, packed; + u8 old_src; + u64 old_weave; + + if (!l2v_map || lpn >= l2v_map_size) + return; + prev = l2v_map[lpn]; + old_src = l2v_src ? l2v_src[lpn] : 0; + old_weave = l2v_weave ? l2v_weave[lpn] : 0; + if ((prev & L2V_VALID) && + !fmss_claim_better(old_src, old_weave, src, weave)) + return; + packed = fmss_l2v_pack(ce, cau, block, page, phys); + l2v_map[lpn] = packed; + if (l2v_src) + l2v_src[lpn] = src; + if (l2v_weave) + l2v_weave[lpn] = weave; + if (!(prev & L2V_VALID)) + l2v_mapped++; + fmss_l2v_index_note(lpn, ce, cau, block, page); + if (!quiet && l2v_mapped <= 8) + pr_info("s5l8740-nand: l2v_set lpn=%u src=%u phys=%d ce=%u cau=%u blk=%u pg=%u weave=%llx\n", + lpn, src, phys, ce, cau, block, page, + (unsigned long long)weave); +} + +static void fmss_lba_set(unsigned int lba, unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, unsigned int sec, + bool phys, u8 src, u64 weave) +{ + u32 prev, packed; + u8 old_src; + u64 old_weave; + + if (lba >= fmss_lba_map_cap() || fmss_lba_map_ensure()) + return; + prev = lba_map[lba]; + old_src = lba_src[lba]; + old_weave = lba_weave[lba]; + if ((prev & L2V_VALID) && + !fmss_claim_better(old_src, old_weave, src, weave)) + return; + packed = fmss_l2v_pack_sec(ce, cau, block, page, sec & 3u, phys); + lba_map[lba] = packed; + lba_src[lba] = src; + lba_weave[lba] = weave; + if (!(prev & L2V_VALID)) + lba_mapped++; + if (!quiet && lba_mapped <= 8) + pr_info("s5l8740-nand: lba_set lba=%u src=%u phys=%d ce=%u cau=%u blk=%u pg=%u sec=%u weave=%llx\n", + lba, src, phys, ce, cau, block, page, sec & 3u, + (unsigned long long)weave); +} + +static int fmss_lba_lookup(unsigned int lba, unsigned int *ce, + unsigned int *cau, unsigned int *block, + unsigned int *page, unsigned int *sec, u32 *packed) +{ + u32 e; + + if (!lba_map || lba >= fmss_lba_map_cap()) + return -ENOENT; + e = lba_map[lba]; + if (!(e & L2V_VALID)) + return -ENOENT; + fmss_l2v_unpack_sec(e, ce, cau, block, page, sec); + if (packed) + *packed = e; + return 0; +} + +static int fmss_l2v_lookup_ex(unsigned int lpn, unsigned int *ce, + unsigned int *cau, unsigned int *block, + unsigned int *page, u32 *packed) +{ + u32 e; + + if (!l2v_map || lpn >= l2v_map_size) + return -ENOENT; + e = l2v_map[lpn]; + if (!(e & L2V_VALID)) + return -ENOENT; + fmss_l2v_unpack(e, ce, cau, block, page); + if (packed) + *packed = e; + return 0; +} + +static int fmss_l2v_lookup(unsigned int lpn, unsigned int *ce, + unsigned int *cau, unsigned int *block, + unsigned int *page) +{ + return fmss_l2v_lookup_ex(lpn, ce, cau, block, page, NULL); +} + +static void fmss_early_lba_free(void) +{ + fmss_lba_map_free(); +} + +/* + * Pass 2: promote PIO/DMA META slots into full lba_map. + * type 0x01 data records; weave newest-wins via fmss_lba_set. + */ +static void fmss_meta_ingest_spare(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, unsigned int slot0) +{ + unsigned int s, nslots; + + if (!fmss_legacy_meta_ingest) + return; + if (!f || f->last_page_ret || f->last_spare_len < 16) + return; + if (fmss_lba_map_ensure()) + return; + nslots = f->last_spare_len / 16; + if (nslots > FMSS_VBAS_PER_PAGE) + nslots = FMSS_VBAS_PER_PAGE; + if (slot0 >= FMSS_VBAS_PER_PAGE) + slot0 = 0; + if (slot0 + nslots > FMSS_VBAS_PER_PAGE) + nslots = FMSS_VBAS_PER_PAGE - slot0; + for (s = 0; s < nslots; s++) { + const u8 *meta = f->last_spare + s * 16; + u32 lba, before; + u64 weave; + + if (meta[0] != 0x01) + continue; + lba = get_unaligned_le32(meta + 8); + if (lba >= fmss_lba_map_cap()) + continue; + weave = fmss_ppn_weave48(meta); + before = lba_map[lba]; + fmss_lba_set(lba, ce, cau, block, page, slot0 + s, true, + L2V_SRC_META, weave); + if (lba_map[lba] != before) + l2v_meta_hits++; + } +} + +/* Compat: physical BTOC-style set (legacy callers). */ +static void __maybe_unused fmss_l2v_set(unsigned int lpn, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page) +{ + fmss_l2v_set_ex(lpn, ce, cau, block, page, true, L2V_SRC_BTOC, 0); +} + +/* Thin wrapper — BTOC/carve physical fills. */ +static void __maybe_unused fmss_early_lba_set(unsigned int lba, unsigned int ce, + unsigned int cau, + unsigned int block, + unsigned int page, + unsigned int sec) +{ + fmss_lba_set(lba, ce, cau, block, page, sec, true, L2V_SRC_BTOC, 0); +} + +static int __maybe_unused fmss_early_lba_lookup(unsigned int lba, + unsigned int *ce, + unsigned int *cau, + unsigned int *block, + unsigned int *page, + unsigned int *sec) +{ + return fmss_lba_lookup(lba, ce, cau, block, page, sec, NULL); +} + +static int __maybe_unused fmss_early_lba_ensure(void) +{ + return fmss_lba_map_ensure(); +} + +/* + * Plausible YaFTL/Whimory BTOC: first entries are small LPNs and usually + * sequential (11,12,13 on blk 64, or 0,1,... on the boot superblock). + * Used by on-demand lpn_read scan; l2v_build uses a looser ingest. + */ +static bool fmss_btoc_plausible(const u8 *btoc_page, unsigned int target_lpn, + unsigned int *opage) +{ + unsigned int p, lpn0, lpn1, lpn2, hits = 0; + + lpn0 = fmss_btoc_entry(btoc_page, 0); + lpn1 = fmss_btoc_entry(btoc_page, 1); + lpn2 = fmss_btoc_entry(btoc_page, 2); + if (target_lpn == 0) { + /* Boot superblock: BTOC[0]==0 && BTOC[1]==1 (not a stray 0 elsewhere). */ + if (lpn0 == 0 && lpn1 == 1) { + *opage = 0; + return true; + } + return false; + } + if (lpn0 > 0x1000000 || lpn1 > 0x1000000) + return false; + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + if (fmss_btoc_entry(btoc_page, p) == target_lpn) { + hits++; + *opage = p; + } + } + if (!hits) + return false; + /* Prefer superblocks whose first slots look like FTL metadata. */ + if (lpn0 <= target_lpn && lpn1 == lpn0 + 1) + return true; + if (lpn0 == 11 && lpn1 == 12 && lpn2 == 13) + return true; + return hits == 1; +} + +/* Strict BTOC: need a dense BE (or LE) run — loose >=2 poisoned L2V. */ +static bool fmss_btoc_ingestible(const u8 *btoc) +{ + unsigned int i, good_be = 0, good_le = 0, seq_be = 0, seq_le = 0; + u32 prev_be = 0xffffffff, prev_le = 0xffffffff; + + if (fmss_page_blankish(btoc, 64)) + return false; + for (i = 0; i < 32; i++) { + u32 be = fmss_btoc_entry_be(btoc, i); + u32 le = fmss_btoc_entry_le(btoc, i); + + if (be != 0xffffffff && be < 0x01000000u) { + good_be++; + if (prev_be != 0xffffffff && be == prev_be + 1) + seq_be++; + prev_be = be; + } else { + prev_be = 0xffffffff; + } + if (le != 0xffffffff && le < 0x01000000u) { + good_le++; + if (prev_le != 0xffffffff && le == prev_le + 1) + seq_le++; + prev_le = le; + } else { + prev_le = 0xffffffff; + } + } + /* Prefer sequential tables (live blk64: 11,12,13…). */ + if (good_be >= 8 && seq_be >= 4) + return true; + if (good_le >= 8 && seq_le >= 4 && good_le > good_be) + return true; + /* Boot SB: (0,1) or (0,vbas_per_page) */ + if (fmss_btoc_entry_be(btoc, 0) == 0 && + (fmss_btoc_entry_be(btoc, 1) == 1 || + fmss_btoc_entry_be(btoc, 1) == FMSS_VBAS_PER_PAGE)) + return true; + if (fmss_btoc_entry_le(btoc, 0) == 0 && + (fmss_btoc_entry_le(btoc, 1) == 1 || + fmss_btoc_entry_le(btoc, 1) == FMSS_VBAS_PER_PAGE)) + return true; + return false; +} + +static int fmss_boot_carve_try(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page); + +static void fmss_l2v_ingest_btoc(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + const u8 *btoc, unsigned int max_lpn) +{ + unsigned int p; + bool use_le; + + if (!fmss_btoc_ingestible(btoc)) + return; + /* N31 live BTOCs are BE; only use LE when clearly better. */ + use_le = fmss_btoc_prefer_le(btoc); + l2v_btoc_hits++; + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + u32 lpn = use_le ? fmss_btoc_entry_le(btoc, p) + : fmss_btoc_entry_be(btoc, p); + + if (lpn == 0xffffffff || lpn > max_lpn) + continue; + if (lpn == 0) { + /* + * Avoid fmss_boot_carve_try here — nested full-page + * reads during the BTOC walk wedge FMSS. Discover + * handles BTOC[0]==0 after the walk. + */ + continue; + } + fmss_l2v_set_ex(lpn, ce, cau, block, p, true, L2V_SRC_BTOC, 0); + /* Fill LBA map for all 4 sectors of this page LPN. */ + { + unsigned int s; + + for (s = 0; s < FMSS_VBAS_PER_PAGE; s++) + fmss_lba_set(lpn * FMSS_VBAS_PER_PAGE + s, + ce, cau, block, p, s, true, + L2V_SRC_BTOC, 0); + } + } +} + +/* + * SFTL on-flash BTOC (meta type 28): 16-byte BE records + * +0 weaveSeqAdd, +4 aux, +8 lba, +12 … +15 span in low byte (live: + * 00 00 00 00 | a7 00 00 1d | 00 00 00 79 | 05 00 00 02 → lba=121 span=2). + * Used when YaFTL u32 LPN table is not ingestible. + */ +static bool fmss_page_looks_bte(const u8 *page) +{ + u32 weave0, lba0, span0, lba1, span1; + + if (fmss_page_blankish(page, 64)) + return false; + weave0 = get_unaligned_be32(page); + lba0 = get_unaligned_be32(page + 8); + span0 = page[15]; + if (weave0 != 0 || !span0 || span0 > 128 || lba0 >= 0x01000000u) + return false; + lba1 = get_unaligned_be32(page + 16 + 8); + span1 = page[16 + 15]; + if (!span1 || span1 > 128 || lba1 >= 0x01000000u) + return false; + /* Prefer abutting/near spans (live 121+2 → 123). */ + if (lba1 != lba0 + span0 && lba1 + span1 != lba0 && + (lba1 < lba0 || lba1 > lba0 + span0 + 8)) + return false; + return true; +} + +static void fmss_l2v_ingest_bte(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + const u8 *page, unsigned int max_lpn) +{ + unsigned int i, recs, vba_ofs = 0, hit = 0; + unsigned int usable = 1024; + + (void)max_lpn; + if (!fmss_page_looks_bte(page)) + return; + if (fmss_lba_map_ensure()) + return; + /* Prefer full page when available (pass 2: fill full lba_map). */ + if (f && f->last_page_len) + usable = f->last_page_len; + if (usable > FMSS_PAGE_LEN) + usable = FMSS_PAGE_LEN; + recs = usable / 16; + for (i = 0; i < recs; i++) { + const u8 *r = page + i * 16; + u32 lba = get_unaligned_be32(r + 8); + u32 span = r[15]; + u64 weave = ((u64)get_unaligned_be32(r) << 16) | + (get_unaligned_be16(r + 4) & 0xffffu); + unsigned int j; + + if (!span || span > 128) + break; + if (lba >= 0x01000000u) + break; + hit++; + for (j = 0; j < span; j++) { + u32 cur = lba + j; + unsigned int pg = vba_ofs / FMSS_VBAS_PER_PAGE; + unsigned int sec = vba_ofs % FMSS_VBAS_PER_PAGE; + + if (pg >= FMSS_BTOC_PAGE) + goto done; + if (cur < fmss_lba_map_cap()) + fmss_lba_set(cur, ce, cau, block, pg, sec, + true, L2V_SRC_BTE, weave); + vba_ofs++; + } + } +done: + if (hit) + l2v_btoc_hits++; +} + +/* + * Classic Whimory block-map heuristic: dense array of u16 vblock ids. + * Tries LE then BE. Returns entry count hint. + */ +static bool fmss_page_looks_block_map(const u8 *page, unsigned int len, + bool *be_out, unsigned int *nents) +{ + unsigned int max = min(len / 2u, 2048u); + unsigned int i, good_le = 0, bad_le = 0, good_be = 0, bad_be = 0; + + if (max < 64) + return false; + for (i = 0; i < max; i++) { + u16 le = get_unaligned_le16(page + i * 2); + u16 be = get_unaligned_be16(page + i * 2); + + if (le && le != 0xffff) { + if (le < FMSS_BLOCKS_PER_CAU) + good_le++; + else + bad_le++; + } + if (be && be != 0xffff) { + if (be < FMSS_BLOCKS_PER_CAU) + good_be++; + else + bad_be++; + } + } + if (good_le >= 32 && bad_le * 4 <= good_le && good_le >= good_be) { + *be_out = false; + *nents = max; + return true; + } + if (good_be >= 32 && bad_be * 4 <= good_be) { + *be_out = true; + *nents = max; + return true; + } + return false; +} + +static unsigned int fmss_vfl_phys(unsigned int cau, unsigned int virt); +static unsigned int fmss_vfl_resolve(unsigned int cau, unsigned int virt); +static unsigned int fmss_map_to_phys(unsigned int cau, unsigned int block, + u32 packed); +static int fmss_vfl_ingest(struct nand_s5l8740 *f, unsigned int cau, + unsigned int block, const u8 *hdr); +static int fmss_read_lpn_page(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, u8 *dst, unsigned int dst_len); + +/* + * Classic VFL ftlctrlblocks[3] are LE u16 at +4/+6/+8 (after usn). + * N31 wrmx headers differ; accept the triple only when all look like + * in-range block ids. Also probe a few other offsets in the first 512B. + */ +static bool fmss_wmr_try_ftlctrl_at(const u8 *hdr, unsigned int off, + u16 *out3) +{ + u16 a, b, c; + unsigned int usable = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + + if (off + 6 > FMSS_VFL_HDR_LEN) + return false; + a = get_unaligned_le16(hdr + off); + b = get_unaligned_le16(hdr + off + 2); + c = get_unaligned_le16(hdr + off + 4); + if (!a || a >= usable || b >= usable || c >= usable) + return false; + /* Prefer distinct-ish ctrl blocks (allow one duplicate). */ + if (a == b && b == c) + return false; + out3[0] = a; + out3[1] = b; + out3[2] = c; + return true; +} + +static bool fmss_wmr_extract_ftlctrl(const u8 *hdr, u16 *out3) +{ + static const unsigned int offs[] = { + offsetof(struct wmr_vfl_cxt, ftlctrlblocks), /* 4 */ + 0x08, 0x0c, 0x10, 0x14, 0x18, 0x20, 0x28, 0x30, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(offs); i++) { + if (fmss_wmr_try_ftlctrl_at(hdr, offs[i], out3)) + return true; + } + return false; +} + +static int fmss_wmr_ensure_map(void) +{ + if (wmr_block_map) + return 0; + wmr_block_map = vmalloc(array_size(WMR_BLOCK_MAP_MAX, sizeof(u16))); + if (!wmr_block_map) + return -ENOMEM; + memset(wmr_block_map, 0xff, WMR_BLOCK_MAP_MAX * sizeof(u16)); + wmr_block_map_n = 0; + return 0; +} + +static unsigned int fmss_wmr_load_map_page(const u8 *page, unsigned int len, + bool be, unsigned int nents, + unsigned int dst_off) +{ + unsigned int i, take; + + if (fmss_wmr_ensure_map()) + return 0; + if (dst_off >= WMR_BLOCK_MAP_MAX) + return 0; + take = min(nents, WMR_BLOCK_MAP_MAX - dst_off); + take = min(take, len / 2u); + for (i = 0; i < take; i++) { + u16 v = be ? get_unaligned_be16(page + i * 2) + : get_unaligned_le16(page + i * 2); + + wmr_block_map[dst_off + i] = v; + } + if (dst_off + take > wmr_block_map_n) + wmr_block_map_n = dst_off + take; + return take; +} + +/* Decode classic vPage → CE/CAU/block/page for N31 PPN. */ +static bool fmss_wmr_vpage_to_phys(u32 vpage, unsigned int *ce, + unsigned int *cau, unsigned int *block, + unsigned int *page) +{ + unsigned int pg = vpage % WMR_PAGES_PER_BLOCK; + unsigned int vbn = vpage / WMR_PAGES_PER_BLOCK; + unsigned int usable = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + unsigned int phys; + + *page = pg; + + /* Prefer CE0/CAU0 with identity VFL remap. */ + if (vbn < usable) { + phys = fmss_vfl_phys(0, vbn); + if (phys < FMSS_BLOCKS_PER_CAU) { + *ce = 0; + *cau = 0; + *block = phys; + return true; + } + } + + /* Pack vblock across CAUs (then CEs) when identity is out of range. */ + if (!usable) + return false; + { + unsigned int blk = vbn % usable; + unsigned int cau_i = (vbn / usable) % FMSS_NUM_CAU; + unsigned int ce_i = (vbn / (usable * FMSS_NUM_CAU)) % + FMSS_NUM_CE; + + phys = fmss_vfl_phys(cau_i, blk); + *ce = ce_i; + *cau = cau_i; + *block = phys; + return true; + } +} + +static void fmss_wmr_maybe_reset(struct nand_s5l8740 *f) +{ + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } +} + +/* Bounded DEVICEINFOSIGN hunt: early blocks + VFL tail, page 0 only. */ +static void fmss_wmr_scan_deviceinfo(struct nand_s5l8740 *f, unsigned int nblocks) +{ + unsigned int ce, cau, b, saved; + unsigned int start_tail; + u32 addr; + int ret; + + wmr_dis_hits = 0; + if (!nblocks || nblocks > WMR_MOUNT_MAX_BLOCKS) + nblocks = min(grep_max_blocks, WMR_MOUNT_MAX_BLOCKS); + if (!nblocks) + nblocks = 16; + start_tail = FMSS_BLOCKS_PER_CAU - nblocks; + + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = 0; b < nblocks; b++) { + fmss_wmr_maybe_reset(f); + addr = fmss_ppn_addr(cau, b, 0, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (!ret && + fmss_find_enc(f->last_page, f->last_page_len, + WMR_DEVICEINFOSIGN, + strlen(WMR_DEVICEINFOSIGN), + NULL)) { + wmr_dis_hits++; + if (wmr_dis_hits == 1) { + wmr_dis_ce = ce; + wmr_dis_cau = cau; + wmr_dis_block = b; + wmr_dis_page = 0; + } + } + } + for (b = start_tail; b < FMSS_BLOCKS_PER_CAU; b++) { + fmss_wmr_maybe_reset(f); + addr = fmss_ppn_addr(cau, b, 0, 1); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (!ret && + fmss_find_enc(f->last_page, f->last_page_len, + WMR_DEVICEINFOSIGN, + strlen(WMR_DEVICEINFOSIGN), + NULL)) { + wmr_dis_hits++; + if (wmr_dis_hits == 1) { + wmr_dis_ce = ce; + wmr_dis_cau = cau; + wmr_dis_block = b; + wmr_dis_page = 0; + } + } + } + } + } + page_chunks = saved; +} + +/* Tail VFL wrmx/xrmw ingest + optional classic ftlctrlblocks. */ +static void fmss_wmr_scan_vfl(struct nand_s5l8740 *f, unsigned int nblocks) +{ + unsigned int ce, cau, i, saved, start; + u16 ctrl[3]; + int ret; + + wmr_vfl_hits = 0; + wmr_ftlctrl_hits = 0; + wmr_ftlctrl_n = 0; + vfl_map_count = 0; + if (!nblocks || nblocks > WMR_MOUNT_MAX_BLOCKS) + nblocks = min(vfl_build_blocks ? vfl_build_blocks : 32u, + WMR_MOUNT_MAX_BLOCKS); + if (nblocks > FMSS_VFL_TAIL) + nblocks = FMSS_VFL_TAIL; + start = FMSS_BLOCKS_PER_CAU - nblocks; + + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (i = 0; i < nblocks; i++) { + unsigned int blk = start + i; + u32 addr; + + fmss_wmr_maybe_reset(f); + addr = fmss_ppn_addr(cau, blk, 0, 1); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret || fmss_page_blankish(f->last_page, 512)) + continue; + if (fmss_vfl_ingest(f, cau, blk, f->last_page)) + wmr_vfl_hits++; + if (fmss_wmr_extract_ftlctrl(f->last_page, + ctrl)) { + wmr_ftlctrl_hits++; + if (!wmr_ftlctrl_n) { + wmr_ftlctrl[0] = ctrl[0]; + wmr_ftlctrl[1] = ctrl[1]; + wmr_ftlctrl[2] = ctrl[2]; + wmr_ftlctrl_n = 3; + } + } + } + } + } + page_chunks = saved; +} + +static void fmss_wmr_try_load_bmap(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int *dst_off) +{ + bool be = false; + unsigned int nents = 0, take; + + if (fmss_read_lpn_page(f, ce, cau, block, 0, NULL, 0)) + return; + if (!fmss_page_looks_block_map(f->last_page, f->last_page_len, + &be, &nents)) + return; + take = fmss_wmr_load_map_page(f->last_page, f->last_page_len, be, + nents, *dst_off); + if (take) { + wmr_bmap_pages++; + *dst_off += take; + } +} + +/* Probe ftlctrl blocks + bounded early/tail for type-0x44-like maps. */ +static void fmss_wmr_scan_block_maps(struct nand_s5l8740 *f, unsigned int nblocks) +{ + unsigned int ce, cau, b, i, saved, dst = 0; + unsigned int start_tail; + + wmr_bmap_pages = 0; + fmss_wmr_map_free(); + if (!nblocks || nblocks > WMR_MOUNT_MAX_BLOCKS) + nblocks = min(grep_max_blocks, WMR_MOUNT_MAX_BLOCKS); + if (!nblocks) + nblocks = 16; + start_tail = FMSS_BLOCKS_PER_CAU - nblocks; + + saved = page_chunks; + page_chunks = 16; + + for (i = 0; i < wmr_ftlctrl_n && dst < WMR_BLOCK_MAP_MAX; i++) { + u16 vb = wmr_ftlctrl[i]; + + if (!vb || vb >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + continue; + for (ce = 0; ce < FMSS_NUM_CE && dst < WMR_BLOCK_MAP_MAX; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU && + dst < WMR_BLOCK_MAP_MAX; cau++) { + unsigned int phys = fmss_vfl_phys(cau, vb); + + fmss_wmr_try_load_bmap(f, ce, cau, phys, &dst); + } + } + } + + for (ce = 0; ce < FMSS_NUM_CE && dst < WMR_BLOCK_MAP_MAX; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU && + dst < WMR_BLOCK_MAP_MAX; cau++) { + unsigned int probes = 0; + + for (b = 0; b < nblocks && probes < 24 && + dst < WMR_BLOCK_MAP_MAX; b++) { + fmss_wmr_try_load_bmap(f, ce, cau, b, &dst); + probes++; + } + for (b = start_tail; b < FMSS_BLOCKS_PER_CAU && + probes < 40 && dst < WMR_BLOCK_MAP_MAX; b++) { + fmss_wmr_try_load_bmap(f, ce, cau, b, &dst); + probes++; + } + } + } + page_chunks = saved; +} + +static unsigned int fmss_wmr_fill_l2v(unsigned int max_lpn) +{ + unsigned int lpn, filled = 0; + + if (!wmr_block_map || !wmr_block_map_n) + return 0; + if (fmss_l2v_ensure(max_lpn)) + return 0; + + for (lpn = 0; lpn <= max_lpn; lpn++) { + u32 vpage; + unsigned int ce, cau, block, page; + + vpage = wmr_lpage_to_vpage(lpn, WMR_PAGES_PER_BLOCK, + wmr_block_map, wmr_block_map_n); + if (vpage == ~0u) + continue; + if (!fmss_wmr_vpage_to_phys(vpage, &ce, &cau, &block, &page)) + continue; + /* Avoid clobber denser BTOC hits already present. */ + if (l2v_map && lpn < l2v_map_size && + (l2v_map[lpn] & L2V_VALID)) + continue; + /* Classic WMR vpage already resolved to physical block. */ + fmss_l2v_set_ex(lpn, ce, cau, block, page, true, + L2V_SRC_WMR, 0); + filled++; + } + return filled; +} + +/* + * Classic freemyipod Whimory mount adapted for N31 PPN. + * Does not clear existing l2v_build / boot_carve results. + * Usage: echo 1 > whimory_mount + * echo "NBLOCKS [MAX_LPN]" > whimory_mount + */ +static int fmss_whimory_mount(struct nand_s5l8740 *f, unsigned int nblocks, + unsigned int max_lpn) +{ + if (!max_lpn) + max_lpn = FMSS_L2V_DEFAULT_MAX_LPN; + if (!nblocks || nblocks > WMR_MOUNT_MAX_BLOCKS) + nblocks = min(grep_max_blocks ? grep_max_blocks : 32u, + WMR_MOUNT_MAX_BLOCKS); + + wmr_l2v_filled = 0; + wmr_mount_ret = 0; + + fmss_wmr_scan_deviceinfo(f, nblocks); + fmss_wmr_scan_vfl(f, min(nblocks, FMSS_VFL_TAIL)); + fmss_wmr_scan_block_maps(f, nblocks); + wmr_l2v_filled = fmss_wmr_fill_l2v(max_lpn); + + if (!wmr_block_map_n && !wmr_l2v_filled) + wmr_mount_ret = -ENOENT; + else + wmr_mount_ret = 0; + + nand_dev_info(f->dev, + "whimory_mount n=%u max_lpn=%u dis=%u vfl=%u ftlctrl=%u bmap_pages=%u map_ents=%u filled=%u ret=%d\n", + nblocks, max_lpn, wmr_dis_hits, wmr_vfl_hits, + wmr_ftlctrl_hits, wmr_bmap_pages, wmr_block_map_n, + wmr_l2v_filled, wmr_mount_ret); + return wmr_mount_ret; +} + +static unsigned int fmss_vfl_phys(unsigned int cau, unsigned int virt) +{ + unsigned int i; + + for (i = 0; i < vfl_map_count; i++) { + if (vfl_map[i].cau == cau && vfl_map[i].virt == virt) + return vfl_map[i].phys; + } + return virt; +} + +/* + * Resolve virt→phys according to vfl_remap_mode. + * Never call this for L2V_PHYS entries — use fmss_map_to_phys(). + */ +static unsigned int fmss_vfl_resolve(unsigned int cau, unsigned int virt) +{ + unsigned int phys, i; + + if (!strncmp(vfl_remap_mode, "off", 3)) + return virt; + + phys = fmss_vfl_phys(cau, virt); + if (phys == virt) + return virt; + + if (!strncmp(vfl_remap_mode, "tail_only", 9)) { + if (virt < FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + return virt; + } else if (strncmp(vfl_remap_mode, "direct256", 9)) { + /* Unknown mode → treat as off. */ + return virt; + } + + for (i = 0; i < vfl_map_count; i++) { + if (vfl_map[i].cau == cau && vfl_map[i].virt == virt) { + vfl_remap_applied++; + if (!quiet && vfl_remap_applied <= 32) + pr_info("s5l8740-nand: vfl_remap mode=%s cau=%u in=%u out=%u idx=%u\n", + vfl_remap_mode, cau, virt, phys, i); + return phys; + } + } + return virt; +} + +static unsigned int fmss_map_to_phys(unsigned int cau, unsigned int block, + u32 packed) +{ + if (packed & L2V_PHYS) { + vfl_remap_skipped_phys++; + return block; + } + return fmss_vfl_resolve(cau, block); +} + +/* + * wrmx/xrmw VFLCxt: 512-byte header, u32 remap table begins @ +0x100. + * Live pod: entries are LE phys block numbers (e.g. 0x827 = 2087). + */ +static int fmss_vfl_ingest(struct nand_s5l8740 *f, unsigned int cau, + unsigned int block, const u8 *hdr) +{ + unsigned int i, virt, phys, added = 0; + const u8 *tab = hdr + 0x100; + const char *magic = "????"; + + if (hdr[0] == 'w' && hdr[1] == 'r' && hdr[2] == 'm' && hdr[3] == 'x') + magic = "wrmx"; + else if (hdr[0] == 'x' && hdr[1] == 'r' && hdr[2] == 'm' && + hdr[3] == 'w') + magic = "xrmw"; + else + return 0; + + if (cau < FMSS_NUM_CAU) { + vfl_ctx_cau[cau] = cau; + vfl_ctx_block[cau] = block; + } + + for (i = 0; i < 256; i++) { + memcpy(&phys, tab + i * 4, 4); + phys = le32_to_cpu(phys); + if (!phys || phys >= FMSS_BLOCKS_PER_CAU) + continue; + virt = i; + if (vfl_map_count < FMSS_VFL_MAP_MAX) { + vfl_map[vfl_map_count].cau = cau; + vfl_map[vfl_map_count].virt = virt; + vfl_map[vfl_map_count].phys = phys; + vfl_map_count++; + added++; + } + } + nand_dev_info(f->dev, "vfl_ingest cau=%u blk=%u magic=%s entries=%u total=%u\n", + cau, block, magic, added, vfl_map_count); + return added; +} + +static void fmss_vfl_format_log(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, u32 addr) +{ + unsigned int i, off = 0; + u32 t0, t1, t2, t3; + const char *magic = "????"; + + if (f->last_page[0] == 'w' && f->last_page[1] == 'r' && + f->last_page[2] == 'm' && f->last_page[3] == 'x') + magic = "wrmx"; + else if (f->last_page[0] == 'x' && f->last_page[1] == 'r' && + f->last_page[2] == 'm' && f->last_page[3] == 'w') + magic = "xrmw"; + + memcpy(&t0, f->last_page + 256, 4); + memcpy(&t1, f->last_page + 260, 4); + memcpy(&t2, f->last_page + 264, 4); + memcpy(&t3, f->last_page + 268, 4); + /* Also dump first table dwords @ +0x100 (OSOS 4EB7E4). */ + if (off < PAGE_SIZE - 80) { + u32 u0, u1, u2, u3; + + memcpy(&u0, f->last_page + 0x100, 4); + memcpy(&u1, f->last_page + 0x104, 4); + memcpy(&u2, f->last_page + 0x108, 4); + memcpy(&u3, f->last_page + 0x10c, 4); + off += scnprintf(vfl_log + off, PAGE_SIZE - off, + "table+0x100: %08x %08x %08x %08x\n", + le32_to_cpu(u0), le32_to_cpu(u1), + le32_to_cpu(u2), le32_to_cpu(u3)); + } + + off += scnprintf(vfl_log + off, PAGE_SIZE - off, + "ce=%u cau=%u blk=%u addr=0x%08x magic=%s\n", + ce, cau, block, addr, magic); + off += scnprintf(vfl_log + off, PAGE_SIZE - off, + "table+256: %08x %08x %08x %08x\n", + le32_to_cpu(t0), le32_to_cpu(t1), + le32_to_cpu(t2), le32_to_cpu(t3)); + for (i = 0; i < FMSS_VFL_HDR_LEN && off < PAGE_SIZE - 4; i++) { + off += scnprintf(vfl_log + off, PAGE_SIZE - off, "%02x%s", + f->last_page[i], ((i + 1) % 16) ? " " : "\n"); + } + vfl_log_len = off; +} + +static ssize_t vfl_log_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!vfl_log_len) + return sysfs_emit(buf, "(no vfl_dump yet)\n"); + memcpy(buf, vfl_log, min(vfl_log_len, PAGE_SIZE)); + return min(vfl_log_len, PAGE_SIZE); +} +static DEVICE_ATTR_RO(vfl_log); + +static ssize_t sector_hex_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!sector_log_len) + return sysfs_emit(buf, "(no lpn_read yet)\n"); + memcpy(buf, sector_log, sector_log_len); + return sector_log_len; +} +static DEVICE_ATTR_RO(sector_hex); + +static ssize_t lpn_index_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int i, n = 0; + + if (!lpn_index_count) + return sysfs_emit(buf, "(empty — run lpn_read or lpn_build)\n"); + for (i = 0; i < lpn_index_count; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, + "lpn=%u ce=%u cau=%u blk=%u pg=%u\n", + lpn_index[i].lpn, lpn_index[i].ce, + lpn_index[i].cau, lpn_index[i].block, + lpn_index[i].page); + return n; +} +static DEVICE_ATTR_RO(lpn_index); + +/* + * Read one VFL context page (SLC page 0) and capture 512-byte header in vfl_log. + * Usage: echo "CE CAU BLOCK" > vfl_dump (CE/CAU default 0) + */ +static ssize_t vfl_dump_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce = 0, cau = 0, block; + unsigned int saved; + u32 addr; + int ret, nf; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u %u", &ce, &cau, &block); + if (nf == 1) { + block = ce; + ce = 0; + cau = 0; + } else if (nf != 3) { + return -EINVAL; + } + if (ce >= FMSS_NUM_CE || cau >= FMSS_NUM_CAU || + block >= FMSS_BLOCKS_PER_CAU) + return -EINVAL; + + mutex_lock(&f->lock); + saved = page_chunks; + page_chunks = 1; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, block, 0, 1); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved; + if (!ret) + fmss_vfl_format_log(f, ce, cau, block, addr); + if (!ret) + fmss_vfl_ingest(f, cau, block, f->last_page); + mutex_unlock(&f->lock); + nand_dev_info(dev, "vfl_dump ce=%u cau=%u blk=%u ret=%d\n", ce, cau, block, ret); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(vfl_dump); + +static int fmss_find_lpn(struct nand_s5l8740 *f, unsigned int target_lpn, + unsigned int *oce, unsigned int *ocau, + unsigned int *oblock, unsigned int *opage); + +static int fmss_lpn_resolve(struct nand_s5l8740 *f, unsigned int target_lpn, + unsigned int *oce, unsigned int *ocau, + unsigned int *oblock, unsigned int *opage) +{ + unsigned int i; + + if (!fmss_l2v_lookup(target_lpn, oce, ocau, oblock, opage)) + return 0; + + for (i = 0; i < lpn_index_count; i++) { + if (lpn_index[i].lpn != target_lpn) + continue; + *oce = lpn_index[i].ce; + *ocau = lpn_index[i].cau; + *oblock = lpn_index[i].block; + *opage = lpn_index[i].page; + return 0; + } + return fmss_find_lpn(f, target_lpn, oce, ocau, oblock, opage); +} + +static int fmss_read_lpn_page(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, u8 *dst, unsigned int dst_len) +{ + unsigned int pblock, saved; + u32 addr; + int ret; + + /* Callers pass physical scan blocks (find_lpn / BTOC). Avoid VFL-remap. */ + pblock = fmss_map_to_phys(cau, block, L2V_PHYS); + saved = page_chunks; + page_chunks = 16; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, pblock, page, 0); + if (f->dma_ok && use_dma) { + ret = fmss_dma_page_read(f, ce, addr); + if (ret) + ret = fmss_page_read(f, ce, addr); + } else { + ret = fmss_page_read(f, ce, addr); + } + f->pages_since_reset++; + page_chunks = saved; + if (!ret && dst && dst_len) { + unsigned int n = min(dst_len, f->last_page_len); + + memcpy(dst, f->last_page, n); + } + return ret; +} + +static int fmss_find_lpn(struct nand_s5l8740 *f, unsigned int target_lpn, + unsigned int *oce, unsigned int *ocau, + unsigned int *oblock, unsigned int *opage) +{ + unsigned int ce, cau, b, p, saved, limit, best = ~0u; + u32 addr; + int ret, found = 0; + + limit = lpn_scan_blocks; + if (!limit || limit > FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + limit = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = 0; b < limit; b++) { + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, b, FMSS_BTOC_PAGE, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret || fmss_page_blankish(f->last_page, 64)) + continue; + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + unsigned int score, pg = p; + unsigned int fat_off = 0; + bool fat_ok = false; + + if (fmss_btoc_entry(f->last_page, p) != target_lpn) + continue; + if (!fmss_btoc_plausible(f->last_page, target_lpn, &pg)) + continue; + if (fmss_read_lpn_page(f, ce, cau, b, pg, NULL, 0)) + continue; + if (target_lpn == 0) { + fat_ok = fmss_page_has_fat_boot( + f->last_page, f->last_page_len, + &fat_off); + if (!fat_ok) + continue; + } else if (fmss_page_blankish(f->last_page, 64)) { + continue; + } + /* Prefer ce0/cau0, lower blocks, LPN0 with FAT @ 0. */ + score = ce * 1000000u + cau * 100000u + b * 100u + pg; + if (target_lpn == 0 && fat_off == 0) + score /= 10; + if (score < best) { + best = score; + *oce = ce; + *ocau = cau; + *oblock = b; + *opage = pg; + found = 1; + } + } + } + } + } + page_chunks = saved; + return found ? 0 : -ENOENT; +} + +static void fmss_boot_apply_bpb(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page, const u8 *bpb) +{ + u16 bps = get_unaligned_le16(bpb + 11); + u16 rsv = get_unaligned_le16(bpb + 14); + u32 fatz = get_unaligned_le32(bpb + 36); + + boot_carve_valid = true; + boot_carve_ce = ce; + boot_carve_cau = cau; + boot_carve_block = block; + boot_carve_page = page; + boot_carve_off = 0; + boot_data_start = fmss_bpb_data_start(bpb); + if (bps == FMSS_SECTOR_LEN && rsv && rsv < 4096) + boot_reserved_sects = rsv; + else + boot_reserved_sects = 32; + if (fatz && fatz < 0x100000u) + boot_fat_sects = fatz; + /* L2V[0] / LBA0 must point at this real boot page (physical). */ + fmss_l2v_set_ex(0, ce, cau, block, page, true, L2V_SRC_CARVE, 0); + fmss_lba_set(0, ce, cau, block, page, 0, true, L2V_SRC_CARVE, 0); + nand_dev_info(f->dev, + "boot_sb ce=%u cau=%u blk=%u pg=%u DataStart=%u rsv=%u fatz=%u\n", + ce, cau, block, page, boot_data_start, + boot_reserved_sects, boot_fat_sects); +} + +/* + * Accept ONLY an aligned live boot sector: BPB at page offset 0 with + * 55AA@510. Mid-page *UOKJIHC (e.g. off=7816) is a file copy — reject. + */ +static int fmss_boot_carve_try(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page) +{ + int ret; + + ret = fmss_read_lpn_page(f, ce, cau, block, page, NULL, 0); + if (ret) + return 0; + if (f->last_page_len < FMSS_SECTOR_LEN) + return 0; + if (!fmss_apple_fat_boot(f->last_page)) + return 0; + fmss_boot_apply_bpb(f, ce, cau, block, page, f->last_page); + return 1; +} + +/* + * Try aligned BPB on page p; on success ingest the saved BTOC table. + */ +static int fmss_boot_try_btoc_page(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + const u8 *btoc_page, unsigned int page) +{ + u8 *btoc_save; + int ok; + + btoc_save = kmemdup(btoc_page, FMSS_PAGE_LEN, GFP_KERNEL); + if (!btoc_save) + return 0; + ok = fmss_boot_carve_try(f, ce, cau, block, page); + if (ok) { + fmss_l2v_ingest_btoc(f, ce, cau, block, btoc_save, + l2v_max_lpn ? l2v_max_lpn : + FMSS_L2V_DEFAULT_MAX_LPN); + } + kfree(btoc_save); + return ok; +} + +/* + * Find boot superblock: ingestible BTOC with LPN0 in any slot (or boot-ish + * BTOC[0]==0 && BTOC[1] in {1,vbas_per_page}), then aligned BPB on that page. + * Falls back to page-0 aligned BPB scan (never mid-page OEM). + */ +static int fmss_boot_carve_discover(struct nand_s5l8740 *f, unsigned int start, + unsigned int nblocks) +{ + unsigned int ce, cau, b, p, saved; + u32 addr, l0, l1; + int ret; + bool use_le; + + /* Optional explicit cache — only if params describe an aligned page. */ + if (boot_carve_block_param && boot_carve_off_param == 0) { + if (fmss_boot_carve_try(f, boot_carve_ce_param, + boot_carve_cau_param, + boot_carve_block_param, + boot_carve_page_param ? + boot_carve_page_param : 0)) + return 0; + nand_dev_info(f->dev, + "cached boot_sb blk%u miss — scanning BTOC LPN0\n", + boot_carve_block_param); + } + + if (!nblocks) + nblocks = l2v_auto_blocks ? l2v_auto_blocks : 512; + if (nblocks > FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + nblocks = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + nblocks; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + if (reset_every && + f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, b, FMSS_BTOC_PAGE, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret || fmss_page_blankish(f->last_page, 64)) + continue; + use_le = fmss_btoc_prefer_le(f->last_page); + l0 = use_le ? fmss_btoc_entry_le(f->last_page, 0) + : fmss_btoc_entry_be(f->last_page, 0); + l1 = use_le ? fmss_btoc_entry_le(f->last_page, 1) + : fmss_btoc_entry_be(f->last_page, 1); + /* + * LPN0 candidate: BTOC[0]==0 (even if [1] is junk — + * live ce1/cau1/blk63). Avoid scan every random + * zero dword in non-ingestible pages (wedges NAND). + */ + if (l0 == 0) { + page_chunks = 16; + if (fmss_boot_try_btoc_page(f, ce, cau, b, + f->last_page, + 0)) { + page_chunks = saved; + return 0; + } + page_chunks = 1; + } + if (!fmss_btoc_ingestible(f->last_page)) + continue; + for (p = 1; p < FMSS_BTOC_PAGE; p++) { + u32 lpn = use_le ? + fmss_btoc_entry_le(f->last_page, p) : + fmss_btoc_entry_be(f->last_page, p); + + if (lpn != 0) + continue; + page_chunks = 16; + if (fmss_boot_try_btoc_page(f, ce, cau, b, + f->last_page, + p)) { + page_chunks = saved; + return 0; + } + page_chunks = 1; + } + } + } + } + + /* + * Aligned BPB: page0 of each block, then all pages of open SBs + * (page0 programmed && page127 not closed BTOC/BTE). Never mid-page OEM. + */ + page_chunks = 16; + { + unsigned int boot_scan = nblocks ? nblocks : 256; + + if (boot_scan > 512) + boot_scan = 512; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + boot_scan; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + if (fmss_boot_carve_try(f, ce, cau, b, 0)) { + page_chunks = saved; + return 0; + } + } + } + } + + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + boot_scan; b++) { + unsigned int pg; + u32 addr; + int r; + bool closed; + + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + if (reset_every && + f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, b, 0, 0); + r = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (r || fmss_page_blankish(f->last_page, 64)) + continue; + addr = fmss_ppn_addr(cau, b, FMSS_BTOC_PAGE, 0); + r = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + closed = !r && !fmss_page_blankish(f->last_page, 64) && + (fmss_btoc_ingestible(f->last_page) || + fmss_page_looks_bte(f->last_page)); + if (closed) + continue; + + page_chunks = 16; + for (pg = 0; pg < FMSS_BTOC_PAGE; pg++) { + unsigned int sec; + + if (fmss_boot_carve_try(f, ce, cau, b, pg)) { + page_chunks = saved; + return 0; + } + if (fmss_read_lpn_page(f, ce, cau, b, pg, + NULL, 0)) + continue; + for (sec = 1; sec < FMSS_VBAS_PER_PAGE; sec++) { + unsigned int off = sec * FMSS_SECTOR_LEN; + const u8 *s = f->last_page + off; + + if (off + FMSS_SECTOR_LEN > f->last_page_len) + break; + if (!fmss_apple_fat_boot(s)) + continue; + fmss_boot_apply_bpb(f, ce, cau, b, pg, s); + fmss_lba_set(0, ce, cau, b, pg, sec, + true, L2V_SRC_CARVE, 0); + page_chunks = saved; + nand_dev_info(f->dev, + "boot_sb open-SB sec=%u ce=%u cau=%u blk=%u pg=%u\n", + sec, ce, cau, b, pg); + return 0; + } + } + page_chunks = 1; + } + } + } + } + page_chunks = saved; + return -ENOENT; +} + +static bool fmss_page_has_n31os_dirent(const u8 *page, unsigned int len) +{ + unsigned int off, flags; + static const char *const needles[] = { + "N31OS", "n31os", "README", "IPOD_CON", "iPod_C", NULL + }; + unsigned int n; + + /* FAT 8.3 short names (ASCII, space-padded) — LE cluster fields elsewhere. */ + for (off = 0; off + 32 <= len; off += 32) { + if (!memcmp(page + off, "N31OS ", 8) || + !memcmp(page + off, "README ", 8) || + !memcmp(page + off, "IPOD_CON", 8)) + return true; + } + for (n = 0; needles[n]; n++) { + flags = fmss_find_enc(page, len, needles[n], + strlen(needles[n]), &off); + if (flags) + return true; + } + return false; +} + +static int fmss_root_dir_discover(struct nand_s5l8740 *f, unsigned int start, + unsigned int nblocks) +{ + unsigned int ce, cau, b, p, lpn; + unsigned int saved, pages = 0; + const unsigned int page_cap = 4096; + + lpn = boot_data_start / NAND_FTL_SECTORS_PER_LPN; + if (!fmss_l2v_lookup(lpn, &ce, &cau, &b, &p)) { + root_dir_valid = true; + root_dir_ce = ce; + root_dir_cau = cau; + root_dir_block = b; + root_dir_page = p; + root_dir_lpn = lpn; + return 0; + } + + if (!nblocks) + nblocks = 24; + if (nblocks > 48) + nblocks = 48; + + saved = page_chunks; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + nblocks; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + if (pages >= page_cap) + goto out; + if (fmss_read_lpn_page(f, ce, cau, b, p, + NULL, 0)) + continue; + pages++; + if (!fmss_page_has_n31os_dirent( + f->last_page, + f->last_page_len)) + continue; + root_dir_valid = true; + root_dir_ce = ce; + root_dir_cau = cau; + root_dir_block = b; + root_dir_page = p; + root_dir_lpn = lpn; + fmss_l2v_set_ex(lpn, ce, cau, b, p, true, + L2V_SRC_CARVE, 0); + page_chunks = saved; + nand_dev_info(f->dev, + "root_dir N31OS ce=%u cau=%u blk=%u pg=%u lpn=%u\n", + ce, cau, b, p, lpn); + return 0; + } + } + } + } +out: + page_chunks = saved; + return -ENOENT; +} + +static void fmss_l2v_try_block_map_page(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int max_lpn) +{ + bool be = false; + unsigned int nents = 0, i, saved, take; + u32 addr; + u8 *mapbuf; + int ret; + + if (fmss_read_lpn_page(f, ce, cau, block, 0, NULL, 0)) + return; + if (!fmss_page_looks_block_map(f->last_page, f->last_page_len, + &be, &nents)) + return; + + take = min(nents, 64u); + mapbuf = kmalloc(take * 2, GFP_KERNEL); + if (!mapbuf) + return; + memcpy(mapbuf, f->last_page, take * 2); + l2v_bmap_hits++; + + saved = page_chunks; + page_chunks = 1; + for (i = 0; i < take; i++) { + u16 vbn = be ? get_unaligned_be16(mapbuf + i * 2) + : get_unaligned_le16(mapbuf + i * 2); + + if (!vbn || vbn >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + continue; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + { + unsigned int pblk = fmss_vfl_resolve(cau, vbn); + + addr = fmss_ppn_addr(cau, pblk, FMSS_BTOC_PAGE, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret || fmss_page_blankish(f->last_page, 64)) + continue; + /* Store physical block + PHYS (already VFL-resolved). */ + fmss_l2v_ingest_btoc(f, ce, cau, pblk, f->last_page, + max_lpn); + } + } + page_chunks = saved; + kfree(mapbuf); +} + +/* + * Build dense L2V from BTOC page 127 (+ optional classic block-map pages). + * Bounded: [start, start+nblocks) per CE/CAU. Also carve boot + root dir. + */ +static int fmss_l2v_build(struct nand_s5l8740 *f, unsigned int max_lpn, + unsigned int start, unsigned int nblocks) +{ + unsigned int ce, cau, b, saved; + u32 addr; + int ret; + + if (!max_lpn) + max_lpn = FMSS_L2V_DEFAULT_MAX_LPN; + if (!nblocks) + nblocks = l2v_auto_blocks; /* 0 = carve-only, no BTOC walk */ + if (nblocks > FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + nblocks = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + if (start >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + start = 0; + + ret = fmss_l2v_ensure(max_lpn); + if (ret) + return ret; + + fmss_legacy_meta_ingest = true; + + /* Rebuild map contents for this pass (keep allocation). */ + memset(l2v_map, 0, l2v_map_size * sizeof(*l2v_map)); + if (l2v_weave) + memset(l2v_weave, 0, l2v_map_size * sizeof(*l2v_weave)); + if (l2v_src) + memset(l2v_src, 0, l2v_map_size * sizeof(*l2v_src)); + l2v_mapped = 0; + l2v_btoc_hits = 0; + l2v_bmap_hits = 0; + l2v_meta_hits = 0; + lpn_index_count = 0; + boot_carve_valid = false; + root_dir_valid = false; + f->ecc_soft_fails = 0; + if (lba_map) { + memset(lba_map, 0, fmss_lba_map_cap() * sizeof(*lba_map)); + if (lba_weave) + memset(lba_weave, 0, fmss_lba_map_cap() * sizeof(*lba_weave)); + if (lba_src) + memset(lba_src, 0, fmss_lba_map_cap() * sizeof(*lba_src)); + lba_mapped = 0; + } else { + fmss_lba_map_ensure(); + } + vfl_remap_applied = 0; + vfl_remap_skipped_phys = 0; + + saved = page_chunks; + page_chunks = 1; + for (ce = 0; nblocks && ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + unsigned int bmap_probes = 0; + + for (b = start; b < start + nblocks; b++) { + bool btoc_ok; + + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + if (reset_every && + f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, b, FMSS_BTOC_PAGE, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + btoc_ok = !ret && + !fmss_page_blankish(f->last_page, 64); + if (btoc_ok) { + if (fmss_btoc_ingestible(f->last_page)) + fmss_l2v_ingest_btoc(f, ce, cau, b, + f->last_page, + max_lpn); + else if (fmss_page_looks_bte(f->last_page)) { + /* + * Pass 2: BTE needs the full + * 16 KiB page; walk used 1-chunk + * probe — re-read full page. + */ + unsigned int saved2 = page_chunks; + + page_chunks = 16; + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved2; + if (!ret) + fmss_l2v_ingest_bte( + f, ce, cau, b, + f->last_page, + max_lpn); + } + } + + /* + * Classic block-map heuristic only when BTOC is + * blank — capped probes to avoid wedging. + */ + if (!btoc_ok && bmap_probes < 32) { + page_chunks = 16; + fmss_l2v_try_block_map_page(f, ce, cau, + b, max_lpn); + page_chunks = 1; + bmap_probes++; + } + } + } + } + page_chunks = saved; + + fmss_boot_carve_discover(f, start, nblocks); + if (nblocks) + fmss_root_dir_discover(f, start ? start : 32, + min_t(unsigned int, nblocks, 48)); + + fmss_legacy_meta_ingest = false; + + nand_dev_info(f->dev, + "l2v_build max_lpn=%u range=%u+%u mapped=%u btoc=%u bmap=%u boot=%d root=%d ecc_soft=%u\n", + max_lpn, start, nblocks, l2v_mapped, l2v_btoc_hits, + l2v_bmap_hits, boot_carve_valid, root_dir_valid, + f->ecc_soft_fails); + return 0; +} + +static int fmss_build_lpn_index(struct nand_s5l8740 *f, unsigned int max_lpn) +{ + unsigned int nblocks = l2v_scan_blocks; + + if (!nblocks) + nblocks = lpn_scan_blocks; + if (!nblocks) + nblocks = FMSS_L2V_DEFAULT_BLOCKS; + return fmss_l2v_build(f, max_lpn, 0, nblocks); +} + +static ssize_t lpn_build_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int max_lpn = 32; + int ret; + + if (!f) + return -ENODEV; + if (buf[0] && buf[0] != '\n' && kstrtouint(buf, 0, &max_lpn)) + return -EINVAL; + mutex_lock(&f->lock); + ret = fmss_build_lpn_index(f, max_lpn); + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(lpn_build); + +/* + * Explicit dense L2V build (bounded). Usage: + * echo 1 > l2v_build + * echo "NBLOCKS" > l2v_build + * echo "START NBLOCKS [MAX_LPN]" > l2v_build + */ +static ssize_t l2v_build_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int start = 0, nblocks = 0, max_lpn = 0; + int nf, ret; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u %u", &start, &nblocks, &max_lpn); + if (nf == 1) { + if (start == 1) { + start = 0; + nblocks = l2v_scan_blocks; + } else { + nblocks = start; + start = 0; + } + } else if (nf == 2) { + ; /* start + nblocks */ + } else if (nf >= 3) { + ; /* start + nblocks + max_lpn */ + } else if (buf[0] && buf[0] != '\n') { + return -EINVAL; + } + if (!nblocks) + nblocks = l2v_scan_blocks ? l2v_scan_blocks + : FMSS_L2V_DEFAULT_BLOCKS; + if (!max_lpn) + max_lpn = FMSS_L2V_DEFAULT_MAX_LPN; + + mutex_lock(&f->lock); + ret = fmss_l2v_build(f, max_lpn, start, nblocks); + mutex_unlock(&f->lock); + if (ret) + return ret; + dev_info(dev, + "l2v_build done mapped=%u btoc=%u bmap=%u boot=%u root=%u DataStart=%u\n", + l2v_mapped, l2v_btoc_hits, l2v_bmap_hits, + boot_carve_valid, root_dir_valid, boot_data_start); + return count; +} +static DEVICE_ATTR_WO(l2v_build); + +static ssize_t l2v_status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, + "mapped=%u max_lpn=%u size=%u btoc_hits=%u bmap_hits=%u meta_hits=%u lba_mapped=%u\n" + "boot_carve=%u ce=%u cau=%u blk=%u pg=%u off=%u DataStart=%u\n" + "root_dir=%u ce=%u cau=%u blk=%u pg=%u lpn=%u\n" + "lpn_index=%u vfl_mode=%s remap_applied=%u skipped_phys=%u\n" + "whimory ret=%d dis=%u vfl=%u ftlctrl=%u bmap_pages=%u map_ents=%u filled=%u\n", + l2v_mapped, l2v_max_lpn, l2v_map_size, l2v_btoc_hits, + l2v_bmap_hits, l2v_meta_hits, lba_mapped, + boot_carve_valid, boot_carve_ce, boot_carve_cau, + boot_carve_block, boot_carve_page, boot_carve_off, + boot_data_start, + root_dir_valid, root_dir_ce, root_dir_cau, root_dir_block, + root_dir_page, root_dir_lpn, + lpn_index_count, vfl_remap_mode, vfl_remap_applied, + vfl_remap_skipped_phys, + wmr_mount_ret, wmr_dis_hits, wmr_vfl_hits, wmr_ftlctrl_hits, + wmr_bmap_pages, wmr_block_map_n, wmr_l2v_filled); +} +static DEVICE_ATTR_RO(l2v_status); + +static ssize_t whimory_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, + "ret=%d\n" + "deviceinfosign_hits=%u first=ce%u/cau%u/blk%u/pg%u\n" + "vfl_hits=%u ftlctrl_hits=%u ftlctrl=%u,%u,%u\n" + "bmap_pages=%u map_ents=%u l2v_filled=%u\n", + wmr_mount_ret, + wmr_dis_hits, wmr_dis_ce, wmr_dis_cau, wmr_dis_block, + wmr_dis_page, + wmr_vfl_hits, wmr_ftlctrl_hits, + wmr_ftlctrl_n > 0 ? wmr_ftlctrl[0] : 0, + wmr_ftlctrl_n > 1 ? wmr_ftlctrl[1] : 0, + wmr_ftlctrl_n > 2 ? wmr_ftlctrl[2] : 0, + wmr_bmap_pages, wmr_block_map_n, wmr_l2v_filled); +} +static DEVICE_ATTR_RO(whimory_status); + +/* + * Classic Whimory mount (bounded). Usage: + * echo 1 > whimory_mount + * echo "NBLOCKS" > whimory_mount + * echo "NBLOCKS MAX_LPN" > whimory_mount + */ +static ssize_t whimory_mount_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int nblocks = 0, max_lpn = 0; + int nf, ret; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u", &nblocks, &max_lpn); + if (nf == 1 && nblocks == 1) + nblocks = 0; /* echo 1 → defaults */ + else if (nf < 1 && buf[0] && buf[0] != '\n') + return -EINVAL; + if (!nblocks) + nblocks = min(grep_max_blocks ? grep_max_blocks : 32u, + WMR_MOUNT_MAX_BLOCKS); + if (!max_lpn) + max_lpn = FMSS_L2V_DEFAULT_MAX_LPN; + + mutex_lock(&f->lock); + ret = fmss_whimory_mount(f, nblocks, max_lpn); + mutex_unlock(&f->lock); + dev_info(dev, + "whimory_mount done ret=%d map_ents=%u filled=%u (l2v mapped=%u)\n", + ret, wmr_block_map_n, wmr_l2v_filled, l2v_mapped); + /* Mount soft-fails with -ENOENT when nothing found; still accept write. */ + if (ret && ret != -ENOENT) + return ret; + return count; +} +static DEVICE_ATTR_WO(whimory_mount); + +/* + * Resolve LPN → NAND page and copy one 4096-byte logical sector (must hold f->lock). + * Uses dense L2V / root-dir cache only — no on-demand BTOC scan (avoids wedging + * the device on sparse unmapped reads). Returns -ENOENT if unmapped. + */ +static int nand_ftl_read_lpn_locked(struct nand_s5l8740 *f, unsigned int target_lpn, + unsigned int sector, u8 *buf) +{ + unsigned int ce, cau, block, page, pblock, off, saved; + u32 addr, packed = L2V_PHYS; + int ret; + + if (sector > NAND_FTL_SECTORS_PER_LPN - 1) + return -EINVAL; + + if (root_dir_valid && target_lpn == root_dir_lpn) { + ce = root_dir_ce; + cau = root_dir_cau; + block = root_dir_block; + page = root_dir_page; + packed = L2V_PHYS; + } else { + ret = fmss_l2v_lookup_ex(target_lpn, &ce, &cau, &block, &page, + &packed); + if (ret) + return ret; + } + + pblock = fmss_map_to_phys(cau, block, packed); + saved = page_chunks; + page_chunks = 16; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, pblock, page, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved; + if (ret) + return ret; + + off = sector * FMSS_SECTOR_LEN; + if (off + FMSS_SECTOR_LEN > f->last_page_len) + return -ERANGE; + + memcpy(buf, f->last_page + off, FMSS_SECTOR_LEN); + return 0; +} + +/* + * Read logical page N (BTOC LPN) and expose 4 KiB sector via sector_hex. + * Usage: echo "LPN [sector_in_page]" > lpn_read (sector 0..3, default 0) + */ +static ssize_t lpn_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int target_lpn, sector = 0; + unsigned int i, ce, cau, block, page, poff, saved; + u8 *secbuf; + u32 addr; + int ret, nf; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u", &target_lpn, §or); + if (nf < 1) + return -EINVAL; + if (sector > 3) + return -EINVAL; + + secbuf = kmalloc(FMSS_SECTOR_LEN, GFP_KERNEL); + if (!secbuf) + return -ENOMEM; + + mutex_lock(&f->lock); + { + u32 packed = L2V_PHYS; + + /* Prefer dense L2V packed flags; else on-demand resolve (PHYS). */ + ret = fmss_l2v_lookup_ex(target_lpn, &ce, &cau, &block, &page, + &packed); + if (ret) + ret = fmss_lpn_resolve(f, target_lpn, &ce, &cau, + &block, &page); + if (!ret) { + saved = page_chunks; + page_chunks = 16; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, + fmss_map_to_phys(cau, block, packed), + page, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved; + if (!ret) { + poff = sector * FMSS_SECTOR_LEN; + if (poff + FMSS_SECTOR_LEN <= f->last_page_len) + memcpy(secbuf, f->last_page + poff, + FMSS_SECTOR_LEN); + else + ret = -ERANGE; + } + } + } + mutex_unlock(&f->lock); + if (ret) { + dev_warn(dev, "lpn_read %u: resolve/read failed (%d)\n", + target_lpn, ret); + kfree(secbuf); + return ret; + } + + sector_log_len = 0; + for (i = 0; i < 128; i++) { + sector_log_len += scnprintf(sector_log + sector_log_len, + sizeof(sector_log) - sector_log_len, + "%02x%s", + secbuf[i], + ((i + 1) % 16) ? " " : "\n"); + } + + nand_dev_info(dev, + "lpn=%u sector=%u head=%02x%02x%02x%02x\n", + target_lpn, sector, secbuf[0], secbuf[1], secbuf[2], secbuf[3]); + kfree(secbuf); + return count; +} +static DEVICE_ATTR_WO(lpn_read); + +static ssize_t resolve_log_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!resolve_log_len) + return sysfs_emit(buf, "(no resolve yet)\n"); + return sysfs_emit(buf, "%s", resolve_log); +} +static DEVICE_ATTR_RO(resolve_log); + +static ssize_t read_sector_dense_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int lba; + u8 *secbuf; + int ret; + + if (!f) + return -ENODEV; + if (kstrtouint(buf, 0, &lba)) + return -EINVAL; + secbuf = kmalloc(FMSS_SECTOR_LEN, GFP_KERNEL); + if (!secbuf) + return -ENOMEM; + ret = nand_ftl_read_sector(lba, secbuf); + dev_info(dev, "read_sector_dense LBA=%u ret=%d %s", + lba, ret, resolve_log); + kfree(secbuf); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(read_sector_dense); + +static ssize_t read_sector_slow_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int lba, lpn, sec, ce, cau, block, page, pblock, saved; + u8 *secbuf; + u32 addr, packed = L2V_PHYS; + int ret; + + if (!f) + return -ENODEV; + if (kstrtouint(buf, 0, &lba)) + return -EINVAL; + secbuf = kmalloc(FMSS_SECTOR_LEN, GFP_KERNEL); + if (!secbuf) + return -ENOMEM; + + /* Try dense first. */ + ret = nand_ftl_read_sector(lba, secbuf); + if (!ret) { + dev_info(dev, "read_sector_slow LBA=%u via dense OK\n", lba); + kfree(secbuf); + return count; + } + + lpn = lba / NAND_FTL_SECTORS_PER_LPN; + sec = lba % NAND_FTL_SECTORS_PER_LPN; + mutex_lock(&f->lock); + ret = fmss_l2v_lookup_ex(lpn, &ce, &cau, &block, &page, &packed); + if (ret) { + ret = fmss_lpn_resolve(f, lpn, &ce, &cau, &block, &page); + packed = L2V_PHYS; + } + if (!ret) { + pblock = fmss_map_to_phys(cau, block, packed); + saved = page_chunks; + page_chunks = 16; + addr = fmss_ppn_addr(cau, pblock, page, 0); + ret = fmss_page_read(f, ce, addr); + page_chunks = saved; + if (!ret) { + memcpy(secbuf, f->last_page + sec * FMSS_SECTOR_LEN, + FMSS_SECTOR_LEN); + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "slow LBA=%u via on-demand lpn_resolve phys=%d ce=%u cau=%u blk=%u→%u pg=%u sec=%u head=%02x%02x%02x%02x\n", + lba, !!(packed & L2V_PHYS), ce, cau, block, + pblock, page, sec, + secbuf[0], secbuf[1], secbuf[2], secbuf[3]); + } + } + mutex_unlock(&f->lock); + dev_info(dev, "read_sector_slow LBA=%u ret=%d %s", lba, ret, resolve_log); + kfree(secbuf); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(read_sector_slow); + +static ssize_t read_sector_phys_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, block, page, sec = 0, saved; + u8 *secbuf; + u32 addr; + int nf, ret; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u %u %u %u", &ce, &cau, &block, &page, &sec); + if (nf < 4) + return -EINVAL; + if (sec > 3) + return -EINVAL; + secbuf = kmalloc(FMSS_SECTOR_LEN, GFP_KERNEL); + if (!secbuf) + return -ENOMEM; + mutex_lock(&f->lock); + saved = page_chunks; + page_chunks = 16; + addr = fmss_ppn_addr(cau, block, page, 0); + ret = fmss_page_read(f, ce, addr); + page_chunks = saved; + if (!ret) { + memcpy(secbuf, f->last_page + sec * FMSS_SECTOR_LEN, + FMSS_SECTOR_LEN); + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "phys ce=%u cau=%u blk=%u pg=%u sec=%u head=%02x%02x%02x%02x\n", + ce, cau, block, page, sec, + secbuf[0], secbuf[1], secbuf[2], secbuf[3]); + memcpy(f->last_page, secbuf, FMSS_SECTOR_LEN); + sector_log_len = 0; + } + mutex_unlock(&f->lock); + dev_info(dev, "read_sector_phys ret=%d %s", ret, resolve_log); + kfree(secbuf); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(read_sector_phys); + +static char grep_log[4096]; +static unsigned int grep_log_len; + +static ssize_t grep_log_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!grep_log_len) + return sysfs_emit(buf, "(no ftl_grep yet)\n"); + memcpy(buf, grep_log, min(grep_log_len, PAGE_SIZE)); + return min(grep_log_len, PAGE_SIZE); +} +static DEVICE_ATTR_RO(grep_log); + +/* + * Walk FTL superblocks and search page data for a short ASCII needle. + * Usage: echo "START N_BLOCKS NEEDLE" > ftl_grep + * echo "32 24 N31OS" > ftl_grep (defaults: start=32, n=24, N31OS) + * Caps N_BLOCKS at FMSS_GREP_MAX_BLOCKS per call to avoid RetailOS watchdog. + */ +static ssize_t ftl_grep_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, b, p, saved, start = 32, nblocks = 16; + unsigned int pages_done = 0, hits = 0; + char needle[48] = "N31OS"; + size_t nlen = 5; + int ret, nf; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u %47s", &start, &nblocks, needle); + if (nf >= 3) + nlen = strnlen(needle, sizeof(needle) - 1); + else if (nf == 2) + ; /* start + nblocks, default needle */ + else if (nf == 1 && start == 1) + start = 32; + if (nblocks == 0) + nblocks = 16; + if (nblocks > grep_max_blocks) + nblocks = grep_max_blocks; + if (start >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + start = 32; + + grep_log_len = 0; + mutex_lock(&f->lock); + saved = page_chunks; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + nblocks; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + /* + * Avoid require BTOC page 127 — Apple FAT clusters + * live in data pages even when BTOC looks blank. + * (Root cause: old code skipped whole superblocks.) + */ + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + unsigned int off = 0, show, flags; + u32 lpn = ~0u; + + ret = fmss_read_lpn_page(f, ce, cau, b, p, + NULL, 0); + pages_done++; + if (ret) + continue; + flags = fmss_find_enc(f->last_page, + f->last_page_len, + needle, nlen, + &off); + if (!flags) + continue; + hits++; + nand_dev_info(dev, + "grep hit ce=%u cau=%u blk=%u pg=%u off=%u enc=%s\n", + ce, cau, b, p, off, + fmss_match_enc_name(flags)); + if (grep_log_len < sizeof(grep_log) - 160) { + show = min(240u, + f->last_page_len - off); + grep_log_len += scnprintf( + grep_log + grep_log_len, + sizeof(grep_log) - grep_log_len, + "ce=%u cau=%u blk=%u pg=%u off=%u enc=%s\n", + ce, cau, b, p, off, + fmss_match_enc_name(flags)); + if (flags & FMSS_MATCH_ASCII) + grep_log_len += scnprintf( + grep_log + grep_log_len, + sizeof(grep_log) - grep_log_len, + "%.*s\n\n", + show, + f->last_page + off); + else + grep_log_len += scnprintf( + grep_log + grep_log_len, + sizeof(grep_log) - grep_log_len, + "(wide/bswap match, %u bytes from off)\n\n", + show); + } + (void)lpn; + } + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + if (!grep_log_len) + grep_log_len = scnprintf(grep_log, sizeof(grep_log), + "NO HIT needle=%s pages=%u (tried ascii/utf16le/utf16be/bswap16)\n", + needle, pages_done); + nand_dev_info(dev, "ftl_grep start=%u n=%u needle=%s pages=%u hits=%u\n", + start, nblocks, needle, pages_done, hits); + return count; +} +static DEVICE_ATTR_WO(ftl_grep); + +/* + * Dump ASCII from a specific FTL page offset (after ftl_grep locates a file). + * Usage: echo "CE CAU BLK PG OFF LEN" > ftl_ascii (LEN default 512, max 2048) + */ +static ssize_t readme_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, b, p, saved, start = 32, nblocks = 48; + unsigned int pages_done = 0; + const char *needle = "N31OS boot files on the RetailOS FAT volume"; + size_t nlen = 43; + int ret, nf, found = 0; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u", &start, &nblocks); + if (nf == 1 && start == 1) + start = 32; + if (nblocks == 0) + nblocks = 48; + if (nblocks > grep_max_blocks) + nblocks = grep_max_blocks; + + grep_log_len = 0; + mutex_lock(&f->lock); + saved = page_chunks; + /* CE0 first — matches disk-mode primary LUN behaviour. */ + for (ce = 0; ce < FMSS_NUM_CE && !found; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU && !found; cau++) { + for (b = start; b < start + nblocks && !found; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + for (p = 0; p < FMSS_BTOC_PAGE && !found; p++) { + unsigned int off, dump; + + if (reset_every && + f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + ret = fmss_read_lpn_page(f, ce, cau, b, p, + NULL, 0); + pages_done++; + if (ret) + continue; + if (!fmss_find(f->last_page, + f->last_page_len, + needle, nlen)) + continue; + off = 0; + while (off + nlen <= f->last_page_len) { + if (!memcmp(f->last_page + off, + needle, nlen)) + break; + off++; + } + found = 1; + dump = min(1024u, f->last_page_len - off); + grep_log_len = scnprintf( + grep_log, sizeof(grep_log), + "OK ce=%u cau=%u blk=%u pg=%u off=%u len=%u pages=%u\n%.*s\n", + ce, cau, b, p, off, dump, pages_done, + dump, f->last_page + off); + nand_dev_info(dev, "readme_read FOUND ce=%u cau=%u blk=%u pg=%u off=%u\n", + ce, cau, b, p, off); + } + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + if (!found) { + dev_warn(dev, "readme_read: not found start=%u n=%u pages=%u (stage D:\\n31os via install-n31os-disk.ps1?)\n", + start, nblocks, pages_done); + grep_log_len = scnprintf(grep_log, sizeof(grep_log), + "NOT FOUND (scanned %u pages from blk %u)\n", + pages_done, start); + return -ENOENT; + } + return count; +} +static DEVICE_ATTR_WO(readme_read); + +/* + * Locate Apple FAT32 boot sector by scanning FTL pages for EB3C90 *UOKJIHC. + * PIO only (fast). Usage: echo 1 > boot_read or echo "START N_BLOCKS" > boot_read + * Result in grep_log + sector_hex (512 B boot sector). + */ +static int fmss_read_ftl_page_pio(struct nand_s5l8740 *f, unsigned int ce, + unsigned int cau, unsigned int block, + unsigned int page) +{ + unsigned int pblock, saved; + u32 addr; + int ret; + + /* Physical block from boot/carve scan — never VFL-remap. */ + pblock = fmss_map_to_phys(cau, block, L2V_PHYS); + saved = page_chunks; + page_chunks = 16; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, pblock, page, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved; + return ret; +} + +static ssize_t boot_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, b, saved, start = 32, nblocks = 32; + unsigned int fat_off = 0, sector_off = 0; + unsigned int pages_done = 0; + int ret, nf, found = 0; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %u", &start, &nblocks); + if (nf == 1 && start == 1) + start = 32; + if (nblocks == 0) + nblocks = 32; + if (nblocks > grep_max_blocks) + nblocks = grep_max_blocks; + + grep_log_len = 0; + sector_log_len = 0; + mutex_lock(&f->lock); + saved = page_chunks; + page_chunks = 16; + + for (ce = 0; ce < FMSS_NUM_CE && !found; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU && !found; cau++) { + for (b = start; b < start + nblocks && !found; b++) { + unsigned int p; + + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + for (p = 0; p < FMSS_BTOC_PAGE && !found; p++) { + ret = fmss_read_ftl_page_pio(f, ce, cau, b, p); + pages_done++; + if (ret) + continue; + if (fmss_page_has_fat_boot(f->last_page, + f->last_page_len, + &fat_off)) { + sector_off = p; + found = 1; + } + } + } + } + } + + page_chunks = saved; + if (found) { + unsigned int i, dump = min(512u, f->last_page_len - fat_off); + + for (i = 0; i < dump; i++) { + sector_log_len += scnprintf(sector_log + sector_log_len, + sizeof(sector_log) - sector_log_len, + "%02x%s", + f->last_page[fat_off + i], + ((i + 1) % 16) ? " " : "\n"); + } + grep_log_len = scnprintf( + grep_log, sizeof(grep_log), + "OK boot ce=%u cau=%u blk=%u pg=%u off=%u pages=%u\nOEM=%.8s vol=%.11s\n", + ce, cau, b, sector_off, fat_off, pages_done, + f->last_page + fat_off + 3, + f->last_page + fat_off + 0x2b); + } else { + dev_warn(dev, "boot_read: no Apple FAT boot start=%u n=%u pages=%u\n", + start, nblocks, pages_done); + grep_log_len = scnprintf(grep_log, sizeof(grep_log), + "NOT FOUND (scanned %u pages from blk %u)\n", + pages_done, start); + } + mutex_unlock(&f->lock); + return found ? count : -ENOENT; +} +static DEVICE_ATTR_WO(boot_read); + +static ssize_t ftl_ascii_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, block, page, off = 0, len = 512; + int ret; + + if (!f) + return -ENODEV; + if (sscanf(buf, "%u %u %u %u %u %u", &ce, &cau, &block, &page, &off, &len) < 4) + return -EINVAL; + if (len > 2048) + len = 2048; + + mutex_lock(&f->lock); + ret = fmss_read_lpn_page(f, ce, cau, block, page, NULL, 0); + if (ret) { + mutex_unlock(&f->lock); + return ret; + } + if (off >= f->last_page_len) { + mutex_unlock(&f->lock); + return -ERANGE; + } + if (off + len > f->last_page_len) + len = f->last_page_len - off; + + grep_log_len = 0; + grep_log_len += scnprintf(grep_log, sizeof(grep_log), + "ce=%u cau=%u blk=%u pg=%u off=%u len=%u\n", + ce, cau, block, page, off, len); + grep_log_len += scnprintf(grep_log + grep_log_len, + sizeof(grep_log) - grep_log_len, + "%.*s\n", + len, f->last_page + off); + mutex_unlock(&f->lock); + nand_dev_info(dev, "ftl_ascii ce=%u cau=%u blk=%u pg=%u off=%u len=%u\n", + ce, cau, block, page, off, len); + return count; +} +static DEVICE_ATTR_WO(ftl_ascii); + +/* + * OSOS 4EB7E4 / Sogeti PPN-VFL: walk last blocks of each CAU, SLC page 0. + * Without DMA meta, any non-blank data page is kept and the 512-byte + * VFLCxt header plus the u32 table at +256. + */ +static ssize_t vfl_scan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, i, saved_chunks; + unsigned int start = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + int hits = 0, xrmw = 0; + u32 addr, t0, t1, t2, t3; + int ret; + + if (!f) + return -ENODEV; + /* echo 1 just triggers. A value in [32, 2087] is the first CAU block. */ + if (buf[0] && buf[0] != '\n') { + if (kstrtouint(buf, 0, &start)) + return -EINVAL; + if (start < 32 || start >= FMSS_BLOCKS_PER_CAU) + start = FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL; + } + + mutex_lock(&f->lock); + saved_chunks = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (i = FMSS_BLOCKS_PER_CAU - 1; i >= start; i--) { + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, i, 0, 1); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret) + continue; + if (fmss_page_blankish(f->last_page, 512)) + continue; + hits++; + memcpy(&t0, f->last_page + 256, 4); + memcpy(&t1, f->last_page + 260, 4); + memcpy(&t2, f->last_page + 264, 4); + memcpy(&t3, f->last_page + 268, 4); + if (f->last_page[0] == 'x' && f->last_page[1] == 'r' && + f->last_page[2] == 'm' && f->last_page[3] == 'w') + xrmw++; + if (f->last_page[0] == 'w' && f->last_page[1] == 'r' && + f->last_page[2] == 'm' && f->last_page[3] == 'x') + xrmw++; + fmss_vfl_ingest(f, cau, i, f->last_page); + nand_dev_info(dev, + "vfl ce=%u cau=%u blk=%u addr=0x%08x %02x %02x %02x %02x %02x %02x %02x %02x +256 %08x %08x %08x %08x\n", + ce, cau, i, addr, + f->last_page[0], f->last_page[1], + f->last_page[2], f->last_page[3], + f->last_page[4], f->last_page[5], + f->last_page[6], f->last_page[7], + le32_to_cpu(t0), le32_to_cpu(t1), + le32_to_cpu(t2), le32_to_cpu(t3)); + } + } + } + page_chunks = saved_chunks; + mutex_unlock(&f->lock); + nand_dev_info(dev, "vfl_scan start_blk=%u hits=%d xrmw=%d\n", start, hits, xrmw); + return count; +} +static DEVICE_ATTR_WO(vfl_scan); + +static ssize_t vfl_map_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int i, n = 0; + + if (!vfl_map_count) + return sysfs_emit(buf, "(empty — run vfl_scan or vfl_build)\n"); + for (i = 0; i < vfl_map_count && i < 32; i++) + n += scnprintf(buf + n, PAGE_SIZE - n, + "cau=%u virt=%u phys=%u\n", + vfl_map[i].cau, vfl_map[i].virt, + vfl_map[i].phys); + if (vfl_map_count > 32) + n += scnprintf(buf + n, PAGE_SIZE - n, "... +%u more\n", + vfl_map_count - 32); + return n; +} +static DEVICE_ATTR_RO(vfl_map); + +/* + * Walk VFL tail (SLC page 0) on each CAU and ingest wrmx/xrmw remap tables. + * Usage: echo 1 > vfl_build (last vfl_build_blocks blocks, default 32) + * echo "START COUNT" > vfl_build (COUNT capped at FMSS_VFL_TAIL) + */ +static ssize_t vfl_build_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, i, saved, start, nblocks, scanned = 0; + int ingested = 0, ret; + + if (!f) + return -ENODEV; + nblocks = vfl_build_blocks; + if (!nblocks || nblocks > FMSS_VFL_TAIL) + nblocks = 32; + start = FMSS_BLOCKS_PER_CAU - nblocks; + if (buf[0] && buf[0] != '\n') { + unsigned int a, b = 0; + int nf = sscanf(buf, "%u %u", &a, &b); + + if (nf < 1) + return -EINVAL; + if (nf == 1 && a == 1) + ; /* echo 1 > vfl_build — tail defaults */ + else { + start = a; + if (nf >= 2 && b) + nblocks = b; + if (nblocks > FMSS_VFL_TAIL) + nblocks = FMSS_VFL_TAIL; + if (start >= FMSS_BLOCKS_PER_CAU) + return -EINVAL; + } + } + + mutex_lock(&f->lock); + vfl_map_count = 0; + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (i = 0; i < nblocks; i++) { + unsigned int blk = start + i; + u32 addr; + + if (blk >= FMSS_BLOCKS_PER_CAU) + break; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, blk, 0, 1); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + scanned++; + if (ret || fmss_page_blankish(f->last_page, 512)) + continue; + ingested += fmss_vfl_ingest(f, cau, blk, f->last_page); + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + nand_dev_info(dev, "vfl_build start=%u n=%u scanned=%u ingested=%d map=%u\n", + start, nblocks, scanned, ingested, vfl_map_count); + return count; +} +static DEVICE_ATTR_WO(vfl_build); + +static char btoc_last[PAGE_SIZE]; +static unsigned int btoc_last_len; + +static ssize_t btoc_log_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!btoc_last_len) + return sysfs_emit(buf, "(no scan yet)\n"); + memcpy(buf, btoc_last, btoc_last_len); + return btoc_last_len; +} +static DEVICE_ATTR_RO(btoc_log); + +/* + * Sogeti/YaFTL BTOC: last page of a user superblock lists LPNs for pages 0..n-2. + * N31 page 127 is a BE u32 array (11,12,13,... observed on cau1 blk 64). + * Find the block whose BTOC[0]==0 — that page 0 is logical page 0 (FAT boot). + */ +static ssize_t btoc_scan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, i, saved, start = 0, n = FMSS_BLOCKS_PER_CAU; + int hits = 0, lpn0 = 0; + u32 addr, a, b; + int ret; + + if (!f) + return -ENODEV; + btoc_last_len = 0; + if (buf[0] && buf[0] != '\n') { + if (sscanf(buf, "%u %u", &start, &n) < 1) + return -EINVAL; + if (n == 0 || n > FMSS_BLOCKS_PER_CAU) + n = FMSS_BLOCKS_PER_CAU; + if (start >= FMSS_BLOCKS_PER_CAU) + return -EINVAL; + if (start + n > FMSS_BLOCKS_PER_CAU) + n = FMSS_BLOCKS_PER_CAU - start; + } + mutex_lock(&f->lock); + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (i = start; i < start + n; i++) { + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, i, 127, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret || fmss_page_blankish(f->last_page, 16)) + continue; + a = ((u32)f->last_page[0] << 24) | ((u32)f->last_page[1] << 16) | + ((u32)f->last_page[2] << 8) | f->last_page[3]; + b = ((u32)f->last_page[4] << 24) | ((u32)f->last_page[5] << 16) | + ((u32)f->last_page[6] << 8) | f->last_page[7]; + hits++; + if (a == 0) + lpn0++; + if (a < 0x100000 && (b == a + 1 || a == 0)) { + nand_dev_info(dev, + "btoc ce=%u cau=%u blk=%u addr=0x%08x lpn0=%u lpn1=%u %02x %02x %02x %02x\n", + ce, cau, i, addr, a, b, + f->last_page[0], f->last_page[1], + f->last_page[2], f->last_page[3]); + if (btoc_last_len < PAGE_SIZE - 80) + btoc_last_len += scnprintf( + btoc_last + btoc_last_len, + PAGE_SIZE - btoc_last_len, + "ce=%u cau=%u blk=%u lpn=%u,%u\n", + ce, cau, i, a, b); + } + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + nand_dev_info(dev, "btoc_scan start=%u n=%u hits=%d lpn0=%d\n", start, n, hits, lpn0); + return count; +} +static DEVICE_ATTR_WO(btoc_scan); + +static ssize_t fat_scan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, i, saved, start = 0, n = FMSS_BLOCKS_PER_CAU; + int hits = 0; + u32 addr; + int ret; + + if (!f) + return -ENODEV; + if (buf[0] && buf[0] != '\n' && sscanf(buf, "%u %u", &start, &n) >= 1) { + if (n == 0 || start + n > FMSS_BLOCKS_PER_CAU) + n = FMSS_BLOCKS_PER_CAU - start; + } + btoc_last_len = 0; + mutex_lock(&f->lock); + saved = page_chunks; + page_chunks = 1; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (i = start; i < start + n; i++) { + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, i, 0, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + if (ret) + continue; + if (fmss_find(f->last_page, 1024, "MSDOS", 5) || + fmss_find(f->last_page, 1024, "UOKJIHC", 7) || + fmss_find(f->last_page, 1024, "AISPOD", 6) || + fmss_find(f->last_page, 1024, "N31OS", 5) || + fmss_find(f->last_page, 1024, "FAT32", 5) || + fmss_find(f->last_page, 1024, "EXFAT", 5) || + fmss_find(f->last_page, 1024, "iPod_Control", 12) || + (f->last_page_len >= 512 && + fmss_apple_fat_boot(f->last_page))) { + hits++; + nand_dev_info(dev, + "fat ce=%u cau=%u blk=%u addr=0x%08x %02x %02x %02x %02x %02x %02x %02x %02x\n", + ce, cau, i, addr, + f->last_page[0], f->last_page[1], + f->last_page[2], f->last_page[3], + f->last_page[4], f->last_page[5], + f->last_page[6], f->last_page[7]); + if (btoc_last_len < PAGE_SIZE - 80) + btoc_last_len += scnprintf( + btoc_last + btoc_last_len, + PAGE_SIZE - btoc_last_len, + "fat ce=%u cau=%u blk=%u p0 %02x %02x %02x %02x\n", + ce, cau, i, + f->last_page[0], f->last_page[1], + f->last_page[2], f->last_page[3]); + } + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + nand_dev_info(dev, "fat_scan start=%u n=%u hits=%d\n", start, n, hits); + return count; +} +static DEVICE_ATTR_WO(fat_scan); + +/* + * Scan FTL user blocks for a FAT12/16/32 boot sector (0xEB/0xE9 + 0xAA55 @ +510). + * Usage: echo "START N_BLOCKS" > fat_boot_scan + */ +static ssize_t fat_boot_scan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, cau, b, p, saved, start = 32, nblocks = 24; + int hits = 0; + + if (!f) + return -ENODEV; + if (buf[0] && buf[0] != '\n' && sscanf(buf, "%u %u", &start, &nblocks) < 1) + return -EINVAL; + if (nblocks > grep_max_blocks) + nblocks = grep_max_blocks; + grep_log_len = 0; + mutex_lock(&f->lock); + saved = page_chunks; + for (ce = 0; ce < FMSS_NUM_CE; ce++) { + for (cau = 0; cau < FMSS_NUM_CAU; cau++) { + for (b = start; b < start + nblocks; b++) { + if (b >= FMSS_BLOCKS_PER_CAU - FMSS_VFL_TAIL) + break; + for (p = 0; p < FMSS_BTOC_PAGE; p++) { + unsigned int off; + + if (fmss_read_lpn_page(f, ce, cau, b, p, NULL, 0)) + continue; + for (off = 0; off + 512 <= f->last_page_len; + off += 512) { + const u8 *s = f->last_page + off; + + if (s[0] != 0xeb && s[0] != 0xe9) + continue; + if (s[510] != 0x55 || s[511] != 0xaa) + continue; + if (!fmss_apple_fat_boot(s) && + !fmss_find(f->last_page + off, 512, + "FAT32", 5)) + continue; + hits++; + nand_dev_info(dev, + "fat_boot ce=%u cau=%u blk=%u pg=%u off=%u oem=%.8s\n", + ce, cau, b, p, off, s + 3); + if (grep_log_len < sizeof(grep_log) - 96) + grep_log_len += scnprintf( + grep_log + grep_log_len, + sizeof(grep_log) - grep_log_len, + "ce=%u cau=%u blk=%u pg=%u off=%u oem=%.8s\n", + ce, cau, b, p, off, + s + 3); + } + } + } + } + } + page_chunks = saved; + mutex_unlock(&f->lock); + nand_dev_info(dev, "fat_boot_scan start=%u n=%u hits=%d\n", start, nblocks, hits); + return count; +} +static DEVICE_ATTR_WO(fat_boot_scan); + +static ssize_t scan_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, start, n, i; + u32 addr; + int nonempty = 0; + + if (!f) + return -ENODEV; + if (sscanf(buf, "%u %u %u", &ce, &start, &n) != 3) + return -EINVAL; + if (ce > 7 || n > 64) + return -EINVAL; + mutex_lock(&f->lock); + for (i = 0; i < n; i++) { + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = (start + i) * 128u; + if (fmss_page_read(f, ce, addr)) { + nand_dev_info(dev, "scan ce=%u blk=%u FAIL\n", ce, start + i); + f->pages_since_reset++; + continue; + } + f->pages_since_reset++; + if (f->last_page[0] != 0xff && f->last_page[0] != 0x00) { + nonempty++; + nand_dev_info(dev, + "scan ce=%u blk=%u p0 %02x %02x %02x %02x %02x %02x %02x %02x\n", + ce, start + i, + f->last_page[0], f->last_page[1], f->last_page[2], + f->last_page[3], f->last_page[4], f->last_page[5], + f->last_page[6], f->last_page[7]); + } + } + mutex_unlock(&f->lock); + nand_dev_info(dev, "scan ce=%u start=%u n=%u nonempty_head=%d\n", + ce, start, n, nonempty); + return count; +} +static DEVICE_ATTR_WO(scan); + +static ssize_t param_hex_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + int i, n = 0; + + if (!f) + return -ENODEV; + for (i = 0; i < 128; i++) { + n += scnprintf(buf + n, PAGE_SIZE - n, "%02x%s", + f->last_param[i], ((i + 1) % 16) ? " " : "\n"); + } + return n; +} +static DEVICE_ATTR_RO(param_hex); + +static ssize_t param_info_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nand_s5l8740 *f = nand_dev; + const u8 *p; + + if (!f) + return -ENODEV; + p = f->last_param; + return sysfs_emit(buf, + "ce=%d ret=%d\n" + "caus_per_channel=%u cau_bits=%u\n" + "blocks_per_cau=%u block_bits=%u\n" + "pages_per_block=%u pages_per_block_slc=%u\n" + "page_address_bits=%u bits_per_cell_addr=%u default_bits_per_cell=%u\n" + "page_size=%u\n", + f->last_param_ce, f->last_param_ret, + fmss_le32(p, 16), fmss_le32(p, 20), + fmss_le32(p, 24), fmss_le32(p, 28), + fmss_le32(p, 32), fmss_le32(p, 36), + fmss_le32(p, 40), fmss_le32(p, 44), fmss_le32(p, 48), + fmss_le32(p, 52)); +} +static DEVICE_ATTR_RO(param_info); + +static ssize_t param_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce = 0; + int ret; + + if (!f) + return -ENODEV; + if (buf[0] && kstrtouint(buf, 0, &ce)) + return -EINVAL; + mutex_lock(&f->lock); + ret = fmss_param_read(f, ce); + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(param_read); + +static ssize_t nand_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + int ret; + + if (!f) + return -ENODEV; + mutex_lock(&f->lock); + ret = fmss_nand_reset(f); + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(nand_reset); + +static ssize_t set_feature_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, feat, val; + int ret; + + if (!f) + return -ENODEV; + if (sscanf(buf, "%u %i %i", &ce, &feat, &val) < 3) + return -EINVAL; + mutex_lock(&f->lock); + ret = fmss_set_feature(f, ce, (u16)feat, val); + mutex_unlock(&f->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(set_feature); + +static ssize_t get_feature_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int ce, feat, len = 16; + int nf, ret; + + if (!f) + return -ENODEV; + nf = sscanf(buf, "%u %i %u", &ce, &feat, &len); + if (nf < 2) + return -EINVAL; + if (len < 4) + len = 4; + if (len > 16) + len = 16; + mutex_lock(&f->lock); + last_feat_ce = (int)ce; + last_feat_id = (u16)feat; + ret = fmss_get_feature(f, ce, (u16)feat, last_feat, len); + last_feat_ret = ret; + mutex_unlock(&f->lock); + return ret ? ret : count; +} + +static ssize_t get_feature_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, + "ce=%d feat=0x%04x ret=%d %02x %02x %02x %02x %02x %02x %02x %02x\n", + last_feat_ce, last_feat_id, last_feat_ret, + last_feat[0], last_feat[1], last_feat[2], last_feat[3], + last_feat[4], last_feat[5], last_feat[6], last_feat[7]); +} +static DEVICE_ATTR_RW(get_feature); + +static ssize_t page_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct nand_s5l8740 *f = nand_dev; + + if (!f) + return -ENODEV; + if (off >= FMSS_PAGE_LEN) + return 0; + if (off + count > FMSS_PAGE_LEN) + count = FMSS_PAGE_LEN - off; + memcpy(buf, f->last_page + off, count); + return count; +} +static BIN_ATTR_RO(page_data, FMSS_PAGE_LEN); + +static struct attribute *fmss_attrs[] = { + &dev_attr_regs.attr, + &dev_attr_id.attr, + &dev_attr_read_id.attr, + &dev_attr_page_read.attr, + &dev_attr_dma_read.attr, + &dev_attr_batch_read.attr, + &dev_attr_cs_canary_read.attr, + &dev_attr_cs_canary_matrix.attr, + &dev_attr_cs_phys_read.attr, + &dev_attr_cs_phys_last.attr, + &dev_attr_lba_weave_scan.attr, + &dev_attr_seq_kick.attr, + &dev_attr_scan.attr, + &dev_attr_vfl_scan.attr, + &dev_attr_vfl_dump.attr, + &dev_attr_vfl_build.attr, + &dev_attr_vfl_map.attr, + &dev_attr_vfl_log.attr, + &dev_attr_lpn_build.attr, + &dev_attr_l2v_build.attr, + &dev_attr_l2v_status.attr, + &dev_attr_whimory_mount.attr, + &dev_attr_whimory_status.attr, + &dev_attr_lpn_read.attr, + &dev_attr_read_sector_dense.attr, + &dev_attr_read_sector_slow.attr, + &dev_attr_read_sector_phys.attr, + &dev_attr_resolve_log.attr, + &dev_attr_ftl_grep.attr, + &dev_attr_readme_read.attr, + &dev_attr_boot_read.attr, + &dev_attr_ftl_ascii.attr, + &dev_attr_grep_log.attr, + &dev_attr_lpn_index.attr, + &dev_attr_sector_hex.attr, + &dev_attr_btoc_scan.attr, + &dev_attr_btoc_log.attr, + &dev_attr_fat_scan.attr, + &dev_attr_fat_boot_scan.attr, + &dev_attr_page_status.attr, + &dev_attr_page_hex.attr, + &dev_attr_spare_hex.attr, + &dev_attr_parity_hex.attr, + &dev_attr_param_read.attr, + &dev_attr_param_hex.attr, + &dev_attr_param_info.attr, + &dev_attr_nand_reset.attr, + &dev_attr_set_feature.attr, + &dev_attr_get_feature.attr, + NULL, +}; + +static struct bin_attribute *fmss_bin_attrs[] = { + &bin_attr_page_data, + NULL, +}; + +static const struct attribute_group nand_group = { + .attrs = fmss_attrs, + .bin_attrs = fmss_bin_attrs, +}; +static const struct attribute_group *nand_groups[] = { &nand_group, NULL }; + +static int fmss_probe(struct platform_device *pdev) +{ + struct nand_s5l8740 *f; + + f = devm_kzalloc(&pdev->dev, sizeof(*f), GFP_KERNEL); + if (!f) + return -ENOMEM; + f->base = devm_ioremap(&pdev->dev, FMSS_PHYS, FMSS_SIZE); + if (!f->base) + return -ENOMEM; + mutex_init(&f->lock); + f->last_ce = -1; + f->last_page_ce = -1; + f->last_page_ret = -1; + f->last_param_ce = -1; + f->last_param_ret = -1; + fmss_dma_setup(f, &pdev->dev); + nand_dev = f; + platform_set_drvdata(pdev, f); + dev_info(&pdev->dev, + "FMSS peek FMCTRL0=0x%08x NANDSTAT=0x%08x quiet=%d (read-only until read_id/page_read)\n", + readl(f->base + FMCTRL0), readl(f->base + NANDSTAT), quiet); + return 0; +} + +/* + * The controller has its own DMA. Handing the machine over with a + * transfer in flight lets it complete into memory the next kernel owns, + * and on the write side it can leave a page half-programmed -- so this + * is the one shutdown here that protects the flash rather than just RAM. + */ +static void fmss_shutdown(struct platform_device *pdev) +{ + struct nand_s5l8740 *f = platform_get_drvdata(pdev); + + if (f) + fmss_dma_teardown(f); +} + +static void fmss_remove(struct platform_device *pdev) +{ + struct nand_s5l8740 *f = platform_get_drvdata(pdev); + + nand_dev = NULL; + fmss_l2v_free(); + fmss_early_lba_free(); + boot_carve_valid = false; + root_dir_valid = false; + if (f) + fmss_dma_teardown(f); +} + +static struct platform_driver nand_driver = { + .probe = fmss_probe, + .remove = fmss_remove, + .shutdown = fmss_shutdown, + .driver = { + .name = "s5l8740-nand", + .dev_groups = nand_groups, + }, +}; + +static struct platform_device *nand_pdev; + +static int __init nand_s5l8740_init(void) +{ + int ret; + + ret = platform_driver_register(&nand_driver); + if (ret) + return ret; + nand_pdev = platform_device_register_simple("s5l8740-nand", -1, NULL, 0); + if (IS_ERR(nand_pdev)) { + platform_driver_unregister(&nand_driver); + return PTR_ERR(nand_pdev); + } + return 0; +} + +static void __exit nand_s5l8740_exit(void) +{ + platform_device_unregister(nand_pdev); + platform_driver_unregister(&nand_driver); +} + +/* --- Exported read-only FTL sector API (ftl-s5l8740.ko) --- */ + +bool nand_ftl_present(void) +{ + return nand_dev != NULL; +} +EXPORT_SYMBOL_GPL(nand_ftl_present); + +struct device *nand_ftl_device(void) +{ + return nand_dev ? nand_dev->dev : NULL; +} +EXPORT_SYMBOL_GPL(nand_ftl_device); + +unsigned int nand_ftl_lpn_count(void) +{ + return l2v_mapped ? l2v_mapped : lpn_index_count; +} +EXPORT_SYMBOL_GPL(nand_ftl_lpn_count); + +int nand_ftl_build_map(unsigned int max_lpn) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int nblocks; + int ret; + + if (!f) + return -ENODEV; + nblocks = l2v_scan_blocks ? l2v_scan_blocks : FMSS_L2V_DEFAULT_BLOCKS; + mutex_lock(&f->lock); + ret = fmss_l2v_build(f, max_lpn, 0, nblocks); + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(nand_ftl_build_map); + +int nand_ftl_read_sector(u64 logical_sector, void *buf) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int lpn, sec, ce, cau, block, page, pblock, off, saved; + u32 addr, packed; + int ret; + int (*hook)(u64 lba, void *buf); + + if (!buf) + return -ENODEV; + + /* + * Whimory registers the real LBA reader after FTL_Open. Call it + * without the FMSS mutex — the FIL page_read wrapper takes that lock. + */ + hook = READ_ONCE(nand_ftl_read_hook); + if (hook) + return hook(logical_sector, buf); + + if (!f) + return -ENODEV; + + mutex_lock(&f->lock); + + /* Prefer full LBA map (BTE / BTOC sector fills / carve / META). */ + if (logical_sector < fmss_lba_map_cap() && + !fmss_lba_lookup((unsigned int)logical_sector, &ce, &cau, + &block, &page, &sec, &packed)) { + pblock = fmss_map_to_phys(cau, block, packed); + saved = page_chunks; + page_chunks = 16; + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + addr = fmss_ppn_addr(cau, pblock, page, 0); + ret = fmss_page_read(f, ce, addr); + f->pages_since_reset++; + page_chunks = saved; + if (!ret) { + if (sec == L2V_SEC_FROM_LBA) + sec = (unsigned int)logical_sector % + FMSS_VBAS_PER_PAGE; + /* Pass 2: refuse stale map if META LBA disagrees. */ + if (f->last_spare_len >= 16 * (sec + 1)) { + const u8 *m = f->last_spare + sec * 16; + + if (m[0] == 0x01) { + u32 mlba = get_unaligned_le32(m + 8); + + if (mlba != (u32)logical_sector) + ret = -EIO; + } + } + if (!ret) { + off = sec * FMSS_SECTOR_LEN; + if (off + FMSS_SECTOR_LEN <= f->last_page_len) + memcpy(buf, f->last_page + off, + FMSS_SECTOR_LEN); + else + ret = -ERANGE; + } + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "dense LBA=%llu src=lba_map phys=%d ce=%u cau=%u blk=%u→%u pg=%u sec=%u ret=%d head=%02x%02x%02x%02x\n", + (unsigned long long)logical_sector, + !!(packed & L2V_PHYS), ce, cau, block, pblock, + page, sec, ret, + ret ? 0 : ((u8 *)buf)[0], + ret ? 0 : ((u8 *)buf)[1], + ret ? 0 : ((u8 *)buf)[2], + ret ? 0 : ((u8 *)buf)[3]); + if (!ret) { + mutex_unlock(&f->lock); + return 0; + } + /* Mapped read failed / META mismatch — fall through to LPN. */ + } else { + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "dense LBA=%llu src=lba_map PHYS-fail ret=%d; try l2v_lpn\n", + (unsigned long long)logical_sector, ret); + /* Fall through to LPN path. */ + } + } + + lpn = (unsigned int)(logical_sector / NAND_FTL_SECTORS_PER_LPN); + sec = (unsigned int)(logical_sector % NAND_FTL_SECTORS_PER_LPN); + + ret = nand_ftl_read_lpn_locked(f, lpn, sec, buf); + if (!ret) + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "dense LBA=%llu src=l2v_lpn lpn=%u sec=%u ret=0 head=%02x%02x%02x%02x\n", + (unsigned long long)logical_sector, lpn, sec, + ((u8 *)buf)[0], ((u8 *)buf)[1], + ((u8 *)buf)[2], ((u8 *)buf)[3]); + else + resolve_log_len = scnprintf( + resolve_log, sizeof(resolve_log), + "dense LBA=%llu src=l2v_lpn lpn=%u sec=%u ret=%d\n", + (unsigned long long)logical_sector, lpn, sec, ret); + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(nand_ftl_read_sector); + +int s5l8740_nand_available(void) +{ + return nand_dev != NULL; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_available); + +int s5l8740_nand_meta_transport_ok(void) +{ + struct nand_s5l8740 *f = nand_dev; + + /* + * Disk registration still requires meta_dma_read=1. + * Early CS phys helpers use s5l8740_nand_cs_phys_read() instead — + * glass-proven span4/rec4112, but FTL must not auto-open yet. + */ + return f && f->dma_ok && meta_dma_read; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_meta_transport_ok); + +int s5l8740_nand_hw_init(void) +{ + struct nand_s5l8740 *f = nand_dev; + int ret; + + if (!f) + return -ENODEV; + mutex_lock(&f->lock); + ret = fmss_nand_reset(f); + if (!ret) + (void)fmss_param_read(f, 0); + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_hw_init); + +int s5l8740_nand_query_geometry(struct s5l8740_nand_geom *g) +{ + struct nand_s5l8740 *f = nand_dev; + const u8 *p; + u32 caus, cau_bits, blocks, block_bits, pages, pages_slc; + u32 page_bits, page_size; + + if (!g) + return -EINVAL; + if (!f) + return -ENODEV; + + memset(g, 0, sizeof(*g)); + g->num_ce = FMSS_NUM_CE; + g->num_cau = FMSS_NUM_CAU; + g->blocks_per_cau = FMSS_BLOCKS_PER_CAU; + g->pages_per_block = 128; + g->pages_per_block_slc = 128; + g->page_size = FMSS_PAGE_LEN; + g->vfl_tail = FMSS_VFL_TAIL; + g->page_bits = FMSS_PAGE_BITS; + g->block_bits = FMSS_BLOCK_BITS; + g->cau_bits = FMSS_CAU_BITS; + g->caus_per_channel = FMSS_NUM_CAU; + + mutex_lock(&f->lock); + if (f->last_param_ret != 0) + (void)fmss_param_read(f, 0); + p = f->last_param; + if (f->last_param_ret == 0) { + caus = fmss_le32(p, 16); + cau_bits = fmss_le32(p, 20); + blocks = fmss_le32(p, 24); + block_bits = fmss_le32(p, 28); + pages = fmss_le32(p, 32); + pages_slc = fmss_le32(p, 36); + page_bits = fmss_le32(p, 40); + page_size = fmss_le32(p, 52); + if (caus && caus <= 4) + g->caus_per_channel = caus; + if (cau_bits && cau_bits <= 4) + g->cau_bits = cau_bits; + if (blocks && blocks <= 8192) + g->blocks_per_cau = blocks; + if (block_bits && block_bits <= 16) + g->block_bits = block_bits; + if (pages && pages <= 256) + g->pages_per_block = pages; + if (pages_slc && pages_slc <= 256) + g->pages_per_block_slc = pages_slc; + if (page_bits && page_bits <= 16) + g->page_bits = page_bits; + if (page_size == 4096 || page_size == 8192 || + page_size == 16384) + g->page_size = page_size; + g->from_param_page = true; + } + mutex_unlock(&f->lock); + + /* + * FIL GetInfo (vtable +80,: + * 101 — NAND present / signature +0x34 geometry (WhimoryBoot.c:169,260) + * 0 → "No NAND device found". Compared to sig[+0x34]. + * Value stored at format is blocks_per_cau → 0x8D102CC + * is the first geometry word copied from the param page). + * 104 — BUF_Init data bytes first arg) = physical page size + * 105 — BUF_Init meta bytes second arg) = 16 + * 135 — stored at 0x8D0CE2C and unused after GetInfo + */ + g->dev_id = g->blocks_per_cau; + g->geom_104 = g->page_size; + g->geom_105 = 16; + g->geom_135 = g->page_size >> 12; + if (!g->dev_id) + return -ENODEV; + return 0; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_query_geometry); + +u32 s5l8740_nand_fil_get_info(u32 selector) +{ + struct s5l8740_nand_geom g; + + if (s5l8740_nand_query_geometry(&g)) + return 0; + switch (selector) { + case 101: + return g.dev_id; + case 104: + return g.geom_104; + case 105: + return g.geom_105; + case 135: + return g.geom_135; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(s5l8740_nand_fil_get_info); + +static int fmss_dma_page_read_records(struct nand_s5l8740 *f, + unsigned int ce, u32 addr, + unsigned int slot, + unsigned int span) +{ + unsigned int saved_slot = dma_slot; + unsigned int saved_nsect = dma_nsect; + unsigned int saved_rec = dma_rec; + int ret; + + /* + * dma_slot / dma_nsect / dma_rec are module-wide, and this borrows + * them for the duration of one transfer. That is only safe under + * f->lock, and both callers on the CS path take it -- assert it so a + * third one cannot quietly arrive and start racing the geometry of + * somebody else's read. + */ + lockdep_assert_held(&f->lock); + + if (slot > 3) + return -EINVAL; + if (!span || span > 4 || slot + span > 4) + return -EINVAL; + + dma_slot = slot; + dma_nsect = span; + dma_rec = FMSS_PPN_REC; /* 4096 data + 16 meta — glass ABI */ + + if (meta_dma_reset_before) + fmss_nand_reset(f); + + ret = fmss_dma_page_read(f, ce, addr); + + dma_rec = saved_rec; + dma_nsect = saved_nsect; + dma_slot = saved_slot; + return ret; +} + +void s5l8740_nand_meta_decode(const u8 *m16, + struct s5l8740_meta_decoded *out) +{ + unsigned int i; + bool blank = true; + + if (!out) + return; + memset(out, 0, sizeof(*out)); + if (!m16) + return; + + out->type = m16[0]; + out->flags = m16[1]; + out->weave = (u64)m16[2] | ((u64)m16[3] << 8) | + ((u64)m16[4] << 16) | ((u64)m16[5] << 24) | + ((u64)m16[6] << 32) | ((u64)m16[7] << 40); + out->lba = (u32)m16[8] | ((u32)m16[9] << 8) | + ((u32)m16[10] << 16) | ((u32)m16[11] << 24); + + for (i = 0; i < N31_META_SLOT_SIZE; i++) { + if (m16[i] != 0x00 && m16[i] != 0xff) { + blank = false; + break; + } + } + out->blank = blank; + out->valid = !blank; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_meta_decode); + +void s5l8740_nand_meta_decode_legacy(const u8 *m16, + struct s5l8740_nand_slot_meta *out) +{ + struct s5l8740_meta_decoded d; + + if (!out) + return; + memset(out, 0, sizeof(*out)); + s5l8740_nand_meta_decode(m16, &d); + out->type = d.type; + out->flags = d.flags; + out->weave48 = d.weave; + out->lba = d.lba; + if (m16) + memcpy(out->aux, m16 + 12, 4); + out->data_like = n31_meta_is_data_record(&d); +} +EXPORT_SYMBOL_GPL(s5l8740_nand_meta_decode_legacy); + +int s5l8740_nand_meta_pick_lba(const struct s5l8740_cs_page *page, + u32 fmss_lba) +{ + int best = -ENOENT; + u64 best_weave = 0; + unsigned int s; + + if (!page) + return -EINVAL; + + for (s = 0; s < N31_DATA_SLOTS; s++) { + const struct s5l8740_meta_decoded *cur = &page->meta[s]; + + if (!n31_meta_is_data_record(cur) || cur->lba != fmss_lba) + continue; + if (best < 0 || n31_weave_newer(cur->weave, best_weave)) { + /* + * A "weave moved backward" check does not belong in + * this branch: n31_weave_newer() has just established + * the opposite, so the test cannot fire. The + * older-skipped case below is the one that reports. + */ + best = (int)s; + best_weave = cur->weave; + } else if (cur->weave < best_weave) { + pr_info_ratelimited("s5l8740-nand: weave older skipped " + "fmss_lba=%u slot=%u weave=%012llx " + "best=%012llx\n", + fmss_lba, s, + (unsigned long long)cur->weave, + (unsigned long long)best_weave); + } + } + return best; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_meta_pick_lba); + +/* + * FTL map CS physical read: always slot0/span4/rec4112. + * Fills struct s5l8740_cs_page. No lba_map ingest. + */ +/* + * Read only the first record of a page. + * + * A page is four 4096+16 records and the full read moves 16448 bytes. + * The FTL classify sweep looks at 64 bytes of slot-0 data and slot-0's + * 16-byte meta to decide "is this block empty", and seventy percent of + * the volume answers yes -- so most of that sweep was transferring 16 KB + * to read 80 bytes. Span 1 moves 4112 bytes instead. + * + * Only the empty test is safe on one record. Anything else classify asks + * -- CXT in particular -- inspects all four slots' meta, so the caller + * must fall back to the full read whenever the answer is not "empty". + * Slots 1..3 are left as 0xff here so a caller that ignores that rule + * sees erased meta rather than stale data from the previous page. + */ +int s5l8740_nand_cs_phys_read_slot0(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out) +{ + return s5l8740_nand_cs_phys_read_span(ce, cau, block, page, out, 1); +} +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_phys_read_slot0); + +int s5l8740_nand_cs_phys_read(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out) +{ + return s5l8740_nand_cs_phys_read_span(ce, cau, block, page, out, 4); +} + +int s5l8740_nand_cs_phys_read_span(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out, unsigned int span) +{ + return s5l8740_nand_cs_phys_read_slc(ce, cau, block, page, 0, out, + span); +} + +/* + * The same read, on a nominated SLC plane. + * + * Everything above reads slc=0 because that is where the user area lives. + * The FPart region at the tail of each CAU does not: it is SLC, and locating + * it means trying slc=1 before slc=0. + * + * This must not be routed through s5l8740_nand_page_read(), which refuses + * any request for meta unless meta_dma_read is set -- and that is + * deliberately clear, because a permanently live CS kick reboots the + * device. Every FPart read would return -EOPNOTSUPP before touching the + * NAND, yielding a "signature not found" verdict about a region that was + * never actually read. + */ +int s5l8740_nand_cs_phys_read_slc(u8 ce, u8 cau, u16 block, u8 page, u8 slc, + struct s5l8740_cs_page *out, unsigned int span) +{ + struct nand_s5l8740 *f = nand_dev; + u32 addr; + bool saved_armed; + int ret; + u64 t0, t1, t2; + unsigned int s; + + if (!f || !out) + return -ENODEV; + if (!f->dma_ok) + return -ENODEV; + if (ce >= FMSS_NUM_CE || cau >= FMSS_NUM_CAU || + block >= FMSS_BLOCKS_PER_CAU || page > FMSS_BTOC_PAGE) + return -EINVAL; + if (dma_dry) + return -EAGAIN; + if (!dma_armed) + return -EPERM; + + memset(out, 0, sizeof(*out)); + addr = fmss_ppn_addr(cau, block, page, slc); + + mutex_lock(&f->lock); + if (cs_reset_every && f->pages_since_reset >= cs_reset_every) { + /* + * Clearing the counter is the half that was missing. Without + * it the threshold latches and every read after the first + * reset resets again, which turns a periodic safeguard into a + * per-read cost. + */ + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + saved_armed = dma_armed; + /* One-shot friendly: re-arm for this kick; disarm after if one_shot. */ + dma_armed = true; + dma_skip_ingest = true; + t0 = ktime_get_ns(); + ret = fmss_dma_page_read_records(f, ce, addr, 0, span); + t1 = ktime_get_ns(); + dma_skip_ingest = false; + if (!dma_one_shot) + dma_armed = saved_armed; + f->pages_since_reset++; + + if (!ret) { + size_t dn = f->last_page_len; + size_t mn = f->last_spare_len; + + if (dn > FMSS_PAGE_LEN) + dn = FMSS_PAGE_LEN; + if (mn > S5L8740_NAND_META_SIZE) + mn = S5L8740_NAND_META_SIZE; + + for (s = 0; s < N31_DATA_SLOTS; s++) { + size_t doff = (size_t)s * N31_DATA_SLOT_SIZE; + size_t moff = (size_t)s * N31_META_SLOT_SIZE; + + memset(out->data[s], 0xff, N31_DATA_SLOT_SIZE); + if (dn > doff) { + size_t n = N31_DATA_SLOT_SIZE; + + if (dn - doff < n) + n = dn - doff; + memcpy(out->data[s], f->last_page + doff, n); + } + memset(out->meta_raw[s], 0xff, N31_META_SLOT_SIZE); + if (mn > moff) { + size_t n = N31_META_SLOT_SIZE; + + if (mn - moff < n) + n = mn - moff; + memcpy(out->meta_raw[s], f->last_spare + moff, n); + } + s5l8740_nand_meta_decode(out->meta_raw[s], &out->meta[s]); + } + } + + /* Where the wall time goes; also the last line the host sees over + * /proc/kmsg if the glass dies mid-recover. + */ + t2 = ktime_get_ns(); + cs_ns_kick += t1 - t0; + cs_ns_copy += t2 - t1; + cs_reads_total++; + if (cs_heartbeat && (cs_reads_total & 0x3ff) == 0) + pr_info("s5l8740-nand: cs_phys_read n=%u since_reset=%u " + "kick_us=%llu copy_us=%llu NANDSTAT=%08x\n", + cs_reads_total, f->pages_since_reset, + div_u64(cs_ns_kick, 1000 * cs_reads_total), + div_u64(cs_ns_copy, 1000 * cs_reads_total), + readl(f->base + NANDSTAT)); + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_phys_read_slc); +EXPORT_SYMBOL_GPL(s5l8740_nand_cs_phys_read); + +/* + * Batch CS sessions for map build: keep armed across many phys reads. + * + * This must be a nesting counter, not a single save slot. + * + * With a single slot begin() refuses with -EBUSY when one is already open, + * and every caller writes + * + * sess = begin(); + * ... + * if (sess == 0) + * end(); + * + * which is only correct when every owner is shorter-lived than the one + * outside it. Three lifetimes use this: per-read (whimory_read_fmss_lba), + * per-operation (recover, fat-critical validation, the sysfs pokes) and + * per-disk (n31_ftl_register_disk). + * + * Nesting a per-disk acquisition inside a per-operation one makes the inner + * begin() fail with -EBUSY, so the disk records that it holds no session + * and the outer end() disarms CS with the disk live. Every csmap fallback + * read then returns -EAGAIN from the dma_dry test before reaching f->lock, + * silently, leaving the layer that exists to rescue a Whimory map miss + * inert. The other order leaks instead: the disk's session is never closed + * and dma_dry stays 0 for the lifetime of the gendisk, which is the + * permanently live C00 kick documented above as rebooting the device. + * + * A depth counter makes both orders correct and lets every call site keep + * the `if (sess == 0)` idiom -- begin() always returns 0, so the guard is + * always true and each begin has exactly one end. + */ +static DEFINE_MUTEX(fmss_dma_session_lock); +static struct { + unsigned int depth; + bool dry; + bool armed; + bool one_shot; +} fmss_dma_batch; + +int s5l8740_nand_dma_session_begin(void) +{ + mutex_lock(&fmss_dma_session_lock); + if (!fmss_dma_batch.depth) { + fmss_dma_batch.dry = dma_dry; + fmss_dma_batch.armed = dma_armed; + fmss_dma_batch.one_shot = dma_one_shot; + dma_dry = false; + dma_one_shot = false; + dma_armed = true; + } + fmss_dma_batch.depth++; + mutex_unlock(&fmss_dma_session_lock); + return 0; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_dma_session_begin); + +void s5l8740_nand_dma_session_end(void) +{ + mutex_lock(&fmss_dma_session_lock); + if (WARN_ON_ONCE(!fmss_dma_batch.depth)) { + mutex_unlock(&fmss_dma_session_lock); + return; + } + if (!--fmss_dma_batch.depth) { + dma_dry = fmss_dma_batch.dry; + dma_armed = fmss_dma_batch.armed; + dma_one_shot = fmss_dma_batch.one_shot; + } + mutex_unlock(&fmss_dma_session_lock); +} +EXPORT_SYMBOL_GPL(s5l8740_nand_dma_session_end); + +/* Is CS currently armed by somebody's session? Diagnostics only. */ +unsigned int s5l8740_nand_dma_session_depth(void) +{ + return READ_ONCE(fmss_dma_batch.depth); +} +EXPORT_SYMBOL_GPL(s5l8740_nand_dma_session_depth); + +int s5l8740_nand_page_read(unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, + unsigned int slc, unsigned int chunks, + void *data, size_t data_len, + void *meta, size_t meta_len) +{ + struct nand_s5l8740 *f = nand_dev; + unsigned int saved; + u32 addr; + int ret; + + if (!f) + return -ENODEV; + if (ce >= FMSS_NUM_CE || cau >= FMSS_NUM_CAU || + block >= FMSS_BLOCKS_PER_CAU || page > FMSS_BTOC_PAGE) + return -EINVAL; + if (!chunks || chunks > FMSS_MAX_CHUNKS) + chunks = FMSS_MAX_CHUNKS; + + mutex_lock(&f->lock); + + if (reset_every && f->pages_since_reset >= reset_every) { + fmss_nand_reset(f); + f->pages_since_reset = 0; + } + + saved = page_chunks; + page_chunks = chunks; + addr = fmss_ppn_addr(cau, block, page, slc); + + if (meta && meta_len) { + /* + * Real metadata: always full-page CS span4/rec4112, then + * software-slice. Never invent PIO spare as Whimory meta. + */ + if (!meta_dma_read || !f->dma_ok) { + ret = -EOPNOTSUPP; + goto out_restore; + } + if (dma_dry) { + ret = -EAGAIN; + goto out_restore; + } + if (!dma_armed) { + ret = -EPERM; + goto out_restore; + } + dma_armed = true; + ret = fmss_dma_page_read_records(f, ce, addr, 0, 4); + } else { + ret = fmss_page_read(f, ce, addr); + } + + f->pages_since_reset++; + + if (!ret) { + if (data && data_len) { + size_t have = f->last_page_len; + + if (have > FMSS_PAGE_LEN) + have = FMSS_PAGE_LEN; + if (data_len > have) + data_len = have; + memcpy(data, f->last_page, data_len); + } + + if (meta && meta_len) { + size_t have = S5L8740_NAND_META_SIZE; + + memset(meta, 0xff, meta_len); + if (have > f->last_spare_len) + have = f->last_spare_len; + if (have > meta_len) + have = meta_len; + memcpy(meta, f->last_spare, have); + } + } + +out_restore: + page_chunks = saved; + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_page_read); + +int s5l8740_nand_reset(void) +{ + struct nand_s5l8740 *f = nand_dev; + int ret; + + if (!f) + return -ENODEV; + mutex_lock(&f->lock); + ret = fmss_nand_reset(f); + mutex_unlock(&f->lock); + return ret; +} +EXPORT_SYMBOL_GPL(s5l8740_nand_reset); + +void s5l8740_nand_register_ftl_read(int (*fn)(u64 lba, void *buf)) +{ + WRITE_ONCE(nand_ftl_read_hook, fn); +} +EXPORT_SYMBOL_GPL(s5l8740_nand_register_ftl_read); + +module_init(nand_s5l8740_init); +module_exit(nand_s5l8740_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("S5L8740 NAND/FMSS controller (Whimory FIL, read-only)"); +MODULE_AUTHOR("n31"); diff --git a/drivers/misc/nand-s5l8740.h b/drivers/misc/nand-s5l8740.h new file mode 100755 index 00000000000000..72162733bc1ffc --- /dev/null +++ b/drivers/misc/nand-s5l8740.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * S5L8740 NAND controller (FMSS/FIL) — raw PPN page I/O for the Whimory / CS-map stack. + * + * Terminology (FTL map): + * fmss_lba — logical LBA from 16-byte CS metadata + * disk_lba — exported FAT volume LBA (Linux/VFAT) + * fat_base_lba — fmss_lba of the FAT32 BPB (disk_lba 0) + * physical record — ce/cau/block/page/slot/weave/type/lba + * CS page — 4×4096 data + 4×16 meta (rec=4112) + * + * disk_lba 0 → fat_base_lba (proven candidate 49279). + * target_fmss_lba = fat_base_lba + disk_lba + * + * nand-s5l8740.ko owns the controller. ftl-s5l8740.ko owns map / block. + */ +#ifndef NAND_S5L8740_H +#define NAND_S5L8740_H + +#include +#include + +#define NAND_FTL_SECTOR_SIZE 4096U +#define NAND_FTL_SECTORS_PER_LPN 4U +#define NAND_FTL_DEFAULT_CAPACITY 3856968U + +#define S5L8740_NAND_MAX_CE 2U +#define S5L8740_NAND_MAX_CAU 2U +#define S5L8740_NAND_PAGE_SIZE 16384U +#define S5L8740_NAND_META_SIZE 64U /* 4 × 16-byte SFTL slots */ + +/* + * Batched meta scan (see s5l8740_nand_cs_read_meta_batch). + * + * A batch transfers one sector per page, so it yields slot 0 meta only -- + * 16 bytes, not the 64-byte four-slot record a full-page read returns. That + * is what a classify scan reads, and asking for less is what makes the + * batch fit: 16 pages of meta land in the 256-byte spare buffer the + * sequencer already had. + */ +#define S5L8740_NAND_BATCH_META_SIZE 16U +#define S5L8740_NAND_BATCH_DATA_HEAD 64U +#define S5L8740_NAND_BATCH_MAX 256U + +/* + * Read-path batch: whole pages, so the cap is lower than the meta scan's -- + * 8 * 16 KiB is the 128 KiB the shared DMA buffer holds, and it happens to + * be exactly one 128 KiB readahead window (8 pages * 4 LBAs). + */ +#define S5L8740_NAND_PAGE_BATCH_MAX 8U +#define S5L8740_NAND_SLOTS_PER_PAGE 4U +#define S5L8740_NAND_SLOT_DATA 4096U +#define S5L8740_NAND_SLOT_META 16U +#define S5L8740_NAND_REC_BYTES 4112U /* 4096 data + 16 meta */ + +/* FTL map names (aliases of the above). */ +#define N31_DATA_SLOTS S5L8740_NAND_SLOTS_PER_PAGE +#define N31_DATA_SLOT_SIZE S5L8740_NAND_SLOT_DATA +#define N31_META_SLOT_SIZE S5L8740_NAND_SLOT_META +#define N31_CS_REC_SIZE S5L8740_NAND_REC_BYTES + +/* PPN DATA spare (CS META stream): glass-proven with rec=4112. */ +#define S5L8740_NAND_META_TYPE_DATA 0x01u +#define S5L8740_NAND_META_TYPE_DATA2 0x02u + +struct s5l8740_nand_geom { + u32 num_ce; + u32 num_cau; + u32 blocks_per_cau; + u32 pages_per_block; + u32 pages_per_block_slc; + u32 page_size; + u32 vfl_tail; + u32 page_bits; + u32 block_bits; + u32 cau_bits; + u32 caus_per_channel; + u32 dev_id; /* FIL selector 101 analogue */ + u32 geom_104; /* FIL selector 104 analogue */ + u32 geom_105; /* FIL selector 105 analogue */ + u32 geom_135; /* FIL selector 135 analogue */ + bool from_param_page; +}; + +/* Decoded 16-byte CS metadata slot (fmss_lba lives in.lba). */ +struct s5l8740_meta_decoded { + u8 type; + u8 flags; + u64 weave; /* weaveSeq from meta bytes; width-limited */ + u32 lba; /* fmss_lba — NOT disk_lba */ + bool valid; + bool blank; +}; + +/* One CS physical page: four data + four meta records (rec=4112). */ +struct s5l8740_cs_page { + u8 data[N31_DATA_SLOTS][N31_DATA_SLOT_SIZE]; + u8 meta_raw[N31_DATA_SLOTS][N31_META_SLOT_SIZE]; + struct s5l8740_meta_decoded meta[N31_DATA_SLOTS]; +}; + +/* Legacy alias used by older call sites / Whimory. */ +struct s5l8740_nand_slot_meta { + u8 type; + u8 flags; + u64 weave48; + u32 lba; + u8 aux[4]; + bool data_like; +}; + +bool nand_ftl_present(void); +struct device *nand_ftl_device(void); +unsigned int nand_ftl_lpn_count(void); +int nand_ftl_build_map(unsigned int max_lpn); +int nand_ftl_read_sector(u64 logical_sector, void *buf); + +u32 s5l8740_nand_fil_get_info(u32 selector); +int s5l8740_nand_available(void); +int s5l8740_nand_meta_transport_ok(void); +int s5l8740_nand_hw_init(void); +int s5l8740_nand_query_geometry(struct s5l8740_nand_geom *g); + +/* + * CS physical read (FTL map ABI): + * always slot=0, span=4, rec=4112, command-list CS DMA + * fills 4 data + 4 meta slots; no lba_map ingest + * Requires dma_dry=0 and dma_armed=1 (one-shot friendly). + */ +int s5l8740_nand_cs_read_meta_batch(u8 ce, u8 cau, const u16 *blocks, u8 page, + unsigned int n, u8 *meta_out, u8 *data_out, + unsigned int data_bytes); +/* + * One physical page, named. A batch is a list of these on a single CE -- + * cau, block and page all vary per entry, because the FTL stripes + * consecutive LBAs across every (ce, cau) before it advances the page, so + * the pages a sequential read wants next are never consecutive on one chip. + */ +struct s5l8740_ppn_ref { + u16 block; + u8 cau; + u8 page; +}; + +int s5l8740_nand_cs_read_pages_batch(u8 ce, const struct s5l8740_ppn_ref *refs, + unsigned int n, u8 *data_out, + u8 *meta_out); + +/* + * Meta-only scan: n arbitrary pages on one CE, one kick, 16 bytes of spare + * per page and no page data at all. This is stock's mount scan shape + * (s_cxt_diff.c:349) -- every read lands its data in the same discarded + * buffer while only the meta cursor advances, so 256 pages cost one kick + * and 4 KiB of meta. + */ +int s5l8740_nand_cs_scan_meta(u8 ce, const struct s5l8740_ppn_ref *refs, + unsigned int n, u8 *meta_out); +int s5l8740_nand_cs_phys_read_slot0(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out); +int s5l8740_nand_cs_phys_read_span(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out, unsigned int span); +int s5l8740_nand_cs_phys_read_slc(u8 ce, u8 cau, u16 block, u8 page, u8 slc, + struct s5l8740_cs_page *out, unsigned int span); +int s5l8740_nand_cs_phys_read(u8 ce, u8 cau, u16 block, u8 page, + struct s5l8740_cs_page *out); + +/* + * Hold dma_armed across a multi-page CS scan; restores prior dry/one_shot + * when the last nested session closes. begin() always returns 0 and nests, + * so every begin must be paired with exactly one end. + */ +int s5l8740_nand_dma_session_begin(void); +void s5l8740_nand_dma_session_end(void); +unsigned int s5l8740_nand_dma_session_depth(void); + +void s5l8740_nand_meta_decode(const u8 *m16, + struct s5l8740_meta_decoded *out); + +/* Legacy decode into slot_meta (aux + data_like). */ +void s5l8740_nand_meta_decode_legacy(const u8 *m16, + struct s5l8740_nand_slot_meta *out); + +static inline bool n31_meta_is_data_record(const struct s5l8740_meta_decoded *m) +{ + if (!m || !m->valid || m->blank) + return false; + return m->type == S5L8740_NAND_META_TYPE_DATA || + m->type == S5L8740_NAND_META_TYPE_DATA2; +} + +static inline bool n31_weave_newer(u64 a, u64 b) +{ + return a > b; +} + +/* + * Among four decoded meta records, pick newest weave claiming @fmss_lba + * with type in {0x01,0x02}. Returns slot 0..3 or -ENOENT. + */ +int s5l8740_nand_meta_pick_lba(const struct s5l8740_cs_page *page, + u32 fmss_lba); + +int s5l8740_nand_page_read(unsigned int ce, unsigned int cau, + unsigned int block, unsigned int page, + unsigned int slc, unsigned int chunks, + void *data, size_t data_len, + void *meta, size_t meta_len); +int s5l8740_nand_reset(void); +void s5l8740_nand_register_ftl_read(int (*fn)(u64 lba, void *buf)); + +#endif /* NAND_S5L8740_H */ diff --git a/drivers/misc/s5l8740-iis2-mmio.c b/drivers/misc/s5l8740-iis2-mmio.c new file mode 100755 index 00000000000000..12792264d863fb --- /dev/null +++ b/drivers/misc/s5l8740-iis2-mmio.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 IIS2 @ 0x3D400000 — BCM2078 digital PCM port (N31). + * + * RetailOS oracles (fm/, bt-*-scsi-live/): + * IIS1 @ 0x3CD00000 = XSP, always zero — NOT BCM TX. + * IIS2 = shared BCM2078 I²S: RX FIFO @ +0x38 (PL080 peri 13, FM + module PCM in), + * TXCON @ +0x04 = 0x0b000099 programmed for music/FM/BT (TXCOM often 0 on BT). + * BT A2DP over-the-air = UART1 @ 0x3DB HCI → BCM2078 (no IIS0/CS42). + * CLKCON +0x00 = 0x1 + * TXCON +0x04 = 0x0b000099 (RetailOS programs this on IIS2 too) + * RXCON +0x30 = 0x1000 + * RXCOM +0x34 = 0x6 (DMA kick; idle/stopped often 0x2) + * RXFIFO +0x38 ← PL080 peri 13 P2M + * STATUS +0x3c = 0x10804 live + * CLKDIV +0x40 = 0x96 (FM oracle; IIS0 play uses 0x177/375) + * REG44 +0x44 = 0x00010007 (same as IIS0 music oracle) + * + * SoC clocks: CLKCON+0x30 = 0x32190 play; FM also +0x10 = 0x4 + * (vs music/idle 0x8004). No FM→BT / A2DP path here — local PCM only. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "n31-audio-rates.h" + +#define S5L8740_IIS2_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define S5L8740_IIS2_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +#define I2SCLKCON 0x00 +#define I2STXCON 0x04 +#define I2SRXCON 0x30 +#define I2SRXCOM 0x34 +#define I2SRXFIFO 0x38 +#define I2SSTATUS 0x3c +#define I2SCLKDIV 0x40 +#define I2SREG44 0x44 + +#define MCLK_ASSUME_HZ 12000000u + +/* fm/20260826T203834Z oracle */ +#define IIS2_CLKCON_ON 0x1u +#define IIS2_TXCON_FM 0x0b000099u +#define IIS2_RXCON_FM 0x1000u +#define IIS2_RXCOM_DMA 0x6u +#define IIS2_RXCOM_IDLE 0x2u +#define IIS2_CLKDIV_FM_ORACLE 0x96u +#define IIS2_REG44_ORACLE 0x00010007u + +static uint iis2_clkdiv; +module_param(iis2_clkdiv, uint, 0644); +MODULE_PARM_DESC(iis2_clkdiv, "IIS2 CLKDIV override; 0 = FM oracle 0x96"); + +#define CLKCON_PHYS 0x3c500000ul +#define CLKCON_AUDIO_OFF 0x30 +#define CLKCON_FM_GATE_OFF 0x10 +#define CLKCON_AUDIO_PLAY 0x32190u +#define CLKCON_AUDIO_IDLE 0x1c20u +#define CLKCON_FM_GATE_ON 0x4u +#define CLKCON_FM_GATE_OFF_VAL 0x8004u + +#define IIS2_REGS_LEN 0x48 + +struct s5l8740_iis2 { + void __iomem *base; + void __iomem *clkcon; + struct device *dev; + struct clk_bulk_data *clks; + int num_clks; + bool has_dma; + struct snd_dmaengine_dai_dma_data cap_dma; + u32 clkcon10_saved; + bool clkcon10_held; + unsigned int rate; +}; + +static u32 iis2_pick_clkdiv(unsigned int rate) +{ + const struct n31_rate_cfg *r; + + if (iis2_clkdiv) + return iis2_clkdiv; + /* + * FM IIS2 oracle differs from IIS0: 0x96 while IIS0 HP path uses + * 0x177 (32 kHz table entry) during the same FM session. + */ + if (rate == 44100 || rate == 48000) + return IIS2_CLKDIV_FM_ORACLE; + r = n31_find_rate(rate); + if (r) + return r->clkdiv; + if (!rate) + rate = 44100; + return MCLK_ASSUME_HZ / rate; +} + +static void iis2_clkcon_audio(struct s5l8740_iis2 *iis2, u32 val) +{ + if (!iis2 || !iis2->clkcon) + return; + writel(val, iis2->clkcon + CLKCON_AUDIO_OFF); +} + +static void iis2_clkcon_fm_gate(struct s5l8740_iis2 *iis2, bool on) +{ + u32 cur; + + if (!iis2 || !iis2->clkcon) + return; + cur = readl(iis2->clkcon + CLKCON_FM_GATE_OFF); + if (on) { + if (!iis2->clkcon10_held) { + iis2->clkcon10_saved = cur; + iis2->clkcon10_held = true; + } + writel(CLKCON_FM_GATE_ON, iis2->clkcon + CLKCON_FM_GATE_OFF); + } else if (iis2->clkcon10_held) { + writel(iis2->clkcon10_saved ? + iis2->clkcon10_saved : CLKCON_FM_GATE_OFF_VAL, + iis2->clkcon + CLKCON_FM_GATE_OFF); + iis2->clkcon10_held = false; + } +} + +/* + * Program IIS2 RX from fm-playing dump. Peri 13 DMA must be armed by + * dmaengine before RXCOM |= 0x6 (same kick model as IIS0 TXCOM). + */ +static void iis2_program_rx(struct s5l8740_iis2 *iis2) +{ + u32 div; + + iis2_clkcon_fm_gate(iis2, true); + iis2_clkcon_audio(iis2, CLKCON_AUDIO_PLAY); + writel(IIS2_CLKCON_ON, iis2->base + I2SCLKCON); + writel(IIS2_TXCON_FM, iis2->base + I2STXCON); + writel(IIS2_RXCON_FM, iis2->base + I2SRXCON); + div = iis2_pick_clkdiv(iis2->rate); + writel(div, iis2->base + I2SCLKDIV); + writel(IIS2_REG44_ORACLE, iis2->base + I2SREG44); +} + +static void iis2_rx_kick(struct s5l8740_iis2 *iis2) +{ + writel(IIS2_RXCOM_DMA, iis2->base + I2SRXCOM); +} + +static void iis2_hw_stop(struct s5l8740_iis2 *iis2) +{ + if (!iis2 || !iis2->base) + return; + writel(IIS2_RXCOM_IDLE, iis2->base + I2SRXCOM); + iis2_clkcon_audio(iis2, CLKCON_AUDIO_IDLE); + iis2_clkcon_fm_gate(iis2, false); +} + +static int s5l8740_iis2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (!iis2 || !iis2->base) + return -ENODEV; + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + iis2->rate = params_rate(params); + iis2_program_rx(iis2); + dev_info(dai->dev, + "IIS2 hw_params rate=%u ch=%u clkdiv=0x%x reg44=0x%x status=0x%x\n", + iis2->rate, params_channels(params), + readl(iis2->base + I2SCLKDIV), readl(iis2->base + I2SREG44), + readl(iis2->base + I2SSTATUS)); + return 0; +} + +static int s5l8740_iis2_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (!iis2 || !iis2->base) + return -ENODEV; + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + iis2_program_rx(iis2); + iis2_rx_kick(iis2); + dev_info(dai->dev, "IIS2 capture start rxcom=0x%x status=0x%x\n", + readl(iis2->base + I2SRXCOM), + readl(iis2->base + I2SSTATUS)); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + iis2_hw_stop(iis2); + return 0; + default: + return -EINVAL; + } +} + +static int s5l8740_iis2_dai_probe(struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (iis2->has_dma) + snd_soc_dai_init_dma_data(dai, NULL, &iis2->cap_dma); + return 0; +} + +static const struct snd_soc_dai_ops s5l8740_iis2_dai_ops = { + .probe = s5l8740_iis2_dai_probe, + .hw_params = s5l8740_iis2_hw_params, + .trigger = s5l8740_iis2_trigger, +}; + +static struct snd_soc_dai_driver s5l8740_iis2_dai = { + .name = "bcm2078-pcm", + .capture = { + .stream_name = "BCM2078 PCM Capture", + .channels_min = 1, + .channels_max = 2, + .rates = S5L8740_IIS2_RATES, + .formats = S5L8740_IIS2_FORMATS, + }, + .ops = &s5l8740_iis2_dai_ops, +}; + +static const struct snd_soc_component_driver s5l8740_iis2_component = { + .name = "bcm2078-pcm", + .legacy_dai_naming = 1, +}; + +static ssize_t regs_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct s5l8740_iis2 *iis2 = dev_get_drvdata(dev); + unsigned int i; + ssize_t n = 0; + + if (!iis2 || !iis2->base) + return sysfs_emit(buf, "not mapped\n"); + + for (i = 0; i < IIS2_REGS_LEN; i += 4) { + n += sysfs_emit_at(buf, n, "%02x: %08x\n", i, + readl(iis2->base + i)); + if (n >= PAGE_SIZE - 32) + break; + } + if (iis2->clkcon) { + n += sysfs_emit_at(buf, n, "clk+10: %08x\n", + readl(iis2->clkcon + CLKCON_FM_GATE_OFF)); + n += sysfs_emit_at(buf, n, "clk+30: %08x\n", + readl(iis2->clkcon + CLKCON_AUDIO_OFF)); + } + return n; +} +static DEVICE_ATTR_RO(regs); + +static int s5l8740_iis2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l8740_iis2 *iis2; + struct resource *res; + int ret; + + iis2 = devm_kzalloc(dev, sizeof(*iis2), GFP_KERNEL); + if (!iis2) + return -ENOMEM; + iis2->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iis2->base = devm_ioremap_resource(dev, res); + if (IS_ERR(iis2->base)) + return PTR_ERR(iis2->base); + + iis2->clkcon = devm_ioremap(dev, CLKCON_PHYS, 0x80); + + ret = devm_clk_bulk_get_all(dev, &iis2->clks); + if (ret > 0) { + iis2->num_clks = ret; + ret = clk_bulk_prepare_enable(iis2->num_clks, iis2->clks); + if (ret) + dev_warn(dev, "clk_bulk: %d\n", ret); + } + + if (res) { + iis2->cap_dma.addr = res->start + I2SRXFIFO; + iis2->cap_dma.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + iis2->cap_dma.maxburst = 1; + } + + platform_set_drvdata(pdev, iis2); + dev_set_drvdata(dev, iis2); + + if (of_property_present(dev->of_node, "dmas")) { + ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0); + if (ret) { + dev_err(dev, "dmaengine_pcm: %d\n", ret); + return ret; + } + iis2->has_dma = true; + } else { + dev_err(dev, "missing dmas (need peri 13 rx)\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_component(dev, &s5l8740_iis2_component, + &s5l8740_iis2_dai, 1); + if (ret) + return ret; + + ret = device_create_file(dev, &dev_attr_regs); + if (ret) + dev_warn(dev, "regs sysfs: %d\n", ret); + + dev_info(dev, + "BCM2078 PCM RX @%pR peri13 FIFO@+0x38 (IIS2; FM/A2DP PCM in)\n", + res); + return 0; +} + +static void s5l8740_iis2_remove(struct platform_device *pdev) +{ + struct s5l8740_iis2 *iis2 = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_regs); + iis2_hw_stop(iis2); + if (iis2 && iis2->num_clks) + clk_bulk_disable_unprepare(iis2->num_clks, iis2->clks); +} + +static const struct of_device_id s5l8740_iis2_of_match[] = { + { .compatible = "apple,s5l8740-bcm2078-pcm" }, + { .compatible = "apple,s5l8740-iis2" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_iis2_of_match); + +static struct platform_driver s5l8740_iis2_driver = { + .probe = s5l8740_iis2_probe, + .remove = s5l8740_iis2_remove, + .driver = { + .name = "s5l8740-iis2", + .of_match_table = s5l8740_iis2_of_match, + }, +}; +module_platform_driver(s5l8740_iis2_driver); + +MODULE_DESCRIPTION("S5L8740 BCM2078 PCM capture DAI (IIS2 @0x3D400000, peri 13 RX)"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: dma_s5l8740_pl080"); diff --git a/drivers/misc/whimory-ftl.h b/drivers/misc/whimory-ftl.h new file mode 100755 index 00000000000000..4e5979080dc845 --- /dev/null +++ b/drivers/misc/whimory-ftl.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Classic Whimory VFL/FTL structs (freemyipod Nano 2G FTL wiki). + * N31 uses PPN/SFTL (wrmx / L2V) but the same spare type codes and + * mount flow (VFL → ftlctrlblocks → FTL cxt 0x43 → map pages 0x44) apply + * as a family reference for Stage C. + * + * Endianness: all multi-byte fields are little-endian on flash. + */ +#ifndef WHIMORY_FTL_H +#define WHIMORY_FTL_H + +#include + +/* Spare type codes (meta[9] / user.type) */ +#define WMR_SPARE_DATA 0x40 +#define WMR_SPARE_DATA_LAST 0x41 +#define WMR_SPARE_FTL_CXT 0x43 +#define WMR_SPARE_BLOCK_MAP 0x44 +#define WMR_SPARE_ERASECTR 0x46 +#define WMR_SPARE_UNCLEAN 0x47 +#define WMR_SPARE_VFL_CXT 0x80 +/* PPN/SFTL (N31) */ +#define WMR_SPARE_VFLCXT_PPN 0x20 +#define WMR_SPARE_SFTL_CXT 0x1F + +#define WMR_DEVICEINFOSIGN "DEVICEINFOSIGN" + +/* User-data spare (types 0x40 / 0x41) — first 0xC bytes + ECC follow */ +struct wmr_spare_user { + __le32 lpn; + __le32 usn; + u8 field_8; + u8 type; + u8 eccmark; + u8 field_B; +} __packed; + +/* Meta spare (FTL/VFL context, block map, …) */ +struct wmr_spare_meta { + __le32 usn; + __le16 idx; + u8 field_6; + u8 field_7; + u8 field_8; + u8 type; + u8 eccmark; + u8 field_B; +} __packed; + +/* + * VFL context — Nano2G size; N31 wrmx header is a different layout but + * still carries FTL ctrl block hints in family ports. + */ +struct wmr_vfl_cxt { + __le32 usn; + __le16 ftlctrlblocks[3]; + u8 field_A[2]; + __le32 updatecount; + __le16 activecxtblock; + __le16 nextcxtpage; + u8 field_14[4]; + __le16 field_18; + __le16 spareused; + __le16 firstspare; + __le16 sparecount; + __le16 remaptable[0x334]; + u8 bbt[0x11A]; + __le16 vflcxtblocks[4]; + __le16 scheduledstart; + u8 field_7AC[0x4C]; + __le32 checksum1; + __le32 checksum2; +} __packed; + +/* FTL context (0x28C used by freemyipod on read) */ +struct wmr_ftl_cxt { + __le32 usn; + __le32 nextblockusn; + __le16 freecount; + __le16 nextfreeidx; + __le16 swapcounter; + __le16 blockpool[0x14]; + __le16 field_36; + __le32 ftl_map_pages[8]; + u8 field_58[0x28]; + __le32 ftl_erasectr_pages[8]; + u8 field_A0[0x70]; + __le32 ftl_map_ptr; + __le32 ftl_erasectr_ptr; + __le32 ftl_log_ptr; + __le32 erasedirty; + __le16 field_120; + __le16 ftlctrlblocks[3]; + __le32 ftlctrlpage; + __le32 clean_flag; + u8 field_130[0x15C]; +} __packed; + +/* Classic lPage → vPage using block map (u16 vBlock per lBlock). */ +static inline u32 wmr_lpage_to_vpage(u32 lpage, u32 pages_per_block, + const u16 *map, u32 map_entries) +{ + u32 lblock = lpage / pages_per_block; + u32 page = lpage % pages_per_block; + u16 vblock; + + if (lblock >= map_entries || !map) + return ~0u; + vblock = map[lblock]; + if (!vblock || vblock == 0xffff) + return ~0u; + return (u32)vblock * pages_per_block + page; +} + + +#endif /* WHIMORY_FTL_H */ diff --git a/drivers/misc/whimory-s5l8740.h b/drivers/misc/whimory-s5l8740.h new file mode 100755 index 00000000000000..7b3b8e2eefe899 --- /dev/null +++ b/drivers/misc/whimory-s5l8740.h @@ -0,0 +1,711 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * N31 Whimory PPN / SFTL / L2V — in-kernel structures. + * + * On-flash constants from OSOS 1.0.2 (sub_111B0C / sub_1122FC / sub_E8CA0 / + * sub_428694 / sub_56AB3C / sub_56C328). Runtime L2V bytes are allocated + * here; they are not present in the static OSOS dump. + */ +#ifndef WHIMORY_S5L8740_H +#define WHIMORY_S5L8740_H + +#include +#include +#include +#include + +#include "nand-s5l8740.h" + +#define WHIMORY_SIG_SIZE 0x600 +#define WHIMORY_SIG_MAGIC 0x776d7278u /* "xrmw" LE — payload, not raw page+0 */ +#define WHIMORY_SIG_TYPE 0xC101u /* FPart special type; op80/op20 */ +#define WHIMORY_SIG_MAGIC_WRMX 0x786d7277u /* "wrmx" LE — VFL CXT, not FPart sig */ +#define WHIMORY_SIG_MAGIC_REV 0x78726d77u /* "wmrx" byte-reversed hunt */ + +/* OSOS fpart_read_special_copy_4F1420 / locate_4EBBDC */ +#define FPART_SPECIAL_TAG 0x30u +#define FPART_SPECIAL_CLASS 1u +#define FPART_SPECIAL_CLASS_MASK 0x3fu +#define FPART_SPECIAL_HDR 0x80u /* chunk0 payload starts here */ +/* meta[0] of every FPart system object; the type word at meta+2 sorts them. */ +#define FPART_META_TYPE_SPECIAL 0x30u +/* Enumerated on the glass: c101 signature, c105 config/serial, c104 context. */ +#define FPART_TYPE_SIGNATURE 0xc101u +#define FPART_TYPE_VFL_CXT 0xc104u +#define FPART_TYPE_CONFIG 0xc105u +#define FPART_SPECIAL_LEN_OFF 0x24u +#define FPART_SPECIAL_GEN_OFF 0x28u +#define FPART_SPECIAL_TABLE_BYTES 0x2d0u /* ctx+112; 120 × 6 */ +#define FPART_SPECIAL_MAX_ENTRIES (FPART_SPECIAL_TABLE_BYTES / 6) +#define FPART_ASSIGN_MAX_PAIRS 8u +#define FPART_SPECIAL_TYPE_C104 0xC104u +#define FPART_SPECIAL_TYPE_C105 0xC105u + +struct fpart_special_entry { + u16 bank; + u16 block; + u16 type_word; +} __packed; + +struct whimory_fpart { + struct fpart_special_entry table[FPART_SPECIAL_MAX_ENTRIES]; + u16 count; + bool scanned; + unsigned int slot_logs; +}; + +#define WHIMORY_LBA_SIZE 4096U +#define WHIMORY_MIN_NODEPOOL_BYTES 0x80000 +#define WHIMORY_L2V_NODE_SIZE 64 +#define WHIMORY_L2V_ROOT_SPAN 0x8000 +#define WHIMORY_L2V_ROOT_REC_SIZE 6 +#define WHIMORY_L2V_INVALID_ROOT 0xffff +#define WHIMORY_META_SIZE 16 + +/* Pages held in the sequential read-ahead window (16 KiB each). */ +#define WHIMORY_RC_SLOTS 8 + +/* Legacy per-block classify prefetch window, in 4 KiB slots. */ +#define WHIMORY_PF_SLOTS 16 +#define WHIMORY_NUM_CE_MAX 2 +#define WHIMORY_VBAS_PER_PAGE 4 +#define WHIMORY_PAGES_PER_SB 128 +#define WHIMORY_DATA_PAGES_PER_SB 127 +#define WHIMORY_BTOC_PAGE 127 +/* + * VBAs per superblock includes the block table of contents page: the last + * VBA a superblock can address falls on page 127. + */ +#define WHIMORY_VBAS_PER_SB (WHIMORY_PAGES_PER_SB * \ + WHIMORY_VBAS_PER_PAGE) +#define WHIMORY_DATA_VBAS_PER_SB (WHIMORY_DATA_PAGES_PER_SB * \ + WHIMORY_VBAS_PER_PAGE) + +#define WHIMORY_META_TYPE_DATA 0x01 +#define WHIMORY_META_TYPE_DATA2 0x02 +#define WHIMORY_META_TYPE_BTOC 0x1c +#define WHIMORY_META_TYPE_SFTL_CXT 0x1f +#define WHIMORY_META_TYPE_VFL_CXT 0x20 + +#define WHIMORY_SB_EMPTY 0 +#define WHIMORY_SB_CLOSED 1 +#define WHIMORY_SB_OPEN 2 +#define WHIMORY_SB_CXT 7 /* s_cxt_diff.c type 7 */ + +#define WHIMORY_SB_UNKNOWN 3 + +#define WHIMORY_CXT_MAX_SB 32 +#define WHIMORY_CXT_TAG_BASE 1 +#define WHIMORY_CXT_TAG_STATS 2 +#define WHIMORY_CXT_TAG_SB 3 +#define WHIMORY_CXT_TAG_L2V 4 +#define WHIMORY_CXT_TAG_USERSEQ 5 +#define WHIMORY_CXT_TAG_READS 6 +/* 0xff is both "nothing written here" and the record-stream terminator. */ +#define WHIMORY_CXT_TAG_END 255 +#define WHIMORY_CXT_TAG_CLEAN 0xff +#define WHIMORY_CXT_CONTIG_SPAN 0xfffffff0u +/* TREE hole sentinel: consumes logical space, maps nothing. */ +#define WHIMORY_CXT_VBA_HOLE 0x7fffffu +#define WHIMORY_FIL_META_BYTES 16 /* FIL GetInfo(105); sub_12ED9C */ + +/* BTOC / META tokens (sub_5688C4). Occupy VBA stream, not user L2V. */ +#define WHIMORY_LBA_HOLE 0xFFFF0000u +#define WHIMORY_LBA_LIST 0xFFFF0001u +#define WHIMORY_LBA_DELETED 0xFFFFFF00u +#define WHIMORY_LBA_BLANK 0xFFFFFFFFu + +/* VFL_GetParam selector: num_superblocks (WhimoryBoot.c sub_130158). */ +#define WHIMORY_VFL_PARAM_NUM_SB 0x02000100u +#define WHIMORY_VFL_SPARE_FREE 0xC070u /* sub_4EABA4 / 3D26D8 */ +#define WHIMORY_VFL_CXT_HDR 0x200u /* sub_4EB02C */ +#define WHIMORY_VFL_SPARE_STRIDE 32u /* sub_4EB098 */ +#define WHIMORY_BTOC_SLOTS 6 /* s_btoc.c sub_56863C */ +#define WHIMORY_BTOC_OPEN 2 +#define WHIMORY_GC_ZONE_MIN 16 /* s_gc.c sub_56A328 */ +#define WHIMORY_L2V_FINDFRAG_WIN 32 /* L2V_FindFrag.c */ +#define WHIMORY_L2V_MIN_FREE 0x22u /* sub_3F8958: free > 0x21 */ +#define WHIMORY_L2V_UPDATE_REPACK 0xC8u /* sub_E8EA8 */ + +struct whimory; + +struct whimory_geometry { + u32 num_ce; + u32 num_cau; + u32 blocks_per_cau; + u32 user_blocks; + u32 pages_per_block; + u32 page_size; + u32 vfl_tail; + u32 dev_id; + u32 geom_104; + u32 geom_105; + u32 geom_135; +}; + +struct whimory_signature { + u8 raw[WHIMORY_SIG_SIZE]; + u32 version; + u32 ftl_major; + u32 ftl_minor; + u32 vfl_major; + u32 vfl_minor; + u32 fpart_major; + u32 fpart_minor; + u32 sig_geom; + u32 flags_or_open; /* +0x20 VFL/open arg */ + u32 fpart_arg; /* +0x2c */ + u32 extra_arg; /* +0x30 */ +}; + +struct whimory_meta { + u8 type; + u8 flags; + u8 unk02[6]; + __le32 lba; + u8 unk0c[4]; +} __packed; + +/* s_btoc.c sub_567E3C / s_cxt_diff.c sub_5694F0 — LE u32×4 */ +struct whimory_bte { + __le32 weave_seq_add; + __le32 aux; + __le32 lba; + __le32 span; +} __packed; + +struct whimory_leaf { + u32 vba; + u32 span; +}; + +struct whimory_l2v { + u32 num_roots; + u32 nodepool_bytes; + u32 nodes_used; + u8 bits_vba; + u8 spanbits_vba; + u8 bits_nodeidx; + u8 spanbits_nodeidx; + u32 invalid_vba; + u32 sentinel_vba; + u32 updates; /* 0x8D100B0 */ + u32 gen; /* 0x8D100B4 */ + u32 frag_count; /* L2V_FindFrag */ + u32 frag_max; + u32 free_head; /* L2V_Mem.c 0x8D100DC; 0xffffffff empty */ + u32 free_count; /* 0x8D100E0 */ + u8 *root; /* 6 bytes × numRoots */ + u8 *nodes; + struct whimory_leaf *leaf_scratch; +}; + +struct whimory_range { + struct rb_node rb; + u32 start; + u32 len; + u32 vba; + u64 weave; + /* + * Which producer put this range here: 1 BTOC, 2 open rebuild, + * 3 CXT seed, 4 list token. Diagnostic only, and the reason it + * exists is that a wrong mapping looks identical whoever wrote + * it -- the CXT record for the failing LBAs parses correctly, + * so the question is who overwrote it afterwards. + */ + u8 src; +}; + +struct whimory_vfl { + u32 *remap[S5L8740_NAND_MAX_CAU]; + u16 *cxt_u16[S5L8740_NAND_MAX_CAU]; + u32 ctx_ce[S5L8740_NAND_MAX_CAU]; + u32 ctx_block[S5L8740_NAND_MAX_CAU]; + u32 remap_count; + u32 cxt_u16_len; + u32 cxt_loc_count; + u32 ctx_hits; + u32 spare_applied; + u32 bitmap_loaded; + /* Block status table from the 0xc104 object: one bit per block. */ + u8 *blk_status; + u32 blk_status_bad; + u32 blk_status_bank; + u32 blk_status_block; + u32 blk_status_mismatch; + bool syscfg_ok; + u32 bank_stride; /* 0x8D0D0F0; N31 = 1 byte/VBN */ + u8 *bank_mask; /* [blocks_per_cau] bank bitmask; sub_3D1438 */ + u16 cached_vbn; + u8 cached_n; + u8 cached_banks[S5L8740_NAND_MAX_CAU]; +}; + +/* + * Apple SysCfg, the 0xc105 FPart object. + * + * A header naming the entry count, then that many records. Each record + * is a four-character tag followed by its value, and the tag is stored + * byte-reversed -- "SrNm" sits in the page as 6d 4e 72 53. Record sizes + * are not uniform, so the parser finds the tags and takes each value as + * the bytes up to the next one rather than assuming a stride. + * + * Read off the glass: 11 entries, SrNm FwId HwId HwVr SwVr MLB# CNTB + * MtCl Mod# Regn BMac. + */ +/* + * The touch calibration block, as an offset into the SysCfg payload. + * + * It sits at 0x1dc0 from the start of the page, and the payload starts at + * FPART_SPECIAL_HDR, so the payload-relative offset is 0x80 less. Using + * the page offset here started the block 0x80 bytes late and cut its + * header off. + */ +#define N31_TOUCH_CAL_OFF (0x1dc0u - FPART_SPECIAL_HDR) + +/* Bytes stock copies from the touch calibration descriptor (sub_564). */ +#define N31_TOUCH_CAL_LEN 0x560u +/* apple-grape's calibration window inside that blob. */ +#define N31_TOUCH_CAL_CAL_OFF 350u +#define N31_TOUCH_CAL_CAL_LEN 0x200u + +/* Candidate tags recorded from one SysCfg section. */ +#define N31_SYSCFG_MAX_CAND 64u + +struct whimory_syscfg { + bool valid; + u32 entries; + char serial[24]; /* SrNm */ + char model[24]; /* Mod# */ + char mlb[32]; /* MLB#, the logic board */ + char sw_ver[24]; /* SwVr */ + char cnt_b[24]; /* CNTB */ + char mt_cl[24]; /* MtCl */ + u8 mac[6]; /* BMac */ + bool mac_ok; + u32 region; /* Regn */ + u32 hw_ver; /* HwVr */ + u32 fw_id; /* FwId */ + /* touch calibration touchscreen calibration; raw, for apple-grape. */ + /* + * The touch calibration blob, verbatim and whole. + * + * Stock takes the descriptor at 0x2202FE18 (magic 0x53797349, data + * pointer at +4) and copies 0x560 bytes from it; apple-grape then + * reads its calibration window at +350 for 0x200. So the consumer + * needs 862 bytes at minimum and the container is 1376, which is + * what U-Boot republishes via /chosen apple,n31-touch_cal-addr and + * apple,n31-touch_cal-size. + * + * A 1024-byte buffer could not hold it. + */ + u8 touch_cal[N31_TOUCH_CAL_LEN]; + u32 touch_cal_len; + int touch_cal_magic_off; /* offset of the container magic, -1 if absent */ + + /* + * The whole SysCfg section, verbatim, plus every 4-byte-aligned + * printable group found in it. + * + * The parser only decodes the eleven tags it knows. Everything else + * in the section is invisible, and this device carries records + * nobody has identified yet -- so keep the raw bytes and a list of + * candidate tags, and let them be read out rather than guessed at. + * + * Tags are stored byte-reversed on disk ("SrNm" is 6d 4e 72 53), so + * cand[].tag holds the un-reversed text. + */ + u8 *raw; + u32 raw_len; + struct { + char tag[5]; + u32 off; + u32 len; + bool known; + } cand[N31_SYSCFG_MAX_CAND]; + u32 n_cand; +}; + +struct whimory_sb { + u16 ce; + u16 cau; + u16 block; + u8 kind; + u64 weave; /* page 0 -- the OLDEST content in the superblock */ + u64 weave_max; /* newest content we have actually seen a weave for */ + u8 weave_max_p127; /* weave_max came from page 127, not page 0 */ +}; + +/* Compact CXT superblock identity; see whimory_cxt_index_build(). */ +struct whimory_cxt_sb_id { + u16 ce; + u16 cau; + u16 block; +}; + +struct whimory_sftl { + u32 vba_factor_a; + u32 vba_factor_b; + u32 nodepool_bytes; + u32 vbas_per_page; + u32 vbas_per_sb; + u32 pages_per_sb; + u32 num_sb; + u32 user_blocks; + u8 *btoc_page; + u8 *data_page; + u8 *meta_page; + /* + * Last NAND page read, cached across calls. + * + * A NAND page is 16 KiB and an LBA is 4 KiB, so four consecutive + * LBAs live in one page. The block layer hands this driver one 4 KiB + * LBA at a time, and n31_vfl_read_vba() reads a whole page to copy + * 4 KiB out of it -- so a sequential read pulls the SAME page off the + * media four times. Measured before this: 520 KB/s and 7.7 ms per + * 4 KiB, on a device where a directory scan is hundreds of reads and + * playback has 341 ms of buffer. + * + * The media is read-only here (whimory_submit_bio_range rejects + * writes outright), so nothing can invalidate this behind our back + * and the cache needs no coherence beyond being dropped on teardown. + */ + u8 *page_cache; + u8 page_cache_spare[S5L8740_NAND_META_SIZE]; + u32 pc_ce, pc_cau, pc_pblock, pc_page; + bool page_cache_valid; + struct s5l8740_cs_page *cs_page; /* CS span4 scratch for recover/read */ + + /* + * Batched page-0 prefetch window for the classify scan. pf_data is + * 16 * 4 KiB and pf_meta 16 * 16 B, allocated at recover and freed + * with it -- the scan is the only thing that reads whole ranges of + * page 0 in order, so nothing outside it needs the window. + */ + u8 *pf_data; + u8 *pf_meta; + unsigned int pf_ce; + unsigned int pf_cau; + unsigned int pf_first; + unsigned int pf_count; + unsigned int pf_kicks; + unsigned int pf_hits; + bool pf_valid; + bool pf_failed; + bool pf_checked; + + /* + * Read-ahead window: S5L8740_NAND_PAGE_BATCH_MAX consecutive physical + * pages of one block, filled by a single batched kick. + * + * This supersedes the one-page cross-call cache for sequential reads. + * Four LBAs share a 16 KiB page, so the old cache turned four block + * layer calls into one NAND read; the window turns thirty-two into + * one, which is a whole 128 KiB readahead request per kick. + */ + u8 *rc_data; + u8 *rc_meta; + u8 *rc_stage; + u8 *rc_stage_meta; + struct { + unsigned int ce; + unsigned int cau; + unsigned int pblock; + unsigned int page; + bool valid; + } rc_key[WHIMORY_RC_SLOTS]; + unsigned int rc_count; + unsigned int rc_fills; + unsigned int rc_hits; + unsigned int rc_misses; + unsigned int rc_fails; + unsigned int scan_kicks; + + /* + * Which (ce, cau) banks each virtual block actually spans. + * + * A superblock is not always all four banks. The VFL keeps a bitmap + * of the banks that carry a given VBN -- s_vfl.c sub_3D1438 counts + * the set bits of one stride-sized row per VBN -- and every address + * the FTL builds is dense over *those* banks: + * + * vba = (vbn * max_banks * pages_per_sb + * + page * nbanks + bank_ofs) * vbas_per_page + slot + * + * so a superblock holds nbanks * pages_per_sb * vbas_per_page + * addresses (sub_4EFE0C), not the full four banks' worth. Decoding a + * three-bank superblock as four puts the read on the wrong page of + * the wrong bank, which is the "sftl lba mismatch" against a page + * written long before the checkpoint. + * + * One byte per virtual block, bit b set for bank b = ce * num_cau + + * cau. Built during classify from which banks carry a real record at + * page 0; an all-zero entry means "not classified", and the decode + * falls back to all banks so an unscanned region behaves as before. + */ + u8 *sb_bank_mask; + u32 sb_bank_blocks; /* entries allocated */ + u32 sb_bank_known; /* vblocks with at least one bank */ + u32 sb_bank_partial; /* vblocks with fewer banks than the maximum */ + u32 sb_bank_hist[S5L8740_NAND_MAX_CE * S5L8740_NAND_MAX_CAU + 1]; + u32 sb_bank_overflow; /* CXT offsets past the derived superblock end */ + u32 sb_bank_conflicts; /* members the VFL bad-block table calls bad */ + + struct whimory_sb *sbs; + u32 mapped_roots; + u32 mapped_lbas; + u32 btoc_sbs; + u32 open_sbs; + u32 empty_sbs; + u32 fast_empty_hits; /* settled by the one-record probe */ + u32 fast_slot0_hits; /* non-empty blocks settled from slot 0 */ + u32 cxt_sbs; + u32 btoc_recs; + u32 range_nodes; + u32 cxt_bases; + u32 token_hole; + u32 token_list; + u32 token_list_applied; + u32 max_pages_per_btoc; + u32 gc_zone_size; + u8 *gc_data; + u8 *gc_meta; + u32 *btoc_lba[WHIMORY_BTOC_OPEN]; + u32 *btoc_map; /* per-VBA LBA decoded from one BTOC page */ + bool cxt_loaded; + bool packed_ok; + u32 cxt_blocks_seen; + u32 cxt_records_seen; + u32 cxt_l2v_updates; + u32 cxt_hole_entries; + u32 cxt_xlate_fail; + /* CXT extents checked against the page's own metadata before use. */ + u32 cxt_meta_confirmed; + u32 cxt_meta_mismatch; + u32 cxt_confirm_pages; + u32 cxt_confirm_unreadable; + /* Extents a stale checkpoint lost, rebuilt from page metadata. */ + u32 cxt_repair_pages; + u32 cxt_repair_slots; + u32 diff_replayed_sbs; + u32 diff_skipped_sbs; + u32 diff_open_kept; /* open SBs the page-0 weave would have skipped */ + u32 open_truncated; /* open SBs dropped by max_open_sbs */ + u32 weave_newer; /* SBs at or after the checkpoint */ + u32 weave_older; /* SBs the checkpoint provably covers */ + u32 weave_none; /* SBs with no usable weave at all */ + u32 open_pages_read; /* pages read rebuilding open SBs */ + u32 cxt_hole_lbas; /* logical space the holes cover */ + u32 cxt_ext_nospc; /* extents dropped, table full */ + u32 cxt_records_lost; /* records after an aborted parse */ + u32 cxt_sb_empty; /* CXT SBs that yielded nothing */ + u32 btoc_blank; /* BTOC pages that were erased */ + u32 btoc_be_bte; /* claimed by the big-endian BTE parser */ + u32 btoc_be_lpn; /* claimed by the big-endian LPN parser */ + u32 btoc_le_bte; /* claimed by the little-endian BTE parser */ + u32 btoc_le_bte_hdr8; /* same, after an 8-byte page header */ + u32 btoc_unclaimed; /* no parser recognised the page */ + u32 btoc_fb_sbs; /* closed SBs rebuilt from per-page meta */ + u32 btoc_fb_pages; /* pages that cost */ + u32 btoc_fb_hits; /* mappings it recovered */ + u32 unk_fb_sbs; /* unclassified SBs rebuilt from meta */ + u32 unk_fb_pages; + u32 unk_fb_hits; + u32 bad_map_logged; /* bounded explanations for bad reads */ + u32 cxt_dumped; /* pairs printed by the record dump */ + u32 cxt_hdr_skipped; /* records whose header said nothing follows */ + u32 cxt_hdr_bad; /* records whose header was not a CONTIG marker */ + u32 cxt_resyncs; /* TREE records that re-anchored the LBA cursor */ + u32 btoc_pages_read; + u32 btoc_pages_valid; + u32 btoc_entries_seen; + u32 btoc_l2v_updates; + u32 btoc_token_ffff0000; + u32 btoc_token_ffffff00; + u32 btoc_token_ffffffff; + u32 btoc_holelist_ffff0001; + /* Classified BTOC/open entry counters (recovery observability). */ + u32 btoc_unmap_entries; /* LIST tokens applied or attempted */ + u32 btoc_hole_entries; /* HOLE / erased physical spans */ + u32 btoc_unknown_entries; + u32 open_unmap_entries; + u32 open_skipped_zero; + u32 open_overrides_closed; + u32 open_rejected_stale; + u32 open_unknown_order; + u32 stale_mapping_rejected; + u32 btoc_meta_mismatch; + u32 btoc_meta_confirmed; + u32 btoc_skipped_zero; + u32 btoc_confirm_pages; + u32 btoc_confirm_capped; + u32 btoc_confirm_budget_stop; + u32 string_hit_itunesdb; + u32 string_hit_f00; + u32 string_hit_apps; + u32 string_hit_mp3; + u32 string_hit_m4a; + u32 string_hit_ipod_control; + u32 string_hit_music; + u32 open_slots_seen; + u32 open_slots_valid_meta; + u32 open_l2v_updates; + u32 l2v_update_calls; + u32 l2v_unmap_calls; + u32 l2v_repack_roots; + u32 meta0_hits; + u32 btoc_dumps_left; + u32 unknown_sbs; + u64 claim_weave; + /* 0=none, 1=BTOC/closed, 2=open, 3=CXT, 4=LIST-unmap */ + u8 claim_source; + unsigned long confirm_start_jiffies; + /* Recover-time replay accounting (widen staging / OOM guard). */ + u32 range_budget_stop; + u32 btoc_verified; + u32 map_gen; /* bumped on every map mutation */ + u32 search_cache_hits; + u32 search_cache_misses; + struct whimory_cxt_sb_id cxt_idx[WHIMORY_CXT_MAX_SB]; + unsigned int n_cxt_idx; +}; + +struct whimory_cxt_base { + u32 sb; + u64 weave; +}; + +/* + * One contiguous (lba, span) -> vba mapping decoded from a CXT TREE record. + * Phase 3 keeps these in a candidate map, separate from the live interval + * map, so the CXT decode can be validated without disturbing a working disk. + */ +/* + * weave is the checkpoint generation this extent came from, and it is not + * optional bookkeeping. + * + * The candidate map is the union of every CXT superblock, and several + * generations describe the same logical ranges. Without a per-extent weave + * the seed had nothing to arbitrate with: it stamped every extent with one + * value, so the newest-wins rule in whimory_range_update() could never fire + * between two seed extents and whichever entry the sort placed last won. + * sort() is heapsort and unstable, so that was decided by the heap, and the + * map came out different on every boot from identical flash. + */ +struct whimory_cxt_extent { + u32 lba; + u32 span; + u32 vba; + u64 weave; +}; + +struct whimory_fpart_ops { + u32 major; + u32 (*minor)(struct whimory *w); + int (*init)(struct whimory *w); + int (*read_special)(struct whimory *w, u32 type, u8 *buf, size_t len); + int (*read_signature)(struct whimory *w, u8 *buf, size_t len); +}; + +struct whimory_vfl_ops { + u32 major; + u32 (*minor)(struct whimory *w); + int (*init)(struct whimory *w); + int (*open)(struct whimory *w); + u32 (*get_param)(struct whimory *w, u32 selector); + int (*read_vba)(struct whimory *w, u32 vba, u32 count, + void *data, struct whimory_meta *meta); +}; + +struct whimory_ftl_ops { + u32 major; + u32 (*minor)(struct whimory *w); + int (*init)(struct whimory *w); + int (*open)(struct whimory *w); + int (*read_lba)(struct whimory *w, u32 lba, void *buf, bool allow_blank); +}; + +struct whimory { + struct device *dev; + struct whimory_geometry geom; + struct whimory_signature sig; + struct whimory_l2v l2v; + struct whimory_vfl vfl; + struct whimory_syscfg syscfg; + struct whimory_sftl sftl; + struct rb_root ranges; + struct whimory_fpart fpart_ctx; + const struct whimory_fpart_ops *fpart; + const struct whimory_vfl_ops *vfl_ops; + const struct whimory_ftl_ops *ftl; + u64 total_4k_sectors; + u8 *bounce; + struct mutex bounce_lock; + struct mutex tree_lock; + struct gendisk *disk; + struct gendisk *ipod_disk; + struct platform_device *pdev; + u32 lba0_vba; + u32 bad_vba; /* what the map answered for a failing read */ + u32 bad_span; + u64 cxt_base_weave; + /* + * The weave the checkpoint's own pages ran out to. + * + * s_cxt.c:81 sets sftl.write.weaveSeq to + * + * cxt->load.baseWeaveSeq + cxt->save.num_sb * s_g_vbas_per_sb + 1 + * + * before the diff runs, so every write after the checkpoint carries a + * weave at or above this. Weaves between the base and here belong to + * the checkpoint superblocks themselves, not to post-checkpoint user + * data, and the skip rule has to draw its line here rather than at + * the base. + */ + u64 cxt_top_weave; + u32 cxt_save_num_sb; /* first word of the BASE record payload */ + struct whimory_cxt_extent *cxt_ext; /* candidate map (Phase 3) */ + u32 n_cxt_ext; + u32 max_cxt_ext; + /* Longest span in cxt_ext; bounds the backward walk in cxt_lookup. */ + u32 cxt_ext_max_span; + u64 cxt_ext_weave; /* base weave it came from */ + u32 cxt_ext_sb; + struct whimory_cxt_base cxt[WHIMORY_CXT_MAX_SB]; + u32 n_cxt; + u32 cxt_next_lba; + bool cxt_lba_valid; + bool fil_ok; + bool sig_ok; + bool vfl_ok; + bool ftl_ok; + bool l2v_ok; + bool lba0_ok; + bool oracle_used; + bool l2v_defer_pack; /* pack once after replay, not per update */ + unsigned long progress_jiffies; /* rate-limits recover progress */ + /* + * Machine-readable mirror of the recover progress lines. The dev_info + * output suits a human reading a console; a progress bar needs the + * numbers without parsing prose, so the same call sites publish here. + */ + const char *prog_phase; + unsigned int prog_cur; + unsigned int prog_total; + /* L2V_Search sequential hint; see whimory_l2v_search(). */ + u32 search_start; + u32 search_len; + u32 search_vba; + u32 search_gen; + bool search_valid; + char status[512]; +}; + +static inline u32 whimory_sig32(const u8 *sig, unsigned int off) +{ + return get_unaligned_le32(sig + off); +} + +const char *whimory_recovery_state_name(void); + +#endif /* WHIMORY_S5L8740_H */ diff --git a/drivers/phy/samsung/phy-s5l8702-usb2.c b/drivers/phy/samsung/phy-s5l8702-usb2.c index 2c2056e61041d7..cedcbf228e3cf7 100644 --- a/drivers/phy/samsung/phy-s5l8702-usb2.c +++ b/drivers/phy/samsung/phy-s5l8702-usb2.c @@ -1,30 +1,51 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Apple/Samsung S5L8702 USB OTG PHY. + * Apple/Samsung S5L8702 / S5L87xx USB OTG PHY. + * + * S5L8702 (Nano 3G) uses the analog stage ramp sequence. + * S5L8723/8740 (Nano 6G/7G) use the shorter Freemyipod/U-Boot + * s5l87xx sequence — N31 must NOT run the 8702 dance or the + * Lightning-facing link dies while DWC2 still loads. */ #include #include +#include +#include #include #include #include #include #include +#include + +enum s5l_usbphy_kind { + S5L_USBPHY_8702 = 0, + S5L_USBPHY_87XX = 1, +}; struct s5l8702_usbphy { struct device *dev; struct phy *phy; void __iomem *base; + enum s5l_usbphy_kind kind; }; #define S5L8702_OTGPHY_PWR 0x000 #define S5L8702_OTGPHY_CLK 0x004 #define S5L8702_OTGPHY_RSTCON 0x008 #define S5L8702_OTGPHY_BIAS 0x018 +#define S5L8702_OTGPHY_MODE 0x01c /* s5l87xx unkcon */ #define S5L8702_OTGPHY_INTFCON 0x030 #define S5L8702_OTGPHY_CTRL1 0x040 #define S5L8702_OTGPHY_CTRL2 0x044 #define S5L8702_OTGPHY_ENABLE 0x100 +/* DWC2 PCGCCTL — clear USB suspend before PHY on (U-Boot does this) */ +#define S5L87XX_OTG_PCGCCTL 0x38400e00 +/* DWC2 DCTL — drop U-Boot D+ before PHY reset or Windows sees 0000:0002 */ +#define S5L87XX_OTG_DCTL 0x38400804 +#define S5L87XX_DCTL_SFTDISCON BIT(1) + static int s5l8702_usbphy_phy_init(struct phy *phy) { return 0; @@ -35,9 +56,76 @@ static int s5l8702_usbphy_phy_exit(struct phy *phy) return 0; } -static int s5l8702_usbphy_phy_power_on(struct phy *phy) +static void s5l87xx_clear_suspend(struct s5l8702_usbphy *usbphy) +{ + void __iomem *pcgc; + + pcgc = ioremap(S5L87XX_OTG_PCGCCTL, 4); + if (!pcgc) { + dev_warn(usbphy->dev, "PCGCCTL ioremap failed\n"); + return; + } + writel(0, pcgc); + iounmap(pcgc); +} + +static void s5l87xx_soft_disconnect(struct s5l8702_usbphy *usbphy) +{ + void __iomem *dctl; + u32 val; + + dctl = ioremap(S5L87XX_OTG_DCTL, 4); + if (!dctl) { + dev_warn(usbphy->dev, "DCTL ioremap failed\n"); + return; + } + val = readl(dctl) | S5L87XX_DCTL_SFTDISCON; + writel(val, dctl); + iounmap(dctl); +} + +/* + * Matches upstream/u-boot arch/arm/mach-s5l87xx/s5l87xx-otg-phy.c + * and is the sequence that already proves DFU gadget on N31. + */ +static int s5l87xx_usbphy_power_on(struct s5l8702_usbphy *usbphy) +{ + void __iomem *b = usbphy->base; + + /* U-Boot DFU leaves D+ pulled up. Drop it before PHY reset. */ + s5l87xx_soft_disconnect(usbphy); + s5l87xx_clear_suspend(usbphy); + mdelay(10); + + writel(0, b + S5L8702_OTGPHY_PWR); + mdelay(10); + writel(1, b + S5L8702_OTGPHY_RSTCON); + mdelay(10); + writel(0, b + S5L8702_OTGPHY_RSTCON); + mdelay(10); + writel(6, b + S5L8702_OTGPHY_MODE); + writel(1, b + S5L8702_OTGPHY_CLK); /* con @ +0x04 */ + /* U-Boot waits ~400ms for PLL lock */ + mdelay(400); + + dev_info(usbphy->dev, "s5l87xx OTG PHY on (N31/N20 path)\n"); + return 0; +} + +static int s5l87xx_usbphy_power_off(struct s5l8702_usbphy *usbphy) +{ + void __iomem *b = usbphy->base; + + writel(0xff, b + S5L8702_OTGPHY_PWR); + mdelay(10); + writel(0xff, b + S5L8702_OTGPHY_RSTCON); + mdelay(10); + writel(4, b + S5L8702_OTGPHY_MODE); + return 0; +} + +static int s5l8702_usbphy_power_on_legacy(struct s5l8702_usbphy *usbphy) { - struct s5l8702_usbphy *usbphy = phy_get_drvdata(phy); void __iomem *b = usbphy->base; writel(0x000, b + S5L8702_OTGPHY_PWR); @@ -45,13 +133,11 @@ static int s5l8702_usbphy_phy_power_on(struct phy *phy) writel(0x400, b + S5L8702_OTGPHY_BIAS); writel(0x007, b + S5L8702_OTGPHY_RSTCON); - /* Analog stage 1 ramp */ writel(0x300, b + S5L8702_OTGPHY_CTRL1); writel(0x340, b + S5L8702_OTGPHY_CTRL1); writel(0x346, b + S5L8702_OTGPHY_CTRL1); writel(0x347, b + S5L8702_OTGPHY_CTRL1); - /* Analog stage 2 ramp */ writel(0x0c00, b + S5L8702_OTGPHY_CTRL2); writel(0x0fc0, b + S5L8702_OTGPHY_CTRL2); writel(0x0fe0, b + S5L8702_OTGPHY_CTRL2); @@ -65,14 +151,12 @@ static int s5l8702_usbphy_phy_power_on(struct phy *phy) writel(0x000, b + S5L8702_OTGPHY_INTFCON); writel(0x000, b + S5L8702_OTGPHY_BIAS); - /* Let the PLL lock before the controller starts poking the core. */ mdelay(40); return 0; } -static int s5l8702_usbphy_phy_power_off(struct phy *phy) +static int s5l8702_usbphy_power_off_legacy(struct s5l8702_usbphy *usbphy) { - struct s5l8702_usbphy *usbphy = phy_get_drvdata(phy); void __iomem *b = usbphy->base; writel(0x0, b + S5L8702_OTGPHY_CTRL2); @@ -82,6 +166,24 @@ static int s5l8702_usbphy_phy_power_off(struct phy *phy) return 0; } +static int s5l8702_usbphy_phy_power_on(struct phy *phy) +{ + struct s5l8702_usbphy *usbphy = phy_get_drvdata(phy); + + if (usbphy->kind == S5L_USBPHY_87XX) + return s5l87xx_usbphy_power_on(usbphy); + return s5l8702_usbphy_power_on_legacy(usbphy); +} + +static int s5l8702_usbphy_phy_power_off(struct phy *phy) +{ + struct s5l8702_usbphy *usbphy = phy_get_drvdata(phy); + + if (usbphy->kind == S5L_USBPHY_87XX) + return s5l87xx_usbphy_power_off(usbphy); + return s5l8702_usbphy_power_off_legacy(usbphy); +} + static const struct phy_ops s5l8702_usbphy_phy_ops = { .init = s5l8702_usbphy_phy_init, .exit = s5l8702_usbphy_phy_exit, @@ -95,6 +197,7 @@ static int s5l8702_usbphy_probe(struct platform_device *pdev) struct s5l8702_usbphy *usbphy; struct phy_provider *phy_provider; struct device *dev = &pdev->dev; + const struct of_device_id *match; int ret; usbphy = devm_kzalloc(dev, sizeof(*usbphy), GFP_KERNEL); @@ -103,6 +206,12 @@ static int s5l8702_usbphy_probe(struct platform_device *pdev) usbphy->dev = dev; dev_set_drvdata(dev, usbphy); + match = of_match_device(dev->driver->of_match_table, dev); + if (match && match->data) + usbphy->kind = (uintptr_t)match->data; + else + usbphy->kind = S5L_USBPHY_8702; + usbphy->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(usbphy->base)) return PTR_ERR(usbphy->base); @@ -118,6 +227,9 @@ static int s5l8702_usbphy_probe(struct platform_device *pdev) phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + dev_info(dev, "USB OTG PHY kind=%s\n", + usbphy->kind == S5L_USBPHY_87XX ? "s5l87xx" : "s5l8702"); + return PTR_ERR_OR_ZERO(phy_provider); } @@ -126,7 +238,12 @@ static void s5l8702_usbphy_remove(struct platform_device *pdev) } static const struct of_device_id s5l8702_usbphy_of_match[] = { - { .compatible = "apple,s5l8702-otgphy", }, + { .compatible = "apple,s5l8702-otgphy", + .data = (void *)(uintptr_t)S5L_USBPHY_8702 }, + { .compatible = "apple,s5l87xx-otgphy", + .data = (void *)(uintptr_t)S5L_USBPHY_87XX }, + { .compatible = "apple,s5l8740-otgphy", + .data = (void *)(uintptr_t)S5L_USBPHY_87XX }, { }, }; MODULE_DEVICE_TABLE(of, s5l8702_usbphy_of_match); @@ -140,3 +257,6 @@ static struct platform_driver s5l8702_usbphy_driver = { } }; module_platform_driver(s5l8702_usbphy_driver); + +MODULE_DESCRIPTION("Apple/Samsung S5L8702/S5L87xx USB OTG PHY"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-s5l8702.c b/drivers/spi/spi-s5l8702.c old mode 100644 new mode 100755 index 6fd63e1c84ab68..f7d84bf738fc54 --- a/drivers/spi/spi-s5l8702.c +++ b/drivers/spi/spi-s5l8702.c @@ -1,159 +1,856 @@ // SPDX-License-Identifier: GPL-2.0 /* - * SPI controller driver for Samsung/Apple S5L8702 - * (iPod nano 3rd generation) + * SPI controller for Samsung/Apple S5L8702 / S5L8740 * - * Ported from Rockbox's spi-s5l8702.c by Michael Sparmann. + * RetailOS PIO (sub_4043D0) is shared by SPI0/CS42 and SPI2/Grape: + * CS = SPIPIN bit1 (4045D4(n): assert=clear, idle=set) + * flush STATUS & 0x7C0 / 0xF800 == 0 + * per-byte: wait !0x7C0, TXDATA, wait 0xF800, RXDATA + * SETUP 0x402C|0x10, CLKDIV 4 + * + * Do not remux SPI0 pads 0–3 (SEC leftover 4/2/2/2). OSOS never GPIOCMDs + * those pads. Do not write 0x3CF00200 — CS42 CS is 4045D4(0), not GPIOCMD. + * + * "DMA" in 4043D0 is the SPI-controller block path (SETUP bit5 after + * 11B70), not PL080. No SPI0/SPI2 PL080 peri IDs in OSOS. sub_3914 + * only does STATUS |= 0x40003F. Grape/CS42 stay on this PIO loop; + * do not invent peri IDs. */ +#include +#include #include -#include #include #include #include #include +#include + +#define SPICTRL 0x00 +#define SPISETUP 0x04 +#define SPISTATUS 0x08 +#define SPIPIN 0x0c +#define SPITXDATA 0x10 +#define SPIRXDATA 0x20 +#define SPICLKDIV 0x30 +#define SPIRXLIMIT 0x34 +#define SPIUNK4C 0x4c /* 4043D0: write 1 after TXDATA */ -#define SPICTRL 0x00 -#define SPISETUP 0x04 -#define SPISTATUS 0x08 -#define SPIPIN 0x0c -#define SPITXDATA 0x10 -#define SPIRXDATA 0x20 -#define SPICLKDIV 0x30 -#define SPIRXLIMIT 0x34 - -// SPISTATUS: TX FIFO level in bits [8:4] (max 16 = full), -// RX FIFO level in bits [13:9]. -#define SPISTATUS_TXFULL 0x100 // TX FIFO level == 16 (full) +#define SPISTATUS_TXFULL 0x100 #define SPISTATUS_TXLVL_MASK 0x1f0 -#define SPISTATUS_RXLVL_MASK 0x3e00 // any RX data present +#define SPISTATUS_RXLVL_MASK 0x3e00 +#define SPISTATUS_TXBUSY_ROS 0x7c0 +#define SPISTATUS_RXRDY_ROS 0xf800 + +/* + * Two different status encodings have been seen on this engine. + * + * ROS TX busy 0x7C0 -> 0, RX ready 0xF800 nonzero + * CLASSIC TX level 0x1F0 -> 0, RX level 0x3E00 nonzero + * + * This driver used to wait on the ROS masks and then, on timeout, accept + * the CLASSIC masks as a fallback -- and vice versa for RX. That is a + * correctness hazard, not just a slow path: the two families' masks + * overlap (0x7C0 vs 0x1F0 share bits 6-8, 0xF800 vs 0x3E00 share bits + * 11-13), so the wrong family's test can read as satisfied while the + * transfer is still in flight. RXDATA is then sampled early and returns + * the previous FIFO contents. That does not look like noise: it repeats, + * which is exactly the shape of the 4f814f81 pattern seen on SPI2. + * + * So the encoding is now a per-instance property. In AUTO the first + * transfer polls both families in one loop and latches whichever + * genuinely satisfies first -- a measurement, made once, and logged. + * After that only the latched family is consulted and a timeout is a + * real timeout rather than a cue to try the other interpretation. + */ +enum s5l8702_spi_fam { + SPI_FAM_AUTO = 0, + SPI_FAM_ROS, + SPI_FAM_CLASSIC, +}; + +static int spi_family; +module_param(spi_family, int, 0644); +MODULE_PARM_DESC(spi_family, + "status encoding: 0=auto-latch per instance, 1=ROS (0x7C0/0xF800), 2=Classic (0x1F0/0x3E00)"); -// SPISETUP: bit 0 enables bulk-receive mode. #define SPISETUP_RXMODE BIT(0) -#define SPISETUP_INIT 0x10618 +/* + * Bits 6:5 select which transfer engine the port is configured for. + * + * sub_4043D0 opens with v10 = (SPISETUP & 0x60) == 0 and runs its polled + * byte loop only when v10 is set, so PIO runs if and only if these bits are + * clear. Before transferring it makes the register agree with the engine it + * picked: from a bits-set state it writes SPISETUP &= ~0x60 and takes the + * PIO arm; from a bits-clear state it writes SPISETUP |= 0x20 and takes the + * interrupt arm (SPIRXLIMIT / +0x4c counts, event 68). + * + * sub_11B70 leaves bit 5 SET at init -- SPISETUP_SPI0_11B70 is 0x403e -- so + * a driver that then runs the polled loop is using stock's PIO code with + * the register configured for the other engine. That is a state stock never + * occupies: the bootloader, which is polled-only, uses 0x401e. + * + * We keep the 0x403e init, because that is what sub_11B70 writes, and clear + * the field in the transfer path, because that is what sub_4043D0 does when + * it selects PIO. Stock does not restore the bits afterwards (its epilogue + * is only SETUP &= ~0x400001), so neither do we. + */ +#define SPISETUP_ENGINE_MASK 0x60u +/* + * SPISETUP_RETAILOS was 0x403c. That value is refuted: no path in either + * image produces it. Both sub_11B70 call sites pass mode 0x1A, which + * yields 0x403E, and the bootloader uses 0x401E on SPI0. + */ +#define SPISETUP_SPI0_11B70 0x403e /* 11B70(0,0x1A,0x2EE0,8) → 0x402E|0x10 */ + +/* + * Transfer word size. sub_11B70 always leaves this at 8-bit for both ports, + * so this field is ours to drive per transfer rather than something stock + * varies at setup time. + * + * Apple's own loaders do vary it mid-download: upstream apple_z2 sends the + * HBPP init payload with 8-bit words and every later firmware blob with + * 16-bit words. On a little-endian host a 16-bit word puts the high byte on + * the wire first, so the same byte array clocks out pair-swapped -- which is + * exactly the "pair-swapped payload bytes" signature our own HBPP14 notes + * record. That makes word width, not just baud rate, a candidate for a + * bootstrap/runtime mismatch. + */ +/* + * There is no runtime word-size control here. + * + * sub_11B70 builds SPISETUP from its mode argument and then ORs in the + * literal 0x4000 unconditionally at EA 0x08011C60, writes it at 0x08011C64, + * and ORs in 0x10 at 0x08011C78. Both of its call sites pass the SAME mode, + * 0x1A -- sub_11B70(2, 0x1A, 0x2EE0, 1) for SPI2 at EA 0x0801A600 and + * sub_11B70(0, 0x1A, 0x2EE0, 8) for SPI0 at EA 0x0807441A -- so both ports + * end up with the same SPISETUP. There are no other callers. + * + * Every other write to SPISETUP in either image preserves bits 13 and 14: + * the masks are ~0x60, ~0x1080, ~0x100, ~0x200000, ~1, 0xFFDFEE7F and + * 0xFFBFFFFE. Bit 13 is never set anywhere in either image. + * + * So this driver used to drive a field stock treats as a constant. The + * per-transfer word-size write is gone. Note also that 0x4000 under the + * bits-13:14 encoding this file used would decode as "32-bit", which + * contradicts the byte-at-a-time movement stock actually performs -- so + * either the field position was wrong or 0x4000 is not a word-size field at + * all. Which of those is true is NOT established from the image; what is + * established is that the bits never change. + */ -// SPICTRL: bits 3:2 reset FIFOs, bit 0 enables the controller. #define SPICTRL_RESET_FIFO 0xc #define SPICTRL_ENABLE 0x1 -// Fixed SoC peripheral addresses. -#define S5L8702_PCON0_PHYS 0x3cf00000UL -#define S5L8702_GPIOCMD_PHYS 0x3cf00200UL +#define SPISTATUS_KICK 0x400000 /* set after SETUP clear RXMODE */ + +#define SPIPIN_CS_BIT BIT(1) -// PWRCON(1) = 0x3C500048 + 4*1; clockgate 34 sits in PWRCON1, bit 2 +#define S5L8702_PCON0_PHYS 0x3cf00000UL +#define S5L8702_GPIOCMD_PHYS 0x3cf001e0UL #define S5L8702_PWRCON1_PHYS 0x3c50004cUL -#define PWRCON1_SPI0_BIT BIT(2) +#define S5L8702_PWRCON4_PHYS 0x3c50006cUL +#define PWRCON1_SPI0_BIT BIT(2) +#define PWRCON1_SPI2_BIT BIT(16) +#define PWRCON4_SPI0_2_BIT BIT(13) /* CLK_SPI0_2 / bring-up table */ +#define SPI_CLKDIV_DEFAULT 4 +#define SPI0_BASE_PHYS 0x3c300000UL +#define SPI2_BASE_PHYS 0x3d200000UL -// PCON0[15:0]: set pins 0-3 to SPI function (each 4 bits = 0x2) -#define PCON0_SPI_MASK 0xffffU -#define PCON0_SPI_FUNC 0x2222U +/* + * Spin budget per status wait. + * + * This was 500000, which is not a timeout so much as a way to make a stuck + * bus look like a dead device: every wait burns the full budget with + * preemption held off, and with dozens of register accesses in one codec + * bring-up the machine stops scheduling userspace entirely. The symptom is + * distinctive and misleading -- ping still answers because that is + * interrupt context, while ssh times out and the device reads as locked. + * More than one "lockup" chased tonight looked exactly like that. + * + * A transfer that has not progressed in a few milliseconds is not going to. + * Fail fast, report it, and let the caller decide. + * + * Do NOT cond_resched() in these loops. transfer_one can run in atomic + * context, and sleeping there is "BUG: scheduling while atomic" -- a panic, + * which with panic=-1 on the cmdline reboots before the message can be + * read. That was added here as a fix for CPU starvation and was itself the + * crash. Lowering the budget is the safe half of that idea; yielding is + * not, and the correct place to solve starvation is a smaller budget, not + * a sleep in a path that may not sleep. + */ +#define SPI_WAIT_GUARD 20000 -// GPIOCMD encoding for SPI0 CS: port 0, pin 0; 0xe = output-low, 0xf = output-high -#define GPIOCMD_SPI0_CS_ASSERT 0x0000eU -#define GPIOCMD_SPI0_CS_DEASSERT 0x0000fU +/* Verbose 11B70/CS setup spam off by default. */ +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Verbose S5L SPI controller logs (default N)"); -// Default clock divider: SPI clock = PClk / (div + 1) -#define SPI0_CLKDIV_DEFAULT 4 +#define spi_vinfo(dev, fmt, ...) \ + do { \ + if (verbose) \ + dev_info((dev), fmt, ##__VA_ARGS__); \ + else \ + dev_dbg((dev), fmt, ##__VA_ARGS__); \ + } while (0) -// Worst-case timeout for a single FIFO slot -#define SPI_POLL_TIMEOUT_US 100000 struct s5l8702_spi { void __iomem *base; - void __iomem *pcon0; + struct device *dev; void __iomem *gpiocmd; + void __iomem *gpio_base; void __iomem *pwrcon1; + struct clk_bulk_data *clks; + int num_clks; + bool spi0; + bool spi2_grape; + bool prepared; + int last_err; + u32 last_status; + enum s5l8702_spi_fam fam; + bool cs_held; /* CS left asserted by a cs_change transfer */ + unsigned int fam_latch_status; + unsigned int tx_timeouts; + unsigned int rx_timeouts; }; -static int s5l8702_spi_wait_rx(struct s5l8702_spi *sspi) { - u32 val; - return readl_poll_timeout(sspi->base + SPISTATUS, val, val & SPISTATUS_RXLVL_MASK, 0, SPI_POLL_TIMEOUT_US); +static void s5l8702_gpiocmd_func(struct s5l8702_spi *sspi, unsigned int gpio, u8 func) +{ + u32 bank = gpio >> 3; + u32 pin = gpio & 7; + void __iomem *b; + u32 dir; + + if (!sspi->gpiocmd) + return; + if (sspi->gpio_base) { + b = sspi->gpio_base + 32 * bank; + dir = readl(b + 0x14); + writel(dir | BIT(pin), b + 0x14); + } + writel((bank << 16) | (pin << 8) | func, sspi->gpiocmd); +} + +static void s5l8702_spi2_pinmux(struct s5l8702_spi *sspi) +{ + void __iomem *b; + u32 pin, punc; + + /* sub_20690(1): sub_23CD0(0x57, 0) clears PUNC (+0x10) on GPIO 87 */ + if (sspi->gpio_base) { + b = sspi->gpio_base + 32 * (0x57 >> 3); + pin = 0x57 & 7; + punc = readl(b + 0x10); + writel(punc & ~BIT(pin), b + 0x10); + } + s5l8702_gpiocmd_func(sspi, 0x57, 5); + s5l8702_gpiocmd_func(sspi, 0x58, 3); + s5l8702_gpiocmd_func(sspi, 0x59, 3); + s5l8702_gpiocmd_func(sspi, 0x5A, 3); +} + +/* OSOS sub_743A4: 43D38C(0,4) (1,2) (2,2) (3,2) then 11B70(0,0x1A,0x2EE0,8). */ +static void s5l8702_spi0_pinmux(struct s5l8702_spi *sspi) +{ + s5l8702_gpiocmd_func(sspi, 0, 4); + s5l8702_gpiocmd_func(sspi, 1, 2); + s5l8702_gpiocmd_func(sspi, 2, 2); + s5l8702_gpiocmd_func(sspi, 3, 2); +} + +/* + * sub_11B70(0, 0x1A, 0x2EE0, 8). Clock ids 4/5 via 43CFCC. + * Id 5 mux 0/5 is fixed 24000 (3D7A2C). 440A58(24000,1000)=24, + * 440A58(24000,0x2EE0)=2. Id 4 uses the same 24 k-unit if PLL field + * is the usual 24 MHz-class source. + */ +static void s5l8702_spi0_11b70(struct s5l8702_spi *sspi) +{ + const unsigned int a4 = 8; + const unsigned int clk_kunit = 24; + u32 dd = clk_kunit * a4; + u32 u3c = 3 * clk_kunit * (a4 + 1); + u32 clkdiv = 2; + + writel(0xf, sspi->base + SPISTATUS); + writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, + sspi->base + SPICTRL); + writel(10, sspi->base + 0x44); + writel(dd, sspi->base + 0x38); + writel(255, sspi->base + 0x40); + writel(u3c, sspi->base + 0x3c); + writel(clkdiv, sspi->base + SPICLKDIV); + writel(0x6, sspi->base + SPIPIN); + writel(SPISETUP_SPI0_11B70, sspi->base + SPISETUP); + writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, + sspi->base + SPICTRL); + writel(SPICTRL_ENABLE, sspi->base + SPICTRL); + sspi->prepared = true; + spi_vinfo(sspi->dev, + "SPI0 11B70 SETUP=0x%x CLKDIV=%u dd=%u u3c=%u u40=255 u44=10\n", + SPISETUP_SPI0_11B70, clkdiv, dd, u3c); +} + +/* + * sub_11B70(2, 0x1A, 0x2EE0, 1) — same engine as SPI0, a4=1. + * CLKDIV stays 2 (440A58(24000, 0x2EE0)). Do not use the generic + * CLKDIV=4 path; that left +0x38/+0x3c at reset and ping RX was junk. + */ +/* + * The RetailOS SPI2 oracle, captured with touch working, has +0x3c at + * 0x18c. The formula below yields 0x90 for a4=1, and it is not a matter + * of the wrong a4: solving 3*24*(a4+1) = 396 gives 4.5, so the + * derivation itself does not reproduce the hardware. +0x38 does match + * at 0x18, and this driver already records that leaving the pair at + * reset made ping RX junk, so the register affects receive behaviour. + * Overridable while that is being tested. + */ +static int spi2_u3c = 0x18c; +module_param(spi2_u3c, int, 0644); +MODULE_PARM_DESC(spi2_u3c, + "SPI2 +0x3c value (0 = computed 0x90, default 0x18c = stock)"); + +static struct s5l8702_spi *s5l8702_spi2_dev; + +static void s5l8702_spi2_11b70(struct s5l8702_spi *sspi) +{ + const unsigned int a4 = 1; + const unsigned int clk_kunit = 24; + u32 dd = clk_kunit * a4; + u32 u3c = spi2_u3c ? (u32)spi2_u3c : 3 * clk_kunit * (a4 + 1); + u32 clkdiv = 2; + + /* + * sub_11B70(2, 0x1A, 0x2EE0, 1) — mode 0x1A → SETUP 0x402E|0x10 + * = 0x403E. Do not use 0x403C (that is a different mode bit). + * OSOS does not write SPIPIN or STATUS here. + */ + writel(10, sspi->base + 0x44); + writel(dd, sspi->base + 0x38); + writel(255, sspi->base + 0x40); + writel(u3c, sspi->base + 0x3c); + writel(clkdiv, sspi->base + SPICLKDIV); + writel(SPISETUP_SPI0_11B70, sspi->base + SPISETUP); + writel(SPICTRL_ENABLE, sspi->base + SPICTRL); + sspi->prepared = true; + spi_vinfo(sspi->dev, + "SPI2 11B70 SETUP=0x%x CLKDIV=%u dd=%u u3c=%u (mode 0x1A)\n", + SPISETUP_SPI0_11B70, clkdiv, dd, u3c); +} + +/* + * Re-run the SPI2 engine setup. The stock firmware reapplies sub_11B70 + * after every pinmux enable, and the touch driver does the same on + * bring-up; routing it here keeps one owner for the divider and timing. + */ +void s5l8702_spi2_reinit(void) +{ + if (s5l8702_spi2_dev) + s5l8702_spi2_11b70(s5l8702_spi2_dev); +} +EXPORT_SYMBOL_GPL(s5l8702_spi2_reinit); + +static void s5l8702_spi_cs(struct s5l8702_spi *sspi, bool assert) +{ + u32 pin = readl(sspi->base + SPIPIN); + + if (assert) + pin &= ~SPIPIN_CS_BIT; + else + pin |= SPIPIN_CS_BIT; + writel(pin, sspi->base + SPIPIN); +} + +static const char *s5l8702_fam_name(enum s5l8702_spi_fam f) +{ + switch (f) { + case SPI_FAM_ROS: + return "ROS(0x7C0/0xF800)"; + case SPI_FAM_CLASSIC: + return "Classic(0x1F0/0x3E00)"; + default: + return "auto"; + } +} + +/* Latch the observed encoding once, and say so. */ +static void s5l8702_latch_fam(struct s5l8702_spi *sspi, + enum s5l8702_spi_fam f, u32 status) +{ + if (sspi->fam == f) + return; + sspi->fam = f; + sspi->fam_latch_status = status; + dev_info(sspi->dev, "status encoding latched: %s (SPISTATUS=0x%08x)\n", + s5l8702_fam_name(f), status); +} + +/* + * Wait for the transmit side to go idle. + * + * Only the latched family is consulted. While still AUTO both are polled in + * the same loop so the first genuine completion decides, rather than one + * family being given a full guard interval of head start. + */ +static int s5l8702_wait_tx_idle(struct s5l8702_spi *sspi) +{ + unsigned int guard = SPI_WAIT_GUARD; + u32 val = 0; + + while (guard--) { + val = readl(sspi->base + SPISTATUS); + + if (sspi->fam != SPI_FAM_CLASSIC && + (val & SPISTATUS_TXBUSY_ROS) == 0) { + s5l8702_latch_fam(sspi, SPI_FAM_ROS, val); + return 0; + } + if (sspi->fam != SPI_FAM_ROS && + (val & SPISTATUS_TXLVL_MASK) == 0) { + s5l8702_latch_fam(sspi, SPI_FAM_CLASSIC, val); + return 0; + } + cpu_relax(); + } + + sspi->tx_timeouts++; + sspi->last_status = val; + return -ETIMEDOUT; } -static int s5l8702_spi_wait_tx(struct s5l8702_spi *sspi) { - u32 val; - return readl_poll_timeout(sspi->base + SPISTATUS, val, (val & SPISTATUS_TXLVL_MASK) != SPISTATUS_TXFULL, 0, SPI_POLL_TIMEOUT_US); +/* Wait for received data to be available. Same latching rule as TX. */ +static int s5l8702_wait_rx_ready(struct s5l8702_spi *sspi) +{ + unsigned int guard = SPI_WAIT_GUARD; + u32 val = 0; + + while (guard--) { + val = readl(sspi->base + SPISTATUS); + + if (sspi->fam != SPI_FAM_CLASSIC && + (val & SPISTATUS_RXRDY_ROS)) { + s5l8702_latch_fam(sspi, SPI_FAM_ROS, val); + return 0; + } + if (sspi->fam != SPI_FAM_ROS && + (val & SPISTATUS_RXLVL_MASK)) { + s5l8702_latch_fam(sspi, SPI_FAM_CLASSIC, val); + return 0; + } + cpu_relax(); + } + + sspi->rx_timeouts++; + sspi->last_status = val; + return -ETIMEDOUT; } -static void s5l8702_spi_prepare(struct s5l8702_spi *sspi) { +/* + * Thin shims so existing call sites keep reading naturally. The mask + * argument now only selects which side is meant, not which encoding. + */ +static int s5l8702_wait_clear(struct s5l8702_spi *sspi, u32 mask) +{ + if (mask == SPISTATUS_TXBUSY_ROS || mask == SPISTATUS_TXLVL_MASK) + return s5l8702_wait_tx_idle(sspi); + + /* Anything else is a literal wait on the caller's own mask. */ + { + unsigned int guard = SPI_WAIT_GUARD; + + while (guard--) { + if ((readl(sspi->base + SPISTATUS) & mask) == 0) + return 0; + cpu_relax(); + } + } + return -ETIMEDOUT; +} + +static int s5l8702_wait_set(struct s5l8702_spi *sspi, u32 mask) +{ + if (mask == SPISTATUS_RXRDY_ROS || mask == SPISTATUS_RXLVL_MASK) + return s5l8702_wait_rx_ready(sspi); + + { + unsigned int guard = SPI_WAIT_GUARD; + + while (guard--) { + if (readl(sspi->base + SPISTATUS) & mask) + return 0; + cpu_relax(); + } + } + return -ETIMEDOUT; +} + +static void s5l8702_spi_hw_init(struct s5l8702_spi *sspi) +{ writel(0xf, sspi->base + SPISTATUS); - writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, sspi->base + SPICTRL); - writel(SPI0_CLKDIV_DEFAULT, sspi->base + SPICLKDIV); - writel(6, sspi->base + SPIPIN); - writel(SPISETUP_INIT, sspi->base + SPISETUP); - writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, sspi->base + SPICTRL); + writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, + sspi->base + SPICTRL); + writel(SPI_CLKDIV_DEFAULT, sspi->base + SPICLKDIV); + /* idle: CS deasserted (bit1 set), match prior SPIPIN=6 */ + writel(0x6, sspi->base + SPIPIN); + writel(SPISETUP_SPI0_11B70, sspi->base + SPISETUP); + writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, + sspi->base + SPICTRL); writel(SPICTRL_ENABLE, sspi->base + SPICTRL); + sspi->prepared = true; } -static void s5l8702_spi_set_cs(struct spi_device *spi, bool enable) { - struct s5l8702_spi *sspi = spi_controller_get_devdata(spi->controller); - writel(enable ? GPIOCMD_SPI0_CS_DEASSERT : GPIOCMD_SPI0_CS_ASSERT, sspi->gpiocmd); +/* Rockbox touch-nano7g: CLKDIV=4, SETUP 0x402C|0x10, CTRL=1. No SPIPIN. */ +static void s5l8702_spi2_hw_init(struct s5l8702_spi *sspi) +{ + writel(4, sspi->base + SPICLKDIV); + writel(0x402c, sspi->base + SPISETUP); + writel(SPISETUP_SPI0_11B70, sspi->base + SPISETUP); + writel(SPICTRL_ENABLE, sspi->base + SPICTRL); + sspi->prepared = true; } -static int s5l8702_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *msg) { - s5l8702_spi_prepare(spi_controller_get_devdata(ctlr)); +static int s5l8702_spi2_pio_one(struct s5l8702_spi *sspi, + const u8 *tx, u8 *rx, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + unsigned int guard = SPI_WAIT_GUARD; + u32 st; + int ret; + + writel(1, sspi->base + SPIRXLIMIT); + + /* + * TX-full is the one test that is genuinely encoding-specific + * here (0x100 is the full flag within the Classic level field), + * so keep it, but only while this instance is actually running + * the Classic encoding. Otherwise defer to the latched waits -- + * this loop previously asserted Classic unconditionally, which + * is how a ROS-encoded SPI2 ended up sampling RXDATA early. + */ + if (sspi->fam == SPI_FAM_CLASSIC) { + while (guard--) { + st = readl(sspi->base + SPISTATUS); + if ((st & SPISTATUS_TXFULL) == 0) + break; + cpu_relax(); + } + if (readl(sspi->base + SPISTATUS) & SPISTATUS_TXFULL) { + sspi->tx_timeouts++; + return -ETIMEDOUT; + } + } else { + ret = s5l8702_wait_tx_idle(sspi); + if (ret) + return ret; + } + + writel(tx ? tx[i] : 0xff, sspi->base + SPITXDATA); + + ret = s5l8702_wait_rx_ready(sspi); + if (ret) + return ret; + { + u8 b = (u8)readl(sspi->base + SPIRXDATA); + + if (rx) + rx[i] = b; + } + } return 0; } -static int s5l8702_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *xfer) { +static void s5l8702_spi_set_cs(struct spi_device *spi, bool enable) +{ + /* CS is owned by transfer_one (RetailOS 4045D4 order) */ + (void)spi; + (void)enable; +} + +static int s5l8702_spi_prepare_message(struct spi_controller *ctlr, + struct spi_message *msg) +{ struct s5l8702_spi *sspi = spi_controller_get_devdata(ctlr); - const u8 *tx = xfer->tx_buf; - u8 *rx = xfer->rx_buf; - unsigned int len = xfer->len; + + if (!sspi->prepared) + s5l8702_spi_hw_init(sspi); + return 0; +} + +/* + * One transfer's worth of PIO. + * + * @assert_cs: pull CS down before the preamble. False when a previous + * transfer already left it down. + * @deassert_cs: release CS when finished. False when the next transfer in + * this message must see the same selection. + * + * This used to assert and release unconditionally, which meant CS dropped + * between the transfers of a multi-transfer message. For a protocol that + * frames on chip select -- HBPP being the one that matters here -- that + * silently splits one frame into several, so a caller could not express a + * held-CS sequence through the SPI core at all and had to drive the + * registers itself. Honouring cs_change is what makes spi_sync() usable. + */ +/* + * Drive the SPISETUP word-size field for one transfer. + * + * Logged the first time each port changes width, because if a bootstrap ever + * starts working after a width change we want the boot log to say so rather + * than leaving it to be re-derived. + */ + +static int s5l8702_spi_pio_one(struct s5l8702_spi *sspi, + const u8 *tx, u8 *rx, unsigned int len, + bool assert_cs, bool deassert_cs) +{ unsigned int i; - int ret = 0; - - if (rx && !tx) { - // Pure receive: program SPIRXLIMIT and set RXMODE. - writel(len, sspi->base + SPIRXLIMIT); - writel(readl(sspi->base + SPISETUP) | SPISETUP_RXMODE, sspi->base + SPISETUP); - for (i = 0; i < len; i++) { - ret = s5l8702_spi_wait_rx(sspi); - if (ret) goto out_rxmode; - rx[i] = readl(sspi->base + SPIRXDATA); - } -out_rxmode: - writel(readl(sspi->base + SPISETUP) & ~SPISETUP_RXMODE, sspi->base + SPISETUP); + int ret; + + /* + * One byte per iteration, because that is what sub_4043D0 does. + * + * Its PIO arm reads the TX buffer with ldrb and writes SPITXDATA a + * byte at a time, sets SPIRXLIMIT (+0x34) and SPIUNK4C (+0x4c) to 1 + * on every single byte, and takes RX back one byte at a time from + * SPIRXDATA. Nothing in either image moves more than a byte per + * FIFO slot. + * + * This used to take a bits-per-word argument and stride two bytes + * at a time. The stride is gone rather than left unreachable. + */ + + if (assert_cs) + s5l8702_spi_cs(sspi, true); + sspi->cs_held = true; + + /* + * Select PIO the way sub_4043D0 does: make SPISETUP agree with the + * engine before any data moves. Without this we run the polled loop + * against a port still configured for the interrupt engine. + */ + if (readl(sspi->base + SPISETUP) & SPISETUP_ENGINE_MASK) + writel(readl(sspi->base + SPISETUP) & ~SPISETUP_ENGINE_MASK, + sspi->base + SPISETUP); + + /* sub_4043D0 preamble */ + writel(readl(sspi->base + SPICTRL) | SPICTRL_RESET_FIFO, + sspi->base + SPICTRL); + ret = s5l8702_wait_clear(sspi, SPISTATUS_TXBUSY_ROS); + /* After 11B70, STATUS bit6 (0x40) stays set; 0x7C0 never hits 0. */ + if (ret && (readl(sspi->base + SPISTATUS) & SPISTATUS_TXBUSY_ROS) == 0x40) + ret = 0; + if (ret) + goto out_cs; + ret = s5l8702_wait_clear(sspi, SPISTATUS_RXRDY_ROS); + if (ret) + goto out_cs; + + /* + * 4043D0: TX present → SETUP &= ~1, STATUS |= 0x400000. + * RX-only (tx==NULL) → SETUP |= 1, STATUS |= 1 (auto-clock). + */ + + if (tx) { + writel(readl(sspi->base + SPISETUP) & ~SPISETUP_RXMODE, + sspi->base + SPISETUP); + writel(readl(sspi->base + SPISTATUS) | SPISTATUS_KICK, + sspi->base + SPISTATUS); } else { - // TX or full-duplex. - for (i = 0; i < len; i++) { - writel(1, sspi->base + SPIRXLIMIT); - ret = s5l8702_spi_wait_tx(sspi); - if (ret) break; - writel(tx ? tx[i] : 0xff, sspi->base + SPITXDATA); - ret = s5l8702_spi_wait_rx(sspi); - if (ret) break; - if (rx) rx[i] = readl(sspi->base + SPIRXDATA); - else readl(sspi->base + SPIRXDATA); + writel(readl(sspi->base + SPISETUP) | SPISETUP_RXMODE, + sspi->base + SPISETUP); + writel(readl(sspi->base + SPISTATUS) | 1, + sspi->base + SPISTATUS); + } + + for (i = 0; i < len; i++) { + /* 4043D0: RXLIMIT=1 only when the call has an RX buffer */ + writel(rx ? 1 : 0, sspi->base + SPIRXLIMIT); + + ret = s5l8702_wait_clear(sspi, SPISTATUS_TXBUSY_ROS); + if (ret && (readl(sspi->base + SPISTATUS) & SPISTATUS_TXBUSY_ROS) == 0x40) + ret = 0; + if (ret) + break; + + if (tx) { + writel(tx[i], sspi->base + SPITXDATA); + /* 4043D0 PIO: *(base+0x4C) = 1 after each TX byte */ + writel(1, sspi->base + SPIUNK4C); + } + + if (rx) { + ret = s5l8702_wait_set(sspi, SPISTATUS_RXRDY_ROS); + if (ret) + break; + rx[i] = (u8)readl(sspi->base + SPIRXDATA); } } + ret = s5l8702_wait_clear(sspi, SPISTATUS_TXBUSY_ROS); + if (ret && (readl(sspi->base + SPISTATUS) & SPISTATUS_TXBUSY_ROS) == 0x40) + ret = 0; + + /* sub_4043D0 epilogue: SETUP &= ~0x400001 */ + writel(readl(sspi->base + SPISETUP) & ~0x400001u, + sspi->base + SPISETUP); +out_cs: + /* + * Always drop CS on error: leaving it asserted after a timeout would + * strand the bus for every later message. + */ + if (deassert_cs || ret) { + s5l8702_spi_cs(sspi, false); + sspi->cs_held = false; + } + if (ret) { + sspi->last_err = ret; + sspi->last_status = readl(sspi->base + SPISTATUS); + dev_err_ratelimited(sspi->dev, + "4043D0 timeout st=%08x setup=%08x ctrl=%08x pin=%08x dd=%08x u3c=%08x u40=%08x u44=%08x\n", + sspi->last_status, + readl(sspi->base + SPISETUP), + readl(sspi->base + SPICTRL), + readl(sspi->base + SPIPIN), + readl(sspi->base + 0x38), + readl(sspi->base + 0x3c), + readl(sspi->base + 0x40), + readl(sspi->base + 0x44)); + dev_err_ratelimited(sspi->dev, "4043D0 u4c=%08x\n", + readl(sspi->base + SPIUNK4C)); + } return ret; } -static int s5l8702_spi_probe(struct platform_device *pdev) { +/* + * Map the SPI core's cs_change rules onto the CS line. + * + * Within a message CS stays down between transfers; a transfer with + * cs_change set toggles it afterwards. On the final transfer the meaning + * inverts -- cs_change there means keep the device selected past the end + * of this message, which is how a caller holds one frame across several + * spi_sync() calls. + */ +static int s5l8702_spi_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct s5l8702_spi *sspi = spi_controller_get_devdata(ctlr); + struct spi_message *msg = ctlr->cur_msg; + bool last = true; + bool deassert; + + (void)spi; + + if (msg) + last = list_is_last(&xfer->transfer_list, &msg->transfers); + + if (last) + deassert = !xfer->cs_change; + else + deassert = xfer->cs_change; + + return s5l8702_spi_pio_one(sspi, xfer->tx_buf, xfer->rx_buf, + xfer->len, + !sspi->cs_held, deassert); +} + +static int s5l8702_spi_probe(struct platform_device *pdev) +{ struct spi_controller *ctlr; struct s5l8702_spi *sspi; + struct resource *res; int ret; ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*sspi)); - if (!ctlr) return -ENOMEM; + if (!ctlr) + return -ENOMEM; sspi = spi_controller_get_devdata(ctlr); + sspi->dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); sspi->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(sspi->base)) return PTR_ERR(sspi->base); + if (IS_ERR(sspi->base)) + return PTR_ERR(sspi->base); + + sspi->spi0 = res && res->start == SPI0_BASE_PHYS; + sspi->spi2_grape = res && res->start == SPI2_BASE_PHYS; - sspi->pcon0 = devm_ioremap(&pdev->dev, S5L8702_PCON0_PHYS, 4); - sspi->gpiocmd = devm_ioremap(&pdev->dev, S5L8702_GPIOCMD_PHYS, 4); - sspi->pwrcon1 = devm_ioremap(&pdev->dev, S5L8702_PWRCON1_PHYS, 4); - if (!sspi->pcon0 || !sspi->gpiocmd || !sspi->pwrcon1) return -ENOMEM; + /* + * Start in AUTO unless told otherwise, so each instance reports the + * encoding it actually uses instead of inheriting an assumption. The + * two instances have been seen to differ depending on which init path + * ran, which is precisely why this is per-instance and not global. + */ + switch (spi_family) { + case 1: + sspi->fam = SPI_FAM_ROS; + break; + case 2: + sspi->fam = SPI_FAM_CLASSIC; + break; + default: + sspi->fam = SPI_FAM_AUTO; + break; + } + + /* Optional DT clocks (CLK_SPI* / secondary); ignore -ENOENT */ + ret = devm_clk_bulk_get_all(&pdev->dev, &sspi->clks); + if (ret > 0) { + sspi->num_clks = ret; + ret = clk_bulk_prepare_enable(sspi->num_clks, sspi->clks); + if (ret) + dev_warn(&pdev->dev, "clk_bulk_prepare_enable failed: %d\n", ret); + else + spi_vinfo(&pdev->dev, "enabled %d SPI clockgate(s)\n", sspi->num_clks); + } else { + sspi->num_clks = 0; + } - // Route GPIO pins to SPI function - writel((readl(sspi->pcon0) & ~PCON0_SPI_MASK) | PCON0_SPI_FUNC, sspi->pcon0); + if (sspi->spi0) { + sspi->pwrcon1 = devm_ioremap(&pdev->dev, S5L8702_PWRCON1_PHYS, 4); + sspi->gpiocmd = devm_ioremap(&pdev->dev, S5L8702_GPIOCMD_PHYS, 4); + sspi->gpio_base = devm_ioremap(&pdev->dev, S5L8702_PCON0_PHYS, 0x400); + if (!sspi->pwrcon1 || !sspi->gpiocmd || !sspi->gpio_base) + return -ENOMEM; + writel(readl(sspi->pwrcon1) & ~PWRCON1_SPI0_BIT, sspi->pwrcon1); + { + void __iomem *pwrcon4 = ioremap(S5L8702_PWRCON4_PHYS, 4); - // Enable SPI0 clock gate - writel(readl(sspi->pwrcon1) & ~PWRCON1_SPI0_BIT, sspi->pwrcon1); + if (pwrcon4) { + writel(readl(pwrcon4) & ~PWRCON4_SPI0_2_BIT, pwrcon4); + iounmap(pwrcon4); + } + } + s5l8702_spi0_pinmux(sspi); + s5l8702_spi0_11b70(sspi); + spi_vinfo(&pdev->dev, "SPI0 CS42 4043D0 CS=SPIPIN.1 PWRCON1=%08x\n", + readl(sspi->pwrcon1)); + } else if (sspi->spi2_grape) { + void __iomem *pwrcon4; - // Deassert CS - writel(GPIOCMD_SPI0_CS_DEASSERT, sspi->gpiocmd); + sspi->pwrcon1 = devm_ioremap(&pdev->dev, S5L8702_PWRCON1_PHYS, 4); + sspi->gpiocmd = devm_ioremap(&pdev->dev, S5L8702_GPIOCMD_PHYS, 4); + sspi->gpio_base = devm_ioremap(&pdev->dev, S5L8702_PCON0_PHYS, 0x400); + if (!sspi->pwrcon1 || !sspi->gpiocmd || !sspi->gpio_base) + return -ENOMEM; + /* bit16 = 8702 SPI2; bit15 = 8720 SPI2 / I2S2 overlap — clear both */ + writel(readl(sspi->pwrcon1) & ~(PWRCON1_SPI2_BIT | BIT(15)), + sspi->pwrcon1); + pwrcon4 = ioremap(0x3c50006cUL, 4); + if (pwrcon4) { + writel(readl(pwrcon4) & ~BIT(15), pwrcon4); /* SPI2_2 */ + iounmap(pwrcon4); + } + spi_vinfo(&pdev->dev, "SPI2 PWRCON1=%08x (after ungate)\n", + readl(sspi->pwrcon1)); + s5l8702_spi2_pinmux(sspi); + s5l8702_spi2_11b70(sspi); + s5l8702_spi2_dev = sspi; + spi_vinfo(&pdev->dev, + "SPI2 Grape 4043D0 PIO (SETUP=0x%x CLKDIV=2 CS=SPIPIN.1 11B70)\n", + SPISETUP_SPI0_11B70); + } ctlr->dev.of_node = pdev->dev.of_node; ctlr->bus_num = pdev->id; @@ -166,15 +863,17 @@ static int s5l8702_spi_probe(struct platform_device *pdev) { platform_set_drvdata(pdev, ctlr); ret = devm_spi_register_controller(&pdev->dev, ctlr); - if (ret) dev_err(&pdev->dev, "failed to register SPI controller: %d\n", ret); + if (ret) + dev_err(&pdev->dev, "failed to register SPI controller: %d\n", ret); return ret; } static const struct of_device_id s5l8702_spi_of_match[] = { { .compatible = "apple,s5l8702-spi" }, + { .compatible = "samsung,s5l8702-spi" }, + { .compatible = "samsung,s5l8740-spi" }, {} }; - MODULE_DEVICE_TABLE(of, s5l8702_spi_of_match); static struct platform_driver s5l8702_spi_driver = { @@ -186,6 +885,5 @@ static struct platform_driver s5l8702_spi_driver = { }; module_platform_driver(s5l8702_spi_driver); -MODULE_DESCRIPTION("SPI controller driver for Samsung/Apple S5L8702"); -MODULE_AUTHOR("Tucker Osman "); +MODULE_DESCRIPTION("SPI controller driver for Samsung/Apple S5L87xx"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ef1df44ee1b2ea..5bcb20c0478611 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -356,6 +356,12 @@ static void dwc2_hsotg_init_fifo(struct dwc2_hsotg *hsotg) dwc2_writel(hsotg, hsotg->hw_params.total_fifo_size | addr << GDFIFOCFG_EPINFOBASE_SHIFT, GDFIFOCFG); + dev_info(hsotg->dev, + "s5l87xx hwfifo rx=%u np=%u d1=%u d2=%u d3=%u d4=%u d5=%u d6=%u\n", + hsotg->params.g_rx_fifo_size, hsotg->params.g_np_tx_fifo_size, + hsotg->params.g_tx_fifo_size[1], hsotg->params.g_tx_fifo_size[2], + hsotg->params.g_tx_fifo_size[3], hsotg->params.g_tx_fifo_size[4], + hsotg->params.g_tx_fifo_size[5], hsotg->params.g_tx_fifo_size[6]); /* * according to p428 of the design guide, we need to ensure that * all fifos are flushed before continuing @@ -4210,8 +4216,13 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep, val = (val >> FIFOSIZE_DEPTH_SHIFT) * 4; if (val < size) continue; - /* Search for smallest acceptable fifo */ - if (val < fifo_size) { + /* Notify (8 B) keeps a stub. HS bulk takes the largest. */ + if (size >= 64) { + if (!fifo_index || val > fifo_size) { + fifo_size = val; + fifo_index = i; + } + } else if (val < fifo_size) { fifo_size = val; fifo_index = i; } @@ -4227,6 +4238,9 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep, epctrl |= DXEPCTL_TXFNUM(fifo_index); hs_ep->fifo_index = fifo_index; hs_ep->fifo_size = fifo_size; + dev_info(hsotg->dev, + "s5l87xx ep%u in fifo=%u bytes=%u mp=%u\n", + index, fifo_index, fifo_size, size); } /* for non control endpoints, set PID to D0 */ diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index cc32d0ebea941a..9f03d4574e37c1 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -110,24 +110,154 @@ static void dwc2_set_s5l8702_params(struct dwc2_hsotg *hsotg) { struct dwc2_core_params *p = &hsotg->params; + /* Nano 3G (S5L8702) — lemonjesus bring-up. Do NOT share blindly with N20/N31. */ p->speed = DWC2_SPEED_PARAM_HIGH; p->otg_caps.hnp_support = true; p->otg_caps.srp_support = true; p->phy_utmi_width = 16; - // If we enable DMA, we get BULK packet corruption, eg. in the CDC EEM - // gadget, eg.: - // [ 5.530000] g_ether gadget.0: invalid EEM CRC - // I _think_ this is a DMA mechanism built into the DWC2 core, and not using - // the kernel DMAEngine, as that seems to be working fine (and this happens - // even if we have no DMA controllers). + /* DWC2 internal DMA corrupts BULK on this core (CDC EEM CRC). */ p->g_dma = false; - // The hardware expects USBRST/ENUMDONE/IEPInt/OEPInt/USBSUSP/WKUPINT/etc. - // masked until SessReqInt/ConIDStsChng signals session-valid. Without - // this gating, these bits fire during disconnect and the OTG state - // machine has no opportunity to make progress. + /* Gate device IRQs until SessReqInt/ConIDStsChng — Nano3-specific. */ p->session_valid_gintmsk_quirk = true; } +/* + * Nano 6G/7G (S5L872x/8740). Same g_dma caution; omit Nano3 GINTMSK gating + * until proven required (Slackware/INIT: Nano3 PHY/dwc2 deltas broke N7). + */ +static void dwc2_set_s5l87xx_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + struct dwc2_hw_params *hw = &hsotg->hw_params; + unsigned n_ep, n_fifo, dirs, ctrl, v1, v3, i, t; + unsigned total, rx, np, left, first, rest, share, stubs; + u32 hwcfg4; + + p->speed = DWC2_SPEED_PARAM_HIGH; + p->otg_caps.hnp_support = true; + p->otg_caps.srp_support = true; + p->phy_utmi_width = 16; + p->g_dma = true; + /* + * RetailOS sub_1B5254 writes GAHBCFG=0x2B (DMA+INCR8) and + * never sets DCFG_DESCDMA. Descriptor-DMA IN is a common + * DWC2 TX-only failure when the ROM used buffer DMA. + */ + p->g_dma_desc = false; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR8 << GAHBCFG_HBSTLEN_SHIFT; + /* + * N31 glass 2026-08-22: quirk-on left USBRST/EP0 masked until + * SessReqInt. That IRQ does not arrive after PHY reset, so Windows + * GET_DESCRIPTOR dies as VID_0000&PID_0002 Code 43. Nano3 gating + * stays on dwc2_set_s5l8702_params only. + */ + p->session_valid_gintmsk_quirk = false; + + total = hw->total_fifo_size; + dev_info(hsotg->dev, + "s5l87xx GHWCFG3[31:16] fifo_words=%u (total DFIFO)\n", + total); + dev_info(hsotg->dev, + "s5l87xx dwc2 num_dev_ep=%u perio_in=%u in_eps=%u dyn=%u ded=%u\n", + hw->num_dev_ep, hw->num_dev_perio_in_ep, hw->num_dev_in_eps, + hw->enable_dynamic_fifo, hw->en_multiple_tx_fifo); + + /* RetailOS sub_1B543A — only when dynamic FIFO is set. */ + if (!hw->enable_dynamic_fifo) + return; + + n_ep = hw->num_dev_ep; + if (n_ep + 1 > 9) + n_ep = 8; + dirs = hw->dev_ep_dirs; + v1 = 0; + v3 = 0; + for (i = 1; i < n_ep; i++) { + t = (dirs >> (2 * i)) & 3; + if (t == 0) { + v1++; + v3++; + } else if (t == 2) { + v1++; + } else if (t == 1) { + v3++; + } + } + + hwcfg4 = dwc2_readl(hsotg, GHWCFG4); + ctrl = (hwcfg4 & GHWCFG4_NUM_DEV_MODE_CTRL_EP_MASK) >> + GHWCFG4_NUM_DEV_MODE_CTRL_EP_SHIFT; + rx = 4 * ctrl + 2 * v1 + 272; + if (hw->rx_fifo_size && rx > hw->rx_fifo_size) + rx = hw->rx_fifo_size; + np = hw->en_multiple_tx_fifo ? 32 : 256; + if (rx + np > total) { + if (total > rx + 16) + np = total - rx - 16; + else + np = 16; + } + p->g_rx_fifo_size = rx; + p->g_np_tx_fifo_size = np; + /* check_params CHECK_RANGE uses these as max. */ + if (hw->rx_fifo_size < rx) + hw->rx_fifo_size = rx; + if (hw->dev_nperio_tx_fifo_size < np) + hw->dev_nperio_tx_fifo_size = np; + left = (total > rx + np) ? total - rx - np : 0; + + n_fifo = hw->num_dev_in_eps; + if (n_fifo < 1) + n_fifo = 1; + if (n_fifo > 15) + n_fifo = 15; + if (!v3) + v3 = n_fifo; + if (v3 > n_fifo) + v3 = n_fifo; + + stubs = 16 * (n_fifo - v3); + if (left > stubs) + left -= stubs; + else + stubs = 0; + + first = left; + if (first > 512) + first = 512; + /* HS bulk MP=512 B needs a dedicated FIFO >= 128 words. */ + if (first && first < 128) + first = (left >= 128) ? 128 : left; + memset(p->g_tx_fifo_size, 0, sizeof(p->g_tx_fifo_size)); + p->g_tx_fifo_size[1] = first ? first : 16; + rest = (left > first) ? left - first : 0; + if (v3 > 1) { + share = rest / (v3 - 1); + if (share < 128 && rest >= (unsigned)(128 * (v3 - 1))) + share = 128; + if (!share) + share = 16; + for (i = 2; i <= v3; i++) { + unsigned sz = (i == v3) ? rest : share; + + if (!sz) + sz = 16; + p->g_tx_fifo_size[i] = sz; + if (i != v3 && rest >= share) + rest -= share; + } + } + for (i = v3 + 1; i <= n_fifo; i++) + p->g_tx_fifo_size[i] = 16; + for (i = 1; i <= n_fifo; i++) + if (p->g_tx_fifo_size[i] > hw->g_tx_fifo_size[i]) + hw->g_tx_fifo_size[i] = p->g_tx_fifo_size[i]; + + dev_info(hsotg->dev, + "s5l87xx retailos fifo rx=%u np=%u in1=%u n=%u v1=%u v3=%u ctrl=%u\n", + rx, np, p->g_tx_fifo_size[1], n_fifo, v1, v3, ctrl); +} + static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg) { struct dwc2_core_params *p = &hsotg->params; @@ -344,8 +474,12 @@ const struct of_device_id dwc2_of_match_table[] = { { .compatible = "snps,dwc2" }, { .compatible = "samsung,s3c6400-hsotg", .data = dwc2_set_s3c6400_params }, - { .compatible = "apple,s5l87xx-usb", + { .compatible = "apple,s5l8702-usb", .data = dwc2_set_s5l8702_params }, + { .compatible = "apple,s5l87xx-usb", + .data = dwc2_set_s5l87xx_params }, + { .compatible = "apple,s5l8740-usb", + .data = dwc2_set_s5l87xx_params }, { .compatible = "amlogic,meson8-usb", .data = dwc2_set_amlogic_params }, { .compatible = "amlogic,meson8b-usb", diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index 7cec19d65fb534..c3775774a23bee 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -580,11 +580,19 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) DBG(cdev, "RNDIS RX/TX early activation ... \n"); net = gether_connect(&rndis->port); - if (IS_ERR(net)) + if (IS_ERR(net)) { + pr_info("s5l87xx gether_connect err=%ld\n", + PTR_ERR(net)); return PTR_ERR(net); + } + pr_info("s5l87xx gether_connect ok\n"); rndis_set_param_dev(rndis->params, net, &rndis->port.cdc_filter); + rndis_set_param_medium(rndis->params, RNDIS_MEDIUM_802_3, + gether_bitrate(cdev->gadget) / 100); + rndis_signal_connect(rndis->params); + pr_info("s5l87xx rndis set_alt signal_connect\n"); } else goto fail; @@ -680,6 +688,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_iad_descriptor.bFunctionClass = rndis_opts->class; rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass; rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol; + rndis_control_intf.bInterfaceClass = rndis_opts->class; + rndis_control_intf.bInterfaceSubClass = rndis_opts->subclass; + rndis_control_intf.bInterfaceProtocol = rndis_opts->protocol; /* * in drivers/usb/gadget/configfs.c:configfs_composite_bind() diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c index afd75d72412c9f..59992c823ce3c4 100644 --- a/drivers/usb/gadget/function/rndis.c +++ b/drivers/usb/gadget/function/rndis.c @@ -256,10 +256,7 @@ static int gen_ndis_query_resp(struct rndis_params *params, u32 OID, u8 *buf, case RNDIS_OID_GEN_LINK_SPEED: if (rndis_debug > 1) pr_debug("%s: RNDIS_OID_GEN_LINK_SPEED\n", __func__); - if (params->media_state == RNDIS_MEDIA_STATE_DISCONNECTED) - *outbuf = cpu_to_le32(0); - else - *outbuf = cpu_to_le32(params->speed); + *outbuf = cpu_to_le32(params->speed); retval = 0; break; @@ -325,7 +322,7 @@ static int gen_ndis_query_resp(struct rndis_params *params, u32 OID, u8 *buf, case RNDIS_OID_GEN_MEDIA_CONNECT_STATUS: if (rndis_debug > 1) pr_debug("%s: RNDIS_OID_GEN_MEDIA_CONNECT_STATUS\n", __func__); - *outbuf = cpu_to_le32(params->media_state); + *outbuf = cpu_to_le32(RNDIS_MEDIA_STATE_CONNECTED); retval = 0; break; @@ -514,6 +511,8 @@ static int gen_ndis_set_resp(struct rndis_params *params, u32 OID, * MULTICAST, ALL_MULTICAST, BROADCAST */ *params->filter = (u16)get_unaligned_le32(buf); + pr_info("s5l87xx rndis filter ndis=0x%x\n", + *params->filter); pr_debug("%s: RNDIS_OID_GEN_CURRENT_PACKET_FILTER %08x\n", __func__, *params->filter); @@ -813,8 +812,17 @@ int rndis_msg_parser(struct rndis_params *params, u8 *buf) case RNDIS_MSG_INIT: pr_debug("%s: RNDIS_MSG_INIT\n", __func__); - params->state = RNDIS_INITIALIZED; - return rndis_init_response(params, (rndis_init_msg_type *)buf); + { + int ret; + + params->state = RNDIS_INITIALIZED; + params->media_state = RNDIS_MEDIA_STATE_CONNECTED; + ret = rndis_init_response(params, + (rndis_init_msg_type *)buf); + rndis_signal_connect(params); + pr_info("s5l87xx rndis INIT connected (tx after SET)\n"); + return ret; + } case RNDIS_MSG_HALT: pr_debug("%s: RNDIS_MSG_HALT\n", diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 6497c4e81e951a..afbad6a01eb0cc 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -343,3 +343,4 @@ config USB_ONBOARD_DEV_USB5744 during hub start-up configuration stage. It is must to enable this option on AMD Kria KR260 Robotics Starter Kit as this hub is connected to USB-SD converter which mounts the root filesystem. + diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 3614a5d29c716e..f8abafd4941fa5 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -493,3 +493,9 @@ config BACKLIGHT_LED endif # BACKLIGHT_CLASS_DEVICE endmenu + +config BACKLIGHT_S5L8740 + tristate "Samsung/Apple S5L8740 LCD backlight" + depends on BACKLIGHT_CLASS_DEVICE && HAS_IOMEM + help + Backlight controller at 0x3E000000 for iPod nano 7G (N31). diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 8fc98f760a8ad4..4defc75de0b094 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o obj-$(CONFIG_BACKLIGHT_BD6107) += bd6107.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o +obj-$(CONFIG_BACKLIGHT_S5L8740) += backlight-s5l8740.o obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o obj-$(CONFIG_BACKLIGHT_EP93XX) += ep93xx_bl.o diff --git a/drivers/video/backlight/backlight-s5l8740.c b/drivers/video/backlight/backlight-s5l8740.c new file mode 100755 index 00000000000000..9615ea58016fef --- /dev/null +++ b/drivers/video/backlight/backlight-s5l8740.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Platform backlight for Samsung/Apple S5L8740 (iPod nano 7G / N31) + * + * MMIO block @ 0x3E000000 (LCD AUX / backlight): + * +0x04 enable — bit0 + * +0x08 level — low 8 bits hold 1..62; bit0 also acts as enable + * + * Init (matches U-Boot / panel bring-up): write 62 to +0x08, then set bit0 + * on +0x04 and +0x08. Brightness 0 = off; userspace 1..max → HW 1..62. + * + * This is MMIO, not the Dialog PMIC, and that is from the decomp rather + * than from anyone's recollection: + * + * - The stock bootloader ends its display bring-up with + * sub_3522(0x3E000008, 62) -- sub_3522 is its plain MMIO write + * helper, the same one used for CLKCON and IIC -- immediately after + * the 0x3D7000xx LCDIF/DSI block and just before 0x3D700008 = 1. + * 62 is exactly this register's full-scale level. + * - OSOS sub_4399FC sets 0x3E000008 bit 0 when SoC power domain 2 + * comes up, and domain 2 is the LCD domain. So the domain gates the + * block; it does not supply the level. + * + * No PMIC register anywhere in the D1830 map carries brightness. A rail + * feeding the LED string would be a separate question, but nothing in the + * PMIC state analysis names one, so do not assume it either way. + * + * Kconfig fragment (wire Makefile / Kconfig separately): + * config BACKLIGHT_S5L8740 + * tristate "Samsung/Apple S5L8740 backlight" + * depends on BACKLIGHT_CLASS_DEVICE && (ARCH_S5L8740 || COMPILE_TEST) + * default y if ARCH_S5L8740 + * help + * LCD backlight at 0x3E000000 for iPod nano 7G (N31). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S5L8740_BL_ENABLE_OFF 0x04 +#define S5L8740_BL_LEVEL_OFF 0x08 +#define S5L8740_BL_MAX 62 + +/* + * The LED boost is the expensive part of the display, so screen sleep + * ramps it rather than cutting it. Stepping happens in a work item so a + * caller in a button handler does not block for the length of the fade. + */ +struct s5l8740_bl { + void __iomem *base; + struct backlight_device *bd; + struct delayed_work fade; + struct mutex lock; + int level; /* what the hardware currently has */ + int target; + unsigned int step_ms; +}; + +static struct s5l8740_bl *s5l8740_bl_dev; + +static void s5l8740_bl_hw_set(struct s5l8740_bl *bl, int level) +{ + u32 en, lvl; + + if (level > S5L8740_BL_MAX) + level = S5L8740_BL_MAX; + bl->level = level > 0 ? level : 0; + + if (level <= 0) { + en = readl(bl->base + S5L8740_BL_ENABLE_OFF); + writel(en & ~BIT(0), bl->base + S5L8740_BL_ENABLE_OFF); + lvl = readl(bl->base + S5L8740_BL_LEVEL_OFF); + writel(lvl & ~BIT(0), bl->base + S5L8740_BL_LEVEL_OFF); + return; + } + + writel((u32)level, bl->base + S5L8740_BL_LEVEL_OFF); + en = readl(bl->base + S5L8740_BL_ENABLE_OFF); + writel(en | BIT(0), bl->base + S5L8740_BL_ENABLE_OFF); + lvl = readl(bl->base + S5L8740_BL_LEVEL_OFF); + writel(lvl | BIT(0), bl->base + S5L8740_BL_LEVEL_OFF); +} + +static void s5l8740_bl_fade_work(struct work_struct *work) +{ + struct s5l8740_bl *bl = container_of(to_delayed_work(work), + struct s5l8740_bl, fade); + bool more; + + mutex_lock(&bl->lock); + if (bl->level < bl->target) + s5l8740_bl_hw_set(bl, bl->level + 1); + else if (bl->level > bl->target) + s5l8740_bl_hw_set(bl, bl->level - 1); + more = bl->level != bl->target; + mutex_unlock(&bl->lock); + + if (more) + schedule_delayed_work(&bl->fade, + msecs_to_jiffies(bl->step_ms)); +} + +/* + * Ramp to `level` over roughly `ms`. ms = 0 jumps straight there, which is + * what the backlight class writes should do. Returns the level that was + * programmed before the fade started, so a caller can restore it on wake. + */ +int n31_backlight_fade(int level, unsigned int ms) +{ + struct s5l8740_bl *bl = s5l8740_bl_dev; + int previous, delta; + + if (!bl) + return -ENODEV; + if (level < 0) + level = 0; + if (level > S5L8740_BL_MAX) + level = S5L8740_BL_MAX; + + cancel_delayed_work_sync(&bl->fade); + mutex_lock(&bl->lock); + previous = bl->level; + bl->target = level; + delta = abs(level - bl->level); + if (!ms || !delta) { + s5l8740_bl_hw_set(bl, level); + mutex_unlock(&bl->lock); + return previous; + } + bl->step_ms = max_t(unsigned int, ms / delta, 1); + mutex_unlock(&bl->lock); + + schedule_delayed_work(&bl->fade, msecs_to_jiffies(bl->step_ms)); + return previous; +} +EXPORT_SYMBOL_GPL(n31_backlight_fade); + +/* Level the hardware currently has, ignoring any fade in progress. */ +int n31_backlight_level(void) +{ + struct s5l8740_bl *bl = s5l8740_bl_dev; + + return bl ? bl->level : -ENODEV; +} +EXPORT_SYMBOL_GPL(n31_backlight_level); + +static int s5l8740_bl_update_status(struct backlight_device *bd) +{ + struct s5l8740_bl *bl = bl_get_data(bd); + int brightness = backlight_get_brightness(bd); + + cancel_delayed_work_sync(&bl->fade); + mutex_lock(&bl->lock); + bl->target = brightness; + s5l8740_bl_hw_set(bl, brightness); + mutex_unlock(&bl->lock); + return 0; +} + +static const struct backlight_ops s5l8740_bl_ops = { + .update_status = s5l8740_bl_update_status, +}; + +static int s5l8740_bl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l8740_bl *bl; + struct backlight_properties props = { }; + struct backlight_device *bd; + + bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL); + if (!bl) + return -ENOMEM; + + bl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bl->base)) + return PTR_ERR(bl->base); + + mutex_init(&bl->lock); + INIT_DELAYED_WORK(&bl->fade, s5l8740_bl_fade_work); + bl->target = S5L8740_BL_MAX; + + /* Full brightness + enables (U-Boot path) */ + s5l8740_bl_hw_set(bl, S5L8740_BL_MAX); + + props.type = BACKLIGHT_RAW; + props.max_brightness = S5L8740_BL_MAX; + props.brightness = S5L8740_BL_MAX; + + bd = devm_backlight_device_register(dev, "s5l8740-backlight", dev, bl, + &s5l8740_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + bl->bd = bd; + s5l8740_bl_dev = bl; + platform_set_drvdata(pdev, bl); + dev_info(dev, "S5L8740 backlight @%pR max=%u\n", + platform_get_resource(pdev, IORESOURCE_MEM, 0), S5L8740_BL_MAX); + return 0; +} + +static const struct of_device_id s5l8740_bl_of_match[] = { + { .compatible = "apple,s5l8740-backlight" }, + { .compatible = "samsung,s5l8740-backlight" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_bl_of_match); + +static void s5l8740_bl_remove(struct platform_device *pdev) +{ + struct s5l8740_bl *bl = platform_get_drvdata(pdev); + + s5l8740_bl_dev = NULL; + cancel_delayed_work_sync(&bl->fade); +} + +static struct platform_driver s5l8740_bl_driver = { + .probe = s5l8740_bl_probe, + .remove = s5l8740_bl_remove, + .driver = { + .name = "backlight-s5l8740", + .of_match_table = s5l8740_bl_of_match, + }, +}; +module_platform_driver(s5l8740_bl_driver); + +MODULE_DESCRIPTION("Samsung/Apple S5L8740 LCD backlight"); +MODULE_LICENSE("GPL"); diff --git a/freemyipod/initramfs/rcS b/freemyipod/initramfs/rcS new file mode 100755 index 00000000000000..8731e36df86f90 --- /dev/null +++ b/freemyipod/initramfs/rcS @@ -0,0 +1,20 @@ +#!/bin/sh + +# Bring up USB gadget network. g_ether may take a moment to register usb0 +# after the host enumerates the device, so retry briefly. +for i in 1 2 3 4 5 6 7 8 9 10; do + if ifconfig usb0 192.168.7.2 netmask 255.255.255.0 up 2>/dev/null; then + echo "usb0 up at 192.168.7.2 (host: 192.168.7.1)" + break + fi + sleep 1 +done + +# No-auth root shell over telnet for development. +telnetd -l /bin/sh && echo "telnetd listening on :23" + +# Sleep/KEY_POWER → poweroff when power-watch is present in rootfs +if [ -x /usr/bin/power-watch ] || [ -x /bin/power-watch ]; then + (command -v power-watch >/dev/null && power-watch) & + echo "power-watch: KEY_POWER → poweroff -f" +fi diff --git a/include/dt-bindings/clock/samsung,s5l8702-clock.h b/include/dt-bindings/clock/samsung,s5l8702-clock.h old mode 100644 new mode 100755 index 5a532f87b8b62e..ce1517797d0582 --- a/include/dt-bindings/clock/samsung,s5l8702-clock.h +++ b/include/dt-bindings/clock/samsung,s5l8702-clock.h @@ -1,13 +1,77 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Device Tree binding constants for Samsung S5L8702 clock controller. + * DT bindings for Samsung/Apple S5L8702/S5L8740 CLKCON @0x3C500000 + * + * Gate IDs match Rockbox CLOCKGATE_* (bank = id>>5, bit = id&31). + * Enable polarity: CLEAR bit in PWRCONn (CLK_GATE_SET_TO_DISABLE). */ #ifndef _DT_BINDINGS_CLOCK_SAMSUNG_S5L8702_CLOCK_H #define _DT_BINDINGS_CLOCK_SAMSUNG_S5L8702_CLOCK_H -#define CLK_SHA1 0 -#define CLK_AES 1 -#define CLK_PRNG 2 +/* Legacy crypto IDs (keep stable) */ +#define CLK_SHA1 0 +#define CLK_AES 1 +#define CLK_PRNG 2 -#endif /* _DT_BINDINGS_CLOCK_SAMSUNG_S5L8702_CLOCK_H */ +/* Fixed / derived */ +#define CLK_OSC24 3 +#define CLK_PCLK 4 + +/* PWRCON0 / AHB @ +0x48 (gates 0..31) */ +#define CLK_LCD 5 +#define CLK_USBOTG 6 +#define CLK_SMX 7 +#define CLK_SM1 8 +#define CLK_ATA 9 +#define CLK_NAND 10 +#define CLK_AES_AHB 11 +#define CLK_NANDECC 12 +#define CLK_DMAC0 13 +#define CLK_DMAC1 14 +#define CLK_ROM 15 + +/* PWRCON1 / APB @ +0x4C (gates 32..63) — N31 proven subset */ +#define CLK_RTC 16 +#define CLK_CWHEEL 17 +#define CLK_SPI0 18 +#define CLK_USBPHY 19 +#define CLK_I2C0 20 +#define CLK_TIMER 21 +#define CLK_I2C1 22 +#define CLK_I2S0 23 +#define CLK_UART 24 +#define CLK_I2S1 25 +#define CLK_SPI1 26 +#define CLK_GPIO 27 +#define CLK_CHIPID 28 +#define CLK_I2S2 29 +#define CLK_SPI2 30 +/* Ambiguous 8720 map: SPI2 sometimes bit15 (gate 47) */ +#define CLK_SPI2_ALT 31 + +/* PWRCON2 @ +0x58 */ +#define CLK_SPI3 32 + +/* PWRCON4 secondary @ +0x6C (8720-style; harmless if RO on 8740) */ +#define CLK_SPI0_2 33 +#define CLK_SPI1_2 34 +#define CLK_SPI2_2 35 +#define CLK_I2C0_2 36 +#define CLK_I2C1_2 37 +#define CLK_UART_2 38 +#define CLK_LCD_2 39 +#define CLK_TIMERA_2 40 + +/* CG16 / RetailOS sub_41CBD8 gate IDs 7..13 (div-half enable = clear 0x8000/0x80000000) */ +#define CLK_CG16_7 41 +#define CLK_CG16_8 42 +#define CLK_CG16_9 43 +#define CLK_CG16_10 44 +#define CLK_CG16_11 45 +#define CLK_CG16_12 46 +#define CLK_CG16_13 47 + +#define CLK_S5L8702_NR_CLKS 48 + +#endif diff --git a/include/linux/apple-n31.h b/include/linux/apple-n31.h new file mode 100755 index 00000000000000..2bdf95578b6ca4 --- /dev/null +++ b/include/linux/apple-n31.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Cross-driver interfaces for the iPod nano 7 (N31, Samsung S5L8740). + * + * A handful of symbols on this board legitimately cross driver boundaries: + * the PMIC gates the touch and audio rails, its interrupt arrives on an SoC + * GPIO owned by another driver, and the touch controller reads its calibration + * blob through the NAND FTL. Collecting the declarations here keeps them in + * one place instead of being repeated as bare externs in each .c file, where + * they were already starting to disagree with each other. + * + * Everything below is exported with EXPORT_SYMBOL_GPL() by the driver named + * in the comment. Consumers must cope with the provider being absent, since + * these are separate modules that can load in any order. + */ +#ifndef __LINUX_APPLE_N31_H +#define __LINUX_APPLE_N31_H + +#include + +struct device; +struct dma_chan; + +/* irq-s5l8740-eic.c — route an SoC GPIO to the external interrupt controller. */ +int s5l8740_eic_enable_gpio(unsigned int gpio, unsigned int irq_type); + +/* gpio-s5l8740.c — report a key press to the board input device. */ +void s5l8740_n31_report_key(unsigned int code, int pressed); + +/* gpio-s5l8740.c — raw level of GPIO 86, the PMIC nIRQ line. */ +int s5l8740_n31_din86(void); + +/* + * gpio-d1830.c — set by the PMIC driver so the GPIO edge handler can fold a + * missed EIC edge back into the button poll. NULL until gpio-d1830 probes. + */ +extern void (*d1830_n31_din_nirq_hook)(void); + +/* gpio-d1830.c — apply the audio LDO trim, for the CS42L81 codec. */ +int d1830_audio_rails(void); + +/* gpio-d1830.c — power the touch controller rail, for the Grape driver. */ +int d1830_grape_rail(bool on); +/* Bluetooth companion rails: reg 87 bits 7:6, reg 88 bit 0 and bits 6:4, + * the fields sub_51688C zeroes on de-init. Saves the boot values on the + * first power-off so power-on can restore them. */ +int d1830_bt_rails(bool on); + +/* + * gpio-d1830.c — refcounted rail control. Rail ids index the PMU rail + * table; a rail stays up while any consumer holds it and powers down a + * few seconds after the last release. + * + * Ownership below comes from the stock firmware's own rail dispatcher, + * which maps a consumer id onto a bit in PMU_ACTIVE_1/2; the call sites + * name the consumer. + * + * LDO_3 ACTIVE_1 bit5 touch controller + * LDO_4 ACTIVE_1 bit6 display, enabled immediately before LCDIF init + * LDO_5 ACTIVE_1 bit7 accessory port, voltage negotiated 2.5-3.3 V + * + * No stock path enables a rail for the audio codec: its analog supply is + * always on. Do not add one. + */ +/* + * Indexes into n31_pmu_rails[], NOT RetailOS logical rail IDs. The two + * numbering systems overlap and disagree, which is a trap worth naming: + * + * this table index 2 -> 0x10 bit 5 -> Grape + * RetailOS logical 4 -> 0x10 bit 5 -> Grape + * + * Same physical bit, different number, and sub_6644 converts logical + * IDs 1..10 into selectors 6..15 before sub_7484 turns those into + * register and bit. So a bare 4 in a decompiler listing and a bare 4 + * here mean different rails. Always carry the {register, mask} pair. + * + * The physical assignments, from sub_7484: + * 0x10 bits 2..7 are selectors 6..11 + * 0x11 bits 0..3 are selectors 12..15 + * Only 0x10 bit 5 has a proven consumer -- Grape, via the call chain + * sub_20766(1) to sub_439B00(1) to sub_6644(4) to sub_7484(9). The + * display and accessory names below are this project's mapping and are + * not re-proven from the firmware. + */ +#define N31_PMU_RAIL_TOUCH 2 /* PMU_LDO_3, 0x10 bit 5, Grape */ +#define N31_PMU_RAIL_DISPLAY 3 /* PMU_LDO_4, 0x10 bit 6, unproven */ +#define N31_PMU_RAIL_ACCESSORY 4 /* PMU_LDO_5, 0x10 bit 7, unproven */ + +int n31_pmu_rail_get(unsigned int id); +void n31_pmu_rail_put(unsigned int id); + +/* + * backlight-s5l8740.c — ramp the LED boost. n31_backlight_fade() returns + * the level that was set before the ramp began, so a screen-sleep caller + * can restore exactly what the user had. ms = 0 applies immediately. + */ +int n31_backlight_fade(int level, unsigned int ms); +int n31_backlight_level(void); + +/* + * apple-grape.c — screen-sleep hooks for the touch controller. Whether + * the rail is cut is the driver's own touch_power_down parameter; the + * caller only says sleep or wake. + */ +int n31_touch_suspend(void); +int n31_touch_resume(void); + +/* + * s5l8740.c (DRM) — display power. Turning the panel off means stopping + * the LCDIF and dropping its rail; turning it back on resets and + * reprograms the interface and repaints. The panel itself needs no + * command sequence. + */ +int n31_lcd_power(bool on); +bool n31_lcd_is_on(void); + +/* spi-s5l8702.c — reapply the SPI2 engine setup after a pinmux change. */ +void s5l8702_spi2_reinit(void); + +/* cs42l81-spi.c — true while the analog play graph is latched. */ +bool n31_audio_playback_active(void); + +/* nand-s5l8740.c — true once the FTL has a usable logical-to-virtual map. */ +bool nand_ftl_present(void); + +/* nand-s5l8740.c — read one 4096-byte logical sector through the FTL. */ +int nand_ftl_read_sector(u64 logical_sector, void *buf); + +/* + * dma-s5l8740-pl080.c — slave-channel lookup. The I2S and IIS2 request lines + * are fixed by the SoC rather than described in the device tree, so consumers + * ask for them by index or by peripheral number instead of going through the + * usual of_dma path. + */ +struct dma_chan *s5l_pl080_request_slave(struct device *consumer, + unsigned int idx); +struct dma_chan *s5l_pl080_lookup_peri(unsigned int peri); +int s5l_pl080_rearm_set_src(struct dma_chan *c, dma_addr_t addr, + size_t bytes); +/* + * Largest byte count one LLI node can carry at the width the channel is + * configured for. A period bigger than this is split across several nodes, + * which the self-linked ring cannot express -- see s5l_pl080_rearm_set_ring(). + */ +size_t s5l_pl080_max_seg_bytes(void); + +int s5l_pl080_rearm_set_ring(struct dma_chan *c, dma_addr_t base, size_t bytes, + size_t period); +int s5l_pl080_peri_snapshot(unsigned int peri, u32 *src, u32 *dst, u32 *en); + +#endif /* __LINUX_APPLE_N31_H */ diff --git a/include/linux/n31-glass-mark.h b/include/linux/n31-glass-mark.h new file mode 100755 index 00000000000000..f3f587ec7d8e0d --- /dev/null +++ b/include/linux/n31-glass-mark.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * N31 LCD-only bring-up witness — no serial, no U-Boot vidconsole. + * pr_alert() + console=tty0 → readable on fbcon once DRM is up. + */ +#ifndef _LINUX_N31_GLASS_MARK_H +#define _LINUX_N31_GLASS_MARK_H + +#include + +#define n31_glass_mark(tag) \ + pr_alert("N31>> %s\n", (tag)) + +#endif /* _LINUX_N31_GLASS_MARK_H */ diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig old mode 100644 new mode 100755 index 793f7782e0d721..3cde54a7e394a0 --- a/sound/soc/apple/Kconfig +++ b/sound/soc/apple/Kconfig @@ -1,8 +1,34 @@ -config SND_SOC_APPLE_MCA - tristate "Apple Silicon MCA driver" - depends on ARCH_APPLE || COMPILE_TEST - select SND_DMAENGINE_PCM - default ARCH_APPLE - help - This option enables an ASoC platform driver for MCA peripherals found - on Apple Silicon SoCs. +config SND_SOC_APPLE_MCA + tristate "Apple Silicon MCA driver" + depends on ARCH_APPLE || COMPILE_TEST + select SND_DMAENGINE_PCM + default ARCH_APPLE + help + This option enables an ASoC platform driver for MCA peripherals found + on Apple Silicon SoCs. + +config SND_SOC_APPLE_NANO7 + tristate "iPod nano 7G audio machine" + depends on SND_SOC + select SND_SOC_APPLE_S5L8740_I2S + select SND_SOC_APPLE_CS42L81_SPI + help + Registers the ASoC card: CS42L81 headphone playback on IIS0 and + BCM2078 FM capture on IIS2, as the two PCMs of one card. + +config SND_SOC_APPLE_S5L8740_I2S + tristate "S5L8740 I2S CPU DAIs (IIS0 playback, IIS2 capture)" + depends on SND_SOC && HAS_IOMEM + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_DMAENGINE_PCM + help + IIS0 @0x3CA00000 playback (PL080 peri 10) and IIS2 @0x3D400000 + BCM2078 digital PCM capture (peri 13). Both ports are in one + driver because they share the SoC audio clock gate. + +config SND_SOC_APPLE_CS42L81_SPI + tristate "CS42L81 / 338S1146 SPI codec (N31)" + depends on SPI && SND_SOC + help + RetailOS-matched SPI0 framing (0x6C/0x6D), analog bring-up, and + ASoC DAI (cs42l81-hifi). Sysfs reg/bringup/audio_on stay. diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile old mode 100644 new mode 100755 index 1eb8fbef60c617..712e5844679602 --- a/sound/soc/apple/Makefile +++ b/sound/soc/apple/Makefile @@ -1,3 +1,6 @@ -snd-soc-apple-mca-y := mca.o - -obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o +snd-soc-apple-mca-y := mca.o + +obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o +obj-$(CONFIG_SND_SOC_APPLE_NANO7) += nano7-audio.o +obj-$(CONFIG_SND_SOC_APPLE_CS42L81_SPI) += cs42l81-spi.o +obj-$(CONFIG_SND_SOC_APPLE_S5L8740_I2S) += s5l8740-i2s.o diff --git a/sound/soc/apple/cs42l81-spi.c b/sound/soc/apple/cs42l81-spi.c new file mode 100755 index 00000000000000..e9ad00a0825226 --- /dev/null +++ b/sound/soc/apple/cs42l81-spi.c @@ -0,0 +1,2511 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CS42L81 / Apple 338S1146 codec, SPI control port (iPod nano 7G / N31). + * + * Every register sequence here is transcribed from the RetailOS image + * (osos.dec.bin) and is documented, access by access, in + * docs-internal/n7g-audio/N31-REGISTER-TRACE-STOCK.md. The matching trace of + * this driver is N31-REGISTER-TRACE-LINUX.md, and the differences between + * them are enumerated in N31-REGISTER-TRACE-COMPARISON.md. + * + * Function names follow the stock routine each one transcribes, so the two + * traces can be read side by side: + * + * sub_D3280(1) cs42_state_park analog config, then clock off + * sub_D2EFC cs42_user_key 0x9901 key, once per boot + * sub_D3280(3) cs42_state_run divider, clock on, unfreeze + * sub_D3280(4) cs42_state_analog_on + * sub_D2D2C cs42_output_path_on/off + * sub_D2F64 cs42_path_mode + * sub_D34C0 cs42_set_rate three-way dispatch on mode38 + * sub_183138 cs42_set_rate_183138 + * sub_42D364 cs42_transport_start/stop + * sub_F141C cs42_analog_mute + * sub_F1444 cs42_fifo_strobe + * sub_570620 cs42_play_graph + * sub_5707D8 cs42_play_graph_static + * sub_165BD4 cs42_slot_map + * sub_400330 cs42_dac_gain gain + 2v5 backpower rail + * sub_3C36A4 cs42_channel_gain + * sub_D2F2C cs42_apply_gains right channel, then left + * + * Deliberate deviations from stock, and only these: + * + * - Every poll is bounded. Stock spins on 0x002F bit 7 with no escape, + * which an RTOS owning the machine can afford and a kernel cannot. + * - The play graph and the mode-18 path config run in .prepare rather than + * in the transport trigger. Between them they carry a 100 ms and a 60 ms + * settle; run from the trigger that delay lands between the application + * asking for playback and the DMA being armed, and the stream underruns + * before it starts. ALSA calls .prepare before every start, xrun recovery + * included, so the ordering relative to stock is preserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "n31-audio-rates.h" + +/* Output gain, 0x0227. sub_D2C98 maps dB to the code the register carries. */ +#define CS42_DB_MIN (-76) +#define CS42_DB_MAX 12 +#define CS42_DB_KNEE (-50) +#define CS42_VOL_MAX (CS42_DB_MAX - CS42_DB_MIN) +#define CS42_VOL_DEFAULT (CS42_VOL_MAX - 32) /* -20 dB */ + +/* sub_400330 moves the 2v5 backpower rail across this gain threshold. */ +#define CS42_RAIL_DB (-8) + +/* Bounded replacement for stock's unbounded readiness poll. */ +#define CS42_READY_POLLS 50 + +static bool trace_spi; +module_param(trace_spi, bool, 0644); +MODULE_PARM_DESC(trace_spi, "log every SPI frame sent to the codec"); + +static bool debug_regs; +module_param(debug_regs, bool, 0644); +MODULE_PARM_DESC(debug_regs, "dump codec state at each lifecycle transition"); + +/* + * sub_570620's graph selector. + * + * The branch is not on a magic variable. Disassembled at 0x08570644: + * + * ldr r0, [pc, #0xa4] ; r0 = 0x08A8F9DC + * ldr.w r0, [r0, #0x17c] ; r0 = config[0x17C / 4 = 95] + * cbz r0, dynamic ; zero selects the dynamic path + * bl sub_5707D8 ; non-zero passes the blob pointer + * + * 0x08A8F9DC is the same runtime config table sub_149E98 reads at indices + * 126 and 127 from sub_D34C0, so the selector is config entry 95. Neither + * the table nor the entry is initialised by either firmware image -- both + * addresses sit past the end of the image -- so it is populated at runtime + * from configuration this driver does not have. + * + * With no config word, the value stock would read is zero, and zero selects + * the dynamic path. That is therefore the faithful default here. The static + * image stays reachable, because it is what stock emits when the entry is + * set, and the two agree: the blob's taps (9, 8) are exactly what + * sub_174E7C computes for the state sub_5706F4 installs. + * + * 0 = static sub_5707D8 (config entry 95 non-zero) + * 1 = dynamic, route 1 (config entry 95 zero -- the default) + * 3, 4 = dynamic, routes 3 and 4 + */ +/* + * Sample-rate-converter selection in cs42_set_rate_long(). 0 follows the + * stock condition; 1 forces it on; 2 forces it off. See the comment there. + */ +static int src_mode; +module_param(src_mode, int, 0644); +MODULE_PARM_DESC(src_mode, + "0=stock condition, 1=force codec SRC on, 2=force it off"); + +static int graph_mode = 1; +module_param(graph_mode, int, 0644); +MODULE_PARM_DESC(graph_mode, + "sub_570620 graph: 1=dynamic route 1 (default), 0=static, 3/4=dynamic"); + +/* + * MEMORY[0x892A02C], the MCLK in kHz. sub_D3280(3) selects clock divider 2 + * when this reads 12000 and 4 otherwise, and sub_D34C0 halves every entry of + * the rate-divider table when it reads 6000. + * + * This has never been measured on the hardware -- every divider in the rate + * table is 12000000/rate, which is consistent with 12 MHz but does not prove + * it. It is a parameter rather than a constant so the assumption is visible + * and can be moved in one place when it is measured. + */ +static unsigned int mclk_khz = 12000; +module_param(mclk_khz, uint, 0644); +MODULE_PARM_DESC(mclk_khz, + "MEMORY[0x892A02C], codec MCLK in kHz (12000 or 6000); UNMEASURED"); + +/* + * sub_174E7C's inputs, read out of the image at 0x0892A05C..0x0892A06C. + * + * This block is initialised data, not BSS -- the bytes are present in + * osos.dec.bin and 0x0892A000..0x0892A100 carries 88 non-zero bytes: + * + * 0x0892A05C count_l = 2 0x0892A05F count_r = 0 + * 0x0892A05D idx_l_lo = 2 0x0892A060 idx_r_lo = 2 + * 0x0892A05E idx_l_hi = 12 0x0892A061 idx_r_hi = 12 + * 0x0892A062 base_l = 160 0x0892A064 base_r = 160 + * + * and the dword table at 0x0892A068 is + * + * 0, 27, 37, 37, 40, 54, 74, 74, 80, 107, 147, 148, 160 + * + * so the per-slot gain sub_174E7C derives is + * + * accum = (idx_lo + 1) * table[idx_hi] = 3 * 160 = 480 = 0x01E0 + * + * which is exactly the gain sub_5707D8's static image writes into each + * active slot, and feeding it back through the tap formula with + * count_l = count_r = 2 and base 160 gives 9 and 8 -- the 0x0403 and 0x0404 + * that image writes. The static and dynamic paths agree exactly, and the + * gain is derived here rather than assumed. + */ +static const u32 cs42_graph_table[] = { + 0, 27, 37, 37, 40, 54, 74, 74, 80, 107, 147, 148, 160, +}; + +#define CS42_IDX_LO 2 +#define CS42_IDX_HI 12 + +/* sub_440AA4 is a plain unsigned divide. */ +static u32 cs42_graph_accum(void) +{ + if (CS42_IDX_HI >= ARRAY_SIZE(cs42_graph_table)) + return 0; + return (CS42_IDX_LO + 1) * cs42_graph_table[CS42_IDX_HI]; +} + +enum cs42_state { + CS42_UNKNOWN = 0, + CS42_PROBED, + CS42_PREPARED, + CS42_PLAYING, + CS42_STANDBY, +}; + +static const char * const cs42_state_names[] = { + "unknown", "probed", "prepared", "playing", "standby", +}; + +struct cs42l81 { + struct spi_device *spi; + struct mutex lock; + struct snd_soc_component *component; + struct delayed_work post_iis_work; + + enum cs42_state state; + unsigned int rate; /* rate the codec is configured for */ + unsigned int user_vol; /* 0..CS42_VOL_MAX */ + bool dai_mute; + bool key_done; /* MEMORY[0x892A028] */ + bool graph_built; + + /* + * MEMORY[0x892A038]: shadow of 0x0220 bits 0x28, set by state 3 and + * recomputed by sub_D2F64. sub_D34C0 dispatches on it. + */ + u8 mode38; + + /* MEMORY[0x892A044] / [0x892A048]: cached left/right gain, in dB. */ + int gain_l_db; + int gain_r_db; +}; + +struct cs42_reg { + u16 reg; + u8 val; +}; + +static struct cs42l81 *cs42l81_dev; + +/* ------------------------------------------------------------------ */ +/* Transport */ +/* ------------------------------------------------------------------ */ + +/* + * Two write frames. sub_43CDB4 sends five bytes and is used for everything + * except a small set of registers; sub_3FA0E0 sends six and writes the + * addressed register *and the one above it* -- the left/right pair of an + * analog control. Dispatch is by register number so no call site can get it + * wrong. + */ +static bool cs42_reg_is_pair(u16 reg) +{ + switch (reg) { + case 0x0206: + case 0x0225: + case 0x0227: + case 0x0229: + return true; + default: + return false; + } +} + +static int cs42_xfer(struct cs42l81 *c, const u8 *tx, u8 *rx, size_t len) +{ + struct spi_transfer t = { .tx_buf = tx, .rx_buf = rx, .len = len }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(c->spi, &m); + if (trace_spi) + dev_info(&c->spi->dev, "spi %*ph%s\n", (int)len, tx, + ret ? " ERR" : ""); + return ret; +} + +/* sub_43CDB4 */ +static int cs42_wr8(struct cs42l81 *c, u16 reg, u8 val) +{ + u8 tx[5] = { 0x6c, reg >> 8, reg & 0xff, 0x00, val }; + + return cs42_xfer(c, tx, NULL, sizeof(tx)); +} + +/* sub_3FA0E0 -- writes reg and reg+1 */ +static int cs42_wr16(struct cs42l81 *c, u16 reg, u8 val) +{ + u8 tx[6] = { 0x6c, reg >> 8, (reg & 0xff) | 0x80, 0x01, val, val }; + + return cs42_xfer(c, tx, NULL, sizeof(tx)); +} + +/* sub_43CDFA */ +static int cs42_rd(struct cs42l81 *c, u16 reg, u8 *val) +{ + u8 tx[5] = { 0x6d, reg >> 8, reg & 0xff, 0x00, 0xff }; + u8 rx[5] = { 0 }; + int ret = cs42_xfer(c, tx, rx, sizeof(tx)); + + if (ret) + return ret; + *val = rx[4]; + return 0; +} + +/* + * sub_42A5D6. The mask narrows the value, not the transaction: a zero mask + * still performs a read and a write, and stock relies on that in sub_D2F64. + * Do not optimise it away. + */ +static int cs42_rmw(struct cs42l81 *c, u16 reg, u8 mask, u8 val) +{ + u8 cur = 0; + int ret = cs42_rd(c, reg, &cur); + + if (ret) + return ret; + return cs42_wr8(c, reg, (cur & ~mask) | (val & mask)); +} + +static int cs42_write_table(struct cs42l81 *c, const struct cs42_reg *t, + unsigned int n) +{ + unsigned int i; + int ret; + + for (i = 0; i < n; i++) { + ret = cs42_wr8(c, t[i].reg, t[i].val); + if (ret) + return ret; + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Message mailbox -- sub_15A50C / sub_19A838 */ +/* ------------------------------------------------------------------ */ + +/* + * 0x051E..0x0525 is a byte-oriented message FIFO, not the "coherent level + * sampling" an earlier version of this driver described: + * + * 0x051E write-side control; bit 0 latches the level pair, bit 5 + * strobes a reset (sub_F1444 pulses it) + * 0x051F write-side status, bit 1 = full + * 0x0520 free space, less one + * 0x0521 write data port + * 0x0523 read-side control, bit 5 strobes a reset + * 0x0524 read-side status + * 0x0525 read data port + * + * The payload is framed by sub_19A838, which appends a CRC-16 over the first + * len+5 bytes using a table at MEMORY[0x892A0C8]. That table is in ROM and is + * bit-exact CRC-16/ARC (reflected polynomial 0xA001), which is what the + * kernel's crc16() computes, so nothing here is invented. + * + * Nothing in the audio path sends a message: sub_15A50C's only callers are + * sub_131B84 and sub_1757B0, neither of which is reachable from sub_D3280, + * sub_D2F64, sub_D34C0, sub_570620 or sub_42D364. This is the accessory / + * headphone-remote channel. It is implemented and exported so the MikeyBus + * side has the transport, and so that the audio path's *absence* of mailbox + * traffic is a deliberate, documented match with stock rather than a gap. + */ +#define CS42_MBOX_LEN_OFF 1 /* payload length lives in byte 1 */ +#define CS42_MBOX_OVERHEAD 7 /* header + the two CRC bytes */ +#define CS42_MBOX_MAX 64 /* bound on the prepare-time FIFO drain */ + +/* sub_19A838: append the CRC-16 and return the full frame length. */ +static size_t cs42_mbox_frame(u8 *buf, size_t buf_size) +{ + size_t len = buf[CS42_MBOX_LEN_OFF]; + u16 crc; + + if (len + CS42_MBOX_OVERHEAD > buf_size) + return 0; + crc = crc16(0, buf, len + 5); + buf[len + 5] = crc >> 8; + buf[len + 6] = crc & 0xff; + return len + CS42_MBOX_OVERHEAD; +} + +/* + * sub_15A50C: check the free space, then push the frame a byte at a time. + * Stock returns 33 when the frame does not fit; -ENOSPC is the equivalent. + */ +static int cs42_mbox_send_locked(struct cs42l81 *c, const u8 *frame, size_t len) +{ + u8 status = 0, space = 0; + size_t i; + int ret; + + ret = cs42_rmw(c, 0x051e, 0x01, 0x01); + if (ret) + return ret; + cs42_rd(c, 0x051f, &status); + cs42_rd(c, 0x0520, &space); + ret = cs42_rmw(c, 0x051e, 0x01, 0x00); + if (ret) + return ret; + + if (len >= ((status & 0x02) ? 0 : (unsigned int)space + 1)) + return -ENOSPC; + + for (i = 0; i < len; i++) { + ret = cs42_wr8(c, 0x0521, frame[i]); + if (ret) + return ret; + } + return 0; +} + +/* + * Frame and send one message. @buf must hold the header, the payload and + * CS42_MBOX_OVERHEAD bytes of room; byte 1 is the payload length, and the + * CRC is appended here. + */ +int cs42l81_mbox_send(u8 *buf, size_t buf_size) +{ + struct cs42l81 *c = cs42l81_dev; + size_t len; + int ret; + + if (!c) + return -ENODEV; + len = cs42_mbox_frame(buf, buf_size); + if (!len) + return -EINVAL; + + mutex_lock(&c->lock); + ret = cs42_mbox_send_locked(c, buf, len); + mutex_unlock(&c->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs42l81_mbox_send); + +/* Drain whatever the read side has queued. Returns the byte count. */ +int cs42l81_mbox_recv(u8 *buf, size_t buf_size) +{ + struct cs42l81 *c = cs42l81_dev; + u8 level = 0; + size_t i; + + if (!c) + return -ENODEV; + + mutex_lock(&c->lock); + cs42_rd(c, 0x0524, &level); + if (level > buf_size) + level = buf_size; + for (i = 0; i < level; i++) { + if (cs42_rd(c, 0x0525, &buf[i])) + break; + } + mutex_unlock(&c->lock); + return i; +} +EXPORT_SYMBOL_GPL(cs42l81_mbox_recv); + +/* ------------------------------------------------------------------ */ +/* SoC clock (owned by the IIS driver) */ +/* ------------------------------------------------------------------ */ + +static void cs42_soc_clk_gate(struct cs42l81 *c, bool on) +{ + void (*gate)(bool); + + gate = (void (*)(bool))__symbol_get("s5l8740_codec_clk_gate"); + if (!gate) { + dev_warn_once(&c->spi->dev, "codec clock gate unavailable\n"); + return; + } + gate(on); + __symbol_put("s5l8740_codec_clk_gate"); +} + +/* + * sub_345D28(9, 0, div), the first thing state 3 does -- before the clock + * gate is opened. The divider follows MEMORY[0x892A02C], the MCLK in kHz: + * 2 when it reads 12000, otherwise 4. Every divider in the rate table is + * 12000000/rate, so 12 MHz is the case this board runs and 2 is the value + * stock selects. + */ +static void cs42_soc_clk_divider(struct cs42l81 *c) +{ + void (*divider)(unsigned int); + + divider = (void (*)(unsigned int)) + __symbol_get("s5l8740_codec_clk_divider"); + if (!divider) { + dev_warn_once(&c->spi->dev, + "codec clock divider unavailable\n"); + return; + } + divider(mclk_khz == 12000 ? 2 : 4); + __symbol_put("s5l8740_codec_clk_divider"); +} + +/* ------------------------------------------------------------------ */ +/* Gain */ +/* ------------------------------------------------------------------ */ + +/* sub_D2C98: dB to register code. 1 dB per step to -50, 2 dB below it. */ +static int cs42_db_to_code(int db) +{ + if (db > CS42_DB_MAX) + db = CS42_DB_MAX; + if (db < CS42_DB_MIN) + db = CS42_DB_MIN; + if (db > CS42_DB_KNEE) + return db; + if (db & 1) + db--; + return CS42_DB_KNEE + (db - CS42_DB_KNEE) / 2; +} + +/* sub_3C6244: register code back to dB. Code -64 is the mute entry. */ +static int cs42_code_to_db(u8 raw) +{ + int code = (s8)((raw & 0x40) ? (raw | 0x80) : (raw & 0x7f)); + + if (code == -64) + return -90; + if (code < CS42_DB_KNEE) + return 2 * code + 50; + return code; +} + +/* + * sub_400330: write the DAC gain, and move the 2.5 V backpower rail across + * the -8 dB threshold. + * + * The rail is the class-H supply for the headphone amplifier. Stock raises + * it when the gain comes up through -8 dB and, going the other way, arms a + * 601 ms timer and drops it when that expires. The delayed drop is not + * implemented: leaving the rail up is the safe direction, and the timer only + * saves idle current. + */ +static int cs42_dac_gain(struct cs42l81 *c, int code) +{ + int new_db = cs42_code_to_db(code & 0x7f); + int old_db; + u8 raw = 0; + int ret; + + ret = cs42_rd(c, 0x0227, &raw); + old_db = ret ? -90 : cs42_code_to_db(raw); + + if (old_db < CS42_RAIL_DB && new_db >= CS42_RAIL_DB) { + u8 rail = 0, comp = 0; + + if (!cs42_rd(c, 0xc96f, &rail) && + !cs42_rd(c, 0x0219, &comp) && + (rail != 0x1e || (comp & 0x07) != 0x01)) { + cs42_wr8(c, 0xc96f, 0x0e); + cs42_rmw(c, 0x0219, 0x07, 0x01); + /* + * sub_345D58(155). Both delay helpers call the same + * primitive; sub_345D48 multiplies its argument by + * 1000 first, so the primitive's unit is microseconds + * and this settle is 155 us, not milliseconds. + */ + usleep_range(155, 200); + cs42_wr8(c, 0xc96f, 0x1e); + } + } else if (old_db >= CS42_RAIL_DB && new_db < CS42_RAIL_DB) { + u8 rail = 0, comp = 0; + + /* + * The other half of the pair, sub_1883A8: drop the 2.5 V + * backpower and clear the compensation bits. + * + * Stock gates it on its own shadow, MEMORY[0x892A024], which + * is set when the rail was raised and cleared here. This + * driver has no such shadow, so it reads the two registers + * instead and only acts when they are in the raised state -- + * the same test the raise above uses, inverted. + * + * Without this the rail stays at 0x1E and 0x0219[2:0] stays + * at 1 for the life of the part once any gain has crossed + * the threshold, because nothing else ever writes them down. + */ + if (!cs42_rd(c, 0xc96f, &rail) && + !cs42_rd(c, 0x0219, &comp) && + (rail == 0x1e || (comp & 0x07) == 0x01)) { + cs42_wr8(c, 0xc96f, 0x0e); + cs42_rmw(c, 0x0219, 0x07, 0x00); + } + } + + return cs42_wr16(c, 0x0227, code & 0x7f); +} + +/* + * sub_3C36A4(sel, 3, dB) for the two selectors this driver uses. Selector 0 + * is the DAC pair at 0x0227 and always routes through sub_400330; selector 1 + * is the pair at 0x0229 and is a plain wide write. + */ +static int cs42_channel_gain(struct cs42l81 *c, int sel, int db) +{ + if (sel == 0) + return cs42_dac_gain(c, cs42_db_to_code(db)); + return cs42_wr16(c, 0x0229, cs42_db_to_code(db) & 0x7f); +} + +/* + * sub_D2F2C, the gain re-apply the playback engine runs before it submits + * its first buffer: right channel first, then left. This is why 0x0229 + * carries a real gain in stock rather than the constant its power-up leaves. + */ +static int cs42_apply_gains(struct cs42l81 *c) +{ + int ret = cs42_channel_gain(c, 1, c->gain_r_db); + + if (ret) + return ret; + return cs42_channel_gain(c, 0, c->gain_l_db); +} + +static int cs42_apply_user_vol(struct cs42l81 *c) +{ + c->gain_l_db = (int)c->user_vol + CS42_DB_MIN; + c->gain_r_db = c->gain_l_db; + return cs42_apply_gains(c); +} + +/* ------------------------------------------------------------------ */ +/* Power states -- sub_D3280 */ +/* ------------------------------------------------------------------ */ + +/* sub_D2EFC: the 0x9901 key, once per boot, from inside state 3. */ +static void cs42_user_key(struct cs42l81 *c) +{ + if (c->key_done) + return; + cs42_wr8(c, 0x9901, 0xa5); + cs42_wr8(c, 0x9901, 0x00); + c->key_done = true; +} + +/* + * sub_D3280(1). Configures the analog block, waits for it to report ready, + * raises the freeze latch and drops the codec clock. State 3 is the matching + * half; stock never runs one without the other. + */ +static int cs42_state_park(struct cs42l81 *c) +{ + unsigned int i; + u8 v2f = 0; + int ret; + + /* + * The codec clock has to be running before any of this is written. + * + * sub_D3280(a1 == 1) does not enable the clock -- it *ends* by + * dropping it, via sub_41CBD8(v6, 0), and simply assumes it was on + * when it was entered. Stock always satisfies that: the bootloader's + * own analog power-up (sub_1310) runs with the clock on, and every + * later entry comes from state 3, which leaves it on. + * + * Nothing in this driver satisfied it. State 3 was the only place + * that ever turned the clock on, and it runs *after* this, so every + * park wrote its whole sequence into a gated bus. The writes were + * issued, the SPI transfers completed, and nothing landed: 0x0225 + * stayed 0x00 instead of 0x33 and 0x0220 kept none of the 0x78 -- + * which is the analog output block never being powered up at all, + * while the codec still answered reads and every register state 3 + * touches looked perfect. + * + * Establish the precondition here rather than in the caller, so it + * holds for every entry to park, not just the one through prepare. + */ + cs42_soc_clk_divider(c); + cs42_soc_clk_gate(c, true); + + /* + * The 0x9901 key has to be in before the 0x02xx writes below, or the + * codec discards them. + * + * sub_D2EFC applies it once per boot and records that in + * MEMORY[0x892A028]; stock issues it from state 3, which in its flow + * has already run long before any state 1. This driver enters park + * first, so the key was still outstanding and every 0x02xx write in + * this function was rejected -- silently, because the SPI transfer + * itself completes normally. + * + * The evidence is in a trace_spi capture: park issues + * 6c 02 a5 01 33 33 0x0225 = 0x33 + * 6c 02 20 00 78 0x0220 = 0x78 + * and state 3's very next read-modify-write of 0x0220 stores 0x28, + * which is (0x00 & ~0x28) | 0x28 -- so the read came back 0x00 and + * neither write had taken. Page 0x00 writes in the same sequence + * (0x0075, 0x0006, 0x0007) all landed, and everything after the key + * landed. 0x0225 is the analog output register, so what this cost was + * the analog block never powering up, with the codec still answering + * reads and every register state 3 touches looking correct. + * + * cs42_user_key() is idempotent via c->key_done, so state 3's call + * stays where stock has it and simply becomes a no-op after this. + */ + cs42_user_key(c); + + ret = cs42_wr16(c, 0x0227, 0x40); + if (ret) + return ret; + ret = cs42_wr16(c, 0x0225, 0x33); + if (ret) + return ret; + ret = cs42_wr16(c, 0x0229, 0x40); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0075, 0x80, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0220, 0x78, 0x78); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0006, 0x01, 0x01); + if (ret) + return ret; + + /* + * Stock spins here forever. sub_D3280 at EA 0x0D347C is + * + * delay(1); read(0x002F); if (!(v & 0x80)) goto back; + * + * with no counter, no deadline and no error arm -- the bpl is the + * only way out. That is the one comment in this driver the decomp + * actually backs. + * + * We cannot copy it: an unbounded spin here wedges whichever thread + * ALSA called us on. The 1 ms per iteration matches stock's delay(1) + * argument; the count of 50 is OURS and has no stock basis. + * + * What we must not do is carry on. This used to warn and fall + * through into the 0x0006 and 0x0007 writes below -- writes stock + * can never reach without bit 7 set, because it is still in the + * loop. Continuing puts the codec in a state stock never produces, + * which is worse than failing. Fail instead. + */ + for (i = 0; i < CS42_READY_POLLS; i++) { + usleep_range(1000, 1500); + ret = cs42_rd(c, 0x002f, &v2f); + if (ret) { + dev_err(&c->spi->dev, + "0x2F read failed (%d) while waiting for analog ready\n", + ret); + return ret; + } + if (v2f & 0x80) + break; + } + if (!(v2f & 0x80)) { + dev_err(&c->spi->dev, + "analog block not ready after %u polls (0x2F=0x%02x)\n", + i, v2f); + return -ETIMEDOUT; + } + + ret = cs42_rmw(c, 0x0006, 0x40, 0x40); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0007, 0x40, 0x40); + if (ret) + return ret; + + cs42_soc_clk_gate(c, false); + return 0; +} + +/* + * sub_D3280(3). Selects the codec clock divider, turns the clock back on, + * sends the key once, releases the freeze latch and reads the two trim bytes + * stock caches at MEMORY[0x892A054]. + */ +static int cs42_state_run(struct cs42l81 *c) +{ + u8 r74 = 0, trim0 = 0, trim1 = 0; + int ret; + + cs42_soc_clk_divider(c); + cs42_soc_clk_gate(c, true); + cs42_user_key(c); + + ret = cs42_rmw(c, 0x0007, 0x40, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0006, 0x40, 0x00); + if (ret) + return ret; + c->mode38 = 0x28; + ret = cs42_rmw(c, 0x0220, 0x28, 0x28); + if (ret) + return ret; + ret = cs42_rmw(c, 0x000f, 0x80, 0x80); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0075, 0x40, 0x40); + if (ret) + return ret; + + ret = cs42_rd(c, 0x0074, &r74); + if (ret) + return ret; + cs42_wr8(c, 0x0074, (r74 & 0xe7) | 0x08); + cs42_rd(c, 0x007b, &trim0); + cs42_rd(c, 0x007c, &trim1); + cs42_wr8(c, 0x0074, r74); + + ret = cs42_rmw(c, 0x0075, 0x40, 0x00); + if (ret) + return ret; + return cs42_rmw(c, 0x0075, 0x80, 0x80); +} + +/* ------------------------------------------------------------------ */ +/* Path mode -- sub_D2F64 / sub_D2D2C */ +/* ------------------------------------------------------------------ */ + +/* + * sub_D2F64 evaluated for the three modes this driver uses. The skeleton is + * fixed and only the values and two conditional blocks differ, so the shape + * is written once and the per-mode values come from a table. + * + * 0x0204 and 0x0203 are issued even when their mask is zero, because stock + * issues them: sub_42A5D6 is a read and a write regardless of the mask. + */ +struct cs42_path_mode { + u8 r06; /* 0x0006 mask 0x04 */ + u8 r220; /* 0x0220 mask 0x28 */ + bool has_r0d; /* 0x000D written at all */ + u8 r0d; + u8 r206; /* 0x0206 and 0x0207, mask 0x3F */ + bool has_r205; /* 0x0205 written at all */ + u8 r205; + u8 r204_mask; /* 0x0204 -- issued even when this is zero */ + u8 r204; + u8 r203; /* 0x0203 mask 0xC0 */ + bool has_eq; /* 0x000E / 0x011F / 0x0120 / 0x012E block */ + unsigned int settle_ms; + u8 mode38; /* MEMORY[0x892A038] afterwards */ +}; + +/* sub_D2F64(271), reached through sub_D2D2C(1). */ +static const struct cs42_path_mode cs42_mode_271 = { + .r06 = 0x00, .r220 = 0x00, + .has_r0d = false, + .r206 = 0x3d, + .has_r205 = true, .r205 = 0x5a, + .r204_mask = 0x00, .r204 = 0x00, + .r203 = 0x00, + .has_eq = true, + .settle_ms = 105, + .mode38 = 0x00, +}; + +/* sub_D2F64(18), applied by the playback engine before its first buffer. */ +static const struct cs42_path_mode cs42_mode_18 = { + .r06 = 0x04, .r220 = 0x08, + .has_r0d = true, .r0d = 0x00, + .r206 = 0x34, + .has_r205 = false, + .r204_mask = 0x03, .r204 = 0x00, + .r203 = 0xc0, + .has_eq = false, + .settle_ms = 60, + .mode38 = 0x08, +}; + +/* sub_D2F64(6), the companion of sub_D2D2C(0). */ +static const struct cs42_path_mode cs42_mode_6 = { + .r06 = 0x04, .r220 = 0x00, + .has_r0d = true, .r0d = 0x00, + .r206 = 0x34, + .has_r205 = false, + .r204_mask = 0x03, .r204 = 0x00, + .r203 = 0xc0, + .has_eq = false, + .settle_ms = 60, + .mode38 = 0x00, +}; + +static int cs42_set_rate(struct cs42l81 *c, unsigned int rate); + +static int cs42_path_mode(struct cs42l81 *c, const struct cs42_path_mode *m) +{ + int ret; + + ret = cs42_rmw(c, 0x0006, 0x04, m->r06); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0220, 0x28, m->r220); + if (ret) + return ret; + c->mode38 = m->mode38; + if (m->has_r0d) { + ret = cs42_rmw(c, 0x000d, 0x03, m->r0d); + if (ret) + return ret; + } + ret = cs42_rmw(c, 0x0206, 0x3f, m->r206); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0207, 0x3f, m->r206); + if (ret) + return ret; + if (m->has_r205) { + ret = cs42_rmw(c, 0x0205, 0xff, m->r205); + if (ret) + return ret; + } + ret = cs42_rmw(c, 0x0204, m->r204_mask, m->r204); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0206, 0xc0, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0207, 0xc0, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0203, 0xc0, m->r203); + if (ret) + return ret; + + if (m->has_eq) { + ret = cs42_rmw(c, 0x000e, 0x40, 0x40); + if (ret) + return ret; + cs42_rmw(c, 0x011f, 0x3f, 0x1c); + cs42_rmw(c, 0x0120, 0x3f, 0x1c); + cs42_rmw(c, 0x012e, 0xff, 0xaa); + ret = cs42_rmw(c, 0x000e, 0x40, 0x00); + if (ret) + return ret; + } + + msleep(m->settle_ms); + + /* + * Re-apply the rate against the shadow this call just installed. + * + * sub_D2F64 ends with a BL to sub_18311C at EA 0x000D31F2 -- found + * by scanning the image for Thumb BL targets, because the .c export + * lists no call site. sub_18311C saves MEMORY[0x892A034], clears it, + * calls sub_D34C0 and restores it: re-run the rate programming. + * + * It matters because sub_D34C0 picks its arm on the shadow this + * function writes. 0x28 takes the short arm, which programs only the + * mute bracket, 0x000F and 0x012F; 0x08 takes the long arm, the only + * one that writes 0x0121, 0x0122, 0x0130, 0x0131, 0x0223, 0x0224 and + * 0x0222. cs42_state_run() sets the shadow to 0x28 at the top of + * every prepare, so without this second pass the converter registers + * keep their bring-up values -- the 48 kHz ones -- at every rate. + * + * Measured on a freshly power-cycled part playing 44.1 kHz without + * it: 0x0222 = 0x0c, 0x0130 = 0xcc, 0x0131 = 0x01, i.e. the codec + * configured for 48 kHz while the serialiser clocked 44117.65 Hz -- + * 2900/4900 Hz spurs at -33 dBc and 0.102% THD. With it, and the + * converter engaged, -55 dBc and 0.0067%. + */ + if (c->rate) + return cs42_set_rate(c, c->rate); + return 0; +} + +/* sub_D2D2C(1) */ +static int cs42_output_path_on(struct cs42l81 *c) +{ + int ret = cs42_rmw(c, 0x0206, 0x3f, 0x08); + + if (ret) + return ret; + ret = cs42_rmw(c, 0x0207, 0x3f, 0x08); + if (ret) + return ret; + return cs42_path_mode(c, &cs42_mode_271); +} + +/* sub_D2D2C(0): the mode runs first, then the path is taken down. */ +static int cs42_output_path_off(struct cs42l81 *c) +{ + int ret = cs42_path_mode(c, &cs42_mode_6); + + if (ret) + return ret; + ret = cs42_rmw(c, 0x0206, 0x3f, 0x00); + if (ret) + return ret; + return cs42_rmw(c, 0x0207, 0x3f, 0x00); +} + +/* + * sub_D3280(4) -- the ANALOG BRING-UP, not a power-down. + * + * This was called cs42_state_standby and described as "a power-down: + * analog enable off, rail down, gain to the mute code". The register + * sequence below is a byte-exact transliteration and always was; only the + * name and the call site were wrong, and the name is what kept it off the + * playback path. + * + * What settles it is the state applier sub_D3700, which IDA omits, read out + * of the raw image at 0x000D3752: + * + * d3756 cmp r1, #4 ; target mode + * d3758 bne 0xd3768 + * d375a cmp r0, #3 ; current mode + * d375c bcs 0xd3768 ; skip when current >= 3 + * d375e movs r0, #3 + * d3760 bl 0xd3280 ; sub_D3280(3) FIRST + * d3768 ldrb r0, [r5, #4] + * d376a bl 0xd3280 ; then sub_D3280(target) + * + * Mode 4 is staged THROUGH mode 3. Nothing stages through the run state to + * reach a power-down; 1 (park) -> 3 (run) -> 4 is a climb. + * + * The body agrees. From osos.dec.bin.ida.c, decimal register numbers + * decoded: 0x219 |= 0x78, 0x201[7:5] = 0x40, 0xC81F = 0xFF, 0xC85F = 0x0F, + * 0xC96F = 0x0E (the 2.5 V backpower), 0x223 = 0x08, 0x224 = 0x09, then + * sub_400330(64)/(65) applying gain, 0x229/0x22A = 0x41, and finally + * 0x00E[7:6] = 0x40 closing the config guard. That is an output stage being + * powered and gained, not torn down. + * + * Corroborated by measurement: our live codec dump read 0x0219 = 0x00, + * i.e. that 0x78 was never set, and the device produces no plop on reboot + * -- nothing to discharge, because the analog stage never came up. + */ +static int cs42_state_analog_on(struct cs42l81 *c) +{ + int ret; + + ret = cs42_rmw(c, 0x0007, 0x40, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0219, 0x78, 0x78); + if (ret) + return ret; + ret = cs42_wr16(c, 0x0229, 0x40); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0006, 0x01, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0201, 0xe0, 0x40); + if (ret) + return ret; + cs42_wr8(c, 0xc81f, 0xff); + cs42_wr8(c, 0xc85f, 0x0f); + cs42_wr8(c, 0xc96f, 0x0e); + cs42_wr8(c, 0x0223, 0x08); + cs42_wr8(c, 0x0224, 0x09); + cs42_wr16(c, 0x0225, 0x00); + + cs42_dac_gain(c, 0x40); + cs42_dac_gain(c, 0x41); + c->gain_l_db = cs42_code_to_db(0x41); + cs42_wr16(c, 0x0229, 0x41); + c->gain_r_db = cs42_code_to_db(0x41); + + ret = cs42_rmw(c, 0x000e, 0xc0, 0x40); + if (!ret) + c->state = CS42_STANDBY; + return ret; +} + +/* ------------------------------------------------------------------ */ +/* Sample rate -- sub_D34C0 / sub_183138 */ +/* ------------------------------------------------------------------ */ + +/* sub_183138(code, 1). Ends muted with the 0x0220 bit-5 hold raised. */ +static int cs42_set_rate_183138(struct cs42l81 *c, u8 code) +{ + int ret; + + ret = cs42_rmw(c, 0x000e, 0xc0, 0xc0); + if (ret) + return ret; + ret = cs42_rmw(c, 0x000f, 0x0f, code); + if (ret) + return ret; + ret = cs42_wr8(c, 0x012f, code | (code << 4)); + if (ret) + return ret; + + if (code == 12) { + cs42_wr8(c, 0x010b, 0x08); + cs42_wr8(c, 0x010c, 0x09); + cs42_rmw(c, 0x0131, 0x01, 0x01); + } else { + cs42_wr8(c, 0x0121, 0x08); + cs42_wr8(c, 0x0122, 0x09); + cs42_rmw(c, 0x0130, 0x0f, code); + cs42_rmw(c, 0x0131, 0x01, 0x00); + cs42_wr8(c, 0x010b, 0x04); + cs42_wr8(c, 0x010c, 0x33); + } + + ret = cs42_rmw(c, 0x000e, 0xc0, 0x40); + if (ret) + return ret; + ret = cs42_dac_gain(c, 0x40); + if (ret) + return ret; + return cs42_rmw(c, 0x0220, 0x20, 0x20); +} + +/* sub_D34C0, short branch: mode38 carries both 0x08 and 0x20. */ +static int cs42_set_rate_short(struct cs42l81 *c, u8 code) +{ + int ret = cs42_rmw(c, 0x000e, 0xc0, 0xc0); + + if (ret) + return ret; + ret = cs42_rmw(c, 0x000f, 0x0f, code); + if (ret) + return ret; + ret = cs42_wr8(c, 0x012f, code | (code << 4)); + if (ret) + return ret; + + return cs42_rmw(c, 0x000e, 0xc0, 0x40); +} + +/* + * sub_D34C0, long branch: mode38 carries 0x08 but not 0x20. Mutes, raises + * the hold, programs the rate inside the 0x000E bracket, drops the hold and + * restores the cached gain. + * + * Stock chooses between the native and converted arms with two config-flag + * lookups crossed with the rate. Without that config the native case is + * taken to be rate code 12 exactly, which is the test sub_183138 uses. + */ +static int cs42_set_rate_long(struct cs42l81 *c, u8 code) +{ + /* + * Stock's condition is + * + * (sub_149E98(126) && rate != 48000) || + * (rate == 32000 && !sub_149E98(127)) + * + * where sub_149E98(n) is *(u32 *)(0x8A8F9DC + 4n) != 0. + * + * That table is resolvable. Its initialiser zeroes all 0xBB entries + * and then populates them only when sub_16297C("_enable_options", ..) + * succeeds and reports the file present -- an Apple diagnostic options + * file under iPod_Control\Device\. When it is absent the function + * returns with the table still entirely zero, which is the retail + * case. + * + * With entries 126 and 127 zero the first clause is false and the + * second reduces to rate == 32000, i.e. rate code 9. Every other rate + * takes the native arm here. + * + * Note this is the opposite sense to sub_183138, which tests its rate + * code against 12 directly in the instruction stream. The two + * functions genuinely differ; they are not two spellings of one test. + */ + /* + * src_mode: 0 = stock condition (default), 1 = force SRC on, + * 2 = force SRC off. An experiment handle, not a feature. + * + * The stock condition above reduces to "rate == 32000" only if + * sub_149E98(126) is zero, which is what we concluded from the + * _enable_options file being absent on a retail device. That + * conclusion is worth testing, because 44.1 kHz is measurably + * distorted and 48 kHz is not, and the difference between them is + * exactly the thing an SRC would absorb: + * + * 48000 -> clkdiv 250, 12 MHz / 250 = 48000.00 exactly + * 44100 -> clkdiv 272, 12 MHz / 272 = 44117.65 + * + * No integer divider off a 12 MHz reference produces 44100, so at + * "44.1" the serialiser really clocks 44117.6 while the codec is + * told rate code 10. If sub_149E98(126) is in fact non-zero on real + * hardware, stock's condition is true for every rate but 48000 and + * stock engages the SRC exactly where we do not. + */ + /* + * Sample-rate-converter selection, stock's condition. + * + * src_mode overrides it for comparison: 1 forces on, 2 forces off. + */ + /* + * Engage the converter for every rate except 48 kHz. + * + * Stock's test is + * + * sub_149E98(126) && rate != 48000 || rate == 32000 && !sub_149E98(127) + * + * and it was implemented here as "rate == 32000", which is what it + * reduces to only if sub_149E98(126) is zero. Measured at the jack, + * that assumption is wrong: with the converter bypassed a 1 kHz tone + * at 44.1 kHz shows 2900/4900 Hz spurs at -33 dBc and 0.102% THD, + * and with it engaged the same tone gives -55 dBc and 0.0067% -- + * better THD than the 48 kHz control. So sub_149E98(126) is + * non-zero on this hardware and the condition is "not 48 kHz". + * + * 48 kHz is the one rate a 12 MHz reference divides exactly + * (12e6/250), so it is also the one rate with nothing for the + * converter to correct. + */ + bool src = code != 12; + + if (src_mode == 1) + src = true; + else if (src_mode == 2) + src = false; + int ret; + + ret = cs42_dac_gain(c, 0x40); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0220, 0x20, 0x20); + if (ret) + return ret; + msleep(50); + + ret = cs42_rmw(c, 0x000e, 0xc0, 0xc0); + if (ret) + return ret; + cs42_rmw(c, 0x000f, 0x0f, code); + cs42_wr8(c, 0x012f, code | (code << 4)); + + if (src) { + cs42_wr8(c, 0x0121, 0x08); + cs42_wr8(c, 0x0122, 0x09); + cs42_rmw(c, 0x0130, 0x0f, code); + cs42_rmw(c, 0x0131, 0x01, 0x00); + cs42_wr8(c, 0x0223, 0x04); + cs42_wr8(c, 0x0224, 0x33); + } else { + /* src_mode=2 only: bypass, for comparison against the above. */ + cs42_rmw(c, 0x0131, 0x01, 0x01); + cs42_wr8(c, 0x0223, 0x08); + cs42_wr8(c, 0x0224, 0x09); + } + cs42_wr8(c, 0x0222, src ? 12 : code); + + ret = cs42_rmw(c, 0x000e, 0xc0, 0x40); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0220, 0x20, 0x00); + if (ret) + return ret; + msleep(60); + + return cs42_apply_gains(c); +} + +/* sub_D34C0's three-way dispatch, on the shadow stock maintains. */ +static int cs42_set_rate(struct cs42l81 *c, unsigned int rate) +{ + const struct n31_rate_cfg *r = n31_find_rate(rate); + int ret; + + if (!r) + return -EINVAL; + + if (!(c->mode38 & 0x08)) + ret = cs42_set_rate_183138(c, r->cs42_rate_code); + else if (c->mode38 & 0x20) + ret = cs42_set_rate_short(c, r->cs42_rate_code); + else + ret = cs42_set_rate_long(c, r->cs42_rate_code); + + if (!ret) + c->rate = rate; + return ret; +} + +/* ------------------------------------------------------------------ */ +/* Play graph -- sub_570620 / sub_5707D8 / sub_165BD4 */ +/* ------------------------------------------------------------------ */ + +/* sub_5707D8, the static image, verbatim. */ +static const struct cs42_reg cs42_graph_static[] = { + { 0x0006, 0x24 }, + { 0x0529, 0x2c }, { 0x052a, 0x2c }, { 0x0533, 0x2c }, { 0x0534, 0x2c }, + + { 0x0400, 0x04 }, { 0x0401, 0x10 }, { 0x0402, 0x00 }, + { 0x0403, 0x09 }, { 0x0404, 0x08 }, { 0x0405, 0x00 }, { 0x0406, 0x00 }, + + { 0x0407, 0x00 }, { 0x0408, 0x01 }, { 0x0409, 0xe0 }, + { 0x040a, 0x01 }, { 0x040b, 0x01 }, { 0x040c, 0xe0 }, + { 0x040d, 0xfe }, { 0x040e, 0x00 }, { 0x040f, 0xa0 }, + { 0x0410, 0x02 }, { 0x0411, 0x00 }, { 0x0412, 0x00 }, + { 0x0413, 0x03 }, { 0x0414, 0x00 }, { 0x0415, 0x00 }, + { 0x0416, 0x04 }, { 0x0417, 0x00 }, { 0x0418, 0x00 }, + { 0x0419, 0x05 }, { 0x041a, 0x00 }, { 0x041b, 0x00 }, + { 0x041c, 0x06 }, { 0x041d, 0x00 }, { 0x041e, 0x00 }, + { 0x041f, 0x07 }, { 0x0420, 0x00 }, { 0x0421, 0x00 }, + { 0x0422, 0x08 }, { 0x0423, 0x00 }, { 0x0424, 0x00 }, + { 0x0425, 0x09 }, { 0x0426, 0x00 }, { 0x0427, 0x00 }, + + { 0x0428, 0x0a }, { 0x0429, 0x01 }, { 0x042a, 0xe0 }, + { 0x042b, 0x0b }, { 0x042c, 0x01 }, { 0x042d, 0xe0 }, + { 0x042e, 0xff }, { 0x042f, 0x00 }, { 0x0430, 0xa0 }, + { 0x0431, 0x0c }, { 0x0432, 0x00 }, { 0x0433, 0x00 }, + { 0x0434, 0x0d }, { 0x0435, 0x00 }, { 0x0436, 0x00 }, + { 0x0437, 0x0e }, { 0x0438, 0x00 }, { 0x0439, 0x00 }, + { 0x043a, 0x0f }, { 0x043b, 0x00 }, { 0x043c, 0x00 }, + { 0x043d, 0x10 }, { 0x043e, 0x00 }, { 0x043f, 0x00 }, + { 0x0440, 0x11 }, { 0x0441, 0x00 }, { 0x0442, 0x00 }, + { 0x0443, 0x12 }, { 0x0444, 0x00 }, { 0x0445, 0x00 }, + { 0x0446, 0x13 }, { 0x0447, 0x00 }, { 0x0448, 0x00 }, + + { 0x0400, 0x04 }, { 0x0401, 0x12 }, +}; + +static int cs42_play_graph_static(struct cs42l81 *c) +{ + u8 status = 0; + int ret; + + ret = cs42_write_table(c, cs42_graph_static, + ARRAY_SIZE(cs42_graph_static)); + if (ret) + return ret; + msleep(100); + /* + * No 0x0500 write here. This used to do cs42_wr8(c, 0x0500, 0x05) + * and it was ours, not stock's. + * + * Stock writes 0x0500 exactly once in the whole image, at EA + * 0x00570AF4 inside sub_5707D8 -- an override branch reached only + * when MEMORY[0x8A8FB58] is non-zero, sitting between a 0x401 = 0x12 + * write and a 0x528 status read. It is not on the audio path and it + * is not a sample-rate or bit-depth setting. + * + * The 0x400/0x500/0x51E-0x534 space on this part is the DSP and + * write-sequencer subsystem, not the serial audio port. This write + * was put here on the assumption that 0x0500 was AIF1_BCLK_Ctrl. + * That assumption is unsupported. + */ + cs42_rd(c, 0x0528, &status); + if (debug_regs) + dev_info(&c->spi->dev, "graph: 0x528=0x%02x\n", status); + return 0; +} + +/* + * sub_165BD4(side): eleven slots of three registers each. The source index + * runs from 0 on the left and 10 on the right; the terminator marks the slot + * that carries the tail value. + */ +static int cs42_slot_map(struct cs42l81 *c, int side, u8 count, u16 base_val, + u16 gain) +{ + u16 base_reg = side ? 0x0428 : 0x0407; + u8 source = side ? 10 : 0; + u8 term = side ? 0xff : 0xfe; + int i, ret; + + for (i = 0; i < 11; i++) { + u16 reg = base_reg + 3 * i; + u8 a, b, cc; + + if (i < count) { + a = source++; + b = gain >> 8; + cc = gain & 0xff; + } else if (i == count && base_val) { + a = term; + b = base_val >> 8; + cc = base_val & 0xff; + } else if (i == 10 && !base_val) { + a = term; + b = 0; + cc = 0; + } else { + a = source++; + b = 0; + cc = 0; + } + + ret = cs42_wr8(c, reg, a); + if (ret) + return ret; + ret = cs42_wr8(c, reg + 1, b); + if (ret) + return ret; + ret = cs42_wr8(c, reg + 2, cc); + if (ret) + return ret; + } + return 0; +} + +/* + * sub_570620's dynamic branch. + * + * sub_5706F4 installs the per-side counts and bases, transcribed from its + * switch: + * + * route 1 count_l 0, count_r 0, base_l 0, base_r 0 + * route 3 count_l 0, count_r 0, base_l 160, base_r 160 + * route 4 count_l 2, count_r 0, base_l 160, base_r 160 + * + * sub_174E7C then derives the tap indices. Its accumulators are only + * refreshed when the matching count is non-zero, and the count multiplies + * the accumulator in the tap term, so a zero count drops the gain out: + * + * if (count_l) accum_l = (idx_l_lo + 1) * table[idx_l_hi] + * tap_l = (base_l + accum_l * count_l + 159) / 160 + 2 + * if (count_r) accum_r = (idx_r_lo + 1) * table[idx_r_hi] + * tap_r = (base_r + accum_r * count_r + 159) / 160 + 1 + * + * With sub_5706F4's count_r of 0 this yields tap_r = 2 on route 4, where + * sub_5707D8's static image writes 8. That is not a defect in either + * transcription: 8 requires count_r = 2, which sub_5706F4 never installs, so + * the static image was baked from a state this function does not produce. + * The left side matches exactly (9). + */ +static int cs42_play_graph_dynamic(struct cs42l81 *c, int mode) +{ + u8 count_l = (mode == 4) ? 2 : 0; + u8 count_r = 0; + u16 base = (mode == 1) ? 0 : 160; + u32 accum_l = count_l ? cs42_graph_accum() : 0; + u32 accum_r = count_r ? cs42_graph_accum() : 0; + u8 tap_l = (base + accum_l * count_l + 159) / 160 + 2; + u8 tap_r = (base + accum_r * count_r + 159) / 160 + 1; + u8 status = 0; + int i, ret; + + /* sub_174E38(side): the packed index nibbles, per active slot. */ + for (i = 0; i < count_l; i++) { + ret = cs42_rmw(c, 0x0529 + i, 0x3f, + CS42_IDX_HI | (CS42_IDX_LO << 4)); + if (ret) + return ret; + } + for (i = 0; i < count_r; i++) { + ret = cs42_rmw(c, 0x0533 + i, 0x3f, + CS42_IDX_HI | (CS42_IDX_LO << 4)); + if (ret) + return ret; + } + + ret = cs42_rmw(c, 0x054f, 0xf0, 0x00); + if (ret) + return ret; + cs42_rmw(c, 0x0401, 0x01, 0x00); + cs42_rmw(c, 0x0401, 0x02, 0x00); + cs42_wr8(c, 0x0402, 0x00); + cs42_wr8(c, 0x0403, tap_l); + cs42_wr8(c, 0x0404, tap_r); + cs42_wr8(c, 0x0405, 0x00); + cs42_wr8(c, 0x0406, 0x00); + + ret = cs42_slot_map(c, 0, count_l, base, accum_l); + if (ret) + return ret; + ret = cs42_slot_map(c, 1, count_r, base, accum_r); + if (ret) + return ret; + + ret = cs42_rmw(c, 0x0401, 0x02, 0x02); + if (ret) + return ret; + msleep(100); + cs42_rd(c, 0x0528, &status); + if (debug_regs) + dev_info(&c->spi->dev, + "graph: mode=%d taps=%u/%u 0x528=0x%02x\n", + mode, tap_l, tap_r, status); + return 0; +} + +static int cs42_play_graph(struct cs42l81 *c) +{ + if (graph_mode == 0) + return cs42_play_graph_static(c); + return cs42_play_graph_dynamic(c, graph_mode); +} + +/* ------------------------------------------------------------------ */ +/* Transport -- sub_42D364 / sub_F141C / sub_F1444 */ +/* ------------------------------------------------------------------ */ + +/* sub_F141C */ +static int cs42_analog_mute(struct cs42l81 *c, bool mute) +{ + return cs42_wr8(c, 0x0527, mute ? 0xff : 0x60); +} + +/* sub_F1444: bit-5 strobes on both mailbox FIFO control registers. */ +static void cs42_fifo_strobe(struct cs42l81 *c) +{ + cs42_rmw(c, 0x051e, 0x20, 0x20); + cs42_rmw(c, 0x051e, 0x20, 0x00); + cs42_rmw(c, 0x0523, 0x20, 0x20); + cs42_rmw(c, 0x0523, 0x20, 0x00); +} + +/* + * sub_42D364(1). The graph is already built by .prepare, so this is the + * unmute and the latch: bit 0 cleared and bit 1 set, which is what + * sub_570620 does around its own graph write. Bit 0 has to be cleared here + * because sub_42D364(0) sets it on every stop and nothing else takes it down. + */ +static int cs42_transport_start(struct cs42l81 *c) +{ + int ret; + + ret = cs42_analog_mute(c, c->dai_mute); + if (ret) + return ret; + + if (!c->graph_built) { + ret = cs42_play_graph(c); + if (ret) + return ret; + c->graph_built = true; + } else { + ret = cs42_rmw(c, 0x0401, 0x01, 0x00); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0401, 0x02, 0x02); + if (ret) + return ret; + } + + /* + * sub_42D364(2) -- the second transport call, which this driver never + * made. + * + * Starting playback is two steps in stock, not one: + * + * sub_42D364(1): 0x892A058 = 1, sub_F141C(1) (the 0x0527 analog + * unmute), then sub_570620(1) + * sub_42D364(2): resolves to 3 or 4, then sub_570620(3 or 4) + * + * and the resolution is + * + * a1 = (MEMORY[0x892A038] & 8) ? 3 : 4; + * + * where 0x892A038 is the shadow of 0x0220 & 0x28 -- so the mode-18 + * path leaves it 0x08 and selects 3, while 271 and 6 select 4. + * + * This matters because sub_5706F4 gives mode 1 a count and a tail of + * zero, which makes sub_165BD4 emit a slot map that is gain 0 all the + * way down. Stopping after step 1, as this function did, therefore + * left the mixer programmed to silence. Modes 3 and 4 both carry a + * tail of 160, and 4 additionally carries two live slots. + * + * Only the dynamic path takes this step: stock's static-blob branch + * calls sub_5707D8 and returns without going near sub_5706F4. + */ + if (graph_mode != 0) { + int mode2 = (c->mode38 & 0x08) ? 3 : 4; + + ret = cs42_play_graph_dynamic(c, mode2); + if (ret) + return ret; + dev_dbg(&c->spi->dev, + "transport: stage 2 graph mode=%d (mode38=0x%02x)\n", + mode2, c->mode38); + } + + c->state = CS42_PLAYING; + return 0; +} + +/* sub_42D364(0) */ +static int cs42_transport_stop(struct cs42l81 *c) +{ + int ret = cs42_analog_mute(c, true); + + if (ret) + return ret; + ret = cs42_rmw(c, 0x0401, 0x01, 0x01); + if (ret) + return ret; + ret = cs42_rmw(c, 0x0401, 0x02, 0x00); + if (ret) + return ret; + cs42_fifo_strobe(c); + if (c->state == CS42_PLAYING) + c->state = CS42_PREPARED; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Lifecycle */ +/* ------------------------------------------------------------------ */ + +static void cs42_log_state(struct cs42l81 *c, const char *tag) +{ + u8 r2f = 0, r401 = 0, r527 = 0, r219 = 0, rc96f = 0, r0f = 0; + + if (!debug_regs) + return; + cs42_rd(c, 0x002f, &r2f); + cs42_rd(c, 0x0401, &r401); + cs42_rd(c, 0x0527, &r527); + cs42_rd(c, 0x0219, &r219); + cs42_rd(c, 0xc96f, &rc96f); + cs42_rd(c, 0x000f, &r0f); + dev_info(&c->spi->dev, + "%s: vol=%u (%d dB) mute=%d 2F=%02x 401=%02x 527=%02x 219=%02x C96F=%02x 0F=%02x mode38=%02x\n", + tag, c->user_vol, c->gain_l_db, c->dai_mute, + r2f, r401, r527, r219, rc96f, r0f, c->mode38); +} + +/* + * The configuration stock performs between "a route exists" and "the first + * buffer is submitted", in stock's order: + * + * sub_D3280(1) analog config, clock off + * sub_D3280(3) divider, clock on, unfreeze + * sub_D2D2C(1) output path, which runs sub_D2F64(271) + * sub_D34C0 rate -- dispatches on the shadow D2F64 just wrote + * sub_F141C(1) analog unmute + * sub_570620 play graph + * sub_D2F64(18) the playback engine's path mode + * sub_D2F2C gain re-apply, right channel then left + * + * The rate call comes after the path mode because it dispatches on the + * shadow the path mode maintains; running it first takes a branch stock + * would never take. + */ + +static int cs42_prepare(struct cs42l81 *c, unsigned int rate) +{ + int ret; + + /* + * One line per bring-up. This is the sequence that plops -- the + * clock gate goes ON/OFF/ON and the analog stage powers -- so it + * needs to be visible when it happens more than once per boot, + * which is exactly the bug this caught. + */ + dev_info(&c->spi->dev, "prepare entry: state=%s rate=%u want=%u\n", + cs42_state_names[c->state], c->rate, rate); + + /* + * Already up at this rate: nothing to do. + * + * CS42_PLAYING used to fall through here, so a second hw_params on a + * running stream re-ran the whole bring-up underneath it -- park, + * clock gate off, run, clock gate on, analog stage on -- on a part + * that was already playing. Every one of those steps is a transient + * on the output, which is the "plop plop plop" before a track starts: + * measured as "prepare entry: state=playing" immediately followed by + * a clock gate OFF, once per mpg123 invocation. + * + * Stock brings the codec up once and leaves it up; there is no path + * in the image that re-parks a playing part to prepare it again. A + * genuine rate change still falls through, because the rate is part + * of the test. + */ + if ((c->state == CS42_PREPARED || c->state == CS42_PLAYING) && + c->rate == rate) + return 0; + + /* + * Two stages that are not codec register writes, and which a + * previous refactor of this file dropped entirely. + * + * The PMIC half trims the sibling LDOs the analog headphone path + * runs from (gpio-d1830.c, sub_23EC in the stock *bootloader* -- + * so nothing in our boot chain performs it). The tristar half + * reports the accessory routing. Both are weak references: these + * drivers are separate modules and either may be absent, so a + * missing symbol is logged and playback continues rather than + * failing. + */ + { + int (*rails)(void); + void (*ts_path)(struct device *); + + /* + * No PMIC call here. + * + * This used to __symbol_get("d1830_audio_rails") and run the whole + * board rail trim on every codec prepare, on the belief that it + * "trims the sibling LDOs the analog headphone path runs from". + * That belief has no support in the decomp, and the sequence it was + * replaying does not exist in the OS image at all. + * + * sub_23EC and sub_27F4 are BOOTLOADER functions. sub_23EC has one + * caller, sub_27F4 at EA 0x29DC; sub_27F4 has one caller, the C + * entry sub_E2C at EA 0x0E48 -- before DRAM init, before the OS + * image is loaded. The rewrite it performs, "bic r0,#0xd0 / orr + * r0,#0x10", occurs exactly once in the whole firmware, at + * bootloader offset 0x24F8. It occurs ZERO times in osos.dec.bin. + * + * The OS cannot write registers 0x14-0x17 even if it wanted to: its + * only path to them is sub_2D404 cases 1-4, whose sole caller + * sub_2A120 maps consumer ids 1..10 to rail indices 6..15, so those + * cases are dead code. Stock's audio path issues no PMIC + * transactions at all, and stock's own codec driver + * (sphwDACCS42L81.c, sub_400330) touches no rail -- it handles the + * 2.5 V back-power concern with a timestamp and a timer. + * + * And the trim has already run on this device: the as-found probe + * dump reads 0x14=0x0b 0x15=0x09 0x16=0x09 0x17=0x09, which are + * exactly the codes sub_23EC computes for fuse index 0. Replaying it + * from here re-cleared ACTIVE1 bits 6 and 7 with the panel live, + * which is the white screen, and it is what the guards in + * d1830_sec_trim_seq() were patching over. + */ + + ts_path = (void (*)(struct device *)) + __symbol_get("apple_tristar_log_audio_path"); + if (ts_path) { + ts_path(&c->spi->dev); + __symbol_put("apple_tristar_log_audio_path"); + } + } + + /* + * graph_built is NOT cleared here any more. + * + * sub_570620, the play graph, is not on the streaming path in stock at + * all. Its only caller is sub_42D364, and sub_42D364 has exactly four + * call sites: three in the route-event task at 0x08587E60, one in + * sub_500E54, and the codec output-enable op at 0x000D314C. None of + * them is the buffer/DMA path, and the MeCCAOutputTask preamble does + * not touch it. + * + * Clearing the flag here forced a full graph rebuild, and its 100 ms + * settle, on every .prepare -- which ALSA calls before every start and + * every xrun recovery. Stock pays that cost on a route change, not per + * stream. + */ + + ret = cs42_state_park(c); + if (ret) + return ret; + ret = cs42_state_run(c); + if (ret) + return ret; + + /* + * sub_D3280(4), which sub_D3700 reaches only after mode 3. We stopped + * at mode 3 and never ran this, so the analog output stage was never + * powered -- 0x0219 read 0x00 instead of 0x78 on a live device. + */ + ret = cs42_state_analog_on(c); + if (ret) + return ret; + + /* + * cs42_mailbox_drain() used to run here. Removed: stock's audio path + * does not drain the mailbox. + * + * 0x051E..0x0525 is the MikeyBus accessory message channel, not an + * audio block -- it just shares the 0x05xx page with the audio graph + * registers. Its users are sub_15A50C (latch/sample), sub_1326D2 + * (read-and-discard ack), sub_155D74 (the consumer of the + * _mikeybus_bulk_debug option), sub_14DD16, sub_15409C and sub_19A07C, + * and **none of them is reachable from the audio call closure** (94 + * functions, depth 4). + * + * What the audio path *does* touch there is sub_F1444, which strobes + * bit 5 of 0x051E and 0x0523 -- and that is implemented faithfully in + * cs42_fifo_strobe(). Draining the FIFO as well was this driver's own + * invention, carried over from a pre-refactor stage list. + */ + + /* + * Rate before the output path, which is the order stock uses and the + * reverse of what this function did. The path configuration depends + * on the rate that is already programmed, so enabling it first + * configures it against the previous rate. + */ + ret = cs42_set_rate(c, rate); + if (ret) + return ret; + ret = cs42_output_path_on(c); + if (ret) + return ret; + + ret = cs42_analog_mute(c, c->dai_mute); + if (ret) + return ret; + ret = cs42_play_graph(c); + if (ret) + return ret; + c->graph_built = true; + + /* + * cs42_mode_18 used to be applied here. It is removed, and the reason + * matters because it changes which graph the transport builds. + * + * sub_D2F64's argument decides the path shadow MEMORY[0x892A038]: + * + * if (a1 & 0x200) { ... } + * else { + * if (a1 & 2) {...} else v3 |= 0x20; + * if (a1 & 4) {...} else v3 |= 0x08; + * MEMORY[0x892A038] = v3 & 0x28; + * } + * + * with v3 = 0 on entry. So the shadow only becomes 0x08 for an + * argument with **bit 2 clear**, and 18 is such a value while 271 and + * 6 are not -- both of those leave it 0x00. + * + * That shadow is what sub_42D364(2) resolves on: + * + * a1 = (MEMORY[0x892A038] & 8) ? 3 : 4; + * + * so mode 18 steered the transport to graph mode 3, whose slot map + * carries a tail of 160 and **no per-slot gain at all**. Mode 4 + * carries two live slots at gain 480 -- and mode 4's output is + * byte-identical to the fixed-constant path in sub_5707D8, which is + * strong corroboration that mode 4 is the real playback graph. + * + * REFUTED, 2026-09-01. The paragraph that stood here claimed the only + * literal sub_D2F64 arguments in the image were 271 and 6, that the + * sole other call site only restored a saved mode from a struct field + * (ldrh r0, [r5, #0xe]), and therefore that "nothing applies 18". + * + * sub_D2F64 has THREE wrappers, not two: sub_D2D2C, the state applier + * at 0x000D3700 (which is the ldrh site, at EA 0x080D37B0), and + * sub_4142D0. sub_4142D0 dispatches slot +8 of the codec op table at + * 0x0891DE74, which is 0x080D2F65 -- so sub_4142D0(x) IS sub_D2F64(x). + * + * And it is called with the literal 18, at EA 0x080B5B1C, in the + * preamble of the MeCCAOutputTask -- the playback task itself, whose + * body is the unnamed function at 0x000B5AE0 reached through the + * thunk at 0x080A8E52. Two further literals exist on the record + * paths: 0x601 at 0x0826DE3E and 0xA01 at 0x0826DE50. + * + * So stock DOES apply 18, on the playback path, once per stream in + * the task preamble, immediately before the producer fills the first + * buffer and the PL080 channel is enabled. + * + * The consequence for the paragraph above is direct: with 18 applied, + * MEMORY[0x892A038] is 0x08, so sub_42D364(2) resolves to mode 3, not + * mode 4. The preference for mode 4 recorded above rests entirely on + * the refuted claim and must be re-derived before it is trusted. + * Left as it is for now rather than changed blind -- it is an audio + * path change and belongs in its own flash. + * + * cs42_output_path_on() already performs sub_D2D2C(1) -- 0x0206/0x0207 + * masked to 8, then mode 271 -- which leaves the shadow at 0. Dropping + * this call lets that stand, so the transport resolves to mode 4. + * + * Which argument sub_D2D2C takes on the music path is NOT established: + * it is reached only through a vtable and has no direct callers. It + * does not matter for this, because 271 and 6 both leave the shadow 0. + */ + /* + * A second 0x054F RMW used to sit here. Stock issues that register + * exactly once in the whole image -- sub_42A5D6(1359, 240, 0), as + * step 1 of sub_570620 -- which cs42_play_graph() already does. This + * one was ours, and repeating a masked write after the path mode is + * not something stock ever does. + */ + ret = cs42_apply_user_vol(c); + if (ret) + return ret; + + c->state = CS42_PREPARED; + cs42_log_state(c, "prepare"); + return 0; +} + +int cs42l81_play_prepare(void) +{ + struct cs42l81 *c = cs42l81_dev; + int ret; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + ret = cs42_prepare(c, c->rate ? c->rate : N31_RATE_DEFAULT); + mutex_unlock(&c->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs42l81_play_prepare); + +int cs42l81_play_start(void) +{ + struct cs42l81 *c = cs42l81_dev; + int ret; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + if (c->state != CS42_PREPARED && c->state != CS42_PLAYING) { + ret = cs42_prepare(c, c->rate ? c->rate : N31_RATE_DEFAULT); + if (ret) + goto out; + } + if (c->state == CS42_PLAYING) { + ret = 0; + goto out; + } + /* + * sub_D2F64(18), where stock has it: the MeCCAOutputTask preamble, + * at EA 0x080B5B1C, immediately before the producer callback fills + * the first buffer and the PL080 channel is enabled. sub_D2F2C + * (gains) follows at 0x080B5B20, which cs42_transport_start does. + * + * This driver applied no 18 at all, on a claim that nothing in the + * image applies it. That was refuted: sub_4142D0 is a third wrapper + * for sub_D2F64 -- it dispatches slot +8 of the codec op table at + * 0x0891DE74, which is 0x080D2F65 -- and it is called with the + * literal 18 on the playback path. + * + * Applying it sets the path shadow MEMORY[0x892A038] to 0x08, which + * is what the stage-2 graph resolves on: sub_42D364(2) computes + * (shadow & 8) ? 3 : 4. So this also moves that graph from 4 to 3 + * without a separate change -- cs42_transport_start already derives + * its mode from c->mode38. + * + * Once per stream start, like stock: the preamble runs once and the + * steady-state loop re-enters below it. + */ + ret = cs42_path_mode(c, &cs42_mode_18); + if (ret) { + dev_err(&c->spi->dev, "path mode 18 failed: %d\n", ret); + goto out; + } + + ret = cs42_transport_start(c); + cs42_log_state(c, "play_start"); +out: + mutex_unlock(&c->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs42l81_play_start); + +int cs42l81_play_stop(void) +{ + struct cs42l81 *c = cs42l81_dev; + int ret; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + ret = cs42_transport_stop(c); + cs42_log_state(c, "play_stop"); + mutex_unlock(&c->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs42l81_play_stop); + +/* + * The IIS driver drives the codec's clock role from its mastership profile. + * Stock sets 0x000F bit 7 once, in state 3, and never clears it. + */ +int cs42l81_set_clock_role(bool drive) +{ + struct cs42l81 *c = cs42l81_dev; + int ret; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + /* + * Stock SETS 0x000F bit 7 exactly once, at EA 0x080D3312 inside + * the sub_D3280(3) transition, and NEVER clears it anywhere in + * the image. Those are the only writes to bit 7 that exist. + * + * So clearing it, which this used to do whenever drive was + * false, has no counterpart in stock at all. Nothing in the + * decomp ties this bit to a clock role either -- that reading + * was ours. Set it or leave it; never clear it. + * + * Note 0x000F[3:0] in the same register is the sample-rate + * index, which is why this must stay a masked RMW. + */ + if (!drive) { + mutex_unlock(&c->lock); + return 0; + } + ret = cs42_rmw(c, 0x000f, 0x80, 0x80); + mutex_unlock(&c->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs42l81_set_clock_role); + +int cs42l81_pre_iis_start(void) +{ + return 0; +} +EXPORT_SYMBOL_GPL(cs42l81_pre_iis_start); + +/* + * Runs after the IIS transmitter is kicked. sub_183138 leaves the part muted + * at -90 dB by design, so the cached gain has to come back; stock does that + * from sub_D2F2C on the same edge. + */ +int cs42l81_post_iis_start(void) +{ + struct cs42l81 *c = cs42l81_dev; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + if (!c->dai_mute) { + cs42_analog_mute(c, false); + cs42_rmw(c, 0x0401, 0x02, 0x02); + } + cs42_apply_gains(c); + cs42_log_state(c, "post_iis"); + mutex_unlock(&c->lock); + return 0; +} +EXPORT_SYMBOL_GPL(cs42l81_post_iis_start); + +static void cs42_post_iis_workfn(struct work_struct *work) +{ + cs42l81_post_iis_start(); +} + +void cs42l81_schedule_post_iis(void) +{ + struct cs42l81 *c = cs42l81_dev; + + if (!c) + return; + mod_delayed_work(system_wq, &c->post_iis_work, msecs_to_jiffies(25)); +} +EXPORT_SYMBOL_GPL(cs42l81_schedule_post_iis); + +/* + * Called from the IIS stop path, which must not block: the work item takes + * c->lock and the caller may already hold it. The synchronous form belongs + * at remove, where the work genuinely must be over. + */ +void cs42l81_cancel_post_iis(void) +{ + struct cs42l81 *c = cs42l81_dev; + + if (!c) + return; + cancel_delayed_work(&c->post_iis_work); +} +EXPORT_SYMBOL_GPL(cs42l81_cancel_post_iis); + +/* Diagnostic, kept so the IIS driver's call site still links. */ +int cs42l81_asp_hold_light(void) +{ + struct cs42l81 *c = cs42l81_dev; + + if (!c) + return -ENODEV; + mutex_lock(&c->lock); + cs42_log_state(c, "asp_status"); + mutex_unlock(&c->lock); + return 0; +} +EXPORT_SYMBOL_GPL(cs42l81_asp_hold_light); + +/* + * The IIS driver used this to pick between two orderings of the codec and + * transport calls. Only stock's ordering exists now. + */ +int cs42l81_get_audio_path_mode(void) +{ + return 1; +} +EXPORT_SYMBOL_GPL(cs42l81_get_audio_path_mode); + +bool n31_audio_playback_active(void) +{ + struct cs42l81 *c = cs42l81_dev; + + return c && c->state == CS42_PLAYING; +} +EXPORT_SYMBOL_GPL(n31_audio_playback_active); + +/* ------------------------------------------------------------------ */ +/* ASoC */ +/* ------------------------------------------------------------------ */ + +static int cs42_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs42l81 *c = snd_soc_component_get_drvdata(dai->component); + unsigned int rate = n31_resolve_rate(params_rate(params)); + int ret; + + mutex_lock(&c->lock); + ret = cs42_prepare(c, rate); + mutex_unlock(&c->lock); + return ret; +} + +static int cs42_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cs42l81_schedule_post_iis(); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* The IIS CPU DAI owns the analog stop. */ + return 0; + default: + return -EINVAL; + } +} + +static const struct snd_soc_dai_ops cs42_dai_ops = { + .hw_params = cs42_hw_params, + .trigger = cs42_trigger, +}; + +static int cs42_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = CS42_VOL_MAX; + return 0; +} + +static int cs42_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct cs42l81 *c = snd_soc_component_get_drvdata(comp); + + ucontrol->value.integer.value[0] = c->user_vol; + return 0; +} + +static int cs42_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct cs42l81 *c = snd_soc_component_get_drvdata(comp); + long vol = ucontrol->value.integer.value[0]; + int changed; + + if (vol < 0 || vol > CS42_VOL_MAX) + return -EINVAL; + + mutex_lock(&c->lock); + changed = vol != c->user_vol; + c->user_vol = vol; + if (changed) + cs42_apply_user_vol(c); + mutex_unlock(&c->lock); + return changed; +} + +static int cs42_sw_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int cs42_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct cs42l81 *c = snd_soc_component_get_drvdata(comp); + + ucontrol->value.integer.value[0] = c->dai_mute ? 0 : 1; + return 0; +} + +static int cs42_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct cs42l81 *c = snd_soc_component_get_drvdata(comp); + bool mute = !ucontrol->value.integer.value[0]; + int changed; + + mutex_lock(&c->lock); + changed = mute != c->dai_mute; + c->dai_mute = mute; + if (changed) + cs42_analog_mute(c, mute); + mutex_unlock(&c->lock); + return changed; +} + +static const DECLARE_TLV_DB_SCALE(cs42_hp_tlv, CS42_DB_MIN * 100, 100, 0); + +static const struct snd_kcontrol_new cs42_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphones Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = cs42_vol_info, + .get = cs42_vol_get, + .put = cs42_vol_put, + .tlv.p = cs42_hp_tlv, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphones Playback Switch", + .info = cs42_sw_info, + .get = cs42_sw_get, + .put = cs42_sw_put, + }, +}; + +static int cs42_component_probe(struct snd_soc_component *component) +{ + struct cs42l81 *c = snd_soc_component_get_drvdata(component); + + c->component = component; + return 0; +} + +static const struct snd_soc_component_driver cs42_component = { + .probe = cs42_component_probe, + .idle_bias_on = 1, + .endianness = 1, + .controls = cs42_controls, + .num_controls = ARRAY_SIZE(cs42_controls), +}; + +static struct snd_soc_dai_driver cs42_dai = { + .name = "cs42l81-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + /* + * Both DAIs on the link have to allow a rate before ALSA will + * offer it, and this is the codec half. It carried the base + * mask while the CPU DAI carried the hi-res one, so 88.2 and + * 96 kHz were refused at open with hires=1 set and the i2s + * hw_params callback never even ran. + * + * The real gate stays in s5l8740-i2s.c: its component open + * only widens the runtime rates when hires is on, so with the + * default this mask being wider changes nothing. + */ + /* + * The codec side advertises the superset, including 44.1 kHz, + * which it has a rate code for (10). Whether 44.1 is offered + * to applications is decided by the CPU DAI, because the + * limitation is the 12 MHz reference rather than the codec: + * no integer divider yields 44100. ASoC intersects the two, + * so keeping the gate in one place keeps allow_44100 + * meaningful. + */ + .rates = N31_RATE_MASK, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cs42_dai_ops, +}; + +/* ------------------------------------------------------------------ */ +/* sysfs */ +/* ------------------------------------------------------------------ */ + +static ssize_t reg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + unsigned int reg, val; + int ret; + + if (sscanf(buf, "%x %x", ®, &val) != 2 || reg > 0xffff || val > 0xff) + return -EINVAL; + mutex_lock(&c->lock); + ret = cs42_reg_is_pair(reg) ? cs42_wr16(c, reg, val) + : cs42_wr8(c, reg, val); + mutex_unlock(&c->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(reg); + +static ssize_t rreg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + unsigned int reg; + u8 val = 0; + int ret; + + if (kstrtouint(buf, 0, ®) || reg > 0xffff) + return -EINVAL; + mutex_lock(&c->lock); + ret = cs42_rd(c, (u16)reg, &val); + mutex_unlock(&c->lock); + if (ret) + return ret; + dev_info(&c->spi->dev, "0x%04x = 0x%02x\n", reg, val); + return count; +} +static DEVICE_ATTR_WO(rreg); + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + u8 r2f = 0, r401 = 0, r527 = 0, rc96f = 0, r0f = 0; + + mutex_lock(&c->lock); + cs42_rd(c, 0x002f, &r2f); + cs42_rd(c, 0x0401, &r401); + cs42_rd(c, 0x0527, &r527); + cs42_rd(c, 0xc96f, &rc96f); + cs42_rd(c, 0x000f, &r0f); + mutex_unlock(&c->lock); + + return sysfs_emit(buf, + "state=%s rate=%u vol=%u/%u (%d dB) mute=%d mode38=0x%02x\n" + "0x002F=0x%02x 0x0401=0x%02x 0x0527=0x%02x 0x000F=0x%02x 0xC96F=0x%02x\n", + cs42_state_names[c->state], c->rate, c->user_vol, + CS42_VOL_MAX, c->gain_l_db, c->dai_mute, c->mode38, + r2f, r401, r527, r0f, rc96f); +} +static DEVICE_ATTR_RO(state); + +static ssize_t standby_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + int ret; + + mutex_lock(&c->lock); + ret = cs42_state_analog_on(c); + mutex_unlock(&c->lock); + return ret ? ret : count; +} +static DEVICE_ATTR_WO(standby); + +static struct attribute *cs42_attrs[] = { + &dev_attr_reg.attr, + &dev_attr_rreg.attr, + &dev_attr_state.attr, + &dev_attr_standby.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cs42); + +/* ------------------------------------------------------------------ */ +/* Driver */ +/* ------------------------------------------------------------------ */ + +/* + * Log the analog/backpower state exactly as the bootloader left it, before + * this driver writes anything. + * + * 0x002F carries the analog-ready and ASP-sync status bits; 0x0219 is the + * analog/backpower companion whose low three bits cs42_dac_gain drives to 1; + * 0xC96F is the 2.5 V backpower rail (0x0E during the settle, 0x1E when up). + * Reading them here is what tells us whether the codec arrives powered or + * whether our own prepare is the only thing that brings it up. + */ +static void cs42_dump_analog(struct cs42l81 *c, const char *tag) +{ + static const u16 regs[] = { 0x002f, 0x0219, 0xc96f }; + static const char *const names[] = { + "analog-ready/ASP-sync", "analog backpower companion", + "2.5V backpower" + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + u8 v = 0; + int ret = cs42_rd(c, regs[i], &v); + + if (ret) + dev_info(&c->spi->dev, "codec %s: 0x%04x %-26s read failed (%d)\n", + tag, regs[i], names[i], ret); + else + dev_info(&c->spi->dev, "codec %s: 0x%04x %-26s = 0x%02x\n", + tag, regs[i], names[i], v); + } +} + +static int cs42l81_probe(struct spi_device *spi) +{ + struct cs42l81 *c; + int ret; + + c = devm_kzalloc(&spi->dev, sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + c->spi = spi; + c->user_vol = CS42_VOL_DEFAULT; + c->gain_l_db = (int)c->user_vol + CS42_DB_MIN; + c->gain_r_db = c->gain_l_db; + c->rate = N31_RATE_DEFAULT; + c->state = CS42_PROBED; + mutex_init(&c->lock); + INIT_DELAYED_WORK(&c->post_iis_work, cs42_post_iis_workfn); + spi_set_drvdata(spi, c); + + ret = devm_snd_soc_register_component(&spi->dev, &cs42_component, + &cs42_dai, 1); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to register component\n"); + + ret = sysfs_create_groups(&spi->dev.kobj, cs42_groups); + if (ret) + return dev_err_probe(&spi->dev, ret, "failed to create sysfs\n"); + + cs42l81_dev = c; + + /* Baseline, before this driver writes a single register. */ + cs42_dump_analog(c, "as-found"); + dev_info(&spi->dev, "CS42L81 codec ready\n"); + return 0; +} + +static void cs42l81_remove(struct spi_device *spi) +{ + struct cs42l81 *c = spi_get_drvdata(spi); + + cs42l81_dev = NULL; + cancel_delayed_work_sync(&c->post_iis_work); + + /* + * Leave the part the way stock leaves it after a session rather than + * in whatever state the last stream produced. There is no reset line + * on this board, so the register file survives module unload and + * carries straight into the next load. + */ + mutex_lock(&c->lock); + if (c->state == CS42_PLAYING) + cs42_transport_stop(c); + if (c->state == CS42_PREPARED) + cs42_output_path_off(c); + mutex_unlock(&c->lock); + + sysfs_remove_groups(&spi->dev.kobj, cs42_groups); + c->component = NULL; +} + +static void cs42l81_shutdown(struct spi_device *spi) +{ + struct cs42l81 *c = spi_get_drvdata(spi); + + if (!c) + return; + mutex_lock(&c->lock); + if (c->state == CS42_PLAYING) + cs42_transport_stop(c); + mutex_unlock(&c->lock); +} + +static const struct of_device_id cs42l81_of_match[] = { + { .compatible = "cirrus,cs42l81" }, + { .compatible = "apple,338s1146" }, + { } +}; +MODULE_DEVICE_TABLE(of, cs42l81_of_match); + +static const struct spi_device_id cs42l81_spi_ids[] = { + { "cs42l81", 0 }, + { "338s1146", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, cs42l81_spi_ids); + +/* + * Suspend / resume. + * + * On S3, stock's codec analog block is brought back by the *bootloader* + * (sub_1314, on the hibernate arm), not by RetailOS. Our boot chain never + * runs that bootloader, so on resume nothing would power the analog block + * and playback would come back silent in exactly the way it did before the + * 0x9901 key ordering was fixed. + * + * Rather than replay a sequence from here, invalidate the cached state so + * the next .prepare runs the whole bring-up in stock's order -- park (which + * now issues the key first), run, mailbox, rate, path, graph, gains. That is + * the sequence that is already known correct; duplicating it in a resume + * path would be a second copy to keep in step. + * + * key_done is cleared because the codec loses the 0x9901 unlock across a + * power cycle. If it did not, re-issuing it is harmless -- sub_D2EFC is + * idempotent in stock too, guarded by MEMORY[0x892A028]. + */ +static int cs42l81_pm_suspend(struct device *dev) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + + mutex_lock(&c->lock); + cs42_state_analog_on(c); + mutex_unlock(&c->lock); + return 0; +} + +static int cs42l81_pm_resume(struct device *dev) +{ + struct cs42l81 *c = spi_get_drvdata(to_spi_device(dev)); + + mutex_lock(&c->lock); + c->state = CS42_UNKNOWN; + c->graph_built = false; + c->key_done = false; + mutex_unlock(&c->lock); + + dev_info(dev, "resume: codec state invalidated, full bring-up on next prepare\n"); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cs42l81_pm_ops, + cs42l81_pm_suspend, cs42l81_pm_resume); + +static struct spi_driver cs42l81_driver = { + .driver = { + .name = "cs42l81-spi", + .of_match_table = cs42l81_of_match, + .pm = pm_sleep_ptr(&cs42l81_pm_ops), + }, + .id_table = cs42l81_spi_ids, + .probe = cs42l81_probe, + .remove = cs42l81_remove, + .shutdown = cs42l81_shutdown, +}; +module_spi_driver(cs42l81_driver); + +MODULE_DESCRIPTION("CS42L81 / Apple 338S1146 codec (SPI)"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/apple/n31-audio-rates.h b/sound/soc/apple/n31-audio-rates.h new file mode 100755 index 00000000000000..5c8f65681b86e1 --- /dev/null +++ b/sound/soc/apple/n31-audio-rates.h @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * N31 sample-rate table — OSOS sub_D34C0 (osos.dec.bin.ida.c). + * + * RetailOS local music observed CLKDIV=272 → 44100 (code 10). + * 48000 (code 12, CLKDIV 250) stays in the table for ALSA requests; + * do not assume 48 kHz. Unspecified rate → N31_RATE_DEFAULT. + */ +#ifndef N31_AUDIO_RATES_H +#define N31_AUDIO_RATES_H + +#include +#include +#include +#include + +/* + * The nine rates sub_D34C0 has codes for. This is stock's set exactly. + * + * 12 MHz divides exactly to the 48 kHz family only; 44.1 and its relatives + * land on a fractional divider (12e6/272 = 44117.65). Stock ships them + * anyway and engages the codec's sample rate converter to absorb the + * difference -- see cs42l81-spi.c. Advertising a narrower set than stock + * pushes conversion into the application for no benefit. + */ +#define N31_RATE_MASK (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_12000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_24000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define N31_RATE_DEFAULT 44100u + +struct n31_rate_cfg { + unsigned int rate; + u8 cs42_rate_code; + u16 clkdiv; +}; + +/* + * Read out of sub_D34C0, not inferred. Each arm sets v10 (the codec rate + * code) and v11 (the divider) and falls into LABEL_23: + * + * 8000 -> 10 = 1, v11 = 1500 24000 -> 8, 500 + * 11025 -> 2, 1088 32000 -> 9, 375 + * 12000 -> 4, 1000 44100 -> 10, 272 + * 16000 -> 5, 750 48000 -> 12, 250 + * 22050 -> 6, 544 + * + * LABEL_23 then does + * + * if (MEMORY[0x892A02C] == 6000) v11 >>= 1; + * sub_4F716(v6, v11); + * + * so the dividers here are the 12 MHz source case. The 6 MHz arm is not + * implemented: it is unreachable on N31, so the halving at sub_D34C0 is + * dead code. + * + * 0x892A02C is a 32-bit static inside the OS image -- file offset 0x92A02C, + * bytes E0 2E 00 00 -- initialised to 12000, and field 0 of a nine-field + * audio config block. It is not a hardware register. + * + * Exactly one store to it exists in the image: + * + * 080D3712 ldr.w r8, [pc, #0x120] ; r8 = 0x892A02C + * 080D373A str.w r1, [r8] + * + * That instruction sits in the function at 0x0D3700, which the .c export + * omits entirely (the banner list jumps 000D34C0 -> 000D3838). It is the + * setter whose bit 0 selects the clock field. Its one and only call site is + * + * 08413E5A movw r1, #0x386 + * 08413E60 bl #0x800121c + * + * and 0x386 is bits 1,2,7,8,9 -- bit 0 CLEAR. So the sole writer is invoked + * exactly once and deliberately excludes this field. The bootloader image + * contains no reference to 0x892A02C and no occurrence of 12000 or 6000. + * + * The same word also picks the SoC codec divider, at 0x080D32BE -- a read the + * decompiler dropped: r0 - 12000, and div = 2 when equal, else 4. One word + * drives both halves consistently, which is why a divider of 2 here makes + * these dividers correct. + * + * There is therefore nothing for a Linux driver to read at runtime: this is + * RetailOS software state in its RAM, not a register. Every divider in the + * table is exactly 12000000 / rate and always will be. + * + * LABEL_13 (return 19) is unreachable: sub_D34C0 snaps the rate through + * sub_191E76 before the switch runs, so stock always substitutes rather + * than rejecting. n31_resolve_rate() below applies the same ceiling rule. + */ +static const struct n31_rate_cfg n31_rates[] = { + { 8000, 1, 1500 }, + { 11025, 2, 1088 }, + { 12000, 4, 1000 }, + { 16000, 5, 750 }, + { 22050, 6, 544 }, + { 24000, 8, 500 }, + { 32000, 9, 375 }, + { 44100, 10, 272 }, + { 48000, 12, 250 }, + +}; + +static inline const struct n31_rate_cfg *n31_find_rate(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(n31_rates); i++) + if (n31_rates[i].rate == rate) + return &n31_rates[i]; + return NULL; +} + +/* Use the requested OSOS rate, else RetailOS 44.1 kHz. */ +static inline unsigned int n31_resolve_rate(unsigned int rate); + +/* + * Stock has no "default rate" fallback -- it ceilings. N31_RATE_DEFAULT is + * kept only as the ALSA-facing default; it is no longer a substitute for an + * unrecognised rate. + */ +static inline unsigned int n31_pick_rate(unsigned int rate) +{ + if (rate && n31_find_rate(rate)) + return rate; + return n31_resolve_rate(rate); +} + +/* + * One resolver, used by BOTH the codec and the IIS driver. + * + * Both sides must resolve identically. If they do not -- for example a + * codec that falls back to N31_RATE_DEFAULT while the IIS side refuses the + * stream -- an out-of-table rate leaves the codec programmed for one rate + * and the clock divider set for another. Two halves of one link configured + * differently produce silence or noise rather than an error. + * + * So: resolve once, here, and let both sides call this. An exact match + * wins; otherwise pick the nearest supported rate by relative distance, + * which keeps the substitution predictable (96000 -> 48000, 5512 -> + * 8000) instead of collapsing everything onto the default. + */ +/* + * Stock's rate snap: sub_191E76 (EA 0x08191E76), called as the very first act + * of sub_D34C0 -- "*a1 = sub_191E76(*a1)" -- before the rate switch runs. + * + * It is a pure CEILING with clamps at both ends: + * + * <= 8000 -> 8000 > 22050 -> 24000 + * > 8000 -> 11025 > 24000 -> 32000 + * > 11025 -> 12000 > 32000 -> 44100 + * > 12000 -> 16000 > 44100 -> 48000 + * > 16000 -> 22050 + * + * Because its output is always one of the nine tabled rates, LABEL_13 in + * sub_D34C0 -- the "return 19" arm -- is UNREACHABLE. + * + * Stock substitutes silently and always; the rule is a ceiling, not + * nearest-by-relative-error. The two differ for any request that falls + * between two tabled rates and is closer to the lower one: nearest gives + * 12000 for 12500 where stock gives 16000, and 32000 for 33000 where stock + * gives 44100. + * + * Note 0 maps to 8000, not to 44100 -- stock's first test is "a1 > 0x1F40", + * so zero falls through to the 8000 clamp. + */ +static inline unsigned int n31_resolve_rate(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(n31_rates); i++) + if (n31_rates[i].rate >= rate) + return n31_rates[i].rate; + + return n31_rates[ARRAY_SIZE(n31_rates) - 1].rate; +} + +/* + * True when the codec must run its sample-rate converter rather than + * clocking the DAC straight off the ASP. + * + * 48 kHz is the special case, not 44.1. sub_183138 branches on the + * rate code and only code 12 gets the native arm: + * + * if (a1 == 12) { + * sub_43CDB4(267, 8); 0x10B = 0x08 + * sub_43CDB4(268, 9); 0x10C = 0x09 + * sub_42A5D6(305, 1, 1); 0x131 bit0 = 1 + * } else { + * sub_43CDB4(289, 8); 0x121 = 0x08 + * sub_43CDB4(290, 9); 0x122 = 0x09 + * sub_42A5D6(304, 15, a1); 0x130 & 0x0F = code + * sub_42A5D6(305, 1, 0); 0x131 bit0 = 0 + * sub_43CDB4(267, 4); 0x10B = 0x04 + * sub_43CDB4(268, 51); 0x10C = 0x33 + * } + * + * so 44.1 kHz runs through the SRC. cs42l81_set_rate_long() mirrors + * both arms, and the three writes before the branch -- 0x00E & 0xC0 = + * 0xC0, 0x00F & 0x0F = code, 0x12F = code | (code << 4) -- and the + * three after -- 0x00E & 0xC0 = 0x40, sub_400330(64), 0x220 bit5 -- + * are there too, in stock's order. + */ +static inline bool n31_rate_uses_src(unsigned int rate) +{ + const struct n31_rate_cfg *r = n31_find_rate(n31_resolve_rate(rate)); + + return r && r->cs42_rate_code != 12; +} + +/* Exact 1 kHz period group: rate / gcd(rate, 1000) frames. */ +static inline unsigned int n31_tone_period_frames(unsigned int rate) +{ + unsigned int a, b, t; + + rate = n31_pick_rate(rate); + a = rate; + b = 1000; + while (b) { + t = a % b; + a = b; + b = t; + } + return rate / a; +} + +/* 256-point sine, peak ≈ 0.7 * 32767. 1 kHz via DDS at any table rate. */ +static const s16 n31_sin256[256] = { + 0, 563, 1125, 1687, 2248, 2808, 3366, 3921, + 4475, 5026, 5573, 6118, 6658, 7195, 7727, 8255, + 8778, 9295, 9807, 10313, 10812, 11306, 11792, 12271, + 12743, 13207, 13664, 14112, 14551, 14982, 15404, 15816, + 16219, 16612, 16995, 17368, 17731, 18082, 18423, 18753, + 19071, 19378, 19674, 19957, 20229, 20488, 20735, 20969, + 21191, 21400, 21596, 21779, 21949, 22106, 22250, 22380, + 22496, 22599, 22689, 22765, 22827, 22875, 22909, 22930, + 22937, 22930, 22909, 22875, 22827, 22765, 22689, 22599, + 22496, 22380, 22250, 22106, 21949, 21779, 21596, 21400, + 21191, 20969, 20735, 20488, 20229, 19957, 19674, 19378, + 19071, 18753, 18423, 18082, 17731, 17368, 16995, 16612, + 16219, 15816, 15404, 14982, 14551, 14112, 13664, 13207, + 12743, 12271, 11792, 11306, 10812, 10313, 9807, 9295, + 8778, 8255, 7727, 7195, 6658, 6118, 5573, 5026, + 4475, 3921, 3366, 2808, 2248, 1687, 1125, 563, + 0, -563, -1125, -1687, -2248, -2808, -3366, -3921, + -4475, -5026, -5573, -6118, -6658, -7195, -7727, -8255, + -8778, -9295, -9807, -10313, -10812, -11306, -11792, -12271, + -12743, -13207, -13664, -14112, -14551, -14982, -15404, -15816, + -16219, -16612, -16995, -17368, -17731, -18082, -18423, -18753, + -19071, -19378, -19674, -19957, -20229, -20488, -20735, -20969, + -21191, -21400, -21596, -21779, -21949, -22106, -22250, -22380, + -22496, -22599, -22689, -22765, -22827, -22875, -22909, -22930, + -22937, -22930, -22909, -22875, -22827, -22765, -22689, -22599, + -22496, -22380, -22250, -22106, -21949, -21779, -21596, -21400, + -21191, -20969, -20735, -20488, -20229, -19957, -19674, -19378, + -19071, -18753, -18423, -18082, -17731, -17368, -16995, -16612, + -16219, -15816, -15404, -14982, -14551, -14112, -13664, -13207, + -12743, -12271, -11792, -11306, -10812, -10313, -9807, -9295, + -8778, -8255, -7727, -7195, -6658, -6118, -5573, -5026, + -4475, -3921, -3366, -2808, -2248, -1687, -1125, -563, +}; + +static inline s16 n31_tone_s16(unsigned int sample, unsigned int rate) +{ + u32 idx; + + rate = n31_pick_rate(rate); + idx = (u32)div_u64((u64)sample * 1000ull * 256ull, rate); + return n31_sin256[idx & 255]; +} + +/* + * Implemented by s5l8740-i2s.c. This is stock's sub_41CBD8 case 9: the + * codec clock gate is CLKCON+0x0C bit 15, cleared to run and set to gate. + * Declared here so both drivers agree on the signature -- cs42l81-spi.c + * reaches it through __symbol_get, which cannot type-check anything. + */ +void s5l8740_codec_clk_gate(bool on); + +/* + * RetailOS sub_345D28(9, 0, div): CLKCON+0x0C bits 3:0 hold the codec clock + * divider as div-1. Stock selects 2 at a 12 MHz MCLK. Declared here so both + * drivers agree on the signature; cs42l81-spi.c reaches it through + * __symbol_get, which cannot type-check anything. + */ +void s5l8740_codec_clk_divider(unsigned int div); + +/* + * Codec entry points the IIS driver drives. Declared here rather than in each + * .c so both sides agree on the signatures and the definitions do not trip + * -Wmissing-prototypes. + */ +int cs42l81_play_prepare(void); +int cs42l81_play_start(void); +int cs42l81_play_stop(void); +int cs42l81_pre_iis_start(void); +int cs42l81_post_iis_start(void); +int cs42l81_set_clock_role(bool drive); +int cs42l81_asp_hold_light(void); +int cs42l81_get_audio_path_mode(void); +void cs42l81_schedule_post_iis(void); +void cs42l81_cancel_post_iis(void); + +/* + * Accessory / headphone-remote message channel on the codec's FIFO + * registers (RetailOS sub_15A50C / sub_19A838). Not used by the audio path. + */ +int cs42l81_mbox_send(u8 *buf, size_t buf_size); +int cs42l81_mbox_recv(u8 *buf, size_t buf_size); + +#endif /* N31_AUDIO_RATES_H */ diff --git a/sound/soc/apple/nano7-audio.c b/sound/soc/apple/nano7-audio.c new file mode 100755 index 00000000000000..833850914dd9e7 --- /dev/null +++ b/sound/soc/apple/nano7-audio.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * iPod nano 7G ASoC machine. + * + * One card, two PCMs: + * playback IIS0 -> CS42L81 -> 3.5 mm headphones (PL080 peri 10) + * capture IIS2 <- BCM2078 digital PCM (FM) (PL080 peri 13) + * + * The two ports face different chips, which is why the capture side has + * no codec of its own and uses the dummy DAI. Bluetooth audio is not + * here and cannot be: A2DP is host-encoded and leaves over UART1 HCI. + * + * The CS42 path has no DAPM route table. Analog routing is done with + * explicit register writes in cs42_codec_prepare() and + * cs42_retailos_play_start/stop(). SoC is master, codec is slave, S16_LE. + */ +#include +#include +#include +#include + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("s5l8740-i2s")), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42l81-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +SND_SOC_DAILINK_DEFS(fm_capture, + DAILINK_COMP_ARRAY(COMP_CPU("bcm2078-pcm")), + DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +static struct snd_soc_dai_link nano7_dais[] = { + { + .name = "CS42L81 Headphones", + .stream_name = "Headphones", + SND_SOC_DAILINK_REG(playback), + .playback_only = 1, + /* + * The IIS0 trigger latches the codec play graph over SPI, and + * spi_sync() sleeps. ASoC runs trigger atomically unless the + * link opts out, so this is required, not a preference. + */ + .nonatomic = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + { + .name = "BCM2078 FM", + .stream_name = "BCM2078 PCM Capture", + SND_SOC_DAILINK_REG(fm_capture), + .capture_only = 1, + /* + * Same reason as the playback link: this DAI's trigger and + * prepare reach the codec over SPI and take mutexes, so it + * must not be called from atomic context. + */ + .nonatomic = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, +}; + +static struct snd_soc_card nano7_card = { + .name = "nano7g-audio", + .owner = THIS_MODULE, + .dai_link = nano7_dais, + .num_links = ARRAY_SIZE(nano7_dais), +}; + +static int nano7_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *cpu_np, *codec_np, *fm_np; + int ret; + + cpu_np = of_parse_phandle(dev->of_node, "apple,cpu", 0); + if (!cpu_np) { + dev_err(dev, "missing apple,cpu phandle (IIS0)\n"); + return -EINVAL; + } + nano7_dais[0].cpus->of_node = cpu_np; + nano7_dais[0].cpus->dai_name = NULL; + /* dmaengine PCM lives on the IIS platform device */ + nano7_dais[0].platforms->of_node = cpu_np; + nano7_dais[0].platforms->name = NULL; + + codec_np = of_parse_phandle(dev->of_node, "apple,codec", 0); + if (!codec_np) + codec_np = of_find_compatible_node(NULL, NULL, "cirrus,cs42l81"); + if (!codec_np) { + dev_err(dev, "missing apple,codec / cirrus,cs42l81\n"); + of_node_put(cpu_np); + return -EINVAL; + } + nano7_dais[0].codecs->of_node = codec_np; + nano7_dais[0].codecs->name = NULL; + nano7_dais[0].codecs->dai_name = "cs42l81-hifi"; + + fm_np = of_parse_phandle(dev->of_node, "apple,fm-cpu", 0); + if (!fm_np) + fm_np = of_find_compatible_node(NULL, NULL, "apple,s5l8740-iis2"); + if (fm_np) { + nano7_dais[1].cpus->of_node = fm_np; + nano7_dais[1].cpus->dai_name = NULL; + nano7_dais[1].platforms->of_node = fm_np; + nano7_dais[1].platforms->name = NULL; + nano7_card.num_links = 2; + } else { + dev_warn(dev, "no IIS2 fm-cpu — playback-only card\n"); + nano7_card.num_links = 1; + } + + nano7_card.dev = dev; + ret = devm_snd_soc_register_card(dev, &nano7_card); + if (ret) { + of_node_put(cpu_np); + of_node_put(codec_np); + of_node_put(fm_np); + if (ret == -EPROBE_DEFER) + return ret; + dev_err(dev, "snd_soc_register_card failed: %d\n", ret); + return ret; + } + + dev_info(dev, "nano7g-audio: headphone playback%s\n", + nano7_card.num_links > 1 ? " + BCM2078 FM capture" : ""); + return 0; +} + +static const struct of_device_id nano7_audio_of_match[] = { + { .compatible = "apple,n31-audio" }, + { } +}; +MODULE_DEVICE_TABLE(of, nano7_audio_of_match); + +static struct platform_driver nano7_audio_driver = { + .probe = nano7_audio_probe, + .driver = { + .name = "nano7-audio", + .of_match_table = nano7_audio_of_match, + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(nano7_audio_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("iPod nano 7G ASoC machine (headphones + FM capture)"); +MODULE_SOFTDEP("pre: cs42l81_spi s5l8740_i2s"); diff --git a/sound/soc/apple/s5l8740-i2s.c b/sound/soc/apple/s5l8740-i2s.c new file mode 100755 index 00000000000000..58141387420aa3 --- /dev/null +++ b/sound/soc/apple/s5l8740-i2s.c @@ -0,0 +1,3665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * S5L8740 I2S platform DAIs — N31 + * + * Both of the board's audio ports live here because they share the + * SoC audio clock gate at CLKCON+0x30, and arbitrating that across two + * modules is not worth the symbol traffic: + * + * IIS0 @ 0x3CA00000 TX FIFO +0x10, PL080 peri 10 -> CS42L81 headphones + * IIS2 @ 0x3D400000 RX FIFO +0x38, PL080 peri 13 <- BCM2078 digital PCM + * + * They face different chips, but to userspace they are simply the + * playback and capture PCMs of one card. + * + * IIS2 register program is from the RetailOS fm-playing MMIO capture: + * CLKCON +0x00 = 0x1 TXCON +0x04 = 0x0b000099 + * RXCON +0x30 = 0x1000 RXCOM +0x34 = 0x6 running, 0x2 idle + * CLKDIV +0x40 = 0x96 REG44 +0x44 = 0x00010007 + * IIS1 @ 0x3CD00000 is XSP and always reads zero -- it is not a BCM port. + * A2DP does not appear here at all: it is host-encoded over UART1 HCI. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "n31-audio-rates.h" +#include "s5l8740-clk.h" + +/* Advertise every rate the divider table can actually clock. */ +#define S5L8740_I2S_RATES N31_RATE_MASK +#define S5L8740_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +#define I2SCLKCON 0x00 +#define I2STXCON 0x04 +#define I2STXCOM 0x08 +#define I2STXFIFO 0x10 +#define I2SRXCON 0x30 +#define I2SRXCOM 0x34 +#define I2SRXFIFO 0x38 +#define I2SSTATUS 0x3c +#define I2SCLKDIV 0x40 /* OSOS 4F716: *(base+64). Not Rockbox +0x24. */ +/* RetailOS music IIS0+0x44 readback 0x00010007 (oracle 2026-08-25). */ +#define I2SREG44 0x44 +/* + * OSOS sub_C095E(port, ch): STATUS W1C — TX sticky is bit15 (1<<15). + * Linux never cleared this; silent dumps always show 0x8xxx. + */ +#define I2SSTATUS_TX_W1C 0x8000u +#define MCLK_ASSUME_HZ 12000000u +/* + * TXCON, read out of sub_BCB60 (0x080BCB60). + * + * sub_BCB60 builds this register two ways, selected by its a3 argument, + * and a3 also picks the pad function in s5l8740_i2s_pads(): + * + * a3 == 0: r0 = (*txcon & 0x780) | 0x08000018; pads function 2 + * a3 != 0: r0 = 0x00100098; pads function 3 + * both: r0 |= (a5 == 24) ? 0x03000041 : 0x03000001; + * *txcon = r0; *(base + 0x30) = 0x1000; + * + * Which arm IIS0 takes is NOT statically known. sub_BCB60 has two callers: + * + * 0x0815DE20 passes a3 = 0 and a5 = 16 as immediates, but its own two + * callers pass unit 2 (0x08471CB0, followed by r7 = 32000) + * and unit 1 (0x08471DD0). It never runs for IIS0. The GPIO + * 7 and 20 mux inside sub_BCB60 is guarded by + * sub_414FAA() == unit, and sub_414FAA returns 0, so those + * two pads are muxed only for unit 0 -- they are IIS0's, and + * the 97/98/119 that caller muxes are IIS1/IIS2's. + * + * 0x08414FC4 sub_414FAE, which loads all five arguments from bytes at + * [r4+21 .. r4+25]. This is the path IIS0 reaches, and its + * a3 is runtime data this image does not pin down. + * + * So the arm is chosen here on two pieces of evidence rather than on a + * static read: + * + * - Hardware. With bit 20 set (the a3 != 0 constant) the transmitter + * never starts: STATUS sits at 0x00000024 indefinitely, the TX FIFO + * fills to its eight entries, and the DMA stops with no error and no + * terminal count. Clearing it moves STATUS to 0x00008020 at once. + * Only the a3 == 0 arm produces a word without bit 20. + * + * - The IIS2 oracle. The RetailOS MMIO capture of IIS2 during FM reads + * TXCON 0x0b000099 (IIS2_TXCON_FM below), which is exactly what the + * a3 == 0 arm yields with bit 7 already set. That confirms the arm's + * arithmetic, on a port we have a capture for. + * + * There is no IIS0 TXCON capture. If one is ever taken and it shows + * 0x03100099, this and the pad function both belong on the a3 != 0 arm. + */ +#define I2STXCON_KEEP 0x780u /* preserved across the RMW */ +#define I2STXCON_A3_0 0x08000018u +#define I2STXCON_A3_NONZERO 0x00100098u +#define I2STXCON_WIDTH_16 0x03000001u +#define I2STXCON_WIDTH_24 0x03000041u +#define I2SRXCON_N31 0x1000u +/* + * 0 follows sub_BCB60; anything else is written verbatim, which is what the + * bring-up sweeps use. + */ +static uint txcon; +/* 0=soc_master, 1=ext_clock, 2=ext_clock with codec ASP bit7 cleared. */ +/* + * TEST 2026-09-01: defaulted to profile 1 to run the other coherent pairing. + * + * sub_BCB60 selects the pad function and the TXCON word from the same a3: + * + * a3 == 0: pads 7,20 -> fn 2, TXCON = (old & 0x780) | 0x8000018 | 0x03000001 + * a3 != 0: pads 7,20 -> fn 3, TXCON = 0x100098 | ... + * + * Both are stock configurations; which one the music path uses depends on + * sub_BCB60's caller, and sub_414FAE has six callers that have not been + * resolved. Profile 0 (fn 2) has been tested repeatedly and is silent. + * Profile 1 (fn 3) has never been run -- and the one historical report of + * audible, wrong-pitch output came from that combination, which would mean + * signal actually reaching the amplifier. + * + * Revert to 0 if this is also silent; it is an A/B between two legitimate + * stock pairings, not a claim that 1 is correct. + */ +/* + * TXCON mastership arm. Default 1, which is what stock asks for. + * + * sub_414FAE calls sub_BCB60(cfg[21], cfg[22], cfg[23], cfg[24], cfg[25]) + * and sub_AA0AE sets cfg[23] = 1, so a3 = 1 is stock, giving TXCON + * 0x03100099 and pads 7/20 at function 3. + * + * This defaulted to 0 on the strength of a measurement and a hypothesis, + * and the hypothesis was wrong. The comment here used to say profile 1 + * could not move a sample "until that register is identified from the + * decomp" -- meaning a codec master-clock enable. No such register exists. + * A raw Thumb-BL scan of the whole image finds only three codec accessors + * (sub_43CDB4, sub_43CDFA, sub_42A5D6) and their complete call set contains + * no master/slave, clock-direction, port-format or frame-length write. + * + * The freeze that justified profile 0 -- one 32-byte burst into the TX + * FIFO, src stuck at 0x09600020, hw_ptr never leaving 3 -- was measured + * against a codec carrying two writes of our own invention: 0x0500 = 0x05, + * which stock only writes in an override branch off the audio path, and a + * clear of 0x000F bit 7, which stock sets once and never clears. Both are + * gone, so that measurement no longer describes this driver. + * + * If profile 1 still freezes, the cause is NOT a missing codec master + * enable, and looking for one again is a dead end. + */ +static uint mastership_profile = 1; +/* module_param moved below s5l8740_i2s_iis0; see txcon_set(). */ +/* + * D34C0 → 4F716(port, div). Table in n31-audio-rates.h. + * 0 = 12 MHz / rate (272 @ 44.1 kHz RetailOS music). + */ + +static uint clkdiv; +module_param(clkdiv, uint, 0644); +MODULE_PARM_DESC(clkdiv, "I2SCLKDIV override; 0 = OSOS table / 12000000/rate"); +/* + * Default IIS program rate for clk_run when no ALSA hw_params. + * 0 = RetailOS 44100. ALSA playback uses the PCM rate, not this. + */ +static uint default_rate; +module_param(default_rate, uint, 0644); +MODULE_PARM_DESC(default_rate, "clk_run rate; 0 = 44100 OSOS default"); +/* + * dma_tone / pio_tone sample rate. 0 = default_rate / OSOS 44100. + */ + +/* Keep TX/codec up this long after START even if ALSA xruns. */ +/* + * Off, and the stop path it used to suppress is fixed. + * + * Ignoring ALSA's STOP for five seconds is a bring-up crutch and it makes + * the PCM layer and the hardware disagree about whether a stream is + * running. It should be 0. But setting it to 0 was the one behavioural + * change between a clean boot and a boot that hangs, and the reason is + * that the STOP path has never actually executed: with the crutch in + * place it was skipped every time. + * + * What it hits is s5l8740_i2s_cancel_asp() -> cs42l81_cancel_post_iis(), + * which is a cancel_delayed_work_sync() on a work item that takes + * c->lock. That blocks until the work completes, so if the work is + * waiting on that lock the stop never returns. + * + * That cancel is now the non-blocking form, so the stop path no longer + * waits on a work item that wants the codec lock. Dead code that has never + * run is not the same as code that works, which is the whole reason this + * surfaced the moment the crutch came off. + */ +/* + * Backtrace the first few hw_params calls, on by default until the thing + * that drives the boot-time open/start loop is identified. + */ +static bool hw_params_trace = true; +module_param(hw_params_trace, bool, 0644); +MODULE_PARM_DESC(hw_params_trace, "1=log caller comm/pid on each hw_params call (default Y)"); +static unsigned int hw_params_seen; + +/* + * Halt after this many hw_params calls, naming the caller. 0 disables. + * A normal boot opens the PCM a handful of times at most. + */ +static unsigned int hw_params_loop_panic; /* off: the loop was tone-loop from net-up */ +module_param(hw_params_loop_panic, uint, 0644); +MODULE_PARM_DESC(hw_params_loop_panic, + "panic after N PCM opens to freeze the caller name on screen (0=off)"); + +static uint sustain_ms; +module_param(sustain_ms, uint, 0644); +MODULE_PARM_DESC(sustain_ms, "ignore ALSA STOP for this many ms after START (default 0 = off)"); + +/* + * Backtrace the first few PCM stops, to identify a caller that is stopping + * a stream this driver did not stop itself. Off by default: dump_stack() + * prints unconditionally, so this cannot live behind dynamic debug, and an + * ordinary boot would spend a few hundred log lines on it. + */ +static bool trace_trigger; +module_param(trace_trigger, bool, 0644); +MODULE_PARM_DESC(trace_trigger, + "backtrace the first few PCM STOPs to name the caller (default N)"); + +/* + * Off. Stock never writes the TX FIFO directly on the DMA path -- across the + * whole image the only IIS0 offsets written are +0x00, +0x04, +0x08, +0x30, + * +0x34, +0x3C and +0x40, and +0x10 is not among them. Priming the FIFO with + * silence was our invention; it also made the DMA's first burst land on a + * partly full FIFO, which confused the reading of how much it had moved. + */ +static uint fifo_prefill; +module_param(fifo_prefill, uint, 0644); +MODULE_PARM_DESC(fifo_prefill, + "silent stereo words to push into TX FIFO before TXCOM kick"); +/* + * OSOS B6620(port,0) does TXCOM |= 6 after PL080 is armed (peri 10). + * RE body: sub_B6620 only ORs 0x6 — not 0xC. Hybrid 0xE was Linux invention. + */ +#define I2STXCOM_DMA 0x6 +#define I2STXCOM_PIO 0xc +/* + * Stopping the TX transport clears bit 2 ONLY -- it does not zero the + * register. + * + * Stock arms and disarms with read-modify-writes, never plain stores: + * + * arm (sub_B6620, dir 0): *(iis + 0x08) |= 6 + * disarm (sub_5705DC, dir 0): *(iis + 0x08) &= ~4 + * + * (The RX side is the same pair against +0x34.) The disarm deliberately + * leaves bit 1 set; only bit 2 is dropped. + * + * This driver used to store 0 here, clearing bit 1 as well, on every stop -- + * including the stop that immediately precedes each arm. Whatever bit 1 + * latches, stock keeps it across the whole stop/start cycle and we were + * destroying it every trigger. + */ +#define I2STXCOM_RUN BIT(2) /* the bit stock drops on disarm */ +#define I2STXCOM_STOP 0x0 /* legacy; do not store this */ +#define CLKCON_PHYS 0x3c500000ul +/* RetailOS oracle dwords at CLKCON+0x30 (music vs idle/A2DP). */ +/* SRAM window: 0x22000000..0x2202FFFF, 192 KiB (bootloader descriptor). */ +#define S5L8740_SRAM_BASE 0x22000000u +#define S5L8740_SRAM_END 0x22030000u +#define CLKCON_PWRCON1 0x4c /* IIS0 gate bank; see s5l8740_i2s_iis0_gate() */ +#define PWRCON1_IIS0_GATE BIT(7) +#define CLKCON_AUDIO_OFF 0x30 +/* Off. +0x30 is a PLL parameter; see s5l8740_audio_clk_set(). */ +#define CLKCON_AUDIO_PLAY 0x32190u +#define CLKCON_AUDIO_IDLE 0x1c20u +/* FM additionally regates CLKCON+0x10; music/idle leaves it at 0x8004. */ +#define CLKCON_FM_GATE 0x10 +#define CLKCON_FM_GATE_ON 0x0u /* bit 15 clear = on */ +#define CLKCON_FM_GATE_IDLE 0x8000u /* bit 15 set = gated */ +/* + * Bit 15 of CLKCON+0x10, and nothing else. + * + * This is stock's own gate, sub_41CBD8 case 11: + * + * if (a2) MEMORY[0x3C500010] &= 0xFFFF7FFF; + * else MEMORY[0x3C500010] |= 0x8000; + * + * so on clears bit 15 and off sets it. Every write to this register in the + * firmware was checked: case 11 above, the divider setter, and the + * save/restore critical section. None of them touches bit 2, which this + * driver used to set from a value of its own -- that bit is not ours and + * is not attested anywhere. CLKCON is shared with every other peripheral + * on this SoC, so the rest of the word is not ours either. + */ +#define CLKCON_FM_GATE_MASK 0x8000u + +/* fm-playing oracle values for the IIS2 side. */ +#define IIS2_CLKCON_ON 0x1u +#define IIS2_TXCON_FM 0x0b000099u +#define IIS2_RXCON_FM 0x1000u +#define IIS2_RXCOM_DMA 0x6u +#define IIS2_RXCOM_IDLE 0x2u +#define IIS2_CLKDIV_FM_ORACLE 0x96u +#define IIS2_REG44_ORACLE 0x00010007u +#define IIS2_REGS_LEN 0x48 + +/* + * IIS2 PCM pads. sub_15DD5C claims these three at function 2 when FM + * powers on and releases them to input when it powers off, in the same + * breath as programming device 2 (0x3D400000) and kicking RXCOM -- so + * they belong to this port, not to the Bluetooth controller. They were + * previously described as BCM shutdown / device-wakeup / host-wakeup and + * handed to hci_bcm, which drove the capture bus as GPIOs. + * + * Claimed alongside the register program rather than from FM power-on, + * so opening the capture PCM works regardless of who owns the tuner. + */ +#define IIS2_PAD_BCLK 97 /* 0x61 */ +#define IIS2_PAD_SYNC 98 /* 0x62 */ +#define IIS2_PAD_DATA 119 /* 0x77 */ +#define IIS2_PAD_FUNC 2 +#define IIS2_PAD_RELEASE 0 + +/* Ports sharing the CLKCON+0x30 gate. */ +#define S5L8740_AUDIO_PORT_IIS0 0 +#define S5L8740_AUDIO_PORT_IIS2 1 +#define S5L8740_AUDIO_PORTS 2 +#define GPIO_PHYS 0x3cf00000ul +#define GPIOCMD_PHYS 0x3cf001e0ul + +/* + * RetailOS user volume is 0..256 (VolumeScalar, 256 = unity). CS42 + * analog 0x527 is only mute/unmute; the integer is a PCM Q8 gain. + */ +#define S5L8740_USER_VOL_MAX 256 +static atomic_t s5l8740_user_vol_q8 = ATOMIC_INIT(S5L8740_USER_VOL_MAX); + +void s5l8740_set_user_vol_q8(unsigned int vol); +unsigned int s5l8740_get_user_vol_q8(void); + +void s5l8740_set_user_vol_q8(unsigned int vol) +{ + if (vol > S5L8740_USER_VOL_MAX) + vol = S5L8740_USER_VOL_MAX; + atomic_set(&s5l8740_user_vol_q8, vol); +} +EXPORT_SYMBOL_GPL(s5l8740_set_user_vol_q8); + +unsigned int s5l8740_get_user_vol_q8(void) +{ + return atomic_read(&s5l8740_user_vol_q8); +} +EXPORT_SYMBOL_GPL(s5l8740_get_user_vol_q8); + +static s16 s5l8740_scale_s16(s16 s) +{ + unsigned int q8 = atomic_read(&s5l8740_user_vol_q8); + + if (q8 >= S5L8740_USER_VOL_MAX) + return s; + return (s16)(((int)s * (int)q8) / S5L8740_USER_VOL_MAX); +} + +static u32 s5l8740_scale_lr(u32 sample) +{ + unsigned int q8 = atomic_read(&s5l8740_user_vol_q8); + s16 l, r; + + if (q8 >= S5L8740_USER_VOL_MAX) + return sample; + l = s5l8740_scale_s16((s16)sample); + r = s5l8740_scale_s16((s16)(sample >> 16)); + return ((u32)(u16)r << 16) | (u16)l; +} + +/* ------------------------------------------------------------------ */ +/* PCM inspection and sample-format probes */ +/* */ +/* The jack carries a 1 kHz fundamental with 2k/3k/4k/5k harmonics at */ +/* comparable amplitude, and a peak around 255 against a source that */ +/* peaks near 23000. A gain error would keep the sine clean and only */ + +/* + * Sample rewrites applied on the way to the FIFO. All default to off, so + * the transport is unchanged unless something is being tested. + */ +static int sample_shift; +module_param(sample_shift, int, 0644); +MODULE_PARM_DESC(sample_shift, + "Arithmetic shift applied per sample, -16..16 (0 = none)"); + +static bool sample_byteswap; +module_param(sample_byteswap, bool, 0644); +MODULE_PARM_DESC(sample_byteswap, "Swap the two bytes of each sample"); + +static bool sample_swap_lr; +module_param(sample_swap_lr, bool, 0644); +MODULE_PARM_DESC(sample_swap_lr, "Swap left and right within each frame"); + + +/* + * 1 = drive the DMA the way RetailOS does: one period-sized transfer at a + * time, re-armed from the completion callback, instead of snd_dmaengine_pcm's + * free-running cyclic chain. See s5l8740_rearm_submit(). + */ +struct s5l8740_i2s; +static struct dma_chan *s5l8740_i2s_tx_get(struct s5l8740_i2s *i2s); +static int s5l8740_rearm_start(struct s5l8740_i2s *i2s); +static void s5l8740_rearm_stop(struct s5l8740_i2s *i2s); +static void s5l8740_rearm_sync(struct s5l8740_i2s *i2s); + +/* + * IIS0+0x44. Not written by stock; see s5l8740_i2s_program(). Non-zero + * writes that value, 0 leaves the register alone. + */ +static uint reg44; +module_param(reg44, uint, 0644); +/* + * Stock NEVER writes IIS0+0x44. All eight functions that resolve an IIS base + * through sub_43A858 -- sub_4F6DC, sub_4F716, sub_B6620, sub_BCB60, + * sub_C093C, sub_C095E, sub_C09AC, sub_5705DC -- contain no access to it at + * all, read or write. + * + * A live read of RetailOS with music playing gives 0x00010007, so that is a + * reset default or a bootloader leftover, not something the audio path + * programs. Leaving it alone is therefore correct and is NOT a gap; the + * register is logged next to the others so ours can be compared against + * 0x00010007 rather than assumed. + */ +MODULE_PARM_DESC(reg44, + "IIS0+0x44 override; 0 = leave alone (default, and what stock does)"); + +/* + * Milliseconds without a DMA completion before the re-arm path gives up and + * xruns the stream. A transfer that never completes must not become an + * unkillable writer blocked in snd_pcm_write: that wedges the PCM, makes + * every later rmmod fail, and costs a reboot. 0 disables the guard. + */ +/* + * Milliseconds the DMA source pointer may stand still during an active + * stream before the driver terminates the transfer and xruns it. Applies to + * both the cyclic and the re-arm paths, because it watches the controller + * rather than either model's bookkeeping. 0 disables it. + */ + +static uint rearm_stall_ms = 500; +module_param(rearm_stall_ms, uint, 0644); +MODULE_PARM_DESC(rearm_stall_ms, + "ms without a DMA completion before xrunning the stream (0=off)"); + +/* + * 0 = snd_dmaengine_pcm's cyclic chain (default), 1 = the per-buffer re-arm + * model stock uses. + * + * The re-arm path was written because stock re-arms per buffer and the + * cyclic path was stalling. That stall turned out to be TXCON bit 20 -- the + * serialiser was never clocking, so nothing could complete under either + * model. Every cyclic measurement was taken against a dead transmitter and + * none of them meant anything. + * + * With the transmitter fixed the standard path deserves the first try: it is + * the one the rest of ALSA is written around. dma_rearm=1 still selects the + * stock-shaped path, which remains the more faithful model if cyclic turns + * out not to suit this controller. + */ +/* + * Advertise the two extrapolated hi-res rates. Read-only at load time; see + * s5l8740_rearm_open() and the table in n31-audio-rates.h for why this is + * not on by default. + */ + +static int dma_rearm = 1; +module_param(dma_rearm, int, 0644); +MODULE_PARM_DESC(dma_rearm, + "1 = per-buffer re-arm like stock (default), 0 = cyclic dmaengine"); + +static int use_pio; +module_param(use_pio, int, 0644); +MODULE_PARM_DESC(use_pio, "1 = CPU FIFO PCM; 0 = PL080 M2P peri 10 from DT (default)"); + +static int txcom_pio = I2STXCOM_PIO; +module_param(txcom_pio, int, 0644); +MODULE_PARM_DESC(txcom_pio, "TXCOM when use_pio=1 (default 0xC; OSOS DMA is 0x6)"); + +/* + * TXCOM kick mode (checkpoint-003 / handoff P0.3): + * 0 = retail: TXCOM |= 0x6 after DMA armed + * 1 = pio: TXCOM = txcom_pio (0xC) + * 2 = hybrid: bit3 then |0x6 (glass experimental default) + */ +/* + * RetailOS music-playing SCSI oracle 2026-08-25: TXCOM readback = 0x6. + * sub_B6620 does |= 6 only. Hybrid 0xE was a Linux false lead. + */ +static int txcom_mode; +module_param(txcom_mode, int, 0644); +MODULE_PARM_DESC(txcom_mode, "0=retail |=6 (default), 1=pio 0xC, 2=hybrid |C|6"); + +/* + * txcom_exact: when >=0, tx_kick writes this value exactly (no OR). -1=use mode. + * Isolation tests: 0x6 / 0xC / 0xE from TXCOM=0 baseline. + */ +static int txcom_exact = -1; +module_param(txcom_exact, int, 0644); +MODULE_PARM_DESC(txcom_exact, "Exact TXCOM write when >=0; -1=txcom_mode (default)"); + +/* RetailOS local music CLKDIV=0x110 (272) ≈ 12 MHz / 44100. */ + +/* + * BCB60 sets DIR. Measured: with DIR left as an input, GPIO 7/20 stop + * and GPIO 6 still toggles -- BCLK/LRCK are SoC-driven, not + * codec-master. + */ + +/* + * Pad bring-up variants (exhaust RE before RetailOS GPIO oracle): + * 0 = local 43D38C(7,3)(20,3) only [BCB60 — CONFIRMED OSOS body] + * 1 = +43D38C(6,3) — NO OSOS 43D38C call site for GPIO6; debug only + * 2 = gpio-s5l8740 s5l8740_iis0_pads_enable(3) — SEC pinmux + GPIOCMD + * 3 = gpio driver mode 2 (BCB60 teardown) + * 4 = SEC pinmux 6/7/20 + local mode3 on all three + * 5 = mode2 + SEC pinmux refresh (func2 only, no mode3) + */ + +struct s5l8740_i2s { + void __iomem *base; + void __iomem *clkcon; + void __iomem *gpio; + void __iomem *gpiocmd; + struct device *dev; + struct clk_bulk_data *clks; + int num_clks; + bool has_dma; + /* Per-buffer re-arm playback; see s5l8740_rearm_submit(). */ + bool rearm_run; + /* + * Stream generation. Incremented on every stop, stamped in when the + * DMA callback queues work, and compared in the handler. + * + * rearm_stop() cancels without waiting, because waiting there + * deadlocks against a closer holding the PCM stream lock. That + * leaves a queued item able to outlive its stream, and rearm_run + * alone cannot detect it: by the time the stale item runs, the next + * stream has set rearm_run back to true. It then reports a period + * the application never wrote, hw_ptr overtakes appl_ptr, and ALSA + * declares an xrun -- which stops the stream and queues another + * stale item, so the stream can never sustain. + */ + unsigned int rearm_gen; + unsigned int rearm_gen_queued; + /* Mirrors stock obj[0x0C]: cleared on channel (re)allocation, set + * once the self-linked node has been armed. */ + bool rearm_armed; + snd_pcm_uframes_t rearm_pos; + unsigned int rearm_periods; + unsigned int rearm_underrun; + struct work_struct rearm_work; + struct delayed_work rearm_watch; + /* + * Not system_wq. rearm_work runs snd_pcm_period_elapsed(), which is + * what tells ALSA a period is free again, and it has to happen well + * inside one period or the application never gets the space back in + * time. system_wq is shared with everything else on this single core + * -- the FTL in particular, which does long NAND scans -- so a period + * callback could sit behind unrelated work for tens of milliseconds + * and the stream would xrun with the FIFO still full. The DMA + * callback path already runs on its own WQ_HIGHPRI queue for the same + * reason; this is the other half of that path. + */ + struct workqueue_struct *rearm_wq; + struct dma_chan *tx_chan; /* cached — avoid dma:tx symlink churn */ + u8 tx_chan_borrowed; /* 1 = lookup_peri, do not dma_release */ + struct mutex dma_lock; + struct snd_dmaengine_dai_dma_data play_dma; + struct snd_pcm_substream *ss; + struct task_struct *kthread; + bool pio_run; + unsigned int pio_hw_ptr; + unsigned int rate; + bool programmed; + unsigned long play_jiffies; +}; + +/* + * SEC sub_2034 leftovers. OSOS 983430 never programs clock 9; + * it does program clocks 6/20 into +0x1C after SEC. If U-Boot + * zeroed the pair, IIS has no parent. Do not write +00/+04/+44. + * + * RetailOS music-playing oracle (checkpoint-010): +0x1C = 0xD0052003. + * That is a sample of a live register, not a value stock ever stores: + * only its bits 29:16 are ours, and only those are written. + * Leaving the SEC bring-up value 0x10122003 yields IIS STATUS 0x82A0 + * and a silent jack even with TXCOM=6 / CS42 unmuted. The divider field + * is what that turned out to need; the gate bits in the same word were + * never ours, and writing them is what cost us storage. + */ +#define SEC_CLKCON_18 0x20012001u +#define SEC_CLKCON_1C 0x10122003u +/* artifacts/retailos-mmio/music-playing/CLKCON.bin — checkpoint-010 */ +#define STOCK_CLKCON_08 0xa009200au +#define STOCK_CLKCON_0C 0x80000001u +#define STOCK_CLKCON_10 0x00008000u +#define STOCK_CLKCON_14 0x80002200u +#define STOCK_CLKCON_18 0x20012001u +#define STOCK_CLKCON_1C 0xD0052003u +/* + * The divider field of CLKCON+0x1C, and the only part of it that belongs to + * audio. Bits 31:30 and 15:14 are clock-domain gates (sub_41CBD8 cases + * 'E','F','G','H'), and bits 13:0 are a second domain's divider; none of + * them are ours to write. Mirrors stock's own "& 0xC000FFFF" writer. + */ +#define CLKCON_1C_DIV_MASK 0x3FFF0000u + +/* + * CLKCON+0x10 is the FM clock, and both paths write it. + * + * The decomp names it: sub_15DD5C powers FM through sub_41CBD8(v2, on) + * with v2 = sub_4E7B0() = 11, and case 11 of sub_41CBD8 clears bit 15 + * of 0x3C500010 to enable and sets it to disable. That is exactly the + * CLKCON_FM_GATE_ON / _IDLE pair below, which had been derived from the + * oracle. + * + * s5l8740_i2s_ungate writes the whole music-playing CLKCON snapshot, + * and in that snapshot FM is off -- STOCK_CLKCON_10 is 0x8000, bit 15 + * set, divider nibble zeroed. Playback starting while FM capture ran + * therefore gated off the capture's own clock and destroyed its + * divider. That is the same failure the +0x30 arbitration above already + * fixes in the other direction, so it gets the same treatment: while + * IIS2 holds the FM gate, playback leaves +0x10 alone. + */ +static bool s5l8740_fm_gate_held; + +/* + * OFF by default. This pushes a RetailOS music-playing snapshot into + * CLKCON +0x08..0x1C as whole-register writes, and those are SoC-wide + * clock gates, not audio-private ones. Blind full-register writes + * therefore discard whatever every other block had set. Observed: the + * device boots, FIL_Init reports the NAND fine, then the first audio + * start overwrites the clock tree and the FMSS controller loses its + * clock -- FMCTRL1 bit 30 never sets again, FMCTRL0 and NANDSTAT both + * read back the same stale word, and storage is gone until reboot. + * + * That paragraph was right, and the code under it was not, which is why + * this kept coming back. The dangerous knob (force_stock_audio_parent) + * defaulted off and carried this warning; meanwhile stock_clkcon_1c + * defaulted ON and did the same class of blind whole-register write to + * the same +0x1C. The comment claimed the branch below was + * read-modify-write and left other blocks alone. It was not, and it did + * not. Anyone who read the warning, checked that the warned-about + * parameter was off, and moved on would conclude the driver was safe. + * + * Both are fixed now. The snapshot path is deleted outright -- there is + * no safe version of it -- and +0x1C is a masked write of the audio + * divider field only. See the note at the write site. + */ +/* + * Write only CLKCON+0x1C, and nothing else. + * + * The whole-snapshot path that used to sit below is deleted. It pushed + * a sampled copy into +0x08..0x1C, and those are SoC-wide gates: + * the first audio start took the FMSS clock with it and storage was gone + * until reboot. That is a good reason to distrust the snapshot, and a bad + * reason to leave the one register the oracle actually calls out. + * + * Checkpoint-010's truth table gives +0x1C = 0xD0052003 for RetailOS + * playing music. We run 0x10122003, the SEC bring-up value. The low half + * is identical -- 0x2003 either way -- so the difference is entirely in + * the upper 16 bits, which is where the audio parent selection lives. + * + * +0x08, +0x0C, +0x10 and +0x14 are the registers that killed the NAND and + * they are not touched. This writes one register that the oracle attests + * to, and the storage path is checked after every test. + */ +static bool stock_clkcon_1c = true; +module_param(stock_clkcon_1c, bool, 0644); +MODULE_PARM_DESC(stock_clkcon_1c, + "write the RetailOS audio parent to CLKCON+0x1C only (default Y)"); + +/* sub_41CBD8(9,1): CLKCON+0x0C bit 15 clear = IIS0 CG16 on. */ +static void s5l8740_i2s_ungate(struct s5l8740_i2s *i2s) +{ + u32 v, r18, r1c; + + if (!i2s->clkcon) + return; + r18 = readl(i2s->clkcon + 0x18); + r1c = readl(i2s->clkcon + 0x1c); + /* + * The whole-register CLKCON snapshot that used to live here is gone. + * + * force_stock_audio_parent stamped a sampled copy of CLKCON+0x08 + * through +0x1C -- six SoC-wide clock registers -- straight over + * whatever every other peripheral had configured. It was sampled + * while RetailOS played music, so it carried the NAND, display and + * USB clock bits along with the audio ones and republished all of + * them. It is documented as destroying storage, and it does. + * + * There is no safe version of that write, so there is no parameter + * for it any more. What audio actually needs from this block is + * done below, one field at a time. + */ + { + if (!r18) + writel(SEC_CLKCON_18, i2s->clkcon + 0x18); + if (stock_clkcon_1c) { + /* + * Write the divider field ONLY. Never the whole word. + * + * This register was killing the NAND, and it did it by + * construction: STOCK_CLKCON_1C is a snapshot of the live + * register sampled while RetailOS played music, and a + * snapshot carries every other peripheral's bits in + * whatever state they happened to be in at that instant. + * Storing it whole republished all of them. + * + * Measured here: live 0x10122003 -> our 0xD0052003, so the + * top two bits go 00 -> 11. Per sub_41CBD8 cases 'E' and + * 'F' those two bits are clock-domain gates whose polarity + * is set-means-off ("if (a2) clear else set"), so the blind + * store gated off two domains, one of which the FMSS needs. + * That is the whole story of storage working at boot and + * dying with -EBUSY the moment audio ran: the CS sequencer + * lost its clock mid-flight and never went idle again, + * which is what fmss_cs_preflight() then refused on. + * + * Stock never leaves a whole-word value here. Every + * audio-path access is a masked read-modify-write, and the + * writer for this very field is + * + * MEMORY[0x3C50001C] = + * MEMORY[0x3C50001C] & 0xC000FFFF | ... + * + * preserving bits 31:30 and 15:0. The only whole-word store + * in the firmware saves the register first and restores it + * before returning. + * + * So this uses stock's own mask: bits 29:16, nothing else. + */ + u32 want = (r1c & ~CLKCON_1C_DIV_MASK) | + (STOCK_CLKCON_1C & CLKCON_1C_DIV_MASK); + + if (r1c != want) { + writel(want, i2s->clkcon + 0x1c); + dev_dbg(i2s->dev, + "CLKCON+1C %08x -> %08x (audio divider only, gates preserved)\n", + r1c, want); + } + } else if (!r1c) { + writel(SEC_CLKCON_1C, i2s->clkcon + 0x1c); + } + v = readl(i2s->clkcon + 0x0c); + if (v & 0x8000u) + writel(v & ~0x8000u, i2s->clkcon + 0x0c); + } +} + +/* RetailOS absolute dword — better than sticky play when idle. */ +/* + * CLKCON+0x30 gates the audio clock for IIS0 and IIS2 together. Each port + * used to write it directly, so stopping FM capture also idled the clock + * out from under music that was still playing. Track which ports want it + * running and only idle the gate once nobody does. + */ +static DEFINE_SPINLOCK(s5l8740_audio_clk_lock); +static bool s5l8740_audio_clk_wanted[S5L8740_AUDIO_PORTS]; + +/* + * CLKCON+0x30 is a PLL parameter, not an audio clock gate. Do not write it. + * + * This used to writel() CLKCON_AUDIO_PLAY (0x32190) here on every play and + * CLKCON_AUDIO_IDLE (0x1c20) on every stop, on the reading that +0x30 was + * the audio enable. It is not. The register is accessed exactly once in the + * whole OSOS image, inside the PLL bring-up at sub_63B4: + * + * if ((MEMORY[0x3C500044] & 1) == 0) { + * MEMORY[0x3C500044] &= ~0x10000u; + * MEMORY[0x3C500044] &= ~1u; + * MEMORY[0x3C500020] = ; + * MEMORY[0x3C500030] = 7200; + * MEMORY[0x3C500044] |= 0x10001u; + * while ((MEMORY[0x3C500040] & 1) == 0) + * ; + * } + * + * 7200 is 0x1C20 -- the value this driver called CLKCON_AUDIO_IDLE. So the + * "idle" write happened to restore the correct PLL parameter, and the + * "play" write put 0x32190 into it. Every playback corrupted the PLL and + * every stop quietly repaired it, which is why the register always read + * 0x1c20 whenever it was inspected at rest and nothing looked wrong. + * + * That is consistent with the whole symptom set: the IIS0 serialiser gets + * no usable clock while playing, so the TX FIFO fills once and never + * drains and the DMA stalls after topping it up; and the FMSS loses its + * clock too, which is how the NAND died mid-session and stayed dead until + * reboot. + * + * The real audio gate is sub_41CBD8, one bit per clock id -- id 9 is + * CLKCON+0x0C bit 15 and is handled by s5l8740_codec_clk_gate(). + * + * The wanted[] bookkeeping stays so the ports still declare intent and the + * log still says who wanted the clock; it simply no longer writes. + */ +static void s5l8740_audio_clk_set(void __iomem *clkcon, unsigned int port, + bool on) +{ + unsigned long flags; + unsigned int i; + bool any = false; + + if (!clkcon) + return; + spin_lock_irqsave(&s5l8740_audio_clk_lock, flags); + s5l8740_audio_clk_wanted[port] = on; + for (i = 0; i < S5L8740_AUDIO_PORTS; i++) + any |= s5l8740_audio_clk_wanted[i]; + spin_unlock_irqrestore(&s5l8740_audio_clk_lock, flags); + + /* + * Nothing is written to CLKCON+0x30 here, and there is no parameter + * to make it happen any more. That register is the audio PLL, it is + * shared, and writing it corrupted the PLL and took the NAND with + * it. The knob existed only to reproduce that on demand, which is + * not something this driver needs to be able to do. The port + * bookkeeping above is the entire job. + */ + (void)any; +} + +static struct s5l8740_i2s *s5l8740_i2s_iis0; + +/* + * Apply TXCON to the live register as soon as it is written. + * + * Sweeping TXCON used to mean setting clkdiv to defeat the "already + * programmed" check in s5l8740_i2s_program(), which re-ran the pad mux and + * the CLKCON writes on every play. This writes IIS0+0x04 and nothing else, + * so a sweep cannot disturb any clock. + */ +/* Resolve a coherent sub_BCB60 arm (or the explicit lab override). */ +static u32 s5l8740_i2s_txcon_value(struct s5l8740_i2s *i2s) +{ + u32 forced = READ_ONCE(txcon); + + if (forced) + return forced; + if (READ_ONCE(mastership_profile)) + return I2STXCON_A3_NONZERO | I2STXCON_WIDTH_16; + return (readl(i2s->base + I2STXCON) & I2STXCON_KEEP) | + I2STXCON_A3_0 | I2STXCON_WIDTH_16; +} + +static int mastership_profile_set(const char *val, + const struct kernel_param *kp) +{ + struct s5l8740_i2s *i2s = READ_ONCE(s5l8740_i2s_iis0); + unsigned int next; + int ret; + + (void)kp; + ret = kstrtouint(val, 0, &next); + if (ret) + return ret; + if (next > 2) + return -EINVAL; + WRITE_ONCE(mastership_profile, next); + if (i2s) { + i2s->programmed = false; + dev_dbg(i2s->dev, + "mastership_profile=%u (%s); applies on next program/start\n", + next, next == 0 ? "soc_master" : + next == 1 ? "ext_clock" : "ext_clock_slavecodec"); + } + return 0; +} + +static const struct kernel_param_ops mastership_profile_ops = { + .set = mastership_profile_set, + .get = param_get_uint, +}; +module_param_cb(mastership_profile, &mastership_profile_ops, + &mastership_profile, 0644); +MODULE_PARM_DESC(mastership_profile, + "0=soc_master (default), 1=ext_clock, 2=ext_clock with codec ASP bit7 clear"); + +static int txcon_set(const char *val, const struct kernel_param *kp) +{ + struct s5l8740_i2s *i2s = READ_ONCE(s5l8740_i2s_iis0); + int ret = param_set_uint(val, kp); + + if (ret) + return ret; + if (i2s && i2s->base) { + u32 v = s5l8740_i2s_txcon_value(i2s); + + writel(v, i2s->base + I2STXCON); + dev_dbg(i2s->dev, "txcon live -> 0x%08x (status=0x%08x)\n", + v, readl(i2s->base + I2SSTATUS)); + } + return 0; +} + +static const struct kernel_param_ops txcon_ops = { + .set = txcon_set, + .get = param_get_uint, +}; +module_param_cb(txcon, &txcon_ops, &txcon, 0644); +MODULE_PARM_DESC(txcon, "I2STXCON override; 0 (default) follows sub_BCB60"); + +/* + * The codec clock gate, RetailOS sub_41CBD8 with the id sub_4F82F8 returns. + * + * sub_4F82F8() returns 9, and case 9 of sub_41CBD8 is CLKCON+0x0C bit 15, + * active low: + * + * if (on) MEMORY[0x3C50000C] &= 0xFFFF7FFF; + * else MEMORY[0x3C50000C] |= 0x8000; + * + * D3280(1) ends by turning it OFF and D3280(3) begins by turning it back + * ON, which is what the 0x0006 / 0x0007 bit-6 freeze latch is bracketing: + * the codec is parked, its clock is stopped, and then the clock returns and + * the latch is released. This driver left the clock running the whole time, + * so that transition never happened. + * + * Exported because the CLKCON mapping lives here and the codec driver needs + * it. One bit, read-modify-write, nothing else touched -- the opposite of + * what s5l8740_audio_clk_set() used to do to CLKCON+0x30. + */ +void s5l8740_codec_clk_gate(bool on) +{ + struct s5l8740_i2s *i2s = READ_ONCE(s5l8740_i2s_iis0); + unsigned long flags; + u32 v; + + if (!i2s || !i2s->clkcon) + return; + spin_lock_irqsave(&s5l8740_audio_clk_lock, flags); + v = readl(i2s->clkcon + 0x0c); + if (on) + v &= ~CG16_DISABLE_BIT; + else + v |= CG16_DISABLE_BIT; + writel(v, i2s->clkcon + 0x0c); + spin_unlock_irqrestore(&s5l8740_audio_clk_lock, flags); + dev_dbg(i2s->dev, "codec clk gate %s (CLKCON+0x0C=0x%08x)\n", + on ? "ON" : "OFF", v); +} +EXPORT_SYMBOL_GPL(s5l8740_codec_clk_gate); + +/* + * Codec clock divider — RetailOS sub_345D28(9, 0, div). + * + * The C export shows this as a bare sub_345D28() with no arguments, which is + * why it was read as a delay for so long. The arguments are visible in the + * disassembly at 0x080D32BE: clock id 9 (the same id s5l8740_codec_clk_gate + * gates), selector 0, and a divider taken from the MCLK word -- + * 2 when it reads 12000, otherwise 4. + * + * The body is a ROM thunk into IRAM at 0x22000930, which sub_434() copies + * from ROM. Its clock-9 case is: + * + * r6 = 0x3C500000 (CLKCON) + * r2 = CLKCON[0x0C] + * bfc r2, #0, #15 clear bits 14:0 + * (selector 0 adds no source bits) + * if (div != 1 && div <= 17) r2 |= (div - 1) & 0xF + * CLKCON[0x0C] = r2 + * + * So bits 3:0 of CLKCON+0x0C are the codec clock divider, held as div-1, + * bits 13:12 are a source select, and bit 15 is the gate. Stock therefore + * runs the codec clock at source/2, and a register left at zero runs it at + * source/1 -- twice stock's rate. + * + * Bit 15 is preserved so this composes with the gate in either order. + */ +void s5l8740_codec_clk_divider(unsigned int div) +{ + struct s5l8740_i2s *i2s = READ_ONCE(s5l8740_i2s_iis0); + unsigned long flags; + unsigned int i; + u32 v; + + if (!i2s || !i2s->clkcon) + return; + if (!div || div > 17) + return; + + /* + * CLKCON+0x0C is CG16_AUD0, a 16-bit clock-gate register; CG16_AUD1 + * sits at +0x0E and therefore occupies bits 31:16 of the same 32-bit + * word (Rockbox s5l87xx.h). The output is + * + * !DISABLE * SEL_freq / (DIV1 + 1) / (DIV2 + 1) + * + * bit 15 DISABLE, set masks the clock -- the "gate" + * owned by s5l8740_codec_clk_gate() + * bits 13:12 SEL, source select (0 = OSC, 1..3 = PLL0..2) + * bits 7:4 DIV2 + * bits 3:0 DIV1 + * + * This is exactly the layout the ROM routine drives: its clock-9 case + * clears bits 14:0 and ORs in (div - 1), and its selector argument + * contributes 0x1000/0x2000/0x3000 -- the SEL field. + * + * Bits 14:0 are all AUD0's own fields, so clearing them is safe and + * reproduces the ROM exactly. What must be preserved is bit 15, which + * belongs to the gate, and bits 31:16, which are CG16_AUD1 -- another + * I2S clock. Never write this register as a whole word. + */ + spin_lock_irqsave(&s5l8740_audio_clk_lock, flags); + v = readl(i2s->clkcon + 0x0c); + v &= ~CG16_FIELDS; + if (div != 1) + v |= ((div - 1) & CG16_DIV1_MSK) << CG16_DIV1_POS; + writel(v, i2s->clkcon + 0x0c); + + /* + * Wait for the divider to take before returning. + * + * The bootloader's sub_14FC does exactly this, and the wait is the + * part that was missing here: + * + * v1 = (read(0x3C50000C) >> 15 << 15) | 1; + * write(0x3C50000C, v1); + * while (read(0x3C50000C) != v1) ; + * clear bit 15 (release the gate) + * + * It writes, spins until the register reads back what it wrote, and + * only then ungates. This driver wrote and returned, leaving the + * caller free to release the gate immediately -- so the codec could + * be handed a clock whose divider had not settled. + * + * Bounded, because a spin with no escape in a driver is not the same + * proposition as one in a bootloader that owns the machine. + */ + for (i = 0; i < 10000; i++) { + if (readl(i2s->clkcon + 0x0c) == v) + break; + cpu_relax(); + } + spin_unlock_irqrestore(&s5l8740_audio_clk_lock, flags); + + if (i == 10000) + dev_warn(i2s->dev, + "codec clk divider did not read back (want 0x%08x, got 0x%08x)\n", + v, readl(i2s->clkcon + 0x0c)); + dev_dbg(i2s->dev, "codec clk CG16_AUD0 DIV1=/%u (CLKCON+0x0C=0x%08x)\n", + div, v); +} +EXPORT_SYMBOL_GPL(s5l8740_codec_clk_divider); + +static void s5l8740_i2s_clkcon_audio(struct s5l8740_i2s *i2s, u32 val) +{ + if (!i2s) + return; + s5l8740_audio_clk_set(i2s->clkcon, S5L8740_AUDIO_PORT_IIS0, + val != CLKCON_AUDIO_IDLE); +} + +/* + * STOP teardown (beats RetailOS sticky TX): TXCOM stop → terminate DMA → + * clear I2SCLKCON → CLKCON+0x30 idle dword. + */ +static void s5l8740_i2s_iis0_gate(struct s5l8740_i2s *i2s, bool on); + +static void s5l8740_i2s_hw_stop(struct s5l8740_i2s *i2s, + struct snd_pcm_substream *substream) +{ + struct dma_chan *chan = NULL; + + if (!i2s || !i2s->base) + return; + + /* Stock's stop writes are below, after the DMA is quiesced. */ + + /* + * Only ask dmaengine_pcm for the channel when dmaengine_pcm is + * actually registered. The re-arm path deliberately does not + * register it -- it owns the PCM itself -- so + * substream->runtime->private_data is not a + * dmaengine_pcm_runtime_data and snd_dmaengine_pcm_get_chan() + * dereferences NULL: + * + * Unable to handle kernel NULL pointer dereference at 00000000 + * PC is at snd_dmaengine_pcm_get_chan+0x8/0x10 + * LR is at s5l8740_i2s_trigger+0x1f8 + * + * i2s->tx_chan is the channel on that path anyway. + */ + if (substream && i2s->has_dma && !use_pio && !dma_rearm) + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + chan = i2s->tx_chan; + if (chan) { + /* + * _async, not _sync. This runs from the DAI trigger, and the + * trigger is reachable from inside the DMA period callback -- + * an xrun in snd_pcm_period_elapsed() calls snd_pcm_do_stop() + * which lands right here. The sync variant waits for that + * callback to finish, from inside the callback. + * + * Nothing is lost by not waiting. terminate_all stops the + * channel synchronously in the controller, with a bounded + * poll, so the hardware is already quiet before the clock + * gating below; the sync half only waits on callback + * delivery, and ALSA does that properly through sync_stop. + */ + dmaengine_terminate_async(chan); + } + + /* + * No teardown. Stock does not tear the transmitter down on stop. + * + * MEASURED on RetailOS. After a music session was stopped and the + * FM radio entered and stopped, with the screen off, IIS0 still read: + * + * CLKCON 00000001 TXCON 03100099 TXCOM 00000006 + * RXCON 00001000 RXCOM 00000000 REG44 00010007 + * + * every one identical to the values captured while it was playing. + * PL080 ch2 was still enabled at the same time -- EnbldChns 4, + * Config 1, cfg 00028a81, ctl 84249000 -- with only src and the + * count at different positions. PWRCON1 bit 7 was still CLEAR, so + * the clock had not been re-gated either. + * + * So sub_C09AC(iis, 0) exists in the image but is not what a normal + * stop runs. This function used to transliterate it faithfully -- + * TXCOM=0, RXCOM=0, CLKCON=0, spin for the stop-ack, re-gate -- and + * that was a faithful copy of a path stock does not take. + * + * It also broke the start side by implication: our start ran against + * a fully torn-down block every time, while stock starts against one + * that is already configured, enabled and clocked. Same start code, + * different initial state. + * + * The DMA is still terminated above, because ALSA owns that buffer + * and must not have it read after the stream goes away. Everything + * the I2S block holds is left exactly as stock leaves it. + */ +} + + +static void s5l8740_i2s_gpiocmd(struct s5l8740_i2s *i2s, unsigned int gpio, u8 cmd) +{ + unsigned int bank = gpio >> 3; + unsigned int pin = gpio & 7; + int (*dir_set)(unsigned int, bool); + void __iomem *b; + + if (!i2s->gpio || !i2s->gpiocmd) + return; + /* + * The direction bit goes through the gpio driver, which owns this + * register and takes a lock across it. + * + * DIR is per-bank: eight pads in one word. This used to be an + * unlocked read-modify-write here while gpio-s5l8740.c did its own + * unlocked read-modify-write on the same word, so either could drop + * a bit the other had just set. Two owners of one register is the + * bug; doing our half under a lock the other owner does not take + * would not have fixed it. + * + * Only the direction is delegated. GPIOCMD below is a separate + * write-only register and this driver is its only user. + */ + dir_set = (int (*)(unsigned int, bool)) + __symbol_get("s5l8740_gpio_dir_set"); + if (dir_set) { + dir_set(gpio, true); + __symbol_put("s5l8740_gpio_dir_set"); + } else { + dev_warn_once(i2s->dev, + "gpio driver absent; DIR left as found for %u\n", + gpio); + } + + b = i2s->gpio + 32 * bank; + writel((bank << 16) | (pin << 8) | cmd, i2s->gpiocmd); +} + +static void s5l8740_i2s_log_iis_gpio(struct s5l8740_i2s *i2s, const char *tag) +{ + void (*logpads)(const char *); + + logpads = (void (*)(const char *))__symbol_get("s5l8740_gpio_log_iis0_pads"); + if (logpads) { + logpads(tag); + __symbol_put("s5l8740_gpio_log_iis0_pads"); + } else if (i2s->dev) { + dev_dbg(i2s->dev, "%s pads (no gpio export)\n", tag); + } +} + +/* + * GPIO 4/5 are deliberately left alone. Two stock paths claim them and it + * is not settled which applies here: sub_71B8 drives them as a two-bit + * output mux, while the per-bus I2C pinmux helper puts them at function 2 + * as a SCL/SDA pair. i2c0 is the Tristar bus and it times out on glass, + * so the I2C reading is the more likely one and forcing them to outputs + * would make that permanent. Nothing in the audio path needs them. + */ + +/* + * sub_BCB60 muxes both IIS0 pads together on every TX enable: + * sub_43D38C(0x14, 3) and sub_43D38C(7, 3), and puts them back to + * function 2 on disable. GPIO 7 is an I2S pad, not a display pad -- the + * panel is driven entirely from the LCDIF and no display code here + * touches GPIO at all. Claiming only GPIO 20 leaves the bus incomplete + * and the jack silent. + */ +/* + * The stock pad set while RetailOS plays, from audio checkpoint-010: + * bank0 PCON 0x32112224 / DIR 0xFF, bank2 PCON 0x02230000 / DIR 0x70. + * + * We set 7 and 20 and have never touched 6, 21 or 22. That gap is worth + * closing now because everything else matches the oracle -- TXCON, TXCOM, + * CLKDIV 272, CLKCON +0x18 and +0x1C, IIS STATUS 0x424, PL080 channel 2 on + * peri 10 -- and the jack is still silent. Clocks and status can all read + * correct while the serialiser's data pin is not muxed out of the SoC, + * which is exactly the case the checkpoint's "wire/data" branch describes. + */ +static const struct { u8 gpio, func; } stock_audio_pads[] = { + { 6, 2 }, { 7, 3 }, { 20, 3 }, { 21, 2 }, { 22, 2 }, +}; + +/* + * Off: measured, and there is nothing to restore. + * + * devmem on the running device reads bank0 PCON 0x32222224 / DIR 0xFF and + * bank2 PCON 0x02230000 / DIR 0x70 against the oracle's 0x32112224 / 0xFF + * and 0x02230000 / 0x70. Every audio pad already matches -- GPIO6 f2, + * GPIO7 f3, GPIO20 f3, GPIO21 f2, GPIO22 f2, all outputs. The only bank0 + * difference is pins 4 and 5, which belong to I2C0 and which the + * checkpoint explicitly says to leave alone. + * + * Kept because it costs nothing and pins the invariant, but it is not the + * silence and turning it on requires a kernel rebuild -- gpio-s5l8740 is + * built in, so the export it needs is not reachable from a module reload. + */ + +static void s5l8740_i2s_pads(struct s5l8740_i2s *i2s) +{ + unsigned int func = READ_ONCE(mastership_profile) ? 3 : 2; + + /* + * GPIO 7 and 20, with their function selected by the same profile + * that selects TXCON. + * + * sub_BCB60 muxes these two pads itself, guarded by + * sub_414FAA() == unit -- and sub_414FAA is "movs r0, #0; bx lr", so + * the guard passes only for unit 0. These are IIS0's pads; the 97, 98 + * and 119 muxed by sub_BCB60's other caller belong to IIS1 and IIS2. + * + * The function comes from the same a3 that selects the TXCON word: + * + * 80bcb8c cbz r7, 80bcbba a3 == 0 ? + * 80bcb96 movs r1, #3 a3 != 0: sub_43D38C(20, 3, 0) + * 80bcba2 movs r1, #3 sub_43D38C(7, 3, 0) + * 80bcbc4 movs r1, #2 a3 == 0: sub_43D38C(20, 2, 0) + * 80bcbce movs r1, #2 sub_43D38C(7, 2, 0) + * + * sub_43D38C is the pad-function call: its bank base is + * 32*(gpio>>3) + 1022361600, and 1022361600 is 0x3CF00000. + * + * The old default split the two halves: function 3 (a3 != 0) with the + * a3 == 0 TXCON. They are kept together here. Profile 1 restores the + * coherent a3 != 0 combination from the only historical warm-boot run + * in which an audible, wrong-pitch tone was reported. That result does + * not establish a cold-boot default, so profile 0 remains the default + * until the on-device matrix is captured. + * + * This is a controlled A/B mechanism, not a claim that either profile + * is correct before the cold-boot evidence exists. + */ + s5l8740_i2s_gpiocmd(i2s, 7, func); + s5l8740_i2s_gpiocmd(i2s, 20, func); + s5l8740_i2s_log_iis_gpio(i2s, "pads-applied"); + dev_dbg(i2s->dev, "mastership_profile=%u pads 7/20 func=%u\n", + mastership_profile, func); +} + +static void s5l8740_i2s_c09ac_start(struct s5l8740_i2s *i2s); +static void s5l8740_i2s_program(struct s5l8740_i2s *i2s, unsigned int rate); +static void s5l8740_i2s_tx_kick(struct s5l8740_i2s *i2s, bool dma); + +static int fifo_wait_loops = 50; +module_param(fifo_wait_loops, int, 0644); +MODULE_PARM_DESC(fifo_wait_loops, "max polls for IIS TX FIFO ready before write"); + +static int s5l8740_i2s_codec_prepare(void) +{ + int (*prep)(void) = __symbol_get("cs42l81_play_prepare"); + int ret = 0; + + if (prep) { + ret = prep(); + __symbol_put("cs42l81_play_prepare"); + } + return ret; +} + +static int s5l8740_i2s_codec_play_start(void) +{ + int (*start)(void) = __symbol_get("cs42l81_play_start"); + int ret = 0; + + if (start) { + ret = start(); + __symbol_put("cs42l81_play_start"); + } + return ret; +} + +static void s5l8740_i2s_codec_play_stop(void) +{ + void (*stop)(void) = __symbol_get("cs42l81_play_stop"); + + if (stop) { + stop(); + __symbol_put("cs42l81_play_stop"); + } +} + +static int s5l8740_i2s_audio_path_mode(void) +{ + int (*mode)(void) = __symbol_get("cs42l81_get_audio_path_mode"); + int m = 1; + + if (mode) { + m = mode(); + __symbol_put("cs42l81_get_audio_path_mode"); + } + return m; +} + +static int __maybe_unused s5l8740_i2s_asp_lock(void) +{ + int (*asp)(void) = __symbol_get("cs42l81_post_iis_start"); + int ret = -ENOENT; + + if (asp) { + msleep(20); + ret = asp(); + __symbol_put("cs42l81_post_iis_start"); + } + return ret; +} + +static int __maybe_unused s5l8740_i2s_asp_hold_light(void) +{ + int (*hold)(void) = __symbol_get("cs42l81_asp_hold_light"); + int ret = -ENOENT; + + if (hold) { + ret = hold(); + __symbol_put("cs42l81_asp_hold_light"); + } + return ret; +} + +static void s5l8740_i2s_log_clocks(struct s5l8740_i2s *i2s, const char *tag) +{ + u32 c0c = 0, c18 = 0, c1c = 0; + u32 clkcon = 0, txcon = 0, txcom = 0, rxcon = 0, rxcom = 0; + u32 status = 0, clkdiv = 0, reg44v = 0, pw1 = 0; + + if (!i2s || !i2s->dev) + return; + if (i2s->clkcon) { + c0c = readl(i2s->clkcon + 0x0c); + c18 = readl(i2s->clkcon + 0x18); + c1c = readl(i2s->clkcon + 0x1c); + } + if (i2s->base) { + clkcon = readl(i2s->base + I2SCLKCON); + txcon = readl(i2s->base + I2STXCON); + txcom = readl(i2s->base + I2STXCOM); + rxcon = readl(i2s->base + I2SRXCON); + rxcom = readl(i2s->base + I2SRXCOM); + status = readl(i2s->base + I2SSTATUS); + clkdiv = readl(i2s->base + I2SCLKDIV); + reg44v = readl(i2s->base + I2SREG44); + } + if (i2s->clkcon) + pw1 = readl(i2s->clkcon + CLKCON_PWRCON1); + dev_dbg(i2s->dev, + "%s CLKCON+0C=%08x +18=%08x +1C=%08x PWRCON1=%08x IIS0 +00=%08x +04=%08x +08=%08x +30=%08x +34=%08x +3C=%08x +40=%08x +44=%08x\n", + tag, c0c, c18, c1c, pw1, clkcon, txcon, txcom, rxcon, rxcom, + status, clkdiv, reg44v); +} + +static void s5l8740_i2s_pre_codec(void) +{ + void (*pre)(void); + + pre = (void (*)(void))__symbol_get("cs42l81_pre_iis_start"); + if (pre) { + pre(); + __symbol_put("cs42l81_pre_iis_start"); + } +} + +static void s5l8740_i2s_set_codec_clock_role(struct s5l8740_i2s *i2s) +{ + int (*set_role)(bool); + unsigned int profile = READ_ONCE(mastership_profile); + bool asp_bit7 = profile != 2; + int ret = -ENOENT; + + set_role = (int (*)(bool))__symbol_get("cs42l81_set_clock_role"); + if (set_role) { + ret = set_role(asp_bit7); + __symbol_put("cs42l81_set_clock_role"); + } + dev_dbg(i2s->dev, + "mastership_profile=%u codec ASP_BIT7=%d ret=%d\n", + profile, asp_bit7, ret); +} + +static void s5l8740_i2s_schedule_asp(void) +{ + void (*sched)(void); + + sched = (void (*)(void))__symbol_get("cs42l81_schedule_post_iis"); + if (sched) { + sched(); + __symbol_put("cs42l81_schedule_post_iis"); + } +} + +static void s5l8740_i2s_cancel_asp(void) +{ + void (*cancel)(void); + + cancel = (void (*)(void))__symbol_get("cs42l81_cancel_post_iis"); + if (cancel) { + cancel(); + __symbol_put("cs42l81_cancel_post_iis"); + } +} +static void s5l8740_i2s_log_txcon(struct device *dev, u32 v, const char *tag) +{ + dev_dbg(dev, + "%s txcon=0x%08x %s%s%s%s\n", + tag, v, + (v & 0x40u) ? "24bit " : "16bit ", + (v & I2STXCON_A3_0) == I2STXCON_A3_0 ? "BCB60-a3zero " : "", + (v & 0x00100000u) ? "bit20-a3nonzero " : "", + v == 0x0B100019u ? "ROCKBOX-POISON " : ""); +} + +/* + * sub_C09AC(port, 1) is CLKCON = 1, but it is preceded by + * sub_345D70(0x26, 0, 1), and what that call does is NOT established. + * + * This comment used to assert "345D70 is JUMPOUT 0x22000350 = bootloader + * sub_350 (SCTLR C-bit)". The thunk half is right and the conclusion is + * wrong. sub_345D70 is "ldr pc, [pc, #-4]" loading 0x22000351 -- bit 0 set, + * so an interworking branch to THUMB at 0x22000350. In the bootloader image + * we have on disk, file offset 0x350 is ARM: + * + * 0x350: mov r1,#4 / mrc p15,0,r0,c1,c0,0 / orr / mcr / bx lr + * 0x364: same with bic 0x378: mov r1,#0x1000 + * + * i.e. SCTLR C set, C cleared, I set. Two things rule that out as the + * callee: + * + * - A Thumb target cannot be that ARM code. Every sibling thunk in the + * same block has the Thumb bit set and lands on an ARM word too: + * 345D28 -> 0x22000931 (e3a000d3), 345D40 -> 0x220002B3 (mid-word), + * 345D48 -> 0x22001023, 345D58 -> 0x2200104B, 345CE8 -> 0x220011E5. + * The SRAM resident at 0x22000000 while the OS runs is not the + * bootloader image on disk; 0x350 merely happens to look decodable. + * + * - An SCTLR cache-bit setter takes no arguments. sub_C09AC passes + * (0x26, 0, 1) and sub_11B70 passes (0x1E, 0, 1) before it touches any + * SPI2 register. Two unrelated callers supplying what reads as + * (peripheral id, ?, on) is not a cache toggle. + * + * So from usage it is a per-peripheral gate keyed by id, and the id is + * selected by port. In sub_C09AC the selection is, from the raw image: + * + * 0x0C09CC cbz r2, 0x0C09DA ; port 0 + * 0x0C09CE cmp r2, #1 + * 0x0C09D0 beq 0x0C09DE + * 0x0C09D2 cmp r2, #2 + * 0x0C09D4 beq 0x0C09F0 + * 0x0C09DA movs r0, #0x26 ; IIS0 <-- our port + * 0x0C09DE movs r0, #0x23 ; IIS1 + * 0x0C09E0 movs r1, #0 + * 0x0C09E4 movs r2, #1 ; 0 on the disable path at 0x0C0A06 + * 0x0C09E6 blx 0x00345D70 + * + * so IIS0 enable is sub_345D70(0x26, 0, 1). sub_11B70 does the same shape + * for SPI with ids 0x22 and 0x2b (0x011BD8 / 0x011BDE), not 0x1E -- an + * earlier note here had both SPI ids wrong. + * + * Note these are BLX, not BL: sub_345D70 is ARM. A BL scan finds nothing, + * which is how the call sites got mis-reported once already. + * + * It is NOT sub_41CBD8/id 9. That one is CLKCON+0x0C bit 15, where CLEAR + * means the IIS0 clock is on, and we already drive it via + * s5l8740_codec_clk_gate(); measured 0x00000001 at trigger, i.e. correct. + * + * WE DO NOT ASSERT THE 0x26 GATE AT ALL. s5l8740_i2s_c09ac_start() below + * writes CLKCON = 1 and nothing else, so this step of sub_C09AC has no + * counterpart here. + * + * This matters: if the 0x26 gate is something the IIS needs and we never + * assert an equivalent, that is a live candidate for the transmitter not + * clocking. Do not resolve it by sweeping CLKCON. + * + * Play 414FAE only starts -- it does not C09AC-stop first. + * RetailOS music: I2SCLKCON = 0x1 (not 0x2 stop-ack). + */ +/* + * IIS0 peripheral clock gate -- CLKCON+0x4C (PWRCON1) bit 7. + * + * MEASURED ON HARDWARE, RetailOS 1.0.2, with music playing and the output + * captured on the USB mixer at -21 dBFS peak / -37.6 dBFS RMS, 92 of 99 + * 100 ms windows active, so the transmitter was demonstrably producing sound + * at the moment these registers were read: + * + * PWRCON1 idle 0xef226fe3 bit 7 SET -- IIS0 gated off + * PWRCON1 playing 0xef226f23 bit 7 CLEAR -- IIS0 ungated + * + * The route to that bit: sub_C09AC(port, 1) calls sub_345D70(id, 0, 1) BEFORE + * writing I2SCLKCON, and picks the id by port -- cbz r2 at 0x0C09CC sends + * port 0 to "movs r0, #0x26". sub_345D70 lives in resident SRAM (not the + * bootloader image on disk, which decodes to something else entirely at the + * same address); dumped over untethered SCSI it is a gate keyed by id: + * + * entry = 0x08917860 + id * 32 + * [entry+0x04] -> mask for CLKCON+0x48 [entry+0x10] -> +0x68 + * [entry+0x08] -> mask for CLKCON+0x4C [entry+0x14] -> +0x6C + * [entry+0x0C] -> mask for CLKCON+0x58 + * cbz r4 -> orrs, else bics, so on == 1 CLEARS the bit + * + * Live table entry 0x26 reads "26 00 00 00 | 80 00 00 00" -- id in field 0, + * then mask 0x80 in the +0x08 slot, i.e. PWRCON1 bit 7. Every bit music + * cleared is accounted for by an entry: 0x26 and 0x27 in PWRCON1, and 0x3C, + * 0x3D, 0x3E in PWRCON3. + * + * Why this is the whole failure: with the gate set, the register interface is + * still clocked, so every IIS0 register we program reads back correct and + * identical to stock's working set -- CLKCON=1, TXCON=0x03100099, TXCOM=6, + * RXCON=0x1000, CLKDIV=0x110. The serialiser is not clocked, so it neither + * drains the TX FIFO nor raises an underrun; the DMA halts after its first + * 32-byte burst, terminal count never arrives, and the PL080 interrupt never + * fires. That is exactly what we measured. + * + * Strict single-bit read-modify-write. PWRCON1 packs 32 unrelated + * peripherals and a set bit gates one OFF, so writing anything wider here + * would switch off blocks nothing has claimed. Do not "simplify" this into a + * whole-register write. + * + * Only IIS0 is handled. sub_C09AC sends port 1 to id 0x23 and port 2 to the + * arm at 0x0C09F0, which is not decoded, so those are left alone. + */ + +static void s5l8740_i2s_iis0_gate(struct s5l8740_i2s *i2s, bool on) +{ + /* + * Deliberately does nothing. PWRCON1 bit 7 has an owner. + * + * This briefly did a direct read-modify-write here, on the strength + * of the RetailOS measurement (PWRCON1 0xef226fe3 idle -> + * 0xef226f23 playing). The bit and the polarity were right and it + * changed nothing: still 0 periods, still no PL080 interrupt. + * + * It was also the wrong way to do it. clk-s5l8702.c already + * registers that exact bit as a clock: + * + * [CLK_I2S0] = GATE("i2s0", NULL, CLKCON_PWRCON1, 7) + * + * so a raw write here gives one bit two owners and fights the clock + * framework. The real defect was in the devicetree: i2s@3ca00000 had + * no clocks property at all, so devm_clk_bulk_get_all() returned + * zero clocks and clk_bulk_prepare_enable() had nothing to enable. + * The nodrm variant of the DTS already carried the property; the + * main one did not. It does now, naming CLK_I2S0 and CLK_CG16_9. + * + * Kept as a stub rather than deleted so the call sites still mark + * where sub_C09AC gates and ungates, and so nobody re-adds the raw + * write when the next gate turns up. + */ + (void)i2s; + (void)on; +} + +static void s5l8740_i2s_c09ac_start(struct s5l8740_i2s *i2s) +{ + /* sub_C09AC order: gate first, then I2SCLKCON. */ + s5l8740_i2s_iis0_gate(i2s, true); + writel(1, i2s->base + I2SCLKCON); +} + +/* OSOS sub_C095E(port, 0): clear TX sticky STATUS bit15 (W1C). */ +static void s5l8740_i2s_status_w1c_tx(struct s5l8740_i2s *i2s) +{ + u32 before, after; + + if (!i2s || !i2s->base) + return; + before = readl(i2s->base + I2SSTATUS); + writel(I2SSTATUS_TX_W1C, i2s->base + I2SSTATUS); + after = readl(i2s->base + I2SSTATUS); + if (i2s->dev && (before & I2SSTATUS_TX_W1C)) + dev_dbg(i2s->dev, "STATUS W1C tx sticky %08x->%08x\n", + before, after); +} + +/* 26DDDE: 41CBD8(9,1), 5705DC RX, 414FAE (C09AC + BCB60), D34C0 CLKDIV. */ +static void s5l8740_i2s_program(struct s5l8740_i2s *i2s, unsigned int rate) +{ + const struct n31_rate_cfg *r; + u32 div; + u32 rxcom; + u32 txcon_v; + + /* Same resolver the codec uses, so the divider always matches. */ + rate = n31_resolve_rate(rate); + r = n31_find_rate(rate); + + /* + * Do not re-do all of this for a rate we are already programmed for. + * + * Something opens the PCM during boot and prepare can fail, and when + * it does the PCM layer retries at the next advertised rate. Every one + * of those retries landed here and re-ran the pad mux, the TXCON and + * RXCON writes and the clock programming from scratch. With two rates + * advertised that was a couple of passes; advertising all nine the + * hardware supports turned it into a visible storm of pinmux and reset + * lines at boot, which is alarming to read and does real work for no + * reason. + * + * Programming is idempotent, so the cheapest correct answer is not to + * repeat it. This does not fix whatever opens the PCM or whatever makes + * prepare fail -- both are still open -- it stops those from thrashing + * the pads and the clock while they are unresolved. + */ + if (i2s->programmed && i2s->rate == rate && !clkdiv) { + dev_dbg(i2s->dev, "program: already at %u, skipping\n", rate); + return; + } + + if (clkdiv) + div = clkdiv; + else if (r) + div = r->clkdiv; + else + div = MCLK_ASSUME_HZ / N31_RATE_DEFAULT; + if (div < 1) + div = 1; + s5l8740_i2s_ungate(i2s); + s5l8740_i2s_clkcon_audio(i2s, CLKCON_AUDIO_PLAY); + s5l8740_i2s_c09ac_start(i2s); + s5l8740_i2s_set_codec_clock_role(i2s); + s5l8740_i2s_pads(i2s); + /* Coherent sub_BCB60 profile, a5 = 16; +0x30 = 0x1000. */ + txcon_v = s5l8740_i2s_txcon_value(i2s); + writel(txcon_v, i2s->base + I2STXCON); + if (i2s->dev) + s5l8740_i2s_log_txcon(i2s->dev, txcon_v, "program"); + writel(I2SRXCON_N31, i2s->base + I2SRXCON); + rxcom = readl(i2s->base + I2SRXCOM); + writel(rxcom & ~4u, i2s->base + I2SRXCOM); + writel(div, i2s->base + I2SCLKDIV); + /* + * IIS0+0x44 is not written by stock. The 0x00010007 here came from a + * register *readback* taken while RetailOS was playing, not from any + * write in the image: enumerating every function that reaches the IIS + * base through sub_43A858 gives offsets +0x00, +0x04, +0x08, +0x30, + * +0x34, +0x3C and +0x40, and nothing else. Whatever put 0x00010007 + * there did it from somewhere we have not identified, so writing it + * ourselves is a guess about a register we cannot name. + * + * reg44 restores the write for comparison. + */ + if (reg44) + writel((u32)reg44, i2s->base + I2SREG44); + /* + * Setup only. TXCOM stays 0 until .trigger START (OSOS sub_B6620). + * + * A previous attempt left 0xC here so that bit 3 would be set before + * dmaengine arms the channel, on the strength of a "bit 3 has to be + * set before the DMA kick, or STATUS sticks at 0x24" note. That note + * is this driver's own, and the Rockbox N7G port inherited it from + * here -- so reading it back out of Rockbox was not corroboration. + * + * The image disagrees. TXCOM is written by exactly two functions in + * the whole of OSOS: sub_B6620 ORs 6 into it (and 6 into RXCOM at + * +52 on the capture side), and sub_C09AC writes 0 on stop. Bit 3 is + * never set anywhere. Whatever makes the serialiser start, it is not + * that. + * + * txcom_mode=2 still sets bit 3 for anyone who wants to retry it as a + * deliberate experiment. + */ + /* sub_5705DC: clear bit 2, keep the rest. */ + writel(readl(i2s->base + I2STXCOM) & ~I2STXCOM_RUN, + i2s->base + I2STXCOM); + i2s->rate = rate; + i2s->programmed = true; +} + +/* + * OSOS B6620(port,0): TXCOM |= 6 after PL080 armed. Glass also needs bit 3 + * (PIO path 0xC) or STATUS stays 0x24 / jack silent. Set bit 3 before DMA. + */ +static void s5l8740_i2s_tx_kick(struct s5l8740_i2s *i2s, bool dma) +{ + u32 txcom, before, after; + + if (!i2s || !i2s->base) + return; + s5l8740_i2s_pre_codec(); + /* + * Prime the FIFO with silence so the serialiser has something to + * clock out between the kick and the first DMA burst. This used to + * push a generated tone, which mixed a chirp into the front of every + * stream the card played. + */ + { + unsigned int n = fifo_prefill, i; + + if (n > 64) + n = 64; + for (i = 0; i < n; i++) + writel(0, i2s->base + I2STXFIFO); + } + before = readl(i2s->base + I2STXCOM); + if (txcom_exact >= 0) { + writel((u32)txcom_exact, i2s->base + I2STXCOM); + } else if (dma) { + switch (txcom_mode) { + case 0: + /* + * sub_B6620(port, 0) is *(base + 8) |= 6 -- an OR, and + * it stays an OR. s5l8740_i2s_program() now leaves + * 0xC here, so this lands on 0xE and bit 3 stays set + * through playback rather than being dropped. + * + * The read is kept because the stock start path does + * one, and a read-then-write is not the same bus + * activity as a bare write on this block. + */ + writel(readl(i2s->base + I2STXCOM) | I2STXCOM_DMA, + i2s->base + I2STXCOM); + break; + case 1: /* pio-only kick (debug) */ + writel(txcom_pio, i2s->base + I2STXCOM); + break; + default: /* hybrid: glass bit3 + DMA */ + txcom = before; + writel(txcom | I2STXCOM_PIO, i2s->base + I2STXCOM); + writel(txcom | I2STXCOM_PIO | I2STXCOM_DMA, + i2s->base + I2STXCOM); + break; + } + } else { + writel(txcom_pio, i2s->base + I2STXCOM); + } + after = readl(i2s->base + I2STXCOM); + /* RetailOS music never leaves TX sticky 0x8000 set — clear after kick. */ + s5l8740_i2s_status_w1c_tx(i2s); + /* Keep C09AC start bit; 0x2 is stop-ack class (sub_C09AC wait). */ + if ((readl(i2s->base + I2SCLKCON) & 1u) == 0) + writel(1, i2s->base + I2SCLKCON); + if (i2s->dev) + dev_dbg(i2s->dev, + "tx_kick dma=%d mode=%d txcom %08x->%08x status=%08x clkcon=%08x\n", + dma, txcom_mode, before, after, + readl(i2s->base + I2SSTATUS), + readl(i2s->base + I2SCLKCON)); +} + +/* + * s5l8740_i2s_dma_watch() is gone. + * + * It sampled the PL080 source pointer every 100 ms and xrun-ed the stream + * after dma_stall_ms of no movement. Stock has no counterpart: sub_43A858, + * the only IIS base resolver, is called from exactly six functions and every + * one is a configuration, enable, disable or status call on a control path. + * Nothing in the image reads Cx_SrcAddr or Cx_DstAddr for progress, and there + * are exactly two audio threads -- MeCCAInputTask and MeCCAOutputTask -- + * with no timer or monitor between them. + * + * Stock's only bound on the DMA is the per-submit semaphore timeout inside + * sub_B65F4 (10000 ticks, returning 31), and sub_BFA50 at EA 0x0BFA6E + * discards that return entirely -- a DMA timeout in the audio path is + * invisible and the task simply carries on to the next period. + * + * A kernel cannot copy that: stock's producer IS the RTOS task, while ours + * has an ALSA writer blocked in snd_pcm_write() that would never be released. + * s5l8740_rearm_watchfn() is now the single bound, and it sits at the same + * point in the sequence as sub_B65F4 -- one deadline per submitted period. + * Its value is OURS; the tick period behind stock's 10000 is not established. + */ + +static int s5l8740_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dai->dev); + unsigned int rate = params_rate(params); + unsigned int resolved = n31_resolve_rate(rate); + const struct n31_rate_cfg *r = n31_find_rate(resolved); + u32 div; + int ret; + + if (!i2s || !i2s->base) + return -ENODEV; + /* + * Do not refuse an out-of-table rate: resolve it to the nearest + * supported one and let the codec SRC carry it. Refusing here while + * the codec silently fell back to 44.1 was how the two ends ended up + * disagreeing. + */ + if (resolved != rate) + dev_warn(dai->dev, "rate %u unsupported, using %u (SRC)\n", + rate, resolved); + /* + * Name whatever is driving the open/start cycle. + * + * The whole sequence -- hw_params, program, pinmux, DMA start, mute -- + * repeats endlessly at boot, and every fix so far has been to a step + * inside it, which cannot stop something that keeps calling the cycle + * again. Individual steps are not the problem; the caller is. + * + * hw_params is the top of that cycle, so print a backtrace for the + * first few passes. Three is enough to see whether it arrives from a + * syscall (a process opening the PCM), from a kernel worker, or from + * the same place every time. + */ + /* + * One line, every time, naming who asked. + * + * This was three dump_stack() calls, which is the wrong shape for a + * device with no console: by the time anyone reads the screen the + * loop has run hundreds of times and those three multi-line traces + * are long gone off the top. current->comm and pid fit on one line + * and answer the only question that matters -- whether this arrives + * from a userspace process, and which, or from a kernel thread. + */ + if (hw_params_trace) { + hw_params_seen++; + dev_info(dai->dev, "hw_params #%u by %s[%d] rate=%u\n", + hw_params_seen, current->comm, current->pid, + params_rate(params)); + + /* + * Stop the scroll and leave the answer on screen. + * + * This device has no console; the log is read off the display + * by eye. A loop that reopens the PCM hundreds of times makes + * every added line unreadable -- it scrolls past faster than + * anyone can parse, so more logging cannot help. Halting can: + * with panic=0 the machine stops with this as the last thing + * printed, and it names exactly who kept asking. + * + * Only fires when the loop is real. A handful of opens during + * a normal boot stays well under the limit. + */ + if (hw_params_loop_panic && + hw_params_seen >= hw_params_loop_panic) + panic("n31: PCM reopened %u times, last by %s[%d] rate=%u", + hw_params_seen, current->comm, current->pid, + params_rate(params)); + } + + ret = s5l8740_i2s_codec_prepare(); + if (ret && i2s->dev) + dev_warn(i2s->dev, "codec prepare in hw_params: %d\n", ret); + s5l8740_i2s_program(i2s, resolved); + div = clkdiv ? clkdiv : (r ? r->clkdiv : 0); + s5l8740_i2s_log_clocks(i2s, "hw_params"); + dev_info(dai->dev, + "IIS hw_params rate=%u resolved=%u code=%u clkdiv=%u dma=%d pio=%d txcom=%08x\n", + rate, resolved, r ? r->cs42_rate_code : 0, div, i2s->has_dma, + use_pio, readl(i2s->base + I2STXCOM)); + return 0; +} + +static int s5l8740_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dai->dev); + int path_mode; + + if (!i2s || !i2s->base) + return -ENODEV; + + /* + * Name who is driving the trigger, and from where. + * + * The stream start/stops about nine times a second and never moves a + * sample: DAI trigger START, then STOP under a millisecond later, + * then START again. The 110 ms between cycles is just the codec + * rate-change settle inside codec_play_start, so the loop is as + * tight as the hardware allows. Nothing in this driver asks for that + * STOP, and the re-arm engine is the only bound left, so the caller + * is upstream of us and this is the only way to see it. + * + * Backtrace on the first few STOPs only. A trace every 110 ms would + * push itself out of the ring buffer before anyone could read it, + * which is exactly what happened to the last set of dump_stack() + * calls put in the hw_params path. + */ + { + static unsigned int trig_seen; + struct snd_pcm_runtime *rt = substream->runtime; + + if (trig_seen < 24) { + trig_seen++; + dev_dbg(dai->dev, + "trigger cmd=%d by %s[%d] state=%d hw_ptr=%lu appl_ptr=%lu%s", + cmd, current->comm, current->pid, + rt ? (int)rt->status->state : -1, + rt ? (unsigned long)rt->status->hw_ptr : 0UL, + rt ? (unsigned long)rt->control->appl_ptr : 0UL, + "\n"); + /* + * dump_stack() prints unconditionally, so dynamic + * debug cannot gate it and an ordinary stream stop + * would emit around two hundred lines per boot. + * Behind a parameter instead: set trace_trigger=1 to + * identify a caller that is stopping the stream. + */ + if (trace_trigger && + cmd == SNDRV_PCM_TRIGGER_STOP && trig_seen < 8) + dump_stack(); + } + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * Stage markers, printed before each step. The trigger has + * hung here, and the DMA start log was the last thing on + * screen -- which tells us only that it got that far, not + * which of the following steps failed to return. Naming the + * step before running it answers that directly, on a device + * whose only diagnostic is what is left on the display. + */ + path_mode = s5l8740_i2s_audio_path_mode(); + /* + * dmaengine_pcm owns the substream on the cyclic path, so the + * stall guard would otherwise have nothing to xrun. The DAI + * trigger sees it under both models. + */ + WRITE_ONCE(i2s->ss, substream); + dev_dbg(dai->dev, "trig: path_mode=%d\n", path_mode); + if (path_mode == 1) { + dev_dbg(dai->dev, "trig: codec_play_start(1)\n"); + s5l8740_i2s_codec_play_start(); + } + if (dma_rearm && !use_pio) { + /* + * Stock submits the first buffer and only then does + * TXCOM |= 6. The dmaengine path would have armed the + * channel before this callback ever ran. + */ + int rret = s5l8740_rearm_start(i2s); + + dev_dbg(dai->dev, "trig: rearm_start ret=%d\n", rret); + } + dev_dbg(dai->dev, "trig: tx_kick\n"); + s5l8740_i2s_tx_kick(i2s, !use_pio); + if (path_mode == 2) { + dev_dbg(dai->dev, "trig: codec_play_start(2)\n"); + s5l8740_i2s_codec_play_start(); + } + dev_dbg(dai->dev, "trig: log_clocks\n"); + s5l8740_i2s_log_clocks(i2s, "trigger_start"); + dev_dbg(dai->dev, "trig: schedule_asp\n"); + s5l8740_i2s_schedule_asp(); + dev_dbg(dai->dev, "trig: done\n"); + i2s->pio_run = use_pio; + i2s->play_jiffies = jiffies; + dev_info_ratelimited(dai->dev, + "DAI trigger START path_mode=%d txcom=%08x sustain=%ums\n", + path_mode, readl(i2s->base + I2STXCOM), sustain_ms); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (sustain_ms && + time_before(jiffies, + i2s->play_jiffies + + msecs_to_jiffies(sustain_ms))) { + dev_info_ratelimited(dai->dev, + "DAI trigger STOP ignored (%ums sustain)\n", + sustain_ms); + return 0; + } + i2s->pio_run = false; + if (!use_pio && !dma_rearm) + WRITE_ONCE(i2s->ss, NULL); + s5l8740_i2s_cancel_asp(); + if (dma_rearm && !use_pio) + s5l8740_rearm_stop(i2s); + s5l8740_i2s_codec_play_stop(); + s5l8740_i2s_hw_stop(i2s, substream); + dev_info_ratelimited(dai->dev, "DAI trigger STOP txcom=0\n"); + return 0; + default: + return -EINVAL; + } +} + +static int s5l8740_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dai->dev); + + if (i2s->has_dma) + snd_soc_dai_init_dma_data(dai, &i2s->play_dma, NULL); + return 0; +} + +/* + * Bring the codec up here, not in the trigger. + * + * cs42l81_play_start() sleeps for 160 ms of stock's own settle time -- + * msleep(60) for the mode-18 path settle and msleep(100) for the stage-2 + * graph. Those durations are stock's and are not ours to shorten. + * + * Where they ran was ours, and it was wrong. The trigger runs inside + * snd_pcm_start() with the PCM stream lock held, and on a nonatomic DAI + * link that lock is a mutex the writer needs to add a period. tinyalsa + * starts the stream at a start_threshold of one period, so the sequence was: + * write one period, block 160 ms in the trigger, DMA starts, the single + * queued period drains in 23 ms, and snd_pcm_period_elapsed() finds + * hw_ptr == appl_ptr and declares an underrun. The application had never + * been scheduled with the lock free. Measured on the device: 1 period, then + * XRUN, then the recovery paid the 160 ms again -- forever. Playback ran + * about 200 ms per second and mmap playback, which prefills the whole + * buffer before starting, ran a full 423-period file with 0 underruns. + * + * prepare() is the callback for exactly this: it runs before the + * application fills anything and before the stream starts, so the settle + * cost is paid once, off the playback clock. That is also stock's order -- + * the MeCCAOutputTask preamble at EA 0x080B5B1C precedes the producer + * callback that fills the first buffer, which precedes the PL080 enable. + * Running the codec start from the trigger put it AFTER the fill, which is + * the divergence, not the fix. + * + * The trigger still calls it. cs42l81_play_start() returns immediately once + * the state is CS42_PLAYING, so that call is a no-op on the normal path and + * remains the backstop if prepare() was skipped. + */ +static int s5l8740_i2s_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dai->dev); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + if (s5l8740_i2s_audio_path_mode() != 1) + return 0; + + WRITE_ONCE(i2s->ss, substream); + return s5l8740_i2s_codec_play_start(); +} + +static const struct snd_soc_dai_ops s5l8740_i2s_dai_ops = { + .probe = s5l8740_i2s_dai_probe, + .hw_params = s5l8740_i2s_hw_params, + .prepare = s5l8740_i2s_dai_prepare, + .trigger = s5l8740_i2s_trigger, +}; + +static struct snd_soc_dai_driver s5l8740_i2s_dai = { + .name = "s5l8740-i2s", + .playback = { + .stream_name = "I2S Playback", + .channels_min = 1, + .channels_max = 2, + /* + * THIS is the hi-res gate, not the component open. + * + * ASoC builds runtime->hw from the DAIs on the link and that + * overrides whatever snd_soc_set_runtime_hwparams() put there, + * so widening the mask in the component achieved nothing: with + * a rate outside the advertised set still negotiated and reached + * hw_params as "rate=96000 code=15", the code measured not to + * lock. Playback then died with an unrecoverable ALSA I/O + * error instead of being resampled. + * + * So this stays at the base mask and the probe widens it only + * otherwise. The codec DAI on the other end of the + * link carries the wide mask unconditionally; ALSA offers the + * intersection, so gating this one end is sufficient. + */ + .rates = N31_RATE_MASK, + .formats = S5L8740_I2S_FORMATS, + }, + .ops = &s5l8740_i2s_dai_ops, +}; + +static const struct snd_pcm_hardware s5l8740_pio_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = S5L8740_I2S_FORMATS, + .rates = S5L8740_I2S_RATES, + /* + * 8000, not 44100: the advertised set is now every rate a 12 MHz + * reference divides to exactly, and 8000 is the lowest of them. + * See N31_RATE_MASK. + */ + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 256, + /* + * One period must fit in ONE LLI node, because the re-arm model is + * a single node pointing at itself and s5l_pl080_rearm_set_src() + * refuses anything else -- rewriting node 0 of a multi-node chain + * would corrupt it. + * + * A node carries at most PL080_MAX_XFER_WORDS (0xfff) transfer units + * of s5l_pl080_unit() bytes. The only format here is S16_LE, so that + * is 4095 * 2 = 8190 bytes, and a frame is 4 bytes, giving 8188. + * + * This said 8192, which is two bytes over. mpg123 asked for exactly + * the advertised maximum, prep_dma_cyclic split it across two nodes, + * and the stream died at the first arm with "cannot stage period 2 + * (-22)" -- no terminal-count interrupt was even enabled on the + * first node (ctl=0x04249000 against the working 0x84249000). + */ + .period_bytes_max = 8188, + /* + * Four, not two. + * + * SDL2 asks for a buffer of exactly two periods, and with a minimum + * of two it got one: measured "period 8188 buffer 16376" out of + * ffplay, which at 48 kHz is 42.6 ms per period and 85 ms of buffer + * in total. The hardware pointer here only moves at period + * boundaries and the period callback comes from a workqueue, so + * every one of those 42 ms windows had to be met or the stream xran + * -- with the FIFO still full, which is why the underrun counter + * stayed at zero through every dropout. Random dropouts every few + * seconds under any other load is exactly that. + * + * Four periods gives the same application 170 ms and the callback + * three periods of slack instead of one. Costs latency no one here + * is asking for; buys a stream that survives an FTL read. + */ + .periods_min = 4, + .periods_max = 16, +}; + +static int s5l8740_pio_thread(void *data) +{ + struct s5l8740_i2s *i2s = data; + + while (!kthread_should_stop()) { + struct snd_pcm_substream *ss = i2s->ss; + struct snd_pcm_runtime *rt; + unsigned int pos, rate, burst, i; + u32 sample; + + if (!READ_ONCE(i2s->pio_run) || !ss) { + usleep_range(2000, 4000); + continue; + } + rt = ss->runtime; + if (!rt || !rt->dma_area) { + usleep_range(2000, 4000); + continue; + } + /* + * Pace with udelay — usleep_range(167us) rounds to a jiffy + * (~10 ms) and a 3s tone hung for a minute. Burst ~4 ms of + * realtime writes, then cond_resched so RNDIS still runs. + */ + rate = i2s->rate ? i2s->rate : N31_RATE_DEFAULT; + burst = rate / 250; /* ~4 ms */ + if (burst < 16) + burst = 16; + pos = i2s->pio_hw_ptr; + for (i = 0; i < burst && READ_ONCE(i2s->pio_run); i++) { + if (pos >= rt->buffer_size) + pos = 0; + sample = s5l8740_scale_lr(*(u32 *)(rt->dma_area + + frames_to_bytes(rt, pos))); + writel(sample, i2s->base + I2STXFIFO); + pos++; + if (pos >= rt->buffer_size) + pos = 0; + if (rt->period_size && (pos % rt->period_size) == 0) + snd_pcm_period_elapsed(ss); + udelay(1000000 / rate); + } + i2s->pio_hw_ptr = pos; + cond_resched(); + } + return 0; +} + +/* + * Per-buffer re-arm playback, the way RetailOS actually drives this DMA. + * + * snd_dmaengine_pcm drives the controller cyclically: one multi-period LLI + * chain, submitted once, left to free-run while the hardware walks the + * descriptors forever. Stock does not do that, and the difference sits on + * exactly the path that stalls. + * + * From the playback engine at 0xB5B14 and sub_BFA50's dispatch: + * + * first buffer sub_BBA2C -> sub_BFA50 -> sub_C34EE -> sub_B424C(a4=1) + * builds a five-dword LLI whose next-pointer is ITSELF, + * then falls through to sub_B6620 for TXCOM |= 6 + * + * every one after sub_BBA12 -> sub_BFA50 -> sub_C34DC -> sub_C4960(a4=0) + * programs the channel registers directly, writes the LLI + * register as 0, and never touches TXCOM again + * + * So the hardware is handed one transfer at a time and software re-arms it + * from the completion path. The self-link on the first descriptor is what + * keeps the channel busy if software is late -- it repeats the last period + * rather than stopping. + * + * This implements that model: one period-sized slave transfer at a time, + * re-armed from the DMA completion callback, with the transport kick issued + * once after the first submit. The ALSA pointer advances from the callback + * rather than from residue, so it does not depend on the residue reporting + * that the cyclic path needs. + * + * The callback also reads IIS STATUS bit 15 and write-1-clears it, counting + * asserts. That is what the stock loop does after every submit (sub_BB9F8 + * via sub_C093C/sub_C095E, incrementing a counter at +0x164), and it is an + * underrun flag, not a completion handshake. + */ +static void s5l8740_i2s_destroy_wq(void *wq) +{ + destroy_workqueue(wq); +} + +static void s5l8740_rearm_done(void *data); + +static int s5l8740_rearm_submit(struct s5l8740_i2s *i2s) +{ + struct snd_pcm_substream *ss = READ_ONCE(i2s->ss); + struct dma_async_tx_descriptor *desc; + struct snd_pcm_runtime *rt; + snd_pcm_uframes_t stage_pos; + dma_addr_t addr, stage; + size_t period; + int ret; + + if (!ss || !READ_ONCE(i2s->rearm_run) || !i2s->tx_chan) + return -ENODEV; + rt = ss->runtime; + if (!rt || !rt->period_size) + return -EINVAL; + + period = frames_to_bytes(rt, rt->period_size); + addr = rt->dma_addr + frames_to_bytes(rt, i2s->rearm_pos); + + /* The period the hardware picks up at its next reload. */ + stage_pos = i2s->rearm_pos + rt->period_size; + if (rt->buffer_size && stage_pos >= rt->buffer_size) + stage_pos -= rt->buffer_size; + stage = rt->dma_addr + frames_to_bytes(rt, stage_pos); + + /* + * Stock arms the channel ONCE and then only rewrites the descriptor. + * + * sub_BFA50 dispatches on a one-shot byte, obj[0x0C]: zero takes + * sub_C34EE -> sub_B424C(a4=1), which programs SrcAddr, DstAddr, LLI, + * Control, Control2 and finally Config with the enable bit, and builds + * a descriptor whose next-pointer is ITSELF. Non-zero takes + * sub_C34DC -> sub_C4960(a4=0), which writes only the 20-byte + * descriptor in memory -- no channel register at all -- and then waits + * on the per-channel semaphore. + * + * Because the node points at itself the transfer never terminates: + * after each terminal count the PL080S reloads SrcAddr, DstAddr, LLI, + * Control and Control2 from the descriptor, and Config.Enable stays + * set for the whole stream. There is no channel-disabled edge; the TC + * interrupt is the only completion signal. + * + * We used to call dmaengine_prep_slave_single() every period, which + * terminates the channel at each boundary and reprograms it from a + * workqueue. The IIS TX FIFO drains in about a millisecond, so every + * period boundary was an underrun window. + * + * prep_dma_cyclic() with buf_len == period_len builds exactly stock's + * topology -- nlli == 1, lli[0].lli == lli_phys -- so the first call + * arms it and every later call only moves the source. + */ + /* + * Nothing to post per period any more -- see the ring note below. + * The controller walks the buffer on its own, so once it is armed + * this is a no-op and only the ALSA bookkeeping in rearm_done runs. + */ + if (i2s->rearm_armed) + return 0; + + desc = dmaengine_prep_dma_cyclic(i2s->tx_chan, addr, period, period, + DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!desc) { + dev_err_ratelimited(i2s->dev, + "rearm: cyclic prep failed (period=%zu)\n", + period); + return -ENOMEM; + } + desc->callback = s5l8740_rearm_done; + desc->callback_param = i2s; + if (dma_submit_error(dmaengine_submit(desc))) + return -EIO; + dma_async_issue_pending(i2s->tx_chan); + i2s->rearm_armed = true; + + /* + * Hand the ring over and let the terminal-count interrupt walk it. + * + * The source walk for a cyclic ALSA buffer is fully determined by + * base, length and period, so software scheduling has no business + * being in the loop. It used to be: this function posted the next + * period from the same workqueue item as snd_pcm_period_elapsed(), + * inside a window exactly one period wide. On a single core also + * running USB that window gets missed, the PL080S reloads the stale + * source, and it replays the period it just finished -- audible as + * break-up, with nothing to show for it because the FIFO never ran + * dry and the underrun counter stayed at zero. + * + * s5l_pl080_start() has already loaded period zero into Cx_SrcAddr, + * which is why set_ring stages period one for the first reload. + */ + ret = s5l_pl080_rearm_set_ring(i2s->tx_chan, rt->dma_addr, + frames_to_bytes(rt, rt->buffer_size), + period); + if (ret) { + dev_err(i2s->dev, "rearm: cannot install ring (%d)\n", ret); + return ret; + } + + dev_dbg(i2s->dev, + "rearm: armed self-linked node, period %zu buffer %zu\n", + period, (size_t)frames_to_bytes(rt, rt->buffer_size)); + return 0; +} + +/* + * Process context, not the DMA tasklet. + * + * The DAI link is nonatomic, which makes the PCM stream lock a mutex, so + * snd_pcm_period_elapsed() sleeps. Calling it from the vchan tasklet gives + * "scheduling while atomic" and kills the re-arm after exactly one period: + * + * __schedule_bug from __schedule + * __mutex_lock from _snd_pcm_stream_lock_irqsave + * snd_pcm_period_elapsed from s5l8740_rearm_done + * s5l8740_rearm_done from vchan_complete + * + * The descriptor prep is deferred with it rather than left in the callback, + * because the PL080 prep path allocates and is not written for atomic + * context either. One period is ~23 ms at 44.1 kHz, so workqueue latency has + * plenty of room. + */ +/* + * Nothing has completed for rearm_stall_ms. Stop the stream so the writer + * gets -EPIPE instead of blocking forever. + */ +static void s5l8740_rearm_watchfn(struct work_struct *work) +{ + struct s5l8740_i2s *i2s = container_of(to_delayed_work(work), + struct s5l8740_i2s, rearm_watch); + struct snd_pcm_substream *ss = READ_ONCE(i2s->ss); + + if (!ss || !READ_ONCE(i2s->rearm_run)) + return; + dev_err(i2s->dev, + "rearm stalled: %u periods done, no completion in %u ms -- xrun\n", + i2s->rearm_periods, rearm_stall_ms); + WRITE_ONCE(i2s->rearm_run, false); + if (i2s->tx_chan) + dmaengine_terminate_async(i2s->tx_chan); + /* + * snd_pcm_stop_xrun() runs the stop synchronously, so this + * reaches s5l8740_i2s_trigger(STOP) and s5l8740_rearm_stop on + * this thread. rearm_stop tests current_work() and skips the + * sync cancel of this very item; without that test it waits for + * a completion only this thread can signal. + */ + snd_pcm_stop_xrun(ss); +} + +static void s5l8740_rearm_workfn(struct work_struct *work) +{ + struct s5l8740_i2s *i2s = container_of(work, struct s5l8740_i2s, + rearm_work); + struct snd_pcm_substream *ss = READ_ONCE(i2s->ss); + int ret; + + if (!ss || !READ_ONCE(i2s->rearm_run)) { + dev_dbg(i2s->dev, + "rearm work: ss=%p run=%d -- stopping\n", + ss, READ_ONCE(i2s->rearm_run)); + return; + } + /* + * Refuse to report a period for a stream that has already been + * stopped, even if a new one is running now. See rearm_gen. + */ + if (READ_ONCE(i2s->rearm_gen_queued) != READ_ONCE(i2s->rearm_gen)) + return; + /* + * Per-period tracing, so dev_dbg rather than dev_info. + * + * This runs once per period for the whole life of every stream. At + * info level that is a steady few lines a second with nothing wrong, + * which fills the log buffer and evicts everything around it over the + * length of a track. Ratelimiting caps the rate; it does not stop a + * healthy stream from writing to the log forever. + * + * Dynamic debug keeps the call site compiled in and silent until it + * is asked for, so per-period tracing stays available: + * + * echo 'file s5l8740-i2s.c +p' >/sys/kernel/debug/dynamic_debug/control + * + * The per-stream summary in rearm_stop() stays at info: one line per + * stream, and it carries the underrun count. + */ + dev_dbg(i2s->dev, "rearm work: elapsed+resubmit\n"); + snd_pcm_period_elapsed(ss); + /* + * period_elapsed() can stop the stream underneath us on an + * underrun, which lands in rearm_stop and clears rearm_run. + * Re-arming after that would restart a stream ALSA has already + * torn down. + */ + if (!READ_ONCE(i2s->rearm_run)) + return; + ret = s5l8740_rearm_submit(i2s); + if (ret) + dev_err_ratelimited(i2s->dev, + "rearm work: resubmit failed %d (period %u)\n", + ret, i2s->rearm_periods); +} + +static void s5l8740_rearm_done(void *data) +{ + struct s5l8740_i2s *i2s = data; + struct snd_pcm_substream *ss = READ_ONCE(i2s->ss); + struct snd_pcm_runtime *rt; + u32 st; + + if (!ss || !READ_ONCE(i2s->rearm_run)) + return; + rt = ss->runtime; + if (!rt || !rt->period_size) + return; + + /* sub_BB9F8: check and write-1-clear the underrun flag. */ + if (i2s->base) { + st = readl(i2s->base + I2SSTATUS); + /* + * sub_BB9F8: sub_C093C reads STATUS bit 15 and sub_C095E writes + * that bit back. Stock issues the clear UNCONDITIONALLY on every + * iteration, with only the counter gated on the test. We used to + * skip the write whenever the bit read clear. + */ + writel(I2SSTATUS_TX_W1C, i2s->base + I2SSTATUS); + if (st & I2SSTATUS_TX_W1C) + i2s->rearm_underrun++; + } + + i2s->rearm_pos += rt->period_size; + if (i2s->rearm_pos >= rt->buffer_size) + i2s->rearm_pos = 0; + i2s->rearm_periods++; + /* Per-period; see the note in rearm_workfn(). */ + dev_dbg(i2s->dev, "rearm done: period %u pos %lu\n", + i2s->rearm_periods, (unsigned long)i2s->rearm_pos); + + if (rearm_stall_ms) + mod_delayed_work(i2s->rearm_wq, &i2s->rearm_watch, + msecs_to_jiffies(rearm_stall_ms)); + WRITE_ONCE(i2s->rearm_gen_queued, READ_ONCE(i2s->rearm_gen)); + queue_work(i2s->rearm_wq, &i2s->rearm_work); +} + +static int s5l8740_rearm_start(struct s5l8740_i2s *i2s) +{ + struct dma_chan *chan; + struct dma_slave_config cfg = { }; + int ret; + + chan = s5l8740_i2s_tx_get(i2s); + if (IS_ERR_OR_NULL(chan)) + return chan ? PTR_ERR(chan) : -ENODEV; + + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = i2s->play_dma.addr; + cfg.dst_addr_width = i2s->play_dma.addr_width; + cfg.dst_maxburst = i2s->play_dma.maxburst; + ret = dmaengine_slave_config(chan, &cfg); + if (ret) + return ret; + + i2s->rearm_pos = 0; + i2s->rearm_periods = 0; + i2s->rearm_armed = false; + i2s->rearm_underrun = 0; + WRITE_ONCE(i2s->rearm_run, true); + + /* First transfer, then the transport kick -- sub_BFA50 then sub_B6620. */ + ret = s5l8740_rearm_submit(i2s); + if (ret) { + WRITE_ONCE(i2s->rearm_run, false); + return ret; + } + if (rearm_stall_ms) + mod_delayed_work(i2s->rearm_wq, &i2s->rearm_watch, + msecs_to_jiffies(rearm_stall_ms)); + return 0; +} + +/* + * Stop the re-arm engine. Must never block. + * + * Two distinct deadlocks reach this path, and neither can be waited out. + * + * Re-entrant: an xrun raised inside rearm_work returns to this function on + * the workqueue thread that is servicing the period, so a synchronous + * cancel would wait for a completion only that thread can signal. + * + * s5l8740_rearm_workfn + * snd_pcm_period_elapsed + * snd_pcm_update_hw_ptr0 -> snd_pcm_update_state + * snd_pcm_do_stop -> soc_pcm_trigger + * s5l8740_i2s_trigger(STOP) -> s5l8740_rearm_stop + * cancel_work_sync(&i2s->rearm_work) <- this work + * + * Lock cycle: any other task closing the PCM holds the substream lock + * across the trigger, while the worker needs that same lock inside + * snd_pcm_period_elapsed(). + * + * closer snd_pcm_drop -> snd_pcm_action_single [holds stream lock] + * soc_pcm_trigger -> s5l8740_rearm_stop + * cancel_work_sync() waits for worker + * worker s5l8740_rearm_workfn + * snd_pcm_period_elapsed + * _snd_pcm_stream_lock_irqsave waits for the lock + * + * A current_work() test does not catch the second one: the canceller is a + * different task, so the re-entrancy check says waiting is safe. Both + * tasks then sit in D state, taking the writer, /proc/asound and the UI + * down with them, and the device has to be power cycled. + * + * So this function only clears rearm_run and cancels without waiting. The + * blocking half is s5l8740_rearm_sync(), which ALSA invokes through + * .sync_stop once the stream lock has been dropped. + * + * The cyclic path in dma-s5l8740-pl080.c guards the same way for the same + * reason. + */ +static void s5l8740_rearm_stop(struct s5l8740_i2s *i2s) +{ + /* + * Clearing this is what actually stops the engine: both handlers + * re-check it and return without resubmitting. + */ + WRITE_ONCE(i2s->rearm_run, false); + /* Anything queued before this point belongs to the ending stream. */ + WRITE_ONCE(i2s->rearm_gen, READ_ONCE(i2s->rearm_gen) + 1); + + cancel_delayed_work(&i2s->rearm_watch); + cancel_work(&i2s->rearm_work); + if (i2s->tx_chan) + dmaengine_terminate_async(i2s->tx_chan); + dev_info_ratelimited(i2s->dev, + "rearm stop: %u periods, %u underruns\n", + i2s->rearm_periods, i2s->rearm_underrun); +} + +/* + * The blocking half, run from .sync_stop. + * + * ALSA calls sync_stop with the stream lock dropped and in a context that + * may sleep, which is the only safe place to wait for a handler that takes + * that lock itself. A worker still blocked in snd_pcm_period_elapsed() + * makes progress as soon as the closing thread releases the lock, so by + * the time this runs there is nothing left to deadlock against. + * + * Still guarded for re-entrancy: an xrun raised from inside rearm_work + * reaches the stop path on the workqueue thread itself. + */ +static void s5l8740_rearm_sync(struct s5l8740_i2s *i2s) +{ + struct work_struct *cur = current_work(); + + if (cur == &i2s->rearm_work || cur == &i2s->rearm_watch.work) { + dev_dbg(i2s->dev, + "rearm sync from the work itself; not waiting\n"); + return; + } + cancel_delayed_work_sync(&i2s->rearm_watch); + cancel_work_sync(&i2s->rearm_work); +} + +static int s5l8740_rearm_sync_stop(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + s5l8740_rearm_sync(i2s); + return 0; +} + +static snd_pcm_uframes_t s5l8740_rearm_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + return i2s->rearm_pos; +} + +static const struct snd_pcm_hardware s5l8740_i2s_dma_hw; + +static int s5l8740_rearm_open(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + /* + * Describe the hardware to ALSA. Without this the runtime carries no + * rate, format or buffer limits and the open is refused outright. + */ + snd_soc_set_runtime_hwparams(ss, &s5l8740_i2s_dma_hw); + + /* + * The buffer must be a whole number of periods. + * + * Without this ALSA is free to hand back a buffer_size that is not a + * multiple of period_size -- it just truncates when it computes the + * period count. mpg123 landed on exactly that: period 8188 bytes, + * buffer 65536, which is eight periods plus 32 bytes. + * + * Both the position walk here and the hardware ring step by whole + * periods and wrap at the buffer, so a remainder puts the last period + * of every lap partly past the end of the buffer and desynchronises + * our position from ALSA's. The ring install refuses that shape; this + * makes sure it never gets asked to accept it. + */ + snd_pcm_hw_constraint_integer(ss->runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + /* + * A period must fit in one LLI node. + * + * The re-arm ring is a single self-linked descriptor, so the whole + * period has to be expressible as one transfer. The controller counts + * in transfer units and tops out at PL080_MAX_XFER_WORDS of them, so + * at the 16-bit width this link runs at the ceiling is 8190 bytes. + * prep_dma_cyclic() splits anything larger across several nodes, + * s5l_pl080_rearm_set_ring() refuses that shape, and the stream then + * produces no periods at all rather than degrading. + * + * period_bytes_max in the hw description is the buffer-side limit and + * is larger than this, so the constraint has to come from the DMA + * driver rather than from that table. With the 65536-byte buffer and + * integer periods above, this lands on 4096 bytes and 16 periods. + */ + { + size_t seg = s5l_pl080_max_seg_bytes(); + + if (seg && seg < s5l8740_i2s_dma_hw.period_bytes_max) + snd_pcm_hw_constraint_minmax(ss->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + s5l8740_i2s_dma_hw.period_bytes_min, + seg); + } + /* + * Use the whole SRAM buffer, always. + * + * The 64 KiB pool is preallocated whether or not a stream uses it, so + * a smaller buffer buys nothing and costs slack. ffplay was choosing + * 32752 bytes -- four periods, 170 ms at 48 kHz -- and had to decode, + * soxr-resample and read the FTL inside every one of those windows. + * It could not, and the result was a restart every few periods: + * 1 to 6 periods per stream, always with "0 underruns", because the + * hardware FIFO was never the thing that ran dry. + * + * 65536 is eight periods and 341 ms, double the slack, for memory + * that was already spent. Latency is irrelevant here -- nothing on + * this device is monitoring or playing live. + */ + snd_pcm_hw_constraint_minmax(ss->runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 65536, 65536); + WRITE_ONCE(i2s->ss, ss); + i2s->rearm_pos = 0; + return 0; +} + +static int s5l8740_rearm_close(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + s5l8740_rearm_stop(i2s); + /* close() may sleep and holds no stream lock. */ + s5l8740_rearm_sync(i2s); + WRITE_ONCE(i2s->ss, NULL); + return 0; +} + +static int s5l8740_rearm_pcm_new(struct snd_soc_component *comp, + struct snd_soc_pcm_runtime *rtd) +{ + /* + * SNDRV_DMA_TYPE_DEV, not VMALLOC: the re-arm path hands + * runtime->dma_addr straight to the controller, so the buffer has to + * be DMA-coherent and physically contiguous. + */ + int ret = snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + comp->dev, 64 * 1024, 64 * 1024); + + /* + * Say where it landed. SRAM is 0x22000000..0x2202FFFF; anything + * else means the memory-region binding did not take and we are + * back on DRAM, which is exactly the difference against stock this + * change exists to remove. + */ + /* + * The buffer MUST be in SRAM. Stock runs PL080 ch2 with its source + * in SRAM -- measured src=0x220025d0 against dst=0x3ca00010 while + * playing -- and whether this controller can sustain the transfer + * from DRAM at all is NOT established. Our DRAM-sourced attempts + * moved exactly one 32-byte burst and then stalled. + * + * So a DRAM buffer is not an acceptable fallback: it would run the + * card in a configuration stock never uses, silently, and every + * measurement taken against it would be worthless. Fail instead. + */ + if (!ret) { + struct snd_pcm_substream *ss = + rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + + if (!ss) + return -ENODEV; + if (ss->dma_buffer.addr < S5L8740_SRAM_BASE || + ss->dma_buffer.addr >= S5L8740_SRAM_END) { + dev_err(comp->dev, + "PCM buffer at %pad is not SRAM -- refusing\n", + &ss->dma_buffer.addr); + return -ENXIO; + } + dev_info(comp->dev, "PCM buffer at %pad (SRAM, like stock)\n", + &ss->dma_buffer.addr); + } + return ret; +} + +static const struct snd_soc_component_driver s5l8740_i2s_rearm_component = { + .name = "s5l8740-i2s", + .legacy_dai_naming = 1, + .open = s5l8740_rearm_open, + .close = s5l8740_rearm_close, + .pointer = s5l8740_rearm_pointer, + .sync_stop = s5l8740_rearm_sync_stop, + .pcm_construct = s5l8740_rearm_pcm_new, +}; + +static int s5l8740_pio_open(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + if (!use_pio) + return 0; + snd_soc_set_runtime_hwparams(ss, &s5l8740_pio_hw); + i2s->ss = ss; + i2s->pio_hw_ptr = 0; + if (!i2s->kthread) { + i2s->kthread = kthread_run(s5l8740_pio_thread, i2s, + "n31-i2s-pio"); + if (IS_ERR(i2s->kthread)) { + int ret = PTR_ERR(i2s->kthread); + + i2s->kthread = NULL; + return ret; + } + } + return 0; +} + +static int s5l8740_pio_close(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + i2s->pio_run = false; + i2s->ss = NULL; + return 0; +} + +static snd_pcm_uframes_t s5l8740_pio_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *ss) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(comp->dev); + + return i2s->pio_hw_ptr; +} + +static int s5l8740_pio_pcm_new(struct snd_soc_component *comp, + struct snd_soc_pcm_runtime *rtd) +{ + if (!use_pio) + return 0; + return snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 64 * 1024, 64 * 1024); +} + +static const struct snd_soc_component_driver s5l8740_i2s_component = { + .name = "s5l8740-i2s", + .legacy_dai_naming = 1, + .open = s5l8740_pio_open, + .close = s5l8740_pio_close, + .pointer = s5l8740_pio_pointer, + .pcm_construct = s5l8740_pio_pcm_new, +}; + +/* DMA path: dmaengine_pcm owns PCM ops. Do not install pointer(). */ +static const struct snd_soc_component_driver s5l8740_i2s_dai_component = { + .name = "s5l8740-i2s", + .legacy_dai_naming = 1, +}; + +static ssize_t regs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dev); + static const u32 offs[] = { + I2SCLKCON, I2STXCON, I2STXCOM, I2STXFIFO, + I2SRXCON, I2SRXCOM, I2SSTATUS, I2SCLKDIV, I2SREG44, + }; + int i, n = 0; + + if (!i2s || !i2s->base) + return -ENODEV; + for (i = 0; i < ARRAY_SIZE(offs); i++) + n += scnprintf(buf + n, PAGE_SIZE - n, "+0x%02x: 0x%08x\n", + offs[i], readl(i2s->base + offs[i])); + if (i2s->clkcon) { + static const u32 clk_offs[] = { + 0x00, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c, + 0x30, 0x44, 0x48, 0x4c, 0x58, 0x68, 0x6c, + }; + int c; + + for (c = 0; c < ARRAY_SIZE(clk_offs); c++) + n += scnprintf(buf + n, PAGE_SIZE - n, + "clk+0x%02x: 0x%08x\n", clk_offs[c], + readl(i2s->clkcon + clk_offs[c])); + } + if (i2s->gpio) { + u32 p0 = readl(i2s->gpio); + u32 d0 = readl(i2s->gpio + 0x04); + u32 p2 = readl(i2s->gpio + 64); + u32 d2 = readl(i2s->gpio + 68); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "pcon0=%08x din0=%08x pcon2=%08x din2=%08x\n", + p0, d0, p2, d2); + } + return n; +} +static DEVICE_ATTR_RO(regs); + +static ssize_t volume_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int vol; + + if (kstrtouint(buf, 0, &vol) || vol > S5L8740_USER_VOL_MAX) + return -EINVAL; + s5l8740_set_user_vol_q8(vol); + return count; +} + +static ssize_t volume_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%u/%u (RetailOS Q8, 256=unity)\n", + s5l8740_get_user_vol_q8(), S5L8740_USER_VOL_MAX); +} +static DEVICE_ATTR_RW(volume); + +static ssize_t pad_scan_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dev); + u32 xor[8] = { }, pcon[8] = { }, last[8] = { }; + unsigned int b, i, n = 0; + + if (!i2s || !i2s->gpio) + return -ENODEV; + for (b = 0; b < 8; b++) { + pcon[b] = readl(i2s->gpio + 32 * b); + last[b] = readl(i2s->gpio + 32 * b + 4); + } + for (i = 0; i < 40000; i++) { + for (b = 0; b < 8; b++) { + u32 d = readl(i2s->gpio + 32 * b + 4); + + xor[b] |= d ^ last[b]; + last[b] = d; + } + } + n += scnprintf(buf + n, PAGE_SIZE - n, + "clkcon=0x%x txcon=0x%x txcom=0x%x status=0x%x\n", + readl(i2s->base + I2SCLKCON), + readl(i2s->base + I2STXCON), + readl(i2s->base + I2STXCOM), + readl(i2s->base + I2SSTATUS)); + for (b = 0; b < 8; b++) + n += scnprintf(buf + n, PAGE_SIZE - n, + "b%u pcon=%08x xor=%02x\n", b, pcon[b], xor[b]); + return n; +} +static DEVICE_ATTR_RO(pad_scan); + +static struct dma_chan *s5l8740_i2s_tx_get(struct s5l8740_i2s *i2s) +{ + struct dma_chan *chan; + + if (i2s->tx_chan) + return i2s->tx_chan; + chan = s5l_pl080_lookup_peri(10); + if (chan) { + i2s->tx_chan = chan; + i2s->tx_chan_borrowed = 1; + return chan; + } + chan = s5l_pl080_request_slave(i2s->dev, 0); + if (IS_ERR_OR_NULL(chan)) + return chan; + i2s->tx_chan = chan; + i2s->tx_chan_borrowed = 0; + return chan; +} + +static void s5l8740_i2s_tx_put(struct s5l8740_i2s *i2s) +{ + if (!i2s || !i2s->tx_chan) + return; + if (!i2s->tx_chan_borrowed) + dma_release_channel(i2s->tx_chan); + i2s->tx_chan = NULL; + i2s->tx_chan_borrowed = 0; +} + +/* Program IIS and leave TXCOM running so BCLK/LRCK (and MCLK if any) stay up. */ +static ssize_t clk_run_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct s5l8740_i2s *i2s = dev_get_drvdata(dev); + unsigned int v; + + if (!i2s || !i2s->base) + return -ENODEV; + if (kstrtouint(buf, 0, &v)) + return -EINVAL; + if (v) { + s5l8740_i2s_program(i2s, n31_pick_rate(default_rate)); + s5l8740_i2s_tx_kick(i2s, false); + } else { + /* sub_5705DC: clear bit 2, keep the rest. */ + writel(readl(i2s->base + I2STXCOM) & ~I2STXCOM_RUN, + i2s->base + I2STXCOM); + } + dev_info(dev, "clk_run=%u status=0x%x txcom=0x%x\n", + v, readl(i2s->base + I2SSTATUS), + readl(i2s->base + I2STXCOM)); + return count; +} +static DEVICE_ATTR_WO(clk_run); + +/* + * Hardware limits for the dmaengine PCM. + * + * The card is registered nonatomic, because the codec is on SPI and its + * trigger sleeps. That makes snd_pcm_period_elapsed() a sleeping call, so the + * DMA driver has to deliver period callbacks from a workqueue rather than + * from its interrupt. A high-priority workqueue still costs milliseconds. + * + * With the defaults, tinyalsa negotiated two 1024-frame periods -- a 46 ms + * buffer with a 23 ms period. Any scheduling delay past 23 ms empties it, and + * measured on the device every stream went to XRUN within ~30 ms of START. + * + * The limits below stay permissive so applications can still choose small + * periods; what they add over the defaults is headroom -- a 256 KB + * preallocation and a 256 KB ceiling -- so an application that asks for a + * large buffer actually gets one. + */ +static const struct snd_pcm_hardware s5l8740_i2s_dma_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = 256 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 32768, + /* + * Four, for the same reason as the playback component: a two-period + * buffer leaves exactly one period of slack, and the period callback + * is a workqueue item on a single core that also runs the FTL. On + * capture the consequence is an overrun rather than an underrun, but + * the timing margin is identical. Measured on playback as random + * dropouts every few seconds with the hardware never once starving. + */ + .periods_min = 4, + .periods_max = 32, + .fifo_size = 8, +}; + +static const struct snd_dmaengine_pcm_config s5l8740_i2s_dma_cfg = { + .pcm_hardware = &s5l8740_i2s_dma_hw, + .prealloc_buffer_size = 256 * 1024, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static int s5l8740_i2s_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l8740_i2s *i2s; + struct resource *res; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + i2s->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + i2s->clkcon = devm_ioremap(dev, CLKCON_PHYS, 0x80); + i2s->gpio = devm_ioremap(dev, GPIO_PHYS, 0x200); + i2s->gpiocmd = devm_ioremap(dev, GPIOCMD_PHYS, 4); + + /* + * Bind the SRAM DMA pool named by memory-region, so the PCM buffer + * comes from SRAM the way stock's does. + * + * RetailOS runs PL080 ch2 with src in SRAM (0x220025d0 measured + * while playing, dst 0x3ca00010); ours was DRAM at 0x095c0020. + * Without this call the memory-region property is inert and + * dma_alloc_coherent() still lands in the default CMA/DRAM pool. + * + * Non-fatal: if the pool is missing or too small we keep the DRAM + * buffer rather than refusing to probe, because a working DRAM + * buffer is strictly better than no sound card at all. + */ + ret = of_reserved_mem_device_init(dev); + if (ret) { + dev_err(dev, + "SRAM dma pool did not bind (%d) -- refusing to run from DRAM\n", + ret); + return ret; + } + dev_info(dev, "PCM buffer pool: SRAM via memory-region\n"); + + ret = devm_clk_bulk_get_all(dev, &i2s->clks); + if (ret > 0) { + i2s->num_clks = ret; + ret = clk_bulk_prepare_enable(i2s->num_clks, i2s->clks); + if (ret) + dev_warn(dev, "clk_bulk_prepare_enable: %d (CLKCON ungate-all still on)\n", + ret); + } + + if (res) { + i2s->play_dma.addr = res->start + I2STXFIFO; + i2s->play_dma.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + i2s->play_dma.maxburst = 4; + } + + platform_set_drvdata(pdev, i2s); + dev_set_drvdata(dev, i2s); + WRITE_ONCE(s5l8740_i2s_iis0, i2s); + mutex_init(&i2s->dma_lock); + INIT_WORK(&i2s->rearm_work, s5l8740_rearm_workfn); + INIT_DELAYED_WORK(&i2s->rearm_watch, s5l8740_rearm_watchfn); + /* See the rearm_wq comment in struct s5l8740_i2s. */ + i2s->rearm_wq = alloc_workqueue("n31-i2s-rearm", + WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); + if (!i2s->rearm_wq) + return -ENOMEM; + ret = devm_add_action_or_reset(dev, s5l8740_i2s_destroy_wq, + i2s->rearm_wq); + if (ret) + return ret; + + if (dma_rearm && !use_pio && of_property_present(dev->of_node, "dmas")) { + /* + * The re-arm path owns the PCM itself, so dmaengine_pcm is not + * registered at all: it would install its own cyclic ops and a + * residue-based pointer, which is the model stock does not use. + */ + i2s->has_dma = true; + dev_info(dev, "PCM: per-buffer re-arm (stock model)\n"); + } else if (!use_pio && of_property_present(dev->of_node, "dmas")) { + ret = devm_snd_dmaengine_pcm_register(dev, + &s5l8740_i2s_dma_cfg, 0); + if (ret) { + dev_warn(dev, "dmaengine_pcm: %d — falling back to PIO\n", + ret); + use_pio = 1; + } else { + i2s->has_dma = true; + } + } else if (!use_pio) { + dev_warn(dev, "no dmas in DT — PIO\n"); + use_pio = 1; + } + + /* + * Widen the advertised rates before the DAI is registered. See the + * .rates comment on s5l8740_i2s_dai: this is the only thing that + * decides whether ALSA will ever offer 88.2/96 kHz, because ASoC + * derives runtime->hw from the DAIs and ignores what the component + * put there. + */ + + ret = devm_snd_soc_register_component(dev, + use_pio ? &s5l8740_i2s_component : + (dma_rearm ? + &s5l8740_i2s_rearm_component : + &s5l8740_i2s_dai_component), + &s5l8740_i2s_dai, 1); + if (ret) + return ret; + + ret = device_create_file(dev, &dev_attr_regs); + if (ret) + dev_warn(dev, "regs sysfs: %d\n", ret); + ret = device_create_file(dev, &dev_attr_volume); + if (ret) + dev_warn(dev, "volume sysfs: %d\n", ret); + ret = device_create_file(dev, &dev_attr_pad_scan); + if (ret) + dev_warn(dev, "pad_scan sysfs: %d\n", ret); + ret = device_create_file(dev, &dev_attr_clk_run); + if (ret) + dev_warn(dev, "clk_run sysfs: %d\n", ret); + + dev_info(dev, "S5L8740 IIS0 @%pR dma=%s pio=%d\n", + res, i2s->has_dma ? "yes" : "no", use_pio); + return 0; +} + +static void s5l8740_i2s_remove(struct platform_device *pdev) +{ + struct s5l8740_i2s *i2s = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_regs); + device_remove_file(&pdev->dev, &dev_attr_volume); + device_remove_file(&pdev->dev, &dev_attr_pad_scan); + device_remove_file(&pdev->dev, &dev_attr_clk_run); + if (i2s && i2s->kthread) { + i2s->pio_run = false; + kthread_stop(i2s->kthread); + i2s->kthread = NULL; + } + s5l8740_i2s_tx_put(i2s); + if (i2s && i2s->num_clks) + clk_bulk_disable_unprepare(i2s->num_clks, i2s->clks); +} + +/* + * IIS0 drives the codec through PL080. Left running across a kexec it + * keeps fetching from a buffer the next kernel has reused, so the + * handover is audible as well as unsafe. hw_stop is the same teardown + * the STOP path uses -- TXCOM stop, DMA terminated, pads released. + */ +static void s5l8740_i2s_shutdown_pdev(struct platform_device *pdev) +{ + struct s5l8740_i2s *i2s = platform_get_drvdata(pdev); + + if (!i2s) + return; + i2s->pio_run = false; + s5l8740_i2s_hw_stop(i2s, NULL); + s5l8740_i2s_tx_put(i2s); +} + +static const struct of_device_id s5l8740_i2s_of_match[] = { + { .compatible = "apple,s5l8740-i2s" }, + { .compatible = "samsung,s5l8740-i2s" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_i2s_of_match); + +static struct platform_driver s5l8740_i2s_driver = { + .probe = s5l8740_i2s_probe, + .remove = s5l8740_i2s_remove, + .shutdown = s5l8740_i2s_shutdown_pdev, + .driver = { + .name = "s5l8740-i2s", + .of_match_table = s5l8740_i2s_of_match, + }, +}; + +/* ------------------------------------------------------------------ */ +/* IIS2 — BCM2078 digital PCM capture */ +/* ------------------------------------------------------------------ */ + +/* + * IIS2 gets its own PCM config rather than sharing the playback one, + * because its buffer lives in the 32 KiB i2s2_sram pool and the shared + * config preallocates 256 KiB -- which would simply exhaust the pool. + * + * periods_min is 4 here for the same reason it is on playback: a + * two-period buffer leaves one period of slack for a callback that runs + * on a workqueue, on a single core that also runs the FTL. + */ +static const struct snd_pcm_hardware s5l8740_iis2_dma_hw = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 8192, + .periods_min = 4, + .periods_max = 16, + .fifo_size = 8, +}; + +static const struct snd_dmaengine_pcm_config s5l8740_iis2_dma_cfg = { + .pcm_hardware = &s5l8740_iis2_dma_hw, + .prealloc_buffer_size = 32 * 1024, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static uint iis2_clkdiv; +module_param(iis2_clkdiv, uint, 0644); +MODULE_PARM_DESC(iis2_clkdiv, "IIS2 CLKDIV override; 0 = FM oracle 0x96"); + +struct s5l8740_iis2 { + void __iomem *base; + void __iomem *clkcon; + void __iomem *gpio; + void __iomem *gpiocmd; + struct device *dev; + struct clk_bulk_data *clks; + int num_clks; + bool has_dma; + /* i2s2_sram bound; see the probe. Capture is refused without it. */ + bool sram_ok; + struct snd_dmaengine_dai_dma_data cap_dma; + u32 fm_gate_saved; + bool fm_gate_held; + unsigned int rate; +}; + +static u32 iis2_pick_clkdiv(unsigned int rate) +{ + const struct n31_rate_cfg *r; + + if (iis2_clkdiv) + return iis2_clkdiv; + /* + * The FM capture uses 0x96 where IIS0 runs 0x177 in the same session, + * so this divider is not derived from the IIS0 rate table. + */ + if (rate == 44100 || rate == 48000) + return IIS2_CLKDIV_FM_ORACLE; + r = n31_find_rate(rate); + if (r) + return r->clkdiv; + if (!rate) + rate = 44100; + return MCLK_ASSUME_HZ / rate; +} + +static void iis2_pads(struct s5l8740_iis2 *iis2, bool claim) +{ + static const u8 pads[] = { + IIS2_PAD_BCLK, IIS2_PAD_SYNC, IIS2_PAD_DATA, + }; + void __iomem *bank; + unsigned int i, pin; + u32 dir; + + if (!iis2->gpio || !iis2->gpiocmd) + return; + for (i = 0; i < ARRAY_SIZE(pads); i++) { + bank = iis2->gpio + 32u * (pads[i] >> 3); + pin = pads[i] & 7; + dir = readl(bank + 0x14); + if (claim) + dir |= BIT(pin); + else + dir &= ~BIT(pin); + writel(dir, bank + 0x14); + writel(((u32)(pads[i] >> 3) << 16) | (pin << 8) | + (claim ? IIS2_PAD_FUNC : IIS2_PAD_RELEASE), + iis2->gpiocmd); + } + if (iis2->dev) + dev_dbg(iis2->dev, "IIS2 pads 97/98/119 %s\n", + claim ? "claimed" : "released"); +} + +static void iis2_fm_gate(struct s5l8740_iis2 *iis2, bool on) +{ + u32 cur; + + if (!iis2 || !iis2->clkcon) + return; + cur = readl(iis2->clkcon + CLKCON_FM_GATE); + if (on) { + if (!iis2->fm_gate_held) { + iis2->fm_gate_saved = cur; + iis2->fm_gate_held = true; + } + WRITE_ONCE(s5l8740_fm_gate_held, true); + writel((cur & ~CLKCON_FM_GATE_MASK) | + (CLKCON_FM_GATE_ON & CLKCON_FM_GATE_MASK), + iis2->clkcon + CLKCON_FM_GATE); + } else if (iis2->fm_gate_held) { + u32 prev = iis2->fm_gate_saved ? iis2->fm_gate_saved : + CLKCON_FM_GATE_IDLE; + + /* Restore our own bits only, not the whole word. */ + writel((cur & ~CLKCON_FM_GATE_MASK) | + (prev & CLKCON_FM_GATE_MASK), + iis2->clkcon + CLKCON_FM_GATE); + iis2->fm_gate_held = false; + WRITE_ONCE(s5l8740_fm_gate_held, false); + } +} + +/* Peri 13 must be armed by dmaengine before RXCOM is kicked. */ +static void iis2_program_rx(struct s5l8740_iis2 *iis2) +{ + iis2_pads(iis2, true); + iis2_fm_gate(iis2, true); + s5l8740_audio_clk_set(iis2->clkcon, S5L8740_AUDIO_PORT_IIS2, true); + writel(IIS2_CLKCON_ON, iis2->base + I2SCLKCON); + writel(IIS2_TXCON_FM, iis2->base + I2STXCON); + writel(IIS2_RXCON_FM, iis2->base + I2SRXCON); + writel(iis2_pick_clkdiv(iis2->rate), iis2->base + I2SCLKDIV); + writel(IIS2_REG44_ORACLE, iis2->base + I2SREG44); +} + +static void iis2_hw_stop(struct s5l8740_iis2 *iis2) +{ + if (!iis2 || !iis2->base) + return; + writel(IIS2_RXCOM_IDLE, iis2->base + I2SRXCOM); + s5l8740_audio_clk_set(iis2->clkcon, S5L8740_AUDIO_PORT_IIS2, false); + iis2_fm_gate(iis2, false); + iis2_pads(iis2, false); +} + +static int s5l8740_iis2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (!iis2 || !iis2->base) + return -ENODEV; + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + /* No SRAM pool, no capture -- see the probe. */ + if (!iis2->sram_ok) { + dev_err_ratelimited(dai->dev, + "capture refused: i2s2_sram did not bind, and DRAM is not a configuration stock uses\n"); + return -ENODEV; + } + iis2->rate = params_rate(params); + iis2_program_rx(iis2); + dev_info(dai->dev, + "IIS2 hw_params rate=%u ch=%u clkdiv=0x%x reg44=0x%x status=0x%x\n", + iis2->rate, params_channels(params), + readl(iis2->base + I2SCLKDIV), readl(iis2->base + I2SREG44), + readl(iis2->base + I2SSTATUS)); + return 0; +} + +static int s5l8740_iis2_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (!iis2 || !iis2->base) + return -ENODEV; + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + iis2_program_rx(iis2); + writel(IIS2_RXCOM_DMA, iis2->base + I2SRXCOM); + dev_info(dai->dev, + "IIS2 capture start rxcom=0x%x status=0x%x\n", + readl(iis2->base + I2SRXCOM), + readl(iis2->base + I2SSTATUS)); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + iis2_hw_stop(iis2); + return 0; + default: + return -EINVAL; + } +} + +static int s5l8740_iis2_dai_probe(struct snd_soc_dai *dai) +{ + struct s5l8740_iis2 *iis2 = snd_soc_dai_get_drvdata(dai); + + if (iis2->has_dma) + snd_soc_dai_init_dma_data(dai, NULL, &iis2->cap_dma); + return 0; +} + +static const struct snd_soc_dai_ops s5l8740_iis2_dai_ops = { + .probe = s5l8740_iis2_dai_probe, + .hw_params = s5l8740_iis2_hw_params, + .trigger = s5l8740_iis2_trigger, +}; + +static struct snd_soc_dai_driver s5l8740_iis2_dai = { + .name = "bcm2078-pcm", + .capture = { + .stream_name = "BCM2078 PCM Capture", + .channels_min = 1, + .channels_max = 2, + /* + * Base mask, never the hi-res one: this is IIS2 to the + * BCM2078, the FM and Bluetooth port. It has nothing to do + * with the headphone path and no reason to claim 88.2/96. + */ + .rates = S5L8740_I2S_RATES, + .formats = S5L8740_I2S_FORMATS, + }, + .ops = &s5l8740_iis2_dai_ops, +}; + +static const struct snd_soc_component_driver s5l8740_iis2_component = { + .name = "bcm2078-pcm", + .legacy_dai_naming = 1, +}; + +static ssize_t iis2_regs_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct s5l8740_iis2 *iis2 = dev_get_drvdata(dev); + unsigned int i; + ssize_t n = 0; + + if (!iis2 || !iis2->base) + return sysfs_emit(buf, "not mapped\n"); + + for (i = 0; i < IIS2_REGS_LEN; i += 4) { + n += sysfs_emit_at(buf, n, "%02x: %08x\n", i, + readl(iis2->base + i)); + if (n >= PAGE_SIZE - 32) + break; + } + if (iis2->clkcon) { + n += sysfs_emit_at(buf, n, "clk+10: %08x\n", + readl(iis2->clkcon + CLKCON_FM_GATE)); + n += sysfs_emit_at(buf, n, "clk+30: %08x\n", + readl(iis2->clkcon + CLKCON_AUDIO_OFF)); + } + return n; +} +/* Same sysfs name as the IIS0 dump; different device, different symbol. */ +static struct device_attribute dev_attr_iis2_regs = + __ATTR(regs, 0444, iis2_regs_show, NULL); + +static int s5l8740_iis2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s5l8740_iis2 *iis2; + struct resource *res; + int ret; + + iis2 = devm_kzalloc(dev, sizeof(*iis2), GFP_KERNEL); + if (!iis2) + return -ENOMEM; + iis2->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iis2->base = devm_ioremap_resource(dev, res); + if (IS_ERR(iis2->base)) + return PTR_ERR(iis2->base); + + iis2->clkcon = devm_ioremap(dev, CLKCON_PHYS, 0x80); + iis2->gpio = devm_ioremap(dev, GPIO_PHYS, 0x200); + iis2->gpiocmd = devm_ioremap(dev, GPIOCMD_PHYS, 4); + + ret = devm_clk_bulk_get_all(dev, &iis2->clks); + if (ret > 0) { + iis2->num_clks = ret; + ret = clk_bulk_prepare_enable(iis2->num_clks, iis2->clks); + if (ret) + dev_warn(dev, "clk_bulk: %d\n", ret); + } + + if (res) { + iis2->cap_dma.addr = res->start + I2SRXFIFO; + iis2->cap_dma.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + iis2->cap_dma.maxburst = 1; + } + + platform_set_drvdata(pdev, iis2); + dev_set_drvdata(dev, iis2); + + if (!of_property_present(dev->of_node, "dmas")) { + dev_err(dev, "missing dmas (need peri 13 rx)\n"); + return -EINVAL; + } + /* + * Capture buffer in SRAM, like playback. + * + * The playback port refuses to run at all unless its buffer is in low + * SRAM: stock's PL080 channel was measured with src=0x220025d0, and + * the DRAM-sourced attempt moved exactly one 32-byte burst and then + * stalled. Whether receive behaves the same way is untested -- the + * point of putting it here is that nobody has to find out. Both ports + * now run from the memory stock uses. + * + * A failure here is recorded and refused at hw_params, NOT at probe. + * Failing the probe was tried and it takes the whole sound card down + * with it: nano7-audio has two dai_links, and with this DAI missing + * the card does not register at all -- "no soundcards", no headphone + * playback, because of a port nothing is using yet. Refusing the + * stream keeps the guarantee (nothing ever captures from DRAM) + * without holding playback hostage to an old DT. + */ + iis2->sram_ok = of_reserved_mem_device_init(dev) == 0; + if (!iis2->sram_ok) + dev_err(dev, + "i2s2_sram pool did not bind -- capture disabled (DT needs the audio-dma@22012000 node)\n"); + + ret = devm_snd_dmaengine_pcm_register(dev, &s5l8740_iis2_dma_cfg, 0); + if (ret) + return dev_err_probe(dev, ret, "dmaengine_pcm\n"); + iis2->has_dma = true; + + ret = devm_snd_soc_register_component(dev, &s5l8740_iis2_component, + &s5l8740_iis2_dai, 1); + if (ret) + return ret; + + ret = device_create_file(dev, &dev_attr_iis2_regs); + if (ret) + dev_warn(dev, "regs sysfs: %d\n", ret); + + dev_info(dev, "BCM2078 PCM RX @%pR peri13 FIFO@+0x38\n", res); + return 0; +} + +static void s5l8740_iis2_remove(struct platform_device *pdev) +{ + struct s5l8740_iis2 *iis2 = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_iis2_regs); + iis2_hw_stop(iis2); + if (iis2 && iis2->num_clks) + clk_bulk_disable_unprepare(iis2->num_clks, iis2->clks); +} + +/* + * IIS2 captures FM over PL080 and holds the CLKCON+0x10 gate while it + * does. Stopping it here also puts that gate back, so the next kernel + * does not inherit a clock enabled by a driver that no longer exists. + */ +/* + * Hand IIS2 over quiesced, DMA channel included. + * + * iis2_hw_stop() puts RXCOM back to idle and gates the clocks, which stops + * the port producing data, but it never touches the DMA. The capture + * channel belongs to snd_dmaengine_pcm, which is not in this path at all + * on a shutdown, so a capture that was running at kexec time carries into + * the next firmware as an enabled PL080 channel holding a descriptor that + * points into DRAM the new image is about to be written over. It stalls + * rather than scribbles, because the port has stopped feeding it -- but + * "stalled and still enabled" is exactly the state this hook exists to + * avoid, and it costs one call to avoid it. + * + * peri 13 is IIS2 RX, matching "dmas = <&dmac 13 0>" in the DT. The lookup + * returns NULL when nothing has claimed it, which is the common case: no + * capture was running, and there is nothing to stop. + */ +static void s5l8740_iis2_shutdown(struct platform_device *pdev) +{ + struct dma_chan *rx; + + iis2_hw_stop(platform_get_drvdata(pdev)); + + rx = s5l_pl080_lookup_peri(13); + if (rx) + dmaengine_terminate_async(rx); +} + +static const struct of_device_id s5l8740_iis2_of_match[] = { + { .compatible = "apple,s5l8740-bcm2078-pcm" }, + { .compatible = "apple,s5l8740-iis2" }, + { } +}; +MODULE_DEVICE_TABLE(of, s5l8740_iis2_of_match); + +static struct platform_driver s5l8740_iis2_driver = { + .probe = s5l8740_iis2_probe, + .shutdown = s5l8740_iis2_shutdown, + .remove = s5l8740_iis2_remove, + .driver = { + .name = "s5l8740-iis2", + .of_match_table = s5l8740_iis2_of_match, + }, +}; + +static struct platform_driver * const s5l8740_audio_drivers[] = { + &s5l8740_i2s_driver, + &s5l8740_iis2_driver, +}; + +static int __init s5l8740_audio_init(void) +{ + return platform_register_drivers(s5l8740_audio_drivers, + ARRAY_SIZE(s5l8740_audio_drivers)); +} +module_init(s5l8740_audio_init); + +static void __exit s5l8740_audio_exit(void) +{ + platform_unregister_drivers(s5l8740_audio_drivers, + ARRAY_SIZE(s5l8740_audio_drivers)); +} +module_exit(s5l8740_audio_exit); + +MODULE_DESCRIPTION("S5L8740 I2S DAIs: IIS0 playback + IIS2 capture (N31)"); +MODULE_LICENSE("GPL");