x1000: SPL refactoring
This streamlines the boot code a bit and reduces target specific boilerplate. The clock init hack used by the bootloader has been "standardized" and works for the main Rockbox binary now, so you can boot rockbox.bin over USB without special hacks. Change-Id: I7c1fac37df5a45873583ce6818eaedb9f71a782b
This commit is contained in:
parent
65aa9ce570
commit
0e1a90ea1d
10 changed files with 612 additions and 379 deletions
|
@ -37,7 +37,7 @@
|
|||
#include "loader_strerror.h"
|
||||
#include "version.h"
|
||||
#include "installer-fiiom3k.h"
|
||||
#include "spl-x1000.h"
|
||||
#include "boot-x1000.h"
|
||||
#include "x1000/cpm.h"
|
||||
|
||||
/* Load address where the binary needs to be placed */
|
||||
|
@ -331,17 +331,8 @@ void main(void)
|
|||
{
|
||||
bool recovery_mode = false;
|
||||
|
||||
/* This hack is needed because when USB booting, we cannot initialize
|
||||
* clocks in the SPL -- it may break the mask ROM's USB code. So if the
|
||||
* SPL has not already initialized the clocks, we need to do that now.
|
||||
*
|
||||
* Also use this as a sign that we should enter the recovery menu since
|
||||
* this is probably the expected result if the user is USB booting...
|
||||
*/
|
||||
if(jz_readf(CPM_MPCR, ENABLE)) {
|
||||
spl_handle_pre_boot(0);
|
||||
if(get_boot_flag(BOOT_FLAG_USB_BOOT))
|
||||
recovery_mode = true;
|
||||
}
|
||||
|
||||
system_init();
|
||||
core_allocator_init();
|
||||
|
|
91
firmware/target/mips/ingenic_x1000/boot-x1000.h
Normal file
91
firmware/target/mips/ingenic_x1000/boot-x1000.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __BOOT_X1000_H__
|
||||
#define __BOOT_X1000_H__
|
||||
|
||||
#include "x1000/cpm.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
enum {
|
||||
BOOT_OPTION_ROCKBOX = 0,
|
||||
BOOT_OPTION_OFW_PLAYER,
|
||||
BOOT_OPTION_OFW_RECOVERY,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* 3 bits to store the boot option selected by the SPL */
|
||||
BOOT_OPTION_MASK = 0x7,
|
||||
BOOT_OPTION_SHIFT = 0,
|
||||
|
||||
/* Set after running clk_init() and setting up system clocks */
|
||||
BOOT_FLAG_CLK_INIT = (1 << 31),
|
||||
|
||||
/* Set by the SPL if it was loaded over USB boot */
|
||||
BOOT_FLAG_USB_BOOT = (1 << 30),
|
||||
};
|
||||
|
||||
/* Note: these functions are inlined to minimize SPL code size.
|
||||
* They are private to the X1000 early boot code anyway... */
|
||||
|
||||
static inline void cpm_scratch_set(uint32_t value)
|
||||
{
|
||||
/* TODO: see if this holds its state over a WDT reset */
|
||||
REG_CPM_SCRATCH_PROT = 0x5a5a;
|
||||
REG_CPM_SCRATCH = value;
|
||||
REG_CPM_SCRATCH_PROT = 0xa5a5;
|
||||
}
|
||||
|
||||
static inline void init_boot_flags(void)
|
||||
{
|
||||
cpm_scratch_set(0);
|
||||
}
|
||||
|
||||
static inline bool get_boot_flag(uint32_t bit)
|
||||
{
|
||||
return (REG_CPM_SCRATCH & bit) != 0;
|
||||
}
|
||||
|
||||
static inline void set_boot_flag(uint32_t bit)
|
||||
{
|
||||
cpm_scratch_set(REG_CPM_SCRATCH | bit);
|
||||
}
|
||||
|
||||
static inline void clr_boot_flag(uint32_t bit)
|
||||
{
|
||||
cpm_scratch_set(REG_CPM_SCRATCH & ~bit);
|
||||
}
|
||||
|
||||
static inline void set_boot_option(int opt)
|
||||
{
|
||||
uint32_t r = REG_CPM_SCRATCH;
|
||||
r &= ~(BOOT_OPTION_MASK << BOOT_OPTION_SHIFT);
|
||||
r |= (opt & BOOT_OPTION_MASK) << BOOT_OPTION_SHIFT;
|
||||
cpm_scratch_set(r);
|
||||
}
|
||||
|
||||
static inline int get_boot_option(void)
|
||||
{
|
||||
uint32_t r = REG_CPM_SCRATCH;
|
||||
return (r >> BOOT_OPTION_SHIFT) & BOOT_OPTION_MASK;
|
||||
}
|
||||
|
||||
#endif /* __BOOT_X1000_H__ */
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "system.h"
|
||||
#include "clk-x1000.h"
|
||||
#include "boot-x1000.h"
|
||||
#include "x1000/cpm.h"
|
||||
#include "x1000/msc.h"
|
||||
#include "x1000/aic.h"
|
||||
|
@ -225,7 +226,89 @@ const char* clk_get_name(x1000_clk_t clk)
|
|||
}
|
||||
}
|
||||
|
||||
/* At present we've only got 24 MHz targets, and they are all using
|
||||
* the same "standard" configuration. */
|
||||
#if X1000_EXCLK_FREQ == 24000000
|
||||
void clk_init_early(void)
|
||||
{
|
||||
jz_writef(CPM_MPCR, ENABLE(0));
|
||||
while(jz_readf(CPM_MPCR, ON));
|
||||
|
||||
/* 24 MHz * 25 = 600 MHz */
|
||||
jz_writef(CPM_MPCR, BS(1), PLLM(25 - 1), PLLN(0), PLLOD(0), ENABLE(1));
|
||||
while(jz_readf(CPM_MPCR, ON) == 0);
|
||||
|
||||
/* 600 MHz / 3 = 200 MHz */
|
||||
clk_set_ddr(X1000_CLK_MPLL, 3);
|
||||
}
|
||||
|
||||
void clk_init(void)
|
||||
{
|
||||
/* make sure we only initialize the clocks once */
|
||||
if(get_boot_flag(BOOT_FLAG_CLK_INIT))
|
||||
return;
|
||||
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(EXCLK) |
|
||||
CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(SCLK_A) |
|
||||
CLKMUX_AHB2(SCLK_A));
|
||||
clk_set_ccr_div(CLKDIV_CPU(1) |
|
||||
CLKDIV_L2(1) |
|
||||
CLKDIV_AHB0(1) |
|
||||
CLKDIV_AHB2(1) |
|
||||
CLKDIV_PCLK(1));
|
||||
|
||||
jz_writef(CPM_APCR, ENABLE(0));
|
||||
while(jz_readf(CPM_APCR, ON));
|
||||
|
||||
/* 24 MHz * 42 = 1008 MHz */
|
||||
jz_writef(CPM_APCR, BS(1), PLLM(42 - 1), PLLN(0), PLLOD(0), ENABLE(1));
|
||||
while(jz_readf(CPM_APCR, ON) == 0);
|
||||
|
||||
#if defined(FIIO_M3K)
|
||||
/* TODO: Allow targets to define their clock frequencies in their config,
|
||||
* instead of having this be a random special case. */
|
||||
if(get_boot_option() == BOOT_OPTION_ROCKBOX) {
|
||||
clk_set_ccr_div(CLKDIV_CPU(1) | /* 1008 MHz */
|
||||
CLKDIV_L2(2) | /* 504 MHz */
|
||||
CLKDIV_AHB0(5) | /* 201.6 MHz */
|
||||
CLKDIV_AHB2(5) | /* 201.6 MHz */
|
||||
CLKDIV_PCLK(10)); /* 100.8 MHz */
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(APLL) |
|
||||
CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(SCLK_A) |
|
||||
CLKMUX_AHB2(SCLK_A));
|
||||
|
||||
/* DDR to 201.6 MHz */
|
||||
clk_set_ddr(X1000_CLK_SCLK_A, 5);
|
||||
|
||||
/* Disable MPLL */
|
||||
jz_writef(CPM_MPCR, ENABLE(0));
|
||||
while(jz_readf(CPM_MPCR, ON));
|
||||
} else {
|
||||
#endif
|
||||
clk_set_ccr_div(CLKDIV_CPU(1) | /* 1008 MHz */
|
||||
CLKDIV_L2(2) | /* 504 MHz */
|
||||
CLKDIV_AHB0(3) | /* 200 MHz */
|
||||
CLKDIV_AHB2(3) | /* 200 MHz */
|
||||
CLKDIV_PCLK(6)); /* 100 MHz */
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(APLL) |
|
||||
CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(MPLL) |
|
||||
CLKMUX_AHB2(MPLL));
|
||||
#if defined(FIIO_M3K)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* mark that clocks have been initialized */
|
||||
set_boot_flag(BOOT_FLAG_CLK_INIT);
|
||||
}
|
||||
#else
|
||||
# error "please write a new clk_init() for this EXCLK frequency"
|
||||
#endif
|
||||
|
||||
#define CCR_MUX_BITS jz_orm(CPM_CCR, SEL_SRC, SEL_CPLL, SEL_H0PLL, SEL_H2PLL)
|
||||
#define CCR_DIV_BITS jz_orm(CPM_CCR, CDIV, L2DIV, H0DIV, H2DIV, PDIV)
|
||||
#define CSR_MUX_BITS jz_orm(CPM_CSR, SRC_MUX, CPU_MUX, AHB0_MUX, AHB2_MUX)
|
||||
#define CSR_DIV_BITS jz_orm(CPM_CSR, H2DIV_BUSY, H0DIV_BUSY, CDIV_BUSY)
|
||||
|
||||
|
@ -241,12 +324,14 @@ void clk_set_ccr_mux(uint32_t muxbits)
|
|||
while((REG_CPM_CSR & CSR_MUX_BITS) != CSR_MUX_BITS);
|
||||
}
|
||||
|
||||
void clk_set_ccr_div(int cpu, int l2, int ahb0, int ahb2, int pclk)
|
||||
void clk_set_ccr_div(uint32_t divbits)
|
||||
{
|
||||
/* Set new divider configuration */
|
||||
jz_writef(CPM_CCR, CDIV(cpu - 1), L2DIV(l2 - 1),
|
||||
H0DIV(ahb0 - 1), H2DIV(ahb2 - 1), PDIV(pclk - 1),
|
||||
CE_CPU(1), CE_AHB0(1), CE_AHB2(1));
|
||||
uint32_t reg = REG_CPM_CCR;
|
||||
reg &= ~CCR_DIV_BITS;
|
||||
reg |= divbits & CCR_DIV_BITS;
|
||||
reg |= jz_orm(CPM_CCR, CE_CPU, CE_AHB0, CE_AHB2);
|
||||
REG_CPM_CCR = reg;
|
||||
|
||||
/* Wait until divider change completes */
|
||||
while(REG_CPM_CSR & CSR_DIV_BITS);
|
||||
|
|
|
@ -31,6 +31,13 @@
|
|||
#define CLKMUX_AHB0(x) jz_orf(CPM_CCR, SEL_H0PLL_V(x))
|
||||
#define CLKMUX_AHB2(x) jz_orf(CPM_CCR, SEL_H2PLL_V(x))
|
||||
|
||||
/* Arguments to clk_set_ccr_div() */
|
||||
#define CLKDIV_CPU(x) jz_orf(CPM_CCR, CDIV((x) - 1))
|
||||
#define CLKDIV_L2(x) jz_orf(CPM_CCR, L2DIV((x) - 1))
|
||||
#define CLKDIV_AHB0(x) jz_orf(CPM_CCR, H0DIV((x) - 1))
|
||||
#define CLKDIV_AHB2(x) jz_orf(CPM_CCR, H2DIV((x) - 1))
|
||||
#define CLKDIV_PCLK(x) jz_orf(CPM_CCR, PDIV((x) - 1))
|
||||
|
||||
typedef enum x1000_clk_t {
|
||||
X1000_CLK_EXCLK,
|
||||
X1000_CLK_APLL,
|
||||
|
@ -59,11 +66,15 @@ extern uint32_t clk_get(x1000_clk_t clk);
|
|||
/* Get the name of a clock for debug purposes */
|
||||
extern const char* clk_get_name(x1000_clk_t clk);
|
||||
|
||||
/* Clock initialization */
|
||||
extern void clk_init_early(void);
|
||||
extern void clk_init(void);
|
||||
|
||||
/* Sets system clock multiplexers */
|
||||
extern void clk_set_ccr_mux(uint32_t muxbits);
|
||||
|
||||
/* Sets system clock dividers */
|
||||
extern void clk_set_ccr_div(int cpu, int l2, int ahb0, int ahb2, int pclk);
|
||||
extern void clk_set_ccr_div(uint32_t divbits);
|
||||
|
||||
/* Sets DDR clock source and divider */
|
||||
extern void clk_set_ddr(x1000_clk_t src, uint32_t div);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
.text
|
||||
.extern main
|
||||
.extern system_early_init
|
||||
.global _start
|
||||
|
||||
.set push
|
||||
|
@ -92,6 +93,8 @@ _irqstack_loop:
|
|||
sw t1, -4(t0)
|
||||
|
||||
/* Jump to C code */
|
||||
jal system_early_init
|
||||
nop
|
||||
j main
|
||||
nop
|
||||
|
||||
|
|
|
@ -20,306 +20,97 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include "system.h"
|
||||
#include "ucl_decompress.h"
|
||||
#include "clk-x1000.h"
|
||||
#include "spl-x1000.h"
|
||||
#include "gpio-x1000.h"
|
||||
#include "clk-x1000.h"
|
||||
#include "nand-x1000.h"
|
||||
#include "x1000/cpm.h"
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Available boot options */
|
||||
#define BOOTOPTION_ROCKBOX 0
|
||||
#define BOOTOPTION_ORIG_FW 1
|
||||
#define BOOTOPTION_RECOVERY 2
|
||||
#define CMDLINE_COMMON \
|
||||
"mem=64M@0x0 no_console_suspend console=ttyS2,115200n8 lpj=5009408 ip=off"
|
||||
#define CMDLINE_NORMAL \
|
||||
" init=/linuxrc ubi.mtd=3 root=ubi0:rootfs ubi.mtd=4 rootfstype=ubifs rw loglevel=8"
|
||||
|
||||
/* Boot select button state must remain stable for this duration
|
||||
* before the choice will be accepted. Currently 100ms.
|
||||
*/
|
||||
#define BTN_STABLE_TIME (100 * (X1000_EXCLK_FREQ / 4000))
|
||||
static int dualboot_setup(void)
|
||||
{
|
||||
spl_dualboot_init_clocktree();
|
||||
spl_dualboot_init_uart2();
|
||||
|
||||
static const char normal_cmdline[] = "mem=64M@0x0\
|
||||
no_console_suspend\
|
||||
console=ttyS2,115200n8\
|
||||
lpj=5009408\
|
||||
ip=off\
|
||||
init=/linuxrc\
|
||||
ubi.mtd=3\
|
||||
root=ubi0:rootfs\
|
||||
ubi.mtd=4\
|
||||
rootfstype=ubifs\
|
||||
rw\
|
||||
loglevel=8";
|
||||
|
||||
static const char recovery_cmdline[] = "mem=64M@0x0\
|
||||
no_console_suspend\
|
||||
console=ttyS2,115200n8\
|
||||
lpj=5009408\
|
||||
ip=off";
|
||||
|
||||
/* Entry point function type, defined to be Linux compatible. */
|
||||
typedef void(*entry_fn)(int, char**, int, int);
|
||||
|
||||
struct spl_boot_option {
|
||||
uint32_t nand_addr;
|
||||
uint32_t nand_size;
|
||||
uint32_t load_addr;
|
||||
uint32_t exec_addr;
|
||||
const char* cmdline; /* for Linux */
|
||||
};
|
||||
/* load PDMA MCU firmware */
|
||||
jz_writef(CPM_CLKGR, PDMA(0));
|
||||
return spl_storage_read(0x4000, 0x2000, (void*)0xb3422000);
|
||||
}
|
||||
|
||||
const struct spl_boot_option spl_boot_options[] = {
|
||||
{
|
||||
/* Rockbox: the first unused NAND page is 26 KiB in, and the
|
||||
* remainder of the block is unused, giving us 102 KiB to use.
|
||||
*/
|
||||
.nand_addr = 0x6800,
|
||||
.nand_size = 0x19800,
|
||||
.load_addr = X1000_DRAM_END - 0x19800,
|
||||
[BOOT_OPTION_ROCKBOX] = {
|
||||
.storage_addr = 0x6800,
|
||||
.storage_size = 102 * 1024,
|
||||
.load_addr = X1000_DRAM_BASE,
|
||||
.exec_addr = X1000_DRAM_BASE,
|
||||
.cmdline = NULL,
|
||||
.flags = BOOTFLAG_UCLPACK,
|
||||
},
|
||||
{
|
||||
/* Original firmware */
|
||||
.nand_addr = 0x20000,
|
||||
.nand_size = 0x400000,
|
||||
[BOOT_OPTION_OFW_PLAYER] = {
|
||||
.storage_addr = 0x20000,
|
||||
.storage_size = 4 * 1024 * 1024,
|
||||
.load_addr = 0x80efffc0,
|
||||
.exec_addr = 0x80f00000,
|
||||
.cmdline = normal_cmdline,
|
||||
.cmdline = CMDLINE_COMMON CMDLINE_NORMAL,
|
||||
.cmdline_addr = 0x80004000,
|
||||
.setup = dualboot_setup,
|
||||
},
|
||||
{
|
||||
/* Recovery image */
|
||||
.nand_addr = 0x420000,
|
||||
.nand_size = 0x500000,
|
||||
[BOOT_OPTION_OFW_RECOVERY] = {
|
||||
.storage_addr = 0x420000,
|
||||
.storage_size = 5 * 1024 * 1024,
|
||||
.load_addr = 0x80efffc0,
|
||||
.exec_addr = 0x80f00000,
|
||||
.cmdline = recovery_cmdline,
|
||||
.cmdline = CMDLINE_COMMON,
|
||||
.cmdline_addr = 0x80004000,
|
||||
.setup = dualboot_setup,
|
||||
},
|
||||
};
|
||||
|
||||
void spl_error(void)
|
||||
{
|
||||
const uint32_t pin = (1 << 24);
|
||||
|
||||
/* Turn on button light */
|
||||
jz_clr(GPIO_INT(GPIO_C), pin);
|
||||
jz_set(GPIO_MSK(GPIO_C), pin);
|
||||
jz_clr(GPIO_PAT1(GPIO_C), pin);
|
||||
jz_set(GPIO_PAT0(GPIO_C), pin);
|
||||
|
||||
while(1) {
|
||||
/* Turn it off */
|
||||
mdelay(100);
|
||||
jz_set(GPIO_PAT0(GPIO_C), pin);
|
||||
|
||||
/* Turn it on */
|
||||
mdelay(100);
|
||||
jz_clr(GPIO_PAT0(GPIO_C), pin);
|
||||
}
|
||||
}
|
||||
|
||||
nand_drv* alloc_nand_drv(uint32_t laddr, uint32_t lsize)
|
||||
{
|
||||
size_t need_size = sizeof(nand_drv) +
|
||||
NAND_DRV_SCRATCHSIZE +
|
||||
NAND_DRV_MAXPAGESIZE;
|
||||
|
||||
/* find a hole to keep the buffers */
|
||||
uintptr_t addr;
|
||||
if(X1000_SDRAM_BASE + need_size <= laddr)
|
||||
addr = X1000_SDRAM_BASE;
|
||||
else
|
||||
addr = CACHEALIGN_UP(X1000_SDRAM_BASE + laddr + lsize);
|
||||
|
||||
uint8_t* page_buf = (uint8_t*)addr;
|
||||
uint8_t* scratch_buf = page_buf + NAND_DRV_MAXPAGESIZE;
|
||||
nand_drv* drv = (nand_drv*)(scratch_buf + NAND_DRV_SCRATCHSIZE);
|
||||
|
||||
drv->page_buf = page_buf;
|
||||
drv->scratch_buf = scratch_buf;
|
||||
drv->refcount = 0;
|
||||
return drv;
|
||||
}
|
||||
|
||||
void spl_target_boot(void)
|
||||
{
|
||||
int opt_index = spl_get_boot_option();
|
||||
const struct spl_boot_option* opt = &spl_boot_options[opt_index];
|
||||
uint8_t* load_addr = (uint8_t*)opt->load_addr;
|
||||
|
||||
/* Clock setup etc. */
|
||||
spl_handle_pre_boot(opt_index);
|
||||
|
||||
/* Since the GPIO refactor, the SFC driver no longer assigns its own pins.
|
||||
* We don't want to call gpio_init(), to keep code size down. Assign them
|
||||
* here manually, we shouldn't need any other pins. */
|
||||
gpioz_configure(GPIO_A, 0x3f << 26, GPIOF_DEVICE(1));
|
||||
|
||||
/* Open NAND chip */
|
||||
nand_drv* ndrv = alloc_nand_drv(opt->load_addr, opt->nand_size);
|
||||
int rc = nand_open(ndrv);
|
||||
if(rc)
|
||||
spl_error();
|
||||
|
||||
/* For OF only: load DMA coprocessor's firmware from flash */
|
||||
if(opt_index != BOOTOPTION_ROCKBOX) {
|
||||
rc = nand_read_bytes(ndrv, 0x4000, 0x2000, (uint8_t*)0xb3422000);
|
||||
if(rc)
|
||||
goto nand_err;
|
||||
}
|
||||
|
||||
/* Read the firmware */
|
||||
rc = nand_read_bytes(ndrv, opt->nand_addr, opt->nand_size, load_addr);
|
||||
if(rc)
|
||||
goto nand_err;
|
||||
|
||||
/* Rockbox doesn't need the NAND; for the OF, we should leave it open */
|
||||
if(opt_index == BOOTOPTION_ROCKBOX)
|
||||
nand_close(ndrv);
|
||||
|
||||
/* Kernel arguments pointer, for Linux only */
|
||||
char** kargv = (char**)0x80004000;
|
||||
|
||||
if(!opt->cmdline) {
|
||||
/* Uncompress the rockbox bootloader */
|
||||
uint32_t out_size = X1000_DRAM_END - opt->exec_addr;
|
||||
int rc = ucl_unpack(load_addr, opt->nand_size,
|
||||
(uint8_t*)opt->exec_addr, &out_size);
|
||||
if(rc != UCL_E_OK)
|
||||
spl_error();
|
||||
} else {
|
||||
/* Set kernel args */
|
||||
kargv[0] = 0;
|
||||
kargv[1] = (char*)opt->cmdline;
|
||||
}
|
||||
|
||||
entry_fn entry = (entry_fn)opt->exec_addr;
|
||||
commit_discard_idcache();
|
||||
entry(2, kargv, 0, 0);
|
||||
__builtin_unreachable();
|
||||
|
||||
nand_err:
|
||||
nand_close(ndrv);
|
||||
spl_error();
|
||||
}
|
||||
|
||||
int spl_get_boot_option(void)
|
||||
{
|
||||
const uint32_t pinmask = (1 << 17) | (1 << 19);
|
||||
/* Button debounce time in OST clock cycles */
|
||||
const uint32_t btn_stable_time = 100 * (X1000_EXCLK_FREQ / 4000);
|
||||
|
||||
uint32_t pin = 1, lastpin = 0;
|
||||
/* Buttons to poll */
|
||||
const unsigned port = GPIO_A;
|
||||
const uint32_t recov_pin = (1 << 19); /* Volume Up */
|
||||
const uint32_t orig_fw_pin = (1 << 17); /* Play */
|
||||
|
||||
uint32_t pin = -1, lastpin = 0;
|
||||
uint32_t deadline = 0;
|
||||
/* Iteration count guards against unlikely case of broken buttons
|
||||
* which never stabilize; if this occurs, we always boot Rockbox. */
|
||||
int iter_count = 0;
|
||||
const int max_iter_count = 30;
|
||||
int iter_count = 30; /* to avoid an infinite loop */
|
||||
|
||||
/* Configure the button GPIOs as inputs */
|
||||
gpioz_configure(GPIO_A, pinmask, GPIOF_INPUT);
|
||||
/* set GPIOs to input state */
|
||||
gpioz_configure(port, recov_pin|orig_fw_pin, GPIOF_INPUT);
|
||||
|
||||
/* Poll the pins for a short duration to detect a keypress */
|
||||
/* Poll until we get a stable reading */
|
||||
do {
|
||||
lastpin = pin;
|
||||
pin = ~REG_GPIO_PIN(GPIO_A) & pinmask;
|
||||
pin = ~REG_GPIO_PIN(port) & (recov_pin|orig_fw_pin);
|
||||
if(pin != lastpin) {
|
||||
/* This will always be set on the first iteration */
|
||||
deadline = __ost_read32() + BTN_STABLE_TIME;
|
||||
iter_count += 1;
|
||||
deadline = __ost_read32() + btn_stable_time;
|
||||
iter_count -= 1;
|
||||
}
|
||||
} while(iter_count < max_iter_count && __ost_read32() < deadline);
|
||||
} while(iter_count > 0 && __ost_read32() < deadline);
|
||||
|
||||
if(iter_count < max_iter_count && (pin & (1 << 17))) {
|
||||
if(pin & (1 << 19))
|
||||
return BOOTOPTION_RECOVERY; /* Play+Volume Up */
|
||||
if(iter_count >= 0 && (pin & orig_fw_pin)) {
|
||||
if(pin & recov_pin)
|
||||
return BOOT_OPTION_OFW_RECOVERY;
|
||||
else
|
||||
return BOOTOPTION_ORIG_FW; /* Play */
|
||||
} else {
|
||||
return BOOTOPTION_ROCKBOX; /* Volume Up or no buttons */
|
||||
return BOOT_OPTION_OFW_PLAYER;
|
||||
}
|
||||
|
||||
return BOOT_OPTION_ROCKBOX;
|
||||
}
|
||||
|
||||
void spl_handle_pre_boot(int bootopt)
|
||||
void spl_error(void)
|
||||
{
|
||||
/* Move system to EXCLK so we can manipulate the PLLs */
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(EXCLK) | CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(SCLK_A) | CLKMUX_AHB2(SCLK_A));
|
||||
clk_set_ccr_div(1, 1, 1, 1, 1);
|
||||
|
||||
/* Enable APLL @ 1008 MHz (24 MHz EXCLK * 42 = 1008 MHz) */
|
||||
jz_writef(CPM_APCR, BS(1), PLLM(41), PLLN(0), PLLOD(0), ENABLE(1));
|
||||
while(jz_readf(CPM_APCR, ON) == 0);
|
||||
|
||||
/* System clock setup -- common to Rockbox and FiiO firmware
|
||||
* ----
|
||||
* CPU at 1 GHz, L2 cache at 500 MHz
|
||||
* AHB0 and AHB2 at 200 MHz
|
||||
* PCLK at 100 MHz
|
||||
* DDR at 200 MHz
|
||||
*/
|
||||
|
||||
if(bootopt == BOOTOPTION_ROCKBOX) {
|
||||
/* We don't use MPLL in Rockbox, so run everything from APLL. */
|
||||
clk_set_ccr_div(1, 2, 5, 5, 10);
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(APLL) | CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(SCLK_A) | CLKMUX_AHB2(SCLK_A));
|
||||
clk_set_ddr(X1000_CLK_SCLK_A, 5);
|
||||
|
||||
/* Turn off MPLL */
|
||||
jz_writef(CPM_MPCR, ENABLE(0));
|
||||
} else {
|
||||
/* Typical ingenic setup -- 1008 MHz APLL, 600 MHz MPLL
|
||||
* with APLL driving the CPU/L2 and MPLL driving busses. */
|
||||
clk_set_ccr_div(1, 2, 3, 3, 6);
|
||||
clk_set_ccr_mux(CLKMUX_SCLK_A(APLL) | CLKMUX_CPU(SCLK_A) |
|
||||
CLKMUX_AHB0(MPLL) | CLKMUX_AHB2(MPLL));
|
||||
|
||||
/* Mimic OF's clock gating setup */
|
||||
jz_writef(CPM_CLKGR, PDMA(0), PCM(1), MAC(1), LCD(1),
|
||||
MSC0(1), MSC1(1), OTG(1), CIM(1));
|
||||
|
||||
/* We now need to define the clock tree by fiddling with
|
||||
* various CPM registers. Although the kernel has code to
|
||||
* set up the clock tree itself, it isn't used and relies
|
||||
* on the SPL for this. */
|
||||
jz_writef(CPM_I2SCDR, CS_V(EXCLK));
|
||||
jz_writef(CPM_PCMCDR, CS_V(EXCLK));
|
||||
|
||||
jz_writef(CPM_MACCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MACCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_LPCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_LPCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_MSC0CDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MSC0CDR, BUSY));
|
||||
|
||||
jz_writef(CPM_MSC1CDR, CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MSC1CDR, BUSY));
|
||||
|
||||
jz_writef(CPM_CIMCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_CIMCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_USBCDR, CLKSRC_V(EXCLK), CE(1), STOP(1));
|
||||
while(jz_readf(CPM_USBCDR, BUSY));
|
||||
|
||||
/* Handle UART initialization */
|
||||
gpioz_configure(GPIO_C, 3 << 30, GPIOF_DEVICE(1));
|
||||
jz_writef(CPM_CLKGR, UART2(0));
|
||||
|
||||
/* TODO: Stop being lazy and make this human readable */
|
||||
volatile uint8_t* ub = (volatile uint8_t*)0xb0032000;
|
||||
ub[0x04] = 0;
|
||||
ub[0x08] = 0xef;
|
||||
ub[0x20] = 0xfc;
|
||||
ub[0x0c] = 3;
|
||||
uint8_t uv = ub[0x0c];
|
||||
ub[0x0c] = uv | 0x80;
|
||||
ub[0x04] = 0;
|
||||
ub[0x00] = 0x0d;
|
||||
ub[0x24] = 0x10;
|
||||
ub[0x28] = 0;
|
||||
ub[0x0c] = uv & 0x7f;
|
||||
ub[0x08] = 0x17;
|
||||
/* Flash the buttonlight */
|
||||
int level = 0;
|
||||
while(1) {
|
||||
gpio_set_function(GPIO_PC(24), GPIOF_OUTPUT(level));
|
||||
mdelay(100);
|
||||
level = 1 - level;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,41 +19,188 @@
|
|||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "system.h"
|
||||
#include "spl-x1000.h"
|
||||
#include "clk-x1000.h"
|
||||
#include "system.h"
|
||||
#include "nand-x1000.h"
|
||||
#include "gpio-x1000.h"
|
||||
#include "boot-x1000.h"
|
||||
#include "x1000/cpm.h"
|
||||
#include "x1000/ost.h"
|
||||
#include "x1000/uart.h"
|
||||
#include "x1000/ddrc.h"
|
||||
#include "x1000/ddrc_apb.h"
|
||||
#include "x1000/ddrphy.h"
|
||||
#include "ucl_decompress.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef FIIO_M3K
|
||||
# define SPL_DDR_MEMORYSIZE 64
|
||||
# define SPL_DDR_AUTOSR_EN 1
|
||||
# define SPL_DDR_NEED_BYPASS 1
|
||||
#else
|
||||
# error "please add SPL memory definitions"
|
||||
# error "please define DRAM settings"
|
||||
#endif
|
||||
|
||||
/* Note: This is based purely on disassembly of the SPL from the FiiO M3K.
|
||||
* The code there is somewhat generic and corresponds roughly to Ingenic's
|
||||
* U-Boot code, but isn't entirely the same.
|
||||
*
|
||||
* I converted all the runtime conditionals to compile-time ones in order to
|
||||
* save code space, since they should be constant for any given target.
|
||||
*
|
||||
* I haven't bothered to decode all the register fields. Some of the values
|
||||
* written are going to bits documented as "Reserved" by Ingenic, but their
|
||||
* documentation doesn't seem completely reliable, so either these are bits
|
||||
* which _do_ have a purpose, or they're only defined on other Ingenic CPUs.
|
||||
*
|
||||
* The DDR PHY registers appear to be from Synopsys "PHY Utility Block Lite".
|
||||
* These aren't documented by Ingenic, but the addresses and names can be found
|
||||
* in their U-Boot code.
|
||||
*/
|
||||
static void ddr_init(void)
|
||||
static void* heap = (void*)(X1000_SDRAM_BASE + X1000_SDRAM_SIZE);
|
||||
static nand_drv* ndrv = NULL;
|
||||
|
||||
void* spl_alloc(size_t count)
|
||||
{
|
||||
heap -= CACHEALIGN_UP(count);
|
||||
memset(heap, 0, CACHEALIGN_UP(count));
|
||||
return heap;
|
||||
}
|
||||
|
||||
int spl_storage_open(void)
|
||||
{
|
||||
/* We need to assign the GPIOs manually */
|
||||
gpioz_configure(GPIO_A, 0x3f << 26, GPIOF_DEVICE(1));
|
||||
|
||||
/* Allocate NAND driver manually in DRAM */
|
||||
ndrv = spl_alloc(sizeof(nand_drv));
|
||||
ndrv->page_buf = spl_alloc(NAND_DRV_MAXPAGESIZE);
|
||||
ndrv->scratch_buf = spl_alloc(NAND_DRV_SCRATCHSIZE);
|
||||
ndrv->refcount = 0;
|
||||
|
||||
return nand_open(ndrv);
|
||||
}
|
||||
|
||||
void spl_storage_close(void)
|
||||
{
|
||||
nand_close(ndrv);
|
||||
}
|
||||
|
||||
int spl_storage_read(uint32_t addr, uint32_t length, void* buffer)
|
||||
{
|
||||
return nand_read_bytes(ndrv, addr, length, buffer);
|
||||
}
|
||||
|
||||
/* Used by:
|
||||
* - FiiO M3K
|
||||
* - Shanling Q1
|
||||
*
|
||||
* Amend it and add #ifdefs for other targets if needed.
|
||||
*/
|
||||
void spl_dualboot_init_clocktree(void)
|
||||
{
|
||||
/* Make sure these are gated to match the OF behavior. */
|
||||
jz_writef(CPM_CLKGR, PCM(1), MAC(1), LCD(1), MSC0(1), MSC1(1), OTG(1), CIM(1));
|
||||
|
||||
/* Set clock sources, and make sure every clock starts out stopped */
|
||||
jz_writef(CPM_I2SCDR, CS_V(EXCLK));
|
||||
jz_writef(CPM_PCMCDR, CS_V(EXCLK));
|
||||
|
||||
jz_writef(CPM_MACCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MACCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_LPCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_LPCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_MSC0CDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MSC0CDR, BUSY));
|
||||
|
||||
jz_writef(CPM_MSC1CDR, CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_MSC1CDR, BUSY));
|
||||
|
||||
jz_writef(CPM_CIMCDR, CLKSRC_V(MPLL), CE(1), STOP(1), CLKDIV(0xfe));
|
||||
while(jz_readf(CPM_CIMCDR, BUSY));
|
||||
|
||||
jz_writef(CPM_USBCDR, CLKSRC_V(EXCLK), CE(1), STOP(1));
|
||||
while(jz_readf(CPM_USBCDR, BUSY));
|
||||
}
|
||||
|
||||
void spl_dualboot_init_uart2(void)
|
||||
{
|
||||
/* Ungate the clock and select UART2 device function */
|
||||
jz_writef(CPM_CLKGR, UART2(0));
|
||||
gpioz_configure(GPIO_C, 3 << 30, GPIOF_DEVICE(1));
|
||||
|
||||
/* Disable all interrupts */
|
||||
jz_write(UART_UIER(2), 0);
|
||||
|
||||
/* FIFO configuration */
|
||||
jz_overwritef(UART_UFCR(2),
|
||||
RDTR(3), /* FIFO trigger level = 60? */
|
||||
UME(0), /* UART module disable */
|
||||
DME(1), /* DMA mode enable? */
|
||||
TFRT(1), /* transmit FIFO reset */
|
||||
RFRT(1), /* receive FIFO reset */
|
||||
FME(1)); /* FIFO mode enable */
|
||||
|
||||
/* IR mode configuration */
|
||||
jz_overwritef(UART_ISR(2),
|
||||
RDPL(1), /* Zero is negative pulse for receive */
|
||||
TDPL(1), /* ... and for transmit */
|
||||
XMODE(1), /* Pulse width 1.6us */
|
||||
RCVEIR(0), /* Disable IR for recieve */
|
||||
XMITIR(0)); /* ... and for transmit */
|
||||
|
||||
/* Line configuration */
|
||||
jz_overwritef(UART_ULCR(2), DLAB(0),
|
||||
WLS_V(8BITS), /* 8 bit words */
|
||||
SBLS_V(1_STOP_BIT), /* 1 stop bit */
|
||||
PARE(0), /* no parity */
|
||||
SBK(0)); /* don't set break */
|
||||
|
||||
/* Set the baud rate... not too sure how this works. (Docs unclear!) */
|
||||
const unsigned divisor = 0x0004;
|
||||
jz_writef(UART_ULCR(2), DLAB(1));
|
||||
jz_write(UART_UDLHR(2), (divisor >> 8) & 0xff);
|
||||
jz_write(UART_UDLLR(2), divisor & 0xff);
|
||||
jz_write(UART_UMR(2), 16);
|
||||
jz_write(UART_UACR(2), 0);
|
||||
jz_writef(UART_ULCR(2), DLAB(0));
|
||||
|
||||
/* Enable UART */
|
||||
jz_overwritef(UART_UFCR(2),
|
||||
RDTR(0), /* FIFO trigger level = 1 */
|
||||
DME(0), /* DMA mode disable */
|
||||
UME(1), /* UART module enable */
|
||||
TFRT(1), /* transmit FIFO reset */
|
||||
RFRT(1), /* receive FIFO reset */
|
||||
FME(1)); /* FIFO mode enable */
|
||||
}
|
||||
|
||||
static void init_ost(void)
|
||||
{
|
||||
/* NOTE: the prescaler needs to be the same as in system-x1000.c */
|
||||
jz_writef(CPM_CLKGR, OST(0));
|
||||
jz_writef(OST_CTRL, PRESCALE2_V(BY_4));
|
||||
jz_overwritef(OST_CLEAR, OST2(1));
|
||||
jz_write(OST_2CNTH, 0);
|
||||
jz_write(OST_2CNTL, 0);
|
||||
jz_setf(OST_ENABLE, OST2);
|
||||
}
|
||||
|
||||
/* NOTE: This is originally based on disassembly of the FiiO M3K SPL, which
|
||||
* is in fact the GPL'd Ingenic X-Loader SPL. Similar stuff can be found in
|
||||
* Ingenic's U-boot code.
|
||||
*
|
||||
* The source code for the Ingenic X-Loader SPL can be found in these repos:
|
||||
* - https://github.com/JaminCheung/x-loader
|
||||
* - https://github.com/YuanhuanLiang/X1000
|
||||
*
|
||||
* Runtime conditionals based on the SoC type, looked up by OTP bits in EFUSE,
|
||||
* are converted to compile-time conditionals here, as they are constant for a
|
||||
* given target and there is no point in wasting precious space on dead code.
|
||||
*
|
||||
* I didn't decode the register fields; note that some values are documented as
|
||||
* "reserved" in the X1000 PM. The X-Loader source might shed more light on it;
|
||||
* it's likely these bits have meaning only on other Ingenic SoCs.
|
||||
*
|
||||
* The DDR PHY registers match Synopsys's "PHY Utility Block Lite." The names
|
||||
* of those registers & their fields can also be found in the X-Loader code,
|
||||
* but they're not documented by Ingenic.
|
||||
*/
|
||||
static int init_dram(void)
|
||||
{
|
||||
#if SPL_DDR_MEMORYSIZE != 64 && SPL_DDR_MEMORYSIZE != 32
|
||||
# error "bad memory size"
|
||||
#endif
|
||||
|
||||
jz_writef(CPM_CLKGR, DDR(0));
|
||||
|
||||
REG_CPM_DRCG = 0x73;
|
||||
mdelay(3);
|
||||
REG_CPM_DRCG = 0x71;
|
||||
|
@ -67,7 +214,11 @@ static void ddr_init(void)
|
|||
REG_DDRC_CTRL = 0;
|
||||
mdelay(3);
|
||||
|
||||
#if SPL_DDR_MEMORYSIZE == 64
|
||||
REG_DDRC_CFG = 0xa468a6c;
|
||||
#elif SPL_DDR_MEMORYSIZE == 32
|
||||
REG_DDRC_CFG = 0xa46896c;
|
||||
#endif
|
||||
REG_DDRC_CTRL = 2;
|
||||
REG_DDRPHY_DTAR = 0x150000;
|
||||
REG_DDRPHY_DCR = 0;
|
||||
|
@ -91,7 +242,7 @@ static void ddr_init(void)
|
|||
while(i > 0 && REG_DDRPHY_PGSR != 7 && REG_DDRPHY_PGSR != 0x1f)
|
||||
i -= 1;
|
||||
if(i == 0)
|
||||
spl_error();
|
||||
return 1;
|
||||
|
||||
#if SPL_DDR_NEED_BYPASS
|
||||
REG_DDRPHY_ACDLLCR = 0x80000000;
|
||||
|
@ -105,14 +256,18 @@ static void ddr_init(void)
|
|||
while(i > 0 && REG_DDRPHY_PGSR != 0xf && REG_DDRPHY_PGSR != 0x1f)
|
||||
i -= 1;
|
||||
if(i == 0)
|
||||
spl_error();
|
||||
return 2;
|
||||
|
||||
REG_DDRC_APB_PHYRST_CFG = 0x400000;
|
||||
mdelay(3);
|
||||
REG_DDRC_APB_PHYRST_CFG = 0;
|
||||
mdelay(3);
|
||||
|
||||
#if SPL_DDR_MEMORYSIZE == 64
|
||||
REG_DDRC_CFG = 0xa468aec;
|
||||
#elif SPL_DDR_MEMORYSIZE == 32
|
||||
REG_DDRC_CFG = 0xa4689ec;
|
||||
#endif
|
||||
REG_DDRC_CTRL = 2;
|
||||
#if SPL_DDR_NEED_BYPASS
|
||||
REG_DDRPHY_PIR = 0x20020081;
|
||||
|
@ -128,15 +283,19 @@ static void ddr_init(void)
|
|||
}
|
||||
|
||||
if(i == 0)
|
||||
spl_error();
|
||||
return 3;
|
||||
|
||||
if((REG_DDRPHY_PGSR & 0x60) != 0 && REG_DDRPHY_PGSR != 0)
|
||||
spl_error();
|
||||
return 4;
|
||||
|
||||
REG_DDRC_CTRL = 0;
|
||||
REG_DDRC_CTRL = 10;
|
||||
REG_DDRC_CTRL = 0;
|
||||
#if SPL_DDR_MEMORYSIZE == 64
|
||||
REG_DDRC_CFG = 0xa468a6c;
|
||||
#elif SPL_DDR_MEMORYSIZE == 32
|
||||
REG_DDRC_CFG = 0xa46896c;
|
||||
#endif
|
||||
REG_DDRC_TIMING1 = 0x2050501;
|
||||
REG_DDRC_TIMING2 = 0x4090404;
|
||||
REG_DDRC_TIMING3 = 0x2704030d;
|
||||
|
@ -149,11 +308,9 @@ static void ddr_init(void)
|
|||
#elif SPL_DDR_MEMORYSIZE == 32
|
||||
REG_DDRC_MMAP0 = 0x20fe;
|
||||
REG_DDRC_MMAP1 = 0x2200;
|
||||
#else
|
||||
# error "Unsupported DDR_MEMORYSIZE"
|
||||
#endif
|
||||
REG_DDRC_CTRL = 10;
|
||||
REG_DDRC_REFCNT = 0x2f0003;
|
||||
REG_DDRC_REFCNT = 0x2f0003; /* is this adjustable for 32M? */
|
||||
REG_DDRC_CTRL = 0xc91e;
|
||||
|
||||
#if SPL_DDR_MEMORYSIZE == 64
|
||||
|
@ -168,8 +325,6 @@ static void ddr_init(void)
|
|||
REG_DDRC_REMAP3 = 0x01000908;
|
||||
REG_DDRC_REMAP4 = 0x0f0e0d0c;
|
||||
REG_DDRC_REMAP5 = 0x13121110;
|
||||
#else
|
||||
# error "Unsupported DDR_MEMORYSIZE"
|
||||
#endif
|
||||
|
||||
REG_DDRC_STATUS &= ~0x40;
|
||||
|
@ -184,69 +339,126 @@ static void ddr_init(void)
|
|||
#endif
|
||||
|
||||
REG_DDRC_AUTOSR_EN = SPL_DDR_AUTOSR_EN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void init(void)
|
||||
static void* get_load_buffer(const struct spl_boot_option* opt)
|
||||
{
|
||||
/* from original firmware SPL */
|
||||
/* read to a temporary location if we need to decompress,
|
||||
* otherwise simply read directly to the load address. */
|
||||
if(opt->flags & BOOTFLAG_UCLPACK)
|
||||
return spl_alloc(opt->storage_size);
|
||||
else
|
||||
return (void*)opt->load_addr;
|
||||
}
|
||||
|
||||
/* Mapping of boot_sel[1:0] pins.
|
||||
* See X1000 PM pg. 687, "XBurst Boot ROM Specification" */
|
||||
enum {
|
||||
BSEL_MSC = 1,
|
||||
BSEL_USB = 2,
|
||||
BSEL_SFC = 3,
|
||||
};
|
||||
|
||||
static uint32_t get_boot_sel(void)
|
||||
{
|
||||
/* This variable holds the level of the boot_sel[2:0] pins at boot time,
|
||||
* and is defined by the maskrom.
|
||||
*
|
||||
* We use it to detect when we're USB booting, but this isn't totally
|
||||
* accurate because it only reflects the selected boot mode at reset and
|
||||
* not the current mode -- if the original selection fails and we fall
|
||||
* back to USB, this variable will only return the original selection.
|
||||
*/
|
||||
return (*(uint32_t*)0xf40001ec) & 3;
|
||||
}
|
||||
|
||||
typedef void(*entry_fn)(int, char**, int, int) __attribute__((noreturn));
|
||||
|
||||
void spl_main(void)
|
||||
{
|
||||
int rc, boot_option;
|
||||
const struct spl_boot_option* opt;
|
||||
void* load_buffer;
|
||||
char** kargv = NULL;
|
||||
int kargc = 0;
|
||||
|
||||
/* magic */
|
||||
REG_CPM_PSWC0ST = 0x00;
|
||||
REG_CPM_PSWC1ST = 0x10;
|
||||
REG_CPM_PSWC2ST = 0x18;
|
||||
REG_CPM_PSWC3ST = 0x08;
|
||||
|
||||
/* enable MPLL */
|
||||
#if X1000_EXCLK_FREQ == 24000000
|
||||
/* 24 * (24+1) = 600 MHz */
|
||||
jz_writef(CPM_MPCR, ENABLE(1), BS(1), PLLN(0), PLLM(24), PLLOD(0));
|
||||
#elif X1000_EXCLK_FREQ == 26000000
|
||||
/* 26 * (22+1) = 598 MHz */
|
||||
jz_writef(CPM_MPCR, ENABLE(1), BS(1), PLLN(0), PLLM(22), PLLOD(0));
|
||||
#else
|
||||
# error "unknown EXCLK frequency"
|
||||
#endif
|
||||
while(jz_readf(CPM_MPCR, ON) == 0);
|
||||
/* set up boot flags */
|
||||
init_boot_flags();
|
||||
set_boot_option(BOOT_OPTION_ROCKBOX);
|
||||
|
||||
/* set DDR clock to MPLL/3 = 200 MHz */
|
||||
jz_writef(CPM_CLKGR, DDR(0));
|
||||
clk_set_ddr(X1000_CLK_MPLL, 3);
|
||||
/* early clock and DRAM init */
|
||||
clk_init_early();
|
||||
init_ost();
|
||||
if(init_dram() != 0)
|
||||
spl_error();
|
||||
|
||||
/* start OST so we can use mdelay/udelay */
|
||||
jz_writef(CPM_CLKGR, OST(0));
|
||||
jz_writef(OST_CTRL, PRESCALE2_V(BY_4));
|
||||
jz_writef(OST_CLEAR, OST2(1));
|
||||
jz_write(OST_2CNTH, 0);
|
||||
jz_write(OST_2CNTL, 0);
|
||||
jz_setf(OST_ENABLE, OST2);
|
||||
|
||||
/* init DDR memory */
|
||||
ddr_init();
|
||||
}
|
||||
|
||||
/* This variable is defined by the maskrom. It's simply the level of the
|
||||
* boot_sel[2:0] pins (GPIOs B28-30) at boot time. Meaning of the bits:
|
||||
*
|
||||
* boot_sel[2] boot_sel[1] boot_sel[0] Description
|
||||
* -----------------------------------------------------------------
|
||||
* 1 X X EXCLK is 26 MHz
|
||||
* 0 X X EXCLK is 24 MHz
|
||||
* X 1 1 Boot from SFC0
|
||||
* X 0 1 Boot from MSC0
|
||||
* X 1 0 Boot from USB 2.0 device
|
||||
* -----------------------------------------------------------------
|
||||
* Source: X1000 PM pg. 687, "XBurst Boot ROM Specification"
|
||||
*/
|
||||
extern const uint32_t boot_sel;
|
||||
|
||||
void spl_main(void)
|
||||
{
|
||||
/* Basic hardware init */
|
||||
init();
|
||||
|
||||
/* If doing a USB boot, host PC will upload 2nd stage itself,
|
||||
* we should not load anything from flash or change clocks. */
|
||||
if((boot_sel & 3) == 2)
|
||||
/* USB boot stops here */
|
||||
if(get_boot_sel() == BSEL_USB) {
|
||||
set_boot_flag(BOOT_FLAG_USB_BOOT);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Just pass control to the target... */
|
||||
spl_target_boot();
|
||||
/* find out what we should boot */
|
||||
boot_option = spl_get_boot_option();
|
||||
opt = &spl_boot_options[boot_option];
|
||||
load_buffer = get_load_buffer(opt);
|
||||
|
||||
/* save the selection for later */
|
||||
set_boot_option(boot_option);
|
||||
|
||||
/* finish up clock init */
|
||||
clk_init();
|
||||
|
||||
/* load the image from storage */
|
||||
rc = spl_storage_open();
|
||||
if(rc != 0)
|
||||
spl_error();
|
||||
|
||||
rc = spl_storage_read(opt->storage_addr, opt->storage_size, load_buffer);
|
||||
if(rc != 0)
|
||||
spl_error();
|
||||
|
||||
/* handle compression */
|
||||
switch(opt->flags & BOOTFLAG_COMPRESSED) {
|
||||
case BOOTFLAG_UCLPACK: {
|
||||
uint32_t out_size = X1000_DRAM_END - opt->load_addr;
|
||||
rc = ucl_unpack((uint8_t*)load_buffer, opt->storage_size,
|
||||
(uint8_t*)opt->load_addr, &out_size);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(rc != 0)
|
||||
spl_error();
|
||||
|
||||
/* call the setup hook */
|
||||
if(opt->setup) {
|
||||
rc = opt->setup();
|
||||
if(rc != 0)
|
||||
spl_error();
|
||||
}
|
||||
|
||||
/* close off storage access */
|
||||
spl_storage_close();
|
||||
|
||||
/* handle kernel command line, if specified */
|
||||
if(opt->cmdline) {
|
||||
kargv = (char**)opt->cmdline_addr;
|
||||
kargv[kargc++] = 0;
|
||||
kargv[kargc++] = (char*)opt->cmdline;
|
||||
}
|
||||
|
||||
/* jump to the entry point */
|
||||
entry_fn fn = (entry_fn)opt->exec_addr;
|
||||
commit_discard_idcache();
|
||||
fn(kargc, kargv, 0, 0);
|
||||
}
|
||||
|
|
|
@ -22,21 +22,51 @@
|
|||
#ifndef __SPL_X1000_H__
|
||||
#define __SPL_X1000_H__
|
||||
|
||||
/* TODO: this needs some refactoring... */
|
||||
#include "boot-x1000.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Called on a fatal error */
|
||||
extern void spl_error(void) __attribute__((noreturn));
|
||||
#define BOOTFLAG_COMPRESSED 0x0f /* mask for compression flags */
|
||||
#define BOOTFLAG_UCLPACK 0x01 /* image is compressed with 'uclpack' */
|
||||
|
||||
/* Called by SPL to handle a main boot */
|
||||
extern void spl_target_boot(void);
|
||||
struct spl_boot_option {
|
||||
uint32_t storage_addr; /* image's location in storage */
|
||||
uint32_t storage_size; /* number of bytes to load */
|
||||
uint32_t load_addr; /* address to load image to */
|
||||
uint32_t exec_addr; /* address of the entry point */
|
||||
uint32_t flags; /* any special flags */
|
||||
const char* cmdline; /* command line; use NULL if not needed */
|
||||
uint32_t cmdline_addr; /* address to contain command line 'argv[]' */
|
||||
int(*setup)(void); /* setup hook, called before jumping to image */
|
||||
};
|
||||
|
||||
/* Invoked by SPL main routine to determine the boot option */
|
||||
/* array of boot option descriptions */
|
||||
extern const struct spl_boot_option spl_boot_options[];
|
||||
|
||||
/* Memory allocator. Allocation starts from the top of DRAM and counts down.
|
||||
* Allocation sizes are rounded up to a multiple of the cacheline size, so
|
||||
* the returned address is always suitably aligned for DMA. */
|
||||
extern void* spl_alloc(size_t count);
|
||||
|
||||
/* Access to boot storage medium, eg. flash or MMC/SD card.
|
||||
*
|
||||
* Read address and length is given in bytes. To make life easier, no
|
||||
* alignment restrictions are placed on the buffer, length, or address.
|
||||
* The buffer doesn't even need to be in DRAM.
|
||||
*/
|
||||
extern int spl_storage_open(void);
|
||||
extern void spl_storage_close(void);
|
||||
extern int spl_storage_read(uint32_t addr, uint32_t length, void* buffer);
|
||||
|
||||
/* Helpers for dual-booting with the Ingenic Linux OF */
|
||||
extern void spl_dualboot_init_clocktree(void);
|
||||
extern void spl_dualboot_init_uart2(void);
|
||||
|
||||
/* Get the boot option selected by the user, eg. by a key press */
|
||||
extern int spl_get_boot_option(void);
|
||||
|
||||
/* Do any setup/initialization needed for the given boot option, this
|
||||
* will be called right before flushing caches + jumping to the image.
|
||||
* Typical use is to set up system clocks, etc.
|
||||
*/
|
||||
extern void spl_handle_pre_boot(int bootopt);
|
||||
/* Called on a fatal error -- it should do something visible to the user
|
||||
* like flash the backlight repeatedly. */
|
||||
extern void spl_error(void) __attribute__((noreturn));
|
||||
|
||||
#endif /* __SPL_X1000_H__ */
|
||||
|
|
|
@ -15,9 +15,6 @@ MEMORY {
|
|||
|
||||
SECTIONS
|
||||
{
|
||||
/* Mask ROM variables, addresses found by disassembly */
|
||||
boot_sel = X1000_TCSM_BASE + 0x1ec;
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.init.spl);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "dma-x1000.h"
|
||||
#include "irq-x1000.h"
|
||||
#include "clk-x1000.h"
|
||||
#include "boot-x1000.h"
|
||||
#include "x1000/cpm.h"
|
||||
#include "x1000/ost.h"
|
||||
#include "x1000/tcu.h"
|
||||
|
@ -59,6 +60,27 @@ static void system_init_irq(void)
|
|||
write_c0_cause(M_CauseIV);
|
||||
}
|
||||
|
||||
/* First function called by crt0.S */
|
||||
void system_early_init(void)
|
||||
{
|
||||
#if defined(FIIO_M3K) && !defined(BOOTLOADER)
|
||||
/* HACK for compatibility: CPM scratchpad has undefined contents at
|
||||
* time of reset and old bootloader revisions don't initialize it.
|
||||
* Therefore we can't rely on its contents on the FiiO M3K. This does
|
||||
* kind of break the entire point of boot flags, but right now they
|
||||
* are really only used by the bootloader so it's not a huge issue.
|
||||
* This hack should keep everything working as usual. */
|
||||
if(jz_readf(CPM_MPCR, ON) == 0) {
|
||||
init_boot_flags();
|
||||
set_boot_option(BOOT_OPTION_ROCKBOX);
|
||||
set_boot_flag(BOOT_FLAG_CLK_INIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Finish up clock init */
|
||||
clk_init();
|
||||
}
|
||||
|
||||
/* First thing called from Rockbox main() */
|
||||
void system_init(void)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue