blob: 1e173d681fff673999b72309c2b59fc0f5a7f3bc [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
* Copyright 2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
/* Function unit addresses. */
enum {
UP_TAG_BASE = 0X60000000,
TIMER_BASE = 0X60005000,
CLK_RST_BASE = 0X60006000,
FLOW_CTLR_BASE = 0X60007000,
TEGRA_EVP_BASE = 0x6000f000,
APB_MISC_BASE = 0x70000000,
PMC_CTLR_BASE = 0X7000e400,
MC_CTLR_BASE = 0X70019000,
SYSCTR_CTLR_BASE = 0X700f0000
};
/* UP tag registers. */
static uint32_t *up_tag_ptr = (void *)(UP_TAG_BASE + 0x0);
enum {
UP_TAG_AVP = 0xaaaaaaaa
};
/* APB Misc JTAG Configuration Register */
static uint32_t *misc_pp_config_ctl_ptr = (void *)(APB_MISC_BASE + 0x24);
enum {
PP_CONFIG_CTL_JTAG = 0x1 << 6
};
/* Timer registers. */
static uint32_t *timer_us_ptr = (void *)(TIMER_BASE + 0x10);
/* Clock and reset controller registers. */
static uint32_t *clk_rst_rst_devices_l_ptr = (void *)(CLK_RST_BASE + 0x4);
enum {
SWR_TRIG_SYS_RST = 0x1 << 2
};
static uint32_t *clk_rst_cclk_burst_policy_ptr = (void *)(CLK_RST_BASE + 0x20);
enum {
CCLK_PLLP_BURST_POLICY = 0x20004444
};
static uint32_t *clk_rst_super_cclk_div_ptr = (void *)(CLK_RST_BASE + 0x24);
enum {
SUPER_CDIV_ENB = 0x1 << 31
};
static uint32_t *clk_rst_osc_ctrl_ptr = (void *)(CLK_RST_BASE + 0x50);
enum {
OSC_XOE = 0x1 << 0,
OSC_XOFS_SHIFT = 4,
OSC_XOFS_MASK = 0x3f << OSC_XOFS_SHIFT,
OSC_FREQ_SHIFT = 28,
OSC_FREQ_MASK = 0xf << OSC_FREQ_SHIFT
};
enum {
OSC_FREQ_13 = 0,
OSC_FREQ_16P8 = 1,
OSC_FREQ_19P2 = 4,
OSC_FREQ_38P4 = 5,
OSC_FREQ_12 = 8,
OSC_FREQ_48 = 9,
OSC_FREQ_26 = 12
};
static uint32_t *clk_rst_pllu_base_ptr = (void *)(CLK_RST_BASE + 0xc0);
enum {
PLLU_DIVM_SHIFT = 0,
PLLU_DIVN_SHIFT = 8,
PLLU_OVERRIDE = 0x1 << 24,
PLLU_ENABLE = 0x1 << 30,
PLLU_BYPASS = 0x1 << 31
};
static uint32_t *clk_rst_pllu_misc_ptr = (void *)(CLK_RST_BASE + 0xcc);
enum {
PLLU_LFCON_SHIFT = 4,
PLLU_CPCON_SHIFT = 8,
PLLU_LOCK_ENABLE = 22
};
static uint32_t *clk_rst_pllx_base_ptr = (void *)(CLK_RST_BASE + 0xe0);
enum {
PLLX_ENABLE = 0x1 << 30
};
static uint32_t *clk_rst_rst_dev_u_clr_ptr = (void *)(CLK_RST_BASE + 0x314);
enum {
SWR_CSITE_RST = 0x1 << 9
};
static uint32_t *clk_rst_clk_enb_l_set_ptr = (void *)(CLK_RST_BASE + 0x320);
enum {
CLK_ENB_CPU = 0x1 << 0
};
static uint32_t *clk_rst_clk_out_enb_u_set_ptr =
(void *)(CLK_RST_BASE + 0x330);
enum {
CLK_ENB_CSITE = 0x1 << 9
};
static uint32_t *clk_rst_cpu_softrst_ctrl2_ptr =
(void *)(CLK_RST_BASE + 0x388);
enum {
CAR2PMC_CPU_ACK_WIDTH_SHIFT = 0,
CAR2PMC_CPU_ACK_WIDTH_MASK = 0xfff << CAR2PMC_CPU_ACK_WIDTH_SHIFT
};
static uint32_t *clk_rst_clk_src_mselect_ptr =
(void *)(CLK_RST_BASE + 0x3b4);
enum {
MSELECT_CLK_DIV_SHIFT = 0,
MSELECT_CLK_SRC_SHIFT = 29,
MSELECT_CLK_SRC_PLLP_OUT0 = 0x0 << MSELECT_CLK_SRC_SHIFT,
MSELECT_CLK_SRC_PLLC2_OUT0 = 0x1 << MSELECT_CLK_SRC_SHIFT,
MSELECT_CLK_SRC_PLLC_OUT0 = 0x2 << MSELECT_CLK_SRC_SHIFT,
MSELECT_CLK_SRC_PLLC3_OUT0 = 0x3 << MSELECT_CLK_SRC_SHIFT
};
static uint32_t *clk_rst_rst_dev_v_clr_ptr = (void *)(CLK_RST_BASE + 0x434);
enum {
SWR_MSELECT_RST = 0x1 << 3
};
static uint32_t *clk_rst_clk_enb_v_set_ptr = (void *)(CLK_RST_BASE + 0x440);
enum {
CLK_ENB_CPUG = 0x1 << 0,
CLK_ENB_CPULP = 0x1 << 1,
CLK_ENB_MSELECT = 0x1 << 3
};
static uint32_t *clk_rst_rst_cpulp_cmplx_clr_ptr =
(void *)(CLK_RST_BASE + 0x45c);
static uint32_t *clk_rst_rst_cpug_cmplx_clr_ptr =
(void *)(CLK_RST_BASE + 0x454);
enum {
CLR_CPURESET0 = 0x1 << 0,
CLR_CPURESET1 = 0x1 << 1,
CLR_CPURESET2 = 0x1 << 2,
CLR_CPURESET3 = 0x1 << 3,
CLR_DBGRESET0 = 0x1 << 12,
CLR_DBGRESET1 = 0x1 << 13,
CLR_DBGRESET2 = 0x1 << 14,
CLR_DBGRESET3 = 0x1 << 15,
CLR_CORERESET0 = 0x1 << 16,
CLR_CORERESET1 = 0x1 << 17,
CLR_CORERESET2 = 0x1 << 18,
CLR_CORERESET3 = 0x1 << 19,
CLR_CXRESET0 = 0x1 << 20,
CLR_CXRESET1 = 0x1 << 21,
CLR_CXRESET2 = 0x1 << 22,
CLR_CXRESET3 = 0x1 << 23,
CLR_NONCPURESET = 0x1 << 29
};
/* Reset vector. */
static uint32_t *evp_cpu_reset_ptr = (void *)(TEGRA_EVP_BASE + 0x100);
/* Flow controller registers. */
static uint32_t *flow_ctlr_halt_cop_events_ptr =
(void *)(FLOW_CTLR_BASE + 0x4);
enum {
EVENT_MSEC = 0x1 << 24,
EVENT_JTAG = 0x1 << 28,
FLOW_MODE_SHIFT = 29,
FLOW_MODE_STOP = 2 << FLOW_MODE_SHIFT,
};
static uint32_t *flow_ctlr_cluster_control_ptr =
(void *)(FLOW_CTLR_BASE + 0x2c);
enum {
FLOW_CLUSTER_ACTIVE_LP = 0x1 << 0
};
/* Power management controller registers. */
enum {
PARTID_CRAIL = 0,
PARTID_CELP = 12,
PARTID_CE0 = 14,
PARTID_C0NC = 15,
PARTID_C1NC = 16
};
static uint32_t *pmc_ctlr_clamp_status_ptr = (void *)(PMC_CTLR_BASE + 0x2c);
static uint32_t *pmc_ctlr_pwrgate_toggle_ptr = (void *)(PMC_CTLR_BASE + 0x30);
enum {
PWRGATE_TOGGLE_START = 0x1 << 8
};
static uint32_t *pmc_ctlr_pwrgate_status_ptr = (void *)(PMC_CTLR_BASE + 0x38);
static uint32_t *pmc_ctlr_scratch4_ptr = (void *)(PMC_CTLR_BASE + 0x60);
enum {
PMC_SCRATCH4_LP = 0x1 << 31
};
static uint32_t *pmc_ctlr_cpupwrgood_timer_ptr =
(void *)(PMC_CTLR_BASE + 0xc8);
static uint32_t *pmc_ctlr_scratch41_ptr = (void *)(PMC_CTLR_BASE + 0x140);
static uint32_t *pmc_ctlr_osc_edpd_over_ptr = (void *)(PMC_CTLR_BASE + 0x1a4);
enum {
PMC_XOFS_SHIFT = 1,
PMC_XOFS_MASK = 0x3f << PMC_XOFS_SHIFT
};
/* Memory controller registers. */
static uint32_t *mc_video_protect_size_mb_ptr = (void *)(MC_CTLR_BASE + 0x64c);
static uint32_t *mc_video_protect_reg_ctrl_ptr =
(void *)(MC_CTLR_BASE + 0x650);
enum {
VIDEO_PROTECT_WRITE_ACCESS_DISABLE = 0x1 << 0,
VIDEO_PROTECT_ALLOW_TZ_WRITE_ACCESS = 0x1 << 1
};
/* System counter registers. */
static uint32_t *sysctr_cntcr_ptr = (void *)(SYSCTR_CTLR_BASE + 0x0);
enum {
TSC_CNTCR_ENABLE = 0x1 << 0,
TSC_CNTCR_HDBG = 0x1 << 1
};
static uint32_t *sysctr_cntfid0_ptr = (void *)(SYSCTR_CTLR_BASE + 0x20);
/* Utility functions. */
static inline void __attribute__((always_inline))
__attribute__((noreturn)) halt(void)
{
for (;;);
}
inline static uint32_t read32(const void *addr)
{
return *(volatile uint32_t *)addr;
}
inline static void write32(uint32_t val, void *addr)
{
*(volatile uint32_t *)addr = val;
}
inline static void setbits32(uint32_t bits, void *addr)
{
write32(read32(addr) | bits, addr);
}
inline static void clrbits32(uint32_t bits, void *addr)
{
write32(read32(addr) & ~bits, addr);
}
static void __attribute__((noreturn)) reset(void)
{
write32(SWR_TRIG_SYS_RST, clk_rst_rst_devices_l_ptr);
halt();
}
static void udelay(unsigned usecs)
{
uint32_t start = read32(timer_us_ptr);
while (read32(timer_us_ptr) - start < usecs)
;
}
/* Accessors. */
static int wakeup_on_lp(void)
{
return !!(read32(pmc_ctlr_scratch4_ptr) & PMC_SCRATCH4_LP);
}
static uint32_t get_wakeup_vector(void)
{
return read32(pmc_ctlr_scratch41_ptr);
}
static unsigned get_osc_freq(void)
{
return (read32(clk_rst_osc_ctrl_ptr) & OSC_FREQ_MASK) >> OSC_FREQ_SHIFT;
}
/* Jtag configuration. */
static void enable_jtag(void)
{
write32(PP_CONFIG_CTL_JTAG, misc_pp_config_ctl_ptr);
}
/* Clock configuration. */
static void config_oscillator(void)
{
// Read oscillator drive strength from OSC_EDPD_OVER.XOFS and copy
// to OSC_CTRL.XOFS and set XOE.
uint32_t xofs = (read32(pmc_ctlr_osc_edpd_over_ptr) &
PMC_XOFS_MASK) >> PMC_XOFS_SHIFT;
uint32_t osc_ctrl = read32(clk_rst_osc_ctrl_ptr);
osc_ctrl &= ~OSC_XOFS_MASK;
osc_ctrl |= (xofs << OSC_XOFS_SHIFT);
osc_ctrl |= OSC_XOE;
write32(osc_ctrl, clk_rst_osc_ctrl_ptr);
}
static void config_pllu(void)
{
// Figure out what parameters to use for PLLU.
uint32_t divm, divn, cpcon, lfcon;
switch (get_osc_freq()) {
case OSC_FREQ_12:
case OSC_FREQ_48:
divm = 0x0c;
divn = 0x3c0;
cpcon = 0x0c;
lfcon = 0x02;
break;
case OSC_FREQ_16P8:
divm = 0x07;
divn = 0x190;
cpcon = 0x05;
lfcon = 0x02;
break;
case OSC_FREQ_19P2:
case OSC_FREQ_38P4:
divm = 0x04;
divn = 0xc8;
cpcon = 0x03;
lfcon = 0x02;
break;
case OSC_FREQ_26:
divm = 0x1a;
divn = 0x3c0;
cpcon = 0x0c;
lfcon = 0x02;
break;
default:
// Map anything that's not recognized to 13MHz.
divm = 0x0d;
divn = 0x3c0;
cpcon = 0x0c;
lfcon = 0x02;
}
// Configure PLLU.
uint32_t base = PLLU_BYPASS | PLLU_OVERRIDE |
(divn << PLLU_DIVN_SHIFT) | (divm << PLLU_DIVM_SHIFT);
write32(base, clk_rst_pllu_base_ptr);
uint32_t misc = (cpcon << PLLU_CPCON_SHIFT) |
(lfcon << PLLU_LFCON_SHIFT);
write32(misc, clk_rst_pllu_misc_ptr);
// Enable PLLU.
base &= ~PLLU_BYPASS;
base |= PLLU_ENABLE;
write32(base, clk_rst_pllu_base_ptr);
misc |= PLLU_LOCK_ENABLE;
write32(misc, clk_rst_pllu_misc_ptr);
}
static void config_tsc(void)
{
// Tell the TSC the oscillator frequency.
switch (get_osc_freq()) {
case OSC_FREQ_12:
write32(12000000, sysctr_cntfid0_ptr);
break;
case OSC_FREQ_48:
write32(48000000, sysctr_cntfid0_ptr);
break;
case OSC_FREQ_16P8:
write32(16800000, sysctr_cntfid0_ptr);
break;
case OSC_FREQ_19P2:
write32(19200000, sysctr_cntfid0_ptr);
break;
case OSC_FREQ_38P4:
write32(38400000, sysctr_cntfid0_ptr);
break;
case OSC_FREQ_26:
write32(26000000, sysctr_cntfid0_ptr);
break;
default:
// Default to 13MHz.
write32(13000000, sysctr_cntfid0_ptr);
break;
}
// Enable the TSC.
setbits32(TSC_CNTCR_ENABLE | TSC_CNTCR_HDBG, sysctr_cntcr_ptr);
}
static void enable_cpu_clocks(void)
{
// Enable the CPU complex clock.
write32(CLK_ENB_CPU, clk_rst_clk_enb_l_set_ptr);
write32(CLK_ENB_CPUG | CLK_ENB_CPULP, clk_rst_clk_enb_v_set_ptr);
}
/* Function unit configuration. */
static void config_core_sight(void)
{
// Enable the CoreSight clock.
write32(CLK_ENB_CSITE, clk_rst_clk_out_enb_u_set_ptr);
/*
* De-assert CoreSight reset.
* NOTE: We're leaving the CoreSight clock on the oscillator for
* now. It will be restored to its original clock source
* when the CPU-side restoration code runs.
*/
write32(SWR_CSITE_RST, clk_rst_rst_dev_u_clr_ptr);
}
static void config_mselect(void)
{
// Set MSELECT clock source to PLLP with 1:4 divider.
write32((6 << MSELECT_CLK_DIV_SHIFT) | MSELECT_CLK_SRC_PLLP_OUT0,
clk_rst_clk_src_mselect_ptr);
// Enable clock to MSELECT.
write32(CLK_ENB_MSELECT, clk_rst_clk_enb_v_set_ptr);
udelay(2);
// Bring MSELECT out of reset.
write32(SWR_MSELECT_RST, clk_rst_rst_dev_v_clr_ptr);
}
/* Resets. */
static void clear_cpu_resets(void)
{
// Take the non-cpu of the G and LP clusters out of reset.
write32(CLR_NONCPURESET, clk_rst_rst_cpulp_cmplx_clr_ptr);
write32(CLR_NONCPURESET, clk_rst_rst_cpug_cmplx_clr_ptr);
// Clear software controlled reset of the slow cluster.
write32(CLR_CPURESET0 | CLR_DBGRESET0 | CLR_CORERESET0 | CLR_CXRESET0,
clk_rst_rst_cpulp_cmplx_clr_ptr);
// Clear software controlled reset of the fast cluster.
write32(CLR_CPURESET0 | CLR_DBGRESET0 | CLR_CORERESET0 | CLR_CXRESET0 |
CLR_CPURESET1 | CLR_DBGRESET1 | CLR_CORERESET1 | CLR_CXRESET1 |
CLR_CPURESET2 | CLR_DBGRESET2 | CLR_CORERESET2 | CLR_CXRESET2 |
CLR_CPURESET3 | CLR_DBGRESET3 | CLR_CORERESET3 | CLR_CXRESET3,
clk_rst_rst_cpug_cmplx_clr_ptr);
}
/* Power. */
static void power_on_partition(unsigned id)
{
uint32_t bit = 0x1 << id;
if (!(read32(pmc_ctlr_pwrgate_status_ptr) & bit)) {
// Partition is not on. Turn it on.
write32(id | PWRGATE_TOGGLE_START, pmc_ctlr_pwrgate_toggle_ptr);
// Wait until the partition is powerd on.
while (!(read32(pmc_ctlr_pwrgate_status_ptr) & bit))
;
// Wait until clamp is off.
while (read32(pmc_ctlr_clamp_status_ptr) & bit)
;
}
}
static void power_on_main_cpu(void)
{
/*
* Reprogram PMC_CPUPWRGOOD_TIMER register:
*
* XXX This is a fragile assumption. XXX
* The kernel prepares PMC_CPUPWRGOOD_TIMER based on a 32768Hz clock.
* Note that PMC_CPUPWRGOOD_TIMER is running at pclk.
*
* We need to reprogram PMC_CPUPWRGOOD_TIMER based on the current pclk
* which is at 204Mhz (pclk = sclk = pllp_out2) after BootROM. Multiply
* PMC_CPUPWRGOOD_TIMER by 204M / 32K.
*
* Save the original PMC_CPUPWRGOOD_TIMER register which we need to
* restore after the CPU is powered up.
*/
uint32_t orig_timer = read32(pmc_ctlr_cpupwrgood_timer_ptr);
write32(orig_timer * (204000000 / 32768),
pmc_ctlr_cpupwrgood_timer_ptr);
if (wakeup_on_lp()) {
power_on_partition(PARTID_C1NC);
power_on_partition(PARTID_CELP);
} else {
power_on_partition(PARTID_CRAIL);
power_on_partition(PARTID_C0NC);
power_on_partition(PARTID_CE0);
}
// Restore the original PMC_CPUPWRGOOD_TIMER.
write32(orig_timer, pmc_ctlr_cpupwrgood_timer_ptr);
}
/* Entry point. */
void lp0_resume(void)
{
// If not on the AVP, reset.
if (read32(up_tag_ptr) != UP_TAG_AVP)
reset();
// Enable JTAG
enable_jtag();
config_oscillator();
// Tell the flow controller which cluster to wake up. The default is
// the fast cluster.
if (wakeup_on_lp())
setbits32(FLOW_CLUSTER_ACTIVE_LP,
flow_ctlr_cluster_control_ptr);
// Program SUPER_CCLK_DIVIDER.
write32(SUPER_CDIV_ENB, clk_rst_super_cclk_div_ptr);
config_core_sight();
config_pllu();
// Set the CPU reset vector.
write32(get_wakeup_vector(), evp_cpu_reset_ptr);
// Select CPU complex clock source.
write32(CCLK_PLLP_BURST_POLICY, clk_rst_cclk_burst_policy_ptr);
config_mselect();
// Disable PLLX since it isn't used as CPU clock source.
clrbits32(PLLX_ENABLE, clk_rst_pllx_base_ptr);
// Set CAR2PMC_CPU_ACK_WIDTH to 408.
uint32_t ack_width = read32(clk_rst_cpu_softrst_ctrl2_ptr);
ack_width &= ~CAR2PMC_CPU_ACK_WIDTH_MASK;
ack_width |= 408 << CAR2PMC_CPU_ACK_WIDTH_SHIFT;
write32(ack_width, clk_rst_cpu_softrst_ctrl2_ptr);
config_tsc();
// Disable VPR.
write32(0, mc_video_protect_size_mb_ptr);
write32(VIDEO_PROTECT_WRITE_ACCESS_DISABLE,
mc_video_protect_reg_ctrl_ptr);
enable_cpu_clocks();
power_on_main_cpu();
clear_cpu_resets();
// Halt the AVP.
while (1)
write32(FLOW_MODE_STOP | EVENT_JTAG,
flow_ctlr_halt_cop_events_ptr);
}
/* Header. */
extern uint8_t blob_data;
extern uint8_t blob_data_size;
extern uint8_t blob_total_size;
struct lp0_header {
uint32_t length_insecure; // Insecure total length.
uint32_t reserved[3];
uint8_t rsa_modulus[256]; // RSA key modulus.
uint8_t aes_signature[16]; // AES signature.
uint8_t rsa_signature[256]; // RSA-PSS signature.
uint8_t random_aes_block[16]; // Random data, may be zero.
uint32_t length_secure; // Secure total length.
uint32_t destination; // Where to load the blob in iRAM.
uint32_t entry_point; // Entry point for the blob.
uint32_t code_length; // Length of just the data.
} __attribute__((packed));
struct lp0_header header __attribute__((section(".header"))) =
{
.length_insecure = (uintptr_t)&blob_total_size,
.length_secure = (uintptr_t)&blob_total_size,
.destination = (uintptr_t)&blob_data,
.entry_point = (uintptr_t)&lp0_resume,
.code_length = (uintptr_t)&blob_data_size
};