diff --git a/Documentation/devicetree/bindings/power/apple,pmc.yaml b/Documentation/devicetree/bindings/power/apple,pmc.yaml index 4c1d9aefc2519..a3728ed3effee 100644 --- a/Documentation/devicetree/bindings/power/apple,pmc.yaml +++ b/Documentation/devicetree/bindings/power/apple,pmc.yaml @@ -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: | @@ -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. @@ -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: @@ -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>; + }; + }; + }; diff --git a/arch/arm64/boot/dts/apple/t8132-j713.dts b/arch/arm64/boot/dts/apple/t8132-j713.dts index f84c4ef92aac1..d72442c6797f0 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713.dts +++ b/arch/arm64/boot/dts/apple/t8132-j713.dts @@ -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"; diff --git a/arch/arm64/boot/dts/apple/t8132-memory.dtsi b/arch/arm64/boot/dts/apple/t8132-memory.dtsi index fea5b3a97dbf5..c6cafa8219d2a 100644 --- a/arch/arm64/boot/dts/apple/t8132-memory.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-memory.dtsi @@ -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>; diff --git a/drivers/pmdomain/apple/Kconfig b/drivers/pmdomain/apple/Kconfig index c177665e17a71..52fe2669e6fe8 100644 --- a/drivers/pmdomain/apple/Kconfig +++ b/drivers/pmdomain/apple/Kconfig @@ -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 diff --git a/drivers/pmdomain/apple/pmc-test.c b/drivers/pmdomain/apple/pmc-test.c index 7cf8a43e8b085..d563a178dc0e8 100644 --- a/drivers/pmdomain/apple/pmc-test.c +++ b/drivers/pmdomain/apple/pmc-test.c @@ -8,6 +8,7 @@ #include #include +#include #define APPLE_PMC_TEST_RAILS_SIZE 0x4000 @@ -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), @@ -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), {} }; diff --git a/drivers/pmdomain/apple/pmc.c b/drivers/pmdomain/apple/pmc.c index 6b1e4c472e399..b6b5ccdb3e53e 100644 --- a/drivers/pmdomain/apple/pmc.c +++ b/drivers/pmdomain/apple/pmc.c @@ -23,6 +23,15 @@ * at its highest state to encode at full speed; macOS raises the same rail * for every encode session. Floors on one rail combine to the highest. * + * On T8132 a display pipe votes through two more interfaces: bandwidth + * requests (BWR) and boost, which raises the display clock. Each has an + * enable word with one bit per agent, and the PMC ignores the votes of an + * agent whose bit is clear: the external pipes' display clock then stays at + * its lowest state, too slow for modes such as 4K at 120 Hz. macOS sets a + * pipe's two bits when its PMGR power state powers on, and clears them + * before it powers off. The driver models them as a power domain too, that + * sits between that power state and the ones that depend on it. + * * Under thermal pressure the driver can also limit the DRAM controller * (DCS) rail: it stops honouring the other agents on that rail and votes * on their behalf through the AP's own agent, capped. This bounds the heat @@ -75,6 +84,10 @@ * @agent_rails: rails on which an agent is enabled; 0 for every rail * @ap_agent: voter agent of the application processor, which a DCS limit * votes through + * @bwr_enable: offset of the bandwidth request (BWR) interface enable word + * in the "rails" region; 0 if the PMC has none + * @boost_enable: offset of the boost interface enable word in the "rails" + * region; 0 if the PMC has none */ struct apple_pmc_hw { u32 agent_stride; @@ -87,6 +100,8 @@ struct apple_pmc_hw { u32 agent_rails; u32 ap_agent; bool has_pending; + u32 bwr_enable; + u32 boost_enable; }; struct apple_pmc { @@ -107,6 +122,11 @@ struct apple_pmc { u32 static_agents; /* Rails with a floor domain. */ u32 floor_rails; + /* BWR and boost interface enables at probe, and the bits of domains. */ + u32 saved_bwr; + u32 saved_boost; + u32 bwr_bits; + u32 boost_bits; struct list_head domains; struct dentry *debugfs; /* Cooling state of the DCS limit: 0 is none, dcs_max holds DCS at 0. */ @@ -129,6 +149,9 @@ struct apple_pmc { * @on: with @floor, whether the floor is held; protected by pmc->lock * @cluster: the domain is a cluster enable bit in the "clusters" region * @bit: with @cluster, the bit in the control and status words + * @interface: the domain is a display pipe's BWR and boost interface enables + * @bwr_bit: with @interface, the bit in the BWR interface enable word + * @boost_bit: with @interface, the bit in the boost interface enable word */ struct apple_pmc_domain { struct generic_pm_domain genpd; @@ -142,6 +165,9 @@ struct apple_pmc_domain { bool on; bool cluster; unsigned int bit; + bool interface; + unsigned int bwr_bit; + unsigned int boost_bit; bool provider; }; @@ -678,10 +704,74 @@ static int apple_pmc_cluster_set(struct apple_pmc_domain *dom, bool on) return ret; } +static u32 apple_pmc_interface_read(struct apple_pmc *pmc, u32 offset) +{ +#if IS_ENABLED(CONFIG_APPLE_PMC_KUNIT_TEST) + KUNIT_STATIC_STUB_REDIRECT(apple_pmc_interface_read, pmc, offset); +#endif + return readl(pmc->rails + offset); +} + +/* + * Caller holds pmc->lock. Set or clear @bit in the interface enable word at + * @offset, and read the word back. + */ +static int apple_pmc_interface_update(struct apple_pmc *pmc, u32 offset, + unsigned int bit, bool on) +{ + u32 old = apple_pmc_interface_read(pmc, offset); + u32 val = on ? old | BIT(bit) : old & ~BIT(bit); + + if (val == old) + return 0; + + writel(val, pmc->rails + offset); + if ((apple_pmc_interface_read(pmc, offset) ^ val) & BIT(bit)) { + dev_err_ratelimited(pmc->dev, + "interface enable %#x bit %u did not turn %s\n", + offset, bit, str_on_off(on)); + return -EIO; + } + + return 0; +} + +/* + * Set or clear the boost and BWR enables of a display pipe. If the BWR + * enable does not take the change, put the boost enable back to what it + * was: genpd keeps the domain in its old state when a callback fails, and + * a boost enable that was already set, such as one the boot loader left + * for a running pipe, must not be lost to a failed power on. Called with + * IRQs off for an IRQ-safe domain; everything here is atomic. + */ +static int apple_pmc_interface_set(struct apple_pmc_domain *dom, bool on) +{ + struct apple_pmc *pmc = dom->pmc; + const struct apple_pmc_hw *hw = pmc->hw; + unsigned long flags; + bool boost; + int ret; + + raw_spin_lock_irqsave(&pmc->lock, flags); + boost = apple_pmc_interface_read(pmc, hw->boost_enable) & BIT(dom->boost_bit); + ret = apple_pmc_interface_update(pmc, hw->boost_enable, dom->boost_bit, on); + if (!ret) { + ret = apple_pmc_interface_update(pmc, hw->bwr_enable, dom->bwr_bit, on); + if (ret) + apple_pmc_interface_update(pmc, hw->boost_enable, + dom->boost_bit, boost); + } + raw_spin_unlock_irqrestore(&pmc->lock, flags); + + return ret; +} + static int apple_pmc_domain_power_on(struct generic_pm_domain *genpd) { struct apple_pmc_domain *dom = genpd_to_apple_pmc_domain(genpd); + if (dom->interface) + return apple_pmc_interface_set(dom, true); if (dom->cluster) return apple_pmc_cluster_set(dom, true); if (dom->floor) @@ -693,6 +783,8 @@ static int apple_pmc_domain_power_off(struct generic_pm_domain *genpd) { struct apple_pmc_domain *dom = genpd_to_apple_pmc_domain(genpd); + if (dom->interface) + return apple_pmc_interface_set(dom, false); if (dom->cluster) return apple_pmc_cluster_set(dom, false); if (dom->floor) @@ -857,6 +949,100 @@ static int apple_pmc_cluster_add(struct apple_pmc *pmc, struct device_node *np) return 0; } +/* + * Initialize the genpd of an interface domain, which starts from the + * hardware state: on if both of its bits are set, so that the enables of a + * pipe the boot loader left running stay set. With only one bit set it + * starts off, and the driver leaves that bit alone until the domain powers + * on, which sets the other one. A failed power on keeps the bit too, so no + * vote the boot loader left is dropped before the pipe powers off. + * + * The domain is IRQ safe, since the PMGR power states that depend on it + * are, and genpd only takes an IRQ-safe subdomain under an IRQ-safe parent. + */ +static int apple_pmc_interface_init(struct apple_pmc *pmc, + struct apple_pmc_domain *dom) +{ + bool on = (pmc->saved_bwr & BIT(dom->bwr_bit)) && + (pmc->saved_boost & BIT(dom->boost_bit)); + + dom->genpd.flags |= GENPD_FLAG_IRQ_SAFE; + dom->genpd.power_on = apple_pmc_domain_power_on; + dom->genpd.power_off = apple_pmc_domain_power_off; + + return pm_genpd_init(&dom->genpd, NULL, !on); +} + +/* A display pipe's BWR and boost interface enables, set while powered. */ +static int apple_pmc_interface_add(struct apple_pmc *pmc, struct device_node *np) +{ + const struct apple_pmc_hw *hw = pmc->hw; + struct device *dev = pmc->dev; + struct apple_pmc_domain *dom; + struct of_phandle_args parent; + const char *label; + u32 bwr, boost; + int i, ret; + + if (!hw->bwr_enable || !hw->boost_enable) + return dev_err_probe(dev, -EINVAL, + "%pOF: no interface enables on this PMC\n", np); + if (of_property_read_u32(np, "apple,bwr-bit", &bwr) || bwr >= 32 || + of_property_read_u32(np, "apple,boost-bit", &boost) || boost >= 32) + return dev_err_probe(dev, -EINVAL, "%pOF: invalid interface bits\n", np); + if ((pmc->bwr_bits & BIT(bwr)) || (pmc->boost_bits & BIT(boost))) + return dev_err_probe(dev, -EINVAL, "%pOF: interface bits in use\n", np); + + dom = kzalloc_obj(*dom); + if (!dom) + return -ENOMEM; + + dom->pmc = pmc; + dom->np = of_node_get(np); + dom->interface = true; + dom->bwr_bit = bwr; + dom->boost_bit = boost; + if (of_property_read_string(np, "label", &label)) + label = np->name; + dom->genpd.name = kasprintf(GFP_KERNEL, "pmc-%s", label); + if (!dom->genpd.name) { + of_node_put(dom->np); + kfree(dom); + return -ENOMEM; + } + + ret = apple_pmc_interface_init(pmc, dom); + if (ret) { + of_node_put(dom->np); + kfree(dom->genpd.name); + kfree(dom); + return dev_err_probe(dev, ret, "%pOF: genpd init failed\n", np); + } + list_add_tail(&dom->list, &pmc->domains); + pmc->bwr_bits |= BIT(bwr); + pmc->boost_bits |= BIT(boost); + + ret = of_genpd_add_provider_simple(np, &dom->genpd); + if (ret) + return dev_err_probe(dev, ret, "%pOF: provider failed\n", np); + dom->provider = true; + + for (i = 0; !of_parse_phandle_with_args(np, "power-domains", + "#power-domain-cells", i, + &parent); i++) { + struct of_phandle_args child = { .np = np }; + + ret = of_genpd_add_subdomain(&parent, &child); + of_node_put(parent.np); + if (ret) + return dev_err_probe(dev, ret, + "%pOF: failed to join parent domain\n", + np); + } + + return 0; +} + /* A rail floor held through the AP's agent while the domain is powered. */ static int apple_pmc_floor_add(struct apple_pmc *pmc, struct device_node *np) { @@ -936,6 +1122,14 @@ static int apple_pmc_status_show(struct seq_file *s, void *unused) i, apple_pmc_enable_read(pmc, i), pmc->saved_enable[i]); seq_printf(s, "dcs limit: state %lu of %u\n", READ_ONCE(pmc->dcs_state), hw->dcs_max); + if (hw->bwr_enable && hw->boost_enable) { + seq_printf(s, "bwr enable: %#010x (saved %#010x)\n", + apple_pmc_interface_read(pmc, hw->bwr_enable), + pmc->saved_bwr); + seq_printf(s, "boost enable: %#010x (saved %#010x)\n", + apple_pmc_interface_read(pmc, hw->boost_enable), + pmc->saved_boost); + } list_for_each_entry(dom, &pmc->domains, list) if (dom->floor) seq_printf(s, "%s: rail%u level %u %s\n", dom->genpd.name, @@ -970,6 +1164,16 @@ static void apple_pmc_release(void *data) for (rail = 0; rail < pmc->hw->rails; rail++) writel(pmc->saved_enable[rail], pmc->rails + pmc->hw->rail_enable + 4 * rail); + /* Nor does an interface enable: give the domains' bits back. */ + if (pmc->bwr_bits || pmc->boost_bits) { + void __iomem *bwr = pmc->rails + pmc->hw->bwr_enable; + void __iomem *boost = pmc->rails + pmc->hw->boost_enable; + + writel((readl(bwr) & ~pmc->bwr_bits) | + (pmc->saved_bwr & pmc->bwr_bits), bwr); + writel((readl(boost) & ~pmc->boost_bits) | + (pmc->saved_boost & pmc->boost_bits), boost); + } pmc->dcs_state = 0; apple_pmc_dcs_vote(pmc); raw_spin_unlock_irqrestore(&pmc->lock, flags); @@ -1037,6 +1241,10 @@ static int apple_pmc_probe(struct platform_device *pdev) "PMC is not responding\n"); pmc->enable[rail] = pmc->saved_enable[rail]; } + if (pmc->hw->bwr_enable && pmc->hw->boost_enable) { + pmc->saved_bwr = apple_pmc_interface_read(pmc, pmc->hw->bwr_enable); + pmc->saved_boost = apple_pmc_interface_read(pmc, pmc->hw->boost_enable); + } pmc->ap_dcs_vote = apple_pmc_ap_dcs_read(pmc); count = of_property_count_u32_elems(np, "apple,static-voters"); @@ -1078,6 +1286,9 @@ static int apple_pmc_probe(struct platform_device *pdev) ret = apple_pmc_floor_add(pmc, child); else if (of_property_present(child, "apple,cluster-bit")) ret = apple_pmc_cluster_add(pmc, child); + else if (of_property_present(child, "apple,bwr-bit") || + of_property_present(child, "apple,boost-bit")) + ret = apple_pmc_interface_add(pmc, child); else ret = apple_pmc_domain_add(pmc, child); if (ret) { @@ -1111,6 +1322,8 @@ static const struct apple_pmc_hw apple_pmc_hw_t8132 = { .dcs_max = 5, .ap_agent = 3, .has_pending = true, + .bwr_enable = 0x2020, + .boost_enable = 0x2028, }; static const struct apple_pmc_hw apple_pmc_hw_t8140 = { diff --git a/drivers/soc/apple/t8132-platform-data.dts b/drivers/soc/apple/t8132-platform-data.dts index 3dd507182ecb4..52d97f73b81b3 100644 --- a/drivers/soc/apple/t8132-platform-data.dts +++ b/drivers/soc/apple/t8132-platform-data.dts @@ -37,6 +37,13 @@ apple,static-voters = <3>; }; +/* + * The PMC interface domains' parents are PMGR states, which a copied phandle + * cannot reach in the loader's tree, and no loader PMGR state uses them. + */ +/delete-node/ &pmc_dispext0; +/delete-node/ &pmc_dispext1; + &ps_gfx { status = "okay"; };