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 @@ -36,7 +36,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
93 changes: 75 additions & 18 deletions drivers/spmi/spmi-apple-controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,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 @@ -89,21 +95,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 @@ -174,6 +180,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_wr, size_t len_wr, u8 *buf_rd, size_t len_rd)
{
Expand All @@ -186,11 +229,9 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 param,
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 @@ -314,7 +355,8 @@ static struct irq_chip apple_spmi_irq_chip = {
.irq_unmask = apple_spmi_irq_unmask,
.irq_ack = apple_spmi_irq_ack,
.irq_set_type = apple_spmi_irq_set_type,
.flags = IRQCHIP_ONESHOT_SAFE,
/* The chained parent stays enabled; children need no separate wake op. */
.flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE,
};

static int apple_spmi_irq_domain_map(struct irq_domain *irqd,
Expand Down Expand Up @@ -385,6 +427,15 @@ static const struct irq_domain_ops apple_spmi_irq_domain_ops = {
.free = apple_spmi_irq_domain_free,
};

static u64 apple_spmi_irq_status(struct apple_spmi *spmi, unsigned long offset)
{
/* Generation 4 exposes 32-bit IRQ banks, including in the guest grant. */
if (spmi->hw->gen4)
return readl(spmi->regs + spmi->hw->irq_ack + offset) |
((u64)readl(spmi->regs + spmi->hw->irq_ack + offset + 4) << 32);
return readq(spmi->regs + spmi->hw->irq_ack + offset);
}

static void apple_spmi_irq_handler(struct irq_desc *desc)
{
struct apple_spmi *spmi = irq_desc_get_handler_data(desc);
Expand All @@ -393,15 +444,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 = apple_spmi_irq_status(spmi, 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 @@ -444,14 +495,15 @@ 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, apple_spmi_teardown_irq, spmi);
if (ret)
return ret;
Expand All @@ -468,6 +520,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 @@ -481,6 +534,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 @@ -495,9 +554,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 @@ -512,6 +568,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
97 changes: 97 additions & 0 deletions tools/testing/selftests/aurora-apif/spmi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/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
from common import block

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; typedef uint64_t u64;
#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, irq_ack; };
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, irq_reads, wide_reads;
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++;
if (off == 0x600) {irq_reads++;return 1;}
if (off == 0x604) {irq_reads++;return 2;}
return 0;
}
static u64 readq(void*p){wide_reads++;return 0x300000004ULL;}
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] + block(source, r"^static u64 apple_spmi_irq_status\(") + r'''
int main(void) {
struct hw hw = {true, 0x200, 0x220, BIT(30)};
struct apple_spmi s = {regs, &hw};
hw.irq_ack=0x600;
assert(apple_spmi_irq_status(&s,0)==0x200000001ULL);
assert(irq_reads==2 && wide_reads==0);
hw.gen4=false;
assert(apple_spmi_irq_status(&s,0)==0x300000004ULL);
assert(irq_reads==2 && wide_reads==1);
hw.gen4=true;
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())