rockbox/bootloader/ipod-s5l87xx.c
Solomon Peachy bdbbb753c5 ipod6gboot: Silence build warning introduced in af2a3b74f4
(ipod6g doesn't utilize the QR Code stuff used by the nano3g)

Change-Id: I99c9e565b5a4c7212662eab097f1bae2f4ed0ea4
2026-09-24 15:46:23 -04:00

1225 lines
34 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2005 by Dave Chapman
*
* 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.
*
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "config.h"
#include "inttypes.h"
#include "cpu.h"
#include "system.h"
#include "lcd.h"
#include "../kernel-internal.h"
#include "file_internal.h"
#include "storage.h"
#include "disk.h"
#include "font.h"
#include "backlight.h"
#include "backlight-target.h"
#include "button.h"
#include "panic.h"
#include "power.h"
#include "file.h"
#include "common.h"
#include "rb-loader.h"
#include "loader_strerror.h"
#include "version.h"
#include "powermgmt.h"
#include "usb.h"
#ifdef IPOD_NANO3G
#include "nand-target.h"
#endif
#ifdef NAND_CHECK
#include "string-extra.h"
#endif
#ifdef HAVE_SERIAL
#include "serial.h"
#endif
#include "s5l87xx.h"
#include "clocking-s5l8702.h"
#include "spi-s5l8702.h"
#include "i2c-s5l8702.h"
#include "gpio-s5l8702.h"
#include "pmu-target.h"
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
#include "norboot-target.h"
#endif
#if defined(IPOD_NANO3G)
#define USE_QRCODE
#endif
#define ERR_RB 0
#define ERR_OF 1
#define ERR_STORAGE 2
#define ERR_LBA28 3
/* Safety measure - maximum allowed firmware image size.
The largest known current (October 2009) firmware is about 6.2MB so
we set this to 8MB.
*/
#define MAX_LOADSIZE (8*1024*1024)
#define LCD_RBYELLOW LCD_RGBPACK(255,192,0)
#define LCD_REDORANGE LCD_RGBPACK(255,70,0)
#define LCD_GREEN LCD_RGBPACK(0,255,0)
extern void bss_init(void);
extern uint32_t _movestart;
extern uint32_t start_loc;
extern int line;
#ifndef S5L87XX_DEVELOPMENT_BOOTLOADER
#ifdef HAVE_BOOTLOADER_USB_MODE
static void usb_mode(void)
{
int button;
verbose = true;
printf("Entering USB mode...");
powermgmt_init();
/* The code will ask for the maximum possible value */
usb_charging_enable(USB_CHARGING_ENABLE);
usb_init();
usb_start_monitoring();
/* Wait until USB is plugged */
while (usb_detect() != USB_INSERTED)
{
printf("Plug USB cable");
line--;
sleep(HZ/10);
}
while(1)
{
button = button_get_w_tmo(HZ/10);
if (button == SYS_USB_CONNECTED)
break; /* Hit */
if (usb_detect() == USB_EXTRACTED)
break; /* Cable pulled */
/* Wait for threads to connect or cable is pulled */
printf("USB: Connecting...");
line--;
}
if (button == SYS_USB_CONNECTED)
{
/* Got the message - wait for disconnect */
printf("Bootloader USB mode");
/* Ack the SYS_USB_CONNECTED polled from the button queue */
usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data());
while(1)
{
button = button_get_w_tmo(HZ/2);
if (button == SYS_USB_DISCONNECTED)
break;
}
}
/* We don't want the HDD to spin up if the USB is attached again */
usb_close();
printf("USB mode exit ");
}
#endif /* HAVE_BOOTLOADER_USB_MODE */
#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \
&& !defined(S5L87XX_DEVELOPMENT_BOOTLOADER)
/* The contributor NAND check (build with -DNAND_CHECK, run from DFU): show
* what the driver identified and how the read-only mount went, add the
* unit's model and firmware version, then serve the raw NAND over USB for
* utils/ipodnano3g/nandcheck/nandcheck.py. Nothing is written to the NAND. */
/* The bootloader's backlight calls are stubs, and the check's screen has
* come up too dim to read at the default level. Full brightness draws more
* than a battery-less unit's USB power alone can supply and can keep it
* from booting at all, so this stops at the default rather than the max. */
static void nand_check_light(void)
{
backlight_hw_brightness(DEFAULT_BRIGHTNESS_SETTING);
backlight_hw_on();
}
/* The report's key lines, short enough for the screen; the whole report
* goes to nandcheck.py */
static void nand_check_print(const char *report)
{
static const char *const keys[] = {
"banks", "row", "mode", "pagesize", "validated", "ftl", "verdict",
"model", "swvr",
};
static char text[SECTOR_SIZE];
char *p, *nl, *sp;
size_t i;
strlcpy(text, report, sizeof(text));
for (p = text; (nl = strchr(p, '\n')); p = nl + 1)
{
*nl = '\0';
sp = strchr(p, ' ');
if (!sp)
continue;
if (!strncmp(p, "ids ", 4))
{
/* bank 0's id; the others are in the report */
printf("id %.8s", sp + 1);
continue;
}
for (i = 0; i < ARRAYLEN(keys); i++)
if ((size_t)(sp - p) == strlen(keys[i])
&& !strncmp(p, keys[i], sp - p))
printf("%s", p);
}
}
static void nand_check(void)
{
static struct SysCfg syscfg;
char line[64];
int rc;
ssize_t n;
nand_check_light();
snprintf(line, sizeof(line), "battery %dmV", _battery_voltage());
rc = storage_init();
/* USB mode unmounts every volume when the host configures the device,
* through the file system's locks and object lists */
filesystem_init();
lcd_set_foreground(LCD_RBYELLOW);
printf("Nano 3G NAND check");
lcd_set_foreground(LCD_WHITE);
printf("%s", line);
nand_check_note(line);
nand_check_light();
if (rc)
printf("storage_init %d", rc);
/* Which unit this is, but not its serial number */
n = syscfg_read(&syscfg);
if (n != -1)
{
size_t i, count = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
for (i = 0; i < count; i++)
{
const struct SysCfgEntry *e = &syscfg.entries[i];
const uint32_t *w = (const uint32_t *)e->data;
if (e->tag == SYSCFG_TAG_MODN)
snprintf(line, sizeof(line), "model %.16s", e->data);
else if (e->tag == SYSCFG_TAG_SWVR)
snprintf(line, sizeof(line), "swvr %.16s", e->data);
else if (e->tag == SYSCFG_TAG_HWVR)
snprintf(line, sizeof(line), "hwvr %08lx",
(unsigned long)w[1]);
else
continue;
nand_check_note(line);
}
}
nand_check_print(nand_check_report());
lcd_set_foreground(LCD_RBYELLOW);
printf("Photo, then connect USB");
printf("and run nandcheck.py");
lcd_set_foreground(LCD_WHITE);
/* One USB session: usb_mode() starts power management each time, so it
* must not run twice */
usb_mode();
nand_check_light();
printf("Done. Hold MENU+SELECT");
printf("to restart");
while (1)
sleep(HZ);
}
#endif
void fatal_error(int err)
{
verbose = true;
/* System font is 6 pixels wide */
line++;
switch (err)
{
case ERR_RB:
#ifdef HAVE_BOOTLOADER_USB_MODE
usb_mode();
printf("Hold MENU+SELECT to reboot");
break;
#endif
case ERR_STORAGE:
printf("Hold MENU+SELECT to reboot");
printf("then SELECT+PLAY for disk mode");
break;
case ERR_OF:
printf("Hold MENU+SELECT to reboot");
printf("and enter Rockbox firmware");
break;
case ERR_LBA28:
printf("Hold MENU+SELECT to reboot");
printf("and LEFT if you are REALLY sure");
break;
}
#if (CONFIG_STORAGE & STORAGE_ATA)
if (ide_powered())
ata_sleepnow(); /* Immediately spindown the disk. */
#endif
line++;
lcd_set_foreground(LCD_REDORANGE);
while (1) {
lcd_puts(0, line, button_hold() ? "Hold switch on!"
: " ");
lcd_update();
}
}
#if (CONFIG_STORAGE & STORAGE_ATA)
extern unsigned short battery_level_disksafe;
static void battery_trap(void)
{
int vbat, old_verb;
int th = 50;
old_verb = verbose;
verbose = true;
usb_charging_maxcurrent_change(100);
while (1)
{
vbat = _battery_voltage();
/* Two reasons to use this threshold (may require adjustments):
* - when USB (or wall adaptor) is plugged/unplugged, Vbat readings
* differ as much as more than 200 mV when charge current is at
* maximum (~340 mA).
* - RB uses some sort of average/compensation for battery voltage
* measurements, battery icon blinks at battery_level_disksafe,
* when the HDD is used heavily (large database) the level drops
* to battery_level_shutoff quickly.
*/
if (vbat >= battery_level_disksafe + th)
break;
th = 200;
if (power_input_status() != POWER_INPUT_NONE) {
lcd_set_foreground(LCD_RBYELLOW);
printf("Low battery: %d mV, charging... ", vbat);
sleep(HZ*3);
}
else {
/* Wait for the user to insert a charger */
int tmo = 10;
lcd_set_foreground(LCD_REDORANGE);
while (1) {
vbat = _battery_voltage();
printf("Low battery: %d mV, power off in %d ", vbat, tmo);
if (!tmo--) {
/* Raise Vsysok (hyst=0.02*Vsysok) to avoid PMU
standby<->active looping */
if (vbat < 3200)
pmu_write(PCF5063X_REG_SVMCTL, 0xA /*3200mV*/);
power_off();
}
sleep(HZ*1);
if (power_input_status() != POWER_INPUT_NONE)
break;
line--;
}
}
line--;
}
verbose = old_verb;
lcd_set_foreground(LCD_WHITE);
printf("Battery status ok: %d mV ", vbat);
}
#endif /* CONFIG_STORAGE & STORAGE_ATA */
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
static int launch_onb(int clkdiv)
{
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
/* SPI clock = PClk/(clkdiv+1) */
spi_clkdiv(SPI_PORT, clkdiv);
/* Actually IRAM1_ORIG contains current RB bootloader IM3 header,
it will be replaced by ONB IM3 header, so this function must
be called once!!! */
struct Im3Info *hinfo = (struct Im3Info*)IRAM1_ORIG;
/* Loads ONB in IRAM0, exception vector table is destroyed !!! */
int rc = im3_read(
NORBOOT_OFF + im3_nor_sz(hinfo), hinfo, (void*)IRAM0_ORIG);
if (rc != 0) {
/* Restore exception vector table */
memcpy((void*)IRAM0_ORIG, &_movestart, 4*(&start_loc-&_movestart));
commit_discard_idcache();
return rc;
}
/* Disable all external interrupts */
eint_init();
commit_discard_idcache();
/* Branch to start of IRAM */
asm volatile("mov pc, %0"::"r"(IRAM0_ORIG));
while(1);
#elif defined(IPOD_NANO4G)
(void) clkdiv;
lcd_set_foreground(LCD_REDORANGE);
printf("Not implemented");
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
return 0;
#endif
}
/* Launch OF when kernel mode is running */
static int kernel_launch_onb(void)
{
disable_irq();
int rc = launch_onb(3); /* 54/4 = 13.5 MHz. */
enable_irq();
return rc;
}
/* The boot sequence is executed on power-on or reset. After power-up
* the device could come from a state of hibernation, OF hibernates
* the iPod after an inactive period of ~30 minutes, on this state the
* SDRAM is in self-refresh mode.
*
* t0 = 0
* S5L8702 BOOTROM loads an IM3 image located at NOR:
* - IM3 header (first 0x800 bytes) is loaded at IRAM1_ORIG
* - IM3 body (decrypted RB bootloader) is loaded at IRAM0_ORIG
* The time needed to load the RB bootloader (~100 Kb) is estimated
* on 200~250 ms. Once executed, RB booloader moves itself from
* IRAM0_ORIG to IRAM1_ORIG+0x800, preserving current IM3 header
* that contains the NOR offset where the ONB (original NOR boot),
* is located (see dualboot.c for details).
*
* t1 = ~250 ms.
* If the PMU is hibernated, decrypted ONB (size 128Kb) is loaded
* and executed, it takes ~120 ms. Then the ONB restores the
* iPod to the state prior to hibernation.
* If not, initialize system and RB kernel, wait for t2.
*
* t2 = ~650 ms.
* Check user button selection.
* If OF, diagmode, or diskmode is selected then launch ONB.
* If not, wait for LCD initialization.
*
* t3 = ~700,~900 ms. (lcd_type_01,lcd_type_23)
* LCD is initialized, baclight ON.
* Wait for HDD spin-up.
*
* t4 = ~2600,~2800 ms.
* HDD is ready.
* If hold switch is locked, then load and launch ONB.
* If not, load rockbox.ipod file from HDD.
*
* t5 = ~2800,~3000 ms.
* rockbox.ipod is executed.
*/
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
#include "piezo.h"
#include "lcd-s5l8702.h"
extern int lcd_type;
static uint16_t alive[] = { 500,100,0, 0 };
static uint16_t alivelcd[] = { 2000,200,0, 0 };
#ifdef HAVE_LCD_SLEEP
static void sleep_test(void)
{
int sleep_tmo = 5;
int awake_tmo = 3;
lcd_clear_display();
lcd_set_foreground(LCD_WHITE);
line = 0;
printf("Entering LCD sleep mode in %d seconds,", sleep_tmo);
printf("during sleep mode you will see a white");
printf("screen for about %d seconds.", awake_tmo);
while (sleep_tmo--) {
printf("Sleep in %d...", sleep_tmo);
sleep(HZ*1);
}
lcd_sleep();
sleep(HZ*awake_tmo);
lcd_awake();
line++;
printf("Awake!");
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
}
#endif
static void pmu_info(void)
{
int loop = 0;
lcd_clear_display();
lcd_update();
while (button_status() != BUTTON_NONE);
while (1)
{
lcd_set_foreground(LCD_WHITE);
lcd_clear_display();
line = 0;
printf("loop: %d", loop++);
for (int i = 0; i < 128; i += 8)
{
unsigned char buf[8];
#if defined(IPOD_NANO3G)
if (i == 0) {
static int flip = 0;
if (flip) {
pmu_write(6, 0xff);
pmu_write(7, 0xff);
}
else {
pmu_write(6, 0xe7);
pmu_write(7, 0xfe);
}
flip ^= 1;
}
#elif defined(IPOD_NANO4G)
if (i == 120)
for (int j = 0; j < 8; j++)
pmu_write(i+j, j);
#endif
for (int j = 0; j < 8; j++)
buf[j] = pmu_read(i+j);
printf(" %2x: %2x %2x %2x %2x %2x %2x %2x %2x", i,
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
}
line++;
printf("USB: %s ", (usb_detect() == USB_INSERTED) ? "inserted" : "not inserted");
#if CONFIG_CHARGING
printf("Firewire: %s ", pmu_firewire_present() ? "inserted" : "not inserted");
#endif
#ifdef IPOD_ACCESSORY_PROTOCOL
printf("Accessory: %s ", pmu_accessory_present() ? "inserted" : "not inserted");
#endif
printf("Hold Switch: %s ", pmu_holdswitch_locked() ? "locked" : "unlocked");
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
if (button_status() == BUTTON_SELECT)
break;
sleep(HZ/2);
}
}
static void gpio_info(void)
{
int loop = 0;
lcd_clear_display();
while (1)
{
lcd_set_foreground(LCD_WHITE);
lcd_clear_display();
line = 0;
printf("loop: %d", loop++);
for (int i = 0; i < GPIO_N_GROUPS; i ++)
{
printf(" %x: %8x %2x %4x %2x %2x", i,
PCON(i), PDAT(i), PUNA(i), PUNB(i), PUNC(i));
}
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
if (button_status() == BUTTON_SELECT)
break;
sleep(HZ/5);
}
}
static void run_of(void)
{
int tmo = 5;
lcd_clear_display();
lcd_set_foreground(LCD_WHITE);
line = 0;
while (tmo--) {
printf("Booting OF in %d...", tmo);
sleep(HZ*1);
}
int rc = kernel_launch_onb();
printf("Load OF error: %d", rc);
sleep(HZ*10);
}
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
static void print_syscfg(void)
{
lcd_clear_display();
lcd_set_foreground(LCD_WHITE);
line = 0;
struct SysCfg syscfg;
const ssize_t result = syscfg_read(&syscfg);
if (result == -1) {
printf("SCfg magic not found. NOR flash is corrupted.");
goto end;
}
printf("Total size: %lu bytes, %lu entries", syscfg.header.size, syscfg.header.num_entries);
if (result > 0) {
printf("Wrong size: expected %ld, got %lu", result, syscfg.header.size);
}
if (syscfg.header.num_entries > SYSCFG_MAX_ENTRIES) {
printf("Too many entries, showing only first %u", SYSCFG_MAX_ENTRIES);
}
const size_t syscfg_num_entries = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
for (size_t i = 0; i < syscfg_num_entries; i++) {
const struct SysCfgEntry* entry = &syscfg.entries[i];
const char* tag = (char *)&entry->tag;
const uint32_t* data32 = (uint32_t *)entry->data;
switch (entry->tag) {
case SYSCFG_TAG_SRNM:
printf("Serial number (SrNm): %s", entry->data);
break;
case SYSCFG_TAG_FWID:
printf("Firmware ID (FwId): %07lX", data32[1] & 0x0FFFFFFF);
break;
case SYSCFG_TAG_HWID:
printf("Hardware ID (HwId): %08lX", data32[0]);
break;
case SYSCFG_TAG_HWVR:
printf("Hardware version (HwVr): %06lX", data32[1]);
break;
case SYSCFG_TAG_CODC:
printf("Codec (Codc): %s", entry->data);
break;
case SYSCFG_TAG_SWVR:
printf("Software version (SwVr): %s", entry->data);
break;
case SYSCFG_TAG_MLBN:
printf("Logic board serial number (MLBN): %s", entry->data);
break;
case SYSCFG_TAG_MODN:
printf("Model number (Mod#): %s", entry->data);
break;
case SYSCFG_TAG_REGN:
printf("Sales region (Regn): %08lX %08lX", data32[0], data32[1]);
break;
default:
printf("%c%c%c%c: %08lX %08lX %08lX %08lX",
tag[3], tag[2], tag[1], tag[0],
data32[0], data32[1], data32[2], data32[3]
);
break;
}
}
end:
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
}
static void print_bootloader_hash(void)
{
lcd_clear_display();
lcd_set_foreground(LCD_WHITE);
line = 0;
struct Im3Info hinfo;
int rc = im3_read(NORBOOT_OFF, &hinfo, NULL);
if (rc != 0) {
printf("Error loading the primary bootloader: %d", rc);
goto end;
}
unsigned char primary_hash[SIGN_SZ];
memcpy(primary_hash, hinfo.u.enc12.data_sign, SIGN_SZ);
hwkeyaes(HWKEYAES_DECRYPT, HWKEYAES_UKEY, primary_hash, SIGN_SZ);
unsigned bl_nor_sz = im3_nor_sz(&hinfo);
rc = im3_read(NORBOOT_OFF + bl_nor_sz, &hinfo, NULL);
if (rc == 0) {
// Rockbox bootloader is installed as primary
// Stock bootloader is backed up
unsigned char backup_hash[SIGN_SZ];
memcpy(backup_hash, hinfo.u.enc12.data_sign, SIGN_SZ);
hwkeyaes(HWKEYAES_DECRYPT, HWKEYAES_UKEY, backup_hash, SIGN_SZ);
printf("Rockbox bootloader hash:");
for (int i = 0; i < SIGN_SZ; i++) {
lcd_putsf(i * 2, line, "%02X", primary_hash[i]);
}
line += 2;
lcd_update();
printf("Stock bootloader hash:");
for (int i = 0; i < SIGN_SZ; i++) {
lcd_putsf(i * 2, line, "%02X", backup_hash[i]);
}
line++;
lcd_update();
}
else {
// Stock bootloader is installed as primary
// No backup bootloader
printf("Rockbox bootloader is not installed!");
line++;
printf("Stock bootloader hash:");
for (int i = 0; i < SIGN_SZ; i++) {
lcd_putsf(i * 2, line, "%02X", primary_hash[i]);
}
line++;
lcd_update();
}
end:
line++;
while (button_status() != BUTTON_NONE)
sleep(HZ/100);
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
}
#ifdef HAVE_SERIAL
#define FLASH_PAGES (FLASH_SIZE >> 12)
#define FLASH_PAGE_SIZE (FLASH_SIZE >> 8)
static void dump_bootflash(void)
{
lcd_clear_display();
lcd_set_foreground(LCD_WHITE);
line = 0;
uint8_t page[FLASH_PAGE_SIZE];
printf("Total pages: %d", FLASH_PAGES);
bootflash_init(SPI_PORT);
for (int i = 0; i < FLASH_PAGES; i++) {
printf("Reading flash... %d", i + 1);
bootflash_read(SPI_PORT, i << 12, FLASH_PAGE_SIZE, page);
printf("Sending over UART... %d", i + 1);
serial_tx_raw(page, FLASH_PAGE_SIZE);
line -= 2;
}
bootflash_close(SPI_PORT);
line += 2;
printf("Done!");
piezo_seq(alive);
line++;
lcd_set_foreground(LCD_RBYELLOW);
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
}
#endif /* HAVE_SERIAL */
#endif /* IPOD_6G || IPOD_NANO3G */
static void devel_menu(void)
{
const char *items[] = {
#ifdef HAVE_LCD_SLEEP
"LCD sleep/awake test",
#endif
"PMU info",
"GPIO info",
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
"Show SysCfg",
"Show bootloader hash",
#ifdef HAVE_SERIAL
"Dump bootflash to UART",
#endif
#endif
"Launch OF",
//"Launch Rockbox",
"Restart",
"Power off",
};
void (*handlers[])(void) = {
#ifdef HAVE_LCD_SLEEP
sleep_test,
#endif
pmu_info,
gpio_info,
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
print_syscfg,
print_bootloader_hash,
#ifdef HAVE_SERIAL
dump_bootflash,
#endif
#endif
run_of,
//run_rockbox,
system_reboot,
power_off,
};
const size_t items_count = sizeof(items) / sizeof(items[0]);
unsigned char selected_item = 0;
while (1)
{
lcd_clear_display();
lcd_set_foreground(LCD_RBYELLOW);
line = 0;
printf("Development menu");
for (size_t i = 0; i < items_count; i++) {
lcd_set_foreground(i == selected_item ? LCD_GREEN : LCD_WHITE);
printf(items[i]);
}
while (button_status() != BUTTON_NONE);
bool done = false;
while (!done)
{
switch (button_status())
{
case BUTTON_MENU:
case BUTTON_LEFT:
if (selected_item > 0) {
selected_item--;
done = true;
}
else {
sleep(HZ/100);
}
break;
case BUTTON_PLAY:
case BUTTON_RIGHT:
if (selected_item < items_count - 1) {
selected_item++;
done = true;
}
else {
sleep(HZ/100);
}
break;
case BUTTON_SELECT:
handlers[selected_item]();
done = true;
break;
default:
sleep(HZ/100);
break;
}
}
}
}
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
#ifdef IPOD_NANO3G
static const char qr_code_data_nano[][22] = {
/*
* qrencode -o - -t ascii -s 1 -m 0 -i 'https://rockbox.org/IPNBL' | \
* cut -c 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41 | \
* awk '{ print "\"" $0 "\"," }'
*/
"####### # #######",
"# # ## # # #",
"# ### # ## # # ### #",
"# ### # # # # # ### #",
"# ### # # ## # ### #",
"# # #### # #",
"####### # # # #######",
" ## ",
"##### ######## # # # ",
"# # #### # # #",
" # # # #",
"## # # # # ## #### ",
"# ### ## ### ",
" ## # # ####",
"####### ## # ### #",
"# # ### # ## ",
"# ### # # #### ###",
"# ### # ### ### ## ",
"# ### # #### # # ",
"# # # ## ## # #",
"####### # ## ## ",
};
#define qr_code_data qr_code_data_nano
#endif
#ifdef USE_QRCODE
static void lcd_qr_code(const char *data, unsigned int row_stride,
int x, int y, unsigned int qr_cols, unsigned int qr_rows,
unsigned int modules_scale, unsigned int margin_thickness)
{
const unsigned int margin_size = margin_thickness * 2;
const unsigned int width = qr_cols * modules_scale + margin_size;
const unsigned int height = qr_rows * modules_scale + margin_size;
// -1 = align right, -2 = align center
const unsigned int x0 = x >= 0 ? (unsigned int)x : (LCD_WIDTH - width) / abs(x);
const unsigned int y0 = y >= 0 ? (unsigned int)y : (LCD_HEIGHT - height) / abs(y);
lcd_set_foreground(LCD_WHITE);
lcd_fillrect(x0, y0, width, height);
lcd_set_foreground(LCD_BLACK);
for (unsigned int row = 0; row < qr_rows; row++) {
for (unsigned int col = 0; col < qr_cols; col++) {
if (data[row * row_stride + col] != ' ') {
lcd_fillrect(
x0 + margin_thickness + col * modules_scale,
y0 + margin_thickness + row * modules_scale,
modules_scale,
modules_scale
);
}
}
}
lcd_update();
}
enum {
QR_COLS = sizeof(qr_code_data[0]) - 1,
QR_ROWS = sizeof(qr_code_data) / sizeof(qr_code_data[0]),
QR_STRIDE = sizeof(qr_code_data[0]),
};
#endif /* USE_QRCODE */
void main(void)
{
int rc = 0;
usec_timer_init();
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
piezo_seq(alive);
#endif
/* Configure I2C0 */
i2c_preinit(0);
if (pmu_is_hibernated()) {
rc = launch_onb(1); /* 27/2 = 13.5 MHz. */
}
system_preinit();
memory_init();
/*
* XXX: BSS is initialized here, do not use .bss before this line
*/
bss_init();
system_init();
kernel_init();
i2c_init();
power_init();
enable_irq();
#ifdef HAVE_SERIAL
serial_setup();
#endif
button_init();
if (rc == 0) {
/* User button selection timeout */
while (USEC_TIMER < 400000);
int btn = button_read_device();
/* This prevents HDD spin-up when the user enters DFU */
if (btn == (BUTTON_SELECT|BUTTON_MENU)) {
while (button_read_device() == (BUTTON_SELECT|BUTTON_MENU))
sleep(HZ/10);
sleep(HZ);
btn = button_read_device();
}
/* Enter OF, diagmode and diskmode using ONB */
if ((btn == BUTTON_MENU)
|| (btn == (BUTTON_SELECT|BUTTON_LEFT))
|| (btn == (BUTTON_SELECT|BUTTON_PLAY))) {
rc = kernel_launch_onb();
}
}
lcd_init();
lcd_set_foreground(LCD_WHITE);
lcd_set_background(LCD_BLACK);
lcd_clear_display();
font_init();
lcd_setfont(FONT_SYSFIXED);
// TODO: see if removing this causes the nano3g LCD to initialize properly
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
sleep(HZ);
for (int i = 0; i < lcd_type+1; i++) {
sleep(HZ/2);
piezo_seq(alivelcd);
}
#endif
lcd_update();
sleep(HZ/40); /* wait for lcd update */
verbose = true;
printf("Rockbox boot loader");
printf("Version: %s", rbversion);
backlight_init(); /* Turns on the backlight */
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
line++;
printf("lcd type: %d", lcd_type);
#ifdef S5L_LCD_WITH_READID
extern unsigned char lcd_id[4];
uint32_t* lcd_id_32 = (uint32_t *)lcd_id;
printf("lcd id: 0x%x", *lcd_id_32);
#endif
#ifdef IPOD_NANO4G
printf("boot cfg: 0x%x", pmu_read(0x7f));
#endif
line++;
printf("Press SELECT to continue");
while (button_status() != BUTTON_SELECT)
sleep(HZ/100);
devel_menu();
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \
&& !defined(S5L87XX_DEVELOPMENT_BOOTLOADER)
nand_check();
#endif
#ifndef S5L87XX_DEVELOPMENT_BOOTLOADER
if (rc == 0) {
#if (CONFIG_STORAGE & STORAGE_ATA)
/* Wait until there is enought power to spin-up HDD */
battery_trap();
#endif
rc = storage_init();
#ifdef IPOD_NANO3G
if (rc == NAND_ERR_UNSUPPORTED) {
/* Rockbox only drives NAND chips proven on hardware. Say which
* one this is and how to get it validated, then leave the unit
* to Apple's firmware, which is untouched. */
lcd_set_foreground(LCD_RBYELLOW);
printf("NAND not supported yet");
lcd_set_foreground(LCD_WHITE);
printf("chip %08lx x %u", (unsigned long)nand_get_id(),
nand_get_bank_count());
printf("Rockbox does not write to");
printf("chips it has not been");
printf("tested on.");
printf("Starting Apple firmware in 60s...");
lcd_qr_code(&qr_code_data[0][0], QR_STRIDE, -2, LCD_HEIGHT / 2, QR_COLS, QR_ROWS, 4, 8);
sleep(60 * HZ); /* long enough to read the message and scan the QR code */
lcd_set_foreground(LCD_WHITE);
rc = kernel_launch_onb();
/* Only reached if the ONB could not be read from NOR */
printf("Apple firmware failed: %d", rc);
printf("Hold MENU+SELECT to reboot,");
printf("then SELECT+PLAY for disk mode");
while (1)
sleep(HZ);
}
#endif
if (rc != 0) {
printf("Storage error: %d", rc);
fatal_error(ERR_STORAGE);
}
filesystem_init();
/* We wait until HDD spins up to check for hold button */
if (button_hold()) {
#ifdef SYSCFG_MAX_ENTRIES
bool lba48 = false;
struct SysCfg syscfg;
const ssize_t result = syscfg_read(&syscfg);
if (result != -1) {
const size_t syscfg_num_entries = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
for (size_t i = 0; i < syscfg_num_entries; i++) {
const struct SysCfgEntry* entry = &syscfg.entries[i];
const uint32_t* data32 = (uint32_t *)entry->data;
if (entry->tag == SYSCFG_TAG_HWVR) {
lba48 = (data32[1] >= 0x130200);
break;
}
}
int btn = button_read_device();
struct storage_info sinfo;
storage_get_info(0, &sinfo);
if (sinfo.num_sectors < (1 << 28) || lba48 || btn & BUTTON_LEFT) {
printf("Executing OF...");
#if (CONFIG_STORAGE & STORAGE_ATA)
ata_sleepnow();
#endif
rc = kernel_launch_onb();
} else {
printf("OF does not support LBA48");
fatal_error(ERR_LBA28);
}
}
#else
printf("Executing OF...");
#if (CONFIG_STORAGE & STORAGE_ATA)
ata_sleepnow();
#endif
rc = kernel_launch_onb();
#endif /* SYSCFG_MAX_ENTRIES */
}
}
if (rc != 0) {
printf("Load OF error: %d", rc);
fatal_error(ERR_OF);
}
#ifdef HAVE_BOOTLOADER_USB_MODE
/* Enter USB mode if SELECT+RIGHT are pressed */
if (button_read_device() == (BUTTON_SELECT|BUTTON_RIGHT)) {
#if defined(MAX_VIRT_SECTOR_SIZE) && defined(DEFAULT_VIRT_SECTOR_SIZE)
#ifdef HAVE_MULTIDRIVE
for (int i = 0 ; i < NUM_DRIVES ; i++)
#endif
disk_set_sector_multiplier(IF_MD(i,) DEFAULT_VIRT_SECTOR_SIZE/SECTOR_SIZE);
#endif
usb_mode();
}
#endif
rc = disk_mount_all();
if (rc <= 0) {
#ifdef STORAGE_GET_INFO
struct storage_info sinfo;
storage_get_info(0, &sinfo);
#ifdef MAX_PHYS_SECTOR_SIZE
printf("id: '%s' s:%u*%u", sinfo.product, sinfo.sector_size, sinfo.phys_sector_mult);
#else
printf("id: '%s' s:%u", sinfo.product, sinfo.sector_size);
#endif
#endif
struct partinfo pinfo;
printf("No partition found");
for (int i = 0 ; i < NUM_VOLUMES ; i++) {
disk_partinfo(i, &pinfo);
if (pinfo.type)
printf("P%d T%02x S%llx",
i, pinfo.type, (unsigned long long)pinfo.size);
}
#if defined(IPOD_NANO3G) && defined(DEFAULT_VIRT_SECTOR_SIZE)
/* Nothing mounted: show USB hosts Apple's 4096-byte sectors, which
* its partition table counts in */
disk_set_sector_multiplier(IF_MD(0,)
DEFAULT_VIRT_SECTOR_SIZE / SECTOR_SIZE);
#endif
fatal_error(ERR_RB);
}
printf("Loading Rockbox...");
unsigned char *loadbuffer = (unsigned char *)DRAM_ORIG;
rc = load_firmware(loadbuffer, BOOTFILE, MAX_LOADSIZE);
if (rc <= EFILE_EMPTY) {
printf("Error!");
printf("Can't load " BOOTFILE ": ");
printf(loader_strerror(rc));
fatal_error(ERR_RB);
}
printf("Rockbox loaded.");
/* If we get here, we have a new firmware image at 0x08000000, run it */
disable_irq();
int (*kernel_entry)(void) = (void*)loadbuffer;
commit_discard_idcache();
rc = kernel_entry();
/* End stop - should not get here */
enable_irq();
printf("ERR: Failed to boot");
while(1);
#endif
}