root/drivers/pmdomain/apple/pmgr-pwrstate.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, &reg);
        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, &reg);
        /*
         * 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, &reg);

        return !!(reg & APPLE_PMGR_RESET);
}

static 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,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_platform_driver(apple_pmgr_ps_driver);