diff mbox series

[3/7] soc: apple: Add driver for Apple PMGR power state controls

Message ID 20211005155923.173399-4-marcan@marcan.st (mailing list archive)
State Not Applicable
Headers show
Series Apple SoC PMGR device power states driver | expand

Commit Message

Hector Martin Oct. 5, 2021, 3:59 p.m. UTC
Implements genpd and reset providers for downstream devices. Each
instance of the driver binds to a single register and represents a
single SoC power domain.

The driver does not currently implement all features (auto-pm,
clockgate-only state), but we declare the respective registers for
documentation purposes. These features will be added as they become
useful for downstream devices.

This also creates the apple/soc tree and Kconfig submenu.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 MAINTAINERS                             |   1 +
 drivers/soc/Kconfig                     |   1 +
 drivers/soc/Makefile                    |   1 +
 drivers/soc/apple/Kconfig               |  21 ++
 drivers/soc/apple/Makefile              |   2 +
 drivers/soc/apple/apple-pmgr-pwrstate.c | 281 ++++++++++++++++++++++++
 6 files changed, 307 insertions(+)
 create mode 100644 drivers/soc/apple/Kconfig
 create mode 100644 drivers/soc/apple/Makefile
 create mode 100644 drivers/soc/apple/apple-pmgr-pwrstate.c

Comments

Hector Martin Oct. 5, 2021, 4:15 p.m. UTC | #1
Hi Linus,

On 06/10/2021 01.08, Linus Walleij wrote:
> Hi Hector,
> 
> On Tue, Oct 5, 2021 at 6:00 PM Hector Martin <marcan@marcan.st> wrote:
> 
>>   drivers/soc/apple/Kconfig               |  21 ++
>>   drivers/soc/apple/Makefile              |   2 +
>>   drivers/soc/apple/apple-pmgr-pwrstate.c | 281 ++++++++++++++++++++++++
> 
> This is traditionally where we put the ARM SoC drivers, but
> Mac has traditionally used drivers/macintosh for their custom
> board etc stuff. Or is that just for any off-chip stuff?
> 
> I suppose it doesn't matter much (unless there is code under
> drivers/macintosh we want to reuse for M1), but it could be a bit
> confusing?

Hmm, it seems that tree is mostly about the PowerPC era Macs; the only 
thing enabled for x86 there is MAC_EMUMOUSEBTN. There is also 
platform/x86/apple-gmux.c for an x86 Mac specific thing...

We already broke tradition with the "apple," DT compatible prefix (used 
to be AAPL for the PowerPC Macs), and these chips aren't even just used 
in Macs (e.g. the iPad, which in theory people would be able to run 
Linux on if someone figures out a jailbreak), so perhaps it's time for 
another break here?
Linus Walleij Oct. 5, 2021, 7:49 p.m. UTC | #2
On Tue, Oct 5, 2021 at 6:15 PM Hector Martin <marcan@marcan.st> wrote:

> We already broke tradition with the "apple," DT compatible prefix (used
> to be AAPL for the PowerPC Macs), and these chips aren't even just used
> in Macs (e.g. the iPad, which in theory people would be able to run
> Linux on if someone figures out a jailbreak), so perhaps it's time for
> another break here?

