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 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; }; }; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 14d3bdf6403357..cefe696b8a7522 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -4823,11 +4823,12 @@ static void dcp_release_after_sleep(struct apple_dcp *dcp) /* * Why an external processor must keep the system awake, or NULL. The manual * path refuses sleep once its firmware was ever started. A native processor - * refuses while its firmware starts or holds a display link, and while a - * display is attached to it at all, lit or not: resume does not bring an - * attached display back. Idle with nothing attached, its firmware stays - * powered across s2idle like the panel's (the gate holds its CPU domain at - * the active floor) and the next attach reconnects through it. + * refuses while its firmware starts or a display is lit (user space turns + * displays off before sleep). A dark attached display sleeps with it: + * suspend releases an HDMI link and keeps a Type-C tunnel, and resume finds + * the display again as after DPMS off. Its firmware stays powered across + * s2idle like the panel's (the gate holds its CPU domain at the active + * floor), and the next attach reconnects through it. * * A processor that failed (its start, or its session later) never refuses * sleep: it drives no display until reboot, attached or not, and its power @@ -4845,15 +4846,8 @@ static const char *dcp_external_sleep_vetoed(struct apple_dcp *dcp) why = iomfb_v14_7_external_busy(dcp); if (why) return why; - if (iomfb_v14_7_external_failed(dcp)) { - if (dcp->v14) - dev_info(dcp->dev, "external display processor failed earlier: sleeping with it as it is\n"); - return NULL; - } - if (READ_ONCE(dcp->active_typec_route) || READ_ONCE(dcp->typec_cable_connected)) - return "a USB-C display is attached"; - if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd) > 0) - return "an HDMI display is attached"; + if (iomfb_v14_7_external_failed(dcp) && dcp->v14) + dev_info(dcp->dev, "external display processor failed earlier: sleeping with it as it is\n"); return NULL; } diff --git a/drivers/gpu/drm/apple/iomfb_v14_7.c b/drivers/gpu/drm/apple/iomfb_v14_7.c index d55f96b2ba123f..7b095aabe5ed1e 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -304,8 +305,14 @@ struct apple_dcp_v14 { enum dcp_v14_bl_gate bl_gate; u32 nits_sent; u64 nits_presents, brightness_presents, nits_refused; + /* Panel: its display controller is stopped (A472 hardware power off). */ + bool panel_off; }; +static bool panel_hw_off = true; +module_param(panel_hw_off, bool, 0644); +MODULE_PARM_DESC(panel_hw_off, "Internal 14.7 panel: stop its display controller on DPMS off, not only blank it [default: true]"); + /* * The panel's DCP session; one per boot, RTKit is never started twice. The * manual external start waits for it, as the PMP runs once the panel's @@ -1150,6 +1157,24 @@ static int dcp_v14_callback(void *cookie, u32 tag, const void *input, u32 in_siz /* Swap information for a completed swap; the link checks its id. */ if (tag == D(589) && SHAPE(0x6f0, 0)) return 0; + /* + * The panel's display-controller power transitions (A472 with the + * hardware selector): will power off and an intent swap, both + * notifications; display DART power and a kernel power assertion, both + * held by the host throughout, so status 0; and the backlight message + * at power on, which the backlight restore after A472(1) answers. Only + * the J514S/J516S panel sends A472 so; elsewhere these stay unhandled. + */ + if (dcp_v14_board_nits(v14) && + ((tag == D(2) && SHAPE(0, 0)) || (tag == D(591) && SHAPE(20, 0)) || + (tag == D(593) && SHAPE(4, 0)))) + return 0; + if (dcp_v14_board_nits(v14) && (tag == D(574) || tag == D(700)) && SHAPE(4, 4)) { + if (tag == D(574) && in[0] > 1) + return -EINVAL; + put_unaligned_le32(0, out); + return 0; + } if ((tag == D(588) && SHAPE(8, 0)) || (tag == D(598) && SHAPE(0, 0))) return 0; /* Main-display query, answered by asking the firmware. */ @@ -2082,8 +2107,8 @@ bool iomfb_v14_7_external_ready(struct apple_dcp *dcp) /* * What this processor's firmware holds that system sleep would not hand - * back, or NULL: a start in progress, or a display link or a powered - * display. The caller also refuses sleep while a display is attached. + * back, or NULL: a start in progress, or a powered display. Suspend + * releases a dark display's link itself. */ const char *iomfb_v14_7_external_busy(struct apple_dcp *dcp) { @@ -2094,10 +2119,8 @@ const char *iomfb_v14_7_external_busy(struct apple_dcp *dcp) /* A stopped session drives nothing, whatever it held last. */ if (dcp->v14 && READ_ONCE(dcp->v14->failed)) return NULL; - if (READ_ONCE(dcp->dptxport[0].connected) || - READ_ONCE(dcp->external_link_ready) || - (dcp->v14 && READ_ONCE(dcp->v14->powered))) - return "a display link is up"; + if (dcp->v14 && READ_ONCE(dcp->v14->powered)) + return "a display is lit"; return NULL; default: return NULL; @@ -3108,6 +3131,32 @@ static int dcp_v14_set_power(struct apple_dcp_v14 *v14, bool on) return ret; } +/* + * A472 with the hardware selector (+8) and a null output word (+11): the + * panel's display controller off or on. Off stops scanout and the panel once + * a blank has been presented, on restarts them; the soft form (+8 clear) + * leaves the controller running. The client, the RTKit session and every + * mapping are kept. + */ +static int dcp_v14_panel_power(struct apple_dcp_v14 *v14, bool on) +{ + u8 in[12] = {}, out[8] = {}; + int ret; + + put_unaligned_le64(on, in); + in[8] = 1; + in[11] = 1; + ret = dcp_v14_call(v14, A(472), in, sizeof(in), out, sizeof(out), 0); + if (!ret && get_unaligned_le32(out + 4)) { + dev_err(v14->dev, "display controller %s refused: %#x\n", on ? "on" : "off", + get_unaligned_le32(out + 4)); + ret = -EIO; + } + if (!ret) + v14->panel_off = !on; + return ret; +} + /* The display is fully described, and no description is in transfer. */ static bool dcp_v14_external_described(struct apple_dcp_v14 *v14) { @@ -3344,8 +3393,35 @@ void iomfb_v14_7_flush(struct apple_dcp *dcp, struct drm_crtc *crtc, dcp_v14_cancel_event(dcp); } +/* A system sleep is in progress, as opposed to a DPMS change. */ +static bool dcp_v14_system_sleep(void) +{ + KUNIT_STATIC_STUB_REDIRECT(dcp_v14_system_sleep); + return pm_suspend_in_progress(); +} + void iomfb_v14_7_poweron(struct apple_dcp *dcp) { + struct apple_dcp_v14 *v14 = dcp->v14; + bool dark; + + /* + * The display controller first, then the backlight, as at boot. A + * refused power on is tried once more. If the controller stays off, + * so does the backlight, rather than light a dark panel; the next + * power on tries again. + */ + if (!dcp->external && v14 && v14->panel_off) { + mutex_lock(&v14->lock); + if (v14->panel_off && dcp_v14_panel_power(v14, true)) + dcp_v14_panel_power(v14, true); + dark = v14->panel_off; + mutex_unlock(&v14->lock); + if (dark) { + dev_err(dcp->dev, "display controller did not power on: backlight left off\n"); + return; + } + } /* The next present restores a level only after Linux has set one. */ if (dcp->v14 && dcp_v14_bl_dpms_applies(dcp->v14, dcp)) dcp_backlight_dpms(dcp, true); @@ -3369,9 +3445,21 @@ void iomfb_v14_7_poweroff(struct apple_dcp *dcp) dcp_mode_invalidate(&dcp->mode_state); return; } - /* Blank to black; the panel and the DCP stay powered. */ - if (v14 && v14->started) - dcp_v14_blank(v14, dcp); + /* Blank to black, then stop the display controller; the DCP stays powered. */ + if (!v14 || !v14->started) + return; + dcp_v14_blank(v14, dcp); + /* + * Only the J514S/J516S panel, the one this was measured on, and only + * on DPMS off: suspend-to-idle cuts that power itself. + */ + if (!panel_hw_off || !dcp_v14_board_nits(v14) || dcp_v14_system_sleep()) + return; + mutex_lock(&v14->lock); + if (!v14->panel_off && !READ_ONCE(v14->failed) && !READ_ONCE(dcp->crashed) && + !v14->active_fb && dcp_v14_panel_power(v14, false)) + dev_err(dcp->dev, "display controller did not power off\n"); + mutex_unlock(&v14->lock); } #if IS_ENABLED(CONFIG_DRM_APPLE_DISPLAY_KUNIT_TEST) diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c b/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c index 29acb9d5fe9e99..52eac9b835979b 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c @@ -31,6 +31,9 @@ struct v14_bl_rpc { u64 iova[5], nits[5]; bool nits_present[5]; struct drm_framebuffer *owner[5]; + /* A472 hardware power states, in order. */ + u64 power[4]; + unsigned int power_calls, power_refusals; bool block_first, refuse; }; @@ -49,6 +52,23 @@ static int v14_bl_rpc_call(struct apple_dcp_v14 *v14, u32 tag, const void *in, u put_unaligned_le32(1, out); return 0; } + /* The panel's display controller: the hardware selector, a null output. */ + if (tag == A(472)) { + KUNIT_EXPECT_EQ(c->test, in_size, 12U); + KUNIT_EXPECT_EQ(c->test, out_size, 8U); + KUNIT_EXPECT_EQ(c->test, ((const u8 *)in)[8], 1); + KUNIT_EXPECT_EQ(c->test, ((const u8 *)in)[11], 1); + /* Off only once the blank has retired the frame. */ + KUNIT_EXPECT_PTR_EQ(c->test, v14->active_fb, NULL); + if (c->power_calls < ARRAY_SIZE(c->power)) + c->power[c->power_calls] = get_unaligned_le64(in); + c->power_calls++; + if (c->power_refusals) { + c->power_refusals--; + put_unaligned_le32(1, (u8 *)out + 4); + } + return 0; + } KUNIT_EXPECT_EQ(c->test, tag, A(407)); KUNIT_EXPECT_EQ(c->test, in_size, (u32)DCP_V14_SWAP_SIZE); n = atomic_inc_return(&c->submits) - 1; @@ -285,8 +305,15 @@ static void v14_bl_dpms_dispatch(struct kunit *test) KUNIT_EXPECT_EQ(test, atomic_read(&c->submits), 2); KUNIT_EXPECT_FALSE(test, c->nits_present[1]); KUNIT_EXPECT_PTR_EQ(test, c->f.v14->active_fb, NULL); + /* The blank, then the display controller off. */ + KUNIT_EXPECT_EQ(test, c->power_calls, 1U); + KUNIT_EXPECT_EQ(test, c->power[0], 0ULL); + KUNIT_EXPECT_TRUE(test, c->f.v14->panel_off); /* Even the advertised default must be an explicit first request. */ dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 2U); + KUNIT_EXPECT_EQ(test, c->power[1], 1ULL); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); v14_bl_request(test, c->f.dcp, DCP_V14_NITS_DEFAULT); KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); KUNIT_EXPECT_TRUE(test, c->nits_present[2]); @@ -296,7 +323,10 @@ static void v14_bl_dpms_dispatch(struct kunit *test) KUNIT_EXPECT_TRUE(test, c->nits_present[3]); KUNIT_EXPECT_EQ(test, c->nits[3], 0ULL); KUNIT_EXPECT_PTR_EQ(test, c->f.v14->active_fb, NULL); + KUNIT_EXPECT_EQ(test, c->power_calls, 3U); dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 4U); + KUNIT_EXPECT_EQ(test, c->power[3], 1ULL); KUNIT_EXPECT_TRUE(test, dcp_backlight_pending(c->f.dcp)); KUNIT_EXPECT_FALSE(test, c->f.dcp->backlight.state.dpms_off); KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); @@ -306,6 +336,85 @@ static void v14_bl_dpms_dispatch(struct kunit *test) KUNIT_EXPECT_FALSE(test, dcp_backlight_pending(c->f.dcp)); } +/* appledrm.panel_hw_off=0: DPMS off only blanks the panel. */ +static void v14_bl_dpms_blank_only(struct kunit *test) +{ + struct v14_bl_rpc *c = v14_bl_rpc_setup(test); + struct platform_device *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + bool saved = panel_hw_off; + + KUNIT_ASSERT_NOT_NULL(test, pdev); + platform_set_drvdata(pdev, c->f.dcp); + KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); + panel_hw_off = false; + dcp_poweroff(pdev); + dcp_poweron(pdev); + panel_hw_off = saved; + KUNIT_EXPECT_EQ(test, atomic_read(&c->submits), 2); + KUNIT_EXPECT_PTR_EQ(test, c->f.v14->active_fb, NULL); + KUNIT_EXPECT_EQ(test, c->power_calls, 0U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); +} + +static bool v14_bl_in_system_sleep(void) +{ + return true; +} + +/* System sleep only blanks the panel; s2idle cuts the controller's power. */ +static void v14_bl_sleep_blank_only(struct kunit *test) +{ + struct v14_bl_rpc *c = v14_bl_rpc_setup(test); + struct platform_device *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pdev); + platform_set_drvdata(pdev, c->f.dcp); + KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); + kunit_activate_static_stub(test, dcp_v14_system_sleep, v14_bl_in_system_sleep); + dcp_poweroff(pdev); + dcp_poweron(pdev); + kunit_deactivate_static_stub(test, dcp_v14_system_sleep); + KUNIT_EXPECT_EQ(test, atomic_read(&c->submits), 2); + KUNIT_EXPECT_PTR_EQ(test, c->f.v14->active_fb, NULL); + KUNIT_EXPECT_EQ(test, c->power_calls, 0U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); +} + +/* + * A refused power on is tried once more; a controller that stays off keeps + * the backlight off, and the next power on tries again. + */ +static void v14_bl_power_on_refused(struct kunit *test) +{ + struct v14_bl_rpc *c = v14_bl_rpc_setup(test); + struct platform_device *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pdev); + platform_set_drvdata(pdev, c->f.dcp); + v14_bl_request(test, c->f.dcp, DCP_V14_NITS_DEFAULT); + KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); + dcp_poweroff(pdev); + KUNIT_ASSERT_TRUE(test, c->f.v14->panel_off); + /* Refused once: the second try powers it on. */ + c->power_refusals = 1; + dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 3U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); + KUNIT_EXPECT_FALSE(test, c->f.dcp->backlight.state.dpms_off); + dcp_poweroff(pdev); + /* Refused twice: dark, with the backlight still off. */ + c->power_refusals = 2; + dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 6U); + KUNIT_EXPECT_TRUE(test, c->f.v14->panel_off); + KUNIT_EXPECT_TRUE(test, c->f.dcp->backlight.state.dpms_off); + /* The next power on tries again and restores the backlight. */ + dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 7U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); + KUNIT_EXPECT_FALSE(test, c->f.dcp->backlight.state.dpms_off); +} + /* An actual failed submit must leave the loader's level unknown. */ static void v14_bl_failed_submit_unknown(struct kunit *test) { @@ -406,6 +515,9 @@ static struct kunit_case v14_bl_lifetime_cases[] = { KUNIT_CASE(v14_bl_present_publication_serialized), KUNIT_CASE(v14_bl_running_work_suspend), KUNIT_CASE(v14_bl_dpms_dispatch), + KUNIT_CASE(v14_bl_dpms_blank_only), + KUNIT_CASE(v14_bl_sleep_blank_only), + KUNIT_CASE(v14_bl_power_on_refused), KUNIT_CASE(v14_bl_failed_submit_unknown), KUNIT_CASE(v14_bl_registration_unbind_race), KUNIT_CASE(v14_bl_class_unregistered), 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/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); } diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index c153a4d7cae0fd..edd69d642ac0d5 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); + /* In system sleep its displays are dark or detached: 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/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, 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)))