diff options
author | Allen Martin <amartin@nvidia.com> | 2012-08-31 08:30:00 +0000 |
---|---|---|
committer | Albert ARIBAUD <albert.u.boot@aribaud.net> | 2012-09-01 14:58:21 +0200 |
commit | 00a2749d7be5b0e6cb6435187ec8fea600b44627 (patch) | |
tree | 3d3107a9b369a09ba5c40abff80d23788939b48d /arch/arm/cpu/armv7/tegra20 | |
parent | 0d04f34a357d004364b58159b64aad354e65137e (diff) | |
download | u-boot-imx-00a2749d7be5b0e6cb6435187ec8fea600b44627.zip u-boot-imx-00a2749d7be5b0e6cb6435187ec8fea600b44627.tar.gz u-boot-imx-00a2749d7be5b0e6cb6435187ec8fea600b44627.tar.bz2 |
tegra20: rename tegra2 -> tegra20
This is make naming consistent with the kernel and devicetree and in
preparation of pulling out the common tegra20 code.
Signed-off-by: Allen Martin <amartin@nvidia.com>
Acked-by: Stephen Warren <swarren@wwwdotorg.org>
Tested-by: Thierry Reding <thierry.reding@avionic-design.de>
Signed-off-by: Tom Warren <twarren@nvidia.com>
Diffstat (limited to 'arch/arm/cpu/armv7/tegra20')
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/Makefile | 60 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/ap20.c | 388 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/board.c | 167 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/clock.c | 1087 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/cmd_enterrcm.c | 65 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/config.mk | 38 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/crypto.c | 230 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/crypto.h | 36 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/emc.c | 286 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/funcmux.c | 249 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/lowlevel_init.S | 42 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/pinmux.c | 572 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/pmu.c | 70 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/sys_info.c | 35 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/timer.c | 111 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/usb.c | 460 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/warmboot.c | 386 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/warmboot_avp.c | 250 | ||||
-rw-r--r-- | arch/arm/cpu/armv7/tegra20/warmboot_avp.h | 81 |
19 files changed, 4613 insertions, 0 deletions
diff --git a/arch/arm/cpu/armv7/tegra20/Makefile b/arch/arm/cpu/armv7/tegra20/Makefile new file mode 100644 index 0000000..da62646 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/Makefile @@ -0,0 +1,60 @@ +# +# (C) Copyright 2010,2011 Nvidia Corporation. +# +# (C) Copyright 2000-2003 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. +# +# See file CREDITS for list of people who contributed to this +# project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + +# The AVP is ARMv4T architecture so we must use special compiler +# flags for any startup files it might use. +CFLAGS_arch/arm/cpu/armv7/tegra20/ap20.o += -march=armv4t +CFLAGS_arch/arm/cpu/armv7/tegra20/clock.o += -march=armv4t +CFLAGS_arch/arm/cpu/armv7/tegra20/warmboot_avp.o += -march=armv4t + +include $(TOPDIR)/config.mk + +LIB = $(obj)lib$(SOC).o + +SOBJS := lowlevel_init.o +COBJS-y := ap20.o board.o clock.o funcmux.o pinmux.o sys_info.o timer.o +COBJS-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o +COBJS-$(CONFIG_TEGRA_PMU) += pmu.o +COBJS-$(CONFIG_USB_EHCI_TEGRA) += usb.o +COBJS-$(CONFIG_TEGRA20_LP0) += crypto.o warmboot.o warmboot_avp.o +COBJS-$(CONFIG_CMD_ENTERRCM) += cmd_enterrcm.o + +COBJS := $(COBJS-y) +SRCS := $(SOBJS:.o=.S) $(COBJS:.o=.c) +OBJS := $(addprefix $(obj),$(COBJS) $(SOBJS)) + +all: $(obj).depend $(LIB) + +$(LIB): $(OBJS) + $(call cmd_link_o_target, $(OBJS)) + +######################################################################### + +# defines $(obj).depend target +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend + +######################################################################### diff --git a/arch/arm/cpu/armv7/tegra20/ap20.c b/arch/arm/cpu/armv7/tegra20/ap20.c new file mode 100644 index 0000000..8b6afbc --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/ap20.c @@ -0,0 +1,388 @@ +/* +* (C) Copyright 2010-2011 +* NVIDIA Corporation <www.nvidia.com> +* +* See file CREDITS for list of people who contributed to this +* project. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* 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. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, +* MA 02111-1307 USA +*/ + +#include <asm/io.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/ap20.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/fuse.h> +#include <asm/arch/gp_padctrl.h> +#include <asm/arch/pmc.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/scu.h> +#include <asm/arch/warmboot.h> +#include <common.h> + +int tegra_get_chip_type(void) +{ + struct apb_misc_gp_ctlr *gp; + struct fuse_regs *fuse = (struct fuse_regs *)TEGRA20_FUSE_BASE; + uint tegra_sku_id, rev; + + /* + * This is undocumented, Chip ID is bits 15:8 of the register + * APB_MISC + 0x804, and has value 0x20 for Tegra20, 0x30 for + * Tegra30 + */ + gp = (struct apb_misc_gp_ctlr *)TEGRA20_APB_MISC_GP_BASE; + rev = (readl(&gp->hidrev) & HIDREV_CHIPID_MASK) >> HIDREV_CHIPID_SHIFT; + + tegra_sku_id = readl(&fuse->sku_info) & 0xff; + + switch (rev) { + case CHIPID_TEGRA20: + switch (tegra_sku_id) { + case SKU_ID_T20: + return TEGRA_SOC_T20; + case SKU_ID_T25SE: + case SKU_ID_AP25: + case SKU_ID_T25: + case SKU_ID_AP25E: + case SKU_ID_T25E: + return TEGRA_SOC_T25; + } + break; + } + /* unknown sku id */ + return TEGRA_SOC_UNKNOWN; +} + +/* Returns 1 if the current CPU executing is a Cortex-A9, else 0 */ +static int ap20_cpu_is_cortexa9(void) +{ + u32 id = readb(NV_PA_PG_UP_BASE + PG_UP_TAG_0); + return id == (PG_UP_TAG_0_PID_CPU & 0xff); +} + +void init_pllx(void) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + struct clk_pll_simple *pll = + &clkrst->crc_pll_simple[CLOCK_ID_XCPU - CLOCK_ID_FIRST_SIMPLE]; + u32 reg; + + /* If PLLX is already enabled, just return */ + if (readl(&pll->pll_base) & PLL_ENABLE_MASK) + return; + + /* Set PLLX_MISC */ + writel(1 << PLL_CPCON_SHIFT, &pll->pll_misc); + + /* Use 12MHz clock here */ + reg = PLL_BYPASS_MASK | (12 << PLL_DIVM_SHIFT); + reg |= 1000 << PLL_DIVN_SHIFT; + writel(reg, &pll->pll_base); + + reg |= PLL_ENABLE_MASK; + writel(reg, &pll->pll_base); + + reg &= ~PLL_BYPASS_MASK; + writel(reg, &pll->pll_base); +} + +static void enable_cpu_clock(int enable) +{ + struct clk_rst_ctlr *clkrst = (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 clk; + + /* + * NOTE: + * Regardless of whether the request is to enable or disable the CPU + * clock, every processor in the CPU complex except the master (CPU 0) + * will have it's clock stopped because the AVP only talks to the + * master. The AVP does not know (nor does it need to know) that there + * are multiple processors in the CPU complex. + */ + + if (enable) { + /* Initialize PLLX */ + init_pllx(); + + /* Wait until all clocks are stable */ + udelay(PLL_STABILIZATION_DELAY); + + writel(CCLK_BURST_POLICY, &clkrst->crc_cclk_brst_pol); + writel(SUPER_CCLK_DIVIDER, &clkrst->crc_super_cclk_div); + } + + /* + * Read the register containing the individual CPU clock enables and + * always stop the clock to CPU 1. + */ + clk = readl(&clkrst->crc_clk_cpu_cmplx); + clk |= 1 << CPU1_CLK_STP_SHIFT; + + /* Stop/Unstop the CPU clock */ + clk &= ~CPU0_CLK_STP_MASK; + clk |= !enable << CPU0_CLK_STP_SHIFT; + writel(clk, &clkrst->crc_clk_cpu_cmplx); + + clock_enable(PERIPH_ID_CPU); +} + +static int is_cpu_powered(void) +{ + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + + return (readl(&pmc->pmc_pwrgate_status) & CPU_PWRED) ? 1 : 0; +} + +static void remove_cpu_io_clamps(void) +{ + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + u32 reg; + + /* Remove the clamps on the CPU I/O signals */ + reg = readl(&pmc->pmc_remove_clamping); + reg |= CPU_CLMP; + writel(reg, &pmc->pmc_remove_clamping); + + /* Give I/O signals time to stabilize */ + udelay(IO_STABILIZATION_DELAY); +} + +static void powerup_cpu(void) +{ + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + u32 reg; + int timeout = IO_STABILIZATION_DELAY; + + if (!is_cpu_powered()) { + /* Toggle the CPU power state (OFF -> ON) */ + reg = readl(&pmc->pmc_pwrgate_toggle); + reg &= PARTID_CP; + reg |= START_CP; + writel(reg, &pmc->pmc_pwrgate_toggle); + + /* Wait for the power to come up */ + while (!is_cpu_powered()) { + if (timeout-- == 0) + printf("CPU failed to power up!\n"); + else + udelay(10); + } + + /* + * Remove the I/O clamps from CPU power partition. + * Recommended only on a Warm boot, if the CPU partition gets + * power gated. Shouldn't cause any harm when called after a + * cold boot according to HW, probably just redundant. + */ + remove_cpu_io_clamps(); + } +} + +static void enable_cpu_power_rail(void) +{ + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + u32 reg; + + reg = readl(&pmc->pmc_cntrl); + reg |= CPUPWRREQ_OE; + writel(reg, &pmc->pmc_cntrl); + + /* + * The TI PMU65861C needs a 3.75ms delay between enabling + * the power rail and enabling the CPU clock. This delay + * between SM1EN and SM1 is for switching time + the ramp + * up of the voltage to the CPU (VDD_CPU from PMU). + */ + udelay(3750); +} + +static void reset_A9_cpu(int reset) +{ + /* + * NOTE: Regardless of whether the request is to hold the CPU in reset + * or take it out of reset, every processor in the CPU complex + * except the master (CPU 0) will be held in reset because the + * AVP only talks to the master. The AVP does not know that there + * are multiple processors in the CPU complex. + */ + + /* Hold CPU 1 in reset, and CPU 0 if asked */ + reset_cmplx_set_enable(1, crc_rst_cpu | crc_rst_de | crc_rst_debug, 1); + reset_cmplx_set_enable(0, crc_rst_cpu | crc_rst_de | crc_rst_debug, + reset); + + /* Enable/Disable master CPU reset */ + reset_set_enable(PERIPH_ID_CPU, reset); +} + +static void clock_enable_coresight(int enable) +{ + u32 rst, src; + + clock_set_enable(PERIPH_ID_CORESIGHT, enable); + reset_set_enable(PERIPH_ID_CORESIGHT, !enable); + + if (enable) { + /* + * Put CoreSight on PLLP_OUT0 (216 MHz) and divide it down by + * 1.5, giving an effective frequency of 144MHz. + * Set PLLP_OUT0 [bits31:30 = 00], and use a 7.1 divisor + * (bits 7:0), so 00000001b == 1.5 (n+1 + .5) + */ + src = CLK_DIVIDER(NVBL_PLLP_KHZ, 144000); + clock_ll_set_source_divisor(PERIPH_ID_CSI, 0, src); + + /* Unlock the CPU CoreSight interfaces */ + rst = 0xC5ACCE55; + writel(rst, CSITE_CPU_DBG0_LAR); + writel(rst, CSITE_CPU_DBG1_LAR); + } +} + +void start_cpu(u32 reset_vector) +{ + /* Enable VDD_CPU */ + enable_cpu_power_rail(); + + /* Hold the CPUs in reset */ + reset_A9_cpu(1); + + /* Disable the CPU clock */ + enable_cpu_clock(0); + + /* Enable CoreSight */ + clock_enable_coresight(1); + + /* + * Set the entry point for CPU execution from reset, + * if it's a non-zero value. + */ + if (reset_vector) + writel(reset_vector, EXCEP_VECTOR_CPU_RESET_VECTOR); + + /* Enable the CPU clock */ + enable_cpu_clock(1); + + /* If the CPU doesn't already have power, power it up */ + powerup_cpu(); + + /* Take the CPU out of reset */ + reset_A9_cpu(0); +} + + +void halt_avp(void) +{ + for (;;) { + writel((HALT_COP_EVENT_JTAG | HALT_COP_EVENT_IRQ_1 \ + | HALT_COP_EVENT_FIQ_1 | (FLOW_MODE_STOP<<29)), + FLOW_CTLR_HALT_COP_EVENTS); + } +} + +void enable_scu(void) +{ + struct scu_ctlr *scu = (struct scu_ctlr *)NV_PA_ARM_PERIPHBASE; + u32 reg; + + /* If SCU already setup/enabled, return */ + if (readl(&scu->scu_ctrl) & SCU_CTRL_ENABLE) + return; + + /* Invalidate all ways for all processors */ + writel(0xFFFF, &scu->scu_inv_all); + + /* Enable SCU - bit 0 */ + reg = readl(&scu->scu_ctrl); + reg |= SCU_CTRL_ENABLE; + writel(reg, &scu->scu_ctrl); +} + +static u32 get_odmdata(void) +{ + /* + * ODMDATA is stored in the BCT in IRAM by the BootROM. + * The BCT start and size are stored in the BIT in IRAM. + * Read the data @ bct_start + (bct_size - 12). This works + * on T20 and T30 BCTs, which are locked down. If this changes + * in new chips (T114, etc.), we can revisit this algorithm. + */ + + u32 bct_start, odmdata; + + bct_start = readl(AP20_BASE_PA_SRAM + NVBOOTINFOTABLE_BCTPTR); + odmdata = readl(bct_start + BCT_ODMDATA_OFFSET); + + return odmdata; +} + +void init_pmc_scratch(void) +{ + struct pmc_ctlr *const pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + u32 odmdata; + int i; + + /* SCRATCH0 is initialized by the boot ROM and shouldn't be cleared */ + for (i = 0; i < 23; i++) + writel(0, &pmc->pmc_scratch1+i); + + /* ODMDATA is for kernel use to determine RAM size, LP config, etc. */ + odmdata = get_odmdata(); + writel(odmdata, &pmc->pmc_scratch20); + +#ifdef CONFIG_TEGRA20_LP0 + /* save Sdram params to PMC 2, 4, and 24 for WB0 */ + warmboot_save_sdram_params(); +#endif +} + +void tegra20_start(void) +{ + struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + + /* If we are the AVP, start up the first Cortex-A9 */ + if (!ap20_cpu_is_cortexa9()) { + /* enable JTAG */ + writel(0xC0, &pmt->pmt_cfg_ctl); + + /* + * If we are ARM7 - give it a different stack. We are about to + * start up the A9 which will want to use this one. + */ + asm volatile("mov sp, %0\n" + : : "r"(AVP_EARLY_BOOT_STACK_LIMIT)); + + start_cpu((u32)_start); + halt_avp(); + /* not reached */ + } + + /* Init PMC scratch memory */ + init_pmc_scratch(); + + enable_scu(); + + /* enable SMP mode and FW for CPU0, by writing to Auxiliary Ctl reg */ + asm volatile( + "mrc p15, 0, r0, c1, c0, 1\n" + "orr r0, r0, #0x41\n" + "mcr p15, 0, r0, c1, c0, 1\n"); + + /* FIXME: should have ap20's L2 disabled too? */ +} diff --git a/arch/arm/cpu/armv7/tegra20/board.c b/arch/arm/cpu/armv7/tegra20/board.c new file mode 100644 index 0000000..e595ff9 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/board.c @@ -0,0 +1,167 @@ +/* + * (C) Copyright 2010,2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/arch/ap20.h> +#include <asm/arch/clock.h> +#include <asm/arch/funcmux.h> +#include <asm/arch/pmc.h> +#include <asm/arch/sys_proto.h> +#include <asm/arch/tegra20.h> + +DECLARE_GLOBAL_DATA_PTR; + +enum { + /* UARTs which we can enable */ + UARTA = 1 << 0, + UARTB = 1 << 1, + UARTD = 1 << 3, + UART_COUNT = 4, +}; + +/* + * Boot ROM initializes the odmdata in APBDEV_PMC_SCRATCH20_0, + * so we are using this value to identify memory size. + */ + +unsigned int query_sdram_size(void) +{ + struct pmc_ctlr *const pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + u32 reg; + + reg = readl(&pmc->pmc_scratch20); + debug("pmc->pmc_scratch20 (ODMData) = 0x%08x\n", reg); + + /* bits 31:28 in OdmData are used for RAM size */ + switch ((reg) >> 28) { + case 1: + return 0x10000000; /* 256 MB */ + case 2: + default: + return 0x20000000; /* 512 MB */ + case 3: + return 0x40000000; /* 1GB */ + } +} + +int dram_init(void) +{ + /* We do not initialise DRAM here. We just query the size */ + gd->ram_size = query_sdram_size(); + return 0; +} + +#ifdef CONFIG_DISPLAY_BOARDINFO +int checkboard(void) +{ + printf("Board: %s\n", sysinfo.board_string); + return 0; +} +#endif /* CONFIG_DISPLAY_BOARDINFO */ + +#ifdef CONFIG_ARCH_CPU_INIT +/* + * Note this function is executed by the ARM7TDMI AVP. It does not return + * in this case. It is also called once the A9 starts up, but does nothing in + * that case. + */ +int arch_cpu_init(void) +{ + /* Fire up the Cortex A9 */ + tegra20_start(); + + /* We didn't do this init in start.S, so do it now */ + cpu_init_cp15(); + + /* Initialize essential common plls */ + clock_early_init(); + + return 0; +} +#endif + +static int uart_configs[] = { +#if defined(CONFIG_TEGRA20_UARTA_UAA_UAB) + FUNCMUX_UART1_UAA_UAB, +#elif defined(CONFIG_TEGRA20_UARTA_GPU) + FUNCMUX_UART1_GPU, +#elif defined(CONFIG_TEGRA20_UARTA_SDIO1) + FUNCMUX_UART1_SDIO1, +#else + FUNCMUX_UART1_IRRX_IRTX, +#endif + FUNCMUX_UART2_IRDA, + -1, + FUNCMUX_UART4_GMC, + -1, +}; + +/** + * Set up the specified uarts + * + * @param uarts_ids Mask containing UARTs to init (UARTx) + */ +static void setup_uarts(int uart_ids) +{ + static enum periph_id id_for_uart[] = { + PERIPH_ID_UART1, + PERIPH_ID_UART2, + PERIPH_ID_UART3, + PERIPH_ID_UART4, + }; + size_t i; + + for (i = 0; i < UART_COUNT; i++) { + if (uart_ids & (1 << i)) { + enum periph_id id = id_for_uart[i]; + + funcmux_select(id, uart_configs[i]); + clock_ll_start_uart(id); + } + } +} + +void board_init_uart_f(void) +{ + int uart_ids = 0; /* bit mask of which UART ids to enable */ + +#ifdef CONFIG_TEGRA20_ENABLE_UARTA + uart_ids |= UARTA; +#endif +#ifdef CONFIG_TEGRA20_ENABLE_UARTB + uart_ids |= UARTB; +#endif +#ifdef CONFIG_TEGRA20_ENABLE_UARTD + uart_ids |= UARTD; +#endif + setup_uarts(uart_ids); +} + +#ifndef CONFIG_SYS_DCACHE_OFF +void enable_caches(void) +{ + /* Enable D-cache. I-cache is already enabled in start.S */ + dcache_enable(); +} +#endif diff --git a/arch/arm/cpu/armv7/tegra20/clock.c b/arch/arm/cpu/armv7/tegra20/clock.c new file mode 100644 index 0000000..2403874 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/clock.c @@ -0,0 +1,1087 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* Tegra20 Clock control functions */ + +#include <asm/io.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/timer.h> +#include <asm/arch/tegra20.h> +#include <common.h> +#include <div64.h> +#include <fdtdec.h> + +/* + * This is our record of the current clock rate of each clock. We don't + * fill all of these in since we are only really interested in clocks which + * we use as parents. + */ +static unsigned pll_rate[CLOCK_ID_COUNT]; + +/* + * The oscillator frequency is fixed to one of four set values. Based on this + * the other clocks are set up appropriately. + */ +static unsigned osc_freq[CLOCK_OSC_FREQ_COUNT] = { + 13000000, + 19200000, + 12000000, + 26000000, +}; + +/* + * Clock types that we can use as a source. The Tegra20 has muxes for the + * peripheral clocks, and in most cases there are four options for the clock + * source. This gives us a clock 'type' and exploits what commonality exists + * in the device. + * + * Letters are obvious, except for T which means CLK_M, and S which means the + * clock derived from 32KHz. Beware that CLK_M (also called OSC in the + * datasheet) and PLL_M are different things. The former is the basic + * clock supplied to the SOC from an external oscillator. The latter is the + * memory clock PLL. + * + * See definitions in clock_id in the header file. + */ +enum clock_type_id { + CLOCK_TYPE_AXPT, /* PLL_A, PLL_X, PLL_P, CLK_M */ + CLOCK_TYPE_MCPA, /* and so on */ + CLOCK_TYPE_MCPT, + CLOCK_TYPE_PCM, + CLOCK_TYPE_PCMT, + CLOCK_TYPE_PCMT16, /* CLOCK_TYPE_PCMT with 16-bit divider */ + CLOCK_TYPE_PCXTS, + CLOCK_TYPE_PDCT, + + CLOCK_TYPE_COUNT, + CLOCK_TYPE_NONE = -1, /* invalid clock type */ +}; + +/* return 1 if a peripheral ID is in range */ +#define clock_type_id_isvalid(id) ((id) >= 0 && \ + (id) < CLOCK_TYPE_COUNT) + +char pllp_valid = 1; /* PLLP is set up correctly */ + +enum { + CLOCK_MAX_MUX = 4 /* number of source options for each clock */ +}; + +/* + * Clock source mux for each clock type. This just converts our enum into + * a list of mux sources for use by the code. Note that CLOCK_TYPE_PCXTS + * is special as it has 5 sources. Since it also has a different number of + * bits in its register for the source, we just handle it with a special + * case in the code. + */ +#define CLK(x) CLOCK_ID_ ## x +static enum clock_id clock_source[CLOCK_TYPE_COUNT][CLOCK_MAX_MUX] = { + { CLK(AUDIO), CLK(XCPU), CLK(PERIPH), CLK(OSC) }, + { CLK(MEMORY), CLK(CGENERAL), CLK(PERIPH), CLK(AUDIO) }, + { CLK(MEMORY), CLK(CGENERAL), CLK(PERIPH), CLK(OSC) }, + { CLK(PERIPH), CLK(CGENERAL), CLK(MEMORY), CLK(NONE) }, + { CLK(PERIPH), CLK(CGENERAL), CLK(MEMORY), CLK(OSC) }, + { CLK(PERIPH), CLK(CGENERAL), CLK(MEMORY), CLK(OSC) }, + { CLK(PERIPH), CLK(CGENERAL), CLK(XCPU), CLK(OSC) }, + { CLK(PERIPH), CLK(DISPLAY), CLK(CGENERAL), CLK(OSC) }, +}; + +/* + * Clock peripheral IDs which sadly don't match up with PERIPH_ID. This is + * not in the header file since it is for purely internal use - we want + * callers to use the PERIPH_ID for all access to peripheral clocks to avoid + * confusion bewteen PERIPH_ID_... and PERIPHC_... + * + * We don't call this CLOCK_PERIPH_ID or PERIPH_CLOCK_ID as it would just be + * confusing. + * + * Note to SOC vendors: perhaps define a unified numbering for peripherals and + * use it for reset, clock enable, clock source/divider and even pinmuxing + * if you can. + */ +enum periphc_internal_id { + /* 0x00 */ + PERIPHC_I2S1, + PERIPHC_I2S2, + PERIPHC_SPDIF_OUT, + PERIPHC_SPDIF_IN, + PERIPHC_PWM, + PERIPHC_SPI1, + PERIPHC_SPI2, + PERIPHC_SPI3, + + /* 0x08 */ + PERIPHC_XIO, + PERIPHC_I2C1, + PERIPHC_DVC_I2C, + PERIPHC_TWC, + PERIPHC_0c, + PERIPHC_10, /* PERIPHC_SPI1, what is this really? */ + PERIPHC_DISP1, + PERIPHC_DISP2, + + /* 0x10 */ + PERIPHC_CVE, + PERIPHC_IDE0, + PERIPHC_VI, + PERIPHC_1c, + PERIPHC_SDMMC1, + PERIPHC_SDMMC2, + PERIPHC_G3D, + PERIPHC_G2D, + + /* 0x18 */ + PERIPHC_NDFLASH, + PERIPHC_SDMMC4, + PERIPHC_VFIR, + PERIPHC_EPP, + PERIPHC_MPE, + PERIPHC_MIPI, + PERIPHC_UART1, + PERIPHC_UART2, + + /* 0x20 */ + PERIPHC_HOST1X, + PERIPHC_21, + PERIPHC_TVO, + PERIPHC_HDMI, + PERIPHC_24, + PERIPHC_TVDAC, + PERIPHC_I2C2, + PERIPHC_EMC, + + /* 0x28 */ + PERIPHC_UART3, + PERIPHC_29, + PERIPHC_VI_SENSOR, + PERIPHC_2b, + PERIPHC_2c, + PERIPHC_SPI4, + PERIPHC_I2C3, + PERIPHC_SDMMC3, + + /* 0x30 */ + PERIPHC_UART4, + PERIPHC_UART5, + PERIPHC_VDE, + PERIPHC_OWR, + PERIPHC_NOR, + PERIPHC_CSITE, + + PERIPHC_COUNT, + + PERIPHC_NONE = -1, +}; + +/* return 1 if a periphc_internal_id is in range */ +#define periphc_internal_id_isvalid(id) ((id) >= 0 && \ + (id) < PERIPHC_COUNT) + +/* + * Clock type for each peripheral clock source. We put the name in each + * record just so it is easy to match things up + */ +#define TYPE(name, type) type +static enum clock_type_id clock_periph_type[PERIPHC_COUNT] = { + /* 0x00 */ + TYPE(PERIPHC_I2S1, CLOCK_TYPE_AXPT), + TYPE(PERIPHC_I2S2, CLOCK_TYPE_AXPT), + TYPE(PERIPHC_SPDIF_OUT, CLOCK_TYPE_AXPT), + TYPE(PERIPHC_SPDIF_IN, CLOCK_TYPE_PCM), + TYPE(PERIPHC_PWM, CLOCK_TYPE_PCXTS), + TYPE(PERIPHC_SPI1, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_SPI22, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_SPI3, CLOCK_TYPE_PCMT), + + /* 0x08 */ + TYPE(PERIPHC_XIO, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_I2C1, CLOCK_TYPE_PCMT16), + TYPE(PERIPHC_DVC_I2C, CLOCK_TYPE_PCMT16), + TYPE(PERIPHC_TWC, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_SPI1, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_DISP1, CLOCK_TYPE_PDCT), + TYPE(PERIPHC_DISP2, CLOCK_TYPE_PDCT), + + /* 0x10 */ + TYPE(PERIPHC_CVE, CLOCK_TYPE_PDCT), + TYPE(PERIPHC_IDE0, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_VI, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_SDMMC1, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_SDMMC2, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_G3D, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_G2D, CLOCK_TYPE_MCPA), + + /* 0x18 */ + TYPE(PERIPHC_NDFLASH, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_SDMMC4, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_VFIR, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_EPP, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_MPE, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_MIPI, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_UART1, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_UART2, CLOCK_TYPE_PCMT), + + /* 0x20 */ + TYPE(PERIPHC_HOST1X, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_TVO, CLOCK_TYPE_PDCT), + TYPE(PERIPHC_HDMI, CLOCK_TYPE_PDCT), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_TVDAC, CLOCK_TYPE_PDCT), + TYPE(PERIPHC_I2C2, CLOCK_TYPE_PCMT16), + TYPE(PERIPHC_EMC, CLOCK_TYPE_MCPT), + + /* 0x28 */ + TYPE(PERIPHC_UART3, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_VI, CLOCK_TYPE_MCPA), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_NONE, CLOCK_TYPE_NONE), + TYPE(PERIPHC_SPI4, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_I2C3, CLOCK_TYPE_PCMT16), + TYPE(PERIPHC_SDMMC3, CLOCK_TYPE_PCMT), + + /* 0x30 */ + TYPE(PERIPHC_UART4, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_UART5, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_VDE, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_OWR, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_NOR, CLOCK_TYPE_PCMT), + TYPE(PERIPHC_CSITE, CLOCK_TYPE_PCMT), +}; + +/* + * This array translates a periph_id to a periphc_internal_id + * + * Not present/matched up: + * uint vi_sensor; _VI_SENSOR_0, 0x1A8 + * SPDIF - which is both 0x08 and 0x0c + * + */ +#define NONE(name) (-1) +#define OFFSET(name, value) PERIPHC_ ## name +static s8 periph_id_to_internal_id[PERIPH_ID_COUNT] = { + /* Low word: 31:0 */ + NONE(CPU), + NONE(RESERVED1), + NONE(RESERVED2), + NONE(AC97), + NONE(RTC), + NONE(TMR), + PERIPHC_UART1, + PERIPHC_UART2, /* and vfir 0x68 */ + + /* 0x08 */ + NONE(GPIO), + PERIPHC_SDMMC2, + NONE(SPDIF), /* 0x08 and 0x0c, unclear which to use */ + PERIPHC_I2S1, + PERIPHC_I2C1, + PERIPHC_NDFLASH, + PERIPHC_SDMMC1, + PERIPHC_SDMMC4, + + /* 0x10 */ + PERIPHC_TWC, + PERIPHC_PWM, + PERIPHC_I2S2, + PERIPHC_EPP, + PERIPHC_VI, + PERIPHC_G2D, + NONE(USBD), + NONE(ISP), + + /* 0x18 */ + PERIPHC_G3D, + PERIPHC_IDE0, + PERIPHC_DISP2, + PERIPHC_DISP1, + PERIPHC_HOST1X, + NONE(VCP), + NONE(RESERVED30), + NONE(CACHE2), + + /* Middle word: 63:32 */ + NONE(MEM), + NONE(AHBDMA), + NONE(APBDMA), + NONE(RESERVED35), + NONE(KBC), + NONE(STAT_MON), + NONE(PMC), + NONE(FUSE), + + /* 0x28 */ + NONE(KFUSE), + NONE(SBC1), /* SBC1, 0x34, is this SPI1? */ + PERIPHC_NOR, + PERIPHC_SPI1, + PERIPHC_SPI2, + PERIPHC_XIO, + PERIPHC_SPI3, + PERIPHC_DVC_I2C, + + /* 0x30 */ + NONE(DSI), + PERIPHC_TVO, /* also CVE 0x40 */ + PERIPHC_MIPI, + PERIPHC_HDMI, + PERIPHC_CSITE, + PERIPHC_TVDAC, + PERIPHC_I2C2, + PERIPHC_UART3, + + /* 0x38 */ + NONE(RESERVED56), + PERIPHC_EMC, + NONE(USB2), + NONE(USB3), + PERIPHC_MPE, + PERIPHC_VDE, + NONE(BSEA), + NONE(BSEV), + + /* Upper word 95:64 */ + NONE(SPEEDO), + PERIPHC_UART4, + PERIPHC_UART5, + PERIPHC_I2C3, + PERIPHC_SPI4, + PERIPHC_SDMMC3, + NONE(PCIE), + PERIPHC_OWR, + + /* 0x48 */ + NONE(AFI), + NONE(CORESIGHT), + NONE(RESERVED74), + NONE(AVPUCQ), + NONE(RESERVED76), + NONE(RESERVED77), + NONE(RESERVED78), + NONE(RESERVED79), + + /* 0x50 */ + NONE(RESERVED80), + NONE(RESERVED81), + NONE(RESERVED82), + NONE(RESERVED83), + NONE(IRAMA), + NONE(IRAMB), + NONE(IRAMC), + NONE(IRAMD), + + /* 0x58 */ + NONE(CRAM2), +}; + +/* + * Get the oscillator frequency, from the corresponding hardware configuration + * field. + */ +enum clock_osc_freq clock_get_osc_freq(void) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 reg; + + reg = readl(&clkrst->crc_osc_ctrl); + return (reg & OSC_FREQ_MASK) >> OSC_FREQ_SHIFT; +} + +int clock_get_osc_bypass(void) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 reg; + + reg = readl(&clkrst->crc_osc_ctrl); + return (reg & OSC_XOBP_MASK) >> OSC_XOBP_SHIFT; +} + +/* Returns a pointer to the registers of the given pll */ +static struct clk_pll *get_pll(enum clock_id clkid) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + + assert(clock_id_is_pll(clkid)); + return &clkrst->crc_pll[clkid]; +} + +int clock_ll_read_pll(enum clock_id clkid, u32 *divm, u32 *divn, + u32 *divp, u32 *cpcon, u32 *lfcon) +{ + struct clk_pll *pll = get_pll(clkid); + u32 data; + + assert(clkid != CLOCK_ID_USB); + + /* Safety check, adds to code size but is small */ + if (!clock_id_is_pll(clkid) || clkid == CLOCK_ID_USB) + return -1; + data = readl(&pll->pll_base); + *divm = (data & PLL_DIVM_MASK) >> PLL_DIVM_SHIFT; + *divn = (data & PLL_DIVN_MASK) >> PLL_DIVN_SHIFT; + *divp = (data & PLL_DIVP_MASK) >> PLL_DIVP_SHIFT; + data = readl(&pll->pll_misc); + *cpcon = (data & PLL_CPCON_MASK) >> PLL_CPCON_SHIFT; + *lfcon = (data & PLL_LFCON_MASK) >> PLL_LFCON_SHIFT; + + return 0; +} + +unsigned long clock_start_pll(enum clock_id clkid, u32 divm, u32 divn, + u32 divp, u32 cpcon, u32 lfcon) +{ + struct clk_pll *pll = get_pll(clkid); + u32 data; + + /* + * We cheat by treating all PLL (except PLLU) in the same fashion. + * This works only because: + * - same fields are always mapped at same offsets, except DCCON + * - DCCON is always 0, doesn't conflict + * - M,N, P of PLLP values are ignored for PLLP + */ + data = (cpcon << PLL_CPCON_SHIFT) | (lfcon << PLL_LFCON_SHIFT); + writel(data, &pll->pll_misc); + + data = (divm << PLL_DIVM_SHIFT) | (divn << PLL_DIVN_SHIFT) | + (0 << PLL_BYPASS_SHIFT) | (1 << PLL_ENABLE_SHIFT); + + if (clkid == CLOCK_ID_USB) + data |= divp << PLLU_VCO_FREQ_SHIFT; + else + data |= divp << PLL_DIVP_SHIFT; + writel(data, &pll->pll_base); + + /* calculate the stable time */ + return timer_get_us() + CLOCK_PLL_STABLE_DELAY_US; +} + +/* return 1 if a peripheral ID is in range and valid */ +static int clock_periph_id_isvalid(enum periph_id id) +{ + if (id < PERIPH_ID_FIRST || id >= PERIPH_ID_COUNT) + printf("Peripheral id %d out of range\n", id); + else { + switch (id) { + case PERIPH_ID_RESERVED1: + case PERIPH_ID_RESERVED2: + case PERIPH_ID_RESERVED30: + case PERIPH_ID_RESERVED35: + case PERIPH_ID_RESERVED56: + case PERIPH_ID_RESERVED74: + case PERIPH_ID_RESERVED76: + case PERIPH_ID_RESERVED77: + case PERIPH_ID_RESERVED78: + case PERIPH_ID_RESERVED79: + case PERIPH_ID_RESERVED80: + case PERIPH_ID_RESERVED81: + case PERIPH_ID_RESERVED82: + case PERIPH_ID_RESERVED83: + printf("Peripheral id %d is reserved\n", id); + break; + default: + return 1; + } + } + return 0; +} + +/* Returns a pointer to the clock source register for a peripheral */ +static u32 *get_periph_source_reg(enum periph_id periph_id) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + enum periphc_internal_id internal_id; + + assert(clock_periph_id_isvalid(periph_id)); + internal_id = periph_id_to_internal_id[periph_id]; + assert(internal_id != -1); + return &clkrst->crc_clk_src[internal_id]; +} + +void clock_ll_set_source_divisor(enum periph_id periph_id, unsigned source, + unsigned divisor) +{ + u32 *reg = get_periph_source_reg(periph_id); + u32 value; + + value = readl(reg); + + value &= ~OUT_CLK_SOURCE_MASK; + value |= source << OUT_CLK_SOURCE_SHIFT; + + value &= ~OUT_CLK_DIVISOR_MASK; + value |= divisor << OUT_CLK_DIVISOR_SHIFT; + + writel(value, reg); +} + +void clock_ll_set_source(enum periph_id periph_id, unsigned source) +{ + u32 *reg = get_periph_source_reg(periph_id); + + clrsetbits_le32(reg, OUT_CLK_SOURCE_MASK, + source << OUT_CLK_SOURCE_SHIFT); +} + +/** + * Given the parent's rate and the required rate for the children, this works + * out the peripheral clock divider to use, in 7.1 binary format. + * + * @param divider_bits number of divider bits (8 or 16) + * @param parent_rate clock rate of parent clock in Hz + * @param rate required clock rate for this clock + * @return divider which should be used + */ +static int clk_get_divider(unsigned divider_bits, unsigned long parent_rate, + unsigned long rate) +{ + u64 divider = parent_rate * 2; + unsigned max_divider = 1 << divider_bits; + + divider += rate - 1; + do_div(divider, rate); + + if ((s64)divider - 2 < 0) + return 0; + + if ((s64)divider - 2 >= max_divider) + return -1; + + return divider - 2; +} + +/** + * Given the parent's rate and the divider in 7.1 format, this works out the + * resulting peripheral clock rate. + * + * @param parent_rate clock rate of parent clock in Hz + * @param divider which should be used in 7.1 format + * @return effective clock rate of peripheral + */ +static unsigned long get_rate_from_divider(unsigned long parent_rate, + int divider) +{ + u64 rate; + + rate = (u64)parent_rate * 2; + do_div(rate, divider + 2); + return rate; +} + +unsigned long clock_get_periph_rate(enum periph_id periph_id, + enum clock_id parent) +{ + u32 *reg = get_periph_source_reg(periph_id); + + return get_rate_from_divider(pll_rate[parent], + (readl(reg) & OUT_CLK_DIVISOR_MASK) >> OUT_CLK_DIVISOR_SHIFT); +} + +/** + * Find the best available 7.1 format divisor given a parent clock rate and + * required child clock rate. This function assumes that a second-stage + * divisor is available which can divide by powers of 2 from 1 to 256. + * + * @param divider_bits number of divider bits (8 or 16) + * @param parent_rate clock rate of parent clock in Hz + * @param rate required clock rate for this clock + * @param extra_div value for the second-stage divisor (not set if this + * function returns -1. + * @return divider which should be used, or -1 if nothing is valid + * + */ +static int find_best_divider(unsigned divider_bits, unsigned long parent_rate, + unsigned long rate, int *extra_div) +{ + int shift; + int best_divider = -1; + int best_error = rate; + + /* try dividers from 1 to 256 and find closest match */ + for (shift = 0; shift <= 8 && best_error > 0; shift++) { + unsigned divided_parent = parent_rate >> shift; + int divider = clk_get_divider(divider_bits, divided_parent, + rate); + unsigned effective_rate = get_rate_from_divider(divided_parent, + divider); + int error = rate - effective_rate; + + /* Given a valid divider, look for the lowest error */ + if (divider != -1 && error < best_error) { + best_error = error; + *extra_div = 1 << shift; + best_divider = divider; + } + } + + /* return what we found - *extra_div will already be set */ + return best_divider; +} + +/** + * Given a peripheral ID and the required source clock, this returns which + * value should be programmed into the source mux for that peripheral. + * + * There is special code here to handle the one source type with 5 sources. + * + * @param periph_id peripheral to start + * @param source PLL id of required parent clock + * @param mux_bits Set to number of bits in mux register: 2 or 4 + * @param divider_bits Set to number of divider bits (8 or 16) + * @return mux value (0-4, or -1 if not found) + */ +static int get_periph_clock_source(enum periph_id periph_id, + enum clock_id parent, int *mux_bits, int *divider_bits) +{ + enum clock_type_id type; + enum periphc_internal_id internal_id; + int mux; + + assert(clock_periph_id_isvalid(periph_id)); + + internal_id = periph_id_to_internal_id[periph_id]; + assert(periphc_internal_id_isvalid(internal_id)); + + type = clock_periph_type[internal_id]; + assert(clock_type_id_isvalid(type)); + + /* + * Special cases here for the clock with a 4-bit source mux and I2C + * with its 16-bit divisor + */ + if (type == CLOCK_TYPE_PCXTS) + *mux_bits = 4; + else + *mux_bits = 2; + if (type == CLOCK_TYPE_PCMT16) + *divider_bits = 16; + else + *divider_bits = 8; + + for (mux = 0; mux < CLOCK_MAX_MUX; mux++) + if (clock_source[type][mux] == parent) + return mux; + + /* + * Not found: it might be looking for the 'S' in CLOCK_TYPE_PCXTS + * which is not in our table. If not, then they are asking for a + * source which this peripheral can't access through its mux. + */ + assert(type == CLOCK_TYPE_PCXTS); + assert(parent == CLOCK_ID_SFROM32KHZ); + if (type == CLOCK_TYPE_PCXTS && parent == CLOCK_ID_SFROM32KHZ) + return 4; /* mux value for this clock */ + + /* if we get here, either us or the caller has made a mistake */ + printf("Caller requested bad clock: periph=%d, parent=%d\n", periph_id, + parent); + return -1; +} + +/** + * Adjust peripheral PLL to use the given divider and source. + * + * @param periph_id peripheral to adjust + * @param source Source number (0-3 or 0-7) + * @param mux_bits Number of mux bits (2 or 4) + * @param divider Required divider in 7.1 or 15.1 format + * @return 0 if ok, -1 on error (requesting a parent clock which is not valid + * for this peripheral) + */ +static int adjust_periph_pll(enum periph_id periph_id, int source, + int mux_bits, unsigned divider) +{ + u32 *reg = get_periph_source_reg(periph_id); + + clrsetbits_le32(reg, OUT_CLK_DIVISOR_MASK, + divider << OUT_CLK_DIVISOR_SHIFT); + udelay(1); + + /* work out the source clock and set it */ + if (source < 0) + return -1; + if (mux_bits == 4) { + clrsetbits_le32(reg, OUT_CLK_SOURCE4_MASK, + source << OUT_CLK_SOURCE4_SHIFT); + } else { + clrsetbits_le32(reg, OUT_CLK_SOURCE_MASK, + source << OUT_CLK_SOURCE_SHIFT); + } + udelay(2); + return 0; +} + +unsigned clock_adjust_periph_pll_div(enum periph_id periph_id, + enum clock_id parent, unsigned rate, int *extra_div) +{ + unsigned effective_rate; + int mux_bits, divider_bits, source; + int divider; + + /* work out the source clock and set it */ + source = get_periph_clock_source(periph_id, parent, &mux_bits, + ÷r_bits); + + if (extra_div) + divider = find_best_divider(divider_bits, pll_rate[parent], + rate, extra_div); + else + divider = clk_get_divider(divider_bits, pll_rate[parent], + rate); + assert(divider >= 0); + if (adjust_periph_pll(periph_id, source, mux_bits, divider)) + return -1U; + debug("periph %d, rate=%d, reg=%p = %x\n", periph_id, rate, + get_periph_source_reg(periph_id), + readl(get_periph_source_reg(periph_id))); + + /* Check what we ended up with. This shouldn't matter though */ + effective_rate = clock_get_periph_rate(periph_id, parent); + if (extra_div) + effective_rate /= *extra_div; + if (rate != effective_rate) + debug("Requested clock rate %u not honored (got %u)\n", + rate, effective_rate); + return effective_rate; +} + +unsigned clock_start_periph_pll(enum periph_id periph_id, + enum clock_id parent, unsigned rate) +{ + unsigned effective_rate; + + reset_set_enable(periph_id, 1); + clock_enable(periph_id); + + effective_rate = clock_adjust_periph_pll_div(periph_id, parent, rate, + NULL); + + reset_set_enable(periph_id, 0); + return effective_rate; +} + +void clock_set_enable(enum periph_id periph_id, int enable) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 *clk = &clkrst->crc_clk_out_enb[PERIPH_REG(periph_id)]; + u32 reg; + + /* Enable/disable the clock to this peripheral */ + assert(clock_periph_id_isvalid(periph_id)); + reg = readl(clk); + if (enable) + reg |= PERIPH_MASK(periph_id); + else + reg &= ~PERIPH_MASK(periph_id); + writel(reg, clk); +} + +void clock_enable(enum periph_id clkid) +{ + clock_set_enable(clkid, 1); +} + +void clock_disable(enum periph_id clkid) +{ + clock_set_enable(clkid, 0); +} + +void reset_set_enable(enum periph_id periph_id, int enable) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 *reset = &clkrst->crc_rst_dev[PERIPH_REG(periph_id)]; + u32 reg; + + /* Enable/disable reset to the peripheral */ + assert(clock_periph_id_isvalid(periph_id)); + reg = readl(reset); + if (enable) + reg |= PERIPH_MASK(periph_id); + else + reg &= ~PERIPH_MASK(periph_id); + writel(reg, reset); +} + +void reset_periph(enum periph_id periph_id, int us_delay) +{ + /* Put peripheral into reset */ + reset_set_enable(periph_id, 1); + udelay(us_delay); + + /* Remove reset */ + reset_set_enable(periph_id, 0); + + udelay(us_delay); +} + +void reset_cmplx_set_enable(int cpu, int which, int reset) +{ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + u32 mask; + + /* Form the mask, which depends on the cpu chosen. Tegra20 has 2 */ + assert(cpu >= 0 && cpu < 2); + mask = which << cpu; + + /* either enable or disable those reset for that CPU */ + if (reset) + writel(mask, &clkrst->crc_cpu_cmplx_set); + else + writel(mask, &clkrst->crc_cpu_cmplx_clr); +} + +unsigned clock_get_rate(enum clock_id clkid) +{ + struct clk_pll *pll; + u32 base; + u32 divm; + u64 parent_rate; + u64 rate; + + parent_rate = osc_freq[clock_get_osc_freq()]; + if (clkid == CLOCK_ID_OSC) + return parent_rate; + + pll = get_pll(clkid); + base = readl(&pll->pll_base); + + /* Oh for bf_unpack()... */ + rate = parent_rate * ((base & PLL_DIVN_MASK) >> PLL_DIVN_SHIFT); + divm = (base & PLL_DIVM_MASK) >> PLL_DIVM_SHIFT; + if (clkid == CLOCK_ID_USB) + divm <<= (base & PLLU_VCO_FREQ_MASK) >> PLLU_VCO_FREQ_SHIFT; + else + divm <<= (base & PLL_DIVP_MASK) >> PLL_DIVP_SHIFT; + do_div(rate, divm); + return rate; +} + +/** + * Set the output frequency you want for each PLL clock. + * PLL output frequencies are programmed by setting their N, M and P values. + * The governing equations are: + * VCO = (Fi / m) * n, Fo = VCO / (2^p) + * where Fo is the output frequency from the PLL. + * Example: Set the output frequency to 216Mhz(Fo) with 12Mhz OSC(Fi) + * 216Mhz = ((12Mhz / m) * n) / (2^p) so n=432,m=12,p=1 + * Please see Tegra TRM section 5.3 to get the detail for PLL Programming + * + * @param n PLL feedback divider(DIVN) + * @param m PLL input divider(DIVN) + * @param p post divider(DIVP) + * @param cpcon base PLL charge pump(CPCON) + * @return 0 if ok, -1 on error (the requested PLL is incorrect and cannot + * be overriden), 1 if PLL is already correct + */ +static int clock_set_rate(enum clock_id clkid, u32 n, u32 m, u32 p, u32 cpcon) +{ + u32 base_reg; + u32 misc_reg; + struct clk_pll *pll; + + pll = get_pll(clkid); + + base_reg = readl(&pll->pll_base); + + /* Set BYPASS, m, n and p to PLL_BASE */ + base_reg &= ~PLL_DIVM_MASK; + base_reg |= m << PLL_DIVM_SHIFT; + + base_reg &= ~PLL_DIVN_MASK; + base_reg |= n << PLL_DIVN_SHIFT; + + base_reg &= ~PLL_DIVP_MASK; + base_reg |= p << PLL_DIVP_SHIFT; + + if (clkid == CLOCK_ID_PERIPH) { + /* + * If the PLL is already set up, check that it is correct + * and record this info for clock_verify() to check. + */ + if (base_reg & PLL_BASE_OVRRIDE_MASK) { + base_reg |= PLL_ENABLE_MASK; + if (base_reg != readl(&pll->pll_base)) + pllp_valid = 0; + return pllp_valid ? 1 : -1; + } + base_reg |= PLL_BASE_OVRRIDE_MASK; + } + + base_reg |= PLL_BYPASS_MASK; + writel(base_reg, &pll->pll_base); + + /* Set cpcon to PLL_MISC */ + misc_reg = readl(&pll->pll_misc); + misc_reg &= ~PLL_CPCON_MASK; + misc_reg |= cpcon << PLL_CPCON_SHIFT; + writel(misc_reg, &pll->pll_misc); + + /* Enable PLL */ + base_reg |= PLL_ENABLE_MASK; + writel(base_reg, &pll->pll_base); + + /* Disable BYPASS */ + base_reg &= ~PLL_BYPASS_MASK; + writel(base_reg, &pll->pll_base); + + return 0; +} + +void clock_ll_start_uart(enum periph_id periph_id) +{ + /* Assert UART reset and enable clock */ + reset_set_enable(periph_id, 1); + clock_enable(periph_id); + clock_ll_set_source(periph_id, 0); /* UARTx_CLK_SRC = 00, PLLP_OUT0 */ + + /* wait for 2us */ + udelay(2); + + /* De-assert reset to UART */ + reset_set_enable(periph_id, 0); +} + +#ifdef CONFIG_OF_CONTROL +/* + * Convert a device tree clock ID to our peripheral ID. They are mostly + * the same but we are very cautious so we check that a valid clock ID is + * provided. + * + * @param clk_id Clock ID according to tegra20 device tree binding + * @return peripheral ID, or PERIPH_ID_NONE if the clock ID is invalid + */ +static enum periph_id clk_id_to_periph_id(int clk_id) +{ + if (clk_id > 95) + return PERIPH_ID_NONE; + + switch (clk_id) { + case 1: + case 2: + case 7: + case 10: + case 20: + case 30: + case 35: + case 49: + case 56: + case 74: + case 76: + case 77: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 91: + case 95: + return PERIPH_ID_NONE; + default: + return clk_id; + } +} + +int clock_decode_periph_id(const void *blob, int node) +{ + enum periph_id id; + u32 cell[2]; + int err; + + err = fdtdec_get_int_array(blob, node, "clocks", cell, + ARRAY_SIZE(cell)); + if (err) + return -1; + id = clk_id_to_periph_id(cell[1]); + assert(clock_periph_id_isvalid(id)); + return id; +} +#endif /* CONFIG_OF_CONTROL */ + +int clock_verify(void) +{ + struct clk_pll *pll = get_pll(CLOCK_ID_PERIPH); + u32 reg = readl(&pll->pll_base); + + if (!pllp_valid) { + printf("Warning: PLLP %x is not correct\n", reg); + return -1; + } + debug("PLLX %x is correct\n", reg); + return 0; +} + +void clock_early_init(void) +{ + /* + * PLLP output frequency set to 216MHz + * PLLC output frequency set to 600Mhz + * + * TODO: Can we calculate these values instead of hard-coding? + */ + switch (clock_get_osc_freq()) { + case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ + clock_set_rate(CLOCK_ID_PERIPH, 432, 12, 1, 8); + clock_set_rate(CLOCK_ID_CGENERAL, 600, 12, 0, 8); + break; + + case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */ + clock_set_rate(CLOCK_ID_PERIPH, 432, 26, 1, 8); + clock_set_rate(CLOCK_ID_CGENERAL, 600, 26, 0, 8); + break; + + case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */ + clock_set_rate(CLOCK_ID_PERIPH, 432, 13, 1, 8); + clock_set_rate(CLOCK_ID_CGENERAL, 600, 13, 0, 8); + break; + case CLOCK_OSC_FREQ_19_2: + default: + /* + * These are not supported. It is too early to print a + * message and the UART likely won't work anyway due to the + * oscillator being wrong. + */ + break; + } +} + +void clock_init(void) +{ + pll_rate[CLOCK_ID_MEMORY] = clock_get_rate(CLOCK_ID_MEMORY); + pll_rate[CLOCK_ID_PERIPH] = clock_get_rate(CLOCK_ID_PERIPH); + pll_rate[CLOCK_ID_CGENERAL] = clock_get_rate(CLOCK_ID_CGENERAL); + pll_rate[CLOCK_ID_OSC] = clock_get_rate(CLOCK_ID_OSC); + pll_rate[CLOCK_ID_SFROM32KHZ] = 32768; + debug("Osc = %d\n", pll_rate[CLOCK_ID_OSC]); + debug("PLLM = %d\n", pll_rate[CLOCK_ID_MEMORY]); + debug("PLLP = %d\n", pll_rate[CLOCK_ID_PERIPH]); +} diff --git a/arch/arm/cpu/armv7/tegra20/cmd_enterrcm.c b/arch/arm/cpu/armv7/tegra20/cmd_enterrcm.c new file mode 100644 index 0000000..75cadb0 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/cmd_enterrcm.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * Derived from code (arch/arm/lib/reset.c) that is: + * + * (C) Copyright 2002 + * Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Marius Groeger <mgroeger@sysgo.de> + * + * (C) Copyright 2002 + * Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Alex Zuepke <azu@sysgo.de> + * + * (C) Copyright 2002 + * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de> + * + * (C) Copyright 2004 + * DAVE Srl + * http://www.dave-tech.it + * http://www.wawnet.biz + * mailto:info@wawnet.biz + * + * (C) Copyright 2004 Texas Insturments + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * 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 <common.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/pmc.h> + +static int do_enterrcm(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + + puts("Entering RCM...\n"); + udelay(50000); + + pmc->pmc_scratch0 = 2; + disable_interrupts(); + reset_cpu(0); + + return 0; +} + +U_BOOT_CMD( + enterrcm, 1, 0, do_enterrcm, + "reset Tegra and enter USB Recovery Mode", + "" +); diff --git a/arch/arm/cpu/armv7/tegra20/config.mk b/arch/arm/cpu/armv7/tegra20/config.mk new file mode 100644 index 0000000..714daaf --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/config.mk @@ -0,0 +1,38 @@ +# +# (C) Copyright 2010,2011 +# NVIDIA Corporation <www.nvidia.com> +# +# (C) Copyright 2002 +# Gary Jennejohn, DENX Software Engineering, <garyj@denx.de> +# +# See file CREDITS for list of people who contributed to this +# project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + +# Tegra has an ARMv4T CPU which runs board_init_f(), so we must build these +# files with compatible flags +ifdef CONFIG_TEGRA20 +CFLAGS_arch/arm/lib/board.o += -march=armv4t +CFLAGS_arch/arm/lib/memset.o += -march=armv4t +CFLAGS_lib/string.o += -march=armv4t +CFLAGS_common/cmd_nvedit.o += -march=armv4t +endif + +USE_PRIVATE_LIBGCC = yes + +CONFIG_ARCH_DEVICE_TREE := tegra20 diff --git a/arch/arm/cpu/armv7/tegra20/crypto.c b/arch/arm/cpu/armv7/tegra20/crypto.c new file mode 100644 index 0000000..5f0b240 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/crypto.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2010 - 2011 NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/errno.h> +#include "crypto.h" +#include "aes.h" + +static u8 zero_key[16]; + +#define AES_CMAC_CONST_RB 0x87 /* from RFC 4493, Figure 2.2 */ + +enum security_op { + SECURITY_SIGN = 1 << 0, /* Sign the data */ + SECURITY_ENCRYPT = 1 << 1, /* Encrypt the data */ +}; + +static void debug_print_vector(char *name, u32 num_bytes, u8 *data) +{ + u32 i; + + debug("%s [%d] @0x%08x", name, num_bytes, (u32)data); + for (i = 0; i < num_bytes; i++) { + if (i % 16 == 0) + debug(" = "); + debug("%02x", data[i]); + if ((i+1) % 16 != 0) + debug(" "); + } + debug("\n"); +} + +/** + * Apply chain data to the destination using EOR + * + * Each array is of length AES_AES_KEY_LENGTH. + * + * \param cbc_chain_data Chain data + * \param src Source data + * \param dst Destination data, which is modified here + */ +static void apply_cbc_chain_data(u8 *cbc_chain_data, u8 *src, u8 *dst) +{ + int i; + + for (i = 0; i < 16; i++) + *dst++ = *src++ ^ *cbc_chain_data++; +} + +/** + * Encrypt some data with AES. + * + * \param key_schedule Expanded key to use + * \param src Source data to encrypt + * \param dst Destination buffer + * \param num_aes_blocks Number of AES blocks to encrypt + */ +static void encrypt_object(u8 *key_schedule, u8 *src, u8 *dst, + u32 num_aes_blocks) +{ + u8 tmp_data[AES_KEY_LENGTH]; + u8 *cbc_chain_data; + u32 i; + + cbc_chain_data = zero_key; /* Convenient array of 0's for IV */ + + for (i = 0; i < num_aes_blocks; i++) { + debug("encrypt_object: block %d of %d\n", i, num_aes_blocks); + debug_print_vector("AES Src", AES_KEY_LENGTH, src); + + /* Apply the chain data */ + apply_cbc_chain_data(cbc_chain_data, src, tmp_data); + debug_print_vector("AES Xor", AES_KEY_LENGTH, tmp_data); + + /* encrypt the AES block */ + aes_encrypt(tmp_data, key_schedule, dst); + debug_print_vector("AES Dst", AES_KEY_LENGTH, dst); + + /* Update pointers for next loop. */ + cbc_chain_data = dst; + src += AES_KEY_LENGTH; + dst += AES_KEY_LENGTH; + } +} + +/** + * Shift a vector left by one bit + * + * \param in Input vector + * \param out Output vector + * \param size Length of vector in bytes + */ +static void left_shift_vector(u8 *in, u8 *out, int size) +{ + int carry = 0; + int i; + + for (i = size - 1; i >= 0; i--) { + out[i] = (in[i] << 1) | carry; + carry = in[i] >> 7; /* get most significant bit */ + } +} + +/** + * Sign a block of data, putting the result into dst. + * + * \param key Input AES key, length AES_KEY_LENGTH + * \param key_schedule Expanded key to use + * \param src Source data of length 'num_aes_blocks' blocks + * \param dst Destination buffer, length AES_KEY_LENGTH + * \param num_aes_blocks Number of AES blocks to encrypt + */ +static void sign_object(u8 *key, u8 *key_schedule, u8 *src, u8 *dst, + u32 num_aes_blocks) +{ + u8 tmp_data[AES_KEY_LENGTH]; + u8 left[AES_KEY_LENGTH]; + u8 k1[AES_KEY_LENGTH]; + u8 *cbc_chain_data; + unsigned i; + + cbc_chain_data = zero_key; /* Convenient array of 0's for IV */ + + /* compute K1 constant needed by AES-CMAC calculation */ + for (i = 0; i < AES_KEY_LENGTH; i++) + tmp_data[i] = 0; + + encrypt_object(key_schedule, tmp_data, left, 1); + debug_print_vector("AES(key, nonce)", AES_KEY_LENGTH, left); + + left_shift_vector(left, k1, sizeof(left)); + debug_print_vector("L", AES_KEY_LENGTH, left); + + if ((left[0] >> 7) != 0) /* get MSB of L */ + k1[AES_KEY_LENGTH-1] ^= AES_CMAC_CONST_RB; + debug_print_vector("K1", AES_KEY_LENGTH, k1); + + /* compute the AES-CMAC value */ + for (i = 0; i < num_aes_blocks; i++) { + /* Apply the chain data */ + apply_cbc_chain_data(cbc_chain_data, src, tmp_data); + + /* for the final block, XOR K1 into the IV */ + if (i == num_aes_blocks - 1) + apply_cbc_chain_data(tmp_data, k1, tmp_data); + + /* encrypt the AES block */ + aes_encrypt(tmp_data, key_schedule, dst); + + debug("sign_obj: block %d of %d\n", i, num_aes_blocks); + debug_print_vector("AES-CMAC Src", AES_KEY_LENGTH, src); + debug_print_vector("AES-CMAC Xor", AES_KEY_LENGTH, tmp_data); + debug_print_vector("AES-CMAC Dst", AES_KEY_LENGTH, dst); + + /* Update pointers for next loop. */ + cbc_chain_data = dst; + src += AES_KEY_LENGTH; + } + + debug_print_vector("AES-CMAC Hash", AES_KEY_LENGTH, dst); +} + +/** + * Encrypt and sign a block of data (depending on security mode). + * + * \param key Input AES key, length AES_KEY_LENGTH + * \param oper Security operations mask to perform (enum security_op) + * \param src Source data + * \param length Size of source data + * \param sig_dst Destination address for signature, AES_KEY_LENGTH bytes + */ +static int encrypt_and_sign(u8 *key, enum security_op oper, u8 *src, + u32 length, u8 *sig_dst) +{ + u32 num_aes_blocks; + u8 key_schedule[AES_EXPAND_KEY_LENGTH]; + + debug("encrypt_and_sign: length = %d\n", length); + debug_print_vector("AES key", AES_KEY_LENGTH, key); + + /* + * The only need for a key is for signing/checksum purposes, so + * if not encrypting, expand a key of 0s. + */ + aes_expand_key(oper & SECURITY_ENCRYPT ? key : zero_key, key_schedule); + + num_aes_blocks = (length + AES_KEY_LENGTH - 1) / AES_KEY_LENGTH; + + if (oper & SECURITY_ENCRYPT) { + /* Perform this in place, resulting in src being encrypted. */ + debug("encrypt_and_sign: begin encryption\n"); + encrypt_object(key_schedule, src, src, num_aes_blocks); + debug("encrypt_and_sign: end encryption\n"); + } + + if (oper & SECURITY_SIGN) { + /* encrypt the data, overwriting the result in signature. */ + debug("encrypt_and_sign: begin signing\n"); + sign_object(key, key_schedule, src, sig_dst, num_aes_blocks); + debug("encrypt_and_sign: end signing\n"); + } + + return 0; +} + +int sign_data_block(u8 *source, unsigned length, u8 *signature) +{ + return encrypt_and_sign(zero_key, SECURITY_SIGN, source, + length, signature); +} diff --git a/arch/arm/cpu/armv7/tegra20/crypto.h b/arch/arm/cpu/armv7/tegra20/crypto.h new file mode 100644 index 0000000..aff67e7 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/crypto.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2010 - 2011 NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +/** + * Sign a block of data + * + * \param source Source data + * \param length Size of source data + * \param signature Destination address for signature, AES_KEY_LENGTH bytes + */ +int sign_data_block(u8 *source, unsigned length, u8 *signature); + +#endif /* #ifndef _CRYPTO_H_ */ diff --git a/arch/arm/cpu/armv7/tegra20/emc.c b/arch/arm/cpu/armv7/tegra20/emc.c new file mode 100644 index 0000000..ffc05e4 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/emc.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <fdtdec.h> +#include <asm/io.h> +#include <asm/arch/ap20.h> +#include <asm/arch/apb_misc.h> +#include <asm/arch/clock.h> +#include <asm/arch/emc.h> +#include <asm/arch/tegra20.h> + +/* + * The EMC registers have shadow registers. When the EMC clock is updated + * in the clock controller, the shadow registers are copied to the active + * registers, allowing glitchless memory bus frequency changes. + * This function updates the shadow registers for a new clock frequency, + * and relies on the clock lock on the emc clock to avoid races between + * multiple frequency changes + */ + +/* + * This table defines the ordering of the registers provided to + * tegra_set_mmc() + * TODO: Convert to fdt version once available + */ +static const unsigned long emc_reg_addr[TEGRA_EMC_NUM_REGS] = { + 0x2c, /* RC */ + 0x30, /* RFC */ + 0x34, /* RAS */ + 0x38, /* RP */ + 0x3c, /* R2W */ + 0x40, /* W2R */ + 0x44, /* R2P */ + 0x48, /* W2P */ + 0x4c, /* RD_RCD */ + 0x50, /* WR_RCD */ + 0x54, /* RRD */ + 0x58, /* REXT */ + 0x5c, /* WDV */ + 0x60, /* QUSE */ + 0x64, /* QRST */ + 0x68, /* QSAFE */ + 0x6c, /* RDV */ + 0x70, /* REFRESH */ + 0x74, /* BURST_REFRESH_NUM */ + 0x78, /* PDEX2WR */ + 0x7c, /* PDEX2RD */ + 0x80, /* PCHG2PDEN */ + 0x84, /* ACT2PDEN */ + 0x88, /* AR2PDEN */ + 0x8c, /* RW2PDEN */ + 0x90, /* TXSR */ + 0x94, /* TCKE */ + 0x98, /* TFAW */ + 0x9c, /* TRPAB */ + 0xa0, /* TCLKSTABLE */ + 0xa4, /* TCLKSTOP */ + 0xa8, /* TREFBW */ + 0xac, /* QUSE_EXTRA */ + 0x114, /* FBIO_CFG6 */ + 0xb0, /* ODT_WRITE */ + 0xb4, /* ODT_READ */ + 0x104, /* FBIO_CFG5 */ + 0x2bc, /* CFG_DIG_DLL */ + 0x2c0, /* DLL_XFORM_DQS */ + 0x2c4, /* DLL_XFORM_QUSE */ + 0x2e0, /* ZCAL_REF_CNT */ + 0x2e4, /* ZCAL_WAIT_CNT */ + 0x2a8, /* AUTO_CAL_INTERVAL */ + 0x2d0, /* CFG_CLKTRIM_0 */ + 0x2d4, /* CFG_CLKTRIM_1 */ + 0x2d8, /* CFG_CLKTRIM_2 */ +}; + +struct emc_ctlr *emc_get_controller(const void *blob) +{ + fdt_addr_t addr; + int node; + + node = fdtdec_next_compatible(blob, 0, COMPAT_NVIDIA_TEGRA20_EMC); + if (node > 0) { + addr = fdtdec_get_addr(blob, node, "reg"); + if (addr != FDT_ADDR_T_NONE) + return (struct emc_ctlr *)addr; + } + return NULL; +} + +/* Error codes we use */ +enum { + ERR_NO_EMC_NODE = -10, + ERR_NO_EMC_REG, + ERR_NO_FREQ, + ERR_FREQ_NOT_FOUND, + ERR_BAD_REGS, + ERR_NO_RAM_CODE, + ERR_RAM_CODE_NOT_FOUND, +}; + +/** + * Find EMC tables for the given ram code. + * + * The tegra EMC binding has two options, one using the ram code and one not. + * We detect which is in use by looking for the nvidia,use-ram-code property. + * If this is not present, then the EMC tables are directly below 'node', + * otherwise we select the correct emc-tables subnode based on the 'ram_code' + * value. + * + * @param blob Device tree blob + * @param node EMC node (nvidia,tegra20-emc compatible string) + * @param ram_code RAM code to select (0-3, or -1 if unknown) + * @return 0 if ok, otherwise a -ve ERR_ code (see enum above) + */ +static int find_emc_tables(const void *blob, int node, int ram_code) +{ + int need_ram_code; + int depth; + int offset; + + /* If we are using RAM codes, scan through the tables for our code */ + need_ram_code = fdtdec_get_bool(blob, node, "nvidia,use-ram-code"); + if (!need_ram_code) + return node; + if (ram_code == -1) { + debug("%s: RAM code required but not supplied\n", __func__); + return ERR_NO_RAM_CODE; + } + + offset = node; + depth = 0; + do { + /* + * Sadly there is no compatible string so we cannot use + * fdtdec_next_compatible_subnode(). + */ + offset = fdt_next_node(blob, offset, &depth); + if (depth <= 0) + break; + + /* Make sure this is a direct subnode */ + if (depth != 1) + continue; + if (strcmp("emc-tables", fdt_get_name(blob, offset, NULL))) + continue; + + if (fdtdec_get_int(blob, offset, "nvidia,ram-code", -1) + == ram_code) + return offset; + } while (1); + + debug("%s: Could not find tables for RAM code %d\n", __func__, + ram_code); + return ERR_RAM_CODE_NOT_FOUND; +} + +/** + * Decode the EMC node of the device tree, returning a pointer to the emc + * controller and the table to be used for the given rate. + * + * @param blob Device tree blob + * @param rate Clock speed of memory controller in Hz (=2x memory bus rate) + * @param emcp Returns address of EMC controller registers + * @param tablep Returns pointer to table to program into EMC. There are + * TEGRA_EMC_NUM_REGS entries, destined for offsets as per the + * emc_reg_addr array. + * @return 0 if ok, otherwise a -ve error code which will allow someone to + * figure out roughly what went wrong by looking at this code. + */ +static int decode_emc(const void *blob, unsigned rate, struct emc_ctlr **emcp, + const u32 **tablep) +{ + struct apb_misc_pp_ctlr *pp = + (struct apb_misc_pp_ctlr *)NV_PA_APB_MISC_BASE; + int ram_code; + int depth; + int node; + + ram_code = (readl(&pp->strapping_opt_a) & RAM_CODE_MASK) + >> RAM_CODE_SHIFT; + /* + * The EMC clock rate is twice the bus rate, and the bus rate is + * measured in kHz + */ + rate = rate / 2 / 1000; + + node = fdtdec_next_compatible(blob, 0, COMPAT_NVIDIA_TEGRA20_EMC); + if (node < 0) { + debug("%s: No EMC node found in FDT\n", __func__); + return ERR_NO_EMC_NODE; + } + *emcp = (struct emc_ctlr *)fdtdec_get_addr(blob, node, "reg"); + if (*emcp == (struct emc_ctlr *)FDT_ADDR_T_NONE) { + debug("%s: No EMC node reg property\n", __func__); + return ERR_NO_EMC_REG; + } + + /* Work out the parent node which contains our EMC tables */ + node = find_emc_tables(blob, node, ram_code & 3); + if (node < 0) + return node; + + depth = 0; + for (;;) { + int node_rate; + + node = fdtdec_next_compatible_subnode(blob, node, + COMPAT_NVIDIA_TEGRA20_EMC_TABLE, &depth); + if (node < 0) + break; + node_rate = fdtdec_get_int(blob, node, "clock-frequency", -1); + if (node_rate == -1) { + debug("%s: Missing clock-frequency\n", __func__); + return ERR_NO_FREQ; /* we expect this property */ + } + + if (node_rate == rate) + break; + } + if (node < 0) { + debug("%s: No node found for clock frequency %d\n", __func__, + rate); + return ERR_FREQ_NOT_FOUND; + } + + *tablep = fdtdec_locate_array(blob, node, "nvidia,emc-registers", + TEGRA_EMC_NUM_REGS); + if (!*tablep) { + debug("%s: node '%s' array missing / wrong size\n", __func__, + fdt_get_name(blob, node, NULL)); + return ERR_BAD_REGS; + } + + /* All seems well */ + return 0; +} + +int tegra_set_emc(const void *blob, unsigned rate) +{ + struct emc_ctlr *emc; + const u32 *table; + int err, i; + + err = decode_emc(blob, rate, &emc, &table); + if (err) { + debug("Warning: no valid EMC (%d), memory timings unset\n", + err); + return err; + } + + debug("%s: Table found, setting EMC values as follows:\n", __func__); + for (i = 0; i < TEGRA_EMC_NUM_REGS; i++) { + u32 value = fdt32_to_cpu(table[i]); + u32 addr = (uintptr_t)emc + emc_reg_addr[i]; + + debug(" %#x: %#x\n", addr, value); + writel(value, addr); + } + + /* trigger emc with new settings */ + clock_adjust_periph_pll_div(PERIPH_ID_EMC, CLOCK_ID_MEMORY, + clock_get_rate(CLOCK_ID_MEMORY), NULL); + debug("EMC clock set to %lu\n", + clock_get_periph_rate(PERIPH_ID_EMC, CLOCK_ID_MEMORY)); + + return 0; +} diff --git a/arch/arm/cpu/armv7/tegra20/funcmux.c b/arch/arm/cpu/armv7/tegra20/funcmux.c new file mode 100644 index 0000000..8cfed64 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/funcmux.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* Tegra20 high-level function multiplexing */ +#include <common.h> +#include <asm/arch/clock.h> +#include <asm/arch/funcmux.h> +#include <asm/arch/pinmux.h> + +int funcmux_select(enum periph_id id, int config) +{ + int bad_config = config != FUNCMUX_DEFAULT; + + switch (id) { + case PERIPH_ID_UART1: + switch (config) { + case FUNCMUX_UART1_IRRX_IRTX: + pinmux_set_func(PINGRP_IRRX, PMUX_FUNC_UARTA); + pinmux_set_func(PINGRP_IRTX, PMUX_FUNC_UARTA); + pinmux_tristate_disable(PINGRP_IRRX); + pinmux_tristate_disable(PINGRP_IRTX); + break; + case FUNCMUX_UART1_UAA_UAB: + pinmux_set_func(PINGRP_UAA, PMUX_FUNC_UARTA); + pinmux_set_func(PINGRP_UAB, PMUX_FUNC_UARTA); + pinmux_tristate_disable(PINGRP_UAA); + pinmux_tristate_disable(PINGRP_UAB); + bad_config = 0; + break; + case FUNCMUX_UART1_GPU: + pinmux_set_func(PINGRP_GPU, PMUX_FUNC_UARTA); + pinmux_tristate_disable(PINGRP_GPU); + bad_config = 0; + break; + case FUNCMUX_UART1_SDIO1: + pinmux_set_func(PINGRP_SDIO1, PMUX_FUNC_UARTA); + pinmux_tristate_disable(PINGRP_SDIO1); + bad_config = 0; + break; + } + if (!bad_config) { + /* + * Tegra appears to boot with function UARTA pre- + * selected on mux group SDB. If two mux groups are + * both set to the same function, it's unclear which + * group's pins drive the RX signals into the HW. + * For UARTA, SDB certainly overrides group IRTX in + * practice. To solve this, configure some alternative + * function on SDB to avoid the conflict. Also, tri- + * state the group to avoid driving any signal onto it + * until we know what's connected. + */ + pinmux_tristate_enable(PINGRP_SDB); + pinmux_set_func(PINGRP_SDB, PMUX_FUNC_SDIO3); + } + break; + + case PERIPH_ID_UART2: + if (config == FUNCMUX_UART2_IRDA) { + pinmux_set_func(PINGRP_UAD, PMUX_FUNC_IRDA); + pinmux_tristate_disable(PINGRP_UAD); + } + break; + + case PERIPH_ID_UART4: + if (config == FUNCMUX_UART4_GMC) { + pinmux_set_func(PINGRP_GMC, PMUX_FUNC_UARTD); + pinmux_tristate_disable(PINGRP_GMC); + } + break; + + case PERIPH_ID_DVC_I2C: + /* there is only one selection, pinmux_config is ignored */ + if (config == FUNCMUX_DVC_I2CP) { + pinmux_set_func(PINGRP_I2CP, PMUX_FUNC_I2C); + pinmux_tristate_disable(PINGRP_I2CP); + } + break; + + case PERIPH_ID_I2C1: + /* support pinmux_config of 0 for now, */ + if (config == FUNCMUX_I2C1_RM) { + pinmux_set_func(PINGRP_RM, PMUX_FUNC_I2C); + pinmux_tristate_disable(PINGRP_RM); + } + break; + case PERIPH_ID_I2C2: /* I2C2 */ + switch (config) { + case FUNCMUX_I2C2_DDC: /* DDC pin group, select I2C2 */ + pinmux_set_func(PINGRP_DDC, PMUX_FUNC_I2C2); + /* PTA to HDMI */ + pinmux_set_func(PINGRP_PTA, PMUX_FUNC_HDMI); + pinmux_tristate_disable(PINGRP_DDC); + break; + case FUNCMUX_I2C2_PTA: /* PTA pin group, select I2C2 */ + pinmux_set_func(PINGRP_PTA, PMUX_FUNC_I2C2); + /* set DDC_SEL to RSVDx (RSVD2 works for now) */ + pinmux_set_func(PINGRP_DDC, PMUX_FUNC_RSVD2); + pinmux_tristate_disable(PINGRP_PTA); + bad_config = 0; + break; + } + break; + case PERIPH_ID_I2C3: /* I2C3 */ + /* support pinmux_config of 0 for now */ + if (config == FUNCMUX_I2C3_DTF) { + pinmux_set_func(PINGRP_DTF, PMUX_FUNC_I2C3); + pinmux_tristate_disable(PINGRP_DTF); + } + break; + + case PERIPH_ID_SDMMC1: + if (config == FUNCMUX_SDMMC1_SDIO1_4BIT) { + pinmux_set_func(PINGRP_SDIO1, PMUX_FUNC_SDIO1); + pinmux_tristate_disable(PINGRP_SDIO1); + } + break; + + case PERIPH_ID_SDMMC2: + if (config == FUNCMUX_SDMMC2_DTA_DTD_8BIT) { + pinmux_set_func(PINGRP_DTA, PMUX_FUNC_SDIO2); + pinmux_set_func(PINGRP_DTD, PMUX_FUNC_SDIO2); + + pinmux_tristate_disable(PINGRP_DTA); + pinmux_tristate_disable(PINGRP_DTD); + } + break; + + case PERIPH_ID_SDMMC3: + switch (config) { + case FUNCMUX_SDMMC3_SDB_SLXA_8BIT: + pinmux_set_func(PINGRP_SLXA, PMUX_FUNC_SDIO3); + pinmux_set_func(PINGRP_SLXC, PMUX_FUNC_SDIO3); + pinmux_set_func(PINGRP_SLXD, PMUX_FUNC_SDIO3); + pinmux_set_func(PINGRP_SLXK, PMUX_FUNC_SDIO3); + + pinmux_tristate_disable(PINGRP_SLXA); + pinmux_tristate_disable(PINGRP_SLXC); + pinmux_tristate_disable(PINGRP_SLXD); + pinmux_tristate_disable(PINGRP_SLXK); + /* fall through */ + + case FUNCMUX_SDMMC3_SDB_4BIT: + pinmux_set_func(PINGRP_SDB, PMUX_FUNC_SDIO3); + pinmux_set_func(PINGRP_SDC, PMUX_FUNC_SDIO3); + pinmux_set_func(PINGRP_SDD, PMUX_FUNC_SDIO3); + + pinmux_tristate_disable(PINGRP_SDB); + pinmux_tristate_disable(PINGRP_SDC); + pinmux_tristate_disable(PINGRP_SDD); + bad_config = 0; + break; + } + break; + + case PERIPH_ID_SDMMC4: + switch (config) { + case FUNCMUX_SDMMC4_ATC_ATD_8BIT: + pinmux_set_func(PINGRP_ATC, PMUX_FUNC_SDIO4); + pinmux_set_func(PINGRP_ATD, PMUX_FUNC_SDIO4); + + pinmux_tristate_disable(PINGRP_ATC); + pinmux_tristate_disable(PINGRP_ATD); + break; + + case FUNCMUX_SDMMC4_ATB_GMA_GME_8_BIT: + pinmux_set_func(PINGRP_GME, PMUX_FUNC_SDIO4); + pinmux_tristate_disable(PINGRP_GME); + /* fall through */ + + case FUNCMUX_SDMMC4_ATB_GMA_4_BIT: + pinmux_set_func(PINGRP_ATB, PMUX_FUNC_SDIO4); + pinmux_set_func(PINGRP_GMA, PMUX_FUNC_SDIO4); + + pinmux_tristate_disable(PINGRP_ATB); + pinmux_tristate_disable(PINGRP_GMA); + bad_config = 0; + break; + } + break; + + case PERIPH_ID_KBC: + if (config == FUNCMUX_DEFAULT) { + enum pmux_pingrp grp[] = {PINGRP_KBCA, PINGRP_KBCB, + PINGRP_KBCC, PINGRP_KBCD, PINGRP_KBCE, + PINGRP_KBCF}; + int i; + + for (i = 0; i < ARRAY_SIZE(grp); i++) { + pinmux_tristate_disable(grp[i]); + pinmux_set_func(grp[i], PMUX_FUNC_KBC); + pinmux_set_pullupdown(grp[i], PMUX_PULL_UP); + } + } + break; + + case PERIPH_ID_USB2: + if (config == FUNCMUX_USB2_ULPI) { + pinmux_set_func(PINGRP_UAA, PMUX_FUNC_ULPI); + pinmux_set_func(PINGRP_UAB, PMUX_FUNC_ULPI); + pinmux_set_func(PINGRP_UDA, PMUX_FUNC_ULPI); + + pinmux_tristate_disable(PINGRP_UAA); + pinmux_tristate_disable(PINGRP_UAB); + pinmux_tristate_disable(PINGRP_UDA); + } + break; + + case PERIPH_ID_SPI1: + if (config == FUNCMUX_SPI1_GMC_GMD) { + pinmux_set_func(PINGRP_GMC, PMUX_FUNC_SFLASH); + pinmux_set_func(PINGRP_GMD, PMUX_FUNC_SFLASH); + + pinmux_tristate_disable(PINGRP_GMC); + pinmux_tristate_disable(PINGRP_GMD); + } + break; + + default: + debug("%s: invalid periph_id %d", __func__, id); + return -1; + } + + if (bad_config) { + debug("%s: invalid config %d for periph_id %d", __func__, + config, id); + return -1; + } + + return 0; +} diff --git a/arch/arm/cpu/armv7/tegra20/lowlevel_init.S b/arch/arm/cpu/armv7/tegra20/lowlevel_init.S new file mode 100644 index 0000000..d117f23 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/lowlevel_init.S @@ -0,0 +1,42 @@ +/* + * SoC-specific setup info + * + * (C) Copyright 2010,2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <config.h> +#include <version.h> +#include <linux/linkage.h> + + .align 5 +ENTRY(reset_cpu) + ldr r1, rstctl @ get addr for global reset + @ reg + ldr r3, [r1] + orr r3, r3, #0x10 + str r3, [r1] @ force reset + mov r0, r0 +_loop_forever: + b _loop_forever +rstctl: + .word PRM_RSTCTRL +ENDPROC(reset_cpu) diff --git a/arch/arm/cpu/armv7/tegra20/pinmux.c b/arch/arm/cpu/armv7/tegra20/pinmux.c new file mode 100644 index 0000000..70e84df --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/pinmux.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* Tegra20 pin multiplexing functions */ + +#include <asm/io.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/pinmux.h> +#include <common.h> + + +/* + * This defines the order of the pin mux control bits in the registers. For + * some reason there is no correspendence between the tristate, pin mux and + * pullup/pulldown registers. + */ +enum pmux_ctlid { + /* 0: APB_MISC_PP_PIN_MUX_CTL_A_0 */ + MUXCTL_UAA, + MUXCTL_UAB, + MUXCTL_UAC, + MUXCTL_UAD, + MUXCTL_UDA, + MUXCTL_RESERVED5, + MUXCTL_ATE, + MUXCTL_RM, + + MUXCTL_ATB, + MUXCTL_RESERVED9, + MUXCTL_ATD, + MUXCTL_ATC, + MUXCTL_ATA, + MUXCTL_KBCF, + MUXCTL_KBCE, + MUXCTL_SDMMC1, + + /* 16: APB_MISC_PP_PIN_MUX_CTL_B_0 */ + MUXCTL_GMA, + MUXCTL_GMC, + MUXCTL_HDINT, + MUXCTL_SLXA, + MUXCTL_OWC, + MUXCTL_SLXC, + MUXCTL_SLXD, + MUXCTL_SLXK, + + MUXCTL_UCA, + MUXCTL_UCB, + MUXCTL_DTA, + MUXCTL_DTB, + MUXCTL_RESERVED28, + MUXCTL_DTC, + MUXCTL_DTD, + MUXCTL_DTE, + + /* 32: APB_MISC_PP_PIN_MUX_CTL_C_0 */ + MUXCTL_DDC, + MUXCTL_CDEV1, + MUXCTL_CDEV2, + MUXCTL_CSUS, + MUXCTL_I2CP, + MUXCTL_KBCA, + MUXCTL_KBCB, + MUXCTL_KBCC, + + MUXCTL_IRTX, + MUXCTL_IRRX, + MUXCTL_DAP1, + MUXCTL_DAP2, + MUXCTL_DAP3, + MUXCTL_DAP4, + MUXCTL_GMB, + MUXCTL_GMD, + + /* 48: APB_MISC_PP_PIN_MUX_CTL_D_0 */ + MUXCTL_GME, + MUXCTL_GPV, + MUXCTL_GPU, + MUXCTL_SPDO, + MUXCTL_SPDI, + MUXCTL_SDB, + MUXCTL_SDC, + MUXCTL_SDD, + + MUXCTL_SPIH, + MUXCTL_SPIG, + MUXCTL_SPIF, + MUXCTL_SPIE, + MUXCTL_SPID, + MUXCTL_SPIC, + MUXCTL_SPIB, + MUXCTL_SPIA, + + /* 64: APB_MISC_PP_PIN_MUX_CTL_E_0 */ + MUXCTL_LPW0, + MUXCTL_LPW1, + MUXCTL_LPW2, + MUXCTL_LSDI, + MUXCTL_LSDA, + MUXCTL_LSPI, + MUXCTL_LCSN, + MUXCTL_LDC, + + MUXCTL_LSCK, + MUXCTL_LSC0, + MUXCTL_LSC1, + MUXCTL_LHS, + MUXCTL_LVS, + MUXCTL_LM0, + MUXCTL_LM1, + MUXCTL_LVP0, + + /* 80: APB_MISC_PP_PIN_MUX_CTL_F_0 */ + MUXCTL_LD0, + MUXCTL_LD1, + MUXCTL_LD2, + MUXCTL_LD3, + MUXCTL_LD4, + MUXCTL_LD5, + MUXCTL_LD6, + MUXCTL_LD7, + + MUXCTL_LD8, + MUXCTL_LD9, + MUXCTL_LD10, + MUXCTL_LD11, + MUXCTL_LD12, + MUXCTL_LD13, + MUXCTL_LD14, + MUXCTL_LD15, + + /* 96: APB_MISC_PP_PIN_MUX_CTL_G_0 */ + MUXCTL_LD16, + MUXCTL_LD17, + MUXCTL_LHP1, + MUXCTL_LHP2, + MUXCTL_LVP1, + MUXCTL_LHP0, + MUXCTL_RESERVED102, + MUXCTL_LPP, + + MUXCTL_LDI, + MUXCTL_PMC, + MUXCTL_CRTP, + MUXCTL_PTA, + MUXCTL_RESERVED108, + MUXCTL_KBCD, + MUXCTL_GPU7, + MUXCTL_DTF, + + MUXCTL_NONE = -1, +}; + +/* + * And this defines the order of the pullup/pulldown controls which are again + * in a different order + */ +enum pmux_pullid { + /* 0: APB_MISC_PP_PULLUPDOWN_REG_A_0 */ + PUCTL_ATA, + PUCTL_ATB, + PUCTL_ATC, + PUCTL_ATD, + PUCTL_ATE, + PUCTL_DAP1, + PUCTL_DAP2, + PUCTL_DAP3, + + PUCTL_DAP4, + PUCTL_DTA, + PUCTL_DTB, + PUCTL_DTC, + PUCTL_DTD, + PUCTL_DTE, + PUCTL_DTF, + PUCTL_GPV, + + /* 16: APB_MISC_PP_PULLUPDOWN_REG_B_0 */ + PUCTL_RM, + PUCTL_I2CP, + PUCTL_PTA, + PUCTL_GPU7, + PUCTL_KBCA, + PUCTL_KBCB, + PUCTL_KBCC, + PUCTL_KBCD, + + PUCTL_SPDI, + PUCTL_SPDO, + PUCTL_GPSLXAU, + PUCTL_CRTP, + PUCTL_SLXC, + PUCTL_SLXD, + PUCTL_SLXK, + + /* 32: APB_MISC_PP_PULLUPDOWN_REG_C_0 */ + PUCTL_CDEV1, + PUCTL_CDEV2, + PUCTL_SPIA, + PUCTL_SPIB, + PUCTL_SPIC, + PUCTL_SPID, + PUCTL_SPIE, + PUCTL_SPIF, + + PUCTL_SPIG, + PUCTL_SPIH, + PUCTL_IRTX, + PUCTL_IRRX, + PUCTL_GME, + PUCTL_RESERVED45, + PUCTL_XM2D, + PUCTL_XM2C, + + /* 48: APB_MISC_PP_PULLUPDOWN_REG_D_0 */ + PUCTL_UAA, + PUCTL_UAB, + PUCTL_UAC, + PUCTL_UAD, + PUCTL_UCA, + PUCTL_UCB, + PUCTL_LD17, + PUCTL_LD19_18, + + PUCTL_LD21_20, + PUCTL_LD23_22, + PUCTL_LS, + PUCTL_LC, + PUCTL_CSUS, + PUCTL_DDRC, + PUCTL_SDC, + PUCTL_SDD, + + /* 64: APB_MISC_PP_PULLUPDOWN_REG_E_0 */ + PUCTL_KBCF, + PUCTL_KBCE, + PUCTL_PMCA, + PUCTL_PMCB, + PUCTL_PMCC, + PUCTL_PMCD, + PUCTL_PMCE, + PUCTL_CK32, + + PUCTL_UDA, + PUCTL_SDMMC1, + PUCTL_GMA, + PUCTL_GMB, + PUCTL_GMC, + PUCTL_GMD, + PUCTL_DDC, + PUCTL_OWC, + + PUCTL_NONE = -1 +}; + +struct tegra_pingroup_desc { + const char *name; + enum pmux_func funcs[4]; + enum pmux_func func_safe; + enum pmux_vddio vddio; + enum pmux_ctlid ctl_id; + enum pmux_pullid pull_id; +}; + + +/* Converts a pmux_pingrp number to a tristate register: 0=A, 1=B, 2=C, 3=D */ +#define TRISTATE_REG(pmux_pingrp) ((pmux_pingrp) >> 5) + +/* Mask value for a tristate (within TRISTATE_REG(id)) */ +#define TRISTATE_MASK(pmux_pingrp) (1 << ((pmux_pingrp) & 0x1f)) + +/* Converts a PUCTL id to a pull register: 0=A, 1=B...4=E */ +#define PULL_REG(pmux_pullid) ((pmux_pullid) >> 4) + +/* Converts a PUCTL id to a shift position */ +#define PULL_SHIFT(pmux_pullid) ((pmux_pullid << 1) & 0x1f) + +/* Converts a MUXCTL id to a ctl register: 0=A, 1=B...6=G */ +#define MUXCTL_REG(pmux_ctlid) ((pmux_ctlid) >> 4) + +/* Converts a MUXCTL id to a shift position */ +#define MUXCTL_SHIFT(pmux_ctlid) ((pmux_ctlid << 1) & 0x1f) + +/* Convenient macro for defining pin group properties */ +#define PINALL(pg_name, vdd, f0, f1, f2, f3, f_safe, mux, pupd) \ + { \ + .vddio = PMUX_VDDIO_ ## vdd, \ + .funcs = { \ + PMUX_FUNC_ ## f0, \ + PMUX_FUNC_ ## f1, \ + PMUX_FUNC_ ## f2, \ + PMUX_FUNC_ ## f3, \ + }, \ + .func_safe = PMUX_FUNC_ ## f_safe, \ + .ctl_id = mux, \ + .pull_id = pupd \ + } + +/* A normal pin group where the mux name and pull-up name match */ +#define PIN(pg_name, vdd, f0, f1, f2, f3, f_safe) \ + PINALL(pg_name, vdd, f0, f1, f2, f3, f_safe, \ + MUXCTL_ ## pg_name, PUCTL_ ## pg_name) + +/* A pin group where the pull-up name doesn't have a 1-1 mapping */ +#define PINP(pg_name, vdd, f0, f1, f2, f3, f_safe, pupd) \ + PINALL(pg_name, vdd, f0, f1, f2, f3, f_safe, \ + MUXCTL_ ## pg_name, PUCTL_ ## pupd) + +/* A pin group number which is not used */ +#define PIN_RESERVED \ + PIN(NONE, NONE, NONE, NONE, NONE, NONE, NONE) + +const struct tegra_pingroup_desc tegra_soc_pingroups[PINGRP_COUNT] = { + PIN(ATA, NAND, IDE, NAND, GMI, RSVD, IDE), + PIN(ATB, NAND, IDE, NAND, GMI, SDIO4, IDE), + PIN(ATC, NAND, IDE, NAND, GMI, SDIO4, IDE), + PIN(ATD, NAND, IDE, NAND, GMI, SDIO4, IDE), + PIN(CDEV1, AUDIO, OSC, PLLA_OUT, PLLM_OUT1, AUDIO_SYNC, OSC), + PIN(CDEV2, AUDIO, OSC, AHB_CLK, APB_CLK, PLLP_OUT4, OSC), + PIN(CSUS, VI, PLLC_OUT1, PLLP_OUT2, PLLP_OUT3, VI_SENSOR_CLK, + PLLC_OUT1), + PIN(DAP1, AUDIO, DAP1, RSVD, GMI, SDIO2, DAP1), + + PIN(DAP2, AUDIO, DAP2, TWC, RSVD, GMI, DAP2), + PIN(DAP3, BB, DAP3, RSVD, RSVD, RSVD, DAP3), + PIN(DAP4, UART, DAP4, RSVD, GMI, RSVD, DAP4), + PIN(DTA, VI, RSVD, SDIO2, VI, RSVD, RSVD4), + PIN(DTB, VI, RSVD, RSVD, VI, SPI1, RSVD1), + PIN(DTC, VI, RSVD, RSVD, VI, RSVD, RSVD1), + PIN(DTD, VI, RSVD, SDIO2, VI, RSVD, RSVD1), + PIN(DTE, VI, RSVD, RSVD, VI, SPI1, RSVD1), + + PINP(GPU, UART, PWM, UARTA, GMI, RSVD, RSVD4, + GPSLXAU), + PIN(GPV, SD, PCIE, RSVD, RSVD, RSVD, PCIE), + PIN(I2CP, SYS, I2C, RSVD, RSVD, RSVD, RSVD4), + PIN(IRTX, UART, UARTA, UARTB, GMI, SPI4, UARTB), + PIN(IRRX, UART, UARTA, UARTB, GMI, SPI4, UARTB), + PIN(KBCB, SYS, KBC, NAND, SDIO2, MIO, KBC), + PIN(KBCA, SYS, KBC, NAND, SDIO2, EMC_TEST0_DLL, KBC), + PINP(PMC, SYS, PWR_ON, PWR_INTR, RSVD, RSVD, PWR_ON, NONE), + + PIN(PTA, NAND, I2C2, HDMI, GMI, RSVD, RSVD4), + PIN(RM, UART, I2C, RSVD, RSVD, RSVD, RSVD4), + PIN(KBCE, SYS, KBC, NAND, OWR, RSVD, KBC), + PIN(KBCF, SYS, KBC, NAND, TRACE, MIO, KBC), + PIN(GMA, NAND, UARTE, SPI3, GMI, SDIO4, SPI3), + PIN(GMC, NAND, UARTD, SPI4, GMI, SFLASH, SPI4), + PIN(SDMMC1, BB, SDIO1, RSVD, UARTE, UARTA, RSVD2), + PIN(OWC, SYS, OWR, RSVD, RSVD, RSVD, OWR), + + PIN(GME, NAND, RSVD, DAP5, GMI, SDIO4, GMI), + PIN(SDC, SD, PWM, TWC, SDIO3, SPI3, TWC), + PIN(SDD, SD, UARTA, PWM, SDIO3, SPI3, PWM), + PIN_RESERVED, + PINP(SLXA, SD, PCIE, SPI4, SDIO3, SPI2, PCIE, CRTP), + PIN(SLXC, SD, SPDIF, SPI4, SDIO3, SPI2, SPI4), + PIN(SLXD, SD, SPDIF, SPI4, SDIO3, SPI2, SPI4), + PIN(SLXK, SD, PCIE, SPI4, SDIO3, SPI2, PCIE), + + PIN(SPDI, AUDIO, SPDIF, RSVD, I2C, SDIO2, RSVD2), + PIN(SPDO, AUDIO, SPDIF, RSVD, I2C, SDIO2, RSVD2), + PIN(SPIA, AUDIO, SPI1, SPI2, SPI3, GMI, GMI), + PIN(SPIB, AUDIO, SPI1, SPI2, SPI3, GMI, GMI), + PIN(SPIC, AUDIO, SPI1, SPI2, SPI3, GMI, GMI), + PIN(SPID, AUDIO, SPI2, SPI1, SPI2_ALT, GMI, GMI), + PIN(SPIE, AUDIO, SPI2, SPI1, SPI2_ALT, GMI, GMI), + PIN(SPIF, AUDIO, SPI3, SPI1, SPI2, RSVD, RSVD4), + + PIN(SPIG, AUDIO, SPI3, SPI2, SPI2_ALT, I2C, SPI2_ALT), + PIN(SPIH, AUDIO, SPI3, SPI2, SPI2_ALT, I2C, SPI2_ALT), + PIN(UAA, BB, SPI3, MIPI_HS, UARTA, ULPI, MIPI_HS), + PIN(UAB, BB, SPI2, MIPI_HS, UARTA, ULPI, MIPI_HS), + PIN(UAC, BB, OWR, RSVD, RSVD, RSVD, RSVD4), + PIN(UAD, UART, IRDA, SPDIF, UARTA, SPI4, SPDIF), + PIN(UCA, UART, UARTC, RSVD, GMI, RSVD, RSVD4), + PIN(UCB, UART, UARTC, PWM, GMI, RSVD, RSVD4), + + PIN_RESERVED, + PIN(ATE, NAND, IDE, NAND, GMI, RSVD, IDE), + PIN(KBCC, SYS, KBC, NAND, TRACE, EMC_TEST1_DLL, KBC), + PIN_RESERVED, + PIN_RESERVED, + PIN(GMB, NAND, IDE, NAND, GMI, GMI_INT, GMI), + PIN(GMD, NAND, RSVD, NAND, GMI, SFLASH, GMI), + PIN(DDC, LCD, I2C2, RSVD, RSVD, RSVD, RSVD4), + + /* 64 */ + PINP(LD0, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD1, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD2, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD3, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD4, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD5, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD6, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD7, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + + PINP(LD8, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD9, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD10, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD11, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD12, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD13, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD14, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD15, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + + PINP(LD16, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LD17), + PINP(LD17, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD17), + PINP(LHP0, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD21_20), + PINP(LHP1, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD19_18), + PINP(LHP2, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD19_18), + PINP(LVP0, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LC), + PINP(LVP1, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD21_20), + PINP(HDINT, LCD, HDMI, RSVD, RSVD, RSVD, HDMI , LC), + + PINP(LM0, LCD, DISPA, DISPB, SPI3, RSVD, RSVD4, LC), + PINP(LM1, LCD, DISPA, DISPB, RSVD, CRT, RSVD3, LC), + PINP(LVS, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LC), + PINP(LSC0, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LC), + PINP(LSC1, LCD, DISPA, DISPB, SPI3, HDMI, DISPA, LS), + PINP(LSCK, LCD, DISPA, DISPB, SPI3, HDMI, DISPA, LS), + PINP(LDC, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LS), + PINP(LCSN, LCD, DISPA, DISPB, SPI3, RSVD, RSVD4, LS), + + /* 96 */ + PINP(LSPI, LCD, DISPA, DISPB, XIO, HDMI, DISPA, LC), + PINP(LSDA, LCD, DISPA, DISPB, SPI3, HDMI, DISPA, LS), + PINP(LSDI, LCD, DISPA, DISPB, SPI3, RSVD, DISPA, LS), + PINP(LPW0, LCD, DISPA, DISPB, SPI3, HDMI, DISPA, LS), + PINP(LPW1, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LS), + PINP(LPW2, LCD, DISPA, DISPB, SPI3, HDMI, DISPA, LS), + PINP(LDI, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD23_22), + PINP(LHS, LCD, DISPA, DISPB, XIO, RSVD, RSVD4, LC), + + PINP(LPP, LCD, DISPA, DISPB, RSVD, RSVD, RSVD4, LD23_22), + PIN_RESERVED, + PIN(KBCD, SYS, KBC, NAND, SDIO2, MIO, KBC), + PIN(GPU7, SYS, RTCK, RSVD, RSVD, RSVD, RTCK), + PIN(DTF, VI, I2C3, RSVD, VI, RSVD, RSVD4), + PIN(UDA, BB, SPI1, RSVD, UARTD, ULPI, RSVD2), + PIN(CRTP, LCD, CRT, RSVD, RSVD, RSVD, RSVD), + PINP(SDB, SD, UARTA, PWM, SDIO3, SPI2, PWM, NONE), + + /* these pin groups only have pullup and pull down control */ + PINALL(CK32, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(DDRC, DDR, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(PMCA, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(PMCB, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(PMCC, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(PMCD, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(PMCE, SYS, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(XM2C, DDR, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), + PINALL(XM2D, DDR, RSVD, RSVD, RSVD, RSVD, RSVD, MUXCTL_NONE, + PUCTL_NONE), +}; + +void pinmux_set_tristate(enum pmux_pingrp pin, int enable) +{ + struct pmux_tri_ctlr *pmt = + (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + u32 *tri = &pmt->pmt_tri[TRISTATE_REG(pin)]; + u32 reg; + + reg = readl(tri); + if (enable) + reg |= TRISTATE_MASK(pin); + else + reg &= ~TRISTATE_MASK(pin); + writel(reg, tri); +} + +void pinmux_tristate_enable(enum pmux_pingrp pin) +{ + pinmux_set_tristate(pin, 1); +} + +void pinmux_tristate_disable(enum pmux_pingrp pin) +{ + pinmux_set_tristate(pin, 0); +} + +void pinmux_set_pullupdown(enum pmux_pingrp pin, enum pmux_pull pupd) +{ + struct pmux_tri_ctlr *pmt = + (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + enum pmux_pullid pull_id = tegra_soc_pingroups[pin].pull_id; + u32 *pull = &pmt->pmt_pull[PULL_REG(pull_id)]; + u32 mask_bit; + u32 reg; + mask_bit = PULL_SHIFT(pull_id); + + reg = readl(pull); + reg &= ~(0x3 << mask_bit); + reg |= pupd << mask_bit; + writel(reg, pull); +} + +void pinmux_set_func(enum pmux_pingrp pin, enum pmux_func func) +{ + struct pmux_tri_ctlr *pmt = + (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + enum pmux_ctlid mux_id = tegra_soc_pingroups[pin].ctl_id; + u32 *muxctl = &pmt->pmt_ctl[MUXCTL_REG(mux_id)]; + u32 mask_bit; + int i, mux = -1; + u32 reg; + + assert(pmux_func_isvalid(func)); + + /* Handle special values */ + if (func >= PMUX_FUNC_RSVD1) { + mux = (func - PMUX_FUNC_RSVD1) & 0x3; + } else { + /* Search for the appropriate function */ + for (i = 0; i < 4; i++) { + if (tegra_soc_pingroups[pin].funcs[i] == func) { + mux = i; + break; + } + } + } + assert(mux != -1); + + mask_bit = MUXCTL_SHIFT(mux_id); + reg = readl(muxctl); + reg &= ~(0x3 << mask_bit); + reg |= mux << mask_bit; + writel(reg, muxctl); +} + +void pinmux_config_pingroup(struct pingroup_config *config) +{ + enum pmux_pingrp pin = config->pingroup; + + pinmux_set_func(pin, config->func); + pinmux_set_pullupdown(pin, config->pull); + pinmux_set_tristate(pin, config->tristate); +} + +void pinmux_config_table(struct pingroup_config *config, int len) +{ + int i; + + for (i = 0; i < len; i++) + pinmux_config_pingroup(&config[i]); +} diff --git a/arch/arm/cpu/armv7/tegra20/pmu.c b/arch/arm/cpu/armv7/tegra20/pmu.c new file mode 100644 index 0000000..53505e9 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/pmu.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2010,2011 NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <tps6586x.h> +#include <asm/io.h> +#include <asm/arch/ap20.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/tegra_i2c.h> +#include <asm/arch/sys_proto.h> + +#define VDD_CORE_NOMINAL_T25 0x17 /* 1.3v */ +#define VDD_CPU_NOMINAL_T25 0x10 /* 1.125v */ + +#define VDD_CORE_NOMINAL_T20 0x16 /* 1.275v */ +#define VDD_CPU_NOMINAL_T20 0x0f /* 1.1v */ + +#define VDD_RELATION 0x02 /* 50mv */ +#define VDD_TRANSITION_STEP 0x06 /* 150mv */ +#define VDD_TRANSITION_RATE 0x06 /* 3.52mv/us */ + +int pmu_set_nominal(void) +{ + int core, cpu, bus; + + /* by default, the table has been filled with T25 settings */ + switch (tegra_get_chip_type()) { + case TEGRA_SOC_T20: + core = VDD_CORE_NOMINAL_T20; + cpu = VDD_CPU_NOMINAL_T20; + break; + case TEGRA_SOC_T25: + core = VDD_CORE_NOMINAL_T25; + cpu = VDD_CPU_NOMINAL_T25; + break; + default: + debug("%s: Unknown chip type\n", __func__); + return -1; + } + + bus = tegra_i2c_get_dvc_bus_num(); + if (bus == -1) { + debug("%s: Cannot find DVC I2C bus\n", __func__); + return -1; + } + tps6586x_init(bus); + tps6586x_set_pwm_mode(TPS6586X_PWM_SM1); + return tps6586x_adjust_sm0_sm1(core, cpu, VDD_TRANSITION_STEP, + VDD_TRANSITION_RATE, VDD_RELATION); +} diff --git a/arch/arm/cpu/armv7/tegra20/sys_info.c b/arch/arm/cpu/armv7/tegra20/sys_info.c new file mode 100644 index 0000000..1a0bb56 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/sys_info.c @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2010,2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> + +#ifdef CONFIG_DISPLAY_CPUINFO +/* Print CPU information */ +int print_cpuinfo(void) +{ + puts("TEGRA20\n"); + + /* TBD: Add printf of major/minor rev info, stepping, etc. */ + return 0; +} +#endif /* CONFIG_DISPLAY_CPUINFO */ diff --git a/arch/arm/cpu/armv7/tegra20/timer.c b/arch/arm/cpu/armv7/tegra20/timer.c new file mode 100644 index 0000000..562e414 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/timer.c @@ -0,0 +1,111 @@ +/* + * (C) Copyright 2010,2011 + * NVIDIA Corporation <www.nvidia.com> + * + * (C) Copyright 2008 + * Texas Instruments + * + * Richard Woodruff <r-woodruff2@ti.com> + * Syed Moahmmed Khasim <khasim@ti.com> + * + * (C) Copyright 2002 + * Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Marius Groeger <mgroeger@sysgo.de> + * Alex Zuepke <azu@sysgo.de> + * + * (C) Copyright 2002 + * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/timer.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* counter runs at 1MHz */ +#define TIMER_CLK 1000000 +#define TIMER_LOAD_VAL 0xffffffff + +/* timer without interrupts */ +ulong get_timer(ulong base) +{ + return get_timer_masked() - base; +} + +/* delay x useconds */ +void __udelay(unsigned long usec) +{ + long tmo = usec * (TIMER_CLK / 1000) / 1000; + unsigned long now, last = timer_get_us(); + + while (tmo > 0) { + now = timer_get_us(); + if (last > now) /* count up timer overflow */ + tmo -= TIMER_LOAD_VAL - last + now; + else + tmo -= now - last; + last = now; + } +} + +ulong get_timer_masked(void) +{ + ulong now; + + /* current tick value */ + now = timer_get_us() / (TIMER_CLK / CONFIG_SYS_HZ); + + if (now >= gd->lastinc) /* normal mode (non roll) */ + /* move stamp forward with absolute diff ticks */ + gd->tbl += (now - gd->lastinc); + else /* we have rollover of incrementer */ + gd->tbl += ((TIMER_LOAD_VAL / (TIMER_CLK / CONFIG_SYS_HZ)) + - gd->lastinc) + now; + gd->lastinc = now; + return gd->tbl; +} + +/* + * This function is derived from PowerPC code (read timebase as long long). + * On ARM it just returns the timer value. + */ +unsigned long long get_ticks(void) +{ + return get_timer(0); +} + +/* + * This function is derived from PowerPC code (timebase clock frequency). + * On ARM it returns the number of timer ticks per second. + */ +ulong get_tbclk(void) +{ + return CONFIG_SYS_HZ; +} + +unsigned long timer_get_us(void) +{ + struct timerus *timer_base = (struct timerus *)NV_PA_TMRUS_BASE; + + return readl(&timer_base->cntr_1us); +} diff --git a/arch/arm/cpu/armv7/tegra20/usb.c b/arch/arm/cpu/armv7/tegra20/usb.c new file mode 100644 index 0000000..178bb13 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/usb.c @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2010,2011 NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm-generic/gpio.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/gpio.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/sys_proto.h> +#include <asm/arch/uart.h> +#include <asm/arch/usb.h> +#include <libfdt.h> +#include <fdtdec.h> + +enum { + USB_PORTS_MAX = 4, /* Maximum ports we allow */ +}; + +/* Parameters we need for USB */ +enum { + PARAM_DIVN, /* PLL FEEDBACK DIVIDer */ + PARAM_DIVM, /* PLL INPUT DIVIDER */ + PARAM_DIVP, /* POST DIVIDER (2^N) */ + PARAM_CPCON, /* BASE PLLC CHARGE Pump setup ctrl */ + PARAM_LFCON, /* BASE PLLC LOOP FILter setup ctrl */ + PARAM_ENABLE_DELAY_COUNT, /* PLL-U Enable Delay Count */ + PARAM_STABLE_COUNT, /* PLL-U STABLE count */ + PARAM_ACTIVE_DELAY_COUNT, /* PLL-U Active delay count */ + PARAM_XTAL_FREQ_COUNT, /* PLL-U XTAL frequency count */ + PARAM_DEBOUNCE_A_TIME, /* 10MS DELAY for BIAS_DEBOUNCE_A */ + PARAM_BIAS_TIME, /* 20US DELAY AFter bias cell op */ + + PARAM_COUNT +}; + +/* Possible port types (dual role mode) */ +enum dr_mode { + DR_MODE_NONE = 0, + DR_MODE_HOST, /* supports host operation */ + DR_MODE_DEVICE, /* supports device operation */ + DR_MODE_OTG, /* supports both */ +}; + +/* Information about a USB port */ +struct fdt_usb { + struct usb_ctlr *reg; /* address of registers in physical memory */ + unsigned utmi:1; /* 1 if port has external tranceiver, else 0 */ + unsigned enabled:1; /* 1 to enable, 0 to disable */ + unsigned has_legacy_mode:1; /* 1 if this port has legacy mode */ + enum dr_mode dr_mode; /* dual role mode */ + enum periph_id periph_id;/* peripheral id */ + struct fdt_gpio_state vbus_gpio; /* GPIO for vbus enable */ +}; + +static struct fdt_usb port[USB_PORTS_MAX]; /* List of valid USB ports */ +static unsigned port_count; /* Number of available ports */ +static int port_current; /* Current port (-1 = none) */ + +/* + * This table has USB timing parameters for each Oscillator frequency we + * support. There are four sets of values: + * + * 1. PLLU configuration information (reference clock is osc/clk_m and + * PLLU-FOs are fixed at 12MHz/60MHz/480MHz). + * + * Reference frequency 13.0MHz 19.2MHz 12.0MHz 26.0MHz + * ---------------------------------------------------------------------- + * DIVN 960 (0x3c0) 200 (0c8) 960 (3c0h) 960 (3c0) + * DIVM 13 (0d) 4 (04) 12 (0c) 26 (1a) + * Filter frequency (MHz) 1 4.8 6 2 + * CPCON 1100b 0011b 1100b 1100b + * LFCON0 0 0 0 0 + * + * 2. PLL CONFIGURATION & PARAMETERS for different clock generators: + * + * Reference frequency 13.0MHz 19.2MHz 12.0MHz 26.0MHz + * --------------------------------------------------------------------------- + * PLLU_ENABLE_DLY_COUNT 02 (0x02) 03 (03) 02 (02) 04 (04) + * PLLU_STABLE_COUNT 51 (33) 75 (4B) 47 (2F) 102 (66) + * PLL_ACTIVE_DLY_COUNT 05 (05) 06 (06) 04 (04) 09 (09) + * XTAL_FREQ_COUNT 127 (7F) 187 (BB) 118 (76) 254 (FE) + * + * 3. Debounce values IdDig, Avalid, Bvalid, VbusValid, VbusWakeUp, and + * SessEnd. Each of these signals have their own debouncer and for each of + * those one out of two debouncing times can be chosen (BIAS_DEBOUNCE_A or + * BIAS_DEBOUNCE_B). + * + * The values of DEBOUNCE_A and DEBOUNCE_B are calculated as follows: + * 0xffff -> No debouncing at all + * <n> ms = <n> *1000 / (1/19.2MHz) / 4 + * + * So to program a 1 ms debounce for BIAS_DEBOUNCE_A, we have: + * BIAS_DEBOUNCE_A[15:0] = 1000 * 19.2 / 4 = 4800 = 0x12c0 + * + * We need to use only DebounceA for BOOTROM. We don't need the DebounceB + * values, so we can keep those to default. + * + * 4. The 20 microsecond delay after bias cell operation. + */ +static const unsigned usb_pll[CLOCK_OSC_FREQ_COUNT][PARAM_COUNT] = { + /* DivN, DivM, DivP, CPCON, LFCON, Delays Debounce, Bias */ + { 0x3C0, 0x0D, 0x00, 0xC, 0, 0x02, 0x33, 0x05, 0x7F, 0x7EF4, 5 }, + { 0x0C8, 0x04, 0x00, 0x3, 0, 0x03, 0x4B, 0x06, 0xBB, 0xBB80, 7 }, + { 0x3C0, 0x0C, 0x00, 0xC, 0, 0x02, 0x2F, 0x04, 0x76, 0x7530, 5 }, + { 0x3C0, 0x1A, 0x00, 0xC, 0, 0x04, 0x66, 0x09, 0xFE, 0xFDE8, 9 } +}; + +/* UTMIP Idle Wait Delay */ +static const u8 utmip_idle_wait_delay = 17; + +/* UTMIP Elastic limit */ +static const u8 utmip_elastic_limit = 16; + +/* UTMIP High Speed Sync Start Delay */ +static const u8 utmip_hs_sync_start_delay = 9; + +/* Put the port into host mode (this only works for OTG ports) */ +static void set_host_mode(struct fdt_usb *config) +{ + if (config->dr_mode == DR_MODE_OTG) { + /* Check whether remote host from USB1 is driving VBus */ + if (readl(&config->reg->phy_vbus_sensors) & VBUS_VLD_STS) + return; + + /* + * If not driving, we set the GPIO to enable VBUS. We assume + * that the pinmux is set up correctly for this. + */ + if (fdt_gpio_isvalid(&config->vbus_gpio)) { + fdtdec_setup_gpio(&config->vbus_gpio); + gpio_direction_output(config->vbus_gpio.gpio, 1); + debug("set_host_mode: GPIO %d high\n", + config->vbus_gpio.gpio); + } + } +} + +void usbf_reset_controller(struct fdt_usb *config, struct usb_ctlr *usbctlr) +{ + /* Reset the USB controller with 2us delay */ + reset_periph(config->periph_id, 2); + + /* + * Set USB1_NO_LEGACY_MODE to 1, Registers are accessible under + * base address + */ + if (config->has_legacy_mode) + setbits_le32(&usbctlr->usb1_legacy_ctrl, USB1_NO_LEGACY_MODE); + + /* Put UTMIP1/3 in reset */ + setbits_le32(&usbctlr->susp_ctrl, UTMIP_RESET); + + /* Enable the UTMIP PHY */ + if (config->utmi) + setbits_le32(&usbctlr->susp_ctrl, UTMIP_PHY_ENB); + + /* + * TODO: where do we take the USB1 out of reset? The old code would + * take USB3 out of reset, but not USB1. This code doesn't do either. + */ +} + +/* set up the USB controller with the parameters provided */ +static int init_usb_controller(struct fdt_usb *config, + struct usb_ctlr *usbctlr, const u32 timing[]) +{ + u32 val; + int loop_count; + + clock_enable(config->periph_id); + + /* Reset the usb controller */ + usbf_reset_controller(config, usbctlr); + + /* Stop crystal clock by setting UTMIP_PHY_XTAL_CLOCKEN low */ + clrbits_le32(&usbctlr->utmip_misc_cfg1, UTMIP_PHY_XTAL_CLOCKEN); + + /* Follow the crystal clock disable by >100ns delay */ + udelay(1); + + /* + * To Use the A Session Valid for cable detection logic, VBUS_WAKEUP + * mux must be switched to actually use a_sess_vld threshold. + */ + if (fdt_gpio_isvalid(&config->vbus_gpio)) { + clrsetbits_le32(&usbctlr->usb1_legacy_ctrl, + VBUS_SENSE_CTL_MASK, + VBUS_SENSE_CTL_A_SESS_VLD << VBUS_SENSE_CTL_SHIFT); + } + + /* + * PLL Delay CONFIGURATION settings. The following parameters control + * the bring up of the plls. + */ + val = readl(&usbctlr->utmip_misc_cfg1); + clrsetbits_le32(&val, UTMIP_PLLU_STABLE_COUNT_MASK, + timing[PARAM_STABLE_COUNT] << UTMIP_PLLU_STABLE_COUNT_SHIFT); + clrsetbits_le32(&val, UTMIP_PLL_ACTIVE_DLY_COUNT_MASK, + timing[PARAM_ACTIVE_DELAY_COUNT] << + UTMIP_PLL_ACTIVE_DLY_COUNT_SHIFT); + writel(val, &usbctlr->utmip_misc_cfg1); + + /* Set PLL enable delay count and crystal frequency count */ + val = readl(&usbctlr->utmip_pll_cfg1); + clrsetbits_le32(&val, UTMIP_PLLU_ENABLE_DLY_COUNT_MASK, + timing[PARAM_ENABLE_DELAY_COUNT] << + UTMIP_PLLU_ENABLE_DLY_COUNT_SHIFT); + clrsetbits_le32(&val, UTMIP_XTAL_FREQ_COUNT_MASK, + timing[PARAM_XTAL_FREQ_COUNT] << + UTMIP_XTAL_FREQ_COUNT_SHIFT); + writel(val, &usbctlr->utmip_pll_cfg1); + + /* Setting the tracking length time */ + clrsetbits_le32(&usbctlr->utmip_bias_cfg1, + UTMIP_BIAS_PDTRK_COUNT_MASK, + timing[PARAM_BIAS_TIME] << UTMIP_BIAS_PDTRK_COUNT_SHIFT); + + /* Program debounce time for VBUS to become valid */ + clrsetbits_le32(&usbctlr->utmip_debounce_cfg0, + UTMIP_DEBOUNCE_CFG0_MASK, + timing[PARAM_DEBOUNCE_A_TIME] << UTMIP_DEBOUNCE_CFG0_SHIFT); + + setbits_le32(&usbctlr->utmip_tx_cfg0, UTMIP_FS_PREAMBLE_J); + + /* Disable battery charge enabling bit */ + setbits_le32(&usbctlr->utmip_bat_chrg_cfg0, UTMIP_PD_CHRG); + + clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_XCVR_LSBIAS_SE); + setbits_le32(&usbctlr->utmip_spare_cfg0, FUSE_SETUP_SEL); + + /* + * Configure the UTMIP_IDLE_WAIT and UTMIP_ELASTIC_LIMIT + * Setting these fields, together with default values of the + * other fields, results in programming the registers below as + * follows: + * UTMIP_HSRX_CFG0 = 0x9168c000 + * UTMIP_HSRX_CFG1 = 0x13 + */ + + /* Set PLL enable delay count and Crystal frequency count */ + val = readl(&usbctlr->utmip_hsrx_cfg0); + clrsetbits_le32(&val, UTMIP_IDLE_WAIT_MASK, + utmip_idle_wait_delay << UTMIP_IDLE_WAIT_SHIFT); + clrsetbits_le32(&val, UTMIP_ELASTIC_LIMIT_MASK, + utmip_elastic_limit << UTMIP_ELASTIC_LIMIT_SHIFT); + writel(val, &usbctlr->utmip_hsrx_cfg0); + + /* Configure the UTMIP_HS_SYNC_START_DLY */ + clrsetbits_le32(&usbctlr->utmip_hsrx_cfg1, + UTMIP_HS_SYNC_START_DLY_MASK, + utmip_hs_sync_start_delay << UTMIP_HS_SYNC_START_DLY_SHIFT); + + /* Preceed the crystal clock disable by >100ns delay. */ + udelay(1); + + /* Resuscitate crystal clock by setting UTMIP_PHY_XTAL_CLOCKEN */ + setbits_le32(&usbctlr->utmip_misc_cfg1, UTMIP_PHY_XTAL_CLOCKEN); + + /* Finished the per-controller init. */ + + /* De-assert UTMIP_RESET to bring out of reset. */ + clrbits_le32(&usbctlr->susp_ctrl, UTMIP_RESET); + + /* Wait for the phy clock to become valid in 100 ms */ + for (loop_count = 100000; loop_count != 0; loop_count--) { + if (readl(&usbctlr->susp_ctrl) & USB_PHY_CLK_VALID) + break; + udelay(1); + } + if (!loop_count) + return -1; + + return 0; +} + +static void power_up_port(struct usb_ctlr *usbctlr) +{ + /* Deassert power down state */ + clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_FORCE_PD_POWERDOWN | + UTMIP_FORCE_PD2_POWERDOWN | UTMIP_FORCE_PDZI_POWERDOWN); + clrbits_le32(&usbctlr->utmip_xcvr_cfg1, UTMIP_FORCE_PDDISC_POWERDOWN | + UTMIP_FORCE_PDCHRP_POWERDOWN | UTMIP_FORCE_PDDR_POWERDOWN); +} + +static void config_clock(const u32 timing[]) +{ + clock_start_pll(CLOCK_ID_USB, + timing[PARAM_DIVM], timing[PARAM_DIVN], timing[PARAM_DIVP], + timing[PARAM_CPCON], timing[PARAM_LFCON]); +} + +/** + * Add a new USB port to the list of available ports. + * + * @param config USB port configuration + * @return 0 if ok, -1 if error (too many ports) + */ +static int add_port(struct fdt_usb *config, const u32 timing[]) +{ + struct usb_ctlr *usbctlr = config->reg; + + if (port_count == USB_PORTS_MAX) { + debug("tegrausb: Cannot register more than %d ports\n", + USB_PORTS_MAX); + return -1; + } + if (init_usb_controller(config, usbctlr, timing)) { + debug("tegrausb: Cannot init port\n"); + return -1; + } + if (config->utmi) { + /* Disable ICUSB FS/LS transceiver */ + clrbits_le32(&usbctlr->icusb_ctrl, IC_ENB1); + + /* Select UTMI parallel interface */ + clrsetbits_le32(&usbctlr->port_sc1, PTS_MASK, + PTS_UTMI << PTS_SHIFT); + clrbits_le32(&usbctlr->port_sc1, STS); + power_up_port(usbctlr); + } + port[port_count++] = *config; + + return 0; +} + +int tegrausb_start_port(unsigned portnum, u32 *hccr, u32 *hcor) +{ + struct usb_ctlr *usbctlr; + + if (portnum >= port_count) + return -1; + tegrausb_stop_port(); + set_host_mode(&port[portnum]); + + usbctlr = port[portnum].reg; + *hccr = (u32)&usbctlr->cap_length; + *hcor = (u32)&usbctlr->usb_cmd; + port_current = portnum; + return 0; +} + +int tegrausb_stop_port(void) +{ + struct usb_ctlr *usbctlr; + + if (port_current == -1) + return -1; + + usbctlr = port[port_current].reg; + + /* Stop controller */ + writel(0, &usbctlr->usb_cmd); + udelay(1000); + + /* Initiate controller reset */ + writel(2, &usbctlr->usb_cmd); + udelay(1000); + port_current = -1; + return 0; +} + +int fdt_decode_usb(const void *blob, int node, unsigned osc_frequency_mhz, + struct fdt_usb *config) +{ + const char *phy, *mode; + + config->reg = (struct usb_ctlr *)fdtdec_get_addr(blob, node, "reg"); + mode = fdt_getprop(blob, node, "dr_mode", NULL); + if (mode) { + if (0 == strcmp(mode, "host")) + config->dr_mode = DR_MODE_HOST; + else if (0 == strcmp(mode, "peripheral")) + config->dr_mode = DR_MODE_DEVICE; + else if (0 == strcmp(mode, "otg")) + config->dr_mode = DR_MODE_OTG; + else { + debug("%s: Cannot decode dr_mode '%s'\n", __func__, + mode); + return -FDT_ERR_NOTFOUND; + } + } else { + config->dr_mode = DR_MODE_HOST; + } + + phy = fdt_getprop(blob, node, "phy_type", NULL); + config->utmi = phy && 0 == strcmp("utmi", phy); + config->enabled = fdtdec_get_is_enabled(blob, node); + config->has_legacy_mode = fdtdec_get_bool(blob, node, + "nvidia,has-legacy-mode"); + config->periph_id = clock_decode_periph_id(blob, node); + if (config->periph_id == PERIPH_ID_NONE) { + debug("%s: Missing/invalid peripheral ID\n", __func__); + return -FDT_ERR_NOTFOUND; + } + fdtdec_decode_gpio(blob, node, "nvidia,vbus-gpio", &config->vbus_gpio); + debug("enabled=%d, legacy_mode=%d, utmi=%d, periph_id=%d, vbus=%d, " + "dr_mode=%d\n", config->enabled, config->has_legacy_mode, + config->utmi, config->periph_id, config->vbus_gpio.gpio, + config->dr_mode); + + return 0; +} + +int board_usb_init(const void *blob) +{ + struct fdt_usb config; + unsigned osc_freq = clock_get_rate(CLOCK_ID_OSC); + enum clock_osc_freq freq; + int node_list[USB_PORTS_MAX]; + int node, count, i; + + /* Set up the USB clocks correctly based on our oscillator frequency */ + freq = clock_get_osc_freq(); + config_clock(usb_pll[freq]); + + /* count may return <0 on error */ + count = fdtdec_find_aliases_for_id(blob, "usb", + COMPAT_NVIDIA_TEGRA20_USB, node_list, USB_PORTS_MAX); + for (i = 0; i < count; i++) { + debug("USB %d: ", i); + node = node_list[i]; + if (!node) + continue; + if (fdt_decode_usb(blob, node, osc_freq, &config)) { + debug("Cannot decode USB node %s\n", + fdt_get_name(blob, node, NULL)); + return -1; + } + + if (add_port(&config, usb_pll[freq])) + return -1; + set_host_mode(&config); + } + port_current = -1; + + return 0; +} diff --git a/arch/arm/cpu/armv7/tegra20/warmboot.c b/arch/arm/cpu/armv7/tegra20/warmboot.c new file mode 100644 index 0000000..809ea01 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/warmboot.c @@ -0,0 +1,386 @@ +/* + * (C) Copyright 2010 - 2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/errno.h> +#include <asm/arch/ap20.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/pmc.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/fuse.h> +#include <asm/arch/emc.h> +#include <asm/arch/gp_padctrl.h> +#include <asm/arch/warmboot.h> +#include <asm/arch/sdram_param.h> + +DECLARE_GLOBAL_DATA_PTR; + +#ifndef CONFIG_TEGRA_CLOCK_SCALING +#error "You must enable CONFIG_TEGRA_CLOCK_SCALING to use CONFIG_TEGRA20_LP0" +#endif + +/* + * This is the place in SRAM where the SDRAM parameters are stored. There + * are 4 blocks, one for each RAM code + */ +#define SDRAM_PARAMS_BASE (AP20_BASE_PA_SRAM + 0x188) + +/* TODO: If we later add support for the Misc GP controller, refactor this */ +union xm2cfga_reg { + struct { + u32 reserved0:2; + u32 hsm_en:1; + u32 reserved1:2; + u32 preemp_en:1; + u32 vref_en:1; + u32 reserved2:5; + u32 cal_drvdn:5; + u32 reserved3:3; + u32 cal_drvup:5; + u32 reserved4:3; + u32 cal_drvdn_slwr:2; + u32 cal_drvup_slwf:2; + }; + u32 word; +}; + +union xm2cfgd_reg { + struct { + u32 reserved0:2; + u32 hsm_en:1; + u32 schmt_en:1; + u32 lpmd:2; + u32 vref_en:1; + u32 reserved1:5; + u32 cal_drvdn:5; + u32 reserved2:3; + u32 cal_drvup:5; + u32 reserved3:3; + u32 cal_drvdn_slwr:2; + u32 cal_drvup_slwf:2; + }; + u32 word; +}; + +/* + * TODO: This register is not documented in the TRM yet. We could move this + * into the EMC and give it a proper interface, but not while it is + * undocumented. + */ +union fbio_spare_reg { + struct { + u32 reserved:24; + u32 cfg_wb0:8; + }; + u32 word; +}; + +/* We pack the resume information into these unions for later */ +union scratch2_reg { + struct { + u32 pllm_base_divm:5; + u32 pllm_base_divn:10; + u32 pllm_base_divp:3; + u32 pllm_misc_lfcon:4; + u32 pllm_misc_cpcon:4; + u32 gp_xm2cfga_padctrl_preemp:1; + u32 gp_xm2cfgd_padctrl_schmt:1; + u32 osc_ctrl_xobp:1; + u32 memory_type:3; + }; + u32 word; +}; + +union scratch4_reg { + struct { + u32 emc_clock_divider:8; + u32 pllm_stable_time:8; + u32 pllx_stable_time:8; + u32 emc_fbio_spare_cfg_wb0:8; + }; + u32 word; +}; + +union scratch24_reg { + struct { + u32 emc_auto_cal_wait:8; + u32 emc_pin_program_wait:8; + u32 warmboot_wait:8; + u32 reserved:8; + }; + u32 word; +}; + +int warmboot_save_sdram_params(void) +{ + u32 ram_code; + struct sdram_params sdram; + struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + struct apb_misc_gp_ctlr *gp = + (struct apb_misc_gp_ctlr *)TEGRA20_APB_MISC_GP_BASE; + struct emc_ctlr *emc = emc_get_controller(gd->fdt_blob); + union scratch2_reg scratch2; + union scratch4_reg scratch4; + union scratch24_reg scratch24; + union xm2cfga_reg xm2cfga; + union xm2cfgd_reg xm2cfgd; + union fbio_spare_reg fbio_spare; + + /* get ram code that is used as index to array sdram_params in BCT */ + ram_code = (readl(&pmt->pmt_strap_opt_a) >> + STRAP_OPT_A_RAM_CODE_SHIFT) & 3; + memcpy(&sdram, + (char *)((struct sdram_params *)SDRAM_PARAMS_BASE + ram_code), + sizeof(sdram)); + + xm2cfga.word = readl(&gp->xm2cfga); + xm2cfgd.word = readl(&gp->xm2cfgd); + + scratch2.word = 0; + scratch2.osc_ctrl_xobp = clock_get_osc_bypass(); + + /* Get the memory PLL settings */ + { + u32 divm, divn, divp, cpcon, lfcon; + + if (clock_ll_read_pll(CLOCK_ID_MEMORY, &divm, &divn, &divp, + &cpcon, &lfcon)) + return -1; + scratch2.pllm_base_divm = divm; + scratch2.pllm_base_divn = divn; + scratch2.pllm_base_divp = divp; + scratch2.pllm_misc_cpcon = cpcon; + scratch2.pllm_misc_lfcon = lfcon; + } + + scratch2.gp_xm2cfga_padctrl_preemp = xm2cfga.preemp_en; + scratch2.gp_xm2cfgd_padctrl_schmt = xm2cfgd.schmt_en; + scratch2.memory_type = sdram.memory_type; + writel(scratch2.word, &pmc->pmc_scratch2); + + /* collect data from various sources for pmc_scratch4 */ + fbio_spare.word = readl(&emc->fbio_spare); + scratch4.word = 0; + scratch4.emc_fbio_spare_cfg_wb0 = fbio_spare.cfg_wb0; + scratch4.emc_clock_divider = sdram.emc_clock_divider; + scratch4.pllm_stable_time = -1; + scratch4.pllx_stable_time = -1; + writel(scratch4.word, &pmc->pmc_scratch4); + + /* collect various data from sdram for pmc_scratch24 */ + scratch24.word = 0; + scratch24.emc_pin_program_wait = sdram.emc_pin_program_wait; + scratch24.emc_auto_cal_wait = sdram.emc_auto_cal_wait; + scratch24.warmboot_wait = sdram.warm_boot_wait; + writel(scratch24.word, &pmc->pmc_scratch24); + + return 0; +} + +static u32 get_major_version(void) +{ + u32 major_id; + struct apb_misc_gp_ctlr *gp = + (struct apb_misc_gp_ctlr *)TEGRA20_APB_MISC_GP_BASE; + + major_id = (readl(&gp->hidrev) & HIDREV_MAJORPREV_MASK) >> + HIDREV_MAJORPREV_SHIFT; + return major_id; +} + +static int is_production_mode_fuse_set(struct fuse_regs *fuse) +{ + return readl(&fuse->production_mode); +} + +static int is_odm_production_mode_fuse_set(struct fuse_regs *fuse) +{ + return readl(&fuse->security_mode); +} + +static int is_failure_analysis_mode(struct fuse_regs *fuse) +{ + return readl(&fuse->fa); +} + +static int ap20_is_odm_production_mode(void) +{ + struct fuse_regs *fuse = (struct fuse_regs *)TEGRA20_FUSE_BASE; + + if (!is_failure_analysis_mode(fuse) && + is_odm_production_mode_fuse_set(fuse)) + return 1; + else + return 0; +} + +static int ap20_is_production_mode(void) +{ + struct fuse_regs *fuse = (struct fuse_regs *)TEGRA20_FUSE_BASE; + + if (get_major_version() == 0) + return 1; + + if (!is_failure_analysis_mode(fuse) && + is_production_mode_fuse_set(fuse) && + !is_odm_production_mode_fuse_set(fuse)) + return 1; + else + return 0; +} + +static enum fuse_operating_mode fuse_get_operation_mode(void) +{ + u32 chip_id; + struct apb_misc_gp_ctlr *gp = + (struct apb_misc_gp_ctlr *)TEGRA20_APB_MISC_GP_BASE; + + chip_id = (readl(&gp->hidrev) & HIDREV_CHIPID_MASK) >> + HIDREV_CHIPID_SHIFT; + if (chip_id == CHIPID_TEGRA20) { + if (ap20_is_odm_production_mode()) { + printf("!! odm_production_mode is not supported !!\n"); + return MODE_UNDEFINED; + } else + if (ap20_is_production_mode()) + return MODE_PRODUCTION; + else + return MODE_UNDEFINED; + } + return MODE_UNDEFINED; +} + +static void determine_crypto_options(int *is_encrypted, int *is_signed, + int *use_zero_key) +{ + switch (fuse_get_operation_mode()) { + case MODE_PRODUCTION: + *is_encrypted = 0; + *is_signed = 1; + *use_zero_key = 1; + break; + case MODE_UNDEFINED: + default: + *is_encrypted = 0; + *is_signed = 0; + *use_zero_key = 0; + break; + } +} + +static int sign_wb_code(u32 start, u32 length, int use_zero_key) +{ + int err; + u8 *source; /* Pointer to source */ + u8 *hash; + + /* Calculate AES block parameters. */ + source = (u8 *)(start + offsetof(struct wb_header, random_aes_block)); + length -= offsetof(struct wb_header, random_aes_block); + hash = (u8 *)(start + offsetof(struct wb_header, hash)); + err = sign_data_block(source, length, hash); + + return err; +} + +int warmboot_prepare_code(u32 seg_address, u32 seg_length) +{ + int err = 0; + u32 length; /* length of the signed/encrypt code */ + struct wb_header *dst_header; /* Pointer to dest WB header */ + int is_encrypted; /* Segment is encrypted */ + int is_signed; /* Segment is signed */ + int use_zero_key; /* Use key of all zeros */ + + /* Determine crypto options. */ + determine_crypto_options(&is_encrypted, &is_signed, &use_zero_key); + + /* Get the actual code limits. */ + length = roundup(((u32)wb_end - (u32)wb_start), 16); + + /* + * The region specified by seg_address must be in SDRAM and must be + * nonzero in length. + */ + if (seg_length == 0 || seg_address < NV_PA_SDRAM_BASE || + seg_address + seg_length >= NV_PA_SDRAM_BASE + gd->ram_size) { + err = -EFAULT; + goto fail; + } + + /* Things must be 16-byte aligned. */ + if ((seg_length & 0xF) || (seg_address & 0xF)) { + err = -EINVAL; + goto fail; + } + + /* Will the code fit? (destination includes wb_header + wb code) */ + if (seg_length < (length + sizeof(struct wb_header))) { + err = -EINVAL; + goto fail; + } + + dst_header = (struct wb_header *)seg_address; + memset((char *)dst_header, 0, sizeof(struct wb_header)); + + /* Populate the random_aes_block as requested. */ + { + u32 *aes_block = (u32 *)&(dst_header->random_aes_block); + u32 *end = (u32 *)(((u32)aes_block) + + sizeof(dst_header->random_aes_block)); + + do { + *aes_block++ = 0; + } while (aes_block < end); + } + + /* Populate the header. */ + dst_header->length_insecure = length + sizeof(struct wb_header); + dst_header->length_secure = length + sizeof(struct wb_header); + dst_header->destination = AP20_WB_RUN_ADDRESS; + dst_header->entry_point = AP20_WB_RUN_ADDRESS; + dst_header->code_length = length; + + if (is_encrypted) { + printf("!!!! Encryption is not supported !!!!\n"); + dst_header->length_insecure = 0; + err = -EACCES; + goto fail; + } else + /* copy the wb code directly following dst_header. */ + memcpy((char *)(dst_header+1), (char *)wb_start, length); + + if (is_signed) + err = sign_wb_code(seg_address, dst_header->length_insecure, + use_zero_key); + +fail: + if (err) + printf("Warning: warmboot code copy failed (error=%d)\n", err); + + return err; +} diff --git a/arch/arm/cpu/armv7/tegra20/warmboot_avp.c b/arch/arm/cpu/armv7/tegra20/warmboot_avp.c new file mode 100644 index 0000000..cd01908 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/warmboot_avp.c @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2010 - 2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/arch/ap20.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/flow.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/pmc.h> +#include <asm/arch/tegra20.h> +#include <asm/arch/warmboot.h> +#include "warmboot_avp.h" + +#define DEBUG_RESET_CORESIGHT + +void wb_start(void) +{ + struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + struct pmc_ctlr *pmc = (struct pmc_ctlr *)TEGRA20_PMC_BASE; + struct flow_ctlr *flow = (struct flow_ctlr *)NV_PA_FLOW_BASE; + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + union osc_ctrl_reg osc_ctrl; + union pllx_base_reg pllx_base; + union pllx_misc_reg pllx_misc; + union scratch3_reg scratch3; + u32 reg; + + /* enable JTAG & TBE */ + writel(CONFIG_CTL_TBE | CONFIG_CTL_JTAG, &pmt->pmt_cfg_ctl); + + /* Are we running where we're supposed to be? */ + asm volatile ( + "adr %0, wb_start;" /* reg: wb_start address */ + : "=r"(reg) /* output */ + /* no input, no clobber list */ + ); + + if (reg != AP20_WB_RUN_ADDRESS) + goto do_reset; + + /* Are we running with AVP? */ + if (readl(NV_PA_PG_UP_BASE + PG_UP_TAG_0) != PG_UP_TAG_AVP) + goto do_reset; + +#ifdef DEBUG_RESET_CORESIGHT + /* Assert CoreSight reset */ + reg = readl(&clkrst->crc_rst_dev[TEGRA_DEV_U]); + reg |= SWR_CSITE_RST; + writel(reg, &clkrst->crc_rst_dev[TEGRA_DEV_U]); +#endif + + /* TODO: Set the drive strength - maybe make this a board parameter? */ + osc_ctrl.word = readl(&clkrst->crc_osc_ctrl); + osc_ctrl.xofs = 4; + osc_ctrl.xoe = 1; + writel(osc_ctrl.word, &clkrst->crc_osc_ctrl); + + /* Power up the CPU complex if necessary */ + if (!(readl(&pmc->pmc_pwrgate_status) & PWRGATE_STATUS_CPU)) { + reg = PWRGATE_TOGGLE_PARTID_CPU | PWRGATE_TOGGLE_START; + writel(reg, &pmc->pmc_pwrgate_toggle); + while (!(readl(&pmc->pmc_pwrgate_status) & PWRGATE_STATUS_CPU)) + ; + } + + /* Remove the I/O clamps from the CPU power partition. */ + reg = readl(&pmc->pmc_remove_clamping); + reg |= CPU_CLMP; + writel(reg, &pmc->pmc_remove_clamping); + + reg = EVENT_ZERO_VAL_20 | EVENT_MSEC | EVENT_MODE_STOP; + writel(reg, &flow->halt_cop_events); + + /* Assert CPU complex reset */ + reg = readl(&clkrst->crc_rst_dev[TEGRA_DEV_L]); + reg |= CPU_RST; + writel(reg, &clkrst->crc_rst_dev[TEGRA_DEV_L]); + + /* Hold both CPUs in reset */ + reg = CPU_CMPLX_CPURESET0 | CPU_CMPLX_CPURESET1 | CPU_CMPLX_DERESET0 | + CPU_CMPLX_DERESET1 | CPU_CMPLX_DBGRESET0 | CPU_CMPLX_DBGRESET1; + writel(reg, &clkrst->crc_cpu_cmplx_set); + + /* Halt CPU1 at the flow controller for uni-processor configurations */ + writel(EVENT_MODE_STOP, &flow->halt_cpu1_events); + + /* + * Set the CPU reset vector. SCRATCH41 contains the physical + * address of the CPU-side restoration code. + */ + reg = readl(&pmc->pmc_scratch41); + writel(reg, EXCEP_VECTOR_CPU_RESET_VECTOR); + + /* Select CPU complex clock source */ + writel(CCLK_PLLP_BURST_POLICY, &clkrst->crc_cclk_brst_pol); + + /* Start the CPU0 clock and stop the CPU1 clock */ + reg = CPU_CMPLX_CPU_BRIDGE_CLKDIV_4 | CPU_CMPLX_CPU0_CLK_STP_RUN | + CPU_CMPLX_CPU1_CLK_STP_STOP; + writel(reg, &clkrst->crc_clk_cpu_cmplx); + + /* Enable the CPU complex clock */ + reg = readl(&clkrst->crc_clk_out_enb[TEGRA_DEV_L]); + reg |= CLK_ENB_CPU; + writel(reg, &clkrst->crc_clk_out_enb[TEGRA_DEV_L]); + + /* Make sure the resets were held for at least 2 microseconds */ + reg = readl(TIMER_USEC_CNTR); + while (readl(TIMER_USEC_CNTR) <= (reg + 2)) + ; + +#ifdef DEBUG_RESET_CORESIGHT + /* + * 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. + */ + reg = readl(&clkrst->crc_rst_dev[TEGRA_DEV_U]); + reg &= ~SWR_CSITE_RST; + writel(reg, &clkrst->crc_rst_dev[TEGRA_DEV_U]); +#endif + + /* Unlock the CPU CoreSight interfaces */ + reg = 0xC5ACCE55; + writel(reg, CSITE_CPU_DBG0_LAR); + writel(reg, CSITE_CPU_DBG1_LAR); + + /* + * Sample the microsecond timestamp again. This is the time we must + * use when returning from LP0 for PLL stabilization delays. + */ + reg = readl(TIMER_USEC_CNTR); + writel(reg, &pmc->pmc_scratch1); + + pllx_base.word = 0; + pllx_misc.word = 0; + scratch3.word = readl(&pmc->pmc_scratch3); + + /* Get the OSC. For 19.2 MHz, use 19 to make the calculations easier */ + reg = (readl(TIMER_USEC_CFG) & USEC_CFG_DIVISOR_MASK) + 1; + + /* + * According to the TRM, for 19.2MHz OSC, the USEC_DIVISOR is 0x5f, and + * USEC_DIVIDEND is 0x04. So, if USEC_DIVISOR > 26, OSC is 19.2 MHz. + * + * reg is used to calculate the pllx freq, which is used to determine if + * to set dccon or not. + */ + if (reg > 26) + reg = 19; + + /* PLLX_BASE.PLLX_DIVM */ + if (scratch3.pllx_base_divm == reg) + reg = 0; + else + reg = 1; + + /* PLLX_BASE.PLLX_DIVN */ + pllx_base.divn = scratch3.pllx_base_divn; + reg = scratch3.pllx_base_divn << reg; + + /* PLLX_BASE.PLLX_DIVP */ + pllx_base.divp = scratch3.pllx_base_divp; + reg = reg >> scratch3.pllx_base_divp; + + pllx_base.bypass = 1; + + /* PLLX_MISC_DCCON must be set for pllx frequency > 600 MHz. */ + if (reg > 600) + pllx_misc.dccon = 1; + + /* PLLX_MISC_LFCON */ + pllx_misc.lfcon = scratch3.pllx_misc_lfcon; + + /* PLLX_MISC_CPCON */ + pllx_misc.cpcon = scratch3.pllx_misc_cpcon; + + writel(pllx_misc.word, &clkrst->crc_pll_simple[SIMPLE_PLLX].pll_misc); + writel(pllx_base.word, &clkrst->crc_pll_simple[SIMPLE_PLLX].pll_base); + + pllx_base.enable = 1; + writel(pllx_base.word, &clkrst->crc_pll_simple[SIMPLE_PLLX].pll_base); + pllx_base.bypass = 0; + writel(pllx_base.word, &clkrst->crc_pll_simple[SIMPLE_PLLX].pll_base); + + writel(0, flow->halt_cpu_events); + + reg = CPU_CMPLX_CPURESET0 | CPU_CMPLX_DBGRESET0 | CPU_CMPLX_DERESET0; + writel(reg, &clkrst->crc_cpu_cmplx_clr); + + reg = PLLM_OUT1_RSTN_RESET_DISABLE | PLLM_OUT1_CLKEN_ENABLE | + PLLM_OUT1_RATIO_VAL_8; + writel(reg, &clkrst->crc_pll[CLOCK_ID_MEMORY].pll_out); + + reg = SCLK_SWAKE_FIQ_SRC_PLLM_OUT1 | SCLK_SWAKE_IRQ_SRC_PLLM_OUT1 | + SCLK_SWAKE_RUN_SRC_PLLM_OUT1 | SCLK_SWAKE_IDLE_SRC_PLLM_OUT1 | + SCLK_SYS_STATE_IDLE; + writel(reg, &clkrst->crc_sclk_brst_pol); + + /* avp_resume: no return after the write */ + reg = readl(&clkrst->crc_rst_dev[TEGRA_DEV_L]); + reg &= ~CPU_RST; + writel(reg, &clkrst->crc_rst_dev[TEGRA_DEV_L]); + + /* avp_halt: */ +avp_halt: + reg = EVENT_MODE_STOP | EVENT_JTAG; + writel(reg, flow->halt_cop_events); + goto avp_halt; + +do_reset: + /* + * Execution comes here if something goes wrong. The chip is reset and + * a cold boot is performed. + */ + writel(SWR_TRIG_SYS_RST, &clkrst->crc_rst_dev[TEGRA_DEV_L]); + goto do_reset; +} + +/* + * wb_end() is a dummy function, and must be directly following wb_start(), + * and is used to calculate the size of wb_start(). + */ +void wb_end(void) +{ +} diff --git a/arch/arm/cpu/armv7/tegra20/warmboot_avp.h b/arch/arm/cpu/armv7/tegra20/warmboot_avp.h new file mode 100644 index 0000000..4b71c07 --- /dev/null +++ b/arch/arm/cpu/armv7/tegra20/warmboot_avp.h @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2010, 2011 + * NVIDIA Corporation <www.nvidia.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _WARMBOOT_AVP_H_ +#define _WARMBOOT_AVP_H_ + +#define TEGRA_DEV_L 0 +#define TEGRA_DEV_H 1 +#define TEGRA_DEV_U 2 + +#define SIMPLE_PLLX (CLOCK_ID_XCPU - CLOCK_ID_FIRST_SIMPLE) +#define SIMPLE_PLLE (CLOCK_ID_EPCI - CLOCK_ID_FIRST_SIMPLE) + +#define TIMER_USEC_CNTR (NV_PA_TMRUS_BASE + 0) +#define TIMER_USEC_CFG (NV_PA_TMRUS_BASE + 4) + +#define USEC_CFG_DIVISOR_MASK 0xffff + +#define CONFIG_CTL_TBE (1 << 7) +#define CONFIG_CTL_JTAG (1 << 6) + +#define CPU_RST (1 << 0) +#define CLK_ENB_CPU (1 << 0) +#define SWR_TRIG_SYS_RST (1 << 2) +#define SWR_CSITE_RST (1 << 9) + +#define PWRGATE_STATUS_CPU (1 << 0) +#define PWRGATE_TOGGLE_PARTID_CPU (0 << 0) +#define PWRGATE_TOGGLE_START (1 << 8) + +#define CPU_CMPLX_CPU_BRIDGE_CLKDIV_4 (3 << 0) +#define CPU_CMPLX_CPU0_CLK_STP_STOP (1 << 8) +#define CPU_CMPLX_CPU0_CLK_STP_RUN (0 << 8) +#define CPU_CMPLX_CPU1_CLK_STP_STOP (1 << 9) +#define CPU_CMPLX_CPU1_CLK_STP_RUN (0 << 9) + +#define CPU_CMPLX_CPURESET0 (1 << 0) +#define CPU_CMPLX_CPURESET1 (1 << 1) +#define CPU_CMPLX_DERESET0 (1 << 4) +#define CPU_CMPLX_DERESET1 (1 << 5) +#define CPU_CMPLX_DBGRESET0 (1 << 12) +#define CPU_CMPLX_DBGRESET1 (1 << 13) + +#define PLLM_OUT1_RSTN_RESET_DISABLE (1 << 0) +#define PLLM_OUT1_CLKEN_ENABLE (1 << 1) +#define PLLM_OUT1_RATIO_VAL_8 (8 << 8) + +#define SCLK_SYS_STATE_IDLE (1 << 28) +#define SCLK_SWAKE_FIQ_SRC_PLLM_OUT1 (7 << 12) +#define SCLK_SWAKE_IRQ_SRC_PLLM_OUT1 (7 << 8) +#define SCLK_SWAKE_RUN_SRC_PLLM_OUT1 (7 << 4) +#define SCLK_SWAKE_IDLE_SRC_PLLM_OUT1 (7 << 0) + +#define EVENT_ZERO_VAL_20 (20 << 0) +#define EVENT_MSEC (1 << 24) +#define EVENT_JTAG (1 << 28) +#define EVENT_MODE_STOP (2 << 29) + +#define CCLK_PLLP_BURST_POLICY 0x20004444 + +#endif |