rockbox/bootloader/ipod6g.c

462 lines
12 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* 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 HAVE_SERIAL
#include "serial.h"
#endif
#include "s5l8702.h"
#include "clocking-s5l8702.h"
#include "spi-s5l8702.h"
#include "i2c-s5l8702.h"
#include "gpio-s5l8702.h"
#include "pmu-target.h"
#include "nor-target.h"
#define FW_ROCKBOX 0
#define FW_APPLE 1
#define ERR_RB 0
#define ERR_OF 1
#define ERR_HDD 2
/* 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)
extern void bss_init(void);
extern uint32_t _movestart;
extern uint32_t start_loc;
extern int line;
#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);
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 */
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_HDD:
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;
}
if (ide_powered())
ata_sleepnow(); /* Immediately spindown the disk. */
line++;
lcd_set_foreground(LCD_REDORANGE);
while (1) {
lcd_puts(0, line, button_hold() ? "Hold switch on!"
: " ");
lcd_update();
}
}
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_dangerous,
* when the HDD is used heavily (large database) the level drops
* to battery_level_shutoff quickly.
*/
if (vbat >= battery_level_dangerous[0] + 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);
}
static int launch_onb(int clkdiv)
{
/* 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);
}
/* 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;
}
static bool pmu_is_hibernated(void)
{
/* OF sets GPIO3 to low when SDRAM is hibernated */
return !(pmu_rd(PCF5063X_REG_GPIO3CFG) & 7) &&
!(pmu_rd(PCF5063X_REG_OOCSHDWN) & PCF5063X_OOCSHDWN_COLDBOOT);
}
/* 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 (FW 1.1.2), 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 (~90 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.
*/
void main(void)
{
// int fw = FW_ROCKBOX;
int rc = 0;
unsigned char *loadbuffer;
int (*kernel_entry)(void);
usec_timer_init();
/* Configure I2C0 */
i2c_preinit(0);
if (pmu_is_hibernated()) {
// fw = FW_APPLE;
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))) {
// fw = FW_APPLE;
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);
lcd_update();
sleep(HZ/40);
verbose = true;
printf("Rockbox boot loader");
printf("Version: %s", rbversion);
backlight_init(); /* Turns on the backlight */
if (rc == 0) {
/* Wait until there is enought power to spin-up HDD */
battery_trap();
rc = storage_init();
if (rc != 0) {
printf("ATA error: %d", rc);
fatal_error(ERR_HDD);
}
filesystem_init();
/* We wait until HDD spins up to check for hold button */
if (button_hold()) {
// fw = FW_APPLE;
printf("Executing OF...");
ata_sleepnow();
rc = kernel_launch_onb();
}
}
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))
usb_mode();
#endif
rc = disk_mount_all();
if (rc <= 0) {
printf("No partition found");
fatal_error(ERR_RB);
}
printf("Loading Rockbox...");
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();
kernel_entry = (void*) loadbuffer;
commit_discard_idcache();
rc = kernel_entry();
/* End stop - should not get here */
enable_irq();
printf("ERR: Failed to boot");
while(1);
}