You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
957 lines
22 KiB
957 lines
22 KiB
/* |
|
* drivers/soc/tegra/pmc.c |
|
* |
|
* Copyright (c) 2010 Google, Inc |
|
* |
|
* Author: |
|
* Colin Cross <ccross@google.com> |
|
* |
|
* This software is licensed under the terms of the GNU General Public |
|
* License version 2, as published by the Free Software Foundation, and |
|
* may be copied, distributed, and modified under those terms. |
|
* |
|
* 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/kernel.h> |
|
#include <linux/clk.h> |
|
#include <linux/clk/tegra.h> |
|
#include <linux/debugfs.h> |
|
#include <linux/delay.h> |
|
#include <linux/err.h> |
|
#include <linux/export.h> |
|
#include <linux/init.h> |
|
#include <linux/io.h> |
|
#include <linux/of.h> |
|
#include <linux/of_address.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/reboot.h> |
|
#include <linux/reset.h> |
|
#include <linux/seq_file.h> |
|
#include <linux/spinlock.h> |
|
|
|
#include <soc/tegra/common.h> |
|
#include <soc/tegra/fuse.h> |
|
#include <soc/tegra/pmc.h> |
|
|
|
#define PMC_CNTRL 0x0 |
|
#define PMC_CNTRL_SYSCLK_POLARITY (1 << 10) /* sys clk polarity */ |
|
#define PMC_CNTRL_SYSCLK_OE (1 << 11) /* system clock enable */ |
|
#define PMC_CNTRL_SIDE_EFFECT_LP0 (1 << 14) /* LP0 when CPU pwr gated */ |
|
#define PMC_CNTRL_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */ |
|
#define PMC_CNTRL_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ |
|
#define PMC_CNTRL_INTR_POLARITY (1 << 17) /* inverts INTR polarity */ |
|
|
|
#define DPD_SAMPLE 0x020 |
|
#define DPD_SAMPLE_ENABLE (1 << 0) |
|
#define DPD_SAMPLE_DISABLE (0 << 0) |
|
|
|
#define PWRGATE_TOGGLE 0x30 |
|
#define PWRGATE_TOGGLE_START (1 << 8) |
|
|
|
#define REMOVE_CLAMPING 0x34 |
|
|
|
#define PWRGATE_STATUS 0x38 |
|
|
|
#define PMC_SCRATCH0 0x50 |
|
#define PMC_SCRATCH0_MODE_RECOVERY (1 << 31) |
|
#define PMC_SCRATCH0_MODE_BOOTLOADER (1 << 30) |
|
#define PMC_SCRATCH0_MODE_RCM (1 << 1) |
|
#define PMC_SCRATCH0_MODE_MASK (PMC_SCRATCH0_MODE_RECOVERY | \ |
|
PMC_SCRATCH0_MODE_BOOTLOADER | \ |
|
PMC_SCRATCH0_MODE_RCM) |
|
|
|
#define PMC_CPUPWRGOOD_TIMER 0xc8 |
|
#define PMC_CPUPWROFF_TIMER 0xcc |
|
|
|
#define PMC_SCRATCH41 0x140 |
|
|
|
#define IO_DPD_REQ 0x1b8 |
|
#define IO_DPD_REQ_CODE_IDLE (0 << 30) |
|
#define IO_DPD_REQ_CODE_OFF (1 << 30) |
|
#define IO_DPD_REQ_CODE_ON (2 << 30) |
|
#define IO_DPD_REQ_CODE_MASK (3 << 30) |
|
|
|
#define IO_DPD_STATUS 0x1bc |
|
#define IO_DPD2_REQ 0x1c0 |
|
#define IO_DPD2_STATUS 0x1c4 |
|
#define SEL_DPD_TIM 0x1c8 |
|
|
|
#define GPU_RG_CNTRL 0x2d4 |
|
|
|
struct tegra_pmc_soc { |
|
unsigned int num_powergates; |
|
const char *const *powergates; |
|
unsigned int num_cpu_powergates; |
|
const u8 *cpu_powergates; |
|
}; |
|
|
|
/** |
|
* struct tegra_pmc - NVIDIA Tegra PMC |
|
* @base: pointer to I/O remapped register region |
|
* @clk: pointer to pclk clock |
|
* @rate: currently configured rate of pclk |
|
* @suspend_mode: lowest suspend mode available |
|
* @cpu_good_time: CPU power good time (in microseconds) |
|
* @cpu_off_time: CPU power off time (in microsecends) |
|
* @core_osc_time: core power good OSC time (in microseconds) |
|
* @core_pmu_time: core power good PMU time (in microseconds) |
|
* @core_off_time: core power off time (in microseconds) |
|
* @corereq_high: core power request is active-high |
|
* @sysclkreq_high: system clock request is active-high |
|
* @combined_req: combined power request for CPU & core |
|
* @cpu_pwr_good_en: CPU power good signal is enabled |
|
* @lp0_vec_phys: physical base address of the LP0 warm boot code |
|
* @lp0_vec_size: size of the LP0 warm boot code |
|
* @powergates_lock: mutex for power gate register access |
|
*/ |
|
struct tegra_pmc { |
|
void __iomem *base; |
|
struct clk *clk; |
|
|
|
const struct tegra_pmc_soc *soc; |
|
|
|
unsigned long rate; |
|
|
|
enum tegra_suspend_mode suspend_mode; |
|
u32 cpu_good_time; |
|
u32 cpu_off_time; |
|
u32 core_osc_time; |
|
u32 core_pmu_time; |
|
u32 core_off_time; |
|
bool corereq_high; |
|
bool sysclkreq_high; |
|
bool combined_req; |
|
bool cpu_pwr_good_en; |
|
u32 lp0_vec_phys; |
|
u32 lp0_vec_size; |
|
|
|
struct mutex powergates_lock; |
|
}; |
|
|
|
static struct tegra_pmc *pmc = &(struct tegra_pmc) { |
|
.base = NULL, |
|
.suspend_mode = TEGRA_SUSPEND_NONE, |
|
}; |
|
|
|
static u32 tegra_pmc_readl(unsigned long offset) |
|
{ |
|
return readl(pmc->base + offset); |
|
} |
|
|
|
static void tegra_pmc_writel(u32 value, unsigned long offset) |
|
{ |
|
writel(value, pmc->base + offset); |
|
} |
|
|
|
/** |
|
* tegra_powergate_set() - set the state of a partition |
|
* @id: partition ID |
|
* @new_state: new state of the partition |
|
*/ |
|
static int tegra_powergate_set(int id, bool new_state) |
|
{ |
|
bool status; |
|
|
|
mutex_lock(&pmc->powergates_lock); |
|
|
|
status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id); |
|
|
|
if (status == new_state) { |
|
mutex_unlock(&pmc->powergates_lock); |
|
return 0; |
|
} |
|
|
|
tegra_pmc_writel(PWRGATE_TOGGLE_START | id, PWRGATE_TOGGLE); |
|
|
|
mutex_unlock(&pmc->powergates_lock); |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* tegra_powergate_power_on() - power on partition |
|
* @id: partition ID |
|
*/ |
|
int tegra_powergate_power_on(int id) |
|
{ |
|
if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) |
|
return -EINVAL; |
|
|
|
return tegra_powergate_set(id, true); |
|
} |
|
|
|
/** |
|
* tegra_powergate_power_off() - power off partition |
|
* @id: partition ID |
|
*/ |
|
int tegra_powergate_power_off(int id) |
|
{ |
|
if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) |
|
return -EINVAL; |
|
|
|
return tegra_powergate_set(id, false); |
|
} |
|
EXPORT_SYMBOL(tegra_powergate_power_off); |
|
|
|
/** |
|
* tegra_powergate_is_powered() - check if partition is powered |
|
* @id: partition ID |
|
*/ |
|
int tegra_powergate_is_powered(int id) |
|
{ |
|
u32 status; |
|
|
|
if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) |
|
return -EINVAL; |
|
|
|
status = tegra_pmc_readl(PWRGATE_STATUS) & (1 << id); |
|
return !!status; |
|
} |
|
|
|
/** |
|
* tegra_powergate_remove_clamping() - remove power clamps for partition |
|
* @id: partition ID |
|
*/ |
|
int tegra_powergate_remove_clamping(int id) |
|
{ |
|
u32 mask; |
|
|
|
if (!pmc->soc || id < 0 || id >= pmc->soc->num_powergates) |
|
return -EINVAL; |
|
|
|
/* |
|
* The Tegra124 GPU has a separate register (with different semantics) |
|
* to remove clamps. |
|
*/ |
|
if (tegra_get_chip_id() == TEGRA124) { |
|
if (id == TEGRA_POWERGATE_3D) { |
|
tegra_pmc_writel(0, GPU_RG_CNTRL); |
|
return 0; |
|
} |
|
} |
|
|
|
/* |
|
* Tegra 2 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, REMOVE_CLAMPING); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(tegra_powergate_remove_clamping); |
|
|
|
/** |
|
* 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(int id, struct clk *clk, |
|
struct reset_control *rst) |
|
{ |
|
int ret; |
|
|
|
reset_control_assert(rst); |
|
|
|
ret = tegra_powergate_power_on(id); |
|
if (ret) |
|
goto err_power; |
|
|
|
ret = clk_prepare_enable(clk); |
|
if (ret) |
|
goto err_clk; |
|
|
|
usleep_range(10, 20); |
|
|
|
ret = tegra_powergate_remove_clamping(id); |
|
if (ret) |
|
goto err_clamp; |
|
|
|
usleep_range(10, 20); |
|
reset_control_deassert(rst); |
|
|
|
return 0; |
|
|
|
err_clamp: |
|
clk_disable_unprepare(clk); |
|
err_clk: |
|
tegra_powergate_power_off(id); |
|
err_power: |
|
return ret; |
|
} |
|
EXPORT_SYMBOL(tegra_powergate_sequence_power_up); |
|
|
|
#ifdef CONFIG_SMP |
|
/** |
|
* tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID |
|
* @cpuid: CPU partition ID |
|
* |
|
* Returns the partition ID corresponding to the CPU partition ID or a |
|
* negative error code on failure. |
|
*/ |
|
static int tegra_get_cpu_powergate_id(int cpuid) |
|
{ |
|
if (pmc->soc && cpuid > 0 && cpuid < pmc->soc->num_cpu_powergates) |
|
return pmc->soc->cpu_powergates[cpuid]; |
|
|
|
return -EINVAL; |
|
} |
|
|
|
/** |
|
* tegra_pmc_cpu_is_powered() - check if CPU partition is powered |
|
* @cpuid: CPU partition ID |
|
*/ |
|
bool tegra_pmc_cpu_is_powered(int cpuid) |
|
{ |
|
int id; |
|
|
|
id = tegra_get_cpu_powergate_id(cpuid); |
|
if (id < 0) |
|
return false; |
|
|
|
return tegra_powergate_is_powered(id); |
|
} |
|
|
|
/** |
|
* tegra_pmc_cpu_power_on() - power on CPU partition |
|
* @cpuid: CPU partition ID |
|
*/ |
|
int tegra_pmc_cpu_power_on(int cpuid) |
|
{ |
|
int id; |
|
|
|
id = tegra_get_cpu_powergate_id(cpuid); |
|
if (id < 0) |
|
return id; |
|
|
|
return tegra_powergate_set(id, true); |
|
} |
|
|
|
/** |
|
* tegra_pmc_cpu_remove_clamping() - remove power clamps for CPU partition |
|
* @cpuid: CPU partition ID |
|
*/ |
|
int tegra_pmc_cpu_remove_clamping(int cpuid) |
|
{ |
|
int id; |
|
|
|
id = tegra_get_cpu_powergate_id(cpuid); |
|
if (id < 0) |
|
return id; |
|
|
|
return tegra_powergate_remove_clamping(id); |
|
} |
|
#endif /* CONFIG_SMP */ |
|
|
|
/** |
|
* tegra_pmc_restart() - reboot the system |
|
* @mode: which mode to reboot in |
|
* @cmd: reboot command |
|
*/ |
|
void tegra_pmc_restart(enum reboot_mode mode, const char *cmd) |
|
{ |
|
u32 value; |
|
|
|
value = tegra_pmc_readl(PMC_SCRATCH0); |
|
value &= ~PMC_SCRATCH0_MODE_MASK; |
|
|
|
if (cmd) { |
|
if (strcmp(cmd, "recovery") == 0) |
|
value |= PMC_SCRATCH0_MODE_RECOVERY; |
|
|
|
if (strcmp(cmd, "bootloader") == 0) |
|
value |= PMC_SCRATCH0_MODE_BOOTLOADER; |
|
|
|
if (strcmp(cmd, "forced-recovery") == 0) |
|
value |= PMC_SCRATCH0_MODE_RCM; |
|
} |
|
|
|
tegra_pmc_writel(value, PMC_SCRATCH0); |
|
|
|
value = tegra_pmc_readl(0); |
|
value |= 0x10; |
|
tegra_pmc_writel(value, 0); |
|
} |
|
|
|
static int powergate_show(struct seq_file *s, void *data) |
|
{ |
|
unsigned int i; |
|
|
|
seq_printf(s, " powergate powered\n"); |
|
seq_printf(s, "------------------\n"); |
|
|
|
for (i = 0; i < pmc->soc->num_powergates; i++) { |
|
if (!pmc->soc->powergates[i]) |
|
continue; |
|
|
|
seq_printf(s, " %9s %7s\n", pmc->soc->powergates[i], |
|
tegra_powergate_is_powered(i) ? "yes" : "no"); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int powergate_open(struct inode *inode, struct file *file) |
|
{ |
|
return single_open(file, powergate_show, inode->i_private); |
|
} |
|
|
|
static const struct file_operations powergate_fops = { |
|
.open = powergate_open, |
|
.read = seq_read, |
|
.llseek = seq_lseek, |
|
.release = single_release, |
|
}; |
|
|
|
static int tegra_powergate_debugfs_init(void) |
|
{ |
|
struct dentry *d; |
|
|
|
d = debugfs_create_file("powergate", S_IRUGO, NULL, NULL, |
|
&powergate_fops); |
|
if (!d) |
|
return -ENOMEM; |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra_io_rail_prepare(int id, unsigned long *request, |
|
unsigned long *status, unsigned int *bit) |
|
{ |
|
unsigned long rate, value; |
|
struct clk *clk; |
|
|
|
*bit = id % 32; |
|
|
|
/* |
|
* There are two sets of 30 bits to select IO rails, but bits 30 and |
|
* 31 are control bits rather than IO rail selection bits. |
|
*/ |
|
if (id > 63 || *bit == 30 || *bit == 31) |
|
return -EINVAL; |
|
|
|
if (id < 32) { |
|
*status = IO_DPD_STATUS; |
|
*request = IO_DPD_REQ; |
|
} else { |
|
*status = IO_DPD2_STATUS; |
|
*request = IO_DPD2_REQ; |
|
} |
|
|
|
clk = clk_get_sys(NULL, "pclk"); |
|
if (IS_ERR(clk)) |
|
return PTR_ERR(clk); |
|
|
|
rate = clk_get_rate(clk); |
|
clk_put(clk); |
|
|
|
tegra_pmc_writel(DPD_SAMPLE_ENABLE, DPD_SAMPLE); |
|
|
|
/* must be at least 200 ns, in APB (PCLK) clock cycles */ |
|
value = DIV_ROUND_UP(1000000000, rate); |
|
value = DIV_ROUND_UP(200, value); |
|
tegra_pmc_writel(value, SEL_DPD_TIM); |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra_io_rail_poll(unsigned long offset, unsigned long mask, |
|
unsigned long val, unsigned long timeout) |
|
{ |
|
unsigned long value; |
|
|
|
timeout = jiffies + msecs_to_jiffies(timeout); |
|
|
|
while (time_after(timeout, jiffies)) { |
|
value = tegra_pmc_readl(offset); |
|
if ((value & mask) == val) |
|
return 0; |
|
|
|
usleep_range(250, 1000); |
|
} |
|
|
|
return -ETIMEDOUT; |
|
} |
|
|
|
static void tegra_io_rail_unprepare(void) |
|
{ |
|
tegra_pmc_writel(DPD_SAMPLE_DISABLE, DPD_SAMPLE); |
|
} |
|
|
|
int tegra_io_rail_power_on(int id) |
|
{ |
|
unsigned long request, status, value; |
|
unsigned int bit, mask; |
|
int err; |
|
|
|
err = tegra_io_rail_prepare(id, &request, &status, &bit); |
|
if (err < 0) |
|
return err; |
|
|
|
mask = 1 << bit; |
|
|
|
value = tegra_pmc_readl(request); |
|
value |= mask; |
|
value &= ~IO_DPD_REQ_CODE_MASK; |
|
value |= IO_DPD_REQ_CODE_OFF; |
|
tegra_pmc_writel(value, request); |
|
|
|
err = tegra_io_rail_poll(status, mask, 0, 250); |
|
if (err < 0) |
|
return err; |
|
|
|
tegra_io_rail_unprepare(); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(tegra_io_rail_power_on); |
|
|
|
int tegra_io_rail_power_off(int id) |
|
{ |
|
unsigned long request, status, value; |
|
unsigned int bit, mask; |
|
int err; |
|
|
|
err = tegra_io_rail_prepare(id, &request, &status, &bit); |
|
if (err < 0) |
|
return err; |
|
|
|
mask = 1 << bit; |
|
|
|
value = tegra_pmc_readl(request); |
|
value |= mask; |
|
value &= ~IO_DPD_REQ_CODE_MASK; |
|
value |= IO_DPD_REQ_CODE_ON; |
|
tegra_pmc_writel(value, request); |
|
|
|
err = tegra_io_rail_poll(status, mask, mask, 250); |
|
if (err < 0) |
|
return err; |
|
|
|
tegra_io_rail_unprepare(); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(tegra_io_rail_power_off); |
|
|
|
#ifdef CONFIG_PM_SLEEP |
|
enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) |
|
{ |
|
return pmc->suspend_mode; |
|
} |
|
|
|
void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) |
|
{ |
|
if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE) |
|
return; |
|
|
|
pmc->suspend_mode = mode; |
|
} |
|
|
|
void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) |
|
{ |
|
unsigned long long rate = 0; |
|
u32 value; |
|
|
|
switch (mode) { |
|
case TEGRA_SUSPEND_LP1: |
|
rate = 32768; |
|
break; |
|
|
|
case TEGRA_SUSPEND_LP2: |
|
rate = clk_get_rate(pmc->clk); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
if (WARN_ON_ONCE(rate == 0)) |
|
rate = 100000000; |
|
|
|
if (rate != pmc->rate) { |
|
u64 ticks; |
|
|
|
ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; |
|
do_div(ticks, USEC_PER_SEC); |
|
tegra_pmc_writel(ticks, PMC_CPUPWRGOOD_TIMER); |
|
|
|
ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; |
|
do_div(ticks, USEC_PER_SEC); |
|
tegra_pmc_writel(ticks, PMC_CPUPWROFF_TIMER); |
|
|
|
wmb(); |
|
|
|
pmc->rate = rate; |
|
} |
|
|
|
value = tegra_pmc_readl(PMC_CNTRL); |
|
value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; |
|
value |= PMC_CNTRL_CPU_PWRREQ_OE; |
|
tegra_pmc_writel(value, PMC_CNTRL); |
|
} |
|
#endif |
|
|
|
static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np) |
|
{ |
|
u32 value, values[2]; |
|
|
|
if (of_property_read_u32(np, "nvidia,suspend-mode", &value)) { |
|
} else { |
|
switch (value) { |
|
case 0: |
|
pmc->suspend_mode = TEGRA_SUSPEND_LP0; |
|
break; |
|
|
|
case 1: |
|
pmc->suspend_mode = TEGRA_SUSPEND_LP1; |
|
break; |
|
|
|
case 2: |
|
pmc->suspend_mode = TEGRA_SUSPEND_LP2; |
|
break; |
|
|
|
default: |
|
pmc->suspend_mode = TEGRA_SUSPEND_NONE; |
|
break; |
|
} |
|
} |
|
|
|
pmc->suspend_mode = tegra_pm_validate_suspend_mode(pmc->suspend_mode); |
|
|
|
if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &value)) |
|
pmc->suspend_mode = TEGRA_SUSPEND_NONE; |
|
|
|
pmc->cpu_good_time = value; |
|
|
|
if (of_property_read_u32(np, "nvidia,cpu-pwr-off-time", &value)) |
|
pmc->suspend_mode = TEGRA_SUSPEND_NONE; |
|
|
|
pmc->cpu_off_time = value; |
|
|
|
if (of_property_read_u32_array(np, "nvidia,core-pwr-good-time", |
|
values, ARRAY_SIZE(values))) |
|
pmc->suspend_mode = TEGRA_SUSPEND_NONE; |
|
|
|
pmc->core_osc_time = values[0]; |
|
pmc->core_pmu_time = values[1]; |
|
|
|
if (of_property_read_u32(np, "nvidia,core-pwr-off-time", &value)) |
|
pmc->suspend_mode = TEGRA_SUSPEND_NONE; |
|
|
|
pmc->core_off_time = value; |
|
|
|
pmc->corereq_high = of_property_read_bool(np, |
|
"nvidia,core-power-req-active-high"); |
|
|
|
pmc->sysclkreq_high = of_property_read_bool(np, |
|
"nvidia,sys-clock-req-active-high"); |
|
|
|
pmc->combined_req = of_property_read_bool(np, |
|
"nvidia,combined-power-req"); |
|
|
|
pmc->cpu_pwr_good_en = of_property_read_bool(np, |
|
"nvidia,cpu-pwr-good-en"); |
|
|
|
if (of_property_read_u32_array(np, "nvidia,lp0-vec", values, |
|
ARRAY_SIZE(values))) |
|
if (pmc->suspend_mode == TEGRA_SUSPEND_LP0) |
|
pmc->suspend_mode = TEGRA_SUSPEND_LP1; |
|
|
|
pmc->lp0_vec_phys = values[0]; |
|
pmc->lp0_vec_size = values[1]; |
|
|
|
return 0; |
|
} |
|
|
|
static void tegra_pmc_init(struct tegra_pmc *pmc) |
|
{ |
|
u32 value; |
|
|
|
/* Always enable CPU power request */ |
|
value = tegra_pmc_readl(PMC_CNTRL); |
|
value |= PMC_CNTRL_CPU_PWRREQ_OE; |
|
tegra_pmc_writel(value, PMC_CNTRL); |
|
|
|
value = tegra_pmc_readl(PMC_CNTRL); |
|
|
|
if (pmc->sysclkreq_high) |
|
value &= ~PMC_CNTRL_SYSCLK_POLARITY; |
|
else |
|
value |= PMC_CNTRL_SYSCLK_POLARITY; |
|
|
|
/* configure the output polarity while the request is tristated */ |
|
tegra_pmc_writel(value, PMC_CNTRL); |
|
|
|
/* now enable the request */ |
|
value = tegra_pmc_readl(PMC_CNTRL); |
|
value |= PMC_CNTRL_SYSCLK_OE; |
|
tegra_pmc_writel(value, PMC_CNTRL); |
|
} |
|
|
|
static int tegra_pmc_probe(struct platform_device *pdev) |
|
{ |
|
void __iomem *base = pmc->base; |
|
struct resource *res; |
|
int err; |
|
|
|
err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node); |
|
if (err < 0) |
|
return err; |
|
|
|
/* take over the memory region from the early initialization */ |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|
pmc->base = devm_ioremap_resource(&pdev->dev, res); |
|
if (IS_ERR(pmc->base)) |
|
return PTR_ERR(pmc->base); |
|
|
|
iounmap(base); |
|
|
|
pmc->clk = devm_clk_get(&pdev->dev, "pclk"); |
|
if (IS_ERR(pmc->clk)) { |
|
err = PTR_ERR(pmc->clk); |
|
dev_err(&pdev->dev, "failed to get pclk: %d\n", err); |
|
return err; |
|
} |
|
|
|
tegra_pmc_init(pmc); |
|
|
|
if (IS_ENABLED(CONFIG_DEBUG_FS)) { |
|
err = tegra_powergate_debugfs_init(); |
|
if (err < 0) |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_PM_SLEEP |
|
static int tegra_pmc_suspend(struct device *dev) |
|
{ |
|
tegra_pmc_writel(virt_to_phys(tegra_resume), PMC_SCRATCH41); |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra_pmc_resume(struct device *dev) |
|
{ |
|
tegra_pmc_writel(0x0, PMC_SCRATCH41); |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static SIMPLE_DEV_PM_OPS(tegra_pmc_pm_ops, tegra_pmc_suspend, tegra_pmc_resume); |
|
|
|
static const char * const tegra20_powergates[] = { |
|
[TEGRA_POWERGATE_CPU] = "cpu", |
|
[TEGRA_POWERGATE_3D] = "3d", |
|
[TEGRA_POWERGATE_VENC] = "venc", |
|
[TEGRA_POWERGATE_VDEC] = "vdec", |
|
[TEGRA_POWERGATE_PCIE] = "pcie", |
|
[TEGRA_POWERGATE_L2] = "l2", |
|
[TEGRA_POWERGATE_MPE] = "mpe", |
|
}; |
|
|
|
static const struct tegra_pmc_soc tegra20_pmc_soc = { |
|
.num_powergates = ARRAY_SIZE(tegra20_powergates), |
|
.powergates = tegra20_powergates, |
|
.num_cpu_powergates = 0, |
|
.cpu_powergates = NULL, |
|
}; |
|
|
|
static const char * const tegra30_powergates[] = { |
|
[TEGRA_POWERGATE_CPU] = "cpu0", |
|
[TEGRA_POWERGATE_3D] = "3d0", |
|
[TEGRA_POWERGATE_VENC] = "venc", |
|
[TEGRA_POWERGATE_VDEC] = "vdec", |
|
[TEGRA_POWERGATE_PCIE] = "pcie", |
|
[TEGRA_POWERGATE_L2] = "l2", |
|
[TEGRA_POWERGATE_MPE] = "mpe", |
|
[TEGRA_POWERGATE_HEG] = "heg", |
|
[TEGRA_POWERGATE_SATA] = "sata", |
|
[TEGRA_POWERGATE_CPU1] = "cpu1", |
|
[TEGRA_POWERGATE_CPU2] = "cpu2", |
|
[TEGRA_POWERGATE_CPU3] = "cpu3", |
|
[TEGRA_POWERGATE_CELP] = "celp", |
|
[TEGRA_POWERGATE_3D1] = "3d1", |
|
}; |
|
|
|
static const u8 tegra30_cpu_powergates[] = { |
|
TEGRA_POWERGATE_CPU, |
|
TEGRA_POWERGATE_CPU1, |
|
TEGRA_POWERGATE_CPU2, |
|
TEGRA_POWERGATE_CPU3, |
|
}; |
|
|
|
static const struct tegra_pmc_soc tegra30_pmc_soc = { |
|
.num_powergates = ARRAY_SIZE(tegra30_powergates), |
|
.powergates = tegra30_powergates, |
|
.num_cpu_powergates = ARRAY_SIZE(tegra30_cpu_powergates), |
|
.cpu_powergates = tegra30_cpu_powergates, |
|
}; |
|
|
|
static const char * const tegra114_powergates[] = { |
|
[TEGRA_POWERGATE_CPU] = "crail", |
|
[TEGRA_POWERGATE_3D] = "3d", |
|
[TEGRA_POWERGATE_VENC] = "venc", |
|
[TEGRA_POWERGATE_VDEC] = "vdec", |
|
[TEGRA_POWERGATE_MPE] = "mpe", |
|
[TEGRA_POWERGATE_HEG] = "heg", |
|
[TEGRA_POWERGATE_CPU1] = "cpu1", |
|
[TEGRA_POWERGATE_CPU2] = "cpu2", |
|
[TEGRA_POWERGATE_CPU3] = "cpu3", |
|
[TEGRA_POWERGATE_CELP] = "celp", |
|
[TEGRA_POWERGATE_CPU0] = "cpu0", |
|
[TEGRA_POWERGATE_C0NC] = "c0nc", |
|
[TEGRA_POWERGATE_C1NC] = "c1nc", |
|
[TEGRA_POWERGATE_DIS] = "dis", |
|
[TEGRA_POWERGATE_DISB] = "disb", |
|
[TEGRA_POWERGATE_XUSBA] = "xusba", |
|
[TEGRA_POWERGATE_XUSBB] = "xusbb", |
|
[TEGRA_POWERGATE_XUSBC] = "xusbc", |
|
}; |
|
|
|
static const u8 tegra114_cpu_powergates[] = { |
|
TEGRA_POWERGATE_CPU0, |
|
TEGRA_POWERGATE_CPU1, |
|
TEGRA_POWERGATE_CPU2, |
|
TEGRA_POWERGATE_CPU3, |
|
}; |
|
|
|
static const struct tegra_pmc_soc tegra114_pmc_soc = { |
|
.num_powergates = ARRAY_SIZE(tegra114_powergates), |
|
.powergates = tegra114_powergates, |
|
.num_cpu_powergates = ARRAY_SIZE(tegra114_cpu_powergates), |
|
.cpu_powergates = tegra114_cpu_powergates, |
|
}; |
|
|
|
static const char * const tegra124_powergates[] = { |
|
[TEGRA_POWERGATE_CPU] = "crail", |
|
[TEGRA_POWERGATE_3D] = "3d", |
|
[TEGRA_POWERGATE_VENC] = "venc", |
|
[TEGRA_POWERGATE_PCIE] = "pcie", |
|
[TEGRA_POWERGATE_VDEC] = "vdec", |
|
[TEGRA_POWERGATE_L2] = "l2", |
|
[TEGRA_POWERGATE_MPE] = "mpe", |
|
[TEGRA_POWERGATE_HEG] = "heg", |
|
[TEGRA_POWERGATE_SATA] = "sata", |
|
[TEGRA_POWERGATE_CPU1] = "cpu1", |
|
[TEGRA_POWERGATE_CPU2] = "cpu2", |
|
[TEGRA_POWERGATE_CPU3] = "cpu3", |
|
[TEGRA_POWERGATE_CELP] = "celp", |
|
[TEGRA_POWERGATE_CPU0] = "cpu0", |
|
[TEGRA_POWERGATE_C0NC] = "c0nc", |
|
[TEGRA_POWERGATE_C1NC] = "c1nc", |
|
[TEGRA_POWERGATE_SOR] = "sor", |
|
[TEGRA_POWERGATE_DIS] = "dis", |
|
[TEGRA_POWERGATE_DISB] = "disb", |
|
[TEGRA_POWERGATE_XUSBA] = "xusba", |
|
[TEGRA_POWERGATE_XUSBB] = "xusbb", |
|
[TEGRA_POWERGATE_XUSBC] = "xusbc", |
|
[TEGRA_POWERGATE_VIC] = "vic", |
|
[TEGRA_POWERGATE_IRAM] = "iram", |
|
}; |
|
|
|
static const u8 tegra124_cpu_powergates[] = { |
|
TEGRA_POWERGATE_CPU0, |
|
TEGRA_POWERGATE_CPU1, |
|
TEGRA_POWERGATE_CPU2, |
|
TEGRA_POWERGATE_CPU3, |
|
}; |
|
|
|
static const struct tegra_pmc_soc tegra124_pmc_soc = { |
|
.num_powergates = ARRAY_SIZE(tegra124_powergates), |
|
.powergates = tegra124_powergates, |
|
.num_cpu_powergates = ARRAY_SIZE(tegra124_cpu_powergates), |
|
.cpu_powergates = tegra124_cpu_powergates, |
|
}; |
|
|
|
static const struct of_device_id tegra_pmc_match[] = { |
|
{ .compatible = "nvidia,tegra124-pmc", .data = &tegra124_pmc_soc }, |
|
{ .compatible = "nvidia,tegra114-pmc", .data = &tegra114_pmc_soc }, |
|
{ .compatible = "nvidia,tegra30-pmc", .data = &tegra30_pmc_soc }, |
|
{ .compatible = "nvidia,tegra20-pmc", .data = &tegra20_pmc_soc }, |
|
{ } |
|
}; |
|
|
|
static struct platform_driver tegra_pmc_driver = { |
|
.driver = { |
|
.name = "tegra-pmc", |
|
.suppress_bind_attrs = true, |
|
.of_match_table = tegra_pmc_match, |
|
.pm = &tegra_pmc_pm_ops, |
|
}, |
|
.probe = tegra_pmc_probe, |
|
}; |
|
module_platform_driver(tegra_pmc_driver); |
|
|
|
/* |
|
* Early initialization to allow access to registers in the very early boot |
|
* process. |
|
*/ |
|
static int __init tegra_pmc_early_init(void) |
|
{ |
|
const struct of_device_id *match; |
|
struct device_node *np; |
|
struct resource regs; |
|
bool invert; |
|
u32 value; |
|
|
|
if (!soc_is_tegra()) |
|
return 0; |
|
|
|
np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match); |
|
if (!np) { |
|
pr_warn("PMC device node not found, disabling powergating\n"); |
|
|
|
regs.start = 0x7000e400; |
|
regs.end = 0x7000e7ff; |
|
regs.flags = IORESOURCE_MEM; |
|
|
|
pr_warn("Using memory region %pR\n", ®s); |
|
} else { |
|
pmc->soc = match->data; |
|
} |
|
|
|
if (of_address_to_resource(np, 0, ®s) < 0) { |
|
pr_err("failed to get PMC registers\n"); |
|
return -ENXIO; |
|
} |
|
|
|
pmc->base = ioremap_nocache(regs.start, resource_size(®s)); |
|
if (!pmc->base) { |
|
pr_err("failed to map PMC registers\n"); |
|
return -ENXIO; |
|
} |
|
|
|
mutex_init(&pmc->powergates_lock); |
|
|
|
invert = of_property_read_bool(np, "nvidia,invert-interrupt"); |
|
|
|
value = tegra_pmc_readl(PMC_CNTRL); |
|
|
|
if (invert) |
|
value |= PMC_CNTRL_INTR_POLARITY; |
|
else |
|
value &= ~PMC_CNTRL_INTR_POLARITY; |
|
|
|
tegra_pmc_writel(value, PMC_CNTRL); |
|
|
|
return 0; |
|
} |
|
early_initcall(tegra_pmc_early_init);
|
|
|