Skip to content
Merged
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
7 changes: 7 additions & 0 deletions Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ properties:
resets:
maxItems: 1

apple,hold-rx-in-sleep:
description:
Messages from this coprocessor need not wake the system. The OS may
leave them in the receive FIFO while the system sleeps and handle
them at resume.
type: boolean

power-domains:
minItems: 1
maxItems: 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ allOf:
then:
properties:
apple,firmware-owned: false
apple,sleep-retained: false
apple,always-on: false
apple,min-state: false
apple,force-disable: false
Expand Down Expand Up @@ -106,6 +107,13 @@ properties:
coprocessors' CPU domains.
type: boolean

apple,sleep-retained:
description:
A coprocessor keeps running in this domain across system sleep with no
system-sleep callbacks of its own. The OS keeps the domain on during
system sleep. Runtime power management is unaffected.
type: boolean

apple,firmware-owned:
description:
The boot loader hands over active hardware whose power domain remains
Expand Down
4 changes: 4 additions & 0 deletions arch/arm64/boot/dts/apple/t8132-j713-gpu.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
/* ASCWrap v6 contains the same mailbox aperture used by ASC v4. */
compatible = "apple,t8132-asc-mailbox", "apple,asc-mailbox-v4";
reg = <0x3 0x02608000 0x0 0x4000>;
/* Idle ring-service kicks need not wake the system. */
apple,hold-rx-in-sleep;
interrupt-parent = <&aic>;
interrupts = <AIC_IRQ 993 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 994 IRQ_TYPE_LEVEL_HIGH>,
Expand All @@ -95,6 +97,8 @@
&ps_gfx {
status = "okay";
power-domains = <&gfx_tvm>;
/* The G16 firmware stays live across suspend-to-idle. */
apple,sleep-retained;
};

/* The existing report provider owns readiness for shared PMP commands. */
Expand Down
68 changes: 54 additions & 14 deletions drivers/iommu/apple-dart.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <linux/slab.h>
#include <linux/soc/apple/tunable.h>
#include <linux/string.h>
#include <linux/suspend.h>
#include <linux/swab.h>
#include <linux/sysfs.h>
#include <linux/types.h>
Expand Down Expand Up @@ -311,6 +312,8 @@ struct apple_dart {
struct apple_dart_dcp *dcp;
struct apple_dart_dcp *frontend[2];
bool preserve_display;
/* Retained radio/display state left in place across suspend-to-idle. */
bool s2idle_kept;
/* Resources reachable by a retained domain outlive device removal. */
bool retained;
bool retained_mtp;
Expand Down Expand Up @@ -4718,10 +4721,23 @@ static const struct apple_dart_hw apple_dart_hw_t8110 = {
.ttbr_count = 1,
};

static void apple_dart_save_state(struct apple_dart *dart)
{
unsigned int sid, idx;

for (sid = 0; sid < dart->num_streams; sid++) {
dart->save_tcr[sid] = apple_dart_readl(dart,
DART_TCR(dart, sid));
for (idx = 0; idx < dart->hw->ttbr_count; idx++)
dart->save_ttbr[sid][idx] =
apple_dart_readl(dart,
DART_TTBR(dart, sid, idx));
}
}

static __maybe_unused int apple_dart_suspend(struct device *dev)
{
struct apple_dart *dart = dev_get_drvdata(dev);
unsigned int sid, idx;

dart->tunnel_state_restored = false;
if (READ_ONCE(dart->commands_gated))
Expand All @@ -4733,9 +4749,19 @@ static __maybe_unused int apple_dart_suspend(struct device *dev)
*/
if (dart->cold_ane)
return 0;
/* Neither retained radio nor display has a qualified suspend boundary. */
if (dart->managed_sids || dart->preserve_display || apple_dart_has_borrowed(dart))
return -EBUSY;
/*
* Retained radio and display DMA has no suspend boundary of its own.
* Suspend-to-idle leaves it running in place: these DARTs have no
* power domain here, and the DCP keeps the display domains on. The
* registers are saved only for resume to check.
*/
if (dart->managed_sids || dart->preserve_display || apple_dart_has_borrowed(dart)) {
if (pm_suspend_target_state != PM_SUSPEND_TO_IDLE)
return -EBUSY;
apple_dart_save_state(dart);
dart->s2idle_kept = true;
return 0;
}
/* The tunneled DART has no state to save when its domain stays on. */
if (dart->power_retained || dart->retained_mtp)
return 0;
Expand All @@ -4744,15 +4770,7 @@ static __maybe_unused int apple_dart_suspend(struct device *dev)
if (dart->locked)
return 0;

for (sid = 0; sid < dart->num_streams; sid++) {
dart->save_tcr[sid] = apple_dart_readl(dart,
DART_TCR(dart, sid));
for (idx = 0; idx < dart->hw->ttbr_count; idx++)
dart->save_ttbr[sid][idx] =
apple_dart_readl(dart,
DART_TTBR(dart, sid, idx));
}

apple_dart_save_state(dart);
return 0;
}

Expand Down Expand Up @@ -4816,6 +4834,28 @@ static int apple_dart_invalidate_each_stream(struct apple_dart *dart)
return 0;
}

/*
* Resume of a DART whose live DMA stayed in place through suspend-to-idle. It
* cannot be restored from the saved copy. If the translations changed, the
* consumers' DMA is no longer what they set up: refuse their further use
* through the same paths as a translation fault before failing resume, since
* a resume error does not stop the PM core from resuming them.
*/
static int apple_dart_resume_retained(struct apple_dart *dart)
{
if (!dart->s2idle_kept)
return -EBUSY;
dart->s2idle_kept = false;
if (apple_dart_state_retained(dart))
return 0;

dev_err(dart->dev, "retained DMA state lost in suspend-to-idle\n");
if (dart->managed_sids)
apple_dart_managed_quarantine(dart);
apple_dart_quarantine_borrowed(dart);
return -EIO;
}