Yeah fair enough. It's probably more intuitive under drivers/soc anyway.
Acked-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij
Mark Kettenis Oct. 5, 2021, 8:21 p.m. UTC | #3
> From: Hector Martin <marcan@marcan.st>
> Date: Wed,  6 Oct 2021 00:59:19 +0900
> 
> Implements genpd and reset providers for downstream devices. Each
> instance of the driver binds to a single register and represents a
> single SoC power domain.
> 
> The driver does not currently implement all features (auto-pm,
> clockgate-only state), but we declare the respective registers for
> documentation purposes. These features will be added as they become
> useful for downstream devices.
> 
> This also creates the apple/soc tree and Kconfig submenu.
> 
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
>  MAINTAINERS                             |   1 +
>  drivers/soc/Kconfig                     |   1 +
>  drivers/soc/Makefile                    |   1 +
>  drivers/soc/apple/Kconfig               |  21 ++
>  drivers/soc/apple/Makefile              |   2 +
>  drivers/soc/apple/apple-pmgr-pwrstate.c | 281 ++++++++++++++++++++++++
>  6 files changed, 307 insertions(+)
>  create mode 100644 drivers/soc/apple/Kconfig
>  create mode 100644 drivers/soc/apple/Makefile
>  create mode 100644 drivers/soc/apple/apple-pmgr-pwrstate.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5fe53d9a2956..def5e05da2bc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1725,6 +1725,7 @@ F:	Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
>  F:	Documentation/devicetree/bindings/power/apple*
>  F:	arch/arm64/boot/dts/apple/
>  F:	drivers/irqchip/irq-apple-aic.c
> +F:	drivers/soc/apple/*
>  F:	include/dt-bindings/interrupt-controller/apple-aic.h
>  F:	include/dt-bindings/pinctrl/apple.h
>  
> diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
> index e8a30c4c5aec..a8562678c437 100644
> --- a/drivers/soc/Kconfig
> +++ b/drivers/soc/Kconfig
> @@ -3,6 +3,7 @@ menu "SOC (System On Chip) specific Drivers"
>  
>  source "drivers/soc/actions/Kconfig"
>  source "drivers/soc/amlogic/Kconfig"
> +source "drivers/soc/apple/Kconfig"
>  source "drivers/soc/aspeed/Kconfig"
>  source "drivers/soc/atmel/Kconfig"
>  source "drivers/soc/bcm/Kconfig"
> diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
> index a05e9fbcd3e0..adb30c2d4fea 100644
> --- a/drivers/soc/Makefile
> +++ b/drivers/soc/Makefile
> @@ -4,6 +4,7 @@
>  #
>  
>  obj-$(CONFIG_ARCH_ACTIONS)	+= actions/
> +obj-$(CONFIG_ARCH_APPLE)	+= apple/
>  obj-y				+= aspeed/
>  obj-$(CONFIG_ARCH_AT91)		+= atmel/
>  obj-y				+= bcm/
> diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
> new file mode 100644
> index 000000000000..271092b6aee7
> --- /dev/null
> +++ b/drivers/soc/apple/Kconfig
> @@ -0,0 +1,21 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +if ARCH_APPLE || COMPILE_TEST
> +
> +menu "Apple SoC drivers"
> +
> +config APPLE_PMGR_PWRSTATE
> +	tristate "Apple SoC PMGR power state control"
> +	select REGMAP
> +	select MFD_SYSCON
> +	select PM_GENERIC_DOMAINS
> +	select RESET_CONTROLLER
> +	default ARCH_APPLE
> +	help
> +	  The PMGR block in Apple SoCs provides high-level power state
> +	  controls for SoC devices. This driver manages them through the
> +	  generic power domain framework, and also provides reset support.
> +
> +endmenu
> +
> +endif
> diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
> new file mode 100644
> index 000000000000..c114e84667e4
> --- /dev/null
> +++ b/drivers/soc/apple/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_APPLE_PMGR_PWRSTATE)	+= apple-pmgr-pwrstate.o
> diff --git a/drivers/soc/apple/apple-pmgr-pwrstate.c b/drivers/soc/apple/apple-pmgr-pwrstate.c
> new file mode 100644
> index 000000000000..a0338dbb29b8
> --- /dev/null
> +++ b/drivers/soc/apple/apple-pmgr-pwrstate.c
> @@ -0,0 +1,281 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Apple SoC PMGR device power state driver
> + *
> + * Copyright The Asahi Linux Contributors
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/bitfield.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/reset-controller.h>
> +#include <linux/module.h>
> +
> +#define APPLE_PMGR_RESET        BIT(31)
> +#define APPLE_PMGR_AUTO_ENABLE  BIT(28)
> +#define APPLE_PMGR_PS_AUTO      GENMASK(27, 24)
> +#define APPLE_PMGR_PARENT_OFF   BIT(11)
> +#define APPLE_PMGR_DEV_DISABLE  BIT(10)
> +#define APPLE_PMGR_WAS_CLKGATED BIT(9)
> +#define APPLE_PMGR_WAS_PWRGATED BIT(8)
> +#define APPLE_PMGR_PS_ACTUAL    GENMASK(7, 4)
> +#define APPLE_PMGR_PS_TARGET    GENMASK(3, 0)
> +
> +#define APPLE_PMGR_PS_ACTIVE    0xf
> +#define APPLE_PMGR_PS_CLKGATE   0x4
> +#define APPLE_PMGR_PS_PWRGATE   0x0
> +
> +#define APPLE_PMGR_PS_SET_TIMEOUT 100
> +#define APPLE_PMGR_RESET_TIME 1
> +
> +struct apple_pmgr_ps {
> +	struct device *dev;
> +	struct generic_pm_domain genpd;
> +	struct reset_controller_dev rcdev;
> +	struct regmap *regmap;
> +	u32 offset;
> +};
> +
> +#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_set(struct generic_pm_domain *genpd, u32 pstate)
> +{
> +	int ret;
> +	struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd);
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);
> +
> +	/* 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(ps->dev, "PS 0x%x: powering off with RESET active\n", ps->offset);
> +
> +	reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED |
> +		 APPLE_PMGR_PS_TARGET);
> +	reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: pwrstate = 0x%x: 0x%x\n", ps->offset, pstate, reg);
> +
> +	regmap_write(ps->regmap, ps->offset, reg);
> +
> +	ret = regmap_read_poll_timeout_atomic(
> +		ps->regmap, ps->offset, reg,
> +		(FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1,
> +		APPLE_PMGR_PS_SET_TIMEOUT);
> +	if (ret < 0)
> +		dev_err(ps->dev, "PS 0x%x: Failed to reach power state 0x%x (now: 0x%x)\n",
> +			ps->offset, pstate, reg);
> +	return ret;
> +}
> +
> +static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps)
> +{
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);
> +	return FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE;
> +}
> +
> +static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd)
> +{
> +	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE);
> +}
> +
> +static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd)
> +{
> +	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE);
> +}
> +
> +static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: asserting RESET while powered down\n", ps->offset);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset);
> +	/* Quiesce device before asserting reset */
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	int ret;
> +
> +	ret = apple_pmgr_reset_assert(rcdev, id);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME);
> +
> +	return apple_pmgr_reset_deassert(rcdev, id);
> +}
> +
> +static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);
> +
> +	return !!(reg & APPLE_PMGR_RESET);
> +}
> +
> +const struct reset_control_ops apple_pmgr_reset_ops = {
> +	.assert		= apple_pmgr_reset_assert,
> +	.deassert	= apple_pmgr_reset_deassert,
> +	.reset		= apple_pmgr_reset_reset,
> +	.status		= apple_pmgr_reset_status,
> +};
> +
> +static int apple_pmgr_reset_xlate(struct reset_controller_dev *rcdev,
> +				  const struct of_phandle_args *reset_spec)
> +{
> +	return 0;
> +}
> +
> +static int apple_pmgr_ps_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *node = dev->of_node;
> +	struct apple_pmgr_ps *ps;
> +	struct regmap *regmap;
> +	struct of_phandle_iterator it;
> +	int ret;
> +	const char *name;
> +
> +	regmap = syscon_node_to_regmap(node->parent);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	ps = devm_kzalloc(dev, sizeof(*ps), GFP_KERNEL);
> +	if (!ps)
> +		return -ENOMEM;
> +
> +	ps->dev = dev;
> +	ps->regmap = regmap;
> +
> +	ret = of_property_read_string(node, "apple,domain-name", &name);
> +	if (ret < 0) {
> +		dev_err(dev, "missing apple,domain-name property\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "reg", &ps->offset);
> +	if (ret < 0) {
> +		dev_err(dev, "missing reg property\n");
> +		return ret;
> +	}
> +
> +	if (of_property_read_bool(node, "apple,always-on"))
> +		ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
> +
> +	ps->genpd.name = name;
> +	ps->genpd.power_on = apple_pmgr_ps_power_on;
> +	ps->genpd.power_off = apple_pmgr_ps_power_off;
> +
> +	ret = pm_genpd_init(&ps->genpd, NULL, !apple_pmgr_ps_is_active(ps));
> +	if (ret < 0) {
> +		dev_err(dev, "pm_genpd_init failed\n");
> +		return ret;
> +	}
> +
> +	ret = of_genpd_add_provider_simple(node, &ps->genpd);
> +	if (ret < 0) {
> +		dev_err(dev, "of_genpd_add_provider_simple failed\n");
> +		return ret;
> +	}
> +
> +	of_for_each_phandle(&it, ret, node, "power-domains", "#power-domain-cells", -1) {
> +		struct of_phandle_args parent, child;
> +
> +		parent.np = it.node;
> +		parent.args_count = of_phandle_iterator_args(&it, parent.args, MAX_PHANDLE_ARGS);
> +		child.np = node;
> +		child.args_count = 0;
> +		ret = of_genpd_add_subdomain(&parent, &child);
> +
> +		if (ret == -EPROBE_DEFER) {
> +			of_node_put(parent.np);
> +			goto err_remove;
> +		} else if (ret < 0) {
> +			dev_err(dev, "failed to add to parent domain: %d (%s -> %s)\n",
> +				ret, it.node->name, node->name);
> +			of_node_put(parent.np);
> +			goto err_remove;
> +		}
> +	}
> +
> +	pm_genpd_remove_device(dev);
> +
> +	ps->rcdev.owner = THIS_MODULE;
> +	ps->rcdev.nr_resets = 1;
> +	ps->rcdev.ops = &apple_pmgr_reset_ops;
> +	ps->rcdev.of_node = dev->of_node;
> +	ps->rcdev.of_reset_n_cells = 0;
> +	ps->rcdev.of_xlate = apple_pmgr_reset_xlate;
> +
> +	ret = devm_reset_controller_register(dev, &ps->rcdev);
> +	if (ret < 0)
> +		goto err_remove;
> +
> +	return 0;
> +err_remove:
> +	of_genpd_del_provider(node);
> +	pm_genpd_remove(&ps->genpd);
> +	return ret;
> +}
> +
> +static const struct of_device_id apple_pmgr_ps_of_match[] = {
> +	{ .compatible = "apple,t8103-pmgr-pwrstate" },
> +	{ .compatible = "apple,pmgr-pwrstate" },
> +	{}
> +};

I think this only needs to list "apple,pmgr-pwrstate" as that is the
one that will be present on all SoCs that is backward compatible with
the t8103 (M1) SoC.

> +
> +MODULE_DEVICE_TABLE(of, apple_pmgr_ps_of_match);
> +
> +static struct platform_driver apple_pmgr_ps_driver = {
> +	.probe = apple_pmgr_ps_probe,
> +	.driver = {
> +		.name = "apple-pmgr-pwrstate",
> +		.of_match_table = apple_pmgr_ps_of_match,
> +	},
> +};
> +
> +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
> +MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs");
> +MODULE_LICENSE("GPL v2");
> +
> +module_platform_driver(apple_pmgr_ps_driver);
> -- 
> 2.33.0
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Krzysztof Kozlowski Oct. 6, 2021, 7:28 a.m. UTC | #4
On 05/10/2021 17:59, Hector Martin wrote:
> Implements genpd and reset providers for downstream devices. Each
> instance of the driver binds to a single register and represents a
> single SoC power domain.
> 
> The driver does not currently implement all features (auto-pm,
> clockgate-only state), but we declare the respective registers for
> documentation purposes. These features will be added as they become
> useful for downstream devices.
> 
> This also creates the apple/soc tree and Kconfig submenu.
> 
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
>  MAINTAINERS                             |   1 +
>  drivers/soc/Kconfig                     |   1 +
>  drivers/soc/Makefile                    |   1 +
>  drivers/soc/apple/Kconfig               |  21 ++
>  drivers/soc/apple/Makefile              |   2 +
>  drivers/soc/apple/apple-pmgr-pwrstate.c | 281 ++++++++++++++++++++++++
>  6 files changed, 307 insertions(+)
>  create mode 100644 drivers/soc/apple/Kconfig
>  create mode 100644 drivers/soc/apple/Makefile
>  create mode 100644 drivers/soc/apple/apple-pmgr-pwrstate.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5fe53d9a2956..def5e05da2bc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1725,6 +1725,7 @@ F:	Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
>  F:	Documentation/devicetree/bindings/power/apple*
>  F:	arch/arm64/boot/dts/apple/
>  F:	drivers/irqchip/irq-apple-aic.c
> +F:	drivers/soc/apple/*
>  F:	include/dt-bindings/interrupt-controller/apple-aic.h
>  F:	include/dt-bindings/pinctrl/apple.h
>  
> diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
> index e8a30c4c5aec..a8562678c437 100644
> --- a/drivers/soc/Kconfig
> +++ b/drivers/soc/Kconfig
> @@ -3,6 +3,7 @@ menu "SOC (System On Chip) specific Drivers"
>  
>  source "drivers/soc/actions/Kconfig"
>  source "drivers/soc/amlogic/Kconfig"
> +source "drivers/soc/apple/Kconfig"
>  source "drivers/soc/aspeed/Kconfig"
>  source "drivers/soc/atmel/Kconfig"
>  source "drivers/soc/bcm/Kconfig"
> diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
> index a05e9fbcd3e0..adb30c2d4fea 100644
> --- a/drivers/soc/Makefile
> +++ b/drivers/soc/Makefile
> @@ -4,6 +4,7 @@
>  #
>  
>  obj-$(CONFIG_ARCH_ACTIONS)	+= actions/
> +obj-$(CONFIG_ARCH_APPLE)	+= apple/
>  obj-y				+= aspeed/
>  obj-$(CONFIG_ARCH_AT91)		+= atmel/
>  obj-y				+= bcm/
> diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
> new file mode 100644
> index 000000000000..271092b6aee7
> --- /dev/null
> +++ b/drivers/soc/apple/Kconfig
> @@ -0,0 +1,21 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +if ARCH_APPLE || COMPILE_TEST
> +
> +menu "Apple SoC drivers"
> +
> +config APPLE_PMGR_PWRSTATE
> +	tristate "Apple SoC PMGR power state control"
> +	select REGMAP
> +	select MFD_SYSCON
> +	select PM_GENERIC_DOMAINS
> +	select RESET_CONTROLLER
> +	default ARCH_APPLE
> +	help
> +	  The PMGR block in Apple SoCs provides high-level power state
> +	  controls for SoC devices. This driver manages them through the
> +	  generic power domain framework, and also provides reset support.
> +
> +endmenu
> +
> +endif
> diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
> new file mode 100644
> index 000000000000..c114e84667e4
> --- /dev/null
> +++ b/drivers/soc/apple/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_APPLE_PMGR_PWRSTATE)	+= apple-pmgr-pwrstate.o
> diff --git a/drivers/soc/apple/apple-pmgr-pwrstate.c b/drivers/soc/apple/apple-pmgr-pwrstate.c
> new file mode 100644
> index 000000000000..a0338dbb29b8
> --- /dev/null
> +++ b/drivers/soc/apple/apple-pmgr-pwrstate.c
> @@ -0,0 +1,281 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Apple SoC PMGR device power state driver
> + *
> + * Copyright The Asahi Linux Contributors
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/bitfield.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/reset-controller.h>
> +#include <linux/module.h>
> +
> +#define APPLE_PMGR_RESET        BIT(31)
> +#define APPLE_PMGR_AUTO_ENABLE  BIT(28)
> +#define APPLE_PMGR_PS_AUTO      GENMASK(27, 24)
> +#define APPLE_PMGR_PARENT_OFF   BIT(11)
> +#define APPLE_PMGR_DEV_DISABLE  BIT(10)
> +#define APPLE_PMGR_WAS_CLKGATED BIT(9)
> +#define APPLE_PMGR_WAS_PWRGATED BIT(8)
> +#define APPLE_PMGR_PS_ACTUAL    GENMASK(7, 4)
> +#define APPLE_PMGR_PS_TARGET    GENMASK(3, 0)
> +
> +#define APPLE_PMGR_PS_ACTIVE    0xf
> +#define APPLE_PMGR_PS_CLKGATE   0x4
> +#define APPLE_PMGR_PS_PWRGATE   0x0
> +
> +#define APPLE_PMGR_PS_SET_TIMEOUT 100
> +#define APPLE_PMGR_RESET_TIME 1
> +
> +struct apple_pmgr_ps {
> +	struct device *dev;
> +	struct generic_pm_domain genpd;
> +	struct reset_controller_dev rcdev;
> +	struct regmap *regmap;
> +	u32 offset;
> +};
> +
> +#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_set(struct generic_pm_domain *genpd, u32 pstate)
> +{
> +	int ret;
> +	struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd);
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);

MMIO accesses should not fail, but regmap API could fail if for example
clk_enable() fails. In such case you will write below value based on
random stack init. Please check the return value here.

> +
> +	/* 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(ps->dev, "PS 0x%x: powering off with RESET active\n", ps->offset);
> +
> +	reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED |
> +		 APPLE_PMGR_PS_TARGET);
> +	reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: pwrstate = 0x%x: 0x%x\n", ps->offset, pstate, reg);
> +
> +	regmap_write(ps->regmap, ps->offset, reg);
> +
> +	ret = regmap_read_poll_timeout_atomic(
> +		ps->regmap, ps->offset, reg,
> +		(FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1,
> +		APPLE_PMGR_PS_SET_TIMEOUT);
> +	if (ret < 0)
> +		dev_err(ps->dev, "PS 0x%x: Failed to reach power state 0x%x (now: 0x%x)\n",
> +			ps->offset, pstate, reg);
> +	return ret;
> +}
> +
> +static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps)
> +{
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);

Check the return value or initialize reg to 0.

> +	return FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE;
> +}
> +
> +static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd)
> +{
> +	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE);
> +}
> +
> +static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd)
> +{
> +	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE);
> +}
> +
> +static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: asserting RESET while powered down\n", ps->offset);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset);
> +	/* Quiesce device before asserting reset */
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);

