blob: 05e25cb32ce4cd50f13f2a79fe5c3c4b83eacbba [file] [log] [blame]
/*
* Copyright (C) 2009 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 <linux/ctype.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/root_dev.h>
#include <linux/types.h>
#include <linux/smp.h>
#include <linux/bmoca.h>
#include <linux/version.h>
#include <linux/serial_reg.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/compiler.h>
#include <linux/mtd/mtd.h>
#include <asm/bootinfo.h>
#include <asm/r4kcache.h>
#include <asm/traps.h>
#include <asm/cacheflush.h>
#include <asm/mipsregs.h>
#include <asm/hazards.h>
#include <asm/brcmstb/brcmstb.h>
#include <asm/fw/cfe/cfe_api.h>
#include <asm/fw/cfe/cfe_error.h>
#include <spaces.h>
int cfe_splashmem_present = 0;
unsigned long brcm_dram0_size_mb;
unsigned long brcm_dram1_size_mb;
unsigned long brcm_dram1_linux_mb;
unsigned long brcm_dram1_start = MEMC1_START;
unsigned long brcm_min_auth_region_size = 0x1000;
static u8 brcm_primary_macaddr[6] = { 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef };
unsigned long brcm_base_baud0 = BRCM_BASE_BAUD_STB; /* UPG UARTA */
unsigned long brcm_base_baud = BRCM_BASE_BAUD_STB; /* UPG_UART[BC] */
unsigned long __initdata cfe_seal;
unsigned long __initdata cfe_entry;
unsigned long __initdata cfe_handle;
#ifdef CONFIG_CMDLINE_BOOL
static char __initdata builtin_cmdline[COMMAND_LINE_SIZE] = CONFIG_CMDLINE;
#endif
/***********************************************************************
* CFE bootloader queries
***********************************************************************/
static int __init hex(char ch)
{
if (ch >= 'a' && ch <= 'f')
return ch-'a'+10;
if (ch >= '0' && ch <= '9')
return ch-'0';
if (ch >= 'A' && ch <= 'F')
return ch-'A'+10;
return -1;
}
static int __init hex16(const char *b)
{
int d0, d1;
d0 = hex(b[0]);
d1 = hex(b[1]);
if ((d0 == -1) || (d1 == -1))
return -1;
return (d0 << 4) | d1;
}
void __init cfe_die(char *fmt, ...)
{
char msg[128];
va_list ap;
int handle;
unsigned int count;
va_start(ap, fmt);
vsprintf(msg, fmt, ap);
strcat(msg, "\r\n");
if (cfe_seal != CFE_EPTSEAL)
goto no_cfe;
/* disable XKS01 so that CFE can access the registers */
#if defined(CONFIG_BMIPS4380)
__write_32bit_c0_register($22, 3,
__read_32bit_c0_register($22, 3) & ~BIT(12));
#elif defined(CONFIG_BMIPS5000)
__write_32bit_c0_register($22, 5,
__read_32bit_c0_register($22, 5) & ~BIT(8));
#endif
handle = cfe_getstdhandle(CFE_STDHANDLE_CONSOLE);
if (handle < 0)
goto no_cfe;
cfe_write(handle, msg, strlen(msg));
for (count = 0; count < 0x7fffffff; count++)
mb();
cfe_exit(0, 1);
while (1)
;
no_cfe:
/* probably won't print anywhere useful */
printk(KERN_ERR "%s", msg);
BUG();
va_end(ap);
}
static inline int __init parse_eth0_hwaddr(const char *buf, u8 *out)
{
int i, t;
u8 addr[6];
for (i = 0; i < 6; i++) {
t = hex16(buf);
if (t == -1)
return -1;
addr[i] = t;
buf += 3;
}
memcpy(out, addr, 6);
return 0;
}
static inline int __init parse_ulong(const char *buf, unsigned long *val)
{
char *endp;
unsigned long tmp;
tmp = simple_strtoul(buf, &endp, 0);
if (*endp == 0) {
*val = tmp;
return 0;
}
return -1;
}
static inline int __init parse_hex(const char *buf, unsigned long *val)
{
char *endp;
unsigned long tmp;
tmp = simple_strtoul(buf, &endp, 16);
if (*endp == 0) {
*val = tmp;
return 0;
}
return -1;
}
static inline int __init parse_boardname(const char *buf, void *slop)
{
int __maybe_unused len;
#if defined(CONFIG_BCM7405)
/* autodetect 97459 DOCSIS boards */
if (strstarts("BCM9745", buf))
brcm_docsis_platform = 1;
#endif
#if defined(CONFIG_BCM7420)
len = strlen(buf);
if (len > 2) {
if (buf[len - 2] == 'D' && buf[len - 1] == 'B') {
/* BCM97420xxDB = satellite board with MidRF MoCA */
brcm_moca_rf_band = MOCA_BAND_MIDRF;
} else {
/* BCM97420xx = cable/DOCSIS board with HighRF MoCA */
brcm_docsis_platform = 1;
}
}
if (strstarts(buf, "BCM93380SMS") ||
strstarts(buf, "BCM93380VMS") ||
strstarts(buf, "BCM97420_MOCA_GN")) {
brcm_enet0_force_ext_mii = 1;
brcm_enet_no_mdio = 1;
}
if (strstarts(buf, "BCM97420_MOCA_GN_SAT"))
brcm_moca_rf_band = MOCA_BAND_MIDRF;
#elif defined(CONFIG_BCM7425)
if (strstarts(buf, "BCM97425VMS")) {
brcm_enet0_force_ext_mii = 1;
brcm_enet_no_mdio = 1;
}
#elif defined(CONFIG_BCM7344)
/* 7344 is normally MidRF, but the 7418 variant might not be */
if (strstarts(buf, "BCM97418SAT"))
brcm_moca_rf_band = MOCA_BAND_MIDRF;
else if (strstarts(buf, "BCM97418"))
brcm_moca_rf_band = MOCA_BAND_HIGHRF;
#elif defined(CONFIG_BCM7408)
if (strstarts(buf, "BCM97408SAT"))
brcm_moca_rf_band = MOCA_BAND_MIDRF;
#endif
#if defined(CONFIG_BCM7405)
/* autodetect 97405-MSG board (special MII configuration) */
if (strstr(buf, "_MSG") != NULL)
brcm_enet_no_mdio = 1;
#endif
strcpy(brcm_cfe_boardname, buf);
return 0;
}
static inline int __init parse_cmdline(const char *buf, char *dst)
{
strlcpy(dst, buf, COMMAND_LINE_SIZE);
return 0;
}
static inline int __init parse_string(const char *buf, char *dst)
{
strlcpy(dst, buf, CFE_STRING_SIZE);
return 0;
}
static char __initdata cfe_buf[COMMAND_LINE_SIZE];
static void __init __maybe_unused cfe_read_configuration(void)
{
int fetched = 0;
printk(KERN_INFO "Fetching vars from bootloader... ");
if (cfe_seal != CFE_EPTSEAL) {
printk(KERN_CONT "none present, using defaults.\n");
return;
}
#define DPRINTK(...) do { } while (0)
/* #define DPRINTK(...) printk(__VA_ARGS__) */
#define FETCH(name, fn, arg) do { \
if (cfe_getenv(name, cfe_buf, COMMAND_LINE_SIZE) == CFE_OK) { \
DPRINTK("Fetch var '%s' = '%s'\n", name, cfe_buf); \
fn(cfe_buf, arg); \
fetched++; \
} else { \
DPRINTK("Could not fetch var '%s'\n", name); \
} \
} while (0)
FETCH("ETH0_HWADDR", parse_eth0_hwaddr, brcm_primary_macaddr);
FETCH("DRAM0_SIZE", parse_ulong, &brcm_dram0_size_mb);
FETCH("DRAM1_SIZE", parse_ulong, &brcm_dram1_size_mb);
FETCH("CFE_BOARDNAME", parse_boardname, NULL);
FETCH("BOOT_FLAGS", parse_cmdline, arcs_cmdline);
FETCH("LINUX_FFS_STARTAD", parse_hex, &brcm_mtd_rootfs_start);
FETCH("LINUX_FFS_SIZE", parse_hex, &brcm_mtd_rootfs_len);
FETCH("LINUX_PART_STARTAD", parse_hex, &brcm_mtd_kernel_start);
FETCH("LINUX_PART_SIZE", parse_hex, &brcm_mtd_kernel_len);
FETCH("OCAP_PART_STARTAD", parse_hex, &brcm_mtd_ocap_start);
FETCH("OCAP_PART_SIZE", parse_hex, &brcm_mtd_ocap_len);
FETCH("FLASH_SIZE", parse_ulong, &brcm_mtd_flash_size_mb);
FETCH("FLASH_TYPE", parse_string, brcm_mtd_flash_type);
printk(KERN_CONT "found %d vars.\n", fetched);
}
/***********************************************************************
* Early printk
***********************************************************************/
static unsigned long brcm_early_uart;
#define UART_REG(x) (brcm_early_uart + ((x) << 2))
#define BAUD 115200
static void __init init_port(void)
{
unsigned int divisor;
BDEV_WR(UART_REG(UART_LCR), 0x3); /* 8n1 */
BDEV_WR(UART_REG(UART_IER), 0); /* no interrupt */
BDEV_WR(UART_REG(UART_FCR), 0); /* no fifo */
BDEV_WR(UART_REG(UART_MCR), 0x3); /* DTR + RTS */
BDEV_SET(UART_REG(UART_LCR), UART_LCR_DLAB);
#if defined(CONFIG_BRCM_IKOS)
/* Reverse-engineer brcm_base_baud0 from the bootloader's setting */
divisor = (BDEV_RD(UART_REG(UART_DLM)) << 8) |
BDEV_RD(UART_REG(UART_DLL));
brcm_base_baud0 = divisor * BAUD;
#endif
divisor = (brcm_base_baud0 + BAUD/2) / BAUD;
BDEV_WR(UART_REG(UART_DLL), divisor & 0xff);
BDEV_WR(UART_REG(UART_DLM), (divisor >> 8) & 0xff);
BDEV_UNSET(UART_REG(UART_LCR), UART_LCR_DLAB);
}
void prom_putchar(char x)
{
while (!(BDEV_RD(UART_REG(UART_LSR)) & UART_LSR_THRE))
;
BDEV_WR(UART_REG(UART_TX), x);
}
static void __init brcm_setup_early_printk(void)
{
char *arg = strstr(arcs_cmdline, "console=");
int dev = CONFIG_BRCM_CONSOLE_DEVICE;
const unsigned long base[] = {
BCHP_UARTA_REG_START, BCHP_UARTB_REG_START,
#ifdef CONFIG_BRCM_HAS_UARTC
BCHP_UARTC_REG_START,
#endif
0, 0,
};
/*
* quick command line parse to pick the early printk console
* valid formats:
* console=ttyS0,115200
* console=0,115200
*/
while (arg && *arg != '\0' && *arg != ' ') {
if ((*arg >= '0') && (*arg <= '3')) {
dev = *arg - '0';
if (base[dev] == 0)
dev = 0;
break;
}
arg++;
}
brcm_early_uart = base[dev];
init_port();
}
#define SPLASH_TOKEN "splashmem="
static int parse_splash_mem(const char *arcs_cmdline,
unsigned long *start,
unsigned long *size)
{
char *ptr;
char *end;
char *endptr; /* local pointer to end of parsed string */
ptr = strstr(arcs_cmdline, SPLASH_TOKEN);
if (!ptr) {
printk(KERN_INFO "No splashmem present\n");
return -1;
}
end = strstr(ptr, "@memc1");
if (!end) {
printk(KERN_WARNING "Invalid mem bank for splashmem\n");
return -1;
}
ptr += sizeof(SPLASH_TOKEN);
*start = (unsigned long)memparse(ptr, &endptr);
ptr = strchr(endptr, '/');
if (!ptr) {
printk(KERN_WARNING "Invalid splashmem format\n");
return -1;
}
++ptr;
*size = (unsigned long)memparse(ptr, &endptr);
cfe_splashmem_present = 1;
return 0;
}
/***********************************************************************
* Main entry point
***********************************************************************/
void __init prom_init(void)
{
char *ptr;
cfe_init(cfe_handle, cfe_entry);
bchip_check_compat();
board_pinmux_setup();
bchip_mips_setup();
/* default to SATA (where available) or MTD rootfs */
#ifdef CONFIG_BRCM_HAS_SATA
ROOT_DEV = Root_SDA1;
#else
ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 0);
#endif
root_mountflags &= ~MS_RDONLY;
bchip_set_features();
#if defined(CONFIG_BRCM_IKOS_DEBUG)
strcpy(arcs_cmdline, "debug initcall_debug");
#elif !defined(CONFIG_BRCM_IKOS)
cfe_read_configuration();
#endif
brcm_setup_early_printk();
#ifdef CONFIG_CMDLINE_BOOL
#ifdef CONFIG_CMDLINE_OVERRIDE
strlcpy(arcs_cmdline, builtin_cmdline, COMMAND_LINE_SIZE);
#else
if (builtin_cmdline[0]) {
strlcat(arcs_cmdline, " ", COMMAND_LINE_SIZE);
strlcat(arcs_cmdline, builtin_cmdline, COMMAND_LINE_SIZE);
}
#endif
#endif
/* provide "ubiroot" alias to reduce typing */
if (strstr(arcs_cmdline, "ubiroot"))
strcat(arcs_cmdline, " ubi.mtd=rootfs rootfstype=ubifs "
"root=ubi0:rootfs");
ptr = strstr(arcs_cmdline, "memc1=");
if (ptr)
brcm_dram1_linux_mb = memparse(ptr + 6, &ptr) >> 20;
printk(KERN_INFO "Options: enet_en=%d enet0_mii=%d enet_no_mdio=%d "
"enet1_en=%d moca=%d\n",
brcm_enet_enabled, brcm_enet0_force_ext_mii,
brcm_enet_no_mdio, brcm_enet1_enabled, brcm_moca_enabled);
printk(KERN_INFO " sata=%d docsis=%d pci=%d pcie=%d smp=%d "
"usb=%d\n",
brcm_sata_enabled, brcm_docsis_platform, brcm_pci_enabled,
brcm_pcie_enabled, brcm_smp_enabled, brcm_usb_enabled);
bchip_early_setup();
board_get_ram_size(&brcm_dram0_size_mb, &brcm_dram1_size_mb);
do {
unsigned long dram0_mb = brcm_dram0_size_mb, mb;
mb = min(dram0_mb, BRCM_MAX_LOWER_MB);
dram0_mb -= mb;
add_memory_region(0, mb << 20, BOOT_MEM_RAM);
if (!dram0_mb)
break;
#ifdef CONFIG_BRCM_UPPER_MEMORY
mb = min(dram0_mb, BRCM_MAX_UPPER_MB);
dram0_mb -= mb;
brcm_upper_tlb_setup();
add_memory_region(UPPERMEM_START, mb << 20, BOOT_MEM_RAM);
if (!dram0_mb)
break;
#endif
#if defined(CONFIG_HIGHMEM)
add_memory_region(HIGHMEM_START, dram0_mb << 20, BOOT_MEM_RAM);
break;
#endif
/*
* We wound up here because the chip's architecture cannot
* make use of all MEMC0 RAM in Linux. i.e. no suitable
* HIGHMEM or upper memory options are supported by the CPU.
*
* But we can still report the excess memory as a "bonus"
* reserved (bmem) region, so the application can manage it.
*/
mb = brcm_dram0_size_mb - dram0_mb; /* Linux memory */
if (!brcm_dram1_size_mb && mb == 256) {
printk(KERN_INFO "MEMC0 split: %lu MB -> Linux; "
"%lu MB -> extra bmem\n", mb, dram0_mb);
brcm_dram1_size_mb = dram0_mb;
brcm_dram1_start = UPPERMEM_START;
}
} while (0);
#if defined(CONFIG_HIGHMEM) && defined(CONFIG_BRCM_HAS_1GB_MEMC1)
if (brcm_dram1_linux_mb > brcm_dram1_size_mb) {
printk(KERN_WARNING "warning: 'memc1=%luM' exceeds "
"available memory (%lu MB); ignoring\n",
brcm_dram1_linux_mb, brcm_dram1_size_mb);
brcm_dram1_linux_mb = 0;
} else if (brcm_dram1_linux_mb) {
/* Since the bootloader can only map the first 256M of memc1
* when it boots, if we get memc1= request from bootloader, we
* should try to pull the memory from the end to avoid crossing
* over the memory that is allocated for boot logo image by
* bootloader.
*/
unsigned long start_mb, start_b, size, splash_bound = 0;
if (0 == parse_splash_mem(arcs_cmdline, &splash_bound, &size)) {
splash_bound += size;
}
start_mb = brcm_dram1_size_mb - brcm_dram1_linux_mb;
start_b = start_mb << 20;
if (splash_bound > start_b) {
unsigned long orig_dram1 = brcm_dram1_linux_mb;
start_mb = (splash_bound + 0x000FFFFF) >> 20;
start_b = start_mb << 20;
brcm_dram1_linux_mb = brcm_dram1_size_mb - start_mb;
printk(KERN_WARNING "warning: 'memc1=%luM' starts "
" before splash memory bound (0x%lx);"
" adjusting to (memc1=%luM)\n",
orig_dram1, splash_bound, brcm_dram1_linux_mb);
}
printk(KERN_INFO "memc1: adding %luMB at %luMB "
"(0x%08lx@0x%08lx)",
brcm_dram1_linux_mb, (MEMC1_START >> 20) + start_mb,
brcm_dram1_linux_mb << 20, MEMC1_START + start_b);
add_memory_region(MEMC1_START + start_b,
brcm_dram1_linux_mb << 20,
BOOT_MEM_RAM);
}
#else
if (brcm_dram1_linux_mb) {
printk(KERN_WARNING "warning: MEMC1 is not available on this "
"system; ignoring\n");
brcm_dram1_linux_mb = 0;
}
#endif
#ifdef CONFIG_SMP
register_smp_ops(&brcmstb_smp_ops);
#endif
}
/***********************************************************************
* Vector relocation for NMI and SMP TP1 boot
***********************************************************************/
extern void nmi_exception_handler(struct pt_regs *regs);
void (*brcm_nmi_handler)(struct pt_regs *) = &nmi_exception_handler;
void brcm_set_nmi_handler(void (*fn)(struct pt_regs *))
{
brcm_nmi_handler = fn;
}
EXPORT_SYMBOL(brcm_set_nmi_handler);
void __cpuinit brcm_wr_vec(unsigned long dst, char *start, char *end)
{
memcpy((void *)dst, start, end - start);
dma_cache_wback((unsigned long)start, end - start);
local_flush_icache_range(dst, dst + (end - start));
instruction_hazard();
}
static inline void __cpuinit brcm_nmi_handler_setup(void)
{
brcm_wr_vec(BRCM_NMI_VEC, brcm_reset_nmi_vec, brcm_reset_nmi_vec_end);
brcm_wr_vec(BRCM_WARM_RESTART_VEC, brcm_tp1_int_vec,
brcm_tp1_int_vec_end);
}
unsigned long __cpuinit brcm_setup_ebase(void)
{
unsigned long ebase = CAC_BASE;
#if defined(CONFIG_BCM7468) || defined(CONFIG_BCM7550)
/* brain-dead */
#elif defined(CONFIG_BMIPS3300) || defined(CONFIG_BMIPS4380)
/*
* Exception vector configuration on BMIPS4380:
*
* 8000_0000 - new reset/NMI vector (was: bfc0_0000)
* 8000_0400 - new !BEV exception base (was: 8000_0000)
*
* The reset/NMI vector can only be adjusted in 1MB increments, so
* we put it at 8000_0000 and then move the runtime exception vectors
* up a little bit.
*
* The initial reset/NMI vector for TP1 is at a000_0000 because the
* BMIPS4380 I$ comes up in an undefined state, but it is almost
* immediately moved down to kseg0.
*
* This is nearly identical on BMIPS3300, except for the fact that
* BMIPS3300 only has a single thread.
*/
unsigned long cbr = BMIPS_GET_CBR();
DEV_WR_RB(cbr + BMIPS_RELO_VECTOR_CONTROL_0, 0x80080800);
#ifdef CONFIG_BMIPS4380
DEV_WR_RB(cbr + BMIPS_RELO_VECTOR_CONTROL_1, 0xa0080800);
#endif
ebase = 0x80000400;
board_nmi_handler_setup = &brcm_nmi_handler_setup;
#elif defined(CONFIG_BMIPS5000)
/*
* BMIPS5000 is similar to BMIPS4380, but it uses different
* configuration registers with different semantics:
*
* 8000_0000 - new reset/NMI vector (was: bfc0_0000)
* 8000_1000 - new !BEV exception base (was: 8000_0000)
*
* The initial reset/NMI vector for TP1 is at a000_0000 because
* CP0 CONFIG comes up in an undefined state, but it is almost
* immediately moved down to kseg0.
*/
ebase = 0x80001000;
write_c0_brcm_bootvec(0xa0088008);
write_c0_ebase(ebase);
board_nmi_handler_setup = &brcm_nmi_handler_setup;
#endif
return ebase;
}
/***********************************************************************
* Miscellaneous utility functions
***********************************************************************/
static char brcm_system_type[64];
const char *get_system_type(void)
{
u32 class = BRCM_CHIP_ID();
if (class >> 16 == 0)
class >>= 8;
else
class >>= 12;
snprintf(brcm_system_type, 64, "BCM%04x%02X %s platform",
BRCM_CHIP_ID(), BRCM_CHIP_REV() + 0xa0,
class == 0x35 ? "DTV" :
(class == 0x76 ? "DVD" : "STB"));
return (const char *)brcm_system_type;
}
void __init prom_free_prom_memory(void) {}
int brcm_alloc_macaddr(u8 *buf)
{
memcpy(buf, brcm_primary_macaddr, ETH_ALEN);
brcm_primary_macaddr[4]++;
return 0;
}
EXPORT_SYMBOL(brcm_alloc_macaddr);