root/drivers/pmdomain/imx/gpc.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2015-2017 Pengutronix, Lucas Stach <kernel@pengutronix.de>
 * Copyright 2011-2013 Freescale Semiconductor, Inc.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>

#define GPC_CNTR                0x000

#define GPC_PGC_CTRL_OFFS       0x0
#define GPC_PGC_PUPSCR_OFFS     0x4
#define GPC_PGC_PDNSCR_OFFS     0x8
#define GPC_PGC_SW2ISO_SHIFT    0x8
#define GPC_PGC_SW_SHIFT        0x0

#define GPC_PGC_PCI_PDN         0x200
#define GPC_PGC_PCI_SR          0x20c

#define GPC_PGC_GPU_PDN         0x260
#define GPC_PGC_GPU_PUPSCR      0x264
#define GPC_PGC_GPU_PDNSCR      0x268
#define GPC_PGC_GPU_SR          0x26c

#define GPC_PGC_DISP_PDN        0x240
#define GPC_PGC_DISP_SR         0x24c

#define GPU_VPU_PUP_REQ         BIT(1)
#define GPU_VPU_PDN_REQ         BIT(0)

#define GPC_CLK_MAX             7

#define PGC_DOMAIN_FLAG_NO_PD           BIT(0)

struct imx_pm_domain {
        struct generic_pm_domain base;
        struct regmap *regmap;
        struct regulator *supply;
        struct clk *clk[GPC_CLK_MAX];
        int num_clks;
        unsigned int reg_offs;
        signed char cntr_pdn_bit;
        unsigned int ipg_rate_mhz;
};

static inline struct imx_pm_domain *
to_imx_pm_domain(struct generic_pm_domain *genpd)
{
        return container_of(genpd, struct imx_pm_domain, base);
}

static int imx6_pm_domain_power_off(struct generic_pm_domain *genpd)
{
        struct imx_pm_domain *pd = to_imx_pm_domain(genpd);
        int iso, iso2sw;
        u32 val;

        /* Read ISO and ISO2SW power down delays */
        regmap_read(pd->regmap, pd->reg_offs + GPC_PGC_PDNSCR_OFFS, &val);
        iso = val & 0x3f;
        iso2sw = (val >> 8) & 0x3f;

        /* Gate off domain when powered down */
        regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS,
                           0x1, 0x1);

        /* Request GPC to power down domain */
        val = BIT(pd->cntr_pdn_bit);
        regmap_update_bits(pd->regmap, GPC_CNTR, val, val);

        /* Wait ISO + ISO2SW IPG clock cycles */
        udelay(DIV_ROUND_UP(iso + iso2sw, pd->ipg_rate_mhz));

        if (pd->supply)
                regulator_disable(pd->supply);

        return 0;
}

static int imx6_pm_domain_power_on(struct generic_pm_domain *genpd)
{
        struct imx_pm_domain *pd = to_imx_pm_domain(genpd);
        int i, ret;
        u32 val, req;

        if (pd->supply) {
                ret = regulator_enable(pd->supply);
                if (ret) {
                        pr_err("%s: failed to enable regulator: %d\n",
                               __func__, ret);
                        return ret;
                }
        }

        /* Enable reset clocks for all devices in the domain */
        for (i = 0; i < pd->num_clks; i++)
                clk_prepare_enable(pd->clk[i]);

        /* Gate off domain when powered down */
        regmap_update_bits(pd->regmap, pd->reg_offs + GPC_PGC_CTRL_OFFS,
                           0x1, 0x1);

        /* Request GPC to power up domain */
        req = BIT(pd->cntr_pdn_bit + 1);
        regmap_update_bits(pd->regmap, GPC_CNTR, req, req);

        /* Wait for the PGC to handle the request */
        ret = regmap_read_poll_timeout(pd->regmap, GPC_CNTR, val, !(val & req),
                                       1, 50);
        if (ret)
                pr_err("powerup request on domain %s timed out\n", genpd->name);

        /* Wait for reset to propagate through peripherals */
        usleep_range(5, 10);

        /* Disable reset clocks for all devices in the domain */
        for (i = 0; i < pd->num_clks; i++)
                clk_disable_unprepare(pd->clk[i]);

        return 0;
}

