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
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ properties:
unlike apple,firmware-owned, their registers remain writable.
type: boolean

apple,preserve-active-control:
description:
Firmware hands off this domain with an active control word that must be
retained until an actual power transition is requested. Do not enable
auto-PM at probe or rewrite a reset-free, non-busy control word whose
target and actual state already satisfy an active request. Power-down
and requests that require a state change retain the normal sequence.
type: boolean

apple,min-state:
description:
Specifies the minimum power state for auto-PM.
Expand Down
47 changes: 46 additions & 1 deletion drivers/pmdomain/apple/pmgr-pwrstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
#define APPLE_PMGR_RESET_TIME 1
#define APPLE_PMGR_T8132_RESET_TIME 10

/* Opt-in investigation: normal power transitions do not log. */
static bool diagnostic_trace;
module_param(diagnostic_trace, bool, 0444);
MODULE_PARM_DESC(diagnostic_trace, "Trace native power handoff and register writes (diagnostic)");

struct apple_pmgr_ps {
struct device *dev;
struct generic_pm_domain genpd;
Expand All @@ -58,6 +63,7 @@ struct apple_pmgr_ps {
bool needs_reset_idle_wait;
bool strict_state;
u32 reset_time_us;
bool preserve_active_control;
};

#define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd)
Expand All @@ -70,10 +76,27 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
u32 reg, cur;
u32 timeout = ps->strict_state ? 10000 : APPLE_PMGR_PS_SET_TIMEOUT;

if (diagnostic_trace)
dev_info(ps->dev, "PMGR begin %s offset=%#x requested=%#x auto=%u\n",
genpd->name, ps->offset, pstate, auto_enable);
ret = regmap_read(ps->regmap, ps->offset, &reg);
if (ret < 0)
return ret;

if (diagnostic_trace)
dev_info(ps->dev, "PMGR read %s offset=%#x value=%#x\n",
genpd->name, ps->offset, reg);

/* Preserve the explicit firmware handoff when the requested active state
* is already satisfied. Real transitions retain the ordinary sequence.
*/
if (ps->preserve_active_control && pstate == APPLE_PMGR_PS_ACTIVE &&
FIELD_GET(APPLE_PMGR_PS_TARGET, reg) == pstate &&
FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate &&
!(reg & (APPLE_PMGR_RESET | APPLE_PMGR_PS_RESET |
APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_BUSY)))
return 0;

/* Resets are synchronous, and only work if the device is powered and clocked. */
if (reg & APPLE_PMGR_RESET && pstate != APPLE_PMGR_PS_ACTIVE)
dev_err_ratelimited(ps->dev, "PS %s: powering off with RESET active\n",
Expand All @@ -88,7 +111,13 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
if (ps->force_reset)
reg_pre |= APPLE_PMGR_PS_RESET;

if (diagnostic_trace)
dev_info(ps->dev, "PMGR pre-write %s offset=%#x value=%#x begin\n",
genpd->name, ps->offset, reg_pre);
regmap_write(ps->regmap, ps->offset, reg_pre);
if (diagnostic_trace)
dev_info(ps->dev, "PMGR pre-write %s offset=%#x completed\n",
genpd->name, ps->offset);

ret = regmap_read_poll_timeout_atomic(
ps->regmap, ps->offset, cur,
Expand All @@ -110,10 +139,16 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a

dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, pstate, reg);

if (diagnostic_trace)
dev_info(ps->dev, "PMGR write %s offset=%#x value=%#x begin\n",
genpd->name, ps->offset, reg);
ret = regmap_write(ps->regmap, ps->offset, reg);
if (ret && ps->strict_state)
return ret;
cur = reg;
if (diagnostic_trace)
dev_info(ps->dev, "PMGR write %s offset=%#x completed\n",
genpd->name, ps->offset);

if (ps->externally_clocked && pstate == APPLE_PMGR_PS_ACTIVE) {
/*
Expand Down Expand Up @@ -147,7 +182,13 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
reg |= APPLE_PMGR_AUTO_ENABLE;
if (ps->strict_state)
return regmap_write(ps->regmap, ps->offset, reg);
if (diagnostic_trace)
dev_info(ps->dev, "PMGR auto-write %s offset=%#x value=%#x begin\n",
genpd->name, ps->offset, reg);
regmap_write(ps->regmap, ps->offset, reg);
if (diagnostic_trace)
dev_info(ps->dev, "PMGR auto-write %s offset=%#x completed\n",
genpd->name, ps->offset);
}

return ret;
Expand Down Expand Up @@ -465,6 +506,9 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
if (of_property_read_bool(node, "apple,externally-clocked"))
ps->externally_clocked = true;

ps->preserve_active_control =
of_property_read_bool(node, "apple,preserve-active-control");

/* A cold-started coprocessor CPU must not be gated behind its firmware. */
ps->no_auto_pm = of_property_read_bool(node, "apple,no-auto-pm");

Expand Down Expand Up @@ -512,7 +556,8 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
}

/* Turn on auto-PM if the domain is already on, unless it must stay off */
if (active && !ps->firmware_owned && !ps->inherited_on && !ps->no_auto_enable)
if (active && !ps->firmware_owned && !ps->inherited_on && !ps->no_auto_enable &&
!ps->preserve_active_control)
regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE,
ps->no_auto_pm ? 0 : APPLE_PMGR_AUTO_ENABLE);

Expand Down
73 changes: 73 additions & 0 deletions tools/testing/selftests/aurora-apif/pmgr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Exercise the production retained-active PMGR contract with a register test double."""
from pathlib import Path
import subprocess,tempfile,sys
root=Path(__file__).resolve().parents[4]
p=Path(sys.argv[1]) if len(sys.argv)>1 else root/'drivers/pmdomain/apple/pmgr-pwrstate.c'
s=p.read_text();start=s.index('static int apple_pmgr_ps_set(');brace=s.index('{',start);end=brace+1;depth=1
while depth:
depth+=(s[end]=='{')-(s[end]=='}');end+=1
fn=s[start:end]
defines='\n'.join(l for l in s.splitlines() if l.startswith('#define APPLE_PMGR_'))
code=r'''
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
typedef uint32_t u32;
#define BIT(n) (1U<<(n))
#define GENMASK(h,l) ((~0U>>(31-(h)))&(~0U<<(l)))
#define FIELD_GET(mask,v) (((v)&(mask))>>__builtin_ctz(mask))
#define FIELD_PREP(mask,v) (((v)<<__builtin_ctz(mask))&(mask))
#define dev_info(...) ((void)0)
#define dev_dbg(...) ((void)0)
#define dev_err(...) ((void)0)
#define dev_err_ratelimited(...) ((void)0)
static bool diagnostic_trace;
''' + defines + r'''
struct device {};
struct generic_pm_domain { const char *name; };
struct regmap { u32 value; unsigned writes; u32 written[8]; int fail_read; };
struct apple_pmgr_ps { struct device *dev; struct generic_pm_domain genpd; struct regmap *regmap; u32 offset; bool force_disable,force_reset,externally_clocked,preserve_active_control,strict_state; };
#define genpd_to_apple_pmgr_ps(g) ((struct apple_pmgr_ps *)((char *)(g)-offsetof(struct apple_pmgr_ps,genpd)))
static int regmap_read(struct regmap *m,u32 offset,u32 *value) {
assert(offset==0x180);if(m->fail_read)return m->fail_read;*value=m->value;return 0;
}
static int regmap_write(struct regmap *m,u32 offset,u32 value) {
assert(offset==0x180&&m->writes<8);m->written[m->writes++]=value;
/* Test double: control transitions reach their requested actual state. */
m->value=(value&~APPLE_PMGR_PS_ACTUAL)|FIELD_PREP(APPLE_PMGR_PS_ACTUAL,FIELD_GET(APPLE_PMGR_PS_TARGET,value));return 0;
}
#define regmap_read_poll_timeout_atomic(map,offset,cur,condition,delay,timeout) \
({ int err=regmap_read(map,offset,&cur);err?err:((condition)?0:-110); })
''' + fn + r'''
int main(void) {
struct regmap m={.value=0x510302ff};
struct apple_pmgr_ps ps={.genpd={.name="retained"},.regmap=&m,.offset=0x180,.preserve_active_control=true};
u32 original=m.value;
assert(!apple_pmgr_ps_set(&ps.genpd,15,true));assert(m.writes==0&&m.value==original);
ps.preserve_active_control=false;m=(struct regmap){.value=original};
assert(!apple_pmgr_ps_set(&ps.genpd,15,true));assert(m.writes==2);
ps.preserve_active_control=true;m=(struct regmap){.value=0};
assert(!apple_pmgr_ps_set(&ps.genpd,15,true));assert(m.writes==2);
assert(FIELD_GET(APPLE_PMGR_PS_TARGET,m.written[0])==15);
m=(struct regmap){.value=original};
assert(!apple_pmgr_ps_set(&ps.genpd,0,false));assert(m.writes==1);
assert(FIELD_GET(APPLE_PMGR_PS_TARGET,m.written[0])==0);
for(unsigned i=0;i<4;i++) {
const u32 invalid[]={APPLE_PMGR_BUSY,APPLE_PMGR_RESET,APPLE_PMGR_PS_RESET,APPLE_PMGR_DEV_DISABLE};
m=(struct regmap){.value=original|invalid[i]};
assert(!apple_pmgr_ps_set(&ps.genpd,15,true));assert(m.writes==2);
assert(!(m.written[0]&(APPLE_PMGR_BUSY|APPLE_PMGR_FLAGS|APPLE_PMGR_AUTO_ENABLE)));
}
m=(struct regmap){.value=original,.fail_read=-5};
assert(apple_pmgr_ps_set(&ps.genpd,15,true)==-5);assert(m.writes==0);
puts("PMGR retained-active and real-transition contracts pass");
}
'''
with tempfile.TemporaryDirectory(prefix='m5-pmgr-host-') as tmp:
t=Path(tmp);(t/'test.c').write_text(code)
subprocess.run(['cc','-std=gnu11','-fsanitize=address,undefined','-fno-omit-frame-pointer',str(t/'test.c'),'-o',str(t/'test')],check=True)
subprocess.run([str(t/'test')],check=True)