mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
8.9 KiB
324 lines
8.9 KiB
// 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); |
|
|
|
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_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); |
|
|
|
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_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); |
|
|
|
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 = 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.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 <[email protected]>"); |
|
MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs"); |
|
MODULE_LICENSE("GPL v2"); |
|
|
|
module_platform_driver(apple_pmgr_ps_driver);
|
|
|