diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea4dc370..558ddfa9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -112,12 +112,16 @@ jobs: runs-on: ubuntu-24.04-arm container: debian:bookworm timeout-minutes: 360 + strategy: + matrix: + device: [j71b, j71t] # Keep in sync with DEVICES in the tools and assets jobs. defaults: run: shell: bash env: ORCHARD_BUILD_NATIVE: '1' ORCHARD_BUILD_ENV_ID: debian-bookworm-arm64-llvm19 + DEVICE: ${{ matrix.device }} steps: - run: apt-get update && apt-get install -y git ca-certificates python3 python3-venv - uses: actions/checkout@v4 @@ -135,18 +139,18 @@ jobs: echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - name: Build kernel, modules and device tree once run: | - bash kernel/build j71b --output "$PWD/out/components/kernel" - dpkg-query -W > out/components/kernel-toolchain-packages.lock + bash kernel/build "$DEVICE" --output "$PWD/out/$DEVICE/components/kernel" + dpkg-query -W > "out/$DEVICE/components/kernel-toolchain-packages.lock" - name: Archive components and corresponding kernel source run: | - tar -C out -czf out/kernel-components.tar.gz components - tar -C kernel/.work/j71b/source --exclude=.git -czf out/kernel-source.tar.gz . + tar -C out -czf "out/kernel-components-$DEVICE.tar.gz" "$DEVICE/components" + tar -C "kernel/.work/$DEVICE/source" --exclude=.git -czf "out/kernel-source-$DEVICE.tar.gz" . - uses: actions/upload-artifact@v4 with: - name: hardware-kernel + name: hardware-kernel-${{ matrix.device }} path: | - out/kernel-components.tar.gz - out/kernel-source.tar.gz + out/kernel-components-${{ matrix.device }}.tar.gz + out/kernel-source-${{ matrix.device }}.tar.gz compression-level: 0 if-no-files-found: error retention-days: 2 @@ -163,6 +167,7 @@ jobs: ORCHARD_BUILD_NATIVE: '1' ORCHARD_BUILD_ENV_ID: debian-bookworm-arm64-llvm19-rust1.90.0 RUSTUP_TOOLCHAIN: '1.90.0' + DEVICES: j71b j71t steps: - name: Reuse unchanged successful outputs if: inputs.reuse_run_id != '' @@ -194,17 +199,22 @@ jobs: - name: Build static recovery tools if: inputs.reuse_run_id == '' run: | + # The static tools are device-independent; every device bundle carries them. OMA_RECOVERY_TOOLS_WORK="$PWD/out/recovery-tools" bash boot/initramfs/j71b/recovery/build-tools - mkdir -p out/components/boot - cp out/recovery-tools/bin/* out/components/boot/ - - name: Build quiet loader + for device in $DEVICES; do + mkdir -p "out/$device/components/boot" + cp out/recovery-tools/bin/* "out/$device/components/boot/" + done + - name: Build quiet loaders if: inputs.reuse_run_id == '' run: | - bash boot/m1n1/build j71b --output "$PWD/out/components/quiet/m1n1.bin" \ - --work-dir "$PWD/out/loader-work" --extra-patch "$PWD/boot/quiet/m1n1.patch" \ - --extra-cflags '-DQUIET_BOOT' - dpkg-query -W > out/components/tools-toolchain-packages.lock - tar -C out -czf out/tool-components.tar.gz components + for device in $DEVICES; do + bash boot/m1n1/build "$device" --output "$PWD/out/$device/components/quiet/m1n1.bin" \ + --work-dir "$PWD/out/loader-work/$device" --extra-patch "$PWD/boot/quiet/m1n1.patch" \ + --extra-cflags '-DQUIET_BOOT' + dpkg-query -W > "out/$device/components/tools-toolchain-packages.lock" + done + tar -C out -czf out/tool-components.tar.gz $(for device in $DEVICES; do echo "$device/components"; done) - uses: actions/upload-artifact@v4 with: name: hardware-tools @@ -289,15 +299,20 @@ jobs: pattern: hardware-* path: out/downloads merge-multiple: true - - name: Assemble public component bundle without rebuilding + - name: Assemble public component bundles without rebuilding + env: + DEVICES: j71b j71t run: | mkdir -p out/publication - tar -xzf out/downloads/kernel-components.tar.gz -C out tar -xzf out/downloads/tool-components.tar.gz -C out - tar -xzf out/downloads/quiet-pongo.tar.gz -C out/components/quiet - python ci/package_components.py --directory out/components --output out/publication/j71b-components.tar.gz + for device in $DEVICES; do + tar -xzf "out/downloads/kernel-components-$device.tar.gz" -C out + tar -xzf out/downloads/quiet-pongo.tar.gz -C "out/$device/components/quiet" + python ci/package_components.py --device "$device" --directory "out/$device/components" \ + --output "out/publication/$device-components.tar.gz" + done cp out/downloads/*.whl out/downloads/*.tar.gz out/publication/ - rm out/publication/kernel-components.tar.gz out/publication/tool-components.tar.gz out/publication/quiet-pongo.tar.gz + rm out/publication/kernel-components-*.tar.gz out/publication/tool-components.tar.gz out/publication/quiet-pongo.tar.gz git archive --format=tar.gz -o out/publication/hardware-source.tar.gz HEAD (cd out/publication && sha256sum ./*.whl ./*.tar.gz > SHA256SUMS) - name: Attest public build diff --git a/BUILD.md b/BUILD.md index e02264f7..82a1a5eb 100644 --- a/BUILD.md +++ b/BUILD.md @@ -26,6 +26,8 @@ HW=$(python -c 'from pathlib import Path; import orchard_hardware; print(Path(or "$HW/boot/m1n1/build" j71b --output "$PWD/out/m1n1.bin" ``` +Replace `j71b` with `j71t` for the iPad 5 Wi-Fi. Image producers take the same device as `--device` (default `j71b`): `orchard_hardware.image.recovery`, `orchard_hardware.image.launcher`, `ci/package_components.py`, `ci/launcher_inputs.py` and `boot/quiet/build.py`; `orchard_hardware.image.runtime.files()` takes it as `profile`. + Choose new output paths. Producers use pinned upstream revisions, hardware patches and public toolchains. Their `.work` cache lives beside the installed producer, so use a writable virtual environment. Kernel/module builds enforce matching kernel releases. See [kernel/README.md](kernel/README.md) and [recovery tools](boot/initramfs/j71b/recovery/README.md) for individual producers. The m1n1 backend is unbranded by default. A distro can pass `--extra-patch FILE`, `--include-header FILE`, and `--extra-cflags FLAGS` for its own presentation. @@ -35,12 +37,12 @@ The m1n1 backend is unbranded by default. A distro can pass `--extra-patch FILE` - `image/runtime.py`: modules, hardware/touch scripts, device API and integrity lists. - `image/prepare_runtime.py`: Python/ELF dependency staging and recovery root-stream receipt. - `image/recovery.py`: recovery assembly, newc packing and m1n1 framing. -- `runtime/j71b`: archive containment, staging cleanup, storage fencing and PID-1 handoff. +- `runtime/j71b`: archive containment, staging cleanup, storage fencing and PID-1 handoff, shared by all devices; `runtime/` adds hardware preparation and platform checks. - `devices/j71b/touch/runtime`: device activation scripts. Image builders pass a `--policy-dir` containing `image_policy.py` and supporting modules. The policy is staged into the image, not imported on the host. It supplies `ready`, `validate_root`, `validate_handoff`, `verify_privileges`, `verify_session_access`, `diagnostics`, and `INIT`; diagnostic retirement also uses `stop_diagnostics` and `DIAGNOSTICS_EXECUTABLE`. Desktop and init policy belong to the image, not the shared hardware runtime. -Recovery and installed roots receive identical public runtime files and integrity lists. Runtime paths are `/run/orchard`, `/etc/orchard`, and `/usr/libexec/orchard-j71b`; the command endpoint is `/usr/bin/orchard-device`. +Recovery and installed roots receive identical public runtime files and integrity lists. Runtime paths are `/run/orchard`, `/etc/orchard`, and `/usr/libexec/orchard-` (for example `orchard-j71b`); the command endpoint is `/usr/bin/orchard-device`. ## Shared launcher and publication diff --git a/NOTICE b/NOTICE index c5592915..55bf6532 100644 --- a/NOTICE +++ b/NOTICE @@ -12,5 +12,13 @@ Reference revision: 944541e67b4c603df328c77d30093551de802c17. Original copyright notices and per-file licenses remain applicable. This is selective reuse and adaptation, not a replacement of the pinned base kernel. +The J71t (iPad 5 Wi-Fi, S8003) kernel patches, configuration and m1n1 patches +come from the omarchy-ipad5 iPad 5 port (https://github.com/brkosebay/omarchy-ipad5; +kernel source https://github.com/brkosebay/linux/tree/a9/orchard-j71t), which builds on the same HoolockLinux base and adapts +Paul Praschl's (Pauli1Go) T8010 PCIe/ANS, SmartIO, Wi-Fi and Bluetooth work to +A9. The video decoder's CAVLC table is generated from GPL-2.0 Imagination +Technologies/Texas Instruments source, whose notices are kept. Original +copyright notices and per-file licenses remain applicable. + m1n1: Asahi Linux Contributors, MIT (LICENSES/m1n1.txt). Wireless regulatory database: LICENSES/wireless-regdb.txt. diff --git a/README.md b/README.md index 06e6ace4..af0c9dd8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ Hardware support and image-building resources for Linux on Apple devices. This r ## Supported hardware -Currently: iPad 6 Wi-Fi (`iPad7,5`, J71b), experimental. Boot is tethered and GPU acceleration is not available. +Currently, all experimental, tethered and without GPU acceleration: + +- iPad 6 Wi-Fi (`iPad7,5`, J71b, A10 T8010). +- iPad 5 Wi-Fi (`iPad6,11`, J71t, A9 S8003), tested on one unit. The Samsung S8000 variant (J71s) is not supported, and there is no shared launcher for J71t yet. ## Use @@ -13,7 +16,7 @@ For installing and starting a published system, use the [Orchard CLI](https://gi For building components or another image, follow [BUILD.md](BUILD.md). All commands run from this repository or installed package resources; no sibling source checkout is needed. - [Kernel producers](kernel/README.md) -- [Recovery tools](boot/initramfs/j71b/recovery/README.md) +- [Recovery tools](boot/initramfs/j71b/recovery/README.md) and [J71t recovery](boot/initramfs/j71t/recovery/README.md) - [GitHub builds and release ordering](docs/ci.md) - [Shared launcher build and publication](boot/quiet/README.md) - [Device protocol](https://github.com/OrchardLinux/cli/blob/main/docs/device-protocol.md) diff --git a/boot/initramfs/j71t/recovery/README.md b/boot/initramfs/j71t/recovery/README.md new file mode 100644 index 00000000..2d7723cb --- /dev/null +++ b/boot/initramfs/j71t/recovery/README.md @@ -0,0 +1,19 @@ +# J71t recovery + +J71t (iPad 5 Wi-Fi, S8003) uses the same RAM recovery as [J71b](../../j71b/recovery/README.md). Only the files here are device-specific: + +- `init`: checks for `apple,j71t` and starts `/usr/libexec/orchard-j71t/handoff.py`. +- `usb-gadget`: presents `J71t Recovery`, which the CLI adapter matches in Linux mode. +- `boot.json`, `boot-verbose.json`: boot arguments, including the NVMe write policy below. + +BusyBox, Dropbear, `authorize-key`, `start-ssh`, `capture-adt` and `udhcpd.conf` come from the J71b recovery directory unchanged. The device API in `runtime/j71b/` is shared; J71t adds `runtime/j71t/phase2-hardware` and `j71t-check-platform`. + +## Storage policy + +The J71t kernel keeps NVMe read-only unless `nvme.write_window=NSID:FIRST-LAST` is set at boot. Only NVMe Write commands inside that LBA range reach the controller; trim, Write Zeroes, Format, Sanitize, firmware and namespace commands stay blocked, and namespaces 2-7 (Apple system namespaces) stay read-only whatever the window says. + +Orchard selects the target partition after boot, so recovery opens the whole of namespace 1 (`1:0-4294967295`) and relies on the same block-layer fence as J71b: `phase2-hardware` sets every `nvme0n1p*` read-only, and the device API makes only the confirmed target writable while it formats or mounts it. The GPT and the iPadOS partition are therefore protected by the block layer, as on J71b, not by the kernel's command filter. + +The runtime accepts any window confined to namespace 1. Someone who knows their Linux partition's LBA range can build recovery with that narrower window in `boot.json` and keep the kernel-level fence; writes to a target outside it then fail with I/O errors and nothing outside the window is written. + +The kernel also holds the PCIe host and the ANS controller (`CONFIG_NVME_APPLE_ANS_EARLY_USB`) until `phase2-hardware` releases them, after the private firmware mixin has been written. diff --git a/boot/initramfs/j71t/recovery/boot-verbose.json b/boot/initramfs/j71t/recovery/boot-verbose.json new file mode 100644 index 00000000..87b1b42b --- /dev/null +++ b/boot/initramfs/j71t/recovery/boot-verbose.json @@ -0,0 +1,3 @@ +{ + "bootargs": "chosen.bootargs=j71t.recovery=1 oma.verbose=1 console=tty0 loglevel=7 ignore_loglevel fbcon=map:0 nvme.write_window=1:0-4294967295 pcie_apple_h9p.ans_boot_mode=handoff nvme.apple_ans_rdy_timeout=5 nvme_core.io_timeout=5 nvme_core.admin_timeout=10 nvme_core.max_retries=1\n" +} diff --git a/boot/initramfs/j71t/recovery/boot.json b/boot/initramfs/j71t/recovery/boot.json new file mode 100644 index 00000000..32314b41 --- /dev/null +++ b/boot/initramfs/j71t/recovery/boot.json @@ -0,0 +1,3 @@ +{ + "bootargs": "chosen.bootargs=j71t.recovery=1 loglevel=7 fbcon=map:9 vt.global_cursor_default=0 nvme.write_window=1:0-4294967295 pcie_apple_h9p.ans_boot_mode=handoff nvme.apple_ans_rdy_timeout=5 nvme_core.io_timeout=5 nvme_core.admin_timeout=10 nvme_core.max_retries=1\n" +} diff --git a/boot/initramfs/j71t/recovery/init b/boot/initramfs/j71t/recovery/init new file mode 100755 index 00000000..6b0e4354 --- /dev/null +++ b/boot/initramfs/j71t/recovery/init @@ -0,0 +1,30 @@ +#!/bin/busybox sh +# RAM-only J71t recovery. No internal storage is mounted or written. +set -eu +export PATH=/bin:/sbin:/usr/bin:/usr/sbin +cd / +/bin/busybox --install -s +mount -t proc -o nodev,noexec,nosuid proc /proc +mount -t sysfs -o nodev,noexec,nosuid sysfs /sys +mount -t devtmpfs -o mode=0755,nosuid dev /dev +mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs /run +mkdir -p /dev/pts /run/recovery /run/orchard +mount -t devpts -o mode=0620,ptmxmode=0666 devpts /dev/pts +exec /dev/console 2>&1 + +[ "$(tr '\000' '\n' < /proc/device-tree/compatible | head -1)" = apple,j71t ] || { + echo 'Wrong device for J71t recovery image' + exec sh +} +/sbin/recovery-usb +ip address add 172.16.42.1/30 dev usb0 +ip link set usb0 up +udhcpd -f /etc/udhcpd.conf & +echo 'J71t recovery: USB serial ready; provision an SSH key before SSH starts.' +( + while getty -n -l /bin/sh ttyGS0 115200 vt100; do sleep 1; done +) & +# Only PID 1 may retire the RAM userspace. A separate writer must first install +# and verify a new sibling root, then arm this one-shot request in /run. +while [ ! -e /run/orchard/handoff.json ]; do sleep 1; done +exec /usr/bin/python3 /usr/libexec/orchard-j71t/handoff.py diff --git a/boot/initramfs/j71t/recovery/usb-gadget b/boot/initramfs/j71t/recovery/usb-gadget new file mode 100755 index 00000000..a39dd8a6 --- /dev/null +++ b/boot/initramfs/j71t/recovery/usb-gadget @@ -0,0 +1,23 @@ +#!/bin/sh +# USB NCM networking and ACM serial, restricted to the attached host. +set -eu +mkdir -p /sys/kernel/config +mount -t configfs -o nodev,noexec,nosuid configfs /sys/kernel/config +g=/sys/kernel/config/usb_gadget/linux-recovery +[ ! -e "$g" ] +identity=$(/usr/bin/python3 /usr/libexec/orchard-j71t/identity.py) +mkdir "$g" +echo 0x05ac > "$g/idVendor" +echo 0x4142 > "$g/idProduct" +mkdir -p "$g/strings/0x409" "$g/configs/c.1/strings/0x409" +echo 'J71t Linux' > "$g/strings/0x409/manufacturer" +echo 'J71t Recovery' > "$g/strings/0x409/product" +printf '%s\n' "$identity" > "$g/strings/0x409/serialnumber" +echo 'USB network and serial' > "$g/configs/c.1/strings/0x409/configuration" +echo 500 > "$g/configs/c.1/MaxPower" +mkdir "$g/functions/ncm.usb0" "$g/functions/acm.usb0" +ln -s "$g/functions/ncm.usb0" "$g/configs/c.1/ncm.usb0" +ln -s "$g/functions/acm.usb0" "$g/configs/c.1/acm.usb0" +set -- /sys/class/udc/* +[ "$#" -eq 1 ] && [ -e "$1" ] +printf '%s\n' "${1##*/}" > "$g/UDC" diff --git a/boot/m1n1/patches/j71t/0001-a9-a10-nvme-hmb.patch b/boot/m1n1/patches/j71t/0001-a9-a10-nvme-hmb.patch new file mode 100644 index 00000000..6f793fc0 --- /dev/null +++ b/boot/m1n1/patches/j71t/0001-a9-a10-nvme-hmb.patch @@ -0,0 +1,74 @@ +kboot: Populate the A9/A10 NVMe HMB region + +Copy the runtime /arm-io/nvme-mmu0 "sart-region" (base, size) that +iBoot sets up for the NVMe host memory buffer into the reserved-memory +node the DT aliases as "nvme-hmb". Linux's Apple ANS NVMe glue and the +H8P/H9P PCIe host bridge use it for the NVMMU SART window. + +Based on Paul Praschl's T8010 version (Pauli1Go/m1n1 ca0ce2ff43), but +a missing or zero sart-region only leaves the node empty instead of +refusing the kernel handoff, and Linux then keeps NVMe off. iBoot fills +it on J71t (S8003). + +diff --git a/src/kboot.c b/src/kboot.c +index 2840651..9383c1a 100644 +--- a/src/kboot.c ++++ b/src/kboot.c +@@ -456,6 +456,48 @@ static int dt_set_memory(void) + return 0; + } + ++static int dt_set_nvme_hmb(void) ++{ ++ const void *prop; ++ u64 region[2]; ++ u32 len = 0; ++ int adt_node; ++ int fdt_node; ++ fdt64_t reg[2]; ++ ++ /* Only DTs that ask for it (A9/A10 PCIe NVMe) have the nvme-hmb alias */ ++ fdt_node = fdt_path_offset(dt, "nvme-hmb"); ++ if (fdt_node < 0) ++ return 0; ++ ++ /* ++ * iBoot puts the NVMe host memory buffer it set up in the runtime ADT. ++ * If it is missing, leave the node empty: Linux then keeps the NVMe ++ * controller untouched instead of failing the boot. ++ */ ++ adt_node = adt_path_offset(adt, "/arm-io/nvme-mmu0"); ++ if (adt_node < 0) { ++ printf("FDT: no /arm-io/nvme-mmu0, NVMe HMB left empty\n"); ++ return 0; ++ } ++ ++ prop = adt_getprop(adt, adt_node, "sart-region", &len); ++ if (prop && len == sizeof(region)) ++ memcpy(region, prop, sizeof(region)); ++ if (!prop || len != sizeof(region) || !region[0] || !region[1]) { ++ printf("FDT: unusable nvme-mmu0.sart-region (len %u), NVMe HMB left empty\n", len); ++ return 0; ++ } ++ ++ fdt64_st(®[0], region[0]); ++ fdt64_st(®[1], region[1]); ++ if (fdt_setprop(dt, fdt_node, "reg", reg, sizeof(reg))) ++ bail("FDT: failed to set NVMe HMB region\n"); ++ ++ printf("FDT: NVMe HMB at 0x%lx size 0x%lx\n", region[0], region[1]); ++ return 0; ++} ++ + static int dt_set_serial_number(void) + { + +@@ -2976,6 +3018,8 @@ int kboot_prepare_dt(void *fdt) + return -1; + if (dt_set_pcie_tunables()) + return -1; ++ if (dt_set_nvme_hmb()) ++ return -1; + if (dt_set_display()) + return -1; + if (dt_set_gpu(dt)) diff --git a/boot/m1n1/patches/j71t/0002-a9-a10-wifi-calibration.patch b/boot/m1n1/patches/j71t/0002-a9-a10-wifi-calibration.patch new file mode 100644 index 00000000..96fb38da --- /dev/null +++ b/boot/m1n1/patches/j71t/0002-a9-a10-wifi-calibration.patch @@ -0,0 +1,68 @@ +kboot: Wi-Fi calibration for A9/A10, whose wlan node sits under a UART + +On A9/A10 (PCIe BCM4355) the ADT has no /arm-io/wlan. The WLAN +properties, including "wifi-calibration-msf", are on the "wlan" +(wlan-pcie-uart,bcm4355) node under /arm-io/uart2, and there is no +"wifi-antenna-sku-info". Fall back to that node, copy the calibration +into the DT's wifi0 "brcm,cal-blob" as on Apple silicon, and keep the +DT's own apple,antenna-sku when the ADT has none instead of failing. + +The MAC address already comes through dt_set_mac_addresses() +(/chosen mac-address-wifi0 -> wifi0 local-mac-address). + +diff --git a/src/kboot.c b/src/kboot.c +index 9383c1a..9240ce2 100644 +--- a/src/kboot.c ++++ b/src/kboot.c +@@ -1041,6 +1041,17 @@ static int dt_set_nvram(void) + static int dt_set_wifi(void) + { + int anode = adt_path_offset(adt, "/arm-io/wlan"); ++ bool on_uart = false; ++ ++ if (anode < 0) { ++ /* ++ * A9/A10 (PCIe BCM4355): the WLAN properties (calibration, ++ * module-instance) sit on the "wlan" node under the UART it uses, ++ * and there is no wifi-antenna-sku-info. ++ */ ++ anode = adt_path_offset(adt, "/arm-io/uart2/wlan"); ++ on_uart = anode >= 0; ++ } + + if (anode < 0) { + /* +@@ -1052,7 +1063,8 @@ static int dt_set_wifi(void) + } + + uint8_t info[16]; +- if (ADT_GETPROP_ARRAY(adt, anode, "wifi-antenna-sku-info", info) < 0) ++ bool have_sku = ADT_GETPROP_ARRAY(adt, anode, "wifi-antenna-sku-info", info) >= 0; ++ if (!have_sku && !on_uart) + bail("ADT: Failed to get wifi-antenna-sku-info\n"); + + const char *path = fdt_get_alias(dt, "wifi0"); +@@ -1063,9 +1075,11 @@ static int dt_set_wifi(void) + if (node < 0) + return 0; + +- char antenna[8]; +- memcpy(antenna, &info[8], sizeof(antenna)); +- fdt_setprop_string(dt, node, "apple,antenna-sku", antenna); ++ if (have_sku) { ++ char antenna[8]; ++ memcpy(antenna, &info[8], sizeof(antenna)); ++ fdt_setprop_string(dt, node, "apple,antenna-sku", antenna); ++ } + + u32 len; + const u8 *cal_blob = adt_getprop(adt, anode, "wifi-calibration-msf", &len); +@@ -1076,6 +1090,8 @@ static int dt_set_wifi(void) + } + + fdt_setprop(dt, node, "brcm,cal-blob", cal_blob, len); ++ if (on_uart) ++ printf("FDT: Wi-Fi calibration (%u bytes) from the uart2 wlan node\n", len); + + return 0; + } diff --git a/boot/quiet/build.py b/boot/quiet/build.py index 8f9960be..e42aad1e 100644 --- a/boot/quiet/build.py +++ b/boot/quiet/build.py @@ -13,6 +13,7 @@ def main(): parser.add_argument('--output', type=Path, required=True) parser.add_argument('--work-dir', type=Path, required=True) parser.add_argument('--resume', action='store_true') + parser.add_argument('--device', default='j71b') args = parser.parse_args() if args.output.exists() or args.output.is_symlink(): parser.error('Choose a new output directory') @@ -22,7 +23,7 @@ def main(): subprocess.run([sys.executable, str(here / 'build-pongo.py'), '--output', str(args.output / 'pongo.bin'), '--work-dir', str(args.work_dir / 'pongo'), *resume], check=True) - subprocess.run(['bash', str(here.parent / 'm1n1/build'), 'j71b', + subprocess.run(['bash', str(here.parent / 'm1n1/build'), args.device, '--output', str(args.output / 'm1n1.bin'), '--work-dir', str(args.work_dir / 'm1n1'), '--extra-patch', str(here / 'm1n1.patch'), @@ -33,7 +34,7 @@ def main(): files[role] = {'sha256': hashlib.sha256(path.read_bytes()).hexdigest(), 'bytes': path.stat().st_size} (args.output / 'quiet-boot.json').write_text(json.dumps({ - 'schema': 1, 'profile': 'j71b', 'presentation': 'black', 'files': files, + 'schema': 1, 'profile': args.device, 'presentation': 'black', 'files': files, 'patches': {name: hashlib.sha256((here / name).read_bytes()).hexdigest() for name in ('pongo.patch', 'm1n1.patch')}, }, indent=2) + '\n') diff --git a/ci/launcher_inputs.py b/ci/launcher_inputs.py index a7fff91b..63444083 100644 --- a/ci/launcher_inputs.py +++ b/ci/launcher_inputs.py @@ -6,7 +6,7 @@ from orchard_hardware.image.integrity import sha -def prepare(root, output): +def prepare(root, output, device='j71b'): inventory = json.loads((root / 'components.json').read_text()) if inventory.get('schema') != 1: raise ValueError('Unsupported component inventory') @@ -23,7 +23,7 @@ def prepare(root, output): if source.stat().st_size != expected['bytes'] or sha(source) != expected['sha256']: raise ValueError('Component differs: ' + name) roles = {'loader': 'quiet/m1n1.bin', 'pongo': 'quiet/pongo.bin', - 'kernel': 'kernel/Image.gz', 'dtb': 'kernel/j71b.dtb', + 'kernel': 'kernel/Image.gz', 'dtb': 'kernel/' + device + '.dtb', 'kernel_release': 'kernel/kernel.release', 'busybox': 'boot/busybox', 'dropbear': 'boot/dropbear', 'dropbearkey': 'boot/dropbearkey'} files = {role: {'path': str((root / name).resolve()), **inventory['files'][name]} @@ -40,5 +40,6 @@ def prepare(root, output): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--components', type=Path, required=True) parser.add_argument('--output', type=Path, required=True) + parser.add_argument('--device', default='j71b') args = parser.parse_args() - prepare(args.components, args.output) + prepare(args.components, args.output, args.device) diff --git a/ci/package_components.py b/ci/package_components.py index d867c817..e11dce30 100644 --- a/ci/package_components.py +++ b/ci/package_components.py @@ -17,17 +17,17 @@ def receipt(path): return {'bytes': path.stat().st_size, 'sha256': digest.hexdigest()} -def package(directory, output): +def package(directory, output, device='j71b'): if output.exists(): raise FileExistsError(output) here = Path(__file__).resolve().parents[1] - for name in ('kernel/Image.gz', 'kernel/j71b.dtb', 'kernel/kernel.release', + for name in ('kernel/Image.gz', 'kernel/' + device + '.dtb', 'kernel/kernel.release', 'boot/busybox', 'boot/dropbear', 'boot/dropbearkey', 'quiet/m1n1.bin', 'quiet/pongo.bin'): receipt(directory / name) if not any((directory / 'kernel/lib/modules').rglob('*.ko')): raise ValueError('Kernel modules are missing') - quiet = {'schema': 1, 'profile': 'j71b', 'presentation': 'black', + quiet = {'schema': 1, 'profile': device, 'presentation': 'black', 'files': {role: receipt(directory / 'quiet' / name) for role, name in (('loader', 'm1n1.bin'), ('pongo', 'pongo.bin'))}, 'patches': {name: receipt(here / 'boot/quiet' / name)['sha256'] @@ -49,5 +49,6 @@ def package(directory, output): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--directory', type=Path, required=True) parser.add_argument('--output', type=Path, required=True) + parser.add_argument('--device', default='j71b') args = parser.parse_args() - package(args.directory, args.output) + package(args.directory, args.output, args.device) diff --git a/devices/j71t/device.toml b/devices/j71t/device.toml new file mode 100644 index 00000000..3f42b1ee --- /dev/null +++ b/devices/j71t/device.toml @@ -0,0 +1,29 @@ +# iPad 5 Wi-Fi (J71t, S8003): hardware identity, boot assets and firmware recipe. +schema = 1 +kind = "hardware.device" + +[device] +profile = "j71t" +name = "iPad 5 (Wi-Fi)" +apple_products = ["iPad6,11"] +dt_compatible = ["apple,j71t"] + +[match.dfu] +chip = 0x8003 # iPad6,11 also ships as J71s (S8000, chip 0x8000); only J71t is supported. +board = 0x10 + +[match.linux] +product = "J71t Recovery" + +[boot] +methods = ["checkm8-pongo"] +# No shared launcher yet: it needs per-device launcher asset names and a J71t +# Minimal Linux root. Add launch_manifest when j71t-launcher.boot.toml exists. + +[firmware] +recipe = "firmware.json" # Relative to this device.toml; public recipe, not firmware bytes. + +[support] +wifi = true # Static support; provisioning requires runtime-confirmed availability. +usb_serial = true +usb_network = true diff --git a/devices/j71t/firmware.json b/devices/j71t/firmware.json new file mode 100644 index 00000000..20239e00 --- /dev/null +++ b/devices/j71t/firmware.json @@ -0,0 +1,36 @@ +{ + "device": "iPad6,11", + "version": "16.7.10", + "build": "20H350", + "ipsw_url": "https://updates.cdn-apple.com/2024SpringFCS/fullrestores/062-47889/FBBD9227-79D6-4FAA-91B9-D20E087BE0E7/iPad_64bit_TouchID_ASTC_16.7.10_20H350_Restore.ipsw", + "ipsw_bytes": 5586431671, + "ipsw_sha256": "1709e8e27b1c0c98dc7c64ae27b4e208b49835c24a95074d1aebe135342b3112", + "extract_patterns": ["C-4355__s-C0/", "J71t[.]mtprops$"], + "extracted_inputs": [ + {"name": "J71t.mtprops", "sha256": "69f27fe207cc5810a748dac3dfe640b1c5a755709f49513b4b5125660784f0fc"} + ], + "files": [ + {"name": "feara.trx", "sha256": "4729b9565834e2c12070330faaf1bbd0b5d49cad7e565692c9d1c7080c413249", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara.bin"}, + {"name": "feara.clmb", "sha256": "1e5add9803fb326a50d6132e791062894c1dbe4942a304568f5a3477c2fb39d4", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara.clm_blob"}, + {"name": "feara.txcb", "sha256": "f67d8e62cbc876904c87ae0cd890b15f056bf6a4856e3f4ca4bdcb5689c3543b", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara.txcap_blob"}, + {"name": "P-feara_M-YSBC_V-m__m-2.1.txt", "sha256": "73333bb7f5fa5ea9d2f6eb644fbb018c34c9c2e33f9b87c54e36f2059a7c0da7", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-m-2.1.txt"}, + {"name": "P-feara_M-YSBC_V-m__m-2.3.txt", "sha256": "73333bb7f5fa5ea9d2f6eb644fbb018c34c9c2e33f9b87c54e36f2059a7c0da7", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-m-2.3.txt"}, + {"name": "P-feara_M-YSBC_V-m__m-3.1.txt", "sha256": "73333bb7f5fa5ea9d2f6eb644fbb018c34c9c2e33f9b87c54e36f2059a7c0da7", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-m-3.1.txt"}, + {"name": "P-feara_M-YSBC_V-u__m-2.1.txt", "sha256": "05eb7f987f4be2c23cbd5885b539470db4633cbe87ab2f1c3449473a178f2dcf", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-u-2.1.txt"}, + {"name": "P-feara_M-YSBC_V-u__m-3.1.txt", "sha256": "05eb7f987f4be2c23cbd5885b539470db4633cbe87ab2f1c3449473a178f2dcf", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-u-3.1.txt"}, + {"name": "P-feara_M-YSBC_V-u__m-3.3.txt", "sha256": "05eb7f987f4be2c23cbd5885b539470db4633cbe87ab2f1c3449473a178f2dcf", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-u-3.3.txt"}, + {"name": "P-feara_M-YSBC_V-u__m-4.1.txt", "sha256": "05eb7f987f4be2c23cbd5885b539470db4633cbe87ab2f1c3449473a178f2dcf", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBC-u-4.1.txt"}, + {"name": "P-feara_M-YSBU_V-m__m-1.1.txt", "sha256": "73333bb7f5fa5ea9d2f6eb644fbb018c34c9c2e33f9b87c54e36f2059a7c0da7", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBU-m-1.1.txt"}, + {"name": "P-feara_M-YSBU_V-u__m-1.1.txt", "sha256": "05eb7f987f4be2c23cbd5885b539470db4633cbe87ab2f1c3449473a178f2dcf", "destination": "lib/firmware/brcm/brcmfmac4355-pcie.apple,feara-YSBU-u-1.1.txt"}, + {"name": "BCM4355C0-FearA-USI.hcd", "sha256": "5513d25b9ebd3103263d27a14e709a884508feab5723521d18780cf26f0c24b2", "destination": "lib/firmware/brcm/BCM.apple,j71t.hcd"}, + {"name": "s8000-smartio.bin", "sha256": "da53095ed3364689ffd56f6576f036e4741a98836147a181c335626389ef5a36", "destination": "lib/firmware/apple/s8000-smartio.bin"}, + {"name": "j71t-n1.bin", "sha256": "98f73566489fed958a04bb73b87f63d8abd6948b7ceacd89d1bc638e64b8998d", "destination": "lib/firmware/apple/j71t-n1.bin"}, + {"name": "vxd-deva.bin", "sha256": "bf5167652e81540471aaca141943fd33d71c3990c137f145471305f5f5ade83b", "destination": "lib/firmware/apple/vxd-deva.bin"} + ], + "generated": [ + {"name": "j71t-n1-calibration.bin", "destination": "lib/firmware/apple/j71t-n1-calibration.bin", "generator": "j71t-adt"}, + {"name": "j71t-LSCI.bin", "destination": "lib/firmware/apple/j71t-LSCI.bin", "generator": "j71t-adt"}, + {"name": "j71t-CBAT.bin", "destination": "lib/firmware/apple/j71t-CBAT.bin", "generator": "j71t-adt"}, + {"name": "j71t-BMac.bin", "destination": "lib/firmware/apple/j71t-BMac.bin", "generator": "j71t-adt"} + ] +} diff --git a/docs/ci.md b/docs/ci.md index 73e257f1..f8bc88b4 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -4,7 +4,9 @@ - Linux ARM64: kernel, modules, DTB, static recovery tools and quiet m1n1. - macOS: quiet Pongo with the pinned newlib revision. -- Assembly: wheel/sdist, `j71b-components.tar.gz`, source archives and `SHA256SUMS`. +- Assembly: wheel/sdist, one `-components.tar.gz` per device (`j71b`, `j71t`), source archives and `SHA256SUMS`. + +The kernel job runs once per device (matrix); the tools job builds the device-independent recovery tools once and one quiet loader per device. The standard GitHub job image is Debian Bookworm, matching the local toolchain base. There is no nested Podman invocation. Native jobs set `ORCHARD_BUILD_NATIVE=1` and `ORCHARD_BUILD_ENV_ID`, run the same `install-toolchain` files used by the Containerfiles, and execute the same compiler recipes. Shared `ORCHARD_*` recipe variables select native filesystem paths in CI and mounted paths locally. Native execution is not a security sandbox: it does not enforce read-only inputs or isolate networking. Native mode is only for trusted, disposable Linux ARM64 workers. Local builds continue to use Podman by default. diff --git a/image/launcher.py b/image/launcher.py index 79bc1dd7..0e46287a 100644 --- a/image/launcher.py +++ b/image/launcher.py @@ -1,4 +1,4 @@ -"""Build shared J71b launch recovery from verified components and root artifacts. +"""Build shared device launch recovery from verified components and root artifacts. Root archives are producer inputs ONLY. Launch clients consume the small boot manifest; no root archive or host install history is needed to select a system. @@ -18,11 +18,11 @@ from .runtime import files as runtime_files -def compatibility(archive, receipt_path, build_path, components): +def compatibility(archive, receipt_path, build_path, components, profile='j71b'): receipt = json.loads(receipt_path.read_text()) build = json.loads(build_path.read_text()) if (receipt.get('schema') != 1 or receipt.get('kind') != 'rootfs' or - receipt.get('profile') != 'j71b' or archive.is_symlink() or + receipt.get('profile') != profile or archive.is_symlink() or archive.stat().st_size != receipt['bytes'] or sha(archive) != receipt['sha256']): raise ValueError('Installed-root producer receipt differs') if build.get('release', build.get('version')) != receipt['release']: @@ -40,8 +40,8 @@ def compatibility(archive, receipt_path, build_path, components): else: raise ValueError('Unsupported root compression') hashes, links, text = {}, {}, {} - wanted = {'etc/orchard/public.SHA256SUMS','etc/orchard/release.json', - 'usr/libexec/orchard-j71b/image_policy.py'} + policy_name = 'usr/libexec/orchard-' + profile + '/image_policy.py' + wanted = {'etc/orchard/public.SHA256SUMS','etc/orchard/release.json', policy_name} seen = set() try: with bundle: @@ -75,7 +75,7 @@ def compatibility(archive, receipt_path, build_path, components): if process.poll() is None: process.kill() process.wait() - policy = ast.parse(text['usr/libexec/orchard-j71b/image_policy.py']) + policy = ast.parse(text[policy_name]) init = [ast.literal_eval(n.value) for n in policy.body if isinstance(n,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='INIT' for t in n.targets)] if len(init)!=1 or init[0] not in ('/sbin/init','/usr/lib/systemd/systemd'): @@ -114,10 +114,10 @@ def compatibility(archive, receipt_path, build_path, components): 'init_target':init_target,'files':pinned} -def verify_quiet_boot(receipt_path, inputs): +def verify_quiet_boot(receipt_path, inputs, profile='j71b'): """Never silently reuse a distro-branded loader in shared discovery.""" receipt = json.loads(receipt_path.read_text()) - if (receipt.get('schema') != 1 or receipt.get('profile') != 'j71b' or + if (receipt.get('schema') != 1 or receipt.get('profile') != profile or receipt.get('presentation') != 'black'): raise ValueError('Shared launcher requires black-screen boot components') here = Path(__file__).resolve().parents[1] / 'boot/quiet' @@ -140,7 +140,9 @@ def main(): parser.add_argument('--release',required=True) parser.add_argument('--public-base-url', help='immutable HTTPS release asset directory') parser.add_argument('--output',type=Path,required=True) + parser.add_argument('--device',default='j71b',help='hardware profile (default: j71b)') args=parser.parse_args() + profile=args.device if args.output.exists(): parser.error('Choose a new output directory') if args.public_base_url: from urllib.parse import urlsplit @@ -149,7 +151,7 @@ def main(): url.query or url.fragment or '/latest/' in url.path): parser.error('--public-base-url must be an immutable HTTPS asset directory') inputs=json.loads(args.inputs.read_text()) - verify_quiet_boot(args.quiet_boot_receipt, inputs) + verify_quiet_boot(args.quiet_boot_receipt, inputs, profile) for row in inputs['files'].values(): path=Path(row['path']) if path.is_symlink() or path.stat().st_size!=row['bytes'] or sha(path)!=row['sha256']: @@ -159,8 +161,8 @@ def main(): if actual!=inputs['modules']['files'] or any(p.is_symlink() for p in modules.rglob('*')): raise ValueError('Module tree differs') policies=Path(__file__).resolve().parents[1]/'launcher' - kernel_release, public=runtime_files(modules,policies) - records=[compatibility(*(Path(p) for p in row),inputs) for row in args.installation] + kernel_release, public=runtime_files(modules,policies,profile) + records=[compatibility(*(Path(p) for p in row),inputs,profile) for row in args.installation] identities={(r['release'],r['root_sha256']) for r in records} if len(identities)!=len(records): raise ValueError('Duplicate launch compatibility records') # Same release string alone is not a kernel/module compatibility guarantee. @@ -171,7 +173,7 @@ def main(): raise ValueError('Installed module compatibility differs: '+name) args.output.mkdir(parents=True) catalogue=args.output/'launch.json' - catalogue.write_text(json.dumps({'schema':1,'profile':'j71b','installations':records},indent=2)+'\n') + catalogue.write_text(json.dumps({'schema':1,'profile':profile,'installations':records},indent=2)+'\n') files=inputs['files'] command=[sys.executable,'-m','orchard_hardware.image.recovery'] for key in ('loader','kernel','dtb','busybox','dropbear','dropbearkey'): @@ -179,16 +181,17 @@ def main(): command+=['--base',str(args.base),'--base-receipt',str(args.base_receipt), '--modules-dir',str(modules),'--policy-dir',str(policies), '--launcher-catalogue',str(catalogue),'--launcher-release',args.release, - '--output',str(args.output/'recovery.bin')] + '--output',str(args.output/'recovery.bin'),'--device',profile] subprocess.run(command,check=True) shutil.copyfile(files['pongo']['path'],args.output/'pongo.bin') def artifact(name): path=args.output/name return {'file':name,'bytes':path.stat().st_size,'sha256':sha(path)} boot,pongo=artifact('recovery.bin'),artifact('pongo.bin') - lines=['schema = 1','kind = "orchard.boot"','device = "j71b"', + lines=['schema = 1','kind = "orchard.boot"','device = '+json.dumps(profile), 'boot_method = "checkm8-pongo"','min_orchard = "0.1.8"','protocol = 1', - 'name = "J71b installed-system launcher"','version = '+json.dumps(args.release)] + 'name = '+json.dumps(profile[0].upper()+profile[1:]+' installed-system launcher'), + 'version = '+json.dumps(args.release)] for name,row in (('boot',boot),('pongo',pongo)): lines+=['','['+name+']',*[key+' = '+json.dumps(value) for key,value in row.items()]] (args.output/'boot.toml').write_text('\n'.join(lines)+'\n') @@ -196,9 +199,9 @@ def artifact(name): public_lines = [ 'url = ' + json.dumps(args.public_base_url.rstrip('/') + '/' + json.loads(line[7:])) if line.startswith('file = ') else line for line in lines] - (args.output/'j71b-launcher.boot.toml').write_text('\n'.join(public_lines)+'\n') + (args.output/(profile+'-launcher.boot.toml')).write_text('\n'.join(public_lines)+'\n') (args.output/'build-receipt.json').write_text(json.dumps({'schema':1,'release':args.release, - 'profile':'j71b','mode':'launcher','presentation':'black','inputs':inputs,'boot':boot,'pongo':pongo, + 'profile':profile,'mode':'launcher','presentation':'black','inputs':inputs,'boot':boot,'pongo':pongo, 'catalogue_sha256':sha(catalogue),'physical_qualification':'not-performed'},indent=2)+'\n') print('Shared launcher:',args.output/'boot.toml') diff --git a/image/prepare_runtime.py b/image/prepare_runtime.py index 4f1a588b..a843f031 100644 --- a/image/prepare_runtime.py +++ b/image/prepare_runtime.py @@ -16,7 +16,8 @@ from .elf import needed PROJECT = Path(__file__).resolve().parents[1] -HERE = PROJECT / 'boot/initramfs/j71b/recovery' +# Device-independent recovery helpers; init and the USB gadget are per device. +SHARED = PROJECT / 'boot/initramfs/j71b/recovery' def copy(source, target, mode): @@ -124,11 +125,16 @@ def main(): purpose.add_argument('--launcher-catalogue', type=Path) parser.add_argument('--launcher-release') parser.add_argument('--cleanup-receipt', type=Path, action='append', default=[]) + parser.add_argument('--device', default='j71b', help='hardware profile (default: j71b)') args = parser.parse_args() + profile = args.device + here = PROJECT / 'boot/initramfs' / profile / 'recovery' + if not re.fullmatch(r'[a-z0-9][a-z0-9_-]*', profile) or not (here / 'init').is_file(): + parser.error('No recovery support for device: ' + profile) cleanup_receipts = [] for receipt in args.cleanup_receipt: previous = json.loads(receipt.read_text()) - if (previous.get('kind') != 'rootfs' or previous.get('profile') != 'j71b' or + if (previous.get('kind') != 'rootfs' or previous.get('profile') != profile or previous.get('schema') != 1 or not previous.get('release') or not re.fullmatch('[0-9a-f]{64}', previous.get('sha256', ''))): raise ValueError('Invalid compatible cleanup receipt') @@ -143,7 +149,7 @@ def main(): (args.dropbearkey, 'usr/bin/dropbearkey')): copy(source, root / name, 0o755) stage_runtime(args.base, args.base_receipt, root) - kernel_release, public = runtime_files(args.modules_dir, args.policy_dir) + kernel_release, public = runtime_files(args.modules_dir, args.policy_dir, profile) for name, (data, mode) in public.items(): path = root / name path.parent.mkdir(parents=True, exist_ok=True) @@ -152,26 +158,27 @@ def main(): if args.launcher_catalogue: catalogue = json.loads(args.launcher_catalogue.read_text()) if (not args.launcher_release or catalogue.get('schema') != 1 or - catalogue.get('profile') != 'j71b' or not catalogue.get('installations') or + catalogue.get('profile') != profile or not catalogue.get('installations') or any(r['kernel_release'] != kernel_release for r in catalogue['installations'])): raise ValueError('Invalid launcher catalogue/kernel identity') copy(args.launcher_catalogue, root / 'etc/orchard/launch.json', 0o644) - configuration = {'protocol':1, 'release':args.launcher_release, 'profile':'j71b', + configuration = {'protocol':1, 'release':args.launcher_release, 'profile':profile, 'kernel_release':kernel_release, 'mode':'launcher'} else: spec = json.loads(args.root_receipt.read_text()) if spec['kernel_release'] != kernel_release: raise ValueError('Root/recovery kernel releases differ') - configuration = {'protocol': 1, 'release': spec['release'], 'profile': 'j71b', + configuration = {'protocol': 1, 'release': spec['release'], 'profile': profile, 'kernel_release': kernel_release, 'cleanup_receipts': cleanup_receipts, 'root': {k: spec[k] for k in ('sha256', 'bytes', 'expanded_bytes', 'members', 'compression')}} (root / 'etc/orchard/release.json').write_text(json.dumps(configuration)) - copy(firmware_path('j71b'), root / 'etc/orchard/firmware.json', 0o644) - for source, name in (('init', 'init'), ('usb-gadget', 'sbin/recovery-usb'), - ('authorize-key', 'sbin/authorize-key'), ('start-ssh', 'sbin/recovery-start-ssh'), - ('capture-adt', 'sbin/capture-adt')): - copy(HERE / source, root / name, 0o755) - copy(HERE / 'udhcpd.conf', root / 'etc/udhcpd.conf', 0o644) + copy(firmware_path(profile), root / 'etc/orchard/firmware.json', 0o644) + for directory, source, name in ((here, 'init', 'init'), (here, 'usb-gadget', 'sbin/recovery-usb'), + (SHARED, 'authorize-key', 'sbin/authorize-key'), + (SHARED, 'start-ssh', 'sbin/recovery-start-ssh'), + (SHARED, 'capture-adt', 'sbin/capture-adt')): + copy(directory / source, root / name, 0o755) + copy(SHARED / 'udhcpd.conf', root / 'etc/udhcpd.conf', 0o644) (root / 'etc/passwd').write_text('root:x:0:0:root:/root:/bin/sh\n') (root / 'etc/group').write_text('root:x:0:\n') (root / 'etc/shadow').write_text('root:!:0:0:99999:7:::\n') diff --git a/image/recovery.py b/image/recovery.py index b192fbf8..062529df 100644 --- a/image/recovery.py +++ b/image/recovery.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Assemble a J71b RAM recovery image from explicit kernel, loader and tool inputs.""" +"""Assemble a device RAM recovery image from explicit kernel, loader and tool inputs.""" import argparse from pathlib import Path import subprocess @@ -7,7 +7,6 @@ import tempfile ROOT = Path(__file__).resolve().parents[1] -HERE = ROOT / 'boot/initramfs/j71b/recovery' def main(): @@ -20,11 +19,15 @@ def main(): purpose.add_argument('--launcher-catalogue', type=Path) parser.add_argument('--launcher-release') parser.add_argument('--cleanup-receipt', type=Path, action='append', default=[]) + parser.add_argument('--device', default='j71b', help='hardware profile (default: j71b)') diagnostics = parser.add_mutually_exclusive_group() diagnostics.add_argument('--verbose', action='store_true', help='enable visible Linux framebuffer console and kernel diagnostics') args = parser.parse_args() manifest = 'boot-verbose.json' if args.verbose else 'boot.json' + here = ROOT / 'boot/initramfs' / args.device / 'recovery' + if '/' in args.device or not (here / manifest).is_file(): + parser.error('No recovery boot manifest for device: ' + args.device) if args.output.exists(): parser.error('choose a new output path') args.output.parent.mkdir(parents=True, exist_ok=True) @@ -44,12 +47,13 @@ def main(): '--dropbearkey', str(args.dropbearkey), '--base', str(args.base), '--base-receipt', str(args.base_receipt), '--modules-dir', str(args.modules_dir), *purpose_args, - '--policy-dir', str(args.policy_dir), '--output', str(stage / 'root'), *cleanup_args], check=True) + '--policy-dir', str(args.policy_dir), '--output', str(stage / 'root'), + '--device', args.device, *cleanup_args], check=True) subprocess.run([sys.executable, '-m', 'orchard_hardware.boot.initramfs.pack', '--root', str(stage / 'root'), '--output', str(initramfs)], check=True) subprocess.run([sys.executable, '-m', 'orchard_hardware.boot.assemble', - '--manifest', str(HERE / manifest), + '--manifest', str(here / manifest), '--loader', str(args.loader), '--dtb', str(args.dtb), '--kernel', str(args.kernel), '--initramfs', str(initramfs), '--output', str(args.output)], check=True) diff --git a/image/runtime.py b/image/runtime.py index 0bb99df1..6c0b83af 100644 --- a/image/runtime.py +++ b/image/runtime.py @@ -4,15 +4,27 @@ from pathlib import Path PROJECT = Path(__file__).resolve().parents[1] -TOUCH = PROJECT / 'devices/j71b/touch/runtime' -MODULES = { - 'touch-start': ('virt-dma', 'apple-sio-core', 'apple-sio-dma', 'j71b_dma_bindings', - 'j71b_sio_denied_test', 'j71b_spi_dma_reset', 'j71b_touch_start'), - 'touch-runtime': ('virt-dma', 'apple-sio-core', 'apple-sio-dma', 'j71b_dma_bindings', 'j71b_z2'), +# The device API in runtime/j71b is shared; each profile adds its hardware +# preparation, platform helpers and optional staged touch activation. +SHARED = PROJECT / 'runtime/j71b' +PROFILES = { + 'j71b': { + 'touch': PROJECT / 'devices/j71b/touch/runtime', + 'modules': { + 'touch-start': ('virt-dma', 'apple-sio-core', 'apple-sio-dma', 'j71b_dma_bindings', + 'j71b_sio_denied_test', 'j71b_spi_dma_reset', 'j71b_touch_start'), + 'touch-runtime': ('virt-dma', 'apple-sio-core', 'apple-sio-dma', 'j71b_dma_bindings', 'j71b_z2'), + }, + 'helpers': ('j71b-check-platform', 'j71b-charge-lease'), + }, + 'j71t': {'touch': None, 'modules': {}, 'helpers': ('j71t-check-platform',)}, } -def files(modules_dir: Path, policy_dir: Path): +def files(modules_dir: Path, policy_dir: Path, profile='j71b'): + if profile not in PROFILES: + raise ValueError('No device runtime for profile: ' + str(profile)) + device = PROFILES[profile] releases = [p for p in modules_dir.iterdir() if p.is_dir() and not p.is_symlink()] if len(releases) != 1: raise ValueError('Expected one kernel module release') @@ -25,10 +37,10 @@ def files(modules_dir: Path, policy_dir: Path): raise ValueError('Public module tree contains a symlink') if path.is_file(): result['usr/lib/modules/' + release + '/' + path.relative_to(releases[0]).as_posix()] = (path.read_bytes(), 0o644) - prefix = 'usr/libexec/orchard-j71b/' - for role, names in MODULES.items(): + prefix = 'usr/libexec/orchard-' + profile + '/' + for role, names in device['modules'].items(): entries = {} - for path in sorted((TOUCH / role).glob('*.sh')): + for path in sorted((device['touch'] / role).glob('*.sh')): entries[path.name] = (path.read_bytes().replace(b'@KERNEL_RELEASE@', release.encode()), 0o755) for name in names: candidates = list(releases[0].rglob(name + '.ko')) @@ -39,24 +51,24 @@ def files(modules_dir: Path, policy_dir: Path): for name, (data, _) in sorted(entries.items())) entries['PUBLIC.SHA256SUMS'] = (sums.encode(), 0o644) result.update({prefix + role + '/' + name: value for name, value in entries.items()}) - result[prefix + 'touch-activate'] = ((TOUCH / 'touch-activate').read_bytes().replace( - b'@KERNEL_RELEASE@', release.encode()), 0o755) - for source, target in (('phase2-hardware', 'hardware'), ('device.py', 'device.py'), - ('archive.py', 'archive.py'), ('handoff.py', 'handoff.py'), - ('staging.py', 'staging.py'), ('ssh_access.py', 'ssh_access.py'), - ('storage.py', 'storage.py'), ('identity.py', 'identity.py'), - ('launch.py', 'launch.py')): - result[prefix + target] = ((PROJECT / 'runtime/j71b' / source).read_bytes(), 0o755) + if device['touch']: + result[prefix + 'touch-activate'] = ((device['touch'] / 'touch-activate').read_bytes().replace( + b'@KERNEL_RELEASE@', release.encode()), 0o755) + result[prefix + 'hardware'] = ((PROJECT / 'runtime' / profile / 'phase2-hardware').read_bytes(), 0o755) + for name in ('device.py', 'archive.py', 'handoff.py', 'staging.py', 'ssh_access.py', + 'storage.py', 'identity.py', 'launch.py'): + result[prefix + name] = ((SHARED / name).read_bytes(), 0o755) result[prefix + 'adt.py'] = ((PROJECT / 'apple/adt.py').read_bytes(), 0o644) - result['usr/bin/orchard-device'] = (b'#!/bin/sh\nexec /usr/bin/python3 /usr/libexec/orchard-j71b/device.py "$@"\n', 0o755) + result['usr/bin/orchard-device'] = (('#!/bin/sh\nexec /usr/bin/python3 /' + prefix + + 'device.py "$@"\n').encode(), 0o755) if not (policy_dir / 'image_policy.py').is_file(): raise ValueError('Image must supply its own image_policy.py') for path in sorted(policy_dir.glob('*.py')): if path.is_symlink() or prefix + path.name in result: raise ValueError('Unsafe/colliding image policy file') result[prefix + path.name] = (path.read_bytes(), 0o644) - for name in ('j71b-check-platform', 'j71b-charge-lease'): - result['usr/libexec/' + name] = ((PROJECT / 'runtime/j71b' / name).read_bytes().replace( + for name in device['helpers']: + result['usr/libexec/' + name] = ((PROJECT / 'runtime' / profile / name).read_bytes().replace( b'@KERNEL_RELEASE@', release.encode()), 0o755) # cfg80211 needs the public database AND its upstream detached signature # in recovery, before Wi-Fi probing. This is not private device firmware. diff --git a/kernel/README.md b/kernel/README.md index 729f02c2..416c9e87 100644 --- a/kernel/README.md +++ b/kernel/README.md @@ -19,3 +19,31 @@ read-write commands. The separate `nvme-core` module also requires `j71b_rw=1` to stop forcing the J71b namespace read-only. Both matching modules must be loaded in a fresh recovery boot. The combined mode is compiled, not yet hardware-proven. + +## J71t + +```sh +./kernel/build j71t --output out/kernel/j71t +``` + +`patches/j71t/` is one patch per feature (S8003 PCIe/ANS NVMe, Wi-Fi and +Bluetooth, SmartIO/SPI, N1 touch, audio, power, USB, sensors/backlight, native +display, H.264 decoder, device trees, MAINTAINERS, then the charging update) +in `git format-patch` form. Each patch is one commit of +[brkosebay/linux `a9/orchard-j71t`](https://github.com/brkosebay/linux/tree/a9/orchard-j71t), which is based on +the same `hoolock` revision and taken from the release 2 kernel of the +[omarchy-ipad5](https://github.com/brkosebay/omarchy-ipad5) port; patch 0013 +adds that port's device-validated charger source detection (computers and +USB-C chargers at up to 2.4 A). Follow-ups are added as new patches, so +published patches never change. It leaves out the GPU research +code, SEP and the Touch ID sensor, AOP and diagnostics-only drivers. The series +applies in order to the pinned revision and reproduces that branch's tree. + +The J71t NVMe driver is built in and read-only by default. Only NVMe Write +commands inside the boot argument `nvme.write_window=NSID:FIRST-LAST` reach the +controller, never trim/Write Zeroes/Format/Sanitize/firmware/namespace +commands, and namespaces 2-7 stay read-only whatever the window says. +`CONFIG_NVME_APPLE_ANS_EARLY_USB` holds the PCIe host and ANS until recovery +releases them (`runtime/j71t/phase2-hardware`). See +[the J71t recovery README](../boot/initramfs/j71t/recovery/README.md) for how +Orchard's target selection maps onto the window. diff --git a/kernel/configs/j71t/README.md b/kernel/configs/j71t/README.md new file mode 100644 index 00000000..b56a6086 --- /dev/null +++ b/kernel/configs/j71t/README.md @@ -0,0 +1,63 @@ +# Kernel configuration + +`config` is the complete, resolved configuration used to build the J71t +kernel. It is a version-controlled build input despite its Kconfig-generated +“DO NOT EDIT” header. + +## Origin + +The starting point is the 16K-page HoolockLinux configuration (`config_16k`, +as for J71b) plus the omarchy-ipad5 port's fragments for PCIe/NVMe, Wi-Fi, +Bluetooth, touch, audio, sensors, power, USB, display and video decoding, as +used by its release 2 kernel. Options for code that is not part of +`../../patches/j71t/` (GPU, SEP, AOP and diagnostics) no longer exist and +dropped out on resolution. + +It was resolved for the patched kernel with Debian Clang/LLVM 19.1.7 (the +toolchain in `../../Containerfile`), after these Orchard-specific changes: + +- `CONFIG_LOCALVERSION_AUTO` is disabled; the build script supplies + `-orchard-j71t`. +- Drivers that read private firmware or calibration when they probe are + modules, because Orchard delivers that data after boot: + `CONFIG_TOUCHSCREEN_APPLE_N1=m` (N1 image and MtCl), + `CONFIG_BT_HCIUART=m` (patchram and Bluetooth address), + `CONFIG_APPLE_D2365_CHARGER=m` (CBAT input-limit calibration), + `CONFIG_APPLE_SIO=m` (SmartIO firmware, used by audio) and + `CONFIG_TSL2583=m` (LSCI ambient-light calibration). Recovery loads the + first three in `runtime/j71t/phase2-hardware`; the installed system's udev + loads the rest. +- `CONFIG_APPLE_A9_TUNING` is disabled, so the opt-in overclocking code is not + built. CPU frequency stays on the stock 300–1800 MHz table. +- `CONFIG_MODULE_COMPRESS` is disabled, as for J71b: modules are plain `.ko` + files for recovery's BusyBox `modprobe`. + +## Important choices + +- `CONFIG_ARM64_16K_PAGES=y`: 16 KiB kernel pages. +- `CONFIG_MTD_PHRAM=y`: the Apple Device Tree that m1n1 exposes, read by + recovery's `capture-adt` and device identity. +- `CONFIG_PCIE_APPLE_H9P=y`, `CONFIG_BLK_DEV_NVME=y`, + `CONFIG_NVME_PCI_READ_ONLY=y`, `CONFIG_NVME_PCI_WRITE_WINDOW=y`: built-in + S8003 PCIe and ANS NVMe. NVMe stays read-only except for the boot-time + `nvme.write_window`; namespaces 2-7 stay read-only regardless. +- `CONFIG_NVME_APPLE_ANS_EARLY_USB=y`: the PCIe host and ANS wait until + `phase2-hardware` releases them. +- `CONFIG_BRCMFMAC=y`: Wi-Fi is built in. It probes only after the PCIe host + is released, when the firmware mixin is already present. +- `CONFIG_DRM_APPLE_ADBE=y`: native KMS on the display pipe iBoot leaves + running. It takes over m1n1's framebuffer from simpledrm during boot and + names the internal panel `eDP-1`; it never programs timings, clocks or the + eDP link. `CONFIG_DRM_SIMPLEDRM=y` stays as the fallback: + `apple_adbe_drm.enable=0` keeps simpledrm. There is no GPU driver. +- `CONFIG_VIDEO_APPLE_VXD=y`: the H.264 decoder is built in, as in omarchy-ipad5 + (the driver calls `pr_flush`, which modules cannot use). It probes without + firmware and reads the DEVA firmware (`apple/vxd-deva.bin`, from the mixin) + only when it is booted through debugfs (`apple-vxd/boot`). + +## How the file is generated + +The build copies `config` into its output directory as `.config`, runs +`make LLVM=-19 ARCH=arm64 olddefconfig` and requires the result to be +byte-identical. To change it, edit a copy, resolve it the same way against the +patched source with the same toolchain, and commit the resolved file. diff --git a/kernel/configs/j71t/config b/kernel/configs/j71t/config new file mode 100644 index 00000000..a3ae84cc --- /dev/null +++ b/kernel/configs/j71t/config @@ -0,0 +1,7529 @@ +# +# Automatically generated file; DO NOT EDIT. +# Linux/arm64 7.3.0-rc1 Kernel Configuration +# +CONFIG_CC_VERSION_TEXT="Debian clang version 19.1.7 (3~deb12u1)" +CONFIG_GCC_VERSION=0 +CONFIG_CC_IS_CLANG=y +CONFIG_CLANG_VERSION=190107 +CONFIG_AS_IS_LLVM=y +CONFIG_AS_VERSION=190107 +CONFIG_LD_VERSION=0 +CONFIG_LD_IS_LLD=y +CONFIG_LLD_VERSION=190107 +CONFIG_RUSTC_VERSION=0 +CONFIG_RUSTC_LLVM_VERSION=0 +CONFIG_RUSTC_LLVM_MAJOR_VERSION=0 +CONFIG_CC_CAN_LINK=y +CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y +CONFIG_CC_HAS_ASM_GOTO_TIED_OUTPUT=y +CONFIG_TOOLS_SUPPORT_RELR=y +CONFIG_CC_HAS_ASM_INLINE=y +CONFIG_CC_HAS_ASSUME=y +CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y +CONFIG_CC_HAS_BROKEN_COUNTED_BY_REF=y +CONFIG_PAHOLE_VERSION=0 +CONFIG_IRQ_WORK=y +CONFIG_BUILDTIME_TABLE_SORT=y +CONFIG_THREAD_INFO_IN_TASK=y + +# +# General setup +# +CONFIG_INIT_ENV_ARG_LIMIT=32 +# CONFIG_COMPILE_TEST is not set +# CONFIG_WERROR is not set +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_BUILD_SALT="" +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_ZSTD=y +CONFIG_KERNEL_GZIP=y +# CONFIG_KERNEL_ZSTD is not set +CONFIG_DEFAULT_INIT="" +CONFIG_DEFAULT_HOSTNAME="(none)" +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_SYSVIPC_COMPAT=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +# CONFIG_WATCH_QUEUE is not set +CONFIG_CROSS_MEMORY_ATTACH=y +CONFIG_AUDIT=y +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +CONFIG_AUDITSYSCALL=y + +# +# IRQ subsystem +# +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y +CONFIG_GENERIC_IRQ_MIGRATION=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_DOMAIN_HIERARCHY=y +CONFIG_GENERIC_IRQ_IPI=y +CONFIG_GENERIC_IRQ_IPI_MUX=y +CONFIG_GENERIC_MSI_IRQ=y +CONFIG_IRQ_MSI_IOMMU=y +CONFIG_GENERIC_IRQ_STAT_SNAPSHOT=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_SPARSE_IRQ=y +# CONFIG_GENERIC_IRQ_DEBUGFS is not set +CONFIG_GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD=y +# end of IRQ subsystem + +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y +CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y +CONFIG_CONTEXT_TRACKING=y +CONFIG_CONTEXT_TRACKING_IDLE=y + +# +# Timers subsystem +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ_COMMON=y +# CONFIG_HZ_PERIODIC is not set +CONFIG_NO_HZ_IDLE=y +# CONFIG_NO_HZ_FULL is not set +# CONFIG_NO_HZ is not set +CONFIG_HIGH_RES_TIMERS=y +# CONFIG_POSIX_AUX_CLOCKS is not set +# end of Timers subsystem + +CONFIG_BPF=y +CONFIG_HAVE_EBPF_JIT=y +CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y + +# +# BPF subsystem +# +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_JIT_DEFAULT_ON=y +# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set +# CONFIG_BPF_PRELOAD is not set +# CONFIG_BPF_LSM is not set +# end of BPF subsystem + +CONFIG_PREEMPT_BUILD=y +CONFIG_ARCH_HAS_PREEMPT_LAZY=y +CONFIG_PREEMPT=y +# CONFIG_PREEMPT_LAZY is not set +# CONFIG_PREEMPT_RT is not set +CONFIG_PREEMPT_COUNT=y +CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS=y +CONFIG_PREEMPTION=y +# CONFIG_PREEMPT_DYNAMIC is not set + +# +# CPU/Task time and stats accounting +# +CONFIG_TICK_CPU_ACCOUNTING=y +# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_SCHED_AVG_IRQ=y +CONFIG_SCHED_HW_PRESSURE=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +# CONFIG_PSI_DEFAULT_DISABLED is not set +# end of CPU/Task time and stats accounting + +CONFIG_CPU_ISOLATION=y + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +CONFIG_PREEMPT_RCU=y +# CONFIG_RCU_EXPERT is not set +CONFIG_TREE_SRCU=y +CONFIG_TASKS_RCU_GENERIC=y +CONFIG_NEED_TASKS_RCU=y +CONFIG_TASKS_RCU=y +CONFIG_TASKS_TRACE_RCU=y +CONFIG_RCU_STALL_COMMON=y +CONFIG_RCU_NEED_SEGCBLIST=y +# end of RCU Subsystem + +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +# CONFIG_IKHEADERS is not set +CONFIG_LOG_BUF_SHIFT=17 +CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 +# CONFIG_PRINTK_INDEX is not set +CONFIG_GENERIC_SCHED_CLOCK=y + +# +# Scheduler features +# +# CONFIG_UCLAMP_TASK is not set +# CONFIG_SCHED_PROXY_EXEC is not set +# end of Scheduler features + +CONFIG_ARCH_HAS_PTE_PROTNONE=y +CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y +CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y +CONFIG_CC_HAS_INT128=y +CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough" +CONFIG_CC_MS_EXTENSIONS="-fms-extensions" +CONFIG_GCC10_NO_ARRAY_BOUNDS=y +CONFIG_GCC_NO_STRINGOP_OVERFLOW=y +CONFIG_ARCH_SUPPORTS_INT128=y +CONFIG_SCHED_CACHE=y +CONFIG_SLAB_OBJ_EXT=y +CONFIG_CGROUPS=y +CONFIG_PAGE_COUNTER=y +# CONFIG_CGROUP_FAVOR_DYNMODS is not set +CONFIG_MEMCG=y +# CONFIG_MEMCG_V1 is not set +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_WRITEBACK=y +CONFIG_CGROUP_SCHED=y +CONFIG_GROUP_SCHED_WEIGHT=y +CONFIG_GROUP_SCHED_BANDWIDTH=y +CONFIG_FAIR_GROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +# CONFIG_RT_GROUP_SCHED is not set +CONFIG_SCHED_MM_CID=y +CONFIG_CGROUP_PIDS=y +# CONFIG_CGROUP_RDMA is not set +# CONFIG_CGROUP_DMEM is not set +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CPUSETS=y +# CONFIG_CPUSETS_V1 is not set +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +# CONFIG_CGROUP_DEBUG is not set +CONFIG_SOCK_CGROUP_DATA=y +CONFIG_NAMESPACES=y +CONFIG_UTS_NS=y +CONFIG_TIME_NS=y +CONFIG_TIME_NS_VDSO=y +CONFIG_IPC_NS=y +CONFIG_USER_NS=y +CONFIG_PID_NS=y +CONFIG_NET_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_SCHED_AUTOGROUP=y +# CONFIG_RELAY is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_RD_XZ=y +CONFIG_RD_LZO=y +CONFIG_RD_LZ4=y +CONFIG_RD_ZSTD=y +CONFIG_BOOT_CONFIG=y +# CONFIG_BOOT_CONFIG_FORCE is not set +# CONFIG_BOOT_CONFIG_EMBED is not set +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=1021 +CONFIG_INITRAMFS_PRESERVE_MTIME=y +# CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_LD_ORPHAN_WARN=y +CONFIG_LD_ORPHAN_WARN_LEVEL="warn" +CONFIG_HAVE_UID16=y +CONFIG_SYSCTL_EXCEPTION_TRACE=y +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_UID16=y +CONFIG_MULTIUSER=y +# CONFIG_SGETMASK_SYSCALL is not set +CONFIG_FHANDLE=y +CONFIG_POSIX_TIMERS=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +# CONFIG_BASE_SMALL is not set +CONFIG_FUTEX=y +CONFIG_FUTEX_PI=y +CONFIG_FUTEX_PRIVATE_HASH=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_IO_URING=y +# CONFIG_IO_URING_MOCK_FILE is not set +CONFIG_ADVISE_SYSCALLS=y +CONFIG_MEMBARRIER=y +CONFIG_KCMP=y +CONFIG_RSEQ=y +# CONFIG_RSEQ_STATS is not set +# CONFIG_RSEQ_DEBUG_DEFAULT_ENABLE is not set +# CONFIG_DEBUG_RSEQ is not set +CONFIG_CACHESTAT_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_SELFTEST is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y +CONFIG_ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS=y +CONFIG_HAVE_PERF_EVENTS=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# end of Kernel Performance Events And Counters + +CONFIG_SYSTEM_DATA_VERIFICATION=y +CONFIG_PROFILING=y +CONFIG_TRACEPOINTS=y + +# +# Kexec and crash features +# +CONFIG_CRASH_RESERVE=y +CONFIG_VMCORE_INFO=y +CONFIG_KEXEC_CORE=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y +# CONFIG_KEXEC_SIG is not set +CONFIG_CRASH_DUMP=y +# CONFIG_CRASH_DM_CRYPT is not set +# end of Kexec and crash features + +# +# Live Update and Kexec HandOver +# +# CONFIG_KEXEC_HANDOVER is not set +# end of Live Update and Kexec HandOver +# end of General setup + +CONFIG_ARM64=y +CONFIG_RUSTC_SUPPORTS_ARM64=y +CONFIG_CLANG_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS=y +CONFIG_64BIT=y +CONFIG_MMU=y +CONFIG_ARM64_CONT_PTE_SHIFT=7 +CONFIG_ARM64_CONT_PMD_SHIFT=5 +CONFIG_ARCH_MMAP_RND_BITS_MIN=16 +CONFIG_ARCH_MMAP_RND_BITS_MAX=31 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=9 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16 +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CSUM=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_SMP=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_FIX_EARLYCON_MEM=y +CONFIG_PGTABLE_LEVELS=4 +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_ARCH_PROC_KCORE_TEXT=y +CONFIG_BUILTIN_RETURN_ADDRESS_STRIPS_PAC=y + +# +# Platform selection +# +# CONFIG_ARCH_ACTIONS is not set +# CONFIG_ARCH_AIROHA is not set +# CONFIG_ARCH_SUNXI is not set +# CONFIG_ARCH_ALPINE is not set +CONFIG_ARCH_APPLE=y +# CONFIG_ARCH_ASPEED is not set +# CONFIG_ARCH_AXIADO is not set +# CONFIG_ARCH_BCM is not set +# CONFIG_ARCH_BERLIN is not set +# CONFIG_ARCH_BITMAIN is not set +# CONFIG_ARCH_BLAIZE is not set +# CONFIG_ARCH_BST is not set +# CONFIG_ARCH_CIX is not set +# CONFIG_ARCH_EXYNOS is not set +# CONFIG_ARCH_K3 is not set +# CONFIG_ARCH_LG1K is not set +# CONFIG_ARCH_HISI is not set +# CONFIG_ARCH_KEEMBAY is not set +# CONFIG_ARCH_MEDIATEK is not set +# CONFIG_ARCH_MESON is not set +# CONFIG_ARCH_MICROCHIP is not set +# CONFIG_ARCH_MMP is not set +# CONFIG_ARCH_MVEBU is not set +# CONFIG_ARCH_NXP is not set +# CONFIG_ARCH_MA35 is not set +# CONFIG_ARCH_NPCM is not set +# CONFIG_ARCH_PENSANDO is not set +# CONFIG_ARCH_QCOM is not set +# CONFIG_ARCH_REALTEK is not set +# CONFIG_ARCH_RENESAS is not set +# CONFIG_ARCH_ROCKCHIP is not set +# CONFIG_ARCH_SEATTLE is not set +# CONFIG_ARCH_INTEL_SOCFPGA is not set +# CONFIG_ARCH_SOPHGO is not set +# CONFIG_ARCH_STM32 is not set +# CONFIG_ARCH_SYNQUACER is not set +# CONFIG_ARCH_TEGRA is not set +# CONFIG_ARCH_SPRD is not set +# CONFIG_ARCH_THUNDER is not set +# CONFIG_ARCH_THUNDER2 is not set +# CONFIG_ARCH_UNIPHIER is not set +# CONFIG_ARCH_VEXPRESS is not set +# CONFIG_ARCH_VISCONTI is not set +# CONFIG_ARCH_XGENE is not set +# CONFIG_ARCH_ZYNQMP is not set +# end of Platform selection + +# +# Kernel Features +# + +# +# ARM errata workarounds via the alternatives framework +# +# CONFIG_AMPERE_ERRATUM_AC03_CPU_38 is not set +# CONFIG_AMPERE_ERRATUM_AC04_CPU_23 is not set +CONFIG_ARM64_ERRATUM_APPLE_FUSION=y +# CONFIG_ARM64_ERRATUM_826319 is not set +# CONFIG_ARM64_ERRATUM_827319 is not set +# CONFIG_ARM64_ERRATUM_824069 is not set +# CONFIG_ARM64_ERRATUM_819472 is not set +# CONFIG_ARM64_ERRATUM_832075 is not set +# CONFIG_ARM64_ERRATUM_1742098 is not set +# CONFIG_ARM64_ERRATUM_845719 is not set +# CONFIG_ARM64_ERRATUM_843419 is not set +# CONFIG_ARM64_ERRATUM_1024718 is not set +# CONFIG_ARM64_ERRATUM_1418040 is not set +# CONFIG_ARM64_ERRATUM_1165522 is not set +# CONFIG_ARM64_ERRATUM_1319367 is not set +# CONFIG_ARM64_ERRATUM_1530923 is not set +# CONFIG_ARM64_ERRATUM_2441007 is not set +# CONFIG_ARM64_ERRATUM_1286807 is not set +# CONFIG_ARM64_ERRATUM_1463225 is not set +# CONFIG_ARM64_ERRATUM_1542419 is not set +# CONFIG_ARM64_ERRATUM_1508412 is not set +# CONFIG_ARM64_ERRATUM_2051678 is not set +# CONFIG_ARM64_ERRATUM_2077057 is not set +# CONFIG_ARM64_ERRATUM_2658417 is not set +# CONFIG_ARM64_ERRATUM_2119858 is not set +# CONFIG_ARM64_ERRATUM_2139208 is not set +# CONFIG_ARM64_ERRATUM_2054223 is not set +# CONFIG_ARM64_ERRATUM_2067961 is not set +# CONFIG_ARM64_ERRATUM_2253138 is not set +# CONFIG_ARM64_ERRATUM_2224489 is not set +# CONFIG_ARM64_ERRATUM_2441009 is not set +# CONFIG_ARM64_ERRATUM_2064142 is not set +# CONFIG_ARM64_ERRATUM_2038923 is not set +# CONFIG_ARM64_ERRATUM_1902691 is not set +# CONFIG_ARM64_ERRATUM_2645198 is not set +# CONFIG_ARM64_ERRATUM_2966298 is not set +# CONFIG_ARM64_ERRATUM_3117295 is not set +# CONFIG_ARM64_ERRATUM_3194386 is not set +# CONFIG_ARM64_ERRATUM_4311569 is not set +# CONFIG_ARM64_ERRATUM_4118414 is not set +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +# CONFIG_CAVIUM_ERRATUM_30115 is not set +# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set +# CONFIG_FUJITSU_ERRATUM_010001 is not set +# CONFIG_HISILICON_ERRATUM_161600802 is not set +# CONFIG_HISILICON_ERRATUM_162100801 is not set +# CONFIG_HISILICON_ERRATUM_162100125 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set +# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set +# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set +CONFIG_NVIDIA_OLYMPUS_1027_ERRATUM=y +CONFIG_RENESAS_ERRATUM_GEN4GICITS1=y +# CONFIG_ROCKCHIP_ERRATUM_3568002 is not set +# CONFIG_ROCKCHIP_ERRATUM_3588001 is not set +# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set +# end of ARM errata workarounds via the alternatives framework + +# CONFIG_ARM64_4K_PAGES is not set +CONFIG_ARM64_16K_PAGES=y +# CONFIG_ARM64_64K_PAGES is not set +# CONFIG_ARM64_VA_BITS_36 is not set +# CONFIG_ARM64_VA_BITS_47 is not set +CONFIG_ARM64_VA_BITS_48=y +# CONFIG_ARM64_VA_BITS_52 is not set +CONFIG_ARM64_VA_BITS=48 +CONFIG_ARM64_PA_BITS_48=y +CONFIG_ARM64_PA_BITS=48 +CONFIG_CPU_LITTLE_ENDIAN=y +CONFIG_NR_CPUS=8 +CONFIG_HOTPLUG_CPU=y +# CONFIG_NUMA is not set +# CONFIG_HZ_100 is not set +# CONFIG_HZ_250 is not set +# CONFIG_HZ_300 is not set +CONFIG_HZ_1000=y +CONFIG_HZ=1000 +CONFIG_SCHED_HRTICK=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_HW_PERF_EVENTS=y +CONFIG_CC_HAVE_SHADOW_CALL_STACK=y +# CONFIG_PARAVIRT is not set +# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set +CONFIG_ARCH_SUPPORTS_KEXEC=y +CONFIG_ARCH_SUPPORTS_KEXEC_FILE=y +CONFIG_ARCH_SELECTS_KEXEC_FILE=y +CONFIG_ARCH_SUPPORTS_KEXEC_SIG=y +CONFIG_ARCH_SUPPORTS_KEXEC_IMAGE_VERIFY_SIG=y +CONFIG_ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG=y +CONFIG_ARCH_SUPPORTS_KEXEC_HANDOVER=y +CONFIG_ARCH_SUPPORTS_CRASH_DUMP=y +CONFIG_ARCH_DEFAULT_CRASH_DUMP=y +CONFIG_ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION=y +CONFIG_TRANS_TABLE=y +# CONFIG_XEN is not set +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_UNMAP_KERNEL_AT_EL0=y +CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY=y +# CONFIG_ARM64_SW_TTBR0_PAN is not set +CONFIG_ARM64_TAGGED_ADDR_ABI=y +CONFIG_COMPAT=y +CONFIG_KUSER_HELPERS=y +CONFIG_COMPAT_VDSO=y +CONFIG_THUMB2_COMPAT_VDSO=y +# CONFIG_COMPAT_ALIGNMENT_FIXUPS is not set +# CONFIG_ARMV8_DEPRECATED is not set + +# +# ARMv8.1 architectural features +# +CONFIG_ARM64_HW_AFDBM=y +# end of ARMv8.1 architectural features + +# +# ARMv8.2 architectural features +# +# CONFIG_ARM64_PMEM is not set +CONFIG_ARM64_RAS_EXTN=y +CONFIG_ARM64_CNP=y +# end of ARMv8.2 architectural features + +# +# ARMv8.3 architectural features +# +# CONFIG_ARM64_PTR_AUTH is not set +CONFIG_CC_HAS_BRANCH_PROT_PAC_RET=y +CONFIG_AS_HAS_CFI_NEGATE_RA_STATE=y +# end of ARMv8.3 architectural features + +# +# ARMv8.4 architectural features +# +# CONFIG_ARM64_AMU_EXTN is not set +# CONFIG_ARM64_TLB_RANGE is not set +# CONFIG_ARM64_MPAM is not set +# end of ARMv8.4 architectural features + +# +# ARMv8.5 architectural features +# +CONFIG_AS_HAS_ARMV8_5=y +# CONFIG_ARM64_BTI is not set +CONFIG_CC_HAS_BRANCH_PROT_PAC_RET_BTI=y +# CONFIG_ARM64_E0PD is not set +CONFIG_ARM64_AS_HAS_MTE=y +# CONFIG_ARM64_MTE is not set +# end of ARMv8.5 architectural features + +# +# ARMv8.7 architectural features +# +# CONFIG_ARM64_EPAN is not set +# end of ARMv8.7 architectural features + +CONFIG_AS_HAS_MOPS=y + +# +# ARMv8.9 architectural features +# +# CONFIG_ARM64_POE is not set +CONFIG_ARCH_PKEY_BITS=3 +# CONFIG_ARM64_HAFT is not set +# end of ARMv8.9 architectural features + +# +# ARMv9.4 architectural features +# +# CONFIG_ARM64_GCS is not set +# end of ARMv9.4 architectural features + +# +# ARMv9.6 architectural features +# +# end of ARMv9.6 architectural features + +# CONFIG_ARM64_SVE is not set +CONFIG_AS_HAS_SME=y +# CONFIG_ARM64_PSEUDO_NMI is not set +CONFIG_RELOCATABLE=y +CONFIG_RANDOMIZE_BASE=y +CONFIG_RANDOMIZE_MODULE_REGION_FULL=y +CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y +CONFIG_STACKPROTECTOR_PER_TASK=y +CONFIG_ARM64_CONTPTE=y +# end of Kernel Features + +# +# Boot options +# +CONFIG_CMDLINE="" +CONFIG_EFI_STUB=y +CONFIG_EFI=y +# CONFIG_COMPRESSED_INSTALL is not set +CONFIG_DMI=y +# end of Boot options + +# +# Power management options +# +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_SUSPEND_SKIP_SYNC is not set +# CONFIG_HIBERNATION is not set +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +# CONFIG_PM_AUTOSLEEP is not set +# CONFIG_PM_USERSPACE_AUTOSLEEP is not set +# CONFIG_PM_WAKELOCKS is not set +# CONFIG_PM_QOS_CPU_SYSTEM_WAKEUP is not set +CONFIG_PM=y +CONFIG_PM_DEBUG=y +CONFIG_PM_ADVANCED_DEBUG=y +# CONFIG_PM_TEST_SUSPEND is not set +CONFIG_PM_SLEEP_DEBUG=y +# CONFIG_DPM_WATCHDOG is not set +CONFIG_PM_CLK=y +CONFIG_PM_GENERIC_DOMAINS=y +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_PM_GENERIC_DOMAINS_SLEEP=y +CONFIG_PM_GENERIC_DOMAINS_OF=y +CONFIG_CPU_PM=y +CONFIG_ENERGY_MODEL=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +# end of Power management options + +# +# CPU Power Management +# + +# +# CPU Idle +# +CONFIG_CPU_IDLE=y +# CONFIG_CPU_IDLE_GOV_LADDER is not set +CONFIG_CPU_IDLE_GOV_MENU=y +# CONFIG_CPU_IDLE_GOV_TEO is not set + +# +# ARM CPU Idle Drivers +# +# CONFIG_ARM_PSCI_CPUIDLE is not set +# end of ARM CPU Idle Drivers +# end of CPU Idle + +# +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_ATTR_SET=y +CONFIG_CPU_FREQ_GOV_COMMON=y +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y + +# +# CPU frequency scaling drivers +# +# CONFIG_CPUFREQ_DT is not set +# CONFIG_CPUFREQ_VIRT is not set +# CONFIG_CPUFREQ_DT_PLATDEV is not set +CONFIG_ARM_APPLE_SOC_CPUFREQ=y +# CONFIG_APPLE_A9_TUNING is not set +# end of CPU Frequency scaling +# end of CPU Power Management + +CONFIG_ARCH_SUPPORTS_ACPI=y +# CONFIG_ACPI is not set +# CONFIG_VIRTUALIZATION is not set +CONFIG_HAVE_LIVEPATCH=y +CONFIG_CPU_MITIGATIONS=y + +# +# General architecture-dependent options +# +CONFIG_HOTPLUG_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_CLUSTER=y +CONFIG_ARCH_SUPPORTS_SCHED_MC=y +# CONFIG_SCHED_SMT is not set +CONFIG_SCHED_CLUSTER=y +CONFIG_SCHED_MC=y +CONFIG_HOTPLUG_CORE_SYNC=y +CONFIG_HOTPLUG_CORE_SYNC_DEAD=y +CONFIG_GENERIC_IRQ_ENTRY=y +# CONFIG_KPROBES is not set +CONFIG_JUMP_LABEL=y +# CONFIG_STATIC_KEYS_SELFTEST is not set +CONFIG_UPROBES=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_FUNCTION_ERROR_INJECTION=y +CONFIG_HAVE_NMI=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_ARCH_HAS_FORTIFY_SOURCE=y +CONFIG_ARCH_HAS_KEEPINITRD=y +CONFIG_ARCH_HAS_SET_MEMORY=y +CONFIG_ARCH_HAS_SET_DIRECT_MAP=y +CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y +CONFIG_ARCH_WANTS_NO_INSTR=y +CONFIG_HAVE_ASM_MODVERSIONS=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_RSEQ=y +CONFIG_HAVE_RUST=y +CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y +CONFIG_MMU_GATHER_TABLE_FREE=y +CONFIG_MMU_GATHER_RCU_TABLE_FREE=y +CONFIG_MMU_LAZY_TLB_REFCOUNT=y +CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y +CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS=y +CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y +CONFIG_HAVE_CMPXCHG_LOCAL=y +CONFIG_HAVE_CMPXCHG_DOUBLE=y +CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION=y +CONFIG_HAVE_ARCH_SECCOMP=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +# CONFIG_SECCOMP_CACHE_DEBUG is not set +CONFIG_HAVE_ARCH_KSTACK_ERASE=y +CONFIG_HAVE_STACKPROTECTOR=y +CONFIG_STACKPROTECTOR=y +CONFIG_STACKPROTECTOR_STRONG=y +CONFIG_ARCH_SUPPORTS_SHADOW_CALL_STACK=y +# CONFIG_SHADOW_CALL_STACK is not set +CONFIG_ARCH_SUPPORTS_LTO_CLANG=y +CONFIG_ARCH_SUPPORTS_LTO_CLANG_THIN=y +CONFIG_HAS_LTO_CLANG=y +CONFIG_LTO_NONE=y +# CONFIG_LTO_CLANG_FULL is not set +# CONFIG_LTO_CLANG_THIN is not set +# CONFIG_LTO_CLANG_THIN_DIST is not set +# CONFIG_AUTOFDO_CLANG is not set +# CONFIG_PROPELLER_CLANG is not set +CONFIG_ARCH_SUPPORTS_CFI=y +# CONFIG_CFI is not set +CONFIG_HAVE_CFI_ICALL_NORMALIZE_INTEGERS=y +CONFIG_HAVE_CFI_ICALL_NORMALIZE_INTEGERS_RUSTC=y +CONFIG_HAVE_CONTEXT_TRACKING_USER=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_MOVE_PUD=y +CONFIG_HAVE_MOVE_PMD=y +CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y +CONFIG_HAVE_ARCH_HUGE_VMAP=y +CONFIG_HAVE_ARCH_HUGE_VMALLOC=y +CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y +CONFIG_ARCH_WANT_PMD_MKWRITE=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_MODULES_USE_ELF_RELA=y +CONFIG_ARCH_WANTS_EXECMEM_LATE=y +CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y +CONFIG_SOFTIRQ_ON_OWN_STACK=y +CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_HAVE_ARCH_MMAP_RND_BITS=y +CONFIG_ARCH_MMAP_RND_BITS=18 +CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y +CONFIG_ARCH_MMAP_RND_COMPAT_BITS=11 +CONFIG_HAVE_PAGE_SIZE_16KB=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_PAGE_SIZE_LESS_THAN_64KB=y +CONFIG_PAGE_SIZE_LESS_THAN_256KB=y +CONFIG_PAGE_SHIFT=14 +CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y +CONFIG_HAVE_RELIABLE_STACKTRACE=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_OLD_SIGSUSPEND3=y +CONFIG_COMPAT_OLD_SIGACTION=y +CONFIG_COMPAT_32BIT_TIME=y +CONFIG_ARCH_SUPPORTS_RT=y +CONFIG_HAVE_ARCH_VMAP_STACK=y +CONFIG_VMAP_STACK=y +CONFIG_HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET=y +CONFIG_RANDOMIZE_KSTACK_OFFSET=y +# CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT is not set +CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y +CONFIG_STRICT_KERNEL_RWX=y +CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y +CONFIG_STRICT_MODULE_RWX=y +CONFIG_HAVE_ARCH_COMPILER_H=y +CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y +CONFIG_ARCH_USE_MEMREMAP_PROT=y +# CONFIG_LOCK_EVENT_COUNTS is not set +CONFIG_ARCH_HAS_RELR=y +CONFIG_RELR=y +CONFIG_ARCH_HAS_MEM_ENCRYPT=y +CONFIG_ARCH_HAS_CC_PLATFORM=y +CONFIG_HAVE_PREEMPT_DYNAMIC=y +CONFIG_HAVE_PREEMPT_DYNAMIC_KEY=y +CONFIG_ARCH_WANT_LD_ORPHAN_WARN=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_ARCH_SUPPORTS_PAGE_TABLE_CHECK=y +CONFIG_ARCH_HAVE_TRACE_MMIO_ACCESS=y +CONFIG_ARCH_HAS_HW_PTE_YOUNG=y +CONFIG_ARCH_HAS_KERNEL_FPU_SUPPORT=y + +# +# GCOV-based kernel profiling +# +# CONFIG_GCOV_KERNEL is not set +CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y +# end of GCOV-based kernel profiling + +CONFIG_HAVE_GCC_PLUGINS=y +CONFIG_FUNCTION_ALIGNMENT_4B=y +CONFIG_FUNCTION_ALIGNMENT=4 +CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT=y +CONFIG_HAVE_ARCH_GET_SECUREBOOT=y +# end of General architecture-dependent options + +CONFIG_RT_MUTEXES=y +CONFIG_MODULES=y +# CONFIG_MODULE_DEBUG is not set +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODULE_UNLOAD_TAINT_TRACKING is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +# CONFIG_MODULE_SIG is not set +# CONFIG_MODULE_COMPRESS is not set +# CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS is not set +CONFIG_MODPROBE_PATH="/sbin/modprobe" +# CONFIG_TRIM_UNUSED_KSYMS is not set +CONFIG_MODULES_TREE_LOOKUP=y +CONFIG_BLOCK=y +CONFIG_BLOCK_LEGACY_AUTOLOAD=y +CONFIG_BLK_CGROUP_PUNT_BIO=y +CONFIG_BLK_DEV_BSG_COMMON=y +# CONFIG_BLK_DEV_BSGLIB is not set +# CONFIG_BLK_DEV_INTEGRITY is not set +CONFIG_BLK_DEV_WRITE_MOUNTED=y +# CONFIG_BLK_DEV_ZONED is not set +# CONFIG_BLK_DEV_THROTTLING is not set +# CONFIG_BLK_WBT is not set +# CONFIG_BLK_CGROUP_IOLATENCY is not set +# CONFIG_BLK_CGROUP_IOCOST is not set +# CONFIG_BLK_CGROUP_IOPRIO is not set +CONFIG_BLK_DEBUG_FS=y +# CONFIG_BLK_SED_OPAL is not set +CONFIG_BLK_INLINE_ENCRYPTION=y +CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_EFI_PARTITION=y +# end of Partition Types + +CONFIG_BLK_PM=y +CONFIG_BLOCK_HOLDER_DEPRECATED=y +CONFIG_BLK_MQ_STACKING=y +# CONFIG_BLK_ERROR_INJECTION is not set + +# +# IO Schedulers +# +CONFIG_MQ_IOSCHED_DEADLINE=y +CONFIG_MQ_IOSCHED_KYBER=y +# CONFIG_IOSCHED_BFQ is not set +# end of IO Schedulers + +CONFIG_PADATA=y +CONFIG_ASN1=y +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_LOCK_SPIN_ON_OWNER=y +CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y +CONFIG_QUEUED_SPINLOCKS=y +# CONFIG_QUEUED_SPINLOCKS_TRACE_CONTENDED_RELEASE is not set +CONFIG_ARCH_USE_QUEUED_RWLOCKS=y +CONFIG_QUEUED_RWLOCKS=y +CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y +CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y +CONFIG_FREEZER=y + +# +# Executable file formats +# +CONFIG_BINFMT_ELF=y +CONFIG_COMPAT_BINFMT_ELF=y +CONFIG_ARCH_BINFMT_ELF_STATE=y +CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y +CONFIG_ARCH_HAVE_ELF_PROT=y +CONFIG_ARCH_USE_GNU_PROPERTY=y +CONFIG_ELFCORE=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +CONFIG_BINFMT_SCRIPT=y +CONFIG_BINFMT_MISC=y +CONFIG_COREDUMP=y +# end of Executable file formats + +# +# Memory Management options +# +CONFIG_SWAP=y +CONFIG_ZSWAP=y +CONFIG_ZSWAP_DEFAULT_ON=y +CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_DEFLATE is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_842 is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4 is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4HC is not set +CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD=y +CONFIG_ZSWAP_COMPRESSOR_DEFAULT="zstd" +CONFIG_ZSMALLOC=y + +# +# Zsmalloc allocator options +# + +# +# Zsmalloc is a common backend allocator for zswap & zram +# +# CONFIG_ZSMALLOC_STAT is not set +CONFIG_ZSMALLOC_CHAIN_SIZE=8 +# end of Zsmalloc allocator options + +# +# Slab allocator options +# +CONFIG_SLUB=y +CONFIG_KVFREE_RCU_BATCHED=y +# CONFIG_SLUB_TINY is not set +CONFIG_SLAB_MERGE_DEFAULT=y +# CONFIG_SLAB_FREELIST_RANDOM is not set +# CONFIG_SLAB_FREELIST_HARDENED is not set +# CONFIG_SLAB_BUCKETS is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_KMALLOC_PARTITION_CACHES is not set +# end of Slab allocator options + +# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set +# CONFIG_COMPAT_BRK is not set +CONFIG_SPARSEMEM=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_HAVE_GUP_FAST=y +CONFIG_ARCH_KEEP_MEMBLOCK=y +CONFIG_MEMORY_ISOLATION=y +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +# CONFIG_MEMORY_HOTPLUG is not set +CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y +CONFIG_SPLIT_PTE_PTLOCKS=y +CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y +CONFIG_SPLIT_PMD_PTLOCKS=y +CONFIG_COMPACTION=y +CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1 +# CONFIG_PAGE_REPORTING is not set +CONFIG_MIGRATION=y +CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y +CONFIG_ARCH_HAS_PMD_SOFTLEAVES=y +CONFIG_CONTIG_ALLOC=y +CONFIG_PCP_BATCH_SCALE_MAX=5 +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_KSM=y +CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 +CONFIG_ARCH_SUPPORTS_MEMORY_FAILURE=y +CONFIG_MEMORY_FAILURE=y +# CONFIG_HWPOISON_INJECT is not set +# CONFIG_PERSISTENT_HUGE_ZERO_FOLIO is not set +CONFIG_MM_ID=y +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y +# CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set +# CONFIG_TRANSPARENT_HUGEPAGE_NEVER is not set +CONFIG_TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER=y +# CONFIG_TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS is not set +# CONFIG_TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE is not set +# CONFIG_TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE is not set +CONFIG_TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER=y +# CONFIG_TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS is not set +# CONFIG_TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE is not set +# CONFIG_TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE is not set +# CONFIG_NO_PAGE_MAPCOUNT is not set +CONFIG_PAGE_MAPCOUNT=y +CONFIG_PGTABLE_HAS_HUGE_LEAVES=y +CONFIG_HAVE_GIGANTIC_FOLIOS=y +CONFIG_ARCH_SUPPORTS_HUGE_PFNMAP=y +CONFIG_ARCH_SUPPORTS_PMD_PFNMAP=y +CONFIG_CMA=y +# CONFIG_CMA_DEBUGFS is not set +# CONFIG_CMA_SYSFS is not set +CONFIG_CMA_AREAS=8 +CONFIG_PAGE_BLOCK_MAX_ORDER=11 +CONFIG_GENERIC_EARLY_IOREMAP=y +# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set +# CONFIG_IDLE_PAGE_TRACKING is not set +CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y +CONFIG_ARCH_HAS_CURRENT_STACK_POINTER=y +CONFIG_ARCH_HAS_ZONE_DMA_SET=y +CONFIG_ZONE_DMA=y +CONFIG_ZONE_DMA32=y +CONFIG_VM_EVENT_COUNTERS=y +# CONFIG_PERCPU_STATS is not set +# CONFIG_GUP_TEST is not set +# CONFIG_DMAPOOL_TEST is not set +CONFIG_ARCH_HAS_PTE_SPECIAL=y +CONFIG_MEMFD_CREATE=y +CONFIG_SECRETMEM=y +# CONFIG_ANON_VMA_NAME is not set +CONFIG_HAVE_ARCH_USERFAULTFD_WP=y +CONFIG_HAVE_ARCH_USERFAULTFD_MINOR=y +CONFIG_USERFAULTFD_RWP=y +CONFIG_USERFAULTFD=y +CONFIG_PTE_MARKER_UFFD_WP=y +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +# CONFIG_LRU_GEN_STATS is not set +CONFIG_LRU_GEN_WALKS_MMU=y +CONFIG_ARCH_SUPPORTS_PER_VMA_LOCK=y +CONFIG_PER_VMA_LOCK=y +CONFIG_LOCK_MM_AND_FIND_VMA=y +CONFIG_EXECMEM=y +CONFIG_PT_RECLAIM=y +CONFIG_ARCH_HAS_LAZY_MMU_MODE=y + +# +# Data Access Monitoring +# +# CONFIG_DAMON is not set +# end of Data Access Monitoring +# end of Memory Management options + +CONFIG_NET=y +CONFIG_NET_INGRESS=y +CONFIG_NET_EGRESS=y +CONFIG_NET_XGRESS=y +CONFIG_SKB_EXTENSIONS=y +CONFIG_NET_DEVMEM=y +CONFIG_NET_CRC32C=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +# CONFIG_INET_PSP is not set +CONFIG_UNIX=y +CONFIG_AF_UNIX_OOB=y +CONFIG_UNIX_DIAG=y +# CONFIG_TLS is not set +# CONFIG_XFRM_USER is not set +# CONFIG_NET_KEY is not set +# CONFIG_DIBS is not set +# CONFIG_XDP_SOCKETS is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +# CONFIG_IP_PNP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE_DEMUX is not set +CONFIG_NET_IP_TUNNEL=y +# CONFIG_IP_MROUTE is not set +CONFIG_SYN_COOKIES=y +# CONFIG_NET_IPVTI is not set +CONFIG_NET_UDP_TUNNEL=y +# CONFIG_NET_FOU is not set +# CONFIG_NET_FOU_IP_TUNNELS is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +CONFIG_INET_TABLE_PERTURB_ORDER=16 +CONFIG_INET_TUNNEL=y +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_INET_UDP_DIAG is not set +# CONFIG_INET_RAW_DIAG is not set +# CONFIG_INET_DIAG_DESTROY is not set +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_AO is not set +# CONFIG_TCP_MD5SIG is not set +CONFIG_IPV6=y +# CONFIG_IPV6_ROUTER_PREF is not set +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_INET6_AH is not set +# CONFIG_INET6_ESP is not set +# CONFIG_INET6_IPCOMP is not set +# CONFIG_IPV6_MIP6 is not set +# CONFIG_IPV6_ILA is not set +# CONFIG_IPV6_VTI is not set +CONFIG_IPV6_SIT=y +# CONFIG_IPV6_SIT_6RD is not set +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_TUNNEL is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +# CONFIG_IPV6_MROUTE is not set +# CONFIG_IPV6_SEG6_LWTUNNEL is not set +# CONFIG_IPV6_SEG6_HMAC is not set +# CONFIG_IPV6_RPL_LWTUNNEL is not set +# CONFIG_IPV6_IOAM6_LWTUNNEL is not set +# CONFIG_NETLABEL is not set +# CONFIG_MPTCP is not set +CONFIG_NETWORK_SECMARK=y +# CONFIG_NETWORK_PHY_TIMESTAMPING is not set +CONFIG_NETFILTER=y +CONFIG_NETFILTER_ADVANCED=y +CONFIG_BRIDGE_NETFILTER=y + +# +# Core Netfilter Configuration +# +CONFIG_NETFILTER_INGRESS=y +CONFIG_NETFILTER_EGRESS=y +CONFIG_NETFILTER_NETLINK=y +CONFIG_NETFILTER_FAMILY_BRIDGE=y +CONFIG_NETFILTER_BPF_LINK=y +# CONFIG_NETFILTER_NETLINK_HOOK is not set +# CONFIG_NETFILTER_NETLINK_ACCT is not set +# CONFIG_NETFILTER_NETLINK_QUEUE is not set +CONFIG_NETFILTER_NETLINK_LOG=y +CONFIG_NETFILTER_NETLINK_OSF=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_LOG_SYSLOG=y +CONFIG_NETFILTER_CONNCOUNT=y +CONFIG_NF_CONNTRACK_MARK=y +# CONFIG_NF_CONNTRACK_SECMARK is not set +# CONFIG_NF_CONNTRACK_ZONES is not set +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_EVENTS=y +# CONFIG_NF_CONNTRACK_TIMEOUT is not set +# CONFIG_NF_CONNTRACK_TIMESTAMP is not set +# CONFIG_NF_CONNTRACK_LABELS is not set +CONFIG_NF_CT_PROTO_SCTP=y +# CONFIG_NF_CONNTRACK_AMANDA is not set +# CONFIG_NF_CONNTRACK_FTP is not set +# CONFIG_NF_CONNTRACK_H323 is not set +# CONFIG_NF_CONNTRACK_IRC is not set +# CONFIG_NF_CONNTRACK_NETBIOS_NS is not set +# CONFIG_NF_CONNTRACK_SNMP is not set +# CONFIG_NF_CONNTRACK_PPTP is not set +# CONFIG_NF_CONNTRACK_SANE is not set +# CONFIG_NF_CONNTRACK_SIP is not set +# CONFIG_NF_CONNTRACK_TFTP is not set +# CONFIG_NF_CT_NETLINK is not set +CONFIG_NF_NAT=y +CONFIG_NF_NAT_REDIRECT=y +CONFIG_NF_NAT_MASQUERADE=y +CONFIG_NETFILTER_SYNPROXY=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=y +CONFIG_NFT_CT=y +# CONFIG_NFT_EXTHDR_DCCP is not set +# CONFIG_NFT_FLOW_OFFLOAD is not set +CONFIG_NFT_CONNLIMIT=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_TUNNEL=y +CONFIG_NFT_QUOTA=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_REJECT_INET=y +CONFIG_NFT_COMPAT=y +CONFIG_NFT_HASH=y +CONFIG_NFT_FIB=y +CONFIG_NFT_FIB_INET=y +CONFIG_NFT_SOCKET=y +CONFIG_NFT_OSF=y +CONFIG_NFT_TPROXY=y +CONFIG_NFT_SYNPROXY=y +CONFIG_NF_DUP_NETDEV=y +CONFIG_NFT_DUP_NETDEV=y +CONFIG_NFT_FWD_NETDEV=y +# CONFIG_NFT_FIB_NETDEV is not set +CONFIG_NFT_REJECT_NETDEV=y +# CONFIG_NF_FLOW_TABLE_INET is not set +CONFIG_NF_FLOW_TABLE=y +CONFIG_NF_FLOW_TABLE_PROCFS=y +CONFIG_NETFILTER_XTABLES=y +CONFIG_NETFILTER_XTABLES_COMPAT=y +# CONFIG_NETFILTER_XTABLES_LEGACY is not set + +# +# Xtables combined modules +# +CONFIG_NETFILTER_XT_MARK=y +CONFIG_NETFILTER_XT_CONNMARK=y + +# +# Xtables targets +# +# CONFIG_NETFILTER_XT_TARGET_AUDIT is not set +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y +# CONFIG_NETFILTER_XT_TARGET_CLASSIFY is not set +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +# CONFIG_NETFILTER_XT_TARGET_CT is not set +# CONFIG_NETFILTER_XT_TARGET_DSCP is not set +# CONFIG_NETFILTER_XT_TARGET_HL is not set +# CONFIG_NETFILTER_XT_TARGET_HMARK is not set +# CONFIG_NETFILTER_XT_TARGET_IDLETIMER is not set +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +# CONFIG_NETFILTER_XT_TARGET_NETMAP is not set +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +# CONFIG_NETFILTER_XT_TARGET_NFQUEUE is not set +# CONFIG_NETFILTER_XT_TARGET_RATEEST is not set +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +# CONFIG_NETFILTER_XT_TARGET_TEE is not set +# CONFIG_NETFILTER_XT_TARGET_TPROXY is not set +# CONFIG_NETFILTER_XT_TARGET_SECMARK is not set +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +# CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP is not set + +# +# Xtables matches +# +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +# CONFIG_NETFILTER_XT_MATCH_BPF is not set +# CONFIG_NETFILTER_XT_MATCH_CGROUP is not set +# CONFIG_NETFILTER_XT_MATCH_CLUSTER is not set +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +# CONFIG_NETFILTER_XT_MATCH_CONNBYTES is not set +# CONFIG_NETFILTER_XT_MATCH_CONNLABEL is not set +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +# CONFIG_NETFILTER_XT_MATCH_CPU is not set +# CONFIG_NETFILTER_XT_MATCH_DCCP is not set +# CONFIG_NETFILTER_XT_MATCH_DEVGROUP is not set +# CONFIG_NETFILTER_XT_MATCH_DSCP is not set +# CONFIG_NETFILTER_XT_MATCH_ECN is not set +# CONFIG_NETFILTER_XT_MATCH_ESP is not set +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +# CONFIG_NETFILTER_XT_MATCH_HELPER is not set +CONFIG_NETFILTER_XT_MATCH_HL=y +# CONFIG_NETFILTER_XT_MATCH_IPCOMP is not set +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +# CONFIG_NETFILTER_XT_MATCH_L2TP is not set +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +# CONFIG_NETFILTER_XT_MATCH_NFACCT is not set +# CONFIG_NETFILTER_XT_MATCH_OSF is not set +CONFIG_NETFILTER_XT_MATCH_OWNER=y +# CONFIG_NETFILTER_XT_MATCH_PHYSDEV is not set +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +# CONFIG_NETFILTER_XT_MATCH_QUOTA is not set +# CONFIG_NETFILTER_XT_MATCH_RATEEST is not set +# CONFIG_NETFILTER_XT_MATCH_REALM is not set +CONFIG_NETFILTER_XT_MATCH_RECENT=y +# CONFIG_NETFILTER_XT_MATCH_SCTP is not set +# CONFIG_NETFILTER_XT_MATCH_SOCKET is not set +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +# CONFIG_NETFILTER_XT_MATCH_STRING is not set +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +# CONFIG_NETFILTER_XT_MATCH_TIME is not set +CONFIG_NETFILTER_XT_MATCH_U32=y +# end of Core Netfilter Configuration + +# CONFIG_IP_SET is not set +# CONFIG_IP_VS is not set + +# +# IP: Netfilter Configuration +# +CONFIG_NF_DEFRAG_IPV4=y +CONFIG_NF_SOCKET_IPV4=y +CONFIG_NF_TPROXY_IPV4=y +CONFIG_NF_TABLES_IPV4=y +CONFIG_NFT_REJECT_IPV4=y +# CONFIG_NFT_DUP_IPV4 is not set +CONFIG_NFT_FIB_IPV4=y +# CONFIG_NF_TABLES_ARP is not set +# CONFIG_NF_DUP_IPV4 is not set +# CONFIG_NF_LOG_ARP is not set +CONFIG_NF_LOG_IPV4=y +CONFIG_NF_REJECT_IPV4=y +CONFIG_IP_NF_IPTABLES=y +# CONFIG_IP_NF_MATCH_AH is not set +# CONFIG_IP_NF_MATCH_ECN is not set +# CONFIG_IP_NF_MATCH_RPFILTER is not set +# CONFIG_IP_NF_MATCH_TTL is not set +CONFIG_IP_NF_TARGET_REJECT=y +# CONFIG_IP_NF_TARGET_SYNPROXY is not set +# CONFIG_IP_NF_TARGET_ECN is not set +# end of IP: Netfilter Configuration + +# +# IPv6: Netfilter Configuration +# +CONFIG_NF_SOCKET_IPV6=y +CONFIG_NF_TPROXY_IPV6=y +CONFIG_NF_TABLES_IPV6=y +CONFIG_NFT_REJECT_IPV6=y +# CONFIG_NFT_DUP_IPV6 is not set +CONFIG_NFT_FIB_IPV6=y +# CONFIG_NF_DUP_IPV6 is not set +CONFIG_NF_REJECT_IPV6=y +CONFIG_NF_LOG_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +# CONFIG_IP6_NF_MATCH_AH is not set +# CONFIG_IP6_NF_MATCH_EUI64 is not set +# CONFIG_IP6_NF_MATCH_FRAG is not set +# CONFIG_IP6_NF_MATCH_OPTS is not set +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +# CONFIG_IP6_NF_MATCH_MH is not set +# CONFIG_IP6_NF_MATCH_RPFILTER is not set +CONFIG_IP6_NF_MATCH_RT=y +# CONFIG_IP6_NF_MATCH_SRH is not set +CONFIG_IP6_NF_TARGET_REJECT=y +# CONFIG_IP6_NF_TARGET_SYNPROXY is not set +# CONFIG_IP6_NF_TARGET_NPT is not set +# end of IPv6: Netfilter Configuration + +CONFIG_NF_DEFRAG_IPV6=y +# CONFIG_NF_TABLES_BRIDGE is not set +# CONFIG_NF_CONNTRACK_BRIDGE is not set +# CONFIG_BRIDGE_NF_EBTABLES is not set +# CONFIG_IP_SCTP is not set +# CONFIG_RDS is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_L2TP is not set +CONFIG_STP=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_IGMP_SNOOPING=y +# CONFIG_BRIDGE_MRP is not set +# CONFIG_BRIDGE_CFM is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +CONFIG_LLC=y +# CONFIG_LLC2 is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_PHONET is not set +# CONFIG_6LOWPAN is not set +# CONFIG_IEEE802154 is not set +CONFIG_NET_SCHED=y + +# +# Queueing/Scheduling +# +# CONFIG_NET_SCH_HTB is not set +# CONFIG_NET_SCH_HFSC is not set +# CONFIG_NET_SCH_PRIO is not set +# CONFIG_NET_SCH_MULTIQ is not set +# CONFIG_NET_SCH_RED is not set +# CONFIG_NET_SCH_SFB is not set +# CONFIG_NET_SCH_SFQ is not set +# CONFIG_NET_SCH_TEQL is not set +# CONFIG_NET_SCH_TBF is not set +# CONFIG_NET_SCH_CBS is not set +# CONFIG_NET_SCH_ETF is not set +# CONFIG_NET_SCH_TAPRIO is not set +# CONFIG_NET_SCH_GRED is not set +# CONFIG_NET_SCH_NETEM is not set +# CONFIG_NET_SCH_DRR is not set +# CONFIG_NET_SCH_MQPRIO is not set +# CONFIG_NET_SCH_SKBPRIO is not set +# CONFIG_NET_SCH_CHOKE is not set +# CONFIG_NET_SCH_QFQ is not set +# CONFIG_NET_SCH_CODEL is not set +CONFIG_NET_SCH_FQ_CODEL=y +# CONFIG_NET_SCH_CAKE is not set +CONFIG_NET_SCH_FQ=y +# CONFIG_NET_SCH_HHF is not set +# CONFIG_NET_SCH_PIE is not set +# CONFIG_NET_SCH_PLUG is not set +# CONFIG_NET_SCH_ETS is not set +# CONFIG_NET_SCH_DUALPI2 is not set +# CONFIG_NET_SCH_DEFAULT is not set + +# +# Classification +# +# CONFIG_NET_CLS_BASIC is not set +# CONFIG_NET_CLS_ROUTE4 is not set +# CONFIG_NET_CLS_FW is not set +# CONFIG_NET_CLS_U32 is not set +# CONFIG_NET_CLS_FLOW is not set +# CONFIG_NET_CLS_CGROUP is not set +# CONFIG_NET_CLS_BPF is not set +# CONFIG_NET_CLS_FLOWER is not set +# CONFIG_NET_CLS_MATCHALL is not set +# CONFIG_NET_EMATCH is not set +# CONFIG_NET_CLS_ACT is not set +CONFIG_NET_SCH_FIFO=y +# CONFIG_DCB is not set +CONFIG_DNS_RESOLVER=y +# CONFIG_BATMAN_ADV is not set +# CONFIG_OPENVSWITCH is not set +# CONFIG_VSOCKETS is not set +CONFIG_NETLINK_DIAG=y +# CONFIG_MPLS is not set +# CONFIG_NET_NSH is not set +# CONFIG_HSR is not set +# CONFIG_NET_SWITCHDEV is not set +CONFIG_NET_L3_MASTER_DEV=y +# CONFIG_QRTR is not set +# CONFIG_NET_NCSI is not set +CONFIG_PCPU_DEV_REFCNT=y +CONFIG_MAX_SKB_FRAGS=17 +CONFIG_RPS=y +CONFIG_RFS_ACCEL=y +CONFIG_SOCK_RX_QUEUE_MAPPING=y +CONFIG_XPS=y +# CONFIG_CGROUP_NET_PRIO is not set +# CONFIG_CGROUP_NET_CLASSID is not set +CONFIG_NET_RX_BUSY_POLL=y +CONFIG_BQL=y +# CONFIG_BPF_STREAM_PARSER is not set +CONFIG_NET_FLOW_LIMIT=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_NET_DROP_MONITOR is not set +# end of Network testing +# end of Networking options + +# CONFIG_CAN is not set +CONFIG_BT=y +CONFIG_BT_BREDR=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_LE=y +CONFIG_BT_LE_L2CAP_ECRED=y +# CONFIG_BT_LEDS is not set +CONFIG_BT_MSFTEXT=y +CONFIG_BT_AOSPEXT=y +CONFIG_BT_DEBUGFS=y +# CONFIG_BT_SELFTEST is not set + +# +# Bluetooth device drivers +# +CONFIG_BT_BCM=m +# CONFIG_BT_HCIBTUSB is not set +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_SERDEV=y +CONFIG_BT_HCIUART_H4=y +# CONFIG_BT_HCIUART_NOKIA is not set +# CONFIG_BT_HCIUART_BCSP is not set +# CONFIG_BT_HCIUART_ATH3K is not set +# CONFIG_BT_HCIUART_LL is not set +# CONFIG_BT_HCIUART_3WIRE is not set +# CONFIG_BT_HCIUART_INTEL is not set +CONFIG_BT_HCIUART_BCM=y +# CONFIG_BT_HCIUART_RTL is not set +# CONFIG_BT_HCIUART_QCA is not set +# CONFIG_BT_HCIUART_AG6XX is not set +# CONFIG_BT_HCIUART_MRVL is not set +# CONFIG_BT_HCIUART_AML is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBCM4377 is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set +# CONFIG_BT_MRVL is not set +# CONFIG_BT_MTKUART is not set +# CONFIG_BT_NXPUART is not set +# end of Bluetooth device drivers + +# CONFIG_AF_RXRPC is not set +# CONFIG_AF_KCM is not set +# CONFIG_MCTP is not set +CONFIG_WIRELESS=y +CONFIG_CFG80211=y +# CONFIG_NL80211_TESTMODE is not set +# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set +# CONFIG_CFG80211_CERTIFICATION_ONUS is not set +CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y +CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS=y +CONFIG_CFG80211_DEFAULT_PS=y +# CONFIG_CFG80211_DEBUGFS is not set +CONFIG_CFG80211_CRDA_SUPPORT=y +# CONFIG_CFG80211_WEXT is not set +# CONFIG_MAC80211 is not set +CONFIG_MAC80211_STA_HASH_MAX_SIZE=0 +CONFIG_RFKILL=y +# CONFIG_RFKILL_INPUT is not set +# CONFIG_RFKILL_GPIO is not set +# CONFIG_NET_9P is not set +# CONFIG_CEPH_LIB is not set +# CONFIG_NFC is not set +# CONFIG_PSAMPLE is not set +# CONFIG_NET_IFE is not set +# CONFIG_LWTUNNEL is not set +CONFIG_DST_CACHE=y +CONFIG_GRO_CELLS=y +CONFIG_NET_SOCK_MSG=y +CONFIG_PAGE_POOL=y +# CONFIG_PAGE_POOL_STATS is not set +# CONFIG_FAILOVER is not set +# CONFIG_ETHTOOL_NETLINK is not set + +# +# Device Drivers +# +CONFIG_ARM_AMBA=y +CONFIG_HAVE_PCI=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_PCI=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_DOMAINS_GENERIC=y +CONFIG_PCI_SYSCALL=y +# CONFIG_PCIEPORTBUS is not set +CONFIG_PCIEASPM=y +CONFIG_PCIEASPM_DEFAULT=y +# CONFIG_PCIEASPM_POWERSAVE is not set +# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set +# CONFIG_PCIEASPM_PERFORMANCE is not set +# CONFIG_PCIE_PTM is not set +CONFIG_PCI_MSI=y +CONFIG_PCI_QUIRKS=y +# CONFIG_PCI_DEBUG is not set +# CONFIG_PCI_STUB is not set +# CONFIG_PCI_TSM is not set +# CONFIG_PCI_DOE is not set +CONFIG_PCI_ECAM=y +# CONFIG_PCI_IOV is not set +# CONFIG_PCI_NPEM is not set +# CONFIG_PCI_PRI is not set +# CONFIG_PCI_PASID is not set +# CONFIG_PCIE_TPH is not set +CONFIG_PCI_LABEL=y +# CONFIG_PCI_DYNAMIC_OF_NODES is not set +CONFIG_VGA_ARB=y +CONFIG_VGA_ARB_MAX_GPUS=16 +# CONFIG_HOTPLUG_PCI is not set + +# +# PCI controller drivers +# +CONFIG_PCI_HOST_COMMON=y +# CONFIG_PCIE_ALTERA is not set +CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR=0xfffff000 +CONFIG_PCIE_APPLE=y +CONFIG_PCIE_APPLE_H9P=y +# CONFIG_PCI_HOST_THUNDER_PEM is not set +# CONFIG_PCI_HOST_THUNDER_ECAM is not set +# CONFIG_PCI_FTPCI100 is not set +# CONFIG_PCI_HOST_GENERIC is not set +# CONFIG_PCI_XGENE is not set +# CONFIG_PCIE_XILINX is not set + +# +# Cadence-based PCIe controllers +# +# CONFIG_PCIE_CADENCE_PLAT_HOST is not set +# end of Cadence-based PCIe controllers + +# +# DesignWare-based PCIe controllers +# +# CONFIG_PCIE_AL is not set +# CONFIG_PCIE_AMD_MDB is not set +# CONFIG_PCI_MESON is not set +# CONFIG_PCI_HISI is not set +# CONFIG_PCIE_KIRIN is not set +# CONFIG_PCIE_DW_PLAT_HOST is not set +# end of DesignWare-based PCIe controllers + +# +# Mobiveil-based PCIe controllers +# +# end of Mobiveil-based PCIe controllers + +# +# PLDA-based PCIe controllers +# +# CONFIG_PCIE_MICROCHIP_HOST is not set +# end of PLDA-based PCIe controllers +# end of PCI controller drivers + +# +# PCI Endpoint +# +# CONFIG_PCI_ENDPOINT is not set +# end of PCI Endpoint + +# +# PCI switch controller drivers +# +# CONFIG_PCI_SW_SWITCHTEC is not set +# end of PCI switch controller drivers + +# CONFIG_PCI_PWRCTRL_GENERIC is not set +# CONFIG_PCI_PWRCTRL_TC9563 is not set +# CONFIG_CXL_BUS is not set +# CONFIG_PCCARD is not set +# CONFIG_RAPIDIO is not set +# CONFIG_PC104 is not set + +# +# Generic Driver Options +# +# CONFIG_UEVENT_HELPER is not set +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DEVTMPFS_SAFE is not set +CONFIG_DRIVER_DEFERRED_PROBE_TIMEOUT=10 +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y + +# +# Firmware loader +# +CONFIG_FW_LOADER=y +CONFIG_FW_LOADER_DEBUG=y +CONFIG_FW_LOADER_PAGED_BUF=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_FW_LOADER_USER_HELPER is not set +CONFIG_FW_LOADER_COMPRESS=y +CONFIG_FW_LOADER_COMPRESS_XZ=y +CONFIG_FW_LOADER_COMPRESS_ZSTD=y +CONFIG_FW_CACHE=y +# CONFIG_FW_UPLOAD is not set +# end of Firmware loader + +CONFIG_WANT_DEV_COREDUMP=y +CONFIG_ALLOW_DEV_COREDUMP=y +CONFIG_DEV_COREDUMP=y +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_DEVRES is not set +# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set +# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set +CONFIG_GENERIC_CPU_DEVICES=y +CONFIG_GENERIC_CPU_AUTOPROBE=y +CONFIG_GENERIC_CPU_VULNERABILITIES=y +CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y +CONFIG_REGMAP_SPI=y +CONFIG_REGMAP_SPMI=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGMAP_IRQ=y +CONFIG_DMA_SHARED_BUFFER=y +CONFIG_GENERIC_ARCH_TOPOLOGY=y +# CONFIG_FW_DEVLINK_SYNC_STATE_TIMEOUT is not set +# end of Generic Driver Options + +# +# Bus devices +# +CONFIG_APPLE_USBCOMPLEX=y +# CONFIG_MOXTET is not set +# CONFIG_VEXPRESS_CONFIG is not set +# CONFIG_MHI_BUS is not set +# CONFIG_MHI_BUS_EP is not set +# end of Bus devices + +# CONFIG_CACHEMAINT_FOR_HOTPLUG is not set +# CONFIG_CONNECTOR is not set + +# +# Firmware Drivers +# + +# +# ARM System Control and Management Interface Protocol +# +# CONFIG_ARM_SCMI_PROTOCOL is not set +# end of ARM System Control and Management Interface Protocol + +# CONFIG_ARM_SDE_INTERFACE is not set +# CONFIG_FIRMWARE_MEMMAP is not set +CONFIG_DMIID=y +# CONFIG_DMI_SYSFS is not set +# CONFIG_FW_CFG_SYSFS is not set +# CONFIG_SYSFB_SIMPLEFB is not set +# CONFIG_ARM_FFA_TRANSPORT is not set +# CONFIG_GOOGLE_FIRMWARE is not set + +# +# EFI (Extensible Firmware Interface) Support +# +CONFIG_EFI_ESRT=y +CONFIG_EFI_VARS_PSTORE=y +# CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE is not set +CONFIG_EFI_PARAMS_FROM_FDT=y +CONFIG_EFI_RUNTIME_WRAPPERS=y +CONFIG_EFI_GENERIC_STUB=y +CONFIG_EFI_ZBOOT=y +CONFIG_EFI_ARMSTUB_DTB_LOADER=y +# CONFIG_EFI_BOOTLOADER_CONTROL is not set +# CONFIG_EFI_CAPSULE_LOADER is not set +# CONFIG_EFI_TEST is not set +# CONFIG_RESET_ATTACK_MITIGATION is not set +# CONFIG_EFI_DISABLE_PCI_DMA is not set +CONFIG_EFI_EARLYCON=y +# CONFIG_EFI_DISABLE_RUNTIME is not set +# CONFIG_EFI_COCO_SECRET is not set +# CONFIG_OVMF_DEBUG_LOG is not set +CONFIG_EFI_SBAT_FILE="" +# end of EFI (Extensible Firmware Interface) Support + +CONFIG_ARM_PSCI_FW=y +# CONFIG_ARM_PSCI_CHECKER is not set + +# +# Qualcomm firmware drivers +# +# CONFIG_QCOM_PAS is not set +# CONFIG_QCOM_SCM is not set +# end of Qualcomm firmware drivers + +CONFIG_HAVE_ARM_SMCCC=y +CONFIG_HAVE_ARM_SMCCC_DISCOVERY=y +# CONFIG_ARM_SMCCC_SOC_ID is not set + +# +# Tegra firmware driver +# +# end of Tegra firmware driver +# end of Firmware Drivers + +# CONFIG_FWCTL is not set +# CONFIG_GNSS is not set +CONFIG_MTD=y +# CONFIG_MTD_TESTS is not set + +# +# Partition parsers +# +# CONFIG_MTD_CMDLINE_PARTS is not set +CONFIG_MTD_OF_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_REDBOOT_PARTS is not set +# end of Partition parsers + +# +# User Modules And Translation Layers +# +# CONFIG_MTD_BLOCK is not set +# CONFIG_MTD_BLOCK_RO is not set +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_SM_FTL is not set +# CONFIG_MTD_OOPS is not set +# CONFIG_MTD_SWAP is not set +# CONFIG_MTD_PARTITIONED_MASTER is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# end of RAM/ROM/Flash chip drivers + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set +# end of Mapping drivers for chip access + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_PMC551 is not set +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_MCHP23K256 is not set +# CONFIG_MTD_MCHP48L640 is not set +# CONFIG_MTD_SST25L is not set +# CONFIG_MTD_SLRAM is not set +CONFIG_MTD_PHRAM=y +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOCG3 is not set +# end of Self-contained MTD device drivers + +# +# NAND +# +# CONFIG_MTD_ONENAND is not set +# CONFIG_MTD_RAW_NAND is not set +# CONFIG_MTD_SPI_NAND is not set + +# +# ECC engine support +# +# CONFIG_MTD_NAND_ECC_SW_HAMMING is not set +# CONFIG_MTD_NAND_ECC_SW_BCH is not set +# CONFIG_MTD_NAND_ECC_MXIC is not set +# end of ECC engine support +# end of NAND + +# +# LPDDR & LPDDR2 PCM memory drivers +# +# CONFIG_MTD_LPDDR is not set +# end of LPDDR & LPDDR2 PCM memory drivers + +CONFIG_MTD_SPI_NOR=y +CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y +# CONFIG_MTD_SPI_NOR_SWP_DISABLE is not set +CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE=y +# CONFIG_MTD_SPI_NOR_SWP_KEEP is not set +# CONFIG_MTD_UBI is not set +# CONFIG_MTD_HYPERBUS is not set +CONFIG_DTC=y +CONFIG_OF=y +# CONFIG_OF_UNITTEST is not set +CONFIG_OF_FLATTREE=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_KOBJ=y +CONFIG_OF_DYNAMIC=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_IRQ=y +CONFIG_OF_RESERVED_MEM=y +CONFIG_OF_RESOLVE=y +CONFIG_OF_OVERLAY=y +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_NULL_BLK is not set +# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set +CONFIG_ZRAM=y +# CONFIG_ZRAM_BACKEND_LZ4 is not set +# CONFIG_ZRAM_BACKEND_LZ4HC is not set +CONFIG_ZRAM_BACKEND_ZSTD=y +# CONFIG_ZRAM_BACKEND_DEFLATE is not set +# CONFIG_ZRAM_BACKEND_842 is not set +# CONFIG_ZRAM_BACKEND_LZO is not set +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_DEF_COMP="zstd" +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_TRACK_ENTRY_ACTIME=y +CONFIG_ZRAM_MEMORY_TRACKING=y +# CONFIG_ZRAM_MULTI_COMP is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 +# CONFIG_BLK_DEV_DRBD is not set +CONFIG_BLK_DEV_NBD=y +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_BLK_DEV_RBD is not set +# CONFIG_BLK_DEV_UBLK is not set + +# +# NVME Support +# +CONFIG_NVME_CORE=y +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_APPLE_ANS_EARLY_USB=y +CONFIG_NVME_PCI_READ_ONLY=y +CONFIG_NVME_PCI_WRITE_WINDOW=y +# CONFIG_NVME_MULTIPATH is not set +# CONFIG_NVME_VERBOSE_ERRORS is not set +CONFIG_NVME_HWMON=y +# CONFIG_NVME_FC is not set +# CONFIG_NVME_TCP is not set +# CONFIG_NVME_HOST_AUTH is not set +CONFIG_NVME_APPLE=y +# CONFIG_NVME_TARGET is not set +# end of NVME Support + +# +# Misc devices +# +# CONFIG_AD525X_DPOT is not set +# CONFIG_DUMMY_IRQ is not set +# CONFIG_PHANTOM is not set +# CONFIG_RPMB is not set +# CONFIG_TI_FPC202 is not set +# CONFIG_TIFM_CORE is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_HI6421V600_IRQ is not set +# CONFIG_HP_ILO is not set +# CONFIG_APDS9802ALS is not set +# CONFIG_ISL29003 is not set +# CONFIG_ISL29020 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_SENSORS_BH1770 is not set +# CONFIG_HMC6352 is not set +# CONFIG_DS1682 is not set +# CONFIG_LATTICE_ECP3_CONFIG is not set +# CONFIG_SRAM is not set +# CONFIG_DW_XDATA_PCIE is not set +# CONFIG_PCI_ENDPOINT_TEST is not set +# CONFIG_XILINX_SDFEC is not set +# CONFIG_OPEN_DICE is not set +# CONFIG_NTSYNC is not set +# CONFIG_VCPU_STALL_DETECTOR is not set +# CONFIG_MCHP_LAN966X_PCI is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +# CONFIG_EEPROM_MAX6875 is not set +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_EEPROM_IDT_89HPESX is not set +# end of EEPROM support + +# CONFIG_CB710_CORE is not set +# CONFIG_SENSORS_LIS3_SPI is not set +# CONFIG_SENSORS_LIS3_I2C is not set +# CONFIG_ALTERA_STAPL is not set +# CONFIG_VMWARE_VMCI is not set +# CONFIG_GENWQE is not set +# CONFIG_BCM_VK is not set +# CONFIG_MISC_ALCOR_PCI is not set +# CONFIG_MISC_RTSX_PCI is not set +# CONFIG_MISC_RTSX_USB is not set +# CONFIG_UACCE is not set +# CONFIG_PVPANIC is not set +# CONFIG_GP_PCI1XXXX is not set +# CONFIG_KEBA_CP500 is not set +# CONFIG_AMD_SBRMI_I2C is not set +# CONFIG_AMD_SBTSI is not set +# CONFIG_MISC_RP1 is not set +# CONFIG_INTEL_SSEI is not set +# end of Misc devices + +# +# SCSI device support +# +CONFIG_SCSI_MOD=y +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI_COMMON=y +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +CONFIG_BLK_DEV_BSG=y +# CONFIG_CHR_DEV_SCH is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# end of SCSI Transports + +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_ISCSI_BOOT_SYSFS is not set +# CONFIG_SCSI_CXGB3_ISCSI is not set +# CONFIG_SCSI_BNX2_ISCSI is not set +# CONFIG_BE2ISCSI is not set +# CONFIG_BLK_DEV_3W_XXXX_RAID is not set +# CONFIG_SCSI_HPSA is not set +# CONFIG_SCSI_3W_9XXX is not set +# CONFIG_SCSI_3W_SAS is not set +# CONFIG_SCSI_ACARD is not set +# CONFIG_SCSI_AACRAID is not set +# CONFIG_SCSI_AIC7XXX is not set +# CONFIG_SCSI_AIC79XX is not set +# CONFIG_SCSI_AIC94XX is not set +# CONFIG_SCSI_MVSAS is not set +# CONFIG_SCSI_MVUMI is not set +# CONFIG_SCSI_ADVANSYS is not set +# CONFIG_SCSI_ARCMSR is not set +# CONFIG_SCSI_ESAS2R is not set +# CONFIG_MEGARAID_NEWGEN is not set +# CONFIG_MEGARAID_LEGACY is not set +# CONFIG_MEGARAID_SAS is not set +# CONFIG_SCSI_MPT3SAS is not set +# CONFIG_SCSI_MPT2SAS is not set +# CONFIG_SCSI_MPI3MR is not set +# CONFIG_SCSI_LEAPRAID is not set +# CONFIG_SCSI_SMARTPQI is not set +# CONFIG_SCSI_HPTIOP is not set +# CONFIG_SCSI_BUSLOGIC is not set +# CONFIG_SCSI_MYRB is not set +# CONFIG_SCSI_MYRS is not set +# CONFIG_SCSI_SNIC is not set +# CONFIG_SCSI_DMX3191D is not set +# CONFIG_SCSI_FDOMAIN_PCI is not set +# CONFIG_SCSI_IPS is not set +# CONFIG_SCSI_INITIO is not set +# CONFIG_SCSI_INIA100 is not set +# CONFIG_SCSI_STEX is not set +# CONFIG_SCSI_SYM53C8XX_2 is not set +# CONFIG_SCSI_IPR is not set +# CONFIG_SCSI_QLOGIC_1280 is not set +# CONFIG_SCSI_QLA_ISCSI is not set +# CONFIG_SCSI_DC395x is not set +# CONFIG_SCSI_AM53C974 is not set +# CONFIG_SCSI_WD719X is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_PMCRAID is not set +# CONFIG_SCSI_PM8001 is not set +# CONFIG_SCSI_DH is not set +# end of SCSI device support + +# CONFIG_ATA is not set +CONFIG_MD=y +# CONFIG_BLK_DEV_MD is not set +# CONFIG_BCACHE is not set +CONFIG_BLK_DEV_DM_BUILTIN=y +CONFIG_BLK_DEV_DM=y +# CONFIG_DM_DEBUG is not set +# CONFIG_DM_UNSTRIPED is not set +CONFIG_DM_CRYPT=y +# CONFIG_DM_INLINECRYPT is not set +# CONFIG_DM_SNAPSHOT is not set +# CONFIG_DM_THIN_PROVISIONING is not set +# CONFIG_DM_CACHE is not set +# CONFIG_DM_WRITECACHE is not set +# CONFIG_DM_EBS is not set +# CONFIG_DM_ERA is not set +# CONFIG_DM_CLONE is not set +# CONFIG_DM_MIRROR is not set +# CONFIG_DM_RAID is not set +# CONFIG_DM_ZERO is not set +# CONFIG_DM_MULTIPATH is not set +# CONFIG_DM_DELAY is not set +# CONFIG_DM_DUST is not set +# CONFIG_DM_INIT is not set +# CONFIG_DM_UEVENT is not set +# CONFIG_DM_FLAKEY is not set +# CONFIG_DM_VERITY is not set +# CONFIG_DM_SWITCH is not set +# CONFIG_DM_LOG_WRITES is not set +# CONFIG_DM_INTEGRITY is not set +# CONFIG_DM_AUDIT is not set +# CONFIG_DM_VDO is not set +# CONFIG_TARGET_CORE is not set +# CONFIG_FUSION is not set + +# +# IEEE 1394 (FireWire) support +# +# CONFIG_FIREWIRE is not set +# CONFIG_FIREWIRE_NOSY is not set +# end of IEEE 1394 (FireWire) support + +CONFIG_NETDEVICES=y +CONFIG_NET_CORE=y +# CONFIG_BONDING is not set +CONFIG_DUMMY=y +# CONFIG_WIREGUARD is not set +# CONFIG_OVPN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_NET_FC is not set +# CONFIG_IFB is not set +# CONFIG_NET_TEAM is not set +CONFIG_MACVLAN=y +# CONFIG_MACVTAP is not set +CONFIG_IPVLAN_L3S=y +CONFIG_IPVLAN=y +# CONFIG_IPVTAP is not set +CONFIG_VXLAN=y +# CONFIG_GENEVE is not set +# CONFIG_BAREUDP is not set +# CONFIG_GTP is not set +# CONFIG_PFCP is not set +# CONFIG_AMT is not set +# CONFIG_MACSEC is not set +CONFIG_NETCONSOLE=y +CONFIG_NETCONSOLE_DYNAMIC=y +# CONFIG_NETCONSOLE_EXTENDED_LOG is not set +CONFIG_NETPOLL=y +CONFIG_NET_POLL_CONTROLLER=y +CONFIG_TUN=y +# CONFIG_TUN_VNET_CROSS_LE is not set +CONFIG_VETH=y +# CONFIG_NLMON is not set +# CONFIG_NETKIT is not set +# CONFIG_ARCNET is not set +# CONFIG_ETHERNET is not set +# CONFIG_FDDI is not set +# CONFIG_PHYLIB is not set +# CONFIG_PSE_CONTROLLER is not set + +# +# PCS device drivers +# +# CONFIG_PCS_XPCS is not set +# end of PCS device drivers + +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_USB_NET_DRIVERS is not set +CONFIG_WLAN=y +CONFIG_WLAN_VENDOR_ADMTEK=y +CONFIG_WLAN_VENDOR_ATH=y +# CONFIG_ATH_DEBUG is not set +# CONFIG_ATH5K_PCI is not set +# CONFIG_ATH6KL is not set +# CONFIG_WIL6210 is not set +CONFIG_WLAN_VENDOR_ATMEL=y +CONFIG_WLAN_VENDOR_BROADCOM=y +CONFIG_BRCMUTIL=y +CONFIG_BRCMFMAC=y +CONFIG_BRCMFMAC_PROTO_MSGBUF=y +# CONFIG_BRCMFMAC_USB is not set +CONFIG_BRCMFMAC_PCIE=y +# CONFIG_BRCM_TRACING is not set +CONFIG_BRCMDBG=y +CONFIG_WLAN_VENDOR_INTEL=y +# CONFIG_IPW2100 is not set +# CONFIG_IPW2200 is not set +# CONFIG_IWLWIFI is not set +CONFIG_WLAN_VENDOR_INTERSIL=y +CONFIG_WLAN_VENDOR_MARVELL=y +# CONFIG_LIBERTAS is not set +# CONFIG_MWIFIEX is not set +CONFIG_WLAN_VENDOR_MEDIATEK=y +CONFIG_WLAN_VENDOR_MICROCHIP=y +# CONFIG_WILC1000_SPI is not set +CONFIG_WLAN_VENDOR_MORSEMICRO=y +CONFIG_WLAN_VENDOR_NXP=y +CONFIG_WLAN_VENDOR_PURELIFI=y +CONFIG_WLAN_VENDOR_RALINK=y +CONFIG_WLAN_VENDOR_REALTEK=y +CONFIG_WLAN_VENDOR_RSI=y +CONFIG_WLAN_VENDOR_SILABS=y +CONFIG_WLAN_VENDOR_ST=y +CONFIG_WLAN_VENDOR_TI=y +CONFIG_WLAN_VENDOR_ZYDAS=y +CONFIG_WLAN_VENDOR_QUANTENNA=y +# CONFIG_QTNFMAC_PCIE is not set +# CONFIG_VIRT_WIFI is not set +# CONFIG_WAN is not set + +# +# Wireless WAN +# +CONFIG_WWAN=y +CONFIG_WWAN_DEBUGFS=y +# CONFIG_WWAN_HWSIM is not set +# CONFIG_IOSM is not set +# CONFIG_MTK_T7XX is not set +# end of Wireless WAN + +# CONFIG_VMXNET3 is not set +# CONFIG_NETDEVSIM is not set +# CONFIG_NET_FAILOVER is not set + +# +# Input device support +# +CONFIG_INPUT=y +CONFIG_INPUT_LEDS=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_SPARSEKMAP is not set +# CONFIG_INPUT_MATRIXKMAP is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ADC is not set +# CONFIG_KEYBOARD_ADP5588 is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_QT1050 is not set +# CONFIG_KEYBOARD_QT1070 is not set +# CONFIG_KEYBOARD_QT2160 is not set +# CONFIG_KEYBOARD_DLINK_DIR685 is not set +# CONFIG_KEYBOARD_LKKBD is not set +CONFIG_KEYBOARD_GPIO=y +# CONFIG_KEYBOARD_GPIO_POLLED is not set +# CONFIG_KEYBOARD_TCA8418 is not set +# CONFIG_KEYBOARD_MATRIX is not set +# CONFIG_KEYBOARD_CHARLIEPLEX is not set +# CONFIG_KEYBOARD_LM8323 is not set +# CONFIG_KEYBOARD_LM8333 is not set +# CONFIG_KEYBOARD_MAX7359 is not set +# CONFIG_KEYBOARD_MPR121 is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_OPENCORES is not set +# CONFIG_KEYBOARD_PINEPHONE is not set +# CONFIG_KEYBOARD_SAMSUNG is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_OMAP4 is not set +# CONFIG_KEYBOARD_TM2_TOUCHKEY is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_CAP11XX is not set +# CONFIG_KEYBOARD_BCM is not set +# CONFIG_KEYBOARD_CYPRESS_SF is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_AD7877 is not set +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_ADC is not set +CONFIG_TOUCHSCREEN_APPLE_N1=m +# CONFIG_TOUCHSCREEN_APPLE_Z2 is not set +# CONFIG_TOUCHSCREEN_AR1021_I2C is not set +# CONFIG_TOUCHSCREEN_ATMEL_MXT is not set +# CONFIG_TOUCHSCREEN_AUO_PIXCIR is not set +# CONFIG_TOUCHSCREEN_BU21013 is not set +# CONFIG_TOUCHSCREEN_BU21029 is not set +# CONFIG_TOUCHSCREEN_CHIPONE_ICN8318 is not set +# CONFIG_TOUCHSCREEN_CY8CTMA140 is not set +# CONFIG_TOUCHSCREEN_CY8CTMG110 is not set +# CONFIG_TOUCHSCREEN_CYTTSP_CORE is not set +# CONFIG_TOUCHSCREEN_CYTTSP5 is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_EGALAX is not set +# CONFIG_TOUCHSCREEN_EGALAX_SERIAL is not set +# CONFIG_TOUCHSCREEN_EXC3000 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GOODIX is not set +# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C is not set +# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI is not set +# CONFIG_TOUCHSCREEN_HIDEEP is not set +# CONFIG_TOUCHSCREEN_HIMAX_HX852X is not set +# CONFIG_TOUCHSCREEN_HYCON_HY46XX is not set +# CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX is not set +# CONFIG_TOUCHSCREEN_HYNITRON_CST816X is not set +# CONFIG_TOUCHSCREEN_ILI210X is not set +# CONFIG_TOUCHSCREEN_ILITEK is not set +# CONFIG_TOUCHSCREEN_S6SY761 is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_EKTF2127 is not set +# CONFIG_TOUCHSCREEN_ELAN is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TOUCHSCREEN_WACOM_I2C is not set +# CONFIG_TOUCHSCREEN_WACOM_W9000 is not set +# CONFIG_TOUCHSCREEN_MAX11801 is not set +# CONFIG_TOUCHSCREEN_MMS114 is not set +# CONFIG_TOUCHSCREEN_MELFAS_MIP4 is not set +# CONFIG_TOUCHSCREEN_MSG2638 is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS is not set +# CONFIG_TOUCHSCREEN_IMAGIS is not set +# CONFIG_TOUCHSCREEN_IMX6UL_TSC is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_EDT_FT5X06 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_PIXCIR is not set +# CONFIG_TOUCHSCREEN_WDT87XX_I2C is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TSC_SERIO is not set +# CONFIG_TOUCHSCREEN_TSC2004 is not set +# CONFIG_TOUCHSCREEN_TSC2005 is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_RM_TS is not set +# CONFIG_TOUCHSCREEN_SILEAD is not set +# CONFIG_TOUCHSCREEN_SIS_I2C is not set +# CONFIG_TOUCHSCREEN_ST1232 is not set +# CONFIG_TOUCHSCREEN_STMFTS is not set +# CONFIG_TOUCHSCREEN_SURFACE3_SPI is not set +# CONFIG_TOUCHSCREEN_SX8654 is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_ZET6223 is not set +# CONFIG_TOUCHSCREEN_ZFORCE is not set +# CONFIG_TOUCHSCREEN_COLIBRI_VF50 is not set +# CONFIG_TOUCHSCREEN_ROHM_BU21023 is not set +# CONFIG_TOUCHSCREEN_IQS5XX is not set +# CONFIG_TOUCHSCREEN_IQS7211 is not set +# CONFIG_TOUCHSCREEN_ZINITIX is not set +# CONFIG_TOUCHSCREEN_HIMAX_HX83112B is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_AD714X is not set +# CONFIG_INPUT_ATMEL_CAPTOUCH is not set +# CONFIG_INPUT_AW86927 is not set +# CONFIG_INPUT_BMA150 is not set +# CONFIG_INPUT_E3X0_BUTTON is not set +# CONFIG_INPUT_MMA8450 is not set +# CONFIG_INPUT_GPIO_BEEPER is not set +# CONFIG_INPUT_GPIO_DECODER is not set +# CONFIG_INPUT_GPIO_VIBRA is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_KXTJ9 is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +# CONFIG_INPUT_REGULATOR_HAPTIC is not set +CONFIG_INPUT_UINPUT=y +# CONFIG_INPUT_PCF8574 is not set +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_DA7280_HAPTICS is not set +# CONFIG_INPUT_ADXL34X is not set +# CONFIG_INPUT_IMS_PCU is not set +# CONFIG_INPUT_IQS269A is not set +# CONFIG_INPUT_IQS626A is not set +# CONFIG_INPUT_IQS7222 is not set +# CONFIG_INPUT_ISA1200_HAPTIC is not set +# CONFIG_INPUT_CMA3000 is not set +# CONFIG_INPUT_DRV260X_HAPTICS is not set +# CONFIG_INPUT_DRV2665_HAPTICS is not set +# CONFIG_INPUT_DRV2667_HAPTICS is not set +CONFIG_INPUT_MACSMC=y +# CONFIG_RMI4_CORE is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set +# end of Hardware I/O ports +# end of Input device support + +# +# Character devices +# +CONFIG_TTY=y +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_VT_CONSOLE_SLEEP=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_LEGACY_TIOCSTI=y +CONFIG_LDISC_AUTOLOAD=y + +# +# Serial drivers +# +CONFIG_SERIAL_EARLYCON=y +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +# CONFIG_SERIAL_EARLYCON_SEMIHOST is not set +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_UARTS=4 +CONFIG_SERIAL_SAMSUNG_CONSOLE=y +# CONFIG_SERIAL_MAX3100 is not set +# CONFIG_SERIAL_MAX310X is not set +# CONFIG_SERIAL_UARTLITE is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_JSM is not set +# CONFIG_SERIAL_SIFIVE is not set +# CONFIG_SERIAL_SCCNXP is not set +# CONFIG_SERIAL_SC16IS7XX is not set +# CONFIG_SERIAL_ALTERA_JTAGUART is not set +# CONFIG_SERIAL_ALTERA_UART is not set +# CONFIG_SERIAL_XILINX_PS_UART is not set +# CONFIG_SERIAL_ARC is not set +# CONFIG_SERIAL_RP2 is not set +# CONFIG_SERIAL_FSL_LPUART is not set +# CONFIG_SERIAL_FSL_LINFLEXUART is not set +# CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set +# CONFIG_SERIAL_SPRD is not set +# end of Serial drivers + +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_N_GSM is not set +# CONFIG_NOZOMI is not set +CONFIG_NULL_TTY=y +# CONFIG_HVC_DCC is not set +CONFIG_SERIAL_DEV_BUS=y +CONFIG_SERIAL_DEV_CTRL_TTYPORT=y +# CONFIG_TTY_PRINTK is not set +# CONFIG_VIRTIO_CONSOLE is not set +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +# CONFIG_HW_RANDOM_BA431 is not set +# CONFIG_HW_RANDOM_CCTRNG is not set +# CONFIG_HW_RANDOM_XIPHERA is not set +CONFIG_HW_RANDOM_ARM_SMCCC_TRNG=y +# CONFIG_HW_RANDOM_CN10K is not set +CONFIG_DEVMEM=y +CONFIG_DEVPORT=y +# CONFIG_TCG_TPM is not set +# CONFIG_XILLYBUS is not set +# CONFIG_XILLYUSB is not set +# end of Character devices + +# +# I2C support +# +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_CHARDEV is not set +CONFIG_I2C_MUX=y + +# +# Multiplexer I2C Chip support +# +# CONFIG_I2C_ARB_GPIO_CHALLENGE is not set +# CONFIG_I2C_MUX_GPIO is not set +# CONFIG_I2C_MUX_GPMUX is not set +# CONFIG_I2C_MUX_LTC4306 is not set +# CONFIG_I2C_MUX_PCA9541 is not set +# CONFIG_I2C_MUX_PCA954x is not set +# CONFIG_I2C_MUX_PINCTRL is not set +# CONFIG_I2C_MUX_REG is not set +# CONFIG_I2C_DEMUX_PINCTRL is not set +# CONFIG_I2C_MUX_MLXCPLD is not set +# end of Multiplexer I2C Chip support + +# CONFIG_I2C_HELPER_AUTO is not set +# CONFIG_I2C_SMBUS is not set + +# +# I2C Algorithms +# +CONFIG_I2C_ALGOBIT=y +# CONFIG_I2C_ALGOPCF is not set +# CONFIG_I2C_ALGOPCA is not set +# end of I2C Algorithms + +# +# I2C Hardware Bus support +# + +# +# PC SMBus host controller drivers +# +# CONFIG_I2C_ALI1535 is not set +# CONFIG_I2C_ALI1563 is not set +# CONFIG_I2C_ALI15X3 is not set +# CONFIG_I2C_AMD756 is not set +# CONFIG_I2C_AMD8111 is not set +# CONFIG_I2C_I801 is not set +# CONFIG_I2C_ISCH is not set +# CONFIG_I2C_PIIX4 is not set +# CONFIG_I2C_NFORCE2 is not set +# CONFIG_I2C_NVIDIA_GPU is not set +# CONFIG_I2C_SIS5595 is not set +# CONFIG_I2C_SIS630 is not set +# CONFIG_I2C_SIS96X is not set +# CONFIG_I2C_VIA is not set +# CONFIG_I2C_VIAPRO is not set + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_CADENCE is not set +# CONFIG_I2C_CBUS_GPIO is not set +# CONFIG_I2C_DESIGNWARE_CORE is not set +# CONFIG_I2C_EMEV2 is not set +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_HISI is not set +# CONFIG_I2C_NOMADIK is not set +# CONFIG_I2C_OCORES is not set +CONFIG_I2C_APPLE=y +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_RK3X is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_THUNDERX is not set +# CONFIG_I2C_XILINX is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_DIOLAN_U2C is not set +# CONFIG_I2C_CP2615 is not set +# CONFIG_I2C_PCI1XXXX is not set +# CONFIG_I2C_ROBOTFUZZ_OSIF is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_VIRTIO is not set +# end of I2C Hardware Bus support + +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_SLAVE is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# end of I2C support + +# CONFIG_I3C is not set +CONFIG_I3C_OR_I2C=y +CONFIG_SPI=y +# CONFIG_SPI_DEBUG is not set +CONFIG_SPI_MASTER=y +CONFIG_SPI_MEM=y + +# +# SPI Master Controller Drivers +# +# CONFIG_SPI_ALTERA is not set +CONFIG_SPI_APPLE_S8000=y +CONFIG_SPI_APPLE=y +# CONFIG_SPI_AXI_SPI_ENGINE is not set +# CONFIG_SPI_BITBANG is not set +# CONFIG_SPI_CADENCE is not set +# CONFIG_SPI_CADENCE_QUADSPI is not set +# CONFIG_SPI_CADENCE_XSPI is not set +# CONFIG_SPI_CH341 is not set +# CONFIG_SPI_DESIGNWARE is not set +# CONFIG_SPI_GPIO is not set +# CONFIG_SPI_FSL_SPI is not set +# CONFIG_SPI_MICROCHIP_CORE_QSPI is not set +# CONFIG_SPI_MICROCHIP_CORE_SPI is not set +# CONFIG_SPI_OC_TINY is not set +# CONFIG_SPI_PCI1XXXX is not set +# CONFIG_SPI_PL022 is not set +# CONFIG_SPI_SC18IS602 is not set +# CONFIG_SPI_SIFIVE is not set +# CONFIG_SPI_SN_F_OSPI is not set +# CONFIG_SPI_MXIC is not set +# CONFIG_SPI_THUNDERX is not set +# CONFIG_SPI_XCOMM is not set +# CONFIG_SPI_XILINX is not set +# CONFIG_SPI_ZYNQMP_GQSPI is not set +# CONFIG_SPI_AMD is not set + +# +# SPI Multiplexer support +# +# CONFIG_SPI_MUX is not set + +# +# SPI Protocol Masters +# +CONFIG_SPI_SPIDEV=y +# CONFIG_SPI_LOOPBACK_TEST is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_SPI_SLAVE is not set +CONFIG_SPI_DYNAMIC=y +CONFIG_SPMI=y +CONFIG_SPMI_APPLE=y +# CONFIG_SPMI_HISI3670 is not set +# CONFIG_HSI is not set +# CONFIG_PPS is not set + +# +# PTP clock support +# +# CONFIG_PTP_1588_CLOCK is not set +CONFIG_PTP_1588_CLOCK_OPTIONAL=y + +# +# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. +# +# end of PTP clock support + +# +# DPLL device support +# +# end of DPLL device support + +CONFIG_PINCTRL=y +CONFIG_GENERIC_PINCTRL_GROUPS=y +CONFIG_PINMUX=y +CONFIG_GENERIC_PINMUX_FUNCTIONS=y +# CONFIG_DEBUG_PINCTRL is not set +CONFIG_PINCTRL_APPLE_GPIO=y +# CONFIG_PINCTRL_AW9523 is not set +# CONFIG_PINCTRL_CY8C95X0 is not set +# CONFIG_PINCTRL_GENERIC_MUX is not set +# CONFIG_PINCTRL_MCP23S08 is not set +# CONFIG_PINCTRL_MICROCHIP_SGPIO is not set +# CONFIG_PINCTRL_OCELOT is not set +# CONFIG_PINCTRL_SINGLE is not set +# CONFIG_PINCTRL_STMFX is not set +# CONFIG_PINCTRL_SX150X is not set + +# +# Renesas pinctrl drivers +# +# end of Renesas pinctrl drivers + +CONFIG_GPIOLIB_LEGACY=y +CONFIG_GPIOLIB=y +CONFIG_GPIOLIB_FASTPATH_LIMIT=512 +CONFIG_OF_GPIO=y +CONFIG_GPIOLIB_IRQCHIP=y +# CONFIG_DEBUG_GPIO is not set +# CONFIG_GPIO_SYSFS is not set +CONFIG_GPIO_CDEV=y +CONFIG_GPIO_CDEV_V1=y + +# +# Memory mapped GPIO drivers +# +# CONFIG_GPIO_74XX_MMIO is not set +# CONFIG_GPIO_ALTERA is not set +# CONFIG_GPIO_BY_PINCTRL is not set +# CONFIG_GPIO_CADENCE is not set +# CONFIG_GPIO_DWAPB is not set +# CONFIG_GPIO_FTGPIO010 is not set +# CONFIG_GPIO_GENERIC_PLATFORM is not set +# CONFIG_GPIO_GRGPIO is not set +# CONFIG_GPIO_HISI is not set +# CONFIG_GPIO_HLWD is not set +# CONFIG_GPIO_LOGICVC is not set +# CONFIG_GPIO_MB86S7X is not set +# CONFIG_GPIO_PL061 is not set +# CONFIG_GPIO_POLARFIRE_SOC is not set +# CONFIG_GPIO_SIFIVE is not set +# CONFIG_GPIO_SYSCON is not set +# CONFIG_GPIO_WAVESHARE_DSI_TOUCH is not set +# CONFIG_GPIO_XGENE is not set +# CONFIG_GPIO_XILINX is not set +# CONFIG_GPIO_AMD_FCH is not set +# end of Memory mapped GPIO drivers + +# +# I2C GPIO expanders +# +# CONFIG_GPIO_ADNP is not set +# CONFIG_GPIO_FXL6408 is not set +# CONFIG_GPIO_DS4520 is not set +# CONFIG_GPIO_GW_PLD is not set +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCA9570 is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_TPIC2810 is not set +# end of I2C GPIO expanders + +# +# MFD GPIO expanders +# +CONFIG_GPIO_APPLE_D2365=y +CONFIG_GPIO_MACSMC=y +# CONFIG_GPIO_QIXIS_FPGA is not set +# end of MFD GPIO expanders + +# +# PCI GPIO expanders +# +# CONFIG_GPIO_BT8XX is not set +# CONFIG_GPIO_PCI_IDIO_16 is not set +# CONFIG_GPIO_PCIE_IDIO_24 is not set +# CONFIG_GPIO_RDC321X is not set +# end of PCI GPIO expanders + +# +# SPI GPIO expanders +# +# CONFIG_GPIO_74X164 is not set +# CONFIG_GPIO_MAX3191X is not set +# CONFIG_GPIO_MAX7301 is not set +# CONFIG_GPIO_MC33880 is not set +# CONFIG_GPIO_PISOSR is not set +# CONFIG_GPIO_XRA1403 is not set +# end of SPI GPIO expanders + +# +# USB GPIO expanders +# +# CONFIG_GPIO_MPSSE is not set +# end of USB GPIO expanders + +# +# Virtual GPIO drivers +# +# CONFIG_GPIO_AGGREGATOR is not set +# CONFIG_GPIO_LATCH is not set +# CONFIG_GPIO_LINE_MUX is not set +# CONFIG_GPIO_MOCKUP is not set +# CONFIG_GPIO_SIM is not set +# end of Virtual GPIO drivers + +# +# GPIO Debugging utilities +# +# CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER is not set +# CONFIG_GPIO_VIRTUSER is not set +# end of GPIO Debugging utilities + +CONFIG_W1=y + +# +# 1-wire Bus Masters +# +# CONFIG_W1_MASTER_AMD_AXI is not set +# CONFIG_W1_MASTER_MATROX is not set +# CONFIG_W1_MASTER_DS2490 is not set +# CONFIG_W1_MASTER_DS2482 is not set +# CONFIG_W1_MASTER_GPIO is not set +# CONFIG_W1_MASTER_SGI is not set +CONFIG_W1_MASTER_UART=y +# end of 1-wire Bus Masters + +# +# 1-wire Slaves +# +# CONFIG_W1_SLAVE_THERM is not set +# CONFIG_W1_SLAVE_SMEM is not set +# CONFIG_W1_SLAVE_DS2405 is not set +# CONFIG_W1_SLAVE_DS2408 is not set +# CONFIG_W1_SLAVE_DS2413 is not set +# CONFIG_W1_SLAVE_DS2406 is not set +# CONFIG_W1_SLAVE_DS2423 is not set +# CONFIG_W1_SLAVE_DS2805 is not set +# CONFIG_W1_SLAVE_DS2430 is not set +# CONFIG_W1_SLAVE_DS2431 is not set +# CONFIG_W1_SLAVE_DS2433 is not set +# CONFIG_W1_SLAVE_DS2438 is not set +# CONFIG_W1_SLAVE_DS250X is not set +# CONFIG_W1_SLAVE_DS2780 is not set +# CONFIG_W1_SLAVE_DS2781 is not set +# CONFIG_W1_SLAVE_DS28E04 is not set +# CONFIG_W1_SLAVE_DS28E17 is not set +# end of 1-wire Slaves + +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_GPIO is not set +# CONFIG_POWER_RESET_GPIO_RESTART is not set +CONFIG_POWER_RESET_MACSMC=y +# CONFIG_POWER_RESET_LTC2952 is not set +# CONFIG_POWER_RESET_REGULATOR is not set +# CONFIG_POWER_RESET_RESTART is not set +# CONFIG_POWER_RESET_XGENE is not set +# CONFIG_POWER_RESET_SYSCON is not set +# CONFIG_POWER_RESET_SYSCON_POWEROFF is not set +# CONFIG_SYSCON_REBOOT_MODE is not set +# CONFIG_NVMEM_REBOOT_MODE is not set +# CONFIG_POWER_RESET_QEMU_VIRT_CTRL is not set +# CONFIG_POWER_SEQUENCING is not set +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_POWER_SUPPLY_HWMON=y +# CONFIG_GENERIC_ADC_BATTERY is not set +# CONFIG_IP5XXX_POWER is not set +# CONFIG_TEST_POWER is not set +# CONFIG_CHARGER_ADP5061 is not set +# CONFIG_BATTERY_CHAGALL is not set +# CONFIG_BATTERY_CW2015 is not set +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_DS2780 is not set +# CONFIG_BATTERY_DS2781 is not set +# CONFIG_BATTERY_DS2782 is not set +# CONFIG_BATTERY_SAMSUNG_SDI is not set +# CONFIG_BATTERY_S2MU005 is not set +# CONFIG_BATTERY_SBS is not set +# CONFIG_CHARGER_SBS is not set +# CONFIG_MANAGER_SBS is not set +CONFIG_BATTERY_BQ27XXX=y +# CONFIG_BATTERY_BQ27XXX_I2C is not set +CONFIG_BATTERY_BQ27XXX_HDQ=y +# CONFIG_APPLE_BELLATRIX_POWER is not set +CONFIG_APPLE_D2365_CHARGER=m +# CONFIG_BATTERY_MAX17040 is not set +# CONFIG_BATTERY_MAX17042 is not set +# CONFIG_BATTERY_MAX1720X is not set +# CONFIG_BATTERY_MAX1721X is not set +# CONFIG_CHARGER_MAX8903 is not set +# CONFIG_CHARGER_LP8727 is not set +# CONFIG_CHARGER_GPIO is not set +# CONFIG_CHARGER_MANAGER is not set +# CONFIG_CHARGER_LT3651 is not set +# CONFIG_CHARGER_LTC4162L is not set +# CONFIG_CHARGER_DETECTOR_MAX14656 is not set +# CONFIG_CHARGER_MAX77976 is not set +# CONFIG_CHARGER_MAX8971 is not set +# CONFIG_CHARGER_BQ2415X is not set +# CONFIG_CHARGER_BQ24257 is not set +# CONFIG_CHARGER_BQ24735 is not set +# CONFIG_CHARGER_BQ2515X is not set +# CONFIG_CHARGER_BQ25890 is not set +# CONFIG_CHARGER_BQ25980 is not set +# CONFIG_CHARGER_BQ25630 is not set +# CONFIG_CHARGER_BQ256XX is not set +# CONFIG_CHARGER_SGM41542 is not set +# CONFIG_CHARGER_SMB347 is not set +# CONFIG_BATTERY_GAUGE_LTC2941 is not set +# CONFIG_BATTERY_GOLDFISH is not set +# CONFIG_BATTERY_RT5033 is not set +# CONFIG_CHARGER_RT5033 is not set +# CONFIG_CHARGER_RT9455 is not set +# CONFIG_CHARGER_RT9467 is not set +# CONFIG_CHARGER_RT9471 is not set +# CONFIG_CHARGER_RT9756 is not set +# CONFIG_CHARGER_UCS1002 is not set +# CONFIG_CHARGER_BD99954 is not set +# CONFIG_BATTERY_UG3105 is not set +# CONFIG_BATTERY_CHARGER_SURFACE_RT is not set +# CONFIG_FUEL_GAUGE_MM8013 is not set +CONFIG_MACSMC_POWER=y +CONFIG_HWMON=y +# CONFIG_HWMON_DEBUG_CHIP is not set + +# +# Native drivers +# +# CONFIG_SENSORS_AD7314 is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM1177 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7310 is not set +# CONFIG_SENSORS_ADT7410 is not set +# CONFIG_SENSORS_ADT7411 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7475 is not set +# CONFIG_SENSORS_AHT10 is not set +# CONFIG_SENSORS_AQUACOMPUTER_D5NEXT is not set +# CONFIG_SENSORS_AS370 is not set +# CONFIG_SENSORS_ASC7621 is not set +# CONFIG_SENSORS_ASUS_ROG_RYUJIN is not set +# CONFIG_SENSORS_AXI_FAN_CONTROL is not set +# CONFIG_SENSORS_KB9002 is not set +# CONFIG_SENSORS_ARCTIC_FAN_CONTROLLER is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_CHIPCAP2 is not set +# CONFIG_SENSORS_CORSAIR_CPRO is not set +# CONFIG_SENSORS_CORSAIR_PSU is not set +# CONFIG_SENSORS_DS620 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_I5K_AMB is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_FTSTEUTATES is not set +# CONFIG_SENSORS_GIGABYTE_WATERFORCE is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_G760A is not set +# CONFIG_SENSORS_G762 is not set +# CONFIG_SENSORS_GPIO_FAN is not set +# CONFIG_SENSORS_HIH6130 is not set +# CONFIG_SENSORS_HS3001 is not set +# CONFIG_SENSORS_HTU31 is not set +# CONFIG_SENSORS_IIO_HWMON is not set +# CONFIG_SENSORS_ISL28022 is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_JC42 is not set +# CONFIG_SENSORS_POWERZ is not set +# CONFIG_SENSORS_POWR1220 is not set +# CONFIG_SENSORS_PROM21_XHCI is not set +# CONFIG_SENSORS_LINEAGE is not set +# CONFIG_SENSORS_LTC2945 is not set +# CONFIG_SENSORS_LTC2947_I2C is not set +# CONFIG_SENSORS_LTC2947_SPI is not set +# CONFIG_SENSORS_LTC2990 is not set +# CONFIG_SENSORS_LTC2991 is not set +# CONFIG_SENSORS_LTC2992 is not set +# CONFIG_SENSORS_LTC4151 is not set +# CONFIG_SENSORS_LTC4215 is not set +# CONFIG_SENSORS_LTC4222 is not set +# CONFIG_SENSORS_LTC4245 is not set +# CONFIG_SENSORS_LTC4260 is not set +# CONFIG_SENSORS_LTC4261 is not set +# CONFIG_SENSORS_LTC4282 is not set +# CONFIG_SENSORS_LTC4283 is not set +CONFIG_SENSORS_MACSMC_HWMON=y +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX127 is not set +# CONFIG_SENSORS_MAX16065 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX1668 is not set +# CONFIG_SENSORS_MAX197 is not set +# CONFIG_SENSORS_MAX31722 is not set +# CONFIG_SENSORS_MAX31730 is not set +# CONFIG_SENSORS_MAX31760 is not set +# CONFIG_MAX31827 is not set +# CONFIG_SENSORS_MAX6620 is not set +# CONFIG_SENSORS_MAX6621 is not set +# CONFIG_SENSORS_MAX6639 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_MAX6697 is not set +# CONFIG_SENSORS_MAX31790 is not set +# CONFIG_SENSORS_MC34VR500 is not set +# CONFIG_SENSORS_MCP3021 is not set +# CONFIG_SENSORS_MCP9982 is not set +# CONFIG_SENSORS_TC654 is not set +# CONFIG_SENSORS_TPS23861 is not set +# CONFIG_SENSORS_MR75203 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +# CONFIG_SENSORS_LM73 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_LM95234 is not set +# CONFIG_SENSORS_LM95241 is not set +# CONFIG_SENSORS_LM95245 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_NTC_THERMISTOR is not set +# CONFIG_SENSORS_NCT6683 is not set +# CONFIG_SENSORS_NCT6775 is not set +# CONFIG_SENSORS_NCT6775_I2C is not set +# CONFIG_SENSORS_NCT7363 is not set +# CONFIG_SENSORS_NCT7802 is not set +# CONFIG_SENSORS_NCT7904 is not set +# CONFIG_SENSORS_NPCM7XX is not set +# CONFIG_SENSORS_NZXT_KRAKEN2 is not set +# CONFIG_SENSORS_NZXT_KRAKEN3 is not set +# CONFIG_SENSORS_NZXT_SMART2 is not set +# CONFIG_SENSORS_OCC_P8_I2C is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_PMBUS is not set +# CONFIG_SENSORS_PT5161L is not set +# CONFIG_SENSORS_SHT15 is not set +# CONFIG_SENSORS_SHT21 is not set +# CONFIG_SENSORS_SHT3x is not set +# CONFIG_SENSORS_SHT4x is not set +# CONFIG_SENSORS_SHTC1 is not set +# CONFIG_SENSORS_SIS5595 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_EMC1403 is not set +# CONFIG_SENSORS_EMC1812 is not set +# CONFIG_SENSORS_EMC2103 is not set +# CONFIG_SENSORS_EMC2305 is not set +# CONFIG_SENSORS_EMC6W201 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_SCH5627 is not set +# CONFIG_SENSORS_SCH5636 is not set +# CONFIG_SENSORS_STTS751 is not set +# CONFIG_SENSORS_ADC128D818 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_ADS7871 is not set +# CONFIG_SENSORS_AMC6821 is not set +# CONFIG_SENSORS_INA209 is not set +# CONFIG_SENSORS_INA2XX is not set +# CONFIG_SENSORS_INA238 is not set +# CONFIG_SENSORS_INA3221 is not set +# CONFIG_SENSORS_SPD5118 is not set +# CONFIG_SENSORS_TC74 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_TMP102 is not set +# CONFIG_SENSORS_TMP103 is not set +# CONFIG_SENSORS_TMP108 is not set +# CONFIG_SENSORS_TMP401 is not set +# CONFIG_SENSORS_TMP421 is not set +# CONFIG_SENSORS_TMP464 is not set +# CONFIG_SENSORS_TMP513 is not set +# CONFIG_SENSORS_TSC1641 is not set +# CONFIG_SENSORS_VIA686A is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_VT8231 is not set +# CONFIG_SENSORS_W83773G is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83795 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +CONFIG_THERMAL=y +# CONFIG_THERMAL_NETLINK is not set +# CONFIG_THERMAL_STATISTICS is not set +# CONFIG_THERMAL_DEBUGFS is not set +# CONFIG_THERMAL_CORE_TESTING is not set +CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=5000 +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set +# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set +# CONFIG_THERMAL_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_GOV_STEP_WISE=y +# CONFIG_THERMAL_GOV_BANG_BANG is not set +# CONFIG_THERMAL_GOV_USER_SPACE is not set +# CONFIG_THERMAL_GOV_POWER_ALLOCATOR is not set +CONFIG_CPU_THERMAL=y +CONFIG_CPU_FREQ_THERMAL=y +# CONFIG_DEVFREQ_THERMAL is not set +# CONFIG_THERMAL_EMULATION is not set +# CONFIG_THERMAL_MMIO is not set +# CONFIG_GENERIC_ADC_THERMAL is not set +CONFIG_APPLE_S8003_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WATCHDOG_NOWAYOUT is not set +CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y +CONFIG_WATCHDOG_OPEN_TIMEOUT=0 +CONFIG_WATCHDOG_SYSFS=y +# CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT is not set + +# +# Watchdog Pretimeout Governors +# +# CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +# CONFIG_GPIO_WATCHDOG is not set +# CONFIG_XILINX_WATCHDOG is not set +# CONFIG_XILINX_WINDOW_WATCHDOG is not set +# CONFIG_ZIIRAVE_WATCHDOG is not set +# CONFIG_ARM_SP805_WATCHDOG is not set +# CONFIG_ARM_SBSA_WATCHDOG is not set +# CONFIG_CADENCE_WATCHDOG is not set +# CONFIG_DW_WATCHDOG is not set +# CONFIG_MAX63XX_WATCHDOG is not set +# CONFIG_ARM_SMC_WATCHDOG is not set +CONFIG_APPLE_WATCHDOG=y +# CONFIG_ALIM7101_WDT is not set +# CONFIG_I6300ESB_WDT is not set +# CONFIG_HP_WATCHDOG is not set +# CONFIG_MEN_A21_WDT is not set + +# +# PCI-based Watchdog Cards +# +# CONFIG_PCIPCWATCHDOG is not set +# CONFIG_WDTPCI is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set +CONFIG_BCMA_POSSIBLE=y +# CONFIG_BCMA is not set + +# +# Multifunction device drivers +# +CONFIG_MFD_CORE=y +# CONFIG_MFD_ADP5585 is not set +# CONFIG_MFD_ACT8945A is not set +# CONFIG_MFD_AS3711 is not set +# CONFIG_MFD_SMPRO is not set +# CONFIG_MFD_AS3722 is not set +# CONFIG_PMIC_ADP5520 is not set +# CONFIG_MFD_AAT2870_CORE is not set +# CONFIG_MFD_ATMEL_FLEXCOM is not set +# CONFIG_MFD_ATMEL_HLCDC is not set +# CONFIG_MFD_BCM590XX is not set +# CONFIG_MFD_BD9571MWV is not set +# CONFIG_MFD_AXP20X_I2C is not set +# CONFIG_MFD_CS40L50_I2C is not set +# CONFIG_MFD_CS40L50_SPI is not set +# CONFIG_MFD_CS42L43_I2C is not set +# CONFIG_MFD_LOCHNAGAR is not set +CONFIG_MFD_MACSMC=y +# CONFIG_MFD_MADERA is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_DA9052_SPI is not set +# CONFIG_MFD_DA9052_I2C is not set +# CONFIG_MFD_DA9055 is not set +# CONFIG_MFD_DA9062 is not set +# CONFIG_MFD_DA9063 is not set +# CONFIG_MFD_DA9150 is not set +# CONFIG_MFD_DLN2 is not set +# CONFIG_MFD_GATEWORKS_GSC is not set +# CONFIG_MFD_MC13XXX_SPI is not set +# CONFIG_MFD_MC13XXX_I2C is not set +# CONFIG_MFD_MP2629 is not set +# CONFIG_MFD_PF1550 is not set +# CONFIG_MFD_HI6421_PMIC is not set +# CONFIG_MFD_HI6421_SPMI is not set +# CONFIG_LPC_ICH is not set +# CONFIG_LPC_SCH is not set +# CONFIG_MFD_IQS62X is not set +# CONFIG_MFD_JANZ_CMODIO is not set +# CONFIG_MFD_KEMPLD is not set +# CONFIG_MFD_88PM800 is not set +# CONFIG_MFD_88PM805 is not set +# CONFIG_MFD_88PM860X is not set +# CONFIG_MFD_88PM886_PMIC is not set +# CONFIG_MFD_MAX5970 is not set +# CONFIG_MFD_MAX14577 is not set +# CONFIG_MFD_MAX77541 is not set +# CONFIG_MFD_MAX77620 is not set +# CONFIG_MFD_MAX77650 is not set +# CONFIG_MFD_MAX77686 is not set +# CONFIG_MFD_MAX77693 is not set +# CONFIG_MFD_MAX77705 is not set +# CONFIG_MFD_MAX77714 is not set +# CONFIG_MFD_MAX77759 is not set +# CONFIG_MFD_MAX77843 is not set +# CONFIG_MFD_MAX8907 is not set +# CONFIG_MFD_MAX8925 is not set +# CONFIG_MFD_MAX8997 is not set +# CONFIG_MFD_MAX8998 is not set +# CONFIG_MFD_MT6360 is not set +# CONFIG_MFD_MT6370 is not set +# CONFIG_MFD_MT6397 is not set +# CONFIG_MFD_MENF21BMC is not set +# CONFIG_MFD_NCT6694 is not set +# CONFIG_MFD_OCELOT is not set +# CONFIG_MFD_CPCAP is not set +# CONFIG_MFD_VIPERBOARD is not set +# CONFIG_MFD_NTXEC is not set +# CONFIG_MFD_RETU is not set +# CONFIG_MFD_SY7636A is not set +# CONFIG_MFD_RDC321X is not set +# CONFIG_MFD_RT4831 is not set +CONFIG_MFD_RT5033=y +# CONFIG_MFD_RT5120 is not set +# CONFIG_MFD_RC5T583 is not set +# CONFIG_MFD_RK8XX_I2C is not set +# CONFIG_MFD_RK8XX_SPI is not set +# CONFIG_MFD_RN5T618 is not set +# CONFIG_MFD_SEC_I2C is not set +# CONFIG_MFD_SI476X_CORE is not set +CONFIG_MFD_SIMPLE_MFD_I2C=y +CONFIG_MFD_SIMPLE_MFD_SPMI=y +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_SKY81452 is not set +# CONFIG_MFD_STMPE is not set +CONFIG_MFD_SYSCON=y +# CONFIG_MFD_LP3943 is not set +# CONFIG_MFD_LP8788 is not set +# CONFIG_MFD_TI_LMU is not set +# CONFIG_MFD_BQ257XX is not set +# CONFIG_MFD_PALMAS is not set +# CONFIG_TPS6105X is not set +# CONFIG_TPS65010 is not set +# CONFIG_TPS6507X is not set +# CONFIG_MFD_TPS65086 is not set +# CONFIG_MFD_TPS65090 is not set +# CONFIG_MFD_TPS65217 is not set +# CONFIG_MFD_TI_LP873X is not set +# CONFIG_MFD_TI_LP87565 is not set +# CONFIG_MFD_TPS65218 is not set +# CONFIG_MFD_TPS65219 is not set +# CONFIG_MFD_TPS6586X is not set +# CONFIG_MFD_TPS65910 is not set +# CONFIG_MFD_TPS65912_I2C is not set +# CONFIG_MFD_TPS65912_SPI is not set +# CONFIG_MFD_TPS6594_I2C is not set +# CONFIG_MFD_TPS6594_SPI is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_TWL6040_CORE is not set +# CONFIG_MFD_LM3533 is not set +# CONFIG_MFD_TC3589X is not set +# CONFIG_MFD_TQMX86 is not set +# CONFIG_MFD_VX855 is not set +# CONFIG_MFD_ARIZONA_I2C is not set +# CONFIG_MFD_ARIZONA_SPI is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM831X_I2C is not set +# CONFIG_MFD_WM831X_SPI is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_WM8994 is not set +# CONFIG_MFD_ROHM_BD718XX is not set +# CONFIG_MFD_ROHM_BD71828 is not set +# CONFIG_MFD_ROHM_BD957XMUF is not set +# CONFIG_MFD_ROHM_BD96801 is not set +# CONFIG_MFD_STPMIC1 is not set +# CONFIG_MFD_STMFX is not set +# CONFIG_MFD_ATC260X_I2C is not set +CONFIG_MFD_QCOM_PM8008=y +# CONFIG_RAVE_SP_CORE is not set +# CONFIG_MFD_INTEL_M10_BMC_SPI is not set +# CONFIG_MFD_QNAP_MCU is not set +# CONFIG_MFD_RSMU_I2C is not set +# CONFIG_MFD_RSMU_SPI is not set +# CONFIG_MFD_MAX7360 is not set +# end of Multifunction device drivers + +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set +# CONFIG_REGULATOR_NETLINK_EVENTS is not set +CONFIG_REGULATOR_APPLE_D2365_TOUCH=y +# CONFIG_REGULATOR_88PG86X is not set +# CONFIG_REGULATOR_ACT8865 is not set +# CONFIG_REGULATOR_AD5398 is not set +# CONFIG_REGULATOR_ADP5055 is not set +# CONFIG_REGULATOR_AW37503 is not set +# CONFIG_REGULATOR_DA9121 is not set +# CONFIG_REGULATOR_DA9210 is not set +# CONFIG_REGULATOR_DA9211 is not set +# CONFIG_REGULATOR_FAN53555 is not set +# CONFIG_REGULATOR_FAN53880 is not set +# CONFIG_REGULATOR_GPIO is not set +# CONFIG_REGULATOR_ISL9305 is not set +# CONFIG_REGULATOR_ISL6271A is not set +# CONFIG_REGULATOR_FP9931 is not set +# CONFIG_REGULATOR_LP3971 is not set +# CONFIG_REGULATOR_LP3972 is not set +# CONFIG_REGULATOR_LP872X is not set +# CONFIG_REGULATOR_LP8755 is not set +# CONFIG_REGULATOR_LTC3589 is not set +# CONFIG_REGULATOR_LTC3676 is not set +# CONFIG_REGULATOR_MAX1586 is not set +# CONFIG_REGULATOR_MAX77503 is not set +# CONFIG_REGULATOR_MAX77675 is not set +# CONFIG_REGULATOR_MAX77857 is not set +# CONFIG_REGULATOR_MAX8649 is not set +# CONFIG_REGULATOR_MAX8660 is not set +# CONFIG_REGULATOR_MAX8893 is not set +# CONFIG_REGULATOR_MAX8952 is not set +# CONFIG_REGULATOR_MAX8973 is not set +# CONFIG_REGULATOR_MAX20086 is not set +# CONFIG_REGULATOR_MAX20411 is not set +# CONFIG_REGULATOR_MAX77826 is not set +# CONFIG_REGULATOR_MAX77838 is not set +# CONFIG_REGULATOR_MCP16502 is not set +# CONFIG_REGULATOR_MP5416 is not set +# CONFIG_REGULATOR_MP8859 is not set +# CONFIG_REGULATOR_MP886X is not set +# CONFIG_REGULATOR_MPQ7920 is not set +# CONFIG_REGULATOR_MT6311 is not set +# CONFIG_REGULATOR_MT6315 is not set +# CONFIG_REGULATOR_MT6316 is not set +# CONFIG_REGULATOR_MT6363 is not set +# CONFIG_REGULATOR_PCA9450 is not set +# CONFIG_REGULATOR_PF9453 is not set +# CONFIG_REGULATOR_PF0900 is not set +# CONFIG_REGULATOR_PF530X is not set +# CONFIG_REGULATOR_PF8X00 is not set +# CONFIG_REGULATOR_PFUZE100 is not set +# CONFIG_REGULATOR_PV88060 is not set +# CONFIG_REGULATOR_PV88080 is not set +# CONFIG_REGULATOR_PV88090 is not set +# CONFIG_REGULATOR_QCOM_PM8008 is not set +# CONFIG_REGULATOR_QCOM_SPMI is not set +# CONFIG_REGULATOR_QCOM_USB_VBUS is not set +# CONFIG_REGULATOR_RAA215300 is not set +# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY is not set +# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2 is not set +# CONFIG_REGULATOR_RT4801 is not set +# CONFIG_REGULATOR_RT4803 is not set +# CONFIG_REGULATOR_RT5033 is not set +# CONFIG_REGULATOR_RT5133 is not set +# CONFIG_REGULATOR_RT5190A is not set +# CONFIG_REGULATOR_RT5739 is not set +# CONFIG_REGULATOR_RT5759 is not set +# CONFIG_REGULATOR_RT6160 is not set +# CONFIG_REGULATOR_RT6190 is not set +# CONFIG_REGULATOR_RT6245 is not set +# CONFIG_REGULATOR_RT8092 is not set +# CONFIG_REGULATOR_RTQ2134 is not set +# CONFIG_REGULATOR_RTMV20 is not set +# CONFIG_REGULATOR_RTQ6752 is not set +# CONFIG_REGULATOR_RTQ2208 is not set +# CONFIG_REGULATOR_SGM3804 is not set +# CONFIG_REGULATOR_SLG51000 is not set +# CONFIG_REGULATOR_SY8106A is not set +# CONFIG_REGULATOR_SY8824X is not set +# CONFIG_REGULATOR_SY8827N is not set +# CONFIG_REGULATOR_TPS51632 is not set +# CONFIG_REGULATOR_TPS62360 is not set +# CONFIG_REGULATOR_TPS6286X is not set +# CONFIG_REGULATOR_TPS6287X is not set +# CONFIG_REGULATOR_TPS65023 is not set +# CONFIG_REGULATOR_TPS6507X is not set +# CONFIG_REGULATOR_TPS65132 is not set +# CONFIG_REGULATOR_TPS65185 is not set +# CONFIG_REGULATOR_TPS6524X is not set +# CONFIG_REGULATOR_VCTRL is not set +# CONFIG_REGULATOR_QCOM_LABIBB is not set +# CONFIG_RC_CORE is not set +CONFIG_CEC_CORE=y + +# +# CEC support +# +# CONFIG_MEDIA_CEC_SUPPORT is not set +# end of CEC support + +CONFIG_MEDIA_SUPPORT=y +# CONFIG_MEDIA_SUPPORT_FILTER is not set +# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set + +# +# Media device types +# +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_ANALOG_TV_SUPPORT=y +CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y +CONFIG_MEDIA_RADIO_SUPPORT=y +CONFIG_MEDIA_SDR_SUPPORT=y +CONFIG_MEDIA_PLATFORM_SUPPORT=y +CONFIG_MEDIA_TEST_SUPPORT=y +# end of Media device types + +# +# Media core support +# +CONFIG_VIDEO_DEV=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_DVB_CORE=y +# end of Media core support + +# +# Video4Linux options +# +CONFIG_VIDEO_V4L2_I2C=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +CONFIG_V4L2_H264=y +CONFIG_V4L2_MEM2MEM_DEV=y +CONFIG_V4L2_FWNODE=y +CONFIG_V4L2_ASYNC=y +# end of Video4Linux options + +# +# Media controller options +# +# CONFIG_MEDIA_CONTROLLER_DVB is not set +# end of Media controller options + +# +# Digital TV options +# +# CONFIG_DVB_MMAP is not set +CONFIG_DVB_NET=y +CONFIG_DVB_MAX_ADAPTERS=16 +CONFIG_DVB_DYNAMIC_MINORS=y +# CONFIG_DVB_DEMUX_SECTION_LOSS_LOG is not set +# CONFIG_DVB_ULE_DEBUG is not set +# end of Digital TV options + +# +# Media drivers +# + +# +# Media drivers +# +# CONFIG_MEDIA_USB_SUPPORT is not set +# CONFIG_MEDIA_PCI_SUPPORT is not set +CONFIG_RADIO_ADAPTERS=y +# CONFIG_RADIO_MAXIRADIO is not set +# CONFIG_RADIO_SAA7706H is not set +# CONFIG_RADIO_SHARK is not set +# CONFIG_RADIO_SHARK2 is not set +# CONFIG_RADIO_SI4713 is not set +# CONFIG_RADIO_TEA5764 is not set +# CONFIG_RADIO_TEF6862 is not set +# CONFIG_USB_DSBR is not set +# CONFIG_USB_KEENE is not set +# CONFIG_USB_MA901 is not set +# CONFIG_USB_MR800 is not set +# CONFIG_USB_RAREMONO is not set +# CONFIG_RADIO_SI470X is not set +CONFIG_MEDIA_PLATFORM_DRIVERS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +# CONFIG_SDR_PLATFORM_DRIVERS is not set +# CONFIG_DVB_PLATFORM_DRIVERS is not set +CONFIG_V4L_MEM2MEM_DRIVERS=y +# CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set +# CONFIG_VIDEO_MUX is not set + +# +# Allegro DVT media platform drivers +# + +# +# Amlogic media platform drivers +# + +# +# Amphion drivers +# + +# +# Apple media platform drivers +# +CONFIG_VIDEO_APPLE_VXD=y + +# +# ARM media platform drivers +# + +# +# Aspeed media platform drivers +# + +# +# Atmel media platform drivers +# + +# +# Cadence media platform drivers +# +# CONFIG_VIDEO_CADENCE_CSI2RX is not set +# CONFIG_VIDEO_CADENCE_CSI2TX is not set + +# +# Chips&Media media platform drivers +# + +# +# Intel media platform drivers +# + +# +# Marvell media platform drivers +# +# CONFIG_VIDEO_CAFE_CCIC is not set + +# +# Mediatek media platform drivers +# + +# +# Microchip Technology, Inc. media platform drivers +# + +# +# Nuvoton media platform drivers +# + +# +# NVidia media platform drivers +# + +# +# NXP media platform drivers +# + +# +# Qualcomm media platform drivers +# + +# +# Raspberry Pi media platform drivers +# +# CONFIG_VIDEO_RP1_CFE is not set + +# +# Renesas media platform drivers +# + +# +# Rockchip media platform drivers +# + +# +# Samsung media platform drivers +# + +# +# STMicroelectronics media platform drivers +# + +# +# Sunxi media platform drivers +# + +# +# Texas Instruments drivers +# + +# +# Verisilicon media platform drivers +# + +# +# VIA media platform drivers +# + +# +# Xilinx media platform drivers +# +# CONFIG_VIDEO_XILINX is not set +# CONFIG_V4L_TEST_DRIVERS is not set +# CONFIG_DVB_TEST_DRIVERS is not set +CONFIG_VIDEOBUF2_CORE=y +CONFIG_VIDEOBUF2_V4L2=y +CONFIG_VIDEOBUF2_MEMOPS=y +CONFIG_VIDEOBUF2_DMA_CONTIG=y +CONFIG_VIDEOBUF2_VMALLOC=m +# end of Media drivers + +# +# Media ancillary drivers +# +CONFIG_MEDIA_ATTACH=y +CONFIG_VIDEO_CAMERA_SENSOR=y +# CONFIG_VIDEO_ALVIUM_CSI2 is not set +# CONFIG_VIDEO_AR0521 is not set +# CONFIG_VIDEO_GC0308 is not set +# CONFIG_VIDEO_GC0310 is not set +# CONFIG_VIDEO_GC05A2 is not set +# CONFIG_VIDEO_GC08A3 is not set +# CONFIG_VIDEO_GC2145 is not set +# CONFIG_VIDEO_HI556 is not set +# CONFIG_VIDEO_HI846 is not set +# CONFIG_VIDEO_HI847 is not set +# CONFIG_VIDEO_IMX111 is not set +# CONFIG_VIDEO_IMX208 is not set +# CONFIG_VIDEO_IMX214 is not set +# CONFIG_VIDEO_IMX219 is not set +# CONFIG_VIDEO_IMX258 is not set +# CONFIG_VIDEO_IMX274 is not set +# CONFIG_VIDEO_IMX283 is not set +# CONFIG_VIDEO_IMX290 is not set +# CONFIG_VIDEO_IMX296 is not set +# CONFIG_VIDEO_IMX319 is not set +# CONFIG_VIDEO_IMX334 is not set +# CONFIG_VIDEO_IMX335 is not set +# CONFIG_VIDEO_IMX355 is not set +# CONFIG_VIDEO_IMX412 is not set +# CONFIG_VIDEO_IMX415 is not set +# CONFIG_VIDEO_IMX471 is not set +# CONFIG_VIDEO_IMX678 is not set +# CONFIG_VIDEO_MT9M001 is not set +# CONFIG_VIDEO_MT9M111 is not set +# CONFIG_VIDEO_MT9M114 is not set +# CONFIG_VIDEO_MT9P031 is not set +# CONFIG_VIDEO_MT9T112 is not set +# CONFIG_VIDEO_MT9V011 is not set +# CONFIG_VIDEO_MT9V032 is not set +# CONFIG_VIDEO_MT9V111 is not set +# CONFIG_VIDEO_OG01A1B is not set +# CONFIG_VIDEO_OG0VE1B is not set +# CONFIG_VIDEO_OS05B10 is not set +# CONFIG_VIDEO_OV01A10 is not set +# CONFIG_VIDEO_OV02A10 is not set +# CONFIG_VIDEO_OV02E10 is not set +# CONFIG_VIDEO_OV02C10 is not set +# CONFIG_VIDEO_OV08D10 is not set +# CONFIG_VIDEO_OV08X40 is not set +# CONFIG_VIDEO_OV13858 is not set +# CONFIG_VIDEO_OV13B10 is not set +# CONFIG_VIDEO_OV2640 is not set +# CONFIG_VIDEO_OV2659 is not set +# CONFIG_VIDEO_OV2680 is not set +# CONFIG_VIDEO_OV2685 is not set +# CONFIG_VIDEO_OV2732 is not set +# CONFIG_VIDEO_OV2735 is not set +# CONFIG_VIDEO_OV4689 is not set +# CONFIG_VIDEO_OV5640 is not set +# CONFIG_VIDEO_OV5645 is not set +# CONFIG_VIDEO_OV5647 is not set +# CONFIG_VIDEO_OV5648 is not set +# CONFIG_VIDEO_OV5670 is not set +# CONFIG_VIDEO_OV5675 is not set +# CONFIG_VIDEO_OV5693 is not set +# CONFIG_VIDEO_OV5695 is not set +# CONFIG_VIDEO_OV6211 is not set +# CONFIG_VIDEO_OV64A40 is not set +# CONFIG_VIDEO_OV7251 is not set +# CONFIG_VIDEO_OV7640 is not set +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_OV772X is not set +# CONFIG_VIDEO_OV7740 is not set +# CONFIG_VIDEO_OV8856 is not set +# CONFIG_VIDEO_OV8858 is not set +# CONFIG_VIDEO_OV8865 is not set +# CONFIG_VIDEO_OV9282 is not set +# CONFIG_VIDEO_OV9640 is not set +# CONFIG_VIDEO_OV9650 is not set +# CONFIG_VIDEO_RDACM20 is not set +# CONFIG_VIDEO_RDACM21 is not set +# CONFIG_VIDEO_RJ54N1 is not set +# CONFIG_VIDEO_S5C73M3 is not set +# CONFIG_VIDEO_S5K3M5 is not set +# CONFIG_VIDEO_S5K5BAF is not set +# CONFIG_VIDEO_S5K6A3 is not set +# CONFIG_VIDEO_S5KJN1 is not set +# CONFIG_VIDEO_VD55G1 is not set +# CONFIG_VIDEO_VD56G3 is not set +# CONFIG_VIDEO_VGXY61 is not set +# CONFIG_VIDEO_CCS is not set +# CONFIG_VIDEO_ET8EK8 is not set + +# +# Camera ISPs +# +# CONFIG_VIDEO_THP7312 is not set +# end of Camera ISPs + +CONFIG_VIDEO_CAMERA_LENS=y +# CONFIG_VIDEO_AD5820 is not set +# CONFIG_VIDEO_AK7375 is not set +# CONFIG_VIDEO_DW9714 is not set +# CONFIG_VIDEO_DW9719 is not set +# CONFIG_VIDEO_DW9768 is not set +# CONFIG_VIDEO_DW9807_VCM is not set + +# +# Flash devices +# +# CONFIG_VIDEO_ADP1653 is not set +# CONFIG_VIDEO_LM3560 is not set +# CONFIG_VIDEO_LM3646 is not set +# end of Flash devices + +# +# Audio decoders, processors and mixers +# +# CONFIG_VIDEO_CS3308 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_SONY_BTF_MPX is not set +# CONFIG_VIDEO_TDA1997X is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_UDA1342 is not set +# CONFIG_VIDEO_VP27SMPX is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_WM8775 is not set +# end of Audio decoders, processors and mixers + +# +# RDS decoders +# +# CONFIG_VIDEO_SAA6588 is not set +# end of RDS decoders + +# +# Video decoders +# +# CONFIG_VIDEO_ADV7180 is not set +# CONFIG_VIDEO_ADV7183 is not set +# CONFIG_VIDEO_ADV748X is not set +# CONFIG_VIDEO_ADV7604 is not set +# CONFIG_VIDEO_ADV7842 is not set +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_ISL7998X is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_MAX9286 is not set +# CONFIG_VIDEO_ML86V7667 is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_TC358743 is not set +# CONFIG_VIDEO_TC358746 is not set +# CONFIG_VIDEO_TVP514X is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_TVP7002 is not set +# CONFIG_VIDEO_TW2804 is not set +# CONFIG_VIDEO_TW9900 is not set +# CONFIG_VIDEO_TW9903 is not set +# CONFIG_VIDEO_TW9906 is not set +# CONFIG_VIDEO_TW9910 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_CX25840 is not set +# end of Video decoders + +# +# Video encoders +# +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set +# CONFIG_VIDEO_ADV7343 is not set +# CONFIG_VIDEO_ADV7393 is not set +# CONFIG_VIDEO_ADV7511 is not set +# CONFIG_VIDEO_AK881X is not set +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_THS8200 is not set +# end of Video encoders + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# end of Video improvement chips + +# +# Audio/Video compression chips +# +# CONFIG_VIDEO_SAA6752HS is not set +# end of Audio/Video compression chips + +# +# SDR tuner chips +# +# CONFIG_SDR_MAX2175 is not set +# end of SDR tuner chips + +# +# Miscellaneous helper chips +# +# CONFIG_VIDEO_I2C is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_ST_MIPID02 is not set +# CONFIG_VIDEO_THS7303 is not set +# end of Miscellaneous helper chips + +# +# Video serializers and deserializers +# +# CONFIG_VIDEO_DS90UB913 is not set +# CONFIG_VIDEO_DS90UB953 is not set +# CONFIG_VIDEO_DS90UB960 is not set +# CONFIG_VIDEO_MAX96714 is not set +# CONFIG_VIDEO_MAX96717 is not set +# end of Video serializers and deserializers + +# +# Media SPI Adapters +# +CONFIG_CXD2880_SPI_DRV=m +# CONFIG_VIDEO_GS1662 is not set +# end of Media SPI Adapters + +CONFIG_MEDIA_TUNER=y + +# +# Customize TV tuners +# +CONFIG_MEDIA_TUNER_E4000=m +CONFIG_MEDIA_TUNER_FC0011=m +CONFIG_MEDIA_TUNER_FC0012=m +CONFIG_MEDIA_TUNER_FC0013=m +CONFIG_MEDIA_TUNER_FC2580=m +CONFIG_MEDIA_TUNER_IT913X=m +CONFIG_MEDIA_TUNER_M88RS6000T=m +CONFIG_MEDIA_TUNER_MAX2165=m +CONFIG_MEDIA_TUNER_MC44S803=m +CONFIG_MEDIA_TUNER_MSI001=m +CONFIG_MEDIA_TUNER_MT2060=m +CONFIG_MEDIA_TUNER_MT2063=m +CONFIG_MEDIA_TUNER_MT20XX=m +CONFIG_MEDIA_TUNER_MT2131=m +CONFIG_MEDIA_TUNER_MT2266=m +CONFIG_MEDIA_TUNER_MXL301RF=m +CONFIG_MEDIA_TUNER_MXL5005S=m +CONFIG_MEDIA_TUNER_MXL5007T=m +CONFIG_MEDIA_TUNER_QM1D1B0004=m +CONFIG_MEDIA_TUNER_QM1D1C0042=m +CONFIG_MEDIA_TUNER_QT1010=m +CONFIG_MEDIA_TUNER_R820T=m +CONFIG_MEDIA_TUNER_SI2157=m +CONFIG_MEDIA_TUNER_SIMPLE=m +CONFIG_MEDIA_TUNER_TDA18212=m +CONFIG_MEDIA_TUNER_TDA18218=m +CONFIG_MEDIA_TUNER_TDA18250=m +CONFIG_MEDIA_TUNER_TDA18271=m +CONFIG_MEDIA_TUNER_TDA827X=m +CONFIG_MEDIA_TUNER_TDA8290=m +CONFIG_MEDIA_TUNER_TDA9887=m +CONFIG_MEDIA_TUNER_TEA5761=m +CONFIG_MEDIA_TUNER_TEA5767=m +CONFIG_MEDIA_TUNER_TUA9001=m +CONFIG_MEDIA_TUNER_XC2028=m +CONFIG_MEDIA_TUNER_XC4000=m +CONFIG_MEDIA_TUNER_XC5000=m +# end of Customize TV tuners + +# +# Customise DVB Frontends +# + +# +# Multistandard (satellite) frontends +# +CONFIG_DVB_M88DS3103=m +CONFIG_DVB_MXL5XX=m +CONFIG_DVB_STB0899=m +CONFIG_DVB_STB6100=m +CONFIG_DVB_STV090x=m +CONFIG_DVB_STV0910=m +CONFIG_DVB_STV6110x=m +CONFIG_DVB_STV6111=m + +# +# Multistandard (cable + terrestrial) frontends +# +CONFIG_DVB_DRXK=m +CONFIG_DVB_MN88472=m +CONFIG_DVB_MN88473=m +CONFIG_DVB_SI2165=m +CONFIG_DVB_TDA18271C2DD=m + +# +# DVB-S (satellite) frontends +# +CONFIG_DVB_CX24110=m +CONFIG_DVB_CX24116=m +CONFIG_DVB_CX24117=m +CONFIG_DVB_CX24120=m +CONFIG_DVB_CX24123=m +CONFIG_DVB_DS3000=m +CONFIG_DVB_MB86A16=m +CONFIG_DVB_MT312=m +CONFIG_DVB_S5H1420=m +CONFIG_DVB_SI21XX=m +CONFIG_DVB_STB6000=m +CONFIG_DVB_STV0288=m +CONFIG_DVB_STV0299=m +CONFIG_DVB_STV0900=m +CONFIG_DVB_STV6110=m +CONFIG_DVB_TDA10071=m +CONFIG_DVB_TDA10086=m +CONFIG_DVB_TDA8083=m +CONFIG_DVB_TDA8261=m +CONFIG_DVB_TDA826X=m +CONFIG_DVB_TS2020=m +CONFIG_DVB_TUA6100=m +CONFIG_DVB_TUNER_CX24113=m +CONFIG_DVB_TUNER_ITD1000=m +CONFIG_DVB_VES1X93=m +CONFIG_DVB_ZL10036=m +CONFIG_DVB_ZL10039=m + +# +# DVB-T (terrestrial) frontends +# +CONFIG_DVB_AF9013=m +CONFIG_DVB_CX22700=m +CONFIG_DVB_CX22702=m +CONFIG_DVB_CXD2820R=m +CONFIG_DVB_CXD2841ER=m +CONFIG_DVB_DIB3000MB=m +CONFIG_DVB_DIB3000MC=m +CONFIG_DVB_DIB7000M=m +CONFIG_DVB_DIB7000P=m +CONFIG_DVB_DIB9000=m +CONFIG_DVB_DRXD=m +CONFIG_DVB_EC100=m +CONFIG_DVB_L64781=m +CONFIG_DVB_MT352=m +CONFIG_DVB_NXT6000=m +CONFIG_DVB_RTL2830=m +CONFIG_DVB_RTL2832=m +CONFIG_DVB_RTL2832_SDR=m +CONFIG_DVB_S5H1432=m +CONFIG_DVB_SI2168=m +CONFIG_DVB_SP887X=m +CONFIG_DVB_STV0367=m +CONFIG_DVB_TDA10048=m +CONFIG_DVB_TDA1004X=m +CONFIG_DVB_ZD1301_DEMOD=m +CONFIG_DVB_ZL10353=m +CONFIG_DVB_CXD2880=m + +# +# DVB-C (cable) frontends +# +CONFIG_DVB_STV0297=m +CONFIG_DVB_TDA10021=m +CONFIG_DVB_TDA10023=m +CONFIG_DVB_VES1820=m + +# +# ATSC (North American/Korean Terrestrial/Cable DTV) frontends +# +CONFIG_DVB_AU8522=m +CONFIG_DVB_AU8522_DTV=m +CONFIG_DVB_AU8522_V4L=m +CONFIG_DVB_BCM3510=m +CONFIG_DVB_LG2160=m +CONFIG_DVB_LGDT3305=m +CONFIG_DVB_LGDT3306A=m +CONFIG_DVB_LGDT330X=m +CONFIG_DVB_MXL692=m +CONFIG_DVB_NXT200X=m +CONFIG_DVB_OR51132=m +CONFIG_DVB_OR51211=m +CONFIG_DVB_S5H1409=m +CONFIG_DVB_S5H1411=m + +# +# ISDB-T (terrestrial) frontends +# +CONFIG_DVB_DIB8000=m +CONFIG_DVB_MB86A20S=m +CONFIG_DVB_S921=m + +# +# ISDB-S (satellite) & ISDB-T (terrestrial) frontends +# +CONFIG_DVB_MN88443X=m +CONFIG_DVB_TC90522=m + +# +# Digital terrestrial only tuners/PLL +# +CONFIG_DVB_PLL=m +CONFIG_DVB_TUNER_DIB0070=m +CONFIG_DVB_TUNER_DIB0090=m + +# +# SEC control devices for DVB-S +# +CONFIG_DVB_A8293=m +CONFIG_DVB_AF9033=m +CONFIG_DVB_ASCOT2E=m +CONFIG_DVB_ATBM8830=m +CONFIG_DVB_HELENE=m +CONFIG_DVB_HORUS3A=m +CONFIG_DVB_ISL6405=m +CONFIG_DVB_ISL6421=m +CONFIG_DVB_ISL6423=m +CONFIG_DVB_IX2505V=m +CONFIG_DVB_LGS8GL5=m +CONFIG_DVB_LGS8GXX=m +CONFIG_DVB_LNBH25=m +CONFIG_DVB_LNBH29=m +CONFIG_DVB_LNBP21=m +CONFIG_DVB_LNBP22=m +CONFIG_DVB_M88RS2000=m +CONFIG_DVB_TDA665x=m +CONFIG_DVB_DRX39XYJ=m + +# +# Common Interface (EN50221) controller drivers +# +CONFIG_DVB_CXD2099=m +CONFIG_DVB_SP2=m +# end of Customise DVB Frontends + +# +# Tools to develop new frontends +# +# CONFIG_DVB_DUMMY_FE is not set +# end of Media ancillary drivers + +# +# Graphics support +# +CONFIG_APERTURE_HELPERS=y +CONFIG_VIDEO=y +# CONFIG_AUXDISPLAY is not set +CONFIG_DRM=y + +# +# DRM debugging options +# +# CONFIG_DRM_WERROR is not set +# CONFIG_DRM_DEBUG_MM is not set +# end of DRM debugging options + +CONFIG_DRM_MIPI_DSI=y +CONFIG_DRM_KMS_HELPER=y +# CONFIG_DRM_PANIC is not set +# CONFIG_DRM_RAS is not set +# CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS is not set +# CONFIG_DRM_DEBUG_MODESET_LOCK is not set +CONFIG_DRM_CLIENT=y +CONFIG_DRM_CLIENT_LIB=y +CONFIG_DRM_CLIENT_SELECTION=y +CONFIG_DRM_CLIENT_SETUP=y + +# +# Supported DRM clients +# +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_FBDEV_OVERALLOC=100 +# CONFIG_DRM_FBDEV_LEAK_PHYS_SMEM is not set +# CONFIG_DRM_CLIENT_LOG is not set +CONFIG_DRM_CLIENT_DEFAULT_FBDEV=y +CONFIG_DRM_CLIENT_DEFAULT="fbdev" +# end of Supported DRM clients + +# CONFIG_DRM_LOAD_EDID_FIRMWARE is not set +CONFIG_DRM_DISPLAY_HELPER=y +CONFIG_DRM_BRIDGE_CONNECTOR=y +# CONFIG_DRM_DISPLAY_DP_AUX_CEC is not set +# CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV is not set +CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_STATE_HELPER=y +CONFIG_DRM_GEM_DMA_HELPER=y +CONFIG_DRM_GEM_SHMEM_HELPER=y +CONFIG_DRM_APPLE_ADBE=y +CONFIG_DRM_ADP=y +# CONFIG_DRM_AMDGPU is not set + +# +# ARM devices +# +# CONFIG_DRM_HDLCD is not set +# CONFIG_DRM_MALI_DISPLAY is not set +# CONFIG_DRM_KOMEDA is not set +# end of ARM devices + +# CONFIG_DRM_AST is not set +CONFIG_DRM_BRIDGE=y +CONFIG_DRM_PANEL_BRIDGE=y + +# +# Display Interface Bridges +# +# CONFIG_DRM_CHIPONE_ICN6211 is not set +# CONFIG_DRM_CHRONTEL_CH7033 is not set +# CONFIG_DRM_DISPLAY_CONNECTOR is not set +# CONFIG_DRM_I2C_NXP_TDA998X is not set +# CONFIG_DRM_ITE_IT6263 is not set +# CONFIG_DRM_ITE_IT6505 is not set +# CONFIG_DRM_LONTIUM_LT8912B is not set +# CONFIG_DRM_LONTIUM_LT9211 is not set +# CONFIG_DRM_LONTIUM_LT9611 is not set +# CONFIG_DRM_LONTIUM_LT9611UXC is not set +# CONFIG_DRM_LONTIUM_LT8713SX is not set +# CONFIG_DRM_ITE_IT66121 is not set +# CONFIG_DRM_LVDS_CODEC is not set +# CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW is not set +# CONFIG_DRM_NWL_MIPI_DSI is not set +# CONFIG_DRM_NXP_PTN3460 is not set +# CONFIG_DRM_PARADE_PS8622 is not set +# CONFIG_DRM_PARADE_PS8640 is not set +# CONFIG_DRM_SAMSUNG_DSIM is not set +# CONFIG_DRM_SIL_SII8620 is not set +# CONFIG_DRM_SII902X is not set +# CONFIG_DRM_SII9234 is not set +# CONFIG_DRM_SIMPLE_BRIDGE is not set +# CONFIG_DRM_SOLOMON_SSD2825 is not set +# CONFIG_DRM_THINE_THC63LVD1024 is not set +# CONFIG_DRM_TOSHIBA_TC358762 is not set +# CONFIG_DRM_TOSHIBA_TC358764 is not set +# CONFIG_DRM_TOSHIBA_TC358767 is not set +# CONFIG_DRM_TOSHIBA_TC358768 is not set +# CONFIG_DRM_TOSHIBA_TC358775 is not set +# CONFIG_DRM_TI_DLPC3433 is not set +# CONFIG_DRM_TI_TDP158 is not set +# CONFIG_DRM_TI_TFP410 is not set +# CONFIG_DRM_TI_SN65DSI83 is not set +# CONFIG_DRM_TI_SN65DSI86 is not set +# CONFIG_DRM_TI_TPD12S015 is not set +# CONFIG_DRM_WAVESHARE_BRIDGE is not set +# CONFIG_DRM_ANALOGIX_ANX6345 is not set +# CONFIG_DRM_ANALOGIX_ANX78XX is not set +# CONFIG_DRM_I2C_ADV7511 is not set +# CONFIG_DRM_CDNS_DSI is not set +# CONFIG_DRM_CDNS_MHDP8546 is not set +# end of Display Interface Bridges + +# CONFIG_DRM_ETNAVIV is not set +# CONFIG_DRM_GUD is not set +# CONFIG_DRM_HISI_HIBMC is not set +# CONFIG_DRM_HISI_KIRIN is not set +# CONFIG_DRM_POWERVR is not set +# CONFIG_DRM_LIMA is not set +# CONFIG_DRM_LOGICVC is not set +# CONFIG_DRM_MGAG200 is not set +# CONFIG_DRM_NOUVEAU is not set +CONFIG_DRM_PANEL=y + +# +# Display Panels +# +# CONFIG_DRM_PANEL_ABT_Y030XX067A is not set +# CONFIG_DRM_PANEL_ANBERNIC_TD4310 is not set +# CONFIG_DRM_PANEL_ARM_VERSATILE is not set +# CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596 is not set +# CONFIG_DRM_PANEL_AUO_A030JTN01 is not set +# CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0 is not set +# CONFIG_DRM_PANEL_BOE_HIMAX8279D is not set +# CONFIG_DRM_PANEL_BOE_TD4320 is not set +# CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A is not set +# CONFIG_DRM_PANEL_BOE_TV101WUM_NL6 is not set +# CONFIG_DRM_PANEL_BOE_TV101WUM_LL2 is not set +# CONFIG_DRM_PANEL_CHIPONE_ICNA35XX is not set +# CONFIG_DRM_PANEL_CHIPWEALTH_CH13726A is not set +# CONFIG_DRM_PANEL_EBBG_FT8719 is not set +# CONFIG_DRM_PANEL_ELIDA_KD35T133 is not set +# CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02 is not set +# CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D is not set +# CONFIG_DRM_PANEL_FOCALTECH_OTA7290B is not set +# CONFIG_DRM_PANEL_DSI_CM is not set +# CONFIG_DRM_PANEL_LVDS is not set +# CONFIG_DRM_PANEL_HIMAX_HX8279 is not set +# CONFIG_DRM_PANEL_HIMAX_HX83102 is not set +# CONFIG_DRM_PANEL_HIMAX_HX83112A is not set +# CONFIG_DRM_PANEL_HIMAX_HX83112B is not set +# CONFIG_DRM_PANEL_HIMAX_HX83121A is not set +# CONFIG_DRM_PANEL_HIMAX_HX8394 is not set +# CONFIG_DRM_PANEL_HYDIS_HV101HD1 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI7807S is not set +# CONFIG_DRM_PANEL_ILITEK_IL9322 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9341 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9488 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9805 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9806E_DSI is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9881C is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9882T is not set +# CONFIG_DRM_PANEL_INNOLUX_EJ030NA is not set +# CONFIG_DRM_PANEL_INNOLUX_P079ZCA is not set +# CONFIG_DRM_PANEL_JADARD_JD9365DA_H3 is not set +# CONFIG_DRM_PANEL_JDI_LPM102A188A is not set +# CONFIG_DRM_PANEL_JDI_LT070ME05000 is not set +# CONFIG_DRM_PANEL_JDI_R63452 is not set +# CONFIG_DRM_PANEL_KHADAS_TS050 is not set +# CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04 is not set +# CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W is not set +# CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829 is not set +# CONFIG_DRM_PANEL_LINCOLNTECH_LCD197 is not set +# CONFIG_DRM_PANEL_LG_LB035Q02 is not set +# CONFIG_DRM_PANEL_LG_LD070WX3 is not set +# CONFIG_DRM_PANEL_LG_LG4573 is not set +# CONFIG_DRM_PANEL_LG_SW43408 is not set +# CONFIG_DRM_PANEL_LXD_M9189A is not set +# CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966 is not set +# CONFIG_DRM_PANEL_MANTIX_MLAF057WE51 is not set +# CONFIG_DRM_PANEL_MOTOROLA_MOT is not set +# CONFIG_DRM_PANEL_NEC_NL8048HL11 is not set +# CONFIG_DRM_PANEL_NEWVISION_NV3051D is not set +# CONFIG_DRM_PANEL_NEWVISION_NV3052C is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35510 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35532 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35560 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35950 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36523 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36536 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36672A is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36672E is not set +# CONFIG_DRM_PANEL_NOVATEK_NT37700F is not set +# CONFIG_DRM_PANEL_NOVATEK_NT37801 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT39016 is not set +# CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO is not set +# CONFIG_DRM_PANEL_ORISETECH_OTA5601A is not set +# CONFIG_DRM_PANEL_ORISETECH_OTM8009A is not set +# CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS is not set +# CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00 is not set +# CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM67191 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM67200 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM68200 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM692E5 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM69380 is not set +# CONFIG_DRM_PANEL_RENESAS_R61307 is not set +# CONFIG_DRM_PANEL_RENESAS_R63419 is not set +# CONFIG_DRM_PANEL_RENESAS_R69328 is not set +# CONFIG_DRM_PANEL_RONBO_RB070D30 is not set +# CONFIG_DRM_PANEL_SAMSUNG_AMS581VF01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_AMS639RQ08 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20 is not set +# CONFIG_DRM_PANEL_SAMSUNG_DB7430 is not set +# CONFIG_DRM_PANEL_SAMSUNG_LD9040 is not set +# CONFIG_DRM_PANEL_SAMSUNG_LTL106HL02 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D16D0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D27A1 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3FC2X01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3HA8 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E63M0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8AA5X01_AMS561RA01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8FC0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_SOFEF00 is not set +# CONFIG_DRM_PANEL_SEIKO_43WVF1G is not set +# CONFIG_DRM_PANEL_SHARP_LQ079L1SX01 is not set +# CONFIG_DRM_PANEL_SHARP_LQ101R1SX01 is not set +# CONFIG_DRM_PANEL_SHARP_LS037V7DW01 is not set +# CONFIG_DRM_PANEL_SHARP_LS043T1LE01 is not set +# CONFIG_DRM_PANEL_SHARP_LS060T1SX01 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7701 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7703 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7789V is not set +# CONFIG_DRM_PANEL_SONY_ACX565AKM is not set +# CONFIG_DRM_PANEL_SONY_TD4353_JDI is not set +# CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521 is not set +# CONFIG_DRM_PANEL_STARTEK_KD070FHFID015 is not set +# CONFIG_DRM_PANEL_EDP is not set +# CONFIG_DRM_PANEL_SIMPLE is not set +CONFIG_DRM_PANEL_SUMMIT=y +# CONFIG_DRM_PANEL_SYNAPTICS_R63353 is not set +# CONFIG_DRM_PANEL_SYNAPTICS_TDDI is not set +# CONFIG_DRM_PANEL_TDO_TL070WSH30 is not set +# CONFIG_DRM_PANEL_TPO_TD028TTEC1 is not set +# CONFIG_DRM_PANEL_TPO_TD043MTEA1 is not set +# CONFIG_DRM_PANEL_TPO_TPG110 is not set +# CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA is not set +# CONFIG_DRM_PANEL_VISIONOX_G2647FB105 is not set +# CONFIG_DRM_PANEL_VISIONOX_R66451 is not set +# CONFIG_DRM_PANEL_VISIONOX_RM69299 is not set +# CONFIG_DRM_PANEL_VISIONOX_RM692E5 is not set +# CONFIG_DRM_PANEL_VISIONOX_VTDR6130 is not set +# CONFIG_DRM_PANEL_WIDECHIPS_WS2401 is not set +# CONFIG_DRM_PANEL_XINPENG_XPP055C272 is not set +# end of Display Panels + +# CONFIG_DRM_PANFROST is not set +# CONFIG_DRM_PANTHOR is not set +# CONFIG_DRM_PL111 is not set +# CONFIG_DRM_QXL is not set +# CONFIG_DRM_RADEON is not set +# CONFIG_DRM_ST7571 is not set +# CONFIG_DRM_ST7586 is not set +# CONFIG_DRM_ST7735R is not set +# CONFIG_DRM_ST7920 is not set +# CONFIG_DRM_SSD130X is not set + +# +# Drivers for system framebuffers +# +CONFIG_DRM_SYSFB_HELPER=y +# CONFIG_DRM_EFIDRM is not set +CONFIG_DRM_SIMPLEDRM=y +# end of Drivers for system framebuffers + +# CONFIG_DRM_TIDSS is not set +# CONFIG_DRM_ARCPGU is not set +# CONFIG_DRM_BOCHS is not set +# CONFIG_DRM_CIRRUS_QEMU is not set +# CONFIG_DRM_GM12U320 is not set +# CONFIG_DRM_PANEL_MIPI_DBI is not set +# CONFIG_DRM_PIXPAPER is not set +# CONFIG_TINYDRM_HX8357D is not set +# CONFIG_TINYDRM_ILI9163 is not set +# CONFIG_TINYDRM_ILI9225 is not set +# CONFIG_TINYDRM_ILI9341 is not set +# CONFIG_TINYDRM_ILI9486 is not set +# CONFIG_TINYDRM_MI0283QT is not set +# CONFIG_TINYDRM_REPAPER is not set +# CONFIG_TINYDRM_SHARP_MEMORY is not set +# CONFIG_DRM_UDL is not set +# CONFIG_DRM_VGEM is not set +# CONFIG_DRM_VKMS is not set +# CONFIG_DRM_VMWGFX is not set +CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y + +# +# Frame buffer Devices +# +CONFIG_FB=y +# CONFIG_FB_CIRRUS is not set +# CONFIG_FB_PM2 is not set +# CONFIG_FB_CYBER2000 is not set +# CONFIG_FB_ASILIANT is not set +# CONFIG_FB_IMSTT is not set +# CONFIG_FB_EFI is not set +# CONFIG_FB_OPENCORES is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_NVIDIA is not set +# CONFIG_FB_RIVA is not set +# CONFIG_FB_I740 is not set +# CONFIG_FB_RADEON is not set +# CONFIG_FB_ATY128 is not set +# CONFIG_FB_ATY is not set +# CONFIG_FB_SAVAGE is not set +# CONFIG_FB_SIS is not set +# CONFIG_FB_NEOMAGIC is not set +# CONFIG_FB_KYRO is not set +# CONFIG_FB_3DFX is not set +# CONFIG_FB_VOODOO1 is not set +# CONFIG_FB_TRIDENT is not set +# CONFIG_FB_PM3 is not set +# CONFIG_FB_CARMINE is not set +# CONFIG_FB_SMSCUFX is not set +# CONFIG_FB_UDL is not set +# CONFIG_FB_IBM_GXT4500 is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +# CONFIG_FB_SSD1307 is not set +# CONFIG_FB_SM712 is not set +CONFIG_FB_CORE=y +CONFIG_FB_NOTIFY=y +CONFIG_FB_DEVICE=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_IMAGEBLIT=y +CONFIG_FB_FOREIGN_ENDIAN=y +CONFIG_FB_BOTH_ENDIAN=y +# CONFIG_FB_BIG_ENDIAN is not set +# CONFIG_FB_LITTLE_ENDIAN is not set +CONFIG_FB_SYSMEM_FOPS=y +CONFIG_FB_DEFERRED_IO=y +CONFIG_FB_DMAMEM_HELPERS=y +CONFIG_FB_DMAMEM_HELPERS_DEFERRED=y +CONFIG_FB_SYSMEM_HELPERS=y +CONFIG_FB_SYSMEM_HELPERS_DEFERRED=y +# CONFIG_FB_TILEBLITTING is not set +# end of Frame buffer Devices + +# +# Backlight & LCD device support +# +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_AW99706 is not set +# CONFIG_BACKLIGHT_KTD253 is not set +# CONFIG_BACKLIGHT_KTD2801 is not set +# CONFIG_BACKLIGHT_KTZ8866 is not set +# CONFIG_BACKLIGHT_SY7758 is not set +# CONFIG_BACKLIGHT_MAX25014 is not set +CONFIG_BACKLIGHT_APPLE_DWI=y +CONFIG_BACKLIGHT_APPLE_PMIC=y +# CONFIG_BACKLIGHT_QCOM_WLED is not set +# CONFIG_BACKLIGHT_ADP8860 is not set +# CONFIG_BACKLIGHT_ADP8870 is not set +# CONFIG_BACKLIGHT_LM3509 is not set +# CONFIG_BACKLIGHT_LM3639 is not set +# CONFIG_BACKLIGHT_GPIO is not set +# CONFIG_BACKLIGHT_LV5207LP is not set +# CONFIG_BACKLIGHT_BD6107 is not set +# CONFIG_BACKLIGHT_ARCXCNN is not set +# CONFIG_BACKLIGHT_LED is not set +# end of Backlight & LCD device support + +CONFIG_VIDEOMODE_HELPERS=y +CONFIG_HDMI=y +# CONFIG_FIRMWARE_EDID is not set + +# +# Console display driver support +# +CONFIG_DUMMY_CONSOLE=y +CONFIG_DUMMY_CONSOLE_COLUMNS=80 +CONFIG_DUMMY_CONSOLE_ROWS=25 +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION is not set +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y +# end of Console display driver support + +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_LOGO_LINUX_CLUT224_FILE="drivers/video/logo/logo_linux_clut224.ppm" +# CONFIG_TRACE_GPU_MEM is not set +# end of Graphics support + +# CONFIG_DRM_ACCEL is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_DMAENGINE_PCM=y +CONFIG_SND_JACK=y +CONFIG_SND_JACK_INPUT_DEV=y +# CONFIG_SND_OSSEMUL is not set +CONFIG_SND_PCM_TIMER=y +# CONFIG_SND_HRTIMER is not set +# CONFIG_SND_DYNAMIC_MINORS is not set +# CONFIG_SND_SUPPORT_OLD_API is not set +CONFIG_SND_PROC_FS=y +CONFIG_SND_VERBOSE_PROCFS=y +CONFIG_SND_CTL_FAST_LOOKUP=y +# CONFIG_SND_DEBUG is not set +# CONFIG_SND_CTL_INPUT_VALIDATION is not set +# CONFIG_SND_UTIMER is not set +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_ALOOP is not set +# CONFIG_SND_PCMTEST is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_SERIAL_GENERIC is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_PCI=y +# CONFIG_SND_AD1889 is not set +# CONFIG_SND_ALS300 is not set +# CONFIG_SND_ALI5451 is not set +# CONFIG_SND_ATIIXP is not set +# CONFIG_SND_ATIIXP_MODEM is not set +# CONFIG_SND_AU8810 is not set +# CONFIG_SND_AU8820 is not set +# CONFIG_SND_AU8830 is not set +# CONFIG_SND_AW2 is not set +# CONFIG_SND_AZT3328 is not set +# CONFIG_SND_BT87X is not set +# CONFIG_SND_CA0106 is not set +# CONFIG_SND_CMIPCI is not set +# CONFIG_SND_OXYGEN is not set +# CONFIG_SND_CS4281 is not set +# CONFIG_SND_CS46XX is not set +# CONFIG_SND_CTXFI is not set +# CONFIG_SND_DARLA20 is not set +# CONFIG_SND_GINA20 is not set +# CONFIG_SND_LAYLA20 is not set +# CONFIG_SND_DARLA24 is not set +# CONFIG_SND_GINA24 is not set +# CONFIG_SND_LAYLA24 is not set +# CONFIG_SND_MONA is not set +# CONFIG_SND_MIA is not set +# CONFIG_SND_ECHO3G is not set +# CONFIG_SND_INDIGO is not set +# CONFIG_SND_INDIGOIO is not set +# CONFIG_SND_INDIGODJ is not set +# CONFIG_SND_INDIGOIOX is not set +# CONFIG_SND_INDIGODJX is not set +# CONFIG_SND_EMU10K1 is not set +# CONFIG_SND_EMU10K1X is not set +# CONFIG_SND_ENS1370 is not set +# CONFIG_SND_ENS1371 is not set +# CONFIG_SND_ES1938 is not set +# CONFIG_SND_ES1968 is not set +# CONFIG_SND_FM801 is not set +# CONFIG_SND_HDSP is not set +# CONFIG_SND_HDSPM is not set +# CONFIG_SND_ICE1712 is not set +# CONFIG_SND_ICE1724 is not set +# CONFIG_SND_INTEL8X0 is not set +# CONFIG_SND_INTEL8X0M is not set +# CONFIG_SND_KORG1212 is not set +# CONFIG_SND_LOLA is not set +# CONFIG_SND_LX6464ES is not set +# CONFIG_SND_MAESTRO3 is not set +# CONFIG_SND_MIXART is not set +# CONFIG_SND_NM256 is not set +# CONFIG_SND_PCXHR is not set +# CONFIG_SND_RIPTIDE is not set +# CONFIG_SND_RME32 is not set +# CONFIG_SND_RME96 is not set +# CONFIG_SND_RME9652 is not set +# CONFIG_SND_SE6X is not set +# CONFIG_SND_SONICVIBES is not set +# CONFIG_SND_TRIDENT is not set +# CONFIG_SND_VIA82XX is not set +# CONFIG_SND_VIA82XX_MODEM is not set +# CONFIG_SND_VIRTUOSO is not set +# CONFIG_SND_VX222 is not set +# CONFIG_SND_YMFPCI is not set + +# +# HD-Audio +# +# CONFIG_SND_HDA_INTEL is not set +# end of HD-Audio + +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_UA101 is not set +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_USB_6FIRE is not set +# CONFIG_SND_USB_HIFACE is not set +# CONFIG_SND_BCD2000 is not set +# CONFIG_SND_USB_POD is not set +# CONFIG_SND_USB_PODHD is not set +# CONFIG_SND_USB_TONEPORT is not set +# CONFIG_SND_USB_VARIAX is not set +CONFIG_SND_SOC=y +CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y + +# +# Analog Devices +# +# CONFIG_SND_SOC_ADI_AXI_I2S is not set +# CONFIG_SND_SOC_ADI_AXI_SPDIF is not set +# end of Analog Devices + +# +# AMD +# +# CONFIG_SND_SOC_AMD_ACP is not set +# CONFIG_SND_AMD_ACP_CONFIG is not set +# end of AMD + +# +# Apple +# +# CONFIG_SND_SOC_APPLE_MCA is not set +CONFIG_SND_SOC_APPLE_S8000_MCA=y +# end of Apple + +# +# Atmel +# +# CONFIG_SND_SOC_MIKROE_PROTO is not set +# end of Atmel + +# +# Au1x +# +# end of Au1x + +# +# Broadcom +# +# CONFIG_SND_BCM63XX_I2S_WHISTLER is not set +# end of Broadcom + +# +# Cirrus Logic +# +# end of Cirrus Logic + +# +# DesignWare +# +# CONFIG_SND_DESIGNWARE_I2S is not set +# end of DesignWare + +# +# Freescale +# + +# +# Common SoC Audio options for Freescale CPUs: +# +# CONFIG_SND_SOC_FSL_ASRC is not set +# CONFIG_SND_SOC_FSL_SAI is not set +# CONFIG_SND_SOC_FSL_AUDMIX is not set +# CONFIG_SND_SOC_FSL_SSI is not set +# CONFIG_SND_SOC_FSL_SPDIF is not set +# CONFIG_SND_SOC_FSL_ESAI is not set +# CONFIG_SND_SOC_FSL_MICFIL is not set +# CONFIG_SND_SOC_FSL_XCVR is not set +# CONFIG_SND_SOC_IMX_AUDMUX is not set +# end of Freescale + +# +# Google +# +# CONFIG_SND_SOC_CHV3_I2S is not set +# end of Google + +# +# Hisilicon +# +# CONFIG_SND_I2S_HI6210_I2S is not set +# end of Hisilicon + +# +# JZ4740 +# +# end of JZ4740 + +# +# Kirkwood +# +# end of Kirkwood + +# +# Loongson +# +# end of Loongson + +# +# Intel +# +# end of Intel + +# +# Mediatek +# +# CONFIG_SND_SOC_MTK_BTCVSD is not set +# end of Mediatek + +# +# PXA +# +# end of PXA + +# +# SoundWire (SDCA) +# +CONFIG_SND_SOC_SDCA_OPTIONAL=y +# end of SoundWire (SDCA) + +# +# ST SPEAr +# +# end of ST SPEAr + +# +# Spreadtrum +# +# end of Spreadtrum + +# +# STMicroelectronics STM32 +# +# end of STMicroelectronics STM32 + +# +# Tegra +# +# end of Tegra + +# +# Xilinx +# +# CONFIG_SND_SOC_XILINX_I2S is not set +# CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER is not set +# CONFIG_SND_SOC_XILINX_SPDIF is not set +# end of Xilinx + +# +# Xtensa +# +# CONFIG_SND_SOC_XTFPGA_I2S is not set +# end of Xtensa + +# CONFIG_SND_SOC_SOF_TOPLEVEL is not set +CONFIG_SND_SOC_I2C_AND_SPI=y + +# +# CODEC drivers +# +# CONFIG_SND_SOC_AC97_CODEC is not set +# CONFIG_SND_SOC_ADAU1372_I2C is not set +# CONFIG_SND_SOC_ADAU1372_SPI is not set +# CONFIG_SND_SOC_ADAU1373 is not set +# CONFIG_SND_SOC_ADAU1701 is not set +# CONFIG_SND_SOC_ADAU1761_I2C is not set +# CONFIG_SND_SOC_ADAU1761_SPI is not set +# CONFIG_SND_SOC_ADAU7002 is not set +# CONFIG_SND_SOC_ADAU7118_HW is not set +# CONFIG_SND_SOC_ADAU7118_I2C is not set +# CONFIG_SND_SOC_AK4104 is not set +# CONFIG_SND_SOC_AK4118 is not set +# CONFIG_SND_SOC_AK4375 is not set +# CONFIG_SND_SOC_AK4458 is not set +# CONFIG_SND_SOC_AK4554 is not set +# CONFIG_SND_SOC_AK4613 is not set +# CONFIG_SND_SOC_AK4619 is not set +# CONFIG_SND_SOC_AK4642 is not set +# CONFIG_SND_SOC_AK5386 is not set +# CONFIG_SND_SOC_AK5558 is not set +# CONFIG_SND_SOC_ALC5623 is not set +# CONFIG_SND_SOC_AUDIO_IIO_AUX is not set +# CONFIG_SND_SOC_AW8738 is not set +# CONFIG_SND_SOC_AW88395 is not set +# CONFIG_SND_SOC_AW88166 is not set +# CONFIG_SND_SOC_AW88261 is not set +# CONFIG_SND_SOC_AW88081 is not set +# CONFIG_SND_SOC_AW87390 is not set +# CONFIG_SND_SOC_AW88399 is not set +# CONFIG_SND_SOC_BD28623 is not set +# CONFIG_SND_SOC_BT_SCO is not set +# CONFIG_SND_SOC_CHV3_CODEC is not set +# CONFIG_SND_SOC_CS35L32 is not set +# CONFIG_SND_SOC_CS35L33 is not set +# CONFIG_SND_SOC_CS35L34 is not set +# CONFIG_SND_SOC_CS35L35 is not set +# CONFIG_SND_SOC_CS35L36 is not set +# CONFIG_SND_SOC_CS35L41_SPI is not set +# CONFIG_SND_SOC_CS35L41_I2C is not set +# CONFIG_SND_SOC_CS35L45_SPI is not set +# CONFIG_SND_SOC_CS35L45_I2C is not set +# CONFIG_SND_SOC_CS35L56_I2C is not set +# CONFIG_SND_SOC_CS35L56_SPI is not set +# CONFIG_SND_SOC_CS42L42 is not set +# CONFIG_SND_SOC_CS42L51_I2C is not set +# CONFIG_SND_SOC_CS42L52 is not set +# CONFIG_SND_SOC_CS42L56 is not set +# CONFIG_SND_SOC_CS42L73 is not set +CONFIG_SND_SOC_CS42L81=y +# CONFIG_SND_SOC_CS42L83 is not set +# CONFIG_SND_SOC_CS42L84 is not set +# CONFIG_SND_SOC_CS4234 is not set +# CONFIG_SND_SOC_CS4265 is not set +# CONFIG_SND_SOC_CS4270 is not set +# CONFIG_SND_SOC_CS4271_I2C is not set +# CONFIG_SND_SOC_CS4271_SPI is not set +# CONFIG_SND_SOC_CS42XX8_SPI is not set +# CONFIG_SND_SOC_CS42XX8_I2C is not set +# CONFIG_SND_SOC_CS43130 is not set +# CONFIG_SND_SOC_CS4341 is not set +# CONFIG_SND_SOC_CS4349 is not set +# CONFIG_SND_SOC_CS48L32 is not set +# CONFIG_SND_SOC_CS53L30 is not set +# CONFIG_SND_SOC_CS530X_I2C is not set +# CONFIG_SND_SOC_CS530X_SPI is not set +# CONFIG_SND_SOC_CX2072X is not set +# CONFIG_SND_SOC_DA7213 is not set +# CONFIG_SND_SOC_DMIC is not set +# CONFIG_SND_SOC_ES7134 is not set +# CONFIG_SND_SOC_ES7241 is not set +# CONFIG_SND_SOC_ES8311 is not set +# CONFIG_SND_SOC_ES8316 is not set +# CONFIG_SND_SOC_ES8323 is not set +# CONFIG_SND_SOC_ES8326 is not set +# CONFIG_SND_SOC_ES8328_I2C is not set +# CONFIG_SND_SOC_ES8328_SPI is not set +# CONFIG_SND_SOC_ES8375 is not set +# CONFIG_SND_SOC_ES8389 is not set +# CONFIG_SND_SOC_FS210X is not set +# CONFIG_SND_SOC_GTM601 is not set +# CONFIG_SND_SOC_HDA is not set +# CONFIG_SND_SOC_ICS43432 is not set +# CONFIG_SND_SOC_IDT821034 is not set +# CONFIG_SND_SOC_MAX98088 is not set +# CONFIG_SND_SOC_MAX98090 is not set +# CONFIG_SND_SOC_MAX98357A is not set +# CONFIG_SND_SOC_MAX98504 is not set +# CONFIG_SND_SOC_MAX9867 is not set +# CONFIG_SND_SOC_MAX98927 is not set +# CONFIG_SND_SOC_MAX98520 is not set +# CONFIG_SND_SOC_MAX98373_I2C is not set +# CONFIG_SND_SOC_MAX98388 is not set +# CONFIG_SND_SOC_MAX98390 is not set +# CONFIG_SND_SOC_MAX98396 is not set +# CONFIG_SND_SOC_MAX9860 is not set +# CONFIG_SND_SOC_MSM8916_WCD_ANALOG is not set +# CONFIG_SND_SOC_MSM8916_WCD_DIGITAL is not set +# CONFIG_SND_SOC_PCM1681 is not set +# CONFIG_SND_SOC_PCM1754 is not set +# CONFIG_SND_SOC_PCM1789_I2C is not set +# CONFIG_SND_SOC_PCM179X_I2C is not set +# CONFIG_SND_SOC_PCM179X_SPI is not set +# CONFIG_SND_SOC_PCM186X_I2C is not set +# CONFIG_SND_SOC_PCM186X_SPI is not set +# CONFIG_SND_SOC_PCM3060_I2C is not set +# CONFIG_SND_SOC_PCM3060_SPI is not set +# CONFIG_SND_SOC_PCM3168A_I2C is not set +# CONFIG_SND_SOC_PCM3168A_SPI is not set +# CONFIG_SND_SOC_PCM5102A is not set +# CONFIG_SND_SOC_PCM512x_I2C is not set +# CONFIG_SND_SOC_PCM512x_SPI is not set +# CONFIG_SND_SOC_PCM6240 is not set +# CONFIG_SND_SOC_PEB2466 is not set +# CONFIG_SND_SOC_RT5575 is not set +# CONFIG_SND_SOC_RT5616 is not set +# CONFIG_SND_SOC_RT5631 is not set +# CONFIG_SND_SOC_RT5640 is not set +# CONFIG_SND_SOC_RT5645 is not set +# CONFIG_SND_SOC_RT5659 is not set +# CONFIG_SND_SOC_RT5677 is not set +# CONFIG_SND_SOC_RT9120 is not set +# CONFIG_SND_SOC_RT9123 is not set +# CONFIG_SND_SOC_RT9123P is not set +# CONFIG_SND_SOC_RTQ9124 is not set +# CONFIG_SND_SOC_RTQ9128 is not set +# CONFIG_SND_SOC_SGTL5000 is not set +# CONFIG_SND_SOC_SIMPLE_AMPLIFIER is not set +# CONFIG_SND_SOC_SIMPLE_MUX is not set +# CONFIG_SND_SOC_SMA1303 is not set +# CONFIG_SND_SOC_SMA1307 is not set +# CONFIG_SND_SOC_SPDIF is not set +# CONFIG_SND_SOC_SRC4XXX_I2C is not set +# CONFIG_SND_SOC_SSM2305 is not set +# CONFIG_SND_SOC_SSM2518 is not set +# CONFIG_SND_SOC_SSM2602_SPI is not set +# CONFIG_SND_SOC_SSM2602_I2C is not set +# CONFIG_SND_SOC_SSM3515 is not set +# CONFIG_SND_SOC_SSM4567 is not set +# CONFIG_SND_SOC_STA32X is not set +# CONFIG_SND_SOC_STA350 is not set +# CONFIG_SND_SOC_STI_SAS is not set +# CONFIG_SND_SOC_TAS2552 is not set +# CONFIG_SND_SOC_TAS2562 is not set +# CONFIG_SND_SOC_TAS2764 is not set +# CONFIG_SND_SOC_TAS2770 is not set +# CONFIG_SND_SOC_TAS2780 is not set +# CONFIG_SND_SOC_TAS2781_I2C is not set +# CONFIG_SND_SOC_TAS5086 is not set +# CONFIG_SND_SOC_TAS571X is not set +# CONFIG_SND_SOC_TAS5720 is not set +# CONFIG_SND_SOC_TAS5805M is not set +# CONFIG_SND_SOC_TAS6424 is not set +# CONFIG_SND_SOC_TAS675X is not set +# CONFIG_SND_SOC_TDA7419 is not set +# CONFIG_SND_SOC_TFA9879 is not set +# CONFIG_SND_SOC_TFA989X is not set +# CONFIG_SND_SOC_TLV320ADC3XXX is not set +# CONFIG_SND_SOC_TLV320AIC23_I2C is not set +# CONFIG_SND_SOC_TLV320AIC23_SPI is not set +# CONFIG_SND_SOC_TLV320AIC31XX is not set +# CONFIG_SND_SOC_TLV320AIC32X4_I2C is not set +# CONFIG_SND_SOC_TLV320AIC32X4_SPI is not set +# CONFIG_SND_SOC_TLV320AIC3X_I2C is not set +# CONFIG_SND_SOC_TLV320AIC3X_SPI is not set +# CONFIG_SND_SOC_TLV320ADCX140 is not set +# CONFIG_SND_SOC_TS3A227E is not set +# CONFIG_SND_SOC_TSCS42XX is not set +# CONFIG_SND_SOC_TSCS454 is not set +# CONFIG_SND_SOC_UDA1334 is not set +# CONFIG_SND_SOC_UDA1342 is not set +# CONFIG_SND_SOC_UDA1380 is not set +# CONFIG_SND_SOC_WM8510 is not set +# CONFIG_SND_SOC_WM8523 is not set +# CONFIG_SND_SOC_WM8524 is not set +# CONFIG_SND_SOC_WM8580 is not set +# CONFIG_SND_SOC_WM8711 is not set +# CONFIG_SND_SOC_WM8728 is not set +# CONFIG_SND_SOC_WM8731_I2C is not set +# CONFIG_SND_SOC_WM8731_SPI is not set +# CONFIG_SND_SOC_WM8737 is not set +# CONFIG_SND_SOC_WM8741 is not set +# CONFIG_SND_SOC_WM8750 is not set +# CONFIG_SND_SOC_WM8753 is not set +# CONFIG_SND_SOC_WM8770 is not set +# CONFIG_SND_SOC_WM8776 is not set +# CONFIG_SND_SOC_WM8782 is not set +# CONFIG_SND_SOC_WM8804_I2C is not set +# CONFIG_SND_SOC_WM8804_SPI is not set +# CONFIG_SND_SOC_WM8903 is not set +# CONFIG_SND_SOC_WM8904 is not set +# CONFIG_SND_SOC_WM8940 is not set +# CONFIG_SND_SOC_WM8960 is not set +# CONFIG_SND_SOC_WM8961 is not set +# CONFIG_SND_SOC_WM8962 is not set +# CONFIG_SND_SOC_WM8974 is not set +# CONFIG_SND_SOC_WM8978 is not set +# CONFIG_SND_SOC_WM8985 is not set +# CONFIG_SND_SOC_WSA885X is not set +# CONFIG_SND_SOC_ZL38060 is not set +# CONFIG_SND_SOC_MAX9759 is not set +# CONFIG_SND_SOC_MT6351 is not set +# CONFIG_SND_SOC_MT6357 is not set +# CONFIG_SND_SOC_MT6358 is not set +# CONFIG_SND_SOC_MT6660 is not set +# CONFIG_SND_SOC_NAU8315 is not set +# CONFIG_SND_SOC_NAU8325 is not set +# CONFIG_SND_SOC_NAU8540 is not set +# CONFIG_SND_SOC_NAU8810 is not set +# CONFIG_SND_SOC_NAU8821 is not set +# CONFIG_SND_SOC_NAU8822 is not set +# CONFIG_SND_SOC_NAU8824 is not set +# CONFIG_SND_SOC_NTP8918 is not set +# CONFIG_SND_SOC_NTP8835 is not set +# CONFIG_SND_SOC_TPA6130A2 is not set +# CONFIG_SND_SOC_LPASS_WSA_MACRO is not set +# CONFIG_SND_SOC_LPASS_VA_MACRO is not set +# CONFIG_SND_SOC_LPASS_RX_MACRO is not set +# CONFIG_SND_SOC_LPASS_TX_MACRO is not set +# end of CODEC drivers + +# +# Generic drivers +# +CONFIG_SND_SIMPLE_CARD_UTILS=y +CONFIG_SND_SIMPLE_CARD=y +# CONFIG_SND_AUDIO_GRAPH_CARD is not set +# CONFIG_SND_AUDIO_GRAPH_CARD2 is not set +# CONFIG_SND_TEST_COMPONENT is not set +# end of Generic drivers + +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_GENERIC=y +# CONFIG_HID_HAPTIC is not set + +# +# Special HID drivers +# +# CONFIG_HID_A4TECH is not set +# CONFIG_HID_ACCUTOUCH is not set +# CONFIG_HID_ACRUX is not set +CONFIG_HID_APPLE=y +# CONFIG_HID_APPLEIR is not set +# CONFIG_HID_ASUS is not set +# CONFIG_HID_AUREAL is not set +# CONFIG_HID_BELKIN is not set +# CONFIG_HID_BETOP_FF is not set +# CONFIG_HID_BIGBEN_FF is not set +# CONFIG_HID_CHERRY is not set +# CONFIG_HID_CHICONY is not set +# CONFIG_HID_CORSAIR is not set +# CONFIG_HID_COUGAR is not set +# CONFIG_HID_MACALLY is not set +# CONFIG_HID_PRODIKEYS is not set +# CONFIG_HID_CMEDIA is not set +# CONFIG_HID_CP2112 is not set +# CONFIG_HID_CREATIVE_SB0540 is not set +# CONFIG_HID_CYPRESS is not set +# CONFIG_HID_DRAGONRISE is not set +# CONFIG_HID_EMS_FF is not set +# CONFIG_HID_ELAN is not set +# CONFIG_HID_ELECOM is not set +# CONFIG_HID_ELO is not set +# CONFIG_HID_EVISION is not set +# CONFIG_HID_EZKEY is not set +# CONFIG_HID_FT260 is not set +# CONFIG_HID_GEMBIRD is not set +# CONFIG_HID_GFRM is not set +# CONFIG_HID_GLORIOUS is not set +# CONFIG_HID_HOLTEK is not set +# CONFIG_HID_HYPERX is not set +# CONFIG_HID_GOODIX_SPI is not set +# CONFIG_HID_GOOGLE_STADIA_FF is not set +# CONFIG_HID_VIVALDI is not set +# CONFIG_HID_GT683R is not set +# CONFIG_HID_MSI is not set +# CONFIG_HID_KEYTOUCH is not set +# CONFIG_HID_KYE is not set +# CONFIG_HID_KYSONA is not set +# CONFIG_HID_UCLOGIC is not set +# CONFIG_HID_WALTOP is not set +# CONFIG_HID_VIEWSONIC is not set +# CONFIG_HID_VRC2 is not set +# CONFIG_HID_XIAOMI is not set +# CONFIG_HID_GYRATION is not set +# CONFIG_HID_ICADE is not set +# CONFIG_HID_ITE is not set +# CONFIG_HID_JABRA is not set +# CONFIG_HID_TWINHAN is not set +# CONFIG_HID_KENSINGTON is not set +# CONFIG_HID_LCPOWER is not set +# CONFIG_HID_LED is not set +# CONFIG_HID_LENOVO is not set +# CONFIG_HID_LETSKETCH is not set +# CONFIG_HID_MAGICMOUSE is not set +# CONFIG_HID_MALTRON is not set +# CONFIG_HID_MAYFLASH is not set +# CONFIG_HID_MEGAWORLD_FF is not set +# CONFIG_HID_RAKK is not set +# CONFIG_HID_REDRAGON is not set +# CONFIG_HID_MICROSOFT is not set +# CONFIG_HID_MONTEREY is not set +# CONFIG_HID_MULTITOUCH is not set +# CONFIG_HID_NINTENDO is not set +# CONFIG_HID_NTI is not set +# CONFIG_HID_NTRIG is not set +# CONFIG_HID_NVIDIA_SHIELD is not set +# CONFIG_HID_ORTEK is not set +# CONFIG_HID_PANTHERLORD is not set +# CONFIG_HID_PENMOUNT is not set +# CONFIG_HID_PETALYNX is not set +# CONFIG_HID_PICOLCD is not set +# CONFIG_HID_PLANTRONICS is not set +# CONFIG_HID_PXRC is not set +# CONFIG_HID_RAPOO is not set +# CONFIG_HID_RAZER is not set +# CONFIG_HID_PRIMAX is not set +# CONFIG_HID_RETRODE is not set +# CONFIG_HID_ROCCAT is not set +# CONFIG_HID_SAITEK is not set +# CONFIG_HID_SAMSUNG is not set +# CONFIG_HID_SEMITEK is not set +# CONFIG_HID_SIGMAMICRO is not set +# CONFIG_HID_SONY is not set +# CONFIG_HID_SPEEDLINK is not set +# CONFIG_HID_STEAM is not set +# CONFIG_HID_STEELSERIES is not set +# CONFIG_HID_SUNPLUS is not set +# CONFIG_HID_RMI is not set +# CONFIG_HID_GREENASIA is not set +# CONFIG_HID_SMARTJOYPLUS is not set +# CONFIG_HID_TIVO is not set +# CONFIG_HID_TOPSEED is not set +# CONFIG_HID_TOPRE is not set +# CONFIG_HID_THINGM is not set +# CONFIG_HID_THRUSTMASTER is not set +# CONFIG_HID_UDRAW_PS3 is not set +# CONFIG_HID_U2FZERO is not set +# CONFIG_HID_WACOM is not set +# CONFIG_HID_WIIMOTE is not set +# CONFIG_HID_WINWING is not set +# CONFIG_HID_XINMO is not set +# CONFIG_HID_ZEROPLUS is not set +# CONFIG_HID_ZYDACRON is not set +# CONFIG_HID_SENSOR_HUB is not set +# CONFIG_HID_ALPS is not set +# CONFIG_HID_MCP2200 is not set +# CONFIG_HID_MCP2221 is not set +# CONFIG_HID_HUAWEI is not set +# end of Special HID drivers + +# +# HID-BPF support +# +# end of HID-BPF support + +# CONFIG_I2C_HID is not set + +# +# USB HID support +# +CONFIG_USB_HID=y +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set +# end of USB HID support + +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +CONFIG_USB_SUPPORT=y +CONFIG_USB_COMMON=y +CONFIG_USB_ULPI_BUS=y +# CONFIG_USB_CONN_GPIO is not set +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB=y +CONFIG_USB_PCI=y +# CONFIG_USB_PCI_AMD is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y + +# +# Miscellaneous USB options +# +CONFIG_USB_DEFAULT_PERSIST=y +# CONFIG_USB_FEW_INIT_RETRIES is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_PRODUCTLIST is not set +# CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB is not set +CONFIG_USB_AUTOSUSPEND_DELAY=2 +CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=1 +# CONFIG_USB_MON is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_XHCI_HCD=y +# CONFIG_USB_XHCI_DBGCAP is not set +CONFIG_USB_XHCI_PCI=y +# CONFIG_USB_XHCI_PCI_RENESAS is not set +CONFIG_USB_XHCI_PLATFORM=y +# CONFIG_USB_XHCI_SIDEBAND is not set +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +CONFIG_USB_EHCI_TT_NEWSCHED=y +CONFIG_USB_EHCI_PCI=y +# CONFIG_USB_EHCI_FSL is not set +CONFIG_USB_EHCI_HCD_PLATFORM=y +# CONFIG_USB_OXU210HP_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_MAX3421_HCD is not set +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_HCD_PCI=y +CONFIG_USB_OHCI_HCD_PLATFORM=y +# CONFIG_USB_UHCI_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HCD_TEST_MODE is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_REALTEK is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_STORAGE_ENE_UB6250 is not set +CONFIG_USB_UAS=y + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +# CONFIG_USBIP_CORE is not set + +# +# USB dual-mode controller drivers +# +# CONFIG_USB_CDNS_SUPPORT is not set +# CONFIG_USB_MUSB_HDRC is not set +# CONFIG_USB_DWC3 is not set +CONFIG_USB_DWC2=y +# CONFIG_USB_DWC2_HOST is not set + +# +# Gadget/Dual-role mode requires USB Gadget support to be enabled +# +# CONFIG_USB_DWC2_PERIPHERAL is not set +CONFIG_USB_DWC2_DUAL_ROLE=y +# CONFIG_USB_DWC2_PCI is not set +# CONFIG_USB_DWC2_DEBUG is not set +# CONFIG_USB_DWC2_TRACK_MISSED_SOFS is not set +# CONFIG_USB_CHIPIDEA is not set +# CONFIG_USB_ISP1760 is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_APPLE_MFI_FASTCHARGE is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_EHSET_TEST_FIXTURE is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_YUREX is not set +# CONFIG_USB_EZUSB_FX2 is not set +# CONFIG_USB_HUB_USB251XB is not set +# CONFIG_USB_HSIC_USB3503 is not set +# CONFIG_USB_HSIC_USB4604 is not set +# CONFIG_USB_LINK_LAYER_TEST is not set +# CONFIG_USB_CHAOSKEY is not set +# CONFIG_USB_ONBOARD_DEV is not set + +# +# USB Physical Layer drivers +# +# CONFIG_NOP_USB_XCEIV is not set +# CONFIG_USB_GPIO_VBUS is not set +# CONFIG_USB_ISP1301 is not set +CONFIG_USB_ULPI=y +CONFIG_USB_ULPI_VIEWPORT=y +# end of USB Physical Layer drivers + +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG is not set +# CONFIG_USB_GADGET_DEBUG_FILES is not set +# CONFIG_USB_GADGET_DEBUG_FS is not set +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=2 +CONFIG_U_SERIAL_CONSOLE=y + +# +# USB Peripheral Controller +# +# CONFIG_USB_GR_UDC is not set +# CONFIG_USB_R8A66597 is not set +# CONFIG_USB_PXA27X is not set +# CONFIG_USB_SNP_UDC_PLAT is not set +# CONFIG_USB_M66592 is not set +# CONFIG_USB_BDC_UDC is not set +# CONFIG_USB_AMD5536UDC is not set +# CONFIG_USB_NET2280 is not set +# CONFIG_USB_GOKU is not set +# CONFIG_USB_EG20T is not set +# CONFIG_USB_GADGET_XILINX is not set +# CONFIG_USB_MAX3420_UDC is not set +# CONFIG_USB_DUMMY_HCD is not set +# end of USB Peripheral Controller + +CONFIG_USB_LIBCOMPOSITE=y +CONFIG_USB_F_ACM=y +CONFIG_USB_U_SERIAL=y +CONFIG_USB_U_ETHER=y +CONFIG_USB_F_SERIAL=y +CONFIG_USB_F_NCM=y +CONFIG_USB_F_ECM=y +CONFIG_USB_F_EEM=y +CONFIG_USB_F_MASS_STORAGE=y +CONFIG_USB_F_FS=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +# CONFIG_USB_CONFIGFS_OBEX is not set +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +# CONFIG_USB_CONFIGFS_ECM_SUBSET is not set +# CONFIG_USB_CONFIGFS_RNDIS is not set +CONFIG_USB_CONFIGFS_EEM=y +# CONFIG_USB_CONFIGFS_MASS_STORAGE is not set +# CONFIG_USB_CONFIGFS_F_LB_SS is not set +CONFIG_USB_CONFIGFS_F_FS=y +# CONFIG_USB_CONFIGFS_F_UAC1 is not set +# CONFIG_USB_CONFIGFS_F_UAC1_LEGACY is not set +# CONFIG_USB_CONFIGFS_F_UAC2 is not set +# CONFIG_USB_CONFIGFS_F_MIDI is not set +# CONFIG_USB_CONFIGFS_F_MIDI2 is not set +# CONFIG_USB_CONFIGFS_F_HID is not set +# CONFIG_USB_CONFIGFS_F_UVC is not set +# CONFIG_USB_CONFIGFS_F_PRINTER is not set + +# +# USB Gadget precomposed configurations +# +# CONFIG_USB_ZERO is not set +# CONFIG_USB_AUDIO is not set +# CONFIG_USB_ETH is not set +# CONFIG_USB_G_NCM is not set +CONFIG_USB_GADGETFS=y +# CONFIG_USB_FUNCTIONFS is not set +# CONFIG_USB_MASS_STORAGE is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +# CONFIG_USB_G_ACM_MS is not set +CONFIG_USB_G_MULTI=y +# CONFIG_USB_G_MULTI_RNDIS is not set +CONFIG_USB_G_MULTI_CDC=y +# CONFIG_USB_G_HID is not set +# CONFIG_USB_G_DBGP is not set +# CONFIG_USB_G_WEBCAM is not set +# CONFIG_USB_RAW_GADGET is not set +# end of USB Gadget precomposed configurations + +# CONFIG_TYPEC is not set +CONFIG_USB_ROLE_SWITCH=y +CONFIG_USB_ROLES_APPLE_S8000=y +# CONFIG_MMC is not set +# CONFIG_SCSI_UFSHCD is not set +# CONFIG_MEMSTICK is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +# CONFIG_LEDS_CLASS_FLASH is not set +# CONFIG_LEDS_CLASS_MULTICOLOR is not set +# CONFIG_LEDS_BRIGHTNESS_HW_CHANGED is not set + +# +# LED drivers +# +# CONFIG_LEDS_AN30259A is not set +# CONFIG_LEDS_OSRAM_AMS_AS3668 is not set +# CONFIG_LEDS_AW200XX is not set +# CONFIG_LEDS_AW2013 is not set +# CONFIG_LEDS_BCM6328 is not set +# CONFIG_LEDS_BCM6358 is not set +# CONFIG_LEDS_CR0014114 is not set +# CONFIG_LEDS_EL15203000 is not set +# CONFIG_LEDS_LM3530 is not set +# CONFIG_LEDS_LM3532 is not set +# CONFIG_LEDS_LM3642 is not set +# CONFIG_LEDS_LM3692X is not set +# CONFIG_LEDS_PCA9532 is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_LP3944 is not set +# CONFIG_LEDS_LP3952 is not set +# CONFIG_LEDS_LP8860 is not set +# CONFIG_LEDS_LP8864 is not set +# CONFIG_LEDS_PCA955X is not set +# CONFIG_LEDS_PCA963X is not set +# CONFIG_LEDS_PCA995X is not set +# CONFIG_LEDS_DAC124S085 is not set +# CONFIG_LEDS_REGULATOR is not set +# CONFIG_LEDS_BD2606MVV is not set +# CONFIG_LEDS_BD2802 is not set +# CONFIG_LEDS_LT3593 is not set +# CONFIG_LEDS_TCA6507 is not set +# CONFIG_LEDS_TLC591XX is not set +# CONFIG_LEDS_LM355x is not set +# CONFIG_LEDS_IS31FL319X is not set +# CONFIG_LEDS_IS31FL32XX is not set + +# +# LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM) +# +# CONFIG_LEDS_BLINKM is not set +# CONFIG_LEDS_SYSCON is not set +# CONFIG_LEDS_MLXREG is not set +# CONFIG_LEDS_USER is not set +# CONFIG_LEDS_SPI_BYTE is not set +# CONFIG_LEDS_LM3697 is not set +# CONFIG_LEDS_ST1202 is not set +# CONFIG_LEDS_LTC3220 is not set + +# +# Flash and Torch LED drivers +# + +# +# RGB LED drivers +# + +# +# LED Triggers +# +# CONFIG_LEDS_TRIGGERS is not set + +# +# Simatic LED drivers +# +# CONFIG_ACCESSIBILITY is not set +# CONFIG_INFINIBAND is not set +CONFIG_EDAC_SUPPORT=y +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +CONFIG_RTC_SYSTOHC=y +CONFIG_RTC_SYSTOHC_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set +CONFIG_RTC_NVMEM=y + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_ABB5ZES3 is not set +# CONFIG_RTC_DRV_ABEOZ9 is not set +# CONFIG_RTC_DRV_ABX80X is not set +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_HYM8563 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_MAX31335 is not set +# CONFIG_RTC_DRV_NCT3018Y is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_ISL12022 is not set +# CONFIG_RTC_DRV_ISL12026 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8523 is not set +# CONFIG_RTC_DRV_PCF85363 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_BQ32K is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8010 is not set +# CONFIG_RTC_DRV_RX8111 is not set +# CONFIG_RTC_DRV_RX8581 is not set +# CONFIG_RTC_DRV_RX8025 is not set +# CONFIG_RTC_DRV_EM3027 is not set +# CONFIG_RTC_DRV_RV3028 is not set +# CONFIG_RTC_DRV_RV3032 is not set +# CONFIG_RTC_DRV_RV8803 is not set +# CONFIG_RTC_DRV_SD2405AL is not set +# CONFIG_RTC_DRV_SD3078 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T93 is not set +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1302 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1343 is not set +# CONFIG_RTC_DRV_DS1347 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6916 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RX4581 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_PCF2123 is not set +# CONFIG_RTC_DRV_MCP795 is not set +CONFIG_RTC_I2C_AND_SPI=y + +# +# SPI and I2C RTC drivers +# +# CONFIG_RTC_DRV_DS3232 is not set +# CONFIG_RTC_DRV_PCF2127 is not set +# CONFIG_RTC_DRV_PCF85063 is not set +# CONFIG_RTC_DRV_RV3029C2 is not set +# CONFIG_RTC_DRV_RX6110 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1685_FAMILY is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_DS2404 is not set +CONFIG_RTC_DRV_APPLE_PMIC=y +# CONFIG_RTC_DRV_EFI is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_RP5C01 is not set +# CONFIG_RTC_DRV_ZYNQMP is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_DRV_PL030 is not set +# CONFIG_RTC_DRV_PL031 is not set +# CONFIG_RTC_DRV_CADENCE is not set +# CONFIG_RTC_DRV_FTRTC010 is not set +# CONFIG_RTC_DRV_R7301 is not set +# CONFIG_RTC_DRV_GOLDFISH is not set +CONFIG_RTC_DRV_MACSMC=y + +# +# HID Sensor RTC drivers +# +CONFIG_DMADEVICES=y +# CONFIG_DMADEVICES_DEBUG is not set + +# +# DMA Devices +# +CONFIG_DMA_ENGINE=y +CONFIG_DMA_VIRTUAL_CHANNELS=m +CONFIG_DMA_OF=y +# CONFIG_ALTERA_MSGDMA is not set +# CONFIG_AMBA_PL08X is not set +# CONFIG_APPLE_ADMAC is not set +CONFIG_APPLE_SIO_DMA=m +# CONFIG_ARM_DMA350 is not set +# CONFIG_DW_AXI_DMAC is not set +# CONFIG_FSL_EDMA is not set +# CONFIG_FSL_QDMA is not set +# CONFIG_INTEL_IDMA64 is not set +# CONFIG_MV_XOR_V2 is not set +# CONFIG_PL330_DMA is not set +# CONFIG_PLX_DMA is not set +# CONFIG_SWITCHTEC_DMA is not set +# CONFIG_XILINX_DMA is not set +# CONFIG_XILINX_XDMA is not set +# CONFIG_XILINX_ZYNQMP_DMA is not set +# CONFIG_XILINX_ZYNQMP_DPDMA is not set +# CONFIG_AMD_QDMA is not set +# CONFIG_QCOM_HIDMA_MGMT is not set +# CONFIG_QCOM_HIDMA is not set +# CONFIG_DW_DMAC is not set +# CONFIG_DW_DMAC_PCI is not set +# CONFIG_DW_EDMA is not set +# CONFIG_SF_PDMA is not set + +# +# DMA Clients +# +# CONFIG_ASYNC_TX_DMA is not set +# CONFIG_DMATEST is not set + +# +# DMABUF options +# +CONFIG_SYNC_FILE=y +# CONFIG_SW_SYNC is not set +CONFIG_UDMABUF=y +# CONFIG_DMABUF_DEBUG is not set +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_SYSTEM=y +# CONFIG_DMABUF_HEAPS_SYSTEM_CC_SHARED is not set +# CONFIG_DMABUF_HEAPS_CMA is not set +# end of DMABUF options + +# CONFIG_UIO is not set +# CONFIG_VFIO is not set +# CONFIG_VIRT_DRIVERS is not set +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VDPA is not set +# CONFIG_VHOST_MENU is not set + +# +# Microsoft Hyper-V guest support +# +# CONFIG_HYPERV is not set +# end of Microsoft Hyper-V guest support + +# CONFIG_GREYBUS is not set +# CONFIG_COMEDI is not set +# CONFIG_GPIB is not set +# CONFIG_STAGING is not set +# CONFIG_GOLDFISH is not set +# CONFIG_CHROME_PLATFORMS is not set +# CONFIG_MELLANOX_PLATFORM is not set +# CONFIG_SURFACE_PLATFORMS is not set +# CONFIG_ARM64_PLATFORM_DEVICES is not set +CONFIG_HAVE_CLK=y +CONFIG_HAVE_CLK_PREPARE=y +CONFIG_COMMON_CLK=y + +# +# Clock driver for ARM Reference designs +# +# CONFIG_CLK_ICST is not set +# CONFIG_CLK_SP810 is not set +# end of Clock driver for ARM Reference designs + +# CONFIG_LMK04832 is not set +CONFIG_COMMON_CLK_APPLE_S8000_TOUCH=y +# CONFIG_COMMON_CLK_APPLE_NCO is not set +# CONFIG_COMMON_CLK_MAX9485 is not set +# CONFIG_COMMON_CLK_SI5341 is not set +# CONFIG_COMMON_CLK_SI5351 is not set +# CONFIG_COMMON_CLK_SI514 is not set +# CONFIG_COMMON_CLK_SI544 is not set +# CONFIG_COMMON_CLK_SI570 is not set +# CONFIG_COMMON_CLK_CDCE706 is not set +# CONFIG_COMMON_CLK_CDCE925 is not set +# CONFIG_COMMON_CLK_CS2000_CP is not set +# CONFIG_COMMON_CLK_AXI_CLKGEN is not set +# CONFIG_COMMON_CLK_XGENE is not set +# CONFIG_COMMON_CLK_RS9_PCIE is not set +# CONFIG_COMMON_CLK_SI521XX is not set +# CONFIG_COMMON_CLK_VC3 is not set +# CONFIG_COMMON_CLK_VC5 is not set +# CONFIG_COMMON_CLK_VC7 is not set +# CONFIG_COMMON_CLK_FIXED_MMIO is not set + +# +# Cixtech Audio Subsystem Clock Driver +# +# end of Cixtech Audio Subsystem Clock Driver + +# CONFIG_XILINX_VCU is not set +# CONFIG_COMMON_CLK_XLNX_CLKWZRD is not set +# CONFIG_HWSPINLOCK is not set + +# +# Clock Source drivers +# +CONFIG_TIMER_OF=y +CONFIG_TIMER_PROBE=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +CONFIG_ARM_ARCH_TIMER_OOL_WORKAROUND=y +CONFIG_FSL_ERRATUM_A008585=y +CONFIG_HISILICON_ERRATUM_161010101=y +CONFIG_ARM64_ERRATUM_858921=y +# CONFIG_ARM_TIMER_SP804 is not set +# end of Clock Source drivers + +# CONFIG_MAILBOX is not set +CONFIG_IOMMU_IOVA=y +CONFIG_IOMMU_API=y +CONFIG_IOMMU_SUPPORT=y + +# +# Generic IOMMU Pagetable Support +# +CONFIG_IOMMU_IO_PGTABLE=y +# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set +# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set +CONFIG_IOMMU_IO_PGTABLE_DART=y +# end of Generic IOMMU Pagetable Support + +# CONFIG_IOMMU_DEBUGFS is not set +CONFIG_IOMMU_DEFAULT_DMA_STRICT=y +# CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set +# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set +CONFIG_OF_IOMMU=y +CONFIG_IOMMU_DMA=y +# CONFIG_ARM_SMMU is not set +# CONFIG_ARM_SMMU_V3 is not set +# CONFIG_IOMMUFD is not set +CONFIG_APPLE_DART=y + +# +# Remoteproc drivers +# +# CONFIG_REMOTEPROC is not set +# end of Remoteproc drivers + +# +# Rpmsg drivers +# +# CONFIG_RPMSG_VIRTIO is not set +# end of Rpmsg drivers + +# CONFIG_SOUNDWIRE is not set + +# +# SOC (System On Chip) specific Drivers +# + +# +# Amlogic SoC drivers +# +# end of Amlogic SoC drivers + +# +# Apple SoC drivers +# +CONFIG_APPLE_MAILBOX=y +# CONFIG_APPLE_PMGR_MISC is not set +CONFIG_APPLE_RTKIT=y +CONFIG_APPLE_PMP_T8015_V1=y +CONFIG_APPLE_SIO=m +CONFIG_APPLE_SART=y +# end of Apple SoC drivers + +# +# Broadcom SoC drivers +# +# end of Broadcom SoC drivers + +# +# NXP/Freescale QorIQ SoC drivers +# +# CONFIG_QUICC_ENGINE is not set +# CONFIG_FSL_RCPM is not set +# end of NXP/Freescale QorIQ SoC drivers + +# +# fujitsu SoC drivers +# +# end of fujitsu SoC drivers + +# +# i.MX SoC drivers +# +# end of i.MX SoC drivers + +# +# Enable LiteX SoC Builder specific drivers +# +# CONFIG_LITEX_SOC_CONTROLLER is not set +# end of Enable LiteX SoC Builder specific drivers + +# CONFIG_WPCM450_SOC is not set +# CONFIG_SOC_TI is not set + +# +# Xilinx SoC drivers +# +# end of Xilinx SoC drivers +# end of SOC (System On Chip) specific Drivers + +# +# PM Domains +# + +# +# Amlogic PM Domains +# +# end of Amlogic PM Domains + +CONFIG_APPLE_PMGR_PWRSTATE=y + +# +# Broadcom PM Domains +# +# end of Broadcom PM Domains + +# +# i.MX PM Domains +# +# end of i.MX PM Domains + +# +# Qualcomm PM Domains +# +# end of Qualcomm PM Domains +# end of PM Domains + +CONFIG_PM_DEVFREQ=y + +# +# DEVFREQ Governors +# +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +# CONFIG_DEVFREQ_GOV_PERFORMANCE is not set +# CONFIG_DEVFREQ_GOV_POWERSAVE is not set +# CONFIG_DEVFREQ_GOV_USERSPACE is not set +# CONFIG_DEVFREQ_GOV_PASSIVE is not set + +# +# DEVFREQ Drivers +# +# CONFIG_PM_DEVFREQ_EVENT is not set +# CONFIG_EXTCON is not set +# CONFIG_MEMORY is not set +CONFIG_IIO=y +CONFIG_IIO_BUFFER=y +# CONFIG_IIO_BUFFER_CB is not set +# CONFIG_IIO_BUFFER_DMA is not set +# CONFIG_IIO_BUFFER_DMAENGINE is not set +# CONFIG_IIO_BUFFER_HW_CONSUMER is not set +CONFIG_IIO_KFIFO_BUF=y +CONFIG_IIO_TRIGGERED_BUFFER=y +# CONFIG_IIO_CONFIGFS is not set +CONFIG_IIO_TRIGGER=y +CONFIG_IIO_CONSUMERS_PER_TRIGGER=2 +# CONFIG_IIO_SW_DEVICE is not set +# CONFIG_IIO_SW_TRIGGER is not set +# CONFIG_IIO_TRIGGERED_EVENT is not set + +# +# Accelerometers +# +# CONFIG_ADIS16201 is not set +# CONFIG_ADIS16209 is not set +# CONFIG_ADXL313_I2C is not set +# CONFIG_ADXL313_SPI is not set +# CONFIG_ADXL345_I2C is not set +# CONFIG_ADXL345_SPI is not set +# CONFIG_ADXL355_I2C is not set +# CONFIG_ADXL355_SPI is not set +# CONFIG_ADXL367_SPI is not set +# CONFIG_ADXL367_I2C is not set +# CONFIG_ADXL372_SPI is not set +# CONFIG_ADXL372_I2C is not set +# CONFIG_ADXL380_SPI is not set +# CONFIG_ADXL380_I2C is not set +# CONFIG_BMA180 is not set +# CONFIG_BMA220 is not set +# CONFIG_BMA400 is not set +# CONFIG_BMC150_ACCEL is not set +# CONFIG_BMI088_ACCEL is not set +# CONFIG_DA280 is not set +# CONFIG_DA311 is not set +# CONFIG_DMARD06 is not set +# CONFIG_DMARD09 is not set +# CONFIG_DMARD10 is not set +# CONFIG_FXLS8962AF_I2C is not set +# CONFIG_FXLS8962AF_SPI is not set +# CONFIG_IIO_ST_ACCEL_3AXIS is not set +# CONFIG_IIO_KX022A_SPI is not set +# CONFIG_IIO_KX022A_I2C is not set +# CONFIG_KXSD9 is not set +# CONFIG_KXCJK1013 is not set +# CONFIG_MC3230 is not set +# CONFIG_MMA7455_I2C is not set +# CONFIG_MMA7455_SPI is not set +# CONFIG_MMA7660 is not set +# CONFIG_MMA8452 is not set +# CONFIG_MMA9551 is not set +# CONFIG_MMA9553 is not set +# CONFIG_MSA311 is not set +# CONFIG_MXC4005 is not set +# CONFIG_MXC6255 is not set +# CONFIG_SCA3000 is not set +# CONFIG_SCA3300 is not set +# CONFIG_STK8312 is not set +# CONFIG_STK8BA50 is not set +# end of Accelerometers + +# +# Analog to digital converters +# +# CONFIG_AD4000 is not set +# CONFIG_AD4080 is not set +# CONFIG_AD4130 is not set +# CONFIG_AD4134 is not set +# CONFIG_AD4170_4 is not set +# CONFIG_AD4691 is not set +# CONFIG_AD4695 is not set +# CONFIG_AD7091R5 is not set +# CONFIG_AD7091R8 is not set +# CONFIG_AD7124 is not set +# CONFIG_AD7173 is not set +# CONFIG_AD7191 is not set +# CONFIG_AD7192 is not set +# CONFIG_AD7266 is not set +# CONFIG_AD7280 is not set +# CONFIG_AD7291 is not set +# CONFIG_AD7292 is not set +# CONFIG_AD7298 is not set +# CONFIG_AD7380 is not set +# CONFIG_AD7476 is not set +# CONFIG_AD7606_IFACE_PARALLEL is not set +# CONFIG_AD7606_IFACE_SPI is not set +# CONFIG_AD7766 is not set +# CONFIG_AD7768_1 is not set +# CONFIG_AD7779 is not set +# CONFIG_AD7780 is not set +# CONFIG_AD7791 is not set +# CONFIG_AD7793 is not set +# CONFIG_AD7887 is not set +# CONFIG_AD7923 is not set +# CONFIG_AD7944 is not set +# CONFIG_AD7949 is not set +# CONFIG_AD799X is not set +# CONFIG_AD9467 is not set +# CONFIG_ADE9000 is not set +# CONFIG_CC10001_ADC is not set +# CONFIG_ENVELOPE_DETECTOR is not set +# CONFIG_GEHC_PMC_ADC is not set +# CONFIG_HI8435 is not set +# CONFIG_HX711 is not set +# CONFIG_INA2XX_ADC is not set +# CONFIG_LTC2309 is not set +# CONFIG_LTC2471 is not set +# CONFIG_LTC2485 is not set +# CONFIG_LTC2496 is not set +# CONFIG_LTC2497 is not set +# CONFIG_MAX1027 is not set +# CONFIG_MAX11100 is not set +# CONFIG_MAX1118 is not set +# CONFIG_MAX11205 is not set +# CONFIG_MAX11410 is not set +# CONFIG_MAX1241 is not set +# CONFIG_MAX1363 is not set +# CONFIG_MAX14001 is not set +# CONFIG_MAX34408 is not set +# CONFIG_MAX9611 is not set +# CONFIG_MCP320X is not set +# CONFIG_MCP3422 is not set +# CONFIG_MCP3564 is not set +# CONFIG_MCP3911 is not set +# CONFIG_NAU7802 is not set +# CONFIG_NCT7201 is not set +# CONFIG_PAC1921 is not set +# CONFIG_PAC1934 is not set +# CONFIG_QCOM_SPMI_IADC is not set +# CONFIG_QCOM_SPMI_VADC is not set +# CONFIG_QCOM_SPMI_ADC5 is not set +# CONFIG_QCOM_SPMI_ADC5_GEN3 is not set +# CONFIG_ROHM_BD79112 is not set +# CONFIG_ROHM_BD79124 is not set +# CONFIG_RICHTEK_RTQ6056 is not set +# CONFIG_SD_ADC_MODULATOR is not set +# CONFIG_TI_ADC081C is not set +# CONFIG_TI_ADC0832 is not set +# CONFIG_TI_ADC084S021 is not set +# CONFIG_TI_ADC108S102 is not set +# CONFIG_TI_ADC12138 is not set +# CONFIG_TI_ADC128S052 is not set +# CONFIG_TI_ADC161S626 is not set +# CONFIG_TI_ADS1015 is not set +# CONFIG_TI_ADS1018 is not set +# CONFIG_TI_ADS1100 is not set +# CONFIG_TI_ADS1119 is not set +# CONFIG_TI_ADS112C14 is not set +# CONFIG_TI_ADS124S08 is not set +# CONFIG_TI_ADS1298 is not set +# CONFIG_TI_ADS131E08 is not set +# CONFIG_TI_ADS131M02 is not set +# CONFIG_TI_ADS7138 is not set +# CONFIG_TI_ADS7924 is not set +# CONFIG_TI_ADS7950 is not set +# CONFIG_TI_ADS8344 is not set +# CONFIG_TI_ADS8688 is not set +# CONFIG_TI_LMP92064 is not set +# CONFIG_TI_TLC4541 is not set +# CONFIG_TI_TSC2046 is not set +# CONFIG_VERSAL_SYSMON_I2C is not set +# CONFIG_VF610_ADC is not set +# CONFIG_XILINX_XADC is not set +# end of Analog to digital converters + +# +# Analog to digital and digital to analog converters +# +# CONFIG_AD74115 is not set +# CONFIG_AD74413R is not set +# end of Analog to digital and digital to analog converters + +# +# Analog Front Ends +# +# CONFIG_IIO_RESCALE is not set +# end of Analog Front Ends + +# +# Amplifiers +# +# CONFIG_AD8366 is not set +# CONFIG_ADA4250 is not set +# CONFIG_ADL8113 is not set +# CONFIG_HMC425 is not set +# end of Amplifiers + +# +# Capacitance to digital converters +# +# CONFIG_AD7150 is not set +# CONFIG_AD7746 is not set +# end of Capacitance to digital converters + +# +# Chemical Sensors +# +# CONFIG_AOSONG_AGS02MA is not set +# CONFIG_ATLAS_PH_SENSOR is not set +# CONFIG_ATLAS_EZO_SENSOR is not set +# CONFIG_BME680 is not set +# CONFIG_CCS811 is not set +# CONFIG_ENS160 is not set +# CONFIG_IAQCORE is not set +# CONFIG_MHZ19B is not set +# CONFIG_PMS7003 is not set +# CONFIG_SCD30_CORE is not set +# CONFIG_SCD4X is not set +# CONFIG_SEN0322 is not set +# CONFIG_SENSIRION_SGP30 is not set +# CONFIG_SENSIRION_SGP40 is not set +# CONFIG_SPS30_I2C is not set +# CONFIG_SPS30_SERIAL is not set +# CONFIG_SENSEAIR_SUNRISE_CO2 is not set +# CONFIG_VZ89X is not set +# end of Chemical Sensors + +# +# Hid Sensor IIO Common +# +# end of Hid Sensor IIO Common + +# +# IIO SCMI Sensors +# +# end of IIO SCMI Sensors + +# +# SSP Sensor Common +# +# CONFIG_IIO_SSP_SENSORHUB is not set +# end of SSP Sensor Common + +# +# Digital to analog converters +# +# CONFIG_AD3530R is not set +# CONFIG_AD3552R_HS is not set +# CONFIG_AD3552R is not set +# CONFIG_AD5064 is not set +# CONFIG_AD5360 is not set +# CONFIG_AD5380 is not set +# CONFIG_AD5421 is not set +# CONFIG_AD5446_SPI is not set +# CONFIG_AD5446_I2C is not set +# CONFIG_AD5449 is not set +# CONFIG_AD5592R is not set +# CONFIG_AD5593R is not set +# CONFIG_AD5504 is not set +# CONFIG_AD5624R_SPI is not set +# CONFIG_AD5706R is not set +# CONFIG_AD9739A is not set +# CONFIG_LTC2688 is not set +# CONFIG_AD5686_SPI is not set +# CONFIG_AD5696_I2C is not set +# CONFIG_AD5755 is not set +# CONFIG_AD5758 is not set +# CONFIG_AD5761 is not set +# CONFIG_AD5764 is not set +# CONFIG_AD5766 is not set +# CONFIG_AD5770R is not set +# CONFIG_AD5791 is not set +# CONFIG_AD7293 is not set +# CONFIG_AD7303 is not set +# CONFIG_AD8460 is not set +# CONFIG_AD8801 is not set +# CONFIG_BD79703 is not set +# CONFIG_DPOT_DAC is not set +# CONFIG_DS4424 is not set +# CONFIG_LTC1660 is not set +# CONFIG_LTC2632 is not set +# CONFIG_LTC2664 is not set +# CONFIG_M62332 is not set +# CONFIG_MAX517 is not set +# CONFIG_MAX22007 is not set +# CONFIG_MAX5522 is not set +# CONFIG_MAX5821 is not set +# CONFIG_MCP4725 is not set +# CONFIG_MCP4728 is not set +# CONFIG_MCP47A1 is not set +# CONFIG_MCP47FEB02 is not set +# CONFIG_MCP4821 is not set +# CONFIG_MCP4922 is not set +# CONFIG_TI_DAC082S085 is not set +# CONFIG_TI_DAC5571 is not set +# CONFIG_TI_DAC7311 is not set +# CONFIG_TI_DAC7612 is not set +# CONFIG_VF610_DAC is not set +# end of Digital to analog converters + +# +# IIO dummy driver +# +# end of IIO dummy driver + +# +# Filters +# +# CONFIG_ADMV8818 is not set +# end of Filters + +# +# Flow sensors +# +# CONFIG_SENSIRION_SLF3S is not set +# end of Flow sensors + +# +# Frequency Synthesizers DDS/PLL +# + +# +# Clock Generator/Distribution +# +# CONFIG_AD9523 is not set +# end of Clock Generator/Distribution + +# +# Phase-Locked Loop (PLL) frequency synthesizers +# +# CONFIG_ADF41513 is not set +# CONFIG_ADF4350 is not set +# CONFIG_ADF4371 is not set +# CONFIG_ADF4377 is not set +# CONFIG_ADMFM2000 is not set +# CONFIG_ADMV1013 is not set +# CONFIG_ADMV1014 is not set +# CONFIG_ADMV4420 is not set +# CONFIG_ADRF6780 is not set +# end of Phase-Locked Loop (PLL) frequency synthesizers +# end of Frequency Synthesizers DDS/PLL + +# +# Digital gyroscope sensors +# +# CONFIG_ADIS16080 is not set +# CONFIG_ADIS16130 is not set +# CONFIG_ADIS16136 is not set +# CONFIG_ADIS16260 is not set +# CONFIG_ADXRS290 is not set +# CONFIG_ADXRS450 is not set +# CONFIG_BMG160 is not set +# CONFIG_FXAS21002C is not set +# CONFIG_MPU3050_I2C is not set +# CONFIG_IIO_ST_GYRO_3AXIS is not set +# CONFIG_ITG3200 is not set +# end of Digital gyroscope sensors + +# +# Health Sensors +# + +# +# Heart Rate Monitors +# +# CONFIG_AFE4403 is not set +# CONFIG_AFE4404 is not set +# CONFIG_MAX30100 is not set +# CONFIG_MAX30102 is not set +# end of Heart Rate Monitors +# end of Health Sensors + +# +# Humidity sensors +# +# CONFIG_AM2315 is not set +# CONFIG_DHT11 is not set +# CONFIG_ENS210 is not set +# CONFIG_HDC100X is not set +# CONFIG_HDC2010 is not set +# CONFIG_HDC3020 is not set +# CONFIG_HTS221 is not set +# CONFIG_HTU21 is not set +# CONFIG_SI7005 is not set +# CONFIG_SI7020 is not set +# end of Humidity sensors + +# +# Inertial measurement units +# +# CONFIG_ADIS16400 is not set +# CONFIG_ADIS16460 is not set +# CONFIG_ADIS16475 is not set +# CONFIG_ADIS16480 is not set +# CONFIG_ADIS16550 is not set +# CONFIG_BMI160_I2C is not set +# CONFIG_BMI160_SPI is not set +# CONFIG_BMI270_I2C is not set +# CONFIG_BMI270_SPI is not set +# CONFIG_BMI323_I2C is not set +# CONFIG_BMI323_SPI is not set +# CONFIG_BOSCH_BNO055_SERIAL is not set +# CONFIG_BOSCH_BNO055_I2C is not set +# CONFIG_FXOS8700_I2C is not set +# CONFIG_FXOS8700_SPI is not set +# CONFIG_KMX61 is not set +# CONFIG_INV_ICM42600_I2C is not set +# CONFIG_INV_ICM42600_SPI is not set +# CONFIG_INV_ICM42607_I2C is not set +# CONFIG_INV_ICM42607_SPI is not set +# CONFIG_INV_ICM45600_I2C is not set +# CONFIG_INV_ICM45600_SPI is not set +# CONFIG_INV_MPU6050_I2C is not set +# CONFIG_INV_MPU6050_SPI is not set +# CONFIG_SMI240 is not set +# CONFIG_SMI330_I2C is not set +# CONFIG_SMI330_SPI is not set +# CONFIG_IIO_ST_LSM6DSX is not set +# CONFIG_IIO_ST_LSM9DS0 is not set +# end of Inertial measurement units + +# +# Light sensors +# +# CONFIG_ADJD_S311 is not set +# CONFIG_ADUX1020 is not set +# CONFIG_AL3000A is not set +# CONFIG_AL3010 is not set +# CONFIG_AL3320A is not set +# CONFIG_APDS9160 is not set +# CONFIG_APDS9300 is not set +# CONFIG_APDS9306 is not set +# CONFIG_APDS9960 is not set +# CONFIG_APDS9999 is not set +# CONFIG_AS73211 is not set +# CONFIG_BH1745 is not set +# CONFIG_BH1750 is not set +# CONFIG_BH1780 is not set +# CONFIG_CM32181 is not set +# CONFIG_CM3232 is not set +# CONFIG_CM3323 is not set +# CONFIG_CM3605 is not set +# CONFIG_CM36651 is not set +# CONFIG_GP2AP002 is not set +# CONFIG_GP2AP020A00F is not set +# CONFIG_SENSORS_ISL29018 is not set +# CONFIG_SENSORS_ISL29028 is not set +# CONFIG_ISL29125 is not set +# CONFIG_ISL76682 is not set +# CONFIG_JSA1212 is not set +# CONFIG_ROHM_BU27034 is not set +# CONFIG_RPR0521 is not set +# CONFIG_LTR390 is not set +# CONFIG_LTR501 is not set +# CONFIG_LTRF216A is not set +# CONFIG_LV0104CS is not set +# CONFIG_MAX44000 is not set +# CONFIG_MAX44009 is not set +# CONFIG_NOA1305 is not set +# CONFIG_OPT3001 is not set +# CONFIG_OPT4001 is not set +# CONFIG_OPT4060 is not set +# CONFIG_PA12203001 is not set +# CONFIG_SI1133 is not set +# CONFIG_SI1145 is not set +# CONFIG_STK3310 is not set +# CONFIG_ST_UVIS25 is not set +# CONFIG_TCS3414 is not set +# CONFIG_TCS3472 is not set +# CONFIG_SENSORS_TSL2563 is not set +CONFIG_TSL2583=m +# CONFIG_TSL2591 is not set +# CONFIG_TSL2772 is not set +# CONFIG_TSL4531 is not set +# CONFIG_US5182D is not set +# CONFIG_VCNL4000 is not set +# CONFIG_VCNL4035 is not set +# CONFIG_VEML3235 is not set +# CONFIG_VEML3328 is not set +# CONFIG_VEML6030 is not set +# CONFIG_VEML6040 is not set +# CONFIG_VEML6046X00 is not set +# CONFIG_VEML6070 is not set +# CONFIG_VEML6075 is not set +# CONFIG_VL6180 is not set +# CONFIG_ZOPT2201 is not set +# end of Light sensors + +# +# Magnetometer sensors +# +# CONFIG_AF8133J is not set +# CONFIG_AK8974 is not set +# CONFIG_AK8975 is not set +# CONFIG_AK09911 is not set +# CONFIG_ALS31300 is not set +# CONFIG_BMC150_MAGN_I2C is not set +# CONFIG_BMC150_MAGN_SPI is not set +# CONFIG_MAG3110 is not set +# CONFIG_MMC35240 is not set +# CONFIG_MMC5633 is not set +# CONFIG_MMC5983 is not set +# CONFIG_IIO_ST_MAGN_3AXIS is not set +# CONFIG_INFINEON_TLV493D is not set +# CONFIG_QMC5883L is not set +# CONFIG_QMC6308 is not set +# CONFIG_SENSORS_HMC5843_I2C is not set +# CONFIG_SENSORS_HMC5843_SPI is not set +# CONFIG_SENSORS_RM3100_I2C is not set +# CONFIG_SENSORS_RM3100_SPI is not set +# CONFIG_SI7210 is not set +# CONFIG_TI_TMAG5273 is not set +# CONFIG_YAMAHA_YAS530 is not set +# end of Magnetometer sensors + +# +# Multiplexers +# +# CONFIG_IIO_MUX is not set +# end of Multiplexers + +# +# Inclinometer sensors +# +# end of Inclinometer sensors + +# +# Triggers - standalone +# +# CONFIG_IIO_SYSFS_TRIGGER is not set +# end of Triggers - standalone + +# +# Linear and angular position sensors +# +# end of Linear and angular position sensors + +# +# Digital potentiometers +# +# CONFIG_AD5110 is not set +# CONFIG_AD5272 is not set +# CONFIG_DS1803 is not set +# CONFIG_MAX5432 is not set +# CONFIG_MAX5481 is not set +# CONFIG_MAX5487 is not set +# CONFIG_MCP4018 is not set +# CONFIG_MCP4131 is not set +# CONFIG_MCP4531 is not set +# CONFIG_MCP41010 is not set +# CONFIG_TPL0102 is not set +# CONFIG_X9250 is not set +# end of Digital potentiometers + +# +# Digital potentiostats +# +# CONFIG_LMP91000 is not set +# end of Digital potentiostats + +# +# Pressure sensors +# +# CONFIG_ABP060MG is not set +# CONFIG_ABP2030PA_I2C is not set +# CONFIG_ABP2030PA_SPI is not set +# CONFIG_ROHM_BM1390 is not set +# CONFIG_BMP280 is not set +# CONFIG_DLHL60D is not set +# CONFIG_DPS310 is not set +# CONFIG_HP03 is not set +# CONFIG_HSC030PA is not set +# CONFIG_ICP10100 is not set +# CONFIG_MPL115_I2C is not set +# CONFIG_MPL115_SPI is not set +# CONFIG_MPL3115 is not set +# CONFIG_MPRLS0025PA_I2C is not set +# CONFIG_MPRLS0025PA_SPI is not set +# CONFIG_MS5611 is not set +# CONFIG_MS5637 is not set +# CONFIG_SDP500 is not set +# CONFIG_IIO_ST_PRESS is not set +# CONFIG_T5403 is not set +# CONFIG_HP206C is not set +# CONFIG_ZPA2326 is not set +# CONFIG_ADP810 is not set +# end of Pressure sensors + +# +# Lightning sensors +# +# CONFIG_AS3935 is not set +# end of Lightning sensors + +# +# Proximity and distance sensors +# +# CONFIG_D3323AA is not set +# CONFIG_HX9023S is not set +# CONFIG_IRSD200 is not set +# CONFIG_ISL29501 is not set +# CONFIG_LIDAR_LITE_V2 is not set +# CONFIG_MB1232 is not set +# CONFIG_PING is not set +# CONFIG_RFD77402 is not set +# CONFIG_SRF04 is not set +# CONFIG_SX9310 is not set +# CONFIG_SX9324 is not set +# CONFIG_SX9360 is not set +# CONFIG_SX9500 is not set +# CONFIG_SRF08 is not set +# CONFIG_VCNL3020 is not set +# CONFIG_VL53L0X_I2C is not set +# CONFIG_VL53L1X_I2C is not set +# CONFIG_AW96103 is not set +# end of Proximity and distance sensors + +# +# Resolver to digital converters +# +# CONFIG_AD2S90 is not set +# CONFIG_AD2S1200 is not set +# CONFIG_AD2S1210 is not set +# end of Resolver to digital converters + +# +# Temperature sensors +# +# CONFIG_LTC2983 is not set +# CONFIG_MAXIM_THERMOCOUPLE is not set +# CONFIG_MLX90614 is not set +# CONFIG_MLX90632 is not set +# CONFIG_MLX90635 is not set +# CONFIG_TMP006 is not set +# CONFIG_TMP007 is not set +# CONFIG_TMP117 is not set +# CONFIG_TSYS01 is not set +# CONFIG_TSYS02D is not set +# CONFIG_MAX30208 is not set +# CONFIG_MAX31856 is not set +# CONFIG_MAX31865 is not set +# CONFIG_MCP9600 is not set +# end of Temperature sensors + +# CONFIG_NTB is not set +# CONFIG_PWM is not set + +# +# IRQ chip support +# +CONFIG_IRQCHIP=y +CONFIG_ARM_GIC=y +CONFIG_ARM_GIC_MAX_NR=1 +CONFIG_ARM_GIC_V2M=y +CONFIG_ARM_GIC_V3=y +CONFIG_ARM_GIC_ITS_PARENT=y +CONFIG_ARM_GIC_V3_ITS=y +CONFIG_ARM_GIC_V5=y +CONFIG_IRQ_MSI_LIB=y +# CONFIG_AL_FIC is not set +# CONFIG_XILINX_INTC is not set +CONFIG_APPLE_AIC=y +# end of IRQ chip support + +# CONFIG_IPACK_BUS is not set +CONFIG_RESET_CONTROLLER=y +# CONFIG_RESET_GPIO is not set +# CONFIG_RESET_SIMPLE is not set +# CONFIG_RESET_TI_SYSCON is not set +# CONFIG_RESET_TI_TPS380X is not set + +# +# PHY Subsystem +# +CONFIG_GENERIC_PHY=y +# CONFIG_PHY_CAN_TRANSCEIVER is not set +# CONFIG_PHY_NXP_PTN3222 is not set +# CONFIG_PHY_NXP_TJA1145 is not set +CONFIG_PHY_APPLE_AUSB=y + +# +# PHY drivers for Broadcom platforms +# +# CONFIG_BCM_KONA_USB2_PHY is not set +# end of PHY drivers for Broadcom platforms + +# CONFIG_PHY_CADENCE_TORRENT is not set +# CONFIG_PHY_CADENCE_DPHY is not set +# CONFIG_PHY_CADENCE_DPHY_RX is not set +# CONFIG_PHY_CADENCE_SIERRA is not set +# CONFIG_PHY_CADENCE_SALVO is not set +# CONFIG_PHY_PXA_28NM_HSIC is not set +# CONFIG_PHY_PXA_28NM_USB2 is not set +# CONFIG_PHY_CPCAP_USB is not set +# CONFIG_PHY_MAPPHONE_MDM6600 is not set +# CONFIG_PHY_OCELOT_SERDES is not set +# CONFIG_PHY_QCOM_USB_HS is not set +# CONFIG_PHY_QCOM_USB_HSIC is not set +# CONFIG_PHY_SAMSUNG_USB2 is not set +# CONFIG_PHY_TI_DS125DF111 is not set +# CONFIG_PHY_TUSB1210 is not set +# end of PHY Subsystem + +# CONFIG_POWERCAP is not set +# CONFIG_MCB is not set + +# +# Performance monitor support +# +# CONFIG_ARM_CCI_PMU is not set +# CONFIG_ARM_CCN is not set +# CONFIG_ARM_CMN is not set +# CONFIG_ARM_NI is not set +CONFIG_ARM_PMU=y +# CONFIG_ARM_SMMU_V3_PMU is not set +CONFIG_ARM_PMUV3=y +# CONFIG_ARM_DSU_PMU is not set +# CONFIG_ARM_SPE_PMU is not set +# CONFIG_ARM64_BRBE is not set +CONFIG_APPLE_M1_CPU_PMU=y +# CONFIG_HISI_PCIE_PMU is not set +# CONFIG_HNS3_PMU is not set +# CONFIG_DWC_PCIE_PMU is not set +# CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU is not set +# end of Performance monitor support + +# CONFIG_RAS is not set +# CONFIG_USB4 is not set + +# +# Android +# +CONFIG_ANDROID_BINDER_IPC=y +# CONFIG_ANDROID_BINDERFS is not set +CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder" +# end of Android + +# CONFIG_LIBNVDIMM is not set +# CONFIG_DAX is not set +CONFIG_NVMEM=y +CONFIG_NVMEM_SYSFS=y +CONFIG_NVMEM_LAYOUTS=y + +# +# Layout Types +# +# CONFIG_NVMEM_LAYOUT_SL28_VPD is not set +# CONFIG_NVMEM_LAYOUT_ONIE_TLV is not set +# CONFIG_NVMEM_LAYOUT_U_BOOT_ENV is not set +# end of Layout Types + +# CONFIG_EEPROM_AT24 is not set +# CONFIG_EEPROM_AT25 is not set +# CONFIG_EEPROM_93XX46 is not set +# CONFIG_EEPROM_EE1004 is not set +# CONFIG_EEPROM_M24LR is not set +CONFIG_NVMEM_APPLE_EFUSES=y +# CONFIG_NVMEM_APPLE_SPMI is not set +# CONFIG_NVMEM_RMEM is not set +CONFIG_NVMEM_SIMPLE_MFD=y +# CONFIG_NVMEM_SPMI_SDAM is not set +# CONFIG_NVMEM_U_BOOT_ENV is not set + +# +# HW tracing support +# +CONFIG_STM=y +# CONFIG_STM_PROTO_BASIC is not set +# CONFIG_STM_PROTO_SYS_T is not set +# CONFIG_STM_DUMMY is not set +# CONFIG_STM_SOURCE_CONSOLE is not set +# CONFIG_STM_SOURCE_HEARTBEAT is not set +# CONFIG_STM_SOURCE_FTRACE is not set +# CONFIG_INTEL_TH is not set +# CONFIG_HISI_PTT is not set +# end of HW tracing support + +# CONFIG_FPGA is not set +# CONFIG_FSI is not set +# CONFIG_TEE is not set +CONFIG_MULTIPLEXER=y +CONFIG_MUX_CORE=y + +# +# Multiplexer drivers +# +# CONFIG_MUX_ADG792A is not set +# CONFIG_MUX_ADGS1408 is not set +# CONFIG_MUX_GPIO is not set +# CONFIG_MUX_MMIO is not set +# end of Multiplexer drivers + +CONFIG_PM_OPP=y +# CONFIG_SIOX is not set +# CONFIG_SLIMBUS is not set +# CONFIG_INTERCONNECT is not set +# CONFIG_COUNTER is not set +# CONFIG_MOST is not set +# CONFIG_PECI is not set +# CONFIG_HTE is not set +# CONFIG_CDX_BUS is not set +# end of Device Drivers + +# +# File systems +# +CONFIG_DCACHE_WORD_ACCESS=y +# CONFIG_VALIDATE_FS_PARSER is not set +CONFIG_FS_IOMAP=y +CONFIG_FS_STACK=y +CONFIG_BUFFER_HEAD=y +CONFIG_LEGACY_DIRECT_IO=y +# CONFIG_EXT2_FS is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_USE_FOR_EXT2=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +# CONFIG_EXT4_DEBUG is not set +CONFIG_JBD2=y +# CONFIG_JBD2_DEBUG is not set +CONFIG_FS_MBCACHE=y +# CONFIG_JFS_FS is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_BTRFS_FS=y +CONFIG_BTRFS_FS_POSIX_ACL=y +# CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set +# CONFIG_BTRFS_DEBUG is not set +# CONFIG_BTRFS_ASSERT is not set +# CONFIG_BTRFS_EXPERIMENTAL is not set +# CONFIG_NILFS2_FS is not set +# CONFIG_F2FS_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_EXPORTFS=y +# CONFIG_EXPORTFS_BLOCK_OPS is not set +CONFIG_FILE_LOCKING=y +# CONFIG_FS_ENCRYPTION is not set +# CONFIG_FS_VERITY is not set +CONFIG_FSNOTIFY=y +CONFIG_DNOTIFY=y +CONFIG_INOTIFY_USER=y +CONFIG_FANOTIFY=y +CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y +CONFIG_QUOTA=y +# CONFIG_QUOTA_NETLINK_INTERFACE is not set +# CONFIG_QUOTA_DEBUG is not set +CONFIG_QUOTA_TREE=y +CONFIG_QFMT_V1=y +CONFIG_QFMT_V2=y +CONFIG_QUOTACTL=y +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=y +# CONFIG_CUSE is not set +# CONFIG_VIRTIO_FS is not set +CONFIG_FUSE_PASSTHROUGH=y +CONFIG_FUSE_IO_URING=y +CONFIG_OVERLAY_FS=y +# CONFIG_OVERLAY_FS_REDIRECT_DIR is not set +CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW=y +# CONFIG_OVERLAY_FS_INDEX is not set +# CONFIG_OVERLAY_FS_XINO_AUTO is not set +# CONFIG_OVERLAY_FS_METACOPY is not set +# CONFIG_OVERLAY_FS_DEBUG is not set + +# +# Caches +# +# end of Caches + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set +# end of CD-ROM/DVD Filesystems + +# +# DOS/FAT/EXFAT/NT Filesystems +# +CONFIG_FAT_FS=y +# CONFIG_MSDOS_FS is not set +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_FAT_DEFAULT_UTF8 is not set +CONFIG_EXFAT_FS=y +CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" +# CONFIG_NTFS_FS is not set +CONFIG_NTFS3_FS=y +# CONFIG_NTFS3_64BIT_CLUSTER is not set +# CONFIG_NTFS3_LZX_XPRESS is not set +# CONFIG_NTFS3_FS_POSIX_ACL is not set +# end of DOS/FAT/EXFAT/NT Filesystems + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +# CONFIG_PROC_KCORE is not set +CONFIG_PROC_VMCORE=y +# CONFIG_PROC_VMCORE_DEVICE_DUMP is not set +CONFIG_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PROC_CHILDREN=y +CONFIG_KERNFS=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +CONFIG_TMPFS_INODE64=y +# CONFIG_TMPFS_QUOTA is not set +CONFIG_ARCH_SUPPORTS_HUGETLBFS=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING=y +CONFIG_ARCH_HAS_GIGANTIC_PAGE=y +CONFIG_CONFIGFS_FS=y +CONFIG_EFIVAR_FS=y +# end of Pseudo filesystems + +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ORANGEFS_FS is not set +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_ECRYPT_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_JFFS2_FS is not set +# CONFIG_CRAMFS is not set +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_FILE_CACHE=y +# CONFIG_SQUASHFS_FILE_DIRECT is not set +CONFIG_SQUASHFS_DECOMP_SINGLE=y +# CONFIG_SQUASHFS_CHOICE_DECOMP_BY_MOUNT is not set +CONFIG_SQUASHFS_COMPILE_DECOMP_SINGLE=y +# CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI is not set +# CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU is not set +# CONFIG_SQUASHFS_XATTR is not set +# CONFIG_SQUASHFS_COMP_CACHE_FULL is not set +CONFIG_SQUASHFS_ZLIB=y +# CONFIG_SQUASHFS_LZ4 is not set +# CONFIG_SQUASHFS_LZO is not set +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set +# CONFIG_SQUASHFS_EMBEDDED is not set +CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QNX6FS_FS is not set +# CONFIG_ROMFS_FS is not set +CONFIG_PSTORE=y +CONFIG_PSTORE_DEFAULT_KMSG_BYTES=1048576 +CONFIG_PSTORE_COMPRESS=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_PMSG=y +CONFIG_PSTORE_RAM=y +# CONFIG_PSTORE_BLK is not set +# CONFIG_UFS_FS is not set +# CONFIG_EROFS_FS is not set +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_MAC_ROMAN is not set +# CONFIG_NLS_MAC_CELTIC is not set +# CONFIG_NLS_MAC_CENTEURO is not set +# CONFIG_NLS_MAC_CROATIAN is not set +# CONFIG_NLS_MAC_CYRILLIC is not set +# CONFIG_NLS_MAC_GAELIC is not set +# CONFIG_NLS_MAC_GREEK is not set +# CONFIG_NLS_MAC_ICELAND is not set +# CONFIG_NLS_MAC_INUIT is not set +# CONFIG_NLS_MAC_ROMANIAN is not set +# CONFIG_NLS_MAC_TURKISH is not set +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set +# CONFIG_UNICODE is not set +CONFIG_IO_WQ=y +# end of File systems + +# +# Security options +# +CONFIG_KEYS=y +# CONFIG_KEYS_REQUEST_CACHE is not set +# CONFIG_PERSISTENT_KEYRINGS is not set +# CONFIG_BIG_KEYS is not set +# CONFIG_TRUSTED_KEYS is not set +# CONFIG_ENCRYPTED_KEYS is not set +# CONFIG_KEY_DH_OPERATIONS is not set +# CONFIG_SECURITY_DMESG_RESTRICT is not set +CONFIG_PROC_MEM_ALWAYS_FORCE=y +# CONFIG_PROC_MEM_FORCE_PTRACE is not set +# CONFIG_PROC_MEM_NO_FORCE is not set +CONFIG_SECURITY=y +CONFIG_HAS_SECURITY_AUDIT=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_PATH=y +# CONFIG_STATIC_USERMODEHELPER is not set +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +# CONFIG_SECURITY_APPARMOR is not set +# CONFIG_SECURITY_LOADPIN is not set +# CONFIG_SECURITY_YAMA is not set +# CONFIG_SECURITY_SAFESETID is not set +# CONFIG_SECURITY_LOCKDOWN_LSM is not set +CONFIG_SECURITY_LANDLOCK=y +CONFIG_SECURITY_LANDLOCK_LOG=y +# CONFIG_SECURITY_IPE is not set +CONFIG_INTEGRITY=y +# CONFIG_INTEGRITY_SIGNATURE is not set +CONFIG_INTEGRITY_AUDIT=y +# CONFIG_IMA is not set +# CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set +# CONFIG_EVM is not set +CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_LSM="landlock" + +# +# Kernel hardening options +# + +# +# Memory initialization +# +CONFIG_CC_HAS_AUTO_VAR_INIT_PATTERN=y +CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO_BARE=y +CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO=y +CONFIG_INIT_STACK_NONE=y +# CONFIG_INIT_STACK_ALL_PATTERN is not set +# CONFIG_INIT_STACK_ALL_ZERO is not set +# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set +# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set +CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y +# CONFIG_ZERO_CALL_USED_REGS is not set +# end of Memory initialization + +# +# Bounds checking +# +# CONFIG_FORTIFY_SOURCE is not set +# CONFIG_HARDENED_USERCOPY is not set +# end of Bounds checking + +# +# Hardening of kernel data structures +# +# CONFIG_LIST_HARDENED is not set +# CONFIG_BUG_ON_DATA_CORRUPTION is not set +# end of Hardening of kernel data structures + +CONFIG_CC_HAS_RANDSTRUCT=y +CONFIG_RANDSTRUCT_NONE=y +# CONFIG_RANDSTRUCT_FULL is not set +# end of Kernel hardening options +# end of Security options + +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_SIG=y +CONFIG_CRYPTO_SIG2=y +CONFIG_CRYPTO_SKCIPHER=y +CONFIG_CRYPTO_SKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_AKCIPHER2=y +CONFIG_CRYPTO_AKCIPHER=y +CONFIG_CRYPTO_KPP2=y +CONFIG_CRYPTO_KPP=y +CONFIG_CRYPTO_ACOMP2=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_USER is not set +# CONFIG_CRYPTO_SELFTESTS is not set +CONFIG_CRYPTO_NULL=y +# CONFIG_CRYPTO_PCRYPT is not set +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_AUTHENC=y +# CONFIG_CRYPTO_KRB5ENC is not set +# CONFIG_CRYPTO_BENCHMARK is not set +# end of Crypto core or helper + +# +# Public-key cryptography +# +CONFIG_CRYPTO_RSA=y +# CONFIG_CRYPTO_DH is not set +CONFIG_CRYPTO_ECC=y +CONFIG_CRYPTO_ECDH=y +# CONFIG_CRYPTO_ECDSA is not set +# CONFIG_CRYPTO_ECRDSA is not set +# CONFIG_CRYPTO_MLDSA is not set +# end of Public-key cryptography + +# +# Block ciphers +# +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARIA is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +CONFIG_CRYPTO_SM4=y +CONFIG_CRYPTO_SM4_GENERIC=y +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set +# end of Block ciphers + +# +# Length-preserving ciphers and modes +# +# CONFIG_CRYPTO_ADIANTUM is not set +# CONFIG_CRYPTO_ARC4 is not set +CONFIG_CRYPTO_CHACHA20=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_HCTR2 is not set +# CONFIG_CRYPTO_LRW is not set +CONFIG_CRYPTO_XTS=y +# end of Length-preserving ciphers and modes + +# +# AEAD (authenticated encryption with associated data) ciphers +# +# CONFIG_CRYPTO_AEGIS128 is not set +# CONFIG_CRYPTO_CHACHA20POLY1305 is not set +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_GENIV=y +CONFIG_CRYPTO_SEQIV=y +# CONFIG_CRYPTO_ECHAINIV is not set +CONFIG_CRYPTO_ESSIV=y +# end of AEAD (authenticated encryption with associated data) ciphers + +# +# Hashes, digests, and MACs +# +# CONFIG_CRYPTO_BLAKE2B is not set +CONFIG_CRYPTO_CMAC=y +CONFIG_CRYPTO_HMAC=y +# CONFIG_CRYPTO_MD4 is not set +CONFIG_CRYPTO_MD5=y +# CONFIG_CRYPTO_RMD160 is not set +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_SHA3=y +# CONFIG_CRYPTO_SM3 is not set +# CONFIG_CRYPTO_STREEBOG is not set +# CONFIG_CRYPTO_WP512 is not set +CONFIG_CRYPTO_XCBC=y +# CONFIG_CRYPTO_XXHASH is not set +# end of Hashes, digests, and MACs + +# +# CRCs (cyclic redundancy checks) +# +CONFIG_CRYPTO_CRC32C=y +# CONFIG_CRYPTO_CRC32 is not set +# end of CRCs (cyclic redundancy checks) + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_842=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_LZ4HC=y +CONFIG_CRYPTO_ZSTD=y +# end of Compression + +# +# Random number generation +# +# CONFIG_CRYPTO_DRBG is not set +CONFIG_CRYPTO_JITTERENTROPY=y +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKS=64 +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE=32 +CONFIG_CRYPTO_JITTERENTROPY_OSR=1 +# end of Random number generation + +# +# Userspace interface (deprecated) +# +CONFIG_CRYPTO_USER_API=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_USER_API_RNG=y +CONFIG_CRYPTO_USER_API_AEAD=y +CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE=y +# end of Userspace interface (deprecated) + +# +# Accelerated Cryptographic Algorithms for CPU (arm64) +# +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y +CONFIG_CRYPTO_AES_ARM64_BS=y +CONFIG_CRYPTO_SM4_ARM64_CE=y +CONFIG_CRYPTO_SM4_ARM64_CE_BLK=y +CONFIG_CRYPTO_SM4_ARM64_NEON_BLK=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +# CONFIG_CRYPTO_SM4_ARM64_CE_CCM is not set +# CONFIG_CRYPTO_SM4_ARM64_CE_GCM is not set +# end of Accelerated Cryptographic Algorithms for CPU (arm64) + +# CONFIG_CRYPTO_HW is not set +CONFIG_ASYMMETRIC_KEY_TYPE=y +CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y +CONFIG_X509_CERTIFICATE_PARSER=y +# CONFIG_PKCS8_PRIVATE_KEY_PARSER is not set +CONFIG_PKCS7_MESSAGE_PARSER=y +# CONFIG_PKCS7_TEST_KEY is not set +# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set +# CONFIG_FIPS_SIGNATURE_SELFTEST is not set + +# +# Certificates for signature checking +# +CONFIG_SYSTEM_TRUSTED_KEYRING=y +CONFIG_SYSTEM_TRUSTED_KEYS="" +# CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set +# CONFIG_SECONDARY_TRUSTED_KEYRING is not set +# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set +# end of Certificates for signature checking + +# CONFIG_CRYPTO_KRB5 is not set +CONFIG_BINARY_PRINTF=y + +# +# Library routines +# +CONFIG_LINEAR_RANGES=y +# CONFIG_PACKING is not set +CONFIG_BITREVERSE=y +CONFIG_HAVE_ARCH_BITREVERSE=y +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +CONFIG_GENERIC_NET_UTILS=y +# CONFIG_CORDIC is not set +# CONFIG_PRIME_NUMBERS is not set +CONFIG_RATIONAL=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_ARCH_HAS_FAST_MULTIPLIER=y +CONFIG_ARCH_USE_SYM_ANNOTATIONS=y +# CONFIG_INDIRECT_PIO is not set +# CONFIG_TRACE_MMIO_ACCESS is not set +CONFIG_CRC16=y +CONFIG_CRC32=y +CONFIG_CRC32_ARCH=y +CONFIG_CRC_OPTIMIZATIONS=y +CONFIG_CRYPTO_HASH_INFO=y +CONFIG_CRYPTO_LIB_UTILS=y +CONFIG_CRYPTO_LIB_AES=y +CONFIG_CRYPTO_LIB_AES_ARCH=y +CONFIG_CRYPTO_LIB_AES_CBC=y +CONFIG_CRYPTO_LIB_AES_CBC_MACS=y +CONFIG_CRYPTO_LIB_AES_CCM=y +CONFIG_CRYPTO_LIB_AES_CTR=y +CONFIG_CRYPTO_LIB_AES_ECB=y +CONFIG_CRYPTO_LIB_AES_GCM=y +CONFIG_CRYPTO_LIB_AES_XTS=y +CONFIG_CRYPTO_LIB_GF128MUL=y +CONFIG_CRYPTO_LIB_BLAKE2B=y +CONFIG_CRYPTO_LIB_CHACHA=y +CONFIG_CRYPTO_LIB_CHACHA_ARCH=y +CONFIG_CRYPTO_LIB_GF128HASH=y +CONFIG_CRYPTO_LIB_GF128HASH_ARCH=y +CONFIG_CRYPTO_LIB_MD5=y +CONFIG_CRYPTO_LIB_POLY1305_RSIZE=9 +CONFIG_CRYPTO_LIB_SHA1=y +CONFIG_CRYPTO_LIB_SHA1_ARCH=y +CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_LIB_SHA256_ARCH=y +CONFIG_CRYPTO_LIB_SHA512=y +CONFIG_CRYPTO_LIB_SHA512_ARCH=y +CONFIG_CRYPTO_LIB_SHA3=y +CONFIG_CRYPTO_LIB_SHA3_ARCH=y +CONFIG_XOR_BLOCKS=y +CONFIG_XOR_BLOCKS_ARCH=y +CONFIG_RAID6_PQ=y +CONFIG_RAID6_PQ_ARCH=y +# CONFIG_RAID6_PQ_BENCHMARK is not set +CONFIG_XXHASH=y +CONFIG_AUDIT_GENERIC=y +CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y +CONFIG_AUDIT_COMPAT_GENERIC=y +CONFIG_842_COMPRESS=y +CONFIG_842_DECOMPRESS=y +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_LZ4_COMPRESS=y +CONFIG_LZ4HC_COMPRESS=y +CONFIG_LZ4_DECOMPRESS=y +CONFIG_ZSTD_COMMON=y +CONFIG_ZSTD_COMPRESS=y +CONFIG_ZSTD_DECOMPRESS=y +CONFIG_XZ_DEC=y +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +# CONFIG_XZ_DEC_MICROLZMA is not set +# CONFIG_XZ_DEC_TEST is not set +CONFIG_DECOMPRESS_GZIP=y +CONFIG_DECOMPRESS_BZIP2=y +CONFIG_DECOMPRESS_LZMA=y +CONFIG_DECOMPRESS_XZ=y +CONFIG_DECOMPRESS_LZO=y +CONFIG_DECOMPRESS_LZ4=y +CONFIG_DECOMPRESS_ZSTD=y +CONFIG_GENERIC_ALLOCATOR=y +CONFIG_REED_SOLOMON=y +CONFIG_REED_SOLOMON_ENC8=y +CONFIG_REED_SOLOMON_DEC8=y +CONFIG_INTERVAL_TREE=y +CONFIG_XARRAY_MULTI=y +CONFIG_ASSOCIATIVE_ARRAY=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_IOPORT_MAP=y +CONFIG_HAS_DMA=y +CONFIG_DMA_OPS_HELPERS=y +CONFIG_NEED_SG_DMA_FLAGS=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_DMA_DECLARE_COHERENT=y +CONFIG_ARCH_HAS_SETUP_DMA_OPS=y +CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE=y +CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU=y +CONFIG_ARCH_HAS_DMA_PREP_COHERENT=y +CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y +CONFIG_ARCH_HAS_BATCHED_DMA_SYNC=y +CONFIG_SWIOTLB=y +CONFIG_SWIOTLB_DEFAULT_SIZE_MB=64 +# CONFIG_SWIOTLB_DYNAMIC is not set +CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y +CONFIG_DMA_NEED_SYNC=y +# CONFIG_DMA_RESTRICTED_POOL is not set +CONFIG_DMA_NONCOHERENT_MMAP=y +CONFIG_DMA_COHERENT_POOL=y +CONFIG_DMA_DIRECT_REMAP=y +CONFIG_DMA_CMA=y + +# +# Default contiguous memory area size: +# +CONFIG_CMA_SIZE_MBYTES=128 +CONFIG_CMA_SIZE_SEL_MBYTES=y +# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set +# CONFIG_CMA_SIZE_SEL_MIN is not set +# CONFIG_CMA_SIZE_SEL_MAX is not set +CONFIG_CMA_ALIGNMENT=8 +# CONFIG_DMA_API_DEBUG is not set +# CONFIG_DMA_MAP_BENCHMARK is not set +CONFIG_SGL_ALLOC=y +CONFIG_CPU_RMAP=y +CONFIG_DQL=y +CONFIG_GLOB=y +CONFIG_NLATTR=y +CONFIG_CLZ_TAB=y +# CONFIG_IRQ_POLL is not set +CONFIG_MPILIB=y +CONFIG_LIBFDT=y +CONFIG_OID_REGISTRY=y +CONFIG_UCS2_STRING=y +CONFIG_VDSO_DATASTORE=y +CONFIG_GENERIC_GETTIMEOFDAY=y +CONFIG_VDSO_GETRANDOM=y +CONFIG_FONT_SUPPORT=y +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_SG_POOL=y +CONFIG_ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION=y +CONFIG_GENERIC_CPU_CACHE_MAINTENANCE=y +CONFIG_ARCH_STACKWALK=y +CONFIG_STACKDEPOT=y +CONFIG_STACKDEPOT_MAX_FRAMES=64 +CONFIG_SBITMAP=y +# CONFIG_LWQ_TEST is not set +# end of Library routines + +CONFIG_GENERIC_IOREMAP=y +CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y +CONFIG_UNION_FIND=y + +# +# Kernel hacking +# + +# +# printk and dmesg options +# +CONFIG_PRINTK_TIME=y +# CONFIG_PRINTK_CALLER is not set +CONFIG_PRINTK_EXECUTION_CTX=y +# CONFIG_STACKTRACE_BUILD_ID is not set +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 +CONFIG_CONSOLE_LOGLEVEL_QUIET=4 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DYNAMIC_DEBUG_CORE=y +CONFIG_SYMBOLIC_ERRNAME=y +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_BUGVERBOSE_DETAILED is not set +# end of printk and dmesg options + +CONFIG_DEBUG_KERNEL=y +# CONFIG_DEBUG_MISC is not set + +# +# Compile-time checks and compiler options +# +CONFIG_AS_HAS_NON_CONST_ULEB128=y +CONFIG_DEBUG_INFO_NONE=y +# CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set +# CONFIG_DEBUG_INFO_DWARF4 is not set +# CONFIG_DEBUG_INFO_DWARF5 is not set +CONFIG_FRAME_WARN=2048 +# CONFIG_STRIP_ASM_SYMS is not set +# CONFIG_HEADERS_INSTALL is not set +CONFIG_SECTION_MISMATCH_WARN_ONLY=y +# CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B is not set +CONFIG_ARCH_WANT_FRAME_POINTERS=y +CONFIG_FRAME_POINTER=y +# CONFIG_VMLINUX_MAP is not set +# end of Compile-time checks and compiler options + +# +# Generic Kernel Debugging Instruments +# +CONFIG_MAGIC_SYSRQ=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 +CONFIG_MAGIC_SYSRQ_SERIAL=y +CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE="" +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_ALLOW_NONE is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +CONFIG_ARCH_HAS_UBSAN=y +# CONFIG_UBSAN is not set +CONFIG_HAVE_ARCH_KCSAN=y +CONFIG_HAVE_KCSAN_COMPILER=y +# CONFIG_KCSAN is not set +# end of Generic Kernel Debugging Instruments + +# +# Networking Debugging +# +# CONFIG_NET_DEV_REFCNT_TRACKER is not set +# CONFIG_NET_NS_REFCNT_TRACKER is not set +# CONFIG_DEBUG_NET is not set +# CONFIG_DEBUG_NET_SMALL_RTNL is not set +# end of Networking Debugging + +# +# Memory Debugging +# +# CONFIG_PAGE_EXTENSION is not set +# CONFIG_DEBUG_PAGEALLOC is not set +CONFIG_SLUB_DEBUG=y +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_PAGE_OWNER is not set +# CONFIG_PAGE_POISONING is not set +# CONFIG_DEBUG_PAGE_REF is not set +# CONFIG_DEBUG_RODATA_TEST is not set +CONFIG_ARCH_HAS_DEBUG_WX=y +# CONFIG_DEBUG_WX is not set +CONFIG_ARCH_HAS_PTDUMP=y +# CONFIG_PTDUMP_DEBUGFS is not set +CONFIG_HAVE_DEBUG_KMEMLEAK=y +# CONFIG_DEBUG_KMEMLEAK is not set +# CONFIG_PER_VMA_LOCK_STATS is not set +# CONFIG_MEM_ALLOC_PROFILING is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_SHRINKER_DEBUG is not set +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_SCHED_STACK_END_CHECK is not set +CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y +# CONFIG_DEBUG_VFS is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_VM_PGTABLE is not set +CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y +# CONFIG_DEBUG_VIRTUAL is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set +CONFIG_HAVE_ARCH_KASAN=y +CONFIG_HAVE_ARCH_KASAN_SW_TAGS=y +CONFIG_HAVE_ARCH_KASAN_VMALLOC=y +CONFIG_CC_HAS_KASAN_GENERIC=y +CONFIG_CC_HAS_KASAN_SW_TAGS=y +CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y +# CONFIG_KASAN is not set +CONFIG_HAVE_ARCH_KFENCE=y +# CONFIG_KFENCE is not set +# end of Memory Debugging + +# CONFIG_DEBUG_SHIRQ is not set + +# +# Debug Oops, Lockups and Hangs +# +# CONFIG_PANIC_ON_OOPS is not set +CONFIG_PANIC_TIMEOUT=-1 +CONFIG_LOCKUP_DETECTOR=y +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM=y +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=0 +CONFIG_HAVE_HARDLOCKUP_DETECTOR_BUDDY=y +CONFIG_HARDLOCKUP_DETECTOR=y +# CONFIG_HARDLOCKUP_DETECTOR_PERF is not set +CONFIG_HARDLOCKUP_DETECTOR_BUDDY=y +# CONFIG_HARDLOCKUP_DETECTOR_ARCH is not set +CONFIG_HARDLOCKUP_DETECTOR_COUNTS_HRTIMER=y +# CONFIG_BOOTPARAM_HARDLOCKUP_PANIC is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=30 +CONFIG_BOOTPARAM_HUNG_TASK_PANIC=0 +CONFIG_DETECT_HUNG_TASK_BLOCKER=y +CONFIG_WQ_WATCHDOG=y +CONFIG_BOOTPARAM_WQ_STALL_PANIC=0 +# CONFIG_WQ_CPU_INTENSIVE_REPORT is not set +# CONFIG_TEST_LOCKUP is not set +# end of Debug Oops, Lockups and Hangs + +# +# Scheduler Debugging +# +CONFIG_SCHED_INFO=y +# CONFIG_SCHEDSTATS is not set +# end of Scheduler Debugging + +# CONFIG_DEBUG_PREEMPT is not set +# CONFIG_DEBUG_ATOMIC is not set + +# +# Lock Debugging (spinlocks, mutexes, etc...) +# +CONFIG_LOCK_DEBUGGING_SUPPORT=y +# CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_DEBUG_SPINLOCK is not set +# CONFIG_DEBUG_MUTEXES is not set +# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set +# CONFIG_DEBUG_RWSEMS is not set +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_DEBUG_ATOMIC_SLEEP is not set +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +# CONFIG_LOCK_TORTURE_TEST is not set +# CONFIG_WW_MUTEX_SELFTEST is not set +# CONFIG_SCF_TORTURE_TEST is not set +# CONFIG_CSD_LOCK_WAIT_DEBUG is not set +# end of Lock Debugging (spinlocks, mutexes, etc...) + +# CONFIG_DEBUG_IRQFLAGS is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set + +# +# Debug kernel data structures +# +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_PLIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_MAPLE_TREE is not set +# end of Debug kernel data structures + +# +# RCU Debugging +# +# CONFIG_RCU_SCALE_TEST is not set +# CONFIG_RCU_TORTURE_TEST is not set +# CONFIG_RCU_REF_SCALE_TEST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=20 +CONFIG_RCU_EXP_CPU_STALL_TIMEOUT=20 +# CONFIG_RCU_CPU_STALL_CPUTIME is not set +CONFIG_RCU_TRACE=y +# CONFIG_RCU_EQS_DEBUG is not set +# end of RCU Debugging + +# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set +# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set +# CONFIG_LATENCYTOP is not set +CONFIG_USER_STACKTRACE_SUPPORT=y +CONFIG_NOP_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_FREGS=y +CONFIG_HAVE_FTRACE_GRAPH_FUNC=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EXTRA_IPI_TRACEPOINTS=y +CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_C_RECORDMCOUNT=y +CONFIG_HAVE_BUILDTIME_MCOUNT_SORT=y +CONFIG_TRACE_CLOCK=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_TRACING=y +CONFIG_TRACING_SUPPORT=y +CONFIG_FTRACE=y +# CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED is not set +CONFIG_BOOTTIME_TRACING=y +# CONFIG_FUNCTION_TRACER is not set +# CONFIG_STACK_TRACER is not set +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_PREEMPT_TRACER is not set +# CONFIG_SCHED_TRACER is not set +# CONFIG_HWLAT_TRACER is not set +# CONFIG_OSNOISE_TRACER is not set +# CONFIG_TIMERLAT_TRACER is not set +# CONFIG_ENABLE_DEFAULT_TRACERS is not set +# CONFIG_FTRACE_SYSCALLS is not set +# CONFIG_TRACER_SNAPSHOT is not set +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_PROBE_EVENTS_DUMP_FETCHARG is not set +CONFIG_UPROBE_EVENTS=y +# CONFIG_EPROBE_EVENTS is not set +CONFIG_BPF_EVENTS=y +CONFIG_DYNAMIC_EVENTS=y +CONFIG_PROBE_EVENTS=y +# CONFIG_SYNTH_EVENTS is not set +# CONFIG_USER_EVENTS is not set +# CONFIG_HIST_TRIGGERS is not set +# CONFIG_TRACE_EVENT_INJECT is not set +# CONFIG_TRACEPOINT_BENCHMARK is not set +# CONFIG_RING_BUFFER_BENCHMARK is not set +# CONFIG_TRACE_EVAL_MAP_FILE is not set +# CONFIG_RING_BUFFER_STARTUP_TEST is not set +# CONFIG_RING_BUFFER_VALIDATE_TIME_DELTAS is not set +# CONFIG_RING_BUFFER_PERSISTENT_INJECT is not set +# CONFIG_PREEMPTIRQ_DELAY_TEST is not set +# CONFIG_RV is not set +# CONFIG_TRACE_REMOTE_TEST is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_SAMPLE_FTRACE_DIRECT=y +CONFIG_HAVE_SAMPLE_FTRACE_DIRECT_MULTI=y +# CONFIG_STRICT_DEVMEM is not set + +# +# arm64 Debugging +# +CONFIG_PID_IN_CONTEXTIDR=y +# CONFIG_ARM64_RELOC_TEST is not set +CONFIG_CORESIGHT=y +CONFIG_CORESIGHT_LINKS_AND_SINKS=y +CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_CATU=y +CONFIG_CORESIGHT_SINK_TPIU=y +CONFIG_CORESIGHT_SINK_ETBV10=y +CONFIG_CORESIGHT_SOURCE_ETM4X=y +CONFIG_ETM4X_IMPDEF_FEATURE=y +CONFIG_CORESIGHT_STM=y +# CONFIG_CORESIGHT_CTCU is not set +CONFIG_CORESIGHT_CPU_DEBUG=y +# CONFIG_CORESIGHT_CPU_DEBUG_DEFAULT_ON is not set +CONFIG_CORESIGHT_CTI=y +# CONFIG_CORESIGHT_CTI_INTEGRATION_REGS is not set +CONFIG_CORESIGHT_TRBE=y +# CONFIG_CORESIGHT_TPDM is not set +# CONFIG_CORESIGHT_TPDA is not set +# CONFIG_CORESIGHT_DUMMY is not set +# CONFIG_CORESIGHT_TNOC is not set +# end of arm64 Debugging + +# +# Kernel Testing and Coverage +# +# CONFIG_KUNIT is not set +# CONFIG_NOTIFIER_ERROR_INJECTION is not set +# CONFIG_FAULT_INJECTION is not set +CONFIG_ARCH_HAS_KCOV=y +# CONFIG_KCOV is not set +CONFIG_RUNTIME_TESTING_MENU=y +# CONFIG_TEST_DHRY is not set +# CONFIG_LKDTM is not set +# CONFIG_TEST_DIV64 is not set +# CONFIG_TEST_MULDIV64 is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +# CONFIG_TEST_REF_TRACKER is not set +# CONFIG_RBTREE_TEST is not set +# CONFIG_REED_SOLOMON_TEST is not set +# CONFIG_INTERVAL_TREE_TEST is not set +# CONFIG_PERCPU_TEST is not set +# CONFIG_ATOMIC64_SELFTEST is not set +# CONFIG_TEST_HEXDUMP is not set +# CONFIG_TEST_KSTRTOX is not set +# CONFIG_TEST_BITMAP is not set +# CONFIG_TEST_XARRAY is not set +# CONFIG_TEST_MAPLE_TREE is not set +# CONFIG_TEST_RHASHTABLE is not set +# CONFIG_TEST_IDA is not set +# CONFIG_TEST_LKM is not set +# CONFIG_TEST_BITOPS is not set +# CONFIG_TEST_VMALLOC is not set +# CONFIG_TEST_WORKQUEUE is not set +# CONFIG_TEST_BPF is not set +# CONFIG_FIND_BIT_BENCHMARK is not set +# CONFIG_REGION_ALLOC_BENCHMARK is not set +# CONFIG_TEST_FIRMWARE is not set +# CONFIG_TEST_SYSCTL is not set +# CONFIG_CONTEXT_ANALYSIS_TEST is not set +# CONFIG_TEST_UDELAY is not set +# CONFIG_TEST_STATIC_KEYS is not set +# CONFIG_TEST_DYNAMIC_DEBUG is not set +# CONFIG_TEST_KMOD is not set +# CONFIG_TEST_KALLSYMS is not set +# CONFIG_TEST_MEMCAT_P is not set +# CONFIG_TEST_MEMINIT is not set +# CONFIG_TEST_FREE_PAGES is not set +# CONFIG_TEST_FPU is not set +# CONFIG_TEST_OBJPOOL is not set +CONFIG_ARCH_USE_MEMTEST=y +# CONFIG_MEMTEST is not set +# end of Kernel Testing and Coverage + +# +# Rust hacking +# +# end of Rust hacking +# end of Kernel hacking + +CONFIG_IO_URING_ZCRX=y +CONFIG_IO_URING_BPF=y diff --git a/kernel/devicetree/j71t/README.md b/kernel/devicetree/j71t/README.md new file mode 100644 index 00000000..961ba624 --- /dev/null +++ b/kernel/devicetree/j71t/README.md @@ -0,0 +1,14 @@ +# J71t device tree + +The J71t kernel patches add the complete S8003 and J71t device trees, so the +kernel build's `s8003-j71t.dtb` is used as is. The adjacent `build` recipe +checks its pinned SHA-256 and writes it to the explicit `--output` path. +`kernel/build j71t --output DIRECTORY` invokes this recipe and includes the +result as `DIRECTORY/j71t.dtb`. + +m1n1 fills in memory, the framebuffer, the NVMe host-memory-buffer region +(`nvme-hmb`), the Wi-Fi MAC and Wi-Fi calibration from the live ADT at boot +(`../../../boot/m1n1/patches/j71t/`). The DTB itself carries no per-device data. + +The tree still describes blocks this port does not drive (GPU, SEP, AOP); +their drivers are not part of the J71t patches. diff --git a/kernel/devicetree/j71t/build b/kernel/devicetree/j71t/build new file mode 100755 index 00000000..bb0c741a --- /dev/null +++ b/kernel/devicetree/j71t/build @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +kernel_build='' headers='' output='' +while (($#)); do + case "$1" in + --kernel-build) kernel_build=$2; shift 2;; + --headers) headers=$2; shift 2;; + --output) output=$2; shift 2;; + *) echo "Unknown argument: $1" >&2; exit 2;; + esac +done +# The J71t kernel patches carry the complete board device tree; no overlays. +base="$kernel_build/arch/arm64/boot/dts/apple/s8003-j71t.dtb" +if [[ ! -f "$base" || ! -d "$headers" || -z "$output" || -e "$output" ]]; then + echo 'Usage: ./kernel/devicetree/j71t/build --kernel-build KERNEL_OUTPUT --headers KERNEL_SOURCE/include --output J71T.dtb' >&2 + exit 2 +fi +actual=$(python3 -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$base") +expected=9d97816c8d41579e25aac71a50e44a1aa7910accf40f234d4d29a104b20dcd1c +[[ "$actual" == "$expected" ]] || { echo "J71t DTB differs: $actual" >&2; exit 1; } +mkdir -p "$(dirname "$output")" +cp "$base" "$output" +echo "DTB: $output (SHA-256 $actual)" diff --git a/kernel/patches/j71t/0001-s8003-pcie-nvme.patch b/kernel/patches/j71t/0001-s8003-pcie-nvme.patch new file mode 100644 index 00000000..4fcb2e54 --- /dev/null +++ b/kernel/patches/j71t/0001-s8003-pcie-nvme.patch @@ -0,0 +1,8916 @@ +From 9c088fd89c872a5205b84f4da6ffa4d19c9b471b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 01/12] PCI/NVMe: apple: add S8003 PCIe host, ANS NVMe and a + boot-time write window +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +S8003 (A9) support for the H9P PCIe host, the ANS NVMe controller, its +DART and PMGR power states, and the watchdog. NVMe stays read-only +except for the range named by nvme.write_window=NSID:FIRST-LAST at +boot; only Write commands inside it reach the controller, and +namespaces 2-7 stay read-only. NVME_APPLE_ANS_EARLY_USB optionally +holds the host until userspace releases it. + +Builds on the T8010 (A10) PCIe/ANS support by Paul Praschl in +HoolockLinux, which includes code by Corellium LLC. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../devicetree/bindings/iommu/apple,dart.yaml | 30 + + .../bindings/pci/apple,t8010-pcie.yaml | 343 ++ + .../bindings/power/apple,pmgr-pwrstate.yaml | 8 + + drivers/iommu/apple-dart.c | 365 +- + drivers/iommu/io-pgtable-dart.c | 21 +- + drivers/nvme/host/Kconfig | 46 + + drivers/nvme/host/Makefile | 2 +- + drivers/nvme/host/core.c | 5 +- + drivers/nvme/host/nvme.h | 3 + + drivers/nvme/host/pci-apple-ans.c | 1244 ++++++ + drivers/nvme/host/pci-internal.h | 220 + + drivers/nvme/host/pci.c | 1061 ++++- + drivers/pci/controller/Kconfig | 12 + + drivers/pci/controller/Makefile | 1 + + drivers/pci/controller/pcie-apple-h9p.c | 3898 +++++++++++++++++ + drivers/pmdomain/apple/pmgr-pwrstate.c | 423 +- + drivers/watchdog/apple_wdt.c | 17 + + include/linux/apple-ans.h | 116 + + include/linux/io-pgtable.h | 6 + + include/linux/soc/apple/pmgr-observe.h | 27 + + include/linux/soc/apple/s8003-power.h | 38 + + 21 files changed, 7660 insertions(+), 226 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml + create mode 100644 drivers/nvme/host/pci-apple-ans.c + create mode 100644 drivers/nvme/host/pci-internal.h + create mode 100644 drivers/pci/controller/pcie-apple-h9p.c + create mode 100644 include/linux/apple-ans.h + create mode 100644 include/linux/soc/apple/pmgr-observe.h + create mode 100644 include/linux/soc/apple/s8003-power.h + +diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml +index de2ce3dd46b00202138a02ebb09a19a884410d92..881c4ef313f304abd61be3c72cb994275a97f462 100644 +--- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml ++++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml +@@ -61,6 +61,36 @@ properties: + power-domains: + maxItems: 1 + ++ apple,dma-range: ++ $ref: /schemas/types.yaml#/definitions/uint32-array ++ minItems: 4 ++ maxItems: 4 ++ description: ++ IOVA window for masters that use a ++ one-cell specifier (e.g. through "iommu-map"). DMA addresses outside it ++ are claimed by other agents on the bus, such as the NVMe MMU and the MSI ++ doorbell behind the A9/A10 PCIe root complex. The window only limits ++ IOVA allocation; it is not an offset. ++ ++ apple,error-reflector: ++ $ref: /schemas/types.yaml#/definitions/uint64 ++ description: ++ S5L8960X-style DARTs only. Physical address of the page that faulting ++ transactions are redirected to, so that they complete instead of ++ stalling the master. Taken from the ADT property of the same name. ++ ++ apple,fetch-config: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: ++ S5L8960X-style DARTs only. Value for the table fetch configuration ++ register, from the ADT property "fetch-config". ++ ++ apple,diag-config: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: ++ S5L8960X-style DARTs only. Value for the diagnostic configuration ++ register, from the ADT property "diag-config". ++ + required: + - compatible + - reg +diff --git a/Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml b/Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..ed6bd936f52d2c7b0c64af1502dd51ad78c6106e +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml +@@ -0,0 +1,343 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/pci/apple,t8010-pcie.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple S8003 and T8010 PCIe host controller ++ ++maintainers: ++ - Paul Praschl ++ ++description: | ++ Apple A9/S8003 (TSMC) and A10/T8010 devices use the H8P/H9P PCIe root ++ complex for internal storage and Wi-Fi. It exposes one ECAM window shared by up to four root ++ ports, controller-specific PHY and port register windows, and a local MSI ++ block. ++ ++ The storage port also contains an NVMMU which translates the proprietary ++ Apple ANS FlatDMA format. Unlike a general-purpose IOMMU, this block uses a ++ loader-provided, physically contiguous host-memory buffer and a fixed IOVA ++ window. The PCIe driver owns that NVMMU state and exposes it only to the ANS ++ NVMe glue. ++ ++ Each enabled root port is represented by a child node. This allows port ++ devices to carry their own reset, clock-request, firmware and other ++ device-specific properties. ++ ++allOf: ++ - $ref: /schemas/pci/pci-host-bridge.yaml# ++ - $ref: /schemas/interrupt-controller/msi-controller.yaml# ++ ++properties: ++ compatible: ++ enum: ++ - apple,s8003-pcie ++ - apple,t8010-pcie ++ ++ reg: ++ maxItems: 9 ++ ++ reg-names: ++ items: ++ - const: config ++ - const: rc ++ - const: phy ++ - const: phy-ip ++ - const: port0 ++ - const: port1 ++ - const: port2 ++ - const: port3 ++ - const: nvmmu ++ ++ interrupts: ++ description: ++ One state interrupt for each root port followed by the storage-port ++ NVMMU fault interrupt. ++ maxItems: 5 ++ ++ interrupt-names: ++ items: ++ - const: port0 ++ - const: port1 ++ - const: port2 ++ - const: port3 ++ - const: nvmmu ++ ++ clocks: ++ maxItems: 3 ++ ++ clock-names: ++ items: ++ - const: core ++ - const: aux ++ - const: ref ++ ++ power-domains: ++ minItems: 3 ++ maxItems: 7 ++ ++ power-domain-names: ++ minItems: 3 ++ items: ++ - const: core ++ - const: aux ++ - const: ref ++ - const: link0 ++ - const: link1 ++ - const: link2 ++ - const: link3 ++ ++ apple,tunable-phy: ++ $ref: /schemas/types.yaml#/definitions/uint32-matrix ++ items: ++ items: ++ - description: register offset in the "phy" block ++ - description: mask ++ - description: value ++ description: ++ PHY tunables (Apple device tree apcie-phy-tunables), applied as ++ (reg & ~mask) | (value & mask) after the common init. ++ ++ apple,tunable-common: ++ $ref: /schemas/types.yaml#/definitions/uint32-matrix ++ items: ++ items: ++ - description: register offset in the "rc" block ++ - description: mask ++ - description: value ++ description: ++ Root complex tunables (apcie-common-tunables), applied after the PHY ++ tunables. ++ ++ apple,nvmmu-invalidate-fifo: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ enum: [0, 1, 2] ++ default: 0 ++ description: ++ NVMMU invalidate workaround (nvme-mmu0 invalidate-fifo-workaround): ++ 1 waits for the invalidate FIFO in the port 0 block to drain before ++ each invalidate, 2 spaces invalidates by a fixed delay. ++ ++ apple,lane-cfg: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ maximum: 15 ++ default: 0 ++ description: ++ Lane configuration of the root complex, the "lane-cfg" property of the ++ Apple device tree apcie node. Programmed into the common control ++ register together with the PHY init request. ++ ++ devpwr-gpios: ++ description: ++ Optional device power GPIOs shared by the root ports. ++ maxItems: 4 ++ ++ msi-parent: true ++ ++ msi-ranges: ++ maxItems: 1 ++ ++ apple,msi-doorbell: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: ++ MSI doorbell address programmed into downstream endpoints. ++ ++ apple,nvmmu-iova: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: ++ Base device-visible address of the storage-port NVMMU window. ++ ++ apple,nvme-hmb: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: ++ Phandle of the loader-populated reserved-memory node used as the Apple ++ ANS NVMe host-memory buffer. ++ ++ interrupt-controller: true ++ ++ '#interrupt-cells': ++ const: 1 ++ ++patternProperties: ++ '^pci@[0-3],0$': ++ type: object ++ properties: ++ reg: ++ maxItems: 1 ++ ++ reset-gpios: ++ maxItems: 1 ++ ++ clkreq-gpios: ++ maxItems: 1 ++ ++ pwren-gpios: ++ maxItems: 1 ++ description: ++ Power enable of the endpoint (e.g. the Wi-Fi chip's REG_ON). A ++ port with this is brought up after probe, once the GPIO exists: ++ endpoint powered with PERST# asserted, then the link is trained ++ and the bus rescanned. ++ ++ device-wake-gpios: ++ maxItems: 1 ++ description: ++ Host-to-endpoint wake (ADT "function-device_wake"), kept ++ deasserted. ++ ++ max-link-speed: ++ description: ++ Target link speed of the root port. Defaults to 3 (8 GT/s) for ++ port 0 on T8010, 2 (5 GT/s) for port 0 on S8003 and 1 otherwise. ++ ++ apple,tunable-config: ++ $ref: /schemas/types.yaml#/definitions/uint32-matrix ++ description: ++ Port block tunables (apcie-config-tunables), . ++ ++ apple,tunable-rc: ++ $ref: /schemas/types.yaml#/definitions/uint32-matrix ++ description: ++ Root port config space tunables (pcie-rc-tunables), ++ . ++ ++ apple,nvme-mode: ++ type: boolean ++ description: ++ The port carries the ANS NVMe endpoint (ADT "nvme-mode"); S8003 ++ then switches the root complex into NVMe mode before powering the ++ port. ++ ++ required: ++ - reg ++ - reset-gpios ++ ++ additionalProperties: true ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - interrupts ++ - interrupt-names ++ - clocks ++ - clock-names ++ - power-domains ++ - power-domain-names ++ - bus-range ++ - ranges ++ - msi-controller ++ - msi-parent ++ - msi-ranges ++ - apple,nvmmu-iova ++ - apple,nvme-hmb ++ - '#interrupt-cells' ++ ++if: ++ properties: ++ compatible: ++ const: apple,t8010-pcie ++then: ++ patternProperties: ++ '^pci@[0-3],0$': ++ required: ++ - clkreq-gpios ++ ++unevaluatedProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ ++ / { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ aliases { ++ nvme-hmb = &nvme_hmb; ++ }; ++ ++ aic: interrupt-controller { ++ interrupt-controller; ++ #interrupt-cells = <3>; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ nvme_hmb: nvme-hmb@0 { ++ reg = <0x0 0x0 0x0 0x0>; /* To be filled by loader */ ++ no-map; ++ }; ++ }; ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ pcie0: pcie@610000000 { ++ compatible = "apple,t8010-pcie"; ++ device_type = "pci"; ++ ++ reg = <0x6 0x10000000 0x0 0x1000000>, ++ <0x6 0x00000000 0x0 0x8000>, ++ <0x6 0x00008000 0x0 0x4000>, ++ <0x6 0x0a000000 0x0 0x40000>, ++ <0x6 0x01000000 0x0 0x4000>, ++ <0x6 0x02000000 0x0 0x4000>, ++ <0x6 0x03000000 0x0 0x4000>, ++ <0x6 0x04000000 0x0 0x4000>, ++ <0x6 0x01004000 0x0 0x4000>; ++ reg-names = "config", "rc", "phy", "phy-ip", ++ "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ interrupt-parent = <&aic>; ++ interrupts = , ++ , ++ , ++ , ++ ; ++ interrupt-names = "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ msi-controller; ++ msi-parent = <&pcie0>; ++ msi-ranges = <&aic AIC_IRQ 288 IRQ_TYPE_EDGE_RISING 32>; ++ apple,msi-doorbell = <0xbffff000>; ++ apple,nvmmu-iova = <0xbc000000>; ++ apple,nvme-hmb = <&nvme_hmb>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ bus-range = <0x00 0x0f>; ++ ranges = <0x03000000 0x0 0xc0000000 ++ 0x7 0xc0000000 0x0 0x40000000>; ++ ++ clocks = <&clkref>, <&clkref>, <&clkref>; ++ clock-names = "core", "aux", "ref"; ++ power-domains = <&ps_pcie>, <&ps_pcie_aux>, ++ <&ps_pcie_ref>; ++ power-domain-names = "core", "aux", "ref"; ++ ++ pci@0,0 { ++ device_type = "pci"; ++ reg = <0x0 0x0 0x0 0x0 0x0>; ++ reset-gpios = <&pinctrl_ap 12 GPIO_ACTIVE_LOW>; ++ clkreq-gpios = <&pinctrl_ap 16 GPIO_ACTIVE_LOW>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges; ++ }; ++ }; ++ }; ++ }; ++ ++... +diff --git a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml +index e269f433524efcc5a1e149886f30ea64e1ebe5a4..c0594368473cd7d56e5630ae8b71fb3063bb3ae1 100644 +--- a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml ++++ b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml +@@ -76,6 +76,14 @@ properties: + Forces this power domain to always be powered up. + type: boolean + ++ apple,idle-power-gate: ++ description: | ++ The block may discard its boot minimum-state floor when genpd powers ++ it off. The original floor is restored before power-on. Requires a ++ driver opt-in as this is experimental on S8000 power controllers. ++ Never use for firmware-owned scanout, CPU, fabric or wake domains. ++ type: boolean ++ + apple,min-state: + description: + Specifies the minimum power state for auto-PM. +diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c +index 85311e95ffc0368cae9227fa83131a166652d501..6bd449f91027129d5bf979a2eea12be3ba63dbf4 100644 +--- a/drivers/iommu/apple-dart.c ++++ b/drivers/iommu/apple-dart.c +@@ -11,6 +11,7 @@ + * Copyright (c) 2011,2016 Samsung Electronics Co., Ltd. + */ + ++#include + #include + #include + #include +@@ -22,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -29,6 +31,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +70,16 @@ + + #define DART_S5L8960X_TTBR 0x40 + ++/* ++ * Written by iOS (AppleS5L8960XDART) at every hardware init, from the ADT ++ * properties of the same names: the error reflector is the page that ++ * faulting transactions are redirected to, so they complete instead of ++ * stalling the requester. ++ */ ++#define DART_S5L8960X_DIAG_CONFIG 0x20 ++#define DART_S5L8960X_ERROR_REFLECTOR 0x24 ++#define DART_S5L8960X_FETCH_CONFIG 0x30 ++ + /* T8020/T6000 registers */ + + #define DART_T8020_STREAM_COMMAND 0x20 +@@ -215,11 +228,24 @@ struct apple_dart { + u32 supports_bypass : 1; + u32 four_level : 1; + ++ struct ratelimit_state fault_rs; ++ ++ /* IOVA window for 1-cell masters (apple,dma-range), default: all of ias */ ++ dma_addr_t dma_min; ++ dma_addr_t dma_max; ++ + struct iommu_group *sid2group[DART_MAX_STREAMS]; + struct iommu_device iommu; + + u32 save_tcr[DART_MAX_STREAMS]; + u32 save_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; ++ ++ /* S5L8960X: apple,error-reflector >> 12 (0 = leave alone), configs */ ++ u32 error_reflector; ++ u32 fetch_config; ++ u32 diag_config; ++ u32 has_fetch_config : 1; ++ u32 has_diag_config : 1; + }; + + struct apple_dart_hw { +@@ -291,6 +317,7 @@ struct apple_dart_domain { + struct io_pgtable_ops *pgtbl_ops; + + bool finalized; ++ int flush_error; /* a failed invalidate blocks DMA until stopped reinit */ + u64 mask; + struct mutex init_lock; + struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE]; +@@ -368,10 +395,12 @@ static u32 apple_dart_s5l8960x_read_tcr(struct apple_dart *dart, u32 sid) + static void apple_dart_s5l8960x_write_tcr(struct apple_dart *dart, u32 sid, u32 val) + { + WARN_ON(sid >= 4); ++ u32 shift = sid * DART_S5L8960X_TCR_BITS_PER_STREAM; + u32 tcr = readl(dart->regs + dart->hw->tcr); + +- tcr &= 0xff << (sid * DART_S5L8960X_TCR_BITS_PER_STREAM); +- tcr |= val; ++ /* One byte per stream: replace this stream's byte, keep the others */ ++ tcr &= ~(0xff << shift); ++ tcr |= (val & 0xff) << shift; + writel(tcr, dart->regs + dart->hw->tcr); + } + +@@ -559,6 +588,39 @@ apple_dart_t8110_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map) + stream_map, DART_T8110_TLB_CMD_OP_FLUSH_SID); + } + ++static void apple_dart_s5l8960x_hw_config(struct apple_dart *dart) ++{ ++ if (dart->error_reflector) ++ writel(dart->error_reflector, ++ dart->regs + DART_S5L8960X_ERROR_REFLECTOR); ++ if (dart->has_fetch_config) ++ writel(dart->fetch_config, ++ dart->regs + DART_S5L8960X_FETCH_CONFIG); ++ if (dart->has_diag_config) ++ writel(dart->diag_config, ++ dart->regs + DART_S5L8960X_DIAG_CONFIG); ++} ++ ++/* Snapshot before any W1C or table replacement. 0x100/0x500 have no ++ * exception-valid bit; direction/attributes alone do not indicate a fault. ++ */ ++static void apple_dart_s5l8960x_log_state(struct apple_dart *dart, ++ const char *when) ++{ ++ u32 error = readl(dart->regs + DART_S5L8960X_ERROR); ++ u32 sid = FIELD_GET(DART_S5L8960X_ERROR_STREAM, error); ++ ++ dev_info(dart->dev, ++ "DART %s before ack: err %#x valid %u code %#x sid %u write %u addr %#x TCR %#x TTBR %#x %#x %#x %#x\n", ++ when, error, !!(error & BIT(31)), error & 0x7f, sid, ++ !!(error & BIT(10)), readl(dart->regs + DART_S5L8960X_ERROR_ADDR), ++ readl(dart->regs + DART_S5L8960X_TCR), ++ readl(dart->regs + DART_TTBR(dart, sid, 0)), ++ readl(dart->regs + DART_TTBR(dart, sid, 1)), ++ readl(dart->regs + DART_TTBR(dart, sid, 2)), ++ readl(dart->regs + DART_TTBR(dart, sid, 3))); ++} ++ + static int apple_dart_hw_reset(struct apple_dart *dart) + { + u32 config; +@@ -572,6 +634,8 @@ static int apple_dart_hw_reset(struct apple_dart *dart) + return -EINVAL; + } + ++ if (dart->hw->type == DART_S5L8960X) ++ apple_dart_s5l8960x_log_state(dart, "hw_reset"); + stream_map.dart = dart; + bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); + bitmap_set(stream_map.sidmap, 0, dart->num_streams); +@@ -593,15 +657,17 @@ static int apple_dart_hw_reset(struct apple_dart *dart) + * S5L8960X DART only supports TLB invalidation if translation is + * enabled. + */ +- if (dart->hw->type == DART_S5L8960X) ++ if (dart->hw->type == DART_S5L8960X) { ++ apple_dart_s5l8960x_hw_config(dart); + return 0; ++ } + + return dart->hw->invalidate_tlb(&stream_map); + } + +-static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) ++static int apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) + { +- int i, j; ++ int i, j, ret, error = 0; + struct apple_dart_atomic_stream_map *domain_stream_map; + struct apple_dart_stream_map stream_map; + +@@ -611,8 +677,17 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) + for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++) + stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]); + +- stream_map.dart->hw->invalidate_tlb(&stream_map); ++ ret = stream_map.dart->hw->invalidate_tlb(&stream_map); ++ if (ret) { ++ if (!READ_ONCE(domain->flush_error)) ++ dev_err(stream_map.dart->dev, ++ "DART domain flush failed %d; blocking DMA\n", ret); ++ WRITE_ONCE(domain->flush_error, ret); ++ error = ret; ++ apple_dart_hw_disable_dma(&stream_map); ++ } + } ++ return error ?: READ_ONCE(domain->flush_error); + } + + static void apple_dart_flush_iotlb_all(struct iommu_domain *domain) +@@ -629,8 +704,7 @@ static void apple_dart_iotlb_sync(struct iommu_domain *domain, + static int apple_dart_iotlb_sync_map(struct iommu_domain *domain, + unsigned long iova, size_t size) + { +- apple_dart_domain_flush_tlb(to_dart_domain(domain)); +- return 0; ++ return apple_dart_domain_flush_tlb(to_dart_domain(domain)); + } + + static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain, +@@ -655,6 +729,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, + + if (!ops) + return -ENODEV; ++ if (READ_ONCE(dart_domain->flush_error)) ++ return READ_ONCE(dart_domain->flush_error); + + return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize, + pgcount, prot, gfp, mapped); +@@ -672,24 +748,123 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain, + gather); + } + +-static void ++static void apple_dart_s5l8960x_attach_config(struct apple_dart *dart) ++{ ++ u32 error; ++ ++ apple_dart_s5l8960x_log_state(dart, "attach/reinit"); ++ error = readl(dart->regs + DART_S5L8960X_ERROR); ++ writel(error, dart->regs + DART_S5L8960X_ERROR); ++} ++ ++static int + apple_dart_setup_translation(struct apple_dart_domain *domain, + struct apple_dart_stream_map *stream_map) + { +- int i; +- struct io_pgtable_cfg *pgtbl_cfg = ++ struct apple_dart *dart = stream_map->dart; ++ struct io_pgtable_cfg *cfg = + &io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg; ++ bool s5l = dart->hw->type == DART_S5L8960X; ++ u32 expected, actual; ++ int i, sid, ret; + +- for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) +- apple_dart_hw_set_ttbr(stream_map, i, +- pgtbl_cfg->apple_dart_cfg.ttbr[i]); +- for (; i < stream_map->dart->hw->ttbr_count; ++i) ++ if (s5l) { ++ apple_dart_s5l8960x_attach_config(dart); ++ apple_dart_hw_disable_dma(stream_map); ++ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { ++ if (dart->hw->read_tcr(dart, sid)) { ++ ret = -EIO; ++ goto fail; ++ } ++ } ++ apple_dart_hw_clear_all_ttbrs(stream_map); ++ /* Apple's init: reflector, stream tables, verified TCR, fetch, ++ * diag. The DART cannot invalidate while TCR is disabled. ++ */ ++ if (dart->error_reflector) ++ writel(dart->error_reflector, ++ dart->regs + DART_S5L8960X_ERROR_REFLECTOR); ++ } ++ for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; i++) ++ apple_dart_hw_set_ttbr(stream_map, i, cfg->apple_dart_cfg.ttbr[i]); ++ for (; i < dart->hw->ttbr_count; i++) + apple_dart_hw_clear_ttbr(stream_map, i); + +- apple_dart_hw_enable_translation(stream_map, +- pgtbl_cfg->apple_dart_cfg.n_levels); +- stream_map->dart->hw->invalidate_tlb(stream_map); ++ dma_wmb(); ++ apple_dart_hw_enable_translation(stream_map, cfg->apple_dart_cfg.n_levels); ++ if (s5l) { ++ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { ++ /* Only selected stream bytes: SID 0 expects 0x80, not ++ * 0x80808080 (which would enable unowned streams). ++ */ ++ if (dart->hw->read_tcr(dart, sid) != 0x80) { ++ ret = -EIO; ++ goto fail; ++ } ++ } ++ if (dart->has_fetch_config) ++ writel(dart->fetch_config, dart->regs + DART_S5L8960X_FETCH_CONFIG); ++ if (dart->has_diag_config) ++ writel(dart->diag_config, dart->regs + DART_S5L8960X_DIAG_CONFIG); ++ } ++ ret = dart->hw->invalidate_tlb(stream_map); ++ if (ret) ++ goto fail; ++ if (s5l) { ++ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { ++ for (i = 0; i < dart->hw->ttbr_count; i++) { ++ expected = i < cfg->apple_dart_cfg.n_ttbrs ? ++ BIT(31) | (cfg->apple_dart_cfg.ttbr[i] >> 12) : 0; ++ actual = readl(dart->regs + DART_TTBR(dart, sid, i)); ++ if (actual != expected || dart->hw->read_tcr(dart, sid) != 0x80) { ++ ret = -EIO; ++ goto fail; ++ } ++ } ++ } ++ dev_info(dart->dev, "DART checked reinit: TCR/TTBR readback and stream flush complete\n"); ++ } ++ return 0; ++fail: ++ WRITE_ONCE(domain->flush_error, ret); ++ apple_dart_hw_disable_dma(stream_map); ++ dev_err(dart->dev, "DART reinit/flush failed %d; translation blocked\n", ret); ++ return ret; ++} ++ ++/* Called only by the j71t ANS driver with DMA stopped. Use the domain's ++ * actual TTBRs, never a global reset or the diagnostic mapping of the DART. ++ */ ++int apple_ans_dart_set_active(struct device *dev, bool active) ++{ ++ struct iommu_domain *domain = iommu_get_domain_for_dev(dev); ++ struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev); ++ struct apple_dart_stream_map *map; ++ struct apple_dart_domain *d; ++ int i, ret; ++ ++ if (!of_machine_is_compatible("apple,j71t") || !domain || !cfg || ++ !cfg->stream_maps[0].dart || ++ (domain->type != IOMMU_DOMAIN_DMA && domain->type != IOMMU_DOMAIN_DMA_FQ)) ++ return -ENODEV; ++ d = to_dart_domain(domain); ++ for_each_stream_map(i, cfg, map) { ++ if (map->dart->hw->type != DART_S5L8960X) ++ return -EOPNOTSUPP; ++ if (!active) { ++ apple_dart_hw_disable_dma(map); ++ continue; ++ } ++ ret = apple_dart_setup_translation(d, map); ++ if (ret) ++ return ret; ++ } ++ if (active) ++ WRITE_ONCE(d->flush_error, 0); ++ dev_info(dev, "Apple ANS: DART force_active(%u) complete\n", active); ++ return 0; + } ++EXPORT_SYMBOL_GPL(apple_ans_dart_set_active); + + static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, + struct apple_dart_master_cfg *cfg) +@@ -722,6 +897,8 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, + .coherent_walk = 1, + .iommu_dev = dart->dev, + }; ++ if (dart->hw->type == DART_S5L8960X) ++ pgtbl_cfg.quirks |= IO_PGTABLE_QUIRK_APPLE_DART_S5L8960X; + + dart_domain->pgtbl_ops = alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg, + &dart_domain->domain); +@@ -730,10 +907,28 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, + goto done; + } + +- if (dart->hw->type == DART_S5L8960X) ++ /* ++ * S5L8960X sees IOVAs relative to the window base: behind the A9 PCIe ++ * root complex, IOVA 0xbbffe000 (window 0x80000000) reaches the DART as ++ * 0x3bffe000 (device log, 2026-10-22). Index the tables the same way. ++ * Masking the base off equals subtracting it only if the base is ++ * aligned beyond the window size. ++ */ ++ if (dart->hw->type == DART_S5L8960X) { ++ if (cfg->dma_min && ++ cfg->dma_max - cfg->dma_min >= (cfg->dma_min & -cfg->dma_min)) { ++ dev_err(dart->dev, ++ "DMA window %pad..%pad is not base-aligned\n", ++ &cfg->dma_min, &cfg->dma_max); ++ free_io_pgtable_ops(dart_domain->pgtbl_ops); ++ dart_domain->pgtbl_ops = NULL; ++ ret = -EINVAL; ++ goto done; ++ } + dart_domain->mask = ~cfg->dma_min; +- else ++ } else { + dart_domain->mask = DMA_BIT_MASK(pgtbl_cfg.ias); ++ } + + dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; + dart_domain->domain.geometry.aperture_start = cfg->dma_min; +@@ -799,8 +994,11 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain, + if (ret) + return ret; + +- for_each_stream_map(i, cfg, stream_map) +- apple_dart_setup_translation(dart_domain, stream_map); ++ for_each_stream_map(i, cfg, stream_map) { ++ ret = apple_dart_setup_translation(dart_domain, stream_map); ++ if (ret) ++ return ret; ++ } + return 0; + } + +@@ -914,8 +1112,8 @@ static int apple_dart_of_xlate(struct device *dev, + struct platform_device *iommu_pdev = of_find_device_by_node(args->np); + struct apple_dart *dart = platform_get_drvdata(iommu_pdev); + struct apple_dart *cfg_dart; +- dma_addr_t dma_max = DMA_BIT_MASK(dart->ias); +- dma_addr_t dma_min = 0; ++ dma_addr_t dma_max = dart->dma_max; ++ dma_addr_t dma_min = dart->dma_min; + int i, sid; + + put_device(&iommu_pdev->dev); +@@ -1179,10 +1377,34 @@ static const struct iommu_ops apple_dart_iommu_ops = { + } + }; + ++/* ++ * Follow the tables the way the hardware does for DART-side address @addr ++ * of stream @sid: TTBR = addr[31:30], STE = addr[29:21], PTE = addr[20:12] ++ * (iOS, AppleS5L8960XDART). Read-only; for fault diagnostics. ++ */ ++static void apple_dart_s5l8960x_walk(struct apple_dart *dart, u32 sid, ++ u32 addr, const char *why) ++{ ++ u32 ttbr_idx = addr >> 30, ste_idx = (addr >> 21) & 0x1ff; ++ u32 pte_idx = (addr >> 12) & 0x1ff; ++ u32 ttbr = readl(dart->regs + DART_TTBR(dart, sid, ttbr_idx)); ++ phys_addr_t pa = (phys_addr_t)(ttbr & GENMASK(23, 0)) << 12; ++ u64 ste = 0, pte = 0; ++ ++ if ((ttbr & DART_T8020_TTBR_VALID) && pfn_valid(PHYS_PFN(pa))) { ++ ste = READ_ONCE(((u64 *)phys_to_virt(pa))[ste_idx]); ++ pa = ste & GENMASK_ULL(35, 12); ++ if ((ste & BIT(0)) && pfn_valid(PHYS_PFN(pa))) ++ pte = READ_ONCE(((u64 *)phys_to_virt(pa))[pte_idx]); ++ } ++ dev_err(dart->dev, ++ "%s: walk %#x: TTBR%u %#x -> STE[%#x] %#llx -> PTE[%#x] %#llx\n", ++ why, addr, ttbr_idx, ttbr, ste_idx, ste, pte_idx, pte); ++} ++ + static irqreturn_t apple_dart_s5l8960x_irq(int irq, void *dev) + { + struct apple_dart *dart = dev; +- struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dart->dev); + const char *fault_name = NULL; + u32 error = readl(dart->regs + DART_S5L8960X_ERROR); + u32 error_code = FIELD_GET(DART_S5L8960X_ERROR_CODE, error); +@@ -1206,10 +1428,20 @@ static irqreturn_t apple_dart_s5l8960x_irq(int irq, void *dev) + else + fault_name = "unknown"; + ++ /* ++ * The DART's own device has no iommu master config (dev->iommu is ++ * NULL), so don't look one up here: doing so turned every fault into ++ * a NULL dereference in hard IRQ context, i.e. a silent panic. The ++ * register holds the address as the DART saw it, i.e. relative to the ++ * window base; add apple,dma-range's base back for the device's IOVA. ++ */ + dev_err_ratelimited( + dart->dev, +- "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", +- error, stream_idx, error_code, fault_name, addr | cfg->dma_min); ++ "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%x (IOVA %#llx)\n", ++ error, stream_idx, error_code, fault_name, addr, ++ (u64)addr | dart->dma_min); ++ if (__ratelimit(&dart->fault_rs)) ++ apple_dart_s5l8960x_walk(dart, stream_idx, addr, "fault"); + + writel(error, dart->regs + DART_S5L8960X_ERROR); + return IRQ_HANDLED; +@@ -1294,6 +1526,74 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) + return IRQ_HANDLED; + } + ++/* ++ * apple,dma-range = : the IOVA window of ++ * masters that reference this DART with a 1-cell specifier (e.g. through ++ * "iommu-map"). On the A9/A10 PCIe DARTs this is the ADT vm-offset/vm-size, ++ * 0x80000000 + 0x3c000000: the root complex sends 0x40000000-0x7fffffff to ++ * the NVMMU (FlatDMA), 0xbc000000+ to the NVMMU SART and 0xbffff000 is the ++ * MSI doorbell, so DART IOVAs must stay inside the window. Doing this here, ++ * not with "dma-ranges" on the PCIe node, keeps the CPU's DMA zones intact. ++ */ ++static int apple_dart_parse_dma_range(struct apple_dart *dart) ++{ ++ u64 range[2]; ++ dma_addr_t end; ++ ++ dart->dma_min = 0; ++ dart->dma_max = DMA_BIT_MASK(dart->ias); ++ ++ if (of_property_read_u64_array(dart->dev->of_node, "apple,dma-range", ++ range, 2)) ++ return 0; ++ ++ if (!range[1] || !IS_ALIGNED(range[0], dart->pgsize) || ++ !IS_ALIGNED(range[1], dart->pgsize) || ++ check_add_overflow(range[0], range[1] - 1, &end) || ++ end > DMA_BIT_MASK(dart->ias)) { ++ dev_err(dart->dev, "bad apple,dma-range %#llx+%#llx\n", ++ range[0], range[1]); ++ return -EINVAL; ++ } ++ ++ dart->dma_min = range[0]; ++ dart->dma_max = end; ++ dev_info(dart->dev, "IOVA window %pad..%pad\n", &dart->dma_min, ++ &dart->dma_max); ++ return 0; ++} ++ ++static int apple_dart_s5l8960x_parse_config(struct apple_dart *dart) ++{ ++ struct device_node *np = dart->dev->of_node; ++ u64 reflector; ++ ++ if (!of_property_read_u64(np, "apple,error-reflector", &reflector)) { ++ if (!reflector || !IS_ALIGNED(reflector, SZ_4K) || ++ reflector >> 12 > U32_MAX) { ++ dev_err(dart->dev, "bad apple,error-reflector %#llx\n", ++ reflector); ++ return -EINVAL; ++ } ++ dart->error_reflector = reflector >> 12; ++ } ++ dart->has_fetch_config = !of_property_read_u32(np, "apple,fetch-config", ++ &dart->fetch_config); ++ dart->has_diag_config = !of_property_read_u32(np, "apple,diag-config", ++ &dart->diag_config); ++ ++ /* All-ones: the DART isn't powered yet (A9 PCIe: until the port is) */ ++ dev_dbg(dart->dev, ++ "boot state: TCR %#x reflector %#x fetch %#x diag %#x error %#x/%#x\n", ++ readl(dart->regs + DART_S5L8960X_TCR), ++ readl(dart->regs + DART_S5L8960X_ERROR_REFLECTOR), ++ readl(dart->regs + DART_S5L8960X_FETCH_CONFIG), ++ readl(dart->regs + DART_S5L8960X_DIAG_CONFIG), ++ readl(dart->regs + DART_S5L8960X_ERROR), ++ readl(dart->regs + DART_S5L8960X_ERROR_ADDR)); ++ return 0; ++} ++ + static int apple_dart_probe(struct platform_device *pdev) + { + int ret; +@@ -1309,6 +1609,7 @@ static int apple_dart_probe(struct platform_device *pdev) + dart->dev = dev; + dart->hw = of_device_get_match_data(dev); + spin_lock_init(&dart->lock); ++ ratelimit_state_init(&dart->fault_rs, 5 * HZ, 4); + + dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(dart->regs)) +@@ -1367,6 +1668,10 @@ static int apple_dart_probe(struct platform_device *pdev) + break; + } + ++ ret = apple_dart_parse_dma_range(dart); ++ if (ret) ++ goto err_clk_disable; ++ + if (dart->num_streams > DART_MAX_STREAMS) { + dev_err(&pdev->dev, "Too many streams (%d > %d)\n", + dart->num_streams, DART_MAX_STREAMS); +@@ -1374,6 +1679,12 @@ static int apple_dart_probe(struct platform_device *pdev) + goto err_clk_disable; + } + ++ if (dart->hw->type == DART_S5L8960X) { ++ ret = apple_dart_s5l8960x_parse_config(dart); ++ if (ret) ++ goto err_clk_disable; ++ } ++ + ret = apple_dart_hw_reset(dart); + if (ret) + goto err_clk_disable; +diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c +index cbc5d6aa2daa23fd11e8e19be697500cd775c7cd..29a6f3f48c4bcc3a5cbdeb50f7b507c0a93df095 100644 +--- a/drivers/iommu/io-pgtable-dart.c ++++ b/drivers/iommu/io-pgtable-dart.c +@@ -63,6 +63,13 @@ + /* marks PTE as valid */ + #define APPLE_DART_PTE_VALID BIT(0) + ++/* ++ * S5L8960X table entries (iOS AppleS5L8960XDART::_createL3Table writes ++ * (pa & 0xffffff000) | 3). With bit 0 only, the DART reports an invalid ++ * STE, i.e. "NO PMD". ++ */ ++#define APPLE_DART_S5L8960X_TABLE_VALID GENMASK_ULL(1, 0) ++ + /* IOPTE accessors */ + #define iopte_deref(pte, d) __va(iopte_to_paddr(pte, d)) + +@@ -125,9 +132,15 @@ static int dart_init_pte(struct dart_io_pgtable *data, + return -EEXIST; + } + +- /* subpage protection: always allow access to the entire page */ +- pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_START, 0); +- pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_END, 0xfff); ++ /* ++ * subpage protection: always allow access to the entire page. S5L8960X ++ * keeps its subpage range at bits 61:50/47:36 and ignores it with bit 1 ++ * (SP_DIS) set; iOS leaves those bits clear, so do the same. ++ */ ++ if (!(data->iop.cfg.quirks & IO_PGTABLE_QUIRK_APPLE_DART_S5L8960X)) { ++ pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_START, 0); ++ pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_END, 0xfff); ++ } + + pte |= APPLE_DART_PTE_VALID; + +@@ -145,6 +158,8 @@ static dart_iopte dart_install_table(dart_iopte *table, + dart_iopte old, new; + + new = paddr_to_iopte(__pa(table), data) | APPLE_DART_PTE_VALID; ++ if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_APPLE_DART_S5L8960X) ++ new |= APPLE_DART_S5L8960X_TABLE_VALID; + + /* + * Ensure the table itself is visible before its PTE can be. +diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig +index 31974c7dd20c9174961eac91e3823e939d325daa..7812ca9389fcc87ccbc22cb83b054ff5a25e7beb 100644 +--- a/drivers/nvme/host/Kconfig ++++ b/drivers/nvme/host/Kconfig +@@ -14,6 +14,52 @@ config BLK_DEV_NVME + To compile this driver as a module, choose M here: the + module will be called nvme. + ++config NVME_APPLE_ANS_EARLY_USB ++ bool "Defer j71t PCIe/ANS probe until early USB userspace is ready" ++ depends on BLK_DEV_NVME && PCIE_APPLE_H9P ++ help ++ Default pcie_apple_h9p.probe and nvme.apple_ans_probe to false on ++ j71t. The boot initramfs must bring up USB logging/rescue access, ++ set both parameters to true and reprobe the platform PCIe host, ++ then any deferred ANS endpoint. This allows /init to run even when ++ the PCIe link fails before endpoint enumeration. Other boards are ++ unchanged. Requires such an initramfs. ++ ++config NVME_PCI_READ_ONLY ++ bool "Refuse writes to NVMe PCI devices" ++ depends on BLK_DEV_NVME ++ default y if ARCH_APPLE ++ help ++ Make every NVMe PCI controller read-only. Namespaces are always ++ marked read-only and the driver refuses, before submission, every ++ command that could modify the medium or the controller's persistent ++ state: I/O queues may only read and flush, and the admin queue only ++ accepts identify, log/feature reads, queue management and a short ++ list of volatile Set Features. There is no runtime override. ++ ++ This exists for bring-up on Apple A9/A10 iDevices, whose internal ++ NVMe disk holds the iOS installation. Say N on any other system. ++ ++config NVME_PCI_WRITE_WINDOW ++ bool "Allow one boot-time write window on read-only NVMe PCI devices" ++ depends on NVME_PCI_READ_ONLY ++ help ++ Accept nvme.write_window=NSID:FIRST-LAST on the kernel command ++ line. NVMe Write commands to namespace NSID whose whole LBA range ++ lies within FIRST..LAST (inclusive, in that namespace's logical ++ blocks) are then allowed, and that namespace is no longer marked ++ read-only. Everything else CONFIG_NVME_PCI_READ_ONLY refuses stays ++ refused: writes outside the window or to any other namespace, ++ Write Zeroes, Dataset Management (trim), Format, Sanitize, firmware ++ and namespace management, and vendor commands. Trim and Write ++ Zeroes aren't advertised to the block layer while a window is set. ++ ++ Without the parameter, or if it doesn't parse, the controller is ++ exactly as read-only as with CONFIG_NVME_PCI_READ_ONLY alone. ++ ++ This lets an Apple iDevice keep iOS on its disk and run Linux from ++ one partition next to it. Say N unless you need that. ++ + config NVME_MULTIPATH + bool "NVMe multipath support" + depends on NVME_CORE +diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile +index 67563a69f7dcca1c612bf71931b12ec7475b3d3d..a06d98b505f62ab0d9946ddd5251b11edca4c909 100644 +--- a/drivers/nvme/host/Makefile ++++ b/drivers/nvme/host/Makefile +@@ -20,7 +20,7 @@ nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS) += fault_inject.o + nvme-core-$(CONFIG_NVME_HWMON) += hwmon.o + nvme-core-$(CONFIG_NVME_HOST_AUTH) += auth.o + +-nvme-y += pci.o ++nvme-y += pci.o pci-apple-ans.o + + nvme-fabrics-y += fabrics.o + +diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c +index 1322c678f4eb80cd437b79ee192cc050ad9bee6b..ccfec1e14e65179d4df008ea0ac9b46f7cade8f4 100644 +--- a/drivers/nvme/host/core.c ++++ b/drivers/nvme/host/core.c +@@ -2194,7 +2194,10 @@ static bool nvme_update_disk_info(struct nvme_ns *ns, struct nvme_id_ns *id, + + static bool nvme_ns_is_readonly(struct nvme_ns *ns, struct nvme_ns_info *info) + { +- return info->is_readonly || test_bit(NVME_NS_FORCE_RO, &ns->flags); ++ return info->is_readonly || test_bit(NVME_NS_FORCE_RO, &ns->flags) || ++ test_bit(NVME_CTRL_FORCE_RO, &ns->ctrl->flags) || ++ (ns->ctrl->ops->ns_is_readonly && ++ ns->ctrl->ops->ns_is_readonly(ns->ctrl, ns->head->ns_id)); + } + + static inline bool nvme_first_scan(struct gendisk *disk) +diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h +index 75e5d5a8a77cc352f94362e8ecaba1f16c7af22d..16b2d89895376e92f09b3a7f5195465f6581039e 100644 +--- a/drivers/nvme/host/nvme.h ++++ b/drivers/nvme/host/nvme.h +@@ -336,6 +336,7 @@ enum nvme_ctrl_flags { + NVME_CTRL_SKIP_ID_CNS_CS = 4, + NVME_CTRL_DIRTY_CAPABILITY = 5, + NVME_CTRL_FROZEN = 6, ++ NVME_CTRL_FORCE_RO = 7, + }; + + struct nvme_ctrl { +@@ -666,6 +667,8 @@ struct nvme_ctrl_ops { + void (*print_device_info)(struct nvme_ctrl *ctrl); + bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl); + unsigned long (*get_virt_boundary)(struct nvme_ctrl *ctrl, bool is_admin); ++ /* Optional: transport-forced read-only namespaces */ ++ bool (*ns_is_readonly)(struct nvme_ctrl *ctrl, u32 nsid); + }; + + /* +diff --git a/drivers/nvme/host/pci-apple-ans.c b/drivers/nvme/host/pci-apple-ans.c +new file mode 100644 +index 0000000000000000000000000000000000000000..19ae70dca5c9df518a4bfd01421833fcf480dd92 +--- /dev/null ++++ b/drivers/nvme/host/pci-apple-ans.c +@@ -0,0 +1,1244 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple ANS PCI NVMe glue for H8 and H9 SoCs. ++ * ++ * Copyright (C) 2020 Corellium LLC ++ * Copyright (C) 2026 Paul Praschl ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci-internal.h" ++ ++#define NVME_CMD_APPLE_ANS_FLATDMA BIT(5) ++ ++#define APPLE_ANS_REG_INIT 0x1800 ++#define APPLE_ANS_REG_INIT_REGULAR 0 ++#define APPLE_ANS_REG_HMB_SIZE_REQ 0x1808 ++#define APPLE_ANS_REG_HMB_ALIGN_REQ 0x180c ++#define APPLE_ANS_REG_HMB_BASE_LO 0x1810 ++#define APPLE_ANS_REG_HMB_BASE_HI 0x1814 ++#define APPLE_ANS_REG_HMB_SIZE 0x1818 ++#define APPLE_ANS_REG_CORE_MASK 0x1824 ++#define APPLE_ANS_REG_LOG_SIZE 0x1828 ++/* ++ * iOS reads this first in InitializeController: 0xbfbfbfbf means the ++ * controller waits for a boot-from-host firmware upload (0x1b00-0x1b14), ++ * which Linux doesn't do. ++ */ ++#define APPLE_ANS_REG_BFH 0x1b18 ++#define APPLE_ANS_BFH_MAGIC 0xbfbfbfbf ++/* ++ * S3E config space: the A10 template wrote 0x17c, 0x18c and 0x188, which are ++ * the LTR max latencies and the L1 PM substates CTL2/CTL1 registers when the ++ * LTR capability sits at 0x178 and L1SS at 0x180 (inferred). Look the ++ * capabilities up instead of hardcoding the offsets. The values match the ++ * ADT "pci-max-latency" and "pci-l1pm-control" (enable bits cleared). ++ */ ++#define APPLE_ANS_LTR_MAX_LATENCY 0x10081008 ++#define APPLE_ANS_L1SS_CTL1_T8010 0x40550000 ++#define APPLE_ANS_L1SS_CTL1_S8000 0x40440000 ++#define APPLE_ANS_NVME_MAX_SECTORS \ ++ (APPLE_ANS_NVMMU_MAX_PAGES * APPLE_ANS_NVMMU_PAGE_SIZE >> SECTOR_SHIFT) ++ ++struct apple_ans_nvme_req { ++ u64 pages[APPLE_ANS_NVMMU_MAX_PAGES]; ++ u32 nsid; ++ u16 qid; ++ u16 cid; ++ u8 opcode; ++ bool mapped; ++}; ++ ++/* ++ * NSID 1 is the data namespace (iOS disk0: the APFS container). The others ++ * hold factory and boot data a DFU restore can't bring back. On j71t: ++ * 2 LLB/iBoot, 3 syscfg, 4 iPCB, 5 NVRAM, 6 effaceable storage, 7 unused. ++ */ ++#define APPLE_ANS_DATA_NSID 1 ++ ++static bool apple_ans_unlock_system_namespaces; ++module_param(apple_ans_unlock_system_namespaces, bool, 0444); ++MODULE_PARM_DESC(apple_ans_unlock_system_namespaces, ++ "Apple ANS: DANGEROUS. Allow commands that can change namespaces other than NSID 1 (LLB/iBoot, syscfg, NVRAM, effaceable storage) or the whole controller (format/sanitize all, firmware, namespace management, security, vendor commands). Default N; boot-time only."); ++ ++static bool apple_ans_dart_hmb; ++module_param(apple_ans_dart_hmb, bool, 0444); ++MODULE_PARM_DESC(apple_ans_dart_hmb, ++ "Apple ANS: if the loader provides no HMB, use a DART-mapped host buffer (the iOS fallback)"); ++ ++/* ++ * Bring-up debugging knobs. Enabling the controller (CC.EN) has frozen ++ * the whole SoC, so the enable can be stopped short, held until userspace ++ * says go, slowed down so each step reaches the screen, and is followed by ++ * a watch of SoC-side state only. ++ */ ++static bool apple_ans_probe = !IS_ENABLED(CONFIG_NVME_APPLE_ANS_EARLY_USB); ++module_param(apple_ans_probe, bool, 0644); ++MODULE_PARM_DESC(apple_ans_probe, ++ "j71t: allow PCI ANS probe (boot initramfs releases after USB logging is ready)"); ++ ++bool nvme_pci_apple_ans_probe_allowed(struct pci_dev *pdev) ++{ ++ return !of_machine_is_compatible("apple,j71t") || ++ READ_ONCE(apple_ans_probe) || !nvme_pci_apple_ans_match(pdev); ++} ++ ++static unsigned int apple_ans_enable = 1; ++module_param(apple_ans_enable, uint, 0644); ++MODULE_PARM_DESC(apple_ans_enable, ++ "Apple ANS: 1 = enable the controller, 0 = stop right before CC.EN (dry run)"); ++ ++static unsigned int apple_ans_hold; ++module_param(apple_ans_hold, uint, 0644); ++MODULE_PARM_DESC(apple_ans_hold, ++ "Apple ANS: wait up to this many seconds before CC.EN for 'echo 1 > /sys/module/nvme/parameters/apple_ans_go' (0 = no wait); on timeout the controller stays disabled"); ++ ++static unsigned int apple_ans_slow_ms; ++module_param(apple_ans_slow_ms, uint, 0644); ++MODULE_PARM_DESC(apple_ans_slow_ms, ++ "Apple ANS: sleep this long after each bring-up message, so it reaches the (deferred) framebuffer console before the next step"); ++ ++/* A j71t diagnostic boot must reach rescue before the 120 s boot deadline. */ ++static unsigned int apple_ans_rdy_timeout = 5; ++module_param(apple_ans_rdy_timeout, uint, 0444); ++MODULE_PARM_DESC(apple_ans_rdy_timeout, ++ "j71t: RDY transition deadline in seconds (clamped to 1..30)"); ++ ++static unsigned int apple_ans_watch_ms = 100; ++module_param(apple_ans_watch_ms, uint, 0644); ++MODULE_PARM_DESC(apple_ans_watch_ms, ++ "Apple ANS: after CC.EN, watch only SoC-side state (link, NVMMU, DART) this long before the first CSTS read"); ++ ++static DECLARE_COMPLETION(apple_ans_go_done); ++ ++static int apple_ans_go_set(const char *val, const struct kernel_param *kp) ++{ ++ bool go; ++ int ret = kstrtobool(val, &go); ++ ++ if (ret) ++ return ret; ++ if (go) ++ complete_all(&apple_ans_go_done); ++ return 0; ++} ++ ++static int apple_ans_go_get(char *buf, const struct kernel_param *kp) ++{ ++ return sysfs_emit(buf, "%c\n", ++ completion_done(&apple_ans_go_done) ? 'Y' : 'N'); ++} ++ ++static const struct kernel_param_ops apple_ans_go_ops = { ++ .set = apple_ans_go_set, ++ .get = apple_ans_go_get, ++}; ++module_param_cb(apple_ans_go, &apple_ans_go_ops, NULL, 0644); ++MODULE_PARM_DESC(apple_ans_go, "Apple ANS: write 1 to release apple_ans_hold"); ++ ++static bool apple_ans_debug; ++module_param(apple_ans_debug, bool, 0644); ++MODULE_PARM_DESC(apple_ans_debug, ++ "Apple ANS: log bring-up diagnostics (MSI setup, SoC-side host/NVMMU/DART dumps, DART table walks for the admin queues, SoC-side changes after CC.EN). Fault diagnostics are always logged."); ++ ++/* Bring-up detail: dynamic debug, or always with nvme.apple_ans_debug=1 */ ++#define ans_dbg(dev, fmt, ...) \ ++ do { \ ++ if (apple_ans_debug) \ ++ dev_info(dev, fmt, ##__VA_ARGS__); \ ++ else \ ++ dev_dbg(dev, fmt, ##__VA_ARGS__); \ ++ } while (0) ++ ++static void apple_ans_settle(void) ++{ ++ if (apple_ans_slow_ms) ++ msleep(apple_ans_slow_ms); ++} ++ ++struct apple_ans_nvme { ++ dma_addr_t hmb_dma; ++ u32 hmb_size; ++ void *hmb_buf; /* DART-mapped HMB, NULL for the loader's */ ++ struct apple_ans_nvme_req req[APPLE_ANS_NVMMU_MAX_REQS]; ++ DECLARE_BITMAP(used_req, APPLE_ANS_NVMMU_MAX_REQS); ++ unsigned int last_req; ++ /* Protects the FlatDMA request-slot bitmap. */ ++ spinlock_t req_lock; ++ /* SoC-side state at CC.EN, for spotting faults while polling CSTS */ ++ struct apple_ans_host_state cc_base; ++ bool cc_base_valid; ++ bool host_fault; /* stop touching the controller */ ++ bool dart_fault_logged; ++ ktime_t cc_written_at; ++ u32 last_cc; ++ bool cc_written; ++}; ++ ++static struct apple_ans_nvme *nvme_pci_apple_ans(struct nvme_dev *dev) ++{ ++ return dev->dma_data; ++} ++ ++static struct device_node *nvme_pci_apple_ans_host_np(struct pci_dev *pdev) ++{ ++ struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); ++ ++ if (!bridge) ++ return NULL; ++ if (bridge->dev.parent && bridge->dev.parent->of_node) ++ return bridge->dev.parent->of_node; ++ return bridge->dev.of_node; ++} ++ ++static const struct of_device_id nvme_pci_apple_ans_hosts[] = { ++ { .compatible = "apple,t8010-pcie", ++ .data = (void *)APPLE_ANS_L1SS_CTL1_T8010 }, ++ { .compatible = "apple,s8000-pcie", ++ .data = (void *)APPLE_ANS_L1SS_CTL1_S8000 }, ++ { .compatible = "apple,s8003-pcie", ++ .data = (void *)APPLE_ANS_L1SS_CTL1_S8000 }, ++ { } ++}; ++ ++/* ++ * The S3E behind an H8/H9 PCIe root complex needs the ANS glue. A10 reports ++ * 106b:2002; the A9 ID is unverified, so match on the host bridge instead. ++ */ ++bool nvme_pci_apple_ans_match(struct pci_dev *pdev) ++{ ++ struct device_node *np; ++ ++ if (pdev->vendor != PCI_VENDOR_ID_APPLE) ++ return false; ++ ++ np = nvme_pci_apple_ans_host_np(pdev); ++ return np && of_match_node(nvme_pci_apple_ans_hosts, np); ++} ++ ++/* ++ * System-namespace guard, on top of (and independent of) the ++ * CONFIG_NVME_PCI_READ_ONLY allowlist. Called for every command at ++ * submission and again when it is copied into the SQ (under the SQ lock). ++ */ ++static bool nvme_pci_apple_ans_cmd_allowed(struct nvme_dev *dev, u16 qid, ++ const struct nvme_command *cmd) ++{ ++ u32 nsid = le32_to_cpu(cmd->common.nsid); ++ u8 opcode = cmd->common.opcode; ++ bool ok; ++ ++ if (apple_ans_unlock_system_namespaces) ++ return true; ++ ++ if (qid) { ++ /* I/O: anything on NSID 1, only reads elsewhere */ ++ ok = nsid == APPLE_ANS_DATA_NSID || opcode == nvme_cmd_read || ++ opcode == nvme_cmd_flush; ++ } else { ++ switch (opcode) { ++ case nvme_admin_delete_sq: ++ case nvme_admin_create_sq: ++ case nvme_admin_get_log_page: ++ case nvme_admin_delete_cq: ++ case nvme_admin_create_cq: ++ case nvme_admin_identify: ++ case nvme_admin_abort_cmd: ++ case nvme_admin_get_features: ++ case nvme_admin_async_event: ++ case nvme_admin_keep_alive: ++ ok = true; ++ break; ++ case nvme_admin_set_features: ++ /* controller-wide or the data namespace only */ ++ ok = nsid == 0 || nsid == APPLE_ANS_DATA_NSID; ++ break; ++ case nvme_admin_format_nvm: ++ ok = nsid == APPLE_ANS_DATA_NSID; ++ break; ++ default: ++ /* ++ * Sanitize, firmware download/commit, namespace ++ * management/attachment, security, directives, ++ * self-test and vendor-specific (Apple NAND tunnel) ++ * commands can reach every namespace. ++ */ ++ ok = false; ++ break; ++ } ++ } ++ ++ if (!ok) ++ dev_err_ratelimited(dev->dev, ++ "Apple ANS: refused opcode %#x for nsid %u on qid %u: system namespaces are protected\n", ++ opcode, nsid, qid); ++ return ok; ++} ++ ++static bool nvme_pci_apple_ans_ns_readonly(struct nvme_dev *dev, u32 nsid) ++{ ++ return !apple_ans_unlock_system_namespaces && ++ nsid != APPLE_ANS_DATA_NSID; ++} ++ ++static int nvme_pci_apple_ans_init(struct nvme_dev *dev, int node) ++{ ++ struct apple_ans_nvme *ans; ++ ++ ans = kzalloc_node(sizeof(*ans), GFP_KERNEL, node); ++ if (!ans) ++ return -ENOMEM; ++ ++ spin_lock_init(&ans->req_lock); ++ dev->dma_data = ans; ++ ++ if (apple_ans_unlock_system_namespaces) ++ dev_warn(dev->dev, ++ "Apple ANS: system namespaces UNLOCKED (nvme.apple_ans_unlock_system_namespaces=1)\n"); ++ else ++ dev_info(dev->dev, ++ "Apple ANS: namespaces other than NSID %u are read-only and only safe admin commands are sent\n", ++ APPLE_ANS_DATA_NSID); ++ return 0; ++} ++ ++static void nvme_pci_apple_ans_exit(struct nvme_dev *dev) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ ++ /* The controller is disabled by now, so it can't touch the HMB */ ++ if (ans && ans->hmb_buf) ++ dma_free_coherent(dev->dev, ans->hmb_size, ans->hmb_buf, ++ ans->hmb_dma); ++ kfree(dev->dma_data); ++ dev->dma_data = NULL; ++} ++ ++static int nvme_pci_apple_ans_find_hmb(struct nvme_dev *dev, u32 size_req, ++ u32 align_req) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ struct pci_dev *pdev = to_pci_dev(dev->dev); ++ struct pci_host_bridge *bridge; ++ struct device_node *pcie_np; ++ struct device_node *mem_np; ++ struct resource res; ++ resource_size_t size; ++ u32 iova; ++ int ret; ++ ++ bridge = pci_find_host_bridge(pdev->bus); ++ if (!bridge) ++ return -ENODEV; ++ ++ pcie_np = bridge->dev.parent ? bridge->dev.parent->of_node : NULL; ++ if (!pcie_np) ++ pcie_np = bridge->dev.of_node; ++ if (!pcie_np) ++ return -ENODEV; ++ ++ mem_np = of_parse_phandle(pcie_np, "apple,nvme-hmb", 0); ++ if (!mem_np) ++ return -ENODEV; ++ ++ ret = of_address_to_resource(mem_np, 0, &res); ++ if (ret) ++ goto out_put_mem; ++ if (!res.start || !resource_size(&res)) { ++ ret = -ENODEV; ++ goto out_put_mem; ++ } ++ ++ ret = of_property_read_u32(pcie_np, "apple,nvmmu-iova", &iova); ++ if (ret) ++ goto out_put_mem; ++ ++ if (!size_req || size_req == U32_MAX) { ++ ret = -EINVAL; ++ goto out_put_mem; ++ } ++ if (!align_req || align_req == U32_MAX) ++ align_req = 1; ++ ++ size = resource_size(&res); ++ if (size < size_req || size > U32_MAX) { ++ ret = -ENOSPC; ++ goto out_put_mem; ++ } ++ if (!IS_ALIGNED(res.start, align_req) || !IS_ALIGNED(iova, align_req)) { ++ ret = -EINVAL; ++ goto out_put_mem; ++ } ++ ++ ans->hmb_dma = iova; ++ ans->hmb_size = size; ++ dev_info(dev->dev, "Apple ANS: HMB %#x@%pa as DMA %#llx\n", ++ ans->hmb_size, &res.start, (u64)ans->hmb_dma); ++ ++out_put_mem: ++ of_node_put(mem_np); ++ return ret; ++} ++ ++/* ++ * iOS (IONVMeFamily) takes the HMB ("scratch buffer") from ++ * nvme-scratch-virt-region, else nvme-scratch-region, else allocates a ++ * buffer of the requested size and maps it through the DART. This is the ++ * last case: the buffer lives in the DART window, the NVMMU SART isn't used. ++ */ ++static int nvme_pci_apple_ans_alloc_dart_hmb(struct nvme_dev *dev, ++ u32 size_req, u32 align_req) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ dma_addr_t dma; ++ void *buf; ++ ++ if (!size_req || size_req == U32_MAX) ++ return -EINVAL; ++ if (!align_req || align_req == U32_MAX) ++ align_req = 1; ++ if (!is_power_of_2(align_req)) ++ return -EINVAL; ++ ++ buf = dma_alloc_coherent(dev->dev, size_req, &dma, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ if (!IS_ALIGNED(dma, align_req)) { ++ dma_free_coherent(dev->dev, size_req, buf, dma); ++ return -EINVAL; ++ } ++ ++ ans->hmb_buf = buf; ++ ans->hmb_dma = dma; ++ ans->hmb_size = size_req; ++ dev_info(dev->dev, "Apple ANS: HMB %#x at DART DMA %pad\n", ++ ans->hmb_size, &ans->hmb_dma); ++ return 0; ++} ++ ++static int nvme_pci_apple_ans_reset_dma(struct nvme_dev *dev) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ unsigned long flags; ++ int ret; ++ ++ ret = apple_ans_nvmmu_reset(dev->dev); ++ if (ret) ++ return ret; ++ ++ /* The hardware init invalidated every tag; do not flood the FIFO. */ ++ spin_lock_irqsave(&ans->req_lock, flags); ++ bitmap_zero(ans->used_req, APPLE_ANS_NVMMU_MAX_REQS); ++ ans->last_req = 0; ++ spin_unlock_irqrestore(&ans->req_lock, flags); ++ return 0; ++} ++ ++static void nvme_pci_apple_ans_config(struct nvme_dev *dev) ++{ ++ struct pci_dev *pdev = to_pci_dev(dev->dev); ++ const struct of_device_id *match; ++ u16 ltr, l1ss; ++ u32 l1ss_ctl1; ++ ++ match = of_match_node(nvme_pci_apple_ans_hosts, ++ nvme_pci_apple_ans_host_np(pdev)); ++ l1ss_ctl1 = match ? (u32)(uintptr_t)match->data : ++ APPLE_ANS_L1SS_CTL1_T8010; ++ ++ pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0x40); ++ ++ ltr = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_LTR); ++ l1ss = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS); ++ ans_dbg(dev->dev, "Apple ANS: LTR cap at %#x, L1SS cap at %#x\n", ++ ltr, l1ss); ++ ++ if (ltr) ++ pci_write_config_dword(pdev, ltr + PCI_LTR_MAX_SNOOP_LAT, ++ APPLE_ANS_LTR_MAX_LATENCY); ++ if (l1ss) { ++ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL2, 0); ++ pci_write_config_dword(pdev, l1ss + PCI_L1SS_CTL1, l1ss_ctl1); ++ } ++} ++ ++/* ++ * A BAR access over a dead link can stall the CPU for good on the H8P/H9P ++ * root complex, so check the link before each step that touches the ++ * controller. The phase messages say where a hang happened. ++ */ ++static int nvme_pci_apple_ans_check_link(struct nvme_dev *dev, ++ const char *step) ++{ ++ int up = apple_ans_link_up(dev->dev); ++ ++ if (up > 0) ++ return 0; ++ dev_err(dev->dev, "Apple ANS: PCIe link down before %s, leaving the controller alone\n", ++ step); ++ return up < 0 ? up : -ENOLINK; ++} ++ ++static int nvme_pci_apple_ans_preinit(struct nvme_dev *dev) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ u32 csts, core_mask, log_size; ++ u32 hmb_size, hmb_align, bfh; ++ int ret; ++ ++ if (!ans) ++ return 0; ++ ++ ret = nvme_pci_apple_ans_check_link(dev, "preinit"); ++ if (ret) ++ return ret; ++ core_mask = readl(dev->bar + APPLE_ANS_REG_CORE_MASK); ++ log_size = readl(dev->bar + APPLE_ANS_REG_LOG_SIZE); ++ hmb_size = readl(dev->bar + APPLE_ANS_REG_HMB_SIZE_REQ); ++ hmb_align = readl(dev->bar + APPLE_ANS_REG_HMB_ALIGN_REQ); ++ ans_dbg(dev->dev, ++ "Apple ANS: core_mask=%#x log_size=%#x hmb_req=%#x align=%#x\n", ++ core_mask, log_size, hmb_size, hmb_align); ++ bfh = readl(dev->bar + APPLE_ANS_REG_BFH); ++ dev_info(dev->dev, "Apple ANS: CAP=%#llx VS=%#x CC=%#x CSTS=%#x BFH=%#x\n", ++ lo_hi_readq(dev->bar + NVME_REG_CAP), ++ readl(dev->bar + NVME_REG_VS), readl(dev->bar + NVME_REG_CC), ++ readl(dev->bar + NVME_REG_CSTS), bfh); ++ apple_ans_settle(); ++ if (bfh == APPLE_ANS_BFH_MAGIC) ++ return dev_err_probe(dev->dev, -ENODEV, ++ "Apple ANS: controller waits for a boot-from-host firmware upload, not supported\n"); ++ ++ /* ++ * Check everything this needs from the loader and the host bridge ++ * before writing anything to the controller. ++ */ ++ if (!ans->hmb_size) { ++ ret = nvme_pci_apple_ans_find_hmb(dev, hmb_size, hmb_align); ++ if (ret && apple_ans_dart_hmb) ++ ret = nvme_pci_apple_ans_alloc_dart_hmb(dev, hmb_size, ++ hmb_align); ++ if (ret) ++ return dev_err_probe(dev->dev, ret, ++ "Apple ANS: no usable HMB (needs a loader that fills /reserved-memory/nvme-hmb, or nvme.apple_ans_dart_hmb=1)\n"); ++ } ++ ++ ret = nvme_pci_apple_ans_check_link(dev, "PCI config setup"); ++ if (ret) ++ return ret; ++ nvme_pci_apple_ans_config(dev); ++ ++ ret = nvme_pci_apple_ans_check_link(dev, "CC=0"); ++ if (ret) ++ return ret; ++ ans_dbg(dev->dev, "Apple ANS: stopping controller (CC=0)\n"); ++ writel(0, dev->bar + NVME_REG_CC); ++ ret = readl_poll_timeout(dev->bar + NVME_REG_CSTS, csts, ++ !(csts & (NVME_CSTS_RDY | NVME_CSTS_CFS)), ++ 1000, 2000000); ++ if (ret) { ++ ans->host_fault = true; ++ apple_ans_host_dump(dev->dev, "controller stop failed"); ++ return dev_err_probe(dev->dev, ret, ++ "Apple ANS: controller did not stop (CSTS=%#x)\n", ++ csts); ++ } ++ dev_info(dev->dev, "Apple ANS: controller stopped (CSTS=%#x), rebuilding DMA\n", csts); ++ if (apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) { ++ ret = apple_ans_dart_set_active(dev->dev, true); ++ if (ret) ++ return ret; ++ } ++ ret = nvme_pci_apple_ans_reset_dma(dev); ++ if (ret) ++ return dev_err_probe(dev->dev, ret, ++ "Apple ANS: failed to rebuild stopped NVMMU\n"); ++ ans->cc_base_valid = false; ++ ans->cc_written = false; ++ apple_ans_settle(); ++ ++ return 0; ++} ++ ++/* The S3E's MSI/MSI-X setup as the controller will use it */ ++void nvme_pci_apple_ans_log_msi(struct nvme_dev *dev) ++{ ++ struct pci_dev *pdev = to_pci_dev(dev->dev); ++ u16 flags = 0, data = 0, xflags = 0; ++ u32 lo = 0, hi = 0; ++ int msi, msix; ++ ++ msi = pdev->msi_cap; ++ if (msi) { ++ pci_read_config_word(pdev, msi + PCI_MSI_FLAGS, &flags); ++ pci_read_config_dword(pdev, msi + PCI_MSI_ADDRESS_LO, &lo); ++ if (flags & PCI_MSI_FLAGS_64BIT) { ++ pci_read_config_dword(pdev, msi + PCI_MSI_ADDRESS_HI, &hi); ++ pci_read_config_word(pdev, msi + PCI_MSI_DATA_64, &data); ++ } else { ++ pci_read_config_word(pdev, msi + PCI_MSI_DATA_32, &data); ++ } ++ } ++ msix = pdev->msix_cap; ++ if (msix) ++ pci_read_config_word(pdev, msix + PCI_MSIX_FLAGS, &xflags); ++ dev_info(dev->dev, ++ "Apple ANS: MSI cap %#x flags %#x addr %#x:%08x data %#x; MSI-X cap %#x flags %#x; irq %d\n", ++ msi, flags, hi, lo, data, msix, xflags, pci_irq_vector(pdev, 0)); ++} ++ ++/* Linux's own mapping of a queue IOVA, then the DART table walk for it */ ++static void nvme_pci_apple_ans_log_queue(struct nvme_dev *dev, const char *what, ++ dma_addr_t iova) ++{ ++ struct iommu_domain *dom = iommu_get_domain_for_dev(dev->dev); ++ phys_addr_t pa = dom ? iommu_iova_to_phys(dom, iova) : 0; ++ ++ ans_dbg(dev->dev, "Apple ANS: %s IOVA %pad -> PA %pa (Linux view)\n", ++ what, &iova, &pa); ++ apple_ans_dart_walk(dev->dev, what, iova); ++} ++ ++/* ++ * Stop short or wait for userspace before CC.EN. Runs in reset_work with ++ * shutdown_lock held, so a reboot during the hold waits for it to end. ++ */ ++static int nvme_pci_apple_ans_gate(struct nvme_dev *dev) ++{ ++ long left; ++ ++ if (!apple_ans_enable) { ++ dev_warn(dev->dev, ++ "Apple ANS: dry run (nvme.apple_ans_enable=0): everything is programmed, CC.EN is not set\n"); ++ return -ENODEV; ++ } ++ if (!apple_ans_hold || completion_done(&apple_ans_go_done)) ++ return 0; ++ ++ dev_warn(dev->dev, ++ "Apple ANS: holding before CC.EN for up to %us: echo 1 > /sys/module/nvme/parameters/apple_ans_go\n", ++ apple_ans_hold); ++ left = wait_for_completion_interruptible_timeout(&apple_ans_go_done, ++ apple_ans_hold * HZ); ++ if (left <= 0) { ++ dev_warn(dev->dev, ++ "Apple ANS: hold ended without go (%ld), controller left disabled\n", ++ left); ++ return left ? left : -ETIMEDOUT; ++ } ++ dev_info(dev->dev, "Apple ANS: go\n"); ++ apple_ans_settle(); ++ return 0; ++} ++ ++static int nvme_pci_apple_ans_prepare_enable(struct nvme_dev *dev) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ int ret; ++ ++ if (!ans) ++ return 0; ++ if (!ans->hmb_size) ++ return -EINVAL; ++ if (nvme_pci_apple_ans_check_link(dev, "HMB setup")) ++ return -ENOLINK; ++ ++ writel(APPLE_ANS_REG_INIT_REGULAR, dev->bar + APPLE_ANS_REG_INIT); ++ writel(lower_32_bits(ans->hmb_dma), ++ dev->bar + APPLE_ANS_REG_HMB_BASE_LO); ++ writel(upper_32_bits(ans->hmb_dma), ++ dev->bar + APPLE_ANS_REG_HMB_BASE_HI); ++ writel(ans->hmb_size, dev->bar + APPLE_ANS_REG_HMB_SIZE); ++ readl(dev->bar + APPLE_ANS_REG_HMB_SIZE); ++ ++ ans_dbg(dev->dev, ++ "Apple ANS: HMB DMA %#llx size %#x programmed; AQA %#x ASQ %#llx ACQ %#llx\n", ++ (u64)ans->hmb_dma, ans->hmb_size, ++ readl(dev->bar + NVME_REG_AQA), ++ lo_hi_readq(dev->bar + NVME_REG_ASQ), ++ lo_hi_readq(dev->bar + NVME_REG_ACQ)); ++ if (apple_ans_debug) { ++ nvme_pci_apple_ans_log_msi(dev); ++ apple_ans_host_dump(dev->dev, "before CC.EN"); ++ nvme_pci_apple_ans_log_queue(dev, "ASQ", ++ dev->queues[0].sq_dma_addr); ++ nvme_pci_apple_ans_log_queue(dev, "ACQ", ++ dev->queues[0].cq_dma_addr); ++ } ++ apple_ans_settle(); ++ ++ ret = nvme_pci_apple_ans_gate(dev); ++ if (ret) ++ return ret; ++ if (nvme_pci_apple_ans_check_link(dev, "CC.EN")) ++ return -ENOLINK; ++ ++ ans->host_fault = false; ++ ans->dart_fault_logged = false; ++ dev_info(dev->dev, ++ "Apple ANS: enabling controller (RDY timeout %llus, SoC-side watch %ums)\n", ++ (NVME_CAP_TIMEOUT(dev->ctrl.cap) + 1) / 2, apple_ans_watch_ms); ++ apple_ans_settle(); ++ return 0; ++} ++ ++/* ++ * An NVMMU error, or a link that stays down. The link can drop for a ++ * moment while it retrains, so re-sample for ~3 ms (@st is updated) before ++ * calling that a fault; a CSTS read over a dead link can stall the CPU. ++ */ ++static bool nvme_pci_apple_ans_new_fault(struct nvme_dev *dev, ++ struct apple_ans_host_state *st) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ int i; ++ ++ if (st->nvmmu_err && st->nvmmu_err != ans->cc_base.nvmmu_err) ++ return true; ++ for (i = 0; i < 10 && !st->link_up; i++) { ++ usleep_range(200, 400); ++ apple_ans_host_state(dev->dev, st); ++ } ++ return !st->link_up; ++} ++ ++static bool nvme_pci_apple_ans_new_dart_fault(const struct apple_ans_host_state *st) ++{ ++ /* Without valid, ERROR/ADDR are a live transaction record. In ++ * particular 0x500 only records write direction, even if ADDR changed. ++ * An IRQ-acked fault cannot be reconstructed from the address alone. ++ */ ++ return st->dart_err & BIT(31); ++} ++ ++/* ++ * CC writes. For CC.EN, write it, then watch only SoC-side state for a ++ * while: a stuck inbound path shows up there without a non-posted CSTS ++ * read that could stall the CPU. A dead link or an NVMMU fault (iOS ++ * panics on those) ends the bring-up before the controller is read. ++ */ ++static int nvme_pci_apple_ans_write_cc(struct nvme_dev *dev, u32 cc) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ struct apple_ans_host_state prev, st; ++ ktime_t start; ++ s64 us; ++ ++ if (ans) { ++ if (ans->host_fault || ++ nvme_pci_apple_ans_check_link(dev, "CC write")) ++ return -EIO; ++ ans->last_cc = cc; ++ ans->cc_written_at = ktime_get(); ++ ans->cc_written = true; ++ } ++ if (!ans || !(cc & NVME_CC_ENABLE) || (cc & NVME_CC_SHN_MASK)) { ++ writel(cc, dev->bar + NVME_REG_CC); ++ return 0; ++ } ++ ++ ans->cc_base_valid = !apple_ans_host_state(dev->dev, &ans->cc_base); ++ writel(cc, dev->bar + NVME_REG_CC); ++ start = ktime_get(); ++ if (!ans->cc_base_valid) ++ return 0; ++ ++ prev = ans->cc_base; ++ do { ++ usleep_range(200, 400); ++ apple_ans_host_state(dev->dev, &st); ++ us = ktime_us_delta(ktime_get(), start); ++ if (memcmp(&st, &prev, sizeof(st))) { ++ ans_dbg(dev->dev, ++ "Apple ANS: CC.EN+%lldus: link %u ltssm %#x port irq %#x nvmmu err %#x dart err %#x addr %#x\n", ++ us, st.link_up, st.ltssm, st.port_irq, ++ st.nvmmu_err, st.dart_err, st.dart_addr); ++ prev = st; ++ } ++ if (nvme_pci_apple_ans_new_fault(dev, &st)) { ++ ans->host_fault = true; ++ dev_err(dev->dev, ++ "Apple ANS: host-side fault %lldus after CC.EN (link %u nvmmu err %#x), giving up on the controller\n", ++ us, st.link_up, st.nvmmu_err); ++ apple_ans_host_dump(dev->dev, "after fault"); ++ return -EIO; ++ } ++ } while (us < apple_ans_watch_ms * USEC_PER_MSEC); ++ ++ dev_info(dev->dev, ++ "Apple ANS: %ums after CC.EN, no host-side fault; polling CSTS\n", ++ apple_ans_watch_ms); ++ if (apple_ans_debug) ++ apple_ans_host_dump(dev->dev, "after watch"); ++ apple_ans_settle(); ++ return 0; ++} ++ ++/* CSTS reads: only while the SoC side still looks sane */ ++static int nvme_pci_apple_ans_read_csts(struct nvme_dev *dev, u32 *csts) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ struct apple_ans_host_state st; ++ ++ if (ans && (ans->host_fault || ++ nvme_pci_apple_ans_check_link(dev, "CSTS read"))) ++ return -EIO; ++ if (ans && ans->cc_base_valid) { ++ if (ans->host_fault) ++ return -EIO; ++ apple_ans_host_state(dev->dev, &st); ++ if (nvme_pci_apple_ans_new_fault(dev, &st)) { ++ ans->host_fault = true; ++ dev_err(dev->dev, ++ "Apple ANS: host-side fault (link %u nvmmu err %#x) while polling CSTS, giving up on the controller\n", ++ st.link_up, st.nvmmu_err); ++ apple_ans_host_dump(dev->dev, "after fault"); ++ return -EIO; ++ } ++ if (!ans->dart_fault_logged && ++ nvme_pci_apple_ans_new_dart_fault(&st)) { ++ ans->dart_fault_logged = true; ++ dev_warn(dev->dev, ++ "Apple ANS: DART fault since CC.EN (err %#x addr %#x), still polling\n", ++ st.dart_err, st.dart_addr); ++ } ++ } ++ ++ *csts = readl(dev->bar + NVME_REG_CSTS); ++ if (ans && ans->cc_written && of_machine_is_compatible("apple,j71t") && ++ !(ans->last_cc & NVME_CC_SHN_MASK) && ++ !!(*csts & NVME_CSTS_RDY) != !!(ans->last_cc & NVME_CC_ENABLE) && ++ ktime_ms_delta(ktime_get(), ans->cc_written_at) >= ++ clamp_val(apple_ans_rdy_timeout, 1, 30) * MSEC_PER_SEC) { ++ dev_err(dev->dev, "Apple ANS: RDY deadline CC %#x CSTS %#x; stopping endpoint access\n", ++ ans->last_cc, *csts); ++ ans->host_fault = true; ++ apple_ans_host_dump(dev->dev, "RDY deadline"); ++ return -ETIMEDOUT; ++ } ++ return 0; ++} ++ ++/* A9 is a PCI S3E endpoint, not the later platform/RTKit ANS controller. ++ * Finish the standard controller reset while its old DART/NVMMU mappings ++ * still exist. SHN alone leaves EN set and is insufficient for kexec. ++ */ ++static void nvme_pci_apple_ans_shutdown_round7(struct nvme_dev *dev, int shutdown_ret) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ struct pci_dev *pdev = to_pci_dev(dev->dev); ++ u32 csts = ~0U; ++ int ret, err = 0; ++ ++ dev_info(dev->dev, "Apple ANS: kexec shutdown: SHN result %d, clearing CC.EN\n", shutdown_ret); ++ if (ans->host_fault || nvme_pci_apple_ans_check_link(dev, "kexec CC=0")) ++ goto block_dma; ++ dev->ctrl.ctrl_config &= ~(NVME_CC_ENABLE | NVME_CC_SHN_MASK); ++ writel(dev->ctrl.ctrl_config, dev->bar + NVME_REG_CC); ++ ret = read_poll_timeout(nvme_pci_apple_ans_read_csts, err, ++ err || !(csts & NVME_CSTS_RDY), 1000, ++ 2000000, false, dev, &csts); ++ if (!ret) ++ ret = err; ++ if (ret) ++ dev_warn(dev->dev, "Apple ANS: CC=0 failed (%d, CSTS %#x); host PERST will isolate the endpoint\n", ++ ret, csts); ++ else ++ dev_info(dev->dev, "Apple ANS: kexec shutdown: RDY=0 (CSTS %#x)\n", csts); ++block_dma: ++ /* PCI core does this too; make the ordering explicit before host teardown. */ ++ pci_clear_master(pdev); ++} ++ ++static int nvme_pci_apple_ans_stop(struct nvme_dev *dev, int shutdown_ret, ++ bool fatal) ++{ ++ enum apple_ans_boot_mode mode = apple_ans_boot_mode(dev->dev); ++ u32 csts = ~0U, cc; ++ int ret, err = 0; ++ ++ if (fatal && mode == APPLE_ANS_ROUND7) { ++ nvme_pci_apple_ans_shutdown_round7(dev, shutdown_ret); ++ return 0; ++ } ++ ret = nvme_pci_apple_ans_read_csts(dev, &csts); ++ if (shutdown_ret || ret || (csts & NVME_CSTS_CFS) || ++ (csts & NVME_CSTS_SHST_MASK) != NVME_CSTS_SHST_CMPLT) { ++ pci_clear_master(to_pci_dev(dev->dev)); ++ apple_ans_host_dump(dev->dev, "SHST failed; refusing kexec"); ++ if (fatal) ++ panic("Apple ANS: SHN failed %d/%d CSTS %#x; refusing unsafe handoff", ++ shutdown_ret, ret, csts); ++ dev_err(dev->dev, "Apple ANS: sleep SHN failed %d/%d CSTS %#x\n", ++ shutdown_ret, ret, csts); ++ return -EBUSY; ++ } ++ ++ /* SHST completion is not a controller reset: EN/RDY can both stay 1. ++ * The incoming full port sequence removes the link/refclk. Finish the ++ * reset while the old link and DMA translations are still available, ++ * before either mode is allowed to touch PERST or port power. ++ */ ++ dev_info(dev->dev, "Apple ANS: STEP shutdown SHST complete; clearing CC.EN/SHN\n"); ++ dev->ctrl.ctrl_config &= ~(NVME_CC_ENABLE | NVME_CC_SHN_MASK); ++ ret = nvme_pci_apple_ans_write_cc(dev, dev->ctrl.ctrl_config); ++ if (!ret) { ++ dev_info(dev->dev, "Apple ANS: STEP shutdown wait-RDY=0 timeout_us=2000000\n"); ++ ret = read_poll_timeout(nvme_pci_apple_ans_read_csts, err, ++ err || !(csts & NVME_CSTS_RDY), 1000, ++ 2000000, false, dev, &csts); ++ if (!ret) ++ ret = err; ++ } ++ if (!ret && (csts & NVME_CSTS_CFS)) ++ ret = -EIO; ++ if (!ret) ++ ret = nvme_pci_apple_ans_check_link(dev, "kexec CC readback"); ++ if (!ret) { ++ cc = readl(dev->bar + NVME_REG_CC); ++ if (cc & (NVME_CC_ENABLE | NVME_CC_SHN_MASK)) ++ ret = -EIO; ++ if (!fatal && (cc != dev->ctrl.ctrl_config || csts)) ++ ret = -EIO; ++ } ++ /* No endpoint MMIO after BME is blocked and translations can retire. */ ++ pci_clear_master(to_pci_dev(dev->dev)); ++ if (ret) { ++ apple_ans_host_dump(dev->dev, "CC disable failed; refusing kexec"); ++ if (fatal) ++ panic("Apple ANS: CC disable failed %d CSTS %#x; refusing unsafe port reset", ++ ret, csts); ++ dev_err(dev->dev, "Apple ANS: sleep CC disable failed %d CSTS %#x\n", ret, csts); ++ return -EBUSY; ++ } ++ dev_info(dev->dev, "Apple ANS: STEP shutdown disabled CC=%#x CSTS=%#x BME=0 mode=%u\n", ++ cc, csts, mode); ++ /* Both trial modes hand off a fully down port for cold bring-up. */ ++ ret = apple_ans_dart_set_active(dev->dev, false); ++ if (ret) { ++ if (fatal) ++ panic("Apple ANS: DART inactive failed %d; refusing kexec", ret); ++ dev_err(dev->dev, "Apple ANS: sleep DART inactive failed %d\n", ret); ++ return -EBUSY; ++ } ++ return 0; ++} ++ ++static void nvme_pci_apple_ans_shutdown(struct nvme_dev *dev, int shutdown_ret) ++{ ++ nvme_pci_apple_ans_stop(dev, shutdown_ret, true); ++} ++ ++static int nvme_pci_apple_ans_stop_for_sleep(struct nvme_dev *dev, int shutdown_ret) ++{ ++ int ret; ++ ++ ret = nvme_pci_apple_ans_stop(dev, shutdown_ret, false); ++ if (!ret) ++ ret = apple_ans_set_sleep_stopped(dev->dev, true); ++ return ret; ++} ++ ++static blk_status_t nvme_pci_apple_ans_alloc_req(struct nvme_dev *dev, ++ struct apple_ans_nvme_req **req, ++ unsigned int *tag) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ unsigned long flags; ++ unsigned int idx; ++ ++ spin_lock_irqsave(&ans->req_lock, flags); ++ idx = find_next_zero_bit(ans->used_req, APPLE_ANS_NVMMU_MAX_REQS, ++ (ans->last_req + 1) % ++ APPLE_ANS_NVMMU_MAX_REQS); ++ if (idx >= APPLE_ANS_NVMMU_MAX_REQS) ++ idx = find_first_zero_bit(ans->used_req, ++ APPLE_ANS_NVMMU_MAX_REQS); ++ if (idx >= APPLE_ANS_NVMMU_MAX_REQS) { ++ spin_unlock_irqrestore(&ans->req_lock, flags); ++ dev_dbg_ratelimited(dev->dev, ++ "Apple ANS NVMe FlatDMA slots exhausted\n"); ++ return BLK_STS_RESOURCE; ++ } ++ ++ ans->last_req = idx; ++ __set_bit(idx, ans->used_req); ++ spin_unlock_irqrestore(&ans->req_lock, flags); ++ ++ *req = &ans->req[idx]; ++ *tag = idx; ++ memset(*req, 0, sizeof(**req)); ++ return BLK_STS_OK; ++} ++ ++static void nvme_pci_apple_ans_free_req(struct nvme_dev *dev, ++ struct apple_ans_nvme_req *req) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ unsigned long flags; ++ unsigned int tag; ++ int ret; ++ ++ if (!ans || !req) ++ return; ++ ++ tag = req - ans->req; ++ if (tag >= APPLE_ANS_NVMMU_MAX_REQS) ++ return; ++ ++ /* Allocation/map failures never gave this slot to the NVMMU. */ ++ if (req->mapped) { ++ ret = apple_ans_nvmmu_map(dev->dev, tag, NULL, 0, false, NULL); ++ if (ret) { ++ /* This API returns -ETIMEDOUT only for an invalidate FIFO ++ * timeout, and -EIO only for its latched poison. Lookup, ++ * ownership and argument errors are not hardware failures. ++ * Keep the slot reserved, but never reset for those errors. ++ */ ++ bool failed = ret == -ETIMEDOUT || ret == -EIO; ++ ++ dev_warn_ratelimited(dev->dev, ++ "ANS DMA slot %u quarantined: %d qid %u cid %#x opcode %#x nsid %u mapped %u; %s\n", ++ tag, ret, req->qid, req->cid, req->opcode, ++ req->nsid, req->mapped, ++ failed ? "confirmed NVMMU failure; scheduling reset" : ++ "local retirement error; no reset"); ++ if (failed) ++ nvme_reset_ctrl(&dev->ctrl); ++ return; ++ } ++ req->mapped = false; ++ } ++ ++ spin_lock_irqsave(&ans->req_lock, flags); ++ __clear_bit(tag, ans->used_req); ++ spin_unlock_irqrestore(&ans->req_lock, flags); ++} ++ ++static blk_status_t nvme_pci_apple_ans_map_bvec(struct nvme_dev *dev, ++ struct request *req, ++ struct nvme_iod *iod, ++ struct apple_ans_nvme_req *areq, ++ const struct bio_vec *bv, ++ unsigned int total, ++ unsigned int *npages, ++ unsigned int *consumed, ++ u64 *offs) ++{ ++ dma_addr_t dma_addr; ++ u64 phys; ++ unsigned int len = bv->bv_len; ++ ++ if (!len) ++ return BLK_STS_OK; ++ ++ if (WARN_ON_ONCE(iod->nr_dma_vecs >= dev->ctrl.max_segments)) ++ return BLK_STS_IOERR; ++ ++ dma_addr = dma_map_bvec(dev->dev, bv, rq_dma_dir(req), 0); ++ if (dma_mapping_error(dev->dev, dma_addr)) ++ return BLK_STS_RESOURCE; ++ ++ iod->dma_vecs[iod->nr_dma_vecs].addr = dma_addr; ++ iod->dma_vecs[iod->nr_dma_vecs].len = len; ++ iod->nr_dma_vecs++; ++ ++ phys = page_to_phys(bv->bv_page) + bv->bv_offset; ++ if (!*consumed) { ++ *offs = phys & (APPLE_ANS_NVMMU_PAGE_SIZE - 1); ++ phys -= *offs; ++ len += *offs; ++ } else if (phys & (APPLE_ANS_NVMMU_PAGE_SIZE - 1)) { ++ dev_err_ratelimited(dev->dev, ++ "Apple ANS FlatDMA segment is not page-aligned: phys=%#llx\n", ++ phys); ++ return BLK_STS_IOERR; ++ } ++ ++ if (*consumed + bv->bv_len != total && ++ (len & (APPLE_ANS_NVMMU_PAGE_SIZE - 1))) { ++ dev_err_ratelimited(dev->dev, ++ "Apple ANS FlatDMA segment length is not page-aligned: len=%#x\n", ++ len); ++ return BLK_STS_IOERR; ++ } ++ ++ while (len) { ++ if (*npages >= APPLE_ANS_NVMMU_MAX_PAGES) ++ return BLK_STS_IOERR; ++ ++ areq->pages[(*npages)++] = phys; ++ phys += APPLE_ANS_NVMMU_PAGE_SIZE; ++ len = len > APPLE_ANS_NVMMU_PAGE_SIZE ? ++ len - APPLE_ANS_NVMMU_PAGE_SIZE : 0; ++ } ++ ++ *consumed += bv->bv_len; ++ return BLK_STS_OK; ++} ++ ++static bool nvme_pci_apple_ans_unmap_data(struct request *req) ++{ ++ struct nvme_iod *iod = blk_mq_rq_to_pdu(req); ++ struct nvme_queue *nvmeq = req->mq_hctx->driver_data; ++ struct nvme_dev *dev = nvmeq->dev; ++ unsigned int i; ++ ++ if (!iod->dma_private) ++ return false; ++ ++ for (i = 0; i < iod->nr_dma_vecs; i++) ++ dma_unmap_page(dev->dev, iod->dma_vecs[i].addr, ++ iod->dma_vecs[i].len, rq_dma_dir(req)); ++ if (iod->dma_vecs) { ++ mempool_free(iod->dma_vecs, dev->dmavec_mempool); ++ iod->dma_vecs = NULL; ++ } ++ iod->nr_dma_vecs = 0; ++ ++ nvme_pci_apple_ans_free_req(dev, iod->dma_private); ++ iod->dma_private = NULL; ++ iod->cmd.common.flags &= ~NVME_CMD_APPLE_ANS_FLATDMA; ++ return true; ++} ++ ++static blk_status_t nvme_pci_apple_ans_map_data(struct request *req) ++{ ++ struct nvme_iod *iod = blk_mq_rq_to_pdu(req); ++ struct nvme_queue *nvmeq = req->mq_hctx->driver_data; ++ struct nvme_dev *dev = nvmeq->dev; ++ struct apple_ans_nvme_req *areq; ++ struct req_iterator iter; ++ struct bio_vec bv; ++ dma_addr_t flatdma; ++ u64 offs = 0; ++ unsigned int tag, npages = 0, consumed = 0; ++ unsigned int total = blk_rq_payload_bytes(req); ++ blk_status_t status; ++ int ret; ++ ++ if (!nvme_pci_apple_ans(dev)) ++ return BLK_STS_NOTSUPP; ++ if (total > APPLE_ANS_NVMMU_MAX_PAGES * APPLE_ANS_NVMMU_PAGE_SIZE) ++ return BLK_STS_IOERR; ++ ++ status = nvme_pci_apple_ans_alloc_req(dev, &areq, &tag); ++ if (status) ++ return status; ++ ++ areq->qid = nvmeq->qid; ++ areq->cid = le16_to_cpu(iod->cmd.common.command_id); ++ areq->opcode = iod->cmd.common.opcode; ++ areq->nsid = le32_to_cpu(iod->cmd.common.nsid); ++ iod->dma_private = areq; ++ iod->dma_vecs = mempool_alloc(dev->dmavec_mempool, GFP_ATOMIC); ++ if (!iod->dma_vecs) { ++ status = BLK_STS_RESOURCE; ++ goto out_unmap; ++ } ++ ++ if (req->rq_flags & RQF_SPECIAL_PAYLOAD) { ++ bv = req_bvec(req); ++ status = nvme_pci_apple_ans_map_bvec(dev, req, iod, areq, &bv, ++ total, &npages, &consumed, ++ &offs); ++ if (status) ++ goto out_unmap; ++ } else { ++ rq_for_each_bvec(bv, req, iter) { ++ status = nvme_pci_apple_ans_map_bvec(dev, req, iod, ++ areq, &bv, total, ++ &npages, &consumed, ++ &offs); ++ if (status) ++ goto out_unmap; ++ } ++ } ++ ++ if (consumed != total) { ++ dev_err_ratelimited(dev->dev, ++ "Apple ANS FlatDMA payload length mismatch: consumed=%#x total=%#x\n", ++ consumed, total); ++ status = BLK_STS_IOERR; ++ goto out_unmap; ++ } ++ ++ ret = apple_ans_nvmmu_map(dev->dev, tag, areq->pages, npages, ++ rq_dma_dir(req) == DMA_FROM_DEVICE, &flatdma); ++ if (ret) { ++ status = errno_to_blk_status(ret); ++ if (status == BLK_STS_NOTSUPP) ++ status = BLK_STS_IOERR; ++ goto out_unmap; ++ } ++ ++ areq->mapped = true; ++ iod->total_len = total; ++ iod->cmd.common.flags |= NVME_CMD_APPLE_ANS_FLATDMA; ++ iod->cmd.common.dptr.prp1 = cpu_to_le64(flatdma + offs); ++ iod->cmd.common.dptr.prp2 = 0; ++ return BLK_STS_OK; ++ ++out_unmap: ++ nvme_pci_apple_ans_unmap_data(req); ++ return status; ++} ++ ++static u32 nvme_pci_apple_ans_queue_depth(struct nvme_dev *dev, u32 depth) ++{ ++ return min_t(u32, depth, APPLE_ANS_NVMMU_MAX_REQS); ++} ++ ++static u32 nvme_pci_apple_ans_max_hw_sectors(struct nvme_dev *dev, ++ u32 max_hw_sectors) ++{ ++ return min_t(u32, max_hw_sectors, APPLE_ANS_NVME_MAX_SECTORS); ++} ++ ++const struct nvme_pci_dma_ops nvme_pci_apple_ans_ops = { ++ .init = nvme_pci_apple_ans_init, ++ .exit = nvme_pci_apple_ans_exit, ++ .preinit = nvme_pci_apple_ans_preinit, ++ .prepare_enable = nvme_pci_apple_ans_prepare_enable, ++ .shutdown = nvme_pci_apple_ans_shutdown, ++ .stop_for_sleep = nvme_pci_apple_ans_stop_for_sleep, ++ .write_cc = nvme_pci_apple_ans_write_cc, ++ .read_csts = nvme_pci_apple_ans_read_csts, ++ .cmd_allowed = nvme_pci_apple_ans_cmd_allowed, ++ .ns_readonly = nvme_pci_apple_ans_ns_readonly, ++ .map_data = nvme_pci_apple_ans_map_data, ++ .unmap_data = nvme_pci_apple_ans_unmap_data, ++ .queue_depth = nvme_pci_apple_ans_queue_depth, ++ .max_hw_sectors = nvme_pci_apple_ans_max_hw_sectors, ++}; ++ ++/* Old queues are synchronized, BME is blocked; no BAR access until rearmed. */ ++int nvme_pci_apple_ans_recover(struct nvme_dev *dev) ++{ ++ struct apple_ans_nvme *ans = nvme_pci_apple_ans(dev); ++ int ret = apple_ans_restart_port(dev->dev); ++ ++ if (ret) ++ return ret; ++ pci_restore_state(to_pci_dev(dev->dev)); ++ ans->host_fault = false; ++ ans->cc_base_valid = false; ++ ans->cc_written = false; ++ ans->dart_fault_logged = false; ++ return 0; ++} +diff --git a/drivers/nvme/host/pci-internal.h b/drivers/nvme/host/pci-internal.h +new file mode 100644 +index 0000000000000000000000000000000000000000..4d9ab30367665b4a91898f197fab4d1e45021372 +--- /dev/null ++++ b/drivers/nvme/host/pci-internal.h +@@ -0,0 +1,220 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Private nvme-pci structures shared with controller-specific glue. ++ * ++ * Copyright (c) 2011-2014, Intel Corporation. ++ * Copyright (C) 2026 Paul Praschl ++ */ ++ ++#ifndef _NVME_PCI_INTERNAL_H ++#define _NVME_PCI_INTERNAL_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nvme.h" ++ ++#define NVME_MAX_NR_DESCRIPTORS 5 ++ ++struct nvme_dev; ++struct nvme_queue; ++ ++struct nvme_descriptor_pools { ++ struct dma_pool *large; ++ struct dma_pool *small; ++}; ++ ++struct nvme_pci_dma_ops { ++ int (*init)(struct nvme_dev *dev, int node); ++ void (*exit)(struct nvme_dev *dev); ++ int (*preinit)(struct nvme_dev *dev); ++ int (*prepare_enable)(struct nvme_dev *dev); ++ /* After SHST complete, before IRQ/DMA mappings are dismantled. */ ++ void (*shutdown)(struct nvme_dev *dev, int shutdown_ret); ++ /* Same controller stop, but sleep must return errors instead of panic. */ ++ int (*stop_for_sleep)(struct nvme_dev *dev, int shutdown_ret); ++ /* Optional: replace the CC write and the CSTS read (bring-up checks) */ ++ int (*write_cc)(struct nvme_dev *dev, u32 cc); ++ int (*read_csts)(struct nvme_dev *dev, u32 *csts); ++ /* ++ * Optional, independent of CONFIG_NVME_PCI_READ_ONLY: refuse commands ++ * (called under the SQ lock, must not sleep) and force namespaces ++ * read-only. ++ */ ++ bool (*cmd_allowed)(struct nvme_dev *dev, u16 qid, ++ const struct nvme_command *cmd); ++ bool (*ns_readonly)(struct nvme_dev *dev, u32 nsid); ++ blk_status_t (*map_data)(struct request *req); ++ bool (*unmap_data)(struct request *req); ++ u32 (*queue_depth)(struct nvme_dev *dev, u32 depth); ++ u32 (*max_hw_sectors)(struct nvme_dev *dev, u32 max_hw_sectors); ++}; ++ ++/* ++ * Represents an NVM Express device. Each nvme_dev is a PCI function. ++ */ ++struct nvme_dev { ++ struct nvme_queue *queues; ++ struct blk_mq_tag_set tagset; ++ struct blk_mq_tag_set admin_tagset; ++ u32 __iomem *dbs; ++ struct device *dev; ++ unsigned int online_queues; ++ unsigned int max_qid; ++ unsigned int io_queues[HCTX_MAX_TYPES]; ++ unsigned int num_vecs; ++ u32 q_depth; ++ int io_sqes; ++ u32 db_stride; ++ void __iomem *bar; ++ unsigned long bar_mapped_size; ++ /* protects shutdown sequencing against reset and remove paths */ ++ struct mutex shutdown_lock; ++ bool subsystem; ++ u64 cmb_size; ++ bool cmb_use_sqes; ++ u32 cmbsz; ++ u32 cmbloc; ++ struct nvme_ctrl ctrl; ++ u32 last_ps; ++ bool hmb; ++ unsigned long apple_ans_flags; ++#define ANS_RECOVERY_USED 0 ++#define ANS_HEALTH_ACTIVE 1 ++#define ANS_RECOVERY_PENDING 2 ++#define ANS_HEALTH_FAILED 3 ++#define ANS_VECTOR_DRAINED 4 /* report the next MSI after a timeout drain */ ++ /* nvme.apple_ans_irq_rescue_ms: CQ head+phase seen pending, per queue */ ++ struct delayed_work ans_irq_rescue; ++ u32 ans_irq_rescue_mark[2]; ++ struct sg_table *hmb_sgt; ++ mempool_t *dmavec_mempool; ++ const struct nvme_pci_dma_ops *dma_ops; ++ void *dma_data; ++ ++ /* shadow doorbell buffer support: */ ++ __le32 *dbbuf_dbs; ++ dma_addr_t dbbuf_dbs_dma_addr; ++ __le32 *dbbuf_eis; ++ dma_addr_t dbbuf_eis_dma_addr; ++ ++ /* host memory buffer support: */ ++ u64 host_mem_size; ++ u32 nr_host_mem_descs; ++ u32 host_mem_descs_size; ++ dma_addr_t host_mem_descs_dma; ++ struct nvme_host_mem_buf_desc *host_mem_descs; ++ void **host_mem_desc_bufs; ++ unsigned int nr_allocated_queues; ++ unsigned int nr_write_queues; ++ unsigned int nr_poll_queues; ++ struct nvme_descriptor_pools descriptor_pools[]; ++}; ++ ++/* ++ * An NVM Express queue. Each device has at least two (one for admin ++ * commands and one for I/O commands). ++ */ ++struct nvme_queue { ++ struct nvme_dev *dev; ++ struct nvme_descriptor_pools descriptor_pools; ++ spinlock_t sq_lock; ++ void *sq_cmds ++ __guarded_by(&sq_lock); ++ /* only used for poll queues: */ ++ spinlock_t cq_poll_lock ____cacheline_aligned_in_smp; ++ struct nvme_completion *cqes; ++ dma_addr_t sq_dma_addr; ++ dma_addr_t cq_dma_addr; ++ u32 __iomem *q_db; ++ u32 q_depth; ++ u16 cq_vector; ++ u16 cq_head; ++ u16 sq_tail ++ __guarded_by(&sq_lock); ++ u16 last_sq_tail ++ __guarded_by(&sq_lock); ++ u16 qid; ++ u8 cq_phase; ++ u8 sqes; ++ unsigned long flags; ++#define NVMEQ_ENABLED 0 ++#define NVMEQ_SQ_CMB 1 ++#define NVMEQ_DELETE_ERROR 2 ++#define NVMEQ_POLLED 3 ++ __le32 *dbbuf_sq_db; ++ __le32 *dbbuf_cq_db; ++ __le32 *dbbuf_sq_ei; ++ __le32 *dbbuf_cq_ei; ++ struct completion delete_done; ++}; ++ ++/* bits for iod->flags */ ++enum nvme_iod_flags { ++ /* this command has been aborted by the timeout handler */ ++ IOD_ABORTED = 1U << 0, ++ ++ /* uses the small descriptor pool */ ++ IOD_SMALL_DESCRIPTOR = 1U << 1, ++ ++ /* single segment dma mapping */ ++ IOD_SINGLE_SEGMENT = 1U << 2, ++ ++ /* Data payload contains p2p memory */ ++ IOD_DATA_P2P = 1U << 3, ++ ++ /* Metadata contains p2p memory */ ++ IOD_META_P2P = 1U << 4, ++ ++ /* Data payload contains MMIO memory */ ++ IOD_DATA_MMIO = 1U << 5, ++ ++ /* Metadata contains MMIO memory */ ++ IOD_META_MMIO = 1U << 6, ++ ++ /* Metadata using non-coalesced MPTR */ ++ IOD_SINGLE_META_SEGMENT = 1U << 7, ++}; ++ ++struct nvme_dma_vec { ++ dma_addr_t addr; ++ unsigned int len; ++}; ++ ++/* ++ * The nvme_iod describes the data in an I/O. ++ */ ++struct nvme_iod { ++ struct nvme_request req; ++ struct nvme_command cmd; ++ u8 flags; ++ u8 nr_descriptors; ++ ++ size_t total_len; ++ struct dma_iova_state dma_state; ++ void *descriptors[NVME_MAX_NR_DESCRIPTORS]; ++ struct nvme_dma_vec *dma_vecs; ++ unsigned int nr_dma_vecs; ++ ++ dma_addr_t meta_dma; ++ size_t meta_total_len; ++ struct dma_iova_state meta_dma_state; ++ struct nvme_sgl_desc *meta_descriptor; ++ void *dma_private; ++}; ++ ++extern const struct nvme_pci_dma_ops nvme_pci_apple_ans_ops; ++bool nvme_pci_apple_ans_match(struct pci_dev *pdev); ++bool nvme_pci_apple_ans_probe_allowed(struct pci_dev *pdev); ++void nvme_pci_apple_ans_log_msi(struct nvme_dev *dev); ++int nvme_pci_apple_ans_recover(struct nvme_dev *dev); ++ ++#endif /* _NVME_PCI_INTERNAL_H */ +diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c +index da93b505d2394a5e1add5af042ed5d900a2d9efe..6efcfadf50059c8b7dc98ba3d854c2d63dc6a46d 100644 +--- a/drivers/nvme/host/pci.c ++++ b/drivers/nvme/host/pci.c +@@ -5,6 +5,8 @@ + */ + + #include ++#include ++#include + #include + #include + #include +@@ -14,12 +16,15 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +35,9 @@ + + #include "trace.h" + #include "nvme.h" ++#include "pci-internal.h" ++ ++#define PCI_DEVICE_ID_APPLE_ANS_NVME 0x2002 + + #define SQ_SIZE(q) ((q)->q_depth << (q)->sqes) + #define CQ_SIZE(q) ((q)->q_depth * sizeof(struct nvme_completion)) +@@ -41,7 +49,6 @@ + * Arbitrary upper bound. + */ + #define NVME_MAX_BYTES SZ_8M +-#define NVME_MAX_NR_DESCRIPTORS 5 + + /* + * For data SGLs we support a single descriptors worth of SGL entries. +@@ -277,66 +284,104 @@ static bool noacpi; + module_param(noacpi, bool, 0444); + MODULE_PARM_DESC(noacpi, "disable acpi bios quirks"); + +-struct nvme_dev; +-struct nvme_queue; ++/* ++ * CONFIG_NVME_PCI_WRITE_WINDOW: the one range the read-only allowlist lets ++ * NVMe Write reach, namespace nsid, LBAs first..last inclusive. Only ++ * nvme.write_window= on the command line sets it. ++ */ ++static struct { ++ bool set; ++ u32 nsid; ++ u64 first; ++ u64 last; ++} write_window; + +-static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown); +-static void nvme_delete_io_queues(struct nvme_dev *dev); +-static void nvme_update_attrs(struct nvme_dev *dev); ++#ifdef CONFIG_NVME_PCI_WRITE_WINDOW ++static char write_window_param[64]; ++ ++static int write_window_set(const char *val, const struct kernel_param *kp) ++{ ++ char buf[64], *lba, *last; ++ u64 first_lba, last_lba; ++ u32 nsid; + +-struct nvme_descriptor_pools { +- struct dma_pool *large; +- struct dma_pool *small; ++ if (strscpy(buf, val, sizeof(buf)) < 0) ++ return -EINVAL; ++ lba = strchr(buf, ':'); ++ last = lba ? strchr(lba + 1, '-') : NULL; ++ if (!last) ++ return -EINVAL; ++ *lba++ = '\0'; ++ *last++ = '\0'; ++ if (kstrtou32(buf, 0, &nsid) || kstrtou64(lba, 0, &first_lba) || ++ kstrtou64(strim(last), 0, &last_lba) || !nsid || ++ nsid == NVME_NSID_ALL || first_lba > last_lba) ++ return -EINVAL; ++ ++ write_window.nsid = nsid; ++ write_window.first = first_lba; ++ write_window.last = last_lba; ++ write_window.set = true; ++ return param_set_copystring(val, kp); ++} ++ ++static const struct kernel_param_ops write_window_ops = { ++ .set = write_window_set, ++ .get = param_get_string, + }; + ++static struct kparam_string write_window_string = { ++ .maxlen = sizeof(write_window_param), ++ .string = write_window_param, ++}; ++ ++module_param_cb(write_window, &write_window_ops, &write_window_string, 0444); ++MODULE_PARM_DESC(write_window, ++ "NSID:FIRST-LAST: on a read-only (CONFIG_NVME_PCI_READ_ONLY) controller, allow NVMe Write to these LBAs (inclusive) of namespace NSID and nothing else. Boot-time only."); ++#endif ++ ++static bool nvme_pci_write_window_active(void) ++{ ++ return IS_ENABLED(CONFIG_NVME_PCI_WRITE_WINDOW) && write_window.set; ++} ++ ++/* A Write whose whole LBA range lies in the window */ ++static bool nvme_pci_in_write_window(const struct nvme_command *cmd) ++{ ++ u64 slba = le64_to_cpu(cmd->rw.slba); ++ u64 nlb = (u64)le16_to_cpu(cmd->rw.length) + 1; ++ ++ return nvme_pci_write_window_active() && ++ le32_to_cpu(cmd->rw.nsid) == write_window.nsid && ++ slba >= write_window.first && slba <= write_window.last && ++ nlb <= write_window.last - slba + 1; ++} ++ + /* +- * Represents an NVM Express device. Each nvme_dev is a PCI function. ++ * The allowlist refuses trim and Write Zeroes even inside the window, so ++ * don't advertise them: the block layer would turn the refusal into EIO ++ * where callers expect EOPNOTSUPP and a fallback to plain writes. ++ * NVME_QUIRK_DISABLE_WRITE_ZEROES (set at probe) covers Write Zeroes; ++ * namespaces read oncs and dmrsl when they are scanned, after this. The ++ * scan re-reads dmrsl from Identify (CNS 06h) unless that is skipped; its ++ * other fields only size trim and Write Zeroes too. + */ +-struct nvme_dev { +- struct nvme_queue *queues; +- struct blk_mq_tag_set tagset; +- struct blk_mq_tag_set admin_tagset; +- u32 __iomem *dbs; +- struct device *dev; +- unsigned online_queues; +- unsigned max_qid; +- unsigned io_queues[HCTX_MAX_TYPES]; +- unsigned int num_vecs; +- u32 q_depth; +- int io_sqes; +- u32 db_stride; +- void __iomem *bar; +- unsigned long bar_mapped_size; +- struct mutex shutdown_lock; +- bool subsystem; +- u64 cmb_size; +- bool cmb_use_sqes; +- u32 cmbsz; +- u32 cmbloc; +- struct nvme_ctrl ctrl; +- u32 last_ps; +- bool hmb; +- struct sg_table *hmb_sgt; +- mempool_t *dmavec_mempool; +- +- /* shadow doorbell buffer support: */ +- __le32 *dbbuf_dbs; +- dma_addr_t dbbuf_dbs_dma_addr; +- __le32 *dbbuf_eis; +- dma_addr_t dbbuf_eis_dma_addr; +- +- /* host memory buffer support: */ +- u64 host_mem_size; +- u32 nr_host_mem_descs; +- u32 host_mem_descs_size; +- dma_addr_t host_mem_descs_dma; +- struct nvme_host_mem_buf_desc *host_mem_descs; +- void **host_mem_desc_bufs; +- unsigned int nr_allocated_queues; +- unsigned int nr_write_queues; +- unsigned int nr_poll_queues; +- struct nvme_descriptor_pools descriptor_pools[]; +-}; ++static void nvme_pci_write_window_limits(struct nvme_dev *dev) ++{ ++ if (!nvme_pci_write_window_active()) ++ return; ++ dev_info(dev->ctrl.device, ++ "write window: controller ONCS %#x VWC %#x DMRSL %u; trim and write zeroes not advertised\n", ++ dev->ctrl.oncs, dev->ctrl.vwc, dev->ctrl.dmrsl); ++ dev->ctrl.oncs &= ~NVME_CTRL_ONCS_DSM; ++ dev->ctrl.dmrl = 0; ++ dev->ctrl.dmrsl = 0; ++ set_bit(NVME_CTRL_SKIP_ID_CNS_CS, &dev->ctrl.flags); ++} ++ ++static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown); ++static void nvme_delete_io_queues(struct nvme_dev *dev); ++static void nvme_update_attrs(struct nvme_dev *dev); + + static int io_queue_depth_set(const char *val, const struct kernel_param *kp) + { +@@ -359,97 +404,6 @@ static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl) + return container_of(ctrl, struct nvme_dev, ctrl); + } + +-/* +- * An NVM Express queue. Each device has at least two (one for admin +- * commands and one for I/O commands). +- */ +-struct nvme_queue { +- struct nvme_dev *dev; +- struct nvme_descriptor_pools descriptor_pools; +- spinlock_t sq_lock; +- void *sq_cmds +- __guarded_by(&sq_lock); +- /* only used for poll queues: */ +- spinlock_t cq_poll_lock ____cacheline_aligned_in_smp; +- struct nvme_completion *cqes; +- dma_addr_t sq_dma_addr; +- dma_addr_t cq_dma_addr; +- u32 __iomem *q_db; +- u32 q_depth; +- u16 cq_vector; +- u16 cq_head; +- u16 sq_tail +- __guarded_by(&sq_lock); +- u16 last_sq_tail +- __guarded_by(&sq_lock); +- u16 qid; +- u8 cq_phase; +- u8 sqes; +- unsigned long flags; +-#define NVMEQ_ENABLED 0 +-#define NVMEQ_SQ_CMB 1 +-#define NVMEQ_DELETE_ERROR 2 +-#define NVMEQ_POLLED 3 +- __le32 *dbbuf_sq_db; +- __le32 *dbbuf_cq_db; +- __le32 *dbbuf_sq_ei; +- __le32 *dbbuf_cq_ei; +- struct completion delete_done; +-}; +- +-/* bits for iod->flags */ +-enum nvme_iod_flags { +- /* this command has been aborted by the timeout handler */ +- IOD_ABORTED = 1U << 0, +- +- /* uses the small descriptor pool */ +- IOD_SMALL_DESCRIPTOR = 1U << 1, +- +- /* single segment dma mapping */ +- IOD_SINGLE_SEGMENT = 1U << 2, +- +- /* Data payload contains p2p memory */ +- IOD_DATA_P2P = 1U << 3, +- +- /* Metadata contains p2p memory */ +- IOD_META_P2P = 1U << 4, +- +- /* Data payload contains MMIO memory */ +- IOD_DATA_MMIO = 1U << 5, +- +- /* Metadata contains MMIO memory */ +- IOD_META_MMIO = 1U << 6, +- +- /* Metadata using non-coalesced MPTR */ +- IOD_SINGLE_META_SEGMENT = 1U << 7, +-}; +- +-struct nvme_dma_vec { +- dma_addr_t addr; +- unsigned int len; +-}; +- +-/* +- * The nvme_iod describes the data in an I/O. +- */ +-struct nvme_iod { +- struct nvme_request req; +- struct nvme_command cmd; +- u8 flags; +- u8 nr_descriptors; +- +- size_t total_len; +- struct dma_iova_state dma_state; +- void *descriptors[NVME_MAX_NR_DESCRIPTORS]; +- struct nvme_dma_vec *dma_vecs; +- unsigned int nr_dma_vecs; +- +- dma_addr_t meta_dma; +- size_t meta_total_len; +- struct dma_iova_state meta_dma_state; +- struct nvme_sgl_desc *meta_descriptor; +-}; +- + static inline unsigned int nvme_dbbuf_size(struct nvme_dev *dev) + { + return dev->nr_allocated_queues * 8 * dev->db_stride; +@@ -737,10 +691,118 @@ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq) + nvmeq->last_sq_tail = nvmeq->sq_tail; + } + ++/* ++ * CONFIG_NVME_PCI_READ_ONLY: allowlist of the commands this kernel may send. ++ * ++ * I/O queues may only read and flush. The admin queue may only identify, ++ * read logs and features, manage its own queues, and set the few volatile ++ * features nvme-core configures at init. Everything else (write, write ++ * zeroes, DSM/trim, format, sanitize, firmware download/commit, namespace ++ * management, security send, directives, vendor-specific opcodes, saved ++ * features) is refused before it reaches the submission queue. The one ++ * exception is CONFIG_NVME_PCI_WRITE_WINDOW: a Write inside nvme.write_window. ++ */ ++static bool nvme_pci_ro_cmd_allowed(const struct nvme_queue *nvmeq, ++ const struct nvme_command *cmd) ++{ ++ u32 cdw10; ++ ++ if (!IS_ENABLED(CONFIG_NVME_PCI_READ_ONLY)) ++ return true; ++ ++ if (nvmeq->qid) { ++ switch (cmd->common.opcode) { ++ case nvme_cmd_flush: ++ case nvme_cmd_read: ++ return true; ++ case nvme_cmd_write: ++ return nvme_pci_in_write_window(cmd); ++ default: ++ return false; ++ } ++ } ++ ++ switch (cmd->common.opcode) { ++ case nvme_admin_delete_sq: ++ case nvme_admin_create_sq: ++ case nvme_admin_get_log_page: ++ case nvme_admin_delete_cq: ++ case nvme_admin_create_cq: ++ case nvme_admin_identify: ++ case nvme_admin_abort_cmd: ++ case nvme_admin_get_features: ++ case nvme_admin_async_event: ++ case nvme_admin_keep_alive: ++ return true; ++ case nvme_admin_set_features: ++ cdw10 = le32_to_cpu(cmd->features.fid); ++ /* SV: the feature would persist across power cycles */ ++ if (cdw10 & BIT(31)) ++ return false; ++ switch (cdw10 & 0xff) { ++ case NVME_FEAT_POWER_MGMT: ++ case NVME_FEAT_TEMP_THRESH: ++ case NVME_FEAT_NUM_QUEUES: ++ case NVME_FEAT_IRQ_COALESCE: ++ case NVME_FEAT_ASYNC_EVENT: ++ case NVME_FEAT_AUTO_PST: ++ case NVME_FEAT_TIMESTAMP: ++ case NVME_FEAT_KATO: ++ case NVME_FEAT_HOST_BEHAVIOR: ++ return true; ++ default: ++ return false; ++ } ++ default: ++ return false; ++ } ++} ++ ++/* ++ * Both gates: the CONFIG_NVME_PCI_READ_ONLY allowlist above and the ++ * device's own policy (Apple ANS: protected system namespaces), which ++ * holds even without CONFIG_NVME_PCI_READ_ONLY. ++ */ ++static bool nvme_pci_cmd_allowed(const struct nvme_queue *nvmeq, ++ const struct nvme_command *cmd) ++{ ++ const struct nvme_pci_dma_ops *ops = nvmeq->dev->dma_ops; ++ ++ if (!nvme_pci_ro_cmd_allowed(nvmeq, cmd)) ++ return false; ++ if (ops && ops->cmd_allowed && ++ !ops->cmd_allowed(nvmeq->dev, nvmeq->qid, cmd)) ++ return false; ++ return true; ++} ++ + static inline void nvme_sq_copy_cmd(struct nvme_queue *nvmeq, + struct nvme_command *cmd) + __must_hold(&nvmeq->sq_lock) + { ++ struct nvme_command safe; ++ ++ /* ++ * Backstop for CONFIG_NVME_PCI_READ_ONLY: nvme_prep_rq() already ++ * failed disallowed requests, so getting here is a bug. Never let the ++ * command reach the device; queue a harmless one in its slot so the ++ * SQ tail stays consistent. ++ */ ++ if (unlikely(!nvme_pci_cmd_allowed(nvmeq, cmd))) { ++ WARN_ONCE(1, "nvme: read-only: neutralized opcode %#x on qid %u\n", ++ cmd->common.opcode, nvmeq->qid); ++ memset(&safe, 0, sizeof(safe)); ++ safe.common.command_id = cmd->common.command_id; ++ if (nvmeq->qid) { ++ safe.common.opcode = nvme_cmd_flush; ++ safe.common.nsid = cmd->common.nsid; ++ } else { ++ safe.features.opcode = nvme_admin_get_features; ++ safe.features.fid = cpu_to_le32(NVME_FEAT_NUM_QUEUES); ++ } ++ cmd = &safe; ++ } ++ + memcpy(nvmeq->sq_cmds + (nvmeq->sq_tail << nvmeq->sqes), + absolute_pointer(cmd), sizeof(*cmd)); + if (++nvmeq->sq_tail == nvmeq->q_depth) +@@ -943,6 +1005,10 @@ static void nvme_unmap_data(struct request *req) + struct device *dma_dev = nvmeq->dev->dev; + unsigned int attrs = 0; + ++ if (nvmeq->dev->dma_ops && nvmeq->dev->dma_ops->unmap_data && ++ nvmeq->dev->dma_ops->unmap_data(req)) ++ return; ++ + if (iod->flags & IOD_SINGLE_SEGMENT) { + static_assert(offsetof(union nvme_data_ptr, prp1) == + offsetof(union nvme_data_ptr, sgl.addr)); +@@ -1257,6 +1323,12 @@ static blk_status_t nvme_map_data(struct request *req) + struct blk_dma_iter iter; + blk_status_t ret; + ++ if (dev->dma_ops && dev->dma_ops->map_data) { ++ ret = dev->dma_ops->map_data(req); ++ if (ret != BLK_STS_NOTSUPP) ++ return ret; ++ } ++ + /* + * Try to skip the DMA iterator for single segment requests, as that + * significantly improves performances for small I/O sizes. +@@ -1412,11 +1484,33 @@ static blk_status_t nvme_prep_rq(struct request *req) + iod->total_len = 0; + iod->meta_total_len = 0; + iod->nr_dma_vecs = 0; ++ iod->dma_vecs = NULL; ++ iod->dma_private = NULL; + + ret = nvme_setup_cmd(req->q->queuedata, req); + if (ret) + return ret; + ++ if (unlikely(!nvme_pci_cmd_allowed(req->mq_hctx->driver_data, ++ &iod->cmd))) { ++ if (iod->cmd.common.opcode == nvme_cmd_write && ++ nvme_pci_write_window_active()) ++ dev_err_ratelimited(nvme_req(req)->ctrl->device, ++ "read-only: refused write nsid %u LBA %llu+%u outside the write window\n", ++ le32_to_cpu(iod->cmd.rw.nsid), ++ le64_to_cpu(iod->cmd.rw.slba), ++ le16_to_cpu(iod->cmd.rw.length) + 1); ++ else ++ dev_err_ratelimited(nvme_req(req)->ctrl->device, ++ "read-only: refused opcode %#x nsid %u on %s queue\n", ++ iod->cmd.common.opcode, ++ le32_to_cpu(iod->cmd.common.nsid), ++ nvme_req(req)->ctrl->admin_q == req->q ? ++ "admin" : "I/O"); ++ ret = BLK_STS_IOERR; ++ goto out_free_cmd; ++ } ++ + if (blk_rq_nr_phys_segments(req)) { + ret = nvme_map_data(req); + if (ret) +@@ -1638,12 +1732,70 @@ static inline bool nvme_poll_cq(struct nvme_queue *nvmeq, + return found; + } + ++/* ++ * Apple ANS signals the admin and I/O CQs through one MSI ++ * (NVME_QUIRK_SINGLE_VECTOR). Servicing one CQ per shared-IRQ action can ++ * leave a completion that reached the admin CQ after its action ran, while ++ * the I/O action was still acknowledging its own CQ. Warm j71t boots then ++ * got no further MSI: the IRQ count froze with completions waiting in both ++ * CQs and every command waited for its timeout. Each invocation therefore ++ * services every CQ on the vector and rescans after its doorbell writes. ++ * The actions of one IRQ never run concurrently, so this needs no more ++ * locking than one CQ does upstream. Threaded handlers can run concurrently ++ * and keep the per-queue behaviour. ++ */ ++static bool nvme_apple_ans_shared_vector(struct nvme_dev *dev) ++{ ++ return dev->dma_ops == &nvme_pci_apple_ans_ops && ++ (dev->ctrl.quirks & NVME_QUIRK_SINGLE_VECTOR) && ++ !use_threaded_interrupts; ++} ++ ++static bool nvme_apple_ans_vector_queue(struct nvme_queue *q, u16 vector) ++{ ++ return test_bit(NVMEQ_ENABLED, &q->flags) && ++ !test_bit(NVMEQ_POLLED, &q->flags) && q->cq_vector == vector; ++} ++ ++/* Hard IRQ only: returns after a full pass over the vector finds nothing. */ ++static bool nvme_apple_ans_irq_vector(struct nvme_dev *dev, u16 vector, ++ struct io_comp_batch *iob) ++{ ++ bool found = false, more; ++ unsigned int i; ++ ++ do { ++ more = false; ++ for (i = 0; i < dev->ctrl.queue_count; i++) { ++ struct nvme_queue *q = &dev->queues[i]; ++ ++ if (nvme_apple_ans_vector_queue(q, vector) && ++ nvme_poll_cq(q, q->qid ? iob : NULL)) ++ more = true; ++ } ++ found |= more; ++ } while (more); ++ return found; ++} ++ + static irqreturn_t nvme_irq(int irq, void *data) + { + struct nvme_queue *nvmeq = data; ++ struct nvme_dev *dev = nvmeq->dev; + DEFINE_IO_COMP_BATCH(iob); ++ bool found; + +- if (nvme_poll_cq(nvmeq, &iob)) { ++ if (nvme_apple_ans_shared_vector(dev)) { ++ /* Device evidence: did the vector signal again after a drain? */ ++ if (unlikely(test_bit(ANS_VECTOR_DRAINED, &dev->apple_ans_flags)) && ++ test_and_clear_bit(ANS_VECTOR_DRAINED, &dev->apple_ans_flags)) ++ dev_info(dev->dev, "Apple ANS: MSI resumed after vector drain (irq %d)\n", ++ irq); ++ found = nvme_apple_ans_irq_vector(dev, nvmeq->cq_vector, &iob); ++ } else { ++ found = nvme_poll_cq(nvmeq, &iob); ++ } ++ if (found) { + if (!rq_list_empty(&iob.req_list)) + nvme_pci_complete_batch(&iob); + return IRQ_HANDLED; +@@ -1679,6 +1831,97 @@ static void nvme_poll_irqdisable(struct nvme_queue *nvmeq) + enable_irq(irq); + } + ++/* ++ * nvme_poll_irqdisable() for the shared ANS vector. Draining one CQ cannot ++ * restart a silent vector while the other CQ still holds a completion, so ++ * drain all of them. The log line and the "MSI resumed" report that follows ++ * it tell whether the controller signals again once every CQ is empty. ++ */ ++static void nvme_apple_ans_drain_vector(struct nvme_queue *nvmeq, ++ const char *reason) ++{ ++ struct nvme_dev *dev = nvmeq->dev; ++ int irq = pci_irq_vector(to_pci_dev(dev->dev), nvmeq->cq_vector); ++ unsigned int i, count = irq > 0 ? kstat_irqs_usr(irq) : 0; ++ unsigned long found = 0; ++ bool more, hit; ++ ++ disable_irq(irq); ++ do { ++ more = false; ++ for (i = 0; i < dev->ctrl.queue_count; i++) { ++ struct nvme_queue *q = &dev->queues[i]; ++ ++ if (!nvme_apple_ans_vector_queue(q, nvmeq->cq_vector)) ++ continue; ++ spin_lock(&q->cq_poll_lock); ++ hit = nvme_poll_cq(q, NULL); ++ spin_unlock(&q->cq_poll_lock); ++ if (hit) { ++ found |= BIT(min_t(unsigned int, i, BITS_PER_LONG - 1)); ++ more = true; ++ } ++ } ++ } while (more); ++ set_bit(ANS_VECTOR_DRAINED, &dev->apple_ans_flags); ++ enable_irq(irq); ++ dev_warn_ratelimited(dev->dev, "Apple ANS: %s: drained every CQ on vector %u, completions on queues %#lx, irq %d count %u\n", ++ reason, nvmeq->cq_vector, found, irq, count); ++} ++ ++static unsigned int apple_ans_irq_rescue_ms; ++module_param(apple_ans_irq_rescue_ms, uint, 0444); ++MODULE_PARM_DESC(apple_ans_irq_rescue_ms, ++ "Apple ANS: drain the shared MSI after an unsignalled completion waits 1-2 periods of this many ms (0 = off)"); ++ ++/* ++ * Optional safety net for the shared ANS vector. A live vector consumes a ++ * completion within microseconds; one still pending at the same CQ head on ++ * two consecutive ticks was not signalled, so drain it now instead of at ++ * the 5-second timeout. Reads are lockless; a stale view only delays or ++ * repeats a drain, which is safe. ++ */ ++static void nvme_apple_ans_irq_rescue_work(struct work_struct *work) ++{ ++ struct nvme_dev *dev = container_of(to_delayed_work(work), ++ struct nvme_dev, ans_irq_rescue); ++ unsigned int i, n; ++ ++ if (nvme_ctrl_state(&dev->ctrl) != NVME_CTRL_LIVE) ++ return; ++ n = min_t(unsigned int, dev->ctrl.queue_count, ++ ARRAY_SIZE(dev->ans_irq_rescue_mark)); ++ for (i = 0; i < n; i++) { ++ struct nvme_queue *q = &dev->queues[i]; ++ u32 mark = 0; ++ ++ if (nvme_apple_ans_vector_queue(q, 0) && nvme_cqe_pending(q)) ++ mark = BIT(31) | READ_ONCE(q->cq_phase) << 16 | ++ READ_ONCE(q->cq_head); ++ if (mark && mark == dev->ans_irq_rescue_mark[i]) { ++ nvme_apple_ans_drain_vector(q, "IRQ rescue"); ++ memset(dev->ans_irq_rescue_mark, 0, ++ sizeof(dev->ans_irq_rescue_mark)); ++ break; ++ } ++ dev->ans_irq_rescue_mark[i] = mark; ++ } ++ schedule_delayed_work(&dev->ans_irq_rescue, ++ msecs_to_jiffies(apple_ans_irq_rescue_ms)); ++} ++ ++static void nvme_apple_ans_irq_rescue_start(struct nvme_dev *dev) ++{ ++ if (!apple_ans_irq_rescue_ms || !nvme_apple_ans_shared_vector(dev) || ++ dev->online_queues < 2) ++ return; ++ memset(dev->ans_irq_rescue_mark, 0, sizeof(dev->ans_irq_rescue_mark)); ++ dev_info(dev->dev, "Apple ANS: IRQ rescue every %u ms\n", ++ apple_ans_irq_rescue_ms); ++ schedule_delayed_work(&dev->ans_irq_rescue, ++ msecs_to_jiffies(apple_ans_irq_rescue_ms)); ++} ++ + static int nvme_poll(struct blk_mq_hw_ctx *hctx, struct io_comp_batch *iob) + { + struct nvme_queue *nvmeq = hctx->driver_data; +@@ -1883,6 +2126,157 @@ static void nvme_warn_reset(struct nvme_dev *dev, u32 csts) + "Try \"nvme_core.default_ps_max_latency_us=0 pcie_aspm=off pcie_port_pm=off\" and report a bug\n"); + } + ++/* No endpoint MMIO/config or doorbell reads: safe first evidence for ANS. */ ++static void nvme_apple_ans_dump(struct nvme_dev *dev, const char *reason) ++{ ++ unsigned int i; ++ ++ dev_info(dev->dev, "Apple ANS: snapshot %s state %d online %u allocated %u vectors %u recovery_used %u\n", ++ reason, nvme_ctrl_state(&dev->ctrl), dev->online_queues, ++ dev->ctrl.queue_count, dev->num_vecs, ++ test_bit(0, &dev->apple_ans_flags)); ++ apple_ans_host_dump(dev->dev, reason); ++ for (i = 0; i < dev->ctrl.queue_count; i++) { ++ struct nvme_queue *q = &dev->queues[i]; ++ u16 head = READ_ONCE(q->cq_head); ++ struct nvme_completion *cqe; ++ int irq = -1; ++ ++ if (!q->cqes || head >= q->q_depth) ++ continue; ++ cqe = &q->cqes[head]; ++ if (test_bit(NVMEQ_ENABLED, &q->flags) && ++ !test_bit(NVMEQ_POLLED, &q->flags)) ++ irq = pci_irq_vector(to_pci_dev(dev->dev), q->cq_vector); ++ dev_info(dev->dev, "Apple ANS: q%u flags %#lx depth %u SQ tail %u/%u CQ head %u phase %u next status %#x cid %#x sqhead %u vector %u irq %d count %u SQ %pad CQ %pad\n", ++ i, READ_ONCE(q->flags), q->q_depth, ++ READ_ONCE(q->sq_tail), READ_ONCE(q->last_sq_tail), ++ head, READ_ONCE(q->cq_phase), ++ le16_to_cpu(READ_ONCE(cqe->status)), ++ le16_to_cpu(READ_ONCE(cqe->command_id)), ++ le16_to_cpu(READ_ONCE(cqe->sq_head)), q->cq_vector, ++ irq, irq > 0 ? kstat_irqs_usr(irq) : 0, ++ &q->sq_dma_addr, &q->cq_dma_addr); ++ } ++} ++ ++/* Five seconds per command, NOWAIT tag allocation, no retry/abort loop. ++ * The timeout callback blocks DMA and cancels requests before the buffers can ++ * be freed. This runs in reset_work before nvme_start_ctrl publishes disks. ++ */ ++static int nvme_apple_ans_health_cmd(struct nvme_dev *dev, ++ struct request_queue *q, ++ struct nvme_command *cmd, void *buffer, ++ unsigned int size, bool *unhealthy) ++{ ++ struct request *req; ++ int ret; ++ ++ req = blk_mq_alloc_request(q, nvme_req_op(cmd), BLK_MQ_REQ_NOWAIT); ++ if (IS_ERR(req)) ++ return PTR_ERR(req); ++ nvme_init_request(req, cmd); ++ req->timeout = 5 * HZ; ++ ret = blk_rq_map_kern(req, buffer, size, GFP_KERNEL); ++ if (!ret) { ++ ret = nvme_execute_rq(req, false); ++ /* Only the timeout path proves that the controller stopped ++ * responding. Allocation/mapping errors and rejected commands ++ * leave this diagnostic inconclusive, never justify a link cycle. ++ */ ++ *unhealthy = test_bit(ANS_HEALTH_FAILED, &dev->apple_ans_flags); ++ } ++ blk_mq_free_request(req); ++ return ret; ++} ++ ++static int nvme_apple_ans_health(struct nvme_dev *dev, bool *unhealthy) ++{ ++ struct queue_limits lim; ++ struct nvme_command cmd = { }; ++ struct request_queue *q; ++ struct nvme_id_ns *id; ++ void *buffer; ++ u64 data_lba; ++ u32 lbs; ++ u8 format; ++ int ret; ++ ++ *unhealthy = false; ++ if (dev->online_queues < 2 || !dev->ctrl.tagset || !dev->ctrl.admin_q) { ++ ret = -ENODEV; ++ goto report; ++ } ++ id = kzalloc_obj(*id); ++ buffer = kzalloc(8192, GFP_KERNEL); ++ if (!id || !buffer) { ++ ret = -ENOMEM; ++ goto free; ++ } ++ set_bit(ANS_HEALTH_ACTIVE, &dev->apple_ans_flags); ++ cmd.identify.opcode = nvme_admin_identify; ++ cmd.identify.nsid = cpu_to_le32(1); ++ cmd.identify.cns = NVME_ID_CNS_NS; ++ ret = nvme_apple_ans_health_cmd(dev, dev->ctrl.admin_q, &cmd, id, ++ sizeof(*id), unhealthy); ++ if (ret) ++ goto done; ++ format = nvme_lbaf_index(id->flbas); ++ if (id->lbaf[format].ms || id->lbaf[format].ds < 9 || ++ id->lbaf[format].ds > 12) { ++ ret = -EINVAL; ++ goto done; ++ } ++ lbs = 1U << id->lbaf[format].ds; ++ /* Identify has already installed validated controller limits on the ++ * admin queue. Keep its conservative PRP boundary/alignment rules for ++ * these passthrough reads, including at least PAGE_SECTORS (32 on A9). ++ * The diagnostic's 8 KiB transfer size is not a legal queue maximum. ++ */ ++ lim = dev->ctrl.admin_q->limits; ++ lim.logical_block_size = lbs; ++ lim.physical_block_size = lbs; ++ q = blk_mq_alloc_queue(&dev->tagset, &lim, NULL); ++ if (IS_ERR(q)) { ++ ret = PTR_ERR(q); ++ goto done; ++ } ++ /* NSID 1 only. Read MBR/GPT, then a block inside the existing root ++ * write window (or LBA 8 on read-only boots). Never send writes. ++ */ ++ data_lba = nvme_pci_write_window_active() && write_window.nsid == 1 ? ++ write_window.first : 8; ++ if (8192 / lbs > le64_to_cpu(id->nsze) || ++ data_lba >= le64_to_cpu(id->nsze) || ++ 4096 / lbs > le64_to_cpu(id->nsze) - data_lba) { ++ ret = -ERANGE; ++ goto destroy; ++ } ++ memset(&cmd, 0, sizeof(cmd)); ++ cmd.rw.opcode = nvme_cmd_read; ++ cmd.rw.nsid = cpu_to_le32(1); ++ /* Both MBR and GPT header even with a native 4K logical block. */ ++ cmd.rw.length = cpu_to_le16(8192 / lbs - 1); ++ ret = nvme_apple_ans_health_cmd(dev, q, &cmd, buffer, 8192, unhealthy); ++ if (!ret) { ++ cmd.rw.slba = cpu_to_le64(data_lba); ++ cmd.rw.length = cpu_to_le16(4096 / lbs - 1); ++ ret = nvme_apple_ans_health_cmd(dev, q, &cmd, buffer, 4096, unhealthy); ++ } ++destroy: ++ blk_mq_destroy_queue(q); ++ blk_put_queue(q); ++done: ++ clear_bit(ANS_HEALTH_ACTIVE, &dev->apple_ans_flags); ++free: ++ kfree(buffer); ++ kfree(id); ++report: ++ dev_info(dev->dev, "Apple ANS: health gate partition/data NSID 1 result %d (%s)\n", ++ ret, !ret ? "passed" : *unhealthy ? "controller timeout" : "gate inconclusive"); ++ return ret; ++} ++ + static enum blk_eh_timer_return nvme_timeout(struct request *req) + { + struct nvme_iod *iod = blk_mq_rq_to_pdu(req); +@@ -1891,8 +2285,33 @@ static enum blk_eh_timer_return nvme_timeout(struct request *req) + struct request *abort_req; + struct nvme_command cmd = { }; + struct pci_dev *pdev = to_pci_dev(dev->dev); +- u32 csts = readl(dev->bar + NVME_REG_CSTS); ++ u32 csts = ~0U; + u8 opcode; ++ bool ans = dev->dma_ops == &nvme_pci_apple_ans_ops; ++ ++ if (ans) { ++ /* Log before the first potentially blocking non-posted BAR read. */ ++ dev_warn(dev->dev, "Apple ANS: timeout tag %d cid %#x qid %u opcode %#x\n", ++ req->tag, nvme_cid(req), nvmeq->qid, ++ nvme_req(req)->cmd->common.opcode); ++ nvme_apple_ans_dump(dev, "timeout before CSTS"); ++ if (test_bit(ANS_HEALTH_ACTIVE, &dev->apple_ans_flags)) { ++ /* Evidence reaches console/netconsole before teardown. No ++ * endpoint read or nested admin deletion on a failed gate. ++ */ ++ pr_flush(1000, false); ++ set_bit(ANS_HEALTH_FAILED, &dev->apple_ans_flags); ++ pci_clear_master(pdev); ++ nvme_dev_disable(dev, false); ++ return BLK_EH_DONE; ++ } ++ if (dev->ctrl.ops->reg_read32(&dev->ctrl, NVME_REG_CSTS, &csts)) ++ goto disable; ++ dev_warn(dev->dev, "Apple ANS: timeout CSTS %#x tag %d qid %u\n", ++ csts, req->tag, nvmeq->qid); ++ } else { ++ csts = readl(dev->bar + NVME_REG_CSTS); ++ } + + /* + * Shutdown the device immediately if we see it is disconnected. This +@@ -1927,6 +2346,8 @@ static enum blk_eh_timer_return nvme_timeout(struct request *req) + */ + if (test_bit(NVMEQ_POLLED, &nvmeq->flags)) + nvme_poll(req->mq_hctx, NULL); ++ else if (nvme_apple_ans_shared_vector(dev)) ++ nvme_apple_ans_drain_vector(nvmeq, "timeout"); + else + nvme_poll_irqdisable(nvmeq); + +@@ -1966,6 +2387,20 @@ static enum blk_eh_timer_return nvme_timeout(struct request *req) + * returned to the driver, or if this is the admin queue. + */ + opcode = nvme_req(req)->cmd->common.opcode; ++ if (ans && of_machine_is_compatible("apple,j71t")) { ++ /* Skip the generic abort/re-arm loop. One reset per device lifetime; ++ * a second stalled request fails the disk instead of trapping mount. ++ */ ++ if (test_and_set_bit(0, &dev->apple_ans_flags)) { ++ dev_err(dev->dev, "Apple ANS: repeated timeout; disabling controller\n"); ++ nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DELETING); ++ } else { ++ dev_warn(dev->dev, "Apple ANS: first timeout; reset without admin abort\n"); ++ if (apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) ++ set_bit(ANS_RECOVERY_PENDING, &dev->apple_ans_flags); ++ } ++ goto disable; ++ } + if (!nvmeq->qid || (iod->flags & IOD_ABORTED)) { + dev_warn(dev->ctrl.device, + "I/O tag %d (%04x) opcode %#x (%s) QID %d timeout, reset controller\n", +@@ -2406,6 +2841,12 @@ static int nvme_pci_configure_admin_queue(struct nvme_dev *dev) + lo_hi_writeq(nvmeq->sq_dma_addr, dev->bar + NVME_REG_ASQ); + lo_hi_writeq(nvmeq->cq_dma_addr, dev->bar + NVME_REG_ACQ); + ++ if (dev->dma_ops && dev->dma_ops->prepare_enable) { ++ result = dev->dma_ops->prepare_enable(dev); ++ if (result) ++ return result; ++ } ++ + result = nvme_enable_ctrl(&dev->ctrl); + if (result) + return result; +@@ -2824,6 +3265,48 @@ static ssize_t hmb_store(struct device *dev, struct device_attribute *attr, + } + static DEVICE_ATTR_RW(hmb); + ++/* Host-only snapshots never cross PCIe. Endpoint mode is an explicit opt-in. */ ++static ssize_t apple_ans_diag_store(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct nvme_dev *dev = to_nvme_dev(dev_get_drvdata(device)); ++ bool endpoint = sysfs_streq(buf, "endpoint"); ++ u32 csts; ++ int ret = 0; ++ ++ if (!endpoint && !sysfs_streq(buf, "host")) ++ return -EINVAL; ++ if (dev->dma_ops != &nvme_pci_apple_ans_ops) ++ return -EOPNOTSUPP; ++ if (!mutex_trylock(&dev->shutdown_lock)) { ++ apple_ans_host_dump(dev->dev, "busy reset/shutdown"); ++ return -EBUSY; ++ } ++ nvme_apple_ans_dump(dev, "rescue"); ++ if (endpoint) { ++ if (!pci_is_enabled(to_pci_dev(dev->dev))) { ++ ret = -ENODEV; ++ goto out; ++ } ++ ret = dev->ctrl.ops->reg_read32(&dev->ctrl, NVME_REG_CSTS, &csts); ++ if (!ret) { ++ dev_info(dev->dev, "Apple ANS: endpoint CSTS %#x CC %#x AQA %#x ASQ %#llx ACQ %#llx HMB %#x:%08x/%#x INTMS %#x\n", ++ csts, readl(dev->bar + NVME_REG_CC), ++ readl(dev->bar + NVME_REG_AQA), ++ lo_hi_readq(dev->bar + NVME_REG_ASQ), ++ lo_hi_readq(dev->bar + NVME_REG_ACQ), ++ readl(dev->bar + 0x1814), readl(dev->bar + 0x1810), ++ readl(dev->bar + 0x1818), readl(dev->bar + NVME_REG_INTMS)); ++ nvme_pci_apple_ans_log_msi(dev); ++ } ++ } ++out: ++ mutex_unlock(&dev->shutdown_lock); ++ return ret ?: count; ++} ++static DEVICE_ATTR_WO(apple_ans_diag); ++ + static umode_t nvme_pci_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) + { +@@ -2837,6 +3320,9 @@ static umode_t nvme_pci_attrs_are_visible(struct kobject *kobj, + if (!dev->cmbsz) + return 0; + } ++ if (a == &dev_attr_apple_ans_diag.attr && ++ dev->dma_ops != &nvme_pci_apple_ans_ops) ++ return 0; + if (a == &dev_attr_hmb.attr && !ctrl->hmpre) + return 0; + +@@ -2848,6 +3334,7 @@ static struct attribute *nvme_pci_attrs[] = { + &dev_attr_cmbloc.attr, + &dev_attr_cmbsz.attr, + &dev_attr_hmb.attr, ++ &dev_attr_apple_ans_diag.attr, + NULL, + }; + +@@ -3212,15 +3699,28 @@ static int nvme_pci_enable(struct nvme_dev *dev) + struct pci_dev *pdev = to_pci_dev(dev->dev); + unsigned int flags = PCI_IRQ_ALL_TYPES; + ++ /* ++ * A host bridge cuts off a port whose link did not come back after ++ * system sleep; a BAR read over it could stall the CPU. ++ */ ++ if (pci_dev_is_disconnected(pdev)) ++ return -ENODEV; ++ + if (pci_enable_device_mem(pdev)) + return result; + +- pci_set_master(pdev); ++ if (dev->dma_ops != &nvme_pci_apple_ans_ops || ++ apple_ans_boot_mode(dev->dev) == APPLE_ANS_ROUND7) ++ pci_set_master(pdev); + +- if (readl(dev->bar + NVME_REG_CSTS) == -1) { +- dev_dbg(dev->ctrl.device, "reading CSTS register failed\n"); +- result = -ENODEV; +- goto disable; ++ { ++ u32 csts; ++ ++ result = dev->ctrl.ops->reg_read32(&dev->ctrl, NVME_REG_CSTS, &csts); ++ if (result || csts == ~0U) { ++ result = result ?: -ENODEV; ++ goto disable; ++ } + } + + /* +@@ -3271,8 +3771,17 @@ static int nvme_pci_enable(struct nvme_dev *dev) + dev_warn(dev->ctrl.device, "IO queue depth clamped to %d\n", + dev->q_depth); + } ++ if (dev->dma_ops && dev->dma_ops->queue_depth) ++ dev->q_depth = dev->dma_ops->queue_depth(dev, dev->q_depth); + dev->ctrl.sqsize = dev->q_depth - 1; /* 0's based queue depth */ + ++ if (dev->dma_ops && dev->dma_ops->preinit) { ++ result = dev->dma_ops->preinit(dev); ++ if (result) ++ goto free_irq; ++ } ++ ++ pci_set_master(pdev); + nvme_map_cmb(dev); + + pci_save_state(pdev); +@@ -3301,23 +3810,40 @@ static bool nvme_pci_ctrl_is_dead(struct nvme_dev *dev) + struct pci_dev *pdev = to_pci_dev(dev->dev); + u32 csts; + ++ if (test_bit(ANS_HEALTH_FAILED, &dev->apple_ans_flags)) ++ return true; ++ + if (!pci_is_enabled(pdev) || !pci_device_is_present(pdev)) + return true; + if (pdev->error_state != pci_channel_io_normal) + return true; + +- csts = readl(dev->bar + NVME_REG_CSTS); ++ if (dev->ctrl.ops->reg_read32(&dev->ctrl, NVME_REG_CSTS, &csts)) ++ return true; + return (csts & NVME_CSTS_CFS) || !(csts & NVME_CSTS_RDY); + } + +-static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) ++static int __nvme_dev_disable(struct nvme_dev *dev, bool shutdown, ++ bool for_sleep) + { + enum nvme_ctrl_state state = nvme_ctrl_state(&dev->ctrl); + struct pci_dev *pdev = to_pci_dev(dev->dev); + bool dead; ++ int ret, sleep_ret = 0; + ++ /* Before queues and IRQs go away; nvme_reset_work() restarts it. */ ++ cancel_delayed_work_sync(&dev->ans_irq_rescue); + mutex_lock(&dev->shutdown_lock); + dead = nvme_pci_ctrl_is_dead(dev); ++ if (for_sleep && (dead || !dev->ctrl.queue_count)) { ++ dev_err(dev->dev, "Apple ANS: sleep refused: controller not live\n"); ++ sleep_ret = -EBUSY; ++ goto unlock; ++ } ++ if (dead && shutdown && kexec_in_progress && ++ dev->dma_ops == &nvme_pci_apple_ans_ops && ++ apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) ++ panic("Apple ANS: dead outgoing controller; refusing kexec"); + if (state == NVME_CTRL_LIVE || state == NVME_CTRL_RESETTING) { + if (pci_is_enabled(pdev)) + nvme_start_freeze(&dev->ctrl); +@@ -3325,15 +3851,54 @@ static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) + * Give the controller a chance to complete all entered requests + * if doing a safe shutdown. + */ +- if (!dead && shutdown) +- nvme_wait_freeze_timeout(&dev->ctrl); ++ if (!dead && shutdown) { ++ ret = nvme_wait_freeze_timeout(&dev->ctrl); ++ if (ret <= 0 && for_sleep) { ++ dev_err(dev->dev, "Apple ANS: sleep I/O drain timeout\n"); ++ nvme_unfreeze(&dev->ctrl); ++ sleep_ret = -EBUSY; ++ goto unlock; ++ } ++ if (ret <= 0 && kexec_in_progress && ++ dev->dma_ops == &nvme_pci_apple_ans_ops && ++ apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) ++ panic("Apple ANS: outgoing I/O did not drain; refusing kexec"); ++ } + } + + nvme_quiesce_io_queues(&dev->ctrl); + + if (!dead && dev->ctrl.queue_count > 0) { ++ if (for_sleep || (shutdown && kexec_in_progress && ++ dev->dma_ops == &nvme_pci_apple_ans_ops && ++ apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7)) { ++ nvme_sync_io_queues(&dev->ctrl); ++ ret = apple_ans_nvmmu_drain(dev->dev); ++ if (ret) { ++ nvme_apple_ans_dump(dev, "outgoing drain failed"); ++ if (for_sleep) { ++ dev_err(dev->dev, "Apple ANS: sleep NVMMU drain failed %d\n", ++ ret); ++ nvme_unquiesce_io_queues(&dev->ctrl); ++ nvme_unfreeze(&dev->ctrl); ++ sleep_ret = -EBUSY; ++ goto unlock; ++ } ++ panic("Apple ANS: outgoing drain failed %d; refusing kexec", ret); ++ } ++ } + nvme_delete_io_queues(dev); +- nvme_disable_ctrl(&dev->ctrl, shutdown); ++ if (shutdown && kexec_in_progress && dev->dma_ops && ++ dev->dma_ops->shutdown) ++ dev_info(dev->dev, "Apple ANS: kexec shutdown: starting NVMe SHN\n"); ++ if (for_sleep) ++ dev_info(dev->dev, "Apple ANS: sleep shutdown: starting NVMe SHN\n"); ++ ret = nvme_disable_ctrl(&dev->ctrl, shutdown); ++ if (for_sleep) ++ sleep_ret = dev->dma_ops->stop_for_sleep(dev, ret); ++ if (shutdown && kexec_in_progress && dev->dma_ops && ++ dev->dma_ops->shutdown) ++ dev->dma_ops->shutdown(dev, ret); + nvme_poll_irqdisable(&dev->queues[0]); + } + nvme_suspend_io_queues(dev); +@@ -3356,7 +3921,14 @@ static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) + if (dev->ctrl.admin_q && !blk_queue_dying(dev->ctrl.admin_q)) + nvme_unquiesce_admin_queue(&dev->ctrl); + } ++unlock: + mutex_unlock(&dev->shutdown_lock); ++ return sleep_ret; ++} ++ ++static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown) ++{ ++ __nvme_dev_disable(dev, shutdown, false); + } + + static int nvme_disable_prepare_reset(struct nvme_dev *dev, bool shutdown) +@@ -3393,16 +3965,32 @@ static void nvme_pci_free_ctrl(struct nvme_ctrl *ctrl) + struct nvme_dev *dev = to_nvme_dev(ctrl); + + nvme_free_tagset(dev); ++ if (dev->dma_ops && dev->dma_ops->exit) ++ dev->dma_ops->exit(dev); + put_device(dev->dev); + kfree(dev->queues); + kfree(dev); + } + ++static bool nvme_apple_ans_retry(struct nvme_dev *dev) ++{ ++ if (dev->dma_ops != &nvme_pci_apple_ans_ops || ++ apple_ans_boot_mode(dev->dev) == APPLE_ANS_ROUND7 || ++ nvme_state_terminal(&dev->ctrl) || ++ test_and_set_bit(ANS_RECOVERY_USED, &dev->apple_ans_flags)) ++ return false; ++ nvme_apple_ans_dump(dev, "health/enable failed before recovery"); ++ pr_flush(1000, false); ++ set_bit(ANS_RECOVERY_PENDING, &dev->apple_ans_flags); ++ return true; ++} ++ + static void nvme_reset_work(struct work_struct *work) + { + struct nvme_dev *dev = + container_of(work, struct nvme_dev, ctrl.reset_work); + bool was_suspend = !!(dev->ctrl.ctrl_config & NVME_CC_SHN_NORMAL); ++ bool unhealthy; + int result; + + if (nvme_ctrl_state(&dev->ctrl) != NVME_CTRL_RESETTING) { +@@ -3412,6 +4000,7 @@ static void nvme_reset_work(struct work_struct *work) + goto out; + } + ++retry: + /* + * If we're called to reset a live controller first shut it down before + * moving on. +@@ -3419,11 +4008,25 @@ static void nvme_reset_work(struct work_struct *work) + if (dev->ctrl.ctrl_config & NVME_CC_ENABLE) + nvme_dev_disable(dev, false); + nvme_sync_queues(&dev->ctrl); ++ if (test_and_clear_bit(ANS_RECOVERY_PENDING, &dev->apple_ans_flags)) { ++ pci_clear_master(to_pci_dev(dev->dev)); ++ result = nvme_pci_apple_ans_recover(dev); ++ if (result) ++ goto out; ++ clear_bit(ANS_HEALTH_FAILED, &dev->apple_ans_flags); ++ } + + mutex_lock(&dev->shutdown_lock); + result = nvme_pci_enable(dev); + if (result) + goto out_unlock; ++ if (!dev->ctrl.admin_q) { ++ /* First enable, deferred from nvme_probe() (Apple ANS) */ ++ result = nvme_alloc_admin_tag_set(&dev->ctrl, &dev->admin_tagset, ++ &nvme_mq_admin_ops, sizeof(struct nvme_iod)); ++ if (result) ++ goto out_unlock; ++ } + nvme_unquiesce_admin_queue(&dev->ctrl); + mutex_unlock(&dev->shutdown_lock); + +@@ -3431,7 +4034,8 @@ static void nvme_reset_work(struct work_struct *work) + * Introduce CONNECTING state from nvme-fc/rdma transports to mark the + * initializing procedure here. + */ +- if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_CONNECTING)) { ++ if (nvme_ctrl_state(&dev->ctrl) != NVME_CTRL_CONNECTING && ++ !nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_CONNECTING)) { + dev_warn(dev->ctrl.device, + "failed to mark controller CONNECTING\n"); + result = -EBUSY; +@@ -3441,6 +4045,7 @@ static void nvme_reset_work(struct work_struct *work) + result = nvme_init_ctrl_finish(&dev->ctrl, was_suspend); + if (result) + goto out; ++ nvme_pci_write_window_limits(dev); + + if (nvme_ctrl_meta_sgl_supported(&dev->ctrl)) + dev->ctrl.max_integrity_segments = NVME_MAX_META_SEGS; +@@ -3467,7 +4072,15 @@ static void nvme_reset_work(struct work_struct *work) + if (dev->online_queues > 1) { + nvme_dbbuf_set(dev); + nvme_unquiesce_io_queues(&dev->ctrl); +- nvme_wait_freeze(&dev->ctrl); ++ if (dev->dma_ops == &nvme_pci_apple_ans_ops && ++ apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) { ++ if (nvme_wait_freeze_timeout(&dev->ctrl) <= 0) { ++ result = -ETIMEDOUT; ++ goto out; ++ } ++ } else { ++ nvme_wait_freeze(&dev->ctrl); ++ } + if (!nvme_pci_update_nr_queues(dev)) + goto out; + nvme_unfreeze(&dev->ctrl); +@@ -3479,6 +4092,16 @@ static void nvme_reset_work(struct work_struct *work) + nvme_free_tagset(dev); + } + ++ if (dev->dma_ops == &nvme_pci_apple_ans_ops && ++ apple_ans_boot_mode(dev->dev) != APPLE_ANS_ROUND7) { ++ result = nvme_apple_ans_health(dev, &unhealthy); ++ if (result && unhealthy) ++ goto out; ++ if (result) ++ dev_warn(dev->dev, "Apple ANS: health gate inconclusive (%d); continuing namespace discovery without recovery\n", ++ result); ++ } ++ + /* + * If only admin queue live, keep it to do further investigation or + * recovery. +@@ -3491,11 +4114,17 @@ static void nvme_reset_work(struct work_struct *work) + } + + nvme_start_ctrl(&dev->ctrl); ++ nvme_apple_ans_irq_rescue_start(dev); + return; + + out_unlock: + mutex_unlock(&dev->shutdown_lock); + out: ++ if (nvme_apple_ans_retry(dev)) { ++ dev_warn(dev->dev, "Apple ANS: controller/admin/health bring-up failed %d; one full controller+port retry before namespace publication\n", ++ result); ++ goto retry; ++ } + /* + * Set state to deleting now to avoid blocking nvme_wait_reset(), which + * may be holding this pci_dev's device lock. +@@ -3512,16 +4141,35 @@ static void nvme_reset_work(struct work_struct *work) + + static int nvme_pci_reg_read32(struct nvme_ctrl *ctrl, u32 off, u32 *val) + { +- *val = readl(to_nvme_dev(ctrl)->bar + off); ++ struct nvme_dev *dev = to_nvme_dev(ctrl); ++ ++ if (off == NVME_REG_CSTS && dev->dma_ops && dev->dma_ops->read_csts) ++ return dev->dma_ops->read_csts(dev, val); ++ *val = readl(dev->bar + off); + return 0; + } + + static int nvme_pci_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val) + { +- writel(val, to_nvme_dev(ctrl)->bar + off); ++ struct nvme_dev *dev = to_nvme_dev(ctrl); ++ ++ if (off == NVME_REG_CC && dev->dma_ops && dev->dma_ops->write_cc) ++ return dev->dma_ops->write_cc(dev, val); ++ writel(val, dev->bar + off); + return 0; + } + ++static bool nvme_pci_ns_is_readonly(struct nvme_ctrl *ctrl, u32 nsid) ++{ ++ struct nvme_dev *dev = to_nvme_dev(ctrl); ++ ++ /* NVME_CTRL_FORCE_RO is off with a write window: only its NSID is rw */ ++ if (nvme_pci_write_window_active() && nsid != write_window.nsid) ++ return true; ++ return dev->dma_ops && dev->dma_ops->ns_readonly && ++ dev->dma_ops->ns_readonly(dev, nsid); ++} ++ + static int nvme_pci_reg_read64(struct nvme_ctrl *ctrl, u32 off, u64 *val) + { + *val = lo_hi_readq(to_nvme_dev(ctrl)->bar + off); +@@ -3579,6 +4227,7 @@ static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = { + .print_device_info = nvme_pci_print_device_info, + .supports_pci_p2pdma = nvme_pci_supports_pci_p2pdma, + .get_virt_boundary = nvme_pci_get_virt_boundary, ++ .ns_is_readonly = nvme_pci_ns_is_readonly, + }; + + static int nvme_dev_map(struct nvme_dev *dev) +@@ -3700,6 +4349,7 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev, + if (!dev) + return ERR_PTR(-ENOMEM); + INIT_WORK(&dev->ctrl.reset_work, nvme_reset_work); ++ INIT_DELAYED_WORK(&dev->ans_irq_rescue, nvme_apple_ans_irq_rescue_work); + mutex_init(&dev->shutdown_lock); + + dev->nr_write_queues = write_queues; +@@ -3729,11 +4379,34 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev, + quirks |= qentry->enabled_quirks; + quirks &= ~qentry->disabled_quirks; + } ++ if (nvme_pci_apple_ans_match(pdev)) { ++ dev->dma_ops = &nvme_pci_apple_ans_ops; ++ quirks |= NVME_QUIRK_SINGLE_VECTOR | NVME_QUIRK_SHARED_TAGS; ++ } ++ if (dev->dma_ops) { ++ ret = dev->dma_ops->init(dev, node); ++ if (ret) ++ goto out_put_device; ++ } + ret = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops, + quirks); + if (ret) + goto out_put_device; + ++ if (IS_ENABLED(CONFIG_NVME_PCI_READ_ONLY) && ++ nvme_pci_write_window_active()) { ++ /* Namespaces other than the window's: nvme_pci_ns_is_readonly() */ ++ dev->ctrl.quirks |= NVME_QUIRK_DISABLE_WRITE_ZEROES; ++ dev_warn(&pdev->dev, ++ "read-only except NVMe Write to NSID %u LBAs %llu-%llu (nvme.write_window): no trim, no write zeroes, no destructive admin commands\n", ++ write_window.nsid, write_window.first, ++ write_window.last); ++ } else if (IS_ENABLED(CONFIG_NVME_PCI_READ_ONLY)) { ++ set_bit(NVME_CTRL_FORCE_RO, &dev->ctrl.flags); ++ dev_info(&pdev->dev, ++ "read-only: media writes and destructive admin commands are refused (CONFIG_NVME_PCI_READ_ONLY)\n"); ++ } ++ + if (dev->ctrl.quirks & NVME_QUIRK_DMA_ADDRESS_BITS_48) + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(48)); + else +@@ -3748,11 +4421,17 @@ static struct nvme_dev *nvme_pci_alloc_dev(struct pci_dev *pdev, + dev->ctrl.max_hw_sectors = min_t(u32, + NVME_MAX_BYTES >> SECTOR_SHIFT, + dma_opt_mapping_size(&pdev->dev) >> 9); ++ if (dev->dma_ops && dev->dma_ops->max_hw_sectors) ++ dev->ctrl.max_hw_sectors = ++ dev->dma_ops->max_hw_sectors(dev, ++ dev->ctrl.max_hw_sectors); + dev->ctrl.max_segments = NVME_MAX_SEGS; + dev->ctrl.max_integrity_segments = 1; + return dev; + + out_put_device: ++ if (dev->dma_ops && dev->dma_ops->exit) ++ dev->dma_ops->exit(dev); + put_device(dev->dev); + kfree(dev->queues); + out_free_dev: +@@ -3765,6 +4444,10 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) + struct nvme_dev *dev; + int result = -ENOMEM; + ++ if (!nvme_pci_apple_ans_probe_allowed(pdev)) ++ return dev_err_probe(&pdev->dev, -EPROBE_DEFER, ++ "Apple ANS: held for initramfs USB diagnostics\n"); ++ + dev = nvme_pci_alloc_dev(pdev, id); + if (IS_ERR(dev)) + return PTR_ERR(dev); +@@ -3783,6 +4466,26 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) + + dev_info(dev->ctrl.device, "pci function %s\n", dev_name(&pdev->dev)); + ++ /* ++ * Apple ANS: do the first enable from reset_work on nvme_reset_wq, like ++ * nvme-pci did before it went synchronous. The probe itself runs in the ++ * async domain that kernel_init() waits for before starting /init, so a ++ * bring-up that hangs or times out (CAP.TO can be minutes) would ++ * otherwise keep the shell from ever coming up. ++ */ ++ if (dev->dma_ops == &nvme_pci_apple_ans_ops) { ++ if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_RESETTING)) { ++ result = -EBUSY; ++ goto out_release_iod_mempool; ++ } ++ pci_set_drvdata(pdev, dev); ++ dev_info(dev->ctrl.device, ++ "Apple ANS: controller bring-up continues in the background\n"); ++ queue_work(nvme_reset_wq, &dev->ctrl.reset_work); ++ nvme_put_ctrl(&dev->ctrl); ++ return 0; ++ } ++ + result = nvme_pci_enable(dev); + if (result) + goto out_release_iod_mempool; +@@ -3806,6 +4509,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) + result = nvme_init_ctrl_finish(&dev->ctrl, false); + if (result) + goto out_disable; ++ nvme_pci_write_window_limits(dev); + + if (nvme_ctrl_meta_sgl_supported(&dev->ctrl)) + dev->ctrl.max_integrity_segments = NVME_MAX_META_SEGS; +@@ -3843,6 +4547,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) + pci_set_drvdata(pdev, dev); + + nvme_start_ctrl(&dev->ctrl); ++ nvme_apple_ans_irq_rescue_start(dev); + nvme_put_ctrl(&dev->ctrl); + flush_work(&dev->ctrl.scan_work); + return 0; +@@ -3953,6 +4658,32 @@ reset: + return nvme_try_sched_reset(ctrl); + } + ++/* An error must abort PM and restart this device: PM will not resume a ++ * device whose own suspend callback failed. Never wait indefinitely for a ++ * reset already in progress just to start sleeping. ++ */ ++static int nvme_apple_ans_suspend(struct nvme_dev *ndev) ++{ ++ int ret; ++ ++ ret = apple_ans_set_sleep_stopped(ndev->dev, false); ++ if (ret) ++ return ret; ++ if (nvme_ctrl_state(&ndev->ctrl) != NVME_CTRL_LIVE || ++ !nvme_change_ctrl_state(&ndev->ctrl, NVME_CTRL_RESETTING)) ++ return -EBUSY; ++ ret = __nvme_dev_disable(ndev, true, true); ++ if (ret) { ++ dev_err(ndev->dev, "Apple ANS: sleep refused %d; scheduling controller restore\n", ++ ret); ++ if (nvme_try_sched_reset(&ndev->ctrl)) ++ dev_err(ndev->dev, "Apple ANS: sleep abort restore could not be queued\n"); ++ return -EBUSY; ++ } ++ dev_info(ndev->dev, "Apple ANS: sleep stop certified; host may power port down\n"); ++ return 0; ++} ++ + static int nvme_suspend(struct device *dev) + { + struct pci_dev *pdev = to_pci_dev(dev); +@@ -3961,6 +4692,9 @@ static int nvme_suspend(struct device *dev) + int ret = -EBUSY; + + ndev->last_ps = U32_MAX; ++ if (of_machine_is_compatible("apple,j71t") && ++ ndev->dma_ops && ndev->dma_ops->stop_for_sleep) ++ return nvme_apple_ans_suspend(ndev); + + /* + * The platform does not remove power for a kernel managed suspend so +@@ -4295,6 +5029,9 @@ static const struct pci_device_id nvme_id_table[] = { + */ + .driver_data = NVME_QUIRK_SINGLE_VECTOR | + NVME_QUIRK_QDEPTH_ONE }, ++ { PCI_DEVICE(PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_ANS_NVME), ++ .driver_data = NVME_QUIRK_SINGLE_VECTOR | ++ NVME_QUIRK_SHARED_TAGS }, + { PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2003) }, + { PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2005), + .driver_data = NVME_QUIRK_SINGLE_VECTOR | +diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig +index d246bbe37948e2a4eb3b941463132f91caa0e66f..186e437ebe06246d3e23f76f2155adc93ab8f006 100644 +--- a/drivers/pci/controller/Kconfig ++++ b/drivers/pci/controller/Kconfig +@@ -54,6 +54,18 @@ config PCIE_APPLE + + If unsure, say Y if you have an Apple Silicon system. + ++config PCIE_APPLE_H9P ++ bool "Apple A9/A10 PCIe controller" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF ++ depends on PCI_MSI ++ select PCI_HOST_COMMON ++ help ++ Say Y here if you want to enable PCIe controller support on Apple ++ A9 (S8003) and A10 SoCs. The controller provides the root ports used by internal ++ NVMe storage and Wi-Fi devices and handles their link setup, MSI ++ routing, and storage-specific DMA translation. ++ + config PCI_VERSATILE + bool "ARM Versatile PB PCI controller" + depends on ARCH_VERSATILE || COMPILE_TEST +diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile +index d478743b514289b495272a84c1f558ef7500780a..925a08c5f67fc50e1bf6592dc89ddd5972d8c278 100644 +--- a/drivers/pci/controller/Makefile ++++ b/drivers/pci/controller/Makefile +@@ -40,6 +40,7 @@ obj-$(CONFIG_PCIE_BRCMSTB) += pcie-brcmstb.o + obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o + obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o + obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o ++obj-$(CONFIG_PCIE_APPLE_H9P) += pcie-apple-h9p.o + obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o + obj-$(CONFIG_PCIE_ASPEED) += pcie-aspeed.o + +diff --git a/drivers/pci/controller/pcie-apple-h9p.c b/drivers/pci/controller/pcie-apple-h9p.c +new file mode 100644 +index 0000000000000000000000000000000000000000..a4c736d7019971351e9973bef167dcfe624dd216 +--- /dev/null ++++ b/drivers/pci/controller/pcie-apple-h9p.c +@@ -0,0 +1,3898 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCIe host bridge driver for Apple H8P/H9P (S8003, T8010) SoCs. ++ * ++ * The controller exposes an ECAM-compatible root complex after the SoC-specific ++ * power, clock and PHY sequence has brought a port out of reset. The hardware ++ * differs enough from the Apple Silicon PCIe controller to keep the early H9P ++ * bring-up sequence separate, while still using the generic PCI host bridge ++ * and MSI subsystems. ++ * ++ * Copyright (C) 2020 Corellium LLC ++ * Copyright (C) 2026 Paul Praschl ++ */ ++ ++#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 ++ ++#include ++ ++#include "../pci.h" ++#include "pci-host-common.h" ++ ++#define H9P_NUM_PORTS 4 ++#define H9P_NUM_MSI 32 ++#define H9P_MSI_PER_PORT (H9P_NUM_MSI / H9P_NUM_PORTS) ++ ++#define H9P_CFG_PORT_STRIDE 0x8000 ++#define H9P_CFG_PORT_MISC 0x08e0 ++ ++#define H9P_RC_COMMON_CTL0 0x0004 ++#define H9P_RC_COMMON_CTL1 0x0014 ++#define H9P_RC_COMMON_CTL2 0x0024 ++#define H9P_RC_COMMON_CTL3 0x0034 ++#define H9P_RC_COMMON_CTL_ENABLE BIT(0) ++#define H9P_RC_COMMON_CTL_INIT BIT(4) ++#define H9P_RC_COMMON_CTL_LANE_CFG GENMASK(3, 0) ++#define H9P_RC_PORT_STRIDE 0x0080 ++#define H9P_RC_PORTSTAT(port) (0x0100 + (port) * H9P_RC_PORT_STRIDE) ++#define H9P_RC_PORT_CTL0(port) (0x0100 + (port) * H9P_RC_PORT_STRIDE) ++#define H9P_RC_PORT_CTL1(port) (0x0124 + (port) * H9P_RC_PORT_STRIDE) ++#define H9P_RC_PORT_CTL2(port) (0x0134 + (port) * H9P_RC_PORT_STRIDE) ++#define H9P_RC_COMMON_STAT 0x0028 ++#define H9P_RC_COMMON_STAT_INIT_DONE BIT(4) ++#define H9P_RC_COMMON_STAT_READY BIT(0) ++#define H9P_RC_PORT_LINK_RATE(port) (0x4020 + (port) * 0x0040) ++#define H9P_PHY_PORTMASK 0x000c ++ ++#define H9P_PHY_IP_EQ_COMMON0 0x0180 ++#define H9P_PHY_IP_EQ_COMMON1 0x0184 ++#define H9P_PHY_IP_EQ_TIME0 0x0090 ++#define H9P_PHY_IP_EQ_TIME1 0x0098 ++#define H9P_PHY_IP_PORT_STRIDE 0x0800 ++#define H9P_PHY_IP_PORT(port, reg) ((reg) + (port) * H9P_PHY_IP_PORT_STRIDE) ++#define H9P_PHY_IP_PORT_EQ_CTL 0x10088 ++#define H9P_PHY_IP_PORT_IDLE 0x10784 ++#define H9P_PHY_IP_PORT_EQ_PRESET 0x10004 ++#define H9P_PHY_IP_PORT_RX_CTL0 0x20788 ++#define H9P_PHY_IP_PORT_RX_CTL1 0x207a0 ++#define H9P_PHY_IP_PORT_RX_CTL2 0x207a8 ++#define H9P_PHY_IP_PORT_RX_CTL3 0x20400 ++#define H9P_PHY_IP_PORT_TIMER0 0x2009c ++#define H9P_PHY_IP_PORT_TIMER1 0x200dc ++#define H9P_PHY_IP_PORT_TIMER2 0x200a0 ++#define H9P_PHY_IP_PORT_TIMER3 0x200e0 ++#define H9P_PHY_IP_PORT_TIMER4 0x200a4 ++#define H9P_PHY_IP_PORT_TIMER5 0x200e4 ++#define H9P_PHY_IP_PORT_CLEAR0 0x20330 ++#define H9P_PHY_IP_PORT_CLEAR1 0x20340 ++#define H9P_PHY_IP_PORT_CLEAR2 0x20350 ++ ++#define H9P_PORT_LTSSMCTL 0x0080 ++#define H9P_PORT_LTSSM_ENABLE BIT(0) ++#define H9P_PORT_IRQSTAT 0x0100 ++#define H9P_PORT_IRQMASK 0x0104 ++#define H9P_PORT_IRQMASK_PRE_LINK 0xff002fff ++#define H9P_PORT_IRQSTAT_PRE_LINK 0x00ffd000 ++#define H9P_PORT_IRQMASK_LINK_UP 0xff002f0f ++#define H9P_PORT_PWRCTL 0x0124 ++#define H9P_PORT_PWRCTL_INIT 0x31 ++#define H9P_PORT_MSIVECBASE 0x0128 ++#define H9P_PORT_ENABLE 0x0140 ++#define H9P_PORT_ENABLE_APPLE BIT(31) ++#define H9P_PORT_LINKSTS 0x0208 ++#define H9P_PORT_LINKSTS_LTSSM GENMASK(13, 8) ++#define H9P_PORT_LTSSM_DETECT 0x11 ++#define H9P_PORT_LTSSM_L0 0x14 ++ ++#define H9P_LINK_SPEED_2_5GT 1 ++#define H9P_LINK_SPEED_5GT 2 ++#define H9P_LINK_SPEED_8GT 3 ++ ++/* ++ * Ports with a powered endpoint (pwren-gpios, e.g. the Wi-Fi chip) come up ++ * from a work item after probe, so port 0 never waits for their power GPIO ++ * provider. Retry the GPIO every 500 ms for at most 30 s. ++ */ ++#define H9P_LATE_PORT_RETRY_MS 500 ++#define H9P_LATE_PORT_TRIES 60 ++/* Endpoint power: off this long first, then settle this long (A10 J172) */ ++#define H9P_PWREN_OFF_MS 100 ++#define H9P_PWREN_SETTLE_MS 100 ++/* Link attempts per late port (power-cycled in between) */ ++#define H9P_LATE_PORT_LINK_TRIES 2 ++ ++/* ++ * The DT keeps ASPM L1 off on every port (aspm-no-l1): iOS pairs L1 with ++ * L1SS/LTR/CLKREQ# and refclk gating, and a link that can't leave L1 takes ++ * the CPU with it. This lets a boot opt in per port for power testing ++ * (e.g. 0x2 = Wi-Fi). L1 then follows the PCI ASPM policy: ++ * /sys/module/pcie_aspm/parameters/policy = default (L1), ++ * powersupersave (+ L1 substates) or performance (off again). ++ */ ++static unsigned int aspm_l1_ports; ++module_param(aspm_l1_ports, uint, 0444); ++MODULE_PARM_DESC(aspm_l1_ports, ++ "Bitmask of root ports that may use ASPM L1 despite the DT's aspm-no-l1 (default 0; 0x2 = Wi-Fi)"); ++ ++/* ++ * System sleep (s2idle). Untested on hardware, so it is refused unless a ++ * boot opts in; the levels add one risk at a time: ++ * 1: every port stays powered and linked. NVMe shuts down (CC.SHN) and ++ * is re-enabled by its reset path, brcmfmac does its D3 handshake. ++ * Resume checks each link and the NVMMU before any endpoint is touched. ++ * 2: as 1, but ports with an endpoint power GPIO (Wi-Fi) take the iOS ++ * port sleep: PME_Turn_Off, L2, PERST#, port off. The chip stays ++ * powered (REG_ON is on the PMU, out of reach in the noirq phase); its ++ * link is retrained on resume and brcmfmac reloads the firmware. ++ * 3: as 2 for every port, NVMe included (experimental). ++ * Endpoints and root ports stay in D0 at every level: D3hot would take ++ * the link to L1, which these ports are not trusted with (aspm-no-l1). ++ * A port that doesn't come back is cut off (config space and drivers see ++ * a disconnected device), so nothing touches a dead link. ++ */ ++#define H9P_SLEEP_OFF 0 ++#define H9P_SLEEP_KEEP_LINKS 1 ++#define H9P_SLEEP_L2_POWERED 2 ++#define H9P_SLEEP_L2_ALL 3 ++static bool crash_quiesce_supported = true; ++module_param(crash_quiesce_supported, bool, 0444); ++MODULE_PARM_DESC(crash_quiesce_supported, "Capture-kernel host-only ANS quiesce is available"); ++ ++static unsigned int h9p_suspend; ++module_param_named(suspend, h9p_suspend, uint, 0644); ++MODULE_PARM_DESC(suspend, ++ "System sleep: 0 refuse (default), 1 keep links up, 2 also put Wi-Fi's port in L2, 3 every port in L2 (experimental)"); ++ ++/* Orchestrator-only fault injection; never enabled by a normal boot. */ ++static bool panic_resume_once; ++module_param(panic_resume_once, bool, 0600); ++MODULE_PARM_DESC(panic_resume_once, ++ "j71t: deliberately panic before one PCIe resume, only with kdump armed"); ++ ++static void apple_h9p_crash_resume_test(struct device *dev) ++{ ++ if (!of_machine_is_compatible("apple,j71t") || ++ !READ_ONCE(panic_resume_once)) ++ return; ++ WRITE_ONCE(panic_resume_once, false); ++ if (!kexec_crash_loaded()) { ++ dev_warn(dev, "resume panic test refused: no capture kernel armed\n"); ++ return; ++ } ++ panic("j71t: deliberate panic before PCIe resume"); ++} ++ ++/* Retrain attempts per port on resume (PERST# asserted in between) */ ++#define H9P_WAKE_LINK_TRIES 2 ++ ++/* ++ * S8003 (A9, TSMC). From the iOS 16 AppleS800xPCIe/AppleS8003PCIe kexts; the ++ * T8010 sequence above does not match it (status register, port registers, ++ * link status, phy-ip lane mapping). See docs/pcie/kernelcache-notes.md. ++ */ ++#define S8003_RC_LANE_CFG 0x0004 /* 0x10 | lane-cfg */ ++#define S8003_RC_NVME_MODE0 0x0014 ++#define S8003_RC_NVME_MODE1 0x0024 ++#define S8003_RC_STAT 0x002c ++#define S8003_RC_STAT_COMMON_READY BIT(0) ++#define S8003_RC_STAT_PORT_READY BIT(4) ++#define S8003_RC_COMMON_DONE 0x0038 ++#define S8003_RC_PORT_CTL(port) (0x0100 + (port) * 0x80) ++#define S8003_RC_PORT_CTL_ENABLE BIT(0) ++#define S8003_RC_PORT_CTL_BIT8 BIT(8) ++#define S8003_RC_PORT_PWR(port) (0x0114 + (port) * 0x80) ++#define S8003_RC_PORT_PWR_ON BIT(0) ++#define S8003_RC_PORT_REFCLK_NOGATE BIT(8) ++#define S8003_RC_PORT_REFCLK(port) (0x0124 + (port) * 0x80) ++#define S8003_RC_PORT_REFCLK_ON BIT(0) ++#define S8003_RC_PORT_READY(port) (0x012c + (port) * 0x80) ++#define S8003_RC_PORT_LSCNT(port) (0x4020 + (port) * 0x40) ++ ++#define S8003_PORT_LINKSTS 0x0088 ++#define S8003_PORT_LINKSTS_UP BIT(0) ++#define S8003_PORT_MSICTL 0x0124 /* 1 | log2(vectors) << 4 */ ++#define S8003_PORT_LINKPMGRSTS 0x0210 ++#define S8003_PORT_LINKCDMSTS 0x0804 ++#define S8003_PORT_LINKCDMSTS_LTSSM GENMASK(14, 9) ++#define S8003_LTSSM_L0 0x11 ++ ++/* ++ * Port sleep, as iOS AppleS800xPCIePort::disableGated: write 0x31 to send ++ * PME_Turn_Off; bit 0 clears on PME_TO_Ack, bit 5 flags a hardware timeout. ++ * iOS polls the ack every 1 ms for 10 ms and continues either way, then ++ * waits for L2 (link status bit 6). ++ */ ++#define S8003_PORT_LINKSTS_L2 BIT(6) ++#define S8003_PORT_PMETO 0x008c ++#define S8003_PORT_PMETO_SEND 0x31 ++#define S8003_PORT_PMETO_PENDING BIT(0) ++#define S8003_PORT_PMETO_HW_TIMEOUT BIT(5) ++#define S8003_PME_TO_ACK_TIMEOUT_US 10000 ++#define S8003_L2_TIMEOUT_US 10000 ++/* iOS clears this port event once the link is down */ ++#define S8003_PORT_IRQSTAT_LINK_DOWN BIT(12) ++/* ADT perst-to-refclk-delay-us */ ++#define S8003_PERST_TO_REFCLK_US 60 ++ ++/* NVMMU invalidate FIFO status in the port 0 block (invalidate-fifo-workaround 1) */ ++#define S8003_PORT_NVMMU_INV_FIFO 0x022c ++#define S8003_PORT_NVMMU_INV_FIFO_EMPTY BIT(0) ++#define S8003_NVMMU_INV_FIFO_TIMEOUT_US 20000 ++/* invalidate-fifo-workaround 2: iOS nvme-invalidate-delay default */ ++#define S8003_NVMMU_INV_DELAY_US 8 ++ ++/* Block sizes, to bounds-check tunable offsets */ ++#define H9P_RC_SIZE 0x8000 ++#define H9P_PHY_SIZE 0x4000 ++#define H9P_PORT_SIZE 0x4000 ++#define H9P_CFG_SIZE 0x1000 ++ ++/* ADT pci-bridge t-refclk-to-perst (both bridges on j71t) */ ++#define S8003_T_REFCLK_TO_PERST_US 100 ++/* Hold endpoint reset across retained-state cleanup and clock restart. */ ++#define S8003_PORT_RESET_MS 100 ++#define S8003_PORT_LINK_TRIES 2 ++ ++/* ANS-only deferral is too late to diagnose a stall in the host probe. */ ++static bool h9p_probe = !IS_ENABLED(CONFIG_NVME_APPLE_ANS_EARLY_USB); ++module_param_named(probe, h9p_probe, bool, 0644); ++MODULE_PARM_DESC(probe, "Allow j71t PCIe host probe after initramfs USB rescue is ready"); ++ ++#define H9P_NVMMU_TCB_CTRL 0x0004 ++#define H9P_NVMMU_TCB_CTRL_INIT 0x10000 ++#define H9P_NVMMU_TCB_BASE_LO 0x0008 ++#define H9P_NVMMU_TCB_BASE_HI 0x000c ++/* iOS (A9) puts a zeroed 4K scratch page here, not a TCB table */ ++#define H9P_NVMMU_TCB_TABLE_LO 0x0010 ++#define H9P_NVMMU_TCB_TABLE_HI 0x0014 ++#define H9P_NVMMU_SART_CTRL 0x0020 ++#define H9P_NVMMU_SART_VA_BASE 0x0024 ++#define H9P_NVMMU_SART_VA_END 0x0028 ++#define H9P_NVMMU_SART_PA_BASE 0x002c ++/* Error registers, read by the iOS handler (AppleNVMeMMU, 0xfffffff005eea2fc) */ ++#define H9P_NVMMU_ERR_INFO 0x0030 /* [29:23] tag */ ++#define H9P_NVMMU_ERR_STATUS 0x0034 /* one bit per error type */ ++#define H9P_NVMMU_ERR_PRP 0x0038 ++#define H9P_NVMMU_ERR_ADDR_LO 0x003c ++#define H9P_NVMMU_ERR_CRYPTO 0x0040 ++#define H9P_NVMMU_ERR_ADDR_HI 0x0044 ++#define H9P_NVMMU_ERR_FETCH 0x0048 ++ ++/* S5L8960X DART registers, for the read-only host dump */ ++#define H9P_DART_TCR 0x000c ++#define H9P_DART_ERROR 0x0010 ++#define H9P_DART_ERROR_FLAG BIT(31) ++#define H9P_DART_ERROR_ADDR 0x001c ++#define H9P_DART_DIAG 0x0020 ++#define H9P_DART_REFLECTOR 0x0024 ++#define H9P_DART_FETCH 0x0030 ++#define H9P_DART_TTBR(i) (0x0040 + (i) * 4) ++ ++#define H9P_NVMMU_TCB_BYTES 0x80 ++#define H9P_NVMMU_TCB_COUNT 128 ++#define H9P_NVMMU_TCB_DWORDS (H9P_NVMMU_TCB_BYTES / sizeof(u32)) ++#define H9P_NVMMU_SGL_WORDS APPLE_ANS_NVMMU_MAX_PAGES ++#define H9P_NVMMU_FLATDMA_BASE 0x40000000ULL ++#define H9P_NVMMU_FLATDMA_STRIDE SZ_8M ++#define H9P_NVMMU_SART_ALIGNMENT SZ_1M ++/* TCB flags, device view: 0x100 device reads memory, 0x200 device writes it */ ++#define H9P_NVMMU_TCB_READ 0x100 ++#define H9P_NVMMU_TCB_WRITE 0x200 ++#define H9P_NVMMU_TCB_NPAGES 1 ++ ++#define H9P_DEFAULT_MSI_DOORBELL 0xbffff000ULL ++ ++struct apple_h9p_pcie; ++ ++struct apple_h9p_hw { ++ /* TSMC PCIe PHY with the "phy-ip" tuning block (T8010, S8003) */ ++ bool has_phy_ip; ++ /* Port 0 target speed when its node has no max-link-speed */ ++ u32 port0_default_speed; ++ /* NVMMU TCB_CTRL bit that stays set while the TCB init runs */ ++ u32 nvmmu_init_busy; ++ /* Accepted apple,lane-cfg values (bitmask) */ ++ u32 lane_cfgs; ++ /* Port-block link status register and its link-up bit (0: unknown) */ ++ u32 linksts; ++ u32 linksts_up; ++ int (*init_common)(struct apple_h9p_pcie *pcie); ++ int (*setup_port)(struct apple_h9p_pcie *pcie, unsigned int port); ++ /* Link to L2 and port off for system sleep; NULL: links stay up */ ++ void (*port_sleep)(struct apple_h9p_pcie *pcie, unsigned int port); ++ /* Refclk and port power off, PERST# already asserted (optional) */ ++ void (*port_off)(struct apple_h9p_pcie *pcie, unsigned int port); ++}; ++ ++/* Round 8 is a supervised trial; round7 retains the previous sequence. */ ++static enum apple_ans_boot_mode ans_boot_mode = APPLE_ANS_HANDOFF; ++static const char * const ans_boot_modes[] = { "round7", "handoff", "full-cycle" }; ++ ++static int ans_boot_mode_set(const char *val, const struct kernel_param *kp) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ans_boot_modes); i++) { ++ if (!sysfs_streq(val, ans_boot_modes[i])) ++ continue; ++ *(enum apple_ans_boot_mode *)kp->arg = i; ++ return 0; ++ } ++ return -EINVAL; ++} ++ ++static int ans_boot_mode_get(char *buf, const struct kernel_param *kp) ++{ ++ return sysfs_emit(buf, "%s\n", ++ ans_boot_modes[*(enum apple_ans_boot_mode *)kp->arg]); ++} ++ ++static const struct kernel_param_ops ans_boot_mode_ops = { ++ .set = ans_boot_mode_set, ++ .get = ans_boot_mode_get, ++}; ++module_param_cb(ans_boot_mode, &ans_boot_mode_ops, &ans_boot_mode, 0444); ++MODULE_PARM_DESC(ans_boot_mode, "j71t ANS warm boot: handoff (trial default), full-cycle, round7"); ++ ++/* Supervised alternative to clock removal; full-cycle remains the control. */ ++static bool ans_keep_power; ++module_param(ans_keep_power, bool, 0444); ++MODULE_PARM_DESC(ans_keep_power, ++ "j71t handoff: retain port 0 power/refclk under PERST across kexec (default false)"); ++ ++struct apple_h9p_nvmmu { ++ struct apple_h9p_pcie *pcie; ++ void __iomem *base; ++ bool ready; ++ bool active; /* iOS setActive(false) only records state; no MMIO */ ++ bool sart; ++ bool poisoned; /* failed invalidate: never reuse a cached translation */ ++ /* ADT invalidate-fifo-workaround: 0 none, 1 poll FIFO, 2 delay */ ++ u32 inv_fifo; ++ /* Serializes invalidates (FIFO poll + write) across CPUs */ ++ spinlock_t inv_lock; ++ /* Tags mapped since their last invalidate */ ++ DECLARE_BITMAP(used, APPLE_ANS_NVMMU_MAX_REQS); ++ u64 pa_base; ++ u32 va_base; ++ u32 size; ++ void *tcb; ++ void *tcb_table; ++ void *tcb_sgl; ++ size_t tcb_size; ++ size_t tcb_table_size; ++ size_t tcb_sgl_size; ++ dma_addr_t tcb_dma; ++ dma_addr_t tcb_table_dma; ++ dma_addr_t tcb_sgl_dma; ++ /* ++ * Port 0's DART, mapped here only to read its status for the ANS ++ * bring-up diagnostics. apple-dart owns it. ++ */ ++ void __iomem *dart; ++ u64 dart_base; /* its apple,dma-range start */ ++}; ++ ++struct apple_h9p_pcie { ++ struct device *dev; ++ struct platform_device *pdev; ++ struct pci_host_bridge *bridge; ++ struct pci_config_window *cfgwin; ++ const struct apple_h9p_hw *hw; ++ ++ void __iomem *base_config; ++ void __iomem *base_rc; ++ void __iomem *base_phy; ++ void __iomem *base_phy_ip; ++ void __iomem *base_port[H9P_NUM_PORTS]; ++ ++ struct clk_bulk_data clks[3]; ++ struct device_node *port_nodes[H9P_NUM_PORTS]; ++ struct gpio_desc *perst[H9P_NUM_PORTS]; ++ struct gpio_desc *clkreq[H9P_NUM_PORTS]; ++ u32 max_speed[H9P_NUM_PORTS]; ++ bool nvme_mode[H9P_NUM_PORTS]; ++ struct gpio_descs *devpwr; ++ struct pinctrl *pinctrl; ++ u32 enabled_ports; ++ /* Not up yet: brought up by late_work once their power GPIO exists */ ++ u32 late_ports; ++ struct gpio_desc *pwren[H9P_NUM_PORTS]; ++ struct gpio_desc *devwake[H9P_NUM_PORTS]; ++ struct delayed_work late_work; ++ unsigned int late_tries; ++ u32 lane_cfg; ++ ++ struct apple_h9p_nvmmu nvmmu; ++ enum apple_ans_boot_mode ans_mode; ++ bool ans_boot_retry_used; ++ bool ans_deep_recovery; ++ bool ans_gen1; ++ bool ans_pre_enumeration; ++ u16 link_cap[H9P_NUM_PORTS]; ++ bool ans_rescue_allowed; ++ unsigned int ans_rescue_attempts; ++ int ans_rescue_result; ++ ++ struct device **pd_dev; ++ struct device_link **pd_link; ++ int pd_count; ++ ++ /* System sleep, see h9p_suspend */ ++ bool ans_sleep_stopped; /* endpoint certified EN/SHN/RDY/BME=0 */ ++ u32 sleep_ports; /* chosen at prepare: go to L2 */ ++ u32 resume_ports; /* linked at suspend_noirq: bring back */ ++ u32 down_ports; /* link down on purpose or dead: no config access below */ ++ struct pci_dev *pinned[8]; /* held in D0 over the sleep */ ++ unsigned int npinned; ++ bool pin_overflow; ++ ++ DECLARE_BITMAP(used_msi[H9P_NUM_PORTS], H9P_MSI_PER_PORT); ++ u64 msi_doorbell; ++ /* Protects the per-port MSI allocation bitmaps. */ ++ spinlock_t used_msi_lock; ++ struct irq_domain *irq_dom; ++ struct irq_fwspec msi_fwspec; ++ u32 nvecs; ++}; ++ ++static inline void h9p_rmw(void __iomem *addr, u32 clear, u32 set) ++{ ++ writel((readl(addr) & ~clear) | set, addr); ++} ++ ++static inline void h9p_rmww(void __iomem *addr, u16 clear, u16 set) ++{ ++ writew((readw(addr) & ~clear) | set, addr); ++} ++ ++static inline void h9p_writel_flush(u32 value, void __iomem *addr) ++{ ++ writel(value, addr); ++ readl(addr); ++} ++ ++static void apple_h9p_pcie_detach_genpd(struct apple_h9p_pcie *pcie) ++{ ++ int i; ++ ++ for (i = pcie->pd_count - 1; i >= 0; i--) { ++ if (pcie->pd_link[i]) ++ device_link_del(pcie->pd_link[i]); ++ if (!IS_ERR_OR_NULL(pcie->pd_dev[i])) ++ dev_pm_domain_detach(pcie->pd_dev[i], true); ++ } ++} ++ ++static int apple_h9p_pcie_attach_genpd(struct apple_h9p_pcie *pcie) ++{ ++ struct device *dev = pcie->dev; ++ int i; ++ ++ pcie->pd_count = of_count_phandle_with_args(dev->of_node, ++ "power-domains", ++ "#power-domain-cells"); ++ if (pcie->pd_count <= 1) ++ return 0; ++ ++ pcie->pd_dev = devm_kcalloc(dev, pcie->pd_count, ++ sizeof(*pcie->pd_dev), GFP_KERNEL); ++ if (!pcie->pd_dev) ++ return -ENOMEM; ++ ++ pcie->pd_link = devm_kcalloc(dev, pcie->pd_count, ++ sizeof(*pcie->pd_link), GFP_KERNEL); ++ if (!pcie->pd_link) ++ return -ENOMEM; ++ ++ for (i = 0; i < pcie->pd_count; i++) { ++ pcie->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); ++ if (IS_ERR(pcie->pd_dev[i])) { ++ apple_h9p_pcie_detach_genpd(pcie); ++ return PTR_ERR(pcie->pd_dev[i]); ++ } ++ ++ pcie->pd_link[i] = device_link_add(dev, pcie->pd_dev[i], ++ DL_FLAG_STATELESS | ++ DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!pcie->pd_link[i]) { ++ apple_h9p_pcie_detach_genpd(pcie); ++ return -EINVAL; ++ } ++ ++ /* ++ * Keep the domains out of system sleep: genpd would power them ++ * off at suspend_noirq, under linked ports and the DARTs. This ++ * driver takes the links down itself (h9p_suspend). ++ */ ++ dev_pm_syscore_device(pcie->pd_dev[i], true); ++ } ++ ++ return 0; ++} ++ ++static void apple_h9p_pcie_genpd_cleanup(void *data) ++{ ++ apple_h9p_pcie_detach_genpd(data); ++} ++ ++static void apple_h9p_pcie_clk_cleanup(void *data) ++{ ++ struct apple_h9p_pcie *pcie = data; ++ ++ clk_bulk_disable_unprepare(ARRAY_SIZE(pcie->clks), pcie->clks); ++} ++ ++static struct apple_h9p_pcie *apple_h9p_pcie_lookup(struct device *dev) ++{ ++ struct pci_host_bridge *bridge = dev_get_drvdata(dev); ++ ++ return bridge ? pci_host_bridge_priv(bridge) : NULL; ++} ++ ++/* Keep the generic failed-link quirk from undoing a successful Gen1 ++ * recovery. supported_speeds is initialized before HEADER fixups; scope ++ * this software ceiling to our recovered port 0, without falsifying ECAM. ++ */ ++static void apple_h9p_ans_gen1_fixup(struct pci_dev *dev) ++{ ++ struct pci_host_bridge *bridge; ++ struct apple_h9p_pcie *pcie; ++ struct device *parent; ++ ++ if (!pci_is_root_bus(dev->bus) || dev->devfn != PCI_DEVFN(0, 0)) ++ return; ++ bridge = pci_find_host_bridge(dev->bus); ++ parent = bridge->dev.parent; ++ if (!parent || !of_device_is_compatible(parent->of_node, "apple,s8003-pcie")) ++ return; ++ pcie = apple_h9p_pcie_lookup(parent); ++ if (pcie && pcie->ans_gen1) { ++ dev->supported_speeds = PCI_EXP_LNKCAP2_SLS_2_5GB; ++ pci_info(dev, "Apple ANS: STEP port 0 Gen1 recovery ceiling retained\n"); ++ } ++} ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_APPLE, PCI_ANY_ID, apple_h9p_ans_gen1_fixup); ++ ++/* The root port that a bus below the root bus hangs off */ ++static unsigned int apple_h9p_pcie_bus_port(struct pci_bus *bus) ++{ ++ while (bus->parent && !pci_is_root_bus(bus->parent)) ++ bus = bus->parent; ++ ++ return bus->self ? PCI_SLOT(bus->self->devfn) : H9P_NUM_PORTS; ++} ++ ++/* ++ * A root port exists only while its port is up. Below a port whose link is ++ * down (asleep, or dead after a resume) no request may go out on the link. ++ */ ++static bool apple_h9p_pcie_cfg_ok(struct pci_bus *bus, unsigned int devfn) ++{ ++ struct pci_config_window *cfg = bus->sysdata; ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(cfg->parent); ++ u32 down; ++ ++ if (bus->number == cfg->busr.start) ++ return pcie && PCI_SLOT(devfn) < H9P_NUM_PORTS && ++ (pcie->enabled_ports & BIT(PCI_SLOT(devfn))); ++ ++ down = pcie ? READ_ONCE(pcie->down_ports) : 0; ++ return !down || !(down & BIT(apple_h9p_pcie_bus_port(bus))); ++} ++ ++static int apple_h9p_pcie_config_read(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 *val) ++{ ++ if (!apple_h9p_pcie_cfg_ok(bus, devfn)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ return pci_generic_config_read(bus, devfn, where, size, val); ++} ++ ++static int apple_h9p_pcie_config_write(struct pci_bus *bus, unsigned int devfn, ++ int where, int size, u32 val) ++{ ++ if (!apple_h9p_pcie_cfg_ok(bus, devfn)) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ if (where <= PCI_INTERRUPT_LINE && where + size > PCI_INTERRUPT_LINE) ++ val |= 0xffu << ((PCI_INTERRUPT_LINE - where) << 3); ++ ++ return pci_generic_config_write(bus, devfn, where, size, val); ++} ++ ++static int apple_h9p_pcie_device_port(struct apple_h9p_pcie *pcie, ++ struct device *dev) ++{ ++ struct pci_dev *pdev; ++ ++ if (!dev_is_pci(dev)) ++ return -ENODEV; ++ ++ pdev = to_pci_dev(dev); ++ if (!pdev->bus) ++ return -ENODEV; ++ ++ /* Completion runs concurrently with late-port rescans. Bridge bus ++ * registers are routing state, not stable ownership: use the same ++ * topology walk as the config-access guard, with no ECAM reads. ++ */ ++ return apple_h9p_pcie_bus_port(pdev->bus); ++} ++ ++static void apple_h9p_msi_compose_msg(struct irq_data *d, struct msi_msg *msg) ++{ ++ struct apple_h9p_pcie *pcie = irq_data_get_irq_chip_data(d); ++ ++ msg->address_lo = lower_32_bits(pcie->msi_doorbell); ++ msg->address_hi = upper_32_bits(pcie->msi_doorbell); ++ msg->data = d->hwirq; ++} ++ ++static struct irq_chip apple_h9p_msi_bottom_chip = { ++ .name = "Apple H9P PCIe MSI", ++ .irq_mask = irq_chip_mask_parent, ++ .irq_unmask = irq_chip_unmask_parent, ++ .irq_eoi = irq_chip_eoi_parent, ++ .irq_set_affinity = irq_chip_set_affinity_parent, ++ .irq_compose_msi_msg = apple_h9p_msi_compose_msg, ++}; ++ ++static int apple_h9p_msi_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct apple_h9p_pcie *pcie = domain->host_data; ++ struct irq_fwspec fwspec = pcie->msi_fwspec; ++ msi_alloc_info_t *info = args; ++ struct msi_desc *desc = info ? info->desc : NULL; ++ struct device *msi_dev = desc ? msi_desc_to_dev(desc) : NULL; ++ struct pci_dev *pdev; ++ unsigned long flags; ++ unsigned int port; ++ int ret, slot; ++ ++ if (nr_irqs != 1 || !msi_dev || !dev_is_pci(msi_dev)) ++ return -ENOSPC; ++ ++ pdev = to_pci_dev(msi_dev); ++ if (!pdev->bus) ++ return -ENOSPC; ++ ++ port = apple_h9p_pcie_device_port(pcie, msi_dev); ++ if (port >= H9P_NUM_PORTS) ++ return -ENOSPC; ++ if (!(pcie->enabled_ports & BIT(port))) ++ return -ENOSPC; ++ ++ spin_lock_irqsave(&pcie->used_msi_lock, flags); ++ slot = find_first_zero_bit(pcie->used_msi[port], H9P_MSI_PER_PORT); ++ if (slot >= H9P_MSI_PER_PORT) { ++ spin_unlock_irqrestore(&pcie->used_msi_lock, flags); ++ return -ENOSPC; ++ } ++ __set_bit(slot, pcie->used_msi[port]); ++ spin_unlock_irqrestore(&pcie->used_msi_lock, flags); ++ ++ fwspec.param[fwspec.param_count - 2] += ++ port * H9P_MSI_PER_PORT + slot; ++ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); ++ if (ret) { ++ spin_lock_irqsave(&pcie->used_msi_lock, flags); ++ __clear_bit(slot, pcie->used_msi[port]); ++ spin_unlock_irqrestore(&pcie->used_msi_lock, flags); ++ return ret; ++ } ++ ++ irq_domain_set_hwirq_and_chip(domain, virq, ++ port * H9P_MSI_PER_PORT + slot, ++ &apple_h9p_msi_bottom_chip, pcie); ++ return 0; ++} ++ ++static void apple_h9p_msi_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct apple_h9p_pcie *pcie = d ? irq_data_get_irq_chip_data(d) : NULL; ++ irq_hw_number_t hwirq; ++ unsigned long flags; ++ unsigned int i; ++ ++ if (!pcie || !d) ++ return; ++ ++ hwirq = d->hwirq; ++ irq_domain_free_irqs_parent(domain, virq, nr_irqs); ++ ++ spin_lock_irqsave(&pcie->used_msi_lock, flags); ++ for (i = 0; i < nr_irqs; i++) { ++ irq_hw_number_t irq = hwirq + i; ++ unsigned int port = irq / H9P_MSI_PER_PORT; ++ unsigned int slot = irq % H9P_MSI_PER_PORT; ++ ++ if (port < H9P_NUM_PORTS) ++ __clear_bit(slot, pcie->used_msi[port]); ++ } ++ spin_unlock_irqrestore(&pcie->used_msi_lock, flags); ++} ++ ++static const struct irq_domain_ops apple_h9p_msi_domain_ops = { ++ .alloc = apple_h9p_msi_alloc, ++ .free = apple_h9p_msi_free, ++}; ++ ++static const struct msi_parent_ops apple_h9p_msi_parent_ops = { ++ .supported_flags = MSI_GENERIC_FLAGS_MASK | MSI_FLAG_PCI_MSIX | ++ MSI_FLAG_MULTI_PCI_MSI, ++ .required_flags = MSI_FLAG_USE_DEF_DOM_OPS | ++ MSI_FLAG_USE_DEF_CHIP_OPS | ++ MSI_FLAG_PCI_MSI_MASK_PARENT, ++ .chip_flags = MSI_CHIP_FLAG_SET_EOI, ++ .bus_select_token = DOMAIN_BUS_PCI_MSI, ++ .init_dev_msi_info = msi_lib_init_dev_msi_info, ++}; ++ ++static int apple_h9p_pcie_setup_msi(struct apple_h9p_pcie *pcie) ++{ ++ struct device *dev = pcie->dev; ++ struct fwnode_handle *fwnode = dev_fwnode(dev); ++ struct irq_domain_info info = { ++ .fwnode = fwnode, ++ .ops = &apple_h9p_msi_domain_ops, ++ .size = H9P_NUM_MSI, ++ .host_data = pcie, ++ }; ++ struct of_phandle_args args = {}; ++ int ret; ++ ++ ret = of_parse_phandle_with_args(to_of_node(fwnode), "msi-ranges", ++ "#interrupt-cells", 0, &args); ++ if (ret) ++ return ret; ++ ++ ret = of_property_read_u32_index(to_of_node(fwnode), "msi-ranges", ++ args.args_count + 1, &pcie->nvecs); ++ if (ret) ++ goto out_put_node; ++ if (pcie->nvecs != H9P_NUM_MSI) { ++ ret = -EINVAL; ++ goto out_put_node; ++ } ++ ++ of_phandle_args_to_fwspec(args.np, args.args, args.args_count, ++ &pcie->msi_fwspec); ++ info.parent = irq_find_matching_fwspec(&pcie->msi_fwspec, ++ DOMAIN_BUS_WIRED); ++ if (!info.parent) { ++ ret = -ENXIO; ++ goto out_put_node; ++ } ++ ++ pcie->irq_dom = msi_create_parent_irq_domain(&info, ++ &apple_h9p_msi_parent_ops); ++ if (!pcie->irq_dom) ++ ret = -ENOMEM; ++ ++out_put_node: ++ of_node_put(args.np); ++ return ret; ++} ++ ++static void apple_h9p_pcie_msi_cleanup(void *data) ++{ ++ struct apple_h9p_pcie *pcie = data; ++ ++ irq_domain_remove(pcie->irq_dom); ++} ++ ++static u64 apple_h9p_read_pci_cap(struct apple_h9p_pcie *pcie, ++ unsigned int busdevfn, u32 type) ++{ ++ void __iomem *cfg = pcie->base_config + (busdevfn << 12); ++ u32 ptr = readl(cfg + PCI_CAPABILITY_LIST) & 0xff; ++ unsigned int ttl = 48; ++ ++ while (ptr && ttl--) { ++ u32 next = readl(cfg + ptr); ++ ++ if (ptr < PCI_STD_HEADER_SIZEOF || ptr > 0xfc || (ptr & 3)) ++ break; ++ if ((next & 0xff) == type) ++ return ptr; ++ ptr = (next >> 8) & 0xff; ++ } ++ ++ return 0; ++} ++ ++static int apple_h9p_wait(void __iomem *addr, u32 mask, u32 min, u32 max, ++ unsigned long timeout_us) ++{ ++ u32 val; ++ ++ return readl_poll_timeout(addr, val, (val & mask) >= min && ++ (val & mask) <= max, 1000, timeout_us); ++} ++ ++static int apple_h9p_wait_gpio(struct gpio_desc *desc, int value, ++ unsigned long timeout_us) ++{ ++ ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); ++ int ret; ++ ++ do { ++ ret = gpiod_get_value_cansleep(desc); ++ if (ret < 0) ++ return ret; ++ if (ret == value) ++ return 0; ++ usleep_range(1000, 2000); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ return -ETIMEDOUT; ++} ++ ++/* ++ * iOS panics on every NVMMU error ("nvme mmu fetch error ...") and never ++ * acks one, so there is no known way to clear it. Log everything once and ++ * mask the interrupt so it can't storm. ++ */ ++static irqreturn_t apple_h9p_nvmmu_irq(int irq, void *data) ++{ ++ struct apple_h9p_nvmmu *nvmmu = data; ++ struct apple_h9p_pcie *pcie = nvmmu->pcie; ++ void __iomem *b = nvmmu->base; ++ ++ dev_err(pcie->dev, ++ "NVMMU fault: status %#x info %#x (tag %lu) prp %#x addr %#x:%08x crypto %#x fetch %#x; masking the NVMMU interrupt\n", ++ readl(b + H9P_NVMMU_ERR_STATUS), readl(b + H9P_NVMMU_ERR_INFO), ++ FIELD_GET(GENMASK(29, 23), readl(b + H9P_NVMMU_ERR_INFO)), ++ readl(b + H9P_NVMMU_ERR_PRP), readl(b + H9P_NVMMU_ERR_ADDR_HI), ++ readl(b + H9P_NVMMU_ERR_ADDR_LO), readl(b + H9P_NVMMU_ERR_CRYPTO), ++ readl(b + H9P_NVMMU_ERR_FETCH)); ++ disable_irq_nosync(irq); ++ return IRQ_HANDLED; ++} ++ ++static void apple_h9p_iounmap(void *addr) ++{ ++ iounmap(addr); ++} ++ ++/* Map port 0's DART (first iommu-map entry) for read-only diagnostics */ ++static void apple_h9p_map_dart(struct apple_h9p_pcie *pcie) ++{ ++ struct device_node *np; ++ ++ np = of_parse_phandle(pcie->dev->of_node, "iommu-map", 1); ++ if (!np) ++ return; ++ if (of_device_is_compatible(np, "apple,s5l8960x-dart") && ++ of_device_is_available(np)) { ++ u64 range[2]; ++ ++ if (!of_property_read_u64_array(np, "apple,dma-range", range, 2)) ++ pcie->nvmmu.dart_base = range[0]; ++ /* of_iomap: apple-dart already requested the region */ ++ pcie->nvmmu.dart = of_iomap(np, 0); ++ if (pcie->nvmmu.dart && ++ devm_add_action_or_reset(pcie->dev, apple_h9p_iounmap, ++ (void __force *)pcie->nvmmu.dart)) ++ pcie->nvmmu.dart = NULL; ++ } ++ of_node_put(np); ++} ++ ++/* ++ * The loader copies the runtime ADT nvme-mmu0 "sart-region" (the HMB iBoot ++ * set up) into the reserved-memory node behind apple,nvme-hmb. Map it through ++ * the NVMMU SART window. Without it FlatDMA still works and the ANS glue can ++ * fall back to a DART-mapped HMB. ++ */ ++static int apple_h9p_nvmmu_get_sart(struct apple_h9p_pcie *pcie) ++{ ++ struct apple_h9p_nvmmu *nvmmu = &pcie->nvmmu; ++ struct device *dev = pcie->dev; ++ struct device_node *mem_np; ++ struct resource res; ++ resource_size_t size; ++ u32 iova; ++ int ret; ++ ++ mem_np = of_parse_phandle(dev->of_node, "apple,nvme-hmb", 0); ++ if (!mem_np) ++ return -ENODEV; ++ ++ ret = of_address_to_resource(mem_np, 0, &res); ++ if (ret) ++ goto out_put_node; ++ ++ ret = of_property_read_u32(dev->of_node, "apple,nvmmu-iova", &iova); ++ if (ret) ++ goto out_put_node; ++ ++ size = resource_size(&res); ++ if (!res.start || !size) { ++ ret = -ENODEV; ++ goto out_put_node; ++ } ++ /* iOS: MB-aligned, and the window must end at or below 0xbff00000 */ ++ if (size < H9P_NVMMU_SART_ALIGNMENT || size > U32_MAX || ++ !IS_ALIGNED(res.start, H9P_NVMMU_SART_ALIGNMENT) || ++ !IS_ALIGNED(size, H9P_NVMMU_SART_ALIGNMENT) || ++ !IS_ALIGNED(iova, H9P_NVMMU_SART_ALIGNMENT) || ++ iova < 0x80000000U || ++ (u64)iova + size - H9P_NVMMU_SART_ALIGNMENT > 0xbff00000U) { ++ ret = -EINVAL; ++ goto out_put_node; ++ } ++ ++ nvmmu->pa_base = res.start; ++ nvmmu->va_base = iova; ++ nvmmu->size = size; ++ ++out_put_node: ++ of_node_put(mem_np); ++ return ret; ++} ++ ++/* NVMMU registers; also re-run on resume if they didn't survive the sleep */ ++static int apple_h9p_nvmmu_program(struct apple_h9p_pcie *pcie) ++{ ++ struct apple_h9p_nvmmu *nvmmu = &pcie->nvmmu; ++ u32 val; ++ int ret; ++ ++ if (nvmmu->inv_fifo == 1) { ++ ret = readl_poll_timeout(pcie->base_port[0] + ++ S8003_PORT_NVMMU_INV_FIFO, val, ++ val & S8003_PORT_NVMMU_INV_FIFO_EMPTY, ++ 10, S8003_NVMMU_INV_FIFO_TIMEOUT_US); ++ if (ret) { ++ dev_err(pcie->dev, "NVMMU: FIFO %#x blocks reinitialization\n", val); ++ return ret; ++ } ++ } ++ /* No old aperture may remain enabled while its address is replaced. */ ++ h9p_writel_flush(0, nvmmu->base + H9P_NVMMU_SART_CTRL); ++ /* Publish WC descriptor/scratch initialization before the hardware init. */ ++ dma_wmb(); ++ /* Same order as iOS: scratch page, TCB base, init, SART */ ++ h9p_writel_flush(lower_32_bits(nvmmu->tcb_table_dma), ++ nvmmu->base + H9P_NVMMU_TCB_TABLE_LO); ++ h9p_writel_flush(upper_32_bits(nvmmu->tcb_table_dma), ++ nvmmu->base + H9P_NVMMU_TCB_TABLE_HI); ++ h9p_writel_flush(lower_32_bits(nvmmu->tcb_dma), ++ nvmmu->base + H9P_NVMMU_TCB_BASE_LO); ++ h9p_writel_flush(upper_32_bits(nvmmu->tcb_dma), ++ nvmmu->base + H9P_NVMMU_TCB_BASE_HI); ++ h9p_writel_flush(H9P_NVMMU_TCB_CTRL_INIT, ++ nvmmu->base + H9P_NVMMU_TCB_CTRL); ++ ++ ret = apple_h9p_wait(nvmmu->base + H9P_NVMMU_TCB_CTRL, ++ pcie->hw->nvmmu_init_busy, 0, 0, 250000); ++ if (ret) ++ return ret; ++ ++ if (nvmmu->sart) { ++ h9p_writel_flush(nvmmu->va_base - 0x80000000U, ++ nvmmu->base + H9P_NVMMU_SART_VA_BASE); ++ h9p_writel_flush(nvmmu->va_base + nvmmu->size - 0x80100000U, ++ nvmmu->base + H9P_NVMMU_SART_VA_END); ++ h9p_writel_flush(nvmmu->pa_base >> 20, ++ nvmmu->base + H9P_NVMMU_SART_PA_BASE); ++ h9p_writel_flush(1, nvmmu->base + H9P_NVMMU_SART_CTRL); ++ } ++ return 0; ++} ++ ++static int apple_h9p_setup_nvmmu(struct apple_h9p_pcie *pcie) ++{ ++ struct apple_h9p_nvmmu *nvmmu = &pcie->nvmmu; ++ struct device *dev = pcie->dev; ++ int irq; ++ int ret; ++ ++ if (!(pcie->enabled_ports & BIT(0)) || !nvmmu->base) ++ return 0; ++ ++ nvmmu->pcie = pcie; ++ spin_lock_init(&nvmmu->inv_lock); ++ of_property_read_u32(dev->of_node, "apple,nvmmu-invalidate-fifo", ++ &nvmmu->inv_fifo); ++ if (nvmmu->inv_fifo > 2) ++ return dev_err_probe(dev, -EINVAL, ++ "bad apple,nvmmu-invalidate-fifo %u\n", ++ nvmmu->inv_fifo); ++ ret = apple_h9p_nvmmu_get_sart(pcie); ++ if (ret == -ENODEV) ++ dev_info(dev, "NVMMU: no loader-provided HMB (apple,nvme-hmb empty), SART off\n"); ++ else if (ret) ++ return dev_err_probe(dev, ret, "NVMMU: bad apple,nvme-hmb\n"); ++ nvmmu->sart = !ret; ++ ++ /* All 128 TCBs the hardware can index, so no tag reads past the end */ ++ nvmmu->tcb_size = round_up(H9P_NVMMU_TCB_COUNT * H9P_NVMMU_TCB_BYTES, ++ PAGE_SIZE); ++ nvmmu->tcb_table_size = PAGE_SIZE * 16; ++ nvmmu->tcb_sgl_size = round_up(APPLE_ANS_NVMMU_MAX_REQS * ++ H9P_NVMMU_SGL_WORDS * sizeof(u32), ++ PAGE_SIZE); ++ ++ nvmmu->tcb = dmam_alloc_attrs(dev, nvmmu->tcb_size, &nvmmu->tcb_dma, ++ GFP_KERNEL | __GFP_ZERO, ++ DMA_ATTR_WRITE_COMBINE); ++ if (!nvmmu->tcb) ++ return -ENOMEM; ++ ++ nvmmu->tcb_table = dmam_alloc_attrs(dev, nvmmu->tcb_table_size, ++ &nvmmu->tcb_table_dma, ++ GFP_KERNEL | __GFP_ZERO, ++ DMA_ATTR_WRITE_COMBINE); ++ if (!nvmmu->tcb_table) ++ return -ENOMEM; ++ ++ nvmmu->tcb_sgl = dmam_alloc_attrs(dev, nvmmu->tcb_sgl_size, ++ &nvmmu->tcb_sgl_dma, ++ GFP_KERNEL | __GFP_ZERO, ++ DMA_ATTR_WRITE_COMBINE); ++ if (!nvmmu->tcb_sgl) ++ return -ENOMEM; ++ ++ /* Port bring-up has isolated the old endpoint before replacing tables. */ ++ ret = apple_h9p_nvmmu_program(pcie); ++ if (ret) ++ return ret; ++ ++ irq = platform_get_irq_byname_optional(pcie->pdev, "nvmmu"); ++ if (irq > 0) { ++ ret = devm_request_irq(dev, irq, apple_h9p_nvmmu_irq, 0, ++ dev_name(dev), nvmmu); ++ if (ret) ++ return ret; ++ } else if (irq != -ENXIO) { ++ return irq; ++ } ++ ++ apple_h9p_map_dart(pcie); ++ ++ nvmmu->ready = true; ++ nvmmu->active = true; ++ if (nvmmu->sart) ++ dev_info(dev, "NVMMU: ready, SART window %#x -> %pa size %#x, invalidate-fifo %u\n", ++ nvmmu->va_base, &nvmmu->pa_base, nvmmu->size, ++ nvmmu->inv_fifo); ++ else ++ dev_info(dev, "NVMMU: ready (FlatDMA only), invalidate-fifo %u\n", ++ nvmmu->inv_fifo); ++ ++ return 0; ++} ++ ++/* ++ * Invalidate one TCB in the NVMMU cache. iOS (AppleNVMeMMU) with ++ * invalidate-fifo-workaround 1 waits for the invalidate FIFO in the port 0 ++ * block to drain before each write, and never reads the register back; ++ * overrunning the FIFO is suspected to stall the port's inbound path. ++ */ ++static int apple_h9p_nvmmu_invalidate(struct apple_h9p_pcie *pcie, ++ unsigned int tag) ++{ ++ struct apple_h9p_nvmmu *nvmmu = &pcie->nvmmu; ++ unsigned long flags; ++ u32 val; ++ int ret = 0; ++ ++ spin_lock_irqsave(&nvmmu->inv_lock, flags); ++ if (nvmmu->inv_fifo == 1) ++ ret = readl_poll_timeout_atomic(pcie->base_port[0] + ++ S8003_PORT_NVMMU_INV_FIFO, val, ++ val & S8003_PORT_NVMMU_INV_FIFO_EMPTY, ++ 1, S8003_NVMMU_INV_FIFO_TIMEOUT_US); ++ else if (nvmmu->inv_fifo == 2) ++ udelay(S8003_NVMMU_INV_DELAY_US); ++ if (!ret) ++ writel(tag, nvmmu->base + H9P_NVMMU_TCB_CTRL); ++ spin_unlock_irqrestore(&nvmmu->inv_lock, flags); ++ ++ if (ret) ++ dev_err_ratelimited(pcie->dev, ++ "NVMMU: invalidate FIFO did not drain (%#x), tag %u not invalidated\n", ++ val, tag); ++ return ret; ++} ++ ++static struct apple_h9p_pcie *apple_ans_host(struct device *dev) ++{ ++ struct device *host_dev = dev; ++ ++ while (host_dev && host_dev->bus == dev->bus) ++ host_dev = host_dev->parent; ++ if (!host_dev || !host_dev->parent) ++ return NULL; ++ ++ return apple_h9p_pcie_lookup(host_dev->parent); ++} ++ ++enum apple_ans_boot_mode apple_ans_boot_mode(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ ++ return pcie ? pcie->ans_mode : APPLE_ANS_ROUND7; ++} ++EXPORT_SYMBOL_GPL(apple_ans_boot_mode); ++ ++/* Set only after the endpoint has stopped and relinquished DMA. */ ++int apple_ans_set_sleep_stopped(struct device *dev, bool stopped) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ ++ if (!pcie || !of_machine_is_compatible("apple,j71t")) ++ return -EOPNOTSUPP; ++ WRITE_ONCE(pcie->ans_sleep_stopped, stopped); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(apple_ans_set_sleep_stopped); ++ ++/* Caller has frozen/drained I/O; no new data mappings can be submitted. */ ++int apple_ans_nvmmu_drain(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ struct apple_h9p_nvmmu *nvmmu; ++ u32 val; ++ int ret = 0; ++ ++ if (!pcie || !pcie->nvmmu.ready) ++ return -ENODEV; ++ nvmmu = &pcie->nvmmu; ++ /* Retire completions, not just queued invalidates: used tags at rest ++ * mean a request never released its mapping. Never silently clear them. ++ */ ++ if (READ_ONCE(nvmmu->poisoned) || ++ !bitmap_empty(nvmmu->used, APPLE_ANS_NVMMU_MAX_REQS)) ++ return -EBUSY; ++ if (nvmmu->inv_fifo == 1) ++ ret = readl_poll_timeout(pcie->base_port[0] + ++ S8003_PORT_NVMMU_INV_FIFO, val, ++ val & S8003_PORT_NVMMU_INV_FIFO_EMPTY, ++ 10, S8003_NVMMU_INV_FIFO_TIMEOUT_US); ++ dev_info(dev, "Apple ANS: outgoing NVMMU FIFO empty check (20ms) result %d\n", ret); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_ans_nvmmu_drain); ++ ++/* ++ * Is the endpoint's link still up? A BAR access over a dead link can stall ++ * the CPU forever on these root complexes, so the ANS glue checks this before ++ * the riskier steps of its bring-up. ++ */ ++int apple_ans_link_up(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ int port; ++ ++ if (!pcie) ++ return -ENODEV; ++ port = apple_h9p_pcie_device_port(pcie, dev); ++ if (port < 0 || port >= H9P_NUM_PORTS || ++ !(pcie->enabled_ports & BIT(port))) ++ return -ENODEV; ++ if (!pcie->hw->linksts) ++ return 1; /* not known for T8010 */ ++ ++ return !!(readl(pcie->base_port[port] + pcie->hw->linksts) & ++ pcie->hw->linksts_up); ++} ++EXPORT_SYMBOL_GPL(apple_ans_link_up); ++ ++/* ++ * SoC-side state of the endpoint's port, NVMMU and DART. None of these ++ * reads goes over the PCIe link, so they can't stall on a wedged ++ * endpoint; the ANS glue watches them around CC.EN instead of hammering ++ * the controller with non-posted CSTS reads. ++ */ ++int apple_ans_host_state(struct device *dev, struct apple_ans_host_state *st) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ void __iomem *pb; ++ ++ memset(st, 0, sizeof(*st)); ++ if (!pcie || apple_h9p_pcie_device_port(pcie, dev) != 0 || ++ !(pcie->enabled_ports & BIT(0))) ++ return -ENODEV; ++ ++ pb = pcie->base_port[0]; ++ st->link_up = pcie->hw->linksts ? ++ !!(readl(pb + pcie->hw->linksts) & pcie->hw->linksts_up) : 1; ++ if (pcie->hw->linksts == S8003_PORT_LINKSTS) ++ st->ltssm = FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, ++ readl(pb + S8003_PORT_LINKCDMSTS)); ++ st->port_irq = readl(pb + H9P_PORT_IRQSTAT); ++ if (pcie->nvmmu.ready) ++ st->nvmmu_err = readl(pcie->nvmmu.base + H9P_NVMMU_ERR_STATUS); ++ if (pcie->nvmmu.dart) { ++ st->dart_err = readl(pcie->nvmmu.dart + H9P_DART_ERROR); ++ st->dart_addr = readl(pcie->nvmmu.dart + H9P_DART_ERROR_ADDR); ++ } ++ return 0; ++} ++EXPORT_SYMBOL_GPL(apple_ans_host_state); ++ ++void apple_ans_host_dump(struct device *dev, const char *when) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ void __iomem *rc, *pb, *nv, *da; ++ ++ if (!pcie || apple_h9p_pcie_device_port(pcie, dev) != 0 || ++ !(pcie->enabled_ports & BIT(0))) ++ return; ++ /* The rc/port offsets below are the S8003 ones */ ++ if (pcie->hw->linksts != S8003_PORT_LINKSTS) ++ return; ++ ++ rc = pcie->base_rc; ++ pb = pcie->base_port[0]; ++ dev_info(dev, ++ "host %s: rc ctl %#x pwr %#x refclk %#x rdy %#x stat %#x; port linksts %#x cdm %#x irq %#x/%#x msi %#x/%#x en %#x pmgr %#x\n", ++ when, readl(rc + S8003_RC_PORT_CTL(0)), ++ readl(rc + S8003_RC_PORT_PWR(0)), ++ readl(rc + S8003_RC_PORT_REFCLK(0)), ++ readl(rc + S8003_RC_PORT_READY(0)), readl(rc + S8003_RC_STAT), ++ readl(pb + S8003_PORT_LINKSTS), ++ readl(pb + S8003_PORT_LINKCDMSTS), ++ readl(pb + H9P_PORT_IRQSTAT), readl(pb + H9P_PORT_IRQMASK), ++ readl(pb + S8003_PORT_MSICTL), ++ readl(pb + H9P_PORT_MSIVECBASE), readl(pb + H9P_PORT_ENABLE), ++ readl(pb + S8003_PORT_LINKPMGRSTS)); ++ ++ dev_info(dev, "host %s: invalidate FIFO %#x mode %u poisoned %u used %*pb\n", ++ when, readl(pb + S8003_PORT_NVMMU_INV_FIFO), ++ pcie->nvmmu.inv_fifo, READ_ONCE(pcie->nvmmu.poisoned), ++ APPLE_ANS_NVMMU_MAX_REQS, pcie->nvmmu.used); ++ nv = pcie->nvmmu.base; ++ if (pcie->nvmmu.ready) ++ dev_info(dev, ++ "host %s: nvmmu tcb %#x:%08x scratch %#x:%08x sart %#x %#x..%#x pa %#x; err %#x %#x %#x %#x:%08x %#x %#x\n", ++ /* Never read the invalidate command register in a live dump. */ ++ when, ++ readl(nv + H9P_NVMMU_TCB_BASE_HI), ++ readl(nv + H9P_NVMMU_TCB_BASE_LO), ++ readl(nv + H9P_NVMMU_TCB_TABLE_HI), ++ readl(nv + H9P_NVMMU_TCB_TABLE_LO), ++ readl(nv + H9P_NVMMU_SART_CTRL), ++ readl(nv + H9P_NVMMU_SART_VA_BASE), ++ readl(nv + H9P_NVMMU_SART_VA_END), ++ readl(nv + H9P_NVMMU_SART_PA_BASE), ++ readl(nv + H9P_NVMMU_ERR_STATUS), ++ readl(nv + H9P_NVMMU_ERR_INFO), ++ readl(nv + H9P_NVMMU_ERR_PRP), ++ readl(nv + H9P_NVMMU_ERR_ADDR_HI), ++ readl(nv + H9P_NVMMU_ERR_ADDR_LO), ++ readl(nv + H9P_NVMMU_ERR_CRYPTO), ++ readl(nv + H9P_NVMMU_ERR_FETCH)); ++ ++ da = pcie->nvmmu.dart; ++ if (da) ++ dev_info(dev, ++ "host %s: dart tcr %#x err %#x addr %#x diag %#x reflector %#x fetch %#x ttbr %#x %#x %#x %#x\n", ++ when, readl(da + H9P_DART_TCR), readl(da + H9P_DART_ERROR), ++ readl(da + H9P_DART_ERROR_ADDR), readl(da + H9P_DART_DIAG), ++ readl(da + H9P_DART_REFLECTOR), readl(da + H9P_DART_FETCH), ++ readl(da + H9P_DART_TTBR(0)), readl(da + H9P_DART_TTBR(1)), ++ readl(da + H9P_DART_TTBR(2)), readl(da + H9P_DART_TTBR(3))); ++} ++EXPORT_SYMBOL_GPL(apple_ans_host_dump); ++ ++/* Port 0 DART, stream 0: what the hardware finds for DART-side @addr */ ++static void apple_h9p_dart_walk(struct apple_h9p_pcie *pcie, struct device *dev, ++ const char *what, const char *view, u32 addr) ++{ ++ u32 ttbr = readl(pcie->nvmmu.dart + H9P_DART_TTBR(addr >> 30)); ++ phys_addr_t pa = (phys_addr_t)(ttbr & GENMASK(23, 0)) << 12; ++ u32 ste_idx = (addr >> 21) & 0x1ff, pte_idx = (addr >> 12) & 0x1ff; ++ u64 ste = 0, pte = 0; ++ ++ if ((ttbr & BIT(31)) && pfn_valid(PHYS_PFN(pa))) { ++ ste = READ_ONCE(((u64 *)phys_to_virt(pa))[ste_idx]); ++ pa = ste & GENMASK_ULL(35, 12); ++ if ((ste & BIT(0)) && pfn_valid(PHYS_PFN(pa))) ++ pte = READ_ONCE(((u64 *)phys_to_virt(pa))[pte_idx]); ++ } ++ dev_info(dev, "dart walk %s, %s %#x: TTBR%u %#x -> STE[%#x] %#llx -> PTE[%#x] %#llx\n", ++ what, view, addr, addr >> 30, ttbr, ste_idx, ste, pte_idx, pte); ++} ++ ++/* ++ * Walk the port 0 DART's stream 0 tables for device IOVA @iova, both as the ++ * hardware sees it (window base removed) and raw, to show where Linux put ++ * the entry and where the DART will look. ++ */ ++void apple_ans_dart_walk(struct device *dev, const char *what, u64 iova) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ ++ if (!pcie || !pcie->nvmmu.dart || ++ apple_h9p_pcie_device_port(pcie, dev) != 0) ++ return; ++ ++ apple_h9p_dart_walk(pcie, dev, what, "dart-side", ++ (u32)(iova - pcie->nvmmu.dart_base)); ++ if (pcie->nvmmu.dart_base) ++ apple_h9p_dart_walk(pcie, dev, what, "raw", (u32)iova); ++} ++EXPORT_SYMBOL_GPL(apple_ans_dart_walk); ++ ++int apple_ans_nvmmu_map(struct device *dev, unsigned int tag, ++ const u64 *pages, unsigned int npages, ++ bool to_memory, dma_addr_t *iova) ++{ ++ struct apple_h9p_nvmmu *nvmmu; ++ struct apple_h9p_pcie *pcie; ++ unsigned int i; ++ u64 sgl_dma; ++ u32 *tcb; ++ u32 *sgl; ++ int ret; ++ ++ if (tag >= APPLE_ANS_NVMMU_MAX_REQS || ++ npages > APPLE_ANS_NVMMU_MAX_PAGES) ++ return -EINVAL; ++ if (npages && !pages) ++ return -EINVAL; ++ ++ pcie = apple_ans_host(dev); ++ if (!pcie) { ++ dev_err_ratelimited(dev, ++ "NVMMU: tag %u host lookup failed; no hardware access\n", tag); ++ return -ENODEV; ++ } ++ ret = apple_h9p_pcie_device_port(pcie, dev); ++ if (ret != 0 || !(pcie->enabled_ports & BIT(0))) { ++ dev_err_ratelimited(dev, ++ "NVMMU: tag %u owner port %d enabled %#x; no hardware access\n", ++ tag, ret, pcie->enabled_ports); ++ return ret < 0 ? ret : -ENODEV; ++ } ++ ++ nvmmu = &pcie->nvmmu; ++ if (!nvmmu->ready) ++ return -EOPNOTSUPP; ++ if (READ_ONCE(nvmmu->poisoned)) ++ return -EIO; ++ ++ tcb = (u32 *)nvmmu->tcb + tag * H9P_NVMMU_TCB_DWORDS; ++ sgl = (u32 *)nvmmu->tcb_sgl + tag * H9P_NVMMU_SGL_WORDS; ++ ++ if (!npages) { ++ /* Retire the slot like iOS: count first, then the rest, then invalidate */ ++ WRITE_ONCE(tcb[H9P_NVMMU_TCB_NPAGES], 0); ++ dma_wmb(); ++ memset(tcb, 0, H9P_NVMMU_TCB_BYTES); ++ dma_wmb(); ++ if (iova) ++ *iova = 0; ++ /* Never-mapped slots aren't cached; don't feed the FIFO */ ++ if (!test_bit(tag, nvmmu->used)) ++ return 0; ++ ret = apple_h9p_nvmmu_invalidate(pcie, tag); ++ if (ret) { ++ WRITE_ONCE(nvmmu->poisoned, true); ++ return ret; ++ } ++ clear_bit(tag, nvmmu->used); ++ return 0; ++ } ++ ++ memset(tcb, 0, H9P_NVMMU_TCB_BYTES); ++ memset(sgl, 0, H9P_NVMMU_SGL_WORDS * sizeof(*sgl)); ++ for (i = 0; i < npages; i++) ++ sgl[i] = pages[i] >> ilog2(APPLE_ANS_NVMMU_PAGE_SIZE); ++ ++ /* iOS uses exactly one direction per TCB (InOut is rejected) */ ++ tcb[0] = to_memory ? H9P_NVMMU_TCB_WRITE : H9P_NVMMU_TCB_READ; ++ tcb[2] = pages[0] >> ilog2(APPLE_ANS_NVMMU_PAGE_SIZE); ++ sgl_dma = nvmmu->tcb_sgl_dma + tag * H9P_NVMMU_SGL_WORDS * sizeof(*sgl); ++ memcpy(&tcb[4], &sgl_dma, sizeof(sgl_dma)); ++ /* The page count makes the TCB valid, so it goes last */ ++ dma_wmb(); ++ WRITE_ONCE(tcb[H9P_NVMMU_TCB_NPAGES], npages); ++ dma_wmb(); ++ set_bit(tag, nvmmu->used); ++ ++ if (iova) ++ *iova = H9P_NVMMU_FLATDMA_BASE + tag * H9P_NVMMU_FLATDMA_STRIDE; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(apple_ans_nvmmu_map); ++ ++/* Caller holds the NVMe shutdown lock, RDY=0 and old queues are synchronized. ++ * CC reset does not reset the host NVMMU. Retiring only software-used tags ++ * cannot recover an invalidate failure or a retained hardware cache entry. ++ */ ++int apple_ans_nvmmu_reset(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ struct apple_h9p_nvmmu *nvmmu; ++ int ret; ++ ++ if (!pcie || apple_h9p_pcie_device_port(pcie, dev) != 0) ++ return -ENODEV; ++ nvmmu = &pcie->nvmmu; ++ if (!nvmmu->ready || !(pcie->enabled_ports & BIT(0))) ++ return -ENODEV; ++ ++ memset(nvmmu->tcb, 0, nvmmu->tcb_size); ++ memset(nvmmu->tcb_table, 0, nvmmu->tcb_table_size); ++ memset(nvmmu->tcb_sgl, 0, nvmmu->tcb_sgl_size); ++ ret = apple_h9p_nvmmu_program(pcie); ++ if (ret) { ++ WRITE_ONCE(nvmmu->poisoned, true); ++ return ret; ++ } ++ bitmap_zero(nvmmu->used, APPLE_ANS_NVMMU_MAX_REQS); ++ WRITE_ONCE(nvmmu->poisoned, false); ++ nvmmu->active = true; ++ dev_info(dev, "NVMMU: stopped-controller cache/SART rebuilt\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(apple_ans_nvmmu_reset); ++ ++static bool apple_h9p_link_up(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ u32 linksts = readl(pcie->base_port[port] + H9P_PORT_LINKSTS); ++ ++ linksts = FIELD_GET(H9P_PORT_LINKSTS_LTSSM, linksts); ++ return linksts >= H9P_PORT_LTSSM_DETECT && linksts <= H9P_PORT_LTSSM_L0; ++} ++ ++static void apple_h9p_phy_ip_tune(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ h9p_rmw(pcie->base_phy_ip + H9P_PHY_IP_EQ_COMMON0, 0, 0x4000); ++ h9p_rmw(pcie->base_phy_ip + H9P_PHY_IP_EQ_COMMON1, 0, 0x4000); ++ h9p_rmw(pcie->base_phy_ip + H9P_PHY_IP_EQ_TIME0, 0xfff, 100); ++ h9p_rmw(pcie->base_phy_ip + H9P_PHY_IP_EQ_TIME1, 0xfff, 25); ++ h9p_rmw(pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_EQ_CTL), ++ 0, 0x4000); ++ writel(0, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_IDLE)); ++ h9p_rmw(pcie->base_phy_ip + ++ H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_EQ_PRESET), 0xfff, 0x600); ++ writel(0x3105, pcie->base_phy_ip + ++ H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_RX_CTL0)); ++ h9p_rmw(pcie->base_phy_ip + ++ H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_RX_CTL1), 0xff, 0x9f); ++ h9p_rmw(pcie->base_phy_ip + ++ H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_RX_CTL2), 0xff, 0x01); ++ h9p_rmw(pcie->base_phy_ip + ++ H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_RX_CTL3), 0x1f, 0x0a); ++ writel(175, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER0)); ++ writel(175, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER1)); ++ writel(333, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER2)); ++ writel(333, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER3)); ++ writel(530, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER4)); ++ writel(530, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_TIMER5)); ++ writel(0, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_CLEAR0)); ++ writel(0, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_CLEAR1)); ++ writel(0, pcie->base_phy_ip + H9P_PHY_IP_PORT(port, H9P_PHY_IP_PORT_CLEAR2)); ++} ++ ++static int apple_t8010_setup_port(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ struct device *dev = pcie->dev; ++ u32 speed = pcie->max_speed[port]; ++ u64 cap; ++ int ret; ++ ++ /* ++ * PERST# was asserted when the GPIO was requested, so the endpoint is ++ * in reset even if the RC still reports a trained link: always run ++ * the full sequence rather than trusting the loader's link state. ++ */ ++ dev_info(dev, "port %u: bringing up link (Gen%u)\n", port, speed); ++ if (apple_h9p_link_up(pcie, port)) ++ dev_info(dev, "port %u: link was up at probe, retraining\n", ++ port); ++ ++ ret = gpiod_direction_output(pcie->perst[port], 1); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to assert port %u PERST#\n", port); ++ ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL2(port), 1, 0); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL1(port), 0, 1); ++ ++ ret = apple_h9p_wait(pcie->base_rc + H9P_RC_COMMON_STAT, ++ H9P_RC_COMMON_STAT_INIT_DONE, ++ H9P_RC_COMMON_STAT_INIT_DONE, ++ H9P_RC_COMMON_STAT_INIT_DONE, 250000); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u init timeout\n", port); ++ ++ usleep_range(250, 1000); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL0(port), 0, 1); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL0(port), 0x100, 0); ++ usleep_range(500, 1000); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL2(port), 0, 1); ++ ++ /* T8010 wrote 3 for its Gen3 port and 0 for Gen1 ports (inferred) */ ++ writel(speed > H9P_LINK_SPEED_2_5GT ? speed : 0, ++ pcie->base_rc + H9P_RC_PORT_LINK_RATE(port)); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL1(port), 0x100, 0); ++ ++ cap = apple_h9p_read_pci_cap(pcie, port << 3, PCI_CAP_ID_EXP); ++ if (cap) ++ h9p_rmww(pcie->base_config + port * H9P_CFG_PORT_STRIDE + ++ cap + PCI_EXP_LNKCTL2, PCI_EXP_LNKCTL2_TLS, speed); ++ ++ h9p_rmw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + ++ H9P_CFG_PORT_MISC, 0, 1); ++ ++ writel(H9P_PORT_IRQMASK_PRE_LINK, ++ pcie->base_port[port] + H9P_PORT_IRQMASK); ++ writel(H9P_PORT_IRQSTAT_PRE_LINK, ++ pcie->base_port[port] + H9P_PORT_IRQSTAT); ++ ++ h9p_rmw(pcie->base_port[port] + H9P_PORT_ENABLE, 0, ++ H9P_PORT_ENABLE_APPLE); ++ writel(H9P_PORT_PWRCTL_INIT, pcie->base_port[port] + H9P_PORT_PWRCTL); ++ writel(port * 0x10001 * H9P_MSI_PER_PORT, ++ pcie->base_port[port] + H9P_PORT_MSIVECBASE); ++ ++ usleep_range(250, 1000); ++ if (!pcie->clkreq[port]) ++ return dev_err_probe(dev, -EINVAL, "port %u has no CLKREQ#\n", ++ port); ++ ret = apple_h9p_wait_gpio(pcie->clkreq[port], 1, 250000); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u CLKREQ# timeout\n", ++ port); ++ ++ ret = gpiod_direction_output(pcie->perst[port], 0); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to deassert port %u PERST#\n", port); ++ usleep_range(250, 1000); ++ ++ ret = apple_h9p_wait(pcie->base_phy + H9P_PHY_PORTMASK, ++ BIT(port), BIT(port), BIT(port), 250000); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u PHY up timeout\n", ++ port); ++ ++ if (pcie->base_phy_ip) ++ apple_h9p_phy_ip_tune(pcie, port); ++ ++ writel(H9P_PORT_IRQMASK_LINK_UP, ++ pcie->base_port[port] + H9P_PORT_IRQMASK); ++ usleep_range(5000, 10000); ++ ++ h9p_rmw(pcie->base_port[port] + H9P_PORT_LTSSMCTL, 0, ++ H9P_PORT_LTSSM_ENABLE); ++ ret = apple_h9p_wait(pcie->base_port[port] + H9P_PORT_LINKSTS, ++ H9P_PORT_LINKSTS_LTSSM, ++ FIELD_PREP(H9P_PORT_LINKSTS_LTSSM, ++ H9P_PORT_LTSSM_DETECT), ++ FIELD_PREP(H9P_PORT_LINKSTS_LTSSM, ++ H9P_PORT_LTSSM_L0), ++ 500000); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "port %u link did not reach L0 (LINKSTS %#x)\n", ++ port, readl(pcie->base_port[port] + ++ H9P_PORT_LINKSTS)); ++ ++ dev_info(dev, "port %u: link up (LINKSTS %#x)\n", port, ++ readl(pcie->base_port[port] + H9P_PORT_LINKSTS)); ++ return 0; ++} ++ ++/* lane-cfg 1: x2 x1 x1 x1; lane-cfg 3 ("X2_X1_X2"): x2 x1 x2 - */ ++static const u8 s8003_lane_start[H9P_NUM_PORTS] = { 0, 2, 3, 4 }; ++ ++static u32 apple_s8003_lane_mask(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ unsigned int width = 1; ++ ++ if (port == 0 || (port == 2 && pcie->lane_cfg == 3)) ++ width = 2; ++ ++ return GENMASK(width - 1, 0) << s8003_lane_start[port]; ++} ++ ++/* ++ * Apply an ADT-style tunable list. iOS reads apcie-phy-tunables etc. from the ++ * live ADT (records {u32 offset, u32 size, u64 mask, u64 value}); here they ++ * come from the DT as u32 triplets copied from the j71t ++ * live ADT. Like iOS, only registers whose value changes are written. ++ */ ++static int apple_h9p_apply_tunables(struct apple_h9p_pcie *pcie, ++ struct device_node *np, const char *prop, ++ void __iomem *base, u32 size, ++ const char *what) ++{ ++ struct device *dev = pcie->dev; ++ int n = of_property_count_u32_elems(np, prop); ++ unsigned int i, changed = 0; ++ u32 off, mask, val, old, new; ++ ++ if (n <= 0) ++ return 0; ++ if (n % 3) ++ return dev_err_probe(dev, -EINVAL, "%pOF: bad %s\n", np, prop); ++ ++ for (i = 0; i < n; i += 3) { ++ of_property_read_u32_index(np, prop, i, &off); ++ of_property_read_u32_index(np, prop, i + 1, &mask); ++ of_property_read_u32_index(np, prop, i + 2, &val); ++ if ((off & 3) || off > size - 4) ++ return dev_err_probe(dev, -EINVAL, ++ "%pOF: %s offset %#x out of range\n", ++ np, prop, off); ++ old = readl(base + off); ++ new = (old & ~mask) | (val & mask); ++ if (new != old) { ++ writel(new, base + off); ++ changed++; ++ } ++ } ++ ++ dev_info(dev, "%s tunables: %u of %d registers changed\n", what, ++ changed, n / 3); ++ return 0; ++} ++ ++static int apple_s8003_init_common(struct apple_h9p_pcie *pcie) ++{ ++ int ret; ++ ++ writel(H9P_RC_COMMON_CTL_INIT | ++ FIELD_PREP(H9P_RC_COMMON_CTL_LANE_CFG, pcie->lane_cfg), ++ pcie->base_rc + S8003_RC_LANE_CFG); ++ if (pcie->ans_mode == APPLE_ANS_ROUND7) ++ h9p_rmw(pcie->base_rc + S8003_RC_PORT_PWR(0), 0, ++ S8003_RC_PORT_PWR_ON); ++ ++ ret = apple_h9p_wait(pcie->base_rc + S8003_RC_STAT, ++ S8003_RC_STAT_COMMON_READY, ++ S8003_RC_STAT_COMMON_READY, ++ S8003_RC_STAT_COMMON_READY, 250000); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, "common init timeout\n"); ++ ++ writel(1, pcie->base_rc + S8003_RC_COMMON_DONE); ++ ++ /* iOS: apcie-phy-tunables, then apcie-common-tunables */ ++ ret = apple_h9p_apply_tunables(pcie, pcie->dev->of_node, ++ "apple,tunable-phy", pcie->base_phy, ++ H9P_PHY_SIZE, "PHY"); ++ if (ret) ++ return ret; ++ return apple_h9p_apply_tunables(pcie, pcie->dev->of_node, ++ "apple,tunable-common", pcie->base_rc, ++ H9P_RC_SIZE, "common"); ++} ++ ++static void apple_s8003_phy_ip_init(struct apple_h9p_pcie *pcie, ++ unsigned int port, u32 lane_mask, u32 speed) ++{ ++ void __iomem *pip = pcie->base_phy_ip; ++ unsigned long lanes = lane_mask; ++ unsigned int lane; ++ ++ h9p_rmw(pip + H9P_PHY_IP_EQ_COMMON0, 0, 0x4000); ++ h9p_rmw(pip + H9P_PHY_IP_EQ_COMMON1, 0, 0x4000); ++ h9p_rmw(pip + H9P_PHY_IP_EQ_TIME0, 0xfff, 0x64); ++ h9p_rmw(pip + H9P_PHY_IP_EQ_TIME1, 0xfff, 0x19); ++ ++ /* The 0x800 stride is per PHY lane, not per port */ ++ for_each_set_bit(lane, &lanes, BITS_PER_LONG) { ++ void __iomem *b = pip + lane * H9P_PHY_IP_PORT_STRIDE; ++ ++ h9p_rmw(b + H9P_PHY_IP_PORT_EQ_CTL, 0, 0x4000); ++ writel(0, b + H9P_PHY_IP_PORT_IDLE); ++ h9p_rmw(b + H9P_PHY_IP_PORT_EQ_PRESET, 0xfff, 0x600); ++ writel(0x3105, b + H9P_PHY_IP_PORT_RX_CTL0); ++ h9p_rmw(b + H9P_PHY_IP_PORT_RX_CTL1, 0xff, 0x9f); ++ h9p_rmw(b + H9P_PHY_IP_PORT_RX_CTL2, 0xff, 0x01); ++ if (speed == H9P_LINK_SPEED_5GT) ++ writel(0x100, b + 0x10188); ++ if (speed <= H9P_LINK_SPEED_5GT) ++ h9p_rmw(b + H9P_PHY_IP_PORT_RX_CTL3, 0x1f, 0x0a); ++ writel(0xa9, b + H9P_PHY_IP_PORT_TIMER0); ++ writel(0xa9, b + H9P_PHY_IP_PORT_TIMER1); ++ writel(0x14d, b + H9P_PHY_IP_PORT_TIMER2); ++ writel(0x14d, b + H9P_PHY_IP_PORT_TIMER3); ++ writel(0, b + H9P_PHY_IP_PORT_CLEAR0); ++ writel(0, b + H9P_PHY_IP_PORT_CLEAR1); ++ writel(0, b + H9P_PHY_IP_PORT_CLEAR2); ++ } ++} ++ ++/* iOS port power-down: refclk off, then the port's power and enable */ ++static void apple_s8003_port_off(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ void __iomem *rc = pcie->base_rc; ++ ++ h9p_rmw(rc + S8003_RC_PORT_REFCLK(port), S8003_RC_PORT_REFCLK_ON, 0); ++ udelay(S8003_PERST_TO_REFCLK_US); ++ h9p_rmw(rc + S8003_RC_PORT_PWR(port), S8003_RC_PORT_PWR_ON, 0); ++ h9p_rmw(rc + S8003_RC_PORT_CTL(port), S8003_RC_PORT_CTL_ENABLE, 0); ++} ++ ++/* iOS port power/ready sequence; no port-block access until this succeeds. */ ++static int apple_s8003_port_power_on(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ void __iomem *rc = pcie->base_rc; ++ u32 val; ++ int ret; ++ ++ dev_info(pcie->dev, "Apple ANS: STEP port %u power-on-refclk-off\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_REFCLK(port), S8003_RC_PORT_REFCLK_ON, 0); ++ /* An old kernel may have allowed CLKREQ# gating: force the clock on. */ ++ dev_info(pcie->dev, "Apple ANS: STEP port %u power-on-request\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_PWR(port), 0, ++ S8003_RC_PORT_PWR_ON | S8003_RC_PORT_REFCLK_NOGATE); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u power-ready-wait-250ms\n", port); ++ ret = apple_h9p_wait(rc + S8003_RC_STAT, S8003_RC_STAT_PORT_READY, ++ S8003_RC_STAT_PORT_READY, ++ S8003_RC_STAT_PORT_READY, 250000); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, "STEP port %u power-ready FAIL\n", port); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u power-settle-100us\n", port); ++ udelay(100); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u port-enable\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_CTL(port), 0, S8003_RC_PORT_CTL_ENABLE); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u port-ready-wait-250ms\n", port); ++ ret = readl_poll_timeout(rc + S8003_RC_PORT_READY(port), val, val & 1, ++ 10, 250000); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, "STEP port %u port-ready FAIL last=%#x\n", ++ port, val); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u port-reset-release\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_CTL(port), S8003_RC_PORT_CTL_BIT8, 0); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u refclk-on\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_REFCLK(port), 0, S8003_RC_PORT_REFCLK_ON); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u refclk-flush\n", port); ++ readl(rc + S8003_RC_PORT_REFCLK(port)); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u refclk-settle-100us\n", port); ++ udelay(S8003_T_REFCLK_TO_PERST_US); ++ return 0; ++} ++ ++/* iOS name table at kernelcache 0xfffffff007a60538, not LINKSTS values. */ ++static const char *apple_s8003_ltssm_name(u32 state) ++{ ++ static const char * const names[] = { ++ "DETECT_QUIET", "DETECT_ACT", "POLL_ACTIVE", "POLL_COMPLIANCE", ++ "POLL_CONFIG", "PRE_DETECT_QUIET", "DETECT_WAIT", "CFG_LINKWD_START", ++ "CFG_LINKWD_ACEPT", "CFG_LANENUM_WAIT", "CFG_LANENUM_ACEPT", ++ "CFG_COMPLETE", "CFG_IDLE", "RCVRY_LOCK", "RCVRY_SPEED", ++ "RCVRY_RCVRCFG", "RCVRY_IDLE", "L0", "L0S", "L123_SEND_EIDLE", ++ "L1_IDLE", "L2_IDLE", "L2_WAKE", "DISABLED_ENTRY", "DISABLED_IDLE", ++ "DISABLED", "LPBK_ENTRY", "LPBK_ACTIVE", "LPBK_EXIT", "LPBK_EXIT_TIMEOUT", ++ "HOT_RESET_ENTRY", "HOT_RESET", "RCVRY_EQ0", "RCVRY_EQ1", ++ "RCVRY_EQ2", "RCVRY_EQ3", ++ }; ++ ++ return state < ARRAY_SIZE(names) ? names[state] : "UNKNOWN"; ++} ++ ++/* Only while the port registers are powered; never read an endpoint BAR. */ ++static void apple_s8003_port_dump(struct apple_h9p_pcie *pcie, ++ unsigned int port, const char *when) ++{ ++ void __iomem *rc = pcie->base_rc; ++ void __iomem *pb = pcie->base_port[port]; ++ u32 link = readl(pb + S8003_PORT_LINKSTS); ++ u32 cdm = readl(pb + S8003_PORT_LINKCDMSTS); ++ ++ dev_info(pcie->dev, ++ "port %u %s: PERST %d ctl %#x pwr %#x refclk %#x ready %#x; LTSSMCTL %#x PME %#x link %#x LTSSM %#lx lanes %#x\n", ++ port, when, gpiod_get_value_cansleep(pcie->perst[port]), ++ readl(rc + S8003_RC_PORT_CTL(port)), ++ readl(rc + S8003_RC_PORT_PWR(port)), ++ readl(rc + S8003_RC_PORT_REFCLK(port)), ++ readl(rc + S8003_RC_PORT_READY(port)), ++ readl(pb + H9P_PORT_LTSSMCTL), readl(pb + S8003_PORT_PMETO), ++ link, ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, ++ cdm), ++ readl(pcie->base_phy + H9P_PHY_PORTMASK)); ++ dev_info(pcie->dev, ++ "Apple ANS: STEP port %u capture %s LINKSTS=%#x CDM=%#x LTSSM=%#lx/%s IRQ=%#x/%#x PMGR=%#x PHY=%#x RC2c=%#x\n", ++ port, when, link, ++ cdm, ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, cdm), ++ apple_s8003_ltssm_name(FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, cdm)), ++ readl(pb + H9P_PORT_IRQSTAT), readl(pb + H9P_PORT_IRQMASK), ++ readl(pb + S8003_PORT_LINKPMGRSTS), ++ readl(pcie->base_phy + H9P_PHY_PORTMASK), ++ readl(pcie->base_rc + S8003_RC_STAT)); ++ if (pcie->link_cap[port]) { ++ void __iomem *cfg = pcie->base_config + port * H9P_CFG_PORT_STRIDE; ++ u16 cap = pcie->link_cap[port]; ++ ++ dev_info(pcie->dev, ++ "Apple ANS: STEP port %u root-capture LNKCTL=%#x LNKCTL2=%#x LNKCAP2=%#x cfgA0=%#x cfg670=%#x cfg76c=%#x\n", ++ port, readw(cfg + cap + PCI_EXP_LNKCTL), ++ readw(cfg + cap + PCI_EXP_LNKCTL2), readl(cfg + cap + PCI_EXP_LNKCAP2), ++ readw(cfg + 0xa0), readl(cfg + 0x670), readl(cfg + 0x76c)); ++ } ++} ++ ++/* Stop the retained LTSSM before changing clocks or releasing PERST#. */ ++static void apple_s8003_port_stop(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ void __iomem *pb = pcie->base_port[port]; ++ ++ writel(H9P_PORT_IRQMASK_PRE_LINK, pb + H9P_PORT_IRQMASK); ++ h9p_rmw(pb + H9P_PORT_LTSSMCTL, H9P_PORT_LTSSM_ENABLE, 0); ++ readl(pb + H9P_PORT_LTSSMCTL); ++ /* Cancel the previous kernel's PME_Turn_Off/L2 request. */ ++ h9p_writel_flush(0, pb + S8003_PORT_PMETO); ++ writel(H9P_PORT_IRQSTAT_PRE_LINK, pb + H9P_PORT_IRQSTAT); ++} ++ ++/* AppleEmbeddedPCIEPort::enableGated + AppleS800xPCIePort::enablePortHardware */ ++static int apple_s8003_setup_port_round7(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ void __iomem *rc = pcie->base_rc; ++ void __iomem *pb = pcie->base_port[port]; ++ struct device *dev = pcie->dev; ++ u32 lanes = apple_s8003_lane_mask(pcie, port); ++ u32 speed = pcie->max_speed[port]; ++ u16 lnkcap2_speeds; ++ u64 cap; ++ u32 val; ++ int ret; ++ ++ if (pcie->lane_cfg == 3 && port == 3) ++ return dev_err_probe(dev, -EINVAL, ++ "port 3 is unusable with lane-cfg 3\n"); ++ ++ /* No port-block access before the port is powered (iOS doesn't) */ ++ dev_info(dev, "port %u: bringing up link (Gen%u, lanes %#x)\n", port, ++ speed, lanes); ++ ++ /* The endpoint stays in reset (PERST# was asserted at GPIO request) */ ++ ret = gpiod_direction_output(pcie->perst[port], 1); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to assert port %u PERST#\n", port); ++ ++ /* ++ * A kexec can leave the port off, in L2, or training in compliance. ++ * First power its register block, stop the old state machine, then ++ * make a real off/on edge. Reapplying tunables alone does not reset it. ++ * Do not reset the shared PHY/PMGR: other ports and DARTs share it. ++ */ ++ if (pcie->nvme_mode[port]) { ++ writel(0x1, rc + S8003_RC_NVME_MODE0); ++ udelay(100); ++ h9p_writel_flush(1, rc + S8003_RC_NVME_MODE1); ++ } ++ ret = apple_s8003_port_power_on(pcie, port); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u initial power timeout\n", port); ++ apple_s8003_port_dump(pcie, port, "inherited"); ++ apple_s8003_port_stop(pcie, port); ++ if (!port && pcie->nvmmu.base && ++ of_machine_is_compatible("apple,j71t")) { ++ dev_info(dev, "ANS inherited NVMMU: SART %#x TCB %#x:%08x; retiring under PERST\n", ++ readl(pcie->nvmmu.base + H9P_NVMMU_SART_CTRL), ++ readl(pcie->nvmmu.base + H9P_NVMMU_TCB_BASE_HI), ++ readl(pcie->nvmmu.base + H9P_NVMMU_TCB_BASE_LO)); ++ h9p_writel_flush(0, pcie->nvmmu.base + H9P_NVMMU_SART_CTRL); ++ } ++ apple_s8003_port_off(pcie, port); ++ if (pcie->nvme_mode[port]) ++ h9p_writel_flush(0, rc + S8003_RC_NVME_MODE1); ++ usleep_range(1000, 2000); ++ if (pcie->nvme_mode[port]) { ++ writel(0x1, rc + S8003_RC_NVME_MODE0); ++ udelay(100); ++ h9p_writel_flush(1, rc + S8003_RC_NVME_MODE1); ++ } ++ ret = apple_s8003_port_power_on(pcie, port); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u restart power timeout\n", port); ++ apple_s8003_port_stop(pcie, port); ++ writel(3, rc + S8003_RC_PORT_LSCNT(port)); ++ val = readl(pb + S8003_PORT_LINKPMGRSTS); ++ if (val) ++ writel(val, pb + S8003_PORT_LINKPMGRSTS); ++ ++ cap = apple_h9p_read_pci_cap(pcie, port << 3, PCI_CAP_ID_EXP); ++ if (!cap) ++ return dev_err_probe(dev, -ENODEV, ++ "port %u root port has no PCIe capability\n", ++ port); ++ /* Clamp to LNKCAP2 like iOS setMaximumLinkSpeed */ ++ lnkcap2_speeds = (readl(pcie->base_config + port * H9P_CFG_PORT_STRIDE + ++ cap + PCI_EXP_LNKCAP2) >> 1) & 0x7f; ++ while (speed > H9P_LINK_SPEED_2_5GT && ++ !(lnkcap2_speeds & BIT(speed - 1))) ++ speed--; ++ dev_info(dev, "port %u: restart LNKCTL %#x LNKCTL2 %#x\n", port, ++ readw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL), ++ readw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL2)); ++ h9p_rmww(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_LD | ++ PCI_EXP_LNKCTL_RL, 0); ++ h9p_rmww(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL2, PCI_EXP_LNKCTL2_TLS | PCI_EXP_LNKCTL2_ENTER_COMP, ++ speed); ++ ++ /* iOS: pcie-rc-tunables (root port config), apcie-config-tunables */ ++ ret = apple_h9p_apply_tunables(pcie, pcie->port_nodes[port], ++ "apple,tunable-rc", ++ pcie->base_config + ++ port * H9P_CFG_PORT_STRIDE, ++ H9P_CFG_SIZE, "root port"); ++ if (ret) ++ return ret; ++ ret = apple_h9p_apply_tunables(pcie, pcie->port_nodes[port], ++ "apple,tunable-config", pb, ++ H9P_PORT_SIZE, "port"); ++ if (ret) ++ return ret; ++ ++ writel(H9P_PORT_IRQMASK_PRE_LINK, pb + H9P_PORT_IRQMASK); ++ writel(H9P_PORT_IRQSTAT_PRE_LINK, pb + H9P_PORT_IRQSTAT); ++ h9p_rmw(pb + H9P_PORT_ENABLE, 0, H9P_PORT_ENABLE_APPLE); ++ writel(1 | ilog2(H9P_MSI_PER_PORT) << 4, pb + S8003_PORT_MSICTL); ++ writel(port * 0x10001 * H9P_MSI_PER_PORT, pb + H9P_PORT_MSIVECBASE); ++ ++ /* Clock is stable and reset is held even when GPIO ownership survived. */ ++ msleep(S8003_PORT_RESET_MS); ++ ret = gpiod_direction_output(pcie->perst[port], 0); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to deassert port %u PERST#\n", port); ++ ++ ret = apple_h9p_wait(pcie->base_phy + H9P_PHY_PORTMASK, lanes, lanes, ++ lanes, 250000); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "port %u PHY lanes %#x not ready (%#x)\n", ++ port, lanes, ++ readl(pcie->base_phy + H9P_PHY_PORTMASK)); ++ ++ apple_s8003_phy_ip_init(pcie, port, lanes, speed); ++ ++ writel(H9P_PORT_IRQMASK_LINK_UP, pb + H9P_PORT_IRQMASK); ++ h9p_rmw(pb + H9P_PORT_LTSSMCTL, 0, H9P_PORT_LTSSM_ENABLE); ++ ++ ret = readl_poll_timeout(pb + S8003_PORT_LINKSTS, val, ++ val & S8003_PORT_LINKSTS_UP, 1000, 500000); ++ val = FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, ++ readl(pb + S8003_PORT_LINKCDMSTS)); ++ if (ret) { ++ apple_s8003_port_dump(pcie, port, "training timeout"); ++ return dev_err_probe(dev, ret, ++ "port %u link did not come up (LTSSM %#x)\n", ++ port, val); ++ } ++ ++ /* Link-up is reported during the Gen2 recovery; let it settle in L0 */ ++ ret = readl_poll_timeout(pb + S8003_PORT_LINKCDMSTS, val, ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, val) == ++ S8003_LTSSM_L0, 100, 100000); ++ if (ret || !(readl(pb + S8003_PORT_LINKSTS) & S8003_PORT_LINKSTS_UP)) { ++ apple_s8003_port_dump(pcie, port, "L0 timeout"); ++ return -ETIMEDOUT; ++ } ++ dev_info(dev, "port %u: link up (LTSSM %#lx)\n", port, ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, val)); ++ return 0; ++} ++ ++/* A timeout cannot cancel a stalled bus transaction. Keep these accesses ++ * host-side, with the register block powered, and log before every phase. ++ * A returning failure drops the port; the early-USB initramfs reaches rescue. ++ */ ++/* Keep the 100 us link poll and its original deadline; sample every 20 ms. ++ * Called only by LINKSTS waits after power/ready and capability setup. ++ */ ++static bool apple_s8003_link_sample(struct apple_h9p_pcie *pcie, ++ unsigned int port, u64 *next) ++{ ++ u64 now = ktime_get_ns(); ++ ++ if (now >= *next) { ++ apple_s8003_port_dump(pcie, port, "training-sample"); ++ *next = now + 20000000; ++ } ++ return true; ++} ++ ++static int apple_s8003_port_wait(struct apple_h9p_pcie *pcie, unsigned int port, ++ void __iomem *addr, u32 mask, u32 expected, ++ unsigned int timeout_us, const char *step) ++{ ++ u32 val; ++ u64 next = 0; ++ bool link = addr == pcie->base_port[port] + S8003_PORT_LINKSTS; ++ int ret; ++ ++ dev_info(pcie->dev, "Apple ANS: STEP port %u %s wait timeout_us=%u\n", ++ port, step, timeout_us); ++ ret = readl_poll_timeout(addr, val, (!link || apple_s8003_link_sample(pcie, port, &next)) && ++ (val & mask) == expected, ++ 100, timeout_us); ++ if (ret) ++ dev_err(pcie->dev, "Apple ANS: STEP port %u %s FAIL %d last=%#x mask=%#x expected=%#x\n", ++ port, step, ret, val, mask, expected); ++ else ++ dev_info(pcie->dev, "Apple ANS: STEP port %u %s done last=%#x\n", ++ port, step, val); ++ return ret; ++} ++ ++static int apple_s8003_link_finish(struct apple_h9p_pcie *pcie, ++ unsigned int port, unsigned int timeout_us) ++{ ++ void __iomem *pb = pcie->base_port[port]; ++ struct device *dev = pcie->dev; ++ u32 val; ++ int ret, sample; ++ ++ ret = apple_s8003_port_wait(pcie, port, pb + S8003_PORT_LINKSTS, ++ S8003_PORT_LINKSTS_UP, S8003_PORT_LINKSTS_UP, ++ timeout_us, "link-up"); ++ if (ret) { ++ apple_s8003_port_dump(pcie, port, "link-up-failed"); ++ return ret; ++ } ++ ret = apple_s8003_port_wait(pcie, port, pb + S8003_PORT_LINKCDMSTS, ++ S8003_PORT_LINKCDMSTS_LTSSM, ++ S8003_LTSSM_L0 << 9, 100000, "link-L0"); ++ if (ret) { ++ apple_s8003_port_dump(pcie, port, "link-L0-failed"); ++ return ret; ++ } ++ dev_info(dev, "Apple ANS: STEP port %u link-up-confirm\n", port); ++ if (!(readl(pb + S8003_PORT_LINKSTS) & S8003_PORT_LINKSTS_UP)) { ++ apple_s8003_port_dump(pcie, port, "link-confirm-failed"); ++ return dev_err_probe(dev, -ENOLINK, "STEP port %u link lost after L0\n", port); ++ } ++ if (!port && pcie->ans_deep_recovery) { ++ /* One transient UP/L0 sample cannot authorize config/BAR traffic. */ ++ dev_info(dev, "Apple ANS: STEP port 0 deep-stable wait-100ms\n"); ++ for (sample = 0; sample < 100; sample++) { ++ usleep_range(1000, 1100); ++ val = readl(pb + S8003_PORT_LINKCDMSTS); ++ if (!(readl(pb + S8003_PORT_LINKSTS) & S8003_PORT_LINKSTS_UP) || ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, val) != S8003_LTSSM_L0) { ++ apple_s8003_port_dump(pcie, port, "deep-stable-failed"); ++ return dev_err_probe(dev, -ENOLINK, ++ "STEP port 0 deep-stable FAIL sample=%d\n", ++ sample); ++ } ++ } ++ dev_info(dev, "Apple ANS: STEP port 0 deep-stable done\n"); ++ } ++ val = readl(pb + S8003_PORT_LINKCDMSTS); ++ dev_info(dev, "Apple ANS: STEP port %u full-bringup-done\n", port); ++ dev_info(dev, "port %u: link up (LTSSM %#lx)\n", port, ++ FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, val)); ++ return 0; ++} ++ ++static int apple_s8003_setup_port_reset(struct apple_h9p_pcie *pcie, ++ unsigned int port, bool keep_power) ++{ ++ void __iomem *rc = pcie->base_rc; ++ void __iomem *pb = pcie->base_port[port]; ++ struct device *dev = pcie->dev; ++ u32 lanes = apple_s8003_lane_mask(pcie, port); ++ u32 speed = !port && pcie->ans_deep_recovery ? ++ H9P_LINK_SPEED_2_5GT : pcie->max_speed[port]; ++ u16 lnkcap2_speeds; ++ u64 cap; ++ u32 val; ++ int ret; ++ ++ if (pcie->lane_cfg == 3 && port == 3) ++ return dev_err_probe(dev, -EINVAL, ++ "port 3 is unusable with lane-cfg 3\n"); ++ ++ /* No port-block access before the port is powered (iOS doesn't) */ ++ dev_info(dev, "port %u: bringing up link (Gen%u, lanes %#x)\n", port, ++ speed, lanes); ++ ++ dev_info(dev, "Apple ANS: STEP port %u perst-assert\n", port); ++ /* The endpoint stays in reset (PERST# was asserted at GPIO request) */ ++ ret = gpiod_direction_output(pcie->perst[port], 1); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to assert port %u PERST#\n", port); ++ ++ /* disablePortHardware: force_active(false) records inactive. No live ++ * tables are zeroed; reactivation is mandatory in endpoint preinit. ++ * rc+124 reset, 60us, rc+114 refclk, rc+100 power (not the legacy ++ * helper's misleading CTL/PWR/REFCLK names). ++ */ ++ dev_info(dev, "Apple ANS: STEP port %u inherited-power-check\n", port); ++ if ((readl(rc + S8003_RC_PORT_CTL(port)) & S8003_RC_PORT_CTL_ENABLE) && ++ (readl(rc + S8003_RC_PORT_PWR(port)) & S8003_RC_PORT_PWR_ON)) { ++ dev_info(dev, "Apple ANS: STEP port %u inherited-stop\n", port); ++ apple_s8003_port_stop(pcie, port); ++ ret = apple_s8003_port_wait(pcie, port, pb + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "inherited-LTSSM-stop"); ++ if (ret) ++ return ret; ++ } ++ if (keep_power) { ++ /* PERST and the stopped LTSSM isolate the endpoint. Keep the ++ * already stable clock instead of making another off/on edge. ++ * Controller queues and DMA translations are rebuilt as usual. ++ */ ++ dev_info(dev, "Apple ANS: STEP port 0 reset with power/refclk retained\n"); ++ h9p_rmw(rc + S8003_RC_PORT_PWR(port), 0, ++ S8003_RC_PORT_REFCLK_NOGATE); ++ readl(rc + S8003_RC_PORT_PWR(port)); ++ } else { ++ dev_info(dev, "Apple ANS: STEP port %u refclk-off\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_REFCLK(port), S8003_RC_PORT_REFCLK_ON, 0); ++ dev_info(dev, "Apple ANS: STEP port %u perst-to-refclk-delay-60us\n", port); ++ udelay(S8003_PERST_TO_REFCLK_US); ++ dev_info(dev, "Apple ANS: STEP port %u power-off\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_PWR(port), S8003_RC_PORT_PWR_ON, 0); ++ dev_info(dev, "Apple ANS: STEP port %u port-disable\n", port); ++ h9p_rmw(rc + S8003_RC_PORT_CTL(port), S8003_RC_PORT_CTL_ENABLE, 0); ++ dev_info(dev, "Apple ANS: STEP port %u power-off-settle-10ms\n", port); ++ usleep_range(10000, 11000); ++ ret = apple_s8003_port_wait(pcie, port, rc + S8003_RC_PORT_CTL(port), ++ S8003_RC_PORT_CTL_ENABLE, 0, 250000, "port-off"); ++ if (ret) ++ return ret; ++ if (pcie->nvme_mode[port]) { ++ dev_info(dev, "Apple ANS: STEP port %u nvme-mode0\n", port); ++ writel(1, rc + S8003_RC_NVME_MODE0); ++ usleep_range(100, 200); ++ dev_info(dev, "Apple ANS: STEP port %u nvme-mode1\n", port); ++ h9p_writel_flush(1, rc + S8003_RC_NVME_MODE1); ++ } ++ ret = apple_s8003_port_power_on(pcie, port); ++ if (ret) ++ return dev_err_probe(dev, ret, "port %u full-cycle power timeout\n", port); ++ } ++ dev_info(dev, "Apple ANS: STEP port %u powered-stop\n", port); ++ apple_s8003_port_stop(pcie, port); ++ ret = apple_s8003_port_wait(pcie, port, pb + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, "powered-LTSSM-stop"); ++ if (ret) ++ return ret; ++ dev_info(dev, "Apple ANS: STEP port %u link-counters\n", port); ++ writel(3, rc + S8003_RC_PORT_LSCNT(port)); ++ dev_info(dev, "Apple ANS: STEP port %u link-pmgr-ack\n", port); ++ val = readl(pb + S8003_PORT_LINKPMGRSTS); ++ if (val) ++ writel(val, pb + S8003_PORT_LINKPMGRSTS); ++ ++ dev_info(dev, "Apple ANS: STEP port %u root-capability-scan-max48\n", port); ++ cap = apple_h9p_read_pci_cap(pcie, port << 3, PCI_CAP_ID_EXP); ++ if (!cap) ++ return dev_err_probe(dev, -ENODEV, ++ "port %u root port has no PCIe capability\n", ++ port); ++ pcie->link_cap[port] = cap; ++ /* Clamp to LNKCAP2 like iOS setMaximumLinkSpeed */ ++ dev_info(dev, "Apple ANS: STEP port %u link-speed-config\n", port); ++ lnkcap2_speeds = (readl(pcie->base_config + port * H9P_CFG_PORT_STRIDE + ++ cap + PCI_EXP_LNKCAP2) >> 1) & 0x7f; ++ while (speed > H9P_LINK_SPEED_2_5GT && ++ !(lnkcap2_speeds & BIT(speed - 1))) ++ speed--; ++ dev_info(dev, "port %u: restart LNKCTL %#x LNKCTL2 %#x\n", port, ++ readw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL), ++ readw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL2)); ++ h9p_rmww(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_LD | ++ PCI_EXP_LNKCTL_RL, 0); ++ h9p_rmww(pcie->base_config + port * H9P_CFG_PORT_STRIDE + cap + ++ PCI_EXP_LNKCTL2, PCI_EXP_LNKCTL2_TLS | PCI_EXP_LNKCTL2_ENTER_COMP, ++ speed); ++ ++ /* iOS: pcie-rc-tunables (root port config), apcie-config-tunables */ ++ dev_info(dev, "Apple ANS: STEP port %u root-tunables\n", port); ++ ret = apple_h9p_apply_tunables(pcie, pcie->port_nodes[port], ++ "apple,tunable-rc", ++ pcie->base_config + ++ port * H9P_CFG_PORT_STRIDE, ++ H9P_CFG_SIZE, "root port"); ++ if (ret) ++ return ret; ++ dev_info(dev, "Apple ANS: STEP port %u port-tunables\n", port); ++ ret = apple_h9p_apply_tunables(pcie, pcie->port_nodes[port], ++ "apple,tunable-config", pb, ++ H9P_PORT_SIZE, "port"); ++ if (ret) ++ return ret; ++ ++ dev_info(dev, "Apple ANS: STEP port %u irq-mask\n", port); ++ writel(H9P_PORT_IRQMASK_PRE_LINK, pb + H9P_PORT_IRQMASK); ++ dev_info(dev, "Apple ANS: STEP port %u irq-ack\n", port); ++ writel(H9P_PORT_IRQSTAT_PRE_LINK, pb + H9P_PORT_IRQSTAT); ++ dev_info(dev, "Apple ANS: STEP port %u apple-port-enable\n", port); ++ h9p_rmw(pb + H9P_PORT_ENABLE, 0, H9P_PORT_ENABLE_APPLE); ++ dev_info(dev, "Apple ANS: STEP port %u msi-control\n", port); ++ writel(1 | ilog2(H9P_MSI_PER_PORT) << 4, pb + S8003_PORT_MSICTL); ++ dev_info(dev, "Apple ANS: STEP port %u msi-vector-base\n", port); ++ writel(port * 0x10001 * H9P_MSI_PER_PORT, pb + H9P_PORT_MSIVECBASE); ++ ++ /* Tunables may include link controls. Enforce the recovery speed LAST. */ ++ if (!port && pcie->ans_deep_recovery) { ++ h9p_rmww(pcie->base_config + cap + PCI_EXP_LNKCTL2, ++ PCI_EXP_LNKCTL2_TLS | PCI_EXP_LNKCTL2_ENTER_COMP, ++ H9P_LINK_SPEED_2_5GT); ++ val = readw(pcie->base_config + cap + PCI_EXP_LNKCTL2); ++ dev_info(dev, "Apple ANS: STEP port 0 deep-Gen1 LNKCTL2=%#x lanes=%#x\n", ++ val, lanes); ++ if ((val & PCI_EXP_LNKCTL2_TLS) != H9P_LINK_SPEED_2_5GT) ++ return -EIO; ++ } ++ ++ /* Clock is stable and reset is held even when GPIO ownership survived. */ ++ dev_info(dev, "Apple ANS: STEP port %u perst-hold-100ms\n", port); ++ msleep(S8003_PORT_RESET_MS); ++ dev_info(dev, "Apple ANS: STEP port %u perst-release\n", port); ++ ret = gpiod_direction_output(pcie->perst[port], 0); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to deassert port %u PERST#\n", port); ++ ++ ret = apple_s8003_port_wait(pcie, port, pcie->base_phy + H9P_PHY_PORTMASK, ++ lanes, lanes, 250000, "phy-lanes-ready"); ++ if (ret) ++ return ret; ++ ++ dev_info(dev, "Apple ANS: STEP port %u phy-ip-init\n", port); ++ apple_s8003_phy_ip_init(pcie, port, lanes, speed); ++ ++ dev_info(dev, "Apple ANS: STEP port %u link-irq-mask\n", port); ++ writel(H9P_PORT_IRQMASK_LINK_UP, pb + H9P_PORT_IRQMASK); ++ dev_info(dev, "Apple ANS: STEP port %u ltssm-start\n", port); ++ h9p_rmw(pb + H9P_PORT_LTSSMCTL, 0, H9P_PORT_LTSSM_ENABLE); ++ ++ return apple_s8003_link_finish(pcie, port, 500000); ++} ++ ++/* Recovery and cold boots always retain the complete off/on sequence. */ ++static int apple_s8003_setup_port_full(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ return apple_s8003_setup_port_reset(pcie, port, false); ++} ++ ++static int apple_s8003_setup_port_once(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ u32 ctl, pwr, reset; ++ ++ if (port || pcie->ans_mode == APPLE_ANS_ROUND7) ++ return apple_s8003_setup_port_round7(pcie, port); ++ /* L0 proves link training, not ownership of inherited DMA state. ++ * Both paths assert PERST before installing new translations. ++ * The outgoing drain/SHST/CC-disable/BME contract is mandatory. ++ */ ++ dev_info(pcie->dev, "Apple ANS: %s full port bring-up\n", ++ ans_boot_modes[pcie->ans_mode]); ++ ctl = readl(pcie->base_rc + S8003_RC_PORT_CTL(0)); ++ pwr = readl(pcie->base_rc + S8003_RC_PORT_PWR(0)); ++ reset = readl(pcie->base_rc + S8003_RC_PORT_REFCLK(0)); ++ dev_info(pcie->dev, "Apple ANS: incoming port 0 RC124=%#x RC114=%#x RC100=%#x; policy=%s\n", ++ reset, pwr, ctl, ans_keep_power && pcie->ans_mode == APPLE_ANS_HANDOFF ? ++ "powered-reset-trial/full-fallback" : "full-down/full-bringup"); ++ /* Accept only the opt-in, powered, reset-held warm handoff. A cold ++ * boot, old outgoing kernel, or incomplete certificate uses full reset. ++ * No live-link/old-queue shortcut: setup still holds PERST for 100 ms, ++ * replays port/PHY/MSI setup and requires fresh UP/L0 before enumeration. ++ */ ++ if (ans_keep_power && pcie->ans_mode == APPLE_ANS_HANDOFF && ++ of_property_read_bool(of_chosen, "linux,booted-from-kexec") && ++ (ctl & S8003_RC_PORT_CTL_ENABLE) && ++ !(ctl & S8003_RC_PORT_CTL_BIT8) && ++ (pwr & S8003_RC_PORT_PWR_ON) && ++ (pwr & S8003_RC_PORT_REFCLK_NOGATE) && ++ (reset & S8003_RC_PORT_REFCLK_ON) && ++ (readl(pcie->base_rc + S8003_RC_PORT_READY(0)) & BIT(0)) && ++ gpiod_get_value_cansleep(pcie->perst[0]) == 1) { ++ int ret; ++ ++ ret = apple_s8003_setup_port_reset(pcie, port, true); ++ if (!ret || (ret != -ETIMEDOUT && ret != -ENOLINK)) ++ return ret; ++ dev_warn(pcie->dev, ++ "Apple ANS: powered reset failed %d; falling back to full port cycle\n", ++ ret); ++ } ++ return apple_s8003_setup_port_full(pcie, port); ++} ++ ++/* Queues synchronized and bus mastering off. Exactly one caller owns the ++ * recovery quota; never reset a shared PMGR domain or the Wi-Fi port. ++ */ ++int apple_ans_restart_port(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_ans_host(dev); ++ int ret; ++ ++ if (!pcie || pcie->ans_mode == APPLE_ANS_ROUND7) ++ return -EOPNOTSUPP; ++ pcie->ans_mode = pcie->ans_mode == APPLE_ANS_HANDOFF ? ++ APPLE_ANS_FULL_CYCLE : APPLE_ANS_HANDOFF; ++ dev_warn(dev, "Apple ANS: one recovery using %s bring-up\n", ++ ans_boot_modes[pcie->ans_mode]); ++ /* Both trial modes cycle the port, so both must rearm translations. */ ++ ret = apple_ans_dart_set_active(dev, false); ++ if (ret) ++ return ret; ++ pcie->nvmmu.active = false; ++ /* Both modes normalize the port before the next controller enable. */ ++ ret = apple_s8003_setup_port_once(pcie, 0); ++ if (ret) ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(0)); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_ans_restart_port); ++ ++/* Both initial NVMe and late Wi-Fi ports recover from retained state. */ ++static int apple_s8003_setup_port(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ unsigned int attempt; ++ int ret; ++ ++ for (attempt = 0; attempt < S8003_PORT_LINK_TRIES; attempt++) { ++ ret = apple_s8003_setup_port_once(pcie, port); ++ if (!ret) ++ return 0; ++ gpiod_direction_output(pcie->perst[port], 1); ++ if (ret != -ETIMEDOUT) ++ break; ++ if (attempt + 1 < S8003_PORT_LINK_TRIES) ++ dev_warn(pcie->dev, "port %u: retrying with port reset\n", port); ++ } ++ return ret; ++} ++ ++/* A9 has an external PCIe S3E, not an AP-visible ANS2/RTKit CPU. ++ * No ps_ans or endpoint PWREN exists in this DT. This resets only the ++ * port/endpoint PCIe interface: do not claim it removes the S3E NAND rail. ++ * PERST stays asserted throughout a 1 s refclk/port-off hold. Only port 0 ++ * changes; shared RC/PHY/PMGR and Wi-Fi state must survive. ++ */ ++static int apple_s8003_deep_cycle(struct apple_h9p_pcie *pcie) ++{ ++ void __iomem *rc = pcie->base_rc; ++ u32 ctl, pwr, reset; ++ int ret; ++ ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(0)); ++ dev_warn(pcie->dev, "Apple ANS: STEP port 0 deep-recovery begin; S3E rail/firmware reset unavailable\n"); ++ ret = gpiod_direction_output(pcie->perst[0], 1); ++ if (ret) ++ return ret; ++ if ((readl(rc + S8003_RC_PORT_CTL(0)) & S8003_RC_PORT_CTL_ENABLE) && ++ (readl(rc + S8003_RC_PORT_PWR(0)) & S8003_RC_PORT_PWR_ON)) { ++ apple_s8003_port_dump(pcie, 0, "deep-before-stop"); ++ apple_s8003_port_stop(pcie, 0); ++ ret = apple_s8003_port_wait(pcie, 0, ++ pcie->base_port[0] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "deep-stop"); ++ if (ret) ++ return ret; ++ } ++ apple_s8003_port_off(pcie, 0); ++ ctl = readl(rc + S8003_RC_PORT_CTL(0)); ++ pwr = readl(rc + S8003_RC_PORT_PWR(0)); ++ reset = readl(rc + S8003_RC_PORT_REFCLK(0)); ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 deep-off RC124=%#x RC114=%#x RC100=%#x hold-1000ms\n", ++ reset, pwr, ctl); ++ if ((ctl | pwr | reset) & BIT(0)) ++ return -EIO; ++ msleep(1000); ++ pcie->ans_deep_recovery = true; ++ ret = apple_s8003_setup_port_full(pcie, 0); ++ pcie->ans_deep_recovery = false; ++ if (ret) { ++ gpiod_direction_output(pcie->perst[0], 1); ++ if ((readl(rc + S8003_RC_PORT_CTL(0)) & S8003_RC_PORT_CTL_ENABLE) && ++ (readl(rc + S8003_RC_PORT_PWR(0)) & S8003_RC_PORT_PWR_ON) && ++ (readl(rc + S8003_RC_PORT_READY(0)) & BIT(0))) { ++ apple_s8003_port_stop(pcie, 0); ++ if (apple_s8003_port_wait(pcie, 0, ++ pcie->base_port[0] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "deep-failed-stop")) ++ return ret; ++ } ++ /* No powered-off port or endpoint readback. */ ++ apple_s8003_port_off(pcie, 0); ++ dev_err(pcie->dev, "Apple ANS: STEP port 0 deep-recovery FAIL %d; PERST asserted, port off\n", ++ ret); ++ } else { ++ pcie->max_speed[0] = H9P_LINK_SPEED_2_5GT; ++ pcie->ans_gen1 = true; ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 deep-recovery done; Gen1 retained\n"); ++ } ++ return ret; ++} ++ ++/* No power toggle: restart LTSSM, optionally re-run lane init at Gen1, ++ * then issue the standard RW1S LNKCTL.RL trigger. Never poll RL for one. ++ */ ++static int apple_s8003_retrain(struct apple_h9p_pcie *pcie, bool gen1) ++{ ++ void __iomem *pb = pcie->base_port[0]; ++ void __iomem *rc = pcie->base_rc; ++ u16 cap = pcie->link_cap[0]; ++ u32 state; ++ int ret; ++ ++ if (!cap || gpiod_get_value_cansleep(pcie->perst[0]) || ++ !(readl(rc + S8003_RC_PORT_CTL(0)) & BIT(0)) || ++ !(readl(rc + S8003_RC_PORT_PWR(0)) & BIT(0)) || ++ !(readl(rc + S8003_RC_PORT_READY(0)) & BIT(0))) ++ return -ENOLINK; ++ state = FIELD_GET(S8003_PORT_LINKCDMSTS_LTSSM, ++ readl(pb + S8003_PORT_LINKCDMSTS)); ++ if (state >= 0x17 && state <= 0x19) { ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 retrain SKIP disabled LTSSM=%#x\n", ++ state); ++ return -ENOLINK; ++ } ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 retrain-%s begin\n", gen1 ? "Gen1" : "current"); ++ apple_s8003_port_dump(pcie, 0, "before-retrain"); ++ apple_s8003_port_stop(pcie, 0); ++ ret = apple_s8003_port_wait(pcie, 0, pb + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 20000, "retrain-stop"); ++ if (ret) ++ return ret; ++ if (gen1) { ++ h9p_rmww(pcie->base_config + cap + PCI_EXP_LNKCTL2, ++ PCI_EXP_LNKCTL2_TLS | PCI_EXP_LNKCTL2_ENTER_COMP, ++ H9P_LINK_SPEED_2_5GT); ++ apple_s8003_phy_ip_init(pcie, 0, apple_s8003_lane_mask(pcie, 0), ++ H9P_LINK_SPEED_2_5GT); ++ } ++ h9p_rmw(pb + H9P_PORT_LTSSMCTL, 0, H9P_PORT_LTSSM_ENABLE); ++ h9p_rmww(pcie->base_config + cap + PCI_EXP_LNKCTL, ++ PCI_EXP_LNKCTL_LD, PCI_EXP_LNKCTL_RL); ++ pcie->ans_deep_recovery = true; ++ ret = apple_s8003_link_finish(pcie, 0, 100000); ++ pcie->ans_deep_recovery = false; ++ if (!ret && gen1) { ++ pcie->ans_gen1 = true; ++ pcie->max_speed[0] = H9P_LINK_SPEED_2_5GT; ++ } ++ return ret; ++} ++ ++static int apple_s8003_replay_phy(struct apple_h9p_pcie *pcie) ++{ ++ void __iomem *rc = pcie->base_rc; ++ unsigned int port; ++ int ret; ++ ++ if (!pcie->ans_pre_enumeration || ++ ((readl(rc + S8003_RC_PORT_CTL(0)) | ++ readl(rc + S8003_RC_PORT_PWR(0)) | ++ readl(rc + S8003_RC_PORT_REFCLK(0))) & BIT(0))) ++ return -EBUSY; ++ for (port = 0; port < H9P_NUM_PORTS; port++) { ++ if (!pcie->perst[port]) ++ continue; ++ if (gpiod_get_value_cansleep(pcie->perst[port]) != 1) ++ return -EBUSY; ++ if (!(readl(rc + S8003_RC_PORT_CTL(port)) & BIT(0)) || ++ !(readl(rc + S8003_RC_PORT_PWR(port)) & BIT(0))) ++ continue; ++ apple_s8003_port_stop(pcie, port); ++ ret = apple_s8003_port_wait(pcie, port, ++ pcie->base_port[port] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "shared-PHY-stop"); ++ if (ret) ++ return ret; ++ } ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 shared-PHY configure replay (pre-scan, all PERST asserted)\n"); ++ return apple_s8003_init_common(pcie); ++} ++ ++static int apple_s8003_deep_recover(struct apple_h9p_pcie *pcie) ++{ ++ unsigned int attempt; ++ int ret; ++ ++ dev_warn(pcie->dev, "Apple ANS: STEP port 0 deep-ladder begin\n"); ++ ret = apple_s8003_retrain(pcie, false); ++ if (!ret) ++ return 0; ++ ret = apple_s8003_retrain(pcie, true); ++ if (!ret) ++ return 0; ++ for (attempt = 0; attempt < 3; attempt++) { ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 deep-cycle %u/3\n", attempt + 1); ++ ret = apple_s8003_deep_cycle(pcie); ++ if (!ret) ++ return 0; ++ if (ret != -ETIMEDOUT && ret != -ENOLINK) ++ break; ++ } ++ /* A common PHY replay changes ALL lanes. Only the initial host probe ++ * precedes Wi-Fi/PCI enumeration; an explicit rescue retry cannot do it. ++ */ ++ if (pcie->ans_pre_enumeration && (ret == -ETIMEDOUT || ret == -ENOLINK)) { ++ ret = apple_s8003_replay_phy(pcie); ++ if (!ret) ++ ret = apple_s8003_deep_cycle(pcie); ++ if (!ret) ++ return 0; ++ } else { ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 shared-PHY replay SKIP after probe or non-link failure\n"); ++ } ++ /* DART platform providers have already probed before this host. The ++ * warm keep-on policy also makes genpd power_off a no-op. Detaching ++ * only this consumer cannot certify actual-off and risks live DART ++ * state. Do not call that an apcie or S3E power cycle. ++ */ ++ dev_err(pcie->dev, "Apple ANS: STEP port 0 apcie-domain-cycle SKIP: DART consumers/keep-on; actual-off unproven\n"); ++ dev_err(pcie->dev, "Apple ANS: deep recovery exhausted %d; DFU required if explicit rescue retry fails\n", ++ ret); ++ return ret; ++} ++ ++/* Before enumeration there is no controller worker to recover a missing root. ++ * After the usual electrical attempts, allow one cold port-0 retry. Endpoint ++ * preinit then rebuilds DART/NVMMU and the controller/admin queues as usual. ++ * Never touch an endpoint BAR, a shared PMGR domain, or the Wi-Fi port here. ++ */ ++static int apple_s8003_boot_retry(struct apple_h9p_pcie *pcie, int reason) ++{ ++ void __iomem *rc = pcie->base_rc; ++ u32 ctl, pwr, reset; ++ int ret; ++ ++ if (pcie->ans_boot_retry_used || ++ pcie->ans_mode == APPLE_ANS_ROUND7 || ++ (reason != -ETIMEDOUT && reason != -ENOLINK)) ++ return reason; ++ pcie->ans_boot_retry_used = true; ++ dev_warn(pcie->dev, "Apple ANS: no root endpoint: port 0 bring-up failed %d; one full controller+port boot retry\n", ++ reason); ++ ret = gpiod_direction_output(pcie->perst[0], 1); ++ if (ret) ++ return ret; ++ if ((readl(rc + S8003_RC_PORT_CTL(0)) & S8003_RC_PORT_CTL_ENABLE) && ++ (readl(rc + S8003_RC_PORT_PWR(0)) & S8003_RC_PORT_PWR_ON)) { ++ apple_s8003_port_stop(pcie, 0); ++ ret = apple_s8003_port_wait(pcie, 0, ++ pcie->base_port[0] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "boot-retry-stop"); ++ if (ret) ++ return ret; ++ } ++ apple_s8003_port_off(pcie, 0); ++ ctl = readl(rc + S8003_RC_PORT_CTL(0)); ++ pwr = readl(rc + S8003_RC_PORT_PWR(0)); ++ reset = readl(rc + S8003_RC_PORT_REFCLK(0)); ++ if ((ctl | pwr | reset) & BIT(0)) ++ return dev_err_probe(pcie->dev, -EIO, ++ "STEP port 0 boot-retry-off FAIL ctl=%#x pwr=%#x reset=%#x\n", ++ ctl, pwr, reset); ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 boot-retry-off hold-100ms\n"); ++ msleep(100); ++ ret = apple_s8003_setup_port_full(pcie, 0); ++ if (ret == -ETIMEDOUT || ret == -ENOLINK) { ++ dev_warn(pcie->dev, "Apple ANS: boot retry failed %d; one deep link recovery\n", ++ ret); ++ ret = apple_s8003_deep_recover(pcie); ++ } ++ if (ret) { ++ gpiod_direction_output(pcie->perst[0], 1); ++ dev_err(pcie->dev, "Apple ANS: boot retry exhausted %d; no root endpoint, leaving rescue armed\n", ++ ret); ++ } else { ++ dev_info(pcie->dev, "Apple ANS: boot retry link restored; proceeding to controller/DMA bring-up\n"); ++ } ++ return ret; ++} ++ ++/* ++ * iOS AppleS800xPCIePort::disableGated for a linked port: clear the root ++ * port's command register and bus numbers, so nothing more is routed to ++ * the link; PME_Turn_Off and the ack; L2; PERST#; port off. A missing ack ++ * or L2 only gets logged, as in iOS: PERST# and the power-down end the ++ * link either way. (iOS has the endpoint in D3hot first; here it stays in ++ * D0, see h9p_suspend, so L2 may not be reached.) ++ */ ++static void apple_s8003_port_sleep(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ void __iomem *cfg = pcie->base_config + port * H9P_CFG_PORT_STRIDE; ++ void __iomem *pb = pcie->base_port[port]; ++ struct device *dev = pcie->dev; ++ u32 ack, l2; ++ int ret; ++ ++ writel(0, cfg + PCI_COMMAND); ++ h9p_rmw(cfg + PCI_PRIMARY_BUS, GENMASK(23, 8), 0); ++ ++ writel(S8003_PORT_PMETO_SEND, pb + S8003_PORT_PMETO); ++ ret = readl_poll_timeout(pb + S8003_PORT_PMETO, ack, ++ !(ack & S8003_PORT_PMETO_PENDING) || ++ (ack & S8003_PORT_PMETO_HW_TIMEOUT), ++ 1000, S8003_PME_TO_ACK_TIMEOUT_US); ++ if (ret || (ack & S8003_PORT_PMETO_PENDING)) ++ dev_warn(dev, "port %u: no PME_TO_Ack (%#x), continuing\n", ++ port, ack); ++ ++ ret = readl_poll_timeout(pb + S8003_PORT_LINKSTS, l2, ++ l2 & S8003_PORT_LINKSTS_L2, 100, ++ S8003_L2_TIMEOUT_US); ++ if (ret) ++ dev_warn(dev, "port %u: link did not enter L2 (%#x), continuing\n", ++ port, l2); ++ ++ gpiod_direction_output(pcie->perst[port], 1); ++ writel(S8003_PORT_IRQSTAT_LINK_DOWN, pb + H9P_PORT_IRQSTAT); ++ udelay(S8003_PERST_TO_REFCLK_US); ++ apple_s8003_port_off(pcie, port); ++ dev_info(dev, "port %u: asleep (PME_TO %#x, link %#x)\n", port, ack, l2); ++} ++ ++static int apple_t8010_init_common(struct apple_h9p_pcie *pcie) ++{ ++ int ret; ++ ++ writel(H9P_RC_COMMON_CTL_INIT | ++ FIELD_PREP(H9P_RC_COMMON_CTL_LANE_CFG, pcie->lane_cfg), ++ pcie->base_rc + H9P_RC_COMMON_CTL0); ++ h9p_rmw(pcie->base_rc + H9P_RC_PORT_CTL1(0), 0, ++ H9P_RC_COMMON_CTL_ENABLE); ++ ++ ret = apple_h9p_wait(pcie->base_rc + H9P_RC_COMMON_STAT, ++ H9P_RC_COMMON_STAT_INIT_DONE, ++ H9P_RC_COMMON_STAT_INIT_DONE, ++ H9P_RC_COMMON_STAT_INIT_DONE, 250000); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, ++ "global PHY init timeout\n"); ++ ++ ret = apple_h9p_wait(pcie->base_rc + H9P_RC_COMMON_STAT, ++ H9P_RC_COMMON_STAT_READY, ++ H9P_RC_COMMON_STAT_READY, ++ H9P_RC_COMMON_STAT_READY, 250000); ++ if (ret) ++ return dev_err_probe(pcie->dev, ret, ++ "global PHY ready timeout\n"); ++ ++ writel(H9P_RC_COMMON_CTL_ENABLE, ++ pcie->base_rc + H9P_RC_COMMON_CTL3); ++ writel(H9P_RC_COMMON_CTL_ENABLE, ++ pcie->base_rc + H9P_RC_COMMON_CTL1); ++ usleep_range(5000, 10000); ++ writel(H9P_RC_COMMON_CTL_ENABLE, ++ pcie->base_rc + H9P_RC_COMMON_CTL2); ++ usleep_range(500, 1000); ++ ++ return 0; ++} ++ ++/* The DT iommu-map assigns bus 1 to ANS/DART0 and bus 2 to Wi-Fi/DART1. ++ * If port 0 is missing, generic scanning otherwise gives bus 1 to Wi-Fi; ++ * a later rescue rescan would give ANS bus 2 and the wrong DART. Preserve ++ * those RIDs before ANY scan of a boot eligible for port-0 rescue. ++ */ ++static void apple_s8003_rescue_bus(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ unsigned int secondary = port + 1; ++ ++ h9p_rmw(pcie->base_config + port * H9P_CFG_PORT_STRIDE + PCI_PRIMARY_BUS, ++ GENMASK(23, 0), secondary << 8 | secondary << 16); ++ dev_info(pcie->dev, "Apple ANS: STEP port %u rescue-bus secondary=%u (fixed DART RID)\n", ++ port, secondary); ++} ++ ++static int apple_h9p_setup_ports(struct apple_h9p_pcie *pcie) ++{ ++ unsigned int port; ++ int ret; ++ ++ ret = pcie->hw->init_common(pcie); ++ if (ret) ++ return ret; ++ ++ for (port = 0; port < H9P_NUM_PORTS; port++) { ++ if (!(pcie->enabled_ports & BIT(port))) ++ continue; ++ ++ /* ++ * A port that fails to come up is dropped, so config accesses ++ * never reach a dead link; the other ports carry on. ++ */ ++ pcie->ans_pre_enumeration = true; ++ ret = pcie->hw->setup_port(pcie, port); ++ if (ret && !port && pcie->ans_mode != APPLE_ANS_ROUND7) ++ ret = apple_s8003_boot_retry(pcie, ret); ++ pcie->ans_pre_enumeration = false; ++ if (!ret) { ++ if (pcie->ans_rescue_allowed) ++ apple_s8003_rescue_bus(pcie, port); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports & ~BIT(port)); ++ } ++ if (ret) { ++ gpiod_direction_output(pcie->perst[port], 1); ++ pcie->enabled_ports &= ~BIT(port); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(port)); ++ if (!port && pcie->ans_mode != APPLE_ANS_ROUND7 && ++ (ret == -ETIMEDOUT || ret == -ENOLINK)) ++ pcie->ans_rescue_allowed = true; ++ dev_err(pcie->dev, "port %u disabled (%d)\n", port, ret); ++ } ++ } ++ ++ if (!pcie->enabled_ports && !pcie->late_ports) ++ return dev_err_probe(pcie->dev, -ENODEV, "no PCIe link came up\n"); ++ ++ /* PCIe r6.0 6.6.1: 100 ms after link-up before the first config request */ ++ msleep(100); ++ return 0; ++} ++ ++/* ++ * Power a late port's endpoint and train its link. PERST# has been asserted ++ * since probe. Returns -EPROBE_DEFER while the power GPIO's provider (e.g. ++ * the PMIC GPIO driver) isn't there yet. ++ */ ++static int apple_h9p_late_port(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ struct device *dev = pcie->dev; ++ struct gpio_desc *pwren; ++ unsigned int try; ++ int ret = 0; ++ ++ if (!pcie->pwren[port]) { ++ pwren = devm_fwnode_gpiod_get(dev, ++ of_fwnode_handle(pcie->port_nodes[port]), ++ "pwren", GPIOD_ASIS, "PWREN"); ++ if (PTR_ERR(pwren) == -EPROBE_DEFER) { ++ if (!(pcie->late_tries % 10)) ++ dev_info(dev, "port %u: waiting for its power GPIO (%u/%u)\n", ++ port, pcie->late_tries + 1, H9P_LATE_PORT_TRIES); ++ return -EPROBE_DEFER; ++ } ++ if (IS_ERR(pwren)) ++ return dev_err_probe(dev, PTR_ERR(pwren), ++ "port %u: no power GPIO\n", port); ++ pcie->pwren[port] = pwren; ++ } ++ ++ for (try = 0; try < H9P_LATE_PORT_LINK_TRIES; try++) { ++ /* A clean power cycle, PERST# asserted throughout */ ++ gpiod_direction_output(pcie->perst[port], 1); ++ ret = gpiod_direction_output(pcie->pwren[port], 0); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "port %u: can't drive power GPIO\n", ++ port); ++ msleep(H9P_PWREN_OFF_MS); ++ gpiod_set_value_cansleep(pcie->pwren[port], 1); ++ msleep(H9P_PWREN_SETTLE_MS); ++ dev_info(dev, "port %u: endpoint powered (attempt %u)\n", port, ++ try + 1); ++ ++ ret = pcie->hw->setup_port(pcie, port); ++ if (!ret) ++ break; ++ } ++ if (ret) { ++ gpiod_direction_output(pcie->perst[port], 1); ++ gpiod_set_value_cansleep(pcie->pwren[port], 0); ++ dev_err(dev, "port %u: no link after %u power cycles (%d), endpoint powered off\n", ++ port, try, ret); ++ return ret; ++ } ++ ++ if (pcie->ans_rescue_allowed) ++ apple_s8003_rescue_bus(pcie, port); ++ /* PCIe r6.0 6.6.1: 100 ms after link-up before the first config request */ ++ msleep(100); ++ pcie->enabled_ports |= BIT(port); ++ return 0; ++} ++ ++static void apple_h9p_late_ports_work(struct work_struct *work) ++{ ++ struct apple_h9p_pcie *pcie = container_of(to_delayed_work(work), ++ struct apple_h9p_pcie, ++ late_work); ++ unsigned long pending = pcie->late_ports; ++ bool rescan = false, retry = false; ++ unsigned int port; ++ int ret; ++ ++ /* The DT maps bus 1 to ANS/DART0, bus 2 to Wi-Fi/DART1. The ++ * generic host scan reassigns buses, overriding rescue_bus(). With ++ * port 0 absent it gives Wi-Fi bus 1 and attaches it to the stopped ++ * ANS DART. Leave late ports unpublished until ANS owns bus 1. ++ */ ++ if (of_machine_is_compatible("apple,j71t") && ++ pcie->ans_mode != APPLE_ANS_ROUND7 && ++ !(READ_ONCE(pcie->enabled_ports) & BIT(0))) { ++ dev_warn(pcie->dev, ++ "ANS port 0 missing; deferring late ports %#lx to preserve DART routing\n", ++ pending); ++ return; ++ } ++ ++ for_each_set_bit(port, &pending, H9P_NUM_PORTS) { ++ ret = apple_h9p_late_port(pcie, port); ++ if (ret == -EPROBE_DEFER) { ++ retry = true; ++ continue; ++ } ++ pcie->late_ports &= ~BIT(port); ++ rescan |= !ret; ++ } ++ ++ if (rescan) { ++ pci_lock_rescan_remove(); ++ pci_rescan_bus(pcie->bridge->bus); ++ pci_unlock_rescan_remove(); ++ } ++ ++ if (!retry) ++ return; ++ if (++pcie->late_tries < H9P_LATE_PORT_TRIES) { ++ schedule_delayed_work(&pcie->late_work, ++ msecs_to_jiffies(H9P_LATE_PORT_RETRY_MS)); ++ return; ++ } ++ dev_err(pcie->dev, "ports %#x: power GPIO never appeared, left off\n", ++ pcie->late_ports); ++ pcie->late_ports = 0; ++} ++ ++static void apple_h9p_late_ports_cancel(void *data) ++{ ++ struct apple_h9p_pcie *pcie = data; ++ ++ cancel_delayed_work_sync(&pcie->late_work); ++} ++ ++static int apple_h9p_pcie_init(struct pci_config_window *cfg) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(cfg->parent); ++ int ret; ++ ++ if (!pcie) ++ return -ENODEV; ++ ++ pcie->cfgwin = cfg; ++ pcie->base_config = cfg->win; ++ ++ ret = apple_h9p_setup_ports(pcie); ++ if (ret) ++ return ret; ++ ++ /* Without the NVMMU only NVMe is lost; keep the other ports. */ ++ ret = apple_h9p_setup_nvmmu(pcie); ++ if (ret) ++ dev_err(pcie->dev, "NVMMU setup failed (%d), NVMe disabled\n", ++ ret); ++ ++ return 0; ++} ++ ++static const struct pci_ecam_ops apple_h9p_pcie_ecam_ops = { ++ .bus_shift = 20, ++ .init = apple_h9p_pcie_init, ++ .pci_ops = { ++ .map_bus = pci_ecam_map_bus, ++ .read = apple_h9p_pcie_config_read, ++ .write = apple_h9p_pcie_config_write, ++ }, ++}; ++ ++static int apple_h9p_pcie_map_resources(struct platform_device *pdev, ++ struct apple_h9p_pcie *pcie) ++{ ++ unsigned int i; ++ ++ pcie->base_rc = devm_platform_ioremap_resource_byname(pdev, "rc"); ++ if (IS_ERR(pcie->base_rc)) ++ return PTR_ERR(pcie->base_rc); ++ ++ pcie->base_phy = devm_platform_ioremap_resource_byname(pdev, "phy"); ++ if (IS_ERR(pcie->base_phy)) ++ return PTR_ERR(pcie->base_phy); ++ ++ if (pcie->hw->has_phy_ip) { ++ pcie->base_phy_ip = ++ devm_platform_ioremap_resource_byname(pdev, "phy-ip"); ++ if (IS_ERR(pcie->base_phy_ip)) ++ return PTR_ERR(pcie->base_phy_ip); ++ } ++ ++ for (i = 0; i < H9P_NUM_PORTS; i++) { ++ char name[8]; ++ ++ if (!((pcie->enabled_ports | pcie->late_ports) & BIT(i))) ++ continue; ++ ++ snprintf(name, sizeof(name), "port%u", i); ++ pcie->base_port[i] = devm_platform_ioremap_resource_byname(pdev, ++ name); ++ if (IS_ERR(pcie->base_port[i])) ++ return PTR_ERR(pcie->base_port[i]); ++ } ++ ++ pcie->nvmmu.base = ++ devm_platform_ioremap_resource_byname(pdev, "nvmmu"); ++ if (IS_ERR(pcie->nvmmu.base)) ++ return PTR_ERR(pcie->nvmmu.base); ++ ++ return 0; ++} ++ ++static void apple_h9p_pcie_put_port_nodes(void *data) ++{ ++ struct apple_h9p_pcie *pcie = data; ++ unsigned int i; ++ ++ for (i = 0; i < H9P_NUM_PORTS; i++) ++ of_node_put(pcie->port_nodes[i]); ++} ++ ++static int apple_h9p_pcie_parse_ports(struct apple_h9p_pcie *pcie) ++{ ++ struct device *dev = pcie->dev; ++ struct device_node *np; ++ u32 reg, port; ++ int ret; ++ ++ for_each_available_child_of_node(dev->of_node, np) { ++ if (!of_node_name_eq(np, "pci")) ++ continue; ++ ++ ret = of_property_read_u32_index(np, "reg", 0, ®); ++ if (ret) ++ goto out_put_child; ++ ++ port = reg >> 11; ++ if (port >= H9P_NUM_PORTS || pcie->port_nodes[port]) { ++ ret = -EINVAL; ++ goto out_put_child; ++ } ++ ++ pcie->port_nodes[port] = of_node_get(np); ++ if (aspm_l1_ports & BIT(port)) { ++ struct property *prop = of_find_property(np, "aspm-no-l1", ++ NULL); ++ ++ if (prop && !of_remove_property(np, prop)) ++ dev_warn(dev, "port %u: ASPM L1 allowed (aspm_l1_ports)\n", ++ port); ++ } ++ if (of_property_present(np, "pwren-gpios")) ++ pcie->late_ports |= BIT(port); ++ else ++ pcie->enabled_ports |= BIT(port); ++ ++ if (of_property_read_u32(np, "max-link-speed", ++ &pcie->max_speed[port])) ++ pcie->max_speed[port] = port ? H9P_LINK_SPEED_2_5GT : ++ pcie->hw->port0_default_speed; ++ pcie->max_speed[port] = clamp_t(u32, pcie->max_speed[port], ++ H9P_LINK_SPEED_2_5GT, ++ H9P_LINK_SPEED_8GT); ++ pcie->nvme_mode[port] = of_property_read_bool(np, "apple,nvme-mode"); ++ } ++ ++ if (!(pcie->enabled_ports | pcie->late_ports)) ++ return -ENODEV; ++ ++ return devm_add_action_or_reset(dev, apple_h9p_pcie_put_port_nodes, ++ pcie); ++ ++out_put_child: ++ of_node_put(np); ++ apple_h9p_pcie_put_port_nodes(pcie); ++ return ret; ++} ++ ++static int apple_h9p_pcie_get_gpios(struct apple_h9p_pcie *pcie) ++{ ++ struct device *dev = pcie->dev; ++ unsigned int i; ++ ++ for (i = 0; i < H9P_NUM_PORTS; i++) { ++ struct fwnode_handle *fwnode; ++ struct gpio_desc *gpio; ++ ++ if (!((pcie->enabled_ports | pcie->late_ports) & BIT(i))) ++ continue; ++ ++ fwnode = of_fwnode_handle(pcie->port_nodes[i]); ++ gpio = devm_fwnode_gpiod_get(dev, fwnode, "reset", ++ !i && pcie->ans_mode == APPLE_ANS_HANDOFF ? ++ GPIOD_ASIS : GPIOD_OUT_HIGH, "PERST#"); ++ pcie->perst[i] = gpio; ++ if (IS_ERR(pcie->perst[i])) ++ return dev_err_probe(dev, PTR_ERR(pcie->perst[i]), ++ "failed to get PERST#%u\n", i); ++ ++ /* ++ * Optional: S8003 leaves CLKREQ# in its peripheral function ++ * (requesting it as a GPIO would take it away from the RC). ++ */ ++ gpio = devm_fwnode_gpiod_get(dev, fwnode, "clkreq", GPIOD_IN, "CLKREQ#"); ++ if (PTR_ERR(gpio) == -ENOENT) ++ gpio = NULL; ++ pcie->clkreq[i] = gpio; ++ if (IS_ERR(pcie->clkreq[i])) ++ return dev_err_probe(dev, PTR_ERR(pcie->clkreq[i]), ++ "failed to get CLKREQ#%u\n", i); ++ ++ /* Host-to-endpoint wake (SoC GPIO); left deasserted */ ++ gpio = devm_fwnode_gpiod_get(dev, fwnode, "device-wake", ++ GPIOD_OUT_LOW, "DEVICE_WAKE"); ++ if (PTR_ERR(gpio) == -ENOENT) ++ gpio = NULL; ++ pcie->devwake[i] = gpio; ++ if (IS_ERR(pcie->devwake[i])) ++ return dev_err_probe(dev, PTR_ERR(pcie->devwake[i]), ++ "failed to get DEVICE_WAKE%u\n", i); ++ } ++ ++ pcie->devpwr = devm_gpiod_get_array_optional(dev, "devpwr", GPIOD_ASIS); ++ if (IS_ERR(pcie->devpwr)) ++ return dev_err_probe(dev, PTR_ERR(pcie->devpwr), ++ "failed to get device power GPIOs\n"); ++ ++ return 0; ++} ++ ++static const struct attribute_group apple_h9p_crash_group; ++ ++/* Only a port which NEVER enumerated in this kernel can be retried here. ++ * Serialize with PCI rescans, close the gate before publishing any function, ++ * and never remove a device or disturb a live/failed controller object. ++ */ ++static ssize_t ans_recover_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ struct pci_dev *rp; ++ int ret; ++ ++ if (!pcie || !sysfs_streq(buf, "1") || is_kdump_kernel() || ++ !of_machine_is_compatible("apple,j71t") || ++ pcie->ans_mode == APPLE_ANS_ROUND7 || ++ !of_device_is_compatible(dev->of_node, "apple,s8003-pcie")) ++ return -EPERM; ++ pci_lock_rescan_remove(); ++ ret = -EBUSY; ++ if (!pcie->ans_rescue_allowed || (pcie->enabled_ports & BIT(0)) || ++ pcie->nvmmu.tcb) ++ goto out; ++ rp = pci_get_slot(pcie->bridge->bus, PCI_DEVFN(0, 0)); ++ if (rp) { ++ pci_dev_put(rp); ++ goto out; ++ } ++ pcie->ans_rescue_attempts++; ++ dev_warn(dev, "Apple ANS: STEP port 0 rescue-retry %u begin\n", ++ pcie->ans_rescue_attempts); ++ ret = apple_s8003_deep_recover(pcie); ++ if (ret) ++ goto out; ++ apple_s8003_rescue_bus(pcie, 0); ++ /* Even an NVMMU allocation/init failure cannot reopen this gate. */ ++ pcie->ans_rescue_allowed = false; ++ pcie->enabled_ports |= BIT(0); ++ ret = apple_h9p_setup_nvmmu(pcie); ++ if (ret) { ++ pcie->enabled_ports &= ~BIT(0); ++ gpiod_direction_output(pcie->perst[0], 1); ++ h9p_writel_flush(0, pcie->nvmmu.base + H9P_NVMMU_SART_CTRL); ++ pcie->nvmmu.ready = false; ++ pcie->nvmmu.active = false; ++ apple_s8003_port_stop(pcie, 0); ++ if (!apple_s8003_port_wait(pcie, 0, ++ pcie->base_port[0] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "rescue-failed-stop")) ++ apple_s8003_port_off(pcie, 0); ++ goto out; ++ } ++ msleep(100); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports & ~BIT(0)); ++ pci_rescan_bus(pcie->bridge->bus); ++ dev_info(dev, "Apple ANS: STEP port 0 rescue-rescan done; controller/DMA health gate required\n"); ++out: ++ pcie->ans_rescue_result = ret; ++ pci_unlock_rescan_remove(); ++ /* Successful ANS publication reserves bus 1 before Wi-Fi can scan. */ ++ if (!ret && pcie->late_ports) ++ schedule_delayed_work(&pcie->late_work, 0); ++ return ret ? ret : count; ++} ++ ++static ssize_t ans_recover_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ ++ return sysfs_emit(buf, "eligible=%u attempts=%u result=%d\n", ++ READ_ONCE(pcie->ans_rescue_allowed), ++ READ_ONCE(pcie->ans_rescue_attempts), ++ READ_ONCE(pcie->ans_rescue_result)); ++} ++static DEVICE_ATTR_RW(ans_recover); ++ ++static struct attribute *apple_h9p_ans_attrs[] = { ++ &dev_attr_ans_recover.attr, ++ NULL, ++}; ++ ++static const struct attribute_group apple_h9p_ans_group = { ++ .attrs = apple_h9p_ans_attrs, ++}; ++ ++static int apple_h9p_pcie_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pci_host_bridge *bridge; ++ struct apple_h9p_pcie *pcie; ++ int ret; ++ ++ if (!READ_ONCE(h9p_probe) && of_machine_is_compatible("apple,j71t")) ++ return dev_err_probe(dev, -EPROBE_DEFER, ++ "waiting for initramfs USB rescue (probe gate)\n"); ++ ++ bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie)); ++ if (!bridge) ++ return -ENOMEM; ++ ++ pcie = pci_host_bridge_priv(bridge); ++ pcie->dev = dev; ++ pcie->pdev = pdev; ++ pcie->bridge = bridge; ++ pcie->ans_mode = of_machine_is_compatible("apple,j71t") ? ++ ans_boot_mode : APPLE_ANS_ROUND7; ++ pcie->hw = of_device_get_match_data(dev); ++ if (!pcie->hw) ++ return -ENODEV; ++ spin_lock_init(&pcie->used_msi_lock); ++ ++ /* ADT "lane-cfg" of the apcie node */ ++ of_property_read_u32(dev->of_node, "apple,lane-cfg", &pcie->lane_cfg); ++ if (pcie->lane_cfg > FIELD_MAX(H9P_RC_COMMON_CTL_LANE_CFG) || ++ !(pcie->hw->lane_cfgs & BIT(pcie->lane_cfg))) ++ return dev_err_probe(dev, -EINVAL, "bad apple,lane-cfg %u\n", ++ pcie->lane_cfg); ++ ++ ret = apple_h9p_pcie_parse_ports(pcie); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to parse root ports\n"); ++ dev_info(dev, "ports %#x, lane-cfg %u\n", pcie->enabled_ports, ++ pcie->lane_cfg); ++ ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to set DMA mask\n"); ++ ++ ret = apple_h9p_pcie_attach_genpd(pcie); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to attach power domains\n"); ++ ret = devm_add_action_or_reset(dev, apple_h9p_pcie_genpd_cleanup, pcie); ++ if (ret) ++ return ret; ++ ++ pcie->clks[0].id = "core"; ++ pcie->clks[1].id = "aux"; ++ pcie->clks[2].id = "ref"; ++ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(pcie->clks), pcie->clks); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to get clocks\n"); ++ ++ ret = clk_bulk_prepare_enable(ARRAY_SIZE(pcie->clks), pcie->clks); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to enable clocks\n"); ++ ret = devm_add_action_or_reset(dev, apple_h9p_pcie_clk_cleanup, pcie); ++ if (ret) ++ return ret; ++ ++ pcie->pinctrl = devm_pinctrl_get_select_default(dev); ++ if (PTR_ERR(pcie->pinctrl) == -ENODEV) ++ pcie->pinctrl = NULL; ++ else if (IS_ERR(pcie->pinctrl)) ++ return dev_err_probe(dev, PTR_ERR(pcie->pinctrl), ++ "failed to select pinctrl state\n"); ++ ++ ret = apple_h9p_pcie_map_resources(pdev, pcie); ++ if (ret) ++ return ret; ++ ++ ret = apple_h9p_pcie_get_gpios(pcie); ++ if (ret) ++ return ret; ++ ++ ret = of_property_read_u64(dev->of_node, "apple,msi-doorbell", ++ &pcie->msi_doorbell); ++ if (ret) ++ pcie->msi_doorbell = H9P_DEFAULT_MSI_DOORBELL; ++ ++ ret = apple_h9p_pcie_setup_msi(pcie); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to set up MSI\n"); ++ ret = devm_add_action_or_reset(dev, apple_h9p_pcie_msi_cleanup, pcie); ++ if (ret) ++ return ret; ++ ++ INIT_DELAYED_WORK(&pcie->late_work, apple_h9p_late_ports_work); ++ ret = devm_add_action_or_reset(dev, apple_h9p_late_ports_cancel, pcie); ++ if (ret) ++ return ret; ++ ++ ret = pci_host_common_init(pdev, bridge, &apple_h9p_pcie_ecam_ops); ++ if (ret) ++ return ret; ++ ++ if (pcie->ans_mode != APPLE_ANS_ROUND7 && ++ of_device_is_compatible(dev->of_node, "apple,s8003-pcie")) { ++ ret = devm_device_add_group(dev, &apple_h9p_ans_group); ++ if (ret) ++ return ret; ++ } ++ ++ if (is_kdump_kernel() && of_machine_is_compatible("apple,j71t")) { ++ ret = devm_device_add_group(dev, &apple_h9p_crash_group); ++ if (ret) ++ return ret; ++ } ++ ++ if (pcie->late_ports) { ++ dev_info(dev, "ports %#x: bringing up after their endpoint power\n", ++ pcie->late_ports); ++ schedule_delayed_work(&pcie->late_work, 0); ++ } ++ return 0; ++} ++ ++/* Shared round-8f shutdown certificate: endpoint must already be stopped. */ ++static int apple_s8003_ans_port_down(struct apple_h9p_pcie *pcie, bool fatal) ++{ ++ void __iomem *cfg = pcie->base_config; ++ void __iomem *rc = pcie->base_rc; ++ u32 ctl, pwr, reset; ++ int ret; ++ ++ writew(0, cfg + PCI_COMMAND); ++ h9p_rmw(cfg + PCI_PRIMARY_BUS, GENMASK(23, 8), 0); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(0)); ++ ret = gpiod_direction_output(pcie->perst[0], 1); ++ if (ret) { ++ if (fatal) ++ panic("Apple ANS: outgoing PERST failed %d; refusing kexec", ret); ++ return ret; ++ } ++ apple_s8003_port_stop(pcie, 0); ++ ret = apple_s8003_port_wait(pcie, 0, ++ pcie->base_port[0] + H9P_PORT_LTSSMCTL, ++ H9P_PORT_LTSSM_ENABLE, 0, 250000, ++ "handoff-stop"); ++ if (ret) { ++ if (fatal) ++ panic("Apple ANS: outgoing LTSSM stop failed %d; refusing kexec", ret); ++ return ret; ++ } ++ if (pcie->nvmmu.ready) { ++ h9p_writel_flush(0, pcie->nvmmu.base + H9P_NVMMU_SART_CTRL); ++ if (fatal) ++ pcie->nvmmu.ready = false; ++ } ++ pcie->nvmmu.active = false; ++ /* Powered retention is kexec-only; sleep keeps its full-off certificate. */ ++ if (fatal && ans_keep_power && pcie->ans_mode == APPLE_ANS_HANDOFF) { ++ h9p_rmw(rc + S8003_RC_PORT_PWR(0), 0, ++ S8003_RC_PORT_REFCLK_NOGATE); ++ ctl = readl(rc + S8003_RC_PORT_CTL(0)); ++ pwr = readl(rc + S8003_RC_PORT_PWR(0)); ++ reset = readl(rc + S8003_RC_PORT_REFCLK(0)); ++ if (!(ctl & S8003_RC_PORT_CTL_ENABLE) || ++ (ctl & S8003_RC_PORT_CTL_BIT8) || ++ !(pwr & S8003_RC_PORT_PWR_ON) || ++ !(pwr & S8003_RC_PORT_REFCLK_NOGATE) || ++ !(reset & S8003_RC_PORT_REFCLK_ON) || ++ !(readl(rc + S8003_RC_PORT_READY(0)) & BIT(0)) || ++ gpiod_get_value_cansleep(pcie->perst[0]) != 1) ++ panic("Apple ANS: outgoing powered port certificate failed; refusing kexec"); ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 handoff-powered RC124=%#x RC114=%#x RC100=%#x PERST=asserted; incoming=powered-reset\n", ++ reset, pwr, ctl); ++ pcie->enabled_ports &= ~BIT(0); ++ return 0; ++ } ++ apple_s8003_port_off(pcie, 0); ++ ctl = readl(rc + S8003_RC_PORT_CTL(0)); ++ pwr = readl(rc + S8003_RC_PORT_PWR(0)); ++ reset = readl(rc + S8003_RC_PORT_REFCLK(0)); ++ if ((ctl | pwr | reset) & BIT(0)) { ++ if (fatal) ++ panic("Apple ANS: outgoing port 0 not down ctl=%#x pwr=%#x reset=%#x; refusing kexec", ++ ctl, pwr, reset); ++ return -EIO; ++ } ++ dev_info(pcie->dev, "Apple ANS: STEP port 0 handoff-down RC124=%#x RC114=%#x RC100=%#x PERST=asserted; incoming=full\n", ++ reset, pwr, ctl); ++ pcie->enabled_ports &= ~BIT(0); ++ if (!fatal) ++ dev_info(pcie->dev, "Apple ANS: sleep port 0 fully down; resume=full bring-up\n"); ++ return 0; ++} ++ ++/* System sleep, see h9p_suspend */ ++ ++static void apple_h9p_pcie_unpin(struct apple_h9p_pcie *pcie) ++{ ++ while (pcie->npinned) { ++ struct pci_dev *pdev = pcie->pinned[--pcie->npinned]; ++ ++ pdev->dev_flags &= ~PCI_DEV_FLAGS_NO_D3; ++ pci_dev_put(pdev); ++ } ++} ++ ++/* Everything below stays in D0 over the sleep, see h9p_suspend */ ++static int apple_h9p_pcie_pin(struct pci_dev *pdev, void *data) ++{ ++ struct apple_h9p_pcie *pcie = data; ++ ++ if (pdev->dev_flags & PCI_DEV_FLAGS_NO_D3) ++ return 0; ++ if (pcie->npinned == ARRAY_SIZE(pcie->pinned)) { ++ pcie->pin_overflow = true; ++ return 1; ++ } ++ ++ pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; ++ pcie->pinned[pcie->npinned++] = pci_dev_get(pdev); ++ return 0; ++} ++ ++static int apple_h9p_pcie_prepare(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ unsigned int mode = READ_ONCE(h9p_suspend); ++ unsigned int port; ++ ++ if (!pcie) ++ return 0; ++ ++ if (mode == H9P_SLEEP_OFF) { ++ dev_warn(dev, "system sleep refused: untested on this SoC (pcie_apple_h9p.suspend=1 allows it)\n"); ++ return -EBUSY; ++ } ++ if (pcie->late_ports || work_busy(&pcie->late_work.work)) { ++ dev_warn(dev, "system sleep refused: ports %#x still coming up\n", ++ pcie->late_ports); ++ return -EBUSY; ++ } ++ if (!pcie->pd_dev && dev->pm_domain) { ++ dev_warn(dev, "system sleep refused: genpd would power the ports off under the links\n"); ++ return -EBUSY; ++ } ++ ++ pcie->sleep_ports = 0; ++ WRITE_ONCE(pcie->ans_sleep_stopped, false); ++ if (mode > H9P_SLEEP_KEEP_LINKS && !pcie->hw->port_sleep) ++ dev_info(dev, "no port sleep sequence for this SoC, links stay up\n"); ++ else if (mode >= H9P_SLEEP_L2_ALL) ++ pcie->sleep_ports = pcie->enabled_ports; ++ else if (mode == H9P_SLEEP_L2_POWERED) ++ for (port = 0; port < H9P_NUM_PORTS; port++) ++ if (pcie->pwren[port]) ++ pcie->sleep_ports |= BIT(port) & pcie->enabled_ports; ++ ++ /* Apple pairs a system-sleep controller stop with full port bring-up. ++ * Even keep-links/powered-only modes must cycle the J71t ANS port. ++ */ ++ if (of_machine_is_compatible("apple,j71t") && pcie->hw->port_sleep) ++ pcie->sleep_ports |= pcie->enabled_ports & BIT(0); ++ ++ pcie->pin_overflow = false; ++ pci_walk_bus(pcie->bridge->bus, apple_h9p_pcie_pin, pcie); ++ if (pcie->pin_overflow) { ++ apple_h9p_pcie_unpin(pcie); ++ dev_warn(dev, "system sleep refused: too many PCI devices to hold in D0\n"); ++ return -EBUSY; ++ } ++ ++ dev_info(dev, "system sleep (mode %u): ports %#x stay linked, ports %#x go to L2\n", ++ mode, pcie->enabled_ports & ~pcie->sleep_ports, ++ pcie->sleep_ports); ++ return 0; ++} ++ ++static void apple_h9p_pcie_complete(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ ++ if (!pcie) ++ return; ++ ++ apple_h9p_pcie_unpin(pcie); ++ pcie->sleep_ports = 0; ++} ++ ++static int apple_h9p_pcie_resume_noirq(struct device *dev); ++ ++/* Runs after every endpoint and root port below has suspended */ ++static int apple_h9p_pcie_suspend_noirq(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ unsigned long ports; ++ unsigned int port; ++ ++ if (!pcie) ++ return 0; ++ ++ ports = pcie->sleep_ports & pcie->enabled_ports; ++ if (of_machine_is_compatible("apple,j71t") && (ports & BIT(0)) && ++ !READ_ONCE(pcie->ans_sleep_stopped)) { ++ dev_err(dev, "Apple ANS: sleep refused: missing endpoint stop certificate\n"); ++ return -EBUSY; ++ } ++ pcie->resume_ports = pcie->enabled_ports; ++ if (of_machine_is_compatible("apple,j71t") && (ports & BIT(0))) { ++ int ret = apple_s8003_ans_port_down(pcie, false); ++ ++ if (ret) { ++ dev_err(dev, "Apple ANS: sleep port stop failed %d; restoring links\n", ++ ret); ++ /* Our failing noirq callback will not get a PM resume call. */ ++ apple_h9p_pcie_resume_noirq(dev); ++ return -EBUSY; ++ } ++ ports &= ~BIT(0); ++ } ++ for_each_set_bit(port, &ports, H9P_NUM_PORTS) { ++ /* Nothing may go out on the link from here on */ ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(port)); ++ pcie->enabled_ports &= ~BIT(port); ++ pcie->hw->port_sleep(pcie, port); ++ } ++ return 0; ++} ++ ++/* PERST# deasserted and the link up, from SoC registers only */ ++static bool apple_h9p_port_alive(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ if (gpiod_get_value_cansleep(pcie->perst[port])) ++ return false; ++ if (!pcie->hw->linksts) ++ return apple_h9p_link_up(pcie, port); ++ ++ return readl(pcie->base_port[port] + pcie->hw->linksts) & ++ pcie->hw->linksts_up; ++} ++ ++/* ++ * A port that stays dead: PERST# on, port off, and every device below it ++ * marked disconnected, so the PCI core and the drivers (nvme, brcmfmac) ++ * leave its BARs alone. A BAR access over a dead link can stall the CPU. ++ */ ++static void apple_h9p_port_cut_off(struct apple_h9p_pcie *pcie, ++ unsigned int port) ++{ ++ struct pci_dev *rp; ++ ++ gpiod_direction_output(pcie->perst[port], 1); ++ if (pcie->hw->port_off) ++ pcie->hw->port_off(pcie, port); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(port)); ++ pcie->enabled_ports &= ~BIT(port); ++ ++ rp = pci_get_slot(pcie->bridge->bus, PCI_DEVFN(port, 0)); ++ if (!rp) ++ return; ++ if (rp->subordinate) ++ pci_walk_bus(rp->subordinate, pci_dev_set_disconnected, NULL); ++ pci_dev_set_disconnected(rp, NULL); ++ pci_dev_put(rp); ++} ++ ++/* ++ * Same bring-up as at boot, from a port that is off with PERST# asserted; ++ * the endpoint stays powered. ++ */ ++static int apple_h9p_port_wake(struct apple_h9p_pcie *pcie, unsigned int port) ++{ ++ struct pci_dev *rp; ++ unsigned int try; ++ int ret = -ENODEV; ++ ++ for (try = 0; try < H9P_WAKE_LINK_TRIES; try++) { ++ ret = pcie->hw->setup_port(pcie, port); ++ if (!ret) ++ break; ++ } ++ if (ret) { ++ apple_h9p_port_cut_off(pcie, port); ++ dev_err(pcie->dev, "port %u: no link after sleep (%d), its devices are cut off until reboot\n", ++ port, ret); ++ return ret; ++ } ++ ++ pcie->enabled_ports |= BIT(port); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports & ~BIT(port)); ++ ++ /* ++ * The root port's routing (bus numbers, windows, command) is needed ++ * before its endpoint resumes, but for a root port left in D0 the PCI ++ * core restores it only in the later resume phase. ++ */ ++ rp = pci_get_slot(pcie->bridge->bus, PCI_DEVFN(port, 0)); ++ if (rp) { ++ if (!rp->state_saved) ++ dev_warn(pcie->dev, "port %u: root port state not saved\n", ++ port); ++ pci_restore_state(rp); ++ pci_dev_put(rp); ++ } ++ return 0; ++} ++ ++/* The NVMMU sits in the always-on part of the RC; check, don't assume */ ++static void apple_h9p_nvmmu_resume(struct apple_h9p_pcie *pcie, bool retrained) ++{ ++ struct apple_h9p_nvmmu *nvmmu = &pcie->nvmmu; ++ struct device *dev = pcie->dev; ++ bool kept; ++ int ret; ++ ++ if (!nvmmu->ready || !(pcie->enabled_ports & BIT(0))) ++ return; ++ ++ kept = readl(nvmmu->base + H9P_NVMMU_TCB_BASE_LO) == ++ lower_32_bits(nvmmu->tcb_dma) && ++ readl(nvmmu->base + H9P_NVMMU_TCB_BASE_HI) == ++ upper_32_bits(nvmmu->tcb_dma) && ++ readl(nvmmu->base + H9P_NVMMU_TCB_TABLE_LO) == ++ lower_32_bits(nvmmu->tcb_table_dma) && ++ (!nvmmu->sart || ++ (readl(nvmmu->base + H9P_NVMMU_SART_CTRL) & 1)); ++ if (kept && !retrained) { ++ dev_info(dev, "NVMMU: state kept over the sleep\n"); ++ return; ++ } ++ ++ dev_info(dev, "NVMMU: %s, programming it again\n", ++ kept ? "port 0 was retrained" : "state lost over the sleep"); ++ ret = apple_h9p_nvmmu_program(pcie); ++ if (ret) { ++ /* apple_ans_nvmmu_map() then fails, and nvme gives up cleanly */ ++ nvmmu->ready = false; ++ dev_err(dev, "NVMMU: TCB init timeout after sleep (%d), NVMe stays off\n", ++ ret); ++ } ++} ++ ++/* ++ * Runs before the root ports and endpoints resume: bring back the ports ++ * that slept, and check that the others really kept their link (the PERST# ++ * GPIOs live in a power domain of their own) before the PCI core and the ++ * drivers touch anything behind them. ++ */ ++static int apple_h9p_pcie_resume_noirq(struct device *dev) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ bool retrained = false, port0 = false; ++ unsigned long ports; ++ unsigned int port; ++ ++ if (!pcie) ++ return 0; ++ ++ apple_h9p_crash_resume_test(dev); ++ ports = pcie->resume_ports; ++ for_each_set_bit(port, &ports, H9P_NUM_PORTS) { ++ bool asleep = pcie->down_ports & BIT(port); ++ ++ if (!asleep) { ++ if (apple_h9p_port_alive(pcie, port)) { ++ dev_info(dev, "port %u: link kept over the sleep\n", ++ port); ++ continue; ++ } ++ dev_warn(dev, "port %u: link lost over the sleep (PERST# %d), retraining\n", ++ port, gpiod_get_value_cansleep(pcie->perst[port])); ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(port)); ++ pcie->enabled_ports &= ~BIT(port); ++ gpiod_direction_output(pcie->perst[port], 1); ++ if (pcie->hw->port_off) ++ pcie->hw->port_off(pcie, port); ++ } ++ ++ if (apple_h9p_port_wake(pcie, port)) ++ continue; ++ retrained = true; ++ port0 |= port == 0; ++ } ++ pcie->resume_ports = 0; ++ WRITE_ONCE(pcie->ans_sleep_stopped, false); ++ ++ /* PCIe r6.0 6.6.1: 100 ms after link-up before the first config request */ ++ if (retrained) ++ msleep(100); ++ ++ apple_h9p_nvmmu_resume(pcie, port0); ++ return 0; ++} ++ ++/* ++ * Children have drained ANS, cleared CC.EN and blocked bus mastering. ++ * Isolate under PERST before retiring SART, but keep clocks/port power on. ++ * PME_TO/L2 and clock removal gained no DMA safety and broke retraining in ++ * test 2. The incoming probe now restarts the port itself before scanning. ++ */ ++static void apple_h9p_pcie_park_round7(struct apple_h9p_pcie *pcie, ++ unsigned long ports) ++{ ++ unsigned int port; ++ ++ cancel_delayed_work_sync(&pcie->late_work); ++ ports &= pcie->enabled_ports; ++ for_each_set_bit(port, &ports, H9P_NUM_PORTS) { ++ void __iomem *cfg = pcie->base_config + port * H9P_CFG_PORT_STRIDE; ++ ++ WRITE_ONCE(pcie->down_ports, pcie->down_ports | BIT(port)); ++ writew(0, cfg + PCI_COMMAND); ++ h9p_rmw(cfg + PCI_PRIMARY_BUS, GENMASK(23, 8), 0); ++ /* Keep the register block/refclk usable across the handoff. */ ++ h9p_rmw(pcie->base_rc + S8003_RC_PORT_PWR(port), 0, ++ S8003_RC_PORT_REFCLK_NOGATE); ++ gpiod_direction_output(pcie->perst[port], 1); ++ apple_s8003_port_stop(pcie, port); ++ udelay(S8003_PERST_TO_REFCLK_US); ++ if (!port && pcie->nvmmu.ready) { ++ h9p_writel_flush(0, pcie->nvmmu.base + H9P_NVMMU_SART_CTRL); ++ pcie->nvmmu.ready = false; ++ } ++ apple_s8003_port_dump(pcie, port, "kexec parked (powered)"); ++ pcie->enabled_ports &= ~BIT(port); ++ } ++ dev_info(pcie->dev, "kexec: PCIe PERST asserted, LTSSM stopped, ports powered; NVMMU SART disabled\n"); ++} ++ ++static void apple_h9p_pcie_park(struct apple_h9p_pcie *pcie, ++ unsigned long ports) ++{ ++ if (pcie->ans_mode != APPLE_ANS_ROUND7 && (ports & BIT(0))) { ++ cancel_delayed_work_sync(&pcie->late_work); ++ dev_info(pcie->dev, "Apple ANS: kexec %s; CC.EN=0/RDY=0 certified by endpoint shutdown\n", ++ ans_boot_modes[pcie->ans_mode]); ++ /* Endpoint shutdown retired DART after the CC-disable certificate. ++ * Both policies isolate with PERST/LTSSM before retiring SART. ++ * The opt-in handoff keeps only host power/refclk, never queues. ++ */ ++ apple_s8003_ans_port_down(pcie, true); ++ ports &= ~BIT(0); ++ } ++ /* Other ports keep the established Round 7 shutdown. */ ++ apple_h9p_pcie_park_round7(pcie, ports); ++} ++ ++static void apple_h9p_pcie_shutdown(struct platform_device *pdev) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(&pdev->dev); ++ ++ if (kexec_in_progress && pcie && ++ of_device_is_compatible(pdev->dev.of_node, "apple,s8003-pcie")) ++ apple_h9p_pcie_park(pcie, pcie->enabled_ports); ++} ++ ++struct apple_h9p_crash_state { ++ struct apple_h9p_pcie *pcie; ++ int result; ++ bool busy; ++ bool found; ++}; ++ ++/* No NVMe driver, queues, SHN, or old DMA buffers are needed in phoenix. */ ++static int apple_h9p_crash_stop_ans(struct pci_dev *pdev, void *data) ++{ ++ struct apple_h9p_crash_state *state = data; ++ struct apple_h9p_pcie *pcie = state->pcie; ++ void __iomem *bar; ++ u32 csts = 0; ++ int i, ret; ++ ++ if (pdev->vendor != PCI_VENDOR_ID_APPLE || ++ ((pdev->class >> 8) != PCI_CLASS_STORAGE_EXPRESS && ++ (pdev->class >> 8) != PCI_CLASS_STORAGE_OTHER)) ++ return 0; ++ state->found = true; ++ if (pdev->driver) { ++ state->busy = true; ++ return 1; ++ } ++ if (apple_ans_link_up(&pdev->dev) <= 0) { ++ state->result = -ENOLINK; ++ return 0; ++ } ++ ret = pci_enable_device_mem(pdev); ++ if (ret) { ++ state->result = ret; ++ return 0; ++ } ++ bar = pci_iomap_range(pdev, 0, 0, SZ_8K); ++ if (!bar) { ++ ret = -ENOMEM; ++ goto disable; ++ } ++ /* An unknown queue must never be rung or sent a shutdown notification. */ ++ dev_info(pcie->dev, "phoenix ANS: CC %#x CSTS %#x, clearing EN without SHN\n", ++ readl(bar + NVME_REG_CC), readl(bar + NVME_REG_CSTS)); ++ writel(0, bar + NVME_REG_CC); ++ ret = -ETIMEDOUT; ++ for (i = 0; i < 2000; i++) { ++ if (apple_ans_link_up(&pdev->dev) <= 0) { ++ ret = -ENOLINK; ++ break; ++ } ++ csts = readl(bar + NVME_REG_CSTS); ++ if (csts == U32_MAX) { ++ ret = -EIO; ++ break; ++ } ++ if (!(csts & NVME_CSTS_RDY)) { ++ ret = 0; ++ break; ++ } ++ usleep_range(1000, 1100); ++ } ++ dev_info(pcie->dev, "phoenix ANS: disable result %d CSTS %#x; blocking bus master\n", ++ ret, csts); ++ pci_iounmap(pdev, bar); ++disable: ++ pci_clear_master(pdev); ++ pci_disable_device(pdev); ++ state->result = ret; ++ return 0; ++} ++ ++static ssize_t crash_quiesce_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_h9p_pcie *pcie = apple_h9p_pcie_lookup(dev); ++ struct apple_h9p_crash_state state = { .pcie = pcie }; ++ ++ if (!is_kdump_kernel() || !of_machine_is_compatible("apple,j71t") || ++ !pcie || !sysfs_streq(buf, "1")) ++ return -EPERM; ++ /* The endpoint gate must stay closed; don't park under a live driver. */ ++ if (pcie->late_ports || work_busy(&pcie->late_work.work)) ++ return -EBUSY; ++ pci_walk_bus(pcie->bridge->bus, apple_h9p_crash_stop_ans, &state); ++ if (state.busy) ++ return -EBUSY; ++ /* Isolation is still required after a bounded RDY timeout. */ ++ /* Wi-Fi stays linked until its own shutdown callback has run. */ ++ /* Panic recovery has no SHST-certified handoff. Keep its isolation. */ ++ apple_h9p_pcie_park_round7(pcie, BIT(0)); ++ dev_info(dev, "phoenix: ANS/PCIe quiesced (controller found %d result %d)\n", ++ state.found, state.result); ++ return count; ++} ++static DEVICE_ATTR_WO(crash_quiesce); ++ ++static struct attribute *apple_h9p_crash_attrs[] = { ++ &dev_attr_crash_quiesce.attr, ++ NULL, ++}; ++ ++static const struct attribute_group apple_h9p_crash_group = { ++ .attrs = apple_h9p_crash_attrs, ++}; ++ ++static const struct dev_pm_ops apple_h9p_pcie_pm_ops = { ++ .prepare = apple_h9p_pcie_prepare, ++ .complete = apple_h9p_pcie_complete, ++ .suspend_noirq = apple_h9p_pcie_suspend_noirq, ++ .resume_noirq = apple_h9p_pcie_resume_noirq, ++}; ++ ++static const struct apple_h9p_hw apple_h9p_hw_t8010 = { ++ .has_phy_ip = true, ++ .port0_default_speed = H9P_LINK_SPEED_8GT, ++ .nvmmu_init_busy = 0x10, ++ .lane_cfgs = GENMASK(15, 0), ++ .init_common = apple_t8010_init_common, ++ .setup_port = apple_t8010_setup_port, ++}; ++ ++static const struct apple_h9p_hw apple_h9p_hw_s8003 = { ++ .has_phy_ip = true, ++ .port0_default_speed = H9P_LINK_SPEED_5GT, ++ .nvmmu_init_busy = H9P_NVMMU_TCB_CTRL_INIT, ++ /* iOS panics on anything else ("X1 on port 0 not supported") */ ++ .lane_cfgs = BIT(1) | BIT(3), ++ .linksts = S8003_PORT_LINKSTS, ++ .linksts_up = S8003_PORT_LINKSTS_UP, ++ .init_common = apple_s8003_init_common, ++ .setup_port = apple_s8003_setup_port, ++ .port_sleep = apple_s8003_port_sleep, ++ .port_off = apple_s8003_port_off, ++}; ++ ++static const struct of_device_id apple_h9p_pcie_of_match[] = { ++ { .compatible = "apple,t8010-pcie", .data = &apple_h9p_hw_t8010 }, ++ { .compatible = "apple,s8003-pcie", .data = &apple_h9p_hw_s8003 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, apple_h9p_pcie_of_match); ++ ++static struct platform_driver apple_h9p_pcie_driver = { ++ .probe = apple_h9p_pcie_probe, ++ .shutdown = apple_h9p_pcie_shutdown, ++ .driver = { ++ .name = "pcie-apple-h9p", ++ .of_match_table = apple_h9p_pcie_of_match, ++ .pm = pm_sleep_ptr(&apple_h9p_pcie_pm_ops), ++ .suppress_bind_attrs = true, ++ }, ++}; ++module_platform_driver(apple_h9p_pcie_driver); ++ ++MODULE_DESCRIPTION("Apple H8P/H9P (S8003/T8010) PCIe host bridge driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c +index 82c33cf727a825d2536644d2fe09c0282acd1ef8..75bf0a0d79bbcffcd1219fe945f7154f3f8936bb 100644 +--- a/drivers/pmdomain/apple/pmgr-pwrstate.c ++++ b/drivers/pmdomain/apple/pmgr-pwrstate.c +@@ -8,14 +8,19 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include + + #define APPLE_PMGR_RESET BIT(31) + #define APPLE_PMGR_AUTO_ENABLE BIT(28) +@@ -37,6 +42,12 @@ + #define APPLE_PMGR_PS_SET_TIMEOUT 100 + #define APPLE_PMGR_RESET_TIME 1 + ++/* DT permission is also required, and only the j71t experiment is supported. */ ++static bool idle_power_gate; ++module_param(idle_power_gate, bool, 0444); ++MODULE_PARM_DESC(idle_power_gate, ++ "Lower DT-selected j71t domain idle floors (default: off)"); ++ + struct apple_pmgr_ps { + struct device *dev; + struct generic_pm_domain genpd; +@@ -44,17 +55,58 @@ struct apple_pmgr_ps { + struct regmap *regmap; + u32 offset; + u32 min_state; ++ bool warm; ++ bool keep_on; ++ u32 active_min_state; ++ bool idle_power_gate; ++ bool retain; /* apple,no-auto-pm: the inherited word is never written */ + }; + + #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd) + #define rcdev_to_apple_pmgr_ps(_rcdev) container_of(_rcdev, struct apple_pmgr_ps, rcdev) + ++/* j71t warm entry does not have iBoot's device/domain reset baseline. */ ++static bool kexec_keep_on = true; ++static unsigned int ps_trace; ++ ++static int __init apple_pmgr_keep_on_setup(char *arg) ++{ ++ return kstrtobool(arg, &kexec_keep_on); ++} ++early_param("apple_pmgr.kexec_keep_on", apple_pmgr_keep_on_setup); ++ ++static int __init apple_pmgr_trace_setup(char *arg) ++{ ++ unsigned int count; ++ int ret = kstrtouint(arg, 0, &count); ++ ++ if (!ret) ++ WRITE_ONCE(ps_trace, min(count, 1024U)); ++ return ret; ++} ++early_param("apple_pmgr.trace", apple_pmgr_trace_setup); ++ ++static void apple_pmgr_ps_trace(struct apple_pmgr_ps *ps, const char *stage, u32 reg) ++{ ++ unsigned int left = READ_ONCE(ps_trace); ++ ++ while (left) { ++ if (try_cmpxchg(&ps_trace, &left, left - 1)) { ++ dev_info(ps->dev, "PMGR %s %s @%x reg=%08x warm=%u hold=%u\n", ++ ps->genpd.name, stage, ps->offset, reg, ++ ps->warm, ps->keep_on); ++ break; ++ } ++ } ++} ++ + static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool auto_enable) + { + int ret; + struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); + u32 reg; + ++ apple_pmgr_ps_trace(ps, pstate ? "on begin" : "off begin", 0); + ret = regmap_read(ps->regmap, ps->offset, ®); + if (ret < 0) + return ret; +@@ -67,56 +119,235 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a + reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET); + reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate); + ++ /* Only genpd can reach this path, after all child/consumer holds drop. */ ++ if (ps->idle_power_gate) { ++ reg &= ~APPLE_PMGR_PS_MIN; ++ if (pstate == APPLE_PMGR_PS_ACTIVE) ++ reg |= FIELD_PREP(APPLE_PMGR_PS_MIN, ps->active_min_state); ++ } ++ + dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, pstate, reg); + +- regmap_write(ps->regmap, ps->offset, reg); ++ ret = regmap_write(ps->regmap, ps->offset, reg); ++ if (ret) ++ return ret; + + ret = regmap_read_poll_timeout_atomic( + ps->regmap, ps->offset, reg, + (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1, + APPLE_PMGR_PS_SET_TIMEOUT); +- if (ret < 0) +- dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n", +- genpd->name, pstate, reg); ++ if (ret < 0) { ++ dev_err(ps->dev, "PS %s @%x: Failed to reach power state 0x%x (now: 0x%x, error %d)\n", ++ genpd->name, ps->offset, pstate, reg, ret); ++ /* Never enable AUTO on a failed handshake. */ ++ return ret; ++ } + +- if (auto_enable) { ++ if (auto_enable && !ps->keep_on) { + /* Not all devices implement this; this is a no-op where not implemented. */ + reg &= ~APPLE_PMGR_FLAGS; + reg |= APPLE_PMGR_AUTO_ENABLE; +- regmap_write(ps->regmap, ps->offset, reg); ++ ret = regmap_write(ps->regmap, ps->offset, reg); + } + ++ apple_pmgr_ps_trace(ps, "set end", reg); + return ret; + } + +-static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps) ++/* A kexec preserves the faulted GPU. Cycle its ADT-identified outer domain ++ * before publishing the provider, hence before a GPU consumer can load firmware. ++ * Explicit opt-in until this has been validated on hardware. No GPU MMIO here. ++ */ ++static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd); ++static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd); ++static bool kexec_gpu_cycle; ++ ++static int __init apple_pmgr_kexec_gpu_cycle(char *arg) ++{ ++ return kstrtobool(arg, &kexec_gpu_cycle); ++} ++early_param("apple_pmgr.kexec_gpu_cycle", apple_pmgr_kexec_gpu_cycle); ++ ++static int apple_pmgr_ps_kexec_gpu_cycle(struct apple_pmgr_ps *ps, bool *active) ++{ ++ u32 reg; ++ int ret; ++ ++ if (!kexec_gpu_cycle || !of_machine_is_compatible("apple,j71t") || ++ ps->offset != 0x80340 || strcmp(ps->genpd.name, "gfx") || ++ !of_device_is_compatible(ps->dev->of_node, "apple,s8000-pmgr-pwrstate") || ++ !of_property_read_bool(of_chosen, "linux,booted-from-kexec")) ++ return 0; ++ ++ ret = regmap_read(ps->regmap, ps->offset, ®); ++ if (ret) ++ return ret; ++ /* A non-zero floor or reset held by the old kernel prevents a real off. ++ * Refuse rather than silently calling a clock gate a power cycle. ++ */ ++ if (reg & (APPLE_PMGR_PS_MIN | APPLE_PMGR_RESET)) ++ return dev_err_probe(ps->dev, -EBUSY, ++ "kexec GFX cycle refused: min/reset set (%08x)\n", reg); ++ ++ dev_info(ps->dev, "kexec GFX power cycle start: %08x\n", reg); ++ /* Explicit recovery is allowed even when ordinary warm gating is held. */ ++ ret = apple_pmgr_ps_set(&ps->genpd, APPLE_PMGR_PS_PWRGATE, false); ++ if (ret) ++ return ret; ++ udelay(10); ++ ret = apple_pmgr_ps_power_on(&ps->genpd); ++ if (ret) ++ return ret; ++ *active = true; ++ dev_info(ps->dev, "kexec GFX power cycle complete (actual 0 -> f)\n"); ++ return 0; ++} ++ ++static int apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps, bool *active) + { + u32 reg = 0; ++ int ret; + +- regmap_read(ps->regmap, ps->offset, ®); ++ ret = regmap_read(ps->regmap, ps->offset, ®); ++ if (ret) ++ return ret; ++ apple_pmgr_ps_trace(ps, "inherited", reg); + /* + * We consider domains as active if they are actually on, or if they have auto-PM + * enabled and the intended target is on. + */ +- return (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE || ++ *active = (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE || + (FIELD_GET(APPLE_PMGR_PS_TARGET, reg) == APPLE_PMGR_PS_ACTIVE && + reg & APPLE_PMGR_AUTO_ENABLE)); ++ return 0; + } + + static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd) + { ++ struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); ++ u32 reg; ++ int ret; ++ ++ /* A retained domain is only ever reported, never requested. */ ++ if (ps->retain) { ++ ret = regmap_read(ps->regmap, ps->offset, ®); ++ if (ret) ++ return ret; ++ if (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE) ++ return 0; ++ dev_err(ps->dev, "PS %s: retained domain is off (%08x), not powering on\n", ++ genpd->name, reg); ++ return -EPERM; ++ } + return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE, true); + } + ++/* ++ * apple,no-auto-pm (j71t ps_sep): keep the word the bootloader chain left ++ * exactly as it is. pongoOS hands sepOS over with SEP at 0x4f0000ff; the cold ++ * path below would add APPLE_PMGR_AUTO_ENABLE (0x5f0000ff), and with that set ++ * sepOS stopped consuming the AP->SEP mailbox (2026-10-26 15:42). No AUTO, no ++ * floor, no warm re-confirm, no power-off and no reset: the register is only ++ * read. A domain inherited off stays off and is published without ALWAYS_ON. ++ */ ++static int apple_pmgr_ps_init_retained(struct apple_pmgr_ps *ps, bool *active) ++{ ++ u32 reg; ++ int ret; ++ ++ ret = regmap_read(ps->regmap, ps->offset, ®); ++ if (ret) ++ return ret; ++ *active = FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE; ++ if (!*active) { ++ dev_err(ps->dev, "PMGR %s retained @%x reg=%08x is off: left untouched, not always-on\n", ++ ps->genpd.name, ps->offset, reg); ++ return 0; ++ } ++ if (of_property_read_bool(ps->dev->of_node, "apple,always-on")) ++ ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; ++ dev_info(ps->dev, "PMGR %s retained @%x reg=%08x: no AUTO, never written\n", ++ ps->genpd.name, ps->offset, reg); ++ return 0; ++} ++ ++static int apple_pmgr_ps_init_state(struct apple_pmgr_ps *ps, bool *active) ++{ ++ struct device_node *node = ps->dev->of_node; ++ int ret; ++ ++ if (ps->retain) ++ return apple_pmgr_ps_init_retained(ps, active); ++ ++ ret = apple_pmgr_ps_is_active(ps, active); ++ if (ret) ++ return ret; ++ /* Actual ON with target OFF is not a stable ON request. AUTO may ++ * also have gated an intended ON domain. Confirm ON before publishing. ++ */ ++ if (ps->warm && *active) { ++ ret = apple_pmgr_ps_power_on(&ps->genpd); ++ if (ret) ++ return ret; ++ } ++ if (of_property_read_bool(node, "apple,always-on")) { ++ ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; ++ if (!*active) { ++ dev_warn(ps->dev, "always-on domain %s is not on at boot\n", ++ ps->genpd.name); ++ ret = apple_pmgr_ps_power_on(&ps->genpd); ++ if (ret) ++ return ret; ++ *active = true; ++ } ++ } ++ /* Preserve the cold boot sequence, including AUTO for active domains. */ ++ if (*active && !ps->warm) ++ return regmap_update_bits(ps->regmap, ps->offset, ++ APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, ++ APPLE_PMGR_AUTO_ENABLE); ++ return 0; ++} ++ + static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd) + { +- return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE, false); ++ struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); ++ int ret, restore; ++ ++ if (ps->retain) { ++ apple_pmgr_ps_trace(ps, "off refused (retained)", 0); ++ return -EBUSY; ++ } ++ ++ /* Do not gate retained firmware/DMA before its driver has reset it. ++ * A failed off keeps genpd ON, including parent use counts. Domains ++ * inherited OFF remain OFF until a consumer requests power normally. ++ */ ++ if (ps->keep_on) { ++ apple_pmgr_ps_trace(ps, "off held", 0); ++ return -EBUSY; ++ } ++ ++ ret = apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE, false); ++ if (ret && ps->idle_power_gate) { ++ /* Keep genpd's failed-off state consistent with powered hardware. */ ++ restore = apple_pmgr_ps_power_on(genpd); ++ if (restore) ++ dev_err(ps->dev, "idle gate rollback failed: %d\n", restore); ++ } ++ return ret; + } + + static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) + { + struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); + unsigned long flags; ++ int ret; ++ ++ if (ps->retain) { ++ dev_err(ps->dev, "PS 0x%x: reset refused on a retained domain\n", ps->offset); ++ return -EPERM; ++ } + + spin_lock_irqsave(&ps->genpd.slock, flags); + +@@ -125,33 +356,46 @@ static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned + + dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset); + /* Quiesce device before asserting reset */ +- regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, ++ ret = regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, + APPLE_PMGR_DEV_DISABLE); +- regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, ++ if (ret) ++ goto out; ++ ret = regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, + APPLE_PMGR_RESET); + ++out: + spin_unlock_irqrestore(&ps->genpd.slock, flags); + +- return 0; ++ return ret; + } + + static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) + { + struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); + unsigned long flags; ++ int ret; ++ ++ if (ps->retain) { ++ dev_err(ps->dev, "PS 0x%x: reset refused on a retained domain\n", ps->offset); ++ return -EPERM; ++ } + + spin_lock_irqsave(&ps->genpd.slock, flags); + + dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset); +- regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, 0); +- regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, 0); ++ ret = regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, 0); ++ if (ret) ++ goto out; ++ ret = regmap_update_bits(ps->regmap, ps->offset, ++ APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, 0); + + if (ps->genpd.status == GENPD_STATE_OFF) + dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset); + ++out: + spin_unlock_irqrestore(&ps->genpd.slock, flags); + +- return 0; ++ return ret; + } + + static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) +@@ -171,8 +415,11 @@ static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned + { + struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev); + u32 reg = 0; ++ int ret; + +- regmap_read(ps->regmap, ps->offset, ®); ++ ret = regmap_read(ps->regmap, ps->offset, ®); ++ if (ret) ++ return ret; + + return !!(reg & APPLE_PMGR_RESET); + } +@@ -200,6 +447,7 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) + int ret; + const char *name; + bool active; ++ u32 reg; + + regmap = syscon_node_to_regmap(node->parent); + if (IS_ERR(regmap)) +@@ -224,30 +472,58 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) + return ret; + } + +- ps->genpd.flags |= GENPD_FLAG_IRQ_SAFE; ++ /* ++ * Keep a domain on over system sleep while a device in it is on the ++ * wakeup path (e.g. a USB gadget kept connected in s2idle). ++ */ ++ ps->genpd.flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_ACTIVE_WAKEUP; + ps->genpd.name = name; + ps->genpd.power_on = apple_pmgr_ps_power_on; + ps->genpd.power_off = apple_pmgr_ps_power_off; ++ ps->warm = of_machine_is_compatible("apple,j71t") && ++ of_device_is_compatible(node, "apple,s8000-pmgr-pwrstate") && ++ of_property_read_bool(of_chosen, "linux,booted-from-kexec"); ++ ps->keep_on = ps->warm && kexec_keep_on; ++ ps->retain = of_property_read_bool(node, "apple,no-auto-pm"); ++ apple_pmgr_ps_trace(ps, "probe begin", 0); ++ if (READ_ONCE(ps_trace)) { ++ u32 inherited; ++ ++ ret = regmap_read(regmap, ps->offset, &inherited); ++ if (ret) ++ return ret; ++ apple_pmgr_ps_trace(ps, "raw before floor", inherited); ++ } + + ret = of_property_read_u32(node, "apple,min-state", &ps->min_state); +- if (ret == 0 && ps->min_state <= APPLE_PMGR_PS_ACTIVE) +- regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN, ++ if (ret == 0 && ps->min_state <= APPLE_PMGR_PS_ACTIVE && !ps->retain) { ++ ret = regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN, + FIELD_PREP(APPLE_PMGR_PS_MIN, ps->min_state)); ++ if (ret) ++ return ret; ++ } + +- active = apple_pmgr_ps_is_active(ps); +- if (of_property_read_bool(node, "apple,always-on")) { +- ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; +- if (!active) { +- dev_warn(dev, "always-on domain %s is not on at boot\n", name); +- /* Turn it on so pm_genpd_init does not fail */ +- active = apple_pmgr_ps_power_on(&ps->genpd) == 0; +- } ++ if (idle_power_gate && of_machine_is_compatible("apple,j71t") && ++ of_device_is_compatible(node, "apple,s8000-pmgr-pwrstate") && ++ of_property_read_bool(node, "apple,idle-power-gate") && ++ !of_property_read_bool(node, "apple,always-on") && !ps->retain) { ++ ret = regmap_read(regmap, ps->offset, ®); ++ if (ret) ++ return ret; ++ ps->active_min_state = FIELD_GET(APPLE_PMGR_PS_MIN, reg); ++ ps->idle_power_gate = true; ++ dev_info(dev, "idle floor opt-in: %s minimum %u -> 0 only at genpd off\n", ++ name, ps->active_min_state); + } + +- /* Turn on auto-PM if the domain is already on */ +- if (active) +- regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, +- APPLE_PMGR_AUTO_ENABLE); ++ ret = apple_pmgr_ps_init_state(ps, &active); ++ if (ret) ++ return ret; ++ ++ /* Keep boot recovery independent of the idle-floor policy above. */ ++ ret = apple_pmgr_ps_kexec_gpu_cycle(ps, &active); ++ if (ret) ++ return ret; + + ret = pm_genpd_init(&ps->genpd, NULL, !active); + if (ret < 0) { +@@ -298,6 +574,8 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev) + if (ret < 0) + goto err_remove; + ++ platform_set_drvdata(pdev, ps); ++ + return 0; + err_remove: + of_genpd_del_provider(node); +@@ -305,6 +583,87 @@ err_remove: + return ret; + } + ++/* A9 AOP observer API: BEGIN */ ++#if IS_ENABLED(CONFIG_APPLE_A9_AOP) ++/* ++ * Read-only observer for bring-up. The caller's nodes follow the same ++ * child -> parent order as genpd power transitions. No domain is attached, ++ * resumed, pinned on, reset, or written by this function. ++ */ ++int apple_pmgr_observe(struct device_node **nodes, unsigned int count, ++ struct apple_pmgr_observation *states, ++ void (*read)(void *), void *data) ++{ ++ struct platform_device *providers[5]; ++ struct apple_pmgr_ps *ps[5]; ++ unsigned long flags; ++ unsigned int i, j, found = 0; ++ int ret = 0; ++ bool accessible = true; ++ ++ if (!count || count > ARRAY_SIZE(providers) || !read) ++ return -EINVAL; ++ for (i = 0; i < count; i++) { ++ for (j = 0; j < i; j++) ++ if (nodes[i] == nodes[j]) { ++ ret = -EINVAL; ++ goto put; ++ } ++ providers[i] = of_find_device_by_node(nodes[i]); ++ if (!providers[i]) { ++ ret = -EPROBE_DEFER; ++ goto put; ++ } ++ device_lock(&providers[i]->dev); ++ found++; ++ if (!providers[i]->dev.driver || ++ strcmp(providers[i]->dev.driver->name, "apple-pmgr-pwrstate")) { ++ ret = -EPROBE_DEFER; ++ goto put; ++ } ++ ps[i] = platform_get_drvdata(providers[i]); ++ if (!ps[i]) { ++ ret = -EPROBE_DEFER; ++ goto put; ++ } ++ } ++ ++ /* PMGR domains use GENPD_FLAG_IRQ_SAFE, i.e. genpd.slock. */ ++ local_irq_save(flags); ++ for (i = 0; i < count; i++) ++ spin_lock_nested(&ps[i]->genpd.slock, i); ++ for (i = 0; i < count; i++) { ++ states[i].genpd_status = ps[i]->genpd.status; ++ ret = regmap_read(ps[i]->regmap, ps[i]->offset, &states[i].raw); ++ if (ret) ++ goto unlock; ++ if (states[i].genpd_status != GENPD_STATE_ON || ++ FIELD_GET(APPLE_PMGR_PS_ACTUAL, states[i].raw) != APPLE_PMGR_PS_ACTIVE || ++ FIELD_GET(APPLE_PMGR_PS_TARGET, states[i].raw) != APPLE_PMGR_PS_ACTIVE || ++ states[i].raw & (APPLE_PMGR_RESET | APPLE_PMGR_DEV_DISABLE | ++ APPLE_PMGR_PARENT_OFF)) ++ accessible = false; ++ } ++ if (accessible) ++ read(data); ++ else ++ ret = -EHOSTDOWN; ++unlock: ++ for (i = count; i > 0; i--) ++ spin_unlock(&ps[i - 1]->genpd.slock); ++ local_irq_restore(flags); ++put: ++ while (found) { ++ found--; ++ device_unlock(&providers[found]->dev); ++ put_device(&providers[found]->dev); ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_pmgr_observe); ++#endif ++/* A9 AOP observer API: END */ ++ + static const struct of_device_id apple_pmgr_ps_of_match[] = { + { .compatible = "apple,t8103-pmgr-pwrstate" }, + { .compatible = "apple,pmgr-pwrstate" }, +diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c +index f533953294f62abb603a715a9fe4ba08f1e5b27c..c1b4bb64ba0a27233d3a2f8c214b24e74bb556d8 100644 +--- a/drivers/watchdog/apple_wdt.c ++++ b/drivers/watchdog/apple_wdt.c +@@ -53,6 +53,18 @@ + + #define APPLE_WDT_TIMEOUT_DEFAULT 30 + ++/* ++ * On iDevices booted through a bootrom exploit (checkm8 -> pongoOS -> m1n1), ++ * a SoC reset boots the stock OS, and getting back to Linux needs a host ++ * computer and a manual DFU entry. With cold_restart=0 the restart handler ++ * refuses, so a reboot that wasn't meant to leave Linux (a failed kexec, a ++ * panic timeout, sysrq-b) halts instead. Writable at runtime, so a ++ * deliberate "reboot to iOS" can turn it back on first. ++ */ ++static bool cold_restart = true; ++module_param(cold_restart, bool, 0644); ++MODULE_PARM_DESC(cold_restart, "Allow restarts through a SoC reset (default: true)"); ++ + struct apple_wdt { + struct watchdog_device wdd; + void __iomem *regs; +@@ -123,6 +135,11 @@ static int apple_wdt_restart(struct watchdog_device *wdd, unsigned long mode, + { + struct apple_wdt *wdt = to_apple_wdt(wdd); + ++ if (!READ_ONCE(cold_restart)) { ++ pr_emerg("apple-wdt: SoC reset refused (apple_wdt.cold_restart=0), halting\n"); ++ return -EPERM; ++ } ++ + writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL); + writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_BITE_TIME); + writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME); +diff --git a/include/linux/apple-ans.h b/include/linux/apple-ans.h +new file mode 100644 +index 0000000000000000000000000000000000000000..58fae548ac6d1b43f55fc1abd65d37a0e465f403 +--- /dev/null ++++ b/include/linux/apple-ans.h +@@ -0,0 +1,116 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _LINUX_APPLE_ANS_H ++#define _LINUX_APPLE_ANS_H ++ ++#include ++#include ++#include ++#include ++ ++struct device; ++ ++/* SoC-side view of the endpoint's port (none of it crosses the link) */ ++struct apple_ans_host_state { ++ u32 link_up; ++ u32 ltssm; /* S8003 LTSSM state, 0x11 = L0 */ ++ u32 port_irq; /* port interrupt status */ ++ u32 nvmmu_err; /* NVMMU error status, 0 = none */ ++ u32 dart_err; /* DART error, bit 31 = fault latched */ ++ u32 dart_addr; /* DART last fault address */ ++}; ++ ++#define APPLE_ANS_NVMMU_MAX_REQS 36 ++#define APPLE_ANS_NVMMU_MAX_PAGES 256 ++#define APPLE_ANS_NVMMU_PAGE_SIZE 4096 ++ ++enum apple_ans_boot_mode { ++ APPLE_ANS_ROUND7, ++ APPLE_ANS_HANDOFF, ++ APPLE_ANS_FULL_CYCLE, ++}; ++ ++#if IS_REACHABLE(CONFIG_APPLE_DART) ++/* Stopped controller / bus mastering blocked; only this device's streams. */ ++int apple_ans_dart_set_active(struct device *dev, bool active); ++#else ++static inline int apple_ans_dart_set_active(struct device *dev, bool active) ++{ ++ return -EOPNOTSUPP; ++} ++#endif ++ ++#if IS_REACHABLE(CONFIG_PCIE_APPLE_H9P) ++enum apple_ans_boot_mode apple_ans_boot_mode(struct device *dev); ++int apple_ans_nvmmu_drain(struct device *dev); ++/* Endpoint certificate, valid only for the current system sleep attempt. */ ++int apple_ans_set_sleep_stopped(struct device *dev, bool stopped); ++int apple_ans_restart_port(struct device *dev); ++/* ++ * Map @npages 4K pages for FlatDMA slot @tag, or retire the slot when ++ * @npages is 0. @to_memory: the device writes host memory (NVMe Read). ++ */ ++int apple_ans_nvmmu_map(struct device *dev, unsigned int tag, ++ const u64 *pages, unsigned int npages, ++ bool to_memory, dma_addr_t *iova); ++/* Controller stopped, queues synchronized: rebuild all NVMMU cache state. */ ++int apple_ans_nvmmu_reset(struct device *dev); ++/* 1 if the endpoint's PCIe link is up, 0 if down, <0 on error */ ++int apple_ans_link_up(struct device *dev); ++int apple_ans_host_state(struct device *dev, struct apple_ans_host_state *st); ++void apple_ans_host_dump(struct device *dev, const char *when); ++void apple_ans_dart_walk(struct device *dev, const char *what, u64 iova); ++#else ++static inline enum apple_ans_boot_mode apple_ans_boot_mode(struct device *dev) ++{ ++ return APPLE_ANS_ROUND7; ++} ++ ++static inline int apple_ans_nvmmu_drain(struct device *dev) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_ans_set_sleep_stopped(struct device *dev, bool stopped) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_ans_restart_port(struct device *dev) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_ans_nvmmu_map(struct device *dev, unsigned int tag, ++ const u64 *pages, unsigned int npages, ++ bool to_memory, dma_addr_t *iova) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_ans_nvmmu_reset(struct device *dev) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_ans_link_up(struct device *dev) ++{ ++ return 1; ++} ++ ++static inline int apple_ans_host_state(struct device *dev, ++ struct apple_ans_host_state *st) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline void apple_ans_host_dump(struct device *dev, const char *when) ++{ ++} ++ ++static inline void apple_ans_dart_walk(struct device *dev, const char *what, ++ u64 iova) ++{ ++} ++#endif ++ ++#endif /* _LINUX_APPLE_ANS_H */ +diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h +index e19872e37e067f67a79d1ff8c1255265fce616d7..c9d84a39fe21e15ccf60cbf006e5c419a05bf4e8 100644 +--- a/include/linux/io-pgtable.h ++++ b/include/linux/io-pgtable.h +@@ -93,6 +93,11 @@ struct io_pgtable_cfg { + * kernel bug, which would justify the WARN_ON(). But for GPU + * drivers, this could be under control of userspace. Which + * deserves an error return, but not to spam dmesg. ++ * ++ * IO_PGTABLE_QUIRK_APPLE_DART_S5L8960X: (Apple DART format) A7-A11 ++ * DARTs. Table entries need bits 1:0 set (bit 0 alone isn't a ++ * valid table entry), and leaf PTEs have no DART1 subpage fields ++ * (S5L8960X puts its subpage range elsewhere; bit 1 disables it). + */ + #define IO_PGTABLE_QUIRK_ARM_NS BIT(0) + #define IO_PGTABLE_QUIRK_NO_PERMS BIT(1) +@@ -103,6 +108,7 @@ struct io_pgtable_cfg { + #define IO_PGTABLE_QUIRK_ARM_HD BIT(7) + #define IO_PGTABLE_QUIRK_ARM_S2FWB BIT(8) + #define IO_PGTABLE_QUIRK_NO_WARN BIT(9) ++ #define IO_PGTABLE_QUIRK_APPLE_DART_S5L8960X BIT(10) + unsigned long quirks; + unsigned long pgsize_bitmap; + unsigned int ias; +diff --git a/include/linux/soc/apple/pmgr-observe.h b/include/linux/soc/apple/pmgr-observe.h +new file mode 100644 +index 0000000000000000000000000000000000000000..477b7fa2235139c757a4bf64a109d3bce6f2d434 +--- /dev/null ++++ b/include/linux/soc/apple/pmgr-observe.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++#ifndef _LINUX_SOC_APPLE_PMGR_OBSERVE_H ++#define _LINUX_SOC_APPLE_PMGR_OBSERVE_H ++ ++#include ++ ++struct device_node; ++ ++struct apple_pmgr_observation { ++ u32 raw; ++ int genpd_status; ++}; ++ ++/* ++ * Diagnostic only. Nodes must be in child-before-parent lock order, with no ++ * duplicates (maximum five). Takes no power/runtime-PM reference. read() must ++ * not sleep or acquire genpd locks; it runs with IRQs disabled and all domain ++ * locks held, only if every genpd and hardware actual/target state is ON and ++ * reset/disable/parent-off are clear. Hardware auto-PM remains untouched and is ++ * recorded in raw; locks exclude software transitions, not autonomous gating. ++ * -EHOSTDOWN means read() was skipped. ++ */ ++int apple_pmgr_observe(struct device_node **nodes, unsigned int count, ++ struct apple_pmgr_observation *states, ++ void (*read)(void *), void *data); ++ ++#endif +diff --git a/include/linux/soc/apple/s8003-power.h b/include/linux/soc/apple/s8003-power.h +new file mode 100644 +index 0000000000000000000000000000000000000000..b95eba2458f14b7cca982147ca2256a035270bf6 +--- /dev/null ++++ b/include/linux/soc/apple/s8003-power.h +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++#ifndef _LINUX_SOC_APPLE_S8003_POWER_H ++#define _LINUX_SOC_APPLE_S8003_POWER_H ++ ++#include ++#include ++ ++struct apple_a9_thermal_profile { ++ bool tuning; ++ int passive, hot, critical, arm, cooldown; ++}; ++ ++static inline struct apple_a9_thermal_profile apple_a9_stock_thermal_profile(void) ++{ ++ return (struct apple_a9_thermal_profile) { ++ .passive = 80000, .hot = 90000, .critical = 100000, ++ .arm = 75000, .cooldown = 70000, ++ }; ++} ++ ++static inline const char * ++apple_a9_thermal_profile_name(struct apple_a9_thermal_profile p) ++{ ++ return !p.tuning ? "stock" : p.passive == 90000 ? "sustained" : "custom"; ++} ++ ++#if IS_ENABLED(CONFIG_APPLE_A9_TUNING) ++void apple_a9_thermal_revert(void); ++struct apple_a9_thermal_profile apple_a9_get_thermal_profile(void); ++#else ++static inline void apple_a9_thermal_revert(void) {} ++static inline struct apple_a9_thermal_profile apple_a9_get_thermal_profile(void) ++{ ++ return apple_a9_stock_thermal_profile(); ++} ++#endif ++ ++#endif diff --git a/kernel/patches/j71t/0002-j71t-wifi-bluetooth.patch b/kernel/patches/j71t/0002-j71t-wifi-bluetooth.patch new file mode 100644 index 00000000..d901ad6e --- /dev/null +++ b/kernel/patches/j71t/0002-j71t-wifi-bluetooth.patch @@ -0,0 +1,955 @@ +From 57ff1534a2bee127d03788ff0030cd3c96b3bbfb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 02/12] wifi/Bluetooth: add BCM4355 Wi-Fi and Bluetooth for + J71t +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +brcmfmac PCIe support for the J71t BCM4355 C0 (Apple board fallback +names, calibration from the device tree), and Bluetooth over the +Samsung UART with serdev: patchram loading and the J71t device +address. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../net/bluetooth/brcm,bluetooth.yaml | 5 + + .../bindings/serial/samsung_uart.yaml | 6 +- + drivers/bluetooth/btbcm.c | 110 +++++++++++++++++- + drivers/bluetooth/btbcm.h | 6 + + drivers/bluetooth/hci_bcm.c | 92 +++++++++++++++ + .../broadcom/brcm80211/brcmfmac/feature.c | 28 +++++ + .../wireless/broadcom/brcm80211/brcmfmac/of.c | 16 ++- + .../broadcom/brcm80211/brcmfmac/pcie.c | 10 ++ + drivers/tty/serdev/core.c | 12 ++ + drivers/tty/serdev/serdev-ttyport.c | 28 +++++ + drivers/tty/serial/samsung_tty.c | 92 ++++++++++++++- + include/linux/serdev.h | 10 ++ + include/net/bluetooth/hci.h | 11 ++ + net/bluetooth/hci_event.c | 8 +- + net/bluetooth/hci_sync.c | 16 ++- + 15 files changed, 433 insertions(+), 17 deletions(-) + +diff --git a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml +index 95501e858e6f824030f304d283eeb3e3be0e4294..0a2bf4fb37dc528b04331e8be0ba90ed695e711d 100644 +--- a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml ++++ b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml +@@ -15,6 +15,10 @@ description: + properties: + compatible: + oneOf: ++ - const: apple,j71t-bcm4355-bt ++ - items: ++ - const: apple,j172-bcm4355c1-bt ++ - const: brcm,bcm4355c1-bt + - items: + - enum: + - infineon,cyw43439-bt +@@ -29,6 +33,7 @@ properties: + - brcm,bcm43438-bt + - brcm,bcm4345c5 + - brcm,bcm43540-bt ++ - brcm,bcm4355c1-bt + - brcm,bcm4335a0 + - brcm,bcm4349-bt + - cypress,cyw4373a0-bt +diff --git a/Documentation/devicetree/bindings/serial/samsung_uart.yaml b/Documentation/devicetree/bindings/serial/samsung_uart.yaml +index 75ac2a08f2576cfa99ea1df2898a2fed6ea916f8..68542bd1b4d94599743c4789b5e95fc1bf0298c3 100644 +--- a/Documentation/devicetree/bindings/serial/samsung_uart.yaml ++++ b/Documentation/devicetree/bindings/serial/samsung_uart.yaml +@@ -30,7 +30,11 @@ properties: + - samsung,exynos8895-uart + - items: + - enum: +- - samsung,exynos2200-uart ++ - apple,s8000-uart ++ - apple,t8010-uart ++ - const: apple,s5l-uart ++ - items: ++ - const: samsung,exynos2200-uart + - const: google,gs101-uart + - items: + - enum: +diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c +index 463d59890befe51c31bbb8135b1d2198ec47b17f..0e7d811512e63e2e7672f665f2bdb47eb828ae7a 100644 +--- a/drivers/bluetooth/btbcm.c ++++ b/drivers/bluetooth/btbcm.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -30,6 +31,8 @@ + #define BDADDR_BCM4330B1 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb1, 0x30, 0x43}}) + #define BDADDR_BCM4334B0 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb0, 0x34, 0x43}}) + #define BDADDR_BCM4345C5 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc5, 0x45, 0x43}}) ++#define BDADDR_BCM4355C0 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc0, 0x55, 0x43}}) ++#define BDADDR_BCM4355C1 (&(bdaddr_t) {{0x93, 0x76, 0x00, 0xc1, 0x55, 0x43}}) + #define BDADDR_BCM43341B (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0x1b, 0x34, 0x43}}) + + #define BCM_FW_NAME_LEN 64 +@@ -120,6 +123,12 @@ int btbcm_check_bdaddr(struct hci_dev *hdev) + * + * The address AA:AA:AA:AA:AA:AA indicates a BCM43430A1 controller + * with no configured address. ++ * ++ * The address 43:55:C0:00:1F:AC indicates a BCM4355C0 controller ++ * with no configured address. ++ * ++ * The address 43:55:C1:00:76:93 indicates a BCM4355C1 controller ++ * with no configured address. + */ + if (!bacmp(&bda->bdaddr, BDADDR_BCM20702A0) || + !bacmp(&bda->bdaddr, BDADDR_BCM20702A1) || +@@ -128,6 +137,8 @@ int btbcm_check_bdaddr(struct hci_dev *hdev) + !bacmp(&bda->bdaddr, BDADDR_BCM4330B1) || + !bacmp(&bda->bdaddr, BDADDR_BCM4334B0) || + !bacmp(&bda->bdaddr, BDADDR_BCM4345C5) || ++ !bacmp(&bda->bdaddr, BDADDR_BCM4355C0) || ++ !bacmp(&bda->bdaddr, BDADDR_BCM4355C1) || + !bacmp(&bda->bdaddr, BDADDR_BCM43430A0) || + !bacmp(&bda->bdaddr, BDADDR_BCM43430A1) || + !bacmp(&bda->bdaddr, BDADDR_BCM43341B)) { +@@ -269,7 +280,7 @@ int btbcm_patchram(struct hci_dev *hdev, const struct firmware *fw) + } + EXPORT_SYMBOL(btbcm_patchram); + +-static int btbcm_reset(struct hci_dev *hdev) ++int btbcm_reset(struct hci_dev *hdev) + { + struct sk_buff *skb; + +@@ -287,6 +298,7 @@ static int btbcm_reset(struct hci_dev *hdev) + + return 0; + } ++EXPORT_SYMBOL_GPL(btbcm_reset); + + static struct sk_buff *btbcm_read_local_name(struct hci_dev *hdev) + { +@@ -500,6 +512,7 @@ static const struct bcm_subver_table bcm_uart_subver_table[] = { + { 0x4606, "BCM4324B5" }, /* 002.006.006 */ + { 0x6109, "BCM4335C0" }, /* 003.001.009 */ + { 0x610c, "BCM4354" }, /* 003.001.012 */ ++ { 0x6214, "BCM4355C1" }, /* 003.002.020 */ + { 0x2122, "BCM4343A0" }, /* 001.001.034 */ + { 0x2209, "BCM43430A1" }, /* 001.002.009 */ + { 0x6119, "BCM4345C0" }, /* 003.001.025 */ +@@ -668,7 +681,11 @@ int btbcm_initialize(struct hci_dev *hdev, bool *fw_load_done, bool use_autobaud + fw_name_count++; + + for (i = 0; i < fw_name_count; i++) { +- err = firmware_request_nowarn(&fw, fw_name[i], &hdev->dev); ++ /* J71t RAM boots have no firmware helper; fail each name fast. */ ++ if (board_name && !strcmp(board_name, "apple,j71t")) ++ err = request_firmware_direct(&fw, fw_name[i], &hdev->dev); ++ else ++ err = firmware_request_nowarn(&fw, fw_name[i], &hdev->dev); + if (err == 0) { + bt_dev_info(hdev, "%s '%s' Patch", + hw_name ? hw_name : "BCM", fw_name[i]); +@@ -694,6 +711,91 @@ int btbcm_initialize(struct hci_dev *hdev, bool *fw_load_done, bool use_autobaud + } + EXPORT_SYMBOL_GPL(btbcm_initialize); + ++/* Public identities are in HCI byte order here; the firmware file is BMac. */ ++static bool btbcm_j71t_bdaddr_valid(const bdaddr_t *addr) ++{ ++ return bacmp(addr, BDADDR_ANY) && !(addr->b[5] & 1); ++} ++ ++static int btbcm_j71t_set_bdaddr(struct hci_dev *hdev) ++{ ++ struct fwnode_handle *fwnode; ++ struct hci_rp_read_bd_addr *rp; ++ const struct firmware *fw; ++ struct sk_buff *skb; ++ const char *source; ++ bdaddr_t addr; ++ int err; ++ ++ if (!hdev->dev.parent || ++ !of_device_is_compatible(hdev->dev.parent->of_node, ++ "apple,j71t-bcm4355-bt")) ++ return 0; ++ ++ fwnode = dev_fwnode(hdev->dev.parent); ++ if (fwnode_property_count_u8(fwnode, "local-bd-address") == sizeof(addr) && ++ !fwnode_property_read_u8_array(fwnode, "local-bd-address", ++ (u8 *)&addr, sizeof(addr))) { ++ if (hci_test_quirk(hdev, HCI_QUIRK_BDADDR_PROPERTY_BROKEN)) { ++ bdaddr_t swapped; ++ ++ baswap(&swapped, &addr); ++ bacpy(&addr, &swapped); ++ } ++ } else { ++ bacpy(&addr, BDADDR_ANY); ++ } ++ ++ source = "device-tree"; ++ if (!btbcm_j71t_bdaddr_valid(&addr)) { ++ /* Direct loading cannot stall a RAM boot in the sysfs fallback. */ ++ err = request_firmware_direct(&fw, "apple/j71t-BMac.bin", ++ &hdev->dev); ++ if (err) { ++ bt_dev_err(hdev, "BCM: No valid J71t device address (%d)", err); ++ return err; ++ } ++ if (fw->size != sizeof(addr)) { ++ release_firmware(fw); ++ return -EINVAL; ++ } ++ /* Resolved iOS BMac is stored in display/network byte order. */ ++ baswap(&addr, (const bdaddr_t *)fw->data); ++ release_firmware(fw); ++ if (!btbcm_j71t_bdaddr_valid(&addr)) ++ return -EINVAL; ++ source = "private BMac"; ++ } ++ ++ /* Program after the last Patchram reset, before the default-address check. */ ++ err = btbcm_set_bdaddr(hdev, &addr); ++ if (err) ++ return err; ++ ++ skb = __hci_cmd_sync(hdev, HCI_OP_READ_BD_ADDR, 0, NULL, ++ HCI_INIT_TIMEOUT); ++ if (IS_ERR(skb)) ++ return PTR_ERR(skb); ++ if (skb->len != sizeof(*rp)) { ++ kfree_skb(skb); ++ return -EIO; ++ } ++ rp = (struct hci_rp_read_bd_addr *)skb->data; ++ err = rp->status || bacmp(&rp->bdaddr, &addr) ? -EIO : 0; ++ kfree_skb(skb); ++ if (err) { ++ bt_dev_err(hdev, "BCM: J71t device address readback failed"); ++ return err; ++ } ++ ++ /* Keep MGMT reconfiguration consistent with the checked controller state. */ ++ bacpy(&hdev->public_addr, &addr); ++ hci_clear_quirk(hdev, HCI_QUIRK_INVALID_BDADDR); ++ hci_clear_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); ++ bt_dev_info(hdev, "BCM: Using %s device address (%pMR)", source, &addr); ++ return 0; ++} ++ + int btbcm_finalize(struct hci_dev *hdev, bool *fw_load_done, bool use_autobaud_mode) + { + int err; +@@ -705,6 +807,10 @@ int btbcm_finalize(struct hci_dev *hdev, bool *fw_load_done, bool use_autobaud_m + return err; + } + ++ err = btbcm_j71t_set_bdaddr(hdev); ++ if (err) ++ return err; ++ + btbcm_check_bdaddr(hdev); + + hci_set_quirk(hdev, HCI_QUIRK_STRICT_DUPLICATE_FILTER); +diff --git a/drivers/bluetooth/btbcm.h b/drivers/bluetooth/btbcm.h +index b4cb24231a202e2e1458a8b8376d2a07d440ad18..c46b8096cdad79ae143c2f0372fe573145419234 100644 +--- a/drivers/bluetooth/btbcm.h ++++ b/drivers/bluetooth/btbcm.h +@@ -52,6 +52,7 @@ struct bcm_set_pcm_format_params { + #if IS_ENABLED(CONFIG_BT_BCM) + + int btbcm_check_bdaddr(struct hci_dev *hdev); ++int btbcm_reset(struct hci_dev *hdev); + int btbcm_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr); + int btbcm_patchram(struct hci_dev *hdev, const struct firmware *fw); + int btbcm_read_pcm_int_params(struct hci_dev *hdev, +@@ -72,6 +73,11 @@ static inline int btbcm_check_bdaddr(struct hci_dev *hdev) + return -EOPNOTSUPP; + } + ++static inline int btbcm_reset(struct hci_dev *hdev) ++{ ++ return -EOPNOTSUPP; ++} ++ + static inline int btbcm_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) + { + return -EOPNOTSUPP; +diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c +index 9a103db7e3558b2db8154a3860a6bd002afab569..d991d62cba2059a4b6a22673e758d68ee23a59da 100644 +--- a/drivers/bluetooth/hci_bcm.c ++++ b/drivers/bluetooth/hci_bcm.c +@@ -53,6 +53,10 @@ + * struct bcm_device_data - device specific data + * @no_early_set_baudrate: Disallow set baudrate before driver setup() + * @drive_rts_on_open: drive RTS signal on ->open() when platform requires it ++ * @wake_before_power_cycle: assert BT_WAKE before cycling BT_REG_ON on powerup ++ * @set_baudrate_before_patch: switch to operational speed before Patchram ++ * @broken_read_transmit_power: controller cannot read LE min/max TX power ++ * @fixup_le_adv_report_evt_type: controller uses reserved advertising bits + * @no_uart_clock_set: UART clock set command for >3Mbps mode is unavailable + * @max_autobaud_speed: max baudrate supported by device in autobaud mode + * @max_speed: max baudrate supported +@@ -60,6 +64,10 @@ + struct bcm_device_data { + bool no_early_set_baudrate; + bool drive_rts_on_open; ++ bool wake_before_power_cycle; ++ bool set_baudrate_before_patch; ++ bool broken_read_transmit_power; ++ bool fixup_le_adv_report_evt_type; + bool no_uart_clock_set; + u32 max_autobaud_speed; + u32 max_speed; +@@ -104,6 +112,10 @@ struct bcm_device_data { + * @is_suspended: whether flow control is currently disabled + * @no_early_set_baudrate: don't set_baudrate before setup() + * @drive_rts_on_open: drive RTS signal on ->open() when platform requires it ++ * @wake_before_power_cycle: assert BT_WAKE before cycling BT_REG_ON on powerup ++ * @set_baudrate_before_patch: switch to operational speed before Patchram ++ * @broken_read_transmit_power: controller cannot read LE min/max TX power ++ * @fixup_le_adv_report_evt_type: controller uses reserved advertising bits + * @no_uart_clock_set: UART clock set command for >3Mbps mode is unavailable + * @pcm_int_params: keep the initial PCM configuration + * @use_autobaud_mode: start Bluetooth device in autobaud mode +@@ -145,6 +157,10 @@ struct bcm_device { + #endif + bool no_early_set_baudrate; + bool drive_rts_on_open; ++ bool wake_before_power_cycle; ++ bool set_baudrate_before_patch; ++ bool broken_read_transmit_power; ++ bool fixup_le_adv_report_evt_type; + bool no_uart_clock_set; + bool use_autobaud_mode; + u8 pcm_int_params[5]; +@@ -277,6 +293,24 @@ static int bcm_gpio_set_power(struct bcm_device *dev, bool powered) + goto err_lpo_clk_disable; + } + ++ if (powered && dev->wake_before_power_cycle) { ++ err = dev->set_device_wakeup(dev, true); ++ if (err) ++ goto err_power_cycle; ++ ++ err = dev->set_shutdown(dev, false); ++ if (err) ++ goto err_power_cycle; ++ ++ usleep_range(100000, 120000); ++ ++ err = dev->set_shutdown(dev, true); ++ if (err) ++ goto err_power_cycle; ++ ++ goto wait_power; ++ } ++ + err = dev->set_shutdown(dev, powered); + if (err) + goto err_txco_clk_disable; +@@ -297,6 +331,7 @@ static int bcm_gpio_set_power(struct bcm_device *dev, bool powered) + dev->supplies); + } + ++wait_power: + /* wait for device to power on and come out of reset */ + usleep_range(100000, 120000); + +@@ -304,6 +339,10 @@ static int bcm_gpio_set_power(struct bcm_device *dev, bool powered) + + return 0; + ++err_power_cycle: ++ dev->set_shutdown(dev, false); ++ dev->set_device_wakeup(dev, false); ++ goto err_txco_clk_disable; + err_revert_shutdown: + dev->set_shutdown(dev, !powered); + err_txco_clk_disable: +@@ -582,6 +621,7 @@ static int bcm_setup(struct hci_uart *hu) + { + struct bcm_data *bcm = hu->priv; + bool fw_load_done = false; ++ bool baudrate_set_before_patch; + bool use_autobaud_mode = (bcm->dev ? bcm->dev->use_autobaud_mode : 0); + unsigned int speed; + int err; +@@ -591,6 +631,30 @@ static int bcm_setup(struct hci_uart *hu) + hu->hdev->set_diag = bcm_set_diag; + hu->hdev->set_bdaddr = btbcm_set_bdaddr; + ++ if (bcm->dev && bcm->dev->broken_read_transmit_power) ++ hci_set_quirk(hu->hdev, ++ HCI_QUIRK_BROKEN_READ_TRANSMIT_POWER); ++ ++ if (bcm->dev && bcm->dev->fixup_le_adv_report_evt_type) ++ hci_set_quirk(hu->hdev, ++ HCI_QUIRK_FIXUP_LE_ADV_REPORT_EVT_TYPE); ++ ++ baudrate_set_before_patch = bcm->dev && ++ bcm->dev->set_baudrate_before_patch && ++ bcm->dev->oper_speed; ++ if (baudrate_set_before_patch) { ++ err = btbcm_reset(hu->hdev); ++ if (err) ++ return err; ++ ++ speed = bcm->dev->oper_speed; ++ err = bcm_set_baudrate(hu, speed); ++ if (err) ++ return err; ++ ++ host_set_baudrate(hu, speed); ++ } ++ + err = btbcm_initialize(hu->hdev, &fw_load_done, use_autobaud_mode); + if (err) + return err; +@@ -1226,6 +1290,7 @@ static int bcm_of_probe(struct bcm_device *bdev) + { + bdev->use_autobaud_mode = device_property_read_bool(bdev->dev, + "brcm,requires-autobaud-mode"); ++ device_property_read_u32(bdev->dev, "current-speed", &bdev->init_speed); + device_property_read_u32(bdev->dev, "max-speed", &bdev->oper_speed); + device_property_read_u8_array(bdev->dev, "brcm,bt-pcm-int-params", + bdev->pcm_int_params, 5); +@@ -1549,6 +1614,13 @@ static int bcm_serdev_probe(struct serdev_device *serdev) + bcmdev->max_autobaud_speed = data->max_autobaud_speed; + bcmdev->no_early_set_baudrate = data->no_early_set_baudrate; + bcmdev->drive_rts_on_open = data->drive_rts_on_open; ++ bcmdev->wake_before_power_cycle = data->wake_before_power_cycle; ++ bcmdev->set_baudrate_before_patch = ++ data->set_baudrate_before_patch; ++ bcmdev->broken_read_transmit_power = ++ data->broken_read_transmit_power; ++ bcmdev->fixup_le_adv_report_evt_type = ++ data->fixup_le_adv_report_evt_type; + bcmdev->no_uart_clock_set = data->no_uart_clock_set; + if (data->max_speed && bcmdev->oper_speed > data->max_speed) + bcmdev->oper_speed = data->max_speed; +@@ -1569,6 +1641,20 @@ static struct bcm_device_data bcm4354_device_data = { + .no_early_set_baudrate = true, + }; + ++static struct bcm_device_data bcm4355_device_data = { ++ .no_early_set_baudrate = true, ++ .drive_rts_on_open = true, ++}; ++ ++static struct bcm_device_data bcm4355_j172_device_data = { ++ .no_early_set_baudrate = true, ++ .wake_before_power_cycle = true, ++ .set_baudrate_before_patch = true, ++ /* Also required by the J71t BCM4355C0 controller. */ ++ .broken_read_transmit_power = true, ++ .fixup_le_adv_report_evt_type = true, ++}; ++ + static struct bcm_device_data bcm43438_device_data = { + .drive_rts_on_open = true, + }; +@@ -1592,6 +1678,12 @@ static const struct of_device_id bcm_bluetooth_of_match[] = { + { .compatible = "brcm,bcm43438-bt", .data = &bcm43438_device_data }, + { .compatible = "brcm,bcm4349-bt", .data = &bcm43438_device_data }, + { .compatible = "brcm,bcm43540-bt", .data = &bcm4354_device_data }, ++ /* A9 module revision is selected by ROM and board-specific firmware. */ ++ { .compatible = "apple,j71t-bcm4355-bt", ++ .data = &bcm4355_j172_device_data }, ++ { .compatible = "apple,j172-bcm4355c1-bt", ++ .data = &bcm4355_j172_device_data }, ++ { .compatible = "brcm,bcm4355c1-bt", .data = &bcm4355_device_data }, + { .compatible = "brcm,bcm4335a0" }, + { .compatible = "cypress,cyw4373a0-bt", .data = &cyw4373a0_device_data }, + { .compatible = "infineon,cyw55572-bt", .data = &cyw55572_device_data }, +diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +index 488364ef8ff2a1f2dfd92386cec9f50b4f90c5cd..63cc18694755de0c0832c4c518cd629c8866d05f 100644 +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +@@ -104,6 +104,33 @@ static const struct brcmf_feat_fwfeat brcmf_feat_fwfeat_map[] = { + { "01-d2cbb8fd", BIT(BRCMF_FEAT_MONITOR_FMT_HW_RX_HDR) }, + }; + ++/* Features a given firmware build advertises but can't actually do */ ++static const struct brcmf_feat_fwfeat brcmf_feat_fwfeat_disable_map[] = { ++ /* ++ * Apple iOS 16 BCM4355 C0 "FearA" firmware (9.44.205.0.3.50.46): ++ * answers the "p2p" iovar but fails "p2p_da_override" with -52, so ++ * every P2P device creation fails. Apple uses AWDL, not Wi-Fi P2P. ++ */ ++ { "01-9787ef4d", BIT(BRCMF_FEAT_P2P) }, ++}; ++ ++static void brcmf_feat_firmware_disables(struct brcmf_pub *drv) ++{ ++ const struct brcmf_feat_fwfeat *e; ++ int i, j; ++ ++ for (i = 0; i < ARRAY_SIZE(brcmf_feat_fwfeat_disable_map); i++) { ++ e = &brcmf_feat_fwfeat_disable_map[i]; ++ if (strcmp(e->fwid, drv->fwver)) ++ continue; ++ for (j = 0; j < BRCMF_FEAT_LAST; j++) ++ if (e->feat_flags & drv->feat_flags & BIT(j)) ++ brcmf_info("firmware %s: disabling feature %s\n", ++ e->fwid, brcmf_feat_names[j]); ++ drv->feat_flags &= ~e->feat_flags; ++ } ++} ++ + static void brcmf_feat_firmware_overrides(struct brcmf_pub *drv) + { + const struct brcmf_feat_fwfeat *e; +@@ -343,6 +370,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) + + brcmf_feat_wlc_version_overrides(drvr); + brcmf_feat_firmware_overrides(drvr); ++ brcmf_feat_firmware_disables(drvr); + + brcmf_fwvid_feat_attach(ifp); + +diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c +index 03efae36a0b2d3e7eb2264799e31b4906efaf920..3dd4a640602f5a0af22048014911d242a82881a2 100644 +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c +@@ -121,13 +121,21 @@ int brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type, + + brcmf_dbg(INFO, "%s LPO clock\n", clk ? "enable" : "no"); + ++ /* ++ * The binding allows the country code map on the PCIe compatibles ++ * (pci14e4,*) too, and without one the firmware never leaves its ++ * NVRAM default country (e.g. "XZ") when cfg80211 sets a country. ++ */ ++ if (np) { ++ err = brcmf_of_get_country_codes(dev, settings); ++ if (err) ++ brcmf_err("failed to get OF country code map (err=%d)\n", ++ err); ++ } ++ + if (!np || !of_device_is_compatible(np, "brcm,bcm4329-fmac")) + return 0; + +- err = brcmf_of_get_country_codes(dev, settings); +- if (err) +- brcmf_err("failed to get OF country code map (err=%d)\n", err); +- + err = of_get_mac_address(np, settings->mac); + if (err == -EPROBE_DEFER) + return err; +diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +index 55f4d7b970f28787f89534838f00248ace00cfee..e4f8216a51446e58bc2a5b66a2758392b765acb9 100644 +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +@@ -2676,6 +2676,16 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) + devinfo = bus->bus_priv.pcie->devinfo; + brcmf_dbg(PCIE, "Enter, dev=%p, bus=%p\n", dev, bus); + ++ /* ++ * The host bridge cut the device off because its link did not come ++ * back. Any BAR access now could stall the CPU, so stay down; with the ++ * bus down, firmware commands and transmits are refused early. ++ */ ++ if (pci_dev_is_disconnected(devinfo->pdev)) { ++ brcmf_err(bus, "device lost over suspend, staying down\n"); ++ return -ENODEV; ++ } ++ + /* Check if device is still up and running, if so we are ready */ + if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) { + brcmf_dbg(PCIE, "Try to wakeup device....\n"); +diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c +index 7500efcdfc218b0f14bfa6bf44105950ee6a8bd1..2901574bd1af24dc650f0628a5fe3a73e5afa3ca 100644 +--- a/drivers/tty/serdev/core.c ++++ b/drivers/tty/serdev/core.c +@@ -351,6 +351,18 @@ int serdev_device_set_parity(struct serdev_device *serdev, + } + EXPORT_SYMBOL_GPL(serdev_device_set_parity); + ++int serdev_device_set_stopbits(struct serdev_device *serdev, ++ enum serdev_stopbits stopbits) ++{ ++ struct serdev_controller *ctrl = serdev->ctrl; ++ ++ if (!ctrl || !ctrl->ops->set_stopbits) ++ return -EOPNOTSUPP; ++ ++ return ctrl->ops->set_stopbits(ctrl, stopbits); ++} ++EXPORT_SYMBOL_GPL(serdev_device_set_stopbits); ++ + void serdev_device_wait_until_sent(struct serdev_device *serdev, long timeout) + { + struct serdev_controller *ctrl = serdev->ctrl; +diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c +index bab1b143b8a68d486cec17890e0eda0bddc47be1..5acb898a7a900cef7d1ad5fa298de23b559c5570 100644 +--- a/drivers/tty/serdev/serdev-ttyport.c ++++ b/drivers/tty/serdev/serdev-ttyport.c +@@ -207,6 +207,33 @@ static int ttyport_set_parity(struct serdev_controller *ctrl, + return 0; + } + ++static int ttyport_set_stopbits(struct serdev_controller *ctrl, ++ enum serdev_stopbits stopbits) ++{ ++ struct serport *serport = serdev_controller_get_drvdata(ctrl); ++ struct tty_struct *tty = serport->tty; ++ struct ktermios ktermios = tty->termios; ++ ++ switch (stopbits) { ++ case SERDEV_STOPBITS_1: ++ ktermios.c_cflag &= ~CSTOPB; ++ break; ++ case SERDEV_STOPBITS_2: ++ ktermios.c_cflag |= CSTOPB; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ tty_set_termios(tty, &ktermios); ++ ++ if ((tty->termios.c_cflag & CSTOPB) != ++ (ktermios.c_cflag & CSTOPB)) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout) + { + struct serport *serport = serdev_controller_get_drvdata(ctrl); +@@ -255,6 +282,7 @@ static const struct serdev_controller_ops ctrl_ops = { + .close = ttyport_close, + .set_flow_control = ttyport_set_flow_control, + .set_parity = ttyport_set_parity, ++ .set_stopbits = ttyport_set_stopbits, + .set_baudrate = ttyport_set_baudrate, + .wait_until_sent = ttyport_wait_until_sent, + .get_tiocm = ttyport_get_tiocm, +diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c +index 63d0232dffc20367222a6784ca0db154db438b71..485059f95077a4fb9affc0de53019319b3a1765d 100644 +--- a/drivers/tty/serial/samsung_tty.c ++++ b/drivers/tty/serial/samsung_tty.c +@@ -22,6 +22,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -65,6 +66,11 @@ + /* flag to ignore all characters coming in */ + #define RXSTAT_DUMMY_READ (0x10000000) + ++#define T8010_UBRDIV_DIV GENMASK(15, 0) ++#define T8010_UBRDIV_OSR GENMASK(19, 16) ++#define T8010_MAX_OVERSAMPLING 16 ++#define T8010_MIN_OVERSAMPLING 4 ++ + enum s3c24xx_port_type { + TYPE_S3C6400, + TYPE_APPLE_S5L, +@@ -96,6 +102,7 @@ struct s3c24xx_serial_drv_data { + const struct s3c24xx_uart_info info; + const struct s3c2410_uartcfg def_cfg; + const unsigned int fifosize[UART_NR]; ++ bool has_ubrdiv_oversampling; + }; + + struct s3c24xx_uart_dma { +@@ -1370,6 +1377,7 @@ static unsigned int s3c24xx_serial_getclk(struct s3c24xx_uart_port *ourport, + u8 *clk_num) + { + const struct s3c24xx_uart_info *info = ourport->info; ++ const struct s3c24xx_serial_drv_data *drv_data = ourport->drv_data; + struct clk *clk; + unsigned long rate; + unsigned int baud, quot, best_quot = 0; +@@ -1407,13 +1415,40 @@ static unsigned int s3c24xx_serial_getclk(struct s3c24xx_uart_port *ourport, + * is easy enough to recalculate. + */ + +- quot = div / 16; ++ quot = div / 16 - 1; + baud = rate / div; ++ } else if (drv_data->has_ubrdiv_oversampling) { ++ unsigned int oversampling; ++ unsigned int divisor; ++ ++ oversampling = min_t(unsigned long, ++ T8010_MAX_OVERSAMPLING, ++ rate / req_baud); ++ if (oversampling < T8010_MIN_OVERSAMPLING) { ++ clk_put(clk); ++ continue; ++ } ++ ++ divisor = DIV_ROUND_CLOSEST(rate, ++ (unsigned long)oversampling * ++ req_baud); ++ if (!divisor || ++ divisor > FIELD_MAX(T8010_UBRDIV_DIV) + 1) { ++ clk_put(clk); ++ continue; ++ } ++ ++ baud = rate / (divisor * oversampling); ++ quot = FIELD_PREP(T8010_UBRDIV_DIV, divisor - 1) | ++ FIELD_PREP(T8010_UBRDIV_OSR, ++ T8010_MAX_OVERSAMPLING - ++ oversampling); + } else { +- quot = (rate + (8 * req_baud)) / (16 * req_baud); ++ quot = DIV_ROUND_CLOSEST(rate, ++ 16UL * req_baud); + baud = rate / (quot * 16); ++ quot--; + } +- quot--; + + calc_deviation = abs(req_baud - baud); + +@@ -2279,6 +2314,8 @@ static void + s3c24xx_serial_get_options(struct uart_port *port, int *baud, + int *parity, int *bits) + { ++ const struct s3c24xx_serial_drv_data *drv_data = ++ to_ourport(port)->drv_data; + struct clk *clk; + unsigned long rate; + u32 ulcon, ucon, ubrdiv; +@@ -2331,7 +2368,19 @@ s3c24xx_serial_get_options(struct uart_port *port, int *baud, + else + rate = 1; + +- *baud = rate / (16 * (ubrdiv + 1)); ++ if (drv_data->has_ubrdiv_oversampling) { ++ unsigned int divisor; ++ unsigned int oversampling; ++ ++ divisor = FIELD_GET(T8010_UBRDIV_DIV, ubrdiv) + 1; ++ oversampling = T8010_MAX_OVERSAMPLING - ++ FIELD_GET(T8010_UBRDIV_OSR, ubrdiv); ++ if (oversampling < T8010_MIN_OVERSAMPLING) ++ oversampling = T8010_MAX_OVERSAMPLING; ++ *baud = rate / (oversampling * divisor); ++ } else { ++ *baud = rate / (16 * (ubrdiv + 1)); ++ } + dev_dbg(port->dev, "calculated baud %d\n", *baud); + } + } +@@ -2558,9 +2607,37 @@ static const struct s3c24xx_serial_drv_data s5l_serial_drv_data = { + .ufcon = S3C2410_UFCON_DEFAULT, + }, + }; ++ ++static const struct s3c24xx_serial_drv_data t8010_serial_drv_data = { ++ .info = { ++ .name = "Apple T8010 UART", ++ .type = TYPE_APPLE_S5L, ++ .port_type = PORT_8250, ++ .iotype = UPIO_MEM32, ++ .fifosize = 16, ++ .rx_fifomask = S3C2410_UFSTAT_RXMASK, ++ .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT, ++ .rx_fifofull = S3C2410_UFSTAT_RXFULL, ++ .tx_fifofull = S3C2410_UFSTAT_TXFULL, ++ .tx_fifomask = S3C2410_UFSTAT_TXMASK, ++ .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT, ++ .def_clk_sel = S3C2410_UCON_CLKSEL0, ++ .num_clks = 1, ++ .clksel_mask = 0, ++ .clksel_shift = 0, ++ .ucon_mask = APPLE_S5L_UCON_MASK, ++ }, ++ .def_cfg = { ++ .ucon = APPLE_S5L_UCON_DEFAULT, ++ .ufcon = S3C2410_UFCON_DEFAULT, ++ }, ++ .has_ubrdiv_oversampling = true, ++}; + #define S5L_SERIAL_DRV_DATA (&s5l_serial_drv_data) ++#define T8010_SERIAL_DRV_DATA (&t8010_serial_drv_data) + #else + #define S5L_SERIAL_DRV_DATA NULL ++#define T8010_SERIAL_DRV_DATA NULL + #endif + + #if defined(CONFIG_ARCH_ARTPEC) +@@ -2610,6 +2687,9 @@ static const struct platform_device_id s3c24xx_serial_driver_ids[] = { + }, { + .name = "s5l-uart", + .driver_data = (kernel_ulong_t)S5L_SERIAL_DRV_DATA, ++ }, { ++ .name = "t8010-uart", ++ .driver_data = (kernel_ulong_t)T8010_SERIAL_DRV_DATA, + }, { + .name = "exynos850-uart", + .driver_data = (kernel_ulong_t)EXYNOS850_SERIAL_DRV_DATA, +@@ -2639,6 +2719,10 @@ static const struct of_device_id s3c24xx_uart_dt_match[] = { + .data = EXYNOS5433_SERIAL_DRV_DATA }, + { .compatible = "apple,s5l-uart", + .data = S5L_SERIAL_DRV_DATA }, ++ { .compatible = "apple,s8000-uart", ++ .data = T8010_SERIAL_DRV_DATA }, ++ { .compatible = "apple,t8010-uart", ++ .data = T8010_SERIAL_DRV_DATA }, + { .compatible = "samsung,exynos850-uart", + .data = EXYNOS850_SERIAL_DRV_DATA }, + { .compatible = "axis,artpec8-uart", +diff --git a/include/linux/serdev.h b/include/linux/serdev.h +index b6c3d957ec1558321be244818c6222c45f77f263..94ab14b6642e532cf7aab41491c1e3fb9c2e8dc1 100644 +--- a/include/linux/serdev.h ++++ b/include/linux/serdev.h +@@ -74,6 +74,11 @@ enum serdev_parity { + SERDEV_PARITY_ODD, + }; + ++enum serdev_stopbits { ++ SERDEV_STOPBITS_1, ++ SERDEV_STOPBITS_2, ++}; ++ + /* + * serdev controller structures + */ +@@ -84,6 +89,8 @@ struct serdev_controller_ops { + void (*close)(struct serdev_controller *); + void (*set_flow_control)(struct serdev_controller *, bool); + int (*set_parity)(struct serdev_controller *, enum serdev_parity); ++ int (*set_stopbits)(struct serdev_controller *ctrl, ++ enum serdev_stopbits stopbits); + unsigned int (*set_baudrate)(struct serdev_controller *, unsigned int); + void (*wait_until_sent)(struct serdev_controller *, long); + int (*get_tiocm)(struct serdev_controller *); +@@ -253,6 +260,7 @@ static inline int serdev_device_set_tiocm(struct serdev_device *serdev, int set, + { + return -EOPNOTSUPP; + } ++ + static inline int serdev_device_break_ctl(struct serdev_device *serdev, int break_state) + { + return -EOPNOTSUPP; +@@ -294,6 +302,8 @@ static inline int serdev_device_set_rts(struct serdev_device *serdev, bool enabl + + int serdev_device_set_parity(struct serdev_device *serdev, + enum serdev_parity parity); ++int serdev_device_set_stopbits(struct serdev_device *serdev, ++ enum serdev_stopbits stopbits); + + /* + * serdev hooks into TTY core +diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h +index 1641d879dbda997604518207ffd5dd4b02c7bf04..a5d3c759a54bafc3bc8e05db1050773ae7f7238f 100644 +--- a/include/net/bluetooth/hci.h ++++ b/include/net/bluetooth/hci.h +@@ -359,6 +359,16 @@ enum { + */ + HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_PHY, + ++ /* ++ * When this quirk is set, reserved upper bits in the Event_Type field ++ * of LE Advertising Report events are discarded. This is required for ++ * some Apple/Broadcom controllers which use those bits for vendor flags. ++ * ++ * This quirk can be set before hci_register_dev is called or during the ++ * hdev->setup vendor callback. ++ */ ++ HCI_QUIRK_FIXUP_LE_ADV_REPORT_EVT_TYPE, ++ + /* When this quirk is set, the HCI_OP_READ_VOICE_SETTING command is + * skipped. This is required for a subset of the CSR controller clones + * which erroneously claim to support it. +@@ -2937,6 +2947,7 @@ struct hci_ev_le_conn_complete { + #define LE_ADV_NONCONN_IND 0x03 + #define LE_ADV_SCAN_RSP 0x04 + #define LE_ADV_INVALID 0x05 ++#define LE_ADV_EVT_TYPE_MASK 0x0f + + /* Legacy event types in extended adv report */ + #define LE_LEGACY_ADV_IND 0x0013 +diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c +index 2f5e21ff975294b300536301ab84be37b5a4918a..ad374f76ecf4338cd8716fc6fa8c084b9cdeb7e4 100644 +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -6453,6 +6453,7 @@ static void hci_le_adv_report_evt(struct hci_dev *hdev, void *data, + + while (ev->num--) { + struct hci_ev_le_advertising_info *info; ++ u8 evt_type; + s8 rssi; + + info = hci_le_ev_skb_pull(hdev, skb, +@@ -6467,9 +6468,14 @@ static void hci_le_adv_report_evt(struct hci_dev *hdev, void *data, + + hci_store_wake_reason(hdev, &info->bdaddr, info->bdaddr_type); + ++ evt_type = info->type; ++ if (hci_test_quirk(hdev, ++ HCI_QUIRK_FIXUP_LE_ADV_REPORT_EVT_TYPE)) ++ evt_type &= LE_ADV_EVT_TYPE_MASK; ++ + if (info->length <= max_adv_len(hdev)) { + rssi = info->data[info->length]; +- process_adv_report(hdev, info->type, &info->bdaddr, ++ process_adv_report(hdev, evt_type, &info->bdaddr, + info->bdaddr_type, NULL, 0, + HCI_ADV_PHY_1M, 0, rssi, + info->data, info->length, false, +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +index ffd7b37e740184feb9cc327933feb3d4540708a1..6a13318ba11d00c59811a2da3589676ba67fa8ed 100644 +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -5190,11 +5190,13 @@ static int hci_init_sync(struct hci_dev *hdev) + return 0; + } + +-#define HCI_QUIRK_BROKEN(_quirk, _desc) { HCI_QUIRK_BROKEN_##_quirk, _desc } ++#define HCI_QUIRK_BROKEN(_quirk, _desc) \ ++ { HCI_QUIRK_BROKEN_##_quirk, _desc, false } + + static const struct { + unsigned long quirk; + const char *desc; ++ bool quiet; + } hci_broken_table[] = { + HCI_QUIRK_BROKEN(LOCAL_COMMANDS, + "HCI Read Local Supported Commands not supported"), +@@ -5204,9 +5206,9 @@ static const struct { + HCI_QUIRK_BROKEN(ERR_DATA_REPORTING, + "HCI Read Default Erroneous Data Reporting command is " + "advertised, but not supported."), +- HCI_QUIRK_BROKEN(READ_TRANSMIT_POWER, +- "HCI Read Transmit Power Level command is advertised, " +- "but not supported."), ++ /* This established workaround still blocks the unsupported command. */ ++ { HCI_QUIRK_BROKEN_READ_TRANSMIT_POWER, ++ "HCI Read Transmit Power Level command is advertised, but not supported.", true }, + HCI_QUIRK_BROKEN(FILTER_CLEAR_ALL, + "HCI Set Event Filter command not supported."), + HCI_QUIRK_BROKEN(ENHANCED_SETUP_SYNC_CONN, +@@ -5250,7 +5252,11 @@ static int hci_dev_setup_sync(struct hci_dev *hdev) + ret = hdev->setup(hdev); + + for (i = 0; i < ARRAY_SIZE(hci_broken_table); i++) { +- if (hci_test_quirk(hdev, hci_broken_table[i].quirk)) ++ if (!hci_test_quirk(hdev, hci_broken_table[i].quirk)) ++ continue; ++ if (hci_broken_table[i].quiet) ++ bt_dev_dbg(hdev, "%s", hci_broken_table[i].desc); ++ else + bt_dev_warn(hdev, "%s", hci_broken_table[i].desc); + } + diff --git a/kernel/patches/j71t/0003-s8000-smartio-spi.patch b/kernel/patches/j71t/0003-s8000-smartio-spi.patch new file mode 100644 index 00000000..4f908dab --- /dev/null +++ b/kernel/patches/j71t/0003-s8000-smartio-spi.patch @@ -0,0 +1,2642 @@ +From 1ada4ff1a93d9ccf58e1a525f7f40f705c922394 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 03/12] soc: apple: add S8000 SmartIO, SIO DMA and SPI +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The SmartIO coprocessor driver (its firmware comes from the iPadOS +kernelcache at runtime), the SIO DMA engine it exposes, and the +S8000 SPI controller. + +Builds on the SmartIO and SIO DMA drivers by Paul Praschl in +HoolockLinux. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../devicetree/bindings/dma/apple,sio.yaml | 107 ++ + drivers/dma/Kconfig | 11 + + drivers/dma/Makefile | 1 + + drivers/dma/apple-sio-dma.c | 866 +++++++++++++ + drivers/soc/apple/Kconfig | 13 + + drivers/soc/apple/Makefile | 2 + + drivers/soc/apple/apple-sio-core.c | 1074 +++++++++++++++++ + drivers/spi/Kconfig | 7 + + drivers/spi/Makefile | 1 + + drivers/spi/spi-apple-s8000.c | 360 ++++++ + include/linux/soc/apple/sio.h | 71 ++ + 11 files changed, 2513 insertions(+) + create mode 100644 Documentation/devicetree/bindings/dma/apple,sio.yaml + create mode 100644 drivers/dma/apple-sio-dma.c + create mode 100644 drivers/soc/apple/apple-sio-core.c + create mode 100644 drivers/spi/spi-apple-s8000.c + create mode 100644 include/linux/soc/apple/sio.h + +diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..9595e6df2443e5ae912a83279dd5bee36d06ee8a +--- /dev/null ++++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml +@@ -0,0 +1,107 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/dma/apple,sio.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple S8000/T8010 SmartIO coprocessor and DMA controller ++ ++description: | ++ The S8000 (A9) and T8010 (A10) SmartIO coprocessor provides DMA channels ++ for peripheral controllers. The application processor boots the SmartIO firmware and ++ submits DMA commands through the SIO message queues. Clients select the ++ firmware endpoint number used by their peripheral. ++ ++maintainers: ++ - Paul Praschl ++ ++allOf: ++ - $ref: dma-controller.yaml# ++ ++properties: ++ compatible: ++ enum: ++ - apple,s8000-sio ++ - apple,t8010-sio ++ ++ reg: ++ items: ++ - description: AppleA7IOPV2 coprocessor and message queue registers ++ - description: SmartIO DMA shim registers ++ ++ reg-names: ++ items: ++ - const: a7iop ++ - const: shim ++ ++ interrupts: ++ minItems: 4 ++ maxItems: 4 ++ description: ++ The four interrupt outputs described by the T8010 SIO device tree node. ++ ++ power-domains: ++ maxItems: 1 ++ ++ resets: ++ maxItems: 1 ++ description: ++ Power-state reset, used if the coprocessor is found already running. ++ ++ apple,dma-endpoints: ++ $ref: /schemas/types.yaml#/definitions/uint32-array ++ maxItems: 130 ++ items: ++ maximum: 0x81 ++ description: ++ Firmware endpoints exposed as DMA channels. Defaults to the SPI, MCA ++ and UART transmit and receive endpoints. ++ ++ firmware-name: ++ maxItems: 1 ++ ++ dma-channels: ++ const: 64 ++ ++ '#dma-cells': ++ const: 1 ++ description: ++ Clients specify the SmartIO DMA endpoint number, for example 0x1a for ++ SPI3 transmit, 0x1b for SPI3 receive or 0x28 for MCA0 transmit. ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - interrupts ++ - power-domains ++ - firmware-name ++ - dma-channels ++ - '#dma-cells' ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ sio: dma-controller@20ae00000 { ++ compatible = "apple,t8010-sio"; ++ reg = <0x2 0x0ae00000 0x0 0x10000>, ++ <0x2 0x0a000000 0x0 0x2000>; ++ reg-names = "a7iop", "shim"; ++ interrupts = , ++ , ++ , ++ ; ++ power-domains = <&ps_sio>; ++ firmware-name = "apple/t8010-smartio.bin"; ++ dma-channels = <64>; ++ #dma-cells = <1>; ++ }; ++ }; +diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig +index ae6a682c9f769fe3de5501d440815616c54c3f9b..08abece22599634050ae6b02daaf94d2fd471771 100644 +--- a/drivers/dma/Kconfig ++++ b/drivers/dma/Kconfig +@@ -92,6 +92,17 @@ config APPLE_ADMAC + help + Enable support for Audio DMA Controller found on Apple Silicon SoCs. + ++config APPLE_SIO_DMA ++ tristate "Apple S8000/T8010 SmartIO DMA support" ++ depends on APPLE_SIO ++ select DMA_ENGINE ++ select DMA_VIRTUAL_CHANNELS ++ help ++ Enable support for the DMA service provided by the SmartIO ++ coprocessor on Apple S8000/S8003 and T8010 SoCs: one channel per ++ firmware endpoint (SPI, MCA audio, UART), slave scatter-gather and ++ cyclic transfers, submitted through the SmartIO endpoint protocol. ++ + config ARM_DMA350 + tristate "Arm DMA-350 support" + depends on ARM || ARM64 || COMPILE_TEST +diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile +index 14aa086629d5c779ed9f79d6312b47457e167458..1d4c09964d310bd9f40052c5cda4ebc035d98371 100644 +--- a/drivers/dma/Makefile ++++ b/drivers/dma/Makefile +@@ -17,6 +17,7 @@ obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o + obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o + obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ + obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o ++obj-$(CONFIG_APPLE_SIO_DMA) += apple-sio-dma.o + obj-$(CONFIG_ARM_DMA350) += arm-dma350.o + obj-$(CONFIG_AT_HDMAC) += at_hdmac.o + obj-$(CONFIG_AT_XDMAC) += at_xdmac.o +diff --git a/drivers/dma/apple-sio-dma.c b/drivers/dma/apple-sio-dma.c +new file mode 100644 +index 0000000000000000000000000000000000000000..b60f799581c7e3a1b3a2d527f6f2d42cd37b9f85 +--- /dev/null ++++ b/drivers/dma/apple-sio-dma.c +@@ -0,0 +1,866 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Apple SmartIO DMA controller (S8000/T8010) ++ * ++ * Copyright (C) 2026 Paul Praschl ++ * Multiple endpoints, cyclic transfers and stop handling: Berkay Kösebay ++ * ++ * Every DMA channel is a SmartIO firmware endpoint (0x1a SPI3 TX, 0x28 MCA0 ++ * TX, ...). The protocol, as iOS 16.7.10's AppleSmartIODMA drives it: ++ * ++ * op 5 configure data = descriptor slot holding the shim record ++ * (struct apple_sio_dma_config); ack 0x65 ++ * op 6 submit data = first slot of a transfer: 6 header slots (word 14 ++ * flags, word 15 segment count), then one {addr lo, ++ * addr hi, length} slot per segment. Ack 0x65, later an ++ * async 0x68 with parameter bit 7, the same tag and ++ * data = bytes moved. Commands complete in submit order; ++ * several may be queued per endpoint. ++ * op 8 stop ack 0x65; every queued command then completes (0x68) ++ * 0x68 without parameter bit 7: channel idle; 0x66: async error ++ * ++ * A cyclic transfer queues one command per period and resubmits a period as ++ * soon as it completes, so the firmware always has the rest of the ring ++ * queued. All firmware communication happens in work items with bounded ++ * timeouts; a dead coprocessor stalls the stream, never the caller. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "virt-dma.h" ++ ++#define SIO_DMA_OP_CONFIGURE 0x05 ++#define SIO_DMA_OP_SUBMIT 0x06 ++#define SIO_DMA_OP_STOP 0x08 ++#define SIO_DMA_OP_ACK 0x65 ++#define SIO_DMA_OP_ASYNC_ERROR 0x66 ++#define SIO_DMA_OP_COMPLETE 0x68 ++ ++#define SIO_DMA_MSG_OPCODE GENMASK_ULL(23, 16) ++#define SIO_DMA_MSG_PARAMETER GENMASK_ULL(31, 24) ++#define SIO_DMA_MSG_DATA GENMASK_ULL(63, 32) ++ ++#define SIO_DMA_SLOT_WORDS 3 ++#define SIO_DMA_HDR_SLOTS 6 ++#define SIO_DMA_HDR_FLAGS 14 /* word: kIOMemoryRemote, 0 */ ++#define SIO_DMA_HDR_SEGMENTS 15 /* word: segment count */ ++#define SIO_DMA_CFG_SLOTS 8 ++#define SIO_DMA_SEG_BOUNDARY SZ_4K /* iOS splits at pages */ ++#define SIO_DMA_MAX_SEGMENTS (0x0aaa - SIO_DMA_HDR_SLOTS) ++#define SIO_DMA_MAX_PERIODS 16 /* < 31 tags per endpoint */ ++ ++#define SIO_DMA_CONFIG_TIMEOUT_MS 1000 ++#define SIO_DMA_STOP_TIMEOUT_MS 200 ++#define SIO_DMA_DRAIN_TIMEOUT_MS 500 ++#define SIO_DMA_SUBMIT_RETRIES 3 ++ ++struct sio_dma_desc; ++ ++struct sio_dma_block { ++ struct sio_dma_desc *desc; ++ u32 first_slot; ++ unsigned int slots; ++ size_t len; ++ bool queued; /* submitted, completion pending */ ++}; ++ ++struct sio_dma_desc { ++ struct virt_dma_desc vd; ++ struct sio_dma_chan *chan; ++ bool cyclic; ++ unsigned int next; /* next block to submit */ ++ unsigned long done; /* completed blocks */ ++ unsigned int nblocks; ++ size_t total; ++ int status; ++ struct sio_dma_block blocks[] __counted_by(nblocks); ++}; ++ ++struct sio_dma_chan { ++ struct virt_dma_chan vc; ++ struct sio_dma *host; ++ u8 endpoint; ++ struct apple_sio_dma_config cfg; ++ bool cfg_dirty; ++ struct sio_dma_desc *active; ++ unsigned int inflight; ++ bool stopping; ++ struct work_struct submit_work; ++ struct work_struct stop_work; ++ struct completion drained; ++ struct completion stopped; ++}; ++ ++struct sio_dma { ++ struct device *dev; ++ struct apple_sio *sio; ++ struct dma_device dma; ++ unsigned int nchans; ++ struct sio_dma_chan chans[] __counted_by(nchans); ++}; ++ ++/* SPI defaults used by the T8010 touch path when no shim record is given */ ++static const struct apple_sio_dma_config sio_dma_default_cfg = { ++ .fifo = 0x80, .trigger = 0x40, .limit = 0x80, ++}; ++ ++/* SPI0-3, MCA0-4 and UART0-8 TX/RX, unless the SIO node lists endpoints */ ++static const u8 sio_dma_default_ranges[][2] = { ++ { 0x14, 0x1b }, { 0x28, 0x31 }, { 0x3c, 0x4d }, ++}; ++ ++static struct sio_dma_chan *to_sio_chan(struct dma_chan *c) ++{ ++ return container_of(c, struct sio_dma_chan, vc.chan); ++} ++ ++static struct sio_dma_desc *to_sio_desc(struct virt_dma_desc *vd) ++{ ++ return container_of(vd, struct sio_dma_desc, vd); ++} ++ ++/* --- descriptors ----------------------------------------------------------- */ ++ ++static unsigned int sio_dma_count_segments(dma_addr_t addr, size_t len) ++{ ++ unsigned int n = 0; ++ ++ while (len) { ++ size_t chunk = min_t(size_t, len, SIO_DMA_SEG_BOUNDARY - ++ (addr & (SIO_DMA_SEG_BOUNDARY - 1))); ++ ++ addr += chunk; ++ len -= chunk; ++ n++; ++ } ++ return n; ++} ++ ++static u32 *sio_dma_fill_segments(u32 *seg, dma_addr_t addr, size_t len) ++{ ++ while (len) { ++ size_t chunk = min_t(size_t, len, SIO_DMA_SEG_BOUNDARY - ++ (addr & (SIO_DMA_SEG_BOUNDARY - 1))); ++ ++ seg[0] = lower_32_bits(addr); ++ seg[1] = upper_32_bits(addr); ++ seg[2] = chunk; ++ seg += SIO_DMA_SLOT_WORDS; ++ addr += chunk; ++ len -= chunk; ++ } ++ return seg; ++} ++ ++/* Allocate a transfer descriptor in the shared slots; returns its segments. */ ++static u32 *sio_dma_block_alloc(struct sio_dma_chan *chan, ++ struct sio_dma_block *b, unsigned int nsegs) ++{ ++ u32 *hdr; ++ ++ if (!nsegs || nsegs > SIO_DMA_MAX_SEGMENTS) ++ return NULL; ++ ++ b->slots = SIO_DMA_HDR_SLOTS + nsegs; ++ hdr = apple_sio_desc0_alloc(chan->host->sio, b->slots, &b->first_slot); ++ if (!hdr) { ++ b->slots = 0; ++ return NULL; ++ } ++ ++ hdr[SIO_DMA_HDR_FLAGS] = 0; ++ hdr[SIO_DMA_HDR_SEGMENTS] = nsegs; ++ return hdr + SIO_DMA_HDR_SLOTS * SIO_DMA_SLOT_WORDS; ++} ++ ++static void sio_dma_desc_free(struct virt_dma_desc *vd) ++{ ++ struct sio_dma_desc *desc = to_sio_desc(vd); ++ struct apple_sio *sio = desc->chan->host->sio; ++ unsigned int i; ++ ++ for (i = 0; i < desc->nblocks; i++) ++ if (desc->blocks[i].slots) ++ apple_sio_desc0_free(sio, desc->blocks[i].first_slot, ++ desc->blocks[i].slots); ++ kfree(desc); ++} ++ ++static struct sio_dma_desc *sio_dma_desc_new(struct sio_dma_chan *chan, ++ unsigned int nblocks, bool cyclic) ++{ ++ struct sio_dma_desc *desc; ++ unsigned int i; ++ ++ desc = kzalloc_flex(*desc, blocks, nblocks, GFP_NOWAIT); ++ if (!desc) ++ return NULL; ++ ++ desc->nblocks = nblocks; ++ desc->chan = chan; ++ desc->cyclic = cyclic; ++ for (i = 0; i < nblocks; i++) ++ desc->blocks[i].desc = desc; ++ return desc; ++} ++ ++static bool sio_dma_dir_ok(enum dma_transfer_direction dir) ++{ ++ return dir == DMA_MEM_TO_DEV || dir == DMA_DEV_TO_MEM; ++} ++ ++static struct dma_async_tx_descriptor * ++sio_dma_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 sio_dma_chan *chan = to_sio_chan(c); ++ struct sio_dma_desc *desc; ++ struct scatterlist *sg; ++ unsigned int nsegs = 0, i; ++ u32 *seg; ++ ++ if (!sio_dma_dir_ok(dir) || !sg_len) ++ return NULL; ++ ++ for_each_sg(sgl, sg, sg_len, i) { ++ if (!sg_dma_len(sg)) ++ return NULL; ++ nsegs += sio_dma_count_segments(sg_dma_address(sg), ++ sg_dma_len(sg)); ++ } ++ ++ desc = sio_dma_desc_new(chan, 1, false); ++ if (!desc) ++ return NULL; ++ ++ seg = sio_dma_block_alloc(chan, &desc->blocks[0], nsegs); ++ if (!seg) { ++ kfree(desc); ++ return NULL; ++ } ++ ++ for_each_sg(sgl, sg, sg_len, i) { ++ seg = sio_dma_fill_segments(seg, sg_dma_address(sg), ++ sg_dma_len(sg)); ++ desc->total += sg_dma_len(sg); ++ } ++ desc->blocks[0].len = desc->total; ++ ++ return vchan_tx_prep(&chan->vc, &desc->vd, flags); ++} ++ ++static struct dma_async_tx_descriptor * ++sio_dma_prep_cyclic(struct dma_chan *c, dma_addr_t buf, size_t buf_len, ++ size_t period_len, enum dma_transfer_direction dir, ++ unsigned long flags) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ struct sio_dma_desc *desc; ++ unsigned int n, i; ++ ++ if (!sio_dma_dir_ok(dir) || !period_len || buf_len % period_len) ++ return NULL; ++ ++ n = buf_len / period_len; ++ if (!n || n > SIO_DMA_MAX_PERIODS) ++ return NULL; ++ ++ desc = sio_dma_desc_new(chan, n, true); ++ if (!desc) ++ return NULL; ++ ++ for (i = 0; i < n; i++) { ++ dma_addr_t addr = buf + (dma_addr_t)i * period_len; ++ struct sio_dma_block *b = &desc->blocks[i]; ++ u32 *seg; ++ ++ seg = sio_dma_block_alloc(chan, b, ++ sio_dma_count_segments(addr, period_len)); ++ if (!seg) { ++ sio_dma_desc_free(&desc->vd); ++ return NULL; ++ } ++ sio_dma_fill_segments(seg, addr, period_len); ++ b->len = period_len; ++ } ++ desc->total = buf_len; ++ ++ return vchan_tx_prep(&chan->vc, &desc->vd, flags); ++} ++ ++/* --- firmware interaction (work items) ----------------------------------- */ ++ ++static int sio_dma_configure(struct sio_dma_chan *chan) ++{ ++ struct apple_sio *sio = chan->host->sio; ++ struct apple_sio_dma_config cfg; ++ unsigned long flags; ++ u32 slot, *rec; ++ int ret; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ if (!chan->cfg_dirty) { ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ return 0; ++ } ++ cfg = chan->cfg; ++ chan->cfg_dirty = false; ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ rec = apple_sio_desc0_alloc(sio, SIO_DMA_CFG_SLOTS, &slot); ++ if (!rec) { ++ ret = -ENOMEM; ++ goto fail; ++ } ++ ++ /* AppleSmartIODMA::_configureDMA record layout */ ++ rec[0] = cfg.xfer; ++ rec[1] = cfg.timeout; ++ rec[2] = cfg.fifo; ++ rec[3] = cfg.trigger; ++ rec[4] = cfg.limit; ++ rec[5] = cfg.word5; ++ rec[6] = cfg.frame; ++ rec[7] = cfg.word7; ++ rec[SIO_DMA_HDR_FLAGS] = 0; ++ dma_wmb(); ++ ++ ret = apple_sio_request(sio, chan->endpoint, SIO_DMA_OP_CONFIGURE, 0, ++ slot, SIO_DMA_OP_ACK, ++ msecs_to_jiffies(SIO_DMA_CONFIG_TIMEOUT_MS), ++ NULL); ++ apple_sio_desc0_free(sio, slot, SIO_DMA_CFG_SLOTS); ++ if (ret) ++ goto fail; ++ ++ dev_dbg(chan->host->dev, ++ "ep %#x shim: xfer %#x timeout %u fifo %u trigger %u limit %u\n", ++ chan->endpoint, cfg.xfer, cfg.timeout, cfg.fifo, cfg.trigger, ++ cfg.limit); ++ return 0; ++ ++fail: ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ chan->cfg_dirty = true; ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ dev_err_ratelimited(chan->host->dev, "ep %#x: configure failed: %d\n", ++ chan->endpoint, ret); ++ return ret; ++} ++ ++static void sio_dma_block_done(void *data, int status, u64 response) ++{ ++ struct sio_dma_block *b = data; ++ struct sio_dma_desc *desc = b->desc; ++ struct sio_dma_chan *chan = desc->chan; ++ bool kick = false; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ b->queued = false; ++ if (!WARN_ON_ONCE(!chan->inflight)) ++ chan->inflight--; ++ ++ if (chan->active == desc && !chan->stopping) { ++ if (status) { ++ desc->status = status; ++ dev_err_ratelimited(chan->host->dev, ++ "ep %#x: transfer failed: %d\n", ++ chan->endpoint, status); ++ } ++ if (desc->cyclic) { ++ if (!status) { ++ desc->done++; ++ vchan_cyclic_callback(&desc->vd); ++ kick = true; ++ } ++ } else { ++ desc->vd.tx_result.result = status ? ++ DMA_TRANS_ABORTED : DMA_TRANS_NOERROR; ++ desc->vd.tx_result.residue = status ? desc->total : 0; ++ vchan_cookie_complete(&desc->vd); ++ chan->active = NULL; ++ kick = true; ++ } ++ } ++ ++ if (chan->stopping && !chan->inflight) ++ complete(&chan->drained); ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ if (kick) ++ queue_work(system_highpri_wq, &chan->submit_work); ++} ++ ++static void sio_dma_submit_work(struct work_struct *work) ++{ ++ struct sio_dma_chan *chan = container_of(work, struct sio_dma_chan, ++ submit_work); ++ struct apple_sio *sio = chan->host->sio; ++ unsigned int retries = 0; ++ ++ if (sio_dma_configure(chan)) ++ return; ++ ++ for (;;) { ++ struct apple_sio_async *req; ++ struct virt_dma_desc *vd; ++ struct sio_dma_desc *desc; ++ struct sio_dma_block *b; ++ unsigned long flags; ++ unsigned int idx; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ if (chan->stopping) ++ goto unlock_out; ++ if (!chan->active) { ++ vd = vchan_next_desc(&chan->vc); ++ if (!vd) ++ goto unlock_out; ++ list_del(&vd->node); ++ chan->active = to_sio_desc(vd); ++ } ++ desc = chan->active; ++ if (desc->status || (!desc->cyclic && desc->next >= desc->nblocks)) ++ goto unlock_out; ++ idx = desc->next; ++ b = &desc->blocks[idx]; ++ if (b->queued) ++ goto unlock_out; ++ b->queued = true; ++ chan->inflight++; ++ desc->next = desc->cyclic ? (idx + 1) % desc->nblocks : idx + 1; ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ dma_wmb(); ++ req = apple_sio_request_async(sio, chan->endpoint, ++ SIO_DMA_OP_SUBMIT, 0, ++ b->first_slot, ++ SIO_DMA_OP_COMPLETE, ++ sio_dma_block_done, b); ++ if (!IS_ERR(req)) { ++ /* errors after allocation arrive via the callback */ ++ apple_sio_async_put(req); ++ continue; ++ } ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ b->queued = false; ++ chan->inflight--; ++ desc->next = idx; ++ if (chan->stopping && !chan->inflight) ++ complete(&chan->drained); ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ if (++retries > SIO_DMA_SUBMIT_RETRIES) { ++ dev_err_ratelimited(chan->host->dev, ++ "ep %#x: submit failed: %ld\n", ++ chan->endpoint, PTR_ERR(req)); ++ return; ++ } ++ usleep_range(500, 1000); ++ continue; ++ ++unlock_out: ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ return; ++ } ++} ++ ++static void sio_dma_stop_work(struct work_struct *work) ++{ ++ struct sio_dma_chan *chan = container_of(work, struct sio_dma_chan, ++ stop_work); ++ struct apple_sio *sio = chan->host->sio; ++ struct sio_dma_desc *desc; ++ unsigned long flags; ++ bool busy, issued; ++ int ret; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ busy = chan->inflight; ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ if (busy) { ++ ret = apple_sio_request(sio, chan->endpoint, SIO_DMA_OP_STOP, 0, ++ 0, SIO_DMA_OP_ACK, ++ msecs_to_jiffies(SIO_DMA_STOP_TIMEOUT_MS), ++ NULL); ++ if (ret) ++ dev_warn(chan->host->dev, "ep %#x: stop failed: %d\n", ++ chan->endpoint, ret); ++ ++ if (!wait_for_completion_timeout(&chan->drained, ++ msecs_to_jiffies(SIO_DMA_DRAIN_TIMEOUT_MS))) { ++ dev_warn(chan->host->dev, ++ "ep %#x: %u commands left after stop, cancelling\n", ++ chan->endpoint, READ_ONCE(chan->inflight)); ++ apple_sio_cancel_endpoint(sio, chan->endpoint); ++ } ++ } ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ desc = chan->active; ++ chan->active = NULL; ++ if (desc) ++ vchan_terminate_vdesc(&desc->vd); ++ chan->inflight = 0; ++ chan->stopping = false; ++ issued = !list_empty(&chan->vc.desc_issued); ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ complete_all(&chan->stopped); ++ if (issued) ++ queue_work(system_highpri_wq, &chan->submit_work); ++} ++ ++/* --- dmaengine ops ----------------------------------------------------------- */ ++ ++static void sio_dma_issue_pending(struct dma_chan *c) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ unsigned long flags; ++ bool issued; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ issued = vchan_issue_pending(&chan->vc) && !chan->stopping; ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ if (issued) ++ queue_work(system_highpri_wq, &chan->submit_work); ++} ++ ++static int sio_dma_terminate_all(struct dma_chan *c) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ unsigned long flags; ++ bool stop = false; ++ LIST_HEAD(head); ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ vchan_get_all_descriptors(&chan->vc, &head); ++ if (chan->active && !chan->stopping) { ++ chan->stopping = true; ++ reinit_completion(&chan->drained); ++ reinit_completion(&chan->stopped); ++ if (!chan->inflight) ++ complete(&chan->drained); ++ stop = true; ++ } ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ vchan_dma_desc_free_list(&chan->vc, &head); ++ if (stop) ++ queue_work(system_highpri_wq, &chan->stop_work); ++ return 0; ++} ++ ++static void sio_dma_synchronize(struct dma_chan *c) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ ++ if (!wait_for_completion_timeout(&chan->stopped, ++ msecs_to_jiffies(SIO_DMA_STOP_TIMEOUT_MS + ++ SIO_DMA_DRAIN_TIMEOUT_MS + 100))) ++ dev_warn(chan->host->dev, "ep %#x: stop did not finish\n", ++ chan->endpoint); ++ vchan_synchronize(&chan->vc); ++} ++ ++static enum dma_status sio_dma_tx_status(struct dma_chan *c, ++ dma_cookie_t cookie, ++ struct dma_tx_state *state) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ struct virt_dma_desc *vd; ++ enum dma_status status; ++ unsigned long flags; ++ size_t residue = 0; ++ ++ status = dma_cookie_status(c, cookie, state); ++ if (status == DMA_COMPLETE || !state) ++ return status; ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ if (chan->active && chan->active->vd.tx.cookie == cookie) { ++ struct sio_dma_desc *desc = chan->active; ++ ++ if (desc->cyclic) ++ residue = desc->total - (desc->done % desc->nblocks) * ++ desc->blocks[0].len; ++ else ++ residue = desc->total; ++ if (desc->status) ++ status = DMA_ERROR; ++ } else { ++ vd = vchan_find_desc(&chan->vc, cookie); ++ if (vd) ++ residue = to_sio_desc(vd)->total; ++ } ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ ++ dma_set_residue(state, residue); ++ return status; ++} ++ ++/* AppleSmartIODMA::validFIFODepth */ ++static u32 sio_dma_valid_depth(const struct apple_sio_dma_config *cfg, ++ u32 req, enum dma_transfer_direction dir) ++{ ++ u32 lo; ++ ++ if (dir == DMA_DEV_TO_MEM) ++ return req ? min(req, cfg->trigger) : 1; ++ ++ lo = min(cfg->trigger, cfg->fifo - cfg->trigger) + 4; ++ return req < lo ? lo : min(req, cfg->fifo); ++} ++ ++static int sio_dma_slave_config(struct dma_chan *c, ++ struct dma_slave_config *sc) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ struct apple_sio_dma_config cfg = sio_dma_default_cfg; ++ unsigned long flags; ++ ++ if (!sio_dma_dir_ok(sc->direction)) ++ return -EINVAL; ++ ++ if (sc->peripheral_config) { ++ if (sc->peripheral_size != sizeof(cfg)) ++ return -EINVAL; ++ memcpy(&cfg, sc->peripheral_config, sizeof(cfg)); ++ if (cfg.fifo <= cfg.trigger) ++ return -EINVAL; ++ if (!cfg.limit) { ++ if (sc->direction == DMA_MEM_TO_DEV) ++ cfg.limit = sc->dst_maxburst * sc->dst_addr_width; ++ else ++ cfg.limit = sc->src_maxburst * sc->src_addr_width; ++ } ++ cfg.limit = sio_dma_valid_depth(&cfg, cfg.limit, sc->direction); ++ } ++ ++ spin_lock_irqsave(&chan->vc.lock, flags); ++ if (memcmp(&chan->cfg, &cfg, sizeof(cfg))) { ++ chan->cfg = cfg; ++ chan->cfg_dirty = true; ++ } ++ spin_unlock_irqrestore(&chan->vc.lock, flags); ++ return 0; ++} ++ ++static int sio_dma_alloc_chan_resources(struct dma_chan *c) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ ++ chan->cfg = sio_dma_default_cfg; ++ chan->cfg_dirty = true; ++ return 0; ++} ++ ++static void sio_dma_free_chan_resources(struct dma_chan *c) ++{ ++ struct sio_dma_chan *chan = to_sio_chan(c); ++ ++ sio_dma_terminate_all(c); ++ sio_dma_synchronize(c); ++ cancel_work_sync(&chan->submit_work); ++ vchan_free_chan_resources(&chan->vc); ++} ++ ++static struct dma_chan *sio_dma_of_xlate(struct of_phandle_args *spec, ++ struct of_dma *ofdma) ++{ ++ struct sio_dma *host = ofdma->of_dma_data; ++ unsigned int i; ++ ++ if (spec->args_count != 1) ++ return NULL; ++ ++ for (i = 0; i < host->nchans; i++) ++ if (host->chans[i].endpoint == spec->args[0]) ++ return dma_get_slave_channel(&host->chans[i].vc.chan); ++ ++ return NULL; ++} ++ ++/* Firmware messages on our endpoints that match no request. */ ++static void sio_dma_notify(void *data, u64 message) ++{ ++ struct sio_dma_chan *chan = data; ++ u8 op = FIELD_GET(SIO_DMA_MSG_OPCODE, message); ++ ++ switch (op) { ++ case SIO_DMA_OP_ACK: /* ack of an async submit */ ++ case SIO_DMA_OP_COMPLETE: /* idle notification (no tag) */ ++ break; ++ case SIO_DMA_OP_ASYNC_ERROR: ++ dev_err_ratelimited(chan->host->dev, ++ "ep %#x: async error %#llx\n", ++ chan->endpoint, ++ FIELD_GET(SIO_DMA_MSG_DATA, message)); ++ break; ++ default: ++ dev_dbg(chan->host->dev, "ep %#x: unexpected message %#llx\n", ++ chan->endpoint, message); ++ } ++} ++ ++static int sio_dma_endpoints(struct device *dev, u8 *eps, unsigned int max) ++{ ++ struct device_node *np = dev->parent->of_node; ++ unsigned int n = 0, i, e; ++ int count; ++ ++ count = of_property_count_u32_elems(np, "apple,dma-endpoints"); ++ if (count > 0) { ++ for (i = 0; i < count && n < max; i++) { ++ u32 ep; ++ ++ of_property_read_u32_index(np, "apple,dma-endpoints", i, &ep); ++ if (ep < APPLE_SIO_MAX_ENDPOINTS) ++ eps[n++] = ep; ++ } ++ return n; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(sio_dma_default_ranges); i++) ++ for (e = sio_dma_default_ranges[i][0]; ++ e <= sio_dma_default_ranges[i][1] && n < max; e++) ++ eps[n++] = e; ++ return n; ++} ++ ++static int sio_dma_probe(struct platform_device *pdev) ++{ ++ struct apple_sio **parent_data = dev_get_platdata(&pdev->dev); ++ u8 eps[APPLE_SIO_MAX_ENDPOINTS]; ++ struct dma_device *dma; ++ struct sio_dma *host; ++ unsigned int n, i; ++ int ret; ++ ++ if (!parent_data || !*parent_data || !pdev->dev.parent) ++ return -ENODEV; ++ ++ n = sio_dma_endpoints(&pdev->dev, eps, ARRAY_SIZE(eps)); ++ if (!n) ++ return -ENODEV; ++ ++ host = devm_kzalloc(&pdev->dev, struct_size(host, chans, n), GFP_KERNEL); ++ if (!host) ++ return -ENOMEM; ++ host->nchans = n; ++ host->dev = &pdev->dev; ++ host->sio = *parent_data; ++ platform_set_drvdata(pdev, host); ++ ++ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "failed to set DMA mask\n"); ++ ++ dma = &host->dma; ++ dma_cap_set(DMA_PRIVATE, dma->cap_mask); ++ dma_cap_set(DMA_SLAVE, dma->cap_mask); ++ dma_cap_set(DMA_CYCLIC, dma->cap_mask); ++ dma->dev = &pdev->dev; ++ dma->device_alloc_chan_resources = sio_dma_alloc_chan_resources; ++ dma->device_free_chan_resources = sio_dma_free_chan_resources; ++ dma->device_prep_slave_sg = sio_dma_prep_slave_sg; ++ dma->device_prep_dma_cyclic = sio_dma_prep_cyclic; ++ dma->device_issue_pending = sio_dma_issue_pending; ++ dma->device_tx_status = sio_dma_tx_status; ++ dma->device_terminate_all = sio_dma_terminate_all; ++ dma->device_synchronize = sio_dma_synchronize; ++ dma->device_config = sio_dma_slave_config; ++ dma->directions = BIT(DMA_MEM_TO_DEV) | BIT(DMA_DEV_TO_MEM); ++ dma->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT; ++ dma->src_addr_widths = dma->dst_addr_widths = ++ BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | ++ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_8_BYTES); ++ INIT_LIST_HEAD(&dma->channels); ++ ++ for (i = 0; i < n; i++) { ++ struct sio_dma_chan *chan = &host->chans[i]; ++ ++ chan->host = host; ++ chan->endpoint = eps[i]; ++ chan->cfg = sio_dma_default_cfg; ++ chan->cfg_dirty = true; ++ INIT_WORK(&chan->submit_work, sio_dma_submit_work); ++ INIT_WORK(&chan->stop_work, sio_dma_stop_work); ++ init_completion(&chan->drained); ++ init_completion(&chan->stopped); ++ complete_all(&chan->stopped); ++ vchan_init(&chan->vc, dma); ++ chan->vc.desc_free = sio_dma_desc_free; ++ apple_sio_set_notify(host->sio, chan->endpoint, sio_dma_notify, ++ chan); ++ } ++ ++ ret = dma_async_device_register(dma); ++ if (ret) ++ goto err_notify; ++ ++ ret = of_dma_controller_register(pdev->dev.parent->of_node, ++ sio_dma_of_xlate, host); ++ if (ret) { ++ dma_async_device_unregister(dma); ++ goto err_notify; ++ } ++ ++ dev_info(&pdev->dev, "%u SmartIO DMA endpoints\n", n); ++ return 0; ++ ++err_notify: ++ for (i = 0; i < n; i++) ++ apple_sio_set_notify(host->sio, host->chans[i].endpoint, NULL, ++ NULL); ++ return dev_err_probe(&pdev->dev, ret, "failed to register DMA device\n"); ++} ++ ++static void sio_dma_remove(struct platform_device *pdev) ++{ ++ struct sio_dma *host = platform_get_drvdata(pdev); ++ unsigned int i; ++ ++ of_dma_controller_free(pdev->dev.parent->of_node); ++ dma_async_device_unregister(&host->dma); ++ for (i = 0; i < host->nchans; i++) { ++ struct sio_dma_chan *chan = &host->chans[i]; ++ ++ sio_dma_terminate_all(&chan->vc.chan); ++ sio_dma_synchronize(&chan->vc.chan); ++ cancel_work_sync(&chan->submit_work); ++ cancel_work_sync(&chan->stop_work); ++ apple_sio_set_notify(host->sio, chan->endpoint, NULL, NULL); ++ vchan_free_chan_resources(&chan->vc); ++ } ++} ++ ++static struct platform_driver sio_dma_driver = { ++ .driver = { ++ .name = "apple-sio-dma", ++ }, ++ .probe = sio_dma_probe, ++ .remove = sio_dma_remove, ++}; ++module_platform_driver(sio_dma_driver); ++ ++MODULE_ALIAS("platform:apple-sio-dma"); ++MODULE_DESCRIPTION("Apple SmartIO DMA controller"); ++MODULE_LICENSE("Dual MIT/GPL"); +diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig +index 99fa23a57c3624849339f79419821f50e9b9e140..0587472546d756fb0cf5e0c980bf09b973b705bc 100644 +--- a/drivers/soc/apple/Kconfig ++++ b/drivers/soc/apple/Kconfig +@@ -48,6 +48,19 @@ config APPLE_PMP_T8015_V1 + + Say 'y' here if you have an Apple A11 SoC. + ++config APPLE_SIO ++ tristate "Apple S8000/T8010 SmartIO coprocessor" ++ depends on ARM64 ++ depends on PM ++ depends on ARCH_APPLE || COMPILE_TEST ++ help ++ Support the SmartIO coprocessor found on Apple T8010 SoCs. SmartIO ++ boots dedicated firmware and exchanges endpoint messages with the ++ application processor. It provides DMA services to peripheral ++ controllers such as SPI. ++ ++ Say Y here if you have an Apple S8000/S8003 or T8010 SoC. ++ + config APPLE_SART + tristate "Apple SART DMA address filter" + depends on ARCH_APPLE || COMPILE_TEST +diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile +index cad33f1cc0e1f003b00286b02df201d70b9f0c0c..465b0e3ebb5ebcaf6c54343681e112b27345e480 100644 +--- a/drivers/soc/apple/Makefile ++++ b/drivers/soc/apple/Makefile +@@ -15,3 +15,5 @@ obj-$(CONFIG_APPLE_TUNABLE) += apple-tunable.o + apple-tunable-y = tunable.o + + obj-$(CONFIG_APPLE_PMP_T8015_V1) += pmp_t8015_v1.o ++obj-$(CONFIG_APPLE_SIO) += apple-sio.o ++apple-sio-y := apple-sio-core.o +diff --git a/drivers/soc/apple/apple-sio-core.c b/drivers/soc/apple/apple-sio-core.c +new file mode 100644 +index 0000000000000000000000000000000000000000..28229fd7f9fb3b1a50e2a451575fb1a4ee556e8c +--- /dev/null ++++ b/drivers/soc/apple/apple-sio-core.c +@@ -0,0 +1,1074 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Apple S8000/T8010 SmartIO coprocessor driver ++ * ++ * Copyright (C) 2026 Paul Praschl ++ */ ++ ++#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 APPLE_SIO_REMAP_AP_LO 0x0008 ++#define APPLE_SIO_REMAP_AP_HI 0x0010 ++#define APPLE_SIO_REMAP_IOP_LO 0x0018 ++#define APPLE_SIO_REMAP_IOP_HI 0x0020 ++#define APPLE_SIO_REMAP_SIZE_LO 0x0028 ++#define APPLE_SIO_REMAP_SIZE_HI 0x0030 ++#define APPLE_SIO_REMAP_ENABLE 0x0038 ++#define APPLE_SIO_CPU_CONTROL 0x0044 ++#define APPLE_SIO_CPU_RUN BIT(4) ++#define APPLE_SIO_TIME_CONTROL 0x080c ++#define APPLE_SIO_IRQ_STATUS_LEGACY 0x0b84 ++#define APPLE_SIO_IRQ_STATUS 0x0b88 ++#define APPLE_SIO_QUEUE_INIT_B 0x0c00 ++#define APPLE_SIO_QUEUE_START_B 0x0c04 ++#define APPLE_SIO_QUEUE_INIT_A 0x4000 ++#define APPLE_SIO_QUEUE_START_A 0x4004 ++#define APPLE_SIO_TX_STATUS 0x4008 ++#define APPLE_SIO_TX_MESSAGE 0x4010 ++#define APPLE_SIO_RX_STATUS 0x4020 ++#define APPLE_SIO_RX_MESSAGE 0x4038 ++#define APPLE_SIO_TIME_LO 0xc030 ++#define APPLE_SIO_TIME_HI 0xc038 ++ ++#define APPLE_SIO_MBOX_ENABLE BIT(0) ++#define APPLE_SIO_MBOX_EMPTY BIT(17) ++#define APPLE_SIO_MBOX_ERROR GENMASK(19, 18) ++ ++#define APPLE_SIO_MSG_ENDPOINT GENMASK_ULL(7, 0) ++#define APPLE_SIO_MSG_TAG GENMASK_ULL(15, 8) ++#define APPLE_SIO_MSG_OPCODE GENMASK_ULL(23, 16) ++#define APPLE_SIO_MSG_PARAMETER GENMASK_ULL(31, 24) ++#define APPLE_SIO_MSG_DATA GENMASK_ULL(63, 32) ++ ++#define APPLE_SIO_OP_DMA_ERROR 0x66 ++#define APPLE_SIO_OP_DMA_COMPLETE 0x68 ++#define APPLE_SIO_DMA_COMPLETE_TAGGED BIT(7) ++#define APPLE_SIO_OP_ERROR 0x6a ++#define APPLE_SIO_OP_READY 0x6d ++#define APPLE_SIO_READY_PARAMETER 0x03 /* SMARTIO_PROTOCOL_VERSION */ ++#define APPLE_SIO_RX_IRQ_INDEX 2 ++#define APPLE_SIO_READY_TIMEOUT_MS 5000 ++#define APPLE_SIO_TX_TIMEOUT_US 200000 ++#define APPLE_SIO_RX_DRAIN_LIMIT 256 ++#define APPLE_SIO_REQUEST_TIMEOUT_MS 1000 ++#define APPLE_SIO_DESC0_SIZE 0x7ff8 ++#define APPLE_SIO_DESC0_COUNT 0x0aaa ++#define APPLE_SIO_DESC0_SLOT_SIZE (3 * sizeof(u32)) ++#define APPLE_SIO_DESC1_SIZE 0x10400 ++#define APPLE_SIO_DESC1_COUNT 0x1040 ++#define APPLE_SIO_SETUP_COMMANDS 10 ++ ++struct apple_sio_region { ++ void *buffer; ++ dma_addr_t dma; ++ size_t size; ++}; ++ ++struct apple_sio_async { ++ struct apple_sio *sio; ++ struct list_head shutdown_node; ++ struct completion done; ++ refcount_t refs; ++ apple_sio_async_callback_t callback; ++ void *callback_data; ++ unsigned long key; ++ u8 endpoint; ++ u8 tag; ++ u8 expected_opcode; ++ int status; ++ u64 response; ++ bool active; ++}; ++ ++struct apple_sio { ++ struct device *dev; ++ void __iomem *base; ++ void *firmware_buffer; ++ dma_addr_t firmware_dma; ++ size_t firmware_size; ++ struct completion ready; ++ struct completion setup_done; ++ /* Serialize access to the single-entry transmit queue. */ ++ struct mutex tx_lock; ++ /* Protect the final transmit status check and message write. */ ++ spinlock_t tx_hw_lock; ++ /* Per-endpoint tag bitmaps (bit n = tag n in use), see sio.h. */ ++ spinlock_t tag_lock; ++ u32 tags[APPLE_SIO_MAX_ENDPOINTS]; ++ struct { ++ apple_sio_notify_t fn; ++ void *data; ++ } notify[APPLE_SIO_MAX_ENDPOINTS]; ++ struct xarray pending; ++ struct apple_sio_region ep0_general; ++ struct apple_sio_region ep0_spi; ++ struct apple_sio_region ep3_aux; ++ struct apple_sio_region desc0; ++ struct apple_sio_region desc1; ++ unsigned long *desc0_bitmap; ++ /* Protect contiguous Descriptor-0 slot allocation. */ ++ spinlock_t desc0_lock; ++ struct platform_device *dma_pdev; ++ unsigned int setup_index; ++ unsigned int irq_count; ++ unsigned int unmatched_count; ++ u32 last_irq_legacy; ++ u32 last_irq; ++ u32 last_rx_status; ++ u32 last_tx_status; ++ u64 last_rx_message; ++ u64 last_tx_message; ++ int setup_status; ++ bool running; ++ bool stopping; ++}; ++ ++static struct apple_sio_async * ++apple_sio_request_async_common(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data, bool atomic, int fixed_tag); ++ ++static void apple_sio_submit_setup(struct apple_sio *sio); ++ ++static unsigned long apple_sio_request_key(u8 endpoint, u8 tag) ++{ ++ return ((unsigned long)endpoint << 8) | tag; ++} ++ ++/* fixed_tag < 0: lowest free tag in 1..APPLE_SIO_MAX_TAG */ ++static int apple_sio_tag_alloc(struct apple_sio *sio, u8 endpoint, ++ int fixed_tag) ++{ ++ unsigned long flags; ++ u32 *map; ++ int tag; ++ ++ if (endpoint >= APPLE_SIO_MAX_ENDPOINTS || ++ fixed_tag > APPLE_SIO_MAX_TAG || fixed_tag == 0) ++ return -EINVAL; ++ ++ map = &sio->tags[endpoint]; ++ spin_lock_irqsave(&sio->tag_lock, flags); ++ if (fixed_tag > 0) { ++ tag = (*map & BIT(fixed_tag)) ? -EBUSY : fixed_tag; ++ } else { ++ /* bit 0 is never used: tags start at 1 */ ++ tag = ffz(*map | BIT(0)); ++ if (tag > APPLE_SIO_MAX_TAG) ++ tag = -EBUSY; ++ } ++ if (tag > 0) ++ *map |= BIT(tag); ++ spin_unlock_irqrestore(&sio->tag_lock, flags); ++ ++ return tag; ++} ++ ++static void apple_sio_tag_free(struct apple_sio *sio, u8 endpoint, u8 tag) ++{ ++ unsigned long flags; ++ ++ if (endpoint >= APPLE_SIO_MAX_ENDPOINTS || tag > APPLE_SIO_MAX_TAG) ++ return; ++ ++ spin_lock_irqsave(&sio->tag_lock, flags); ++ sio->tags[endpoint] &= ~BIT(tag); ++ spin_unlock_irqrestore(&sio->tag_lock, flags); ++} ++ ++void apple_sio_set_notify(struct apple_sio *sio, u8 endpoint, ++ apple_sio_notify_t notify, void *data) ++{ ++ unsigned long flags; ++ ++ if (endpoint >= APPLE_SIO_MAX_ENDPOINTS) ++ return; ++ ++ /* the RX handler reads these under the pending lock */ ++ xa_lock_irqsave(&sio->pending, flags); ++ sio->notify[endpoint].fn = notify; ++ sio->notify[endpoint].data = data; ++ xa_unlock_irqrestore(&sio->pending, flags); ++} ++EXPORT_SYMBOL_GPL(apple_sio_set_notify); ++ ++void *apple_sio_desc0_alloc(struct apple_sio *sio, unsigned int slots, ++ u32 *first_slot) ++{ ++ unsigned long flags; ++ unsigned long slot; ++ void *buffer; ++ ++ if (!first_slot || !slots || slots > APPLE_SIO_DESC0_COUNT) ++ return NULL; ++ ++ spin_lock_irqsave(&sio->desc0_lock, flags); ++ slot = bitmap_find_next_zero_area(sio->desc0_bitmap, ++ APPLE_SIO_DESC0_COUNT, 0, ++ slots, 0); ++ if (slot < APPLE_SIO_DESC0_COUNT) ++ bitmap_set(sio->desc0_bitmap, slot, slots); ++ spin_unlock_irqrestore(&sio->desc0_lock, flags); ++ ++ if (slot >= APPLE_SIO_DESC0_COUNT) ++ return NULL; ++ ++ buffer = sio->desc0.buffer + slot * APPLE_SIO_DESC0_SLOT_SIZE; ++ memset(buffer, 0, slots * APPLE_SIO_DESC0_SLOT_SIZE); ++ *first_slot = slot; ++ return buffer; ++} ++EXPORT_SYMBOL_GPL(apple_sio_desc0_alloc); ++ ++void apple_sio_desc0_free(struct apple_sio *sio, u32 first_slot, ++ unsigned int slots) ++{ ++ unsigned long flags; ++ ++ if (!slots || first_slot >= APPLE_SIO_DESC0_COUNT || ++ slots > APPLE_SIO_DESC0_COUNT - first_slot) ++ return; ++ ++ spin_lock_irqsave(&sio->desc0_lock, flags); ++ bitmap_clear(sio->desc0_bitmap, first_slot, slots); ++ spin_unlock_irqrestore(&sio->desc0_lock, flags); ++} ++EXPORT_SYMBOL_GPL(apple_sio_desc0_free); ++ ++static bool apple_sio_can_read(u32 status) ++{ ++ return (status & APPLE_SIO_MBOX_ENABLE) && ++ !(status & (APPLE_SIO_MBOX_ERROR | APPLE_SIO_MBOX_EMPTY)); ++} ++ ++static bool apple_sio_can_write(u32 status) ++{ ++ return (status & APPLE_SIO_MBOX_ENABLE) && ++ !(status & APPLE_SIO_MBOX_ERROR) && ++ (status & APPLE_SIO_MBOX_EMPTY); ++} ++ ++static bool apple_sio_is_ready(u64 message) ++{ ++ return FIELD_GET(APPLE_SIO_MSG_ENDPOINT, message) == 0 && ++ FIELD_GET(APPLE_SIO_MSG_TAG, message) == 0 && ++ FIELD_GET(APPLE_SIO_MSG_OPCODE, message) == APPLE_SIO_OP_READY && ++ FIELD_GET(APPLE_SIO_MSG_PARAMETER, message) == ++ APPLE_SIO_READY_PARAMETER; ++} ++ ++static void apple_sio_dispatch(struct apple_sio *sio, u64 message) ++{ ++ struct apple_sio_async *request; ++ unsigned long flags; ++ bool matched = false; ++ u8 endpoint = FIELD_GET(APPLE_SIO_MSG_ENDPOINT, message); ++ u8 tag = FIELD_GET(APPLE_SIO_MSG_TAG, message); ++ u8 opcode = FIELD_GET(APPLE_SIO_MSG_OPCODE, message); ++ unsigned long key = apple_sio_request_key(endpoint, tag); ++ ++ if (apple_sio_is_ready(message)) { ++ if (!READ_ONCE(sio->running) && !READ_ONCE(sio->stopping)) { ++ WRITE_ONCE(sio->running, true); ++ apple_sio_submit_setup(sio); ++ } ++ complete_all(&sio->ready); ++ return; ++ } ++ ++ apple_sio_notify_t notify = NULL; ++ void *notify_data = NULL; ++ u8 parameter = FIELD_GET(APPLE_SIO_MSG_PARAMETER, message); ++ /* ++ * A DMA "complete" (0x68) only refers to a command when parameter bit ++ * 7 is set (AppleSmartIODMA::_asyncAction); without it, it is the ++ * channel-idle notification and its tag field means nothing. ++ */ ++ bool tagged = !(opcode == APPLE_SIO_OP_DMA_COMPLETE && ++ !(parameter & APPLE_SIO_DMA_COMPLETE_TAGGED)); ++ ++ xa_lock_irqsave(&sio->pending, flags); ++ request = tagged ? xa_load(&sio->pending, key) : NULL; ++ if (request && (opcode == request->expected_opcode || ++ opcode == APPLE_SIO_OP_ERROR || ++ opcode == APPLE_SIO_OP_DMA_ERROR)) { ++ __xa_erase(&sio->pending, key); ++ request->active = false; ++ request->response = message; ++ request->status = opcode == request->expected_opcode ? 0 : -EIO; ++ matched = true; ++ } else if (endpoint < APPLE_SIO_MAX_ENDPOINTS) { ++ notify = sio->notify[endpoint].fn; ++ notify_data = sio->notify[endpoint].data; ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ ++ if (!matched) { ++ /* acks of async submits, DMA idle notifications, ... */ ++ sio->unmatched_count++; ++ if (notify) ++ notify(notify_data, message); ++ return; ++ } ++ ++ apple_sio_tag_free(sio, request->endpoint, request->tag); ++ if (request->callback) ++ request->callback(request->callback_data, request->status, ++ request->response); ++ complete_all(&request->done); ++ apple_sio_async_put(request); ++} ++ ++static irqreturn_t apple_sio_irq(int irq, void *data) ++{ ++ struct apple_sio *sio = data; ++ unsigned int count = 0; ++ u32 status; ++ ++ sio->irq_count++; ++ sio->last_irq_legacy = ++ readl_relaxed(sio->base + APPLE_SIO_IRQ_STATUS_LEGACY); ++ sio->last_irq = readl_relaxed(sio->base + APPLE_SIO_IRQ_STATUS); ++ while (count < APPLE_SIO_RX_DRAIN_LIMIT) { ++ status = readl_relaxed(sio->base + APPLE_SIO_RX_STATUS); ++ sio->last_rx_status = status; ++ if (!apple_sio_can_read(status)) ++ break; ++ ++ sio->last_rx_message = ++ readq_relaxed(sio->base + APPLE_SIO_RX_MESSAGE); ++ apple_sio_dispatch(sio, sio->last_rx_message); ++ count++; ++ } ++ ++ if (count == APPLE_SIO_RX_DRAIN_LIMIT) ++ dev_warn_ratelimited(sio->dev, "RX queue drain limit reached\n"); ++ ++ return IRQ_HANDLED; ++} ++ ++static int apple_sio_send(struct apple_sio *sio, u64 message) ++{ ++ unsigned long flags; ++ u32 status; ++ int ret; ++ ++ mutex_lock(&sio->tx_lock); ++ if (READ_ONCE(sio->stopping)) { ++ ret = -ESHUTDOWN; ++ goto out_unlock; ++ } ++ ++ ret = readl_poll_timeout(sio->base + APPLE_SIO_TX_STATUS, status, ++ apple_sio_can_write(status), 10, ++ APPLE_SIO_TX_TIMEOUT_US); ++ if (ret) ++ goto out_unlock; ++ ++ spin_lock_irqsave(&sio->tx_hw_lock, flags); ++ if (sio->stopping) { ++ ret = -ESHUTDOWN; ++ goto out_hw_unlock; ++ } ++ ++ status = readl_relaxed(sio->base + APPLE_SIO_TX_STATUS); ++ if (!apple_sio_can_write(status)) { ++ ret = -EBUSY; ++ goto out_hw_unlock; ++ } ++ ++ dma_wmb(); ++ sio->last_tx_message = message; ++ writeq_relaxed(message, sio->base + APPLE_SIO_TX_MESSAGE); ++ ++out_hw_unlock: ++ spin_unlock_irqrestore(&sio->tx_hw_lock, flags); ++out_unlock: ++ mutex_unlock(&sio->tx_lock); ++ return ret; ++} ++ ++static int apple_sio_send_atomic(struct apple_sio *sio, u64 message) ++{ ++ unsigned long flags; ++ u32 status; ++ int ret = 0; ++ ++ spin_lock_irqsave(&sio->tx_hw_lock, flags); ++ if (sio->stopping) { ++ ret = -ESHUTDOWN; ++ goto out_unlock; ++ } ++ ++ status = readl_relaxed(sio->base + APPLE_SIO_TX_STATUS); ++ sio->last_tx_status = status; ++ if (!apple_sio_can_write(status)) { ++ ret = -EBUSY; ++ goto out_unlock; ++ } ++ ++ dma_wmb(); ++ sio->last_tx_message = message; ++ writeq_relaxed(message, sio->base + APPLE_SIO_TX_MESSAGE); ++ ++out_unlock: ++ spin_unlock_irqrestore(&sio->tx_hw_lock, flags); ++ return ret; ++} ++ ++void apple_sio_async_put(struct apple_sio_async *request) ++{ ++ if (refcount_dec_and_test(&request->refs)) ++ kfree(request); ++} ++EXPORT_SYMBOL_GPL(apple_sio_async_put); ++ ++static void apple_sio_async_finish(struct apple_sio_async *request, ++ int status, u64 response, bool notify) ++{ ++ struct apple_sio *sio = request->sio; ++ ++ apple_sio_tag_free(sio, request->endpoint, request->tag); ++ request->status = status; ++ request->response = response; ++ if (notify && request->callback) ++ request->callback(request->callback_data, status, response); ++ complete_all(&request->done); ++ apple_sio_async_put(request); ++} ++ ++static struct apple_sio_async * ++apple_sio_request_async_common(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data, bool atomic, int fixed_tag) ++{ ++ struct apple_sio_async *request; ++ gfp_t gfp = atomic ? GFP_ATOMIC : GFP_KERNEL; ++ unsigned long flags; ++ u64 message; ++ bool finish = false; ++ int tag; ++ int ret; ++ ++ if (!READ_ONCE(sio->running) || READ_ONCE(sio->stopping)) ++ return ERR_PTR(-ESHUTDOWN); ++ ++ request = kzalloc_obj(*request, gfp); ++ if (!request) ++ return ERR_PTR(-ENOMEM); ++ ++ tag = apple_sio_tag_alloc(sio, endpoint, fixed_tag); ++ if (tag < 0) { ++ kfree(request); ++ return ERR_PTR(tag); ++ } ++ ++ request->sio = sio; ++ request->endpoint = endpoint; ++ INIT_LIST_HEAD(&request->shutdown_node); ++ init_completion(&request->done); ++ refcount_set(&request->refs, 2); ++ request->callback = callback; ++ request->callback_data = callback_data; ++ request->key = apple_sio_request_key(endpoint, tag); ++ request->tag = tag; ++ request->expected_opcode = expected_opcode; ++ request->status = -EINPROGRESS; ++ request->active = true; ++ ++ ret = xa_insert(&sio->pending, request->key, request, gfp); ++ if (ret) { ++ apple_sio_tag_free(sio, endpoint, tag); ++ apple_sio_async_put(request); ++ apple_sio_async_put(request); ++ return ERR_PTR(ret); ++ } ++ ++ xa_lock_irqsave(&sio->pending, flags); ++ if (sio->stopping && ++ xa_load(&sio->pending, request->key) == request) { ++ __xa_erase(&sio->pending, request->key); ++ request->active = false; ++ finish = true; ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ if (finish) { ++ apple_sio_async_finish(request, -ESHUTDOWN, 0, true); ++ return request; ++ } ++ ++ message = FIELD_PREP(APPLE_SIO_MSG_ENDPOINT, endpoint) | ++ FIELD_PREP(APPLE_SIO_MSG_TAG, tag) | ++ FIELD_PREP(APPLE_SIO_MSG_OPCODE, opcode) | ++ FIELD_PREP(APPLE_SIO_MSG_PARAMETER, parameter) | ++ FIELD_PREP(APPLE_SIO_MSG_DATA, data); ++ ++ ret = atomic ? apple_sio_send_atomic(sio, message) : ++ apple_sio_send(sio, message); ++ if (ret) { ++ xa_lock_irqsave(&sio->pending, flags); ++ if (xa_load(&sio->pending, request->key) == request) { ++ __xa_erase(&sio->pending, request->key); ++ request->active = false; ++ finish = true; ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ if (finish) ++ apple_sio_async_finish(request, ret, 0, true); ++ } ++ ++ return request; ++} ++ ++struct apple_sio_async * ++apple_sio_request_async(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data) ++{ ++ return apple_sio_request_async_common(sio, endpoint, opcode, parameter, ++ data, expected_opcode, callback, ++ callback_data, false, -1); ++} ++EXPORT_SYMBOL_GPL(apple_sio_request_async); ++ ++struct apple_sio_async * ++apple_sio_request_async_atomic(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data) ++{ ++ return apple_sio_request_async_common(sio, endpoint, opcode, parameter, ++ data, expected_opcode, callback, ++ callback_data, true, -1); ++} ++EXPORT_SYMBOL_GPL(apple_sio_request_async_atomic); ++ ++static void apple_sio_setup_done(void *data, int status, u64 response) ++{ ++ struct apple_sio *sio = data; ++ ++ (void)response; ++ if (status) { ++ sio->setup_status = status; ++ complete_all(&sio->setup_done); ++ return; ++ } ++ ++ sio->setup_index++; ++ if (sio->setup_index == APPLE_SIO_SETUP_COMMANDS) { ++ sio->setup_status = 0; ++ complete_all(&sio->setup_done); ++ return; ++ } ++ ++ apple_sio_submit_setup(sio); ++} ++ ++static void apple_sio_submit_setup(struct apple_sio *sio) ++{ ++ static const u8 endpoints[APPLE_SIO_SETUP_COMMANDS] = { ++ 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, ++ }; ++ static const u8 tags[APPLE_SIO_SETUP_COMMANDS] = { ++ 1, 2, 3, 4, 1, 2, 5, 6, 7, 8, ++ }; ++ static const u8 parameters[APPLE_SIO_SETUP_COMMANDS] = { ++ 0x0f, 0x10, 0x1a, 0x1b, 0x0d, ++ 0x0e, 0x01, 0x02, 0x0b, 0x0c, ++ }; ++ struct apple_sio_async *request; ++ u32 index = sio->setup_index; ++ u32 value; ++ ++ switch (index) { ++ case 0: ++ value = lower_32_bits(sio->ep0_general.dma >> 12); ++ break; ++ case 1: ++ value = SZ_16K; ++ break; ++ case 2: ++ value = lower_32_bits(sio->ep0_spi.dma >> 12); ++ break; ++ case 3: ++ value = SZ_8K; ++ break; ++ case 4: ++ value = lower_32_bits(sio->ep3_aux.dma >> 12); ++ break; ++ case 5: ++ value = SZ_16K; ++ break; ++ case 6: ++ value = lower_32_bits(sio->desc0.dma >> 12); ++ break; ++ case 7: ++ value = APPLE_SIO_DESC0_COUNT; ++ break; ++ case 8: ++ value = lower_32_bits(sio->desc1.dma >> 12); ++ break; ++ case 9: ++ value = APPLE_SIO_DESC1_COUNT; ++ break; ++ default: ++ sio->setup_status = -EINVAL; ++ complete_all(&sio->setup_done); ++ return; ++ } ++ ++ request = apple_sio_request_async_common(sio, endpoints[index], 0x03, ++ parameters[index], value, 0x65, ++ apple_sio_setup_done, sio, true, ++ tags[index]); ++ if (IS_ERR(request)) { ++ sio->setup_status = PTR_ERR(request); ++ complete_all(&sio->setup_done); ++ return; ++ } ++ ++ apple_sio_async_put(request); ++} ++ ++int apple_sio_async_cancel(struct apple_sio_async *request) ++{ ++ struct apple_sio *sio = request->sio; ++ unsigned long flags; ++ bool cancelled = false; ++ ++ xa_lock_irqsave(&sio->pending, flags); ++ if (request->active && ++ xa_load(&sio->pending, request->key) == request) { ++ __xa_erase(&sio->pending, request->key); ++ request->active = false; ++ cancelled = true; ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ ++ if (cancelled) { ++ apple_sio_async_finish(request, -ECANCELED, 0, false); ++ return 0; ++ } ++ ++ wait_for_completion(&request->done); ++ return -EALREADY; ++} ++EXPORT_SYMBOL_GPL(apple_sio_async_cancel); ++ ++void apple_sio_cancel_endpoint(struct apple_sio *sio, u8 endpoint) ++{ ++ struct apple_sio_async *request; ++ LIST_HEAD(cancelled); ++ unsigned long index; ++ unsigned long flags; ++ ++ xa_lock_irqsave(&sio->pending, flags); ++ xa_for_each(&sio->pending, index, request) { ++ if (request->endpoint != endpoint) ++ continue; ++ __xa_erase(&sio->pending, index); ++ request->active = false; ++ list_add_tail(&request->shutdown_node, &cancelled); ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ ++ while (!list_empty(&cancelled)) { ++ request = list_first_entry(&cancelled, struct apple_sio_async, ++ shutdown_node); ++ list_del_init(&request->shutdown_node); ++ apple_sio_async_finish(request, -ECANCELED, 0, true); ++ } ++} ++EXPORT_SYMBOL_GPL(apple_sio_cancel_endpoint); ++ ++int apple_sio_request(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ unsigned long timeout, u64 *response) ++{ ++ struct apple_sio_async *request; ++ bool timed_out = false; ++ int ret; ++ ++ request = apple_sio_request_async(sio, endpoint, opcode, parameter, ++ data, expected_opcode, NULL, NULL); ++ if (IS_ERR(request)) ++ return PTR_ERR(request); ++ ++ if (!wait_for_completion_timeout(&request->done, timeout)) { ++ if (!apple_sio_async_cancel(request)) ++ timed_out = true; ++ } ++ ++ ret = timed_out ? -ETIMEDOUT : request->status; ++ if (!ret && response) ++ *response = request->response; ++ ++ apple_sio_async_put(request); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_sio_request); ++ ++static void apple_sio_fail_pending(struct apple_sio *sio) ++{ ++ struct apple_sio_async *request; ++ LIST_HEAD(shutdown); ++ unsigned long index; ++ unsigned long flags; ++ ++ xa_lock_irqsave(&sio->pending, flags); ++ sio->stopping = true; ++ xa_for_each(&sio->pending, index, request) { ++ __xa_erase(&sio->pending, index); ++ request->active = false; ++ list_add_tail(&request->shutdown_node, &shutdown); ++ } ++ xa_unlock_irqrestore(&sio->pending, flags); ++ ++ while (!list_empty(&shutdown)) { ++ request = list_first_entry(&shutdown, struct apple_sio_async, ++ shutdown_node); ++ list_del_init(&request->shutdown_node); ++ apple_sio_async_finish(request, -ESHUTDOWN, 0, true); ++ } ++} ++ ++static void apple_sio_stop(struct apple_sio *sio) ++{ ++ u32 control; ++ ++ WRITE_ONCE(sio->running, false); ++ WRITE_ONCE(sio->stopping, true); ++ ++ mutex_lock(&sio->tx_lock); ++ spin_lock_irq(&sio->tx_hw_lock); ++ control = readl(sio->base + APPLE_SIO_CPU_CONTROL); ++ writel(control & ~APPLE_SIO_CPU_RUN, ++ sio->base + APPLE_SIO_CPU_CONTROL); ++ readl(sio->base + APPLE_SIO_CPU_CONTROL); ++ writel(0, sio->base + APPLE_SIO_REMAP_ENABLE); ++ spin_unlock_irq(&sio->tx_hw_lock); ++ mutex_unlock(&sio->tx_lock); ++ ++ apple_sio_fail_pending(sio); ++} ++ ++static int apple_sio_start(struct apple_sio *sio, const char *firmware_name) ++{ ++ const struct firmware *firmware; ++ dma_addr_t dma; ++ void *buffer; ++ size_t size; ++ u64 counter; ++ unsigned long ready_timeout; ++ u32 control; ++ u32 status; ++ int ret; ++ ++ control = readl(sio->base + APPLE_SIO_CPU_CONTROL); ++ if (control & APPLE_SIO_CPU_RUN) { ++ /* Left running by the bootloader chain: reset via the PMGR. */ ++ ret = device_reset_optional(sio->dev); ++ control = readl(sio->base + APPLE_SIO_CPU_CONTROL); ++ if (ret || (control & APPLE_SIO_CPU_RUN)) ++ return dev_err_probe(sio->dev, -EBUSY, ++ "SmartIO CPU is already running (reset %d)\n", ++ ret); ++ } ++ ++ /* No user-helper fallback: a missing file must not stall boot. */ ++ ret = request_firmware_direct(&firmware, firmware_name, sio->dev); ++ if (ret) ++ return dev_err_probe(sio->dev, ret, ++ "failed to load firmware %s\n", ++ firmware_name); ++ ++ /* iOS maps the image page-truncated; round up so all of it is mapped */ ++ size = ALIGN(firmware->size, SZ_4K); ++ buffer = dma_alloc_coherent(sio->dev, size, &dma, GFP_KERNEL); ++ if (!buffer) { ++ ret = -ENOMEM; ++ goto out_release_firmware; ++ } ++ ++ memset(buffer, 0, size); ++ memcpy(buffer, firmware->data, firmware->size); ++ dma_wmb(); ++ ++ sio->firmware_buffer = buffer; ++ sio->firmware_dma = dma; ++ sio->firmware_size = size; ++ ++ writel(0, sio->base + APPLE_SIO_REMAP_IOP_LO); ++ writel(0, sio->base + APPLE_SIO_REMAP_IOP_HI); ++ writel(lower_32_bits(size), sio->base + APPLE_SIO_REMAP_SIZE_LO); ++ writel(upper_32_bits(size), sio->base + APPLE_SIO_REMAP_SIZE_HI); ++ writel(lower_32_bits(dma), sio->base + APPLE_SIO_REMAP_AP_LO); ++ writel(upper_32_bits(dma), sio->base + APPLE_SIO_REMAP_AP_HI); ++ writel(1, sio->base + APPLE_SIO_REMAP_ENABLE); ++ ++ writel(0x1111, sio->base + APPLE_SIO_QUEUE_INIT_A); ++ writel(0x1111, sio->base + APPLE_SIO_QUEUE_INIT_B); ++ writel(1, sio->base + APPLE_SIO_QUEUE_START_A); ++ writel(1, sio->base + APPLE_SIO_QUEUE_START_B); ++ writel(0x1000, sio->base + APPLE_SIO_QUEUE_START_A); ++ writel(0x1000, sio->base + APPLE_SIO_QUEUE_START_B); ++ status = readl(sio->base + APPLE_SIO_RX_STATUS); ++ writel(status | APPLE_SIO_MBOX_ENABLE, ++ sio->base + APPLE_SIO_RX_STATUS); ++ ++ control = readl(sio->base + APPLE_SIO_TIME_CONTROL); ++ writel(control & ~BIT(1), sio->base + APPLE_SIO_TIME_CONTROL); ++ counter = arch_timer_read_counter(); ++ writel(lower_32_bits(counter), sio->base + APPLE_SIO_TIME_LO); ++ writel(upper_32_bits(counter), sio->base + APPLE_SIO_TIME_HI); ++ writel(control | BIT(1), sio->base + APPLE_SIO_TIME_CONTROL); ++ ++ control = readl(sio->base + APPLE_SIO_CPU_CONTROL); ++ writel(control | APPLE_SIO_CPU_RUN, ++ sio->base + APPLE_SIO_CPU_CONTROL); ++ ++ ready_timeout = msecs_to_jiffies(APPLE_SIO_READY_TIMEOUT_MS); ++ if (!wait_for_completion_timeout(&sio->ready, ready_timeout)) { ++ ret = -ETIMEDOUT; ++ goto out_stop; ++ } ++ ++ WRITE_ONCE(sio->running, true); ++ release_firmware(firmware); ++ dev_info(sio->dev, "SmartIO firmware ready (%zu bytes)\n", size); ++ return 0; ++ ++out_stop: ++ apple_sio_stop(sio); ++ dma_free_coherent(sio->dev, size, buffer, dma); ++ sio->firmware_buffer = NULL; ++ dev_err(sio->dev, "SmartIO firmware did not become ready\n"); ++out_release_firmware: ++ release_firmware(firmware); ++ return ret; ++} ++ ++static int apple_sio_alloc_region(struct apple_sio *sio, ++ struct apple_sio_region *region, size_t size) ++{ ++ region->buffer = dmam_alloc_coherent(sio->dev, size, ®ion->dma, ++ GFP_KERNEL); ++ if (!region->buffer) ++ return -ENOMEM; ++ ++ region->size = size; ++ memset(region->buffer, 0, size); ++ return 0; ++} ++ ++static int apple_sio_alloc_shared_regions(struct apple_sio *sio) ++{ ++ int ret; ++ ++ ret = apple_sio_alloc_region(sio, &sio->ep0_general, SZ_16K); ++ if (ret) ++ return ret; ++ ++ ret = apple_sio_alloc_region(sio, &sio->ep0_spi, SZ_8K); ++ if (ret) ++ return ret; ++ ++ ret = apple_sio_alloc_region(sio, &sio->ep3_aux, SZ_16K); ++ if (ret) ++ return ret; ++ ++ ret = apple_sio_alloc_region(sio, &sio->desc0, ++ APPLE_SIO_DESC0_SIZE); ++ if (ret) ++ return ret; ++ ++ return apple_sio_alloc_region(sio, &sio->desc1, ++ APPLE_SIO_DESC1_SIZE); ++} ++ ++static int apple_sio_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const char *firmware_name; ++ unsigned long setup_timeout; ++ struct apple_sio *sio; ++ int irq; ++ int ret; ++ ++ sio = devm_kzalloc(dev, sizeof(*sio), GFP_KERNEL); ++ if (!sio) ++ return -ENOMEM; ++ ++ sio->dev = dev; ++ init_completion(&sio->ready); ++ init_completion(&sio->setup_done); ++ sio->setup_status = -EINPROGRESS; ++ mutex_init(&sio->tx_lock); ++ spin_lock_init(&sio->tx_hw_lock); ++ spin_lock_init(&sio->tag_lock); ++ xa_init(&sio->pending); ++ spin_lock_init(&sio->desc0_lock); ++ platform_set_drvdata(pdev, sio); ++ ++ ret = devm_pm_runtime_enable(dev); ++ if (ret) ++ goto out_destroy_allocators; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) { ++ dev_err_probe(dev, ret, "failed to activate power domain\n"); ++ goto out_destroy_allocators; ++ } ++ ++ sio->base = devm_platform_ioremap_resource_byname(pdev, "a7iop"); ++ if (IS_ERR(sio->base)) { ++ ret = PTR_ERR(sio->base); ++ goto out_pm_put; ++ } ++ ++ irq = platform_get_irq(pdev, APPLE_SIO_RX_IRQ_INDEX); ++ if (irq < 0) { ++ ret = irq; ++ goto out_pm_put; ++ } ++ ++ ret = devm_request_irq(dev, irq, apple_sio_irq, 0, ++ dev_name(dev), sio); ++ if (ret) { ++ ret = dev_err_probe(dev, ret, "failed to request RX interrupt\n"); ++ goto out_pm_put; ++ } ++ ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); ++ if (ret) { ++ ret = dev_err_probe(dev, ret, "failed to set DMA mask\n"); ++ goto out_pm_put; ++ } ++ ++ sio->desc0_bitmap = devm_bitmap_zalloc(dev, APPLE_SIO_DESC0_COUNT, ++ GFP_KERNEL); ++ if (!sio->desc0_bitmap) { ++ ret = -ENOMEM; ++ goto out_pm_put; ++ } ++ ++ ret = apple_sio_alloc_shared_regions(sio); ++ if (ret) ++ goto out_pm_put; ++ ++ ret = of_property_read_string(dev->of_node, "firmware-name", ++ &firmware_name); ++ if (ret) { ++ ret = dev_err_probe(dev, ret, "missing firmware-name\n"); ++ goto out_pm_put; ++ } ++ ++ ret = apple_sio_start(sio, firmware_name); ++ if (ret) ++ goto out_pm_put; ++ ++ setup_timeout = msecs_to_jiffies(APPLE_SIO_REQUEST_TIMEOUT_MS); ++ if (!wait_for_completion_timeout(&sio->setup_done, setup_timeout)) { ++ ret = -ETIMEDOUT; ++ dev_err(dev, ++ "SmartIO setup timed out: index=%u irqs=%u unmatched=%u irq=%#x/%#x rx_status=%#x tx_status=%#x rx=%#llx tx=%#llx\n", ++ sio->setup_index, sio->irq_count, sio->unmatched_count, ++ sio->last_irq_legacy, sio->last_irq, ++ sio->last_rx_status, sio->last_tx_status, ++ sio->last_rx_message, sio->last_tx_message); ++ goto out_stop; ++ } ++ ret = sio->setup_status; ++ if (ret) { ++ dev_err_probe(dev, ret, "SmartIO shared-region setup failed\n"); ++ goto out_stop; ++ } ++ ++ dev_info(dev, "SmartIO shared regions configured\n"); ++ ++ sio->dma_pdev = platform_device_register_data(dev, "apple-sio-dma", ++ PLATFORM_DEVID_AUTO, ++ &sio, sizeof(sio)); ++ if (IS_ERR(sio->dma_pdev)) { ++ ret = dev_err_probe(dev, PTR_ERR(sio->dma_pdev), ++ "failed to register DMA child\n"); ++ sio->dma_pdev = NULL; ++ goto out_stop; ++ } ++ ++ return 0; ++ ++out_stop: ++ apple_sio_stop(sio); ++ dma_free_coherent(dev, sio->firmware_size, sio->firmware_buffer, ++ sio->firmware_dma); ++ sio->firmware_buffer = NULL; ++ ++out_pm_put: ++ pm_runtime_put_sync(dev); ++out_destroy_allocators: ++ xa_destroy(&sio->pending); ++ return ret; ++} ++ ++static void apple_sio_remove(struct platform_device *pdev) ++{ ++ struct apple_sio *sio = platform_get_drvdata(pdev); ++ ++ platform_device_unregister(sio->dma_pdev); ++ apple_sio_stop(sio); ++ dma_free_coherent(sio->dev, sio->firmware_size, ++ sio->firmware_buffer, sio->firmware_dma); ++ pm_runtime_put_sync(sio->dev); ++ xa_destroy(&sio->pending); ++} ++ ++static const struct of_device_id apple_sio_of_match[] = { ++ { .compatible = "apple,s8000-sio" }, ++ { .compatible = "apple,t8010-sio" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, apple_sio_of_match); ++ ++static struct platform_driver apple_sio_driver = { ++ .driver = { ++ .name = "apple-sio", ++ .of_match_table = apple_sio_of_match, ++ /* firmware boot waits up to ~6 s: never hold up other probes */ ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++ .probe = apple_sio_probe, ++ .remove = apple_sio_remove, ++}; ++module_platform_driver(apple_sio_driver); ++ ++MODULE_FIRMWARE("apple/s8000-smartio.bin"); ++MODULE_FIRMWARE("apple/t8010-smartio.bin"); ++MODULE_DESCRIPTION("Apple S8000/T8010 SmartIO coprocessor driver"); ++MODULE_LICENSE("Dual MIT/GPL"); +diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig +index a0f37e2df71658f54e2076f13d77dc494557e290..fc3bc0c0de2ac7c89afc25f23f348e65da40269c 100644 +--- a/drivers/spi/Kconfig ++++ b/drivers/spi/Kconfig +@@ -118,6 +118,13 @@ config SPI_AMLOGIC_SPISG + communication controller), which is available on platforms such as + Amlogic A4 SoCs. + ++config SPI_APPLE_S8000 ++ tristate "Apple S8000 Samsung SPI v1 PIO controller" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF && COMMON_CLK ++ help ++ Bounded byte-wide PIO support for the legacy SPI controller on A9. ++ + config SPI_APPLE + tristate "Apple SoC SPI Controller platform driver" + depends on ARCH_APPLE || COMPILE_TEST +diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile +index a18814fbde93f416bb1ca7877443669e61dbb7ad..515b424bc2963d35d2411974b5cce87c73654712 100644 +--- a/drivers/spi/Makefile ++++ b/drivers/spi/Makefile +@@ -181,3 +181,4 @@ obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o + # SPI offload triggers + obj-$(CONFIG_SPI_OFFLOAD_TRIGGER_PWM) += spi-offload-trigger-pwm.o + obj-$(CONFIG_SPI_OFFLOAD_TRIGGER_ADI_UTIL_SD) += spi-offload-trigger-adi-util-sigma-delta.o ++obj-$(CONFIG_SPI_APPLE_S8000) += spi-apple-s8000.o +diff --git a/drivers/spi/spi-apple-s8000.c b/drivers/spi/spi-apple-s8000.c +new file mode 100644 +index 0000000000000000000000000000000000000000..7bfa9be80c30adcb78e396d6e300dc9dfee4de19 +--- /dev/null ++++ b/drivers/spi/spi-apple-s8000.c +@@ -0,0 +1,360 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* Apple S8000 Samsung SPI v1 controller, bounded byte-wide PIO. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define S8000_CTRL 0x00 ++#define S8000_CTRL_ENABLE 0x0d ++#define S8000_CFG 0x04 ++#define S8000_CFG_BASE 0x4018 ++#define S8000_CFG_CPHA BIT(1) ++#define S8000_CFG_CPOL BIT(2) ++#define S8000_CFG_PIO BIT(5) ++#define S8000_STATUS 0x08 ++#define S8000_STATUS_CLEAR 0x40000f ++#define S8000_TX_LEVEL GENMASK(10, 6) ++#define S8000_RX_LEVEL GENMASK(15, 11) ++#define S8000_PIN 0x0c ++#define S8000_PIN_CS BIT(1) ++#define S8000_TXDATA 0x10 ++#define S8000_RXDATA 0x20 ++#define S8000_CLKDIV 0x30 ++#define S8000_RXCNT 0x34 ++#define S8000_WORD_DELAY 0x38 ++#define S8000_TXCNT 0x4c ++#define S8000_FIFO_DEPTH 16 ++#define S8000_DIV_MAX 2047 ++#define S8000_XFER_TIMEOUT_US 1000000 ++ ++static bool autosuspend; ++module_param(autosuspend, bool, 0444); ++MODULE_PARM_DESC(autosuspend, "Experimental PIO domain autosuspend (default: off)"); ++ ++struct s8000_spi { ++ void __iomem *base; ++ struct clk *clk; ++ bool held; ++ unsigned int traced_xfers; ++}; ++ ++/* Only documented version-1 registers, with RUN, DMA/IRQ modes and counts off. ++ * Retain the existing CTRL_ENABLE sequence for subsequent FIFO transfers. ++ * Do not touch SmartIO: this driver never submits DMA descriptors. ++ */ ++static int s8000_spi_idle(struct s8000_spi *spi, struct device *dev) ++{ ++ u32 ctrl, cfg, status; ++ ++ dev_info(dev, "SPI idle begin\n"); ++ ctrl = readl(spi->base + S8000_CTRL); ++ cfg = readl(spi->base + S8000_CFG); ++ status = readl(spi->base + S8000_STATUS); ++ dev_info(dev, "SPI inherited ctrl=%#x cfg=%#x status=%#x\n", ++ ctrl, cfg, status); ++ writel(0, spi->base + S8000_CTRL); ++ writel(S8000_CFG_BASE, spi->base + S8000_CFG); ++ writel(0, spi->base + S8000_RXCNT); ++ writel(0, spi->base + S8000_TXCNT); ++ writel(S8000_PIN_CS, spi->base + S8000_PIN); ++ writel(S8000_STATUS_CLEAR, spi->base + S8000_STATUS); ++ /* Complete posted writes before publishing children or leaving Linux. */ ++ ctrl = readl(spi->base + S8000_CTRL); ++ if (ctrl) { ++ dev_err(dev, "SPI failed to stop: ctrl=%#x\n", ctrl); ++ return -EIO; ++ } ++ dev_info(dev, "SPI idle complete\n"); ++ return 0; ++} ++ ++static void s8000_spi_pm_put(void *data) ++{ ++ struct device *dev = data; ++ struct spi_controller *ctlr = dev_get_drvdata(dev); ++ struct s8000_spi *spi = spi_controller_get_devdata(ctlr); ++ ++ pm_runtime_dont_use_autosuspend(dev); ++ if (spi->held) ++ pm_runtime_put_sync(dev); ++ else ++ pm_runtime_barrier(dev); ++} ++ ++static void s8000_spi_set_cs(struct spi_device *device, bool high) ++{ ++ struct s8000_spi *spi = spi_controller_get_devdata(device->controller); ++ ++ /* Descriptor-backed chip selects belong to the SPI core. */ ++ if (!spi_get_csgpiod(device, 0)) ++ writel(high ? S8000_PIN_CS : 0, spi->base + S8000_PIN); ++} ++ ++static size_t s8000_spi_max_transfer_size(struct spi_device *device) ++{ ++ /* Bound both FIFO work and worst-case wall time for malformed clients. */ ++ return SZ_64K; ++} ++ ++static int s8000_spi_transfer_one(struct spi_controller *ctlr, ++ struct spi_device *device, ++ struct spi_transfer *transfer) ++{ ++ struct s8000_spi *spi = spi_controller_get_devdata(ctlr); ++ const u8 *tx = transfer->tx_buf; ++ u8 *rx = transfer->rx_buf; ++ unsigned long rate = clk_get_rate(spi->clk); ++ unsigned int sent = 0, received = 0, iterations = 0; ++ u32 speed = transfer->speed_hz ?: device->max_speed_hz; ++ u32 divisor, cfg, status, tx_level, rx_level; ++ u64 word_delay_cycles; ++ ktime_t deadline; ++ int word_delay_ns, ret = 0; ++ bool traced = spi->traced_xfers < 8; ++ ++ if (!transfer->len) ++ return 0; ++ if (transfer->bits_per_word != 8 || !speed || !rate || ++ transfer->len > SZ_64K) ++ return -EINVAL; ++ divisor = DIV_ROUND_UP(rate, speed); ++ if (!divisor || divisor > S8000_DIV_MAX) ++ return -EINVAL; ++ transfer->effective_speed_hz = rate / divisor; ++ /* The SPI core merges the device minimum into transfer->word_delay. */ ++ word_delay_ns = spi_delay_to_ns(&transfer->word_delay, transfer); ++ if (word_delay_ns < 0) ++ return word_delay_ns; ++ /* WORD_DELAY counts input-clock cycles, not divided SCK cycles. */ ++ word_delay_cycles = DIV_ROUND_UP_ULL((u64)rate * word_delay_ns, ++ NSEC_PER_SEC); ++ if (word_delay_cycles > U32_MAX) ++ return -EINVAL; ++ cfg = S8000_CFG_BASE; ++ if (device->mode & SPI_CPHA) ++ cfg |= S8000_CFG_CPHA; ++ if (device->mode & SPI_CPOL) ++ cfg |= S8000_CFG_CPOL; ++ ++ if (traced) { ++ spi->traced_xfers++; ++ dev_info(&device->dev, "PIO begin %u len=%u speed=%u\n", ++ spi->traced_xfers, transfer->len, transfer->effective_speed_hz); ++ } ++ /* spi-version 1 has FIFO counts in STATUS, unlike the M1 controller. */ ++ writel(0, spi->base + S8000_CTRL); ++ writel(divisor, spi->base + S8000_CLKDIV); ++ writel(word_delay_cycles, spi->base + S8000_WORD_DELAY); ++ writel(S8000_STATUS_CLEAR, spi->base + S8000_STATUS); ++ writel(cfg, spi->base + S8000_CFG); ++ writel(S8000_CTRL_ENABLE, spi->base + S8000_CTRL); ++ writel(transfer->len, spi->base + S8000_RXCNT); ++ writel(transfer->len, spi->base + S8000_TXCNT); ++ writel(cfg | S8000_CFG_PIO, spi->base + S8000_CFG); ++ deadline = ktime_add_us(ktime_get(), S8000_XFER_TIMEOUT_US); ++ ++ while (received < transfer->len) { ++ status = readl(spi->base + S8000_STATUS); ++ tx_level = FIELD_GET(S8000_TX_LEVEL, status); ++ rx_level = FIELD_GET(S8000_RX_LEVEL, status); ++ if (tx_level > S8000_FIFO_DEPTH || rx_level > S8000_FIFO_DEPTH || ++ rx_level > transfer->len - received || ++ rx_level > sent - received) { ++ dev_err(&device->dev, ++ "invalid PIO FIFO: tx=%u rx=%u status=%#x\n", ++ sent, received, status); ++ ret = -EIO; ++ break; ++ } ++ writel(status, spi->base + S8000_STATUS); ++ while (rx_level--) { ++ u8 value = readl(spi->base + S8000_RXDATA); ++ ++ if (rx) ++ rx[received] = value; ++ received++; ++ } ++ /* Never queue more than one RX FIFO's worth of unconsumed data. */ ++ while (sent < transfer->len && tx_level < S8000_FIFO_DEPTH && ++ sent - received < S8000_FIFO_DEPTH) { ++ writel(tx ? tx[sent] : 0xff, spi->base + S8000_TXDATA); ++ sent++; ++ tx_level++; ++ } ++ if (ktime_compare(ktime_get(), deadline) >= 0) { ++ dev_err(&device->dev, ++ "PIO timeout: tx %u/%u rx %u/%u status=%#x\n", ++ sent, transfer->len, received, transfer->len, status); ++ ret = -ETIMEDOUT; ++ break; ++ } ++ if (!(++iterations & 0xff)) ++ cond_resched(); ++ cpu_relax(); ++ } ++ writel(cfg, spi->base + S8000_CFG); ++ writel(0, spi->base + S8000_CTRL); ++ writel(S8000_STATUS_CLEAR, spi->base + S8000_STATUS); ++ readl(spi->base + S8000_CTRL); ++ if (traced) { ++ dev_info(&device->dev, "PIO end %u ret=%d tx=%u rx=%u\n", ++ spi->traced_xfers, ret, sent, received); ++ } ++ return ret; ++} ++ ++static int s8000_spi_runtime_suspend(struct device *dev) ++{ ++ struct s8000_spi *spi = spi_controller_get_devdata(dev_get_drvdata(dev)); ++ ++ /* SPI core holds a PM reference across the entire message, including CS. */ ++ writel(0, spi->base + S8000_CTRL); ++ readl(spi->base + S8000_CTRL); ++ return 0; ++} ++ ++static int s8000_spi_runtime_resume(struct device *dev) ++{ ++ struct s8000_spi *spi = spi_controller_get_devdata(dev_get_drvdata(dev)); ++ ++ if (!autosuspend) ++ return 0; ++ /* genpd has powered the block before this callback. No IRQs or DMA. */ ++ writel(0, spi->base + S8000_CTRL); ++ writel(S8000_PIN_CS, spi->base + S8000_PIN); ++ writel(S8000_CFG_BASE, spi->base + S8000_CFG); ++ writel(S8000_STATUS_CLEAR, spi->base + S8000_STATUS); ++ /* CLKDIV, WORD_DELAY, CFG and counts are programmed per transfer. */ ++ return 0; ++} ++ ++static const struct dev_pm_ops s8000_spi_pm_ops = { ++ SET_RUNTIME_PM_OPS(s8000_spi_runtime_suspend, s8000_spi_runtime_resume, NULL) ++}; ++ ++static int s8000_spi_probe(struct platform_device *pdev) ++{ ++ struct spi_controller *ctlr; ++ struct s8000_spi *spi; ++ unsigned long rate; ++ int ret; ++ ++ ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*spi)); ++ if (!ctlr) ++ return -ENOMEM; ++ spi = spi_controller_get_devdata(ctlr); ++ spi->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(spi->base)) ++ return PTR_ERR(spi->base); ++ spi->clk = devm_clk_get_enabled(&pdev->dev, NULL); ++ if (IS_ERR(spi->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "missing SPI input clock\n"); ++ rate = clk_get_rate(spi->clk); ++ if (!rate) ++ return -EINVAL; ++ platform_set_drvdata(pdev, ctlr); ++ if (autosuspend) { ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 1000); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ } ++ ret = devm_pm_runtime_enable(&pdev->dev); ++ if (ret) ++ return ret; ++ ret = pm_runtime_resume_and_get(&pdev->dev); ++ if (ret < 0) ++ return ret; ++ spi->held = true; ++ /* Keep the default bring-up reference unless explicitly opted in. */ ++ ret = devm_add_action_or_reset(&pdev->dev, s8000_spi_pm_put, &pdev->dev); ++ if (ret) ++ return ret; ++ ++ ctlr->bus_num = -1; ++ ctlr->num_chipselect = 1; ++ ctlr->mode_bits = SPI_CPHA | SPI_CPOL; ++ ctlr->bits_per_word_mask = SPI_BPW_MASK(8); ++ ctlr->min_speed_hz = DIV_ROUND_UP(rate, S8000_DIV_MAX); ++ ctlr->max_speed_hz = rate; ++ ctlr->set_cs = s8000_spi_set_cs; ++ ctlr->transfer_one = s8000_spi_transfer_one; ++ ctlr->max_transfer_size = s8000_spi_max_transfer_size; ++ ctlr->use_gpio_descriptors = true; ++ ctlr->auto_runtime_pm = autosuspend; ++ ret = s8000_spi_idle(spi, &pdev->dev); ++ if (ret) ++ return ret; ++ /* No controller IRQ enable bits are set: every PIO transfer is bounded. */ ++ dev_info(&pdev->dev, "Samsung SPI v1 PIO, input clock %lu Hz\n", rate); ++ dev_info(&pdev->dev, "SPI register/children begin\n"); ++ ret = devm_spi_register_controller(&pdev->dev, ctlr); ++ dev_info(&pdev->dev, "SPI register/children end ret=%d\n", ret); ++ if (ret) ++ return ret; ++ if (autosuspend) { ++ spi->held = false; ++ pm_runtime_mark_last_busy(&pdev->dev); ++ pm_runtime_put_autosuspend(&pdev->dev); ++ dev_info(&pdev->dev, "experimental 1000ms domain autosuspend enabled\n"); ++ } ++ return 0; ++} ++ ++static void s8000_spi_shutdown(struct platform_device *pdev) ++{ ++ struct spi_controller *ctlr = platform_get_drvdata(pdev); ++ struct s8000_spi *spi = spi_controller_get_devdata(ctlr); ++ int ret; ++ unsigned int cs; ++ ++ /* device_shutdown() has already stopped the children (N1 masks/frees ++ * its IRQ before asserting reset). Drain before touching controller MMIO. ++ */ ++ dev_info(&pdev->dev, "SPI shutdown queue begin\n"); ++ ret = spi_controller_suspend(ctlr); ++ if (!ret) ++ ret = pm_runtime_resume_and_get(&pdev->dev); ++ if (ret >= 0) { ++ /* Core-owned GPIO selects may have been retained by cs_change. */ ++ if (ctlr->cs_gpiods) ++ for (cs = 0; cs < ctlr->num_chipselect; cs++) ++ gpiod_set_value_cansleep(ctlr->cs_gpiods[cs], 0); ++ ret = s8000_spi_idle(spi, &pdev->dev); ++ } ++ if (ret < 0) { ++ dev_err(&pdev->dev, "SPI shutdown failed: %d\n", ret); ++ if (kexec_in_progress) ++ panic("S8000 SPI shutdown failed before kexec: %d", ret); ++ return; ++ } ++ /* Pin runtime power until the jump; no transfers may restart here. */ ++ dev_info(&pdev->dev, "SPI shutdown complete\n"); ++} ++ ++static const struct of_device_id s8000_spi_match[] = { ++ { .compatible = "apple,s8000-spi" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, s8000_spi_match); ++ ++static struct platform_driver s8000_spi_driver = { ++ .probe = s8000_spi_probe, ++ .shutdown = s8000_spi_shutdown, ++ .driver = { ++ .name = "apple-s8000-spi", ++ .of_match_table = s8000_spi_match, ++ .pm = &s8000_spi_pm_ops, ++ }, ++}; ++module_platform_driver(s8000_spi_driver); ++MODULE_DESCRIPTION("Apple S8000 Samsung SPI v1 PIO controller"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/soc/apple/sio.h b/include/linux/soc/apple/sio.h +new file mode 100644 +index 0000000000000000000000000000000000000000..6a0510644a8c07676948dde429feff80ebbb65f4 +--- /dev/null ++++ b/include/linux/soc/apple/sio.h +@@ -0,0 +1,71 @@ ++/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ ++#ifndef _LINUX_SOC_APPLE_SIO_H_ ++#define _LINUX_SOC_APPLE_SIO_H_ ++ ++#include ++ ++struct apple_sio; ++struct apple_sio_async; ++ ++/* Endpoint numbers are 8 bits; the firmware names 0x00..0x81. */ ++#define APPLE_SIO_MAX_ENDPOINTS 0x82 ++ ++/* ++ * The firmware keeps 32 response slots per endpoint and indexes them by ++ * tag - 1 (AppleSmartIODMAController::responseForEndpointAndTag), so tags ++ * are allocated per endpoint in 1..APPLE_SIO_MAX_TAG. ++ */ ++#define APPLE_SIO_MAX_TAG 31 ++ ++/* ++ * Shim configuration of a DMA endpoint (op 5). These are the words of an ++ * Apple ADT "dma-channels" record after the endpoint number, as sent by ++ * AppleSmartIODMA::_configureDMA. DMA clients pass it through ++ * dma_slave_config.peripheral_config; @limit 0 means "derive from the ++ * slave config's burst". ++ */ ++struct apple_sio_dma_config { ++ u32 xfer; /* transfer control, e.g. 0x21 16-bit / 0x22 32-bit (MCA) */ ++ u32 timeout; ++ u32 fifo; /* shim FIFO, bytes */ ++ u32 trigger; /* shim trigger level, bytes */ ++ u32 limit; /* client FIFO depth, bytes (setFIFODepth) */ ++ u32 word5; /* ADT word 5 */ ++ u32 frame; /* setFrameSize, 0 unless the client sets it */ ++ u32 word7; /* ADT word 6 */ ++}; ++ ++typedef void (*apple_sio_async_callback_t)(void *data, int status, ++ u64 response); ++typedef void (*apple_sio_notify_t)(void *data, u64 message); ++ ++void *apple_sio_desc0_alloc(struct apple_sio *sio, unsigned int slots, ++ u32 *first_slot); ++void apple_sio_desc0_free(struct apple_sio *sio, u32 first_slot, ++ unsigned int slots); ++ ++struct apple_sio_async * ++apple_sio_request_async(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data); ++struct apple_sio_async * ++apple_sio_request_async_atomic(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ apple_sio_async_callback_t callback, ++ void *callback_data); ++int apple_sio_async_cancel(struct apple_sio_async *request); ++void apple_sio_async_put(struct apple_sio_async *request); ++ ++int apple_sio_request(struct apple_sio *sio, u8 endpoint, u8 opcode, ++ u8 parameter, u32 data, u8 expected_opcode, ++ unsigned long timeout, u64 *response); ++ ++/* Fail every outstanding request on @endpoint with -ECANCELED. */ ++void apple_sio_cancel_endpoint(struct apple_sio *sio, u8 endpoint); ++ ++/* Messages on @endpoint that match no pending request (IRQ context). */ ++void apple_sio_set_notify(struct apple_sio *sio, u8 endpoint, ++ apple_sio_notify_t notify, void *data); ++ ++#endif diff --git a/kernel/patches/j71t/0004-j71t-n1-touch.patch b/kernel/patches/j71t/0004-j71t-n1-touch.patch new file mode 100644 index 00000000..eec5d72e --- /dev/null +++ b/kernel/patches/j71t/0004-j71t-n1-touch.patch @@ -0,0 +1,1515 @@ +From 8ba40b6e37defa8321438227d381fba7a475e4cd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 04/12] Input: add the J71t N1 touchscreen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +N1 multitouch over SPI, with its clock and regulator. The N1 image +comes from the IPSW and the per-unit calibration from the device +tree, both as firmware files. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + drivers/clk/Kconfig | 8 + + drivers/clk/Makefile | 1 + + drivers/clk/clk-apple-s8000-touch.c | 132 +++ + drivers/input/touchscreen/Kconfig | 9 + + drivers/input/touchscreen/Makefile | 2 + + drivers/input/touchscreen/apple_n1.c | 1188 +++++++++++++++++++++++++ + drivers/regulator/Kconfig | 7 + + drivers/regulator/Makefile | 1 + + drivers/regulator/apple-d2365-touch.c | 60 ++ + 9 files changed, 1408 insertions(+) + create mode 100644 drivers/clk/clk-apple-s8000-touch.c + create mode 100644 drivers/input/touchscreen/apple_n1.c + create mode 100644 drivers/regulator/apple-d2365-touch.c + +diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig +index f9592fd9ec2bb625084e7bca3fa1e63006caea96..8461813b90a366cead96109ae6c6d41af335f28a 100644 +--- a/drivers/clk/Kconfig ++++ b/drivers/clk/Kconfig +@@ -58,6 +58,14 @@ config LMK04832 + Say yes here to build support for Texas Instruments' LMK04832 Ultra + Low-Noise JESD204B Compliant Clock Jitter Cleaner With Dual Loop PLLs + ++config COMMON_CLK_APPLE_S8000_TOUCH ++ tristate "Apple S8000 touchscreen clock" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF ++ select MFD_SYSCON ++ help ++ S8000 PMGR TCLK gate and divider for the J71t N1 touchscreen. ++ + config COMMON_CLK_APPLE_NCO + tristate "Clock driver for Apple SoC NCOs" + depends on ARCH_APPLE || COMPILE_TEST +diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile +index b18af485d7f031e0eb002686bcd2000c502ed525..f9b9457f1d0592e627a0e4a19a75fef7c15a0ca1 100644 +--- a/drivers/clk/Makefile ++++ b/drivers/clk/Makefile +@@ -183,3 +183,4 @@ endif + obj-y += xilinx/ + obj-$(CONFIG_ARCH_ZYNQ) += zynq/ + obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/ ++obj-$(CONFIG_COMMON_CLK_APPLE_S8000_TOUCH) += clk-apple-s8000-touch.o +diff --git a/drivers/clk/clk-apple-s8000-touch.c b/drivers/clk/clk-apple-s8000-touch.c +new file mode 100644 +index 0000000000000000000000000000000000000000..c05353e3ae0e8b561619bb0cacee82ad66706c2b +--- /dev/null ++++ b/drivers/clk/clk-apple-s8000-touch.c +@@ -0,0 +1,132 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* Apple S8000 TCLK function, reconstructed from j71t iOS 16.7.10. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TOUCH_DISABLE BIT(31) ++#define TOUCH_ENABLE BIT(19) ++#define TOUCH_BUSY BIT(18) ++#define TOUCH_DIV GENMASK(9, 0) ++#define TOUCH_OFFSET 0x78000 ++#define TOUCH_DIVISOR (24000000 / 32768) ++ ++struct s8000_touch_clock { ++ struct clk_hw hw; ++ struct regmap *regmap; ++ struct device *dev; ++}; ++ ++#define to_touch_clock(hw) container_of(hw, struct s8000_touch_clock, hw) ++ ++static int s8000_touch_clock_prepare(struct clk_hw *hw) ++{ ++ struct s8000_touch_clock *clock = to_touch_clock(hw); ++ u32 value; ++ int ret; ++ ++ ret = regmap_update_bits(clock->regmap, TOUCH_OFFSET, TOUCH_DISABLE, 0); ++ if (ret) ++ return ret; ++ udelay(8); ++ ret = regmap_update_bits(clock->regmap, TOUCH_OFFSET, ++ TOUCH_DISABLE | TOUCH_ENABLE | TOUCH_DIV, ++ TOUCH_ENABLE | TOUCH_DIVISOR); ++ if (!ret) ++ ret = regmap_read_poll_timeout(clock->regmap, TOUCH_OFFSET, value, ++ !(value & TOUCH_BUSY), 10, 20000); ++ if (ret) { ++ regmap_update_bits(clock->regmap, TOUCH_OFFSET, ++ TOUCH_ENABLE | TOUCH_DISABLE, TOUCH_DISABLE); ++ dev_err(clock->dev, "touch clock enable failed: %d\n", ret); ++ } ++ return ret; ++} ++ ++static void s8000_touch_clock_unprepare(struct clk_hw *hw) ++{ ++ struct s8000_touch_clock *clock = to_touch_clock(hw); ++ u32 value; ++ int ret; ++ ++ ret = regmap_update_bits(clock->regmap, TOUCH_OFFSET, TOUCH_ENABLE, 0); ++ if (!ret) ++ ret = regmap_read_poll_timeout(clock->regmap, TOUCH_OFFSET, value, ++ !(value & TOUCH_BUSY), 10, 20000); ++ if (ret) ++ dev_err(clock->dev, "touch clock disable failed: %d\n", ret); ++ regmap_update_bits(clock->regmap, TOUCH_OFFSET, TOUCH_DISABLE, TOUCH_DISABLE); ++ udelay(100); ++} ++ ++static unsigned long s8000_touch_clock_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct s8000_touch_clock *clock = to_touch_clock(hw); ++ u32 value; ++ ++ if (regmap_read(clock->regmap, TOUCH_OFFSET, &value)) ++ return 0; ++ value = FIELD_GET(TOUCH_DIV, value); ++ return value ? parent_rate / value : 0; ++} ++ ++static const struct clk_ops s8000_touch_clock_ops = { ++ .prepare = s8000_touch_clock_prepare, ++ .unprepare = s8000_touch_clock_unprepare, ++ .recalc_rate = s8000_touch_clock_recalc_rate, ++}; ++ ++static int s8000_touch_clock_probe(struct platform_device *pdev) ++{ ++ struct s8000_touch_clock *clock; ++ struct clk_init_data init = {}; ++ const char *parent; ++ u32 offset; ++ int ret; ++ ++ clock = devm_kzalloc(&pdev->dev, sizeof(*clock), GFP_KERNEL); ++ if (!clock) ++ return -ENOMEM; ++ clock->dev = &pdev->dev; ++ clock->regmap = syscon_node_to_regmap(pdev->dev.of_node->parent); ++ if (IS_ERR(clock->regmap)) ++ return PTR_ERR(clock->regmap); ++ ret = of_property_read_u32_index(pdev->dev.of_node, "reg", 0, &offset); ++ if (ret || offset != TOUCH_OFFSET) ++ return -EINVAL; ++ parent = of_clk_get_parent_name(pdev->dev.of_node, 0); ++ if (!parent) ++ return -EINVAL; ++ init.name = "s8000-touch"; ++ init.ops = &s8000_touch_clock_ops; ++ init.flags = CLK_GET_RATE_NOCACHE; ++ init.parent_names = &parent; ++ init.num_parents = 1; ++ clock->hw.init = &init; ++ ret = devm_clk_hw_register(&pdev->dev, &clock->hw); ++ if (ret) ++ return ret; ++ return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, &clock->hw); ++} ++ ++static const struct of_device_id s8000_touch_clock_match[] = { ++ { .compatible = "apple,s8000-touch-clock" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, s8000_touch_clock_match); ++static struct platform_driver s8000_touch_clock_driver = { ++ .probe = s8000_touch_clock_probe, ++ .driver = { ++ .name = "apple-s8000-touch-clock", ++ .of_match_table = s8000_touch_clock_match, ++ }, ++}; ++module_platform_driver(s8000_touch_clock_driver); ++MODULE_DESCRIPTION("Apple S8000 touchscreen clock"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig +index 9b9ae8ac3f7fd3b9e948083fcd05afabdcd69bca..9095f827b33eab627bc57f2984a83f9e62853818 100644 +--- a/drivers/input/touchscreen/Kconfig ++++ b/drivers/input/touchscreen/Kconfig +@@ -103,6 +103,15 @@ config TOUCHSCREEN_ADC + To compile this driver as a module, choose M here: the + module will be called resistive-adc-touch.ko. + ++config TOUCHSCREEN_APPLE_N1 ++ tristate "Apple J71t N1 SPI touchscreen" ++ depends on SPI && OF && GPIOLIB && COMMON_CLK ++ depends on REGULATOR ++ select INPUT_MT ++ help ++ N1 (multi-touch,j72/C1F15,1) touchscreen on Apple J71t. ++ Requires this unit's firmware and calibration, and legacy A9 SPI. ++ + config TOUCHSCREEN_APPLE_Z2 + tristate "Apple Z2 touchscreens" + depends on SPI && (ARCH_APPLE || COMPILE_TEST) +diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile +index bfd9de83389d8833dbe7cc2641cb24493cdf3807..ff2f1cdf12795a09952eb441dbc733d492e83e46 100644 +--- a/drivers/input/touchscreen/Makefile ++++ b/drivers/input/touchscreen/Makefile +@@ -119,3 +119,5 @@ 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_N1) += apple_n1.o +diff --git a/drivers/input/touchscreen/apple_n1.c b/drivers/input/touchscreen/apple_n1.c +new file mode 100644 +index 0000000000000000000000000000000000000000..b491b766c0e4667df079cee1839765e508b94524 +--- /dev/null ++++ b/drivers/input/touchscreen/apple_n1.c +@@ -0,0 +1,1188 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Apple N1 touchscreen (J71t, C1F15,1). ++ * Protocol reconstructed from this unit's iOS 16.7.10 software. See the ++ * omarchy-ipad5 docs/touch/n1-protocol.md for addresses and evidence boundaries. ++ * The HBPP boot protocol and byte-stream runtime protocol are distinct. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define N1_XFER_MAX 65536 ++#define N1_FRAME_MAX 4096 ++#define N1_REPORT_MAX 512 ++#define N1_CAL_SIZE 1024 ++#define N1_SLOTS 32 ++#define N1_RETRIES 5 ++ ++/* Staged hardware tests: 1=HBPP/identity, 2=boot/descriptors, 3=evdev. */ ++static unsigned int boot_stage = 3; ++module_param(boot_stage, uint, 0644); ++MODULE_PARM_DESC(boot_stage, "1=identity, 2=firmware/descriptors, 3=touchscreen (on next probe)"); ++static bool trace; ++module_param(trace, bool, 0644); ++MODULE_PARM_DESC(trace, "Log up to 64 boot/control exchanges and eight runtime headers per probe"); ++ ++struct apple_n1 { ++ struct spi_device *spi; ++ struct gpio_desc *reset; ++ struct gpio_desc *ldo; ++ struct gpio_desc *atn; ++ struct regulator *vdd; ++ struct clk *clk; ++ struct pinctrl *pins; ++ struct pinctrl_state *sync_on; ++ struct pinctrl_state *sync_off; ++ struct input_dev *input; ++ struct touchscreen_properties props; ++ u8 *tx; ++ u8 *rx; ++ u16 max_packet; ++ s16 x_min, x_max, y_min, y_max; ++ u8 sequence; ++ unsigned int traced_frames; ++ unsigned int traced_controls; ++ size_t control_length; ++ unsigned long last_wake; ++ bool wake_valid; ++ bool powered; ++ bool irq_requested; ++ /* Serialize IRQ, recovery and PM; one disable-depth count at most. */ ++ struct mutex lock; ++ struct delayed_work recovery_work; ++ unsigned int irq_errors; ++ unsigned int recovery_attempts; ++ bool irq_masked; ++ bool suspended; ++}; ++ ++static u32 n1_sum(const u8 *data, size_t len) ++{ ++ u32 sum = 0; ++ ++ while (len--) ++ sum += *data++; ++ return sum; ++} ++ ++/* Apple bootloader's 32-bit encoding: high byte of low halfword first. */ ++static void n1_put32(u8 *p, u32 value) ++{ ++ p[0] = value >> 8; ++ p[1] = value; ++ p[2] = value >> 24; ++ p[3] = value >> 16; ++} ++ ++static u32 n1_get32(const u8 *p) ++{ ++ return (u32)p[2] << 24 | (u32)p[3] << 16 | (u32)p[0] << 8 | p[1]; ++} ++ ++/* All callers use heap-backed shared buffers; never SPI from the stack. ++ * The A9 controller's PIO implementation has a deadline per transfer. ++ */ ++static int n1_xfer(struct apple_n1 *n1, size_t len) ++{ ++ struct spi_transfer transfer = { ++ .tx_buf = n1->tx, ++ .rx_buf = n1->rx, ++ .len = len, ++ .bits_per_word = 8, ++ }; ++ ++ if (!len || len > N1_XFER_MAX) ++ return -EMSGSIZE; ++ memset(n1->rx, 0, len); ++ return spi_sync_transfer(n1->spi, &transfer, 1); ++} ++ ++/* Never pass firmware or calibration payloads to these diagnostic helpers. */ ++static void n1_dump_error(struct apple_n1 *n1, const char *phase, ++ const u8 *packet, size_t len) ++{ ++ size_t offset; ++ ++ for (offset = 0; offset < len; offset += 32) ++ dev_err(&n1->spi->dev, "%s rx[%zu/%zu]: %*ph\n", phase, ++ offset, len, (int)min_t(size_t, 32, len - offset), packet + offset); ++} ++ ++static int n1_control_xfer(struct apple_n1 *n1, const char *phase, size_t len) ++{ ++ bool log = READ_ONCE(trace) && n1->traced_controls < 64; ++ int ret; ++ ++ n1->control_length = len; ++ if (log) { ++ n1->traced_controls++; ++ dev_info(&n1->spi->dev, "trace %s tx[%zu]: %*ph\n", phase, ++ len, (int)min_t(size_t, len, 32), n1->tx); ++ } ++ ret = n1_xfer(n1, len); ++ if (ret) ++ dev_err(&n1->spi->dev, "%s transfer (%zu bytes) failed: %d\n", ++ phase, len, ret); ++ if (log) ++ dev_info(&n1->spi->dev, "trace %s rx[%zu] ret=%d: %*ph\n", phase, ++ len, ret, (int)min_t(size_t, len, 32), n1->rx); ++ return ret; ++} ++ ++static int n1_check_control(struct apple_n1 *n1, u8 command, u8 id, size_t len) ++{ ++ u16 received = get_unaligned_le16(n1->rx + len - 2); ++ u16 calculated = n1_sum(n1->rx, len - 2); ++ ++ if (n1->rx[0] == command && received == calculated) ++ return 0; ++ dev_err(&n1->spi->dev, ++ "control %02x/%02x %s mismatch: echo=%02x expected=%02x checksum=%04x calculated=%04x\n", ++ command, id, n1->rx[0] != command ? "echo" : "checksum", ++ n1->rx[0], command, received, calculated); ++ n1_dump_error(n1, "control", n1->rx, len); ++ return -EBADMSG; ++} ++ ++static int n1_ack(struct apple_n1 *n1, u16 *reply) ++{ ++ int ret; ++ ++ n1->tx[0] = 0x1a; ++ n1->tx[1] = 0xa1; ++ ret = n1_control_xfer(n1, "HBPP ACK", 2); ++ if (!ret) ++ *reply = get_unaligned_be16(n1->rx); ++ return ret; ++} ++ ++static int n1_send_blob(struct apple_n1 *n1, size_t len) ++{ ++ u16 reply; ++ int ret, attempt; ++ u8 *packet; ++ ++ /* ATN_ACK uses tx[0:2], preserve the whole packet for a bounded retry. */ ++ packet = kmemdup(n1->tx, len, GFP_KERNEL); ++ if (!packet) ++ return -ENOMEM; ++ for (attempt = 0; attempt < N1_RETRIES; attempt++) { ++ memcpy(n1->tx, packet, len); ++ ret = n1_xfer(n1, len); ++ if (!ret) { ++ ret = n1_ack(n1, &reply); ++ if (!ret && reply == 0x4bc1) ++ break; ++ if (!ret) { ++ dev_err(&n1->spi->dev, ++ "upload %zu bytes attempt %d/%u: ACK=%04x expected=4bc1 rx=%*ph\n", ++ len, attempt + 1, N1_RETRIES, reply, 2, n1->rx); ++ ret = -EPROTO; ++ } ++ } ++ usleep_range(1000, 2000); ++ } ++ kfree(packet); ++ return ret; ++} ++ ++static int n1_read_register(struct apple_n1 *n1, u32 address, u32 *value) ++{ ++ static const u8 result_command[] = { ++ 0x1a, 0xa1, 0x18, 0xe1, 0x18, 0xe1, 0x18, 0xe1, ++ }; ++ int ret; ++ ++ put_unaligned_le16(0x731c, n1->tx); ++ n1_put32(n1->tx + 2, address); ++ put_unaligned_be16(n1_sum(n1->tx + 2, 4), n1->tx + 6); ++ ret = n1_control_xfer(n1, "register request", 8); ++ if (ret) ++ return ret; ++ memcpy(n1->tx, result_command, sizeof(result_command)); ++ ret = n1_control_xfer(n1, "register result", sizeof(result_command)); ++ if (!ret) ++ *value = n1_get32(n1->rx + 2); ++ return ret; ++} ++ ++static int n1_write_register(struct apple_n1 *n1, u32 address, u32 value) ++{ ++ u16 reply; ++ int ret; ++ ++ put_unaligned_le16(0x331e, n1->tx); ++ n1_put32(n1->tx + 2, address); ++ n1_put32(n1->tx + 6, 0xffffffff); ++ n1_put32(n1->tx + 10, value); ++ put_unaligned_be16(n1_sum(n1->tx + 2, 12), n1->tx + 14); ++ ret = n1_control_xfer(n1, "register write", 16); ++ if (ret) ++ return ret; ++ ret = n1_ack(n1, &reply); ++ if (ret) ++ return ret; ++ if (reply == 0x4ad1) ++ return 0; ++ dev_err(&n1->spi->dev, "register %#x ACK=%04x expected=4ad1 rx=%*ph\n", ++ address, reply, 2, n1->rx); ++ return -EPROTO; ++} ++ ++static bool n1_hbpp_word(u16 word) ++{ ++ switch (word) { ++ case 0x18e1: case 0x1aa1: case 0x1f01: case 0x4879: ++ case 0x4969: case 0x4ad1: case 0x4bc1: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int n1_boot_nop(struct apple_n1 *n1) ++{ ++ int i; ++ ++ put_unaligned_le16(0xa11a, n1->tx); ++ for (i = 2; i < 16; i += 2) ++ put_unaligned_le16(0xe118, n1->tx + i); ++ return n1_xfer(n1, 16); ++} ++ ++static int n1_check_hbpp(struct apple_n1 *n1) ++{ ++ int ret; ++ ++ ret = n1_boot_nop(n1); ++ if (ret) ++ return ret; ++ if (!n1_hbpp_word(get_unaligned_be16(n1->rx)) || ++ !n1_hbpp_word(get_unaligned_be16(n1->rx + 2))) { ++ dev_err(&n1->spi->dev, "HBPP not detected: %*ph\n", 4, n1->rx); ++ return -ENODEV; ++ } ++ return 0; ++} ++ ++static int n1_validate_firmware(const struct firmware *fw) ++{ ++ size_t payload; ++ ++ if (fw->size != 57016 || memcmp(fw->data, "\x18\xe1\x30\x01", 4)) ++ return -EINVAL; ++ payload = get_unaligned_be16(fw->data + 4) * 4; ++ if (payload + 16 != fw->size || n1_get32(fw->data + 6) || ++ get_unaligned_be16(fw->data + 10) != n1_sum(fw->data + 4, 6) || ++ n1_get32(fw->data + 12 + payload) != n1_sum(fw->data + 12, payload)) ++ return -EBADMSG; ++ return 0; ++} ++ ++static int n1_upload_calibration(struct apple_n1 *n1, const u8 *data) ++{ ++ size_t offset, length, i; ++ int ret; ++ ++ /* N1 shared Z2 calibration download, maximum payload 0x3f0. */ ++ for (offset = 0; offset < N1_CAL_SIZE; offset += length) { ++ length = min_t(size_t, N1_CAL_SIZE - offset, 0x3f0); ++ put_unaligned_le16(0xe118, n1->tx); ++ put_unaligned_le16(0x0130, n1->tx + 2); ++ put_unaligned_be16(length / 4, n1->tx + 4); ++ n1_put32(n1->tx + 6, 0x10009000 + offset); ++ put_unaligned_be16(n1_sum(n1->tx + 4, 6), n1->tx + 10); ++ for (i = 0; i < length; i += 2) { ++ n1->tx[12 + i] = data[offset + i + 1]; ++ n1->tx[13 + i] = data[offset + i]; ++ } ++ n1_put32(n1->tx + 12 + length, n1_sum(data + offset, length)); ++ ret = n1_send_blob(n1, length + 16); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ ++static int n1_calibrate(struct apple_n1 *n1, u32 version) ++{ ++ const struct { u32 address, value; } registers[] = { ++ { 0x10003060, 6089 }, ++ { 0x1000305c, 0x20 }, ++ { 0x10003058, 6 }, ++ { 0x10003000, version == 0x434d11a0 ? 2 : 3 }, ++ { 0x10003518, 1 }, ++ }; ++ u32 apu; ++ u16 reply; ++ int i, ret; ++ ++ dev_info(&n1->spi->dev, "calibrating controller %#x, const-cal=%u\n", ++ version, version == 0x434d11a0 ? 2 : 3); ++ for (i = 0; i < ARRAY_SIZE(registers); i++) { ++ ret = n1_write_register(n1, registers[i].address, registers[i].value); ++ if (ret) { ++ dev_err(&n1->spi->dev, "calibration register %#x failed: %d\n", ++ registers[i].address, ret); ++ return ret; ++ } ++ } ++ ret = n1_read_register(n1, 0x10003800, &apu); ++ if (ret) ++ return ret; ++ dev_dbg(&n1->spi->dev, "SPI_APU_EN=%#x\n", apu); ++ put_unaligned_le16(0x011f, n1->tx); ++ ret = n1_control_xfer(n1, "calibration request", 2); ++ if (ret) ++ return ret; ++ msleep(65); ++ ret = n1_ack(n1, &reply); ++ if (!ret) ++ dev_info(&n1->spi->dev, "calibration transport complete, status %#x\n", reply); ++ /* Apple's N1 path checks transport only; do not invent a success word. */ ++ return ret; ++} ++ ++static int n1_execute(struct apple_n1 *n1) ++{ ++ int ret; ++ ++ put_unaligned_le16(0x531d, n1->tx); ++ n1_put32(n1->tx + 2, 0x10003400); ++ put_unaligned_le32(0x100, n1->tx + 6); ++ put_unaligned_be16(n1_sum(n1->tx + 2, 8), n1->tx + 10); ++ ret = n1_control_xfer(n1, "firmware execute", 12); ++ if (!ret) { ++ msleep(40); ++ /* bootloadTimerFiredHandler waits another 2ms before discovery. */ ++ usleep_range(2000, 3000); ++ } ++ return ret; ++} ++ ++static int n1_wake(struct apple_n1 *n1) ++{ ++ int ret; ++ ++ /* N1 suppresses duplicate wake packets within 100ms. */ ++ if (n1->wake_valid && time_before(jiffies, n1->last_wake + msecs_to_jiffies(100))) ++ return 0; ++ memset(n1->tx, 0, 16); ++ n1->tx[0] = 0xee; ++ put_unaligned_le16(0xee, n1->tx + 14); ++ ret = n1_control_xfer(n1, "runtime wake", 16); ++ if (!ret) { ++ n1->last_wake = jiffies; ++ n1->wake_valid = true; ++ usleep_range(2000, 3000); ++ } ++ return ret; ++} ++ ++static int n1_command16(struct apple_n1 *n1, u8 command, u8 argument) ++{ ++ int ret; ++ ++ ret = n1_wake(n1); ++ if (ret) ++ return ret; ++ memset(n1->tx, 0, 16); ++ n1->tx[0] = command; ++ n1->tx[1] = argument; ++ put_unaligned_le16(n1_sum(n1->tx, 14), n1->tx + 14); ++ ret = n1_control_xfer(n1, "control request", 16); ++ if (ret) ++ return ret; ++ /* E2 repeats immediately; E3 has the configured control-read delay. */ ++ if (command == 0xe3) ++ udelay(25); ++ ret = n1_control_xfer(n1, "control response", 16); ++ if (ret) ++ return ret; ++ return n1_check_control(n1, command, argument, 16); ++} ++ ++/* data has N1_REPORT_MAX bytes; accept descriptor extensions Apple ignores. */ ++static int n1_get_report_once(struct apple_n1 *n1, u8 id, u8 *data, size_t minimum) ++{ ++ u16 length, checksum; ++ size_t transfer_length; ++ u8 command; ++ int ret; ++ ++ ret = n1_command16(n1, 0xe3, id); ++ if (ret) ++ return ret; ++ length = get_unaligned_le16(n1->rx + 3); ++ if (n1->rx[2] || length < minimum || length > N1_REPORT_MAX) { ++ dev_err(&n1->spi->dev, ++ "report %02x info: status=%02x length=%u expected=%zu..%u\n", ++ id, n1->rx[2], length, minimum, N1_REPORT_MAX); ++ n1_dump_error(n1, "report info", n1->rx, 16); ++ return -EPROTO; ++ } ++ ret = n1_wake(n1); ++ if (ret) ++ return ret; ++ command = length < 12 ? 0xe6 : 0xe7; ++ transfer_length = length < 12 ? 16 : length + 5; ++ memset(n1->tx, 0, max_t(size_t, 16, transfer_length)); ++ n1->tx[0] = command; ++ n1->tx[1] = id; ++ put_unaligned_le16(length, n1->tx + 3); ++ checksum = n1_sum(n1->tx, 5); ++ put_unaligned_le16(checksum, n1->tx + 14); ++ ret = n1_control_xfer(n1, "report request", 16); ++ if (ret) ++ return ret; ++ udelay(25); ++ n1->tx[2] = 1; ++ /* Stage two preserves stage one's checksum, despite changing byte 2. */ ++ if (command == 0xe7) { ++ n1->tx[14] = 0; ++ n1->tx[15] = 0; ++ put_unaligned_le16(checksum, n1->tx + transfer_length - 2); ++ } ++ ret = n1_control_xfer(n1, "report response", transfer_length); ++ if (ret) ++ return ret; ++ ret = n1_check_control(n1, command, id, transfer_length); ++ if (ret) ++ return ret; ++ memcpy(data, n1->rx + 3, length); ++ return 0; ++} ++ ++static int n1_get_report(struct apple_n1 *n1, u8 id, u8 *data, size_t minimum) ++{ ++ int ret, attempt; ++ ++ /* iOS delays every GET invocation by 2ms and retries corrupt replies. ++ * Use one aggregate budget instead of its nested E3/GET retry budgets. ++ * Transport timeouts and valid unsupported/malformed descriptors abort. ++ */ ++ for (attempt = 0; attempt < N1_RETRIES; attempt++) { ++ usleep_range(2000, 3000); ++ ret = n1_get_report_once(n1, id, data, minimum); ++ if (ret != -EBADMSG) ++ return ret; ++ dev_err(&n1->spi->dev, "report %02x attempt %d/%u failed: %d\n", ++ id, attempt + 1, N1_RETRIES, ret); ++ if (attempt + 1 < N1_RETRIES) ++ usleep_range(1000, 2000); ++ } ++ return ret; ++} ++ ++static int n1_discover(struct apple_n1 *n1) ++{ ++ u8 family, sensor[5], descriptor[N1_REPORT_MAX]; ++ const u8 *surface = descriptor; ++ int ret, attempt; ++ ++ for (attempt = 0; attempt < 4; attempt++) { ++ ret = n1_command16(n1, 0xe2, 0); ++ if (!ret) ++ break; ++ dev_err(&n1->spi->dev, "E2 discovery attempt %d/4 failed: %d\n", ++ attempt + 1, ret); ++ usleep_range(3000, 4000); ++ } ++ if (ret) ++ return ret; ++ n1->max_packet = get_unaligned_le16(n1->rx + 3); ++ if (n1->rx[2] != 1 || n1->max_packet < 16 || n1->max_packet > N1_FRAME_MAX) { ++ dev_err(&n1->spi->dev, "E2 interface=%u expected=1 max-packet=%u expected=16..%u\n", ++ n1->rx[2], n1->max_packet, N1_FRAME_MAX); ++ n1_dump_error(n1, "device info", n1->rx, 16); ++ return -EPROTO; ++ } ++ ret = n1_get_report(n1, 0xd1, descriptor, 1); ++ if (ret) ++ return ret; ++ family = descriptor[0]; ++ ret = n1_get_report(n1, 0xd3, descriptor, sizeof(sensor)); ++ if (ret) ++ return ret; ++ memcpy(sensor, descriptor, sizeof(sensor)); ++ if (sensor[0] != 1) { ++ dev_err(&n1->spi->dev, "D3 endianness=%u expected=1\n", sensor[0]); ++ n1_dump_error(n1, "D3", n1->rx, n1->control_length); ++ return -EPROTO; ++ } ++ ret = n1_get_report(n1, 0xd0, descriptor, 8); ++ if (ret) ++ return ret; ++ ret = n1_get_report(n1, 0xa1, descriptor, 6); ++ if (ret) ++ return ret; ++ ret = n1_get_report(n1, 0xd9, descriptor, 16); ++ if (ret) ++ return ret; ++ /* Values from the live J71t descriptor, not display pixels. */ ++ if (get_unaligned_le32(surface) != 14745 || ++ get_unaligned_le32(surface + 4) != 19657) { ++ dev_err(&n1->spi->dev, "D9 surface identity mismatch, expected 14745x19657\n"); ++ n1_dump_error(n1, "D9", n1->rx, n1->control_length); ++ return -EPROTO; ++ } ++ n1->x_min = (s16)get_unaligned_le16(surface + 8); ++ n1->y_min = (s16)get_unaligned_le16(surface + 10); ++ n1->x_max = (s16)get_unaligned_le16(surface + 12); ++ n1->y_max = (s16)get_unaligned_le16(surface + 14); ++ if (n1->x_min >= n1->x_max || n1->y_min >= n1->y_max) { ++ dev_err(&n1->spi->dev, "D9 invalid edges: x=%d..%d y=%d..%d\n", ++ n1->x_min, n1->x_max, n1->y_min, n1->y_max); ++ n1_dump_error(n1, "D9", n1->rx, n1->control_length); ++ return -EPROTO; ++ } ++ dev_info(&n1->spi->dev, ++ "N1 ready: interface 1, max packet %u, family %u, %ux%u, FW %#x, surface 14745x19657\n", ++ n1->max_packet, family, sensor[1], sensor[2], ++ get_unaligned_be16(sensor + 3)); ++ return 0; ++} ++ ++static int n1_parse_contacts(struct apple_n1 *n1, const u8 *report, size_t length) ++{ ++ DECLARE_BITMAP(ids, 256); ++ size_t offset, stride, count, i; ++ const u8 *finger; ++ int slot, x, y; ++ bool active; ++ ++ if (!length) ++ return -EPROTO; ++ /* Classic reports dispatched by MTClassicPacketParser; no HID wrapper. */ ++ if (report[0] != 0x43 && report[0] != 0x44 && report[0] != 0x45) ++ return 0; ++ if (length < 22) ++ return -EPROTO; ++ stride = report[17]; ++ count = report[16]; ++ offset = report[2] + get_unaligned_le16(report + 14); ++ if (report[2] < 22 || offset > length || count > N1_SLOTS || ++ (count && (stride < 20 || stride > 64 || count > (length - offset) / stride))) ++ return -EPROTO; ++ bitmap_zero(ids, 256); ++ /* Validate the entire report before changing input slots. */ ++ for (i = 0; i < count; i++) { ++ finger = report + offset + i * stride; ++ if (test_and_set_bit(finger[0], ids)) ++ return -EPROTO; ++ } ++ for (i = 0; i < count; i++) { ++ finger = report + offset + i * stride; ++ slot = input_mt_get_slot_by_key(n1->input, finger[0]); ++ if (slot < 0) ++ continue; ++ active = finger[1] == 3 || finger[1] == 4; ++ input_mt_slot(n1->input, slot); ++ if (!input_mt_report_slot_state(n1->input, MT_TOOL_FINGER, active)) ++ continue; ++ x = (s16)get_unaligned_le16(finger + 4); ++ y = (s16)get_unaligned_le16(finger + 6); ++ touchscreen_report_pos(n1->input, &n1->props, ++ clamp(x, n1->x_min, n1->x_max) - n1->x_min, ++ clamp(y, n1->y_min, n1->y_max) - n1->y_min, true); ++ input_report_abs(n1->input, ABS_MT_TOUCH_MAJOR, ++ get_unaligned_le16(finger + 12)); ++ input_report_abs(n1->input, ABS_MT_TOUCH_MINOR, ++ get_unaligned_le16(finger + 14)); ++ input_report_abs(n1->input, ABS_MT_ORIENTATION, ++ (s16)get_unaligned_le16(finger + 16)); ++ input_report_abs(n1->input, ABS_MT_PRESSURE, ++ stride >= 28 ? get_unaligned_le16(finger + 26) : 0); ++ } ++ input_mt_sync_frame(n1->input); ++ input_sync(n1->input); ++ return 0; ++} ++ ++/* Returns 1 for a consumed frame, 0 for an empty controller, or an errno. */ ++static int n1_read_frame(struct apple_n1 *n1, bool report_events) ++{ ++ u16 length, payload_length; ++ size_t wire_length; ++ int ret; ++ ++ memset(n1->tx, 0, 16); ++ n1->tx[0] = 0xeb; ++ n1->tx[1] = n1->sequence; ++ put_unaligned_le16(n1_sum(n1->tx, 14), n1->tx + 14); ++ ret = n1_xfer(n1, 16); ++ if (ret) ++ return ret; ++ if ((n1->rx[0] & 0xf0) != 0xe0 || ++ get_unaligned_le16(n1->rx + 14) != (u16)n1_sum(n1->rx, 14)) ++ return -EBADMSG; ++ length = get_unaligned_le16(n1->rx + 1); ++ if (!length) ++ return 0; ++ wire_length = length + 5; ++ /* E2's max-packet semantics are not proven for N1. Bound by our actual ++ * buffers, as Apple's N1 PIO path does, rather than the legacy limit. ++ */ ++ if (length < 2 || wire_length > N1_FRAME_MAX) ++ return -EMSGSIZE; ++ memset(n1->tx, 0, max_t(size_t, 16, wire_length)); ++ n1->tx[0] = 0xeb; ++ n1->tx[1] = n1->sequence; ++ n1->tx[2] = 1; ++ put_unaligned_le16(n1_sum(n1->tx, 14), n1->tx + wire_length - 2); ++ ret = n1_xfer(n1, wire_length); ++ if (ret) ++ return ret; ++ if ((n1->rx[0] != 0xea && n1->rx[0] != 0xeb) || ++ (n1_sum(n1->rx, 5) & 0xff)) ++ return -EBADMSG; ++ payload_length = get_unaligned_le16(n1->rx + 2); ++ if (payload_length < 2 || payload_length > length || ++ get_unaligned_le16(n1->rx + wire_length - 2) != ++ (u16)n1_sum(n1->rx + 5, payload_length - 2)) ++ return -EBADMSG; ++ n1->sequence = n1->sequence == 1 ? 2 : 1; ++ if (READ_ONCE(trace) && n1->traced_frames < 8 && payload_length > 2) { ++ n1->traced_frames++; ++ dev_info(&n1->spi->dev, "frame %u, %u payload bytes: %*ph\n", ++ n1->traced_frames, payload_length - 2, ++ min_t(unsigned int, payload_length - 2, 32), n1->rx + 5); ++ } ++ if (report_events && payload_length > 2) { ++ ret = n1_parse_contacts(n1, n1->rx + 5, payload_length - 2); ++ if (ret) ++ return ret; ++ } ++ return 1; ++} ++ ++static void n1_release_contacts(struct apple_n1 *n1) ++{ ++ int i; ++ ++ if (!n1->input) ++ return; ++ for (i = 0; i < N1_SLOTS; i++) { ++ input_mt_slot(n1->input, i); ++ input_mt_report_slot_state(n1->input, MT_TOOL_FINGER, false); ++ } ++ input_mt_sync_frame(n1->input); ++ input_sync(n1->input); ++} ++ ++static int n1_irq_frame(struct apple_n1 *n1) ++{ ++ int attempt, ret, asserted; ++ ++ /* Retry while this falling-edge IRQ is masked: a failed read may leave ++ * ATN asserted, so waiting for another falling edge would lose recovery. ++ */ ++ for (attempt = 0; attempt < 3; attempt++) { ++ ret = n1_read_frame(n1, true); ++ if (ret > 0) ++ return ret; ++ if (!ret) { ++ asserted = gpiod_get_value_cansleep(n1->atn); ++ if (!asserted) ++ return 0; ++ ret = asserted < 0 ? asserted : -EPROTO; ++ } ++ usleep_range(1000, 2000); ++ } ++ return ret; ++} ++ ++static void n1_mask_irq(struct apple_n1 *n1) ++{ ++ if (!n1->irq_masked) { ++ disable_irq_nosync(n1->spi->irq); ++ n1->irq_masked = true; ++ } ++} ++ ++static int n1_drain(struct apple_n1 *n1) ++{ ++ int i, ret; ++ ++ for (i = 0; i < 8; i++) { ++ ret = n1_read_frame(n1, true); ++ if (ret <= 0) ++ break; ++ } ++ if (ret < 0 || i == 8) ++ return ret < 0 ? ret : -EOVERFLOW; ++ ret = gpiod_get_value_cansleep(n1->atn); ++ return ret > 0 ? -EBUSY : ret; ++} ++ ++/* Called with lock held and ATN IRQ masked, only while SPI is available. */ ++static int n1_rearm(struct apple_n1 *n1) ++{ ++ int ret; ++ ++ n1->wake_valid = false; ++ ret = n1_wake(n1); ++ if (!ret) ++ ret = n1_drain(n1); ++ if (ret) { ++ dev_warn(&n1->spi->dev, "runtime drain failed %d; trying discovery once\n", ret); ++ n1_release_contacts(n1); ++ /* Runtime init only: never reset, reload firmware or calibration. */ ++ n1->sequence = 1; ++ ret = n1_discover(n1); ++ if (!ret) ++ ret = n1_drain(n1); ++ } ++ if (ret) ++ return ret; ++ ++ n1->irq_errors = 0; ++ if (n1->irq_masked) { ++ n1->irq_masked = false; ++ enable_irq(n1->spi->irq); ++ } ++ dev_info(&n1->spi->dev, "N1 runtime wake/drain complete; IRQ rearmed\n"); ++ return 0; ++} ++ ++static void n1_queue_recovery(struct apple_n1 *n1) ++{ ++ if (n1->recovery_attempts < 3) ++ schedule_delayed_work(&n1->recovery_work, msecs_to_jiffies(1000)); ++ else ++ dev_err(&n1->spi->dev, "N1 recovery budget exhausted; rebind to recover\n"); ++} ++ ++static void n1_recovery_work(struct work_struct *work) ++{ ++ struct apple_n1 *n1 = container_of(to_delayed_work(work), ++ struct apple_n1, recovery_work); ++ int ret; ++ ++ mutex_lock(&n1->lock); ++ if (!n1->suspended && n1->irq_requested && n1->recovery_attempts < 3) { ++ n1->recovery_attempts++; ++ ret = n1_rearm(n1); ++ dev_info(&n1->spi->dev, "N1 recovery %u/3 result %d\n", ++ n1->recovery_attempts, ret); ++ if (ret) ++ n1_queue_recovery(n1); ++ } ++ mutex_unlock(&n1->lock); ++} ++ ++static irqreturn_t n1_irq(int irq, void *data) ++{ ++ struct apple_n1 *n1 = data; ++ int ret, i, asserted; ++ ++ mutex_lock(&n1->lock); ++ if (n1->suspended || n1->irq_masked) ++ goto out; ++ ++ /* A stuck ATN cannot monopolize the IRQ thread. */ ++ for (i = 0; i < 8; i++) { ++ ret = n1_irq_frame(n1); ++ if (ret <= 0) ++ break; ++ asserted = gpiod_get_value_cansleep(n1->atn); ++ if (asserted < 0) { ++ ret = asserted; ++ break; ++ } ++ if (!asserted) ++ break; ++ } ++ if (ret < 0 || i == 8) { ++ n1_release_contacts(n1); ++ n1->irq_errors++; ++ dev_err_ratelimited(&n1->spi->dev, "frame/ATN error %d (count %u); IRQ masked\n", ++ ret < 0 ? ret : -EOVERFLOW, n1->irq_errors); ++ n1_mask_irq(n1); ++ n1_queue_recovery(n1); ++ } else { ++ n1->irq_errors = 0; ++ } ++out: ++ mutex_unlock(&n1->lock); ++ return IRQ_HANDLED; ++} ++ ++static void n1_power_off(struct apple_n1 *n1) ++{ ++ if (!n1->powered) ++ return; ++ gpiod_set_value_cansleep(n1->reset, 1); ++ if (pinctrl_select_state(n1->pins, n1->sync_off)) ++ dev_warn(&n1->spi->dev, "failed to stop display-sync pin\n"); ++ clk_disable_unprepare(n1->clk); ++ gpiod_set_value_cansleep(n1->ldo, 0); ++ regulator_disable(n1->vdd); ++ n1->powered = false; ++} ++ ++static int n1_power_on(struct apple_n1 *n1) ++{ ++ int ret; ++ ++ dev_info(&n1->spi->dev, "N1 power/reset begin (reset asserted, LDO off)\n"); ++ /* Ensure a real reset interval even after a retained/panic entry. */ ++ usleep_range(5000, 6000); ++ ret = regulator_enable(n1->vdd); ++ if (ret) ++ return ret; ++ gpiod_set_value_cansleep(n1->ldo, 1); ++ /* afterPowerOn(): enable-CS helper's pre-delay, then TCLK and wake. */ ++ usleep_range(5000, 6000); ++ ret = clk_prepare_enable(n1->clk); ++ if (ret) { ++ gpiod_set_value_cansleep(n1->ldo, 0); ++ regulator_disable(n1->vdd); ++ return ret; ++ } ++ n1->powered = true; ++ usleep_range(15000, 17000); ++ put_unaligned_le16(0xc119, n1->tx); ++ ret = n1_xfer(n1, 2); ++ if (ret) ++ goto fail; ++ usleep_range(10000, 12000); ++ ret = pinctrl_select_state(n1->pins, n1->sync_on); ++ if (ret) ++ goto fail; ++ /* The 16-byte HBPP NOP transaction also leaves S_CLK high for reset. */ ++ ret = n1_boot_nop(n1); ++ if (ret) ++ goto fail; ++ gpiod_set_value_cansleep(n1->reset, 0); ++ usleep_range(15000, 17000); ++ return 0; ++fail: ++ n1_power_off(n1); ++ return ret; ++} ++ ++static int n1_probe(struct spi_device *spi) ++{ ++ struct device *dev = &spi->dev; ++ const struct firmware *fw = NULL, *calibration = NULL; ++ struct apple_n1 *n1; ++ const char *name, *cal_name; ++ const u8 *cal_data = NULL; ++ u8 *dt_calibration; ++ u32 version; ++ unsigned int stage = READ_ONCE(boot_stage); ++ int ret, count, i; ++ ++ dev_info(dev, "N1 probe begin stage=%u\n", stage); ++ if (stage < 1 || stage > 3) ++ return -EINVAL; ++ n1 = devm_kzalloc(dev, sizeof(*n1), GFP_KERNEL); ++ if (!n1) ++ return -ENOMEM; ++ n1->spi = spi; ++ n1->sequence = 1; ++ n1->tx = devm_kmalloc(dev, N1_XFER_MAX, GFP_KERNEL); ++ n1->rx = devm_kmalloc(dev, N1_XFER_MAX, GFP_KERNEL); ++ if (!n1->tx || !n1->rx) ++ return -ENOMEM; ++ spi_set_drvdata(spi, n1); ++ mutex_init(&n1->lock); ++ INIT_DELAYED_WORK(&n1->recovery_work, n1_recovery_work); ++ n1->irq_masked = true; ++ spi->bits_per_word = 8; ++ /* N1 configureSPI overrides the ADT +12 word delay from 5000ns to 0. */ ++ spi->word_delay.value = 0; ++ spi->word_delay.unit = SPI_DELAY_UNIT_USECS; ++ ret = spi_setup(spi); ++ if (ret) ++ return ret; ++ n1->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(n1->reset)) ++ return dev_err_probe(dev, PTR_ERR(n1->reset), "reset GPIO\n"); ++ n1->ldo = devm_gpiod_get(dev, "ldo", GPIOD_OUT_LOW); ++ if (IS_ERR(n1->ldo)) ++ return dev_err_probe(dev, PTR_ERR(n1->ldo), "LDO GPIO\n"); ++ n1->atn = devm_gpiod_get(dev, "attn", GPIOD_IN); ++ if (IS_ERR(n1->atn)) ++ return dev_err_probe(dev, PTR_ERR(n1->atn), "ATN GPIO\n"); ++ n1->vdd = devm_regulator_get(dev, "vdd"); ++ if (IS_ERR(n1->vdd)) ++ return dev_err_probe(dev, PTR_ERR(n1->vdd), "analog supply\n"); ++ n1->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(n1->clk)) ++ return dev_err_probe(dev, PTR_ERR(n1->clk), "reference clock\n"); ++ n1->pins = devm_pinctrl_get(dev); ++ if (IS_ERR(n1->pins)) ++ return dev_err_probe(dev, PTR_ERR(n1->pins), "display-sync pinctrl\n"); ++ n1->sync_on = pinctrl_lookup_state(n1->pins, "sync-on"); ++ if (IS_ERR(n1->sync_on)) ++ return dev_err_probe(dev, PTR_ERR(n1->sync_on), "sync-on state\n"); ++ n1->sync_off = pinctrl_lookup_state(n1->pins, "sync-off"); ++ if (IS_ERR(n1->sync_off)) ++ return dev_err_probe(dev, PTR_ERR(n1->sync_off), "sync-off state\n"); ++ dev_info(dev, "N1 resources ready; loading firmware/calibration\n"); ++ if (stage >= 2) { ++ ret = device_property_read_string(dev, "firmware-name", &name); ++ if (ret) ++ return ret; ++ /* Never use the sysfs fallback: a missing file must fail immediately. */ ++ ret = request_firmware_direct(&fw, name, dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "N1 firmware %s\n", name); ++ ret = n1_validate_firmware(fw); ++ if (ret) { ++ dev_err(dev, "invalid N1 firmware %s: %d\n", name, ret); ++ goto release_firmware; ++ } ++ if (device_property_present(dev, "apple,n1-calibration")) { ++ count = device_property_count_u8(dev, "apple,n1-calibration"); ++ if (count != N1_CAL_SIZE) { ++ ret = -EINVAL; ++ goto release_firmware; ++ } ++ dt_calibration = devm_kmalloc(dev, N1_CAL_SIZE, GFP_KERNEL); ++ if (!dt_calibration) { ++ ret = -ENOMEM; ++ goto release_firmware; ++ } ++ ret = device_property_read_u8_array(dev, "apple,n1-calibration", ++ dt_calibration, N1_CAL_SIZE); ++ if (ret) ++ goto release_firmware; ++ cal_data = dt_calibration; ++ } else { ++ ret = device_property_read_string(dev, "apple,calibration-firmware", &cal_name); ++ if (ret) ++ goto release_firmware; ++ ret = request_firmware_direct(&calibration, cal_name, dev); ++ if (ret) { ++ dev_err(dev, "N1 calibration %s: %d\n", cal_name, ret); ++ goto release_firmware; ++ } ++ if (calibration->size != N1_CAL_SIZE) { ++ ret = -EINVAL; ++ goto release_firmware; ++ } ++ cal_data = calibration->data; ++ } ++ if (!memchr_inv(cal_data, 0, N1_CAL_SIZE) || ++ !memchr_inv(cal_data, 0xff, N1_CAL_SIZE)) { ++ ret = -EINVAL; ++ goto release_firmware; ++ } ++ } ++ ret = n1_power_on(n1); ++ if (ret) ++ goto release_firmware; ++ dev_info(dev, "N1 power/reset complete; HBPP begin\n"); ++ ret = n1_check_hbpp(n1); ++ if (ret) ++ goto power_off; ++ ret = n1_read_register(n1, 0x10008ffc, &version); ++ if (ret) ++ goto power_off; ++ if (!version || version == 0xffffffff) { ++ ret = -ENODEV; ++ goto power_off; ++ } ++ dev_info(dev, "HBPP detected; N1 controller %#x (stage %u)\n", version, stage); ++ if (stage == 1) ++ goto power_off; ++ dev_info(dev, "N1 calibration upload begin\n"); ++ ret = n1_upload_calibration(n1, cal_data); ++ if (ret) ++ goto power_off; ++ dev_info(dev, "N1 firmware upload begin\n"); ++ memcpy(n1->tx, fw->data, fw->size); ++ ret = n1_send_blob(n1, fw->size); ++ if (ret) ++ goto power_off; ++ dev_info(dev, "uploaded 1024-byte MtCl and 57016-byte C1F15,1 firmware\n"); ++ /* Apple's calibration stage reads the version after the image upload. */ ++ ret = n1_read_register(n1, 0x10008ffc, &version); ++ if (ret) ++ goto power_off; ++ ret = n1_calibrate(n1, version); ++ if (ret) ++ goto power_off; ++ dev_info(dev, "N1 execute begin\n"); ++ ret = n1_execute(n1); ++ if (ret) ++ goto power_off; ++ dev_info(dev, "N1 discovery begin\n"); ++ ret = n1_discover(n1); ++ if (ret || stage == 2) ++ goto power_off; ++ n1->input = devm_input_allocate_device(dev); ++ if (!n1->input) { ++ ret = -ENOMEM; ++ goto power_off; ++ } ++ n1->input->name = "Apple N1 Touchscreen"; ++ n1->input->id.bustype = BUS_SPI; ++ input_set_capability(n1->input, EV_KEY, BTN_TOUCH); ++ input_set_abs_params(n1->input, ABS_MT_POSITION_X, 0, n1->x_max - n1->x_min, 0, 0); ++ input_set_abs_params(n1->input, ABS_MT_POSITION_Y, 0, n1->y_max - n1->y_min, 0, 0); ++ input_set_abs_params(n1->input, ABS_MT_TOUCH_MAJOR, 0, 65535, 0, 0); ++ input_set_abs_params(n1->input, ABS_MT_TOUCH_MINOR, 0, 65535, 0, 0); ++ input_set_abs_params(n1->input, ABS_MT_ORIENTATION, -32768, 32767, 0, 0); ++ input_set_abs_params(n1->input, ABS_MT_PRESSURE, 0, 65535, 0, 0); ++ /* Classic N1 coordinates/ellipse axes are hundredths of a millimetre. */ ++ input_abs_set_res(n1->input, ABS_MT_POSITION_X, 100); ++ input_abs_set_res(n1->input, ABS_MT_POSITION_Y, 100); ++ input_abs_set_res(n1->input, ABS_MT_TOUCH_MAJOR, 100); ++ input_abs_set_res(n1->input, ABS_MT_TOUCH_MINOR, 100); ++ touchscreen_parse_properties(n1->input, true, &n1->props); ++ ret = input_mt_init_slots(n1->input, N1_SLOTS, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); ++ if (ret) ++ goto power_off; ++ ret = input_register_device(n1->input); ++ if (ret) ++ goto power_off; ++ ret = devm_request_threaded_irq(dev, spi->irq, NULL, n1_irq, ++ IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(dev), n1); ++ if (ret) ++ goto power_off; ++ n1->irq_requested = true; ++ /* Drain old boot/runtime reports before accepting interrupts. */ ++ for (i = 0; i < 8; i++) { ++ ret = n1_read_frame(n1, false); ++ if (ret <= 0) ++ break; ++ } ++ if (ret < 0 || i == 8) { ++ if (!ret || i == 8) ++ ret = -EOVERFLOW; ++ goto power_off; ++ } ++ ret = gpiod_get_value_cansleep(n1->atn); ++ if (ret) { ++ if (ret > 0) ++ ret = -EBUSY; ++ dev_err(dev, "ATN did not deassert after bounded initial drain\n"); ++ goto power_off; ++ } ++ n1->irq_masked = false; ++ enable_irq(spi->irq); ++ dev_info(dev, "touchscreen reporting enabled (inverted-y=%u)\n", n1->props.invert_y); ++ goto release_firmware; ++ ++power_off: ++ n1_power_off(n1); ++release_firmware: ++ release_firmware(calibration); ++ release_firmware(fw); ++ if (ret) ++ dev_err(dev, "N1 stage %u failed: %d (reset asserted)\n", stage, ret); ++ return ret; ++} ++ ++static void n1_remove(struct spi_device *spi) ++{ ++ struct apple_n1 *n1 = spi_get_drvdata(spi); ++ ++ dev_info(&spi->dev, "N1 stop begin irq=%u powered=%u\n", ++ n1->irq_requested, n1->powered); ++ mutex_lock(&n1->lock); ++ n1->suspended = true; ++ if (n1->irq_requested) ++ n1_mask_irq(n1); ++ mutex_unlock(&n1->lock); ++ cancel_delayed_work_sync(&n1->recovery_work); ++ if (n1->irq_requested) { ++ devm_free_irq(&spi->dev, spi->irq, n1); ++ n1->irq_requested = false; ++ } ++ n1_release_contacts(n1); ++ n1_power_off(n1); ++ dev_info(&spi->dev, "N1 stopped; reset asserted\n"); ++} ++ ++static int n1_suspend(struct device *dev) ++{ ++ struct apple_n1 *n1 = dev_get_drvdata(dev); ++ ++ if (!n1->irq_requested) ++ return 0; ++ mutex_lock(&n1->lock); ++ n1->suspended = true; ++ n1_mask_irq(n1); ++ mutex_unlock(&n1->lock); ++ /* No thread/work may transfer after the SPI parent/domain suspends. */ ++ synchronize_irq(n1->spi->irq); ++ cancel_delayed_work_sync(&n1->recovery_work); ++ n1_release_contacts(n1); ++ dev_info(dev, "N1 suspended: IRQ quiesced, firmware/rails retained\n"); ++ return 0; ++} ++ ++static int n1_resume(struct device *dev) ++{ ++ struct apple_n1 *n1 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!n1->irq_requested) ++ return 0; ++ /* Normal resume runs after the SPI parent and genpd have resumed. */ ++ mutex_lock(&n1->lock); ++ n1->suspended = false; ++ n1->irq_errors = 0; ++ ret = n1_rearm(n1); ++ if (ret) { ++ dev_err(dev, "N1 resume failed %d; IRQ stays masked for bounded recovery\n", ret); ++ n1_queue_recovery(n1); ++ } ++ mutex_unlock(&n1->lock); ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(n1_pm_ops, n1_suspend, n1_resume); ++ ++static const struct of_device_id n1_of_match[] = { ++ { .compatible = "apple,j71t-n1" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, n1_of_match); ++ ++static const struct spi_device_id n1_spi_ids[] = { ++ { "j71t-n1" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(spi, n1_spi_ids); ++ ++static struct spi_driver n1_driver = { ++ .driver = { ++ .name = "apple-n1", ++ .of_match_table = n1_of_match, ++ .pm = pm_sleep_ptr(&n1_pm_ops), ++ }, ++ .probe = n1_probe, ++ .remove = n1_remove, ++ .shutdown = n1_remove, ++ .id_table = n1_spi_ids, ++}; ++module_spi_driver(n1_driver); ++ ++MODULE_DESCRIPTION("Apple J71t N1 SPI touchscreen"); ++MODULE_AUTHOR("Berkay Kösebay "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("apple/j71t-n1.bin"); ++MODULE_FIRMWARE("apple/j71t-n1-calibration.bin"); +diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig +index 89789ac7a786c8c4c4a750cdcaa27a205762ac18..68dbc6f5fdcd4d755029e4300965ef407d4bf9f4 100644 +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -66,6 +66,13 @@ config REGULATOR_NETLINK_EVENTS + + If unsure, say no. + ++config REGULATOR_APPLE_D2365_TOUCH ++ tristate "Apple D2365 touchscreen analog supply" ++ depends on OF && REGMAP ++ help ++ Enable-only D2365 analog touch supply at register 0x031c bit zero. ++ The parent apple,i2c-pmic must provide the register map. ++ + config REGULATOR_88PG86X + tristate "Marvell 88PG86X voltage regulators" + depends on I2C +diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile +index 5a764cec8df834de6f7775574e4bbd1d4dd8f6ef..be362a9f1830da967805958e884fe34b3fafb190 100644 +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -220,3 +220,4 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o + obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o + + ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG ++obj-$(CONFIG_REGULATOR_APPLE_D2365_TOUCH) += apple-d2365-touch.o +diff --git a/drivers/regulator/apple-d2365-touch.c b/drivers/regulator/apple-d2365-touch.c +new file mode 100644 +index 0000000000000000000000000000000000000000..2c42a32d11b846337841b699913516afaa4c5d8e +--- /dev/null ++++ b/drivers/regulator/apple-d2365-touch.c +@@ -0,0 +1,60 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* D2365 pmuL(0x21f): enable-only analog touch supply. */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const struct regulator_ops d2365_touch_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .is_enabled = regulator_is_enabled_regmap, ++}; ++ ++static const struct regulator_desc d2365_touch_desc = { ++ .name = "d2365-touch-analog", ++ .type = REGULATOR_VOLTAGE, ++ .owner = THIS_MODULE, ++ .ops = &d2365_touch_ops, ++ .enable_reg = 0x031c, ++ .enable_mask = BIT(0), ++}; ++ ++static int d2365_touch_probe(struct platform_device *pdev) ++{ ++ struct regulator_config config = {}; ++ struct regulator_dev *regulator; ++ u32 address; ++ int ret; ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "reg", 0, &address); ++ if (ret || address != d2365_touch_desc.enable_reg) ++ return -EINVAL; ++ config.dev = &pdev->dev; ++ config.of_node = pdev->dev.of_node; ++ config.regmap = dev_get_regmap(pdev->dev.parent, NULL); ++ if (!config.regmap) ++ return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "PMIC register map unavailable\n"); ++ config.init_data = of_get_regulator_init_data(&pdev->dev, config.of_node, ++ &d2365_touch_desc); ++ regulator = devm_regulator_register(&pdev->dev, &d2365_touch_desc, &config); ++ return PTR_ERR_OR_ZERO(regulator); ++} ++ ++static const struct of_device_id d2365_touch_match[] = { ++ { .compatible = "apple,d2365-touch-analog" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, d2365_touch_match); ++static struct platform_driver d2365_touch_driver = { ++ .probe = d2365_touch_probe, ++ .driver = { ++ .name = "apple-d2365-touch", ++ .of_match_table = d2365_touch_match, ++ }, ++}; ++module_platform_driver(d2365_touch_driver); ++MODULE_DESCRIPTION("D2365 touchscreen analog power switch"); ++MODULE_LICENSE("GPL"); diff --git a/kernel/patches/j71t/0005-j71t-audio.patch b/kernel/patches/j71t/0005-j71t-audio.patch new file mode 100644 index 00000000..3a85e782 --- /dev/null +++ b/kernel/patches/j71t/0005-j71t-audio.patch @@ -0,0 +1,3191 @@ +From 3e47c1c2a3d75643d9fbf283192608745c363be1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 05/12] ASoC: add the CS42L81 codec and S8000 MCA audio +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Speakers, headphone routing, jack detection and microphone capture +through the S8000 MCA and the Cirrus CS42L81 codec. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../bindings/sound/apple,s8000-mca.yaml | 106 ++ + .../bindings/sound/cirrus,cs42l81.yaml | 85 + + sound/soc/apple/Kconfig | 11 + + sound/soc/apple/Makefile | 2 + + sound/soc/apple/s8000-mca.c | 1262 +++++++++++++ + sound/soc/codecs/Kconfig | 8 + + sound/soc/codecs/Makefile | 2 + + sound/soc/codecs/cs42l81.c | 1613 +++++++++++++++++ + 8 files changed, 3089 insertions(+) + create mode 100644 Documentation/devicetree/bindings/sound/apple,s8000-mca.yaml + create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs42l81.yaml + create mode 100644 sound/soc/apple/s8000-mca.c + create mode 100644 sound/soc/codecs/cs42l81.c + +diff --git a/Documentation/devicetree/bindings/sound/apple,s8000-mca.yaml b/Documentation/devicetree/bindings/sound/apple,s8000-mca.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..1026f82730b9c0ae52639ae6cdf310bc7f211fa6 +--- /dev/null ++++ b/Documentation/devicetree/bindings/sound/apple,s8000-mca.yaml +@@ -0,0 +1,106 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/sound/apple,s8000-mca.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple S8000 (A9) MCA I2S controller ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: ++ The A9 "mca-version 4" serializer. Besides its register window it owns a ++ 32-bit MCLK control word (exported as a clock) and programs a route in the ++ shared i2s-switch. ++ ++allOf: ++ - $ref: dai-common.yaml# ++ ++properties: ++ compatible: ++ const: apple,s8000-mca ++ ++ reg: ++ items: ++ - description: MCA registers ++ - description: MCLK control word ++ ++ reg-names: ++ items: ++ - const: mca ++ - const: mclk ++ ++ interrupts: ++ maxItems: 1 ++ ++ power-domains: ++ maxItems: 1 ++ ++ dmas: ++ minItems: 1 ++ items: ++ - description: SmartIO transmit (playback) endpoint ++ - description: SmartIO receive (capture) endpoint ++ ++ dma-names: ++ minItems: 1 ++ items: ++ - const: tx ++ - const: rx ++ ++ '#sound-dai-cells': ++ const: 0 ++ ++ '#clock-cells': ++ const: 0 ++ ++ clock-output-names: ++ maxItems: 1 ++ ++ apple,mclk-control: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: Value written to the MCLK word to enable MCLK output. ++ ++ apple,mclk-frequency: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ ++ apple,i2s-switch: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: The i2s-switch syscon. ++ ++ apple,i2s-switch-route: ++ $ref: /schemas/types.yaml#/definitions/uint32-matrix ++ items: ++ items: ++ - description: register byte offset ++ - description: value ++ description: i2s-switch registers to set while the port runs. ++ ++ apple,sio-dma-config: ++ $ref: /schemas/types.yaml#/definitions/uint32-array ++ items: ++ - description: SmartIO transfer control for 16-bit samples ++ - description: SmartIO transfer control for 32-bit samples ++ - description: shim timeout ++ - description: shim FIFO size, bytes ++ - description: shim trigger level, bytes ++ description: ++ The SmartIO shim words of this MCA's DMA endpoints (Apple ADT ++ dma-channels; the transmit and receive entries carry the same words). ++ ++ apple,pio-fifo-offset: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ description: Experimental CPU-writable TX FIFO register (no DMA). ++ ++ apple,pio-burst: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - interrupts ++ - '#sound-dai-cells' ++ ++unevaluatedProperties: false +diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l81.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l81.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..1d056d8b0e8cac3782115fed02b8ef0edb99579b +--- /dev/null ++++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l81.yaml +@@ -0,0 +1,85 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/sound/cirrus,cs42l81.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Cirrus Logic CS42L81 audio codec (Apple) ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: ++ Apple-specific Cirrus Logic codec with integrated speaker drivers, a ++ headphone driver and digital microphone inputs, used on the A9 iPad 5. ++ Controlled over SPI with a 5-byte paged register protocol. The codec DAI ++ has outputs SPKOUT and HPOUT and DMIC inputs DMIC1A and DMIC1B for ++ machine routing. ++ ++allOf: ++ - $ref: dai-common.yaml# ++ - $ref: /schemas/spi/spi-peripheral-props.yaml# ++ ++properties: ++ compatible: ++ const: cirrus,cs42l81 ++ ++ reg: ++ maxItems: 1 ++ ++ clocks: ++ maxItems: 1 ++ ++ clock-names: ++ const: mclk ++ ++ mute-gpios: ++ maxItems: 1 ++ description: Speaker mute line, asserted while the speakers are muted. ++ ++ hs-switch-gpios: ++ maxItems: 2 ++ description: ++ The two control lines of the external headset switch (Apple ++ function-ext_sw1, function-ext_sw2). Their direction is not known; ++ the driver only reads them. ++ ++ apple,max-speaker-gain-db-q8: ++ $ref: /schemas/types.yaml#/definitions/int32 ++ description: Maximum speaker gain in 1/256 dB (ADT spkr-gain-db). ++ ++ apple,speaker-mode: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ enum: [1, 2] ++ description: Number of speaker outputs in use. ++ ++ '#sound-dai-cells': ++ const: 0 ++ ++required: ++ - compatible ++ - reg ++ - '#sound-dai-cells' ++ ++unevaluatedProperties: false ++ ++examples: ++ - | ++ #include ++ ++ spi { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ audio-codec@0 { ++ compatible = "cirrus,cs42l81"; ++ reg = <0>; ++ spi-max-frequency = <1000000>; ++ clocks = <&mca0>; ++ clock-names = "mclk"; ++ mute-gpios = <&pinctrl_ap 64 GPIO_ACTIVE_LOW>; ++ hs-switch-gpios = <&pinctrl_ap 68 GPIO_ACTIVE_HIGH>, ++ <&pinctrl_ap 66 GPIO_ACTIVE_HIGH>; ++ #sound-dai-cells = <0>; ++ }; ++ }; +diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig +index f1d071a99fbf6ef4f2ba60ad8ae0a44afcaddf89..a03470c0fadadf16fc09f69ccb6e1141477c5351 100644 +--- a/sound/soc/apple/Kconfig ++++ b/sound/soc/apple/Kconfig +@@ -9,4 +9,15 @@ config SND_SOC_APPLE_MCA + This option enables an ASoC platform driver for MCA peripherals found + on Apple Silicon SoCs. + ++config SND_SOC_APPLE_S8000_MCA ++ tristate "Apple S8000 (A9) MCA driver" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on COMMON_CLK ++ select MFD_SYSCON ++ select SND_DMAENGINE_PCM ++ select SND_SOC_GENERIC_DMAENGINE_PCM ++ help ++ ASoC CPU DAI for the "mca-version 4" serializer of the Apple A9 ++ (S8000/S8003), including its MCLK output and i2s-switch route. ++ + endmenu +diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile +index 252ba5fc18cb9046f6b2912c32de596d6f9dfdb6..0058680040a380b4011c3a96ca9fcaeb636ab385 100644 +--- a/sound/soc/apple/Makefile ++++ b/sound/soc/apple/Makefile +@@ -1,4 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0-only + snd-soc-apple-mca-y := mca.o ++snd-soc-apple-s8000-mca-y := s8000-mca.o + + obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o ++obj-$(CONFIG_SND_SOC_APPLE_S8000_MCA) += snd-soc-apple-s8000-mca.o +diff --git a/sound/soc/apple/s8000-mca.c b/sound/soc/apple/s8000-mca.c +new file mode 100644 +index 0000000000000000000000000000000000000000..6d942e5e88826a66a70230ff5919bc1871502c52 +--- /dev/null ++++ b/sound/soc/apple/s8000-mca.c +@@ -0,0 +1,1262 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Apple S8000/S8003 (A9) MCA, "mca-version 4", as an ASoC CPU DAI. ++ * ++ * Register layout and the programming sequence follow iOS 16.7.10's ++ * AppleAE2MCA::_setIISConfig_v4 (see docs/audio/playback-path.md in ++ * omarchy-ipad5). Also handles the two neighbours iOS drives from the MCA ++ * driver: the per-MCA MCLK control word (exported as a clock, the codec's ++ * MCLK) and the i2s-switch route (a syscon; route register values from DT). ++ * ++ * Data path: iOS feeds the MCA through SmartIO DMA (endpoint 0x28 for MCA0 ++ * TX, 0x29 for MCA0 RX). With "dmas"/"dma-names = tx[, rx]" the generic ++ * dmaengine PCM runs on the apple-sio-dma driver; the SmartIO shim record ++ * (ADT dma-channels words) is passed as the slave config's ++ * peripheral_config. Capture exists only with an "rx" channel. Without DMA ++ * (or with nodma=1), a timer-driven playback PCM keeps the stream running ++ * and, if "apple,pio-fifo-offset" names a CPU writable FIFO port ++ * (unverified, see test plan), feeds it from the MCA's TX-low-water ++ * interrupt. Otherwise samples are discarded: clocks, codec and speakers ++ * still run. ++ * ++ * TX and RX share the frame (clocks, slot width, run bit) and the IRQ: the ++ * first prepared direction programs the whole block, the other one only ++ * resets and programs its own half, and the frame stops with the last. ++ */ ++ ++#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 MCA_VERSION 0x00 ++#define MCA_FIFO_SIZE 0x04 ++#define MCA_FIFO_SIZE_TX GENMASK(9, 0) ++#define MCA_FIFO_SIZE_RX GENMASK(25, 16) ++#define MCA_SAMPLE 0x08 ++#define MCA_RESET 0x0c ++#define MCA_RESET_TX BIT(0) ++#define MCA_RESET_RX BIT(1) ++#define MCA_FRAME 0x10 ++#define MCA_FRAME_MASTER BIT(0) ++#define MCA_FRAME_RUN BIT(1) ++#define MCA_FRAME_FMT BIT(3) /* set unless format flag bit 1 */ ++#define MCA_FRAME_DUTY BIT(6) ++#define MCA_FRAME_MCLK_DIV GENMASK(11, 8) ++#define MCA_FRAME_SLOTS GENMASK(15, 12) ++#define MCA_FRAME_WIDTH GENMASK(23, 20) ++#define MCA_FRAME_HALF GENMASK(31, 24) ++#define MCA_PAD 0x14 ++#define MCA_IRQ_STATUS 0x1c ++#define MCA_IRQ_MASK 0x20 ++#define MCA_IRQ_TX_UNDERFLOW BIT(0) ++#define MCA_IRQ_TX_OVERFLOW BIT(1) ++#define MCA_IRQ_TX_LOW_WATER BIT(2) ++#define MCA_IRQ_RX_UNDERFLOW BIT(3) ++#define MCA_IRQ_RX_OVERFLOW BIT(4) ++#define MCA_IRQ_RX_HIGH_WATER BIT(5) ++#define MCA_IRQ_FRAME_ERROR BIT(6) ++#define MCA_IRQ_BCLK BIT(7) ++#define MCA_IRQ_NO_BCLK BIT(8) ++#define MCA_IRQ_FSYNC BIT(9) ++#define MCA_IRQ_NO_FSYNC BIT(10) ++#define MCA_IRQ_SHORT_FSYNC BIT(11) ++#define MCA_IRQ_RATE BIT(12) ++#define MCA_IRQ_ALL GENMASK(12, 0) ++#define MCA_IRQ_ERRORS (MCA_IRQ_TX_UNDERFLOW | MCA_IRQ_TX_OVERFLOW | \ ++ MCA_IRQ_RX_UNDERFLOW | MCA_IRQ_RX_OVERFLOW | \ ++ MCA_IRQ_FRAME_ERROR) ++/* ++ * iOS v4 unmasks only the four FIFO error causes (mask 0x1fe4); split per ++ * direction so a direction's causes are only open while it is prepared. ++ */ ++#define MCA_IRQ_TX_CAUSES (MCA_IRQ_TX_UNDERFLOW | MCA_IRQ_TX_OVERFLOW) ++#define MCA_IRQ_RX_CAUSES (MCA_IRQ_RX_UNDERFLOW | MCA_IRQ_RX_OVERFLOW) ++#define MCA_IRQ_TX_ALL GENMASK(2, 0) ++#define MCA_IRQ_RX_ALL GENMASK(5, 3) ++#define MCA_FLUSH 0x24 ++#define MCA_TX_CFG 0x60 ++#define MCA_TX_FIFO_CFG 0x64 ++#define MCA_TX_SLOTS 0x68 ++#define MCA_TX_SLOTS_HI 0x6c ++#define MCA_RX_CFG 0x80 ++#define MCA_RX_FIFO_CFG 0x84 ++#define MCA_RX_SLOTS 0x88 ++#define MCA_RX_SLOTS_HI 0x8c ++#define MCA_XX_CFG_EN BIT(0) ++ ++/* iOS _setIISConfig_v4 values for MCA0 (playback-path.md section 3) */ ++#define MCA_TX_CFG_I2S (BIT(8) | 0x33 | 0x4) /* enable: 0x137 */ ++#define MCA_RX_CFG_I2S (BIT(8) | 0x33) /* enable: 0x133 */ ++#define MCA_RX_FIFO_CFG_IOS 0x00030004 /* RX depth 4 (mcaKeepDMABusy) */ ++ ++static bool nodma; ++module_param(nodma, bool, 0444); ++MODULE_PARM_DESC(nodma, "ignore dmas, use the timer PCM (clock/codec tests)"); ++ ++/* bring-up overrides, read at every prepare / MCLK enable */ ++static uint mclk_ctl; ++module_param(mclk_ctl, uint, 0644); ++MODULE_PARM_DESC(mclk_ctl, "MCLK control word override (0 = from DT)"); ++static char *route = ""; ++module_param(route, charp, 0644); ++MODULE_PARM_DESC(route, "i2s-switch route override: \"off=val,off=val\" (byte offsets)"); ++ ++#define MCA_POLL_US 10 ++#define MCA_TIMEOUT_US 10000 ++ ++/* i2s-switch v3 reset values (AppleAE2I2SSwitch3 +0x580) */ ++#define I2S_SW_NODE_DEFAULT 0x00181818 ++#define I2S_SW_DSPC_DEFAULT 0x18 ++#define I2S_SW_DSPC_OFFSET 0x38 ++ ++#define MCA_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ ++ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ ++ SNDRV_PCM_RATE_48000) ++#define MCA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) ++ ++struct s8000_mca { ++ struct device *dev; ++ void __iomem *base; ++ void __iomem *mclk_reg; ++ struct regmap *sw; ++ u32 *sw_route; /* offset, value pairs */ ++ unsigned int sw_route_len; ++ ++ struct clk_hw mclk_hw; ++ u32 mclk_ctl; ++ u32 mclk_rate; ++ ++ u32 fifo_tx; ++ bool use_dma; ++ bool capture; /* SmartIO "rx" channel present */ ++ struct snd_soc_dai_driver dai_drv; ++ struct snd_dmaengine_dai_dma_data dma_tx, dma_rx; ++ /* SmartIO shim words from DT: xfer 16-bit, xfer 32-bit, timeout, fifo, trigger */ ++ u32 sio_dt[5]; ++ struct apple_sio_dma_config sio_cfg[2]; /* per SNDRV_PCM_STREAM_* */ ++ ++ /* computed in hw_params, written in prepare */ ++ u32 sample, frame, tx_cfg, tx_fifo_cfg, tx_slots, rx_slots; ++ ++ /* ++ * BIT(SNDRV_PCM_STREAM_*): prepared (prepare .. hw_free) and running ++ * (trigger start .. stop). Under hw_lock with frame RUN and irq_mask. ++ */ ++ spinlock_t hw_lock; ++ unsigned int prep_mask, run_mask; ++ ++ /* timer/PIO PCM */ ++ spinlock_t lock; ++ struct snd_pcm_substream *substream; ++ struct hrtimer timer; ++ ktime_t period; ++ snd_pcm_uframes_t pos; ++ unsigned int pio_off; /* 0 = no CPU FIFO port */ ++ unsigned int pio_burst; ++ bool running; ++ bool warned_null; ++ ++ int irq; ++ unsigned long irq_count, err_count, spurious_count; ++ u32 last_status; ++ u32 irq_mask; /* current MCA_IRQ_MASK value */ ++ u32 mclk_cur; /* last MCLK word written (0 = off) */ ++ bool mclk_on; /* MCLK clock enabled: word written */ ++ bool route_on; /* i2s-switch route applied */ ++ /* last stream per direction: a start-up underflow vs recurring ones */ ++ struct { ++ ktime_t start_time; ++ s64 first_err_us; /* first error IRQ after start, -1 = none */ ++ u32 first_err_status; ++ u32 stop_status; /* raw IRQ_STATUS at stop */ ++ } last[2]; ++ bool mclk_forced; /* debugfs s8000-mca-mclk */ ++ struct mutex dbg_lock; /* debugfs s8000-mca-mclk */ ++ u32 route_ovr[16]; /* parsed "route" parameter */ ++ unsigned int route_ovr_len; ++}; ++ ++static inline void mca_write(struct s8000_mca *mca, u32 off, u32 val) ++{ ++ writel_relaxed(val, mca->base + off); ++} ++ ++static inline u32 mca_read(struct s8000_mca *mca, u32 off) ++{ ++ return readl_relaxed(mca->base + off); ++} ++ ++/* --- MCLK control word as a clock ----------------------------------------- */ ++ ++static struct s8000_mca *hw_to_mca(struct clk_hw *hw) ++{ ++ return container_of(hw, struct s8000_mca, mclk_hw); ++} ++ ++static int mca_mclk_prepare(struct clk_hw *hw) ++{ ++ return pm_runtime_resume_and_get(hw_to_mca(hw)->dev); ++} ++ ++static void mca_mclk_unprepare(struct clk_hw *hw) ++{ ++ pm_runtime_put(hw_to_mca(hw)->dev); ++} ++ ++static u32 mca_mclk_word(struct s8000_mca *mca) ++{ ++ return READ_ONCE(mclk_ctl) ?: mca->mclk_ctl; ++} ++ ++static int mca_mclk_enable(struct clk_hw *hw) ++{ ++ struct s8000_mca *mca = hw_to_mca(hw); ++ ++ /* AppleAE2MCA _getMCLKClkCon: nclk/2 (0x18) | output enable (0x40) */ ++ mca->mclk_cur = mca_mclk_word(mca); ++ writel(mca->mclk_cur, mca->mclk_reg); ++ mca->mclk_on = true; ++ return 0; ++} ++ ++static void mca_mclk_disable(struct clk_hw *hw) ++{ ++ struct s8000_mca *mca = hw_to_mca(hw); ++ ++ mca->mclk_on = false; ++ if (!mca->mclk_forced) { ++ writel(0, mca->mclk_reg); ++ mca->mclk_cur = 0; ++ } ++} ++ ++static unsigned long mca_mclk_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ return hw_to_mca(hw)->mclk_rate; ++} ++ ++static const struct clk_ops mca_mclk_ops = { ++ .prepare = mca_mclk_prepare, ++ .unprepare = mca_mclk_unprepare, ++ .enable = mca_mclk_enable, ++ .disable = mca_mclk_disable, ++ .recalc_rate = mca_mclk_recalc_rate, ++}; ++ ++/* --- i2s-switch ---------------------------------------------------------- */ ++ ++/* parse the "route" parameter ("off=val,off=val") into route_ovr */ ++static void mca_parse_route(struct s8000_mca *mca) ++{ ++ char buf[128], *p, *tok; ++ unsigned int n = 0; ++ u32 off, val; ++ ++ strscpy(buf, READ_ONCE(route) ?: "", sizeof(buf)); ++ p = buf; ++ while ((tok = strsep(&p, ",")) && n + 1 < ARRAY_SIZE(mca->route_ovr)) { ++ if (sscanf(tok, "%i=%i", &off, &val) != 2 || off >= 0x50 || (off & 3)) ++ continue; ++ mca->route_ovr[n++] = off; ++ mca->route_ovr[n++] = val; ++ } ++ mca->route_ovr_len = n; ++} ++ ++static void mca_route(struct s8000_mca *mca, bool on) ++{ ++ const u32 *r = mca->sw_route; ++ unsigned int i, len = mca->sw_route_len; ++ ++ if (!mca->sw) ++ return; ++ ++ if (on) ++ mca_parse_route(mca); ++ if (mca->route_ovr_len) { ++ r = mca->route_ovr; ++ len = mca->route_ovr_len; ++ } ++ ++ for (i = 0; i + 1 < len; i += 2) { ++ u32 off = r[i], val = r[i + 1]; ++ ++ if (!on) ++ val = off < I2S_SW_DSPC_OFFSET ? I2S_SW_NODE_DEFAULT : ++ off == I2S_SW_DSPC_OFFSET ? I2S_SW_DSPC_DEFAULT : 0; ++ regmap_write(mca->sw, off, val); ++ } ++ mca->route_on = on; ++} ++ ++/* --- DAI ------------------------------------------------------------------ */ ++ ++static int mca_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) ++{ ++ /* iOS runs MCA0 as the clock consumer of the CS42L81. */ ++ if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BC_FC) ++ return -EINVAL; ++ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) ++ return -EINVAL; ++ if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) ++ return -EINVAL; ++ return 0; ++} ++ ++/* AppleAE2MCA::_getFIFODepth, TX direction */ ++static u32 mca_tx_depth(struct s8000_mca *mca, unsigned int rate, ++ unsigned int channels) ++{ ++ u32 per_ms = round_up(rate * channels / 2000, 4); ++ u32 min = (channels * 2 + 3) & 0x3fc; ++ u32 depth; ++ ++ if (per_ms < min) ++ depth = min; ++ else ++ depth = min_t(u32, per_ms, mca->fifo_tx - 3); ++ ++ depth &= ~3; ++ return max_t(u32, depth, 4); ++} ++ ++static int mca_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(dai); ++ struct snd_dmaengine_dai_dma_data *dma = ++ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? &mca->dma_tx : ++ &mca->dma_rx; ++ unsigned int bits = params_width(params); ++ unsigned int slot = params_physical_width(params); ++ unsigned int channels = params_channels(params); ++ unsigned int slots = 2, frame_bits = slot * slots, depth; ++ u32 width_code; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ switch (slot) { ++ case 16: width_code = 1; break; ++ case 32: width_code = 4; break; ++ default: return -EINVAL; ++ } ++ ++ /* ++ * _setIISConfig_v4 for: consumer, MCLK out, I2S, 2 slots. The DAI is ++ * symmetric, so both directions compute the same values for both ++ * halves; prepare writes the half (or the block) it owns. ++ */ ++ mca->sample = (32 - bits) << 8 | (32 - bits); ++ depth = mca_tx_depth(mca, params_rate(params), channels); ++ mca->tx_fifo_cfg = depth | (depth - 1) << 16; ++ mca->tx_slots = GENMASK(slots - 1, 0); ++ mca->rx_slots = GENMASK(slots - 1, 0); ++ /* bits 15:8 = data delay (1, I2S); 0x33 base; 0x4 for format 0 */ ++ mca->tx_cfg = MCA_TX_CFG_I2S; ++ mca->frame = FIELD_PREP(MCA_FRAME_HALF, frame_bits / 2 - 1) | ++ FIELD_PREP(MCA_FRAME_WIDTH, width_code) | ++ FIELD_PREP(MCA_FRAME_SLOTS, slots - 1) | ++ FIELD_PREP(MCA_FRAME_MCLK_DIV, 0) | ++ MCA_FRAME_DUTY | MCA_FRAME_FMT; ++ ++ dma->addr_width = slot == 16 ? DMA_SLAVE_BUSWIDTH_2_BYTES : ++ DMA_SLAVE_BUSWIDTH_4_BYTES; ++ /* informational: the SmartIO limit comes from mca_prepare_slave_config */ ++ dma->maxburst = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? depth : ++ MCA_RX_FIFO_CFG_IOS & 0xffff; ++ ++ dev_dbg(mca->dev, "sample %#x frame %#x txcfg %#x txfifo %#x\n", ++ mca->sample, mca->frame, mca->tx_cfg, mca->tx_fifo_cfg); ++ return 0; ++} ++ ++static u32 mca_dir_causes(struct s8000_mca *mca, bool tx) ++{ ++ if (!tx) ++ return MCA_IRQ_RX_CAUSES; ++ return MCA_IRQ_TX_CAUSES | (mca->pio_off ? MCA_IRQ_TX_LOW_WATER : 0); ++} ++ ++static void mca_write_tx(struct s8000_mca *mca) ++{ ++ mca_write(mca, MCA_TX_FIFO_CFG, mca->tx_fifo_cfg); ++ mca_write(mca, MCA_TX_SLOTS, mca->tx_slots); ++ mca_write(mca, MCA_TX_SLOTS_HI, 0); ++ mca_write(mca, MCA_TX_CFG, mca->tx_cfg & ~MCA_XX_CFG_EN); ++} ++ ++static void mca_write_rx(struct s8000_mca *mca) ++{ ++ mca_write(mca, MCA_RX_FIFO_CFG, MCA_RX_FIFO_CFG_IOS); ++ mca_write(mca, MCA_RX_SLOTS, mca->rx_slots); ++ mca_write(mca, MCA_RX_SLOTS_HI, 0); ++ mca_write(mca, MCA_RX_CFG, MCA_RX_CFG_I2S & ~MCA_XX_CFG_EN); ++} ++ ++/* the whole block, as _setIISConfig_v4: only while no direction is prepared */ ++static int mca_init_block(struct s8000_mca *mca) ++{ ++ unsigned long flags; ++ u32 val; ++ int ret; ++ ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ mca->irq_mask = MCA_IRQ_ALL; ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ ++ mca_write(mca, MCA_RESET, MCA_RESET_TX | MCA_RESET_RX); ++ ret = readl_relaxed_poll_timeout(mca->base + MCA_RESET, val, ++ !(val & (MCA_RESET_TX | MCA_RESET_RX)), ++ MCA_POLL_US, MCA_TIMEOUT_US); ++ if (ret) { ++ dev_err(mca->dev, "reset timed out (%#x)\n", val); ++ return ret; ++ } ++ ++ mca_write(mca, MCA_FLUSH, 1); ++ ret = readl_relaxed_poll_timeout(mca->base + MCA_FLUSH, val, !(val & 1), ++ MCA_POLL_US, MCA_TIMEOUT_US); ++ if (ret) { ++ dev_err(mca->dev, "flush timed out (%#x)\n", val); ++ return ret; ++ } ++ ++ mca_write(mca, MCA_IRQ_MASK, MCA_IRQ_ALL); ++ mca_write(mca, MCA_IRQ_STATUS, MCA_IRQ_ALL); ++ mca_write(mca, MCA_SAMPLE, mca->sample); ++ mca_write(mca, MCA_TX_FIFO_CFG, mca->tx_fifo_cfg); ++ mca_write(mca, MCA_RX_FIFO_CFG, MCA_RX_FIFO_CFG_IOS); ++ mca_write(mca, MCA_PAD, 0); ++ mca_write(mca, MCA_TX_SLOTS, mca->tx_slots); ++ mca_write(mca, MCA_TX_SLOTS_HI, 0); ++ mca_write(mca, MCA_RX_SLOTS, mca->rx_slots); ++ mca_write(mca, MCA_RX_SLOTS_HI, 0); ++ mca_write(mca, MCA_TX_CFG, mca->tx_cfg & ~MCA_XX_CFG_EN); ++ mca_write(mca, MCA_RX_CFG, MCA_RX_CFG_I2S & ~MCA_XX_CFG_EN); ++ return 0; ++} ++ ++/* one direction while the other is prepared or running: leave the frame */ ++static int mca_init_dir(struct s8000_mca *mca, bool tx) ++{ ++ u32 bit = tx ? MCA_RESET_TX : MCA_RESET_RX, val; ++ int ret; ++ ++ mca_write(mca, MCA_RESET, bit); ++ ret = readl_relaxed_poll_timeout(mca->base + MCA_RESET, val, ++ !(val & bit), MCA_POLL_US, ++ MCA_TIMEOUT_US); ++ if (ret) { ++ dev_err(mca->dev, "%s reset timed out (%#x)\n", ++ tx ? "TX" : "RX", val); ++ return ret; ++ } ++ ++ mca_write(mca, MCA_SAMPLE, mca->sample); ++ if (tx) ++ mca_write_tx(mca); ++ else ++ mca_write_rx(mca); ++ mca_write(mca, MCA_IRQ_STATUS, tx ? MCA_IRQ_TX_ALL : MCA_IRQ_RX_ALL); ++ return 0; ++} ++ ++static int mca_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(dai); ++ int dir = substream->stream; ++ bool tx = dir == SNDRV_PCM_STREAM_PLAYBACK, first; ++ unsigned long flags; ++ int ret; ++ ++ /* ASoC serializes prepare/hw_free of the card's streams (pcm_mutex) */ ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ first = !(mca->prep_mask & ~BIT(dir)); ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ ++ ret = first ? mca_init_block(mca) : mca_init_dir(mca, tx); ++ if (ret) ++ return ret; ++ ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ mca->prep_mask |= BIT(dir); ++ mca->irq_mask &= ~mca_dir_causes(mca, tx); ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ if (first) ++ mca_write(mca, MCA_FRAME, mca->frame); ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ ++ if (first) ++ mca_route(mca, true); ++ return 0; ++} ++ ++static int mca_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(dai); ++ int dir = substream->stream; ++ unsigned long flags; ++ bool last; ++ ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ mca->prep_mask &= ~BIT(dir); ++ last = !mca->prep_mask; ++ if (last) { ++ /* back to the idle state runtime resume sets up: everything masked */ ++ mca->irq_mask = MCA_IRQ_ALL; ++ mca_write(mca, MCA_FRAME, 0); ++ } else { ++ mca->irq_mask |= dir == SNDRV_PCM_STREAM_PLAYBACK ? ++ MCA_IRQ_TX_ALL : MCA_IRQ_RX_ALL; ++ } ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ ++ if (last) ++ mca_route(mca, false); ++ return 0; ++} ++ ++static int mca_trigger(struct snd_pcm_substream *substream, int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(dai); ++ int dir = substream->stream; ++ bool tx = dir == SNDRV_PCM_STREAM_PLAYBACK; ++ u32 reset = tx ? MCA_RESET_TX : MCA_RESET_RX, val; ++ unsigned long flags; ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ mca->last[dir].start_time = ktime_get(); ++ mca->last[dir].first_err_us = -1; ++ mca->last[dir].stop_status = 0; ++ /* the other direction's pending causes are not ours to clear */ ++ mca_write(mca, MCA_IRQ_STATUS, !mca->run_mask ? MCA_IRQ_ALL : ++ tx ? MCA_IRQ_TX_ALL : MCA_IRQ_RX_ALL); ++ if (tx && mca->pio_off) { ++ mca->irq_mask &= ~MCA_IRQ_TX_LOW_WATER; ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ } ++ if (tx) ++ mca_write(mca, MCA_TX_CFG, mca->tx_cfg | MCA_XX_CFG_EN); ++ else ++ mca_write(mca, MCA_RX_CFG, MCA_RX_CFG_I2S | MCA_XX_CFG_EN); ++ mca->run_mask |= BIT(dir); ++ mca_write(mca, MCA_FRAME, mca->frame | MCA_FRAME_RUN); ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ return 0; ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ spin_lock_irqsave(&mca->hw_lock, flags); ++ mca->last[dir].stop_status = mca_read(mca, MCA_IRQ_STATUS); ++ if (tx) { ++ /* an empty FIFO would keep TX low-water firing until prepare */ ++ mca->irq_mask |= MCA_IRQ_TX_LOW_WATER; ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ mca_write(mca, MCA_TX_CFG, 0); ++ } else { ++ mca_write(mca, MCA_RX_CFG, 0); ++ } ++ mca->run_mask &= ~BIT(dir); ++ if (!mca->run_mask) ++ mca_write(mca, MCA_FRAME, mca->frame); ++ /* TX stops with RESET = 1, RX with RESET = 2 (iOS) */ ++ mca_write(mca, MCA_RESET, reset); ++ ret = readl_relaxed_poll_timeout_atomic(mca->base + MCA_RESET, ++ val, !(val & reset), 1, ++ 1000); ++ spin_unlock_irqrestore(&mca->hw_lock, flags); ++ if (ret) ++ dev_warn(mca->dev, "%s reset timed out\n", ++ tx ? "TX" : "RX"); ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int mca_dai_probe(struct snd_soc_dai *dai) ++{ ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(dai); ++ ++ snd_soc_dai_init_dma_data(dai, &mca->dma_tx, ++ mca->capture ? &mca->dma_rx : NULL); ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops mca_dai_ops = { ++ .probe = mca_dai_probe, ++ .set_fmt = mca_set_fmt, ++ .hw_params = mca_hw_params, ++ .prepare = mca_prepare, ++ .hw_free = mca_hw_free, ++ .trigger = mca_trigger, ++}; ++ ++/* template; probe drops .capture without a SmartIO "rx" channel */ ++static const struct snd_soc_dai_driver mca_dai = { ++ .name = "s8000-mca", ++ .playback = { ++ .stream_name = "Playback", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = MCA_RATES, ++ .formats = MCA_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "Capture", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = MCA_RATES, ++ .formats = MCA_FORMATS, ++ }, ++ .ops = &mca_dai_ops, ++ /* one frame (clocks, slot width) for both directions */ ++ .symmetric_rate = 1, ++ .symmetric_sample_bits = 1, ++}; ++ ++/* --- timer / PIO PCM (no SIO DMA) ----------------------------------------- */ ++ ++static const struct snd_pcm_hardware mca_pcm_hw = { ++ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | ++ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER, ++ .formats = MCA_FORMATS, ++ .rates = MCA_RATES, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ .channels_min = 2, ++ .channels_max = 2, ++ .buffer_bytes_max = 512 * 1024, ++ .period_bytes_min = 256, ++ .period_bytes_max = 64 * 1024, ++ .periods_min = 2, ++ .periods_max = 128, ++}; ++ ++/* Called with mca->lock held; returns true when a period boundary passed. */ ++static bool mca_pio_fill(struct s8000_mca *mca) ++{ ++ struct snd_pcm_runtime *rt = mca->substream->runtime; ++ unsigned int ch = rt->channels, n = mca->pio_burst / ch, i, c; ++ snd_pcm_uframes_t old = mca->pos; ++ ++ for (i = 0; i < n; i++) { ++ void *frame = rt->dma_area + frames_to_bytes(rt, mca->pos); ++ ++ for (c = 0; c < ch; c++) { ++ u32 s; ++ ++ if (rt->sample_bits == 16) ++ s = (u16)((s16 *)frame)[c]; ++ else ++ s = ((u32 *)frame)[c]; ++ writel_relaxed(s, mca->base + mca->pio_off); ++ } ++ if (++mca->pos >= rt->buffer_size) ++ mca->pos = 0; ++ } ++ ++ return old / rt->period_size != mca->pos / rt->period_size; ++} ++ ++static enum hrtimer_restart mca_timer_fn(struct hrtimer *t) ++{ ++ struct s8000_mca *mca = container_of(t, struct s8000_mca, timer); ++ struct snd_pcm_substream *ss; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mca->lock, flags); ++ ss = mca->running ? mca->substream : NULL; ++ if (ss) { ++ mca->pos += ss->runtime->period_size; ++ if (mca->pos >= ss->runtime->buffer_size) ++ mca->pos -= ss->runtime->buffer_size; ++ } ++ spin_unlock_irqrestore(&mca->lock, flags); ++ ++ if (!ss) ++ return HRTIMER_NORESTART; ++ ++ snd_pcm_period_elapsed(ss); ++ hrtimer_forward_now(t, mca->period); ++ return HRTIMER_RESTART; ++} ++ ++static int mca_pcm_open(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct s8000_mca *mca = snd_soc_component_get_drvdata(component); ++ ++ snd_soc_set_runtime_hwparams(substream, &mca_pcm_hw); ++ mca->substream = substream; ++ ++ if (!mca->pio_off && !mca->warned_null) { ++ dev_warn(mca->dev, ++ "no SIO DMA and no PIO FIFO port: samples are discarded (clocks and codec still run)\n"); ++ mca->warned_null = true; ++ } ++ return 0; ++} ++ ++static int mca_pcm_close(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct s8000_mca *mca = snd_soc_component_get_drvdata(component); ++ ++ hrtimer_cancel(&mca->timer); ++ mca->substream = NULL; ++ return 0; ++} ++ ++static int mca_pcm_prepare(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct s8000_mca *mca = snd_soc_component_get_drvdata(component); ++ struct snd_pcm_runtime *rt = substream->runtime; ++ ++ mca->pos = 0; ++ mca->period = ns_to_ktime(div_u64((u64)rt->period_size * NSEC_PER_SEC, ++ rt->rate)); ++ return 0; ++} ++ ++static int mca_pcm_trigger(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream, int cmd) ++{ ++ struct s8000_mca *mca = snd_soc_component_get_drvdata(component); ++ unsigned long flags; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ spin_lock_irqsave(&mca->lock, flags); ++ mca->running = true; ++ spin_unlock_irqrestore(&mca->lock, flags); ++ if (!mca->pio_off) ++ hrtimer_start(&mca->timer, mca->period, ++ HRTIMER_MODE_REL_SOFT); ++ return 0; ++ case SNDRV_PCM_TRIGGER_STOP: ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ spin_lock_irqsave(&mca->lock, flags); ++ mca->running = false; ++ spin_unlock_irqrestore(&mca->lock, flags); ++ hrtimer_try_to_cancel(&mca->timer); ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static snd_pcm_uframes_t mca_pcm_pointer(struct snd_soc_component *component, ++ struct snd_pcm_substream *substream) ++{ ++ struct s8000_mca *mca = snd_soc_component_get_drvdata(component); ++ ++ return READ_ONCE(mca->pos); ++} ++ ++static int mca_pcm_new(struct snd_soc_component *component, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, ++ 0, 0); ++ return 0; ++} ++ ++static const struct snd_soc_component_driver mca_component_pio = { ++ .name = "s8000-mca", ++ .open = mca_pcm_open, ++ .close = mca_pcm_close, ++ .prepare = mca_pcm_prepare, ++ .trigger = mca_pcm_trigger, ++ .pointer = mca_pcm_pointer, ++ .pcm_new = mca_pcm_new, ++ .legacy_dai_naming = 1, ++}; ++ ++static const struct snd_soc_component_driver mca_component_dma = { ++ .name = "s8000-mca", ++ .legacy_dai_naming = 1, ++}; ++ ++/* SmartIO queues one command per period; it has 31 tags per endpoint. */ ++/* ++ * SmartIO reports the position per completed period (residue granularity ++ * "segment"). The generic dmaengine PCM would flag that as BATCH, but it ++ * skips that logic when the driver supplies its own hardware limits, so ++ * set it here: PipeWire/PulseAudio need it to keep a period of headroom. ++ */ ++static const struct snd_pcm_hardware mca_dma_hw = { ++ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | ++ SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER | ++ SNDRV_PCM_INFO_BATCH, ++ .formats = MCA_FORMATS, ++ .rates = MCA_RATES, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ .channels_min = 2, ++ .channels_max = 2, ++ .buffer_bytes_max = 128 * 1024, ++ .period_bytes_min = 512, ++ .period_bytes_max = 32 * 1024, ++ .periods_min = 2, ++ .periods_max = 16, ++}; ++ ++static int mca_prepare_slave_config(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct dma_slave_config *sc) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct s8000_mca *mca = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)); ++ struct apple_sio_dma_config *cfg = &mca->sio_cfg[substream->stream]; ++ unsigned int bytes = params_physical_width(params) / 8; ++ unsigned int per_ms; ++ int ret; ++ ++ ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, sc); ++ if (ret) ++ return ret; ++ ++ /* ++ * AppleSmartIODMA shim record as AppleAE2MCA sets it up: transfer ++ * width from the dma-channels entry (16 or 32 bit; the RX entries ++ * have the same words), and the FIFO depth it requests for either ++ * direction: sample width * samples per ms, rounded to 4. The DMA ++ * driver applies SmartIO's validFIFODepth: TX at least trigger + 4, ++ * RX at most the trigger (192 B at 48 kHz, under 0x180). ++ */ ++ per_ms = round_up(params_rate(params) * params_channels(params) / 1000, 4); ++ *cfg = (struct apple_sio_dma_config) { ++ .xfer = bytes == 2 ? mca->sio_dt[0] : mca->sio_dt[1], ++ .timeout = mca->sio_dt[2], ++ .fifo = mca->sio_dt[3], ++ .trigger = mca->sio_dt[4], ++ .limit = bytes * per_ms, ++ }; ++ sc->peripheral_config = cfg; ++ sc->peripheral_size = sizeof(*cfg); ++ return 0; ++} ++ ++static const struct snd_dmaengine_pcm_config mca_dma_config = { ++ .prepare_slave_config = mca_prepare_slave_config, ++ .pcm_hardware = &mca_dma_hw, ++ .prealloc_buffer_size = 128 * 1024, ++}; ++ ++/* --- IRQ ------------------------------------------------------------------ */ ++ ++static irqreturn_t mca_irq(int irq, void *data) ++{ ++ struct s8000_mca *mca = data; ++ struct snd_pcm_substream *elapsed = NULL; ++ u32 raw, status; ++ int dir; ++ ++ spin_lock(&mca->hw_lock); ++ raw = mca_read(mca, MCA_IRQ_STATUS); ++ /* ++ * Only bits 12:0 are interrupt causes (iOS tests status & 0x1fff and ++ * logs "spurious intr" otherwise). Bit 31 also reads as set while a ++ * cause is pending (seen as 0x80000008), so it must not count as ++ * handled. Otherwise a cause that is masked only in irq_mask, not in ++ * the hardware mask, is "handled" forever. That IRQ storm is the ++ * likely cause of the round-4 hard lockup. ++ */ ++ status = raw & MCA_IRQ_ALL & ~mca->irq_mask; ++ if (!status) { ++ /* resync the hardware mask, so a stale open mask cannot storm */ ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ mca_write(mca, MCA_IRQ_STATUS, raw & MCA_IRQ_ALL); ++ mca->spurious_count++; ++ mca->last_status = raw; ++ spin_unlock(&mca->hw_lock); ++ return IRQ_NONE; ++ } ++ ++ mca_write(mca, MCA_IRQ_STATUS, status); ++ mca->irq_count++; ++ mca->last_status = raw; ++ ++ if (status & MCA_IRQ_ERRORS) { ++ /* ++ * Error bits re-latch every frame while the condition lasts ++ * (e.g. TX underflow without a data path): count once, mask ++ * them until the direction's next prepare instead of storming. ++ */ ++ mca->err_count++; ++ for (dir = 0; dir < 2; dir++) { ++ u32 mine = dir == SNDRV_PCM_STREAM_PLAYBACK ? ++ MCA_IRQ_TX_ALL : MCA_IRQ_RX_ALL; ++ ++ if (!(status & mine & MCA_IRQ_ERRORS) || ++ mca->last[dir].first_err_us >= 0) ++ continue; ++ mca->last[dir].first_err_us = ++ ktime_us_delta(ktime_get(), ++ mca->last[dir].start_time); ++ mca->last[dir].first_err_status = raw; ++ } ++ mca->irq_mask |= status & MCA_IRQ_ERRORS; ++ mca_write(mca, MCA_IRQ_MASK, mca->irq_mask); ++ dev_dbg_ratelimited(mca->dev, "irq status %#x, masked\n", status); ++ } ++ spin_unlock(&mca->hw_lock); ++ ++ if ((status & MCA_IRQ_TX_LOW_WATER) && mca->pio_off) { ++ spin_lock(&mca->lock); ++ if (mca->running && mca->substream && mca_pio_fill(mca)) ++ elapsed = mca->substream; ++ spin_unlock(&mca->lock); ++ } ++ ++ /* outside mca->lock: period_elapsed takes the stream lock */ ++ if (elapsed) ++ snd_pcm_period_elapsed(elapsed); ++ ++ return IRQ_HANDLED; ++} ++ ++/* --- runtime PM ----------------------------------------------------------- */ ++ ++/* ++ * The IRQ is only enabled while the block is powered: the handler reads ++ * MCA registers, and an MMIO access to a power-gated Apple block can hang ++ * the CPU. ++ */ ++static int mca_runtime_suspend(struct device *dev) ++{ ++ struct s8000_mca *mca = dev_get_drvdata(dev); ++ ++ disable_irq(mca->irq); ++ mca_write(mca, MCA_IRQ_MASK, MCA_IRQ_ALL); ++ return 0; ++} ++ ++static int mca_runtime_resume(struct device *dev) ++{ ++ struct s8000_mca *mca = dev_get_drvdata(dev); ++ ++ /* ++ * The power domain was just switched on, so the MCA may be at its ++ * reset state, with causes like no-BCLK open in the hardware mask. ++ * Mask and clear everything before the IRQ is enabled. Prepare ++ * unmasks what a stream needs. ++ */ ++ mca->irq_mask = MCA_IRQ_ALL; ++ mca_write(mca, MCA_IRQ_MASK, MCA_IRQ_ALL); ++ mca_write(mca, MCA_IRQ_STATUS, MCA_IRQ_ALL); ++ enable_irq(mca->irq); ++ return 0; ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(mca_pm_ops, mca_runtime_suspend, ++ mca_runtime_resume, NULL); ++ ++/* --- debugfs: register snapshot for the device test ----------------------- */ ++ ++/* ++ * No debugfs access changes the power state or touches a suspended block: ++ * the MCA registers are read only while the device is already runtime ++ * active (stream running, or power/control = on). The MCLK word and the ++ * i2s-switch are read back only while the driver itself has them ++ * programmed. Their power domain is not known. ++ */ ++static int mca_regs_show(struct seq_file *s, void *unused) ++{ ++ struct s8000_mca *mca = dev_get_drvdata(s->private); ++ unsigned int off, val; ++ int dir; ++ ++ seq_printf(s, "irqs %lu errors %lu spurious %lu last status %#x irq_mask %#x\n", ++ mca->irq_count, mca->err_count, mca->spurious_count, ++ mca->last_status, mca->irq_mask); ++ seq_printf(s, "mclk word %#x (last written)%s%s, route %s\n", ++ mca->mclk_cur, mca->mclk_on ? ", clock on" : "", ++ mca->mclk_forced ? ", forced" : "", ++ mca->route_on ? "applied" : "off"); ++ seq_printf(s, "capture %s, prepared %#x running %#x (bit 0 playback, 1 capture)\n", ++ mca->capture ? "yes" : "no", mca->prep_mask, mca->run_mask); ++ for (dir = 0; dir < 2; dir++) { ++ const char *name = dir == SNDRV_PCM_STREAM_PLAYBACK ? ++ "playback" : "capture"; ++ ++ if (mca->last[dir].first_err_us >= 0) ++ seq_printf(s, "last %s stream: first error %lld us after start (status %#x), status at stop %#x\n", ++ name, mca->last[dir].first_err_us, ++ mca->last[dir].first_err_status, ++ mca->last[dir].stop_status); ++ else ++ seq_printf(s, "last %s stream: no error IRQ, status at stop %#x\n", ++ name, mca->last[dir].stop_status); ++ } ++ ++ if (pm_runtime_get_if_active(mca->dev) <= 0) { ++ seq_puts(s, "runtime-suspended: registers not read\n"); ++ return 0; ++ } ++ ++ for (off = 0; off < 0x100; off += 4) ++ seq_printf(s, "mca+%#05x = %#010x\n", off, mca_read(mca, off)); ++ if (mca->mclk_on || mca->mclk_forced) ++ seq_printf(s, "mclk = %#010x\n", readl(mca->mclk_reg)); ++ if (mca->sw && mca->route_on) { ++ for (off = 0; off < 0x50; off += 4) { ++ regmap_read(mca->sw, off, &val); ++ seq_printf(s, "sw+%#04x = %#010x\n", off, val); ++ } ++ } ++ ++ pm_runtime_put(mca->dev); ++ return 0; ++} ++ ++/* ++ * debugfs s8000-mca-mclk: "1"/"0" or a raw word forces MCLK for pad tests. ++ * Forcing holds a runtime PM reference, so the MCA domain is on (and its ++ * IRQ masked by runtime resume) before the word is written. ++ */ ++static ssize_t mca_mclk_write(struct file *f, const char __user *ubuf, ++ size_t len, loff_t *ppos) ++{ ++ struct s8000_mca *mca = f->private_data; ++ u32 val; ++ int ret; ++ ++ ret = kstrtou32_from_user(ubuf, len, 0, &val); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&mca->dbg_lock); ++ if (val && !mca->mclk_forced) { ++ ret = pm_runtime_resume_and_get(mca->dev); ++ if (ret) ++ return ret; ++ mca->mclk_forced = true; ++ } ++ if (val) { ++ mca->mclk_cur = val == 1 ? mca_mclk_word(mca) : val; ++ writel(mca->mclk_cur, mca->mclk_reg); ++ } else if (mca->mclk_forced) { ++ /* back to what the clock wants: its word, or off */ ++ mca->mclk_cur = mca->mclk_on ? mca_mclk_word(mca) : 0; ++ writel(mca->mclk_cur, mca->mclk_reg); ++ mca->mclk_forced = false; ++ pm_runtime_put(mca->dev); ++ } ++ return len; ++} ++ ++/* no MMIO: reports the last written word */ ++static ssize_t mca_mclk_read(struct file *f, char __user *ubuf, size_t len, ++ loff_t *ppos) ++{ ++ struct s8000_mca *mca = f->private_data; ++ char buf[48]; ++ int n; ++ ++ n = scnprintf(buf, sizeof(buf), "forced %d word %#x\n", ++ mca->mclk_forced, mca->mclk_cur); ++ return simple_read_from_buffer(ubuf, len, ppos, buf, n); ++} ++ ++static const struct file_operations mca_mclk_fops = { ++ .open = simple_open, ++ .read = mca_mclk_read, ++ .write = mca_mclk_write, ++ .llseek = default_llseek, ++}; ++ ++/* --- probe ---------------------------------------------------------------- */ ++ ++static int s8000_mca_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; ++ struct clk_init_data init = {}; ++ const char *clk_name = "mca-mclk"; ++ struct s8000_mca *mca; ++ u32 ver, fifo; ++ int ret, n; ++ ++ mca = devm_kzalloc(dev, sizeof(*mca), GFP_KERNEL); ++ if (!mca) ++ return -ENOMEM; ++ mca->dev = dev; ++ spin_lock_init(&mca->lock); ++ spin_lock_init(&mca->hw_lock); ++ ret = devm_mutex_init(dev, &mca->dbg_lock); ++ if (ret) ++ return ret; ++ platform_set_drvdata(pdev, mca); ++ ++ mca->base = devm_platform_ioremap_resource_byname(pdev, "mca"); ++ if (IS_ERR(mca->base)) ++ return PTR_ERR(mca->base); ++ mca->mclk_reg = devm_platform_ioremap_resource_byname(pdev, "mclk"); ++ if (IS_ERR(mca->mclk_reg)) ++ return PTR_ERR(mca->mclk_reg); ++ ++ mca->sw = syscon_regmap_lookup_by_phandle_optional(np, "apple,i2s-switch"); ++ if (IS_ERR(mca->sw)) ++ return dev_err_probe(dev, PTR_ERR(mca->sw), "no i2s-switch\n"); ++ n = of_property_count_u32_elems(np, "apple,i2s-switch-route"); ++ if (mca->sw && n > 0) { ++ mca->sw_route = devm_kcalloc(dev, n, sizeof(u32), GFP_KERNEL); ++ if (!mca->sw_route) ++ return -ENOMEM; ++ of_property_read_u32_array(np, "apple,i2s-switch-route", ++ mca->sw_route, n); ++ mca->sw_route_len = n; ++ } ++ ++ mca->mclk_ctl = 0x58; ++ of_property_read_u32(np, "apple,mclk-control", &mca->mclk_ctl); ++ mca->mclk_rate = 12000000; ++ of_property_read_u32(np, "apple,mclk-frequency", &mca->mclk_rate); ++ of_property_read_u32(np, "apple,pio-fifo-offset", &mca->pio_off); ++ mca->pio_burst = 32; ++ of_property_read_u32(np, "apple,pio-burst", &mca->pio_burst); ++ if (mca->pio_off >= 0x4000 || (mca->pio_off & 3)) ++ return dev_err_probe(dev, -EINVAL, "bad apple,pio-fifo-offset\n"); ++ ++ mca->irq_mask = MCA_IRQ_ALL; ++ mca->last[SNDRV_PCM_STREAM_PLAYBACK].first_err_us = -1; ++ mca->last[SNDRV_PCM_STREAM_CAPTURE].first_err_us = -1; ++ mca->irq = platform_get_irq(pdev, 0); ++ if (mca->irq < 0) ++ return mca->irq; ++ /* enabled by runtime resume, once the hardware mask is set */ ++ ret = devm_request_irq(dev, mca->irq, mca_irq, IRQF_NO_AUTOEN, ++ dev_name(dev), mca); ++ if (ret) ++ return ret; ++ ++ hrtimer_setup(&mca->timer, mca_timer_fn, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL_SOFT); ++ ++ ret = devm_pm_runtime_enable(dev); ++ if (ret) ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "power up failed\n"); ++ ver = mca_read(mca, MCA_VERSION); ++ fifo = mca_read(mca, MCA_FIFO_SIZE); ++ pm_runtime_put(dev); ++ ++ mca->fifo_tx = FIELD_GET(MCA_FIFO_SIZE_TX, fifo); ++ mca->use_dma = of_property_present(np, "dmas") && !nodma; ++ /* the generic PCM fails the card if a DAI stream has no channel */ ++ mca->capture = mca->use_dma && ++ of_property_match_string(np, "dma-names", "rx") >= 0; ++ dev_info(dev, "MCA version %u, FIFO tx %u rx %u, %s%s\n", ver, ++ mca->fifo_tx, (u32)FIELD_GET(MCA_FIFO_SIZE_RX, fifo), ++ mca->use_dma ? "SmartIO DMA" : ++ mca->pio_off ? "PIO (experimental)" : "timer PCM, no data path", ++ mca->capture ? ", capture" : ""); ++ if (ver != 4 || mca->fifo_tx != 0x40) ++ dev_warn(dev, "expected version 4 / FIFO 0x40 (iOS)\n"); ++ if (mca->fifo_tx < 8) ++ mca->fifo_tx = 0x40; ++ ++ /* MCLK control word -> codec MCLK */ ++ of_property_read_string(np, "clock-output-names", &clk_name); ++ init.name = clk_name; ++ init.ops = &mca_mclk_ops; ++ mca->mclk_hw.init = &init; ++ ret = devm_clk_hw_register(dev, &mca->mclk_hw); ++ if (ret) ++ return ret; ++ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &mca->mclk_hw); ++ if (ret) ++ return ret; ++ ++ /* MCA0 TX in Apple's ADT dma-channels: 0x28 {0x21|0x22, 0, 0x300, 0x180} */ ++ mca->sio_dt[0] = 0x21; ++ mca->sio_dt[1] = 0x22; ++ mca->sio_dt[2] = 0; ++ mca->sio_dt[3] = 0x300; ++ mca->sio_dt[4] = 0x180; ++ of_property_read_u32_array(np, "apple,sio-dma-config", mca->sio_dt, ++ ARRAY_SIZE(mca->sio_dt)); ++ ++ mca->dai_drv = mca_dai; ++ if (!mca->capture) ++ memset(&mca->dai_drv.capture, 0, sizeof(mca->dai_drv.capture)); ++ ++ ret = devm_snd_soc_register_component(dev, mca->use_dma ? ++ &mca_component_dma : ++ &mca_component_pio, ++ &mca->dai_drv, 1); ++ if (ret) ++ return ret; ++ ++ if (mca->use_dma) { ++ ret = devm_snd_dmaengine_pcm_register(dev, &mca_dma_config, 0); ++ if (ret) ++ return dev_err_probe(dev, ret, "dmaengine PCM failed\n"); ++ } ++ ++ debugfs_create_devm_seqfile(dev, "s8000-mca-regs", NULL, mca_regs_show); ++ debugfs_create_file("s8000-mca-mclk", 0600, NULL, mca, &mca_mclk_fops); ++ return 0; ++} ++ ++static const struct of_device_id s8000_mca_of_match[] = { ++ { .compatible = "apple,s8000-mca" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, s8000_mca_of_match); ++ ++static struct platform_driver s8000_mca_driver = { ++ .driver = { ++ .name = "s8000-mca", ++ .of_match_table = s8000_mca_of_match, ++ .pm = pm_ptr(&mca_pm_ops), ++ }, ++ .probe = s8000_mca_probe, ++}; ++module_platform_driver(s8000_mca_driver); ++ ++MODULE_DESCRIPTION("Apple S8000 (A9) MCA I2S driver"); ++MODULE_LICENSE("Dual MIT/GPL"); +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +index f9a47e262a77cbba6f18bde10b2d1a8ff2bfe2e8..0c5db937c63466bfb2742a13f1bed23d092fc94f 100644 +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -1032,6 +1032,14 @@ config SND_SOC_CS42L73 + tristate "Cirrus Logic CS42L73 CODEC" + depends on I2C + ++config SND_SOC_CS42L81 ++ tristate "Cirrus Logic CS42L81 CODEC (Apple)" ++ depends on SPI_MASTER ++ select REGMAP ++ help ++ Apple-specific Cirrus Logic CS42L81 codec with integrated speaker ++ drivers, controlled over SPI (A9 iPad 5). ++ + config SND_SOC_CS42L83 + tristate "Cirrus Logic CS42L83 CODEC" + depends on I2C +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +index 3d122ace75adee2d9b4584e2357e49ed2a30037d..a23e9144d051a6e7636c149b28140dfc0f469a87 100644 +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -94,6 +94,7 @@ snd-soc-cs42l51-i2c-y := cs42l51-i2c.o + snd-soc-cs42l52-y := cs42l52.o + snd-soc-cs42l56-y := cs42l56.o + snd-soc-cs42l73-y := cs42l73.o ++snd-soc-cs42l81-y := cs42l81.o + snd-soc-cs42l83-i2c-y := cs42l83-i2c.o + snd-soc-cs42l84-y := cs42l84.o + snd-soc-cs4234-y := cs4234.o +@@ -538,6 +539,7 @@ obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o + obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o + obj-$(CONFIG_SND_SOC_CS42L56) += snd-soc-cs42l56.o + obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o ++obj-$(CONFIG_SND_SOC_CS42L81) += snd-soc-cs42l81.o + obj-$(CONFIG_SND_SOC_CS42L83) += snd-soc-cs42l83-i2c.o + obj-$(CONFIG_SND_SOC_CS42L84) += snd-soc-cs42l84.o + obj-$(CONFIG_SND_SOC_CS4234) += snd-soc-cs4234.o +diff --git a/sound/soc/codecs/cs42l81.c b/sound/soc/codecs/cs42l81.c +new file mode 100644 +index 0000000000000000000000000000000000000000..22ecfd104a62562a7c738bfae7188427b260088f +--- /dev/null ++++ b/sound/soc/codecs/cs42l81.c +@@ -0,0 +1,1613 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR MIT ++/* ++ * Cirrus Logic CS42L81 (Apple 338S-series) codec: speaker and headphone ++ * playback, built-in DMIC capture, headphone jack. ++ * ++ * The CS42L81 is an Apple-specific Cirrus codec with integrated speaker ++ * drivers, used on the A9 iPad 5 (J71t). There is no public datasheet; the ++ * register sequences below were reconstructed from iOS 16.7.10's ++ * AppleCS42L81Audio driver (see docs/audio/playback-path.md and ++ * docs/audio/ds-capture-and-jack.md in omarchy-ipad5). Register names are ++ * descriptive guesses. ++ * ++ * Control port: SPI, 16-bit register address (page << 8 | reg), 8-bit ++ * data, framed as write: 6C page reg 00 val ++ * read: 6D page reg 00 xx (value in the 5th byte) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define CS42L81_SPI_WRITE 0x6c ++#define CS42L81_SPI_READ 0x6d ++ ++#define CS42L81_DEVID_AB 0x0001 /* reads 0x42 */ ++#define CS42L81_DEVID_CD 0x0002 /* reads 0xa8 */ ++#define CS42L81_DEVID_E 0x0003 /* reads 0x1x */ ++#define CS42L81_REVID 0x0005 ++#define CS42L81_PWR_CTL1 0x0006 ++#define CS42L81_PWR_CTL1_PDN BIT(0) ++#define CS42L81_PWR_CTL1_BIT2 BIT(2) ++#define CS42L81_PWR_CTL1_FREEZE BIT(6) /* set after PDN_DONE */ ++#define CS42L81_PWR_CTL1_REVA0 BIT(7) ++#define CS42L81_PWR_CTL2 0x0007 ++/* ++ * Bits 1/3/5 power the bias of the analog mics mic1/mic3/mic4. iOS sets ++ * them only for mics with a "-bias" or "-intmic" ADT property, ++ * so on J71t (DMICs only) it never touches them. ++ */ ++#define CS42L81_ADC_PWR 0x000d ++/* iOS updateRouting: 7 cleared for output, 6 for input, 5 for either */ ++#define CS42L81_PATH_PWR 0x000e ++#define CS42L81_PATH_PWR_DAC_PDN BIT(7) ++#define CS42L81_PATH_PWR_ADC_PDN BIT(6) ++#define CS42L81_PATH_PWR_COMMON_PDN BIT(5) ++#define CS42L81_CLK_CTL 0x000f /* [3:0] rate code, bit 7 see below */ ++/* ++ * iOS enableAudio writes CLK_CTL[7:6,3:0] = 0x80 when the IIS link's ++ * isMaster() is false (codec provides BCLK/LRCK from MCLK, J71t) and 0x0c ++ * when the SoC is clock master (AppleCS42L81Audio flag +0x1ec bit 0 = ++ * !AppleARMIISDevice::isMaster() = !(iisConfig[0] & 1)). ++ */ ++#define CS42L81_CLK_CTL_CODEC_MASTER 0x80 ++#define CS42L81_CLK_CTL_CODEC_SLAVE 0x0c ++#define CS42L81_ASP_PWR 0x0011 /* bit 7 ASP RX PDN, 6 ASP TX PDN */ ++#define CS42L81_ASP_PWR_RX_PDN BIT(7) ++#define CS42L81_ASP_PWR_TX_PDN BIT(6) ++#define CS42L81_ASP_CLK 0x0012 /* bit 7 ASP clock master, [3:0] rate */ ++/* ASP TX slot n: byte offset in the frame, bit 7 = slot unused (no width) */ ++#define CS42L81_ASP_TX_SLOT(n) (0x0014 + (n)) /* n = 0..9 */ ++#define CS42L81_ASP_TX_SLOT_EMPTY BIT(7) ++/* ASP TX byte-lane enable mask, frame byte 0 = bit 0 of 0x0021 */ ++#define CS42L81_ASP_TX_EN(n) (0x001e + (n)) /* n = 0..3, MSB first */ ++#define CS42L81_ASP_RX_SLOT(n) (0x0022 + (n)) /* n = 0..9 */ ++#define CS42L81_INT_STATUS1 0x002e ++#define CS42L81_INT_STATUS2 0x002f ++#define CS42L81_INT_STATUS2_PDN_DONE BIT(7) ++#define CS42L81_INT_STATUS3 0x0033 ++#define CS42L81_INT_STATUS4 0x007b ++#define CS42L81_INT_STATUS5 0x007c ++#define CS42L81_INT_STATUS6 0x0106 ++/* ASP TX slot n source: 0x18 = ADC A, 0x1c = ADC B (0x20/0x24 = DMIC2A/B) */ ++#define CS42L81_ASP_TX_SRC(n) (0x0115 + (n)) /* n = 0..9 */ ++/* ADC channel n source, same codes as ASP_TX_SRC */ ++#define CS42L81_ADC_SRC(n) (0x011f + (n)) /* n = 0..1 */ ++#define CS42L81_SRC_RATE_A 0x0123 ++#define CS42L81_ASP_TX_PAIR_RATE(p) (0x0124 + (p)) /* slot pair p = 0..4 */ ++#define CS42L81_ASP_PAIR_RATE(p) (0x0129 + (p)) /* channel pair p = 0..4 */ ++#define CS42L81_ASP_TX_RATE 0x012e ++#define CS42L81_ASP_RX_RATE 0x012f ++#define CS42L81_ASP_CTL 0x0131 ++#define CS42L81_OUT_CTL 0x0201 ++/* ++ * ADC input select. iOS starts at 0x04 and, per DMIC route, clears bit 2 ++ * and toggles 0x40 (dmic1a) / 0x80 (dmic1b); 0xc4 is its no-input value. ++ */ ++#define CS42L81_ADC_ROUTE 0x0203 ++#define CS42L81_ADC_ROUTE_NONE 0xc4 ++#define CS42L81_ADC_ROUTE_DMIC1 0xc0 ++#define CS42L81_ADC_CTL_A 0x0204 /* [1:0] ADC A +20 dB boost (inferred) */ ++#define CS42L81_ADC_RATE_A 0x0205 /* [3:0] rate, [7:4] analog inputs */ ++#define CS42L81_ADC_PGA_A 0x0206 /* analog PGA, 0 for DMICs */ ++#define CS42L81_ADC_PGA_B 0x0207 ++#define CS42L81_ADC_VOL_A 0x0208 /* signed, 1 dB (inferred), 0x80 mute */ ++#define CS42L81_ADC_CTL_B 0x020f ++#define CS42L81_ADC_CTL_C 0x0210 /* [1:0] ADC B boost (inferred) */ ++#define CS42L81_ADC_RATE_B 0x0211 ++#define CS42L81_ADC_VOL_B 0x0212 ++#define CS42L81_ADC_BOOST GENMASK(1, 0) ++#define CS42L81_ADC_VOL_MUTE 0x80 ++/* DMIC1 (1A + 1B) control: bit 7 = iOS control 0x188, [6:4] by rate */ ++#define CS42L81_DMIC1_CTL 0x0215 ++#define CS42L81_DMIC2_CTL 0x0216 ++#define CS42L81_HPCP_CTL 0x0219 ++#define CS42L81_DRV_PDN 0x0220 /* bit 5 HP PDN, bit 3 line PDN */ ++#define CS42L81_DRV_PDN_HP BIT(5) ++#define CS42L81_DRV_PDN_LINE BIT(3) ++#define CS42L81_DRV_CTL1 0x0221 ++#define CS42L81_DAC_RATE 0x0222 ++#define CS42L81_DRV_CTL2 0x0223 ++#define CS42L81_DRV_CTL3 0x0224 ++#define CS42L81_SPK_VOL_A 0x0225 ++#define CS42L81_SPK_VOL_B 0x0226 ++#define CS42L81_HP_VOL_A 0x0227 ++#define CS42L81_HP_VOL_B 0x0228 ++#define CS42L81_LINE_VOL_A 0x0229 ++#define CS42L81_LINE_VOL_B 0x022a ++#define CS42L81_SPK_PDN 0x022e /* bit 4 speaker B PDN, bit 3 speaker A PDN */ ++#define CS42L81_SPK_PDN_A BIT(3) ++#define CS42L81_SPK_PDN_B BIT(4) ++#define CS42L81_SPK_RATE 0x022f ++#define CS42L81_DRV_CTL4 0x0230 ++#define CS42L81_SPK_MODE 0x0231 /* [5:4] */ ++#define CS42L81_SPK_MODE_MASK GENMASK(5, 4) ++#define CS42L81_MAIN_VOL 0x0232 ++#define CS42L81_INT_STATUS7 0x0528 ++#define CS42L81_TRIM1 0xc81f ++#define CS42L81_TRIM2 0xc85f ++#define CS42L81_TRIM3 0xc96f ++#define CS42L81_UNLOCK 0x9901 ++ ++#define CS42L81_VOL_MUTE 0x19 /* digital volume "mute" code */ ++#define CS42L81_VOL_OFFSET 0x34 /* control 0 = -102 dB */ ++#define CS42L81_VOL_MAX_CTL 228 /* +12 dB */ ++#define CS42L81_HP_MUTE 0x40 ++ ++/* Headphone control: 0.5 dB steps, 0 = -50 dB, 100 = 0 dB, 124 = +12 dB */ ++#define CS42L81_HP_VOL_0DB 100 ++#define CS42L81_HP_VOL_MAX 124 ++#define CS42L81_HP_VOL_DEFAULT 40 /* -30 dB */ ++/* iOS keeps the HP charge pump off at or below -9 dB and while settling */ ++#define CS42L81_HP_CP_MIN_DB (-9) ++ ++/* Capture control: 1 dB steps (inferred), 0 = -96 dB, 96 = 0 dB, +32 dB max */ ++#define CS42L81_ADC_VOL_0DB 96 ++#define CS42L81_ADC_VOL_MAX 128 ++ ++#define CS42L81_PDN_POLL_US 10000 ++#define CS42L81_PDN_TIMEOUT_US 1000000 /* iOS: 100 x 10 ms */ ++ ++/* ++ * Headphone jack (docs/audio/ds-capture-and-jack.md section 3). iOS polls ++ * the plug state on a timer; the codec status bit is not known yet, so ++ * polling is off by default and, without jack_reg, only logs which status ++ * bits change (test T1). ++ */ ++static unsigned int jack_poll_ms; ++module_param(jack_poll_ms, uint, 0644); ++MODULE_PARM_DESC(jack_poll_ms, ++ "poll the codec status registers every N ms (0 = off, min 100)"); ++static unsigned int jack_reg; ++module_param(jack_reg, uint, 0644); ++MODULE_PARM_DESC(jack_reg, "status register with the plug bit (0 = unknown, log only)"); ++static unsigned int jack_mask; ++module_param(jack_mask, uint, 0644); ++MODULE_PARM_DESC(jack_mask, "plug bit(s) in jack_reg"); ++static bool jack_invert; ++module_param(jack_invert, bool, 0644); ++MODULE_PARM_DESC(jack_invert, "plug bit is set while unplugged"); ++/* iOS reads control 0x188 with default 1; J71t publishes no such control */ ++static bool dmic_bit7 = true; ++module_param(dmic_bit7, bool, 0644); ++MODULE_PARM_DESC(dmic_bit7, "set DMIC1 control (0x0215) bit 7 at capture start"); ++ ++#define CS42L81_JACK_POLL_MIN_MS 100 ++#define CS42L81_JACK_DEBOUNCE 2 /* equal polls before a report */ ++ ++/* latched (read-to-clear) status registers, as iOS enableAudio clears them */ ++static const u16 cs42l81_status_regs[] = { ++ 0x002e, 0x002f, 0x0033, 0x007b, 0x007c, 0x0106, 0x0528, ++}; ++ ++/* ASP TX slot 0/1 and ADC channel 0/1 sources: ADC A (DMIC1A), ADC B (DMIC1B) */ ++static const u8 cs42l81_adc_src[2] = { 0x18, 0x1c }; ++ ++struct cs42l81 { ++ struct device *dev; ++ struct spi_device *spi; ++ struct regmap *regmap; ++ struct clk *mclk; ++ struct gpio_desc *mute_gpio; ++ struct gpio_descs *hs_switch; /* ext_sw1/2, read only */ ++ u8 *xfer_buf; /* 2 x 5 bytes, DMA-safe */ ++ ++ unsigned int rate_code; ++ unsigned int slot_bytes; ++ unsigned int spk_vol; /* control value, 0..vol_max */ ++ unsigned int vol_max; ++ bool spk_switch; ++ bool spk_on; ++ bool core_on; ++ bool clk_provider; /* codec drives BCLK/LRCK (set_fmt) */ ++ unsigned int dbg_reg; /* debugfs cs42l81-reg selection */ ++ u32 spk_mode; /* 2 = stereo speakers (both outputs) */ ++ ++ /* headphones */ ++ unsigned int hp_vol; /* control value, 0..CS42L81_HP_VOL_MAX */ ++ unsigned int hp_switch; ++ bool hp_on; ++ bool hp_settled; /* 60 ms after the HP driver came up */ ++ bool hp_cp_on; /* charge pump */ ++ ++ /* capture */ ++ unsigned int adc_vol; /* control value, 0..CS42L81_ADC_VOL_MAX */ ++ unsigned int adc_switch; ++ bool adc_on; ++ ++ /* jack */ ++ struct snd_soc_component *component; ++ struct snd_soc_jack jack; ++ struct delayed_work jack_work; ++ struct mutex status_lock; /* read-to-clear status vs PDN_DONE poll */ ++ struct mutex jack_lock; /* jack_* below, reports */ ++ bool jack_ready; ++ int jack_forced; /* -1 = detect, else forced state */ ++ bool jack_state; /* last reported */ ++ bool jack_pending; ++ unsigned int jack_stable; ++ u8 status_last[ARRAY_SIZE(cs42l81_status_regs)]; ++ u8 status_seen[ARRAY_SIZE(cs42l81_status_regs)]; ++}; ++ ++/* --- SPI regmap bus ------------------------------------------------------ */ ++ ++static int cs42l81_bus_write(void *context, unsigned int reg, unsigned int val) ++{ ++ struct cs42l81 *cs = context; ++ u8 *tx = cs->xfer_buf; ++ ++ tx[0] = CS42L81_SPI_WRITE; ++ tx[1] = reg >> 8; ++ tx[2] = reg & 0x7f; ++ tx[3] = 0; ++ tx[4] = val; ++ ++ return spi_write(cs->spi, tx, 5); ++} ++ ++static int cs42l81_bus_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ struct cs42l81 *cs = context; ++ u8 *tx = cs->xfer_buf, *rx = cs->xfer_buf + 5; ++ struct spi_transfer t = { ++ .tx_buf = tx, ++ .rx_buf = rx, ++ .len = 5, ++ }; ++ int ret; ++ ++ tx[0] = CS42L81_SPI_READ; ++ tx[1] = reg >> 8; ++ tx[2] = reg & 0x7f; ++ tx[3] = 0; ++ tx[4] = 0; ++ memset(rx, 0, 5); ++ ++ ret = spi_sync_transfer(cs->spi, &t, 1); ++ if (ret) ++ return ret; ++ ++ *val = rx[4]; ++ return 0; ++} ++ ++static const struct regmap_bus cs42l81_bus = { ++ .reg_write = cs42l81_bus_write, ++ .reg_read = cs42l81_bus_read, ++}; ++ ++static bool cs42l81_valid_reg(struct device *dev, unsigned int reg) ++{ ++ /* The register byte is 7 bits wide on the wire. */ ++ return !(reg & 0x80); ++} ++ ++static const struct regmap_config cs42l81_regmap = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = 0xffff, ++ .readable_reg = cs42l81_valid_reg, ++ .writeable_reg = cs42l81_valid_reg, ++ .cache_type = REGCACHE_NONE, ++}; ++ ++/* --- helpers -------------------------------------------------------------- */ ++ ++static int cs42l81_rate_code(unsigned int rate) ++{ ++ switch (rate) { ++ case 8000: return 0x1; ++ case 11025: return 0x2; ++ case 12000: return 0x4; ++ case 16000: return 0x5; ++ case 22050: return 0x6; ++ case 24000: return 0x8; ++ case 32000: return 0x9; ++ case 44100: return 0xa; ++ case 48000: return 0xc; ++ default: return -EINVAL; ++ } ++} ++ ++static void cs42l81_set_rate(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int c = cs->rate_code; ++ ++ /* iOS setSampleRate (vtable +0x650) */ ++ regmap_update_bits(map, CS42L81_CLK_CTL, 0x0f, c); ++ regmap_write(map, CS42L81_ASP_TX_RATE, c | c << 4); ++ regmap_write(map, CS42L81_ASP_RX_RATE, c | c << 4); ++ regmap_write(map, CS42L81_SRC_RATE_A, c); ++ regmap_write(map, CS42L81_DAC_RATE, c); ++ regmap_update_bits(map, CS42L81_ADC_RATE_A, 0x0f, c); ++ regmap_update_bits(map, CS42L81_ADC_RATE_B, 0x0f, c); ++ regmap_write(map, CS42L81_SPK_RATE, c); ++} ++ ++static void cs42l81_set_asp(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int c = cs->rate_code, b = cs->slot_bytes, n; ++ ++ /* Codec drives BCLK/LRCK (MCA0 is the clock consumer). */ ++ regmap_write(map, CS42L81_ASP_CLK, 0x80 | c); ++ ++ /* ASP RX slot n: byte offset n*b, width b bytes */ ++ for (n = 0; n < 10; n++) ++ regmap_write(map, CS42L81_ASP_RX_SLOT(n), ++ ((n * b) | ((b - 1) << 5)) & 0xff); ++ for (n = 0; n < 5; n++) ++ regmap_write(map, CS42L81_ASP_PAIR_RATE(n), c | c << 4); ++} ++ ++static void cs42l81_apply_spk_volume(struct cs42l81 *cs) ++{ ++ unsigned int v = CS42L81_VOL_MUTE; ++ ++ if (cs->spk_switch) ++ v = (cs->spk_vol + CS42L81_VOL_OFFSET) & 0xff; ++ ++ /* Speaker-only state, as iOS: per-speaker volume, main volume muted */ ++ regmap_write(cs->regmap, CS42L81_SPK_VOL_A, v); ++ regmap_write(cs->regmap, CS42L81_SPK_VOL_B, v); ++ regmap_write(cs->regmap, CS42L81_MAIN_VOL, CS42L81_VOL_MUTE); ++} ++ ++/* --- capture: DMIC1A/1B -> ADC A/B -> ASP TX slots 0/1 -------------------- */ ++ ++/* iOS updateRouting: 0x0215 = control 0x188 << 7 | divider from the rate */ ++static unsigned int cs42l81_dmic_ctl(struct cs42l81 *cs) ++{ ++ unsigned int div; ++ ++ switch (cs->rate_code) { ++ case 0x1 ... 0x8: ++ div = 0x70; /* 8..24 kHz */ ++ break; ++ case 0x9: ++ div = 0x40; /* 32 kHz */ ++ break; ++ case 0xa: ++ case 0xb: ++ div = 0x10; /* 44.1 kHz */ ++ break; ++ default: ++ div = 0; /* 48 kHz: iOS writes no divider */ ++ break; ++ } ++ return (READ_ONCE(dmic_bit7) ? 0x80 : 0) | div; ++} ++ ++/* ++ * iOS DMIC gain (control 0x170, 0x60 = 0 dB): from +12 dB on the ADC ++ * +20 dB boost is on and the digital volume carries the rest. ++ */ ++static void cs42l81_apply_adc_volume(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ int db = (int)cs->adc_vol - CS42L81_ADC_VOL_0DB; ++ unsigned int boost = 0, vol = CS42L81_ADC_VOL_MUTE; ++ ++ if (cs->adc_switch) { ++ if (db >= 12) { ++ boost = CS42L81_ADC_BOOST; ++ db -= 20; ++ } ++ vol = db & 0xff; ++ } ++ ++ regmap_update_bits(map, CS42L81_ADC_CTL_A, 0x73, boost); ++ regmap_write(map, CS42L81_ADC_PGA_A, 0); ++ regmap_write(map, CS42L81_ADC_PGA_B, 0); ++ regmap_write(map, CS42L81_ADC_VOL_A, vol); ++ regmap_update_bits(map, CS42L81_ADC_CTL_C, 0x43, boost); ++ regmap_write(map, CS42L81_ADC_VOL_B, vol); ++} ++ ++/* iOS updateRouting, input slots: ADC A/B in slots 0/1, slots 2..9 unused */ ++static void cs42l81_set_asp_tx(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int c = cs->rate_code, b = cs->slot_bytes, n; ++ u32 lanes = GENMASK(2 * b - 1, 0); /* frame bytes of slots 0/1 */ ++ ++ for (n = 0; n < 10; n++) { ++ regmap_write(map, CS42L81_ASP_TX_SLOT(n), ++ n * b | (n < 2 ? 0 : CS42L81_ASP_TX_SLOT_EMPTY)); ++ regmap_write(map, CS42L81_ASP_TX_PAIR_RATE(n / 2), c | c << 4); ++ if (n < 2) ++ regmap_write(map, CS42L81_ASP_TX_SRC(n), ++ cs42l81_adc_src[n]); ++ } ++ /* iOS writes these as one 4-byte burst of the byte-swapped mask */ ++ for (n = 0; n < 4; n++) ++ regmap_write(map, CS42L81_ASP_TX_EN(n), ++ (lanes >> (24 - 8 * n)) & 0xff); ++} ++ ++/* iOS updateRouting with the input stream active, routes dmic1a + dmic1b */ ++static void cs42l81_capture_power_up(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int ch; ++ ++ cs42l81_set_asp_tx(cs); ++ for (ch = 0; ch < 2; ch++) { ++ /* digital input: no analog input select bits */ ++ regmap_update_bits(map, CS42L81_ADC_RATE_A, ch ? 0xc0 : 0x30, 0); ++ regmap_write(map, CS42L81_ADC_SRC(ch), cs42l81_adc_src[ch]); ++ } ++ cs42l81_apply_adc_volume(cs); ++ /* CS42L81_ADC_PWR (analog mic bias) stays as it is, as on iOS */ ++ regmap_write(map, CS42L81_ADC_ROUTE, CS42L81_ADC_ROUTE_DMIC1); ++ regmap_write(map, CS42L81_ADC_CTL_B, 0x04); ++ regmap_clear_bits(map, CS42L81_ASP_PWR, CS42L81_ASP_PWR_TX_PDN); ++ regmap_set_bits(map, CS42L81_ASP_CTL, 0x01); ++ regmap_write(map, CS42L81_DMIC1_CTL, cs42l81_dmic_ctl(cs)); ++ regmap_clear_bits(map, CS42L81_PATH_PWR, CS42L81_PATH_PWR_ADC_PDN | ++ CS42L81_PATH_PWR_COMMON_PDN); ++ cs->adc_on = true; ++} ++ ++/* iOS updateRouting once the input stream is gone; 0x0215 is left as is */ ++static void cs42l81_capture_power_down(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ ++ cs->adc_on = false; ++ regmap_write(map, CS42L81_ADC_ROUTE, CS42L81_ADC_ROUTE_NONE); ++ regmap_write(map, CS42L81_ADC_CTL_B, 0x04); ++ regmap_set_bits(map, CS42L81_ASP_PWR, CS42L81_ASP_PWR_TX_PDN); ++ regmap_set_bits(map, CS42L81_PATH_PWR, CS42L81_PATH_PWR_ADC_PDN); ++} ++ ++/* --- headphones ------------------------------------------------------------ */ ++ ++static void cs42l81_hp_cp_on(struct cs42l81 *cs) ++{ ++ /* iOS setHeadphoneVolume */ ++ regmap_write(cs->regmap, CS42L81_TRIM3, 0x0e); ++ regmap_write(cs->regmap, CS42L81_HPCP_CTL, 0x01); ++ usleep_range(155, 200); /* iOS IODelay(155) */ ++ regmap_write(cs->regmap, CS42L81_TRIM3, 0x1e); ++ cs->hp_cp_on = true; ++} ++ ++static void cs42l81_hp_cp_off(struct cs42l81 *cs) ++{ ++ /* iOS updateRouting with the headphone route gone */ ++ regmap_write(cs->regmap, CS42L81_TRIM3, 0x0e); ++ usleep_range(1000, 2000); ++ regmap_write(cs->regmap, CS42L81_HPCP_CTL, 0x00); ++ cs->hp_cp_on = false; ++} ++ ++/* ++ * iOS setHeadphoneVolume without a line out (J71t): the HP driver gain in ++ * 1 dB steps (0x0227, 7-bit signed, 0x40 = mute) plus a 0.5 dB trim in the ++ * digital volume (0x0225). Until the driver has settled, and at or below ++ * -9 dB, the charge pump stays off and the gain is at most -9 dB. ++ */ ++static void cs42l81_apply_hp_volume(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ int g = (int)cs->hp_vol - CS42L81_HP_VOL_0DB; /* 0.5 dB */ ++ int gain = g / 2, trim = g - 2 * gain; ++ ++ if (!cs->hp_on) ++ return; ++ ++ if (!cs->hp_switch) { ++ regmap_write(map, CS42L81_HP_VOL_A, CS42L81_HP_MUTE); ++ return; ++ } ++ ++ regmap_write(map, CS42L81_SPK_VOL_A, trim & 0xff); ++ /* iOS leaves 0x0226 alone here; keep both channels equal (inferred) */ ++ regmap_write(map, CS42L81_SPK_VOL_B, trim & 0xff); ++ if (gain > CS42L81_HP_CP_MIN_DB) { ++ if (!cs->hp_settled) ++ gain = CS42L81_HP_CP_MIN_DB; ++ else if (!cs->hp_cp_on) ++ cs42l81_hp_cp_on(cs); ++ } ++ regmap_write(map, CS42L81_HP_VOL_A, gain & 0x7f); ++} ++ ++/* --- power sequences (DAPM) ---------------------------------------------- */ ++ ++static int cs42l81_core_power_up(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int rev, val; ++ int ret; ++ ++ ret = clk_prepare_enable(cs->mclk); ++ if (ret) { ++ dev_err(cs->dev, "could not enable MCLK: %d\n", ret); ++ return ret; ++ } ++ ++ /* iOS enableAudio (vtable +0x600), cold path */ ++ regmap_write(map, CS42L81_UNLOCK, 0xa5); ++ msleep(10); ++ regmap_write(map, CS42L81_UNLOCK, 0x00); ++ ++ /* read to clear latched status */ ++ mutex_lock(&cs->status_lock); ++ regmap_read(map, CS42L81_INT_STATUS1, &val); ++ regmap_read(map, CS42L81_INT_STATUS2, &val); ++ regmap_read(map, CS42L81_INT_STATUS3, &val); ++ regmap_read(map, CS42L81_INT_STATUS4, &val); ++ regmap_read(map, CS42L81_INT_STATUS5, &val); ++ regmap_read(map, CS42L81_INT_STATUS6, &val); ++ regmap_read(map, CS42L81_INT_STATUS7, &val); ++ mutex_unlock(&cs->status_lock); ++ ++ ret = regmap_read(map, CS42L81_REVID, &rev); ++ if (ret) ++ goto err; ++ if (rev == 0xa0) ++ regmap_set_bits(map, CS42L81_PWR_CTL1, CS42L81_PWR_CTL1_REVA0); ++ ++ regmap_clear_bits(map, CS42L81_PWR_CTL2, 0x80); ++ regmap_update_bits(map, CS42L81_CLK_CTL, 0xcf, ++ cs->clk_provider ? CS42L81_CLK_CTL_CODEC_MASTER : ++ CS42L81_CLK_CTL_CODEC_SLAVE); ++ regmap_clear_bits(map, CS42L81_ASP_CLK, 0xc0); ++ regmap_clear_bits(map, CS42L81_ASP_PWR, 0x10); ++ ++ regmap_update_bits(map, CS42L81_PWR_CTL1, ++ CS42L81_PWR_CTL1_PDN | CS42L81_PWR_CTL1_BIT2, ++ CS42L81_PWR_CTL1_BIT2); ++ ++ regmap_write(map, CS42L81_OUT_CTL, 0xe0); ++ regmap_write(map, CS42L81_HPCP_CTL, 0x00); ++ regmap_write(map, CS42L81_TRIM1, 0xff); ++ regmap_write(map, CS42L81_TRIM2, 0x0f); ++ regmap_write(map, CS42L81_TRIM3, 0x0e); ++ cs->hp_cp_on = false; ++ regmap_set_bits(map, CS42L81_DRV_CTL1, 0x20); ++ regmap_write(map, CS42L81_DRV_CTL2, 0x08); ++ regmap_write(map, CS42L81_DRV_CTL3, 0x09); ++ regmap_set_bits(map, CS42L81_ADC_CTL_A, 0x04); ++ regmap_set_bits(map, CS42L81_ADC_CTL_C, 0x04); ++ regmap_write(map, CS42L81_DRV_CTL4, 0x0c); ++ ++ cs42l81_set_rate(cs); ++ ++ /* updateRouting: playback-only state */ ++ cs42l81_set_asp(cs); ++ regmap_update_bits(map, CS42L81_ADC_PWR, 0x3f, 0); ++ regmap_write(map, CS42L81_ADC_ROUTE, 0xc4); ++ regmap_write(map, CS42L81_ADC_CTL_B, 0x04); ++ regmap_write(map, CS42L81_DRV_CTL2, 0x08); ++ regmap_write(map, CS42L81_DRV_CTL3, 0x09); ++ regmap_update_bits(map, CS42L81_PWR_CTL1, CS42L81_PWR_CTL1_BIT2, ++ CS42L81_PWR_CTL1_BIT2); ++ regmap_update_bits(map, CS42L81_ASP_PWR, 0xc0, 0x40); ++ regmap_set_bits(map, CS42L81_ASP_CTL, 0x01); ++ regmap_clear_bits(map, CS42L81_PATH_PWR, 0xa0); ++ regmap_set_bits(map, CS42L81_PATH_PWR, 0x40); ++ ++ cs->core_on = true; ++ /* latched status after power-up: clock-error bits help the bring-up */ ++ { ++ unsigned int st[4] = {}; ++ ++ mutex_lock(&cs->status_lock); ++ regmap_read(map, CS42L81_INT_STATUS1, &st[0]); ++ regmap_read(map, CS42L81_INT_STATUS2, &st[1]); ++ regmap_read(map, CS42L81_INT_STATUS3, &st[2]); ++ mutex_unlock(&cs->status_lock); ++ regmap_read(map, CS42L81_CLK_CTL, &st[3]); ++ dev_info(cs->dev, ++ "powered up: rev %#x clk_ctl %#x status 2e=%#x 2f=%#x 33=%#x\n", ++ rev, st[3], st[0], st[1], st[2]); ++ } ++ return 0; ++ ++err: ++ clk_disable_unprepare(cs->mclk); ++ return ret; ++} ++ ++static void cs42l81_core_power_down(struct cs42l81 *cs) ++{ ++ struct regmap *map = cs->regmap; ++ unsigned int val; ++ int ret; ++ ++ /* iOS disableAudio (vtable +0x608) */ ++ cs->core_on = false; ++ regmap_write(map, CS42L81_HP_VOL_A, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_HP_VOL_B, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_LINE_VOL_A, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_LINE_VOL_B, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_SPK_VOL_A, CS42L81_VOL_MUTE); ++ regmap_write(map, CS42L81_SPK_VOL_B, CS42L81_VOL_MUTE); ++ regmap_write(map, CS42L81_MAIN_VOL, CS42L81_VOL_MUTE); ++ regmap_write(map, CS42L81_DRV_PDN, 0x28); ++ regmap_write(map, CS42L81_SPK_PDN, 0x18); ++ ++ /* the jack poll must not consume PDN_DONE (read to clear) */ ++ mutex_lock(&cs->status_lock); ++ regmap_read(map, CS42L81_INT_STATUS2, &val); ++ if (val & CS42L81_INT_STATUS2_PDN_DONE) ++ dev_dbg(cs->dev, "PDN_DONE already set before power down\n"); ++ ++ regmap_update_bits(map, CS42L81_PWR_CTL1, ++ CS42L81_PWR_CTL1_PDN | CS42L81_PWR_CTL1_BIT2, ++ CS42L81_PWR_CTL1_PDN | CS42L81_PWR_CTL1_BIT2); ++ ++ ret = regmap_read_poll_timeout(map, CS42L81_INT_STATUS2, val, ++ val & CS42L81_INT_STATUS2_PDN_DONE, ++ CS42L81_PDN_POLL_US, ++ CS42L81_PDN_TIMEOUT_US); ++ mutex_unlock(&cs->status_lock); ++ if (ret) ++ dev_warn(cs->dev, "timed out waiting for codec shutdown\n"); ++ ++ regmap_set_bits(map, CS42L81_PWR_CTL1, CS42L81_PWR_CTL1_FREEZE); ++ msleep(10); ++ regmap_write(map, CS42L81_UNLOCK, 0xa5); ++ ++ clk_disable_unprepare(cs->mclk); ++} ++ ++static int cs42l81_core_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ return cs42l81_core_power_up(cs); ++ case SND_SOC_DAPM_POST_PMD: ++ cs42l81_core_power_down(cs); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int cs42l81_spk_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ struct regmap *map = cs->regmap; ++ unsigned int pdn = CS42L81_SPK_PDN_A | CS42L81_SPK_PDN_B; ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ /* ++ * iOS never drives speakers and headphones together (it mutes ++ * the speakers when the headphones take the volume); the jack ++ * pins keep the two exclusive, this only guards a manual mix. ++ */ ++ if (cs->hp_on) { ++ dev_warn(cs->dev, "headphones active, speakers stay off\n"); ++ break; ++ } ++ gpiod_set_value_cansleep(cs->mute_gpio, 1); ++ regmap_update_bits(map, CS42L81_SPK_MODE, CS42L81_SPK_MODE_MASK, ++ cs->spk_mode == 2 ? 0 : 0x10); ++ regmap_write(map, CS42L81_OUT_CTL, 0xe0); ++ regmap_update_bits(map, CS42L81_DRV_PDN, ++ CS42L81_DRV_PDN_HP | CS42L81_DRV_PDN_LINE, ++ CS42L81_DRV_PDN_HP | CS42L81_DRV_PDN_LINE); ++ regmap_update_bits(map, CS42L81_SPK_PDN, pdn, ++ cs->spk_mode == 2 ? 0 : CS42L81_SPK_PDN_B); ++ msleep(50); ++ cs->spk_on = true; ++ cs42l81_apply_spk_volume(cs); ++ /* iOS waits for the volume ramp: (v + 27) / 2 ms */ ++ msleep((cs->spk_vol + 27) / 2); ++ break; ++ case SND_SOC_DAPM_POST_PMU: ++ if (cs->spk_on) ++ gpiod_set_value_cansleep(cs->mute_gpio, 0); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ if (!cs->spk_on) ++ break; ++ gpiod_set_value_cansleep(cs->mute_gpio, 1); ++ cs->spk_on = false; ++ regmap_write(map, CS42L81_SPK_VOL_A, CS42L81_VOL_MUTE); ++ regmap_write(map, CS42L81_SPK_VOL_B, CS42L81_VOL_MUTE); ++ regmap_update_bits(map, CS42L81_SPK_PDN, pdn, pdn); ++ msleep(20); ++ break; ++ } ++ ++ return 0; ++} ++ ++/* iOS updateRouting: speaker route -> headphone route, and back */ ++static int cs42l81_hp_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ struct regmap *map = cs->regmap; ++ unsigned int pdn = CS42L81_SPK_PDN_A | CS42L81_SPK_PDN_B, old = pdn; ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ if (cs->spk_on) { ++ dev_warn(cs->dev, "speakers active, headphones stay off\n"); ++ break; ++ } ++ gpiod_set_value_cansleep(cs->mute_gpio, 1); ++ regmap_write(map, CS42L81_OUT_CTL, 0xe0); ++ /* HP driver on, line driver off */ ++ regmap_update_bits(map, CS42L81_DRV_PDN, ++ CS42L81_DRV_PDN_HP | CS42L81_DRV_PDN_LINE, ++ CS42L81_DRV_PDN_LINE); ++ /* speakers off; iOS drops OUT_CTL bit 5 around the change */ ++ regmap_read(map, CS42L81_SPK_PDN, &old); ++ if (~old & pdn) { ++ regmap_write(map, CS42L81_OUT_CTL, 0xc0); ++ regmap_update_bits(map, CS42L81_SPK_PDN, pdn, pdn); ++ regmap_write(map, CS42L81_OUT_CTL, 0xe0); ++ msleep(20); ++ } ++ cs->hp_on = true; ++ cs->hp_settled = false; ++ cs42l81_apply_hp_volume(cs); /* at most -9 dB, pump off */ ++ msleep(60); ++ cs->hp_settled = true; ++ cs42l81_apply_hp_volume(cs); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ if (!cs->hp_on) ++ break; ++ cs->hp_on = false; ++ cs->hp_settled = false; ++ regmap_write(map, CS42L81_LINE_VOL_A, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_HP_VOL_A, CS42L81_HP_MUTE); ++ regmap_write(map, CS42L81_SPK_VOL_A, CS42L81_VOL_MUTE); ++ regmap_write(map, CS42L81_SPK_VOL_B, CS42L81_VOL_MUTE); ++ regmap_update_bits(map, CS42L81_DRV_PDN, ++ CS42L81_DRV_PDN_HP | CS42L81_DRV_PDN_LINE, ++ CS42L81_DRV_PDN_HP | CS42L81_DRV_PDN_LINE); ++ msleep(20); ++ if (cs->hp_cp_on) ++ cs42l81_hp_cp_off(cs); ++ break; ++ } ++ ++ return 0; ++} ++ ++static int cs42l81_adc_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ cs42l81_capture_power_up(cs); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ cs42l81_capture_power_down(cs); ++ break; ++ } ++ ++ return 0; ++} ++ ++/* --- controls ------------------------------------------------------------- */ ++ ++/* 0.5 dB steps; control 0 = -102 dB. Max is limited by spkr-gain-db. */ ++static const DECLARE_TLV_DB_SCALE(cs42l81_spk_tlv, -10200, 50, 0); ++ ++static int cs42l81_spk_vol_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = 1; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = cs->vol_max; ++ return 0; ++} ++ ++static int cs42l81_spk_vol_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = cs->spk_vol; ++ return 0; ++} ++ ++static int cs42l81_spk_vol_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ long v = ucontrol->value.integer.value[0]; ++ ++ if (v < 0 || v > cs->vol_max) ++ return -EINVAL; ++ if (v == cs->spk_vol) ++ return 0; ++ ++ cs->spk_vol = v; ++ if (cs->spk_on) ++ cs42l81_apply_spk_volume(cs); ++ return 1; ++} ++ ++static int cs42l81_spk_sw_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = cs->spk_switch; ++ return 0; ++} ++ ++static int cs42l81_spk_sw_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ bool on = !!ucontrol->value.integer.value[0]; ++ ++ if (on == cs->spk_switch) ++ return 0; ++ ++ cs->spk_switch = on; ++ if (cs->spk_on) ++ cs42l81_apply_spk_volume(cs); ++ return 1; ++} ++ ++/* Headphone and capture controls: the soc_mixer_control reg is the field */ ++enum { ++ CS42L81_CTL_HP_VOL, ++ CS42L81_CTL_HP_SW, ++ CS42L81_CTL_ADC_VOL, ++ CS42L81_CTL_ADC_SW, ++}; ++ ++static unsigned int *cs42l81_ctl_field(struct cs42l81 *cs, unsigned int id) ++{ ++ switch (id) { ++ case CS42L81_CTL_HP_VOL: ++ return &cs->hp_vol; ++ case CS42L81_CTL_HP_SW: ++ return &cs->hp_switch; ++ case CS42L81_CTL_ADC_VOL: ++ return &cs->adc_vol; ++ default: ++ return &cs->adc_switch; ++ } ++} ++ ++static int cs42l81_ctl_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ ++ ucontrol->value.integer.value[0] = *cs42l81_ctl_field(cs, mc->reg); ++ return 0; ++} ++ ++static int cs42l81_ctl_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ struct soc_mixer_control *mc = ++ (struct soc_mixer_control *)kcontrol->private_value; ++ unsigned int *field = cs42l81_ctl_field(cs, mc->reg); ++ long v = ucontrol->value.integer.value[0]; ++ ++ if (v < 0 || v > mc->max) ++ return -EINVAL; ++ if (v == *field) ++ return 0; ++ ++ *field = v; ++ if (mc->reg == CS42L81_CTL_HP_VOL || mc->reg == CS42L81_CTL_HP_SW) ++ cs42l81_apply_hp_volume(cs); ++ else if (cs->adc_on) ++ cs42l81_apply_adc_volume(cs); ++ return 1; ++} ++ ++/* HP: 0.5 dB steps from -50 dB. Capture: 1 dB steps from -96 dB (inferred) */ ++static const DECLARE_TLV_DB_SCALE(cs42l81_hp_tlv, -5000, 50, 0); ++static const DECLARE_TLV_DB_SCALE(cs42l81_adc_tlv, -9600, 100, 0); ++ ++static const struct snd_kcontrol_new cs42l81_controls[] = { ++ { ++ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, ++ .name = "Speaker Playback Volume", ++ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | ++ SNDRV_CTL_ELEM_ACCESS_TLV_READ, ++ .tlv.p = cs42l81_spk_tlv, ++ .info = cs42l81_spk_vol_info, ++ .get = cs42l81_spk_vol_get, ++ .put = cs42l81_spk_vol_put, ++ }, ++ SOC_SINGLE_BOOL_EXT("Speaker Playback Switch", 0, ++ cs42l81_spk_sw_get, cs42l81_spk_sw_put), ++ SOC_SINGLE_EXT_TLV("Headphone Playback Volume", CS42L81_CTL_HP_VOL, 0, ++ CS42L81_HP_VOL_MAX, 0, cs42l81_ctl_get, ++ cs42l81_ctl_put, cs42l81_hp_tlv), ++ SOC_SINGLE_EXT("Headphone Playback Switch", CS42L81_CTL_HP_SW, 0, 1, 0, ++ cs42l81_ctl_get, cs42l81_ctl_put), ++ SOC_SINGLE_EXT_TLV("Capture Volume", CS42L81_CTL_ADC_VOL, 0, ++ CS42L81_ADC_VOL_MAX, 0, cs42l81_ctl_get, ++ cs42l81_ctl_put, cs42l81_adc_tlv), ++ SOC_SINGLE_EXT("Capture Switch", CS42L81_CTL_ADC_SW, 0, 1, 0, ++ cs42l81_ctl_get, cs42l81_ctl_put), ++}; ++ ++static const struct snd_soc_dapm_widget cs42l81_widgets[] = { ++ SND_SOC_DAPM_SUPPLY("Core", SND_SOC_NOPM, 0, 0, cs42l81_core_event, ++ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), ++ SND_SOC_DAPM_AIF_IN("ASPRX", "ASP Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_OUT_DRV_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0, ++ cs42l81_spk_event, ++ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | ++ SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_OUT_DRV_E("HP Amp", SND_SOC_NOPM, 0, 0, NULL, 0, ++ cs42l81_hp_event, ++ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_OUTPUT("SPKOUT"), ++ SND_SOC_DAPM_OUTPUT("HPOUT"), ++ ++ /* the two built-in DMICs (ADT dmic1a-intmic, dmic1b-intmic) */ ++ SND_SOC_DAPM_INPUT("DMIC1A"), ++ SND_SOC_DAPM_INPUT("DMIC1B"), ++ SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, cs42l81_adc_event, ++ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), ++ SND_SOC_DAPM_AIF_OUT("ASPTX", "ASP Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route cs42l81_routes[] = { ++ { "ASPRX", NULL, "Core" }, ++ { "Speaker Amp", NULL, "ASPRX" }, ++ { "SPKOUT", NULL, "Speaker Amp" }, ++ { "HP Amp", NULL, "ASPRX" }, ++ { "HPOUT", NULL, "HP Amp" }, ++ ++ { "ADC", NULL, "Core" }, ++ { "ADC", NULL, "DMIC1A" }, ++ { "ADC", NULL, "DMIC1B" }, ++ { "ASPTX", NULL, "ADC" }, ++}; ++ ++/* --- DAI ------------------------------------------------------------------ */ ++ ++static int cs42l81_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) ++{ ++ /* The codec provides BCLK and LRCK; MCA0 is the consumer (iOS). */ ++ if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBP_CFP) ++ return -EINVAL; ++ ((struct cs42l81 *)snd_soc_component_get_drvdata(dai->component))->clk_provider = true; ++ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) ++ return -EINVAL; ++ if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) ++ return -EINVAL; ++ return 0; ++} ++ ++static int cs42l81_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(dai->component); ++ int code = cs42l81_rate_code(params_rate(params)); ++ unsigned int width = params_physical_width(params); ++ ++ if (code < 0) ++ return code; ++ if (width != 16 && width != 32) ++ return -EINVAL; ++ ++ if (code == cs->rate_code && width / 8 == cs->slot_bytes) ++ return 0; ++ ++ cs->rate_code = code; ++ cs->slot_bytes = width / 8; ++ ++ /* DAPM keeps the core up for pmdown_time after a stream: reapply. */ ++ if (cs->core_on) { ++ cs42l81_set_rate(cs); ++ cs42l81_set_asp(cs); ++ } ++ if (cs->adc_on) { ++ cs42l81_set_asp_tx(cs); ++ regmap_write(cs->regmap, CS42L81_DMIC1_CTL, cs42l81_dmic_ctl(cs)); ++ } ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops cs42l81_dai_ops = { ++ .set_fmt = cs42l81_set_fmt, ++ .hw_params = cs42l81_hw_params, ++}; ++ ++static struct snd_soc_dai_driver cs42l81_dai = { ++ .name = "cs42l81-asp", ++ .playback = { ++ .stream_name = "ASP Playback", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | ++ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .capture = { ++ .stream_name = "ASP Capture", ++ .channels_min = 2, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | ++ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &cs42l81_dai_ops, ++ /* one ASP clock and one slot width for both directions */ ++ .symmetric_rate = 1, ++ .symmetric_sample_bits = 1, ++}; ++ ++/* --- headphone jack -------------------------------------------------------- */ ++ ++/* ++ * A plug moves playback from SPKOUT to HPOUT. The pins are switched here, ++ * not as jack pins, because snd_soc_jack_add_pins() would add a " ++ * Jack" control per pin; userspace looks for "Headphone Jack". ++ */ ++static void cs42l81_jack_report(struct cs42l81 *cs, bool plugged) ++{ ++ struct snd_soc_dapm_context *dapm = ++ snd_soc_component_to_dapm(cs->component); ++ ++ cs->jack_state = plugged; ++ dev_info(cs->dev, "headphones %s\n", plugged ? "plugged" : "unplugged"); ++ if (plugged) { ++ snd_soc_dapm_disable_pin(dapm, "SPKOUT"); ++ snd_soc_dapm_enable_pin(dapm, "HPOUT"); ++ } else { ++ snd_soc_dapm_disable_pin(dapm, "HPOUT"); ++ snd_soc_dapm_enable_pin(dapm, "SPKOUT"); ++ } ++ snd_soc_dapm_sync(dapm); ++ snd_soc_jack_report(&cs->jack, plugged ? SND_JACK_HEADPHONE : 0, ++ SND_JACK_HEADPHONE); ++} ++ ++/* hs-switch lines (ADT function-ext_sw1/2) are only read, never driven */ ++static int cs42l81_hs_switch_str(struct cs42l81 *cs, char *buf, size_t len) ++{ ++ unsigned int i; ++ int n = 0; ++ ++ for (i = 0; cs->hs_switch && i < cs->hs_switch->ndescs; i++) { ++ struct gpio_desc *d = cs->hs_switch->desc[i]; ++ ++ n += scnprintf(buf + n, len - n, " ext_sw%u %s=%d", i + 1, ++ gpiod_get_direction(d) ? "in" : "out", ++ gpiod_get_raw_value_cansleep(d)); ++ } ++ if (!n && len) ++ buf[0] = '\0'; ++ return n; ++} ++ ++/* ++ * Survey (test T1): log every change of the latched status registers with ++ * the hs-switch levels. With jack_reg/jack_mask set, also debounce that bit ++ * and report it through the jack, whose pins switch HPOUT and SPKOUT. ++ */ ++static void cs42l81_jack_work(struct work_struct *work) ++{ ++ struct cs42l81 *cs = container_of(to_delayed_work(work), ++ struct cs42l81, jack_work); ++ unsigned int period = READ_ONCE(jack_poll_ms); ++ unsigned int reg = READ_ONCE(jack_reg), mask = READ_ONCE(jack_mask); ++ unsigned int i, val, det = 0; ++ bool have_det = false, plugged, rearm; ++ char sw[64]; ++ ++ if (!period) ++ return; ++ ++ mutex_lock(&cs->status_lock); ++ for (i = 0; i < ARRAY_SIZE(cs42l81_status_regs); i++) { ++ if (regmap_read(cs->regmap, cs42l81_status_regs[i], &val)) ++ continue; ++ cs->status_seen[i] |= val; ++ if (val != cs->status_last[i]) { ++ cs42l81_hs_switch_str(cs, sw, sizeof(sw)); ++ dev_info_ratelimited(cs->dev, ++ "status %04x: %02x -> %02x%s\n", ++ cs42l81_status_regs[i], ++ cs->status_last[i], val, sw); ++ cs->status_last[i] = val; ++ } ++ if (cs42l81_status_regs[i] == reg) { ++ det = val; ++ have_det = true; ++ } ++ } ++ mutex_unlock(&cs->status_lock); ++ ++ /* reports take the DAPM mutex: never under status_lock */ ++ mutex_lock(&cs->jack_lock); ++ if (cs->jack_ready && have_det && mask && cs->jack_forced < 0) { ++ plugged = !!(det & mask) ^ READ_ONCE(jack_invert); ++ if (plugged != cs->jack_pending) { ++ cs->jack_pending = plugged; ++ cs->jack_stable = 1; ++ } else if (cs->jack_stable < CS42L81_JACK_DEBOUNCE) { ++ cs->jack_stable++; ++ } ++ if (cs->jack_stable >= CS42L81_JACK_DEBOUNCE && ++ plugged != cs->jack_state) ++ cs42l81_jack_report(cs, plugged); ++ } ++ /* component remove clears jack_ready, then cancels: no requeue after */ ++ rearm = cs->jack_ready; ++ mutex_unlock(&cs->jack_lock); ++ ++ if (rearm) ++ schedule_delayed_work(&cs->jack_work, ++ msecs_to_jiffies(max(period, ++ CS42L81_JACK_POLL_MIN_MS))); ++} ++ ++static int cs42l81_component_probe(struct snd_soc_component *component) ++{ ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ cs->component = component; ++ ret = snd_soc_card_jack_new(component->card, "Headphone Jack", ++ SND_JACK_HEADPHONE, &cs->jack); ++ if (ret) ++ return dev_err_probe(cs->dev, ret, "cannot create jack\n"); ++ ++ /* ++ * Until something reports a plug (the poll, once test T1 names the ++ * bit, or debugfs cs42l81-jack) the speakers stay selected. ++ */ ++ ret = snd_soc_dapm_disable_pin(snd_soc_component_to_dapm(component), ++ "HPOUT"); ++ if (ret) ++ dev_warn(cs->dev, "cannot deselect the headphones: %d\n", ret); ++ ++ mutex_lock(&cs->jack_lock); ++ cs->jack_state = false; ++ cs->jack_pending = false; ++ cs->jack_stable = 0; ++ cs->jack_forced = -1; ++ cs->jack_ready = true; ++ mutex_unlock(&cs->jack_lock); ++ if (READ_ONCE(jack_poll_ms)) ++ schedule_delayed_work(&cs->jack_work, 0); ++ return 0; ++} ++ ++static void cs42l81_component_remove(struct snd_soc_component *component) ++{ ++ struct cs42l81 *cs = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&cs->jack_lock); ++ cs->jack_ready = false; ++ mutex_unlock(&cs->jack_lock); ++ cancel_delayed_work_sync(&cs->jack_work); ++} ++ ++static const struct snd_soc_component_driver cs42l81_component = { ++ .probe = cs42l81_component_probe, ++ .remove = cs42l81_component_remove, ++ .controls = cs42l81_controls, ++ .num_controls = ARRAY_SIZE(cs42l81_controls), ++ .dapm_widgets = cs42l81_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(cs42l81_widgets), ++ .dapm_routes = cs42l81_routes, ++ .num_dapm_routes = ARRAY_SIZE(cs42l81_routes), ++ .endianness = 1, ++}; ++ ++/* --- debugfs (bring-up) ---------------------------------------------------- */ ++ ++/* ++ * Every register the driver touches, plus the latched status registers ++ * (0x002e, 0x002f, 0x0033, 0x007b, 0x007c, 0x0106, 0x0528: read to clear). ++ */ ++static const u16 cs42l81_dbg_regs[] = { ++ 0x0001, 0x0002, 0x0003, 0x0005, 0x0006, 0x0007, 0x000d, 0x000e, ++ 0x000f, 0x0011, 0x0012, 0x0014, 0x0015, 0x0016, 0x001e, 0x001f, ++ 0x0020, 0x0021, 0x0022, 0x0023, 0x002e, 0x002f, 0x0033, 0x007b, ++ 0x007c, 0x0106, 0x0115, 0x0116, 0x011f, 0x0120, 0x0123, 0x0124, ++ 0x0129, 0x012e, 0x012f, 0x0131, 0x0201, 0x0203, 0x0204, 0x0205, ++ 0x0206, 0x0207, 0x0208, 0x020f, 0x0210, 0x0211, 0x0212, 0x0215, ++ 0x0216, 0x0219, 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, ++ 0x0226, 0x0227, 0x0228, 0x0229, 0x022e, 0x022f, 0x0230, 0x0231, ++ 0x0232, 0x0528, 0x9901, 0xc81f, 0xc85f, 0xc96f, ++}; ++ ++static int cs42l81_regs_show(struct seq_file *s, void *unused) ++{ ++ struct cs42l81 *cs = dev_get_drvdata(s->private); ++ unsigned int i, val; ++ ++ seq_printf(s, "core_on %d spk_on %d hp_on %d (cp %d) adc_on %d clk_provider %d rate %#x slot %u\n", ++ cs->core_on, cs->spk_on, cs->hp_on, cs->hp_cp_on, cs->adc_on, ++ cs->clk_provider, cs->rate_code, cs->slot_bytes); ++ mutex_lock(&cs->status_lock); ++ for (i = 0; i < ARRAY_SIZE(cs42l81_dbg_regs); i++) { ++ if (regmap_read(cs->regmap, cs42l81_dbg_regs[i], &val)) ++ seq_printf(s, "%04x = (err)\n", cs42l81_dbg_regs[i]); ++ else ++ seq_printf(s, "%04x = %02x\n", cs42l81_dbg_regs[i], val); ++ } ++ mutex_unlock(&cs->status_lock); ++ return 0; ++} ++ ++/* ++ * cs42l81-jack. Read: poll settings, jack state, the OR of every status ++ * value the poll has seen (cleared by the read) and the hs-switch levels. ++ * Write: "1"/"0" force a plug/unplug report (tests T3/T4 before the status ++ * bit is known), "auto" returns to the poll, "poll" restarts the poll after ++ * jack_poll_ms changed. ++ */ ++static int cs42l81_jack_show(struct seq_file *s, void *unused) ++{ ++ struct cs42l81 *cs = s->private; ++ unsigned int i; ++ char sw[64]; ++ ++ seq_printf(s, "poll %u ms, reg %#x mask %#x invert %d\n", ++ READ_ONCE(jack_poll_ms), READ_ONCE(jack_reg), ++ READ_ONCE(jack_mask), READ_ONCE(jack_invert)); ++ mutex_lock(&cs->jack_lock); ++ seq_printf(s, "jack %s, reported %s\n", ++ !cs->jack_ready ? "not created" : ++ cs->jack_forced < 0 ? "auto" : ++ cs->jack_forced ? "forced plugged" : "forced unplugged", ++ cs->jack_state ? "plugged" : "unplugged"); ++ mutex_unlock(&cs->jack_lock); ++ ++ mutex_lock(&cs->status_lock); ++ seq_puts(s, "seen:"); ++ for (i = 0; i < ARRAY_SIZE(cs42l81_status_regs); i++) { ++ seq_printf(s, " %04x=%02x", cs42l81_status_regs[i], ++ cs->status_seen[i]); ++ cs->status_seen[i] = 0; ++ } ++ mutex_unlock(&cs->status_lock); ++ ++ cs42l81_hs_switch_str(cs, sw, sizeof(sw)); ++ seq_printf(s, "\nhs-switch:%s\n", sw[0] ? sw : " (none)"); ++ return 0; ++} ++ ++static int cs42l81_jack_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, cs42l81_jack_show, inode->i_private); ++} ++ ++static ssize_t cs42l81_jack_write(struct file *f, const char __user *ubuf, ++ size_t len, loff_t *ppos) ++{ ++ struct cs42l81 *cs = ((struct seq_file *)f->private_data)->private; ++ char buf[16] = {}, *cmd; ++ int forced; ++ ++ if (len >= sizeof(buf)) ++ return -EINVAL; ++ if (copy_from_user(buf, ubuf, len)) ++ return -EFAULT; ++ cmd = strim(buf); ++ ++ if (!strcmp(cmd, "1") || !strcmp(cmd, "plug")) ++ forced = 1; ++ else if (!strcmp(cmd, "0") || !strcmp(cmd, "unplug")) ++ forced = 0; ++ else if (!strcmp(cmd, "auto")) ++ forced = -1; ++ else if (!strcmp(cmd, "poll")) ++ forced = -2; /* only restart the poll */ ++ else ++ return -EINVAL; ++ ++ mutex_lock(&cs->jack_lock); ++ if (!cs->jack_ready) { ++ mutex_unlock(&cs->jack_lock); ++ return -ENODEV; ++ } ++ if (forced != -2) { ++ cs->jack_forced = forced; ++ cs->jack_stable = 0; ++ if (forced >= 0 && forced != cs->jack_state) ++ cs42l81_jack_report(cs, forced); ++ } ++ if (READ_ONCE(jack_poll_ms)) ++ mod_delayed_work(system_wq, &cs->jack_work, 0); ++ mutex_unlock(&cs->jack_lock); ++ return len; ++} ++ ++static const struct file_operations cs42l81_jack_fops = { ++ .open = cs42l81_jack_open, ++ .read = seq_read, ++ .write = cs42l81_jack_write, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++/* write "REG" to select, "REG VAL" to write; read prints the selection */ ++static ssize_t cs42l81_reg_write(struct file *f, const char __user *ubuf, ++ size_t len, loff_t *ppos) ++{ ++ struct cs42l81 *cs = f->private_data; ++ unsigned int reg, val; ++ char buf[32] = {}; ++ int n; ++ ++ if (len >= sizeof(buf)) ++ return -EINVAL; ++ if (copy_from_user(buf, ubuf, len)) ++ return -EFAULT; ++ n = sscanf(buf, "%x %x", ®, &val); ++ if (n < 1 || reg > 0xffff || (reg & 0x80)) ++ return -EINVAL; ++ cs->dbg_reg = reg; ++ if (n == 2 && regmap_write(cs->regmap, reg, val & 0xff)) ++ return -EIO; ++ return len; ++} ++ ++static ssize_t cs42l81_reg_read(struct file *f, char __user *ubuf, ++ size_t len, loff_t *ppos) ++{ ++ struct cs42l81 *cs = f->private_data; ++ unsigned int val = 0; ++ char buf[32]; ++ int n; ++ ++ if (regmap_read(cs->regmap, cs->dbg_reg, &val)) ++ return -EIO; ++ n = scnprintf(buf, sizeof(buf), "%04x = %02x\n", cs->dbg_reg, val); ++ return simple_read_from_buffer(ubuf, len, ppos, buf, n); ++} ++ ++static const struct file_operations cs42l81_reg_fops = { ++ .open = simple_open, ++ .read = cs42l81_reg_read, ++ .write = cs42l81_reg_write, ++ .llseek = default_llseek, ++}; ++ ++/* --- probe ---------------------------------------------------------------- */ ++ ++static void cs42l81_debugfs_remove(void *dentry) ++{ ++ debugfs_remove(dentry); ++} ++ ++static void cs42l81_cancel_jack_work(void *data) ++{ ++ struct cs42l81 *cs = data; ++ ++ cancel_delayed_work_sync(&cs->jack_work); ++} ++ ++static int cs42l81_check_id(struct cs42l81 *cs) ++{ ++ unsigned int id[3] = {}, rev = 0; ++ int ret, i; ++ ++ /* MCLK is not known to be needed for the control port; keep it on ++ * anyway while probing so a negative result is meaningful. */ ++ dev_info(cs->dev, "codec ID MCLK enable begin\n"); ++ ret = clk_prepare_enable(cs->mclk); ++ dev_info(cs->dev, "codec ID MCLK enable end ret=%d\n", ret); ++ if (ret) ++ return dev_err_probe(cs->dev, ret, "could not enable MCLK\n"); ++ dev_info(cs->dev, "codec ID SPI reads begin\n"); ++ ++ for (i = 0; i < 3; i++) { ++ ret = regmap_read(cs->regmap, CS42L81_DEVID_AB + i, &id[i]); ++ if (ret) ++ break; ++ } ++ if (!ret) ++ ret = regmap_read(cs->regmap, CS42L81_REVID, &rev); ++ dev_info(cs->dev, "codec ID SPI reads end ret=%d; MCLK disable begin\n", ret); ++ clk_disable_unprepare(cs->mclk); ++ dev_info(cs->dev, "codec ID MCLK disable end\n"); ++ ++ if (ret) ++ return dev_err_probe(cs->dev, ret, "SPI read failed\n"); ++ ++ if (id[0] != 0x42 || id[1] != 0xa8 || (id[2] & 0xf0) != 0x10) ++ return dev_err_probe(cs->dev, -ENODEV, ++ "unexpected ID %02x %02x %02x (want 42 a8 1x)\n", ++ id[0], id[1], id[2]); ++ ++ dev_info(cs->dev, "CS42L81 ID %02x%02x%02x rev %#x\n", ++ id[0], id[1], id[2], rev); ++ return 0; ++} ++ ++static int cs42l81_spi_probe(struct spi_device *spi) ++{ ++ struct device *dev = &spi->dev; ++ struct cs42l81 *cs; ++ u32 max_db_q8 = 0; ++ int ret; ++ ++ dev_info(dev, "codec probe begin\n"); ++ ++ cs = devm_kzalloc(dev, sizeof(*cs), GFP_KERNEL); ++ if (!cs) ++ return -ENOMEM; ++ ++ cs->dev = dev; ++ cs->spi = spi; ++ cs->xfer_buf = devm_kzalloc(dev, 10, GFP_KERNEL); ++ if (!cs->xfer_buf) ++ return -ENOMEM; ++ spi_set_drvdata(spi, cs); ++ ++ ret = devm_mutex_init(dev, &cs->status_lock); ++ if (ret) ++ return ret; ++ ret = devm_mutex_init(dev, &cs->jack_lock); ++ if (ret) ++ return ret; ++ INIT_DELAYED_WORK(&cs->jack_work, cs42l81_jack_work); ++ ret = devm_add_action(dev, cs42l81_cancel_jack_work, cs); ++ if (ret) ++ return ret; ++ cs->jack_forced = -1; ++ ++ cs->regmap = devm_regmap_init(dev, &cs42l81_bus, cs, &cs42l81_regmap); ++ if (IS_ERR(cs->regmap)) ++ return dev_err_probe(dev, PTR_ERR(cs->regmap), "regmap init failed\n"); ++ ++ cs->mclk = devm_clk_get_optional(dev, "mclk"); ++ if (IS_ERR(cs->mclk)) ++ return dev_err_probe(dev, PTR_ERR(cs->mclk), "failed to get MCLK\n"); ++ ++ /* logical 1 = muted; the line itself is active low (inferred) */ ++ cs->mute_gpio = devm_gpiod_get_optional(dev, "mute", GPIOD_OUT_HIGH); ++ if (IS_ERR(cs->mute_gpio)) ++ return dev_err_probe(dev, PTR_ERR(cs->mute_gpio), ++ "failed to get mute GPIO\n"); ++ ++ /* ++ * Headset switch lines: their direction is not known (iOS may drive ++ * them), so take them as they are and only read them for the jack log. ++ * GPIOD_ASIS does not touch the pad. ++ */ ++ cs->hs_switch = devm_gpiod_get_array_optional(dev, "hs-switch", ++ GPIOD_ASIS); ++ if (IS_ERR(cs->hs_switch)) { ++ if (PTR_ERR(cs->hs_switch) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_warn(dev, "hs-switch GPIOs unavailable: %pe\n", cs->hs_switch); ++ cs->hs_switch = NULL; ++ } ++ ++ cs->spk_mode = 2; ++ of_property_read_u32(dev->of_node, "apple,speaker-mode", &cs->spk_mode); ++ ++ /* ++ * apple,max-speaker-gain-db-q8: max speaker gain in 1/256 dB (iOS ADT ++ * spkr-gain-db is -3.0 dB). Clamp the control there. ++ */ ++ cs->vol_max = CS42L81_VOL_MAX_CTL; ++ if (!of_property_read_u32(dev->of_node, "apple,max-speaker-gain-db-q8", ++ &max_db_q8)) { ++ int db_x2 = (int)max_db_q8 * 2 / 256; /* 0.5 dB units */ ++ int max = 204 + db_x2; /* 204 = 0 dB */ ++ ++ cs->vol_max = clamp(max, 0, CS42L81_VOL_MAX_CTL); ++ } ++ /* Default: -24 dB, quiet but audible */ ++ cs->spk_vol = min_t(unsigned int, 204 - 48, cs->vol_max); ++ cs->spk_switch = true; ++ /* the DAI only accepts codec-provided clocks (J71t, as on iOS) */ ++ cs->clk_provider = true; ++ cs->rate_code = 0xc; ++ cs->slot_bytes = 2; ++ cs->hp_vol = CS42L81_HP_VOL_DEFAULT; ++ cs->hp_switch = 1; ++ cs->adc_vol = CS42L81_ADC_VOL_0DB; ++ cs->adc_switch = 1; ++ ++ ret = cs42l81_check_id(cs); ++ if (ret) ++ return ret; ++ ++ debugfs_create_devm_seqfile(dev, "cs42l81-regs", NULL, cs42l81_regs_show); ++ ret = devm_add_action_or_reset(dev, cs42l81_debugfs_remove, ++ debugfs_create_file("cs42l81-reg", 0600, ++ NULL, cs, ++ &cs42l81_reg_fops)); ++ if (ret) ++ return ret; ++ ret = devm_add_action_or_reset(dev, cs42l81_debugfs_remove, ++ debugfs_create_file("cs42l81-jack", 0600, ++ NULL, cs, ++ &cs42l81_jack_fops)); ++ if (ret) ++ return ret; ++ ++ return devm_snd_soc_register_component(dev, &cs42l81_component, ++ &cs42l81_dai, 1); ++} ++ ++static const struct of_device_id cs42l81_of_match[] = { ++ { .compatible = "cirrus,cs42l81" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, cs42l81_of_match); ++ ++static const struct spi_device_id cs42l81_spi_ids[] = { ++ { "cs42l81" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(spi, cs42l81_spi_ids); ++ ++static struct spi_driver cs42l81_spi_driver = { ++ .driver = { ++ .name = "cs42l81", ++ .of_match_table = cs42l81_of_match, ++ }, ++ .probe = cs42l81_spi_probe, ++ .id_table = cs42l81_spi_ids, ++}; ++module_spi_driver(cs42l81_spi_driver); ++ ++MODULE_DESCRIPTION("Cirrus Logic CS42L81 codec (Apple iPad 5) driver"); ++MODULE_LICENSE("Dual MIT/GPL"); diff --git a/kernel/patches/j71t/0006-j71t-power.patch b/kernel/patches/j71t/0006-j71t-power.patch new file mode 100644 index 00000000..12289e2b --- /dev/null +++ b/kernel/patches/j71t/0006-j71t-power.patch @@ -0,0 +1,8918 @@ +From f5ce108287c309c122eb64ab06caeb1bcee05125 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 06/12] power: supply: add J71t PMIC GPIO, charging, battery + and thermal +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +D2365 PMIC GPIO and NVMEM, the D2365/J71t charger with source +detection and the ADT input limit, the Lightning (Tristar) power +role, the bq27540 gauge over HDQ, S8003 thermal sensors and the +stock CPU frequency table. The overclocking code in +apple-a9-tuning.h is only built with CONFIG_APPLE_A9_TUNING. + +Builds on the D2365 charger and Bellatrix power drivers by Paul +Praschl in HoolockLinux. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + Documentation/ABI/testing/sysfs-apple-charge7 | 19 + + .../bindings/gpio/apple,d2365-pmic-gpio.yaml | 59 + + .../devicetree/bindings/mfd/apple,pmic.yaml | 37 + + .../bindings/nvmem/apple,pmic-nvmem.yaml | 6 + + .../power/supply/apple,bellatrix.yaml | 49 + + .../power/supply/apple,d2365-charger.yaml | 80 + + .../supply/apple,j71t-cbtl1610-power.yaml | 58 + + .../bindings/power/supply/ti,bq27545-hdq.yaml | 51 + + .../bindings/thermal/apple,s8003-thermal.yaml | 53 + + .../devicetree/bindings/w1/w1-uart.yaml | 34 +- + drivers/cpufreq/Kconfig.arm | 11 + + drivers/cpufreq/apple-a9-tuning.h | 639 +++++++ + drivers/cpufreq/apple-soc-cpufreq.c | 115 +- + drivers/gpio/Kconfig | 11 + + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-apple-d2365.c | 624 +++++++ + drivers/nvmem/simple-mfd-nvmem.c | 21 +- + drivers/power/supply/Kconfig | 41 + + drivers/power/supply/Makefile | 2 + + drivers/power/supply/apple_bellatrix_power.c | 273 +++ + drivers/power/supply/apple_d2365_charger.c | 772 ++++++++ + drivers/power/supply/apple_j71t_charger.c | 1007 ++++++++++ + drivers/power/supply/apple_tristar_auto.h | 326 ++++ + drivers/power/supply/apple_tristar_idbus.h | 957 ++++++++++ + drivers/power/supply/apple_tristar_power.c | 1648 +++++++++++++++++ + drivers/power/supply/apple_tristar_source.h | 10 + + drivers/power/supply/bq27xxx_battery.c | 441 ++++- + drivers/power/supply/bq27xxx_battery_hdq.c | 67 +- + drivers/power/supply/power_supply_core.c | 3 + + drivers/thermal/Kconfig | 9 + + drivers/thermal/Makefile | 2 + + drivers/thermal/apple-s8003-thermal.c | 367 ++++ + drivers/w1/masters/Kconfig | 8 +- + drivers/w1/masters/w1-uart.c | 327 +++- + include/linux/power/bq27xxx_battery.h | 25 + + 35 files changed, 8090 insertions(+), 63 deletions(-) + create mode 100644 Documentation/ABI/testing/sysfs-apple-charge7 + create mode 100644 Documentation/devicetree/bindings/gpio/apple,d2365-pmic-gpio.yaml + create mode 100644 Documentation/devicetree/bindings/power/supply/apple,bellatrix.yaml + create mode 100644 Documentation/devicetree/bindings/power/supply/apple,d2365-charger.yaml + create mode 100644 Documentation/devicetree/bindings/power/supply/apple,j71t-cbtl1610-power.yaml + create mode 100644 Documentation/devicetree/bindings/power/supply/ti,bq27545-hdq.yaml + create mode 100644 Documentation/devicetree/bindings/thermal/apple,s8003-thermal.yaml + create mode 100644 drivers/cpufreq/apple-a9-tuning.h + create mode 100644 drivers/gpio/gpio-apple-d2365.c + create mode 100644 drivers/power/supply/apple_bellatrix_power.c + create mode 100644 drivers/power/supply/apple_d2365_charger.c + create mode 100644 drivers/power/supply/apple_j71t_charger.c + create mode 100644 drivers/power/supply/apple_tristar_auto.h + create mode 100644 drivers/power/supply/apple_tristar_idbus.h + create mode 100644 drivers/power/supply/apple_tristar_power.c + create mode 100644 drivers/power/supply/apple_tristar_source.h + create mode 100644 drivers/thermal/apple-s8003-thermal.c + +diff --git a/Documentation/ABI/testing/sysfs-apple-charge7 b/Documentation/ABI/testing/sysfs-apple-charge7 +new file mode 100644 +index 0000000000000000000000000000000000000000..b328fcc8a446583667c5680d0b22cec4efefefee +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-apple-charge7 +@@ -0,0 +1,19 @@ ++What: /sys/class/power_supply/charger/charge7_state ++Date: October 2026 ++Contact: Berkay Kösebay ++Description: ++ Read-only J71t charging policy snapshot. Space-separated key=value ++ pairs: attach (physical source epoch), source_type (numeric Linux ++ power_supply_usb_type, also exposed textually by usb/usb_type), ++ ceiling_ua (detected/board ceiling), step_ua (active nominal ladder ++ budget, zero if inactive), live_ua (0xa03 setting decoded in uA, ++ negative errno on read failure), override_ua (per-attach user cap), ++ adaptive_ua (back-off cap), backoffs (count this attach), ++ last_backoff (none or pmic-input-reduced), vbus_mv (unavailable), ++ policy_error (last policy errno), active (boolean). ++ ++ live_ua is a programmed setting, not measured current. An observed ++ lower PMU input setting triggers bounded back-off. No verified ++ voltage-triggered droop protection or D2365 VINDPM setting is ++ claimed by this interface. The epoch advances on observed VBUS ++ removal; a pulse shorter than all polling observations can be missed. +diff --git a/Documentation/devicetree/bindings/gpio/apple,d2365-pmic-gpio.yaml b/Documentation/devicetree/bindings/gpio/apple,d2365-pmic-gpio.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..93b320d18ded9dd4390504f70170cd360ac13170 +--- /dev/null ++++ b/Documentation/devicetree/bindings/gpio/apple,d2365-pmic-gpio.yaml +@@ -0,0 +1,59 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/gpio/apple,d2365-pmic-gpio.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple D2365 PMIC GPIO controller ++ ++maintainers: ++ - Paul Praschl ++ ++description: | ++ The Dialog D2365 PMIC used in Apple A9/A10 devices provides 24 GPIOs. The ++ controller is accessed through the regmap exported by the parent Apple I2C ++ PMIC device. ++ ++properties: ++ compatible: ++ const: apple,d2365-pmic-gpio ++ ++ gpio-controller: true ++ ++ "#gpio-cells": ++ const: 2 ++ ++ gpio-reserved-ranges: ++ minItems: 1 ++ maxItems: 12 ++ ++ apple,enable-hall-irqs: ++ type: boolean ++ description: | ++ Serve the GPIO events of lines 0 and 10 (PMIC events 0x30 and 0x3a, ++ the iPad 5 Smart Cover hall sensors) through the interrupt domain of ++ the parent PMIC, which must then describe its AP interrupt and be an ++ interrupt controller. The line function and edge configuration left by ++ the boot firmware are used as they are. Without this property the ++ driver never reads the event or writes the interrupt mask registers. ++ ++required: ++ - compatible ++ - gpio-controller ++ - "#gpio-cells" ++ ++additionalProperties: false ++ ++examples: ++ - | ++ pmic { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ gpio { ++ compatible = "apple,d2365-pmic-gpio"; ++ #gpio-cells = <2>; ++ gpio-controller; ++ }; ++ }; ++... +diff --git a/Documentation/devicetree/bindings/mfd/apple,pmic.yaml b/Documentation/devicetree/bindings/mfd/apple,pmic.yaml +index 8bf64a835559e6ec3efdc916d3daee033c4f2d5c..8f1dd6b3e50f5650e708575fc3a966332e675c28 100644 +--- a/Documentation/devicetree/bindings/mfd/apple,pmic.yaml ++++ b/Documentation/devicetree/bindings/mfd/apple,pmic.yaml +@@ -52,9 +52,46 @@ properties: + const: 1 + + "#size-cells": ++ enum: [0, 1] ++ description: | ++ Zero describes non-MMIO register addresses on the PMIC bus. NVMEM ++ children then provide their register count with the size property. ++ One preserves the legacy register-address and byte-count tuples. ++ ++ interrupts: ++ description: D2365 active-low event output to the AP GPIO controller. ++ maxItems: 1 ++ ++ interrupt-controller: true ++ ++ "#interrupt-cells": ++ description: D2365 event number within the PMIC interrupt domain. + const: 1 + ++ wakeup-source: true ++ ++ gpio: ++ $ref: /schemas/gpio/apple,d2365-pmic-gpio.yaml ++ ++ interrupts: ++ description: D2365 active-low event output to the AP GPIO controller. ++ maxItems: 1 ++ ++ interrupt-controller: true ++ ++ "#interrupt-cells": ++ description: D2365 event number within the PMIC interrupt domain. ++ const: 1 ++ ++ wakeup-source: true ++ ++ gpio: ++ $ref: /schemas/gpio/apple,d2365-pmic-gpio.yaml ++ + patternProperties: ++ "^charger@[0-9a-f]+$": ++ $ref: /schemas/power/supply/apple,d2365-charger.yaml ++ + "^backlight(@[0-9a-f]+)?$": + $ref: /schemas/leds/backlight/apple,pmic-bl.yaml + +diff --git a/Documentation/devicetree/bindings/nvmem/apple,pmic-nvmem.yaml b/Documentation/devicetree/bindings/nvmem/apple,pmic-nvmem.yaml +index b184c10ed087794db2cdb6e4e6ddd80eee12995b..c20eb02dfea17bdc6c20a1a06f864e46f0a7b622 100644 +--- a/Documentation/devicetree/bindings/nvmem/apple,pmic-nvmem.yaml ++++ b/Documentation/devicetree/bindings/nvmem/apple,pmic-nvmem.yaml +@@ -29,6 +29,12 @@ properties: + reg: + maxItems: 1 + ++ size: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ minimum: 1 ++ description: ++ Number of byte registers when the parent PMIC has zero size cells. ++ + required: + - compatible + - reg +diff --git a/Documentation/devicetree/bindings/power/supply/apple,bellatrix.yaml b/Documentation/devicetree/bindings/power/supply/apple,bellatrix.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..72e158f91d875eacb0ea25b66ae5db737bb2a109 +--- /dev/null ++++ b/Documentation/devicetree/bindings/power/supply/apple,bellatrix.yaml +@@ -0,0 +1,49 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/power/supply/apple,bellatrix.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple Bellatrix USB power controller ++ ++maintainers: ++ - Paul Praschl ++ ++description: | ++ Bellatrix is an I2C power and accessory controller used in some Apple ++ T8010 devices. The Linux driver reports cached USB input presence without ++ changing charging, ADC, power-role, or interrupt configuration. ++ ++allOf: ++ - $ref: power-supply.yaml# ++ ++properties: ++ compatible: ++ const: apple,bellatrix ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ ++ i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ usb-power@13 { ++ compatible = "apple,bellatrix"; ++ reg = <0x13>; ++ interrupts = <13 IRQ_TYPE_LEVEL_LOW>; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/power/supply/apple,d2365-charger.yaml b/Documentation/devicetree/bindings/power/supply/apple,d2365-charger.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..d0460b84194e4e5dc3b77261ea24e78de8d039f9 +--- /dev/null ++++ b/Documentation/devicetree/bindings/power/supply/apple,d2365-charger.yaml +@@ -0,0 +1,80 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/power/supply/apple,d2365-charger.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple D2365 PMIC charger ++ ++maintainers: ++ - Paul Praschl ++ ++description: | ++ The Apple D2365 PMIC contains power-source status and charger-control banks. ++ The J172 variant uses a platform-calibrated, guarded input-current policy. ++ J71t uses the board ceiling for a USB host source, requiring ++ private input-current calibration and live Tristar source presence. ++ ++properties: ++ compatible: ++ oneOf: ++ - const: apple,d2365-charger ++ - const: apple,j71t-d2365-charger ++ - items: ++ - const: apple,j172-d2365-charger ++ - const: apple,d2365-charger ++ ++ reg: ++ items: ++ - description: Charger status register bank. ++ - description: Charger control register bank. ++ ++ reg-names: ++ items: ++ - const: status ++ - const: control ++ ++ power-supplies: ++ maxItems: 1 ++ ++ apple,usb-input-limit-max-microamp: ++ description: Board input ceiling, from the ADT usb-input-limit-max value. ++ enum: [500000, 1000000, 2100000, 2400000] ++ default: 500000 ++ ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ ++allOf: ++ - $ref: power-supply.yaml# ++ - required: ++ - power-supplies ++ ++additionalProperties: false ++ ++examples: ++ - | ++ i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ usb_power: usb-power@13 { ++ compatible = "apple,bellatrix"; ++ reg = <0x13>; ++ }; ++ }; ++ ++ pmic { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ charger@180 { ++ compatible = "apple,d2365-charger"; ++ reg = <0x180 0x1e>, <0xa00 0x0d>; ++ reg-names = "status", "control"; ++ power-supplies = <&usb_power>; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/power/supply/apple,j71t-cbtl1610-power.yaml b/Documentation/devicetree/bindings/power/supply/apple,j71t-cbtl1610-power.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..47d7a1b380485a7cf6ce557eb9321074e0e64810 +--- /dev/null ++++ b/Documentation/devicetree/bindings/power/supply/apple,j71t-cbtl1610-power.yaml +@@ -0,0 +1,58 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/power/supply/apple,j71t-cbtl1610-power.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple J71t CBTL1610 USB source detection ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: | ++ VBUS presence, Apple USB host allowances and bounded analog charger ++ signature detection for the CBTL1610 revision used by J71t. Temporary ++ data-pin monitoring restores the saved route after each measurement. ++ ++properties: ++ compatible: ++ const: apple,j71t-cbtl1610-power ++ ++ reg: ++ maxItems: 1 ++ ++ usb-controller: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: USB device controller reporting host current allowances. ++ ++ apple,usb-role-switch: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: | ++ Shared Lightning USB role switch. With this link, analog charger ++ classification is disabled to avoid rerouting host data pins, and ++ host mode reports no input current allowance. ++ ++ apple,pmic: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: D2365 PMIC providing the brick-ID ADC input. ++ ++required: ++ - compatible ++ - reg ++ - usb-controller ++ - apple,pmic ++ ++additionalProperties: false ++ ++examples: ++ - | ++ i2c { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ power-controller@1a { ++ compatible = "apple,j71t-cbtl1610-power"; ++ reg = <0x1a>; ++ usb-controller = <&usbdev>; ++ apple,pmic = <&pmic>; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/power/supply/ti,bq27545-hdq.yaml b/Documentation/devicetree/bindings/power/supply/ti,bq27545-hdq.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..e95ee04ee0fae5c9f65fc7178a8640922831140e +--- /dev/null ++++ b/Documentation/devicetree/bindings/power/supply/ti,bq27545-hdq.yaml +@@ -0,0 +1,51 @@ ++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/power/supply/ti,bq27545-hdq.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Texas Instruments BQ27545 fuel gauge on an HDQ bus ++ ++maintainers: ++ - Paul Praschl ++ ++description: | ++ The BQ27545 is a single-cell fuel gauge which can communicate using the ++ Texas Instruments single-wire HDQ protocol. An HDQ device has no 1-Wire ++ ROM address, so it is represented as the single child of an HDQ bus. ++ ++allOf: ++ - $ref: power-supply.yaml# ++ ++properties: ++ compatible: ++ enum: ++ - ti,bq27545-hdq ++ - apple,j71t-bq27540 ++ ++ power-supplies: ++ maxItems: 1 ++ ++ monitored-battery: true ++ ++required: ++ - compatible ++ ++additionalProperties: false ++ ++examples: ++ - | ++ battery_info: battery { ++ compatible = "simple-battery"; ++ }; ++ ++ serial { ++ onewire { ++ compatible = "ti,hdq-uart"; ++ ++ fuel-gauge { ++ compatible = "ti,bq27545-hdq"; ++ monitored-battery = <&battery_info>; ++ }; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/thermal/apple,s8003-thermal.yaml b/Documentation/devicetree/bindings/thermal/apple,s8003-thermal.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..e0728a48fdd9b25e736332f0d75f3c89576679d8 +--- /dev/null ++++ b/Documentation/devicetree/bindings/thermal/apple,s8003-thermal.yaml +@@ -0,0 +1,53 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/thermal/apple,s8003-thermal.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple S8003 die temperature sensors ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: | ++ Three PMGR SOC and three ACC TCC sensors. Sensor IDs 0 through 2 select ++ SOC0 through SOC2; IDs 3 through 5 select TCC0 through TCC2. Calibration ++ is performed in hardware using the boot-programmed per-chip trim. The ++ driver preserves trim, alarm registers and hardware SoCHOT thresholds. ++ ++allOf: ++ - $ref: thermal-sensor.yaml# ++ ++properties: ++ compatible: ++ const: apple,s8003-thermal ++ reg: ++ maxItems: 2 ++ reg-names: ++ items: ++ - const: soc ++ - const: tcc ++ '#thermal-sensor-cells': ++ const: 1 ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - '#thermal-sensor-cells' ++ ++additionalProperties: false ++ ++examples: ++ - | ++ / { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ thermal-sensor@20e0c0000 { ++ compatible = "apple,s8003-thermal"; ++ reg = <0x2 0x0e0c0000 0x0 0x1000>, ++ <0x2 0x02230000 0x0 0x1000>; ++ reg-names = "soc", "tcc"; ++ #thermal-sensor-cells = <1>; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/w1/w1-uart.yaml b/Documentation/devicetree/bindings/w1/w1-uart.yaml +index bd7c62d780b82d7f5d276547c746ebd43e6479fb..607ad99d2fbe7f715a8333e74729ed74342908ab 100644 +--- a/Documentation/devicetree/bindings/w1/w1-uart.yaml ++++ b/Documentation/devicetree/bindings/w1/w1-uart.yaml +@@ -4,7 +4,7 @@ + $id: http://devicetree.org/schemas/w1/w1-uart.yaml# + $schema: http://devicetree.org/meta-schemas/core.yaml# + +-title: UART 1-Wire Bus ++title: UART 1-Wire and HDQ Bus + + maintainers: + - Christoph Winklhofer +@@ -25,9 +25,16 @@ description: | + + https://www.analog.com/en/technical-articles/using-a-uart-to-implement-a-1wire-bus-master.html + ++ The UART can alternatively implement the Texas Instruments HDQ protocol. ++ HDQ mode uses 57600 baud, 8 data bits, no parity, two stop bits, and a ++ serial BREAK for the HDQ break pulse. Each HDQ bit is represented by one ++ UART byte, and the bus contains a single device without a 1-Wire ROM ID. ++ + properties: + compatible: +- const: w1-uart ++ enum: ++ - w1-uart ++ - ti,hdq-uart + + reset-bps: + default: 9600 +@@ -44,6 +51,16 @@ properties: + description: + The baud rate for the 1-Wire write-1 and read cycle. + ++ mux-states: ++ maxItems: 1 ++ description: ++ Optional mux state which routes the HDQ line to the UART. The state is ++ selected for each HDQ transaction and deselected afterwards. ++ ++ mux-state-names: ++ items: ++ - const: hdq ++ + required: + - compatible + +@@ -57,3 +74,16 @@ examples: + compatible = "w1-uart"; + }; + }; ++ ++ - | ++ serial { ++ onewire { ++ compatible = "ti,hdq-uart"; ++ mux-states = <&hdq_mux 1>; ++ mux-state-names = "hdq"; ++ ++ fuel-gauge { ++ compatible = "ti,bq27545-hdq"; ++ }; ++ }; ++ }; +diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm +index a441668f9e0cc698ff7b4cacc7ca43a87689492e..705b15ca1e36acdaf11c153f6666114dcd88dfa1 100644 +--- a/drivers/cpufreq/Kconfig.arm ++++ b/drivers/cpufreq/Kconfig.arm +@@ -273,3 +273,14 @@ config ARM_PXA2xx_CPUFREQ + This add the CPUFreq driver support for Intel PXA2xx SOCs. + + If in doubt, say N. ++ ++config APPLE_A9_TUNING ++ bool "Guarded experimental S8003 CPU clocks" ++ depends on ARM_APPLE_SOC_CPUFREQ=y ++ depends on APPLE_S8003_THERMAL=y && CPU_THERMAL && DEBUG_FS ++ depends on !THERMAL_EMULATION ++ help ++ Root-only, default-off, heartbeat-protected S8003 stock boost and ++ experimental clocks with decoded CPU core/buck7 DAC offsets. ++ Exact 3125 uV increments, bounded per rail to the cloned stock state ++ plus 50000 uV. No PMIC or factory-calibration writes are exposed. Enable with apple_soc_cpufreq.a9_tuning=1. +diff --git a/drivers/cpufreq/apple-a9-tuning.h b/drivers/cpufreq/apple-a9-tuning.h +new file mode 100644 +index 0000000000000000000000000000000000000000..18b19a5ee732b803018af430fdcf3614b4768f70 +--- /dev/null ++++ b/drivers/cpufreq/apple-a9-tuning.h +@@ -0,0 +1,639 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Private apple-soc-cpufreq implementation; included after the request helper. */ ++static unsigned int a9_psinfo1_offset(unsigned int state) ++{ ++ return state < 8 ? 0x68 + state * 8 : 0x368 + (state - 8) * 8; ++} ++ ++static unsigned int a9_psinfo_khz(u64 word) ++{ ++ unsigned int prediv = FIELD_GET(GENMASK_ULL(17, 13), word); ++ ++ /* Matching j71t iBoot 20H350 at 0x87001c35c. Zero prediv means one. */ ++ return 24000 * FIELD_GET(GENMASK_ULL(12, 4), word) / ++ max(prediv, 1U) / (FIELD_GET(GENMASK_ULL(3, 0), word) + 1); ++} ++ ++#ifdef CONFIG_APPLE_A9_TUNING ++#define A9_STOCK_KHZ 1800000U ++#define A9_BOOST_KHZ 1848000U ++#define A9_PLL_MAX_KHZ (511U * 24000U) /* PSINFO1[12:4], stock dividers. */ ++#define A9_BOOST_STATE 15U /* All-load stock clone; native slot 8 stays intact. */ ++#define A9_OVERVOLT_LIMIT_UV 50000U /* Fixed ceiling: never a module parameter. */ ++#define A9_DAC_STEP_UV 3125U ++#define A9_CORE_BASE_UV 450000U ++#define A9_AUX_BASE_UV 600000U ++#define A9_HEARTBEAT_MS 2000 ++#define A9_POLL_MS 100 ++ ++static bool a9_tuning; ++module_param(a9_tuning, bool, 0400); ++MODULE_PARM_DESC(a9_tuning, "Enable guarded RAM-only S8003 tuning (default off)"); ++ ++/* ++ * Hottest trusted sensor at which the guard reverts to stock (passive). ++ * 80 C by default; user-selectable up to 92 C. The hot (95 C) and critical ++ * (100 C) reverts and the hardware thermal protection are unchanged. ++ */ ++static unsigned int a9_passive_millic = 80000; ++module_param(a9_passive_millic, uint, 0400); ++MODULE_PARM_DESC(a9_passive_millic, "Guard passive revert threshold in milli-C, 70000..92000 (default 80000)"); ++ ++/* Boot parameters are parsed before either driver's probe. No guard state or ++ * probe ordering is involved, and the parameters are read-only after boot. ++ * One selection controls the guard and the thermal framework's cooling trips. ++ */ ++struct apple_a9_thermal_profile apple_a9_get_thermal_profile(void) ++{ ++ struct apple_a9_thermal_profile p = apple_a9_stock_thermal_profile(); ++ ++ if (a9_tuning && of_machine_is_compatible("apple,j71t")) { ++ p.tuning = true; ++ p.passive = clamp(a9_passive_millic, 70000U, 92000U); ++ p.hot = 95000; ++ p.arm = p.passive - 5000; ++ p.cooldown = p.passive - 10000; ++ } ++ return p; ++} ++EXPORT_SYMBOL_GPL(apple_a9_get_thermal_profile); ++ ++static const unsigned int a9_step_khz[] = { ++ 1896000, 1944000, 1992000, 2040000, 2088000, 2160000, ++}; ++ ++/* Preserve the round-5 mapping; higher OPPs share the last dormant slot. ++ * 48 MHz rungs end at 12240 MHz, then the final 24 MHz reaches multiplier 511. ++ * This is an encoding bound, not an analog PLL operating-range claim. ++ */ ++static unsigned int a9_step_state(unsigned int khz) ++{ ++ unsigned int i; ++ ++ if (khz == A9_BOOST_KHZ) ++ return A9_BOOST_STATE; ++ for (i = 0; i < ARRAY_SIZE(a9_step_khz); i++) ++ if (khz == a9_step_khz[i]) ++ return i + 9; ++ if ((khz > 2160000 && khz <= A9_PLL_MAX_KHZ && ++ (khz - 2160000) % 48000 == 0) || khz == A9_PLL_MAX_KHZ) ++ return 14; ++ return 0; ++} ++ ++static const char * const a9_zone_names[] = { ++ "apple-a9-soc0", "apple-a9-soc1", "apple-a9-tcc2", ++}; ++ ++struct a9_guard { ++ struct cpufreq_policy *policy; ++ struct apple_cpu_priv *priv; ++ struct delayed_work work; ++ struct mutex control; ++ struct freq_qos_request qos; ++ struct dentry *directory; ++ void __iomem *voltage_base; ++ u64 stock1[16], stock2[16]; ++ unsigned int limit, configured, dirty, delta_uv; ++ u32 trip_generation; ++ u32 voltage_ctl_boot, voltage_routes_boot; ++ int control_error; ++ unsigned long heartbeat; ++ bool armed, ready, latched; ++ bool thermal_qos_pending; ++ const char *reason; ++ int revert_error, hottest; ++}; ++ ++static struct a9_guard __rcu *a9_active_guard; ++ ++/* The cold-boot j71t logs observe 0/1782, not a sticky copy of m1n1's ++ * write of 1. This is an exact observed-handoff allowlist, not a new hardware ++ * mode decoder. Preserve it; never program voltage control, DWI or the PMIC. ++ * Both the initial handoff and every live check must match. Unknown bits, ++ * routing changes and a changed handoff are refused even at delta zero. ++ */ ++static bool a9_voltage_handoff_ok(struct a9_guard *g) ++{ ++ return g->voltage_ctl_boot == 0 && g->voltage_routes_boot == 0x1782 && ++ readl(g->voltage_base) == 0 && readl(g->voltage_base + 4) == 0x1782; ++} ++ ++/* LLB 0x180396138 converts logical rails 0/7 through the PMIC table before ++ * placing them in PSINFO1[63:56]/PSINFO2[39:32]. iBoot independently exports ++ * core mV from that byte using base 450 and step 3125 uV. Buck7 has base 600. ++ * Apply equal exact DAC increments to both rails; retain the boot separation. ++ * See docs/power/overclock-round3.md in the workspace for hashes and evidence. ++ */ ++static int a9_voltage_words(u64 stock1, u64 stock2, unsigned int delta_uv, ++ u64 *word1, u64 *word2) ++{ ++ unsigned int core = FIELD_GET(GENMASK_ULL(63, 56), stock1); ++ unsigned int aux = FIELD_GET(GENMASK_ULL(39, 32), stock2); ++ unsigned int steps; ++ ++ if (delta_uv > A9_OVERVOLT_LIMIT_UV) ++ return -ERANGE; ++ /* Refuse fractional codes, including rounding that could exceed +50 mV. */ ++ if (delta_uv % A9_DAC_STEP_UV) ++ return -EINVAL; ++ steps = delta_uv / A9_DAC_STEP_UV; ++ if (core + steps > 255 || aux + steps > 255) ++ return -ERANGE; ++ if (A9_CORE_BASE_UV + (core + steps) * A9_DAC_STEP_UV > 1296000 || ++ A9_AUX_BASE_UV + (aux + steps) * A9_DAC_STEP_UV > 1396000) ++ return -ERANGE; ++ *word1 = (stock1 & ~GENMASK_ULL(63, 56)) | ++ FIELD_PREP(GENMASK_ULL(63, 56), core + steps); ++ *word2 = (stock2 & ~GENMASK_ULL(39, 32)) | ++ FIELD_PREP(GENMASK_ULL(39, 32), aux + steps); ++ return 0; ++} ++ ++static int a9_temperatures(int *hottest) ++{ ++ int i, temp, ret; ++ struct thermal_zone_device *zone; ++ ++ *hottest = INT_MIN; ++ for (i = 0; i < ARRAY_SIZE(a9_zone_names); i++) { ++ zone = thermal_zone_get_zone_by_name(a9_zone_names[i]); ++ if (IS_ERR(zone)) ++ return PTR_ERR(zone); ++ ret = thermal_zone_get_temp(zone, &temp); ++ if (ret) ++ return ret; ++ if (temp < -40000 || temp > 150000) ++ return -ERANGE; ++ *hottest = max(*hottest, temp); ++ } ++ return 0; ++} ++ ++/* Called under priv->lock: blocks every target, including racing governors. */ ++static int a9_restore_locked(struct a9_guard *g) ++{ ++ struct apple_cpu_priv *priv = g->priv; ++ u64 command; ++ u32 status; ++ unsigned int fallback; ++ int ret, state; ++ ++ g->limit = A9_STOCK_KHZ; ++ status = readl(priv->reg_base + APPLE_DVFS_STATUS); ++ fallback = (status >> 4) & 15; ++ if (!fallback || fallback > 7) ++ fallback = 7; ++ /* Never raise a CPU already cooled below the stock maximum. */ ++ ret = apple_soc_cpufreq_request(priv, fallback); ++ if (ret) ++ return ret; ++ status = readl(priv->reg_base + APPLE_DVFS_STATUS); ++ command = readq(priv->reg_base + APPLE_DVFS_CMD); ++ /* A boost/load/thermal limitation may legitimately deliver a lower state. */ ++ if (((status >> 4) & 15) > 7 || (status & 15) > 7 || ++ FIELD_GET(APPLE_DVFS_CMD_PS1, command) != fallback) ++ return -EIO; ++ for (state = 9; state < 16; state++) { ++ if (!(g->dirty & BIT(state))) ++ continue; ++ writeq(g->stock1[state], priv->reg_base + a9_psinfo1_offset(state)); ++ writeq(g->stock2[state], priv->reg_base + 0x868 + state * 8); ++ if (readq(priv->reg_base + a9_psinfo1_offset(state)) != g->stock1[state] || ++ readq(priv->reg_base + 0x868 + state * 8) != g->stock2[state]) ++ return -EIO; ++ g->dirty &= ~BIT(state); ++ } ++ g->configured = 0; ++ g->delta_uv = 0; ++ return 0; ++} ++ ++static void a9_revert(struct a9_guard *g, const char *reason) ++{ ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&g->priv->lock, flags); ++ g->armed = false; ++ g->latched = true; ++ g->reason = reason; ++ g->revert_error = a9_restore_locked(g); ++ raw_spin_unlock_irqrestore(&g->priv->lock, flags); ++ if (g->ready) ++ freq_qos_update_request(&g->qos, A9_STOCK_KHZ); ++ if (g->revert_error) ++ dev_err(g->priv->cpu_dev, "A9 stock revert failed: %d\n", g->revert_error); ++} ++ ++/* Thermal callbacks hold a zone mutex. Do not take g->control here: the ++ * polling worker may hold it while acquiring that same zone mutex. RCU keeps ++ * the driver state alive and priv->lock serializes the bounded MMIO sequence. ++ */ ++void apple_a9_thermal_revert(void) ++{ ++ struct a9_guard *g; ++ unsigned long flags; ++ ++ rcu_read_lock(); ++ g = rcu_dereference(a9_active_guard); ++ if (g) { ++ raw_spin_lock_irqsave(&g->priv->lock, flags); ++ g->trip_generation++; ++ g->armed = false; ++ g->latched = true; ++ g->reason = "thermal-trip"; ++ g->revert_error = a9_restore_locked(g); ++ WRITE_ONCE(g->thermal_qos_pending, true); ++ raw_spin_unlock_irqrestore(&g->priv->lock, flags); ++ /* QoS notification can sleep; finish it outside this RCU section. */ ++ mod_delayed_work(system_wq, &g->work, 0); ++ } ++ rcu_read_unlock(); ++} ++EXPORT_SYMBOL_GPL(apple_a9_thermal_revert); ++ ++static const char *a9_guard_fault(int sensor_error, int hottest, bool expired) ++{ ++ struct apple_a9_thermal_profile p = apple_a9_get_thermal_profile(); ++ ++ if (sensor_error) ++ return "sensor-error"; ++ if (hottest >= p.critical) ++ return "thermal-critical"; ++ if (hottest >= p.hot) ++ return "thermal-hot"; ++ if (hottest >= p.passive) ++ return "thermal-passive"; ++ if (expired) ++ return "heartbeat-timeout"; ++ return NULL; ++} ++ ++static void a9_guard_work(struct work_struct *work) ++{ ++ struct a9_guard *g = container_of(to_delayed_work(work), struct a9_guard, work); ++ int hottest, ret; ++ const char *fault; ++ ++ mutex_lock(&g->control); ++ if (READ_ONCE(g->thermal_qos_pending)) { ++ freq_qos_update_request(&g->qos, A9_STOCK_KHZ); ++ WRITE_ONCE(g->thermal_qos_pending, false); ++ } ++ ret = a9_temperatures(&hottest); ++ g->hottest = ret ? INT_MIN : hottest; ++ if (g->armed) { ++ fault = !a9_voltage_handoff_ok(g) ? ++ "voltage-mode" : a9_guard_fault(ret, hottest, ++ time_after(jiffies, g->heartbeat + msecs_to_jiffies(A9_HEARTBEAT_MS))); ++ if (fault) ++ a9_revert(g, fault); ++ } else if (!ret && hottest >= apple_a9_get_thermal_profile().hot && !g->latched) { ++ a9_revert(g, "thermal-hot"); ++ } ++ mutex_unlock(&g->control); ++ mod_delayed_work(system_wq, &g->work, msecs_to_jiffies(A9_POLL_MS)); ++} ++ ++static int a9_check_stock(struct a9_guard *g) ++{ ++ struct cpufreq_frequency_table *p; ++ static const u8 core_code[] = { 0, 0, 31, 40, 61, 80, 104, 116, 116 }; ++ static const u8 aux_code[] = { 0, 0, 64, 64, 64, 64, 77, 101, 101 }; ++ unsigned int n; ++ ++ /* All published stock clocks must match this chip's boot PSINFO words. */ ++ cpufreq_for_each_valid_entry(p, g->policy->freq_table) { ++ n = p->driver_data; ++ if (n < 1 || n > 15 || n == 8) ++ return -EINVAL; ++ if (n >= 9 && a9_step_state(p->frequency) != n) ++ return -EINVAL; ++ if (n <= 8 && a9_psinfo_khz(g->stock1[n]) != p->frequency) ++ return -EINVAL; ++ } ++ /* This decoder is for the saved S8003 j71t calibration, not arbitrary A9s. */ ++ for (n = 1; n <= 8; n++) ++ if (FIELD_GET(GENMASK_ULL(63, 56), g->stock1[n]) != core_code[n] || ++ FIELD_GET(GENMASK_ULL(39, 32), g->stock2[n]) != aux_code[n]) ++ return -EINVAL; ++ if (a9_psinfo_khz(g->stock1[8]) != A9_BOOST_KHZ || ++ (g->stock1[8] >> 56) != (g->stock1[7] >> 56) || ++ FIELD_GET(GENMASK_ULL(39, 32), g->stock2[8]) != ++ FIELD_GET(GENMASK_ULL(39, 32), g->stock2[7]) || ++ FIELD_GET(GENMASK_ULL(43, 40), g->stock2[8]) != 2) ++ return -EINVAL; ++ /* Slots 9..15 must contain the observed boot placeholder, not other data. */ ++ for (n = 9; n < 16; n++) ++ if (g->stock1[n] != g->stock1[2] || g->stock2[n] != g->stock2[2]) ++ return -EBUSY; ++ return 0; ++} ++ ++static int a9_arm(struct a9_guard *g) ++{ ++ unsigned long flags; ++ u32 generation = READ_ONCE(g->trip_generation); ++ int ret, hottest; ++ ++ if (!apple_a9_get_thermal_profile().tuning || !g->ready) ++ return -EPERM; ++ /* Validate the observed cold-boot handoff, without mode/routing writes. */ ++ if (!a9_voltage_handoff_ok(g)) ++ return -EOPNOTSUPP; ++ ret = a9_check_stock(g); ++ if (ret) ++ return ret; ++ ret = a9_temperatures(&hottest); ++ if (ret || hottest >= apple_a9_get_thermal_profile().arm) ++ return ret ?: -EBUSY; ++ if (g->dirty || g->revert_error || g->armed) ++ return -EBUSY; ++ raw_spin_lock_irqsave(&g->priv->lock, flags); ++ if (!a9_voltage_handoff_ok(g)) { ++ raw_spin_unlock_irqrestore(&g->priv->lock, flags); ++ return -EOPNOTSUPP; ++ } ++ if (generation != g->trip_generation) { ++ raw_spin_unlock_irqrestore(&g->priv->lock, flags); ++ return -EAGAIN; ++ } ++ g->latched = false; ++ g->armed = true; ++ g->limit = A9_BOOST_KHZ; ++ g->heartbeat = jiffies; ++ g->reason = "armed"; ++ raw_spin_unlock_irqrestore(&g->priv->lock, flags); ++ ret = freq_qos_update_request(&g->qos, A9_BOOST_KHZ); ++ if (ret < 0) ++ a9_revert(g, "qos-error"); ++ return ret < 0 ? ret : 0; ++} ++ ++static int a9_configure(struct a9_guard *g, unsigned int khz, unsigned int delta_uv) ++{ ++ struct apple_cpu_priv *priv = g->priv; ++ unsigned long flags; ++ u64 word1, word2; ++ unsigned int state; ++ int ret, hottest; ++ ++ /* Validate the ceiling before rounding, conversion or any hardware store. */ ++ if (delta_uv > A9_OVERVOLT_LIMIT_UV) ++ return -ERANGE; ++ if (khz == A9_BOOST_KHZ && delta_uv) ++ return -EINVAL; ++ ret = a9_voltage_words(g->stock1[7], g->stock2[7], delta_uv, &word1, &word2); ++ if (ret) ++ return ret; ++ if (!a9_tuning || !g->armed || ++ time_after(jiffies, g->heartbeat + msecs_to_jiffies(A9_HEARTBEAT_MS))) ++ return -EPERM; ++ if (!a9_voltage_handoff_ok(g)) ++ return -EOPNOTSUPP; ++ ret = a9_temperatures(&hottest); ++ if (ret || hottest >= apple_a9_get_thermal_profile().arm) ++ return ret ?: -EBUSY; ++ state = a9_step_state(khz); ++ if (!state || khz % 24000 || khz > A9_PLL_MAX_KHZ || ++ FIELD_GET(GENMASK_ULL(17, 13), word1) > 1 || ++ FIELD_GET(GENMASK_ULL(3, 0), word1)) ++ return -EINVAL; ++ raw_spin_lock_irqsave(&priv->lock, flags); ++ /* A thermal callback may have latched while temperatures were sampled. */ ++ if (!g->armed || time_after(jiffies, g->heartbeat + msecs_to_jiffies(A9_HEARTBEAT_MS))) { ++ ret = -EPERM; ++ goto unlock; ++ } ++ if (!a9_voltage_handoff_ok(g)) { ++ ret = -EOPNOTSUPP; ++ goto unlock; ++ } ++ ret = a9_restore_locked(g); ++ if (ret) ++ goto unlock; ++ /* Clone the stock all-load 1800 MHz state. Change the PLL multiplier and ++ * both voltage bytes by a bounded equal delta. Timing/load stay stock. ++ * State 8, its load=2 restriction and all Apple states 0..8 are untouched. ++ */ ++ word1 = (word1 & ~GENMASK_ULL(12, 4)) | ++ FIELD_PREP(GENMASK_ULL(12, 4), khz / 24000); ++ g->dirty |= BIT(state); ++ writeq(word2, priv->reg_base + 0x868 + state * 8); ++ writeq(word1, priv->reg_base + a9_psinfo1_offset(state)); ++ if (readq(priv->reg_base + a9_psinfo1_offset(state)) != word1 || ++ readq(priv->reg_base + 0x868 + state * 8) != word2) { ++ ret = -EIO; ++ goto unlock; ++ } ++ g->configured = BIT(state); ++ g->delta_uv = delta_uv; ++ g->limit = khz; ++ g->heartbeat = jiffies; ++unlock: ++ raw_spin_unlock_irqrestore(&priv->lock, flags); ++ if (ret) ++ a9_revert(g, "configure-error"); ++ else { ++ ret = freq_qos_update_request(&g->qos, khz); ++ if (ret < 0) ++ a9_revert(g, "qos-error"); ++ } ++ return ret < 0 ? ret : 0; ++} ++ ++static int a9_status_show(struct seq_file *s, void *unused) ++{ ++ struct a9_guard *g = s->private; ++ struct apple_a9_thermal_profile p = apple_a9_get_thermal_profile(); ++ unsigned int n; ++ ++ mutex_lock(&g->control); ++ seq_printf(s, "format=3 enable=%u armed=%u latched=%u reason=%s limit_khz=%u configured=%x dirty=%x revert_error=%d hottest_millic=%d heartbeat_ms=%u ceiling_delta_uv=%u voltage_writes=psinfo dac_step_uv=%u delta_uv=%u ", ++ a9_tuning, g->armed, g->latched, g->reason, g->limit, ++ g->configured, g->dirty, g->revert_error, g->hottest, ++ jiffies_to_msecs(jiffies - g->heartbeat), A9_OVERVOLT_LIMIT_UV, ++ A9_DAC_STEP_UV, g->delta_uv); ++ seq_printf(s, "clock_profile=s8003-r6-shared-slot boost_state=%u thermal_profile_format=1 profile=%s profile_tuning=%u passive_millic=%u hot_millic=%u critical_millic=%u arm_millic=%u cooldown_millic=%u\n", ++ A9_BOOST_STATE, ++ apple_a9_thermal_profile_name(p), p.tuning, p.passive, p.hot, ++ p.critical, p.arm, p.cooldown); ++ seq_printf(s, "cmd=%016llx status=%08x actual_khz=%u voltage_ctl=%08x voltage_routes=%08x voltage_ctl_boot=%08x voltage_routes_boot=%08x voltage_handoff_ok=%u handoff_policy=s8003-r4 control_error=%d\n", ++ readq(g->priv->reg_base + APPLE_DVFS_CMD), ++ readl(g->priv->reg_base + APPLE_DVFS_STATUS), ++ apple_soc_cpufreq_get_rate(g->policy->cpu), ++ readl(g->voltage_base), readl(g->voltage_base + 4), ++ g->voltage_ctl_boot, g->voltage_routes_boot, ++ a9_voltage_handoff_ok(g), g->control_error); ++ for (n = 1; n < 16; n++) { ++ u64 live1 = readq(g->priv->reg_base + a9_psinfo1_offset(n)); ++ u64 live2 = readq(g->priv->reg_base + 0x868 + n * 8); ++ unsigned int core = FIELD_GET(GENMASK_ULL(63, 56), live1); ++ unsigned int aux = FIELD_GET(GENMASK_ULL(39, 32), live2); ++ ++ seq_printf(s, "state=%u boot_psinfo1=%016llx boot_psinfo2=%016llx boot_khz=%u psinfo1=%016llx psinfo2=%016llx core_code=%u aux_code=%u core_nominal_uv=%u aux_nominal_uv=%u rail_measured=0\n", ++ n, g->stock1[n], g->stock2[n], a9_psinfo_khz(g->stock1[n]), ++ live1, live2, core, aux, A9_CORE_BASE_UV + A9_DAC_STEP_UV * core, ++ A9_AUX_BASE_UV + A9_DAC_STEP_UV * aux); ++ } ++ mutex_unlock(&g->control); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(a9_status); ++ ++static ssize_t a9_control_write(struct file *file, const char __user *buffer, ++ size_t count, loff_t *position) ++{ ++ struct a9_guard *g = file->private_data; ++ char command[80], tail; ++ unsigned int khz, delta; ++ int ret = 0; ++ ++ if (!capable(CAP_SYS_RAWIO)) ++ return -EPERM; ++ if (!count || count >= sizeof(command)) ++ return -EINVAL; ++ if (copy_from_user(command, buffer, count)) ++ return -EFAULT; ++ if (memchr(command, 0, count)) ++ return -EINVAL; ++ command[count] = 0; ++ strim(command); ++ mutex_lock(&g->control); ++ if (sysfs_streq(command, "revert")) { ++ a9_revert(g, "user-revert"); ++ ret = g->revert_error; ++ } else if (sysfs_streq(command, "arm")) { ++ ret = a9_arm(g); ++ } else if (sysfs_streq(command, "heartbeat")) { ++ if (!g->armed) ++ ret = -EPERM; ++ else if (time_after(jiffies, g->heartbeat + msecs_to_jiffies(A9_HEARTBEAT_MS))) { ++ a9_revert(g, "late-heartbeat"); ++ ret = -ETIMEDOUT; ++ } else ++ WRITE_ONCE(g->heartbeat, jiffies); ++ } else if (sscanf(command, "configure %u %u %c", &khz, &delta, &tail) == 2) { ++ ret = a9_configure(g, khz, delta); ++ if (ret && (g->armed || g->dirty)) ++ a9_revert(g, "configure-error"); ++ } else { ++ ret = -EINVAL; ++ } ++ g->control_error = ret; ++ mutex_unlock(&g->control); ++ return ret ? ret : count; ++} ++ ++static const struct file_operations a9_control_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = a9_control_write, ++ .llseek = noop_llseek, ++}; ++ ++static int a9_guard_init(struct cpufreq_policy *policy) ++{ ++ struct apple_cpu_priv *priv = policy->driver_data; ++ struct a9_guard *g; ++ unsigned int n; ++ ++ if (!of_machine_is_compatible("apple,s8003")) ++ return 0; ++ g = kzalloc_obj(*g); ++ if (!g) ++ return -ENOMEM; ++ g->voltage_base = ioremap(0x20e0a0000, 0x20); ++ if (!g->voltage_base) { ++ kfree(g); ++ return -ENOMEM; ++ } ++ g->voltage_ctl_boot = readl(g->voltage_base); ++ g->voltage_routes_boot = readl(g->voltage_base + 4); ++ g->priv = priv; ++ g->policy = policy; ++ g->limit = A9_STOCK_KHZ; ++ g->reason = "disabled"; ++ g->hottest = INT_MIN; ++ mutex_init(&g->control); ++ INIT_DELAYED_WORK(&g->work, a9_guard_work); ++ for (n = 0; n < 16; n++) { ++ g->stock1[n] = readq(priv->reg_base + a9_psinfo1_offset(n)); ++ g->stock2[n] = readq(priv->reg_base + 0x868 + n * 8); ++ } ++ priv->guard = g; ++ return 0; ++} ++ ++static void a9_guard_ready(struct cpufreq_policy *policy) ++{ ++ struct apple_cpu_priv *priv = policy->driver_data; ++ struct a9_guard *g = priv->guard; ++ int ret; ++ ++ if (!g || g->ready) ++ return; ++ ret = freq_qos_add_request(&policy->constraints, &g->qos, FREQ_QOS_MAX, A9_STOCK_KHZ); ++ if (ret < 0) { ++ g->reason = "qos-init-error"; ++ return; ++ } ++ g->ready = true; ++ rcu_assign_pointer(a9_active_guard, g); ++ g->directory = debugfs_create_dir("a9_tuning", NULL); ++ debugfs_create_file("status", 0400, g->directory, g, &a9_status_fops); ++ debugfs_create_file("control", 0200, g->directory, g, &a9_control_fops); ++ mod_delayed_work(system_wq, &g->work, msecs_to_jiffies(A9_POLL_MS)); ++} ++ ++static void a9_guard_exit(struct apple_cpu_priv *priv) ++{ ++ struct a9_guard *g = priv->guard; ++ ++ if (!g) ++ return; ++ RCU_INIT_POINTER(a9_active_guard, NULL); ++ synchronize_rcu(); ++ debugfs_remove_recursive(g->directory); ++ cancel_delayed_work_sync(&g->work); ++ a9_revert(g, "driver-exit"); ++ if (g->ready) ++ freq_qos_remove_request(&g->qos); ++ iounmap(g->voltage_base); ++ kfree(g); ++} ++ ++static bool a9_target_allowed(struct apple_cpu_priv *priv, unsigned int state, ++ unsigned int khz) ++{ ++ struct a9_guard *g = priv->guard; ++ ++ if (!g) ++ return state <= 7 || !priv->a9; ++ if (khz > g->limit) ++ return false; ++ if (state > 8 && (!(g->configured & BIT(state)) || khz != g->limit)) ++ return false; ++ return state <= 7 || (g->armed && a9_tuning && a9_voltage_handoff_ok(g) && ++ !time_after(jiffies, READ_ONCE(g->heartbeat) + msecs_to_jiffies(A9_HEARTBEAT_MS))); ++} ++#else ++static int a9_guard_init(struct cpufreq_policy *policy) ++{ ++ return 0; ++} ++ ++static void a9_guard_ready(struct cpufreq_policy *policy) ++{ ++} ++ ++static void a9_guard_exit(struct apple_cpu_priv *priv) ++{ ++} ++ ++static bool a9_target_allowed(struct apple_cpu_priv *priv, unsigned int state, ++ unsigned int khz) ++{ ++ return state <= 7 || !priv->a9; ++} ++#endif +diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c +index 7c50ce74f91176b6f53a76bfdbd48777f35517da..bf50503d1df41f82098c6fbbca2af919167deb96 100644 +--- a/drivers/cpufreq/apple-soc-cpufreq.c ++++ b/drivers/cpufreq/apple-soc-cpufreq.c +@@ -7,6 +7,8 @@ + * Based on scpi-cpufreq.c + */ + ++#include ++#include + #include + #include + #include +@@ -20,6 +22,15 @@ + #include + #include + #include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + #include + + #define APPLE_DVFS_CMD 0x20 +@@ -69,7 +80,12 @@ struct apple_soc_cpufreq_info { + u64 ps1_shift; + }; + ++struct a9_guard; ++ + struct apple_cpu_priv { ++ bool a9; ++ raw_spinlock_t lock; ++ struct a9_guard *guard; + struct device *cpu_dev; + void __iomem *reg_base; + const struct apple_soc_cpufreq_info *info; +@@ -86,6 +102,15 @@ static const struct apple_soc_cpufreq_info soc_s5l8960x_info = { + .ps1_shift = APPLE_DVFS_CMD_PS1_S5L8960X_SHIFT, + }; + ++static const struct apple_soc_cpufreq_info soc_s8000_info = { ++ .has_ps2 = true, ++ .max_pstate = 15, ++ .cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8103, ++ .cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8103, ++ .ps1_mask = GENMASK(3, 0), ++ .ps1_shift = 0, ++}; ++ + static const struct apple_soc_cpufreq_info soc_t8103_info = { + .has_ps2 = true, + .max_pstate = 15, +@@ -113,6 +138,10 @@ static const struct apple_soc_cpufreq_info soc_default_info = { + }; + + static const struct of_device_id apple_soc_cpufreq_of_match[] __maybe_unused = { ++ { ++ .compatible = "apple,s8000-cluster-cpufreq", ++ .data = &soc_s8000_info, ++ }, + { + .compatible = "apple,s5l8960x-cluster-cpufreq", + .data = &soc_s5l8960x_info, +@@ -132,6 +161,9 @@ static const struct of_device_id apple_soc_cpufreq_of_match[] __maybe_unused = { + {} + }; + ++static unsigned int a9_psinfo1_offset(unsigned int state); ++static unsigned int a9_psinfo_khz(u64 word); ++ + static unsigned int apple_soc_cpufreq_get_rate(unsigned int cpu) + { + struct cpufreq_policy *policy; +@@ -159,6 +191,10 @@ static unsigned int apple_soc_cpufreq_get_rate(unsigned int cpu) + pstate = FIELD_GET(APPLE_DVFS_CMD_PS1, reg); + } + ++ /* Extended OPPs share slot 14: decode the live word, not its first alias. */ ++ if (priv->a9 && pstate > 8 && pstate <= 15) ++ return a9_psinfo_khz(readq(priv->reg_base + a9_psinfo1_offset(pstate))); ++ + cpufreq_for_each_valid_entry(p, policy->freq_table) + if (p->driver_data == pstate) + return p->frequency; +@@ -168,32 +204,64 @@ static unsigned int apple_soc_cpufreq_get_rate(unsigned int cpu) + return 0; + } + +-static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, +- unsigned int index) ++/* Caller serializes A9 command and PSINFO stores with priv->lock. */ ++static int apple_soc_cpufreq_request(struct apple_cpu_priv *priv, unsigned int pstate) + { +- struct apple_cpu_priv *priv = policy->driver_data; +- unsigned int pstate = policy->freq_table[index].driver_data; + u64 reg; + +- /* Fallback for newer SoCs */ +- if (index > priv->info->max_pstate) +- index = priv->info->max_pstate; +- ++ if (pstate > priv->info->max_pstate) ++ return -EINVAL; + if (readq_poll_timeout_atomic(priv->reg_base + APPLE_DVFS_CMD, reg, + !(reg & APPLE_DVFS_CMD_BUSY), 2, +- APPLE_DVFS_TRANSITION_TIMEOUT)) { +- return -EIO; +- } +- ++ APPLE_DVFS_TRANSITION_TIMEOUT)) ++ return -ETIMEDOUT; + reg &= ~priv->info->ps1_mask; +- reg |= pstate << priv->info->ps1_shift; ++ reg |= (u64)pstate << priv->info->ps1_shift; + if (priv->info->has_ps2) + FIELD_MODIFY(APPLE_DVFS_CMD_PS2, ®, pstate); + reg |= APPLE_DVFS_CMD_SET; ++ writeq(reg, priv->reg_base + APPLE_DVFS_CMD); ++ /* Match iOS's synchronous A9 path. Actual state can remain lower when ++ * hardware enforces Apple's PSINFO2 max-load or thermal restrictions. ++ */ ++ if (priv->a9 && readq_poll_timeout_atomic(priv->reg_base + APPLE_DVFS_CMD, reg, ++ !(reg & APPLE_DVFS_CMD_BUSY), 2, ++ APPLE_DVFS_TRANSITION_TIMEOUT)) ++ return -ETIMEDOUT; ++ return 0; ++} + +- writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD); ++#include "apple-a9-tuning.h" + +- return 0; ++static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy, ++ unsigned int index) ++{ ++ struct apple_cpu_priv *priv = policy->driver_data; ++ struct cpufreq_frequency_table *entry = &policy->freq_table[index]; ++ unsigned long flags; ++ int ret; ++ ++ raw_spin_lock_irqsave(&priv->lock, flags); ++ if (!a9_target_allowed(priv, entry->driver_data, entry->frequency)) ++ ret = -EPERM; ++ else ++ ret = apple_soc_cpufreq_request(priv, entry->driver_data); ++ raw_spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++ ++static int apple_soc_cpufreq_set_boost(struct cpufreq_policy *policy, int state) ++{ ++ struct apple_cpu_priv *priv = policy->driver_data; ++ unsigned long flags; ++ bool allowed; ++ ++ raw_spin_lock_irqsave(&priv->lock, flags); ++ allowed = !priv->a9 || !state || a9_target_allowed(priv, 8, 1848000); ++ raw_spin_unlock_irqrestore(&priv->lock, flags); ++ if (!allowed) ++ return -EPERM; ++ return cpufreq_boost_set_sw(policy, state); + } + + static unsigned int apple_soc_cpufreq_fast_switch(struct cpufreq_policy *policy, +@@ -220,13 +288,15 @@ static int apple_soc_cpufreq_find_cluster(struct cpufreq_policy *policy, + return ret; + + match = of_match_node(apple_soc_cpufreq_of_match, args.np); +- of_node_put(args.np); +- if (!match) ++ if (!match) { ++ of_node_put(args.np); + return -ENODEV; ++ } + + *info = match->data; + + *reg_base = of_iomap(args.np, 0); ++ of_node_put(args.np); + if (!*reg_base) + return -ENOMEM; + +@@ -291,6 +361,8 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) + dev_pm_opp_put(opp); + } + ++ raw_spin_lock_init(&priv->lock); ++ priv->a9 = info == &soc_s8000_info; + priv->cpu_dev = cpu_dev; + priv->reg_base = reg_base; + priv->info = info; +@@ -303,9 +375,12 @@ static int apple_soc_cpufreq_init(struct cpufreq_policy *policy) + + policy->cpuinfo.transition_latency = transition_latency; + policy->dvfs_possible_from_any_cpu = true; +- policy->fast_switch_possible = true; ++ policy->fast_switch_possible = !priv->a9; + policy->suspend_freq = freq_table[0].frequency; + ++ ret = a9_guard_init(policy); ++ if (ret) ++ goto out_free_cpufreq_table; + return 0; + + out_free_cpufreq_table: +@@ -323,6 +398,7 @@ static void apple_soc_cpufreq_exit(struct cpufreq_policy *policy) + { + struct apple_cpu_priv *priv = policy->driver_data; + ++ a9_guard_exit(priv); + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); + dev_pm_opp_of_cpumask_remove_table(policy->cpus); + iounmap(priv->reg_base); +@@ -340,7 +416,8 @@ static struct cpufreq_driver apple_soc_cpufreq_driver = { + .target_index = apple_soc_cpufreq_set_target, + .fast_switch = apple_soc_cpufreq_fast_switch, + .register_em = cpufreq_register_em_with_opp, +- .set_boost = cpufreq_boost_set_sw, ++ .set_boost = apple_soc_cpufreq_set_boost, ++ .ready = a9_guard_ready, + .suspend = cpufreq_generic_suspend, + }; + +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index a48586bb8edbae8f075cc062189a4c8c27839429..630ff33d90a1b4ffc8fad4d9ccb3f46495e5f3ba 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -1348,6 +1348,17 @@ config GPIO_ALTERA_A10SR + includes reads of pushbuttons and DIP switches as well + as writes to LEDs. + ++config GPIO_APPLE_D2365 ++ tristate "Apple D2365 PMIC GPIO support" ++ depends on MFD_SIMPLE_MFD_I2C ++ select IRQ_DOMAIN ++ help ++ Say yes here to enable support for the 24 GPIOs provided by the ++ Dialog D2365 PMIC used in Apple A10 devices. The GPIO controller is ++ exposed as a child of the Apple I2C PMIC and can control board-level ++ signals such as peripheral power enables. ++ Optional event IRQs let the iPad 5 Smart Cover halls wake the system. ++ + config GPIO_ARIZONA + tristate "Wolfson Microelectronics Arizona class devices" + depends on MFD_ARIZONA +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +index dc9e6d643b5bca5a61645a98b08b05f625c7b36b..5e6debc1c25404142f45426de9bfd440f78a1495 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -36,6 +36,7 @@ obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o + obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o + obj-$(CONFIG_GPIO_AMD_FCH) += gpio-amd-fch.o + obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o ++obj-$(CONFIG_GPIO_APPLE_D2365) += gpio-apple-d2365.o + obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o + obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o + obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o +diff --git a/drivers/gpio/gpio-apple-d2365.c b/drivers/gpio/gpio-apple-d2365.c +new file mode 100644 +index 0000000000000000000000000000000000000000..ee90b0d36f141dd1c3351d96208327b0aa816ef9 +--- /dev/null ++++ b/drivers/gpio/gpio-apple-d2365.c +@@ -0,0 +1,624 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * GPIO driver for the Apple/Dialog D2365 PMIC. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define D2365_GPIO_COUNT 24 ++#define D2365_GPIO_SPLIT 18 ++#define D2365_GPIO_CFG_0_17 0x900 ++#define D2365_GPIO_CFG_18_23 0x8bd ++#define D2365_GPIO_INPUT0 0x186 ++ ++/* ++ * PMIC events, one bit each. iOS fetches and discards them by reading 30 ++ * bytes at 0x140 (clear on read); 0x180 is the live status and 0x1c0 the ++ * interrupt mask (set = masked), all ones after its probe. GPIO line N is ++ * event 0x30 + N. ++ */ ++#define D2365_EVENTS 0x140 ++#define D2365_IRQ_MASK 0x1c0 ++#define D2365_EVENT_BYTES 30 ++#define D2365_EVENT_COUNT (D2365_EVENT_BYTES * 8) ++#define D2365_GPIO_EVENT 0x30 ++#define D2365_GPIO_IRQ_MASK (D2365_IRQ_MASK + D2365_GPIO_EVENT / 8) ++#define D2365_HALL_LINES (BIT(0) | BIT(10)) ++#define D2365_IRQ_BURST 64 ++#define D2365_IRQ_RETRIES 5 ++ ++#define D2365_GPIO_VALUE BIT(0) ++#define D2365_GPIO_INPUT BIT(3) ++#define D2365_GPIO_FUNCTION BIT(4) ++#define D2365_GPIO_MODE_MASK (D2365_GPIO_VALUE | D2365_GPIO_INPUT | \ ++ D2365_GPIO_FUNCTION) ++ ++struct d2365_gpio { ++ struct gpio_chip chip; ++ struct regmap *regmap; ++ struct device *pmu; ++ struct irq_domain *domain; ++ /* Serializes the masks, the event thread and the suspend gate. */ ++ struct mutex irq_lock; ++ struct delayed_work irq_retry; ++ u8 boot_mask[D2365_EVENT_BYTES]; ++ unsigned long irq_enabled; /* lines unmasked while awake */ ++ unsigned long wake_enabled; /* lines unmasked while asleep */ ++ int wake_delta; /* parent wake references to apply */ ++ unsigned long irq_window; ++ unsigned int irq_burst; ++ unsigned int retries; ++ int parent_irq; ++ bool irq_ready; ++ bool suspended; ++ bool irq_held; ++ bool removing; ++}; ++ ++/* Unmask the GPIO events of @lines; every other GPIO event stays masked. */ ++static int d2365_irq_sync(struct d2365_gpio *gpio, unsigned long lines) ++{ ++ u8 mask[2]; ++ ++ lines &= D2365_HALL_LINES; ++ mask[0] = ~lines & 0xff; ++ mask[1] = ~(lines >> 8) & 0xff; ++ return regmap_bulk_write(gpio->regmap, D2365_GPIO_IRQ_MASK, mask, ++ sizeof(mask)); ++} ++ ++static void d2365_irq_bus_lock(struct irq_data *data) ++{ ++ struct d2365_gpio *gpio = irq_data_get_irq_chip_data(data); ++ ++ mutex_lock(&gpio->irq_lock); ++} ++ ++static void d2365_irq_bus_sync_unlock(struct irq_data *data) ++{ ++ struct d2365_gpio *gpio = irq_data_get_irq_chip_data(data); ++ int delta = gpio->wake_delta; ++ int ret; ++ ++ gpio->wake_delta = 0; ++ /* No bus access between our suspend and resume callbacks. */ ++ if (!gpio->suspended) { ++ ret = d2365_irq_sync(gpio, READ_ONCE(gpio->irq_enabled)); ++ if (ret) ++ dev_err_ratelimited(gpio->chip.parent, ++ "hall IRQ mask write failed: %d\n", ret); ++ } ++ mutex_unlock(&gpio->irq_lock); ++ ++ /* ++ * A cascaded domain has no hierarchical parent_data for ++ * irq_chip_set_wake_parent(). Count the wake references on the shared ++ * AP GPIO here, outside the child descriptor lock, so that ++ * suspend_device_irqs() leaves that line armed. ++ */ ++ for (; delta > 0; delta--) { ++ ret = irq_set_irq_wake(gpio->parent_irq, 1); ++ if (ret) ++ dev_err(gpio->chip.parent, ++ "cannot arm the event IRQ for wake: %d\n", ret); ++ } ++ for (; delta < 0; delta++) ++ irq_set_irq_wake(gpio->parent_irq, 0); ++} ++ ++static void d2365_irq_mask(struct irq_data *data) ++{ ++ struct d2365_gpio *gpio = irq_data_get_irq_chip_data(data); ++ ++ clear_bit(data->hwirq - D2365_GPIO_EVENT, &gpio->irq_enabled); ++} ++ ++static void d2365_irq_unmask(struct irq_data *data) ++{ ++ struct d2365_gpio *gpio = irq_data_get_irq_chip_data(data); ++ ++ set_bit(data->hwirq - D2365_GPIO_EVENT, &gpio->irq_enabled); ++} ++ ++static int d2365_irq_set_type(struct irq_data *data, unsigned int type) ++{ ++ /* The inherited event configuration reports both transitions. */ ++ return type == IRQ_TYPE_NONE || type == IRQ_TYPE_EDGE_BOTH ? 0 : -EINVAL; ++} ++ ++/* Called with the bus lock held; the parent follows in sync_unlock. */ ++static int d2365_irq_set_wake(struct irq_data *data, unsigned int on) ++{ ++ struct d2365_gpio *gpio = irq_data_get_irq_chip_data(data); ++ unsigned int line = data->hwirq - D2365_GPIO_EVENT; ++ ++ if (on && !test_bit(line, &gpio->wake_enabled)) { ++ __set_bit(line, &gpio->wake_enabled); ++ gpio->wake_delta++; ++ } else if (!on && test_bit(line, &gpio->wake_enabled)) { ++ __clear_bit(line, &gpio->wake_enabled); ++ gpio->wake_delta--; ++ } ++ return 0; ++} ++ ++static const struct irq_chip d2365_irq_chip = { ++ .name = "d2365-hall", ++ .irq_bus_lock = d2365_irq_bus_lock, ++ .irq_bus_sync_unlock = d2365_irq_bus_sync_unlock, ++ .irq_mask = d2365_irq_mask, ++ .irq_unmask = d2365_irq_unmask, ++ .irq_set_type = d2365_irq_set_type, ++ .irq_set_wake = d2365_irq_set_wake, ++}; ++ ++static int d2365_irq_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ struct d2365_gpio *gpio = domain->host_data; ++ ++ /* Other events need their own evidence and consumer before use. */ ++ if (hwirq < D2365_GPIO_EVENT || ++ hwirq >= D2365_GPIO_EVENT + D2365_GPIO_COUNT || ++ !(BIT(hwirq - D2365_GPIO_EVENT) & D2365_HALL_LINES)) ++ return -EINVAL; ++ ++ irq_set_chip_data(irq, gpio); ++ irq_set_chip(irq, &d2365_irq_chip); ++ irq_set_nested_thread(irq, 1); ++ irq_set_parent(irq, gpio->parent_irq); ++ irq_set_noprobe(irq); ++ return 0; ++} ++ ++static const struct irq_domain_ops d2365_irq_domain_ops = { ++ .map = d2365_irq_map, ++ .xlate = irq_domain_xlate_onecell, ++}; ++ ++/* Called with irq_lock held, including from the threaded handler. */ ++static void d2365_irq_hold(struct d2365_gpio *gpio, bool retry) ++{ ++ if (!gpio->irq_held) { ++ disable_irq_nosync(gpio->parent_irq); ++ gpio->irq_held = true; ++ } ++ if (!retry) ++ return; ++ if (gpio->retries < D2365_IRQ_RETRIES) { ++ gpio->retries++; ++ mod_delayed_work(system_dfl_wq, &gpio->irq_retry, ++ msecs_to_jiffies(100 << gpio->retries)); ++ } else { ++ dev_err(gpio->chip.parent, ++ "hall event IRQ held until the next resume\n"); ++ } ++} ++ ++static void d2365_irq_retry(struct work_struct *work) ++{ ++ struct d2365_gpio *gpio = container_of(to_delayed_work(work), ++ struct d2365_gpio, irq_retry); ++ ++ mutex_lock(&gpio->irq_lock); ++ if (!gpio->suspended && !gpio->removing && gpio->irq_held) { ++ gpio->irq_held = false; ++ enable_irq(gpio->parent_irq); ++ } ++ mutex_unlock(&gpio->irq_lock); ++} ++ ++static irqreturn_t d2365_irq_thread(int irq, void *data) ++{ ++ struct d2365_gpio *gpio = data; ++ u8 events[D2365_EVENT_BYTES]; ++ unsigned long pending; ++ unsigned int line, child; ++ int ret; ++ ++ mutex_lock(&gpio->irq_lock); ++ if (gpio->removing) ++ goto unlock; ++ if (gpio->suspended) { ++ /* ++ * resume_device_irqs() runs before our I2C ancestors resume. ++ * Leave the event latched and the level IRQ held until ++ * ->resume. A wake event in the window before ++ * suspend_device_irqs() must abort the transition, as the ++ * armed IRQ does afterwards. ++ */ ++ if (gpio->wake_enabled) ++ pm_wakeup_hard_event(gpio->pmu); ++ d2365_irq_hold(gpio, false); ++ goto unlock; ++ } ++ ++ /* Bound a stuck line or a bouncing sensor. */ ++ if (time_after(jiffies, gpio->irq_window + HZ)) { ++ gpio->irq_window = jiffies; ++ gpio->irq_burst = 0; ++ } ++ if (++gpio->irq_burst > D2365_IRQ_BURST) { ++ ret = -ELOOP; ++ goto hold; ++ } ++ ++ /* Reading the events clears them and releases the line. */ ++ ret = regmap_bulk_read(gpio->regmap, D2365_EVENTS, events, ++ sizeof(events)); ++ if (ret) ++ goto hold; ++ gpio->retries = 0; ++ ++ pending = events[D2365_GPIO_EVENT / 8] | ++ events[D2365_GPIO_EVENT / 8 + 1] << 8; ++ dev_dbg(gpio->chip.parent, "events %*phN\n", D2365_EVENT_BYTES, events); ++ pending &= READ_ONCE(gpio->irq_enabled) & D2365_HALL_LINES; ++ /* ++ * Nothing latched for us (a debug read may have cleared it): the ++ * consumers read the line level again, so let them all resync. ++ */ ++ if (!pending) ++ pending = READ_ONCE(gpio->irq_enabled) & D2365_HALL_LINES; ++ mutex_unlock(&gpio->irq_lock); ++ ++ /* A child may mask its IRQ, which takes the bus lock. */ ++ for_each_set_bit(line, &pending, D2365_GPIO_COUNT) { ++ child = irq_find_mapping(gpio->domain, D2365_GPIO_EVENT + line); ++ if (child) ++ handle_nested_irq(child); ++ } ++ return IRQ_HANDLED; ++ ++hold: ++ dev_err_ratelimited(gpio->chip.parent, ++ "event read failed or storm (%d), holding the IRQ, retry %u/%u\n", ++ ret, gpio->retries + 1, D2365_IRQ_RETRIES); ++ d2365_irq_hold(gpio, true); ++unlock: ++ mutex_unlock(&gpio->irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int d2365_gpio_suspend(struct device *dev) ++{ ++ struct d2365_gpio *gpio = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!gpio->irq_ready) ++ return 0; ++ ++ cancel_delayed_work_sync(&gpio->irq_retry); ++ mutex_lock(&gpio->irq_lock); ++ /* A failed event path must not keep a closed iPad awake. */ ++ if (gpio->irq_held) ++ dev_warn(dev, "hall event IRQ held, only the buttons can wake this sleep\n"); ++ /* Only the wake-enabled halls may pull the line while asleep. */ ++ ret = d2365_irq_sync(gpio, gpio->wake_enabled); ++ if (ret) ++ dev_warn(dev, "hall wake mask write failed: %d\n", ret); ++ gpio->suspended = true; ++ mutex_unlock(&gpio->irq_lock); ++ /* A thread that passed the gate before it closed has finished now. */ ++ synchronize_irq(gpio->parent_irq); ++ return 0; ++} ++ ++static int d2365_gpio_resume(struct device *dev) ++{ ++ struct d2365_gpio *gpio = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!gpio->irq_ready) ++ return 0; ++ ++ /* Our I2C ancestors have completed their normal resume callbacks. */ ++ mutex_lock(&gpio->irq_lock); ++ gpio->suspended = false; ++ gpio->retries = 0; ++ ret = d2365_irq_sync(gpio, READ_ONCE(gpio->irq_enabled)); ++ if (ret) ++ dev_warn(dev, "hall IRQ mask write failed: %d\n", ret); ++ /* An event that arrived while gated is still latched. */ ++ if (gpio->irq_held) { ++ gpio->irq_held = false; ++ enable_irq(gpio->parent_irq); ++ } ++ mutex_unlock(&gpio->irq_lock); ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(d2365_gpio_pm_ops, d2365_gpio_suspend, ++ d2365_gpio_resume); ++ ++static void d2365_irq_restore_mask(void *data) ++{ ++ struct d2365_gpio *gpio = data; ++ ++ regmap_bulk_write(gpio->regmap, D2365_IRQ_MASK, gpio->boot_mask, ++ sizeof(gpio->boot_mask)); ++} ++ ++static void d2365_irq_remove_domain(void *data) ++{ ++ struct d2365_gpio *gpio = data; ++ unsigned int line, irq; ++ unsigned long lines = D2365_HALL_LINES; ++ ++ for_each_set_bit(line, &lines, D2365_GPIO_COUNT) { ++ irq = irq_find_mapping(gpio->domain, D2365_GPIO_EVENT + line); ++ if (irq) ++ irq_dispose_mapping(irq); ++ } ++ irq_domain_remove(gpio->domain); ++} ++ ++static void d2365_irq_stop(void *data) ++{ ++ struct d2365_gpio *gpio = data; ++ ++ mutex_lock(&gpio->irq_lock); ++ gpio->removing = true; ++ gpio->irq_ready = false; ++ mutex_unlock(&gpio->irq_lock); ++ cancel_delayed_work_sync(&gpio->irq_retry); ++ free_irq(gpio->parent_irq, gpio); ++} ++ ++static int d2365_irq_setup(struct d2365_gpio *gpio) ++{ ++ struct device *dev = gpio->chip.parent; ++ u8 buf[D2365_EVENT_BYTES]; ++ int ret; ++ ++ if (!device_property_read_bool(gpio->pmu, "interrupt-controller")) ++ return -EINVAL; ++ gpio->parent_irq = of_irq_get(gpio->pmu->of_node, 0); ++ if (gpio->parent_irq < 0) ++ return gpio->parent_irq; ++ if (!gpio->parent_irq) ++ return -ENXIO; ++ ++ ret = devm_mutex_init(dev, &gpio->irq_lock); ++ if (ret) ++ return ret; ++ INIT_DELAYED_WORK(&gpio->irq_retry, d2365_irq_retry); ++ ++ /* What the boot chain armed and latched is the evidence to keep. */ ++ ret = regmap_bulk_read(gpio->regmap, D2365_IRQ_MASK, gpio->boot_mask, ++ sizeof(gpio->boot_mask)); ++ if (ret) ++ return ret; ++ dev_info(dev, "boot event mask %*phN\n", D2365_EVENT_BYTES, ++ gpio->boot_mask); ++ ++ /* As iOS does at probe: mask all, unmask only what has a consumer. */ ++ memset(buf, 0xff, sizeof(buf)); ++ ret = regmap_bulk_write(gpio->regmap, D2365_IRQ_MASK, buf, sizeof(buf)); ++ if (ret) ++ return ret; ++ ret = devm_add_action_or_reset(dev, d2365_irq_restore_mask, gpio); ++ if (ret) ++ return ret; ++ ++ gpio->domain = irq_domain_create_linear(dev_fwnode(gpio->pmu), ++ D2365_EVENT_COUNT, ++ &d2365_irq_domain_ops, gpio); ++ if (!gpio->domain) ++ return -ENOMEM; ++ ret = devm_add_action_or_reset(dev, d2365_irq_remove_domain, gpio); ++ if (ret) ++ return ret; ++ ++ /* Drop stale events so the line is released before it goes live. */ ++ ret = regmap_bulk_read(gpio->regmap, D2365_EVENTS, buf, sizeof(buf)); ++ if (ret) ++ return ret; ++ dev_info(dev, "boot events %*phN\n", D2365_EVENT_BYTES, buf); ++ ++ ret = request_threaded_irq(gpio->parent_irq, NULL, d2365_irq_thread, ++ IRQF_ONESHOT, "d2365-events", gpio); ++ if (ret) ++ return ret; ++ gpio->irq_ready = true; ++ return devm_add_action_or_reset(dev, d2365_irq_stop, gpio); ++} ++ ++static int d2365_irq_probe(struct d2365_gpio *gpio) ++{ ++ struct device *dev = gpio->chip.parent; ++ void *group; ++ int ret; ++ ++ /* Old DTBs and the polled default never touch the event registers. */ ++ if (!device_property_read_bool(dev, "apple,enable-hall-irqs")) ++ return 0; ++ ++ group = devres_open_group(dev, NULL, GFP_KERNEL); ++ if (!group) ++ return -ENOMEM; ++ ret = d2365_irq_setup(gpio); ++ if (ret) { ++ gpio->irq_ready = false; ++ devres_release_group(dev, group); ++ gpio->domain = NULL; ++ return ret; ++ } ++ devres_remove_group(dev, group); ++ dev_info(dev, "hall events 0x30/0x3a on IRQ %d\n", gpio->parent_irq); ++ return 0; ++} ++ ++static unsigned int d2365_gpio_config_reg(unsigned int offset) ++{ ++ if (offset < D2365_GPIO_SPLIT) ++ return D2365_GPIO_CFG_0_17 + 2 * offset; ++ ++ return D2365_GPIO_CFG_18_23 + 6 * offset; ++} ++ ++static int d2365_gpio_get_direction(struct gpio_chip *chip, ++ unsigned int offset) ++{ ++ struct d2365_gpio *gpio = gpiochip_get_data(chip); ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(gpio->regmap, d2365_gpio_config_reg(offset), &value); ++ if (ret) ++ return ret; ++ if (value & D2365_GPIO_FUNCTION) ++ return -EINVAL; ++ ++ return value & D2365_GPIO_INPUT ? GPIO_LINE_DIRECTION_IN : ++ GPIO_LINE_DIRECTION_OUT; ++} ++ ++static int d2365_gpio_direction_input(struct gpio_chip *chip, ++ unsigned int offset) ++{ ++ struct d2365_gpio *gpio = gpiochip_get_data(chip); ++ unsigned int reg = d2365_gpio_config_reg(offset); ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(gpio->regmap, reg, &value); ++ if (ret) ++ return ret; ++ ++ /* ++ * A line the PMIC's event logic owns also feeds its wake logic (the ++ * Smart Cover halls, for one): never take it over, and say why. ++ */ ++ if (value & D2365_GPIO_FUNCTION) { ++ dev_warn(chip->parent, ++ "line %u belongs to a PMIC function (config 0x%02x), not using it as a GPIO input\n", ++ offset, value); ++ return -EBUSY; ++ } ++ ++ dev_info(chip->parent, "line %u config 0x%02x (%s)\n", offset, value, ++ value & D2365_GPIO_INPUT ? "input" : "output, switching to input"); ++ ++ /* Already an input: reading it must not write anything. */ ++ if (value & D2365_GPIO_INPUT) ++ return 0; ++ ++ return regmap_update_bits(gpio->regmap, reg, D2365_GPIO_MODE_MASK, ++ D2365_GPIO_INPUT); ++} ++ ++static int d2365_gpio_direction_output(struct gpio_chip *chip, ++ unsigned int offset, int value) ++{ ++ struct d2365_gpio *gpio = gpiochip_get_data(chip); ++ ++ return regmap_update_bits(gpio->regmap, ++ d2365_gpio_config_reg(offset), ++ D2365_GPIO_MODE_MASK, ++ value ? D2365_GPIO_VALUE : 0); ++} ++ ++static int d2365_gpio_get(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct d2365_gpio *gpio = gpiochip_get_data(chip); ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(gpio->regmap, d2365_gpio_config_reg(offset), &value); ++ if (ret) ++ return ret; ++ if (!(value & D2365_GPIO_INPUT)) ++ return !!(value & D2365_GPIO_VALUE); ++ ++ ret = regmap_read(gpio->regmap, D2365_GPIO_INPUT0 + offset / 8, ++ &value); ++ if (ret) ++ return ret; ++ ++ return !!(value & BIT(offset % 8)); ++} ++ ++static int d2365_gpio_set(struct gpio_chip *chip, unsigned int offset, ++ int value) ++{ ++ struct d2365_gpio *gpio = gpiochip_get_data(chip); ++ ++ return regmap_update_bits(gpio->regmap, ++ d2365_gpio_config_reg(offset), ++ D2365_GPIO_VALUE, ++ value ? D2365_GPIO_VALUE : 0); ++} ++ ++static int d2365_gpio_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct d2365_gpio *gpio; ++ int ret; ++ ++ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); ++ if (!gpio) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, gpio); ++ ++ gpio->regmap = dev_get_regmap(dev->parent, NULL); ++ if (!gpio->regmap) ++ return dev_err_probe(dev, -ENODEV, "parent regmap unavailable\n"); ++ gpio->pmu = dev->parent; ++ ++ gpio->chip.label = "d2365-pmic-gpio"; ++ gpio->chip.parent = dev; ++ gpio->chip.owner = THIS_MODULE; ++ gpio->chip.base = -1; ++ gpio->chip.ngpio = D2365_GPIO_COUNT; ++ gpio->chip.can_sleep = true; ++ gpio->chip.get_direction = d2365_gpio_get_direction; ++ gpio->chip.direction_input = d2365_gpio_direction_input; ++ gpio->chip.direction_output = d2365_gpio_direction_output; ++ gpio->chip.get = d2365_gpio_get; ++ gpio->chip.set = d2365_gpio_set; ++ ++ ret = devm_gpiochip_add_data(dev, &gpio->chip, gpio); ++ if (ret) ++ return ret; ++ ++ /* The GPIOs stay usable without the event path; only defer. */ ++ ret = d2365_irq_probe(gpio); ++ if (ret == -EPROBE_DEFER) ++ return ret; ++ if (ret) ++ dev_warn(dev, "hall event IRQs unavailable: %d\n", ret); ++ return 0; ++} ++ ++static const struct of_device_id d2365_gpio_of_match[] = { ++ { .compatible = "apple,d2365-pmic-gpio" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, d2365_gpio_of_match); ++ ++static struct platform_driver d2365_gpio_driver = { ++ .probe = d2365_gpio_probe, ++ .driver = { ++ .name = "apple-d2365-pmic-gpio", ++ .of_match_table = d2365_gpio_of_match, ++ .pm = pm_sleep_ptr(&d2365_gpio_pm_ops), ++ }, ++}; ++module_platform_driver(d2365_gpio_driver); ++ ++MODULE_DESCRIPTION("Apple D2365 PMIC GPIO driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/nvmem/simple-mfd-nvmem.c b/drivers/nvmem/simple-mfd-nvmem.c +index 2ae862185b01082bb3b50e8df4b9adfd8b064c30..00f4ef95f7b1c6b5d41eaa0c3e71c18d2ae2d33a 100644 +--- a/drivers/nvmem/simple-mfd-nvmem.c ++++ b/drivers/nvmem/simple-mfd-nvmem.c +@@ -37,7 +37,8 @@ static int simple_mfd_nvmem_probe(struct platform_device *pdev) + { + struct simple_mfd_nvmem *nvmem; + const __be32 *addr; +- int len; ++ int len, ret; ++ u32 size; + struct nvmem_config nvmem_cfg = { + .dev = &pdev->dev, + .name = "simple_mfd_nvmem", +@@ -66,13 +67,23 @@ static int simple_mfd_nvmem_probe(struct platform_device *pdev) + dev_err(&pdev->dev, "No reg property\n"); + return -EINVAL; + } +- if (len != 2 * sizeof(u32)) { +- dev_err(&pdev->dev, "Invalid reg property\n"); +- return -EINVAL; ++ /* Address-only PMIC buses describe register count separately. */ ++ if (of_n_size_cells(pdev->dev.of_node) == 0 && len == sizeof(u32)) { ++ ret = of_property_read_u32(pdev->dev.of_node, "size", &size); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, "Missing NVMEM size\n"); ++ } else if (of_n_size_cells(pdev->dev.of_node) == 1 && ++ len == 2 * sizeof(u32)) { ++ /* Preserve the original binding for other boards. */ ++ size = be32_to_cpup(&addr[1]); ++ } else { ++ return dev_err_probe(&pdev->dev, -EINVAL, "Invalid reg property\n"); + } ++ if (!size) ++ return dev_err_probe(&pdev->dev, -EINVAL, "Empty NVMEM range\n"); + + nvmem->base = be32_to_cpup(&addr[0]); +- nvmem_cfg.size = be32_to_cpup(&addr[1]); ++ nvmem_cfg.size = size; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(&pdev->dev, &nvmem_cfg)); + } +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index b89ef40df7f82a41a40eba230999e49094047608..136bb142cfdd614e83ceb3c7a84da18896d98968 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -334,6 +334,47 @@ config BATTERY_BQ27XXX_HDQ + Say Y here to enable support for batteries with BQ27xxx chips + connected over an HDQ bus. + ++config APPLE_BELLATRIX_POWER ++ tristate "Apple Bellatrix USB power status" ++ depends on I2C ++ help ++ Say Y here to enable read-only USB input presence reporting for the ++ Apple Bellatrix power controller found in some T8010 devices. ++ ++ USB input presence is polled and cached so power supply consumers do not ++ access the I2C bus directly. This driver intentionally does not control ++ charging, input current, ADC conversion, power roles, or interrupts. ++ ++ This driver can also be built as a module. If so, the module will be ++ called apple_bellatrix_power. ++ ++config APPLE_D2365_CHARGER ++ tristate "Apple D2365 charger status" ++ depends on MFD_SIMPLE_MFD_I2C ++ depends on USB_GADGET || !USB_GADGET ++ depends on USB_ROLES_APPLE_S8000 || !USB_ROLES_APPLE_S8000 ++ help ++ Say Y here to enable charger status reporting and the guarded input-current ++ policy for the Apple D2365 PMIC found in some T8010 devices. ++ ++ The policy raises the input-current budget only while external power and ++ safe battery conditions are present, and leaves charge termination to the ++ PMIC. It does not configure charge current, charge voltage, or interrupts. ++ ++ J71t also provides CBTL1610 source detection and an automatic ++ current policy using verified Apple USB host grants and resistor ++ signatures, with a conservative ordinary USB fallback. It requires ++ private CBAT calibration and safe battery telemetry, preserves the ++ handoff settings, and restores them on disconnect, suspend or error. ++ ++ On J71t the CBTL1610 driver also identifies Lightning accessories ++ over the ID bus. For the camera adapter it runs the power handshake ++ and makes the port a USB host, as iOS does, while charging from the ++ adapter continues. ++ ++ The modules are apple_d2365_charger, apple_j71t_charger and ++ apple_tristar_power. ++ + config BATTERY_BQ27XXX_DT_UPDATES_NVM + bool "BQ27xxx support for update of NVM/flash data memory" + depends on BATTERY_BQ27XXX_I2C +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index f3c0d1006c1a2a9fdaf3228391ead9e33235cc66..4db918a2a3e6e5925848cd466bd8898cc1b57caf 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -52,6 +52,8 @@ obj-$(CONFIG_MANAGER_SBS) += sbs-manager.o + obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o + obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o + obj-$(CONFIG_BATTERY_BQ27XXX_HDQ) += bq27xxx_battery_hdq.o ++obj-$(CONFIG_APPLE_BELLATRIX_POWER) += apple_bellatrix_power.o ++obj-$(CONFIG_APPLE_D2365_CHARGER) += apple_d2365_charger.o apple_j71t_charger.o apple_tristar_power.o + obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o + obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o + obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o +diff --git a/drivers/power/supply/apple_bellatrix_power.c b/drivers/power/supply/apple_bellatrix_power.c +new file mode 100644 +index 0000000000000000000000000000000000000000..0537cf8f91cddbfef73851ec94a54de9357c298e +--- /dev/null ++++ b/drivers/power/supply/apple_bellatrix_power.c +@@ -0,0 +1,273 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Read-only USB power status for the Apple Bellatrix controller ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define BELLATRIX_VBUS_STATUS 0x16 ++#define BELLATRIX_VBUS_EXT_VBUS BIT(1) ++#define BELLATRIX_STATUS2 0x17 ++#define BELLATRIX_STATUS2_PROVIDER BIT(0) ++#define BELLATRIX_STATUS2_CONSUMER BIT(1) ++#define BELLATRIX_CHIP_ID 0x73 ++#define BELLATRIX_CHIP_ID_VALUE 0x74 ++ ++#define BELLATRIX_INIT_DELAY_MS 10 ++#define BELLATRIX_INIT_RETRY_DELAY_MS 100 ++#define BELLATRIX_INIT_ATTEMPTS 10 ++#define BELLATRIX_POLL_INTERVAL_MS 1000 ++ ++struct apple_bellatrix { ++ struct i2c_client *client; ++ struct power_supply *psy; ++ struct delayed_work init_work; ++ /* Protects cached properties. */ ++ struct mutex lock; ++ unsigned int init_attempts; ++ bool initialized; ++ int online; ++ int online_error; ++}; ++ ++static int apple_bellatrix_read(struct i2c_client *client, u8 reg) ++{ ++ u8 value; ++ struct i2c_msg msgs[] = { ++ { ++ .addr = client->addr, ++ .flags = I2C_M_STOP, ++ .len = 1, ++ .buf = ®, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = 1, ++ .buf = &value, ++ }, ++ }; ++ int ret; ++ ++ /* ++ * Bellatrix register reads require a STOP after the subaddress. Keep the ++ * selector write and data read in one locked adapter segment so no other ++ * transfer can run between them. ++ */ ++ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ ret = __i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ ++ if (ret != ARRAY_SIZE(msgs)) { ++ if (ret >= 0) ++ ret = -EIO; ++ return ret; ++ } ++ ++ ret = value; ++ ++ return ret; ++} ++ ++static int apple_bellatrix_update(struct apple_bellatrix *bellatrix, ++ bool *changed) ++{ ++ int old_error; ++ int old_online; ++ int chip_id; ++ int status2; ++ int vbus; ++ int online = 0; ++ int ret = 0; ++ ++ *changed = false; ++ ++ chip_id = apple_bellatrix_read(bellatrix->client, BELLATRIX_CHIP_ID); ++ if (chip_id < 0) { ++ ret = chip_id; ++ goto out_store; ++ } ++ ++ if (chip_id != BELLATRIX_CHIP_ID_VALUE) { ++ dev_err(&bellatrix->client->dev, ++ "unexpected chip id 0x%02x\n", chip_id); ++ ret = -ENODEV; ++ goto out_store; ++ } ++ ++ vbus = apple_bellatrix_read(bellatrix->client, BELLATRIX_VBUS_STATUS); ++ if (vbus < 0) { ++ ret = vbus; ++ goto out_store; ++ } ++ ++ if (!(vbus & BELLATRIX_VBUS_EXT_VBUS)) { ++ online = 0; ++ goto out_store; ++ } ++ ++ status2 = apple_bellatrix_read(bellatrix->client, BELLATRIX_STATUS2); ++ if (status2 < 0) { ++ ret = status2; ++ goto out_store; ++ } ++ ++ if (status2 & BELLATRIX_STATUS2_PROVIDER) ++ online = 0; ++ else ++ online = !!(status2 & BELLATRIX_STATUS2_CONSUMER); ++ ++out_store: ++ mutex_lock(&bellatrix->lock); ++ old_error = bellatrix->online_error; ++ old_online = bellatrix->online; ++ bellatrix->online_error = ret; ++ if (!ret) ++ bellatrix->online = online; ++ *changed = old_error != ret || (!ret && old_online != online); ++ mutex_unlock(&bellatrix->lock); ++ ++ return ret; ++} ++ ++static int apple_bellatrix_get_property(struct power_supply *psy, ++ enum power_supply_property property, ++ union power_supply_propval *val) ++{ ++ struct apple_bellatrix *bellatrix = power_supply_get_drvdata(psy); ++ int ret; ++ ++ mutex_lock(&bellatrix->lock); ++ switch (property) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = bellatrix->online_error; ++ if (!ret) ++ val->intval = bellatrix->online; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ mutex_unlock(&bellatrix->lock); ++ ++ return ret; ++} ++ ++static const enum power_supply_property apple_bellatrix_properties[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++}; ++ ++static const struct power_supply_desc apple_bellatrix_desc = { ++ .name = "usb", ++ .type = POWER_SUPPLY_TYPE_USB, ++ .properties = apple_bellatrix_properties, ++ .num_properties = ARRAY_SIZE(apple_bellatrix_properties), ++ .get_property = apple_bellatrix_get_property, ++}; ++ ++static void apple_bellatrix_init_work(struct work_struct *work) ++{ ++ struct apple_bellatrix *bellatrix = ++ container_of(to_delayed_work(work), struct apple_bellatrix, ++ init_work); ++ bool changed; ++ int ret; ++ ++ ret = apple_bellatrix_update(bellatrix, &changed); ++ if (changed) ++ power_supply_changed(bellatrix->psy); ++ ++ if (!ret) { ++ bellatrix->initialized = true; ++ schedule_delayed_work(&bellatrix->init_work, ++ msecs_to_jiffies(BELLATRIX_POLL_INTERVAL_MS)); ++ return; ++ } ++ ++ if (bellatrix->initialized) { ++ schedule_delayed_work(&bellatrix->init_work, ++ msecs_to_jiffies(BELLATRIX_POLL_INTERVAL_MS)); ++ return; ++ } ++ ++ bellatrix->init_attempts++; ++ ++ if ((ret == -ENXIO || ret == -EIO) && ++ bellatrix->init_attempts < BELLATRIX_INIT_ATTEMPTS) { ++ schedule_delayed_work(&bellatrix->init_work, ++ msecs_to_jiffies(BELLATRIX_INIT_RETRY_DELAY_MS)); ++ return; ++ } ++ ++ dev_err(&bellatrix->client->dev, ++ "failed to read USB power status after %u attempt(s): %pe\n", ++ bellatrix->init_attempts, ERR_PTR(ret)); ++} ++ ++static int apple_bellatrix_probe(struct i2c_client *client) ++{ ++ struct power_supply_config config = {}; ++ struct apple_bellatrix *bellatrix; ++ int ret; ++ ++ bellatrix = devm_kzalloc(&client->dev, sizeof(*bellatrix), GFP_KERNEL); ++ if (!bellatrix) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(&client->dev, &bellatrix->lock); ++ if (ret) ++ return ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ++ return dev_err_probe(&client->dev, -EOPNOTSUPP, ++ "plain I2C transfers unsupported\n"); ++ ++ bellatrix->client = client; ++ bellatrix->online_error = -ENODATA; ++ ++ config.drv_data = bellatrix; ++ config.fwnode = dev_fwnode(&client->dev); ++ config.no_wakeup_source = true; ++ ++ bellatrix->psy = devm_power_supply_register(&client->dev, ++ &apple_bellatrix_desc, ++ &config); ++ if (IS_ERR(bellatrix->psy)) ++ return PTR_ERR(bellatrix->psy); ++ ++ ret = devm_delayed_work_autocancel(&client->dev, ++ &bellatrix->init_work, ++ apple_bellatrix_init_work); ++ if (ret) ++ return ret; ++ ++ schedule_delayed_work(&bellatrix->init_work, ++ msecs_to_jiffies(BELLATRIX_INIT_DELAY_MS)); ++ ++ return 0; ++} ++ ++static const struct of_device_id apple_bellatrix_of_match[] = { ++ { .compatible = "apple,bellatrix" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, apple_bellatrix_of_match); ++ ++static struct i2c_driver apple_bellatrix_driver = { ++ .probe = apple_bellatrix_probe, ++ .driver = { ++ .name = "apple-bellatrix-power", ++ .of_match_table = apple_bellatrix_of_match, ++ }, ++}; ++module_i2c_driver(apple_bellatrix_driver); ++ ++MODULE_AUTHOR("Paul Praschl"); ++MODULE_DESCRIPTION("Read-only Apple Bellatrix USB power status"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/apple_d2365_charger.c b/drivers/power/supply/apple_d2365_charger.c +new file mode 100644 +index 0000000000000000000000000000000000000000..fa17482ca06064bce9c7ff18c36dcbfa60bfbcf8 +--- /dev/null ++++ b/drivers/power/supply/apple_d2365_charger.c +@@ -0,0 +1,772 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Charging status and guarded input-current policy for the Apple D2365 PMIC ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define D2365_STATUS_CHARGE 0x0a ++#define D2365_STATUS_CHARGE_STATE GENMASK(1, 0) ++#define D2365_STATUS_CHARGE_END BIT(2) ++#define D2365_STATUS_CHARGE_TIMEOUT BIT(4) ++#define D2365_CONTROL_INPUT 0x00 ++#define D2365_CONTROL_INPUT_LIMIT 0x03 ++#define D2365_CONTROL_MAX_CHARGE 0x0c ++#define D2365_CONTROL_MAX_CHARGE_MASK GENMASK(5, 0) ++#define D2365_POLICY_CONTROL 0x00 ++#define D2365_POLICY_MIN_TEMP 100 ++#define D2365_POLICY_MAX_TEMP 400 ++#define D2365_POLICY_MIN_VOLTAGE_UV 3000000 ++#define D2365_POLICY_MAX_VOLTAGE_UV 4350000 ++#define D2365_POLICY_INTERVAL_MS 5000 ++#define D2365_RESTORE_RETRY_MS 100 ++#define D2365_STOP_RESTORE_ATTEMPTS 3 ++ ++struct apple_d2365_config { ++ u8 handoff_input_limit; ++ u8 charging_input_limit; ++ u8 max_charge; ++}; ++ ++static const struct apple_d2365_config apple_d2365_j172_config = { ++ .handoff_input_limit = 0x02, ++ .charging_input_limit = 0x6c, ++ .max_charge = 0x34, ++}; ++ ++struct apple_d2365_battery_snapshot { ++ int present; ++ int temperature; ++ int voltage_uv; ++}; ++ ++struct apple_d2365_charger { ++ /* Protects cached properties while probe completes. */ ++ struct mutex lock; ++ /* Serializes policy validation, writes and restore. */ ++ struct mutex policy_lock; ++ struct delayed_work charge_policy_work; ++ struct device *dev; ++ struct regmap *regmap; ++ struct power_supply *psy; ++ const struct apple_d2365_config *config; ++ u32 status_base; ++ u32 control_base; ++ unsigned int raw_status; ++ int status_error; ++ unsigned int raw_input_limit; ++ int input_limit_error; ++ u8 policy_old_input_limit; ++ bool policy_old_input_valid; ++ bool policy_enabled; ++ bool restore_pending; ++ bool shutting_down; ++}; ++ ++static int apple_d2365_charger_online(struct power_supply *psy, ++ union power_supply_propval *val); ++ ++static int apple_d2365_get_bank(struct device *dev, const char *name, ++ u32 min_size, u32 *base) ++{ ++ u64 address; ++ u64 size; ++ int index; ++ int ret; ++ ++ index = of_property_match_string(dev->of_node, "reg-names", name); ++ if (index < 0) ++ return dev_err_probe(dev, index, ++ "missing %s register range\n", name); ++ ++ ret = of_property_read_reg(dev->of_node, index, &address, &size); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to read %s register range\n", name); ++ ++ if (size < min_size || address + min_size - 1 > U32_MAX) ++ return dev_err_probe(dev, -EINVAL, ++ "invalid %s register range\n", name); ++ ++ *base = address; ++ return 0; ++} ++ ++static int apple_d2365_battery_status(union power_supply_propval *val) ++{ ++ struct power_supply *battery; ++ int ret; ++ ++ battery = power_supply_get_by_name("battery"); ++ if (!battery) ++ return -ENODEV; ++ ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_STATUS, val); ++ power_supply_put(battery); ++ ++ return ret; ++} ++ ++static void apple_d2365_cache_status(struct apple_d2365_charger *charger, ++ int ret, unsigned int status) ++{ ++ bool changed; ++ ++ mutex_lock(&charger->lock); ++ changed = charger->status_error != ret || ++ (!ret && charger->raw_status != status); ++ charger->status_error = ret; ++ if (!ret) ++ charger->raw_status = status; ++ mutex_unlock(&charger->lock); ++ ++ if (changed && charger->psy) ++ power_supply_changed(charger->psy); ++} ++ ++static void ++apple_d2365_cache_input_limit(struct apple_d2365_charger *charger, int ret, ++ unsigned int input_limit) ++{ ++ bool changed; ++ ++ mutex_lock(&charger->lock); ++ changed = charger->input_limit_error != ret || ++ (!ret && charger->raw_input_limit != input_limit); ++ charger->input_limit_error = ret; ++ if (!ret) ++ charger->raw_input_limit = input_limit; ++ mutex_unlock(&charger->lock); ++ ++ if (changed && charger->psy) ++ power_supply_changed(charger->psy); ++} ++ ++static int ++apple_d2365_battery_snapshot(struct apple_d2365_battery_snapshot *snapshot) ++{ ++ union power_supply_propval val; ++ struct power_supply *battery; ++ int ret; ++ ++ battery = power_supply_get_by_name("battery"); ++ if (!battery) ++ return -ENODEV; ++ ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_PRESENT, ++ &val); ++ if (ret) ++ goto out_put; ++ snapshot->present = val.intval; ++ ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_TEMP, &val); ++ if (ret) ++ goto out_put; ++ snapshot->temperature = val.intval; ++ ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ &val); ++ if (ret) ++ goto out_put; ++ snapshot->voltage_uv = val.intval; ++ ++out_put: ++ power_supply_put(battery); ++ return ret; ++} ++ ++static int ++apple_d2365_restore_input_limit(struct apple_d2365_charger *charger, ++ u8 old_input_limit) ++{ ++ unsigned int readback; ++ int ret = -EIO; ++ int attempt; ++ ++ for (attempt = 1; attempt <= 3; attempt++) { ++ ret = regmap_write(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT_LIMIT, ++ old_input_limit); ++ if (!ret) { ++ ret = regmap_read(charger->regmap, ++ charger->control_base + ++ D2365_CONTROL_INPUT_LIMIT, &readback); ++ if (!ret && readback == old_input_limit) { ++ apple_d2365_cache_input_limit(charger, 0, ++ old_input_limit); ++ return 0; ++ } ++ if (!ret) ++ ret = -EIO; ++ } ++ ++ if (attempt < 3) ++ msleep(20); ++ } ++ apple_d2365_cache_input_limit(charger, ret, 0); ++ ++ return ret; ++} ++ ++static bool ++apple_d2365_battery_is_safe(const struct apple_d2365_battery_snapshot *snapshot) ++{ ++ return snapshot->present == 1 && ++ snapshot->temperature >= D2365_POLICY_MIN_TEMP && ++ snapshot->temperature <= D2365_POLICY_MAX_TEMP && ++ snapshot->voltage_uv >= D2365_POLICY_MIN_VOLTAGE_UV && ++ snapshot->voltage_uv <= D2365_POLICY_MAX_VOLTAGE_UV; ++} ++ ++static int ++apple_d2365_policy_restore_locked(struct apple_d2365_charger *charger, ++ const char *reason) ++{ ++ unsigned int control; ++ int ret; ++ ++ if (!charger->restore_pending) ++ return 0; ++ if (!charger->policy_old_input_valid) ++ return -EINVAL; ++ ++ charger->policy_enabled = false; ++ ++ ret = apple_d2365_restore_input_limit(charger, ++ charger->policy_old_input_limit); ++ if (ret) { ++ dev_err(charger->dev, ++ "failed to restore input limit (%s): %d\n", reason, ret); ++ return ret; ++ } ++ charger->restore_pending = false; ++ charger->policy_old_input_valid = false; ++ power_supply_changed(charger->psy); ++ ++ ret = regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT, &control); ++ if (ret || control != D2365_POLICY_CONTROL) { ++ dev_err(charger->dev, ++ "D2365 charging policy control integrity failed (%s): ret=%d control=0x%02x\n", ++ reason, ret, ret ? 0 : control); ++ return ret ?: -EIO; ++ } ++ ++ return 0; ++} ++ ++static int apple_d2365_policy_read_controls(struct apple_d2365_charger *charger, ++ unsigned int *control, ++ unsigned int *input_limit, ++ unsigned int *max_charge, ++ unsigned int *status) ++{ ++ int ret; ++ ++ ret = regmap_read(charger->regmap, ++ charger->status_base + D2365_STATUS_CHARGE, status); ++ apple_d2365_cache_status(charger, ret, ret ? 0 : *status); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT, control); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT_LIMIT, ++ input_limit); ++ apple_d2365_cache_input_limit(charger, ret, ret ? 0 : *input_limit); ++ if (ret) ++ return ret; ++ ++ return regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_MAX_CHARGE, ++ max_charge); ++} ++ ++static void apple_d2365_charge_policy_work(struct work_struct *work) ++{ ++ struct apple_d2365_charger *charger; ++ struct apple_d2365_battery_snapshot battery; ++ union power_supply_propval online; ++ unsigned int control; ++ unsigned int input_limit; ++ unsigned int max_charge; ++ unsigned int readback; ++ unsigned int status; ++ const char *reject_reason = NULL; ++ int ret; ++ ++ charger = container_of(to_delayed_work(work), ++ struct apple_d2365_charger, charge_policy_work); ++ ++ mutex_lock(&charger->policy_lock); ++ if (charger->shutting_down) ++ goto out_unlock; ++ if (charger->restore_pending && !charger->policy_enabled) { ++ apple_d2365_policy_restore_locked(charger, "retry"); ++ goto out_reschedule; ++ } ++ ++ if (!charger->config) { ++ ret = regmap_read(charger->regmap, ++ charger->status_base + D2365_STATUS_CHARGE, ++ &status); ++ apple_d2365_cache_status(charger, ret, ret ? 0 : status); ++ ret = regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT_LIMIT, ++ &input_limit); ++ apple_d2365_cache_input_limit(charger, ret, ret ? 0 : input_limit); ++ goto out_reschedule; ++ } ++ ++ ret = apple_d2365_charger_online(charger->psy, &online); ++ if (ret || online.intval != 1) { ++ reject_reason = "supplier offline"; ++ goto out_stop; ++ } ++ ++ ret = apple_d2365_battery_snapshot(&battery); ++ if (ret || !apple_d2365_battery_is_safe(&battery)) { ++ reject_reason = "battery guard"; ++ goto out_stop; ++ } ++ ++ ret = apple_d2365_policy_read_controls(charger, &control, &input_limit, ++ &max_charge, &status); ++ if (ret) { ++ reject_reason = "control read"; ++ goto out_stop; ++ } ++ ++ /* ++ * Keep the input budget in place after charge termination so the system ++ * can remain externally powered while the PMIC controls battery current. ++ */ ++ if (status & D2365_STATUS_CHARGE_TIMEOUT) { ++ reject_reason = "charge timeout"; ++ goto out_stop; ++ } ++ ++ if (control != D2365_POLICY_CONTROL || ++ (max_charge & D2365_CONTROL_MAX_CHARGE_MASK) != ++ charger->config->max_charge) { ++ reject_reason = "control integrity"; ++ goto out_stop; ++ } ++ ++ if (charger->policy_enabled) { ++ if (input_limit != charger->config->charging_input_limit) { ++ reject_reason = "input-limit integrity"; ++ goto out_stop; ++ } ++ goto out_reschedule; ++ } ++ ++ if (input_limit != charger->config->handoff_input_limit) { ++ reject_reason = "unexpected handoff input-limit"; ++ goto out_stop; ++ } ++ ++ charger->policy_old_input_limit = input_limit; ++ charger->policy_old_input_valid = true; ++ charger->restore_pending = true; ++ ret = regmap_write(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT_LIMIT, ++ charger->config->charging_input_limit); ++ if (ret) ++ goto out_write_error; ++ ++ ret = regmap_read(charger->regmap, ++ charger->control_base + D2365_CONTROL_INPUT_LIMIT, ++ &readback); ++ apple_d2365_cache_input_limit(charger, ret, ret ? 0 : readback); ++ if (ret || readback != charger->config->charging_input_limit) { ++ if (!ret) ++ ret = -EIO; ++ goto out_write_error; ++ } ++ ++ charger->policy_enabled = true; ++ power_supply_changed(charger->psy); ++ ++out_reschedule: ++ if (!charger->shutting_down) ++ mod_delayed_work(system_power_efficient_wq, ++ &charger->charge_policy_work, ++ msecs_to_jiffies(D2365_POLICY_INTERVAL_MS)); ++ goto out_unlock; ++ ++out_write_error: ++ dev_err(charger->dev, "D2365 charging policy enable failed: %d\n", ret); ++ apple_d2365_policy_restore_locked(charger, "enable error"); ++ goto out_reschedule; ++ ++out_stop: ++ if (charger->policy_enabled || charger->restore_pending) ++ apple_d2365_policy_restore_locked(charger, reject_reason); ++ goto out_reschedule; ++ ++out_unlock: ++ mutex_unlock(&charger->policy_lock); ++} ++ ++static int apple_d2365_charger_online(struct power_supply *psy, ++ union power_supply_propval *val) ++{ ++ return power_supply_get_property_from_supplier(psy, ++ POWER_SUPPLY_PROP_ONLINE, ++ val); ++} ++ ++static int apple_d2365_charger_status(struct power_supply *psy, ++ union power_supply_propval *val) ++{ ++ struct apple_d2365_charger *charger = power_supply_get_drvdata(psy); ++ union power_supply_propval battery_status; ++ union power_supply_propval online; ++ unsigned int charge_state; ++ unsigned int raw_status; ++ int battery_ret; ++ int ret; ++ ++ mutex_lock(&charger->lock); ++ ret = charger->status_error; ++ raw_status = charger->raw_status; ++ mutex_unlock(&charger->lock); ++ ++ if (ret) ++ return ret; ++ ++ charge_state = raw_status & D2365_STATUS_CHARGE_STATE; ++ ++ ret = apple_d2365_charger_online(psy, &online); ++ if (!ret && !online.intval) { ++ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ return 0; ++ } ++ ++ if (raw_status & D2365_STATUS_CHARGE_TIMEOUT) { ++ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ return 0; ++ } ++ ++ battery_ret = apple_d2365_battery_status(&battery_status); ++ if (!ret && online.intval == 1 && !battery_ret && ++ battery_status.intval == POWER_SUPPLY_STATUS_FULL) { ++ val->intval = POWER_SUPPLY_STATUS_FULL; ++ return 0; ++ } ++ ++ if (raw_status & D2365_STATUS_CHARGE_END) { ++ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ return 0; ++ } ++ ++ /* No Tristar supplier yet: require the HDQ gauge to corroborate. */ ++ if (ret && !charger->config && !battery_ret) { ++ if (battery_status.intval == POWER_SUPPLY_STATUS_FULL || ++ battery_status.intval == POWER_SUPPLY_STATUS_DISCHARGING || ++ (charge_state && ++ battery_status.intval == POWER_SUPPLY_STATUS_CHARGING)) { ++ val->intval = battery_status.intval; ++ return 0; ++ } ++ } ++ ++ /* ++ * Apple uses the non-zero D2365 field as IsCharging, but live data shows ++ * it can remain set while Bellatrix reports USB offline. Require both ++ * the PMIC state and positive external-power evidence before exporting ++ * Charging. A missing supplier remains conservative. ++ */ ++ if (!ret) ++ val->intval = charge_state ? POWER_SUPPLY_STATUS_CHARGING : ++ POWER_SUPPLY_STATUS_NOT_CHARGING; ++ else ++ val->intval = charge_state ? POWER_SUPPLY_STATUS_UNKNOWN : ++ POWER_SUPPLY_STATUS_NOT_CHARGING; ++ ++ return 0; ++} ++ ++static int ++apple_d2365_charger_input_limit(struct power_supply *psy, ++ union power_supply_propval *val) ++{ ++ struct apple_d2365_charger *charger = power_supply_get_drvdata(psy); ++ unsigned int input_limit; ++ int ret; ++ ++ mutex_lock(&charger->lock); ++ ret = charger->input_limit_error; ++ input_limit = charger->raw_input_limit; ++ mutex_unlock(&charger->lock); ++ if (ret) ++ return ret; ++ ++ /* D2365 code 0 is 75 mA; each following step adds 12.5 mA. */ ++ val->intval = 75000 + DIV_ROUND_CLOSEST(input_limit * 25000, 2); ++ return 0; ++} ++ ++static int apple_d2365_charger_charge_type(struct power_supply *psy, ++ union power_supply_propval *val) ++{ ++ union power_supply_propval status; ++ int ret; ++ ++ ret = apple_d2365_charger_status(psy, &status); ++ if (ret) ++ return ret; ++ ++ val->intval = status.intval == POWER_SUPPLY_STATUS_CHARGING || ++ status.intval == POWER_SUPPLY_STATUS_UNKNOWN ? ++ POWER_SUPPLY_CHARGE_TYPE_UNKNOWN : POWER_SUPPLY_CHARGE_TYPE_NONE; ++ ++ return 0; ++} ++ ++static int apple_d2365_charger_get_property(struct power_supply *psy, ++ enum power_supply_property property, ++ union power_supply_propval *val) ++{ ++ int ret; ++ ++ switch (property) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = apple_d2365_charger_online(psy, val); ++ break; ++ case POWER_SUPPLY_PROP_STATUS: ++ ret = apple_d2365_charger_status(psy, val); ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_TYPE: ++ ret = apple_d2365_charger_charge_type(psy, val); ++ break; ++ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ ret = apple_d2365_charger_input_limit(psy, val); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static void ++apple_d2365_charger_external_power_changed(struct power_supply *psy) ++{ ++ struct apple_d2365_charger *charger = power_supply_get_drvdata(psy); ++ ++ if (!READ_ONCE(charger->shutting_down)) ++ mod_delayed_work(system_power_efficient_wq, ++ &charger->charge_policy_work, 0); ++ ++ power_supply_changed(psy); ++} ++ ++static const enum power_supply_property apple_d2365_charger_properties[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_CHARGE_TYPE, ++ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, ++}; ++ ++static const struct power_supply_desc apple_d2365_charger_desc = { ++ .name = "charger", ++ .type = POWER_SUPPLY_TYPE_UNKNOWN, ++ .properties = apple_d2365_charger_properties, ++ .num_properties = ARRAY_SIZE(apple_d2365_charger_properties), ++ .get_property = apple_d2365_charger_get_property, ++ .external_power_changed = apple_d2365_charger_external_power_changed, ++}; ++ ++static const enum power_supply_property apple_d2365_readonly_properties[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_CHARGE_TYPE, ++ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, ++}; ++ ++static const struct power_supply_desc apple_d2365_readonly_desc = { ++ .name = "charger", ++ .type = POWER_SUPPLY_TYPE_UNKNOWN, ++ .properties = apple_d2365_readonly_properties, ++ .num_properties = ARRAY_SIZE(apple_d2365_readonly_properties), ++ .get_property = apple_d2365_charger_get_property, ++}; ++ ++static int apple_d2365_charger_probe(struct platform_device *pdev) ++{ ++ struct power_supply_config config = {}; ++ struct device *dev = &pdev->dev; ++ struct apple_d2365_charger *charger; ++ struct power_supply *psy; ++ struct regmap *regmap; ++ unsigned int status; ++ u32 control_base; ++ u32 status_base; ++ int ret; ++ ++ regmap = dev_get_regmap(dev->parent, NULL); ++ if (!regmap) ++ return dev_err_probe(dev, -ENODEV, "parent regmap unavailable\n"); ++ ++ ret = apple_d2365_get_bank(dev, "status", ++ D2365_STATUS_CHARGE + 1, &status_base); ++ if (ret) ++ return ret; ++ ++ ret = apple_d2365_get_bank(dev, "control", ++ D2365_CONTROL_MAX_CHARGE + 1, ++ &control_base); ++ if (ret) ++ return ret; ++ ++ charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL); ++ if (!charger) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(dev, &charger->lock); ++ if (ret) ++ return ret; ++ ret = devm_mutex_init(dev, &charger->policy_lock); ++ if (ret) ++ return ret; ++ ++ charger->dev = dev; ++ charger->regmap = regmap; ++ charger->config = device_get_match_data(dev); ++ charger->status_base = status_base; ++ charger->control_base = control_base; ++ charger->status_error = -ENODATA; ++ charger->input_limit_error = -ENODATA; ++ ret = devm_delayed_work_autocancel(dev, &charger->charge_policy_work, ++ apple_d2365_charge_policy_work); ++ if (ret) ++ return ret; ++ ++ config.drv_data = charger; ++ config.fwnode = dev_fwnode(dev); ++ config.no_wakeup_source = true; ++ ++ psy = devm_power_supply_register(dev, ++ charger->config ? &apple_d2365_charger_desc : ++ &apple_d2365_readonly_desc, ++ &config); ++ if (IS_ERR(psy)) ++ return dev_err_probe(dev, PTR_ERR(psy), ++ "failed to register power supply\n"); ++ charger->psy = psy; ++ platform_set_drvdata(pdev, charger); ++ ++ /* ++ * Do not access the PMIC before all supplier probe deferrals are done. ++ */ ++ ret = regmap_read(regmap, status_base + D2365_STATUS_CHARGE, &status); ++ apple_d2365_cache_status(charger, ret, ret ? 0 : status); ++ ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to read charging status\n"); ++ ++ ret = regmap_read(regmap, control_base + D2365_CONTROL_INPUT_LIMIT, ++ &status); ++ apple_d2365_cache_input_limit(charger, ret, ret ? 0 : status); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to read input-current limit\n"); ++ ++ { ++ mod_delayed_work(system_power_efficient_wq, ++ &charger->charge_policy_work, 0); ++ } ++ ++ power_supply_changed(psy); ++ ++ return 0; ++} ++ ++static void apple_d2365_charger_stop(struct apple_d2365_charger *charger, ++ const char *reason) ++{ ++ bool restore_pending = false; ++ int attempt; ++ ++ mutex_lock(&charger->policy_lock); ++ charger->shutting_down = true; ++ mutex_unlock(&charger->policy_lock); ++ ++ cancel_delayed_work_sync(&charger->charge_policy_work); ++ ++ for (attempt = 1; attempt <= D2365_STOP_RESTORE_ATTEMPTS; attempt++) { ++ mutex_lock(&charger->policy_lock); ++ apple_d2365_policy_restore_locked(charger, reason); ++ restore_pending = charger->restore_pending; ++ mutex_unlock(&charger->policy_lock); ++ ++ if (!restore_pending) ++ break; ++ if (attempt < D2365_STOP_RESTORE_ATTEMPTS) ++ msleep(D2365_RESTORE_RETRY_MS); ++ } ++ ++ if (restore_pending) ++ dev_crit(charger->dev, ++ "input-limit restore remains pending after %s\n", reason); ++} ++ ++static void apple_d2365_charger_remove(struct platform_device *pdev) ++{ ++ struct apple_d2365_charger *charger = platform_get_drvdata(pdev); ++ ++ apple_d2365_charger_stop(charger, "remove"); ++} ++ ++static void apple_d2365_charger_shutdown(struct platform_device *pdev) ++{ ++ struct apple_d2365_charger *charger = platform_get_drvdata(pdev); ++ ++ apple_d2365_charger_stop(charger, "shutdown"); ++} ++ ++static const struct of_device_id apple_d2365_charger_of_match[] = { ++ { ++ .compatible = "apple,j172-d2365-charger", ++ .data = &apple_d2365_j172_config, ++ }, ++ /* J71t has a separate, explicitly armed charging driver. */ ++ { .compatible = "apple,d2365-charger" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, apple_d2365_charger_of_match); ++ ++static struct platform_driver apple_d2365_charger_driver = { ++ .probe = apple_d2365_charger_probe, ++ .remove = apple_d2365_charger_remove, ++ .shutdown = apple_d2365_charger_shutdown, ++ .driver = { ++ .name = "apple-d2365-charger", ++ .of_match_table = apple_d2365_charger_of_match, ++ }, ++}; ++module_platform_driver(apple_d2365_charger_driver); ++ ++MODULE_AUTHOR("Paul Praschl"); ++MODULE_DESCRIPTION("Apple D2365 charger status and guarded charging policy"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/apple_j71t_charger.c b/drivers/power/supply/apple_j71t_charger.c +new file mode 100644 +index 0000000000000000000000000000000000000000..a7ea39fdedaa0a313f2d77d138266502ff22720b +--- /dev/null ++++ b/drivers/power/supply/apple_j71t_charger.c +@@ -0,0 +1,1007 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* Guarded J71t D2365 charging from verified source-current allowances. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple_tristar_source.h" ++ ++#define J71T_STATUS 0x18a ++#define J71T_INPUT_CONTROL 0xa00 ++#define J71T_INPUT_SUSPEND BIT(1) ++#define J71T_INPUT_LIMIT 0xa03 ++#define J71T_CHARGE_CURRENT 0xa08 ++#define J71T_MAX_CHARGE_CURRENT 0xa0c ++#define J71T_CHARGE_TIMEOUT BIT(4) ++#define J71T_CHARGE_END BIT(2) ++#define J71T_CHARGE_CODE 50 /* 2500 mA, still bounded by 0xa0c[5:0]. */ ++#define J71T_USB_CHARGE_CODE 8 /* Conservative 400 mA battery ceiling at 500 mA input. */ ++#define J71T_REQUEST_UA 2400000 ++#define J71T_FLOOR_UA 500000 ++#define J71T_EREGREADBACK (-EUCLEAN) ++#define J71T_LINK_GRACE_MS 5000 ++#define J71T_SAMPLE_GRACE_MS 60000 ++#define J71T_FAULT_RETRY_MS 60000 ++#define J71T_OWNER_RETRY_MS 30000 ++/* ++ * Battery sanity window. This unit's cell charges above 4.2 V: iOS reports ++ * 4256-4274 mV at 100 % (2026-10-27 capture) and Linux saw 4249 mV at 98 % ++ * while charging. With a 4.2 V ceiling the policy stopped charging near full ++ * every 30 s. The PMIC owns charge voltage and termination; this window only ++ * rejects implausible gauge samples. ++ */ ++#define J71T_VBAT_MIN_UV 3000000 ++#define J71T_VBAT_MAX_UV 4400000 ++/* One budget reassert per window before a register change counts as a fault. */ ++#define J71T_REASSERT_WINDOW_MS 10000 ++#define J71T_BACKOFF_HOLD_MS 60000 ++ ++static const int j71t_budgets_ua[] = { 500000, 1000000, 2100000, 2400000 }; ++ ++struct j71t_charger { ++ struct device *dev; ++ struct regmap *map; ++ struct power_supply *psy; ++ struct power_supply *source; ++ /* Serializes policy changes, register transactions and work lifetime. */ ++ struct mutex lock; ++ struct delayed_work work; ++ u8 input_code; ++ u8 input_codes[ARRAY_SIZE(j71t_budgets_ua)]; ++ u8 charge_code; ++ int budget_ua; ++ int user_limit_ua; ++ int max_limit_ua; ++ int ceiling_ua; ++ int source_type; ++ int adaptive_ua; ++ unsigned int attach_id; ++ unsigned int backoffs; ++ unsigned long adaptive_after; ++ const char *backoff_cause; ++ bool thermal_limited; ++ bool holding; ++ bool sample_pending; ++ unsigned long hold_until; ++ unsigned long sample_until; ++ unsigned long fault_until; ++ unsigned long retry_after; ++ unsigned long ramp_after; ++ unsigned long reassert_after; ++ u8 old_control; ++ u8 old_input; ++ u8 old_charge; ++ bool calibrated; ++ bool saved; ++ bool active; ++ bool stopping; ++ bool disabled; ++ bool faulted; ++ bool policy_logged; ++ int policy_error; ++ unsigned long policy_log_after; ++}; ++ ++/* AppleD2365PMU 0x6bc07ec middle/high branches, then 0x6bbff6c. */ ++static int j71t_calibrate_budget(const u8 *data, size_t size, int target_ma, u8 *code) ++{ ++ s64 point[5], slope, gain, result, ma; ++ int i; ++ ++ if (!data || size != 24 || target_ma < 500 || target_ma > 2400 || ++ get_unaligned_le32(data + 20) != 1) ++ return -EINVAL; ++ for (i = 0; i < 5; i++) { ++ point[i] = (s32)get_unaligned_le32(data + i * 4); ++ if (point[i] <= 0 || point[i] > (3000LL << 16) || ++ (i && point[i] <= point[i - 1])) ++ return -ERANGE; ++ } ++ if (target_ma < 1500) { ++ slope = div_s64(point[2] - point[1], 53); ++ gain = div_s64(point[1] - point[2] + (1025LL << 16), 525); ++ result = ((gain * ((target_ma - 475LL) * 65536)) >> 16) - ++ point[1] + (925LL << 16); ++ } else { ++ slope = div_s64(point[4] - point[3], 30); ++ gain = div_s64(point[3] - point[4] + (600LL << 16), 300); ++ result = ((gain * ((target_ma - 2100LL) * 65536)) >> 16) - ++ point[3] + (4125LL << 16); ++ } ++ if (slope < (8LL << 16) || slope > (12LL << 16)) ++ return -ERANGE; ++ ma = result >> 16; ++ if (ma < 75 || ma > target_ma) ++ return -ERANGE; ++ *code = div_s64((ma * 8 - 600) & 0xfff8, 100); ++ return *code <= 0xba ? 0 : -ERANGE; ++} ++ ++static int j71t_calibrate(const u8 *data, size_t size, u8 *code) ++{ ++ return j71t_calibrate_budget(data, size, 2400, code); ++} ++ ++static int j71t_choose_budget(int source_ua) ++{ ++ int i; ++ ++ for (i = ARRAY_SIZE(j71t_budgets_ua) - 1; i >= 0; i--) ++ if (source_ua >= j71t_budgets_ua[i]) ++ return i; ++ return -ERANGE; ++} ++ ++static int j71t_read(struct j71t_charger *charger, unsigned int reg, u8 *value) ++{ ++ unsigned int raw; ++ int ret = regmap_read(charger->map, reg, &raw); ++ ++ if (!ret) ++ *value = raw; ++ return ret; ++} ++ ++static int j71t_write_checked(struct j71t_charger *charger, unsigned int reg, u8 value) ++{ ++ u8 readback; ++ int ret; ++ ++ ret = regmap_write(charger->map, reg, value); ++ if (ret) ++ return ret; ++ ret = j71t_read(charger, reg, &readback); ++ if (ret) ++ return ret; ++ return readback == value ? 0 : J71T_EREGREADBACK; ++} ++ ++/* Own only bit 1; preserve every other current control-register bit. */ ++static int j71t_set_suspend(struct j71t_charger *charger, u8 saved) ++{ ++ u8 control; ++ int ret; ++ ++ ret = j71t_read(charger, J71T_INPUT_CONTROL, &control); ++ if (ret) ++ return ret; ++ if ((control & J71T_INPUT_SUSPEND) == (saved & J71T_INPUT_SUSPEND)) ++ return 0; ++ control = (control & ~J71T_INPUT_SUSPEND) | (saved & J71T_INPUT_SUSPEND); ++ return j71t_write_checked(charger, J71T_INPUT_CONTROL, control); ++} ++ ++static int j71t_online(struct j71t_charger *charger) ++{ ++ union power_supply_propval value; ++ int ret; ++ ++ if (!charger->psy) ++ return -EAGAIN; ++ ret = power_supply_get_property_from_supplier(charger->psy, ++ POWER_SUPPLY_PROP_ONLINE, &value); ++ return ret ? ret : !!value.intval; ++} ++ ++static int j71t_source_limit(struct j71t_charger *charger) ++{ ++ union power_supply_propval value; ++ int ret; ++ ++ if (!charger->psy) ++ return -EAGAIN; ++ ret = power_supply_get_property_from_supplier(charger->psy, ++ POWER_SUPPLY_PROP_CURRENT_MAX, &value); ++ return ret ? ret : value.intval; ++} ++ ++/* An unknown DT ceiling must never grant an uncalibrated budget. */ ++static int j71t_valid_limit(unsigned int limit) ++{ ++ int index = j71t_choose_budget(limit); ++ ++ return index >= 0 && (unsigned int)j71t_budgets_ua[index] == limit ? limit : J71T_FLOOR_UA; ++} ++ ++static int j71t_source_eligible(struct j71t_charger *charger) ++{ ++ union power_supply_propval value; ++ int ret; ++ ++ ret = power_supply_get_property_from_supplier(charger->psy, ++ POWER_SUPPLY_PROP_USB_TYPE, &value); ++ if (ret) ++ return -EAGAIN; ++ /* ACA: the identified camera adapter, a source while the iPad is the host. */ ++ return value.intval == POWER_SUPPLY_USB_TYPE_SDP || ++ value.intval == POWER_SUPPLY_USB_TYPE_CDP || ++ value.intval == POWER_SUPPLY_USB_TYPE_DCP || ++ value.intval == POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID || ++ value.intval == POWER_SUPPLY_USB_TYPE_ACA; ++} ++ ++/* Link PM/reset is not source removal; only ONLINE=0 revokes the user override. */ ++static int j71t_policy_limit(struct j71t_charger *charger) ++{ ++ int source_ua = j71t_source_limit(charger); ++ union power_supply_propval type; ++ int eligible; ++ ++ if (source_ua < 0) ++ return -EAGAIN; ++ eligible = j71t_source_eligible(charger); ++ if (eligible < 0) ++ return eligible; ++ if (power_supply_get_property_from_supplier(charger->psy, ++ POWER_SUPPLY_PROP_USB_TYPE, &type)) ++ return -EAGAIN; ++ charger->source_type = type.intval; ++ /* iOS USBHost policy, separate from the USB stack's enumeration budget. */ ++ if (type.intval == POWER_SUPPLY_USB_TYPE_SDP) ++ source_ua = charger->max_limit_ua; ++ /* A reset can temporarily lose classification without losing VBUS. */ ++ if (source_ua < J71T_FLOOR_UA || !eligible) { ++ charger->ceiling_ua = J71T_FLOOR_UA; ++ if (!charger->holding) { ++ charger->holding = true; ++ charger->hold_until = jiffies + msecs_to_jiffies(J71T_LINK_GRACE_MS); ++ } ++ if (charger->active && time_before(jiffies, charger->hold_until)) ++ return charger->budget_ua; ++ return J71T_FLOOR_UA; ++ } ++ charger->holding = false; ++ charger->ceiling_ua = min(charger->max_limit_ua, source_ua); ++ source_ua = charger->ceiling_ua; ++ if (charger->user_limit_ua) ++ source_ua = min(source_ua, charger->user_limit_ua); ++ if (charger->adaptive_ua) ++ source_ua = min(source_ua, charger->adaptive_ua); ++ return source_ua; ++} ++ ++static int j71t_charge_code(int budget_ua) ++{ ++ return budget_ua == 500000 ? J71T_USB_CHARGE_CODE : J71T_CHARGE_CODE; ++} ++ ++static void j71t_revoke_override(struct j71t_charger *charger) ++{ ++ if (charger->user_limit_ua) ++ dev_info(charger->dev, "user input override revoked\n"); ++ charger->user_limit_ua = 0; ++} ++ ++/* Called even when an offline interval fell entirely between policy ticks. */ ++static void j71t_new_attach(struct j71t_charger *charger, unsigned int id) ++{ ++ j71t_revoke_override(charger); ++ charger->disabled = false; ++ charger->holding = false; ++ charger->sample_pending = false; ++ charger->hold_until = 0; ++ charger->sample_until = 0; ++ charger->retry_after = 0; ++ charger->ramp_after = 0; ++ charger->reassert_after = jiffies; ++ charger->adaptive_ua = 0; ++ charger->adaptive_after = 0; ++ charger->backoff_cause = NULL; ++ charger->backoffs = 0; ++ charger->ceiling_ua = 0; ++ charger->source_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ charger->attach_id = id; ++} ++ ++static int j71t_battery_safe(struct j71t_charger *charger) ++{ ++ struct power_supply *battery; ++ union power_supply_propval value; ++ int ret; ++ ++ battery = power_supply_get_by_name("battery"); ++ if (!battery) ++ return -EAGAIN; ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_PRESENT, &value); ++ if (ret) ++ goto transient; ++ /* PRESENT=0 also means a failed cached flags read in bq27xxx. */ ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_VOLTAGE_NOW, &value); ++ if (ret) ++ goto transient; ++ if (value.intval < J71T_VBAT_MIN_UV || value.intval > J71T_VBAT_MAX_UV) { ++ ret = -ERANGE; ++ goto out; ++ } ++ ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_TEMP, &value); ++ if (ret) ++ goto transient; ++ if (value.intval >= 450) ++ charger->thermal_limited = true; ++ else if (value.intval <= 420) ++ charger->thermal_limited = false; ++ ret = value.intval < 100 ? -ERANGE : ++ charger->thermal_limited ? -EOVERFLOW : 0; ++ goto out; ++transient: ++ ret = -EAGAIN; ++out: ++ power_supply_put(battery); ++ return ret; ++} ++ ++/* Match the iOS order without using input suspend as a transaction lock. */ ++static int j71t_apply(struct j71t_charger *charger, u8 charge, u8 input) ++{ ++ int ret = j71t_write_checked(charger, J71T_CHARGE_CURRENT, charge); ++ ++ if (!ret) ++ ret = j71t_write_checked(charger, J71T_INPUT_LIMIT, input); ++ return ret; ++} ++ ++static int j71t_restore(struct j71t_charger *charger) ++{ ++ int ret; ++ ++ charger->active = false; ++ if (!charger->saved) ++ return 0; ++ ret = j71t_apply(charger, charger->old_charge, charger->old_input); ++ if (!ret) ++ charger->saved = false; ++ return ret; ++} ++ ++static bool j71t_is_fault(int error) ++{ ++ return error == J71T_EREGREADBACK; ++} ++ ++static void j71t_fault(struct j71t_charger *charger, int error) ++{ ++ if (j71t_is_fault(error)) { ++ charger->faulted = true; ++ charger->fault_until = jiffies + msecs_to_jiffies(J71T_FAULT_RETRY_MS); ++ } ++} ++ ++static int j71t_guard(struct j71t_charger *charger) ++{ ++ u8 status, cap; ++ unsigned int id; ++ int ret = j71t_online(charger); ++ ++ if (ret != 1) ++ return ret < 0 ? ret : -ENOLINK; ++ ret = apple_tristar_attach_id(charger->source, &id); ++ if (ret || id != charger->attach_id) ++ return ret ?: -ENOLINK; ++ ret = j71t_policy_limit(charger); ++ if (ret < charger->budget_ua) ++ return ret < 0 ? ret : -ERANGE; ++ ret = j71t_battery_safe(charger); ++ if (ret == -EOVERFLOW && charger->budget_ua == J71T_FLOOR_UA) ++ ret = 0; ++ if (ret) ++ return ret; ++ ret = j71t_read(charger, J71T_STATUS, &status); ++ if (ret) ++ return ret; ++ ret = j71t_read(charger, J71T_MAX_CHARGE_CURRENT, &cap); ++ if (ret) ++ return ret; ++ if ((cap & 0x3f) < charger->charge_code) ++ return -ERANGE; ++ if (status & J71T_CHARGE_TIMEOUT) ++ return -ETIME; ++ return status & J71T_CHARGE_END ? -EBUSY : 0; ++} ++ ++static int j71t_enable(struct j71t_charger *charger) ++{ ++ int ret; ++ ++ if (!charger->calibrated) ++ return -ENODATA; ++ ret = j71t_guard(charger); ++ if (charger->budget_ua == J71T_FLOOR_UA && (ret == -ETIME || ret == -EBUSY)) ++ ret = 0; ++ if (ret) ++ return ret; ++ if (!charger->saved) { ++ ret = j71t_read(charger, J71T_INPUT_CONTROL, &charger->old_control); ++ if (!ret) ++ ret = j71t_read(charger, J71T_INPUT_LIMIT, &charger->old_input); ++ if (!ret) ++ ret = j71t_read(charger, J71T_CHARGE_CURRENT, &charger->old_charge); ++ if (ret) ++ return ret; ++ charger->saved = true; ++ } ++ ret = j71t_apply(charger, charger->charge_code, charger->input_code); ++ if (!ret) { ++ ret = j71t_guard(charger); ++ if (charger->budget_ua == J71T_FLOOR_UA && (ret == -ETIME || ret == -EBUSY)) ++ ret = 0; ++ } ++ /* Clear a genuine handoff/user disable only after the budgets are valid. */ ++ if (!ret && (charger->old_control & J71T_INPUT_SUSPEND)) { ++ ret = j71t_set_suspend(charger, 0); ++ if (!ret) ++ charger->old_control &= ~J71T_INPUT_SUSPEND; ++ } ++ if (ret) { ++ int restore = j71t_restore(charger); ++ ++ j71t_fault(charger, ret); ++ j71t_fault(charger, restore); ++ return ret; ++ } ++ charger->active = true; ++ charger->faulted = false; ++ charger->fault_until = 0; ++ dev_info_ratelimited(charger->dev, ++ "%s budget %d mA: input 0x%02x, battery limit %d mA, type %d ceiling %d mA, back-off %s\n", ++ charger->user_limit_ua ? "user override" : "source", ++ charger->budget_ua / 1000, charger->input_code, ++ charger->charge_code * 50, ++ charger->source_type, charger->ceiling_ua / 1000, ++ charger->backoff_cause ?: "none"); ++ return 0; ++} ++ ++/* Log transitions immediately and persistent deferred decisions every 30 s. */ ++static void j71t_policy_log(struct j71t_charger *charger, int error) ++{ ++ if (charger->policy_logged && charger->policy_error == error && ++ time_before(jiffies, charger->policy_log_after)) ++ return; ++ charger->policy_logged = true; ++ charger->policy_error = error; ++ charger->policy_log_after = jiffies + msecs_to_jiffies(30000); ++ if (charger->faulted) ++ dev_info(charger->dev, "charging policy ownership fault; automatic retry in 60 s\n"); ++ else if (charger->disabled) ++ dev_info(charger->dev, "charging policy disabled by user\n"); ++ else if (!charger->calibrated) ++ dev_info(charger->dev, "charging policy blocked: valid CBAT required\n"); ++ else if (error == -EAGAIN) ++ dev_info(charger->dev, "charging policy waiting for valid battery/source sample; retry in 1000 ms\n"); ++ else if (error) ++ dev_info(charger->dev, "charging policy deferred: %d; retry scheduled\n", error); ++} ++ ++static int j71t_check_owned(struct j71t_charger *charger, u8 *input, u8 *control, u8 *charge) ++{ ++ int ret = j71t_read(charger, J71T_INPUT_LIMIT, input); ++ ++ if (!ret) ++ ret = j71t_read(charger, J71T_INPUT_CONTROL, control); ++ if (!ret) ++ ret = j71t_read(charger, J71T_CHARGE_CURRENT, charge); ++ if (!ret && (*input != charger->input_code || ++ (*control & J71T_INPUT_SUSPEND) || *charge != charger->charge_code)) ++ ret = J71T_EREGREADBACK; ++ return ret; ++} ++ ++/* ++ * The PMIC changes the input settings on its own shortly after a source ++ * attaches (seen 0.15 s and about 1 s after the first enable, 2026-10-26/27). ++ * Put the active budget back once per window; a second change within the ++ * window is a real ownership fault. ++ */ ++static int j71t_reassert(struct j71t_charger *charger, u8 input, u8 control, u8 charge) ++{ ++ int ret; ++ ++ if (time_before(jiffies, charger->reassert_after)) ++ return J71T_EREGREADBACK; ++ charger->reassert_after = jiffies + msecs_to_jiffies(J71T_REASSERT_WINDOW_MS); ++ dev_info(charger->dev, ++ "charger registers changed (input 0x%02x, control 0x%02x, charge 0x%02x); reasserting the %d mA budget\n", ++ input, control, charge, charger->budget_ua / 1000); ++ ret = j71t_apply(charger, charger->charge_code, charger->input_code); ++ if (!ret && (control & J71T_INPUT_SUSPEND)) ++ ret = j71t_set_suspend(charger, 0); ++ if (!ret) ++ ret = j71t_check_owned(charger, &input, &control, &charge); ++ return ret; ++} ++ ++/* ++ * Do not fight a lower PMU input setting. Its electrical cause is unknown: ++ * D2365's iOS isInputLimited callback (6bc038c) is constant false. This is ++ * an observed setpoint reduction, not a fabricated VBUS/droop measurement. ++ */ ++static int j71t_input_reduced(struct j71t_charger *charger, u8 input) ++{ ++ int index = j71t_choose_budget(charger->budget_ua); ++ int ret; ++ ++ if (index > 0) ++ index--; ++ while (index > 0 && charger->input_codes[index] > input) ++ index--; ++ charger->adaptive_ua = j71t_budgets_ua[index]; ++ charger->adaptive_after = jiffies + msecs_to_jiffies(J71T_BACKOFF_HOLD_MS); ++ charger->backoff_cause = "pmic-input-reduced"; ++ charger->backoffs++; ++ charger->budget_ua = charger->adaptive_ua; ++ charger->input_code = min(charger->input_codes[index], input); ++ charger->charge_code = j71t_charge_code(charger->budget_ua); ++ ret = j71t_enable(charger); ++ dev_warn_ratelimited(charger->dev, ++ "PMU input reduced to 0x%02x; back-off %d mA for 60 s (cause unverified), result %d\n", ++ input, charger->budget_ua / 1000, ret); ++ return ret; ++} ++ ++/* A readable hot pack must still trip while another supplier read fails. */ ++static int j71t_thermal_floor(struct j71t_charger *charger) ++{ ++ u8 cap; ++ int ret = j71t_read(charger, J71T_MAX_CHARGE_CURRENT, &cap); ++ ++ if (ret) ++ return ret; ++ if ((cap & 0x3f) < J71T_USB_CHARGE_CODE) ++ return -ERANGE; ++ ret = j71t_apply(charger, J71T_USB_CHARGE_CODE, charger->input_codes[0]); ++ if (!ret) { ++ charger->budget_ua = J71T_FLOOR_UA; ++ charger->charge_code = J71T_USB_CHARGE_CODE; ++ charger->input_code = charger->input_codes[0]; ++ charger->retry_after = jiffies + msecs_to_jiffies(J71T_OWNER_RETRY_MS); ++ dev_warn(charger->dev, "thermal floor while another safety sample is unavailable\n"); ++ } ++ return ret; ++} ++ ++static void j71t_policy_work(struct work_struct *work) ++{ ++ struct j71t_charger *charger = container_of(to_delayed_work(work), ++ struct j71t_charger, work); ++ int ret = 0, target, index, online, previous; ++ unsigned int id; ++ ++ mutex_lock(&charger->lock); ++ if (charger->stopping) ++ goto out; ++ if (charger->faulted && !time_before(jiffies, charger->fault_until)) ++ charger->faulted = false; ++ online = j71t_online(charger); ++ ret = apple_tristar_attach_id(charger->source, &id); ++ if (!ret && id != charger->attach_id) { ++ j71t_new_attach(charger, id); ++ ret = j71t_restore(charger); ++ if (ret) ++ goto report; ++ } ++ if (!online) { ++ charger->holding = false; ++ charger->sample_pending = false; ++ ret = j71t_restore(charger); ++ if (!ret) ++ ret = j71t_set_suspend(charger, J71T_INPUT_SUSPEND); ++ goto report; ++ } ++ if (charger->disabled || charger->faulted || !charger->calibrated) { ++ ret = j71t_restore(charger); ++ goto report; ++ } ++ /* A failed rollback must finish before taking a fresh snapshot. */ ++ if (charger->saved && !charger->active) { ++ ret = j71t_restore(charger); ++ if (ret) ++ goto report; ++ } ++ if (charger->active) { ++ u8 input = 0, control = 0, charge = 0; ++ ++ ret = j71t_check_owned(charger, &input, &control, &charge); ++ if (j71t_is_fault(ret) && input < charger->input_code && ++ !(control & J71T_INPUT_SUSPEND) && charge == charger->charge_code) { ++ ret = j71t_input_reduced(charger, input); ++ if (ret) ++ goto restore; ++ } ++ if (j71t_is_fault(ret)) ++ ret = j71t_reassert(charger, input, control, charge); ++ if (j71t_is_fault(ret)) ++ goto restore; ++ if (ret) ++ goto transient; ++ } ++ if (online < 0) ++ goto transient; ++ /* One slow upward retry per minute after an observed PMU reduction. */ ++ if (charger->adaptive_ua && !time_before(jiffies, charger->adaptive_after)) { ++ index = j71t_choose_budget(charger->adaptive_ua); ++ if (index + 1 < (int)ARRAY_SIZE(j71t_budgets_ua)) ++ charger->adaptive_ua = j71t_budgets_ua[index + 1]; ++ charger->adaptive_after = jiffies + msecs_to_jiffies(J71T_BACKOFF_HOLD_MS); ++ } ++ target = j71t_policy_limit(charger); ++ if (target < 0) ++ goto transient; ++ ret = j71t_battery_safe(charger); ++ if (ret == -EAGAIN) ++ goto transient; ++ if (ret && ret != -EOVERFLOW) ++ goto restore; ++ if (ret == -EOVERFLOW) ++ target = J71T_FLOOR_UA; ++ /* Guard the existing budget before considering a new one. */ ++ if (charger->active) { ++ ret = j71t_guard(charger); ++ if (ret == -EOVERFLOW || ret == -ETIME || ret == -EBUSY) ++ target = J71T_FLOOR_UA; ++ else if (ret == -ERANGE && target < charger->budget_ua) ++ ret = 0; ++ else if (ret == -EAGAIN || (ret && ret != -ERANGE && ret != -ENOLINK)) ++ goto transient; ++ else if (ret) ++ goto restore; ++ } ++ charger->sample_pending = false; ++ previous = charger->active ? charger->budget_ua : 0; ++ if (target < previous) ++ charger->retry_after = jiffies + msecs_to_jiffies(J71T_OWNER_RETRY_MS); ++ else if (previous && target > previous && ++ time_before(jiffies, charger->retry_after)) ++ target = previous; ++ index = j71t_choose_budget(target); ++ /* At most one upward step per second, including supplier notifications. */ ++ if (index >= 0 && target > previous) { ++ int next = previous ? j71t_choose_budget(previous) + 1 : 0; ++ ++ if (previous && time_before(jiffies, charger->ramp_after)) ++ goto report; ++ index = min(index, next); ++ } ++ if (index >= 0 && (!charger->active || j71t_budgets_ua[index] != previous)) { ++ charger->budget_ua = j71t_budgets_ua[index]; ++ charger->input_code = charger->input_codes[index]; ++ charger->charge_code = j71t_charge_code(charger->budget_ua); ++ ret = j71t_enable(charger); ++ if (ret) ++ goto restore; ++ charger->ramp_after = jiffies + msecs_to_jiffies(1000); ++ } ++ goto report; ++transient: ++ /* Source/PMIC transport grace must not mask a valid pack guard. */ ++ ret = j71t_battery_safe(charger); ++ if (ret == -ERANGE) ++ goto restore; ++ if (ret == -EOVERFLOW && charger->active && charger->budget_ua > J71T_FLOOR_UA) { ++ ret = j71t_thermal_floor(charger); ++ if (ret) ++ goto restore; ++ } ++ ret = -EAGAIN; ++ if (!charger->sample_pending) { ++ charger->sample_pending = true; ++ charger->sample_until = jiffies + msecs_to_jiffies(J71T_SAMPLE_GRACE_MS); ++ } ++ if (!charger->active || time_before(jiffies, charger->sample_until)) ++ goto report; ++ dev_warn(charger->dev, "source/battery sample unavailable for 60 s; restoring handoff\n"); ++restore: ++ j71t_fault(charger, ret); ++ if (charger->active) ++ dev_warn(charger->dev, "charging policy stepped down: %d\n", ret); ++ j71t_fault(charger, j71t_restore(charger)); ++ charger->retry_after = jiffies + msecs_to_jiffies(J71T_OWNER_RETRY_MS); ++report: ++ j71t_fault(charger, ret); ++ if (charger->faulted) ++ ret = J71T_EREGREADBACK; ++ else if (charger->disabled) ++ ret = -ECANCELED; ++ else if (!charger->calibrated) ++ ret = -ENODATA; ++ j71t_policy_log(charger, ret); ++ mod_delayed_work(system_power_efficient_wq, &charger->work, ++ msecs_to_jiffies(1000)); ++out: ++ mutex_unlock(&charger->lock); ++ if (charger->psy) ++ power_supply_changed(charger->psy); ++} ++ ++static int j71t_get_property(struct power_supply *psy, enum power_supply_property prop, ++ union power_supply_propval *value) ++{ ++ struct j71t_charger *charger = power_supply_get_drvdata(psy); ++ u8 raw; ++ int ret, online; ++ ++ mutex_lock(&charger->lock); ++ switch (prop) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ online = j71t_online(charger); ++ ret = online < 0 ? online : 0; ++ value->intval = online == 1; ++ break; ++ case POWER_SUPPLY_PROP_USB_TYPE: ++ ret = power_supply_get_property_from_supplier(psy, ++ POWER_SUPPLY_PROP_USB_TYPE, value); ++ break; ++ case POWER_SUPPLY_PROP_SCOPE: ++ value->intval = POWER_SUPPLY_SCOPE_SYSTEM; ++ ret = 0; ++ break; ++ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ ret = j71t_read(charger, J71T_INPUT_LIMIT, &raw); ++ if (!ret) ++ value->intval = 75000 + raw * 12500; ++ break; ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ ret = j71t_read(charger, J71T_CHARGE_CURRENT, &raw); ++ if (!ret) ++ value->intval = raw * 50000; ++ break; ++ case POWER_SUPPLY_PROP_STATUS: ++ ret = j71t_read(charger, J71T_STATUS, &raw); ++ online = j71t_online(charger); ++ if (!ret) { ++ value->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ if (online == 1 && !(raw & (J71T_CHARGE_TIMEOUT | J71T_CHARGE_END)) && ++ (raw & 3)) ++ value->intval = POWER_SUPPLY_STATUS_CHARGING; ++ } ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ mutex_unlock(&charger->lock); ++ return ret; ++} ++ ++static int j71t_set_property(struct power_supply *psy, enum power_supply_property prop, ++ const union power_supply_propval *value) ++{ ++ struct j71t_charger *charger = power_supply_get_drvdata(psy); ++ int ret = 0; ++ ++ if (prop != POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT || value->intval < 0 || ++ (value->intval && j71t_valid_limit(value->intval) != value->intval)) ++ return -EINVAL; ++ mutex_lock(&charger->lock); ++ if (charger->stopping) { ++ ret = -ESHUTDOWN; ++ } else if (value->intval > charger->max_limit_ua) { ++ ret = -EINVAL; ++ } else if (!value->intval) { ++ charger->disabled = true; ++ j71t_revoke_override(charger); ++ ret = j71t_restore(charger); ++ /* Target zero is a real disable, never a budget-change ritual. */ ++ if (!ret) ++ ret = j71t_set_suspend(charger, J71T_INPUT_SUSPEND); ++ } else { ++ charger->disabled = false; ++ charger->user_limit_ua = value->intval; ++ /* Authorization persists across deferred guards; work applies it. */ ++ dev_info(charger->dev, "user authorizes %d mA input\n", value->intval / 1000); ++ } ++ j71t_fault(charger, ret); ++ if (!charger->stopping) ++ mod_delayed_work(system_power_efficient_wq, &charger->work, 0); ++ mutex_unlock(&charger->lock); ++ power_supply_changed(psy); ++ return ret; ++} ++ ++static int j71t_property_writeable(struct power_supply *psy, enum power_supply_property prop) ++{ ++ return prop == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; ++} ++ ++static void j71t_external_power_changed(struct power_supply *psy) ++{ ++ struct j71t_charger *charger = power_supply_get_drvdata(psy); ++ ++ mutex_lock(&charger->lock); ++ if (!charger->stopping) ++ mod_delayed_work(system_power_efficient_wq, &charger->work, 0); ++ mutex_unlock(&charger->lock); ++} ++ ++static const enum power_supply_property j71t_properties[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_USB_TYPE, ++ POWER_SUPPLY_PROP_SCOPE, ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, ++}; ++ ++static const struct power_supply_desc j71t_desc = { ++ .name = "charger", ++ .type = POWER_SUPPLY_TYPE_USB, ++ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | ++ BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | ++ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA), ++ .properties = j71t_properties, ++ .num_properties = ARRAY_SIZE(j71t_properties), ++ .get_property = j71t_get_property, ++ .set_property = j71t_set_property, ++ .property_is_writeable = j71t_property_writeable, ++ .external_power_changed = j71t_external_power_changed, ++}; ++ ++static ssize_t charge7_state_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct j71t_charger *charger = power_supply_get_drvdata(dev_get_drvdata(dev)); ++ u8 input = 0; ++ int ret, len; ++ ++ mutex_lock(&charger->lock); ++ ret = j71t_read(charger, J71T_INPUT_LIMIT, &input); ++ len = sysfs_emit(buf, ++ "attach=%u source_type=%d ceiling_ua=%d step_ua=%d live_ua=%d override_ua=%d adaptive_ua=%d backoffs=%u last_backoff=%s vbus_mv=unavailable policy_error=%d active=%d\n", ++ charger->attach_id, charger->source_type, charger->ceiling_ua, ++ charger->active ? charger->budget_ua : 0, ++ ret ? ret : 75000 + input * 12500, charger->user_limit_ua, ++ charger->adaptive_ua, charger->backoffs, ++ charger->backoff_cause ?: "none", charger->policy_error, charger->active); ++ mutex_unlock(&charger->lock); ++ return len; ++} ++static DEVICE_ATTR_RO(charge7_state); ++ ++static struct attribute *j71t_attrs[] = { ++ &dev_attr_charge7_state.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(j71t); ++ ++static int j71t_probe(struct platform_device *pdev) ++{ ++ struct power_supply_config config = {}; ++ const struct firmware *fw; ++ struct j71t_charger *charger; ++ u32 limit; ++ int ret, i; ++ ++ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); ++ if (!charger) ++ return -ENOMEM; ++ charger->dev = &pdev->dev; ++ charger->source = devm_power_supply_get_by_reference(&pdev->dev, "power-supplies"); ++ if (IS_ERR(charger->source)) ++ return PTR_ERR(charger->source); ++ if (!charger->source) ++ return -EPROBE_DEFER; ++ if (!device_link_add(&pdev->dev, &charger->source->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) ++ return -ENOMEM; ++ charger->map = dev_get_regmap(pdev->dev.parent, NULL); ++ if (!charger->map) ++ return -ENODEV; ++ ret = devm_mutex_init(&pdev->dev, &charger->lock); ++ if (ret) ++ return ret; ++ ret = devm_delayed_work_autocancel(&pdev->dev, &charger->work, j71t_policy_work); ++ if (ret) ++ return ret; ++ ret = request_firmware_direct(&fw, "apple/j71t-CBAT.bin", &pdev->dev); ++ if (!ret) { ++ for (i = 0; i < ARRAY_SIZE(j71t_budgets_ua); i++) { ++ if (j71t_budgets_ua[i] == J71T_REQUEST_UA) ++ ret = j71t_calibrate(fw->data, fw->size, &charger->input_codes[i]); ++ else ++ ret = j71t_calibrate_budget(fw->data, fw->size, ++ j71t_budgets_ua[i] / 1000, ++ &charger->input_codes[i]); ++ if (ret) ++ break; ++ } ++ release_firmware(fw); ++ charger->calibrated = !ret; ++ } ++ if (ret) ++ dev_warn(&pdev->dev, ++ "CBAT unavailable/unsupported (%d): charging writes disabled\n", ret); ++ charger->reassert_after = jiffies; ++ charger->max_limit_ua = J71T_FLOOR_UA; ++ if (!of_property_read_u32(pdev->dev.of_node, ++ "apple,usb-input-limit-max-microamp", &limit)) ++ charger->max_limit_ua = j71t_valid_limit(limit); ++ config.drv_data = charger; ++ config.fwnode = dev_fwnode(&pdev->dev); ++ config.attr_grp = j71t_groups; ++ charger->psy = devm_power_supply_register(&pdev->dev, &j71t_desc, &config); ++ if (IS_ERR(charger->psy)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(charger->psy), ++ "charger supply registration\n"); ++ platform_set_drvdata(pdev, charger); ++ mod_delayed_work(system_power_efficient_wq, &charger->work, 0); ++ dev_info(&pdev->dev, "J71t charger: automatic guarded source-current policy\n"); ++ return 0; ++} ++ ++static int j71t_quiesce(struct device *dev) ++{ ++ struct j71t_charger *charger = dev_get_drvdata(dev); ++ int attempt, ret = 0; ++ ++ mutex_lock(&charger->lock); ++ charger->stopping = true; ++ mutex_unlock(&charger->lock); ++ cancel_delayed_work_sync(&charger->work); ++ for (attempt = 0; attempt < 3; attempt++) { ++ mutex_lock(&charger->lock); ++ ret = j71t_restore(charger); ++ mutex_unlock(&charger->lock); ++ if (!ret) ++ break; ++ msleep(20); ++ } ++ if (ret) ++ dev_crit(charger->dev, "charger restore failed at shutdown/remove: %d\n", ret); ++ return ret; ++} ++ ++static void j71t_stop(struct platform_device *pdev) ++{ ++ j71t_quiesce(&pdev->dev); ++} ++ ++static int j71t_resume(struct device *dev) ++{ ++ struct j71t_charger *charger = dev_get_drvdata(dev); ++ ++ mutex_lock(&charger->lock); ++ charger->stopping = false; ++ mod_delayed_work(system_power_efficient_wq, &charger->work, 0); ++ mutex_unlock(&charger->lock); ++ return 0; ++} ++ ++static int j71t_suspend(struct device *dev) ++{ ++ int ret = j71t_quiesce(dev); ++ ++ /* A failed suspend need not receive resume; keep rollback retries alive. */ ++ if (ret) ++ j71t_resume(dev); ++ return ret; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(j71t_pm_ops, j71t_suspend, j71t_resume); ++ ++static const struct of_device_id j71t_match[] = { ++ { .compatible = "apple,j71t-d2365-charger" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, j71t_match); ++ ++static struct platform_driver j71t_driver = { ++ .probe = j71t_probe, ++ .remove = j71t_stop, ++ .shutdown = j71t_stop, ++ .driver = { ++ .name = "apple-j71t-charger", ++ .of_match_table = j71t_match, ++ .pm = pm_sleep_ptr(&j71t_pm_ops), ++ }, ++}; ++module_platform_driver(j71t_driver); ++MODULE_DESCRIPTION("Source-aware guarded Apple J71t D2365 charging policy"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/apple_tristar_auto.h b/drivers/power/supply/apple_tristar_auto.h +new file mode 100644 +index 0000000000000000000000000000000000000000..1b34eb423913f7c7e86fd0ef77960c523092c772 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_auto.h +@@ -0,0 +1,326 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * J71t Lightning accessory policy: camera adapter to USB host mode. ++ * ++ * iOS 16.7.10 on this unit (2026-10-27 capture, DigitalID 11 f0 00 00 00 00): ++ * about 1 s after the ID, IOAccessoryManagerUserClient selector 1 calls ++ * configureUSBMode(2). IOAccessoryManager_updateUSBConnectType maps mode 2 ++ * to ConnectType 4 "Device", the connect shim publishes AppleUSBCableType ++ * USBDevice, and the arbitrator applies publish mask 5: EHCI and OHCI are ++ * published and the device nub stays. The adapter's hub enumerates at high ++ * speed. There is no device-mode or iAP phase. The power handshake follows, ++ * and the input limit is then 2100 mA at 5 V while the iPad is the host. ++ * ++ * This file holds only the decisions, so the offline harness runs the same ++ * code as the kernel. The driver does all I/O and reports the results. ++ * Times are milliseconds and may wrap. ++ */ ++#ifndef APPLE_TRISTAR_AUTO_H ++#define APPLE_TRISTAR_AUTO_H ++ ++/* CBTL1610 0x0c: both low bits are strict VBUS, bit 7 an attached plug. */ ++#define TRISTAR_AUTO_VBUS 0x03 ++#define TRISTAR_AUTO_PRESENT 0x80 ++#define TRISTAR_AUTO_ORIENTATION 0x40 ++#define TRISTAR_AUTO_ID_LEN 6 ++ ++enum tristar_auto_phase { ++ TRISTAR_AUTO_OFF, /* policy disabled */ ++ TRISTAR_AUTO_EMPTY, /* nothing attached */ ++ TRISTAR_AUTO_DEVICE, /* attached, device role, GetID pending */ ++ TRISTAR_AUTO_OTHER, /* identified, not a host-mode accessory */ ++ TRISTAR_AUTO_HANDSHAKE, /* host adapter identified, 0x72/0x70 next */ ++ TRISTAR_AUTO_ENTER, /* switch to host requested */ ++ TRISTAR_AUTO_HOST, /* host role, owned by this policy */ ++ TRISTAR_AUTO_INHIBIT, /* role taken over or entry failed; wait for unplug */ ++}; ++ ++enum tristar_auto_action { ++ TRISTAR_AUTO_NONE, ++ TRISTAR_AUTO_PROBE, /* GetID without reorient */ ++ TRISTAR_AUTO_POWER_HANDSHAKE, ++ TRISTAR_AUTO_TO_HOST, ++ TRISTAR_AUTO_TO_DEVICE, ++}; ++ ++struct tristar_auto_cfg { ++ unsigned int probe_ms; /* GetID period, no active USB link */ ++ unsigned int busy_probe_ms; /* ... while a USB host uses the gadget */ ++ unsigned int verify_ms; /* host role: re-check the ID */ ++ unsigned int settle_ms; /* after a plug edge */ ++ unsigned int detach_polls; /* consecutive empty 0x0c reads */ ++ unsigned int handshake_tries; ++ int input_ua; /* host role, after a good power handshake */ ++ int floor_ua; /* host role, no power handshake */ ++}; ++ ++struct tristar_auto { ++ enum tristar_auto_phase phase; ++ int status; /* last good 0x0c, or -1 */ ++ unsigned int empty; /* consecutive empty 0x0c reads */ ++ bool was_empty; /* the previous read was empty */ ++ unsigned long next_probe; ++ u8 id[TRISTAR_AUTO_ID_LEN]; ++ bool id_valid; ++ bool adapter; /* id selects host mode */ ++ int id_status; /* last GetID result */ ++ int ph_status; /* last power handshake, 1 = not run */ ++ unsigned int ph_tries; ++ bool ph_ok; ++ unsigned int probes; ++ unsigned int probe_errors; ++ unsigned int host_entries; ++ unsigned int role_errors; ++ unsigned int detaches; ++ unsigned int inhibits; ++}; ++ ++struct tristar_auto_in { ++ unsigned long now; ++ int status; /* 0x0c or -errno */ ++ bool enabled; ++ bool host; /* the role switch is in host mode */ ++ bool owned; /* ... on this policy's request */ ++ bool link_active; /* a USB host configured the gadget, not suspended */ ++}; ++ ++static inline bool tristar_auto_due(unsigned long now, unsigned long when) ++{ ++ return (long)(now - when) >= 0; ++} ++ ++/* The camera adapter, DigitalID 11 f0 ..: USB=2 UART=1 PWR=0xf. */ ++static inline bool tristar_auto_is_host_adapter(const u8 *id) ++{ ++ return id[0] == 0x11 && id[1] == 0xf0; ++} ++ ++static inline bool tristar_auto_attached(int status) ++{ ++ return status >= 0 && (status & (TRISTAR_AUTO_PRESENT | TRISTAR_AUTO_VBUS)); ++} ++ ++static inline const char *tristar_auto_phase_name(enum tristar_auto_phase phase) ++{ ++ static const char *const names[] = { ++ [TRISTAR_AUTO_OFF] = "off", ++ [TRISTAR_AUTO_EMPTY] = "empty", ++ [TRISTAR_AUTO_DEVICE] = "device", ++ [TRISTAR_AUTO_OTHER] = "other", ++ [TRISTAR_AUTO_HANDSHAKE] = "handshake", ++ [TRISTAR_AUTO_ENTER] = "enter-host", ++ [TRISTAR_AUTO_HOST] = "host", ++ [TRISTAR_AUTO_INHIBIT] = "inhibit", ++ }; ++ ++ return (unsigned int)phase < ARRAY_SIZE(names) ? names[phase] : "?"; ++} ++ ++static inline void tristar_auto_forget(struct tristar_auto *ta) ++{ ++ ta->id_valid = false; ++ ta->adapter = false; ++ ta->ph_ok = false; ++ ta->ph_status = 1; ++ ta->ph_tries = 0; ++} ++ ++static inline void tristar_auto_init(struct tristar_auto *ta) ++{ ++ *ta = (struct tristar_auto){ .phase = TRISTAR_AUTO_OFF, .status = -1, .ph_status = 1 }; ++} ++ ++/* One poll. The caller then runs the returned action and reports back. */ ++static inline enum tristar_auto_action ++tristar_auto_step(struct tristar_auto *ta, const struct tristar_auto_cfg *cfg, ++ const struct tristar_auto_in *in) ++{ ++ const int plug_bits = TRISTAR_AUTO_PRESENT | TRISTAR_AUTO_ORIENTATION; ++ bool attached, edge, replug; ++ int prev; ++ ++ if (!in->enabled) { ++ ta->phase = TRISTAR_AUTO_OFF; ++ return TRISTAR_AUTO_NONE; ++ } ++ if (ta->phase == TRISTAR_AUTO_OFF) { ++ ta->phase = TRISTAR_AUTO_EMPTY; ++ ta->empty = 0; ++ ta->was_empty = false; ++ ta->status = -1; ++ tristar_auto_forget(ta); ++ } ++ /* An I2C error is no evidence either way. */ ++ if (in->status < 0) ++ return TRISTAR_AUTO_NONE; ++ ++ attached = tristar_auto_attached(in->status); ++ prev = ta->status; ++ ta->status = in->status; ++ replug = attached && ta->was_empty; ++ ta->was_empty = !attached; ++ ta->empty = attached ? 0 : ta->empty + 1; ++ edge = replug || (prev >= 0 && attached && ((prev ^ in->status) & plug_bits)); ++ ++ if (ta->empty >= cfg->detach_polls && ta->phase != TRISTAR_AUTO_EMPTY) { ++ bool release = ta->phase == TRISTAR_AUTO_HOST && in->host && in->owned; ++ ++ ta->phase = TRISTAR_AUTO_EMPTY; ++ tristar_auto_forget(ta); ++ if (release) { ++ ta->detaches++; ++ return TRISTAR_AUTO_TO_DEVICE; ++ } ++ return TRISTAR_AUTO_NONE; ++ } ++ ++ switch (ta->phase) { ++ case TRISTAR_AUTO_EMPTY: ++ if (attached) { ++ ta->phase = TRISTAR_AUTO_DEVICE; ++ ta->next_probe = in->now + cfg->settle_ms; ++ } ++ return TRISTAR_AUTO_NONE; ++ case TRISTAR_AUTO_DEVICE: ++ case TRISTAR_AUTO_OTHER: ++ /* A host role someone else asked for is theirs to end. */ ++ if (!attached || in->host) ++ return TRISTAR_AUTO_NONE; ++ if (edge) { ++ ta->phase = TRISTAR_AUTO_DEVICE; ++ ta->next_probe = in->now + cfg->settle_ms; ++ } ++ if (!tristar_auto_due(in->now, ta->next_probe)) ++ return TRISTAR_AUTO_NONE; ++ ta->next_probe = in->now + (in->link_active ? cfg->busy_probe_ms : cfg->probe_ms); ++ ta->probes++; ++ return TRISTAR_AUTO_PROBE; ++ case TRISTAR_AUTO_HANDSHAKE: ++ return TRISTAR_AUTO_POWER_HANDSHAKE; ++ case TRISTAR_AUTO_ENTER: ++ if (in->host && in->owned) { ++ ta->phase = TRISTAR_AUTO_HOST; ++ return TRISTAR_AUTO_NONE; ++ } ++ return TRISTAR_AUTO_TO_HOST; ++ case TRISTAR_AUTO_HOST: ++ if (!in->host || !in->owned) { ++ ta->phase = TRISTAR_AUTO_INHIBIT; ++ ta->inhibits++; ++ return TRISTAR_AUTO_NONE; ++ } ++ if (!attached) ++ return TRISTAR_AUTO_NONE; ++ if (edge || tristar_auto_due(in->now, ta->next_probe)) { ++ ta->next_probe = in->now + cfg->verify_ms; ++ ta->probes++; ++ return TRISTAR_AUTO_PROBE; ++ } ++ return TRISTAR_AUTO_NONE; ++ case TRISTAR_AUTO_INHIBIT: ++ case TRISTAR_AUTO_OFF: ++ break; ++ } ++ return TRISTAR_AUTO_NONE; ++} ++ ++/* GetID finished: ret 0 with a valid id, or -errno. */ ++static inline enum tristar_auto_action ++tristar_auto_probed(struct tristar_auto *ta, const struct tristar_auto_cfg *cfg, ++ unsigned long now, int ret, const u8 *id) ++{ ++ bool adapter; ++ ++ ta->id_status = ret; ++ if (ta->phase != TRISTAR_AUTO_DEVICE && ta->phase != TRISTAR_AUTO_OTHER && ++ ta->phase != TRISTAR_AUTO_HOST) ++ return TRISTAR_AUTO_NONE; ++ if (ret) { ++ ta->probe_errors++; ++ return TRISTAR_AUTO_NONE; ++ } ++ adapter = tristar_auto_is_host_adapter(id); ++ memcpy(ta->id, id, TRISTAR_AUTO_ID_LEN); ++ ta->id_valid = true; ++ if (ta->phase == TRISTAR_AUTO_HOST) { ++ if (adapter) ++ return TRISTAR_AUTO_NONE; ++ /* A fast swap to another accessory, e.g. the Mac cable. */ ++ ta->adapter = false; ++ ta->ph_ok = false; ++ ta->phase = TRISTAR_AUTO_OTHER; ++ ta->next_probe = now + cfg->probe_ms; ++ ta->detaches++; ++ return TRISTAR_AUTO_TO_DEVICE; ++ } ++ ta->adapter = adapter; ++ if (!adapter) { ++ ta->phase = TRISTAR_AUTO_OTHER; ++ return TRISTAR_AUTO_NONE; ++ } ++ ta->phase = TRISTAR_AUTO_HANDSHAKE; ++ ta->ph_status = 1; ++ ta->ph_tries = 0; ++ ta->ph_ok = false; ++ return TRISTAR_AUTO_POWER_HANDSHAKE; ++} ++ ++/* ++ * Power handshake finished. iOS enumerated the hub before its handshake ++ * completed, so host mode does not wait for it: after the last try the port ++ * becomes a host anyway, with the floor input budget. ++ */ ++static inline enum tristar_auto_action ++tristar_auto_handshake_done(struct tristar_auto *ta, const struct tristar_auto_cfg *cfg, ++ int ret) ++{ ++ if (ta->phase != TRISTAR_AUTO_HANDSHAKE) ++ return TRISTAR_AUTO_NONE; ++ ta->ph_tries++; ++ ta->ph_status = ret; ++ ta->ph_ok = !ret; ++ if (ret && ta->ph_tries < cfg->handshake_tries) ++ return TRISTAR_AUTO_NONE; ++ ta->phase = TRISTAR_AUTO_ENTER; ++ return TRISTAR_AUTO_TO_HOST; ++} ++ ++static inline void tristar_auto_role_done(struct tristar_auto *ta, ++ const struct tristar_auto_cfg *cfg, ++ unsigned long now, bool to_host, int ret) ++{ ++ if (!to_host) ++ return; ++ if (ta->phase != TRISTAR_AUTO_ENTER) ++ return; ++ if (ret) { ++ /* No retry loop: the next unplug starts over. */ ++ ta->role_errors++; ++ ta->phase = TRISTAR_AUTO_INHIBIT; ++ return; ++ } ++ ta->phase = TRISTAR_AUTO_HOST; ++ ta->host_entries++; ++ ta->next_probe = now + cfg->verify_ms; ++} ++ ++/* ++ * Input power while the adapter is ours. iOS: PowerHandshake Yes, USBPower ++ * Yes, 2100 mA at 5 V in host mode. Returns false when the ordinary ++ * device-mode rules apply. ++ */ ++static inline bool tristar_auto_supply(const struct tristar_auto *ta, ++ const struct tristar_auto_cfg *cfg, int status, ++ int *online, int *current_ua) ++{ ++ if (!ta->id_valid || !ta->adapter) ++ return false; ++ if (ta->phase != TRISTAR_AUTO_HANDSHAKE && ta->phase != TRISTAR_AUTO_ENTER && ++ ta->phase != TRISTAR_AUTO_HOST) ++ return false; ++ *online = status >= 0 && (status & TRISTAR_AUTO_VBUS) == TRISTAR_AUTO_VBUS; ++ *current_ua = !*online ? 0 : ta->ph_ok ? cfg->input_ua : cfg->floor_ua; ++ return true; ++} ++ ++#endif /* APPLE_TRISTAR_AUTO_H */ +diff --git a/drivers/power/supply/apple_tristar_idbus.h b/drivers/power/supply/apple_tristar_idbus.h +new file mode 100644 +index 0000000000000000000000000000000000000000..ed20e4d8a28093b1e35ed4dfd7ac9bee24e64f38 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_idbus.h +@@ -0,0 +1,957 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * CBTL1610 (Tristar) Lightning ID-bus master, driven over I2C, as ++ * reconstructed from the iOS 16.7.10 J71t kernelcache (AppleCBTL1610 and ++ * AppleTriStarIC) and corrected by the 2026-10-26 hardware trial. Tristar ++ * clocks the wire, frames BREAKs and appends the request CRC itself. The ++ * host prepares the engine (0x60 = 0x01), loads the TX window that ends at ++ * 0x60 (request bytes reversed, then the RX length byte at 0x60), reads it ++ * back, writes GO to 0x61 and reads the response forward from the RX window ++ * at 0x20. ++ * ++ * The RX window is never written: on this IC a write there overwrites the ++ * retained response. 0x20/0x40/0x41 are another Tristar generation's ++ * transfer registers and are inert here. ++ * ++ * All register access goes through struct tristar_idbus_ops so that the ++ * offline harness runs this exact code against a fake register file. The ++ * includer provides u8, bool, size_t, memcpy, memcmp, memset, snprintf, ++ * __printf and the errno values. ++ * ++ * Safety envelope: only volatile registers 0x05, 0x0a and the TX window ++ * 0x5d..0x61 are written. The RX window 0x20..0x27, 0x17 (AID switch ++ * config), 0x0e (detect latch), the mux registers 0x01..0x03 and every ++ * OTP/ESN request stay untouched. ++ */ ++#ifndef APPLE_TRISTAR_IDBUS_H ++#define APPLE_TRISTAR_IDBUS_H ++ ++#define TRISTAR_IDBUS_REG_DXCTRL 0x01 ++#define TRISTAR_IDBUS_REG_ACCMUX 0x02 ++/* bit 0 VBUS sink, bit 1 reorient, bit 2 BREAK, bit 3 ID-bus reset hold */ ++#define TRISTAR_IDBUS_REG_CTRL 0x05 ++#define TRISTAR_IDBUS_CTRL_REORIENT 0x02 ++#define TRISTAR_IDBUS_CTRL_RESET 0x08 ++/* Pin faults: bit 0 ACC1, bit 3 ACC2, bit 1 DP1/DN1, bit 2 DP2/DN2 */ ++#define TRISTAR_IDBUS_REG_FAULT 0x0a ++#define TRISTAR_IDBUS_FAULT_PINS 0x0f ++#define TRISTAR_IDBUS_REG_STATUS 0x0c ++#define TRISTAR_IDBUS_REG_EVENT 0x0d ++#define TRISTAR_IDBUS_REG_REVISION 0x0f ++#define TRISTAR_IDBUS_REG_AIDSW 0x17 ++#define TRISTAR_IDBUS_REG_RX 0x20 /* response, forward; read only */ ++#define TRISTAR_IDBUS_REG_TX_END 0x60 /* RX length byte; prepare */ ++#define TRISTAR_IDBUS_REG_GO 0x61 /* ((tx - 1) << 1) | 1 */ ++#define TRISTAR_IDBUS_TX_PREPARE 0x01 ++#define TRISTAR_IDBUS_REVISION_J71T 0x8a ++ ++#define TRISTAR_IDBUS_EV_DETECT 0x01 ++#define TRISTAR_IDBUS_EV_WAKE 0x02 ++#define TRISTAR_IDBUS_EV_RESET 0x04 ++#define TRISTAR_IDBUS_EV_DIGITAL_ID 0x08 ++#define TRISTAR_IDBUS_EV_DONE 0x10 ++#define TRISTAR_IDBUS_EV_ERROR 0x20 ++#define TRISTAR_IDBUS_EV_TIMEOUT 0x40 ++#define TRISTAR_IDBUS_EV_FAULT 0x80 ++#define TRISTAR_IDBUS_EV_DONE_MASK 0x38 ++ ++#define TRISTAR_IDBUS_GET_ID 0x74 ++#define TRISTAR_IDBUS_ID 0x75 ++#define TRISTAR_IDBUS_ID_LEN 6 ++#define TRISTAR_IDBUS_HOSTID 0x0002 /* IOAccessoryManagerSystemID */ ++#define TRISTAR_IDBUS_POWER 0x70 ++#define TRISTAR_IDBUS_POWER_ACK 0x71 ++#define TRISTAR_IDBUS_GET_STATE 0x72 ++#define TRISTAR_IDBUS_STATE 0x73 ++#define TRISTAR_IDBUS_POWER_RETRY 0x08 /* 0x70 flags bit on iOS's retry path */ ++#define TRISTAR_IDBUS_MAX_TX 3 /* 0x5d..0x5f; 0x60 holds the RX length */ ++#define TRISTAR_IDBUS_MAX_FRAME 8 ++ ++#define TRISTAR_IDBUS_POLL_MS 10 ++#define TRISTAR_IDBUS_POLL_STEPS 10 /* 100 ms per transfer */ ++#define TRISTAR_IDBUS_SETTLE_MS 20 ++#define TRISTAR_IDBUS_REORIENT_STEPS 3 /* 20 + 3 * 10 = 50 ms after a reorient */ ++#define TRISTAR_IDBUS_RESET_HOLD_MS 500 /* AppleCBTL1610 reset pulse */ ++#define TRISTAR_IDBUS_CMDID_GAP_MS 100 /* iOS idCommandTooFast */ ++ ++/* Identify mode flags (manual variants; never applied automatically) */ ++#define TRISTAR_IDBUS_SWAP_HOSTID 0x1 ++#define TRISTAR_IDBUS_NO_REORIENT 0x2 ++ ++/* Link recovery steps (ds-lightning-host-idbus.md 9.6) */ ++#define TRISTAR_IDBUS_RECOVER_REORIENT 0 ++#define TRISTAR_IDBUS_RECOVER_RESET 1 ++#define TRISTAR_IDBUS_RECOVER_FAULT_ACK 2 ++ ++struct tristar_idbus_ops { ++ /* One register: value 0..255 or -errno */ ++ int (*read)(void *ctx, u8 reg); ++ int (*write)(void *ctx, u8 reg, u8 val); ++ /* Bus-atomic read, write (old & ~clear) | set; returns old or -errno */ ++ int (*update)(void *ctx, u8 reg, u8 clear, u8 set); ++ /* len bytes upward from reg, one I2C transaction each; 0 or -errno */ ++ int (*block_read)(void *ctx, u8 reg, u8 *buf, unsigned int len); ++ int (*block_write)(void *ctx, u8 reg, const u8 *buf, unsigned int len); ++ void (*sleep_ms)(void *ctx, unsigned int ms); ++ void (*log)(void *ctx, const char *fmt, ...) __printf(2, 3); ++}; ++ ++struct tristar_idbus { ++ const struct tristar_idbus_ops *ops; ++ void *ctx; ++ /* First I/O error of the current run; every later access is refused */ ++ int io_error; ++ /* Most recent values seen; 0 until read */ ++ u8 last_ctrl; ++ u8 last_event; ++ u8 last_fault; ++ u8 events; /* OR of every event-register read */ ++ unsigned int faults; /* pin-fault acknowledgements */ ++}; ++ ++static const u8 tristar_idbus_baseline_regs[] = { ++ TRISTAR_IDBUS_REG_STATUS, TRISTAR_IDBUS_REG_EVENT, TRISTAR_IDBUS_REG_CTRL, ++ TRISTAR_IDBUS_REG_DXCTRL, TRISTAR_IDBUS_REG_ACCMUX, TRISTAR_IDBUS_REG_AIDSW, ++ TRISTAR_IDBUS_REG_FAULT, ++}; ++ ++#define TRISTAR_IDBUS_NBASE ARRAY_SIZE(tristar_idbus_baseline_regs) ++ ++struct tristar_idbus_id_result { ++ int status; ++ unsigned int flags; ++ unsigned int attempts; /* GetID commands started */ ++ bool released_reset; ++ bool reoriented; ++ bool autonomous; /* ID from the reorient-armed exchange */ ++ u8 baseline[TRISTAR_IDBUS_NBASE]; ++ u8 retained[TRISTAR_IDBUS_MAX_FRAME]; /* RX window before this run */ ++ bool retained_ok; /* ... and a valid 0x75 frame */ ++ u8 reorient_frame[TRISTAR_IDBUS_MAX_FRAME]; /* RX right after the reorient */ ++ bool reorient_read; ++ int reorient_status; /* verdict, or -ESTALE */ ++ u8 reorient_events; /* 0x0d OR after the reorient */ ++ u8 frame[TRISTAR_IDBUS_MAX_FRAME]; /* the response, kept if bad */ ++ unsigned int frame_len; ++ int frame_status; /* header/CRC verdict */ ++ u8 orientation; /* 0x0c read with the frame */ ++ u8 id[TRISTAR_IDBUS_ID_LEN]; ++ bool id_valid; ++}; ++ ++struct tristar_idbus_ph_result { ++ int status; ++ u8 state[6]; /* 0x73 + 4 bytes + CRC */ ++ bool state_ok; ++ u8 request[3]; /* the 0x70 frame sent */ ++ u8 ack[2]; /* 0x71 + CRC */ ++ bool ack_ok; ++}; ++ ++struct tristar_idbus_recover_result { ++ int status; ++ unsigned int action; ++ int status_before; /* 0x0c, or -1 if not read */ ++ int status_after; ++ u8 events; /* 0x0d OR after a reorient */ ++ u8 fault; /* 0x0a read by fault_ack */ ++ u8 before[TRISTAR_IDBUS_MAX_FRAME]; /* RX window before a reorient */ ++ u8 frame[TRISTAR_IDBUS_MAX_FRAME]; /* ... and after */ ++ bool frame_read; ++ int frame_status; ++}; ++ ++struct tristar_idbus_fields { ++ u8 usb, accx, db, mb, uart; ++ u8 pwr, hv, ai, au; ++ u8 pu, bp, cc, bd; ++ u8 po, hs, hpm; ++}; ++ ++/* Generator 0x8c (0x31 reflected), LSB first. Seed 0xff on the wire. */ ++static u8 tristar_idbus_crc8_seed(u8 crc, const u8 *buf, unsigned int len) ++{ ++ unsigned int i; ++ ++ while (len--) { ++ crc ^= *buf++; ++ for (i = 0; i < 8; i++) ++ crc = (crc >> 1) ^ ((crc & 1) ? 0x8c : 0); ++ } ++ return crc; ++} ++ ++static u8 tristar_idbus_crc8(const u8 *buf, unsigned int len) ++{ ++ return tristar_idbus_crc8_seed(0xff, buf, len); ++} ++ ++/* A response is type + rx_len - 1 data bytes + CRC over the first rx_len. */ ++static int tristar_idbus_check_frame(const u8 *frame, unsigned int rx_len, u8 type) ++{ ++ if (frame[0] != type) ++ return -EPROTO; ++ if (tristar_idbus_crc8(frame, rx_len) != frame[rx_len]) ++ return -EBADMSG; ++ return 0; ++} ++ ++static const char *tristar_idbus_verdict(int verdict) ++{ ++ return verdict == -EPROTO ? "bad header" : verdict == -EBADMSG ? "bad CRC" : ++ verdict == -ESTALE ? "same as the retained frame, no completion event" : ++ verdict ? "not read" : "header and CRC ok"; ++} ++ ++static const char *tristar_idbus_recover_name(unsigned int action) ++{ ++ return action == TRISTAR_IDBUS_RECOVER_REORIENT ? "reorient" : ++ action == TRISTAR_IDBUS_RECOVER_RESET ? "idbus_reset" : ++ action == TRISTAR_IDBUS_RECOVER_FAULT_ACK ? "fault_ack" : "unknown"; ++} ++ ++/* IOAccessoryManagerAID::handleDigitalID field split */ ++static void tristar_idbus_decode(const u8 *id, struct tristar_idbus_fields *f) ++{ ++ f->accx = id[0] >> 6; ++ f->db = (id[0] >> 5) & 1; ++ f->usb = (id[0] >> 3) & 3; ++ f->mb = (id[0] >> 2) & 1; ++ f->uart = id[0] & 3; ++ f->pwr = id[1] >> 4; ++ f->hv = (id[1] >> 2) & 3; ++ f->ai = (id[1] >> 1) & 1; ++ f->au = id[1] & 1; ++ f->pu = id[2] >> 7; ++ f->bp = (id[2] >> 6) & 1; ++ f->cc = (id[2] >> 3) & 1; ++ f->bd = id[2] & 1; ++ f->po = id[3] >> 7; ++ f->hs = (id[3] >> 4) & 7; ++ f->hpm = id[3] & 7; ++} ++ ++/* iOS's own log order and names */ ++static int tristar_idbus_format_fields(const u8 *id, char *buf, size_t size) ++{ ++ struct tristar_idbus_fields f; ++ ++ tristar_idbus_decode(id, &f); ++ return snprintf(buf, size, ++ "USB=%x ACCx=%x DB=%x MB=%x UART=%x PWR=%x HV=%x AI=%x AU=%x PU=%x BP=%x PO=%x CC=%x BD=%x HS=%x HPM=%x", ++ f.usb, f.accx, f.db, f.mb, f.uart, f.pwr, f.hv, f.ai, f.au, ++ f.pu, f.bp, f.po, f.cc, f.bd, f.hs, f.hpm); ++} ++ ++static int tristar_idbus_format_hex(const u8 *data, unsigned int len, char *buf, size_t size) ++{ ++ unsigned int i; ++ int n = 0; ++ ++ if (size) ++ buf[0] = '\0'; ++ for (i = 0; i < len && (size_t)n + 3 < size; i++) ++ n += snprintf(buf + n, size - n, i ? " %02x" : "%02x", data[i]); ++ return n; ++} ++ ++static void tristar_idbus_log_status(struct tristar_idbus *ib, const char *when, int val) ++{ ++ ib->ops->log(ib->ctx, "0x0c %s: 0x%02x (VBUS %s, bit7 %u, orientation bit6 %u)", when, val, ++ (val & 3) == 3 ? "present" : "absent", (val >> 7) & 1, (val >> 6) & 1); ++} ++ ++/* ++ * Register access. The first I/O error is kept in ib->io_error and refuses ++ * every later access of the run, so nothing is written after a failure. ++ */ ++static int tristar_idbus_fail(struct tristar_idbus *ib, int err) ++{ ++ if (!ib->io_error) ++ ib->io_error = err; ++ return err; ++} ++ ++static int tristar_idbus_read(struct tristar_idbus *ib, u8 reg) ++{ ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ ret = ib->ops->read(ib->ctx, reg); ++ if (ret < 0) { ++ ib->ops->log(ib->ctx, "read 0x%02x failed: %d", reg, ret); ++ return tristar_idbus_fail(ib, ret); ++ } ++ if (reg == TRISTAR_IDBUS_REG_CTRL) ++ ib->last_ctrl = ret; ++ else if (reg == TRISTAR_IDBUS_REG_FAULT) ++ ib->last_fault = ret; ++ return ret; ++} ++ ++static int tristar_idbus_write(struct tristar_idbus *ib, u8 reg, u8 val) ++{ ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ ret = ib->ops->write(ib->ctx, reg, val); ++ if (ret) { ++ ib->ops->log(ib->ctx, "write 0x%02x = 0x%02x failed: %d", reg, val, ret); ++ return tristar_idbus_fail(ib, ret); ++ } ++ return 0; ++} ++ ++static int tristar_idbus_block_read(struct tristar_idbus *ib, u8 reg, u8 *buf, unsigned int len) ++{ ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ ret = ib->ops->block_read(ib->ctx, reg, buf, len); ++ if (ret) { ++ ib->ops->log(ib->ctx, "read of %u bytes at 0x%02x failed: %d", len, reg, ret); ++ return tristar_idbus_fail(ib, ret); ++ } ++ return 0; ++} ++ ++static int tristar_idbus_block_write(struct tristar_idbus *ib, u8 reg, const u8 *buf, ++ unsigned int len) ++{ ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ ret = ib->ops->block_write(ib->ctx, reg, buf, len); ++ if (ret) { ++ ib->ops->log(ib->ctx, "write of %u bytes at 0x%02x failed: %d", len, reg, ret); ++ return tristar_idbus_fail(ib, ret); ++ } ++ return 0; ++} ++ ++/* ++ * Read-modify-write; returns the value read back afterwards. *old gets the ++ * value the write was computed from. ++ */ ++static int tristar_idbus_update_logged(struct tristar_idbus *ib, u8 reg, u8 clear, u8 set, ++ u8 *old) ++{ ++ int before, after; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ before = ib->ops->update(ib->ctx, reg, clear, set); ++ if (before < 0) { ++ ib->ops->log(ib->ctx, "update 0x%02x (clear 0x%02x, set 0x%02x) failed: %d", ++ reg, clear, set, before); ++ return tristar_idbus_fail(ib, before); ++ } ++ *old = before; ++ after = tristar_idbus_read(ib, reg); ++ if (after < 0) ++ return after; ++ ib->ops->log(ib->ctx, "write 0x%02x: before 0x%02x, wrote 0x%02x, after 0x%02x", ++ reg, before, (u8)((before & ~clear) | set), after); ++ return after; ++} ++ ++static void tristar_idbus_log_fault_names(struct tristar_idbus *ib, const char *what, u8 fault) ++{ ++ ib->ops->log(ib->ctx, "pin fault 0x%02x %s:%s%s%s%s", fault, what, ++ fault & 0x01 ? " ACC1" : "", fault & 0x08 ? " ACC2" : "", ++ fault & 0x02 ? " DP1/DN1" : "", fault & 0x04 ? " DP2/DN2" : ""); ++} ++ ++/* 0x0d bit 7: report, then acknowledge by echoing exactly the value read. */ ++static int tristar_idbus_ack_fault(struct tristar_idbus *ib) ++{ ++ u8 fault; ++ int ret = tristar_idbus_update_logged(ib, TRISTAR_IDBUS_REG_FAULT, 0, 0, &fault); ++ ++ if (ret < 0) ++ return ret; ++ ib->faults++; ++ tristar_idbus_log_fault_names(ib, "acknowledged", fault); ++ return 0; ++} ++ ++static int tristar_idbus_read_event(struct tristar_idbus *ib) ++{ ++ int ev = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_EVENT); ++ ++ if (ev < 0) ++ return ev; ++ ib->last_event = ev; ++ ib->events |= ev; ++ return ev; ++} ++ ++static int tristar_idbus_check_revision(struct tristar_idbus *ib) ++{ ++ int ret = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_REVISION); ++ ++ if (ret < 0) ++ return ret; ++ if (ret != TRISTAR_IDBUS_REVISION_J71T) { ++ ib->ops->log(ib->ctx, "revision 0x%02x is not CBTL1610 0x8a; nothing written", ret); ++ return -ENODEV; ++ } ++ return 0; ++} ++ ++/* ++ * getIdBusData: the response sits forward in the RX window at 0x20 until ++ * the next transfer replaces it. Read rx_len + 1 bytes (CRC last) and, when ++ * asked, 0x0c for the orientation. Never writes. Returns 0 or -errno; the ++ * header/CRC verdict goes to *verdict. ++ */ ++static int tristar_idbus_read_rx(struct tristar_idbus *ib, const char *what, u8 *frame, ++ unsigned int rx_len, u8 type, int *orientation, int *verdict) ++{ ++ char hex[3 * TRISTAR_IDBUS_MAX_FRAME + 1]; ++ int ret, val; ++ ++ ret = tristar_idbus_block_read(ib, TRISTAR_IDBUS_REG_RX, frame, rx_len + 1); ++ if (ret) ++ return ret; ++ *verdict = tristar_idbus_check_frame(frame, rx_len, type); ++ tristar_idbus_format_hex(frame, rx_len + 1, hex, sizeof(hex)); ++ if (!orientation) { ++ ib->ops->log(ib->ctx, "%s RX 0x20: [%s] %s", what, hex, ++ tristar_idbus_verdict(*verdict)); ++ return 0; ++ } ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val < 0) ++ return val; ++ *orientation = val; ++ ib->ops->log(ib->ctx, "%s RX 0x20: [%s] %s; 0x0c = 0x%02x (orientation bit6 %u)", what, hex, ++ tristar_idbus_verdict(*verdict), val, (val >> 6) & 1); ++ return 0; ++} ++ ++/* ++ * One command (AppleCBTL1610 sendIDBusCommand, the J71t dock path): ++ * prepare 0x60 = 0x01, write the request reversed plus the RX length byte ++ * (rx_len - 1) << 1 so that it ends at 0x60, read the window back and ++ * compare (no GO on a mismatch), then GO at 0x61. Nothing else touches the ++ * bus in between. Poll 0x0d every 10 ms for at most 100 ms. Completion is ++ * the transfer-done code ((0x0d & 0x38) == 0x10) or, for GetID only, the ++ * EVENT_DIGITAL_ID bit (hardware: 0x18), whose iOS handler reads the same ++ * frame. The response (rx_len + CRC byte) is read back and kept even when ++ * its header or CRC is wrong. *waited accumulates the poll sleeps. ++ */ ++static int tristar_idbus_transfer(struct tristar_idbus *ib, const u8 *tx, unsigned int tx_len, ++ unsigned int rx_len, u8 rx_type, bool digital_id, ++ u8 *frame, unsigned int *frame_len, int *orientation, ++ unsigned int *waited) ++{ ++ u8 win[TRISTAR_IDBUS_MAX_TX + 1], back[TRISTAR_IDBUS_MAX_TX + 1]; ++ char req[3 * TRISTAR_IDBUS_MAX_TX + 1], hex[3 * TRISTAR_IDBUS_MAX_FRAME + 1]; ++ u8 base = TRISTAR_IDBUS_REG_TX_END - tx_len, go = ((tx_len - 1) << 1) | 1; ++ unsigned int i, step; ++ int ev, ret, verdict; ++ ++ *frame_len = 0; ++ if (!tx_len || tx_len > TRISTAR_IDBUS_MAX_TX || !rx_len || ++ rx_len >= TRISTAR_IDBUS_MAX_FRAME) ++ return -EINVAL; ++ for (i = 0; i < tx_len; i++) ++ win[i] = tx[tx_len - 1 - i]; ++ win[tx_len] = (rx_len - 1) << 1; ++ tristar_idbus_format_hex(tx, tx_len, req, sizeof(req)); ++ tristar_idbus_format_hex(win, tx_len + 1, hex, sizeof(hex)); ++ ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_TX_END, TRISTAR_IDBUS_TX_PREPARE); ++ if (ret) ++ return ret; ++ ib->ops->log(ib->ctx, "write 0x60 = 0x01 (prepare)"); ++ ret = tristar_idbus_block_write(ib, base, win, tx_len + 1); ++ if (ret) ++ return ret; ++ ib->ops->log(ib->ctx, ++ "write TX 0x%02x: [%s] (request [%s] reversed, RX length 0x%02x; Tristar appends CRC 0x%02x)", ++ base, hex, req, win[tx_len], tristar_idbus_crc8(tx, tx_len)); ++ ret = tristar_idbus_block_read(ib, base, back, tx_len + 1); ++ if (ret) ++ return ret; ++ if (memcmp(back, win, tx_len + 1)) { ++ tristar_idbus_format_hex(back, tx_len + 1, hex, sizeof(hex)); ++ ib->ops->log(ib->ctx, "readback mismatch at 0x%02x: [%s]; no GO", base, hex); ++ return -EREMOTEIO; ++ } ++ ib->ops->log(ib->ctx, "readback TX 0x%02x matches", base); ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_GO, go); ++ if (ret) ++ return ret; ++ ib->ops->log(ib->ctx, "write 0x61 = 0x%02x (GO)", go); ++ ++ for (step = 0; ; step++) { ++ ev = tristar_idbus_read_event(ib); ++ if (ev < 0) ++ return ev; ++ if (ev) ++ ib->ops->log(ib->ctx, "poll %u ms: 0x0d = 0x%02x", *waited, ev); ++ if (ev & TRISTAR_IDBUS_EV_FAULT) { ++ ret = tristar_idbus_ack_fault(ib); ++ if (ret) ++ return ret; ++ } ++ if ((digital_id && (ev & TRISTAR_IDBUS_EV_DIGITAL_ID)) || ++ (ev & TRISTAR_IDBUS_EV_DONE_MASK) == TRISTAR_IDBUS_EV_DONE) ++ break; ++ if (ev & TRISTAR_IDBUS_EV_ERROR) { ++ ib->ops->log(ib->ctx, "transfer error event; no retry"); ++ return -EIO; ++ } ++ if (ev & TRISTAR_IDBUS_EV_RESET) { ++ ib->ops->log(ib->ctx, "ID-bus reset/detach event during transfer"); ++ return -ECONNRESET; ++ } ++ if (ev & TRISTAR_IDBUS_EV_TIMEOUT) { ++ ib->ops->log(ib->ctx, "Tristar timeout event after %u ms", *waited); ++ return -ETIMEDOUT; ++ } ++ if (step == TRISTAR_IDBUS_POLL_STEPS) { ++ ib->ops->log(ib->ctx, "no completion after %u ms (last 0x0d 0x%02x)", ++ *waited, ev); ++ return -ETIMEDOUT; ++ } ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_POLL_MS); ++ *waited += TRISTAR_IDBUS_POLL_MS; ++ } ++ ++ ret = tristar_idbus_read_rx(ib, "read", frame, rx_len, rx_type, orientation, &verdict); ++ if (ret) ++ return ret; ++ *frame_len = rx_len + 1; ++ return verdict; ++} ++ ++/* ++ * Reorient (0x05 |= 0x02, VBUS-sink bit kept; the bit self-clears), then ++ * wait 20 ms and poll 0x0d for up to 30 ms more for the exchange it re-arms. ++ * Only 0x05 and, on a pin fault, 0x0a are written. ++ */ ++static int tristar_idbus_reorient(struct tristar_idbus *ib, u8 *events, unsigned int *waited) ++{ ++ unsigned int step, elapsed = TRISTAR_IDBUS_SETTLE_MS; ++ int ev, ret; ++ u8 old; ++ ++ ret = tristar_idbus_update_logged(ib, TRISTAR_IDBUS_REG_CTRL, 0, ++ TRISTAR_IDBUS_CTRL_REORIENT, &old); ++ if (ret < 0) ++ return ret; ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_SETTLE_MS); ++ for (step = 0; ; step++) { ++ ev = tristar_idbus_read_event(ib); ++ if (ev < 0) ++ return ev; ++ *events |= ev; ++ if (ev) ++ ib->ops->log(ib->ctx, "%u ms after reorient: 0x0d = 0x%02x", elapsed, ev); ++ if (ev & TRISTAR_IDBUS_EV_FAULT) { ++ ret = tristar_idbus_ack_fault(ib); ++ if (ret) ++ return ret; ++ } ++ if (ev & (TRISTAR_IDBUS_EV_DIGITAL_ID | TRISTAR_IDBUS_EV_DONE)) ++ break; ++ if (step == TRISTAR_IDBUS_REORIENT_STEPS) { ++ ib->ops->log(ib->ctx, "no completion event %u ms after reorient", elapsed); ++ break; ++ } ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_POLL_MS); ++ elapsed += TRISTAR_IDBUS_POLL_MS; ++ } ++ *waited += elapsed; ++ return 0; ++} ++ ++/* ++ * After the reorient, read what the re-armed exchange left in the RX window ++ * before writing anything. Accept it only if it is valid and fresh: a ++ * completion event since the reorient, or bytes that differ from the frame ++ * retained before this run (that one may be an older accessory's). Returns ++ * 0 when accepted, 1 to send GetID again, or -errno. ++ */ ++static int tristar_idbus_after_reorient(struct tristar_idbus *ib, ++ struct tristar_idbus_id_result *res, unsigned int *waited) ++{ ++ int ret, verdict, orientation; ++ bool event; ++ ++ ret = tristar_idbus_reorient(ib, &res->reorient_events, waited); ++ if (ret) ++ return ret; ++ ret = tristar_idbus_read_rx(ib, "after reorient", res->reorient_frame, 7, ++ TRISTAR_IDBUS_ID, &orientation, &verdict); ++ if (ret) ++ return ret; ++ res->reorient_read = true; ++ event = res->reorient_events & (TRISTAR_IDBUS_EV_DIGITAL_ID | TRISTAR_IDBUS_EV_DONE); ++ if (!verdict && !event && ++ !memcmp(res->reorient_frame, res->retained, sizeof(res->retained))) ++ verdict = -ESTALE; ++ res->reorient_status = verdict; ++ if (verdict) { ++ ib->ops->log(ib->ctx, "frame after reorient not accepted (%s); sending GetID again", ++ tristar_idbus_verdict(verdict)); ++ return 1; ++ } ++ memcpy(res->frame, res->reorient_frame, sizeof(res->frame)); ++ res->frame_len = sizeof(res->frame); ++ res->frame_status = 0; ++ res->orientation = orientation; ++ res->autonomous = true; ++ ib->ops->log(ib->ctx, "accepted the frame from the reorient-armed exchange (%s)", ++ event ? "completion event" : "differs from the retained frame"); ++ return 0; ++} ++ ++/* ++ * The manual GetID sequence of ds-lightning-host-idbus.md 9.2. Only the ++ * reorient retry is automatic, once, and only after a timeout with VBUS. ++ */ ++static int tristar_idbus_identify(struct tristar_idbus *ib, unsigned int flags, ++ struct tristar_idbus_id_result *res) ++{ ++ char text[160]; ++ u8 tx[3] = { TRISTAR_IDBUS_GET_ID, TRISTAR_IDBUS_HOSTID >> 8, TRISTAR_IDBUS_HOSTID & 0xff }; ++ unsigned int i, waited; ++ int ret, val, orientation; ++ u8 old; ++ ++ memset(res, 0, sizeof(*res)); ++ ib->io_error = 0; ++ res->flags = flags; ++ if (flags & TRISTAR_IDBUS_SWAP_HOSTID) { ++ tx[1] = TRISTAR_IDBUS_HOSTID & 0xff; ++ tx[2] = TRISTAR_IDBUS_HOSTID >> 8; ++ } ++ ib->ops->log(ib->ctx, "identify: HOSTID bytes %02x %02x%s", tx[1], tx[2], ++ flags & TRISTAR_IDBUS_NO_REORIENT ? ", reorient disabled" : ""); ++ ret = tristar_idbus_check_revision(ib); ++ if (ret) ++ goto out; ++ ++ /* 0. Baseline, then the RX window's retained frame (read only). */ ++ for (i = 0; i < TRISTAR_IDBUS_NBASE; i++) { ++ u8 reg = tristar_idbus_baseline_regs[i]; ++ ++ val = reg == TRISTAR_IDBUS_REG_EVENT ? tristar_idbus_read_event(ib) : ++ tristar_idbus_read(ib, reg); ++ if (val < 0) { ++ ret = val; ++ goto out; ++ } ++ res->baseline[i] = val; ++ ib->ops->log(ib->ctx, "baseline 0x%02x = 0x%02x", reg, val); ++ } ++ ret = tristar_idbus_block_read(ib, TRISTAR_IDBUS_REG_RX, res->retained, ++ TRISTAR_IDBUS_MAX_FRAME); ++ if (ret) ++ goto out; ++ tristar_idbus_format_hex(res->retained, TRISTAR_IDBUS_MAX_FRAME, text, sizeof(text)); ++ res->retained_ok = !tristar_idbus_check_frame(res->retained, 7, TRISTAR_IDBUS_ID); ++ ib->ops->log(ib->ctx, "baseline RX 0x20 = [%s]%s", text, ++ res->retained_ok ? " (retained valid ID frame; may be stale)" : ""); ++ tristar_idbus_log_status(ib, "baseline", res->baseline[0]); ++ if (ib->last_event & TRISTAR_IDBUS_EV_FAULT) { ++ ret = tristar_idbus_ack_fault(ib); ++ if (ret) ++ goto out; ++ } ++ ++ /* 1. Release a latched ID-bus reset hold. */ ++ if (ib->last_ctrl & TRISTAR_IDBUS_CTRL_RESET) { ++ val = tristar_idbus_update_logged(ib, TRISTAR_IDBUS_REG_CTRL, ++ TRISTAR_IDBUS_CTRL_RESET, 0, &old); ++ if (val < 0) { ++ ret = val; ++ goto out; ++ } ++ res->released_reset = true; ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_SETTLE_MS); ++ if (val & TRISTAR_IDBUS_CTRL_RESET) { ++ ib->ops->log(ib->ctx, "ID-bus reset hold still set; giving up"); ++ ret = -EBUSY; ++ goto out; ++ } ++ } ++ ++ /* 2./3. GetID; after a timeout with VBUS, one reorient, then at most once more. */ ++ for (;;) { ++ waited = 0; ++ res->attempts++; ++ ret = tristar_idbus_transfer(ib, tx, sizeof(tx), 7, TRISTAR_IDBUS_ID, true, ++ res->frame, &res->frame_len, &orientation, &waited); ++ res->frame_status = ret; ++ if (!ret) ++ res->orientation = orientation; ++ if (ret != -ETIMEDOUT || ib->io_error || res->reoriented || ++ (flags & TRISTAR_IDBUS_NO_REORIENT)) ++ break; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val < 0) { ++ ret = val; ++ break; ++ } ++ if ((val & 3) != 3) { ++ ib->ops->log(ib->ctx, "timeout without VBUS (0x0c = 0x%02x): no reorient", ++ val); ++ break; ++ } ++ res->reoriented = true; ++ ret = tristar_idbus_after_reorient(ib, res, &waited); ++ if (ret <= 0) ++ break; ++ if (waited < TRISTAR_IDBUS_CMDID_GAP_MS) ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_CMDID_GAP_MS - waited); ++ } ++ if (!ret) { ++ memcpy(res->id, res->frame + 1, TRISTAR_IDBUS_ID_LEN); ++ res->id_valid = true; ++ tristar_idbus_format_hex(res->id, TRISTAR_IDBUS_ID_LEN, text, sizeof(text)); ++ ib->ops->log(ib->ctx, "digital ID %s (%s)", text, ++ res->autonomous ? "reorient-armed exchange" : "GetID command"); ++ tristar_idbus_format_fields(res->id, text, sizeof(text)); ++ ib->ops->log(ib->ctx, "%s", text); ++ } ++out: ++ res->status = ret; ++ ib->ops->log(ib->ctx, "identify %s (%d) after %u GetID command(s)%s", ++ ret ? "failed" : "ok", ret, res->attempts, ++ res->reoriented ? ", reoriented once" : ""); ++ return ret; ++} ++ ++/* ++ * Optional power handshake (ds-lightning-host-idbus.md 6): VBUS-gated 0x72 ++ * GetState, expecting 0x73 + 4 bytes, then one 3-byte 0x70 frame ++ * {0x70, state[1] & ~8, state[2]} expecting 0x71. Which state bytes iOS ++ * echoes is inferred. Same TX window as GetID; no retries. ++ */ ++static int tristar_idbus_power_handshake(struct tristar_idbus *ib, ++ struct tristar_idbus_ph_result *res) ++{ ++ const u8 get_state = TRISTAR_IDBUS_GET_STATE; ++ unsigned int len, waited = 0; ++ int ret, val; ++ ++ memset(res, 0, sizeof(*res)); ++ ib->io_error = 0; ++ ret = tristar_idbus_check_revision(ib); ++ if (ret) ++ goto out; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val < 0) { ++ ret = val; ++ goto out; ++ } ++ if ((val & 3) != 3) { ++ ib->ops->log(ib->ctx, ++ "no VBUS detected when starting powerhandshake (0x0c = 0x%02x)", val); ++ ret = -ENODEV; ++ goto out; ++ } ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_CTRL); ++ if (val < 0) { ++ ret = val; ++ goto out; ++ } ++ if (val & TRISTAR_IDBUS_CTRL_RESET) { ++ ib->ops->log(ib->ctx, "ID-bus in reset (0x05 = 0x%02x); run identify first", val); ++ ret = -EBUSY; ++ goto out; ++ } ++ ret = tristar_idbus_transfer(ib, &get_state, 1, 5, TRISTAR_IDBUS_STATE, false, ++ res->state, &len, NULL, &waited); ++ if (ret) ++ goto out; ++ res->state_ok = true; ++ res->request[0] = TRISTAR_IDBUS_POWER; ++ res->request[1] = res->state[1] & ~TRISTAR_IDBUS_POWER_RETRY; ++ res->request[2] = res->state[2]; ++ waited = 0; ++ ret = tristar_idbus_transfer(ib, res->request, sizeof(res->request), 1, ++ TRISTAR_IDBUS_POWER_ACK, false, res->ack, &len, NULL, &waited); ++ res->ack_ok = !ret; ++out: ++ res->status = ret; ++ ib->ops->log(ib->ctx, "power handshake %s (%d)", ret ? "failed" : "ok", ret); ++ return ret; ++} ++ ++/* ++ * ID-bus reset pulse (AppleCBTL1610 5ff5930): 0x05 |= 0x08, 500 ms, then ++ * clear bit 3. Once the hold may have been set, the release is sent even ++ * after an I/O error, with two retries: a held reset blocks every ID-bus ++ * operation and must not outlive the pulse. ++ */ ++static int tristar_idbus_reset_pulse(struct tristar_idbus *ib) ++{ ++ int ret = 0, old, release = 0, err = 0, val, i; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ old = ib->ops->update(ib->ctx, TRISTAR_IDBUS_REG_CTRL, 0, TRISTAR_IDBUS_CTRL_RESET); ++ if (old < 0) { ++ ib->ops->log(ib->ctx, "update 0x05 (set 0x08) failed: %d", old); ++ ret = tristar_idbus_fail(ib, old); ++ } else { ++ ib->ops->log(ib->ctx, "write 0x05: before 0x%02x, wrote 0x%02x (reset hold)", old, ++ old | TRISTAR_IDBUS_CTRL_RESET); ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_RESET_HOLD_MS); ++ } ++ for (i = 0; i < 3; i++) { ++ if (i) ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_SETTLE_MS); ++ release = ib->ops->update(ib->ctx, TRISTAR_IDBUS_REG_CTRL, ++ TRISTAR_IDBUS_CTRL_RESET, 0); ++ if (release >= 0) ++ break; ++ ib->ops->log(ib->ctx, "ID-bus reset release failed: %d", release); ++ if (!err) ++ err = release; ++ } ++ if (release < 0) { ++ ib->ops->log(ib->ctx, "ID-bus reset hold may still be set; run idbus_reset again"); ++ return tristar_idbus_fail(ib, ret ?: release); ++ } ++ ib->ops->log(ib->ctx, "write 0x05: before 0x%02x, wrote 0x%02x (release)", release, ++ release & ~TRISTAR_IDBUS_CTRL_RESET); ++ if (ret) ++ return ret; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_CTRL); ++ if (val < 0) ++ return val; ++ if (val & TRISTAR_IDBUS_CTRL_RESET) { ++ ib->ops->log(ib->ctx, "0x05 = 0x%02x: ID-bus reset hold still set", val); ++ return -EBUSY; ++ } ++ ib->ops->log(ib->ctx, "0x05 after release: 0x%02x", val); ++ ib->ops->sleep_ms(ib->ctx, TRISTAR_IDBUS_SETTLE_MS); ++ /* Released on a retry: still report the I/O error. */ ++ return err ? tristar_idbus_fail(ib, err) : 0; ++} ++ ++/* Read 0x0a; only if a pin fault is latched, write back exactly that value. */ ++static int tristar_idbus_fault_ack(struct tristar_idbus *ib, ++ struct tristar_idbus_recover_result *res) ++{ ++ int val, ret; ++ ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_FAULT); ++ if (val < 0) ++ return val; ++ res->fault = val; ++ if (!(val & TRISTAR_IDBUS_FAULT_PINS)) { ++ ib->ops->log(ib->ctx, "0x0a = 0x%02x: no pin fault latched; nothing written", val); ++ return 0; ++ } ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_FAULT, val); ++ if (ret) ++ return ret; ++ ib->faults++; ++ tristar_idbus_log_fault_names(ib, "written back", val); ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_FAULT); ++ if (val < 0) ++ return val; ++ ib->ops->log(ib->ctx, "0x0a after: 0x%02x", val); ++ return 0; ++} ++ ++/* Reorient again and consume the RX window read only (9.6 step 1). */ ++static int tristar_idbus_recover_reorient(struct tristar_idbus *ib, ++ struct tristar_idbus_recover_result *res) ++{ ++ char hex[3 * TRISTAR_IDBUS_MAX_FRAME + 1], text[160]; ++ unsigned int waited = 0; ++ int ret, val, orientation; ++ ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_CTRL); ++ if (val < 0) ++ return val; ++ if (val & TRISTAR_IDBUS_CTRL_RESET) { ++ ib->ops->log(ib->ctx, "0x05 = 0x%02x: ID-bus reset hold set; use idbus_reset", val); ++ return -EBUSY; ++ } ++ ret = tristar_idbus_block_read(ib, TRISTAR_IDBUS_REG_RX, res->before, sizeof(res->before)); ++ if (ret) ++ return ret; ++ tristar_idbus_format_hex(res->before, sizeof(res->before), hex, sizeof(hex)); ++ ib->ops->log(ib->ctx, "before RX 0x20: [%s]", hex); ++ ret = tristar_idbus_reorient(ib, &res->events, &waited); ++ if (ret) ++ return ret; ++ ret = tristar_idbus_read_rx(ib, "after reorient", res->frame, 7, TRISTAR_IDBUS_ID, ++ &orientation, &res->frame_status); ++ if (ret) ++ return ret; ++ res->frame_read = true; ++ ib->ops->log(ib->ctx, "RX %s; completion event %s", ++ memcmp(res->frame, res->before, sizeof(res->frame)) ? "changed" : "unchanged", ++ res->events & (TRISTAR_IDBUS_EV_DIGITAL_ID | TRISTAR_IDBUS_EV_DONE) ? ++ "seen" : "not seen"); ++ if (!res->frame_status) { ++ tristar_idbus_format_fields(res->frame + 1, text, sizeof(text)); ++ ib->ops->log(ib->ctx, "%s", text); ++ } ++ return 0; ++} ++ ++/* The volatile link recovery levers of ds-lightning-host-idbus.md 9.6. */ ++static int tristar_idbus_recover(struct tristar_idbus *ib, unsigned int action, ++ struct tristar_idbus_recover_result *res) ++{ ++ int ret, val; ++ ++ memset(res, 0, sizeof(*res)); ++ ib->io_error = 0; ++ res->action = action; ++ res->status_before = -1; ++ res->status_after = -1; ++ ib->ops->log(ib->ctx, "recover: %s", tristar_idbus_recover_name(action)); ++ if (action > TRISTAR_IDBUS_RECOVER_FAULT_ACK) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ret = tristar_idbus_check_revision(ib); ++ if (ret) ++ goto out; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val < 0) { ++ ret = val; ++ goto out; ++ } ++ res->status_before = val; ++ tristar_idbus_log_status(ib, "before", val); ++ if (action == TRISTAR_IDBUS_RECOVER_REORIENT) ++ ret = tristar_idbus_recover_reorient(ib, res); ++ else if (action == TRISTAR_IDBUS_RECOVER_RESET) ++ ret = tristar_idbus_reset_pulse(ib); ++ else ++ ret = tristar_idbus_fault_ack(ib, res); ++ if (!ib->io_error) { ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val >= 0) { ++ res->status_after = val; ++ tristar_idbus_log_status(ib, "after", val); ++ } else if (!ret) { ++ ret = val; ++ } ++ } ++out: ++ res->status = ret; ++ ib->ops->log(ib->ctx, "recover %s %s (%d)", tristar_idbus_recover_name(action), ++ ret ? "failed" : "done", ret); ++ return ret; ++} ++ ++#endif /* APPLE_TRISTAR_IDBUS_H */ +diff --git a/drivers/power/supply/apple_tristar_power.c b/drivers/power/supply/apple_tristar_power.c +new file mode 100644 +index 0000000000000000000000000000000000000000..2f58419da71a6e59b94d496b5d3138c5285e9261 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_power.c +@@ -0,0 +1,1648 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* J71t CBTL1610 source detection, reconstructed from the iOS kernelcache. */ ++#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 "apple_tristar_idbus.h" ++#include "apple_tristar_auto.h" ++#include "apple_tristar_source.h" ++ ++#define TRISTAR_VBUS 0x0c ++#define TRISTAR_REVISION 0x0f ++#define TRISTAR_J71T_REVISION 0x8a ++#define TRISTAR_DX_CONTROL 0x01 ++#define TRISTAR_DCP_CONTROL 0x03 ++#define D2365_ADC_DONE 0x156 ++#define D2365_ADC_START 0x4000 ++#define D2365_ADC_RESULT 0x4001 ++ ++static bool allow_power_handshake; ++module_param(allow_power_handshake, bool, 0644); ++MODULE_PARM_DESC(allow_power_handshake, ++ "Permit the debugfs-triggered 0x72/0x70 ID-bus power handshake (default: off)"); ++ ++static bool auto_host = true; ++module_param(auto_host, bool, 0644); ++MODULE_PARM_DESC(auto_host, ++ "Identify Lightning accessories and make the port a USB host for the camera adapter, as iOS does (default: on)"); ++ ++#define APPLE_TRISTAR_FLOOR_UA 500000 ++#define APPLE_TRISTAR_ADAPTER_MAX_UA 2100000 ++ ++static unsigned int auto_input_ua = APPLE_TRISTAR_ADAPTER_MAX_UA; ++module_param(auto_input_ua, uint, 0644); ++MODULE_PARM_DESC(auto_input_ua, ++ "Input current offered by the camera adapter after its power handshake (default and maximum: 2100000, iOS's limit)"); ++ ++/* Last manual ID-bus runs, published read-only in debugfs. */ ++struct apple_tristar_idbus { ++ struct tristar_idbus ib; ++ struct tristar_idbus_id_result id; ++ struct tristar_idbus_ph_result ph; ++ struct tristar_idbus_recover_result rc; ++ unsigned long id_at; ++ bool id_run; ++ bool ph_run; ++ bool rc_run; ++ /* Automatic GetID: transcript only, no kernel log lines */ ++ bool quiet; ++ char *log; ++ size_t log_len; ++ size_t log_size; ++ char id_log[4096]; ++ char ph_log[2048]; ++ char rc_log[2048]; ++}; ++ ++struct apple_tristar { ++ struct i2c_client *client; ++ struct power_supply *psy; ++ struct regmap *pmic; ++ struct device_node *usb_node; ++ struct usb_role_switch *role_sw; ++ struct apple_tristar_idbus *idbus; ++ struct dentry *debugfs; ++ struct notifier_block usb_notifier; ++ atomic_t reset_generation; ++ int seen_generation; ++ struct delayed_work work; ++ /* Serializes register reads and protects the cached source state. */ ++ struct mutex lock; ++ /* Lightning accessory policy, its last 0x0c read and its log */ ++ struct tristar_auto autop; ++ int status; ++ char auto_log[2048]; ++ size_t auto_log_len; ++ int online; ++ int error; ++ int current_ua; ++ int usb_type; ++ int brick_ua; ++ int analog_type; ++ unsigned int attach_id; ++ bool source_present; ++ bool computer; ++ bool detach_role; ++ unsigned long classify_after; ++ u8 saved_route; ++ bool restore_pending; ++ bool dcp_pending; ++ bool brick_sampled; ++ bool adc_poisoned; ++ bool stopping; ++ bool notifier_registered; ++ bool sleeping; ++}; ++ ++static int apple_tristar_read(struct i2c_client *client, u8 reg) ++{ ++ union i2c_smbus_data data; ++ int ret; ++ ++ /* iOS op=1: selector + STOP, then a separate read START. */ ++ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, ++ I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE, NULL); ++ if (!ret) ++ ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, ++ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data); ++ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ return ret ?: data.byte; ++} ++ ++static int apple_tristar_write_reg_checked(struct apple_tristar *ts, u8 reg, u8 value) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(ts->client, reg, value); ++ if (ret) ++ return ret; ++ ret = apple_tristar_read(ts->client, reg); ++ return ret < 0 ? ret : ret == value ? 0 : -EIO; ++} ++ ++static int apple_tristar_write_checked(struct apple_tristar *ts, u8 value) ++{ ++ return apple_tristar_write_reg_checked(ts, TRISTAR_DX_CONTROL, value); ++} ++ ++static int apple_tristar_restore_route(struct apple_tristar *ts) ++{ ++ int ret; ++ ++ if (ts->dcp_pending) { ++ ret = apple_tristar_write_reg_checked(ts, TRISTAR_DCP_CONTROL, 0); ++ if (ret) ++ return ret; ++ ts->dcp_pending = false; ++ } ++ if (!ts->restore_pending) ++ return 0; ++ ret = apple_tristar_write_checked(ts, ts->saved_route); ++ if (!ret) ++ ts->restore_pending = false; ++ return ret; ++} ++ ++static int apple_tristar_pmic_read(struct apple_tristar *ts, unsigned int reg) ++{ ++ unsigned int value; ++ int ret = regmap_read(ts->pmic, reg, &value); ++ ++ return ret ?: value; ++} ++ ++/* Exclusive ADC use. Never re-trigger after uncertain completion. */ ++static int apple_tristar_adc_mv(struct apple_tristar *ts) ++{ ++ unsigned int raw, q16; ++ u8 result[2]; ++ int status, ret; ++ ++ if (ts->adc_poisoned) ++ return -EIO; ++ /* Observe the completion latch LOW before starting a new sample. */ ++ ret = read_poll_timeout(apple_tristar_pmic_read, status, ++ status < 0 || !(status & 1), 1000, 20000, ++ false, ts, D2365_ADC_DONE); ++ if (ret || status < 0) ++ return ret ?: status; ++ ret = regmap_write(ts->pmic, D2365_ADC_START, 1); ++ if (ret) ++ goto poison; ++ ret = read_poll_timeout(apple_tristar_pmic_read, status, ++ status < 0 || (status & 1), 1000, 1000000, ++ false, ts, D2365_ADC_DONE); ++ if (ret || status < 0) { ++ ret = ret ?: status; ++ goto poison; ++ } ++ ret = regmap_bulk_read(ts->pmic, D2365_ADC_RESULT, result, sizeof(result)); ++ if (ret) ++ return ret; ++ if (result[0] & 0x80) ++ return -ENODATA; ++ raw = (result[0] & 0x0f) | (result[1] << 4); ++ /* Exact positive Q16-volts conversion, then the source's integer mV. */ ++ q16 = div_u64((40ULL << 16) + raw * 5000ULL * 16, 1000); ++ return (q16 * 1000U) >> 16; ++poison: ++ /* A late completion must never be mistaken for the next pin's sample. */ ++ ts->adc_poisoned = true; ++ return ret; ++} ++ ++static bool apple_tristar_sample_valid(struct apple_tristar *ts) ++{ ++ struct usb_gadget_current_budget budget; ++ ++ return !usb_gadget_get_current_budget(ts->usb_node, &budget) && ++ !budget.connected && !budget.configured && !budget.host_grant && ++ ts->seen_generation == atomic_read(&ts->reset_generation); ++} ++ ++static int apple_tristar_pin_mv(struct apple_tristar *ts, bool dm, bool dcp) ++{ ++ int ret, restore; ++ u8 route; ++ ++ if (!apple_tristar_sample_valid(ts)) ++ return -EAGAIN; ++ ret = apple_tristar_read(ts->client, TRISTAR_DX_CONTROL); ++ if (ret < 0) ++ return ret; ++ ts->saved_route = ret; ++ if ((ret & 7) == 1) ++ route = (ret & 0xf0) | (dm ? 0x0d : 0x0c); ++ else if ((ret & 0x70) == 0x10) ++ route = (ret & 0x0f) | (dm ? 0xd0 : 0xc0); ++ else ++ return -EOPNOTSUPP; ++ ts->restore_pending = true; ++ ret = apple_tristar_write_checked(ts, route); ++ if (!ret) { ++ usleep_range(5000, 6000); ++ if (dcp) { ++ /* iOS 5ff5a08: monitor 4 (D-), 5 (D+), 40 ms pulse. */ ++ u8 pulse = dm ? 9 : 10; ++ ++ if ((ts->saved_route & 7) != 1) ++ pulse += 2; ++ ts->dcp_pending = true; ++ ret = apple_tristar_write_reg_checked(ts, TRISTAR_DCP_CONTROL, pulse); ++ if (!ret) ++ msleep(40); ++ } ++ if (!ret) ++ ret = apple_tristar_sample_valid(ts) ? apple_tristar_adc_mv(ts) : -EAGAIN; ++ } ++ restore = apple_tristar_restore_route(ts); ++ if (!restore && !apple_tristar_sample_valid(ts)) ++ return -EAGAIN; ++ return restore ?: ret; ++} ++ ++static int apple_tristar_brick_current(int dp_mv, int dm_mv) ++{ ++ static const int limits[] = { 500, 1000, 1000, 2100, 2400, 2400, 1000, 1000, 1000 }; ++ int dp, dm; ++ ++ if (dp_mv < 0 || dm_mv < 0 || dp_mv > 4000 || dm_mv > 4000) ++ return -ERANGE; ++ if (dp_mv < 1500 || dm_mv < 1500) ++ return 0; ++ dp = dp_mv >= 2880 ? 2 : dp_mv >= 2200 ? 1 : 0; ++ dm = dm_mv >= 2880 ? 2 : dm_mv >= 2200 ? 1 : 0; ++ return limits[3 * dp + dm] * 1000; ++} ++ ++static void apple_tristar_classify(struct apple_tristar *ts) ++{ ++ struct usb_gadget_current_budget budget; ++ int dp, dm, ret; ++ ++ /* Caller holds the role hardware lock before ts->lock. */ ++ if (ts->computer || ts->brick_sampled || ts->adc_poisoned || !ts->online || ts->error || ++ time_before(jiffies, ts->classify_after)) ++ return; ++ ts->seen_generation = atomic_read(&ts->reset_generation); ++ ret = usb_gadget_get_current_budget(ts->usb_node, &budget); ++ /* Never divert active USB traffic to the analog monitor. */ ++ if (ret || budget.connected || budget.configured || budget.host_grant || ++ ts->seen_generation != atomic_read(&ts->reset_generation)) ++ return; ++ dp = apple_tristar_pin_mv(ts, false, false); ++ if (dp < 0) ++ goto fail; ++ ret = usb_gadget_get_current_budget(ts->usb_node, &budget); ++ if (ret || budget.connected || budget.configured || budget.host_grant || ++ ts->seen_generation != atomic_read(&ts->reset_generation)) ++ return; ++ dm = apple_tristar_pin_mv(ts, true, false); ++ if (dm < 0) ++ goto fail; ++ ret = usb_gadget_get_current_budget(ts->usb_node, &budget); ++ if (ret || budget.connected || budget.configured || budget.host_grant || ++ ts->seen_generation != atomic_read(&ts->reset_generation)) ++ return; ++ ret = apple_tristar_read(ts->client, TRISTAR_VBUS); ++ if (ret < 0 || (ret & 3) != 3 || ++ ts->seen_generation != atomic_read(&ts->reset_generation)) ++ return; ++ ret = apple_tristar_brick_current(dp, dm); ++ if (ret < 0) ++ goto fail; ++ ts->analog_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; ++ if (!ret) { ++ /* iOS 5de33e4/5de35dc, ordinary USB source (connect type 0). */ ++ dm = apple_tristar_pin_mv(ts, true, true); ++ if (dm < 0) ++ goto fail; ++ if (dm >= 326 && dm <= 899) { ++ dp = apple_tristar_pin_mv(ts, false, true); ++ if (dp < 0) ++ goto fail; ++ ts->analog_type = dp < 326 ? POWER_SUPPLY_USB_TYPE_DCP : ++ POWER_SUPPLY_USB_TYPE_CDP; ++ ret = 1500000; ++ } ++ } ++ if (!apple_tristar_sample_valid(ts)) ++ return; ++ dp = apple_tristar_read(ts->client, TRISTAR_VBUS); ++ if (dp < 0 || (dp & 3) != 3 || !apple_tristar_sample_valid(ts)) ++ return; ++ ts->brick_ua = ret; ++ ts->brick_sampled = true; ++ dev_info_ratelimited(&ts->client->dev, "analog charger type %d, budget %d mA\n", ++ ts->analog_type, ret / 1000); ++ return; ++fail: ++ ts->classify_after = jiffies + msecs_to_jiffies(1000); ++ dev_warn_ratelimited(&ts->client->dev, ++ "brick detection unavailable; no elevated analog allowance\n"); ++} ++ ++/* Never call USB role APIs while holding ts->lock: role changes unbind UDC. */ ++static int apple_tristar_monitor(void *data) ++{ ++ struct apple_tristar *ts = data; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ if (!ts->stopping && !ts->sleeping) { ++ ret = apple_tristar_restore_route(ts); ++ if (!ret) ++ apple_tristar_classify(ts); ++ } ++ if (ts->restore_pending || ts->dcp_pending) ++ ret = -EUCLEAN; ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static int apple_tristar_budget(const struct usb_gadget_current_budget *budget, ++ int brick_ua, int *type) ++{ ++ *type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ if (!budget) ++ return 100000; ++ if (budget->connected) { ++ *type = POWER_SUPPLY_USB_TYPE_SDP; ++ if (budget->suspended) ++ return 500000; /* Link PM does not remove the enumerated source. */ ++ if (budget->host_grant) ++ return min(budget->current_ma, 2400U) * 1000; ++ if (budget->configured) ++ return min(budget->current_ma, 500U) * 1000; ++ return min(budget->current_ma, 100U) * 1000; ++ } ++ /* A proven brick is independent of USB suspend on an unenumerated port. */ ++ if (brick_ua > 0) { ++ *type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; ++ return min(brick_ua, 2400000); ++ } ++ return 500000; ++} ++ ++static void apple_tristar_auto_cfg(struct tristar_auto_cfg *cfg) ++{ ++ *cfg = (struct tristar_auto_cfg){ ++ .probe_ms = 2000, ++ .busy_probe_ms = 10000, ++ .verify_ms = 5000, ++ .settle_ms = 300, ++ /* 250 ms polls: an unplug shows within 750 ms */ ++ .detach_polls = 3, ++ .handshake_tries = 2, ++ .input_ua = clamp_t(int, READ_ONCE(auto_input_ua), APPLE_TRISTAR_FLOOR_UA, ++ APPLE_TRISTAR_ADAPTER_MAX_UA), ++ .floor_ua = APPLE_TRISTAR_FLOOR_UA, ++ }; ++} ++ ++static int apple_tristar_update(struct apple_tristar *ts) ++{ ++ struct usb_gadget_current_budget budget; ++ struct tristar_auto_cfg cfg; ++ int value, error, online, current_ua = 0, type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ int old_current, old_type, auto_online, auto_ua; ++ bool changed, accessory; ++ /* Query before taking ts->lock: role changes unbind the gadget. */ ++ bool host = ts->role_sw && ++ usb_role_switch_get_role(ts->role_sw) == USB_ROLE_HOST; ++ ++ apple_tristar_auto_cfg(&cfg); ++ ++ mutex_lock(&ts->lock); ++ if (ts->sleeping || ts->stopping) { ++ mutex_unlock(&ts->lock); ++ return -EHOSTDOWN; ++ } ++ old_current = ts->current_ua; ++ old_type = ts->usb_type; ++ value = apple_tristar_read(ts->client, TRISTAR_REVISION); ++ error = value < 0 ? value : value == TRISTAR_J71T_REVISION ? 0 : -ENODEV; ++ if (!error) { ++ value = apple_tristar_read(ts->client, TRISTAR_VBUS); ++ if (value < 0) ++ error = value; ++ } ++ ts->status = error ?: value; ++ /* A transport failure and USB reset/suspend are not source removal. */ ++ if (!error && ts->source_present && !(value & 3)) { ++ ts->source_present = false; ++ ts->attach_id++; ++ ts->computer = false; ++ ts->brick_sampled = false; ++ ts->brick_ua = 0; ++ ts->analog_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ ts->classify_after = jiffies + msecs_to_jiffies(1000); ++ ts->detach_role = true; ++ tristar_auto_forget(&ts->autop); ++ ts->autop.phase = TRISTAR_AUTO_EMPTY; ++ ts->autop.was_empty = true; ++ ts->autop.empty = 0; ++ usb_gadget_current_source_detached(ts->usb_node); ++ } ++ if (!error && (value & 3) == 3) ++ ts->source_present = true; ++ /* ++ * The camera adapter, once identified, is a power source in host mode ++ * too (iOS: USBPower Yes, 2100 mA after the handshake). Anything else ++ * in host mode is not a grant to charge from our own host port. ++ */ ++ accessory = !error && tristar_auto_supply(&ts->autop, &cfg, value, &auto_online, &auto_ua); ++ /* IC getVBUSDetect(strict=1): both low bits, never orientation bits. */ ++ online = accessory ? auto_online : !host && !error && (value & 3) == 3; ++ if (ts->restore_pending) { ++ int restore = apple_tristar_restore_route(ts); ++ ++ if (restore) ++ error = restore; ++ } ++ if (accessory && online && !error) { ++ /* An Accessory Charger Adapter: power in while the iPad is the host. */ ++ current_ua = auto_ua; ++ type = POWER_SUPPLY_USB_TYPE_ACA; ++ } else if (online && !error) { ++ if (usb_gadget_get_current_budget(ts->usb_node, &budget)) { ++ current_ua = apple_tristar_budget(NULL, 0, &type); ++ if (ts->computer) { ++ type = POWER_SUPPLY_USB_TYPE_SDP; ++ current_ua = APPLE_TRISTAR_FLOOR_UA; ++ } else if (ts->brick_ua) { ++ type = ts->analog_type; ++ current_ua = ts->brick_ua; ++ } ++ } else { ++ if ((budget.connected || budget.configured || budget.host_grant) && ++ (!ts->brick_ua || ++ ts->analog_type == POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID)) { ++ ts->computer = true; ++ ts->brick_ua = 0; ++ } ++ current_ua = apple_tristar_budget(&budget, ts->brick_ua, &type); ++ if (ts->brick_ua && ++ ts->analog_type != POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) { ++ type = ts->analog_type; ++ current_ua = ts->brick_ua; ++ } ++ if (ts->computer && !ts->brick_ua) { ++ type = POWER_SUPPLY_USB_TYPE_SDP; ++ current_ua = max(current_ua, APPLE_TRISTAR_FLOOR_UA); ++ } ++ } ++ } ++ changed = ts->error != error || ts->online != online; ++ changed |= old_current != current_ua || old_type != type; ++ ts->error = error; ++ ts->online = online; ++ ts->current_ua = current_ua; ++ ts->usb_type = type; ++ mutex_unlock(&ts->lock); ++ if (changed && ts->psy) ++ power_supply_changed(ts->psy); ++ return error; ++} ++ ++static int ++apple_tristar_get_property(struct power_supply *psy, ++ enum power_supply_property prop, ++ union power_supply_propval *value) ++{ ++ struct apple_tristar *ts = power_supply_get_drvdata(psy); ++ int ret; ++ ++ /* Policy consumers get a live read rather than a stale cached cable. */ ++ ret = apple_tristar_update(ts); ++ mutex_lock(&ts->lock); ++ switch (prop) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ value->intval = ts->online; ++ break; ++ case POWER_SUPPLY_PROP_CURRENT_MAX: ++ value->intval = ts->current_ua; ++ break; ++ case POWER_SUPPLY_PROP_USB_TYPE: ++ value->intval = ts->usb_type; ++ break; ++ case POWER_SUPPLY_PROP_SCOPE: ++ value->intval = POWER_SUPPLY_SCOPE_SYSTEM; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void apple_tristar_auto_run(struct apple_tristar *ts); ++ ++static void apple_tristar_poll(struct work_struct *work) ++{ ++ struct apple_tristar *ts = container_of(to_delayed_work(work), ++ struct apple_tristar, work); ++ ++ apple_tristar_update(ts); ++ apple_tristar_auto_run(ts); ++ if (ts->role_sw) ++ apple_s8000_usb_role_monitor(ts->role_sw, apple_tristar_monitor, ts); ++ else ++ apple_tristar_monitor(ts); ++ apple_tristar_update(ts); ++ mutex_lock(&ts->lock); ++ if (!ts->stopping && !ts->sleeping) ++ mod_delayed_work(system_power_efficient_wq, &ts->work, msecs_to_jiffies(250)); ++ mutex_unlock(&ts->lock); ++} ++ ++static int apple_tristar_usb_event(struct notifier_block *nb, unsigned long event, void *data) ++{ ++ struct apple_tristar *ts = container_of(nb, struct apple_tristar, usb_notifier); ++ const struct usb_gadget_current_budget *budget = data; ++ ++ if (budget->of_node != ts->usb_node || READ_ONCE(ts->stopping) || ++ READ_ONCE(ts->sleeping)) ++ return NOTIFY_DONE; ++ /* Cancel an in-flight analog sample; never revoke an attach here. */ ++ atomic_inc(&ts->reset_generation); ++ mod_delayed_work(system_power_efficient_wq, &ts->work, 0); ++ return NOTIFY_OK; ++} ++ ++static int apple_tristar_quiesce(struct apple_tristar *ts) ++{ ++ int attempt, ret = 0; ++ ++ mutex_lock(&ts->lock); ++ WRITE_ONCE(ts->sleeping, true); ++ mutex_unlock(&ts->lock); ++ if (ts->notifier_registered) { ++ usb_gadget_unregister_current_notifier(&ts->usb_notifier); ++ ts->notifier_registered = false; ++ } ++ cancel_delayed_work_sync(&ts->work); ++ mutex_lock(&ts->lock); ++ for (attempt = 0; attempt < 3; attempt++) { ++ ret = apple_tristar_restore_route(ts); ++ if (!ret) ++ break; ++ msleep(20); ++ } ++ mutex_unlock(&ts->lock); ++ if (ret) ++ dev_crit(&ts->client->dev, "Tristar route restore failed at quiesce: %d\n", ret); ++ return ret; ++} ++ ++static void apple_tristar_stop(void *data) ++{ ++ struct apple_tristar *ts = data; ++ ++ mutex_lock(&ts->lock); ++ WRITE_ONCE(ts->stopping, true); ++ mutex_unlock(&ts->lock); ++ apple_tristar_quiesce(ts); ++} ++ ++static int apple_tristar_resume(struct device *dev) ++{ ++ struct apple_tristar *ts = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = usb_gadget_register_current_notifier(&ts->usb_notifier); ++ if (ret && ret != -EOPNOTSUPP) ++ return ret; ++ ts->notifier_registered = !ret; ++ mutex_lock(&ts->lock); ++ ts->classify_after = jiffies + msecs_to_jiffies(1000); ++ WRITE_ONCE(ts->sleeping, false); ++ mod_delayed_work(system_power_efficient_wq, &ts->work, 0); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++ ++static int apple_tristar_suspend(struct device *dev) ++{ ++ struct apple_tristar *ts = dev_get_drvdata(dev); ++ int ret = apple_tristar_quiesce(ts); ++ ++ if (ret) ++ apple_tristar_resume(dev); ++ return ret; ++} ++ ++static void apple_tristar_shutdown(struct i2c_client *client) ++{ ++ apple_tristar_stop(i2c_get_clientdata(client)); ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(apple_tristar_pm_ops, ++ apple_tristar_suspend, apple_tristar_resume); ++ ++/* ++ * Lightning ID-bus: manual, debugfs-triggered GetID, link recovery and ++ * power handshake. Nothing here runs on its own and nothing changes the ++ * USB role. ++ */ ++ ++#define APPLE_TRISTAR_PH_WINDOW_MS 30000 ++ ++static int apple_tristar_idbus_read(void *ctx, u8 reg) ++{ ++ struct apple_tristar *ts = ctx; ++ ++ return apple_tristar_read(ts->client, reg); ++} ++ ++static int apple_tristar_idbus_write(void *ctx, u8 reg, u8 val) ++{ ++ struct apple_tristar *ts = ctx; ++ ++ return i2c_smbus_write_byte_data(ts->client, reg, val); ++} ++ ++/* Hold the bus across the read and write: 0x05 is shared with the role switch. */ ++static int apple_tristar_idbus_update(void *ctx, u8 reg, u8 clear, u8 set) ++{ ++ struct apple_tristar *ts = ctx; ++ struct i2c_client *client = ts->client; ++ union i2c_smbus_data data; ++ u8 old = 0; ++ int ret; ++ ++ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, ++ I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE, NULL); ++ if (!ret) ++ ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, ++ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data); ++ if (!ret) { ++ old = data.byte; ++ data.byte = (old & ~clear) | set; ++ ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, ++ I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, &data); ++ } ++ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ return ret ?: old; ++} ++ ++/* ++ * iOS operation 1 with an N-byte result: pointer write + STOP, then a ++ * separate read. PA Semi offers no SMBus I2C-block transfer, so use ++ * plain messages, each ending in STOP. ++ */ ++static int apple_tristar_idbus_block_read(void *ctx, u8 reg, u8 *buf, unsigned int len) ++{ ++ struct apple_tristar *ts = ctx; ++ struct i2c_client *client = ts->client; ++ struct i2c_msg sel = { ++ .addr = client->addr, ++ .flags = client->flags & I2C_M_TEN, ++ .len = 1, ++ .buf = ®, ++ }; ++ struct i2c_msg rx = { ++ .addr = client->addr, ++ .flags = (client->flags & I2C_M_TEN) | I2C_M_RD, ++ .len = len, ++ .buf = buf, ++ }; ++ int ret; ++ ++ i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ ret = __i2c_transfer(client->adapter, &sel, 1); ++ if (ret == 1) ++ ret = __i2c_transfer(client->adapter, &rx, 1); ++ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ return ret == 1 ? 0 : ret < 0 ? ret : -EIO; ++} ++ ++/* iOS operation 2: subaddress and data in one write message. */ ++static int apple_tristar_idbus_block_write(void *ctx, u8 reg, const u8 *buf, unsigned int len) ++{ ++ struct apple_tristar *ts = ctx; ++ u8 msg[1 + TRISTAR_IDBUS_MAX_FRAME]; ++ int ret; ++ ++ if (len > TRISTAR_IDBUS_MAX_FRAME) ++ return -EINVAL; ++ msg[0] = reg; ++ memcpy(msg + 1, buf, len); ++ ret = i2c_master_send(ts->client, msg, len + 1); ++ return ret == (int)len + 1 ? 0 : ret < 0 ? ret : -EIO; ++} ++ ++static void apple_tristar_idbus_sleep(void *ctx, unsigned int ms) ++{ ++ fsleep(ms * USEC_PER_MSEC); ++} ++ ++/* Every line goes to the kernel log and to the run's debugfs transcript. */ ++static __printf(2, 3) void apple_tristar_idbus_log(void *ctx, const char *fmt, ...) ++{ ++ struct apple_tristar *ts = ctx; ++ struct apple_tristar_idbus *st = ts->idbus; ++ struct va_format vaf; ++ va_list args; ++ ++ if (!st->quiet) { ++ va_start(args, fmt); ++ vaf.fmt = fmt; ++ vaf.va = &args; ++ dev_info(&ts->client->dev, "idbus: %pV\n", &vaf); ++ va_end(args); ++ } ++ if (st->log_len + 2 >= st->log_size) ++ return; ++ va_start(args, fmt); ++ st->log_len += vscnprintf(st->log + st->log_len, st->log_size - st->log_len - 1, ++ fmt, args); ++ va_end(args); ++ st->log[st->log_len++] = '\n'; ++ st->log[st->log_len] = '\0'; ++} ++ ++static const struct tristar_idbus_ops apple_tristar_idbus_ops = { ++ .read = apple_tristar_idbus_read, ++ .write = apple_tristar_idbus_write, ++ .update = apple_tristar_idbus_update, ++ .block_read = apple_tristar_idbus_block_read, ++ .block_write = apple_tristar_idbus_block_write, ++ .sleep_ms = apple_tristar_idbus_sleep, ++ .log = apple_tristar_idbus_log, ++}; ++ ++/* Caller holds ts->lock. */ ++static void apple_tristar_idbus_begin(struct apple_tristar *ts, char *log, size_t size) ++{ ++ struct apple_tristar_idbus *st = ts->idbus; ++ ++ st->log = log; ++ st->log_size = size; ++ st->log_len = 0; ++ log[0] = '\0'; ++} ++ ++/* ++ * Manual (debugfs) or automatic (accessory policy, quiet) GetID. On success ++ * the ID is copied to @id if given. ++ */ ++static int apple_tristar_identify(struct apple_tristar *ts, unsigned int flags, bool quiet, ++ u8 *id) ++{ ++ struct apple_tristar_idbus *st = ts->idbus; ++ /* Query before taking ts->lock, as apple_tristar_update() does. */ ++ enum usb_role role = ts->role_sw ? usb_role_switch_get_role(ts->role_sw) : USB_ROLE_NONE; ++ int ret; ++ ++ mutex_lock(&ts->lock); ++ apple_tristar_idbus_begin(ts, st->id_log, sizeof(st->id_log)); ++ memset(&st->id, 0, sizeof(st->id)); ++ st->id_run = true; ++ st->quiet = quiet; ++ if (ts->sleeping || ts->stopping) { ++ apple_tristar_idbus_log(ts, "identify refused: supplier stopped"); ++ ret = -EHOSTDOWN; ++ } else if (!i2c_check_functionality(ts->client->adapter, I2C_FUNC_I2C)) { ++ apple_tristar_idbus_log(ts, "identify refused: adapter lacks plain I2C messages"); ++ ret = -EOPNOTSUPP; ++ } else { ++ apple_tristar_idbus_log(ts, "%s identify, USB role %s", ++ quiet ? "automatic" : "manual", usb_role_string(role)); ++ ret = tristar_idbus_identify(&st->ib, flags, &st->id); ++ st->id_at = jiffies; ++ if (!ret && !st->id.id_valid) ++ ret = -ENODATA; ++ } ++ st->id.status = ret; ++ st->quiet = false; ++ if (!ret && id) ++ memcpy(id, st->id.id, TRISTAR_IDBUS_ID_LEN); ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++/* @policy: the accessory policy's own run, which allow_power_handshake does not gate. */ ++static int apple_tristar_power_handshake(struct apple_tristar *ts, bool policy) ++{ ++ struct apple_tristar_idbus *st = ts->idbus; ++ int ret; ++ ++ mutex_lock(&ts->lock); ++ apple_tristar_idbus_begin(ts, st->ph_log, sizeof(st->ph_log)); ++ memset(&st->ph, 0, sizeof(st->ph)); ++ st->ph_run = true; ++ if (policy) ++ apple_tristar_idbus_log(ts, "power handshake for the identified camera adapter"); ++ if (!policy && !READ_ONCE(allow_power_handshake)) { ++ apple_tristar_idbus_log(ts, "power handshake refused: allow_power_handshake=0"); ++ ret = -EPERM; ++ } else if (ts->sleeping || ts->stopping) { ++ apple_tristar_idbus_log(ts, "power handshake refused: supplier stopped"); ++ ret = -EHOSTDOWN; ++ } else if (!st->id.id_valid || ++ time_after(jiffies, st->id_at + msecs_to_jiffies(APPLE_TRISTAR_PH_WINDOW_MS))) { ++ apple_tristar_idbus_log(ts, "power handshake refused: no ID verified in %u s", ++ APPLE_TRISTAR_PH_WINDOW_MS / 1000); ++ ret = -ENOLINK; ++ } else { ++ ret = tristar_idbus_power_handshake(&st->ib, &st->ph); ++ } ++ st->ph.status = ret; ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static int apple_tristar_recover(struct apple_tristar *ts, unsigned int action) ++{ ++ struct apple_tristar_idbus *st = ts->idbus; ++ int ret; ++ ++ mutex_lock(&ts->lock); ++ apple_tristar_idbus_begin(ts, st->rc_log, sizeof(st->rc_log)); ++ memset(&st->rc, 0, sizeof(st->rc)); ++ st->rc_run = true; ++ if (ts->sleeping || ts->stopping) { ++ apple_tristar_idbus_log(ts, "recover refused: supplier stopped"); ++ ret = -EHOSTDOWN; ++ } else if (!i2c_check_functionality(ts->client->adapter, I2C_FUNC_I2C)) { ++ apple_tristar_idbus_log(ts, "recover refused: adapter lacks plain I2C messages"); ++ ret = -EOPNOTSUPP; ++ } else { ++ ret = tristar_idbus_recover(&st->ib, action, &st->rc); ++ } ++ st->rc.status = ret; ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++/* ++ * Lightning accessory policy (apple_tristar_auto.h): the iOS mode-2 path. ++ * GetID on plug-in and periodically while no USB host uses the gadget; for ++ * the camera adapter, the power handshake and then the host role, kept ++ * until the adapter is unplugged. ++ */ ++ ++/* Caller holds ts->lock. Logs phase changes and the given event. */ ++static __printf(3, 4) void apple_tristar_auto_note(struct apple_tristar *ts, ++ enum tristar_auto_phase old, ++ const char *fmt, ...) ++{ ++ enum tristar_auto_phase phase = ts->autop.phase; ++ char msg[96] = "", line[192]; ++ size_t len, drop; ++ va_list args; ++ ++ if (old == phase && !fmt) ++ return; ++ if (fmt) { ++ va_start(args, fmt); ++ vscnprintf(msg, sizeof(msg), fmt, args); ++ va_end(args); ++ } ++ if (old == phase) ++ len = scnprintf(line, sizeof(line), "%llu ms %s: %s", ++ ktime_to_ms(ktime_get_boottime()), ++ tristar_auto_phase_name(phase), msg); ++ else ++ len = scnprintf(line, sizeof(line), "%llu ms %s -> %s%s%s", ++ ktime_to_ms(ktime_get_boottime()), ++ tristar_auto_phase_name(old), tristar_auto_phase_name(phase), ++ *msg ? ": " : "", msg); ++ dev_info(&ts->client->dev, "lightning: %s\n", line); ++ /* Keep the newest lines: drop whole lines from the front. */ ++ while (ts->auto_log_len + len + 2 > sizeof(ts->auto_log)) { ++ char *nl = memchr(ts->auto_log, '\n', ts->auto_log_len); ++ ++ drop = nl ? nl - ts->auto_log + 1 : ts->auto_log_len; ++ memmove(ts->auto_log, ts->auto_log + drop, ts->auto_log_len - drop); ++ ts->auto_log_len -= drop; ++ } ++ memcpy(ts->auto_log + ts->auto_log_len, line, len); ++ ts->auto_log_len += len; ++ ts->auto_log[ts->auto_log_len++] = '\n'; ++ ts->auto_log[ts->auto_log_len] = '\0'; ++} ++ ++static enum tristar_auto_action ++apple_tristar_auto_act(struct apple_tristar *ts, const struct tristar_auto_cfg *cfg, ++ enum tristar_auto_action action) ++{ ++ struct tristar_auto *ta = &ts->autop; ++ u8 id[TRISTAR_IDBUS_ID_LEN] = {}; ++ enum tristar_auto_phase old; ++ bool had, same; ++ int ret; ++ ++ switch (action) { ++ case TRISTAR_AUTO_PROBE: ++ /* No reorient: it can move a live Mac tether to the other lanes. */ ++ ret = apple_tristar_identify(ts, TRISTAR_IDBUS_NO_REORIENT, true, id); ++ mutex_lock(&ts->lock); ++ old = ta->phase; ++ had = ta->id_valid; ++ same = had && !ret && !memcmp(ta->id, id, sizeof(id)); ++ action = tristar_auto_probed(ta, cfg, ktime_to_ms(ktime_get_boottime()), ret, id); ++ if (!ret && !same) ++ apple_tristar_auto_note(ts, old, "digital ID %*ph%s", (int)sizeof(id), id, ++ ta->adapter ? " (camera adapter)" : ""); ++ else ++ apple_tristar_auto_note(ts, old, NULL); ++ mutex_unlock(&ts->lock); ++ return action; ++ case TRISTAR_AUTO_POWER_HANDSHAKE: ++ ret = apple_tristar_power_handshake(ts, true); ++ mutex_lock(&ts->lock); ++ old = ta->phase; ++ action = tristar_auto_handshake_done(ta, cfg, ret); ++ apple_tristar_auto_note(ts, old, "power handshake %s (%d), try %u", ++ ret ? "failed" : "ok", ret, ta->ph_tries); ++ mutex_unlock(&ts->lock); ++ break; ++ case TRISTAR_AUTO_TO_HOST: ++ ret = apple_s8000_usb_role_claim(ts->role_sw, true); ++ if (!ret) ++ ret = usb_role_switch_set_role(ts->role_sw, USB_ROLE_HOST); ++ if (ret) ++ apple_s8000_usb_role_claim(ts->role_sw, false); ++ mutex_lock(&ts->lock); ++ old = ta->phase; ++ tristar_auto_role_done(ta, cfg, ktime_to_ms(ktime_get_boottime()), true, ret); ++ apple_tristar_auto_note(ts, old, "host role %s (%d)", ret ? "failed" : "on", ret); ++ mutex_unlock(&ts->lock); ++ action = TRISTAR_AUTO_NONE; ++ break; ++ case TRISTAR_AUTO_TO_DEVICE: ++ ret = 0; ++ if (apple_s8000_usb_role_accessory(ts->role_sw)) ++ ret = usb_role_switch_set_role(ts->role_sw, USB_ROLE_DEVICE); ++ mutex_lock(&ts->lock); ++ old = ta->phase; ++ tristar_auto_role_done(ta, cfg, ktime_to_ms(ktime_get_boottime()), false, ret); ++ apple_tristar_auto_note(ts, old, "device role %s (%d)", ret ? "failed" : "on", ret); ++ mutex_unlock(&ts->lock); ++ action = TRISTAR_AUTO_NONE; ++ break; ++ case TRISTAR_AUTO_NONE: ++ return action; ++ } ++ /* The input budget follows the handshake and the role; notifies on change. */ ++ apple_tristar_update(ts); ++ return action; ++} ++ ++static void apple_tristar_auto_run(struct apple_tristar *ts) ++{ ++ struct usb_gadget_current_budget budget; ++ struct tristar_auto_cfg cfg; ++ struct tristar_auto_in in = { .enabled = READ_ONCE(auto_host) }; ++ enum tristar_auto_action action; ++ enum tristar_auto_phase old; ++ unsigned int i; ++ ++ if (!ts->role_sw || !ts->idbus) ++ return; ++ apple_tristar_auto_cfg(&cfg); ++ mutex_lock(&ts->lock); ++ if (ts->detach_role) { ++ ts->detach_role = false; ++ mutex_unlock(&ts->lock); ++ apple_tristar_auto_act(ts, &cfg, TRISTAR_AUTO_TO_DEVICE); ++ } else { ++ mutex_unlock(&ts->lock); ++ } ++ /* Role and gadget queries before ts->lock, as apple_tristar_update() does. */ ++ in.host = usb_role_switch_get_role(ts->role_sw) == USB_ROLE_HOST; ++ in.owned = in.host && apple_s8000_usb_role_accessory(ts->role_sw); ++ in.link_active = !usb_gadget_get_current_budget(ts->usb_node, &budget) && ++ budget.configured && !budget.suspended; ++ mutex_lock(&ts->lock); ++ if (ts->sleeping || ts->stopping) { ++ mutex_unlock(&ts->lock); ++ return; ++ } ++ in.now = ktime_to_ms(ktime_get_boottime()); ++ in.status = ts->status; ++ old = ts->autop.phase; ++ action = tristar_auto_step(&ts->autop, &cfg, &in); ++ apple_tristar_auto_note(ts, old, NULL); ++ mutex_unlock(&ts->lock); ++ /* A probe can lead to the handshake and then the role switch. */ ++ for (i = 0; i < 4 && action != TRISTAR_AUTO_NONE; i++) ++ action = apple_tristar_auto_act(ts, &cfg, action); ++} ++ ++/* "1" or "identify", optionally with "swap" and/or "noreorient". */ ++static int apple_tristar_parse_trigger(const char __user *ubuf, size_t count, ++ unsigned int *flags) ++{ ++ char buf[48], *cur, *tok; ++ bool go = false; ++ ++ if (!count || count >= sizeof(buf)) ++ return -EINVAL; ++ if (copy_from_user(buf, ubuf, count)) ++ return -EFAULT; ++ buf[count] = '\0'; ++ *flags = 0; ++ cur = buf; ++ while ((tok = strsep(&cur, " \t\n"))) { ++ if (!*tok) ++ continue; ++ if (!strcmp(tok, "1") || !strcmp(tok, "identify")) ++ go = true; ++ else if (!strcmp(tok, "swap")) ++ *flags |= TRISTAR_IDBUS_SWAP_HOSTID; ++ else if (!strcmp(tok, "noreorient")) ++ *flags |= TRISTAR_IDBUS_NO_REORIENT; ++ else ++ return -EINVAL; ++ } ++ return go ? 0 : -EINVAL; ++} ++ ++static ssize_t apple_tristar_identify_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ unsigned int flags; ++ int ret; ++ ++ ret = apple_tristar_parse_trigger(ubuf, count, &flags); ++ if (!ret) ++ ret = apple_tristar_identify(file->private_data, flags, false, NULL); ++ if (ret) ++ return ret; ++ return count; ++} ++ ++static ssize_t apple_tristar_power_handshake_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ unsigned int flags; ++ int ret; ++ ++ ret = apple_tristar_parse_trigger(ubuf, count, &flags); ++ if (!ret && flags) ++ ret = -EINVAL; ++ if (!ret) ++ ret = apple_tristar_power_handshake(file->private_data, false); ++ if (ret) ++ return ret; ++ return count; ++} ++ ++/* Exactly one of "reorient", "idbus_reset" or "fault_ack". */ ++static ssize_t apple_tristar_recover_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ static const char *const verbs[] = { ++ [TRISTAR_IDBUS_RECOVER_REORIENT] = "reorient", ++ [TRISTAR_IDBUS_RECOVER_RESET] = "idbus_reset", ++ [TRISTAR_IDBUS_RECOVER_FAULT_ACK] = "fault_ack", ++ }; ++ char buf[16]; ++ int action, ret; ++ ++ if (!count || count >= sizeof(buf)) ++ return -EINVAL; ++ if (copy_from_user(buf, ubuf, count)) ++ return -EFAULT; ++ buf[count] = '\0'; ++ action = sysfs_match_string(verbs, buf); ++ if (action < 0) ++ return action; ++ ret = apple_tristar_recover(file->private_data, action); ++ if (ret) ++ return ret; ++ return count; ++} ++ ++static const struct file_operations apple_tristar_recover_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_tristar_recover_write, ++ .llseek = noop_llseek, ++}; ++ ++static const struct file_operations apple_tristar_identify_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_tristar_identify_write, ++ .llseek = noop_llseek, ++}; ++ ++static const struct file_operations apple_tristar_power_handshake_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_tristar_power_handshake_write, ++ .llseek = noop_llseek, ++}; ++ ++static int apple_tristar_identify_log_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_puts(s, ts->idbus->id_run ? ts->idbus->id_log : "identify has not run\n"); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_identify_log); ++ ++static int apple_tristar_power_handshake_log_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_puts(s, ts->idbus->ph_run ? ts->idbus->ph_log : "power handshake has not run\n"); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_power_handshake_log); ++ ++static int apple_tristar_recover_log_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_puts(s, ts->idbus->rc_run ? ts->idbus->rc_log : "recover has not run\n"); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_recover_log); ++ ++static int apple_tristar_digital_id_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ struct tristar_idbus_id_result *id = &ts->idbus->id; ++ char hex[3 * TRISTAR_IDBUS_MAX_FRAME + 1]; ++ ++ mutex_lock(&ts->lock); ++ if (id->id_valid) { ++ tristar_idbus_format_hex(id->id, TRISTAR_IDBUS_ID_LEN, hex, sizeof(hex)); ++ seq_printf(s, "%s\n", hex); ++ } else if (ts->idbus->id_run) { ++ seq_printf(s, "none (status %d)\n", id->status); ++ } else { ++ seq_puts(s, "none (identify has not run)\n"); ++ } ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_digital_id); ++ ++static int apple_tristar_digital_id_decoded_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ struct tristar_idbus_id_result *id = &ts->idbus->id; ++ char text[160]; ++ ++ mutex_lock(&ts->lock); ++ if (id->id_valid) { ++ tristar_idbus_format_fields(id->id, text, sizeof(text)); ++ seq_printf(s, "%s\n", text); ++ } else { ++ seq_puts(s, "none\n"); ++ } ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_digital_id_decoded); ++ ++/* Raw evidence, published even when the header or CRC was wrong. */ ++static int apple_tristar_idbus_frame_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ struct tristar_idbus_id_result *id = &ts->idbus->id; ++ char hex[3 * TRISTAR_IDBUS_MAX_FRAME + 1]; ++ ++ mutex_lock(&ts->lock); ++ if (id->frame_len) { ++ tristar_idbus_format_hex(id->frame, id->frame_len, hex, sizeof(hex)); ++ seq_printf(s, "frame: %s (%s), source %s, 0x0c 0x%02x\n", hex, ++ tristar_idbus_verdict(id->frame_status), ++ id->autonomous ? "reorient-armed exchange" : "GetID command", ++ id->orientation); ++ } else { ++ seq_puts(s, "frame: none\n"); ++ } ++ tristar_idbus_format_hex(id->retained, TRISTAR_IDBUS_MAX_FRAME, hex, sizeof(hex)); ++ seq_printf(s, "retained before GetID: %s (%s)\n", hex, ++ id->retained_ok ? "valid ID frame, may be stale" : "not a valid ID frame"); ++ if (id->reorient_read) { ++ tristar_idbus_format_hex(id->reorient_frame, TRISTAR_IDBUS_MAX_FRAME, hex, ++ sizeof(hex)); ++ seq_printf(s, "after reorient: %s (%s), 0x0d events 0x%02x\n", hex, ++ tristar_idbus_verdict(id->reorient_status), id->reorient_events); ++ } ++ seq_printf(s, "status %d, GetID commands %u, reoriented %d, reset released %d, flags 0x%x\n", ++ id->status, id->attempts, id->reoriented, id->released_reset, id->flags); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_idbus_frame); ++ ++/* Values cached from the last run; reading these never touches the bus. */ ++static int apple_tristar_reg_05_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_printf(s, "0x%02x\n", ts->idbus->ib.last_ctrl); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_reg_05); ++ ++static int apple_tristar_reg_0d_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_printf(s, "0x%02x (seen this run 0x%02x)\n", ts->idbus->ib.last_event, ++ ts->idbus->ib.events); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_reg_0d); ++ ++static int apple_tristar_reg_0a_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_printf(s, "0x%02x (acknowledged %u)\n", ts->idbus->ib.last_fault, ++ ts->idbus->ib.faults); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_reg_0a); ++ ++static int apple_tristar_auto_log_show(struct seq_file *s, void *unused) ++{ ++ struct apple_tristar *ts = s->private; ++ ++ mutex_lock(&ts->lock); ++ seq_puts(s, ts->auto_log_len ? ts->auto_log : "no accessory policy events\n"); ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_tristar_auto_log); ++ ++static const char *apple_tristar_usb_type_name(int type) ++{ ++ switch (type) { ++ case POWER_SUPPLY_USB_TYPE_SDP: ++ return "SDP"; ++ case POWER_SUPPLY_USB_TYPE_DCP: ++ return "DCP"; ++ case POWER_SUPPLY_USB_TYPE_CDP: ++ return "CDP"; ++ case POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID: ++ return "BrickID"; ++ case POWER_SUPPLY_USB_TYPE_ACA: ++ return "ACA"; ++ default: ++ return "Unknown"; ++ } ++} ++ ++static int apple_tristar_psy_int(const char *name, enum power_supply_property prop, int *val) ++{ ++ union power_supply_propval value; ++ struct power_supply *psy = power_supply_get_by_name(name); ++ int ret; ++ ++ if (!psy) ++ return -ENODEV; ++ ret = power_supply_get_property(psy, prop, &value); ++ power_supply_put(psy); ++ if (!ret) ++ *val = value.intval; ++ return ret; ++} ++ ++static const char *apple_tristar_status_name(int status) ++{ ++ switch (status) { ++ case POWER_SUPPLY_STATUS_CHARGING: ++ return "Charging"; ++ case POWER_SUPPLY_STATUS_DISCHARGING: ++ return "Discharging"; ++ case POWER_SUPPLY_STATUS_NOT_CHARGING: ++ return "Not charging"; ++ case POWER_SUPPLY_STATUS_FULL: ++ return "Full"; ++ default: ++ return "Unknown"; ++ } ++} ++ ++static ssize_t apple_tristar_emit_psy(char *buf, ssize_t len, const char *name, ++ enum power_supply_property prop, const char *label) ++{ ++ int val = 0, ret = apple_tristar_psy_int(name, prop, &val); ++ ++ if (ret) ++ return len + sysfs_emit_at(buf, len, " %s=err%d", label, ret); ++ if (prop == POWER_SUPPLY_PROP_STATUS) ++ return len + sysfs_emit_at(buf, len, " %s=%s", label, ++ apple_tristar_status_name(val)); ++ return len + sysfs_emit_at(buf, len, " %s=%d", label, val); ++} ++ ++/* ++ * One world-readable snapshot for test tooling: policy phase, role, ID, ++ * handshake, what this supplier reports and what the charger and the ++ * battery see. No GetID or register write; the charger's ONLINE read ++ * refreshes this supplier as any consumer's would. ++ */ ++static ssize_t lightning_state_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct apple_tristar *ts = dev_get_drvdata(dev); ++ struct tristar_auto ta; ++ int status, online, current_ua, type, error; ++ enum usb_role role = USB_ROLE_NONE; ++ bool owned = false; ++ ssize_t len = 0; ++ ++ if (ts->role_sw) { ++ role = usb_role_switch_get_role(ts->role_sw); ++ owned = apple_s8000_usb_role_accessory(ts->role_sw); ++ } ++ mutex_lock(&ts->lock); ++ ta = ts->autop; ++ status = ts->status; ++ online = ts->online; ++ current_ua = ts->current_ua; ++ type = ts->usb_type; ++ error = ts->error; ++ mutex_unlock(&ts->lock); ++ ++ len += sysfs_emit_at(buf, len, "auto_host: %d\nphase: %s\nrole: %s%s\n", ++ READ_ONCE(auto_host), tristar_auto_phase_name(ta.phase), ++ usb_role_string(role), owned ? " (accessory)" : ""); ++ if (status < 0) ++ len += sysfs_emit_at(buf, len, "status_0c: error %d\n", status); ++ else ++ len += sysfs_emit_at(buf, len, ++ "status_0c: 0x%02x (vbus %d, present %d, orientation %d)\n", ++ status, (status & TRISTAR_AUTO_VBUS) == TRISTAR_AUTO_VBUS, ++ !!(status & TRISTAR_AUTO_PRESENT), ++ !!(status & TRISTAR_AUTO_ORIENTATION)); ++ if (ta.id_valid) ++ len += sysfs_emit_at(buf, len, "digital_id: %*ph%s\n", (int)sizeof(ta.id), ta.id, ++ ta.adapter ? " (camera adapter)" : ""); ++ else ++ len += sysfs_emit_at(buf, len, "digital_id: none (last GetID %d)\n", ta.id_status); ++ if (ta.ph_status == 1) ++ len += sysfs_emit_at(buf, len, "power_handshake: not run\n"); ++ else ++ len += sysfs_emit_at(buf, len, "power_handshake: %s (%d, tries %u)\n", ++ ta.ph_ok ? "ok" : "failed", ta.ph_status, ta.ph_tries); ++ len += sysfs_emit_at(buf, len, ++ "supply: online %d, type %s, current_max_ua %d, error %d\n", ++ online, apple_tristar_usb_type_name(type), current_ua, error); ++ /* Not under ts->lock: the charger reads this supplier. */ ++ len += sysfs_emit_at(buf, len, "charger:"); ++ len = apple_tristar_emit_psy(buf, len, "charger", POWER_SUPPLY_PROP_ONLINE, "online"); ++ len = apple_tristar_emit_psy(buf, len, "charger", POWER_SUPPLY_PROP_STATUS, "status"); ++ len = apple_tristar_emit_psy(buf, len, "charger", POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, ++ "input_current_limit_ua"); ++ len += sysfs_emit_at(buf, len, "\nbattery:"); ++ len = apple_tristar_emit_psy(buf, len, "battery", POWER_SUPPLY_PROP_STATUS, "status"); ++ len = apple_tristar_emit_psy(buf, len, "battery", POWER_SUPPLY_PROP_CURRENT_NOW, ++ "current_now_ua"); ++ len = apple_tristar_emit_psy(buf, len, "battery", POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ "voltage_now_uv"); ++ len = apple_tristar_emit_psy(buf, len, "battery", POWER_SUPPLY_PROP_CAPACITY, "capacity"); ++ len += sysfs_emit_at(buf, len, ++ "\ncounters: probes %u probe_errors %u host_entries %u detaches %u inhibits %u role_errors %u\n", ++ ta.probes, ta.probe_errors, ta.host_entries, ta.detaches, ta.inhibits, ++ ta.role_errors); ++ return len; ++} ++static DEVICE_ATTR_RO(lightning_state); ++ ++static struct attribute *apple_tristar_attrs[] = { ++ &dev_attr_lightning_state.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(apple_tristar); ++ ++static void apple_tristar_remove_debugfs(void *data) ++{ ++ struct apple_tristar *ts = data; ++ ++ /* Waits for an in-flight identify before the supplier stops. */ ++ debugfs_remove_recursive(ts->debugfs); ++} ++ ++/* Optional: an ID-bus setup failure never blocks the supplier. */ ++static void apple_tristar_debugfs_init(struct apple_tristar *ts) ++{ ++ struct device *dev = &ts->client->dev; ++ struct dentry *dir; ++ ++ ts->idbus = devm_kzalloc(dev, sizeof(*ts->idbus), GFP_KERNEL); ++ if (!ts->idbus) ++ return; ++ ts->idbus->ib.ops = &apple_tristar_idbus_ops; ++ ts->idbus->ib.ctx = ts; ++ dir = debugfs_create_dir("apple-tristar", NULL); ++ ts->debugfs = dir; ++ debugfs_create_file("identify", 0200, dir, ts, &apple_tristar_identify_fops); ++ debugfs_create_file("identify_log", 0444, dir, ts, &apple_tristar_identify_log_fops); ++ debugfs_create_file("digital_id", 0444, dir, ts, &apple_tristar_digital_id_fops); ++ debugfs_create_file("digital_id_decoded", 0444, dir, ts, ++ &apple_tristar_digital_id_decoded_fops); ++ debugfs_create_file("idbus_frame", 0444, dir, ts, &apple_tristar_idbus_frame_fops); ++ debugfs_create_file("reg_05", 0444, dir, ts, &apple_tristar_reg_05_fops); ++ debugfs_create_file("reg_0d", 0444, dir, ts, &apple_tristar_reg_0d_fops); ++ debugfs_create_file("reg_0a", 0444, dir, ts, &apple_tristar_reg_0a_fops); ++ debugfs_create_file("power_handshake", 0200, dir, ts, ++ &apple_tristar_power_handshake_fops); ++ debugfs_create_file("power_handshake_log", 0444, dir, ts, ++ &apple_tristar_power_handshake_log_fops); ++ debugfs_create_file("recover", 0200, dir, ts, &apple_tristar_recover_fops); ++ debugfs_create_file("recover_log", 0444, dir, ts, &apple_tristar_recover_log_fops); ++ debugfs_create_file("auto_log", 0444, dir, ts, &apple_tristar_auto_log_fops); ++ if (devm_add_action_or_reset(dev, apple_tristar_remove_debugfs, ts)) ++ dev_warn(dev, "ID-bus debug interface removed\n"); ++} ++ ++static void apple_tristar_put_device(void *data) ++{ ++ put_device(data); ++} ++ ++static void apple_tristar_put_node(void *data) ++{ ++ of_node_put(data); ++} ++ ++static const enum power_supply_property apple_tristar_properties[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_CURRENT_MAX, ++ POWER_SUPPLY_PROP_USB_TYPE, ++ POWER_SUPPLY_PROP_SCOPE, ++}; ++ ++static const struct power_supply_desc apple_tristar_desc = { ++ .name = "usb", ++ .type = POWER_SUPPLY_TYPE_USB, ++ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | ++ BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | ++ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA), ++ .properties = apple_tristar_properties, ++ .num_properties = ARRAY_SIZE(apple_tristar_properties), ++ .get_property = apple_tristar_get_property, ++}; ++ ++/** ++ * apple_tristar_attach_id - snapshot the physical source epoch ++ * @psy: Tristar supplier, held by the consumer ++ * @id: epoch incremented on each observed VBUS removal ++ * ++ * Return: 0, or a live transport error. Link PM never increments the epoch. ++ */ ++int apple_tristar_attach_id(struct power_supply *psy, unsigned int *id) ++{ ++ struct apple_tristar *ts; ++ int ret; ++ ++ if (psy->desc != &apple_tristar_desc) ++ return -ENODEV; ++ ts = power_supply_get_drvdata(psy); ++ ret = apple_tristar_update(ts); ++ if (ret) ++ return ret; ++ mutex_lock(&ts->lock); ++ *id = ts->attach_id; ++ mutex_unlock(&ts->lock); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(apple_tristar_attach_id); ++ ++static void apple_tristar_put_role(void *data) ++{ ++ usb_role_switch_put(data); ++} ++ ++static int apple_tristar_probe(struct i2c_client *client) ++{ ++ struct power_supply_config config = {}; ++ struct apple_tristar *ts; ++ struct device_node *node; ++ struct i2c_client *pmic_client; ++ struct platform_device *usb_device; ++ struct device_link *link; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE)) ++ return -EOPNOTSUPP; ++ ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); ++ if (!ts) ++ return -ENOMEM; ++ ts->client = client; ++ ts->status = -ENODATA; ++ tristar_auto_init(&ts->autop); ++ i2c_set_clientdata(client, ts); ++ node = of_parse_phandle(client->dev.of_node, "apple,usb-role-switch", 0); ++ if (node) { ++ ts->role_sw = usb_role_switch_find_by_fwnode(of_fwnode_handle(node)); ++ of_node_put(node); ++ if (!ts->role_sw) ++ return -EPROBE_DEFER; ++ ret = devm_add_action_or_reset(&client->dev, apple_tristar_put_role, ++ ts->role_sw); ++ if (ret) ++ return ret; ++ } ++ ts->usb_node = of_parse_phandle(client->dev.of_node, "usb-controller", 0); ++ if (!ts->usb_node) ++ return -EINVAL; ++ ret = devm_add_action_or_reset(&client->dev, apple_tristar_put_node, ts->usb_node); ++ if (ret) ++ return ret; ++ node = of_parse_phandle(client->dev.of_node, "apple,pmic", 0); ++ if (!node) ++ return -EINVAL; ++ pmic_client = of_find_i2c_device_by_node(node); ++ of_node_put(node); ++ if (!pmic_client) ++ return -EPROBE_DEFER; ++ ret = devm_add_action_or_reset(&client->dev, apple_tristar_put_device, &pmic_client->dev); ++ if (ret) ++ return ret; ++ ts->pmic = dev_get_regmap(&pmic_client->dev, NULL); ++ if (!ts->pmic) ++ return -EPROBE_DEFER; ++ if (!device_link_add(&client->dev, &pmic_client->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) ++ return -ENOMEM; ++ usb_device = of_find_device_by_node(ts->usb_node); ++ if (!usb_device) ++ return -EPROBE_DEFER; ++ link = device_link_add(&client->dev, &usb_device->dev, DL_FLAG_AUTOREMOVE_CONSUMER); ++ ret = link ? 0 : -ENOMEM; ++ put_device(&usb_device->dev); ++ if (ret) ++ return ret; ++ atomic_set(&ts->reset_generation, 0); ++ /* Allow old iOS work to quiesce; no first-boot ADC idle bit is known. */ ++ ts->classify_after = jiffies + msecs_to_jiffies(2000); ++ ret = devm_mutex_init(&client->dev, &ts->lock); ++ if (ret) ++ return ret; ++ INIT_DELAYED_WORK(&ts->work, apple_tristar_poll); ++ ret = apple_tristar_update(ts); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, "CBTL1610 revision/VBUS read failed\n"); ++ config.drv_data = ts; ++ config.fwnode = dev_fwnode(&client->dev); ++ ts->psy = devm_power_supply_register(&client->dev, &apple_tristar_desc, &config); ++ if (IS_ERR(ts->psy)) ++ return dev_err_probe(&client->dev, PTR_ERR(ts->psy), "USB supply registration\n"); ++ ts->usb_notifier.notifier_call = apple_tristar_usb_event; ++ ret = usb_gadget_register_current_notifier(&ts->usb_notifier); ++ if (ret && ret != -EOPNOTSUPP) ++ return ret; ++ ts->notifier_registered = !ret; ++ ret = devm_add_action_or_reset(&client->dev, apple_tristar_stop, ts); ++ if (ret) ++ return ret; ++ /* Registered after stop, so teardown removes debugfs first. */ ++ apple_tristar_debugfs_init(ts); ++ mod_delayed_work(system_power_efficient_wq, &ts->work, 0); ++ dev_info(&client->dev, "CBTL1610 rev 0x8a: guarded Apple source detection, accessory policy %s\n", ++ READ_ONCE(auto_host) ? "on" : "off"); ++ return 0; ++} ++ ++static const struct of_device_id apple_tristar_of_match[] = { ++ { .compatible = "apple,j71t-cbtl1610-power" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, apple_tristar_of_match); ++ ++static struct i2c_driver apple_tristar_driver = { ++ .driver = { ++ .name = "apple-tristar-power", ++ .of_match_table = apple_tristar_of_match, ++ .pm = pm_sleep_ptr(&apple_tristar_pm_ops), ++ .dev_groups = apple_tristar_groups, ++ }, ++ .probe = apple_tristar_probe, ++ .shutdown = apple_tristar_shutdown, ++}; ++module_i2c_driver(apple_tristar_driver); ++MODULE_DESCRIPTION("Apple J71t Tristar source-current detection"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/apple_tristar_source.h b/drivers/power/supply/apple_tristar_source.h +new file mode 100644 +index 0000000000000000000000000000000000000000..086e995e50b0832d4d686c8ff991acb78e3b4e7c +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_source.h +@@ -0,0 +1,10 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++#ifndef APPLE_TRISTAR_SOURCE_H ++#define APPLE_TRISTAR_SOURCE_H ++ ++struct power_supply; ++ ++/* A physical source epoch, independent of USB suspend/reset notifications. */ ++int apple_tristar_attach_id(struct power_supply *psy, unsigned int *id); ++ ++#endif +diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c +index 01c9ec1116fa09146fa61796dce171ba6d32d483..c8dde7242fb360e7dcf0b7ee34b9060a2c648345 100644 +--- a/drivers/power/supply/bq27xxx_battery.c ++++ b/drivers/power/supply/bq27xxx_battery.c +@@ -783,6 +783,41 @@ static enum power_supply_property bq27545_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + }; + ++/* ++ * Cache-only transports use this BQ27545 subset so property requests never ++ * initiate bus transactions. The power-supply core exposes design charge ++ * only when valid static monitored-battery data is available. ++ */ ++static const enum power_supply_property bq27545_cache_props[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, ++ POWER_SUPPLY_PROP_TEMP, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CHARGE_NOW, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_POWER_AVG, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++}; ++ ++static bool bq27xxx_cache_has_prop(const struct bq27xxx_device_info *di, ++ enum power_supply_property property) ++{ ++ size_t i; ++ ++ for (i = 0; i < di->num_cache_properties; i++) ++ if (di->cache_properties[i] == property) ++ return true; ++ ++ return false; ++} ++ + static enum power_supply_property bq27421_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, +@@ -1127,7 +1162,8 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k + + mutex_lock(&bq27xxx_list_lock); + list_for_each_entry(di, &bq27xxx_battery_devices, list) +- mod_delayed_work(system_percpu_wq, &di->work, 0); ++ if (!di->cache_only) ++ mod_delayed_work(system_percpu_wq, &di->work, 0); + mutex_unlock(&bq27xxx_list_lock); + + return ret; +@@ -1630,6 +1666,15 @@ static inline int bq27xxx_battery_read_rc(struct bq27xxx_device_info *di, + return bq27xxx_battery_read_charge(di, BQ27XXX_REG_RC, val); + } + ++static int bq27xxx_battery_read_charge_now(struct bq27xxx_device_info *di, ++ union power_supply_propval *val) ++{ ++ if (di->cache_use_remaining_capacity) ++ return bq27xxx_battery_read_rc(di, val); ++ ++ return bq27xxx_battery_read_nac(di, val); ++} ++ + /* + * Return the battery Full Charge Capacity in µAh + * Or < 0 if something fails. +@@ -1778,7 +1823,7 @@ static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags) + { + if (di->opts & BQ27XXX_O_OTDC) + return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD); +- if (di->opts & BQ27XXX_O_UTOT) ++ if (di->opts & BQ27XXX_O_UTOT) + return flags & BQ27XXX_FLAG_OT; + + return false; +@@ -1852,6 +1897,45 @@ static bool bq27xxx_battery_is_full(struct bq27xxx_device_info *di, int flags) + return (flags & BQ27XXX_FLAG_FC); + } + ++static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di, ++ union power_supply_propval *val); ++static int bq27xxx_battery_pwr_avg(struct bq27xxx_device_info *di, ++ union power_supply_propval *val); ++ ++static void bq27xxx_cache_value(struct bq27xxx_cached_value *cached, int error, ++ union power_supply_propval *val) ++{ ++ if (!error) { ++ cached->error = 0; ++ cached->value = val->intval; ++ return; ++ } ++ ++ /* ++ * Keep the last good sample across a transient bus error instead of ++ * failing every read until the next refresh. -ENODATA is a genuine ++ * "no value" answer from the gauge and is always reported. ++ */ ++ if (error == -ENODATA || cached->error) ++ cached->error = error; ++} ++ ++static void bq27xxx_battery_init_cache(struct bq27xxx_reg_cache *cache) ++{ ++ memset(cache, 0, sizeof(*cache)); ++ cache->capacity = -ENODATA; ++ cache->flags = -ENODATA; ++ cache->current_now.error = -ENODATA; ++ cache->status.error = -ENODATA; ++ cache->time_to_empty_now.error = -ENODATA; ++ cache->voltage.error = -ENODATA; ++ cache->temperature.error = -ENODATA; ++ cache->charge_now.error = -ENODATA; ++ cache->charge_full.error = -ENODATA; ++ cache->cycle_count.error = -ENODATA; ++ cache->power_avg.error = -ENODATA; ++} ++ + /* + * Return the battery average current in µA and the status + * Note that current can be negative signed as well +@@ -1915,10 +1999,27 @@ static int bq27xxx_battery_current_and_status( + static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di) + { + union power_supply_propval status = di->last_status; +- struct bq27xxx_reg_cache cache = {0, }; +- bool has_singe_flag = di->opts & BQ27XXX_O_ZERO; ++ union power_supply_propval value; ++ struct bq27xxx_reg_cache cache = {}; ++ bool has_charge_full = false; ++ bool has_charge_now = false; ++ bool has_cycle_count = false; ++ bool has_power_avg = false; ++ bool has_tte = false; ++ bool single_flags = di->opts & BQ27XXX_O_ZERO; ++ bool changed; ++ int ret; ++ ++ if (di->cache_only) { ++ bq27xxx_battery_init_cache(&cache); ++ has_charge_full = bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CHARGE_FULL); ++ has_charge_now = bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CHARGE_NOW); ++ has_cycle_count = bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CYCLE_COUNT); ++ has_power_avg = bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_POWER_AVG); ++ has_tte = bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW); ++ } + +- cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); ++ cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, single_flags); + if (di->chip == BQ27000 && (cache.flags & 0xff) == 0xff) + cache.flags = -ENODEV; /* bq27000 hdq read error */ + if (cache.flags >= 0) { +@@ -1928,23 +2029,73 @@ static void bq27xxx_battery_update_unlocked(struct bq27xxx_device_info *di) + * On gauges with signed current reporting the current must be + * checked to detect charging <-> discharging status changes. + */ +- if (!(di->opts & BQ27XXX_O_ZERO)) ++ if (di->cache_only) { ++ if (cache.capacity >= 0) { ++ ret = bq27xxx_battery_current_and_status(di, &value, ++ &status, &cache); ++ bq27xxx_cache_value(&cache.current_now, ret, &value); ++ bq27xxx_cache_value(&cache.status, ret, &status); ++ ++ if (!ret) { ++ ret = bq27xxx_battery_voltage(di, &value); ++ bq27xxx_cache_value(&cache.voltage, ret, &value); ++ } ++ ++ if (!ret) { ++ ret = bq27xxx_battery_read_temperature(di, &value); ++ bq27xxx_cache_value(&cache.temperature, ret, &value); ++ } ++ ++ if (!ret && has_tte) { ++ ret = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE, ++ &value); ++ bq27xxx_cache_value(&cache.time_to_empty_now, ++ ret, &value); ++ } ++ ++ if (ret == -ENODATA) ++ ret = 0; ++ ++ if (!ret && has_charge_now) { ++ ret = bq27xxx_battery_read_charge_now(di, &value); ++ bq27xxx_cache_value(&cache.charge_now, ret, &value); ++ } ++ ++ if (!ret && has_charge_full) { ++ ret = bq27xxx_battery_read_fcc(di, &value); ++ bq27xxx_cache_value(&cache.charge_full, ret, &value); ++ } ++ ++ if (!ret && has_cycle_count) { ++ ret = bq27xxx_battery_read_cyct(di, &value); ++ bq27xxx_cache_value(&cache.cycle_count, ret, &value); ++ } ++ ++ if (!ret && has_power_avg) { ++ ret = bq27xxx_battery_pwr_avg(di, &value); ++ bq27xxx_cache_value(&cache.power_avg, ret, &value); ++ } ++ } ++ } else if (!(di->opts & BQ27XXX_O_ZERO)) { + bq27xxx_battery_current_and_status(di, NULL, &status, &cache); ++ } + } + +- if ((di->cache.capacity != cache.capacity) || +- (di->cache.flags != cache.flags) || +- (di->last_status.intval != status.intval)) { ++ changed = memcmp(&di->cache, &cache, sizeof(cache)) != 0; ++ if (changed) ++ di->cache = cache; ++ ++ if (di->bat && ++ (changed || di->last_status.intval != status.intval)) { + di->last_status.intval = status.intval; + power_supply_changed(di->bat); ++ } else { ++ di->last_status.intval = status.intval; + } + +- if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) +- di->cache = cache; +- + di->last_update = jiffies; + +- if (!di->removed && poll_interval > 0) ++ if (!di->removed && !di->cache_only && poll_interval > 0) + mod_delayed_work(system_percpu_wq, &di->work, poll_interval * HZ); + } + +@@ -1956,13 +2107,128 @@ void bq27xxx_battery_update(struct bq27xxx_device_info *di) + } + EXPORT_SYMBOL_GPL(bq27xxx_battery_update); + ++static void bq27xxx_battery_refresh_cache(struct bq27xxx_device_info *di) ++{ ++ union power_supply_propval current_now; ++ union power_supply_propval status; ++ union power_supply_propval temperature; ++ union power_supply_propval value; ++ union power_supply_propval voltage; ++ struct bq27xxx_reg_cache cache; ++ bool single_flags; ++ bool changed; ++ int capacity; ++ int flags; ++ int ret; ++ ++ mutex_lock(&di->lock); ++ if (di->removed) ++ goto out_unlock; ++ ++ cache = di->cache; ++ ++ single_flags = di->opts & BQ27XXX_O_ZERO; ++ flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, single_flags); ++ if (flags < 0) { ++ ret = flags; ++ goto out_error; ++ } ++ cache.flags = flags; ++ ++ capacity = bq27xxx_battery_read_soc(di); ++ if (capacity < 0) { ++ ret = capacity; ++ goto out_error; ++ } ++ cache.capacity = capacity; ++ ++ ret = bq27xxx_battery_current_and_status(di, ¤t_now, &status, &cache); ++ if (ret) ++ goto out_error; ++ bq27xxx_cache_value(&cache.current_now, 0, ¤t_now); ++ bq27xxx_cache_value(&cache.status, 0, &status); ++ ++ ret = bq27xxx_battery_voltage(di, &voltage); ++ if (ret) ++ goto out_error; ++ bq27xxx_cache_value(&cache.voltage, 0, &voltage); ++ ++ ret = bq27xxx_battery_read_temperature(di, &temperature); ++ if (ret) ++ goto out_error; ++ bq27xxx_cache_value(&cache.temperature, 0, &temperature); ++ /* Optional measurements do not determine safety-sample readiness. */ ++ di->cache_ready = true; ++ ++ if (bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW)) { ++ ret = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE, &value); ++ bq27xxx_cache_value(&cache.time_to_empty_now, ret, &value); ++ } ++ ++ if (bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CHARGE_NOW)) { ++ ret = bq27xxx_battery_read_charge_now(di, &value); ++ bq27xxx_cache_value(&cache.charge_now, ret, &value); ++ } ++ ++ if (bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CHARGE_FULL)) { ++ ret = bq27xxx_battery_read_fcc(di, &value); ++ bq27xxx_cache_value(&cache.charge_full, ret, &value); ++ } ++ ++ if (bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_CYCLE_COUNT)) { ++ ret = bq27xxx_battery_read_cyct(di, &value); ++ bq27xxx_cache_value(&cache.cycle_count, ret, &value); ++ } ++ ++ if (bq27xxx_cache_has_prop(di, POWER_SUPPLY_PROP_POWER_AVG)) { ++ ret = bq27xxx_battery_pwr_avg(di, &value); ++ bq27xxx_cache_value(&cache.power_avg, ret, &value); ++ } ++ ++ changed = memcmp(&di->cache, &cache, sizeof(cache)) != 0; ++ di->cache = cache; ++ di->last_status = status; ++ di->last_update = jiffies; ++ ++ if (changed) ++ power_supply_changed(di->bat); ++ goto out_unlock; ++ ++out_error: ++ cache.flags = ret; ++ cache.capacity = ret; ++ cache.current_now.error = ret; ++ cache.status.error = ret; ++ cache.voltage.error = ret; ++ cache.temperature.error = ret; ++ changed = memcmp(&di->cache, &cache, sizeof(cache)) != 0; ++ di->cache = cache; ++ di->last_update = jiffies; ++ if (di->cache_ready) ++ dev_warn_ratelimited(di->dev, "battery cache refresh failed: %d\n", ret); ++ else ++ dev_dbg(di->dev, "battery cache warming up: %d\n", ret); ++ if (changed) ++ power_supply_changed(di->bat); ++ ++out_unlock: ++ if (!di->removed && di->cache_refresh_ms) ++ queue_delayed_work(system_freezable_wq, &di->work, ++ msecs_to_jiffies(di->cache_ready ? ++ di->cache_refresh_ms : 1000)); ++ mutex_unlock(&di->lock); ++} ++ + static void bq27xxx_battery_poll(struct work_struct *work) + { + struct bq27xxx_device_info *di = + container_of(work, struct bq27xxx_device_info, + work.work); + +- bq27xxx_battery_update(di); ++ if (di->cache_only) ++ bq27xxx_battery_refresh_cache(di); ++ else ++ bq27xxx_battery_update(di); + } + + /* +@@ -2118,6 +2384,110 @@ static int bq27xxx_simple_value(int value, + return 0; + } + ++static int bq27xxx_cached_value(const struct bq27xxx_cached_value *cached, ++ union power_supply_propval *val) ++{ ++ if (cached->error) ++ return cached->error; ++ ++ val->intval = cached->value; ++ return 0; ++} ++ ++/* ++ * Optional measurements must not abort a uevent or attribute walk when the ++ * cache holds no sample for them. ++ */ ++static int bq27xxx_cached_optional_value(const struct bq27xxx_cached_value *cached, ++ union power_supply_propval *val) ++{ ++ return bq27xxx_cached_value(cached, val) ? -ENODATA : 0; ++} ++ ++static int bq27xxx_battery_get_cached_property(struct power_supply *psy, ++ struct bq27xxx_device_info *di, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ int ret; ++ ++ mutex_lock(&di->lock); ++ /* Presence is also unknown until the first complete safety sample. */ ++ if (!di->cache_ready) { ++ ret = -ENODATA; ++ goto out_unlock; ++ } ++ if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0) { ++ ret = di->cache.flags; ++ goto out_unlock; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_STATUS: ++ ret = bq27xxx_cached_value(&di->cache.status, val); ++ break; ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ ret = bq27xxx_cached_value(&di->cache.voltage, val); ++ break; ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = di->cache.flags < 0 ? 0 : 1; ++ ret = 0; ++ break; ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ ret = bq27xxx_cached_value(&di->cache.current_now, val); ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY: ++ ret = bq27xxx_simple_value(di->cache.capacity, val); ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ ret = bq27xxx_battery_capacity_level(di, val); ++ break; ++ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: ++ ret = bq27xxx_cached_optional_value(&di->cache.time_to_empty_now, val); ++ break; ++ case POWER_SUPPLY_PROP_TEMP: ++ ret = bq27xxx_cached_value(&di->cache.temperature, val); ++ break; ++ case POWER_SUPPLY_PROP_TECHNOLOGY: ++ if (di->opts & BQ27XXX_O_MUL_CHEM) ++ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; ++ else ++ val->intval = POWER_SUPPLY_TECHNOLOGY_LION; ++ ret = 0; ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_NOW: ++ ret = bq27xxx_cached_optional_value(&di->cache.charge_now, val); ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_FULL: ++ ret = bq27xxx_cached_optional_value(&di->cache.charge_full, val); ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ++ ret = power_supply_battery_info_get_prop(psy->battery_info, ++ psp, val); ++ break; ++ case POWER_SUPPLY_PROP_CYCLE_COUNT: ++ ret = bq27xxx_cached_optional_value(&di->cache.cycle_count, val); ++ break; ++ case POWER_SUPPLY_PROP_POWER_AVG: ++ ret = bq27xxx_cached_optional_value(&di->cache.power_avg, val); ++ break; ++ case POWER_SUPPLY_PROP_HEALTH: ++ ret = bq27xxx_battery_read_health(di, val); ++ break; ++ case POWER_SUPPLY_PROP_MANUFACTURER: ++ val->strval = BQ27XXX_MANUFACTURER; ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++out_unlock: ++ mutex_unlock(&di->lock); ++ return ret; ++} ++ + static int bq27xxx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +@@ -2125,6 +2495,9 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, + int ret = 0; + struct bq27xxx_device_info *di = power_supply_get_drvdata(psy); + ++ if (di->cache_only) ++ return bq27xxx_battery_get_cached_property(psy, di, psp, val); ++ + mutex_lock(&di->lock); + if (time_is_before_jiffies(di->last_update + 5 * HZ)) + bq27xxx_battery_update_unlocked(di); +@@ -2244,29 +2617,54 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) + di->dm_regs = bq27xxx_chip_data[di->chip].dm_regs; + di->opts = bq27xxx_chip_data[di->chip].opts; + ++ if (di->cache_only && di->chip != BQ27545) ++ return dev_err_probe(di->dev, -EINVAL, ++ "cache-only mode only supports BQ27545\n"); ++ if (di->cache_only && !di->cache_properties) { ++ di->cache_properties = bq27545_cache_props; ++ di->num_cache_properties = ARRAY_SIZE(bq27545_cache_props); ++ } ++ if (di->cache_only) { ++ bq27xxx_battery_init_cache(&di->cache); ++ di->cache_ready = false; ++ } ++ + psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = di->name; + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; +- psy_desc->properties = bq27xxx_chip_data[di->chip].props; +- psy_desc->num_properties = bq27xxx_chip_data[di->chip].props_size; ++ if (di->cache_only) { ++ psy_desc->properties = di->cache_properties; ++ psy_desc->num_properties = di->num_cache_properties; ++ } else { ++ psy_desc->properties = bq27xxx_chip_data[di->chip].props; ++ psy_desc->num_properties = bq27xxx_chip_data[di->chip].props_size; ++ } + psy_desc->get_property = bq27xxx_battery_get_property; +- psy_desc->external_power_changed = bq27xxx_external_power_changed; ++ if (!di->cache_only) ++ psy_desc->external_power_changed = bq27xxx_external_power_changed; + + di->bat = devm_power_supply_register(di->dev, psy_desc, &psy_cfg); + if (IS_ERR(di->bat)) + return dev_err_probe(di->dev, PTR_ERR(di->bat), + "failed to register battery\n"); + +- bq27xxx_battery_settings(di); +- bq27xxx_battery_update(di); ++ if (!di->cache_only) { ++ bq27xxx_battery_settings(di); ++ bq27xxx_battery_update(di); ++ } + + mutex_lock(&bq27xxx_list_lock); + list_add(&di->list, &bq27xxx_battery_devices); + mutex_unlock(&bq27xxx_list_lock); + ++ if (di->cache_only && di->cache_refresh_ms) { ++ /* Populate asynchronously so a transient HDQ error can recover. */ ++ queue_delayed_work(system_freezable_wq, &di->work, 0); ++ } ++ + return 0; + } + EXPORT_SYMBOL_GPL(bq27xxx_battery_setup); +@@ -2299,7 +2697,10 @@ static int bq27xxx_battery_resume(struct device *dev) + { + struct bq27xxx_device_info *di = dev_get_drvdata(dev); + +- schedule_delayed_work(&di->work, 0); ++ if (di->cache_only && di->cache_refresh_ms) ++ queue_delayed_work(system_freezable_wq, &di->work, 0); ++ else if (!di->cache_only) ++ schedule_delayed_work(&di->work, 0); + return 0; + } + #endif /* CONFIG_PM_SLEEP */ +diff --git a/drivers/power/supply/bq27xxx_battery_hdq.c b/drivers/power/supply/bq27xxx_battery_hdq.c +index 922759ab2e045472920d158025b340c8d822f824..888b443ee40b378e7ca24644935529d295601010 100644 +--- a/drivers/power/supply/bq27xxx_battery_hdq.c ++++ b/drivers/power/supply/bq27xxx_battery_hdq.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -25,16 +26,33 @@ static int F_ID; + module_param(F_ID, int, S_IRUSR); + MODULE_PARM_DESC(F_ID, "1-wire slave FID for BQ27xxx device"); + ++static const enum power_supply_property bq27545_d111_cache_props[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_TEMP, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CHARGE_NOW, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++}; ++ + static int w1_bq27000_read(struct w1_slave *sl, unsigned int reg) + { + u8 val; ++ int ret; + + mutex_lock(&sl->master->bus_mutex); + w1_write_8(sl->master, HDQ_CMD_READ | reg); +- val = w1_read_8(sl->master); ++ ret = w1_read_block(sl->master, &val, 1); + mutex_unlock(&sl->master->bus_mutex); + +- return val; ++ return ret == 1 ? val : -EIO; + } + + static int bq27xxx_battery_hdq_read(struct bq27xxx_device_info *di, u8 reg, +@@ -74,27 +92,50 @@ static int bq27xxx_battery_hdq_read(struct bq27xxx_device_info *di, u8 reg, + + static int bq27xxx_battery_hdq_add_slave(struct w1_slave *sl) + { ++ const struct of_device_id *match; + struct bq27xxx_device_info *di; ++ int ret; + + di = devm_kzalloc(&sl->dev, sizeof(*di), GFP_KERNEL); + if (!di) + return -ENOMEM; + +- dev_set_drvdata(&sl->dev, di); +- + di->dev = &sl->dev; +- di->chip = BQ27000; +- di->name = "bq27000-battery"; ++ match = of_match_node(sl->family->of_match_table, sl->dev.of_node); ++ if (match) { ++ di->chip = (uintptr_t)match->data; ++ di->name = "battery"; ++ di->cache_only = true; ++ di->cache_refresh_ms = 30000; ++ if (of_device_is_compatible(sl->dev.of_node, ++ "apple,d111-bq27545")) { ++ di->cache_properties = bq27545_d111_cache_props; ++ di->num_cache_properties = ++ ARRAY_SIZE(bq27545_d111_cache_props); ++ di->cache_use_remaining_capacity = true; ++ } ++ } else { ++ di->chip = BQ27000; ++ di->name = "bq27000-battery"; ++ } + di->bus.read = bq27xxx_battery_hdq_read; + +- return bq27xxx_battery_setup(di); ++ ret = bq27xxx_battery_setup(di); ++ if (ret) ++ return ret; ++ ++ /* The W1 core can retain the slave after a failed add callback. */ ++ dev_set_drvdata(&sl->dev, di); ++ ++ return 0; + } + + static void bq27xxx_battery_hdq_remove_slave(struct w1_slave *sl) + { + struct bq27xxx_device_info *di = dev_get_drvdata(&sl->dev); + +- bq27xxx_battery_teardown(di); ++ if (di) ++ bq27xxx_battery_teardown(di); + } + + static const struct w1_family_ops bq27xxx_battery_hdq_fops = { +@@ -102,9 +143,19 @@ static const struct w1_family_ops bq27xxx_battery_hdq_fops = { + .remove_slave = bq27xxx_battery_hdq_remove_slave, + }; + ++static const struct of_device_id bq27xxx_battery_hdq_of_match[] = { ++ { .compatible = "apple,d111-bq27545", .data = (void *)BQ27545 }, ++ /* J71t ADT says bq27540; validate this J172 register-map profile. */ ++ { .compatible = "apple,j71t-bq27540", .data = (void *)BQ27545 }, ++ { .compatible = "ti,bq27545-hdq", .data = (void *)BQ27545 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, bq27xxx_battery_hdq_of_match); ++ + static struct w1_family bq27xxx_battery_hdq_family = { + .fid = W1_FAMILY_BQ27000, + .fops = &bq27xxx_battery_hdq_fops, ++ .of_match_table = bq27xxx_battery_hdq_of_match, + }; + + static int __init bq27xxx_battery_hdq_init(void) +diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c +index 00d8bc98d5887e0a6f7f7178c129cc638ffa71e2..85caf3305e35f2424db8593b3b1d35e1ad2050bf 100644 +--- a/drivers/power/supply/power_supply_core.c ++++ b/drivers/power/supply/power_supply_core.c +@@ -1653,6 +1653,9 @@ static int power_supply_read_temp(struct thermal_zone_device *tzd, + WARN_ON(tzd == NULL); + psy = thermal_zone_device_priv(tzd); + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); ++ /* The thermal core retries EAGAIN quietly instead of logging no data. */ ++ if (ret == -ENODATA) ++ return -EAGAIN; + if (ret) + return ret; + +diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig +index 810eeccedfbab29f001720d1711526e3a13a7710..c0d6b3ad6cd1175783a12176e09bc7abe6e0f7f6 100644 +--- a/drivers/thermal/Kconfig ++++ b/drivers/thermal/Kconfig +@@ -530,4 +530,13 @@ config LOONGSON2_THERMAL + is higher than the high temperature threshold or lower than the low + temperature threshold, the interrupt will occur. + ++config APPLE_S8003_THERMAL ++ tristate "Apple S8003 on-die temperature sensors" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF && THERMAL_OF ++ depends on HWMON ++ help ++ Validated SOC0/SOC1/TCC2 zones and six-channel raw diagnostics on A9 ++ S8003. Preserves bootloader trim and hardware SoCHOT protections. ++ + endif +diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile +index 3b249195c088efd453efcc92a95c2241d6f4d882..22d434ed70fee64780295b13cd90441cd5c8fb75 100644 +--- a/drivers/thermal/Makefile ++++ b/drivers/thermal/Makefile +@@ -70,3 +70,5 @@ obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o + obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o + obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o + obj-$(CONFIG_THERMAL_CORE_TESTING) += testing/ ++ ++obj-$(CONFIG_APPLE_S8003_THERMAL) += apple-s8003-thermal.o +diff --git a/drivers/thermal/apple-s8003-thermal.c b/drivers/thermal/apple-s8003-thermal.c +new file mode 100644 +index 0000000000000000000000000000000000000000..a2ed849d1f62bef030562f9027b621a38202e137 +--- /dev/null ++++ b/drivers/thermal/apple-s8003-thermal.c +@@ -0,0 +1,367 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * S8003 on-die sensors, recovered from AppleT700XTempSensor (j71t 20H350). ++ * The hardware reports calibrated signed Q6 Celsius. Preserve iBoot's trim, ++ * CTL1/2, alarm and SoCHOT configuration; never copy another chip's fuses. ++ * See docs/power/overclock-round2.md in omarchy-ipad5 for evidence. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define A9_SENSORS 6 ++#define A9_SAMPLE_MS 100 ++#define A9_STALE_MS 500 ++/* integ31 live readings validate these three; retain the others as raw ++ * diagnostics until their corrected mapping/calibration is checked on-device. ++ */ ++#define A9_TRUSTED_MASK (BIT(0) | BIT(1) | BIT(5)) ++ ++struct a9_thermal; ++struct a9_sensor { ++ struct a9_thermal *parent; ++ unsigned int id; ++ u32 bk, count, maximum, ctl; ++ int temp, error; ++ unsigned long sampled; ++}; ++ ++struct a9_thermal { ++ void __iomem *base[2]; ++ struct mutex lock; ++ struct delayed_work work; ++ struct a9_sensor sensor[A9_SENSORS]; ++ struct dentry *debugfs; ++ bool profile_ready; ++}; ++ ++static const char * const a9_names[A9_SENSORS] = { ++ "apple-a9-soc0", "apple-a9-soc1", "apple-a9-soc2", ++ "apple-a9-tcc0", "apple-a9-tcc1", "apple-a9-tcc2", ++}; ++ ++static const u16 a9_offsets[A9_SENSORS] = { 0, 0x100, 0x200, 0, 0x40, 0x200 }; ++static const u16 a9_clear_offsets[A9_SENSORS] = { 4, 0x104, 0x204, 0x408, 0x438, 0x468 }; ++ ++static bool a9_sensor_trusted(unsigned int id) ++{ ++ return id < A9_SENSORS && (A9_TRUSTED_MASK & BIT(id)); ++} ++ ++static void __iomem *a9_sensor_base(struct a9_sensor *s) ++{ ++ return s->parent->base[s->id / 3] + a9_offsets[s->id]; ++} ++ ++static void a9_sample(struct a9_sensor *s) ++{ ++ void __iomem *base = a9_sensor_base(s); ++ bool tcc = s->id >= 3; ++ int q6; ++ ++ /* iOS timer: freeze accumulation, read, then start the next interval. */ ++ writel(BIT(3), s->parent->base[s->id / 3] + a9_clear_offsets[s->id]); ++ s->ctl = readl(base); ++ s->bk = readl(base + (tcc ? 0x20 : 0x18)); ++ s->count = readl(base + (tcc ? 0x38 : 0x24)); ++ s->maximum = readl(base + (tcc ? 0x28 : 0x1c)); ++ writel(BIT(3), base); ++ ++ s->error = -EAGAIN; ++ if (!(s->ctl & BIT(0)) || !(s->bk & BIT(31)) || ++ !s->count || (s->count & BIT(31))) ++ return; ++ q6 = sign_extend32(s->bk, tcc ? 15 : 14); ++ s->temp = q6 * 1000 / 64; ++ /* Reject impossible codes rather than replacing them with iOS's 30 C. */ ++ if (s->temp < -40000 || s->temp > 150000) { ++ s->error = -ERANGE; ++ return; ++ } ++ s->sampled = jiffies; ++ s->error = 0; ++} ++ ++static void a9_sample_work(struct work_struct *work) ++{ ++ struct a9_thermal *a = container_of(to_delayed_work(work), ++ struct a9_thermal, work); ++ int i; ++ ++ mutex_lock(&a->lock); ++ for (i = 0; i < A9_SENSORS; i++) ++ a9_sample(&a->sensor[i]); ++ mutex_unlock(&a->lock); ++ mod_delayed_work(system_wq, &a->work, msecs_to_jiffies(A9_SAMPLE_MS)); ++} ++ ++static int a9_sensor_temp(struct a9_sensor *s, int *temp) ++{ ++ int ret; ++ ++ mutex_lock(&s->parent->lock); ++ ret = a9_sensor_trusted(s->id) ? s->error : -ENODATA; ++ if (!ret && time_after(jiffies, s->sampled + msecs_to_jiffies(A9_STALE_MS))) ++ ret = -ETIMEDOUT; ++ if (!ret) ++ *temp = s->temp; ++ mutex_unlock(&s->parent->lock); ++ return ret; ++} ++ ++static int a9_get_temp(struct thermal_zone_device *tz, int *temp) ++{ ++ return a9_sensor_temp(thermal_zone_device_priv(tz), temp); ++} ++ ++static void a9_thermal_hot(struct thermal_zone_device *tz) ++{ ++ struct a9_sensor *s = thermal_zone_device_priv(tz); ++ ++ if (a9_sensor_trusted(s->id)) ++ apple_a9_thermal_revert(); ++} ++ ++static void a9_thermal_critical(struct thermal_zone_device *tz) ++{ ++ struct a9_sensor *s = thermal_zone_device_priv(tz); ++ ++ if (!a9_sensor_trusted(s->id)) ++ return; ++ /* Synchronous bounded fallback first, then the normal shutdown path. */ ++ apple_a9_thermal_revert(); ++ thermal_zone_device_critical(tz); ++} ++ ++static const struct thermal_zone_device_ops a9_thermal_ops = { ++ .get_temp = a9_get_temp, ++ .hot = a9_thermal_hot, ++ .critical = a9_thermal_critical, ++}; ++ ++struct a9_trip_profile { ++ struct thermal_zone_device *tz; ++ struct apple_a9_thermal_profile profile; ++ unsigned int seen; ++ bool apply; ++}; ++ ++/* thermal_zone_for_each_trip holds the zone lock. Use the core setter so ++ * trip ordering/thresholds and the existing cpufreq cooling binding stay valid. ++ * Validate the unmodified DT first; never raise an unexpected critical trip. ++ */ ++static int a9_profile_trip(struct thermal_trip *trip, void *data) ++{ ++ struct a9_trip_profile *p = data; ++ unsigned int bit, stock, selected; ++ ++ switch (trip->type) { ++ case THERMAL_TRIP_PASSIVE: ++ bit = BIT(0); ++ stock = 80000; ++ selected = p->profile.passive; ++ break; ++ case THERMAL_TRIP_HOT: ++ bit = BIT(1); ++ stock = 90000; ++ selected = p->profile.hot; ++ break; ++ case THERMAL_TRIP_CRITICAL: ++ bit = BIT(2); ++ stock = 100000; ++ selected = stock; ++ break; ++ default: ++ return -EINVAL; ++ } ++ if (!p->apply) { ++ if ((p->seen & bit) || trip->temperature != stock) ++ return -EINVAL; ++ p->seen |= bit; ++ } else if (trip->type != THERMAL_TRIP_CRITICAL) { ++ thermal_zone_set_trip_temp(p->tz, trip, selected); ++ } ++ return 0; ++} ++ ++static int a9_apply_profile(struct thermal_zone_device *tz) ++{ ++ struct a9_trip_profile p = { ++ .tz = tz, .profile = apple_a9_get_thermal_profile(), ++ }; ++ int ret; ++ ++ if (!p.profile.tuning) ++ return 0; ++ ret = thermal_zone_for_each_trip(tz, a9_profile_trip, &p); ++ if (ret || p.seen != (BIT(0) | BIT(1) | BIT(2))) ++ return ret ?: -EINVAL; ++ p.apply = true; ++ ret = thermal_zone_for_each_trip(tz, a9_profile_trip, &p); ++ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); ++ return ret; ++} ++ ++static umode_t a9_hwmon_visible(const void *data, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ return type == hwmon_temp ? 0444 : 0; ++} ++ ++static int a9_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *value) ++{ ++ struct a9_thermal *a = dev_get_drvdata(dev); ++ int temp, ret; ++ ++ ret = a9_sensor_temp(&a->sensor[channel], &temp); ++ if (attr == hwmon_temp_fault) { ++ *value = !!ret; ++ return 0; ++ } ++ if (ret) ++ return ret; ++ *value = temp; ++ return 0; ++} ++ ++static int a9_hwmon_label(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ *str = a9_names[channel]; ++ return 0; ++} ++ ++static const struct hwmon_ops a9_hwmon_ops = { ++ .is_visible = a9_hwmon_visible, ++ .read = a9_hwmon_read, ++ .read_string = a9_hwmon_label, ++}; ++ ++#define A9_HWMON (HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_FAULT) ++static const struct hwmon_channel_info * const a9_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(temp, A9_HWMON, A9_HWMON, A9_HWMON, ++ A9_HWMON, A9_HWMON, A9_HWMON), NULL, ++}; ++static const struct hwmon_chip_info a9_hwmon_chip = { ++ .ops = &a9_hwmon_ops, ++ .info = a9_hwmon_info, ++}; ++ ++static int a9_thermal_snapshot_show(struct seq_file *seq, void *unused) ++{ ++ struct a9_thermal *a = seq->private; ++ struct apple_a9_thermal_profile p = apple_a9_get_thermal_profile(); ++ int i; ++ ++ seq_printf(seq, "format=3 units=milliC trim=boot-preserved sample_ms=100 trusted_mask=%02x thermal_profile_format=1 profile_ready=%u profile=%s profile_tuning=%u passive_millic=%u hot_millic=%u critical_millic=%u arm_millic=%u cooldown_millic=%u\n", ++ (unsigned int)A9_TRUSTED_MASK, READ_ONCE(a->profile_ready), ++ apple_a9_thermal_profile_name(p), p.tuning, p.passive, p.hot, ++ p.critical, p.arm, p.cooldown); ++ mutex_lock(&a->lock); ++ for (i = 0; i < A9_SENSORS; i++) { ++ struct a9_sensor *s = &a->sensor[i]; ++ void __iomem *base = a9_sensor_base(s); ++ bool tcc = i >= 3; ++ ++ seq_printf(seq, "%s bk=%08x count=%08x max=%08x ctl=%08x temp=%d error=%d age_ms=%u trusted=%u ctl_offset=%03x ctl1=%08x ctl2=%08x params=%08x\n", ++ a9_names[i], s->bk, s->count, s->maximum, s->ctl, ++ s->temp, s->error, jiffies_to_msecs(jiffies - s->sampled), ++ a9_sensor_trusted(i), a9_offsets[i], readl(base + 8), ++ readl(base + (tcc ? 16 : 12)), tcc ? readl(base + 24) : 0); ++ } ++ /* LLB 20H350 table at 0x1803b1af8 initializes these shared TCC ++ * coefficients. The old uniform-stride bug wrote CTL bits into +100. ++ * Read only: a normal LLB boot must restore calibration after integ31. ++ */ ++ for (i = 0; i < 3; i++) { ++ void __iomem *base = a->base[1] + 0x100 + i * 0x18; ++ ++ seq_printf(seq, "tcc-coeff%u offset=%03x a=%08x b=%08x c=%08x\n", ++ i, 0x100 + i * 0x18, readl(base), readl(base + 8), readl(base + 16)); ++ } ++ mutex_unlock(&a->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(a9_thermal_snapshot); ++ ++static void a9_thermal_stop(void *data) ++{ ++ struct a9_thermal *a = data; ++ ++ cancel_delayed_work_sync(&a->work); ++ debugfs_remove_recursive(a->debugfs); ++ /* Keep sensors and the boot hardware protections running on unload. */ ++} ++ ++static int a9_thermal_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct a9_thermal *a; ++ struct device *hwmon; ++ int i, ret; ++ ++ a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL); ++ if (!a) ++ return -ENOMEM; ++ mutex_init(&a->lock); ++ for (i = 0; i < 2; i++) { ++ a->base[i] = devm_platform_ioremap_resource(pdev, i); ++ if (IS_ERR(a->base[i])) ++ return PTR_ERR(a->base[i]); ++ } ++ INIT_DELAYED_WORK(&a->work, a9_sample_work); ++ for (i = 0; i < A9_SENSORS; i++) { ++ a->sensor[i].parent = a; ++ a->sensor[i].id = i; ++ a->sensor[i].error = -EAGAIN; ++ /* iOS start: CTL0_SET=1. Do not alter CTL1/2 or trim params. */ ++ writel(BIT(0) | BIT(3), a9_sensor_base(&a->sensor[i])); ++ } ++ a->debugfs = debugfs_create_dir("a9_thermal", NULL); ++ debugfs_create_file("snapshot", 0400, a->debugfs, a, &a9_thermal_snapshot_fops); ++ ret = devm_add_action_or_reset(dev, a9_thermal_stop, a); ++ if (ret) ++ return ret; ++ mod_delayed_work(system_wq, &a->work, msecs_to_jiffies(A9_SAMPLE_MS)); ++ for (i = 0; i < A9_SENSORS; i++) { ++ struct thermal_zone_device *tz; ++ ++ if (!a9_sensor_trusted(i)) ++ continue; ++ tz = devm_thermal_of_zone_register(dev, i, &a->sensor[i], &a9_thermal_ops); ++ if (IS_ERR(tz)) ++ return dev_err_probe(dev, PTR_ERR(tz), "thermal zone %d\n", i); ++ ret = a9_apply_profile(tz); ++ if (ret) ++ return dev_err_probe(dev, ret, "thermal profile zone %d\n", i); ++ } ++ WRITE_ONCE(a->profile_ready, true); ++ hwmon = devm_hwmon_device_register_with_info(dev, "apple_a9", a, &a9_hwmon_chip, NULL); ++ return PTR_ERR_OR_ZERO(hwmon); ++} ++ ++static const struct of_device_id a9_thermal_match[] = { ++ { .compatible = "apple,s8003-thermal" }, {} ++}; ++MODULE_DEVICE_TABLE(of, a9_thermal_match); ++ ++static struct platform_driver a9_thermal_driver = { ++ .probe = a9_thermal_probe, ++ .driver = { ++ .name = "apple-s8003-thermal", ++ .of_match_table = a9_thermal_match, ++ }, ++}; ++module_platform_driver(a9_thermal_driver); ++MODULE_DESCRIPTION("Apple S8003 validated die thermals and raw channel diagnostics"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig +index e6049a75b35b8f099b779104ed9302147e001df8..09fda2f69c08d95c665f2fc73348d8e86c99895a 100644 +--- a/drivers/w1/masters/Kconfig ++++ b/drivers/w1/masters/Kconfig +@@ -79,14 +79,14 @@ config W1_MASTER_SGI + will be called sgi_w1. + + config W1_MASTER_UART +- tristate "UART 1-wire driver" ++ tristate "UART 1-wire and HDQ driver" + depends on SERIAL_DEV_BUS ++ select MUX_CORE + help +- Say Y here if you want to communicate with your 1-wire devices using +- UART interface. ++ Say Y here if you want to communicate with your 1-wire or HDQ devices ++ using a UART interface. + + This support is also available as a module. If so, the module + will be called w1-uart. + + endmenu +- +diff --git a/drivers/w1/masters/w1-uart.c b/drivers/w1/masters/w1-uart.c +index c87eea3478067894753c83b15b13846d332ba758..f29fa08a2e86bc7d491e6d660c5a80a573d50129 100644 +--- a/drivers/w1/masters/w1-uart.c ++++ b/drivers/w1/masters/w1-uart.c +@@ -17,8 +17,10 @@ + #include + #include + #include ++#include + #include + #include ++#include + #include + + /* UART packet contains start and stop bit */ +@@ -27,6 +29,30 @@ + /* Timeout to wait for completion of serdev-receive */ + #define W1_UART_TIMEOUT msecs_to_jiffies(500) + ++#define W1_UART_HDQ_BAUDRATE 57600 ++/* A transaction takes a few ms; do not hold the bus for long on a miss. */ ++#define W1_UART_HDQ_TIMEOUT msecs_to_jiffies(100) ++#define W1_UART_HDQ_TRIES 3 ++#define W1_UART_HDQ_BREAK_MIN_US 250 ++#define W1_UART_HDQ_BREAK_MAX_US 500 ++#define W1_UART_HDQ_RECOVERY_MIN_US 150 ++#define W1_UART_HDQ_RECOVERY_MAX_US 500 ++#define W1_UART_HDQ_BITS_PER_BYTE 8 ++#define W1_UART_HDQ_RX_BITS 16 ++#define W1_UART_HDQ_ONE 0xfe ++#define W1_UART_HDQ_ZERO 0xc0 ++#define W1_UART_HDQ_RX_ONE_MIN 0xf0 ++#define W1_UART_HDQ_FAMILY 0x01 ++ ++enum w1_uart_mode { ++ W1_UART_MODE_1WIRE, ++ W1_UART_MODE_HDQ, ++}; ++ ++struct w1_uart_variant { ++ enum w1_uart_mode mode; ++}; ++ + /** + * struct w1_uart_config - configuration for 1-Wire operation + * @baudrate: baud-rate returned from serdev +@@ -43,6 +69,8 @@ struct w1_uart_config { + * struct w1_uart_device - 1-Wire UART device structure + * @serdev: serial device + * @bus: w1-bus master ++ * @mode: selected 1-Wire or HDQ protocol ++ * @hdq_mux: optional mux state routing HDQ to the application processor + * @cfg_reset: config for 1-Wire reset + * @cfg_touch_0: config for 1-Wire write-0 cycle + * @cfg_touch_1: config for 1-Wire write-1 and read cycle +@@ -50,10 +78,18 @@ struct w1_uart_config { + * @rx_mutex: mutex to protect rx_err and rx_byte + * @rx_err: indicates an error in serdev-receive + * @rx_byte: result byte from serdev-receive ++ * @hdq_rx_lock: protects HDQ receive state from the UART callback ++ * @hdq_command: encoded HDQ command and expected UART echo ++ * @hdq_response: decoded HDQ response byte ++ * @hdq_rx_bit: number of accepted echo and response symbols ++ * @hdq_receiving: whether an HDQ transaction is receiving symbols ++ * @hdq_response_valid: whether @hdq_response is ready for the W1 client + */ + struct w1_uart_device { + struct serdev_device *serdev; + struct w1_bus_master bus; ++ enum w1_uart_mode mode; ++ struct mux_state *hdq_mux; + + struct w1_uart_config cfg_reset; + struct w1_uart_config cfg_touch_0; +@@ -67,6 +103,13 @@ struct w1_uart_device { + struct mutex rx_mutex; + int rx_err; + u8 rx_byte; ++ ++ spinlock_t hdq_rx_lock; /* Protects the HDQ receive state. */ ++ u8 hdq_command[W1_UART_HDQ_BITS_PER_BYTE]; ++ u8 hdq_response; ++ u8 hdq_rx_bit; ++ bool hdq_receiving; ++ bool hdq_response_valid; + }; + + /** +@@ -228,6 +271,26 @@ static int w1_uart_serdev_open(struct w1_uart_device *w1dev) + return ret; + } + ++ serdev_device_set_flow_control(serdev, false); ++ ++ if (w1dev->mode == W1_UART_MODE_HDQ) { ++ unsigned int baudrate; ++ ++ baudrate = serdev_device_set_baudrate(serdev, ++ W1_UART_HDQ_BAUDRATE); ++ if (baudrate != W1_UART_HDQ_BAUDRATE) ++ return dev_err_probe(dev, -EINVAL, ++ "failed to set %u baud (got %u)\n", ++ W1_UART_HDQ_BAUDRATE, baudrate); ++ ++ ret = serdev_device_set_stopbits(serdev, SERDEV_STOPBITS_2); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to set two stop bits\n"); ++ ++ return 0; ++ } ++ + ret = w1_uart_set_config_reset(w1dev); + if (ret < 0) { + dev_err(dev, "config for reset failed\n"); +@@ -246,11 +309,134 @@ static int w1_uart_serdev_open(struct w1_uart_device *w1dev) + return ret; + } + +- serdev_device_set_flow_control(serdev, false); ++ return 0; ++} ++ ++static void w1_uart_hdq_cancel_receive(struct w1_uart_device *w1dev) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&w1dev->hdq_rx_lock, flags); ++ w1dev->hdq_receiving = false; ++ w1dev->hdq_response_valid = false; ++ spin_unlock_irqrestore(&w1dev->hdq_rx_lock, flags); ++} ++ ++static int w1_uart_hdq_break(struct w1_uart_device *w1dev) ++{ ++ struct serdev_device *serdev = w1dev->serdev; ++ int ret; ++ ++ ret = serdev_device_break_ctl(serdev, -1); ++ if (ret) ++ return ret; ++ ++ usleep_range(W1_UART_HDQ_BREAK_MIN_US, W1_UART_HDQ_BREAK_MAX_US); ++ ret = serdev_device_break_ctl(serdev, 0); ++ if (ret) ++ return ret; ++ ++ usleep_range(W1_UART_HDQ_RECOVERY_MIN_US, ++ W1_UART_HDQ_RECOVERY_MAX_US); + + return 0; + } + ++/* ++ * One break + command + response cycle. A corrupted echo symbol is never ++ * accepted by the receive callback, so the response then never completes ++ * and the cycle ends with -ETIMEDOUT. ++ */ ++static int w1_uart_hdq_xfer_once(struct w1_uart_device *w1dev) ++{ ++ struct serdev_device *serdev = w1dev->serdev; ++ unsigned long flags; ++ unsigned long timeout; ++ ssize_t written; ++ int ret; ++ ++ ret = w1_uart_hdq_break(w1dev); ++ if (ret) ++ return ret; ++ ++ reinit_completion(&w1dev->rx_byte_received); ++ spin_lock_irqsave(&w1dev->hdq_rx_lock, flags); ++ w1dev->hdq_response = 0; ++ w1dev->hdq_rx_bit = 0; ++ w1dev->hdq_receiving = true; ++ w1dev->hdq_response_valid = false; ++ spin_unlock_irqrestore(&w1dev->hdq_rx_lock, flags); ++ ++ written = serdev_device_write(serdev, w1dev->hdq_command, ++ sizeof(w1dev->hdq_command), HZ); ++ if (written < 0) { ++ ret = written; ++ goto cancel_receive; ++ } ++ if (written != sizeof(w1dev->hdq_command)) { ++ ret = -EIO; ++ goto cancel_receive; ++ } ++ ++ serdev_device_wait_until_sent(serdev, HZ); ++ timeout = wait_for_completion_timeout(&w1dev->rx_byte_received, ++ W1_UART_HDQ_TIMEOUT); ++ if (!timeout) { ++ ret = -ETIMEDOUT; ++ goto cancel_receive; ++ } ++ ++ return 0; ++ ++cancel_receive: ++ w1_uart_hdq_cancel_receive(w1dev); ++ ++ return ret; ++} ++ ++static int w1_uart_hdq_xfer(struct w1_uart_device *w1dev, u8 command) ++{ ++ unsigned int bit; ++ unsigned int try; ++ int ret; ++ int deselect_ret; ++ ++ w1_uart_hdq_cancel_receive(w1dev); ++ ++ if (w1dev->hdq_mux) { ++ ret = mux_state_select(w1dev->hdq_mux); ++ if (ret) ++ return ret; ++ } ++ ++ for (bit = 0; bit < W1_UART_HDQ_BITS_PER_BYTE; bit++) ++ w1dev->hdq_command[bit] = command & BIT(bit) ? ++ W1_UART_HDQ_ONE : W1_UART_HDQ_ZERO; ++ ++ /* ++ * Retry a lost cycle: the gauge's response window is long over after ++ * the timeout, and the next break resynchronizes its HDQ state. ++ */ ++ for (try = 0; try < W1_UART_HDQ_TRIES; try++) { ++ ret = w1_uart_hdq_xfer_once(w1dev); ++ if (ret != -ETIMEDOUT) ++ break; ++ dev_dbg(&w1dev->serdev->dev, ++ "HDQ command %02x timed out (try %u/%u)\n", ++ command, try + 1, W1_UART_HDQ_TRIES); ++ } ++ ++ if (w1dev->hdq_mux) { ++ deselect_ret = mux_state_deselect(w1dev->hdq_mux); ++ if (!ret && deselect_ret) { ++ w1_uart_hdq_cancel_receive(w1dev); ++ ret = deselect_ret; ++ } ++ } ++ ++ return ret; ++} ++ + /* + * Send one byte (tx_byte) and read one byte (rx_byte) via serdev. + */ +@@ -293,6 +479,33 @@ static size_t w1_uart_serdev_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t count) + { + struct w1_uart_device *w1dev = serdev_device_get_drvdata(serdev); ++ unsigned long flags; ++ size_t i; ++ ++ if (w1dev->mode == W1_UART_MODE_HDQ) { ++ spin_lock_irqsave(&w1dev->hdq_rx_lock, flags); ++ for (i = 0; i < count && w1dev->hdq_receiving; i++) { ++ if (w1dev->hdq_rx_bit < W1_UART_HDQ_BITS_PER_BYTE && ++ buf[i] != w1dev->hdq_command[w1dev->hdq_rx_bit]) ++ continue; ++ ++ if (w1dev->hdq_rx_bit >= W1_UART_HDQ_BITS_PER_BYTE && ++ buf[i] >= W1_UART_HDQ_RX_ONE_MIN) ++ w1dev->hdq_response |= ++ BIT(w1dev->hdq_rx_bit - ++ W1_UART_HDQ_BITS_PER_BYTE); ++ ++ w1dev->hdq_rx_bit++; ++ if (w1dev->hdq_rx_bit == W1_UART_HDQ_RX_BITS) { ++ w1dev->hdq_receiving = false; ++ w1dev->hdq_response_valid = true; ++ complete(&w1dev->rx_byte_received); ++ } ++ } ++ spin_unlock_irqrestore(&w1dev->hdq_rx_lock, flags); ++ ++ return count; ++ } + + mutex_lock(&w1dev->rx_mutex); + +@@ -355,22 +568,117 @@ static u8 w1_uart_touch_bit(void *data, u8 bit) + return val == w1cfg->tx_byte ? 1 : 0; + } + ++static void w1_uart_hdq_write_byte(void *data, u8 byte) ++{ ++ struct w1_uart_device *w1dev = data; ++ int ret; ++ ++ ret = w1_uart_hdq_xfer(w1dev, byte); ++ if (ret) ++ dev_dbg(&w1dev->serdev->dev, "HDQ transfer failed: %d\n", ret); ++} ++ ++static bool w1_uart_hdq_take_response(struct w1_uart_device *w1dev, ++ u8 *response) ++{ ++ unsigned long flags; ++ bool valid; ++ ++ spin_lock_irqsave(&w1dev->hdq_rx_lock, flags); ++ valid = w1dev->hdq_response_valid; ++ if (valid) { ++ *response = w1dev->hdq_response; ++ w1dev->hdq_response_valid = false; ++ } ++ spin_unlock_irqrestore(&w1dev->hdq_rx_lock, flags); ++ ++ return valid; ++} ++ ++static u8 w1_uart_hdq_reset_bus(void *data) ++{ ++ struct w1_uart_device *w1dev = data; ++ int ret; ++ ++ w1_uart_hdq_cancel_receive(w1dev); ++ if (w1dev->hdq_mux) { ++ ret = mux_state_select(w1dev->hdq_mux); ++ if (ret) ++ return 1; ++ } ++ ++ ret = w1_uart_hdq_break(w1dev); ++ if (w1dev->hdq_mux && mux_state_deselect(w1dev->hdq_mux) && !ret) ++ ret = -EIO; ++ ++ return ret ? 1 : 0; ++} ++ ++static u8 w1_uart_hdq_read_byte(void *data) ++{ ++ struct w1_uart_device *w1dev = data; ++ u8 response; ++ ++ return w1_uart_hdq_take_response(w1dev, &response) ? response : 0xff; ++} ++ ++static u8 w1_uart_hdq_read_block(void *data, u8 *buf, int len) ++{ ++ struct w1_uart_device *w1dev = data; ++ ++ if (len != 1) ++ return 0; ++ ++ return w1_uart_hdq_take_response(w1dev, buf) ? 1 : 0; ++} ++ ++static void w1_uart_hdq_search(void *data, struct w1_master *master, ++ u8 search_type, ++ w1_slave_found_callback slave_found) ++{ ++ u64 module_id = W1_UART_HDQ_FAMILY; ++ u64 rn_le = cpu_to_le64(module_id); ++ u8 crc; ++ ++ crc = w1_calc_crc8((u8 *)&rn_le, 7); ++ slave_found(master, (u64)crc << 56 | module_id); ++} ++ + static int w1_uart_probe(struct serdev_device *serdev) + { + struct device *dev = &serdev->dev; ++ const struct w1_uart_variant *variant; + struct w1_uart_device *w1dev; + int ret; + + w1dev = devm_kzalloc(dev, sizeof(*w1dev), GFP_KERNEL); + if (!w1dev) + return -ENOMEM; +- w1dev->bus.data = w1dev; +- w1dev->bus.reset_bus = w1_uart_reset_bus; +- w1dev->bus.touch_bit = w1_uart_touch_bit; + w1dev->serdev = serdev; ++ w1dev->bus.data = w1dev; ++ variant = device_get_match_data(dev); ++ if (variant) ++ w1dev->mode = variant->mode; ++ ++ if (w1dev->mode == W1_UART_MODE_HDQ) { ++ w1dev->hdq_mux = devm_mux_state_get_optional(dev, "hdq"); ++ if (IS_ERR(w1dev->hdq_mux)) ++ return dev_err_probe(dev, PTR_ERR(w1dev->hdq_mux), ++ "failed to get HDQ mux state\n"); ++ ++ w1dev->bus.write_byte = w1_uart_hdq_write_byte; ++ w1dev->bus.read_byte = w1_uart_hdq_read_byte; ++ w1dev->bus.read_block = w1_uart_hdq_read_block; ++ w1dev->bus.reset_bus = w1_uart_hdq_reset_bus; ++ w1dev->bus.search = w1_uart_hdq_search; ++ } else { ++ w1dev->bus.reset_bus = w1_uart_reset_bus; ++ w1dev->bus.touch_bit = w1_uart_touch_bit; ++ } + + init_completion(&w1dev->rx_byte_received); + mutex_init(&w1dev->rx_mutex); ++ spin_lock_init(&w1dev->hdq_rx_lock); + + serdev_device_set_drvdata(serdev, w1dev); + serdev_device_set_client_ops(serdev, &w1_uart_serdev_ops); +@@ -393,8 +701,17 @@ static void w1_uart_remove(struct serdev_device *serdev) + w1_remove_master_device(&w1dev->bus); + } + ++static const struct w1_uart_variant w1_uart_1wire = { ++ .mode = W1_UART_MODE_1WIRE, ++}; ++ ++static const struct w1_uart_variant w1_uart_hdq = { ++ .mode = W1_UART_MODE_HDQ, ++}; ++ + static const struct of_device_id w1_uart_of_match[] = { +- { .compatible = "w1-uart" }, ++ { .compatible = "w1-uart", .data = &w1_uart_1wire }, ++ { .compatible = "ti,hdq-uart", .data = &w1_uart_hdq }, + {}, + }; + MODULE_DEVICE_TABLE(of, w1_uart_of_match); +diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h +index d56e1276aafe5ee5d6baf78961ed7bd9e2b278fa..5b5feb1ff1fbcd6302e0ac39b2ede03aa6b1f9b3 100644 +--- a/include/linux/power/bq27xxx_battery.h ++++ b/include/linux/power/bq27xxx_battery.h +@@ -46,9 +46,23 @@ struct bq27xxx_access_methods { + int (*write_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len); + }; + ++struct bq27xxx_cached_value { ++ int value; ++ int error; ++}; ++ + struct bq27xxx_reg_cache { + int capacity; + int flags; ++ struct bq27xxx_cached_value current_now; ++ struct bq27xxx_cached_value status; ++ struct bq27xxx_cached_value time_to_empty_now; ++ struct bq27xxx_cached_value temperature; ++ struct bq27xxx_cached_value voltage; ++ struct bq27xxx_cached_value charge_now; ++ struct bq27xxx_cached_value charge_full; ++ struct bq27xxx_cached_value cycle_count; ++ struct bq27xxx_cached_value power_avg; + }; + + struct bq27xxx_device_info { +@@ -60,6 +74,17 @@ struct bq27xxx_device_info { + u32 unseal_key; + struct bq27xxx_access_methods bus; + struct bq27xxx_reg_cache cache; ++ /* Serve properties only from the periodically populated register cache. */ ++ bool cache_only; ++ /* At least one complete cache-only safety sample succeeded. */ ++ bool cache_ready; ++ /* Optional transport-specific subset for cache-only operation. */ ++ const enum power_supply_property *cache_properties; ++ size_t num_cache_properties; ++ /* Use RemainingCapacity rather than NominalAvailableCapacity. */ ++ bool cache_use_remaining_capacity; ++ /* Optional periodic refresh interval for cache-only transports. */ ++ unsigned int cache_refresh_ms; + int charge_design_full; + int voltage_min_design; + int voltage_max_design; diff --git a/kernel/patches/j71t/0007-j71t-usb.patch b/kernel/patches/j71t/0007-j71t-usb.patch new file mode 100644 index 00000000..28c7fc67 --- /dev/null +++ b/kernel/patches/j71t/0007-j71t-usb.patch @@ -0,0 +1,2634 @@ +From 7dcb77b9834c613ebac360f833f5edb8dfb86fb5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 07/12] usb: add the J71t gadget, Lightning host role and PHY +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +DWC2 gadget support, the Lightning camera-adapter host role switch +and the S8000 USB PHY. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../bindings/phy/apple,ausb-phy.yaml | 7 + + .../usb/apple,s8000-usb-role-switch.yaml | 129 ++ + .../devicetree/bindings/usb/dwc2.yaml | 6 + + drivers/phy/apple/ausb.c | 123 +- + drivers/usb/dwc2/core.h | 3 + + drivers/usb/dwc2/gadget.c | 4 +- + drivers/usb/dwc2/platform.c | 36 + + drivers/usb/gadget/composite.c | 24 + + drivers/usb/gadget/function/f_ecm.c | 5 + + drivers/usb/gadget/udc/core.c | 250 +++ + drivers/usb/host/ehci-platform.c | 14 + + drivers/usb/host/ohci-platform.c | 14 + + drivers/usb/roles/Kconfig | 14 + + drivers/usb/roles/Makefile | 1 + + drivers/usb/roles/apple-s8000-usb-role.c | 1452 +++++++++++++++++ + include/linux/usb/apple-s8000-role.h | 42 + + include/linux/usb/gadget.h | 64 + + 17 files changed, 2167 insertions(+), 21 deletions(-) + create mode 100644 Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml + create mode 100644 drivers/usb/roles/apple-s8000-usb-role.c + create mode 100644 include/linux/usb/apple-s8000-role.h + +diff --git a/Documentation/devicetree/bindings/phy/apple,ausb-phy.yaml b/Documentation/devicetree/bindings/phy/apple,ausb-phy.yaml +index 9c51d1d0fb42bcd5e7c56e149d2d92a68ec42598..7bd912367bb442828ef4fd9f6462b58495d49d4a 100644 +--- a/Documentation/devicetree/bindings/phy/apple,ausb-phy.yaml ++++ b/Documentation/devicetree/bindings/phy/apple,ausb-phy.yaml +@@ -33,6 +33,13 @@ properties: + "#phy-cells": + const: 1 + ++ apple,enable-runtime-pm: ++ type: boolean ++ description: ++ Permit the experimental driver runtime-PM policy. The PHY domain can ++ be released only after its controller drops its PHY power reference. ++ Requires a separate driver opt-in on the j71t board. ++ + mode-switch: + type: boolean + description: +diff --git a/Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml b/Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..23effbd62c255b428858df2bc564496769793fba +--- /dev/null ++++ b/Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml +@@ -0,0 +1,129 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/usb/apple,s8000-usb-role-switch.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple S8000 USB role switch ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: | ++ The Apple A9 (S8000/S8003) USB complex pairs a device-only DWC2 controller ++ with a separate EHCI + OHCI host pair. All three share one AUSB PHY, and ++ the USB_CTL register bank selects whether the PHY serves the host pair. ++ The switch owns USB_CTL and the host controllers, which are its child ++ nodes and exist only while the port is in the host role. Device mode is ++ the default. The switch sits in the USB complex node, which owns the ++ complex's DMA remap registers. ++ ++properties: ++ compatible: ++ const: apple,s8000-usb-role-switch ++ ++ reg: ++ items: ++ - description: USB_CTL register bank ++ - description: USB complex controller glue registers ++ ++ reg-names: ++ items: ++ - const: usb-ctl ++ - const: glue ++ ++ "#address-cells": ++ const: 2 ++ ++ "#size-cells": ++ const: 2 ++ ++ ranges: true ++ ++ dma-ranges: true ++ ++ nonposted-mmio: ++ $ref: /schemas/types.yaml#/definitions/flag ++ description: The host controller registers need non-posted accesses. ++ ++ phys: ++ maxItems: 1 ++ ++ phy-names: ++ const: usb2-phy ++ ++ apple,usb-device: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: The DWC2 device controller that shares the PHY. ++ ++ apple,pmic: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: ++ PMIC providing the Lightning accessory power LDO and its switch. ++ ++ apple,tristar: ++ $ref: /schemas/types.yaml#/definitions/phandle-array ++ items: ++ - items: ++ - description: I2C bus of the Tristar port controller ++ - description: 7-bit I2C address of the Tristar ++ description: ++ Tristar port controller with the connector VBUS sink switch. ++ ++patternProperties: ++ "^usb@[0-9a-f]+$": ++ type: object ++ description: The EHCI and its OHCI companion. ++ ++required: ++ - compatible ++ - reg ++ - reg-names ++ - "#address-cells" ++ - "#size-cells" ++ - ranges ++ - phys ++ - phy-names ++ - apple,usb-device ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ ++ bus { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ usb-role-switch@20e0d8000 { ++ compatible = "apple,s8000-usb-role-switch"; ++ reg = <0x2 0x0e0d8000 0x0 0x1000>, ++ <0x2 0x0c000000 0x0 0x30>; ++ reg-names = "usb-ctl", "glue"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ dma-ranges; ++ nonposted-mmio; ++ phys = <&ausbphy0 PHY_TYPE_USB2>; ++ phy-names = "usb2-phy"; ++ apple,usb-device = <&usbdev>; ++ apple,pmic = <&pmic>; ++ apple,tristar = <&i2c0 0x1a>; ++ ++ usb@20c200000 { ++ compatible = "apple,s8000-ehci", "generic-ehci"; ++ reg = <0x2 0x0c200000 0x0 0x10000>; ++ interrupts = ; ++ }; ++ ++ usb@20c300000 { ++ compatible = "apple,s8000-ohci", "generic-ohci"; ++ reg = <0x2 0x0c300000 0x0 0x10000>; ++ interrupts = ; ++ }; ++ }; ++ }; +diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml +index 166d37cd921257524e2d93bf16bc248b38723a87..37159d7a9c54e51915daad7edbdc474b0895a73e 100644 +--- a/Documentation/devicetree/bindings/usb/dwc2.yaml ++++ b/Documentation/devicetree/bindings/usb/dwc2.yaml +@@ -78,6 +78,12 @@ properties: + - apple,t8015-dwc2 + - const: apple,dwc2 + ++ apple,host-current-budget: ++ type: boolean ++ description: | ++ Enable Apple device-mode current-limit requests and source-budget ++ notifications, bounded to the J71t 2400 mA platform maximum. ++ + reg: + maxItems: 1 + +diff --git a/drivers/phy/apple/ausb.c b/drivers/phy/apple/ausb.c +index 39ba39d833776a67e1b55fe794939fd450a1b767..69057cecdeb06d730f0233ebdb2971e695d25546 100644 +--- a/drivers/phy/apple/ausb.c ++++ b/drivers/phy/apple/ausb.c +@@ -15,6 +15,11 @@ + #include + #include + #include ++#include ++ ++static bool runtime_pm; ++module_param(runtime_pm, bool, 0444); ++MODULE_PARM_DESC(runtime_pm, "Enable DT-selected j71t PHY runtime PM (default: off)"); + + /* These look similar to ATC's USB2PHY_CTL and USB2PHY_SIG */ + #define AUSB_CTL 0x00 +@@ -30,6 +35,7 @@ + /* Tunable registers */ + #define AUSB_CFG0 0x08 + #define AUSB_CFG1 0x0c ++#define AUSB_TRIM 0x1c + + struct ausb_usbphy { + struct device *dev; +@@ -40,6 +46,9 @@ struct ausb_usbphy { + u32 cfg1_device; + u32 cfg0_host; + u32 cfg1_host; ++ /* Serialized by the PHY core's phy->mutex. */ ++ bool host; ++ bool powered; + }; + + static inline void mask32(void __iomem *reg, u32 mask, u32 set) +@@ -60,11 +69,24 @@ static inline void clear32(void __iomem *reg, u32 clear) + mask32(reg, clear, 0); + } + +-#if 0 +-static __maybe_unused int ausb_phy_usb2_set_mode(struct phy *phy, enum phy_mode mode, +- int submode) ++static void ausb_phy_write_tunables(struct ausb_usbphy *aphy) ++{ ++ writel(aphy->host ? aphy->cfg0_host : aphy->cfg0_device, ++ aphy->base + AUSB_CFG0); ++ writel(aphy->host ? aphy->cfg1_host : aphy->cfg1_device, ++ aphy->base + AUSB_CFG1); ++} ++ ++/* ++ * Role change on a running PHY, as AppleS5L8960XUSBPhy::enableHostMode does ++ * it for both directions: mode tunables, trim word 0, then a reset pulse. ++ * iOS does not touch AUSB_SIG here, so neither do we. ++ */ ++static int ausb_phy_usb2_set_mode(struct phy *phy, enum phy_mode mode, ++ int submode) + { + struct ausb_usbphy *aphy = phy_get_drvdata(phy); ++ bool host; + + switch (mode) { + case PHY_MODE_USB_HOST: +@@ -72,35 +94,45 @@ static __maybe_unused int ausb_phy_usb2_set_mode(struct phy *phy, enum phy_mode + case PHY_MODE_USB_HOST_FS: + case PHY_MODE_USB_HOST_HS: + case PHY_MODE_USB_HOST_SS: +- writel(aphy->cfg0_host, aphy->base + AUSB_CFG0); +- writel(aphy->cfg1_host, aphy->base + AUSB_CFG1); +- set32(aphy->base + AUSB_SIG, AUSB_SIG_HOST); +- return 0; +- ++ host = true; ++ break; + case PHY_MODE_USB_DEVICE: + case PHY_MODE_USB_DEVICE_LS: + case PHY_MODE_USB_DEVICE_FS: + case PHY_MODE_USB_DEVICE_HS: + case PHY_MODE_USB_DEVICE_SS: +- writel(aphy->cfg0_device, aphy->base + AUSB_CFG0); +- writel(aphy->cfg1_device, aphy->base + AUSB_CFG1); +- clear32(aphy->base + AUSB_SIG, AUSB_SIG_HOST); +- return 0; +- ++ host = false; ++ break; + default: + dev_err(aphy->dev, "Unknown mode for usb2 phy: %d\n", mode); + return -EINVAL; + } ++ ++ /* A second host-side user (EHCI, then OHCI) must not re-pulse reset. */ ++ if (host == aphy->host) ++ return 0; ++ aphy->host = host; ++ /* A powered-down PHY picks the mode up at its next power-on. */ ++ if (!aphy->powered) ++ return 0; ++ ++ ausb_phy_write_tunables(aphy); ++ writel(0, aphy->base + AUSB_TRIM); ++ set32(aphy->base + AUSB_CTL, AUSB_CTL_RESET); ++ usleep_range(20, 40); ++ clear32(aphy->base + AUSB_CTL, AUSB_CTL_RESET); ++ usleep_range(1000, 1500); ++ dev_dbg(aphy->dev, "%s mode\n", host ? "host" : "device"); ++ ++ return 0; + } +-#endif + + static int ausb_phy_usb2_power_on(struct phy *phy) + { + struct ausb_usbphy *aphy = phy_get_drvdata(phy); + +- /* set a sane tunable */ +- writel(aphy->cfg0_device, aphy->base + AUSB_CFG0); +- writel(aphy->cfg1_device, aphy->base + AUSB_CFG1); ++ /* set a sane tunable: device mode unless a host user asked first */ ++ ausb_phy_write_tunables(aphy); + + set32(aphy->base + AUSB_CTL, AUSB_CTL_RESET); + udelay(20); +@@ -109,6 +141,7 @@ static int ausb_phy_usb2_power_on(struct phy *phy) + clear32(aphy->base + AUSB_CTL, AUSB_CTL_RESET); + udelay(1500); + set32(aphy->base + AUSB_SIG, AUSB_SIG_VBUSDET_FORCE_EN); ++ aphy->powered = true; + + return 0; + } +@@ -124,6 +157,9 @@ static int ausb_phy_usb2_power_off(struct phy *phy) + clear32(aphy->base + AUSB_CTL, AUSB_CTL_RESET); + udelay(1500); + clear32(aphy->base + AUSB_SIG, AUSB_SIG_VBUSDET_FORCE_EN); ++ aphy->powered = false; ++ /* The device controller never sets a mode: default it for the next user. */ ++ aphy->host = false; + + return 0; + } +@@ -131,16 +167,48 @@ static int ausb_phy_usb2_power_off(struct phy *phy) + + static const struct phy_ops ausb_phy_usb2_ops = { + .owner = THIS_MODULE, +- //.set_mode = ausb_phy_usb2_set_mode, ++ .set_mode = ausb_phy_usb2_set_mode, + .init = ausb_phy_usb2_power_on, + .exit = ausb_phy_usb2_power_off, + }; + ++/* ++ * Generic PHY core holds a parent runtime-PM reference from phy_power_on() ++ * through phy_power_off(). DWC2 therefore keeps the PHY domain powered while ++ * it owns the link. No cable/traffic heuristic or connected-gadget suspend. ++ */ ++static const struct phy_ops ausb_pm_phy_ops = { ++ .set_mode = ausb_phy_usb2_set_mode, ++ .owner = THIS_MODULE, ++}; ++ ++static int ausb_runtime_suspend(struct device *dev) ++{ ++ struct ausb_usbphy *aphy = dev_get_drvdata(dev); ++ ++ if (device_may_wakeup(dev) || dev->power.wakeup_path) ++ return -EBUSY; ++ return ausb_phy_usb2_power_off(aphy->phy); ++} ++ ++static int ausb_runtime_resume(struct device *dev) ++{ ++ struct ausb_usbphy *aphy = dev_get_drvdata(dev); ++ ++ /* genpd is on before this callback; replay tunables and reset delays. */ ++ return ausb_phy_usb2_power_on(aphy->phy); ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(ausb_pm_ops, ausb_runtime_suspend, ++ ausb_runtime_resume, NULL); ++ + static int ausb_usbphy_probe(struct platform_device *pdev) { + struct ausb_usbphy *aphy; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct resource *res; ++ bool use_runtime_pm; ++ int ret; + + aphy = devm_kzalloc(&pdev->dev, sizeof(*aphy), GFP_KERNEL); + if (!aphy) +@@ -161,7 +229,23 @@ static int ausb_usbphy_probe(struct platform_device *pdev) { + if (IS_ERR(aphy->base)) + return PTR_ERR(aphy->base); + +- aphy->phy = devm_phy_create(aphy->dev, NULL, &ausb_phy_usb2_ops); ++ platform_set_drvdata(pdev, aphy); ++ use_runtime_pm = runtime_pm && of_machine_is_compatible("apple,j71t") && ++ of_property_read_bool(np, "apple,enable-runtime-pm"); ++ if (use_runtime_pm) { ++ /* The first PHY operation must replay setup, even after firmware use. */ ++ ret = pm_runtime_set_suspended(dev); ++ if (ret) ++ return ret; ++ /* Must precede phy_create(), which inherits the parent's PM policy. */ ++ ret = devm_pm_runtime_enable(dev); ++ if (ret) ++ return ret; ++ dev_info(dev, "opt-in runtime PM; controller retains active PHY hold\n"); ++ } ++ ++ aphy->phy = devm_phy_create(aphy->dev, NULL, ++ use_runtime_pm ? &ausb_pm_phy_ops : &ausb_phy_usb2_ops); + if (IS_ERR(aphy->phy)) + return PTR_ERR(aphy->phy); + +@@ -183,6 +267,7 @@ static struct platform_driver ausb_usbphy_driver = { + .driver = { + .of_match_table = ausb_usbphy_of_match, + .name = "phy-apple-ausb", ++ .pm = pm_ptr(&ausb_pm_ops), + } + + }; +diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h +index 767251aa1aa3f82757b1824b30f0433bbd1b31e1..31809b4f925bde6f8ed6de6926e16828ed96274d 100644 +--- a/drivers/usb/dwc2/core.h ++++ b/drivers/usb/dwc2/core.h +@@ -876,6 +876,8 @@ struct dwc2_hregs_backup { + * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a + * remote wakeup. + * @phy_off_for_suspend: Status of whether we turned the PHY off at suspend. ++ * @kept_for_s2idle: The gadget was left connected and running over ++ * suspend-to-idle (dwc2.s2idle_keep_gadget). + * @need_phy_for_wake: Quirk saying that we should keep the PHY on at + * suspend if we need USB to wake us up. + * @frame_number: Frame number read from the core. For both device +@@ -1074,6 +1076,7 @@ struct dwc2_hsotg { + unsigned int reset_phy_on_wake:1; + unsigned int need_phy_for_wake:1; + unsigned int phy_off_for_suspend:1; ++ unsigned int kept_for_s2idle:1; + u16 frame_number; + + struct phy *phy; +diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c +index 102169b404196cc3880cb8240297c556d5b75f02..ccd9d775e5c9859b88112bbf6454bb0ea8fb3bb2 100644 +--- a/drivers/usb/dwc2/gadget.c ++++ b/drivers/usb/dwc2/gadget.c +@@ -5741,8 +5741,8 @@ void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) + dwc2_writel(hsotg, dctl, DCTL); + } + +- /* Change to L0 state */ +- call_gadget(hsotg, resume); ++ /* Resume callbacks may immediately queue endpoint requests. */ + hsotg->lx_state = DWC2_L0; + hsotg->bus_suspended = false; ++ call_gadget(hsotg, resume); + } +diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c +index ef0d7307703473ee0d17085fdfe361898c8aedcd..57b86ec14f8e1cffdc4a579500b8da02b893d08a 100644 +--- a/drivers/usb/dwc2/platform.c ++++ b/drivers/usb/dwc2/platform.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + + #include + +@@ -646,6 +647,33 @@ error: + return retval; + } + ++static bool s2idle_keep_gadget; ++module_param(s2idle_keep_gadget, bool, 0644); ++MODULE_PARM_DESC(s2idle_keep_gadget, ++ "Leave a connected gadget running over suspend-to-idle instead of disconnecting it (default off)"); ++ ++/* ++ * Suspend-to-idle with dwc2.s2idle_keep_gadget: leave the controller and ++ * its PHY powered and the gadget connected. Its interrupt is off while the ++ * system sleeps, so the host sees NAKs and nothing else; a gadget network ++ * link (and anything on it, e.g. an NBD root) survives a short sleep. ++ * The controller and PHY are put on the wakeup path so that their power ++ * domains stay on (GENPD_FLAG_ACTIVE_WAKEUP). ++ */ ++static bool dwc2_keep_gadget_for_s2idle(struct dwc2_hsotg *dwc2) ++{ ++ if (!s2idle_keep_gadget || !pm_suspend_no_platform() || ++ !dwc2_is_device_mode(dwc2) || dwc2->lx_state != DWC2_L0) ++ return false; ++ ++ device_set_wakeup_path(dwc2->dev); ++ if (dwc2->phy && dwc2->phy->dev.parent) ++ device_set_wakeup_path(dwc2->phy->dev.parent); ++ dwc2->kept_for_s2idle = 1; ++ dev_info(dwc2->dev, "s2idle: gadget stays connected\n"); ++ return true; ++} ++ + static int __maybe_unused dwc2_suspend(struct device *dev) + { + struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); +@@ -655,6 +683,9 @@ static int __maybe_unused dwc2_suspend(struct device *dev) + if (!dwc2->ll_hw_enabled) + return 0; + ++ if (dwc2_keep_gadget_for_s2idle(dwc2)) ++ return 0; ++ + is_device_mode = dwc2_is_device_mode(dwc2); + if (is_device_mode) + dwc2_hsotg_suspend(dwc2); +@@ -735,6 +766,11 @@ static int __maybe_unused dwc2_resume(struct device *dev) + if (!dwc2->ll_hw_enabled) + return 0; + ++ if (dwc2->kept_for_s2idle) { ++ dwc2->kept_for_s2idle = 0; ++ return 0; ++ } ++ + if (dwc2->phy_off_for_suspend && dwc2->ll_hw_enabled) { + ret = __dwc2_lowlevel_hw_enable(dwc2); + if (ret) +diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c +index df39e3487c1f3b03c7105f8933a7faf201bd6069..8cd83d6fa1b904b319df92966cf5dc6cf567a86b 100644 +--- a/drivers/usb/gadget/composite.c ++++ b/drivers/usb/gadget/composite.c +@@ -1593,6 +1593,16 @@ static int composite_ep0_queue(struct usb_composite_dev *cdev, + return ret; + } + ++static void composite_current_grant_complete(struct usb_ep *ep, ++ struct usb_request *req) ++{ ++ struct usb_composite_dev *cdev = req->context; ++ ++ if (req->status && cdev) ++ usb_gadget_revoke_current_grant(cdev->gadget); ++ composite_setup_complete(ep, req); ++} ++ + static int count_ext_compat(struct usb_configuration *c) + { + int i, res; +@@ -1765,6 +1775,16 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) + struct usb_function *iter; + u8 endp; + ++ value = usb_gadget_handle_current_grant(gadget, ctrl); ++ if (value != -EOPNOTSUPP) { ++ req->zero = 0; ++ req->context = cdev; ++ req->complete = composite_current_grant_complete; ++ req->length = 0; ++ gadget->ep0->driver_data = cdev; ++ goto check_value; ++ } ++ + if (w_length > USB_COMP_EP0_BUFSIZ) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* Cast away the const, we are going to overwrite on purpose. */ +@@ -2271,6 +2291,8 @@ check_value: + value = composite_ep0_queue(cdev, req, GFP_ATOMIC); + if (value < 0) { + DBG(cdev, "ep_queue --> %d\n", value); ++ if (req->complete == composite_current_grant_complete) ++ usb_gadget_revoke_current_grant(gadget); + req->status = 0; + composite_setup_complete(gadget->ep0, req); + } +@@ -2588,6 +2610,7 @@ void composite_suspend(struct usb_gadget *gadget) + * suspend/resume callbacks? + */ + DBG(cdev, "suspend\n"); ++ usb_gadget_current_budget_suspend(gadget, true); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->suspend) +@@ -2616,6 +2639,7 @@ void composite_resume(struct usb_gadget *gadget) + * suspend/resume callbacks? + */ + DBG(cdev, "resume\n"); ++ usb_gadget_current_budget_suspend(gadget, false); + if (cdev->driver->resume) + cdev->driver->resume(cdev); + if (cdev->config) { +diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c +index e495bac4efeb5a39107a09d06b69ba86eaab1926..1f57c6bfbef4a19e7d00add4ef132fd946777100 100644 +--- a/drivers/usb/gadget/function/f_ecm.c ++++ b/drivers/usb/gadget/function/f_ecm.c +@@ -372,6 +372,7 @@ static void ecm_do_notify(struct f_ecm *ecm) + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + __le32 *data; + int status; ++ u8 state = ecm->notify_state; + + /* notification already in flight? */ + if (atomic_read(&ecm->notify_count)) +@@ -417,6 +418,8 @@ static void ecm_do_notify(struct f_ecm *ecm) + status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC); + if (status < 0) { + atomic_dec(&ecm->notify_count); ++ /* A suspended UDC can reject the request with -EAGAIN. */ ++ ecm->notify_state = state; + DBG(cdev, "notify --> %d\n", status); + } + } +@@ -886,6 +889,8 @@ static void ecm_resume(struct usb_function *f) + DBG(cdev, "ECM Resume\n"); + + gether_resume(&ecm->port); ++ /* Refresh host carrier, including notifications lost at suspend. */ ++ ecm_notify(ecm); + } + + static int ecm_get_status(struct usb_function *f) +diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c +index f6da12b553a0eee8faeaddb90aacd33e7a6f4ac0..7d6d0e1661263cf627cc3bd30cfbf9e59b9e6572 100644 +--- a/drivers/usb/gadget/udc/core.c ++++ b/drivers/usb/gadget/udc/core.c +@@ -17,6 +17,8 @@ + #include + #include + #include ++#include ++#include + + #include + #include +@@ -70,6 +72,225 @@ static LIST_HEAD(udc_list); + /* Protects udc_list, udc->driver, driver->is_bound, and related calls */ + static DEFINE_MUTEX(udc_lock); + ++/* Authorization from USB configuration and the opt-in Apple host protocol. */ ++static ATOMIC_NOTIFIER_HEAD(gadget_current_notifier); ++ ++static void gadget_current_snapshot(struct usb_gadget *gadget, ++ struct usb_gadget_current_budget *budget) ++{ ++ int base = gadget->current_base_ma; ++ int extra = gadget->current_extra_ma; ++ int budget_ma = base; ++ ++ budget->of_node = dev_of_node(gadget->dev.parent); ++ budget->configured = gadget->current_configured; ++ budget->connected = gadget->current_bus_connected; ++ budget->suspended = gadget->current_suspended || ++ gadget->state == USB_STATE_SUSPENDED; ++ budget->host_grant = gadget->current_host_grant; ++ if (budget->host_grant) { ++ /* IOUSBDeviceFamily active-bus policy: signed extra is wIndex. */ ++ budget_ma = base && extra ? 500 + extra : base + extra; ++ budget_ma = clamp(budget_ma, 0, 2400); ++ } ++ budget->current_ma = budget->connected && !budget->suspended ? budget_ma : 0; ++} ++ ++static void gadget_current_notify(struct usb_gadget *gadget) ++{ ++ struct usb_gadget_current_budget budget; ++ unsigned long flags; ++ ++ if (!gadget->current_budget_enabled) ++ return; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget_current_snapshot(gadget, &budget); ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ atomic_notifier_call_chain(&gadget_current_notifier, 0, &budget); ++} ++ ++/** ++ * usb_gadget_register_current_notifier - subscribe to gadget budget changes ++ * @nb: notifier whose callback must not sleep or query the budget ++ * ++ * Callbacks receive a temporary struct usb_gadget_current_budget pointer. ++ * A consumer should schedule work, then query the current snapshot by its ++ * controller node. Register before the initial query to avoid lost events. ++ */ ++int usb_gadget_register_current_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_register(&gadget_current_notifier, nb); ++} ++EXPORT_SYMBOL_GPL(usb_gadget_register_current_notifier); ++ ++int usb_gadget_unregister_current_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_unregister(&gadget_current_notifier, nb); ++} ++EXPORT_SYMBOL_GPL(usb_gadget_unregister_current_notifier); ++ ++/** ++ * usb_gadget_get_current_budget - query an opted-in controller's authorization ++ * @node: controller node held by the caller ++ * @budget: returned snapshot; valid only on success ++ * ++ * Sleepable. A device-tree phandle identifies the supplying controller. ++ * The notifier callback must not call this function: the UDC lock may be held. ++ */ ++int usb_gadget_get_current_budget(const struct device_node *node, ++ struct usb_gadget_current_budget *budget) ++{ ++ struct usb_udc *udc; ++ unsigned long flags; ++ int ret = -ENODEV; ++ ++ if (!node || !budget) ++ return -EINVAL; ++ mutex_lock(&udc_lock); ++ list_for_each_entry(udc, &udc_list, list) { ++ struct usb_gadget *gadget = udc->gadget; ++ ++ if (!gadget->current_budget_enabled || ++ dev_of_node(gadget->dev.parent) != node) ++ continue; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget_current_snapshot(gadget, budget); ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ ret = 0; ++ break; ++ } ++ mutex_unlock(&udc_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(usb_gadget_get_current_budget); ++ ++/** ++ * usb_gadget_current_source_detached - forget accounting on measured VBUS loss ++ * @node: supplying controller node ++ * ++ * Sleepable; called only for a physical detach, never for USB link PM/reset. ++ * Does not disconnect or unbind the gadget and performs no controller I/O. ++ */ ++void usb_gadget_current_source_detached(const struct device_node *node) ++{ ++ struct usb_udc *udc; ++ unsigned long flags; ++ ++ mutex_lock(&udc_lock); ++ list_for_each_entry(udc, &udc_list, list) { ++ struct usb_gadget *gadget = udc->gadget; ++ ++ if (!gadget->current_budget_enabled || ++ dev_of_node(gadget->dev.parent) != node) ++ continue; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget->current_extra_ma = 0; ++ gadget->current_host_grant = false; ++ gadget->current_base_ma = 0; ++ gadget->current_configured = false; ++ gadget->current_bus_connected = false; ++ gadget->current_suspended = false; ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ gadget_current_notify(gadget); ++ } ++ mutex_unlock(&udc_lock); ++} ++EXPORT_SYMBOL_GPL(usb_gadget_current_source_detached); ++ ++static void gadget_current_set_base(struct usb_gadget *gadget, unsigned int ma) ++{ ++ unsigned long flags; ++ ++ if (!gadget->current_budget_enabled) ++ return; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget->current_base_ma = min(ma, 500U); ++ if (!ma) { ++ gadget->current_extra_ma = 0; ++ gadget->current_host_grant = false; ++ } ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ gadget_current_notify(gadget); ++} ++ ++/* Also used to revoke a grant when its ep0 status transfer fails. */ ++void usb_gadget_revoke_current_grant(struct usb_gadget *gadget) ++{ ++ unsigned long flags; ++ ++ if (!gadget->current_budget_enabled) ++ return; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget->current_extra_ma = 0; ++ gadget->current_host_grant = false; ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ gadget_current_notify(gadget); ++} ++EXPORT_SYMBOL_GPL(usb_gadget_revoke_current_grant); ++ ++void usb_gadget_current_budget_suspend(struct usb_gadget *gadget, bool suspended) ++{ ++ unsigned long flags; ++ ++ if (!gadget->current_budget_enabled) ++ return; ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ gadget->current_suspended = suspended; ++ /* Resume requires a fresh grant; do not reuse an earlier high budget. */ ++ gadget->current_extra_ma = 0; ++ gadget->current_host_grant = false; ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ gadget_current_notify(gadget); ++} ++EXPORT_SYMBOL_GPL(usb_gadget_current_budget_suspend); ++ ++int usb_gadget_handle_current_grant(struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl) ++{ ++ unsigned long flags; ++ s16 extra = le16_to_cpu(ctrl->wIndex); ++ s16 suspend = le16_to_cpu(ctrl->wValue); ++ bool newly_connected = false; ++ ++ if (!gadget->current_budget_enabled) ++ return -EOPNOTSUPP; ++ /* Some UDCs omit DEFAULT/ADDRESS notifications during enumeration. */ ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ if (!gadget->current_bus_connected) { ++ gadget->current_bus_connected = true; ++ if (!gadget->current_base_ma) ++ gadget->current_base_ma = 100; ++ newly_connected = true; ++ } ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ if (newly_connected) ++ gadget_current_notify(gadget); ++ if (ctrl->bRequest != 0x40 || ++ (ctrl->bRequestType != 0x40 && ctrl->bRequestType != 0xc0)) ++ return -EOPNOTSUPP; ++ /* Only the exact no-data device vendor request is reserved on this board. */ ++ if (ctrl->wLength || extra < -2400 || extra > 2400 || ++ suspend < 0 || suspend > 2400) { ++ usb_gadget_revoke_current_grant(gadget); ++ return -EINVAL; ++ } ++ spin_lock_irqsave(&gadget->state_lock, flags); ++ if (gadget->current_suspended) { ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ usb_gadget_revoke_current_grant(gadget); ++ return -ESHUTDOWN; ++ } ++ gadget->current_extra_ma = extra; ++ gadget->current_host_grant = true; ++ spin_unlock_irqrestore(&gadget->state_lock, flags); ++ dev_info_ratelimited(&gadget->dev, ++ "Apple host current grant: extra %d mA, suspend %d mA\n", ++ extra, suspend); ++ gadget_current_notify(gadget); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(usb_gadget_handle_current_grant); ++ + /* ------------------------------------------------------------------------- */ + + /** +@@ -669,6 +890,8 @@ int usb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) + gadget->mA = mA; + + out: ++ /* Authorization is independent of optional PHY power programming. */ ++ gadget_current_set_base(gadget, mA); + trace_usb_gadget_vbus_draw(gadget, ret); + + return ret; +@@ -1137,10 +1360,28 @@ void usb_gadget_set_state(struct usb_gadget *gadget, + + spin_lock_irqsave(&gadget->state_lock, flags); + gadget->state = state; ++ if (state <= USB_STATE_DEFAULT) { ++ gadget->current_host_grant = false; ++ gadget->current_extra_ma = 0; ++ gadget->current_suspended = false; ++ gadget->current_configured = false; ++ gadget->current_bus_connected = state == USB_STATE_DEFAULT; ++ gadget->current_base_ma = state == USB_STATE_DEFAULT ? 100 : 0; ++ } else if (state == USB_STATE_SUSPENDED) { ++ gadget->current_host_grant = false; ++ gadget->current_extra_ma = 0; ++ gadget->current_suspended = true; ++ } else if (state == USB_STATE_ADDRESS || state == USB_STATE_CONFIGURED) { ++ gadget->current_bus_connected = true; ++ if (state == USB_STATE_CONFIGURED) ++ gadget->current_configured = true; ++ gadget->current_suspended = false; ++ } + if (!gadget->teardown) + schedule_work(&gadget->work); + spin_unlock_irqrestore(&gadget->state_lock, flags); + trace_usb_gadget_set_state(gadget, 0); ++ gadget_current_notify(gadget); + } + EXPORT_SYMBOL_GPL(usb_gadget_set_state); + +@@ -1389,6 +1630,14 @@ void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, + gadget->teardown = false; + INIT_WORK(&gadget->work, usb_gadget_state_work); + gadget->dev.parent = parent; ++ gadget->current_base_ma = 0; ++ gadget->current_extra_ma = 0; ++ gadget->current_host_grant = false; ++ gadget->current_suspended = false; ++ gadget->current_configured = false; ++ gadget->current_bus_connected = false; ++ gadget->current_budget_enabled = ++ device_property_read_bool(parent, "apple,host-current-budget"); + + if (release) + gadget->dev.release = release; +@@ -1581,6 +1830,7 @@ void usb_del_gadget(struct usb_gadget *gadget) + return; + + dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); ++ gadget_current_set_base(gadget, 0); + + mutex_lock(&udc_lock); + list_del(&udc->list); +diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c +index f61f095cedabbe8b95bbc232b9af4ef758dac9ac..75c487fbb001dd76ce125e73bde2534cac25fc74 100644 +--- a/drivers/usb/host/ehci-platform.c ++++ b/drivers/usb/host/ehci-platform.c +@@ -45,6 +45,16 @@ + + #define BCM_USB_FIFO_THRESHOLD 0x00800040 + ++/* ++ * J71t: the Lightning port never sources 5 V VBUS (accessory LDO5 is not a ++ * general-purpose 5 V supply); a powered camera adapter feeds its own hub. ++ * This is enumeration accounting only. Read at each host entry. ++ */ ++static unsigned int apple_port_budget_ma = 500; ++module_param(apple_port_budget_ma, uint, 0644); ++MODULE_PARM_DESC(apple_port_budget_ma, ++ "J71t Lightning root-port power budget in mA (default 500; 100 = ADT port-current-limit)"); ++ + struct ehci_platform_priv { + struct clk *clks[EHCI_MAX_CLKS]; + struct reset_control *rsts; +@@ -377,6 +387,10 @@ static int ehci_platform_probe(struct platform_device *dev) + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + ++ /* J71t: see apple_port_budget_ma. */ ++ if (of_device_is_compatible(dev->dev.of_node, "apple,s8000-ehci")) ++ hcd->power_budget = READ_ONCE(apple_port_budget_ma); ++ + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c +index c801527d5bd29200a4621a6250b5a2fbbddc650c..0736b6374ef502ec0117930986b7441571902e0a 100644 +--- a/drivers/usb/host/ohci-platform.c ++++ b/drivers/usb/host/ohci-platform.c +@@ -36,6 +36,16 @@ + #define OHCI_MAX_CLKS 4 + #define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv) + ++/* ++ * J71t: the Lightning port never sources 5 V VBUS (accessory LDO5 is not a ++ * general-purpose 5 V supply); a powered camera adapter feeds its own hub. ++ * This is enumeration accounting only. Read at each host entry. ++ */ ++static unsigned int apple_port_budget_ma = 500; ++module_param(apple_port_budget_ma, uint, 0644); ++MODULE_PARM_DESC(apple_port_budget_ma, ++ "J71t Lightning root-port power budget in mA (default 500; 100 = ADT port-current-limit)"); ++ + struct ohci_platform_priv { + struct clk *clks[OHCI_MAX_CLKS]; + struct reset_control *resets; +@@ -207,6 +217,10 @@ static int ohci_platform_probe(struct platform_device *dev) + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + ++ /* J71t: see apple_port_budget_ma. */ ++ if (of_device_is_compatible(dev->dev.of_node, "apple,s8000-ohci")) ++ hcd->power_budget = READ_ONCE(apple_port_budget_ma); ++ + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig +index f8b31aa67526b4bfc7a1ee220227f7f23b5ff859..5690439b270456567b61a74e6fd81b7fd2902529 100644 +--- a/drivers/usb/roles/Kconfig ++++ b/drivers/usb/roles/Kconfig +@@ -26,4 +26,18 @@ config USB_ROLES_INTEL_XHCI + To compile the driver as a module, choose M here: the module will + be called intel-xhci-usb-role-switch. + ++config USB_ROLES_APPLE_S8000 ++ tristate "Apple S8000 (A9) Lightning port role switch" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF && I2C ++ select REGMAP ++ help ++ Role switch for the Apple A9 USB complex, which pairs a device-only ++ DWC2 with a separate EHCI + OHCI host pair behind one PHY. Device ++ mode stays the default; host mode is entered on request through ++ /sys/class/usb_role and reverts after a timeout unless confirmed. ++ ++ To compile the driver as a module, choose M here: the module will ++ be called apple-s8000-usb-role. ++ + endif # USB_ROLE_SWITCH +diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile +index 757a7d2797eb6a416d27a5d51ee481ddf37c9619..0b0b0968c2ef6f49402261b885c351fbb1f494f7 100644 +--- a/drivers/usb/roles/Makefile ++++ b/drivers/usb/roles/Makefile +@@ -3,3 +3,4 @@ + obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o + roles-y := class.o + obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o ++obj-$(CONFIG_USB_ROLES_APPLE_S8000) += apple-s8000-usb-role.o +diff --git a/drivers/usb/roles/apple-s8000-usb-role.c b/drivers/usb/roles/apple-s8000-usb-role.c +new file mode 100644 +index 0000000000000000000000000000000000000000..1cb31b3cbbba05d8018f06a57f87f955c1cdbd57 +--- /dev/null ++++ b/drivers/usb/roles/apple-s8000-usb-role.c +@@ -0,0 +1,1452 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Apple S8000 (A9) Lightning port USB role switch. ++ * ++ * The A9 USB complex has a device-only DWC2 (GHWCFG2.OP_MODE 4) and a ++ * separate EHCI + OHCI host pair, all behind one AUSB PHY. For a USB device ++ * on the port (cable type USBDevice) iOS's arbitrator applies publish mask ++ * 5: EHCI and OHCI (publish-criteria 4) are published and the device nub ++ * (criteria 3) stays. The PHY moves the pins with USB_CTL bit 0 and the ++ * host tunables (AppleS5L8960XUSBPhy::enableHostMode); its power-up ++ * (setPowerLevel >= 2) also sets USB_CTL bit 1, which iOS keeps set while ++ * the PHY is powered. ++ * ++ * Device mode is the default. Host mode is entered on request through the ++ * usb_role class and, unless confirmed, reverts to device mode after a ++ * timeout: the gadget network link is down while the port is a host. A host ++ * role claimed by the Lightning accessory policy does not revert; it lasts ++ * until the accessory is unplugged. ++ * ++ * The D2365 LDO5 rows below come from the iOS PMU LDO table (ADT dock ++ * function-acc_pwr = pmuL(0x5), function-acc_sw_en = pmuL(0x80000005)). ++ * iOS powers the camera adapter that way (IOAccessoryActivePowerMode 4, ++ * the acc_sw limit). The Tristar bit is AppleTriStarBuiltIn::setVBUSSink; ++ * on this board setting it makes the CBTL1610 report no VBUS, so the ++ * accessory path never uses it. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* USB_CTL (ADT otgphyctrl reg[1]) */ ++#define USB_CTL_HOST 0x00 ++#define USB_CTL_HOST_EN BIT(0) ++/* Set by AppleS5L8960XUSBPhy::setPowerLevel(>= 2), cleared below that. */ ++#define USB_CTL_PHY_ON BIT(1) ++/* ++ * Not written by iOS: its device-mode enable is PHY SIG (+0x04) bit 1, the ++ * bit phy-apple-ausb sets as VBUSDET_FORCE_EN. Kept for the old opt-in knob. ++ */ ++#define USB_CTL_DEVICE 0x04 ++#define USB_CTL_DEVICE_EN BIT(1) ++ ++/* ++ * USB complex DMA remap words, owned and programmed at probe by the parent ++ * usb-complex driver (drivers/bus/apple-usbcomplex.c), as m1n1 does too. ++ */ ++#define REMAP_DEVICE 0x1c ++#define REMAP_EHCI0 0x3c ++#define REMAP_OHCI0 0x5c ++#define REMAP_EHCI1 0x7c ++#define REMAP_LAST 0x9c ++#define REMAP_EN BIT(8) ++#define REMAP_UPPER GENMASK(3, 0) ++/* DRAM is at 0x8_0000_0000; the bus dma-ranges hand out 32-bit addresses. */ ++#define REMAP_TO_DRAM (REMAP_EN | FIELD_PREP(REMAP_UPPER, 0x8)) ++ ++/* Glue words: EHCI0 +0x00, OHCI0 +0x08, device +0x20 (ADT reg[1] entries) */ ++#define GLUE_SIZE 0x30 ++ ++/* D2365 LDO5, iOS AppleD2365PMU LDO table row 5 */ ++#define D2365_LDO5_ENABLE 0x0310 ++#define D2365_LDO5_EN BIT(0) ++#define D2365_LDO5_VSEL 0x2600 ++#define D2365_LDO5_SWITCH 0x2603 ++ ++/* CBTL1610 (Tristar) */ ++#define TRISTAR_DX_CONTROL 0x01 ++#define TRISTAR_VBUS_SINK 0x05 ++#define TRISTAR_VBUS_SINK_EN BIT(0) ++#define TRISTAR_STATUS 0x0c ++#define TRISTAR_REVISION 0x0f ++#define TRISTAR_J71T_REVISION 0x8a ++#define TRISTAR_SINK_SETTLE_MS 18 ++ ++#define GATE_PMU BIT(0) ++#define GATE_TRISTAR BIT(1) ++ ++static const char * const gate_names[] = { ++ [0] = "none", ++ [GATE_PMU] = "pmu", ++ [GATE_TRISTAR] = "tristar", ++ [GATE_PMU | GATE_TRISTAR] = "both", ++}; ++ ++static unsigned int revert_timeout = 60; ++module_param(revert_timeout, uint, 0644); ++MODULE_PARM_DESC(revert_timeout, ++ "Default seconds before an unconfirmed host role reverts to device (0: never)"); ++ ++static bool clear_device_enable; ++module_param(clear_device_enable, bool, 0644); ++MODULE_PARM_DESC(clear_device_enable, ++ "Also clear USB_CTL+4 bit 1 while in host mode (default: leave it; iOS never writes it)"); ++ ++static bool usb_ctl_phy_on = true; ++module_param(usb_ctl_phy_on, bool, 0644); ++MODULE_PARM_DESC(usb_ctl_phy_on, ++ "Set USB_CTL+0 bit 1 in host mode, as iOS does while its PHY is powered (default: on)"); ++ ++/* ++ * Debug: once host mode is up, write this value to USB_CTL+0 and log the ++ * readback. Only bits 0 (host select, always kept) and 1 are used. ++ */ ++static unsigned int usb_ctl_force; ++module_param(usb_ctl_force, uint, 0644); ++MODULE_PARM_DESC(usb_ctl_force, ++ "Debug: after host entry write USB_CTL+0 = this | 1 (e.g. 3) and log readbacks (0: off)"); ++ ++#define USB_CTL_TRACE_MAX 8 ++ ++/* Only one switch exists on this SoC; the accessory API checks against it. */ ++static DEFINE_MUTEX(apple_usb_role_instance_lock); ++static struct apple_usb_role *apple_usb_role_instance; ++ ++struct apple_usb_role { ++ struct device *dev; ++ void __iomem *usb_ctl; ++ /* Shared, not claimed: the usb-complex driver owns the remap words. */ ++ void __iomem *remap; ++ void __iomem *glue; ++ /* Read-only views of registers that other drivers own */ ++ void __iomem *phy_regs; ++ void __iomem *udc_regs; ++ void __iomem *ehci_regs; ++ void __iomem *ohci_regs; ++ ++ struct phy *phy; ++ struct platform_device *udc; ++ struct device_node *ehci_np; ++ struct device_node *ohci_np; ++ struct regmap *pmic; ++ struct i2c_adapter *tristar; ++ u16 tristar_addr; ++ ++ struct usb_role_switch *sw; ++ struct delayed_work revert_work; ++ struct dentry *debugfs; ++ ++ /* Serializes role changes, the gates and the state below. */ ++ struct mutex lock; ++ enum usb_role role; ++ struct device *gadget; ++ struct platform_device *ehci; ++ struct platform_device *ohci; ++ unsigned long revert_at; ++ unsigned int revert_timeout; ++ unsigned int gates; ++ unsigned int gates_on; ++ /* Gates for a host role claimed by the accessory policy */ ++ unsigned int accessory_gates; ++ bool revert_pending; ++ bool phy_held; ++ bool ctl_host; ++ bool ctl_phy_on; ++ bool cleared_device_enable; ++ /* The next host entry is the accessory policy's */ ++ bool accessory_claim; ++ /* The current host role is the accessory policy's: no revert */ ++ bool accessory; ++ bool dying; ++ bool monitor_fault; ++ /* USB_CTL+0 readbacks at each step of the last host entry */ ++ const char *ctl_trace_what[USB_CTL_TRACE_MAX]; ++ u32 ctl_trace[USB_CTL_TRACE_MAX]; ++ unsigned int ctl_trace_n; ++ /* Last debug write (usb_ctl_force or debugfs usb_ctl) and its readbacks */ ++ u32 ctl_forced; ++ u32 ctl_forced_rb[3]; ++ bool ctl_force_done; ++}; ++ ++static void apple_usb_role_iounmap(void *addr) ++{ ++ iounmap(addr); ++} ++ ++/* Map a register range without claiming it: its owner is another driver. */ ++static void __iomem *apple_usb_role_view(struct apple_usb_role *ur, ++ struct device_node *np, int index) ++{ ++ void __iomem *base = of_iomap(np, index); ++ ++ if (!base || devm_add_action_or_reset(ur->dev, apple_usb_role_iounmap, ++ (void __force *)base)) ++ return NULL; ++ return base; ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Accessory power gates */ ++ ++static int apple_usb_role_tristar_read(struct apple_usb_role *ur, u8 reg) ++{ ++ union i2c_smbus_data data; ++ int ret; ++ ++ /* Selector + STOP, then a separate read, as the Tristar driver does. */ ++ i2c_lock_bus(ur->tristar, I2C_LOCK_SEGMENT); ++ ret = __i2c_smbus_xfer(ur->tristar, ur->tristar_addr, 0, ++ I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE, NULL); ++ if (!ret) ++ ret = __i2c_smbus_xfer(ur->tristar, ur->tristar_addr, 0, ++ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data); ++ i2c_unlock_bus(ur->tristar, I2C_LOCK_SEGMENT); ++ return ret ?: data.byte; ++} ++ ++static int apple_usb_role_tristar_sink(struct apple_usb_role *ur, bool on) ++{ ++ union i2c_smbus_data data; ++ int ret; ++ ++ if (!ur->tristar) ++ return -ENODEV; ++ ret = apple_usb_role_tristar_read(ur, TRISTAR_REVISION); ++ if (ret < 0) ++ return ret; ++ if (ret != TRISTAR_J71T_REVISION) ++ return -ENODEV; ++ ret = apple_usb_role_tristar_read(ur, TRISTAR_VBUS_SINK); ++ if (ret < 0) ++ return ret; ++ data.byte = on ? ret | TRISTAR_VBUS_SINK_EN : ret & ~TRISTAR_VBUS_SINK_EN; ++ ret = i2c_smbus_xfer(ur->tristar, ur->tristar_addr, 0, I2C_SMBUS_WRITE, ++ TRISTAR_VBUS_SINK, I2C_SMBUS_BYTE_DATA, &data); ++ if (!ret && on) ++ msleep(TRISTAR_SINK_SETTLE_MS); ++ return ret; ++} ++ ++static int apple_usb_role_ldo5(struct apple_usb_role *ur, bool on) ++{ ++ if (!ur->pmic) ++ return -ENODEV; ++ return regmap_update_bits(ur->pmic, D2365_LDO5_ENABLE, D2365_LDO5_EN, ++ on ? D2365_LDO5_EN : 0); ++} ++ ++static int apple_usb_role_ldo5_switch(struct apple_usb_role *ur, bool on) ++{ ++ if (!ur->pmic) ++ return -ENODEV; ++ /* iOS writes the whole register: 1 or 0. */ ++ return regmap_write(ur->pmic, D2365_LDO5_SWITCH, on); ++} ++ ++static void apple_usb_role_gates_off(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ if (ur->gates_on & GATE_TRISTAR) { ++ ret = apple_usb_role_tristar_sink(ur, false); ++ if (ret) ++ dev_err(ur->dev, "Tristar VBUS sink off: %d\n", ret); ++ } ++ if (ur->gates_on & GATE_PMU) { ++ ret = apple_usb_role_ldo5_switch(ur, false); ++ if (ret) ++ dev_err(ur->dev, "LDO5 switch off: %d\n", ret); ++ ret = apple_usb_role_ldo5(ur, false); ++ if (ret) ++ dev_err(ur->dev, "LDO5 off: %d\n", ret); ++ } ++ ur->gates_on = 0; ++} ++ ++static int apple_usb_role_gates_on(struct apple_usb_role *ur, unsigned int gates) ++{ ++ int ret; ++ ++ if (gates & GATE_PMU) { ++ /* Order is inferred: rail first, then the accessory switch. */ ++ ur->gates_on |= GATE_PMU; ++ ret = apple_usb_role_ldo5(ur, true); ++ if (!ret) ++ ret = apple_usb_role_ldo5_switch(ur, true); ++ if (ret) { ++ dev_err(ur->dev, "accessory power (LDO5): %d\n", ret); ++ return ret; ++ } ++ } ++ if (gates & GATE_TRISTAR) { ++ ur->gates_on |= GATE_TRISTAR; ++ ret = apple_usb_role_tristar_sink(ur, true); ++ if (ret) { ++ dev_err(ur->dev, "Tristar VBUS sink: %d\n", ret); ++ return ret; ++ } ++ } ++ return 0; ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Role changes */ ++ ++static int apple_usb_role_is_gadget(struct device *dev, const void *data) ++{ ++ return dev->bus && !strcmp(dev->bus->name, "gadget"); ++} ++ ++/* ++ * iOS terminates the device nub first; unbinding the gadget driver also ++ * makes DWC2 (dr_mode peripheral) drop its PHY and pull-up. ++ */ ++static void apple_usb_role_release_gadget(struct apple_usb_role *ur) ++{ ++ struct device *gadget; ++ ++ gadget = device_find_child(&ur->udc->dev, NULL, apple_usb_role_is_gadget); ++ if (!gadget) { ++ dev_info(ur->dev, "no gadget registered on %s\n", dev_name(&ur->udc->dev)); ++ return; ++ } ++ if (!gadget->driver) { ++ /* Nothing to give back later either. */ ++ put_device(gadget); ++ return; ++ } ++ dev_info(ur->dev, "unbinding gadget driver %s\n", gadget->driver->name); ++ device_release_driver(gadget); ++ ur->gadget = gadget; ++} ++ ++static void apple_usb_role_restore_gadget(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ if (!ur->gadget) ++ return; ++ /* configfs keeps its UDC name, so the bus rematches the same gadget. */ ++ ret = device_attach(ur->gadget); ++ if (ret < 0) ++ dev_err(ur->dev, "gadget rebind: %d\n", ret); ++ else if (!ret) ++ dev_warn(ur->dev, "no gadget driver matched; write the configfs UDC\n"); ++ else ++ dev_info(ur->dev, "gadget driver %s bound again\n", ur->gadget->driver->name); ++ put_device(ur->gadget); ++ ur->gadget = NULL; ++} ++ ++/* Only repairs a value lost since the usb-complex driver's probe. */ ++static void apple_usb_role_remap(struct apple_usb_role *ur, unsigned int offset) ++{ ++ const u32 want = REMAP_TO_DRAM; ++ u32 val = readl(ur->remap + offset); ++ ++ if (val == want) ++ return; ++ dev_info(ur->dev, "DMA remap +%#x: %#x -> %#x\n", offset, val, want); ++ writel(want, ur->remap + offset); ++} ++ ++static struct platform_device *apple_usb_role_add_hc(struct apple_usb_role *ur, ++ struct device_node *np) ++{ ++ struct platform_device *pdev = of_platform_device_create(np, NULL, ur->dev); ++ ++ if (!pdev) { ++ dev_err(ur->dev, "%pOFn: could not create the controller\n", np); ++ return NULL; ++ } ++ /* ehci/ohci-platform prefer asynchronous probing. */ ++ wait_for_device_probe(); ++ if (!pdev->dev.driver) { ++ dev_err(ur->dev, "%s: no driver bound\n", dev_name(&pdev->dev)); ++ of_platform_device_destroy(&pdev->dev, NULL); ++ return NULL; ++ } ++ return pdev; ++} ++ ++/* Undo whatever enter_host() got to, in reverse order. Idempotent. */ ++static void apple_usb_role_host_off(struct apple_usb_role *ur) ++{ ++ if (ur->ohci) { ++ of_platform_device_destroy(&ur->ohci->dev, NULL); ++ ur->ohci = NULL; ++ } ++ if (ur->ehci) { ++ of_platform_device_destroy(&ur->ehci->dev, NULL); ++ ur->ehci = NULL; ++ } ++ /* iOS order: enableHostMode(0) clears bit 0, the power-down bit 1. */ ++ if (ur->ctl_host) { ++ writel(readl(ur->usb_ctl + USB_CTL_HOST) & ~USB_CTL_HOST_EN, ++ ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_host = false; ++ } ++ if (ur->ctl_phy_on) { ++ writel(readl(ur->usb_ctl + USB_CTL_HOST) & ~USB_CTL_PHY_ON, ++ ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_phy_on = false; ++ } ++ if (ur->cleared_device_enable) { ++ writel(readl(ur->usb_ctl + USB_CTL_DEVICE) | USB_CTL_DEVICE_EN, ++ ur->usb_ctl + USB_CTL_DEVICE); ++ ur->cleared_device_enable = false; ++ } ++ if (ur->phy_held) { ++ phy_set_mode(ur->phy, PHY_MODE_USB_DEVICE); ++ phy_power_off(ur->phy); ++ phy_exit(ur->phy); ++ ur->phy_held = false; ++ } ++ apple_usb_role_gates_off(ur); ++} ++ ++/* Record USB_CTL+0 after a step of the host entry (2026-10-27: bit 1 was lost). */ ++static void apple_usb_role_ctl_trace(struct apple_usb_role *ur, const char *what) ++{ ++ if (ur->ctl_trace_n >= USB_CTL_TRACE_MAX) ++ return; ++ ur->ctl_trace_what[ur->ctl_trace_n] = what; ++ ur->ctl_trace[ur->ctl_trace_n++] = readl(ur->usb_ctl + USB_CTL_HOST); ++} ++ ++static void apple_usb_role_ctl_trace_log(struct apple_usb_role *ur) ++{ ++ char line[192]; ++ size_t len = 0; ++ unsigned int i; ++ ++ for (i = 0; i < ur->ctl_trace_n; i++) ++ len += scnprintf(line + len, sizeof(line) - len, "%s%s %#x", i ? ", " : "", ++ ur->ctl_trace_what[i], ur->ctl_trace[i]); ++ dev_info(ur->dev, "USB_CTL+0 trace: %s\n", len ? line : "none"); ++} ++ ++/* Debug write of USB_CTL+0 in host mode; bit 0 (host select) always kept. */ ++static void apple_usb_role_ctl_force(struct apple_usb_role *ur, u32 value) ++{ ++ u32 val = (value & USB_CTL_PHY_ON) | USB_CTL_HOST_EN; ++ ++ writel(val, ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_forced_rb[0] = readl(ur->usb_ctl + USB_CTL_HOST); ++ usleep_range(1000, 1500); ++ ur->ctl_forced_rb[1] = readl(ur->usb_ctl + USB_CTL_HOST); ++ msleep(100); ++ ur->ctl_forced_rb[2] = readl(ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_forced = val; ++ ur->ctl_force_done = true; ++ /* Bit 1 written by us is cleared again when leaving host mode. */ ++ if (val & USB_CTL_PHY_ON) ++ ur->ctl_phy_on = true; ++ dev_info(ur->dev, "USB_CTL+0 forced to %#x: read back %#x, %#x after 1 ms, %#x after 100 ms\n", ++ val, ur->ctl_forced_rb[0], ur->ctl_forced_rb[1], ur->ctl_forced_rb[2]); ++} ++ ++static int apple_usb_role_enter_host(struct apple_usb_role *ur, unsigned int gates) ++{ ++ unsigned int force = READ_ONCE(usb_ctl_force); ++ u32 ctl; ++ int ret; ++ ++ ur->ctl_trace_n = 0; ++ ur->ctl_force_done = false; ++ apple_usb_role_ctl_trace(ur, "entry"); ++ apple_usb_role_release_gadget(ur); ++ apple_usb_role_ctl_trace(ur, "gadget-off"); ++ ++ ret = apple_usb_role_gates_on(ur, gates); ++ if (ret) ++ goto fail; ++ ++ apple_usb_role_remap(ur, REMAP_EHCI0); ++ apple_usb_role_remap(ur, REMAP_OHCI0); ++ ++ /* ++ * iOS setPowerLevel(1 -> 2): USB_CTL bit 1 first, then the PHY reset ++ * sequence. Write it before phy_init(), which powers the PHY up again ++ * once the gadget has let go of it. ++ */ ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ if (usb_ctl_phy_on && !(ctl & USB_CTL_PHY_ON)) { ++ writel(ctl | USB_CTL_PHY_ON, ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_phy_on = true; ++ } ++ apple_usb_role_ctl_trace(ur, "bit1"); ++ ++ ret = phy_init(ur->phy); ++ if (ret) ++ goto fail; ++ ret = phy_power_on(ur->phy); ++ if (ret) { ++ phy_exit(ur->phy); ++ goto fail; ++ } ++ ur->phy_held = true; ++ apple_usb_role_ctl_trace(ur, "phy-on"); ++ ++ /* enableHostMode: host select, then tunables and the reset (set_mode). */ ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ if (usb_ctl_phy_on) ++ ctl |= USB_CTL_PHY_ON; ++ writel(ctl | USB_CTL_HOST_EN, ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_host = true; ++ apple_usb_role_ctl_trace(ur, "host"); ++ if (clear_device_enable) { ++ writel(readl(ur->usb_ctl + USB_CTL_DEVICE) & ~USB_CTL_DEVICE_EN, ++ ur->usb_ctl + USB_CTL_DEVICE); ++ ur->cleared_device_enable = true; ++ } ++ ret = phy_set_mode(ur->phy, PHY_MODE_USB_HOST); ++ if (ret) ++ goto fail; ++ apple_usb_role_ctl_trace(ur, "phy-host"); ++ ++ /* EHCI before its OHCI companion, as for any companion pair. */ ++ ur->ehci = apple_usb_role_add_hc(ur, ur->ehci_np); ++ if (!ur->ehci) { ++ ret = -ENODEV; ++ goto fail; ++ } ++ apple_usb_role_ctl_trace(ur, "ehci"); ++ ur->ohci = apple_usb_role_add_hc(ur, ur->ohci_np); ++ if (!ur->ohci) ++ dev_warn(ur->dev, "no OHCI: low/full-speed devices will not work\n"); ++ apple_usb_role_ctl_trace(ur, "ohci"); ++ ++ /* If something dropped bit 1 on the way, assert it once more. */ ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ if (usb_ctl_phy_on && !(ctl & USB_CTL_PHY_ON)) { ++ writel(ctl | USB_CTL_PHY_ON, ur->usb_ctl + USB_CTL_HOST); ++ apple_usb_role_ctl_trace(ur, "reassert"); ++ } ++ apple_usb_role_ctl_trace_log(ur); ++ if (force) ++ apple_usb_role_ctl_force(ur, force); ++ ++ dev_info(ur->dev, "host mode%s (USB_CTL %#x/%#x, accessory power: %s)\n", ++ ur->accessory_claim ? " for the Lightning accessory" : "", ++ readl(ur->usb_ctl + USB_CTL_HOST), readl(ur->usb_ctl + USB_CTL_DEVICE), ++ gate_names[ur->gates_on]); ++ return 0; ++ ++fail: ++ apple_usb_role_host_off(ur); ++ apple_usb_role_restore_gadget(ur); ++ return ret; ++} ++ ++static void apple_usb_role_enter_device(struct apple_usb_role *ur) ++{ ++ apple_usb_role_host_off(ur); ++ apple_usb_role_restore_gadget(ur); ++ dev_info(ur->dev, "device mode\n"); ++} ++ ++static void apple_usb_role_arm_revert(struct apple_usb_role *ur) ++{ ++ if (!ur->revert_timeout) { ++ ur->revert_pending = false; ++ return; ++ } ++ ur->revert_at = jiffies + secs_to_jiffies(ur->revert_timeout); ++ ur->revert_pending = true; ++ mod_delayed_work(system_wq, &ur->revert_work, ++ secs_to_jiffies(ur->revert_timeout)); ++ dev_info(ur->dev, "returning to device mode in %u s unless confirmed\n", ++ ur->revert_timeout); ++} ++ ++static int apple_usb_role_set(struct usb_role_switch *sw, enum usb_role role) ++{ ++ struct apple_usb_role *ur = usb_role_switch_get_drvdata(sw); ++ int ret = 0; ++ ++ /* There is no idle state: the gadget is the resting role. */ ++ if (role == USB_ROLE_NONE) ++ role = USB_ROLE_DEVICE; ++ ++ mutex_lock(&ur->lock); ++ if (ur->dying) { ++ ret = -ESHUTDOWN; ++ } else if (role == USB_ROLE_HOST && ur->monitor_fault) { ++ ret = -EUCLEAN; ++ } else if (role == ur->role) { ++ if (role == USB_ROLE_HOST && ur->accessory_claim) { ++ /* The accessory takes over a manual host role. */ ++ ur->accessory = true; ++ ur->revert_pending = false; ++ cancel_delayed_work(&ur->revert_work); ++ } else if (role == USB_ROLE_HOST && ur->revert_pending) { ++ /* Asking for host again restarts an unconfirmed countdown. */ ++ apple_usb_role_arm_revert(ur); ++ } ++ } else if (role == USB_ROLE_HOST) { ++ bool accessory = ur->accessory_claim; ++ ++ ret = apple_usb_role_enter_host(ur, accessory ? ur->accessory_gates : ur->gates); ++ if (!ret) { ++ WRITE_ONCE(ur->role, USB_ROLE_HOST); ++ ur->accessory = accessory; ++ if (!accessory) ++ apple_usb_role_arm_revert(ur); ++ } ++ } else { ++ ur->revert_pending = false; ++ /* Not _sync: this may be the revert work itself. */ ++ cancel_delayed_work(&ur->revert_work); ++ apple_usb_role_enter_device(ur); ++ WRITE_ONCE(ur->role, USB_ROLE_DEVICE); ++ ur->accessory = false; ++ } ++ ur->accessory_claim = false; ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++/* Caller holds apple_usb_role_instance_lock, which keeps the instance alive. */ ++static struct apple_usb_role *apple_usb_role_from_switch(struct usb_role_switch *sw) ++{ ++ struct apple_usb_role *ur; ++ ++ lockdep_assert_held(&apple_usb_role_instance_lock); ++ if (IS_ERR_OR_NULL(sw) || !apple_usb_role_instance) ++ return NULL; ++ ur = usb_role_switch_get_drvdata(sw); ++ return ur == apple_usb_role_instance ? ur : NULL; ++} ++ ++/** ++ * apple_s8000_usb_role_claim - mark the next host entry as the accessory's ++ * @sw: the Lightning role switch ++ * @claim: true before usb_role_switch_set_role(USB_ROLE_HOST); false to drop ++ * a claim that was not used ++ * ++ * A claimed host role has no revert timeout and uses the accessory power ++ * gates. Any device-role request ends it. ++ * ++ * Return: 0, or -ENODEV if @sw is not this driver's switch. ++ */ ++int apple_s8000_usb_role_claim(struct usb_role_switch *sw, bool claim) ++{ ++ struct apple_usb_role *ur; ++ int ret = 0; ++ ++ mutex_lock(&apple_usb_role_instance_lock); ++ ur = apple_usb_role_from_switch(sw); ++ if (ur) { ++ mutex_lock(&ur->lock); ++ if (claim && ur->dying) ++ ret = -ESHUTDOWN; ++ ur->accessory_claim = claim && !ret; ++ mutex_unlock(&ur->lock); ++ } else { ++ ret = -ENODEV; ++ } ++ mutex_unlock(&apple_usb_role_instance_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_s8000_usb_role_claim); ++ ++/** ++ * apple_s8000_usb_role_accessory - whether the accessory owns the host role ++ * @sw: the Lightning role switch ++ * ++ * Return: true while the port is a host on the accessory policy's request. ++ */ ++bool apple_s8000_usb_role_accessory(struct usb_role_switch *sw) ++{ ++ struct apple_usb_role *ur; ++ bool ret = false; ++ ++ mutex_lock(&apple_usb_role_instance_lock); ++ ur = apple_usb_role_from_switch(sw); ++ if (ur) { ++ mutex_lock(&ur->lock); ++ ret = ur->role == USB_ROLE_HOST && ur->accessory; ++ mutex_unlock(&ur->lock); ++ } ++ mutex_unlock(&apple_usb_role_instance_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_s8000_usb_role_accessory); ++ ++/** ++ * apple_s8000_usb_role_monitor - serialize an analog monitor with role changes ++ * @sw: the Lightning role switch ++ * @sample: sleepable callback; must not query or change the USB role ++ * @data: callback argument ++ * ++ * Lock order: instance, role hardware, consumer, gadget budget. The callback ++ * must restore DX_CONTROL before returning. EUCLEAN inhibits host entry until ++ * a subsequent callback repairs the route. No role transition can overlap it. ++ * ++ * Return: callback result, or an error without calling it outside device role. ++ */ ++int apple_s8000_usb_role_monitor(struct usb_role_switch *sw, ++ int (*sample)(void *data), void *data) ++{ ++ struct apple_usb_role *ur; ++ int ret = -ENODEV; ++ ++ mutex_lock(&apple_usb_role_instance_lock); ++ ur = apple_usb_role_from_switch(sw); ++ if (ur) { ++ mutex_lock(&ur->lock); ++ if (ur->dying || ur->role != USB_ROLE_DEVICE) { ++ ret = -EBUSY; ++ } else { ++ ret = sample(data); ++ ur->monitor_fault = ret == -EUCLEAN; ++ } ++ mutex_unlock(&ur->lock); ++ } ++ mutex_unlock(&apple_usb_role_instance_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_s8000_usb_role_monitor); ++ ++static enum usb_role apple_usb_role_get(struct usb_role_switch *sw) ++{ ++ struct apple_usb_role *ur = usb_role_switch_get_drvdata(sw); ++ ++ return READ_ONCE(ur->role); ++} ++ ++static void apple_usb_role_revert(struct work_struct *work) ++{ ++ struct apple_usb_role *ur = container_of(to_delayed_work(work), ++ struct apple_usb_role, revert_work); ++ ++ dev_warn(ur->dev, "host mode was not confirmed, returning to device mode\n"); ++ usb_role_switch_set_role(ur->sw, USB_ROLE_DEVICE); ++} ++ ++/* -------------------------------------------------------------------- */ ++/* sysfs */ ++ ++static ssize_t revert_timeout_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ ++ return sysfs_emit(buf, "%u\n", READ_ONCE(ur->revert_timeout)); ++} ++ ++static ssize_t revert_timeout_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret; ++ ++ ret = kstrtouint(buf, 0, &val); ++ if (ret) ++ return ret; ++ /* Takes effect at the next host entry or host re-request. */ ++ WRITE_ONCE(ur->revert_timeout, val); ++ return count; ++} ++static DEVICE_ATTR_RW(revert_timeout); ++ ++static ssize_t revert_pending_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ long left = 0; ++ ++ mutex_lock(&ur->lock); ++ if (ur->revert_pending) ++ left = max_t(long, 0, (long)(ur->revert_at - jiffies)); ++ mutex_unlock(&ur->lock); ++ return sysfs_emit(buf, "%u\n", jiffies_to_msecs(left) / 1000); ++} ++static DEVICE_ATTR_RO(revert_pending); ++ ++static ssize_t confirm_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ bool val; ++ int ret; ++ ++ ret = kstrtobool(buf, &val); ++ if (ret) ++ return ret; ++ if (!val) ++ return count; ++ mutex_lock(&ur->lock); ++ if (ur->role != USB_ROLE_HOST) { ++ ret = -EINVAL; ++ } else if (ur->revert_pending) { ++ ur->revert_pending = false; ++ cancel_delayed_work(&ur->revert_work); ++ dev_info(ur->dev, "host mode confirmed\n"); ++ } ++ mutex_unlock(&ur->lock); ++ return ret ?: count; ++} ++static DEVICE_ATTR_WO(confirm); ++ ++static ssize_t vbus_gates_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ unsigned int i, gates = READ_ONCE(ur->gates); ++ ssize_t len = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(gate_names); i++) ++ len += sysfs_emit_at(buf, len, i == gates ? "[%s] " : "%s ", gate_names[i]); ++ buf[len - 1] = '\n'; ++ return len; ++} ++ ++static ssize_t vbus_gates_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ int gates = sysfs_match_string(gate_names, buf); ++ ++ if (gates < 0) ++ return gates; ++ if (((gates & GATE_PMU) && !ur->pmic) || ++ ((gates & GATE_TRISTAR) && !ur->tristar)) ++ return -ENODEV; ++ /* Takes effect at the next host entry. */ ++ WRITE_ONCE(ur->gates, gates); ++ return count; ++} ++static DEVICE_ATTR_RW(vbus_gates); ++ ++static ssize_t accessory_gates_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ unsigned int i, gates = READ_ONCE(ur->accessory_gates); ++ ssize_t len = 0; ++ ++ for (i = 0; i <= GATE_PMU; i++) ++ len += sysfs_emit_at(buf, len, i == gates ? "[%s] " : "%s ", gate_names[i]); ++ buf[len - 1] = '\n'; ++ return len; ++} ++ ++/* The Tristar bit removes VBUS from the CBTL1610 status: never for the accessory. */ ++static ssize_t accessory_gates_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ int gates = sysfs_match_string(gate_names, buf); ++ ++ if (gates < 0) ++ return gates; ++ if (gates & GATE_TRISTAR) ++ return -EINVAL; ++ if ((gates & GATE_PMU) && !ur->pmic) ++ return -ENODEV; ++ WRITE_ONCE(ur->accessory_gates, gates); ++ return count; ++} ++static DEVICE_ATTR_RW(accessory_gates); ++ ++static ssize_t accessory_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ bool accessory; ++ ++ mutex_lock(&ur->lock); ++ accessory = ur->role == USB_ROLE_HOST && ur->accessory; ++ mutex_unlock(&ur->lock); ++ return sysfs_emit(buf, "%d\n", accessory); ++} ++static DEVICE_ATTR_RO(accessory); ++ ++static struct attribute *apple_usb_role_attrs[] = { ++ &dev_attr_revert_timeout.attr, ++ &dev_attr_revert_pending.attr, ++ &dev_attr_confirm.attr, ++ &dev_attr_vbus_gates.attr, ++ &dev_attr_accessory_gates.attr, ++ &dev_attr_accessory.attr, ++ NULL ++}; ++ATTRIBUTE_GROUPS(apple_usb_role); ++ ++/* -------------------------------------------------------------------- */ ++/* debugfs */ ++ ++static void apple_usb_role_dump(struct seq_file *s, const char *name, ++ void __iomem *base, unsigned int first, ++ unsigned int last) ++{ ++ unsigned int off; ++ ++ seq_printf(s, "%s:", name); ++ for (off = first; off <= last; off += 4) ++ seq_printf(s, " +%02x=%08x", off, readl(base + off)); ++ seq_putc(s, '\n'); ++} ++ ++static void apple_usb_role_dump_pmic(struct seq_file *s, struct apple_usb_role *ur, ++ unsigned int reg) ++{ ++ unsigned int val; ++ int ret = regmap_read(ur->pmic, reg, &val); ++ ++ if (ret) ++ seq_printf(s, " %04x=err%d", reg, ret); ++ else ++ seq_printf(s, " %04x=%02x", reg, val); ++} ++ ++static int apple_usb_role_regs_show(struct seq_file *s, void *unused) ++{ ++ static const u8 tristar_regs[] = { ++ TRISTAR_REVISION, TRISTAR_DX_CONTROL, TRISTAR_VBUS_SINK, TRISTAR_STATUS, ++ }; ++ struct apple_usb_role *ur = s->private; ++ unsigned int i; ++ int ret; ++ ++ mutex_lock(&ur->lock); ++ seq_printf(s, "role: %s%s%s, accessory power: %s (armed for host: %s, accessory: %s)\n", ++ usb_role_string(ur->role), ++ ur->revert_pending ? " (revert pending)" : "", ++ ur->role == USB_ROLE_HOST && ur->accessory ? " (Lightning accessory)" : "", ++ gate_names[ur->gates_on], gate_names[ur->gates], ++ gate_names[ur->accessory_gates]); ++ seq_printf(s, "phy: init %d power %d, held by switch: %d\n", ++ ur->phy->init_count, ur->phy->power_count, ur->phy_held); ++ seq_printf(s, "usb_ctl: +00=%08x +04=%08x (host select %d, phy-on bit1 %d set by switch %d)\n", ++ readl(ur->usb_ctl + USB_CTL_HOST), readl(ur->usb_ctl + USB_CTL_DEVICE), ++ ur->ctl_host, !!(readl(ur->usb_ctl + USB_CTL_HOST) & USB_CTL_PHY_ON), ++ ur->ctl_phy_on); ++ seq_puts(s, "usb_ctl trace (last host entry):"); ++ for (i = 0; i < ur->ctl_trace_n; i++) ++ seq_printf(s, " %s=%x", ur->ctl_trace_what[i], ur->ctl_trace[i]); ++ seq_putc(s, '\n'); ++ if (ur->ctl_force_done) ++ seq_printf(s, "usb_ctl forced: wrote %x, read %x, %x after 1 ms, %x after 100 ms\n", ++ ur->ctl_forced, ur->ctl_forced_rb[0], ur->ctl_forced_rb[1], ++ ur->ctl_forced_rb[2]); ++ apple_usb_role_dump(s, "remap", ur->remap, REMAP_DEVICE, REMAP_LAST); ++ ++ if (ur->phy_regs) { ++ ret = phy_pm_runtime_get_sync(ur->phy); ++ /* Without runtime PM (the default) the PHY domain stays on. */ ++ if (ret >= 0 || !pm_runtime_enabled(&ur->phy->dev)) ++ apple_usb_role_dump(s, "phy", ur->phy_regs, 0, 0x1c); ++ else ++ seq_printf(s, "phy: runtime resume failed: %d\n", ret); ++ if (ret >= 0) ++ phy_pm_runtime_put(ur->phy); ++ } ++ ++ /* ++ * Controller registers and their glue words only while the controller's ++ * driver holds its power domain: the domain may be off otherwise. ++ */ ++ if (ur->udc_regs && ur->udc->dev.driver) { ++ seq_printf(s, "dwc2: gotgctl=%08x gusbcfg=%08x ghwcfg2=%08x hprt0=%08x dctl=%08x dsts=%08x\n", ++ readl(ur->udc_regs + 0x000), readl(ur->udc_regs + 0x00c), ++ readl(ur->udc_regs + 0x048), readl(ur->udc_regs + 0x440), ++ readl(ur->udc_regs + 0x804), readl(ur->udc_regs + 0x808)); ++ apple_usb_role_dump(s, "device glue", ur->glue, 0x20, GLUE_SIZE - 4); ++ } ++ if (ur->ehci_regs && ur->ehci && ur->ehci->dev.driver) { ++ u32 cap = readl(ur->ehci_regs); ++ void __iomem *op = ur->ehci_regs + (cap & 0xff); ++ ++ seq_printf(s, "ehci: cap=%08x hcs=%08x hcc=%08x cmd=%08x sts=%08x seg=%08x cf=%08x portsc1=%08x\n", ++ cap, readl(ur->ehci_regs + 0x04), readl(ur->ehci_regs + 0x08), ++ readl(op + 0x00), readl(op + 0x04), readl(op + 0x10), ++ readl(op + 0x40), readl(op + 0x44)); ++ apple_usb_role_dump(s, "ehci insnreg", ur->ehci_regs, 0x90, 0xa4); ++ apple_usb_role_dump(s, "ehci glue", ur->glue, 0x00, 0x04); ++ } ++ if (ur->ohci_regs && ur->ohci && ur->ohci->dev.driver) { ++ seq_printf(s, "ohci: rev=%08x ctrl=%08x sts=%08x rhdesca=%08x rhdescb=%08x rhsts=%08x port1=%08x\n", ++ readl(ur->ohci_regs + 0x00), readl(ur->ohci_regs + 0x04), ++ readl(ur->ohci_regs + 0x08), readl(ur->ohci_regs + 0x48), ++ readl(ur->ohci_regs + 0x4c), readl(ur->ohci_regs + 0x50), ++ readl(ur->ohci_regs + 0x54)); ++ apple_usb_role_dump(s, "ohci glue", ur->glue, 0x08, 0x0c); ++ } ++ ++ if (ur->tristar) { ++ seq_puts(s, "tristar:"); ++ for (i = 0; i < ARRAY_SIZE(tristar_regs); i++) { ++ ret = apple_usb_role_tristar_read(ur, tristar_regs[i]); ++ if (ret < 0) ++ seq_printf(s, " %02x=err%d", tristar_regs[i], ret); ++ else ++ seq_printf(s, " %02x=%02x", tristar_regs[i], ret); ++ } ++ seq_putc(s, '\n'); ++ } ++ if (ur->pmic) { ++ seq_puts(s, "pmu ldo5:"); ++ apple_usb_role_dump_pmic(s, ur, D2365_LDO5_ENABLE); ++ apple_usb_role_dump_pmic(s, ur, D2365_LDO5_VSEL); ++ apple_usb_role_dump_pmic(s, ur, D2365_LDO5_SWITCH); ++ seq_putc(s, '\n'); ++ } ++ mutex_unlock(&ur->lock); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_usb_role_regs); ++ ++/* Raw gate knobs for measurements; they bypass the host-session bookkeeping. */ ++static int apple_usb_role_ldo5_set(void *data, u64 val) ++{ ++ struct apple_usb_role *ur = data; ++ int ret; ++ ++ mutex_lock(&ur->lock); ++ ret = apple_usb_role_ldo5(ur, val); ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++static int apple_usb_role_ldo5_get(void *data, u64 *val) ++{ ++ struct apple_usb_role *ur = data; ++ unsigned int reg; ++ int ret = regmap_read(ur->pmic, D2365_LDO5_ENABLE, ®); ++ ++ if (ret) ++ return ret; ++ *val = reg & D2365_LDO5_EN; ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(apple_usb_role_ldo5_fops, apple_usb_role_ldo5_get, ++ apple_usb_role_ldo5_set, "%llu\n"); ++ ++static int apple_usb_role_ldo5_switch_set(void *data, u64 val) ++{ ++ struct apple_usb_role *ur = data; ++ int ret; ++ ++ mutex_lock(&ur->lock); ++ ret = apple_usb_role_ldo5_switch(ur, val); ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++static int apple_usb_role_ldo5_switch_get(void *data, u64 *val) ++{ ++ struct apple_usb_role *ur = data; ++ unsigned int reg; ++ int ret = regmap_read(ur->pmic, D2365_LDO5_SWITCH, ®); ++ ++ if (ret) ++ return ret; ++ *val = reg; ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(apple_usb_role_ldo5_switch_fops, ++ apple_usb_role_ldo5_switch_get, ++ apple_usb_role_ldo5_switch_set, "%llu\n"); ++ ++static int apple_usb_role_sink_set(void *data, u64 val) ++{ ++ struct apple_usb_role *ur = data; ++ int ret; ++ ++ mutex_lock(&ur->lock); ++ ret = apple_usb_role_tristar_sink(ur, val); ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++static int apple_usb_role_sink_get(void *data, u64 *val) ++{ ++ struct apple_usb_role *ur = data; ++ int ret = apple_usb_role_tristar_read(ur, TRISTAR_VBUS_SINK); ++ ++ if (ret < 0) ++ return ret; ++ *val = ret & TRISTAR_VBUS_SINK_EN; ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(apple_usb_role_sink_fops, apple_usb_role_sink_get, ++ apple_usb_role_sink_set, "%llu\n"); ++ ++/* Debug: write USB_CTL+0 (bit 1, bit 0 kept) while in host mode; read: current value. */ ++static int apple_usb_role_usb_ctl_set(void *data, u64 val) ++{ ++ struct apple_usb_role *ur = data; ++ int ret = 0; ++ ++ if (val & ~(u64)(USB_CTL_HOST_EN | USB_CTL_PHY_ON)) ++ return -EINVAL; ++ mutex_lock(&ur->lock); ++ if (ur->role != USB_ROLE_HOST || !ur->ctl_host) ++ ret = -EBUSY; ++ else ++ apple_usb_role_ctl_force(ur, val); ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++static int apple_usb_role_usb_ctl_get(void *data, u64 *val) ++{ ++ struct apple_usb_role *ur = data; ++ ++ *val = readl(ur->usb_ctl + USB_CTL_HOST); ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(apple_usb_role_usb_ctl_fops, apple_usb_role_usb_ctl_get, ++ apple_usb_role_usb_ctl_set, "0x%llx\n"); ++ ++static void apple_usb_role_debugfs(struct apple_usb_role *ur) ++{ ++ ur->debugfs = debugfs_create_dir("apple-usb-role", NULL); ++ debugfs_create_file("regs", 0400, ur->debugfs, ur, &apple_usb_role_regs_fops); ++ debugfs_create_file_unsafe("usb_ctl", 0600, ur->debugfs, ur, ++ &apple_usb_role_usb_ctl_fops); ++ if (ur->pmic) { ++ debugfs_create_file_unsafe("pmu_ldo5", 0600, ur->debugfs, ur, ++ &apple_usb_role_ldo5_fops); ++ debugfs_create_file_unsafe("pmu_ldo5_switch", 0600, ur->debugfs, ur, ++ &apple_usb_role_ldo5_switch_fops); ++ } ++ if (ur->tristar) ++ debugfs_create_file_unsafe("tristar_vbus_sink", 0600, ur->debugfs, ur, ++ &apple_usb_role_sink_fops); ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Probe */ ++ ++static void apple_usb_role_put_device(void *data) ++{ ++ put_device(data); ++} ++ ++static void apple_usb_role_put_node(void *data) ++{ ++ of_node_put(data); ++} ++ ++static void apple_usb_role_put_adapter(void *data) ++{ ++ i2c_put_adapter(data); ++} ++ ++static void apple_usb_role_unregister(void *data) ++{ ++ usb_role_switch_unregister(data); ++} ++ ++static int apple_usb_role_get_pmic(struct apple_usb_role *ur) ++{ ++ struct device_node *np; ++ struct i2c_client *client; ++ int ret; ++ ++ np = of_parse_phandle(ur->dev->of_node, "apple,pmic", 0); ++ if (!np) ++ return 0; ++ client = of_find_i2c_device_by_node(np); ++ of_node_put(np); ++ if (!client) ++ return -EPROBE_DEFER; ++ ret = devm_add_action_or_reset(ur->dev, apple_usb_role_put_device, &client->dev); ++ if (ret) ++ return ret; ++ ur->pmic = dev_get_regmap(&client->dev, NULL); ++ if (!ur->pmic) ++ return -EPROBE_DEFER; ++ if (!device_link_add(ur->dev, &client->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) ++ return -EINVAL; ++ return 0; ++} ++ ++static int apple_usb_role_get_tristar(struct apple_usb_role *ur) ++{ ++ struct of_phandle_args args; ++ int ret; ++ ++ ret = of_parse_phandle_with_fixed_args(ur->dev->of_node, "apple,tristar", ++ 1, 0, &args); ++ if (ret) ++ return ret == -ENOENT ? 0 : ret; ++ ur->tristar = of_get_i2c_adapter_by_node(args.np); ++ of_node_put(args.np); ++ if (!ur->tristar) ++ return -EPROBE_DEFER; ++ ret = devm_add_action_or_reset(ur->dev, apple_usb_role_put_adapter, ur->tristar); ++ if (ret) ++ return ret; ++ if (args.args[0] > 0x7f) ++ return -EINVAL; ++ ur->tristar_addr = args.args[0]; ++ return 0; ++} ++ ++/* ++ * The remap words belong to the parent usb-complex node, whose driver ++ * claims them; the glue words may be claimed by a controller node later. ++ * Both are mapped without requesting the region. ++ */ ++static int apple_usb_role_get_complex(struct apple_usb_role *ur) ++{ ++ struct device_node *np = ur->dev->of_node; ++ struct device_node *complex; ++ int index; ++ ++ complex = of_get_parent(np); ++ if (complex && of_device_is_compatible(complex, "apple,s5l8960x-usb-complex")) ++ ur->remap = apple_usb_role_view(ur, complex, 0); ++ of_node_put(complex); ++ if (!ur->remap) ++ return -ENODEV; ++ ++ index = of_property_match_string(np, "reg-names", "glue"); ++ if (index < 0) ++ return index; ++ ur->glue = apple_usb_role_view(ur, np, index); ++ return ur->glue ? 0 : -ENOMEM; ++} ++ ++static int apple_usb_role_get_udc(struct apple_usb_role *ur) ++{ ++ struct device_node *np; ++ int ret; ++ ++ np = of_parse_phandle(ur->dev->of_node, "apple,usb-device", 0); ++ if (!np) ++ return -EINVAL; ++ ur->udc = of_find_device_by_node(np); ++ if (ur->udc) ++ ur->udc_regs = apple_usb_role_view(ur, np, 0); ++ of_node_put(np); ++ if (!ur->udc) ++ return -EPROBE_DEFER; ++ ret = devm_add_action_or_reset(ur->dev, apple_usb_role_put_device, &ur->udc->dev); ++ return ret; ++} ++ ++static int apple_usb_role_get_hcs(struct apple_usb_role *ur) ++{ ++ struct device_node *np = ur->dev->of_node; ++ int ret; ++ ++ ur->ehci_np = of_get_compatible_child(np, "generic-ehci"); ++ if (!ur->ehci_np) ++ return -ENODEV; ++ ret = devm_add_action_or_reset(ur->dev, apple_usb_role_put_node, ur->ehci_np); ++ if (ret) ++ return ret; ++ ur->ohci_np = of_get_compatible_child(np, "generic-ohci"); ++ if (!ur->ohci_np) ++ return -ENODEV; ++ ret = devm_add_action_or_reset(ur->dev, apple_usb_role_put_node, ur->ohci_np); ++ if (ret) ++ return ret; ++ /* Their power domains are off until host mode: only read when bound. */ ++ ur->ehci_regs = apple_usb_role_view(ur, ur->ehci_np, 0); ++ ur->ohci_regs = apple_usb_role_view(ur, ur->ohci_np, 0); ++ return 0; ++} ++ ++static void apple_usb_role_teardown(void *data) ++{ ++ struct apple_usb_role *ur = data; ++ ++ mutex_lock(&apple_usb_role_instance_lock); ++ if (apple_usb_role_instance == ur) ++ apple_usb_role_instance = NULL; ++ mutex_unlock(&apple_usb_role_instance_lock); ++ ++ mutex_lock(&ur->lock); ++ ur->dying = true; ++ ur->revert_pending = false; ++ mutex_unlock(&ur->lock); ++ cancel_delayed_work_sync(&ur->revert_work); ++ debugfs_remove_recursive(ur->debugfs); ++ ++ mutex_lock(&ur->lock); ++ if (ur->role == USB_ROLE_HOST) { ++ apple_usb_role_enter_device(ur); ++ WRITE_ONCE(ur->role, USB_ROLE_DEVICE); ++ } ++ mutex_unlock(&ur->lock); ++} ++ ++static int apple_usb_role_probe(struct platform_device *pdev) ++{ ++ struct usb_role_switch_desc desc = {}; ++ struct device *dev = &pdev->dev; ++ struct apple_usb_role *ur; ++ int ret; ++ ++ ur = devm_kzalloc(dev, sizeof(*ur), GFP_KERNEL); ++ if (!ur) ++ return -ENOMEM; ++ ur->dev = dev; ++ ur->role = USB_ROLE_DEVICE; ++ ur->revert_timeout = revert_timeout; ++ /* iOS powers the camera adapter (IOAccessoryActivePowerMode 4). */ ++ ur->accessory_gates = GATE_PMU; ++ platform_set_drvdata(pdev, ur); ++ ret = devm_mutex_init(dev, &ur->lock); ++ if (ret) ++ return ret; ++ INIT_DELAYED_WORK(&ur->revert_work, apple_usb_role_revert); ++ ++ ur->usb_ctl = devm_platform_ioremap_resource_byname(pdev, "usb-ctl"); ++ if (IS_ERR(ur->usb_ctl)) ++ return PTR_ERR(ur->usb_ctl); ++ ret = apple_usb_role_get_complex(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "usb-complex registers\n"); ++ ++ ur->phy = devm_phy_get(dev, "usb2-phy"); ++ if (IS_ERR(ur->phy)) ++ return dev_err_probe(dev, PTR_ERR(ur->phy), "PHY\n"); ++ if (ur->phy->dev.parent) ++ ur->phy_regs = apple_usb_role_view(ur, ur->phy->dev.parent->of_node, 0); ++ ++ ret = apple_usb_role_get_udc(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "device controller\n"); ++ ret = apple_usb_role_get_hcs(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "host controller nodes\n"); ++ ret = apple_usb_role_get_pmic(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "PMIC\n"); ++ if (!ur->pmic) ++ ur->accessory_gates = 0; ++ ret = apple_usb_role_get_tristar(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "Tristar\n"); ++ ++ /* ++ * No register access here: device mode must keep working exactly as ++ * before even if one of these banks turns out to need a clock. ++ */ ++ desc.fwnode = dev_fwnode(dev); ++ desc.name = "lightning"; ++ desc.set = apple_usb_role_set; ++ desc.get = apple_usb_role_get; ++ desc.allow_userspace_control = true; ++ desc.driver_data = ur; ++ ur->sw = usb_role_switch_register(dev, &desc); ++ if (IS_ERR(ur->sw)) ++ return dev_err_probe(dev, PTR_ERR(ur->sw), "role switch\n"); ++ ret = devm_add_action_or_reset(dev, apple_usb_role_unregister, ur->sw); ++ if (ret) ++ return ret; ++ /* Runs before the switch goes away: no role change can race it. */ ++ ret = devm_add_action_or_reset(dev, apple_usb_role_teardown, ur); ++ if (ret) ++ return ret; ++ ++ apple_usb_role_debugfs(ur); ++ mutex_lock(&apple_usb_role_instance_lock); ++ apple_usb_role_instance = ur; ++ mutex_unlock(&apple_usb_role_instance_lock); ++ dev_info(dev, "device role; host on request (revert after %u s) or for the Lightning accessory\n", ++ ur->revert_timeout); ++ return 0; ++} ++ ++/* ++ * Kexec and reboot: the next kernel's gadget needs the device routing and ++ * tunables. The host controllers were already shut down (children go ++ * first) and are left registered; only the shared hardware is put back. ++ */ ++static void apple_usb_role_shutdown(struct platform_device *pdev) ++{ ++ struct apple_usb_role *ur = platform_get_drvdata(pdev); ++ ++ mutex_lock(&ur->lock); ++ ur->dying = true; ++ ur->revert_pending = false; ++ cancel_delayed_work(&ur->revert_work); ++ if (ur->ctl_host) { ++ writel(readl(ur->usb_ctl + USB_CTL_HOST) & ~USB_CTL_HOST_EN, ++ ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_host = false; ++ } ++ if (ur->ctl_phy_on) { ++ writel(readl(ur->usb_ctl + USB_CTL_HOST) & ~USB_CTL_PHY_ON, ++ ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_phy_on = false; ++ } ++ if (ur->cleared_device_enable) { ++ writel(readl(ur->usb_ctl + USB_CTL_DEVICE) | USB_CTL_DEVICE_EN, ++ ur->usb_ctl + USB_CTL_DEVICE); ++ ur->cleared_device_enable = false; ++ } ++ if (ur->phy_held) ++ phy_set_mode(ur->phy, PHY_MODE_USB_DEVICE); ++ apple_usb_role_gates_off(ur); ++ mutex_unlock(&ur->lock); ++} ++ ++static int apple_usb_role_prepare(struct device *dev) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ ++ /* Rebinding the gadget is not possible mid-suspend. */ ++ if (READ_ONCE(ur->role) == USB_ROLE_HOST) { ++ dev_warn(dev, "host mode: return to device mode before suspending\n"); ++ return -EBUSY; ++ } ++ return 0; ++} ++ ++static const struct dev_pm_ops apple_usb_role_pm_ops = { ++ .prepare = pm_sleep_ptr(apple_usb_role_prepare), ++}; ++ ++static const struct of_device_id apple_usb_role_of_match[] = { ++ { .compatible = "apple,s8000-usb-role-switch" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, apple_usb_role_of_match); ++ ++static struct platform_driver apple_usb_role_driver = { ++ .probe = apple_usb_role_probe, ++ .shutdown = apple_usb_role_shutdown, ++ .driver = { ++ .name = "apple-s8000-usb-role", ++ .of_match_table = apple_usb_role_of_match, ++ .dev_groups = apple_usb_role_groups, ++ .pm = pm_ptr(&apple_usb_role_pm_ops), ++ }, ++}; ++module_platform_driver(apple_usb_role_driver); ++ ++MODULE_DESCRIPTION("Apple S8000 Lightning port USB role switch"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/usb/apple-s8000-role.h b/include/linux/usb/apple-s8000-role.h +new file mode 100644 +index 0000000000000000000000000000000000000000..7b6ebf193f5c716f97b3fc1903cb2217d2d346d6 +--- /dev/null ++++ b/include/linux/usb/apple-s8000-role.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Apple S8000 (A9) Lightning role switch: host role owned by an accessory. ++ * ++ * The Lightning accessory policy (apple_tristar_power) claims the next host ++ * entry before it calls usb_role_switch_set_role(). A claimed host role has ++ * no revert timeout and uses the accessory power gates; it ends when the ++ * policy, or anyone else, sets the device role. ++ */ ++#ifndef __LINUX_USB_APPLE_S8000_ROLE_H ++#define __LINUX_USB_APPLE_S8000_ROLE_H ++ ++#include ++#include ++#include ++ ++struct usb_role_switch; ++ ++#if IS_REACHABLE(CONFIG_USB_ROLES_APPLE_S8000) ++int apple_s8000_usb_role_monitor(struct usb_role_switch *sw, ++ int (*sample)(void *data), void *data); ++int apple_s8000_usb_role_claim(struct usb_role_switch *sw, bool claim); ++bool apple_s8000_usb_role_accessory(struct usb_role_switch *sw); ++#else ++static inline int apple_s8000_usb_role_monitor(struct usb_role_switch *sw, ++ int (*sample)(void *data), void *data) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline int apple_s8000_usb_role_claim(struct usb_role_switch *sw, bool claim) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static inline bool apple_s8000_usb_role_accessory(struct usb_role_switch *sw) ++{ ++ return false; ++} ++#endif ++ ++#endif /* __LINUX_USB_APPLE_S8000_ROLE_H */ +diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h +index 8285b19a25e02cce4d21aef9b46fb5aeb2b0999a..9878fd7326afc14a94d55f94ed7e40dfbc061323 100644 +--- a/include/linux/usb/gadget.h ++++ b/include/linux/usb/gadget.h +@@ -360,6 +360,27 @@ struct usb_gadget_ops { + int (*check_config)(struct usb_gadget *gadget); + }; + ++struct device_node; ++struct notifier_block; ++ ++/** ++ * struct usb_gadget_current_budget - instantaneous USB power authorization ++ * @of_node: controller node, valid for the synchronous callback or caller query ++ * @current_ma: active authorization in mA, zero when disconnected or suspended ++ * @host_grant: an explicit host vendor request supplied the additional budget ++ * @configured: a configuration was selected in this bus session, even if suspended ++ * @connected: actual host bus activity was observed, preserved across suspend ++ * @suspended: the bus is suspended; an earlier host grant has been revoked ++ */ ++struct usb_gadget_current_budget { ++ const struct device_node *of_node; ++ unsigned int current_ma; ++ bool host_grant; ++ bool configured; ++ bool connected; ++ bool suspended; ++}; ++ + /** + * struct usb_gadget - represents a usb device + * @work: (internal use) Workqueue to be used for sysfs_notify() +@@ -386,6 +407,13 @@ struct usb_gadget_ops { + * @out_epnum: last used out ep number + * @in_epnum: last used in ep number + * @mA: last set mA value ++ * @current_base_ma: standard configuration current authorization ++ * @current_extra_ma: signed extra current from an opt-in host grant ++ * @current_host_grant: an explicit host grant is active ++ * @current_suspended: composite bus suspend revokes active authorization ++ * @current_configured: a configuration was selected in this bus session ++ * @current_bus_connected: a host reset/address/configuration or SETUP was seen ++ * @current_budget_enabled: board opted into USB current authorization + * @otg_caps: OTG capabilities of this gadget. + * @sg_supported: true if we can handle scatter-gather + * @is_otg: True if the USB device port uses a Mini-AB jack, so that the +@@ -462,6 +490,14 @@ struct usb_gadget { + unsigned out_epnum; + unsigned in_epnum; + unsigned mA; ++ /* Opt-in current authorization, protected by state_lock. */ ++ unsigned int current_base_ma; ++ s16 current_extra_ma; ++ bool current_host_grant; ++ bool current_suspended; ++ bool current_configured; ++ bool current_bus_connected; ++ bool current_budget_enabled; + struct usb_otg_caps *otg_caps; + + unsigned sg_supported:1; +@@ -637,6 +673,17 @@ static inline int gadget_is_otg(struct usb_gadget *g) + /*-------------------------------------------------------------------------*/ + + #if IS_ENABLED(CONFIG_USB_GADGET) ++ ++int usb_gadget_register_current_notifier(struct notifier_block *nb); ++void usb_gadget_current_source_detached(const struct device_node *node); ++int usb_gadget_unregister_current_notifier(struct notifier_block *nb); ++int usb_gadget_get_current_budget(const struct device_node *node, ++ struct usb_gadget_current_budget *budget); ++int usb_gadget_handle_current_grant(struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl); ++void usb_gadget_revoke_current_grant(struct usb_gadget *gadget); ++void usb_gadget_current_budget_suspend(struct usb_gadget *gadget, bool suspended); ++ + int usb_gadget_frame_number(struct usb_gadget *gadget); + int usb_gadget_wakeup(struct usb_gadget *gadget); + int usb_gadget_set_remote_wakeup(struct usb_gadget *gadget, int set); +@@ -651,6 +698,23 @@ int usb_gadget_deactivate(struct usb_gadget *gadget); + int usb_gadget_activate(struct usb_gadget *gadget); + int usb_gadget_check_config(struct usb_gadget *gadget); + #else ++ ++static inline int usb_gadget_register_current_notifier(struct notifier_block *nb) ++{ return -ENODEV; } ++static inline int usb_gadget_unregister_current_notifier(struct notifier_block *nb) ++{ return 0; } ++static inline void usb_gadget_current_source_detached(const struct device_node *node) ++{ } ++static inline int usb_gadget_get_current_budget(const struct device_node *node, ++ struct usb_gadget_current_budget *budget) ++{ return -ENODEV; } ++static inline int usb_gadget_handle_current_grant(struct usb_gadget *gadget, ++ const struct usb_ctrlrequest *ctrl) ++{ return -EOPNOTSUPP; } ++static inline void usb_gadget_revoke_current_grant(struct usb_gadget *gadget) { } ++static inline void usb_gadget_current_budget_suspend(struct usb_gadget *gadget, ++ bool suspended) { } ++ + static inline int usb_gadget_frame_number(struct usb_gadget *gadget) + { return 0; } + static inline int usb_gadget_wakeup(struct usb_gadget *gadget) diff --git a/kernel/patches/j71t/0008-j71t-sensors-backlight.patch b/kernel/patches/j71t/0008-j71t-sensors-backlight.patch new file mode 100644 index 00000000..56bc27b1 --- /dev/null +++ b/kernel/patches/j71t/0008-j71t-sensors-backlight.patch @@ -0,0 +1,2041 @@ +From e846959b24f0062b7583d6c16838fcf59a532872 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 08/12] iio/backlight: add the CT819 light sensor, pinctrl and + backlight +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The CT819 (TSL2583-compatible) ambient light sensor with per-unit +calibration, GPIO pin control and the DWI panel backlight. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../bindings/iio/light/tsl2583.yaml | 60 +- + .../bindings/leds/backlight/apple,dwi-bl.yaml | 4 + + drivers/iio/light/Kconfig | 10 + + drivers/iio/light/Makefile | 1 + + drivers/iio/light/tsl2583-test.c | 234 ++++++ + drivers/iio/light/tsl2583.c | 779 ++++++++++++++++-- + drivers/iio/light/tsl2583.h | 176 ++++ + drivers/pinctrl/pinctrl-apple-gpio.c | 100 ++- + drivers/video/backlight/apple_dwi_bl.c | 89 +- + 9 files changed, 1373 insertions(+), 80 deletions(-) + create mode 100644 drivers/iio/light/tsl2583-test.c + create mode 100644 drivers/iio/light/tsl2583.h + +diff --git a/Documentation/devicetree/bindings/iio/light/tsl2583.yaml b/Documentation/devicetree/bindings/iio/light/tsl2583.yaml +index 7b92ba8cbb9fbdd5b73ffa976036f2a0aa159289..91f454010a996e0be18ec2beb0ef1b4da3719f05 100644 +--- a/Documentation/devicetree/bindings/iio/light/tsl2583.yaml ++++ b/Documentation/devicetree/bindings/iio/light/tsl2583.yaml +@@ -10,14 +10,23 @@ maintainers: + - Brian Masney + + description: | +- Ambient light sensing with an i2c interface. ++ Ambient light sensing with an i2c interface. The Apple CT819 and CT821 ++ factory calibration is stored in SysCfg. On systems where the SysCfg NVMEM ++ provider is created by boot firmware, the static board tree omits the ++ NVMEM link and firmware adds both NVMEM properties before kernel handoff. + + properties: + compatible: +- enum: +- - amstaos,tsl2580 +- - amstaos,tsl2581 +- - amstaos,tsl2583 ++ oneOf: ++ - enum: ++ - amstaos,tsl2580 ++ - amstaos,tsl2581 ++ - amstaos,tsl2583 ++ - items: ++ - enum: ++ - apple,ct819 ++ - apple,ct821 ++ - const: amstaos,tsl2581 + + reg: + maxItems: 1 +@@ -28,10 +37,45 @@ properties: + vcc-supply: + description: Regulator that provides power to the sensor + ++ firmware-name: ++ description: Private LSCI record from this device, when NVMEM is unavailable. ++ maxItems: 1 ++ ++ nvmem-cells: ++ description: ++ Apple factory LSCI ambient-light calibration data. Boot firmware must ++ populate this together with nvmem-cell-names when the provider is not ++ statically representable. ++ maxItems: 1 ++ ++ nvmem-cell-names: ++ items: ++ - const: calibration ++ + required: + - compatible + - reg + ++allOf: ++ - if: ++ properties: ++ compatible: ++ contains: ++ enum: ++ - apple,ct819 ++ - apple,ct821 ++ then: ++ dependentRequired: ++ nvmem-cells: ++ - nvmem-cell-names ++ nvmem-cell-names: ++ - nvmem-cells ++ else: ++ properties: ++ firmware-name: false ++ nvmem-cells: false ++ nvmem-cell-names: false ++ + additionalProperties: false + + examples: +@@ -41,8 +85,10 @@ examples: + #size-cells = <0>; + + light-sensor@29 { +- compatible = "amstaos,tsl2581"; +- reg = <0x29>; ++ compatible = "apple,ct821", "amstaos,tsl2581"; ++ reg = <0x29>; ++ nvmem-cells = <&als_calibration>; ++ nvmem-cell-names = "calibration"; + }; + }; + ... +diff --git a/Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml b/Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml +index 29caeb356e6f02d8945ab1b1f5f7572ff8592ead..3f443e10e9da0e68373e2ac6b86b5253d6dc7599 100644 +--- a/Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml ++++ b/Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml +@@ -32,6 +32,10 @@ properties: + - const: apple,dwi-bl + + reg: ++ description: | ++ WLED channel registers on legacy systems. On S8000/S8003, map the ++ complete 16 KiB DWI block, including the configuration registers ++ at offsets 0x00, 0x04 and 0xd0 and the WLED channel at 0x80. + maxItems: 1 + + power-domains: +diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig +index f23bbce12c72c7333897b5e640cd9e29c3f16b57..c8d8084a3a0f0a3f762965cf8d84846c43cc38a9 100644 +--- a/drivers/iio/light/Kconfig ++++ b/drivers/iio/light/Kconfig +@@ -638,6 +638,16 @@ config TSL2583 + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + ++config TSL2583_KUNIT_TEST ++ tristate "KUnit tests for TSL2583" if !KUNIT_ALL_TESTS ++ depends on KUNIT ++ default KUNIT_ALL_TESTS ++ help ++ Build tests for the Apple CT819 and CT821 factory-calibration ++ gain-factor parser, including rejection of zero and overflowing gain ++ factors from LSCI. These tests do not require hardware. If built as a ++ module, it will be called tsl2583-test. ++ + config TSL2591 + tristate "TAOS TSL2591 ambient light sensor" + depends on I2C +diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile +index 64e354c49ed8d7f4291b39137ba3a16848ebb70c..fdbef07f8faa1fafb21b25510080d5885b061dd8 100644 +--- a/drivers/iio/light/Makefile ++++ b/drivers/iio/light/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_TCS3414) += tcs3414.o + obj-$(CONFIG_TCS3472) += tcs3472.o + obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o + obj-$(CONFIG_TSL2583) += tsl2583.o ++obj-$(CONFIG_TSL2583_KUNIT_TEST) += tsl2583-test.o + obj-$(CONFIG_TSL2591) += tsl2591.o + obj-$(CONFIG_TSL2772) += tsl2772.o + obj-$(CONFIG_TSL4531) += tsl4531.o +diff --git a/drivers/iio/light/tsl2583-test.c b/drivers/iio/light/tsl2583-test.c +new file mode 100644 +index 0000000000000000000000000000000000000000..703ca131dc75304a85632ae8b5acc4e4d78ccb96 +--- /dev/null ++++ b/drivers/iio/light/tsl2583-test.c +@@ -0,0 +1,234 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++ ++#include "tsl2583.h" ++ ++static void ct821_gain_factor_valid_test(struct kunit *test) ++{ ++ u32 factor; ++ ++ KUNIT_ASSERT_EQ(test, 0, ++ apple_als_calculate_gain_factor(65536, 510, ++ CT821_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, 131072U, factor); ++} ++ ++static void ct821_gain_factor_zero_test(struct kunit *test) ++{ ++ u32 factor = 1; ++ ++ KUNIT_EXPECT_EQ(test, -EINVAL, ++ apple_als_calculate_gain_factor(65536, 0, ++ CT821_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, -EINVAL, ++ apple_als_calculate_gain_factor(0, 255, ++ CT821_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, 1U, factor); ++} ++ ++static void ct821_gain_factor_overflow_test(struct kunit *test) ++{ ++ u32 factor; ++ ++ KUNIT_ASSERT_EQ(test, 0, ++ apple_als_calculate_gain_factor(65536, 0xff00, ++ CT821_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_ASSERT_EQ(test, 1U << 24, factor); ++ KUNIT_EXPECT_EQ(test, -ERANGE, ++ apple_als_calculate_gain_factor(factor, 0xff00, ++ CT821_CAL_RATIO_SCALE, ++ &factor)); ++} ++ ++static void ct819_gain_factor_valid_test(struct kunit *test) ++{ ++ u32 factor; ++ ++ /* ++ * The gain ratios of the factory record of an iPad 7 fold into the ++ * nominal 8x, 16x and 128x gain steps of the part. ++ */ ++ KUNIT_ASSERT_EQ(test, 0, ++ apple_als_calculate_gain_factor(65536, 2010, ++ CT819_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, 514560U, factor); ++ KUNIT_ASSERT_EQ(test, 0, ++ apple_als_calculate_gain_factor(factor, 521, ++ CT819_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, 1047210U, factor); ++ KUNIT_ASSERT_EQ(test, 0, ++ apple_als_calculate_gain_factor(factor, 1862, ++ CT819_CAL_RATIO_SCALE, ++ &factor)); ++ KUNIT_EXPECT_EQ(test, 7616816U, factor); ++} ++ ++/* ++ * A calibration with round numbers: 8x gain and 400 ms (148 cycles of ++ * 2.7 ms) as the reference, a1/b1 = 1/2 up to a ch1/ch0 ratio of 0.4, a2/b2 = ++ * 0.8/1.2 above it, fudge 204/255 = 0.8, and the iPad 7 gain ratios as above. ++ */ ++static const struct apple_als_calibration ct819_test_cal = { ++ .lux_fudge = 204, ++ .integration_time = 400, ++ .gain = 1, ++ .a_coeff1 = 65536, ++ .b_coeff1 = 131072, ++ .upper_range = 26214, ++ .a_coeff2 = 52429, ++ .b_coeff2 = 78643, ++ .gain_factor = { ++ { 65536, 65536 }, ++ { 514560, 514560 }, ++ { 1047210, 1047210 }, ++ { 7616816, 7616816 }, ++ }, ++}; ++ ++#define CT819_TEST_CYCLE_US 2700 ++#define CT819_TEST_REF_CYCLES 148 ++#define CT819_TEST_UNITY_TRIM 1000 ++ ++static int ct819_test_lux(u32 cycles, unsigned int gain, u32 lux_max, ++ u16 ch0, u16 ch1) ++{ ++ return apple_als_calc_lux(&ct819_test_cal, gain, cycles, ++ CT819_TEST_CYCLE_US, CT819_COUNTS_PER_CYCLE, ++ CT819_TEST_UNITY_TRIM, lux_max, ch0, ch1); ++} ++ ++static void ct819_lux_curve_test(struct kunit *test) ++{ ++ /* ch1/ch0 = 0.1 uses the first segment: (1000 * 1 - 100 * 2) * 0.8 */ ++ KUNIT_EXPECT_EQ(test, 640, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 1000, 100)); ++ /* ch1/ch0 = 0.5 is past the limit: (1000 * 0.8 - 500 * 1.2) * 0.8 */ ++ KUNIT_EXPECT_EQ(test, 160, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 1000, 500)); ++ /* Mostly infrared light cannot go negative */ ++ KUNIT_EXPECT_EQ(test, 0, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 100, 1000)); ++ KUNIT_EXPECT_EQ(test, 0, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 0, 0)); ++} ++ ++static void ct819_lux_integration_time_test(struct kunit *test) ++{ ++ /* The same light in 50 ms (18 cycles) gives 18/148 of the counts */ ++ KUNIT_EXPECT_EQ(test, 947, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 1480, 148)); ++ KUNIT_EXPECT_EQ(test, 947, ++ ct819_test_lux(18, 1, 5000, 180, 18)); ++} ++ ++static void ct819_lux_gain_test(struct kunit *test) ++{ ++ /* 16x sees 1047210 / 514560 times the counts of 8x */ ++ KUNIT_EXPECT_EQ(test, 640, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 2, 5000, 2035, 204)); ++} ++ ++static void ct819_lux_saturation_test(struct kunit *test) ++{ ++ /* 1024 counts per cycle, 16 bits at most */ ++ KUNIT_EXPECT_EQ(test, 5000, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 65535, 100)); ++ KUNIT_EXPECT_EQ(test, 5000, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 1000, 65535)); ++ KUNIT_EXPECT_EQ(test, 1000000, ct819_test_lux(18, 1, 1000000, 18431, 0)); ++ KUNIT_EXPECT_NE(test, 1000000, ct819_test_lux(18, 1, 1000000, 18430, 0)); ++ /* One count short of the limit still computes, then clamps */ ++ KUNIT_EXPECT_EQ(test, 48000, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 65535, 60000, 0)); ++ KUNIT_EXPECT_EQ(test, 5000, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 1, 5000, 60000, 0)); ++} ++ ++static void apple_lux_16bit_limit_test(struct kunit *test) ++{ ++ /* Without a per-cycle limit only the 16 bit range clips (the CT821) */ ++ KUNIT_EXPECT_EQ(test, 65535, ++ apple_als_calc_lux(&ct819_test_cal, 1, 18, CT819_TEST_CYCLE_US, ++ 0, CT819_TEST_UNITY_TRIM, 65535, 18431, 0)); ++ KUNIT_EXPECT_NE(test, 1000000, ++ apple_als_calc_lux(&ct819_test_cal, 1, 18, CT819_TEST_CYCLE_US, ++ 0, CT819_TEST_UNITY_TRIM, 1000000, 18431, 0)); ++} ++ ++static void ct819_lux_invalid_test(struct kunit *test) ++{ ++ KUNIT_EXPECT_EQ(test, -EINVAL, ct819_test_lux(0, 1, 5000, 100, 10)); ++ KUNIT_EXPECT_EQ(test, -EINVAL, ++ ct819_test_lux(CT819_TEST_REF_CYCLES, 8, 5000, 100, 10)); ++} ++ ++static void ct819_lux_dark_counts_test(struct kunit *test) ++{ ++ struct apple_als_calibration cal = ct819_test_cal; ++ ++ cal.ch0_dark_counts = 4; ++ cal.ch1_dark_counts = 2; ++ /* (1004 - 4) and (102 - 2) are the counts of the first test */ ++ KUNIT_EXPECT_EQ(test, 640, ++ apple_als_calc_lux(&cal, 1, CT819_TEST_REF_CYCLES, ++ CT819_TEST_CYCLE_US, CT819_COUNTS_PER_CYCLE, ++ 1000, 5000, 1004, 102)); ++ /* The dark counts of an unlit sensor leave nothing to report */ ++ KUNIT_EXPECT_EQ(test, 0, ++ apple_als_calc_lux(&cal, 1, CT819_TEST_REF_CYCLES, ++ CT819_TEST_CYCLE_US, CT819_COUNTS_PER_CYCLE, ++ 1000, 5000, 3, 2)); ++} ++ ++static struct kunit_case ct819_gain_factor_test_cases[] = { ++ KUNIT_CASE(ct819_gain_factor_valid_test), ++ {} ++}; ++ ++static struct kunit_suite ct819_gain_factor_test_suite = { ++ .name = "ct819-gain-factor", ++ .test_cases = ct819_gain_factor_test_cases, ++}; ++ ++static struct kunit_case ct819_lux_test_cases[] = { ++ KUNIT_CASE(ct819_lux_curve_test), ++ KUNIT_CASE(ct819_lux_integration_time_test), ++ KUNIT_CASE(ct819_lux_gain_test), ++ KUNIT_CASE(ct819_lux_saturation_test), ++ KUNIT_CASE(apple_lux_16bit_limit_test), ++ KUNIT_CASE(ct819_lux_invalid_test), ++ KUNIT_CASE(ct819_lux_dark_counts_test), ++ {} ++}; ++ ++static struct kunit_suite ct819_lux_test_suite = { ++ .name = "ct819-lux", ++ .test_cases = ct819_lux_test_cases, ++}; ++ ++static struct kunit_case ct821_gain_factor_test_cases[] = { ++ KUNIT_CASE(ct821_gain_factor_valid_test), ++ KUNIT_CASE(ct821_gain_factor_zero_test), ++ KUNIT_CASE(ct821_gain_factor_overflow_test), ++ {} ++}; ++ ++static struct kunit_suite ct821_gain_factor_test_suite = { ++ .name = "ct821-gain-factor", ++ .test_cases = ct821_gain_factor_test_cases, ++}; ++ ++kunit_test_suites(&ct819_gain_factor_test_suite, ++ &ct819_lux_test_suite, ++ &ct821_gain_factor_test_suite); ++ ++MODULE_DESCRIPTION("KUnit tests for the Apple ALS calibration gain factors and lux computation"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c +index 53fd423aa7aef67652bca7faa81f94d6880b40d8..9d710101d6e2a7d04cbc1c89c0d77bad77ab4b54 100644 +--- a/drivers/iio/light/tsl2583.c ++++ b/drivers/iio/light/tsl2583.c +@@ -9,6 +9,8 @@ + + #include + #include ++#include ++#include + #include + #include + #include +@@ -16,15 +18,22 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include ++#include ++ ++#include "tsl2583.h" + + /* Device Registers and Masks */ + #define TSL2583_CNTRL 0x00 + #define TSL2583_ALS_TIME 0X01 + #define TSL2583_INTERRUPT 0x02 + #define TSL2583_GAIN 0x07 ++#define CT821_ALS_CONFIG 0x0c ++#define CT821_GAIN 0x0f + #define TSL2583_REVID 0x11 + #define TSL2583_CHIPID 0x12 + #define TSL2583_ALS_CHAN0LO 0x14 +@@ -38,18 +47,19 @@ + #define TSL2583_CMD_REG 0x80 + #define TSL2583_CMD_SPL_FN 0x60 + #define TSL2583_CMD_ALS_INT_CLR 0x01 ++#define CT821_CMD_ALS_INT_CLR 0xc1 + + /* tsl2583 cntrl reg masks */ + #define TSL2583_CNTL_ADC_ENBL 0x02 + #define TSL2583_CNTL_PWR_OFF 0x00 + #define TSL2583_CNTL_PWR_ON 0x01 ++#define CT821_CNTL_ENABLE 0x10 + +-/* tsl2583 status reg masks */ +-#define TSL2583_STA_ADC_VALID 0x01 +-#define TSL2583_STA_ADC_INTR 0x10 ++#define CT821_ALS_CONFIG_ENABLE 0x40 + +-/* Lux calculation constants */ +-#define TSL2583_LUX_CALC_OVER_FLOW 65535 ++/* tsl2583 status reg masks */ ++#define TSL2583_STA_ADC_VALID 0x10 ++#define TSL2583_STA_ADC_INTR 0x20 + + #define TSL2583_INTERRUPT_DISABLED 0x00 + +@@ -58,6 +68,25 @@ + + #define TSL2583_POWER_OFF_DELAY_MS 2000 + ++/* Nominal length of one ADC integration cycle on the generic TSL258x parts */ ++#define TSL2583_INTEGRATION_CYCLE_US 2700 ++ ++/* The LSCI record layout is versioned; see tsl2583.h for what they share. */ ++#define APPLE_ALS_CAL_HEADER_SIZE 8 ++ ++#define CT819_CAL_RECORD_SIZE_V2 0x40 ++#define CT819_CAL_GAIN_COUNT 4 ++#define CT819_CAL_RATIO_OFF 0x30 ++/* AppleEmbeddedLightSensor publishes ALSSensorMax = 5000 for the CT819. */ ++#define CT819_LUX_MAX 5000 ++ ++#define CT821_CAL_RECORD_SIZE_V3 0x54 ++#define CT821_CAL_RECORD_DATA_SIZE_V3 0x52 ++#define CT821_CAL_GAIN_COUNT 8 ++#define CT821_CAL_GAIN_COUNT_OFF 0x30 ++#define CT821_CAL_RATIO_OFF 0x36 ++#define CT821_INTEGRATION_CYCLE_US 2780 ++ + /* Per-device data */ + struct tsl2583_als_info { + u16 als_ch0; +@@ -102,6 +131,10 @@ struct tsl2583_chip { + struct tsl2583_settings als_settings; + int als_time_scale; + int als_saturation; ++ const struct tsl2583_chip_info *info; ++ struct apple_als_calibration cal; ++ u16 integration_cycles; ++ bool needs_reinit; + }; + + struct gainadj { +@@ -110,14 +143,276 @@ struct gainadj { + s16 mean; + }; + +-/* Index = (0 - 3) Used to validate the gain selection index */ +-static const struct gainadj gainadj[] = { ++/* ++ * Describes one supported part. @gainadj is indexed by the gain selection ++ * index programmed into @gain_reg. ++ * ++ * The Apple variants are TSL258x compatible parts with a factory LSCI ++ * calibration record in SysCfg. They select a different lux computation and ++ * therefore also different chip-specific register handling: ++ * ++ * @gain_reg: register holding the gain selection index ++ * @int_clr_data_cmd: if non-zero, the ALS interrupt is cleared by a byte ++ * write to this command instead of the TSL258x ++ * special function ++ * @cntl_enable: extra CNTRL bits the part needs while measuring ++ * @als_config: part needs its analog frontend programmed explicitly ++ * while the core is powered down ++ * @cal_record_size: size of the supported LSCI calibration record ++ * @cal_record_len: record length field of the supported record ++ * @cal_gain_count: number of gain steps described by that record ++ * @cal_gain_count_off: offset of the gain-count byte, zero if the record has ++ * none ++ * @cal_ratio_off: offset of the gain ratio pair of the second gain step ++ * @cal_ratio_scale: fixed-point scale of the recorded gain ratios ++ * @integration_cycle_us: length of one ADC integration cycle ++ * @counts_per_cycle: ADC counts per integration cycle, zero if only the 16 bit ++ * range limits them ++ * @lux_max: largest lux value the calibrated computation reports ++ */ ++struct tsl2583_chip_info { ++ const struct gainadj *gainadj; ++ unsigned int num_gainadj; ++ const struct iio_info *iio_info; ++ int default_als_time; ++ int default_als_gain; ++ bool apple; ++ u8 gain_reg; ++ u8 int_clr_data_cmd; ++ u8 cntl_enable; ++ bool als_config; ++ u8 cal_version; ++ u8 cal_record_size; ++ u8 cal_record_len; ++ unsigned int cal_gain_count; ++ unsigned int cal_gain_count_off; ++ unsigned int cal_ratio_off; ++ unsigned int cal_ratio_scale; ++ unsigned int integration_cycle_us; ++ unsigned int counts_per_cycle; ++ unsigned int lux_max; ++}; ++ ++static const struct gainadj tsl2583_gainadj[] = { + { 1, 1, 1 }, + { 8, 8, 8 }, + { 16, 16, 16 }, + { 107, 115, 111 } + }; + ++/* ++ * The CT819 uses the TSL2581 gain register and its four gain steps. The ++ * nominal factors below only select the step; the exact per-device gain comes ++ * from the LSCI calibration record. ++ */ ++static const struct gainadj ct819_gainadj[] = { ++ { 1, 1, 1 }, ++ { 8, 8, 8 }, ++ { 16, 16, 16 }, ++ { 128, 128, 128 }, ++}; ++ ++static const struct gainadj ct821_gainadj[] = { ++ { 1, 1, 1 }, ++ { 2, 2, 2 }, ++ { 4, 4, 4 }, ++ { 8, 8, 8 }, ++ { 16, 16, 16 }, ++ { 32, 32, 32 }, ++ { 64, 64, 64 }, ++ { 140, 140, 140 }, ++}; ++ ++/* ++ * Two things about the CT819 are not settled by the static analysis of the iOS ++ * driver (docs/periph/ds-als-hall.md, A1 and A2). Both can be flipped on a ++ * running system through /sys/module/tsl2583/parameters/ (or tsl2583.= ++ * on the kernel command line) while the answer is measured. ++ * ++ * apple_no_pwr_bit: iOS writes CONTROL = 0x02 (ADC enable only); Linux writes ++ * POWER | ADC_ENABLE = 0x03. Set this to write what iOS writes. The next ++ * power-up (autosuspend, or a write to integration_time) applies it. ++ * apple_int_clear: how the ALS interrupt status is cleared after a sample. ++ * 0 the TSL258x special function (command 0xe1), the default ++ * 1 a write of 0x40 to command 0x8c, as a sibling iOS class does ++ * 2 do not clear at all ++ */ ++static bool apple_no_pwr_bit; ++module_param(apple_no_pwr_bit, bool, 0644); ++MODULE_PARM_DESC(apple_no_pwr_bit, "Apple ALS: write CONTROL = ADC enable only, as iOS does"); ++ ++static int apple_int_clear; ++module_param(apple_int_clear, int, 0644); ++MODULE_PARM_DESC(apple_int_clear, "Apple ALS interrupt clear: 0 special function, 1 write 0x40 to 0x8c, 2 none"); ++ ++#define APPLE_ALS_INT_CLR_SPL_FN 0 ++#define APPLE_ALS_INT_CLR_REG_0C 1 ++#define APPLE_ALS_INT_CLR_NONE 2 ++#define APPLE_ALS_INT_CLR_REG (TSL2583_CMD_REG | 0x0c) ++#define APPLE_ALS_INT_CLR_REG_VALUE 0x40 ++ ++static int apple_als_get_lux(struct tsl2583_chip *chip, u16 ch0, u16 ch1); ++ ++static int apple_als_gain_to_index(const struct tsl2583_chip_info *info, u8 gain) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < info->num_gainadj; i++) ++ if (info->gainadj[i].mean == gain) ++ return i; ++ ++ return -EINVAL; ++} ++ ++/* ++ * The recorded gain ratios are relative to the previous gain step, so the ++ * first step has no entry of its own. ++ */ ++static unsigned int apple_als_ratio_offset(const struct tsl2583_chip_info *info, ++ unsigned int gain) ++{ ++ return info->cal_ratio_off + (gain - 1) * 4; ++} ++ ++static bool apple_als_record_is_supported(const struct tsl2583_chip_info *info, ++ const u8 *record) ++{ ++ if (record[0] || record[3] != info->cal_record_len || record[4] != 1) ++ return false; ++ ++ if (info->cal_gain_count_off && ++ record[info->cal_gain_count_off] != info->cal_gain_count) ++ return false; ++ ++ return true; ++} ++ ++static int apple_als_load_calibration(struct tsl2583_chip *chip) ++{ ++ const struct tsl2583_chip_info *info = chip->info; ++ struct device *dev = &chip->client->dev; ++ struct apple_als_calibration *cal = &chip->cal; ++ struct nvmem_cell *cell; ++ const struct firmware *fw; ++ const char *fw_name; ++ const u8 *record; ++ size_t len; ++ u32 checksum = 0; ++ u8 *data; ++ int gain_index; ++ int ret; ++ int i; ++ ++ if (!device_property_present(dev, "nvmem-cells") && ++ !device_property_read_string(dev, "firmware-name", &fw_name)) { ++ /* Missing calibration must not wait for a userspace helper. */ ++ ret = request_firmware_direct(&fw, fw_name, dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to load LSCI file\n"); ++ len = fw->size; ++ data = kmemdup(fw->data, len, GFP_KERNEL); ++ release_firmware(fw); ++ if (!data) ++ return -ENOMEM; ++ } else { ++ cell = devm_nvmem_cell_get(dev, "calibration"); ++ if (IS_ERR(cell)) ++ return dev_err_probe(dev, PTR_ERR(cell), ++ "failed to get LSCI calibration\n"); ++ data = nvmem_cell_read(cell, &len); ++ if (IS_ERR(data)) ++ return dev_err_probe(dev, PTR_ERR(data), ++ "failed to read LSCI calibration\n"); ++ } ++ ++ if (len < APPLE_ALS_CAL_HEADER_SIZE + info->cal_record_size || ++ len & 1 || data[1] != info->cal_version || data[2] != 1 || ++ data[3] != APPLE_ALS_CAL_HEADER_SIZE || ++ get_unaligned_le16(data + 4) != len) { ++ dev_err(dev, "invalid LSCI v%u calibration header\n", ++ info->cal_version); ++ goto invalid; ++ } ++ ++ for (i = 0; i < len; i += 2) ++ checksum += get_unaligned_le16(data + i); ++ if ((u16)checksum != U16_MAX) { ++ dev_err(dev, "invalid LSCI calibration checksum\n"); ++ goto invalid; ++ } ++ ++ record = data + data[3]; ++ if (!apple_als_record_is_supported(info, record)) { ++ dev_err(dev, "unsupported LSCI v%u calibration record\n", ++ info->cal_version); ++ goto invalid; ++ } ++ if (memchr_inv(record + 0x2a, 0, 6)) { ++ dev_err(dev, "unsupported LSCI backlight-leakage calibration\n"); ++ goto invalid; ++ } ++ ++ cal->lux_fudge = get_unaligned_le16(record + 0x08); ++ cal->integration_time = get_unaligned_le16(record + 0x0a); ++ gain_index = apple_als_gain_to_index(info, record[0x10]); ++ cal->a_coeff1 = get_unaligned_le32(record + 0x14); ++ cal->b_coeff1 = get_unaligned_le32(record + 0x18); ++ cal->upper_range = get_unaligned_le32(record + 0x1c); ++ cal->a_coeff2 = get_unaligned_le32(record + 0x20); ++ cal->b_coeff2 = get_unaligned_le32(record + 0x24); ++ cal->ch0_dark_counts = record[0x28]; ++ cal->ch1_dark_counts = record[0x29]; ++ ++ if (!cal->lux_fudge || cal->integration_time < 50 || ++ cal->integration_time > 650 || gain_index < 0 || ++ cal->a_coeff1 <= 0 || cal->b_coeff1 <= 0 || ++ cal->upper_range <= 0 || cal->a_coeff2 <= 0 || ++ cal->b_coeff2 <= 0) { ++ dev_err(dev, "invalid LSCI calibration values\n"); ++ goto invalid; ++ } ++ cal->gain = gain_index; ++ ++ cal->gain_factor[0][0] = APPLE_ALS_CAL_SCALE; ++ cal->gain_factor[0][1] = APPLE_ALS_CAL_SCALE; ++ for (i = 1; i < info->cal_gain_count; i++) { ++ const u8 *ratios = record + apple_als_ratio_offset(info, i); ++ u16 ch0 = get_unaligned_le16(ratios); ++ u16 ch1 = get_unaligned_le16(ratios + 2); ++ u32 previous; ++ u32 *factor; ++ ++ previous = cal->gain_factor[i - 1][0]; ++ factor = &cal->gain_factor[i][0]; ++ ret = apple_als_calculate_gain_factor(previous, ch0, ++ info->cal_ratio_scale, ++ factor); ++ if (!ret) { ++ previous = cal->gain_factor[i - 1][1]; ++ factor = &cal->gain_factor[i][1]; ++ ret = apple_als_calculate_gain_factor(previous, ch1, ++ info->cal_ratio_scale, ++ factor); ++ } ++ if (ret) { ++ dev_err(dev, "invalid LSCI gain factors\n"); ++ goto invalid; ++ } ++ } ++ ++ chip->als_settings.als_time = cal->integration_time; ++ chip->als_settings.als_gain = cal->gain; ++ dev_info(dev, "loaded LSCI v%u ambient-light calibration\n", ++ info->cal_version); ++ kfree(data); ++ ++ return 0; ++ ++invalid: ++ kfree(data); ++ return -EINVAL; ++} ++ + /* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. +@@ -126,15 +421,15 @@ static void tsl2583_defaults(struct tsl2583_chip *chip) + { + /* + * The integration time must be a multiple of 50ms and within the +- * range [50, 600] ms. ++ * range [50, 650] ms. + */ +- chip->als_settings.als_time = 100; ++ chip->als_settings.als_time = chip->info->default_als_time; + + /* + * This is an index into the gainadj table. Assume clear glass as the + * default. + */ +- chip->als_settings.als_gain = 0; ++ chip->als_settings.als_gain = chip->info->default_als_gain; + + /* Default gain trim to account for aperture effects */ + chip->als_settings.als_gain_trim = 1000; +@@ -147,6 +442,34 @@ static void tsl2583_defaults(struct tsl2583_chip *chip) + sizeof(tsl2583_default_lux)); + } + ++/* The Apple part that is a plain TSL2581 apart from its calibration. */ ++static bool tsl2583_is_ct819(const struct tsl2583_chip *chip) ++{ ++ return chip->info->apple && !chip->info->als_config; ++} ++ ++static int tsl2583_clear_interrupt(struct tsl2583_chip *chip) ++{ ++ if (chip->info->int_clr_data_cmd) ++ return i2c_smbus_write_byte_data(chip->client, ++ chip->info->int_clr_data_cmd, 0); ++ ++ if (tsl2583_is_ct819(chip)) { ++ switch (READ_ONCE(apple_int_clear)) { ++ case APPLE_ALS_INT_CLR_REG_0C: ++ return i2c_smbus_write_byte_data(chip->client, ++ APPLE_ALS_INT_CLR_REG, ++ APPLE_ALS_INT_CLR_REG_VALUE); ++ case APPLE_ALS_INT_CLR_NONE: ++ return 0; ++ } ++ } ++ ++ return i2c_smbus_write_byte(chip->client, ++ TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | ++ TSL2583_CMD_ALS_INT_CLR); ++} ++ + /* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last +@@ -179,8 +502,8 @@ static int tsl2583_get_lux(struct iio_dev *indio_dev) + } + + /* is data new & valid */ +- if (!(ret & TSL2583_STA_ADC_INTR)) { +- dev_err(&chip->client->dev, "%s: data not valid; returning last value\n", ++ if (!(ret & TSL2583_STA_ADC_VALID)) { ++ dev_dbg(&chip->client->dev, "%s: data not valid; returning last value\n", + __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto done; +@@ -203,13 +526,21 @@ static int tsl2583_get_lux(struct iio_dev *indio_dev) + * integration cycle to start. This has to be done even though this + * driver currently does not support interrupts. + */ +- ret = i2c_smbus_write_byte(chip->client, +- (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | +- TSL2583_CMD_ALS_INT_CLR)); ++ ret = tsl2583_clear_interrupt(chip); + if (ret < 0) { +- dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n", +- __func__); +- goto done; /* have no data, so return failure */ ++ /* ++ * The CT819's clear is a guess (A2 in ds-als-hall.md): the counts ++ * are already in hand, so keep them and say so once. ++ */ ++ if (tsl2583_is_ct819(chip)) { ++ dev_warn_once(&chip->client->dev, ++ "%s: failed to clear the interrupt bit (%d)\n", ++ __func__, ret); ++ } else { ++ dev_err(&chip->client->dev, ++ "%s: failed to clear the interrupt bit\n", __func__); ++ goto done; /* have no data, so return failure */ ++ } + } + + /* extract ALS/lux data */ +@@ -218,6 +549,12 @@ static int tsl2583_get_lux(struct iio_dev *indio_dev) + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; ++ if (chip->info->apple) { ++ ret = apple_als_get_lux(chip, ch0, ch1); ++ if (ret >= 0) ++ chip->als_cur_info.lux = ret; ++ goto done; ++ } + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; +@@ -247,11 +584,11 @@ static int tsl2583_get_lux(struct iio_dev *indio_dev) + u32 ch0lux, ch1lux; + + ch0lux = ((ch0 * p->ch0) + +- (gainadj[chip->als_settings.als_gain].ch0 >> 1)) +- / gainadj[chip->als_settings.als_gain].ch0; ++ (chip->info->gainadj[chip->als_settings.als_gain].ch0 >> 1)) ++ / chip->info->gainadj[chip->als_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + +- (gainadj[chip->als_settings.als_gain].ch1 >> 1)) +- / gainadj[chip->als_settings.als_gain].ch1; ++ (chip->info->gainadj[chip->als_settings.als_gain].ch1 >> 1)) ++ / chip->info->gainadj[chip->als_settings.als_gain].ch1; + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { +@@ -300,6 +637,16 @@ done: + return ret; + } + ++static int apple_als_get_lux(struct tsl2583_chip *chip, u16 ch0, u16 ch1) ++{ ++ return apple_als_calc_lux(&chip->cal, chip->als_settings.als_gain, ++ chip->integration_cycles, ++ chip->info->integration_cycle_us, ++ chip->info->counts_per_cycle, ++ chip->als_settings.als_gain_trim, ++ chip->info->lux_max, ch0, ch1); ++} ++ + /* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). +@@ -369,7 +716,12 @@ static int tsl2583_set_als_time(struct tsl2583_chip *chip) + u8 val; + + /* determine als integration register */ +- als_count = DIV_ROUND_CLOSEST(chip->als_settings.als_time * 100, 270); ++ if (chip->info->apple) ++ als_count = chip->als_settings.als_time * USEC_PER_MSEC / ++ chip->info->integration_cycle_us; ++ else ++ als_count = DIV_ROUND_CLOSEST(chip->als_settings.als_time * 100, ++ 270); + if (!als_count) + als_count = 1; /* ensure at least one cycle */ + +@@ -389,6 +741,8 @@ static int tsl2583_set_als_time(struct tsl2583_chip *chip) + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = DIV_ROUND_CLOSEST(als_time, 50); ++ if (chip->info->apple) ++ chip->integration_cycles = als_count; + + return ret; + } +@@ -399,7 +753,7 @@ static int tsl2583_set_als_gain(struct tsl2583_chip *chip) + + /* Set the gain based on als_settings struct */ + ret = i2c_smbus_write_byte_data(chip->client, +- TSL2583_CMD_REG | TSL2583_GAIN, ++ TSL2583_CMD_REG | chip->info->gain_reg, + chip->als_settings.als_gain); + if (ret < 0) + dev_err(&chip->client->dev, +@@ -423,6 +777,41 @@ static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) + return ret; + } + ++/* ++ * iOS never sets the POWER bit of CONTROL on the CT819, so whether the part ++ * keeps it is not known (docs/periph/ds-als-hall.md, A1). Read CONTROL back: ++ * the ADC enable must stick, the power bit is allowed not to, and what the ++ * part answered is logged once. ++ */ ++static int apple_als_check_control(struct tsl2583_chip *chip, u8 written) ++{ ++ const u8 mask = TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON; ++ int ret; ++ ++ ret = i2c_smbus_read_byte_data(chip->client, ++ TSL2583_CMD_REG | TSL2583_CNTRL); ++ if (ret < 0) { ++ dev_err(&chip->client->dev, "failed to read CONTROL back\n"); ++ return ret; ++ } ++ ++ if (!(ret & TSL2583_CNTL_ADC_ENBL)) { ++ dev_err(&chip->client->dev, ++ "ADC enable did not stick: CONTROL 0x%02x after writing 0x%02x\n", ++ ret, written); ++ return -EIO; ++ } ++ ++ if ((ret & mask) != (written & mask)) ++ dev_info_once(&chip->client->dev, ++ "CONTROL reads 0x%02x after writing 0x%02x (power bit not kept)\n", ++ ret, written); ++ else ++ dev_dbg(&chip->client->dev, "CONTROL 0x%02x\n", ret); ++ ++ return 0; ++} ++ + /* + * Turn the device on. + * Configuration must be set before calling this function. +@@ -430,13 +819,33 @@ static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) + static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) + { + struct tsl2583_chip *chip = iio_priv(indio_dev); ++ u8 cntl; + int ret; + ++ if (chip->info->apple) ++ chip->needs_reinit = true; ++ + /* Power on the device; ADC off. */ +- ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON); ++ ret = tsl2583_set_power_state(chip, chip->info->als_config ? ++ TSL2583_CNTL_PWR_OFF : TSL2583_CNTL_PWR_ON); + if (ret < 0) + return ret; + ++ if (chip->info->als_config) { ++ /* CT821 requires its analog frontend to be enabled before ADC use. */ ++ ret = i2c_smbus_write_byte_data(chip->client, ++ TSL2583_CMD_REG | CT821_ALS_CONFIG, ++ CT821_ALS_CONFIG_ENABLE); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_write_byte_data(chip->client, ++ TSL2583_CMD_REG | CT821_ALS_CONFIG, ++ CT821_ALS_CONFIG_ENABLE); ++ if (ret < 0) ++ return ret; ++ } ++ + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_INTERRUPT, + TSL2583_INTERRUPT_DISABLED); +@@ -456,8 +865,111 @@ static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) + + usleep_range(3000, 3500); + +- return tsl2583_set_power_state(chip, +- TSL2583_CNTL_PWR_ON | TSL2583_CNTL_ADC_ENBL); ++ cntl = TSL2583_CNTL_ADC_ENBL | chip->info->cntl_enable; ++ if (!tsl2583_is_ct819(chip) || !READ_ONCE(apple_no_pwr_bit)) ++ cntl |= TSL2583_CNTL_PWR_ON; ++ ret = tsl2583_set_power_state(chip, cntl); ++ if (ret < 0) ++ return ret; ++ ++ if (tsl2583_is_ct819(chip)) { ++ ret = apple_als_check_control(chip, cntl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ if (chip->info->als_config) { ++ ret = tsl2583_set_als_time(chip); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_write_byte_data(chip->client, ++ TSL2583_CMD_REG | CT821_ALS_CONFIG, ++ CT821_ALS_CONFIG_ENABLE); ++ if (ret < 0) ++ return ret; ++ } ++ ++ /* Account for the 2.7 ms integration-time granularity. */ ++ msleep(chip->als_settings.als_time + 3); ++ chip->needs_reinit = false; ++ ++ return ret; ++} ++ ++static int apple_als_reconfigure(struct iio_dev *indio_dev, int als_time, ++ int als_gain, bool *force_suspend) ++{ ++ struct tsl2583_chip *chip = iio_priv(indio_dev); ++ int old_time = chip->als_settings.als_time; ++ int old_gain = chip->als_settings.als_gain; ++ int power_ret; ++ int restore_ret; ++ int ret; ++ ++ chip->als_settings.als_time = als_time; ++ chip->als_settings.als_gain = als_gain; ++ ret = tsl2583_chip_init_and_power_on(indio_dev); ++ if (!ret) ++ return 0; ++ ++ chip->als_settings.als_time = old_time; ++ chip->als_settings.als_gain = old_gain; ++ restore_ret = tsl2583_chip_init_and_power_on(indio_dev); ++ if (restore_ret) { ++ dev_err(&chip->client->dev, ++ "failed to restore configuration: %d\n", restore_ret); ++ power_ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); ++ if (power_ret) ++ dev_err(&chip->client->dev, ++ "failed to power off after restore failure: %d\n", ++ power_ret); ++ *force_suspend = true; ++ } ++ ++ return ret; ++} ++ ++static int tsl2583_reconfigure_gain(struct iio_dev *indio_dev, int gain, ++ bool *force_suspend) ++{ ++ struct tsl2583_chip *chip = iio_priv(indio_dev); ++ int old_gain; ++ int ret; ++ ++ if (chip->info->apple) ++ return apple_als_reconfigure(indio_dev, ++ chip->als_settings.als_time, gain, ++ force_suspend); ++ ++ old_gain = chip->als_settings.als_gain; ++ chip->als_settings.als_gain = gain; ++ ret = tsl2583_set_als_gain(chip); ++ if (ret < 0) ++ chip->als_settings.als_gain = old_gain; ++ ++ return ret; ++} ++ ++static int tsl2583_apply_time(struct iio_dev *indio_dev, int als_time, ++ bool *force_suspend) ++{ ++ struct tsl2583_chip *chip = iio_priv(indio_dev); ++ int old_time; ++ int ret; ++ ++ if (chip->info->apple) ++ return apple_als_reconfigure(indio_dev, als_time, ++ chip->als_settings.als_gain, ++ force_suspend); ++ ++ old_time = chip->als_settings.als_time; ++ chip->als_settings.als_time = als_time; ++ ret = tsl2583_set_als_time(chip); ++ if (ret < 0) ++ chip->als_settings.als_time = old_time; ++ ++ return ret; + } + + /* Sysfs Interface Functions */ +@@ -471,7 +983,7 @@ static ssize_t in_illuminance_input_target_show(struct device *dev, + int ret; + + mutex_lock(&chip->als_mutex); +- ret = sysfs_emit(buf, "%d\n", chip->als_settings.als_cal_target); ++ ret = sprintf(buf, "%d\n", chip->als_settings.als_cal_target); + mutex_unlock(&chip->als_mutex); + + return ret; +@@ -529,10 +1041,10 @@ static ssize_t in_illuminance_lux_table_show(struct device *dev, + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) { +- offset += sysfs_emit_at(buf, offset, "%u,%u,%u,", +- chip->als_settings.als_device_lux[i].ratio, +- chip->als_settings.als_device_lux[i].ch0, +- chip->als_settings.als_device_lux[i].ch1); ++ offset += sprintf(buf + offset, "%u,%u,%u,", ++ chip->als_settings.als_device_lux[i].ratio, ++ chip->als_settings.als_device_lux[i].ch0, ++ chip->als_settings.als_device_lux[i].ch1); + if (chip->als_settings.als_device_lux[i].ratio == 0) { + /* + * We just printed the first "0" entry. +@@ -543,7 +1055,7 @@ static ssize_t in_illuminance_lux_table_show(struct device *dev, + } + } + +- offset += sysfs_emit_at(buf, offset, "\n"); ++ offset += sprintf(buf + offset, "\n"); + + return offset; + } +@@ -594,6 +1106,12 @@ done: + } + + static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111"); ++static IIO_CONST_ATTR_NAMED(in_illuminance_calibscale_available_ct819, ++ in_illuminance_calibscale_available, ++ "1 8 16 128"); ++static IIO_CONST_ATTR_NAMED(in_illuminance_calibscale_available_ct821, ++ in_illuminance_calibscale_available, ++ "1 2 4 8 16 32 64 140"); + static IIO_CONST_ATTR(in_illuminance_integration_time_available, + "0.050 0.100 0.150 0.200 0.250 0.300 0.350 0.400 0.450 0.500 0.550 0.600 0.650"); + static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0); +@@ -609,10 +1127,30 @@ static struct attribute *sysfs_attrs_ctrl[] = { + NULL + }; + ++static struct attribute *ct819_sysfs_attrs_ctrl[] = { ++ &iio_const_attr_in_illuminance_calibscale_available_ct819.dev_attr.attr, ++ &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, ++ NULL ++}; ++ ++static struct attribute *ct821_sysfs_attrs_ctrl[] = { ++ &iio_const_attr_in_illuminance_calibscale_available_ct821.dev_attr.attr, ++ &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, ++ NULL ++}; ++ + static const struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, + }; + ++static const struct attribute_group ct819_attribute_group = { ++ .attrs = ct819_sysfs_attrs_ctrl, ++}; ++ ++static const struct attribute_group ct821_attribute_group = { ++ .attrs = ct821_sysfs_attrs_ctrl, ++}; ++ + static const struct iio_chan_spec tsl2583_channels[] = { + { + .type = IIO_LIGHT, +@@ -635,19 +1173,50 @@ static const struct iio_chan_spec tsl2583_channels[] = { + }, + }; + ++static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on) ++{ ++ if (on) ++ return pm_runtime_resume_and_get(&chip->client->dev); ++ ++ return pm_runtime_put_autosuspend(&chip->client->dev); ++} ++ ++static void tsl2583_put_pm_after_error(struct tsl2583_chip *chip, ++ bool force_suspend) ++{ ++ int ret; ++ ++ if (force_suspend) ++ ret = pm_runtime_put_sync_suspend(&chip->client->dev); ++ else ++ ret = tsl2583_set_pm_runtime_busy(chip, false); ++ if (ret < 0) ++ dev_err(&chip->client->dev, ++ "failed to release runtime PM after I/O error: %d\n", ret); ++} ++ + static int tsl2583_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) + { + struct tsl2583_chip *chip = iio_priv(indio_dev); ++ bool force_suspend = false; + int ret, pm_ret; + +- ret = pm_runtime_resume_and_get(&chip->client->dev); ++ ret = tsl2583_set_pm_runtime_busy(chip, true); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + ++ if (chip->needs_reinit) { ++ ret = tsl2583_chip_init_and_power_on(indio_dev); ++ if (ret < 0) { ++ force_suspend = true; ++ goto read_done; ++ } ++ } ++ + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_RAW: +@@ -691,14 +1260,14 @@ static int tsl2583_read_raw(struct iio_dev *indio_dev, + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { +- *val = gainadj[chip->als_settings.als_gain].mean; ++ *val = chip->info->gainadj[chip->als_settings.als_gain].mean; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; +- *val2 = chip->als_settings.als_time; ++ *val2 = chip->als_settings.als_time * USEC_PER_MSEC; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; +@@ -710,16 +1279,16 @@ read_done: + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { +- pm_runtime_put_autosuspend(&chip->client->dev); ++ tsl2583_put_pm_after_error(chip, force_suspend); + return ret; + } + + /* + * Preserve the ret variable if the call to +- * pm_runtime_put_autosuspend() is successful so the reading ++ * tsl2583_set_pm_runtime_busy() is successful so the reading + * (if applicable) is returned to user space. + */ +- pm_ret = pm_runtime_put_autosuspend(&chip->client->dev); ++ pm_ret = tsl2583_set_pm_runtime_busy(chip, false); + if (pm_ret < 0) + return pm_ret; + +@@ -731,18 +1300,27 @@ static int tsl2583_write_raw(struct iio_dev *indio_dev, + int val, int val2, long mask) + { + struct tsl2583_chip *chip = iio_priv(indio_dev); ++ bool force_suspend = false; + int ret; + +- ret = pm_runtime_resume_and_get(&chip->client->dev); ++ ret = tsl2583_set_pm_runtime_busy(chip, true); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + ++ if (chip->needs_reinit) { ++ ret = tsl2583_chip_init_and_power_on(indio_dev); ++ if (ret < 0) { ++ force_suspend = true; ++ goto write_done; ++ } ++ } ++ + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: +- if (chan->type == IIO_LIGHT) { ++ if (chan->type == IIO_LIGHT && val >= 250 && val <= 4000) { + chip->als_settings.als_gain_trim = val; + ret = 0; + } +@@ -751,38 +1329,41 @@ static int tsl2583_write_raw(struct iio_dev *indio_dev, + if (chan->type == IIO_LIGHT) { + unsigned int i; + +- for (i = 0; i < ARRAY_SIZE(gainadj); i++) { +- if (gainadj[i].mean == val) { +- chip->als_settings.als_gain = i; +- ret = tsl2583_set_als_gain(chip); ++ for (i = 0; i < chip->info->num_gainadj; i++) { ++ if (chip->info->gainadj[i].mean == val) { ++ ret = tsl2583_reconfigure_gain(indio_dev, i, ++ &force_suspend); + break; + } + } + } + break; + case IIO_CHAN_INFO_INT_TIME: +- if (chan->type == IIO_LIGHT && !val && val2 >= 50 && +- val2 <= 650 && !(val2 % 50)) { +- chip->als_settings.als_time = val2; +- ret = tsl2583_set_als_time(chip); ++ if (chan->type == IIO_LIGHT && !val && ++ val2 >= 50 * USEC_PER_MSEC && ++ val2 <= 650 * USEC_PER_MSEC && ++ !(val2 % (50 * USEC_PER_MSEC))) { ++ val2 /= USEC_PER_MSEC; ++ ret = tsl2583_apply_time(indio_dev, val2, &force_suspend); + } + break; + default: + break; + } + ++write_done: + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { +- pm_runtime_put_autosuspend(&chip->client->dev); ++ tsl2583_put_pm_after_error(chip, force_suspend); + return ret; + } + +- ret = pm_runtime_put_autosuspend(&chip->client->dev); ++ ret = tsl2583_set_pm_runtime_busy(chip, false); + if (ret < 0) + return ret; + +- return 0; ++ return ret; + } + + static const struct iio_info tsl2583_info = { +@@ -791,6 +1372,75 @@ static const struct iio_info tsl2583_info = { + .write_raw = tsl2583_write_raw, + }; + ++static const struct iio_info ct819_iio_info = { ++ .attrs = &ct819_attribute_group, ++ .read_raw = tsl2583_read_raw, ++ .write_raw = tsl2583_write_raw, ++}; ++ ++static const struct iio_info ct821_iio_info = { ++ .attrs = &ct821_attribute_group, ++ .read_raw = tsl2583_read_raw, ++ .write_raw = tsl2583_write_raw, ++}; ++ ++static const struct tsl2583_chip_info tsl2583_chip_info = { ++ .gainadj = tsl2583_gainadj, ++ .num_gainadj = ARRAY_SIZE(tsl2583_gainadj), ++ .iio_info = &tsl2583_info, ++ .default_als_time = 100, ++ .default_als_gain = 0, ++ .gain_reg = TSL2583_GAIN, ++ .integration_cycle_us = TSL2583_INTEGRATION_CYCLE_US, ++}; ++ ++/* ++ * Apart from the LSCI calibration and the lux computation the CT819 behaves ++ * like a plain TSL2581: it keeps the gain in the TSL258x gain register, ++ * clears the ALS interrupt through the TSL258x special function and needs no ++ * extra control or analog frontend programming. ++ */ ++static const struct tsl2583_chip_info ct819_chip_info = { ++ .gainadj = ct819_gainadj, ++ .num_gainadj = ARRAY_SIZE(ct819_gainadj), ++ .iio_info = &ct819_iio_info, ++ .default_als_time = 400, ++ .default_als_gain = 1, ++ .apple = true, ++ .gain_reg = TSL2583_GAIN, ++ .cal_version = 2, ++ .cal_record_size = CT819_CAL_RECORD_SIZE_V2, ++ .cal_record_len = CT819_CAL_RECORD_SIZE_V2, ++ .cal_gain_count = CT819_CAL_GAIN_COUNT, ++ .cal_ratio_off = CT819_CAL_RATIO_OFF, ++ .cal_ratio_scale = CT819_CAL_RATIO_SCALE, ++ .integration_cycle_us = TSL2583_INTEGRATION_CYCLE_US, ++ .counts_per_cycle = CT819_COUNTS_PER_CYCLE, ++ .lux_max = CT819_LUX_MAX, ++}; ++ ++static const struct tsl2583_chip_info ct821_chip_info = { ++ .gainadj = ct821_gainadj, ++ .num_gainadj = ARRAY_SIZE(ct821_gainadj), ++ .iio_info = &ct821_iio_info, ++ .default_als_time = 500, ++ .default_als_gain = 7, ++ .apple = true, ++ .gain_reg = CT821_GAIN, ++ .int_clr_data_cmd = CT821_CMD_ALS_INT_CLR, ++ .cntl_enable = CT821_CNTL_ENABLE, ++ .als_config = true, ++ .cal_version = 3, ++ .cal_record_size = CT821_CAL_RECORD_SIZE_V3, ++ .cal_record_len = CT821_CAL_RECORD_DATA_SIZE_V3, ++ .cal_gain_count = CT821_CAL_GAIN_COUNT, ++ .cal_gain_count_off = CT821_CAL_GAIN_COUNT_OFF, ++ .cal_ratio_off = CT821_CAL_RATIO_OFF, ++ .cal_ratio_scale = CT821_CAL_RATIO_SCALE, ++ .integration_cycle_us = CT821_INTEGRATION_CYCLE_US, ++ .lux_max = TSL2583_LUX_CALC_OVER_FLOW, ++}; ++ + static int tsl2583_probe(struct i2c_client *clientp) + { + int ret; +@@ -810,6 +1460,9 @@ static int tsl2583_probe(struct i2c_client *clientp) + + chip = iio_priv(indio_dev); + chip->client = clientp; ++ chip->info = device_get_match_data(&clientp->dev); ++ if (!chip->info) ++ chip->info = &tsl2583_chip_info; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); +@@ -828,12 +1481,20 @@ static int tsl2583_probe(struct i2c_client *clientp) + return -EINVAL; + } + +- indio_dev->info = &tsl2583_info; ++ indio_dev->info = chip->info->iio_info; + indio_dev->channels = tsl2583_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + ++ /* Load defaults before exposing the IIO device to userspace. */ ++ tsl2583_defaults(chip); ++ if (chip->info->apple) { ++ ret = apple_als_load_calibration(chip); ++ if (ret) ++ return ret; ++ } ++ + pm_runtime_enable(&clientp->dev); + pm_runtime_set_autosuspend_delay(&clientp->dev, + TSL2583_POWER_OFF_DELAY_MS); +@@ -843,12 +1504,10 @@ static int tsl2583_probe(struct i2c_client *clientp) + if (ret) { + dev_err(&clientp->dev, "%s: iio registration failed\n", + __func__); ++ pm_runtime_disable(&clientp->dev); + return ret; + } + +- /* Load up the V2 defaults (these are hard coded defaults for now) */ +- tsl2583_defaults(chip); +- + dev_info(&clientp->dev, "Light sensor found.\n"); + + return 0; +@@ -909,9 +1568,11 @@ static const struct i2c_device_id tsl2583_idtable[] = { + MODULE_DEVICE_TABLE(i2c, tsl2583_idtable); + + static const struct of_device_id tsl2583_of_match[] = { +- { .compatible = "amstaos,tsl2580", }, +- { .compatible = "amstaos,tsl2581", }, +- { .compatible = "amstaos,tsl2583", }, ++ { .compatible = "apple,ct819", .data = &ct819_chip_info }, ++ { .compatible = "apple,ct821", .data = &ct821_chip_info }, ++ { .compatible = "amstaos,tsl2580", .data = &tsl2583_chip_info }, ++ { .compatible = "amstaos,tsl2581", .data = &tsl2583_chip_info }, ++ { .compatible = "amstaos,tsl2583", .data = &tsl2583_chip_info }, + { } + }; + MODULE_DEVICE_TABLE(of, tsl2583_of_match); +diff --git a/drivers/iio/light/tsl2583.h b/drivers/iio/light/tsl2583.h +new file mode 100644 +index 0000000000000000000000000000000000000000..949c5f7407d8a30d18c16f67255a038e71a1be11 +--- /dev/null ++++ b/drivers/iio/light/tsl2583.h +@@ -0,0 +1,176 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++#ifndef _TSL2583_H ++#define _TSL2583_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * Fixed-point scale of the per-gain-step ratios in an Apple LSCI ambient-light ++ * calibration record. The record stores the sensitivity of one gain step ++ * relative to the previous one, so the factors have to be folded together. ++ */ ++#define CT819_CAL_RATIO_SCALE 256 ++#define CT821_CAL_RATIO_SCALE 255 ++ ++static inline int apple_als_calculate_gain_factor(u32 previous, u16 ratio, ++ u32 scale, u32 *next) ++{ ++ u64 value; ++ ++ if (!previous || !ratio || !scale) ++ return -EINVAL; ++ ++ value = DIV_ROUND_CLOSEST_ULL((u64)previous * ratio, scale); ++ if (!value || value > U32_MAX) ++ return -ERANGE; ++ ++ *next = value; ++ ++ return 0; ++} ++ ++/* Largest lux value any of the parts reports */ ++#define TSL2583_LUX_CALC_OVER_FLOW 65535 ++ ++/* ++ * Apple pairs the TSL258x register interface with its own factory LSCI ++ * calibration record. The record layout is versioned; every version shares ++ * the same 8 byte header and the same fixed-point scale for the lux ++ * coefficients, but differs in the record size and in the number of ++ * calibrated gain steps. ++ */ ++#define APPLE_ALS_CAL_SCALE 65536 ++#define APPLE_ALS_LUX_FUDGE_SCALE 255 ++#define APPLE_ALS_MAX_GAIN_COUNT 8 ++ ++/* ADC counts per integration cycle of the CT819 (a TSL2581 clone) */ ++#define CT819_COUNTS_PER_CYCLE 1024 ++ ++struct apple_als_calibration { ++ u16 lux_fudge; ++ u16 integration_time; ++ u8 gain; ++ s32 a_coeff1; ++ s32 b_coeff1; ++ s32 upper_range; ++ s32 a_coeff2; ++ s32 b_coeff2; ++ u8 ch0_dark_counts; ++ u8 ch1_dark_counts; ++ u32 gain_factor[APPLE_ALS_MAX_GAIN_COUNT][2]; ++}; ++ ++/* ++ * Scale a raw channel reading back to the integration time and gain the ++ * factory calibration was taken at. ++ */ ++static inline int apple_als_normalize_counts(const struct apple_als_calibration *cal, ++ unsigned int gain_index, u32 cycles, ++ u32 cycle_us, u16 raw, int channel, ++ u32 *normalized) ++{ ++ u64 value; ++ u32 calibration_gain; ++ u32 current_gain; ++ u32 calibration_cycles; ++ ++ if (!cycle_us || gain_index >= APPLE_ALS_MAX_GAIN_COUNT) ++ return -EINVAL; ++ ++ calibration_cycles = cal->integration_time * USEC_PER_MSEC / cycle_us; ++ current_gain = cal->gain_factor[gain_index][channel]; ++ calibration_gain = cal->gain_factor[cal->gain][channel]; ++ if (!calibration_cycles || !cycles || !current_gain || !calibration_gain) ++ return -EINVAL; ++ ++ value = (u64)raw * calibration_cycles * calibration_gain; ++ value = DIV_ROUND_CLOSEST_ULL(value, (u64)cycles * current_gain); ++ *normalized = min_t(u64, value, U32_MAX); ++ ++ return 0; ++} ++ ++/** ++ * apple_als_calc_lux - lux from raw ADC counts and an Apple LSCI record ++ * @cal: parsed calibration record ++ * @gain_index: gain step the counts were taken at ++ * @cycles: integration cycles the counts were taken over ++ * @cycle_us: length of one integration cycle ++ * @counts_per_cycle: the ADC's counts per cycle, 0 if only 16 bits limit it ++ * @trim: user gain trim, 1000 is unity ++ * @lux_max: the largest value to report; a saturated channel reports it ++ * @ch0: counts of the visible + infrared channel ++ * @ch1: counts of the infrared channel ++ * ++ * The counts are first scaled to the record's reference integration time and ++ * gain and the dark counts are taken off. Then lux is the two-segment ++ * a * ch0 - b * ch1 curve (coefficients in Q16), times fudge / 255. A channel ++ * at its ADC limit (@counts_per_cycle per cycle, 16 bits at most) is clipped, ++ * so its ratio is meaningless: report @lux_max. ++ * ++ * Return: lux, or a negative error for a record or setting that can't be used. ++ */ ++static inline int apple_als_calc_lux(const struct apple_als_calibration *cal, ++ unsigned int gain_index, u32 cycles, ++ u32 cycle_us, u32 counts_per_cycle, u32 trim, ++ u32 lux_max, u16 ch0, u16 ch1) ++{ ++ s64 lux_scaled; ++ s32 a_coeff; ++ s32 b_coeff; ++ u64 lux; ++ u32 norm_ch0; ++ u32 norm_ch1; ++ u32 saturation; ++ ++ if (!cycles) ++ return -EINVAL; ++ ++ saturation = U16_MAX; ++ if (counts_per_cycle) ++ saturation = min_t(u32, saturation, counts_per_cycle * cycles - 1); ++ if (ch0 >= saturation || ch1 >= saturation) ++ return lux_max; ++ ++ if (apple_als_normalize_counts(cal, gain_index, cycles, cycle_us, ch0, 0, ++ &norm_ch0) || ++ apple_als_normalize_counts(cal, gain_index, cycles, cycle_us, ch1, 1, ++ &norm_ch1)) ++ return -EINVAL; ++ norm_ch0 = max_t(u32, norm_ch0, cal->ch0_dark_counts) - ++ cal->ch0_dark_counts; ++ norm_ch1 = max_t(u32, norm_ch1, cal->ch1_dark_counts) - ++ cal->ch1_dark_counts; ++ ++ if (!norm_ch0) ++ return 0; ++ ++ if ((u64)norm_ch1 * APPLE_ALS_CAL_SCALE <= ++ (u64)norm_ch0 * cal->upper_range) { ++ a_coeff = cal->a_coeff1; ++ b_coeff = cal->b_coeff1; ++ } else { ++ a_coeff = cal->a_coeff2; ++ b_coeff = cal->b_coeff2; ++ } ++ ++ lux_scaled = (s64)norm_ch0 * a_coeff - (s64)norm_ch1 * b_coeff; ++ if (lux_scaled <= 0) ++ return 0; ++ ++ if (check_mul_overflow((u64)lux_scaled, (u64)cal->lux_fudge, &lux)) ++ return lux_max; ++ lux = div_u64(lux, APPLE_ALS_CAL_SCALE * APPLE_ALS_LUX_FUDGE_SCALE); ++ if (check_mul_overflow(lux, (u64)trim, &lux)) ++ return lux_max; ++ lux = div_u64(lux, 1000); ++ ++ return min_t(u64, lux, lux_max); ++} ++ ++#endif /* _TSL2583_H */ +diff --git a/drivers/pinctrl/pinctrl-apple-gpio.c b/drivers/pinctrl/pinctrl-apple-gpio.c +index 6305b96aaa7331a17bffa1daecacb0404d521346..7b21d5014d8df4791d7ed11977ab2e65f2654531 100644 +--- a/drivers/pinctrl/pinctrl-apple-gpio.c ++++ b/drivers/pinctrl/pinctrl-apple-gpio.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -30,12 +31,18 @@ + #include "core.h" + #include "pinmux.h" + ++struct apple_gpio_soc_data { ++ bool s8000_mux; ++}; ++ + struct apple_gpio_pinctrl { + struct device *dev; + struct pinctrl_dev *pctldev; ++ const struct apple_gpio_soc_data *soc; + + void __iomem *base; + struct regmap *map; ++ bool warm; + + struct pinctrl_desc pinctrl_desc; + struct gpio_chip gpio_chip; +@@ -182,9 +189,22 @@ static int apple_gpio_pinmux_set(struct pinctrl_dev *pctldev, unsigned int func, + { + struct apple_gpio_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); + +- apple_gpio_set_reg( +- pctl, group, REG_GPIOx_PERIPH | REG_GPIOx_INPUT_ENABLE, +- FIELD_PREP(REG_GPIOx_PERIPH, func) | REG_GPIOx_INPUT_ENABLE); ++ u32 mask = REG_GPIOx_PERIPH | REG_GPIOx_INPUT_ENABLE; ++ u32 value = FIELD_PREP(REG_GPIOx_PERIPH, func) | REG_GPIOx_INPUT_ENABLE; ++ ++ /* S8000 GPIO platform-function modes, verified in its iOS driver: ++ * peripheral routing clears bit 4; GPIO mux selects output mode. ++ * This reproduces the N1 display-sync on/off states without changing ++ * the generic/M1 register semantics or the pin data/pull fields. ++ */ ++ if (pctl->soc && pctl->soc->s8000_mux) { ++ mask |= BIT(4); ++ if (!func) { ++ mask |= REG_GPIOx_MODE; ++ value |= FIELD_PREP(REG_GPIOx_MODE, REG_GPIOx_OUT); ++ } ++ } ++ apple_gpio_set_reg(pctl, group, mask, value); + + return 0; + } +@@ -354,8 +374,33 @@ static void apple_gpio_irq_handler(struct irq_desc *desc) + chained_irq_enter(chip, desc); + for (pinh = 0; pinh < gc->ngpio; pinh += 32) { + pending = readl_relaxed(pctl->base + REG_IRQ(*grpp, pinh)); +- for_each_set_bit(pinl, &pending, 32) +- generic_handle_domain_irq(gc->irq.domain, pinh + pinl); ++ for_each_set_bit(pinl, &pending, 32) { ++ unsigned int pin = pinh + pinl; ++ int ret; ++ ++ ret = pin < gc->ngpio ? ++ generic_handle_domain_irq(gc->irq.domain, pin) : -EINVAL; ++ if (ret && pctl->warm) { ++ /* An inherited, unmapped source has nobody to ack it. ++ * Mask only this GPIO, then clear this group's latch. ++ */ ++ if (pin < gc->ngpio) { ++ u32 reg = apple_gpio_get_reg(pctl, pin); ++ u32 mode = FIELD_GET(REG_GPIOx_MODE, reg); ++ u32 off = FIELD_PREP(REG_GPIOx_MODE, ++ REG_GPIOx_IN_IRQ_OFF); ++ ++ if (!(reg & REG_GPIOx_PERIPH) && ++ mode >= REG_GPIOx_IN_IRQ_HI && ++ mode <= REG_GPIOx_IN_IRQ_ANY) ++ apple_gpio_set_reg(pctl, pin, REG_GPIOx_MODE, off); ++ } ++ writel(BIT(pinl), pctl->base + REG_IRQ(*grpp, pinh)); ++ dev_warn_ratelimited(pctl->dev, ++ "masked orphan warm GPIO IRQ group=%u pin=%u\n", ++ *grpp, pin); ++ } ++ } + } + chained_irq_exit(chip, desc); + } +@@ -367,12 +412,46 @@ static const struct irq_chip apple_gpio_irqchip = { + .irq_mask = apple_gpio_irq_mask, + .irq_unmask = apple_gpio_irq_unmask, + .irq_set_type = apple_gpio_irq_set_type, +- .flags = IRQCHIP_IMMUTABLE, ++ /* ++ * Nothing to arm: the bank interrupts are chained to the AIC, which ++ * stays up in suspend-to-idle, and any of them wakes the CPU. Without ++ * this, enable_irq_wake() fails and so does gpio-keys' suspend. ++ */ ++ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, + }; + + /* Probe & register */ + ++static void apple_gpio_warm_quiesce(struct apple_gpio_pinctrl *pctl) ++{ ++ struct gpio_chip *gc = &pctl->gpio_chip; ++ unsigned int pin, group, mode, masked = 0; ++ u32 reg; ++ ++ if (!pctl->warm) ++ return; ++ /* Before chained parent IRQs are enabled, retire old GPIO IRQ modes. ++ * Preserve outputs, mux, pulls, data and grouping. Consumers rearm their ++ * own pins later; panic entry need not have run any shutdown callbacks. ++ */ ++ for (pin = 0; pin < gc->ngpio; pin++) { ++ reg = apple_gpio_get_reg(pctl, pin); ++ mode = FIELD_GET(REG_GPIOx_MODE, reg); ++ if (!(reg & REG_GPIOx_PERIPH) && mode >= REG_GPIOx_IN_IRQ_HI && ++ mode <= REG_GPIOx_IN_IRQ_ANY) { ++ apple_gpio_set_reg(pctl, pin, REG_GPIOx_MODE, ++ FIELD_PREP(REG_GPIOx_MODE, REG_GPIOx_IN_IRQ_OFF)); ++ masked++; ++ } ++ } ++ for (group = 0; group < gc->irq.num_parents; group++) ++ for (pin = 0; pin < gc->ngpio; pin += 32) ++ writel(GENMASK(min(32U, gc->ngpio - pin) - 1, 0), ++ pctl->base + REG_IRQ(group, pin)); ++ dev_info(pctl->dev, "warm GPIO IRQ baseline: masked %u inherited inputs\n", masked); ++} ++ + static int apple_gpio_register(struct apple_gpio_pinctrl *pctl) + { + struct gpio_irq_chip *girq = &pctl->gpio_chip.irq; +@@ -422,6 +501,7 @@ static int apple_gpio_register(struct apple_gpio_pinctrl *pctl) + girq->handler = handle_level_irq; + } + ++ apple_gpio_warm_quiesce(pctl); + ret = devm_gpiochip_add_data(pctl->dev, &pctl->gpio_chip, pctl); + + out_free_irq_data: +@@ -455,6 +535,9 @@ static int apple_gpio_pinctrl_probe(struct platform_device *pdev) + if (!pctl) + return -ENOMEM; + pctl->dev = &pdev->dev; ++ pctl->warm = of_machine_is_compatible("apple,j71t") && ++ of_property_read_bool(of_chosen, "linux,booted-from-kexec"); ++ pctl->soc = of_device_get_match_data(&pdev->dev); + pctl->gpio_chip.irq.num_parents = nirqs; + dev_set_drvdata(&pdev->dev, pctl); + +@@ -521,7 +604,12 @@ static int apple_gpio_pinctrl_probe(struct platform_device *pdev) + return apple_gpio_register(pctl); + } + ++static const struct apple_gpio_soc_data s8000_gpio_data = { ++ .s8000_mux = true, ++}; ++ + static const struct of_device_id apple_gpio_pinctrl_of_match[] = { ++ { .compatible = "apple,s8000-pinctrl", .data = &s8000_gpio_data }, + { .compatible = "apple,t8103-pinctrl", }, + { .compatible = "apple,pinctrl", }, + { } +diff --git a/drivers/video/backlight/apple_dwi_bl.c b/drivers/video/backlight/apple_dwi_bl.c +index 93bd744972d60adcb564c91908f7d81d057d5c3c..0422e396cdb93b13500e4ed34285f265a22f5e9a 100644 +--- a/drivers/video/backlight/apple_dwi_bl.c ++++ b/drivers/video/backlight/apple_dwi_bl.c +@@ -9,7 +9,11 @@ + #include + #include + #include ++#include + #include ++#include ++#include ++#include + #include + + #define DWI_BL_CTL 0x0 +@@ -34,24 +38,64 @@ + #define DWI_BL_MAX_BRIGHTNESS 2047 + + struct apple_dwi_bl { ++ struct device *dev; + void __iomem *base; ++ void __iomem *block; ++ /* Serialize block initialization and brightness transactions. */ ++ struct mutex lock; + }; + ++static int dwi_bl_wait(struct apple_dwi_bl *dwi_bl) ++{ ++ u32 value; ++ int ret; ++ ++ ret = readl_poll_timeout(dwi_bl->base + DWI_BL_CTL, value, ++ !(value & DWI_BL_CTL_SEND1), 10, 1000); ++ if (ret) ++ dev_err_ratelimited(dwi_bl->dev, "DWI busy timeout: %#x\n", value); ++ return ret; ++} ++ ++/* AppleS8000DWI::start(), J71t nclk=1, polarity=1, STR=8000, lockout=5. */ ++static int dwi_bl_init(struct apple_dwi_bl *dwi_bl) ++{ ++ int ret; ++ ++ if (!dwi_bl->block) ++ return 0; ++ ++ mutex_lock(&dwi_bl->lock); ++ writel(0x00000001, dwi_bl->block + 0x00); ++ writel(0x0002ee00, dwi_bl->block + 0xd0); ++ writel(0x00000078, dwi_bl->block + 0x04); ++ writel(0, dwi_bl->block + 0x84); ++ writel(0x41, dwi_bl->block + 0x80); ++ ret = dwi_bl_wait(dwi_bl); ++ mutex_unlock(&dwi_bl->lock); ++ if (!ret) ++ dev_info(dwi_bl->dev, "DWI block initialized (1 ms transaction bound)\n"); ++ return ret; ++} ++ + static int dwi_bl_update_status(struct backlight_device *bl) + { + struct apple_dwi_bl *dwi_bl = bl_get_data(bl); +- + int brightness = backlight_get_brightness(bl); +- + u32 cmd = 0; ++ int ret; + + cmd |= FIELD_PREP(DWI_BL_CMD_DATA, brightness); + cmd |= FIELD_PREP(DWI_BL_CMD_TYPE, DWI_BL_CMD_TYPE_SET_BRIGHTNESS); + +- writel(cmd, dwi_bl->base + DWI_BL_CMD); +- writel(DWI_BL_CTL_SEND, dwi_bl->base + DWI_BL_CTL); +- +- return 0; ++ mutex_lock(&dwi_bl->lock); ++ ret = dwi_bl_wait(dwi_bl); ++ if (!ret) { ++ writel(cmd, dwi_bl->base + DWI_BL_CMD); ++ writel(DWI_BL_CTL_SEND, dwi_bl->base + DWI_BL_CTL); ++ } ++ mutex_unlock(&dwi_bl->lock); ++ return ret; + } + + static int dwi_bl_get_brightness(struct backlight_device *bl) +@@ -75,6 +119,7 @@ static int dwi_bl_probe(struct platform_device *dev) + struct backlight_device *bl; + struct backlight_properties props; + struct resource *res; ++ int brightness, ret; + + dwi_bl = devm_kzalloc(&dev->dev, sizeof(*dwi_bl), GFP_KERNEL); + if (!dwi_bl) +@@ -84,6 +129,25 @@ static int dwi_bl_probe(struct platform_device *dev) + if (IS_ERR(dwi_bl->base)) + return PTR_ERR(dwi_bl->base); + ++ dwi_bl->dev = &dev->dev; ++ mutex_init(&dwi_bl->lock); ++ if (of_device_is_compatible(dev->dev.of_node, "apple,s8000-dwi-bl")) { ++ if (resource_size(res) >= 0xd4) { ++ /* The recovered boot words are specific to this board. */ ++ if (of_machine_is_compatible("apple,j71t")) ++ dwi_bl->block = dwi_bl->base; ++ dwi_bl->base += 0x80; ++ } else { ++ dev_warn(&dev->dev, "legacy DWI window: block resume init unavailable\n"); ++ } ++ } ++ /* Preserve the boot brightness before the init transaction clears DATA. */ ++ brightness = FIELD_GET(DWI_BL_CMD_DATA, ++ readl(dwi_bl->base + DWI_BL_CMD)); ++ ret = dwi_bl_init(dwi_bl); ++ if (ret) ++ return ret; ++ + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = DWI_BL_MAX_BRIGHTNESS; +@@ -96,11 +160,19 @@ static int dwi_bl_probe(struct platform_device *dev) + + platform_set_drvdata(dev, dwi_bl); + +- bl->props.brightness = dwi_bl_get_brightness(bl); ++ bl->props.brightness = brightness; + +- return 0; ++ return backlight_update_status(bl); + } + ++/* The backlight class child restores brightness after this parent resumes. */ ++static int dwi_bl_resume(struct device *dev) ++{ ++ return dwi_bl_init(dev_get_drvdata(dev)); ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(dwi_bl_pm_ops, NULL, dwi_bl_resume); ++ + static const struct of_device_id dwi_bl_of_match[] = { + { .compatible = "apple,dwi-bl" }, + {}, +@@ -111,6 +183,7 @@ MODULE_DEVICE_TABLE(of, dwi_bl_of_match); + static struct platform_driver dwi_bl_driver = { + .driver = { + .name = "apple-dwi-bl", ++ .pm = pm_sleep_ptr(&dwi_bl_pm_ops), + .of_match_table = dwi_bl_of_match + }, + .probe = dwi_bl_probe, diff --git a/kernel/patches/j71t/0009-j71t-display-adbe.patch b/kernel/patches/j71t/0009-j71t-display-adbe.patch new file mode 100644 index 00000000..63a07652 --- /dev/null +++ b/kernel/patches/j71t/0009-j71t-display-adbe.patch @@ -0,0 +1,1992 @@ +From 71a3dfd5d39e0c956471096d78e15f809a96c88a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 09/12] drm: adbe: add a KMS driver for the A9 boot display + pipe +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Adopts the disp0 pipe and mode iBoot leaves running, takes the +framebuffer over from simpledrm, and adds page flips and vblank +interrupts. It never programs timings, clocks, power or the eDP +link. The internal panel appears as eDP-1. apple_adbe_drm.enable=0 +keeps simpledrm. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../bindings/display/adbe0,s8000.yaml | 82 ++ + drivers/gpu/drm/Kconfig | 1 + + drivers/gpu/drm/Makefile | 1 + + drivers/gpu/drm/adbe/Kconfig | 20 + + drivers/gpu/drm/adbe/Makefile | 3 + + drivers/gpu/drm/adbe/adbe_addr.h | 61 + + drivers/gpu/drm/adbe/adbe_drv.c | 1276 +++++++++++++++++ + drivers/gpu/drm/adbe/adbe_hw.h | 453 ++++++ + 8 files changed, 1897 insertions(+) + create mode 100644 Documentation/devicetree/bindings/display/adbe0,s8000.yaml + create mode 100644 drivers/gpu/drm/adbe/Kconfig + create mode 100644 drivers/gpu/drm/adbe/Makefile + create mode 100644 drivers/gpu/drm/adbe/adbe_addr.h + create mode 100644 drivers/gpu/drm/adbe/adbe_drv.c + create mode 100644 drivers/gpu/drm/adbe/adbe_hw.h + +diff --git a/Documentation/devicetree/bindings/display/adbe0,s8000.yaml b/Documentation/devicetree/bindings/display/adbe0,s8000.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..bbc6871b44c703b776b057ab8ab253fe12c0d50a +--- /dev/null ++++ b/Documentation/devicetree/bindings/display/adbe0,s8000.yaml +@@ -0,0 +1,82 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/display/adbe0,s8000.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple A9 display subsystem (disp0) ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: | ++ The A9 ADP/CLCD display subsystem has six register windows and five ++ interrupts. The compatible is the original Apple DeviceTree spelling. ++ This experimental binding describes the pipe that iBoot leaves running, ++ with an optional reserved VRAM area. The first window holds the ADP ++ control registers, the parameter FIFO port and both plane banks; the ++ first interrupt carries the pipe's VBI and packet-synced events. A ++ driver may adopt the running mode and replace the simple-framebuffer, ++ but never retrains the link. The node deliberately has no power-domain, ++ clock, reset, or IOMMU consumers: attaching those can reset the live ++ eDP link or the display DART, which firmware must leave as it is. ++ ++properties: ++ compatible: ++ const: adbe0,s8000 ++ ++ reg: ++ items: ++ - description: Main display window ++ - description: Display window A ++ - description: Display window B ++ - description: Display window C ++ - description: Display window D ++ - description: Seven-register side window ++ ++ interrupts: ++ items: ++ - description: ADP pipe interrupt (VBI, packet synced, pipe errors) ++ - description: Back-end interrupt candidate ++ - description: Third interrupt candidate ++ - description: Fourth interrupt candidate ++ - description: Fifth interrupt candidate ++ ++ memory-region: ++ maxItems: 1 ++ description: | ++ Reserved boot VRAM containing both the simple-framebuffer and optional ++ scratch scanout. Reservation alone does not prove DART reachability. ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ display-subsystem@206200000 { ++ compatible = "adbe0,s8000"; ++ reg = <0x2 0x06200000 0 0x100000>, ++ <0x2 0x06400000 0 0x8000>, ++ <0x2 0x06440000 0 0x8000>, ++ <0x2 0x06480000 0 0x8000>, ++ <0x2 0x064c0000 0 0x8000>, ++ <0x2 0x06500000 0 0x1c>; ++ interrupt-parent = <&aic>; ++ interrupts = , ++ , ++ , ++ , ++ ; ++ }; ++ }; +diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig +index 323422861e8f683590492184ae24c0064c1ca931..4c74c15014f8874da40dc048b002b168e33f2349 100644 +--- a/drivers/gpu/drm/Kconfig ++++ b/drivers/gpu/drm/Kconfig +@@ -299,6 +299,7 @@ config DRM_PRIVACY_SCREEN + default n + + # DRM driver Kconfig files, sorted ++source "drivers/gpu/drm/adbe/Kconfig" + source "drivers/gpu/drm/adp/Kconfig" + source "drivers/gpu/drm/amd/amdgpu/Kconfig" + source "drivers/gpu/drm/arm/Kconfig" +diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile +index e635fcffd3790954bc2f2dd52566621d086239de..feb80313477ea63171f8763cb52523df77809404 100644 +--- a/drivers/gpu/drm/Makefile ++++ b/drivers/gpu/drm/Makefile +@@ -219,6 +219,7 @@ obj-y += sysfb/ + obj-y += tiny/ + obj-$(CONFIG_DRM_PL111) += pl111/ + obj-$(CONFIG_DRM_TVE200) += tve200/ ++obj-y += adbe/ + obj-$(CONFIG_DRM_ADP) += adp/ + obj-$(CONFIG_DRM_XEN) += xen/ + obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/ +diff --git a/drivers/gpu/drm/adbe/Kconfig b/drivers/gpu/drm/adbe/Kconfig +new file mode 100644 +index 0000000000000000000000000000000000000000..97dde4b67a00f2a8bd56b6fac53d76ced9795796 +--- /dev/null ++++ b/drivers/gpu/drm/adbe/Kconfig +@@ -0,0 +1,20 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++config DRM_APPLE_ADBE ++ tristate "DRM support for the Apple A9 display pipe" ++ depends on DRM && OF && ARM64 && MMU ++ depends on ARCH_APPLE || COMPILE_TEST ++ select APERTURE_HELPERS ++ select DRM_CLIENT_SELECTION ++ select DRM_GEM_DMA_HELPER ++ select DRM_KMS_HELPER ++ help ++ KMS driver for the A9 (S8000/S8003) disp0 pipe that iBoot leaves ++ running. It adopts the boot mode, takes the framebuffer over from ++ simpledrm, and adds page flips through the pipe's parameter FIFO ++ and vblank interrupts. It never programs timings, clocks, power or ++ the eDP link. Scanout buffers are DMA (CMA) allocations. ++ ++ Built in, it replaces simpledrm during boot. Booting with ++ apple_adbe_drm.enable=0 keeps simpledrm instead. ++ ++ If M is selected, the module is called apple_adbe_drm. +diff --git a/drivers/gpu/drm/adbe/Makefile b/drivers/gpu/drm/adbe/Makefile +new file mode 100644 +index 0000000000000000000000000000000000000000..2b414419e5ca46756563590f77ff922ba3c93ba7 +--- /dev/null ++++ b/drivers/gpu/drm/adbe/Makefile +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++apple_adbe_drm-y := adbe_drv.o ++obj-$(CONFIG_DRM_APPLE_ADBE) += apple_adbe_drm.o +diff --git a/drivers/gpu/drm/adbe/adbe_addr.h b/drivers/gpu/drm/adbe/adbe_addr.h +new file mode 100644 +index 0000000000000000000000000000000000000000..d27359b96c44218668143c9f1974bfa780fe93f0 +--- /dev/null ++++ b/drivers/gpu/drm/adbe/adbe_addr.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Pure arithmetic for the A9 display pipe, shared by the drivers and an ++ * offline host test: parameter FIFO word encoding and bus address bounds. ++ * No kernel includes; the includer provides bool, u16, u32, u64 and ++ * BIT_ULL(). ++ */ ++#ifndef ADBE_ADDR_H ++#define ADBE_ADDR_H ++ ++#define ADBE_BUS_LIMIT BIT_ULL(32) /* the pipe's bus is 32-bit */ ++#define ADBE_ALIGN 64U /* plane base and stride */ ++ ++/* ++ * Preamble: bit 31, armed bit 30, words that follow [28:16], token [15:0]. ++ * The pipe loads the token into its frame counter (ADP+0x1034) when it ++ * takes the packet (round-2 device log), so it is a frame number. ++ */ ++static inline u32 adbe_pkt_preamble(u32 count, u16 token, bool armed) ++{ ++ return 0x80000000U | (armed ? 0x40000000U : 0) | ++ ((count & 0x1fffU) << 16) | token; ++} ++ ++/* Entry header: bit 29, one data word in [28:16], register in [15:0]. */ ++static inline u32 adbe_pkt_header(u32 reg) ++{ ++ return 0x20000000U | (1U << 16) | (reg & 0xffffU); ++} ++ ++/* Base/stride word: 64-byte aligned value plus a 6-bit tag. */ ++static inline u32 adbe_tag_word(u32 value, u32 tag) ++{ ++ return (value & ~(ADBE_ALIGN - 1)) | (tag & (ADBE_ALIGN - 1)); ++} ++ ++/* ++ * Bus address of [cpu, cpu + len) on the pipe's 32-bit bus, where ++ * bus = cpu - offset. Rejects misalignment, underflow and wrap. ++ */ ++static inline bool adbe_cpu_to_bus(u64 cpu, u64 offset, u64 len, u32 *bus) ++{ ++ if (!len || cpu < offset || (cpu & (ADBE_ALIGN - 1)) || ++ cpu - offset >= ADBE_BUS_LIMIT || ++ len > ADBE_BUS_LIMIT - (cpu - offset)) ++ return false; ++ *bus = (u32)(cpu - offset); ++ return true; ++} ++ ++/* ++ * A scratch frame at @offset after the boot frame, inside the reserved ++ * boot VRAM of @reserved bytes, not overlapping the live frame. ++ */ ++static inline bool adbe_scratch_ok(u64 frame, u64 offset, u64 reserved) ++{ ++ return frame && !(offset & (ADBE_ALIGN - 1)) && offset >= frame && ++ offset <= reserved && frame <= reserved - offset; ++} ++ ++#endif /* ADBE_ADDR_H */ +diff --git a/drivers/gpu/drm/adbe/adbe_drv.c b/drivers/gpu/drm/adbe/adbe_drv.c +new file mode 100644 +index 0000000000000000000000000000000000000000..758242433f5d1c2405e35951cc5ed1e7b16b029f +--- /dev/null ++++ b/drivers/gpu/drm/adbe/adbe_drv.c +@@ -0,0 +1,1276 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * DRM/KMS driver for the Apple A9 (S8000/S8003) display pipe, disp0. ++ * ++ * iBoot leaves the eDP link, the panel and the timing generator running. ++ * This driver adopts that 1536x2048 mode and never programs timings, ++ * clocks, power or the link. It offers one XRGB8888 primary plane; a new ++ * framebuffer reaches the pipe through the ADP parameter FIFO, which holds ++ * the packet until the next frame boundary and applies it there. Interrupt ++ * 0 carries both events used here: VBI (vblank) and 0x400 (packet applied), ++ * which completes the page flip, so a flip event never arrives before the ++ * new buffer is being scanned out. The packet's token is the next frame ++ * number, because the pipe loads it into its frame counter. ++ * Disabling the CRTC scans out a black buffer (the former boot framebuffer) ++ * instead of clearing the layer enable or touching the link, which the ++ * reverse engineering has not verified. ++ * ++ * Built in, it replaces simpledrm during boot; apple_adbe_drm.enable=0 on ++ * the kernel command line keeps simpledrm instead. ++ * ++ * The pipe bus is 32 bits wide with DRAM at bus 0 (bus = CPU - offset, the ++ * offset taken from the live scanout). Buffers are ordinary DMA allocations, ++ * so probe requires the display DART to pass the scanout through without ++ * translation; it checks this by reading the DART, never by writing it. ++ * ++ * The driver holds no framebuffer or GEM references of its own: the atomic ++ * helpers own every buffer's lifetime. debugfs (adbe_state, adbe_bos) still ++ * tracks each GEM object, its CPU mappings and whether it sits in CMA, so ++ * that a soak shows a buffer leak, and who holds it, directly. ++ */ ++#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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "adbe_hw.h" ++ ++#define ADBE_WIDTH 1536 ++#define ADBE_HEIGHT 2048 ++#define ADBE_PITCH (ADBE_WIDTH * 4) ++#define ADBE_KICK_WAIT_MS 20 /* pre-kick gate; iOS allows 1000 */ ++#define ADBE_FLIP_VBLANKS 3 /* complete on VBI if 0x400 never comes */ ++#define ADBE_FLIP_TIMEOUT_MS 100 /* complete anyway if no interrupt */ ++#define ADBE_PARK_WAIT_MS 100 ++#define ADBE_LAT_BUCKETS 4 /* flip latency 0, 1, 2, 3+ frames */ ++/* Interrupt sources while vblank is on (added to iBoot's mask). */ ++#define ADBE_INTEN_VBLANK (ADP_ST_VBI | ADP_ST_APPLIED) ++/* The CMA area accessors are not exported: only a built-in driver can tell. */ ++#define ADBE_CMA_KNOWN (IS_BUILTIN(CONFIG_DRM_APPLE_ADBE) && IS_ENABLED(CONFIG_DMA_CMA)) ++ ++/* Kill switch: apple_adbe_drm.enable=0 leaves the display to simpledrm. */ ++static bool enable = true; ++module_param(enable, bool, 0400); ++MODULE_PARM_DESC(enable, "Drive disp0 (default); 0 keeps simpledrm on the boot framebuffer"); ++ ++/* Writable; read once per bind, so a rebind picks up new values. */ ++static char *commit = "base"; ++module_param(commit, charp, 0600); ++MODULE_PARM_DESC(commit, "Plane commit: base (packet with base[/stride]), full (whole layer set), sync (direct store + empty packet)"); ++ ++static char *token = "next"; ++module_param(token, charp, 0600); ++MODULE_PARM_DESC(token, "Preamble token (loaded into the frame counter): next (counter + 1), same, serial"); ++ ++enum adbe_bo_kind { ++ ADBE_BO_DUMB, /* allocated here: scanout or fbdev buffer */ ++ ADBE_BO_IMPORT, /* PRIME import, another device's memory */ ++ ADBE_BO_KINDS, ++}; ++ ++static const char * const adbe_bo_kind_names[ADBE_BO_KINDS] = { ++ [ADBE_BO_DUMB] = "dumb", ++ [ADBE_BO_IMPORT] = "import", ++}; ++ ++/* A GEM object with debugfs bookkeeping. */ ++struct adbe_bo { ++ struct drm_gem_dma_object dma; /* first: the helpers kfree() it */ ++ struct list_head node; /* adbe->bos once accounted, else empty */ ++ enum adbe_bo_kind kind; ++ bool in_cma; ++ atomic_t mmaps; /* live CPU mappings (VMAs) */ ++ ktime_t born; ++ pid_t pid; /* creator */ ++ char comm[TASK_COMM_LEN]; ++}; ++ ++static_assert(offsetof(struct adbe_bo, dma) == 0); ++ ++struct adbe { ++ struct drm_device drm; ++ struct drm_plane plane; ++ struct drm_crtc crtc; ++ struct drm_encoder encoder; ++ struct drm_connector connector; ++ void __iomem *adp; ++ void __iomem *boot_va; ++ struct resource boot; ++ struct device_node *boot_np; /* the simple-framebuffer node */ ++ u64 bus_offset; ++ u32 boot_base; ++ u32 boot_stride; ++ u32 saved_inten; ++ bool vbl_enabled; ++ enum adbe_commit commit; ++ enum adbe_token token; ++ int irq; ++ /* Interrupt handler only. */ ++ u16 last_frame; ++ bool frame_valid; ++ /* Staged by the plane update, pushed by the CRTC flush. */ ++ u32 next_base; ++ u32 next_stride; ++ bool next_dirty; ++ /* Commit-tail only (serialized by the atomic helpers). */ ++ u32 cur_base; ++ bool boot_black; ++ u16 serial; ++ /* Protected by drm.event_lock. */ ++ struct drm_pending_vblank_event *event; ++ unsigned int event_vbls; ++ bool event_applied; /* 0x400 seen for the pending flip */ ++ u32 pending_base; ++ u64 kick_seq; /* vblank count when the flip was pushed */ ++ ktime_t kick_time; ++ int kick_done; /* completions counted before the push */ ++ struct timer_list flip_timer; ++ /* ++ * Statistics for debugfs. Every status read that shows a completion ++ * bit counts once, wherever it happens, so that kicks equals ++ * completions plus the packet still in the FIFO. ++ */ ++ atomic_t kicks; ++ atomic_t completions; ++ atomic_t applied; /* reads with 0x400 */ ++ atomic_t synced; /* reads with 0x8 (iOS's bit, never seen yet) */ ++ atomic_t busy; ++ atomic_t vbl_fallbacks; ++ atomic_t timeouts; ++ atomic_t latch_mismatch; /* base readback != pushed at completion */ ++ atomic_t frame_jumps; /* counter moved by != 1 between VBIs */ ++ atomic_t errors; ++ atomic_t lat_frames[ADBE_LAT_BUCKETS]; /* push to completion */ ++ u32 lat_us_max; ++ u64 lat_us_sum; ++ u32 lat_count; ++ atomic_t vbis; /* VBI interrupts handled while vblank is on */ ++ atomic_t vbl_events; /* flip events armed with nothing to push */ ++ /* Live GEM objects, protected by bo_lock. */ ++ struct mutex bo_lock; ++ struct list_head bos; ++ unsigned int bo_count[ADBE_BO_KINDS]; ++ u64 bo_bytes[ADBE_BO_KINDS]; ++ u64 bo_cma_bytes[ADBE_BO_KINDS]; ++ u64 bo_dumb_peak; ++}; ++ ++static struct adbe *to_adbe(struct drm_device *drm) ++{ ++ return container_of(drm, struct adbe, drm); ++} ++ ++static struct adbe_bo *to_adbe_bo(struct drm_gem_object *obj) ++{ ++ return container_of(obj, struct adbe_bo, dma.base); ++} ++ ++static const struct drm_display_mode adbe_mode = { ++ DRM_MODE_INIT(60, ADBE_WIDTH, ADBE_HEIGHT, 148, 197), ++}; ++ ++/* Caller holds event_lock. Returns the event to complete, if any. */ ++static struct drm_pending_vblank_event *adbe_take_event(struct adbe *adbe) ++{ ++ struct drm_pending_vblank_event *event = adbe->event; ++ ++ adbe->event = NULL; ++ return event; ++} ++ ++static void adbe_send_event(struct adbe *adbe, struct drm_pending_vblank_event *event) ++{ ++ unsigned long flags; ++ ++ if (!event) ++ return; ++ spin_lock_irqsave(&adbe->drm.event_lock, flags); ++ drm_crtc_send_vblank_event(&adbe->crtc, event); ++ spin_unlock_irqrestore(&adbe->drm.event_lock, flags); ++ drm_crtc_vblank_put(&adbe->crtc); ++} ++ ++/* Account one status read's completion bits (any context). */ ++static void adbe_count_done(struct adbe *adbe, u32 status) ++{ ++ if (!(status & ADP_ST_DONE)) ++ return; ++ atomic_inc(&adbe->completions); ++ if (status & ADP_ST_APPLIED) ++ atomic_inc(&adbe->applied); ++ if (status & ADP_ST_SYNCED) ++ atomic_inc(&adbe->synced); ++} ++ ++/* Push-to-completion latency of one flip, after the vblank was counted. */ ++static void adbe_count_latency(struct adbe *adbe, u64 seq, ktime_t start) ++{ ++ u64 frames = drm_crtc_vblank_count(&adbe->crtc) - seq; ++ u32 us = ktime_us_delta(ktime_get(), start); ++ ++ atomic_inc(&adbe->lat_frames[min_t(u64, frames, ADBE_LAT_BUCKETS - 1)]); ++ WRITE_ONCE(adbe->lat_us_max, max(adbe->lat_us_max, us)); ++ WRITE_ONCE(adbe->lat_us_sum, adbe->lat_us_sum + us); ++ WRITE_ONCE(adbe->lat_count, adbe->lat_count + 1); ++} ++ ++static irqreturn_t adbe_irq(int irq, void *data) ++{ ++ struct adbe *adbe = data; ++ struct drm_pending_vblank_event *done = NULL; ++ bool applied = false; ++ ktime_t start = 0; ++ u64 seq = 0; ++ u32 status; ++ ++ /* ++ * Read and acknowledge under event_lock so a flip armed by ++ * adbe_kick() never completes on a status read before its push. ++ */ ++ spin_lock(&adbe->drm.event_lock); ++ status = readl(adbe->adp + ADP_STATUS); ++ if (status) ++ writel(status, adbe->adp + ADP_STATUS); /* W1C, level line */ ++ adbe_count_done(adbe, status); ++ if (adbe->event) { ++ if (status & ADP_ST_DONE) ++ adbe->event_applied = true; ++ /* ++ * Complete once the boundary's vblank is counted (VBI here, or ++ * already handled), so the event carries that frame's sequence ++ * and timestamp even if 0x400 interrupts a moment before VBI. ++ */ ++ if (adbe->event_applied && ++ ((status & ADP_ST_VBI) || ++ drm_crtc_vblank_count(&adbe->crtc) != adbe->kick_seq)) { ++ done = adbe_take_event(adbe); ++ applied = true; ++ seq = adbe->kick_seq; ++ start = adbe->kick_time; ++ /* Camera-free cross-check: the plane holds the pushed base. */ ++ if (readl(adbe->adp + ADP_BASE) != adbe->pending_base) ++ atomic_inc(&adbe->latch_mismatch); ++ } else if (!adbe->event_applied && (status & ADP_ST_VBI) && ++ ++adbe->event_vbls >= ADBE_FLIP_VBLANKS) { ++ done = adbe_take_event(adbe); ++ atomic_inc(&adbe->vbl_fallbacks); ++ } ++ } ++ spin_unlock(&adbe->drm.event_lock); ++ if (!status) ++ return IRQ_NONE; ++ if (status & ADP_ST_ERRORS) { ++ atomic_or(status & ADP_ST_ERRORS, &adbe->errors); ++ dev_warn_ratelimited(adbe->drm.dev, "pipe error bits %#x\n", ++ status & ADP_ST_ERRORS); ++ } ++ /* VBI is sticky: only count it while it is enabled (not on OutUnder). */ ++ if ((status & ADP_ST_VBI) && READ_ONCE(adbe->vbl_enabled)) { ++ u16 frame = readl(adbe->adp + ADP_FRAME); ++ ++ /* A reloaded counter (or a missed VBI) shows up here. */ ++ if (adbe->frame_valid && adbe_frame_jump(adbe->last_frame, frame)) ++ atomic_inc(&adbe->frame_jumps); ++ adbe->last_frame = frame; ++ adbe->frame_valid = true; ++ atomic_inc(&adbe->vbis); ++ drm_crtc_handle_vblank(&adbe->crtc); ++ } ++ /* The event carries this boundary's vblank sequence and timestamp. */ ++ if (applied) ++ adbe_count_latency(adbe, seq, start); ++ adbe_send_event(adbe, done); ++ return IRQ_HANDLED; ++} ++ ++/* Safety net: never leave a flip pending if no interrupt completes it. */ ++static void adbe_flip_timeout(struct timer_list *t) ++{ ++ struct adbe *adbe = timer_container_of(adbe, t, flip_timer); ++ struct drm_pending_vblank_event *done; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&adbe->drm.event_lock, flags); ++ done = adbe_take_event(adbe); ++ spin_unlock_irqrestore(&adbe->drm.event_lock, flags); ++ if (done) { ++ atomic_inc(&adbe->timeouts); ++ dev_warn_ratelimited(adbe->drm.dev, "flip not acknowledged in %u ms\n", ++ ADBE_FLIP_TIMEOUT_MS); ++ } ++ adbe_send_event(adbe, done); ++} ++ ++/* ++ * Push one plane update. Sleeps for at most ADBE_KICK_WAIT_MS on the ++ * pre-kick gate; a busy FIFO skips the update instead of spinning. If ++ * @event is given and vblank is on, it completes when the pipe applies ++ * the packet. Returns 0 if the packet was pushed. ++ */ ++static int adbe_kick(struct adbe *adbe, u32 base, u32 stride, ++ struct drm_pending_vblank_event **event) ++{ ++ struct adbe_pkt pkt; ++ u32 parmst, ptr, status; ++ unsigned long flags; ++ bool armed = false; ++ int ret; ++ ++ ret = adbe_pkt_build(&pkt, adbe->adp, adbe->commit, base, stride); ++ if (ret) ++ return ret; ++ ret = adbe_fifo_wait_ready(adbe->adp, ADBE_KICK_WAIT_MS, &parmst, &ptr); ++ if (ret) { ++ atomic_inc(&adbe->busy); ++ dev_warn_ratelimited(adbe->drm.dev, ++ "FIFO busy (0x101c=%#x 0x1040=%#x), update skipped\n", ++ parmst, ptr); ++ return ret; ++ } ++ if (event && *event && !drm_crtc_vblank_get(&adbe->crtc)) ++ armed = true; ++ spin_lock_irqsave(&adbe->drm.event_lock, flags); ++ /* Completions before this push belong to an earlier packet. */ ++ status = readl(adbe->adp + ADP_STATUS) & ADP_ST_DONE; ++ if (status) { ++ adbe_count_done(adbe, status); ++ writel(status, adbe->adp + ADP_STATUS); ++ } ++ if (adbe->commit == ADBE_COMMIT_SYNC) { ++ if (readl(adbe->adp + ADP_STRIDE) != stride) ++ writel(stride, adbe->adp + ADP_STRIDE); ++ writel(base, adbe->adp + ADP_BASE); ++ } ++ adbe->pending_base = base; ++ /* From here on, a new completion can only be this packet's. */ ++ adbe->kick_done = atomic_read(&adbe->completions); ++ adbe_pkt_push(adbe->adp, &pkt, adbe->token, ++adbe->serial, true); ++ atomic_inc(&adbe->kicks); ++ if (armed) { ++ adbe->event = *event; ++ adbe->event_vbls = 0; ++ adbe->event_applied = false; ++ adbe->kick_seq = drm_crtc_vblank_count(&adbe->crtc); ++ adbe->kick_time = ktime_get(); ++ *event = NULL; ++ } ++ spin_unlock_irqrestore(&adbe->drm.event_lock, flags); ++ if (armed) ++ mod_timer(&adbe->flip_timer, ++ jiffies + msecs_to_jiffies(ADBE_FLIP_TIMEOUT_MS)); ++ adbe->cur_base = base; ++ return 0; ++} ++ ++/* ++ * Scan out the boot framebuffer, cleared to black: "off" for the CRTC and ++ * the state left behind on unbind, shutdown and kexec. Polls for the ++ * completion (the vblank interrupt is off by now, and the completion is ++ * counted like any other); if the packet is not confirmed, falls back to ++ * a direct store plus an empty packet. ++ */ ++static void adbe_park(struct adbe *adbe) ++{ ++ struct adbe_pkt empty = { .count = 0 }; ++ u32 parmst, ptr; ++ unsigned int ms; ++ ++ if (!adbe->boot_black) { ++ memset_io(adbe->boot_va, 0, ADBE_PITCH * ADBE_HEIGHT); ++ wmb(); /* publish write-combined stores before the flip */ ++ adbe->boot_black = true; ++ } ++ if (adbe->cur_base == adbe->boot_base && ++ readl(adbe->adp + ADP_BASE) == adbe->boot_base) ++ return; ++ if (!adbe_kick(adbe, adbe->boot_base, adbe->boot_stride, NULL)) { ++ for (ms = 0; ms < ADBE_PARK_WAIT_MS; ms++) { ++ unsigned long flags; ++ u32 status; ++ ++ /* Same lock as the interrupt handler: count it once. */ ++ spin_lock_irqsave(&adbe->drm.event_lock, flags); ++ status = readl(adbe->adp + ADP_STATUS) & ADP_ST_DONE; ++ if (status) { ++ adbe_count_done(adbe, status); ++ writel(status, adbe->adp + ADP_STATUS); ++ } ++ spin_unlock_irqrestore(&adbe->drm.event_lock, flags); ++ if (atomic_read(&adbe->completions) != adbe->kick_done) { ++ if (readl(adbe->adp + ADP_BASE) == adbe->boot_base) ++ return; ++ break; ++ } ++ usleep_range(900, 1100); ++ } ++ } ++ dev_warn(adbe->drm.dev, "park packet not confirmed; storing the boot base directly\n"); ++ writel(adbe->boot_base, adbe->adp + ADP_BASE); ++ if (!adbe_fifo_wait_ready(adbe->adp, ADBE_KICK_WAIT_MS, &parmst, &ptr)) ++ adbe_pkt_push(adbe->adp, &empty, adbe->token, ++adbe->serial, true); ++ adbe->cur_base = adbe->boot_base; ++ msleep(50); ++ if (readl(adbe->adp + ADP_BASE) != adbe->boot_base) ++ dev_err(adbe->drm.dev, "boot base not restored (readback %#x)\n", ++ readl(adbe->adp + ADP_BASE)); ++} ++ ++static int adbe_plane_atomic_check(struct drm_plane *plane, ++ struct drm_atomic_commit *state) ++{ ++ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); ++ struct adbe *adbe = to_adbe(plane->dev); ++ struct drm_crtc_state *crtc_state = NULL; ++ struct drm_framebuffer *fb = new_state->fb; ++ dma_addr_t addr; ++ u64 len; ++ u32 bus; ++ int ret; ++ ++ if (new_state->crtc) ++ crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); ++ ret = drm_atomic_helper_check_plane_state(new_state, crtc_state, ++ DRM_PLANE_NO_SCALING, ++ DRM_PLANE_NO_SCALING, false, false); ++ if (ret || !new_state->visible) ++ return ret; ++ if (fb->pitches[0] % ADBE_ALIGN || fb->pitches[0] < ADBE_PITCH || ++ fb->pitches[0] > SZ_32K) { ++ drm_dbg_kms(plane->dev, "pitch %u unsupported\n", fb->pitches[0]); ++ return -EINVAL; ++ } ++ addr = drm_fb_dma_get_gem_addr(fb, new_state, 0); ++ len = (u64)fb->pitches[0] * (ADBE_HEIGHT - 1) + ADBE_PITCH; ++ if (!adbe_cpu_to_bus(addr, adbe->bus_offset, len, &bus)) { ++ drm_dbg_kms(plane->dev, "buffer %pad not reachable on the pipe bus\n", &addr); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static void adbe_plane_atomic_update(struct drm_plane *plane, ++ struct drm_atomic_commit *state) ++{ ++ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); ++ struct adbe *adbe = to_adbe(plane->dev); ++ struct drm_framebuffer *fb = new_state->fb; ++ dma_addr_t addr; ++ u32 bus; ++ ++ if (!new_state->visible) ++ return; ++ addr = drm_fb_dma_get_gem_addr(fb, new_state, 0); ++ /* Checked in atomic_check; the length only matters there. */ ++ if (!adbe_cpu_to_bus(addr, adbe->bus_offset, ADBE_ALIGN, &bus)) ++ return; ++ adbe->next_base = adbe_tag_word(bus, adbe->boot_base); ++ adbe->next_stride = adbe_tag_word(fb->pitches[0], adbe->boot_stride); ++ adbe->next_dirty = true; ++} ++ ++static const struct drm_plane_helper_funcs adbe_plane_helper_funcs = { ++ .atomic_check = adbe_plane_atomic_check, ++ .atomic_update = adbe_plane_atomic_update, ++}; ++ ++static const struct drm_plane_funcs adbe_plane_funcs = { ++ .update_plane = drm_atomic_helper_update_plane, ++ .disable_plane = drm_atomic_helper_disable_plane, ++ .destroy = drm_plane_cleanup, ++ .reset = drm_atomic_helper_plane_reset, ++ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, ++}; ++ ++static const u32 adbe_formats[] = { ++ DRM_FORMAT_XRGB8888, ++}; ++ ++static const u64 adbe_modifiers[] = { ++ DRM_FORMAT_MOD_LINEAR, ++ DRM_FORMAT_MOD_INVALID, ++}; ++ ++static int adbe_crtc_atomic_check(struct drm_crtc *crtc, ++ struct drm_atomic_commit *state) ++{ ++ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ++ ++ if (!crtc_state->enable) ++ return 0; ++ if (crtc_state->mode.hdisplay != ADBE_WIDTH || ++ crtc_state->mode.vdisplay != ADBE_HEIGHT) ++ return -EINVAL; ++ return drm_atomic_helper_check_crtc_primary_plane(crtc_state); ++} ++ ++static void adbe_crtc_atomic_flush(struct drm_crtc *crtc, ++ struct drm_atomic_commit *state) ++{ ++ struct adbe *adbe = to_adbe(crtc->dev); ++ struct drm_pending_vblank_event *event = crtc->state->event; ++ unsigned long flags; ++ ++ crtc->state->event = NULL; ++ if (adbe->next_dirty) { ++ adbe->next_dirty = false; ++ /* On success the event moves into adbe->event. */ ++ adbe_kick(adbe, adbe->next_base, adbe->next_stride, &event); ++ } ++ if (!event) ++ return; ++ /* Nothing pushed: complete at the next vblank, or now if it is off. */ ++ spin_lock_irqsave(&crtc->dev->event_lock, flags); ++ atomic_inc(&adbe->vbl_events); ++ if (drm_crtc_vblank_get(crtc) == 0) ++ drm_crtc_arm_vblank_event(crtc, event); ++ else ++ drm_crtc_send_vblank_event(crtc, event); ++ spin_unlock_irqrestore(&crtc->dev->event_lock, flags); ++} ++ ++static void adbe_crtc_atomic_enable(struct drm_crtc *crtc, ++ struct drm_atomic_commit *state) ++{ ++ drm_crtc_vblank_on(crtc); ++} ++ ++static void adbe_crtc_atomic_disable(struct drm_crtc *crtc, ++ struct drm_atomic_commit *state) ++{ ++ struct adbe *adbe = to_adbe(crtc->dev); ++ struct drm_pending_vblank_event *done; ++ unsigned long flags; ++ ++ timer_delete_sync(&adbe->flip_timer); ++ spin_lock_irqsave(&crtc->dev->event_lock, flags); ++ done = adbe_take_event(adbe); ++ spin_unlock_irqrestore(&crtc->dev->event_lock, flags); ++ adbe_send_event(adbe, done); ++ drm_crtc_vblank_off(crtc); ++ adbe->next_dirty = false; ++ adbe_park(adbe); ++ spin_lock_irqsave(&crtc->dev->event_lock, flags); ++ if (crtc->state->event && !crtc->state->active) { ++ drm_crtc_send_vblank_event(crtc, crtc->state->event); ++ crtc->state->event = NULL; ++ } ++ spin_unlock_irqrestore(&crtc->dev->event_lock, flags); ++} ++ ++static int adbe_enable_vblank(struct drm_crtc *crtc) ++{ ++ struct adbe *adbe = to_adbe(crtc->dev); ++ ++ adbe->frame_valid = false; /* no jump across a vblank-off gap */ ++ /* ++ * VBI stays latched while vblank is off; unmasking it would report a ++ * stale boundary as a vblank now. Clear it first (round 2b: 64 VBI ++ * interrupts for 63 frames). ++ */ ++ writel(ADP_ST_VBI, adbe->adp + ADP_STATUS); ++ WRITE_ONCE(adbe->vbl_enabled, true); ++ writel(adbe->saved_inten | ADBE_INTEN_VBLANK, adbe->adp + ADP_INTEN); ++ return 0; ++} ++ ++static void adbe_disable_vblank(struct drm_crtc *crtc) ++{ ++ struct adbe *adbe = to_adbe(crtc->dev); ++ ++ /* The exact word iBoot left, not a bit clear. */ ++ writel(adbe->saved_inten, adbe->adp + ADP_INTEN); ++ WRITE_ONCE(adbe->vbl_enabled, false); ++} ++ ++static const struct drm_crtc_helper_funcs adbe_crtc_helper_funcs = { ++ .atomic_check = adbe_crtc_atomic_check, ++ .atomic_flush = adbe_crtc_atomic_flush, ++ .atomic_enable = adbe_crtc_atomic_enable, ++ .atomic_disable = adbe_crtc_atomic_disable, ++}; ++ ++static const struct drm_crtc_funcs adbe_crtc_funcs = { ++ .reset = drm_atomic_helper_crtc_reset, ++ .set_config = drm_atomic_helper_set_config, ++ .page_flip = drm_atomic_helper_page_flip, ++ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, ++ .enable_vblank = adbe_enable_vblank, ++ .disable_vblank = adbe_disable_vblank, ++}; ++ ++static int adbe_connector_get_modes(struct drm_connector *connector) ++{ ++ return drm_connector_helper_get_modes_fixed(connector, &adbe_mode); ++} ++ ++static const struct drm_connector_helper_funcs adbe_connector_helper_funcs = { ++ .get_modes = adbe_connector_get_modes, ++}; ++ ++static const struct drm_connector_funcs adbe_connector_funcs = { ++ .reset = drm_atomic_helper_connector_reset, ++ .fill_modes = drm_helper_probe_single_connector_modes, ++ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, ++ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, ++}; ++ ++static const struct drm_mode_config_funcs adbe_mode_config_funcs = { ++ .fb_create = drm_gem_fb_create, ++ .atomic_check = drm_atomic_helper_check, ++ .atomic_commit = drm_atomic_helper_commit, ++}; ++ ++/* Enable the CRTC before planes, so a flip always has vblank available. */ ++static const struct drm_mode_config_helper_funcs adbe_mode_config_helpers = { ++ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, ++}; ++ ++/* ++ * Whether a buffer sits in the default CMA area. When CMA is full, the DMA ++ * allocator falls back to one high-order page allocation per buffer (order ++ * 10 for a 12 MiB scanout buffer), and that is where reclaim and compaction ++ * pressure comes from. ++ */ ++static bool adbe_in_cma(struct device *dev, dma_addr_t addr, size_t size) ++{ ++#if ADBE_CMA_KNOWN ++ struct cma *cma = dev_get_cma_area(dev); ++ phys_addr_t pa = dma_to_phys(dev, addr), base; ++ ++ if (!cma) ++ return false; ++ base = cma_get_base(cma); ++ return pa >= base && pa + size <= base + cma_get_size(cma); ++#else ++ return false; ++#endif ++} ++ ++/* ++ * Start tracking a new object; the caller holds a reference to it. A dumb ++ * handle closed and reused by another thread before the lookup can name an ++ * object that is already tracked: leave that one alone. ++ */ ++static void adbe_bo_account(struct adbe *adbe, struct drm_gem_object *obj, ++ enum adbe_bo_kind kind) ++{ ++ struct adbe_bo *bo = to_adbe_bo(obj); ++ ++ mutex_lock(&adbe->bo_lock); ++ if (!list_empty(&bo->node)) { ++ mutex_unlock(&adbe->bo_lock); ++ return; ++ } ++ bo->kind = kind; ++ bo->in_cma = adbe_in_cma(adbe->drm.dev, bo->dma.dma_addr, obj->size); ++ bo->born = ktime_get(); ++ bo->pid = task_tgid_nr(current); ++ get_task_comm(bo->comm, current); ++ list_add_tail(&bo->node, &adbe->bos); ++ adbe->bo_count[kind]++; ++ adbe->bo_bytes[kind] += obj->size; ++ if (bo->in_cma) ++ adbe->bo_cma_bytes[kind] += obj->size; ++ adbe->bo_dumb_peak = max(adbe->bo_dumb_peak, adbe->bo_bytes[ADBE_BO_DUMB]); ++ mutex_unlock(&adbe->bo_lock); ++} ++ ++static void adbe_gem_free(struct drm_gem_object *obj) ++{ ++ struct adbe *adbe = to_adbe(obj->dev); ++ struct adbe_bo *bo = to_adbe_bo(obj); ++ ++ mutex_lock(&adbe->bo_lock); ++ if (!list_empty(&bo->node)) { ++ list_del(&bo->node); ++ adbe->bo_count[bo->kind]--; ++ adbe->bo_bytes[bo->kind] -= obj->size; ++ if (bo->in_cma) ++ adbe->bo_cma_bytes[bo->kind] -= obj->size; ++ } ++ mutex_unlock(&adbe->bo_lock); ++ drm_gem_dma_free(&bo->dma); ++} ++ ++/* A mapping holds an object reference: count them, for orphaned mmaps. */ ++static void adbe_gem_vm_open(struct vm_area_struct *vma) ++{ ++ atomic_inc(&to_adbe_bo(vma->vm_private_data)->mmaps); ++ drm_gem_vm_open(vma); ++} ++ ++static void adbe_gem_vm_close(struct vm_area_struct *vma) ++{ ++ atomic_dec(&to_adbe_bo(vma->vm_private_data)->mmaps); ++ drm_gem_vm_close(vma); ++} ++ ++static const struct vm_operations_struct adbe_gem_vm_ops = { ++ .open = adbe_gem_vm_open, ++ .close = adbe_gem_vm_close, ++}; ++ ++static int adbe_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) ++{ ++ int ret = drm_gem_dma_mmap(to_drm_gem_dma_obj(obj), vma); ++ ++ if (!ret) ++ atomic_inc(&to_adbe_bo(obj)->mmaps); ++ return ret; ++} ++ ++/* drm_gem_dma_default_funcs, plus the bookkeeping. */ ++static const struct drm_gem_object_funcs adbe_gem_funcs = { ++ .free = adbe_gem_free, ++ .print_info = drm_gem_dma_object_print_info, ++ .get_sg_table = drm_gem_dma_object_get_sg_table, ++ .vmap = drm_gem_dma_object_vmap, ++ .mmap = adbe_gem_mmap, ++ .vm_ops = &adbe_gem_vm_ops, ++}; ++ ++static struct drm_gem_object *adbe_gem_create_object(struct drm_device *drm, ++ size_t size) ++{ ++ struct adbe_bo *bo = kzalloc(sizeof(*bo), GFP_KERNEL); ++ ++ if (!bo) ++ return ERR_PTR(-ENOMEM); ++ INIT_LIST_HEAD(&bo->node); ++ bo->dma.base.funcs = &adbe_gem_funcs; ++ return &bo->dma.base; ++} ++ ++static int adbe_dumb_create(struct drm_file *file, struct drm_device *drm, ++ struct drm_mode_create_dumb *args) ++{ ++ struct drm_gem_object *obj; ++ int ret = drm_mode_size_dumb(drm, args, ADBE_ALIGN, 0); ++ ++ if (!ret) ++ ret = drm_gem_dma_dumb_create_internal(file, drm, args); ++ if (ret) ++ return ret; ++ /* Only the handle holds it, and another thread may close it already. */ ++ obj = drm_gem_object_lookup(file, args->handle); ++ if (obj) { ++ adbe_bo_account(to_adbe(drm), obj, ADBE_BO_DUMB); ++ drm_gem_object_put(obj); ++ } ++ return 0; ++} ++ ++static struct drm_gem_object * ++adbe_gem_prime_import_sg_table(struct drm_device *drm, ++ struct dma_buf_attachment *attach, ++ struct sg_table *sgt) ++{ ++ struct drm_gem_object *obj; ++ ++ obj = drm_gem_dma_prime_import_sg_table(drm, attach, sgt); ++ if (!IS_ERR(obj)) ++ adbe_bo_account(to_adbe(drm), obj, ADBE_BO_IMPORT); ++ return obj; ++} ++ ++/* Buffers per client: GEM handles and the framebuffers made from them. */ ++static void adbe_show_clients(struct seq_file *m, struct adbe *adbe) ++{ ++ struct drm_device *drm = &adbe->drm; ++ struct drm_file *file; ++ ++ mutex_lock(&drm->filelist_mutex); ++ list_for_each_entry_reverse(file, &drm->filelist, lhead) { ++ unsigned int handles[ADBE_BO_KINDS] = {}, fbs = 0; ++ u64 bytes[ADBE_BO_KINDS] = {}; ++ bool master = drm_is_current_master(file); ++ struct drm_framebuffer *fb; ++ struct drm_gem_object *obj; ++ struct task_struct *task; ++ struct pid *pid; ++ int id; ++ ++ spin_lock(&file->table_lock); ++ idr_for_each_entry(&file->object_idr, obj, id) { ++ enum adbe_bo_kind kind = to_adbe_bo(obj)->kind; ++ ++ handles[kind]++; ++ bytes[kind] += obj->size; ++ } ++ spin_unlock(&file->table_lock); ++ mutex_lock(&file->fbs_lock); ++ list_for_each_entry(fb, &file->fbs, filp_head) ++ fbs++; ++ mutex_unlock(&file->fbs_lock); ++ ++ rcu_read_lock(); /* file->pid and the task's comm */ ++ pid = rcu_dereference(file->pid); ++ task = pid_task(pid, PIDTYPE_TGID); ++ seq_printf(m, "client %s[%d] master=%c fbs=%u dumb=%u/%lluK import=%u/%lluK\n", ++ task ? task->comm : "?", pid_vnr(pid), master ? 'y' : 'n', ++ fbs, handles[ADBE_BO_DUMB], bytes[ADBE_BO_DUMB] >> 10, ++ handles[ADBE_BO_IMPORT], bytes[ADBE_BO_IMPORT] >> 10); ++ rcu_read_unlock(); ++ } ++ mutex_unlock(&drm->filelist_mutex); ++} ++ ++/* ++ * Memory summary. Orphans have no handle left: only a framebuffer, a CPU ++ * mapping, a dma-buf export or the kernel (fbdev) keeps them alive. ++ */ ++static void adbe_show_memory(struct seq_file *m, struct adbe *adbe) ++{ ++ struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(&adbe->crtc); ++ unsigned int orphans = 0, mmaps = 0; ++ u64 orphan_bytes = 0; ++ struct adbe_bo *bo; ++ unsigned long flags; ++ bool pending; ++ int fbs; ++ ++ mutex_lock(&adbe->bo_lock); ++ list_for_each_entry(bo, &adbe->bos, node) { ++ mmaps += atomic_read(&bo->mmaps); ++ if (!READ_ONCE(bo->dma.base.handle_count)) { ++ orphans++; ++ orphan_bytes += bo->dma.base.size; ++ } ++ } ++ seq_printf(m, "gem dumb=%u/%lluK import=%u/%lluK dumb-peak=%lluK orphans=%u/%lluK mmaps=%u", ++ adbe->bo_count[ADBE_BO_DUMB], adbe->bo_bytes[ADBE_BO_DUMB] >> 10, ++ adbe->bo_count[ADBE_BO_IMPORT], adbe->bo_bytes[ADBE_BO_IMPORT] >> 10, ++ adbe->bo_dumb_peak >> 10, orphans, orphan_bytes >> 10, mmaps); ++ if (ADBE_CMA_KNOWN) ++ seq_printf(m, " dumb-in-cma=%lluK import-in-cma=%lluK", ++ adbe->bo_cma_bytes[ADBE_BO_DUMB] >> 10, ++ adbe->bo_cma_bytes[ADBE_BO_IMPORT] >> 10); ++ else ++ seq_puts(m, " dumb-in-cma=? import-in-cma=?"); ++ mutex_unlock(&adbe->bo_lock); ++ ++ mutex_lock(&adbe->drm.mode_config.fb_lock); ++ fbs = adbe->drm.mode_config.num_fb; ++ mutex_unlock(&adbe->drm.mode_config.fb_lock); ++ seq_printf(m, " fbs=%d cma-free=%luK\n", fbs, ++ global_zone_page_state(NR_FREE_CMA_PAGES) << (PAGE_SHIFT - 10)); ++ ++ spin_lock_irqsave(&adbe->drm.event_lock, flags); ++ pending = adbe->event; ++ spin_unlock_irqrestore(&adbe->drm.event_lock, flags); ++ seq_printf(m, "vblank refcount=%d enabled=%d vbi=%d vbl-events=%d flip-pending=%d timer=%d\n", ++ atomic_read(&vblank->refcount), READ_ONCE(vblank->enabled), ++ atomic_read(&adbe->vbis), atomic_read(&adbe->vbl_events), ++ pending, timer_pending(&adbe->flip_timer)); ++ adbe_show_clients(m, adbe); ++} ++ ++/* One line per live GEM object, oldest first. */ ++static int adbe_bos_show(struct seq_file *m, void *data) ++{ ++ struct drm_debugfs_entry *entry = m->private; ++ struct adbe *adbe = to_adbe(entry->dev); ++ ktime_t now = ktime_get(); ++ struct adbe_bo *bo; ++ ++ mutex_lock(&adbe->bo_lock); ++ list_for_each_entry(bo, &adbe->bos, node) { ++ struct drm_gem_object *obj = &bo->dma.base; ++ ++ seq_printf(m, "%-6s %6zuK dma=%pad cma=%c handles=%u mmaps=%d refs=%u exported=%c age=%llds by %s[%d]\n", ++ adbe_bo_kind_names[bo->kind], obj->size >> 10, ++ &bo->dma.dma_addr, ++ ADBE_CMA_KNOWN ? (bo->in_cma ? 'y' : 'n') : '?', ++ READ_ONCE(obj->handle_count), atomic_read(&bo->mmaps), ++ kref_read(&obj->refcount), READ_ONCE(obj->dma_buf) ? 'y' : 'n', ++ ktime_ms_delta(now, bo->born) / MSEC_PER_SEC, ++ bo->comm, bo->pid); ++ } ++ mutex_unlock(&adbe->bo_lock); ++ return 0; ++} ++ ++static int adbe_state_show(struct seq_file *m, void *data) ++{ ++ struct drm_debugfs_entry *entry = m->private; ++ struct adbe *adbe = to_adbe(entry->dev); ++ u32 count = READ_ONCE(adbe->lat_count); ++ /* A packet not yet applied: the FIFO still holds it. */ ++ bool in_flight = readl(adbe->adp + ADP_FIFO_PTR) & ADP_FIFO_PTR_BUSY; ++ ++ seq_printf(m, "commit=%s token=%s kicks=%d completions=%d in-flight=%u applied-0x400=%d synced-0x8=%d busy=%d vbl-fallbacks=%d timeouts=%d latch-mismatch=%d frame-jumps=%d errors=%#x\n", ++ adbe_commit_names[adbe->commit], adbe_token_names[adbe->token], ++ atomic_read(&adbe->kicks), atomic_read(&adbe->completions), ++ in_flight, atomic_read(&adbe->applied), ++ atomic_read(&adbe->synced), atomic_read(&adbe->busy), ++ atomic_read(&adbe->vbl_fallbacks), atomic_read(&adbe->timeouts), ++ atomic_read(&adbe->latch_mismatch), ++ atomic_read(&adbe->frame_jumps), atomic_read(&adbe->errors)); ++ seq_printf(m, "flip-latency frames 0:%d 1:%d 2:%d 3+:%d; us avg=%llu max=%u (n=%u)\n", ++ atomic_read(&adbe->lat_frames[0]), atomic_read(&adbe->lat_frames[1]), ++ atomic_read(&adbe->lat_frames[2]), atomic_read(&adbe->lat_frames[3]), ++ count ? READ_ONCE(adbe->lat_us_sum) / count : 0, ++ READ_ONCE(adbe->lat_us_max), count); ++ seq_printf(m, "base=%#x stride=%#x boot-base=%#x bus-offset=%#llx inten=%#x saved-inten=%#x\n", ++ readl(adbe->adp + ADP_BASE), readl(adbe->adp + ADP_STRIDE), ++ adbe->boot_base, adbe->bus_offset, ++ readl(adbe->adp + ADP_INTEN), adbe->saved_inten); ++ seq_printf(m, "status=%#x frame=%u 0x101c=%#x 0x1040=%#x\n", ++ readl(adbe->adp + ADP_STATUS), ++ readl(adbe->adp + ADP_FRAME) & 0xffff, ++ readl(adbe->adp + ADP_PARMST), readl(adbe->adp + ADP_FIFO_PTR)); ++ adbe_show_memory(m, adbe); ++ return 0; ++} ++ ++DEFINE_DRM_GEM_DMA_FOPS(adbe_fops); ++ ++static const struct drm_driver adbe_drm_driver = { ++ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, ++ .fops = &adbe_fops, ++ .gem_create_object = adbe_gem_create_object, ++ .dumb_create = adbe_dumb_create, ++ .gem_prime_import_sg_table = adbe_gem_prime_import_sg_table, ++ DRM_FBDEV_DMA_DRIVER_OPS, ++ .name = "adbe", ++ .desc = "Apple A9 display pipe", ++ .major = 1, ++ .minor = 0, ++}; ++ ++static void adbe_put_node(void *np) ++{ ++ of_node_put(np); ++} ++ ++/* The simple-framebuffer iBoot set up: CPU address and geometry. */ ++static int adbe_boot_framebuffer(struct adbe *adbe) ++{ ++ struct device_node *np; ++ const char *format; ++ u32 width, height, stride; ++ int ret = -ENODEV; ++ ++ for_each_compatible_node(np, NULL, "simple-framebuffer") { ++ if (!of_device_is_available(np)) ++ continue; ++ ret = of_reserved_mem_region_to_resource(np, 0, &adbe->boot); ++ if (ret) ++ ret = of_address_to_resource(np, 0, &adbe->boot); ++ if (ret) ++ break; ++ ret = of_property_read_u32(np, "width", &width); ++ ret |= of_property_read_u32(np, "height", &height); ++ ret |= of_property_read_u32(np, "stride", &stride); ++ ret |= of_property_read_string(np, "format", &format); ++ if (ret || width != ADBE_WIDTH || height != ADBE_HEIGHT || ++ stride != ADBE_PITCH || strcmp(format, "x8r8g8b8") || ++ resource_size(&adbe->boot) < ADBE_PITCH * ADBE_HEIGHT) ++ ret = -EINVAL; ++ break; ++ } ++ if (!ret) ++ adbe->boot_np = of_node_get(np); ++ of_node_put(np); ++ if (ret) ++ return ret; ++ return devm_add_action_or_reset(adbe->drm.dev, adbe_put_node, adbe->boot_np); ++} ++ ++/* ++ * Retire the firmware framebuffer device: the aperture helpers unplug ++ * simpledrm if it is bound, and a device simpledrm has not bound yet (a ++ * built-in probe order, or a deferred probe) is unregistered so that it ++ * cannot take the boot framebuffer back later. ++ */ ++static int adbe_take_boot_fb(struct adbe *adbe) ++{ ++ struct platform_device *fb; ++ int ret; ++ ++ ret = aperture_remove_conflicting_devices(adbe->boot.start, ++ resource_size(&adbe->boot), ++ adbe_drm_driver.name); ++ if (ret) ++ return ret; ++ fb = of_find_device_by_node(adbe->boot_np); ++ if (fb) { ++ dev_info(adbe->drm.dev, "retiring %s before simpledrm binds it\n", ++ dev_name(&fb->dev)); ++ platform_device_unregister(fb); ++ put_device(&fb->dev); ++ } ++ return 0; ++} ++ ++/* Everything is read-only until the aperture is taken over. */ ++static int adbe_adopt(struct adbe *adbe) ++{ ++ struct device *dev = adbe->drm.dev; ++ struct adbe_dart dart = {}; ++ u32 base, stride; ++ u16 frame; ++ int ret; ++ ++ if ((readl(adbe->adp + ADP_SPDS) & 0xffff) != ADP_SPDS_VERSION || ++ !adbe_format_known(readl(adbe->adp + ADP_FORMAT)) || ++ !(readl(adbe->adp + ADP_VALID) & 1) || ++ (readl(adbe->adp + ADP_LAYERS) & 0xf) != 1 || ++ (readl(adbe->adp + ADP_STATUS) & ADP_ST_ERRORS)) ++ return dev_err_probe(dev, -ENODEV, "pipe is not running as iBoot leaves it\n"); ++ frame = readl(adbe->adp + ADP_FRAME); ++ msleep(50); ++ if ((u16)readl(adbe->adp + ADP_FRAME) == frame) ++ return dev_err_probe(dev, -ENODEV, "frame counter is not advancing\n"); ++ ret = adbe_boot_framebuffer(adbe); ++ if (ret) ++ return dev_err_probe(dev, ret, "need the live 1536x2048 simple-framebuffer\n"); ++ base = readl(adbe->adp + ADP_BASE); ++ stride = readl(adbe->adp + ADP_STRIDE); ++ if ((stride & ~(ADBE_ALIGN - 1)) != ADBE_PITCH || ++ (base & ~(ADBE_ALIGN - 1)) != lower_32_bits(adbe->boot.start)) ++ return dev_err_probe(dev, -ENODEV, ++ "plane %#x/%#x is not the boot framebuffer %pa\n", ++ base, stride, &adbe->boot.start); ++ adbe->boot_base = base; ++ adbe->boot_stride = stride; ++ adbe->cur_base = base; ++ adbe->bus_offset = adbe->boot.start - (base & ~(ADBE_ALIGN - 1)); ++ adbe->saved_inten = readl(adbe->adp + ADP_INTEN); ++ ++ ret = adbe_dart_read(dev, &dart); ++ if (ret) ++ return dev_err_probe(dev, ret, "cannot read the display DART\n"); ++ ret = adbe_dart_scanout_streams(&dart, base & ~(ADBE_ALIGN - 1), adbe->boot.start); ++ if (ret) ++ return dev_err_probe(dev, ret < 0 ? ret : -ENODEV, ++ "display DART %s; DMA buffers would not be reachable\n", ++ ret < 0 ? "state unknown" : "translates the scanout"); ++ dev_info(dev, "adopting %ux%u at %pa (plane %#x, stride %#x, bus offset %#llx, DART bypass, inten %#x)\n", ++ ADBE_WIDTH, ADBE_HEIGHT, &adbe->boot.start, base, stride, ++ adbe->bus_offset, adbe->saved_inten); ++ return 0; ++} ++ ++static int adbe_modeset_init(struct adbe *adbe) ++{ ++ struct drm_device *drm = &adbe->drm; ++ int ret; ++ ++ ret = drmm_mode_config_init(drm); ++ if (ret) ++ return ret; ++ drm->mode_config.min_width = 32; ++ drm->mode_config.min_height = 32; ++ drm->mode_config.max_width = 4096; ++ drm->mode_config.max_height = 4096; ++ drm->mode_config.preferred_depth = 24; ++ /* Write-combined buffers: CPU readback is slow, render elsewhere. */ ++ drm->mode_config.prefer_shadow = 1; ++ drm->mode_config.funcs = &adbe_mode_config_funcs; ++ drm->mode_config.helper_private = &adbe_mode_config_helpers; ++ ++ ret = drm_universal_plane_init(drm, &adbe->plane, BIT(0), &adbe_plane_funcs, ++ adbe_formats, ARRAY_SIZE(adbe_formats), ++ adbe_modifiers, DRM_PLANE_TYPE_PRIMARY, NULL); ++ if (ret) ++ return ret; ++ drm_plane_helper_add(&adbe->plane, &adbe_plane_helper_funcs); ++ ++ ret = drmm_crtc_init_with_planes(drm, &adbe->crtc, &adbe->plane, NULL, ++ &adbe_crtc_funcs, NULL); ++ if (ret) ++ return ret; ++ drm_crtc_helper_add(&adbe->crtc, &adbe_crtc_helper_funcs); ++ ++ ret = drmm_encoder_init(drm, &adbe->encoder, NULL, DRM_MODE_ENCODER_NONE, NULL); ++ if (ret) ++ return ret; ++ adbe->encoder.possible_crtcs = drm_crtc_mask(&adbe->crtc); ++ ++ ret = drmm_connector_init(drm, &adbe->connector, &adbe_connector_funcs, ++ DRM_MODE_CONNECTOR_eDP, NULL); ++ if (ret) ++ return ret; ++ drm_connector_helper_add(&adbe->connector, &adbe_connector_helper_funcs); ++ ret = drm_connector_attach_encoder(&adbe->connector, &adbe->encoder); ++ if (ret) ++ return ret; ++ ++ ret = drm_vblank_init(drm, 1); ++ if (ret) ++ return ret; ++ drm_mode_config_reset(drm); ++ return 0; ++} ++ ++static void adbe_teardown(struct adbe *adbe) ++{ ++ drm_atomic_helper_shutdown(&adbe->drm); ++ timer_delete_sync(&adbe->flip_timer); ++ /* Even if the CRTC was never enabled: boot buffer, iBoot's mask. */ ++ adbe_park(adbe); ++ writel(adbe->saved_inten, adbe->adp + ADP_INTEN); ++} ++ ++static int adbe_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct adbe *adbe; ++ int ret; ++ ++ if (!enable) { ++ dev_info(dev, "disabled by apple_adbe_drm.enable=0; simpledrm keeps the display\n"); ++ return -ENODEV; ++ } ++ adbe = devm_drm_dev_alloc(dev, &adbe_drm_driver, struct adbe, drm); ++ if (IS_ERR(adbe)) ++ return PTR_ERR(adbe); ++ platform_set_drvdata(pdev, adbe); ++ kernel_param_lock(THIS_MODULE); ++ ret = sysfs_match_string(adbe_commit_names, commit); ++ if (ret >= 0) { ++ adbe->commit = ret; ++ ret = sysfs_match_string(adbe_token_names, token); ++ adbe->token = ret; ++ } ++ kernel_param_unlock(THIS_MODULE); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "commit: base|full|sync, token: next|same|serial\n"); ++ timer_setup(&adbe->flip_timer, adbe_flip_timeout, 0); ++ INIT_LIST_HEAD(&adbe->bos); ++ ret = drmm_mutex_init(&adbe->drm, &adbe->bo_lock); ++ if (ret) ++ return ret; ++ ++ adbe->adp = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(adbe->adp)) ++ return PTR_ERR(adbe->adp); ++ ret = adbe_adopt(adbe); ++ if (ret) ++ return ret; ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); ++ if (ret) ++ return ret; ++ adbe->boot_va = devm_ioremap_wc(dev, adbe->boot.start, ADBE_PITCH * ADBE_HEIGHT); ++ if (!adbe->boot_va) ++ return -ENOMEM; ++ ret = adbe_modeset_init(adbe); ++ if (ret) ++ return ret; ++ adbe->irq = platform_get_irq(pdev, 0); ++ if (adbe->irq < 0) ++ return adbe->irq; ++ /* No packet of ours yet: a latched completion is not one to count. */ ++ writel(ADP_ST_DONE, adbe->adp + ADP_STATUS); ++ /* After vblank init; iBoot's mask (OutUnder only) until vblank is on. */ ++ ret = devm_request_irq(dev, adbe->irq, adbe_irq, 0, dev_name(dev), adbe); ++ if (ret) ++ return ret; ++ ++ /* Past this point simpledrm is gone; the pipe still shows its buffer. */ ++ ret = adbe_take_boot_fb(adbe); ++ if (ret) ++ return dev_err_probe(dev, ret, "cannot take over the boot framebuffer\n"); ++ ret = drm_dev_register(&adbe->drm, 0); ++ if (ret) { ++ dev_err(dev, "registration failed (%d); the panel keeps the last boot image\n", ++ ret); ++ return ret; ++ } ++ drm_debugfs_add_file(&adbe->drm, "adbe_state", adbe_state_show, NULL); ++ drm_debugfs_add_file(&adbe->drm, "adbe_bos", adbe_bos_show, NULL); ++ drm_client_setup(&adbe->drm, NULL); ++ return 0; ++} ++ ++static void adbe_remove(struct platform_device *pdev) ++{ ++ struct adbe *adbe = platform_get_drvdata(pdev); ++ ++ drm_dev_unplug(&adbe->drm); ++ adbe_teardown(adbe); ++} ++ ++static void adbe_shutdown(struct platform_device *pdev) ++{ ++ adbe_teardown(platform_get_drvdata(pdev)); ++} ++ ++static int adbe_suspend(struct device *dev) ++{ ++ struct adbe *adbe = dev_get_drvdata(dev); ++ ++ return drm_mode_config_helper_suspend(&adbe->drm); ++} ++ ++static int adbe_resume(struct device *dev) ++{ ++ struct adbe *adbe = dev_get_drvdata(dev); ++ ++ return drm_mode_config_helper_resume(&adbe->drm); ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(adbe_pm_ops, adbe_suspend, adbe_resume); ++ ++static const struct of_device_id adbe_of_match[] = { ++ { .compatible = "adbe0,s8000" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, adbe_of_match); ++ ++static struct platform_driver adbe_platform_driver = { ++ .probe = adbe_probe, ++ .remove = adbe_remove, ++ .shutdown = adbe_shutdown, ++ .driver = { ++ .name = "apple-adbe", ++ .of_match_table = adbe_of_match, ++ .pm = pm_sleep_ptr(&adbe_pm_ops), ++ }, ++}; ++module_platform_driver(adbe_platform_driver); ++ ++MODULE_DESCRIPTION("DRM driver for the Apple A9 display pipe"); ++MODULE_AUTHOR("Berkay Kösebay "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/gpu/drm/adbe/adbe_hw.h b/drivers/gpu/drm/adbe/adbe_hw.h +new file mode 100644 +index 0000000000000000000000000000000000000000..3cdefddd314269d90a04a245a826de8bcd233f12 +--- /dev/null ++++ b/drivers/gpu/drm/adbe/adbe_hw.h +@@ -0,0 +1,453 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Apple A9 (S8000/S8003) display pipe: register map, ADP parameter FIFO ++ * packet encoder and a read-only view of the display DART left by iBoot. ++ * ++ * Offsets are relative to the ADP window, DT reg[0] (0x206200000). The ADP ++ * control registers (0x1000..) and both pipe banks (0x4000, 0x6000) share it. ++ * Include this header from exactly one source file per module. ++ */ ++#ifndef ADBE_HW_H ++#define ADBE_HW_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "adbe_addr.h" ++ ++#define ADP_SPDS 0x1014 /* low 16 bits: SPDS version, 0x13 */ ++#define ADP_PARMST 0x101c /* [7:4] ParmSt: 2 blocks a kick */ ++#define ADP_PARMST_STATE GENMASK(7, 4) ++#define ADP_INTEN 0x1028 ++#define ADP_STATUS 0x102c /* W1C, reads are raw (unmasked) */ ++#define ADP_FRAME 0x1034 /* [15:0] frame counter */ ++#define ADP_VALID 0x1038 /* bit 0: pipe state usable */ ++#define ADP_FIFO_PTR 0x1040 /* [12:0] != 0: packet in flight */ ++#define ADP_FIFO_PTR_BUSY GENMASK(12, 0) ++#define ADP_PKT 0x10c0 /* packet push port, write-only */ ++ ++#define ADP_FORMAT 0x402c ++#define ADP_BASE 0x4030 ++#define ADP_STRIDE 0x4038 ++#define ADP_LAYERS 0x4068 ++#define ADP_SIZE 0x407c ++#define ADP_ADDR_TAG GENMASK(5, 0) /* low bits of base/stride words */ ++ ++#define ADP_ST_VBI BIT(0) ++#define ADP_ST_OUTUNDER BIT(1) ++/* ++ * Packet completion. On this pipe every parameter packet raises 0x400 at ++ * the frame boundary where it is applied: the FIFO holds it (ParmSt 3, ++ * 0x1040 = 1) until that boundary, then ParmSt returns to 1, 0x1040 to 0 ++ * and 0x400 sets together with VBI (round 2b phase C trace; 0x10 also ++ * sets). 0x400 is the primary completion. 0x8 is iOS's "packet synced" ++ * (iOS waits on 0x408); it never appeared here, with any token, and is ++ * only counted. ++ */ ++#define ADP_ST_SYNCED BIT(3) ++#define ADP_ST_APPLIED BIT(10) ++#define ADP_ST_DONE (ADP_ST_SYNCED | ADP_ST_APPLIED) ++#define ADP_ST_ERRORS 0xa42 /* OutUnder, PFAlign, PFOver, RdErr */ ++ ++#define ADP_SPDS_VERSION 0x13 ++#define ADP_FMT_FLAGS (BIT(0) | BIT(21) | BIT(22) | GENMASK(27, 24)) ++ ++/* Words after the preamble; a full layer set needs 32. */ ++#define ADP_PKT_MAX_WORDS 40 ++ ++/* ++ * Pipe 0, layer 0 register set in the order iOS queues it (study §12.4, ++ * §13.3): format, stride, base, rects, scales, size, position, layer ++ * enable (last of the layer group), destination window, background. ++ */ ++static const u16 adbe_layer_regs[] = { ++ 0x402c, 0x4038, 0x4030, 0x4040, 0x4048, 0x4094, 0x40a4, 0x407c, ++ 0x406c, 0x4068, 0x4050, 0x4054, 0x4058, 0x405c, 0x4060, 0x4064, ++}; ++ ++/* How a plane update reaches the pipe; selected by module parameters. */ ++enum adbe_commit { ++ ADBE_COMMIT_BASE, /* packet: [0x4038] + 0x4030 */ ++ ADBE_COMMIT_FULL, /* packet: the whole adbe_layer_regs set */ ++ ADBE_COMMIT_SYNC, /* direct stores + empty armed packet */ ++}; ++ ++static const char * const adbe_commit_names[] = { ++ [ADBE_COMMIT_BASE] = "base", ++ [ADBE_COMMIT_FULL] = "full", ++ [ADBE_COMMIT_SYNC] = "sync", ++}; ++ ++/* ++ * The preamble token becomes the frame counter. "next" keeps the counter ++ * continuous (the packet lands on the next frame); "serial" is round 2's ++ * 1, 2, 3... which reset it. ++ */ ++enum adbe_token { ++ ADBE_TOKEN_NEXT, ++ ADBE_TOKEN_SAME, ++ ADBE_TOKEN_SERIAL, ++}; ++ ++static const char * const adbe_token_names[] = { ++ [ADBE_TOKEN_NEXT] = "next", ++ [ADBE_TOKEN_SAME] = "same", ++ [ADBE_TOKEN_SERIAL] = "serial", ++}; ++ ++struct adbe_pkt { ++ u32 word[ADP_PKT_MAX_WORDS]; ++ unsigned int count; /* words after the preamble */ ++}; ++ ++static inline bool adbe_format_known(u32 format) ++{ ++ format &= ~ADP_FMT_FLAGS; ++ return format == 0xc6000 || format == 0xe4000; ++} ++ ++static inline int adbe_pkt_add(struct adbe_pkt *pkt, u32 reg, u32 value) ++{ ++ if (pkt->count + 2 > ADP_PKT_MAX_WORDS) ++ return -ENOSPC; ++ pkt->word[pkt->count++] = adbe_pkt_header(reg); ++ pkt->word[pkt->count++] = value; ++ return 0; ++} ++ ++/* ++ * Build a packet that changes only base and, if different, stride. For the ++ * full variant every other layer register is re-sent with its live value. ++ */ ++static inline int adbe_pkt_build(struct adbe_pkt *pkt, void __iomem *adp, ++ enum adbe_commit commit, u32 base, u32 stride) ++{ ++ unsigned int i; ++ int ret = 0; ++ ++ pkt->count = 0; ++ if (commit == ADBE_COMMIT_SYNC) ++ return 0; ++ if (commit == ADBE_COMMIT_BASE) { ++ if (readl(adp + ADP_STRIDE) != stride) ++ ret = adbe_pkt_add(pkt, ADP_STRIDE, stride); ++ return ret ?: adbe_pkt_add(pkt, ADP_BASE, base); ++ } ++ for (i = 0; i < ARRAY_SIZE(adbe_layer_regs) && !ret; i++) { ++ u32 reg = adbe_layer_regs[i]; ++ u32 value = readl(adp + reg); ++ ++ if (reg == ADP_BASE) ++ value = base; ++ else if (reg == ADP_STRIDE) ++ value = stride; ++ ret = adbe_pkt_add(pkt, reg, value); ++ } ++ return ret; ++} ++ ++/* readCurrentParamFIFOState(): 1 ready, 0 in flight, -EIO undefined. */ ++static inline int adbe_fifo_state(void __iomem *adp, u32 *parmst, u32 *ptr) ++{ ++ *ptr = readl(adp + ADP_FIFO_PTR); ++ *parmst = readl(adp + ADP_PARMST); ++ if (*ptr & ADP_FIFO_PTR_BUSY) ++ return 0; ++ if (FIELD_GET(ADP_PARMST_STATE, *parmst) > 5) ++ return -EIO; ++ return FIELD_GET(ADP_PARMST_STATE, *parmst) != 2; ++} ++ ++/* Bounded, sleeping wait for the pre-kick gate. Never spins forever. */ ++static inline int adbe_fifo_wait_ready(void __iomem *adp, unsigned int ms, ++ u32 *parmst, u32 *ptr) ++{ ++ int state; ++ ++ for (;;) { ++ state = adbe_fifo_state(adp, parmst, ptr); ++ if (state) ++ return state > 0 ? 0 : state; ++ if (!ms--) ++ return -EBUSY; ++ usleep_range(900, 1100); ++ } ++} ++ ++/* ++ * Push one packet: preamble (armed, word count, token), then the words. ++ * The port has no per-word handshake, so keep the burst together, and ++ * read the frame counter for the token right next to it. For ++ * ADBE_COMMIT_SYNC the caller has already stored the registers and this ++ * pushes only the empty preamble, as the upstream ADP driver does for the ++ * related H7 pipe. Returns the token used. ++ */ ++static inline u16 adbe_pkt_push(void __iomem *adp, const struct adbe_pkt *pkt, ++ enum adbe_token policy, u16 serial, bool armed) ++{ ++ unsigned long flags; ++ unsigned int i; ++ u16 token; ++ ++ local_irq_save(flags); ++ token = serial; ++ if (policy != ADBE_TOKEN_SERIAL) ++ token = readl(adp + ADP_FRAME) + (policy == ADBE_TOKEN_NEXT); ++ writel(adbe_pkt_preamble(pkt->count, token, armed), adp + ADP_PKT); ++ for (i = 0; i < pkt->count; i++) ++ writel(pkt->word[i], adp + ADP_PKT); ++ local_irq_restore(flags); ++ return token; ++} ++ ++/* The counter moved by other than one frame: reloaded or skipped. */ ++static inline bool adbe_frame_jump(u16 last, u16 now) ++{ ++ return now != last && now != (u16)(last + 1); ++} ++ ++/* ++ * Display DART (dart-disp0, "dart,s5l8960x" family), read-only. ++ * ++ * Linux has no node for it: attaching apple-dart would reset the streams ++ * under the live scanout. The two ADT windows are read here only to learn ++ * whether the pipe's bus addresses are translated. TCR at 0xc (one byte per ++ * stream, bit 7 = translate), TTBRs at 0x40 (valid bit 31, PA >> 12). ++ * Page tables: 4 KiB granule, TTBR index = bus[31:30], L1 = bus[29:21], ++ * L2 = bus[20:12], PTE valid bit 0, address bits [35:12]. ++ */ ++#define ADBE_DART_WINDOWS 2 ++#define ADBE_DART_STREAMS 4 ++#define ADBE_DART_TTBRS 4 ++#define ADBE_DART_SIZE 0x4000 ++#define DART_TCR 0x0c ++#define DART_TCR_TRANSLATE BIT(7) ++#define DART_ERROR 0x10 ++#define DART_TTBR(sid, idx) (0x40 + 4 * ((sid) * ADBE_DART_TTBRS + (idx))) ++#define DART_TTBR_VALID BIT(31) ++#define DART_TTBR_ADDR GENMASK(23, 0) ++#define DART_PTE_VALID BIT_ULL(0) ++#define DART_PTE_ADDR GENMASK_ULL(35, 12) ++ ++/* ADT dart-disp0 reg order: 0x206304000, then 0x206300000. */ ++static const phys_addr_t adbe_dart_phys[ADBE_DART_WINDOWS] = { ++ 0x206304000, 0x206300000, ++}; ++ ++struct adbe_dart { ++ u32 tcr[ADBE_DART_WINDOWS]; ++ u32 error[ADBE_DART_WINDOWS]; ++ u32 ttbr[ADBE_DART_WINDOWS][ADBE_DART_STREAMS][ADBE_DART_TTBRS]; ++ bool read; ++}; ++ ++static inline bool adbe_dart_translates(const struct adbe_dart *dart, ++ unsigned int win, unsigned int sid) ++{ ++ return (dart->tcr[win] >> (8 * sid)) & DART_TCR_TRANSLATE; ++} ++ ++/* Snapshot TCR, error and TTBR words of both windows; reads only. */ ++static inline int adbe_dart_read(struct device *dev, struct adbe_dart *dart) ++{ ++ unsigned int win, sid, idx; ++ ++ for (win = 0; win < ADBE_DART_WINDOWS; win++) { ++ void __iomem *regs = ioremap_np(adbe_dart_phys[win], ADBE_DART_SIZE); ++ ++ if (!regs) ++ return -ENOMEM; ++ dart->tcr[win] = readl(regs + DART_TCR); ++ dart->error[win] = readl(regs + DART_ERROR); ++ for (sid = 0; sid < ADBE_DART_STREAMS; sid++) ++ for (idx = 0; idx < ADBE_DART_TTBRS; idx++) ++ dart->ttbr[win][sid][idx] = ++ readl(regs + DART_TTBR(sid, idx)); ++ iounmap(regs); ++ dev_info(dev, "DART win%u %pa: tcr=%#010x error=%#x\n", win, ++ &adbe_dart_phys[win], dart->tcr[win], dart->error[win]); ++ for (sid = 0; sid < ADBE_DART_STREAMS; sid++) ++ dev_info(dev, "DART win%u sid%u translate=%u ttbr=%#x %#x %#x %#x\n", ++ win, sid, adbe_dart_translates(dart, win, sid), ++ dart->ttbr[win][sid][0], dart->ttbr[win][sid][1], ++ dart->ttbr[win][sid][2], dart->ttbr[win][sid][3]); ++ } ++ dart->read = true; ++ return 0; ++} ++ ++/* Table pages are only read if they are inside a /memory range. */ ++static inline bool adbe_pa_is_dram(phys_addr_t pa) ++{ ++ struct device_node *np; ++ struct resource res; ++ bool found = false; ++ int i; ++ ++ for_each_node_by_type(np, "memory") { ++ for (i = 0; !found && !of_address_to_resource(np, i, &res); i++) ++ found = pa >= res.start && pa + SZ_4K - 1 <= res.end; ++ if (found) { ++ of_node_put(np); ++ break; ++ } ++ } ++ return found; ++} ++ ++static inline int adbe_dart_pte(phys_addr_t table, unsigned int index, u64 *pte) ++{ ++ u64 *page; ++ ++ if (!adbe_pa_is_dram(table)) ++ return -EFAULT; ++ page = memremap(table, SZ_4K, MEMREMAP_WB); ++ if (!page) ++ return -ENOMEM; ++ *pte = READ_ONCE(page[index]); ++ memunmap(page); ++ return 0; ++} ++ ++/* Translate one bus page through one stream; -ENOENT if unmapped. */ ++static inline int adbe_dart_lookup(const struct adbe_dart *dart, ++ unsigned int win, unsigned int sid, ++ u32 bus, phys_addr_t *pa) ++{ ++ u32 ttbr = dart->ttbr[win][sid][bus >> 30]; ++ u64 pte; ++ int ret; ++ ++ if (!(ttbr & DART_TTBR_VALID)) ++ return -ENOENT; ++ ret = adbe_dart_pte((phys_addr_t)FIELD_GET(DART_TTBR_ADDR, ttbr) << 12, ++ (bus >> 21) & 0x1ff, &pte); ++ if (ret || !(pte & DART_PTE_VALID)) ++ return ret ?: -ENOENT; ++ ret = adbe_dart_pte(pte & DART_PTE_ADDR, (bus >> 12) & 0x1ff, &pte); ++ if (ret || !(pte & DART_PTE_VALID)) ++ return ret ?: -ENOENT; ++ *pa = (pte & DART_PTE_ADDR) | (bus & 0xfff); ++ return 0; ++} ++ ++/* ++ * Bytes from @bus (page aligned) that one stream maps linearly onto @pa. ++ * Stops at the first gap; bounded by @len. ++ */ ++static inline u64 adbe_dart_linear(const struct adbe_dart *dart, ++ unsigned int win, unsigned int sid, ++ u32 bus, phys_addr_t pa, u64 len) ++{ ++ u64 done; ++ ++ for (done = 0; done < len; done += SZ_4K) { ++ phys_addr_t got; ++ ++ if ((u64)bus + done > U32_MAX || ++ adbe_dart_lookup(dart, win, sid, bus + done, &got) || ++ got != pa + done) ++ break; ++ } ++ return min(done, len); ++} ++ ++/* ++ * 1 if this stream translates and maps the live scanout page, 0 if it does ++ * not translate or leaves that page unmapped/elsewhere (so it cannot be the ++ * stream the pipe reads through), <0 if its page tables could not be read. ++ */ ++static inline int adbe_dart_is_scanout(const struct adbe_dart *dart, ++ unsigned int win, unsigned int sid, ++ u32 live_bus, phys_addr_t live_pa) ++{ ++ phys_addr_t got; ++ int ret; ++ ++ if (!adbe_dart_translates(dart, win, sid)) ++ return 0; ++ ret = adbe_dart_lookup(dart, win, sid, live_bus & ~0xfffU, &got); ++ if (ret) ++ return ret == -ENOENT ? 0 : ret; ++ return got == (live_pa & ~(phys_addr_t)0xfff); ++} ++ ++/* Translating streams that carry the live scanout (0: bypass), or <0. */ ++static inline int adbe_dart_scanout_streams(const struct adbe_dart *dart, ++ u32 live_bus, phys_addr_t live_pa) ++{ ++ unsigned int win, sid; ++ int ret, streams = 0; ++ ++ if (!dart->read) ++ return -EIO; ++ for (win = 0; win < ADBE_DART_WINDOWS; win++) { ++ for (sid = 0; sid < ADBE_DART_STREAMS; sid++) { ++ ret = adbe_dart_is_scanout(dart, win, sid, live_bus, live_pa); ++ if (ret < 0) ++ return ret; ++ streams += ret; ++ } ++ } ++ return streams; ++} ++ ++/* ++ * Whether the pipe can read [@bus, @bus + @len) as [@pa, ...). The live ++ * scanout (@live_bus -> @live_pa) is known good, so a translating stream ++ * that does not map it is not the scanout stream and is ignored. If no ++ * translating stream maps it, the pipe reads through bypass, which is ++ * linear; the caller derived the offset from the same live scanout. ++ * Returns the verified length (0..@len); <0 if the DART was not read or a ++ * page table was unreadable (unknown is never treated as bypass). ++ */ ++static inline s64 adbe_dart_reachable(struct device *dev, ++ const struct adbe_dart *dart, ++ u32 live_bus, phys_addr_t live_pa, ++ u32 bus, phys_addr_t pa, u64 len) ++{ ++ unsigned int win, sid, streams = 0; ++ u64 covered = len; ++ ++ if (!dart->read) ++ return -EIO; ++ for (win = 0; win < ADBE_DART_WINDOWS; win++) { ++ for (sid = 0; sid < ADBE_DART_STREAMS; sid++) { ++ int scanout = adbe_dart_is_scanout(dart, win, sid, ++ live_bus, live_pa); ++ u64 linear; ++ ++ if (scanout < 0) { ++ dev_warn(dev, "DART win%u sid%u: page table unreadable (%d)\n", ++ win, sid, scanout); ++ return scanout; ++ } ++ if (!scanout) { ++ if (adbe_dart_translates(dart, win, sid)) ++ dev_info(dev, "DART win%u sid%u translates but not the live scanout; ignored\n", ++ win, sid); ++ continue; ++ } ++ streams++; ++ linear = adbe_dart_linear(dart, win, sid, bus, pa, len); ++ dev_info(dev, "DART win%u sid%u maps the live scanout; %#llx of %#llx bytes at bus %#x linear\n", ++ win, sid, linear, len, bus); ++ covered = min(covered, linear); ++ } ++ } ++ if (!streams) ++ dev_info(dev, "DART: no translating stream maps the scanout; bypass, bus %#x linear\n", ++ bus); ++ return covered; ++} ++ ++#endif /* ADBE_HW_H */ diff --git a/kernel/patches/j71t/0010-j71t-video-decoder.patch b/kernel/patches/j71t/0010-j71t-video-decoder.patch new file mode 100644 index 00000000..c7d36b73 --- /dev/null +++ b/kernel/patches/j71t/0010-j71t-video-decoder.patch @@ -0,0 +1,9089 @@ +From b4d792c0fc3866629b253ace728571314d9c7ec3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 10/12] media: apple: add the VXD stateless H.264 decoder +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +V4L2 stateless H.264 decoding on the A9 VXD (PowerVR MSVDX) core. +The DEVA firmware is not included; it is loaded from +/lib/firmware/apple/vxd-deva.bin, and only when booted through +apple_vxd.boot_firmware=1 or debugfs. The CAVLC table is generated +from the GPL-2.0 Imagination Technologies/Texas Instruments source +in upstream/h264_vlc.h, with its notices kept. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../bindings/media/apple,s8000-vxd.yaml | 60 + + drivers/media/platform/Kconfig | 1 + + drivers/media/platform/Makefile | 1 + + drivers/media/platform/apple/Kconfig | 5 + + drivers/media/platform/apple/Makefile | 4 + + drivers/media/platform/apple/vxd/Kconfig | 21 + + drivers/media/platform/apple/vxd/Makefile | 7 + + .../media/platform/apple/vxd/apple-vxd-cmd.c | 393 +++++ + .../media/platform/apple/vxd/apple-vxd-cmd.h | 86 + + .../platform/apple/vxd/apple-vxd-cmdbuf.c | 487 ++++++ + .../media/platform/apple/vxd/apple-vxd-core.c | 598 +++++++ + .../media/platform/apple/vxd/apple-vxd-fw.c | 531 +++++++ + .../media/platform/apple/vxd/apple-vxd-gart.c | 283 ++++ + .../platform/apple/vxd/apple-vxd-h264-cmd.c | 623 ++++++++ + .../platform/apple/vxd/apple-vxd-h264-cmd.h | 83 + + .../platform/apple/vxd/apple-vxd-h264-parse.c | 544 +++++++ + .../platform/apple/vxd/apple-vxd-h264-parse.h | 100 ++ + .../apple/vxd/apple-vxd-h264-tables.h | 94 ++ + .../media/platform/apple/vxd/apple-vxd-h264.c | 582 +++++++ + .../media/platform/apple/vxd/apple-vxd-mbox.c | 1406 +++++++++++++++++ + .../media/platform/apple/vxd/apple-vxd-regs.h | 280 ++++ + .../media/platform/apple/vxd/apple-vxd-v4l2.c | 1405 ++++++++++++++++ + drivers/media/platform/apple/vxd/apple-vxd.h | 528 +++++++ + .../media/platform/apple/vxd/h264-tables.py | 132 ++ + .../platform/apple/vxd/upstream/h264_vlc.h | 604 +++++++ + 25 files changed, 8858 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/apple,s8000-vxd.yaml + create mode 100644 drivers/media/platform/apple/Kconfig + create mode 100644 drivers/media/platform/apple/Makefile + create mode 100644 drivers/media/platform/apple/vxd/Kconfig + create mode 100644 drivers/media/platform/apple/vxd/Makefile + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-cmd.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-cmd.h + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-cmdbuf.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-core.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-fw.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-gart.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.h + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264-parse.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264-parse.h + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264-tables.h + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-h264.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-mbox.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-regs.h + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd-v4l2.c + create mode 100644 drivers/media/platform/apple/vxd/apple-vxd.h + create mode 100644 drivers/media/platform/apple/vxd/h264-tables.py + create mode 100644 drivers/media/platform/apple/vxd/upstream/h264_vlc.h + +diff --git a/Documentation/devicetree/bindings/media/apple,s8000-vxd.yaml b/Documentation/devicetree/bindings/media/apple,s8000-vxd.yaml +new file mode 100644 +index 0000000000000000000000000000000000000000..fc003e246627022f98f6782f888933acf9ea7d91 +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/apple,s8000-vxd.yaml +@@ -0,0 +1,60 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/apple,s8000-vxd.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Apple VXD hardware video decoder (Imagination PowerVR MSVDX) ++ ++maintainers: ++ - Berkay Kosebay ++ ++description: | ++ The Apple "VXD" peripheral is a multi-standard hardware video decoder, ++ an Imagination PowerVR MSVDX core running the resident DEVA firmware on ++ its MTX (META) microcontroller. Apple has shipped this IP family since ++ the S5L8940X (A4); the S8000/S8003 (A9) in the iPad 5 reports ++ vxd-version 7. The block has no IOMMU (DART): it carries its own ++ two-level "GART" translation unit, programmed by the host through the ++ MTX MMU registers. ++ ++ Linux exposes it as a V4L2 stateless H.264 decoder. The DEVA firmware ++ image must be supplied as /lib/firmware/apple/vxd-deva.bin. ++ ++properties: ++ compatible: ++ items: ++ - const: apple,s8000-vxd ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ power-domains: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - power-domains ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ vxd@207f00000 { ++ compatible = "apple,s8000-vxd"; ++ reg = <0x2 0x07f00000 0x0 0x100000>; ++ interrupts = ; ++ power-domains = <&ps_vdec>; ++ }; ++ }; +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index 2c7699b6610b0fa0da5137dcee5164c8519739ee..280a9db25935e0a211c9160ed511a5e1c443ba47 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -66,6 +66,7 @@ source "drivers/media/platform/allegro-dvt/Kconfig" + source "drivers/media/platform/amd/Kconfig" + source "drivers/media/platform/amlogic/Kconfig" + source "drivers/media/platform/amphion/Kconfig" ++source "drivers/media/platform/apple/Kconfig" + source "drivers/media/platform/arm/Kconfig" + source "drivers/media/platform/aspeed/Kconfig" + source "drivers/media/platform/atmel/Kconfig" +diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile +index d47c47d817da2bacafa3a7aa4a6e1cb07fa35699..aa82e189936b541e026c04b5d3d3a9dba68b4236 100644 +--- a/drivers/media/platform/Makefile ++++ b/drivers/media/platform/Makefile +@@ -9,6 +9,7 @@ obj-y += allegro-dvt/ + obj-y += amd/ + obj-y += amlogic/ + obj-y += amphion/ ++obj-y += apple/ + obj-y += arm/ + obj-y += aspeed/ + obj-y += atmel/ +diff --git a/drivers/media/platform/apple/Kconfig b/drivers/media/platform/apple/Kconfig +new file mode 100644 +index 0000000000000000000000000000000000000000..5415ff3d04ab6f8cc3ae2b2d48c477fb2665de4b +--- /dev/null ++++ b/drivers/media/platform/apple/Kconfig +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++comment "Apple media platform drivers" ++ ++source "drivers/media/platform/apple/vxd/Kconfig" +diff --git a/drivers/media/platform/apple/Makefile b/drivers/media/platform/apple/Makefile +new file mode 100644 +index 0000000000000000000000000000000000000000..e8b14cc81a3070bc192e7faf8c81e2e5b9612204 +--- /dev/null ++++ b/drivers/media/platform/apple/Makefile +@@ -0,0 +1,4 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# Place here, alphabetically sorted by directory ++# (e. g. LC_ALL=C sort Makefile) ++obj-y += vxd/ +diff --git a/drivers/media/platform/apple/vxd/Kconfig b/drivers/media/platform/apple/vxd/Kconfig +new file mode 100644 +index 0000000000000000000000000000000000000000..5c68e3df446dac184d09a191c137cbd6770694a8 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/Kconfig +@@ -0,0 +1,21 @@ ++# SPDX-License-Identifier: GPL-2.0 ++config VIDEO_APPLE_VXD ++ tristate "Apple VXD (PowerVR MSVDX) video decoder driver" ++ depends on VIDEO_DEV ++ depends on ARCH_APPLE || COMPILE_TEST ++ select MEDIA_CONTROLLER ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_MEM2MEM_DEV ++ select V4L2_H264 ++ select CRC32 ++ help ++ Support for the Apple "VXD" video decoder present on pre-M1 Apple ++ SoCs starting with the S5L8940X: an Imagination PowerVR MSVDX core ++ driven by the resident DEVA (MTX) firmware and host-side parsed ++ parameters. On the S8000/S8003 (A9) it exposes a V4L2 stateless ++ H.264 decoder device. ++ The DEVA firmware image is not included; stage it as ++ /lib/firmware/apple/vxd-deva.bin, carved from the AppleD5500 ++ kext in the iPadOS kernelcache. ++ To compile this driver as a module, choose M here: the module ++ will be called apple-vxd. +diff --git a/drivers/media/platform/apple/vxd/Makefile b/drivers/media/platform/apple/vxd/Makefile +new file mode 100644 +index 0000000000000000000000000000000000000000..b7329d1b4c6408c79e9109f2d315fb923c3fe68e +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++apple-vxd-objs := apple-vxd-core.o apple-vxd-gart.o apple-vxd-fw.o \ ++ apple-vxd-mbox.o apple-vxd-cmd.o apple-vxd-cmdbuf.o \ ++ apple-vxd-v4l2.o apple-vxd-h264.o apple-vxd-h264-parse.o \ ++ apple-vxd-h264-cmd.o ++ ++obj-$(CONFIG_VIDEO_APPLE_VXD) += apple-vxd.o +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-cmd.c b/drivers/media/platform/apple/vxd/apple-vxd-cmd.c +new file mode 100644 +index 0000000000000000000000000000000000000000..9ace49457e7f6fa9e66174dbbcf3b6a693be5d20 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-cmd.c +@@ -0,0 +1,393 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - command-stream encoders (R3b, split out for R3c) ++ * ++ * The H.264 command stream is an MSVDX register program ++ * (docs/periph/ds-vxd-cmdstream.md): per slice one chunk of command words ++ * in a GART-mapped buffer, padded to 64 bytes, plus one 0x78-byte packet ++ * describing that chunk; per picture one more chunk of single-register ++ * writes (the kind-4 / mailbox 0x88 packet). ++ * ++ * What the kext does, byte for byte [E]: ++ * ++ * CH264Decoder::HOST_H264DecodeFrame, per slice (0xfffffff0062967cc): ++ * bytes = wptr - base - chunk_start (payload +0x0c) ++ * zero-fill 64 - (wptr & 63) bytes, advance by that & 0x7c ++ * packet = addCommand(pktlist, 0x80000007, 0x78) <- a second object ++ * payload +0x10 = chunk_start + cmd->[0x18] ++ * after the slice loop, 0x62b7ab0: one chunk of 0x000f0000 pairs, padded ++ * by 0x40 - (pairs & 7) * 8 bytes, payload +0x0c counting the pad ++ * CmdBuf::addCommand (0xfffffff0062982f8): {magic, size, 0xabcdefab} at ++ * the list's own offset, gate offset + size + 0xc < capacity ++ * SubmitCommandBufferInternal (0xfffffff00628a8d4): walk the packet list ++ * from its base, check both magics, one mailbox message per packet ++ * ++ * The packet list and the register program are therefore two buffers: ++ * the walker starts at the list's base and would reject the first command ++ * word (0x90000000) otherwise. Only the register program is device ++ * memory. ++ * ++ * Register runs (0xfffffff00629b068) are written through a scoped writer ++ * that asserts exclusive use of the buffer: consecutive registers merge ++ * into one 0x1 header (count += 1) only inside one group, so groups are ++ * explicit here (apple_vxd_cmd_group_open/close) and no other command may ++ * be emitted while one is open. ++ * ++ * Every emitter is bounds-checked; a failure sets a sticky error that the ++ * caller checks once at the end of a chunk. Nothing here touches MMIO, ++ * the DMA API or a lock, so the file builds unchanged on the host. ++ */ ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd-cmd.h" ++ ++void apple_vxd_cmdbuf_reset(struct apple_vxd_cmdbuf *cb) ++{ ++ cb->used = 0; ++ cb->chunk_start = 0; ++ cb->group_open = false; ++ cb->group_flags = 0; ++ cb->run_hdr = SIZE_MAX; ++ cb->run_next_reg = 0; ++ cb->npairs = 0; ++ cb->error = false; ++ cb->pkt_used = 0; ++ cb->npackets = 0; ++} ++ ++static bool vxd_cmd_room(struct apple_vxd_cmdbuf *cb, size_t bytes) ++{ ++ if (cb->error) ++ return false; ++ if (bytes > cb->size - cb->used) { ++ cb->error = true; ++ return false; ++ } ++ return true; ++} ++ ++static void vxd_cmd_put(struct apple_vxd_cmdbuf *cb, u32 word) ++{ ++ cb->vaddr[cb->used / 4] = word; ++ cb->used += 4; ++} ++ ++/* Every non-0x1 command: no open register group, room for @words. */ ++static bool vxd_cmd_begin(struct apple_vxd_cmdbuf *cb, size_t words) ++{ ++ if (cb->group_open) { ++ cb->error = true; ++ return false; ++ } ++ return vxd_cmd_room(cb, words * 4); ++} ++ ++void apple_vxd_cmd_word(struct apple_vxd_cmdbuf *cb, u32 word) ++{ ++ if (vxd_cmd_begin(cb, 1)) ++ vxd_cmd_put(cb, word); ++} ++ ++/* Open a 0x1 register group; @flags is OR-ed into every run header. */ ++void apple_vxd_cmd_group_open(struct apple_vxd_cmdbuf *cb, u32 flags) ++{ ++ if (cb->group_open) { ++ cb->error = true; ++ return; ++ } ++ cb->group_open = true; ++ cb->group_flags = flags; ++ cb->run_hdr = SIZE_MAX; ++} ++ ++/* ++ * 0x1 form: 0x10010000 | flags | reg, value; a write to the register right ++ * after the previous one extends that run instead (0xfffffff00629b068). ++ */ ++void apple_vxd_cmd_reg(struct apple_vxd_cmdbuf *cb, u32 reg, u32 val) ++{ ++ u32 *hdr; ++ ++ if (!cb->group_open) { ++ cb->error = true; ++ return; ++ } ++ ++ hdr = cb->run_hdr != SIZE_MAX ? &cb->vaddr[cb->run_hdr / 4] : NULL; ++ if (hdr && cb->run_next_reg == reg && ++ FIELD_GET(VXD_CMD_REG_RUN_COUNT, *hdr) < FIELD_MAX(VXD_CMD_REG_RUN_COUNT)) { ++ if (!vxd_cmd_room(cb, 4)) ++ return; ++ *hdr += VXD_CMD_REG_RUN_ONE; ++ } else { ++ if (!vxd_cmd_room(cb, 8)) ++ return; ++ cb->run_hdr = cb->used; ++ vxd_cmd_put(cb, (reg & VXD_CMD_REG_ADDR) | cb->group_flags | ++ VXD_CMD_REG_RUN | VXD_CMD_REG_RUN_ONE); ++ } ++ vxd_cmd_put(cb, val); ++ cb->run_next_reg = reg + 4; ++} ++ ++void apple_vxd_cmd_group_close(struct apple_vxd_cmdbuf *cb) ++{ ++ if (!cb->group_open) ++ cb->error = true; ++ cb->group_open = false; ++ cb->run_hdr = SIZE_MAX; ++} ++ ++/* 0x000f0000 form: one register, (flags & 0xf0) << 16 (0xfffffff00629aa5c). */ ++void apple_vxd_cmd_reg1(struct apple_vxd_cmdbuf *cb, u32 reg, u32 val, ++ u32 flags) ++{ ++ if (!vxd_cmd_begin(cb, 2)) ++ return; ++ vxd_cmd_put(cb, ((flags & VXD_CMD_REG1_FLAGS) << 16) | ++ (reg & VXD_CMD_REG_ADDR) | VXD_CMD_REG1); ++ vxd_cmd_put(cb, val); ++ cb->npairs++; ++} ++ ++/* 0x9: the 0x28-byte per-slice block, emitted all zero by Apple. */ ++void apple_vxd_cmd_slice_block(struct apple_vxd_cmdbuf *cb) ++{ ++ unsigned int i; ++ ++ if (!vxd_cmd_begin(cb, VXD_CMD_SLICE_BLOCK_WORDS)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_SLICE_BLOCK); ++ for (i = 1; i < VXD_CMD_SLICE_BLOCK_WORDS; i++) ++ vxd_cmd_put(cb, 0); ++} ++ ++/* ++ * 0x9 with its payload: the AVC path fills the ten-word CTRL_ALLOC_HEADER ++ * (0xfffffff0062c4c98, ds-vxd-cmdstream.md section 16.4). ++ */ ++void apple_vxd_cmd_ctrl_alloc(struct apple_vxd_cmdbuf *cb, ++ const u32 words[VXD_CMD_SLICE_BLOCK_WORDS - 1]) ++{ ++ unsigned int i; ++ ++ if (!vxd_cmd_begin(cb, VXD_CMD_SLICE_BLOCK_WORDS)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_SLICE_BLOCK); ++ for (i = 0; i < VXD_CMD_SLICE_BLOCK_WORDS - 1; i++) ++ vxd_cmd_put(cb, words[i]); ++} ++ ++/* ++ * 0x5: one block of @n consecutive registers from @reg, never merged with ++ * a neighbour (0xfffffff00629a568 / 0x629a6c0 / 0x629a81c: the header ++ * count is patched when the block is closed, boundaries are kept). ++ */ ++void apple_vxd_cmd_block(struct apple_vxd_cmdbuf *cb, u32 reg, u32 flags, ++ const u32 *vals, unsigned int n) ++{ ++ unsigned int i; ++ ++ if (!n || n > FIELD_MAX(VXD_CMD_BLOCK_COUNT) || ++ (flags & ~VXD_CMD_BLOCK_FLAG_REFS)) { ++ cb->error = true; ++ return; ++ } ++ if (!vxd_cmd_begin(cb, 1 + n)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_BLOCK | flags | ++ FIELD_PREP(VXD_CMD_BLOCK_COUNT, n) | (reg & VXD_CMD_REG_ADDR)); ++ for (i = 0; i < n; i++) ++ vxd_cmd_put(cb, vals[i]); ++} ++ ++/* 0xe: DMA @bytes from @va into the core, @type selects the target. */ ++void apple_vxd_cmd_dma(struct apple_vxd_cmdbuf *cb, u32 type, u32 bytes, ++ u32 va) ++{ ++ if (bytes & ~VXD_CMD_DMA_BYTES) { ++ cb->error = true; ++ return; ++ } ++ if (!vxd_cmd_begin(cb, 2)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_DMA | type | bytes); ++ vxd_cmd_put(cb, va); ++} ++ ++/* 0xb: {0xb0000001, header bits consumed, slice bytes} (0x62b5b00). */ ++void apple_vxd_cmd_slice_desc(struct apple_vxd_cmdbuf *cb, u32 w1, u32 w2) ++{ ++ if (!vxd_cmd_begin(cb, 3)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_SLICE_DESC); ++ vxd_cmd_put(cb, w1); ++ vxd_cmd_put(cb, w2); ++} ++ ++/* ++ * 0xa: load @bytes from device address @va (0xfffffff00629a444, asserts ++ * bytes < 1 << 28). This is how the slice bitstream reaches the core: ++ * @va is the GART VA of the OUTPUT buffer plus the slice offset. ++ */ ++void apple_vxd_cmd_load(struct apple_vxd_cmdbuf *cb, u32 va, u32 bytes) ++{ ++ if (bytes & ~VXD_CMD_LOAD_MAX) { ++ cb->error = true; ++ return; ++ } ++ if (!vxd_cmd_begin(cb, 2)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_LOAD | bytes); ++ vxd_cmd_put(cb, va); ++} ++ ++/* 0xf: install @bytes at @va into destination @dst (0x62b5fcc). */ ++void apple_vxd_cmd_table_xfer(struct apple_vxd_cmdbuf *cb, u32 bytes, ++ u32 va, u32 dst) ++{ ++ if (bytes & ~VXD_CMD_LOAD_MAX) { ++ cb->error = true; ++ return; ++ } ++ if (!vxd_cmd_begin(cb, 3)) ++ return; ++ vxd_cmd_put(cb, VXD_CMD_TABLE_XFER | bytes); ++ vxd_cmd_put(cb, va); ++ vxd_cmd_put(cb, dst); ++} ++ ++void apple_vxd_cmd_end_slice(struct apple_vxd_cmdbuf *cb) ++{ ++ apple_vxd_cmd_word(cb, VXD_CMD_END_SLICE); ++} ++ ++static int vxd_cmd_pad(struct apple_vxd_cmdbuf *cb, size_t pad) ++{ ++ if (!vxd_cmd_room(cb, pad)) ++ return -ENOSPC; ++ memset((u8 *)cb->vaddr + cb->used, 0, pad); ++ cb->used += pad; ++ cb->chunk_start = cb->used; ++ cb->npairs = 0; ++ return 0; ++} ++ ++/* ++ * Close the current slice chunk: report its offset and its byte count ++ * (payload +0x0c, before padding), then zero-fill 64 - (used & 63) bytes ++ * as Apple does (a full 64 when already aligned). The next chunk starts ++ * 64-byte aligned. Returns -ENOSPC on a sticky error. ++ */ ++int apple_vxd_cmdbuf_chunk_end(struct apple_vxd_cmdbuf *cb, u32 *offset, ++ u32 *bytes) ++{ ++ if (cb->group_open) ++ cb->error = true; ++ if (cb->error) ++ return -ENOSPC; ++ ++ *offset = cb->chunk_start; ++ *bytes = cb->used - cb->chunk_start; ++ return vxd_cmd_pad(cb, VXD_CMD_CHUNK_ALIGN - ++ (cb->used & (VXD_CMD_CHUNK_ALIGN - 1))); ++} ++ ++/* ++ * Close an AVC slice chunk: the AVC emitter records the exact used bytes ++ * and starts the next chunk right behind it, with no pad (0x62c4c98, ++ * ds-vxd-cmdstream.md section 16.3). ++ */ ++int apple_vxd_cmdbuf_chunk_end_exact(struct apple_vxd_cmdbuf *cb, ++ u32 *offset, u32 *bytes) ++{ ++ if (cb->group_open) ++ cb->error = true; ++ if (cb->error) ++ return -ENOSPC; ++ ++ *offset = cb->chunk_start; ++ *bytes = cb->used - cb->chunk_start; ++ cb->chunk_start = cb->used; ++ cb->npairs = 0; ++ return 0; ++} ++ ++/* ++ * Close a chunk made only of 0x000f0000 pairs (the per-picture kind-4 ++ * chunk, 0x62b7ab0): pad by 0x40 - (pairs & 7) * 8 bytes, a full 64 when ++ * the pair count is a multiple of eight, and report the byte count ++ * including that pad (the kext measures it after the pad; the message ++ * sends it as a pair count, >> 3). ++ */ ++int apple_vxd_cmdbuf_pair_chunk_end(struct apple_vxd_cmdbuf *cb, u32 *offset, ++ u32 *bytes) ++{ ++ size_t pad; ++ int ret; ++ ++ if (cb->group_open || cb->used - cb->chunk_start != cb->npairs * 8) ++ cb->error = true; ++ if (cb->error) ++ return -ENOSPC; ++ ++ *offset = cb->chunk_start; ++ pad = VXD_CMD_CHUNK_ALIGN - (cb->npairs & 7) * 8; ++ ret = vxd_cmd_pad(cb, pad); ++ if (!ret) ++ *bytes = cb->used - *offset; ++ return ret; ++} ++ ++/* ++ * CmdBuf::addCommand: append {magic, size, 0xabcdefab} and a zeroed ++ * payload of @size bytes to the host packet list. Same gate as the kext ++ * (offset + size + 0xc < capacity). Returns the payload or NULL. ++ */ ++u32 *apple_vxd_cmdbuf_add_packet(struct apple_vxd_cmdbuf *cb, u32 magic, ++ u32 size) ++{ ++ u32 *hdr; ++ ++ if (!IS_ALIGNED(size, 4) || ++ (u64)cb->pkt_used + size + VXD_PKT_HDR_BYTES >= cb->pkt_size) { ++ cb->error = true; ++ return NULL; ++ } ++ ++ hdr = (u32 *)(cb->pkt + cb->pkt_used); ++ hdr[0] = magic; ++ hdr[1] = size; ++ hdr[2] = VXD_PKT_MAGIC1; ++ memset(&hdr[3], 0, size); ++ cb->pkt_used += VXD_PKT_HDR_BYTES + size; ++ cb->npackets++; ++ return &hdr[3]; ++} ++ ++/* ++ * The submit path's container check (0xfffffff00628ac78): from the list ++ * base, both magics on every packet, size + 0xc steps, landing exactly on ++ * the end. Returns the packet count or -EINVAL. ++ */ ++int apple_vxd_cmdbuf_validate(const struct apple_vxd_cmdbuf *cb) ++{ ++ size_t off = 0; ++ int n = 0; ++ ++ while (off < cb->pkt_used) { ++ const u32 *hdr = (const u32 *)(cb->pkt + off); ++ ++ if (cb->pkt_used - off < VXD_PKT_HDR_BYTES || ++ hdr[0] != VXD_PKT_MAGIC0 || hdr[2] != VXD_PKT_MAGIC1 || ++ hdr[1] > cb->pkt_used - off - VXD_PKT_HDR_BYTES) ++ return -EINVAL; ++ off += VXD_PKT_HDR_BYTES + hdr[1]; ++ n++; ++ } ++ return n == cb->npackets ? n : -EINVAL; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-cmd.h b/drivers/media/platform/apple/vxd/apple-vxd-cmd.h +new file mode 100644 +index 0000000000000000000000000000000000000000..5e4f302d39b2cf3675bc9246fb33317ed8d459a1 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-cmd.h +@@ -0,0 +1,86 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apple VXD - command-stream encoders (R3b, split out for R3c) ++ * ++ * The containers and encodings of docs/periph/ds-vxd-cmdstream.md, with ++ * no MMIO, no DMA API and no locking, so the H.264 builder on top of them ++ * also compiles on the host (scripts/vdec/host/) and its output can be ++ * diffed word for word against the reference generator. ++ */ ++#ifndef __APPLE_VXD_CMD_H__ ++#define __APPLE_VXD_CMD_H__ ++ ++#include ++#include ++ ++#include "apple-vxd-regs.h" ++ ++struct apple_vxd_fence; ++ ++enum apple_vxd_cmdbuf_state { ++ APPLE_VXD_CMDBUF_FREE = 0, ++ APPLE_VXD_CMDBUF_HOST, ++ APPLE_VXD_CMDBUF_DEVICE, ++}; ++ ++/* ++ * One command buffer: the device-visible register program plus the ++ * host-only packet list that the submit path turns into mailbox messages ++ * (see apple-vxd.h for the pool and the ownership rules). ++ */ ++struct apple_vxd_cmdbuf { ++ unsigned int index; ++ enum apple_vxd_cmdbuf_state state; /* under mbox_lock */ ++ struct apple_vxd_fence *fence; /* releases it, while DEVICE */ ++ ++ /* Register program (device-visible). */ ++ u32 *vaddr; /* 64 KiB aligned */ ++ dma_addr_t dma; ++ u32 va; /* GART device VA */ ++ size_t size; ++ size_t used; /* byte cursor, 4-byte granular */ ++ size_t chunk_start; /* current chunk, 64 B aligned */ ++ bool group_open; /* inside a 0x1 register group */ ++ u32 group_flags; /* OR-ed into every run header */ ++ size_t run_hdr; /* open run header, or SIZE_MAX */ ++ u32 run_next_reg; /* register that extends the run */ ++ unsigned int npairs; /* 0x000f0000 writes in this chunk */ ++ bool error; /* sticky: overflow or misuse */ ++ ++ /* Packet list (host-only). */ ++ u8 *pkt; ++ size_t pkt_size; ++ size_t pkt_used; ++ unsigned int npackets; ++}; ++ ++void apple_vxd_cmdbuf_reset(struct apple_vxd_cmdbuf *cb); ++void apple_vxd_cmd_word(struct apple_vxd_cmdbuf *cb, u32 word); ++void apple_vxd_cmd_group_open(struct apple_vxd_cmdbuf *cb, u32 flags); ++void apple_vxd_cmd_reg(struct apple_vxd_cmdbuf *cb, u32 reg, u32 val); ++void apple_vxd_cmd_group_close(struct apple_vxd_cmdbuf *cb); ++void apple_vxd_cmd_reg1(struct apple_vxd_cmdbuf *cb, u32 reg, u32 val, ++ u32 flags); ++void apple_vxd_cmd_slice_block(struct apple_vxd_cmdbuf *cb); ++void apple_vxd_cmd_ctrl_alloc(struct apple_vxd_cmdbuf *cb, ++ const u32 words[VXD_CMD_SLICE_BLOCK_WORDS - 1]); ++void apple_vxd_cmd_block(struct apple_vxd_cmdbuf *cb, u32 reg, u32 flags, ++ const u32 *vals, unsigned int n); ++void apple_vxd_cmd_dma(struct apple_vxd_cmdbuf *cb, u32 type, u32 bytes, ++ u32 va); ++void apple_vxd_cmd_slice_desc(struct apple_vxd_cmdbuf *cb, u32 w1, u32 w2); ++void apple_vxd_cmd_load(struct apple_vxd_cmdbuf *cb, u32 va, u32 bytes); ++void apple_vxd_cmd_table_xfer(struct apple_vxd_cmdbuf *cb, u32 bytes, ++ u32 va, u32 dst); ++void apple_vxd_cmd_end_slice(struct apple_vxd_cmdbuf *cb); ++int apple_vxd_cmdbuf_chunk_end(struct apple_vxd_cmdbuf *cb, u32 *offset, ++ u32 *bytes); ++int apple_vxd_cmdbuf_chunk_end_exact(struct apple_vxd_cmdbuf *cb, ++ u32 *offset, u32 *bytes); ++int apple_vxd_cmdbuf_pair_chunk_end(struct apple_vxd_cmdbuf *cb, u32 *offset, ++ u32 *bytes); ++u32 *apple_vxd_cmdbuf_add_packet(struct apple_vxd_cmdbuf *cb, u32 magic, ++ u32 size); ++int apple_vxd_cmdbuf_validate(const struct apple_vxd_cmdbuf *cb); ++ ++#endif /* __APPLE_VXD_CMD_H__ */ +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-cmdbuf.c b/drivers/media/platform/apple/vxd/apple-vxd-cmdbuf.c +new file mode 100644 +index 0000000000000000000000000000000000000000..4385f14843ff1cb98a9a26fb512e879b8c753087 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-cmdbuf.c +@@ -0,0 +1,487 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - command buffers (R3b) ++ * ++ * The pool of eight 256 KiB GART-mapped command buffers with their ++ * host-only packet lists, the packet -> mailbox message arithmetic, the ++ * probe-time self-test and the debugfs view. The encoders themselves are ++ * in apple-vxd-cmd.c (host-buildable, R3c). ++ */ ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++/* ++ * Decode message word 3. The kext builds it from the GART directory ++ * descriptor (+0x27a8, the object it also programs into MTX_MMU_DIR_ADR ++ * 0x1020): (u32)(dir >> 8), low byte replaced by the hwContextID ++ * (0xfffffff00628ae5c: lsr x8, x0, #8; str w8; strb w21). It is the MMU ++ * page-table context, not the command buffer's address; the 64 KiB ++ * directory alignment is what keeps the replaced byte zero. ++ */ ++u32 apple_vxd_mmu_ctx_word(struct apple_vxd_dev *vxd, u8 hw_ctx) ++{ ++ return ((u32)(vxd->gart.dir_dma >> 8) & 0xffffff00) | hw_ctx; ++} ++ ++/* ++ * One mailbox message from one packet, SubmitCommandBufferInternal's ++ * arithmetic for the three kinds AppleD5500 produces [E]: ++ * ++ * kind 1 -> 0x81: w1 = (bytes >> 2) << 16 | flags | (last ? 0x4004 : 0x2000), ++ * the flag OR-ed back into the packet as the kext does ++ * kind 3 -> 0x87: w1 = (bytes >> 2) << 16 | flags, ++ * w4 = (payload +0x08 & ~0x6000) | 0x4000 ++ * kind 4 -> 0x88: w1 = (bytes >> 3) << 16 | flags | (last ? 0x4004 : 0) ++ * all: w2 = payload +0x10, w3 = MMU context word, ++ * w4 = payload +0x08, w5 = payload +0x24 ++ * ++ * Six-word form only (the kext adds w6 = payload +0x28 when the core has ++ * a second pipe flag set; this core does not). @pkt points at the packet ++ * header. Before pushing a 0x81 the submit path must rewrite 0x1008 = ++ * 0xf00 if gart.dirty (the kext's +0x27a0 rule). Returns the message type ++ * or -EINVAL. ++ */ ++int apple_vxd_packet_msg(struct apple_vxd_dev *vxd, u32 *pkt, u16 fence, ++ u8 hw_ctx, bool last, u32 msg[VXD_DECODE_MSG_WORDS]) ++{ ++ u32 *p = &pkt[VXD_PKT_HDR_BYTES / 4]; ++ u32 flags, words, w4; ++ u8 type; ++ ++ if (pkt[0] != VXD_PKT_MAGIC0 || pkt[2] != VXD_PKT_MAGIC1 || ++ pkt[1] < VXD_PKT_H264_PAYLOAD) ++ return -EINVAL; ++ ++ w4 = p[VXD_PKT_W4 / 4]; ++ switch (p[VXD_PKT_KIND / 4]) { ++ case VXD_PKT_KIND_PICTURE: ++ type = VXD_MSG_DECODE; ++ words = p[VXD_PKT_CMD_BYTES / 4] >> 2; ++ p[VXD_PKT_FLAGS / 4] |= last ? VXD_MSG_FLAG_LAST : VXD_MSG_FLAG_MORE; ++ flags = p[VXD_PKT_FLAGS / 4]; ++ break; ++ case VXD_PKT_KIND_DEBLOCK: ++ type = VXD_MSG_DEBLOCK; ++ words = p[VXD_PKT_CMD_BYTES / 4] >> 2; ++ flags = p[VXD_PKT_FLAGS / 4]; ++ w4 = (w4 & ~0x6000) | 0x4000; ++ break; ++ case VXD_PKT_KIND_RENDER_TARGET: ++ type = VXD_MSG_RENDER_TARGET; ++ words = p[VXD_PKT_CMD_BYTES / 4] >> 3; ++ flags = p[VXD_PKT_FLAGS / 4] | (last ? VXD_MSG_FLAG_LAST : 0); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ msg[0] = apple_vxd_msg_hdr(fence, type, VXD_DECODE_MSG_WORDS); ++ msg[1] = (words << 16) | (flags & 0xffff); ++ msg[2] = p[VXD_PKT_W2 / 4]; ++ msg[3] = apple_vxd_mmu_ctx_word(vxd, hw_ctx); ++ msg[4] = w4; ++ msg[5] = p[VXD_PKT_W5 / 4]; ++ return type; ++} ++ ++/* --- pool ---------------------------------------------------------------- */ ++ ++struct apple_vxd_cmdbuf *apple_vxd_cmdbuf_get(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_cmdbuf_pool *pool = &vxd->cmdbufs; ++ struct apple_vxd_cmdbuf *cb = NULL; ++ unsigned long flags; ++ unsigned int i; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ for (i = 0; pool->ready && i < APPLE_VXD_NUM_CMDBUFS; i++) { ++ if (pool->bufs[i].state == APPLE_VXD_CMDBUF_FREE) { ++ cb = &pool->bufs[i]; ++ cb->state = APPLE_VXD_CMDBUF_HOST; ++ break; ++ } ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ if (cb) ++ apple_vxd_cmdbuf_reset(cb); ++ return cb; ++} ++ ++/* HOST -> FREE: a buffer that was built but never submitted. */ ++void apple_vxd_cmdbuf_put(struct apple_vxd_dev *vxd, ++ struct apple_vxd_cmdbuf *cb) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ WARN_ON(cb->state != APPLE_VXD_CMDBUF_HOST); ++ cb->state = APPLE_VXD_CMDBUF_FREE; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++} ++ ++/* ++ * HOST -> DEVICE, before the message that @fence tags is pushed: from now ++ * on the core may read the buffer, and only that fence's completion (or a ++ * reclaim after the firmware is stopped) gives it back. ++ */ ++int apple_vxd_cmdbuf_bind(struct apple_vxd_dev *vxd, ++ struct apple_vxd_cmdbuf *cb, ++ struct apple_vxd_fence *fence) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ if (cb->state != APPLE_VXD_CMDBUF_HOST || cb->error || ++ fence->state != APPLE_VXD_FENCE_ARMED || fence->cmdbuf) { ++ ret = -EINVAL; ++ } else { ++ cb->state = APPLE_VXD_CMDBUF_DEVICE; ++ cb->fence = fence; ++ fence->cmdbuf = cb; ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ return ret; ++} ++ ++/* DEVICE -> FREE on its fence's 0xc0. mbox_lock held. */ ++void apple_vxd_cmdbuf_retire_locked(struct apple_vxd_cmdbuf *cb) ++{ ++ cb->state = APPLE_VXD_CMDBUF_FREE; ++ cb->fence = NULL; ++} ++ ++/* The firmware is stopped: nothing reads any buffer any more. */ ++void apple_vxd_cmdbufs_reclaim(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_cmdbuf_pool *pool = &vxd->cmdbufs; ++ unsigned long flags; ++ unsigned int i; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ for (i = 0; i < APPLE_VXD_NUM_CMDBUFS; i++) { ++ struct apple_vxd_cmdbuf *cb = &pool->bufs[i]; ++ ++ if (cb->state == APPLE_VXD_CMDBUF_DEVICE) { ++ if (cb->fence) ++ cb->fence->cmdbuf = NULL; ++ apple_vxd_cmdbuf_retire_locked(cb); ++ } ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++} ++ ++/* Ownership walk on the real pool: all eight out, a ninth refused, all back. */ ++static int vxd_cmdbufs_check(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_cmdbuf *got[APPLE_VXD_NUM_CMDBUFS], *extra; ++ unsigned int i, n; ++ int ret = 0; ++ ++ for (n = 0; n < APPLE_VXD_NUM_CMDBUFS; n++) { ++ got[n] = apple_vxd_cmdbuf_get(vxd); ++ if (!got[n]) ++ break; ++ if (!IS_ALIGNED(got[n]->va, APPLE_VXD_CMDBUF_ALIGN) || ++ !IS_ALIGNED(got[n]->dma, APPLE_VXD_CMDBUF_ALIGN)) ++ ret = -EINVAL; ++ } ++ if (n != APPLE_VXD_NUM_CMDBUFS) { ++ ret = -EINVAL; ++ } else { ++ extra = apple_vxd_cmdbuf_get(vxd); ++ if (extra) { ++ apple_vxd_cmdbuf_put(vxd, extra); ++ ret = -EINVAL; ++ } ++ } ++ for (i = 0; i < n; i++) ++ apple_vxd_cmdbuf_put(vxd, got[i]); ++ return ret; ++} ++ ++/* ++ * Boot path, before the MTX starts: allocate and GART-map the eight ++ * buffers once, so the core's MMU never sees them change under it. ++ */ ++int apple_vxd_cmdbufs_init(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_cmdbuf_pool *pool = &vxd->cmdbufs; ++ size_t total = (size_t)APPLE_VXD_NUM_CMDBUFS * APPLE_VXD_CMDBUF_SIZE; ++ unsigned int i; ++ int ret; ++ ++ ret = apple_vxd_dma_alloc_aligned(vxd, total, APPLE_VXD_CMDBUF_ALIGN, ++ &pool->mem); ++ if (ret) ++ return ret; ++ ++ ret = apple_vxd_gart_map(vxd, pool->mem.dma, total, &pool->map); ++ if (ret) ++ goto err_free; ++ if (!IS_ALIGNED(pool->map.va, APPLE_VXD_CMDBUF_ALIGN)) { ++ dev_err(vxd->dev, ++ "command buffers at device VA %#x are not 64 KiB aligned (va_offset %#x)\n", ++ pool->map.va, vxd->va_offset); ++ ret = -EINVAL; ++ goto err_unmap; ++ } ++ ++ pool->pkt_mem = kvcalloc(APPLE_VXD_NUM_CMDBUFS, APPLE_VXD_PKTLIST_SIZE, ++ GFP_KERNEL); ++ if (!pool->pkt_mem) { ++ ret = -ENOMEM; ++ goto err_unmap; ++ } ++ ++ for (i = 0; i < APPLE_VXD_NUM_CMDBUFS; i++) { ++ struct apple_vxd_cmdbuf *cb = &pool->bufs[i]; ++ size_t off = (size_t)i * APPLE_VXD_CMDBUF_SIZE; ++ ++ cb->index = i; ++ cb->state = APPLE_VXD_CMDBUF_FREE; ++ cb->vaddr = pool->mem.vaddr + off; ++ cb->dma = pool->mem.dma + off; ++ cb->va = pool->map.va + off; ++ cb->size = APPLE_VXD_CMDBUF_SIZE; ++ cb->pkt = pool->pkt_mem + (size_t)i * APPLE_VXD_PKTLIST_SIZE; ++ cb->pkt_size = APPLE_VXD_PKTLIST_SIZE; ++ apple_vxd_cmdbuf_reset(cb); ++ } ++ pool->ready = true; ++ ++ ret = vxd_cmdbufs_check(vxd); ++ if (ret) { ++ dev_err(vxd->dev, "command-buffer pool ownership check failed\n"); ++ goto err_kfree; ++ } ++ ++ dev_info(vxd->dev, ++ "command buffers: %u x %u KiB at %pad, device VA %#x..%#x, packet lists %u x %u B; MMU context word (hwctx 0) %#010x\n", ++ APPLE_VXD_NUM_CMDBUFS, APPLE_VXD_CMDBUF_SIZE / SZ_1K, ++ &pool->mem.dma, pool->map.va, ++ pool->map.va + (u32)total - 1, APPLE_VXD_NUM_CMDBUFS, ++ APPLE_VXD_PKTLIST_SIZE, apple_vxd_mmu_ctx_word(vxd, 0)); ++ return 0; ++ ++err_kfree: ++ pool->ready = false; ++ kvfree(pool->pkt_mem); ++ pool->pkt_mem = NULL; ++err_unmap: ++ apple_vxd_gart_unmap(vxd, &pool->map); ++err_free: ++ apple_vxd_dma_free_aligned(vxd, &pool->mem); ++ memset(pool, 0, sizeof(*pool)); ++ return ret; ++} ++ ++/* After the mailbox is stopped. */ ++void apple_vxd_cmdbufs_fini(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_cmdbuf_pool *pool = &vxd->cmdbufs; ++ unsigned long flags; ++ unsigned int i; ++ ++ if (!pool->mem.raw_vaddr) ++ return; ++ ++ apple_vxd_cmdbufs_reclaim(vxd); ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ pool->ready = false; ++ for (i = 0; i < APPLE_VXD_NUM_CMDBUFS; i++) ++ WARN_ON(pool->bufs[i].state != APPLE_VXD_CMDBUF_FREE); ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ apple_vxd_gart_unmap(vxd, &pool->map); ++ kvfree(pool->pkt_mem); ++ apple_vxd_dma_free_aligned(vxd, &pool->mem); ++ memset(pool, 0, sizeof(*pool)); ++} ++ ++/* --- self-test: encodings on a scratch buffer, no MMIO, no DMA ------------ */ ++ ++int apple_vxd_cmdbuf_selftest(struct apple_vxd_dev *vxd) ++{ ++ static const u32 expect[] = { ++ 0x10023098, 1, 2, /* merged run 0x3098, 0x309c */ ++ 0x100130a8, 3, /* gap: new run */ ++ 0x10013000, 4, /* new group: no merge */ ++ 0x001f50c0, 0x12345678, /* 0xf form, flags 0x10 */ ++ 0x90000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0xb0000001, 7, 9, ++ 0xa0001234, 0x40001000, ++ 0xf0000038, 0x40002000, 0x80, ++ 0x60000000, ++ }; ++ struct apple_vxd_cmdbuf cb = { }; ++ u32 msg[VXD_DECODE_MSG_WORDS]; ++ u32 off, bytes, *p, *pkt0; ++ const char *what; ++ unsigned int i; ++ int ret = -EINVAL; ++ ++ cb.vaddr = kzalloc(1024, GFP_KERNEL); ++ cb.pkt = kzalloc(512, GFP_KERNEL); ++ if (!cb.vaddr || !cb.pkt) { ++ ret = -ENOMEM; ++ what = "allocation"; ++ goto fail; ++ } ++ cb.size = 1024; ++ cb.pkt_size = 512; ++ apple_vxd_cmdbuf_reset(&cb); ++ ++ what = "command encodings"; ++ apple_vxd_cmd_group_open(&cb, 0); ++ apple_vxd_cmd_reg(&cb, 0x3098, 1); ++ apple_vxd_cmd_reg(&cb, 0x309c, 2); ++ apple_vxd_cmd_reg(&cb, 0x30a8, 3); ++ apple_vxd_cmd_group_close(&cb); ++ apple_vxd_cmd_group_open(&cb, 0); ++ apple_vxd_cmd_reg(&cb, 0x3000, 4); ++ apple_vxd_cmd_group_close(&cb); ++ apple_vxd_cmd_reg1(&cb, 0x50c0, 0x12345678, 0x10); ++ apple_vxd_cmd_slice_block(&cb); ++ apple_vxd_cmd_slice_desc(&cb, 7, 9); ++ apple_vxd_cmd_load(&cb, 0x40001000, 0x1234); ++ apple_vxd_cmd_table_xfer(&cb, 0x38, 0x40002000, 0x80); ++ apple_vxd_cmd_end_slice(&cb); ++ if (cb.error || cb.used != sizeof(expect) || ++ memcmp(cb.vaddr, expect, sizeof(expect))) ++ goto fail; ++ ++ what = "chunk padding"; ++ if (apple_vxd_cmdbuf_chunk_end(&cb, &off, &bytes) || off || ++ bytes != sizeof(expect) || cb.used != 128 || cb.chunk_start != 128) ++ goto fail; ++ /* Already aligned: Apple still pads a full 64 bytes. */ ++ if (apple_vxd_cmdbuf_chunk_end(&cb, &off, &bytes) || off != 128 || ++ bytes || cb.used != 192) ++ goto fail; ++ ++ what = "misuse is sticky"; ++ apple_vxd_cmd_reg(&cb, 0x3000, 0); /* outside a group */ ++ if (!cb.error) ++ goto fail; ++ cb.error = false; ++ apple_vxd_cmd_group_open(&cb, 0); ++ apple_vxd_cmd_load(&cb, 0, 0); /* inside a group */ ++ if (!cb.error) ++ goto fail; ++ cb.group_open = false; ++ cb.error = false; ++ apple_vxd_cmd_load(&cb, 0, BIT(28)); /* too long */ ++ if (!cb.error) ++ goto fail; ++ cb.error = false; ++ ++ what = "overflow"; ++ for (i = 0; i < 300; i++) ++ apple_vxd_cmd_word(&cb, i); ++ if (!cb.error || cb.used > cb.size || ++ apple_vxd_cmdbuf_chunk_end(&cb, &off, &bytes) != -ENOSPC) ++ goto fail; ++ ++ what = "packet list"; ++ pkt0 = (u32 *)cb.pkt; ++ p = apple_vxd_cmdbuf_add_packet(&cb, VXD_PKT_MAGIC0, VXD_PKT_H264_PAYLOAD); ++ if (!p) ++ goto fail; ++ p[VXD_PKT_KIND / 4] = VXD_PKT_KIND_PICTURE; ++ p[VXD_PKT_FLAGS / 4] = 0x11; ++ p[VXD_PKT_CMD_BYTES / 4] = 0xab0; ++ p[VXD_PKT_W2 / 4] = 0x40010040; ++ p[VXD_PKT_W4 / 4] = 0x6005; ++ p[VXD_PKT_W5 / 4] = 0x77; ++ p = apple_vxd_cmdbuf_add_packet(&cb, VXD_PKT_MAGIC0, VXD_PKT_H264_PAYLOAD); ++ if (!p) ++ goto fail; ++ p[VXD_PKT_KIND / 4] = VXD_PKT_KIND_DEBLOCK; ++ p[VXD_PKT_FLAGS / 4] = 0x2; ++ p[VXD_PKT_CMD_BYTES / 4] = 0x100; ++ p[VXD_PKT_W4 / 4] = 0x6005; ++ if (apple_vxd_cmdbuf_validate(&cb) != 2 || ++ apple_vxd_cmdbuf_add_packet(&cb, VXD_PKT_MAGIC0, 0x200)) ++ goto fail; ++ cb.error = false; ++ pkt0[2] = 0; ++ if (apple_vxd_cmdbuf_validate(&cb) != -EINVAL) ++ goto fail; ++ pkt0[2] = VXD_PKT_MAGIC1; ++ ++ what = "decode message"; ++ if (apple_vxd_packet_msg(vxd, pkt0, 0x1230, 2, false, msg) != VXD_MSG_DECODE || ++ msg[0] != 0x12308106 || msg[1] != (0x2ac << 16 | 0x2011) || ++ msg[2] != 0x40010040 || msg[4] != 0x6005 || msg[5] != 0x77 || ++ msg[3] != apple_vxd_mmu_ctx_word(vxd, 2) || (msg[3] & 0xff) != 2 || ++ (msg[3] & 0xffffff00) != ((u32)(vxd->gart.dir_dma >> 8) & 0xffffff00)) ++ goto fail; ++ if (apple_vxd_packet_msg(vxd, (u32 *)(cb.pkt + VXD_PKT_HDR_BYTES + ++ VXD_PKT_H264_PAYLOAD), ++ 0x1232, 2, true, msg) != VXD_MSG_DEBLOCK || ++ msg[0] != 0x12328706 || msg[1] != (0x40 << 16 | 0x2) || ++ msg[4] != 0x4005) ++ goto fail; ++ ++ ret = 0; ++ goto out; ++fail: ++ dev_err(vxd->dev, "command-buffer self-test FAILED at %s\n", what); ++out: ++ kfree(cb.pkt); ++ kfree(cb.vaddr); ++ return ret; ++} ++ ++/* --- debugfs ------------------------------------------------------------- */ ++ ++static const char *const vxd_cmdbuf_state_names[] = { ++ [APPLE_VXD_CMDBUF_FREE] = "free", ++ [APPLE_VXD_CMDBUF_HOST] = "host", ++ [APPLE_VXD_CMDBUF_DEVICE] = "device", ++}; ++ ++static int apple_vxd_cmdbufs_show(struct seq_file *m, void *v) ++{ ++ struct apple_vxd_dev *vxd = m->private; ++ struct apple_vxd_cmdbuf_pool *pool = &vxd->cmdbufs; ++ struct apple_vxd_gart *gart = &vxd->gart; ++ struct apple_vxd_mapping *map; ++ unsigned long flags; ++ unsigned int i; ++ ++ mutex_lock(&gart->lock); ++ seq_printf(m, "gart: dir %pad, dirty %d, mmu context word (hwctx 0) %#010x\n", ++ &gart->dir_dma, gart->dirty, apple_vxd_mmu_ctx_word(vxd, 0)); ++ list_for_each_entry(map, &gart->mappings, list) ++ seq_printf(m, " map va %#010x..%#010x dma %pad\n", map->va, ++ map->va + (u32)map->size - 1, &map->dma); ++ mutex_unlock(&gart->lock); ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ seq_printf(m, "pool: ready %d\n", pool->ready); ++ for (i = 0; pool->ready && i < APPLE_VXD_NUM_CMDBUFS; i++) { ++ struct apple_vxd_cmdbuf *cb = &pool->bufs[i]; ++ ++ seq_printf(m, " buf[%u] %-6s va %#010x dma %pad used %zu packets %u fence %#x\n", ++ i, vxd_cmdbuf_state_names[cb->state], cb->va, &cb->dma, ++ cb->used, cb->npackets, cb->fence ? cb->fence->id : 0); ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(apple_vxd_cmdbufs); ++ ++void apple_vxd_cmdbufs_debugfs(struct apple_vxd_dev *vxd) ++{ ++ if (vxd->debugfs_dir) ++ debugfs_create_file("cmdbufs", 0444, vxd->debugfs_dir, vxd, ++ &apple_vxd_cmdbufs_fops); ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-core.c b/drivers/media/platform/apple/vxd/apple-vxd-core.c +new file mode 100644 +index 0000000000000000000000000000000000000000..93ac893599c17b05c24217a72c94659a3de3242d +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-core.c +@@ -0,0 +1,598 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD (Imagination MSVDX) video decoder driver - core bring-up ++ * ++ * Copyright (c) 2026 Berkay Kösebay ++ * ++ * R0: power the PMGR domain (ps_vdec), map the single 1 MiB MMIO region, ++ * request the level IRQ (157) without arming it, and read the core id/rev ++ * and feature registers. Nothing is written except through the firmware ++ * boot path (R1), which stays behind the apple_vxd.boot_firmware module ++ * parameter (or the debugfs "boot" trigger) until the rung has been ++ * proven on the device. ++ * ++ * Round 2: the V4L2 m2m device is wired into probe again (the round-1 ++ * restructure dropped the call, so no /dev/video* ever appeared), remove() ++ * is ordered and init-aware (the round-1 unbind crash was ++ * v4l2_m2m_unregister_media_controller() on a never-initialized NULL ++ * m2m_dev), and every boot/remove step announces itself with dev_emerg + ++ * printk flush so a panic names the step in netconsole or the panic log. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++static bool apple_vxd_boot_firmware_param; ++module_param_named(boot_firmware, apple_vxd_boot_firmware_param, bool, 0644); ++MODULE_PARM_DESC(boot_firmware, ++ "boot the resident DEVA image on the MTX at probe (R1)"); ++ ++/* ++ * Device-space offset added to the low 32 bits of every DMA address when ++ * both programming the GART and handing addresses to the block (e.g. the ++ * 0x6a00 firmware fetch address). Default 0 assumes the GART identity ++ * window starts at device VA 0; a nonzero base can be tried here without ++ * code changes if the ready poll times out (test plan T4). ++ */ ++static uint apple_vxd_va_offset; ++module_param_named(va_offset, apple_vxd_va_offset, uint, 0644); ++MODULE_PARM_DESC(va_offset, ++ "extra device-VA base offset (hex) applied to GART programming"); ++ ++static bool apple_vxd_debug; ++module_param_named(debug, apple_vxd_debug, bool, 0444); ++MODULE_PARM_DESC(debug, "log register details beyond probe summary"); ++ ++/* ++ * Round 2's oops was in the first open() of the freshly exposed video ++ * node (udev's v4l_id). apple_vxd.v4l2=0 skips the V4L2 registration ++ * entirely so the probe/GART/boot rungs can be worked on without any ++ * /dev/video* or /dev/media* node existing. Read once, at probe. ++ */ ++static bool apple_vxd_v4l2_param = true; ++module_param_named(v4l2, apple_vxd_v4l2_param, bool, 0444); ++MODULE_PARM_DESC(v4l2, "register the V4L2 m2m/media devices (0 = probe only)"); ++ ++/* ++ * Per-ioctl/per-job STEP lines (vxd_step_io). On by default for the ++ * bring-up rounds so a crash in any V4L2 handler names it; a decode run or ++ * v4l2-compliance produces hundreds of them, so they can be switched off ++ * at runtime through /sys/module/apple_vxd/parameters/step_ioctls. ++ */ ++static bool apple_vxd_step_ioctls = true; ++module_param_named(step_ioctls, apple_vxd_step_ioctls, bool, 0644); ++MODULE_PARM_DESC(step_ioctls, ++ "announce every V4L2 ioctl/job with a flushed STEP line (default on)"); ++ ++static void vxd_vstep(const struct device *dev, const char *fmt, va_list args) ++{ ++ struct va_format vaf = { .fmt = fmt, .va = &args }; ++ ++ dev_emerg(dev, "STEP %pV\n", &vaf); ++ pr_flush(100, false); ++} ++ ++/* ++ * Crash reporting for the bring-up rounds: announce the next step BEFORE ++ * doing it and flush the console, so the last line on the screen or in ++ * netconsole (or the pstore panic record) names the step that faulted. ++ * Round 1's rebind panic left nothing behind; this removes the ambiguity. ++ * Process context only (pr_flush may sleep). ++ */ ++void vxd_step(const struct device *dev, const char *fmt, ...) ++{ ++ va_list args; ++ ++ va_start(args, fmt); ++ vxd_vstep(dev, fmt, args); ++ va_end(args); ++} ++ ++/* Same, for the high-rate V4L2 entry points (gated by step_ioctls). */ ++void vxd_step_io(const struct device *dev, const char *fmt, ...) ++{ ++ va_list args; ++ ++ if (!READ_ONCE(apple_vxd_step_ioctls)) ++ return; ++ ++ va_start(args, fmt); ++ vxd_vstep(dev, fmt, args); ++ va_end(args); ++} ++ ++/* ++ * Take the active job if @last_signalled says it is done (the completion ++ * path) or unconditionally (the watchdog). job_lock makes the two paths ++ * exclusive: exactly one of them finishes a job. ++ */ ++static bool vxd_job_take(struct apple_vxd_dev *vxd, bool need_signal, ++ struct apple_vxd_job *job) ++{ ++ unsigned long flags; ++ bool taken = false; ++ ++ spin_lock_irqsave(&vxd->job_lock, flags); ++ if (vxd->job_active && vxd->job.nfences && ++ (!need_signal || ++ READ_ONCE(vxd->job.fences[vxd->job.nfences - 1]->state) == ++ APPLE_VXD_FENCE_SIGNALLED)) { ++ *job = vxd->job; ++ vxd->job_active = false; ++ taken = true; ++ } ++ spin_unlock_irqrestore(&vxd->job_lock, flags); ++ return taken; ++} ++ ++/* ++ * Every fence of the job goes back to the pool. A packet whose 0xc0 did ++ * not arrive (the firmware answers only the 0x4004 message) is simply ++ * released; a fault on any armed packet that did answer fails the picture. ++ */ ++static void vxd_job_complete(struct apple_vxd_dev *vxd, ++ const struct apple_vxd_job *job, bool timed_out) ++{ ++ struct apple_vxd_fence *last = job->fences[job->nfences - 1]; ++ unsigned int i, answered = 0; ++ bool error = timed_out; ++ u32 status = 0; ++ ++ for (i = 0; i < job->nfences; i++) { ++ struct apple_vxd_fence *f = job->fences[i]; ++ ++ if (READ_ONCE(f->state) == APPLE_VXD_FENCE_SIGNALLED) { ++ answered++; ++ error |= f->error; ++ status |= f->status; ++ } ++ } ++ vxd_step_io(vxd->dev, ++ "decode: %s: fence %#x (seq %#x), %u of %u packets answered, status %#x, %lld us -> %s", ++ timed_out ? "TIMEOUT" : "completion", last->id, job->seq, ++ answered, job->npackets, status, ++ ktime_us_delta(ktime_get(), job->submitted), ++ error ? "ERROR" : "DONE"); ++ for (i = 0; i < job->nfences; i++) ++ apple_vxd_fence_release(vxd, job->fences[i]); ++ apple_vxd_job_finish(job->ctx, error ? VB2_BUF_STATE_ERROR : ++ VB2_BUF_STATE_DONE); ++} ++ ++void apple_vxd_job_done(struct work_struct *work) ++{ ++ struct apple_vxd_dev *vxd = container_of(work, struct apple_vxd_dev, ++ job_done_work); ++ struct apple_vxd_job job; ++ ++ if (!vxd_job_take(vxd, true, &job)) ++ return; ++ cancel_delayed_work(&vxd->watchdog_work); ++ vxd_job_complete(vxd, &job, false); ++} ++ ++/* ++ * No 0xc0 for the last packet in APPLE_VXD_JOB_TIMEOUT_MS: fail the ++ * picture. The command buffer stays quarantined (the core may still ++ * read it) until the firmware is stopped. ++ */ ++void apple_vxd_watchdog(struct work_struct *work) ++{ ++ struct apple_vxd_dev *vxd = container_of(work, struct apple_vxd_dev, ++ watchdog_work.work); ++ struct apple_vxd_job job; ++ ++ if (!vxd_job_take(vxd, false, &job)) ++ return; ++ ++ dev_warn(vxd->dev, ++ "decode job timed out: no completion for fence %#x in %u ms (TO_MTX rd %u wr %u, TO_HOST rd %u wr %u, 0x200c %#x, irq status %#x)\n", ++ job.fences[job.nfences - 1]->id, APPLE_VXD_JOB_TIMEOUT_MS, ++ vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX), ++ vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX), ++ vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX), ++ vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX), ++ vxd_read(vxd, VXD_REG_FW_PC), vxd_read(vxd, VXD_REG_IRQ_STATUS)); ++ vxd_job_complete(vxd, &job, true); ++} ++ ++/* ++ * Read-only PMGR pwrstate observation (the GPU lane's pattern: share the ++ * provider's syscon without writing). Logs the target/actual/power ++ * history fields of the genpd backing this device so a boot that touches ++ * a half-powered block is identifiable from the log. ++ */ ++void apple_vxd_log_pwrstate(struct apple_vxd_dev *vxd, const char *when) ++{ ++ struct device_node *domain, *parent; ++ struct regmap *pmgr; ++ u32 offset, raw = 0; ++ int ret; ++ ++ domain = of_parse_phandle(vxd->dev->of_node, "power-domains", 0); ++ if (!domain) ++ return; ++ ret = of_property_read_u32(domain, "reg", &offset); ++ parent = of_get_parent(domain); ++ if (ret || !parent || ++ !of_property_read_bool(parent, "syscon") || ++ !of_device_is_compatible(parent, "apple,pmgr")) { ++ of_node_put(domain); ++ of_node_put(parent); ++ return; ++ } ++ of_node_put(parent); ++ ++ pmgr = syscon_node_to_regmap(parent); ++ of_node_put(domain); ++ if (IS_ERR(pmgr)) ++ return; ++ ++ ret = regmap_read(pmgr, offset, &raw); ++ if (ret) ++ return; ++ ++ dev_dbg(vxd->dev, ++ "POWER %s PMGR ps_vdec@%x=%08x target=%x actual=%x auto=%u clkgate_hist=%u pwrgate_hist=%u parent_off=%u\n", ++ when, offset, raw, raw & 0xf, (raw >> 4) & 0xf, ++ (raw >> 28) & 1, (raw >> 9) & 1, (raw >> 8) & 1, ++ (raw >> 11) & 1); ++} ++ ++/* R0: power on, read identity and feature registers. Read-only. */ ++static int apple_vxd_probe_hw(struct apple_vxd_dev *vxd) ++{ ++ u32 memcfg, id, rev; ++ int ret; ++ ++ vxd_step(vxd->dev, "power the vdec domain + read core identity"); ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) { ++ dev_err_probe(vxd->dev, ret, "failed to power the vdec domain\n"); ++ return ret; ++ } ++ ++ rev = vxd_read(vxd, VXD_REG_CORE_REV); ++ id = vxd_read(vxd, VXD_REG_CORE_ID); ++ vxd->core_rev = rev; ++ vxd->core_id = id; ++ ++ /* ++ * AppleD5500::GetCoreRev rejects MSVDX cores whose coreId high half ++ * is <= 6 ("MSVDX is unsupported"). Note the register semantics: ++ * the gate value is the high u16 of 0x630 (on this A9: 0x0303 = 771, ++ * well above 6), while the ADT's "vxd-version 7" most plausibly ++ * matches the 0x7 nibble of coreRev 0x70b00. AppleD5500 itself ++ * never parses coreRev further; feature gating in iOS uses the ADT ++ * vxd-version property. (j71t device run 1, 2026-10-23.) ++ */ ++ if ((id >> VXD_CORE_ID_MAJOR_SH) <= 6) { ++ dev_err(vxd->dev, "MSVDX is unsupported (coreId %#x)\n", id); ++ pm_runtime_put_autosuspend(vxd->dev); ++ return -ENODEV; ++ } ++ ++ vxd->num_entropy_pipes = id & VXD_CORE_ID_ENT_PIPES; ++ vxd->num_pixel_pipes = FIELD_GET(VXD_CORE_ID_PIX_PIPES, id); ++ ++ memcfg = vxd_read(vxd, VXD_REG_MTX_MEM_CFG); ++ vxd->mtx_mem_size = (u32)((FIELD_GET(VXD_MTX_MEM_BANK_COUNT, memcfg) - 1) * ++ (4 << FIELD_GET(VXD_MTX_MEM_BANK_SIZE, memcfg))) + ++ (4 << FIELD_GET(VXD_MTX_MEM_LAST_BANK, memcfg)); ++ ++ vxd->mmu_40bit = ((vxd_read(vxd, VXD_REG_MTX_MMU_CFG0) & 0xf0) == ++ VXD_MTX_MMU_CFG0_40BIT); ++ vxd->mmu_tiling_ctx = !!(vxd_read(vxd, VXD_REG_MTX_MMU_CFG1) & ++ VXD_MTX_MMU_CFG1_TILING_CTX); ++ ++ /* Raw pipe status; per-pipe selection (0x460) is an R1+ write. */ ++ vxd->pipe_status = vxd_read(vxd, VXD_REG_PIPE_STATUS); ++ ++ dev_info(vxd->dev, ++ "coreId %#x (id gate %#x), coreRev %#x, entropy pipes %u, pixel pipes %u, mtx mem %u KiB, mmu40 %u, tiling-ctx %u, pipe status %#x\n", ++ id, id >> VXD_CORE_ID_MAJOR_SH, rev, ++ vxd->num_entropy_pipes, vxd->num_pixel_pipes, ++ vxd->mtx_mem_size, vxd->mmu_40bit, ++ vxd->mmu_tiling_ctx, vxd->pipe_status); ++ ++ if (apple_vxd_debug) { ++ apple_vxd_log_pwrstate(vxd, "probe"); ++ dev_dbg(vxd->dev, "regs after power-up:\n"); ++ dev_dbg(vxd->dev, " 0x0400 cmd ctrl: %#x\n", ++ vxd_read(vxd, VXD_REG_CMD_CTRL)); ++ dev_dbg(vxd->dev, " 0x0440 man clk: %#x\n", ++ vxd_read(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE)); ++ dev_dbg(vxd->dev, " 0x0504 dmac status: %#x\n", ++ vxd_read(vxd, VXD_REG_DMAC_STATUS)); ++ dev_dbg(vxd->dev, " 0x1000 mtx ctrl: %#x\n", ++ vxd_read(vxd, VXD_REG_MTX_CTRL)); ++ dev_dbg(vxd->dev, " 0x1008 mtx clk: %#x\n", ++ vxd_read(vxd, VXD_REG_MTX_MAN_CLK_ENABLE)); ++ dev_dbg(vxd->dev, " 0x2000 comms state: %#x\n", ++ vxd_read(vxd, VXD_REG_COMMS_STATE)); ++ } ++ ++ pm_runtime_put_autosuspend(vxd->dev); ++ return 0; ++} ++ ++/* ++ * Job plumbing shared by the m2m device and the watchdog; lives in core.c ++ * so the bring-up driver is self-contained and the full kernel links at ++ * every rung. ++ */ ++void apple_vxd_job_finish_no_pm(struct apple_vxd_ctx *ctx, ++ enum vb2_buffer_state result) ++{ ++ v4l2_m2m_buf_done_and_job_finish(ctx->vxd->m2m_dev, ctx->fh.m2m_ctx, ++ result); ++} ++ ++void apple_vxd_job_finish(struct apple_vxd_ctx *ctx, ++ enum vb2_buffer_state result) ++{ ++ struct apple_vxd_dev *vxd = ctx ? ctx->vxd : NULL; ++ ++ if (vxd) { ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vxd->job_lock, flags); ++ vxd->job_active = false; ++ spin_unlock_irqrestore(&vxd->job_lock, flags); ++ pm_runtime_put_autosuspend(vxd->dev); ++ } ++ ++ if (ctx) ++ apple_vxd_job_finish_no_pm(ctx, result); ++} ++ ++void apple_vxd_run_preamble(struct apple_vxd_ctx *ctx, ++ struct apple_vxd_run *run) ++{ ++ struct media_request *src_req; ++ ++ memset(run, 0, sizeof(*run)); ++ ++ run->src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); ++ run->dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); ++ ++ /* Apply request(s) controls if needed. */ ++ src_req = run->src->vb2_buf.req_obj.req; ++ if (src_req) ++ v4l2_ctrl_request_setup(src_req, &ctx->ctrl_hdl); ++ ++ v4l2_m2m_buf_copy_metadata(run->src, run->dst); ++} ++ ++void apple_vxd_run_postamble(struct apple_vxd_ctx *ctx, ++ struct apple_vxd_run *run) ++{ ++ struct media_request *src_req = run->src->vb2_buf.req_obj.req; ++ ++ if (src_req) ++ v4l2_ctrl_request_complete(src_req, &ctx->ctrl_hdl); ++} ++ ++/* Runtime PM stubs: the genpd (ps_vdec) does all the work. */ ++static int apple_vxd_runtime_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++static int apple_vxd_runtime_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static const struct dev_pm_ops apple_vxd_pm_ops = { ++ RUNTIME_PM_OPS(apple_vxd_runtime_suspend, apple_vxd_runtime_resume, NULL) ++}; ++ ++static const struct apple_vxd_variant s8000_vxd_variant = { ++ .vxd_version = 7, ++ .num_gart_pts = 512, /* "setting 2GB mapping" [E] */ ++ /* HEVCSupported = ((rev - 7) < 4) && ((0xD >> (rev - 7)) & 1) [E] */ ++ .hevc_supported = true, ++}; ++ ++static const struct of_device_id apple_vxd_of_match[] = { ++ { .compatible = "apple,s8000-vxd", .data = &s8000_vxd_variant }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, apple_vxd_of_match); ++ ++static int apple_vxd_probe(struct platform_device *pdev) ++{ ++ const struct apple_vxd_variant *variant; ++ struct apple_vxd_dev *vxd; ++ struct resource *res; ++ unsigned int i; ++ int ret, irq; ++ ++ variant = of_device_get_match_data(&pdev->dev); ++ if (!variant) ++ return -EINVAL; ++ ++ vxd = devm_kzalloc(&pdev->dev, sizeof(*vxd), GFP_KERNEL); ++ if (!vxd) ++ return -ENOMEM; ++ ++ vxd->dev = &pdev->dev; ++ vxd->variant = variant; ++ vxd->va_offset = apple_vxd_va_offset; ++ platform_set_drvdata(pdev, vxd); ++ ++ mutex_init(&vxd->vdev_lock); ++ mutex_init(&vxd->power_lock); ++ spin_lock_init(&vxd->job_lock); ++ spin_lock_init(&vxd->mbox_lock); ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) ++ init_completion(&vxd->fences[i].done); ++ INIT_DELAYED_WORK(&vxd->watchdog_work, apple_vxd_watchdog); ++ INIT_WORK(&vxd->job_done_work, apple_vxd_job_done); ++ ++ vxd_step(&pdev->dev, "probe: map MMIO"); ++ vxd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); ++ if (IS_ERR(vxd->regs)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(vxd->regs), ++ "failed to map the vxd registers\n"); ++ vxd->regs_size = resource_size(res); ++ ++ /* The block has no DART; DMA addresses are physical and the core's ++ * own GART translates the low 32 bits into device address space. ++ * GART entries hold PA >> 8 in 32 bits: 40-bit physical addresses. ++ */ ++ ret = dma_set_mask_and_coherent(&pdev->dev, ++ DMA_BIT_MASK(VXD_GART_PA_BITS)); ++ if (ret) { ++ dev_err_probe(&pdev->dev, ret, ++ "failed to set the DMA mask\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ vxd->irq = irq; ++ ++ /* ++ * Level IRQ 157. Requested now (rung R0), enabled only once the ++ * firmware has taken its init message (apple_vxd_mbox_start()). ++ */ ++ ret = devm_request_irq(&pdev->dev, irq, apple_vxd_irq, IRQF_NO_AUTOEN, ++ dev_name(&pdev->dev), vxd); ++ if (ret) { ++ dev_err_probe(&pdev->dev, ret, "failed to request vxd IRQ\n"); ++ return ret; ++ } ++ ++ vxd_step(&pdev->dev, "probe: enable runtime PM"); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 100); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_enable(&pdev->dev); ++ ++ ret = apple_vxd_probe_hw(vxd); ++ if (ret) ++ goto err_disable_runtime_pm; ++ ++ ret = apple_vxd_gart_init(vxd); ++ if (ret) ++ goto err_disable_runtime_pm; ++ ++ /* ++ * Codec registration fills vxd->coded_fmts before any V4L2 node can ++ * exist; apple_vxd_v4l2_init() refuses to register without it. ++ */ ++ ret = apple_vxd_h264_register(vxd); ++ if (ret) ++ goto err_gart_fini; ++ ++ /* ++ * The protocol arithmetic (ring indices, headers, the init message, ++ * GART entries) is checked before anything can reach the hardware; ++ * a failure keeps the firmware boot off. ++ */ ++ vxd_step(&pdev->dev, "probe: mailbox protocol self-test"); ++ vxd->selftest_failed = apple_vxd_mbox_selftest(vxd) != 0; ++ if (!vxd->selftest_failed) ++ dev_info(&pdev->dev, "mailbox self-test ok: ring arithmetic, headers, init message, GART entries\n"); ++ vxd_step(&pdev->dev, "probe: command-buffer encoding self-test"); ++ if (apple_vxd_cmdbuf_selftest(vxd)) ++ vxd->selftest_failed = true; ++ else ++ dev_info(&pdev->dev, "command-buffer self-test ok: register runs, 0x9/0xa/0xb/0xf/0x6, chunk padding, packet list, decode messages\n"); ++ ++ /* ++ * Debugfs (dumps + the boot trigger) first so a later step can be ++ * re-run or inspected no matter how far probe got. Nothing after ++ * this point fails the probe: an error return here would free vxd ++ * (devm) under the live debugfs files. ++ */ ++ apple_vxd_mbox_init(vxd); ++ ++ if (apple_vxd_v4l2_param) { ++ ret = apple_vxd_v4l2_init(vxd); ++ if (ret) ++ dev_err(&pdev->dev, ++ "V4L2 registration failed (%d); continuing without /dev/video (debugfs boot trigger still usable)\n", ++ ret); ++ } else { ++ dev_info(&pdev->dev, ++ "v4l2: registration skipped (apple_vxd.v4l2=0)\n"); ++ } ++ ++ if (apple_vxd_boot_firmware_param) { ++ ret = apple_vxd_boot_firmware(vxd); ++ if (ret) ++ dev_warn(&pdev->dev, ++ "DEVA firmware boot failed (%d), see test plan\n", ++ ret); ++ } ++ ++ return 0; ++ ++err_gart_fini: ++ apple_vxd_gart_fini(vxd); ++err_disable_runtime_pm: ++ pm_runtime_dont_use_autosuspend(&pdev->dev); ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++} ++ ++static void apple_vxd_remove(struct platform_device *pdev) ++{ ++ struct apple_vxd_dev *vxd = platform_get_drvdata(pdev); ++ ++ /* ++ * Round 1's unbind panic was remove() cleaning up a V4L2 state ++ * that had never been initialized (m2m_dev == NULL). Order and ++ * guard every step now. ++ */ ++ vxd_step(&pdev->dev, "remove: cancel watchdog"); ++ cancel_delayed_work_sync(&vxd->watchdog_work); ++ ++ vxd_step(&pdev->dev, "remove: drop debugfs"); ++ apple_vxd_mbox_fini(vxd); ++ ++ if (vxd->v4l2_ready) { ++ vxd_step(&pdev->dev, "remove: unregister V4L2 device"); ++ apple_vxd_v4l2_cleanup(vxd); ++ } ++ ++ vxd_step(&pdev->dev, "remove: free firmware buffer"); ++ apple_vxd_fw_cleanup(vxd); ++ /* fw_cleanup failed every fence: let a pending job finish first. */ ++ flush_work(&vxd->job_done_work); ++ cancel_delayed_work_sync(&vxd->watchdog_work); ++ ++ vxd_step(&pdev->dev, "remove: free GART"); ++ apple_vxd_gart_fini(vxd); ++ ++ vxd_step(&pdev->dev, "remove: disable runtime PM"); ++ pm_runtime_dont_use_autosuspend(&pdev->dev); ++ pm_runtime_disable(&pdev->dev); ++} ++ ++static struct platform_driver apple_vxd_driver = { ++ .probe = apple_vxd_probe, ++ .remove = apple_vxd_remove, ++ .driver = { ++ .name = APPLE_VXD_DRV_NAME, ++ .pm = &apple_vxd_pm_ops, ++ .of_match_table = apple_vxd_of_match, ++ }, ++}; ++module_platform_driver(apple_vxd_driver); ++ ++MODULE_DESCRIPTION("Apple VXD (PowerVR MSVDX) video decoder driver"); ++MODULE_AUTHOR("Berkay Kösebay "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-fw.c b/drivers/media/platform/apple/vxd/apple-vxd-fw.c +new file mode 100644 +index 0000000000000000000000000000000000000000..5d7bd0d0420cc158d96978bc28d11b0df1a6aaa2 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-fw.c +@@ -0,0 +1,531 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - boot the resident DEVA firmware on the MTX core (R1) ++ * ++ * The DEVA image (REL_812.0_14_Deva, 97,944 bytes, built 08/11/2016) ships ++ * inside the iOS AppleD5500 kext; it is not committed to this tree. Obtain ++ * it as omarchy-ipad5 docs/periph/ds-video-decoder.md describes and stage ++ * it as /lib/firmware/apple/vxd-deva.bin (sha256 ++ * bf5167652e81540471aaca141943fd33d71c3990c137f145471305f5f5ade83b). ++ * ++ * Everything below is transcribed from the kext with the annotation tooling ++ * (tools/ds-video/) and follows AppleD5500/CPVDECCore's exact call and ++ * write order: ++ * ++ * PrepareCore: ++ * known state v1, core discovery (InitializeCoreConfig), firmware ++ * buffer allocation (dma + GART map + copy) ++ * LoadFirmwareAndStartCore: ++ * known state v1 (again), the clock save/quiesce/restore dance, ++ * known state v2, ProcessorInitialise (MMU + comms rings), the MTX ++ * boot block, 0x418 = 0x100000, post-boot-init registers ++ * poll 0x2014 for 0xa5a5a5a5, then read 0x2024 / 0x2018 ++ * ++ * Every step announces itself with dev_emerg + console flush (vxd_step) ++ * so the last line before a crash names it. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++static const u8 apple_vxd_deva_sha256[SHA256_DIGEST_SIZE] = { ++ 0xbf, 0x51, 0x67, 0x65, 0x2e, 0x81, 0x54, 0x04, ++ 0x71, 0xaa, 0xca, 0x14, 0x19, 0x43, 0xfd, 0x33, ++ 0xd7, 0x1c, 0x39, 0x90, 0xc1, 0x37, 0xf1, 0x45, ++ 0x47, 0x13, 0x05, 0xf5, 0xf5, 0xad, 0xe8, 0x3b, ++}; ++ ++/* ++ * PollMSVDX [E]: poll until (val & mask) == match, 1 ms steps, until the ++ * millisecond timeout. ++ */ ++static int vxd_poll_msvdx(struct apple_vxd_dev *vxd, u32 off, u32 mask, ++ u32 match, unsigned long timeout_ms) ++{ ++ u32 val; ++ ++ return readl_poll_timeout(vxd->regs + off, val, (val & mask) == match, ++ 1000, timeout_ms * 1000); ++} ++ ++/* ++ * SetClocks [E]: write default, verify the read-back, then write the ++ * value. The kext's read-back check is an assert-and-continue (only the ++ * register polls below decide), so a mismatch is a warning here, not a ++ * boot abort. ++ */ ++static void vxd_set_clocks(struct apple_vxd_dev *vxd, u32 val) ++{ ++ u32 readback; ++ ++ vxd_write(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE, VXD_CLK_ENABLE_DEFAULT); ++ readback = vxd_read(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE); ++ if (readback != VXD_CLK_ENABLE_DEFAULT) ++ dev_warn(vxd->dev, ++ "SetClocks - core register write differs from read (wrote %#x, read %#x): core not powered/clocked?\n", ++ VXD_CLK_ENABLE_DEFAULT, readback); ++ ++ vxd_write(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE, val); ++} ++ ++/* ++ * Known state v1 [E] (CPVDECCore fffffff006297f94) - called by both ++ * PrepareCore and LoadFirmwareAndStartCore; two attempts, then the ++ * epilogue writes. Every attempt: clocks on, pipe resets, CMD_CTRL ++ * pulse 0xffff0000/0/1 with a busy-clear poll, then the MTX clock select ++ * poll. ++ */ ++static int vxd_known_state_v1(struct apple_vxd_dev *vxd, const char *which) ++{ ++ unsigned int attempt, pipe; ++ int ret; ++ ++ for (attempt = 0; attempt < 2; attempt++) { ++ vxd_step(vxd->dev, "known state v1: clocks"); ++ vxd_set_clocks(vxd, VXD_CLK_ENABLE_ALL); ++ ++ vxd_step(vxd->dev, "known state v1: pipe resets"); ++ for (pipe = 1; pipe <= vxd->num_pixel_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_CTRL, 0xffffffff); ++ } ++ ++ for (pipe = 1; pipe <= vxd->num_entropy_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_SET_0, 0xffffffff); ++ } ++ ++ vxd_step(vxd->dev, "known state v1: CMD_CTRL pulse"); ++ vxd_write(vxd, VXD_REG_CMD_CTRL, VXD_CMD_CTRL_RESET); ++ vxd_read(vxd, VXD_REG_CMD_CTRL); /* APB flush read [E] */ ++ vxd_write(vxd, VXD_REG_CMD_CTRL, 0); ++ vxd_write(vxd, VXD_REG_CMD_CTRL, 1); ++ ++ ret = vxd_poll_msvdx(vxd, VXD_REG_CMD_CTRL, 1, 0, 100); ++ if (ret) { ++ dev_err(vxd->dev, ++ "known state v1 (%s): 0x400 busy poll attempt %u failed: %d\n", ++ which, attempt, ret); ++ return ret; ++ } ++ ++ vxd_step(vxd->dev, "known state v1: MTX clock select"); ++ vxd_write(vxd, VXD_REG_MTX_MAN_CLK_ENABLE, VXD_MTX_CLK_SEL); ++ ret = vxd_poll_msvdx(vxd, VXD_REG_MTX_MAN_CLK_ENABLE, ++ VXD_MTX_CLK_SEL, 0, 100); ++ if (ret) { ++ dev_err(vxd->dev, ++ "known state v1 (%s): 0x1008 poll attempt %u failed: %d\n", ++ which, attempt, ret); ++ return ret; ++ } ++ } ++ ++ /* Epilogue [E]: host interrupt disabled, pending causes cleared. */ ++ vxd_write(vxd, VXD_REG_IRQ_ENABLE, 0); ++ vxd_write(vxd, VXD_REG_IRQ_CLEAR, 0xffff0000); ++ return 0; ++} ++ ++/* ++ * Known state v2 [E] (fffffff0062980fc) - immediately before ++ * ProcessorInitialise; same reset value, but the MTX clock poll runs on ++ * the first attempt only and the epilogue differs. ++ */ ++static int vxd_known_state_v2(struct apple_vxd_dev *vxd) ++{ ++ unsigned int attempt, pipe; ++ int ret; ++ ++ for (attempt = 0; attempt < 2; attempt++) { ++ vxd_set_clocks(vxd, VXD_CLK_ENABLE_ALL); ++ ++ for (pipe = 1; pipe <= vxd->num_pixel_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_CTRL, 0xffffffff); ++ } ++ ++ for (pipe = 1; pipe <= vxd->num_entropy_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_SET_0, 0xffffffff); ++ } ++ ++ vxd_step(vxd->dev, "known state v2: CMD_CTRL pulse"); ++ vxd_write(vxd, VXD_REG_CMD_CTRL, VXD_CMD_CTRL_RESET); ++ vxd_read(vxd, VXD_REG_CMD_CTRL); /* APB flush read [E] */ ++ vxd_write(vxd, VXD_REG_CMD_CTRL, 0); ++ ++ if (!attempt) { ++ vxd_write(vxd, VXD_REG_MTX_MAN_CLK_ENABLE, ++ VXD_MTX_CLK_SEL); ++ ret = vxd_poll_msvdx(vxd, VXD_REG_MTX_MAN_CLK_ENABLE, ++ VXD_MTX_CLK_SEL, 0, 100); ++ if (ret) ++ return ret; ++ } ++ } ++ ++ vxd_read(vxd, VXD_REG_COMMS_STATE); ++ vxd_write(vxd, VXD_REG_IRQ_CLEAR, 0xffffffff); ++ return 0; ++} ++ ++/* ++ * Core discovery [E] (InitializeCoreConfig fffffff006299330, head only): ++ * the entropy-pipe feature flag, a clock save-and-set to the firmware ++ * value, and the per-pixel-pipe config write with the pipe status reads. ++ * The kext's per-pipe register recompute (a few hundred lines of bitfield ++ * algebra on the statuses) is still TODO; the boot works with the ++ * constructor defaults until the R2 dumps say otherwise. ++ */ ++static void vxd_discover_core(struct apple_vxd_dev *vxd) ++{ ++ u32 pipe, status = 0, maxa = 0; ++ ++ vxd->cfg_flags = ((vxd->num_entropy_pipes != 0) << 21); ++ ++ /* ++ * save 0x440, set the firmware clock value [E]. The saved value is ++ * not restored in this prologue (round 2 already left it unused), so ++ * only the read is kept, in the kext's order. ++ */ ++ vxd_read(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE); ++ vxd_set_clocks(vxd, VXD_CLK_ENABLE_FW); ++ ++ for (pipe = 1; pipe <= vxd->num_pixel_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_CTRL, 0x20000); ++ status |= vxd_read(vxd, VXD_REG_PIPE_STATUS); ++ maxa = max(maxa, vxd_read(vxd, VXD_REG_PIPE_STATUS_2) & 0x3); ++ } ++ vxd->pipe_status_cfg = status; ++ vxd->pipe_status_2_max = maxa; ++} ++ ++/* ++ * The quiesce dance [E] (sub_fffffff0062995cc, between the second known ++ * state v1 and the boot): save 0x440, set the firmware value, touch the ++ * pixel pipes with 0x20000, then restore the saved value. ++ */ ++static void vxd_quiesce_clocks(struct apple_vxd_dev *vxd) ++{ ++ u32 saved, pipe; ++ ++ saved = vxd_read(vxd, VXD_REG_CR_PVDEC_MAN_CLK_ENABLE); ++ vxd_set_clocks(vxd, VXD_CLK_ENABLE_FW); ++ ++ for (pipe = 1; pipe <= vxd->num_pixel_pipes; pipe++) { ++ vxd_write(vxd, VXD_REG_CMD_KICK, pipe); ++ vxd_write(vxd, VXD_REG_PIPE_CTRL, 0x20000); ++ } ++ ++ vxd_set_clocks(vxd, saved); ++} ++ ++/* ++ * ProcessorInitialise [E]: message ring setup and MMU programming. The ++ * 0x202c flags take the !core[0xc0] branch (0x1000000); the 0x1003000 ++ * branch and what core[0xc0] means are [U], pinned down by the R2 dump. ++ */ ++static void vxd_processor_initialise(struct apple_vxd_dev *vxd) ++{ ++ /* This core always programs its GART before MTX boot: there is no ++ * DART and no other address path [E]. ++ */ ++ vxd_step(vxd->dev, "ProcessorInitialise: MMU programming"); ++ vxd_read(vxd, VXD_REG_MTX_MMU_CFG0); ++ vxd_write(vxd, VXD_REG_MTX_MMU_0, 0); ++ vxd_write(vxd, VXD_REG_MTX_MMU_DIR_ADR, ++ (u32)((u64)vxd->gart.dir_dma >> 8)); ++ vxd_write(vxd, VXD_REG_MTX_MMU_MODE, ++ vxd->mmu_40bit ? VXD_MTX_MMU_MODE_40BIT : 0); ++ ++ vxd_step(vxd->dev, "ProcessorInitialise: comms rings"); ++ vxd_write(vxd, VXD_REG_FW_READY_MAGIC, 0); ++ vxd_write(vxd, VXD_REG_COMMS_MSG_COUNT, 200); ++ vxd_write(vxd, VXD_REG_FW_PC, 0); ++ vxd_write(vxd, VXD_REG_TO_HOST_RD_INDEX, 0); ++ vxd_write(vxd, VXD_REG_TO_HOST_WR_INDEX, 0); ++ vxd_write(vxd, VXD_REG_TO_MTX_RD_INDEX, 0); ++ vxd_write(vxd, VXD_REG_TO_MTX_WR_INDEX, 0); ++ vxd_write(vxd, VXD_REG_COMMS_2008, 0); ++ vxd_write(vxd, VXD_REG_COMMS_FLAGS, 0x1000000 | 0x60); ++ ++ vxd_read(vxd, VXD_REG_COMMS_STATE); ++} ++ ++/* MTX boot block programming [E] (BootFirmware). */ ++static void vxd_boot_mtx(struct apple_vxd_dev *vxd, u32 fw_words) ++{ ++ vxd_step(vxd->dev, "MTX boot: clocks + pipe config"); ++ vxd_write(vxd, VXD_REG_CMD_KICK, 1); ++ vxd_set_clocks(vxd, VXD_CLK_ENABLE_FW); ++ ++ vxd_write(vxd, VXD_REG_PIPE_CTRL, 0x30000); ++ vxd_write(vxd, VXD_REG_ENTDEC_0_CFG, 0); ++ vxd_write(vxd, VXD_REG_ENTDEC_0_SIZE, ++ (((fw_words + 15) & ~7) & ~0x10000) | 0x10000); ++ vxd_write(vxd, VXD_REG_DMAC_STATUS, VXD_DMAC_STATUS_FLUSH); ++ vxd_write(vxd, VXD_REG_PIPE_4004, 0); ++ ++ /* Boot block; BOOT_5 first (MTX clock enable; order is [E]). */ ++ vxd_step(vxd->dev, "MTX boot: boot block writes"); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_5, VXD_MTX_BOOT_5_ENABLE); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_3, 0); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_FW_ADR, vxd->fw_dev_va); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_4, VXD_MTX_BOOT_4_VALUE); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_2, VXD_MTX_BOOT_2_VALUE); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_1, ++ VXD_MTX_BOOT_1_STAGE0 | fw_words); ++ vxd_write(vxd, VXD_REG_MTX_BOOT_1, ++ VXD_MTX_BOOT_1_STAGE1 | fw_words); ++ ++ vxd_read(vxd, VXD_REG_COMMS_STATE); ++} ++ ++/* ++ * Post-boot-init [E]: comms flags, MTX_CTRL read-modify-write (bits 0 and ++ * 16 come from core flags, zero on a fresh core) and the per-pipe ++ * registers. The pipe configuration values are the CPVDECCore constructor ++ * defaults recovered from __TEXT.__const (the per-pipe recompute in ++ * InitializeCoreConfig stays a TODO for R2). ++ */ ++static void vxd_post_boot_init(struct apple_vxd_dev *vxd) ++{ ++ u32 val; ++ ++ vxd_write(vxd, VXD_REG_COMMS_FLAGS, 0x1000000 | 0x60); ++ ++ val = vxd_read(vxd, VXD_REG_MTX_CTRL); ++ val &= VXD_MTX_CTRL_KEEP_MASK; ++ vxd_write(vxd, VXD_REG_MTX_CTRL, val); ++ ++ vxd_write(vxd, VXD_REG_CMD_KICK, 1); ++ vxd_write(vxd, VXD_REG_PIPE_CR0, 0); ++ vxd_write(vxd, VXD_REG_PIPE_CR1, 0x4ce); ++ vxd_write(vxd, VXD_REG_PIPE_CR2, 0); ++ vxd_write(vxd, VXD_REG_PIPE_CR3, 0x2008); ++ vxd_write(vxd, VXD_REG_PIPE_SET_1, 0); ++ vxd_write(vxd, VXD_REG_PIPE_SET_2, 0x334); ++} ++ ++int apple_vxd_boot_firmware(struct apple_vxd_dev *vxd) ++{ ++ const struct firmware *fw; ++ u8 digest[SHA256_DIGEST_SIZE]; ++ u32 fw_words; ++ int ret; ++ ++ mutex_lock(&vxd->power_lock); ++ if (vxd->fw_loaded) { ++ /* Up, but a mailbox that never took the init message stays an error. */ ++ ret = vxd->hw_ready ? 0 : -EIO; ++ if (ret) ++ dev_err(vxd->dev, ++ "firmware is running but its mailbox did not start; unbind/bind to retry\n"); ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++ } ++ ++ if (vxd->selftest_failed) { ++ dev_err(vxd->dev, "mailbox self-test failed at probe: not booting the firmware\n"); ++ mutex_unlock(&vxd->power_lock); ++ return -EINVAL; ++ } ++ ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) { ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++ } ++ ++ vxd_step(vxd->dev, "boot: power domain + PMGR pwrstate"); ++ apple_vxd_log_pwrstate(vxd, "boot"); ++ ++ /* ++ * AllocateFirmwareBuffer [E]: a 4-byte-aligned coherent buffer of ++ * blob_size, GART-mapped (the MTX fetches through its own MMU - ++ * with the round-1 dummy-PTE slots this fetch address was ++ * unbacked), and the firmware must fit under 0x10000 MTX words. ++ */ ++ vxd_step(vxd->dev, "boot: request DEVA firmware + sha256"); ++ ret = request_firmware(&fw, APPLE_VXD_FW_NAME, vxd->dev); ++ if (ret) { ++ dev_err(vxd->dev, ++ "DEVA firmware %s not available (%d); extract it from the iPadOS kernelcache (AppleD5500) and install it\n", ++ APPLE_VXD_FW_NAME, ret); ++ goto out_put; ++ } ++ ++ if (!fw->size || (fw->size & 3) || ++ fw->size / sizeof(u32) >= APPLE_VXD_FW_MAX_WORDS) { ++ dev_err(vxd->dev, "firmware size %zu is not usable (< 0x10000 words)\n", ++ fw->size); ++ ret = -EINVAL; ++ goto out_release; ++ } ++ fw_words = fw->size / sizeof(u32); ++ vxd->fw_blob_size = fw->size; ++ ++ sha256(fw->data, fw->size, digest); ++ if (memcmp(digest, apple_vxd_deva_sha256, sizeof(digest))) { ++ print_hex_dump(KERN_WARNING, "vxd: unexpected fw digest ", ++ DUMP_PREFIX_OFFSET, 16, 1, digest, ++ sizeof(digest), false); ++ dev_warn(vxd->dev, ++ "this is not the REL_812.0_14_Deva image we reverse engineered\n"); ++ } else { ++ dev_info(vxd->dev, "DEVA firmware: REL_812.0_14_Deva (sha256 verified)\n"); ++ } ++ ++ vxd_step(vxd->dev, "boot: allocate + GART-map firmware buffer"); ++ vxd->fw_size = PAGE_ALIGN(fw->size); ++ vxd->fw_vaddr = dma_alloc_coherent(vxd->dev, vxd->fw_size, ++ &vxd->fw_dma, GFP_KERNEL); ++ if (!vxd->fw_vaddr) { ++ ret = -ENOMEM; ++ goto out_release; ++ } ++ ++ /* ++ * Device VA = low 32 bits of the DMA address (identity GART), plus ++ * the va_offset module parameter applied to both the GART slot and ++ * the 0x6a00 fetch address, so a wrong aperture base can be tested ++ * without a rebuild (test plan T4). ++ */ ++ vxd->fw_dev_va = (u32)vxd->fw_dma + vxd->va_offset; ++ ret = apple_vxd_gart_map_at(vxd, vxd->fw_dma, vxd->fw_size, ++ vxd->fw_dev_va, &vxd->fw_map); ++ if (ret) { ++ dev_err(vxd->dev, ++ "failed to GART-map the firmware buffer (va %#x): %d\n", ++ vxd->fw_dev_va, ret); ++ goto out_fwbuf; ++ } ++ ++ memcpy(vxd->fw_vaddr, fw->data, fw->size); ++ release_firmware(fw); ++ /* Every later failure unwinds through out_release: no double free. */ ++ fw = NULL; ++ ++ /* ++ * R3b: the eight command buffers are mapped now, before the MTX and ++ * its MMU start, so no mapping changes under a running core. ++ */ ++ vxd_step(vxd->dev, "boot: allocate + GART-map 8 x 256 KiB command buffers"); ++ ret = apple_vxd_cmdbufs_init(vxd); ++ if (ret) { ++ dev_err(vxd->dev, "command buffers: %d\n", ret); ++ goto out_unmap; ++ } ++ ++ /* PrepareCore [E], in the kext's order. */ ++ ret = vxd_known_state_v1(vxd, "PrepareCore"); ++ if (ret) ++ goto out_cmdbufs; ++ ++ vxd_step(vxd->dev, "boot: core discovery"); ++ vxd_discover_core(vxd); ++ ++ /* LoadFirmwareAndStartCore [E], in the kext's order. */ ++ ret = vxd_known_state_v1(vxd, "LoadFirmwareAndStartCore"); ++ if (ret) ++ goto out_cmdbufs; ++ ++ vxd_step(vxd->dev, "boot: clock save/quiesce/restore"); ++ vxd_quiesce_clocks(vxd); ++ ++ ret = vxd_known_state_v2(vxd); ++ if (ret) ++ goto out_cmdbufs; ++ ++ vxd_processor_initialise(vxd); ++ vxd_boot_mtx(vxd, fw_words); ++ ++ vxd_step(vxd->dev, "boot: post-boot-init registers"); ++ vxd_write(vxd, VXD_REG_IRQ_ENABLE, VXD_IRQ_FW_MSG); ++ vxd_post_boot_init(vxd); ++ ++ vxd_step(vxd->dev, "boot: firmware ready poll (1 s)"); ++ ret = vxd_poll_msvdx(vxd, VXD_REG_FW_READY_MAGIC, 0xffffffff, ++ VXD_FW_READY_MAGIC, 1000); ++ if (ret) { ++ dev_err(vxd->dev, ++ "firmware ready indication - Poll returns %d (0 = no error): firmware did not come up\n", ++ ret); ++ goto out_cmdbufs; ++ } ++ ++ /* ++ * 0x2024/0x2018 are the ring geometry the firmware publishes with the ++ * magic (rounds 1-3 logged them as "build halves"). ++ */ ++ vxd_step(vxd->dev, "boot: firmware alive, reading the ring geometry (0x2024/0x2018)"); ++ ret = apple_vxd_mbox_open(vxd); ++ if (ret) ++ goto out_cmdbufs; ++ ++ /* ++ * The MTX runs from here on: keep the image, its mapping and the PM ++ * reference until apple_vxd_fw_cleanup(), whatever the mailbox does. ++ */ ++ vxd->fw_loaded = true; ++ ++ ret = apple_vxd_mbox_start(vxd); ++ if (ret) { ++ dev_err(vxd->dev, ++ "DEVA firmware is up but did not take the init message (%d): decode stays off, debugfs mbox/ping still work\n", ++ ret); ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++ } ++ ++ vxd->hw_ready = true; ++ dev_info(vxd->dev, ++ "DEVA firmware ready: mailbox up, init message consumed, IRQ %d armed\n", ++ vxd->irq); ++ mutex_unlock(&vxd->power_lock); ++ return 0; ++ ++out_cmdbufs: ++ apple_vxd_cmdbufs_fini(vxd); ++out_unmap: ++ apple_vxd_gart_unmap(vxd, &vxd->fw_map); ++out_fwbuf: ++ dma_free_coherent(vxd->dev, vxd->fw_size, vxd->fw_vaddr, vxd->fw_dma); ++ vxd->fw_vaddr = NULL; ++out_release: ++ release_firmware(fw); ++out_put: ++ pm_runtime_put_autosuspend(vxd->dev); ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++} ++ ++void apple_vxd_fw_cleanup(struct apple_vxd_dev *vxd) ++{ ++ bool was_loaded; ++ ++ mutex_lock(&vxd->power_lock); ++ was_loaded = vxd->fw_loaded; ++ /* IRQ off and every fence waiter woken before the buffers go. */ ++ apple_vxd_mbox_stop(vxd); ++ apple_vxd_cmdbufs_fini(vxd); ++ if (vxd->fw_vaddr) { ++ apple_vxd_gart_unmap(vxd, &vxd->fw_map); ++ dma_free_coherent(vxd->dev, vxd->fw_size, vxd->fw_vaddr, ++ vxd->fw_dma); ++ vxd->fw_vaddr = NULL; ++ } ++ vxd->fw_loaded = false; ++ vxd->hw_ready = false; ++ mutex_unlock(&vxd->power_lock); ++ ++ if (was_loaded) ++ pm_runtime_put_autosuspend(vxd->dev); ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-gart.c b/drivers/media/platform/apple/vxd/apple-vxd-gart.c +new file mode 100644 +index 0000000000000000000000000000000000000000..cc2eb1f995256936b6d18f3773cea9760e67c1ee +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-gart.c +@@ -0,0 +1,283 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - the core's own two-level memory translation unit ("GART") ++ * ++ * The vxd node has no iommu-parent; instead AppleD5500 programs a page ++ * table hierarchy of its own (AppleD5500::initialize_gart [E]): ++ * ++ * - directory table: 1024 x 4-byte entries, one per 4 MiB of device VA, ++ * in a 4 KiB buffer allocated 64 KiB aligned ++ * - page tables: 1024 x 4-byte entries, one per 4 KiB of device VA, ++ * num_pts consecutive 4 KiB tables ++ * - a dummy page filling every unmapped directory/page slot ++ * - entries: ((PA >> 8) & 0xfffffff0) | 1 on the 40-bit physical address ++ * - the directory table address is programmed through MTX_MMU_DIR_ADR ++ * (0x1020) as (PA >> 8) in ProcessorInitialise [E] ++ * ++ * GART pages are 4 KiB whatever the kernel page size: every table walk ++ * below steps VXD_GART_PAGE_SIZE, never PAGE_SIZE (the device kernel runs ++ * 16 KiB pages, which rounds 1-3 got wrong). ++ * ++ * We pre-allocate variant->num_gart_pts page tables (512 = the kext's ++ * "2GB mapping" for a 2 GiB device) and identity-map the low 32 bits of ++ * every DMA address, so mapping is just writing the slot selected by the ++ * address itself. The tables are dma_alloc_coherent memory, so no cache ++ * maintenance is needed when they change. ++ */ ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++/* ++ * Coherent buffer with a bus-address alignment beyond PAGE_SIZE. The ++ * direct allocator hands out naturally aligned blocks in practice, so the ++ * exact size is tried first; only a misaligned result is retried with ++ * enough slack to align inside it. ++ */ ++int apple_vxd_dma_alloc_aligned(struct apple_vxd_dev *vxd, size_t size, ++ size_t align, struct apple_vxd_dma_buf *buf) ++{ ++ struct device *dev = vxd->dev; ++ size_t off; ++ ++ if (WARN_ON(!size || !is_power_of_2(align))) ++ return -EINVAL; ++ ++ memset(buf, 0, sizeof(*buf)); ++ buf->raw_size = PAGE_ALIGN(size); ++ buf->raw_vaddr = dma_alloc_coherent(dev, buf->raw_size, &buf->raw_dma, ++ GFP_KERNEL); ++ if (!buf->raw_vaddr) ++ return -ENOMEM; ++ ++ if (buf->raw_dma & (align - 1)) { ++ dma_free_coherent(dev, buf->raw_size, buf->raw_vaddr, ++ buf->raw_dma); ++ buf->raw_size = PAGE_ALIGN(size + align); ++ buf->raw_vaddr = dma_alloc_coherent(dev, buf->raw_size, ++ &buf->raw_dma, GFP_KERNEL); ++ if (!buf->raw_vaddr) ++ return -ENOMEM; ++ } ++ ++ off = ALIGN(buf->raw_dma, align) - buf->raw_dma; ++ buf->vaddr = buf->raw_vaddr + off; ++ buf->dma = buf->raw_dma + off; ++ buf->size = size; ++ return 0; ++} ++ ++void apple_vxd_dma_free_aligned(struct apple_vxd_dev *vxd, ++ struct apple_vxd_dma_buf *buf) ++{ ++ if (buf->raw_vaddr) ++ dma_free_coherent(vxd->dev, buf->raw_size, buf->raw_vaddr, ++ buf->raw_dma); ++ memset(buf, 0, sizeof(*buf)); ++} ++ ++int apple_vxd_gart_init(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_gart *gart = &vxd->gart; ++ struct device *dev = vxd->dev; ++ u32 num_pts = vxd->variant->num_gart_pts; ++ u32 i; ++ int ret; ++ ++ mutex_init(&gart->lock); ++ INIT_LIST_HEAD(&gart->mappings); ++ ++ ret = apple_vxd_dma_alloc_aligned(vxd, ++ VXD_GART_DIR_ENTRIES * sizeof(u32), ++ VXD_GART_DIR_ALIGN, &gart->dir_buf); ++ if (ret) ++ goto err; ++ gart->dir = gart->dir_buf.vaddr; ++ gart->dir_dma = gart->dir_buf.dma; ++ ++ gart->dummy = dma_alloc_coherent(dev, PAGE_SIZE, &gart->dummy_dma, ++ GFP_KERNEL); ++ if (!gart->dummy) { ++ ret = -ENOMEM; ++ goto err_free_dir; ++ } ++ ++ /* Page tables are one contiguous DMA block of num_pts 4 KiB tables: ++ * table i sits at pts_dma + i * 4 KiB, which is what the directory ++ * entry for i points at. ++ */ ++ gart->pts = dma_alloc_coherent(dev, (size_t)num_pts * VXD_GART_PT_SIZE, ++ &gart->pts_dma, GFP_KERNEL); ++ if (!gart->pts) { ++ ret = -ENOMEM; ++ goto err_free_dummy; ++ } ++ gart->num_pts = num_pts; ++ ++ if (upper_32_bits(gart->pts_dma) >> (VXD_GART_PA_BITS - 32) || ++ upper_32_bits(gart->dir_dma) >> (VXD_GART_PA_BITS - 32) || ++ upper_32_bits(gart->dummy_dma) >> (VXD_GART_PA_BITS - 32)) { ++ dev_err(dev, "GART tables above the %u-bit PA range\n", ++ VXD_GART_PA_BITS); ++ ret = -ERANGE; ++ goto err_free_pts; ++ } ++ ++ /* Unmapped VAs resolve to the dummy page, as in AppleD5500. */ ++ for (i = 0; i < num_pts * VXD_GART_PT_ENTRIES; i++) ++ gart->pts[i] = VXD_GART_PTE(gart->dummy_dma); ++ for (i = 0; i < VXD_GART_DIR_ENTRIES; i++) { ++ if (i < num_pts) ++ gart->dir[i] = VXD_GART_PTE(gart->pts_dma + ++ (dma_addr_t)i * VXD_GART_PT_SIZE); ++ else ++ gart->dir[i] = VXD_GART_PTE(gart->dummy_dma); ++ } ++ ++ dev_info(dev, ++ "GART initialized: dir %pad (64 KiB aligned), %u x 4 KiB page tables at %pad, dummy %pad, dir[0] %#x\n", ++ &gart->dir_dma, num_pts, &gart->pts_dma, &gart->dummy_dma, ++ gart->dir[0]); ++ ++ return 0; ++ ++err_free_pts: ++ dma_free_coherent(dev, (size_t)num_pts * VXD_GART_PT_SIZE, gart->pts, ++ gart->pts_dma); ++err_free_dummy: ++ dma_free_coherent(dev, PAGE_SIZE, gart->dummy, gart->dummy_dma); ++err_free_dir: ++ apple_vxd_dma_free_aligned(vxd, &gart->dir_buf); ++err: ++ memset(gart, 0, sizeof(*gart)); ++ return ret; ++} ++ ++void apple_vxd_gart_fini(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_gart *gart = &vxd->gart; ++ struct device *dev = vxd->dev; ++ ++ WARN_ON(gart->pts && !list_empty(&gart->mappings)); ++ ++ if (gart->pts) ++ dma_free_coherent(dev, (size_t)gart->num_pts * VXD_GART_PT_SIZE, ++ gart->pts, gart->pts_dma); ++ if (gart->dummy) ++ dma_free_coherent(dev, PAGE_SIZE, gart->dummy, gart->dummy_dma); ++ apple_vxd_dma_free_aligned(vxd, &gart->dir_buf); ++ memset(gart, 0, sizeof(*gart)); ++} ++ ++/* Slot of device VA @va in the page-table block. */ ++static u32 *vxd_gart_slot(struct apple_vxd_gart *gart, u32 va) ++{ ++ u32 dir_idx = va >> 22; /* 4 MiB per page table */ ++ u32 ent_idx = (va >> VXD_GART_PAGE_SHIFT) & (VXD_GART_PT_ENTRIES - 1); ++ ++ if (dir_idx >= gart->num_pts) ++ return NULL; ++ return &gart->pts[dir_idx * VXD_GART_PT_ENTRIES + ent_idx]; ++} ++ ++int apple_vxd_gart_map_at(struct apple_vxd_dev *vxd, dma_addr_t dma, ++ size_t size, u32 va_base, ++ struct apple_vxd_mapping *map) ++{ ++ struct apple_vxd_gart *gart = &vxd->gart; ++ u32 npages, i; ++ ++ memset(map, 0, sizeof(*map)); ++ INIT_LIST_HEAD(&map->list); ++ ++ if (!size || !IS_ALIGNED(size, VXD_GART_PAGE_SIZE) || ++ !IS_ALIGNED(dma, VXD_GART_PAGE_SIZE) || ++ !IS_ALIGNED(va_base, VXD_GART_PAGE_SIZE) || ++ upper_32_bits(dma + size - 1) >> (VXD_GART_PA_BITS - 32)) ++ return -EINVAL; ++ ++ npages = size >> VXD_GART_PAGE_SHIFT; ++ if ((u64)va_base + size > (u64)gart->num_pts << 22) { ++ /* Device aperture beyond the mapped window. */ ++ dev_err(vxd->dev, ++ "GART: VA %#x (DMA %pad+0x%zx) maps outside the %u page-table window\n", ++ va_base, &dma, size, gart->num_pts); ++ return -ENOSPC; ++ } ++ ++ mutex_lock(&gart->lock); ++ for (i = 0; i < npages; i++) ++ *vxd_gart_slot(gart, va_base + (i << VXD_GART_PAGE_SHIFT)) = ++ VXD_GART_PTE(dma + ((dma_addr_t)i << VXD_GART_PAGE_SHIFT)); ++ map->dma = dma; ++ map->size = size; ++ map->va = va_base; ++ list_add_tail(&map->list, &gart->mappings); ++ gart->dirty = true; ++ mutex_unlock(&gart->lock); ++ return 0; ++} ++ ++int apple_vxd_gart_map(struct apple_vxd_dev *vxd, dma_addr_t dma, size_t size, ++ struct apple_vxd_mapping *map) ++{ ++ return apple_vxd_gart_map_at(vxd, dma, size, (u32)dma + vxd->va_offset, ++ map); ++} ++ ++void apple_vxd_gart_unmap(struct apple_vxd_dev *vxd, struct apple_vxd_mapping *map) ++{ ++ struct apple_vxd_gart *gart = &vxd->gart; ++ u32 npages, i; ++ u32 *slot; ++ ++ if (!map->size) ++ return; ++ ++ mutex_lock(&gart->lock); ++ npages = map->size >> VXD_GART_PAGE_SHIFT; ++ for (i = 0; i < npages; i++) { ++ slot = vxd_gart_slot(gart, map->va + (i << VXD_GART_PAGE_SHIFT)); ++ if (slot) ++ *slot = VXD_GART_PTE(gart->dummy_dma); ++ } ++ list_del(&map->list); ++ gart->dirty = true; ++ mutex_unlock(&gart->lock); ++ memset(map, 0, sizeof(*map)); ++} ++ ++/* ++ * A zeroed coherent buffer, 4 KiB aligned (every MSVDX base-address ++ * register drops the low 12 bits), GART-mapped for its lifetime. ++ */ ++int apple_vxd_dev_buf_alloc(struct apple_vxd_dev *vxd, size_t size, ++ struct apple_vxd_dev_buf *buf) ++{ ++ int ret; ++ ++ ret = apple_vxd_dma_alloc_aligned(vxd, ALIGN(size, VXD_GART_PAGE_SIZE), ++ VXD_GART_PAGE_SIZE, &buf->mem); ++ if (ret) ++ return ret; ++ memset(buf->mem.vaddr, 0, buf->mem.size); ++ ++ ret = apple_vxd_gart_map(vxd, buf->mem.dma, buf->mem.size, &buf->map); ++ if (ret) { ++ apple_vxd_dma_free_aligned(vxd, &buf->mem); ++ return ret; ++ } ++ buf->va = buf->map.va; ++ return 0; ++} ++ ++void apple_vxd_dev_buf_free(struct apple_vxd_dev *vxd, ++ struct apple_vxd_dev_buf *buf) ++{ ++ if (!buf->mem.raw_vaddr) ++ return; ++ apple_vxd_gart_unmap(vxd, &buf->map); ++ apple_vxd_dma_free_aligned(vxd, &buf->mem); ++ buf->va = 0; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.c b/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.c +new file mode 100644 +index 0000000000000000000000000000000000000000..597cfb7bc467a4475e7e7f68d1773a68bfff8e20 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.c +@@ -0,0 +1,623 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - H.264 (AVC) command-stream builder (R3c) ++ * ++ * The AVC path of the kext (CH264Decoder::HOST_H264DecodeFrame ++ * 0xfffffff0062a15f0 -> DecodePictureInternal 0x62a0c00 -> ++ * DecodeExecuteH264VLD 0x62c4c98, docs/periph/ds-vxd-cmdstream.md section ++ * 16.3-16.6) builds one command chunk and one kind-1 packet (mailbox ++ * 0x81) per slice, in Imagination's DXVA control-allocation format ++ * (psb_video's dxva_fw_ctrl.h): ++ * ++ * CTRL_ALLOC_HEADER 0x90000000 + 9 words (slice params, strides, ++ * first/last MB, chroma format) ++ * CMD_DMA the 0x440-byte CAVLC table, type VLC ++ * REGVALPAIR 26 constant VLC table address registers ++ * SR_SETUP + BITSTREAM_DMA the slice NAL from the OUTPUT buffer, with ++ * the core stripping emulation prevention ++ * REGVALPAIR entropy front end: control, SPS0, PPS0, PIC0, ++ * SLICE0, SLICE1, first MB ++ * RENDEC_BLOCK x n entropy back end, scaling lists, B-slice POCs ++ * and co-located picture, reference lists, ++ * reference surfaces, prediction weights, ++ * picture geometry, intra buffer, cache policy, ++ * stride ++ * CMD_COMPLETION 0x60000000 ++ * ++ * The chunks follow each other with no pad; there is no per-picture ++ * (kind-4) chunk in the AVC path. Apple's register numbers map onto the ++ * public MSVDX layout as 0x6000 + off = VEC + off and 0x7000 + off = ++ * CMDS + off (FE_SPS0 0x200, BE_FOC0 0x250, BE_SPS0 0x2d8, ++ * BE_BASE_ADDR_CUR_PIC 0x2ec, BE_REF0 0x2f0, BE_COL_PIC0 0x2f4, ++ * BE_LIST0 0x33c, H264_WEIGHTED_FACTORS_A 0x200, VLC_TABLE_ADDR0 0xec, ++ * ENTDEC_FE/BE_CONTROL 0xc/0x10). ++ * ++ * Supported here, by apple_vxd_h264_validate() and the slice checks: ++ * progressive frames, 8-bit 4:2:0, I, P and B slices, CAVLC and CABAC, ++ * 8x8 transform and scaling matrices (High profile), explicit and implicit ++ * weighted prediction. Not supported: interlace (field pictures, MBAFF), ++ * FMO/ASO, SP/SI slices, the second deblock pass, alternate output. ++ */ ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd-h264-cmd.h" ++ ++/* Entropy decoder (Apple 0x6000 bank = MSVDX VEC). */ ++#define VXD_H264_FE_CONTROL 0x600c ++#define VXD_H264_BE_CONTROL 0x6010 ++#define VXD_H264_VLC_ADDR0 0x60ec /* + 22 */ ++#define VXD_H264_VLC_ADDR_HI 0x61c0 /* + 2, the 4:2:2 tables */ ++#define VXD_H264_FE_SPS0 0x6200 /* SPS0 PPS0 PIC0 SLICE0 SLICE1 */ ++#define VXD_H264_FE_SLICE2 0x6218 ++#define VXD_H264_BE_FOC0 0x6250 /* current, then per DPB slot */ ++#define VXD_H264_BE_SPS0 0x62d8 /* ... SLICE1, CUR_PIC, REF0 */ ++#define VXD_H264_BE_COL_PIC0 0x62f4 /* COL_PIC, LIST0_INVERSE, LIST1 */ ++#define VXD_H264_BE_L0 0x633c ++#define VXD_H264_SCALING 0xe000 ++/* Render commands (Apple 0x7000 bank = MSVDX CMDS). */ ++#define VXD_H264_CMDS_SIZE 0x7000 /* display, coded, mode, Y, UV */ ++#define VXD_H264_CMDS_INTRA 0x7018 ++#define VXD_H264_CMDS_CACHE 0x701c ++#define VXD_H264_CMDS_WEIGHT_DENOM 0x7024 ++#define VXD_H264_CMDS_STRIDE 0x7040 ++#define VXD_H264_CMDS_REFS 0x7100 /* Y, UV per DPB slot */ ++#define VXD_H264_CMDS_CHROMA 0x71b4 ++#define VXD_H264_CMDS_WEIGHTS(list) (0x7200 + 0x100 * (list)) ++ ++/* Apple's forced profile 3 translates to 0x11 on both ends (0x62c6564). */ ++#define VXD_H264_ENTDEC_CONTROL 0x11 ++#define VXD_H264_CACHE_POLICY 0x013c000c /* 0x62c4788 */ ++#define VXD_H264_WEIGHTS_DEFAULT 0x55 /* 5 / 5 */ ++#define VXD_H264_SCALING_WORDS 56 /* 2 x 8x8 + 6 x 4x4 */ ++#define VXD_H264_SCALING_FLAT 0x10 ++#define VXD_H264_FIELD_FRAME 2 /* progressive */ ++#define VXD_H264_WEIGHT_WORDS 64 /* 32 factors, 32 offsets */ ++ ++/* Slice codes of the core: I 0, P 1, B 2 (0xfffffff007a6e7b4). */ ++#define VXD_H264_CODE_I 0 ++#define VXD_H264_CODE_P 1 ++#define VXD_H264_CODE_B 2 ++ ++/* The 26 constant VLC table registers (0xfffffff005597148 [E]). */ ++static const u32 vxd_h264_vlc_addr[] = { ++ 0x00026000, 0x000738a0, 0x000828f4, 0x000a312d, 0x000b5959, ++ 0x000c517b, 0x000d1196, 0x000db1ad, 0x000e21be, 0x000e59c8, ++ 0x000e79cd, 0x000eb1d3, 0x000ed1d8, 0x000f09dd, 0x000f71e7, ++ 0x001041f6, 0x1256a4dd, 0x01489292, 0x11248050, 0x000022a2, ++ 0x00002a02, 0x8108282a, 0x00000012, ++}; ++ ++static const u32 vxd_h264_vlc_addr_hi[] = { ++ 0x0010920e, 0x0010d216, 0x0010f21c, ++}; ++ ++/* ++ * The scaling-list block (0x62c66b0 L39756-39766 of the dump): Inter Y and ++ * Intra Y 8x8, then the 4x4 lists in this order of the H.264 list index ++ * (Intra Y, Inter Y, Inter Cb, Intra Cb, Inter Cr, Intra Cr), each in ++ * raster order: AppleD5500's scaling_list() parser (0xfffffff00629c918) ++ * stores list[zigzag[i]], and its defaults (0xfffffff005596170) are raster ++ * tables, which is also V4L2's order. ++ */ ++static const u8 vxd_h264_scaling_4x4_order[6] = { 0, 3, 4, 1, 5, 2 }; ++ ++/* Raster position of the i-th coefficient in frame zig-zag scan. */ ++static const u8 vxd_h264_zigzag4[16] = { ++ 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15, ++}; ++ ++static const u8 vxd_h264_zigzag8[64] = { ++ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, ++ 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, ++ 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, ++ 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, ++}; ++ ++/* One raster list of @n x @n in the requested order. */ ++static void vxd_h264_scaling_list(u8 *dst, const u8 *raster, unsigned int n, ++ enum apple_vxd_h264_scaling_order order) ++{ ++ const u8 *zz = n == 4 ? vxd_h264_zigzag4 : vxd_h264_zigzag8; ++ unsigned int i; ++ ++ for (i = 0; i < n * n; i++) { ++ switch (order) { ++ case APPLE_VXD_H264_SCALING_ZIGZAG: ++ dst[i] = raster[zz[i]]; ++ break; ++ case APPLE_VXD_H264_SCALING_TRANSPOSED: ++ dst[i] = raster[(i % n) * n + i / n]; ++ break; ++ default: ++ dst[i] = raster[i]; ++ break; ++ } ++ } ++} ++ ++static int vxd_h264_slice_code(u8 slice_type) ++{ ++ switch (slice_type) { ++ case V4L2_H264_SLICE_TYPE_I: ++ return VXD_H264_CODE_I; ++ case V4L2_H264_SLICE_TYPE_P: ++ return VXD_H264_CODE_P; ++ case V4L2_H264_SLICE_TYPE_B: ++ return VXD_H264_CODE_B; ++ default: ++ return -EINVAL; ++ } ++} ++ ++u32 apple_vxd_h264_colocated_size(unsigned int width_mbs, ++ unsigned int height_mbs) ++{ ++ /* (W * H * 0x80 + 0x41ff) & ~0xfff, 0xfffffff0062c3b8c [E] */ ++ return (width_mbs * height_mbs * 0x80 + 0x41ff) & ~0xfffU; ++} ++ ++static bool vxd_h264_has_scaling(const struct v4l2_ctrl_h264_pps *pps) ++{ ++ return pps->flags & V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT; ++} ++ ++/* A scaling factor is 1..255: a zero list means the control was never set. */ ++static bool vxd_h264_scaling_set(const struct v4l2_ctrl_h264_pps *pps, ++ const struct v4l2_ctrl_h264_scaling_matrix *m) ++{ ++ unsigned int i, j; ++ ++ if (!m) ++ return false; ++ for (i = 0; i < 6; i++) ++ for (j = 0; j < 16; j++) ++ if (!m->scaling_list_4x4[i][j]) ++ return false; ++ if (!(pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE)) ++ return true; ++ for (i = 0; i < 2; i++) ++ for (j = 0; j < 64; j++) ++ if (!m->scaling_list_8x8[i][j]) ++ return false; ++ return true; ++} ++ ++int apple_vxd_h264_validate(const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_pps *pps, ++ const struct v4l2_ctrl_h264_scaling_matrix *scaling, ++ const char **why) ++{ ++ if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)) ++ *why = "interlaced coding (frame_mbs_only_flag = 0)"; ++ else if (sps->flags & V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE) ++ *why = "separate colour planes"; ++ else if (V4L2_H264_SPS_HAS_CHROMA_FORMAT(sps) && sps->chroma_format_idc != 1) ++ *why = "chroma format other than 4:2:0"; ++ else if (sps->bit_depth_luma_minus8 || sps->bit_depth_chroma_minus8) ++ *why = "bit depth above 8"; ++ else if (sps->flags & V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS) ++ *why = "lossless transform bypass"; ++ else if (pps->num_slice_groups_minus1) ++ *why = "slice groups (FMO)"; ++ else if (pps->weighted_bipred_idc > 2) ++ *why = "weighted_bipred_idc out of range"; ++ else if (vxd_h264_has_scaling(pps) && !vxd_h264_scaling_set(pps, scaling)) ++ *why = "scaling matrices signalled but the SCALING_MATRIX control is not set"; ++ else ++ return 0; ++ return -EINVAL; ++} ++ ++/* Bits of the slice NAL before slice_data(), escaped, NAL header included. */ ++static u32 vxd_h264_header_bits(const struct v4l2_ctrl_h264_pps *pps, ++ const struct apple_vxd_h264_slice *sl) ++{ ++ u32 bits = 8 + sl->header_bits_raw; ++ ++ /* CABAC slice data starts after cabac_alignment_one_bit padding. */ ++ if (pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE) ++ bits = ALIGN(bits, 8); ++ return bits; ++} ++ ++/* POC registers: 17 bits, sign in bit 16 (0x62c66b0 L39779-39792). */ ++static u32 vxd_h264_poc(s32 poc) ++{ ++ return ((u32)poc >> 15 & 0x10000) | ((u32)poc & 0xffff); ++} ++ ++/* Explicit weights in the bitstream (the kext's 0x62be348 condition). */ ++static bool vxd_h264_explicit_weights(const struct v4l2_ctrl_h264_pps *pps, ++ int code) ++{ ++ return ((pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED) && ++ code == VXD_H264_CODE_P) || ++ (pps->weighted_bipred_idc == 1 && code == VXD_H264_CODE_B); ++} ++ ++/* The weight tables go out for explicit and implicit modes (L40086). */ ++static bool vxd_h264_weight_tables(const struct v4l2_ctrl_h264_pps *pps, ++ int code) ++{ ++ return ((pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED) && ++ code == VXD_H264_CODE_P) || ++ (pps->weighted_bipred_idc && code == VXD_H264_CODE_B); ++} ++ ++static void vxd_h264_scaling_words(const struct apple_vxd_h264_frame *f, ++ u32 *w) ++{ ++ const struct v4l2_ctrl_h264_scaling_matrix *m = f->scaling; ++ u8 bytes[4 * VXD_H264_SCALING_WORDS]; ++ unsigned int i; ++ ++ memset(bytes, VXD_H264_SCALING_FLAT, sizeof(bytes)); ++ if (vxd_h264_has_scaling(f->pps) && m) { ++ /* 8x8 lists only with the 8x8 transform (0x62be538 L33469). */ ++ if (f->pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE) { ++ vxd_h264_scaling_list(&bytes[0], m->scaling_list_8x8[1], 8, ++ f->scaling_order); ++ vxd_h264_scaling_list(&bytes[64], m->scaling_list_8x8[0], 8, ++ f->scaling_order); ++ } ++ for (i = 0; i < 6; i++) ++ vxd_h264_scaling_list(&bytes[128 + 16 * i], ++ m->scaling_list_4x4[vxd_h264_scaling_4x4_order[i]], ++ 4, f->scaling_order); ++ } ++ for (i = 0; i < VXD_H264_SCALING_WORDS; i++) ++ w[i] = bytes[4 * i] | bytes[4 * i + 1] << 8 | ++ bytes[4 * i + 2] << 16 | (u32)bytes[4 * i + 3] << 24; ++} ++ ++/* @n bytes of a reference list, four per word, low byte first. */ ++static unsigned int vxd_h264_pack_list(const u8 *list, unsigned int n, u32 *w) ++{ ++ unsigned int i, words = DIV_ROUND_UP(n, 4); ++ ++ for (i = 0; i < words; i++) ++ w[i] = 0; ++ for (i = 0; i < 4 * words; i++) ++ w[i / 4] |= (u32)list[i] << (8 * (i % 4)); ++ return words; ++} ++ ++static bool vxd_h264_dpb_active(const struct v4l2_h264_dpb_entry *e) ++{ ++ return (e->flags & V4L2_H264_DPB_ENTRY_FLAG_VALID) && ++ (e->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE); ++} ++ ++/* ++ * B slices: the picture order counts (BE_FOC0/1, then TOP/BOT_FOC per ++ * hardware DPB slot) and the co-located picture block (BE_COL_PIC0, ++ * BE_BASE_ADDR_COL_PIC, BE_LIST0_INVERSE x 8, BE_LIST1), 0x62c66b0 ++ * L39776-39887. The co-located picture is RefPicList1[0]. ++ */ ++static void vxd_h264_emit_b(struct apple_vxd_cmdbuf *cb, ++ const struct apple_vxd_h264_frame *f, ++ const u8 *l0, const u8 *l1, u8 l1_minus1, ++ unsigned int highwater, u32 *w) ++{ ++ const struct v4l2_ctrl_h264_decode_params *dp = f->dp; ++ u8 inverse[V4L2_H264_REF_LIST_LEN]; ++ unsigned int i, n; ++ u8 col = l1[0]; ++ ++ w[0] = vxd_h264_poc(dp->top_field_order_cnt); ++ w[1] = vxd_h264_poc(dp->bottom_field_order_cnt); ++ for (i = 0; i < highwater; i++) { ++ const struct v4l2_h264_dpb_entry *e = &dp->dpb[i]; ++ bool active = vxd_h264_dpb_active(e); ++ ++ w[2 + 2 * i] = active ? vxd_h264_poc(e->top_field_order_cnt) : 0; ++ w[3 + 2 * i] = active ? vxd_h264_poc(e->bottom_field_order_cnt) : 0; ++ } ++ apple_vxd_cmd_block(cb, VXD_H264_BE_FOC0, 0, w, 2 + 2 * highwater); ++ ++ if (col < V4L2_H264_NUM_DPB_ENTRIES && f->dpb_colocated[col]) { ++ const struct v4l2_h264_dpb_entry *e = &dp->dpb[col]; ++ s32 cur = min(dp->top_field_order_cnt, dp->bottom_field_order_cnt); ++ s64 dtop = abs((s64)cur - e->top_field_order_cnt); ++ s64 dbot = abs((s64)cur - e->bottom_field_order_cnt); ++ ++ /* ++ * Frame co-located picture: COL_NOTFRAME and COL_MBAFF clear, ++ * bottom field flag by POC distance, as psb_video and the kext. ++ */ ++ w[0] = dbot <= dtop; ++ w[1] = f->dpb_colocated[col]; ++ } else { ++ w[0] = APPLE_VXD_H264_UNUSED_VA; ++ w[1] = APPLE_VXD_H264_UNUSED_VA; ++ } ++ ++ /* Inverse L0: first position of each slot (bottom fields at +16). */ ++ memset(inverse, APPLE_VXD_H264_NO_REF, sizeof(inverse)); ++ for (i = V4L2_H264_REF_LIST_LEN; i--; ) ++ if (l0[i] != APPLE_VXD_H264_NO_REF) ++ inverse[(l0[i] >> 3 & 0x10) | (l0[i] & 0x1f)] = i; ++ vxd_h264_pack_list(inverse, V4L2_H264_REF_LIST_LEN, &w[2]); ++ n = vxd_h264_pack_list(l1, l1_minus1 + 1, &w[10]); ++ apple_vxd_cmd_block(cb, VXD_H264_BE_COL_PIC0, 0, w, 10 + n); ++} ++ ++/* ++ * H264_WEIGHTED_FACTORS/OFFSET A (list 0) and B (list 1), 32 entries each ++ * (0x62c66b0 L40086-40134, field layout as psb_video's msvdx_cmds_io2.h). ++ * Implicit bi-prediction leaves the kext's tables unrefreshed (zero in a ++ * fresh context); the core derives those weights from the POCs. ++ */ ++static void vxd_h264_emit_weights(struct apple_vxd_cmdbuf *cb, ++ const struct apple_vxd_h264_frame *f, ++ const struct apple_vxd_h264_slice *sl, ++ const u8 *ref_slots, unsigned int list, ++ u8 n_minus1, int code, u32 *w) ++{ ++ bool expl = vxd_h264_explicit_weights(f->pps, code); ++ unsigned int i; ++ ++ memset(w, 0, VXD_H264_WEIGHT_WORDS * sizeof(*w)); ++ for (i = 0; expl && i <= n_minus1; i++) { ++ const struct apple_vxd_h264_weight *wt = &sl->weights[list][i]; ++ ++ if (ref_slots[i] == APPLE_VXD_H264_NO_REF) ++ continue; ++ w[i] = ((u32)wt->weight[2] & 0x1ff) << 18 | ++ ((u32)wt->weight[1] & 0x1ff) << 9 | ++ ((u32)wt->weight[0] & 0x1ff); ++ w[32 + i] = (u32)(u8)wt->offset[2] << 16 | ++ (u32)(u8)wt->offset[1] << 8 | (u8)wt->offset[0]; ++ } ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_WEIGHTS(list), 0, w, ++ VXD_H264_WEIGHT_WORDS); ++} ++ ++static void vxd_h264_emit_slice(struct apple_vxd_cmdbuf *cb, ++ const struct apple_vxd_h264_frame *f, ++ unsigned int idx, int code) ++{ ++ const struct v4l2_ctrl_h264_sps *sps = f->sps; ++ const struct v4l2_ctrl_h264_pps *pps = f->pps; ++ const struct apple_vxd_h264_slice *sl = &f->slices[idx]; ++ const u8 *l0 = f->ref_slots[idx][0]; ++ const u8 *l1 = f->ref_slots[idx][1]; ++ u32 w = sps->pic_width_in_mbs_minus1 + 1; ++ u32 h = sps->pic_height_in_map_units_minus1 + 1; ++ u32 mb_x = sl->first_mb_in_slice % w, mb_y = sl->first_mb_in_slice / w; ++ u32 chroma = V4L2_H264_SPS_HAS_CHROMA_FORMAT(sps) ? sps->chroma_format_idc : 1; ++ u32 wmode = !!(pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED) | ++ pps->weighted_bipred_idc << 1; ++ u32 hdr[VXD_CMD_SLICE_BLOCK_WORDS - 1] = { }; ++ u32 sps0, pps0, pic0, slice0, slice1, skip, bits, longterm = 0; ++ unsigned int i, n, highwater = 0; ++ bool min_bipred8, used[V4L2_H264_NUM_DPB_ENTRIES] = { }; ++ /* ++ * I slices zero both counts; P slices keep the parsed L1 count (the ++ * PPS default, 0x62a0058) as the kext does (0x62c5fb8 L39477-39483). ++ */ ++ u8 l0_minus1 = code ? sl->num_ref_idx_l0_active_minus1 : 0; ++ u8 l1_minus1 = code ? sl->num_ref_idx_l1_active_minus1 : 0; ++ /* Scratch for one block at a time: the largest is the weight table. */ ++ u32 buf[VXD_H264_WEIGHT_WORDS]; ++ ++ min_bipred8 = w > 80 && sps->level_idc > 30; ++ sps0 = ((chroma >> 1) & 1) << 28 | sps->bit_depth_chroma_minus8 << 25 | ++ sps->bit_depth_luma_minus8 << 22 | 1 << 14 | 3 << 12 | ++ min_bipred8 << 11 | ++ !!(sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE) << 10 | ++ (chroma & 1) << 9 | ++ !!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 8 | (w - 1); ++ pps0 = ((u8)pps->second_chroma_qp_index_offset & 31) << 24 | ++ ((u8)pps->chroma_qp_index_offset & 31) << 16 | ++ pps->weighted_bipred_idc << 8 | ++ pps->num_slice_groups_minus1 << 4 | ++ !!(pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE) << 2 | ++ !!(pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED) << 1 | ++ !!(pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE); ++ pic0 = (w * h - 1) << 16 | (h - 1) << 8 | !!f->dp->nal_ref_idc << 3; ++ slice0 = sl->direct_spatial_mv_pred << 31 | ++ sl->disable_deblocking_filter_idc << 28 | ++ ((u8)sl->slice_alpha_c0_offset_div2 & 15) << 24 | ++ ((u8)sl->slice_beta_offset_div2 & 15) << 20 | ++ VXD_H264_FIELD_FRAME << 18 | code << 16 | ++ sl->cabac_init_idc << 14 | (idx & 0x3fff); ++ slice1 = mb_y << 24 | mb_x << 16 | ++ ((26 + pps->pic_init_qp_minus26 + sl->slice_qp_delta) & 63) << 10 | ++ (l1_minus1 & 31) << 5 | (l0_minus1 & 31); ++ ++ /* CTRL_ALLOC_HEADER; MODE_CONFIG = weighted_pred | bipred_idc << 1. */ ++ hdr[0] = !!(pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED) << 23 | ++ wmode << 17 | ++ sl->disable_deblocking_filter_idc << 12 | ++ ((u8)sl->slice_alpha_c0_offset_div2 & 15) << 8 | ++ ((u8)sl->slice_beta_offset_div2 & 15) << 4 | ++ VXD_H264_FIELD_FRAME << 2 | code; ++ hdr[2] = f->dst_stride | f->dst_stride << 16; ++ hdr[3] = mb_x | mb_y << 16; ++ hdr[4] = (w - 1) | (h - 1) << 16; ++ hdr[8] = sps->bit_depth_chroma_minus8 << 12 | ++ sps->bit_depth_luma_minus8 << 8 | chroma; ++ apple_vxd_cmd_ctrl_alloc(cb, hdr); ++ ++ /* CAVLC table + its address registers, every slice. */ ++ apple_vxd_cmd_dma(cb, VXD_CMD_DMA_TYPE_VLC, APPLE_VXD_H264_VLC_BYTES, ++ f->bufs.cavlc); ++ apple_vxd_cmd_group_open(cb, 0); ++ for (i = 0; i < ARRAY_SIZE(vxd_h264_vlc_addr); i++) ++ apple_vxd_cmd_reg(cb, VXD_H264_VLC_ADDR0 + 4 * i, ++ vxd_h264_vlc_addr[i]); ++ for (i = 0; i < ARRAY_SIZE(vxd_h264_vlc_addr_hi); i++) ++ apple_vxd_cmd_reg(cb, VXD_H264_VLC_ADDR_HI + 4 * i, ++ vxd_h264_vlc_addr_hi[i]); ++ apple_vxd_cmd_group_close(cb); ++ ++ /* ++ * The slice NAL from its header byte: whole 32-bit words of header go ++ * by DMA offset, the rest by the SR bit offset (0x62c5b18 [E]). ++ */ ++ bits = vxd_h264_header_bits(pps, sl); ++ skip = (bits & 0xffe0) >> 3; ++ apple_vxd_cmd_slice_desc(cb, bits & 31, sl->nal_size - skip); ++ apple_vxd_cmd_load(cb, f->src_va + sl->nal_offset + skip, ++ sl->nal_size - skip); ++ ++ /* Entropy front end. */ ++ apple_vxd_cmd_group_open(cb, 0); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_CONTROL, VXD_H264_ENTDEC_CONTROL); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SPS0, sps0); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SPS0 + 4, pps0); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SPS0 + 8, pic0); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SPS0 + 12, slice0); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SPS0 + 16, slice1); ++ apple_vxd_cmd_reg(cb, VXD_H264_FE_SLICE2, sl->first_mb_in_slice << 16); ++ apple_vxd_cmd_group_close(cb); ++ ++ /* Entropy back end. */ ++ buf[0] = VXD_H264_ENTDEC_CONTROL; ++ apple_vxd_cmd_block(cb, VXD_H264_BE_CONTROL, 0, buf, 1); ++ ++ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { ++ const struct v4l2_h264_dpb_entry *e = &f->dp->dpb[i]; ++ ++ if (!vxd_h264_dpb_active(e)) ++ continue; ++ highwater = i + 1; ++ if (e->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) ++ longterm |= BIT(i); ++ } ++ buf[0] = sps0; ++ buf[1] = pps0; ++ buf[2] = pic0; ++ buf[3] = slice0; ++ buf[4] = slice1; ++ buf[5] = f->colocated; ++ buf[6] = longterm; ++ apple_vxd_cmd_block(cb, VXD_H264_BE_SPS0, 0, buf, 7); ++ ++ vxd_h264_scaling_words(f, buf); ++ apple_vxd_cmd_block(cb, VXD_H264_SCALING, 0, buf, ++ VXD_H264_SCALING_WORDS); ++ ++ if (code == VXD_H264_CODE_B) ++ vxd_h264_emit_b(cb, f, l0, l1, l1_minus1, highwater, buf); ++ ++ if (code) { ++ n = vxd_h264_pack_list(l0, l0_minus1 + 1, buf); ++ apple_vxd_cmd_block(cb, VXD_H264_BE_L0, 0, buf, n); ++ } ++ ++ /* Surfaces only for the references this slice's lists use. */ ++ for (i = 0; code && i <= l0_minus1; i++) ++ if (l0[i] < V4L2_H264_NUM_DPB_ENTRIES) ++ used[l0[i]] = true; ++ for (i = 0; code == VXD_H264_CODE_B && i <= l1_minus1; i++) ++ if (l1[i] < V4L2_H264_NUM_DPB_ENTRIES) ++ used[l1[i]] = true; ++ if (highwater) { ++ for (i = 0; i < highwater; i++) { ++ bool real = used[i] && f->dpb_luma[i]; ++ ++ buf[2 * i] = real ? f->dpb_luma[i] : APPLE_VXD_H264_UNUSED_VA; ++ buf[2 * i + 1] = real ? f->dpb_chroma[i] : ++ APPLE_VXD_H264_UNUSED_VA; ++ } ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_REFS, VXD_CMD_BLOCK_FLAG_REFS, ++ buf, 2 * highwater); ++ } ++ ++ if (vxd_h264_weight_tables(pps, code)) { ++ vxd_h264_emit_weights(cb, f, sl, l0, 0, l0_minus1, code, buf); ++ if (code == VXD_H264_CODE_B) ++ vxd_h264_emit_weights(cb, f, sl, l1, 1, l1_minus1, code, ++ buf); ++ } ++ ++ /* Picture geometry, operating mode, output. */ ++ buf[0] = (16 * h - 1) << 12 | (16 * w - 1); ++ buf[1] = buf[0]; ++ buf[2] = APPLE_VXD_H264_OPMODE_BASE | 2 << 20 | (chroma != 0) << 12; ++ buf[3] = f->dst_luma; ++ buf[4] = f->dst_chroma; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_SIZE, 0, buf, 5); ++ buf[0] = f->bufs.intra; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_INTRA, 0, buf, 1); ++ /* Denominators: the slice's when explicit, 5 / 5 otherwise. */ ++ buf[0] = vxd_h264_explicit_weights(pps, code) ? ++ sl->chroma_log2_weight_denom << 4 | sl->luma_log2_weight_denom : ++ VXD_H264_WEIGHTS_DEFAULT; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_WEIGHT_DENOM, 0, buf, 1); ++ buf[0] = VXD_H264_CACHE_POLICY; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_CACHE, 0, buf, 1); ++ buf[0] = f->dst_stride & ~63U; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_STRIDE, 0, buf, 1); ++ buf[0] = chroma; ++ apple_vxd_cmd_block(cb, VXD_H264_CMDS_CHROMA, 0, buf, 1); ++ ++ apple_vxd_cmd_end_slice(cb); ++} ++ ++/* ++ * One picture: per slice, the chunk and its kind-1 packet (payload as ++ * 0x62c4c98 fills it, L39392-39429 of the dump). Returns 0, or -EINVAL / ++ * -ENOSPC with @why set; the buffer is then unusable for this picture. ++ */ ++int apple_vxd_h264_build(struct apple_vxd_cmdbuf *cb, ++ const struct apple_vxd_h264_frame *f, ++ const char **why) ++{ ++ u32 opmode = APPLE_VXD_H264_OPMODE_BASE | 2 << 20 | 1 << 12; ++ unsigned int i; ++ int ret; ++ ++ *why = NULL; ++ ret = apple_vxd_h264_validate(f->sps, f->pps, f->scaling, why); ++ if (ret) ++ return ret; ++ if (!f->nslices) { ++ *why = "no slices"; ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < f->nslices; i++) { ++ const struct apple_vxd_h264_slice *sl = &f->slices[i]; ++ int code = vxd_h264_slice_code(sl->slice_type); ++ u32 off, bytes, *p; ++ ++ if (code < 0) { ++ *why = "SP/SI slices are not supported"; ++ return -EINVAL; ++ } ++ if (vxd_h264_header_bits(f->pps, sl) >= 0x10000 || ++ (vxd_h264_header_bits(f->pps, sl) & 0xffe0) >> 3 >= sl->nal_size) { ++ *why = "slice header longer than the NAL"; ++ return -EINVAL; ++ } ++ ++ vxd_h264_emit_slice(cb, f, i, code); ++ ret = apple_vxd_cmdbuf_chunk_end_exact(cb, &off, &bytes); ++ if (ret) { ++ *why = "command buffer full or misused"; ++ return ret; ++ } ++ ++ p = apple_vxd_cmdbuf_add_packet(cb, VXD_PKT_MAGIC0, ++ VXD_PKT_H264_PAYLOAD); ++ if (!p) { ++ *why = "packet list full"; ++ return -ENOSPC; ++ } ++ p[VXD_PKT_FLAGS / 4] = (i ? 0 : 1) | ++ (!i && f->first_picture ? 0x10 : 0); ++ p[VXD_PKT_W4 / 4] = opmode; ++ p[VXD_PKT_CMD_BYTES / 4] = bytes; ++ p[VXD_PKT_W2 / 4] = cb->va + off; ++ p[VXD_PKT_W5 / 4] = f->bufs.stream_state; ++ p[VXD_PKT_KIND / 4] = VXD_PKT_KIND_PICTURE; ++ p[VXD_PKT_NOT_REF / 4] = !f->dp->nal_ref_idc; ++ } ++ return 0; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.h b/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.h +new file mode 100644 +index 0000000000000000000000000000000000000000..0898953ffbb0af311fa630d057fc8b9b068c0fb1 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264-cmd.h +@@ -0,0 +1,83 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apple VXD - H.264 (AVC) command-stream builder (R3c) ++ * ++ * Pure function of its inputs: no allocation, no MMIO, no V4L2 core, so ++ * it builds unchanged in the host harness (scripts/vdec/host/) and is ++ * diffed word for word against tools/ds-video/cmdstream.py --gen. ++ */ ++#ifndef __APPLE_VXD_H264_CMD_H__ ++#define __APPLE_VXD_H264_CMD_H__ ++ ++#include ++#include ++ ++#include "apple-vxd-cmd.h" ++#include "apple-vxd-h264-parse.h" ++ ++/* Hardware DPB slot of a reference list entry; slot == V4L2 dpb[] index. */ ++#define APPLE_VXD_H264_NO_REF 0xff ++#define APPLE_VXD_H264_UNUSED_VA 0xdeadbeef ++ ++/* The CAVLC table travels by CMD_DMA, 0x440 bytes, every slice. */ ++#define APPLE_VXD_H264_VLC_BYTES 0x440 ++ ++/* Operating mode for 8-bit 4:2:0 frames (CMDS 0x7008, payload +0x08). */ ++#define APPLE_VXD_H264_OPMODE_BASE 0x10010000 ++ ++/* ++ * Element order of the scaling lists in the 0xe000 block. The kext ++ * uploads raster order (its parser stores list[zigzag[j]]); the others are ++ * a device-side check of that reading (module parameter scaling_order). ++ */ ++enum apple_vxd_h264_scaling_order { ++ APPLE_VXD_H264_SCALING_RASTER = 0, ++ APPLE_VXD_H264_SCALING_ZIGZAG, ++ APPLE_VXD_H264_SCALING_TRANSPOSED, ++}; ++ ++/* Device VAs of the decode context's own buffers (DecodeCreateH264VLD). */ ++struct apple_vxd_h264_bufs { ++ u32 cavlc; /* 0x1000, the 0x440-byte VLC table */ ++ u32 intra; /* IntraBuffer */ ++ u32 stream_state; /* StreamStateBuffer -> message w5 */ ++}; ++ ++struct apple_vxd_h264_frame { ++ const struct v4l2_ctrl_h264_sps *sps; ++ const struct v4l2_ctrl_h264_pps *pps; ++ const struct v4l2_ctrl_h264_decode_params *dp; ++ /* ++ * Used when the PPS has V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT; ++ * flat (16) lists otherwise. ++ */ ++ const struct v4l2_ctrl_h264_scaling_matrix *scaling; ++ const struct apple_vxd_h264_slice *slices; ++ unsigned int nslices; ++ /* Per slice: final RefPicList0/1 as DPB slots, NO_REF past the end. */ ++ const u8 (*ref_slots)[2][V4L2_H264_REF_LIST_LEN]; ++ ++ u32 src_va; /* OUTPUT buffer, byte 0 */ ++ u32 dst_luma, dst_chroma; /* CAPTURE planes */ ++ u32 dst_stride; /* bytes, luma and chroma (NV12) */ ++ u32 colocated; /* CAPTURE's stored-MB parameter buffer */ ++ u32 dpb_luma[V4L2_H264_NUM_DPB_ENTRIES]; /* 0: not resolvable */ ++ u32 dpb_chroma[V4L2_H264_NUM_DPB_ENTRIES]; ++ /* Each reference's stored-MB buffer: the B co-located picture's. */ ++ u32 dpb_colocated[V4L2_H264_NUM_DPB_ENTRIES]; ++ struct apple_vxd_h264_bufs bufs; ++ bool first_picture; /* first picture of the context */ ++ enum apple_vxd_h264_scaling_order scaling_order; ++}; ++ ++int apple_vxd_h264_validate(const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_pps *pps, ++ const struct v4l2_ctrl_h264_scaling_matrix *scaling, ++ const char **why); ++int apple_vxd_h264_build(struct apple_vxd_cmdbuf *cb, ++ const struct apple_vxd_h264_frame *f, ++ const char **why); ++u32 apple_vxd_h264_colocated_size(unsigned int width_mbs, ++ unsigned int height_mbs); ++ ++#endif /* __APPLE_VXD_H264_CMD_H__ */ +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.c b/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.c +new file mode 100644 +index 0000000000000000000000000000000000000000..b091235c47e0fac74f48e8d9c28bea09478ad040 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.c +@@ -0,0 +1,544 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - H.264 slice finder and slice-header parser (R3c) ++ * ++ * Frame-based decode with Annex-B start codes: the OUTPUT buffer carries ++ * the whole access unit and V4L2_CID_STATELESS_H264_SLICE_PARAMS is not ++ * used (uapi: "When this mode is selected, ... SLICE_PARAMS is not ++ * used"). The MSVDX command stream still needs per-slice values: where ++ * the slice bytes are (the 0xa load), how many header bits precede the ++ * slice data (the 0xb descriptor), the slice type, first MB, QP and ++ * deblocking parameters, and the reference-list modifications. Apple's ++ * kext gets them from its own NAL walk; so does this file. ++ * ++ * Scope: progressive frames (frame_mbs_only), one slice group (no FMO), ++ * no separate colour planes, no data partitioning. Everything else of ++ * clause 7.3.3 is parsed so the header length is exact. ++ */ ++#include ++#include ++#include ++ ++#include "apple-vxd-h264-parse.h" ++ ++struct vxd_bits { ++ const u8 *buf; /* escaped bytes after the NAL header */ ++ size_t len; ++ size_t byte; /* raw index of the current byte */ ++ unsigned int bit; /* bits of buf[byte] already read */ ++ unsigned int zeros; /* zero bytes right before buf[byte] */ ++ u32 rbsp_bits; /* bits read, emulation bytes excluded */ ++ bool overrun; ++}; ++ ++static unsigned int vxd_bit(struct vxd_bits *b) ++{ ++ unsigned int v; ++ ++ if (!b->bit && b->zeros >= 2 && b->byte < b->len && ++ b->buf[b->byte] == 0x03) { ++ /* emulation_prevention_three_byte */ ++ b->byte++; ++ b->zeros = 0; ++ } ++ if (b->byte >= b->len) { ++ b->overrun = true; ++ return 0; ++ } ++ ++ v = (b->buf[b->byte] >> (7 - b->bit)) & 1; ++ b->rbsp_bits++; ++ if (++b->bit == 8) { ++ b->zeros = b->buf[b->byte] ? 0 : b->zeros + 1; ++ b->byte++; ++ b->bit = 0; ++ } ++ return v; ++} ++ ++static u32 vxd_bits_u(struct vxd_bits *b, unsigned int n) ++{ ++ u32 v = 0; ++ ++ while (n--) ++ v = (v << 1) | vxd_bit(b); ++ return v; ++} ++ ++static u32 vxd_bits_ue(struct vxd_bits *b) ++{ ++ unsigned int zeros = 0; ++ ++ while (!vxd_bit(b)) { ++ if (b->overrun || ++zeros > 31) { ++ b->overrun = true; ++ return 0; ++ } ++ } ++ if (!zeros) ++ return 0; ++ return (u32)((1ULL << zeros) - 1) + vxd_bits_u(b, zeros); ++} ++ ++static s32 vxd_bits_se(struct vxd_bits *b) ++{ ++ u32 k = vxd_bits_ue(b); ++ ++ return (k & 1) ? (s32)((k + 1) / 2) : -(s32)(k / 2); ++} ++ ++static bool vxd_slice_is(u8 slice_type, u8 want) ++{ ++ return slice_type == want; ++} ++ ++/* ChromaArrayType; profiles without chroma_format_idc in the SPS imply 4:2:0. */ ++static unsigned int vxd_chroma_array_type(const struct v4l2_ctrl_h264_sps *sps) ++{ ++ if (sps->flags & V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE) ++ return 0; ++ if (!V4L2_H264_SPS_HAS_CHROMA_FORMAT(sps)) ++ return 1; ++ return sps->chroma_format_idc; ++} ++ ++static int vxd_parse_mods(struct vxd_bits *b, struct apple_vxd_h264_slice *sl, ++ unsigned int list) ++{ ++ unsigned int n = 0; ++ u32 idc; ++ ++ if (!vxd_bit(b)) /* ref_pic_list_modification_flag_lX */ ++ return 0; ++ ++ do { ++ idc = vxd_bits_ue(b); ++ if (idc > 3 || n >= ARRAY_SIZE(sl->mods[list]) || b->overrun) ++ return -EINVAL; ++ sl->mods[list][n].idc = idc; ++ sl->mods[list][n].val = idc < 3 ? vxd_bits_ue(b) : 0; ++ if (idc == 3) ++ break; ++ n++; ++ } while (1); ++ sl->num_mods[list] = n; ++ return 0; ++} ++ ++/* luma/chroma_weight and _offset: se(v) in -128..127 (7.4.3.2, 8 bits). */ ++static bool vxd_weight_ok(s32 v) ++{ ++ return v >= -128 && v <= 127; ++} ++ ++/* ++ * Clause 7.3.3.2, with the inferred weights and offsets filled in. The ++ * command stream carries the table for every active reference. ++ */ ++static int vxd_parse_pred_weight_table(struct vxd_bits *b, ++ const struct v4l2_ctrl_h264_sps *sps, ++ struct apple_vxd_h264_slice *sl) ++{ ++ unsigned int chroma = vxd_chroma_array_type(sps); ++ unsigned int list, i, c, n; ++ u32 v; ++ ++ v = vxd_bits_ue(b); ++ if (v > 7) ++ return -EINVAL; ++ sl->luma_log2_weight_denom = v; ++ if (chroma) { ++ v = vxd_bits_ue(b); ++ if (v > 7) ++ return -EINVAL; ++ sl->chroma_log2_weight_denom = v; ++ } ++ ++ for (list = 0; list < 2; list++) { ++ if (list && !vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_B)) ++ break; ++ n = list ? sl->num_ref_idx_l1_active_minus1 : ++ sl->num_ref_idx_l0_active_minus1; ++ for (i = 0; i <= n && !b->overrun; i++) { ++ struct apple_vxd_h264_weight *w = &sl->weights[list][i]; ++ s32 wt, off; ++ ++ w->weight[0] = 1 << sl->luma_log2_weight_denom; ++ if (vxd_bit(b)) { /* luma_weight_lX_flag */ ++ wt = vxd_bits_se(b); ++ off = vxd_bits_se(b); ++ if (!vxd_weight_ok(wt) || !vxd_weight_ok(off)) ++ return -EINVAL; ++ w->weight[0] = wt; ++ w->offset[0] = off; ++ } ++ if (!chroma) ++ continue; ++ for (c = 1; c < 3; c++) ++ w->weight[c] = 1 << sl->chroma_log2_weight_denom; ++ if (!vxd_bit(b)) /* chroma_weight_lX_flag */ ++ continue; ++ for (c = 1; c < 3; c++) { ++ wt = vxd_bits_se(b); ++ off = vxd_bits_se(b); ++ if (!vxd_weight_ok(wt) || !vxd_weight_ok(off)) ++ return -EINVAL; ++ w->weight[c] = wt; ++ w->offset[c] = off; ++ } ++ } ++ } ++ return 0; ++} ++ ++static int vxd_skip_dec_ref_pic_marking(struct vxd_bits *b, ++ struct apple_vxd_h264_slice *sl) ++{ ++ unsigned int n = 0; ++ u32 mmco; ++ ++ if (sl->nal_unit_type == APPLE_VXD_H264_NAL_IDR) { ++ sl->no_output_of_prior_pics = vxd_bit(b); ++ sl->long_term_reference = vxd_bit(b); ++ return 0; ++ } ++ ++ sl->adaptive_ref_pic_marking = vxd_bit(b); ++ if (!sl->adaptive_ref_pic_marking) ++ return 0; ++ ++ do { ++ mmco = vxd_bits_ue(b); ++ if (mmco > 6 || ++n > 66 || b->overrun) ++ return -EINVAL; ++ if (mmco == 1 || mmco == 3) ++ vxd_bits_ue(b); /* difference_of_pic_nums_minus1 */ ++ if (mmco == 2) ++ vxd_bits_ue(b); /* long_term_pic_num */ ++ if (mmco == 3 || mmco == 6) ++ vxd_bits_ue(b); /* long_term_frame_idx */ ++ if (mmco == 4) ++ vxd_bits_ue(b); /* max_long_term_frame_idx_plus1 */ ++ } while (mmco); ++ return 0; ++} ++ ++/* Clause 7.3.3, for one NAL whose header byte is @nal[0]. */ ++static int vxd_parse_slice_header(const u8 *nal, size_t len, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_pps *pps, ++ struct apple_vxd_h264_slice *sl, ++ const char **why) ++{ ++ bool p, b_slice, sp, si, i_slice; ++ struct vxd_bits b = { ++ .buf = nal + 1, ++ .len = len - 1, ++ }; ++ u32 v; ++ ++ sl->nal_ref_idc = (nal[0] >> 5) & 3; ++ sl->nal_unit_type = nal[0] & 0x1f; ++ ++ sl->first_mb_in_slice = vxd_bits_ue(&b); ++ v = vxd_bits_ue(&b); ++ if (v > 9) { ++ *why = "slice_type out of range"; ++ return -EINVAL; ++ } ++ sl->slice_type = v % 5; ++ v = vxd_bits_ue(&b); ++ if (v != pps->pic_parameter_set_id) { ++ *why = "slice references another PPS"; ++ return -EINVAL; ++ } ++ sl->pps_id = v; ++ ++ p = vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_P); ++ b_slice = vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_B); ++ sp = vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_SP); ++ si = vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_SI); ++ i_slice = vxd_slice_is(sl->slice_type, V4L2_H264_SLICE_TYPE_I); ++ ++ sl->frame_num = vxd_bits_u(&b, sps->log2_max_frame_num_minus4 + 4); ++ /* frame_mbs_only_flag is enforced by try_ctrl: no field_pic_flag. */ ++ if (sl->nal_unit_type == APPLE_VXD_H264_NAL_IDR) ++ sl->idr_pic_id = vxd_bits_ue(&b); ++ if (sps->pic_order_cnt_type == 0) { ++ sl->pic_order_cnt_lsb = ++ vxd_bits_u(&b, sps->log2_max_pic_order_cnt_lsb_minus4 + 4); ++ if (pps->flags & V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT) ++ sl->delta_pic_order_cnt_bottom = vxd_bits_se(&b); ++ } else if (sps->pic_order_cnt_type == 1 && ++ !(sps->flags & V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO)) { ++ sl->delta_pic_order_cnt[0] = vxd_bits_se(&b); ++ if (pps->flags & V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT) ++ sl->delta_pic_order_cnt[1] = vxd_bits_se(&b); ++ } ++ if (pps->flags & V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT) ++ sl->redundant_pic_cnt = vxd_bits_ue(&b); ++ if (b_slice) ++ sl->direct_spatial_mv_pred = vxd_bit(&b); ++ ++ sl->num_ref_idx_l0_active_minus1 = pps->num_ref_idx_l0_default_active_minus1; ++ sl->num_ref_idx_l1_active_minus1 = pps->num_ref_idx_l1_default_active_minus1; ++ if (p || sp || b_slice) { ++ sl->num_ref_idx_override = vxd_bit(&b); ++ if (sl->num_ref_idx_override) { ++ v = vxd_bits_ue(&b); ++ if (v > 31) { ++ *why = "num_ref_idx_l0_active_minus1 > 31"; ++ return -EINVAL; ++ } ++ sl->num_ref_idx_l0_active_minus1 = v; ++ if (b_slice) { ++ v = vxd_bits_ue(&b); ++ if (v > 31) { ++ *why = "num_ref_idx_l1_active_minus1 > 31"; ++ return -EINVAL; ++ } ++ sl->num_ref_idx_l1_active_minus1 = v; ++ } ++ } ++ } ++ ++ if (!i_slice && !si) { ++ if (vxd_parse_mods(&b, sl, 0)) { ++ *why = "bad ref_pic_list_modification (l0)"; ++ return -EINVAL; ++ } ++ if (b_slice && vxd_parse_mods(&b, sl, 1)) { ++ *why = "bad ref_pic_list_modification (l1)"; ++ return -EINVAL; ++ } ++ } ++ ++ if (((pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED) && (p || sp)) || ++ (pps->weighted_bipred_idc == 1 && b_slice)) { ++ sl->has_pred_weight_table = true; ++ if (vxd_parse_pred_weight_table(&b, sps, sl)) { ++ *why = "bad pred_weight_table"; ++ return -EINVAL; ++ } ++ } ++ ++ if (sl->nal_ref_idc && vxd_skip_dec_ref_pic_marking(&b, sl)) { ++ *why = "bad dec_ref_pic_marking"; ++ return -EINVAL; ++ } ++ ++ if ((pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE) && !i_slice && !si) ++ sl->cabac_init_idc = vxd_bits_ue(&b); ++ sl->slice_qp_delta = vxd_bits_se(&b); ++ if (sp || si) { ++ if (sp) ++ sl->sp_for_switch = vxd_bit(&b); ++ sl->slice_qs_delta = vxd_bits_se(&b); ++ } ++ if (pps->flags & V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT) { ++ sl->disable_deblocking_filter_idc = vxd_bits_ue(&b); ++ if (sl->disable_deblocking_filter_idc != 1) { ++ sl->slice_alpha_c0_offset_div2 = vxd_bits_se(&b); ++ sl->slice_beta_offset_div2 = vxd_bits_se(&b); ++ } ++ } ++ /* num_slice_groups_minus1 == 0 (try_ctrl): no slice_group_change_cycle. */ ++ ++ if (b.overrun) { ++ *why = "slice header runs past the NAL unit"; ++ return -EINVAL; ++ } ++ sl->header_bits = b.rbsp_bits; ++ sl->header_bits_raw = b.byte * 8 + b.bit; ++ return 0; ++} ++ ++/* Next 0x000001 at or after @from, or @len. */ ++static size_t vxd_next_start_code(const u8 *buf, size_t len, size_t from) ++{ ++ size_t i; ++ ++ for (i = from; i + 3 <= len; i++) ++ if (!buf[i] && !buf[i + 1] && buf[i + 2] == 1) ++ return i; ++ return len; ++} ++ ++/* ++ * Walk the Annex-B access unit and parse every slice NAL (types 1 and 5). ++ * Parameter sets, SEI, AUD and the like may be present and are skipped; ++ * redundant slices (redundant_pic_cnt > 0) are dropped as decoders may. ++ * The slices of one picture must agree on the picture-level syntax and ++ * come in increasing first_mb_in_slice order. ++ */ ++int apple_vxd_h264_find_slices(const u8 *buf, size_t len, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_pps *pps, ++ struct apple_vxd_h264_slice *slices, ++ unsigned int max, unsigned int *count, ++ const char **why) ++{ ++ size_t sc = vxd_next_start_code(buf, len, 0); ++ unsigned int n = 0; ++ int ret; ++ ++ *count = 0; ++ *why = NULL; ++ while (sc < len) { ++ size_t nal = sc + 3, next, end; ++ struct apple_vxd_h264_slice *sl; ++ u8 type; ++ ++ next = vxd_next_start_code(buf, len, nal); ++ end = next; ++ while (end > nal && !buf[end - 1]) /* trailing_zero_8bits */ ++ end--; ++ if (sc && !buf[sc - 1]) /* 4-byte start code */ ++ sc--; ++ if (end <= nal) ++ goto next_nal; ++ ++ if (buf[nal] & 0x80) { ++ *why = "forbidden_zero_bit set"; ++ return -EINVAL; ++ } ++ type = buf[nal] & 0x1f; ++ if (type >= 2 && type <= 4) { ++ *why = "data partitioning is not supported"; ++ return -EINVAL; ++ } ++ if (type != APPLE_VXD_H264_NAL_SLICE && ++ type != APPLE_VXD_H264_NAL_IDR) ++ goto next_nal; ++ ++ if (n == max) { ++ *why = "too many slices in one picture"; ++ return -E2BIG; ++ } ++ sl = &slices[n]; ++ memset(sl, 0, sizeof(*sl)); ++ sl->sc_offset = sc; ++ sl->nal_offset = nal; ++ sl->nal_size = end - nal; ++ ret = vxd_parse_slice_header(buf + nal, end - nal, sps, pps, sl, why); ++ if (ret) ++ return ret; ++ if (sl->redundant_pic_cnt) ++ goto next_nal; ++ ++ if (!n && sl->first_mb_in_slice) { ++ *why = "the first slice does not start at macroblock 0"; ++ return -EINVAL; ++ } ++ if (n && (sl->first_mb_in_slice <= slices[n - 1].first_mb_in_slice || ++ sl->frame_num != slices[0].frame_num || ++ sl->nal_unit_type != slices[0].nal_unit_type || ++ sl->idr_pic_id != slices[0].idr_pic_id || ++ sl->pic_order_cnt_lsb != slices[0].pic_order_cnt_lsb)) { ++ *why = "slices disagree on the picture or are out of order (ASO)"; ++ return -EINVAL; ++ } ++ n++; ++next_nal: ++ sc = next; ++ } ++ ++ if (!n) { ++ *why = "no slice NAL unit in the buffer"; ++ return -ENOENT; ++ } ++ *count = n; ++ return 0; ++} ++ ++/* FrameNumWrap of a short-term DPB entry (clause 8.2.4.1, frames). */ ++static s32 vxd_pic_num(const struct v4l2_h264_dpb_entry *e, u32 cur_frame_num, ++ u32 max_frame_num) ++{ ++ return e->frame_num > cur_frame_num ? (s32)e->frame_num - (s32)max_frame_num : ++ (s32)e->frame_num; ++} ++ ++static bool vxd_dpb_is_ref(const struct v4l2_h264_dpb_entry *e) ++{ ++ return (e->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) && ++ (e->flags & V4L2_H264_DPB_ENTRY_FLAG_VALID); ++} ++ ++/* ++ * Clause 8.2.4.3, frames only: apply the slice's modifications to the ++ * initial list (indices into dp->dpb, as built by the v4l2-h264 helper). ++ * @len is num_ref_idx_lX_active_minus1 + 1; @reflist holds at least ++ * V4L2_H264_REF_LIST_LEN entries. ++ */ ++int apple_vxd_h264_modify_ref_list(const struct apple_vxd_h264_slice *sl, ++ unsigned int list, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_decode_params *dp, ++ struct v4l2_h264_reference *reflist, ++ unsigned int len) ++{ ++ struct v4l2_h264_reference tmp[V4L2_H264_REF_LIST_LEN + 1]; ++ u32 max_frame_num = 1U << (sps->log2_max_frame_num_minus4 + 4); ++ s32 cur = sl->frame_num, pred = cur; ++ unsigned int m, ref_idx = 0; ++ ++ if (!sl->num_mods[list]) ++ return 0; ++ if (!len || len > V4L2_H264_REF_LIST_LEN) ++ return -EINVAL; ++ ++ memcpy(tmp, reflist, len * sizeof(*tmp)); ++ for (m = 0; m < sl->num_mods[list]; m++) { ++ const struct apple_vxd_h264_ref_mod *mod = &sl->mods[list][m]; ++ unsigned int i, c, nidx; ++ bool long_term = mod->idc == 2; ++ s32 pic_num = 0; ++ int found = -1; ++ ++ if (!long_term) { ++ s32 no_wrap, diff = mod->val + 1; ++ ++ if (diff > (s32)max_frame_num) ++ return -EINVAL; ++ if (mod->idc == 0) { ++ no_wrap = pred - diff; ++ if (no_wrap < 0) ++ no_wrap += max_frame_num; ++ } else { ++ no_wrap = pred + diff; ++ if (no_wrap >= (s32)max_frame_num) ++ no_wrap -= max_frame_num; ++ } ++ pred = no_wrap; ++ pic_num = no_wrap > cur ? no_wrap - (s32)max_frame_num : no_wrap; ++ } ++ ++ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { ++ const struct v4l2_h264_dpb_entry *e = &dp->dpb[i]; ++ bool lt = e->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM; ++ ++ if (!vxd_dpb_is_ref(e) || lt != long_term) ++ continue; ++ if (long_term ? e->pic_num == mod->val : ++ vxd_pic_num(e, cur, max_frame_num) == pic_num) { ++ found = i; ++ break; ++ } ++ } ++ if (found < 0) ++ return -ENOENT; ++ ++ for (c = len; c > ref_idx; c--) ++ tmp[c] = tmp[c - 1]; ++ tmp[ref_idx].index = found; ++ tmp[ref_idx].fields = V4L2_H264_FRAME_REF; ++ ref_idx++; ++ nidx = ref_idx; ++ for (c = ref_idx; c <= len; c++) ++ if (tmp[c].index != found) ++ tmp[nidx++] = tmp[c]; ++ } ++ memcpy(reflist, tmp, len * sizeof(*tmp)); ++ return 0; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.h b/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.h +new file mode 100644 +index 0000000000000000000000000000000000000000..625bca644f63de4c5e191a6b0c5281bcc8d3144e +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264-parse.h +@@ -0,0 +1,100 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apple VXD - H.264 slice finder and slice-header parser (R3c) ++ * ++ * The device runs in V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED with ++ * Annex-B start codes: the OUTPUT buffer holds a whole picture and the ++ * driver itself locates the slices, as AppleD5500's host NAL walk does ++ * (HOST_H264DecodeFrame, 0xfffffff0062965f4). Every value the command ++ * stream needs per slice comes from here; the picture-invariant values ++ * come from the SPS/PPS/DECODE_PARAMS controls. ++ * ++ * Host-buildable: no allocation, no locking, no MMIO. ++ */ ++#ifndef __APPLE_VXD_H264_PARSE_H__ ++#define __APPLE_VXD_H264_PARSE_H__ ++ ++#include ++#include ++ ++#define APPLE_VXD_H264_MAX_SLICES 128 ++ ++#define APPLE_VXD_H264_NAL_SLICE 1 ++#define APPLE_VXD_H264_NAL_IDR 5 ++ ++struct apple_vxd_h264_ref_mod { ++ u8 idc; /* modification_of_pic_nums_idc */ ++ u32 val; /* abs_diff_pic_num_minus1 or long_term_pic_num */ ++}; ++ ++/* ++ * One pred_weight_table() entry with the values clause 7.4.3.2 infers for ++ * absent flags (weight 1 << denominator, offset 0), the way AppleD5500's ++ * copy (0xfffffff0062b0e9c) fills its per-reference arrays. ++ */ ++struct apple_vxd_h264_weight { ++ s16 weight[3]; /* Y, Cb, Cr */ ++ s8 offset[3]; ++}; ++ ++struct apple_vxd_h264_slice { ++ /* Location in the OUTPUT buffer. */ ++ u32 sc_offset; /* first byte of the start code */ ++ u32 nal_offset; /* the NAL header byte */ ++ u32 nal_size; /* NAL header .. last non-zero byte */ ++ /* ++ * slice_header() length, counted from the first bit after the NAL ++ * header byte: in RBSP bits, and in bits of the escaped byte stream ++ * (emulation prevention bytes inside the header included). ++ */ ++ u32 header_bits; ++ u32 header_bits_raw; ++ ++ u8 nal_ref_idc; ++ u8 nal_unit_type; ++ u32 first_mb_in_slice; ++ u8 slice_type; /* 0..4 (V4L2_H264_SLICE_TYPE_*) */ ++ u8 pps_id; ++ u16 frame_num; ++ u16 idr_pic_id; ++ u16 pic_order_cnt_lsb; ++ s32 delta_pic_order_cnt_bottom; ++ s32 delta_pic_order_cnt[2]; ++ u8 redundant_pic_cnt; ++ bool direct_spatial_mv_pred; ++ bool num_ref_idx_override; ++ u8 num_ref_idx_l0_active_minus1; ++ u8 num_ref_idx_l1_active_minus1; ++ u8 num_mods[2]; ++ struct apple_vxd_h264_ref_mod mods[2][V4L2_H264_REF_LIST_LEN + 1]; ++ bool has_pred_weight_table; ++ u8 luma_log2_weight_denom; ++ u8 chroma_log2_weight_denom; ++ struct apple_vxd_h264_weight weights[2][V4L2_H264_REF_LIST_LEN]; ++ bool no_output_of_prior_pics; ++ bool long_term_reference; ++ bool adaptive_ref_pic_marking; ++ u8 cabac_init_idc; ++ s8 slice_qp_delta; ++ bool sp_for_switch; ++ s8 slice_qs_delta; ++ u8 disable_deblocking_filter_idc; ++ s8 slice_alpha_c0_offset_div2; ++ s8 slice_beta_offset_div2; ++}; ++ ++int apple_vxd_h264_find_slices(const u8 *buf, size_t len, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_pps *pps, ++ struct apple_vxd_h264_slice *slices, ++ unsigned int max, unsigned int *count, ++ const char **why); ++ ++int apple_vxd_h264_modify_ref_list(const struct apple_vxd_h264_slice *sl, ++ unsigned int list, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_decode_params *dp, ++ struct v4l2_h264_reference *reflist, ++ unsigned int len); ++ ++#endif /* __APPLE_VXD_H264_PARSE_H__ */ +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264-tables.h b/drivers/media/platform/apple/vxd/apple-vxd-h264-tables.h +new file mode 100644 +index 0000000000000000000000000000000000000000..cfba744735ab19ea2a0d90aac273912dfe97b88a +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264-tables.h +@@ -0,0 +1,94 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) Imagination Technologies Ltd. ++ * Copyright (c) 2021 Texas Instruments Incorporated - http://www.ti.com/ ++ * ++ * H.264 CAVLC decode table, generated by h264-tables.py from ++ * img-vxe-vxd decoder/h264_vlc.h (h264_vlc_table_data). ++ * Upstream author: Angela Stegmaier ++ * https://github.com/TexasInstruments/img-vxe-vxd/blob/9453ad8ebdd42a5e5a384c21cd5f6bc2c86aa6d8/decoder/h264_vlc.h ++ * Source SHA-256: 9b349a315038817651a8f6c0be87bb82321f9b046901957a3d65e73f6f1eeb66 ++ * Packing: opcode << 12 | width << 9 | symbol. No Apple binary input. ++ * CRC-32 of the 1088 little-endian bytes: 0xa0d60f2e. ++ */ ++#ifndef __APPLE_VXD_H264_TABLES_H__ ++#define __APPLE_VXD_H264_TABLES_H__ ++ ++#include ++ ++#define APPLE_VXD_H264_VLC_ENTRIES 544 ++#define APPLE_VXD_H264_VLC_CRC32 0xa0d60f2e ++ ++/* opcode[14:12] | width[11:9] | symbol[8:0] */ ++static const u16 apple_vxd_h264_vlc_table[APPLE_VXD_H264_VLC_ENTRIES] = { ++ 0x4000, 0x4205, 0x440a, 0x2204, 0x2206, 0x0208, 0x040b, 0x400f, ++ 0x4204, 0x4209, 0x4013, 0x420e, 0x4217, 0x421b, 0x4212, 0x420d, ++ 0x4208, 0x2a08, 0x0232, 0x0035, 0x0036, 0x441f, 0x4416, 0x4411, ++ 0x440c, 0x0407, 0x040e, 0x0415, 0x041c, 0x0223, 0x4a35, 0x3a00, ++ 0x4420, 0x4426, 0x4421, 0x441c, 0x442b, 0x4422, 0x441d, 0x4418, ++ 0x4433, 0x442e, 0x4429, 0x4428, 0x442f, 0x442a, 0x4425, 0x4424, ++ 0x443b, 0x4436, 0x4431, 0x4430, 0x4437, 0x4432, 0x442d, 0x442c, ++ 0x4443, 0x443e, 0x443d, 0x4438, 0x443f, 0x443a, 0x4439, 0x4434, ++ 0x4240, 0x4242, 0x4241, 0x423c, 0x4227, 0x421e, 0x4219, 0x4214, ++ 0x4023, 0x401a, 0x4015, 0x4010, 0x0410, 0x0249, 0x024c, 0x004f, ++ 0x4613, 0x460f, 0x440a, 0x440a, 0x4205, 0x4205, 0x4205, 0x4205, ++ 0x4200, 0x4200, 0x4200, 0x4200, 0x2a08, 0x0231, 0x0034, 0x0035, ++ 0x4423, 0x4416, 0x4415, 0x440c, 0x0407, 0x040e, 0x0415, 0x121c, ++ 0x0222, 0x4a3f, 0x3a00, 0x442f, 0x4426, 0x4425, 0x4420, 0x442b, ++ 0x4422, 0x4421, 0x441c, 0x442c, 0x442e, 0x442d, 0x4428, 0x4433, ++ 0x442a, 0x4429, 0x4424, 0x443b, 0x4436, 0x4435, 0x4434, 0x4437, ++ 0x4432, 0x4431, 0x4430, 0x0203, 0x423a, 0x4238, 0x423d, 0x423c, ++ 0x423e, 0x4239, 0x4243, 0x4242, 0x4241, 0x4240, 0x4227, 0x421e, ++ 0x421d, 0x4218, 0x4014, 0x401a, 0x4019, 0x4010, 0x421f, 0x4212, ++ 0x4211, 0x4208, 0x421b, 0x420e, 0x420d, 0x4204, 0x4017, 0x4009, ++ 0x2210, 0x0432, 0x0239, 0x023c, 0x600a, 0x6008, 0x003d, 0x003e, ++ 0x461f, 0x461b, 0x4617, 0x4613, 0x460f, 0x460a, 0x4605, 0x4600, ++ 0x0403, 0x040a, 0x0611, 0x4433, 0x442e, 0x4429, 0x4424, 0x442f, ++ 0x442a, 0x4425, 0x4420, 0x4430, 0x4436, 0x4431, 0x442c, 0x4437, ++ 0x4432, 0x442d, 0x4428, 0x3600, 0x4640, 0x4643, 0x4642, 0x4641, ++ 0x463c, 0x463f, 0x463e, 0x463d, 0x4638, 0x463b, 0x463a, 0x4639, ++ 0x4634, 0x4435, 0x4435, 0x441c, 0x4418, 0x4426, 0x4414, 0x442b, ++ 0x4422, 0x4421, 0x4410, 0x420c, 0x421e, 0x421d, 0x4208, 0x4227, ++ 0x421a, 0x4219, 0x4204, 0x400d, 0x4023, 0x400e, 0x4009, 0x2208, ++ 0x5406, 0x540a, 0x540e, 0x5412, 0x5416, 0x541a, 0x541e, 0x5204, ++ 0x0002, 0x5002, 0x3000, 0x4000, 0x4005, 0x4200, 0x440a, 0x0401, ++ 0x1208, 0x000a, 0x4410, 0x440c, 0x4408, 0x440f, 0x4409, 0x4404, ++ 0x4013, 0x4212, 0x4211, 0x400e, 0x400d, 0x4000, 0x4205, 0x440a, ++ 0x0404, 0x480f, 0x4a13, 0x2609, 0x441b, 0x4417, 0x4412, 0x440e, ++ 0x440d, 0x4409, 0x4408, 0x4404, 0x0205, 0x0208, 0x020b, 0x020e, ++ 0x1411, 0x4216, 0x4211, 0x4210, 0x420c, 0x421f, 0x421a, 0x4215, ++ 0x4214, 0x4223, 0x421e, 0x4219, 0x4218, 0x4222, 0x4221, 0x421d, ++ 0x421c, 0x3400, 0x3400, 0x3400, 0x4420, 0x4000, 0x0006, 0x0007, ++ 0x0008, 0x0009, 0x000a, 0x040b, 0x4002, 0x4001, 0x4004, 0x4003, ++ 0x4006, 0x4005, 0x4008, 0x4007, 0x400a, 0x4009, 0x3400, 0x440f, ++ 0x440e, 0x440d, 0x420c, 0x420c, 0x420b, 0x420b, 0x1208, 0x000e, ++ 0x000f, 0x4404, 0x4403, 0x4402, 0x4401, 0x4400, 0x0203, 0x420a, ++ 0x4209, 0x420e, 0x420d, 0x420c, 0x420b, 0x4008, 0x4007, 0x4006, ++ 0x4005, 0x0208, 0x000d, 0x000e, 0x4407, 0x4406, 0x4403, 0x4402, ++ 0x4401, 0x0004, 0x420c, 0x420a, 0x4209, 0x400d, 0x400b, 0x4008, ++ 0x4005, 0x4004, 0x4000, 0x0208, 0x000b, 0x000c, 0x4408, 0x4406, ++ 0x4405, 0x4404, 0x4401, 0x420c, 0x420b, 0x420a, 0x4200, 0x4009, ++ 0x4007, 0x4003, 0x4002, 0x2208, 0x000a, 0x000b, 0x4407, 0x4406, ++ 0x4405, 0x4404, 0x4403, 0x400a, 0x4209, 0x420b, 0x4008, 0x4002, ++ 0x4001, 0x4000, 0x2408, 0x4409, 0x4407, 0x4406, 0x4405, 0x4404, ++ 0x4403, 0x4402, 0x4008, 0x4201, 0x4400, 0x440a, 0x2408, 0x4408, ++ 0x4406, 0x4404, 0x4403, 0x4402, 0x4205, 0x4205, 0x4007, 0x4201, ++ 0x4400, 0x4409, 0x2604, 0x0008, 0x4205, 0x4204, 0x4007, 0x4201, ++ 0x4402, 0x4600, 0x4608, 0x4006, 0x4003, 0x2604, 0x4206, 0x4204, ++ 0x4203, 0x4005, 0x4202, 0x4407, 0x4600, 0x4601, 0x2404, 0x4205, ++ 0x4204, 0x4203, 0x4002, 0x4206, 0x4400, 0x4401, 0x4004, 0x0003, ++ 0x4402, 0x5000, 0x4003, 0x4005, 0x4003, 0x4202, 0x4404, 0x5000, ++ 0x4002, 0x4203, 0x5000, 0x5000, 0x4002, 0x4000, 0x4001, 0x4000, ++ 0x4201, 0x4402, 0x4403, 0x4000, 0x4201, 0x4202, 0x4001, 0x4000, ++ 0x4001, 0x4000, 0x4000, 0x4201, 0x4202, 0x4203, 0x4202, 0x4201, ++ 0x4200, 0x0004, 0x4202, 0x4201, 0x4200, 0x4004, 0x4003, 0x0203, ++ 0x4201, 0x4200, 0x4205, 0x4204, 0x4203, 0x4202, 0x4401, 0x4402, ++ 0x4404, 0x4403, 0x4406, 0x4405, 0x4200, 0x4200, 0x2a08, 0x4406, ++ 0x4405, 0x4404, 0x4403, 0x4402, 0x4401, 0x4400, 0x4007, 0x4208, ++ 0x4409, 0x460a, 0x480b, 0x4a0c, 0x2201, 0x400d, 0x420e, 0x3200, ++ 0x4000, 0x6000, 0x6001, 0x4605, 0x4806, 0x4807, 0x6201, 0x4201, ++ 0x4402, 0x4400, 0x5000, 0x4202, 0x4203, 0x5002, 0x6000, 0x4203, ++ 0x4400, 0x4404, 0x5000, 0x5001, 0x5000, 0x4002, 0x4000, 0x4001, ++}; ++ ++#endif /* __APPLE_VXD_H264_TABLES_H__ */ +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-h264.c b/drivers/media/platform/apple/vxd/apple-vxd-h264.c +new file mode 100644 +index 0000000000000000000000000000000000000000..04f3e5ce7f8968afae719cf9d386492d7ba0e5c1 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-h264.c +@@ -0,0 +1,582 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - H.264 backend (V4L2 stateless, frame based) ++ * ++ * Userspace parses the parameter sets and the DPB (V4L2 stateless ++ * contract); the OUTPUT buffer carries a whole Annex-B access unit. Per ++ * picture this file ++ * ++ * 1. validates SPS/PPS against what the AVC recipe covers, ++ * 2. finds and parses the slices itself (frame-based mode has no ++ * SLICE_PARAMS, apple-vxd-h264-parse.c), ++ * 3. builds RefPicList0/1 (v4l2-h264 helper + the slice's ++ * modifications) and resolves the DPB to CAPTURE device addresses ++ * (and, for B slices, stored-MB buffers) by timestamp, ++ * 4. builds the command stream (apple-vxd-h264-cmd.c) into a pooled ++ * command buffer and submits one 0x81 message per slice. ++ * ++ * The job completes from the last packet's 0xc0 (apple_vxd_job_done()). ++ * ++ * The context owns the buffers DecodeCreateH264VLD allocates for the ++ * AVC path (ds-vxd-cmdstream.md section 16.5): the packed CAVLC table ++ * (0x1000, the 0x440-byte constant), the IntraBuffer and the ++ * StreamStateBuffer, each sized for the larger of Apple's two platform ++ * multipliers. Each CAPTURE buffer carries its stored-macroblock ++ * parameter buffer (apple_vxd_buffer.aux). ++ */ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "apple-vxd.h" ++#include "apple-vxd-h264-cmd.h" ++#include "apple-vxd-h264-parse.h" ++#include "apple-vxd-h264-tables.h" ++ ++static unsigned int apple_vxd_scaling_order; ++module_param_named(scaling_order, apple_vxd_scaling_order, uint, 0644); ++MODULE_PARM_DESC(scaling_order, ++ "debug: scaling-list element order, 0 raster (AppleD5500, default), 1 zig-zag, 2 transposed"); ++ ++/* DecodeCreateH264VLD sizes with Apple's multiplier M = 2. */ ++#define APPLE_VXD_H264_CAVLC_BUF SZ_4K ++#define APPLE_VXD_H264_INTRA_BUF (2 << 16) ++#define APPLE_VXD_H264_STREAM_STATE_BUF (2 << 12) ++ ++struct apple_vxd_h264_ctx { ++ struct apple_vxd_dev_buf cavlc; ++ struct apple_vxd_dev_buf intra; ++ struct apple_vxd_dev_buf stream_state; ++ bool first_picture; ++ struct apple_vxd_h264_slice slices[APPLE_VXD_H264_MAX_SLICES]; ++ u8 ref_slots[APPLE_VXD_H264_MAX_SLICES][2][V4L2_H264_REF_LIST_LEN]; ++}; ++ ++static_assert(sizeof(apple_vxd_h264_vlc_table) == APPLE_VXD_H264_VLC_BYTES); ++ ++static int apple_vxd_h264_validate_sps(struct apple_vxd_ctx *ctx, ++ const struct v4l2_ctrl_h264_sps *sps) ++{ ++ const struct apple_vxd_dev *vxd = ctx->vxd; ++ ++ /* ++ * Reject the profiles whose tools the 8-bit 4:2:0 decoder lacks; ++ * High 10/4:2:2/4:4:4 are caught by the chroma/bit-depth checks below. ++ * No allow-list: the control's default (profile_idc 0, before ++ * userspace sets an SPS) must pass, or TRY_EXT_CTRLS on the current ++ * values fails (v4l2-compliance, round 3 QEMU smoke). What the AVC ++ * command recipe does not cover (interlace, FMO) is refused per ++ * picture. ++ */ ++ switch (sps->profile_idc) { ++ case 88: /* extended: data partitioning */ ++ case 83: /* scalable baseline */ ++ case 86: /* scalable high */ ++ case 118: /* multiview high */ ++ case 128: /* stereo high */ ++ case 134: /* MFC high */ ++ case 135: /* MFC depth high */ ++ case 138: /* multiview depth high */ ++ case 139: /* enhanced multiview depth high */ ++ dev_dbg(vxd->dev, "sps: unsupported profile %u\n", ++ sps->profile_idc); ++ return -EINVAL; ++ default: ++ break; ++ } ++ ++ if (sps->chroma_format_idc > 1) { ++ dev_dbg(vxd->dev, "sps: chroma format 4:2:2/4:4:4 not supported\n"); ++ return -EINVAL; ++ } ++ ++ if (sps->bit_depth_luma_minus8 > 0 || sps->bit_depth_chroma_minus8 > 0) { ++ dev_dbg(vxd->dev, "sps: 10-bit not supported\n"); ++ return -EINVAL; ++ } ++ ++ /* ++ * DT advertises h264-playback-level = 42: 1920x1088 [E]. Tile ++ * decode could lift this, but is not explored yet. ++ */ ++ if (sps->pic_width_in_mbs_minus1 > (APPLE_VXD_MAX_PIC_W / 16 - 1) || ++ sps->pic_height_in_map_units_minus1 > (APPLE_VXD_MAX_PIC_H / 16 - 1)) { ++ dev_dbg(vxd->dev, "sps: %ux%u exceeds level 4.2\n", ++ (sps->pic_width_in_mbs_minus1 + 1) * 16, ++ (sps->pic_height_in_map_units_minus1 + 1) * 16); ++ return -EINVAL; ++ } ++ ++ if (sps->max_num_ref_frames > V4L2_H264_NUM_DPB_ENTRIES) { ++ dev_dbg(vxd->dev, "sps: max_num_ref_frames %u too many\n", ++ sps->max_num_ref_frames); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int apple_vxd_h264_try_ctrl(struct apple_vxd_ctx *ctx, ++ struct v4l2_ctrl *ctrl) ++{ ++ if (ctrl->id == V4L2_CID_STATELESS_H264_SPS) ++ return apple_vxd_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps); ++ ++ return 0; ++} ++ ++static int apple_vxd_h264_start(struct apple_vxd_ctx *ctx) ++{ ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ struct apple_vxd_h264_ctx *h; ++ int ret; ++ ++ h = kvzalloc_obj(*h); ++ if (!h) ++ return -ENOMEM; ++ ++ ret = apple_vxd_dev_buf_alloc(vxd, APPLE_VXD_H264_CAVLC_BUF, &h->cavlc); ++ if (ret) ++ goto err_free; ++ ret = apple_vxd_dev_buf_alloc(vxd, APPLE_VXD_H264_INTRA_BUF, &h->intra); ++ if (ret) ++ goto err_cavlc; ++ ret = apple_vxd_dev_buf_alloc(vxd, APPLE_VXD_H264_STREAM_STATE_BUF, ++ &h->stream_state); ++ if (ret) ++ goto err_intra; ++ ++ memcpy(h->cavlc.mem.vaddr, apple_vxd_h264_vlc_table, ++ sizeof(apple_vxd_h264_vlc_table)); ++ if ((crc32_le(~0U, h->cavlc.mem.vaddr, APPLE_VXD_H264_VLC_BYTES) ^ ~0U) != ++ APPLE_VXD_H264_VLC_CRC32) { ++ dev_err(vxd->dev, "h264: CAVLC table CRC mismatch\n"); ++ ret = -EIO; ++ goto err_state; ++ } ++ h->first_picture = true; ++ ctx->priv = h; ++ ++ vxd_step_io(vxd->dev, ++ "h264: context buffers: cavlc %#x, intra %#x (%u bytes), stream state %#x (%u bytes)", ++ h->cavlc.va, h->intra.va, APPLE_VXD_H264_INTRA_BUF, ++ h->stream_state.va, APPLE_VXD_H264_STREAM_STATE_BUF); ++ return 0; ++ ++err_state: ++ apple_vxd_dev_buf_free(vxd, &h->stream_state); ++err_intra: ++ apple_vxd_dev_buf_free(vxd, &h->intra); ++err_cavlc: ++ apple_vxd_dev_buf_free(vxd, &h->cavlc); ++err_free: ++ kvfree(h); ++ return ret; ++} ++ ++static void apple_vxd_h264_stop(struct apple_vxd_ctx *ctx) ++{ ++ struct apple_vxd_h264_ctx *h = ctx->priv; ++ ++ if (!h) ++ return; ++ apple_vxd_dev_buf_free(ctx->vxd, &h->stream_state); ++ apple_vxd_dev_buf_free(ctx->vxd, &h->intra); ++ apple_vxd_dev_buf_free(ctx->vxd, &h->cavlc); ++ kvfree(h); ++ ctx->priv = NULL; ++} ++ ++static enum apple_vxd_image_fmt ++apple_vxd_h264_get_image_fmt(struct apple_vxd_ctx *ctx, struct v4l2_ctrl *ctrl) ++{ ++ if (ctrl->id != V4L2_CID_STATELESS_H264_SPS) ++ return APPLE_VXD_IMG_FMT_ANY; ++ ++ return APPLE_VXD_IMG_FMT_420_8BIT; ++} ++ ++static size_t apple_vxd_h264_capture_aux_size(struct apple_vxd_ctx *ctx) ++{ ++ const struct v4l2_pix_format_mplane *pix = &ctx->decoded_fmt.fmt.pix_mp; ++ ++ return apple_vxd_h264_colocated_size(DIV_ROUND_UP(pix->width, 16), ++ DIV_ROUND_UP(pix->height, 16)); ++} ++ ++static const void *apple_vxd_h264_ctrl(struct apple_vxd_ctx *ctx, u32 id) ++{ ++ struct v4l2_ctrl *ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, id); ++ ++ return ctrl ? ctrl->p_cur.p : NULL; ++} ++ ++/* ++ * RefPicList0/1 per slice as DPB slots: the initial lists (8.2.4.2.1 for ++ * P, 8.2.4.2.3 for B, built once per picture by the v4l2-h264 helper), ++ * truncated to num_ref_idx_lX_active, then the slice's modifications ++ * (8.2.4.3). Entries past the available references are NO_REF. ++ */ ++enum { VXD_LIST_P, VXD_LIST_B0, VXD_LIST_B1, VXD_LISTS }; ++ ++static int apple_vxd_h264_ref_lists(struct apple_vxd_h264_ctx *h, ++ unsigned int nslices, ++ const struct v4l2_ctrl_h264_sps *sps, ++ const struct v4l2_ctrl_h264_decode_params *dp) ++{ ++ struct v4l2_h264_reference init[VXD_LISTS][V4L2_H264_REF_LIST_LEN]; ++ struct v4l2_h264_reference list[V4L2_H264_REF_LIST_LEN]; ++ struct v4l2_h264_reflist_builder b; ++ bool have_p = false, have_b = false; ++ unsigned int i, k, lst; ++ int ret; ++ ++ v4l2_h264_init_reflist_builder(&b, dp, sps, dp->dpb); ++ for (i = 0; i < nslices; i++) { ++ const struct apple_vxd_h264_slice *sl = &h->slices[i]; ++ bool bslice = sl->slice_type == V4L2_H264_SLICE_TYPE_B; ++ ++ memset(h->ref_slots[i], APPLE_VXD_H264_NO_REF, ++ sizeof(h->ref_slots[i])); ++ if (sl->slice_type != V4L2_H264_SLICE_TYPE_P && !bslice) ++ continue; ++ ++ if (!bslice && !have_p) { ++ v4l2_h264_build_p_ref_list(&b, init[VXD_LIST_P]); ++ have_p = true; ++ } ++ if (bslice && !have_b) { ++ v4l2_h264_build_b_ref_lists(&b, init[VXD_LIST_B0], ++ init[VXD_LIST_B1]); ++ have_b = true; ++ } ++ for (lst = 0; lst < (bslice ? 2 : 1); lst++) { ++ const struct v4l2_h264_reference *src = ++ init[bslice ? VXD_LIST_B0 + lst : VXD_LIST_P]; ++ unsigned int len = 1 + (lst ? sl->num_ref_idx_l1_active_minus1 : ++ sl->num_ref_idx_l0_active_minus1); ++ ++ for (k = 0; k < V4L2_H264_REF_LIST_LEN; k++) { ++ list[k] = src[k]; ++ if (k >= b.num_valid) ++ list[k].index = APPLE_VXD_H264_NO_REF; ++ } ++ ret = apple_vxd_h264_modify_ref_list(sl, lst, sps, dp, ++ list, len); ++ if (ret) ++ return ret; ++ for (k = 0; k < len; k++) ++ h->ref_slots[i][lst][k] = list[k].index; ++ } ++ } ++ return 0; ++} ++ ++/* Every reference a slice names must resolve to a CAPTURE buffer. */ ++static const char *apple_vxd_h264_check_refs(const struct apple_vxd_h264_ctx *h, ++ unsigned int nslices, ++ const struct apple_vxd_h264_frame *f) ++{ ++ unsigned int i, k, lst; ++ ++ for (i = 0; i < nslices; i++) { ++ const struct apple_vxd_h264_slice *sl = &h->slices[i]; ++ bool bslice = sl->slice_type == V4L2_H264_SLICE_TYPE_B; ++ ++ if (sl->slice_type != V4L2_H264_SLICE_TYPE_P && !bslice) ++ continue; ++ for (lst = 0; lst < (bslice ? 2 : 1); lst++) { ++ unsigned int len = 1 + (lst ? sl->num_ref_idx_l1_active_minus1 : ++ sl->num_ref_idx_l0_active_minus1); ++ ++ for (k = 0; k < len; k++) { ++ u8 slot = h->ref_slots[i][lst][k]; ++ ++ if (slot < V4L2_H264_NUM_DPB_ENTRIES && ++ !f->dpb_luma[slot]) ++ return "a reference picture has no CAPTURE buffer"; ++ } ++ } ++ if (bslice) { ++ u8 col = h->ref_slots[i][1][0]; ++ ++ if (col >= V4L2_H264_NUM_DPB_ENTRIES || ++ !f->dpb_colocated[col]) ++ return "co-located picture (RefPicList1[0]) unresolved"; ++ } ++ } ++ return NULL; ++} ++ ++static int apple_vxd_h264_run(struct apple_vxd_ctx *ctx) ++{ ++ const struct v4l2_pix_format_mplane *pix = &ctx->decoded_fmt.fmt.pix_mp; ++ const struct v4l2_ctrl_h264_scaling_matrix *scaling; ++ const struct v4l2_ctrl_h264_decode_params *dp; ++ const struct v4l2_ctrl_h264_sps *sps; ++ const struct v4l2_ctrl_h264_pps *pps; ++ struct apple_vxd_h264_ctx *h = ctx->priv; ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ struct apple_vxd_h264_frame f = { }; ++ struct apple_vxd_cmdbuf *cb = NULL; ++ struct apple_vxd_run run; ++ const char *why = NULL; ++ struct vb2_queue *cap_q; ++ unsigned int i, nslices; ++ const u8 *src; ++ size_t src_len; ++ u32 stride; ++ int ret; ++ ++ apple_vxd_run_preamble(ctx, &run); ++ ++ if (!vxd->hw_ready || vxd->fw_panicked) { ++ dev_dbg(vxd->dev, ++ "decode job with no usable firmware (not booted, init message not taken, or panicked)\n"); ++ ret = -EAGAIN; ++ goto out; ++ } ++ if (!h) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ sps = apple_vxd_h264_ctrl(ctx, V4L2_CID_STATELESS_H264_SPS); ++ pps = apple_vxd_h264_ctrl(ctx, V4L2_CID_STATELESS_H264_PPS); ++ dp = apple_vxd_h264_ctrl(ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); ++ scaling = apple_vxd_h264_ctrl(ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); ++ if (!sps || !pps || !dp) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ret = apple_vxd_h264_validate(sps, pps, scaling, &why); ++ if (ret) ++ goto out_why; ++ if (dp->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC) { ++ why = "field pictures"; ++ ret = -EINVAL; ++ goto out_why; ++ } ++ if ((sps->pic_width_in_mbs_minus1 + 1) * 16 > pix->width || ++ (sps->pic_height_in_map_units_minus1 + 1) * 16 > pix->height) { ++ why = "picture larger than the CAPTURE format"; ++ ret = -EINVAL; ++ goto out_why; ++ } ++ ++ src_len = vb2_get_plane_payload(&run.src->vb2_buf, 0); ++ src = vb2_plane_vaddr(&run.src->vb2_buf, 0); ++ if (!src) { ++ why = "OUTPUT buffer has no kernel mapping"; ++ ret = -EINVAL; ++ goto out_why; ++ } ++ ret = apple_vxd_h264_find_slices(src, src_len, sps, pps, h->slices, ++ APPLE_VXD_H264_MAX_SLICES, &nslices, ++ &why); ++ if (ret) ++ goto out_why; ++ if (nslices > APPLE_VXD_JOB_MAX_PACKETS) { ++ why = "more slices in one picture than packets in a job"; ++ ret = -EINVAL; ++ goto out_why; ++ } ++ ret = apple_vxd_h264_ref_lists(h, nslices, sps, dp); ++ if (ret) { ++ why = "a reference-list modification names a picture not in the DPB"; ++ goto out_why; ++ } ++ ++ stride = pix->plane_fmt[0].bytesperline; ++ f.sps = sps; ++ f.pps = pps; ++ f.dp = dp; ++ f.scaling = scaling; ++ f.slices = h->slices; ++ f.nslices = nslices; ++ f.ref_slots = (const u8 (*)[2][V4L2_H264_REF_LIST_LEN])h->ref_slots; ++ f.src_va = apple_vxd_buf_va(run.src); ++ f.dst_luma = apple_vxd_buf_va(run.dst); ++ f.dst_chroma = f.dst_luma + stride * pix->height; ++ f.dst_stride = stride; ++ f.colocated = apple_vxd_buf_aux_va(run.dst); ++ f.bufs.cavlc = h->cavlc.va; ++ f.bufs.intra = h->intra.va; ++ f.bufs.stream_state = h->stream_state.va; ++ f.first_picture = h->first_picture; ++ f.scaling_order = min_t(unsigned int, apple_vxd_scaling_order, ++ APPLE_VXD_H264_SCALING_TRANSPOSED); ++ ++ cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); ++ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { ++ const struct v4l2_h264_dpb_entry *e = &dp->dpb[i]; ++ struct vb2_buffer *ref; ++ ++ if (!(e->flags & V4L2_H264_DPB_ENTRY_FLAG_VALID)) ++ continue; ++ ref = vb2_find_buffer(cap_q, e->reference_ts); ++ if (!ref) { ++ dev_dbg(vxd->dev, "h264: dpb[%u] ts %llu has no CAPTURE buffer\n", ++ i, e->reference_ts); ++ continue; ++ } ++ f.dpb_luma[i] = apple_vxd_buf_va(to_vb2_v4l2_buffer(ref)); ++ f.dpb_chroma[i] = f.dpb_luma[i] + stride * pix->height; ++ f.dpb_colocated[i] = apple_vxd_buf_aux_va(to_vb2_v4l2_buffer(ref)); ++ } ++ why = apple_vxd_h264_check_refs(h, nslices, &f); ++ if (why) { ++ ret = -EINVAL; ++ goto out_why; ++ } ++ ++ cb = apple_vxd_cmdbuf_get(vxd); ++ if (!cb) { ++ why = "no free command buffer"; ++ ret = -EBUSY; ++ goto out_why; ++ } ++ ret = apple_vxd_h264_build(cb, &f, &why); ++ if (ret) ++ goto out_why; ++ ++ vxd_step_io(vxd->dev, ++ "decode: src %u va %#x (%zu bytes, %u slices, first %c NAL %#x+%u, header %u bits), dst %u Y %#x UV %#x stride %u, %zu command bytes in buf %u (va %#x), %u packets", ++ run.src->vb2_buf.index, f.src_va, src_len, nslices, ++ "PBIps"[h->slices[0].slice_type % 5], ++ h->slices[0].nal_offset, h->slices[0].nal_size, ++ 8 + h->slices[0].header_bits_raw, run.dst->vb2_buf.index, ++ f.dst_luma, f.dst_chroma, stride, cb->used, cb->index, ++ cb->va, cb->npackets); ++ ++ /* ++ * The controls were only read while building: complete them before ++ * the submit, after which the job may finish (and the request with ++ * it) at any moment. ++ */ ++ apple_vxd_run_postamble(ctx, &run); ++ ret = apple_vxd_submit(ctx, cb); ++ if (ret) { ++ dev_warn_ratelimited(vxd->dev, "decode: submit failed: %d\n", ret); ++ if (cb->state == APPLE_VXD_CMDBUF_HOST) ++ apple_vxd_cmdbuf_put(vxd, cb); ++ return ret; ++ } ++ h->first_picture = false; ++ return 0; ++ ++out_why: ++ dev_warn_ratelimited(vxd->dev, "decode: picture refused (%d): %s\n", ++ ret, why ? why : "?"); ++out: ++ if (cb) ++ apple_vxd_cmdbuf_put(vxd, cb); ++ apple_vxd_run_postamble(ctx, &run); ++ return ret; ++} ++ ++static const struct apple_vxd_ctrl_desc apple_vxd_h264_ctrl_descs[] = { ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_SPS, ++ .cfg.ops = &apple_vxd_ctrl_ops, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_PPS, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_PRED_WEIGHTS, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_SLICE_PARAMS, ++ .cfg.flags = V4L2_CTRL_FLAG_DYNAMIC_ARRAY, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_DECODE_MODE, ++ .cfg.min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, ++ .cfg.max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, ++ .cfg.def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, ++ }, ++ { ++ .cfg.id = V4L2_CID_STATELESS_H264_START_CODE, ++ .cfg.min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, ++ .cfg.def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, ++ .cfg.max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, ++ }, ++ { ++ .cfg.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, ++ .cfg.min = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE, ++ .cfg.max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA, ++ .cfg.menu_skip_mask = ++ BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED) | ++ BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE), ++ .cfg.def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, ++ }, ++ { ++ .cfg.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, ++ .cfg.min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, ++ .cfg.max = V4L2_MPEG_VIDEO_H264_LEVEL_4_2, ++ .cfg.def = V4L2_MPEG_VIDEO_H264_LEVEL_4_2, ++ }, ++}; ++ ++static const struct apple_vxd_ctrls apple_vxd_h264_ctrls = { ++ .ctrls = apple_vxd_h264_ctrl_descs, ++ .num_ctrls = ARRAY_SIZE(apple_vxd_h264_ctrl_descs), ++}; ++ ++static const struct apple_vxd_decoded_fmt_desc ++apple_vxd_h264_decoded_fmts[] = { ++ { ++ .fourcc = V4L2_PIX_FMT_NV12, ++ .image_fmt = APPLE_VXD_IMG_FMT_420_8BIT, ++ }, ++}; ++ ++static const struct apple_vxd_coded_fmt_desc apple_vxd_h264_fmt = { ++ .fourcc = V4L2_PIX_FMT_H264_SLICE, ++ .frmsize = { ++ .min_width = APPLE_VXD_MIN_PIC_W, ++ .max_width = APPLE_VXD_MAX_PIC_W, ++ .step_width = 16, ++ .min_height = APPLE_VXD_MIN_PIC_H, ++ .max_height = APPLE_VXD_MAX_PIC_H, ++ .step_height = 16, ++ }, ++ .ctrls = &apple_vxd_h264_ctrls, ++ .ops = &(const struct apple_vxd_coded_fmt_ops){ ++ .adjust_fmt = NULL, ++ .start = apple_vxd_h264_start, ++ .stop = apple_vxd_h264_stop, ++ .run = apple_vxd_h264_run, ++ .try_ctrl = apple_vxd_h264_try_ctrl, ++ .get_image_fmt = apple_vxd_h264_get_image_fmt, ++ .capture_aux_size = apple_vxd_h264_capture_aux_size, ++ }, ++ .num_decoded_fmts = ARRAY_SIZE(apple_vxd_h264_decoded_fmts), ++ .decoded_fmts = apple_vxd_h264_decoded_fmts, ++}; ++ ++static const struct apple_vxd_coded_fmt_desc apple_vxd_coded_fmts[] = { ++ apple_vxd_h264_fmt, ++}; ++ ++/* ++ * Fills the device's coded-format table. Called during probe, before any ++ * V4L2 node exists: apple_vxd_v4l2_init() and open() both refuse to run ++ * without it (round 2 had no such fill and oopsed in the first open()). ++ */ ++int apple_vxd_h264_register(struct apple_vxd_dev *vxd) ++{ ++ vxd->coded_fmts = apple_vxd_coded_fmts; ++ vxd->num_coded_fmts = ARRAY_SIZE(apple_vxd_coded_fmts); ++ return 0; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-mbox.c b/drivers/media/platform/apple/vxd/apple-vxd-mbox.c +new file mode 100644 +index 0000000000000000000000000000000000000000..b030d70d48886b2d01aaa7f232b8eb9d577e41ca +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-mbox.c +@@ -0,0 +1,1406 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - host<->MTX mailbox (R3a) ++ * ++ * docs/periph/ds-vxd-decode-protocol.md sections 2, 3 and 6. The rings ++ * are word windows in the register file, not DMA queues: after the ready ++ * magic the DEVA firmware publishes each ring as (base << 16) | capacity ++ * in 0x2024 (TO_MTX) and 0x2018 (TO_HOST), and word i of a ring is the ++ * register at 0x2000 + base + 4 * i. ++ * ++ * push (0xfffffff0062990bc): words, 0x2030 = wr, flush reads of ++ * 0x2028/0x2030/0x201c/0x2020, 0x88 = 1, ++ * flush read of 0x2000 ++ * ISR (0xfffffff00629a0ac): read 0x410, 0x414 = 0x100000, then drain ++ * TO_HOST (0x201c host rd, 0x2020 fw wr) ++ * init (0xfffffff006298d40): one 0x80 message after the ready magic ++ * ++ * The firmware answers decode packets with a 0xc0 completion carrying the ++ * fence; it answers nothing else. 0x80 and the benign 0x83..0x85 are ++ * acknowledged only by TO_MTX_RD (0x2028) moving past them: the DEVA ++ * reader publishes the new read index before it dispatches (fw 0x1000a9c). ++ * That is what the boot and the debugfs ping wait for. ++ * ++ * Every ring access is bounded: indices are checked against the published ++ * capacity, the geometry against the MMIO window, message lengths against ++ * both sides' limits, and every wait has a timeout. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++#define VXD_MSG_CONSUME_TIMEOUT_US 100000 /* init + ping */ ++#define VXD_MSG_PUSH_TIMEOUT_US 100000 /* free-slot wait */ ++#define VXD_PING_COMPLETION_MS 20 ++#define VXD_DRAIN_MAX_MSGS 0x70 /* the kext's bound */ ++#define VXD_RING_MAX_CAP 0x4000 /* sanity bound, words */ ++#define VXD_RING_DUMP_MAX_WORDS 64 ++#define VXD_PING_COOKIE 0x50494e47 /* "PING" */ ++/* ++ * The firmware raises bit 20 only after posting a message, so an interrupt ++ * normally drains at least one. This many in a row with nothing to drain ++ * means 0x414 does not clear the level line: stop taking it rather than ++ * livelock the CPU, and leave the hand drain (ping) working. ++ */ ++#define VXD_IRQ_STORM_LIMIT 1000 ++ ++/* --- ring arithmetic: pure, shared with the self-test ------------------ */ ++ ++static u32 vxd_ring_next(u32 idx, u32 cap) ++{ ++ return idx + 1 < cap ? idx + 1 : 0; ++} ++ ++/* Words from @from forward to @to. */ ++static u32 vxd_ring_dist(u32 from, u32 to, u32 cap) ++{ ++ return to >= from ? to - from : to + cap - from; ++} ++ ++/* ++ * Free TO_MTX words, AppleD5500's helper (0xfffffff0062996b4 [E]): ++ * rd + ~wr, plus the capacity unless wr < rd. One word always stays free, ++ * so rd == wr means empty. ++ */ ++static u32 vxd_ring_free(u32 rd, u32 wr, u32 cap) ++{ ++ if (wr < rd) ++ return rd - wr - 1; ++ return rd - wr - 1 + cap; ++} ++ ++/* ++ * The reader has taken the @t message once it moved at least nwords past ++ * its start without overtaking the writer. A read index still behind the ++ * start (earlier messages pending) is "not yet". ++ */ ++static bool vxd_ring_consumed(const struct apple_vxd_msg_ticket *t, u32 rd, ++ u32 wr, u32 cap) ++{ ++ u32 done = vxd_ring_dist(t->start, rd, cap); ++ ++ return done >= t->nwords && done <= vxd_ring_dist(t->start, wr, cap); ++} ++ ++/* Apple's fence: (submission << 4) | (packet * 2 & 0xe) | core, core 0. */ ++static u16 vxd_fence_id(u16 seq, unsigned int packet) ++{ ++ return (seq << 4) | ((packet * 2) & 0xe); ++} ++ ++/* ++ * InitMessageBuffers [E]: w1 = firmware device address (the 0x6a00 value), ++ * w2 = (u8)seq << 16 | image size in 4 KiB pages (the blob, not the ++ * page-aligned allocation: 0x17e98 bytes -> 0x17), w3 = 0xffff0000. ++ */ ++void apple_vxd_build_init_msg(u32 msg[VXD_INIT_MSG_WORDS], u32 fw_va, ++ size_t fw_bytes, u16 seq) ++{ ++ msg[0] = apple_vxd_msg_hdr(0, VXD_MSG_INIT, VXD_INIT_MSG_WORDS); ++ msg[1] = fw_va; ++ msg[2] = FIELD_PREP(VXD_INIT_SEQ, seq & 0xff) | ++ FIELD_PREP(VXD_INIT_PAGES, fw_bytes >> 12); ++ msg[3] = VXD_INIT_SENTINEL; ++ msg[4] = 0; ++} ++ ++/* --- geometry ---------------------------------------------------------- */ ++ ++static int vxd_ring_parse(struct apple_vxd_dev *vxd, const char *name, ++ u32 raw, struct apple_vxd_ring *ring) ++{ ++ ring->base = FIELD_GET(VXD_RING_BASE, raw); ++ ring->cap = FIELD_GET(VXD_RING_CAP, raw); ++ ++ if (ring->cap <= VXD_MSG_TO_MTX_MAX_WORDS || ++ ring->cap > VXD_RING_MAX_CAP || ring->base & 3 || ++ VXD_RING_WINDOW + ring->base + 4 * ring->cap > vxd->regs_size) { ++ dev_err(vxd->dev, ++ "%s ring geometry %#010x (base %#x, %u words) is not usable: needs %u < words <= %u, a 4-byte aligned base and the ring inside the %#llx-byte register window\n", ++ name, raw, ring->base, ring->cap, ++ VXD_MSG_TO_MTX_MAX_WORDS, VXD_RING_MAX_CAP, ++ (unsigned long long)vxd->regs_size); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static u32 vxd_ring_reg(const struct apple_vxd_ring *ring, u32 idx) ++{ ++ return VXD_RING_WINDOW + ring->base + 4 * idx; ++} ++ ++/* ++ * Read the geometry the firmware published with the ready magic, once per ++ * boot. Called by the boot under power_lock. ++ */ ++int apple_vxd_mbox_open(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_ring to_mtx, to_host; ++ u32 raw_mtx, raw_host; ++ unsigned long flags; ++ unsigned int i; ++ int ret; ++ ++ raw_mtx = vxd_read(vxd, VXD_REG_TO_MTX_RING); ++ raw_host = vxd_read(vxd, VXD_REG_TO_HOST_RING); ++ dev_info(vxd->dev, ++ "ring geometry: TO_MTX (0x2024) %#010x, TO_HOST (0x2018) %#010x\n", ++ raw_mtx, raw_host); ++ ++ ret = vxd_ring_parse(vxd, "TO_MTX", raw_mtx, &to_mtx); ++ if (!ret) ++ ret = vxd_ring_parse(vxd, "TO_HOST", raw_host, &to_host); ++ if (ret) ++ return ret; ++ ++ if (to_mtx.base < to_host.base + 4 * to_host.cap && ++ to_host.base < to_mtx.base + 4 * to_mtx.cap) { ++ dev_err(vxd->dev, "TO_MTX and TO_HOST rings overlap\n"); ++ return -EINVAL; ++ } ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ vxd->to_mtx = to_mtx; ++ vxd->to_host = to_host; ++ memset(&vxd->stats, 0, sizeof(vxd->stats)); ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) ++ vxd->fences[i].state = APPLE_VXD_FENCE_FREE; ++ vxd->fw_panicked = false; ++ vxd->irq_storm = false; ++ vxd->irq_empty = 0; ++ vxd->mbox_ready = true; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ dev_info(vxd->dev, ++ "mailbox: TO_MTX regs %#x..%#x (%u words), TO_HOST regs %#x..%#x (%u words)\n", ++ vxd_ring_reg(&to_mtx, 0), vxd_ring_reg(&to_mtx, to_mtx.cap) - 1, ++ to_mtx.cap, vxd_ring_reg(&to_host, 0), ++ vxd_ring_reg(&to_host, to_host.cap) - 1, to_host.cap); ++ return 0; ++} ++ ++/* --- push -------------------------------------------------------------- */ ++ ++/* ++ * Push one message to TO_MTX in AppleD5500's exact order, waiting (bounded) ++ * for room. Process context only. @ticket records where it went so the ++ * caller can wait for the firmware to take it. ++ */ ++int apple_vxd_msg_push(struct apple_vxd_dev *vxd, const u32 *msg, ++ struct apple_vxd_msg_ticket *ticket) ++{ ++ u32 nwords = FIELD_GET(VXD_MSG_NWORDS, msg[0]); ++ u32 rd, wr, cap, i; ++ unsigned long flags; ++ ktime_t deadline; ++ ++ might_sleep(); ++ if (!nwords || nwords > VXD_MSG_TO_MTX_MAX_WORDS || msg[0] & BIT(7)) ++ return -EINVAL; ++ ++ deadline = ktime_add_us(ktime_get(), VXD_MSG_PUSH_TIMEOUT_US); ++ for (;;) { ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ if (!vxd->mbox_ready) { ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ return -ENODEV; ++ } ++ ++ cap = vxd->to_mtx.cap; ++ rd = vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ wr = vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ if (rd >= cap || wr >= cap) { ++ vxd->stats.malformed++; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ dev_err(vxd->dev, ++ "TO_MTX indices out of range: rd %u wr %u (capacity %u)\n", ++ rd, wr, cap); ++ return -EIO; ++ } ++ if (vxd_ring_free(rd, wr, cap) >= nwords) ++ break; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ if (ktime_after(ktime_get(), deadline)) { ++ dev_err(vxd->dev, ++ "TO_MTX full for 100 ms: rd %u wr %u, %u words free, %u needed\n", ++ rd, wr, vxd_ring_free(rd, wr, cap), nwords); ++ return -EBUSY; ++ } ++ usleep_range(100, 200); ++ } ++ ++ ticket->start = wr; ++ ticket->nwords = nwords; ++ for (i = 0; i < nwords; i++) { ++ vxd_write(vxd, vxd_ring_reg(&vxd->to_mtx, wr), msg[i]); ++ wr = vxd_ring_next(wr, cap); ++ } ++ ticket->end = wr; ++ vxd_write(vxd, VXD_REG_TO_MTX_WR_INDEX, wr); ++ ++ /* APB flush reads, doorbell, final flush: the kext's sequence. */ ++ vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX); ++ vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX); ++ vxd_write(vxd, VXD_REG_MTX_DOORBELL, 1); ++ vxd_read(vxd, VXD_REG_COMMS_STATE); ++ vxd->stats.sent++; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ return 0; ++} ++ ++static bool vxd_msg_is_consumed(struct apple_vxd_dev *vxd, ++ const struct apple_vxd_msg_ticket *ticket) ++{ ++ u32 rd = vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ u32 wr = vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ ++ if (rd >= vxd->to_mtx.cap || wr >= vxd->to_mtx.cap) ++ return false; ++ return vxd_ring_consumed(ticket, rd, wr, vxd->to_mtx.cap); ++} ++ ++/* Poll 0x2028 until the firmware has taken the message (bounded). */ ++int apple_vxd_msg_wait_consumed(struct apple_vxd_dev *vxd, ++ const struct apple_vxd_msg_ticket *ticket, ++ unsigned int timeout_us, unsigned int *waited_us) ++{ ++ ktime_t start = ktime_get(); ++ bool done; ++ int ret; ++ ++ ret = read_poll_timeout(vxd_msg_is_consumed, done, done, 50, ++ timeout_us, false, vxd, ticket); ++ if (waited_us) ++ *waited_us = ktime_us_delta(ktime_get(), start); ++ return ret; ++} ++ ++/* --- fences ------------------------------------------------------------ */ ++ ++/* 12-bit submission counter, never 0: fence 0 is the init message's. mbox_lock held. */ ++static u16 vxd_fence_next_seq_locked(struct apple_vxd_dev *vxd) ++{ ++ vxd->fence_seq = (vxd->fence_seq + 1) & 0xfff; ++ if (!vxd->fence_seq) ++ vxd->fence_seq = 1; ++ return vxd->fence_seq; ++} ++ ++/* mbox_lock held. */ ++static struct apple_vxd_fence *vxd_fence_arm_locked(struct apple_vxd_dev *vxd, ++ u16 id, u8 type) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) { ++ struct apple_vxd_fence *fence = &vxd->fences[i]; ++ ++ if (fence->state != APPLE_VXD_FENCE_FREE) ++ continue; ++ fence->id = id; ++ fence->type = type; ++ fence->status = 0; ++ fence->error = false; ++ fence->job = false; ++ fence->cmdbuf = NULL; ++ fence->state = APPLE_VXD_FENCE_ARMED; ++ reinit_completion(&fence->done); ++ return fence; ++ } ++ return NULL; ++} ++ ++struct apple_vxd_fence *apple_vxd_fence_arm(struct apple_vxd_dev *vxd, ++ unsigned int packet, u8 type) ++{ ++ struct apple_vxd_fence *fence; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ fence = vxd_fence_arm_locked(vxd, ++ vxd_fence_id(vxd_fence_next_seq_locked(vxd), ++ packet), type); ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ return fence; ++} ++ ++void apple_vxd_fence_release(struct apple_vxd_dev *vxd, ++ struct apple_vxd_fence *fence) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ /* Never completed: its command buffer stays with the device. */ ++ if (fence->cmdbuf) { ++ fence->cmdbuf->fence = NULL; ++ fence->cmdbuf = NULL; ++ } ++ fence->job = false; ++ fence->state = APPLE_VXD_FENCE_FREE; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++} ++ ++/* ++ * Called with mbox_lock held. A real 0xc0 (@device_done) means the core ++ * is finished with the bound command buffer, whatever the status; a panic ++ * or a teardown wakes the waiter but leaves the buffer quarantined until ++ * apple_vxd_cmdbufs_reclaim(). ++ */ ++static void vxd_fence_complete(struct apple_vxd_dev *vxd, ++ struct apple_vxd_fence *fence, u32 status, ++ bool error, bool device_done) ++{ ++ struct apple_vxd_cmdbuf *cb = fence->cmdbuf; ++ ++ if (cb) { ++ if (device_done) ++ apple_vxd_cmdbuf_retire_locked(cb); ++ else ++ cb->fence = NULL; ++ fence->cmdbuf = NULL; ++ } ++ fence->status = status; ++ fence->error = error; ++ fence->state = APPLE_VXD_FENCE_SIGNALLED; ++ complete_all(&fence->done); ++ /* The job is finished in process context (v4l2-m2m, runtime PM). */ ++ if (fence->job) ++ queue_work(system_highpri_wq, &vxd->job_done_work); ++} ++ ++/* Called with mbox_lock held: wake every waiter with an error. */ ++static void vxd_fences_fail_all(struct apple_vxd_dev *vxd) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) ++ if (vxd->fences[i].state == APPLE_VXD_FENCE_ARMED) ++ vxd_fence_complete(vxd, &vxd->fences[i], 0, true, false); ++} ++ ++/* --- drain ------------------------------------------------------------- */ ++ ++/* ++ * Pop one TO_HOST message (the kext's 0xfffffff0062997b0, consuming mode). ++ * mbox_lock held. Returns the word count, 0 if the ring is empty, or ++ * -EIO if it is corrupt. ++ */ ++static int vxd_msg_pop(struct apple_vxd_dev *vxd, u32 *msg) ++{ ++ u32 cap = vxd->to_host.cap; ++ u32 rd = vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX); ++ u32 wr = vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX); ++ u32 n, i; ++ ++ if (rd >= cap || wr >= cap) { ++ vxd->stats.malformed++; ++ dev_err_ratelimited(vxd->dev, ++ "TO_HOST indices out of range: rd %u wr %u (capacity %u)\n", ++ rd, wr, cap); ++ return -EIO; ++ } ++ if (rd == wr) ++ return 0; ++ ++ msg[0] = vxd_read(vxd, vxd_ring_reg(&vxd->to_host, rd)); ++ n = FIELD_GET(VXD_MSG_NWORDS, msg[0]); ++ if (n > VXD_MSG_TO_HOST_MAX_WORDS || n > vxd_ring_dist(rd, wr, cap)) { ++ /* ++ * The kext logs "received oversize fw->host msg" and stops; ++ * resync to the writer so one bad header cannot pin the ring. ++ */ ++ vxd->stats.malformed++; ++ dev_err_ratelimited(vxd->dev, ++ "TO_HOST message %#010x at %u claims %u words (%u queued); dropping the queue\n", ++ msg[0], rd, n, vxd_ring_dist(rd, wr, cap)); ++ vxd_write(vxd, VXD_REG_TO_HOST_RD_INDEX, wr); ++ return -EIO; ++ } ++ if (!n) { ++ /* The kext consumes a zero-length header as one word. */ ++ vxd->stats.malformed++; ++ n = 1; ++ } ++ ++ rd = vxd_ring_next(rd, cap); ++ for (i = 1; i < n; i++) { ++ msg[i] = vxd_read(vxd, vxd_ring_reg(&vxd->to_host, rd)); ++ rd = vxd_ring_next(rd, cap); ++ } ++ vxd_write(vxd, VXD_REG_TO_HOST_RD_INDEX, rd); ++ vxd->stats.received++; ++ return n; ++} ++ ++static void vxd_msg_completion(struct apple_vxd_dev *vxd, const u32 *msg, ++ u32 n) ++{ ++ u16 id = FIELD_GET(VXD_MSG_FENCE, msg[0]); ++ u32 status = n > 2 ? msg[2] : 0; ++ u32 w3 = n > 3 ? msg[3] : 0; ++ bool fault = status & VXD_COMPLETION_FAULT; ++ unsigned int i; ++ ++ vxd->stats.completions++; ++ if (fault) { ++ vxd->stats.faults++; ++ if ((status & VXD_COMPLETION_AFTER_RESET) && !w3) ++ dev_warn_ratelimited(vxd->dev, ++ "completion fence %#x after core reset (status %#x)\n", ++ id, status); ++ else ++ dev_err_ratelimited(vxd->dev, ++ "completion fence %#x with fault: status %#x, word1 %#x, word3 %#x\n", ++ id, status, n > 1 ? msg[1] : 0, w3); ++ } ++ if (status & VXD_COMPLETION_REATTEMPT) ++ dev_warn_ratelimited(vxd->dev, ++ "completion fence %#x asks for REATTEMPT_SINGLEPIPE (0x200c %#x)\n", ++ id, vxd_read(vxd, VXD_REG_FW_PC)); ++ ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) { ++ struct apple_vxd_fence *fence = &vxd->fences[i]; ++ ++ if (fence->state == APPLE_VXD_FENCE_ARMED && fence->id == id) { ++ vxd_fence_complete(vxd, fence, status, fault, true); ++ return; ++ } ++ } ++ ++ vxd->stats.unknown_fences++; ++ dev_warn_ratelimited(vxd->dev, ++ "completion for unknown fence %#x (status %#x)\n", ++ id, status); ++} ++ ++/* ProcessFWMessages' type switch (0xfffffff006299bb4 [E]). mbox_lock held. */ ++static void vxd_msg_dispatch(struct apple_vxd_dev *vxd, const u32 *msg, u32 n) ++{ ++ u8 type = FIELD_GET(VXD_MSG_TYPE, msg[0]); ++ ++ switch (type) { ++ case VXD_MSG_COMPLETION: ++ vxd_msg_completion(vxd, msg, n); ++ break; ++ case VXD_MSG_CONTIGUITY: ++ vxd->stats.contiguity++; ++ dev_warn_ratelimited(vxd->dev, ++ "firmware contiguity warning (%#x %#x)\n", ++ n > 1 ? msg[1] : 0, n > 2 ? msg[2] : 0); ++ break; ++ case VXD_MSG_PANIC: ++ vxd->stats.panics++; ++ vxd->fw_panicked = true; ++ dev_err_ratelimited(vxd->dev, ++ "FW_DEVA_PANIC: fence %#x, error int %#x; decode is off until the firmware is rebooted\n", ++ (u32)FIELD_GET(VXD_MSG_FENCE, msg[0]), ++ n > 1 ? msg[1] : 0); ++ vxd_fences_fail_all(vxd); ++ break; ++ default: ++ /* The kext drops 0xd1..0xd4 and 0xe1..0xe4 without a word. */ ++ if ((type >= 0xd1 && type <= 0xd4) || ++ (type >= 0xe1 && type <= 0xe4)) { ++ dev_dbg(vxd->dev, "firmware message %#x ignored\n", type); ++ break; ++ } ++ vxd->stats.unknown++; ++ dev_warn_ratelimited(vxd->dev, ++ "unknown message %#010x from the DEVA firmware (%u words)\n", ++ msg[0], n); ++ break; ++ } ++} ++ ++/* mbox_lock held. */ ++static unsigned int vxd_mbox_drain_locked(struct apple_vxd_dev *vxd) ++{ ++ u32 msg[VXD_MSG_TO_HOST_MAX_WORDS]; ++ unsigned int count; ++ int n; ++ ++ for (count = 0; count < VXD_DRAIN_MAX_MSGS; count++) { ++ n = vxd_msg_pop(vxd, msg); ++ if (n <= 0) ++ return count; ++ vxd_msg_dispatch(vxd, msg, n); ++ } ++ ++ dev_warn_ratelimited(vxd->dev, "too many messages in the fw2host queue\n"); ++ return count; ++} ++ ++/* ++ * Drain by hand: Apple's timeout path runs ProcessInterrupt manually, and ++ * the ping uses this to collect what an unarmed IRQ would have. ++ */ ++unsigned int apple_vxd_mbox_drain(struct apple_vxd_dev *vxd) ++{ ++ unsigned int count = 0; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ if (vxd->mbox_ready) ++ count = vxd_mbox_drain_locked(vxd); ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ return count; ++} ++ ++/* ++ * IRQ 157, level. Armed only while the firmware runs (the boot holds a ++ * runtime PM reference for that whole time, so the block is powered). ++ * Apple's order: status, clear, then drain. ++ */ ++irqreturn_t apple_vxd_irq(int irq, void *data) ++{ ++ struct apple_vxd_dev *vxd = data; ++ u32 status; ++ ++ spin_lock(&vxd->mbox_lock); ++ if (!vxd->mbox_ready) { ++ spin_unlock(&vxd->mbox_lock); ++ return IRQ_NONE; ++ } ++ ++ status = vxd_read(vxd, VXD_REG_IRQ_STATUS); ++ vxd->stats.last_irq_status = status; ++ if (!(status & VXD_IRQ_FW_MSG)) { ++ vxd->stats.spurious++; ++ spin_unlock(&vxd->mbox_lock); ++ return IRQ_NONE; ++ } ++ ++ vxd_write(vxd, VXD_REG_IRQ_CLEAR, VXD_IRQ_FW_MSG); ++ vxd->stats.irqs++; ++ if (vxd_mbox_drain_locked(vxd)) { ++ vxd->irq_empty = 0; ++ } else if (++vxd->irq_empty == VXD_IRQ_STORM_LIMIT) { ++ disable_irq_nosync(irq); ++ vxd->irq_storm = true; ++ dev_err(vxd->dev, ++ "IRQ %d: %u interrupts in a row with nothing to drain (0x410 %#x): 0x414 does not clear the line; IRQ disabled, the debugfs ping still drains by hand\n", ++ irq, VXD_IRQ_STORM_LIMIT, status); ++ } ++ spin_unlock(&vxd->mbox_lock); ++ ++ return IRQ_HANDLED; ++} ++ ++/* --- start / stop ------------------------------------------------------- */ ++ ++static void vxd_mbox_log_state(struct apple_vxd_dev *vxd, const char *what) ++{ ++ dev_info(vxd->dev, ++ "%s: TO_MTX rd %u wr %u, TO_HOST rd %u wr %u, 0x200c %#x, 0x0 %#x, comms state %#x, irq status %#x\n", ++ what, vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX), ++ vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX), ++ vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX), ++ vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX), ++ vxd_read(vxd, VXD_REG_FW_PC), ++ vxd_read(vxd, VXD_REG_MTX_MSG_CONSUMED), ++ vxd_read(vxd, VXD_REG_COMMS_STATE), ++ vxd_read(vxd, VXD_REG_IRQ_STATUS)); ++} ++ ++/* ++ * The mandatory 0x80 init message, then the IRQ. Without the init ++ * message the firmware has no picture of its own image and never ++ * schedules work (ds-vxd-decode-protocol.md section 3.1). Boot path, ++ * power_lock held, mailbox open. ++ */ ++int apple_vxd_mbox_start(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_msg_ticket ticket; ++ u32 msg[VXD_INIT_MSG_WORDS]; ++ unsigned int waited; ++ int ret; ++ ++ apple_vxd_build_init_msg(msg, vxd->fw_dev_va, vxd->fw_blob_size, ++ vxd->init_seq); ++ vxd_step(vxd->dev, ++ "mbox: push the 0x80 init message {%#x %#x %#x %#x %#x}", ++ msg[0], msg[1], msg[2], msg[3], msg[4]); ++ ret = apple_vxd_msg_push(vxd, msg, &ticket); ++ if (ret) { ++ dev_err(vxd->dev, "init message push failed: %d\n", ret); ++ return ret; ++ } ++ vxd->init_seq++; ++ ++ vxd_step(vxd->dev, ++ "mbox: wait for TO_MTX rd to reach %u (init message taken, 100 ms)", ++ ticket.end); ++ ret = apple_vxd_msg_wait_consumed(vxd, &ticket, ++ VXD_MSG_CONSUME_TIMEOUT_US, &waited); ++ if (ret) { ++ vxd_mbox_log_state(vxd, "init message NOT consumed after 100 ms"); ++ return ret; ++ } ++ dev_info(vxd->dev, "init message consumed after %u us\n", waited); ++ vxd_mbox_log_state(vxd, "after init"); ++ ++ vxd_step(vxd->dev, "mbox: arm IRQ %d (0x418 = %#x)", vxd->irq, ++ vxd_read(vxd, VXD_REG_IRQ_ENABLE)); ++ enable_irq(vxd->irq); ++ vxd->irq_armed = true; ++ return 0; ++} ++ ++/* Teardown, power_lock held: no IRQ, no pushes, every waiter woken. */ ++void apple_vxd_mbox_stop(struct apple_vxd_dev *vxd) ++{ ++ unsigned long flags; ++ ++ if (vxd->irq_armed) { ++ /* The storm guard already disabled it from the handler. */ ++ if (READ_ONCE(vxd->irq_storm)) ++ synchronize_irq(vxd->irq); ++ else ++ disable_irq(vxd->irq); ++ vxd->irq_armed = false; ++ } ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ if (vxd->mbox_ready) { ++ /* Quiesce the line the way known state does. */ ++ vxd_write(vxd, VXD_REG_IRQ_ENABLE, 0); ++ vxd_write(vxd, VXD_REG_IRQ_CLEAR, 0xffffffff); ++ } ++ vxd->mbox_ready = false; ++ vxd_fences_fail_all(vxd); ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++} ++ ++/* ++ * Decode submission: one picture's command buffer, one mailbox message per ++ * packet, in packet-list order (SubmitCommandBufferInternal ++ * 0xfffffff00628a8d4 [E]). The last message carries 0x4004 and its fence ++ * completes the job (apple_vxd_job_done(), process context); the buffer ++ * is bound to that fence and freed by its 0xc0. Returns 0 once every ++ * message is in TO_MTX; on an error nothing is left armed, and a buffer ++ * the firmware may already have been told about stays quarantined. ++ * ++ * Messages are built twice (checked before anything is armed, rebuilt for ++ * the push) so a 128-slice picture needs no per-packet array on the stack. ++ */ ++int apple_vxd_submit(struct apple_vxd_ctx *ctx, struct apple_vxd_cmdbuf *cb) ++{ ++ struct apple_vxd_fence *fences[APPLE_VXD_JOB_MAX_FENCES] = { }; ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ struct apple_vxd_msg_ticket ticket; ++ u32 msg[VXD_DECODE_MSG_WORDS]; ++ unsigned int i, narmed, first_armed, pushed = 0; ++ struct apple_vxd_fence *last = NULL; ++ unsigned long flags; ++ bool gart_dirty; ++ int n, ret; ++ size_t off; ++ u16 seq; ++ ++ n = apple_vxd_cmdbuf_validate(cb); ++ if (n <= 0 || n > APPLE_VXD_JOB_MAX_PACKETS) { ++ dev_err(vxd->dev, "decode: %d packets in the command buffer (1..%u)\n", ++ n, APPLE_VXD_JOB_MAX_PACKETS); ++ return -EINVAL; ++ } ++ narmed = n <= APPLE_VXD_JOB_MAX_FENCES ? n : 1; ++ first_armed = n - narmed; ++ ++ /* Every packet must make a message before anything is armed. */ ++ for (i = 0, off = 0; i < n; i++) { ++ u32 *pkt = (u32 *)(cb->pkt + off); ++ ++ ret = apple_vxd_packet_msg(vxd, pkt, 0, 0, i == n - 1, msg); ++ if (ret < 0) ++ return ret; ++ off += VXD_PKT_HDR_BYTES + pkt[1]; ++ } ++ ++ mutex_lock(&vxd->power_lock); ++ if (!vxd->mbox_ready || !vxd->hw_ready || vxd->fw_panicked) { ++ ret = -ENODEV; ++ goto out_unlock; ++ } ++ ++ /* Fences first: the firmware may answer before the last push. */ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ seq = vxd_fence_next_seq_locked(vxd); ++ for (i = 0; i < narmed; i++) { ++ fences[i] = vxd_fence_arm_locked(vxd, ++ vxd_fence_id(seq, first_armed + i), ++ VXD_MSG_DECODE); ++ if (!fences[i]) ++ break; ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ if (i < narmed) { ++ ret = -EBUSY; ++ goto out_fences; ++ } ++ ++ last = fences[narmed - 1]; ++ last->job = true; ++ ret = apple_vxd_cmdbuf_bind(vxd, cb, last); ++ if (ret) ++ goto out_fences; ++ ++ spin_lock_irqsave(&vxd->job_lock, flags); ++ if (vxd->job_active) { ++ spin_unlock_irqrestore(&vxd->job_lock, flags); ++ ret = -EBUSY; ++ goto out_unbind; ++ } ++ vxd->job.ctx = ctx; ++ vxd->job.nfences = narmed; ++ vxd->job.npackets = n; ++ vxd->job.seq = seq; ++ vxd->job.submitted = ktime_get(); ++ memcpy(vxd->job.fences, fences, sizeof(fences)); ++ vxd->job_active = true; ++ spin_unlock_irqrestore(&vxd->job_lock, flags); ++ ++ /* The kext rewrites 0x1008 before a submit that follows a GART change. */ ++ mutex_lock(&vxd->gart.lock); ++ gart_dirty = vxd->gart.dirty; ++ vxd->gart.dirty = false; ++ mutex_unlock(&vxd->gart.lock); ++ if (gart_dirty) ++ vxd_write(vxd, VXD_REG_MMU_CONTROL1, VXD_MMU_INVALDC_ALL); ++ ++ for (i = 0, off = 0; i < n; i++) { ++ u32 *pkt = (u32 *)(cb->pkt + off); ++ int type; ++ ++ off += VXD_PKT_HDR_BYTES + pkt[1]; ++ type = apple_vxd_packet_msg(vxd, pkt, vxd_fence_id(seq, i), 0, ++ i == n - 1, msg); ++ if (i >= first_armed) ++ fences[i - first_armed]->type = type; ++ vxd_step_io(vxd->dev, ++ "decode: push %#x fence %#x%s {%#x %#x %#x %#x %#x %#x}", ++ type, vxd_fence_id(seq, i), ++ i >= first_armed ? "" : " (not armed)", msg[0], ++ msg[1], msg[2], msg[3], msg[4], msg[5]); ++ ret = apple_vxd_msg_push(vxd, msg, &ticket); ++ if (ret) ++ break; ++ pushed++; ++ } ++ if (ret) { ++ dev_err(vxd->dev, "decode: push %u of %d failed: %d%s\n", pushed + 1, ++ n, ret, pushed ? "; the command buffer stays quarantined" : ""); ++ spin_lock_irqsave(&vxd->job_lock, flags); ++ vxd->job_active = false; ++ spin_unlock_irqrestore(&vxd->job_lock, flags); ++ if (pushed) ++ goto out_fences; /* release leaves the buffer DEVICE */ ++ goto out_unbind; ++ } ++ ++ schedule_delayed_work(&vxd->watchdog_work, ++ msecs_to_jiffies(APPLE_VXD_JOB_TIMEOUT_MS)); ++ mutex_unlock(&vxd->power_lock); ++ return 0; ++ ++out_unbind: ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ if (cb->state == APPLE_VXD_CMDBUF_DEVICE && cb->fence == last) { ++ cb->state = APPLE_VXD_CMDBUF_HOST; ++ cb->fence = NULL; ++ last->cmdbuf = NULL; ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++out_fences: ++ for (i = 0; i < narmed; i++) ++ if (fences[i]) ++ apple_vxd_fence_release(vxd, fences[i]); ++out_unlock: ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++} ++ ++/* --- debugfs ------------------------------------------------------------- */ ++ ++/* ++ * Debugfs boot trigger: write anything to trigger apple_vxd_boot_firmware ++ * (the R1 recipe) without an unbind/bind cycle. Returns 0 (bytes written) ++ * on success or the negative error, so the failure is visible at the ++ * shell: `echo 1 > /sys/kernel/debug/apple-vxd/boot`. ++ */ ++static ssize_t apple_vxd_boot_trigger_write(struct file *file, ++ const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ struct apple_vxd_dev *vxd = file->private_data; ++ int ret; ++ ++ ret = apple_vxd_boot_firmware(vxd); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++static const struct file_operations apple_vxd_boot_trigger_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_vxd_boot_trigger_write, ++ .llseek = default_llseek, ++}; ++ ++/* ++ * One benign message through the whole mailbox: push, doorbell, wait for ++ * TO_MTX rd to pass it (bounded), drain TO_HOST by hand, then give a fence ++ * completion a short window. 0x83..0x85 are dropped by the DEVA ++ * dispatcher, so "consumed, no completion" is the expected answer; 0x80 ++ * re-sends the init message (Apple's recovery path 0xfffffff00629964c ++ * does the same). ++ */ ++static int apple_vxd_ping(struct apple_vxd_dev *vxd, u8 type) ++{ ++ struct apple_vxd_fence *fence = NULL; ++ struct apple_vxd_msg_ticket ticket; ++ u32 msg[VXD_INIT_MSG_WORDS]; ++ unsigned int waited, drained; ++ int ret; ++ ++ if (type != VXD_MSG_INIT && ++ (type < VXD_MSG_NOP_FIRST || type > VXD_MSG_NOP_LAST)) ++ return -EINVAL; ++ ++ mutex_lock(&vxd->power_lock); ++ if (!vxd->mbox_ready) { ++ dev_err(vxd->dev, "ping: the firmware mailbox is not up (boot first)\n"); ++ ret = -ENODEV; ++ goto out_unlock; ++ } ++ ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) ++ goto out_unlock; ++ ++ if (type == VXD_MSG_INIT) { ++ apple_vxd_build_init_msg(msg, vxd->fw_dev_va, vxd->fw_blob_size, ++ vxd->init_seq); ++ } else { ++ fence = apple_vxd_fence_arm(vxd, 0, type); ++ if (!fence) { ++ ret = -EBUSY; ++ goto out_put; ++ } ++ msg[0] = apple_vxd_msg_hdr(fence->id, type, 2); ++ msg[1] = VXD_PING_COOKIE ^ fence->id; ++ } ++ ++ vxd_step(vxd->dev, "ping: push type %#x, fence %#x, %u words", ++ type, (u32)FIELD_GET(VXD_MSG_FENCE, msg[0]), ++ (u32)FIELD_GET(VXD_MSG_NWORDS, msg[0])); ++ ret = apple_vxd_msg_push(vxd, msg, &ticket); ++ if (ret) { ++ dev_err(vxd->dev, "ping: push failed: %d\n", ret); ++ goto out_fence; ++ } ++ if (type == VXD_MSG_INIT) ++ vxd->init_seq++; ++ ++ vxd_step(vxd->dev, ++ "ping: pushed at TO_MTX [%u..%u), doorbell rung; wait for rd to reach %u (100 ms)", ++ ticket.start, ticket.end, ticket.end); ++ ret = apple_vxd_msg_wait_consumed(vxd, &ticket, ++ VXD_MSG_CONSUME_TIMEOUT_US, &waited); ++ if (ret) ++ vxd_mbox_log_state(vxd, "ping: NOT consumed after 100 ms"); ++ else ++ dev_info(vxd->dev, "ping: type %#x consumed after %u us\n", ++ type, waited); ++ ++ drained = apple_vxd_mbox_drain(vxd); ++ if (fence && !ret) { ++ if (wait_for_completion_timeout(&fence->done, ++ msecs_to_jiffies(VXD_PING_COMPLETION_MS))) ++ dev_info(vxd->dev, ++ "ping: completion for fence %#x, status %#x%s\n", ++ fence->id, fence->status, ++ fence->error ? " (error)" : ""); ++ else ++ dev_info(vxd->dev, ++ "ping: no completion for fence %#x within %u ms (expected: the DEVA dispatcher drops 0x83-0x85 without a reply)\n", ++ fence->id, VXD_PING_COMPLETION_MS); ++ } ++ vxd_step(vxd->dev, "ping: done, rc %d, %u TO_HOST messages drained by hand", ++ ret, drained); ++ ++out_fence: ++ if (fence) ++ apple_vxd_fence_release(vxd, fence); ++out_put: ++ pm_runtime_put_autosuspend(vxd->dev); ++out_unlock: ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++} ++ ++/* `echo [0x83|0x84|0x85|0x80] > ping`; an empty write sends 0x83. */ ++static ssize_t apple_vxd_ping_write(struct file *file, const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ struct apple_vxd_dev *vxd = file->private_data; ++ u8 type = VXD_MSG_NOP_FIRST; ++ int ret; ++ ++ if (count > 1) { ++ ret = kstrtou8_from_user(ubuf, count, 0, &type); ++ if (ret) ++ return ret; ++ } ++ ++ ret = apple_vxd_ping(vxd, type); ++ return ret ? ret : count; ++} ++ ++static const struct file_operations apple_vxd_ping_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_vxd_ping_write, ++ .llseek = default_llseek, ++}; ++ ++struct vxd_comms_snap { ++ u32 rd, wr, r0, r200c, r2010; ++}; ++ ++static void vxd_comms_snap(struct apple_vxd_dev *vxd, struct vxd_comms_snap *s, ++ const char *when) ++{ ++ s->rd = vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ s->wr = vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ s->r0 = vxd_read(vxd, VXD_REG_MTX_MSG_CONSUMED); ++ s->r200c = vxd_read(vxd, VXD_REG_FW_PC); ++ s->r2010 = vxd_read(vxd, VXD_REG_COMMS_MSG_COUNT); ++ dev_info(vxd->dev, ++ "consume_check: %s: TO_MTX rd %u wr %u, 0x0 %#x, 0x200c %#x, 0x2010 %#x\n", ++ when, s->rd, s->wr, s->r0, s->r200c, s->r2010); ++} ++ ++static u32 vxd_to_mtx_rd(struct apple_vxd_dev *vxd) ++{ ++ return vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++} ++ ++/* ++ * Is TO_MTX_RD (0x2028) really written by the firmware? Every message so ++ * far was "consumed after 0 us", which a reader that moves rd before the ++ * host even looks would also produce. One benign 0x83 in three stages: ++ * ++ * 1. ring words written, write index NOT published: rd must not move ++ * (20 ms), and the ring window must read back what was written; ++ * 2. write index published, no doorbell: does the reader poll 0x2030 ++ * (rd moves) or wait for the doorbell (rd stays)? Either is a finding; ++ * 3. doorbell: rd must reach the new write index (100 ms). ++ * ++ * Returns 0 when stage 1 held and stage 3 was reached, -EIO when rd moved ++ * with nothing published, -ETIMEDOUT when the message was never taken. ++ * Same serialization as the ping (power_lock); refuses while a decode job ++ * is in flight. ++ */ ++static int apple_vxd_consume_check(struct apple_vxd_dev *vxd) ++{ ++ struct apple_vxd_fence *fence = NULL; ++ struct vxd_comms_snap s0, s1; ++ u32 msg[2], back[2], cap, wr, wr1, rd; ++ unsigned long flags; ++ bool moved_early = false, moved_nodb = false; ++ unsigned int i, drained; ++ ktime_t t0; ++ int ret, err; ++ ++ mutex_lock(&vxd->power_lock); ++ if (!vxd->mbox_ready) { ++ dev_err(vxd->dev, "consume_check: the firmware mailbox is not up (boot first)\n"); ++ ret = -ENODEV; ++ goto out_unlock; ++ } ++ if (READ_ONCE(vxd->job_active)) { ++ ret = -EBUSY; ++ goto out_unlock; ++ } ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) ++ goto out_unlock; ++ ++ fence = apple_vxd_fence_arm(vxd, 0, VXD_MSG_NOP_FIRST); ++ if (!fence) { ++ ret = -EBUSY; ++ goto out_put; ++ } ++ msg[0] = apple_vxd_msg_hdr(fence->id, VXD_MSG_NOP_FIRST, 2); ++ msg[1] = VXD_PING_COOKIE ^ fence->id; ++ ++ vxd_comms_snap(vxd, &s0, "before"); ++ cap = vxd->to_mtx.cap; ++ if (s0.rd >= cap || s0.wr >= cap || s0.rd != s0.wr) { ++ dev_err(vxd->dev, "consume_check: TO_MTX not idle (rd %u wr %u, capacity %u)\n", ++ s0.rd, s0.wr, cap); ++ ret = -EBUSY; ++ goto out_fence; ++ } ++ ++ /* Stage 1: words only. */ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ wr = s0.wr; ++ for (i = 0; i < ARRAY_SIZE(msg); i++) { ++ vxd_write(vxd, vxd_ring_reg(&vxd->to_mtx, wr), msg[i]); ++ wr = vxd_ring_next(wr, cap); ++ } ++ wr1 = wr; ++ wr = s0.wr; ++ for (i = 0; i < ARRAY_SIZE(msg); i++) { ++ back[i] = vxd_read(vxd, vxd_ring_reg(&vxd->to_mtx, wr)); ++ wr = vxd_ring_next(wr, cap); ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ vxd_step(vxd->dev, ++ "consume_check: stage 1: %#010x %#010x written at TO_MTX [%u..%u), read back %#010x %#010x, write index NOT published; wait 20 ms", ++ msg[0], msg[1], s0.wr, wr1, back[0], back[1]); ++ msleep(20); ++ rd = vxd_to_mtx_rd(vxd); ++ moved_early = rd != s0.rd; ++ dev_info(vxd->dev, "consume_check: stage 1: rd %u after 20 ms (%s), wr register %u, ring readback %s\n", ++ rd, moved_early ? "MOVED with nothing published: FAIL" : "unchanged: ok", ++ vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX), ++ back[0] == msg[0] && back[1] == msg[1] ? "matches" : "DIFFERS"); ++ ++ /* Stage 2: publish the write index, no doorbell. */ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ vxd_write(vxd, VXD_REG_TO_MTX_WR_INDEX, wr1); ++ vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX); ++ vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX); ++ vxd->stats.sent++; ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ t0 = ktime_get(); ++ err = read_poll_timeout(vxd_to_mtx_rd, rd, rd == wr1, 20, 50000, false, vxd); ++ moved_nodb = !err; ++ dev_info(vxd->dev, "consume_check: stage 2: write index %u published, no doorbell: rd %u, %s after %lld us\n", ++ wr1, rd, moved_nodb ? "taken (the reader polls 0x2030)" : ++ "not taken (the reader waits for the doorbell)", ++ ktime_us_delta(ktime_get(), t0)); ++ ++ /* Stage 3: doorbell. */ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ vxd_write(vxd, VXD_REG_MTX_DOORBELL, 1); ++ vxd_read(vxd, VXD_REG_COMMS_STATE); ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ t0 = ktime_get(); ++ err = read_poll_timeout(vxd_to_mtx_rd, rd, rd == wr1, 20, 100000, false, vxd); ++ dev_info(vxd->dev, "consume_check: stage 3: doorbell rung: rd %u, %s after %lld us\n", ++ rd, err ? "NOT taken" : "taken", ktime_us_delta(ktime_get(), t0)); ++ ++ drained = apple_vxd_mbox_drain(vxd); ++ vxd_comms_snap(vxd, &s1, "after"); ++ if (moved_early) ++ ret = -EIO; ++ else if (err) ++ ret = -ETIMEDOUT; ++ dev_info(vxd->dev, ++ "consume_check: %s: rd firmware-driven %s, reader %s, %u TO_HOST messages drained, 0x0 %s, 0x200c %s, 0x2010 %s\n", ++ ret ? "FAIL" : "PASS", moved_early ? "NO" : "yes", ++ moved_nodb ? "polls the write index" : "is doorbell-driven", drained, ++ s1.r0 == s0.r0 ? "unchanged" : "changed", ++ s1.r200c == s0.r200c ? "unchanged" : "changed", ++ s1.r2010 == s0.r2010 ? "unchanged" : "changed"); ++ ++out_fence: ++ apple_vxd_fence_release(vxd, fence); ++out_put: ++ pm_runtime_put_autosuspend(vxd->dev); ++out_unlock: ++ mutex_unlock(&vxd->power_lock); ++ return ret; ++} ++ ++static ssize_t apple_vxd_consume_check_write(struct file *file, ++ const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ int ret = apple_vxd_consume_check(file->private_data); ++ ++ return ret ? ret : count; ++} ++ ++static const struct file_operations apple_vxd_consume_check_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = apple_vxd_consume_check_write, ++ .llseek = default_llseek, ++}; ++ ++static void vxd_dump_ring(struct seq_file *m, struct apple_vxd_dev *vxd, ++ const char *name, const struct apple_vxd_ring *ring) ++{ ++ u32 i, n = min_t(u32, ring->cap, VXD_RING_DUMP_MAX_WORDS); ++ ++ seq_printf(m, "%s words (%u of %u):", name, n, ring->cap); ++ for (i = 0; i < n; i++) ++ seq_printf(m, "%s%08x", i % 8 ? " " : "\n ", ++ vxd_read(vxd, vxd_ring_reg(ring, i))); ++ seq_puts(m, "\n"); ++} ++ ++static const char *const vxd_fence_state_names[] = { ++ [APPLE_VXD_FENCE_FREE] = "free", ++ [APPLE_VXD_FENCE_ARMED] = "armed", ++ [APPLE_VXD_FENCE_SIGNALLED] = "signalled", ++}; ++ ++/* ++ * The dumps read MMIO, and the block autosuspends 100 ms after probe (the ++ * ps_vdec genpd then cuts it): take a runtime PM reference around every ++ * dump so a `cat` never reads an unpowered block. ++ */ ++static int apple_vxd_mbox_dump_show(struct seq_file *m, void *v) ++{ ++ struct apple_vxd_dev *vxd = m->private; ++ struct apple_vxd_mbox_stats st; ++ struct apple_vxd_ring to_mtx, to_host; ++ /* ++ * Only what the dump prints: a whole struct apple_vxd_fence carries a ++ * struct completion, which lockdep makes large (a 16-entry copy ++ * overflowed the 2 KiB frame limit in the QEMU smoke's KASAN build). ++ */ ++ struct { ++ enum apple_vxd_fence_state state; ++ u16 id; ++ u8 type; ++ bool error; ++ u32 status; ++ } fences[APPLE_VXD_NUM_FENCES]; ++ u32 raw_mtx, raw_host, rd, wr; ++ unsigned long flags; ++ bool ready; ++ unsigned int i; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) ++ return ret; ++ ++ spin_lock_irqsave(&vxd->mbox_lock, flags); ++ ready = vxd->mbox_ready; ++ to_mtx = vxd->to_mtx; ++ to_host = vxd->to_host; ++ st = vxd->stats; ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) { ++ fences[i].state = vxd->fences[i].state; ++ fences[i].id = vxd->fences[i].id; ++ fences[i].type = vxd->fences[i].type; ++ fences[i].error = vxd->fences[i].error; ++ fences[i].status = vxd->fences[i].status; ++ } ++ spin_unlock_irqrestore(&vxd->mbox_lock, flags); ++ ++ raw_mtx = vxd_read(vxd, VXD_REG_TO_MTX_RING); ++ raw_host = vxd_read(vxd, VXD_REG_TO_HOST_RING); ++ ++ seq_printf(m, "comms state %#x\n", vxd_read(vxd, VXD_REG_COMMS_STATE)); ++ seq_printf(m, "reg 0x0 %#x\n", vxd_read(vxd, VXD_REG_MTX_MSG_CONSUMED)); ++ seq_printf(m, "comms 0x2008 %#x\n", vxd_read(vxd, VXD_REG_COMMS_2008)); ++ seq_printf(m, "reg 0x200c %#x\n", vxd_read(vxd, VXD_REG_FW_PC)); ++ seq_printf(m, "msg count (2010) %#x\n", vxd_read(vxd, VXD_REG_COMMS_MSG_COUNT)); ++ seq_printf(m, "fw ready (2014) %#x%s\n", vxd_read(vxd, VXD_REG_FW_READY_MAGIC), ++ vxd->fw_loaded ? " (firmware alive)" : ""); ++ seq_printf(m, "TO_MTX (2024) %#010x: base %#lx, %lu words\n", raw_mtx, ++ FIELD_GET(VXD_RING_BASE, raw_mtx), FIELD_GET(VXD_RING_CAP, raw_mtx)); ++ seq_printf(m, "TO_HOST (2018) %#010x: base %#lx, %lu words\n", raw_host, ++ FIELD_GET(VXD_RING_BASE, raw_host), FIELD_GET(VXD_RING_CAP, raw_host)); ++ seq_printf(m, "to_host rd/wr %#x/%#x\n", ++ vxd_read(vxd, VXD_REG_TO_HOST_RD_INDEX), ++ vxd_read(vxd, VXD_REG_TO_HOST_WR_INDEX)); ++ rd = vxd_read(vxd, VXD_REG_TO_MTX_RD_INDEX); ++ wr = vxd_read(vxd, VXD_REG_TO_MTX_WR_INDEX); ++ seq_printf(m, "to_mtx rd/wr %#x/%#x", rd, wr); ++ if (ready && rd < to_mtx.cap && wr < to_mtx.cap) ++ seq_printf(m, " (free %u, %s)", vxd_ring_free(rd, wr, to_mtx.cap), ++ rd == wr ? "all consumed" : "pending"); ++ seq_puts(m, "\n"); ++ seq_printf(m, "comms flags %#x\n", vxd_read(vxd, VXD_REG_COMMS_FLAGS)); ++ seq_printf(m, "irq status/enable %#x/%#x\n", ++ vxd_read(vxd, VXD_REG_IRQ_STATUS), ++ vxd_read(vxd, VXD_REG_IRQ_ENABLE)); ++ seq_printf(m, "state mbox_ready %d irq_armed %d irq_storm %d hw_ready %d fw_panicked %d init_seq %u fence_seq %#x\n", ++ ready, vxd->irq_armed, vxd->irq_storm, vxd->hw_ready, ++ vxd->fw_panicked, vxd->init_seq, vxd->fence_seq); ++ seq_printf(m, "stats sent %u received %u irqs %u spurious %u completions %u unknown_fences %u faults %u contiguity %u panics %u unknown %u malformed %u last_irq_status %#x\n", ++ st.sent, st.received, st.irqs, st.spurious, st.completions, ++ st.unknown_fences, st.faults, st.contiguity, st.panics, ++ st.unknown, st.malformed, st.last_irq_status); ++ for (i = 0; i < APPLE_VXD_NUM_FENCES; i++) ++ if (fences[i].state != APPLE_VXD_FENCE_FREE) ++ seq_printf(m, "fence[%u] %s id %#x type %#x status %#x%s\n", ++ i, vxd_fence_state_names[fences[i].state], ++ fences[i].id, fences[i].type, fences[i].status, ++ fences[i].error ? " error" : ""); ++ ++ if (ready) { ++ vxd_dump_ring(m, vxd, "TO_MTX", &to_mtx); ++ vxd_dump_ring(m, vxd, "TO_HOST", &to_host); ++ } ++ ++ pm_runtime_put_autosuspend(vxd->dev); ++ return 0; ++} ++ ++static int apple_vxd_regs_dump_show(struct seq_file *m, void *v) ++{ ++ static const struct { const char *name; u32 off; } regs[] = { ++ { "core id (0x630)", VXD_REG_CORE_ID }, ++ { "core rev (0x640)", VXD_REG_CORE_REV }, ++ { "vec cfg (0x200)", VXD_REG_VECT_CFG }, ++ { "vec cfg2 (0x208)", VXD_REG_VECT_CFG2 }, ++ { "entdec size (0x340)", VXD_REG_ENTDEC_0_SIZE }, ++ { "entdec cfg (0x344)", VXD_REG_ENTDEC_0_CFG }, ++ { "cmd ctrl (0x400)", VXD_REG_CMD_CTRL }, ++ { "irq status (0x410)", VXD_REG_IRQ_STATUS }, ++ { "irq clear (0x414)", VXD_REG_IRQ_CLEAR }, ++ { "irq enable (0x418)", VXD_REG_IRQ_ENABLE }, ++ { "man clk enable (0x440)", VXD_REG_CR_PVDEC_MAN_CLK_ENABLE }, ++ { "mtx mem cfg (0x500)", VXD_REG_MTX_MEM_CFG }, ++ { "dmac status (0x504)", VXD_REG_DMAC_STATUS }, ++ { "mtx ctrl (0x1000)", VXD_REG_MTX_CTRL }, ++ { "mtx clk (0x1008)", VXD_REG_MTX_MAN_CLK_ENABLE }, ++ { "mmu 0 (0x1010)", VXD_REG_MTX_MMU_0 }, ++ { "mmu dir (0x1020)", VXD_REG_MTX_MMU_DIR_ADR }, ++ { "mmu mode (0x1070)", VXD_REG_MTX_MMU_MODE }, ++ { "mmu cfg0 (0x1080)", VXD_REG_MTX_MMU_CFG0 }, ++ { "mmu cfg1 (0x1084)", VXD_REG_MTX_MMU_CFG1 }, ++ { "pipe 4004", VXD_REG_PIPE_4004 }, ++ { "pipe ctrl (0x4020)", VXD_REG_PIPE_CTRL }, ++ { "pipe cr0 (0x4060)", VXD_REG_PIPE_CR0 }, ++ { "pipe cr1 (0x4068)", VXD_REG_PIPE_CR1 }, ++ { "pipe cr2 (0x4070)", VXD_REG_PIPE_CR2 }, ++ { "pipe cr3 (0x4078)", VXD_REG_PIPE_CR3 }, ++ { "pipe status (0x40c0)", VXD_REG_PIPE_STATUS }, ++ { "pipe status2 (0x40c4)", VXD_REG_PIPE_STATUS_2 }, ++ { "boot fw adr (0x6a00)", VXD_REG_MTX_BOOT_FW_ADR }, ++ { "boot 1 (0x6a04)", VXD_REG_MTX_BOOT_1 }, ++ { "boot 2 (0x6a08)", VXD_REG_MTX_BOOT_2 }, ++ { "boot 4 (0x6a14)", VXD_REG_MTX_BOOT_4 }, ++ { "boot 5 (0x6a18)", VXD_REG_MTX_BOOT_5 }, ++ }; ++ struct apple_vxd_dev *vxd = m->private; ++ unsigned int i; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < ARRAY_SIZE(regs); i++) ++ seq_printf(m, "%-24s %#x\n", regs[i].name, vxd_read(vxd, regs[i].off)); ++ ++ pm_runtime_put_autosuspend(vxd->dev); ++ return 0; ++} ++ ++DEFINE_SHOW_ATTRIBUTE(apple_vxd_mbox_dump); ++DEFINE_SHOW_ATTRIBUTE(apple_vxd_regs_dump); ++ ++int apple_vxd_mbox_init(struct apple_vxd_dev *vxd) ++{ ++ struct dentry *dir; ++ ++ if (!IS_ENABLED(CONFIG_DEBUG_FS)) ++ return 0; ++ ++ dir = debugfs_create_dir(APPLE_VXD_DRV_NAME, NULL); ++ if (IS_ERR(dir)) ++ return 0; /* debugfs is optional for bring-up */ ++ ++ vxd->debugfs_dir = dir; ++ debugfs_create_file("mbox", 0444, dir, vxd, &apple_vxd_mbox_dump_fops); ++ debugfs_create_file("regs", 0444, dir, vxd, &apple_vxd_regs_dump_fops); ++ debugfs_create_file("boot", 0200, dir, vxd, &apple_vxd_boot_trigger_fops); ++ debugfs_create_file("ping", 0200, dir, vxd, &apple_vxd_ping_fops); ++ debugfs_create_file("consume_check", 0200, dir, vxd, ++ &apple_vxd_consume_check_fops); ++ apple_vxd_cmdbufs_debugfs(vxd); ++ return 0; ++} ++ ++void apple_vxd_mbox_fini(struct apple_vxd_dev *vxd) ++{ ++ debugfs_remove_recursive(vxd->debugfs_dir); ++ vxd->debugfs_dir = NULL; ++} ++ ++/* --- self-test: protocol arithmetic only, no MMIO ------------------------ */ ++ ++int apple_vxd_mbox_selftest(struct apple_vxd_dev *vxd) ++{ ++ static const struct { u32 rd, wr, cap, free; } frees[] = { ++ { 0, 0, 16, 15 }, { 5, 3, 16, 1 }, { 3, 5, 16, 13 }, ++ { 0, 15, 16, 0 }, { 7, 6, 16, 0 }, ++ }; ++ const struct apple_vxd_msg_ticket t = { .start = 14, .nwords = 4, .end = 2 }; ++ u32 msg[VXD_INIT_MSG_WORDS]; ++ const char *what; ++ unsigned int i; ++ ++ what = "ring free-slot formula"; ++ for (i = 0; i < ARRAY_SIZE(frees); i++) ++ if (vxd_ring_free(frees[i].rd, frees[i].wr, frees[i].cap) != ++ frees[i].free) ++ goto fail; ++ ++ what = "ring wrap"; ++ if (vxd_ring_next(15, 16) || vxd_ring_next(3, 16) != 4 || ++ vxd_ring_dist(14, 2, 16) != 4) ++ goto fail; ++ ++ what = "consumption check"; ++ if (vxd_ring_consumed(&t, 14, 2, 16) || /* untouched */ ++ vxd_ring_consumed(&t, 0, 2, 16) || /* half taken */ ++ vxd_ring_consumed(&t, 10, 2, 16) || /* older pending */ ++ !vxd_ring_consumed(&t, 2, 2, 16) || /* taken */ ++ !vxd_ring_consumed(&t, 4, 6, 16)) /* taken, more queued */ ++ goto fail; ++ ++ what = "message header"; ++ if (apple_vxd_msg_hdr(0xabc0, VXD_MSG_NOP_FIRST, 2) != 0xabc08302 || ++ vxd_fence_id(0xabc, 1) != 0xabc2) ++ goto fail; ++ ++ what = "init message"; ++ apple_vxd_build_init_msg(msg, 0x12345000, 0x17e98, 0x1ff); ++ if (msg[0] != 0x8005 || msg[1] != 0x12345000 || msg[2] != 0xff0017 || ++ msg[3] != 0xffff0000 || msg[4]) ++ goto fail; ++ ++ what = "GART entry"; ++ if (VXD_GART_PTE(0x803758000ULL) != 0x08037581 || ++ VXD_GART_PTE(0xfffffff000ULL) != 0xfffffff1) ++ goto fail; ++ ++ return 0; ++ ++fail: ++ dev_err(vxd->dev, "mailbox self-test FAILED at %s\n", what); ++ return -EINVAL; ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-regs.h b/drivers/media/platform/apple/vxd/apple-vxd-regs.h +new file mode 100644 +index 0000000000000000000000000000000000000000..44dc742c2664521b50989fa25cd0e6f3c9bbad95 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-regs.h +@@ -0,0 +1,280 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apple VXD (Imagination MSVDX, "PowerVR video decoder") register map ++ * ++ * Offsets recovered by mining the iOS 16.7.10 com.apple.driver.AppleD5500 ++ * kext (iPad6,11 kernelcache, unslid virtual addresses); every offset below ++ * is reached through CPVDECCore::ReadReg/WriteReg in that kext. See ++ * docs/periph/ds-video-decoder.md. Names follow the public MSVDX layout used ++ * by Imagination-derived drivers; offset are facts, names are inferred. ++ */ ++#ifndef __APPLE_VXD_REGS_H__ ++#define __APPLE_VXD_REGS_H__ ++ ++#include ++ ++/* Core identification (CPVDECCore::GetCoreRev [E]) */ ++#define VXD_REG_CORE_ID 0x0630 ++#define VXD_CORE_ID_MAJOR_SH 16 ++#define VXD_CORE_ID_ENT_PIPES GENMASK(3, 0) ++#define VXD_CORE_ID_PIX_PIPES GENMASK(7, 4) ++#define VXD_REG_CORE_REV 0x0640 ++ ++/* ++ * Clock enables (CPVDECCore_Clocks.cpp SetClocks [E]). Written with the ++ * default value first, read back for a sanity check, then written with the ++ * requested value. A read-back mismatch means the core is not powered or ++ * not clocked ("Core register write differs from read"). ++ */ ++#define VXD_REG_CR_PVDEC_MAN_CLK_ENABLE 0x0440 ++#define VXD_CLK_ENABLE_DEFAULT 0x00000011 ++#define VXD_CLK_ENABLE_ALL 0xffffffff ++#define VXD_CLK_ENABLE_FW 0x01010113 ++ ++/* ++ * MTX (META) block. MTX_CTRL is read-modify-written with bits 0 and 16 ++ * taken from core flags; MTX_MAN_CLK_ENABLE is written with bit 28 set and ++ * polled for equality during the boot sequence. ++ */ ++#define VXD_REG_MTX_CTRL 0x1000 ++#define VXD_MTX_CTRL_KEEP_MASK 0xfffefffe ++#define VXD_REG_MTX_MAN_CLK_ENABLE 0x1008 ++/* ++ * The same 0x1008 seen from the submit path: the kext writes 0xf00 there ++ * before a decode submit that follows a GART change (+0x27a0 rule, ++ * 0xfffffff00628af2c [E]). In Imagination's BUS4 MMU layout (PVDEC, ++ * img-vxe-vxd) 0x1000 + 8 is MMU_CONTROL1 and bits 11:8 are MMU_INVALDC, ++ * one directory-cache invalidate per context [I]. ++ */ ++#define VXD_REG_MMU_CONTROL1 0x1008 ++#define VXD_MMU_INVALDC_ALL 0x00000f00 ++#define VXD_MTX_CLK_SEL 0x10000000 ++#define VXD_REG_MTX_MMU_0 0x1010 ++#define VXD_REG_MTX_MMU_DIR_ADR 0x1020 ++#define VXD_REG_MTX_MMU_MODE 0x1070 ++#define VXD_MTX_MMU_MODE_40BIT 0x10 ++#define VXD_REG_MTX_MMU_CFG0 0x1080 ++#define VXD_MTX_MMU_CFG0_40BIT 0x80 /* (cfg0 & 0xf0) == 0x80 */ ++#define VXD_REG_MTX_MMU_CFG1 0x1084 ++#define VXD_MTX_MMU_CFG1_TILING_CTX BIT(29) ++ ++/* ++ * MTX boot block, programmed by LoadFirmwareAndStartCore [E]. BOOT_5 (7) ++ * enables the MTX clocks and must be written before the rest of the block; ++ * BOOT_1 is written twice (stage 0 then stage 1) to release the MTX from ++ * reset; BOOT_FW_ADR holds the low 32 bits of the firmware buffer address. ++ */ ++#define VXD_REG_MTX_BOOT_FW_ADR 0x6a00 ++#define VXD_REG_MTX_BOOT_1 0x6a04 ++#define VXD_MTX_BOOT_1_STAGE0 0x22000000 ++#define VXD_MTX_BOOT_1_STAGE1 0x22010000 ++#define VXD_REG_MTX_BOOT_2 0x6a08 ++#define VXD_MTX_BOOT_2_VALUE 0x01000000 ++#define VXD_REG_MTX_BOOT_3 0x6a0c ++#define VXD_REG_MTX_BOOT_4 0x6a14 ++#define VXD_MTX_BOOT_4_VALUE 0x00000350 ++#define VXD_REG_MTX_BOOT_5 0x6a18 ++#define VXD_MTX_BOOT_5_ENABLE 0x7 ++ ++/* VEC block and entropy decoder pipes */ ++#define VXD_REG_VECT_CFG 0x0200 ++#define VXD_REG_VECT_CFG2 0x0208 ++#define VXD_VECT_CFG2_MSGS_MINUS1 0x000000c7 /* core[0xb4] - 1 */ ++#define VXD_REG_ENTDEC_0_SIZE 0x0340 ++#define VXD_REG_ENTDEC_0_CFG 0x0344 ++ ++/* ++ * MTX -> host "message consumed" word: the firmware stores the word count ++ * of the message it last took from TO_MTX here (fw 0x1000a90 [E]). ++ */ ++#define VXD_REG_MTX_MSG_CONSUMED 0x0000 ++ ++/* Host -> MTX doorbell, written 1 after every message push [E]. */ ++#define VXD_REG_MTX_DOORBELL 0x0088 ++ ++/* ++ * CMD block control. The reset value 0xffff0000 is written before and ++ * after firmware load (known state v1/v2 [E]). ++ */ ++#define VXD_REG_CMD_CTRL 0x0400 ++#define VXD_CMD_CTRL_RESET 0xffff0000 ++ ++/* ++ * Host interrupt: status / write-1-to-clear / enable. The firmware raises ++ * bit 20 by writing it to 0x410 when it posts a TO_HOST message with the ++ * interrupt flag (fw 0x1000fe4 [E]); the kext ISR reads 0x410, clears with ++ * 0x414 = 0x100000 before draining (0xfffffff00629a0ac [E]). Known state ++ * writes 0 to the enable and all-ones to the clear; boot enables bit 20. ++ */ ++#define VXD_REG_IRQ_STATUS 0x0410 ++#define VXD_REG_IRQ_CLEAR 0x0414 ++#define VXD_REG_IRQ_ENABLE 0x0418 ++#define VXD_IRQ_FW_MSG BIT(20) ++ ++/* DMAC */ ++#define VXD_REG_MTX_MEM_CFG 0x0500 ++#define VXD_MTX_MEM_BANK_COUNT GENMASK(11, 8) ++#define VXD_MTX_MEM_BANK_SIZE GENMASK(19, 16) ++#define VXD_MTX_MEM_LAST_BANK GENMASK(27, 24) ++#define VXD_REG_DMAC_STATUS 0x0504 ++#define VXD_DMAC_STATUS_FLUSH 0x80000000 ++ ++/* ++ * Pipe select + per-pipe registers. The block multiplexes several register ++ * banks behind CMD_KICK: writing the pipe index (1-based) there selects ++ * which pipe the 0x30xx/0x40xx banks talk to. ++ */ ++#define VXD_REG_CMD_KICK 0x0460 ++#define VXD_REG_PIPE_SET_0 0x3020 ++#define VXD_REG_PIPE_SET_1 0x3030 ++#define VXD_REG_PIPE_SET_2 0x3038 ++#define VXD_REG_PIPE_4004 0x4004 ++#define VXD_REG_PIPE_CTRL 0x4020 ++#define VXD_REG_PIPE_CR0 0x4060 ++#define VXD_REG_PIPE_CR1 0x4068 ++#define VXD_REG_PIPE_CR2 0x4070 ++#define VXD_REG_PIPE_CR3 0x4078 ++#define VXD_REG_PIPE_STATUS 0x40c0 ++#define VXD_REG_PIPE_STATUS_2 0x40c4 ++ ++/* ++ * Host<->MTX message rings (comms), docs/periph/ds-vxd-decode-protocol.md ++ * section 2. The four indices are confirmed by the kext recovery path: ++ * 0x201c TO_HOST_RD_INDEX (host) 0x2020 TO_HOST_WR_INDEX (fw) ++ * 0x2028 TO_MTX_RD_INDEX (fw) 0x2030 TO_MTX_WR_INDEX (host) ++ * FW_READY_MAGIC is polled (0 = the kext clears it, 0xa5a5a5a5 = firmware ++ * alive). After the magic the firmware has published each ring's geometry ++ * as (base_bytes << 16) | capacity_words: word i of a ring is the register ++ * at VXD_RING_WINDOW + base + 4 * i. Rounds 1-3 called 0x2018/0x2024 ++ * "build halves". ++ */ ++#define VXD_REG_COMMS_STATE 0x2000 ++#define VXD_REG_COMMS_2008 0x2008 /* cleared at ring setup [E] */ ++#define VXD_REG_FW_PC 0x200c /* fw stores its PC; host clears */ ++#define VXD_REG_COMMS_MSG_COUNT 0x2010 /* fw->host messages posted */ ++#define VXD_REG_FW_READY_MAGIC 0x2014 ++#define VXD_FW_READY_MAGIC 0xa5a5a5a5 ++#define VXD_REG_TO_HOST_RING 0x2018 ++#define VXD_REG_TO_HOST_RD_INDEX 0x201c ++#define VXD_REG_TO_HOST_WR_INDEX 0x2020 ++#define VXD_REG_TO_MTX_RING 0x2024 ++#define VXD_REG_TO_MTX_RD_INDEX 0x2028 ++#define VXD_REG_COMMS_FLAGS 0x202c ++#define VXD_REG_TO_MTX_WR_INDEX 0x2030 ++ ++#define VXD_RING_WINDOW 0x2000 ++#define VXD_RING_BASE GENMASK(31, 16) ++#define VXD_RING_CAP GENMASK(15, 0) ++ ++/* ++ * Message header, both directions: (fence << 16) | (type << 8) | nwords, ++ * nwords counting the header. The firmware drops TO_MTX messages longer ++ * than 0x14 words (fw 0x1000b80) and a zero-length header wedges its ++ * reader (fw 0x1000a4c: it re-publishes the same read index forever). ++ * The kext refuses TO_HOST messages of 0xf0 / 4 = 60 words or more. ++ */ ++#define VXD_MSG_NWORDS GENMASK(6, 0) ++#define VXD_MSG_TYPE GENMASK(15, 8) ++#define VXD_MSG_FENCE GENMASK(31, 16) ++#define VXD_MSG_TO_MTX_MAX_WORDS 0x14 ++#define VXD_MSG_TO_HOST_MAX_WORDS (0xf0 / 4 - 1) ++ ++/* ++ * Host -> firmware types: the DEVA dispatcher (fw 0x1000ad4) handles ++ * 0x80..0x8b. 0x83..0x85 jump straight back to the reader loop: they are ++ * consumed and dropped, which makes them the benign ping. ++ */ ++#define VXD_MSG_INIT 0x80 ++#define VXD_MSG_DECODE 0x81 ++#define VXD_MSG_NOP_FIRST 0x83 ++#define VXD_MSG_NOP_LAST 0x85 ++#define VXD_MSG_DEBLOCK 0x87 ++#define VXD_MSG_RENDER_TARGET 0x88 ++ ++/* Firmware -> host types (ProcessFWMessages 0xfffffff006299bb4 [E]). */ ++#define VXD_MSG_COMPLETION 0xc0 ++#define VXD_MSG_CONTIGUITY 0xc6 ++#define VXD_MSG_PANIC 0xc7 ++ ++/* 0xc0 word 2: fault class, "after core reset", REATTEMPT_SINGLEPIPE. */ ++#define VXD_COMPLETION_FAULT GENMASK(28, 27) ++#define VXD_COMPLETION_AFTER_RESET BIT(28) ++#define VXD_COMPLETION_REATTEMPT BIT(31) ++ ++/* 0x80 init message (InitMessageBuffers 0xfffffff006298d40 [E]). */ ++#define VXD_INIT_MSG_WORDS 5 ++#define VXD_INIT_PAGES GENMASK(15, 0) ++#define VXD_INIT_SEQ GENMASK(23, 16) ++#define VXD_INIT_SENTINEL 0xffff0000 ++ ++/* ++ * Decode-family message w1 low-half flags (SubmitCommandBufferInternal ++ * 0xfffffff00628a8d4 [E]): last packet of a submission | interrupt the ++ * host on completion; more packets follow. ++ */ ++#define VXD_MSG_FLAG_LAST 0x4004 ++#define VXD_MSG_FLAG_MORE 0x2000 ++ ++/* ++ * MSVDX command stream (docs/periph/ds-vxd-cmdstream.md section 2, the ++ * emitters at 0xfffffff00629a34c..0xfffffff00629b15c [E]). These words ++ * live in the GART-mapped command buffer and are read by the core's ++ * command processor, not by the DEVA firmware. ++ */ ++#define VXD_CMD_REG_RUN 0x10000000 /* | count << 16 | reg */ ++#define VXD_CMD_REG_RUN_ONE 0x00010000 /* count += 1 */ ++#define VXD_CMD_REG_RUN_COUNT GENMASK(27, 16) ++#define VXD_CMD_REG1 0x000f0000 /* | (flags & 0xf0) << 16 | reg */ ++#define VXD_CMD_REG1_FLAGS GENMASK(7, 4) ++#define VXD_CMD_REG_ADDR GENMASK(15, 0) ++#define VXD_CMD_SLICE_BLOCK 0x90000000 /* + 9 zero words */ ++#define VXD_CMD_SLICE_BLOCK_WORDS 10 ++#define VXD_CMD_SLICE_DESC 0xb0000001 /* + 2 words */ ++#define VXD_CMD_LOAD 0xa0000000 /* | bytes, + device address */ ++#define VXD_CMD_LOAD_MAX GENMASK(27, 0) ++#define VXD_CMD_TABLE_XFER 0xf0000000 /* | bytes, + address, + dest */ ++#define VXD_CMD_END_SLICE 0x60000000 ++/* ++ * AVC additions (ds-vxd-cmdstream.md section 16.3-16.6; Imagination's ++ * DXVA control-allocation names from psb_video's dxva_fw_ctrl.h): ++ * 0x5 CMD_RENDEC_BLOCK 0x50000000 | flags | count << 16 | reg, values ++ * 0xe CMD_DMA 0xe0000000 | type | bytes, device address ++ * 0xb CMD_SR_SETUP flag 1 = ENABLE_RBDU_EXTRACTION (the core strips the ++ * emulation-prevention bytes itself) ++ */ ++#define VXD_CMD_BLOCK 0x50000000 ++#define VXD_CMD_BLOCK_COUNT GENMASK(26, 16) ++#define VXD_CMD_BLOCK_FLAG_REFS 0x08000000 /* on the 0x7100 block */ ++#define VXD_CMD_DMA 0xe0000000 ++#define VXD_CMD_DMA_BYTES GENMASK(15, 0) ++#define VXD_CMD_DMA_TYPE_VLC 0x00000000 ++#define VXD_CMD_CHUNK_ALIGN 64 /* zero pad between slices */ ++ ++/* ++ * Host-side packet list (CmdBuf::addCommand 0xfffffff0062982f8 [E]): ++ * {magic0, payload bytes, magic1} + payload, back to back. The submit ++ * path walks it from its base, checks both magics (0xfffffff00628ac78) and ++ * turns every packet into one mailbox message; the list itself never ++ * reaches the device. ++ */ ++#define VXD_PKT_MAGIC0 0x80000007 ++#define VXD_PKT_MAGIC1 0xabcdefab ++#define VXD_PKT_HDR_BYTES 12 ++#define VXD_PKT_H264_PAYLOAD 0x78 ++/* Payload offsets (header-relative offset - 0xc). */ ++#define VXD_PKT_FLAGS 0x04 /* -> w1[15:0] */ ++#define VXD_PKT_W4 0x08 /* -> w4 */ ++#define VXD_PKT_CMD_BYTES 0x0c /* -> w1[31:16] (>> 2, kind 4: >> 3) */ ++#define VXD_PKT_W2 0x10 /* -> w2 */ ++#define VXD_PKT_W5 0x24 /* -> w5 */ ++#define VXD_PKT_W6 0x28 /* -> w6, 7-word messages only */ ++#define VXD_PKT_KIND 0x4c ++#define VXD_PKT_KIND_PICTURE 1 /* -> 0x81 */ ++#define VXD_PKT_KIND_DEBLOCK 3 /* -> 0x87 */ ++#define VXD_PKT_KIND_RENDER_TARGET 4 /* -> 0x88 */ ++#define VXD_PKT_NOT_REF 0x50 /* AVC: host-side bookkeeping */ ++#define VXD_DECODE_MSG_WORDS 6 ++ ++#define VXD_MMIO_SIZE 0x100000 ++ ++#endif /* __APPLE_VXD_REGS_H__ */ +diff --git a/drivers/media/platform/apple/vxd/apple-vxd-v4l2.c b/drivers/media/platform/apple/vxd/apple-vxd-v4l2.c +new file mode 100644 +index 0000000000000000000000000000000000000000..12ff4848f324cdddef90ff03daf908ca78a4f07b +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd-v4l2.c +@@ -0,0 +1,1405 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Apple VXD - V4L2 stateless m2m device (R3/R4 skeleton) ++ * ++ * Structure follows the rkvdec stateless template: one /dev/video node, ++ * media controller entity, mplane m2m queues with the request API ++ * mandatory on the output queue, and the stateless H.264 control set. ++ * The hardware submission layer behind the H.264 backend refuses jobs ++ * until the command-stream generator (R3c) lands; that failure path is ++ * part of the bring-up contract (test plan T6). ++ * ++ * Round 3: every handler body takes the context, not the file, so the ++ * register-time self-test runs the same code as open(), the format ioctls ++ * and release() on a private context before the node is exposed. Every ++ * V4L2 entry point announces itself with a flushed STEP line; nothing here ++ * touches state that only the firmware boot creates (the only consumer of ++ * hw_ready is the H.264 job path, which fails the job instead). ++ */ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "apple-vxd.h" ++ ++/* Smallest bitstream buffer handed to userspace. */ ++#define APPLE_VXD_MIN_BITSTREAM_SIZE SZ_1M ++ ++/* ++ * The VXD is not DMA-coherent, so coherent dma-contig buffers are mapped ++ * uncached for the CPU. Every consumer that reads decoded pictures on the ++ * CPU (FFmpeg's hwframe download, GStreamer's videoconvert, Chromium's ++ * NV12->ARGB converter) then reads uncached memory. capture_cached=1 ++ * makes the CAPTURE queues of contexts opened afterwards allocate MMAP ++ * buffers non-coherent (cacheable): vb2 cleans them at QBUF and ++ * invalidates them at DQBUF, and userspace only reads a picture after ++ * DQBUF. They stay physically contiguous, so the GART path is unchanged. ++ */ ++static bool apple_vxd_capture_cached; ++module_param_named(capture_cached, apple_vxd_capture_cached, bool, 0644); ++MODULE_PARM_DESC(capture_cached, ++ "cacheable MMAP CAPTURE buffers for contexts opened afterwards (vb2 syncs at QBUF/DQBUF)"); ++ ++static struct apple_vxd_ctx *file_to_vxd_ctx(struct file *file) ++{ ++ return container_of(file_to_v4l2_fh(file), struct apple_vxd_ctx, fh); ++} ++ ++static void apple_vxd_reset_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_format *f, u32 fourcc) ++{ ++ memset(f, 0, sizeof(*f)); ++ f->fmt.pix_mp.pixelformat = fourcc; ++ f->fmt.pix_mp.field = V4L2_FIELD_NONE; ++ f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; ++ f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; ++ f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; ++ f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; ++} ++ ++/* ++ * Every MSVDX surface base register (luma and chroma reconstruction, ++ * references) drops the low 12 bits, and the extended row stride counts ++ * 64-byte units. With single-plane NV12 the chroma plane starts at ++ * bytesperline * height, so the luma stride is rounded up to 256 bytes: ++ * with the height a multiple of 16 that keeps the chroma plane 4 KiB ++ * aligned behind a page-aligned buffer. ++ */ ++#define APPLE_VXD_NV12_STRIDE_ALIGN 256 ++ ++static void apple_vxd_fill_decoded_pixfmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_pix_format_mplane *pix_mp) ++{ ++ struct v4l2_plane_pix_format *plane = &pix_mp->plane_fmt[0]; ++ ++ v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, ++ pix_mp->width, pix_mp->height); ++ if (pix_mp->pixelformat == V4L2_PIX_FMT_NV12) { ++ plane->bytesperline = ALIGN(pix_mp->width, ++ APPLE_VXD_NV12_STRIDE_ALIGN); ++ plane->sizeimage = plane->bytesperline * pix_mp->height * 3 / 2; ++ } ++} ++ ++static const struct apple_vxd_decoded_fmt_desc * ++apple_vxd_find_decoded_fmt(const struct apple_vxd_coded_fmt_desc *desc, ++ enum apple_vxd_image_fmt image_fmt) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < desc->num_decoded_fmts; i++) ++ if (desc->decoded_fmts[i].image_fmt == image_fmt) ++ return &desc->decoded_fmts[i]; ++ ++ return NULL; ++} ++ ++static u32 apple_vxd_enum_decoded_fmt(struct apple_vxd_ctx *ctx, int index, ++ enum apple_vxd_image_fmt image_fmt) ++{ ++ const struct apple_vxd_decoded_fmt_desc *dfmt; ++ ++ if (image_fmt == APPLE_VXD_IMG_FMT_ANY) ++ image_fmt = APPLE_VXD_IMG_FMT_420_8BIT; ++ dfmt = apple_vxd_find_decoded_fmt(ctx->coded_fmt_desc, image_fmt); ++ if (dfmt && !index) ++ return dfmt->fourcc; ++ return 0; ++} ++ ++static bool apple_vxd_is_valid_fmt(struct apple_vxd_ctx *ctx, u32 fourcc, ++ enum apple_vxd_image_fmt image_fmt) ++{ ++ return apple_vxd_enum_decoded_fmt(ctx, 0, image_fmt) == fourcc; ++} ++ ++static const struct apple_vxd_coded_fmt_desc * ++apple_vxd_find_coded_fmt_desc(struct apple_vxd_ctx *ctx, u32 fourcc) ++{ ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ unsigned int i; ++ ++ for (i = 0; i < vxd->num_coded_fmts; i++) ++ if (vxd->coded_fmts[i].fourcc == fourcc) ++ return &vxd->coded_fmts[i]; ++ return NULL; ++} ++ ++static void apple_vxd_reset_coded_fmt(struct apple_vxd_ctx *ctx) ++{ ++ struct v4l2_format *f = &ctx->coded_fmt; ++ ++ /* apple_vxd_ctx_init() checked vxd->coded_fmts before calling us. */ ++ ctx->coded_fmt_desc = &ctx->vxd->coded_fmts[0]; ++ apple_vxd_reset_fmt(ctx, f, ctx->coded_fmt_desc->fourcc); ++ f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ f->fmt.pix_mp.width = ctx->coded_fmt_desc->frmsize.min_width; ++ f->fmt.pix_mp.height = ctx->coded_fmt_desc->frmsize.min_height; ++ /* Same normalization as TRY_FMT: one plane, a usable bitstream size. */ ++ f->fmt.pix_mp.num_planes = 1; ++ f->fmt.pix_mp.plane_fmt[0].sizeimage = APPLE_VXD_MIN_BITSTREAM_SIZE; ++ ++ if (ctx->coded_fmt_desc->ops->adjust_fmt) ++ ctx->coded_fmt_desc->ops->adjust_fmt(ctx, f); ++} ++ ++static void apple_vxd_reset_decoded_fmt(struct apple_vxd_ctx *ctx) ++{ ++ struct v4l2_format *f = &ctx->decoded_fmt; ++ u32 fourcc = apple_vxd_enum_decoded_fmt(ctx, 0, ctx->image_fmt); ++ ++ apple_vxd_reset_fmt(ctx, f, fourcc); ++ f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ f->fmt.pix_mp.width = ctx->coded_fmt.fmt.pix_mp.width; ++ f->fmt.pix_mp.height = ctx->coded_fmt.fmt.pix_mp.height; ++ apple_vxd_fill_decoded_pixfmt(ctx, &f->fmt.pix_mp); ++} ++ ++static int apple_vxd_image_fmt_changed(struct apple_vxd_ctx *ctx, ++ enum apple_vxd_image_fmt image_fmt) ++{ ++ if (image_fmt == APPLE_VXD_IMG_FMT_ANY) ++ return ctx->image_fmt != image_fmt; ++ ++ return apple_vxd_find_decoded_fmt(ctx->coded_fmt_desc, image_fmt) != ++ apple_vxd_find_decoded_fmt(ctx->coded_fmt_desc, ctx->image_fmt); ++} ++ ++static int apple_vxd_try_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct apple_vxd_ctx *ctx = ++ container_of(ctrl->handler, struct apple_vxd_ctx, ctrl_hdl); ++ const struct apple_vxd_coded_fmt_desc *desc = ctx->coded_fmt_desc; ++ ++ if (desc->ops->try_ctrl) ++ return desc->ops->try_ctrl(ctx, ctrl); ++ return 0; ++} ++ ++/* ++ * Also runs from v4l2_ctrl_handler_setup() inside apple_vxd_ctx_init(), so ++ * the m2m context (and its queues) must exist before the controls do. ++ */ ++static int apple_vxd_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct apple_vxd_ctx *ctx = ++ container_of(ctrl->handler, struct apple_vxd_ctx, ctrl_hdl); ++ const struct apple_vxd_coded_fmt_desc *desc = ctx->coded_fmt_desc; ++ enum apple_vxd_image_fmt image_fmt; ++ struct vb2_queue *vq; ++ ++ if (!desc->ops->get_image_fmt) ++ return 0; ++ ++ image_fmt = desc->ops->get_image_fmt(ctx, ctrl); ++ if (apple_vxd_image_fmt_changed(ctx, image_fmt)) { ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, ++ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ ctx->image_fmt = image_fmt; ++ apple_vxd_reset_decoded_fmt(ctx); ++ } ++ ++ return 0; ++} ++ ++const struct v4l2_ctrl_ops apple_vxd_ctrl_ops = { ++ .try_ctrl = apple_vxd_try_ctrl, ++ .s_ctrl = apple_vxd_s_ctrl, ++}; ++ ++static int apple_vxd_add_ctrls(struct apple_vxd_ctx *ctx, ++ const struct apple_vxd_ctrls *ctrls) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ctrls->num_ctrls; i++) { ++ v4l2_ctrl_new_custom(&ctx->ctrl_hdl, &ctrls->ctrls[i].cfg, ctx); ++ if (ctx->ctrl_hdl.error) ++ return ctx->ctrl_hdl.error; ++ } ++ ++ return 0; ++} ++ ++static int apple_vxd_init_ctrls(struct apple_vxd_ctx *ctx) ++{ ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ unsigned int i, nctrls = 0; ++ int ret; ++ ++ for (i = 0; i < vxd->num_coded_fmts; i++) ++ nctrls += vxd->coded_fmts[i].ctrls->num_ctrls; ++ ++ v4l2_ctrl_handler_init(&ctx->ctrl_hdl, nctrls); ++ ++ for (i = 0; i < vxd->num_coded_fmts; i++) { ++ ret = apple_vxd_add_ctrls(ctx, vxd->coded_fmts[i].ctrls); ++ if (ret) ++ goto err_free_handler; ++ } ++ ++ ret = v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); ++ if (ret) ++ goto err_free_handler; ++ ++ ctx->fh.ctrl_handler = &ctx->ctrl_hdl; ++ return 0; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&ctx->ctrl_hdl); ++ return ret; ++} ++ ++/* --- handler bodies (context based; shared with the self-test) --------- */ ++ ++static int apple_vxd_do_enum_framesizes(struct apple_vxd_ctx *ctx, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ const struct apple_vxd_coded_fmt_desc *desc; ++ ++ if (fsize->index != 0) ++ return -EINVAL; ++ ++ desc = apple_vxd_find_coded_fmt_desc(ctx, fsize->pixel_format); ++ if (!desc) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; ++ fsize->stepwise.min_width = 1; ++ fsize->stepwise.max_width = desc->frmsize.max_width; ++ fsize->stepwise.step_width = 1; ++ fsize->stepwise.min_height = 1; ++ fsize->stepwise.max_height = desc->frmsize.max_height; ++ fsize->stepwise.step_height = 1; ++ ++ return 0; ++} ++ ++/* ++ * The V4L2 core fills device_caps, capabilities and bus_info (from the ++ * node's parent device, "platform:207f00000.vxd", matching the media ++ * device's default) before calling the driver. ++ */ ++static void apple_vxd_fill_querycap(struct apple_vxd_dev *vxd, ++ struct v4l2_capability *cap) ++{ ++ strscpy(cap->driver, APPLE_VXD_DRV_NAME, sizeof(cap->driver)); ++ strscpy(cap->card, APPLE_VXD_DRV_NAME, sizeof(cap->card)); ++} ++ ++static int apple_vxd_do_try_capture_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_format *f) ++{ ++ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; ++ const struct apple_vxd_coded_fmt_desc *coded_desc = ctx->coded_fmt_desc; ++ ++ if (!apple_vxd_is_valid_fmt(ctx, pix_mp->pixelformat, ctx->image_fmt)) ++ pix_mp->pixelformat = ++ apple_vxd_enum_decoded_fmt(ctx, 0, ctx->image_fmt); ++ ++ /* Always apply the frmsize constraint of the coded end. */ ++ pix_mp->width = max(pix_mp->width, ctx->coded_fmt.fmt.pix_mp.width); ++ pix_mp->height = max(pix_mp->height, ctx->coded_fmt.fmt.pix_mp.height); ++ v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, ++ &coded_desc->frmsize); ++ ++ apple_vxd_fill_decoded_pixfmt(ctx, pix_mp); ++ pix_mp->field = V4L2_FIELD_NONE; ++ ++ return 0; ++} ++ ++static int apple_vxd_do_try_output_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_format *f) ++{ ++ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; ++ const struct apple_vxd_coded_fmt_desc *desc; ++ ++ desc = apple_vxd_find_coded_fmt_desc(ctx, pix_mp->pixelformat); ++ if (!desc) { ++ desc = &ctx->vxd->coded_fmts[0]; ++ pix_mp->pixelformat = desc->fourcc; ++ } ++ ++ v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, ++ &desc->frmsize); ++ ++ pix_mp->field = V4L2_FIELD_NONE; ++ /* All coded formats are considered single planar for now. */ ++ pix_mp->num_planes = 1; ++ ++ if (desc->ops->adjust_fmt) { ++ int ret; ++ ++ ret = desc->ops->adjust_fmt(ctx, f); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * One access unit per buffer (frame-based decode): one byte per ++ * pixel covers a level 4.2 1080p intra picture (about 2 MiB), ++ * userspace such as GStreamer leaves sizeimage to the driver. ++ */ ++ pix_mp->plane_fmt[0].sizeimage = ++ max3(pix_mp->plane_fmt[0].sizeimage, ++ (u32)APPLE_VXD_MIN_BITSTREAM_SIZE, ++ (u32)PAGE_ALIGN(pix_mp->width * pix_mp->height)); ++ return 0; ++} ++ ++static int apple_vxd_do_s_capture_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_format *f) ++{ ++ struct vb2_queue *vq; ++ int ret; ++ ++ /* Change not allowed if queue is busy */ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, ++ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ ret = apple_vxd_do_try_capture_fmt(ctx, f); ++ if (ret) ++ return ret; ++ ++ ctx->decoded_fmt = *f; ++ return 0; ++} ++ ++static int apple_vxd_do_s_output_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_format *f) ++{ ++ struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; ++ const struct apple_vxd_coded_fmt_desc *desc; ++ struct v4l2_format *cap_fmt; ++ struct vb2_queue *peer_vq, *vq; ++ int ret; ++ ++ /* ++ * The iOS parser is one-stream-configuration-per-instance ("SPS ++ * changes not supported!"); resolution changes only within the ++ * same pixelformat. Never while streaming. ++ */ ++ vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); ++ if (vb2_is_streaming(vq) || ++ (vb2_is_busy(vq) && ++ f->fmt.pix_mp.pixelformat != ctx->coded_fmt.fmt.pix_mp.pixelformat)) ++ return -EBUSY; ++ ++ peer_vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); ++ if (vb2_is_busy(peer_vq)) ++ return -EBUSY; ++ ++ ret = apple_vxd_do_try_output_fmt(ctx, f); ++ if (ret) ++ return ret; ++ ++ desc = apple_vxd_find_coded_fmt_desc(ctx, f->fmt.pix_mp.pixelformat); ++ if (!desc) ++ return -EINVAL; ++ ctx->coded_fmt_desc = desc; ++ ctx->coded_fmt = *f; ++ ++ /* ++ * Reset the decoded format; the user is mandated to set it again ++ * after we return. ++ */ ++ ctx->image_fmt = APPLE_VXD_IMG_FMT_ANY; ++ apple_vxd_reset_decoded_fmt(ctx); ++ ++ /* Propagate colorspace information to capture. */ ++ cap_fmt = &ctx->decoded_fmt; ++ cap_fmt->fmt.pix_mp.colorspace = f->fmt.pix_mp.colorspace; ++ cap_fmt->fmt.pix_mp.xfer_func = f->fmt.pix_mp.xfer_func; ++ cap_fmt->fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; ++ cap_fmt->fmt.pix_mp.quantization = f->fmt.pix_mp.quantization; ++ ++ return 0; ++} ++ ++static int apple_vxd_do_enum_output_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_fmtdesc *f) ++{ ++ if (f->index >= ctx->vxd->num_coded_fmts) ++ return -EINVAL; ++ f->pixelformat = ctx->vxd->coded_fmts[f->index].fourcc; ++ return 0; ++} ++ ++static int apple_vxd_do_enum_capture_fmt(struct apple_vxd_ctx *ctx, ++ struct v4l2_fmtdesc *f) ++{ ++ u32 fourcc; ++ ++ fourcc = apple_vxd_enum_decoded_fmt(ctx, f->index, ctx->image_fmt); ++ if (!fourcc) ++ return -EINVAL; ++ f->pixelformat = fourcc; ++ return 0; ++} ++ ++/* --- ioctl entry points ------------------------------------------------ */ ++ ++/* ++ * Driver-side STEP lines: the ioctl-entry line (apple_vxd_ioctl) names the ++ * command before the V4L2 core runs; these name the driver handler, so a ++ * crash between the two is in core code. ++ */ ++static void apple_vxd_step_handler(struct file *file, const char *what) ++{ ++ struct apple_vxd_dev *vxd = video_drvdata(file); ++ ++ vxd_step_io(vxd->dev, "handler: %s", what); ++} ++ ++/* ++ * Only a cached CAPTURE queue has allow_cache_hints set (queue_init); ask ++ * vb2 for non-coherent memory on it whatever the application passed. ++ */ ++static bool apple_vxd_cached_queue(struct file *file, u32 type, u32 memory) ++{ ++ struct vb2_queue *vq = v4l2_m2m_get_vq(file_to_vxd_ctx(file)->fh.m2m_ctx, ++ type); ++ ++ return vq->allow_cache_hints && memory == V4L2_MEMORY_MMAP; ++} ++ ++static int apple_vxd_reqbufs(struct file *file, void *priv, ++ struct v4l2_requestbuffers *rb) ++{ ++ if (apple_vxd_cached_queue(file, rb->type, rb->memory)) ++ rb->flags |= V4L2_MEMORY_FLAG_NON_COHERENT; ++ return v4l2_m2m_ioctl_reqbufs(file, priv, rb); ++} ++ ++static int apple_vxd_create_bufs(struct file *file, void *priv, ++ struct v4l2_create_buffers *create) ++{ ++ if (apple_vxd_cached_queue(file, create->format.type, create->memory)) ++ create->flags |= V4L2_MEMORY_FLAG_NON_COHERENT; ++ return v4l2_m2m_ioctl_create_bufs(file, priv, create); ++} ++ ++static int apple_vxd_enum_framesizes(struct file *file, void *priv, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ apple_vxd_step_handler(file, "enum_framesizes"); ++ return apple_vxd_do_enum_framesizes(file_to_vxd_ctx(file), fsize); ++} ++ ++static int apple_vxd_querycap(struct file *file, void *priv, ++ struct v4l2_capability *cap) ++{ ++ apple_vxd_step_handler(file, "querycap"); ++ apple_vxd_fill_querycap(video_drvdata(file), cap); ++ return 0; ++} ++ ++static int apple_vxd_try_capture_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "try_fmt capture"); ++ return apple_vxd_do_try_capture_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static int apple_vxd_try_output_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "try_fmt output"); ++ return apple_vxd_do_try_output_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static int apple_vxd_s_capture_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "s_fmt capture"); ++ return apple_vxd_do_s_capture_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static int apple_vxd_s_output_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "s_fmt output"); ++ return apple_vxd_do_s_output_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static int apple_vxd_g_output_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "g_fmt output"); ++ *f = file_to_vxd_ctx(file)->coded_fmt; ++ return 0; ++} ++ ++static int apple_vxd_g_capture_fmt(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ apple_vxd_step_handler(file, "g_fmt capture"); ++ *f = file_to_vxd_ctx(file)->decoded_fmt; ++ return 0; ++} ++ ++static int apple_vxd_enum_output_fmt(struct file *file, void *priv, ++ struct v4l2_fmtdesc *f) ++{ ++ apple_vxd_step_handler(file, "enum_fmt output"); ++ return apple_vxd_do_enum_output_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static int apple_vxd_enum_capture_fmt(struct file *file, void *priv, ++ struct v4l2_fmtdesc *f) ++{ ++ apple_vxd_step_handler(file, "enum_fmt capture"); ++ return apple_vxd_do_enum_capture_fmt(file_to_vxd_ctx(file), f); ++} ++ ++static const struct v4l2_ioctl_ops apple_vxd_ioctl_ops = { ++ .vidioc_querycap = apple_vxd_querycap, ++ .vidioc_enum_framesizes = apple_vxd_enum_framesizes, ++ ++ .vidioc_try_fmt_vid_cap_mplane = apple_vxd_try_capture_fmt, ++ .vidioc_try_fmt_vid_out_mplane = apple_vxd_try_output_fmt, ++ .vidioc_s_fmt_vid_cap_mplane = apple_vxd_s_capture_fmt, ++ .vidioc_s_fmt_vid_out_mplane = apple_vxd_s_output_fmt, ++ .vidioc_g_fmt_vid_out_mplane = apple_vxd_g_output_fmt, ++ .vidioc_g_fmt_vid_cap_mplane = apple_vxd_g_capture_fmt, ++ .vidioc_enum_fmt_vid_out = apple_vxd_enum_output_fmt, ++ .vidioc_enum_fmt_vid_cap = apple_vxd_enum_capture_fmt, ++ ++ .vidioc_reqbufs = apple_vxd_reqbufs, ++ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, ++ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, ++ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, ++ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, ++ .vidioc_create_bufs = apple_vxd_create_bufs, ++ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, ++ ++ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, ++ ++ .vidioc_streamon = v4l2_m2m_ioctl_streamon, ++ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, ++ ++ .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd, ++ .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd, ++}; ++ ++static const struct { ++ unsigned int cmd; ++ const char *name; ++} apple_vxd_ioctl_names[] = { ++ { VIDIOC_QUERYCAP, "VIDIOC_QUERYCAP" }, ++ { VIDIOC_ENUM_FMT, "VIDIOC_ENUM_FMT" }, ++ { VIDIOC_G_FMT, "VIDIOC_G_FMT" }, ++ { VIDIOC_S_FMT, "VIDIOC_S_FMT" }, ++ { VIDIOC_TRY_FMT, "VIDIOC_TRY_FMT" }, ++ { VIDIOC_ENUM_FRAMESIZES, "VIDIOC_ENUM_FRAMESIZES" }, ++ { VIDIOC_ENUM_FRAMEINTERVALS, "VIDIOC_ENUM_FRAMEINTERVALS" }, ++ { VIDIOC_REQBUFS, "VIDIOC_REQBUFS" }, ++ { VIDIOC_QUERYBUF, "VIDIOC_QUERYBUF" }, ++ { VIDIOC_QBUF, "VIDIOC_QBUF" }, ++ { VIDIOC_DQBUF, "VIDIOC_DQBUF" }, ++ { VIDIOC_PREPARE_BUF, "VIDIOC_PREPARE_BUF" }, ++ { VIDIOC_CREATE_BUFS, "VIDIOC_CREATE_BUFS" }, ++ { VIDIOC_EXPBUF, "VIDIOC_EXPBUF" }, ++ { VIDIOC_STREAMON, "VIDIOC_STREAMON" }, ++ { VIDIOC_STREAMOFF, "VIDIOC_STREAMOFF" }, ++ { VIDIOC_QUERYCTRL, "VIDIOC_QUERYCTRL" }, ++ { VIDIOC_QUERY_EXT_CTRL, "VIDIOC_QUERY_EXT_CTRL" }, ++ { VIDIOC_QUERYMENU, "VIDIOC_QUERYMENU" }, ++ { VIDIOC_G_CTRL, "VIDIOC_G_CTRL" }, ++ { VIDIOC_S_CTRL, "VIDIOC_S_CTRL" }, ++ { VIDIOC_G_EXT_CTRLS, "VIDIOC_G_EXT_CTRLS" }, ++ { VIDIOC_S_EXT_CTRLS, "VIDIOC_S_EXT_CTRLS" }, ++ { VIDIOC_TRY_EXT_CTRLS, "VIDIOC_TRY_EXT_CTRLS" }, ++ { VIDIOC_SUBSCRIBE_EVENT, "VIDIOC_SUBSCRIBE_EVENT" }, ++ { VIDIOC_UNSUBSCRIBE_EVENT, "VIDIOC_UNSUBSCRIBE_EVENT" }, ++ { VIDIOC_DQEVENT, "VIDIOC_DQEVENT" }, ++ { VIDIOC_DECODER_CMD, "VIDIOC_DECODER_CMD" }, ++ { VIDIOC_TRY_DECODER_CMD, "VIDIOC_TRY_DECODER_CMD" }, ++ { VIDIOC_G_PRIORITY, "VIDIOC_G_PRIORITY" }, ++ { VIDIOC_S_PRIORITY, "VIDIOC_S_PRIORITY" }, ++ { VIDIOC_ENUMINPUT, "VIDIOC_ENUMINPUT" }, ++ { VIDIOC_ENUMOUTPUT, "VIDIOC_ENUMOUTPUT" }, ++ { VIDIOC_G_INPUT, "VIDIOC_G_INPUT" }, ++ { VIDIOC_G_OUTPUT, "VIDIOC_G_OUTPUT" }, ++ { VIDIOC_G_PARM, "VIDIOC_G_PARM" }, ++ { VIDIOC_LOG_STATUS, "VIDIOC_LOG_STATUS" }, ++}; ++ ++static const char *apple_vxd_ioctl_name(unsigned int cmd) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(apple_vxd_ioctl_names); i++) ++ if (apple_vxd_ioctl_names[i].cmd == cmd) ++ return apple_vxd_ioctl_names[i].name; ++ return "unknown"; ++} ++ ++/* ++ * Every ioctl on the video node passes here first: the STEP line names ++ * the command (and the caller) before the V4L2 core or a handler runs. ++ * The device comes from the node, not the file handle, so the line is ++ * printed even if the per-file context is broken. ++ */ ++static long apple_vxd_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct apple_vxd_dev *vxd = video_drvdata(file); ++ ++ vxd_step_io(vxd->dev, "ioctl %s (%#x) by %s[%d]", ++ apple_vxd_ioctl_name(cmd), cmd, current->comm, ++ task_pid_nr(current)); ++ return video_ioctl2(file, cmd, arg); ++} ++ ++/* --- vb2 / m2m / media ops --------------------------------------------- */ ++ ++static int apple_vxd_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, ++ unsigned int *num_planes, unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vq); ++ struct v4l2_format *f; ++ unsigned int i; ++ ++ f = V4L2_TYPE_IS_OUTPUT(vq->type) ? &ctx->coded_fmt : &ctx->decoded_fmt; ++ ++ if (*num_planes) { ++ if (*num_planes != f->fmt.pix_mp.num_planes) ++ return -EINVAL; ++ ++ for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { ++ if (sizes[i] < f->fmt.pix_mp.plane_fmt[i].sizeimage) ++ return -EINVAL; ++ } ++ } else { ++ *num_planes = f->fmt.pix_mp.num_planes; ++ for (i = 0; i < f->fmt.pix_mp.num_planes; i++) ++ sizes[i] = f->fmt.pix_mp.plane_fmt[i].sizeimage; ++ } ++ ++ return 0; ++} ++ ++static int apple_vxd_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ struct v4l2_format *f; ++ unsigned int i; ++ ++ f = V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type) ? &ctx->coded_fmt : ++ &ctx->decoded_fmt; ++ ++ for (i = 0; i < f->fmt.pix_mp.num_planes; ++i) { ++ u32 sizeimage = f->fmt.pix_mp.plane_fmt[i].sizeimage; ++ ++ if (vb2_plane_size(vb, i) < sizeimage) ++ return -EINVAL; ++ } ++ ++ /* ++ * Buffer's bytesused must be written by driver for CAPTURE buffers. ++ * (for OUTPUT buffers, if userspace passes 0 bytesused, v4l2-core ++ * sets it to buffer length). ++ */ ++ if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) ++ vb2_set_plane_payload(vb, 0, ++ f->fmt.pix_mp.plane_fmt[0].sizeimage); ++ ++ return 0; ++} ++ ++static struct apple_vxd_buffer *vxd_buf(struct vb2_v4l2_buffer *vbuf) ++{ ++ return container_of(vbuf, struct apple_vxd_buffer, base.vb); ++} ++ ++/* ++ * R3b: every buffer gets a GART mapping for its whole life. The command ++ * stream names the bitstream by device address (the 0xa load command, ++ * ds-vxd-cmdstream.md section 3.3) and the render target / DPB surfaces ++ * the same way (section 6). The mapping is the identity window ++ * (low 32 bits of the DMA address + va_offset), 4 KiB granular; vb2 ++ * dma-contig buffers are physically contiguous here (no IOMMU on this ++ * node) and start page aligned. Only page-table memory is written: no ++ * register access, so this is safe with the firmware down. ++ */ ++static int apple_vxd_buf_init(struct vb2_buffer *vb) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ struct apple_vxd_buffer *buf = vxd_buf(to_vb2_v4l2_buffer(vb)); ++ dma_addr_t dma = vb2_dma_contig_plane_dma_addr(vb, 0); ++ dma_addr_t start = ALIGN_DOWN(dma, VXD_GART_PAGE_SIZE); ++ size_t len = vb2_plane_size(vb, 0); ++ int ret; ++ ++ ret = apple_vxd_gart_map(ctx->vxd, start, ++ ALIGN(dma + len, VXD_GART_PAGE_SIZE) - start, ++ &buf->map); ++ if (ret) { ++ dev_err(ctx->vxd->dev, ++ "%s buffer %u: GART map of %pad (+%zu) failed: %d\n", ++ V4L2_TYPE_IS_OUTPUT(vb->type) ? "output" : "capture", ++ vb->index, &dma, len, ret); ++ return ret; ++ } ++ buf->va = buf->map.va + (u32)(dma - start); ++ vxd_step_io(ctx->vxd->dev, ++ "vb2: %s buffer %u GART-mapped: dma %pad, %zu bytes -> device VA %#x", ++ V4L2_TYPE_IS_OUTPUT(vb->type) ? "output" : "capture", ++ vb->index, &dma, len, buf->va); ++ ++ if (V4L2_TYPE_IS_CAPTURE(vb->type) && ++ ctx->coded_fmt_desc->ops->capture_aux_size) { ++ size_t aux = ctx->coded_fmt_desc->ops->capture_aux_size(ctx); ++ ++ if (aux) { ++ ret = apple_vxd_dev_buf_alloc(ctx->vxd, aux, &buf->aux); ++ if (ret) { ++ dev_err(ctx->vxd->dev, ++ "capture buffer %u: %zu-byte side buffer: %d\n", ++ vb->index, aux, ret); ++ apple_vxd_gart_unmap(ctx->vxd, &buf->map); ++ buf->va = 0; ++ return ret; ++ } ++ vxd_step_io(ctx->vxd->dev, ++ "vb2: capture buffer %u side buffer %zu bytes at device VA %#x", ++ vb->index, aux, buf->aux.va); ++ } ++ } ++ return 0; ++} ++ ++static void apple_vxd_buf_cleanup(struct vb2_buffer *vb) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ struct apple_vxd_buffer *buf = vxd_buf(to_vb2_v4l2_buffer(vb)); ++ ++ apple_vxd_dev_buf_free(ctx->vxd, &buf->aux); ++ apple_vxd_gart_unmap(ctx->vxd, &buf->map); ++ buf->va = 0; ++} ++ ++/* Device VA of plane 0 of a queued buffer (valid between buf_init and cleanup). */ ++u32 apple_vxd_buf_va(struct vb2_v4l2_buffer *vbuf) ++{ ++ return vxd_buf(vbuf)->va; ++} ++ ++/* Device VA of a CAPTURE buffer's side buffer, 0 if it has none. */ ++u32 apple_vxd_buf_aux_va(struct vb2_v4l2_buffer *vbuf) ++{ ++ return vxd_buf(vbuf)->aux.va; ++} ++ ++static void apple_vxd_buf_queue(struct vb2_buffer *vb) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ ++ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); ++} ++ ++static int apple_vxd_buf_out_validate(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ ++ vbuf->field = V4L2_FIELD_NONE; ++ return 0; ++} ++ ++static void apple_vxd_buf_request_complete(struct vb2_buffer *vb) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ ++ v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_hdl); ++} ++ ++static int apple_vxd_start_streaming(struct vb2_queue *q, unsigned int count) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(q); ++ int ret; ++ ++ vxd_step_io(ctx->vxd->dev, "vb2: start_streaming %s", ++ V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture"); ++ if (V4L2_TYPE_IS_CAPTURE(q->type)) ++ return 0; ++ ++ if (!ctx->coded_fmt_desc->ops->start) ++ return 0; ++ ++ ret = ctx->coded_fmt_desc->ops->start(ctx); ++ if (ret) { ++ dev_err(ctx->vxd->dev, ++ "failed to start decode context: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void apple_vxd_queue_cleanup(struct vb2_queue *vq, u32 state) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(vq); ++ ++ while (true) { ++ struct vb2_v4l2_buffer *vbuf; ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) ++ vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); ++ else ++ vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); ++ ++ if (!vbuf) ++ break; ++ ++ v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, ++ &ctx->ctrl_hdl); ++ v4l2_m2m_buf_done(vbuf, state); ++ } ++} ++ ++static void apple_vxd_stop_streaming(struct vb2_queue *q) ++{ ++ struct apple_vxd_ctx *ctx = vb2_get_drv_priv(q); ++ ++ vxd_step_io(ctx->vxd->dev, "vb2: stop_streaming %s", ++ V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture"); ++ if (V4L2_TYPE_IS_OUTPUT(q->type) && ctx->coded_fmt_desc->ops->stop) ++ ctx->coded_fmt_desc->ops->stop(ctx); ++ ++ apple_vxd_queue_cleanup(q, VB2_BUF_STATE_ERROR); ++} ++ ++static const struct vb2_ops apple_vxd_queue_ops = { ++ .queue_setup = apple_vxd_queue_setup, ++ .buf_init = apple_vxd_buf_init, ++ .buf_cleanup = apple_vxd_buf_cleanup, ++ .buf_prepare = apple_vxd_buf_prepare, ++ .buf_queue = apple_vxd_buf_queue, ++ .buf_out_validate = apple_vxd_buf_out_validate, ++ .buf_request_complete = apple_vxd_buf_request_complete, ++ .start_streaming = apple_vxd_start_streaming, ++ .stop_streaming = apple_vxd_stop_streaming, ++}; ++ ++/* The media node's only driver hooks are the request ops. */ ++static int apple_vxd_request_validate(struct media_request *req) ++{ ++ struct apple_vxd_dev *vxd = container_of(req->mdev, struct apple_vxd_dev, ++ mdev); ++ unsigned int count; ++ ++ vxd_step_io(vxd->dev, "media: request validate"); ++ count = vb2_request_buffer_cnt(req); ++ if (!count) ++ return -ENOENT; ++ else if (count > 1) ++ return -EINVAL; ++ ++ return vb2_request_validate(req); ++} ++ ++static void apple_vxd_request_queue(struct media_request *req) ++{ ++ struct apple_vxd_dev *vxd = container_of(req->mdev, struct apple_vxd_dev, ++ mdev); ++ ++ vxd_step_io(vxd->dev, "media: request queue"); ++ v4l2_m2m_request_queue(req); ++} ++ ++static const struct media_device_ops apple_vxd_media_ops = { ++ .req_validate = apple_vxd_request_validate, ++ .req_queue = apple_vxd_request_queue, ++}; ++ ++static void apple_vxd_device_run(void *priv) ++{ ++ struct apple_vxd_ctx *ctx = priv; ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ const struct apple_vxd_coded_fmt_desc *desc = ctx->coded_fmt_desc; ++ int ret; ++ ++ vxd_step_io(vxd->dev, "m2m: device_run (firmware %s)", ++ vxd->hw_ready ? "alive" : "not booted"); ++ if (WARN_ON(!desc)) ++ return; ++ ++ ret = pm_runtime_resume_and_get(vxd->dev); ++ if (ret < 0) { ++ apple_vxd_job_finish_no_pm(ctx, VB2_BUF_STATE_ERROR); ++ return; ++ } ++ ++ ret = desc->ops->run(ctx); ++ if (ret) ++ apple_vxd_job_finish(ctx, VB2_BUF_STATE_ERROR); ++} ++ ++static const struct v4l2_m2m_ops apple_vxd_m2m_ops = { ++ .device_run = apple_vxd_device_run, ++}; ++ ++static int apple_vxd_queue_init(void *priv, struct vb2_queue *src_vq, ++ struct vb2_queue *dst_vq) ++{ ++ struct apple_vxd_ctx *ctx = priv; ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ int ret; ++ ++ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ src_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ src_vq->drv_priv = ctx; ++ src_vq->ops = &apple_vxd_queue_ops; ++ src_vq->mem_ops = &vb2_dma_contig_memops; ++ /* ++ * Frame-based decode: the driver walks the access unit and parses ++ * the slice headers itself, so the bitstream needs a kernel mapping. ++ */ ++ src_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES; ++ src_vq->buf_struct_size = sizeof(struct apple_vxd_buffer); ++ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ src_vq->lock = &vxd->vdev_lock; ++ src_vq->dev = vxd->v4l2_dev.dev; ++ src_vq->supports_requests = true; ++ src_vq->requires_requests = true; ++ ++ ret = vb2_queue_init(src_vq); ++ if (ret) ++ return ret; ++ ++ dst_vq->bidirectional = true; ++ dst_vq->mem_ops = &vb2_dma_contig_memops; ++ if (READ_ONCE(apple_vxd_capture_cached)) { ++ /* ++ * dma_alloc_noncontiguous() takes no other attribute; a ++ * non-coherent buffer has no kernel mapping until vb2 asks. ++ */ ++ dst_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES; ++ dst_vq->allow_cache_hints = 1; ++ } else { ++ dst_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES | ++ DMA_ATTR_NO_KERNEL_MAPPING; ++ } ++ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ dst_vq->drv_priv = ctx; ++ dst_vq->ops = &apple_vxd_queue_ops; ++ dst_vq->buf_struct_size = sizeof(struct apple_vxd_buffer); ++ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ++ dst_vq->lock = &vxd->vdev_lock; ++ dst_vq->dev = vxd->v4l2_dev.dev; ++ ++ return vb2_queue_init(dst_vq); ++} ++ ++/* --- open / release ---------------------------------------------------- */ ++ ++/* ++ * The driver half of open(): everything except the v4l2_fh bookkeeping, ++ * which is V4L2 core and needs a real file. @who prefixes the STEP lines ++ * ("open" or "self-test"). Order matters: the control handler setup runs ++ * s_ctrl, which inspects the m2m queues, so the m2m context comes first. ++ */ ++static int apple_vxd_ctx_init(struct apple_vxd_ctx *ctx, const char *who) ++{ ++ struct apple_vxd_dev *vxd = ctx->vxd; ++ int ret; ++ ++ /* ++ * Round 2's oops: the coded-format table was never filled and the ++ * first open() (udev's v4l_id) read coded_fmts[0] at address 0. ++ */ ++ if (!vxd->coded_fmts || !vxd->num_coded_fmts || !vxd->m2m_dev) { ++ dev_err(vxd->dev, "%s: V4L2 state missing (coded fmts %u, m2m %s)\n", ++ who, vxd->num_coded_fmts, vxd->m2m_dev ? "ok" : "none"); ++ return -ENODEV; ++ } ++ ++ vxd_step(vxd->dev, "%s: format defaults", who); ++ apple_vxd_reset_coded_fmt(ctx); ++ apple_vxd_reset_decoded_fmt(ctx); ++ ++ vxd_step(vxd->dev, "%s: m2m context + vb2 queues", who); ++ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(vxd->m2m_dev, ctx, ++ apple_vxd_queue_init); ++ if (IS_ERR(ctx->fh.m2m_ctx)) { ++ ret = PTR_ERR(ctx->fh.m2m_ctx); ++ ctx->fh.m2m_ctx = NULL; ++ return ret; ++ } ++ ++ vxd_step(vxd->dev, "%s: control handler + setup", who); ++ ret = apple_vxd_init_ctrls(ctx); ++ if (ret) { ++ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); ++ ctx->fh.m2m_ctx = NULL; ++ return ret; ++ } ++ ++ return 0; ++} ++ ++/* The driver half of release(). */ ++static void apple_vxd_ctx_fini(struct apple_vxd_ctx *ctx) ++{ ++ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); ++ v4l2_ctrl_handler_free(&ctx->ctrl_hdl); ++} ++ ++static int apple_vxd_open(struct file *filp) ++{ ++ struct video_device *vdev = video_devdata(filp); ++ struct apple_vxd_dev *vxd = video_drvdata(filp); ++ struct apple_vxd_ctx *ctx; ++ int ret; ++ ++ vxd_step(vxd->dev, "open: /dev/%s by %s[%d]", ++ video_device_node_name(vdev), current->comm, ++ task_pid_nr(current)); ++ ++ ctx = kzalloc_obj(*ctx); ++ if (!ctx) ++ return -ENOMEM; ++ ++ ctx->vxd = vxd; ++ v4l2_fh_init(&ctx->fh, vdev); ++ ++ ret = apple_vxd_ctx_init(ctx, "open"); ++ if (ret) { ++ v4l2_fh_exit(&ctx->fh); ++ kfree(ctx); ++ return ret; ++ } ++ ++ v4l2_fh_add(&ctx->fh, filp); ++ return 0; ++} ++ ++static int apple_vxd_release(struct file *filp) ++{ ++ struct apple_vxd_dev *vxd = video_drvdata(filp); ++ struct apple_vxd_ctx *ctx = file_to_vxd_ctx(filp); ++ ++ vxd_step(vxd->dev, "release: /dev/%s by %s[%d]", ++ video_device_node_name(&vxd->vdev), current->comm, ++ task_pid_nr(current)); ++ ++ v4l2_fh_del(&ctx->fh, filp); ++ apple_vxd_ctx_fini(ctx); ++ v4l2_fh_exit(&ctx->fh); ++ kfree(ctx); ++ ++ return 0; ++} ++ ++static int apple_vxd_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ struct apple_vxd_dev *vxd = video_drvdata(file); ++ ++ vxd_step_io(vxd->dev, "mmap: offset %#lx", vma->vm_pgoff << PAGE_SHIFT); ++ return v4l2_m2m_fop_mmap(file, vma); ++} ++ ++static const struct v4l2_file_operations apple_vxd_fops = { ++ .owner = THIS_MODULE, ++ .open = apple_vxd_open, ++ .release = apple_vxd_release, ++ .poll = v4l2_m2m_fop_poll, ++ .unlocked_ioctl = apple_vxd_ioctl, ++ .mmap = apple_vxd_mmap, ++}; ++ ++/* --- register-time self-test ------------------------------------------- */ ++ ++static int apple_vxd_selftest_check(struct apple_vxd_dev *vxd, int ret, ++ bool ok, const char *what) ++{ ++ if (!ret && ok) ++ return 0; ++ ++ dev_err(vxd->dev, "v4l2 self-test FAILED at %s (ret %d)\n", what, ret); ++ return ret ? ret : -EINVAL; ++} ++ ++/* ++ * Runs the driver half of open(), every format/enum/framesize handler ++ * body, the querycap fill and the driver half of release() on a private ++ * context, BEFORE any node is exposed. The round-2 class of bug (state ++ * that some later step was supposed to create) then fails the V4L2 ++ * registration with a log line naming the check, instead of oopsing under ++ * udev. The v4l2_fh bookkeeping is V4L2 core and is not run: ++ * v4l2_fh_init() on a not yet registered node would also mark its ++ * priority ioctls as disabled. ++ */ ++static int apple_vxd_v4l2_selftest(struct apple_vxd_dev *vxd) ++{ ++ const struct apple_vxd_coded_fmt_desc *desc; ++ struct v4l2_frmsizeenum fsize = { }; ++ struct v4l2_capability cap = { }; ++ struct v4l2_fmtdesc fdesc = { }; ++ struct v4l2_format f = { }; ++ struct apple_vxd_ctx *ctx; ++ unsigned int i; ++ int ret = 0; ++ ++ vxd_step(vxd->dev, "self-test: static tables + node wiring"); ++ for (i = 0; i < vxd->num_coded_fmts; i++) { ++ desc = &vxd->coded_fmts[i]; ++ ret = apple_vxd_selftest_check(vxd, 0, ++ desc->ops && desc->ops->run && ++ desc->ctrls && desc->num_decoded_fmts && ++ desc->decoded_fmts, ++ "coded format descriptor"); ++ if (ret) ++ return ret; ++ } ++ ret = apple_vxd_selftest_check(vxd, 0, ++ vxd->v4l2_dev.dev && vxd->vdev.v4l2_dev && ++ vxd->vdev.fops && vxd->vdev.ioctl_ops && ++ vxd->vdev.device_caps, ++ "video node wiring"); ++ if (ret) ++ return ret; ++ ++ ctx = kzalloc_obj(*ctx); ++ if (!ctx) ++ return -ENOMEM; ++ ctx->vxd = vxd; ++ ++ /* open() */ ++ ret = apple_vxd_ctx_init(ctx, "self-test"); ++ if (ret) { ++ dev_err(vxd->dev, "v4l2 self-test FAILED at open (ret %d)\n", ret); ++ kfree(ctx); ++ return ret; ++ } ++ ++ /* VIDIOC_QUERYCAP (the driver part; the core fills caps/bus_info) */ ++ vxd_step(vxd->dev, "self-test: querycap"); ++ apple_vxd_fill_querycap(vxd, &cap); ++ ret = apple_vxd_selftest_check(vxd, 0, cap.driver[0] && cap.card[0], ++ "querycap"); ++ if (ret) ++ goto out; ++ ++ /* VIDIOC_ENUM_FMT */ ++ vxd_step(vxd->dev, "self-test: enum_fmt"); ++ fdesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ ret = apple_vxd_do_enum_output_fmt(ctx, &fdesc); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ fdesc.pixelformat == vxd->coded_fmts[0].fourcc, ++ "enum_fmt output 0"); ++ if (ret) ++ goto out; ++ fdesc.index = vxd->num_coded_fmts; ++ ret = apple_vxd_selftest_check(vxd, 0, ++ apple_vxd_do_enum_output_fmt(ctx, &fdesc) == -EINVAL, ++ "enum_fmt output end"); ++ if (ret) ++ goto out; ++ memset(&fdesc, 0, sizeof(fdesc)); ++ fdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ ret = apple_vxd_do_enum_capture_fmt(ctx, &fdesc); ++ ret = apple_vxd_selftest_check(vxd, ret, fdesc.pixelformat, ++ "enum_fmt capture 0"); ++ if (ret) ++ goto out; ++ ++ /* VIDIOC_G_FMT */ ++ vxd_step(vxd->dev, "self-test: g_fmt"); ++ ret = apple_vxd_selftest_check(vxd, 0, ++ ctx->coded_fmt.fmt.pix_mp.num_planes == 1 && ++ ctx->coded_fmt.fmt.pix_mp.plane_fmt[0].sizeimage, ++ "g_fmt output"); ++ if (ret) ++ goto out; ++ ret = apple_vxd_selftest_check(vxd, 0, ++ ctx->decoded_fmt.fmt.pix_mp.num_planes && ++ ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].sizeimage, ++ "g_fmt capture"); ++ if (ret) ++ goto out; ++ ++ /* VIDIOC_TRY_FMT: oversized, unknown fourcc -> clamped coded fmt 0 */ ++ vxd_step(vxd->dev, "self-test: try_fmt"); ++ f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ f.fmt.pix_mp.pixelformat = 0; ++ f.fmt.pix_mp.width = 8192; ++ f.fmt.pix_mp.height = 8192; ++ ret = apple_vxd_do_try_output_fmt(ctx, &f); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ f.fmt.pix_mp.pixelformat == vxd->coded_fmts[0].fourcc && ++ f.fmt.pix_mp.width <= APPLE_VXD_MAX_PIC_W && ++ f.fmt.pix_mp.height <= APPLE_VXD_MAX_PIC_H, ++ "try_fmt output"); ++ if (ret) ++ goto out; ++ f = ctx->decoded_fmt; ++ ret = apple_vxd_do_try_capture_fmt(ctx, &f); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ f.fmt.pix_mp.plane_fmt[0].sizeimage, ++ "try_fmt capture"); ++ if (ret) ++ goto out; ++ ++ /* VIDIOC_S_FMT: 1080p bitstream, then the matching NV12 surface */ ++ vxd_step(vxd->dev, "self-test: s_fmt"); ++ memset(&f, 0, sizeof(f)); ++ f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ f.fmt.pix_mp.pixelformat = vxd->coded_fmts[0].fourcc; ++ f.fmt.pix_mp.width = 1920; ++ f.fmt.pix_mp.height = 1080; ++ ret = apple_vxd_do_s_output_fmt(ctx, &f); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ ctx->decoded_fmt.fmt.pix_mp.width >= 1920 && ++ ctx->decoded_fmt.fmt.pix_mp.height >= 1080, ++ "s_fmt output"); ++ if (ret) ++ goto out; ++ f = ctx->decoded_fmt; ++ ret = apple_vxd_do_s_capture_fmt(ctx, &f); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].sizeimage, ++ "s_fmt capture"); ++ if (ret) ++ goto out; ++ ++ /* VIDIOC_ENUM_FRAMESIZES */ ++ vxd_step(vxd->dev, "self-test: enum_framesizes"); ++ fsize.pixel_format = vxd->coded_fmts[0].fourcc; ++ ret = apple_vxd_do_enum_framesizes(ctx, &fsize); ++ ret = apple_vxd_selftest_check(vxd, ret, ++ fsize.stepwise.max_width == APPLE_VXD_MAX_PIC_W, ++ "enum_framesizes"); ++ ++out: ++ /* release() */ ++ vxd_step(vxd->dev, "self-test: release"); ++ apple_vxd_ctx_fini(ctx); ++ kfree(ctx); ++ if (!ret) ++ dev_info(vxd->dev, ++ "v4l2 self-test ok: open/querycap/enum/g/try/s_fmt/release\n"); ++ return ret; ++} ++ ++/* --- registration ------------------------------------------------------ */ ++ ++int apple_vxd_v4l2_init(struct apple_vxd_dev *vxd) ++{ ++ int ret; ++ ++ if (!vxd->coded_fmts || !vxd->num_coded_fmts) { ++ dev_err(vxd->dev, "v4l2: no coded formats registered, not exposing a node\n"); ++ return -ENODEV; ++ } ++ ++ vxd_step(vxd->dev, "v4l2: register v4l2 device"); ++ ret = v4l2_device_register(vxd->dev, &vxd->v4l2_dev); ++ if (ret) { ++ dev_err(vxd->dev, "failed to register v4l2 device\n"); ++ return ret; ++ } ++ ++ vxd_step(vxd->dev, "v4l2: init mem2mem device"); ++ vxd->m2m_dev = v4l2_m2m_init(&apple_vxd_m2m_ops); ++ if (IS_ERR(vxd->m2m_dev)) { ++ ret = PTR_ERR(vxd->m2m_dev); ++ vxd->m2m_dev = NULL; ++ v4l2_err(&vxd->v4l2_dev, "failed to init mem2mem device\n"); ++ goto err_unregister_v4l2; ++ } ++ ++ vxd->vdev.lock = &vxd->vdev_lock; ++ vxd->vdev.v4l2_dev = &vxd->v4l2_dev; ++ vxd->vdev.fops = &apple_vxd_fops; ++ vxd->vdev.release = video_device_release_empty; ++ vxd->vdev.vfl_dir = VFL_DIR_M2M; ++ vxd->vdev.device_caps = V4L2_CAP_STREAMING | ++ V4L2_CAP_VIDEO_M2M_MPLANE; ++ vxd->vdev.ioctl_ops = &apple_vxd_ioctl_ops; ++ video_set_drvdata(&vxd->vdev, vxd); ++ strscpy(vxd->vdev.name, APPLE_VXD_DRV_NAME, sizeof(vxd->vdev.name)); ++ ++ /* Nothing is exposed yet: fail here, not under udev. */ ++ ret = apple_vxd_v4l2_selftest(vxd); ++ if (ret) ++ goto err_release_m2m; ++ ++ /* bus_info stays empty: media_device_init() derives it from mdev.dev. */ ++ vxd->mdev.dev = vxd->dev; ++ strscpy(vxd->mdev.model, APPLE_VXD_DRV_NAME, sizeof(vxd->mdev.model)); ++ media_device_init(&vxd->mdev); ++ vxd->mdev.ops = &apple_vxd_media_ops; ++ vxd->v4l2_dev.mdev = &vxd->mdev; ++ ++ vxd_step(vxd->dev, "v4l2: register video device"); ++ ret = video_register_device(&vxd->vdev, VFL_TYPE_VIDEO, -1); ++ if (ret) { ++ v4l2_err(&vxd->v4l2_dev, "failed to register video device\n"); ++ goto err_cleanup_mc; ++ } ++ dev_info(vxd->dev, "v4l2: video device /dev/%s registered\n", ++ video_device_node_name(&vxd->vdev)); ++ ++ vxd_step(vxd->dev, "v4l2: register m2m media controller"); ++ ret = v4l2_m2m_register_media_controller(vxd->m2m_dev, &vxd->vdev, ++ MEDIA_ENT_F_PROC_VIDEO_DECODER); ++ if (ret) { ++ v4l2_err(&vxd->v4l2_dev, ++ "failed to initialize v4l2 m2m media controller\n"); ++ goto err_unregister_vdev; ++ } ++ ++ vxd_step(vxd->dev, "v4l2: register media device"); ++ ret = media_device_register(&vxd->mdev); ++ if (ret) { ++ v4l2_err(&vxd->v4l2_dev, "failed to register media device\n"); ++ goto err_unregister_mc; ++ } ++ dev_info(vxd->dev, "v4l2: media device /dev/media%d registered (model %s, bus %s)\n", ++ vxd->mdev.devnode->minor, vxd->mdev.model, vxd->mdev.bus_info); ++ ++ /* Only now does remove() own a full teardown. */ ++ vxd->v4l2_ready = true; ++ return 0; ++ ++err_unregister_mc: ++ v4l2_m2m_unregister_media_controller(vxd->m2m_dev); ++err_unregister_vdev: ++ video_unregister_device(&vxd->vdev); ++err_cleanup_mc: ++ vxd->v4l2_dev.mdev = NULL; ++ media_device_cleanup(&vxd->mdev); ++err_release_m2m: ++ v4l2_m2m_release(vxd->m2m_dev); ++ vxd->m2m_dev = NULL; ++err_unregister_v4l2: ++ v4l2_device_unregister(&vxd->v4l2_dev); ++ return ret; ++} ++ ++void apple_vxd_v4l2_cleanup(struct apple_vxd_dev *vxd) ++{ ++ vxd->v4l2_ready = false; ++ media_device_unregister(&vxd->mdev); ++ v4l2_m2m_unregister_media_controller(vxd->m2m_dev); ++ video_unregister_device(&vxd->vdev); ++ media_device_cleanup(&vxd->mdev); ++ v4l2_m2m_release(vxd->m2m_dev); ++ vxd->m2m_dev = NULL; ++ v4l2_device_unregister(&vxd->v4l2_dev); ++} +diff --git a/drivers/media/platform/apple/vxd/apple-vxd.h b/drivers/media/platform/apple/vxd/apple-vxd.h +new file mode 100644 +index 0000000000000000000000000000000000000000..cb1110fddcf63ad1d092ffd687ade02786d95d6d +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/apple-vxd.h +@@ -0,0 +1,528 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apple VXD (Imagination MSVDX) video decoder driver ++ * ++ * Bring-up driver for the S8000/S8003 "VXD" block: PowerVR MSVDX with the ++ * resident DEVA (MTX) firmware, reverse engineered from the iOS 16.7.10 ++ * AppleD5500 kext. The driver follows the rung ladder in ++ * docs/periph/ds-video-decoder.md: ++ * ++ * R0 probe without firmware: power domain, MMIO, IRQ, core revision ++ * R1 boot the resident DEVA image on the MTX ++ * R3a host<->firmware mailbox: ring geometry, 0x80 init message, push, ++ * IRQ drain, fences, debugfs ping (ds-vxd-decode-protocol.md) ++ * R3b command buffers: 8 x 256 KiB pool, packet container, command ++ * emitters, GART-mapped V4L2 buffers (ds-vxd-cmdstream.md) ++ * R3c+ H.264 command-stream generator and the decode submit (pending) ++ */ ++#ifndef __APPLE_VXD_H__ ++#define __APPLE_VXD_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-vxd-cmd.h" ++#include "apple-vxd-regs.h" ++ ++struct dentry; ++ ++#define APPLE_VXD_DRV_NAME "apple-vxd" ++#define APPLE_VXD_FW_NAME "apple/vxd-deva.bin" ++#define APPLE_VXD_FW_MAX_WORDS 0x10000 ++ ++/* ++ * DT advertises h264-playback-level = 42: H.264 level 4.2, 1920x1088 [E]. ++ * Tile decode exists (H264DecoderSupportsTileDecode = true) and could lift ++ * the pixel bound, but that path is not explored yet. ++ */ ++#define APPLE_VXD_MIN_PIC_W 16 ++#define APPLE_VXD_MIN_PIC_H 16 ++#define APPLE_VXD_MAX_PIC_W 1920 ++#define APPLE_VXD_MAX_PIC_H 1088 ++ ++/* Output image formats. P010/NV16 follow once HEVC/4:2:2 are proven. */ ++enum apple_vxd_image_fmt { ++ APPLE_VXD_IMG_FMT_ANY = 0, ++ APPLE_VXD_IMG_FMT_420_8BIT, ++}; ++ ++/* SoC variant data (compatible table). */ ++struct apple_vxd_variant { ++ unsigned int vxd_version; /* ADT "vxd-version" */ ++ u32 num_gart_pts; /* mapped page tables (512 = 2 GiB) */ ++ bool hevc_supported; ++}; ++ ++/* ++ * The core's own two-level "GART" (there is no DART on this node): a 1024 ++ * entry directory of 4 KiB page tables covering 4 GiB of device address ++ * space, entries beyond the mapped page tables pointing at a dummy page. ++ * ++ * GART pages are always 4 KiB, whatever the kernel page size (the device ++ * kernel runs 16 KiB pages). Entries are ((PA >> 8) & 0xfffffff0) | 1, ++ * computed on the full 40-bit physical address exactly as ++ * AppleD5500::initialize_gart does [E]: DRAM sits at 0x8_0000_0000 here, ++ * so truncating to 32 bits first (rounds 1-3) pointed every entry below ++ * DRAM. ++ * ++ * The directory is 64 KiB aligned: Apple allocates it that way ++ * (withOptions(0x403, 0x1000, alignment 0x10000)) because the decode ++ * message carries (MMU_DIR_ADR value & ~0xff) | hwContextID in one word ++ * (SubmitCommandBufferInternal, ds-vxd-decode-protocol.md correction). ++ * ++ * We identity-map the low 32 bits of every DMA address, so device address ++ * == dma_addr & 0xffffffff for the whole 2 GiB of DRAM on this device, and ++ * the mapped page tables (indices 0..num_gart_pts) cover it. ++ */ ++#define VXD_GART_PAGE_SHIFT 12 ++#define VXD_GART_PAGE_SIZE BIT(VXD_GART_PAGE_SHIFT) ++#define VXD_GART_DIR_ENTRIES 1024 ++#define VXD_GART_PT_ENTRIES 1024 ++#define VXD_GART_PT_SIZE (VXD_GART_PT_ENTRIES * sizeof(u32)) ++#define VXD_GART_DIR_ALIGN SZ_64K ++#define VXD_GART_PA_BITS 40 ++#define VXD_GART_PTE_VALID BIT(0) ++#define VXD_GART_PTE(phys) (((u32)((u64)(phys) >> 8) & 0xfffffff0) | \ ++ VXD_GART_PTE_VALID) ++ ++struct apple_vxd_mapping { ++ struct list_head list; ++ dma_addr_t dma; ++ size_t size; ++ u32 va; /* device address, == low 32 of dma */ ++}; ++ ++/* ++ * A coherent DMA buffer whose bus address is aligned beyond PAGE_SIZE. ++ * dma_alloc_coherent() makes no alignment promise past the page, so the ++ * buffer is over-allocated when the first try is misaligned. ++ */ ++struct apple_vxd_dma_buf { ++ void *vaddr; /* aligned CPU address */ ++ dma_addr_t dma; /* aligned bus address */ ++ size_t size; /* usable bytes from vaddr */ ++ void *raw_vaddr; /* what dma_free_coherent() needs */ ++ dma_addr_t raw_dma; ++ size_t raw_size; ++}; ++ ++/* A zeroed, GART-mapped buffer the core reads or writes (decode context). */ ++struct apple_vxd_dev_buf { ++ struct apple_vxd_dma_buf mem; ++ struct apple_vxd_mapping map; ++ u32 va; /* device address of mem.vaddr */ ++}; ++ ++struct apple_vxd_gart { ++ struct mutex lock; /* serializes table updates */ ++ struct apple_vxd_dma_buf dir_buf; /* directory, 64 KiB aligned */ ++ u32 *dir; /* directory table, VXD_GART_DIR_ENTRIES */ ++ dma_addr_t dir_dma; ++ u32 *dummy; /* unmapped VAs resolve here */ ++ dma_addr_t dummy_dma; ++ u32 *pts; /* num_gart_pts consecutive 4 KiB tables */ ++ dma_addr_t pts_dma; ++ u32 num_pts; ++ struct list_head mappings; /* every live apple_vxd_mapping */ ++ /* ++ * Set by every map/unmap. Apple rewrites 0x1008 = 0xf00 before the ++ * next decode submit when its GART changed (AppleD5500+0x27a0 [E]); ++ * the decode submit path consumes and clears it. ++ */ ++ bool dirty; ++}; ++ ++/* ++ * One message ring as published by the firmware after the ready magic: ++ * word i lives at VXD_RING_WINDOW + base + 4 * i. Read once per boot, ++ * validated against the MMIO window, never trusted beyond it. ++ */ ++struct apple_vxd_ring { ++ u32 base; /* byte offset from VXD_RING_WINDOW */ ++ u32 cap; /* capacity in words */ ++}; ++ ++/* Where a pushed message sits in TO_MTX, for the consumption check. */ ++struct apple_vxd_msg_ticket { ++ u32 start; /* write index before the push */ ++ u32 nwords; ++ u32 end; /* write index after the push */ ++}; ++ ++/* ++ * Fence tracking. The fence is the 16-bit tag in w0[31:16] of every ++ * message; the firmware echoes it in the 0xc0 completion. Apple builds it ++ * as (submission << 4) | (packet * 2 & 0xe) | core (0xfffffff00628a8d4 ++ * [E]), and routes completions to core (fence & 1). A slot is armed ++ * before the push and signalled from the IRQ drain. ++ */ ++#define APPLE_VXD_NUM_FENCES 16 ++ ++enum apple_vxd_fence_state { ++ APPLE_VXD_FENCE_FREE = 0, ++ APPLE_VXD_FENCE_ARMED, /* message pushed, completion pending */ ++ APPLE_VXD_FENCE_SIGNALLED, /* 0xc0 seen (or failed by panic/teardown) */ ++}; ++ ++struct apple_vxd_fence { ++ enum apple_vxd_fence_state state; ++ u16 id; /* w0[31:16] */ ++ u8 type; /* message type it tagged */ ++ bool error; /* fault bits, panic or teardown */ ++ u32 status; /* 0xc0 word 2 */ ++ struct apple_vxd_cmdbuf *cmdbuf; /* freed by a clean completion */ ++ bool job; /* last packet of a decode job */ ++ struct completion done; ++}; ++ ++/* ++ * R3b: command buffers. Apple allocates eight 0x40000-byte register ++ * programs and eight 0x10000-byte packet lists per H.264 decoder ++ * (DecodeCreateH264VLD 0xfffffff0062c3d98, ds-vxd-cmdstream.md section ++ * 16.5 [E]); a B slice with weight tables takes about 1.5 KiB, so 256 KiB ++ * holds APPLE_VXD_JOB_MAX_PACKETS of the largest. Each one is two things: ++ * ++ * - the device-visible register program, GART-mapped, filled with the ++ * ds-vxd-cmdstream.md command words one 64-byte-aligned chunk per ++ * slice, and read by the core's command processor; ++ * - a host-only packet list (one {0x80000007, 0x78, 0xabcdefab} + ++ * payload per slice) that the submit path turns into one mailbox ++ * message each and never hands to the device. ++ * ++ * Ownership: FREE (pool) -> HOST (CPU writes it) -> DEVICE (submitted, ++ * bound to the fence of its last message) -> FREE when that fence ++ * completes. A buffer whose fence was failed by a panic or a teardown ++ * stays DEVICE: the core may still be reading it, so only ++ * apple_vxd_cmdbufs_reclaim() after the firmware is stopped frees it. ++ */ ++#define APPLE_VXD_NUM_CMDBUFS 8 ++#define APPLE_VXD_CMDBUF_SIZE SZ_256K ++#define APPLE_VXD_CMDBUF_ALIGN SZ_64K ++#define APPLE_VXD_PKTLIST_SIZE SZ_32K /* 128 x (12 + 0x78) bytes */ ++ ++struct apple_vxd_cmdbuf_pool { ++ struct apple_vxd_dma_buf mem; /* NUM x SIZE, 64 KiB aligned */ ++ struct apple_vxd_mapping map; ++ u8 *pkt_mem; ++ struct apple_vxd_cmdbuf bufs[APPLE_VXD_NUM_CMDBUFS]; ++ bool ready; ++}; ++ ++/* ++ * V4L2 buffer with its GART mapping: the 0xa load command takes the ++ * bitstream by device address, and the render target / DPB registers take ++ * the capture surfaces the same way (ds-vxd-cmdstream.md sections 3.3, 6). ++ */ ++struct apple_vxd_buffer { ++ struct v4l2_m2m_buffer base; ++ struct apple_vxd_mapping map; ++ u32 va; /* device VA of plane 0's first byte */ ++ /* ++ * CAPTURE only: the per-picture side buffer the core keeps with the ++ * reconstructed surface (H.264: stored macroblock parameters, BE ++ * register 0x62ec), allocated with the buffer. ++ */ ++ struct apple_vxd_dev_buf aux; ++}; ++ ++struct apple_vxd_mbox_stats { ++ u32 sent; /* messages pushed to TO_MTX */ ++ u32 received; /* messages drained from TO_HOST */ ++ u32 irqs; /* handled interrupts */ ++ u32 spurious; /* interrupts without 0x410 bit 20 */ ++ u32 completions; /* 0xc0 */ ++ u32 unknown_fences; /* 0xc0 for no armed fence */ ++ u32 faults; /* 0xc0 with fault bits */ ++ u32 contiguity; /* 0xc6 */ ++ u32 panics; /* 0xc7 */ ++ u32 unknown; /* anything else */ ++ u32 malformed; /* bad length / index */ ++ u32 last_irq_status; /* 0x410 at the last interrupt */ ++}; ++ ++/* ++ * Active decode job. Submission is one picture: one stateless request, one ++ * bitstream buffer, one capture surface. Completion arrives from the ++ * firmware through the TO_HOST ring as a 0xc0 message. ++ */ ++struct apple_vxd_run { ++ struct vb2_v4l2_buffer *src; ++ struct vb2_v4l2_buffer *dst; ++}; ++ ++/* ++ * One submission in flight: one picture, one command buffer, one message ++ * per packet. Every message carries a fence of the same submission, ++ * (seq << 4) | (packet * 2 & 0xe) as Apple computes it, so the tags repeat ++ * after eight packets. Up to eight packets every fence is armed and a ++ * 0xc0 for any of them is recognised (round 5 on the device: only the ++ * 0x4004 message answers); past eight only the last packet's is, since ++ * the firmware never answers a 0x2000 message. The job completes on the ++ * last packet's fence or on the watchdog. ++ */ ++#define APPLE_VXD_JOB_MAX_FENCES 8 ++#define APPLE_VXD_JOB_MAX_PACKETS 128 ++#define APPLE_VXD_JOB_TIMEOUT_MS 1000 ++ ++struct apple_vxd_job { ++ struct apple_vxd_ctx *ctx; ++ struct apple_vxd_fence *fences[APPLE_VXD_JOB_MAX_FENCES]; ++ unsigned int nfences; /* armed; the last one is the job's */ ++ unsigned int npackets; ++ u16 seq; ++ ktime_t submitted; ++}; ++ ++struct apple_vxd_dev { ++ struct device *dev; ++ const struct apple_vxd_variant *variant; ++ ++ void __iomem *regs; ++ resource_size_t regs_size; /* bounds every ring access */ ++ int irq; ++ ++ /* Bring-up state (R0/R1). */ ++ struct mutex power_lock; /* boot vs. runtime suspend */ ++ bool hw_ready; /* firmware took its init message */ ++ bool selftest_failed; /* protocol self-test: no boot */ ++ u32 core_id; ++ u32 core_rev; ++ u32 va_offset; /* device-VA base offset (module param) */ ++ u32 num_entropy_pipes; ++ u32 num_pixel_pipes; ++ u32 mtx_mem_size; /* KiB, from MTX_MEM_CFG */ ++ bool mmu_40bit; ++ bool mmu_tiling_ctx; ++ u32 pipe_status; /* raw 0x40c0 after power-up */ ++ u32 pipe_status_cfg; /* OR of per-pipe 0x40c0 (R1 discovery) */ ++ u32 pipe_status_2_max; /* max per-pipe 0x40c4 & 3 */ ++ u32 cfg_flags; /* InitializeCoreConfig core[0x54] */ ++ ++ /* R1: firmware buffer + boot results. */ ++ bool fw_loaded; /* MTX up: ready magic seen */ ++ void *fw_vaddr; ++ dma_addr_t fw_dma; ++ size_t fw_size; /* PAGE_ALIGNed allocation */ ++ size_t fw_blob_size; /* image bytes (init message pages) */ ++ u32 fw_dev_va; ++ struct apple_vxd_mapping fw_map; /* GART mapping of fw_dev_va */ ++ ++ struct apple_vxd_gart gart; ++ ++ /* ++ * R3a: host<->MTX mailbox. mbox_lock covers ring MMIO in both ++ * directions, the fence table and the stats; it is taken from the ++ * hard IRQ handler. mbox_ready/hw_ready/irq_armed change only under ++ * power_lock. ++ */ ++ spinlock_t mbox_lock; ++ struct apple_vxd_ring to_mtx; ++ struct apple_vxd_ring to_host; ++ bool mbox_ready; /* geometry read and valid */ ++ bool irq_armed; /* IRQ line enabled */ ++ bool irq_storm; /* disabled by the storm guard */ ++ u32 irq_empty; /* consecutive IRQs with nothing to drain */ ++ bool fw_panicked; /* 0xc7 seen since boot */ ++ u16 init_seq; /* InitMessageBuffers core[0x70] */ ++ u16 fence_seq; /* submission counter, fence[15:4] */ ++ struct apple_vxd_fence fences[APPLE_VXD_NUM_FENCES]; ++ struct apple_vxd_mbox_stats stats; ++ ++ /* R3b: command buffers, allocated and mapped before the MTX starts. */ ++ struct apple_vxd_cmdbuf_pool cmdbufs; ++ ++ /* Active job (R3d; decode jobs fail until the command stream lands). */ ++ spinlock_t job_lock; ++ bool job_active; ++ struct apple_vxd_job job; /* under job_lock while job_active */ ++ struct work_struct job_done_work; ++ struct delayed_work watchdog_work; ++ ++ /* V4L2 stateless device (R3/R4 skeleton). */ ++ struct v4l2_device v4l2_dev; ++ struct media_device mdev; ++ struct video_device vdev; ++ struct v4l2_m2m_dev *m2m_dev; ++ struct mutex vdev_lock; /* serializes ioctls */ ++ bool v4l2_ready; /* set last, once every node is up */ ++ /* ++ * Coded formats, filled by apple_vxd_h264_register() during probe ++ * before any V4L2 node exists. Round 2 never filled them (the ++ * variant table had no entry), so the first open() of /dev/video0 ++ * read coded_fmts[0] at address 0. ++ */ ++ const struct apple_vxd_coded_fmt_desc *coded_fmts; ++ unsigned int num_coded_fmts; ++ struct dentry *debugfs_dir; /* dumps, boot trigger, ping */ ++}; ++ ++struct apple_vxd_ctx { ++ struct v4l2_fh fh; ++ struct apple_vxd_dev *vxd; ++ struct v4l2_format coded_fmt; ++ struct v4l2_format decoded_fmt; ++ const struct apple_vxd_coded_fmt_desc *coded_fmt_desc; ++ struct v4l2_ctrl_handler ctrl_hdl; ++ enum apple_vxd_image_fmt image_fmt; ++ void *priv; ++}; ++ ++struct apple_vxd_coded_fmt_ops { ++ int (*adjust_fmt)(struct apple_vxd_ctx *ctx, struct v4l2_format *f); ++ int (*start)(struct apple_vxd_ctx *ctx); ++ void (*stop)(struct apple_vxd_ctx *ctx); ++ int (*run)(struct apple_vxd_ctx *ctx); ++ int (*try_ctrl)(struct apple_vxd_ctx *ctx, struct v4l2_ctrl *ctrl); ++ enum apple_vxd_image_fmt (*get_image_fmt)(struct apple_vxd_ctx *ctx, ++ struct v4l2_ctrl *ctrl); ++ /* Bytes of per-CAPTURE side buffer, 0 for none. */ ++ size_t (*capture_aux_size)(struct apple_vxd_ctx *ctx); ++}; ++ ++struct apple_vxd_decoded_fmt_desc { ++ u32 fourcc; ++ enum apple_vxd_image_fmt image_fmt; ++}; ++ ++struct apple_vxd_ctrl_desc { ++ struct v4l2_ctrl_config cfg; ++}; ++ ++struct apple_vxd_ctrls { ++ const struct apple_vxd_ctrl_desc *ctrls; ++ unsigned int num_ctrls; ++}; ++ ++struct apple_vxd_coded_fmt_desc { ++ u32 fourcc; ++ struct v4l2_frmsize_stepwise frmsize; ++ const struct apple_vxd_ctrls *ctrls; ++ const struct apple_vxd_coded_fmt_ops *ops; ++ unsigned int num_decoded_fmts; ++ const struct apple_vxd_decoded_fmt_desc *decoded_fmts; ++}; ++ ++/* Message header word: (fence << 16) | (type << 8) | nwords. */ ++static inline u32 apple_vxd_msg_hdr(u16 fence, u8 type, u32 nwords) ++{ ++ return FIELD_PREP(VXD_MSG_FENCE, fence) | ++ FIELD_PREP(VXD_MSG_TYPE, type) | ++ FIELD_PREP(VXD_MSG_NWORDS, nwords); ++} ++ ++/* Register access (CPVDECCore::ReadReg/WriteReg [E], native endian). */ ++static inline void vxd_write(struct apple_vxd_dev *vxd, u32 off, u32 val) ++{ ++ writel(val, vxd->regs + off); ++} ++ ++static inline u32 vxd_read(struct apple_vxd_dev *vxd, u32 off) ++{ ++ return readl(vxd->regs + off); ++} ++ ++/* core.c */ ++irqreturn_t apple_vxd_irq(int irq, void *data); ++void apple_vxd_watchdog(struct work_struct *work); ++void apple_vxd_job_done(struct work_struct *work); ++void apple_vxd_job_finish(struct apple_vxd_ctx *ctx, ++ enum vb2_buffer_state result); ++void apple_vxd_job_finish_no_pm(struct apple_vxd_ctx *ctx, ++ enum vb2_buffer_state result); ++void apple_vxd_run_preamble(struct apple_vxd_ctx *ctx, ++ struct apple_vxd_run *run); ++void apple_vxd_run_postamble(struct apple_vxd_ctx *ctx, ++ struct apple_vxd_run *run); ++void apple_vxd_log_pwrstate(struct apple_vxd_dev *vxd, const char *when); ++__printf(2, 3) void vxd_step(const struct device *dev, const char *fmt, ...); ++__printf(2, 3) void vxd_step_io(const struct device *dev, const char *fmt, ...); ++ ++/* gart.c */ ++int apple_vxd_dma_alloc_aligned(struct apple_vxd_dev *vxd, size_t size, ++ size_t align, struct apple_vxd_dma_buf *buf); ++void apple_vxd_dma_free_aligned(struct apple_vxd_dev *vxd, ++ struct apple_vxd_dma_buf *buf); ++int apple_vxd_gart_init(struct apple_vxd_dev *vxd); ++void apple_vxd_gart_fini(struct apple_vxd_dev *vxd); ++int apple_vxd_gart_map(struct apple_vxd_dev *vxd, dma_addr_t dma, size_t size, ++ struct apple_vxd_mapping *map); ++int apple_vxd_gart_map_at(struct apple_vxd_dev *vxd, dma_addr_t dma, ++ size_t size, u32 va_base, ++ struct apple_vxd_mapping *map); ++void apple_vxd_gart_unmap(struct apple_vxd_dev *vxd, ++ struct apple_vxd_mapping *map); ++int apple_vxd_dev_buf_alloc(struct apple_vxd_dev *vxd, size_t size, ++ struct apple_vxd_dev_buf *buf); ++void apple_vxd_dev_buf_free(struct apple_vxd_dev *vxd, ++ struct apple_vxd_dev_buf *buf); ++ ++/* fw.c */ ++int apple_vxd_boot_firmware(struct apple_vxd_dev *vxd); ++void apple_vxd_fw_cleanup(struct apple_vxd_dev *vxd); ++ ++/* mbox.c */ ++int apple_vxd_mbox_init(struct apple_vxd_dev *vxd); ++void apple_vxd_mbox_fini(struct apple_vxd_dev *vxd); ++int apple_vxd_mbox_open(struct apple_vxd_dev *vxd); ++int apple_vxd_mbox_start(struct apple_vxd_dev *vxd); ++void apple_vxd_mbox_stop(struct apple_vxd_dev *vxd); ++int apple_vxd_msg_push(struct apple_vxd_dev *vxd, const u32 *msg, ++ struct apple_vxd_msg_ticket *ticket); ++int apple_vxd_msg_wait_consumed(struct apple_vxd_dev *vxd, ++ const struct apple_vxd_msg_ticket *ticket, ++ unsigned int timeout_us, unsigned int *waited_us); ++unsigned int apple_vxd_mbox_drain(struct apple_vxd_dev *vxd); ++struct apple_vxd_fence *apple_vxd_fence_arm(struct apple_vxd_dev *vxd, ++ unsigned int packet, u8 type); ++void apple_vxd_fence_release(struct apple_vxd_dev *vxd, ++ struct apple_vxd_fence *fence); ++void apple_vxd_build_init_msg(u32 msg[VXD_INIT_MSG_WORDS], u32 fw_va, ++ size_t fw_bytes, u16 seq); ++int apple_vxd_mbox_selftest(struct apple_vxd_dev *vxd); ++int apple_vxd_submit(struct apple_vxd_ctx *ctx, struct apple_vxd_cmdbuf *cb); ++ ++/* cmdbuf.c */ ++int apple_vxd_cmdbufs_init(struct apple_vxd_dev *vxd); ++void apple_vxd_cmdbufs_fini(struct apple_vxd_dev *vxd); ++void apple_vxd_cmdbufs_reclaim(struct apple_vxd_dev *vxd); ++struct apple_vxd_cmdbuf *apple_vxd_cmdbuf_get(struct apple_vxd_dev *vxd); ++void apple_vxd_cmdbuf_put(struct apple_vxd_dev *vxd, ++ struct apple_vxd_cmdbuf *cb); ++int apple_vxd_cmdbuf_bind(struct apple_vxd_dev *vxd, ++ struct apple_vxd_cmdbuf *cb, ++ struct apple_vxd_fence *fence); ++void apple_vxd_cmdbuf_retire_locked(struct apple_vxd_cmdbuf *cb); ++u32 apple_vxd_mmu_ctx_word(struct apple_vxd_dev *vxd, u8 hw_ctx); ++int apple_vxd_packet_msg(struct apple_vxd_dev *vxd, u32 *pkt, u16 fence, ++ u8 hw_ctx, bool last, u32 msg[VXD_DECODE_MSG_WORDS]); ++int apple_vxd_cmdbuf_selftest(struct apple_vxd_dev *vxd); ++void apple_vxd_cmdbufs_debugfs(struct apple_vxd_dev *vxd); ++ ++/* v4l2.c */ ++u32 apple_vxd_buf_va(struct vb2_v4l2_buffer *vbuf); ++u32 apple_vxd_buf_aux_va(struct vb2_v4l2_buffer *vbuf); ++int apple_vxd_v4l2_init(struct apple_vxd_dev *vxd); ++void apple_vxd_v4l2_cleanup(struct apple_vxd_dev *vxd); ++extern const struct v4l2_ctrl_ops apple_vxd_ctrl_ops; ++ ++/* h264.c */ ++int apple_vxd_h264_register(struct apple_vxd_dev *vxd); ++ ++#endif /* __APPLE_VXD_H__ */ +diff --git a/drivers/media/platform/apple/vxd/h264-tables.py b/drivers/media/platform/apple/vxd/h264-tables.py +new file mode 100644 +index 0000000000000000000000000000000000000000..9053365e9bd838e682d17da9537dde4771e65c23 +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/h264-tables.py +@@ -0,0 +1,132 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: GPL-2.0 ++"""Offline, pinned-source H.264 VLC generator and exact comparator. ++ ++No kext, device, network, compiler, or third-party Python package is needed. ++The upstream file is preserved verbatim in upstream/h264_vlc.h. ++""" ++import argparse ++import hashlib ++import json ++from pathlib import Path ++import re ++import struct ++import sys ++import zlib ++ ++COMMIT = "9453ad8ebdd42a5e5a384c21cd5f6bc2c86aa6d8" ++URL = f"https://github.com/TexasInstruments/img-vxe-vxd/blob/{COMMIT}/decoder/h264_vlc.h" ++SHA256 = "9b349a315038817651a8f6c0be87bb82321f9b046901957a3d65e73f6f1eeb66" ++PSB_SHA256 = "096be85abdd08365e6a701a67fcbdf224bd181f3377e1d1e1bb84a274baf3285" ++SOURCE = Path(__file__).with_name("upstream") / "h264_vlc.h" ++ ++ ++def array(text, name): ++ """Parse numeric C initializers only; reject unrecognized expressions.""" ++ text = re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S) ++ match = re.search(r"\b" + re.escape(name) + r"\s*(?:\[[^\]]*\])+\s*=\s*\{(.*?)\};", text, re.S) ++ if not match: ++ raise ValueError(f"missing initializer: {name}") ++ body = match[1] ++ tokens = re.findall(r"0[xX][0-9a-fA-F]+|\d+", body) ++ if re.sub(r"0[xX][0-9a-fA-F]+|\d+|[\s,{}]", "", body): ++ raise ValueError(f"unsupported initializer: {name}") ++ return [int(t, 16 if t.lower().startswith("0x") else 10) for t in tokens] ++ ++ ++def packed(source=SOURCE): ++ raw = source.read_bytes() ++ if hashlib.sha256(raw).hexdigest() != SHA256: ++ raise ValueError("GPL source SHA-256 differs from pinned upstream") ++ nums = array(raw.decode(), "h264_vlc_table_data") ++ if len(nums) != 544 * 3: ++ raise ValueError(f"expected 1632 values, found {len(nums)}") ++ result = [] ++ for opcode, width, symbol in zip(nums[::3], nums[1::3], nums[2::3]): ++ if not (0 <= opcode < 8 and 0 <= width < 8 and 0 <= symbol < 512): ++ raise ValueError("VLC field overflow") ++ result.append((opcode << 12) | (width << 9) | symbol) ++ return result ++ ++ ++def render_header(values, workspace=False): ++ prefix = "VXD" if workspace else "APPLE_VXD" ++ symbol = "vxd" if workspace else "apple_vxd" ++ guard = f"__{prefix}_H264_TABLES_H__" ++ crc = zlib.crc32(struct.pack("<544H", *values)) ++ byte_define = f"#define {prefix}_H264_VLC_BYTES\t1088\n" if workspace else "" ++ rows = "\n".join("\t" + ", ".join(f"0x{v:04x}" for v in values[i:i + 8]) + "," ++ for i in range(0, len(values), 8)) ++ return f"""/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) Imagination Technologies Ltd. ++ * Copyright (c) 2021 Texas Instruments Incorporated - http://www.ti.com/ ++ * ++ * H.264 CAVLC decode table, generated by h264-tables.py from ++ * img-vxe-vxd decoder/h264_vlc.h (h264_vlc_table_data). ++ * Upstream author: Angela Stegmaier ++ * {URL} ++ * Source SHA-256: {SHA256} ++ * Packing: opcode << 12 | width << 9 | symbol. No Apple binary input. ++ * CRC-32 of the 1088 little-endian bytes: 0x{crc:08x}. ++ */ ++#ifndef {guard} ++#define {guard} ++ ++#include ++ ++#define {prefix}_H264_VLC_ENTRIES\t544 ++{byte_define}#define {prefix}_H264_VLC_CRC32\t0x{crc:08x} ++ ++/* opcode[14:12] | width[11:9] | symbol[8:0] */ ++static const u16 {symbol}_h264_vlc_table[{prefix}_H264_VLC_ENTRIES] = {{ ++{rows} ++}}; ++ ++#endif /* {guard} */ ++""" ++ ++ ++def compare(expected, actual, label): ++ if expected != actual: ++ mismatch = next((i for i, (a, b) in enumerate(zip(expected, actual)) if a != b), None) ++ raise ValueError(f"{label}: mismatch at {mismatch}; lengths {len(expected)}/{len(actual)}") ++ return {"comparison": label, "matching_entries": len(expected)} ++ ++ ++def main(): ++ ap = argparse.ArgumentParser(description=__doc__) ++ ap.add_argument("--source", type=Path, default=SOURCE) ++ ap.add_argument("--compare", action="append", type=Path, default=[], help="kernel or workspace header") ++ ap.add_argument("--psb", type=Path, help="pinned psb_video src/pnw_H264.c") ++ ap.add_argument("--write", type=Path, help="generate header; otherwise print JSON evidence") ++ ap.add_argument("--workspace", action="store_true", help="use workspace symbol names") ++ args = ap.parse_args() ++ values = packed(args.source) ++ checks = [] ++ for path in args.compare: ++ text = path.read_text() ++ name = "apple_vxd_h264_vlc_table" if "apple_vxd_h264_vlc_table[" in text else "vxd_h264_vlc_table" ++ checks.append(compare(values, array(text, name), str(path))) ++ if args.psb: ++ raw = args.psb.read_bytes() ++ if hashlib.sha256(raw).hexdigest() != PSB_SHA256: ++ raise ValueError("psb source SHA-256 differs from pinned upstream") ++ psb = array(raw.decode(), "ui16H264VLCTableData") ++ if len(psb) != 520: ++ raise ValueError(f"expected 520 psb entries, found {len(psb)}") ++ checks.append(compare(values[:520], psb, "MIT psb_video prefix")) ++ data = struct.pack("<544H", *values) ++ if args.write: ++ args.write.write_text(render_header(values, args.workspace)) ++ print(json.dumps({"source_url": URL, "source_commit": COMMIT, ++ "source_sha256": SHA256, "entries": len(values), ++ "table_sha256": hashlib.sha256(data).hexdigest(), ++ "table_crc32": f"0x{zlib.crc32(data):08x}", "checks": checks}, indent=2)) ++ ++ ++if __name__ == "__main__": ++ try: ++ main() ++ except (ValueError, OSError) as error: ++ sys.exit(str(error)) +diff --git a/drivers/media/platform/apple/vxd/upstream/h264_vlc.h b/drivers/media/platform/apple/vxd/upstream/h264_vlc.h +new file mode 100644 +index 0000000000000000000000000000000000000000..7cd79ada8ecc245791e5bf0c5fe25c57bdd042ee +--- /dev/null ++++ b/drivers/media/platform/apple/vxd/upstream/h264_vlc.h +@@ -0,0 +1,604 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * h264 vlc table definitions ++ * ++ * Copyright (c) Imagination Technologies Ltd. ++ * Copyright (c) 2021 Texas Instruments Incorporated - http://www.ti.com/ ++ * ++ * Authors: ++ * Angela Stegmaier ++ */ ++ ++#ifndef __H264_VLC_H__ ++#define __H264_VLC_H__ ++ ++#include ++ ++static unsigned short h264_vlc_table_data[] = { ++/* NumCoeffTrailingOnes_Table9-5_nC_0-1.out */ ++ 4, 0, 0, ++ 4, 1, 5, ++ 4, 2, 10, ++ 2, 1, 4, ++ 2, 1, 6, ++ 0, 1, 8, ++ 0, 2, 11, ++ 4, 0, 15, ++ 4, 1, 4, ++ 4, 1, 9, ++ 4, 0, 19, ++ 4, 1, 14, ++ 4, 1, 23, ++ 4, 1, 27, ++ 4, 1, 18, ++ 4, 1, 13, ++ 4, 1, 8, ++ 2, 5, 8, ++ 0, 1, 50, ++ 0, 0, 53, ++ 0, 0, 54, ++ 4, 2, 31, ++ 4, 2, 22, ++ 4, 2, 17, ++ 4, 2, 12, ++ 0, 2, 7, ++ 0, 2, 14, ++ 0, 2, 21, ++ 0, 2, 28, ++ 0, 1, 35, ++ 4, 5, 53, ++ 3, 5, 0, ++ 4, 2, 32, ++ 4, 2, 38, ++ 4, 2, 33, ++ 4, 2, 28, ++ 4, 2, 43, ++ 4, 2, 34, ++ 4, 2, 29, ++ 4, 2, 24, ++ 4, 2, 51, ++ 4, 2, 46, ++ 4, 2, 41, ++ 4, 2, 40, ++ 4, 2, 47, ++ 4, 2, 42, ++ 4, 2, 37, ++ 4, 2, 36, ++ 4, 2, 59, ++ 4, 2, 54, ++ 4, 2, 49, ++ 4, 2, 48, ++ 4, 2, 55, ++ 4, 2, 50, ++ 4, 2, 45, ++ 4, 2, 44, ++ 4, 2, 67, ++ 4, 2, 62, ++ 4, 2, 61, ++ 4, 2, 56, ++ 4, 2, 63, ++ 4, 2, 58, ++ 4, 2, 57, ++ 4, 2, 52, ++ 4, 1, 64, ++ 4, 1, 66, ++ 4, 1, 65, ++ 4, 1, 60, ++ 4, 1, 39, ++ 4, 1, 30, ++ 4, 1, 25, ++ 4, 1, 20, ++ 4, 0, 35, ++ 4, 0, 26, ++ 4, 0, 21, ++ 4, 0, 16, ++/* NumCoeffTrailingOnes_Table9-5_nC_2-3.out */ ++ 0, 2, 16, ++ 0, 1, 73, ++ 0, 1, 76, ++ 0, 0, 79, ++ 4, 3, 19, ++ 4, 3, 15, ++ 4, 2, 10, ++ 4, 2, 10, ++ 4, 1, 5, ++ 4, 1, 5, ++ 4, 1, 5, ++ 4, 1, 5, ++ 4, 1, 0, ++ 4, 1, 0, ++ 4, 1, 0, ++ 4, 1, 0, ++ 2, 5, 8, ++ 0, 1, 49, ++ 0, 0, 52, ++ 0, 0, 53, ++ 4, 2, 35, ++ 4, 2, 22, ++ 4, 2, 21, ++ 4, 2, 12, ++ 0, 2, 7, ++ 0, 2, 14, ++ 0, 2, 21, ++ 1, 1, 28, ++ 0, 1, 34, ++ 4, 5, 63, ++ 3, 5, 0, ++ 4, 2, 47, ++ 4, 2, 38, ++ 4, 2, 37, ++ 4, 2, 32, ++ 4, 2, 43, ++ 4, 2, 34, ++ 4, 2, 33, ++ 4, 2, 28, ++ 4, 2, 44, ++ 4, 2, 46, ++ 4, 2, 45, ++ 4, 2, 40, ++ 4, 2, 51, ++ 4, 2, 42, ++ 4, 2, 41, ++ 4, 2, 36, ++ 4, 2, 59, ++ 4, 2, 54, ++ 4, 2, 53, ++ 4, 2, 52, ++ 4, 2, 55, ++ 4, 2, 50, ++ 4, 2, 49, ++ 4, 2, 48, ++ 0, 1, 3, ++ 4, 1, 58, ++ 4, 1, 56, ++ 4, 1, 61, ++ 4, 1, 60, ++ 4, 1, 62, ++ 4, 1, 57, ++ 4, 1, 67, ++ 4, 1, 66, ++ 4, 1, 65, ++ 4, 1, 64, ++ 4, 1, 39, ++ 4, 1, 30, ++ 4, 1, 29, ++ 4, 1, 24, ++ 4, 0, 20, ++ 4, 0, 26, ++ 4, 0, 25, ++ 4, 0, 16, ++ 4, 1, 31, ++ 4, 1, 18, ++ 4, 1, 17, ++ 4, 1, 8, ++ 4, 1, 27, ++ 4, 1, 14, ++ 4, 1, 13, ++ 4, 1, 4, ++ 4, 0, 23, ++ 4, 0, 9, ++/* NumCoeffTrailingOnes_Table9-5_nC_4-7.out */ ++ 2, 1, 16, ++ 0, 2, 50, ++ 0, 1, 57, ++ 0, 1, 60, ++ 6, 0, 10, ++ 6, 0, 8, ++ 0, 0, 61, ++ 0, 0, 62, ++ 4, 3, 31, ++ 4, 3, 27, ++ 4, 3, 23, ++ 4, 3, 19, ++ 4, 3, 15, ++ 4, 3, 10, ++ 4, 3, 5, ++ 4, 3, 0, ++ 0, 2, 3, ++ 0, 2, 10, ++ 0, 3, 17, ++ 4, 2, 51, ++ 4, 2, 46, ++ 4, 2, 41, ++ 4, 2, 36, ++ 4, 2, 47, ++ 4, 2, 42, ++ 4, 2, 37, ++ 4, 2, 32, ++ 4, 2, 48, ++ 4, 2, 54, ++ 4, 2, 49, ++ 4, 2, 44, ++ 4, 2, 55, ++ 4, 2, 50, ++ 4, 2, 45, ++ 4, 2, 40, ++ 3, 3, 0, ++ 4, 3, 64, ++ 4, 3, 67, ++ 4, 3, 66, ++ 4, 3, 65, ++ 4, 3, 60, ++ 4, 3, 63, ++ 4, 3, 62, ++ 4, 3, 61, ++ 4, 3, 56, ++ 4, 3, 59, ++ 4, 3, 58, ++ 4, 3, 57, ++ 4, 3, 52, ++ 4, 2, 53, ++ 4, 2, 53, ++ 4, 2, 28, ++ 4, 2, 24, ++ 4, 2, 38, ++ 4, 2, 20, ++ 4, 2, 43, ++ 4, 2, 34, ++ 4, 2, 33, ++ 4, 2, 16, ++ 4, 1, 12, ++ 4, 1, 30, ++ 4, 1, 29, ++ 4, 1, 8, ++ 4, 1, 39, ++ 4, 1, 26, ++ 4, 1, 25, ++ 4, 1, 4, ++ 4, 0, 13, ++ 4, 0, 35, ++ 4, 0, 14, ++ 4, 0, 9, ++/* NumCoeffTrailingOnesFixedLen.out */ ++ 2, 1, 8, ++ 5, 2, 6, ++ 5, 2, 10, ++ 5, 2, 14, ++ 5, 2, 18, ++ 5, 2, 22, ++ 5, 2, 26, ++ 5, 2, 30, ++ 5, 1, 4, ++ 0, 0, 2, ++ 5, 0, 2, ++ 3, 0, 0, ++ 4, 0, 0, ++/* NumCoeffTrailingOnesChromaDC_YUV420.out */ ++ 4, 0, 5, ++ 4, 1, 0, ++ 4, 2, 10, ++ 0, 2, 1, ++ 1, 1, 8, ++ 0, 0, 10, ++ 4, 2, 16, ++ 4, 2, 12, ++ 4, 2, 8, ++ 4, 2, 15, ++ 4, 2, 9, ++ 4, 2, 4, ++ 4, 0, 19, ++ 4, 1, 18, ++ 4, 1, 17, ++ 4, 0, 14, ++ 4, 0, 13, ++/* NumCoeffTrailingOnesChromaDC_YUV422.out */ ++ 4, 0, 0, ++ 4, 1, 5, ++ 4, 2, 10, ++ 0, 2, 4, ++ 4, 4, 15, ++ 4, 5, 19, ++ 2, 3, 9, ++ 4, 2, 27, ++ 4, 2, 23, ++ 4, 2, 18, ++ 4, 2, 14, ++ 4, 2, 13, ++ 4, 2, 9, ++ 4, 2, 8, ++ 4, 2, 4, ++ 0, 1, 5, ++ 0, 1, 8, ++ 0, 1, 11, ++ 0, 1, 14, ++ 1, 2, 17, ++ 4, 1, 22, ++ 4, 1, 17, ++ 4, 1, 16, ++ 4, 1, 12, ++ 4, 1, 31, ++ 4, 1, 26, ++ 4, 1, 21, ++ 4, 1, 20, ++ 4, 1, 35, ++ 4, 1, 30, ++ 4, 1, 25, ++ 4, 1, 24, ++ 4, 1, 34, ++ 4, 1, 33, ++ 4, 1, 29, ++ 4, 1, 28, ++ 3, 2, 0, ++ 3, 2, 0, ++ 3, 2, 0, ++ 4, 2, 32, ++/* TotalZeros_00.out */ ++ 4, 0, 0, ++ 0, 0, 6, ++ 0, 0, 7, ++ 0, 0, 8, ++ 0, 0, 9, ++ 0, 0, 10, ++ 0, 2, 11, ++ 4, 0, 2, ++ 4, 0, 1, ++ 4, 0, 4, ++ 4, 0, 3, ++ 4, 0, 6, ++ 4, 0, 5, ++ 4, 0, 8, ++ 4, 0, 7, ++ 4, 0, 10, ++ 4, 0, 9, ++ 3, 2, 0, ++ 4, 2, 15, ++ 4, 2, 14, ++ 4, 2, 13, ++ 4, 1, 12, ++ 4, 1, 12, ++ 4, 1, 11, ++ 4, 1, 11, ++/* TotalZeros_01.out */ ++ 1, 1, 8, ++ 0, 0, 14, ++ 0, 0, 15, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 2, 2, ++ 4, 2, 1, ++ 4, 2, 0, ++ 0, 1, 3, ++ 4, 1, 10, ++ 4, 1, 9, ++ 4, 1, 14, ++ 4, 1, 13, ++ 4, 1, 12, ++ 4, 1, 11, ++ 4, 0, 8, ++ 4, 0, 7, ++ 4, 0, 6, ++ 4, 0, 5, ++/* TotalZeros_02.out */ ++ 0, 1, 8, ++ 0, 0, 13, ++ 0, 0, 14, ++ 4, 2, 7, ++ 4, 2, 6, ++ 4, 2, 3, ++ 4, 2, 2, ++ 4, 2, 1, ++ 0, 0, 4, ++ 4, 1, 12, ++ 4, 1, 10, ++ 4, 1, 9, ++ 4, 0, 13, ++ 4, 0, 11, ++ 4, 0, 8, ++ 4, 0, 5, ++ 4, 0, 4, ++ 4, 0, 0, ++/* TotalZeros_03.out */ ++ 0, 1, 8, ++ 0, 0, 11, ++ 0, 0, 12, ++ 4, 2, 8, ++ 4, 2, 6, ++ 4, 2, 5, ++ 4, 2, 4, ++ 4, 2, 1, ++ 4, 1, 12, ++ 4, 1, 11, ++ 4, 1, 10, ++ 4, 1, 0, ++ 4, 0, 9, ++ 4, 0, 7, ++ 4, 0, 3, ++ 4, 0, 2, ++/* TotalZeros_04.out */ ++ 2, 1, 8, ++ 0, 0, 10, ++ 0, 0, 11, ++ 4, 2, 7, ++ 4, 2, 6, ++ 4, 2, 5, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 0, 10, ++ 4, 1, 9, ++ 4, 1, 11, ++ 4, 0, 8, ++ 4, 0, 2, ++ 4, 0, 1, ++ 4, 0, 0, ++/* TotalZeros_05.out */ ++ 2, 2, 8, ++ 4, 2, 9, ++ 4, 2, 7, ++ 4, 2, 6, ++ 4, 2, 5, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 2, 2, ++ 4, 0, 8, ++ 4, 1, 1, ++ 4, 2, 0, ++ 4, 2, 10, ++/* TotalZeros_06.out */ ++ 2, 2, 8, ++ 4, 2, 8, ++ 4, 2, 6, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 2, 2, ++ 4, 1, 5, ++ 4, 1, 5, ++ 4, 0, 7, ++ 4, 1, 1, ++ 4, 2, 0, ++ 4, 2, 9, ++/* TotalZeros_07.out */ ++ 2, 3, 4, ++ 0, 0, 8, ++ 4, 1, 5, ++ 4, 1, 4, ++ 4, 0, 7, ++ 4, 1, 1, ++ 4, 2, 2, ++ 4, 3, 0, ++ 4, 3, 8, ++ 4, 0, 6, ++ 4, 0, 3, ++/* TotalZeros_08.out */ ++ 2, 3, 4, ++ 4, 1, 6, ++ 4, 1, 4, ++ 4, 1, 3, ++ 4, 0, 5, ++ 4, 1, 2, ++ 4, 2, 7, ++ 4, 3, 0, ++ 4, 3, 1, ++/* TotalZeros_09.out */ ++ 2, 2, 4, ++ 4, 1, 5, ++ 4, 1, 4, ++ 4, 1, 3, ++ 4, 0, 2, ++ 4, 1, 6, ++ 4, 2, 0, ++ 4, 2, 1, ++/* TotalZeros_10.out */ ++ 4, 0, 4, ++ 0, 0, 3, ++ 4, 2, 2, ++ 5, 0, 0, ++ 4, 0, 3, ++ 4, 0, 5, ++/* TotalZeros_11.out */ ++ 4, 0, 3, ++ 4, 1, 2, ++ 4, 2, 4, ++ 5, 0, 0, ++/* TotalZeros_12.out */ ++ 4, 0, 2, ++ 4, 1, 3, ++ 5, 0, 0, ++/* TotalZeros_13.out */ ++ 5, 0, 0, ++ 4, 0, 2, ++/* TotalZeros_14.out */ ++ 4, 0, 0, ++ 4, 0, 1, ++/* TotalZerosChromaDC_YUV420_00.out */ ++ 4, 0, 0, ++ 4, 1, 1, ++ 4, 2, 2, ++ 4, 2, 3, ++/* TotalZerosChromaDC_YUV420_01.out */ ++ 4, 0, 0, ++ 4, 1, 1, ++ 4, 1, 2, ++/* TotalZerosChromaDC_YUV420_02.out */ ++ 4, 0, 1, ++ 4, 0, 0, ++/* Run_00.out */ ++ 4, 0, 1, ++ 4, 0, 0, ++/* Run_01.out */ ++ 4, 0, 0, ++ 4, 1, 1, ++ 4, 1, 2, ++/* Run_02.out */ ++ 4, 1, 3, ++ 4, 1, 2, ++ 4, 1, 1, ++ 4, 1, 0, ++/* Run_03.out */ ++ 0, 0, 4, ++ 4, 1, 2, ++ 4, 1, 1, ++ 4, 1, 0, ++ 4, 0, 4, ++ 4, 0, 3, ++/* Run_04.out */ ++ 0, 1, 3, ++ 4, 1, 1, ++ 4, 1, 0, ++ 4, 1, 5, ++ 4, 1, 4, ++ 4, 1, 3, ++ 4, 1, 2, ++/* Run_05.out */ ++ 4, 2, 1, ++ 4, 2, 2, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 2, 6, ++ 4, 2, 5, ++ 4, 1, 0, ++ 4, 1, 0, ++/* Run_06.out */ ++ 2, 5, 8, ++ 4, 2, 6, ++ 4, 2, 5, ++ 4, 2, 4, ++ 4, 2, 3, ++ 4, 2, 2, ++ 4, 2, 1, ++ 4, 2, 0, ++ 4, 0, 7, ++ 4, 1, 8, ++ 4, 2, 9, ++ 4, 3, 10, ++ 4, 4, 11, ++ 4, 5, 12, ++ 2, 1, 1, ++ 4, 0, 13, ++ 4, 1, 14, ++ 3, 1, 0, ++/* TotalZerosChromaDC_YUV422_00.out */ ++ 4, 0, 0, ++ 6, 0, 0, ++ 6, 0, 1, ++ 4, 3, 5, ++ 4, 4, 6, ++ 4, 4, 7, ++/* TotalZerosChromaDC_YUV422_01.out */ ++ 6, 1, 1, ++ 4, 1, 1, ++ 4, 2, 2, ++ 4, 2, 0, ++/* TotalZerosChromaDC_YUV422_02.out */ ++ 5, 0, 0, ++ 4, 1, 2, ++ 4, 1, 3, ++ 5, 0, 2, ++/* TotalZerosChromaDC_YUV422_03.out */ ++ 6, 0, 0, ++ 4, 1, 3, ++ 4, 2, 0, ++ 4, 2, 4, ++/* TotalZerosChromaDC_YUV422_04.out */ ++ 5, 0, 0, ++ 5, 0, 1, ++/* TotalZerosChromaDC_YUV422_05.out */ ++ 5, 0, 0, ++ 4, 0, 2, ++/* TotalZerosChromaDC_YUV422_06.out */ ++ 4, 0, 0, ++ 4, 0, 1 ++}; ++ ++static const unsigned short h264_vlc_table_size = 544; ++ ++#endif diff --git a/kernel/patches/j71t/0011-j71t-devicetree.patch b/kernel/patches/j71t/0011-j71t-devicetree.patch new file mode 100644 index 00000000..b811b942 --- /dev/null +++ b/kernel/patches/j71t/0011-j71t-devicetree.patch @@ -0,0 +1,2739 @@ +From e40d2403f8aac5858f58030d2c013381c186c4c2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 11/12] arm64: dts: apple: add S8003 and J71t device trees +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +S8003 SoC and iPad 5 (J71t) board device trees for the drivers in this +series. Nodes for blocks without a driver here (GPU, SEP, AOP) stay +described but are not used. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + arch/arm64/boot/dts/apple/s800-0-3-pmgr.dtsi | 5 + + arch/arm64/boot/dts/apple/s800-0-3.dtsi | 53 +- + .../boot/dts/apple/s8003-j71t-audio.dtsi | 161 ++ + .../boot/dts/apple/s8003-j71t-display.dtsi | 47 + + .../boot/dts/apple/s8003-j71t-periph.dtsi | 180 ++ + .../boot/dts/apple/s8003-j71t-touch.dtsi | 80 + + .../boot/dts/apple/s8003-j71t-usbhost.dtsi | 56 + + arch/arm64/boot/dts/apple/s8003-j71t.dts | 142 ++ + arch/arm64/boot/dts/apple/s8003-j72t.dts | 32 + + arch/arm64/boot/dts/apple/s8003-thermal.dtsi | 107 ++ + arch/arm64/boot/dts/apple/s8003.dtsi | 1513 ++++++++++++++++- + arch/arm64/boot/dts/apple/s800x-ipad5.dtsi | 47 + + arch/arm64/boot/dts/apple/t8010-common.dtsi | 14 +- + arch/arm64/boot/dts/apple/t8010.dtsi | 72 + + 14 files changed, 2501 insertions(+), 8 deletions(-) + create mode 100644 arch/arm64/boot/dts/apple/s8003-j71t-audio.dtsi + create mode 100644 arch/arm64/boot/dts/apple/s8003-j71t-display.dtsi + create mode 100644 arch/arm64/boot/dts/apple/s8003-j71t-periph.dtsi + create mode 100644 arch/arm64/boot/dts/apple/s8003-j71t-touch.dtsi + create mode 100644 arch/arm64/boot/dts/apple/s8003-j71t-usbhost.dtsi + create mode 100644 arch/arm64/boot/dts/apple/s8003-thermal.dtsi + +diff --git a/arch/arm64/boot/dts/apple/s800-0-3-pmgr.dtsi b/arch/arm64/boot/dts/apple/s800-0-3-pmgr.dtsi +index 196b8e745a957c3735ea68bd2d035c0fb891b9fa..bdae99a4e2839a5caa7fa76ef90327be2ecfac5d 100644 +--- a/arch/arm64/boot/dts/apple/s800-0-3-pmgr.dtsi ++++ b/arch/arm64/boot/dts/apple/s800-0-3-pmgr.dtsi +@@ -82,6 +82,11 @@ + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "gpio"; ++ /* ++ * Holds the PCIe PERST# lines and the wake buttons: suspend ++ * must not power it off under live links. ++ */ ++ apple,always-on; + }; + + ps_pms: power-controller@80120 { +diff --git a/arch/arm64/boot/dts/apple/s800-0-3.dtsi b/arch/arm64/boot/dts/apple/s800-0-3.dtsi +index fc7dc7c5ba3a4ff53b104b7f1b87d5a4766d8c9f..21fe566414f5eca806ebdc0cf26339c51704b136 100644 +--- a/arch/arm64/boot/dts/apple/s800-0-3.dtsi ++++ b/arch/arm64/boot/dts/apple/s800-0-3.dtsi +@@ -76,6 +76,21 @@ + #performance-domain-cells = <0>; + }; + ++ /* ++ * Imagination PowerVR MSVDX ("VXD") hardware video decoder, ++ * ADT node vxd@7F00000 (vxd-version 7, level 4.2 H.264). ++ * No DART: the core carries its own two-level GART, which ++ * the apple-vxd driver programs. The DEVA firmware is not ++ * in the DT (the iOS kext embeds it); Linux loads it from ++ * /lib/firmware/apple/vxd-deva.bin. ++ */ ++ vxd@207f00000 { ++ compatible = "apple,s8000-vxd"; ++ reg = <0x2 0x07f00000 0x0 0x100000>; ++ interrupts = ; ++ power-domains = <&ps_vdec>; ++ }; ++ + serial0: serial@20a0c0000 { + compatible = "apple,s5l-uart"; + reg = <0x2 0x0a0c0000 0x0 0x4000>; +@@ -209,9 +224,9 @@ + power-domains = <&ps_aic>; + }; + +- dwi_bl: backlight@20e200080 { ++ dwi_bl: backlight@20e200000 { + compatible = "apple,s8000-dwi-bl", "apple,dwi-bl"; +- reg = <0x2 0x0e200080 0x0 0x8>; ++ reg = <0x2 0x0e200000 0x0 0x4000>; + power-domains = <&ps_dwi>; + status = "disabled"; + }; +@@ -290,6 +305,40 @@ + interrupt-parent = <&aic>; + interrupts = ; + }; ++ ++ /* ++ * dma-range, error-reflector, fetch-config and diag-config are ++ * the j71t ADT dart-apcie0/1 vm-offset/vm-size, error-reflector, ++ * fetch-config and diag-config, which iOS programs at every ++ * DART init. ++ */ ++ pcie0_dart0: iommu@601008000 { ++ compatible = "apple,s8000-dart", "apple,s5l8960x-dart"; ++ reg = <0x6 0x01008000 0x0 0x4000>; ++ #iommu-cells = <1>; ++ interrupt-parent = <&aic>; ++ interrupts = ; ++ power-domains = <&ps_pcie_link0>; ++ apple,dma-range = <0x0 0x80000000 0x0 0x3c000000>; ++ apple,error-reflector = /bits/ 64 <0x20fffc000>; ++ apple,fetch-config = <0x000e0303>; ++ apple,diag-config = <0x00000100>; ++ status = "disabled"; ++ }; ++ ++ pcie0_dart1: iommu@602008000 { ++ compatible = "apple,s8000-dart", "apple,s5l8960x-dart"; ++ reg = <0x6 0x02008000 0x0 0x4000>; ++ #iommu-cells = <1>; ++ interrupt-parent = <&aic>; ++ interrupts = ; ++ power-domains = <&ps_pcie_link1>; ++ apple,dma-range = <0x0 0x80000000 0x0 0x3ffff000>; ++ apple,error-reflector = /bits/ 64 <0x20fffc000>; ++ apple,fetch-config = <0x000e0303>; ++ apple,diag-config = <0x00000100>; ++ status = "disabled"; ++ }; + }; + + timer { +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t-audio.dtsi b/arch/arm64/boot/dts/apple/s8003-j71t-audio.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..1a101a4211ae9341eab446aa4e8a8437332e39b3 +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-j71t-audio.dtsi +@@ -0,0 +1,161 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++/* ++ * J71t audio: CS42L81 on SPI1 (control) + MCA0 (data). Built-in speakers, ++ * 3.5 mm headphones and the two built-in DMICs (capture over MCA0 RX). ++ * Values: Apple's J71t ADT and iOS 16.7.10's AppleCS42L81Audio/AppleAE2MCA ++ * (omarchy-ipad5 docs/audio/playback-path.md, ds-capture-and-jack.md). Items ++ * marked "inferred" are unverified on hardware. ++ */ ++ ++/ { ++ /* Live iOS arm-io clock-frequencies[0x12e - 0x100] (spi1 clock-ids) */ ++ spi1_nclk: clock-spi1 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <61538461>; ++ }; ++ ++ sound: sound { ++ compatible = "simple-audio-card"; ++ /* name kept from the speaker-only card: ALSA card id iPad5Speakers */ ++ simple-audio-card,name = "iPad5 Speakers"; ++ simple-audio-card,format = "i2s"; ++ simple-audio-card,bitclock-master = <&j71t_codec_dai>; ++ simple-audio-card,frame-master = <&j71t_codec_dai>; ++ simple-audio-card,widgets = "Speaker", "Speakers", ++ "Headphone", "Headphones", ++ "Microphone", "Internal Mic"; ++ /* ADT dmic1a-intmic "cimi", dmic1b-intmic "cims" */ ++ simple-audio-card,routing = "Speakers", "SPKOUT", ++ "Headphones", "HPOUT", ++ "DMIC1A", "Internal Mic", ++ "DMIC1B", "Internal Mic"; ++ /* ++ * The codec's "Headphone Jack" switches HPOUT/SPKOUT; these add ++ * user switches on top (ALSA "Speakers/Headphones Switch"). ++ */ ++ simple-audio-card,pin-switches = "Speakers", "Headphones"; ++ ++ simple-audio-card,cpu { ++ sound-dai = <&mca0>; ++ }; ++ ++ j71t_codec_dai: simple-audio-card,codec { ++ sound-dai = <&cs42l81>; ++ }; ++ }; ++}; ++ ++/* ++ * SmartIO coprocessor domain: PMGR device "SIO" is index 12 in the ADT, next ++ * to SIO_BUSIF (10, 0x80150) and SIO_P (11, 0x80158) -> 0x80160 (inferred). ++ */ ++&pmgr { ++ ps_sio: power-controller@80160 { ++ compatible = "apple,s8000-pmgr-pwrstate", "apple,pmgr-pwrstate"; ++ reg = <0x80160 4>; ++ #power-domain-cells = <0>; ++ #reset-cells = <0>; ++ label = "sio"; ++ power-domains = <&ps_sio_p>; ++ }; ++}; ++ ++&{/soc} { ++ /* ADT /arm-io/sio: iop,s8000, iop-version 1 (AppleA7IOPV1) */ ++ sio: dma-controller@20ae00000 { ++ compatible = "apple,s8000-sio"; ++ reg = <0x2 0x0ae00000 0x0 0x10000>, ++ <0x2 0x0a000000 0x0 0x2000>; ++ reg-names = "a7iop", "shim"; ++ interrupt-parent = <&aic>; ++ /* ADT order; the driver takes index 2 (187) as the RX mailbox */ ++ interrupts = , ++ , ++ , ++ ; ++ power-domains = <&ps_sio>; ++ resets = <&ps_sio>; ++ firmware-name = "apple/s8000-smartio.bin"; ++ dma-channels = <64>; ++ #dma-cells = <1>; ++ }; ++ ++ i2s_switch: syscon@20a003000 { ++ compatible = "apple,s8000-i2s-switch", "syscon"; ++ reg = <0x2 0x0a003000 0x0 0x1000>; ++ }; ++ ++ mca0: mca@20a0a0000 { ++ compatible = "apple,s8000-mca"; ++ reg = <0x2 0x0a0a0000 0x0 0x4000>, ++ <0x2 0x0a002000 0x0 0x4>; ++ reg-names = "mca", "mclk"; ++ interrupt-parent = <&aic>; ++ interrupts = ; ++ power-domains = <&ps_mca0>; ++ #sound-dai-cells = <0>; ++ #clock-cells = <0>; ++ clock-output-names = "mca0-mclk"; ++ /* nclk (24 MHz fixed, inferred) / 2 = 12 MHz, output enabled */ ++ apple,mclk-control = <0x58>; ++ apple,mclk-frequency = <12000000>; ++ apple,i2s-switch = <&i2s_switch>; ++ /* ++ * Byte offset, value. Codec is clock master on pin0 (inferred): ++ * pin0 data from aud0, aud0 clocks+data from pin0, pin0 drives ++ * data only. ++ */ ++ apple,i2s-switch-route = <0x00 0x00000018>, ++ <0x14 0x00080808>, ++ <0x3c 0x00000010>; ++ /* ++ * SmartIO MCA0 TX/RX, ADT dma-channels: 0x28 and 0x29, both ++ * {0x21|0x22, 0, 0x300, 0x180}. ++ */ ++ dmas = <&sio 0x28>, <&sio 0x29>; ++ dma-names = "tx", "rx"; ++ apple,sio-dma-config = <0x21 0x22 0 0x300 0x180>; ++ }; ++ ++ j71t_spi1: spi@20a084000 { ++ /* driver: spi-apple-s8000 (touch workstream) */ ++ compatible = "apple,s8000-spi"; ++ reg = <0x2 0x0a084000 0x0 0x4000>; ++ interrupt-parent = <&aic>; ++ interrupts = ; ++ clocks = <&spi1_nclk>; ++ power-domains = <&ps_spi1>; ++ /* ADT function-spi_cs0 = GPIO(0x10, 0x10001) */ ++ cs-gpios = <&pinctrl_ap 16 GPIO_ACTIVE_LOW>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ cs42l81: audio-codec@0 { ++ compatible = "cirrus,cs42l81"; ++ reg = <0>; ++ /* ADT gives no speed; iOS framing is mode 0 (inferred) */ ++ spi-max-frequency = <1000000>; ++ clocks = <&mca0>; ++ clock-names = "mclk"; ++ /* function-spkr_mute = GPIO(0x40, 0x10001): active low (inferred) */ ++ mute-gpios = <&pinctrl_ap 64 GPIO_ACTIVE_LOW>; ++ /* spkr-gain-db = -3.0 dB */ ++ apple,max-speaker-gain-db-q8 = <(-768)>; ++ apple,speaker-mode = <2>; ++ /* ++ * ADT function-ext_sw1 = GPIO(0x44, 0x101), ext_sw2 = ++ * GPIO(0x42, 0x101): headset switch, direction unknown. ++ * The driver only reads them (GPIOD_ASIS). ++ */ ++ hs-switch-gpios = <&pinctrl_ap 68 GPIO_ACTIVE_HIGH>, ++ <&pinctrl_ap 66 GPIO_ACTIVE_HIGH>; ++ /* ++ * No interrupts yet: ADT has <0x36 0x1> (AP GPIO 54), ++ * but iOS detects the jack with a timer; the driver ++ * polls (jack_poll_ms). ++ */ ++ #sound-dai-cells = <0>; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t-display.dtsi b/arch/arm64/boot/dts/apple/s8003-j71t-display.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..690ac1368f9664e73ae9d055b3d6eafc34410c7e +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-j71t-display.dtsi +@@ -0,0 +1,47 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++ ++/* Observer only: no clock, power-domain, reset or IOMMU consumers. */ ++&{/soc} { ++ disp0: display-subsystem@206200000 { ++ compatible = "adbe0,s8000"; ++ reg = <0x2 0x06200000 0 0x100000>, ++ <0x2 0x06400000 0 0x8000>, ++ <0x2 0x06440000 0 0x8000>, ++ <0x2 0x06480000 0 0x8000>, ++ <0x2 0x064c0000 0 0x8000>, ++ <0x2 0x06500000 0 0x1c>; ++ interrupts = , ++ , ++ , ++ , ++ ; ++ memory-region = <&adbe_boot_vram>; ++ }; ++}; ++ ++/* ++ * Live j71t ADT /vram: CPU 0x87c95c000, length 0x2400000 (36 MiB). ++ * Reserve it against allocation; this does not assert any DART mapping. ++ * D2 separately requires the operator to verify the current mapping span. ++ */ ++/ { ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ adbe_boot_vram: framebuffer@87c95c000 { ++ reg = <0x8 0x7c95c000 0 0x2400000>; ++ no-map; ++ }; ++ }; ++}; ++ ++/* Also keep standalone a9/display builds safe, before the pcie merge. */ ++&ps_disp0 { ++ apple,always-on; ++}; ++ ++&ps_dp { ++ apple,always-on; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t-periph.dtsi b/arch/arm64/boot/dts/apple/s8003-j71t-periph.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..e3266d36431105070f4091d9b9724bdc90a10640 +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-j71t-periph.dtsi +@@ -0,0 +1,180 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++/* Round 1 J71t peripherals. Hardware values: Apple's J71t ADT. */ ++ ++#include ++ ++/* ++ * Smart Cover hall sensors: PMIC GPIO lines 0 ("halleffect") and 10 ++ * ("halleffect1") in the ADT's /buttons node. Which level means "cover ++ * closed" is not measured yet (docs/periph/ds-als-hall.md H1); the guess is a ++ * magnet pulling the line low. After measuring with evtest, change the ++ * define (GPIO_ACTIVE_HIGH if SW_LID reads 1 with the cover open) and rebuild ++ * the dtb, or flip the flag of a built dtb with scripts/sensors/cover-polarity.sh. ++ */ ++#define J71T_COVER0_ACTIVE GPIO_ACTIVE_LOW ++#define J71T_COVER1_ACTIVE GPIO_ACTIVE_LOW ++ ++/ { ++ aliases { ++ serial3 = &serial3; ++ serial5 = &serial5; ++ bluetooth0 = &bluetooth; ++ }; ++ ++ /* ++ * Not named "Lid Switch": systemd-logind and Omarchy's Hyprland ++ * bindings pick the lid up by that name and would suspend on a cover ++ * close, which does not resume yet. The ipad-lid user daemon of the ++ * tablet layer reads these instead. ++ * ++ * Polled: gpio-apple-d2365 has no irq_chip, so there is no wakeup ++ * either (docs/periph/ds-als-hall.md 4.2). Each line is its own device so ++ * that one the PMIC keeps for itself (the driver refuses those) cannot ++ * take the other down. ++ */ ++ smart-cover { ++ compatible = "gpio-keys-polled"; ++ poll-interval = <50>; ++ label = "iPad Smart Cover"; ++ ++ lid-switch { ++ label = "Smart Cover (PMIC GPIO 0)"; ++ gpios = <&periph_pmu_gpio 0 J71T_COVER0_ACTIVE>; ++ linux,input-type = ; ++ linux,code = ; ++ debounce-interval = <50>; ++ }; ++ }; ++ ++ /* ++ * The second hall, which this does not tell apart from the first one ++ * (H4): a code of its own, so evtest shows each line separately. ++ */ ++ smart-cover-aux { ++ compatible = "gpio-keys-polled"; ++ poll-interval = <50>; ++ label = "iPad Smart Cover 2"; ++ ++ cover-switch { ++ label = "Smart Cover (PMIC GPIO 10)"; ++ gpios = <&periph_pmu_gpio 10 J71T_COVER1_ACTIVE>; ++ linux,input-type = ; ++ linux,code = ; ++ debounce-interval = <50>; ++ }; ++ }; ++}; ++ ++&{/soc} { ++ serial3: serial@20a0cc000 { ++ compatible = "apple,s8000-uart", "apple,s5l-uart"; ++ reg = <0x2 0x0a0cc000 0 0x4000>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&clkref>, <&clkref>; ++ clock-names = "uart", "clk_uart_baud0"; ++ power-domains = <&ps_uart3>; ++ pinctrl-0 = <&bluetooth_uart3_pins>; ++ pinctrl-names = "default"; ++ uart-has-rtscts; ++ status = "okay"; ++ ++ bluetooth: bluetooth { ++ compatible = "apple,j71t-bcm4355-bt"; ++ /* 115200 ROM rate inferred from A10; runtime rate is in ADT. */ ++ current-speed = <115200>; ++ max-speed = <3000000>; ++ shutdown-gpios = <&periph_pmu_gpio 13 GPIO_ACTIVE_HIGH>; ++ device-wakeup-gpios = <&pinctrl_ap 142 GPIO_ACTIVE_HIGH>; ++ }; ++ }; ++ ++ serial5: serial@20a0d4000 { ++ compatible = "apple,s5l-uart"; ++ reg = <0x2 0x0a0d4000 0 0x4000>; ++ reg-io-width = <4>; ++ interrupts = ; ++ clocks = <&clkref>, <&clkref>; ++ clock-names = "uart", "clk_uart_baud0"; ++ power-domains = <&ps_uart5>; ++ pinctrl-0 = <&battery_swi_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ onewire { ++ compatible = "ti,hdq-uart"; ++ ++ battery: fuel-gauge { ++ /* ADT bq27540; uses the experimental J172 map, not an ID claim. */ ++ compatible = "apple,j71t-bq27540"; ++ }; ++ }; ++ }; ++}; ++ ++&pinctrl_ap { ++ bluetooth_uart3_pins: bluetooth-uart3-pins { ++ pinmux = , , ++ , ; ++ }; ++ ++ battery_swi_pins: battery-swi-pins { ++ pinmux = ; ++ }; ++}; ++ ++&{/soc/i2c@20a110000/pmic@3c} { ++ /* Internal register offsets are not CPU MMIO resources. */ ++ #size-cells = <0>; ++ ++ rtc@500 { ++ reg = <0x500>; ++ }; ++ ++ nvmem@5000 { ++ reg = <0x5000>; ++ size = <0x28>; ++ read-only; ++ }; ++ ++ periph_pmu_gpio: gpio { ++ compatible = "apple,d2365-pmic-gpio"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ /* Line 13 is Bluetooth enable, 0 and 10 the Smart Cover halls. */ ++ gpio-reserved-ranges = <1 9>, <11 2>, <14 10>; ++ }; ++ ++ charger@180 { ++ /* Automatic policy uses source authorization and private CBAT. */ ++ compatible = "apple,j71t-d2365-charger"; ++ reg = <0x180>, <0xa00>; ++ reg-names = "status", "control"; ++ power-supplies = <&periph_tristar>; ++ }; ++}; ++ ++&i2c2 { ++ status = "okay"; ++ ++ light-sensor@39 { ++ compatible = "apple,ct819", "amstaos,tsl2581"; ++ reg = <0x39>; ++ interrupts-extended = <&pinctrl_ap 71 IRQ_TYPE_LEVEL_LOW>; ++ firmware-name = "apple/j71t-LSCI.bin"; ++ }; ++}; ++ ++&i2c0 { ++ periph_tristar: power-controller@1a { ++ apple,usb-role-switch = <&usb_role>; ++ compatible = "apple,j71t-cbtl1610-power"; ++ usb-controller = <&usbdev>; ++ apple,pmic = <&{/soc/i2c@20a110000/pmic@3c}>; ++ reg = <0x1a>; ++ }; ++}; ++ ++&usbdev { ++ apple,host-current-budget; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t-touch.dtsi b/arch/arm64/boot/dts/apple/s8003-j71t-touch.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..efdc12e5ec53347b23a8d8a4bfa35cdd55e72ae4 +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-j71t-touch.dtsi +@@ -0,0 +1,80 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++/* J71t N1 touch wiring; portrait framebuffer, home button at the bottom. */ ++/ { ++ /* Live iOS arm-io clock-frequencies[0x130 - 0x100]. */ ++ spi3_nclk: clock-spi3 { ++ compatible = "fixed-clock"; ++ #clock-cells = <0>; ++ clock-frequency = <61538461>; ++ }; ++}; ++ ++&pmgr { ++ touch_clk: clock-controller@78000 { ++ compatible = "apple,s8000-touch-clock"; ++ reg = <0x78000 4>; ++ #clock-cells = <0>; ++ clocks = <&clkref>; ++ }; ++}; ++ ++&i2c0 { ++ pmic@3c { ++ touch_analog: regulator@31c { ++ compatible = "apple,d2365-touch-analog"; ++ reg = <0x31c 1>; ++ regulator-name = "touch-analog"; ++ }; ++ }; ++}; ++ ++&pinctrl_ap { ++ compatible = "apple,s8000-pinctrl", "apple,pinctrl"; ++ ++ touch_sync_on: touch-sync-on-pins { ++ pinmux = ; ++ }; ++ ++ touch_sync_off: touch-sync-off-pins { ++ pinmux = ; ++ }; ++}; ++ ++/* Isolated board hunk: does not enable this N1 personality on J71s/J72t. */ ++&{/soc} { ++ spi3: spi@20a08c000 { ++ compatible = "apple,s8000-spi"; ++ reg = <0x2 0x0a08c000 0 0x4000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ interrupts = ; ++ clocks = <&spi3_nclk>; ++ power-domains = <&ps_spi3>; ++ cs-gpios = <&pinctrl_ap 102 GPIO_ACTIVE_LOW>; ++ status = "okay"; ++ ++ touchscreen@0 { ++ compatible = "apple,j71t-n1"; ++ reg = <0>; ++ /* Hardware-confirmed corner taps: X agrees, raw Y grows upward. */ ++ touchscreen-inverted-y; ++ /* N1 selects 83 ns; controller rounds up to divider 6. */ ++ spi-max-frequency = <12048192>; ++ /* This pinned SPI core applies GPIO CS setup twice. */ ++ spi-cs-setup-delay-ns = <5000>; ++ spi-cs-hold-delay-ns = <10000>; ++ interrupt-parent = <&pinctrl_ap>; ++ interrupts = <65 IRQ_TYPE_EDGE_FALLING>; ++ attn-gpios = <&pinctrl_ap 65 GPIO_ACTIVE_LOW>; ++ reset-gpios = <&pinctrl_ap 75 GPIO_ACTIVE_LOW>; ++ ldo-gpios = <&pinctrl_ap 73 GPIO_ACTIVE_HIGH>; ++ vdd-supply = <&touch_analog>; ++ clocks = <&touch_clk>; ++ pinctrl-names = "sync-on", "sync-off"; ++ pinctrl-0 = <&touch_sync_on>; ++ pinctrl-1 = <&touch_sync_off>; ++ firmware-name = "apple/j71t-n1.bin"; ++ apple,calibration-firmware = "apple/j71t-n1-calibration.bin"; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t-usbhost.dtsi b/arch/arm64/boot/dts/apple/s8003-j71t-usbhost.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..cb3664fce8706b8d2fc706d63a1d6b724fca7016 +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-j71t-usbhost.dtsi +@@ -0,0 +1,56 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++/* ++ * J71t Lightning port USB host mode: the EHCI + OHCI pair behind a role ++ * switch that shares the AUSB PHY with the device-only DWC2. Hardware ++ * values: Apple's J71t ADT (otgphyctrl, usb-complex, dock). ++ * ++ * Included right after /dts-v1/ (before the SoC dtsi, like the AOP ++ * observer): full paths only, and it includes the binding headers it uses. ++ */ ++ ++#include ++#include ++#include ++ ++/ { ++ soc { ++ /* usb-complex */ ++ bus@20c000000 { ++ usb_role: usb-role-switch@20e0d8000 { ++ compatible = "apple,s8000-usb-role-switch"; ++ /* The DMA remap words belong to the usb-complex. */ ++ reg = <0x2 0x0e0d8000 0x0 0x1000>, ++ <0x2 0x0c000000 0x0 0x30>; ++ reg-names = "usb-ctl", "glue"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ /* The usb-complex dma-ranges apply to the host pair. */ ++ dma-ranges; ++ nonposted-mmio; ++ phys = <&ausbphy0 PHY_TYPE_USB2>; ++ phy-names = "usb2-phy"; ++ apple,usb-device = <&usbdev>; ++ /* dock function-acc_pwr / acc_sw_en: D2365 LDO5 */ ++ apple,pmic = <&{/soc/i2c@20a110000/pmic@3c}>; ++ /* CBTL1610 VBUS sink bit */ ++ apple,tristar = <&i2c0 0x1a>; ++ ++ /* Created by the switch in host mode only. */ ++ usb@20c200000 { ++ compatible = "apple,s8000-ehci", "generic-ehci"; ++ reg = <0x2 0x0c200000 0x0 0x10000>; ++ interrupts = ; ++ power-domains = <&ps_usb2host0>; ++ }; ++ ++ usb@20c300000 { ++ compatible = "apple,s8000-ohci", "generic-ohci"; ++ reg = <0x2 0x0c300000 0x0 0x10000>; ++ interrupts = ; ++ power-domains = <&ps_usb2host0_ohci>; ++ }; ++ }; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003-j71t.dts b/arch/arm64/boot/dts/apple/s8003-j71t.dts +index 0d906ae80b075622709c7a231aa28cc87b36e9e8..3c48bec20902ed38a6afc592219a1f2da84d6b1e 100644 +--- a/arch/arm64/boot/dts/apple/s8003-j71t.dts ++++ b/arch/arm64/boot/dts/apple/s8003-j71t.dts +@@ -4,12 +4,154 @@ + * Copyright (c) 2022, Konrad Dybcio + */ + ++#define APPLE_J71T_BOOT_DISPLAY ++ + /dts-v1/; + ++/memreserve/ 0x210e00000 0x9b000; ++/memreserve/ 0x858000000 0x400000; ++ + #include "s8003.dtsi" + #include "s800x-ipad5.dtsi" ++#include "s8003-j71t-periph.dtsi" + ++#include "s8003-j71t-audio.dtsi" + / { + compatible = "apple,j71t", "apple,s8003", "apple,arm-platform"; + model = "Apple iPad 5 (Wi-Fi) (TSMC)"; ++ ++ aliases { ++ nvme-hmb = &nvme_hmb; /* Used by loader */ ++ serial2 = &wlan_uart; ++ wifi0 = &wifi0; /* m1n1: local-mac-address, brcm,cal-blob */ ++ }; ++ ++ reserved-memory { ++ nvme_hmb: nvme-hmb@0 { ++ reg = <0 0 0 0>; /* To be filled by loader */ ++ no-map; ++ }; ++ ++ /* ++ * sepOS root shared-memory page, handed over by pongoOS with ++ * op 6 on endpoint 0xfe (2026-10-26 boot: data=87adb0). sepOS ++ * may use it for the rest of its life. The address belongs to ++ * that pongoOS session: after a new sepOS boot, take it from ++ * the "root shmem ... data=" line of the pongoOS log. ++ */ ++ sep-shmem@87adb0000 { ++ reg = <0x8 0x7adb0000 0x0 0x4000>; ++ no-map; ++ }; ++ }; ++}; ++ ++/* ++ * Keep the SEP PMGR word pongoOS hands over (0x4f0000ff) bit for bit. The ++ * cold-boot AUTO_ENABLE update (0x5f0000ff) was in place when sepOS stopped ++ * consuming the AP->SEP mailbox (2026-10-26 15:42). ++ */ ++&ps_sep { ++ apple,no-auto-pm; ++}; ++ ++&pcie0_dart0 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ apple,lane-cfg = <1>; ++ apple,nvme-hmb = <&nvme_hmb>; ++ /* live ADT nvme-mmu0 invalidate-fifo-workaround (added by iBoot) */ ++ apple,nvmmu-invalidate-fifo = <1>; ++ status = "okay"; + }; ++ ++/* ++ * Wi-Fi: BCM4355 C0 (USI module "FearA"), 14e4:43dc, on port 1. From the ++ * j71t live ADT: ++ * - REG_ON: uart2/wlan function-reg_on = D2365 PMU GPIO 14 ++ * - DEVICE_WAKE: pci-bridge1 function-device_wake = AP GPIO 67 ++ * - PERST#: pci-bridge1 function-perst = AP GPIO 161 (s8003.dtsi) ++ * - board type: uart2/wlan module-instance "feara" ++ * The PCIe driver powers the chip and trains the link after probe (the ++ * port has pwren-gpios), then rescans; NVMe on port 0 doesn't wait for it. ++ */ ++&{/soc/i2c@20a110000/pmic@3c} { ++ wlan_pmu_gpio: gpio { ++ compatible = "apple,d2365-pmic-gpio"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ /* 13: Bluetooth power enable, 14: WLAN REG_ON */ ++ gpio-reserved-ranges = <0 13>, <15 9>; ++ }; ++}; ++ ++&{/soc} { ++ /* ++ * uart2 hosts the ADT's "wlan" (wlan-pcie-uart,bcm4355) node. Keep it ++ * powered and in its UART pin function while Wi-Fi runs, as Pauli1Go's ++ * A10 wlan-uart provider does (ADT function-tx = GPIO 192, ++ * function-rts = GPIO 194). The serial driver does exactly that. ++ */ ++ wlan_uart: serial@20a0c8000 { ++ compatible = "apple,s5l-uart"; ++ reg = <0x2 0x0a0c8000 0x0 0x4000>; ++ reg-io-width = <4>; ++ interrupt-parent = <&aic>; ++ interrupts = ; ++ clocks = <&clkref>, <&clkref>; ++ clock-names = "uart", "clk_uart_baud0"; ++ power-domains = <&ps_uart2>; ++ pinctrl-0 = <&wlan_uart2_pins>; ++ pinctrl-names = "default"; ++ }; ++}; ++ ++&pinctrl_ap { ++ wlan_uart2_pins: wlan-uart2-pins { ++ pinmux = , ; ++ }; ++ ++ /* ++ * Port 1 CLKREQ# (pci-bridge1 function-clkreq, AP GPIO 165) in its ++ * peripheral function: iBoot doesn't set it up for Wi-Fi on a tethered ++ * boot. Function 1 by analogy with A10 J172 (pin 19) and the UART pins. ++ */ ++ wifi_clkreq_pins: wifi-clkreq-pins { ++ pinmux = ; ++ }; ++}; ++ ++&pcie0 { ++ pinctrl-0 = <&wifi_clkreq_pins>; ++ pinctrl-names = "default"; ++}; ++ ++&pcie0_dart1 { ++ status = "okay"; ++}; ++ ++&pcie0_port1 { ++ /* Linux numbers it after port 0's bus 1 */ ++ bus-range = <2 2>; ++ pwren-gpios = <&wlan_pmu_gpio 14 GPIO_ACTIVE_HIGH>; ++ device-wake-gpios = <&pinctrl_ap 67 GPIO_ACTIVE_HIGH>; ++ ++ wifi0: wifi@0,0 { ++ compatible = "pci14e4,43dc"; ++ /* port 1's secondary bus is 2 (iommu-map 0x200 -> pcie0_dart1) */ ++ reg = <0x20000 0x0 0x0 0x0 0x0>; ++ brcm,board-type = "apple,feara"; ++ /* the staged firmware's antenna SKU (A10 J172 uses the same) */ ++ apple,antenna-sku = "XX"; ++ /* filled by m1n1 from the ADT (/chosen mac-address-wifi0) */ ++ local-mac-address = [00 00 00 00 00 00]; ++ /* cfg80211's country goes to the firmware as /0 */ ++ brcm,ccode-map-trivial; ++ }; ++}; ++ ++#include "s8003-j71t-touch.dtsi" ++ ++#include "s8003-j71t-usbhost.dtsi" +diff --git a/arch/arm64/boot/dts/apple/s8003-j72t.dts b/arch/arm64/boot/dts/apple/s8003-j72t.dts +index 0cd7d88e9dfbb220d389b9d7a74a7d942b7994b6..957091e590c75857ceaaf569f0d624c3a67abc87 100644 +--- a/arch/arm64/boot/dts/apple/s8003-j72t.dts ++++ b/arch/arm64/boot/dts/apple/s8003-j72t.dts +@@ -12,4 +12,36 @@ + / { + compatible = "apple,j72t", "apple,s8003", "apple,arm-platform"; + model = "Apple iPad 5 (Cellular) (TSMC)"; ++ ++ aliases { ++ nvme-hmb = &nvme_hmb; /* Used by loader */ ++ }; ++ ++ reserved-memory { ++ nvme_hmb: nvme-hmb@0 { ++ reg = <0 0 0 0>; /* To be filled by loader */ ++ no-map; ++ }; ++ }; ++}; ++ ++&pcie0_dart0 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ apple,lane-cfg = <1>; ++ apple,nvme-hmb = <&nvme_hmb>; ++ /* live ADT nvme-mmu0 invalidate-fifo-workaround (added by iBoot) */ ++ apple,nvmmu-invalidate-fifo = <1>; ++ status = "okay"; ++}; ++ ++/* ++ * The BCM4355 needs a power sequence (PMU-controlled VDD, wlan-uart ++ * provider) before its link can train; until that exists, leave port 1 ++ * and its DART off. ++ */ ++&pcie0_port1 { ++ status = "disabled"; + }; +diff --git a/arch/arm64/boot/dts/apple/s8003-thermal.dtsi b/arch/arm64/boot/dts/apple/s8003-thermal.dtsi +new file mode 100644 +index 0000000000000000000000000000000000000000..20fd657db41ef6672c90319285d7462edd783fb2 +--- /dev/null ++++ b/arch/arm64/boot/dts/apple/s8003-thermal.dtsi +@@ -0,0 +1,107 @@ ++// SPDX-License-Identifier: GPL-2.0+ OR MIT ++/* j71t live ADT, AppleT700XTempSensor. Thresholds are conservative lab ++ * margins below Apple's 100 C alarm, not thermalmonitord skin policy. ++ */ ++#include ++ ++&cpu0 { #cooling-cells = <2>; }; ++&cpu1 { #cooling-cells = <2>; }; ++ ++&{/soc} { ++ a9_thermal: thermal-sensor@20e0c0000 { ++ compatible = "apple,s8003-thermal"; ++ reg = <0x2 0x0e0c0000 0x0 0x1000>, ++ <0x2 0x02230000 0x0 0x1000>; ++ reg-names = "soc", "tcc"; ++ #thermal-sensor-cells = <1>; ++ }; ++}; ++ ++/ { ++ thermal-zones { ++ apple-a9-soc0 { ++ polling-delay-passive = <100>; ++ polling-delay = <100>; ++ thermal-sensors = <&a9_thermal 0>; ++ trips { ++ a9_soc0_passive: passive { ++ temperature = <80000>; ++ hysteresis = <5000>; ++ type = "passive"; ++ }; ++ hot { ++ temperature = <90000>; ++ hysteresis = <5000>; ++ type = "hot"; ++ }; ++ critical { ++ temperature = <100000>; ++ hysteresis = <0>; ++ type = "critical"; ++ }; ++ }; ++ cooling-maps { ++ cpu { ++ trip = <&a9_soc0_passive>; ++ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; ++ }; ++ }; ++ }; ++ apple-a9-soc1 { ++ polling-delay-passive = <100>; ++ polling-delay = <100>; ++ thermal-sensors = <&a9_thermal 1>; ++ trips { ++ a9_soc1_passive: passive { ++ temperature = <80000>; ++ hysteresis = <5000>; ++ type = "passive"; ++ }; ++ hot { ++ temperature = <90000>; ++ hysteresis = <5000>; ++ type = "hot"; ++ }; ++ critical { ++ temperature = <100000>; ++ hysteresis = <0>; ++ type = "critical"; ++ }; ++ }; ++ cooling-maps { ++ cpu { ++ trip = <&a9_soc1_passive>; ++ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; ++ }; ++ }; ++ }; ++ apple-a9-tcc2 { ++ polling-delay-passive = <100>; ++ polling-delay = <100>; ++ thermal-sensors = <&a9_thermal 5>; ++ trips { ++ a9_tcc2_passive: passive { ++ temperature = <80000>; ++ hysteresis = <5000>; ++ type = "passive"; ++ }; ++ hot { ++ temperature = <90000>; ++ hysteresis = <5000>; ++ type = "hot"; ++ }; ++ critical { ++ temperature = <100000>; ++ hysteresis = <0>; ++ type = "critical"; ++ }; ++ }; ++ cooling-maps { ++ cpu { ++ trip = <&a9_tcc2_passive>; ++ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; ++ }; ++ }; ++ }; ++ }; ++}; +diff --git a/arch/arm64/boot/dts/apple/s8003.dtsi b/arch/arm64/boot/dts/apple/s8003.dtsi +index 79df5c7832600beb994952222fdf15bca93c2cb8..696f5a028c228f8aea2b9dabedcc1c011c330219 100644 +--- a/arch/arm64/boot/dts/apple/s8003.dtsi ++++ b/arch/arm64/boot/dts/apple/s8003.dtsi +@@ -9,6 +9,10 @@ + + #include "s800-0-3.dtsi" + ++#ifdef APPLE_J71T_BOOT_DISPLAY ++#include "s8003-j71t-display.dtsi" ++#endif ++ + / { + twister_opp: opp-table { + compatible = "operating-points-v2"; +@@ -48,15 +52,1496 @@ + opp-level = <7>; + clock-latency-ns = <38000>; + }; +-#if 0 +- /* Not available until CPU deep sleep is implemented */ ++/* Opt-in stock boost; hardware load restriction is preserved. */ + opp08 { +- opp-hz = /bits/ 64 <1844000000>; +- opp-level = <8>; ++ opp-hz = /bits/ 64 <1848000000>; ++ opp-level = <15>; + clock-latency-ns = <38000>; + turbo-mode; + }; +-#endif ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp09 { ++ opp-hz = /bits/ 64 <1896000000>; ++ opp-level = <9>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp10 { ++ opp-hz = /bits/ 64 <1944000000>; ++ opp-level = <10>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp11 { ++ opp-hz = /bits/ 64 <1992000000>; ++ opp-level = <11>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp12 { ++ opp-hz = /bits/ 64 <2040000000>; ++ opp-level = <12>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp13 { ++ opp-hz = /bits/ 64 <2088000000>; ++ opp-level = <13>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Guarded experimental slot; inaccessible until explicitly configured. */ ++ opp14 { ++ opp-hz = /bits/ 64 <2160000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ ++ /* Round-6 shared-slot OPPs: begin */ ++ opp-r6-2208000 { ++ opp-hz = /bits/ 64 <2208000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2256000 { ++ opp-hz = /bits/ 64 <2256000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2304000 { ++ opp-hz = /bits/ 64 <2304000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2352000 { ++ opp-hz = /bits/ 64 <2352000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2400000 { ++ opp-hz = /bits/ 64 <2400000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2448000 { ++ opp-hz = /bits/ 64 <2448000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2496000 { ++ opp-hz = /bits/ 64 <2496000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2544000 { ++ opp-hz = /bits/ 64 <2544000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2592000 { ++ opp-hz = /bits/ 64 <2592000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2640000 { ++ opp-hz = /bits/ 64 <2640000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2688000 { ++ opp-hz = /bits/ 64 <2688000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2736000 { ++ opp-hz = /bits/ 64 <2736000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2784000 { ++ opp-hz = /bits/ 64 <2784000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2832000 { ++ opp-hz = /bits/ 64 <2832000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2880000 { ++ opp-hz = /bits/ 64 <2880000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2928000 { ++ opp-hz = /bits/ 64 <2928000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-2976000 { ++ opp-hz = /bits/ 64 <2976000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3024000 { ++ opp-hz = /bits/ 64 <3024000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3072000 { ++ opp-hz = /bits/ 64 <3072000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3120000 { ++ opp-hz = /bits/ 64 <3120000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3168000 { ++ opp-hz = /bits/ 64 <3168000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3216000 { ++ opp-hz = /bits/ 64 <3216000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3264000 { ++ opp-hz = /bits/ 64 <3264000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3312000 { ++ opp-hz = /bits/ 64 <3312000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3360000 { ++ opp-hz = /bits/ 64 <3360000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3408000 { ++ opp-hz = /bits/ 64 <3408000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3456000 { ++ opp-hz = /bits/ 64 <3456000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3504000 { ++ opp-hz = /bits/ 64 <3504000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3552000 { ++ opp-hz = /bits/ 64 <3552000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3600000 { ++ opp-hz = /bits/ 64 <3600000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3648000 { ++ opp-hz = /bits/ 64 <3648000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3696000 { ++ opp-hz = /bits/ 64 <3696000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3744000 { ++ opp-hz = /bits/ 64 <3744000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3792000 { ++ opp-hz = /bits/ 64 <3792000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3840000 { ++ opp-hz = /bits/ 64 <3840000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3888000 { ++ opp-hz = /bits/ 64 <3888000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3936000 { ++ opp-hz = /bits/ 64 <3936000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-3984000 { ++ opp-hz = /bits/ 64 <3984000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4032000 { ++ opp-hz = /bits/ 64 <4032000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4080000 { ++ opp-hz = /bits/ 64 <4080000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4128000 { ++ opp-hz = /bits/ 64 <4128000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4176000 { ++ opp-hz = /bits/ 64 <4176000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4224000 { ++ opp-hz = /bits/ 64 <4224000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4272000 { ++ opp-hz = /bits/ 64 <4272000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4320000 { ++ opp-hz = /bits/ 64 <4320000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4368000 { ++ opp-hz = /bits/ 64 <4368000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4416000 { ++ opp-hz = /bits/ 64 <4416000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4464000 { ++ opp-hz = /bits/ 64 <4464000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4512000 { ++ opp-hz = /bits/ 64 <4512000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4560000 { ++ opp-hz = /bits/ 64 <4560000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4608000 { ++ opp-hz = /bits/ 64 <4608000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4656000 { ++ opp-hz = /bits/ 64 <4656000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4704000 { ++ opp-hz = /bits/ 64 <4704000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4752000 { ++ opp-hz = /bits/ 64 <4752000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4800000 { ++ opp-hz = /bits/ 64 <4800000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4848000 { ++ opp-hz = /bits/ 64 <4848000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4896000 { ++ opp-hz = /bits/ 64 <4896000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4944000 { ++ opp-hz = /bits/ 64 <4944000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-4992000 { ++ opp-hz = /bits/ 64 <4992000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5040000 { ++ opp-hz = /bits/ 64 <5040000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5088000 { ++ opp-hz = /bits/ 64 <5088000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5136000 { ++ opp-hz = /bits/ 64 <5136000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5184000 { ++ opp-hz = /bits/ 64 <5184000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5232000 { ++ opp-hz = /bits/ 64 <5232000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5280000 { ++ opp-hz = /bits/ 64 <5280000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5328000 { ++ opp-hz = /bits/ 64 <5328000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5376000 { ++ opp-hz = /bits/ 64 <5376000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5424000 { ++ opp-hz = /bits/ 64 <5424000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5472000 { ++ opp-hz = /bits/ 64 <5472000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5520000 { ++ opp-hz = /bits/ 64 <5520000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5568000 { ++ opp-hz = /bits/ 64 <5568000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5616000 { ++ opp-hz = /bits/ 64 <5616000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5664000 { ++ opp-hz = /bits/ 64 <5664000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5712000 { ++ opp-hz = /bits/ 64 <5712000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5760000 { ++ opp-hz = /bits/ 64 <5760000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5808000 { ++ opp-hz = /bits/ 64 <5808000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5856000 { ++ opp-hz = /bits/ 64 <5856000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5904000 { ++ opp-hz = /bits/ 64 <5904000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-5952000 { ++ opp-hz = /bits/ 64 <5952000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6000000 { ++ opp-hz = /bits/ 64 <6000000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6048000 { ++ opp-hz = /bits/ 64 <6048000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6096000 { ++ opp-hz = /bits/ 64 <6096000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6144000 { ++ opp-hz = /bits/ 64 <6144000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6192000 { ++ opp-hz = /bits/ 64 <6192000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6240000 { ++ opp-hz = /bits/ 64 <6240000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6288000 { ++ opp-hz = /bits/ 64 <6288000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6336000 { ++ opp-hz = /bits/ 64 <6336000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6384000 { ++ opp-hz = /bits/ 64 <6384000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6432000 { ++ opp-hz = /bits/ 64 <6432000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6480000 { ++ opp-hz = /bits/ 64 <6480000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6528000 { ++ opp-hz = /bits/ 64 <6528000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6576000 { ++ opp-hz = /bits/ 64 <6576000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6624000 { ++ opp-hz = /bits/ 64 <6624000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6672000 { ++ opp-hz = /bits/ 64 <6672000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6720000 { ++ opp-hz = /bits/ 64 <6720000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6768000 { ++ opp-hz = /bits/ 64 <6768000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6816000 { ++ opp-hz = /bits/ 64 <6816000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6864000 { ++ opp-hz = /bits/ 64 <6864000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6912000 { ++ opp-hz = /bits/ 64 <6912000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-6960000 { ++ opp-hz = /bits/ 64 <6960000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7008000 { ++ opp-hz = /bits/ 64 <7008000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7056000 { ++ opp-hz = /bits/ 64 <7056000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7104000 { ++ opp-hz = /bits/ 64 <7104000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7152000 { ++ opp-hz = /bits/ 64 <7152000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7200000 { ++ opp-hz = /bits/ 64 <7200000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7248000 { ++ opp-hz = /bits/ 64 <7248000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7296000 { ++ opp-hz = /bits/ 64 <7296000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7344000 { ++ opp-hz = /bits/ 64 <7344000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7392000 { ++ opp-hz = /bits/ 64 <7392000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7440000 { ++ opp-hz = /bits/ 64 <7440000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7488000 { ++ opp-hz = /bits/ 64 <7488000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7536000 { ++ opp-hz = /bits/ 64 <7536000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7584000 { ++ opp-hz = /bits/ 64 <7584000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7632000 { ++ opp-hz = /bits/ 64 <7632000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7680000 { ++ opp-hz = /bits/ 64 <7680000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7728000 { ++ opp-hz = /bits/ 64 <7728000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7776000 { ++ opp-hz = /bits/ 64 <7776000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7824000 { ++ opp-hz = /bits/ 64 <7824000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7872000 { ++ opp-hz = /bits/ 64 <7872000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7920000 { ++ opp-hz = /bits/ 64 <7920000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-7968000 { ++ opp-hz = /bits/ 64 <7968000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8016000 { ++ opp-hz = /bits/ 64 <8016000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8064000 { ++ opp-hz = /bits/ 64 <8064000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8112000 { ++ opp-hz = /bits/ 64 <8112000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8160000 { ++ opp-hz = /bits/ 64 <8160000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8208000 { ++ opp-hz = /bits/ 64 <8208000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8256000 { ++ opp-hz = /bits/ 64 <8256000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8304000 { ++ opp-hz = /bits/ 64 <8304000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8352000 { ++ opp-hz = /bits/ 64 <8352000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8400000 { ++ opp-hz = /bits/ 64 <8400000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8448000 { ++ opp-hz = /bits/ 64 <8448000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8496000 { ++ opp-hz = /bits/ 64 <8496000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8544000 { ++ opp-hz = /bits/ 64 <8544000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8592000 { ++ opp-hz = /bits/ 64 <8592000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8640000 { ++ opp-hz = /bits/ 64 <8640000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8688000 { ++ opp-hz = /bits/ 64 <8688000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8736000 { ++ opp-hz = /bits/ 64 <8736000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8784000 { ++ opp-hz = /bits/ 64 <8784000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8832000 { ++ opp-hz = /bits/ 64 <8832000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8880000 { ++ opp-hz = /bits/ 64 <8880000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8928000 { ++ opp-hz = /bits/ 64 <8928000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-8976000 { ++ opp-hz = /bits/ 64 <8976000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9024000 { ++ opp-hz = /bits/ 64 <9024000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9072000 { ++ opp-hz = /bits/ 64 <9072000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9120000 { ++ opp-hz = /bits/ 64 <9120000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9168000 { ++ opp-hz = /bits/ 64 <9168000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9216000 { ++ opp-hz = /bits/ 64 <9216000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9264000 { ++ opp-hz = /bits/ 64 <9264000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9312000 { ++ opp-hz = /bits/ 64 <9312000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9360000 { ++ opp-hz = /bits/ 64 <9360000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9408000 { ++ opp-hz = /bits/ 64 <9408000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9456000 { ++ opp-hz = /bits/ 64 <9456000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9504000 { ++ opp-hz = /bits/ 64 <9504000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9552000 { ++ opp-hz = /bits/ 64 <9552000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9600000 { ++ opp-hz = /bits/ 64 <9600000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9648000 { ++ opp-hz = /bits/ 64 <9648000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9696000 { ++ opp-hz = /bits/ 64 <9696000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9744000 { ++ opp-hz = /bits/ 64 <9744000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9792000 { ++ opp-hz = /bits/ 64 <9792000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9840000 { ++ opp-hz = /bits/ 64 <9840000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9888000 { ++ opp-hz = /bits/ 64 <9888000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9936000 { ++ opp-hz = /bits/ 64 <9936000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-9984000 { ++ opp-hz = /bits/ 64 <9984000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10032000 { ++ opp-hz = /bits/ 64 <10032000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10080000 { ++ opp-hz = /bits/ 64 <10080000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10128000 { ++ opp-hz = /bits/ 64 <10128000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10176000 { ++ opp-hz = /bits/ 64 <10176000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10224000 { ++ opp-hz = /bits/ 64 <10224000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10272000 { ++ opp-hz = /bits/ 64 <10272000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10320000 { ++ opp-hz = /bits/ 64 <10320000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10368000 { ++ opp-hz = /bits/ 64 <10368000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10416000 { ++ opp-hz = /bits/ 64 <10416000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10464000 { ++ opp-hz = /bits/ 64 <10464000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10512000 { ++ opp-hz = /bits/ 64 <10512000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10560000 { ++ opp-hz = /bits/ 64 <10560000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10608000 { ++ opp-hz = /bits/ 64 <10608000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10656000 { ++ opp-hz = /bits/ 64 <10656000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10704000 { ++ opp-hz = /bits/ 64 <10704000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10752000 { ++ opp-hz = /bits/ 64 <10752000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10800000 { ++ opp-hz = /bits/ 64 <10800000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10848000 { ++ opp-hz = /bits/ 64 <10848000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10896000 { ++ opp-hz = /bits/ 64 <10896000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10944000 { ++ opp-hz = /bits/ 64 <10944000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-10992000 { ++ opp-hz = /bits/ 64 <10992000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11040000 { ++ opp-hz = /bits/ 64 <11040000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11088000 { ++ opp-hz = /bits/ 64 <11088000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11136000 { ++ opp-hz = /bits/ 64 <11136000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11184000 { ++ opp-hz = /bits/ 64 <11184000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11232000 { ++ opp-hz = /bits/ 64 <11232000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11280000 { ++ opp-hz = /bits/ 64 <11280000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11328000 { ++ opp-hz = /bits/ 64 <11328000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11376000 { ++ opp-hz = /bits/ 64 <11376000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11424000 { ++ opp-hz = /bits/ 64 <11424000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11472000 { ++ opp-hz = /bits/ 64 <11472000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11520000 { ++ opp-hz = /bits/ 64 <11520000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11568000 { ++ opp-hz = /bits/ 64 <11568000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11616000 { ++ opp-hz = /bits/ 64 <11616000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11664000 { ++ opp-hz = /bits/ 64 <11664000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11712000 { ++ opp-hz = /bits/ 64 <11712000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11760000 { ++ opp-hz = /bits/ 64 <11760000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11808000 { ++ opp-hz = /bits/ 64 <11808000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11856000 { ++ opp-hz = /bits/ 64 <11856000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11904000 { ++ opp-hz = /bits/ 64 <11904000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-11952000 { ++ opp-hz = /bits/ 64 <11952000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12000000 { ++ opp-hz = /bits/ 64 <12000000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12048000 { ++ opp-hz = /bits/ 64 <12048000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12096000 { ++ opp-hz = /bits/ 64 <12096000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12144000 { ++ opp-hz = /bits/ 64 <12144000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12192000 { ++ opp-hz = /bits/ 64 <12192000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12240000 { ++ opp-hz = /bits/ 64 <12240000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ opp-r6-12264000 { ++ opp-hz = /bits/ 64 <12264000000>; ++ opp-level = <14>; ++ clock-latency-ns = <400000>; ++ turbo-mode; ++ }; ++ /* Round-6 shared-slot OPPs: end */ ++ ++ }; ++}; ++ ++&{/soc} { ++ /* ++ * The TSMC part has the same PCIe PHY ("phy-ip" block) as T8010; the ++ * Samsung S8000 apcie has no phy-ip region and is not described yet. ++ */ ++ pcie0: pcie@610000000 { ++ compatible = "apple,s8003-pcie"; ++ device_type = "pci"; ++ ++ reg = <0x6 0x10000000 0x0 0x1000000>, ++ <0x6 0x00000000 0x0 0x8000>, ++ <0x6 0x00008000 0x0 0x4000>, ++ <0x6 0x0a000000 0x0 0x40000>, ++ <0x6 0x01000000 0x0 0x4000>, ++ <0x6 0x02000000 0x0 0x4000>, ++ <0x6 0x03000000 0x0 0x4000>, ++ <0x6 0x04000000 0x0 0x4000>, ++ <0x6 0x01004000 0x0 0x4000>; ++ reg-names = "config", "rc", "phy", "phy-ip", ++ "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ interrupt-parent = <&aic>; ++ interrupts = , ++ , ++ , ++ , ++ ; ++ interrupt-names = "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ msi-controller; ++ msi-parent = <&pcie0>; ++ msi-ranges = <&aic AIC_IRQ 256 IRQ_TYPE_EDGE_RISING 32>; ++ apple,msi-doorbell = <0xbffff000>; ++ apple,nvmmu-iova = <0xbc000000>; ++ ++ /* ++ * Root-complex inbound windows: 0x40000000 NVMMU FlatDMA, ++ * 0x80000000 DART (ADT vm-offset/vm-size), 0xbc000000 NVMMU ++ * SART, 0xbffff000 MSI. The DARTs' apple,dma-range keeps IOVAs ++ * inside the DART window. No dma-ranges here: one that ends ++ * below RAM empties ZONE_DMA/DMA32 (no CMA, no swiotlb). ++ */ ++ iommu-map = <0x100 &pcie0_dart0 0 1>, ++ <0x200 &pcie0_dart1 0 1>; ++ iommu-map-mask = <0xff00>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ bus-range = <0x00 0x0f>; ++ ranges = <0x03000000 0x0 0xc0000000 ++ 0x7 0xc0000000 0x0 0x40000000>; ++ ++ clocks = <&clkref>, <&clkref>, <&clkref>; ++ clock-names = "core", "aux", "ref"; ++ /* ADT apcie clock-gates 57-62: pcie, pcie_aux, pcie_link0..3 */ ++ power-domains = <&ps_pcie>, <&ps_pcie_aux>, <&ps_pcie_ref>, ++ <&ps_pcie_link0>, <&ps_pcie_link1>, ++ <&ps_pcie_link2>, <&ps_pcie_link3>; ++ power-domain-names = "core", "aux", "ref", "link0", "link1", ++ "link2", "link3"; ++ ++ /* ++ * Tunables iBoot puts in the live ADT (apcie-phy-tunables, ++ * apcie-common-tunables; per bridge apcie-config-tunables and ++ * pcie-rc-tunables), as . Copied from the ++ * j71t live ADT (iOS 16.7.10 iBoot); both bridges carry the same ++ * values there. ++ */ ++ apple,tunable-phy = <0x098 0x0000000f 0x00000000>, ++ <0x154 0x00f8ff00 0x00000000>, ++ <0x000 0x0000318c 0x00002108>, ++ <0x004 0x0000318c 0x00002108>; ++ apple,tunable-common = <0x008 0x7f7f7f7f 0x00000000>, ++ <0x00c 0x00073f3f 0x00043f00>, ++ <0x010 0x00000700 0x00000000>, ++ <0x018 0x00ffffff 0x000c0960>, ++ <0x01c 0x00001fff 0x0000092c>, ++ <0x028 0xffffffff 0x02001000>, ++ <0x030 0x000000ff 0x00000009>, ++ <0x100 0x31100010 0x01000010>, ++ <0x104 0x00000011 0x00000001>, ++ <0x108 0x00000007 0x00000000>, ++ <0x10c 0x00073f3f 0x00043f00>, ++ <0x110 0x0333003f 0x0111000f>, ++ <0x120 0x000000ff 0x00000040>, ++ <0x128 0x0000007f 0x0000003e>, ++ <0x130 0x00000707 0x00000000>, ++ <0x134 0x00073f3f 0x00043f00>, ++ <0x180 0x31100010 0x01000000>, ++ <0x190 0x0333003f 0x0111000f>, ++ <0x1a0 0x000000ff 0x00000040>, ++ <0x1a8 0x0000007f 0x0000003e>, ++ <0x1b0 0x00000707 0x00000000>, ++ <0x1b4 0x00073f3f 0x00043f00>, ++ <0x200 0x31100010 0x01000000>, ++ <0x210 0x0333003f 0x0111000f>, ++ <0x220 0x000000ff 0x00000040>, ++ <0x228 0x0000007f 0x0000003e>, ++ <0x230 0x00000707 0x00000000>, ++ <0x234 0x00073f3f 0x00043f00>, ++ <0x280 0x31100010 0x01000000>, ++ <0x290 0x0333003f 0x0111000f>, ++ <0x2a0 0x000000ff 0x00000040>, ++ <0x2a8 0x0000007f 0x0000003e>, ++ <0x2b0 0x00000707 0x00000000>, ++ <0x2b4 0x00073f3f 0x00043f00>; ++ status = "disabled"; ++ ++ /* ++ * PERST# from the j71t/j72t ADT pci-bridge0/1 function-perst. ++ * CLKREQ# (GPIO 164/165) stays in its peripheral function: ++ * iOS never waits on it here (no clkreq-wait-time). ++ */ ++ pcie0_port0: pci@0,0 { ++ device_type = "pci"; ++ reg = <0x0 0x0 0x0 0x0 0x0>; ++ reset-gpios = <&pinctrl_ap 160 GPIO_ACTIVE_LOW>; ++ max-link-speed = <2>; ++ apple,nvme-mode; ++ /* ++ * Linux would enable ASPM L1 on its own on DT platforms; ++ * iOS pairs L1 with L1SS/LTR/CLKREQ# setup we don't do. ++ */ ++ aspm-no-l0s; ++ aspm-no-l1; ++ apple,tunable-config = <0x090 0x000000ff 0x00000028>, ++ <0x130 0x00000005 0x00000005>, ++ <0x134 0x00000001 0x00000001>, ++ <0x138 0x00007f7f 0x00000000>, ++ <0x13c 0x00000003 0x00000002>, ++ <0x140 0x0073ffff 0x00704c4b>; ++ apple,tunable-rc = <0x098 0x0000000f 0x00000000>, ++ <0x154 0x00f8ff00 0x00000000>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges; ++ }; ++ ++ pcie0_port1: pci@1,0 { ++ device_type = "pci"; ++ reg = <0x800 0x0 0x0 0x0 0x0>; ++ reset-gpios = <&pinctrl_ap 161 GPIO_ACTIVE_LOW>; ++ max-link-speed = <1>; ++ aspm-no-l0s; ++ aspm-no-l1; ++ apple,tunable-config = <0x090 0x000000ff 0x00000028>, ++ <0x130 0x00000005 0x00000005>, ++ <0x134 0x00000001 0x00000001>, ++ <0x138 0x00007f7f 0x00000000>, ++ <0x13c 0x00000003 0x00000002>, ++ <0x140 0x0073ffff 0x00704c4b>; ++ apple,tunable-rc = <0x098 0x0000000f 0x00000000>, ++ <0x154 0x00f8ff00 0x00000000>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges; ++ }; + }; + }; + +@@ -66,3 +1551,21 @@ + * the S8003 (APL1022) on 16nm. There are some minor differences + * such as timing in cpufreq state transistions. + */ ++ ++/* j71t ADT: two GPU windows; explicitly activated diagnostic only. */ ++&{/soc} { ++ gpu_diag: gpu@201000000 { ++ compatible = "gpu,s8003"; ++ reg = <0x2 0x01000000 0x0 0x100000>, ++ <0x2 0x01e00000 0x0 0x100000>; ++ reg-names = "rgx", "fender"; ++ power-domains = <&ps_gfx>; ++ interrupts = , ++ , ++ , ++ , ++ ; ++ }; ++}; ++ ++#include "s8003-thermal.dtsi" +diff --git a/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi b/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi +index 972d84d6c0109e2f47bd6766ad13056237eeacac..b96193537e5ac9536147bb7bb6a0d69a52a7d195 100644 +--- a/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi ++++ b/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi +@@ -7,10 +7,26 @@ + + #include "s800-0-3-common.dtsi" + #include ++#include ++#include + + / { + chassis-type = "tablet"; + ++ /* ++ * Secure Enclave. Inert unless the sep-a9 driver is enabled ++ * (sep_a9.enable=1); the mailbox sits 0x4000 into the block. ++ */ ++ sep: sep@20da00000 { ++ compatible = "apple,s8003-sep", "apple,s8000-sep"; ++ reg = <0x2 0x0da00000 0x0 0x10000>; ++ interrupt-parent = <&aic>; ++ interrupts = , ++ , ++ , ++ ; ++ }; ++ + gpio-keys { + compatible = "gpio-keys"; + +@@ -46,6 +62,21 @@ + power-domains = <&ps_disp0 &ps_dp>; + }; + ++/* ++ * The framebuffer depends on the display pipe and eDP link that the boot ++ * loader set up, and both reset when these power-domains are powered off. ++ * That happens on suspend, when genpd powers off the framebuffer's domains ++ * in the noirq phase. Mark them as always on until a display driver can ++ * bring the panel back up. ++ */ ++&ps_disp0 { ++ apple,always-on; ++}; ++ ++&ps_dp { ++ apple,always-on; ++}; ++ + &i2c0 { + status = "okay"; + +@@ -55,6 +86,22 @@ + #address-cells = <1>; + #size-cells = <1>; + ++ /* D2365 event line: ADT GPIO 0x4c, not CPU PMU AIC 76. */ ++ interrupt-parent = <&pinctrl_ap>; ++ interrupts = <76 IRQ_TYPE_LEVEL_LOW>; ++ wakeup-source; ++ ++ /* Child event specifiers need a PMIC irqchip before use. */ ++ interrupt-controller; ++ #interrupt-cells = <1>; ++ ++ /* ADT j71s/j71t/j72s/j72t: usb-input-limit-max = 0x960 mA. */ ++ charger@180 { ++ reg = <0x180 0x1e>, <0xa00 0x0d>; ++ reg-names = "status", "control"; ++ apple,usb-input-limit-max-microamp = <2400000>; ++ }; ++ + rtc@500 { + compatible = "apple,angel-pmic-rtc", "apple,antigua-pmic-rtc"; + reg = <0x500 0x10>; +diff --git a/arch/arm64/boot/dts/apple/t8010-common.dtsi b/arch/arm64/boot/dts/apple/t8010-common.dtsi +index 7426147af3ea9dfb3e3e7f11d1806052f64f6d33..e583b1c57659b7cc0521e523dd42e747a5b7b326 100644 +--- a/arch/arm64/boot/dts/apple/t8010-common.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010-common.dtsi +@@ -12,6 +12,7 @@ + / { + aliases { + ausbphy0 = &ausbphy0; /* Used by loader */ ++ nvme-hmb = &nvme_hmb; /* Used by loader */ + serial0 = &serial0; + }; + +@@ -40,7 +41,10 @@ + #size-cells = <2>; + ranges; + +- /* To be filled by loader */ ++ nvme_hmb: nvme-hmb@0 { ++ reg = <0 0 0 0>; /* To be filled by loader */ ++ no-map; ++ }; + }; + }; + +@@ -48,6 +52,14 @@ + status = "okay"; + }; + ++&pcie0_dart0 { ++ status = "okay"; ++}; ++ ++&pcie0 { ++ status = "okay"; ++}; ++ + &serial0 { + status = "okay"; + }; +diff --git a/arch/arm64/boot/dts/apple/t8010.dtsi b/arch/arm64/boot/dts/apple/t8010.dtsi +index 72ec543d7c8b426c2a8ba47e303e8da96e5630cb..e77f5d28ad06f82f4ca35a136944db89ae8d9079 100644 +--- a/arch/arm64/boot/dts/apple/t8010.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010.dtsi +@@ -276,6 +276,78 @@ + #endif + }; + ++ pcie0_dart0: iommu@601008000 { ++ compatible = "apple,t8010-dart", "apple,s5l8960x-dart"; ++ reg = <0x6 0x01008000 0x0 0x4000>; ++ #iommu-cells = <1>; ++ interrupts = ; ++ power-domains = <&ps_pcie>; ++ apple,dma-range = <0x0 0x80000000 0x0 0x3c000000>; ++ status = "disabled"; ++ }; ++ ++ pcie0: pcie@610000000 { ++ compatible = "apple,t8010-pcie"; ++ device_type = "pci"; ++ ++ reg = <0x6 0x10000000 0x0 0x1000000>, ++ <0x6 0x00000000 0x0 0x8000>, ++ <0x6 0x00008000 0x0 0x4000>, ++ <0x6 0x0a000000 0x0 0x40000>, ++ <0x6 0x01000000 0x0 0x4000>, ++ <0x6 0x02000000 0x0 0x4000>, ++ <0x6 0x03000000 0x0 0x4000>, ++ <0x6 0x04000000 0x0 0x4000>, ++ <0x6 0x01004000 0x0 0x4000>; ++ reg-names = "config", "rc", "phy", "phy-ip", ++ "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ interrupts = , ++ , ++ , ++ , ++ ; ++ interrupt-names = "port0", "port1", "port2", "port3", ++ "nvmmu"; ++ ++ msi-controller; ++ msi-parent = <&pcie0>; ++ msi-ranges = <&aic AIC_IRQ 288 IRQ_TYPE_EDGE_RISING 32>; ++ apple,msi-doorbell = <0xbffff000>; ++ apple,nvmmu-iova = <0xbc000000>; ++ apple,nvme-hmb = <&nvme_hmb>; ++ ++ iommu-map = <0x100 &pcie0_dart0 0 1>; ++ iommu-map-mask = <0xff00>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ #interrupt-cells = <1>; ++ interrupt-controller; ++ bus-range = <0x00 0x0f>; ++ ranges = <0x03000000 0x0 0xc0000000 ++ 0x7 0xc0000000 0x0 0x40000000>; ++ ++ clocks = <&clkref>, <&clkref>, <&clkref>; ++ clock-names = "core", "aux", "ref"; ++ power-domains = <&ps_pcie>, <&ps_pcie_aux>, ++ <&ps_pcie_ref>; ++ power-domain-names = "core", "aux", "ref"; ++ status = "disabled"; ++ ++ pci@0,0 { ++ device_type = "pci"; ++ reg = <0x0 0x0 0x0 0x0 0x0>; ++ reset-gpios = <&pinctrl_ap 12 GPIO_ACTIVE_LOW>; ++ clkreq-gpios = <&pinctrl_ap 16 GPIO_ACTIVE_LOW>; ++ ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges; ++ }; ++ }; ++ + pmgr: power-management@20e000000 { + compatible = "apple,t8010-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + #address-cells = <1>; diff --git a/kernel/patches/j71t/0012-maintainers.patch b/kernel/patches/j71t/0012-maintainers.patch new file mode 100644 index 00000000..3e7b8245 --- /dev/null +++ b/kernel/patches/j71t/0012-maintainers.patch @@ -0,0 +1,69 @@ +From 4dfc90dd91f4e902e9e9405ad7db92a8ee069011 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 15:47:02 +0000 +Subject: [PATCH 12/12] MAINTAINERS: add the T8010/S8003 PCIe and ANS files +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +List the Apple PCIe host and ANS NVMe files. + +From the release 2 kernel of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + MAINTAINERS | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 7cb94e3bd9b15b3ced63fa1f5b0320e67c713808..ade85b570526e5fc0d7b3dcdcdda7e1cc2d3618f 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2652,6 +2652,7 @@ F: Documentation/devicetree/bindings/nvmem/apple,efuses.yaml + F: Documentation/devicetree/bindings/nvmem/apple,pmic-nvmem.yaml + F: Documentation/devicetree/bindings/nvmem/apple,spmi-nvmem.yaml + F: Documentation/devicetree/bindings/pci/apple,pcie.yaml ++F: Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml + F: Documentation/devicetree/bindings/phy/apple,atcphy.yaml + F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml + F: Documentation/devicetree/bindings/power/apple* +@@ -2684,9 +2685,12 @@ F: drivers/irqchip/irq-apple-aic.c + F: drivers/mfd/macsmc.c + F: drivers/mfd/simple-mfd-spmi.c + F: drivers/nvme/host/apple.c ++F: drivers/nvme/host/pci-apple-ans.c ++F: drivers/nvme/host/pci-internal.h + F: drivers/nvmem/apple-efuses.c + F: drivers/nvmem/apple-spmi-nvmem.c + F: drivers/nvmem/simple-mfd-nvmem.c ++F: drivers/pci/controller/pcie-apple-h9p.c + F: drivers/phy/apple/ + F: drivers/pinctrl/pinctrl-apple-gpio.c + F: drivers/power/reset/macsmc-reboot.c +@@ -2702,11 +2706,24 @@ F: drivers/video/backlight/apple_dwi_bl.c + F: drivers/video/backlight/apple_pmic_bl.c + F: drivers/watchdog/apple_wdt.c + F: include/dt-bindings/interrupt-controller/apple-aic.h ++F: include/linux/apple-ans.h + F: include/dt-bindings/pinctrl/apple.h + F: include/linux/mfd/macsmc.h + F: include/linux/soc/apple/* + F: include/uapi/drm/asahi_drm.h + ++APPLE T8010 PCIE AND ANS SUPPORT ++M: Paul Praschl ++L: asahi@lists.linux.dev ++L: linux-nvme@lists.infradead.org ++L: linux-pci@vger.kernel.org ++S: Maintained ++F: Documentation/devicetree/bindings/pci/apple,t8010-pcie.yaml ++F: drivers/nvme/host/pci-apple-ans.c ++F: drivers/nvme/host/pci-internal.h ++F: drivers/pci/controller/pcie-apple-h9p.c ++F: include/linux/apple-ans.h ++ + ARM/ARTPEC MACHINE SUPPORT + M: Jesper Nilsson + M: Lars Persson diff --git a/kernel/patches/j71t/0013-j71t-charging-sources.patch b/kernel/patches/j71t/0013-j71t-charging-sources.patch new file mode 100644 index 00000000..f9754628 --- /dev/null +++ b/kernel/patches/j71t/0013-j71t-charging-sources.patch @@ -0,0 +1,1198 @@ +From 0dd7ae0ce28ac531913ec71284663d0eb9e19537 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 27 Oct 2026 18:26:06 +0000 +Subject: [PATCH] power: supply: apple: charge J71t from computers and USB-C + chargers at 2.4 A +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Charger source detection for the J71t Lightning port, as validated on +an iPad 5 Wi-Fi (S8003) on 2026-10-27: + +- The Tristar power role reads the USB-C CC advertisement through + Apple's USB-C to Lightning cable (3 A sources are capped at the + 2.4 A board maximum), keeps BC1.2 results across the cable's ID + exchange, monitors idle Lightning lanes, and prefers an enumerated + USB host over an early BC1.2 reading, so computers get the board + maximum too. +- The D2365 charger restores the policy input limit after the PMIC's + attach default (0x02) instead of treating it as a weak source. +- bq27xxx reports Discharging when the J71t gauge measures negative + current, also at 100 %. +- The gadget core tells current-budget listeners why it notified + them (no hardware operation). +- The diagnostics added to the lightning_state and charge7_state + files are documented in sysfs-apple-charge{8,9,10}. + +Computers (USB-A, and a Mac USB-C port through the USB-C to Lightning +cable), a USB-C wall charger and a Mac USB-C power adapter charge at +2.4 A, and the battery no longer drains on wall chargers. Charging is +5 V only; the iPad 5 has no USB Power Delivery. + +From the validated charging update of the iPad 5 port +(https://github.com/brkosebay/omarchy-ipad5), filtered for OrchardLinux. + +Signed-off-by: Berkay Kösebay +Co-Authored-By: Claude Opus 5.5 +--- + .../ABI/testing/sysfs-apple-charge10 | 19 + + Documentation/ABI/testing/sysfs-apple-charge8 | 23 + + Documentation/ABI/testing/sysfs-apple-charge9 | 27 + + drivers/power/supply/apple_j71t_charger.c | 45 +- + drivers/power/supply/apple_tristar_power.c | 485 ++++++++++++++---- + drivers/power/supply/bq27xxx_battery.c | 43 ++ + drivers/usb/gadget/udc/core.c | 18 +- + include/linux/usb/gadget.h | 11 + + 8 files changed, 566 insertions(+), 105 deletions(-) + create mode 100644 Documentation/ABI/testing/sysfs-apple-charge10 + create mode 100644 Documentation/ABI/testing/sysfs-apple-charge8 + create mode 100644 Documentation/ABI/testing/sysfs-apple-charge9 + +diff --git a/Documentation/ABI/testing/sysfs-apple-charge10 b/Documentation/ABI/testing/sysfs-apple-charge10 +new file mode 100644 +index 0000000000000000000000000000000000000000..8f2b0b5f3bad871b105110b89ba5c7d2163b0a05 +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-apple-charge10 +@@ -0,0 +1,19 @@ ++What: /sys/bus/i2c/devices/*-001a/lightning_state ++Date: October 2026 ++Contact: Berkay Kösebay ++Description: ++ source_epoch adds computer_seen: a positive USB data session ++ (connected, configured, or host-granted gadget evidence) observed ++ in this source epoch. It persists across USB suspend/reset and is ++ cleared by the same VBUS/ID/phase boundaries as other source policy. ++ A real computer supersedes prior divider/BC1.2/CC source results: ++ usb_type becomes SDP, with the charger's generic board ceiling. ++ bc_ua/bc_type remain diagnostic evidence and cannot cap that host. ++ ++ bc_primary_mv and bc_secondary_mv report the most recent BC pin ++ measurements, distinct from passive dp_mv/dm_mv. Negative values ++ are errno/unavailable. They are reset on a new source epoch and ++ retained during a CC-only refresh. For ordinary USB, secondary D+ ++ below 326 mV means CDP, at/above 326 mV means DCP. These are pin ++ voltages, not VBUS. A never-enumerating source keeps its proven ++ BC1.2/CC allowance, bounded by the existing charger policy. +diff --git a/Documentation/ABI/testing/sysfs-apple-charge8 b/Documentation/ABI/testing/sysfs-apple-charge8 +new file mode 100644 +index 0000000000000000000000000000000000000000..4577e4472878077678099953d64d12ac9b6382d8 +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-apple-charge8 +@@ -0,0 +1,23 @@ ++What: /sys/bus/i2c/devices/*-001a/lightning_state ++Date: October 2026 ++Contact: Berkay Kösebay ++Description: ++ The charger_detect line adds cached diagnostics to the existing ++ Lightning snapshot. Reading it never starts a pin conversion. ++ stage/pin identify the last detection operation; error is its errno, ++ monitor_error is the role-monitor callback result (including a ++ refused callback), attempts/failures count this physical source, ++ retry_ms is the failure backoff (1000..60000 ms), poisoned means ++ ADC completion was uncertain and further triggers are prohibited ++ until reboot. Detach does not clear that poison; poison_stage and ++ poison_attach retain the operation and source epoch that latched it. ++ ++ route is the last pre-monitor DX_CONTROL byte, adc_status the last ++ 0x156 read. Negative values indicate errno or unavailable (-ENODATA). ++ dp_mv/dm_mv are passive divider samples, cc_mv the last opposite-pair ++ D+ sample for verified DigitalID 10 09 08 00 00 00. These are pin ++ voltages, never VBUS. sampled means classification completed. ++ Type C budgets come from stable CC advertisement (1500/3000 mA), ++ 3 A requires BC1.2 charging-port evidence; otherwise it is capped ++ at 1500 mA. All budgets are bounded by the charger's board ceiling and supported current ladder. ++ Classification does not negotiate USB Power Delivery or change VBUS. +diff --git a/Documentation/ABI/testing/sysfs-apple-charge9 b/Documentation/ABI/testing/sysfs-apple-charge9 +new file mode 100644 +index 0000000000000000000000000000000000000000..d004764f00a493eee99f7be82e74c5ce52c80b5e +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-apple-charge9 +@@ -0,0 +1,27 @@ ++What: /sys/class/power_supply/charger/charge7_state ++Date: October 2026 ++Contact: Berkay Kösebay ++Description: ++ The attach_resets field counts successfully repaired D2365 0xa03 ++ attach-default resets (0x02) in the first fifteen seconds of ownership ++ or a new source epoch. These do not create adaptive backoffs. A ++ policy step always uses its calibrated register code, including the ++ floor after other unexplained PMIC input reductions. Transport and ++ battery safety failures can defer a repair; live_ua remains readback, ++ not an assertion that nominal and actual currents are identical. ++ ++What: /sys/bus/i2c/devices/*-001a/lightning_state ++Date: October 2026 ++Contact: Berkay Kösebay ++Description: ++ source_epoch records id, reason (boot, vbus-detach, digital-id or ++ accessory-phase), retained bc_ua/bc_type, cc_first_mv, lifetime ++ usb_events/usb_idle_events, data-session usb_generation and last ++ usb_event_source. Origin codes: 1 source forget, 2 base budget, ++ 3 grant revoke, 4 suspend, 5 SETUP, 6 grant, 7 gadget state. ++ Idle notifications do not invalidate conversions; actual USB session ++ activity still does. A first ID refines the current epoch, preserving ++ verified BC evidence and scheduling one settled reclassification. ++ Changed IDs and phase drops to device/empty revoke the source even ++ when polling missed the VBUS-low interval. No poll-based guarantee ++ exists for an otherwise unobserved same-ID swap. +diff --git a/drivers/power/supply/apple_j71t_charger.c b/drivers/power/supply/apple_j71t_charger.c +index a7ea39fdedaa0a313f2d77d138266502ff22720b..18bdc6547d8d97da217174ec6371cfca4f16a532 100644 +--- a/drivers/power/supply/apple_j71t_charger.c ++++ b/drivers/power/supply/apple_j71t_charger.c +@@ -47,6 +47,8 @@ + /* One budget reassert per window before a register change counts as a fault. */ + #define J71T_REASSERT_WINDOW_MS 10000 + #define J71T_BACKOFF_HOLD_MS 60000 ++#define J71T_ATTACH_RESET_MS 15000 ++#define J71T_ATTACH_INPUT_DEFAULT 0x02 + + static const int j71t_budgets_ua[] = { 500000, 1000000, 2100000, 2400000 }; + +@@ -69,6 +71,8 @@ struct j71t_charger { + int adaptive_ua; + unsigned int attach_id; + unsigned int backoffs; ++ unsigned int attach_resets; ++ unsigned long attach_until; + unsigned long adaptive_after; + const char *backoff_cause; + bool thermal_limited; +@@ -228,6 +232,7 @@ static int j71t_source_eligible(struct j71t_charger *charger) + return value.intval == POWER_SUPPLY_USB_TYPE_SDP || + value.intval == POWER_SUPPLY_USB_TYPE_CDP || + value.intval == POWER_SUPPLY_USB_TYPE_DCP || ++ value.intval == POWER_SUPPLY_USB_TYPE_C || + value.intval == POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID || + value.intval == POWER_SUPPLY_USB_TYPE_ACA; + } +@@ -303,6 +308,8 @@ static void j71t_new_attach(struct j71t_charger *charger, unsigned int id) + charger->ceiling_ua = 0; + charger->source_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + charger->attach_id = id; ++ charger->attach_resets = 0; ++ charger->attach_until = jiffies + msecs_to_jiffies(J71T_ATTACH_RESET_MS); + } + + static int j71t_battery_safe(struct j71t_charger *charger) +@@ -430,6 +437,7 @@ static int j71t_enable(struct j71t_charger *charger) + if (ret) + return ret; + charger->saved = true; ++ charger->attach_until = jiffies + msecs_to_jiffies(J71T_ATTACH_RESET_MS); + } + ret = j71t_apply(charger, charger->charge_code, charger->input_code); + if (!ret) { +@@ -523,7 +531,8 @@ static int j71t_reassert(struct j71t_charger *charger, u8 input, u8 control, u8 + } + + /* +- * Do not fight a lower PMU input setting. Its electrical cause is unknown: ++ * Reduce the policy step after a non-default lower PMU setting; then assert ++ * the calibrated code for that step. Its electrical cause is unknown: + * D2365's iOS isInputLimited callback (6bc038c) is constant false. This is + * an observed setpoint reduction, not a fabricated VBUS/droop measurement. + */ +@@ -541,7 +550,7 @@ static int j71t_input_reduced(struct j71t_charger *charger, u8 input) + charger->backoff_cause = "pmic-input-reduced"; + charger->backoffs++; + charger->budget_ua = charger->adaptive_ua; +- charger->input_code = min(charger->input_codes[index], input); ++ charger->input_code = charger->input_codes[index]; + charger->charge_code = j71t_charge_code(charger->budget_ua); + ret = j71t_enable(charger); + dev_warn_ratelimited(charger->dev, +@@ -577,6 +586,7 @@ static void j71t_policy_work(struct work_struct *work) + struct j71t_charger, work); + int ret = 0, target, index, online, previous; + unsigned int id; ++ bool attach_reset = false; + + mutex_lock(&charger->lock); + if (charger->stopping) +@@ -613,6 +623,13 @@ static void j71t_policy_work(struct work_struct *work) + u8 input = 0, control = 0, charge = 0; + + ret = j71t_check_owned(charger, &input, &control, &charge); ++ if (j71t_is_fault(ret) && input == J71T_ATTACH_INPUT_DEFAULT && ++ time_before(jiffies, charger->attach_until) && ++ !(control & J71T_INPUT_SUSPEND) && charge == charger->charge_code) { ++ /* D2365 attach reset, observed 26 ms after applying the floor. */ ++ attach_reset = true; ++ ret = 0; ++ } + if (j71t_is_fault(ret) && input < charger->input_code && + !(control & J71T_INPUT_SUSPEND) && charge == charger->charge_code) { + ret = j71t_input_reduced(charger, input); +@@ -669,17 +686,27 @@ static void j71t_policy_work(struct work_struct *work) + if (index >= 0 && target > previous) { + int next = previous ? j71t_choose_budget(previous) + 1 : 0; + +- if (previous && time_before(jiffies, charger->ramp_after)) +- goto report; ++ if (previous && time_before(jiffies, charger->ramp_after)) { ++ if (!attach_reset) ++ goto report; ++ next = j71t_choose_budget(previous); ++ } + index = min(index, next); + } +- if (index >= 0 && (!charger->active || j71t_budgets_ua[index] != previous)) { ++ if (index >= 0 && ++ (!charger->active || j71t_budgets_ua[index] != previous || attach_reset)) { + charger->budget_ua = j71t_budgets_ua[index]; + charger->input_code = charger->input_codes[index]; + charger->charge_code = j71t_charge_code(charger->budget_ua); + ret = j71t_enable(charger); + if (ret) + goto restore; ++ if (attach_reset) { ++ charger->attach_resets++; ++ dev_info_ratelimited(charger->dev, ++ "attach default 0x02: restored policy input 0x%02x\n", ++ charger->input_code); ++ } + charger->ramp_after = jiffies + msecs_to_jiffies(1000); + } + goto report; +@@ -837,7 +864,8 @@ static const struct power_supply_desc j71t_desc = { + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | +- BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA), ++ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA) | ++ BIT(POWER_SUPPLY_USB_TYPE_C), + .properties = j71t_properties, + .num_properties = ARRAY_SIZE(j71t_properties), + .get_property = j71t_get_property, +@@ -855,12 +883,13 @@ static ssize_t charge7_state_show(struct device *dev, struct device_attribute *a + mutex_lock(&charger->lock); + ret = j71t_read(charger, J71T_INPUT_LIMIT, &input); + len = sysfs_emit(buf, +- "attach=%u source_type=%d ceiling_ua=%d step_ua=%d live_ua=%d override_ua=%d adaptive_ua=%d backoffs=%u last_backoff=%s vbus_mv=unavailable policy_error=%d active=%d\n", ++ "attach=%u source_type=%d ceiling_ua=%d step_ua=%d live_ua=%d override_ua=%d adaptive_ua=%d backoffs=%u last_backoff=%s vbus_mv=unavailable policy_error=%d active=%d attach_resets=%u\n", + charger->attach_id, charger->source_type, charger->ceiling_ua, + charger->active ? charger->budget_ua : 0, + ret ? ret : 75000 + input * 12500, charger->user_limit_ua, + charger->adaptive_ua, charger->backoffs, +- charger->backoff_cause ?: "none", charger->policy_error, charger->active); ++ charger->backoff_cause ?: "none", charger->policy_error, charger->active, ++ charger->attach_resets); + mutex_unlock(&charger->lock); + return len; + } +diff --git a/drivers/power/supply/apple_tristar_power.c b/drivers/power/supply/apple_tristar_power.c +index 2f58419da71a6e59b94d496b5d3138c5285e9261..c97848d8fa4a498713fcc2dc5bd8dc7e656a7e6b 100644 +--- a/drivers/power/supply/apple_tristar_power.c ++++ b/drivers/power/supply/apple_tristar_power.c +@@ -90,6 +90,20 @@ struct apple_tristar { + struct notifier_block usb_notifier; + atomic_t reset_generation; + int seen_generation; ++ atomic_t usb_session; ++ atomic_t computer_seen; ++ atomic_t usb_events; ++ atomic_t usb_idle_events; ++ unsigned long usb_event_source; ++ int bc_ua; ++ int bc_type; ++ int cc_first_mv; ++ int bc_primary_mv; ++ int bc_secondary_mv; ++ u8 source_id[TRISTAR_AUTO_ID_LEN]; ++ bool source_id_valid; ++ bool epoch_notify; ++ const char *epoch_reason; + struct delayed_work work; + /* Serializes register reads and protects the cached source state. */ + struct mutex lock; +@@ -109,11 +123,28 @@ struct apple_tristar { + bool computer; + bool detach_role; + unsigned long classify_after; ++ unsigned long classify_log_after; ++ const char *detect_stage; ++ const char *detect_pin; ++ int detect_error; ++ int monitor_error; ++ int sample_status; ++ int route_before; ++ int adc_status; ++ int dp_mv; ++ int dm_mv; ++ int cc_mv; ++ unsigned int detect_attempts; ++ unsigned int detect_failures; ++ unsigned int retry_ms; ++ bool detect_logged; + u8 saved_route; + bool restore_pending; + bool dcp_pending; + bool brick_sampled; + bool adc_poisoned; ++ const char *adc_fault_stage; ++ unsigned int adc_fault_attach; + bool stopping; + bool notifier_registered; + bool sleeping; +@@ -187,21 +218,27 @@ static int apple_tristar_adc_mv(struct apple_tristar *ts) + if (ts->adc_poisoned) + return -EIO; + /* Observe the completion latch LOW before starting a new sample. */ ++ ts->detect_stage = "adc-idle"; + ret = read_poll_timeout(apple_tristar_pmic_read, status, + status < 0 || !(status & 1), 1000, 20000, + false, ts, D2365_ADC_DONE); ++ ts->adc_status = status; + if (ret || status < 0) + return ret ?: status; ++ ts->detect_stage = "adc-trigger"; + ret = regmap_write(ts->pmic, D2365_ADC_START, 1); + if (ret) + goto poison; ++ ts->detect_stage = "adc-done"; + ret = read_poll_timeout(apple_tristar_pmic_read, status, + status < 0 || (status & 1), 1000, 1000000, + false, ts, D2365_ADC_DONE); ++ ts->adc_status = status; + if (ret || status < 0) { + ret = ret ?: status; + goto poison; + } ++ ts->detect_stage = "adc-result"; + ret = regmap_bulk_read(ts->pmic, D2365_ADC_RESULT, result, sizeof(result)); + if (ret) + return ret; +@@ -214,46 +251,93 @@ static int apple_tristar_adc_mv(struct apple_tristar *ts) + poison: + /* A late completion must never be mistaken for the next pin's sample. */ + ts->adc_poisoned = true; ++ ts->adc_fault_stage = ts->detect_stage; ++ ts->adc_fault_attach = ts->attach_id; + return ret; + } + ++/* Caller holds the role hardware lock and ts->lock. */ + static bool apple_tristar_sample_valid(struct apple_tristar *ts) + { + struct usb_gadget_current_budget budget; ++ int ret = usb_gadget_get_current_budget(ts->usb_node, &budget); + +- return !usb_gadget_get_current_budget(ts->usb_node, &budget) && +- !budget.connected && !budget.configured && !budget.host_grant && +- ts->seen_generation == atomic_read(&ts->reset_generation); ++ if (ret) { ++ ts->detect_stage = "usb-budget"; ++ return false; ++ } ++ if (budget.connected || budget.configured || budget.host_grant || ++ atomic_read(&ts->computer_seen)) { ++ ts->detect_stage = "usb-active"; ++ return false; ++ } ++ if (ts->seen_generation != atomic_read(&ts->reset_generation)) { ++ ts->detect_stage = "usb-event"; ++ return false; ++ } ++ return true; + } + +-static int apple_tristar_pin_mv(struct apple_tristar *ts, bool dm, bool dcp) ++static int apple_tristar_sample_vbus(struct apple_tristar *ts) + { +- int ret, restore; +- u8 route; ++ int status; + + if (!apple_tristar_sample_valid(ts)) + return -EAGAIN; ++ ts->detect_stage = "vbus"; ++ status = apple_tristar_read(ts->client, TRISTAR_VBUS); ++ if (status < 0) ++ return status; ++ if ((status & 3) != 3) ++ return -ENOLINK; ++ ts->detect_stage = "orientation"; ++ if (!(status & TRISTAR_AUTO_PRESENT) || ++ ((status ^ ts->sample_status) & TRISTAR_AUTO_ORIENTATION)) ++ return -EAGAIN; ++ return apple_tristar_sample_valid(ts) ? 0 : -EAGAIN; ++} ++ ++/* ++ * Normal USB: monitor the oriented pair. Type-C CC: D+ on the OTHER pair ++ * (iOS 5e23a90 -> 5fe52dc -> 5ff5ed0), never a fixed cable-ID allowance. ++ * A zero mux is valid after replug: the direct iOS lane monitor also accepts ++ * it. Only touch an idle or USB-selected nibble; restore the exact byte. ++ */ ++static int apple_tristar_pin_mv(struct apple_tristar *ts, bool dm, bool dcp, bool cc) ++{ ++ int ret, restore, nibble; ++ bool high = !!(ts->sample_status & TRISTAR_AUTO_ORIENTATION) ^ cc; ++ u8 route; ++ ++ ts->detect_pin = cc ? "cc" : dcp ? dm ? "primary" : "secondary" : dm ? "dm" : "dp"; ++ ret = apple_tristar_sample_vbus(ts); ++ if (ret) ++ return ret; ++ ts->detect_stage = "route-read"; + ret = apple_tristar_read(ts->client, TRISTAR_DX_CONTROL); ++ ts->route_before = ret; + if (ret < 0) + return ret; + ts->saved_route = ret; +- if ((ret & 7) == 1) +- route = (ret & 0xf0) | (dm ? 0x0d : 0x0c); +- else if ((ret & 0x70) == 0x10) +- route = (ret & 0x0f) | (dm ? 0xd0 : 0xc0); +- else ++ nibble = high ? ret >> 4 : ret & 15; ++ ts->detect_stage = "route-select"; ++ if (nibble != 0 && (nibble & 7) != 1) + return -EOPNOTSUPP; ++ route = high ? (ret & 0x0f) | (dm ? 0xd0 : 0xc0) : ++ (ret & 0xf0) | (dm ? 0x0d : 0x0c); ++ if (!apple_tristar_sample_valid(ts)) ++ return -EAGAIN; + ts->restore_pending = true; ++ ts->detect_stage = "route-write"; + ret = apple_tristar_write_checked(ts, route); + if (!ret) { + usleep_range(5000, 6000); + if (dcp) { + /* iOS 5ff5a08: monitor 4 (D-), 5 (D+), 40 ms pulse. */ +- u8 pulse = dm ? 9 : 10; ++ u8 pulse = (dm ? 9 : 10) + (high ? 2 : 0); + +- if ((ts->saved_route & 7) != 1) +- pulse += 2; + ts->dcp_pending = true; ++ ts->detect_stage = "dcp-write"; + ret = apple_tristar_write_reg_checked(ts, TRISTAR_DCP_CONTROL, pulse); + if (!ret) + msleep(40); +@@ -262,11 +346,33 @@ static int apple_tristar_pin_mv(struct apple_tristar *ts, bool dm, bool dcp) + ret = apple_tristar_sample_valid(ts) ? apple_tristar_adc_mv(ts) : -EAGAIN; + } + restore = apple_tristar_restore_route(ts); +- if (!restore && !apple_tristar_sample_valid(ts)) +- return -EAGAIN; ++ if (restore) { ++ ts->detect_stage = "restore"; ++ return restore; ++ } ++ /* Preserve the original error/stage unless the conversion succeeded. */ ++ if (ret < 0) ++ return ret; ++ restore = apple_tristar_sample_vbus(ts); + return restore ?: ret; + } + ++/* Verified 10 09 08 cable: USB=2, CC=1 (5e34408/5e4d9ac). */ ++static bool apple_tristar_typec_cable(struct apple_tristar *ts) ++{ ++ static const u8 id[] = { 0x10, 0x09, 0x08, 0, 0, 0 }; ++ ++ return ts->autop.id_valid && !memcmp(ts->autop.id, id, sizeof(id)); ++} ++ ++/* IOAccessoryPowerSourceItemUSB_TypeC_Current, 5de4024, inclusive edges. */ ++static int apple_tristar_cc_current(int mv) ++{ ++ if (mv < 0 || mv > 4000) ++ return -ERANGE; ++ return mv > 1229 ? 3000000 : mv > 659 ? 1500000 : 0; ++} ++ + static int apple_tristar_brick_current(int dp_mv, int dm_mv) + { + static const int limits[] = { 500, 1000, 1000, 2100, 2400, 2400, 1000, 1000, 1000 }; +@@ -281,84 +387,182 @@ static int apple_tristar_brick_current(int dp_mv, int dm_mv) + return limits[3 * dp + dm] * 1000; + } + ++static void apple_tristar_detect_failed(struct apple_tristar *ts, int ret) ++{ ++ ts->detect_error = ret; ++ ts->detect_failures++; ++ ts->retry_ms = min(1000U << min(ts->detect_failures - 1, 6U), 60000U); ++ ts->classify_after = jiffies + msecs_to_jiffies(ts->retry_ms); ++ /* No repeated 1 Hz warnings, even if the global printk bucket refills. */ ++ if (!ts->detect_logged || !time_before(jiffies, ts->classify_log_after)) { ++ dev_warn_ratelimited(&ts->client->dev, ++ "charger detect: %s/%s error %d route %d adc %d poisoned %d attempt %u retry %u ms\n", ++ ts->detect_pin ?: "none", ts->detect_stage ?: "none", ret, ++ ts->route_before, ts->adc_status, ts->adc_poisoned, ++ ts->detect_attempts, ts->adc_poisoned ? 0 : ts->retry_ms); ++ ts->classify_log_after = jiffies + msecs_to_jiffies(60000); ++ ts->detect_logged = true; ++ } ++} ++ + static void apple_tristar_classify(struct apple_tristar *ts) + { +- struct usb_gadget_current_budget budget; +- int dp, dm, ret; ++ int dp, dm, ret, cc, attempt, cc_ua = 0, type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ int bc_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ bool typec = apple_tristar_typec_cable(ts); + + /* Caller holds the role hardware lock before ts->lock. */ +- if (ts->computer || ts->brick_sampled || ts->adc_poisoned || !ts->online || ts->error || +- time_before(jiffies, ts->classify_after)) ++ if (ts->computer || atomic_read(&ts->computer_seen) || ++ (ts->brick_sampled && !typec) || ts->adc_poisoned || ++ !ts->online || ts->error || time_before(jiffies, ts->classify_after)) + return; + ts->seen_generation = atomic_read(&ts->reset_generation); +- ret = usb_gadget_get_current_budget(ts->usb_node, &budget); +- /* Never divert active USB traffic to the analog monitor. */ +- if (ret || budget.connected || budget.configured || budget.host_grant || +- ts->seen_generation != atomic_read(&ts->reset_generation)) ++ if (!apple_tristar_sample_valid(ts)) + return; +- dp = apple_tristar_pin_mv(ts, false, false); +- if (dp < 0) ++ ts->detect_attempts++; ++ ts->sample_status = ts->status; ++ ts->dp_mv = -ENODATA; ++ ts->dm_mv = -ENODATA; ++ ts->cc_mv = -ENODATA; ++ if (typec) { ++ cc_ua = -1; ++ /* Ignore one unsettled first reading; require two equal advertisements. */ ++ for (attempt = 0; attempt < 3; attempt++) { ++ cc = apple_tristar_pin_mv(ts, false, false, true); ++ ts->cc_mv = cc; ++ if (!attempt) ++ ts->cc_first_mv = cc; ++ if (cc < 0) { ++ ret = cc; ++ goto fail; ++ } ++ ts->detect_stage = "cc-range"; ++ ret = apple_tristar_cc_current(cc); ++ if (ret < 0) ++ goto fail; ++ if (ret == cc_ua) ++ break; ++ cc_ua = ret; ++ if (attempt < 2) ++ msleep(20); ++ } ++ ts->detect_stage = "cc-debounce"; ++ if (attempt == 3) { ++ ret = -EAGAIN; ++ goto fail; ++ } ++ if (ts->bc_ua) { ++ /* CC refresh cannot erase a proven BC item from this epoch. */ ++ ret = ts->bc_ua; ++ type = ts->bc_type; ++ goto combine; ++ } ++ } ++ dp = apple_tristar_pin_mv(ts, false, false, false); ++ ts->dp_mv = dp; ++ if (dp < 0) { ++ ret = dp; + goto fail; +- ret = usb_gadget_get_current_budget(ts->usb_node, &budget); +- if (ret || budget.connected || budget.configured || budget.host_grant || +- ts->seen_generation != atomic_read(&ts->reset_generation)) +- return; +- dm = apple_tristar_pin_mv(ts, true, false); +- if (dm < 0) ++ } ++ dm = apple_tristar_pin_mv(ts, true, false, false); ++ ts->dm_mv = dm; ++ if (dm < 0) { ++ ret = dm; + goto fail; +- ret = usb_gadget_get_current_budget(ts->usb_node, &budget); +- if (ret || budget.connected || budget.configured || budget.host_grant || +- ts->seen_generation != atomic_read(&ts->reset_generation)) +- return; +- ret = apple_tristar_read(ts->client, TRISTAR_VBUS); +- if (ret < 0 || (ret & 3) != 3 || +- ts->seen_generation != atomic_read(&ts->reset_generation)) +- return; ++ } ++ ts->detect_stage = "divider-range"; + ret = apple_tristar_brick_current(dp, dm); + if (ret < 0) + goto fail; +- ts->analog_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; +- if (!ret) { ++ if (ret) { ++ type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID; ++ } else { + /* iOS 5de33e4/5de35dc, ordinary USB source (connect type 0). */ +- dm = apple_tristar_pin_mv(ts, true, true); +- if (dm < 0) ++ dm = apple_tristar_pin_mv(ts, true, true, false); ++ ts->bc_primary_mv = dm; ++ if (dm < 0) { ++ ret = dm; ++ goto fail; ++ } ++ ts->detect_stage = "primary-range"; ++ if (dm > 4000) { ++ ret = -ERANGE; + goto fail; ++ } + if (dm >= 326 && dm <= 899) { +- dp = apple_tristar_pin_mv(ts, false, true); +- if (dp < 0) ++ dp = apple_tristar_pin_mv(ts, false, true, false); ++ ts->bc_secondary_mv = dp; ++ if (dp < 0) { ++ ret = dp; + goto fail; +- ts->analog_type = dp < 326 ? POWER_SUPPLY_USB_TYPE_DCP : +- POWER_SUPPLY_USB_TYPE_CDP; ++ } ++ ts->detect_stage = "secondary-range"; ++ if (dp > 4000) { ++ ret = -ERANGE; ++ goto fail; ++ } ++ /* iOS internal 2=CDP, 1=DCP (type names at 6de2df0). */ ++ type = dp < 326 ? POWER_SUPPLY_USB_TYPE_CDP : POWER_SUPPLY_USB_TYPE_DCP; + ret = 1500000; + } + } +- if (!apple_tristar_sample_valid(ts)) +- return; +- dp = apple_tristar_read(ts->client, TRISTAR_VBUS); +- if (dp < 0 || (dp & 3) != 3 || !apple_tristar_sample_valid(ts)) +- return; ++combine: ++ bc_type = type; ++ /* iOS 5e23a90: without a charging-port type, clamp 3 A CC to 1.5 A. */ ++ if (cc_ua == 3000000 && type != POWER_SUPPLY_USB_TYPE_DCP && ++ type != POWER_SUPPLY_USB_TYPE_CDP) ++ cc_ua = 1500000; ++ if (cc_ua && cc_ua >= ret) { ++ ret = cc_ua; ++ type = POWER_SUPPLY_USB_TYPE_C; ++ } ++ cc = apple_tristar_sample_vbus(ts); ++ if (cc) { ++ ret = cc; ++ goto fail; ++ } ++ if (bc_type == POWER_SUPPLY_USB_TYPE_DCP || bc_type == POWER_SUPPLY_USB_TYPE_CDP) { ++ ts->bc_ua = 1500000; ++ ts->bc_type = bc_type; ++ } ++ /* Refresh Type-C advertisement, including reductions, while idle. */ ++ ts->classify_after = jiffies + msecs_to_jiffies(5000); ++ if (!ts->brick_sampled || ts->brick_ua != ret || ts->analog_type != type) ++ dev_info_ratelimited(&ts->client->dev, ++ "charger detected type %d budget %d mA dp %d dm %d cc %d mV\n", ++ type, ret / 1000, ts->dp_mv, ts->dm_mv, ts->cc_mv); + ts->brick_ua = ret; ++ ts->analog_type = type; + ts->brick_sampled = true; +- dev_info_ratelimited(&ts->client->dev, "analog charger type %d, budget %d mA\n", +- ts->analog_type, ret / 1000); ++ ts->detect_error = 0; ++ ts->detect_stage = "complete"; ++ ts->detect_failures = 0; ++ ts->retry_ms = 0; + return; + fail: +- ts->classify_after = jiffies + msecs_to_jiffies(1000); +- dev_warn_ratelimited(&ts->client->dev, +- "brick detection unavailable; no elevated analog allowance\n"); ++ /* Revoke uncertain CC, retaining only separately verified BC evidence. */ ++ if (typec) { ++ ts->brick_ua = ts->bc_ua; ++ ts->analog_type = ts->bc_type; ++ ts->brick_sampled = false; ++ } ++ apple_tristar_detect_failed(ts, ret); + } + + /* Never call USB role APIs while holding ts->lock: role changes unbind UDC. */ + static int apple_tristar_monitor(void *data) + { + struct apple_tristar *ts = data; +- int ret = 0; ++ int ret; + + mutex_lock(&ts->lock); +- if (!ts->stopping && !ts->sleeping) { +- ret = apple_tristar_restore_route(ts); +- if (!ret) +- apple_tristar_classify(ts); ++ /* Repair even when quiescing; never restore outside the role lock. */ ++ ret = apple_tristar_restore_route(ts); ++ if (ret) { ++ ts->detect_stage = "restore"; ++ ts->detect_error = ret; ++ } else if (!ts->stopping && !ts->sleeping) { ++ apple_tristar_classify(ts); + } + if (ts->restore_pending || ts->dcp_pending) + ret = -EUCLEAN; +@@ -406,6 +610,62 @@ static void apple_tristar_auto_cfg(struct tristar_auto_cfg *cfg) + }; + } + ++/* Caller holds ts->lock. Poll gaps can hide VBUS loss; ID/phase are evidence too. */ ++static void apple_tristar_new_source(struct apple_tristar *ts, const char *reason) ++{ ++ ts->attach_id++; ++ ts->epoch_reason = reason; ++ ts->epoch_notify = true; ++ ts->source_id_valid = false; ++ ts->computer = false; ++ atomic_set(&ts->computer_seen, 0); ++ ts->brick_sampled = false; ++ ts->brick_ua = 0; ++ ts->bc_ua = 0; ++ ts->bc_primary_mv = -ENODATA; ++ ts->bc_secondary_mv = -ENODATA; ++ ts->bc_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ ts->analog_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ ts->current_ua = APPLE_TRISTAR_FLOOR_UA; ++ ts->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; ++ ts->classify_after = jiffies + msecs_to_jiffies(300); ++ ts->detect_attempts = 0; ++ ts->detect_logged = false; ++ ts->detect_failures = 0; ++ ts->retry_ms = 0; ++ ts->detect_error = 0; ++ ts->detect_stage = reason; ++ usb_gadget_current_source_detached(ts->usb_node); ++} ++ ++/* First ID refines this source; a changed ID proves a new source epoch. */ ++static void apple_tristar_source_id(struct apple_tristar *ts, const u8 *id) ++{ ++ bool changed = !ts->source_id_valid || memcmp(ts->source_id, id, sizeof(ts->source_id)); ++ ++ if (ts->source_id_valid && changed) ++ apple_tristar_new_source(ts, "digital-id"); ++ memcpy(ts->source_id, id, sizeof(ts->source_id)); ++ ts->source_id_valid = true; ++ if (changed) { ++ /* Preserve this epoch's BC evidence; once after ID settle, try CC too. */ ++ ts->brick_sampled = false; ++ ts->classify_after = jiffies + msecs_to_jiffies(300); ++ ts->detect_failures = 0; ++ ts->retry_ms = 0; ++ } ++} ++ ++static void apple_tristar_source_phase(struct apple_tristar *ts, enum tristar_auto_phase old) ++{ ++ if (old != TRISTAR_AUTO_OFF && old != TRISTAR_AUTO_EMPTY && ++ old != ts->autop.phase && ++ (ts->autop.phase == TRISTAR_AUTO_DEVICE || ts->autop.phase == TRISTAR_AUTO_EMPTY)) { ++ apple_tristar_new_source(ts, "accessory-phase"); ++ tristar_auto_forget(&ts->autop); ++ } ++} ++ + static int apple_tristar_update(struct apple_tristar *ts) + { + struct usb_gadget_current_budget budget; +@@ -437,18 +697,12 @@ static int apple_tristar_update(struct apple_tristar *ts) + /* A transport failure and USB reset/suspend are not source removal. */ + if (!error && ts->source_present && !(value & 3)) { + ts->source_present = false; +- ts->attach_id++; +- ts->computer = false; +- ts->brick_sampled = false; +- ts->brick_ua = 0; +- ts->analog_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; +- ts->classify_after = jiffies + msecs_to_jiffies(1000); ++ apple_tristar_new_source(ts, "vbus-detach"); + ts->detach_role = true; + tristar_auto_forget(&ts->autop); + ts->autop.phase = TRISTAR_AUTO_EMPTY; + ts->autop.was_empty = true; + ts->autop.empty = 0; +- usb_gadget_current_source_detached(ts->usb_node); + } + if (!error && (value & 3) == 3) + ts->source_present = true; +@@ -460,17 +714,19 @@ static int apple_tristar_update(struct apple_tristar *ts) + accessory = !error && tristar_auto_supply(&ts->autop, &cfg, value, &auto_online, &auto_ua); + /* IC getVBUSDetect(strict=1): both low bits, never orientation bits. */ + online = accessory ? auto_online : !host && !error && (value & 3) == 3; +- if (ts->restore_pending) { +- int restore = apple_tristar_restore_route(ts); +- +- if (restore) +- error = restore; +- } ++ /* Route repair belongs to the role-serialized monitor, never a getter. */ ++ if (ts->restore_pending || ts->dcp_pending) ++ error = -EUCLEAN; + if (accessory && online && !error) { + /* An Accessory Charger Adapter: power in while the iPad is the host. */ + current_ua = auto_ua; + type = POWER_SUPPLY_USB_TYPE_ACA; + } else if (online && !error) { ++ /* Positive USB session evidence outranks every earlier analog result. */ ++ if (atomic_read(&ts->computer_seen)) { ++ ts->computer = true; ++ ts->brick_ua = 0; ++ } + if (usb_gadget_get_current_budget(ts->usb_node, &budget)) { + current_ua = apple_tristar_budget(NULL, 0, &type); + if (ts->computer) { +@@ -481,9 +737,8 @@ static int apple_tristar_update(struct apple_tristar *ts) + current_ua = ts->brick_ua; + } + } else { +- if ((budget.connected || budget.configured || budget.host_grant) && +- (!ts->brick_ua || +- ts->analog_type == POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID)) { ++ if (budget.connected || budget.configured || budget.host_grant) { ++ atomic_set(&ts->computer_seen, 1); + ts->computer = true; + ts->brick_ua = 0; + } +@@ -499,7 +754,8 @@ static int apple_tristar_update(struct apple_tristar *ts) + } + } + } +- changed = ts->error != error || ts->online != online; ++ changed = ts->error != error || ts->online != online || ts->epoch_notify; ++ ts->epoch_notify = false; + changed |= old_current != current_ua || old_type != type; + ts->error = error; + ts->online = online; +@@ -548,13 +804,16 @@ static void apple_tristar_poll(struct work_struct *work) + { + struct apple_tristar *ts = container_of(to_delayed_work(work), + struct apple_tristar, work); ++ int ret; + + apple_tristar_update(ts); + apple_tristar_auto_run(ts); +- if (ts->role_sw) +- apple_s8000_usb_role_monitor(ts->role_sw, apple_tristar_monitor, ts); +- else ++ ret = ts->role_sw ? ++ apple_s8000_usb_role_monitor(ts->role_sw, apple_tristar_monitor, ts) : + apple_tristar_monitor(ts); ++ mutex_lock(&ts->lock); ++ ts->monitor_error = ret; ++ mutex_unlock(&ts->lock); + apple_tristar_update(ts); + mutex_lock(&ts->lock); + if (!ts->stopping && !ts->sleeping) +@@ -570,8 +829,20 @@ static int apple_tristar_usb_event(struct notifier_block *nb, unsigned long even + if (budget->of_node != ts->usb_node || READ_ONCE(ts->stopping) || + READ_ONCE(ts->sleeping)) + return NOTIFY_DONE; +- /* Cancel an in-flight analog sample; never revoke an attach here. */ +- atomic_inc(&ts->reset_generation); ++ /* Idle budget/disconnect notifications are not USB data sessions. */ ++ WRITE_ONCE(ts->usb_event_source, event); ++ atomic_inc(&ts->usb_events); ++ if (budget->connected || budget->configured || budget->host_grant) { ++ atomic_set(&ts->usb_session, 1); ++ /* Retain a short session even if it ends before the next worker. */ ++ atomic_set(&ts->computer_seen, 1); ++ atomic_inc(&ts->reset_generation); ++ } else if (atomic_xchg(&ts->usb_session, 0)) { ++ /* A short real session ending between two samples still cancels. */ ++ atomic_inc(&ts->reset_generation); ++ } else { ++ atomic_inc(&ts->usb_idle_events); ++ } + mod_delayed_work(system_power_efficient_wq, &ts->work, 0); + return NOTIFY_OK; + } +@@ -589,13 +860,18 @@ static int apple_tristar_quiesce(struct apple_tristar *ts) + } + cancel_delayed_work_sync(&ts->work); + mutex_lock(&ts->lock); ++ ret = ts->restore_pending || ts->dcp_pending; ++ mutex_unlock(&ts->lock); ++ if (!ret) ++ return 0; + for (attempt = 0; attempt < 3; attempt++) { +- ret = apple_tristar_restore_route(ts); ++ ret = ts->role_sw ? ++ apple_s8000_usb_role_monitor(ts->role_sw, apple_tristar_monitor, ts) : ++ apple_tristar_monitor(ts); + if (!ret) + break; + msleep(20); + } +- mutex_unlock(&ts->lock); + if (ret) + dev_crit(&ts->client->dev, "Tristar route restore failed at quiesce: %d\n", ret); + return ret; +@@ -949,6 +1225,8 @@ apple_tristar_auto_act(struct apple_tristar *ts, const struct tristar_auto_cfg * + old = ta->phase; + had = ta->id_valid; + same = had && !ret && !memcmp(ta->id, id, sizeof(id)); ++ if (!ret) ++ apple_tristar_source_id(ts, id); + action = tristar_auto_probed(ta, cfg, ktime_to_ms(ktime_get_boottime()), ret, id); + if (!ret && !same) + apple_tristar_auto_note(ts, old, "digital ID %*ph%s", (int)sizeof(id), id, +@@ -1032,6 +1310,7 @@ static void apple_tristar_auto_run(struct apple_tristar *ts) + in.status = ts->status; + old = ts->autop.phase; + action = tristar_auto_step(&ts->autop, &cfg, &in); ++ apple_tristar_source_phase(ts, old); + apple_tristar_auto_note(ts, old, NULL); + mutex_unlock(&ts->lock); + /* A probe can lead to the handshake and then the role switch. */ +@@ -1309,6 +1588,8 @@ static const char *apple_tristar_usb_type_name(int type) + return "BrickID"; + case POWER_SUPPLY_USB_TYPE_ACA: + return "ACA"; ++ case POWER_SUPPLY_USB_TYPE_C: ++ return "C"; + default: + return "Unknown"; + } +@@ -1385,6 +1666,21 @@ static ssize_t lightning_state_show(struct device *dev, struct device_attribute + current_ua = ts->current_ua; + type = ts->usb_type; + error = ts->error; ++ len += sysfs_emit_at(buf, len, ++ "charger_detect: stage=%s pin=%s error=%d monitor_error=%d attempts=%u failures=%u retry_ms=%u poisoned=%d route=%d adc_status=%d dp_mv=%d dm_mv=%d cc_mv=%d sampled=%d poison_stage=%s poison_attach=%u\n", ++ ts->detect_stage ?: "none", ts->detect_pin ?: "none", ++ ts->detect_error, ts->monitor_error, ts->detect_attempts, ++ ts->detect_failures, ts->retry_ms, ts->adc_poisoned, ++ ts->route_before, ts->adc_status, ts->dp_mv, ts->dm_mv, ++ ts->cc_mv, ts->brick_sampled, ts->adc_fault_stage ?: "none", ++ ts->adc_fault_attach); ++ len += sysfs_emit_at(buf, len, ++ "source_epoch: id=%u reason=%s bc_ua=%d bc_type=%d cc_first_mv=%d usb_events=%d usb_idle_events=%d usb_generation=%d usb_event_source=%lu computer_seen=%d bc_primary_mv=%d bc_secondary_mv=%d\n", ++ ts->attach_id, ts->epoch_reason ?: "boot", ts->bc_ua, ts->bc_type, ++ ts->cc_first_mv, atomic_read(&ts->usb_events), ++ atomic_read(&ts->usb_idle_events), atomic_read(&ts->reset_generation), ++ READ_ONCE(ts->usb_event_source), atomic_read(&ts->computer_seen), ++ ts->bc_primary_mv, ts->bc_secondary_mv); + mutex_unlock(&ts->lock); + + len += sysfs_emit_at(buf, len, "auto_host: %d\nphase: %s\nrole: %s%s\n", +@@ -1501,7 +1797,8 @@ static const struct power_supply_desc apple_tristar_desc = { + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | +- BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA), ++ BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_ACA) | ++ BIT(POWER_SUPPLY_USB_TYPE_C), + .properties = apple_tristar_properties, + .num_properties = ARRAY_SIZE(apple_tristar_properties), + .get_property = apple_tristar_get_property, +@@ -1554,6 +1851,14 @@ static int apple_tristar_probe(struct i2c_client *client) + return -ENOMEM; + ts->client = client; + ts->status = -ENODATA; ++ ts->route_before = -ENODATA; ++ ts->adc_status = -ENODATA; ++ ts->dp_mv = -ENODATA; ++ ts->dm_mv = -ENODATA; ++ ts->cc_mv = -ENODATA; ++ ts->bc_primary_mv = -ENODATA; ++ ts->bc_secondary_mv = -ENODATA; ++ ts->detect_stage = "initial"; + tristar_auto_init(&ts->autop); + i2c_set_clientdata(client, ts); + node = of_parse_phandle(client->dev.of_node, "apple,usb-role-switch", 0); +@@ -1597,6 +1902,10 @@ static int apple_tristar_probe(struct i2c_client *client) + if (ret) + return ret; + atomic_set(&ts->reset_generation, 0); ++ atomic_set(&ts->usb_session, 0); ++ atomic_set(&ts->computer_seen, 0); ++ atomic_set(&ts->usb_events, 0); ++ atomic_set(&ts->usb_idle_events, 0); + /* Allow old iOS work to quiesce; no first-boot ADC idle bit is known. */ + ts->classify_after = jiffies + msecs_to_jiffies(2000); + ret = devm_mutex_init(&client->dev, &ts->lock); +diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c +index c8dde7242fb360e7dcf0b7ee34b9060a2c648345..ebb6193ae40f9d8cee56a22c1982d78e74a64c47 100644 +--- a/drivers/power/supply/bq27xxx_battery.c ++++ b/drivers/power/supply/bq27xxx_battery.c +@@ -2404,11 +2404,50 @@ static int bq27xxx_cached_optional_value(const struct bq27xxx_cached_value *cach + return bq27xxx_cached_value(cached, val) ? -ENODATA : 0; + } + ++/* A full flag describes capacity, not whether this pack is supplying power. */ ++static int bq27xxx_j71t_status(int current_ua, int online, int status) ++{ ++ if (current_ua < 0) ++ return POWER_SUPPLY_STATUS_DISCHARGING; ++ if (online < 0) ++ return online; ++ if (!online) ++ return POWER_SUPPLY_STATUS_NOT_CHARGING; ++ if (status == POWER_SUPPLY_STATUS_FULL) ++ return status; ++ return current_ua > 0 ? POWER_SUPPLY_STATUS_CHARGING : POWER_SUPPLY_STATUS_NOT_CHARGING; ++} ++ ++/* Never query another supply while holding the gauge mutex. No gauge writes. */ ++static int bq27xxx_battery_j71t_status(int current_ua, union power_supply_propval *val) ++{ ++ struct power_supply *source; ++ union power_supply_propval online; ++ int ret = -ENODEV; ++ ++ if (current_ua < 0) { ++ val->intval = POWER_SUPPLY_STATUS_DISCHARGING; ++ return 0; ++ } ++ source = power_supply_get_by_name("usb"); ++ if (source) { ++ ret = power_supply_get_property(source, POWER_SUPPLY_PROP_ONLINE, &online); ++ power_supply_put(source); ++ } ++ ret = bq27xxx_j71t_status(current_ua, ret ? ret : online.intval, val->intval); ++ if (ret < 0) ++ return ret; ++ val->intval = ret; ++ return 0; ++} ++ + static int bq27xxx_battery_get_cached_property(struct power_supply *psy, + struct bq27xxx_device_info *di, + enum power_supply_property psp, + union power_supply_propval *val) + { ++ union power_supply_propval battery_current; ++ bool j71t = of_device_is_compatible(di->dev->of_node, "apple,j71t-bq27540"); + int ret; + + mutex_lock(&di->lock); +@@ -2425,6 +2464,8 @@ static int bq27xxx_battery_get_cached_property(struct power_supply *psy, + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq27xxx_cached_value(&di->cache.status, val); ++ if (!ret && j71t) ++ ret = bq27xxx_cached_value(&di->cache.current_now, &battery_current); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq27xxx_cached_value(&di->cache.voltage, val); +@@ -2485,6 +2526,8 @@ static int bq27xxx_battery_get_cached_property(struct power_supply *psy, + + out_unlock: + mutex_unlock(&di->lock); ++ if (!ret && j71t && psp == POWER_SUPPLY_PROP_STATUS) ++ ret = bq27xxx_battery_j71t_status(battery_current.intval, val); + return ret; + } + +diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c +index 7d6d0e1661263cf627cc3bd30cfbf9e59b9e6572..7c49a9718c1f796fd3c4a636689da1c7b8992e0e 100644 +--- a/drivers/usb/gadget/udc/core.c ++++ b/drivers/usb/gadget/udc/core.c +@@ -96,7 +96,7 @@ static void gadget_current_snapshot(struct usb_gadget *gadget, + budget->current_ma = budget->connected && !budget->suspended ? budget_ma : 0; + } + +-static void gadget_current_notify(struct usb_gadget *gadget) ++static void gadget_current_notify(struct usb_gadget *gadget, unsigned long event) + { + struct usb_gadget_current_budget budget; + unsigned long flags; +@@ -106,7 +106,7 @@ static void gadget_current_notify(struct usb_gadget *gadget) + spin_lock_irqsave(&gadget->state_lock, flags); + gadget_current_snapshot(gadget, &budget); + spin_unlock_irqrestore(&gadget->state_lock, flags); +- atomic_notifier_call_chain(&gadget_current_notifier, 0, &budget); ++ atomic_notifier_call_chain(&gadget_current_notifier, event, &budget); + } + + /** +@@ -191,7 +191,7 @@ void usb_gadget_current_source_detached(const struct device_node *node) + gadget->current_bus_connected = false; + gadget->current_suspended = false; + spin_unlock_irqrestore(&gadget->state_lock, flags); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_DETACH); + } + mutex_unlock(&udc_lock); + } +@@ -210,7 +210,7 @@ static void gadget_current_set_base(struct usb_gadget *gadget, unsigned int ma) + gadget->current_host_grant = false; + } + spin_unlock_irqrestore(&gadget->state_lock, flags); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_BASE); + } + + /* Also used to revoke a grant when its ep0 status transfer fails. */ +@@ -224,7 +224,7 @@ void usb_gadget_revoke_current_grant(struct usb_gadget *gadget) + gadget->current_extra_ma = 0; + gadget->current_host_grant = false; + spin_unlock_irqrestore(&gadget->state_lock, flags); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_REVOKE); + } + EXPORT_SYMBOL_GPL(usb_gadget_revoke_current_grant); + +@@ -240,7 +240,7 @@ void usb_gadget_current_budget_suspend(struct usb_gadget *gadget, bool suspended + gadget->current_extra_ma = 0; + gadget->current_host_grant = false; + spin_unlock_irqrestore(&gadget->state_lock, flags); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_SUSPEND); + } + EXPORT_SYMBOL_GPL(usb_gadget_current_budget_suspend); + +@@ -264,7 +264,7 @@ int usb_gadget_handle_current_grant(struct usb_gadget *gadget, + } + spin_unlock_irqrestore(&gadget->state_lock, flags); + if (newly_connected) +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_SETUP); + if (ctrl->bRequest != 0x40 || + (ctrl->bRequestType != 0x40 && ctrl->bRequestType != 0xc0)) + return -EOPNOTSUPP; +@@ -286,7 +286,7 @@ int usb_gadget_handle_current_grant(struct usb_gadget *gadget, + dev_info_ratelimited(&gadget->dev, + "Apple host current grant: extra %d mA, suspend %d mA\n", + extra, suspend); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_GRANT); + return 0; + } + EXPORT_SYMBOL_GPL(usb_gadget_handle_current_grant); +@@ -1381,7 +1381,7 @@ void usb_gadget_set_state(struct usb_gadget *gadget, + schedule_work(&gadget->work); + spin_unlock_irqrestore(&gadget->state_lock, flags); + trace_usb_gadget_set_state(gadget, 0); +- gadget_current_notify(gadget); ++ gadget_current_notify(gadget, USB_GADGET_CURRENT_STATE); + } + EXPORT_SYMBOL_GPL(usb_gadget_set_state); + +diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h +index 9878fd7326afc14a94d55f94ed7e40dfbc061323..c10407ab435cf91ab40ea3fe6d5d008585fae1a2 100644 +--- a/include/linux/usb/gadget.h ++++ b/include/linux/usb/gadget.h +@@ -363,6 +363,17 @@ struct usb_gadget_ops { + struct device_node; + struct notifier_block; + ++/* Origin of an opted-in current-budget notification; no hardware operation. */ ++enum usb_gadget_current_event { ++ USB_GADGET_CURRENT_DETACH = 1, ++ USB_GADGET_CURRENT_BASE, ++ USB_GADGET_CURRENT_REVOKE, ++ USB_GADGET_CURRENT_SUSPEND, ++ USB_GADGET_CURRENT_SETUP, ++ USB_GADGET_CURRENT_GRANT, ++ USB_GADGET_CURRENT_STATE, ++}; ++ + /** + * struct usb_gadget_current_budget - instantaneous USB power authorization + * @of_node: controller node, valid for the synchronous callback or caller query diff --git a/pyproject.toml b/pyproject.toml index ab6b6219..19118b6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ package-dir = {orchard_hardware = "."} orchard_hardware = [ "devices/*/device.toml", "devices/*/firmware.json", "devices/*/public-firmware/*", "schemas/v1/*.json", "launcher/*.py", "runtime/j71b/*.py", "runtime/j71b/phase2-hardware", "runtime/j71b/j71b-*", + "runtime/j71t/phase2-hardware", "runtime/j71t/j71t-*", "devices/j71b/touch/runtime/touch-activate", "devices/j71b/touch/runtime/*/*.sh", "boot/initramfs/j71b/recovery/*.json", "boot/initramfs/j71b/recovery/Containerfile", "boot/initramfs/j71b/recovery/init", "boot/initramfs/j71b/recovery/usb-gadget", @@ -33,6 +34,7 @@ orchard_hardware = [ "boot/initramfs/j71b/recovery/capture-adt", "boot/initramfs/j71b/recovery/udhcpd.conf", "boot/initramfs/j71b/recovery/upstream.lock", "boot/initramfs/j71b/recovery/build-tools*", "boot/initramfs/j71b/recovery/install-toolchain", + "boot/initramfs/j71t/recovery/*.json", "boot/initramfs/j71t/recovery/init", "boot/initramfs/j71t/recovery/usb-gadget", "boot/initramfs/j71b/busybox/*.py", "boot/initramfs/j71b/busybox/Containerfile", "boot/quiet/*.py", "boot/quiet/*.patch", "boot/m1n1/build", "boot/m1n1/install-toolchain", "boot/m1n1/Containerfile", "boot/m1n1/upstream.lock", "boot/m1n1/patches/*/*.patch", diff --git a/runtime/j71b/device.py b/runtime/j71b/device.py index 6f9501e2..e19cb508 100644 --- a/runtime/j71b/device.py +++ b/runtime/j71b/device.py @@ -26,6 +26,14 @@ stop_diagnostics, DIAGNOSTICS_EXECUTABLE) PROTOCOL = 1 +PROFILE = identity.PROFILE +RUNTIME = Path(__file__).resolve().parent +# Kernel evidence that the boot-time storage fence is intact: (parameter, +# full-match pattern). J71t accepts any write window confined to namespace 1. +FENCES = {'j71b': ('/sys/module/nvme/parameters/j71b_ro_failed', 'N'), + 'j71t': ('/sys/module/nvme/parameters/write_window', '1:[0-9]+-[0-9]+')} +# Private firmware copied into J71b's staged touch activation roles. +ROLE_FIRMWARE = {'j71b': ('t8010-smartio.bin', 'j71b-fwstart.z2fw')} RUN = Path('/run/orchard') CONFIG = Path('/etc/orchard/release.json') FIRMWARE = Path('/etc/orchard/firmware.json') @@ -86,7 +94,8 @@ def no_faults(): text = command('dmesg').decode(errors='replace') if re.search(r'apple-dart.*translation fault', text): raise ValueError('DART fault; hardware cannot be reused in this recovery boot') - if Path('/sys/module/nvme/parameters/j71b_ro_failed').read_text().strip() != 'N': + parameter, expected = FENCES[PROFILE] + if not re.fullmatch(expected, Path(parameter).read_text().strip()): raise ValueError('Storage fence failed') @@ -122,7 +131,7 @@ def revoke_ssh(request): def status(_): device = identity.cached() - return {'release': config()['release'], 'profile': 'j71b', 'state': state(), + return {'release': config()['release'], 'profile': PROFILE, 'state': state(), 'mode': config().get('mode', 'installer'), 'ecid': device['ecid'] if device is not None else None, 'boot_id': ssh_access.BOOT_ID.read_text().strip(), @@ -287,7 +296,7 @@ def prepare(request): records = {m.name: bundle.extractfile(m).read() for m in members} metadata = json.loads(records.pop('mixin.json')) live_adt = command('/sbin/capture-adt') - if metadata['adt_sha256'] != hashlib.sha256(live_adt).hexdigest() or metadata['profile'] != 'j71b': + if metadata['adt_sha256'] != hashlib.sha256(live_adt).hexdigest() or metadata['profile'] != PROFILE: raise ValueError('Private mixin is not for this recovery boot') if set(records) != allowed or set(metadata['files']) != allowed: raise ValueError('Private mixin must contain exactly firmware data') @@ -306,17 +315,18 @@ def prepare(request): path.write_bytes(content) path.chmod(0o600) atomic(RUN / 'mixin.json', metadata) - for role in ('touch-start', 'touch-runtime'): - directory = Path('/usr/libexec/orchard-j71b') / role - for name in ('t8010-smartio.bin', 'j71b-fwstart.z2fw'): + names = ROLE_FIRMWARE.get(PROFILE, ()) + for role in ('touch-start', 'touch-runtime') if names else (): + directory = RUNTIME / role + for name in names: (directory / 'firmware').mkdir(exist_ok=True) shutil.copyfile('/lib/firmware/apple/' + name, directory / 'firmware' / name) # Public hash list is baked; append only private firmware hashes. text = (directory / 'PUBLIC.SHA256SUMS').read_text() text += ''.join(sha(directory / 'firmware' / name) + ' firmware/' + name + '\n' - for name in ('t8010-smartio.bin', 'j71b-fwstart.z2fw')) + for name in names) (directory / 'SHA256SUMS').write_text(text) - process = subprocess.Popen(['/usr/libexec/orchard-j71b/hardware'], + process = subprocess.Popen([str(RUNTIME / 'hardware')], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) for line in process.stdout: emit('progress', message=line.rstrip()) @@ -534,7 +544,7 @@ def install(request): or space.f_favail < image.get('members', 65536) + 1024): raise ValueError('Insufficient space or free inodes after deletion; replacement cannot be restored') temporary.mkdir(mode=0o700) - atomic(temporary / staging.MARKER, {'schema': 1, 'profile': 'j71b', 'status': 'incomplete', + atomic(temporary / staging.MARKER, {'schema': 1, 'profile': PROFILE, 'status': 'incomplete', 'partition': partition['id'], 'filesystem_uuid': partition['filesystem_uuid'], 'directory': name, 'release': release['release'], 'root_sha256': image['sha256']}) # Archive members cannot overwrite either ownership or operation state. diff --git a/runtime/j71b/identity.py b/runtime/j71b/identity.py index 9d2f5ecc..64f46a32 100644 --- a/runtime/j71b/identity.py +++ b/runtime/j71b/identity.py @@ -8,6 +8,8 @@ from adt import validate +# Installed as /usr/libexec/orchard-PROFILE; runtime/j71b in the source tree. +PROFILE = Path(__file__).resolve().parent.name.replace('orchard-', '', 1) RUN = Path('/run/orchard') BOOT_ID = Path('/proc/sys/kernel/random/boot_id') MTD = Path('/proc/mtd') @@ -22,7 +24,7 @@ def cached(): if not stat.S_ISREG(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o077: raise ValueError('Device identity record is not protected') result = json.loads(path.read_text()) - if (result.get('profile') != 'j71b' or type(result.get('ecid')) is not int or + if (result.get('profile') != PROFILE or type(result.get('ecid')) is not int or not 0 < result['ecid'] < 2**64 or result.get('boot_id') != str(uuid.UUID(BOOT_ID.read_text().strip()))): raise ValueError('Device identity record differs from this boot') @@ -45,7 +47,7 @@ def initialize(): blob = stream.read(size) if len(blob) != size: raise ValueError('Live ADT read was truncated') - result = {'profile': 'j71b', 'ecid': validate(blob), + result = {'profile': PROFILE, 'ecid': validate(blob), 'boot_id': str(uuid.UUID(BOOT_ID.read_text().strip()))} RUN.mkdir(mode=0o700, parents=True, exist_ok=True) if RUN.is_symlink(): diff --git a/runtime/j71b/staging.py b/runtime/j71b/staging.py index 0f91cbf0..e2e091b9 100644 --- a/runtime/j71b/staging.py +++ b/runtime/j71b/staging.py @@ -7,6 +7,8 @@ import shutil import stat +# Installed as /usr/libexec/orchard-PROFILE; runtime/j71b in the source tree. +PROFILE = Path(__file__).resolve().parent.name.replace('orchard-', '', 1) MARKER = '.orchard-staging.json' PATTERN = re.compile(r'\.([A-Za-z0-9][A-Za-z0-9._-]{0,63})\.install-[0-9a-f]{32}') @@ -43,7 +45,7 @@ def identify(path, partition, receipts, firmware_names=(), owner_uid=0): if not any(record.get('release') == r['release'] and record.get('root_sha256') == r['sha256'] for r in receipts): return None if modern: - if (record.get('profile') != 'j71b' or record.get('status') != 'incomplete' or + if (record.get('profile') != PROFILE or record.get('status') != 'incomplete' or record.get('directory') != match.group(1)): return None else: diff --git a/runtime/j71t/j71t-check-platform b/runtime/j71t/j71t-check-platform new file mode 100755 index 00000000..5b6a0ce7 --- /dev/null +++ b/runtime/j71t/j71t-check-platform @@ -0,0 +1,11 @@ +#!/bin/sh +# Verify the installed payload and the recovery activation receipt, read-only. +set -eu +[ "$(uname -r)" = @KERNEL_RELEASE@ ] +[ "$(tr '\000' '\n' < /proc/device-tree/compatible | head -1)" = apple,j71t ] +[ "$(awk '{print}' /sys/class/block/nvme0n1p1/ro)" = 1 ] +case "$(awk '{print}' /sys/module/nvme/parameters/write_window)" in 1:[0-9]*-[0-9]*) ;; *) exit 1 ;; esac +[ -e /run/ipadlinux/j71t-early-ready ] +[ -d /sys/module/apple_n1 ] && [ -d /sys/module/apple_j71t_charger ] +[ -d /sys/class/net/wlan0 ] && [ -d /sys/class/bluetooth/hci0 ] +sha256sum -c /usr/share/ipadlinux/hardware.SHA256SUMS >/dev/null diff --git a/runtime/j71t/phase2-hardware b/runtime/j71t/phase2-hardware new file mode 100644 index 00000000..2aab808d --- /dev/null +++ b/runtime/j71t/phase2-hardware @@ -0,0 +1,74 @@ +#!/bin/sh +# Public device-owned J71t hardware preparation. No association or filesystem mounts. +# The kernel holds the PCIe host and ANS until released here, after the private +# mixin has been written, and drivers that read private firmware at probe time +# are modules loaded below. +set -eu +phase=preflight +failed() { + rc=$? + [ "$rc" = 0 ] && return + printf 'Hardware failed: %s (exit %s); inspect, do not retry\n' "$phase" "$rc" >&2 + dmesg > /run/orchard/hardware-failed.dmesg 2>/dev/null || true + exit "$rc" +} +trap failed EXIT +step() { phase=$1; echo "$phase"; } +wait_for() { + n=0 + until eval "$2"; do n=$((n+1)); [ "$n" -le "$1" ]; sleep 1; done +} +no_faults() { + [ "$(dmesg | grep -c 'apple-dart.*translation fault' || true)" = 0 ] + [ "$(cat /sys/module/nvme/parameters/write_window)" = "$window" ] +} +window=$(cat /sys/module/nvme/parameters/write_window) +host_gate=/sys/module/pcie_apple_h9p/parameters/probe +ans_gate=/sys/module/nvme/parameters/apple_ans_probe +host=610000000.pcie +[ "$(awk '$2=="/" {print $3}' /proc/mounts)" = rootfs ] +[ "$(grep -c '/dev/nvme' /proc/mounts || true)" = 0 ] +step boot-readiness +# Recovery boot arguments set the window; it must be confined to namespace 1. +case "$window" in 1:[0-9]*-[0-9]*) ;; *) false ;; esac +[ "$(cat "$host_gate")" = N ] && [ "$(cat "$ans_gate")" = N ] +[ ! -e /sys/class/nvme/nvme0 ] +no_faults +[ "$(cat /sys/class/power_supply/battery/present)" = 1 ] +sha256sum -c /etc/orchard/public.SHA256SUMS > /run/orchard/public-verification.log +step wifi +echo Y > "$host_gate" +# A synchronous reprobe could block on link training; poll with a deadline. +[ -L "/sys/bus/platform/devices/$host/driver" ] || { echo "$host" > /sys/bus/platform/drivers_probe & } +wait_for 30 '[ -d /sys/class/net/wlan0 ]' +step storage-fence +echo Y > "$ans_gate" +for device in /sys/bus/pci/devices/*; do + [ -L "$device/driver" ] || [ "$(cat "$device/vendor")" != 0x106b ] && continue + case "$(cat "$device/class")" in 0x010802|0x018002) echo "${device##*/}" > /sys/bus/pci/drivers_probe & ;; esac +done +wait_for 60 '[ "$(cat /sys/class/nvme/nvme0/state 2>/dev/null)" = live ] && [ -e /sys/class/block/nvme0n1p1 ]' +no_faults +# Every partition starts read-only; the device API makes only the confirmed +# target writable while it formats or mounts it. +for part in /sys/class/block/nvme0n1p*; do + [ -d "$part" ]; blockdev --setro "/dev/${part##*/}"; [ "$(cat "$part/ro")" = 1 ] +done +for namespace in /sys/class/block/nvme0n[2-9]; do + [ ! -e "$namespace" ] || [ "$(cat "$namespace/ro")" = 1 ] +done +step touch +modprobe apple_n1 +wait_for 20 'grep -q "Apple N1 Touchscreen" /proc/bus/input/devices' +step bluetooth +modprobe hci_uart +wait_for 20 '[ -d /sys/class/bluetooth/hci0 ]' +[ "$(dmesg | grep -c 'BCM: No valid J71t device address' || true)" = 0 ] +step charging-policy +modprobe apple_j71t_charger +wait_for 10 '[ -d /sys/class/power_supply/charger ]' +[ "$(dmesg | grep -c 'CBAT unavailable' || true)" = 0 ] +no_faults +mkdir -p /run/ipadlinux +echo ready > /run/ipadlinux/j71t-early-ready +echo hardware-ready diff --git a/tests/test_device_profiles.py b/tests/test_device_profiles.py new file mode 100644 index 00000000..12d2406d --- /dev/null +++ b/tests/test_device_profiles.py @@ -0,0 +1,93 @@ +"""Device-profile selection in the public runtime producer and the shared device API.""" +import importlib.util +from pathlib import Path +import shutil +import sys +import tempfile +import types +import unittest +from unittest.mock import patch + +from orchard_hardware.image import runtime + +HARDWARE = Path(__file__).resolve().parents[1] +RELEASE = '7.3.0-orchard-test' +COMMON = {'archive.py', 'adt.py', 'device.py', 'handoff.py', 'hardware', 'identity.py', + 'image_policy.py', 'launch.py', 'ssh_access.py', 'staging.py', 'storage.py'} + + +class RuntimeProfiles(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + root = Path(temporary.name) + self.modules = root / 'kernel/lib/modules' + (self.modules / RELEASE / 'kernel').mkdir(parents=True) + for name in ('virt-dma', 'apple-sio-core', 'apple-sio-dma', 'j71b_dma_bindings', + 'j71b_sio_denied_test', 'j71b_spi_dma_reset', 'j71b_touch_start', 'j71b_z2'): + (self.modules / RELEASE / 'kernel' / (name + '.ko')).write_bytes(name.encode()) + (root / 'kernel/kernel.release').write_text(RELEASE + '\n') + self.policy = root / 'policy' + self.policy.mkdir() + (self.policy / 'image_policy.py').write_text('INIT = "/sbin/init"\n') + + def runtime_names(self, profile): + release, result = runtime.files(self.modules, self.policy, profile) + self.assertEqual(release, RELEASE) + prefix = 'usr/libexec/orchard-' + profile + '/' + wrapper = result['usr/bin/orchard-device'][0].decode() + self.assertEqual(wrapper, '#!/bin/sh\nexec /usr/bin/python3 /' + prefix + 'device.py "$@"\n') + self.assertFalse([name for name in result + if name.startswith('usr/libexec/orchard-') and not name.startswith(prefix)]) + return {name[len(prefix):] for name in result if name.startswith(prefix)}, result + + def test_j71b_layout_is_unchanged(self): + names, result = self.runtime_names('j71b') + roles = {name.split('/', 1)[0] for name in names if '/' in name} + self.assertEqual(roles, {'touch-start', 'touch-runtime'}) + self.assertEqual({name for name in names if '/' not in name}, COMMON | {'touch-activate'}) + self.assertIn('usr/libexec/j71b-check-platform', result) + self.assertIn('usr/libexec/j71b-charge-lease', result) + + def test_j71t_uses_shared_api_with_its_hardware_preparation(self): + names, result = self.runtime_names('j71t') + self.assertEqual(names, COMMON) + self.assertEqual(result['usr/libexec/orchard-j71t/hardware'][0], + (HARDWARE / 'runtime/j71t/phase2-hardware').read_bytes()) + self.assertEqual(result['usr/libexec/orchard-j71t/device.py'][0], + (HARDWARE / 'runtime/j71b/device.py').read_bytes()) + self.assertNotIn(b'@KERNEL_RELEASE@', result['usr/libexec/j71t-check-platform'][0]) + self.assertNotIn('usr/libexec/j71b-charge-lease', result) + self.assertIn('usr/lib/firmware/regulatory.db', result) + + def test_unknown_profile_is_refused(self): + with self.assertRaises(ValueError): + runtime.files(self.modules, self.policy, 'j99z') + + def test_device_api_profile_follows_install_directory(self): + with tempfile.TemporaryDirectory() as temporary: + directory = Path(temporary) / 'orchard-j71t' + directory.mkdir() + for name in ('identity.py', 'staging.py'): + shutil.copyfile(HARDWARE / 'runtime/j71b' / name, directory / name) + profiles = [] + for name in ('identity', 'staging'): + spec = importlib.util.spec_from_file_location('profile_' + name, directory / (name + '.py')) + module = importlib.util.module_from_spec(spec) + with patch.dict(sys.modules, {'adt': types.SimpleNamespace(validate=None)}): + spec.loader.exec_module(module) + profiles.append(module.PROFILE) + self.assertEqual(profiles, ['j71t', 'j71t']) + + def test_every_profile_has_recovery_and_storage_fence(self): + device = (HARDWARE / 'runtime/j71b/device.py').read_text() + for profile in runtime.PROFILES: + recovery = HARDWARE / 'boot/initramfs' / profile / 'recovery' + for name in ('init', 'usb-gadget', 'boot.json', 'boot-verbose.json'): + self.assertTrue((recovery / name).is_file(), profile + ': ' + name) + self.assertTrue((HARDWARE / 'runtime' / profile / 'phase2-hardware').is_file()) + self.assertIn("'" + profile + "': ('/sys/module/nvme/parameters/", device) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_j71t_data.py b/tests/test_j71t_data.py new file mode 100644 index 00000000..0982427b --- /dev/null +++ b/tests/test_j71t_data.py @@ -0,0 +1,113 @@ +"""J71t identity, firmware recipe and device-specific recovery/runtime files.""" +import hashlib +import json +from pathlib import Path, PurePosixPath +import re +try: + import tomllib +except ImportError: + import tomli as tomllib +import unittest +from urllib.parse import urlsplit + +HARDWARE = Path(__file__).resolve().parents[1] +DEVICE = HARDWARE / 'devices/j71t/device.toml' +RECIPE_SHA256 = '27fed6fe95cde773bd2d66d6b37003f8dcfb335db332a1a4c38e5f501db37ef6' +WINDOW = 'nvme.write_window=1:0-4294967295' + + +class J71tData(unittest.TestCase): + def setUp(self): + self.device = tomllib.loads(DEVICE.read_text()) + self.recipe_path = DEVICE.parent / self.device['firmware']['recipe'] + self.recipe = json.loads(self.recipe_path.read_text()) + + def test_firmware_recipe_integrity(self): + self.assertFalse(self.recipe_path.is_symlink()) + self.assertEqual(hashlib.sha256(self.recipe_path.read_bytes()).hexdigest(), RECIPE_SHA256) + + def test_device_identity_matches_recipe(self): + self.assertEqual(self.device['device']['profile'], 'j71t') + self.assertEqual(self.device['device']['apple_products'], [self.recipe['device']]) + self.assertEqual(self.device['device']['dt_compatible'], ['apple,j71t']) + # iPad6,11 is also J71s (S8000, chip 0x8000); only S8003 matches. + self.assertEqual(self.device['match']['dfu'], {'chip': 0x8003, 'board': 0x10}) + self.assertEqual(self.device['match']['linux']['product'], 'J71t Recovery') + self.assertEqual(self.device['boot']['methods'], ['checkm8-pongo']) + self.assertNotIn('launch_manifest', self.device['boot']) # No J71t launcher is published yet. + + def test_ipsw_is_pinned(self): + url = urlsplit(self.recipe['ipsw_url']) + self.assertEqual(url.scheme, 'https') + self.assertEqual(url.hostname, 'updates.cdn-apple.com') + self.assertEqual((self.recipe['version'], self.recipe['build']), ('16.7.10', '20H350')) + self.assertEqual(self.recipe['ipsw_bytes'], 5586431671) + self.assertRegex(self.recipe['ipsw_sha256'], r'^[0-9a-f]{64}$') + + def test_extraction_patterns_and_input_hashes(self): + self.assertEqual(self.recipe['extract_patterns'], ['C-4355__s-C0/', 'J71t[.]mtprops$']) + self.assertEqual([entry['name'] for entry in self.recipe['extracted_inputs']], ['J71t.mtprops']) + for entry in self.recipe['files'] + self.recipe['extracted_inputs']: + self.assertRegex(entry['sha256'], r'^[0-9a-f]{64}$') + for pattern in self.recipe['extract_patterns']: + re.compile(pattern) + + def test_wifi_names_follow_brcmfmac_board_fallbacks(self): + # brcmfmac tries apple,feara-MODULE-VENDOR-VERSION before apple,feara, + # so every NVRAM variant is installed and one firmware image serves all. + brcm = {entry['name']: PurePosixPath(entry['destination']).name for entry in self.recipe['files'] + if entry['destination'].startswith('lib/firmware/brcm/brcmfmac')} + self.assertEqual(brcm.pop('feara.trx'), 'brcmfmac4355-pcie.apple,feara.bin') + self.assertEqual(brcm.pop('feara.clmb'), 'brcmfmac4355-pcie.apple,feara.clm_blob') + self.assertEqual(brcm.pop('feara.txcb'), 'brcmfmac4355-pcie.apple,feara.txcap_blob') + self.assertEqual(len(brcm), 9) + for name, destination in brcm.items(): + module, vendor, version = re.fullmatch(r'P-feara_M-(\w+)_V-(\w)__m-([0-9.]+)\.txt', name).groups() + self.assertEqual(destination, f'brcmfmac4355-pcie.apple,feara-{module}-{vendor}-{version}.txt') + + def test_data_only_destinations_unique_and_contained(self): + entries = self.recipe['files'] + self.recipe['generated'] + self.assertEqual(len(entries), 20) + self.assertEqual(len({entry['name'] for entry in entries}), len(entries)) + self.assertEqual(len({entry['destination'] for entry in entries}), len(entries)) + for entry in entries: + path = PurePosixPath(entry['destination']) + self.assertFalse(path.is_absolute()) + self.assertNotIn('..', path.parts) + self.assertEqual(path.parts[:2], ('lib', 'firmware')) + self.assertEqual({entry['generator'] for entry in self.recipe['generated']}, {'j71t-adt'}) + self.assertEqual([entry['name'] for entry in self.recipe['generated']], + ['j71t-n1-calibration.bin', 'j71t-LSCI.bin', 'j71t-CBAT.bin', 'j71t-BMac.bin']) + + def test_recovery_identity_matches_device(self): + recovery = HARDWARE / 'boot/initramfs/j71t/recovery' + self.assertIn('= apple,j71t ]', (recovery / 'init').read_text()) + self.assertIn('/usr/libexec/orchard-j71t/handoff.py', (recovery / 'init').read_text()) + gadget = (recovery / 'usb-gadget').read_text() + self.assertIn("echo '" + self.device['match']['linux']['product'] + "'", gadget) + self.assertIn('echo 0x4142 > "$g/idProduct"', gadget) + for name in ('boot.json', 'boot-verbose.json'): + bootargs = json.loads((recovery / name).read_text())['bootargs'] + self.assertTrue(bootargs.startswith('chosen.bootargs=j71t.recovery=1 ')) + self.assertEqual(bootargs.split().count(WINDOW), 1) + self.assertLess(len(bootargs), 1000) + + def test_hardware_preparation_keeps_one_storage_window(self): + runtime = HARDWARE / 'runtime/j71t' + window = WINDOW.split('=', 1)[1] + phase2 = (runtime / 'phase2-hardware').read_text() + self.assertIn('case "$window" in 1:[0-9]*-[0-9]*) ;; *) false ;; esac', phase2) + self.assertIn('blockdev --setro', phase2) + self.assertNotIn('--setrw', phase2) + self.assertNotIn('mount ', phase2.replace('/proc/mounts', '')) + self.assertIn('write_window)" in 1:[0-9]*-[0-9]*)', (runtime / 'j71t-check-platform').read_text()) + device = (HARDWARE / 'runtime/j71b/device.py').read_text() + pattern = re.search(r"'j71t': \('/sys/module/nvme/parameters/write_window', '([^']+)'\)", device).group(1) + self.assertTrue(re.fullmatch(pattern, window)) + self.assertTrue(re.fullmatch(pattern, '1:3146240-7812095')) + for refused in ('', '2:0-4294967295', '1:0-', 'N'): + self.assertFalse(re.fullmatch(pattern, refused)) + + +if __name__ == '__main__': + unittest.main()