/* * Copyright (C) 2012,2013 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #define PMC_CTRL 0x0 #define PMC_CTRL_INTR_LOW (1 << 17) #define PMC_PWRGATE_TOGGLE 0x30 #define PMC_PWRGATE_TOGGLE_START (1 << 8) #define PMC_REMOVE_CLAMPING 0x34 #define PMC_PWRGATE_STATUS 0x38 #define TEGRA_POWERGATE_PCIE 3 #define TEGRA_POWERGATE_VDEC 4 #define TEGRA_POWERGATE_CPU1 9 #define TEGRA_POWERGATE_CPU2 10 #define TEGRA_POWERGATE_CPU3 11 static u8 tegra_cpu_domains[] = { 0xFF, /* not available for CPU0 */ TEGRA_POWERGATE_CPU1, TEGRA_POWERGATE_CPU2, TEGRA_POWERGATE_CPU3, }; static DEFINE_SPINLOCK(tegra_powergate_lock); static void __iomem *tegra_pmc_base; static bool tegra_pmc_invert_interrupt; static inline u32 tegra_pmc_readl(u32 reg) { return readl(tegra_pmc_base + reg); } static inline void tegra_pmc_writel(u32 val, u32 reg) { writel(val, tegra_pmc_base + reg); } static int tegra_pmc_get_cpu_powerdomain_id(int cpuid) { if (cpuid <= 0 || cpuid >= num_possible_cpus()) return -EINVAL; return tegra_cpu_domains[cpuid]; } static bool tegra_pmc_powergate_is_powered(int id) { return (tegra_pmc_readl(PMC_PWRGATE_STATUS) >> id) & 1; } static int tegra_pmc_powergate_set(int id, bool new_state) { bool old_state; unsigned long flags; spin_lock_irqsave(&tegra_powergate_lock, flags); old_state = tegra_pmc_powergate_is_powered(id); WARN_ON(old_state == new_state); tegra_pmc_writel(PMC_PWRGATE_TOGGLE_START | id, PMC_PWRGATE_TOGGLE); spin_unlock_irqrestore(&tegra_powergate_lock, flags); return 0; } static int tegra_pmc_powergate_remove_clamping(int id) { u32 mask; /* * Tegra has a bug where PCIE and VDE clamping masks are * swapped relatively to the partition ids. */ if (id == TEGRA_POWERGATE_VDEC) mask = (1 << TEGRA_POWERGATE_PCIE); else if (id == TEGRA_POWERGATE_PCIE) mask = (1 << TEGRA_POWERGATE_VDEC); else mask = (1 << id); tegra_pmc_writel(mask, PMC_REMOVE_CLAMPING); return 0; } bool tegra_pmc_cpu_is_powered(int cpuid) { int id; id = tegra_pmc_get_cpu_powerdomain_id(cpuid); if (id < 0) return false; return tegra_pmc_powergate_is_powered(id); } int tegra_pmc_cpu_power_on(int cpuid) { int id; id = tegra_pmc_get_cpu_powerdomain_id(cpuid); if (id < 0) return id; return tegra_pmc_powergate_set(id, true); } int tegra_pmc_cpu_remove_clamping(int cpuid) { int id; id = tegra_pmc_get_cpu_powerdomain_id(cpuid); if (id < 0) return id; return tegra_pmc_powergate_remove_clamping(id); } static const struct of_device_id matches[] __initconst = { { .compatible = "nvidia,tegra114-pmc" }, { .compatible = "nvidia,tegra30-pmc" }, { .compatible = "nvidia,tegra20-pmc" }, { } }; static void tegra_pmc_parse_dt(void) { struct device_node *np; np = of_find_matching_node(NULL, matches); BUG_ON(!np); tegra_pmc_base = of_iomap(np, 0); tegra_pmc_invert_interrupt = of_property_read_bool(np, "nvidia,invert-interrupt"); } void __init tegra_pmc_init(void) { u32 val; tegra_pmc_parse_dt(); val = tegra_pmc_readl(PMC_CTRL); if (tegra_pmc_invert_interrupt) val |= PMC_CTRL_INTR_LOW; else val &= ~PMC_CTRL_INTR_LOW; tegra_pmc_writel(val, PMC_CTRL); }