From 2037f350528853f26c5d5521158629768f1906fd Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Tue, 20 Oct 2026 15:12:40 +0200 Subject: [PATCH 1/5] spmi: apple: Let peripheral interrupts wake the system The controller sits in an always-on block and its parent interrupt is a chained handler, so it keeps running through suspend. A wake-armed peripheral interrupt therefore needs nothing more from the chip, but without irq_set_wake or IRQCHIP_SKIP_SET_WAKE enable_irq_wake() fails and no SPMI device can wake the system. Set IRQCHIP_SKIP_SET_WAKE. Signed-off-by: Eryk Wieliczko --- drivers/spmi/spmi-apple-controller.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 172899d2bb219f..1cfaecdce65efd 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -308,7 +308,12 @@ 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 controller sits in an always-on block and its parent interrupt is + * chained, so it stays live through suspend: a wake-armed peripheral + * interrupt needs nothing more from the chip to wake the system. + */ + .flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE, }; static int apple_spmi_irq_domain_map(struct irq_domain *irqd, From 7099dacc3b8f1fd6e8bcf06ee286c4ac8c597459 Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Thu, 29 Oct 2026 07:36:08 +0100 Subject: [PATCH 2/5] dt-bindings: rtc: apple,smc-rtc: Describe the PMU alarm The PMU that keeps the RTC counter also has a one-shot alarm, reached through three more PMU registers and an SPMI peripheral interrupt that can wake the system. Allow the alarm control, compare and mask cells after the offset cell, all three or none, and the alarm interrupt and wakeup-source. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eryk Wieliczko --- .../bindings/rtc/apple,smc-rtc.yaml | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml b/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml index 2d468e3c302c66..f0581393dbcd9b 100644 --- a/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml @@ -20,15 +20,36 @@ properties: const: apple,smc-rtc nvmem-cells: + minItems: 1 items: - description: RTC offset. By default a 48bit value, specified in 32768 (2^15) Hz clock ticks. With apple,rtc-offset-seconds, a 64bit little-endian count of whole seconds. + - description: + PMU alarm control byte, with the alarm enable, the monitor bit and + the latched alarm event. + - description: + PMU alarm compare value, a 32-bit count of whole seconds of the RTC + counter without the offset. + - description: PMU alarm mask byte. nvmem-cell-names: - items: - - const: rtc_offset + oneOf: + - items: + - const: rtc_offset + - items: + - const: rtc_offset + - const: rtc_ctrl + - const: rtc_alarm + - const: rtc_alarm_mask + + interrupts: + maxItems: 1 + description: + The PMU's RTC alarm interrupt. Only used with the three alarm cells. + + wakeup-source: true apple,rtc-offset-seconds: type: boolean From 0d8a55c70a26199b3b69e68d665c8a6d3ad9503e Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Tue, 20 Oct 2026 15:12:40 +0200 Subject: [PATCH 3/5] rtc: macsmc: Add the PMU alarm The PMU that keeps the RTC counter also has a one-shot alarm: a 32-bit compare value in whole seconds of the free-running counter (CLKM >> 15, without the wall-clock offset), a mask, and a control byte with an enable, a monitor bit and a latched event that is cleared by writing it back. It raises an SPMI peripheral interrupt. When the device tree describes the alarm registers as nvmem cells and the interrupt, implement read_alarm, set_alarm and alarm_irq_enable, report the alarm from a threaded handler, and make it a wake source if the node says so. Without them the RTC stays alarm-less as before. Updates of the control byte keep its other bits. Firmware can leave the alarm armed or its event latched, so the probe disarms it before it requests the interrupt. Signed-off-by: Eryk Wieliczko Assisted-by: Claude:claude-opus-5-5 --- drivers/rtc/rtc-macsmc.c | 282 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 280 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c index a28dd7c577bded..aed6940cc61c09 100644 --- a/drivers/rtc/rtc-macsmc.c +++ b/drivers/rtc/rtc-macsmc.c @@ -5,11 +5,14 @@ */ #include +#include #include #include +#include #include #include #include +#include #include #include #include @@ -24,12 +27,26 @@ /* Offset kept as a 64-bit little-endian count of whole seconds */ #define RTC_OFFSET_SECS_BYTES 8 +/* + * PMU alarm control. The alarm compares a 32-bit count of whole seconds of + * the free-running counter, i.e. CLKM >> RTC_SEC_SHIFT, without the offset. + */ +#define RTC_CTRL_MONITOR BIT(0) +#define RTC_CTRL_ALARM_EVENT BIT(1) +#define RTC_CTRL_ALARM_EN BIT(6) + struct macsmc_rtc { struct device *dev; struct apple_smc *smc; struct rtc_device *rtc_dev; struct nvmem_cell *rtc_offset; bool offset_in_seconds; + /* Alarm, if the PMU's alarm registers and interrupt are described */ + struct nvmem_cell *alarm_ctrl; + struct nvmem_cell *alarm_value; + struct nvmem_cell *alarm_mask; + struct mutex alarm_lock; + u32 alarm_secs; }; static int macsmc_rtc_get_time_secs(struct macsmc_rtc *rtc, u64 ctr, @@ -131,15 +148,268 @@ static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm) return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES); } + +static int macsmc_rtc_read_u8(struct nvmem_cell *cell, u8 *val) +{ + size_t len; + void *p; + + p = nvmem_cell_read(cell, &len); + if (IS_ERR(p)) + return PTR_ERR(p); + if (len < 1) { + kfree(p); + return -EIO; + } + *val = *(u8 *)p; + kfree(p); + + return 0; +} + +static int macsmc_rtc_write_u8(struct nvmem_cell *cell, u8 val) +{ + int ret = nvmem_cell_write(cell, &val, sizeof(val)); + + return ret < 0 ? ret : 0; +} + +/* The wall-clock offset in whole seconds: wall time = counter seconds + offset */ +static int macsmc_rtc_offset_secs(struct macsmc_rtc *rtc, s64 *secs) +{ + u64 off = 0; + size_t len; + void *p; + + p = nvmem_cell_read(rtc->rtc_offset, &len); + if (IS_ERR(p)) + return PTR_ERR(p); + + if (rtc->offset_in_seconds) { + if (len < RTC_OFFSET_SECS_BYTES) { + kfree(p); + return -EIO; + } + *secs = (s64)get_unaligned_le64(p); + } else { + if (len < RTC_BYTES) { + kfree(p); + return -EIO; + } + memcpy(&off, p, RTC_BYTES); + *secs = sign_extend64(off, RTC_BITS - 1) >> RTC_SEC_SHIFT; + } + kfree(p); + + return 0; +} + +/* + * Arm the alarm at counter second @secs, or disarm it if @secs is 0. The + * other bits of the control byte are kept. A latched event is cleared by + * writing it back, so the event bit is written as 1 only to clear it. + */ +static int macsmc_rtc_program_alarm(struct macsmc_rtc *rtc, u32 secs) +{ + __le32 val = cpu_to_le32(secs); + u8 ctrl; + int ret; + + ret = macsmc_rtc_read_u8(rtc->alarm_ctrl, &ctrl); + if (ret) + return ret; + if (ctrl & RTC_CTRL_ALARM_EVENT) { + ret = macsmc_rtc_write_u8(rtc->alarm_ctrl, ctrl); + if (ret) + return ret; + } + ctrl &= ~RTC_CTRL_ALARM_EVENT; + + if (!secs) { + ret = macsmc_rtc_write_u8(rtc->alarm_mask, 1); + if (ret) + return ret; + return macsmc_rtc_write_u8(rtc->alarm_ctrl, + (ctrl & ~RTC_CTRL_ALARM_EN) | + RTC_CTRL_MONITOR); + } + + ret = nvmem_cell_write(rtc->alarm_value, &val, sizeof(val)); + if (ret < 0) + return ret; + ret = macsmc_rtc_write_u8(rtc->alarm_mask, 0); + if (ret) + return ret; + return macsmc_rtc_write_u8(rtc->alarm_ctrl, ctrl | RTC_CTRL_ALARM_EN | + RTC_CTRL_MONITOR); +} + +static int macsmc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + size_t len; + s64 off; + u32 secs; + void *p; + u8 ctrl; + int ret; + + ret = macsmc_rtc_offset_secs(rtc, &off); + if (ret) + return ret; + + mutex_lock(&rtc->alarm_lock); + ret = macsmc_rtc_read_u8(rtc->alarm_ctrl, &ctrl); + if (ret) + goto out; + p = nvmem_cell_read(rtc->alarm_value, &len); + if (IS_ERR(p)) { + ret = PTR_ERR(p); + goto out; + } + secs = len >= sizeof(u32) ? get_unaligned_le32(p) : 0; + kfree(p); + + rtc_time64_to_tm((time64_t)secs + off, &alrm->time); + alrm->enabled = !!(ctrl & RTC_CTRL_ALARM_EN); + alrm->pending = !!(ctrl & RTC_CTRL_ALARM_EVENT); +out: + mutex_unlock(&rtc->alarm_lock); + return ret; +} + +static int macsmc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + s64 off, secs; + int ret; + + ret = macsmc_rtc_offset_secs(rtc, &off); + if (ret) + return ret; + + /* + * With a tick offset, off is rounded down to whole seconds but the + * time is floor((ctr + off) / 2^15), so the alarm fires at the set + * second or up to one second after it. + */ + secs = rtc_tm_to_time64(&alrm->time) - off; + if (secs <= 0 || secs > U32_MAX) + return -ERANGE; + + mutex_lock(&rtc->alarm_lock); + rtc->alarm_secs = secs; + ret = macsmc_rtc_program_alarm(rtc, alrm->enabled ? rtc->alarm_secs : 0); + mutex_unlock(&rtc->alarm_lock); + + return ret; +} + +static int macsmc_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct macsmc_rtc *rtc = dev_get_drvdata(dev); + int ret; + + mutex_lock(&rtc->alarm_lock); + ret = macsmc_rtc_program_alarm(rtc, enabled ? rtc->alarm_secs : 0); + mutex_unlock(&rtc->alarm_lock); + + return ret; +} + +static irqreturn_t macsmc_rtc_alarm_irq(int irq, void *data) +{ + struct macsmc_rtc *rtc = data; + u8 ctrl; + + mutex_lock(&rtc->alarm_lock); + if (macsmc_rtc_read_u8(rtc->alarm_ctrl, &ctrl) || + !(ctrl & RTC_CTRL_ALARM_EVENT)) { + mutex_unlock(&rtc->alarm_lock); + return IRQ_NONE; + } + /* One-shot: clear the event and disarm until the alarm is set again */ + macsmc_rtc_program_alarm(rtc, 0); + mutex_unlock(&rtc->alarm_lock); + + rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); + + return IRQ_HANDLED; +} + static const struct rtc_class_ops macsmc_rtc_ops = { .read_time = macsmc_rtc_get_time, .set_time = macsmc_rtc_set_time, + .read_alarm = macsmc_rtc_read_alarm, + .set_alarm = macsmc_rtc_set_alarm, + .alarm_irq_enable = macsmc_rtc_alarm_irq_enable, }; +/* A missing cell means no alarm, not an error */ +static struct nvmem_cell *macsmc_rtc_cell_optional(struct device *dev, + const char *id) +{ + struct nvmem_cell *cell = devm_nvmem_cell_get(dev, id); + + if (IS_ERR(cell) && PTR_ERR(cell) == -ENOENT) + return NULL; + return cell; +} + +/* The alarm is optional: it needs the PMU's alarm registers and interrupt. */ +static int macsmc_rtc_init_alarm(struct platform_device *pdev, + struct macsmc_rtc *rtc) +{ + struct device *dev = &pdev->dev; + int irq, ret; + + rtc->alarm_ctrl = macsmc_rtc_cell_optional(dev, "rtc_ctrl"); + rtc->alarm_value = macsmc_rtc_cell_optional(dev, "rtc_alarm"); + rtc->alarm_mask = macsmc_rtc_cell_optional(dev, "rtc_alarm_mask"); + if (IS_ERR(rtc->alarm_ctrl)) + return PTR_ERR(rtc->alarm_ctrl); + if (IS_ERR(rtc->alarm_value)) + return PTR_ERR(rtc->alarm_value); + if (IS_ERR(rtc->alarm_mask)) + return PTR_ERR(rtc->alarm_mask); + if (!rtc->alarm_ctrl || !rtc->alarm_value || !rtc->alarm_mask) + return -ENODEV; + + irq = platform_get_irq_optional(pdev, 0); + if (irq == -EPROBE_DEFER) + return irq; + if (irq <= 0) + return -ENODEV; + + /* + * Firmware can leave the alarm armed or its event latched. Neither is + * an alarm Linux set, so disarm it before the interrupt is requested. + */ + ret = macsmc_rtc_program_alarm(rtc, 0); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, macsmc_rtc_alarm_irq, + IRQF_ONESHOT, dev_name(dev), rtc); + if (ret) + return ret; + + if (of_property_read_bool(dev->of_node, "wakeup-source")) { + ret = devm_device_init_wakeup(dev); + if (!ret) + ret = devm_pm_set_wake_irq(dev, irq); + if (ret) + dev_warn(dev, "alarm cannot wake the system: %d\n", ret); + } + + return 0; +} + static int macsmc_rtc_probe(struct platform_device *pdev) { struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); struct macsmc_rtc *rtc; + int ret; /* * MFD will probe this device even without a node in the device tree, @@ -155,6 +425,8 @@ static int macsmc_rtc_probe(struct platform_device *pdev) rtc->dev = &pdev->dev; rtc->smc = smc; + mutex_init(&rtc->alarm_lock); + platform_set_drvdata(pdev, rtc); rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset"); if (IS_ERR(rtc->rtc_offset)) @@ -169,6 +441,14 @@ static int macsmc_rtc_probe(struct platform_device *pdev) "apple,rtc-offset-seconds"); rtc->rtc_dev->ops = &macsmc_rtc_ops; + ret = macsmc_rtc_init_alarm(pdev, rtc); + if (ret == -EPROBE_DEFER) + return ret; + if (ret) { + if (ret != -ENODEV) + dev_warn(&pdev->dev, "no alarm: %d\n", ret); + clear_bit(RTC_FEATURE_ALARM, rtc->rtc_dev->features); + } if (rtc->offset_in_seconds) { rtc->rtc_dev->range_min = 0; rtc->rtc_dev->range_max = RTC_TIMESTAMP_END_9999; @@ -177,8 +457,6 @@ static int macsmc_rtc_probe(struct platform_device *pdev) rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS)); } - platform_set_drvdata(pdev, rtc); - return devm_rtc_register_device(rtc->rtc_dev); } From 1d55fad4d3550c1ae7c69e095a0a0a112c83eedd Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Tue, 20 Oct 2026 15:12:40 +0200 Subject: [PATCH 4/5] arm64: dts: apple: t6030: Describe the RTC alarm The Chapel PMIC's RTC alarm uses registers 0x2000 (control and event), 0x2008 (compare value) and 0x200e (interrupt mask) and raises peripheral interrupt 2 of nub-spmi0. Make the controller an interrupt controller, add the alarm cells to the PMIC layout and give them, the interrupt and wakeup-source to the SMC RTC, so a timer can wake the system. Signed-off-by: Eryk Wieliczko --- arch/arm64/boot/dts/apple/t6030.dtsi | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6030.dtsi b/arch/arm64/boot/dts/apple/t6030.dtsi index 52ad06a0668893..56e3da67947061 100644 --- a/arch/arm64/boot/dts/apple/t6030.dtsi +++ b/arch/arm64/boot/dts/apple/t6030.dtsi @@ -1060,6 +1060,8 @@ #size-cells = <0>; interrupt-parent = <&aic>; interrupts = ; + interrupt-controller; + #interrupt-cells = <2>; pmic1: pmic@b { compatible = "apple,chapel-pmic", "apple,spmi-nvmem"; @@ -1070,10 +1072,22 @@ #address-cells = <1>; #size-cells = <1>; + rtc_ctrl: rtc-ctrl@2000 { + reg = <0x2000 0x1>; + }; + pm_setting: pm-setting@2001 { reg = <0x2001 0x1>; }; + rtc_alarm: rtc-alarm@2008 { + reg = <0x2008 0x4>; + }; + + rtc_alarm_mask: rtc-alarm-mask@200e { + reg = <0x200e 0x1>; + }; + rtc_offset: rtc-offset@2100 { reg = <0x2100 0x6>; }; @@ -1182,8 +1196,13 @@ rtc { compatible = "apple,smc-rtc"; - nvmem-cells = <&rtc_offset>; - nvmem-cell-names = "rtc_offset"; + nvmem-cells = <&rtc_offset>, <&rtc_ctrl>, <&rtc_alarm>, + <&rtc_alarm_mask>; + nvmem-cell-names = "rtc_offset", "rtc_ctrl", "rtc_alarm", + "rtc_alarm_mask"; + /* The PMIC's RTC alarm, peripheral interrupt 2 of nub-spmi0 */ + interrupts-extended = <&nub_spmi 2 IRQ_TYPE_EDGE_RISING>; + wakeup-source; }; }; From b28cb3a4bb171450a6bfbb84db21bfa0335dac7b Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Wed, 28 Oct 2026 19:12:34 +0100 Subject: [PATCH 5/5] pmdomain: apple: pmp-report: withdraw external display requests in sleep An acknowledged DISPEXT request keeps the PMP's external display state up while no display is shown. The requests are asserted once at probe (apple,always-on) and never withdrawn, so they also hold through suspend-to-idle. Give report entries marked apple,release-for-sleep noirq sleep callbacks: by then every display processor has suspended, with its displays off and its links released or idle. Withdraw such a request if it is acknowledged, with one replacement write of the request entry as macOS releases its display requests, and assert it again in resume_noirq, before the processors resume. Without the request the external display stays dark until reboot, so a re-assert the PMP does not acknowledge in time is written again, three times at most. The T6030 display gate marks the DISPEXT requests of the processors it enables. The PMP graph host test runs the new callbacks against its fake PMP: an acknowledged request is withdrawn and asserted again, an unacknowledged or unmarked one is left alone, and a re-assert acknowledged late is written again. Signed-off-by: Eryk Wieliczko Assisted-by: Claude:claude-opus-5-5 --- drivers/pmdomain/apple/pmp-report.c | 79 ++++++++++++++++++++++++++ drivers/soc/apple/t6030-display-gate.c | 3 + tools/asahi/t6030-pmp-graph-tests.c | 56 +++++++++++++++++- tools/asahi/t6030-pmp-graph-tests.py | 16 +++++- 4 files changed, 152 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index 98d96d08f8a434..9aac3ced17cda1 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -1261,6 +1261,9 @@ struct apple_pmp_report_entry { u32 id; /* The PMP does not acknowledge this request (a started PMP only). */ bool no_ack; + /* Withdrawn for system sleep (apple,release-for-sleep), and now so. */ + bool sleep_release; + bool released; bool removed; }; @@ -1368,6 +1371,80 @@ static int apple_pmp_report_entry_set_state(struct generic_pm_domain *genpd, boo return ret; } +/* + * System sleep. An external display processor's request keeps the PMP's + * external display state up while nothing is shown, about 0.25 W in + * suspend-to-idle on J514S. By the noirq phase every display processor has + * suspended, its displays off and its links released or idle: withdraw such a + * request if it is acknowledged, with one replacement write as macOS releases + * its display requests, and assert it again before the processors resume. + */ +static int apple_pmp_report_entry_sleep(struct apple_pmp_report_entry *ent, bool release) +{ + struct apple_pmp_report *rep; + int ret = -ENODEV; + + rcu_read_lock(); + list_for_each_entry_rcu(rep, &apple_pmp_reports, list) { + if (rep->dev->of_node != ent->report_node) + continue; + if (release && (!(readq(rep->base + rep->offsets->status) & PMP_REPORT_READY) || + !(readq(rep->base + rep->offsets->actual) & BIT_ULL(ent->id)))) + ret = -EALREADY; + else + ret = apple_pmp_report_set_state(rep, ent->id, !release, ent->no_ack); + break; + } + rcu_read_unlock(); + return ret; +} + +static int apple_pmp_report_entry_suspend_noirq(struct device *dev) +{ + struct apple_pmp_report_entry *ent = dev_get_drvdata(dev); + int ret; + + if (!ent || !ent->sleep_release || READ_ONCE(ent->removed)) + return 0; + ret = apple_pmp_report_entry_sleep(ent, true); + if (ret == -EALREADY) + return 0; + /* A withdrawal that was not acknowledged is asserted again all the same. */ + if (ret) + dev_warn(dev, "request %u not withdrawn for sleep: %d\n", ent->id, ret); + ent->released = true; + return 0; +} + +static int apple_pmp_report_entry_resume_noirq(struct device *dev) +{ + struct apple_pmp_report_entry *ent = dev_get_drvdata(dev); + int ret, tries = 0; + + if (!ent || !ent->released) + return 0; + ent->released = false; + /* + * Without the request the external display stays dark until reboot: + * write it again, three times at most, while the PMP does not + * acknowledge it in time. + */ + do { + ret = apple_pmp_report_entry_sleep(ent, false); + } while (ret == -ETIMEDOUT && ++tries < 3); + if (ret) + dev_err(dev, "request %u not asserted after sleep: %d\n", ent->id, ret); + else if (tries) + dev_warn(dev, "request %u asserted after sleep on write %d\n", ent->id, + tries + 1); + return ret; +} + +static const struct dev_pm_ops apple_pmp_report_entry_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmp_report_entry_suspend_noirq, + apple_pmp_report_entry_resume_noirq) +}; + static int apple_pmp_report_entry_power_on(struct generic_pm_domain *genpd) { return apple_pmp_report_entry_set_state(genpd, true); @@ -1440,6 +1517,7 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) } ent->no_ack = of_property_read_bool(node, "apple,no-ack"); } + ent->sleep_release = of_property_read_bool(node, "apple,release-for-sleep"); ret = of_property_read_string(node, "label", &name); if (ret < 0) { @@ -1521,6 +1599,7 @@ static struct platform_driver apple_pmp_report_entry_driver = { .driver = { .name = "apple-pmp-report-entry", .of_match_table = apple_pmp_report_entry_of_match, + .pm = pm_sleep_ptr(&apple_pmp_report_entry_pm_ops), /* Power-domain providers are not intended for manual unbinding. */ .suppress_bind_attrs = true, }, diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index c153a4d7cae0fd..7ee02087c969b8 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -1461,6 +1461,9 @@ static int __init gate_ext_apply(struct gate_ext *ext, bool native) /* The driver must wait for this request's acknowledgement before RUN. */ if (!ret) ret = gate_set_u32(cs, ext->dcp, "apple,pmp-report", ext->entry->phandle); + /* No external display is attached in system sleep: the PMP may drop it. */ + if (!ret) + ret = gate_add_bool(cs, ext->entry, "apple,release-for-sleep"); if (!ret) ret = of_changeset_update_prop_string(cs, ext->entry, "status", "okay"); if (!ret) diff --git a/tools/asahi/t6030-pmp-graph-tests.c b/tools/asahi/t6030-pmp-graph-tests.c index e8a6bac35a9a50..d7d079a9673a64 100644 --- a/tools/asahi/t6030-pmp-graph-tests.c +++ b/tools/asahi/t6030-pmp-graph-tests.c @@ -45,7 +45,7 @@ static void host_log(const char *format, ...) #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) struct device_node { const char *name, *compatible; - bool available, always_on, no_ack, deferred, uuid, soc_record; + bool available, always_on, no_ack, deferred, uuid, soc_record, release_sleep; u32 reg; int refs; struct device_node *parent, *child, *sibling, *pmp; @@ -142,6 +142,7 @@ struct apple_pmp_report_entry { struct device_node *node, *report_node; u32 id; bool no_ack, removed; + bool sleep_release, released; }; #define genpd_to_apple_pmp_report_entry(p) \ container_of(p, struct apple_pmp_report_entry, genpd) @@ -297,11 +298,18 @@ static u64 readq(const void *a) { return *(const u64 *)a; } +/* The sleep cases: the PMP acknowledges the requests it is given, ack_late writes late. */ +static bool ack_follows; +static unsigned int ack_late; static void writeq(u64 v, void *a) { assert(a == (char *)regs + 0x108c0); *(u64 *)a = v; regs[0x1180 / 8] = v; + if (ack_follows && ack_late) + ack_late--; + else if (ack_follows) + regs[0x11c0 / 8] = v; } #define readq_poll_timeout(address, value, condition, delay, timeout) \ ({ \ @@ -364,6 +372,8 @@ static bool of_property_read_bool(const struct device_node *n, const char *p) return n->deferred; if (!strcmp(p, "apple,soc-device-record")) return n->soc_record; + if (!strcmp(p, "apple,release-for-sleep")) + return n->release_sleep; return false; } static bool of_property_present(const struct device_node *n, const char *p) @@ -666,6 +676,8 @@ static struct apple_pmp_report *setup(void) put32(devices + i * 0x7c + 8, 2); } ack_polls = 0; + ack_follows = false; + ack_late = 0; check_error = enable_error = 0; hw_binds = hw_ready = true; hw_ack = BIT_ULL(7); @@ -798,6 +810,48 @@ int main(void) assert(g15_vote() == -EINVAL && commands == 0 && !enables); cases++; cleanup(); +#ifdef HAVE_SLEEP_RELEASE + /* System sleep withdraws an acknowledged request marked for it, then asserts it again. */ + setup(); + entries[1].release_sleep = true; + assert(apple_pmp_report_probe(&report_dev) == 0); + assert(apple_pmp_report_wait_ready(&entries[0], 30000) == 0); + struct apple_pmp_report_entry *disp = entry_devs[0].dev.data; + struct apple_pmp_report_entry *ext = entry_devs[1].dev.data; + + assert(ext->sleep_release && !disp->sleep_release); + ack_follows = true; + regs[0x11c0 / 8] = regs[0x1180 / 8]; + u64 target = regs[0x1180 / 8]; + + assert(target & BIT_ULL(8)); + assert(apple_pmp_report_entry_suspend_noirq(&entry_devs[0].dev) == 0 && + !disp->released && regs[0x1180 / 8] == target); + assert(apple_pmp_report_entry_suspend_noirq(&entry_devs[1].dev) == 0); + assert(ext->released && regs[0x1180 / 8] == (target & ~BIT_ULL(8))); + assert(apple_pmp_report_entry_resume_noirq(&entry_devs[1].dev) == 0); + assert(!ext->released && regs[0x1180 / 8] == target); + cases++; + /* A request the PMP has not acknowledged is left as it is. */ + regs[0x11c0 / 8] &= ~BIT_ULL(8); + assert(apple_pmp_report_entry_suspend_noirq(&entry_devs[1].dev) == 0); + assert(!ext->released && regs[0x1180 / 8] == target); + assert(apple_pmp_report_entry_resume_noirq(&entry_devs[1].dev) == 0 && + regs[0x1180 / 8] == target); + cases++; + /* A re-assert the PMP acknowledges late is written again, three times at most. */ + regs[0x11c0 / 8] = target; + assert(apple_pmp_report_entry_suspend_noirq(&entry_devs[1].dev) == 0 && ext->released); + ack_late = 2; + assert(apple_pmp_report_entry_resume_noirq(&entry_devs[1].dev) == 0 && + !ack_late && regs[0x11c0 / 8] == target); + assert(apple_pmp_report_entry_suspend_noirq(&entry_devs[1].dev) == 0 && ext->released); + ack_late = 5; + assert(apple_pmp_report_entry_resume_noirq(&entry_devs[1].dev) == -ETIMEDOUT && + ack_late == 2 && regs[0x1180 / 8] == target); + cases++; + cleanup(); +#endif printf("PASS: %u actual overlay/probe/entry/DCP7/managed-start/supplier graph cases\n", cases); return 0; diff --git a/tools/asahi/t6030-pmp-graph-tests.py b/tools/asahi/t6030-pmp-graph-tests.py index 50cea55ee727ac..8757ee04cde1d6 100644 --- a/tools/asahi/t6030-pmp-graph-tests.py +++ b/tools/asahi/t6030-pmp-graph-tests.py @@ -45,6 +45,13 @@ def main(): 'static int apple_pmp_report_wait_t6030_14(', 'int apple_pmp_report_wait_supplier_ready('] if 'static int apple_pmp_report_wait_t6030_bound(' in source: names.insert(names.index('static struct device_node *apple_pmp_report_t6030_entry('), 'static int apple_pmp_report_wait_t6030_bound(') + # System sleep (apple,release-for-sleep), where the source has it. + sleep = 'static int apple_pmp_report_entry_sleep(' in source + if sleep: + at = names.index('static int apple_pmp_report_entry_power_on(') + names[at:at] = ['static int apple_pmp_report_entry_sleep(', + 'static int apple_pmp_report_entry_suspend_noirq(', + 'static int apple_pmp_report_entry_resume_noirq('] offsets = re.search(r'static const struct apple_pmp_report_offsets apple_pmp_offsets_t6030 = \{.*?\n\};', source, re.S)[0] functions = '\n'.join(extract.function(source, n) for n in names) # The T8122 14.x wait (CONFIG_APPLE_T8122_GPU_PMP_LINK=0 here) precedes the dispatch; it is @@ -54,7 +61,7 @@ def main(): def with_t8122(text): return text.replace(dispatch, t8122 + dispatch, 1) validation = re.sub(r'^#include .*\n', '', (ROOT / 'drivers/pmdomain/apple/pmp-report-validation.h').read_text(), flags=re.M) - code = offsets + '\n' + validation + '\n' + functions + code = ('#define HAVE_SLEEP_RELEASE 1\n' if sleep else '') + offsets + '\n' + validation + '\n' + functions # The actual overlay must still describe a halted supplier and disabled DMA/mailbox. assert overlay.count('status = \"disabled\";') >= 3 (args.output / 'production.h').write_text(with_t8122(code)) @@ -87,6 +94,13 @@ def with_t8122(text): ('storage-ack-layout', 'st->ack & ~st->record_seed,', 'st->seed & ~st->record_seed,'), ('skip-supplier-wait', 'apple_pmp_report_wait_ready(entry, msecs_to_jiffies(timeout_ms))', '((void)timeout_ms, 0)'), ] + if sleep: + controls += [ + ('sleep-releases-unacked', '!(readq(rep->base + rep->offsets->actual) & BIT_ULL(ent->id))', 'false'), + ('sleep-not-reasserted', 'ret = apple_pmp_report_entry_sleep(ent, false);', 'ret = 0;'), + ('sleep-ignores-mark', '!ent->sleep_release ||', ''), + ('sleep-not-rewritten', '++tries < 3', '++tries < 1'), + ] for name, old, new in controls: assert code.count(old) == 1, (name, code.count(old)) (args.output / 'production.h').write_text(with_t8122(code.replace(old, new)))