static __maybe_unused int apple_dart_resume(struct device *dev)
{
struct apple_dart *dart = dev_get_drvdata(dev);
Expand All @@ -4826,7 +4866,7 @@ static __maybe_unused int apple_dart_resume(struct device *dev)
if (dart->cold_ane)
return 0;
if (dart->managed_sids || dart->preserve_display || apple_dart_has_borrowed(dart))
return -EBUSY;
return apple_dart_resume_retained(dart);

/* PCIe-C restored these mappings before starting its downstream link. */
if (dart->tunnel_state_restored) {
Expand Down
6 changes: 5 additions & 1 deletion drivers/pci/controller/pcie-apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -3638,8 +3638,12 @@ static int apple_pcie_suspend_noirq(struct device *dev)
{
struct apple_pcie *pcie = apple_pcie_lookup(dev);

/*
* The managed radio has no suspend boundary of its own; suspend-to-idle
* leaves it in place like any other internal port (no power domain).
*/
if (pcie->managed_sids)
return -EBUSY;
return pm_suspend_target_state == PM_SUSPEND_TO_IDLE ? 0 : -EBUSY;
if (!pcie->hw->tunneled)
return 0;
pcie->reset_on_resume = false;
Expand Down
9 changes: 8 additions & 1 deletion drivers/pmdomain/apple/pmgr-pwrstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct apple_pmgr_ps {
bool externally_clocked;
bool system_sleep;
bool firmware_owned;
/* apple,sleep-retained: kept on while the system sleeps */
bool sleep_retained;
/* Never enable automatic power management (apple,no-auto-pm). */
bool no_auto_pm;
bool inherited_on;
Expand Down Expand Up @@ -263,6 +265,9 @@ static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd)
return 0;
if (ps->inherited_on)
return -EBUSY;
/* A live coprocessor keeps this domain through system sleep. */
if (ps->sleep_retained && ps->system_sleep)
return -EBUSY;

ret = apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE, false);
if (ret)
Expand Down Expand Up @@ -524,14 +529,16 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
* vote and never read, reset or change the hardware state.
*/
ps->firmware_owned = of_property_read_bool(node, "apple,firmware-owned");
ps->sleep_retained = of_property_read_bool(node, "apple,sleep-retained");
/*
* Only T8132 hands over inherited-on domains with this contract. Older
* M3 device trees carry the property with another meaning; keep them
* as they were.
*/
ps->inherited_on = t8132 && of_property_read_bool(node, "apple,inherited-on");
if (ps->inherited_on &&
(ps->firmware_owned || of_property_present(node, "apple,min-state") ||
(ps->firmware_owned || ps->sleep_retained ||
of_property_present(node, "apple,min-state") ||
of_property_present(node, "apple,always-on") ||
of_property_present(node, "apple,force-disable") ||
of_property_present(node, "apple,force-reset") ||
Expand Down
30 changes: 30 additions & 0 deletions drivers/soc/apple/mailbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ static int apple_mbox_probe(struct platform_device *pdev)
return -ENOMEM;

mbox->dev = &pdev->dev;
mbox->hold_rx_in_sleep = of_property_read_bool(dev->of_node, "apple,hold-rx-in-sleep");
mbox->hw = of_device_get_match_data(dev);
if (!mbox->hw)
return -EINVAL;
Expand Down Expand Up @@ -1133,10 +1134,39 @@ static const struct of_device_id apple_mbox_of_match[] = {
};
MODULE_DEVICE_TABLE(of, apple_mbox_of_match);

/*
* The receive IRQ is IRQF_NO_SUSPEND, so each message wakes a CPU out of
* suspend-to-idle. A coprocessor whose messages need not wake the system
* keeps them in the FIFO until resume. The mailbox is a supplier of its
* consumer, so this runs after the consumer suspended and before it resumes.
*/
static int apple_mbox_suspend_noirq(struct device *dev)
{
struct apple_mbox *mbox = dev_get_drvdata(dev);

if (mbox && mbox->hold_rx_in_sleep)
apple_mbox_hold_rx(mbox);
return 0;
}

static int apple_mbox_resume_noirq(struct device *dev)
{
struct apple_mbox *mbox = dev_get_drvdata(dev);

if (mbox && mbox->hold_rx_in_sleep)
apple_mbox_release_rx(mbox);
return 0;
}

static const struct dev_pm_ops apple_mbox_pm_ops = {
NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_mbox_suspend_noirq, apple_mbox_resume_noirq)
};

static struct platform_driver apple_mbox_driver = {
.driver = {
.name = "apple-mailbox",
.of_match_table = apple_mbox_of_match,
.pm = pm_sleep_ptr(&apple_mbox_pm_ops),
},
.probe = apple_mbox_probe,
.remove = apple_mbox_remove,
Expand Down
2 changes: 2 additions & 0 deletions include/linux/soc/apple/mailbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ struct apple_mbox {
bool active;
bool ascwrap_v6_cpu_running;
bool removing;
/* apple,hold-rx-in-sleep: receive IRQ masked across system sleep */
bool hold_rx_in_sleep;

int irq_recv_not_empty;
int irq_send_empty; /* < 0: no send-empty IRQ, TX polls */
Expand Down
Loading