/* * drivers/soc/tegra/pmc.c * * Copyright (c) 2010 Google, Inc * * Author: * Colin Cross * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 PMC_SENSOR_CTRL 0x1b0 #define PMC_SENSOR_CTRL_SCRATCH_WRITE (1 << 2) #define PMC_SENSOR_CTRL_ENABLE_RST (1 << 1) #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 PMC_SCRATCH54 0x258 #define PMC_SCRATCH54_DATA_SHIFT 8 #define PMC_SCRATCH54_ADDR_SHIFT 0 #define PMC_SCRATCH55 0x25c #define PMC_SCRATCH55_RESET_TEGRA (1 << 31) #define PMC_SCRATCH55_CNTRL_ID_SHIFT 27 #define PMC_SCRATCH55_PINMUX_SHIFT 24 #define PMC_SCRATCH55_16BITOP (1 << 15) #define PMC_SCRATCH55_CHECKSUM_SHIFT 16 #define PMC_SCRATCH55_I2CSLV1_SHIFT 0 #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; bool has_tsense_reset; bool has_gpu_clamps; }; /** * 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 { struct device *dev; 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; /* * On Tegra124 and later, the clamps for the GPU are controlled by a * separate register (with different semantics). */ if (id == TEGRA_POWERGATE_3D) { if (pmc->soc->has_gpu_clamps) { 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); } void tegra_pmc_init_tsense_reset(struct tegra_pmc *pmc) { static const char disabled[] = "emergency thermal reset disabled"; u32 pmu_addr, ctrl_id, reg_addr, reg_data, pinmux; struct device *dev = pmc->dev; struct device_node *np; u32 value, checksum; if (!pmc->soc->has_tsense_reset) return; np = of_find_node_by_name(pmc->dev->of_node, "i2c-thermtrip"); if (!np) { dev_warn(dev, "i2c-thermtrip node not found, %s.\n", disabled); return; } if (of_property_read_u32(np, "nvidia,i2c-controller-id", &ctrl_id)) { dev_err(dev, "I2C controller ID missing, %s.\n", disabled); goto out; } if (of_property_read_u32(np, "nvidia,bus-addr", &pmu_addr)) { dev_err(dev, "nvidia,bus-addr missing, %s.\n", disabled); goto out; } if (of_property_read_u32(np, "nvidia,reg-addr", ®_addr)) { dev_err(dev, "nvidia,reg-addr missing, %s.\n", disabled); goto out; } if (of_property_read_u32(np, "nvidia,reg-data", ®_data)) { dev_err(dev, "nvidia,reg-data missing, %s.\n", disabled); goto out; } if (of_property_read_u32(np, "nvidia,pinmux-id", &pinmux)) pinmux = 0; value = tegra_pmc_readl(PMC_SENSOR_CTRL); value |= PMC_SENSOR_CTRL_SCRATCH_WRITE; tegra_pmc_writel(value, PMC_SENSOR_CTRL); value = (reg_data << PMC_SCRATCH54_DATA_SHIFT) | (reg_addr << PMC_SCRATCH54_ADDR_SHIFT); tegra_pmc_writel(value, PMC_SCRATCH54); value = PMC_SCRATCH55_RESET_TEGRA; value |= ctrl_id << PMC_SCRATCH55_CNTRL_ID_SHIFT; value |= pinmux << PMC_SCRATCH55_PINMUX_SHIFT; value |= pmu_addr << PMC_SCRATCH55_I2CSLV1_SHIFT; /* * Calculate checksum of SCRATCH54, SCRATCH55 fields. Bits 23:16 will * contain the checksum and are currently zero, so they are not added. */ checksum = reg_addr + reg_data + (value & 0xff) + ((value >> 8) & 0xff) + ((value >> 24) & 0xff); checksum &= 0xff; checksum = 0x100 - checksum; value |= checksum << PMC_SCRATCH55_CHECKSUM_SHIFT; tegra_pmc_writel(value, PMC_SCRATCH55); value = tegra_pmc_readl(PMC_SENSOR_CTRL); value |= PMC_SENSOR_CTRL_ENABLE_RST; tegra_pmc_writel(value, PMC_SENSOR_CTRL); dev_info(pmc->dev, "emergency thermal reset enabled\n"); out: of_node_put(np); return; } 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; } pmc->dev = &pdev->dev; tegra_pmc_init(pmc); tegra_pmc_init_tsense_reset(pmc); if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_powergate_debugfs_init(); if (err < 0) return err; } return 0; } #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM) 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; } static SIMPLE_DEV_PM_OPS(tegra_pmc_pm_ops, tegra_pmc_suspend, tegra_pmc_resume); #endif 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, .has_tsense_reset = false, .has_gpu_clamps = false, }; 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, .has_tsense_reset = true, .has_gpu_clamps = false, }; 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, .has_tsense_reset = true, .has_gpu_clamps = false, }; 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, .has_tsense_reset = true, .has_gpu_clamps = true, }; 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, #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_ARM) .pm = &tegra_pmc_pm_ops, #endif }, .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);