static int imx_pgc_get_clocks(struct device *dev, struct imx_pm_domain *domain)
{
        int i, ret;

        for (i = 0; ; i++) {
                struct clk *clk = of_clk_get(dev->of_node, i);
                if (IS_ERR(clk))
                        break;
                if (i >= GPC_CLK_MAX) {
                        dev_err(dev, "more than %d clocks\n", GPC_CLK_MAX);
                        ret = -EINVAL;
                        goto clk_err;
                }
                domain->clk[i] = clk;
        }
        domain->num_clks = i;

        return 0;

clk_err:
        while (i--)
                clk_put(domain->clk[i]);

        return ret;
}

static void imx_pgc_put_clocks(struct imx_pm_domain *domain)
{
        int i;

        for (i = domain->num_clks - 1; i >= 0; i--)
                clk_put(domain->clk[i]);
}

static int imx_pgc_parse_dt(struct device *dev, struct imx_pm_domain *domain)
{
        /* try to get the domain supply regulator */
        domain->supply = devm_regulator_get_optional(dev, "power");
        if (IS_ERR(domain->supply)) {
                if (PTR_ERR(domain->supply) == -ENODEV)
                        domain->supply = NULL;
                else
                        return PTR_ERR(domain->supply);
        }

        /* try to get all clocks needed for reset propagation */
        return imx_pgc_get_clocks(dev, domain);
}

static int imx_pgc_power_domain_probe(struct platform_device *pdev)
{
        struct imx_pm_domain *domain = pdev->dev.platform_data;
        struct device *dev = &pdev->dev;
        int ret;

        /* if this PD is associated with a DT node try to parse it */
        if (dev->of_node) {
                ret = imx_pgc_parse_dt(dev, domain);
                if (ret)
                        return ret;
        }

        /* initially power on the domain */
        if (domain->base.power_on)
                domain->base.power_on(&domain->base);

        if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
                pm_genpd_init(&domain->base, NULL, false);
                ret = of_genpd_add_provider_simple(dev->of_node, &domain->base);
                if (ret)
                        goto genpd_err;
        }

        device_link_add(dev, dev->parent, DL_FLAG_AUTOREMOVE_CONSUMER);

        return 0;

genpd_err:
        pm_genpd_remove(&domain->base);
        imx_pgc_put_clocks(domain);

        return ret;
}

static void imx_pgc_power_domain_remove(struct platform_device *pdev)
{
        struct imx_pm_domain *domain = pdev->dev.platform_data;

        if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
                of_genpd_del_provider(pdev->dev.of_node);
                pm_genpd_remove(&domain->base);
                imx_pgc_put_clocks(domain);
        }
}

static const struct platform_device_id imx_pgc_power_domain_id[] = {
        { "imx-pgc-power-domain"},
        { },
};

static struct platform_driver imx_pgc_power_domain_driver = {
        .driver = {
                .name = "imx-pgc-pd",
        },
        .probe = imx_pgc_power_domain_probe,
        .remove = imx_pgc_power_domain_remove,
        .id_table = imx_pgc_power_domain_id,
};
builtin_platform_driver(imx_pgc_power_domain_driver)

#define GPC_PGC_DOMAIN_ARM      0
#define GPC_PGC_DOMAIN_PU       1
#define GPC_PGC_DOMAIN_DISPLAY  2
#define GPC_PGC_DOMAIN_PCI      3

static struct genpd_power_state imx6_pm_domain_pu_state = {
        .power_off_latency_ns = 25000,
        .power_on_latency_ns = 2000000,
};

static struct imx_pm_domain imx_gpc_domains[] = {
        [GPC_PGC_DOMAIN_ARM] = {
                .base = {
                        .name = "ARM",
                        .flags = GENPD_FLAG_ALWAYS_ON,
                },
        },
        [GPC_PGC_DOMAIN_PU] = {
                .base = {
                        .name = "PU",
                        .power_off = imx6_pm_domain_power_off,
                        .power_on = imx6_pm_domain_power_on,
                        .states = &imx6_pm_domain_pu_state,
                        .state_count = 1,
                },
                .reg_offs = 0x260,
                .cntr_pdn_bit = 0,
        },
        [GPC_PGC_DOMAIN_DISPLAY] = {
                .base = {
                        .name = "DISPLAY",
                        .power_off = imx6_pm_domain_power_off,
                        .power_on = imx6_pm_domain_power_on,
                },
                .reg_offs = 0x240,
                .cntr_pdn_bit = 4,
        },
        [GPC_PGC_DOMAIN_PCI] = {
                .base = {
                        .name = "PCI",
                        .power_off = imx6_pm_domain_power_off,
                        .power_on = imx6_pm_domain_power_on,
                },
                .reg_offs = 0x200,
                .cntr_pdn_bit = 6,
        },
};

