Skip to content
Draft
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
1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -2764,6 +2764,7 @@ F: drivers/gpio/gpio-macsmc.c
F: drivers/hwmon/macsmc-hwmon*.c
F: drivers/pmdomain/apple/
F: drivers/pmdomain/apple/.kunitconfig.apple-pmp
F: drivers/pmdomain/apple/.kunitconfig.pmgr-pwrstate
F: drivers/i2c/busses/i2c-pasemi-core.c
F: drivers/i2c/busses/i2c-pasemi-platform.c
F: drivers/input/misc/macsmc-input.c
Expand Down
5 changes: 5 additions & 0 deletions drivers/pmdomain/apple/.kunitconfig.pmgr-pwrstate
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_ARCH_APPLE=y
CONFIG_PM=y
CONFIG_APPLE_PMGR_PWRSTATE=y
CONFIG_APPLE_PMGR_PWRSTATE_KUNIT_TEST=y
3 changes: 2 additions & 1 deletion drivers/pmdomain/apple/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ config APPLE_PMGR_PWRSTATE_KUNIT_TEST
power management when it is powered on or when a failed power-off
is undone, and that other domains still do. The tests run the
driver's power callbacks against an emulated power state register
and need no Apple hardware.
and need no Apple hardware. Also check the T6000 GFX missing-parent
wait, its timeout, and the separate actual-state acknowledgment.

config APPLE_PMP_REPORT
bool "Apple PMP report control"
Expand Down
150 changes: 144 additions & 6 deletions drivers/pmdomain/apple/pmgr-pwrstate-test.c
Original file line number Diff line number Diff line change
@@ -1,35 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* Included by pmgr-pwrstate.c: apple,no-auto-pm on the power-on and
* power-off paths. The power state register is emulated: its actual state
* follows the target written, unless the test holds it where it is.
* Included by pmgr-pwrstate.c: power transitions and apple,no-auto-pm.
* The emulated register can delay or withhold the parent and actual ACKs.
*/
#include <kunit/device.h>
#include <kunit/test.h>

struct pmgr_ps_test_reg {
u32 val;
bool stuck; /* the actual state no longer follows the target */
unsigned int active_reads; /* reads before a delayed power-on ACK */
unsigned int parent_reads; /* reads before the parent becomes available */
bool parent_stuck;
bool parent_read_error;
unsigned int busy_writes;
unsigned int auto_writes; /* writes that enabled automatic PM */
};

static int pmgr_ps_test_read(void *ctx, unsigned int reg, unsigned int *val)
{
struct pmgr_ps_test_reg *r = ctx;

if (FIELD_GET(APPLE_PMGR_PS_TARGET, r->val) != APPLE_PMGR_PS_ACTIVE)
goto out;
if (r->parent_read_error)
return -EIO;
if (!r->parent_stuck && r->parent_reads && !--r->parent_reads)
r->val &= ~APPLE_PMGR_BUSY;
if (!r->stuck && !(r->val & APPLE_PMGR_BUSY) &&
(!r->active_reads || !--r->active_reads)) {
r->val &= ~APPLE_PMGR_PS_ACTUAL;
r->val |= FIELD_PREP(APPLE_PMGR_PS_ACTUAL, APPLE_PMGR_PS_ACTIVE);
}
out:
*val = r->val;
return 0;
}

static int pmgr_ps_test_write(void *ctx, unsigned int reg, unsigned int val)
{
struct pmgr_ps_test_reg *r = ctx;
u32 actual = r->stuck ? FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val) :
FIELD_GET(APPLE_PMGR_PS_TARGET, val);
u32 parent = r->val & APPLE_PMGR_BUSY;
u32 actual;

if (FIELD_GET(APPLE_PMGR_PS_TARGET, val) == APPLE_PMGR_PS_ACTIVE &&
(r->parent_stuck || r->parent_reads))
parent = APPLE_PMGR_BUSY;
actual = r->stuck || r->active_reads || parent ?
FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val) : FIELD_GET(APPLE_PMGR_PS_TARGET, val);

