summaryrefslogtreecommitdiff
path: root/bootloader/ipod6g.c
diff options
context:
space:
mode:
Diffstat (limited to 'bootloader/ipod6g.c')
-rw-r--r--bootloader/ipod6g.c462
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);
+}