This looks wrong: it can be a spin-lock, not mutex, so you should use
genpd_lock.

However now I wonder if there could be a case when a reset-controller
consumer calls it from it's GENPD_NOTIFY_ON notifier? In such case you
would have this lock taken.

> +
> +	dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	int ret;
> +
> +	ret = apple_pmgr_reset_assert(rcdev, id);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME);
> +
> +	return apple_pmgr_reset_deassert(rcdev, id);
> +}
> +
> +static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +	u32 reg;
> +
> +	regmap_read(ps->regmap, ps->offset, &reg);
> +
> +	return !!(reg & APPLE_PMGR_RESET);
> +}
> +
> +const struct reset_control_ops apple_pmgr_reset_ops = {
> +	.assert		= apple_pmgr_reset_assert,
> +	.deassert	= apple_pmgr_reset_deassert,
> +	.reset		= apple_pmgr_reset_reset,
> +	.status		= apple_pmgr_reset_status,
> +};
> +
> +static int apple_pmgr_reset_xlate(struct reset_controller_dev *rcdev,
> +				  const struct of_phandle_args *reset_spec)
> +{
> +	return 0;
> +}
> +
> +static int apple_pmgr_ps_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *node = dev->of_node;
> +	struct apple_pmgr_ps *ps;
> +	struct regmap *regmap;
> +	struct of_phandle_iterator it;
> +	int ret;
> +	const char *name;
> +
> +	regmap = syscon_node_to_regmap(node->parent);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	ps = devm_kzalloc(dev, sizeof(*ps), GFP_KERNEL);
> +	if (!ps)
> +		return -ENOMEM;
> +
> +	ps->dev = dev;
> +	ps->regmap = regmap;
> +
> +	ret = of_property_read_string(node, "apple,domain-name", &name);
> +	if (ret < 0) {
> +		dev_err(dev, "missing apple,domain-name property\n");
> +		return ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "reg", &ps->offset);
> +	if (ret < 0) {
> +		dev_err(dev, "missing reg property\n");
> +		return ret;
> +	}
> +
> +	if (of_property_read_bool(node, "apple,always-on"))
> +		ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
> +
> +	ps->genpd.name = name;
> +	ps->genpd.power_on = apple_pmgr_ps_power_on;
> +	ps->genpd.power_off = apple_pmgr_ps_power_off;
> +
> +	ret = pm_genpd_init(&ps->genpd, NULL, !apple_pmgr_ps_is_active(ps));
> +	if (ret < 0) {
> +		dev_err(dev, "pm_genpd_init failed\n");
> +		return ret;
> +	}
> +
> +	ret = of_genpd_add_provider_simple(node, &ps->genpd);
> +	if (ret < 0) {
> +		dev_err(dev, "of_genpd_add_provider_simple failed\n");
> +		return ret;
> +	}
> +
> +	of_for_each_phandle(&it, ret, node, "power-domains", "#power-domain-cells", -1) {
> +		struct of_phandle_args parent, child;
> +
> +		parent.np = it.node;
> +		parent.args_count = of_phandle_iterator_args(&it, parent.args, MAX_PHANDLE_ARGS);
> +		child.np = node;
> +		child.args_count = 0;
> +		ret = of_genpd_add_subdomain(&parent, &child);
> +
> +		if (ret == -EPROBE_DEFER) {
> +			of_node_put(parent.np);
> +			goto err_remove;
> +		} else if (ret < 0) {
> +			dev_err(dev, "failed to add to parent domain: %d (%s -> %s)\n",
> +				ret, it.node->name, node->name);
> +			of_node_put(parent.np);
> +			goto err_remove;
> +		}
> +	}
> +
> +	pm_genpd_remove_device(dev);
> +
> +	ps->rcdev.owner = THIS_MODULE;
> +	ps->rcdev.nr_resets = 1;
> +	ps->rcdev.ops = &apple_pmgr_reset_ops;
> +	ps->rcdev.of_node = dev->of_node;
> +	ps->rcdev.of_reset_n_cells = 0;
> +	ps->rcdev.of_xlate = apple_pmgr_reset_xlate;
> +
> +	ret = devm_reset_controller_register(dev, &ps->rcdev);
> +	if (ret < 0)
> +		goto err_remove;
> +
> +	return 0;
> +err_remove:
> +	of_genpd_del_provider(node);
> +	pm_genpd_remove(&ps->genpd);
> +	return ret;
> +}
> +
> +static const struct of_device_id apple_pmgr_ps_of_match[] = {
> +	{ .compatible = "apple,t8103-pmgr-pwrstate" },
> +	{ .compatible = "apple,pmgr-pwrstate" },

You call the device/driver "pwrstate", which it seems is "power state".
These are not power states. These are power controllers or power
domains. Power state is rather a state of power domain - e.g. on or
gated. How about renaming it to power domain or pd?

> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(of, apple_pmgr_ps_of_match);
> +
> +static struct platform_driver apple_pmgr_ps_driver = {
> +	.probe = apple_pmgr_ps_probe,
> +	.driver = {
> +		.name = "apple-pmgr-pwrstate",
> +		.of_match_table = apple_pmgr_ps_of_match,
> +	},
> +};
> +
> +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
> +MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs");
> +MODULE_LICENSE("GPL v2");
> +
> +module_platform_driver(apple_pmgr_ps_driver);
> 


Best regards,
Krzysztof
Philipp Zabel Oct. 6, 2021, 9:24 a.m. UTC | #5
Hi Hector,

On Wed, 2021-10-06 at 00:59 +0900, Hector Martin wrote:
> Implements genpd and reset providers for downstream devices. Each
> instance of the driver binds to a single register and represents a
> single SoC power domain.
> 
> The driver does not currently implement all features (auto-pm,
> clockgate-only state), but we declare the respective registers for
> documentation purposes. These features will be added as they become
> useful for downstream devices.
> 
> This also creates the apple/soc tree and Kconfig submenu.
> 
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
>  MAINTAINERS                             |   1 +
>  drivers/soc/Kconfig                     |   1 +
>  drivers/soc/Makefile                    |   1 +
>  drivers/soc/apple/Kconfig               |  21 ++
>  drivers/soc/apple/Makefile              |   2 +
>  drivers/soc/apple/apple-pmgr-pwrstate.c | 281 ++++++++++++++++++++++++
>  6 files changed, 307 insertions(+)
>  create mode 100644 drivers/soc/apple/Kconfig
>  create mode 100644 drivers/soc/apple/Makefile
>  create mode 100644 drivers/soc/apple/apple-pmgr-pwrstate.c
> 
[...]
> diff --git a/drivers/soc/apple/apple-pmgr-pwrstate.c b/drivers/soc/apple/apple-pmgr-pwrstate.c
> new file mode 100644
> index 000000000000..a0338dbb29b8
> --- /dev/null
> +++ b/drivers/soc/apple/apple-pmgr-pwrstate.c
> @@ -0,0 +1,281 @@
[...]
> +static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: asserting RESET while powered down\n", ps->offset);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset);
> +	/* Quiesce device before asserting reset */
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
> +
> +	mutex_lock(&ps->genpd.mlock);
> +
> +	dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
> +	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
> +
> +	if (ps->genpd.status == GENPD_STATE_OFF)
> +		dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset);
> +
> +	mutex_unlock(&ps->genpd.mlock);
> +
> +	return 0;
> +}
> +
> +static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> +	int ret;
> +
> +	ret = apple_pmgr_reset_assert(rcdev, id);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME);

