diff options
Diffstat (limited to 'drivers/pmdomain/apple/pmgr-pwrstate.c')
-rw-r--r-- | drivers/pmdomain/apple/pmgr-pwrstate.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c new file mode 100644 index 0000000000..d62a776c89 --- /dev/null +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -0,0 +1,326 @@ +// 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_PS_MIN GENMASK(19, 16) +#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_FLAGS (APPLE_PMGR_WAS_CLKGATED | APPLE_PMGR_WAS_PWRGATED) + +#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; + u32 min_state; +}; + +#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, bool auto_enable) +{ + int ret; + struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd); + u32 reg; + + ret = regmap_read(ps->regmap, ps->offset, ®); + if (ret < 0) + return ret; + + /* 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 %s: powering off with RESET active\n", + genpd->name); + + reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET); + reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate); + + dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, 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 %s: Failed to reach power state 0x%x (now: 0x%x)\n", + genpd->name, pstate, reg); + + if (auto_enable) { + /* Not all devices implement this; this is a no-op where not implemented. */ + reg &= ~APPLE_PMGR_FLAGS; + reg |= APPLE_PMGR_AUTO_ENABLE; + regmap_write(ps->regmap, ps->offset, reg); + } + + return ret; +} + +static bool apple_pmgr_ps_is_active(struct apple_pmgr_ps *ps) +{ + u32 reg = 0; + + regmap_read(ps->regmap, ps->offset, ®); + /* + * We consider domains as active if they are actually on, or if they have auto-PM + * enabled and the intended target is on. + */ + return (FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == APPLE_PMGR_PS_ACTIVE || + (FIELD_GET(APPLE_PMGR_PS_TARGET, reg) == APPLE_PMGR_PS_ACTIVE && + reg & APPLE_PMGR_AUTO_ENABLE)); +} + +static int apple_pmgr_ps_power_on(struct generic_pm_domain *genpd) +{ + return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_ACTIVE, true); +} + +static int apple_pmgr_ps_power_off(struct generic_pm_domain *genpd) +{ + return apple_pmgr_ps_set(genpd, APPLE_PMGR_PS_PWRGATE, false); +} + +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); + unsigned long flags; + + spin_lock_irqsave(&ps->genpd.slock, flags); + + 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_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, + APPLE_PMGR_DEV_DISABLE); + regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, + APPLE_PMGR_RESET); + + spin_unlock_irqrestore(&ps->genpd.slock, flags); + + 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); + unsigned long flags; + + spin_lock_irqsave(&ps->genpd.slock, flags); + + dev_dbg(ps->dev, "PS 0x%x: deassert reset\n", ps->offset); + regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_RESET, 0); + regmap_update_bits(ps->regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_DEV_DISABLE, 0); + + if (ps->genpd.status == GENPD_STATE_OFF) + dev_err(ps->dev, "PS 0x%x: RESET was deasserted while powered down\n", ps->offset); + + spin_unlock_irqrestore(&ps->genpd.slock, flags); + + 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 = 0; + + regmap_read(ps->regmap, ps->offset, ®); + + 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; + bool active; + + 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, "label", &name); + if (ret < 0) { + dev_err(dev, "missing label 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; + } + + ps->genpd.flags |= GENPD_FLAG_IRQ_SAFE; + ps->genpd.name = name; + ps->genpd.power_on = apple_pmgr_ps_power_on; + ps->genpd.power_off = apple_pmgr_ps_power_off; + + ret = of_property_read_u32(node, "apple,min-state", &ps->min_state); + if (ret == 0 && ps->min_state <= APPLE_PMGR_PS_ACTIVE) + regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN, + FIELD_PREP(APPLE_PMGR_PS_MIN, ps->min_state)); + + active = apple_pmgr_ps_is_active(ps); + if (of_property_read_bool(node, "apple,always-on")) { + ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON; + if (!active) { + dev_warn(dev, "always-on domain %s is not on at boot\n", name); + /* Turn it on so pm_genpd_init does not fail */ + active = apple_pmgr_ps_power_on(&ps->genpd) == 0; + } + } + + /* Turn on auto-PM if the domain is already on */ + if (active) + regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE, + APPLE_PMGR_AUTO_ENABLE); + + ret = pm_genpd_init(&ps->genpd, NULL, !active); + 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; + } + } + + /* + * Do not participate in regular PM; parent power domains are handled via the + * genpd hierarchy. + */ + 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,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_platform_driver(apple_pmgr_ps_driver); |