if (val & APPLE_PMGR_BUSY)
r->busy_writes++;
if (val & APPLE_PMGR_AUTO_ENABLE)
r->auto_writes++;
r->val = (val & ~APPLE_PMGR_PS_ACTUAL) | FIELD_PREP(APPLE_PMGR_PS_ACTUAL, actual);
r->val = (val & ~(APPLE_PMGR_PS_ACTUAL | APPLE_PMGR_BUSY)) | parent |
FIELD_PREP(APPLE_PMGR_PS_ACTUAL, actual);
return 0;
}

Expand Down Expand Up @@ -130,10 +155,123 @@ static void pmgr_ps_power_on_fails_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, r->auto_writes, 0U);
}

/* Only the missing-parent phase gets more than 100 us, followed by an actual ACK. */
static void pmgr_ps_power_on_parent_delayed_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, false, &r);
ps->wait_parent = true;
r->parent_reads = 250;
r->active_reads = 2;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), 0);
KUNIT_EXPECT_EQ(test, r->active_reads, 0U);
KUNIT_EXPECT_EQ(test, r->parent_reads, 0U);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val), APPLE_PMGR_PS_ACTIVE);
KUNIT_EXPECT_EQ(test, r->auto_writes, 1U);
KUNIT_EXPECT_EQ(test, r->busy_writes, 0U);
}

/* Ordinary domains retain the 100 us budget, even with a missing parent. */
static void pmgr_ps_power_on_other_domain_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, true, &r);
ps->system_sleep = true;
r->parent_reads = 1000;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -ETIMEDOUT);
KUNIT_EXPECT_GT(test, r->parent_reads, 0U);
}

/* With no missing parent, even GFX must acknowledge within the usual budget. */
static void pmgr_ps_power_on_actual_delayed_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, true, &r);
ps->wait_parent = true;
ps->system_sleep = true;
r->active_reads = 1000;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -ETIMEDOUT);
KUNIT_EXPECT_GT(test, r->active_reads, 0U);
}

/* An unavailable parent is a bounded failure, including during noirq resume. */
static void pmgr_ps_power_on_parent_stuck_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, true, &r);
ps->wait_parent = true;
ps->system_sleep = true;
r->parent_stuck = true;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -ETIMEDOUT);
KUNIT_EXPECT_TRUE(test, r->val & APPLE_PMGR_BUSY);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val), APPLE_PMGR_PS_CLKGATE);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_TARGET, r->val), APPLE_PMGR_PS_ACTIVE);
KUNIT_EXPECT_EQ(test, r->busy_writes, 0U);
KUNIT_EXPECT_EQ(test, r->auto_writes, 0U);
}

/* Parent availability alone is not an actual-state acknowledgment. */
static void pmgr_ps_power_on_parent_only_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, true, &r);
ps->wait_parent = true;
ps->system_sleep = true;
r->parent_reads = 250;
r->stuck = true;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -ETIMEDOUT);
KUNIT_EXPECT_EQ(test, r->parent_reads, 0U);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val), APPLE_PMGR_PS_CLKGATE);
}

static void pmgr_ps_power_on_parent_read_error_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, true, &r);
ps->wait_parent = true;
ps->system_sleep = true;
r->parent_read_error = true;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -EIO);
KUNIT_EXPECT_EQ(test, r->auto_writes, 0U);
}

/* genpd ignores noirq power-on errors: retain the on request, but fail. */
static void pmgr_ps_power_on_sleep_stuck_test(struct kunit *test)
{
struct pmgr_ps_test_reg *r;
struct apple_pmgr_ps *ps;

ps = pmgr_ps_test_domain(test, APPLE_PMGR_PS_CLKGATE, false, &r);
ps->system_sleep = true;
r->stuck = true;
KUNIT_EXPECT_EQ(test, apple_pmgr_ps_power_on(&ps->genpd), -ETIMEDOUT);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_ACTUAL, r->val), APPLE_PMGR_PS_CLKGATE);
KUNIT_EXPECT_EQ(test, FIELD_GET(APPLE_PMGR_PS_TARGET, r->val), APPLE_PMGR_PS_ACTIVE);
}