Is this delay known to be long enough for all consumers using the
reset_control_reset() functionality? Are there any users at all?

Is it ok for a genpd transition to happen during this sleep?

> +	return apple_pmgr_reset_deassert(rcdev, id);
> +}

regards
Philipp
Hector Martin Oct. 6, 2021, 4 p.m. UTC | #6
On 06/10/2021 05.21, Mark Kettenis wrote:
>> +static const struct of_device_id apple_pmgr_ps_of_match[] = {
>> +	{ .compatible = "apple,t8103-pmgr-pwrstate" },
>> +	{ .compatible = "apple,pmgr-pwrstate" },
>> +	{}
>> +};
> 
> I think this only needs to list "apple,pmgr-pwrstate" as that is the
> one that will be present on all SoCs that is backward compatible with
> the t8103 (M1) SoC.

Ah, yeah, we don't need to bind to t8103 unless we run into a quirk 
problem. I'll remove it. Thanks!
Hector Martin Oct. 6, 2021, 4:08 p.m. UTC | #7
On 06/10/2021 16.28, Krzysztof Kozlowski wrote:
>> +static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate)
>> +{
>> +	int ret;
>> +	struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd);
>> +	u32 reg;
>> +
>> +	regmap_read(ps->regmap, ps->offset, &reg);
> 
> MMIO accesses should not fail, but regmap API could fail if for example
> clk_enable() fails. In such case you will write below value based on
> random stack init. Please check the return value here.

