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
78 changes: 76 additions & 2 deletions Documentation/devicetree/bindings/power/apple,pmc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ properties:
description: |
The perf-state floor words of the voter agents ("voters"), and the
per-agent words holding the floor the PMC honours for each agent with
the per-rail interface enable words ("rails"). Optionally, the cluster
enable control word followed by its status word ("clusters").
the per-rail interface enable words ("rails"); on T8132 the rails
block also holds the bandwidth request and boost interface enable
words. Optionally, the cluster enable control word followed by its
status word ("clusters").

reg-names:
description: |
Expand Down Expand Up @@ -178,6 +180,43 @@ patternProperties:
- '#power-domain-cells'
- apple,cluster-bit

'^interface-[a-z0-9-]+$':
type: object
description: |
A display pipe's interface enables, provided as a power domain. The
bandwidth request (BWR) and boost interfaces each have an enable word
with one bit per agent, and the PMC ignores the pipe's bandwidth and
boost votes while its bits are clear. Both bits are set after the
parent domain powers on, and cleared before it powers off.
additionalProperties: false

properties:
label:
description: Name of the power domain.

'#power-domain-cells':
const: 0

power-domains:
maxItems: 1
description: Domain that must be powered while the enables are set.

apple,bwr-bit:
$ref: /schemas/types.yaml#/definitions/uint32
maximum: 31
description: Bit of the pipe in the BWR interface enable word.

apple,boost-bit:
$ref: /schemas/types.yaml#/definitions/uint32
maximum: 31
description: Bit of the pipe in the boost interface enable word.

required:
- '#power-domain-cells'
- power-domains
- apple,bwr-bit
- apple,boost-bit

'^performance-controller@[0-9a-f]+$':
type: object
description: The host performance controller's voter agent.
Expand Down Expand Up @@ -207,6 +246,17 @@ required:
- reg
- reg-names

allOf:
- if:
properties:
compatible:
not:
contains:
const: apple,t8132-pmc
then:
patternProperties:
'^interface-[a-z0-9-]+$': false

additionalProperties: false

examples:
Expand Down Expand Up @@ -245,3 +295,27 @@ examples:
};
};
};

- |
soc {
#address-cells = <2>;
#size-cells = <2>;

power-management@382800000 {
compatible = "apple,t8132-pmc";
reg = <0x3 0x82800000 0x0 0x10000>,
<0x3 0x828aa000 0x0 0x3000>;
reg-names = "voters", "rails";
#address-cells = <1>;
#size-cells = <0>;
apple,static-voters = <3>;

interface-dispext0 {
label = "dispext0-interface";
#power-domain-cells = <0>;
power-domains = <&ps_dispext0_sys>;
apple,bwr-bit = <3>;
apple,boost-bit = <1>;
};
};
};
12 changes: 12 additions & 0 deletions arch/arm64/boot/dts/apple/t8132-j713.dts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,18 @@
apple,static-voters = <3>;
};

/*
* The PMC enables of an external pipe are set once the pipe's SYS state is
* on, and before its FE state powers on.
*/
&ps_dispext0_fe {
power-domains = <&ps_dispext0_sys>, <&pmc_dispext0>;
};

&ps_dispext1_fe {
power-domains = <&ps_dispext1_sys>, <&pmc_dispext1>;
};

&smc {
status = "okay";

Expand Down
21 changes: 21 additions & 0 deletions arch/arm64/boot/dts/apple/t8132-memory.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@
apple,pmp = <&pmp>;
status = "disabled";

/*
* The external display pipes' bandwidth request (BWR) and
* boost interface enables. The PMC ignores a pipe's bandwidth
* and display clock votes while they are clear.
*/
pmc_dispext0: interface-dispext0 {
label = "dispext0-interface";
#power-domain-cells = <0>;
power-domains = <&ps_dispext0_sys>;
apple,bwr-bit = <3>;
apple,boost-bit = <1>;
};

pmc_dispext1: interface-dispext1 {
label = "dispext1-interface";
#power-domain-cells = <0>;
power-domains = <&ps_dispext1_sys>;
apple,bwr-bit = <4>;
apple,boost-bit = <2>;
};

clpc: performance-controller@3 {
compatible = "apple,t8132-clpc";
reg = <3>;
Expand Down
5 changes: 3 additions & 2 deletions drivers/pmdomain/apple/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ config APPLE_PMC
shared rails, such as the DRAM controller and the SoC fabric,
between voter agents. This driver enables the static agents and
provides power domains that enable a dynamic agent, such as the
GPU, while its device is powered on. It can also limit the DRAM
controller rail as a thermal cooling device.
GPU, or a display pipe's bandwidth and boost interfaces, while
its device is powered on. It can also limit the DRAM controller
rail as a thermal cooling device.

config APPLE_PMC_KUNIT_TEST
bool "KUnit tests for the Apple PMC voter interface" if !KUNIT_ALL_TESTS
Expand Down
208 changes: 208 additions & 0 deletions drivers/pmdomain/apple/pmc-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <kunit/device.h>
#include <kunit/test.h>
#include <linux/pm_runtime.h>

#define APPLE_PMC_TEST_RAILS_SIZE 0x4000

Expand Down Expand Up @@ -281,6 +282,209 @@ static void apple_pmc_test_dcs_floor(struct kunit *test)
KUNIT_EXPECT_EQ(test, apple_pmc_test_ap_floor(pmc) & ~APPLE_PMC_RAIL_MASK(hw->dcs_rail), 0U);
}

/*
* A T8132 interface domain sets and clears its own BWR and boost enables
* only: the internal display's boost enable, bit 0, stays set.
*/
static void apple_pmc_test_interface(struct kunit *test)
{
struct apple_pmc *pmc = apple_pmc_test_alloc(test);
struct apple_pmc_domain dom = {
.pmc = pmc, .interface = true, .bwr_bit = 3, .boost_bit = 1,
};
const struct apple_pmc_hw *hw = &apple_pmc_hw_t8132;

pmc->hw = hw;
writel(BIT(0), pmc->rails + hw->boost_enable);

KUNIT_ASSERT_EQ(test, apple_pmc_domain_power_on(&dom.genpd), 0);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->bwr_enable), BIT(3));
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->boost_enable), BIT(0) | BIT(1));
KUNIT_ASSERT_EQ(test, apple_pmc_domain_power_off(&dom.genpd), 0);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->bwr_enable), 0U);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->boost_enable), BIT(0));
}

