/drivers/soc/imx/pm-domains.c
C | 505 lines | 368 code | 90 blank | 47 comment | 77 complexity | 97f474c32177ee458d1fc3d2499790f4 MD5 | raw file
Possible License(s): GPL-2.0
- /*
- * Copyright (C) 2016 Freescale Semiconductor, Inc.
- * Copyright 2017 NXP
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
- #include <linux/clk.h>
- #include <linux/clk-provider.h>
- #include <linux/console.h>
- #include <linux/delay.h>
- #include <linux/io.h>
- #include <linux/irq.h>
- #include <linux/irqchip.h>
- #include <linux/irqchip/arm-gic.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/of_platform.h>
- #include <linux/platform_device.h>
- #include <linux/pm.h>
- #include <linux/pm_runtime.h>
- #include <linux/pm_clock.h>
- #include <linux/slab.h>
- #include <linux/syscore_ops.h>
- #include <soc/imx8/sc/sci.h>
- #include "pm-domain-imx8.h"
- static sc_ipc_t pm_ipc_handle;
- static sc_rsrc_t early_power_on_rsrc[] = {
- SC_R_LAST, SC_R_LAST, SC_R_LAST, SC_R_LAST, SC_R_LAST,
- SC_R_LAST, SC_R_LAST, SC_R_LAST, SC_R_LAST, SC_R_LAST,
- };
- static sc_rsrc_t rsrc_debug_console;
- #define IMX8_WU_MAX_IRQS (((SC_R_LAST + 31) / 32 ) * 32 )
- static sc_rsrc_t irq2rsrc[IMX8_WU_MAX_IRQS];
- static sc_rsrc_t wakeup_rsrc_id[IMX8_WU_MAX_IRQS / 32];
- static DEFINE_SPINLOCK(imx8_wu_lock);
- enum imx_pd_state {
- PD_LP,
- PD_OFF,
- };
- struct clk_stat {
- struct clk *clk;
- struct clk *parent;
- unsigned long rate;
- };
- static int imx8_pd_power(struct generic_pm_domain *domain, bool power_on)
- {
- struct imx8_pm_domain *pd;
- sc_err_t sci_err = SC_ERR_NONE;
- pd = container_of(domain, struct imx8_pm_domain, pd);
- if (pd->rsrc_id == SC_R_LAST)
- return 0;
- /* keep uart console power on for no_console_suspend */
- if (pd->rsrc_id == rsrc_debug_console &&
- !console_suspend_enabled && !power_on)
- return 0;
- /* keep resource power on if it is a wakeup source */
- if (!power_on && ((1 << pd->rsrc_id % 32) &
- wakeup_rsrc_id[pd->rsrc_id / 32]))
- return 0;
- sci_err = sc_pm_set_resource_power_mode(pm_ipc_handle, pd->rsrc_id,
- (power_on) ? SC_PM_PW_MODE_ON :
- pd->pd.state_idx ? SC_PM_PW_MODE_OFF : SC_PM_PW_MODE_LP);
- if (sci_err)
- pr_err("Failed power operation on resource %d\n", pd->rsrc_id);
- return 0;
- }
- static int imx8_pd_power_on(struct generic_pm_domain *domain)
- {
- struct imx8_pm_domain *pd;
- struct imx8_pm_rsrc_clks *imx8_rsrc_clk;
- int ret = 0;
- pd = container_of(domain, struct imx8_pm_domain, pd);
- ret = imx8_pd_power(domain, true);
- if (!list_empty(&pd->clks) && (pd->pd.state_idx == PD_OFF)) {
- if (pd->clk_state_saved) {
- /*
- * The SS is powered on restore the clock rates that
- * may be lost.
- */
- list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
- if (imx8_rsrc_clk->parent)
- clk_set_parent(imx8_rsrc_clk->clk,
- imx8_rsrc_clk->parent);
- if (imx8_rsrc_clk->rate) {
- /*
- * Need to read the clock so that rate in
- * Linux is reset.
- */
- clk_get_rate(imx8_rsrc_clk->clk);
- /* Restore the clock rate. */
- clk_set_rate(imx8_rsrc_clk->clk,
- imx8_rsrc_clk->rate);
- }
- }
- } else if (pd->clk_state_may_lost) {
- struct clk_stat *clk_stats;
- int count = 0;
- int i = 0;
- /*
- * The SS is powered down before without saving clk rates,
- * try to restore the lost clock rates if any
- *
- * As a parent clk rate restore will cause the clk recalc
- * to all possible child clks which may result in child clk
- * previous state lost due to power domain lost before, we
- * have to first walk through all child clks to retrieve the
- * state via clk_hw_get_rate which bypassed the clk recalc,
- * then we can restore them one by one.
- */
- list_for_each_entry(imx8_rsrc_clk, &pd->clks, node)
- count++;
- clk_stats = kzalloc(count * sizeof(*clk_stats), GFP_KERNEL);
- if (!clk_stats) {
- pr_warn("%s: failed to alloc mem for clk state recovery\n", pd->name);
- return -ENOMEM;
- }
- list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
- clk_stats[i].clk = imx8_rsrc_clk->clk;
- clk_stats[i].parent = clk_get_parent(imx8_rsrc_clk->clk);
- clk_stats[i].rate = clk_hw_get_rate(__clk_get_hw(imx8_rsrc_clk->clk));
- i++;
- }
- for (i = 0; i < count; i++) {
- /* restore parent first */
- if (clk_stats[i].parent)
- clk_set_parent(clk_stats[i].clk, clk_stats[i].parent);
- if (clk_stats[i].rate) {
- /* invalid cached rate first by get rate once */
- clk_get_rate(clk_stats[i].clk);
- /* restore the lost rate */
- clk_set_rate(clk_stats[i].clk, clk_stats[i].rate);
- }
- }
- kfree(clk_stats);
- }
- }
- return ret;
- }
- static int imx8_pd_power_off(struct generic_pm_domain *domain)
- {
- struct imx8_pm_domain *pd;
- struct imx8_pm_rsrc_clks *imx8_rsrc_clk;
- pd = container_of(domain, struct imx8_pm_domain, pd);
- if (!list_empty(&pd->clks) && (pd->pd.state_idx == PD_OFF)) {
- /*
- * The SS is going to be powered off, store the clock rates
- * that may be lost.
- */
- list_for_each_entry(imx8_rsrc_clk, &pd->clks, node) {
- imx8_rsrc_clk->parent = clk_get_parent(imx8_rsrc_clk->clk);
- imx8_rsrc_clk->rate = clk_hw_get_rate(__clk_get_hw(imx8_rsrc_clk->clk));
- }
- pd->clk_state_saved = true;
- pd->clk_state_may_lost = false;
- } else if (pd->pd.state_idx == PD_OFF) {
- pd->clk_state_saved = false;
- pd->clk_state_may_lost = true;
- } else {
- pd->clk_state_saved = false;
- pd->clk_state_may_lost = false;
- }
- return imx8_pd_power(domain, false);
- }
- static int imx8_attach_dev(struct generic_pm_domain *genpd, struct device *dev)
- {
- struct imx8_pm_domain *pd;
- struct device_node *node = dev->of_node;
- struct of_phandle_args clkspec;
- int rc, index, num_clks;
- pd = container_of(genpd, struct imx8_pm_domain, pd);
- num_clks = of_count_phandle_with_args(node, "assigned-clocks",
- "#clock-cells");
- if (num_clks == -EINVAL)
- pr_err("%s: Invalid value of assigned-clocks property at %s\n",
- pd->name, node->full_name);
- for (index = 0; index < num_clks; index++) {
- struct imx8_pm_rsrc_clks *imx8_rsrc_clk;
- rc = of_parse_phandle_with_args(node, "assigned-clocks",
- "#clock-cells", index, &clkspec);
- if (rc < 0) {
- /* skip empty (null) phandles */
- if (rc == -ENOENT)
- continue;
- else
- return rc;
- }
- if (clkspec.np == node)
- return 0;
- imx8_rsrc_clk = devm_kzalloc(dev, sizeof(*imx8_rsrc_clk),
- GFP_KERNEL);
- if (!imx8_rsrc_clk)
- return -ENOMEM;
- imx8_rsrc_clk->clk = of_clk_get_from_provider(&clkspec);
- if (!IS_ERR(imx8_rsrc_clk->clk))
- list_add_tail(&imx8_rsrc_clk->node, &pd->clks);
- }
- return 0;
- }
- static void imx8_detach_dev(struct generic_pm_domain *genpd, struct device *dev)
- {
- struct imx8_pm_domain *pd;
- struct imx8_pm_rsrc_clks *imx8_rsrc_clk, *tmp;
- pd = container_of(genpd, struct imx8_pm_domain, pd);
- /* Free all the clock entry nodes. */
- if (list_empty(&pd->clks))
- return;
- list_for_each_entry_safe(imx8_rsrc_clk, tmp, &pd->clks, node) {
- list_del(&imx8_rsrc_clk->node);
- devm_kfree(dev, imx8_rsrc_clk);
- }
- }
- static void imx8_pm_domains_resume(void)
- {
- sc_err_t sci_err = SC_ERR_NONE;
- int i;
- for (i = 0; i < (sizeof(early_power_on_rsrc) /
- sizeof(sc_rsrc_t)); i++) {
- if (early_power_on_rsrc[i] != SC_R_LAST) {
- sci_err = sc_pm_set_resource_power_mode(pm_ipc_handle,
- early_power_on_rsrc[i], SC_PM_PW_MODE_ON);
- if (sci_err != SC_ERR_NONE)
- pr_err("fail to power on resource %d\n",
- early_power_on_rsrc[i]);
- }
- }
- }
- struct syscore_ops imx8_pm_domains_syscore_ops = {
- .resume = imx8_pm_domains_resume,
- };
- static void imx8_pd_setup(struct imx8_pm_domain *pd)
- {
- pd->pd.power_off = imx8_pd_power_off;
- pd->pd.power_on = imx8_pd_power_on;
- pd->pd.attach_dev = imx8_attach_dev;
- pd->pd.detach_dev = imx8_detach_dev;
- pd->pd.states[0].power_off_latency_ns = 25000;
- pd->pd.states[0].power_on_latency_ns = 25000;
- pd->pd.states[1].power_off_latency_ns = 2500000;
- pd->pd.states[1].power_on_latency_ns = 2500000;
- pd->pd.state_count = 2;
- }
- static int __init imx8_add_pm_domains(struct device_node *parent,
- struct generic_pm_domain *genpd_parent)
- {
- struct device_node *np;
- static int index;
- for_each_child_of_node(parent, np) {
- struct imx8_pm_domain *imx8_pd;
- sc_rsrc_t rsrc_id;
- u32 wakeup_irq;
- if (!of_device_is_available(np))
- continue;
- imx8_pd = kzalloc(sizeof(*imx8_pd), GFP_KERNEL);
- if (!imx8_pd)
- return -ENOMEM;
- if (!of_property_read_string(np, "name", &imx8_pd->pd.name))
- imx8_pd->name = imx8_pd->pd.name;
- if (!of_property_read_u32(np, "reg", &rsrc_id))
- imx8_pd->rsrc_id = rsrc_id;
- if (imx8_pd->rsrc_id != SC_R_LAST) {
- imx8_pd_setup(imx8_pd);
- if (of_property_read_bool(np, "early_power_on")
- && index < (sizeof(early_power_on_rsrc) /
- sizeof(sc_rsrc_t))) {
- early_power_on_rsrc[index++] = imx8_pd->rsrc_id;
- }
- if (of_property_read_bool(np, "debug_console"))
- rsrc_debug_console = imx8_pd->rsrc_id;
- if (!of_property_read_u32(np, "wakeup-irq",
- &wakeup_irq))
- irq2rsrc[wakeup_irq] = imx8_pd->rsrc_id;
- }
- INIT_LIST_HEAD(&imx8_pd->clks);
- pm_genpd_init(&imx8_pd->pd, NULL, true);
- if (genpd_parent)
- pm_genpd_add_subdomain(genpd_parent, &imx8_pd->pd);
- of_genpd_add_provider_simple(np, &imx8_pd->pd);
- imx8_add_pm_domains(np, &imx8_pd->pd);
- }
- return 0;
- }
- static int __init imx8_init_pm_domains(void)
- {
- struct device_node *np;
- sc_err_t sci_err;
- sc_rsrc_t rsrc_id;
- uint32_t mu_id;
- /* skip pm domains for non-SCFW system */
- if (!of_find_compatible_node(NULL, NULL, "nxp,imx8-pd"))
- return 0;
- pr_info("***** imx8_init_pm_domains *****\n");
- for_each_compatible_node(np, NULL, "nxp,imx8-pd") {
- struct imx8_pm_domain *imx8_pd;
- if (!of_device_is_available(np))
- continue;
- imx8_pd = kzalloc(sizeof(struct imx8_pm_domain), GFP_KERNEL);
- if (!imx8_pd) {
- pr_err("%s: failed to allocate memory for domain\n",
- __func__);
- return -ENOMEM;
- }
- if (!of_property_read_string(np, "name", &imx8_pd->pd.name))
- imx8_pd->name = imx8_pd->pd.name;
- if (!of_property_read_u32(np, "reg", &rsrc_id))
- imx8_pd->rsrc_id = rsrc_id;
- if (imx8_pd->rsrc_id != SC_R_LAST)
- imx8_pd_setup(imx8_pd);
- INIT_LIST_HEAD(&imx8_pd->clks);
- pm_genpd_init(&imx8_pd->pd, NULL, true);
- of_genpd_add_provider_simple(np, &imx8_pd->pd);
- imx8_add_pm_domains(np, &imx8_pd->pd);
- }
- sci_err = sc_ipc_getMuID(&mu_id);
- if (sci_err != SC_ERR_NONE) {
- pr_info("Cannot obtain MU ID\n");
- return sci_err;
- }
- sci_err = sc_ipc_open(&pm_ipc_handle, mu_id);
- register_syscore_ops(&imx8_pm_domains_syscore_ops);
- return 0;
- }
- early_initcall(imx8_init_pm_domains);
- static int imx8_wu_irq_set_wake(struct irq_data *d, unsigned int on)
- {
- unsigned int idx = irq2rsrc[d->hwirq] / 32;
- u32 mask = 1 << irq2rsrc[d->hwirq] % 32;
- spin_lock(&imx8_wu_lock);
- wakeup_rsrc_id[idx] = on ? wakeup_rsrc_id[idx] | mask :
- wakeup_rsrc_id[idx] & ~mask;
- spin_unlock(&imx8_wu_lock);
- return 0;
- }
- static struct irq_chip imx8_wu_chip = {
- .name = "IMX8-WU",
- .irq_eoi = irq_chip_eoi_parent,
- .irq_mask = irq_chip_mask_parent,
- .irq_unmask = irq_chip_unmask_parent,
- .irq_retrigger = irq_chip_retrigger_hierarchy,
- .irq_set_wake = imx8_wu_irq_set_wake,
- .irq_set_affinity = irq_chip_set_affinity_parent,
- };
- static int imx8_wu_domain_translate(struct irq_domain *d,
- struct irq_fwspec *fwspec,
- unsigned long *hwirq,
- unsigned int *type)
- {
- if (is_of_node(fwspec->fwnode)) {
- if (fwspec->param_count != 3)
- return -EINVAL;
- /* No PPI should point to this domain */
- if (fwspec->param[0] != 0)
- return -EINVAL;
- *hwirq = fwspec->param[1];
- *type = fwspec->param[2];
- return 0;
- }
- return -EINVAL;
- }
- static int imx8_wu_domain_alloc(struct irq_domain *domain,
- unsigned int irq,
- unsigned int nr_irqs, void *data)
- {
- struct irq_fwspec *fwspec = data;
- struct irq_fwspec parent_fwspec;
- irq_hw_number_t hwirq;
- int i;
- if (fwspec->param_count != 3)
- return -EINVAL; /* Not GIC compliant */
- if (fwspec->param[0] != 0)
- return -EINVAL; /* No PPI should point to this domain */
- hwirq = fwspec->param[1];
- if (hwirq >= IMX8_WU_MAX_IRQS)
- return -EINVAL; /* Can't deal with this */
- for (i = 0; i < nr_irqs; i++)
- irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
- &imx8_wu_chip, NULL);
- parent_fwspec = *fwspec;
- parent_fwspec.fwnode = domain->parent->fwnode;
- return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
- &parent_fwspec);
- }
- static const struct irq_domain_ops imx8_wu_domain_ops = {
- .translate = imx8_wu_domain_translate,
- .alloc = imx8_wu_domain_alloc,
- .free = irq_domain_free_irqs_common,
- };
- static int __init imx8_wu_init(struct device_node *node,
- struct device_node *parent)
- {
- struct irq_domain *parent_domain, *domain;
- if (!parent) {
- pr_err("%s: no parent, giving up\n", node->full_name);
- return -ENODEV;
- }
- parent_domain = irq_find_host(parent);
- if (!parent_domain) {
- pr_err("%s: unable to obtain parent domain\n", node->full_name);
- return -ENXIO;
- }
- domain = irq_domain_add_hierarchy(parent_domain, 0, IMX8_WU_MAX_IRQS,
- node, &imx8_wu_domain_ops,
- NULL);
- if (!domain)
- return -ENOMEM;
- return 0;
- }
- IRQCHIP_DECLARE(imx8_wakeup_unit, "fsl,imx8-wu", imx8_wu_init);