From 925dbf2ce21baa12ee4eed4808215d80e03051fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= Date: Fri, 13 Nov 2026 06:31:24 +0800 Subject: [PATCH 1/5] kernel: USB host through the Lightning camera adapter on the iPad 5 With Apple's Lightning camera adapter, the port turns into a USB host: the Tristar charger talks to the adapter over the Lightning ID bus, an S8000 USB role switch moves the port between device (USB serial and network to a computer) and host, and the AUSB PHY follows. USB sticks, SD readers, Ethernet adapters and USB audio work (J71t, tested by hand); class drivers for them are built on both platforms. From orchard PR #126 (commit 864b4971), reworked: the source behind the adapter is not identified, so host mode takes the 500 mA of any unidentified source instead of 2.1 A; host mode only follows the adapter (no manual sysfs selection, no 60 s timer); the iPad 5 device tree change moved to the iPad 5 layer. Boards without apple,usb-role-switch are unchanged. --- devices/d10/device.toml | 1 + devices/d101/device.toml | 1 + devices/d11/device.toml | 1 + devices/d111/device.toml | 1 + devices/d20/device.toml | 1 + devices/d201/device.toml | 1 + devices/d21/device.toml | 1 + devices/d211/device.toml | 1 + devices/d22/device.toml | 1 + devices/d221/device.toml | 1 + devices/j1/device.toml | 1 + devices/j120/device.toml | 1 + devices/j121/device.toml | 1 + devices/j127/device.toml | 1 + devices/j128/device.toml | 1 + devices/j171/device.toml | 1 + devices/j172/device.toml | 1 + devices/j207/device.toml | 1 + devices/j208/device.toml | 1 + devices/j71/device.toml | 1 + devices/j71s/device.toml | 1 + devices/j71t/device.toml | 1 + devices/j72s/device.toml | 1 + devices/j72t/device.toml | 1 + devices/j73/device.toml | 1 + devices/j81/device.toml | 1 + devices/j82/device.toml | 1 + devices/j85/device.toml | 1 + devices/j85m/device.toml | 1 + devices/j86/device.toml | 1 + devices/j86m/device.toml | 1 + devices/j87/device.toml | 1 + devices/j87m/device.toml | 1 + devices/j96/device.toml | 1 + devices/j97/device.toml | 1 + devices/j98a/device.toml | 1 + devices/j99a/device.toml | 1 + devices/n51/device.toml | 1 + devices/n53/device.toml | 1 + devices/n56/device.toml | 1 + devices/n61/device.toml | 1 + devices/n66/device.toml | 1 + devices/n66m/device.toml | 1 + devices/n69/device.toml | 1 + devices/n69u/device.toml | 1 + devices/n71/device.toml | 1 + devices/n71m/device.toml | 1 + devices/p105/device.toml | 1 + features.toml | 1 + kernel/configs/apple-16k/README.md | 13 + kernel/configs/apple-16k/config | 252 +++- kernel/configs/apple-4k/README.md | 6 + kernel/configs/apple-4k/config | 251 +++- ...s800-0-3-Add-the-USB-role-switch-and.patch | 75 + ...ipad5-Enable-USB-host-on-the-Lightni.patch | 53 + ...witch-the-PHY-between-host-and-devic.patch | 166 +++ ...r-supply-apple-tristar-charger-Add-t.patch | 47 + ...le_tristar_charger-Add-the-Lightning.patch | 701 +++++++++ ...le_tristar_charger-Host-mode-for-the.patch | 1311 +++++++++++++++++ ...-Add-the-Apple-S8000-USB-role-switch.patch | 165 +++ ...e-Apple-A9-A10-Lightning-port-role-s.patch | 1060 +++++++++++++ schemas/v1/device.schema.json | 9 + tools/catalog/feature-drivers.toml | 1 + 63 files changed, 4145 insertions(+), 14 deletions(-) create mode 100644 kernel/patches/devices/s8000/0011-arm64-dts-apple-s800-0-3-Add-the-USB-role-switch-and.patch create mode 100644 kernel/patches/devices/s8000/0012-arm64-dts-apple-ipad5-Enable-USB-host-on-the-Lightni.patch create mode 100644 kernel/patches/drivers/phy/0001-phy-apple-ausb-Switch-the-PHY-between-host-and-devic.patch create mode 100644 kernel/patches/drivers/power/0027-dt-bindings-power-supply-apple-tristar-charger-Add-t.patch create mode 100644 kernel/patches/drivers/power/0028-power-supply-apple_tristar_charger-Add-the-Lightning.patch create mode 100644 kernel/patches/drivers/power/0029-power-supply-apple_tristar_charger-Host-mode-for-the.patch create mode 100644 kernel/patches/drivers/usb/0011-dt-bindings-usb-Add-the-Apple-S8000-USB-role-switch.patch create mode 100644 kernel/patches/drivers/usb/0012-usb-roles-Add-the-Apple-A9-A10-Lightning-port-role-s.patch diff --git a/devices/d10/device.toml b/devices/d10/device.toml index 9c773e59..8ad77c6b 100644 --- a/devices/d10/device.toml +++ b/devices/d10/device.toml @@ -55,6 +55,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d101/device.toml b/devices/d101/device.toml index 45cf05f6..7939eb5a 100644 --- a/devices/d101/device.toml +++ b/devices/d101/device.toml @@ -55,6 +55,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d11/device.toml b/devices/d11/device.toml index 01540275..6cc832fd 100644 --- a/devices/d11/device.toml +++ b/devices/d11/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d111/device.toml b/devices/d111/device.toml index b025d023..c36eff4a 100644 --- a/devices/d111/device.toml +++ b/devices/d111/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d20/device.toml b/devices/d20/device.toml index b3cdca42..86246bfb 100644 --- a/devices/d20/device.toml +++ b/devices/d20/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d201/device.toml b/devices/d201/device.toml index f530c173..8c703c27 100644 --- a/devices/d201/device.toml +++ b/devices/d201/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d21/device.toml b/devices/d21/device.toml index 98ce032f..68e88375 100644 --- a/devices/d21/device.toml +++ b/devices/d21/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d211/device.toml b/devices/d211/device.toml index ad431385..60b2846e 100644 --- a/devices/d211/device.toml +++ b/devices/d211/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d22/device.toml b/devices/d22/device.toml index 05c9f6c4..9459357a 100644 --- a/devices/d22/device.toml +++ b/devices/d22/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/d221/device.toml b/devices/d221/device.toml index 99cfcde0..dda4cd87 100644 --- a/devices/d221/device.toml +++ b/devices/d221/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "working" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j1/device.toml b/devices/j1/device.toml index 55284f4c..f8173294 100644 --- a/devices/j1/device.toml +++ b/devices/j1/device.toml @@ -56,6 +56,7 @@ resume = "missing" # The P105 USB topic preserves legacy tuning; its shared gate helper needs a J1 regression boot. usb_serial = "untested" # Root shell on ttyGS0. usb_network = "untested" # CDC Ethernet, pinged both ways. +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j120/device.toml b/devices/j120/device.toml index e9ac0a90..a4b71078 100644 --- a/devices/j120/device.toml +++ b/devices/j120/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j121/device.toml b/devices/j121/device.toml index 0d0dee24..42b71fda 100644 --- a/devices/j121/device.toml +++ b/devices/j121/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j127/device.toml b/devices/j127/device.toml index f0d7fa6b..d06afce1 100644 --- a/devices/j127/device.toml +++ b/devices/j127/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j128/device.toml b/devices/j128/device.toml index e86b1a68..ea86f865 100644 --- a/devices/j128/device.toml +++ b/devices/j128/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j171/device.toml b/devices/j171/device.toml index e1283022..e317cf78 100644 --- a/devices/j171/device.toml +++ b/devices/j171/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "working" audio_input = "missing" camera = "missing" diff --git a/devices/j172/device.toml b/devices/j172/device.toml index c3e829ef..188f2d59 100644 --- a/devices/j172/device.toml +++ b/devices/j172/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "untested" audio_input = "missing" camera = "missing" diff --git a/devices/j207/device.toml b/devices/j207/device.toml index 1b3a1bf9..9b02fab7 100644 --- a/devices/j207/device.toml +++ b/devices/j207/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j208/device.toml b/devices/j208/device.toml index 0141c6bc..9543d0c5 100644 --- a/devices/j208/device.toml +++ b/devices/j208/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j71/device.toml b/devices/j71/device.toml index 461b7528..4dc28054 100644 --- a/devices/j71/device.toml +++ b/devices/j71/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j71s/device.toml b/devices/j71s/device.toml index 6b3368ed..5ada8f56 100644 --- a/devices/j71s/device.toml +++ b/devices/j71s/device.toml @@ -54,6 +54,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "untested" # The iPad 5 device tree J71t was tested with. audio_output = "untested" audio_input = "untested" camera = "missing" diff --git a/devices/j71t/device.toml b/devices/j71t/device.toml index 0808d450..bedf92b4 100644 --- a/devices/j71t/device.toml +++ b/devices/j71t/device.toml @@ -51,6 +51,7 @@ suspend = "untested" resume = "untested" usb_serial = "working" usb_network = "working" +usb_host = "working" audio_output = "working" audio_input = "untested" camera = "missing" diff --git a/devices/j72s/device.toml b/devices/j72s/device.toml index 97f9de0e..6dade8fb 100644 --- a/devices/j72s/device.toml +++ b/devices/j72s/device.toml @@ -54,6 +54,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "untested" # The iPad 5 device tree J71t was tested with. audio_output = "untested" audio_input = "untested" camera = "missing" diff --git a/devices/j72t/device.toml b/devices/j72t/device.toml index c30c0854..32b98b69 100644 --- a/devices/j72t/device.toml +++ b/devices/j72t/device.toml @@ -51,6 +51,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "untested" # The iPad 5 device tree J71t was tested with. audio_output = "untested" audio_input = "untested" camera = "missing" diff --git a/devices/j73/device.toml b/devices/j73/device.toml index 93881ff2..94469faf 100644 --- a/devices/j73/device.toml +++ b/devices/j73/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j81/device.toml b/devices/j81/device.toml index ba559d4c..da7029a6 100644 --- a/devices/j81/device.toml +++ b/devices/j81/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "working" usb_network = "working" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j82/device.toml b/devices/j82/device.toml index 4f8494dc..1aa334c2 100644 --- a/devices/j82/device.toml +++ b/devices/j82/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j85/device.toml b/devices/j85/device.toml index d11e8ef1..14fece48 100644 --- a/devices/j85/device.toml +++ b/devices/j85/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j85m/device.toml b/devices/j85m/device.toml index 64ce961f..17f44be4 100644 --- a/devices/j85m/device.toml +++ b/devices/j85m/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j86/device.toml b/devices/j86/device.toml index 688b2a1a..aad86df8 100644 --- a/devices/j86/device.toml +++ b/devices/j86/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j86m/device.toml b/devices/j86m/device.toml index 6ab531bc..38cfec08 100644 --- a/devices/j86m/device.toml +++ b/devices/j86m/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j87/device.toml b/devices/j87/device.toml index ce91ec6a..50551168 100644 --- a/devices/j87/device.toml +++ b/devices/j87/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j87m/device.toml b/devices/j87m/device.toml index 35f57773..ba3c757c 100644 --- a/devices/j87m/device.toml +++ b/devices/j87m/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j96/device.toml b/devices/j96/device.toml index f4d6cf26..d480d775 100644 --- a/devices/j96/device.toml +++ b/devices/j96/device.toml @@ -55,6 +55,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j97/device.toml b/devices/j97/device.toml index 52984e73..85511d9e 100644 --- a/devices/j97/device.toml +++ b/devices/j97/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j98a/device.toml b/devices/j98a/device.toml index 6dc027e0..fd896119 100644 --- a/devices/j98a/device.toml +++ b/devices/j98a/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/j99a/device.toml b/devices/j99a/device.toml index 2048f84c..d6ffe552 100644 --- a/devices/j99a/device.toml +++ b/devices/j99a/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n51/device.toml b/devices/n51/device.toml index 08808d5a..65697614 100644 --- a/devices/n51/device.toml +++ b/devices/n51/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n53/device.toml b/devices/n53/device.toml index 0e0e32cb..cd0c9854 100644 --- a/devices/n53/device.toml +++ b/devices/n53/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n56/device.toml b/devices/n56/device.toml index 2818de3e..6fc414ad 100644 --- a/devices/n56/device.toml +++ b/devices/n56/device.toml @@ -53,6 +53,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n61/device.toml b/devices/n61/device.toml index e528c490..2eca9e42 100644 --- a/devices/n61/device.toml +++ b/devices/n61/device.toml @@ -51,6 +51,7 @@ suspend = "untested" resume = "untested" usb_serial = "working" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n66/device.toml b/devices/n66/device.toml index 52c91ccf..2b96e047 100644 --- a/devices/n66/device.toml +++ b/devices/n66/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n66m/device.toml b/devices/n66m/device.toml index 53e3a7e5..16d8fd63 100644 --- a/devices/n66m/device.toml +++ b/devices/n66m/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n69/device.toml b/devices/n69/device.toml index 0ff2e9e2..127f326d 100644 --- a/devices/n69/device.toml +++ b/devices/n69/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n69u/device.toml b/devices/n69u/device.toml index 64859dbd..b6cc2851 100644 --- a/devices/n69u/device.toml +++ b/devices/n69u/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n71/device.toml b/devices/n71/device.toml index b1e8ce21..1ab256a8 100644 --- a/devices/n71/device.toml +++ b/devices/n71/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/n71m/device.toml b/devices/n71m/device.toml index 1113bd86..343de746 100644 --- a/devices/n71m/device.toml +++ b/devices/n71m/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "missing" audio_input = "missing" camera = "missing" diff --git a/devices/p105/device.toml b/devices/p105/device.toml index c9908c6d..1e8f94be 100644 --- a/devices/p105/device.toml +++ b/devices/p105/device.toml @@ -63,6 +63,7 @@ resume = "missing" # See docs/bring-up/p105-usb-handoff.md for observations and software checks. usb_serial = "untested" usb_network = "untested" +usb_host = "missing" audio_output = "untested" audio_input = "untested" camera = "missing" diff --git a/features.toml b/features.toml index 98fc2086..220ec1d7 100644 --- a/features.toml +++ b/features.toml @@ -28,6 +28,7 @@ suspend = "Suspend" resume = "Resume" usb_serial = "USB serial" usb_network = "USB network" +usb_host = "USB host" audio_output = "Speakers" audio_input = "Microphone" camera = "Cameras" diff --git a/kernel/configs/apple-16k/README.md b/kernel/configs/apple-16k/README.md index 8cdcc06d..1bfe865f 100644 --- a/kernel/configs/apple-16k/README.md +++ b/kernel/configs/apple-16k/README.md @@ -72,3 +72,16 @@ To change configuration, use Kconfig tooling such as `menuconfig` or `scripts/config`, resolve it with `olddefconfig`, and review the resulting diff before updating the committed file. Preserve the pinned source/toolchain when checking reproducibility. + +## USB host + +The Lightning to USB camera adapter makes the A9 and A10 iPads a USB host: +`USB_ROLES_APPLE_S8000=y` (the port's role switch) and +`APPLE_TRISTAR_ACCESSORY=y` (the charger's accessory policy); EHCI and OHCI +were already built in. Devices behind the adapter need their class drivers, +built as modules: USB mass storage and UAS with SCSI disk support, exFAT +with UTF-8 names (`EXFAT_DEFAULT_IOCHARSET="utf8"`, `NLS_UTF8`), USB +Ethernet (`USB_RTL8152`, `USB_NET_AX88179_178A`, `USB_NET_AX8817X`, +`USB_NET_CDCETHER`, `USB_NET_CDC_NCM`, `USB_NET_CDC_EEM`, +`USB_NET_RNDIS_HOST`) and USB audio (`SND_USB_AUDIO`). Kconfig selects the +PHY, MII and MDIO helpers they need. diff --git a/kernel/configs/apple-16k/config b/kernel/configs/apple-16k/config index f11529b5..7b7cc31d 100644 --- a/kernel/configs/apple-16k/config +++ b/kernel/configs/apple-16k/config @@ -837,6 +837,7 @@ CONFIG_MODPROBE_PATH="/sbin/modprobe" CONFIG_MODULES_TREE_LOOKUP=y CONFIG_BLOCK=y CONFIG_BLOCK_LEGACY_AUTOLOAD=y +CONFIG_BLK_DEV_BSG_COMMON=m # CONFIG_BLK_DEV_BSGLIB is not set # CONFIG_BLK_DEV_INTEGRITY is not set CONFIG_BLK_DEV_WRITE_MOUNTED=y @@ -1446,6 +1447,7 @@ CONFIG_RFKILL=m # CONFIG_LWTUNNEL is not set CONFIG_DST_CACHE=y CONFIG_GRO_CELLS=y +CONFIG_NET_SELFTESTS=m CONFIG_NET_SOCK_MSG=y CONFIG_PAGE_POOL=y # CONFIG_PAGE_POOL_STATS is not set @@ -1886,9 +1888,87 @@ CONFIG_NVME_APPLE=m # # SCSI device support # -CONFIG_SCSI_MOD=y +CONFIG_SCSI_MOD=m # CONFIG_RAID_ATTRS is not set -# CONFIG_SCSI is not set +CONFIG_SCSI_COMMON=m +CONFIG_SCSI=m +CONFIG_SCSI_DMA=y +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=m +# 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 @@ -1934,12 +2014,14 @@ CONFIG_DM_CRYPT=y # end of IEEE 1394 (FireWire) support CONFIG_NETDEVICES=y +CONFIG_MII=m CONFIG_NET_CORE=y # CONFIG_BONDING is not set # CONFIG_DUMMY is not set # 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 is not set @@ -1964,9 +2046,101 @@ CONFIG_TUN=y # CONFIG_ARCNET is not set # CONFIG_ETHERNET is not set # CONFIG_FDDI is not set -# CONFIG_PHYLIB is not set +CONFIG_PHYLINK=m +CONFIG_PHYLIB=m +CONFIG_SWPHY=y +CONFIG_FIXED_PHY=m +# CONFIG_SFP is not set + +# +# MII PHY device drivers +# +# CONFIG_AS21XXX_PHY is not set +# CONFIG_AIR_AN8801_PHY is not set +# CONFIG_AIR_EN8811H_PHY is not set +# CONFIG_AMD_PHY is not set +# CONFIG_ADIN_PHY is not set +# CONFIG_ADIN1100_PHY is not set +# CONFIG_ADIN1140_PHY is not set +# CONFIG_AQUANTIA_PHY is not set +CONFIG_AX88796B_PHY=m +# CONFIG_BROADCOM_PHY is not set +# CONFIG_BCM54140_PHY is not set +# CONFIG_BCM7XXX_PHY is not set +# CONFIG_BCM84881_PHY is not set +# CONFIG_BCM87XX_PHY is not set +# CONFIG_CICADA_PHY is not set +# CONFIG_CORTINA_PHY is not set +# CONFIG_DAP8211R_PHY is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_ICPLUS_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_INTEL_XWAY_PHY is not set +# CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_MARVELL_PHY is not set +# CONFIG_MARVELL_10G_PHY is not set +# CONFIG_MARVELL_88Q2XXX_PHY is not set +# CONFIG_MARVELL_88X2222_PHY is not set +# CONFIG_MAXLINEAR_GPHY is not set +# CONFIG_MAXLINEAR_86110_PHY is not set +# CONFIG_MEDIATEK_GE_PHY is not set +# CONFIG_MICREL_PHY is not set +# CONFIG_MICROCHIP_T1S_PHY is not set +# CONFIG_MICROCHIP_PHY is not set +# CONFIG_MICROCHIP_T1_PHY is not set +# CONFIG_MICROSEMI_PHY is not set +# CONFIG_MOTORCOMM_PHY is not set +# CONFIG_NATIONAL_PHY is not set +# CONFIG_NXP_CBTX_PHY is not set +# CONFIG_NXP_C45_TJA11XX_PHY is not set +# CONFIG_NXP_TJA11XX_PHY is not set +# CONFIG_NCN26000_PHY is not set +# CONFIG_AT803X_PHY is not set +# CONFIG_QCA83XX_PHY is not set +# CONFIG_QCA808X_PHY is not set +# CONFIG_QCA807X_PHY is not set +# CONFIG_QSEMI_PHY is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_RENESAS_PHY is not set +# CONFIG_ROCKCHIP_PHY is not set +# CONFIG_SMSC_PHY is not set +# CONFIG_STE10XP is not set +# CONFIG_TERANETICS_PHY is not set +# CONFIG_DP83822_PHY is not set +# CONFIG_DP83TC811_PHY is not set +# CONFIG_DP83848_PHY is not set +# CONFIG_DP83867_PHY is not set +# CONFIG_DP83869_PHY is not set +# CONFIG_DP83TD510_PHY is not set +# CONFIG_DP83TG720_PHY is not set +# CONFIG_VITESSE_PHY is not set +# CONFIG_XILINX_GMII2RGMII is not set # CONFIG_PSE_CONTROLLER is not set +# +# MDIO controller drivers +# +CONFIG_FWNODE_MDIO=m +CONFIG_OF_MDIO=m +# CONFIG_MDIO_BITBANG is not set +# CONFIG_MDIO_BCM_UNIMAC is not set +# CONFIG_MDIO_HISI_FEMAC is not set +# CONFIG_MDIO_MVUSB is not set +# CONFIG_MDIO_MSCC_MIIM is not set +# CONFIG_MDIO_OCTEON is not set +# CONFIG_MDIO_IPQ4019 is not set +# CONFIG_MDIO_IPQ8064 is not set +# CONFIG_MDIO_THUNDER is not set + +# +# MDIO Multiplexers +# +# CONFIG_MDIO_BUS_MUX_GPIO is not set +# CONFIG_MDIO_BUS_MUX_MULTIPLEXER is not set +# CONFIG_MDIO_BUS_MUX_MMIOREG is not set +# end of MDIO Multiplexers +# end of MDIO controller drivers + # # PCS device drivers # @@ -1975,7 +2149,44 @@ CONFIG_TUN=y # CONFIG_PPP is not set # CONFIG_SLIP is not set -# CONFIG_USB_NET_DRIVERS is not set +CONFIG_USB_NET_DRIVERS=y +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_RTL8152=m +# CONFIG_USB_LAN78XX is not set +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +# CONFIG_USB_NET_HUAWEI_CDC_NCM is not set +# CONFIG_USB_NET_CDC_MBIM is not set +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SR9700 is not set +# CONFIG_USB_NET_SR9800 is not set +# CONFIG_USB_NET_SMSC75XX is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +CONFIG_USB_NET_RNDIS_HOST=m +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_USB_NET_CX82310_ETH is not set +# CONFIG_USB_NET_KALMIA is not set +# CONFIG_USB_NET_QMI_WWAN is not set +# CONFIG_USB_HSO is not set +# CONFIG_USB_NET_INT51X1 is not set +# CONFIG_USB_IPHETH is not set +# CONFIG_USB_SIERRA_NET is not set +# CONFIG_USB_VL600 is not set +# CONFIG_USB_NET_CH9200 is not set +# CONFIG_USB_NET_AQC111 is not set +CONFIG_USB_RTL8153_ECM=m CONFIG_WLAN=y CONFIG_WLAN_VENDOR_ADMTEK=y # CONFIG_ADM8211 is not set @@ -2696,6 +2907,7 @@ CONFIG_BATTERY_BQ27XXX_HDQ=y CONFIG_APPLE_BELLATRIX_POWER=y CONFIG_APPLE_D2365_CHARGER=y CONFIG_APPLE_TRISTAR_CHARGER=y +CONFIG_APPLE_TRISTAR_ACCESSORY=y # CONFIG_BATTERY_MAX17040 is not set # CONFIG_BATTERY_MAX17042 is not set # CONFIG_BATTERY_MAX1720X is not set @@ -4051,6 +4263,8 @@ CONFIG_SND=m CONFIG_SND_TIMER=m CONFIG_SND_PCM=m CONFIG_SND_DMAENGINE_PCM=m +CONFIG_SND_HWDEP=m +CONFIG_SND_RAWMIDI=m CONFIG_SND_JACK=y CONFIG_SND_JACK_INPUT_DEV=y # CONFIG_SND_OSSEMUL is not set @@ -4146,7 +4360,9 @@ CONFIG_SND_PCI=y CONFIG_SND_SPI=y CONFIG_SND_USB=y -# CONFIG_SND_USB_AUDIO is not set +CONFIG_SND_USB_AUDIO=m +# CONFIG_SND_USB_AUDIO_MIDI_V2 is not set +CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER=y # CONFIG_SND_USB_UA101 is not set # CONFIG_SND_USB_CAIAQ is not set # CONFIG_SND_USB_6FIRE is not set @@ -4158,6 +4374,7 @@ CONFIG_SND_USB=y # CONFIG_SND_USB_VARIAX is not set CONFIG_SND_SOC=m CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y +# CONFIG_SND_SOC_USB is not set # # Analog Devices @@ -4729,11 +4946,28 @@ CONFIG_USB_OHCI_HCD_PLATFORM=y # # NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info # +CONFIG_USB_STORAGE=m +# 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=m # # USB Imaging devices # # CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set # CONFIG_USBIP_CORE is not set # @@ -4888,7 +5122,9 @@ CONFIG_USB_G_MULTI_CDC=y # 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 is not set # CONFIG_ACCESSIBILITY is not set @@ -6157,7 +6393,8 @@ 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 is not set +CONFIG_EXFAT_FS=m +CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" # CONFIG_NTFS_FS is not set # CONFIG_NTFS3_FS is not set # end of DOS/FAT/EXFAT/NT Filesystems @@ -6310,7 +6547,7 @@ CONFIG_NLS_ISO8859_1=y # CONFIG_NLS_MAC_INUIT is not set # CONFIG_NLS_MAC_ROMANIAN is not set # CONFIG_NLS_MAC_TURKISH is not set -# CONFIG_NLS_UTF8 is not set +CONFIG_NLS_UTF8=m # CONFIG_DLM is not set # CONFIG_UNICODE is not set CONFIG_IO_WQ=y @@ -6732,6 +6969,7 @@ 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 diff --git a/kernel/configs/apple-4k/README.md b/kernel/configs/apple-4k/README.md index 38208c81..d247e6f6 100644 --- a/kernel/configs/apple-4k/README.md +++ b/kernel/configs/apple-4k/README.md @@ -12,6 +12,12 @@ function as modules. RAM and recovery images can bind their configfs gadget at startup; users of the legacy composite gadget can load it when needed. FUSE remains a module. +The USB host class drivers (storage, UAS, exFAT, USB Ethernet, USB audio) +are the same as in `apple-16k`. The A9/A10 Lightning role switch and the +charger's accessory policy (`USB_ROLES_APPLE_S8000`, +`APPLE_TRISTAR_ACCESSORY`) are not built here, like the other drivers for +A9/A10-only hardware. + ## How the file is generated From `../apple-16k/config`, with `CONFIG_ARM64_4K_PAGES=y` and diff --git a/kernel/configs/apple-4k/config b/kernel/configs/apple-4k/config index 59f9f76c..8b9e07ac 100644 --- a/kernel/configs/apple-4k/config +++ b/kernel/configs/apple-4k/config @@ -836,6 +836,7 @@ CONFIG_MODPROBE_PATH="/sbin/modprobe" CONFIG_MODULES_TREE_LOOKUP=y CONFIG_BLOCK=y CONFIG_BLOCK_LEGACY_AUTOLOAD=y +CONFIG_BLK_DEV_BSG_COMMON=m # CONFIG_BLK_DEV_BSGLIB is not set # CONFIG_BLK_DEV_INTEGRITY is not set CONFIG_BLK_DEV_WRITE_MOUNTED=y @@ -1447,6 +1448,7 @@ CONFIG_RFKILL=m # CONFIG_LWTUNNEL is not set CONFIG_DST_CACHE=y CONFIG_GRO_CELLS=y +CONFIG_NET_SELFTESTS=m CONFIG_NET_SOCK_MSG=y CONFIG_PAGE_POOL=y # CONFIG_PAGE_POOL_STATS is not set @@ -1887,9 +1889,87 @@ CONFIG_BLK_DEV_NVME=m # # SCSI device support # -CONFIG_SCSI_MOD=y +CONFIG_SCSI_MOD=m # CONFIG_RAID_ATTRS is not set -# CONFIG_SCSI is not set +CONFIG_SCSI_COMMON=m +CONFIG_SCSI=m +CONFIG_SCSI_DMA=y +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=m +# 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 @@ -1935,12 +2015,14 @@ CONFIG_DM_CRYPT=y # end of IEEE 1394 (FireWire) support CONFIG_NETDEVICES=y +CONFIG_MII=m CONFIG_NET_CORE=y # CONFIG_BONDING is not set # CONFIG_DUMMY is not set # 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 is not set @@ -1965,9 +2047,101 @@ CONFIG_TUN=y # CONFIG_ARCNET is not set # CONFIG_ETHERNET is not set # CONFIG_FDDI is not set -# CONFIG_PHYLIB is not set +CONFIG_PHYLINK=m +CONFIG_PHYLIB=m +CONFIG_SWPHY=y +CONFIG_FIXED_PHY=m +# CONFIG_SFP is not set + +# +# MII PHY device drivers +# +# CONFIG_AS21XXX_PHY is not set +# CONFIG_AIR_AN8801_PHY is not set +# CONFIG_AIR_EN8811H_PHY is not set +# CONFIG_AMD_PHY is not set +# CONFIG_ADIN_PHY is not set +# CONFIG_ADIN1100_PHY is not set +# CONFIG_ADIN1140_PHY is not set +# CONFIG_AQUANTIA_PHY is not set +CONFIG_AX88796B_PHY=m +# CONFIG_BROADCOM_PHY is not set +# CONFIG_BCM54140_PHY is not set +# CONFIG_BCM7XXX_PHY is not set +# CONFIG_BCM84881_PHY is not set +# CONFIG_BCM87XX_PHY is not set +# CONFIG_CICADA_PHY is not set +# CONFIG_CORTINA_PHY is not set +# CONFIG_DAP8211R_PHY is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_ICPLUS_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_INTEL_XWAY_PHY is not set +# CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_MARVELL_PHY is not set +# CONFIG_MARVELL_10G_PHY is not set +# CONFIG_MARVELL_88Q2XXX_PHY is not set +# CONFIG_MARVELL_88X2222_PHY is not set +# CONFIG_MAXLINEAR_GPHY is not set +# CONFIG_MAXLINEAR_86110_PHY is not set +# CONFIG_MEDIATEK_GE_PHY is not set +# CONFIG_MICREL_PHY is not set +# CONFIG_MICROCHIP_T1S_PHY is not set +# CONFIG_MICROCHIP_PHY is not set +# CONFIG_MICROCHIP_T1_PHY is not set +# CONFIG_MICROSEMI_PHY is not set +# CONFIG_MOTORCOMM_PHY is not set +# CONFIG_NATIONAL_PHY is not set +# CONFIG_NXP_CBTX_PHY is not set +# CONFIG_NXP_C45_TJA11XX_PHY is not set +# CONFIG_NXP_TJA11XX_PHY is not set +# CONFIG_NCN26000_PHY is not set +# CONFIG_AT803X_PHY is not set +# CONFIG_QCA83XX_PHY is not set +# CONFIG_QCA808X_PHY is not set +# CONFIG_QCA807X_PHY is not set +# CONFIG_QSEMI_PHY is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_RENESAS_PHY is not set +# CONFIG_ROCKCHIP_PHY is not set +# CONFIG_SMSC_PHY is not set +# CONFIG_STE10XP is not set +# CONFIG_TERANETICS_PHY is not set +# CONFIG_DP83822_PHY is not set +# CONFIG_DP83TC811_PHY is not set +# CONFIG_DP83848_PHY is not set +# CONFIG_DP83867_PHY is not set +# CONFIG_DP83869_PHY is not set +# CONFIG_DP83TD510_PHY is not set +# CONFIG_DP83TG720_PHY is not set +# CONFIG_VITESSE_PHY is not set +# CONFIG_XILINX_GMII2RGMII is not set # CONFIG_PSE_CONTROLLER is not set +# +# MDIO controller drivers +# +CONFIG_FWNODE_MDIO=m +CONFIG_OF_MDIO=m +# CONFIG_MDIO_BITBANG is not set +# CONFIG_MDIO_BCM_UNIMAC is not set +# CONFIG_MDIO_HISI_FEMAC is not set +# CONFIG_MDIO_MVUSB is not set +# CONFIG_MDIO_MSCC_MIIM is not set +# CONFIG_MDIO_OCTEON is not set +# CONFIG_MDIO_IPQ4019 is not set +# CONFIG_MDIO_IPQ8064 is not set +# CONFIG_MDIO_THUNDER is not set + +# +# MDIO Multiplexers +# +# CONFIG_MDIO_BUS_MUX_GPIO is not set +# CONFIG_MDIO_BUS_MUX_MULTIPLEXER is not set +# CONFIG_MDIO_BUS_MUX_MMIOREG is not set +# end of MDIO Multiplexers +# end of MDIO controller drivers + # # PCS device drivers # @@ -1976,7 +2150,44 @@ CONFIG_TUN=y # CONFIG_PPP is not set # CONFIG_SLIP is not set -# CONFIG_USB_NET_DRIVERS is not set +CONFIG_USB_NET_DRIVERS=y +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_RTL8152=m +# CONFIG_USB_LAN78XX is not set +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +# CONFIG_USB_NET_HUAWEI_CDC_NCM is not set +# CONFIG_USB_NET_CDC_MBIM is not set +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SR9700 is not set +# CONFIG_USB_NET_SR9800 is not set +# CONFIG_USB_NET_SMSC75XX is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +CONFIG_USB_NET_RNDIS_HOST=m +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_USB_NET_CX82310_ETH is not set +# CONFIG_USB_NET_KALMIA is not set +# CONFIG_USB_NET_QMI_WWAN is not set +# CONFIG_USB_HSO is not set +# CONFIG_USB_NET_INT51X1 is not set +# CONFIG_USB_IPHETH is not set +# CONFIG_USB_SIERRA_NET is not set +# CONFIG_USB_VL600 is not set +# CONFIG_USB_NET_CH9200 is not set +# CONFIG_USB_NET_AQC111 is not set +CONFIG_USB_RTL8153_ECM=m CONFIG_WLAN=y CONFIG_WLAN_VENDOR_ADMTEK=y # CONFIG_ADM8211 is not set @@ -4053,6 +4264,8 @@ CONFIG_SND=m CONFIG_SND_TIMER=m CONFIG_SND_PCM=m CONFIG_SND_DMAENGINE_PCM=m +CONFIG_SND_HWDEP=m +CONFIG_SND_RAWMIDI=m CONFIG_SND_JACK=y CONFIG_SND_JACK_INPUT_DEV=y # CONFIG_SND_OSSEMUL is not set @@ -4148,7 +4361,9 @@ CONFIG_SND_PCI=y CONFIG_SND_SPI=y CONFIG_SND_USB=y -# CONFIG_SND_USB_AUDIO is not set +CONFIG_SND_USB_AUDIO=m +# CONFIG_SND_USB_AUDIO_MIDI_V2 is not set +CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER=y # CONFIG_SND_USB_UA101 is not set # CONFIG_SND_USB_CAIAQ is not set # CONFIG_SND_USB_6FIRE is not set @@ -4160,6 +4375,7 @@ CONFIG_SND_USB=y # CONFIG_SND_USB_VARIAX is not set CONFIG_SND_SOC=m CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y +# CONFIG_SND_SOC_USB is not set # # Analog Devices @@ -4731,11 +4947,28 @@ CONFIG_USB_OHCI_HCD_PLATFORM=y # # NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info # +CONFIG_USB_STORAGE=m +# 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=m # # USB Imaging devices # # CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set # CONFIG_USBIP_CORE is not set # @@ -4890,7 +5123,9 @@ CONFIG_USB_G_MULTI_CDC=y # CONFIG_TYPEC is not set CONFIG_USB_ROLE_SWITCH=y +# CONFIG_USB_ROLES_APPLE_S8000 is not set # CONFIG_MMC is not set +# CONFIG_SCSI_UFSHCD is not set # CONFIG_MEMSTICK is not set # CONFIG_NEW_LEDS is not set # CONFIG_ACCESSIBILITY is not set @@ -6156,7 +6391,8 @@ 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 is not set +CONFIG_EXFAT_FS=m +CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" # CONFIG_NTFS_FS is not set # CONFIG_NTFS3_FS is not set # end of DOS/FAT/EXFAT/NT Filesystems @@ -6309,7 +6545,7 @@ CONFIG_NLS_ISO8859_1=y # CONFIG_NLS_MAC_INUIT is not set # CONFIG_NLS_MAC_ROMANIAN is not set # CONFIG_NLS_MAC_TURKISH is not set -# CONFIG_NLS_UTF8 is not set +CONFIG_NLS_UTF8=m # CONFIG_DLM is not set # CONFIG_UNICODE is not set CONFIG_IO_WQ=y @@ -6731,6 +6967,7 @@ 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 diff --git a/kernel/patches/devices/s8000/0011-arm64-dts-apple-s800-0-3-Add-the-USB-role-switch-and.patch b/kernel/patches/devices/s8000/0011-arm64-dts-apple-s800-0-3-Add-the-USB-role-switch-and.patch new file mode 100644 index 00000000..610ea758 --- /dev/null +++ b/kernel/patches/devices/s8000/0011-arm64-dts-apple-s800-0-3-Add-the-USB-role-switch-and.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 3 Nov 2026 14:46:21 +0100 +Subject: [PATCH] arm64: dts: apple: s800-0-3: Add the USB role switch and + EHCI/OHCI + +Add the A9 USB role switch to the USB complex, disabled, with the EHCI +and OHCI it creates in the host role as its children: they share the +AUSB PHY with usbdev, and the switch decides which side has it. The +EHCI and OHCI nodes move out of the disabled block into the switch. + +[Orchard: from orchard PR #126, commit 864b4971; the unused ehci0/ohci0 + copies in the #if 0 block are dropped] + +Assisted-by: LLM +--- + arch/arm64/boot/dts/apple/s800-0-3.dtsi | 43 +++++++++++++++++-------- + 1 file changed, 30 insertions(+), 13 deletions(-) + +diff --git a/arch/arm64/boot/dts/apple/s800-0-3.dtsi b/arch/arm64/boot/dts/apple/s800-0-3.dtsi +index 8973c43b9..2338d6429 100644 +--- a/arch/arm64/boot/dts/apple/s800-0-3.dtsi ++++ b/arch/arm64/boot/dts/apple/s800-0-3.dtsi +@@ -277,21 +277,38 @@ usbdev: usbdev@20c100000 { + resets = <&ps_usbotg>; + dr_mode = "peripheral"; + }; +-#if 0 +- ehci0: usb-ehci@20c200000 { +- compatible = "apple,s8000-ehci", "generic-ehci"; +- reg = <0x2 0x0c200000 0x0 0x10000>; +- interrupts = ; +- power-domains = <&ps_usb2host0>; +- }; + +- ohci0: usb-ohci@20c300000 { +- compatible = "apple,s8000-ohci", "generic-ohci"; +- reg = <0x2 0x0c300000 0x0 0x10000>; +- interrupts = ; +- power-domains = <&ps_usb2host0_ohci>; +- }; ++ /* Moves the PHY between usbdev and the EHCI/OHCI pair. */ ++ usb_role: usb-role-switch@20e0d8000 { ++ compatible = "apple,s8000-usb-role-switch"; ++ reg = <0x2 0x0e0d8000 0x0 0x1000>; ++ #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>; ++ status = "disabled"; + ++ /* Created by the switch in the host role 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>; ++ }; ++ }; ++#if 0 + ehci1: usb-ehci@20c400000 { + compatible = "apple,s8000-ehci", "generic-ehci"; + reg = <0x2 0x0c400000 0x0 0x10000>; diff --git a/kernel/patches/devices/s8000/0012-arm64-dts-apple-ipad5-Enable-USB-host-on-the-Lightni.patch b/kernel/patches/devices/s8000/0012-arm64-dts-apple-ipad5-Enable-USB-host-on-the-Lightni.patch new file mode 100644 index 00000000..a23709bf --- /dev/null +++ b/kernel/patches/devices/s8000/0012-arm64-dts-apple-ipad5-Enable-USB-host-on-the-Lightni.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Tue, 3 Nov 2026 14:46:21 +0100 +Subject: [PATCH] arm64: dts: apple: ipad5: Enable USB host on the Lightning + port + +Enable the S8000 USB role switch on the iPad 5 with the D2365 PMIC +(accessory power LDO) and the Tristar (ACC pin routing), and point the +Lightning charger at it, so that its accessory policy can give the port +to the host controllers while the camera adapter is attached. The +charger keeps setting the device controller's role through its port. + +The Apple device tree gives the accessory rail as LDO5 of the PMU +(dock: function-acc_pwr, function-acc_sw_en) and the Tristar as a +CBTL1610 on i2c0. + +Tested on an iPad 5 (J71t) with the Lightning to USB camera adapter: +attaching it switches the port to the host role with EHCI and OHCI up; +USB mass storage transfers at about 36 MB/s; a powered USB-C hub +enumerates with its hub, SD card reader and RTL8152 Ethernet; detaching +the adapter returns the port to the device role and restores the USB +gadget. + +[Orchard: from orchard PR #126, commit 864b4971; moved from the t8010 + layer to the iPad 5 board layer] + +Assisted-by: LLM +--- + arch/arm64/boot/dts/apple/s800x-ipad5.dtsi | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi b/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi +index 1b21643..5f54d10 100644 +--- a/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi ++++ b/arch/arm64/boot/dts/apple/s800x-ipad5.dtsi +@@ -383,6 +383,17 @@ + status = "okay"; + }; + ++/* Lightning host role: accessory power from LDO5, ACC routing by the Tristar. */ ++&tristar_charger { ++ apple,usb-role-switch = <&usb_role>; ++}; ++ ++&usb_role { ++ apple,pmic = <&d2365_pmic>; ++ apple,tristar = <&tristar>; ++ status = "okay"; ++}; ++ + &vxd { + status = "okay"; + }; diff --git a/kernel/patches/drivers/phy/0001-phy-apple-ausb-Switch-the-PHY-between-host-and-devic.patch b/kernel/patches/drivers/phy/0001-phy-apple-ausb-Switch-the-PHY-between-host-and-devic.patch new file mode 100644 index 00000000..f21dc4c6 --- /dev/null +++ b/kernel/patches/drivers/phy/0001-phy-apple-ausb-Switch-the-PHY-between-host-and-devic.patch @@ -0,0 +1,166 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 16:50:08 +0100 +Subject: [PATCH] phy: apple: ausb: Switch the PHY between host and device mode + +The AUSB PHY has separate tunables for the device and the host role. The +driver always loads the device tunables, and its set_mode callback was +disabled, so a host controller sharing the PHY ran it with device +tunables. + +Implement set_mode as iOS does when it moves the port between the two +roles: load the tunables of the new role, clear the trim word at 0x1c, +then pulse the PHY reset. AUSB_SIG is left alone. Only an actual mode +change pulses the reset, so a second host-side user does not. A +powered-down PHY takes the mode at its next power-on, and power-off +returns it to device mode for the next user. + +The DWC2 never sets a PHY mode, and no host controller node in the Apple +device trees has a phys property, so nothing changes until a user calls +phy_set_mode(): the A9/A10 Lightning port role switch added later. + +Tested on an iPad 5 (J71t) with the Lightning to USB camera adapter. + +[Orchard: from orchard PR #126, commit 864b4971; commit message only] + +Assisted-by: LLM +--- + drivers/phy/apple/ausb.c | 66 +++++++++++++++++++++++++++++----------- + 1 file changed, 48 insertions(+), 18 deletions(-) + +diff --git a/drivers/phy/apple/ausb.c b/drivers/phy/apple/ausb.c +index 39ba39d83..f3223b28d 100644 +--- a/drivers/phy/apple/ausb.c ++++ b/drivers/phy/apple/ausb.c +@@ -30,6 +30,7 @@ + /* Tunable registers */ + #define AUSB_CFG0 0x08 + #define AUSB_CFG1 0x0c ++#define AUSB_TRIM 0x1c + + struct ausb_usbphy { + struct device *dev; +@@ -40,6 +41,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 +64,23 @@ 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 iOS does it in both directions: mode ++ * tunables, trim word 0, then a reset pulse. AUSB_SIG is left alone. ++ */ ++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 +88,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 must not pulse the reset again. */ ++ 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 +135,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 +151,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,7 +161,7 @@ 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, + }; diff --git a/kernel/patches/drivers/power/0027-dt-bindings-power-supply-apple-tristar-charger-Add-t.patch b/kernel/patches/drivers/power/0027-dt-bindings-power-supply-apple-tristar-charger-Add-t.patch new file mode 100644 index 00000000..13623074 --- /dev/null +++ b/kernel/patches/drivers/power/0027-dt-bindings-power-supply-apple-tristar-charger-Add-t.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 16:48:16 +0100 +Subject: [PATCH] dt-bindings: power: supply: apple,tristar-charger: Add the + USB role switch + +The Lightning port is either the USB device (DWC2) or, for the Lightning +to USB camera adapter, a USB host (EHCI/OHCI), and a role switch moves +it between the two. The adapter announces itself over the Lightning ID +bus, which the charger's Tristar (CBTL1610) masters, so the charger is +where the decision is made. + +The charger already sets the device controller's role through its port. +Add an optional phandle to the role switch that gives the PHY to the +host controllers; while the port is a host, the device controller stays +off. A board without it keeps the port a USB device and identifies no +accessory. + +[Orchard: from orchard PR #126, commit 864b4971; description no longer + names a board without the switch] + +Assisted-by: LLM +--- + .../bindings/power/supply/apple,tristar-charger.yaml | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/Documentation/devicetree/bindings/power/supply/apple,tristar-charger.yaml b/Documentation/devicetree/bindings/power/supply/apple,tristar-charger.yaml +index c839ad0ac..c8556e9c3 100644 +--- a/Documentation/devicetree/bindings/power/supply/apple,tristar-charger.yaml ++++ b/Documentation/devicetree/bindings/power/supply/apple,tristar-charger.yaml +@@ -60,6 +60,16 @@ properties: + $ref: /schemas/types.yaml#/definitions/phandle + description: The Tristar (CBTL1610) port controller on its I2C bus. + ++ apple,usb-role-switch: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: | ++ The role switch that gives the Lightning port's PHY to the USB host ++ controllers (apple,s8000-usb-role-switch). When present, the charger ++ also identifies Lightning accessories over the Tristar ID bus and makes ++ the port a USB host while a camera adapter is attached, as iOS does. ++ The device controller linked through port then stays off. Without it, ++ the port stays a USB device and no accessory is identified. ++ + input-current-limit-microamp: + description: Board maximum input current. + diff --git a/kernel/patches/drivers/power/0028-power-supply-apple_tristar_charger-Add-the-Lightning.patch b/kernel/patches/drivers/power/0028-power-supply-apple_tristar_charger-Add-the-Lightning.patch new file mode 100644 index 00000000..3982d605 --- /dev/null +++ b/kernel/patches/drivers/power/0028-power-supply-apple_tristar_charger-Add-the-Lightning.patch @@ -0,0 +1,701 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 16:52:29 +0100 +Subject: [PATCH] power: supply: apple_tristar_charger: Add the Lightning + ID-bus master + +A Lightning accessory announces itself on the ID bus: asked for its +digital ID, it answers with six bytes that say what it is and which pins +it uses. The Tristar (CBTL1610) is the bus master. The host loads a +request, reversed, into the TX window that ends at register 0x60, reads +it back, starts the transfer through 0x61 and polls the event register +0x0d; the Tristar clocks the wire, frames BREAKs, appends the request +CRC and leaves the response in the RX window at 0x20. + +Add the master as the commands iOS sends, in its order and with its I2C +transaction shapes: GetID (0x74) after acknowledging a latched pin fault +and releasing a latched ID-bus reset hold; for the camera adapter the +follow-up iOS runs before USB host mode, the interface information +(0x76), the module and accessory serials (0x78, 0x7a) and two SystemInfo +packets (0x84) that the adapter normally leaves unanswered, where iOS's +timeout recovery (engine abort, BREAK before the next transfer) applies; +and the power handshake, GetState (0x72) then the power request (0x70). +Serial numbers read from the accessory are not kept. + +The ID-bus master is a separate object of the charger module, under the +new APPLE_TRISTAR_ACCESSORY option. The module keeps its name; only the +composite object is spelled with dashes, so that the charger's source +file keeps its name. + +Tested on an iPad 5 (J71t) with the Lightning to USB camera adapter. + +[Orchard: from orchard PR #126, commit 864b4971; commit message only] + +Assisted-by: LLM +--- + drivers/power/supply/Kconfig | 11 + + drivers/power/supply/Makefile | 4 +- + drivers/power/supply/apple_tristar_idbus.c | 563 +++++++++++++++++++++ + drivers/power/supply/apple_tristar_idbus.h | 46 ++ + 4 files changed, 623 insertions(+), 1 deletion(-) + create mode 100644 drivers/power/supply/apple_tristar_idbus.c + create mode 100644 drivers/power/supply/apple_tristar_idbus.h + +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index 6afca3ebe..50e012f32 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -392,6 +392,17 @@ config APPLE_TRISTAR_CHARGER + This driver can also be built as a module. If so, the module will be + called apple_tristar_charger. + ++config APPLE_TRISTAR_ACCESSORY ++ bool "Lightning accessory identification" ++ depends on APPLE_TRISTAR_CHARGER ++ depends on USB_ROLE_SWITCH=y || USB_ROLE_SWITCH=APPLE_TRISTAR_CHARGER ++ help ++ Say Y here to let the Tristar charger master the Lightning ID bus, ++ over which Lightning accessories identify themselves: it asks an ++ accessory for its digital ID and runs the exchange iOS runs with ++ the camera adapter before USB host mode, including the power ++ handshake. ++ + 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 fedeaf842..654359c0d 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -55,7 +55,9 @@ obj-$(CONFIG_BATTERY_BQ27XXX_HDQ) += bq27xxx_battery_hdq.o + obj-$(CONFIG_CHARGER_APPLE_SN2400) += apple_sn2400_charger.o + obj-$(CONFIG_APPLE_BELLATRIX_POWER) += apple_bellatrix_power.o + obj-$(CONFIG_APPLE_D2365_CHARGER) += apple_d2365_charger.o +-obj-$(CONFIG_APPLE_TRISTAR_CHARGER) += apple_tristar_charger.o ++obj-$(CONFIG_APPLE_TRISTAR_CHARGER) += apple-tristar-charger.o ++apple-tristar-charger-y := apple_tristar_charger.o ++apple-tristar-charger-$(CONFIG_APPLE_TRISTAR_ACCESSORY) += apple_tristar_idbus.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_tristar_idbus.c b/drivers/power/supply/apple_tristar_idbus.c +new file mode 100644 +index 000000000..8fb45b9a0 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_idbus.c +@@ -0,0 +1,563 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Lightning ID-bus master of the Tristar (CBTL1610) port controller. ++ * ++ * A Lightning accessory announces itself on the ID bus: asked for its ++ * digital ID, it answers with six bytes that say what it is and which pins ++ * it uses. The Tristar clocks the wire, frames BREAKs and appends the ++ * request CRC itself; the host drives it over I2C, as iOS does: ++ * ++ * 1. prepare the engine: 0x60 = 0x01; ++ * 2. write the request, reversed, into the TX window that ends at 0x60, ++ * with the RX length byte ((rx_len - 1) << 1) at 0x60 itself; ++ * 3. read the window back, and send nothing if it differs; ++ * 4. GO: 0x61 = ((tx_len - 1) << 1) | 1; ++ * 5. poll the event register 0x0d every 10 ms, for at most 100 ms; ++ * 6. read the response forward from the RX window at 0x20: the opcode, ++ * rx_len - 1 data bytes and a CRC over the first rx_len bytes. ++ * ++ * The RX window is never written: a write there overwrites the retained ++ * response. Only the control register 0x05, the pin-fault register 0x0a and ++ * the TX window 0x3c-0x61 are written; the mux registers 0x01-0x03 belong ++ * to the charger and the role switch. ++ * ++ * Copyright The Orchard Linux contributors ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple_tristar_idbus.h" ++ ++#define TRISTAR_IDBUS_REG_DXCTRL 0x01 ++#define TRISTAR_IDBUS_REG_ACCMUX 0x02 ++/* bit 0 VBUS sink (the role switch's), bit 2 BREAK, bit 3 ID-bus reset hold */ ++#define TRISTAR_IDBUS_REG_CTRL 0x05 ++#define TRISTAR_IDBUS_CTRL_BREAK 0x04 ++#define TRISTAR_IDBUS_CTRL_RESET 0x08 ++#define TRISTAR_IDBUS_REG_FAULT 0x0a ++#define TRISTAR_IDBUS_REG_STATUS 0x0c ++#define TRISTAR_IDBUS_STATUS_VBUS 0x03 ++#define TRISTAR_IDBUS_REG_EVENT 0x0d ++#define TRISTAR_IDBUS_REG_REVISION 0x0f ++#define TRISTAR_IDBUS_REG_AIDSW 0x17 ++#define TRISTAR_IDBUS_REG_RX 0x20 ++#define TRISTAR_IDBUS_REG_TX_END 0x60 ++#define TRISTAR_IDBUS_REG_GO 0x61 ++#define TRISTAR_IDBUS_TX_PREPARE 0x01 ++/* The revision whose ID-bus engine this file drives */ ++#define TRISTAR_IDBUS_CBTL1610_REV 0x8a ++ ++#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 ++ ++/* Commands and their responses */ ++#define TRISTAR_IDBUS_GET_ID 0x74 ++#define TRISTAR_IDBUS_ID 0x75 ++#define TRISTAR_IDBUS_HOSTID 0x0002 /* the host's system ID, as iOS sends it */ ++#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_GET_INTERFACE 0x76 ++#define TRISTAR_IDBUS_INTERFACE 0x77 ++#define TRISTAR_IDBUS_GET_MODULE_SN 0x78 ++#define TRISTAR_IDBUS_MODULE_SN 0x79 ++#define TRISTAR_IDBUS_GET_ACCESSORY_SN 0x7a ++#define TRISTAR_IDBUS_ACCESSORY_SN 0x7b ++#define TRISTAR_IDBUS_SYSTEM_INFO 0x84 ++#define TRISTAR_IDBUS_INFO_MODEL 0 ++#define TRISTAR_IDBUS_INFO_OSVERSION 1 ++/* 0x70 flag that iOS sets only when it retries */ ++#define TRISTAR_IDBUS_POWER_RETRY 0x08 ++ ++#define TRISTAR_IDBUS_MAX_TX (TRISTAR_IDBUS_INFO_MAX + 4) ++#define TRISTAR_IDBUS_MAX_FRAME 22 /* a serial: opcode, 20 bytes, CRC */ ++#define TRISTAR_IDBUS_ID_FRAME_LEN 8 ++ ++#define TRISTAR_IDBUS_POLL_MS 10 ++#define TRISTAR_IDBUS_POLL_STEPS 10 /* 100 ms per transfer */ ++#define TRISTAR_IDBUS_SETTLE_MS 20 ++ ++/* Read before every GetID, in this order; reading 0x0d also clears stale events. */ ++static const u8 tristar_idbus_baseline[] = { ++ 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, ++}; ++ ++/* CRC-8, polynomial 0x31 reflected (0x8c), LSB first, seed 0xff. */ ++static u8 tristar_idbus_crc8(const u8 *buf, unsigned int len) ++{ ++ unsigned int i; ++ u8 crc = 0xff; ++ ++ while (len--) { ++ crc ^= *buf++; ++ for (i = 0; i < 8; i++) ++ crc = (crc >> 1) ^ ((crc & 1) ? 0x8c : 0); ++ } ++ return crc; ++} ++ ++/* ++ * A response is its opcode, rx_len - 1 data bytes and a CRC over the first ++ * rx_len bytes. A zero @type accepts any opcode with a good CRC, as iOS does ++ * for the SystemInfo replies. ++ */ ++static int tristar_idbus_check_frame(const u8 *frame, unsigned int rx_len, u8 type) ++{ ++ if (type && frame[0] != type) ++ return -EPROTO; ++ if (tristar_idbus_crc8(frame, rx_len) != frame[rx_len]) ++ return -EBADMSG; ++ return 0; ++} ++ ++/* ---- I2C access, in the transaction shapes iOS uses ---- */ ++ ++/* The register selector and STOP, then a separate read. */ ++static int tristar_idbus_reg_read(struct i2c_client *client, u8 reg) ++{ ++ union i2c_smbus_data data; ++ 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); ++ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); ++ return ret ?: data.byte; ++} ++ ++/* ++ * The first I/O error is kept in ib->io_error and refuses every later access ++ * of the sequence, 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 = tristar_idbus_reg_read(ib->client, reg); ++ if (ret < 0) ++ return tristar_idbus_fail(ib, ret); ++ if (reg == TRISTAR_IDBUS_REG_CTRL) ++ ib->last_ctrl = 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 = i2c_smbus_write_byte_data(ib->client, reg, val); ++ return ret ? tristar_idbus_fail(ib, ret) : 0; ++} ++ ++/* ++ * Read-modify-write that holds the bus throughout: the role switch writes ++ * the VBUS-sink bit of 0x05. Returns the value read back afterwards. ++ */ ++static int tristar_idbus_update(struct tristar_idbus *ib, u8 reg, u8 clear, u8 set) ++{ ++ struct i2c_client *client = ib->client; ++ union i2c_smbus_data data; ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ 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) { ++ data.byte = (data.byte & ~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); ++ if (ret) ++ return tristar_idbus_fail(ib, ret); ++ return tristar_idbus_read(ib, reg); ++} ++ ++/* The address pointer and STOP, then a separate read of @len bytes. */ ++static int tristar_idbus_block_read(struct tristar_idbus *ib, u8 reg, u8 *buf, ++ unsigned int len) ++{ ++ struct i2c_client *client = ib->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; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ 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); ++ if (ret == 1) ++ return 0; ++ return tristar_idbus_fail(ib, ret < 0 ? ret : -EIO); ++} ++ ++/* The address and the data in one write message. */ ++static int tristar_idbus_block_write(struct tristar_idbus *ib, u8 reg, const u8 *buf, ++ unsigned int len) ++{ ++ u8 msg[1 + TRISTAR_IDBUS_MAX_TX + 1]; ++ int ret; ++ ++ if (ib->io_error) ++ return ib->io_error; ++ if (len > TRISTAR_IDBUS_MAX_TX + 1) ++ return -EINVAL; ++ msg[0] = reg; ++ memcpy(msg + 1, buf, len); ++ ret = i2c_master_send(ib->client, msg, len + 1); ++ if (ret == (int)len + 1) ++ return 0; ++ return tristar_idbus_fail(ib, ret < 0 ? ret : -EIO); ++} ++ ++static int tristar_idbus_read_event(struct tristar_idbus *ib) ++{ ++ int ev = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_EVENT); ++ ++ if (ev >= 0) ++ ib->last_event = 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; ++ return ret == TRISTAR_IDBUS_CBTL1610_REV ? 0 : -ENODEV; ++} ++ ++/* ---- Transfers ---- */ ++ ++/* A pin fault (0x0d bit 7): acknowledge it by writing back what 0x0a reads. */ ++static int tristar_idbus_ack_fault(struct tristar_idbus *ib) ++{ ++ int ret = tristar_idbus_update(ib, TRISTAR_IDBUS_REG_FAULT, 0, 0); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++/* 0x05 |= 0x04; the read-back is the 20 us the BREAK needs. */ ++static int tristar_idbus_send_break(struct tristar_idbus *ib) ++{ ++ int ret = tristar_idbus_update(ib, TRISTAR_IDBUS_REG_CTRL, 0, ++ TRISTAR_IDBUS_CTRL_BREAK); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++/* ++ * One command and its response; see the top of this file. Completion is the ++ * transfer-done code or, for GetID, the digital-ID event. Returns 0, the ++ * response's header or CRC verdict, or -errno. ++ */ ++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) ++{ ++ u8 win[TRISTAR_IDBUS_MAX_TX + 1], back[TRISTAR_IDBUS_MAX_TX + 1]; ++ u8 base = TRISTAR_IDBUS_REG_TX_END - tx_len, go = ((tx_len - 1) << 1) | 1; ++ unsigned int i, step; ++ int ev, ret; ++ ++ 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; ++ ++ if (ib->break_pending) { ++ ib->break_pending = false; ++ ret = tristar_idbus_send_break(ib); ++ if (ret) ++ return ret; ++ } ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_TX_END, TRISTAR_IDBUS_TX_PREPARE); ++ if (!ret) ++ ret = tristar_idbus_block_write(ib, base, win, tx_len + 1); ++ if (!ret) ++ ret = tristar_idbus_block_read(ib, base, back, tx_len + 1); ++ if (ret) ++ return ret; ++ if (memcmp(back, win, tx_len + 1)) ++ return -EREMOTEIO; ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_GO, go); ++ if (ret) ++ return ret; ++ ++ for (step = 0; ; step++) { ++ ev = tristar_idbus_read_event(ib); ++ if (ev < 0) ++ return 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) ++ return -EIO; ++ if (ev & TRISTAR_IDBUS_EV_RESET) ++ return -ECONNRESET; ++ if ((ev & TRISTAR_IDBUS_EV_TIMEOUT) || step == TRISTAR_IDBUS_POLL_STEPS) ++ return -ETIMEDOUT; ++ fsleep(TRISTAR_IDBUS_POLL_MS * USEC_PER_MSEC); ++ } ++ ++ ret = tristar_idbus_block_read(ib, TRISTAR_IDBUS_REG_RX, frame, rx_len + 1); ++ if (ret) ++ return ret; ++ /* With the digital ID, iOS also reads the status (orientation). */ ++ if (digital_id) { ++ ret = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (ret < 0) ++ return ret; ++ } ++ return tristar_idbus_check_frame(frame, rx_len, rx_type); ++} ++ ++/** ++ * tristar_idbus_status - read the Tristar status register ++ * @ib: ID-bus master ++ * ++ * Bits 0 and 1 are VBUS, bit 6 the plug orientation, bit 7 an attached plug. ++ * ++ * Return: the register value, -ENODEV for another Tristar revision, or ++ * -errno. ++ */ ++int tristar_idbus_status(struct tristar_idbus *ib) ++{ ++ int ret = tristar_idbus_reg_read(ib->client, TRISTAR_IDBUS_REG_REVISION); ++ ++ if (ret < 0) ++ return ret; ++ if (ret != TRISTAR_IDBUS_CBTL1610_REV) ++ return -ENODEV; ++ return tristar_idbus_reg_read(ib->client, TRISTAR_IDBUS_REG_STATUS); ++} ++ ++/** ++ * tristar_idbus_identify - ask the accessory for its digital ID ++ * @ib: ID-bus master ++ * @id: TRISTAR_IDBUS_ID_LEN bytes for the ID ++ * ++ * Acknowledges a latched pin fault and releases a latched ID-bus reset hold ++ * first. One GetID, without a reorientation: a reorientation can move a live ++ * USB session to the other lanes. ++ * ++ * Return: 0 with @id filled, or -errno. ++ */ ++int tristar_idbus_identify(struct tristar_idbus *ib, u8 *id) ++{ ++ static const u8 tx[] = { ++ TRISTAR_IDBUS_GET_ID, TRISTAR_IDBUS_HOSTID >> 8, TRISTAR_IDBUS_HOSTID & 0xff, ++ }; ++ u8 frame[TRISTAR_IDBUS_ID_FRAME_LEN]; ++ unsigned int i; ++ int ret, val; ++ ++ ib->io_error = 0; ++ ib->break_pending = false; ++ ret = tristar_idbus_check_revision(ib); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < ARRAY_SIZE(tristar_idbus_baseline); i++) { ++ u8 reg = tristar_idbus_baseline[i]; ++ ++ val = reg == TRISTAR_IDBUS_REG_EVENT ? tristar_idbus_read_event(ib) : ++ tristar_idbus_read(ib, reg); ++ if (val < 0) ++ return val; ++ } ++ /* Then the RX window as the last exchange left it, read only. */ ++ ret = tristar_idbus_block_read(ib, TRISTAR_IDBUS_REG_RX, frame, sizeof(frame)); ++ if (ret) ++ return ret; ++ if (ib->last_event & TRISTAR_IDBUS_EV_FAULT) { ++ ret = tristar_idbus_ack_fault(ib); ++ if (ret) ++ return ret; ++ } ++ if (ib->last_ctrl & TRISTAR_IDBUS_CTRL_RESET) { ++ val = tristar_idbus_update(ib, TRISTAR_IDBUS_REG_CTRL, ++ TRISTAR_IDBUS_CTRL_RESET, 0); ++ if (val < 0) ++ return val; ++ fsleep(TRISTAR_IDBUS_SETTLE_MS * USEC_PER_MSEC); ++ if (val & TRISTAR_IDBUS_CTRL_RESET) ++ return -EBUSY; ++ } ++ ++ ret = tristar_idbus_transfer(ib, tx, sizeof(tx), TRISTAR_IDBUS_ID_FRAME_LEN - 1, ++ TRISTAR_IDBUS_ID, true, frame); ++ if (!ret) ++ memcpy(id, frame + 1, TRISTAR_IDBUS_ID_LEN); ++ return ret; ++} ++ ++/* ++ * One SystemInfo packet. The adapter normally does not answer; iOS then ++ * advances when the transfer times out. That timeout runs iOS's recovery: ++ * the engine is aborted (0x60 = 0x01) and the next transfer starts with a ++ * BREAK. Any other failure, or a reply with a bad CRC, is an error. ++ */ ++static int tristar_idbus_system_info(struct tristar_idbus *ib, u8 type, ++ const u8 *data, unsigned int len) ++{ ++ u8 tx[TRISTAR_IDBUS_MAX_TX], reply[2]; ++ int ret; ++ ++ len = min(len, TRISTAR_IDBUS_INFO_MAX); ++ tx[0] = TRISTAR_IDBUS_SYSTEM_INFO; ++ tx[1] = type; ++ tx[2] = len >> 8; ++ tx[3] = len; ++ memcpy(tx + 4, data, len); ++ ret = tristar_idbus_transfer(ib, tx, len + 4, 1, 0, false, reply); ++ if (ret != -ETIMEDOUT || ib->io_error) ++ return ret; ++ ret = tristar_idbus_write(ib, TRISTAR_IDBUS_REG_TX_END, TRISTAR_IDBUS_TX_PREPARE); ++ if (!ret) ++ ib->break_pending = true; ++ return ret; ++} ++ ++/** ++ * tristar_idbus_accessory_info - the exchange iOS runs after the digital ID ++ * @ib: ID-bus master ++ * @model: the host's model number, sent as SystemInfo type 0 ++ * @model_len: its length; at most TRISTAR_IDBUS_INFO_MAX bytes are sent ++ * @osversion: the host's OS version, sent as SystemInfo type 1 ++ * @osversion_len: its length; at most TRISTAR_IDBUS_INFO_MAX bytes are sent ++ * ++ * For the camera adapter iOS reads the interface information (0x76), the ++ * module serial (0x78) and the accessory serial (0x7a), then sends the two ++ * SystemInfo packets (0x84). The adapter expects this exchange before the ++ * power handshake. The serials are not kept. ++ * ++ * Return: 0, or -errno. ++ */ ++int tristar_idbus_accessory_info(struct tristar_idbus *ib, ++ const u8 *model, unsigned int model_len, ++ const u8 *osversion, unsigned int osversion_len) ++{ ++ static const struct { ++ u8 cmd, rx_len, rx_type; ++ } reads[] = { ++ { TRISTAR_IDBUS_GET_INTERFACE, 11, TRISTAR_IDBUS_INTERFACE }, ++ { TRISTAR_IDBUS_GET_MODULE_SN, 21, TRISTAR_IDBUS_MODULE_SN }, ++ { TRISTAR_IDBUS_GET_ACCESSORY_SN, 21, TRISTAR_IDBUS_ACCESSORY_SN }, ++ }; ++ u8 frame[TRISTAR_IDBUS_MAX_FRAME]; ++ unsigned int i; ++ int ret; ++ ++ ib->io_error = 0; ++ ib->break_pending = false; ++ ret = tristar_idbus_check_revision(ib); ++ for (i = 0; i < ARRAY_SIZE(reads) && !ret; i++) ++ ret = tristar_idbus_transfer(ib, &reads[i].cmd, 1, reads[i].rx_len, ++ reads[i].rx_type, false, frame); ++ memzero_explicit(frame, sizeof(frame)); ++ if (!ret) ++ ret = tristar_idbus_system_info(ib, TRISTAR_IDBUS_INFO_MODEL, ++ model, model_len); ++ if (!ret) ++ ret = tristar_idbus_system_info(ib, TRISTAR_IDBUS_INFO_OSVERSION, ++ osversion, osversion_len); ++ /* A pending BREAK is kept for the power handshake that follows. */ ++ if (ret) ++ ib->break_pending = false; ++ return ret; ++} ++ ++/** ++ * tristar_idbus_power_handshake - agree on accessory power ++ * @ib: ID-bus master ++ * ++ * With VBUS present: GetState (0x72), answered by 0x73 and four state ++ * bytes, then the power request (0x70) echoing the first two state bytes ++ * without the retry flag, answered by 0x71. No retries here. ++ * ++ * Return: 0, -ENODEV without VBUS, -EBUSY with the ID bus held in reset, ++ * or -errno. ++ */ ++int tristar_idbus_power_handshake(struct tristar_idbus *ib) ++{ ++ static const u8 get_state = TRISTAR_IDBUS_GET_STATE; ++ u8 state[6], request[3], ack[2]; ++ int ret, val; ++ ++ ib->io_error = 0; ++ ret = tristar_idbus_check_revision(ib); ++ if (ret) ++ return ret; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_STATUS); ++ if (val < 0) ++ return val; ++ if ((val & TRISTAR_IDBUS_STATUS_VBUS) != TRISTAR_IDBUS_STATUS_VBUS) ++ return -ENODEV; ++ val = tristar_idbus_read(ib, TRISTAR_IDBUS_REG_CTRL); ++ if (val < 0) ++ return val; ++ if (val & TRISTAR_IDBUS_CTRL_RESET) ++ return -EBUSY; ++ ret = tristar_idbus_transfer(ib, &get_state, 1, 5, TRISTAR_IDBUS_STATE, false, state); ++ if (ret) ++ return ret; ++ request[0] = TRISTAR_IDBUS_POWER; ++ request[1] = state[1] & ~TRISTAR_IDBUS_POWER_RETRY; ++ request[2] = state[2]; ++ return tristar_idbus_transfer(ib, request, sizeof(request), 1, ++ TRISTAR_IDBUS_POWER_ACK, false, ack); ++} +diff --git a/drivers/power/supply/apple_tristar_idbus.h b/drivers/power/supply/apple_tristar_idbus.h +new file mode 100644 +index 000000000..7a47af814 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_idbus.h +@@ -0,0 +1,46 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Lightning ID-bus master of the Tristar (CBTL1610) port controller. ++ * ++ * Copyright The Orchard Linux contributors ++ */ ++#ifndef _APPLE_TRISTAR_IDBUS_H ++#define _APPLE_TRISTAR_IDBUS_H ++ ++#include ++ ++struct i2c_client; ++ ++#define TRISTAR_IDBUS_ID_LEN 6 ++/* Longest SystemInfo string: 32 data bytes after opcode, type and length */ ++#define TRISTAR_IDBUS_INFO_MAX 32 ++ ++/** ++ * struct tristar_idbus - ID-bus master state ++ * @client: the Tristar ++ * @io_error: first I/O error of the current command sequence; every later ++ * register access of that sequence is refused ++ * @break_pending: a SystemInfo packet timed out; the next transfer starts ++ * with an ID-bus BREAK ++ * @last_ctrl: the control register as last read ++ * @last_event: the event register as last read ++ * ++ * The caller serializes all use of one instance with the other users of the ++ * Tristar. ++ */ ++struct tristar_idbus { ++ struct i2c_client *client; ++ int io_error; ++ bool break_pending; ++ u8 last_ctrl; ++ u8 last_event; ++}; ++ ++int tristar_idbus_status(struct tristar_idbus *ib); ++int tristar_idbus_identify(struct tristar_idbus *ib, u8 *id); ++int tristar_idbus_accessory_info(struct tristar_idbus *ib, ++ const u8 *model, unsigned int model_len, ++ const u8 *osversion, unsigned int osversion_len); ++int tristar_idbus_power_handshake(struct tristar_idbus *ib); ++ ++#endif /* _APPLE_TRISTAR_IDBUS_H */ diff --git a/kernel/patches/drivers/power/0029-power-supply-apple_tristar_charger-Host-mode-for-the.patch b/kernel/patches/drivers/power/0029-power-supply-apple_tristar_charger-Host-mode-for-the.patch new file mode 100644 index 00000000..006f51ea --- /dev/null +++ b/kernel/patches/drivers/power/0029-power-supply-apple_tristar_charger-Host-mode-for-the.patch @@ -0,0 +1,1311 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 17:12:29 +0100 +Subject: [PATCH] power: supply: apple_tristar_charger: Host mode for the + camera adapter + +The Lightning to USB camera adapter (digital ID 11 f0) has the iPad act +as USB host. iOS identifies it on every attach, runs the post-ID +exchange with accessory power on, makes the port a host, then agrees on +power over the ID bus. On unplug the port returns to the USB device +controller. + +Add that policy to the charger, active only when the device tree names +the port's role switch (apple,usb-role-switch). Every 250 ms it reads +the Tristar status (VBUS, orientation, plug present) inside the role +switch's monitor, so that no role change overlaps the read. With a plug +attached it asks for the digital ID 300 ms after the plug edge and then +every 2 s (10 s while a USB host has configured the gadget). For the +adapter it switches accessory power on, runs the post-ID exchange, +requests the host role, then runs the power handshake once strict VBUS +appears (two tries, 15 s deadline) and keeps the role, re-checking the +ID every 5 s. Unplug detection follows iOS: a fully empty status at +once (three reads); while the adapter is ours, plug-present clear with +VBUS lingering for 2 s (an unplug in host mode can read 0x25); +plug-present back after 0.5 s or more is a replug, which releases the +role and identifies again. A different ID in host mode (a fast swap to +a computer's cable) releases the role as well. + +The charger and the policy share the Tristar under the charger's lock. +While the policy holds the port (from the host request until the role is +given back, and around each accessory-power or role change), the charger +does not sample the mux. A charger on the adapter's power port is then +not identified, and the power handshake's state bytes are not decoded, +so the charger takes it as an unidentified source: 500 mA, through its +usual ramp and battery checks. It identifies the source again once the +policy lets go. Role-switch calls are never made with the charger's lock +held, since the role switch calls back into the charger with its own +lock held. The policy stops with the charger's poll on remove, shutdown +and suspend, and gives a held host role back on unbind. The policy looks +the role switch up from its own poll, so charging never waits for the +role switch to bind. The charger owns the device controller's role (off +until the source is identified). While the policy holds the port, that +role stays off even once the source is identified, so the device +controller never pulls up while the PHY serves the host controllers; the +first poll after the policy lets go turns it on, or identifies the +source again if it was the adapter. Stopping the charger always turns it +on. + +A board without the role switch gets no new polling and no extra +Tristar traffic; with the option off, the charger is unchanged. + +The interface between the policy and the role switch driver, which +implements it, is added here as include/linux/usb/apple-s8000-role.h: +the policy switches accessory power and lets the role switch sample the +Tristar status. Without the role switch driver the header's stubs return +-EOPNOTSUPP. + +Tested on an iPad 5 (J71t) with the Lightning to USB camera adapter. + +[Orchard: from orchard PR #126, commit 864b4971; input limit in host + mode is 500 mA instead of 2.1 A after the power handshake, since the + source behind the adapter is not identified; the policy is the only + user of the host role, so the host-entry claim is gone] + +Assisted-by: LLM +--- + drivers/power/supply/Kconfig | 15 +- + drivers/power/supply/Makefile | 3 +- + .../power/supply/apple_tristar_accessory.c | 872 ++++++++++++++++++ + .../power/supply/apple_tristar_accessory.h | 55 ++ + drivers/power/supply/apple_tristar_charger.c | 86 +- + include/linux/usb/apple-s8000-role.h | 36 + + 6 files changed, 1058 insertions(+), 9 deletions(-) + create mode 100644 drivers/power/supply/apple_tristar_accessory.c + create mode 100644 drivers/power/supply/apple_tristar_accessory.h + create mode 100644 include/linux/usb/apple-s8000-role.h + +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index 50e012f32..ae31df2ff 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -396,12 +396,17 @@ config APPLE_TRISTAR_ACCESSORY + bool "Lightning accessory identification" + depends on APPLE_TRISTAR_CHARGER + depends on USB_ROLE_SWITCH=y || USB_ROLE_SWITCH=APPLE_TRISTAR_CHARGER ++ depends on USB_ROLES_APPLE_S8000=y || USB_ROLES_APPLE_S8000=APPLE_TRISTAR_CHARGER + help +- Say Y here to let the Tristar charger master the Lightning ID bus, +- over which Lightning accessories identify themselves: it asks an +- accessory for its digital ID and runs the exchange iOS runs with +- the camera adapter before USB host mode, including the power +- handshake. ++ Say Y here to let the Tristar charger identify Lightning ++ accessories over the Lightning ID bus and make the port a USB host ++ while a Lightning-to-USB camera adapter is attached, as iOS does. ++ A charger on the adapter's power port is not identified and is ++ allowed 500 mA. ++ ++ It needs the port's USB role switch (USB_ROLES_APPLE_S8000) and ++ is active only on boards whose device tree names that switch for ++ the charger. + + config BATTERY_BQ27XXX_DT_UPDATES_NVM + bool "BQ27xxx support for update of NVM/flash data memory" +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index 654359c0d..53a6668f3 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -57,7 +57,8 @@ obj-$(CONFIG_APPLE_BELLATRIX_POWER) += apple_bellatrix_power.o + obj-$(CONFIG_APPLE_D2365_CHARGER) += apple_d2365_charger.o + obj-$(CONFIG_APPLE_TRISTAR_CHARGER) += apple-tristar-charger.o + apple-tristar-charger-y := apple_tristar_charger.o +-apple-tristar-charger-$(CONFIG_APPLE_TRISTAR_ACCESSORY) += apple_tristar_idbus.o ++apple-tristar-charger-$(CONFIG_APPLE_TRISTAR_ACCESSORY) += apple_tristar_accessory.o \ ++ apple_tristar_idbus.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_tristar_accessory.c b/drivers/power/supply/apple_tristar_accessory.c +new file mode 100644 +index 000000000..f97524caf +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_accessory.c +@@ -0,0 +1,872 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Lightning accessory policy of the Tristar charger: USB host mode for the ++ * Lightning-to-USB camera adapter. ++ * ++ * The camera adapter announces itself with digital ID 11 f0 (USB, UART and ++ * power pins). On it iOS runs the post-ID exchange (interface information, ++ * serials, SystemInfo), makes the port a USB host, and then agrees on power ++ * with the adapter over the ID bus; the adapter's hub enumerates at high ++ * speed. There is no device-mode phase in between. This policy does the ++ * same through the port's USB role switch, which owns the host controllers ++ * and the Tristar's data and accessory-power routes. When the adapter is ++ * unplugged, the port returns to the USB device controller. ++ * ++ * A charger on the adapter's power port is not identified: the Tristar mux ++ * cannot be sampled while the port is a host, and the state bytes of the ++ * power handshake are not decoded. While the adapter is ours, the input ++ * limit is therefore 500 mA, as for any unidentified source. ++ * ++ * Every 250 ms the policy reads the Tristar status (0x0c: bits 0-1 VBUS, ++ * bit 6 orientation, bit 7 an attached plug). With a plug attached it asks ++ * for the digital ID 300 ms after the plug edge, then every 2 s, or every ++ * 10 s while a USB host uses the gadget; with the adapter in host mode it ++ * checks the ID every 5 s. An unplug is three empty reads in a row; while ++ * the adapter is ours, bit 7 alone counts (an unplug in host mode can leave ++ * the VBUS bits set) and the wait is 2 s; bit 7 back after 0.5 s or more is ++ * a replug and starts over, as iOS identifies on every attach. ++ * ++ * Sharing the Tristar with the charger: every Tristar access of either side ++ * holds the charger's lock, so ID-bus transfers and mux samples never ++ * interleave. The charger samples the mux only while the policy does not ++ * hold the port: from the request for host mode until it is given back, and ++ * around each change of accessory power or USB role, the charger takes the ++ * adapter's allowance instead of identifying the source (see ++ * tristar_accessory_holds()). The hold is taken under the lock before any ++ * role-switch call, so no mux sample is in flight when the role switch ++ * rewrites the routes. Role-switch calls are never made with the lock held: ++ * the role switch calls back into the charger (the status sample below, the ++ * gadget's vbus_draw) with its own lock held. ++ * ++ * Copyright The Orchard Linux contributors ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple_tristar_accessory.h" ++#include "apple_tristar_idbus.h" ++ ++/* Tristar status 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_ACCESSORY_POLL_MS 250 ++/* A power handshake needs a digital ID read this recently. */ ++#define TRISTAR_ACCESSORY_ID_WINDOW_MS 30000 ++/* The gadget's draw before configuration; more means a configured session. */ ++#define TRISTAR_ACCESSORY_UNCONFIGURED_MA 100 ++ ++/* ++ * The SystemInfo values iOS sends: the root model number and its OS build. ++ * The adapter does not answer them. Without a model number in the device ++ * tree, send the values this exchange was validated with. ++ */ ++#define TRISTAR_ACCESSORY_MODEL "MP2G2" ++#define TRISTAR_ACCESSORY_OSVERSION "20H350" ++ ++enum tristar_auto_phase { ++ TRISTAR_AUTO_EMPTY, /* nothing attached */ ++ TRISTAR_AUTO_DEVICE, /* attached, device role, GetID pending */ ++ TRISTAR_AUTO_OTHER, /* identified, not a host-mode accessory */ ++ TRISTAR_AUTO_ENTER, /* switch to host requested */ ++ TRISTAR_AUTO_HANDSHAKE, /* host is on; wait for VBUS, then 0x72/0x70 */ ++ 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 */ ++ 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 owned_detach_polls; /* ... adapter ours, bit 7 clear but VBUS bits set */ ++ unsigned int owned_replug_polls; /* bit 7 clear this long, then back: a replug */ ++ unsigned int handshake_tries; ++ unsigned int handshake_wait_ms; /* host-on deadline for strict VBUS */ ++ int input_ua; /* host role: the source is not identified */ ++}; ++ ++static const struct tristar_auto_cfg tristar_auto_cfg = { ++ .probe_ms = 2000, ++ .busy_probe_ms = 10000, ++ .verify_ms = 5000, ++ .settle_ms = 300, ++ .detach_polls = 3, ++ .owned_detach_polls = 8, ++ .owned_replug_polls = 2, ++ .handshake_tries = 2, ++ .handshake_wait_ms = 15000, ++ .input_ua = 500000, ++}; ++ ++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_IDBUS_ID_LEN]; ++ bool id_valid; ++ bool adapter; /* id selects host mode */ ++ unsigned int ph_tries; ++ unsigned long ph_deadline; ++}; ++ ++struct tristar_auto_in { ++ unsigned long now; ++ int status; /* 0x0c or -errno */ ++ bool host; /* the role switch is in host mode */ ++ bool link_active; /* a USB host configured the gadget, not suspended */ ++}; ++ ++/** ++ * struct tristar_accessory - Lightning accessory policy ++ * @dev: the charger ++ * @lock: the charger's lock; serializes every Tristar access and the fields below ++ * @role_np: the role switch's node ++ * @role_sw: the Lightning port's role switch, once it is registered ++ * @work: the policy's poll ++ * @ib: ID-bus master ++ * @autop: policy state ++ * @stopped: no poll is queued and none may queue itself ++ * @status: last Tristar status read, or -errno ++ * @vbus: strict VBUS was seen since the last detach ++ * @detach_role: VBUS went away; give the role back at the next poll ++ * @host: the role switch was in host mode at the last poll ++ * @busy: an accessory-power or role change is in progress ++ * @usb_ma: what the USB host allows the gadget, as the charger last saw it ++ * @mux_fault: the charger could not verify a Tristar mux restore ++ * @id_ok: the last GetID succeeded, at @id_at (jiffies) ++ * @id_at: see @id_ok ++ * @source_id: the digital ID of the attached accessory ++ * @source_id_valid: @source_id holds one ++ * @info_valid: the post-ID exchange ran for this attachment ++ * @model: SystemInfo model number ++ * @model_len: its length ++ */ ++struct tristar_accessory { ++ struct device *dev; ++ struct mutex *lock; /* the charger's: every Tristar access */ ++ struct device_node *role_np; ++ struct usb_role_switch *role_sw; ++ struct delayed_work work; ++ struct tristar_idbus ib; ++ struct tristar_auto autop; ++ bool stopped; ++ int status; ++ bool vbus; ++ bool detach_role; ++ bool host; ++ bool busy; ++ unsigned int usb_ma; ++ bool mux_fault; ++ bool id_ok; ++ unsigned long id_at; ++ u8 source_id[TRISTAR_IDBUS_ID_LEN]; ++ bool source_id_valid; ++ bool info_valid; ++ u8 model[TRISTAR_IDBUS_INFO_MAX]; ++ unsigned int model_len; ++}; ++ ++/* ---- Decisions: no I/O here; the caller runs the action and reports back ---- */ ++ ++static bool tristar_auto_due(unsigned long now, unsigned long when) ++{ ++ return (long)(now - when) >= 0; ++} ++ ++/* The camera adapter, digital ID 11 f0 ..: USB=2 UART=1 PWR=0xf. */ ++static bool tristar_auto_is_host_adapter(const u8 *id) ++{ ++ return id[0] == 0x11 && id[1] == 0xf0; ++} ++ ++static bool tristar_auto_attached(int status) ++{ ++ return status >= 0 && (status & (TRISTAR_AUTO_PRESENT | TRISTAR_AUTO_VBUS)); ++} ++ ++/* ++ * The camera adapter belongs to us from the host request until it is gone. ++ * An unplug in host mode can read 0x0c = 0x25: bit 7 (plug present) clear, ++ * VBUS bit 0 still set. Only bit 7 says the plug is there. ++ */ ++static bool tristar_auto_owned(const struct tristar_auto *ta) ++{ ++ return ta->id_valid && ta->adapter && ++ (ta->phase == TRISTAR_AUTO_ENTER || ta->phase == TRISTAR_AUTO_HANDSHAKE || ++ ta->phase == TRISTAR_AUTO_HOST); ++} ++ ++/* ++ * iOS identifies the accessory again on every attach. Forget the adapter ++ * information whenever the policy enters a phase without an owned adapter. ++ */ ++static bool tristar_auto_reidentify(enum tristar_auto_phase old, ++ enum tristar_auto_phase now) ++{ ++ bool was = old == TRISTAR_AUTO_EMPTY || old == TRISTAR_AUTO_DEVICE; ++ bool is = now == TRISTAR_AUTO_EMPTY || now == TRISTAR_AUTO_DEVICE; ++ ++ return is && !was; ++} ++ ++/* Host entry can briefly remove strict VBUS while the same adapter is present. */ ++static bool tristar_auto_hold_source(const struct tristar_auto *ta, int status) ++{ ++ return status >= 0 && (status & TRISTAR_AUTO_PRESENT) && ta->id_valid && ta->adapter && ++ (ta->phase == TRISTAR_AUTO_ENTER || ta->phase == TRISTAR_AUTO_HANDSHAKE || ++ ta->phase == TRISTAR_AUTO_HOST); ++} ++ ++static const char *tristar_auto_phase_name(enum tristar_auto_phase phase) ++{ ++ static const char *const names[] = { ++ [TRISTAR_AUTO_EMPTY] = "empty", ++ [TRISTAR_AUTO_DEVICE] = "device", ++ [TRISTAR_AUTO_OTHER] = "other", ++ [TRISTAR_AUTO_ENTER] = "enter-host", ++ [TRISTAR_AUTO_HANDSHAKE] = "handshake", ++ [TRISTAR_AUTO_HOST] = "host", ++ [TRISTAR_AUTO_INHIBIT] = "inhibit", ++ }; ++ ++ return (unsigned int)phase < ARRAY_SIZE(names) ? names[phase] : "?"; ++} ++ ++static void tristar_auto_forget(struct tristar_auto *ta) ++{ ++ ta->id_valid = false; ++ ta->adapter = false; ++ ta->ph_tries = 0; ++} ++ ++static void tristar_auto_init(struct tristar_auto *ta) ++{ ++ *ta = (struct tristar_auto){ .phase = TRISTAR_AUTO_EMPTY, .status = -1 }; ++} ++ ++/* One poll. The caller then runs the returned action and reports back. */ ++static 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, owned; ++ unsigned int limit, was_empty; ++ int prev; ++ ++ /* An I2C error is no evidence either way. */ ++ if (in->status < 0) ++ return TRISTAR_AUTO_NONE; ++ ++ owned = tristar_auto_owned(ta); ++ attached = owned ? in->status >= 0 && (in->status & TRISTAR_AUTO_PRESENT) : ++ tristar_auto_attached(in->status); ++ was_empty = ta->empty; ++ 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)); ++ ++ /* A fully empty 0x0c is an unplug at once; bit 7 clear with VBUS lingering waits longer. */ ++ limit = tristar_auto_owned(ta) && tristar_auto_attached(in->status) ? ++ cfg->owned_detach_polls : cfg->detach_polls; ++ if (ta->empty >= limit && ta->phase != TRISTAR_AUTO_EMPTY) { ++ bool release = ta->adapter && in->host; ++ ++ ta->phase = TRISTAR_AUTO_EMPTY; ++ tristar_auto_forget(ta); ++ return release ? TRISTAR_AUTO_TO_DEVICE : TRISTAR_AUTO_NONE; ++ } ++ ++ /* ++ * A replug in host mode (bit 7 clear for a second or more, then back) ++ * leaves the adapter unidentified. Release the role and attach it ++ * again, as iOS does on every attach. ++ */ ++ if (owned && attached && was_empty >= cfg->owned_replug_polls && ++ ta->phase != TRISTAR_AUTO_EMPTY) { ++ bool release = in->host; ++ ++ ta->phase = TRISTAR_AUTO_EMPTY; ++ tristar_auto_forget(ta); ++ return release ? TRISTAR_AUTO_TO_DEVICE : 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: ++ /* No identification while the port is still a host. */ ++ 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); ++ return TRISTAR_AUTO_PROBE; ++ case TRISTAR_AUTO_HANDSHAKE: ++ if (!in->host) { ++ ta->phase = TRISTAR_AUTO_INHIBIT; ++ return TRISTAR_AUTO_NONE; ++ } ++ /* Without strict VBUS the handshake would fail: do not spend tries on it. */ ++ if ((in->status & TRISTAR_AUTO_VBUS) == TRISTAR_AUTO_VBUS) ++ return TRISTAR_AUTO_POWER_HANDSHAKE; ++ if (tristar_auto_due(in->now, ta->ph_deadline)) ++ ta->phase = TRISTAR_AUTO_HOST; ++ return TRISTAR_AUTO_NONE; ++ case TRISTAR_AUTO_ENTER: ++ if (in->host) { ++ ta->phase = TRISTAR_AUTO_HANDSHAKE; ++ ta->ph_deadline = in->now + cfg->handshake_wait_ms; ++ return TRISTAR_AUTO_NONE; ++ } ++ return TRISTAR_AUTO_TO_HOST; ++ case TRISTAR_AUTO_HOST: ++ if (!in->host) { ++ ta->phase = TRISTAR_AUTO_INHIBIT; ++ 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; ++ return TRISTAR_AUTO_PROBE; ++ } ++ return TRISTAR_AUTO_NONE; ++ case TRISTAR_AUTO_INHIBIT: ++ break; ++ } ++ return TRISTAR_AUTO_NONE; ++} ++ ++/* GetID finished: ret 0 with a valid id, or -errno. */ ++static 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; ++ ++ if (ta->phase != TRISTAR_AUTO_DEVICE && ta->phase != TRISTAR_AUTO_OTHER && ++ ta->phase != TRISTAR_AUTO_HOST) ++ return TRISTAR_AUTO_NONE; ++ if (ret) ++ return TRISTAR_AUTO_NONE; ++ adapter = tristar_auto_is_host_adapter(id); ++ memcpy(ta->id, id, TRISTAR_IDBUS_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. a computer's cable. */ ++ ta->adapter = false; ++ ta->phase = TRISTAR_AUTO_OTHER; ++ ta->next_probe = now + cfg->probe_ms; ++ return TRISTAR_AUTO_TO_DEVICE; ++ } ++ ta->adapter = adapter; ++ if (!adapter) { ++ ta->phase = TRISTAR_AUTO_OTHER; ++ return TRISTAR_AUTO_NONE; ++ } ++ ta->phase = TRISTAR_AUTO_ENTER; ++ ta->ph_tries = 0; ++ return TRISTAR_AUTO_TO_HOST; ++} ++ ++/* ++ * Power handshake finished. Host is already on: iOS enumerates the hub ++ * before its handshake completes. After the last try, keep the port in host ++ * mode. ++ */ ++static void tristar_auto_handshake_done(struct tristar_auto *ta, ++ const struct tristar_auto_cfg *cfg, int ret) ++{ ++ if (ta->phase != TRISTAR_AUTO_HANDSHAKE) ++ return; ++ ta->ph_tries++; ++ if (ret && ta->ph_tries < cfg->handshake_tries) ++ return; ++ ta->phase = TRISTAR_AUTO_HOST; ++} ++ ++/* Host entry finished. No retry on failure: the next unplug starts over. */ ++static void tristar_auto_host_done(struct tristar_auto *ta, ++ const struct tristar_auto_cfg *cfg, ++ unsigned long now, int ret) ++{ ++ if (ta->phase != TRISTAR_AUTO_ENTER) ++ return; ++ if (ret) { ++ ta->phase = TRISTAR_AUTO_INHIBIT; ++ return; ++ } ++ ta->phase = TRISTAR_AUTO_HANDSHAKE; ++ ta->next_probe = now + cfg->verify_ms; ++ ta->ph_deadline = now + cfg->handshake_wait_ms; ++} ++ ++/* ++ * Input power while the adapter is ours: an unidentified source behind the ++ * adapter. Returns false when the ordinary device-mode rules apply. ++ */ ++static bool tristar_auto_supply(const struct tristar_auto *ta, ++ const struct tristar_auto_cfg *cfg, int status, ++ int *current_ua) ++{ ++ bool online; ++ ++ 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 ? cfg->input_ua : 0; ++ return true; ++} ++ ++/* ---- Policy ---- */ ++ ++static unsigned long tristar_accessory_now(void) ++{ ++ return ktime_to_ms(ktime_get_boottime()); ++} ++ ++/* A new attachment, or a changed accessory: run the post-ID exchange again. Caller holds lock. */ ++static void tristar_accessory_new_source(struct tristar_accessory *acc) ++{ ++ acc->source_id_valid = false; ++ acc->info_valid = false; ++} ++ ++/* Caller holds lock. */ ++static void tristar_accessory_source_id(struct tristar_accessory *acc, const u8 *id) ++{ ++ if (acc->source_id_valid && memcmp(acc->source_id, id, sizeof(acc->source_id))) ++ tristar_accessory_new_source(acc); ++ memcpy(acc->source_id, id, sizeof(acc->source_id)); ++ acc->source_id_valid = true; ++} ++ ++/* Caller holds lock. */ ++static void tristar_accessory_phase(struct tristar_accessory *acc, enum tristar_auto_phase old) ++{ ++ struct tristar_auto *ta = &acc->autop; ++ ++ if (old != TRISTAR_AUTO_EMPTY && old != ta->phase && ++ (ta->phase == TRISTAR_AUTO_DEVICE || ta->phase == TRISTAR_AUTO_EMPTY)) { ++ tristar_accessory_new_source(acc); ++ tristar_auto_forget(ta); ++ } ++ if (tristar_auto_reidentify(old, ta->phase)) ++ acc->info_valid = false; ++ if (old != ta->phase) ++ dev_dbg(acc->dev, "accessory: %s -> %s\n", tristar_auto_phase_name(old), ++ tristar_auto_phase_name(ta->phase)); ++} ++ ++/* Hold the port for a role-switch call; see the top of this file. */ ++static void tristar_accessory_busy(struct tristar_accessory *acc, bool busy) ++{ ++ mutex_lock(acc->lock); ++ acc->busy = busy; ++ mutex_unlock(acc->lock); ++} ++ ++/* ++ * The role switch runs this with its lock held, in the device role only, so ++ * that no role change overlaps the read. -EUCLEAN keeps the role switch from ++ * entering host mode while the charger has a Tristar mux restore unverified. ++ */ ++static int tristar_accessory_sample(void *data) ++{ ++ struct tristar_accessory *acc = data; ++ int ret; ++ ++ mutex_lock(acc->lock); ++ acc->status = tristar_idbus_status(&acc->ib); ++ ret = acc->mux_fault ? -EUCLEAN : 0; ++ mutex_unlock(acc->lock); ++ return ret; ++} ++ ++static enum tristar_auto_action tristar_accessory_probe_id(struct tristar_accessory *acc) ++{ ++ struct tristar_auto *ta = &acc->autop; ++ u8 id[TRISTAR_IDBUS_ID_LEN] = {}; ++ enum tristar_auto_phase old; ++ enum tristar_auto_action action; ++ bool need_info = false, powered = false, same = false; ++ int ret; ++ ++ mutex_lock(acc->lock); ++ ret = tristar_idbus_identify(&acc->ib, id); ++ acc->id_ok = !ret; ++ if (!ret) { ++ acc->id_at = jiffies; ++ same = ta->id_valid && !memcmp(ta->id, id, sizeof(id)); ++ tristar_accessory_source_id(acc, id); ++ need_info = tristar_auto_is_host_adapter(id) && !acc->info_valid; ++ acc->busy = need_info; ++ } ++ mutex_unlock(acc->lock); ++ ++ /* iOS switches the accessory power on before the post-ID exchange. */ ++ if (need_info) { ++ ret = apple_s8000_usb_role_accessory_power(acc->role_sw, true); ++ powered = !ret; ++ if (!ret) { ++ mutex_lock(acc->lock); ++ ret = tristar_idbus_accessory_info(&acc->ib, acc->model, acc->model_len, ++ (const u8 *)TRISTAR_ACCESSORY_OSVERSION, ++ strlen(TRISTAR_ACCESSORY_OSVERSION)); ++ mutex_unlock(acc->lock); ++ } ++ if (ret && powered) ++ apple_s8000_usb_role_accessory_power(acc->role_sw, false); ++ } ++ ++ mutex_lock(acc->lock); ++ acc->busy = false; ++ if (need_info && !ret) ++ acc->info_valid = true; ++ old = ta->phase; ++ action = tristar_auto_probed(ta, &tristar_auto_cfg, tristar_accessory_now(), ret, id); ++ if (!ret && !same) ++ dev_dbg(acc->dev, "accessory: digital ID %*ph%s\n", (int)sizeof(id), id, ++ ta->adapter ? " (camera adapter)" : ""); ++ else if (ret) ++ dev_dbg(acc->dev, "accessory: identification failed (%d)\n", ret); ++ tristar_accessory_phase(acc, old); ++ mutex_unlock(acc->lock); ++ return action; ++} ++ ++static enum tristar_auto_action ++tristar_accessory_act(struct tristar_accessory *acc, enum tristar_auto_action action) ++{ ++ struct tristar_auto *ta = &acc->autop; ++ enum tristar_auto_phase old; ++ int ret = 0; ++ ++ switch (action) { ++ case TRISTAR_AUTO_PROBE: ++ return tristar_accessory_probe_id(acc); ++ case TRISTAR_AUTO_POWER_HANDSHAKE: ++ mutex_lock(acc->lock); ++ if (!acc->id_ok || time_after(jiffies, acc->id_at + ++ msecs_to_jiffies(TRISTAR_ACCESSORY_ID_WINDOW_MS))) ++ ret = -ENOLINK; ++ else ++ ret = tristar_idbus_power_handshake(&acc->ib); ++ old = ta->phase; ++ tristar_auto_handshake_done(ta, &tristar_auto_cfg, ret); ++ dev_dbg(acc->dev, "accessory: power handshake %s (%d), try %u\n", ++ ret ? "failed" : "done", ret, ta->ph_tries); ++ tristar_accessory_phase(acc, old); ++ mutex_unlock(acc->lock); ++ break; ++ case TRISTAR_AUTO_TO_HOST: ++ /* The policy is in enter-host: the charger leaves the port alone. */ ++ ret = usb_role_switch_set_role(acc->role_sw, USB_ROLE_HOST); ++ mutex_lock(acc->lock); ++ old = ta->phase; ++ tristar_auto_host_done(ta, &tristar_auto_cfg, tristar_accessory_now(), ret); ++ tristar_accessory_phase(acc, old); ++ mutex_unlock(acc->lock); ++ if (ret) ++ dev_warn(acc->dev, "camera adapter: USB host mode failed: %d\n", ret); ++ else ++ dev_dbg(acc->dev, "camera adapter: USB host mode\n"); ++ break; ++ case TRISTAR_AUTO_TO_DEVICE: ++ tristar_accessory_busy(acc, true); ++ if (usb_role_switch_get_role(acc->role_sw) == USB_ROLE_HOST) ++ ret = usb_role_switch_set_role(acc->role_sw, USB_ROLE_DEVICE); ++ tristar_accessory_busy(acc, false); ++ if (ret) ++ dev_warn(acc->dev, "accessory: USB device mode failed: %d\n", ret); ++ else ++ dev_dbg(acc->dev, "accessory: USB device mode\n"); ++ break; ++ case TRISTAR_AUTO_NONE: ++ break; ++ } ++ return TRISTAR_AUTO_NONE; ++} ++ ++static void tristar_accessory_step(struct tristar_accessory *acc) ++{ ++ struct tristar_auto *ta = &acc->autop; ++ struct tristar_auto_in in = {}; ++ enum tristar_auto_action action; ++ enum tristar_auto_phase old; ++ unsigned int i; ++ bool detach; ++ int ret; ++ ++ /* The charger does not wait for the role switch; the policy does. */ ++ if (!acc->role_sw) { ++ acc->role_sw = usb_role_switch_find_by_fwnode(of_fwnode_handle(acc->role_np)); ++ if (!acc->role_sw) ++ return; ++ } ++ ++ ret = apple_s8000_usb_role_monitor(acc->role_sw, tristar_accessory_sample, acc); ++ mutex_lock(acc->lock); ++ /* Not in the device role: the role switch did not sample. */ ++ if (ret && ret != -EUCLEAN) ++ acc->status = tristar_idbus_status(&acc->ib); ++ /* VBUS gone, other than during host entry: a detach, whatever the plug bit says. */ ++ if (acc->status >= 0 && acc->vbus && !(acc->status & TRISTAR_AUTO_VBUS) && ++ !tristar_auto_hold_source(ta, acc->status)) { ++ acc->vbus = false; ++ acc->detach_role = true; ++ tristar_accessory_new_source(acc); ++ tristar_auto_forget(ta); ++ ta->phase = TRISTAR_AUTO_EMPTY; ++ ta->was_empty = true; ++ ta->empty = 0; ++ } ++ if (acc->status >= 0 && (acc->status & TRISTAR_AUTO_VBUS) == TRISTAR_AUTO_VBUS) ++ acc->vbus = true; ++ detach = acc->detach_role; ++ acc->detach_role = false; ++ mutex_unlock(acc->lock); ++ if (detach) ++ tristar_accessory_act(acc, TRISTAR_AUTO_TO_DEVICE); ++ ++ in.host = usb_role_switch_get_role(acc->role_sw) == USB_ROLE_HOST; ++ mutex_lock(acc->lock); ++ acc->host = in.host; ++ in.link_active = acc->usb_ma > TRISTAR_ACCESSORY_UNCONFIGURED_MA; ++ in.now = tristar_accessory_now(); ++ in.status = acc->status; ++ old = ta->phase; ++ action = tristar_auto_step(ta, &tristar_auto_cfg, &in); ++ tristar_accessory_phase(acc, old); ++ mutex_unlock(acc->lock); ++ ++ /* A probe can lead to host entry; nothing chains further. */ ++ for (i = 0; i < 4 && action != TRISTAR_AUTO_NONE; i++) ++ action = tristar_accessory_act(acc, action); ++} ++ ++static void tristar_accessory_work(struct work_struct *work) ++{ ++ struct tristar_accessory *acc = container_of(to_delayed_work(work), ++ struct tristar_accessory, work); ++ ++ tristar_accessory_step(acc); ++ mutex_lock(acc->lock); ++ if (!acc->stopped) ++ mod_delayed_work(system_power_efficient_wq, &acc->work, ++ msecs_to_jiffies(TRISTAR_ACCESSORY_POLL_MS)); ++ mutex_unlock(acc->lock); ++} ++ ++/* ---- Interface to the charger ---- */ ++ ++/** ++ * tristar_accessory_holds - whether the policy holds the Lightning port ++ * @acc: accessory policy, or NULL ++ * @ma: the input allowance while held, in mA ++ * ++ * While held, the charger must not sample the Tristar mux and takes @ma as ++ * the source's allowance. Caller holds the charger lock. ++ * ++ * Return: a description of the holder, or NULL when the charger may ++ * identify the source itself. ++ */ ++const char *tristar_accessory_holds(struct tristar_accessory *acc, unsigned int *ma) ++{ ++ int ua; ++ ++ if (!acc) ++ return NULL; ++ if (tristar_auto_supply(&acc->autop, &tristar_auto_cfg, acc->status, &ua)) { ++ *ma = ua / 1000; ++ return "Lightning camera adapter, USB host mode"; ++ } ++ *ma = 0; ++ if (acc->busy) ++ return "Lightning accessory, changing USB role"; ++ if (acc->host) ++ return "USB host mode"; ++ return NULL; ++} ++ ++/** ++ * tristar_accessory_report - what the charger knows, once per charger poll ++ * @acc: accessory policy, or NULL ++ * @usb_ma: what the USB host allows the gadget (vbus_draw), 0 without one ++ * @mux_fault: the charger could not verify a Tristar mux restore ++ * ++ * Caller holds the charger lock. ++ */ ++void tristar_accessory_report(struct tristar_accessory *acc, unsigned int usb_ma, ++ bool mux_fault) ++{ ++ if (!acc) ++ return; ++ acc->usb_ma = usb_ma; ++ acc->mux_fault = mux_fault; ++} ++ ++/** ++ * tristar_accessory_start - start or resume the policy ++ * @acc: accessory policy, or NULL ++ */ ++void tristar_accessory_start(struct tristar_accessory *acc) ++{ ++ if (!acc) ++ return; ++ mutex_lock(acc->lock); ++ acc->stopped = false; ++ mod_delayed_work(system_power_efficient_wq, &acc->work, 0); ++ mutex_unlock(acc->lock); ++} ++ ++/** ++ * tristar_accessory_stop - stop the policy ++ * @acc: accessory policy, or NULL ++ * ++ * Returns once no poll runs; a role change in progress has finished and ++ * none starts until tristar_accessory_start(). The port keeps its role. ++ * Caller must not hold the charger lock. ++ */ ++void tristar_accessory_stop(struct tristar_accessory *acc) ++{ ++ if (!acc) ++ return; ++ mutex_lock(acc->lock); ++ acc->stopped = true; ++ mutex_unlock(acc->lock); ++ cancel_delayed_work_sync(&acc->work); ++} ++ ++/* Unbind: give a host role the policy holds back to the USB device controller. */ ++static void tristar_accessory_release(void *data) ++{ ++ struct tristar_accessory *acc = data; ++ ++ tristar_accessory_stop(acc); ++ if (acc->role_sw) { ++ if (usb_role_switch_get_role(acc->role_sw) == USB_ROLE_HOST) ++ usb_role_switch_set_role(acc->role_sw, USB_ROLE_DEVICE); ++ usb_role_switch_put(acc->role_sw); ++ } ++ of_node_put(acc->role_np); ++} ++ ++/* SystemInfo model: the root model number, if the boot loader passed one. */ ++static void tristar_accessory_model(struct tristar_accessory *acc) ++{ ++ struct device_node *root = of_find_node_by_path("/"); ++ const u8 *prop = NULL; ++ int len = 0; ++ ++ if (root) ++ prop = of_get_property(root, "model-number", &len); ++ if (prop && len > 0) { ++ acc->model_len = min_t(unsigned int, len, sizeof(acc->model)); ++ memcpy(acc->model, prop, acc->model_len); ++ while (acc->model_len && !acc->model[acc->model_len - 1]) ++ acc->model_len--; ++ } ++ of_node_put(root); ++ if (!acc->model_len) { ++ acc->model_len = strlen(TRISTAR_ACCESSORY_MODEL); ++ memcpy(acc->model, TRISTAR_ACCESSORY_MODEL, acc->model_len); ++ } ++} ++ ++/** ++ * tristar_accessory_probe - set up the policy if the board has a role switch ++ * @dev: the charger ++ * @tristar: the Tristar ++ * @lock: the charger's lock, which serializes every Tristar access ++ * @accp: the policy, or NULL when the device tree names no role switch ++ * ++ * The policy does nothing until tristar_accessory_start(). It finds the role ++ * switch from its own poll, so that charging never waits for the role switch. ++ * ++ * Return: 0, or -errno. ++ */ ++int tristar_accessory_probe(struct device *dev, struct i2c_client *tristar, ++ struct mutex *lock, struct tristar_accessory **accp) ++{ ++ struct tristar_accessory *acc; ++ struct device_node *np; ++ bool available; ++ int ret; ++ ++ np = of_parse_phandle(dev->of_node, "apple,usb-role-switch", 0); ++ if (!np) ++ return 0; ++ available = of_device_is_available(np); ++ if (!available || !IS_REACHABLE(CONFIG_USB_ROLES_APPLE_S8000)) { ++ of_node_put(np); ++ if (available) ++ dev_warn(dev, "no Lightning role switch driver; accessories stay unidentified\n"); ++ return 0; ++ } ++ ++ acc = devm_kzalloc(dev, sizeof(*acc), GFP_KERNEL); ++ if (!acc) { ++ of_node_put(np); ++ return -ENOMEM; ++ } ++ acc->dev = dev; ++ acc->lock = lock; ++ acc->role_np = np; ++ acc->ib.client = tristar; ++ acc->status = -ENODATA; ++ acc->stopped = true; ++ tristar_auto_init(&acc->autop); ++ tristar_accessory_model(acc); ++ INIT_DELAYED_WORK(&acc->work, tristar_accessory_work); ++ ret = devm_add_action_or_reset(dev, tristar_accessory_release, acc); ++ if (ret) ++ return ret; ++ ++ *accp = acc; ++ dev_info(dev, "Lightning accessory identification and USB host mode enabled\n"); ++ return 0; ++} +diff --git a/drivers/power/supply/apple_tristar_accessory.h b/drivers/power/supply/apple_tristar_accessory.h +new file mode 100644 +index 000000000..8df74ed37 +--- /dev/null ++++ b/drivers/power/supply/apple_tristar_accessory.h +@@ -0,0 +1,55 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Lightning accessory policy of the Tristar charger: the interface between ++ * the charger and the policy. See apple_tristar_accessory.c. ++ * ++ * Copyright The Orchard Linux contributors ++ */ ++#ifndef _APPLE_TRISTAR_ACCESSORY_H ++#define _APPLE_TRISTAR_ACCESSORY_H ++ ++#include ++#include ++ ++struct device; ++struct i2c_client; ++struct mutex; ++struct tristar_accessory; ++ ++#if IS_ENABLED(CONFIG_APPLE_TRISTAR_ACCESSORY) ++int tristar_accessory_probe(struct device *dev, struct i2c_client *tristar, ++ struct mutex *lock, struct tristar_accessory **accp); ++void tristar_accessory_start(struct tristar_accessory *acc); ++void tristar_accessory_stop(struct tristar_accessory *acc); ++void tristar_accessory_report(struct tristar_accessory *acc, unsigned int usb_ma, ++ bool mux_fault); ++const char *tristar_accessory_holds(struct tristar_accessory *acc, unsigned int *ma); ++#else ++static inline int tristar_accessory_probe(struct device *dev, struct i2c_client *tristar, ++ struct mutex *lock, ++ struct tristar_accessory **accp) ++{ ++ return 0; ++} ++ ++static inline void tristar_accessory_start(struct tristar_accessory *acc) ++{ ++} ++ ++static inline void tristar_accessory_stop(struct tristar_accessory *acc) ++{ ++} ++ ++static inline void tristar_accessory_report(struct tristar_accessory *acc, ++ unsigned int usb_ma, bool mux_fault) ++{ ++} ++ ++static inline const char *tristar_accessory_holds(struct tristar_accessory *acc, ++ unsigned int *ma) ++{ ++ return NULL; ++} ++#endif ++ ++#endif /* _APPLE_TRISTAR_ACCESSORY_H */ +diff --git a/drivers/power/supply/apple_tristar_charger.c b/drivers/power/supply/apple_tristar_charger.c +index f6328742a..220d4cfd2 100644 +--- a/drivers/power/supply/apple_tristar_charger.c ++++ b/drivers/power/supply/apple_tristar_charger.c +@@ -54,6 +54,8 @@ + #include + #include + ++#include "apple_tristar_accessory.h" ++ + /* PMIC registers, relative to their banks */ + #define TRISTAR_CHG_STATUS 0x0a /* status bank */ + #define TRISTAR_CHG_STATUS_ONLINE 0x42 +@@ -169,6 +171,10 @@ struct tristar_charger { + u64 step_ns; + u64 guard_ns; + unsigned int epoch_seen; ++ /* Lightning accessory policy, on boards with a role switch */ ++ struct tristar_accessory *acc; ++ /* USB data is on, but the device controller is held off for a USB host role. */ ++ bool usb_held; + }; + + static unsigned int tristar_code_ma(unsigned int code) +@@ -609,6 +615,7 @@ static int tristar_identify_usba(struct tristar_charger *chg, unsigned int orien + static int tristar_identify(struct tristar_charger *chg, struct tristar_ident *id) + { + unsigned int epoch = READ_ONCE(chg->epoch), orientation; ++ const char *holder; + bool usbc; + u8 route, state; + int ret; +@@ -616,6 +623,18 @@ static int tristar_identify(struct tristar_charger *chg, struct tristar_ident *i + id->ma = 0; + id->type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + id->sampled = false; ++ holder = tristar_accessory_holds(chg->acc, &id->ma); ++ if (holder) { ++ /* ++ * The accessory policy holds the port: the mux is not ours to ++ * sample, and no cable is known to compare after a source event. ++ */ ++ id->type = POWER_SUPPLY_USB_TYPE_ACA; ++ id->usbc = false; ++ id->orientation = 0; ++ strscpy(id->what, holder, sizeof(id->what)); ++ return 0; ++ } + ret = cbtl_read(chg, CBTL_MUX_ROUTE, &route, 1); + if (!ret) + ret = cbtl_read(chg, CBTL_MUX_STATE, &state, 1); +@@ -653,13 +672,19 @@ static unsigned int tristar_usb_ma(struct tristar_charger *chg) + + /* + * Turn USB data on or hold it off. A failed hold counts as on: the mux is +- * then left alone. Caller holds lock. ++ * then left alone. While the accessory policy holds the port as a USB host, ++ * the device controller stays off even with USB data on; the next poll after ++ * the policy lets go turns it on. Stopping always turns it on. Caller holds ++ * lock. + */ + static void tristar_set_usb(struct tristar_charger *chg, bool on) + { ++ unsigned int ma; ++ bool held = on && !chg->stopping && tristar_accessory_holds(chg->acc, &ma); + int ret = usb_role_switch_set_role(chg->role_sw, +- on ? USB_ROLE_DEVICE : USB_ROLE_NONE); ++ on && !held ? USB_ROLE_DEVICE : USB_ROLE_NONE); + ++ chg->usb_held = held && !ret; + if (ret) + dev_err(chg->dev, "cannot turn USB data %s (%d)\n", on ? "on" : "off", ret); + chg->usb_on = on || ret; +@@ -692,6 +717,15 @@ static void tristar_update_target(struct tristar_charger *chg) + } + } + ++/* Turn on a device controller held off for a host role the policy gave back. Caller holds lock. */ ++static void tristar_unhold_usb(struct tristar_charger *chg) ++{ ++ unsigned int ma; ++ ++ if (chg->usb_held && !tristar_accessory_holds(chg->acc, &ma)) ++ tristar_set_usb(chg, true); ++} ++ + /* Caller holds lock. */ + static void tristar_new_attachment(struct tristar_charger *chg, u64 now) + { +@@ -923,6 +957,38 @@ static void tristar_no_source(struct tristar_charger *chg, unsigned int status, + tristar_set_usb(chg, true); + } + ++/* ++ * While the accessory policy holds the port (the camera adapter in USB host ++ * mode), the source behind the adapter cannot be identified: take the ++ * policy's allowance. Once the policy lets go, identify the source again, or ++ * turn on the device controller that tristar_set_usb() held off. Caller ++ * holds lock. ++ */ ++static void tristar_follow_accessory(struct tristar_charger *chg, u64 now) ++{ ++ bool charging = chg->phase == TRISTAR_RAMPING || chg->phase == TRISTAR_CHARGING; ++ const char *holder; ++ unsigned int ma; ++ ++ if (!IS_ENABLED(CONFIG_APPLE_TRISTAR_ACCESSORY)) ++ return; ++ holder = tristar_accessory_holds(chg->acc, &ma); ++ if (!holder) { ++ if (charging && chg->usb_type == POWER_SUPPLY_USB_TYPE_ACA) ++ tristar_new_attachment(chg, now); ++ else ++ tristar_unhold_usb(chg); ++ return; ++ } ++ if (!charging || (chg->usb_type == POWER_SUPPLY_USB_TYPE_ACA && chg->source_ma == ma)) ++ return; ++ strscpy(chg->source, holder, sizeof(chg->source)); ++ chg->source_ma = ma; ++ chg->usb_type = POWER_SUPPLY_USB_TYPE_ACA; ++ tristar_update_target(chg); ++ dev_dbg(chg->dev, "%s: %u mA\n", holder, ma); ++} ++ + static void tristar_poll(struct work_struct *work) + { + struct tristar_charger *chg = container_of(to_delayed_work(work), +@@ -975,6 +1041,8 @@ static void tristar_poll(struct work_struct *work) + /* USB data must not depend on charging working. */ + if (!chg->usb_on) + tristar_set_usb(chg, true); ++ else ++ tristar_unhold_usb(chg); + goto again; + } + if (READ_ONCE(chg->epoch) != chg->epoch_seen && chg->phase != TRISTAR_SETTLING) { +@@ -1002,6 +1070,7 @@ static void tristar_poll(struct work_struct *work) + chg->step_ns = 0; + } + } ++ tristar_follow_accessory(chg, now); + switch (chg->phase) { + case TRISTAR_SETTLING: + if ((s64)(now - chg->attach_ns) >= (s64)TRISTAR_LOOK_MS * NSEC_PER_MSEC) +@@ -1017,6 +1086,7 @@ static void tristar_poll(struct work_struct *work) + break; + } + again: ++ tristar_accessory_report(chg->acc, chg->usb_ma, chg->phase == TRISTAR_FAULT); + mod_delayed_work(chg->wq, &chg->poll_work, + msecs_to_jiffies(TRISTAR_POLL_MS)); + if (chg->code != old_code || chg->online != old_online) +@@ -1205,7 +1275,9 @@ static const struct power_supply_desc tristar_desc = { + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | +- BIT(POWER_SUPPLY_USB_TYPE_C), ++ BIT(POWER_SUPPLY_USB_TYPE_C) | ++ (IS_ENABLED(CONFIG_APPLE_TRISTAR_ACCESSORY) ? ++ BIT(POWER_SUPPLY_USB_TYPE_ACA) : 0), + .properties = tristar_properties, + .num_properties = ARRAY_SIZE(tristar_properties), + .get_property = tristar_get_property, +@@ -1253,6 +1325,7 @@ static void tristar_stop(struct tristar_charger *chg, const char *reason) + for (i = 0; i < chg->nirqs; i++) + disable_irq(chg->irqs[i]); + cancel_delayed_work_sync(&chg->poll_work); ++ tristar_accessory_stop(chg->acc); + mutex_lock(&chg->lock); + if (tristar_set_code(chg, TRISTAR_CHG_HANDOFF_CODE)) + dev_crit(chg->dev, "%s: handoff restore failed\n", reason); +@@ -1346,6 +1419,9 @@ static int tristar_probe(struct platform_device *pdev) + if (!chg->wq) + return -ENOMEM; + INIT_DELAYED_WORK(&chg->poll_work, tristar_poll); ++ ret = tristar_accessory_probe(dev, chg->tristar, &chg->lock, &chg->acc); ++ if (ret) ++ return ret; + + /* Never adopt a limit we cannot explain; anything higher drops to the fallback. */ + ret = regmap_read(chg->regmap, chg->control_base + TRISTAR_CHG_INPUT_LIMIT, &code); +@@ -1394,6 +1470,7 @@ static int tristar_probe(struct platform_device *pdev) + mutex_unlock(&chg->lock); + + mod_delayed_work(chg->wq, &chg->poll_work, 0); ++ tristar_accessory_start(chg->acc); + return 0; + } + +@@ -1438,11 +1515,13 @@ static int tristar_suspend(struct device *dev) + if (ret) + return ret; + cancel_delayed_work_sync(&chg->poll_work); ++ tristar_accessory_stop(chg->acc); + if (chg->plug_irq && device_may_wakeup(dev)) { + ret = enable_irq_wake(chg->plug_irq); + if (ret) + dev_warn(dev, "cannot wake on plug events: %d\n", ret); + } ++ + return 0; + } + +@@ -1456,6 +1535,7 @@ static int tristar_resume(struct device *dev) + chg->stopping = false; + mutex_unlock(&chg->lock); + mod_delayed_work(chg->wq, &chg->poll_work, 0); ++ tristar_accessory_start(chg->acc); + return 0; + } + +diff --git a/include/linux/usb/apple-s8000-role.h b/include/linux/usb/apple-s8000-role.h +new file mode 100644 +index 000000000..dcf019faf +--- /dev/null ++++ b/include/linux/usb/apple-s8000-role.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Apple S8000 (A9) and T8010 (A10) Lightning port role switch: the interface ++ * of the Lightning accessory policy (apple_tristar_charger). ++ * ++ * The policy is the only user that sets the host role. It powers the ++ * accessory before it identifies it, and it samples the Tristar through the ++ * switch so that no role change overlaps the sample. ++ */ ++#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_accessory_power(struct usb_role_switch *sw, bool on); ++#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_accessory_power(struct usb_role_switch *sw, bool on) ++{ ++ return -EOPNOTSUPP; ++} ++#endif ++ ++#endif /* __LINUX_USB_APPLE_S8000_ROLE_H */ diff --git a/kernel/patches/drivers/usb/0011-dt-bindings-usb-Add-the-Apple-S8000-USB-role-switch.patch b/kernel/patches/drivers/usb/0011-dt-bindings-usb-Add-the-Apple-S8000-USB-role-switch.patch new file mode 100644 index 00000000..7ce9e434 --- /dev/null +++ b/kernel/patches/drivers/usb/0011-dt-bindings-usb-Add-the-Apple-S8000-USB-role-switch.patch @@ -0,0 +1,165 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 16:50:08 +0100 +Subject: [PATCH] dt-bindings: usb: Add the Apple S8000 USB role switch + +The Apple A9 (S8000/S8003) and A10 (T8010) USB complex has a device-only +DWC2 and a separate EHCI + OHCI host pair behind one AUSB PHY. A USB_CTL +register bank selects whether the PHY serves the host pair. On the +Lightning iPads the port reaches the host pair only through this switch. + +Describe the switch: its USB_CTL bank, the shared PHY, the DWC2 whose +gadget it disconnects in the host role, and the EHCI and OHCI child +nodes, which the switch creates only in the host role. A Lightning board +also names the PMIC with the accessory power LDO and the Tristar port +controller, whose ACC pin routing is set up before accessory power is +turned on. + +[Orchard: from orchard PR #126, commit 864b4971; T8010 compatible added, + whose USB complex, USB_CTL bank (0x20e0d8000) and PHY tunables the + Apple device trees of J71b, J72b and J171 give as on J71t] + +Assisted-by: LLM +--- + .../usb/apple,s8000-usb-role-switch.yaml | 132 ++++++++++++++++++ + 1 file changed, 132 insertions(+) + create mode 100644 Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml + +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 000000000..a53b23c04 +--- /dev/null ++++ b/Documentation/devicetree/bindings/usb/apple,s8000-usb-role-switch.yaml +@@ -0,0 +1,132 @@ ++# 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 and T8010 USB role switch ++ ++maintainers: ++ - Berkay Kösebay ++ ++description: | ++ The Apple A9 (S8000/S8003) and A10 (T8010) 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. ++ ++ On a Lightning port, the host role for an accessory such as the camera ++ adapter also needs accessory power from the PMIC and the ACC pin routing ++ of the Tristar port controller. ++ ++properties: ++ compatible: ++ oneOf: ++ - const: apple,s8000-usb-role-switch ++ - items: ++ - const: apple,t8010-usb-role-switch ++ - const: apple,s8000-usb-role-switch ++ ++ reg: ++ items: ++ - description: USB_CTL register bank ++ ++ "#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. Its gadget is ++ disconnected while the port is a host. ++ ++ apple,pmic: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: ++ PMIC with the Lightning accessory power LDO and its switch. Without it ++ the host role does not power accessories. ++ ++ apple,tristar: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: ++ Tristar (CBTL1610) Lightning port controller whose ACC pin routing is ++ set up before accessory power is turned on. Required for accessory ++ power. ++ ++patternProperties: ++ "^usb@[0-9a-f]+$": ++ type: object ++ description: The EHCI and its OHCI companion. ++ ++required: ++ - compatible ++ - reg ++ - "#address-cells" ++ - "#size-cells" ++ - ranges ++ - phys ++ - phy-names ++ - apple,usb-device ++ ++dependentRequired: ++ apple,pmic: [ "apple,tristar" ] ++ ++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>; ++ #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 = <&tristar>; ++ ++ 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/kernel/patches/drivers/usb/0012-usb-roles-Add-the-Apple-A9-A10-Lightning-port-role-s.patch b/kernel/patches/drivers/usb/0012-usb-roles-Add-the-Apple-A9-A10-Lightning-port-role-s.patch new file mode 100644 index 00000000..fa063d2d --- /dev/null +++ b/kernel/patches/drivers/usb/0012-usb-roles-Add-the-Apple-A9-A10-Lightning-port-role-s.patch @@ -0,0 +1,1060 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20K=C3=B6sebay?= +Date: Fri, 30 Oct 2026 16:50:08 +0100 +Subject: [PATCH] usb: roles: Add the Apple A9/A10 Lightning port role switch + +The A9 and A10 USB complex pairs a device-only DWC2 with a separate EHCI ++ OHCI host pair behind one AUSB PHY. On the Lightning iPads the port is +a USB device by default; the camera adapter needs it to be a host. + +Add a usb_role switch for the port. Device mode stays the default and +needs no register access. Host mode follows the iOS order: soft- +disconnect the gadget (it stays bound, as iOS keeps the device +controller published), make sure the complex's DMA remap words for the +host pair are set, power the PHY, create EHCI and then OHCI from the +switch's child nodes so that their power domains are on, set USB_CTL bit +1 and then host select, and switch the PHY to its host tunables. Leaving +host mode undoes the steps in reverse and reconnects the gadget, which +comes on once the device controller's own role allows it (the Lightning +charger sets that role). + +The Lightning accessory policy in the Tristar charger is the only user +of the host role; userspace cannot set it, so a USB stick in use is +never pulled away by anything but the adapter's unplug. Before the +policy's ID-bus identification and on host entry, the switch powers the +accessory as iOS does: it routes the Tristar ACC pin to the power rail +and the data lanes for USB (ACC_CTRL 0x30 and dxctrl 0xa1 for the +adapter in orientation 1, through an intermediate dxctrl byte when a +lane pair changes), then enables the PMIC's accessory LDO, its switch +and its sleep enable. The Tristar bytes it found are restored when the +power goes off. The policy also samples the Tristar through the switch, +so that no role change overlaps the sample. System sleep is refused in +the host role. + +Tested on an iPad 5 (J71t) with the Lightning to USB camera adapter: a +USB stick, a powered USB-C hub with an SD card reader and RTL8152 +Ethernet enumerate, and unplugging returns the port to the USB gadget. + +[Orchard: from orchard PR #126, commit 864b4971; the host role is no + longer settable from userspace, which also drops the claim interface + and the 60 s return of a manual host role (patch 0018 there)] + +Assisted-by: LLM +--- + drivers/usb/roles/Kconfig | 15 + + drivers/usb/roles/Makefile | 1 + + drivers/usb/roles/apple-s8000-tristar-route.h | 87 ++ + drivers/usb/roles/apple-s8000-usb-role.c | 877 ++++++++++++++++++ + 4 files changed, 980 insertions(+) + create mode 100644 drivers/usb/roles/apple-s8000-tristar-route.h + create mode 100644 drivers/usb/roles/apple-s8000-usb-role.c + +diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig +index f8b31aa67..5de1699ce 100644 +--- a/drivers/usb/roles/Kconfig ++++ b/drivers/usb/roles/Kconfig +@@ -26,4 +26,19 @@ 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 A9 and A10 Lightning port role switch" ++ depends on ARCH_APPLE || COMPILE_TEST ++ depends on OF && I2C && USB_GADGET ++ select REGMAP ++ help ++ Role switch for the Apple A9 and A10 USB complex, which pairs a ++ device-only DWC2 with a separate EHCI + OHCI host pair behind one ++ PHY. Device mode is the default; the Lightning accessory policy of ++ the Tristar charger enters host mode while the camera adapter is ++ attached. ++ ++ 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 757a7d279..0b0b0968c 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-tristar-route.h b/drivers/usb/roles/apple-s8000-tristar-route.h +new file mode 100644 +index 000000000..bb477dc1d +--- /dev/null ++++ b/drivers/usb/roles/apple-s8000-tristar-route.h +@@ -0,0 +1,87 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Tristar (CBTL1610) transport route and ACC pin configuration for the ++ * Lightning camera adapter, digital ID 11 f0 00 00 00 00 (USB 2, UART 1, ++ * power f). Pure arithmetic, no I/O. ++ * ++ * As iOS does for this ID: ++ * - The ID decodes to the transports {usb0, urt0} and the ACC roles ++ * {idbs, apwr}. Both pairs swap when the plug is in orientation 2. ++ * - Orientation (status register 0x0c): 0 unless bit 7, else 1 + bit 6. ++ * - dxctrl (register 0x01): the low nibble routes the first lane pair, the ++ * high nibble the second. usb0 is 1, urt0 is 2 (low) or 0x20 (high). ++ * Bit 3 / bit 7 mark a pair that differs from the default, which is USB ++ * on the oriented pair and the other pair open. The current byte is ++ * first normalised to that default; when a pair changes, an ++ * intermediate byte with that pair's override bit is written before ++ * the final one. ++ * - ACC_CTRL (register 0x02): ACC1 nibble idbs 0, urt1 1, jtag 2, apwr 3; ++ * ACC2 the same, shifted left by 4. ++ * ++ * For the adapter in orientation 1 this gives ACC_CTRL 0x30 and dxctrl 0xa1. ++ */ ++#ifndef APPLE_S8000_TRISTAR_ROUTE_H ++#define APPLE_S8000_TRISTAR_ROUTE_H ++ ++#include ++ ++#define TRISTAR_ROUTE_STATUS_PRESENT 0x80 ++#define TRISTAR_ROUTE_STATUS_ORIENT2 0x40 ++ ++#define TRISTAR_ROUTE_ACC_O1 0x30 /* ACC1 idbs, ACC2 apwr */ ++#define TRISTAR_ROUTE_ACC_O2 0x03 /* ACC1 apwr, ACC2 idbs */ ++#define TRISTAR_ROUTE_DX_O1 0xa1 /* usb0 | urt0 << 4 | override of pair 2 */ ++#define TRISTAR_ROUTE_DX_O2 0x1a /* urt0 | usb0 << 4 | override of pair 1 */ ++ ++/** ++ * struct tristar_route_plan - Tristar writes for the camera adapter ++ * @orientation: 0 without a plug, else 1 or 2 ++ * @acc: ACC_CTRL value (register 0x02) ++ * @dx_first: intermediate dxctrl value (register 0x01) ++ * @dx_target: final dxctrl value ++ * @dx_write: register 0x01 differs from @dx_target ++ * @dx_two_steps: @dx_first is written before @dx_target ++ */ ++struct tristar_route_plan { ++ unsigned int orientation; ++ u8 acc; ++ u8 dx_first; ++ u8 dx_target; ++ bool dx_write; ++ bool dx_two_steps; ++}; ++ ++static inline unsigned int tristar_route_orientation(u8 status) ++{ ++ if (!(status & TRISTAR_ROUTE_STATUS_PRESENT)) ++ return 0; ++ return status & TRISTAR_ROUTE_STATUS_ORIENT2 ? 2 : 1; ++} ++ ++/* @status is register 0x0c and @dx_cur register 0x01. False without a plug. */ ++static inline bool tristar_route_plan_for(u8 status, u8 dx_cur, ++ struct tristar_route_plan *plan) ++{ ++ u8 norm = dx_cur, lo, hi; ++ ++ plan->orientation = tristar_route_orientation(status); ++ if (!plan->orientation) ++ return false; ++ plan->acc = plan->orientation == 1 ? TRISTAR_ROUTE_ACC_O1 : TRISTAR_ROUTE_ACC_O2; ++ plan->dx_target = plan->orientation == 1 ? TRISTAR_ROUTE_DX_O1 : TRISTAR_ROUTE_DX_O2; ++ plan->dx_write = dx_cur != plan->dx_target; ++ /* A pair without its override bit is at the default: USB on the oriented pair. */ ++ if (plan->orientation == 1) { ++ if (!(norm & 0x08)) ++ norm = (norm & ~0x07) | 0x01; ++ } else if (!(norm & 0x80)) { ++ norm = (norm & ~0x70) | 0x10; ++ } ++ lo = (plan->dx_target ^ norm) & 0x07 ? 0x08 : plan->dx_target & 0x0f; ++ hi = (plan->dx_target ^ norm) & 0x70 ? 0x80 : plan->dx_target & 0xf0; ++ plan->dx_first = lo | hi; ++ plan->dx_two_steps = plan->dx_write && plan->dx_first != plan->dx_target; ++ return true; ++} ++ ++#endif /* APPLE_S8000_TRISTAR_ROUTE_H */ +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 000000000..d653019f9 +--- /dev/null ++++ b/drivers/usb/roles/apple-s8000-usb-role.c +@@ -0,0 +1,877 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Apple S8000 (A9) and T8010 (A10) Lightning port USB role switch. ++ * ++ * The A9 and A10 USB complex has a device-only DWC2 and a separate EHCI + ++ * OHCI host pair, all behind one AUSB PHY. For a USB device on the port, iOS ++ * keeps the device controller and publishes EHCI and OHCI next to it. The ++ * USB_CTL register bank moves the PHY to the host pair with bit 0; bit 1 is ++ * set while the PHY is powered for the host pair. ++ * ++ * Device mode is the default. The Lightning accessory policy of the Tristar ++ * charger sets the host role while the camera adapter is attached (see ++ * ). Host entry also powers the accessory ++ * through the PMIC's accessory LDO (LDO5) and its switch, after routing the ++ * Tristar ACC pin to the power rail, as iOS does. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apple-s8000-tristar-route.h" ++ ++/* USB_CTL */ ++#define USB_CTL_HOST 0x00 ++#define USB_CTL_HOST_EN BIT(0) ++/* Set while the PHY is powered for the host pair. */ ++#define USB_CTL_PHY_ON BIT(1) ++ ++/* ++ * USB complex DMA remap words, owned and programmed at probe by the parent ++ * usb-complex driver. ++ */ ++#define REMAP_EHCI0 0x3c ++#define REMAP_OHCI0 0x5c ++#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)) ++ ++/* D2365 accessory LDO (LDO5) */ ++#define D2365_LDO5_ENABLE 0x0310 ++#define D2365_LDO5_EN BIT(0) ++/* Keeps the rail up while the system sleeps. */ ++#define D2365_LDO5_SLEEP_EN GENMASK(3, 1) ++#define D2365_LDO5_SWITCH 0x2603 ++ ++/* CBTL1610 (Tristar) */ ++#define TRISTAR_DX_CONTROL 0x01 ++#define TRISTAR_ACC_CTRL 0x02 ++#define TRISTAR_STATUS 0x0c ++#define TRISTAR_REVISION 0x0f ++/* The revision whose ACC and route encoding is implemented here. */ ++#define TRISTAR_CBTL1610_REVISION 0x8a ++#define TRISTAR_ROUTE_SETTLE_MS 5 ++ ++/* ++ * The port has one switch. The accessory API checks a switch handed to it ++ * against this instance, under the lock, so that an unbound switch is never ++ * dereferenced. ++ */ ++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; ++ ++ struct phy *phy; ++ struct platform_device *udc; ++ struct device_node *ehci_np; ++ struct device_node *ohci_np; ++ struct regmap *pmic; ++ struct i2c_client *tristar; ++ ++ struct usb_role_switch *sw; ++ ++ /* Serializes role changes, accessory power and the state below. */ ++ struct mutex lock; ++ enum usb_role role; ++ struct device *gadget; ++ struct platform_device *ehci; ++ struct platform_device *ohci; ++ bool phy_held; ++ bool ctl_host; ++ bool ctl_phy_on; ++ /* LDO5 and its switch were (perhaps partly) turned on. */ ++ bool acc_power; ++ bool sleep_power_on; ++ /* Accessory power was applied before the policy's ID-bus reads. */ ++ bool accessory_power_prepared; ++ /* Tristar 0x01/0x02 were changed for the adapter; the saved bytes restore them. */ ++ bool acc_applied; ++ u8 acc_saved; ++ u8 dx_saved; ++ bool dying; ++ bool monitor_fault; ++}; ++ ++static void apple_usb_role_iounmap(void *addr) ++{ ++ iounmap(addr); ++} ++ ++/* Map a register range without claiming it: another driver claims it. */ ++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 */ ++ ++static int apple_usb_role_tristar_read(struct apple_usb_role *ur, u8 reg) ++{ ++ struct i2c_client *client = ur->tristar; ++ union i2c_smbus_data data; ++ int ret; ++ ++ /* Register selector and STOP, then a separate read. */ ++ 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_usb_role_tristar_write(struct apple_usb_role *ur, u8 reg, u8 val) ++{ ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(ur->tristar, reg, val); ++ if (ret) ++ return ret; ++ msleep(TRISTAR_ROUTE_SETTLE_MS); ++ ret = apple_usb_role_tristar_read(ur, reg); ++ if (ret < 0) ++ return ret; ++ return ret == val ? 0 : -EIO; ++} ++ ++static int apple_usb_role_ldo5(struct apple_usb_role *ur, bool on) ++{ ++ 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) ++{ ++ /* iOS writes the whole register: 1 or 0. */ ++ return regmap_write(ur->pmic, D2365_LDO5_SWITCH, on); ++} ++ ++static int apple_usb_role_ldo5_sleep(struct apple_usb_role *ur, bool on) ++{ ++ return regmap_update_bits(ur->pmic, D2365_LDO5_ENABLE, ++ D2365_LDO5_SLEEP_EN, ++ on ? D2365_LDO5_SLEEP_EN : 0); ++} ++ ++/* ++ * iOS applies the ACC and route configuration when the accessory attaches ++ * and again before it enables accessory power, so that the ACC pin carries ++ * the power rail (apwr) before LDO5 comes on. The values and the dxctrl ++ * write order are in apple-s8000-tristar-route.h. ++ */ ++static int apple_usb_role_acc_config(struct apple_usb_role *ur) ++{ ++ struct tristar_route_plan plan; ++ int ret, status; ++ ++ if (ur->acc_applied) ++ return 0; ++ if (!ur->tristar) ++ return -ENODEV; ++ ret = apple_usb_role_tristar_read(ur, TRISTAR_REVISION); ++ if (ret < 0) ++ return ret; ++ if (ret != TRISTAR_CBTL1610_REVISION) ++ return -ENODEV; ++ status = apple_usb_role_tristar_read(ur, TRISTAR_STATUS); ++ if (status < 0) ++ return status; ++ ret = apple_usb_role_tristar_read(ur, TRISTAR_DX_CONTROL); ++ if (ret < 0) ++ return ret; ++ ur->dx_saved = ret; ++ ret = apple_usb_role_tristar_read(ur, TRISTAR_ACC_CTRL); ++ if (ret < 0) ++ return ret; ++ ur->acc_saved = ret; ++ if (!tristar_route_plan_for(status, ur->dx_saved, &plan)) { ++ dev_warn(ur->dev, "Tristar reports no plug (status %#04x)\n", status); ++ return -ENOLINK; ++ } ++ /* From the first write on, teardown restores both registers. */ ++ ur->acc_applied = true; ++ ret = 0; ++ if (plan.dx_write) { ++ if (plan.dx_two_steps) ++ ret = apple_usb_role_tristar_write(ur, TRISTAR_DX_CONTROL, plan.dx_first); ++ if (!ret) ++ ret = apple_usb_role_tristar_write(ur, TRISTAR_DX_CONTROL, plan.dx_target); ++ if (ret) ++ return ret; ++ } ++ if (ur->acc_saved != plan.acc) ++ ret = apple_usb_role_tristar_write(ur, TRISTAR_ACC_CTRL, plan.acc); ++ dev_dbg(ur->dev, "orientation %u: dxctrl %#04x -> %#04x, ACC_CTRL %#04x -> %#04x (%d)\n", ++ plan.orientation, ur->dx_saved, plan.dx_target, ur->acc_saved, ++ plan.acc, ret); ++ return ret; ++} ++ ++static void apple_usb_role_acc_restore_reg(struct apple_usb_role *ur, u8 reg, u8 saved) ++{ ++ int ret = apple_usb_role_tristar_read(ur, reg); ++ ++ if (ret == saved) ++ return; ++ if (ret >= 0) ++ ret = apple_usb_role_tristar_write(ur, reg, saved); ++ if (ret < 0) ++ dev_err(ur->dev, "Tristar register %#04x restore: %d\n", reg, ret); ++} ++ ++static void apple_usb_role_acc_restore(struct apple_usb_role *ur) ++{ ++ if (!ur->acc_applied) ++ return; ++ ur->acc_applied = false; ++ /* Reverse of the apply order: ACC_CTRL, then dxctrl. */ ++ apple_usb_role_acc_restore_reg(ur, TRISTAR_ACC_CTRL, ur->acc_saved); ++ apple_usb_role_acc_restore_reg(ur, TRISTAR_DX_CONTROL, ur->dx_saved); ++} ++ ++static void apple_usb_role_acc_power_off(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ if (ur->acc_power) { ++ if (ur->sleep_power_on) { ++ ret = apple_usb_role_ldo5_sleep(ur, false); ++ if (ret) ++ dev_err(ur->dev, "LDO5 sleep power off: %d\n", ret); ++ ur->sleep_power_on = false; ++ } ++ 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->acc_power = false; ++ } ++ apple_usb_role_acc_restore(ur); ++} ++ ++/* ++ * The power iOS gives the camera adapter: ACC pin routing first, then the ++ * rail, its switch and its sleep enable, before the hub enumerates. Without ++ * a PMIC there is nothing to switch. ++ */ ++static int apple_usb_role_acc_power_on(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ if (!ur->pmic) ++ return 0; ++ ret = apple_usb_role_acc_config(ur); ++ if (ret) { ++ dev_err(ur->dev, "Tristar ACC configuration: %d\n", ret); ++ return ret; ++ } ++ ur->acc_power = true; ++ ret = apple_usb_role_ldo5(ur, true); ++ if (!ret) ++ ret = apple_usb_role_ldo5_switch(ur, true); ++ if (!ret) { ++ ret = apple_usb_role_ldo5_sleep(ur, true); ++ if (!ret) ++ ur->sleep_power_on = true; ++ } ++ if (ret) ++ dev_err(ur->dev, "accessory power (LDO5): %d\n", ret); ++ return ret; ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Role changes */ ++ ++static int apple_usb_role_is_gadget(struct device *dev, const void *data) ++{ ++ return dev->bus && !strcmp(dev->bus->name, "gadget"); ++} ++ ++/* ++ * iOS leaves the device controller published while its host controllers ++ * are active. Keep the gadget driver bound and drop only its pull-up. ++ */ ++static int apple_usb_role_release_gadget(struct apple_usb_role *ur) ++{ ++ struct device *gadget; ++ int ret; ++ ++ gadget = device_find_child(&ur->udc->dev, NULL, apple_usb_role_is_gadget); ++ if (!gadget) ++ return 0; ++ if (!gadget->driver) { ++ /* Nothing to give back later either. */ ++ put_device(gadget); ++ return 0; ++ } ++ ret = usb_gadget_disconnect(dev_to_usb_gadget(gadget)); ++ if (ret) { ++ dev_err(ur->dev, "gadget soft disconnect: %d\n", ret); ++ put_device(gadget); ++ return ret; ++ } ++ ur->gadget = gadget; ++ return 0; ++} ++ ++static void apple_usb_role_restore_gadget(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ if (!ur->gadget) ++ return; ++ ret = usb_gadget_connect(dev_to_usb_gadget(ur->gadget)); ++ if (ret) ++ dev_err(ur->dev, "gadget soft reconnect: %d\n", ret); ++ 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) ++{ ++ if (readl(ur->remap + offset) != REMAP_TO_DRAM) ++ writel(REMAP_TO_DRAM, 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; ++} ++ ++static int apple_usb_role_add_hcs(struct apple_usb_role *ur) ++{ ++ /* EHCI before its OHCI companion, as for any companion pair. */ ++ ur->ehci = apple_usb_role_add_hc(ur, ur->ehci_np); ++ if (!ur->ehci) ++ return -ENODEV; ++ 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"); ++ return 0; ++} ++ ++static void apple_usb_role_ctl_clear(struct apple_usb_role *ur) ++{ ++ /* As iOS leaves host mode: host select first, then 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; ++ } ++} ++ ++/* Undo whatever enter_host() got to, in reverse order. Idempotent. */ ++static void apple_usb_role_host_off(struct apple_usb_role *ur) ++{ ++ int ret; ++ ++ 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; ++ } ++ apple_usb_role_ctl_clear(ur); ++ if (ur->phy_held) { ++ ret = phy_set_mode(ur->phy, PHY_MODE_USB_DEVICE); ++ if (ret) ++ dev_err(ur->dev, "PHY device mode: %d\n", ret); ++ phy_power_off(ur->phy); ++ phy_exit(ur->phy); ++ ur->phy_held = false; ++ } ++ apple_usb_role_acc_power_off(ur); ++ ur->accessory_power_prepared = false; ++} ++ ++static int apple_usb_role_enter_host(struct apple_usb_role *ur) ++{ ++ u32 ctl; ++ int ret; ++ ++ ret = apple_usb_role_release_gadget(ur); ++ if (ret) ++ goto fail; ++ ++ ret = apple_usb_role_acc_power_on(ur); ++ if (ret) ++ goto fail; ++ ++ apple_usb_role_remap(ur, REMAP_EHCI0); ++ apple_usb_role_remap(ur, REMAP_OHCI0); ++ ++ 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; ++ ++ /* ++ * iOS creates EHCI and OHCI before it selects host mode, so their ++ * power domains are on when USB_CTL bit 1 is written: with the domains ++ * still off the hardware discards the bit. ++ */ ++ ret = apple_usb_role_add_hcs(ur); ++ if (ret) ++ goto fail; ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ if (!(ctl & USB_CTL_PHY_ON)) { ++ writel(ctl | USB_CTL_PHY_ON, ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_phy_on = true; ++ } ++ ++ /* Host select, then the PHY's host tunables and reset. */ ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ writel(ctl | USB_CTL_PHY_ON | USB_CTL_HOST_EN, ur->usb_ctl + USB_CTL_HOST); ++ ur->ctl_host = true; ++ ret = phy_set_mode(ur->phy, PHY_MODE_USB_HOST); ++ if (ret) ++ goto fail; ++ ++ /* If something dropped bit 1 on the way, assert it once more. */ ++ ctl = readl(ur->usb_ctl + USB_CTL_HOST); ++ if (!(ctl & USB_CTL_PHY_ON)) ++ writel(ctl | USB_CTL_PHY_ON, ur->usb_ctl + USB_CTL_HOST); ++ ++ dev_dbg(ur->dev, "host mode\n"); ++ 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_dbg(ur->dev, "device mode\n"); ++} ++ ++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 == USB_ROLE_HOST && ur->role != USB_ROLE_HOST) { ++ ret = apple_usb_role_enter_host(ur); ++ if (!ret) { ++ WRITE_ONCE(ur->role, USB_ROLE_HOST); ++ ur->accessory_power_prepared = false; ++ } ++ } else if (role == USB_ROLE_DEVICE && ur->role != USB_ROLE_DEVICE) { ++ apple_usb_role_enter_device(ur); ++ WRITE_ONCE(ur->role, USB_ROLE_DEVICE); ++ } ++ mutex_unlock(&ur->lock); ++ return ret; ++} ++ ++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); ++} ++ ++/* 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_accessory_power - apply or release accessory power ++ * @sw: the Lightning role switch ++ * @on: apply before ID-bus identification, or unwind after a failed sequence ++ * ++ * iOS powers the accessory before it reads the accessory's information or ++ * changes the USB mode. This only sets up the local PMIC and Tristar state; ++ * it sends nothing on the ID bus. A successful host entry takes the power ++ * over, and leaving host mode turns it off. ++ * ++ * Return: 0, -EBUSY outside the device role, -ESHUTDOWN while the switch ++ * goes away, -ENODEV if @sw is not this driver's switch, or the error of a ++ * failed PMIC or Tristar access. ++ */ ++int apple_s8000_usb_role_accessory_power(struct usb_role_switch *sw, bool on) ++{ ++ 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) { ++ ret = -ESHUTDOWN; ++ } else if (ur->role != USB_ROLE_DEVICE) { ++ ret = -EBUSY; ++ } else if (on && !ur->accessory_power_prepared) { ++ ret = apple_usb_role_acc_power_on(ur); ++ if (ret) ++ apple_usb_role_acc_power_off(ur); ++ else ++ ur->accessory_power_prepared = true; ++ } else if (!on && ur->accessory_power_prepared) { ++ apple_usb_role_acc_power_off(ur); ++ ur->accessory_power_prepared = false; ++ ret = 0; ++ } else { ++ ret = 0; ++ } ++ mutex_unlock(&ur->lock); ++ } ++ mutex_unlock(&apple_usb_role_instance_lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(apple_s8000_usb_role_accessory_power); ++ ++/** ++ * apple_s8000_usb_role_monitor - serialize a Tristar sample 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, then the caller's locks taken in @sample. The ++ * callback must restore DX_CONTROL before it returns. If it returns -EUCLEAN ++ * (route left broken), host entry is refused until a later callback succeeds. ++ * No role change can overlap the callback. ++ * ++ * Return: the callback's result, -EBUSY outside the device role, or -ENODEV ++ * if @sw is not this driver's switch. ++ */ ++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); ++ ++/* -------------------------------------------------------------------- */ ++/* 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_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 device_node *np; ++ ++ np = of_parse_phandle(ur->dev->of_node, "apple,tristar", 0); ++ if (!np) ++ return 0; ++ ur->tristar = of_find_i2c_device_by_node(np); ++ of_node_put(np); ++ if (!ur->tristar) ++ return -EPROBE_DEFER; ++ return devm_add_action_or_reset(ur->dev, apple_usb_role_put_device, ++ &ur->tristar->dev); ++} ++ ++/* The remap words belong to the parent usb-complex, whose driver claims them. */ ++static int apple_usb_role_get_complex(struct apple_usb_role *ur) ++{ ++ struct device_node *complex = of_get_parent(ur->dev->of_node); ++ ++ if (complex && of_device_is_compatible(complex, "apple,s5l8960x-usb-complex")) ++ ur->remap = apple_usb_role_view(ur, complex, 0); ++ of_node_put(complex); ++ return ur->remap ? 0 : -ENODEV; ++} ++ ++static int apple_usb_role_get_udc(struct apple_usb_role *ur) ++{ ++ struct device_node *np; ++ ++ np = of_parse_phandle(ur->dev->of_node, "apple,usb-device", 0); ++ if (!np) ++ return -EINVAL; ++ ur->udc = of_find_device_by_node(np); ++ of_node_put(np); ++ if (!ur->udc) ++ return -EPROBE_DEFER; ++ return devm_add_action_or_reset(ur->dev, apple_usb_role_put_device, &ur->udc->dev); ++} ++ ++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; ++ return devm_add_action_or_reset(ur->dev, apple_usb_role_put_node, ur->ohci_np); ++} ++ ++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; ++ 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; ++ platform_set_drvdata(pdev, ur); ++ ret = devm_mutex_init(dev, &ur->lock); ++ if (ret) ++ return ret; ++ ++ ur->usb_ctl = devm_platform_ioremap_resource(pdev, 0); ++ 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"); ++ ++ 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"); ++ ret = apple_usb_role_get_tristar(ur); ++ if (ret) ++ return dev_err_probe(dev, ret, "Tristar\n"); ++ ++ /* No register access here: device mode must work as without the switch. */ ++ desc.fwnode = dev_fwnode(dev); ++ desc.name = "lightning"; ++ desc.set = apple_usb_role_set; ++ desc.get = apple_usb_role_get; ++ 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; ++ ++ mutex_lock(&apple_usb_role_instance_lock); ++ apple_usb_role_instance = ur; ++ mutex_unlock(&apple_usb_role_instance_lock); ++ dev_info(dev, "Lightning port in device role%s\n", ++ ur->pmic ? ", accessory power available" : ""); ++ return 0; ++} ++ ++/* ++ * The accessory rail and the Tristar mux keep their state across an SoC ++ * reset, and the boot loader brings the port up as a device again: put the ++ * shared hardware back. The host controllers, children of this device, are ++ * already shut down. ++ */ ++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; ++ apple_usb_role_ctl_clear(ur); ++ if (ur->phy_held) ++ phy_set_mode(ur->phy, PHY_MODE_USB_DEVICE); ++ apple_usb_role_acc_power_off(ur); ++ mutex_unlock(&ur->lock); ++} ++ ++static int apple_usb_role_prepare(struct device *dev) ++{ ++ struct apple_usb_role *ur = dev_get_drvdata(dev); ++ ++ /* The role cannot change 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, ++ .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/schemas/v1/device.schema.json b/schemas/v1/device.schema.json index e1d0a630..34698a61 100644 --- a/schemas/v1/device.schema.json +++ b/schemas/v1/device.schema.json @@ -338,6 +338,14 @@ "none" ] }, + "usb_host": { + "enum": [ + "working", + "untested", + "missing", + "none" + ] + }, "audio_output": { "enum": [ "working", @@ -394,6 +402,7 @@ "resume", "usb_serial", "usb_network", + "usb_host", "audio_output", "audio_input", "camera", diff --git a/tools/catalog/feature-drivers.toml b/tools/catalog/feature-drivers.toml index 4ed8524b..2411a5eb 100644 --- a/tools/catalog/feature-drivers.toml +++ b/tools/catalog/feature-drivers.toml @@ -17,5 +17,6 @@ battery = ["bq27xxx_battery_hdq", "macsmc_hwmon"] charging = ["apple_d2365_charger", "apple_tristar_charger"] usb_serial = ["dwc2"] usb_network = ["dwc2"] +usb_host = ["apple_s8000_usb_role"] audio_output = ["snd_soc_apple_s8000_mca"] video_decode_hevc = ["apple_vxd"] From f5adf37731da50cb523bffccdb4b9bf2059a9c73 Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Fri, 13 Nov 2026 06:31:24 +0800 Subject: [PATCH 2/5] kernel: USB host through the camera adapter on the iPad 6 too Apple's device trees of the iPad 6 (J71b, J72b) match the iPad 5 in everything the camera adapter needs: the USB complex (device, EHCI and OHCI at the same offsets), the otgphyctrl bank and tunables, the D2365 and the cbtl1610 Tristar at i2c0 0x1a, and the dock accessory power on LDO5. Add the S8000 role switch to the T8010 and enable it on the iPad 6. Untested on the iPad 6; the iPad 7 keeps it off. --- devices/j71b/device.toml | 1 + devices/j72b/device.toml | 1 + docs/compatibility.md | 210 +++++++++--------- ...t8010-Add-the-USB-role-switch-and-EH.patch | 81 +++++++ ...ipad6-Enable-USB-host-on-the-Lightni.patch | 71 ++++++ 5 files changed, 262 insertions(+), 102 deletions(-) create mode 100644 kernel/patches/devices/t8010/0025-arm64-dts-apple-t8010-Add-the-USB-role-switch-and-EH.patch create mode 100644 kernel/patches/devices/t8010/0026-arm64-dts-apple-ipad6-Enable-USB-host-on-the-Lightni.patch diff --git a/devices/j71b/device.toml b/devices/j71b/device.toml index 9b19418a..c499f6c9 100644 --- a/devices/j71b/device.toml +++ b/devices/j71b/device.toml @@ -49,6 +49,7 @@ suspend = "working" resume = "working" usb_serial = "working" usb_network = "working" +usb_host = "untested" # Same Tristar, PMU rail and USB complex as J71t in the Apple device tree. audio_output = "working" audio_input = "missing" camera = "missing" diff --git a/devices/j72b/device.toml b/devices/j72b/device.toml index 2d05d5f8..3a299a66 100644 --- a/devices/j72b/device.toml +++ b/devices/j72b/device.toml @@ -52,6 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" +usb_host = "untested" # Same Tristar, PMU rail and USB complex as J71t in the Apple device tree. audio_output = "untested" audio_input = "missing" camera = "missing" diff --git a/docs/compatibility.md b/docs/compatibility.md index bf0215ea..820dc03b 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -136,118 +136,122 @@ Not generated while in review: `j72` (PR #59). Boards Orchard builds for. States: working, untested, missing, none (no such hardware). -| Board | Display | Screen on/off | Backlight | Touchscreen | Buttons | Accelerometer | Gyroscope | Light sensor | Touch ID | Internal storage read | Internal storage write | Wi-Fi | Bluetooth input devices | Bluetooth audio | Cellular | Battery status | Charging | Charging control | Suspend | Resume | USB serial | USB network | Speakers | Microphone | Cameras | Hardware H.265 video decoding | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `p105` | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | untested | untested | untested | none | untested | untested | untested | missing | missing | untested | untested | untested | untested | missing | missing | -| `j1` | untested | missing | missing | untested | missing | missing | missing | missing | none | missing | missing | untested | missing | missing | none | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | -| `j71` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j73` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j85` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j85m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j86` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j86m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j87` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j87m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n51` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n53` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j96` | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | missing | missing | none | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j97` | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n56` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n61` | working | untested | untested | working | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | missing | missing | untested | untested | working | untested | missing | missing | missing | missing | -| `j81` | working | untested | working | working | working | missing | missing | missing | missing | missing | missing | working | untested | untested | none | working | missing | missing | untested | untested | working | working | missing | missing | missing | missing | -| `j82` | untested | untested | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | untested | untested | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j71s` | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | none | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | -| `j72s` | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | -| `n66` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n69u` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n71` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j71t` | untested | untested | untested | untested | untested | working | working | working | missing | untested | untested | untested | untested | untested | none | untested | untested | untested | untested | untested | working | working | working | untested | missing | working | -| `j72t` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | untested | untested | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | -| `n66m` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n69` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `n71m` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j127` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j128` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j98a` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j99a` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d10` | untested | untested | untested | missing | untested | untested | untested | missing | missing | untested | untested | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d101` | untested | untested | untested | missing | untested | untested | untested | missing | missing | untested | untested | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d11` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d111` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j171` | working | untested | working | working | working | untested | untested | untested | missing | working | working | working | missing | missing | none | working | untested | missing | untested | untested | untested | untested | working | missing | missing | untested | -| `j172` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | untested | missing | missing | untested | -| `j71b` | working | working | working | working | working | working | untested | untested | missing | working | working | working | working | working | none | working | working | working | working | working | working | working | working | missing | missing | untested | -| `j72b` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | untested | -| `j120` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j121` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j207` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `j208` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d20` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d201` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d21` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d211` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d22` | untested | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | -| `d221` | working | working | working | working | working | missing | missing | missing | none | working | working | untested | missing | missing | missing | working | missing | missing | untested | untested | working | untested | missing | missing | missing | missing | +| Board | Display | Screen on/off | Backlight | Touchscreen | Buttons | Accelerometer | Gyroscope | Light sensor | Touch ID | Internal storage read | Internal storage write | Wi-Fi | Bluetooth input devices | Bluetooth audio | Cellular | Battery status | Charging | Charging control | Suspend | Resume | USB serial | USB network | USB host | Speakers | Microphone | Cameras | Hardware H.265 video decoding | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `p105` | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | untested | untested | untested | none | untested | untested | untested | missing | missing | untested | untested | missing | untested | untested | missing | missing | +| `j1` | untested | missing | missing | untested | missing | missing | missing | missing | none | missing | missing | untested | missing | missing | none | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | missing | +| `j71` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j73` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j85` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j85m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j86` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j86m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j87` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j87m` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n51` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n53` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j96` | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | missing | missing | none | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j97` | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n56` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n61` | working | untested | untested | working | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | missing | missing | untested | untested | working | untested | missing | missing | missing | missing | missing | +| `j81` | working | untested | working | working | working | missing | missing | missing | missing | missing | missing | working | untested | untested | none | working | missing | missing | untested | untested | working | working | missing | missing | missing | missing | missing | +| `j82` | untested | untested | untested | untested | untested | missing | missing | missing | missing | missing | missing | untested | untested | untested | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j71s` | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | none | untested | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | +| `j72s` | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | +| `n66` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n69u` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n71` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j71t` | untested | untested | untested | untested | untested | working | working | working | missing | untested | untested | untested | untested | untested | none | untested | untested | untested | untested | untested | working | working | working | working | untested | missing | working | +| `j72t` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | untested | untested | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | +| `n66m` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n69` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `n71m` | untested | missing | untested | missing | untested | untested | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j127` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j128` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j98a` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j99a` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d10` | untested | untested | untested | missing | untested | untested | untested | missing | missing | untested | untested | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d101` | untested | untested | untested | missing | untested | untested | untested | missing | missing | untested | untested | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d11` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d111` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j171` | working | untested | working | working | working | untested | untested | untested | missing | working | working | working | missing | missing | none | working | untested | missing | untested | untested | untested | untested | missing | working | missing | missing | untested | +| `j172` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | missing | untested | missing | missing | untested | +| `j71b` | working | working | working | working | working | working | untested | untested | missing | working | working | working | working | working | none | working | working | working | working | working | working | working | untested | working | missing | missing | untested | +| `j72b` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | untested | +| `j120` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j121` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j207` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `j208` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d20` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d201` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d21` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d211` | untested | missing | untested | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d22` | untested | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | missing | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | +| `d221` | working | working | working | working | working | missing | missing | missing | none | working | working | untested | missing | missing | missing | working | missing | missing | untested | untested | working | untested | missing | missing | missing | missing | missing | ## Drivers A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-apple-16k`, `7.3.0-rc1-orchard-apple-4k`. It does not mean the feature works. Read by `tools/catalog/generate kernel`. -| Board | Status | Backlight | Touchscreen | Buttons | Accelerometer | Gyroscope | Light sensor | Internal storage read | Internal storage write | Wi-Fi | Bluetooth input devices | Bluetooth audio | Battery status | Charging | USB serial | USB network | Speakers | Hardware H.265 video decoding | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `j71` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j73` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j85` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j85m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j86` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j86m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j87` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j87m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n51` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n53` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j96` | experimental | yes | yes | yes | – | – | – | – | – | yes | – | – | yes | – | yes | yes | – | – | -| `j97` | experimental | yes | yes | yes | – | – | – | – | – | yes | – | – | yes | – | yes | yes | – | – | -| `n56` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | yes | – | yes | yes | – | – | -| `n61` | experimental | yes | yes | yes | – | – | – | – | – | – | – | – | yes | – | yes | yes | – | – | -| `j81` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | -| `j82` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | -| `j71s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | yes | yes | -| `j72s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | yes | yes | -| `n66` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n69u` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n71` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j71t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `j72t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `n66m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n69` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `n71m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j127` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j128` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j98a` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j99a` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `d10` | experimental | yes | – | yes | yes | yes | – | yes | yes | – | yes | yes | – | – | yes | yes | – | – | -| `d101` | experimental | yes | – | yes | yes | yes | – | yes | yes | – | yes | yes | – | – | yes | yes | – | – | -| `d11` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | -| `d111` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | -| `j171` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `j172` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `j71b` | supported | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `j72b` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| `j120` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j121` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j207` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `j208` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | -| `d20` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | -| `d201` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | -| `d21` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | -| `d211` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | -| `d22` | experimental | yes | yes | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | -| `d221` | experimental | yes | yes | – | – | – | – | yes | yes | yes | – | – | yes | – | yes | yes | – | – | +| Board | Status | Backlight | Touchscreen | Buttons | Accelerometer | Gyroscope | Light sensor | Internal storage read | Internal storage write | Wi-Fi | Bluetooth input devices | Bluetooth audio | Battery status | Charging | USB serial | USB network | USB host | Speakers | Hardware H.265 video decoding | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| `j71` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j73` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j85` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j85m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j86` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j86m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j87` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j87m` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n51` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n53` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j96` | experimental | yes | yes | yes | – | – | – | – | – | yes | – | – | yes | – | yes | yes | – | – | – | +| `j97` | experimental | yes | yes | yes | – | – | – | – | – | yes | – | – | yes | – | yes | yes | – | – | – | +| `n56` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | yes | – | yes | yes | – | – | – | +| `n61` | experimental | yes | yes | yes | – | – | – | – | – | – | – | – | yes | – | yes | yes | – | – | – | +| `j81` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | – | +| `j82` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | – | +| `j71s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j72s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `n66` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n69u` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n71` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j71t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j72t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `n66m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n69` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `n71m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j127` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j128` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j98a` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j99a` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `d10` | experimental | yes | – | yes | yes | yes | – | yes | yes | – | yes | yes | – | – | yes | yes | – | – | – | +| `d101` | experimental | yes | – | yes | yes | yes | – | yes | yes | – | yes | yes | – | – | yes | yes | – | – | – | +| `d11` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | – | +| `d111` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | – | +| `j171` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j172` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j71b` | supported | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j72b` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j120` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j121` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j207` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `j208` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | +| `d20` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | – | +| `d201` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | – | +| `d21` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | – | +| `d211` | experimental | yes | – | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | – | +| `d22` | experimental | yes | yes | – | – | – | – | yes | yes | – | – | – | yes | – | yes | yes | – | – | – | +| `d221` | experimental | yes | yes | – | – | – | – | yes | yes | yes | – | – | yes | – | yes | yes | – | – | – | ### Where feature states and drivers disagree | Board | Feature | | | --- | --- | --- | +| `j71s` | USB host | untested, but no driver binds | +| `j72s` | USB host | untested, but no driver binds | +| `j71t` | USB host | working, but no driver binds | +| `j72t` | USB host | untested, but no driver binds | | `d10` | Bluetooth input devices | missing, but a driver binds | | `d10` | Bluetooth audio | missing, but a driver binds | | `d101` | Bluetooth input devices | missing, but a driver binds | @@ -260,8 +264,10 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | `j171` | Bluetooth audio | missing, but a driver binds | | `j172` | Bluetooth input devices | missing, but a driver binds | | `j172` | Bluetooth audio | missing, but a driver binds | +| `j71b` | USB host | untested, but no driver binds | | `j72b` | Bluetooth input devices | missing, but a driver binds | | `j72b` | Bluetooth audio | missing, but a driver binds | +| `j72b` | USB host | untested, but no driver binds | | `d22` | Touchscreen | missing, but a driver binds | | `d22` | Backlight | missing, but a driver binds | | `d221` | Buttons | working, but no driver binds | diff --git a/kernel/patches/devices/t8010/0025-arm64-dts-apple-t8010-Add-the-USB-role-switch-and-EH.patch b/kernel/patches/devices/t8010/0025-arm64-dts-apple-t8010-Add-the-USB-role-switch-and-EH.patch new file mode 100644 index 00000000..d0db9f50 --- /dev/null +++ b/kernel/patches/devices/t8010/0025-arm64-dts-apple-t8010-Add-the-USB-role-switch-and-EH.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Henrik Klee +Date: Fri, 13 Nov 2026 12:00:00 +0100 +Subject: [PATCH] arm64: dts: apple: t8010: Add the USB role switch and + EHCI/OHCI + +The A10 USB complex is the A9's: the Apple device trees of the iPad 6 +(J71b, J72b) and iPad 7 (J171) describe a device-only usb-device with +usb-ehci0 and its usb-ohci0 companion at the same offsets as the iPad 5 +(J71t), interrupts 242 and 243, otgphyctrl with the USB_CTL bank at +0x20e0d8000 and the same host and device PHY tunables, and the same +usb-complex remap words for EHCI0 and OHCI0. + +Add the role switch to the USB complex, disabled, with the EHCI and OHCI +it creates in the host role as its children, as on the A9. The EHCI and +OHCI nodes move out of the disabled block into the switch. + +[Orchard: harmonizes orchard PR #126, commit 864b4971, onto the A10] + +Assisted-by: LLM +--- + arch/arm64/boot/dts/apple/t8010.dtsi | 44 ++++++++++++++++++++-------- + 1 file changed, 31 insertions(+), 13 deletions(-) + +diff --git a/arch/arm64/boot/dts/apple/t8010.dtsi b/arch/arm64/boot/dts/apple/t8010.dtsi +index 64fdf4dbb..e08ce0d60 100644 +--- a/arch/arm64/boot/dts/apple/t8010.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010.dtsi +@@ -436,21 +436,39 @@ usbdev: usbdev@20c100000 { + resets = <&ps_usbotg>; + dr_mode = "peripheral"; + }; +-#if 0 +- ehci0: usb-ehci@20c200000 { +- compatible = "apple,t8010-ehci", "generic-ehci"; +- reg = <0x2 0x0c200000 0x0 0x10000>; +- interrupts = ; +- power-domains = <&ps_usb2host0>; +- }; + +- ohci0: usb-ohci@20c300000 { +- compatible = "apple,t8010-ohci", "generic-ohci"; +- reg = <0x2 0x0c300000 0x0 0x10000>; +- interrupts = ; +- power-domains = <&ps_usb2host0_ohci>; ++ /* Moves the PHY between usbdev and the EHCI/OHCI pair. */ ++ usb_role: usb-role-switch@20e0d8000 { ++ compatible = "apple,t8010-usb-role-switch", ++ "apple,s8000-usb-role-switch"; ++ reg = <0x2 0x0e0d8000 0x0 0x1000>; ++ #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>; ++ status = "disabled"; ++ ++ /* Created by the switch in the host role only. */ ++ usb@20c200000 { ++ compatible = "apple,t8010-ehci", "generic-ehci"; ++ reg = <0x2 0x0c200000 0x0 0x10000>; ++ interrupts = ; ++ power-domains = <&ps_usb2host0>; ++ }; ++ ++ usb@20c300000 { ++ compatible = "apple,t8010-ohci", "generic-ohci"; ++ reg = <0x2 0x0c300000 0x0 0x10000>; ++ interrupts = ; ++ power-domains = <&ps_usb2host0_ohci>; ++ }; + }; +- ++#if 0 + ehci1: usb-ehci@20c400000 { + compatible = "apple,t8010-ehci", "generic-ehci"; + reg = <0x2 0x0c400000 0x0 0x10000>; diff --git a/kernel/patches/devices/t8010/0026-arm64-dts-apple-ipad6-Enable-USB-host-on-the-Lightni.patch b/kernel/patches/devices/t8010/0026-arm64-dts-apple-ipad6-Enable-USB-host-on-the-Lightni.patch new file mode 100644 index 00000000..b47704c5 --- /dev/null +++ b/kernel/patches/devices/t8010/0026-arm64-dts-apple-ipad6-Enable-USB-host-on-the-Lightni.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Henrik Klee +Date: Fri, 13 Nov 2026 12:00:00 +0100 +Subject: [PATCH] arm64: dts: apple: ipad6: Enable USB host on the Lightning + port + +The iPad 6 Lightning port has the iPad 5's hardware: the Apple device +trees of J71b and J72b give the same Tristar (tristar,cbtl1610 on i2c0 +at 0x1a), the same D2365 PMU, and the same accessory rail in the dock +node (function-acc_pwr and function-acc_sw_en on PMU LDO5, +function-acc_sleep_pwr 0x220) as J71t. Enable the role switch with the +PMIC and the Tristar, and point the Lightning charger at it, as on the +iPad 5. + +The iPad 7 shares this file but has no Tristar charger node; its role +switch stays disabled. + +Not yet tested on an iPad 6. + +[Orchard: harmonizes orchard PR #126, commit 864b4971, onto the iPad 6] + +Assisted-by: LLM +--- + arch/arm64/boot/dts/apple/t8010-ipad6.dtsi | 8 ++++++++ + arch/arm64/boot/dts/apple/t8010-ipad7.dtsi | 7 +++++++ + 2 files changed, 15 insertions(+) + +diff --git a/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi b/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi +index fe76af181..fa7b40ac2 100644 +--- a/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi +@@ -207,6 +207,7 @@ tristar_charger: charger@180 { + <72 IRQ_TYPE_EDGE_RISING>, + <77 IRQ_TYPE_EDGE_RISING>; + apple,tristar = <&tristar>; ++ apple,usb-role-switch = <&usb_role>; + input-current-limit-microamp = <2400000>; + + /* The charger decides when USB data may come on. */ +@@ -427,6 +428,13 @@ usbdev_role: endpoint { + }; + }; + ++/* Lightning host role: accessory power from LDO5, ACC routing by the Tristar. */ ++&usb_role { ++ apple,pmic = <&d2365_pmic>; ++ apple,tristar = <&tristar>; ++ status = "okay"; ++}; ++ + &vxd { + status = "okay"; + }; +diff --git a/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi b/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi +index f493f505f..de3f38768 100644 +--- a/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi +@@ -22,6 +22,13 @@ &aop { + /delete-node/ &tristar_charger; + /delete-node/ &tristar; + ++/* Without the Tristar accessory policy the port stays a USB device. */ ++&usb_role { ++ /delete-property/ apple,pmic; ++ /delete-property/ apple,tristar; ++ status = "disabled"; ++}; ++ + &usbdev { + /delete-property/ usb-psy-name; + /delete-property/ usb-role-switch; From 176c047056d600958f292229df5b78726e8f743f Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Fri, 13 Nov 2026 06:37:57 +0800 Subject: [PATCH 3/5] kernel: The iPad 7 gets the iPad 6 Lightning port, charging and power-off Apple's device tree of the iPad 7 has the iPad 6's Lightning port hardware (Tristar, USB complex, accessory power); the Bellatrix that Orchard used as its port controller is the Smart Connector's. The iPad 7 now uses the iPad 6 charger, so it identifies chargers and Apple hosts instead of staying at 500 mA, wakes on plug and gets USB host. The PMIC power-off moves from the J71b file to the shared iPad 6 file, for the J72b and both iPad 7 models. Untested on those boards. --- devices/j171/device.toml | 2 +- devices/j172/device.toml | 2 +- docs/compatibility.md | 6 +- ...-ipad7-Use-the-iPad-6-Lightning-port.patch | 81 +++++++++++++++++++ ...-off-through-the-PMIC-on-every-model.patch | 49 +++++++++++ 5 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 kernel/patches/devices/t8010/0027-arm64-dts-apple-ipad7-Use-the-iPad-6-Lightning-port.patch create mode 100644 kernel/patches/devices/t8010/0028-arm64-dts-apple-ipad6-Power-off-through-the-PMIC-on-every-model.patch diff --git a/devices/j171/device.toml b/devices/j171/device.toml index e317cf78..cda24766 100644 --- a/devices/j171/device.toml +++ b/devices/j171/device.toml @@ -52,7 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" -usb_host = "missing" +usb_host = "untested" audio_output = "working" audio_input = "missing" camera = "missing" diff --git a/devices/j172/device.toml b/devices/j172/device.toml index 188f2d59..17b2cbc9 100644 --- a/devices/j172/device.toml +++ b/devices/j172/device.toml @@ -52,7 +52,7 @@ suspend = "untested" resume = "untested" usb_serial = "untested" usb_network = "untested" -usb_host = "missing" +usb_host = "untested" audio_output = "untested" audio_input = "missing" camera = "missing" diff --git a/docs/compatibility.md b/docs/compatibility.md index 820dc03b..6a683fe7 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -174,8 +174,8 @@ Boards Orchard builds for. States: working, untested, missing, none (no such har | `d101` | untested | untested | untested | missing | untested | untested | untested | missing | missing | untested | untested | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | | `d11` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | | `d111` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | -| `j171` | working | untested | working | working | working | untested | untested | untested | missing | working | working | working | missing | missing | none | working | untested | missing | untested | untested | untested | untested | missing | working | missing | missing | untested | -| `j172` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | missing | untested | missing | missing | untested | +| `j171` | working | untested | working | working | working | untested | untested | untested | missing | working | working | working | missing | missing | none | working | untested | missing | untested | untested | untested | untested | untested | working | missing | missing | untested | +| `j172` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | missing | untested | untested | untested | untested | untested | untested | missing | missing | untested | | `j71b` | working | working | working | working | working | working | untested | untested | missing | working | working | working | working | working | none | working | working | working | working | working | working | working | untested | working | missing | missing | untested | | `j72b` | untested | untested | untested | untested | untested | untested | untested | untested | missing | untested | untested | untested | missing | missing | missing | untested | untested | untested | untested | untested | untested | untested | untested | untested | missing | missing | untested | | `j120` | untested | missing | untested | missing | untested | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | missing | untested | untested | untested | untested | missing | missing | missing | missing | missing | @@ -262,8 +262,10 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | `d111` | Bluetooth audio | missing, but a driver binds | | `j171` | Bluetooth input devices | missing, but a driver binds | | `j171` | Bluetooth audio | missing, but a driver binds | +| `j171` | USB host | untested, but no driver binds | | `j172` | Bluetooth input devices | missing, but a driver binds | | `j172` | Bluetooth audio | missing, but a driver binds | +| `j172` | USB host | untested, but no driver binds | | `j71b` | USB host | untested, but no driver binds | | `j72b` | Bluetooth input devices | missing, but a driver binds | | `j72b` | Bluetooth audio | missing, but a driver binds | diff --git a/kernel/patches/devices/t8010/0027-arm64-dts-apple-ipad7-Use-the-iPad-6-Lightning-port.patch b/kernel/patches/devices/t8010/0027-arm64-dts-apple-ipad7-Use-the-iPad-6-Lightning-port.patch new file mode 100644 index 00000000..6fcf9d75 --- /dev/null +++ b/kernel/patches/devices/t8010/0027-arm64-dts-apple-ipad7-Use-the-iPad-6-Lightning-port.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Henrik Klee +Date: Fri, 13 Nov 2026 07:00:00 +0800 +Subject: [PATCH] arm64: dts: apple: ipad7: Use the iPad 6 Lightning port + +Apple's device tree of the iPad 7 (J171) has the iPad 6's Lightning +port hardware: the cbtl1610 Tristar at i2c0 0x1a, the same USB complex +and otgphyctrl, the dock accessory power on LDO5 and the k48 charger. +Its Bellatrix (i2c1 orion-ic, 0x13) belongs to dock-orion, the Smart +Connector, not to the Lightning port. + +Stop deleting the Tristar, its charger and the USB role switch, and +drop the D2365 charger node that took Bellatrix as its USB power +source: the iPad 7 now identifies its charger (BrickID, BC1.2, an Apple +host's grant) instead of staying at 500 mA, wakes on plug, and gets USB +host through the camera adapter like the iPad 6. The Bellatrix node +stays. Untested on the iPad 7. + +Assisted-by: LLM +--- +diff --git a/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi b/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi +index de3f387..0564832 100644 +--- a/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010-ipad7.dtsi +@@ -18,22 +18,12 @@ + firmware-name = "apple/aop-ipad7c.bin", "apple/aop-ipad7c-ext.bin"; + }; + +-/* The iPad 7 port controller is Bellatrix, with its own charger policy. */ +-/delete-node/ &tristar_charger; +-/delete-node/ &tristar; +- +-/* Without the Tristar accessory policy the port stays a USB device. */ +-&usb_role { +- /delete-property/ apple,pmic; +- /delete-property/ apple,tristar; +- status = "disabled"; +-}; +- +-&usbdev { +- /delete-property/ usb-psy-name; +- /delete-property/ usb-role-switch; +- /delete-node/ port; +-}; ++/* ++ * The Lightning port is the iPad 6's: Apple's device tree has the same ++ * cbtl1610 Tristar at i2c0 0x1a, USB complex, otgphyctrl and dock ++ * accessory power, so the iPad 6 charger and USB role switch apply. ++ * Bellatrix (i2c1 orion-ic) belongs to dock-orion, the Smart Connector. ++ */ + + &battery_info { + charge-full-design-microamp-hours = <8557000>; +@@ -83,6 +73,7 @@ + sda-gpios = <&pinctrl_ap 39 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + status = "okay"; + ++ /* The Smart Connector's controller (dock-orion), not the Lightning port */ + usb_power: usb-power@13 { + compatible = "apple,bellatrix"; + reg = <0x13>; +@@ -91,17 +82,6 @@ + }; + }; + +-&d2365_pmic { +- d2365_charger: charger@180 { +- compatible = "apple,j172-d2365-charger", "apple,d2365-charger"; +- reg = <0x180 0x1e>, <0xa00 0x0d>; +- reg-names = "status", "control"; +- interrupt-parent = <&pmu_gpio>; +- interrupts = <72 IRQ_TYPE_EDGE_RISING>; +- power-supplies = <&usb_power>; +- }; +-}; +- + &touchscreen { + compatible = "apple,j172-touchscreen"; + spi-max-frequency = <24000000>; +-- +2.54.0 diff --git a/kernel/patches/devices/t8010/0028-arm64-dts-apple-ipad6-Power-off-through-the-PMIC-on-every-model.patch b/kernel/patches/devices/t8010/0028-arm64-dts-apple-ipad6-Power-off-through-the-PMIC-on-every-model.patch new file mode 100644 index 00000000..a60423cd --- /dev/null +++ b/kernel/patches/devices/t8010/0028-arm64-dts-apple-ipad6-Power-off-through-the-PMIC-on-every-model.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Henrik Klee +Date: Fri, 13 Nov 2026 07:00:00 +0800 +Subject: [PATCH] arm64: dts: apple: ipad6: Power off through the PMIC on + every model + +The D2365 power-off node was in the J71b board file only. Move it to +the shared iPad 6 file, so the J72b and the iPad 7 (J171, J172), which +have the same PMIC and, now, the same Tristar charger, power off +through the PMIC too. Untested on those three. + +Assisted-by: LLM +--- +diff --git a/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi b/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi +index fa7b40a..214dfc4 100644 +--- a/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi ++++ b/arch/arm64/boot/dts/apple/t8010-ipad6.dtsi +@@ -271,6 +271,14 @@ + "0", "0", "1"; + }; + ++/* The PMIC powers the iPad 6 and iPad 7 off; the charger can keep it on. */ ++&d2365_pmic { ++ poweroff { ++ compatible = "apple,d2365-poweroff"; ++ power-supplies = <&tristar_charger>; ++ }; ++}; ++ + &i2s_switch { + status = "okay"; + }; +diff --git a/arch/arm64/boot/dts/apple/t8010-j71b.dts b/arch/arm64/boot/dts/apple/t8010-j71b.dts +index e6b9a30..28ada67 100644 +--- a/arch/arm64/boot/dts/apple/t8010-j71b.dts ++++ b/arch/arm64/boot/dts/apple/t8010-j71b.dts +@@ -24,10 +24,3 @@ + &touchscreen { + firmware-name = "apple/j71b-fwstart.z2fw"; + }; +- +-&d2365_pmic { +- poweroff { +- compatible = "apple,d2365-poweroff"; +- power-supplies = <&tristar_charger>; +- }; +-}; +-- +2.54.0 From ae59743bd0633d33b2d29b6895437310211f24c9 Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Fri, 13 Nov 2026 06:45:20 +0800 Subject: [PATCH 4/5] catalog: USB host binds on every iPad 5, 6 and 7 model Driver list regenerated from a built apple-16k kernel. --- docs/compatibility.md | 24 ++++++++---------------- tools/catalog/drivers.json | 8 ++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/compatibility.md b/docs/compatibility.md index 6a683fe7..2a0b0e7b 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -211,13 +211,13 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | `n61` | experimental | yes | yes | yes | – | – | – | – | – | – | – | – | yes | – | yes | yes | – | – | – | | `j81` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | – | | `j82` | experimental | yes | yes | yes | – | – | – | – | – | yes | yes | yes | yes | – | yes | yes | – | – | – | -| `j71s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | – | yes | yes | -| `j72s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j71s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| `j72s` | experimental | yes | yes | yes | yes | yes | yes | – | – | – | yes | yes | yes | yes | yes | yes | yes | yes | yes | | `n66` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `n69u` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `n71` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | -| `j71t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | -| `j72t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j71t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| `j72t` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | | `n66m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `n69` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `n71m` | experimental | yes | – | yes | yes | yes | – | – | – | – | – | – | – | – | yes | yes | – | – | – | @@ -229,10 +229,10 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | `d101` | experimental | yes | – | yes | yes | yes | – | yes | yes | – | yes | yes | – | – | yes | yes | – | – | – | | `d11` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | – | | `d111` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | – | – | – | -| `j171` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | -| `j172` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | -| `j71b` | supported | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | -| `j72b` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | – | yes | yes | +| `j171` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| `j172` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| `j71b` | supported | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| `j72b` | experimental | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | | `j120` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `j121` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | | `j207` | experimental | yes | – | yes | – | – | – | – | – | – | – | – | – | – | yes | yes | – | – | – | @@ -248,10 +248,6 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | Board | Feature | | | --- | --- | --- | -| `j71s` | USB host | untested, but no driver binds | -| `j72s` | USB host | untested, but no driver binds | -| `j71t` | USB host | working, but no driver binds | -| `j72t` | USB host | untested, but no driver binds | | `d10` | Bluetooth input devices | missing, but a driver binds | | `d10` | Bluetooth audio | missing, but a driver binds | | `d101` | Bluetooth input devices | missing, but a driver binds | @@ -262,14 +258,10 @@ A yes means an enabled device tree node binds a driver in `7.3.0-rc1-orchard-app | `d111` | Bluetooth audio | missing, but a driver binds | | `j171` | Bluetooth input devices | missing, but a driver binds | | `j171` | Bluetooth audio | missing, but a driver binds | -| `j171` | USB host | untested, but no driver binds | | `j172` | Bluetooth input devices | missing, but a driver binds | | `j172` | Bluetooth audio | missing, but a driver binds | -| `j172` | USB host | untested, but no driver binds | -| `j71b` | USB host | untested, but no driver binds | | `j72b` | Bluetooth input devices | missing, but a driver binds | | `j72b` | Bluetooth audio | missing, but a driver binds | -| `j72b` | USB host | untested, but no driver binds | | `d22` | Touchscreen | missing, but a driver binds | | `d22` | Backlight | missing, but a driver binds | | `d221` | Buttons | working, but no driver binds | diff --git a/tools/catalog/drivers.json b/tools/catalog/drivers.json index 0af8941c..e3afd718 100644 --- a/tools/catalog/drivers.json +++ b/tools/catalog/drivers.json @@ -145,6 +145,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", @@ -164,6 +165,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", @@ -201,6 +203,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", @@ -218,6 +221,7 @@ "gyroscope", "light_sensor", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc" @@ -236,6 +240,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", @@ -255,6 +260,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", @@ -272,6 +278,7 @@ "gyroscope", "light_sensor", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc" @@ -290,6 +297,7 @@ "storage_read", "storage_write", "touch", + "usb_host", "usb_network", "usb_serial", "video_decode_hevc", From a27bb193d9c249650717add941b6f09c9ec37478 Mon Sep 17 00:00:00 2001 From: Henrik Klee Date: Fri, 13 Nov 2026 06:52:21 +0800 Subject: [PATCH 5/5] tests: Check the PMIC power-off in the shared iPad 6 file The power-off node moved from the J71b board file to t8010-ipad6.dtsi, which every iPad 6 and iPad 7 board includes. Resolve that file from its pinned upstream copy through the real series, as for the J71b file. --- tests/fixtures/d2365/README.md | 4 ++ tests/fixtures/d2365/t8010-ipad6.dtsi | 56 +++++++++++++++++++++++++++ tests/test_d2365_poweroff.py | 38 +++++++++--------- 3 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 tests/fixtures/d2365/t8010-ipad6.dtsi diff --git a/tests/fixtures/d2365/README.md b/tests/fixtures/d2365/README.md index 62e31c07..df0bc2bb 100644 --- a/tests/fixtures/d2365/README.md +++ b/tests/fixtures/d2365/README.md @@ -11,3 +11,7 @@ every matching patch in the real J71b series with `git apply`, then checks the resulting file. This keeps the test offline without mistaking the last patch's added lines for the final device tree. When the upstream pin moves, refresh this fixture and `J71B_UPSTREAM` together. + +`t8010-ipad6.dtsi` is the unmodified upstream file from the same commit +(Git blob `1e46e4a3a7f4ad9f00d3dc0724aec2928c54b332`), for the PMIC power-off node, which sits in the +shared iPad 6 file that every iPad 6 and iPad 7 board includes. diff --git a/tests/fixtures/d2365/t8010-ipad6.dtsi b/tests/fixtures/d2365/t8010-ipad6.dtsi new file mode 100644 index 00000000..1e46e4a3 --- /dev/null +++ b/tests/fixtures/d2365/t8010-ipad6.dtsi @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Apple iPad 6 common device tree + * + * Copyright (c) 2022, Konrad Dybcio + */ + +#include "t8010.dtsi" +#include "t8010-common.dtsi" +#include + +/ { + chassis-type = "tablet"; + + gpio-keys { + compatible = "gpio-keys"; + + button-home { + label = "Home Button"; + gpios = <&pinctrl_ap 180 GPIO_ACTIVE_LOW>; + linux,code = ; + wakeup-source; + }; + + button-power { + label = "Power Button"; + gpios = <&pinctrl_ap 179 GPIO_ACTIVE_LOW>; + linux,code = ; + wakeup-source; + }; + + button-voldown { + label = "Volume Down"; + gpios = <&pinctrl_ap 89 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + + button-volup { + label = "Volume Up"; + gpios = <&pinctrl_ap 90 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; +}; + +&framebuffer0 { + power-domains = <&ps_disp0_fe &ps_disp0_be &ps_dp>; +}; + +&hurricane_opp09 { + status = "okay"; +}; + +&hurricane_opp10 { + status = "okay"; +}; diff --git a/tests/test_d2365_poweroff.py b/tests/test_d2365_poweroff.py index ebda5f4a..cb778be1 100644 --- a/tests/test_d2365_poweroff.py +++ b/tests/test_d2365_poweroff.py @@ -16,6 +16,7 @@ DRIVER = 'drivers/power/reset/apple-d2365-poweroff.c' ROOT = Path(__file__).resolve().parents[1] J71B_DTS = 'arch/arm64/boot/dts/apple/t8010-j71b.dts' +IPAD6_DTSI = 'arch/arm64/boot/dts/apple/t8010-ipad6.dtsi' J71B_UPSTREAM = 'cee9395acd8043be0644b25c34bfa86623f2b935' @@ -45,7 +46,7 @@ def driver_source(): return patched_source(DRIVER) -def resolved_j71b_dts(extra_patches=()): +def resolved_j71b_dts(extra_patches=(), path=J71B_DTS): """Apply the real platform series to a pinned upstream file, offline.""" lock = dict(line.split('=', 1) for line in (ROOT / 'kernel/upstream.lock').read_text().splitlines()) @@ -53,15 +54,15 @@ def resolved_j71b_dts(extra_patches=()): raise AssertionError('Refresh the J71b DTS fixture for kernel/upstream.lock') series = runpy.run_path(str(ROOT / 'kernel/series.py'))['patches']('j71b') with tempfile.TemporaryDirectory() as temporary: - source = Path(temporary) / J71B_DTS + source = Path(temporary) / path source.parent.mkdir(parents=True) - shutil.copyfile(ROOT / 'tests/fixtures/d2365/t8010-j71b.dts', source) + shutil.copyfile(ROOT / 'tests/fixtures/d2365' / Path(path).name, source) for patch in [*series, *extra_patches]: # Skip other files, but include deletions of this one as well. lines = patch.read_text().splitlines() - if not {f'--- a/{J71B_DTS}', f'+++ b/{J71B_DTS}'}.intersection(lines): + if not {f'--- a/{path}', f'+++ b/{path}'}.intersection(lines): continue - result = subprocess.run(['git', 'apply', f'--include={J71B_DTS}', str(patch)], + result = subprocess.run(['git', 'apply', f'--include={path}', str(patch)], cwd=temporary, capture_output=True, text=True, timeout=10) if result.returncode: raise AssertionError(f'{patch}: {result.stdout}{result.stderr}') @@ -69,11 +70,12 @@ def resolved_j71b_dts(extra_patches=()): class D2365DeviceTree(unittest.TestCase): - def test_j71b_poweroff_node_has_supply(self): - source = resolved_j71b_dts() + def test_ipad6_poweroff_node_has_supply(self): + # Every iPad 6 and iPad 7 board includes the shared file with the node. + source = resolved_j71b_dts(path=IPAD6_DTSI) self.assertIn('compatible = "apple,d2365-poweroff";', source) self.assertIn('power-supplies = <&tristar_charger>;', source) - self.assertIn('simple-audio-card,name = "J71b Speakers";', source) + self.assertIn('simple-audio-card,name = "J71b Speakers";', resolved_j71b_dts()) def test_ipad6_boards_share_the_tdm_speaker_link(self): # Several patches add to the iPad 6 boards' shared file; the speaker link is in one of them. @@ -81,30 +83,30 @@ def test_ipad6_boards_share_the_tdm_speaker_link(self): self.assertIn('maxim,tdm;', source) self.assertIn('simple-audio-card,format = "dsp_a";', source) - def with_later_patch(self, before, after): + def with_later_patch(self, before, after, path=J71B_DTS): with tempfile.TemporaryDirectory() as temporary: patch = Path(temporary) / '9999-later-j71b-change.patch' patch.write_text(''.join(difflib.unified_diff( before.splitlines(keepends=True), after.splitlines(keepends=True), - fromfile='a/' + J71B_DTS, tofile='b/' + J71B_DTS))) - return resolved_j71b_dts([patch]) + fromfile='a/' + path, tofile='b/' + path))) + return resolved_j71b_dts([patch], path=path) - def test_later_speaker_edit_preserves_poweroff_node(self): - before = resolved_j71b_dts() - after = before.replace('"J71b Speakers"', '"J71b Test Speakers"') + def test_later_unrelated_edit_preserves_poweroff_node(self): + before = resolved_j71b_dts(path=IPAD6_DTSI) + after = before.replace('apple/aop-ipad7b.bin', 'apple/aop-test.bin') self.assertNotEqual(before, after) - # The last patch has only sound-card hunks, not the poweroff node. - resolved = self.with_later_patch(before, after) + # The last patch has only firmware-name hunks, not the poweroff node. + resolved = self.with_later_patch(before, after, IPAD6_DTSI) self.assertEqual(resolved, after) self.assertIn('compatible = "apple,d2365-poweroff";', resolved) self.assertIn('power-supplies = <&tristar_charger>;', resolved) def test_later_deletion_is_not_hidden_by_earlier_added_lines(self): - before = resolved_j71b_dts() + before = resolved_j71b_dts(path=IPAD6_DTSI) supply = '\t\tpower-supplies = <&tristar_charger>;\n' self.assertIn(supply, before) after = before.replace(supply, '') - resolved = self.with_later_patch(before, after) + resolved = self.with_later_patch(before, after, IPAD6_DTSI) self.assertEqual(resolved, after) self.assertNotIn('power-supplies = <&tristar_charger>;', resolved)