e2ad626f8f
It has been pointed out that naming a subsystem "genpd" isn't very self-explanatory and the acronym itself that means Generic PM Domain, is known only by a limited group of people. In a way to improve the situation, let's rename the subsystem to pmdomain, which ideally should indicate that this is about so called Power Domains or "PM domains" as we often also use within the Linux Kernel terminology. Suggested-by: Rafael J. Wysocki <rafael@kernel.org> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Acked-by: Arnd Bergmann <arnd@arndb.de> Acked-by: Heiko Stuebner <heiko@sntech.de> Acked-by: Rafael J. Wysocki <rafael@kernel.org> Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Link: https://lore.kernel.org/r/20230912221127.487327-1-ulf.hansson@linaro.org
327 lines
9.1 KiB
C
327 lines
9.1 KiB
C
// 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);
|