struct imx_gpc_dt_data {
        int num_domains;
        bool err009619_present;
        bool err006287_present;
};

static const struct imx_gpc_dt_data imx6q_dt_data = {
        .num_domains = 2,
        .err009619_present = false,
        .err006287_present = false,
};

static const struct imx_gpc_dt_data imx6qp_dt_data = {
        .num_domains = 2,
        .err009619_present = true,
        .err006287_present = false,
};

static const struct imx_gpc_dt_data imx6sl_dt_data = {
        .num_domains = 3,
        .err009619_present = false,
        .err006287_present = true,
};

static const struct imx_gpc_dt_data imx6sx_dt_data = {
        .num_domains = 4,
        .err009619_present = false,
        .err006287_present = false,
};

static const struct of_device_id imx_gpc_dt_ids[] = {
        { .compatible = "fsl,imx6q-gpc", .data = &imx6q_dt_data },
        { .compatible = "fsl,imx6qp-gpc", .data = &imx6qp_dt_data },
        { .compatible = "fsl,imx6sl-gpc", .data = &imx6sl_dt_data },
        { .compatible = "fsl,imx6sx-gpc", .data = &imx6sx_dt_data },
        { }
};

static const struct regmap_range yes_ranges[] = {
        regmap_reg_range(GPC_CNTR, GPC_CNTR),
        regmap_reg_range(GPC_PGC_PCI_PDN, GPC_PGC_PCI_SR),
        regmap_reg_range(GPC_PGC_GPU_PDN, GPC_PGC_GPU_SR),
        regmap_reg_range(GPC_PGC_DISP_PDN, GPC_PGC_DISP_SR),
};

static const struct regmap_access_table access_table = {
        .yes_ranges     = yes_ranges,
        .n_yes_ranges   = ARRAY_SIZE(yes_ranges),
};

static const struct regmap_config imx_gpc_regmap_config = {
        .reg_bits = 32,
        .val_bits = 32,
        .reg_stride = 4,
        .rd_table = &access_table,
        .wr_table = &access_table,
        .max_register = 0x2ac,
};

static struct generic_pm_domain *imx_gpc_onecell_domains[] = {
        &imx_gpc_domains[GPC_PGC_DOMAIN_ARM].base,
        &imx_gpc_domains[GPC_PGC_DOMAIN_PU].base,
};

static struct genpd_onecell_data imx_gpc_onecell_data = {
        .domains = imx_gpc_onecell_domains,
        .num_domains = 2,
};

static int imx_gpc_old_dt_init(struct device *dev, struct regmap *regmap,
                               unsigned int num_domains)
{
        struct imx_pm_domain *domain;
        int i, ret;

        for (i = 0; i < num_domains; i++) {
                domain = &imx_gpc_domains[i];
                domain->regmap = regmap;
                domain->ipg_rate_mhz = 66;

                if (i == 1) {
                        domain->supply = devm_regulator_get(dev, "pu");
                        if (IS_ERR(domain->supply))
                                return PTR_ERR(domain->supply);

                        ret = imx_pgc_get_clocks(dev, domain);
                        if (ret)
                                goto clk_err;

                        domain->base.power_on(&domain->base);
                }
        }

        for (i = 0; i < num_domains; i++)
                pm_genpd_init(&imx_gpc_domains[i].base, NULL, false);

        if (IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS)) {
                ret = of_genpd_add_provider_onecell(dev->of_node,
                                                    &imx_gpc_onecell_data);
                if (ret)
                        goto genpd_err;
        }

        return 0;

genpd_err:
        for (i = 0; i < num_domains; i++)
                pm_genpd_remove(&imx_gpc_domains[i].base);
        imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]);
clk_err:
        return ret;
}

