diff options
Diffstat (limited to 'bootloader/ipod6g.c')
-rw-r--r-- | bootloader/ipod6g.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/bootloader/ipod6g.c b/bootloader/ipod6g.c new file mode 100644 index 0000000000..0ab9444578 --- /dev/null +++ b/bootloader/ipod6g.c @@ -0,0 +1,462 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "fat.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); +} |