struct apple_pmc_test_stuck {
u32 offset;
u32 value;
};

/* The interface enable word at stuck->offset keeps stuck->value. */
static u32 apple_pmc_test_stuck_read(struct apple_pmc *pmc, u32 offset)
{
struct kunit *test = kunit_get_current_test();
struct apple_pmc_test_stuck *stuck = test->priv;

if (offset == stuck->offset)
writel(stuck->value, pmc->rails + offset);
return readl(pmc->rails + offset);
}

/*
* If the BWR enable does not change, the boost enable goes back too, both
* when the domain powers on and when it powers off.
*/
static void apple_pmc_test_interface_stuck(struct kunit *test)
{
struct apple_pmc *pmc = apple_pmc_test_alloc(test);
struct apple_pmc_domain dom = {
.pmc = pmc, .interface = true, .bwr_bit = 3, .boost_bit = 1,
};
const struct apple_pmc_hw *hw = &apple_pmc_hw_t8132;
struct apple_pmc_test_stuck stuck = { .offset = hw->bwr_enable };

pmc->hw = hw;
writel(BIT(0), pmc->rails + hw->boost_enable);
test->priv = &stuck;
kunit_activate_static_stub(test, apple_pmc_interface_read,
apple_pmc_test_stuck_read);

KUNIT_EXPECT_EQ(test, apple_pmc_domain_power_on(&dom.genpd), -EIO);
KUNIT_EXPECT_EQ(test, apple_pmc_interface_read(pmc, hw->bwr_enable), 0U);
KUNIT_EXPECT_EQ(test, apple_pmc_interface_read(pmc, hw->boost_enable), BIT(0));

stuck.value = BIT(3);
writel(BIT(0) | BIT(1), pmc->rails + hw->boost_enable);
KUNIT_EXPECT_EQ(test, apple_pmc_domain_power_off(&dom.genpd), -EIO);
KUNIT_EXPECT_EQ(test, apple_pmc_interface_read(pmc, hw->bwr_enable), BIT(3));
KUNIT_EXPECT_EQ(test, apple_pmc_interface_read(pmc, hw->boost_enable),
BIT(0) | BIT(1));

kunit_deactivate_static_stub(test, apple_pmc_interface_read);
test->priv = NULL;
}

static void apple_pmc_test_genpd_remove(void *genpd)
{
pm_genpd_remove(genpd);
}

static void apple_pmc_test_genpd_detach(void *dev)
{
pm_runtime_disable(dev);
pm_genpd_remove_device(dev);
}

/* A genpd that is removed again when the test ends, children first. */
static struct generic_pm_domain *apple_pmc_test_genpd(struct kunit *test,
struct generic_pm_domain *genpd,
int ret)
{
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
apple_pmc_test_genpd_remove,
genpd), 0);
return genpd;
}

static struct apple_pmc_domain *apple_pmc_test_interface_dom(struct kunit *test,
struct apple_pmc *pmc,
const char *name)
{
struct apple_pmc_domain *dom = kunit_kzalloc(test, sizeof(*dom), GFP_KERNEL);

KUNIT_ASSERT_NOT_NULL(test, dom);
dom->pmc = pmc;
dom->interface = true;
dom->bwr_bit = 3;
dom->boost_bit = 1;
dom->genpd.name = name;
apple_pmc_test_genpd(test, &dom->genpd, apple_pmc_interface_init(pmc, dom));
return dom;
}

/*
* Link the interface domain as the PMGR driver does: under a SYS power
* state, with an FE power state below both, all of them IRQ safe. Powering
* FE through a device sets the enables after SYS is on, and powering it
* off clears them.
*/
static void apple_pmc_test_interface_genpd(struct kunit *test)
{
struct apple_pmc *pmc = apple_pmc_test_alloc(test);
const struct apple_pmc_hw *hw = &apple_pmc_hw_t8132;
struct generic_pm_domain *sys, *fe;
struct apple_pmc_domain *dom;
struct device *dev;

pmc->hw = hw;
writel(BIT(0), pmc->rails + hw->boost_enable);
pmc->saved_boost = BIT(0);
dev = kunit_device_register(test, "apple-pmc-test-fe");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);