static struct kunit_case pmgr_ps_test_cases[] = {
KUNIT_CASE(pmgr_ps_power_on_test),
KUNIT_CASE(pmgr_ps_power_off_undo_test),
KUNIT_CASE(pmgr_ps_power_on_fails_test),
KUNIT_CASE(pmgr_ps_power_on_parent_delayed_test),
KUNIT_CASE(pmgr_ps_power_on_other_domain_test),
KUNIT_CASE(pmgr_ps_power_on_actual_delayed_test),
KUNIT_CASE(pmgr_ps_power_on_parent_stuck_test),
KUNIT_CASE(pmgr_ps_power_on_parent_only_test),
KUNIT_CASE(pmgr_ps_power_on_parent_read_error_test),
KUNIT_CASE(pmgr_ps_power_on_sleep_stuck_test),
{}
};

Expand Down
39 changes: 37 additions & 2 deletions drivers/pmdomain/apple/pmgr-pwrstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <linux/bitops.h>
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
Expand Down Expand Up @@ -37,6 +38,7 @@
#define APPLE_PMGR_PS_PWRGATE 0x0

#define APPLE_PMGR_PS_SET_TIMEOUT 100
#define APPLE_PMGR_PS_PARENT_TIMEOUT 10000
#define APPLE_PMGR_RESET_TIME 1
#define APPLE_PMGR_T8132_RESET_TIME 10

Expand All @@ -58,12 +60,25 @@ struct apple_pmgr_ps {
bool no_auto_enable;
bool needs_reset_idle_wait;
bool strict_state;
bool wait_parent;
u32 reset_time_us;
};

#define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd)
#define rcdev_to_apple_pmgr_ps(_rcdev) container_of(_rcdev, struct apple_pmgr_ps, rcdev)

static int apple_pmgr_ps_wait_parent(struct apple_pmgr_ps *ps, u32 *cur)
{
int ret, err;

/* Bit 11 reports a missing upstream power/clock on T6000 GFX. */
ret = read_poll_timeout_atomic(regmap_read, err,
err || !(*cur & APPLE_PMGR_BUSY),
1, APPLE_PMGR_PS_PARENT_TIMEOUT, false,
ps->regmap, ps->offset, cur);
return err ?: ret;
}

static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool auto_enable)
{
int ret;
Expand Down Expand Up @@ -116,6 +131,20 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
return ret;
cur = reg;

if (ps->wait_parent && pstate == APPLE_PMGR_PS_ACTIVE) {
/*
* GFX can remain clock-gated with bit 11 set on noirq resume,
* even after genpd has powered its AFR parent on. Wait for that
* hardware dependency separately, then allow the usual 100 us
* for the actual state. A missing parent must still fail.
* Use delay-based polling so the wait is bounded even while
* timekeeping is suspended.
*/
ret = apple_pmgr_ps_wait_parent(ps, &cur);
if (ret)
goto report;
}

if (ps->externally_clocked && pstate == APPLE_PMGR_PS_ACTIVE) {
/*
* If this clock domain requires an external clock, then
Expand All @@ -124,11 +153,13 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
ret = regmap_read_poll_timeout_atomic(
ps->regmap, ps->offset, cur,
FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) >= APPLE_PMGR_PS_CLKGATE, 1,
APPLE_PMGR_PS_SET_TIMEOUT);
timeout);
} else {
ret = regmap_read_poll_timeout_atomic(
ps->regmap, ps->offset, cur,
FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1,
FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate &&
(!ps->wait_parent || pstate != APPLE_PMGR_PS_ACTIVE ||
!(cur & APPLE_PMGR_BUSY)), 1,
timeout);
}
if (ps->strict_state) {
Expand All @@ -138,6 +169,7 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
return ret;
}

report:
if (ret < 0)
dev_err_ratelimited(ps->dev,
"PS %s: Failed to reach power state 0x%x (requested 0x%x, now: 0x%x)\n",
Expand Down Expand Up @@ -478,6 +510,9 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
return ret;
}

ps->wait_parent = of_device_is_compatible(node, "apple,t6000-pmgr-pwrstate") &&
ps->offset == 0x3c0;

ps->genpd.flags |= GENPD_FLAG_IRQ_SAFE;
ps->genpd.name = name;
ps->genpd.power_on = apple_pmgr_ps_power_on;
Expand Down