// SPDX-License-Identifier: GPL-2.0-only OR MIT /* * Apple SoC PMGR device power state driver * * Copyright The Asahi Linux Contributors */ #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("PMGR power state driver for Apple SoCs"); MODULE_LICENSE("GPL v2"); module_platform_driver(apple_pmgr_ps_driver);