diff options
| -rw-r--r-- | drivers/soc/tegra/pmc.c | 425 | ||||
| -rw-r--r-- | include/soc/tegra/pmc.h | 60 |
2 files changed, 384 insertions, 101 deletions
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index f3760a3b3026..9cdbd8ba94be 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -28,6 +28,7 @@ #include <linux/iopoll.h> #include <linux/irqdomain.h> #include <linux/irq.h> +#include <linux/irq_work.h> #include <linux/kernel.h> #include <linux/of_address.h> #include <linux/of_clk.h> @@ -201,18 +202,20 @@ #define TEGRA_SMC_PMC_WRITE 0xbb struct pmc_clk { - struct clk_hw hw; - unsigned long offs; - u32 mux_shift; - u32 force_en_shift; + struct clk_hw hw; + struct tegra_pmc *pmc; + unsigned long offs; + u32 mux_shift; + u32 force_en_shift; }; #define to_pmc_clk(_hw) container_of(_hw, struct pmc_clk, hw) struct pmc_clk_gate { - struct clk_hw hw; - unsigned long offs; - u32 shift; + struct clk_hw hw; + struct tegra_pmc *pmc; + unsigned long offs; + u32 shift; }; #define to_pmc_clk_gate(_hw) container_of(_hw, struct pmc_clk_gate, hw) @@ -265,6 +268,17 @@ static const struct pmc_clk_init_data tegra_pmc_clks_data[] = { }, }; +struct tegra_pmc_core_pd { + struct generic_pm_domain genpd; + struct tegra_pmc *pmc; +}; + +static inline struct tegra_pmc_core_pd * +to_core_pd(struct generic_pm_domain *genpd) +{ + return container_of(genpd, struct tegra_pmc_core_pd, genpd); +} + struct tegra_powergate { struct generic_pm_domain genpd; struct tegra_pmc *pmc; @@ -467,7 +481,13 @@ struct tegra_pmc { unsigned long *wake_type_dual_edge_map; unsigned long *wake_sw_status_map; unsigned long *wake_cntrl_level_map; + + struct notifier_block reboot_notifier; struct syscore syscore; + + /* Pending wake IRQ processing */ + struct irq_work wake_work; + u32 *wake_status; }; static struct tegra_pmc *pmc = &(struct tegra_pmc) { @@ -541,12 +561,7 @@ static void tegra_pmc_scratch_writel(struct tegra_pmc *pmc, u32 value, writel(value, pmc->scratch + offset); } -/* - * TODO Figure out a way to call this with the struct tegra_pmc * passed in. - * This currently doesn't work because readx_poll_timeout() can only operate - * on functions that take a single argument. - */ -static inline bool tegra_powergate_state(int id) +static inline bool tegra_powergate_state(struct tegra_pmc *pmc, int id) { if (id == TEGRA_POWERGATE_3D && pmc->soc->has_gpu_clamps) return (tegra_pmc_readl(pmc, GPU_RG_CNTRL) & 0x1) == 0; @@ -598,8 +613,9 @@ static int tegra20_powergate_set(struct tegra_pmc *pmc, unsigned int id, tegra_pmc_writel(pmc, PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); /* wait for PMC to execute the command */ - ret = readx_poll_timeout(tegra_powergate_state, id, status, - status == new_state, 1, 10); + ret = read_poll_timeout(tegra_powergate_state, status, + status == new_state, 1, 10, false, + pmc, id); } while (ret == -ETIMEDOUT && retries--); return ret; @@ -631,8 +647,9 @@ static int tegra114_powergate_set(struct tegra_pmc *pmc, unsigned int id, return err; /* wait for PMC to execute the command */ - err = readx_poll_timeout(tegra_powergate_state, id, status, - status == new_state, 10, 100000); + err = read_poll_timeout(tegra_powergate_state, status, + status == new_state, 10, 100000, false, + pmc, id); if (err) return err; @@ -655,7 +672,7 @@ static int tegra_powergate_set(struct tegra_pmc *pmc, unsigned int id, mutex_lock(&pmc->powergates_lock); - if (tegra_powergate_state(id) == new_state) { + if (tegra_powergate_state(pmc, id) == new_state) { mutex_unlock(&pmc->powergates_lock); return 0; } @@ -940,30 +957,122 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain) return err; } +static void tegra_pmc_put_device(void *data) +{ + struct tegra_pmc *pmc = data; + + put_device(pmc->dev); +} + +static const struct of_device_id tegra_pmc_match[]; + +static struct tegra_pmc *tegra_pmc_get(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct tegra_pmc *pmc; + + np = of_parse_phandle(dev->of_node, "nvidia,pmc", 0); + if (!np) { + struct device_node *parent = of_node_get(dev->of_node); + + while ((parent = of_get_next_parent(parent)) != NULL) { + np = of_find_matching_node(parent, tegra_pmc_match); + if (np) + break; + } + + of_node_put(parent); + + if (!np) + return ERR_PTR(-ENODEV); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + + pmc = platform_get_drvdata(pdev); + if (!pmc) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + return pmc; +} + /** - * tegra_powergate_power_on() - power on partition + * tegra_pmc_get() - find the PMC for a given device + * @dev: device for which to find the PMC + * + * Returns a pointer to the PMC on success or an ERR_PTR()-encoded error code + * otherwise. + */ +struct tegra_pmc *devm_tegra_pmc_get(struct device *dev) +{ + struct tegra_pmc *pmc; + int err; + + pmc = tegra_pmc_get(dev); + if (IS_ERR(pmc)) + return pmc; + + err = devm_add_action_or_reset(dev, tegra_pmc_put_device, pmc); + if (err < 0) + return ERR_PTR(err); + + return pmc; +} +EXPORT_SYMBOL(devm_tegra_pmc_get); + +/** + * tegra_pmc_powergate_power_on() - power on partition + * @pmc: power management controller * @id: partition ID */ -int tegra_powergate_power_on(unsigned int id) +int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id) { if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; return tegra_powergate_set(pmc, id, true); } +EXPORT_SYMBOL(tegra_pmc_powergate_power_on); + +/** + * tegra_powergate_power_on() - power on partition + * @id: partition ID + */ +int tegra_powergate_power_on(unsigned int id) +{ + return tegra_pmc_powergate_power_on(pmc, id); +} EXPORT_SYMBOL(tegra_powergate_power_on); /** - * tegra_powergate_power_off() - power off partition + * tegra_pmc_powergate_power_off() - power off partition + * @pmc: power management controller * @id: partition ID */ -int tegra_powergate_power_off(unsigned int id) +int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id) { if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; return tegra_powergate_set(pmc, id, false); } +EXPORT_SYMBOL(tegra_pmc_powergate_power_off); + +/** + * tegra_powergate_power_off() - power off partition + * @id: partition ID + */ +int tegra_powergate_power_off(unsigned int id) +{ + return tegra_pmc_powergate_power_off(pmc, id); +} EXPORT_SYMBOL(tegra_powergate_power_off); /** @@ -976,32 +1085,45 @@ static int tegra_powergate_is_powered(struct tegra_pmc *pmc, unsigned int id) if (!tegra_powergate_is_valid(pmc, id)) return -EINVAL; - return tegra_powergate_state(id); + return tegra_powergate_state(pmc, id); } /** - * tegra_powergate_remove_clamping() - remove power clamps for partition + * tegra_pmc_powergate_remove_clamping() - remove power clamps for partition + * @pmc: power management controller * @id: partition ID */ -int tegra_powergate_remove_clamping(unsigned int id) +int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id) { if (!tegra_powergate_is_available(pmc, id)) return -EINVAL; return __tegra_powergate_remove_clamping(pmc, id); } +EXPORT_SYMBOL(tegra_pmc_powergate_remove_clamping); + +/** + * tegra_powergate_remove_clamping() - remove power clamps for partition + * @id: partition ID + */ +int tegra_powergate_remove_clamping(unsigned int id) +{ + return tegra_pmc_powergate_remove_clamping(pmc, id); +} EXPORT_SYMBOL(tegra_powergate_remove_clamping); /** - * tegra_powergate_sequence_power_up() - power up partition + * tegra_pmc_powergate_sequence_power_up() - power up partition + * @pmc: power management controller * @id: partition ID * @clk: clock for partition * @rst: reset for partition * * Must be called with clk disabled, and returns with clk enabled. */ -int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, - struct reset_control *rst) +int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, + unsigned int id, struct clk *clk, + struct reset_control *rst) { struct tegra_powergate *pg; int err; @@ -1035,6 +1157,21 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, return err; } +EXPORT_SYMBOL(tegra_pmc_powergate_sequence_power_up); + +/** + * tegra_powergate_sequence_power_up() - power up partition + * @id: partition ID + * @clk: clock for partition + * @rst: reset for partition + * + * Must be called with clk disabled, and returns with clk enabled. + */ +int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, + struct reset_control *rst) +{ + return tegra_pmc_powergate_sequence_power_up(pmc, id, clk, rst); +} EXPORT_SYMBOL(tegra_powergate_sequence_power_up); /** @@ -1099,7 +1236,8 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) return tegra_powergate_remove_clamping(id); } -static void tegra_pmc_program_reboot_reason(const char *cmd) +static void tegra_pmc_program_reboot_reason(struct tegra_pmc *pmc, + const char *cmd) { u32 value; @@ -1123,17 +1261,15 @@ static void tegra_pmc_program_reboot_reason(const char *cmd) static int tegra_pmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data) { + struct tegra_pmc *pmc = container_of(this, struct tegra_pmc, + reboot_notifier); if (action == SYS_RESTART) - tegra_pmc_program_reboot_reason(data); + tegra_pmc_program_reboot_reason(pmc, data); return NOTIFY_DONE; } -static struct notifier_block tegra_pmc_reboot_notifier = { - .notifier_call = tegra_pmc_reboot_notify, -}; - -static void tegra_pmc_restart(void) +static void tegra_pmc_restart(struct tegra_pmc *pmc) { u32 value; @@ -1145,13 +1281,17 @@ static void tegra_pmc_restart(void) static int tegra_pmc_restart_handler(struct sys_off_data *data) { - tegra_pmc_restart(); + struct tegra_pmc *pmc = data->cb_data; + + tegra_pmc_restart(pmc); return NOTIFY_DONE; } static int tegra_pmc_power_off_handler(struct sys_off_data *data) { + struct tegra_pmc *pmc = data->cb_data; + /* * Reboot Nexus 7 into special bootloader mode if USB cable is * connected in order to display battery status and power off. @@ -1161,7 +1301,7 @@ static int tegra_pmc_power_off_handler(struct sys_off_data *data) const u32 go_to_charger_mode = 0xa5a55a5a; tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37); - tegra_pmc_restart(); + tegra_pmc_restart(pmc); } return NOTIFY_DONE; @@ -1169,6 +1309,7 @@ static int tegra_pmc_power_off_handler(struct sys_off_data *data) static int powergate_show(struct seq_file *s, void *data) { + struct tegra_pmc *pmc = data; unsigned int i; int status; @@ -1377,6 +1518,8 @@ static int tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd, unsigned int level) { + struct tegra_pmc_core_pd *pd = to_core_pd(genpd); + struct tegra_pmc *pmc = pd->pmc; struct dev_pm_opp *opp; int err; @@ -1404,30 +1547,31 @@ tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd, static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np) { - struct generic_pm_domain *genpd; const char *rname[] = { "core", NULL}; + struct tegra_pmc_core_pd *pd; int err; - genpd = devm_kzalloc(pmc->dev, sizeof(*genpd), GFP_KERNEL); - if (!genpd) + pd = devm_kzalloc(pmc->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) return -ENOMEM; - genpd->name = "core"; - genpd->flags = GENPD_FLAG_NO_SYNC_STATE; - genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state; + pd->genpd.name = "core"; + pd->genpd.flags = GENPD_FLAG_NO_SYNC_STATE; + pd->genpd.set_performance_state = tegra_pmc_core_pd_set_performance_state; + pd->pmc = pmc; err = devm_pm_opp_set_regulators(pmc->dev, rname); if (err) return dev_err_probe(pmc->dev, err, "failed to set core OPP regulator\n"); - err = pm_genpd_init(genpd, NULL, false); + err = pm_genpd_init(&pd->genpd, NULL, false); if (err) { dev_err(pmc->dev, "failed to init core genpd: %d\n", err); return err; } - err = of_genpd_add_provider_simple(np, genpd); + err = of_genpd_add_provider_simple(np, &pd->genpd); if (err) { dev_err(pmc->dev, "failed to add core genpd: %d\n", err); goto remove_genpd; @@ -1436,7 +1580,7 @@ static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np) return 0; remove_genpd: - pm_genpd_remove(genpd); + pm_genpd_remove(&pd->genpd); return err; } @@ -1499,7 +1643,7 @@ static void tegra_powergate_remove(struct generic_pm_domain *genpd) kfree(pg->clks); - set_bit(pg->id, pmc->powergates_available); + set_bit(pg->id, pg->pmc->powergates_available); kfree(pg); } @@ -1603,11 +1747,12 @@ static void tegra_io_pad_unprepare(struct tegra_pmc *pmc) /** * tegra_io_pad_power_enable() - enable power to I/O pad + * @pmc: power management controller * @id: Tegra I/O pad ID for which to enable power * * Returns: 0 on success or a negative error code on failure. */ -int tegra_io_pad_power_enable(enum tegra_io_pad id) +int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id) { const struct tegra_io_pad_soc *pad; unsigned long request, status; @@ -1642,15 +1787,28 @@ unlock: mutex_unlock(&pmc->powergates_lock); return err; } +EXPORT_SYMBOL(tegra_pmc_io_pad_power_enable); + +/** + * tegra_io_pad_power_enable() - enable power to I/O pad + * @id: Tegra I/O pad ID for which to enable power + * + * Returns: 0 on success or a negative error code on failure. + */ +int tegra_io_pad_power_enable(enum tegra_io_pad id) +{ + return tegra_pmc_io_pad_power_enable(pmc, id); +} EXPORT_SYMBOL(tegra_io_pad_power_enable); /** - * tegra_io_pad_power_disable() - disable power to I/O pad + * tegra_pmc_io_pad_power_disable() - disable power to I/O pad + * @pmc: power management controller * @id: Tegra I/O pad ID for which to disable power * * Returns: 0 on success or a negative error code on failure. */ -int tegra_io_pad_power_disable(enum tegra_io_pad id) +int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) { const struct tegra_io_pad_soc *pad; unsigned long request, status; @@ -1685,6 +1843,18 @@ unlock: mutex_unlock(&pmc->powergates_lock); return err; } +EXPORT_SYMBOL(tegra_pmc_io_pad_power_disable); + +/** + * tegra_io_pad_power_disable() - disable power to I/O pad + * @id: Tegra I/O pad ID for which to disable power + * + * Returns: 0 on success or a negative error code on failure. + */ +int tegra_io_pad_power_disable(enum tegra_io_pad id) +{ + return tegra_pmc_io_pad_power_disable(pmc, id); +} EXPORT_SYMBOL(tegra_io_pad_power_disable); static int tegra_io_pad_is_powered(struct tegra_pmc *pmc, enum tegra_io_pad id) @@ -1905,6 +2075,50 @@ static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np) return 0; } +/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */ +static void tegra186_pmc_wake_handler(struct irq_work *work) +{ + struct tegra_pmc *pmc = container_of(work, struct tegra_pmc, wake_work); + unsigned int i, wake; + + for (i = 0; i < pmc->soc->max_wake_vectors; i++) { + unsigned long status = pmc->wake_status[i]; + + for_each_set_bit(wake, &status, 32) { + irq_hw_number_t hwirq = wake + (i * 32); + struct irq_desc *desc; + unsigned int irq; + + irq = irq_find_mapping(pmc->domain, hwirq); + if (!irq) { + dev_warn(pmc->dev, + "No IRQ found for WAKE#%lu!\n", + hwirq); + continue; + } + + dev_dbg(pmc->dev, + "Resume caused by WAKE#%lu mapped to IRQ#%u\n", + hwirq, irq); + + desc = irq_to_desc(irq); + if (!desc) { + dev_warn(pmc->dev, + "No descriptor found for IRQ#%u\n", + irq); + continue; + } + + if (!desc->action || !desc->action->name) + continue; + + generic_handle_irq(irq); + } + + pmc->wake_status[i] = 0; + } +} + static int tegra_pmc_init(struct tegra_pmc *pmc) { if (pmc->soc->max_wake_events > 0) { @@ -1923,6 +2137,18 @@ static int tegra_pmc_init(struct tegra_pmc *pmc) pmc->wake_cntrl_level_map = bitmap_zalloc(pmc->soc->max_wake_events, GFP_KERNEL); if (!pmc->wake_cntrl_level_map) return -ENOMEM; + + pmc->wake_status = kcalloc(pmc->soc->max_wake_vectors, sizeof(u32), GFP_KERNEL); + if (!pmc->wake_status) + return -ENOMEM; + + /* + * Initialize IRQ work for processing wake IRQs. Must use + * HARD_IRQ variant to run in hard IRQ context on PREEMPT_RT + * because we call generic_handle_irq() which requires hard + * IRQ context. + */ + pmc->wake_work = IRQ_WORK_INIT_HARD(tegra186_pmc_wake_handler); } if (pmc->soc->init) @@ -2104,9 +2330,9 @@ static int tegra_io_pad_pinconf_set(struct pinctrl_dev *pctl_dev, switch (param) { case PIN_CONFIG_MODE_LOW_POWER: if (arg) - err = tegra_io_pad_power_disable(pad->id); + err = tegra_pmc_io_pad_power_disable(pmc, pad->id); else - err = tegra_io_pad_power_enable(pad->id); + err = tegra_pmc_io_pad_power_enable(pmc, pad->id); if (err) return err; break; @@ -2163,6 +2389,7 @@ static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc) static ssize_t reset_reason_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct tegra_pmc *pmc = dev_get_drvdata(dev); u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); @@ -2180,6 +2407,7 @@ static DEVICE_ATTR_RO(reset_reason); static ssize_t reset_level_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct tegra_pmc *pmc = dev_get_drvdata(dev); u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); @@ -2543,7 +2771,7 @@ static int tegra_pmc_clk_notify_cb(struct notifier_block *nb, return NOTIFY_OK; } -static void pmc_clk_fence_udelay(u32 offset) +static void pmc_clk_fence_udelay(struct tegra_pmc *pmc, u32 offset) { tegra_pmc_readl(pmc, offset); /* pmc clk propagation delay 2 us */ @@ -2555,7 +2783,7 @@ static u8 pmc_clk_mux_get_parent(struct clk_hw *hw) struct pmc_clk *clk = to_pmc_clk(hw); u32 val; - val = tegra_pmc_readl(pmc, clk->offs) >> clk->mux_shift; + val = tegra_pmc_readl(clk->pmc, clk->offs) >> clk->mux_shift; val &= PMC_CLK_OUT_MUX_MASK; return val; @@ -2566,11 +2794,11 @@ static int pmc_clk_mux_set_parent(struct clk_hw *hw, u8 index) struct pmc_clk *clk = to_pmc_clk(hw); u32 val; - val = tegra_pmc_readl(pmc, clk->offs); + val = tegra_pmc_readl(clk->pmc, clk->offs); val &= ~(PMC_CLK_OUT_MUX_MASK << clk->mux_shift); val |= index << clk->mux_shift; - tegra_pmc_writel(pmc, val, clk->offs); - pmc_clk_fence_udelay(clk->offs); + tegra_pmc_writel(clk->pmc, val, clk->offs); + pmc_clk_fence_udelay(clk->pmc, clk->offs); return 0; } @@ -2580,26 +2808,27 @@ static int pmc_clk_is_enabled(struct clk_hw *hw) struct pmc_clk *clk = to_pmc_clk(hw); u32 val; - val = tegra_pmc_readl(pmc, clk->offs) & BIT(clk->force_en_shift); + val = tegra_pmc_readl(clk->pmc, clk->offs) & BIT(clk->force_en_shift); return val ? 1 : 0; } -static void pmc_clk_set_state(unsigned long offs, u32 shift, int state) +static void pmc_clk_set_state(struct tegra_pmc *pmc, unsigned long offs, + u32 shift, int state) { u32 val; val = tegra_pmc_readl(pmc, offs); val = state ? (val | BIT(shift)) : (val & ~BIT(shift)); tegra_pmc_writel(pmc, val, offs); - pmc_clk_fence_udelay(offs); + pmc_clk_fence_udelay(pmc, offs); } static int pmc_clk_enable(struct clk_hw *hw) { struct pmc_clk *clk = to_pmc_clk(hw); - pmc_clk_set_state(clk->offs, clk->force_en_shift, 1); + pmc_clk_set_state(clk->pmc, clk->offs, clk->force_en_shift, 1); return 0; } @@ -2608,7 +2837,7 @@ static void pmc_clk_disable(struct clk_hw *hw) { struct pmc_clk *clk = to_pmc_clk(hw); - pmc_clk_set_state(clk->offs, clk->force_en_shift, 0); + pmc_clk_set_state(clk->pmc, clk->offs, clk->force_en_shift, 0); } static const struct clk_ops pmc_clk_ops = { @@ -2640,6 +2869,7 @@ tegra_pmc_clk_out_register(struct tegra_pmc *pmc, CLK_SET_PARENT_GATE; pmc_clk->hw.init = &init; + pmc_clk->pmc = pmc; pmc_clk->offs = offset; pmc_clk->mux_shift = data->mux_shift; pmc_clk->force_en_shift = data->force_en_shift; @@ -2650,15 +2880,16 @@ tegra_pmc_clk_out_register(struct tegra_pmc *pmc, static int pmc_clk_gate_is_enabled(struct clk_hw *hw) { struct pmc_clk_gate *gate = to_pmc_clk_gate(hw); + u32 value = tegra_pmc_readl(gate->pmc, gate->offs); - return tegra_pmc_readl(pmc, gate->offs) & BIT(gate->shift) ? 1 : 0; + return value & BIT(gate->shift) ? 1 : 0; } static int pmc_clk_gate_enable(struct clk_hw *hw) { struct pmc_clk_gate *gate = to_pmc_clk_gate(hw); - pmc_clk_set_state(gate->offs, gate->shift, 1); + pmc_clk_set_state(gate->pmc, gate->offs, gate->shift, 1); return 0; } @@ -2667,7 +2898,7 @@ static void pmc_clk_gate_disable(struct clk_hw *hw) { struct pmc_clk_gate *gate = to_pmc_clk_gate(hw); - pmc_clk_set_state(gate->offs, gate->shift, 0); + pmc_clk_set_state(gate->pmc, gate->offs, gate->shift, 0); } static const struct clk_ops pmc_clk_gate_ops = { @@ -2695,6 +2926,7 @@ tegra_pmc_clk_gate_register(struct tegra_pmc *pmc, const char *name, init.flags = 0; gate->hw.init = &init; + gate->pmc = pmc; gate->offs = offset; gate->shift = shift; @@ -2858,6 +3090,8 @@ static int tegra_pmc_regmap_init(struct tegra_pmc *pmc) static void tegra_pmc_reset_suspend_mode(void *data) { + struct tegra_pmc *pmc = data; + pmc->suspend_mode = TEGRA_SUSPEND_NOT_READY; } @@ -2880,7 +3114,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) return err; err = devm_add_action_or_reset(&pdev->dev, tegra_pmc_reset_suspend_mode, - NULL); + pmc); if (err) return err; @@ -2931,8 +3165,10 @@ static int tegra_pmc_probe(struct platform_device *pdev) * CPU without resetting everything else. */ if (pmc->scratch) { + pmc->reboot_notifier.notifier_call = tegra_pmc_reboot_notify; + err = devm_register_reboot_notifier(&pdev->dev, - &tegra_pmc_reboot_notifier); + &pmc->reboot_notifier); if (err) { dev_err(&pdev->dev, "unable to register reboot notifier, %d\n", @@ -2944,7 +3180,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) err = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_LOW, - tegra_pmc_restart_handler, NULL); + tegra_pmc_restart_handler, + pmc); if (err) { dev_err(&pdev->dev, "failed to register sys-off handler: %d\n", err); @@ -2958,7 +3195,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) err = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_FIRMWARE, - tegra_pmc_power_off_handler, NULL); + tegra_pmc_power_off_handler, + pmc); if (err) { dev_err(&pdev->dev, "failed to register sys-off handler: %d\n", err); @@ -3024,7 +3262,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (pmc->soc->set_wake_filters) pmc->soc->set_wake_filters(pmc); - debugfs_create_file("powergate", 0444, NULL, NULL, &powergate_fops); + debugfs_create_file("powergate", 0444, NULL, pmc, &powergate_fops); return 0; @@ -3129,47 +3367,33 @@ static void wke_clear_wake_status(struct tegra_pmc *pmc) } } -/* translate sc7 wake sources back into IRQs to catch edge triggered wakeups */ -static void tegra186_pmc_process_wake_events(struct tegra_pmc *pmc, unsigned int index, - unsigned long status) -{ - unsigned int wake; - - dev_dbg(pmc->dev, "Wake[%d:%d] status=%#lx\n", (index * 32) + 31, index * 32, status); - - for_each_set_bit(wake, &status, 32) { - irq_hw_number_t hwirq = wake + 32 * index; - struct irq_desc *desc; - unsigned int irq; - - irq = irq_find_mapping(pmc->domain, hwirq); - - desc = irq_to_desc(irq); - if (!desc || !desc->action || !desc->action->name) { - dev_dbg(pmc->dev, "Resume caused by WAKE%ld, IRQ %d\n", hwirq, irq); - continue; - } - - dev_dbg(pmc->dev, "Resume caused by WAKE%ld, %s\n", hwirq, desc->action->name); - generic_handle_irq(irq); - } -} - static void tegra186_pmc_wake_syscore_resume(void *data) { - u32 status, mask; + struct tegra_pmc *pmc = data; unsigned int i; + u32 mask; for (i = 0; i < pmc->soc->max_wake_vectors; i++) { mask = readl(pmc->wake + WAKE_AOWAKE_TIER2_ROUTING(i)); - status = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask; - - tegra186_pmc_process_wake_events(pmc, i, status); + pmc->wake_status[i] = readl(pmc->wake + WAKE_AOWAKE_STATUS_R(i)) & mask; } + + /* Schedule IRQ work to process wake IRQs (if any) */ + irq_work_queue(&pmc->wake_work); } static int tegra186_pmc_wake_syscore_suspend(void *data) { + struct tegra_pmc *pmc = data; + unsigned int i; + + /* Check if there are unhandled wake IRQs */ + for (i = 0; i < pmc->soc->max_wake_vectors; i++) + if (pmc->wake_status[i]) + dev_warn(pmc->dev, + "Unhandled wake IRQs pending vector[%u]: 0x%x\n", + i, pmc->wake_status[i]); + wke_read_sw_wake_status(pmc); /* flip the wakeup trigger for dual-edge triggered pads @@ -3843,6 +4067,7 @@ static const struct tegra_pmc_regs tegra186_pmc_regs = { static void tegra186_pmc_init(struct tegra_pmc *pmc) { pmc->syscore.ops = &tegra186_pmc_wake_syscore_ops; + pmc->syscore.data = pmc; register_syscore(&pmc->syscore); } diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index c545875d0ff1..1fd21be02577 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -16,6 +16,7 @@ struct clk; struct reset_control; +struct tegra_pmc; bool tegra_pmc_cpu_is_powered(unsigned int cpuid); int tegra_pmc_cpu_power_on(unsigned int cpuid); @@ -149,11 +150,24 @@ enum tegra_io_pad { }; #ifdef CONFIG_SOC_TEGRA_PMC +struct tegra_pmc *devm_tegra_pmc_get(struct device *dev); + +int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id); +int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id); +int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id); + +/* Must be called with clk disabled, and returns with clk enabled */ +int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, + unsigned int id, struct clk *clk, + struct reset_control *rst); +int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id); +int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id); + +/* legacy */ int tegra_powergate_power_on(unsigned int id); int tegra_powergate_power_off(unsigned int id); int tegra_powergate_remove_clamping(unsigned int id); -/* Must be called with clk disabled, and returns with clk enabled */ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, struct reset_control *rst); @@ -166,6 +180,50 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); bool tegra_pmc_core_domain_state_synced(void); #else +static inline struct tegra_pmc *devm_tegra_pmc_get(struct device *dev) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int +tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id) +{ + return -ENOSYS; +} + +static inline int +tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id) +{ + return -ENOSYS; +} + +static inline int +tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id) +{ + return -ENOSYS; +} + +/* Must be called with clk disabled, and returns with clk enabled */ +static inline int +tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, unsigned int id, + struct clk *clk, + struct reset_control *rst) +{ + return -ENOSYS; +} + +static inline int +tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id) +{ + return -ENOSYS; +} + +static inline int +tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) +{ + return -ENOSYS; +} + static inline int tegra_powergate_power_on(unsigned int id) { return -ENOSYS; |
