From b4c9d28044b3b6e8b996020d377b81c3f8d3717c Mon Sep 17 00:00:00 2001 From: Dj Date: Sat, 10 Oct 2026 23:42:16 -0400 Subject: [PATCH] spmi: apple: Support generation-4 interrupts and FIFO recovery Use the matched interrupt register banks on T6050 and T8142. Recover failed transactions through the generation-4 queue control registers, with bounded draining and restoration of the inherited enable state. Keep generation-1 flushing and the existing polling interval. Signed-off-by: Dj --- .../devicetree/bindings/spmi/apple,spmi.yaml | 6 +- drivers/spmi/spmi-apple-controller.c | 82 ++++++++++++++---- tools/testing/selftests/aurora-apif/Makefile | 2 +- tools/testing/selftests/aurora-apif/spmi.py | 86 +++++++++++++++++++ 4 files changed, 155 insertions(+), 21 deletions(-) create mode 100755 tools/testing/selftests/aurora-apif/spmi.py diff --git a/Documentation/devicetree/bindings/spmi/apple,spmi.yaml b/Documentation/devicetree/bindings/spmi/apple,spmi.yaml index 5a52f808b6f5b..f724c1744f328 100644 --- a/Documentation/devicetree/bindings/spmi/apple,spmi.yaml +++ b/Documentation/devicetree/bindings/spmi/apple,spmi.yaml @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) AND GPL-2.0-only %YAML 1.2 --- $id: http://devicetree.org/schemas/spmi/apple,spmi.yaml# @@ -34,7 +34,9 @@ properties: - apple,t6000-spmi - apple,t8112-spmi - const: apple,spmi - - const: apple,t8142-spmi + - enum: + - apple,t8142-spmi + - apple,t6050-spmi reg: maxItems: 1 diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index d31bcf33a8a35..06f81d546e52c 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -57,7 +57,9 @@ struct apple_spmi_hw { u32 command; u32 reply; u32 rx_empty; - bool legacy_irqs; + u32 irq_mask; + u32 irq_ack; + bool gen4; }; static const struct apple_spmi_hw apple_spmi_gen1 = { @@ -65,7 +67,8 @@ static const struct apple_spmi_hw apple_spmi_gen1 = { .command = SPMI_CMD_REG, .reply = SPMI_RSP_REG, .rx_empty = SPMI_RX_FIFO_EMPTY, - .legacy_irqs = true, + .irq_mask = SPMI_IRQ_MASK_BASE, + .irq_ack = SPMI_IRQ_ACK_BASE, }; static const struct apple_spmi_hw apple_spmi_gen4 = { @@ -73,6 +76,9 @@ static const struct apple_spmi_hw apple_spmi_gen4 = { .command = 0x210, .reply = 0x220, .rx_empty = BIT(30), + .irq_mask = 0x400, + .irq_ack = 0x600, + .gen4 = true, }; struct apple_spmi { @@ -90,21 +96,21 @@ struct apple_spmi { static void apple_spmi_irq_ack_raw(struct apple_spmi *spmi, u32 irq) { - u32 __iomem *reg = spmi->regs + SPMI_IRQ_ACK_BASE + (irq / 32) * 4; + u32 __iomem *reg = spmi->regs + spmi->hw->irq_ack + (irq / 32) * 4; writel(BIT(irq % 32), reg); } static void apple_spmi_irq_mask_raw(struct apple_spmi *spmi, u32 irq) { - u32 __iomem *reg = spmi->regs + SPMI_IRQ_MASK_BASE + (irq / 32) * 4; + u32 __iomem *reg = spmi->regs + spmi->hw->irq_mask + (irq / 32) * 4; writel(readl(reg) & ~BIT(irq % 32), reg); } static void apple_spmi_irq_unmask_raw(struct apple_spmi *spmi, u32 irq) { - u32 __iomem *reg = spmi->regs + SPMI_IRQ_MASK_BASE + (irq / 32) * 4; + u32 __iomem *reg = spmi->regs + spmi->hw->irq_mask + (irq / 32) * 4; writel(readl(reg) | BIT(irq % 32), reg); } @@ -175,6 +181,43 @@ static int apple_spmi_wait_rx_not_empty(struct spmi_controller *ctrl) return 0; } +/* Gen4 resets the command queue through its control register. The native + * sequence disables the controller, drains responses, resets pending commands, + * then restores the previous enable state. Never write reset data to the FIFO. + */ +static int apple_spmi_recover(struct apple_spmi *spmi) +{ + u32 enable, status; + int ret = 0, remaining = 512; + + if (!spmi->hw->gen4) { + writel(SPMI_ACT_FIFO_FLUSH, spmi->regs + SPMI_ACT_REG); + goto ack; + } + enable = readl(spmi->regs); + writel(0, spmi->regs); + while (!(readl(spmi->regs + spmi->hw->status) & spmi->hw->rx_empty)) { + if (!remaining--) { + ret = -EIO; + goto restore; + } + readl(spmi->regs + spmi->hw->reply); + } + if (!(readl(spmi->regs + spmi->hw->status) & BIT(14))) { + writel(BIT(0), spmi->regs + 4); + ret = readl_poll_timeout(spmi->regs + 4, status, + !(status & BIT(0)), 50, 50000); + if (!ret && (readl(spmi->regs + spmi->hw->status) & + (BIT(14) | BIT(30))) != (BIT(14) | BIT(30))) + ret = -EIO; + } +restore: + writel(enable, spmi->regs); +ack: + apple_spmi_irq_ack_raw(spmi, SPMI_IRQ_NOTIFY); + return ret; +} + static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 param, const u8 *buf, size_t len, u8 *ibuf, size_t ilen) { @@ -188,11 +231,9 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, guard(mutex)(&spmi->fifo_lock); if (spmi->prev_fail) { - /* Do not consume a late reply as the reply to a new command. */ - if (!spmi->hw->legacy_irqs) - return -EIO; - writel(SPMI_ACT_FIFO_FLUSH, spmi->regs + SPMI_ACT_REG); - apple_spmi_irq_ack_raw(spmi, SPMI_IRQ_NOTIFY); + ret = apple_spmi_recover(spmi); + if (ret) + return ret; spmi->prev_fail = false; } reinit_completion(&spmi->fifo_rx); @@ -388,7 +429,7 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) unsigned long val, offset, bit; chained_irq_enter(chip, desc); - val = readl(spmi->regs + SPMI_IRQ_ACK_BASE + (SPMI_IRQ_NOTIFY / 32) * 4); + val = readl(spmi->regs + spmi->hw->irq_ack + (SPMI_IRQ_NOTIFY / 32) * 4); if (val & BIT(SPMI_IRQ_NOTIFY % 32)) { apple_spmi_irq_ack_raw(spmi, SPMI_IRQ_NOTIFY); complete(&spmi->fifo_rx); @@ -396,7 +437,7 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) } for (offset = 0; offset < SPMI_NUM_PERIPHERAL_IRQS / 8; offset += sizeof(val)) { - val = readq(spmi->regs + SPMI_IRQ_ACK_BASE + offset); + val = readq(spmi->regs + spmi->hw->irq_ack + offset); /** * because of other masters in the bus, we're going to get a multitude of * interrupts we're not interested in. irq_resolve_mapping isn't very @@ -436,8 +477,8 @@ static int apple_spmi_init_irq(struct platform_device *pdev, raw_spin_lock_init(&spmi->irq_mask_lock); for (size_t offset = 0; offset < SPMI_NUM_IRQS / 8; offset += 4) { - writel(0, spmi->regs + SPMI_IRQ_MASK_BASE + offset); - writel(U32_MAX, spmi->regs + SPMI_IRQ_ACK_BASE + offset); + writel(0, spmi->regs + spmi->hw->irq_mask + offset); + writel(U32_MAX, spmi->regs + spmi->hw->irq_ack + offset); } spmi->irqd = devm_irq_domain_instantiate(&pdev->dev, &info); @@ -445,7 +486,7 @@ static int apple_spmi_init_irq(struct platform_device *pdev, return PTR_ERR(spmi->irqd); spmi->notify_irq = true; - ret = devm_add_action(&pdev->dev, remove_chained_handler, (void *)(uintptr_t)spmi->irq); + ret = devm_add_action_or_reset(&pdev->dev, remove_chained_handler, (void *)(uintptr_t)spmi->irq); if (ret) return ret; @@ -460,6 +501,7 @@ static int apple_spmi_probe(struct platform_device *pdev) struct apple_spmi *spmi; struct spmi_controller *ctrl; int ret; + struct resource *res; ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*spmi)); if (IS_ERR(ctrl)) @@ -473,6 +515,12 @@ static int apple_spmi_probe(struct platform_device *pdev) init_completion(&spmi->fifo_rx); platform_set_drvdata(pdev, spmi); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res || resource_size(res) < + max_t(u32, SPMI_ACT_REG + 4, + spmi->hw->irq_ack + DIV_ROUND_UP(SPMI_NUM_IRQS, 32) * 4)) + return -EINVAL; + spmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(spmi->regs)) return PTR_ERR(spmi->regs); @@ -487,9 +535,6 @@ static int apple_spmi_probe(struct platform_device *pdev) if (spmi->irq < 0 && spmi->irq != -ENXIO) return spmi->irq; if (spmi->irq >= 0) { - if (!spmi->hw->legacy_irqs) - return dev_err_probe(&pdev->dev, -EOPNOTSUPP, - "Generation 4 interrupt layout is not supported\n"); ret = apple_spmi_init_irq(pdev, spmi, spmi->irq); if (ret) return ret; @@ -504,6 +549,7 @@ static int apple_spmi_probe(struct platform_device *pdev) } static const struct of_device_id apple_spmi_match_table[] = { + { .compatible = "apple,t6050-spmi", .data = &apple_spmi_gen4 }, { .compatible = "apple,t8142-spmi", .data = &apple_spmi_gen4 }, { .compatible = "apple,t8103-spmi", .data = &apple_spmi_gen1 }, { .compatible = "apple,spmi", .data = &apple_spmi_gen1 }, diff --git a/tools/testing/selftests/aurora-apif/Makefile b/tools/testing/selftests/aurora-apif/Makefile index 5c75739110910..678abc357e559 100644 --- a/tools/testing/selftests/aurora-apif/Makefile +++ b/tools/testing/selftests/aurora-apif/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -TEST_PROGS := apif_mmio.py pmp.py guest.py +TEST_PROGS := apif_mmio.py pmp.py guest.py spmi.py include ../lib.mk diff --git a/tools/testing/selftests/aurora-apif/spmi.py b/tools/testing/selftests/aurora-apif/spmi.py new file mode 100755 index 0000000000000..e51ba68f4fa95 --- /dev/null +++ b/tools/testing/selftests/aurora-apif/spmi.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise generation-4 FIFO recovery using the production function.""" +from pathlib import Path +import shutil +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[4] + + +def main(): + print("TAP version 13\n1..1", flush=True) + if not shutil.which("cc"): + print("ok 1 - SPMI FIFO recovery # SKIP cc unavailable") + return 4 + source = (ROOT / "drivers/spmi/spmi-apple-controller.c").read_text() + start = source.index("static int apple_spmi_recover(") + end = source.index("static int spmi_raw_cmd(", start) + program = r''' +#include +#include +#include +#include +typedef uint32_t u32; +#define BIT(n) (1U << (n)) +#define SPMI_ACT_FIFO_FLUSH 1 +#define SPMI_ACT_REG 0xa4 +#define SPMI_IRQ_NOTIFY 256 +struct hw { bool gen4; u32 status, reply, rx_empty; }; +struct apple_spmi { unsigned char *regs; const struct hw *hw; }; +static unsigned char regs[0x800]; +static u32 status, enabled, reset; +static int reads, acks, poll_result; +static u32 readl(void *p) { + unsigned long off = (unsigned char *)p - regs; + if (off == 0) return enabled; + if (off == 0x200) return status; + if (off == 0x220) reads++; + return 0; +} +static void writel(u32 v, void *p) { + unsigned long off = (unsigned char *)p - regs; + if (off == 0) enabled = v; + if (off == 4) reset = v; +} +static int poll(u32 *v) { + *v = 0; + if (!poll_result) status = BIT(14) | BIT(30); + return poll_result; +} +#define readl_poll_timeout(addr, val, cond, delay, timeout) poll(&(val)) +static void apple_spmi_irq_ack_raw(struct apple_spmi *s, u32 irq) { acks++; } +''' + source[start:end] + r''' +int main(void) { + struct hw hw = {true, 0x200, 0x220, BIT(30)}; + struct apple_spmi s = {regs, &hw}; + enabled = 7; status = BIT(14) | BIT(30); + assert(apple_spmi_recover(&s) == 0); + assert(enabled == 7 && reset == 0 && acks == 1); + status = 0; reads = 0; + assert(apple_spmi_recover(&s) == -EIO); + assert(enabled == 7 && reads == 512 && acks == 2); + status = BIT(30); poll_result = -ETIMEDOUT; + assert(apple_spmi_recover(&s) == -ETIMEDOUT); + assert(enabled == 7 && reset == 1 && acks == 3); + status = BIT(30); poll_result = 0; + assert(apple_spmi_recover(&s) == 0 && enabled == 7); + return 0; +} +''' + try: + with tempfile.TemporaryDirectory() as tmp: + src, binary = Path(tmp) / "test.c", Path(tmp) / "test" + src.write_text(program) + subprocess.run(["cc", "-std=gnu11", str(src), "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + except subprocess.CalledProcessError: + print("not ok 1 - SPMI FIFO recovery") + return 1 + print("ok 1 - SPMI FIFO recovery") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())