Ack, will fix for v2 (as well as the related ones below).

>> +static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
>> +{
>> +	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
>> +
>> +	mutex_lock(&ps->genpd.mlock);
> 
> This looks wrong: it can be a spin-lock, not mutex, so you should use
> genpd_lock.

genpd_lock() is not part of the public API, which is why I did it like 
this. This gets decided by whether the GENPD_FLAG_IRQ_SAFE flag is set, 
so it should be a mutex in this case, as that is not set.

> However now I wonder if there could be a case when a reset-controller
> consumer calls it from it's GENPD_NOTIFY_ON notifier? In such case you
> would have this lock taken.

Hm, yeah, I wonder if we'll hit that use case. Probably not, though. I 
mostly expect our drivers to only reset devices on initial probe or in 
some kind of panic recovery scenario, not while doing PM stuff.

>> +static const struct of_device_id apple_pmgr_ps_of_match[] = {
>> +	{ .compatible = "apple,t8103-pmgr-pwrstate" },
>> +	{ .compatible = "apple,pmgr-pwrstate" },
> 
> You call the device/driver "pwrstate", which it seems is "power state".
> These are not power states. These are power controllers or power
> domains. Power state is rather a state of power domain - e.g. on or
> gated. How about renaming it to power domain or pd?

It's a bit confusing. Apple calls these registers "ps" registers, which 
presumably stands for "power state". They can both clockgate and 
powergate devices (where supported), as well as enable auto-PM and also 
handle reset. So they're a bit more complex and higher level than a 
simple power domain, which is why I called the driver "pwrstate", since 
it controls the power state of a specific SoC domain or block. In fact, 
the device PM is controlled via a 4-bit power state index, though right 
now only 0, 4, 15 are used (power gated, clock gated, active). Many 
devices will not support individual power gating and would just 
clockgate at 0, and right now the driver never uses 4, but might in the 
future. If that needs to be exposed to consumers, then it'd have to be 
via genpd idle states.
Hector Martin Oct. 6, 2021, 4:11 p.m. UTC | #8
On 06/10/2021 18.24, Philipp Zabel wrote:
>> +static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
>> +{
>> +	int ret;
>> +
>> +	ret = apple_pmgr_reset_assert(rcdev, id);
>> +	if (ret)
>> +		return ret;
>> +
>> +	usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME);
> 
> Is this delay known to be long enough for all consumers using the
> reset_control_reset() functionality? Are there any users at all?

I tested this for UART and ANS outside of Linux, and found that even 
back to back register writes worked, so the 1us thing is already 
conservative. I have no idea if we'll run into some weirdo block that 
needs more time, though. If we do then this will have to be bumped or 
turned into a property.

> Is it ok for a genpd transition to happen during this sleep?

I expect consumers to call reset while the device is active; it won't 
even work without that, as the reset is synchronous and just doesn't 
take effect while clock gated (at least for UART). See the dev_err()s 
that fire when that happens.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 5fe53d9a2956..def5e05da2bc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1725,6 +1725,7 @@  F:	Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
 F:	Documentation/devicetree/bindings/power/apple*
 F:	arch/arm64/boot/dts/apple/
 F:	drivers/irqchip/irq-apple-aic.c
+F:	drivers/soc/apple/*
 F:	include/dt-bindings/interrupt-controller/apple-aic.h
 F:	include/dt-bindings/pinctrl/apple.h
 
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index e8a30c4c5aec..a8562678c437 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -3,6 +3,7 @@  menu "SOC (System On Chip) specific Drivers"
 
 source "drivers/soc/actions/Kconfig"
 source "drivers/soc/amlogic/Kconfig"
+source "drivers/soc/apple/Kconfig"
 source "drivers/soc/aspeed/Kconfig"
 source "drivers/soc/atmel/Kconfig"
 source "drivers/soc/bcm/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index a05e9fbcd3e0..adb30c2d4fea 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -4,6 +4,7 @@ 
 #
 
 obj-$(CONFIG_ARCH_ACTIONS)	+= actions/
+obj-$(CONFIG_ARCH_APPLE)	+= apple/
 obj-y				+= aspeed/
 obj-$(CONFIG_ARCH_AT91)		+= atmel/
 obj-y				+= bcm/
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
new file mode 100644
index 000000000000..271092b6aee7
--- /dev/null
+++ b/drivers/soc/apple/Kconfig
@@ -0,0 +1,21 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+if ARCH_APPLE || COMPILE_TEST
+
+menu "Apple SoC drivers"
+
+config APPLE_PMGR_PWRSTATE
+	tristate "Apple SoC PMGR power state control"
+	select REGMAP
+	select MFD_SYSCON
+	select PM_GENERIC_DOMAINS
+	select RESET_CONTROLLER
+	default ARCH_APPLE
+	help
+	  The PMGR block in Apple SoCs provides high-level power state
+	  controls for SoC devices. This driver manages them through the
+	  generic power domain framework, and also provides reset support.
+
+endmenu
+
+endif
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
new file mode 100644
index 000000000000..c114e84667e4
--- /dev/null
+++ b/drivers/soc/apple/Makefile
@@ -0,0 +1,2 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_APPLE_PMGR_PWRSTATE)	+= apple-pmgr-pwrstate.o
diff --git a/drivers/soc/apple/apple-pmgr-pwrstate.c b/drivers/soc/apple/apple-pmgr-pwrstate.c
new file mode 100644
index 000000000000..a0338dbb29b8
--- /dev/null
+++ b/drivers/soc/apple/apple-pmgr-pwrstate.c
@@ -0,0 +1,281 @@ 
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SoC PMGR device power state driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/reset-controller.h>
+#include <linux/module.h>
+
+#define APPLE_PMGR_RESET        BIT(31)
+#define APPLE_PMGR_AUTO_ENABLE  BIT(28)
+#define APPLE_PMGR_PS_AUTO      GENMASK(27, 24)
+#define APPLE_PMGR_PARENT_OFF   BIT(11)
+#define APPLE_PMGR_DEV_DISABLE  BIT(10)
+#define APPLE_PMGR_WAS_CLKGATED BIT(9)
+#define APPLE_PMGR_WAS_PWRGATED BIT(8)
+#define APPLE_PMGR_PS_ACTUAL    GENMASK(7, 4)
+#define APPLE_PMGR_PS_TARGET    GENMASK(3, 0)
+
+#define APPLE_PMGR_PS_ACTIVE    0xf
+#define APPLE_PMGR_PS_CLKGATE   0x4
+#define APPLE_PMGR_PS_PWRGATE   0x0
+
+#define APPLE_PMGR_PS_SET_TIMEOUT 100
+#define APPLE_PMGR_RESET_TIME 1
+
+struct apple_pmgr_ps {
+	struct device *dev;
+	struct generic_pm_domain genpd;
+	struct reset_controller_dev rcdev;
+	struct regmap *regmap;
+	u32 offset;
+};
+
+#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_set(struct generic_pm_domain *genpd, u32 pstate)
+{
+	int ret;
+	struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd);
+	u32 reg;
+
+	regmap_read(ps->regmap, ps->offset, &reg);
+
+	/* 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(ps->dev, "PS 0x%x: powering off with RESET active\n", ps->offset);
+
+	reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED |
+		 APPLE_PMGR_PS_TARGET);
+	reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate);
+
+	dev_dbg(ps->dev, "PS 0x%x: pwrstate = 0x%x: 0x%x\n", ps->offset, pstate, reg);
+
+	regmap_write(ps->regmap, ps->offset, reg);
+
+	ret = regmap_read_poll_timeout_atomic(
+		ps->regmap, ps->offset, reg,
+		(FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1,
+		APPLE_PMGR_PS_SET_TIMEOUT);
+	if (ret < 0)
+		dev_err(ps->dev, "PS 0x%x: Failed to reach power state 0x%x (now: 0x%x)\n",
+			ps->offset, pstate, reg);
+	return ret;
+}
+
+static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps)
+{
+	u32 reg;
+
+	regmap_read(ps->regmap, ps->offset, &reg);
+	return FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE;
+}
+
+static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd)
+{
+	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE);
+}
+
+static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd)
+{
+	return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE);
+}
+
+static int apple_pmgr_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
+
+	mutex_lock(&ps->genpd.mlock);
+
+	if (ps->genpd.status == GENPD_STATE_OFF)
+		dev_err(ps->dev, "PS 0x%x: asserting RESET while powered down\n", ps->offset);
+
+	dev_dbg(ps->dev, "PS 0x%x: assert reset\n", ps->offset);
+	/* Quiesce device before asserting reset */
+	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
+	regmap_set_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
+
+	mutex_unlock(&ps->genpd.mlock);
+
+	return 0;
+}
+
+static int apple_pmgr_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
+
+	mutex_lock(&ps->genpd.mlock);
+
+	dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset);
+	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_RESET);
+	regmap_clear_bits(ps->regmap, ps->offset, APPLE_PMGR_DEV_DISABLE);
+
+	if (ps->genpd.status == GENPD_STATE_OFF)
+		dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset);
+
+	mutex_unlock(&ps->genpd.mlock);
+
+	return 0;
+}
+
+static int apple_pmgr_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
+{
+	int ret;
+
+	ret = apple_pmgr_reset_assert(rcdev, id);
+	if (ret)
+		return ret;
+
+	usleep_range(APPLE_PMGR_RESET_TIME, 2 * APPLE_PMGR_RESET_TIME);
+
+	return apple_pmgr_reset_deassert(rcdev, id);
+}
+
+static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned long id)
+{
+	struct apple_pmgr_ps *ps = rcdev_to_apple_pmgr_ps(rcdev);
+	u32 reg;
+
+	regmap_read(ps->regmap, ps->offset, &reg);
+
+	return !!(reg & APPLE_PMGR_RESET);
+}
+
+const struct reset_control_ops apple_pmgr_reset_ops = {
+	.assert		= apple_pmgr_reset_assert,
+	.deassert	= apple_pmgr_reset_deassert,
+	.reset		= apple_pmgr_reset_reset,
+	.status		= apple_pmgr_reset_status,
+};
+
+static int apple_pmgr_reset_xlate(struct reset_controller_dev *rcdev,
+				  const struct of_phandle_args *reset_spec)
+{
+	return 0;
+}
+
+static int apple_pmgr_ps_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct apple_pmgr_ps *ps;
+	struct regmap *regmap;
+	struct of_phandle_iterator it;
+	int ret;
+	const char *name;
+
+	regmap = syscon_node_to_regmap(node->parent);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	ps = devm_kzalloc(dev, sizeof(*ps), GFP_KERNEL);
+	if (!ps)
+		return -ENOMEM;
+
+	ps->dev = dev;
+	ps->regmap = regmap;
+
+	ret = of_property_read_string(node, "apple,domain-name", &name);
+	if (ret < 0) {
+		dev_err(dev, "missing apple,domain-name property\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(node, "reg", &ps->offset);
+	if (ret < 0) {
+		dev_err(dev, "missing reg property\n");
+		return ret;
+	}
+
+	if (of_property_read_bool(node, "apple,always-on"))
+		ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
+
+	ps->genpd.name = name;
+	ps->genpd.power_on = apple_pmgr_ps_power_on;
+	ps->genpd.power_off = apple_pmgr_ps_power_off;
+
+	ret = pm_genpd_init(&ps->genpd, NULL, !apple_pmgr_ps_is_active(ps));
+	if (ret < 0) {
+		dev_err(dev, "pm_genpd_init failed\n");
+		return ret;
+	}
+
+	ret = of_genpd_add_provider_simple(node, &ps->genpd);
+	if (ret < 0) {
+		dev_err(dev, "of_genpd_add_provider_simple failed\n");
+		return ret;
+	}
+
+	of_for_each_phandle(&it, ret, node, "power-domains", "#power-domain-cells", -1) {
+		struct of_phandle_args parent, child;
+
+		parent.np = it.node;
+		parent.args_count = of_phandle_iterator_args(&it, parent.args, MAX_PHANDLE_ARGS);
+		child.np = node;
+		child.args_count = 0;
+		ret = of_genpd_add_subdomain(&parent, &child);
+
+		if (ret == -EPROBE_DEFER) {
+			of_node_put(parent.np);
+			goto err_remove;
+		} else if (ret < 0) {
+			dev_err(dev, "failed to add to parent domain: %d (%s -> %s)\n",
+				ret, it.node->name, node->name);
+			of_node_put(parent.np);
+			goto err_remove;
+		}
+	}
+
+	pm_genpd_remove_device(dev);
+
+	ps->rcdev.owner = THIS_MODULE;
+	ps->rcdev.nr_resets = 1;
+	ps->rcdev.ops = &apple_pmgr_reset_ops;
+	ps->rcdev.of_node = dev->of_node;
+	ps->rcdev.of_reset_n_cells = 0;
+	ps->rcdev.of_xlate = apple_pmgr_reset_xlate;
+
+	ret = devm_reset_controller_register(dev, &ps->rcdev);
+	if (ret < 0)
+		goto err_remove;
+
+	return 0;
+err_remove:
+	of_genpd_del_provider(node);
+	pm_genpd_remove(&ps->genpd);
+	return ret;
+}
+
+static const struct of_device_id apple_pmgr_ps_of_match[] = {
+	{ .compatible = "apple,t8103-pmgr-pwrstate" },
+	{ .compatible = "apple,pmgr-pwrstate" },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, apple_pmgr_ps_of_match);
+
+static struct platform_driver apple_pmgr_ps_driver = {
+	.probe = apple_pmgr_ps_probe,
+	.driver = {
+		.name = "apple-pmgr-pwrstate",
+		.of_match_table = apple_pmgr_ps_of_match,
+	},
+};
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs");
+MODULE_LICENSE("GPL v2");
+
+module_platform_driver(apple_pmgr_ps_driver);