sys = kunit_kzalloc(test, sizeof(*sys), GFP_KERNEL);
fe = kunit_kzalloc(test, sizeof(*fe), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, sys);
KUNIT_ASSERT_NOT_NULL(test, fe);
sys->name = "apple-pmc-test-sys";
sys->flags = GENPD_FLAG_IRQ_SAFE;
fe->name = "apple-pmc-test-fe";
fe->flags = GENPD_FLAG_IRQ_SAFE;
apple_pmc_test_genpd(test, sys, pm_genpd_init(sys, NULL, false));
dom = apple_pmc_test_interface_dom(test, pmc, "apple-pmc-test-interface");
KUNIT_EXPECT_EQ(test, dom->genpd.status, GENPD_STATE_OFF);
apple_pmc_test_genpd(test, fe, pm_genpd_init(fe, NULL, true));

KUNIT_ASSERT_EQ(test, pm_genpd_add_subdomain(sys, &dom->genpd), 0);
KUNIT_ASSERT_EQ(test, pm_genpd_add_subdomain(sys, fe), 0);
KUNIT_ASSERT_EQ(test, pm_genpd_add_subdomain(&dom->genpd, fe), 0);

KUNIT_ASSERT_EQ(test, pm_genpd_add_device(fe, dev), 0);
pm_runtime_enable(dev);
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
apple_pmc_test_genpd_detach,
dev), 0);

KUNIT_ASSERT_EQ(test, pm_runtime_resume_and_get(dev), 0);
KUNIT_EXPECT_EQ(test, dom->genpd.status, GENPD_STATE_ON);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->bwr_enable), BIT(3));
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->boost_enable), BIT(0) | BIT(1));

KUNIT_EXPECT_EQ(test, pm_runtime_put_sync_suspend(dev), 0);
KUNIT_EXPECT_EQ(test, dom->genpd.status, GENPD_STATE_OFF);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->bwr_enable), 0U);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->boost_enable), BIT(0));
}

/*
* A pipe with both enables set at boot starts on. One with only its boost
* enable set starts off, and a power on that fails on the BWR enable keeps
* that boost enable instead of clearing it.
*/
static void apple_pmc_test_interface_boot_state(struct kunit *test)
{
struct apple_pmc *pmc = apple_pmc_test_alloc(test);
const struct apple_pmc_hw *hw = &apple_pmc_hw_t8132;
struct apple_pmc_test_stuck stuck = { .offset = hw->bwr_enable };
struct apple_pmc_domain *dom;

pmc->hw = hw;
pmc->saved_bwr = BIT(3);
pmc->saved_boost = BIT(0) | BIT(1);
dom = apple_pmc_test_interface_dom(test, pmc, "apple-pmc-test-on");
KUNIT_EXPECT_EQ(test, dom->genpd.status, GENPD_STATE_ON);

pmc->saved_bwr = 0;
writel(BIT(0) | BIT(1), pmc->rails + hw->boost_enable);
dom = apple_pmc_test_interface_dom(test, pmc, "apple-pmc-test-mixed");
KUNIT_EXPECT_EQ(test, dom->genpd.status, GENPD_STATE_OFF);

test->priv = &stuck;
kunit_activate_static_stub(test, apple_pmc_interface_read,
apple_pmc_test_stuck_read);
KUNIT_EXPECT_EQ(test, apple_pmc_domain_power_on(&dom->genpd), -EIO);
KUNIT_EXPECT_EQ(test, apple_pmc_interface_read(pmc, hw->boost_enable),
BIT(0) | BIT(1));
kunit_deactivate_static_stub(test, apple_pmc_interface_read);
test->priv = NULL;

KUNIT_EXPECT_EQ(test, apple_pmc_domain_power_on(&dom->genpd), 0);
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->bwr_enable), BIT(3));
KUNIT_EXPECT_EQ(test, readl(pmc->rails + hw->boost_enable), BIT(0) | BIT(1));
}

static struct kunit_case apple_pmc_test_cases[] = {
KUNIT_CASE(apple_pmc_test_dcs_floor),
KUNIT_CASE(apple_pmc_test_partial_enable),
Expand All @@ -291,6 +495,10 @@ static struct kunit_case apple_pmc_test_cases[] = {
KUNIT_CASE(apple_pmc_test_dcs_limit_restores_ap_vote),
KUNIT_CASE(apple_pmc_test_floor_aggregate),
KUNIT_CASE(apple_pmc_test_floor_pending_timeout),
KUNIT_CASE(apple_pmc_test_interface),
KUNIT_CASE(apple_pmc_test_interface_stuck),
KUNIT_CASE(apple_pmc_test_interface_genpd),
KUNIT_CASE(apple_pmc_test_interface_boot_state),
{}
};

Expand Down
Loading