static int imx_gpc_probe(struct platform_device *pdev)
{
        const struct imx_gpc_dt_data *of_id_data = device_get_match_data(&pdev->dev);
        struct device_node *pgc_node __free(device_node)
                = of_get_child_by_name(pdev->dev.of_node, "pgc");
        struct regmap *regmap;
        void __iomem *base;
        int ret;

        /* bail out if DT too old and doesn't provide the necessary info */
        if (!of_property_present(pdev->dev.of_node, "#power-domain-cells") &&
            !pgc_node)
                return 0;

        base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(base))
                return PTR_ERR(base);

        regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base,
                                           &imx_gpc_regmap_config);
        if (IS_ERR(regmap)) {
                ret = PTR_ERR(regmap);
                dev_err(&pdev->dev, "failed to init regmap: %d\n",
                        ret);
                return ret;
        }

        /*
         * Disable PU power down by runtime PM if ERR009619 is present.
         *
         * The PRE clock will be paused for several cycles when turning on the
         * PU domain LDO from power down state. If PRE is in use at that time,
         * the IPU/PRG cannot get the correct display data from the PRE.
         *
         * This is not a concern when the whole system enters suspend state, so
         * it's safe to power down PU in this case.
         */
        if (of_id_data->err009619_present)
                imx_gpc_domains[GPC_PGC_DOMAIN_PU].base.flags |=
                                GENPD_FLAG_RPM_ALWAYS_ON;

        /* Keep DISP always on if ERR006287 is present */
        if (of_id_data->err006287_present)
                imx_gpc_domains[GPC_PGC_DOMAIN_DISPLAY].base.flags |=
                                GENPD_FLAG_ALWAYS_ON;

        if (!pgc_node) {
                ret = imx_gpc_old_dt_init(&pdev->dev, regmap,
                                          of_id_data->num_domains);
                if (ret)
                        return ret;
        } else {
                struct imx_pm_domain *domain;
                struct platform_device *pd_pdev;
                struct clk *ipg_clk;
                unsigned int ipg_rate_mhz;
                int domain_index;

                ipg_clk = devm_clk_get(&pdev->dev, "ipg");
                if (IS_ERR(ipg_clk))
                        return PTR_ERR(ipg_clk);
                ipg_rate_mhz = clk_get_rate(ipg_clk) / 1000000;

                for_each_child_of_node_scoped(pgc_node, np) {
                        ret = of_property_read_u32(np, "reg", &domain_index);
                        if (ret)
                                return ret;

                        if (domain_index >= of_id_data->num_domains)
                                continue;

                        pd_pdev = platform_device_alloc("imx-pgc-power-domain",
                                                        domain_index);
                        if (!pd_pdev)
                                return -ENOMEM;

                        ret = platform_device_add_data(pd_pdev,
                                                       &imx_gpc_domains[domain_index],
                                                       sizeof(imx_gpc_domains[domain_index]));
                        if (ret) {
                                platform_device_put(pd_pdev);
                                return ret;
                        }
                        domain = pd_pdev->dev.platform_data;
                        domain->regmap = regmap;
                        domain->ipg_rate_mhz = ipg_rate_mhz;

                        pd_pdev->dev.parent = &pdev->dev;
                        pd_pdev->dev.of_node = np;
                        pd_pdev->dev.fwnode = of_fwnode_handle(np);

                        ret = platform_device_add(pd_pdev);
                        if (ret) {
                                platform_device_put(pd_pdev);
                                return ret;
                        }
                }
        }

        return 0;
}

static void imx_gpc_remove(struct platform_device *pdev)
{
        struct device_node *pgc_node;
        int ret;

        pgc_node = of_get_child_by_name(pdev->dev.of_node, "pgc");

        /* bail out if DT too old and doesn't provide the necessary info */
        if (!of_property_present(pdev->dev.of_node, "#power-domain-cells") &&
            !pgc_node)
                return;

        /*
         * If the old DT binding is used the toplevel driver needs to
         * de-register the power domains
         */
        if (!pgc_node) {
                of_genpd_del_provider(pdev->dev.of_node);

                ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_PU].base);
                if (ret) {
                        dev_err(&pdev->dev, "Failed to remove PU power domain (%pe)\n",
                                ERR_PTR(ret));
                        return;
                }
                imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]);

                ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_ARM].base);
                if (ret) {
                        dev_err(&pdev->dev, "Failed to remove ARM power domain (%pe)\n",
                                ERR_PTR(ret));
                        return;
                }
        }

        of_node_put(pgc_node);
}

static struct platform_driver imx_gpc_driver = {
        .driver = {
                .name = "imx-gpc",
                .of_match_table = imx_gpc_dt_ids,
        },
        .probe = imx_gpc_probe,
        .remove = imx_gpc_remove,
};
builtin_platform_driver(imx_gpc_driver)