[PATCH v17 6/6] spi: pxa2xx-pci: disable DMA and runtime autosuspend for Apple MacBook8,1
From: Shih-Yuan Lee
Date: Wed Sep 30 2026 - 16:09:02 EST
On MacBook8,1 (early 2015 12" MacBook), the LPSS SPI controller at
00:15.4 (PCI ID 8086:9ce6, PCI_DEVICE_ID_INTEL_LPT1_1) registers and
allocates DMA channels successfully. However, during actual SPI
transfers, the DMA handshake synchronization fails.
This leads to continuous transfer timeouts and locks up the driver
queue, making the connected keyboard and touchpad completely dead:
[ 1582.050494] spi_master spi1: failed to transfer one message from queue
[ 1582.258387] applespi spi-APP000D:00: SPI transfer timed out
[ 1582.258498] applespi spi-APP000D:00: Error reading from device: -110
Empirical hardware register inspection reveals that the companion LPSS
DMA controller at 00:15.0 has its LPSS Private Reset Register (BAR0 +
0x204) left strictly at 0x00000000 by the EFI firmware. Even after
deasserting this reset and overriding ACPI _PRT to assign shared IRQ 21,
the DMA interrupt counter remains strictly at 0, confirming that DMA
completion interrupts are not delivered on this platform. Furthermore,
the SPI controller driver at 00:15.4 cannot safely reach across the PCI
bus to manipulate private registers of the separate DMAC function at
00:15.0 without violating driver layering.
Reverse engineering of the official OS drivers on the same hardware
corroborates that DMA is deliberately bypassed:
1. On macOS (Big Sur), IOKit registry shows that while the LPSS DMAC
driver (AppleIntelLpssDmac) is loaded, the allocated channel count
(AppleIntelLpssDmacChannel) is 0 when the SPI device driver
(AppleHSSPIHIDDriver) is active.
2. On Windows 10 (Boot Camp), dynamic MMIO monitoring of BAR1 shows
that the LPSS DMA control registers (offset 0x800+) remain
inactive (all zeros) during active keyboard and touchpad
transactions, and transfers are handled solely via the FIFO data
registers at the beginning of BAR1.
3. Logic board schematics confirm that a dedicated out-of-band GPIO
interrupt line (TPAD_SPI_INT_L) is routed directly to the PCH for
input events.
Additionally, when operating in PIO mode servicing high-frequency
keyboard and touchpad input streams operating at 60-125 Hz, the default
50 ms autosuspend delay causes the controller to repeatedly transition
between D0 and D3hot between bursts of user input. These runtime resume
transitions introduce latency spikes that drop key presses and cause
erratic touchpad motion.
Implement the forced PIO mode and runtime autosuspend lockout DMI quirk
in spi-pxa2xx-pci.c (the LPSS host controller PCI glue driver) scoped
strictly to MacBook8,1:
1. Include <linux/dmi.h> in alphabetical order for DMI system matching.
2. Add 'quirks' field in struct pxa2xx_spi_pci_config and define
PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND.
3. Add pxa2xx_spi_pci_dmi_table matching Apple MacBook8,1 and query it
once in lpss_spi_setup() scoped to PCI_DEVICE_ID_INTEL_LPT1_1
(0x9ce6). When matched, log the system ident with pci_info(), set
enable_dma = 0 as a workaround, cache PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND
in cfg->quirks, and return immediately to skip unused DMA channel
setup.
4. In pxa2xx_spi_pci_probe(), configure autosuspend with a 50 ms delay
for all devices, but conditionally skip pm_runtime_allow() when
PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND is set. This keeps the controller in
D0 by default (as initialized by PCI core pci_pm_init()) while bound,
eliminating D3hot resume latency spikes, while ensuring that if
userspace explicitly enables autosuspend via sysfs, the safe 50 ms
delay is preserved rather than defaulting to 0. Standard system
sleep (S3) remains fully operational via pm_runtime_force_suspend().
Assisted-by: Antigravity:gemini-3.8-flash sparse
Signed-off-by: Shih-Yuan Lee <fourdollars@xxxxxxxxxx>
---
drivers/spi/spi-pxa2xx-pci.c | 61 +++++++++++++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi-pxa2xx-pci.c b/drivers/spi/spi-pxa2xx-pci.c
index f8d71362e840..f12a8ef12b17 100644
--- a/drivers/spi/spi-pxa2xx-pci.c
+++ b/drivers/spi/spi-pxa2xx-pci.c
@@ -7,6 +7,7 @@
*/
#include <linux/clk-provider.h>
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/pci.h>
@@ -40,6 +41,8 @@
#define LPSS_PRIV_RESETS_IDMA BIT(2)
#define LPSS_PRIV_REG_COUNT 9
+#define PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND BIT(0)
+
struct pxa_spi_info {
int (*setup)(struct pci_dev *pdev, struct pxa2xx_spi_controller *c);
};
@@ -47,6 +50,7 @@ struct pxa_spi_info {
struct pxa2xx_spi_pci_config {
struct pxa2xx_spi_controller pdata;
u32 lpss_priv_ctx[LPSS_PRIV_REG_COUNT];
+ u8 quirks;
bool is_lpt;
};
@@ -106,8 +110,53 @@ static void lpss_dma_put_device(void *dma_dev)
pci_dev_put(dma_dev);
}
+/*
+ * Apple MacBook8,1 (early 2015 12" MacBook):
+ *
+ * The LPSS SPI controller at 00:15.4 connects to the internal SPI keyboard
+ * and touchpad (applespi). When DMA mode is enabled, DMA transfers consistently
+ * time out (-110) because DMA completion interrupts are never delivered.
+ *
+ * Empirical hardware inspection and OS analysis show that:
+ * 1. Hardware/firmware configuration: EFI leaves companion LPSS DMA (00:15.0)
+ * held in reset (LPSS_PRIV_RESETS == 0x0 / BAR0 + 0x204). Even after
+ * deasserting the reset and overriding ACPI _PRT to assign shared IRQ 21,
+ * the DMA interrupt counter remains strictly 0, confirming that DMA
+ * completion interrupts are not delivered on this platform.
+ * 2. Operating system parity: Both macOS (AppleIntelLpssDmac channel count
+ * is 0) and Windows 10 Boot Camp bypass DMA and handle keyboard/touchpad
+ * transactions strictly in PIO mode via FIFO registers.
+ * 3. Layering: The SPI controller driver (00:15.4) cannot safely reconfigure
+ * the private registers of the separate DMAC PCI function (00:15.0).
+ * 4. Runtime autosuspend: In PIO mode servicing 60-125 Hz input events, the
+ * default 50 ms autosuspend delay introduces D3hot <-> D0 transition
+ * latency spikes that drop key presses and cause erratic touchpad motion.
+ *
+ * Therefore, as a workaround, disable DMA channel allocation to force PIO mode
+ * and keep the device in D0 without enabling runtime autosuspend.
+ */
+static const struct dmi_system_id pxa2xx_spi_pci_dmi_table[] = {
+ {
+ .ident = "Apple MacBook8,1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MacBook8,1"),
+ },
+ },
+ { }
+};
+
+static const struct dmi_system_id *pxa2xx_spi_pci_get_dmi_id(struct pci_dev *dev)
+{
+ if (dev->device == PCI_DEVICE_ID_INTEL_LPT1_1)
+ return dmi_first_match(pxa2xx_spi_pci_dmi_table);
+
+ return NULL;
+}
+
static int lpss_spi_setup(struct pci_dev *dev, struct pxa2xx_spi_controller *c)
{
+ const struct dmi_system_id *dmi_id;
struct pxa2xx_spi_pci_config *cfg = container_of(c, struct pxa2xx_spi_pci_config, pdata);
struct ssp_device *ssp = &c->ssp;
struct dw_dma_slave *tx, *rx;
@@ -165,6 +214,14 @@ static int lpss_spi_setup(struct pci_dev *dev, struct pxa2xx_spi_controller *c)
if (ret)
return ret;
+ dmi_id = pxa2xx_spi_pci_get_dmi_id(dev);
+ if (dmi_id) {
+ pci_info(dev, "%s detected: disabling DMA to force PIO mode\n", dmi_id->ident);
+ c->enable_dma = 0;
+ cfg->quirks |= PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND;
+ return 0;
+ }
+
dma_dev = pci_get_slot(dev->bus, PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
ret = devm_add_action_or_reset(&dev->dev, lpss_dma_put_device, dma_dev);
if (ret)
@@ -446,7 +503,9 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
pm_runtime_set_autosuspend_delay(&dev->dev, 50);
pm_runtime_use_autosuspend(&dev->dev);
pm_runtime_put_autosuspend(&dev->dev);
- pm_runtime_allow(&dev->dev);
+
+ if (!(cfg->quirks & PXA2XX_SPI_QUIRK_NO_AUTOSUSPEND))
+ pm_runtime_allow(&dev->dev);
return 0;
}
--
2.39.5