Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Documentation/devicetree/bindings/spmi/apple,spmi.yaml
Original file line number Diff line number Diff line change
@@ -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#
Expand Down Expand Up @@ -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
Expand Down
82 changes: 64 additions & 18 deletions drivers/spmi/spmi-apple-controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,28 @@ 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 = {
.status = SPMI_STATUS_REG,
.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 = {
.status = 0x200,
.command = 0x210,
.reply = 0x220,
.rx_empty = BIT(30),
.irq_mask = 0x400,
.irq_ack = 0x600,
.gen4 = true,
};

struct apple_spmi {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -388,15 +429,15 @@ 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);
handled = true;
}

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
Expand Down Expand Up @@ -436,16 +477,16 @@ 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);
if (IS_ERR(spmi->irqd))
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;

Expand All @@ -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))
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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 },
Expand Down
2 changes: 1 addition & 1 deletion tools/testing/selftests/aurora-apif/Makefile
Original file line number Diff line number Diff line change
@@ -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
86 changes: 86 additions & 0 deletions tools/testing/selftests/aurora-apif/spmi.py
Original file line number Diff line number Diff line change
@@ -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 <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
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())