FiiO M3K: Add dual boot support

Change-Id: Ic34d50855b317b5f4073b232dbf458edf82f55e1
This commit is contained in:
Aidan MacDonald 2021-06-13 17:39:29 +01:00
parent 89f4064743
commit 95408f2117
3 changed files with 148 additions and 78 deletions

View File

@ -19,11 +19,15 @@
*
****************************************************************************/
#include "system.h"
#include "ucl_decompress.h"
#include "spl-x1000.h"
#include "gpio-x1000.h"
#include "clk-x1000.h"
#include "system.h"
#include "nand-x1000.h"
#include "x1000/cpm.h"
#include <string.h>
#include <stdint.h>
/* Available boot options */
#define BOOTOPTION_ROCKBOX 0
@ -54,6 +58,17 @@ static const char recovery_cmdline[] = "mem=64M@0x0\
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 */
};
const struct spl_boot_option spl_boot_options[] = {
{
/* Rockbox: the first unused NAND page is 26 KiB in, and the
@ -104,6 +119,75 @@ void spl_error(void)
}
}
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 */
int rc = nand_open();
if(rc)
spl_error();
int mf_id, dev_id;
rc = nand_identify(&mf_id, &dev_id);
if(rc)
goto nand_err;
/* For OF only: load DMA coprocessor's firmware from flash */
if(opt_index != BOOTOPTION_ROCKBOX) {
rc = nand_read(0x4000, 0x2000, (uint8_t*)0xb3422000);
if(rc)
goto nand_err;
}
/* Read the firmware */
rc = nand_read(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
* and also make sure to turn off the write protect bits. */
if(opt_index == BOOTOPTION_ROCKBOX)
nand_close();
else
nand_enable_writes(true);
/* 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();
spl_error();
}
int spl_get_boot_option(void)
{
const uint32_t pinmask = (1 << 17) | (1 << 19);
@ -157,17 +241,69 @@ void spl_handle_pre_boot(int bootopt)
* PCLK at 100 MHz
* DDR at 200 MHz
*/
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));
if(bootopt == BOOTOPTION_ROCKBOX) {
/* We don't use MPLL in Rockbox, so switch DDR memory to APLL */
/* 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 {
/* TODO: Original firmware needs a lot of other clocks turned on */
/* 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;
}
}

View File

@ -21,14 +21,12 @@
#include "spl-x1000.h"
#include "clk-x1000.h"
#include "nand-x1000.h"
#include "system.h"
#include "x1000/cpm.h"
#include "x1000/ost.h"
#include "x1000/ddrc.h"
#include "x1000/ddrc_apb.h"
#include "x1000/ddrphy.h"
#include "ucl_decompress.h"
#ifdef FIIO_M3K
# define SPL_DDR_MEMORYSIZE 64
@ -224,29 +222,6 @@ static void init(void)
ddr_init();
}
static int nandread(uint32_t addr, uint32_t size, void* buffer)
{
int rc;
int mf_id, dev_id;
if((rc = nand_open()))
return rc;
if((rc = nand_identify(&mf_id, &dev_id))) {
nand_close();
return rc;
}
rc = nand_read(addr, size, (uint8_t*)buffer);
nand_close();
return rc;
}
/* Entry point function type, defined to be Linux compatible. */
typedef void(*entry_fn)(int, char**, int, int);
/* Kernel command line arguments */
static char* argv[2];
/* 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:
*
@ -264,10 +239,6 @@ extern const uint32_t boot_sel;
void spl_main(void)
{
int opt_index;
uint8_t* load_addr;
const struct spl_boot_option* opt;
/* Basic hardware init */
init();
@ -276,35 +247,6 @@ void spl_main(void)
if((boot_sel & 3) == 2)
return;
/* Get the boot option */
opt_index = spl_get_boot_option();
opt = &spl_boot_options[opt_index];
load_addr = (uint8_t*)opt->load_addr;
/* Set up hardware, load stuff from flash */
spl_handle_pre_boot(opt_index);
if(nandread(opt->nand_addr, opt->nand_size, load_addr))
spl_error();
if(!opt->cmdline) {
/* No command line => we are booting Rockbox, decompress bootloader.
* In the case of Rockbox, load binary directly to exec address */
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();
}
/* Reading the Linux command line from the bootloader is handled by
* arch/mips/xburst/core/prom.c -- see Ingenic kernel sources. It's
* simply an (int argc, char* argv[]) thing.
*/
entry_fn entry = (entry_fn)opt->exec_addr;
argv[0] = 0;
argv[1] = (char*)opt->cmdline;
commit_discard_idcache();
entry(2, argv, 0, 0);
__builtin_unreachable();
/* Just pass control to the target... */
spl_target_boot();
}

View File

@ -22,22 +22,14 @@
#ifndef __SPL_X1000_H__
#define __SPL_X1000_H__
#include <stdint.h>
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 */
};
/* Defined by target, order is not important */
extern const struct spl_boot_option spl_boot_options[];
/* TODO: this needs some refactoring... */
/* Called on a fatal error */
extern void spl_error(void) __attribute__((noreturn));
/* Called by SPL to handle a main boot */
extern void spl_target_boot(void);
/* Invoked by SPL main routine to determine the boot option */
extern int spl_get_boot_option(void);