/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); static DEFINE_MUTEX(rsrc_pm_list_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_NONE) 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 sc_err %d\n", pd->rsrc_id, sci_err); return -EINVAL; } /* keep HDMI TX resource power on */ if (power_on && (pd->rsrc_id == SC_R_HDMI || pd->rsrc_id == SC_R_HDMI_I2S || pd->rsrc_id == SC_R_HDMI_I2C_0 || pd->rsrc_id == SC_R_HDMI_PLL_0 || pd->rsrc_id == SC_R_HDMI_PLL_1)) pd->pd.flags |= GENPD_FLAG_ALWAYS_ON; 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 (ret) return ret; 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 0; } 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->dev = dev; 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) { /* only delete those clocks belonged to this devive */ if (imx8_rsrc_clk->dev != dev) continue; list_del(&imx8_rsrc_clk->node); devm_kfree(dev, imx8_rsrc_clk); } } static int imx8_pm_domains_suspend(void) { struct arm_smccc_res res; unsigned int i; for (i = 0; i < IMX8_WU_MAX_IRQS / 32; i++) { if (wakeup_rsrc_id[i] != 0) { arm_smccc_smc(FSL_SIP_WAKEUP_SRC, FSL_SIP_WAKEUP_SRC_IRQSTEER, 0, 0, 0, 0, 0, 0, &res); return 0; } } arm_smccc_smc(FSL_SIP_WAKEUP_SRC, FSL_SIP_WAKEUP_SRC_SCU, 0, 0, 0, 0, 0, 0, &res); return 0; } 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 = { .suspend = imx8_pm_domains_suspend, .resume = imx8_pm_domains_resume, }; static void imx8_pd_setup(struct imx8_pm_domain *pd) { pd->pd.states = kzalloc(2 * sizeof(struct genpd_power_state), GFP_KERNEL); BUG_ON(!pd->pd.states); 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_NONE) { 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_NONE) 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); /*** debugfs support ***/ #ifdef CONFIG_DEBUG_FS #include #include #include #include #include #include #define SC_PM_PW_MODE_FAIL (SC_PM_PW_MODE_ON + 1) static struct dentry *imx8_rsrc_pm_debugfs_dir; static int imx8_rsrc_pm_summary_one(struct seq_file *s, sc_rsrc_t rsrc_id) { static const char * const status_lookup[] = { [SC_PM_PW_MODE_OFF] = "OFF", [SC_PM_PW_MODE_STBY] = "STBY", [SC_PM_PW_MODE_LP] = "LP", [SC_PM_PW_MODE_ON] = "ON", [SC_PM_PW_MODE_FAIL] = "FAIL", }; sc_err_t sci_err = SC_ERR_NONE; sc_pm_power_mode_t mode; char state[16]; sci_err = sc_pm_get_resource_power_mode(pm_ipc_handle, rsrc_id, &mode); if (sci_err) { pr_debug("failed to get power mode on resource %d, ret %d\n", rsrc_id, sci_err); mode = SC_PM_PW_MODE_FAIL; } if (WARN_ON(mode >= ARRAY_SIZE(status_lookup))) return 0; snprintf(state, sizeof(state), "%s", status_lookup[mode]); seq_printf(s, "%-30d %-15s ", rsrc_id, state); seq_puts(s, "\n"); return 0; } static int imx8_rsrc_pm_summary_show(struct seq_file *s, void *data) { int ret = 0; int i; seq_puts(s, "resource_id power_mode\n"); seq_puts(s, "---------------------------------------------\n"); ret = mutex_lock_interruptible(&rsrc_pm_list_lock); if (ret) return -ERESTARTSYS; for (i = 0; i < SC_R_LAST; i++) { ret = imx8_rsrc_pm_summary_one(s, i); if (ret) break; } mutex_unlock(&rsrc_pm_list_lock); return ret; } static int imx8_rsrc_pm_summary_open(struct inode *inode, struct file *file) { return single_open(file, imx8_rsrc_pm_summary_show, NULL); } static const struct file_operations imx8_rsrc_pm_summary_fops = { .open = imx8_rsrc_pm_summary_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init imx8_pm_debug_init(void) { struct dentry *d; /* skip for non-SCFW system */ if (!of_find_compatible_node(NULL, NULL, "nxp,imx8-pd")) return 0; imx8_rsrc_pm_debugfs_dir = debugfs_create_dir("imx_rsrc_pm", NULL); if (!imx8_rsrc_pm_debugfs_dir) return -ENOMEM; d = debugfs_create_file("imx_rsrc_pm_summary", 0444, imx8_rsrc_pm_debugfs_dir, NULL, &imx8_rsrc_pm_summary_fops); if (!d) return -ENOMEM; return 0; } late_initcall(imx8_pm_debug_init); static void __exit imx8_rsrc_pm_debug_exit(void) { debugfs_remove_recursive(imx8_rsrc_pm_debugfs_dir); } __exitcall(imx8_rsrc_pm_debug_exit); #endif /* CONFIG_DEBUG_FS */