5181 lines
141 KiB
C
5181 lines
141 KiB
C
/*
|
|
* drivers/soc/tegra/pmc.c
|
|
*
|
|
* Copyright (c) 2010 Google, Inc
|
|
* Copyright (c) 2012-2020, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "tegra-pmc: " fmt
|
|
|
|
#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/gpio.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pinctrl/pinctrl.h>
|
|
#include <linux/pinctrl/pinconf-generic.h>
|
|
#include <linux/pinctrl/pinconf.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/psci.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/wakeup_reason.h>
|
|
#include <soc/tegra/chip-id.h>
|
|
#include <linux/tegra_prod.h>
|
|
#include <linux/power/reset/system-pmic.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/irqchip/tegra.h>
|
|
|
|
#include <dt-bindings/soc/tegra-pmc.h>
|
|
|
|
#include <soc/tegra/common.h>
|
|
#include <soc/tegra/fuse.h>
|
|
#include <soc/tegra/pmc.h>
|
|
|
|
#include <asm/system_misc.h>
|
|
|
|
#include <dt-bindings/pinctrl/pinctrl-tegra-io-pad.h>
|
|
|
|
#define PMC_CNTRL 0x0
|
|
#define PMC_CNTRL_LATCH_WAKEUPS (1 << 5)
|
|
#define PMC_CNTRL_PWRREQ_POLARITY (1 << 8) /* core power req polarity */
|
|
#define PMC_CNTRL_PWRREQ_OE (1 << 9) /* core power req enable */
|
|
#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 PMC_WAKE_MASK 0xc
|
|
#define PMC_WAKE_LEVEL 0x10
|
|
#define PMC_WAKE_STATUS 0x14
|
|
#define PMC_SW_WAKE_STATUS 0x18
|
|
|
|
#define DPD_SAMPLE 0x020
|
|
#define DPD_SAMPLE_ENABLE (1 << 0)
|
|
#define DPD_SAMPLE_DISABLE (0 << 0)
|
|
|
|
#define DPD_ENABLE 0x24
|
|
#define DPD_ENABLE_ON (1 << 0)
|
|
#define DPD_ENABLE_TSC_MULT_ENABLE (1 << 1)
|
|
|
|
#define PWRGATE_TOGGLE 0x30
|
|
#define PWRGATE_TOGGLE_START (1 << 8)
|
|
|
|
#define REMOVE_CLAMPING 0x34
|
|
|
|
#define PWRGATE_STATUS 0x38
|
|
|
|
#define PMC_COREPWRGOOD_TIMER 0x3c
|
|
|
|
#define PMC_PWR_DET_ENABLE 0x48
|
|
|
|
#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_SCRATCH1 0x54
|
|
|
|
#define PMC_CPUPWRGOOD_TIMER 0xc8
|
|
#define PMC_CPUPWROFF_TIMER 0xcc
|
|
|
|
#define PMC_WAKE_DELAY 0xe0
|
|
#define PMC_COREPWROFF_TIMER PMC_WAKE_DELAY
|
|
|
|
#define PMC_PWR_DET_VAL 0xe4
|
|
|
|
#define KERNEL_PANIC_FLAG (1 << 24)
|
|
|
|
#define PMC_SCRATCH41 0x140
|
|
|
|
#define PMC_WAKE2_MASK 0x160
|
|
#define PMC_WAKE2_LEVEL 0x164
|
|
#define PMC_WAKE2_STATUS 0x168
|
|
#define PMC_SW_WAKE2_STATUS 0x16c
|
|
|
|
#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_CSIA (1 << 0)
|
|
#define IO_DPD_CSIB (1 << 1)
|
|
#define IO_DPD_DSI (1 << 2)
|
|
#define IO_DPD_MIPI_BIAS (1 << 3)
|
|
#define IO_DPD_PEX_BIAS (1 << 4)
|
|
#define IO_DPD_PEX_CLK1 (1 << 5)
|
|
#define IO_DPD_PEX_CLK2 (1 << 6)
|
|
#define IO_DPD_PEX_CLK3 (1 << 7)
|
|
#define IO_DPD_DAC (1 << 8)
|
|
#define IO_DPD_USB0 (1 << 9)
|
|
#define IO_DPD_USB1 (1 << 10)
|
|
#define IO_DPD_USB2 (1 << 11)
|
|
#define IO_DPD_USB_BIAS (1 << 12)
|
|
#define IO_DPD_NAND (1 << 13)
|
|
#define IO_DPD_UART (1 << 14)
|
|
#define IO_DPD_BB (1 << 15)
|
|
#define IO_DPD_VI (1 << 16)
|
|
#define IO_DPD_AUDIO (1 << 17)
|
|
#define IO_DPD_LCD (1 << 18)
|
|
#define IO_DPD_HSIC (1 << 19)
|
|
#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_ENABLE_LSB 30
|
|
|
|
#define IO_DPD_STATUS 0x1bc
|
|
|
|
#define IO_DPD2_REQ 0x1c0
|
|
#define IO_DPD2_PEX_CNTRL (1 << 0)
|
|
#define IO_DPD2_SDMMC1 (1 << 1)
|
|
#define IO_DPD2_SDMMC3 (1 << 2)
|
|
#define IO_DPD2_SDMMC4 (1 << 3)
|
|
#define IO_DPD2_CAM (1 << 4)
|
|
#define IO_DPD2_RES_RAIL (1 << 5)
|
|
#define IO_DPD2_HV (1 << 6)
|
|
#define IO_DPD2_DSIB (1 << 7)
|
|
#define IO_DPD2_DSIC (1 << 8)
|
|
#define IO_DPD2_DSID (1 << 9)
|
|
#define IO_DPD2_CSIC (1 << 10)
|
|
#define IO_DPD2_CSID (1 << 11)
|
|
#define IO_DPD2_CSIE (1 << 12)
|
|
#define IO_DPD2_ENABLE_LSB 30
|
|
|
|
#define IO_DPD2_STATUS 0x1c4
|
|
#define SEL_DPD_TIM 0x1c8
|
|
|
|
#define DPD_STATE_CHANGE_DELAY 700
|
|
|
|
#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
|
|
|
|
#define PMC_FUSE_CTRL 0x450
|
|
#define PMC_FUSE_CTRL_ENABLE_REDIRECTION (1 << 0)
|
|
#define PMC_FUSE_CTRL_ENABLE_REDIRECTION_STICKY (1 << 1)
|
|
#define PMC_FUSE_CTRL_PS18_LATCH_SET (1 << 8)
|
|
#define PMC_FUSE_CTRL_PS18_LATCH_CLEAR (1 << 9)
|
|
|
|
/* Scratch 250: Bootrom i2c command base */
|
|
#define PMC_BR_COMMAND_BASE 0x908
|
|
|
|
/* USB2 SLEEPWALK registers */
|
|
#define UTMIP(_port, _offset1, _offset2) \
|
|
(((_port) <= 2) ? (_offset1) : (_offset2))
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(x) UTMIP(x, 0x1fc, 0x4d0)
|
|
#define UTMIP_MASTER_ENABLE(x) UTMIP(x, BIT(8 * (x)), BIT(0))
|
|
#define UTMIP_FSLS_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 1), \
|
|
BIT(1))
|
|
#define UTMIP_PCTRL_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 2), \
|
|
BIT(2))
|
|
#define UTMIP_TCTRL_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 3), \
|
|
BIT(3))
|
|
#define UTMIP_WAKE_VAL(_port, _value) (((_value) & 0xf) << \
|
|
(UTMIP(_port, 8 * (_port) + 4, 4)))
|
|
#define UTMIP_WAKE_VAL_NONE(_port) UTMIP_WAKE_VAL(_port, 12)
|
|
#define UTMIP_WAKE_VAL_ANY(_port) UTMIP_WAKE_VAL(_port, 15)
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG1 (0x4d0)
|
|
#define UTMIP_RPU_SWITC_LOW_USE_PMC_PX(x) BIT((x) + 8)
|
|
#define UTMIP_RPD_CTRL_USE_PMC_PX(x) BIT((x) + 16)
|
|
|
|
#define APBDEV_PMC_UTMIP_MASTER_CONFIG (0x274)
|
|
#define UTMIP_PWR(x) UTMIP(x, BIT(x), BIT(4))
|
|
#define UHSIC_PWR(x) BIT(3)
|
|
|
|
#define APBDEV_PMC_USB_DEBOUNCE_DEL (0xec)
|
|
#define DEBOUNCE_VAL(x) (((x) & 0xffff) << 0)
|
|
#define UTMIP_LINE_DEB_CNT(x) (((x) & 0xf) << 16)
|
|
#define UHSIC_LINE_DEB_CNT(x) (((x) & 0xf) << 20)
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_FAKE(x) UTMIP(x, 0x218, 0x294)
|
|
#define UTMIP_FAKE_USBOP_VAL(x) UTMIP(x, BIT(4 * (x)), BIT(8))
|
|
#define UTMIP_FAKE_USBON_VAL(x) UTMIP(x, BIT(4 * (x) + 1), \
|
|
BIT(9))
|
|
#define UTMIP_FAKE_USBOP_EN(x) UTMIP(x, BIT(4 * (x) + 2), \
|
|
BIT(10))
|
|
#define UTMIP_FAKE_USBON_EN(x) UTMIP(x, BIT(4 * (x) + 3), \
|
|
BIT(11))
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_SLEEPWALK_CFG(x) UTMIP(x, 0x200, 0x288)
|
|
#define UTMIP_LINEVAL_WALK_EN(x) UTMIP(x, BIT(8 * (x) + 7), \
|
|
BIT(15))
|
|
|
|
#define APBDEV_PMC_USB_AO (0xf0)
|
|
#define USBOP_VAL_PD(x) UTMIP(x, BIT(4 * (x)), BIT(20))
|
|
#define USBON_VAL_PD(x) UTMIP(x, BIT(4 * (x) + 1), \
|
|
BIT(21))
|
|
#define STROBE_VAL_PD(x) BIT(12)
|
|
#define DATA0_VAL_PD(x) BIT(13)
|
|
#define DATA1_VAL_PD BIT(24)
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_SAVED_STATE(x) UTMIP(x, 0x1f0, 0x280)
|
|
#define SPEED(_port, _value) (((_value) & 0x3) << \
|
|
(UTMIP(_port, 8 * (_port), 8)))
|
|
#define UTMI_HS(_port) SPEED(_port, 0)
|
|
#define UTMI_FS(_port) SPEED(_port, 1)
|
|
#define UTMI_LS(_port) SPEED(_port, 2)
|
|
#define UTMI_RST(_port) SPEED(_port, 3)
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_TRIGGERS (0x1ec)
|
|
#define UTMIP_CLR_WALK_PTR(x) UTMIP(x, BIT(x), BIT(16))
|
|
#define UTMIP_CAP_CFG(x) UTMIP(x, BIT((x) + 4), BIT(17))
|
|
#define UTMIP_CLR_WAKE_ALARM(x) UTMIP(x, BIT((x) + 12), \
|
|
BIT(19))
|
|
#define UHSIC_CLR_WALK_PTR BIT(3)
|
|
#define UHSIC_CLR_WAKE_ALARM BIT(15)
|
|
|
|
#define APBDEV_PMC_UTMIP_SLEEPWALK_PX(x) UTMIP(x, 0x204 + (4 * (x)), \
|
|
0x4e0)
|
|
/* phase A */
|
|
#define UTMIP_USBOP_RPD_A BIT(0)
|
|
#define UTMIP_USBON_RPD_A BIT(1)
|
|
#define UTMIP_AP_A BIT(4)
|
|
#define UTMIP_AN_A BIT(5)
|
|
#define UTMIP_HIGHZ_A BIT(6)
|
|
/* phase B */
|
|
#define UTMIP_USBOP_RPD_B BIT(8)
|
|
#define UTMIP_USBON_RPD_B BIT(9)
|
|
#define UTMIP_AP_B BIT(12)
|
|
#define UTMIP_AN_B BIT(13)
|
|
#define UTMIP_HIGHZ_B BIT(14)
|
|
/* phase C */
|
|
#define UTMIP_USBOP_RPD_C BIT(16)
|
|
#define UTMIP_USBON_RPD_C BIT(17)
|
|
#define UTMIP_AP_C BIT(20)
|
|
#define UTMIP_AN_C BIT(21)
|
|
#define UTMIP_HIGHZ_C BIT(22)
|
|
/* phase D */
|
|
#define UTMIP_USBOP_RPD_D BIT(24)
|
|
#define UTMIP_USBON_RPD_D BIT(25)
|
|
#define UTMIP_AP_D BIT(28)
|
|
#define UTMIP_AN_D BIT(29)
|
|
#define UTMIP_HIGHZ_D BIT(30)
|
|
|
|
#define APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP (0x26c)
|
|
#define UTMIP_LINE_WAKEUP_EN(x) UTMIP(x, BIT(x), BIT(4))
|
|
#define UHSIC_LINE_WAKEUP_EN BIT(3)
|
|
|
|
#define APBDEV_PMC_UTMIP_TERM_PAD_CFG (0x1f8)
|
|
#define PCTRL_VAL(x) (((x) & 0x3f) << 1)
|
|
#define TCTRL_VAL(x) (((x) & 0x3f) << 7)
|
|
|
|
#define APBDEV_PMC_UTMIP_PAD_CFGX(x) (0x4c0 + (4 * (x)))
|
|
#define RPD_CTRL_PX(x) (((x) & 0x1f) << 22)
|
|
|
|
#define APBDEV_PMC_UHSIC_SLEEP_CFG \
|
|
APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(0)
|
|
#define UHSIC_MASTER_ENABLE BIT(24)
|
|
#define UHSIC_WAKE_VAL(_value) (((_value) & 0xf) << 28)
|
|
#define UHSIC_WAKE_VAL_SD10 UHSIC_WAKE_VAL(2)
|
|
#define UHSIC_WAKE_VAL_NONE UHSIC_WAKE_VAL(12)
|
|
|
|
#define APBDEV_PMC_UHSIC_FAKE APBDEV_PMC_UTMIP_UHSIC_FAKE(0)
|
|
#define UHSIC_FAKE_STROBE_VAL BIT(12)
|
|
#define UHSIC_FAKE_DATA_VAL BIT(13)
|
|
#define UHSIC_FAKE_STROBE_EN BIT(14)
|
|
#define UHSIC_FAKE_DATA_EN BIT(15)
|
|
|
|
#define APBDEV_PMC_UHSIC_SAVED_STATE \
|
|
APBDEV_PMC_UTMIP_UHSIC_SAVED_STATE(0)
|
|
#define UHSIC_MODE(_value) (((_value) & 0x1) << 24)
|
|
#define UHSIC_HS UHSIC_MODE(0)
|
|
#define UHSIC_RST UHSIC_MODE(1)
|
|
|
|
#define APBDEV_PMC_UHSIC_SLEEPWALK_CFG \
|
|
APBDEV_PMC_UTMIP_UHSIC_SLEEPWALK_CFG(0)
|
|
#define UHSIC_WAKE_WALK_EN BIT(30)
|
|
#define UHSIC_LINEVAL_WALK_EN BIT(31)
|
|
|
|
#define APBDEV_PMC_UHSIC_SLEEPWALK_P0 (0x210)
|
|
#define UHSIC_DATA0_RPD_A BIT(1)
|
|
#define UHSIC_DATA0_RPU_B BIT(11)
|
|
#define UHSIC_DATA0_RPU_C BIT(19)
|
|
#define UHSIC_DATA0_RPU_D BIT(27)
|
|
#define UHSIC_STROBE_RPU_A BIT(2)
|
|
#define UHSIC_STROBE_RPD_B BIT(8)
|
|
#define UHSIC_STROBE_RPD_C BIT(16)
|
|
#define UHSIC_STROBE_RPD_D BIT(24)
|
|
|
|
/* Bootrom comand register */
|
|
#define PMC_REG_8bit_MASK 0xFF
|
|
#define PMC_REG_16bit_MASK 0xFFFF
|
|
#define PMC_BR_COMMAND_I2C_ADD_MASK 0x7F
|
|
#define PMC_BR_COMMAND_WR_COMMANDS_MASK 0x3F
|
|
#define PMC_BR_COMMAND_WR_COMMANDS_SHIFT 8
|
|
#define PMC_BR_COMMAND_OPERAND_SHIFT 15
|
|
#define PMC_BR_COMMAND_CSUM_MASK 0xFF
|
|
#define PMC_BR_COMMAND_CSUM_SHIFT 16
|
|
#define PMC_BR_COMMAND_PMUX_MASK 0x7
|
|
#define PMC_BR_COMMAND_PMUX_SHIFT 24
|
|
#define PMC_BR_COMMAND_CTRL_ID_MASK 0x7
|
|
#define PMC_BR_COMMAND_CTRL_ID_SHIFT 27
|
|
#define PMC_BR_COMMAND_CTRL_TYPE_SHIFT 30
|
|
#define PMC_BR_COMMAND_RST_EN_SHIFT 31
|
|
|
|
/* io dpd off request code */
|
|
#define IO_DPD_CODE_OFF 1
|
|
|
|
/*** Tegra186 register definitions **/
|
|
#define PMC_IMPL_HALT_IN_FIQ_MASK BIT(28)
|
|
|
|
#define PMC_RST_LEVEL_MASK 0x3
|
|
#define PMC_RST_LEVEL_SHIFT 0x0
|
|
#define PMC_RST_SOURCE_MASK 0x3C
|
|
#define PMC_RST_SOURCE_SHIFT 0x2
|
|
#define T210_PMC_RST_LEVEL_MASK 0x7
|
|
|
|
#define TEGRA210_PMC_DPD_PADS_ORIDE_BLINK BIT(20)
|
|
#define TEGRA210_PMC_CTRL_BLINK_EN BIT(7)
|
|
|
|
/*** Tegra210b01 led soft blink **/
|
|
#define PMC_LED_BREATHING_EN BIT(0)
|
|
#define PMC_SHORT_LOW_PERIOD_EN BIT(1)
|
|
#define PMC_LED_SOFT_BLINK_1CYCLE_NS 32000000
|
|
|
|
#define NR_SMC_REGS 6
|
|
#define PMC_READ 0xaa
|
|
#define PMC_WRITE 0xbb
|
|
#define TEGRA_SIP_PMC_COMMAND_FID 0xC2FFFE00
|
|
|
|
/* PMIC watchdog reset bit */
|
|
#define PMIC_WATCHDOG_RESET 0x02
|
|
|
|
#ifdef CONFIG_ARM64
|
|
#define SMC_ARG0 "x0"
|
|
#define SMC_ARG1 "x1"
|
|
#define SMC_ARG2 "x2"
|
|
#define SMC_ARG3 "x3"
|
|
#define SMC_ARCH_EXTENSION ""
|
|
#define SMC_REGISTERS_TRASHED "x4","x5","x6","x7","x8","x9","x10","x11", \
|
|
"x12","x13","x14","x15","x16","x17"
|
|
#else
|
|
#define SMC_ARG0 "r0"
|
|
#define SMC_ARG1 "r1"
|
|
#define SMC_ARG2 "r2"
|
|
#define SMC_ARG3 "r3"
|
|
#define SMC_ARCH_EXTENSION ".arch_extension sec\n"
|
|
#define SMC_REGISTERS_TRASHED "ip"
|
|
#endif
|
|
|
|
struct pmc_smc_regs {
|
|
u64 args[NR_SMC_REGS];
|
|
};
|
|
|
|
static inline ulong send_smc(u32 func, struct pmc_smc_regs *regs)
|
|
{
|
|
register ulong _r0 asm(SMC_ARG0) = func;
|
|
register ulong _r1 asm(SMC_ARG1) = (*regs).args[0];
|
|
register ulong _r2 asm(SMC_ARG2) = (*regs).args[1];
|
|
register ulong _r3 asm(SMC_ARG3) = (*regs).args[2];
|
|
|
|
asm volatile(
|
|
__asmeq("%0", SMC_ARG0)
|
|
__asmeq("%1", SMC_ARG1)
|
|
__asmeq("%2", SMC_ARG2)
|
|
__asmeq("%3", SMC_ARG3)
|
|
__asmeq("%4", SMC_ARG0)
|
|
__asmeq("%5", SMC_ARG1)
|
|
__asmeq("%6", SMC_ARG2)
|
|
__asmeq("%7", SMC_ARG3)
|
|
SMC_ARCH_EXTENSION
|
|
"smc #0" /* switch to secure world */
|
|
: "=r" (_r0), "=r" (_r1), "=r" (_r2), "=r" (_r3)
|
|
: "r" (_r0), "r" (_r1), "r" (_r2), "r" (_r3)
|
|
: SMC_REGISTERS_TRASHED);
|
|
(*regs).args[0] = _r1;
|
|
return _r0;
|
|
}
|
|
|
|
struct io_dpd_reg_info {
|
|
u32 req_reg_off;
|
|
u8 dpd_code_lsb;
|
|
};
|
|
|
|
static struct io_dpd_reg_info t3_io_dpd_req_regs[] = {
|
|
{0x1b8, 30},
|
|
{0x1c0, 30},
|
|
};
|
|
|
|
enum pmc_regs {
|
|
TEGRA_PMC_CNTRL,
|
|
TEGRA_PMC_WAKE_MASK,
|
|
TEGRA_PMC_WAKE_LEVEL,
|
|
TEGRA_PMC_WAKE_STATUS,
|
|
TEGRA_PMC_WAKE_DELAY,
|
|
TEGRA_PMC_SW_WAKE_STATUS,
|
|
TEGRA_PMC_DPD_PADS_ORIDE,
|
|
TEGRA_PMC_BLINK_TIMER,
|
|
TEGRA_PMC_WAKE2_MASK,
|
|
TEGRA_PMC_WAKE2_LEVEL,
|
|
TEGRA_PMC_WAKE2_STATUS,
|
|
TEGRA_PMC_SW_WAKE2_STATUS,
|
|
TEGRA_PMC_IO_DPD_SAMPLE,
|
|
TEGRA_PMC_IO_DPD_ENABLE,
|
|
TEGRA_PMC_IO_DPD_REQ,
|
|
TEGRA_PMC_IO_DPD_STATUS,
|
|
TEGRA_PMC_IO_DPD2_REQ,
|
|
TEGRA_PMC_IO_DPD2_STATUS,
|
|
TEGRA_PMC_SEL_DPD_TIM,
|
|
TEGRA_PMC_PWR_NO_IOPOWER,
|
|
TEGRA_PMC_PWR_DET_ENABLE,
|
|
TEGRA_PMC_PWR_DET_VAL,
|
|
TEGRA_PMC_REMOVE_CLAMPING,
|
|
TEGRA_PMC_PWRGATE_TOGGLE,
|
|
TEGRA_PMC_PWRGATE_STATUS,
|
|
TEGRA_PMC_COREPWRGOOD_TIMER,
|
|
TEGRA_PMC_CPUPWRGOOD_TIMER,
|
|
TEGRA_PMC_CPUPWROFF_TIMER,
|
|
TEGRA_PMC_COREPWROFF_TIMER,
|
|
TEGRA_PMC_SENSOR_CTRL,
|
|
TEGRA_PMC_GPU_RG_CNTRL,
|
|
TEGRA_PMC_FUSE_CTRL,
|
|
TEGRA_PMC_BR_COMMAND_BASE,
|
|
TEGRA_PMC_SCRATCH0,
|
|
TEGRA_PMC_SCRATCH1,
|
|
TEGRA_PMC_SCRATCH37,
|
|
TEGRA_PMC_SCRATCH41,
|
|
TEGRA_PMC_SCRATCH43,
|
|
TEGRA_PMC_SCRATCH54,
|
|
TEGRA_PMC_SCRATCH55,
|
|
TEGRA_PMC_SCRATCH203,
|
|
TEGRA_PMC_RST_STATUS,
|
|
TEGRA_PMC_IMPL_RAMDUMP_CTL_STATUS,
|
|
TEGRA_PMC_SATA_PWRGT_0,
|
|
TEGRA_PMC_UFSHC_PWR_CNTRL_0,
|
|
TEGRA_PMC_E_33V_PWR,
|
|
TEGRA_PMC_E_18V_PWR,
|
|
TEGRA_PMC_LED_BREATHING_CTRL,
|
|
TEGRA_PMC_LED_BREATHING_COUNTER0,
|
|
TEGRA_PMC_LED_BREATHING_COUNTER1,
|
|
TEGRA_PMC_LED_BREATHING_COUNTER2,
|
|
TEGRA_PMC_LED_BREATHING_COUNTER3,
|
|
TEGRA_PMC_LED_BREATHING_STATUS,
|
|
|
|
/* Last entry */
|
|
TEGRA_PMC_MAX_REG,
|
|
};
|
|
|
|
static bool get_secure_pmc_setting(void);
|
|
static u32 tegra_pmc_reg_readl(enum pmc_regs reg);
|
|
static void tegra_pmc_reg_writel(u32 value, enum pmc_regs reg);
|
|
|
|
/* Bootrom commands structures */
|
|
struct tegra_bootrom_block {
|
|
const char *name;
|
|
int address;
|
|
bool reg_8bits;
|
|
bool data_8bits;
|
|
bool i2c_controller;
|
|
int controller_id;
|
|
bool enable_reset;
|
|
int ncommands;
|
|
u32 *commands;
|
|
};
|
|
|
|
struct tegra_bootrom_commands {
|
|
u32 command_retry_count;
|
|
u32 delay_between_commands;
|
|
u32 wait_before_bus_clear;
|
|
struct tegra_bootrom_block *blocks;
|
|
int nblocks;
|
|
};
|
|
|
|
static struct tegra_bootrom_commands *br_rst_commands;
|
|
static struct tegra_bootrom_commands *br_off_commands;
|
|
|
|
#define PMC_PWR_NO_IOPOWER 0x44
|
|
|
|
static DEFINE_SPINLOCK(pwr_lock);
|
|
|
|
struct tegra_pmc_io_pad_soc {
|
|
const char *name;
|
|
unsigned int dpd;
|
|
unsigned int voltage;
|
|
unsigned int io_power;
|
|
const unsigned int pins[1];
|
|
unsigned int npins;
|
|
int dpd_req_reg;
|
|
int dpd_status_reg;
|
|
int dpd_timer_reg;
|
|
int dpd_sample_reg;
|
|
bool bdsdmem_cfc;
|
|
unsigned int io_pad_pwr_det_enable_reg;
|
|
unsigned int io_pad_pwr_det_val_reg;
|
|
int pad_uv_0;
|
|
int pad_uv_1;
|
|
};
|
|
|
|
struct tegra_pmc_soc {
|
|
unsigned int num_powergates;
|
|
const char *const *powergates;
|
|
unsigned int num_cpu_powergates;
|
|
const u8 *cpu_powergates;
|
|
const struct tegra_pmc_io_pad_soc *io_pads;
|
|
unsigned int num_io_pads;
|
|
const struct pinctrl_pin_desc *descs;
|
|
unsigned int num_descs;
|
|
const unsigned long *rmap;
|
|
|
|
bool has_tsense_reset;
|
|
bool has_gpu_clamps;
|
|
bool has_ps18;
|
|
bool has_bootrom_command;
|
|
bool has_pclk_clock;
|
|
bool has_interrupt_polarity_support;
|
|
bool has_reboot_base_address;
|
|
bool show_legacy_reset_status;
|
|
bool skip_lp0_vector_setup;
|
|
bool skip_legacy_pmc_init;
|
|
bool skip_power_gate_debug_fs_init;
|
|
bool skip_restart_register;
|
|
bool skip_arm_pm_restart;
|
|
bool has_misc_base_address;
|
|
bool sata_power_gate_in_misc;
|
|
};
|
|
|
|
struct tegra_io_pad_regulator {
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
struct regulator *regulator;
|
|
struct notifier_block nb;
|
|
};
|
|
|
|
/**
|
|
* struct tegra_pmc - NVIDIA Tegra PMC
|
|
* @base: pointer to I/O remapped register region
|
|
* @reboot_base: pointer to I/O remapped register region for reboot address
|
|
* @early_base: pointer to I/O remapped register region for early init
|
|
* @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
|
|
* @pctl: pinctrl handle which is returned after registering pinctrl
|
|
* @pinctrl_desc: Pincontrol descriptor for IO pads
|
|
*/
|
|
struct tegra_pmc {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
void __iomem *reboot_base;
|
|
void __iomem *early_base;
|
|
void __iomem *misc_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;
|
|
struct pinctrl_dev *pctl;
|
|
struct pinctrl_desc pinctrl_desc;
|
|
bool *allow_dynamic_switch;
|
|
bool voltage_switch_restriction_enabled;
|
|
struct tegra_prod *tprod;
|
|
};
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
#define PMC_WAKE_TYPE_INDEX 0
|
|
#define PMC_WAKE_MASK_INDEX 1
|
|
#define PMC_TRIGGER_TYPE_INDEX 2
|
|
#define PMC_OF_ARGS_COUNT 3
|
|
struct pmc_wakeup {
|
|
u32 wake_type;
|
|
u32 wake_mask_offset;
|
|
u32 irq_num;
|
|
struct list_head list;
|
|
};
|
|
|
|
struct pmc_lp0_wakeup {
|
|
struct device_node *of_node;
|
|
u64 enable;
|
|
u64 level;
|
|
u64 level_any;
|
|
struct list_head wake_list;
|
|
};
|
|
static struct pmc_lp0_wakeup tegra_lp0_wakeup;
|
|
static u32 io_dpd_reg, io_dpd2_reg;
|
|
#endif
|
|
|
|
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
|
|
.base = NULL,
|
|
.suspend_mode = TEGRA_SUSPEND_NONE,
|
|
.lp0_vec_phys = 0,
|
|
.lp0_vec_size = 0,
|
|
};
|
|
|
|
static const char * const nvcsi_ab_bricks_pads[] = {
|
|
"csia",
|
|
"csib",
|
|
};
|
|
|
|
static const char * const nvcsi_cdef_bricks_pads[] = {
|
|
"csic",
|
|
"csid",
|
|
"csie",
|
|
"csif",
|
|
};
|
|
|
|
/* PMC misc register read/write with pmc register enums */
|
|
static u32 tegra_pmc_misc_readl(enum pmc_regs reg)
|
|
{
|
|
return readl(pmc->misc_base + pmc->soc->rmap[reg]);
|
|
}
|
|
|
|
static void tegra_pmc_misc_writel(u32 value, enum pmc_regs reg)
|
|
{
|
|
writel(value, pmc->misc_base + pmc->soc->rmap[reg]);
|
|
}
|
|
|
|
static void tegra_pmc_misc_register_update(enum pmc_regs reg,
|
|
unsigned long mask,
|
|
unsigned long val)
|
|
{
|
|
u32 pmc_reg;
|
|
|
|
pmc_reg = tegra_pmc_misc_readl(reg);
|
|
pmc_reg = (pmc_reg & ~mask) | (val & mask);
|
|
tegra_pmc_misc_writel(pmc_reg, reg);
|
|
}
|
|
|
|
static void tegra_pmc_register_update(enum pmc_regs reg,
|
|
unsigned long mask,
|
|
unsigned long val)
|
|
{
|
|
u32 pmc_reg;
|
|
|
|
pmc_reg = tegra_pmc_reg_readl(reg);
|
|
pmc_reg = (pmc_reg & ~mask) | (val & mask);
|
|
tegra_pmc_reg_writel(pmc_reg, reg);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
int tegra_read_wake_status(u32 *wake_status)
|
|
{
|
|
if (soc_is_tegra186_n_later())
|
|
return tegra18x_read_wake_status(wake_status);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifndef CONFIG_TEGRA_POWERGATE
|
|
/**
|
|
* 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_reg_readl(TEGRA_PMC_PWRGATE_STATUS) & (1 << id);
|
|
|
|
if (status == new_state) {
|
|
mutex_unlock(&pmc->powergates_lock);
|
|
return 0;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(PWRGATE_TOGGLE_START | id,
|
|
TEGRA_PMC_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_reg_readl(TEGRA_PMC_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_reg_writel(0, TEGRA_PMC_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_reg_writel(mask, TEGRA_PMC_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 */
|
|
#endif /* CONFIG_TEGRA_POWERGATE */
|
|
|
|
/**
|
|
* tegra_pmc_get_system_reset_reason() - last reset reason status
|
|
*/
|
|
enum tegra_system_reset_reason tegra_pmc_get_system_reset_reason(void)
|
|
{
|
|
u32 reset_reg, rst_src;
|
|
enum tegra_system_reset_reason tegra_rst_rsn_sts;
|
|
|
|
reset_reg = tegra_pmc_reg_readl(TEGRA_PMC_RST_STATUS);
|
|
|
|
if (pmc->soc->show_legacy_reset_status) {
|
|
rst_src = reset_reg & T210_PMC_RST_LEVEL_MASK;
|
|
switch (rst_src) {
|
|
case 0:
|
|
/* In case of PMIC watchdog, Reset is Power On Reset.
|
|
* PMIC status register is saved in SRATCH203 register.
|
|
* PMC driver checks watchdog status bit to identify
|
|
* POR is because of watchdog timer reset */
|
|
if (tegra_pmc_reg_readl(TEGRA_PMC_SCRATCH203) &
|
|
PMIC_WATCHDOG_RESET)
|
|
tegra_rst_rsn_sts = PMIC_WATCHDOG_POR;
|
|
else
|
|
tegra_rst_rsn_sts = TEGRA_POWER_ON_RESET;
|
|
break;
|
|
|
|
case 1:
|
|
tegra_rst_rsn_sts = TEGRA_WATCHDOG;
|
|
break;
|
|
|
|
case 2:
|
|
tegra_rst_rsn_sts = TEGRA_SENSOR;
|
|
break;
|
|
|
|
case 3:
|
|
tegra_rst_rsn_sts = TEGRA_SOFTWARE_RESET;
|
|
break;
|
|
|
|
case 4:
|
|
tegra_rst_rsn_sts = TEGRA_LP0;
|
|
break;
|
|
|
|
case 5:
|
|
tegra_rst_rsn_sts = TEGRA_AOTAG;
|
|
break;
|
|
|
|
default:
|
|
tegra_rst_rsn_sts = TEGRA_RESET_REASON_MAX;
|
|
}
|
|
} else {
|
|
rst_src = (reset_reg & PMC_RST_SOURCE_MASK) >> PMC_RST_SOURCE_SHIFT;
|
|
tegra_rst_rsn_sts = rst_src;
|
|
}
|
|
|
|
return tegra_rst_rsn_sts;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_get_system_reset_reason);
|
|
|
|
enum tegra_system_reset_level tegra_pmc_get_system_reset_level(void)
|
|
{
|
|
u32 rst_lvl, reset_reg;
|
|
enum tegra_system_reset_level tegra_rst_lvl_sts;
|
|
|
|
reset_reg = tegra_pmc_reg_readl(TEGRA_PMC_RST_STATUS);
|
|
|
|
if (pmc->soc->show_legacy_reset_status) {
|
|
tegra_rst_lvl_sts = TEGRA_RESET_LEVEL_MAX;
|
|
} else {
|
|
rst_lvl = (reset_reg & PMC_RST_LEVEL_MASK) >> PMC_RST_LEVEL_SHIFT;
|
|
tegra_rst_lvl_sts = rst_lvl;
|
|
}
|
|
|
|
return tegra_rst_lvl_sts;
|
|
}
|
|
|
|
static void tegra_pmc_program_reboot_reason(const char *cmd)
|
|
{
|
|
u32 value;
|
|
|
|
value = tegra_pmc_reg_readl(TEGRA_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_reg_writel(value, TEGRA_PMC_SCRATCH0);
|
|
}
|
|
|
|
static int tegra_pmc_restart_notify(struct notifier_block *this,
|
|
unsigned long action, void *data)
|
|
{
|
|
const char *cmd = data;
|
|
u32 value;
|
|
|
|
tegra_pmc_program_reboot_reason(cmd);
|
|
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
value |= 0x10;
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_CNTRL);
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block tegra_pmc_restart_handler = {
|
|
.notifier_call = tegra_pmc_restart_notify,
|
|
.priority = 128,
|
|
};
|
|
|
|
static int tegra_pmc_panic_handler(struct notifier_block *this,
|
|
unsigned long action, void *data)
|
|
{
|
|
u32 pmc_reg_val;
|
|
|
|
pmc_reg_val = tegra_pmc_reg_readl(TEGRA_PMC_SCRATCH37);
|
|
tegra_pmc_reg_writel((pmc_reg_val | KERNEL_PANIC_FLAG), TEGRA_PMC_SCRATCH37);
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
|
|
struct notifier_block tegra_pmc_panic_notifier = {
|
|
.notifier_call = tegra_pmc_panic_handler,
|
|
.priority = INT_MAX-1,
|
|
};
|
|
|
|
#ifndef CONFIG_TEGRA_POWERGATE
|
|
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;
|
|
|
|
if (pmc->soc->skip_power_gate_debug_fs_init)
|
|
return 0;
|
|
|
|
d = debugfs_create_file("powergate", S_IRUGO, NULL, NULL,
|
|
&powergate_fops);
|
|
if (!d)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int tegra_powergate_debugfs_init(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifndef CONFIG_TEGRA_POWERGATE
|
|
/* Legacy APIs for IO pad enable/disable */
|
|
int tegra_io_rail_power_on(int id)
|
|
{
|
|
switch (id) {
|
|
case TEGRA_IO_RAIL_LVDS:
|
|
return tegra_pmc_io_pad_low_power_disable("lvds");
|
|
case TEGRA_IO_RAIL_HDMI:
|
|
return tegra_pmc_io_pad_low_power_disable("hdmi");
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_io_rail_power_on);
|
|
|
|
int tegra_io_rail_power_off(int id)
|
|
{
|
|
switch (id) {
|
|
case TEGRA_IO_RAIL_LVDS:
|
|
return tegra_pmc_io_pad_low_power_enable("lvds");
|
|
case TEGRA_IO_RAIL_HDMI:
|
|
return tegra_pmc_io_pad_low_power_enable("hdmi");
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_io_rail_power_off);
|
|
#endif /* CONFIG_TEGRA_POWERGATE */
|
|
|
|
void tegra_pmc_write_bootrom_command(u32 command_offset, unsigned long val)
|
|
{
|
|
tegra_pmc_writel(val, command_offset + PMC_BR_COMMAND_BASE);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_write_bootrom_command);
|
|
|
|
void tegra_pmc_reset_system(void)
|
|
{
|
|
u32 val;
|
|
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
val |= 0x10;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_CNTRL);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_reset_system);
|
|
|
|
int tegra_pmc_clear_reboot_reason(u32 reason)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl_relaxed(pmc->reboot_base +
|
|
pmc->soc->rmap[TEGRA_PMC_SCRATCH0]);
|
|
val &= ~reason;
|
|
writel_relaxed(val, pmc->reboot_base +
|
|
pmc->soc->rmap[TEGRA_PMC_SCRATCH0]);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_clear_reboot_reason);
|
|
|
|
int tegra_pmc_set_reboot_reason(u32 reason)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl_relaxed(pmc->reboot_base +
|
|
pmc->soc->rmap[TEGRA_PMC_SCRATCH0]);
|
|
val |= reason;
|
|
writel_relaxed(val, pmc->reboot_base +
|
|
pmc->soc->rmap[TEGRA_PMC_SCRATCH0]);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_set_reboot_reason);
|
|
|
|
/* UFS power gate control */
|
|
void tegra_pmc_ufs_pwrcntrl_update(unsigned long mask, unsigned long val)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pwr_lock, flags);
|
|
tegra_pmc_register_update(TEGRA_PMC_UFSHC_PWR_CNTRL_0, mask, val);
|
|
spin_unlock_irqrestore(&pwr_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_ufs_pwrcntrl_update);
|
|
|
|
unsigned long tegra_pmc_ufs_pwrcntrl_get(void)
|
|
{
|
|
return tegra_pmc_reg_readl(TEGRA_PMC_UFSHC_PWR_CNTRL_0);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_ufs_pwrcntrl_get);
|
|
|
|
/* SATA power gate control */
|
|
void tegra_pmc_sata_pwrgt_update(unsigned long mask, unsigned long val)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pwr_lock, flags);
|
|
if (pmc->soc->sata_power_gate_in_misc)
|
|
tegra_pmc_misc_register_update(TEGRA_PMC_SATA_PWRGT_0,
|
|
mask, val);
|
|
else
|
|
tegra_pmc_register_update(TEGRA_PMC_SATA_PWRGT_0,
|
|
mask, val);
|
|
spin_unlock_irqrestore(&pwr_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_sata_pwrgt_update);
|
|
|
|
unsigned long tegra_pmc_sata_pwrgt_get(void)
|
|
{
|
|
if (pmc->soc->sata_power_gate_in_misc)
|
|
return tegra_pmc_misc_readl(TEGRA_PMC_SATA_PWRGT_0);
|
|
else
|
|
return tegra_pmc_reg_readl(TEGRA_PMC_SATA_PWRGT_0);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_sata_pwrgt_get);
|
|
|
|
int tegra_pmc_save_se_context_buffer_address(u32 add)
|
|
{
|
|
tegra_pmc_reg_writel(add, TEGRA_PMC_SCRATCH43);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_save_se_context_buffer_address);
|
|
|
|
u32 tegra_pmc_get_se_context_buffer_address(void)
|
|
{
|
|
return tegra_pmc_reg_readl(TEGRA_PMC_SCRATCH43);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_get_se_context_buffer_address);
|
|
|
|
/* cleans io dpd settings from bootloader during kernel init */
|
|
static void _tegra_bl_io_dpd_cleanup(void)
|
|
{
|
|
int i;
|
|
unsigned int dpd_mask;
|
|
unsigned int dpd_status;
|
|
|
|
pr_info("Clear bootloader IO dpd settings\n");
|
|
/* clear all dpd requests from bootloader */
|
|
for (i = 0; i < ARRAY_SIZE(t3_io_dpd_req_regs); i++) {
|
|
dpd_mask = ((1 << t3_io_dpd_req_regs[i].dpd_code_lsb) - 1);
|
|
dpd_mask |= (IO_DPD_CODE_OFF <<
|
|
t3_io_dpd_req_regs[i].dpd_code_lsb);
|
|
tegra_pmc_writel(dpd_mask, t3_io_dpd_req_regs[i].req_reg_off);
|
|
/* dpd status register is next to req reg in tegra3 */
|
|
dpd_status =
|
|
tegra_pmc_readl(t3_io_dpd_req_regs[i].req_reg_off + 4);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void tegra_pmc_io_dpd_clear(void)
|
|
{
|
|
_tegra_bl_io_dpd_cleanup();
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_io_dpd_clear);
|
|
|
|
/* T210 USB2 SLEEPWALK APIs */
|
|
int tegra_pmc_utmi_phy_enable_sleepwalk(int port, enum usb_device_speed speed,
|
|
struct tegra_utmi_pad_config *config)
|
|
{
|
|
u32 reg;
|
|
|
|
pr_info("PMC %s : port %d, speed %d\n", __func__, port, speed);
|
|
|
|
/* ensure sleepwalk logic is disabled */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~UTMIP_MASTER_ENABLE(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
/* ensure sleepwalk logics are in low power mode */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_MASTER_CONFIG);
|
|
reg |= UTMIP_PWR(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_MASTER_CONFIG);
|
|
|
|
/* set debounce time */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_DEBOUNCE_DEL);
|
|
reg &= ~UTMIP_LINE_DEB_CNT(~0);
|
|
reg |= UTMIP_LINE_DEB_CNT(0x1);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_DEBOUNCE_DEL);
|
|
|
|
/* ensure fake events of sleepwalk logic are desiabled */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_FAKE(port));
|
|
reg &= ~(UTMIP_FAKE_USBOP_VAL(port) | UTMIP_FAKE_USBON_VAL(port) |
|
|
UTMIP_FAKE_USBOP_EN(port) | UTMIP_FAKE_USBON_EN(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_FAKE(port));
|
|
|
|
/* ensure wake events of sleepwalk logic are not latched */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg &= ~UTMIP_LINE_WAKEUP_EN(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
/* disable wake event triggers of sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~UTMIP_WAKE_VAL(port, ~0);
|
|
reg |= UTMIP_WAKE_VAL_NONE(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
/* power down the line state detectors of the pad */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
/* save state per speed */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SAVED_STATE(port));
|
|
reg &= ~SPEED(port, ~0);
|
|
if (speed == USB_SPEED_HIGH)
|
|
reg |= UTMI_HS(port);
|
|
else if (speed == USB_SPEED_FULL)
|
|
reg |= UTMI_FS(port);
|
|
else if (speed == USB_SPEED_LOW)
|
|
reg |= UTMI_LS(port);
|
|
else
|
|
reg |= UTMI_RST(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SAVED_STATE(port));
|
|
|
|
/* enable the trigger of the sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port));
|
|
reg |= UTMIP_LINEVAL_WALK_EN(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port));
|
|
|
|
/* reset the walk pointer and clear the alarm of the sleepwalk logic,
|
|
* as well as capture the configuration of the USB2.0 pad
|
|
*/
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
reg |= (UTMIP_CLR_WALK_PTR(port) | UTMIP_CLR_WAKE_ALARM(port) |
|
|
UTMIP_CAP_CFG(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
|
|
/* program electrical parameters read from XUSB PADCTL */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_TERM_PAD_CFG);
|
|
reg &= ~(TCTRL_VAL(~0) | PCTRL_VAL(~0));
|
|
reg |= (TCTRL_VAL(config->tctrl) | PCTRL_VAL(config->pctrl));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_TERM_PAD_CFG);
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_PAD_CFGX(port));
|
|
reg &= ~RPD_CTRL_PX(~0);
|
|
reg |= RPD_CTRL_PX(config->rpd_ctrl);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_PAD_CFGX(port));
|
|
|
|
/* setup the pull-ups and pull-downs of the signals during the four
|
|
* stages of sleepwalk.
|
|
* if device is connected, program sleepwalk logic to maintain a J and
|
|
* keep driving K upon seeing remote wake.
|
|
*/
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_SLEEPWALK_PX(port));
|
|
reg = (UTMIP_USBOP_RPD_A | UTMIP_USBOP_RPD_B | UTMIP_USBOP_RPD_C |
|
|
UTMIP_USBOP_RPD_D);
|
|
reg |= (UTMIP_USBON_RPD_A | UTMIP_USBON_RPD_B | UTMIP_USBON_RPD_C |
|
|
UTMIP_USBON_RPD_D);
|
|
if (speed == USB_SPEED_UNKNOWN) {
|
|
reg |= (UTMIP_HIGHZ_A | UTMIP_HIGHZ_B | UTMIP_HIGHZ_C |
|
|
UTMIP_HIGHZ_D);
|
|
} else if ((speed == USB_SPEED_HIGH) || (speed == USB_SPEED_FULL)) {
|
|
/* J state: D+/D- = high/low, K state: D+/D- = low/high */
|
|
reg |= UTMIP_HIGHZ_A;
|
|
reg |= UTMIP_AP_A;
|
|
reg |= (UTMIP_AN_B | UTMIP_AN_C | UTMIP_AN_D);
|
|
} else if (speed == USB_SPEED_LOW) {
|
|
/* J state: D+/D- = low/high, K state: D+/D- = high/low */
|
|
reg |= UTMIP_HIGHZ_A;
|
|
reg |= UTMIP_AN_A;
|
|
reg |= (UTMIP_AP_B | UTMIP_AP_C | UTMIP_AP_D);
|
|
}
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_SLEEPWALK_PX(port));
|
|
|
|
/* power up the line state detectors of the pad */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg &= ~(USBOP_VAL_PD(port) | USBON_VAL_PD(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
usleep_range(50, 100);
|
|
|
|
/* switch the electric control of the USB2.0 pad to PMC */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg |= (UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) |
|
|
UTMIP_TCTRL_USE_PMC(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG1);
|
|
reg |= (UTMIP_RPD_CTRL_USE_PMC_PX(port) |
|
|
UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG1);
|
|
|
|
/* set the wake signaling trigger events */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~UTMIP_WAKE_VAL(port, ~0);
|
|
reg |= UTMIP_WAKE_VAL_ANY(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
/* enable the wake detection */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg |= UTMIP_MASTER_ENABLE(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg |= UTMIP_LINE_WAKEUP_EN(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_utmi_phy_enable_sleepwalk);
|
|
|
|
int tegra_pmc_utmi_phy_disable_sleepwalk(int port)
|
|
{
|
|
u32 reg;
|
|
|
|
pr_info("PMC %s : port %d\n", __func__, port);
|
|
|
|
/* disable the wake detection */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~UTMIP_MASTER_ENABLE(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg &= ~UTMIP_LINE_WAKEUP_EN(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
/* switch the electric control of the USB2.0 pad to XUSB or USB2 */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~(UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) |
|
|
UTMIP_TCTRL_USE_PMC(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG1);
|
|
reg &= ~(UTMIP_RPD_CTRL_USE_PMC_PX(port) |
|
|
UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG1);
|
|
|
|
/* disable wake event triggers of sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
reg &= ~UTMIP_WAKE_VAL(port, ~0);
|
|
reg |= UTMIP_WAKE_VAL_NONE(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_SLEEP_CFG(port));
|
|
|
|
/* power down the line state detectors of the port */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port));
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
/* clear alarm of the sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
reg |= UTMIP_CLR_WAKE_ALARM(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_utmi_phy_disable_sleepwalk);
|
|
|
|
int tegra_pmc_hsic_phy_enable_sleepwalk(int port)
|
|
{
|
|
u32 reg;
|
|
|
|
pr_info("PMC %s : port %dn", __func__, port);
|
|
|
|
/* ensure sleepwalk logic is disabled */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg &= ~UHSIC_MASTER_ENABLE;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
/* ensure sleepwalk logics are in low power mode */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_MASTER_CONFIG);
|
|
reg |= UHSIC_PWR(port);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_MASTER_CONFIG);
|
|
|
|
/* set debounce time */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_DEBOUNCE_DEL);
|
|
reg &= ~UHSIC_LINE_DEB_CNT(~0);
|
|
reg |= UHSIC_LINE_DEB_CNT(0x1);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_DEBOUNCE_DEL);
|
|
|
|
/* ensure fake events of sleepwalk logic are desiabled */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_FAKE);
|
|
reg &= ~(UHSIC_FAKE_STROBE_VAL | UHSIC_FAKE_DATA_VAL |
|
|
UHSIC_FAKE_STROBE_EN | UHSIC_FAKE_DATA_EN);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_FAKE);
|
|
|
|
/* ensure wake events of sleepwalk logic are not latched */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg &= ~UHSIC_LINE_WAKEUP_EN;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
/* disable wake event triggers of sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg &= ~UHSIC_WAKE_VAL(~0);
|
|
reg |= UHSIC_WAKE_VAL_NONE;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
/* power down the line state detectors of the port */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
/* save state, HSIC always comes up as HS */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SAVED_STATE);
|
|
reg &= ~UHSIC_MODE(~0);
|
|
reg |= UHSIC_HS;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SAVED_STATE);
|
|
|
|
/* enable the trigger of the sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEPWALK_CFG);
|
|
reg |= (UHSIC_WAKE_WALK_EN | UHSIC_LINEVAL_WALK_EN);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEPWALK_CFG);
|
|
|
|
/* reset the walk pointer and clear the alarm of the sleepwalk logic,
|
|
* as well as capture the configuration of the USB2.0 port
|
|
*/
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
reg |= (UHSIC_CLR_WALK_PTR | UHSIC_CLR_WAKE_ALARM);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
|
|
/* setup the pull-ups and pull-downs of the signals during the four
|
|
* stages of sleepwalk.
|
|
* maintain a HSIC IDLE and keep driving HSIC RESUME upon remote wake
|
|
*/
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEPWALK_P0);
|
|
reg = (UHSIC_DATA0_RPD_A | UHSIC_DATA0_RPU_B | UHSIC_DATA0_RPU_C |
|
|
UHSIC_DATA0_RPU_D);
|
|
reg |= (UHSIC_STROBE_RPU_A | UHSIC_STROBE_RPD_B | UHSIC_STROBE_RPD_C |
|
|
UHSIC_STROBE_RPD_D);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEPWALK_P0);
|
|
|
|
/* power up the line state detectors of the port */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg &= ~(STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
usleep_range(50, 100);
|
|
|
|
/* set the wake signaling trigger events */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg &= ~UHSIC_WAKE_VAL(~0);
|
|
reg |= UHSIC_WAKE_VAL_SD10;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
/* enable the wake detection */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg |= UHSIC_MASTER_ENABLE;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg |= UHSIC_LINE_WAKEUP_EN;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_hsic_phy_enable_sleepwalk);
|
|
|
|
int tegra_pmc_hsic_phy_disable_sleepwalk(int port)
|
|
{
|
|
u32 reg;
|
|
|
|
pr_info("PMC %s : port %dn", __func__, port);
|
|
|
|
/* disable the wake detection */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg &= ~UHSIC_MASTER_ENABLE;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
reg &= ~UHSIC_LINE_WAKEUP_EN;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_LINE_WAKEUP);
|
|
|
|
/* disable wake event triggers of sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
reg &= ~UHSIC_WAKE_VAL(~0);
|
|
reg |= UHSIC_WAKE_VAL_NONE;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UHSIC_SLEEP_CFG);
|
|
|
|
/* power down the line state detectors of the port */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_USB_AO);
|
|
reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
|
|
tegra_pmc_writel(reg, APBDEV_PMC_USB_AO);
|
|
|
|
/* clear alarm of the sleepwalk logic */
|
|
reg = tegra_pmc_readl(APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
reg |= UHSIC_CLR_WAKE_ALARM;
|
|
tegra_pmc_writel(reg, APBDEV_PMC_UTMIP_UHSIC_TRIGGERS);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_hsic_phy_disable_sleepwalk);
|
|
|
|
void tegra_pmc_fuse_control_ps18_latch_set(void)
|
|
{
|
|
u32 val;
|
|
|
|
if (!pmc->soc->has_ps18)
|
|
return;
|
|
|
|
if (pmc->soc->has_misc_base_address) {
|
|
val = tegra_pmc_misc_readl(TEGRA_PMC_FUSE_CTRL);
|
|
val &= ~(PMC_FUSE_CTRL_PS18_LATCH_CLEAR);
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
mdelay(1);
|
|
val |= PMC_FUSE_CTRL_PS18_LATCH_SET;
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
} else {
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_FUSE_CTRL);
|
|
val &= ~(PMC_FUSE_CTRL_PS18_LATCH_CLEAR);
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
mdelay(1);
|
|
val |= PMC_FUSE_CTRL_PS18_LATCH_SET;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
|
|
mdelay(1);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_fuse_control_ps18_latch_set);
|
|
|
|
void tegra_pmc_fuse_control_ps18_latch_clear(void)
|
|
{
|
|
u32 val;
|
|
|
|
if (!pmc->soc->has_ps18)
|
|
return;
|
|
|
|
if (pmc->soc->has_misc_base_address) {
|
|
val = tegra_pmc_misc_readl(TEGRA_PMC_FUSE_CTRL);
|
|
val &= ~(PMC_FUSE_CTRL_PS18_LATCH_SET);
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
mdelay(1);
|
|
val |= PMC_FUSE_CTRL_PS18_LATCH_CLEAR;
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
} else {
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_FUSE_CTRL);
|
|
val &= ~(PMC_FUSE_CTRL_PS18_LATCH_SET);
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
mdelay(1);
|
|
val |= PMC_FUSE_CTRL_PS18_LATCH_CLEAR;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
mdelay(1);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_fuse_control_ps18_latch_clear);
|
|
|
|
void tegra_pmc_fuse_disable_mirroring(void)
|
|
{
|
|
u32 val;
|
|
|
|
if (pmc->soc->has_misc_base_address) {
|
|
val = tegra_pmc_misc_readl(TEGRA_PMC_FUSE_CTRL);
|
|
if (val & PMC_FUSE_CTRL_ENABLE_REDIRECTION) {
|
|
val &= ~PMC_FUSE_CTRL_ENABLE_REDIRECTION;
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
} else {
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_FUSE_CTRL);
|
|
if (val & PMC_FUSE_CTRL_ENABLE_REDIRECTION) {
|
|
val &= ~PMC_FUSE_CTRL_ENABLE_REDIRECTION;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_fuse_disable_mirroring);
|
|
|
|
void tegra_pmc_fuse_enable_mirroring(void)
|
|
{
|
|
u32 val;
|
|
|
|
if (pmc->soc->has_misc_base_address) {
|
|
val = tegra_pmc_misc_readl(TEGRA_PMC_FUSE_CTRL);
|
|
if (!(val & PMC_FUSE_CTRL_ENABLE_REDIRECTION)) {
|
|
val |= PMC_FUSE_CTRL_ENABLE_REDIRECTION;
|
|
tegra_pmc_misc_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
} else {
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_FUSE_CTRL);
|
|
if (!(val & PMC_FUSE_CTRL_ENABLE_REDIRECTION)) {
|
|
val |= PMC_FUSE_CTRL_ENABLE_REDIRECTION;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_FUSE_CTRL);
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_fuse_enable_mirroring);
|
|
|
|
bool tegra_pmc_fuse_is_redirection_enabled(void)
|
|
{
|
|
u32 val;
|
|
|
|
if (pmc->soc->has_misc_base_address)
|
|
val = tegra_pmc_misc_readl(TEGRA_PMC_FUSE_CTRL);
|
|
else
|
|
val = tegra_pmc_reg_readl(TEGRA_PMC_FUSE_CTRL);
|
|
|
|
if (val & PMC_FUSE_CTRL_ENABLE_REDIRECTION_STICKY)
|
|
return true;
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_fuse_is_redirection_enabled);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static void tegra_pmc_remove_dpd_req(void)
|
|
{
|
|
/* Clear DPD req */
|
|
tegra_pmc_reg_writel(io_dpd_reg | IO_DPD_REQ_CODE_OFF,
|
|
TEGRA_PMC_IO_DPD_REQ);
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_REQ); /* unblock posted write */
|
|
/* delay apb_clk * (SEL_DPD_TIM*5) */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
tegra_pmc_reg_writel(io_dpd2_reg | IO_DPD_REQ_CODE_OFF,
|
|
TEGRA_PMC_IO_DPD2_REQ);
|
|
tegra_pmc_readl(TEGRA_PMC_IO_DPD2_REQ); /* unblock posted write */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
}
|
|
|
|
static void tegra_pmc_clear_dpd_sample(void)
|
|
{
|
|
/* Clear DPD sample */
|
|
tegra_pmc_reg_writel(0x0, TEGRA_PMC_IO_DPD_SAMPLE);
|
|
}
|
|
|
|
static void tegra_pmc_add_wakeup_event(struct of_phandle_args *ph_args,
|
|
struct device *dev,
|
|
struct device_node *np)
|
|
{
|
|
struct platform_device *pdev;
|
|
struct pmc_wakeup *pmc_wake_source;
|
|
struct irq_desc *irqd;
|
|
struct irq_data *irq_data;
|
|
int pmc_wake_type, wake;
|
|
int irq, pmc_trigger_type;
|
|
|
|
if (ph_args->np != tegra_lp0_wakeup.of_node)
|
|
return;
|
|
if (ph_args->args_count != PMC_OF_ARGS_COUNT)
|
|
return;
|
|
|
|
pdev = to_platform_device(dev);
|
|
irq = platform_get_irq(pdev, 0);
|
|
pmc_wake_type = ph_args->args[PMC_WAKE_TYPE_INDEX];
|
|
|
|
switch (pmc_wake_type) {
|
|
case PMC_WAKE_TYPE_GPIO:
|
|
if (irq < 0) {
|
|
int gpio;
|
|
|
|
gpio = of_get_named_gpio(np, "gpios", 0);
|
|
irq = gpio_to_irq(gpio);
|
|
if (WARN_ON(irq < 0))
|
|
return;
|
|
}
|
|
irqd = irq_to_desc(irq);
|
|
irq_data = &irqd->irq_data;
|
|
pmc_trigger_type = irqd_get_trigger_type(irq_data);
|
|
break;
|
|
case PMC_WAKE_TYPE_EVENT:
|
|
pmc_trigger_type = ph_args->args[PMC_TRIGGER_TYPE_INDEX];
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
pmc_wake_source = kzalloc(sizeof(*pmc_wake_source), GFP_KERNEL);
|
|
if (!pmc_wake_source)
|
|
return;
|
|
|
|
pmc_wake_source->wake_type = pmc_wake_type;
|
|
pmc_wake_source->irq_num = irq;
|
|
pmc_wake_source->wake_mask_offset = ph_args->args[PMC_WAKE_MASK_INDEX];
|
|
wake = pmc_wake_source->wake_mask_offset;
|
|
|
|
list_add_tail(&pmc_wake_source->list, &tegra_lp0_wakeup.wake_list);
|
|
|
|
tegra_lp0_wakeup.enable |= 1ULL << wake;
|
|
switch (pmc_trigger_type) {
|
|
case IRQF_TRIGGER_FALLING:
|
|
case IRQF_TRIGGER_LOW:
|
|
tegra_lp0_wakeup.level &= ~(1ULL << wake);
|
|
tegra_lp0_wakeup.level_any &= ~(1ULL << wake);
|
|
break;
|
|
case IRQF_TRIGGER_HIGH:
|
|
case IRQF_TRIGGER_RISING:
|
|
tegra_lp0_wakeup.level |= (1ULL << wake);
|
|
tegra_lp0_wakeup.level_any &= ~(1ULL << wake);
|
|
break;
|
|
case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING:
|
|
tegra_lp0_wakeup.level_any |= (1ULL << wake);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void tegra_of_device_add_pmc_wake(struct device *dev)
|
|
{
|
|
struct of_phandle_args ph_args;
|
|
struct device_node *np = NULL;
|
|
int child_node_num, i = 0;
|
|
|
|
child_node_num = of_get_child_count(dev->of_node);
|
|
while (!of_parse_phandle_with_args(dev->of_node,
|
|
"nvidia,pmc-wakeup",
|
|
"#nvidia,wake-cells",
|
|
i++, &ph_args)) {
|
|
tegra_pmc_add_wakeup_event(&ph_args, dev, dev->of_node);
|
|
of_node_put(ph_args.np);
|
|
}
|
|
if (child_node_num > 0) {
|
|
for_each_child_of_node(dev->of_node, np) {
|
|
i = 0;
|
|
while (!of_parse_phandle_with_args(np,
|
|
"nvidia,pmc-wakeup",
|
|
"#nvidia,wake-cells",
|
|
i++, &ph_args)) {
|
|
tegra_pmc_add_wakeup_event(&ph_args, dev, np);
|
|
of_node_put(ph_args.np);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int tegra_pmc_wake_notifier_call(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
|
|
switch (event) {
|
|
case BUS_NOTIFY_BOUND_DRIVER:
|
|
if (dev->of_node)
|
|
tegra_of_device_add_pmc_wake(dev);
|
|
break;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block tegra_pmc_wake_notifier = {
|
|
.notifier_call = tegra_pmc_wake_notifier_call,
|
|
};
|
|
|
|
static int __init tegra_pmc_lp0_wakeup_init(void)
|
|
{
|
|
if (!soc_is_tegra210_n_before())
|
|
return 0;
|
|
|
|
bus_register_notifier(&platform_bus_type, &tegra_pmc_wake_notifier);
|
|
return 0;
|
|
}
|
|
arch_initcall(tegra_pmc_lp0_wakeup_init);
|
|
|
|
static inline void write_pmc_wake_mask(u64 value)
|
|
{
|
|
pr_info("PMC wake enable = 0x%llx\n", value);
|
|
tegra_pmc_reg_writel((u32)value, TEGRA_PMC_WAKE_MASK);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
tegra_pmc_reg_writel((u32)(value >> 32), TEGRA_PMC_WAKE2_MASK);
|
|
}
|
|
|
|
static inline u64 read_pmc_wake_level(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_WAKE_LEVEL);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_reg_readl(TEGRA_PMC_WAKE2_LEVEL)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void write_pmc_wake_level(u64 value)
|
|
{
|
|
pr_info("PMC wake level = 0x%llx\n", value);
|
|
tegra_pmc_reg_writel((u32)value, TEGRA_PMC_WAKE_LEVEL);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
tegra_pmc_reg_writel((u32)(value >> 32), TEGRA_PMC_WAKE2_LEVEL);
|
|
}
|
|
|
|
static inline u64 read_pmc_wake_status(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_WAKE_STATUS);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_reg_readl(TEGRA_PMC_WAKE2_STATUS)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void clear_pmc_wake_status(void)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_WAKE_STATUS);
|
|
if (reg)
|
|
tegra_pmc_reg_writel(reg, TEGRA_PMC_WAKE_STATUS);
|
|
if (tegra_get_chip_id() != TEGRA20) {
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_WAKE2_STATUS);
|
|
if (reg)
|
|
tegra_pmc_reg_writel(reg, TEGRA_PMC_WAKE2_STATUS);
|
|
}
|
|
}
|
|
|
|
static inline u64 read_pmc_sw_wake_status(void)
|
|
{
|
|
u64 reg;
|
|
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_SW_WAKE_STATUS);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
reg |= ((u64)tegra_pmc_reg_readl(TEGRA_PMC_SW_WAKE2_STATUS)) << 32;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static inline void clear_pmc_sw_wake_status(void)
|
|
{
|
|
tegra_pmc_reg_writel(0, TEGRA_PMC_SW_WAKE_STATUS);
|
|
if (tegra_get_chip_id() != TEGRA20)
|
|
tegra_pmc_reg_writel(0, TEGRA_PMC_SW_WAKE2_STATUS);
|
|
}
|
|
|
|
/* translate lp0 wake sources back into irqs to catch edge triggered wakeups */
|
|
static void tegra_pmc_wake_syscore_resume(void)
|
|
{
|
|
struct pmc_wakeup *wake;
|
|
struct irq_desc *desc;
|
|
u64 wake_status = read_pmc_wake_status();
|
|
|
|
pr_info("PMC wake status = 0x%llx\n", wake_status);
|
|
|
|
list_for_each_entry(wake, &tegra_lp0_wakeup.wake_list, list) {
|
|
if (!(wake_status & BIT(wake->wake_mask_offset)))
|
|
continue;
|
|
|
|
if (wake->irq_num <= 0) {
|
|
pr_info("Resume caused by PMC WAKE%d\n",
|
|
wake->wake_mask_offset);
|
|
continue;
|
|
}
|
|
|
|
desc = irq_to_desc(wake->irq_num);
|
|
if (!desc || !desc->action || !desc->action->name) {
|
|
pr_info("Resume caused by PMC WAKE%d, irq %d\n",
|
|
wake->wake_mask_offset, wake->irq_num);
|
|
log_wakeup_reason(wake->irq_num);
|
|
continue;
|
|
}
|
|
|
|
pr_info("Resume caused by PMC WAKE%d, %s\n",
|
|
wake->wake_mask_offset, desc->action->name);
|
|
generic_handle_irq(wake->irq_num);
|
|
}
|
|
}
|
|
|
|
static int tegra_pmc_wake_syscore_suspend(void)
|
|
{
|
|
u32 reg;
|
|
u64 status;
|
|
u64 lvl;
|
|
u64 wake_level;
|
|
u64 wake_enb;
|
|
|
|
clear_pmc_sw_wake_status();
|
|
|
|
/* enable PMC wake */
|
|
reg = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
reg |= PMC_CNTRL_LATCH_WAKEUPS;
|
|
tegra_pmc_reg_writel(reg, TEGRA_PMC_CNTRL);
|
|
udelay(120);
|
|
|
|
reg &= ~PMC_CNTRL_LATCH_WAKEUPS;
|
|
tegra_pmc_reg_writel(reg, TEGRA_PMC_CNTRL);
|
|
udelay(120);
|
|
|
|
status = read_pmc_sw_wake_status();
|
|
|
|
lvl = read_pmc_wake_level();
|
|
|
|
/*
|
|
* flip the wakeup trigger for any-edge triggered pads
|
|
* which are currently asserting as wakeups
|
|
*/
|
|
lvl ^= status;
|
|
|
|
lvl &= tegra_lp0_wakeup.level_any;
|
|
|
|
wake_level = lvl | tegra_lp0_wakeup.level;
|
|
wake_enb = tegra_lp0_wakeup.enable;
|
|
|
|
/* Clear PMC Wake Status registers while going to suspend */
|
|
clear_pmc_wake_status();
|
|
write_pmc_wake_level(wake_level);
|
|
write_pmc_wake_mask(wake_enb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_suspend(void)
|
|
{
|
|
#ifdef CONFIG_ARM
|
|
tegra_pmc_reg_writel(virt_to_phys(tegra_resume), TEGRA_PMC_SCRATCH41);
|
|
#else /* CONFIG_ARM64 */
|
|
enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode();
|
|
tegra_pmc_enter_suspend_mode(mode);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_pmc_resume(void)
|
|
{
|
|
u32 chip_id = tegra_get_chip_id();
|
|
|
|
switch (chip_id) {
|
|
case TEGRA114:
|
|
case TEGRA124:
|
|
case TEGRA132:
|
|
tegra_pmc_remove_dpd_req();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tegra_pmc_clear_dpd_sample();
|
|
|
|
/* Clear DPD Enable */
|
|
switch (chip_id) {
|
|
case TEGRA20:
|
|
case TEGRA30:
|
|
case TEGRA114:
|
|
break;
|
|
default:
|
|
/* Don't access the secure DPD_ENABLE register
|
|
* if the secure-pmc flag is enabled.
|
|
*/
|
|
if (!get_secure_pmc_setting())
|
|
tegra_pmc_reg_writel(0x0, TEGRA_PMC_IO_DPD_ENABLE);
|
|
break;
|
|
}
|
|
|
|
#ifdef CONFIG_ARM
|
|
tegra_pmc_reg_writel(0x0, TEGRA_PMC_SCRATCH41);
|
|
#endif
|
|
}
|
|
|
|
static void set_core_power_timers(void)
|
|
{
|
|
unsigned long osc, pmu, off;
|
|
|
|
osc = DIV_ROUND_UP_ULL(pmc->core_osc_time * 8192, 1000000);
|
|
pmu = DIV_ROUND_UP_ULL(pmc->core_pmu_time * 32768, 1000000);
|
|
off = DIV_ROUND_UP_ULL(pmc->core_off_time * 32768, 1000000);
|
|
|
|
tegra_pmc_reg_writel(((osc << 8) & 0xff00) | (pmu & 0xff),
|
|
TEGRA_PMC_COREPWRGOOD_TIMER);
|
|
tegra_pmc_reg_writel(off, TEGRA_PMC_COREPWROFF_TIMER);
|
|
}
|
|
|
|
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 boot_flag, cntrl_value;
|
|
|
|
cntrl_value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
cntrl_value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
|
|
if (pmc->combined_req)
|
|
cntrl_value &= ~PMC_CNTRL_PWRREQ_OE;
|
|
else
|
|
cntrl_value |= PMC_CNTRL_PWRREQ_OE;
|
|
cntrl_value |= PMC_CNTRL_CPU_PWRREQ_OE;
|
|
|
|
switch (mode) {
|
|
case TEGRA_SUSPEND_SC7:
|
|
rate = 32768;
|
|
break;
|
|
|
|
case TEGRA_SUSPEND_LP0:
|
|
/*
|
|
* Enable DPD sample to trigger sampling pads data and direction
|
|
* in which pad will be driven during LP0 mode.
|
|
*/
|
|
tegra_pmc_reg_writel(0x1, TEGRA_PMC_IO_DPD_SAMPLE);
|
|
|
|
/*
|
|
* Power down IO logic
|
|
*/
|
|
switch (tegra_get_chip_id()) {
|
|
case TEGRA114:
|
|
case TEGRA124:
|
|
case TEGRA132:
|
|
io_dpd_reg = IO_DPD_CSIA | IO_DPD_CSIB | IO_DPD_DSI |
|
|
IO_DPD_MIPI_BIAS | IO_DPD_PEX_BIAS |
|
|
IO_DPD_PEX_CLK1 | IO_DPD_PEX_CLK2 |
|
|
IO_DPD_PEX_CLK3 | IO_DPD_DAC | IO_DPD_USB0 |
|
|
IO_DPD_USB1 | IO_DPD_USB2 | IO_DPD_USB_BIAS |
|
|
IO_DPD_UART | IO_DPD_BB | IO_DPD_VI |
|
|
IO_DPD_AUDIO | IO_DPD_LCD | IO_DPD_HSIC;
|
|
io_dpd2_reg = IO_DPD2_PEX_CNTRL | IO_DPD2_SDMMC1 |
|
|
IO_DPD2_SDMMC3 | IO_DPD2_SDMMC4 | IO_DPD2_CAM |
|
|
IO_DPD2_RES_RAIL | IO_DPD2_HV | IO_DPD2_DSIB |
|
|
IO_DPD2_DSIC | IO_DPD2_DSID | IO_DPD2_CSIC |
|
|
IO_DPD2_CSID | IO_DPD2_CSIE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
tegra_pmc_reg_writel(io_dpd_reg | IO_DPD_REQ_CODE_ON,
|
|
TEGRA_PMC_IO_DPD_REQ);
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_REQ); /* unblock posted write */
|
|
|
|
/* delay apb_clk * (SEL_DPD_TIM*5) */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
tegra_pmc_reg_writel(io_dpd2_reg | IO_DPD_REQ_CODE_ON,
|
|
TEGRA_PMC_IO_DPD2_REQ);
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD2_REQ); /* unblock posted write */
|
|
udelay(DPD_STATE_CHANGE_DELAY);
|
|
|
|
/* Set warmboot flag */
|
|
boot_flag = tegra_pmc_reg_readl(TEGRA_PMC_SCRATCH0);
|
|
tegra_pmc_reg_writel(boot_flag | 1, TEGRA_PMC_SCRATCH0);
|
|
|
|
if (pmc->lp0_vec_phys != 0)
|
|
tegra_pmc_reg_writel(pmc->lp0_vec_phys, TEGRA_PMC_SCRATCH1);
|
|
cntrl_value |= PMC_CNTRL_SIDE_EFFECT_LP0;
|
|
|
|
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_reg_writel(ticks, TEGRA_PMC_CPUPWRGOOD_TIMER);
|
|
|
|
ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
|
|
do_div(ticks, USEC_PER_SEC);
|
|
tegra_pmc_reg_writel(ticks, TEGRA_PMC_CPUPWROFF_TIMER);
|
|
|
|
wmb();
|
|
|
|
pmc->rate = rate;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(cntrl_value, TEGRA_PMC_CNTRL);
|
|
}
|
|
#else
|
|
#define tegra_pmc_suspend NULL
|
|
#define tegra_pmc_resume NULL
|
|
#define tegra_pmc_wake_syscore_suspend NULL
|
|
#define tegra_pmc_wake_syscore_resume NULL
|
|
static inline void set_core_power_timers(void) { }
|
|
#endif
|
|
|
|
static struct syscore_ops tegra_pmc_syscore_ops = {
|
|
.suspend = tegra_pmc_suspend,
|
|
.resume = tegra_pmc_resume,
|
|
.save = tegra_pmc_suspend,
|
|
.restore = tegra_pmc_resume,
|
|
};
|
|
|
|
static struct syscore_ops tegra_pmc_wake_syscore_ops = {
|
|
.suspend = tegra_pmc_wake_syscore_suspend,
|
|
.resume = tegra_pmc_wake_syscore_resume,
|
|
.save = tegra_pmc_wake_syscore_suspend,
|
|
.restore = tegra_pmc_wake_syscore_resume,
|
|
};
|
|
|
|
static void tegra_pmc_syscore_init(void)
|
|
{
|
|
register_syscore_ops(&tegra_pmc_syscore_ops);
|
|
}
|
|
|
|
static void tegra_pmc_wake_syscore_init(void)
|
|
{
|
|
register_syscore_ops(&tegra_pmc_wake_syscore_ops);
|
|
}
|
|
|
|
/* PMC Bootrom commands */
|
|
static int tegra_pmc_parse_bootrom_cmd(struct device *dev,
|
|
struct device_node *np,
|
|
struct tegra_bootrom_commands **br_cmds)
|
|
{
|
|
struct device_node *child;
|
|
struct tegra_bootrom_commands *bcommands;
|
|
int *command_ptr;
|
|
struct tegra_bootrom_block *block;
|
|
int nblocks;
|
|
u32 reg, data, pval;
|
|
u32 *wr_commands;
|
|
int count, nblock, ncommands, i, reg_shift;
|
|
int ret;
|
|
int sz_bcommand, sz_blocks;
|
|
|
|
nblocks = of_get_available_child_count(np);
|
|
if (!nblocks) {
|
|
dev_info(dev, "PMC: No Bootrom Command\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
count = 0;
|
|
for_each_available_child_of_node(np, child) {
|
|
ret = of_property_count_u32_elems(child,
|
|
"nvidia,write-commands");
|
|
if (ret < 0) {
|
|
dev_err(dev, "PMC: Node %s does not have write-commnds\n",
|
|
child->full_name);
|
|
return -EINVAL;
|
|
}
|
|
count += ret / 2;
|
|
}
|
|
|
|
sz_bcommand = (sizeof(*bcommands) + 0x3) & ~0x3;
|
|
sz_blocks = (sizeof(*block) + 0x3) & ~0x3;
|
|
bcommands = devm_kzalloc(dev, sz_bcommand + nblocks * sz_blocks +
|
|
count * sizeof(u32), GFP_KERNEL);
|
|
if (!bcommands)
|
|
return -ENOMEM;
|
|
|
|
bcommands->nblocks = nblocks;
|
|
bcommands->blocks = (void *)bcommands + sz_bcommand;
|
|
command_ptr = (void *)bcommands->blocks + nblocks * sz_blocks;
|
|
|
|
of_property_read_u32(np, "nvidia,command-retries-count",
|
|
&bcommands->command_retry_count);
|
|
of_property_read_u32(np, "nvidia,delay-between-commands-us",
|
|
&bcommands->delay_between_commands);
|
|
|
|
ret = of_property_read_u32(np, "nvidia,wait-before-start-bus-clear-us",
|
|
&bcommands->wait_before_bus_clear);
|
|
if (ret < 0)
|
|
of_property_read_u32(np, "nvidia,wait-start-bus-clear-us",
|
|
&bcommands->wait_before_bus_clear);
|
|
|
|
nblock = 0;
|
|
for_each_available_child_of_node(np, child) {
|
|
block = &bcommands->blocks[nblock];
|
|
ret = of_property_read_u32(child, "reg", &pval);
|
|
if (ret) {
|
|
dev_err(dev, "PMC: Reg property missing on block %s\n",
|
|
child->name);
|
|
return ret;
|
|
}
|
|
block->address = pval;
|
|
of_property_read_string(child, "nvidia,command-names",
|
|
&block->name);
|
|
block->reg_8bits = !of_property_read_bool(child,
|
|
"nvidia,enable-16bit-register");
|
|
block->data_8bits = !of_property_read_bool(child,
|
|
"nvidia,enable-16bit-data");
|
|
block->i2c_controller = of_property_read_bool(child,
|
|
"nvidia,controller-type-i2c");
|
|
block->enable_reset = of_property_read_bool(child,
|
|
"nvidia,enable-controller-reset");
|
|
count = of_property_count_u32_elems(child,
|
|
"nvidia,write-commands");
|
|
ncommands = count / 2;
|
|
|
|
block->commands = command_ptr;
|
|
command_ptr += ncommands;
|
|
wr_commands = block->commands;
|
|
reg_shift = (block->data_8bits) ? 8 : 16;
|
|
for (i = 0; i < ncommands; ++i) {
|
|
of_property_read_u32_index(child,
|
|
"nvidia,write-commands",
|
|
i * 2, ®);
|
|
of_property_read_u32_index(child,
|
|
"nvidia,write-commands",
|
|
i * 2 + 1, &data);
|
|
|
|
wr_commands[i] = (data << reg_shift) | reg;
|
|
}
|
|
block->ncommands = ncommands;
|
|
nblock++;
|
|
}
|
|
*br_cmds = bcommands;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_read_bootrom_cmd(struct device *dev,
|
|
struct tegra_bootrom_commands **br_rst_cmds,
|
|
struct tegra_bootrom_commands **br_off_cmds)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *br_np, *rst_np, *off_np;
|
|
int ret;
|
|
|
|
*br_rst_cmds = NULL;
|
|
*br_off_cmds = NULL;
|
|
|
|
br_np = of_find_node_by_name(np, "bootrom-commands");
|
|
if (!br_np) {
|
|
dev_info(dev, "PMC: Bootrom commmands not found\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
rst_np = of_find_node_by_name(br_np, "reset-commands");
|
|
if (!rst_np) {
|
|
dev_info(dev, "PMC: bootrom-commands used for reset\n");
|
|
rst_np = br_np;
|
|
}
|
|
|
|
ret = tegra_pmc_parse_bootrom_cmd(dev, rst_np, br_rst_cmds);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (rst_np == br_np)
|
|
return 0;
|
|
|
|
off_np = of_find_node_by_name(br_np, "power-off-commands");
|
|
if (!off_np)
|
|
return 0;
|
|
ret = tegra_pmc_parse_bootrom_cmd(dev, off_np, br_off_cmds);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_configure_bootrom_scratch(struct device *dev,
|
|
struct tegra_bootrom_commands *br_commands)
|
|
{
|
|
struct tegra_bootrom_block *block;
|
|
int i, j, k;
|
|
u32 cmd;
|
|
int reg_offset = 1;
|
|
u32 reg_data_mask;
|
|
int cmd_pw;
|
|
u32 block_add, block_val, csum;
|
|
|
|
for (i = 0; i < br_commands->nblocks; ++i) {
|
|
block = &br_commands->blocks[i];
|
|
|
|
cmd = block->address & PMC_BR_COMMAND_I2C_ADD_MASK;
|
|
cmd |= block->ncommands << PMC_BR_COMMAND_WR_COMMANDS_SHIFT;
|
|
if (!block->reg_8bits || !block->data_8bits)
|
|
cmd |= BIT(PMC_BR_COMMAND_OPERAND_SHIFT);
|
|
|
|
if (block->enable_reset)
|
|
cmd |= BIT(PMC_BR_COMMAND_RST_EN_SHIFT);
|
|
|
|
cmd |= (block->controller_id & PMC_BR_COMMAND_CTRL_ID_MASK) <<
|
|
PMC_BR_COMMAND_CTRL_ID_SHIFT;
|
|
|
|
/* Checksum will be added after parsing from reg/data */
|
|
tegra_pmc_write_bootrom_command(reg_offset * 4, cmd);
|
|
block_add = reg_offset * 4;
|
|
block_val = cmd;
|
|
reg_offset++;
|
|
|
|
cmd_pw = (block->reg_8bits && block->data_8bits) ? 2 : 1;
|
|
reg_data_mask = (cmd_pw == 1) ? 0xFFFF : 0xFFFFFFFFUL;
|
|
csum = 0;
|
|
|
|
for (j = 0; j < block->ncommands; j++) {
|
|
cmd = block->commands[j] & reg_data_mask;
|
|
if (cmd_pw == 2) {
|
|
j++;
|
|
if (j == block->ncommands)
|
|
goto reg_update;
|
|
cmd |= (block->commands[j] & reg_data_mask) <<
|
|
16;
|
|
}
|
|
reg_update:
|
|
tegra_pmc_write_bootrom_command(reg_offset * 4, cmd);
|
|
for (k = 0; k < 4; ++k)
|
|
csum += (cmd >> (k * 8)) & 0xFF;
|
|
reg_offset++;
|
|
}
|
|
for (k = 0; k < 4; ++k)
|
|
csum += (block_val >> (k * 8)) & 0xFF;
|
|
csum = 0x100 - csum;
|
|
block_val = (block_val & 0xFF00FFFF) | ((csum & 0xFF) << 16);
|
|
tegra_pmc_write_bootrom_command(block_add, block_val);
|
|
}
|
|
|
|
cmd = br_commands->command_retry_count & 0x7;
|
|
cmd |= (br_commands->delay_between_commands & 0x1F) << 3;
|
|
cmd |= (br_commands->nblocks & 0x7) << 8;
|
|
cmd |= (br_commands->wait_before_bus_clear & 0x1F) << 11;
|
|
tegra_pmc_write_bootrom_command(0, cmd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_init_bootrom_power_off_cmd(struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
if (!br_off_commands) {
|
|
dev_info(dev, "PMC: Power Off Command not available\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = tegra_pmc_configure_bootrom_scratch(NULL, br_off_commands);
|
|
if (ret < 0) {
|
|
dev_err(dev, "PMC: Failed to configure power-off command: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_info(dev, "PMC: Successfully configure power-off commands\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_pmc_soc_power_off(void)
|
|
{
|
|
tegra_pmc_init_bootrom_power_off_cmd(pmc->dev);
|
|
tegra_pmc_reset_system();
|
|
}
|
|
|
|
static int tegra_pmc_init_boorom_cmds(struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = tegra_pmc_read_bootrom_cmd(dev, &br_rst_commands,
|
|
&br_off_commands);
|
|
if (ret < 0) {
|
|
if (ret == -ENOENT)
|
|
ret = 0;
|
|
else
|
|
dev_info(dev,
|
|
"PMC: Failed to read bootrom cmd: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
if (br_off_commands)
|
|
set_soc_specific_power_off(tegra_pmc_soc_power_off);
|
|
|
|
ret = tegra_pmc_configure_bootrom_scratch(dev, br_rst_commands);
|
|
if (ret < 0) {
|
|
dev_info(dev, "PMC: Failed to write bootrom scratch register: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_info(dev, "PMC: Successfully configure bootrom reset commands\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* IO Pads configurations */
|
|
static int tegra_pmc_io_pad_prepare(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
unsigned long rate, value;
|
|
|
|
if (pad->dpd == UINT_MAX)
|
|
return -ENOTSUPP;
|
|
|
|
if (!pmc->clk)
|
|
return 0;
|
|
|
|
rate = clk_get_rate(pmc->clk);
|
|
if (!rate) {
|
|
dev_err(pmc->dev, "Failed to get clock rate\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(DPD_SAMPLE_ENABLE, pad->dpd_sample_reg);
|
|
|
|
/* 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_reg_writel(value, pad->dpd_timer_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_io_pad_poll(const struct tegra_pmc_io_pad_soc *pad,
|
|
u32 val, unsigned long timeout)
|
|
{
|
|
u32 mask = BIT(pad->dpd);
|
|
u32 value;
|
|
|
|
timeout = jiffies + msecs_to_jiffies(timeout);
|
|
|
|
usleep_range(10, 10);
|
|
while (time_after(timeout, jiffies)) {
|
|
value = tegra_pmc_reg_readl(pad->dpd_status_reg);
|
|
if ((value & mask) == val)
|
|
return 0;
|
|
|
|
usleep_range(250, 1000);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void tegra_pmc_io_pad_unprepare(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
if (!pmc->clk)
|
|
return;
|
|
|
|
tegra_pmc_reg_writel(DPD_SAMPLE_DISABLE, pad->dpd_sample_reg);
|
|
}
|
|
|
|
/**
|
|
* tegra_pmc_io_pad_power_enable() - enable power to I/O pad
|
|
* @pad: Tegra I/O pad SOC data for which to enable power
|
|
*
|
|
* Returns: 0 on success or a negative error code on failure.
|
|
*/
|
|
static int tegra_pmc_io_pad_power_enable(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&pmc->powergates_lock);
|
|
|
|
err = tegra_pmc_io_pad_prepare(pad);
|
|
if (err < 0) {
|
|
dev_err(pmc->dev, "Failed to prepare I/O pad %s: %d\n",
|
|
pad->name, err);
|
|
goto unlock;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_OFF | BIT(pad->dpd), pad->dpd_req_reg);
|
|
|
|
err = tegra_pmc_io_pad_poll(pad, 0, 250);
|
|
if (err < 0) {
|
|
dev_err(pmc->dev, "Failed to enable I/O pad %s: %d\n",
|
|
pad->name, err);
|
|
dev_err(pmc->dev, "DPDREQ: 0x%08x DPD2REQ:: 0x%08x"
|
|
"DPD_STATUS: 0x%08x DPD2_STATUS:: 0x%08x\n",
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_REQ),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD2_REQ),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_STATUS),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD2_STATUS));
|
|
goto unlock;
|
|
}
|
|
|
|
tegra_pmc_io_pad_unprepare(pad);
|
|
|
|
unlock:
|
|
mutex_unlock(&pmc->powergates_lock);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* tegra_pmc_io_pad_power_disable() - disable power to I/O pad
|
|
* @pad: Tegra I/O pad SOC data for which to disable power
|
|
*
|
|
* Returns: 0 on success or a negative error code on failure.
|
|
*/
|
|
static int tegra_pmc_io_pad_power_disable(
|
|
const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&pmc->powergates_lock);
|
|
|
|
err = tegra_pmc_io_pad_prepare(pad);
|
|
if (err < 0) {
|
|
dev_err(pmc->dev, "Failed to prepare I/O pad %s: %d\n",
|
|
pad->name, err);
|
|
goto unlock;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_ON | BIT(pad->dpd), pad->dpd_req_reg);
|
|
|
|
err = tegra_pmc_io_pad_poll(pad, BIT(pad->dpd), 250);
|
|
if (err < 0) {
|
|
dev_err(pmc->dev, "Failed to disable I/O pad %s: %d\n",
|
|
pad->name, err);
|
|
dev_err(pmc->dev, "DPDREQ: 0x%08x DPD2REQ:: 0x%08x"
|
|
"DPD_STATUS: 0x%08x DPD2_STATUS:: 0x%08x\n",
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_REQ),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD2_REQ),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD_STATUS),
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IO_DPD2_STATUS));
|
|
goto unlock;
|
|
}
|
|
|
|
tegra_pmc_io_pad_unprepare(pad);
|
|
|
|
unlock:
|
|
mutex_unlock(&pmc->powergates_lock);
|
|
return err;
|
|
}
|
|
|
|
static int _tegra_pmc_io_pad_set_voltage(const struct tegra_pmc_io_pad_soc *pad,
|
|
int io_pad_uv)
|
|
{
|
|
u32 value;
|
|
|
|
if (pad->voltage == UINT_MAX)
|
|
return -ENOTSUPP;
|
|
|
|
if ((io_pad_uv != pad->pad_uv_0) &&
|
|
(io_pad_uv != pad->pad_uv_1))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&pmc->powergates_lock);
|
|
|
|
/* write-enable PMC_PWR_DET_VALUE[pad->voltage] */
|
|
if (pad->io_pad_pwr_det_enable_reg != UINT_MAX) {
|
|
value = tegra_pmc_reg_readl(pad->io_pad_pwr_det_enable_reg);
|
|
value |= BIT(pad->voltage);
|
|
tegra_pmc_reg_writel(value, pad->io_pad_pwr_det_enable_reg);
|
|
}
|
|
|
|
/* update I/O voltage */
|
|
value = tegra_pmc_reg_readl(pad->io_pad_pwr_det_val_reg);
|
|
|
|
if (io_pad_uv == pad->pad_uv_0)
|
|
value &= ~BIT(pad->voltage);
|
|
else
|
|
value |= BIT(pad->voltage);
|
|
|
|
tegra_pmc_reg_writel(value, pad->io_pad_pwr_det_val_reg);
|
|
|
|
mutex_unlock(&pmc->powergates_lock);
|
|
|
|
usleep_range(100, 250);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _tegra_pmc_io_pad_get_voltage(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
u32 value;
|
|
|
|
if (pad->voltage == UINT_MAX)
|
|
return -ENOTSUPP;
|
|
|
|
value = tegra_pmc_reg_readl(pad->io_pad_pwr_det_val_reg);
|
|
|
|
if ((value & BIT(pad->voltage)) == 0)
|
|
return pad->pad_uv_0;
|
|
|
|
return pad->pad_uv_1;
|
|
}
|
|
|
|
/**
|
|
* tegra_pmc_io_pad_is_powered() - check if IO pad is powered
|
|
* @pad: Tegra I/O pad SOC data for which power status need to check
|
|
*
|
|
* Return 1 if power-ON, 0 if power OFF and error number in
|
|
* negative if pad ID is not valid or power down not supported
|
|
* on given IO pad.
|
|
*/
|
|
static int tegra_pmc_io_pad_is_powered(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
u32 value;
|
|
|
|
if (pad->dpd == UINT_MAX)
|
|
return -ENOTSUPP;
|
|
|
|
value = tegra_pmc_reg_readl(pad->dpd_status_reg);
|
|
|
|
return !(value & BIT(pad->dpd));
|
|
}
|
|
|
|
static int tegra_pmc_io_pads_pinctrl_get_groups_count(
|
|
struct pinctrl_dev *pctldev)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
return tpmc->soc->num_io_pads;
|
|
}
|
|
|
|
static const char *tegra_pmc_io_pads_pinctrl_get_group_name(
|
|
struct pinctrl_dev *pctldev, unsigned int group)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
return tpmc->soc->io_pads[group].name;
|
|
}
|
|
|
|
static int tegra_pmc_io_pads_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
|
|
unsigned int group,
|
|
const unsigned int **pins,
|
|
unsigned int *num_pins)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
*pins = tpmc->soc->io_pads[group].pins;
|
|
*num_pins = tpmc->soc->io_pads[group].npins;
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum tegra_io_rail_pads_params {
|
|
TEGRA_IO_PAD_POWER_SOURCE_VOLTAGE = PIN_CONFIG_END + 1,
|
|
TEGRA_IO_PAD_DYNAMIC_VOLTAGE_SWITCH = PIN_CONFIG_END + 2,
|
|
};
|
|
|
|
static const struct pinconf_generic_params tegra_io_pads_cfg_params[] = {
|
|
{
|
|
.property = "nvidia,power-source-voltage",
|
|
.param = TEGRA_IO_PAD_POWER_SOURCE_VOLTAGE,
|
|
}, {
|
|
.property = "nvidia,enable-voltage-switching",
|
|
.param = TEGRA_IO_PAD_DYNAMIC_VOLTAGE_SWITCH,
|
|
},
|
|
};
|
|
|
|
static const struct pinctrl_ops tegra_pmc_io_pads_pinctrl_ops = {
|
|
.get_groups_count = tegra_pmc_io_pads_pinctrl_get_groups_count,
|
|
.get_group_name = tegra_pmc_io_pads_pinctrl_get_group_name,
|
|
.get_group_pins = tegra_pmc_io_pads_pinctrl_get_group_pins,
|
|
.dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
|
|
.dt_free_map = pinconf_generic_dt_free_map,
|
|
};
|
|
|
|
static int tegra_pmc_io_pads_pinconf_get(struct pinctrl_dev *pctldev,
|
|
unsigned int pin,
|
|
unsigned long *config)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
u16 param = pinconf_to_config_param(*config);
|
|
const struct tegra_pmc_io_pad_soc *pad = &tpmc->soc->io_pads[pin];
|
|
u16 arg = 0;
|
|
int ret;
|
|
|
|
switch (param) {
|
|
case PIN_CONFIG_LOW_POWER_MODE:
|
|
ret = tegra_pmc_io_pad_is_powered(pad);
|
|
if (ret < 0)
|
|
return ret;
|
|
arg = !ret;
|
|
break;
|
|
|
|
case TEGRA_IO_PAD_POWER_SOURCE_VOLTAGE:
|
|
if (pmc->soc->io_pads[pin].voltage == UINT_MAX)
|
|
return -EINVAL;
|
|
|
|
ret = _tegra_pmc_io_pad_get_voltage(pad);
|
|
if (ret < 0)
|
|
return ret;
|
|
arg = ret;
|
|
|
|
break;
|
|
|
|
case TEGRA_IO_PAD_DYNAMIC_VOLTAGE_SWITCH:
|
|
if (pmc->soc->io_pads[pin].voltage == UINT_MAX)
|
|
return -EINVAL;
|
|
|
|
if (pmc->voltage_switch_restriction_enabled &&
|
|
pmc->allow_dynamic_switch[pin])
|
|
arg = 1;
|
|
else
|
|
arg = 0;
|
|
break;
|
|
|
|
default:
|
|
dev_dbg(tpmc->dev, "I/O pad %s does not support param %d\n",
|
|
pad->name, param);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*config = pinconf_to_config_packed(param, arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_pmc_io_pads_pinconf_set(struct pinctrl_dev *pctldev,
|
|
unsigned int pin,
|
|
unsigned long *configs,
|
|
unsigned int num_configs)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
const struct tegra_pmc_io_pad_soc *pad = &tpmc->soc->io_pads[pin];
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num_configs; i++) {
|
|
int ret;
|
|
u16 param_val = pinconf_to_config_argument(configs[i]);
|
|
u16 param = pinconf_to_config_param(configs[i]);
|
|
|
|
switch (param) {
|
|
case PIN_CONFIG_LOW_POWER_MODE:
|
|
if (param_val)
|
|
ret = tegra_pmc_io_pad_power_disable(pad);
|
|
else
|
|
ret = tegra_pmc_io_pad_power_enable(pad);
|
|
if (ret < 0) {
|
|
dev_err(tpmc->dev,
|
|
"Failed to set low power %s of I/O pad %s: %d\n",
|
|
(param_val) ? "disable" : "enable",
|
|
pad->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEGRA_IO_PAD_POWER_SOURCE_VOLTAGE:
|
|
if (pmc->soc->io_pads[pin].voltage == UINT_MAX)
|
|
return -EINVAL;
|
|
|
|
if (pmc->voltage_switch_restriction_enabled &&
|
|
!pmc->allow_dynamic_switch[pin]) {
|
|
dev_err(tpmc->dev,
|
|
"IO Pad %s: Dynamic voltage switching not allowed\n",
|
|
pad->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = _tegra_pmc_io_pad_set_voltage(pad, param_val);
|
|
if (ret < 0) {
|
|
dev_err(tpmc->dev,
|
|
"Failed to set voltage %d of pin %u: %d\n",
|
|
param_val, pin, ret);
|
|
return ret;
|
|
}
|
|
|
|
break;
|
|
|
|
case TEGRA_IO_PAD_DYNAMIC_VOLTAGE_SWITCH:
|
|
if (pmc->soc->io_pads[pin].voltage == UINT_MAX)
|
|
return -EINVAL;
|
|
|
|
pmc->allow_dynamic_switch[pin] = true;
|
|
break;
|
|
|
|
default:
|
|
dev_err(tpmc->dev, "I/O pad %s does not support param %d\n",
|
|
pad->name, param);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static void tegra_pmc_io_pads_pinconf_dbg_show(struct pinctrl_dev *pctldev,
|
|
struct seq_file *s,
|
|
unsigned int pin)
|
|
{
|
|
struct tegra_pmc *tpmc = pinctrl_dev_get_drvdata(pctldev);
|
|
unsigned long config = 0;
|
|
u16 param, param_val;
|
|
int ret;
|
|
int i;
|
|
|
|
for (i = 0; i < tpmc->pinctrl_desc.num_custom_params; ++i) {
|
|
param = tpmc->pinctrl_desc.custom_params[i].param;
|
|
config = pinconf_to_config_packed(param, 0);
|
|
ret = tegra_pmc_io_pads_pinconf_get(pctldev, pin, &config);
|
|
if (ret < 0)
|
|
continue;
|
|
param_val = pinconf_to_config_argument(config);
|
|
switch (param) {
|
|
case TEGRA_IO_PAD_POWER_SOURCE_VOLTAGE:
|
|
if (param_val == TEGRA_IO_PAD_VOLTAGE_1200000UV)
|
|
seq_puts(s, "\n\t\tPad voltage 1200000uV");
|
|
else if (param_val == TEGRA_IO_PAD_VOLTAGE_1800000UV)
|
|
seq_puts(s, "\n\t\tPad voltage 1800000uV");
|
|
else
|
|
seq_puts(s, "\n\t\tPad voltage 3300000uV");
|
|
break;
|
|
|
|
case TEGRA_IO_PAD_DYNAMIC_VOLTAGE_SWITCH:
|
|
seq_printf(s, "\n\t\tSwitching voltage: %s",
|
|
(param_val) ? "Enable" : "Disable");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
static void tegra_pmc_io_pads_pinconf_dbg_show(struct pinctrl_dev *pctldev,
|
|
struct seq_file *s,
|
|
unsigned int pin)
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
static const struct pinconf_ops tegra_pmc_io_pads_pinconf_ops = {
|
|
.pin_config_get = tegra_pmc_io_pads_pinconf_get,
|
|
.pin_config_set = tegra_pmc_io_pads_pinconf_set,
|
|
.pin_config_dbg_show = tegra_pmc_io_pads_pinconf_dbg_show,
|
|
.is_generic = true,
|
|
};
|
|
|
|
static int tegra_pmc_io_pads_pinctrl_init(struct tegra_pmc *pmc)
|
|
{
|
|
if (!pmc->soc->num_descs)
|
|
return 0;
|
|
|
|
pmc->allow_dynamic_switch = devm_kzalloc(pmc->dev, pmc->soc->num_descs *
|
|
sizeof(*pmc->allow_dynamic_switch),
|
|
GFP_KERNEL);
|
|
if (!pmc->allow_dynamic_switch) {
|
|
dev_err(pmc->dev, "Failed to allocate allow_dynamic_switch\n");
|
|
return 0;
|
|
}
|
|
|
|
pmc->voltage_switch_restriction_enabled = false;
|
|
pmc->pinctrl_desc.name = "pinctrl-pmc-io-pads";
|
|
pmc->pinctrl_desc.pctlops = &tegra_pmc_io_pads_pinctrl_ops;
|
|
pmc->pinctrl_desc.confops = &tegra_pmc_io_pads_pinconf_ops;
|
|
pmc->pinctrl_desc.pins = pmc->soc->descs;
|
|
pmc->pinctrl_desc.npins = pmc->soc->num_descs;
|
|
pmc->pinctrl_desc.custom_params = tegra_io_pads_cfg_params;
|
|
pmc->pinctrl_desc.num_custom_params =
|
|
ARRAY_SIZE(tegra_io_pads_cfg_params);
|
|
|
|
pmc->pctl = devm_pinctrl_register(pmc->dev, &pmc->pinctrl_desc, pmc);
|
|
if (IS_ERR(pmc->pctl)) {
|
|
int ret = PTR_ERR(pmc->pctl);
|
|
|
|
dev_err(pmc->dev, "Failed to register pinctrl-io-pad: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
pmc->voltage_switch_restriction_enabled =
|
|
of_property_read_bool(pmc->dev->of_node,
|
|
"nvidia,restrict-voltage-switch");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct tegra_pmc_io_pad_soc *tegra_pmc_get_pad_by_name(
|
|
const char *pad_name)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < pmc->soc->num_io_pads; ++i) {
|
|
if (!strcmp(pad_name, pmc->soc->io_pads[i].name))
|
|
return &pmc->soc->io_pads[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int tegra_pmc_get_dpd_masks_by_names(const char * const *io_pads,
|
|
int n_iopads, u32 *mask)
|
|
{
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
int i;
|
|
|
|
*mask = 0;
|
|
|
|
for (i = 0; i < n_iopads; i++) {
|
|
pad = tegra_pmc_get_pad_by_name(io_pads[i]);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO pad %s not found\n", io_pads[i]);
|
|
return -EINVAL;
|
|
}
|
|
*mask |= BIT(pad->dpd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra_pmc_io_pad_low_power_enable(const char *pad_name)
|
|
{
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
|
|
pad = tegra_pmc_get_pad_by_name(pad_name);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO Pad %s not found\n", pad_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_pmc_io_pad_power_disable(pad);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_io_pad_low_power_enable);
|
|
|
|
int tegra_pmc_io_pad_low_power_disable(const char *pad_name)
|
|
{
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
|
|
pad = tegra_pmc_get_pad_by_name(pad_name);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO Pad %s not found\n", pad_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_pmc_io_pad_power_enable(pad);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_io_pad_low_power_disable);
|
|
|
|
int tegra_pmc_io_pad_set_voltage(const char *pad_name, unsigned int pad_uv)
|
|
{
|
|
int io_pad_uv;
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
|
|
pad = tegra_pmc_get_pad_by_name(pad_name);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO Pad %s not found\n", pad_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (pad_uv) {
|
|
case 1800000:
|
|
io_pad_uv = TEGRA_IO_PAD_VOLTAGE_1800000UV;
|
|
break;
|
|
|
|
case 3300000:
|
|
io_pad_uv = TEGRA_IO_PAD_VOLTAGE_3300000UV;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return _tegra_pmc_io_pad_set_voltage(pad, io_pad_uv);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_io_pad_set_voltage);
|
|
|
|
int tegra_pmc_io_pad_get_voltage(const char *pad_name)
|
|
{
|
|
int io_pad_uv;
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
|
|
pad = tegra_pmc_get_pad_by_name(pad_name);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO Pad %s not found\n", pad_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
io_pad_uv = _tegra_pmc_io_pad_get_voltage(pad);
|
|
if (io_pad_uv < 0)
|
|
return -EINVAL;
|
|
|
|
switch (io_pad_uv) {
|
|
case TEGRA_IO_PAD_VOLTAGE_1800000UV:
|
|
return 1800000;
|
|
|
|
case TEGRA_IO_PAD_VOLTAGE_3300000UV:
|
|
return 3300000;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_io_pad_get_voltage);
|
|
|
|
int tegra_pmc_nvcsi_brick_getstatus(const char *pad_name)
|
|
{
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
u32 value;
|
|
|
|
pad = tegra_pmc_get_pad_by_name(pad_name);
|
|
if (!pad) {
|
|
dev_err(pmc->dev, "IO Pad %s not found\n", pad_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
value = tegra_pmc_reg_readl(pad->dpd_status_reg);
|
|
return !!(value & BIT(pad->dpd));
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_nvcsi_brick_getstatus);
|
|
|
|
int tegra_pmc_nvcsi_ab_brick_dpd_enable(void)
|
|
{
|
|
u32 pad_mask;
|
|
int ret;
|
|
|
|
ret = tegra_pmc_get_dpd_masks_by_names(nvcsi_ab_bricks_pads,
|
|
ARRAY_SIZE(nvcsi_ab_bricks_pads),
|
|
&pad_mask);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_ON | pad_mask, TEGRA_PMC_IO_DPD_REQ);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_nvcsi_ab_brick_dpd_enable);
|
|
|
|
int tegra_pmc_nvcsi_ab_brick_dpd_disable(void)
|
|
{
|
|
u32 pad_mask;
|
|
int ret;
|
|
|
|
ret = tegra_pmc_get_dpd_masks_by_names(nvcsi_ab_bricks_pads,
|
|
ARRAY_SIZE(nvcsi_ab_bricks_pads),
|
|
&pad_mask);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_OFF | pad_mask, TEGRA_PMC_IO_DPD_REQ);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_nvcsi_ab_brick_dpd_disable);
|
|
|
|
int tegra_pmc_nvcsi_cdef_brick_dpd_enable(void)
|
|
{
|
|
u32 pad_mask;
|
|
int ret;
|
|
|
|
ret = tegra_pmc_get_dpd_masks_by_names(nvcsi_cdef_bricks_pads,
|
|
ARRAY_SIZE(nvcsi_cdef_bricks_pads),
|
|
&pad_mask);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_ON | pad_mask, TEGRA_PMC_IO_DPD2_REQ);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_nvcsi_cdef_brick_dpd_enable);
|
|
|
|
int tegra_pmc_nvcsi_cdef_brick_dpd_disable(void)
|
|
{
|
|
u32 pad_mask = 0;
|
|
int ret;
|
|
|
|
ret = tegra_pmc_get_dpd_masks_by_names(nvcsi_cdef_bricks_pads,
|
|
ARRAY_SIZE(nvcsi_cdef_bricks_pads),
|
|
&pad_mask);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
tegra_pmc_reg_writel(IO_DPD_REQ_CODE_OFF | pad_mask, TEGRA_PMC_IO_DPD2_REQ);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_nvcsi_cdef_brick_dpd_disable);
|
|
|
|
int tegra_pmc_pwm_blink_enable(void)
|
|
{
|
|
tegra_pmc_register_update(TEGRA_PMC_DPD_PADS_ORIDE,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK);
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_CNTRL, TEGRA210_PMC_CTRL_BLINK_EN,
|
|
TEGRA210_PMC_CTRL_BLINK_EN);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_pwm_blink_enable);
|
|
|
|
int tegra_pmc_pwm_blink_disable(void)
|
|
{
|
|
tegra_pmc_register_update(TEGRA_PMC_CNTRL, TEGRA210_PMC_CTRL_BLINK_EN,
|
|
0);
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_DPD_PADS_ORIDE,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK, 0);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_pwm_blink_disable);
|
|
|
|
int tegra_pmc_pwm_blink_config(int duty_ns, int period_ns)
|
|
{
|
|
int data_on;
|
|
int data_off;
|
|
u32 val;
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_CNTRL,
|
|
TEGRA210_PMC_CTRL_BLINK_EN, 0);
|
|
udelay(64);
|
|
|
|
/* 16 x 32768 Hz = 1000000000/(32768*16) = 488281ns */
|
|
data_on = (duty_ns - 30517) / 488281;
|
|
data_off = (period_ns - duty_ns - 30517) / 488281;
|
|
|
|
if (data_off > 0xFFFF)
|
|
data_off = 0xFFFF;
|
|
|
|
if (data_on > 0x7FFF)
|
|
data_on = 0x7FFF;
|
|
|
|
val = (data_off << 16) | BIT(15) | data_on;
|
|
tegra_pmc_reg_writel(val, TEGRA_PMC_BLINK_TIMER);
|
|
udelay(64);
|
|
tegra_pmc_register_update(TEGRA_PMC_CNTRL,
|
|
TEGRA210_PMC_CTRL_BLINK_EN, 1);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_pwm_blink_config);
|
|
|
|
int tegra_pmc_soft_led_blink_enable(void)
|
|
{
|
|
tegra_pmc_register_update(TEGRA_PMC_DPD_PADS_ORIDE,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK);
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_CNTRL,
|
|
TEGRA210_PMC_CTRL_BLINK_EN, 0);
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_LED_BREATHING_CTRL,
|
|
PMC_LED_BREATHING_EN,
|
|
PMC_LED_BREATHING_EN);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_soft_led_blink_enable);
|
|
|
|
int tegra_pmc_soft_led_blink_disable(void)
|
|
{
|
|
tegra_pmc_register_update(TEGRA_PMC_DPD_PADS_ORIDE,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK,
|
|
TEGRA210_PMC_DPD_PADS_ORIDE_BLINK);
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_LED_BREATHING_CTRL,
|
|
PMC_LED_BREATHING_EN, 0);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_soft_led_blink_disable);
|
|
|
|
int tegra_pmc_soft_led_blink_configure(int duty_cycle_ns, int ll_period_ns,
|
|
int ramp_time_ns)
|
|
{
|
|
int plateau_cnt;
|
|
int plateau_ns;
|
|
int period;
|
|
|
|
if (duty_cycle_ns) {
|
|
plateau_ns = duty_cycle_ns - (2 * ramp_time_ns);
|
|
if (plateau_ns < 0) {
|
|
dev_err(pmc->dev, "duty cycle is less than 2xramptime:\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
plateau_cnt = plateau_ns / PMC_LED_SOFT_BLINK_1CYCLE_NS;
|
|
tegra_pmc_reg_writel(plateau_cnt, TEGRA_PMC_LED_BREATHING_COUNTER1);
|
|
}
|
|
|
|
if (ll_period_ns) {
|
|
period = ll_period_ns / PMC_LED_SOFT_BLINK_1CYCLE_NS;
|
|
tegra_pmc_reg_writel(period, TEGRA_PMC_LED_BREATHING_COUNTER3);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_soft_led_blink_configure);
|
|
|
|
int tegra_pmc_soft_led_blink_set_ramptime(int ramp_time_ns)
|
|
{
|
|
u32 nsteps;
|
|
u32 rt_nanoseconds = 0;
|
|
|
|
if (ramp_time_ns < 0)
|
|
return -EINVAL;
|
|
|
|
/* (n + 1) x (n + 2) * 1 cycle = ramp_time */
|
|
/* 1 cycle = 1/32 KHz duration = 32000000ns*/
|
|
for (nsteps = 0; rt_nanoseconds < ramp_time_ns; nsteps++) {
|
|
rt_nanoseconds = (nsteps * nsteps) + (3 * nsteps) + 2;
|
|
rt_nanoseconds = rt_nanoseconds * PMC_LED_SOFT_BLINK_1CYCLE_NS;
|
|
}
|
|
|
|
tegra_pmc_reg_writel(nsteps - 1, TEGRA_PMC_LED_BREATHING_COUNTER0);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_soft_led_blink_set_ramptime);
|
|
|
|
int tegra_pmc_soft_led_blink_set_short_period(int short_low_period_ns)
|
|
{
|
|
u32 period;
|
|
|
|
if (short_low_period_ns < 0)
|
|
return -EINVAL;
|
|
|
|
if (short_low_period_ns) {
|
|
/* enable and configure short low period */
|
|
period = short_low_period_ns / PMC_LED_SOFT_BLINK_1CYCLE_NS;
|
|
|
|
tegra_pmc_reg_writel(period, TEGRA_PMC_LED_BREATHING_COUNTER2);
|
|
tegra_pmc_register_update(TEGRA_PMC_LED_BREATHING_CTRL,
|
|
PMC_SHORT_LOW_PERIOD_EN,
|
|
PMC_SHORT_LOW_PERIOD_EN);
|
|
} else {
|
|
/* disable short low period */
|
|
tegra_pmc_register_update(TEGRA_PMC_LED_BREATHING_CTRL,
|
|
PMC_SHORT_LOW_PERIOD_EN, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_soft_led_blink_set_short_period);
|
|
|
|
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:
|
|
if (tegra_get_chip_id() == TEGRA210)
|
|
pmc->suspend_mode = TEGRA_SUSPEND_SC7;
|
|
else
|
|
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;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_ARM
|
|
pmc->suspend_mode = tegra_pm_validate_suspend_mode(pmc->suspend_mode);
|
|
#endif
|
|
|
|
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-pwr-req-active-high");
|
|
if (!pmc->corereq_high)
|
|
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 (pmc->lp0_vec_size && pmc->lp0_vec_phys)
|
|
goto out;
|
|
|
|
values[0] = values[1] = 0;
|
|
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];
|
|
|
|
out:
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_pmc_init(struct tegra_pmc *pmc)
|
|
{
|
|
u32 value;
|
|
|
|
if (pmc->soc->skip_legacy_pmc_init)
|
|
return;
|
|
|
|
/* Always enable CPU power request */
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
value |= PMC_CNTRL_CPU_PWRREQ_OE;
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_CNTRL);
|
|
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
|
|
if (pmc->sysclkreq_high)
|
|
value &= ~PMC_CNTRL_SYSCLK_POLARITY;
|
|
else
|
|
value |= PMC_CNTRL_SYSCLK_POLARITY;
|
|
|
|
if (pmc->corereq_high)
|
|
value &= ~PMC_CNTRL_PWRREQ_POLARITY;
|
|
else
|
|
value |= PMC_CNTRL_PWRREQ_POLARITY;
|
|
|
|
/* configure the output polarity while the request is tristated */
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_CNTRL);
|
|
|
|
/* now enable the request */
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
value |= PMC_CNTRL_SYSCLK_OE;
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_CNTRL);
|
|
|
|
set_core_power_timers();
|
|
tegra_pmc_syscore_init();
|
|
tegra_pmc_wake_syscore_init();
|
|
}
|
|
|
|
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_get_child_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_reg_readl(TEGRA_PMC_SENSOR_CTRL);
|
|
value |= PMC_SENSOR_CTRL_SCRATCH_WRITE;
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_SENSOR_CTRL);
|
|
|
|
value = (reg_data << PMC_SCRATCH54_DATA_SHIFT) |
|
|
(reg_addr << PMC_SCRATCH54_ADDR_SHIFT);
|
|
tegra_pmc_reg_writel(value, TEGRA_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_reg_writel(value, TEGRA_PMC_SCRATCH55);
|
|
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_SENSOR_CTRL);
|
|
value |= PMC_SENSOR_CTRL_ENABLE_RST;
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_SENSOR_CTRL);
|
|
|
|
dev_info(pmc->dev, "emergency thermal reset enabled\n");
|
|
|
|
out:
|
|
of_node_put(np);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
struct tegra_pmc_scratch_export_info {
|
|
const char **reg_names;
|
|
u32 *reg_offset;
|
|
int cnt_reg_offset;
|
|
int cnt_reg_names;
|
|
};
|
|
static struct tegra_pmc_scratch_export_info scratch_info;
|
|
|
|
static inline u32 tegra_pmc_debug_scratch_readl(u32 reg)
|
|
{
|
|
return readl(pmc->reboot_base + reg);
|
|
}
|
|
|
|
static inline void tegra_pmc_debug_scratch_writel(u32 val, u32 reg)
|
|
{
|
|
writel(val, pmc->reboot_base + reg);
|
|
}
|
|
|
|
static ssize_t tegra_pmc_debug_scratch_reg_read(struct file *file,
|
|
char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[64] = {};
|
|
unsigned char *dfsname = file->f_path.dentry->d_iname;
|
|
ssize_t ret;
|
|
u32 value;
|
|
int id;
|
|
|
|
for (id = 0; id < scratch_info.cnt_reg_offset; id++) {
|
|
if (!strcmp(dfsname, scratch_info.reg_names[id]))
|
|
break;
|
|
}
|
|
|
|
if (id == scratch_info.cnt_reg_offset)
|
|
return -EINVAL;
|
|
|
|
value = tegra_pmc_debug_scratch_readl(scratch_info.reg_offset[id]);
|
|
ret = snprintf(buf, sizeof(buf), "Reg: 0x%x : Value: 0x%x\n",
|
|
scratch_info.reg_offset[id], value);
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
|
}
|
|
|
|
static ssize_t tegra_pmc_debug_scratch_reg_write(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[64] = { };
|
|
unsigned char *dfsname = file->f_path.dentry->d_iname;
|
|
ssize_t buf_size;
|
|
u32 value = 0;
|
|
int id;
|
|
|
|
for (id = 0; id < scratch_info.cnt_reg_offset; id++) {
|
|
if (!strcmp(dfsname, scratch_info.reg_names[id]))
|
|
break;
|
|
}
|
|
if (id == scratch_info.cnt_reg_offset)
|
|
return -EINVAL;
|
|
|
|
buf_size = min(count, (sizeof(buf) - 1));
|
|
if (copy_from_user(buf, user_buf, buf_size))
|
|
return -EFAULT;
|
|
|
|
if (!sscanf(buf, "%x\n", &value))
|
|
return -EINVAL;
|
|
|
|
pr_info("PMC reg: 0x%x Value: 0x%x\n", scratch_info.reg_offset[id], value);
|
|
tegra_pmc_debug_scratch_writel(value, scratch_info.reg_offset[id]);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations pmc_debugfs_fops = {
|
|
.open = simple_open,
|
|
.write = tegra_pmc_debug_scratch_reg_write,
|
|
.read = tegra_pmc_debug_scratch_reg_read,
|
|
};
|
|
|
|
static int tegra_pmc_debug_scratch_reg_init(struct tegra_pmc *pmc)
|
|
{
|
|
struct device_node *np = pmc->dev->of_node;
|
|
const char *srname;
|
|
struct property *prop;
|
|
int count, i;
|
|
int ret;
|
|
int cnt_reg_names, cnt_reg_offset;
|
|
struct dentry *dbgfs_root;
|
|
|
|
cnt_reg_offset = of_property_count_u32_elems(np,
|
|
"export-pmc-scratch-reg-offset");
|
|
if (cnt_reg_offset <= 0) {
|
|
dev_info(pmc->dev, "scratch reg offset dts data not present\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
scratch_info.cnt_reg_offset = cnt_reg_offset;
|
|
|
|
cnt_reg_names = of_property_count_strings(np,
|
|
"export-pmc-scratch-reg-name");
|
|
if (cnt_reg_names < 0 || (cnt_reg_offset != cnt_reg_names)) {
|
|
dev_info(pmc->dev, "reg offset and names count not matching\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
scratch_info.cnt_reg_names = cnt_reg_names;
|
|
scratch_info.reg_names = devm_kzalloc(pmc->dev, (cnt_reg_offset + 1) *
|
|
sizeof(*scratch_info.reg_names),
|
|
GFP_KERNEL);
|
|
if (!scratch_info.reg_names)
|
|
return -ENOMEM;
|
|
|
|
count = 0;
|
|
of_property_for_each_string(np, "export-pmc-scratch-reg-name",
|
|
prop, srname)
|
|
scratch_info.reg_names[count++] = srname;
|
|
|
|
scratch_info.reg_names[count] = NULL;
|
|
|
|
scratch_info.reg_offset = devm_kzalloc(pmc->dev, sizeof(u32) *
|
|
cnt_reg_offset, GFP_KERNEL);
|
|
if (!scratch_info.reg_offset)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_u32_array(np, "export-pmc-scratch-reg-offset",
|
|
scratch_info.reg_offset, cnt_reg_offset);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
dbgfs_root = debugfs_create_dir("PMC", NULL);
|
|
if (!dbgfs_root) {
|
|
dev_info(pmc->dev, "PMC:Failed to create debugfs dir\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < cnt_reg_offset; i++) {
|
|
debugfs_create_file(scratch_info.reg_names[i], S_IRUGO | S_IWUSR,
|
|
dbgfs_root, NULL, &pmc_debugfs_fops);
|
|
dev_info(pmc->dev, "create /sys/kernel/debug/%s/%s\n",
|
|
dbgfs_root->d_name.name, scratch_info.reg_names[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
static int tegra_pmc_debug_scratch_reg_init(struct tegra_pmc *pmc)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
bool tegra_pmc_is_halt_in_fiq(void)
|
|
{
|
|
return !!(PMC_IMPL_HALT_IN_FIQ_MASK &
|
|
tegra_pmc_reg_readl(TEGRA_PMC_IMPL_RAMDUMP_CTL_STATUS));
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_is_halt_in_fiq);
|
|
|
|
static void tegra_pmc_halt_in_fiq_init(struct tegra_pmc *pmc)
|
|
{
|
|
struct device_node *np = pmc->dev->of_node;
|
|
|
|
if (!of_property_read_bool(np, "nvidia,enable-halt-in-fiq"))
|
|
return;
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_IMPL_RAMDUMP_CTL_STATUS,
|
|
PMC_IMPL_HALT_IN_FIQ_MASK,
|
|
PMC_IMPL_HALT_IN_FIQ_MASK);
|
|
}
|
|
|
|
char *pmc_reset_reason_string[] = {
|
|
"TEGRA_POWER_ON_RESET",
|
|
"TEGRA_AO_WATCHDOG",
|
|
"TEGRA_BCCPLEX_WATCHDOG",
|
|
"TEGRA_BPMP_WATCHDOG",
|
|
"TEGRA_SCE_WATCHDOG",
|
|
"TEGRA_SPE_WATCHDOG",
|
|
"TEGRA_APE_WATCHDOG",
|
|
"TEGRA_LCCPLEX_WATCHDOG",
|
|
"TEGRA_SENSOR",
|
|
"TEGRA_AOTAG",
|
|
"TEGRA_VFSENSOR",
|
|
"TEGRA_SOFTWARE_RESET",
|
|
"TEGRA_SC7",
|
|
"TEGRA_HSM",
|
|
"TEGRA_CSITE",
|
|
"TEGRA_WATCHDOG",
|
|
"TEGRA_LP0",
|
|
"PMIC_WATCHDOG_POR",
|
|
"TEGRA_RESET_REASON_MAX",
|
|
};
|
|
|
|
static char *pmc_reset_level_string[] = {
|
|
"TEGRA_RESET_LEVEL_L0",
|
|
"TEGRA_RESET_LEVEL_L1",
|
|
"TEGRA_RESET_LEVEL_L2",
|
|
"TEGRA_RESET_LEVEL_WARM",
|
|
"TEGRA_RESET_LEVEL_NOT_SUPPORTED",
|
|
};
|
|
|
|
static void tegra_pmc_show_reset_status(void)
|
|
{
|
|
u32 reset_reg;
|
|
enum tegra_system_reset_reason reset_reason = tegra_pmc_get_system_reset_reason();
|
|
enum tegra_system_reset_level reset_level = tegra_pmc_get_system_reset_level();
|
|
|
|
reset_reg = tegra_pmc_reg_readl(TEGRA_PMC_RST_STATUS);
|
|
|
|
pr_info("### PMC reset source: %s\n", pmc_reset_reason_string[reset_reason]);
|
|
pr_info("### PMC reset level: %s\n", pmc_reset_level_string[reset_level]);
|
|
pr_info("### PMC reset status reg: 0x%x\n", reset_reg);
|
|
}
|
|
|
|
#if defined(CONFIG_DEBUG_FS)
|
|
static int pmc_reset_show(struct seq_file *s, void *data)
|
|
{
|
|
enum tegra_system_reset_reason reset_reason = tegra_pmc_get_system_reset_reason();
|
|
enum tegra_system_reset_level reset_level = tegra_pmc_get_system_reset_level();
|
|
|
|
seq_printf(s, "### PMC reset source: %s\n", pmc_reset_reason_string[reset_reason]);
|
|
seq_printf(s, "### PMC reset level: %s\n", pmc_reset_level_string[reset_level]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pmc_reset_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, pmc_reset_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations pmc_reset_fops = {
|
|
.open = pmc_reset_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void tegra_pmc_reset_debugfs_init(struct device *dev)
|
|
{
|
|
struct dentry *d;
|
|
|
|
d = debugfs_create_file("pmc-reset", S_IRUGO, NULL, NULL,
|
|
&pmc_reset_fops);
|
|
if (!d)
|
|
dev_err(dev, "Error in creating the debugFS for pmc-reset\n");
|
|
}
|
|
#else
|
|
static void tegra_pmc_reset_debugfs_init(struct device *dev)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_SYSFS)
|
|
static ssize_t tegra_reset_reason_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
enum tegra_system_reset_reason reset_status =
|
|
tegra_pmc_get_system_reset_reason();
|
|
return sprintf(buf, "%s\n", pmc_reset_reason_string[reset_status]);
|
|
}
|
|
|
|
static ssize_t tegra_reset_level_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
enum tegra_system_reset_level reset_level =
|
|
tegra_pmc_get_system_reset_level();
|
|
return sprintf(buf, "%s\n", pmc_reset_level_string[reset_level]);
|
|
}
|
|
|
|
static struct kobj_attribute tegra_rst_reason =
|
|
__ATTR_RO(tegra_reset_reason);
|
|
|
|
static struct kobj_attribute tegra_rst_level =
|
|
__ATTR_RO(tegra_reset_level);
|
|
|
|
static struct attribute *tegra_attributes[] = {
|
|
&tegra_rst_reason.attr,
|
|
&tegra_rst_level.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group pmc_attribute_group = {
|
|
.attrs = tegra_attributes,
|
|
};
|
|
|
|
static void tegra_pmc_reset_sysfs_init(struct device *dev)
|
|
{
|
|
int error;
|
|
static struct kobject *pmc_kobject;
|
|
|
|
pmc_kobject = kobject_create_and_add("pmc", kernel_kobj);
|
|
|
|
if (!pmc_kobject) {
|
|
dev_err(dev, "Failed to create sysfs dir - /sys/kernel/pmc\n");
|
|
return;
|
|
}
|
|
|
|
error = sysfs_create_group(pmc_kobject, &pmc_attribute_group);
|
|
|
|
if (error)
|
|
dev_err(dev, "Failed to create sysfs entries for pmc\n");
|
|
}
|
|
#else
|
|
static void tegra_pmc_reset_sysfs_init(struct device *dev)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int tegra_pmc_probe(struct platform_device *pdev)
|
|
{
|
|
void __iomem *base;
|
|
void __iomem *io_map_base[5] = {NULL};
|
|
int mem_count = 0;
|
|
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);
|
|
|
|
io_map_base[mem_count++] = pmc->base;
|
|
|
|
/* unmap the base address from the early init, then mark it to NULL */
|
|
iounmap(pmc->early_base);
|
|
pmc->early_base = NULL;
|
|
|
|
if (pmc->soc->has_reboot_base_address) {
|
|
base = pmc->reboot_base;
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
pmc->reboot_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(pmc->reboot_base))
|
|
return PTR_ERR(pmc->reboot_base);
|
|
|
|
io_map_base[mem_count++] = pmc->reboot_base;
|
|
iounmap(base);
|
|
} else {
|
|
pmc->reboot_base = pmc->base;
|
|
}
|
|
|
|
if (pmc->soc->has_misc_base_address) {
|
|
base = pmc->misc_base;
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
|
pmc->misc_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(pmc->misc_base))
|
|
return PTR_ERR(pmc->misc_base);
|
|
|
|
io_map_base[mem_count++] = pmc->misc_base;
|
|
iounmap(base);
|
|
} else {
|
|
pmc->misc_base = pmc->base;
|
|
}
|
|
|
|
while (mem_count < 5) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, mem_count);
|
|
if (!res)
|
|
break;
|
|
|
|
base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
io_map_base[mem_count++] = base;
|
|
}
|
|
|
|
if (pmc->soc->has_pclk_clock) {
|
|
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);
|
|
|
|
tegra_pmc_halt_in_fiq_init(pmc);
|
|
|
|
tegra_pmc_debug_scratch_reg_init(pmc);
|
|
|
|
tegra_pmc_show_reset_status();
|
|
tegra_pmc_reset_debugfs_init(&pdev->dev);
|
|
tegra_pmc_reset_sysfs_init(&pdev->dev);
|
|
|
|
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
|
|
err = tegra_powergate_debugfs_init();
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
pmc->tprod = devm_tegra_prod_get(&pdev->dev);
|
|
if (IS_ERR_OR_NULL(pmc->tprod)) {
|
|
pmc->tprod = NULL;
|
|
}
|
|
|
|
if (pmc->tprod) {
|
|
err = tegra_prod_set_by_name(io_map_base, "prod", pmc->tprod);
|
|
if (!err)
|
|
pr_info("PMC Prod config success\n");
|
|
else
|
|
pr_info("Failed to configure PMC prod: %d\n", err);
|
|
}
|
|
|
|
if (!pmc->soc->skip_restart_register) {
|
|
err = register_restart_handler(&tegra_pmc_restart_handler);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "unable to register restart handler, %d\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = tegra_pmc_io_pads_pinctrl_init(pmc);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Register as pad controller */
|
|
err = tegra_io_pads_padctrl_init(&pdev->dev);
|
|
if (err)
|
|
pr_err("ERROR: Pad control driver init failed: %d\n", err);
|
|
|
|
if (pmc->soc->has_bootrom_command)
|
|
tegra_pmc_init_boorom_cmds(&pdev->dev);
|
|
|
|
/* handle PMC reboot reason with PSCI */
|
|
if (!pmc->soc->skip_arm_pm_restart && arm_pm_restart)
|
|
psci_handle_reboot_cmd = tegra_pmc_program_reboot_reason;
|
|
|
|
/* For writing to kernel panic flag for cboot on t21x chips */
|
|
if (tegra_hidrev_get_chipid(tegra_read_chipid()) == TEGRA210) {
|
|
err = atomic_notifier_chain_register(&panic_notifier_list,
|
|
&tegra_pmc_panic_notifier);
|
|
if (err != 0) {
|
|
pr_err("ERROR: Failed to register PMC panic notifier: %d", err);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_TEGRA_POWERGATE
|
|
#define TEGRA_POWERGATE_CPU 0
|
|
#define TEGRA_POWERGATE_3D 1
|
|
#define TEGRA_POWERGATE_VENC 2
|
|
#define TEGRA_POWERGATE_PCIE 3
|
|
#define TEGRA_POWERGATE_VDEC 4
|
|
#define TEGRA_POWERGATE_L2 5
|
|
#define TEGRA_POWERGATE_MPE 6
|
|
#define TEGRA_POWERGATE_HEG 7
|
|
#define TEGRA_POWERGATE_SATA 8
|
|
#define TEGRA_POWERGATE_CPU1 9
|
|
#define TEGRA_POWERGATE_CPU2 10
|
|
#define TEGRA_POWERGATE_CPU3 11
|
|
#define TEGRA_POWERGATE_CELP 12
|
|
#define TEGRA_POWERGATE_3D1 13
|
|
#define TEGRA_POWERGATE_CPU0 14
|
|
#define TEGRA_POWERGATE_C0NC 15
|
|
#define TEGRA_POWERGATE_C1NC 16
|
|
#define TEGRA_POWERGATE_SOR 17
|
|
#define TEGRA_POWERGATE_DIS 18
|
|
#define TEGRA_POWERGATE_DISB 19
|
|
#define TEGRA_POWERGATE_XUSBA 20
|
|
#define TEGRA_POWERGATE_XUSBB 21
|
|
#define TEGRA_POWERGATE_XUSBC 22
|
|
#define TEGRA_POWERGATE_VIC 23
|
|
#define TEGRA_POWERGATE_IRAM 24
|
|
#define TEGRA_POWERGATE_NVDEC 25
|
|
#define TEGRA_POWERGATE_NVJPG 26
|
|
#define TEGRA_POWERGATE_AUD 27
|
|
#define TEGRA_POWERGATE_DFD 28
|
|
#define TEGRA_POWERGATE_VE2 29
|
|
#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,
|
|
.has_bootrom_command = 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,
|
|
.has_bootrom_command = 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,
|
|
.has_bootrom_command = 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,
|
|
.has_bootrom_command = false,
|
|
};
|
|
|
|
static const unsigned long tegra210_register_map[TEGRA_PMC_MAX_REG] = {
|
|
[TEGRA_PMC_CNTRL] = 0x00,
|
|
[TEGRA_PMC_WAKE_MASK] = 0x0c,
|
|
[TEGRA_PMC_WAKE_LEVEL] = 0x10,
|
|
[TEGRA_PMC_WAKE_STATUS] = 0x14,
|
|
[TEGRA_PMC_SW_WAKE_STATUS] = 0x18,
|
|
[TEGRA_PMC_DPD_PADS_ORIDE] = 0x1c,
|
|
[TEGRA_PMC_BLINK_TIMER] = 0x40,
|
|
[TEGRA_PMC_WAKE_DELAY] = 0xe0,
|
|
[TEGRA_PMC_WAKE2_MASK] = 0x160,
|
|
[TEGRA_PMC_WAKE2_LEVEL] = 0x164,
|
|
[TEGRA_PMC_WAKE2_STATUS] = 0x168,
|
|
[TEGRA_PMC_SW_WAKE2_STATUS] = 0x16c,
|
|
[TEGRA_PMC_IO_DPD_SAMPLE] = 0x20,
|
|
[TEGRA_PMC_IO_DPD_ENABLE] = 0x24,
|
|
[TEGRA_PMC_IO_DPD_REQ] = 0x1b8,
|
|
[TEGRA_PMC_IO_DPD_STATUS] = 0x1bc,
|
|
[TEGRA_PMC_IO_DPD2_REQ] = 0x1c0,
|
|
[TEGRA_PMC_IO_DPD2_STATUS] = 0x1c4,
|
|
[TEGRA_PMC_SEL_DPD_TIM] = 0x1c8,
|
|
[TEGRA_PMC_PWR_NO_IOPOWER] = 0x44,
|
|
[TEGRA_PMC_PWR_DET_ENABLE] = 0x48,
|
|
[TEGRA_PMC_PWR_DET_VAL] = 0xe4,
|
|
[TEGRA_PMC_REMOVE_CLAMPING] = 0x34,
|
|
[TEGRA_PMC_PWRGATE_TOGGLE] = 0x30,
|
|
[TEGRA_PMC_PWRGATE_STATUS] = 0x38,
|
|
[TEGRA_PMC_COREPWRGOOD_TIMER] = 0x3c,
|
|
[TEGRA_PMC_COREPWROFF_TIMER] = 0xe0,
|
|
[TEGRA_PMC_CPUPWRGOOD_TIMER] = 0xc8,
|
|
[TEGRA_PMC_CPUPWROFF_TIMER] = 0xcc,
|
|
[TEGRA_PMC_SENSOR_CTRL] = 0x1b0,
|
|
[TEGRA_PMC_RST_STATUS] = 0x1b4,
|
|
[TEGRA_PMC_GPU_RG_CNTRL] = 0x2d4,
|
|
[TEGRA_PMC_FUSE_CTRL] = 0x450,
|
|
[TEGRA_PMC_BR_COMMAND_BASE] = 0x908,
|
|
[TEGRA_PMC_SCRATCH0] = 0x50,
|
|
[TEGRA_PMC_SCRATCH1] = 0x54,
|
|
[TEGRA_PMC_SCRATCH37] = 0x130,
|
|
[TEGRA_PMC_SCRATCH41] = 0x140,
|
|
[TEGRA_PMC_SCRATCH43] = 0x22c,
|
|
[TEGRA_PMC_SCRATCH54] = 0x258,
|
|
[TEGRA_PMC_SCRATCH55] = 0x25c,
|
|
[TEGRA_PMC_SCRATCH203] = 0x84c,
|
|
[TEGRA_PMC_LED_BREATHING_CTRL] = 0xb48,
|
|
[TEGRA_PMC_LED_BREATHING_COUNTER0] = 0xb4c,
|
|
[TEGRA_PMC_LED_BREATHING_COUNTER1] = 0xb50,
|
|
[TEGRA_PMC_LED_BREATHING_COUNTER2] = 0xb54,
|
|
[TEGRA_PMC_LED_BREATHING_COUNTER3] = 0xb58,
|
|
[TEGRA_PMC_LED_BREATHING_STATUS] = 0xb5c,
|
|
};
|
|
|
|
static const char * const tegra210_powergates[] = {
|
|
[TEGRA_POWERGATE_CPU] = "crail",
|
|
[TEGRA_POWERGATE_3D] = "3d",
|
|
[TEGRA_POWERGATE_VENC] = "venc",
|
|
[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_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",
|
|
[TEGRA_POWERGATE_NVDEC] = "nvdec",
|
|
[TEGRA_POWERGATE_NVJPG] = "nvjpg",
|
|
[TEGRA_POWERGATE_AUD] = "aud",
|
|
[TEGRA_POWERGATE_DFD] = "dfd",
|
|
[TEGRA_POWERGATE_VE2] = "ve2",
|
|
};
|
|
|
|
static const u8 tegra210_cpu_powergates[] = {
|
|
TEGRA_POWERGATE_CPU0,
|
|
TEGRA_POWERGATE_CPU1,
|
|
TEGRA_POWERGATE_CPU2,
|
|
TEGRA_POWERGATE_CPU3,
|
|
};
|
|
|
|
#define TEGRA210X_IO_PAD_CONFIG(_pin, _npins, _name, _dpd, \
|
|
_vbit, _iopower, _reg, _bds) \
|
|
{ \
|
|
.name = #_name, \
|
|
.pins = {(_pin)}, \
|
|
.npins = _npins, \
|
|
.dpd = _dpd, \
|
|
.voltage = _vbit, \
|
|
.io_power = _iopower, \
|
|
.dpd_req_reg = TEGRA_PMC_IO_##_reg##_REQ, \
|
|
.dpd_status_reg = TEGRA_PMC_IO_##_reg##_STATUS, \
|
|
.dpd_timer_reg = TEGRA_PMC_SEL_DPD_TIM, \
|
|
.dpd_sample_reg = TEGRA_PMC_IO_DPD_SAMPLE, \
|
|
.bdsdmem_cfc = _bds, \
|
|
.io_pad_pwr_det_enable_reg = TEGRA_PMC_PWR_DET_ENABLE, \
|
|
.io_pad_pwr_det_val_reg = TEGRA_PMC_PWR_DET_VAL, \
|
|
.pad_uv_0 = TEGRA_IO_PAD_VOLTAGE_1800000UV, \
|
|
.pad_uv_1 = TEGRA_IO_PAD_VOLTAGE_3300000UV, \
|
|
},
|
|
|
|
#define TEGRA210_IO_PAD_CONFIG(_pin, _npins, _name, _dpd, \
|
|
_vbit, _iopower, _reg) \
|
|
TEGRA210X_IO_PAD_CONFIG(_pin, _npins, _name, _dpd, \
|
|
_vbit, _iopower, _reg, false)
|
|
|
|
/**
|
|
* All IO pads of Tegra SoCs do not support the low power and multi level
|
|
* voltage configurations for its pads.
|
|
* Defining macros for different cases as follows:
|
|
* TEGRA_IO_PAD_LPONLY : IO pad which support low power state but
|
|
* operate in single level of IO voltage.
|
|
* TEGRA_IO_PAD_LP_N_PV: IO pad which support low power state as well as
|
|
* it can operate in multi-level voltages.
|
|
* TEGRA_IO_PAD_PVONLY: IO pad which does not support low power state but
|
|
* it can operate in multi-level voltages.
|
|
*/
|
|
#define TEGRA210_IO_PAD_LPONLY(_pin, _name, _dpd, _reg) \
|
|
TEGRA210_IO_PAD_CONFIG(_pin, 1, _name, _dpd, UINT_MAX, UINT_MAX, _reg)
|
|
|
|
#define TEGRA210_IO_PAD_LP_N_PV(_pin, _name, _dpd, _vbit, _io , _reg) \
|
|
TEGRA210_IO_PAD_CONFIG(_pin, 1, _name, _dpd, _vbit, _io, _reg)
|
|
|
|
#define TEGRA210_IO_PAD_PVONLY(_pin, _name, _vbit, _io, _reg) \
|
|
TEGRA210_IO_PAD_CONFIG(_pin, 0, _name, UINT_MAX, _vbit, _io, _reg)
|
|
|
|
#define TEGRA210_IO_PAD_DESC_LP(_pin, _name, _dpd, _reg) \
|
|
{ \
|
|
.number = _pin, \
|
|
.name = #_name, \
|
|
},
|
|
#define TEGRA210_IO_PAD_DESC_LP_N_PV(_pin, _name, _dpd, _vbit, _io, _reg) \
|
|
TEGRA210_IO_PAD_DESC_LP(_pin, _name, _dpd, _reg)
|
|
|
|
#define TEGRA210_IO_PAD_DESC_PV(_pin, _name, _vbit, _io, _reg) \
|
|
TEGRA210_IO_PAD_DESC_LP(_pin, _name, UINT_MAX, _reg)
|
|
|
|
#define TEGRA210_IO_PAD_TABLE(_lponly_, _pvonly_, _lp_n_pv_) \
|
|
_lp_n_pv_(0, audio, 17, 5, 5, DPD) \
|
|
_lp_n_pv_(1, audio-hv, 29, 18, 18, DPD2) \
|
|
_lp_n_pv_(2, cam, 4, 10, 10, DPD2) \
|
|
_lponly_(3, csia, 0, DPD) \
|
|
_lponly_(4, csib, 1, DPD) \
|
|
_lponly_(5, csic, 10, DPD2) \
|
|
_lponly_(6, csid, 11, DPD2) \
|
|
_lponly_(7, csie, 12, DPD2) \
|
|
_lponly_(8, csif, 13, DPD2) \
|
|
_lp_n_pv_(9, dbg, 25, 19, 19, DPD) \
|
|
_lponly_(10, debug-nonao, 26, DPD) \
|
|
_lp_n_pv_(11, dmic, 18, 20, 20, DPD2) \
|
|
_lponly_(12, dp, 19, DPD2) \
|
|
_lponly_(13, dsi, 2, DPD) \
|
|
_lponly_(14, dsib, 7, DPD2) \
|
|
_lponly_(15, dsic, 8, DPD2) \
|
|
_lponly_(16, dsid, 9, DPD2) \
|
|
_lponly_(17, emmc, 3, DPD2) \
|
|
_lponly_(18, emmc2, 5, DPD2) \
|
|
_lp_n_pv_(19, gpio, 27, 21, 21, DPD) \
|
|
_lponly_(20, hdmi, 28, DPD) \
|
|
_lponly_(21, hsic, 19, DPD) \
|
|
_lponly_(22, lvds, 25, DPD2) \
|
|
_lponly_(23, mipi-bias, 3, DPD) \
|
|
_lponly_(24, pex-bias, 4, DPD) \
|
|
_lponly_(25, pex-clk1, 5, DPD) \
|
|
_lponly_(26, pex-clk2, 6, DPD) \
|
|
_pvonly_(27, pex-ctrl, 11, 11, DPD2) \
|
|
_lp_n_pv_(28, sdmmc1, 1, 12, 12, DPD2) \
|
|
_lp_n_pv_(29, sdmmc3, 2, 13, 13, DPD2) \
|
|
_lp_n_pv_(30, spi, 14, 22, 22, DPD2) \
|
|
_lp_n_pv_(31, spi-hv, 15, 23, 23, DPD2) \
|
|
_lp_n_pv_(32, uart, 14, 2, 2, DPD) \
|
|
_lponly_(33, usb0, 9, DPD) \
|
|
_lponly_(34, usb1, 10, DPD) \
|
|
_lponly_(35, usb2, 11, DPD) \
|
|
_lponly_(36, usb3, 18, DPD) \
|
|
_lponly_(37, usb-bias, 12, DPD) \
|
|
_pvonly_(38, sys, 12, UINT_MAX, DPD)
|
|
|
|
static const struct tegra_pmc_io_pad_soc tegra210_io_pads[] = {
|
|
TEGRA210_IO_PAD_TABLE(TEGRA210_IO_PAD_LPONLY, TEGRA210_IO_PAD_PVONLY,
|
|
TEGRA210_IO_PAD_LP_N_PV)
|
|
};
|
|
|
|
static const struct pinctrl_pin_desc tegra210_io_pads_pinctrl_desc[] = {
|
|
TEGRA210_IO_PAD_TABLE(TEGRA210_IO_PAD_DESC_LP, TEGRA210_IO_PAD_DESC_PV,
|
|
TEGRA210_IO_PAD_DESC_LP_N_PV)
|
|
};
|
|
|
|
static const struct tegra_pmc_soc tegra210_pmc_soc = {
|
|
.num_powergates = ARRAY_SIZE(tegra210_powergates),
|
|
.powergates = tegra210_powergates,
|
|
.num_cpu_powergates = ARRAY_SIZE(tegra210_cpu_powergates),
|
|
.cpu_powergates = tegra210_cpu_powergates,
|
|
.has_tsense_reset = true,
|
|
.has_gpu_clamps = true,
|
|
.has_ps18 = true,
|
|
.has_bootrom_command = true,
|
|
.has_pclk_clock = true,
|
|
.has_interrupt_polarity_support = true,
|
|
.show_legacy_reset_status = true,
|
|
.has_reboot_base_address = false,
|
|
.skip_lp0_vector_setup = false,
|
|
.skip_legacy_pmc_init = false,
|
|
.skip_power_gate_debug_fs_init = false,
|
|
.skip_restart_register = false,
|
|
.skip_arm_pm_restart = false,
|
|
.num_io_pads = ARRAY_SIZE(tegra210_io_pads),
|
|
.io_pads = tegra210_io_pads,
|
|
.num_descs = ARRAY_SIZE(tegra210_io_pads_pinctrl_desc),
|
|
.descs = tegra210_io_pads_pinctrl_desc,
|
|
.rmap = tegra210_register_map,
|
|
.has_misc_base_address = false,
|
|
.sata_power_gate_in_misc = false,
|
|
};
|
|
|
|
#define TEGRA210B01_IO_PAD_LP_N_PV(_pin, _name, _dpd, _vbit, _io, _reg, _bds) \
|
|
TEGRA210X_IO_PAD_CONFIG(_pin, 1, _name, _dpd, _vbit, _io, _reg, _bds)
|
|
|
|
#define TEGRA210B01_IO_PAD_DESC_LP_N_PV(_pin, _name, _dpd, _vbit, _io, _reg, \
|
|
_bds) \
|
|
TEGRA210_IO_PAD_DESC_LP(_pin, _name, _dpd, _reg)
|
|
|
|
#define TEGRA210B01_IO_PAD_TABLE(_lponly_, _pvonly_, _lp_n_pv_) \
|
|
_lp_n_pv_(0, audio, 17, 5, 5, DPD, false) \
|
|
_lp_n_pv_(1, audio-hv, 29, 18, 18, DPD2, true) \
|
|
_lp_n_pv_(2, cam, 4, 10, 10, DPD2, false) \
|
|
_lponly_(3, csia, 0, DPD) \
|
|
_lponly_(4, csib, 1, DPD) \
|
|
_lponly_(5, csic, 10, DPD2) \
|
|
_lponly_(6, csid, 11, DPD2) \
|
|
_lponly_(7, csie, 12, DPD2) \
|
|
_lponly_(8, csif, 13, DPD2) \
|
|
_lp_n_pv_(9, dbg, 25, 19, 19, DPD, false) \
|
|
_lponly_(10, debug-nonao, 26, DPD) \
|
|
_lp_n_pv_(11, dmic, 18, 20, 20, DPD2, false) \
|
|
_lponly_(12, dp, 19, DPD2) \
|
|
_lponly_(13, dsi, 2, DPD) \
|
|
_lponly_(14, dsib, 7, DPD2) \
|
|
_lponly_(15, dsic, 8, DPD2) \
|
|
_lponly_(16, dsid, 9, DPD2) \
|
|
_lponly_(17, emmc, 3, DPD2) \
|
|
_lponly_(18, emmc2, 5, DPD2) \
|
|
_lp_n_pv_(19, gpio, 27, 21, 21, DPD, true) \
|
|
_lponly_(20, hdmi, 28, DPD) \
|
|
_lponly_(21, hsic, 19, DPD) \
|
|
_lponly_(22, lvds, 25, DPD2) \
|
|
_lponly_(23, mipi-bias, 3, DPD) \
|
|
_lponly_(24, pex-bias, 4, DPD) \
|
|
_lponly_(25, pex-clk1, 5, DPD) \
|
|
_lponly_(26, pex-clk2, 6, DPD) \
|
|
_pvonly_(27, pex-ctrl, 11, 11, DPD2) \
|
|
_lp_n_pv_(28, sdmmc1, 1, 12, 12, DPD2, true) \
|
|
_lp_n_pv_(29, sdmmc3, 2, 13, 13, DPD2, true) \
|
|
_lp_n_pv_(30, spi, 14, 22, 22, DPD2, false) \
|
|
_lp_n_pv_(31, spi-hv, 15, 23, 23, DPD2, false) \
|
|
_lp_n_pv_(32, uart, 14, 2, 2, DPD, false) \
|
|
_lponly_(33, usb0, 9, DPD) \
|
|
_lponly_(34, usb1, 10, DPD) \
|
|
_lponly_(35, usb2, 11, DPD) \
|
|
_lponly_(36, usb3, 18, DPD) \
|
|
_lponly_(37, usb-bias, 12, DPD) \
|
|
_pvonly_(38, sys, 12, UINT_MAX, DPD)
|
|
|
|
static const struct tegra_pmc_io_pad_soc tegra210b01_io_pads[] = {
|
|
TEGRA210B01_IO_PAD_TABLE(TEGRA210_IO_PAD_LPONLY, TEGRA210_IO_PAD_PVONLY,
|
|
TEGRA210B01_IO_PAD_LP_N_PV)
|
|
};
|
|
|
|
static const struct pinctrl_pin_desc tegra210b01_io_pads_pinctrl_desc[] = {
|
|
TEGRA210B01_IO_PAD_TABLE(TEGRA210_IO_PAD_DESC_LP,
|
|
TEGRA210_IO_PAD_DESC_PV, TEGRA210B01_IO_PAD_DESC_LP_N_PV)
|
|
};
|
|
|
|
static const struct tegra_pmc_soc tegra210b01_pmc_soc = {
|
|
.num_powergates = ARRAY_SIZE(tegra210_powergates),
|
|
.powergates = tegra210_powergates,
|
|
.num_cpu_powergates = ARRAY_SIZE(tegra210_cpu_powergates),
|
|
.cpu_powergates = tegra210_cpu_powergates,
|
|
.has_tsense_reset = true,
|
|
.has_gpu_clamps = true,
|
|
.has_ps18 = true,
|
|
.has_bootrom_command = true,
|
|
.has_pclk_clock = true,
|
|
.has_interrupt_polarity_support = true,
|
|
.show_legacy_reset_status = true,
|
|
.has_reboot_base_address = false,
|
|
.skip_lp0_vector_setup = false,
|
|
.skip_legacy_pmc_init = false,
|
|
.skip_power_gate_debug_fs_init = false,
|
|
.skip_restart_register = false,
|
|
.skip_arm_pm_restart = false,
|
|
.num_io_pads = ARRAY_SIZE(tegra210b01_io_pads),
|
|
.io_pads = tegra210b01_io_pads,
|
|
.num_descs = ARRAY_SIZE(tegra210b01_io_pads_pinctrl_desc),
|
|
.descs = tegra210b01_io_pads_pinctrl_desc,
|
|
.rmap = tegra210_register_map,
|
|
};
|
|
|
|
/* Tegra 186 register map */
|
|
static const unsigned long tegra186_register_map[TEGRA_PMC_MAX_REG] = {
|
|
[TEGRA_PMC_CNTRL] = 0x0,
|
|
[TEGRA_PMC_DPD_PADS_ORIDE] = 0x08,
|
|
[TEGRA_PMC_BLINK_TIMER] = 0x30,
|
|
[TEGRA_PMC_FUSE_CTRL] = 0x100,
|
|
[TEGRA_PMC_IMPL_RAMDUMP_CTL_STATUS] = 0x10C,
|
|
[TEGRA_PMC_E_18V_PWR] = 0x3C,
|
|
[TEGRA_PMC_E_33V_PWR] = 0x40,
|
|
[TEGRA_PMC_PWR_NO_IOPOWER] = 0x44,
|
|
[TEGRA_PMC_RST_STATUS] = 0x70,
|
|
[TEGRA_PMC_SATA_PWRGT_0] = 0x68,
|
|
[TEGRA_PMC_UFSHC_PWR_CNTRL_0] = 0xF4,
|
|
[TEGRA_PMC_IO_DPD_REQ] = 0x74,
|
|
[TEGRA_PMC_IO_DPD_STATUS] = 0x78,
|
|
[TEGRA_PMC_IO_DPD2_REQ] = 0x7C,
|
|
[TEGRA_PMC_IO_DPD2_STATUS] = 0x80,
|
|
[TEGRA_PMC_SCRATCH0] = 0x2000,
|
|
};
|
|
|
|
#define TEGRA186_IO_PAD_CONFIG(_pin, _npins, _name, _dpd_reg, _dpd_bit, \
|
|
_padv_reg, _padv_bit, _v0, _v1, _iopwr_bit, \
|
|
_bds) \
|
|
{ \
|
|
.name = #_name, \
|
|
.pins = {(_pin)}, \
|
|
.npins = _npins, \
|
|
.dpd_req_reg = TEGRA_PMC_IO_##_dpd_reg##_REQ, \
|
|
.dpd_status_reg = TEGRA_PMC_IO_##_dpd_reg##_STATUS, \
|
|
.dpd_timer_reg = TEGRA_PMC_SEL_DPD_TIM, \
|
|
.dpd_sample_reg = TEGRA_PMC_IO_DPD_SAMPLE, \
|
|
.dpd = _dpd_bit, \
|
|
.io_pad_pwr_det_val_reg = TEGRA_PMC_##_padv_reg##_PWR, \
|
|
.io_pad_pwr_det_enable_reg = UINT_MAX, \
|
|
.pad_uv_0 = TEGRA_IO_PAD_VOLTAGE_##_v0##000UV, \
|
|
.pad_uv_1 = TEGRA_IO_PAD_VOLTAGE_##_v1##000UV, \
|
|
.voltage = _padv_bit, \
|
|
.io_power = _iopwr_bit, \
|
|
.bdsdmem_cfc = _bds, \
|
|
},
|
|
|
|
#define TEGRA186_IO_PAD_LPONLY(_pin, _name, _dpd_reg, _dpd_bit, _iopwr_bit, \
|
|
_bds) \
|
|
TEGRA186_IO_PAD_CONFIG(_pin, 1, _name, _dpd_reg, _dpd_bit, \
|
|
E_33V, UINT_MAX, 1200, 1200, _iopwr_bit, _bds)
|
|
|
|
#define TEGRA186_IO_PAD_LP_N_PV(_pin, _name, _dpd_reg, _dpd_bit, _padv_reg, \
|
|
_padv_bit, _v0, _v1, _iopwr_bit, _bds) \
|
|
TEGRA186_IO_PAD_CONFIG(_pin, 1, _name, _dpd_reg, _dpd_bit, \
|
|
_padv_reg, _padv_bit, _v0, _v1, _iopwr_bit, \
|
|
_bds)
|
|
|
|
#define TEGRA186_IO_PAD_PVONLY(_pin, _name, _padv_reg, _padv_bit, _v0, _v1, \
|
|
_iopwr_bit, _bds) \
|
|
TEGRA186_IO_PAD_CONFIG(_pin, 1, _name, DPD, UINT_MAX, _padv_reg, \
|
|
_padv_bit, _v0, _v1, _iopwr_bit, _bds)
|
|
|
|
#define TEGRA186_IO_PAD_DESC_LP(_pin, _name, _dpd_reg, _dpd_bit, _iopwr_bit, \
|
|
_bds) \
|
|
{ \
|
|
.number = _pin, \
|
|
.name = #_name, \
|
|
},
|
|
|
|
#define TEGRA186_IO_PAD_DESC_LP_N_PV(_pin, _name, _dpd_reg, _dpd_bit, \
|
|
_padv_reg, _padv_bit, _v0, _v1, \
|
|
_iopwr_bit, _bds) \
|
|
TEGRA186_IO_PAD_DESC_LP(_pin, _name, _dpd_reg, _dpd_bit, _iopwr_bit, _bds)
|
|
|
|
#define TEGRA186_IO_PAD_DESC_PV(_pin, _name, _padv_reg, _padv_bit, _v0, _v1, \
|
|
_iopwr_bit, _bds) \
|
|
TEGRA186_IO_PAD_DESC_LP(_pin, _name, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX)
|
|
|
|
#define TEGRA186_IO_PAD_TABLE(_lponly_, _pvonly_, _lp_n_pv_) \
|
|
_lponly_(0, csia, DPD, 0, UINT_MAX, false) \
|
|
_lponly_(1, csib, DPD, 1, UINT_MAX, false) \
|
|
_lponly_(2, dsi, DPD, 2, UINT_MAX, false) \
|
|
_lponly_(3, mipi-bias, DPD, 3, 9, false) \
|
|
_lponly_(4, pex-clk-bias, DPD, 4, UINT_MAX, false) \
|
|
_lponly_(5, pex-clk3, DPD, 5, UINT_MAX, false) \
|
|
_lponly_(6, pex-clk2, DPD, 6, UINT_MAX, false) \
|
|
_lponly_(7, pex-clk1, DPD, 7, UINT_MAX, false) \
|
|
_lponly_(8, usb0, DPD, 9, UINT_MAX, false) \
|
|
_lponly_(9, usb1, DPD, 10, UINT_MAX, false) \
|
|
_lponly_(10, usb2, DPD, 11, UINT_MAX, false) \
|
|
_lponly_(11, usb-bias, DPD, 12, UINT_MAX, false) \
|
|
_lponly_(12, uart, DPD, 14, 2, false) \
|
|
_lponly_(13, audio, DPD, 17, 5, false) \
|
|
_lponly_(14, hsic, DPD, 19, UINT_MAX, false) \
|
|
_lp_n_pv_(15, dbg, DPD, 25, E_18V, 4, 1200, 1800, 19, false) \
|
|
_lponly_(16, hdmi-dp0, DPD, 28, UINT_MAX, false) \
|
|
_lponly_(17, hdmi-dp1, DPD, 29, UINT_MAX, false) \
|
|
_lponly_(18, pex-ctrl, DPD2, 0, 11, false) \
|
|
_lp_n_pv_(19, sdmmc2-hv, DPD2, 2, E_33V, 5, 1800, 3300, 30, true) \
|
|
_lponly_(20, sdmmc4, DPD2, 4, 14, false) \
|
|
_lponly_(21, cam, DPD2, 6, 10, false) \
|
|
_lponly_(22, dsib, DPD2, 8, UINT_MAX, false) \
|
|
_lponly_(23, dsic, DPD2, 9, UINT_MAX, false) \
|
|
_lponly_(24, dsid, DPD2, 10, UINT_MAX, false) \
|
|
_lponly_(25, csic, DPD2, 11, UINT_MAX, false) \
|
|
_lponly_(26, csid, DPD2, 12, UINT_MAX, false) \
|
|
_lponly_(27, csie, DPD2, 13, UINT_MAX, false) \
|
|
_lponly_(28, csif, DPD2, 14, UINT_MAX, false) \
|
|
_lp_n_pv_(29, spi, DPD2, 15, E_18V, 5, 1200, 1800, 22, false) \
|
|
_lp_n_pv_(30, ufs, DPD2, 17, E_18V, 0, 1200, 1800, 6, false) \
|
|
_lp_n_pv_(31, dmic-hv, DPD2, 20, E_33V, 2, 1800, 3300, 28, true) \
|
|
_lponly_(32, edp, DPD2, 21, 4, false) \
|
|
_lp_n_pv_(33, sdmmc1-hv, DPD2, 23, E_33V, 4, 1800, 3300, 15, true) \
|
|
_lp_n_pv_(34, sdmmc3-hv, DPD2, 24, E_33V, 6, 1800, 3300, 31, true) \
|
|
_lponly_(35, conn, DPD2, 28, 3, false) \
|
|
_lp_n_pv_(36, audio-hv, DPD2, 29, E_33V, 1, 1800, 3300, 18, true) \
|
|
_pvonly_(37, ao-hv, E_33V, 0, 1800, 3300, 27, true)
|
|
|
|
static const struct tegra_pmc_io_pad_soc tegra186_io_pads[] = {
|
|
TEGRA186_IO_PAD_TABLE(TEGRA186_IO_PAD_LPONLY, TEGRA186_IO_PAD_PVONLY,
|
|
TEGRA186_IO_PAD_LP_N_PV)
|
|
};
|
|
|
|
static const struct pinctrl_pin_desc tegra186_io_pads_pinctrl_desc[] = {
|
|
TEGRA186_IO_PAD_TABLE(TEGRA186_IO_PAD_DESC_LP, TEGRA186_IO_PAD_DESC_PV,
|
|
TEGRA186_IO_PAD_DESC_LP_N_PV)
|
|
};
|
|
|
|
static const struct tegra_pmc_soc tegra186_pmc_soc = {
|
|
.has_tsense_reset = false,
|
|
.has_pclk_clock = false,
|
|
.has_interrupt_polarity_support = false,
|
|
.show_legacy_reset_status = false,
|
|
.has_reboot_base_address = true,
|
|
.skip_lp0_vector_setup = true,
|
|
.skip_legacy_pmc_init = true,
|
|
.skip_power_gate_debug_fs_init = true,
|
|
.skip_restart_register = true,
|
|
.skip_arm_pm_restart = true,
|
|
.num_io_pads = ARRAY_SIZE(tegra186_io_pads),
|
|
.io_pads = tegra186_io_pads,
|
|
.num_descs = ARRAY_SIZE(tegra186_io_pads_pinctrl_desc),
|
|
.descs = tegra186_io_pads_pinctrl_desc,
|
|
.rmap = tegra186_register_map,
|
|
.has_ps18 = true,
|
|
.has_misc_base_address = false,
|
|
.sata_power_gate_in_misc = false,
|
|
};
|
|
|
|
/* Tegra 194 register map */
|
|
static const unsigned long tegra194_register_map[TEGRA_PMC_MAX_REG] = {
|
|
[TEGRA_PMC_FUSE_CTRL] = 0x10,
|
|
[TEGRA_PMC_IMPL_RAMDUMP_CTL_STATUS] = 0x10C,
|
|
[TEGRA_PMC_E_18V_PWR] = 0x3C,
|
|
[TEGRA_PMC_E_33V_PWR] = 0x40,
|
|
[TEGRA_PMC_RST_STATUS] = 0x70,
|
|
[TEGRA_PMC_SATA_PWRGT_0] = 0x8,
|
|
[TEGRA_PMC_UFSHC_PWR_CNTRL_0] = 0xF4,
|
|
[TEGRA_PMC_IO_DPD_REQ] = 0x74,
|
|
[TEGRA_PMC_IO_DPD_STATUS] = 0x78,
|
|
[TEGRA_PMC_IO_DPD2_REQ] = 0x7C,
|
|
[TEGRA_PMC_IO_DPD2_STATUS] = 0x80,
|
|
[TEGRA_PMC_SCRATCH0] = 0x2000,
|
|
};
|
|
|
|
#define TEGRA194_IO_PAD_TABLE(_lponly_, _pvonly_, _lp_n_pv_) \
|
|
_lponly_(0, csia, DPD, 0, UINT_MAX, false) \
|
|
_lponly_(1, csib, DPD, 1, UINT_MAX, false) \
|
|
_lponly_(2, mipi-bias, DPD, 3, UINT_MAX, false) \
|
|
_lponly_(3, pex-clk-bias, DPD, 4, UINT_MAX, false) \
|
|
_lponly_(4, pex-clk3, DPD, 5, UINT_MAX, false) \
|
|
_lponly_(5, pex-clk2, DPD, 6, UINT_MAX, false) \
|
|
_lponly_(6, pex-clk1, DPD, 7, UINT_MAX, false) \
|
|
_lponly_(7, uart, DPD, 14, UINT_MAX, false) \
|
|
_lponly_(8, audio, DPD, 17, UINT_MAX, false) \
|
|
_lp_n_pv_(9, dbg, DPD, 25, E_18V, 4, 1200, 1800, UINT_MAX, false) \
|
|
_lponly_(10, hdmi-dp3, DPD, 26, UINT_MAX, false) \
|
|
_lponly_(11, hdmi-dp2, DPD, 27, UINT_MAX, false) \
|
|
_lponly_(12, hdmi-dp0, DPD, 28, UINT_MAX, false) \
|
|
_lponly_(13, hdmi-dp1, DPD, 29, UINT_MAX, false) \
|
|
_lponly_(14, pex-ctrl, DPD2, 0, UINT_MAX, false) \
|
|
_lponly_(15, sdmmc4, DPD2, 4, UINT_MAX, false) \
|
|
_lponly_(16, cam, DPD2, 6, UINT_MAX, false) \
|
|
_lponly_(17, csic, DPD2, 11, UINT_MAX, false) \
|
|
_lponly_(18, csid, DPD2, 12, UINT_MAX, false) \
|
|
_lponly_(19, csie, DPD2, 13, UINT_MAX, false) \
|
|
_lponly_(20, csif, DPD2, 14, UINT_MAX, false) \
|
|
_lp_n_pv_(21, spi, DPD2, 15, E_18V, 5, 1200, 1800, UINT_MAX, false) \
|
|
_lp_n_pv_(22, ufs, DPD2, 17, E_18V, 0, 1200, 1800, UINT_MAX, false) \
|
|
_lponly_(23, edp, DPD2, 21, UINT_MAX, false) \
|
|
_lp_n_pv_(24, sdmmc1-hv, DPD2, 23, E_33V, 4, 1800, 3300, UINT_MAX, true) \
|
|
_lp_n_pv_(25, sdmmc3-hv, DPD2, 24, E_33V, 6, 1800, 3300, UINT_MAX, true) \
|
|
_lponly_(26, conn, DPD2, 28, UINT_MAX, false) \
|
|
_lp_n_pv_(27, audio-hv, DPD2, 29, E_33V, 1, 1800, 3300, UINT_MAX, true) \
|
|
_pvonly_(28, ao-hv, E_33V, 0, 1800, 3300, UINT_MAX, true) \
|
|
_lponly_(29, csig, DPD2, 18, UINT_MAX, false) \
|
|
_lponly_(30, csih, DPD2, 19, UINT_MAX, false)
|
|
|
|
static const struct tegra_pmc_io_pad_soc tegra194_io_pads[] = {
|
|
TEGRA194_IO_PAD_TABLE(TEGRA186_IO_PAD_LPONLY, TEGRA186_IO_PAD_PVONLY,
|
|
TEGRA186_IO_PAD_LP_N_PV)
|
|
};
|
|
|
|
static const struct pinctrl_pin_desc tegra194_io_pads_pinctrl_desc[] = {
|
|
TEGRA194_IO_PAD_TABLE(TEGRA186_IO_PAD_DESC_LP, TEGRA186_IO_PAD_DESC_PV,
|
|
TEGRA186_IO_PAD_DESC_LP_N_PV)
|
|
};
|
|
|
|
static const struct tegra_pmc_soc tegra194_pmc_soc = {
|
|
.has_tsense_reset = false,
|
|
.has_pclk_clock = false,
|
|
.has_interrupt_polarity_support = false,
|
|
.show_legacy_reset_status = false,
|
|
.has_reboot_base_address = true,
|
|
.skip_lp0_vector_setup = true,
|
|
.skip_legacy_pmc_init = true,
|
|
.skip_power_gate_debug_fs_init = true,
|
|
.skip_restart_register = true,
|
|
.skip_arm_pm_restart = true,
|
|
.num_io_pads = ARRAY_SIZE(tegra194_io_pads),
|
|
.io_pads = tegra194_io_pads,
|
|
.num_descs = ARRAY_SIZE(tegra194_io_pads_pinctrl_desc),
|
|
.descs = tegra194_io_pads_pinctrl_desc,
|
|
.rmap = tegra194_register_map,
|
|
.has_ps18 = true,
|
|
.has_misc_base_address = true,
|
|
.sata_power_gate_in_misc = true,
|
|
};
|
|
|
|
static const struct of_device_id tegra_pmc_match[] = {
|
|
{ .compatible = "nvidia,tegra194-pmc", .data = &tegra194_pmc_soc },
|
|
{ .compatible = "nvidia,tegra186-pmc", .data = &tegra186_pmc_soc },
|
|
{ .compatible = "nvidia,tegra210-pmc", .data = &tegra210_pmc_soc },
|
|
{
|
|
.compatible = "nvidia,tegra210b01-pmc",
|
|
.data = &tegra210b01_pmc_soc
|
|
},
|
|
{ .compatible = "nvidia,tegra132-pmc", .data = &tegra124_pmc_soc },
|
|
{ .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,
|
|
},
|
|
.probe = tegra_pmc_probe,
|
|
};
|
|
|
|
static int __init _tegra_pmc_driver_init(void)
|
|
{
|
|
return platform_driver_register(&tegra_pmc_driver);
|
|
}
|
|
arch_initcall(_tegra_pmc_driver_init);
|
|
|
|
/*
|
|
* Looking for lp0_vec=size@start.
|
|
*/
|
|
static int __init tegra_lp0_vec_arg(char *options)
|
|
{
|
|
|
|
char *p = options;
|
|
|
|
pmc->lp0_vec_size = memparse(p, &p);
|
|
|
|
if (*p == '@')
|
|
pmc->lp0_vec_phys = memparse(p+1, NULL);
|
|
|
|
if (!pmc->lp0_vec_size || !pmc->lp0_vec_phys) {
|
|
pmc->lp0_vec_size = 0;
|
|
pmc->lp0_vec_phys = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
early_param("lp0_vec", tegra_lp0_vec_arg);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match);
|
|
if (!np) {
|
|
/*
|
|
* Fall back to legacy initialization for 32-bit ARM only. All
|
|
* 64-bit ARM device tree files for Tegra are required to have
|
|
* a PMC node.
|
|
*
|
|
* This is for backwards-compatibility with old device trees
|
|
* that didn't contain a PMC node. Note that in this case the
|
|
* SoC data can't be matched and therefore powergating is
|
|
* disabled.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_ARM) && soc_is_tegra210_n_before()) {
|
|
pr_warn("DT node not found, powergating disabled\n");
|
|
|
|
regs.start = 0x7000e400;
|
|
regs.end = 0x7000e7ff;
|
|
regs.flags = IORESOURCE_MEM;
|
|
|
|
pr_warn("Using memory region %pR\n", ®s);
|
|
} else {
|
|
/*
|
|
* At this point we're not running on Tegra, so play
|
|
* nice with multi-platform kernels.
|
|
*/
|
|
return 0;
|
|
}
|
|
} else {
|
|
/*
|
|
* Extract information from the device tree if we've found a
|
|
* matching node.
|
|
*/
|
|
if (of_address_to_resource(np, 0, ®s) < 0) {
|
|
pr_err("failed to get PMC registers\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
pmc->soc = match->data;
|
|
}
|
|
|
|
pmc->base = ioremap_nocache(regs.start, resource_size(®s));
|
|
if (!pmc->base) {
|
|
pr_err("failed to map PMC registers\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (pmc->soc && pmc->soc->has_reboot_base_address) {
|
|
pmc->reboot_base = of_iomap(np, 1);
|
|
if (!pmc->reboot_base) {
|
|
pr_err("Failed to map reboot PMC registers\n");
|
|
return -ENXIO;
|
|
}
|
|
} else {
|
|
pmc->reboot_base = pmc->base;
|
|
}
|
|
|
|
if (pmc->soc && pmc->soc->has_misc_base_address) {
|
|
pmc->misc_base = of_iomap(np, 2);
|
|
if (!pmc->misc_base) {
|
|
pr_err("Failed to map misc PMC registers\n");
|
|
return -ENXIO;
|
|
}
|
|
} else {
|
|
pmc->misc_base = pmc->base;
|
|
}
|
|
|
|
mutex_init(&pmc->powergates_lock);
|
|
|
|
/* backup the base address */
|
|
pmc->early_base = pmc->base;
|
|
|
|
/*
|
|
* Invert the interrupt polarity if a PMC device tree node exists and
|
|
* contains the nvidia,invert-interrupt property.
|
|
*/
|
|
if (pmc->soc && pmc->soc->has_interrupt_polarity_support) {
|
|
invert = of_property_read_bool(np, "nvidia,invert-interrupt");
|
|
value = tegra_pmc_reg_readl(TEGRA_PMC_CNTRL);
|
|
|
|
if (invert)
|
|
value |= PMC_CNTRL_INTR_POLARITY;
|
|
else
|
|
value &= ~PMC_CNTRL_INTR_POLARITY;
|
|
|
|
tegra_pmc_reg_writel(value, TEGRA_PMC_CNTRL);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
if (pmc->soc && pmc->soc->skip_lp0_vector_setup)
|
|
goto skip_lp0_setup;
|
|
|
|
tegra_lp0_wakeup.of_node = np;
|
|
INIT_LIST_HEAD(&tegra_lp0_wakeup.wake_list);
|
|
|
|
if (pmc->lp0_vec_size &&
|
|
(pmc->lp0_vec_phys < memblock_end_of_DRAM()))
|
|
if (memblock_reserve(pmc->lp0_vec_phys, pmc->lp0_vec_size))
|
|
pr_err("Failed to reserve lp0_vec %08llx@%08llx\n",
|
|
(u64)pmc->lp0_vec_size,
|
|
(u64)pmc->lp0_vec_phys);
|
|
skip_lp0_setup:
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
early_initcall(tegra_pmc_early_init);
|
|
|
|
static bool get_secure_pmc_setting(void)
|
|
{
|
|
struct device_node *np;
|
|
static bool secure_pmc;
|
|
static bool initialized;
|
|
struct resource regs;
|
|
|
|
if (!initialized) {
|
|
initialized = true;
|
|
np = of_find_matching_node(NULL, tegra_pmc_match);
|
|
if (!np) {
|
|
pr_err("%s: compatible node not found\n", __func__);
|
|
secure_pmc = false;
|
|
return secure_pmc;
|
|
}
|
|
secure_pmc = of_find_property(np,"nvidia,secure-pmc",NULL);
|
|
pr_info("%s: done secure_pmc=%d\n", __func__, secure_pmc);
|
|
|
|
/*
|
|
* Initialize pmc->base for calls which are performed much
|
|
* before tegra_pmc_probe() or tegra_pmc_early_init().
|
|
*/
|
|
if (!secure_pmc) {
|
|
if (!(of_address_to_resource(np, 0, ®s) < 0))
|
|
pmc->base = ioremap_nocache(regs.start,
|
|
resource_size(®s));
|
|
else {
|
|
pr_err("failed to set pmc-base\n");
|
|
WARN_ON(1);
|
|
}
|
|
}
|
|
}
|
|
return secure_pmc;
|
|
}
|
|
|
|
/* PMC register read/write/update with offset from the base */
|
|
u32 tegra_pmc_readl(unsigned long offset)
|
|
{
|
|
struct pmc_smc_regs regs;
|
|
|
|
if (get_secure_pmc_setting()) {
|
|
regs.args[0] = PMC_READ;
|
|
regs.args[1] = offset;
|
|
regs.args[2] = 0;
|
|
regs.args[3] = 0;
|
|
regs.args[4] = 0;
|
|
regs.args[5] = 0;
|
|
send_smc(TEGRA_SIP_PMC_COMMAND_FID, ®s);
|
|
return (u32)regs.args[0];
|
|
}
|
|
return readl(pmc->base + offset);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_readl);
|
|
|
|
void tegra_pmc_writel(u32 value, unsigned long offset)
|
|
{
|
|
struct pmc_smc_regs regs;
|
|
|
|
if (get_secure_pmc_setting()) {
|
|
regs.args[0] = PMC_WRITE;
|
|
regs.args[1] = offset;
|
|
regs.args[2] = value;
|
|
regs.args[3] = 0;
|
|
regs.args[4] = 0;
|
|
regs.args[5] = 0;
|
|
send_smc(TEGRA_SIP_PMC_COMMAND_FID, ®s);
|
|
} else {
|
|
writel(value, pmc->base + offset);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_writel);
|
|
|
|
void tegra_pmc_writel_relaxed(u32 value, unsigned long offset)
|
|
{
|
|
if (get_secure_pmc_setting())
|
|
tegra_pmc_writel(value, offset);
|
|
else
|
|
writel_relaxed(value, pmc->base + offset);
|
|
}
|
|
EXPORT_SYMBOL(tegra_pmc_writel_relaxed);
|
|
|
|
/* PMC register read/write/update with pmc register enums */
|
|
static u32 tegra_pmc_reg_readl(enum pmc_regs reg)
|
|
{
|
|
return tegra_pmc_readl(pmc->soc->rmap[reg]);
|
|
}
|
|
|
|
static void tegra_pmc_reg_writel(u32 value, enum pmc_regs reg)
|
|
{
|
|
tegra_pmc_writel(value, pmc->soc->rmap[reg]);
|
|
}
|
|
|
|
static void pmc_iopower_enable(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
if (pad->io_power == UINT_MAX)
|
|
return;
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_PWR_NO_IOPOWER,
|
|
BIT(pad->io_power), 0);
|
|
}
|
|
|
|
static void pmc_iopower_disable(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
if (pad->io_power == UINT_MAX)
|
|
return;
|
|
|
|
tegra_pmc_register_update(TEGRA_PMC_PWR_NO_IOPOWER, BIT(pad->io_power),
|
|
BIT(pad->io_power));
|
|
}
|
|
|
|
static int pmc_iopower_get_status(const struct tegra_pmc_io_pad_soc *pad)
|
|
{
|
|
unsigned int no_iopower;
|
|
|
|
if (pad->io_power == UINT_MAX)
|
|
return 1;
|
|
|
|
no_iopower = tegra_pmc_reg_readl(TEGRA_PMC_PWR_NO_IOPOWER);
|
|
|
|
return !(no_iopower & BIT(pad->io_power));
|
|
}
|
|
|
|
|
|
static int tegra_pmc_io_rail_change_notify_cb(struct notifier_block *nb,
|
|
unsigned long event, void *v)
|
|
{
|
|
struct tegra_io_pad_regulator *tip_reg;
|
|
const struct tegra_pmc_io_pad_soc *pad;
|
|
unsigned long flags;
|
|
|
|
if (!(event & (REGULATOR_EVENT_POST_ENABLE |
|
|
REGULATOR_EVENT_PRE_DISABLE |
|
|
REGULATOR_EVENT_PRE_ENABLE |
|
|
REGULATOR_EVENT_DISABLE)))
|
|
return NOTIFY_OK;
|
|
|
|
tip_reg = container_of(nb, struct tegra_io_pad_regulator, nb);
|
|
pad = tip_reg->pad;
|
|
|
|
spin_lock_irqsave(&pwr_lock, flags);
|
|
|
|
if (pad->bdsdmem_cfc) {
|
|
if (event & REGULATOR_EVENT_PRE_ENABLE)
|
|
pmc_iopower_enable(pad);
|
|
|
|
if (event & REGULATOR_EVENT_DISABLE)
|
|
pmc_iopower_disable(pad);
|
|
} else {
|
|
if (event & REGULATOR_EVENT_POST_ENABLE)
|
|
pmc_iopower_enable(pad);
|
|
|
|
if (event & REGULATOR_EVENT_PRE_DISABLE)
|
|
pmc_iopower_disable(pad);
|
|
}
|
|
|
|
dev_dbg(pmc->dev, "tegra-iopower: %s: event 0x%08lx state: %d\n",
|
|
pad->name, event, pmc_iopower_get_status(pad));
|
|
|
|
spin_unlock_irqrestore(&pwr_lock, flags);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int tegra_pmc_io_power_init_one(struct device *dev,
|
|
const struct tegra_pmc_io_pad_soc *pad,
|
|
u32 *disabled_mask,
|
|
bool enable_pad_volt_config)
|
|
{
|
|
struct tegra_io_pad_regulator *tip_reg;
|
|
char regname[32]; /* 32 is max size of property name */
|
|
char *prefix;
|
|
int curr_io_uv;
|
|
int ret;
|
|
|
|
prefix = "vddio";
|
|
snprintf(regname, 32, "%s-%s-supply", prefix, pad->name);
|
|
if (!of_find_property(dev->of_node, regname, NULL)) {
|
|
prefix = "iopower";
|
|
snprintf(regname, 32, "%s-%s-supply", prefix, pad->name);
|
|
if (!of_find_property(dev->of_node, regname, NULL)) {
|
|
dev_info(dev, "Regulator supply %s not available\n",
|
|
regname);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
tip_reg = devm_kzalloc(dev, sizeof(*tip_reg), GFP_KERNEL);
|
|
if (!tip_reg)
|
|
return -ENOMEM;
|
|
|
|
tip_reg->pad = pad;
|
|
|
|
snprintf(regname, 32, "%s-%s", prefix, pad->name);
|
|
tip_reg->regulator = devm_regulator_get(dev, regname);
|
|
if (IS_ERR(tip_reg->regulator)) {
|
|
ret = PTR_ERR(tip_reg->regulator);
|
|
dev_err(dev, "Failed to get regulator %s: %d\n", regname, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (!enable_pad_volt_config)
|
|
goto skip_pad_config;
|
|
|
|
ret = regulator_get_voltage(tip_reg->regulator);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to get IO rail %s voltage: %d\n",
|
|
regname, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ret == 1200000)
|
|
curr_io_uv = TEGRA_IO_PAD_VOLTAGE_1200000UV;
|
|
else if (ret == 1800000)
|
|
curr_io_uv = TEGRA_IO_PAD_VOLTAGE_1800000UV;
|
|
else
|
|
curr_io_uv = TEGRA_IO_PAD_VOLTAGE_3300000UV;
|
|
|
|
ret = _tegra_pmc_io_pad_set_voltage(pad, curr_io_uv);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set voltage %duV of I/O pad %s: %d\n",
|
|
curr_io_uv, pad->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
skip_pad_config:
|
|
tip_reg->nb.notifier_call = tegra_pmc_io_rail_change_notify_cb;
|
|
ret = devm_regulator_register_notifier(tip_reg->regulator,
|
|
&tip_reg->nb);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to register regulator %s notifier: %d\n",
|
|
regname, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (regulator_is_enabled(tip_reg->regulator)) {
|
|
pmc_iopower_enable(pad);
|
|
} else {
|
|
*disabled_mask |= BIT(pad->io_power);
|
|
pmc_iopower_disable(pad);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int io_pad_show(struct seq_file *s, void *data)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < pmc->soc->num_io_pads; i++) {
|
|
const struct tegra_pmc_io_pad_soc *pad = &pmc->soc->io_pads[i];
|
|
seq_printf(s, "%16s: dpd = %2d, v = %2d io_power = %2d ",
|
|
pad->name, pad->dpd, pad->voltage, pad->io_power);
|
|
seq_printf(s, "pins[0] = %2d npins = %2d dpdreq = 0x%-3x ",
|
|
pad->pins[0], pad->npins, pad->dpd_req_reg);
|
|
seq_printf(s, "dpdsts = 0x%-3x dpdtmr = 0x%-3x ",
|
|
pad->dpd_status_reg, pad->dpd_timer_reg);
|
|
seq_printf(s, "dpdsmpl = 0x%-3x ", pad->dpd_sample_reg);
|
|
seq_printf(s, "bds = %d detenb = 0x%-3x detval = 0x%-3x ",
|
|
pad->bdsdmem_cfc, pad->io_pad_pwr_det_enable_reg,
|
|
pad->io_pad_pwr_det_val_reg);
|
|
seq_printf(s, "pad_uv_0 = %d pad_uv_1 = %d\n",
|
|
pad->pad_uv_0, pad->pad_uv_1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int io_pad_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, io_pad_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations io_pad_fops = {
|
|
.open = io_pad_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void tegra_pmc_io_pad_debugfs_init(struct device *dev)
|
|
{
|
|
struct dentry *d;
|
|
|
|
d = debugfs_create_file("tegra-pmc-io-pads", S_IRUGO, NULL, NULL,
|
|
&io_pad_fops);
|
|
if (!d)
|
|
dev_err(dev, "Error in creating the debugFS for pmc-io-pad\n");
|
|
}
|
|
#else
|
|
static void tegra_pmc_io_pad_debugfs_init(struct device *dev)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int tegra_pmc_iopower_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
bool enable_pad_volt_config = false;
|
|
u32 pwrio_disabled_mask = 0;
|
|
int i, ret;
|
|
|
|
if (!pmc->base) {
|
|
dev_err(dev, "PMC Driver is not ready\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
enable_pad_volt_config = of_property_read_bool(dev->of_node,
|
|
"nvidia,auto-pad-voltage-config");
|
|
|
|
for (i = 0; i < pmc->soc->num_io_pads; ++i) {
|
|
if (pmc->soc->io_pads[i].io_power == UINT_MAX)
|
|
continue;
|
|
|
|
ret = tegra_pmc_io_power_init_one(&pdev->dev,
|
|
&pmc->soc->io_pads[i],
|
|
&pwrio_disabled_mask,
|
|
enable_pad_volt_config);
|
|
if (ret < 0)
|
|
dev_info(dev, "io-power cell %s init failed: %d\n",
|
|
pmc->soc->io_pads[i].name, ret);
|
|
}
|
|
|
|
dev_info(dev, "NO_IOPOWER setting 0x%x\n", pwrio_disabled_mask);
|
|
tegra_pmc_io_pad_debugfs_init(dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id tegra_pmc_iopower_match[] = {
|
|
{ .compatible = "nvidia,tegra186-pmc-iopower", },
|
|
{ .compatible = "nvidia,tegra210-pmc-iopower", },
|
|
{ }
|
|
};
|
|
|
|
static struct platform_driver tegra_pmc_iopower_driver = {
|
|
.probe = tegra_pmc_iopower_probe,
|
|
.driver = {
|
|
.name = "tegra-pmc-iopower",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = tegra_pmc_iopower_match,
|
|
},
|
|
};
|
|
|
|
builtin_platform_driver(tegra_pmc_iopower_driver);
|