Bring Gigabeat S bootloader one step close to a release version.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17442 a1c6a512-1295-4272-9138-f99709370657
diff --git a/bootloader/gigabeat-s.c b/bootloader/gigabeat-s.c
index 30c2955..dfe13e4 100644
--- a/bootloader/gigabeat-s.c
+++ b/bootloader/gigabeat-s.c
@@ -21,11 +21,15 @@
 #include <sprintf.h>
 #include "kernel.h"
 #include "string.h"
+#include "adc.h"
+#include "powermgmt.h"
 #include "ata.h"
 #include "dir.h"
 #include "disk.h"
 #include "common.h"
+#include "backlight.h"
 #include "usb.h"
+#include "button.h"
 #include "font.h"
 #include "lcd.h"
 #include "usb-target.h"
@@ -39,11 +43,15 @@
 /* Can use memory after vector table up to 0x01f00000 */
 static char * const tarbuf = (char *)0x00000040;
 static const size_t tarbuf_size = 0x01f00000 - 0x00000040;
-/* Queue to get notifications when in USB mode */
-static struct event_queue usb_wait_queue;
+/* Firmware data */
+static void * const load_buf = 0x00000000;
+static const size_t load_buf_size = 0x20000000 - 0x100000;
+static const void * const start_addr = 0x00000000;
 
 static void show_splash(int timeout, const char *msg)
 {
+    backlight_on();
+    reset_screen();
     lcd_putsxy( (LCD_WIDTH - (SYSFONT_WIDTH * strlen(msg))) / 2,
                 (LCD_HEIGHT - SYSFONT_HEIGHT) / 2, msg);
     lcd_update();
@@ -51,6 +59,95 @@
     sleep(timeout);
 }
 
+static bool pause_if_button_pressed(bool pre_usb)
+{
+    while (1)
+    {
+        int button = button_read_device();
+
+        if (pre_usb && !usb_plugged())
+            return false;
+
+        /* Exit if no button or only the menu (settings reset) button */
+        switch (button)
+        {
+        case BUTTON_MENU:
+        case BUTTON_NONE:
+            return true;
+        }
+
+        sleep(HZ/5);
+
+        /* If the disk powers off, the firmware will lock at startup */
+        ata_spin();
+    }
+}
+
+/* TODO: Handle charging while connected */
+static void handle_usb(void)
+{
+    int button;
+
+    /* Check if plugged and pause to look at messages. If the cable was pulled
+     * while waiting, proceed as if it never was plugged. */
+    if (!usb_plugged() || !pause_if_button_pressed(true))
+    {
+        /* Bang on the controller */
+        usb_init_device();
+        return;
+    }
+
+    /** Enter USB mode **/
+
+    /* We need full button and backlight handling now */
+    backlight_init();
+    button_init();
+
+    /* Start the USB driver */
+    usb_init();
+    usb_start_monitoring();
+
+    /* Wait for threads to connect or cable is pulled */
+    show_splash(HZ/2, "Waiting for USB");
+
+    while (1)
+    {
+        button = button_get_w_tmo(HZ/2);
+
+        if (button == SYS_USB_CONNECTED)
+            break; /* Hit */
+
+        if (!usb_plugged())
+            break; /* Cable pulled */
+    }
+
+    if (button == SYS_USB_CONNECTED)
+    {
+        /* Got the message - wait for disconnect */
+        show_splash(0, "Bootloader USB mode");
+
+        usb_acknowledge(SYS_USB_CONNECTED_ACK);
+
+        while (1)
+        {
+            button = button_get(true);
+            if (button == SYS_USB_DISCONNECTED)
+            {
+                usb_acknowledge(SYS_USB_DISCONNECTED_ACK);
+                break;
+            }
+        }
+    }
+
+    /* Put drivers initialized for USB connection into a known state */
+    backlight_on();
+    usb_close();
+    button_close();
+    backlight_close();
+
+    reset_screen();
+}
+
 static void untar(int tar_fd)
 {
     char header[TAR_HEADER_SIZE];
@@ -60,13 +157,15 @@
     int ret;
     size_t size = filesize(tar_fd);
 
-    if (size > tarbuf_size) {
+    if (size > tarbuf_size)
+    {
         printf("tar file too large"); /* Paranoid but proper */
         return;
     }
 
     ret = read(tar_fd, tarbuf, filesize(tar_fd));
-    if (ret < 0) {
+    if (ret < 0)
+    {
         printf("couldn't read tar file (%d)", ret);
         return;
     }
@@ -131,153 +230,91 @@
     }
 }
 
-void main(void)
+/* Look for a tar file or rockbox binary in the MTP directory */
+static void handle_untar(void)
 {
     char buf[MAX_PATH];
     char tarstring[6];
     char model[5];
-
-    /* Flush and invalidate all caches (because vectors were written) */
-    invalidate_icache();
-
-    lcd_clear_display();
-    printf("Gigabeat S Rockbox Bootloader v.00000013");
-    system_init();
-    kernel_init();
-    printf("kernel init done");
-    int rc;
-
-    enable_interrupt(IRQ_FIQ_STATUS);
-
-    rc = ata_init();
-    if(rc)
-    {
-        reset_screen();
-        error(EATA, rc);
-    }
-    printf("ata init done");
-
-    disk_init();
-    printf("disk init done");
-
-    rc = disk_mount_all();
-    if (rc<=0)
-    {
-        error(EDISK,rc);
-    }
-
-    /* Look for a tar file */
     struct dirent_uncached* entry;
     DIR_UNCACHED* dir;
     int fd;
+    int rc;
+
     dir = opendir_uncached(basedir);
+
     while ((entry = readdir_uncached(dir)))
     {
-        if (*entry->d_name != '.')
-        {
-            snprintf(buf, sizeof(buf), "%s%s", basedir, entry->d_name);
-            fd = open(buf, O_RDONLY);
-            if (fd >= 0)
-            {
-                /* Check whether the file is a rockbox binary. */
-                lseek(fd, 4, SEEK_SET);
-                rc = read(fd, model, 4);
-                if (rc == 4)
-                {
-                    model[4] = 0;
-                    if (strcmp(model, "gigs") == 0)
-                    {
-                        printf("Found rockbox binary. Moving...");
-                        close(fd);
-                        remove("/.rockbox/rockbox.gigabeat");
-                        int ret = rename(buf, "/.rockbox/rockbox.gigabeat");
-                        printf("returned %d", ret);
-                        sleep(HZ);
-                        break;
-                    }
-                }
+        if (*entry->d_name == '.')
+            continue;
 
-                /* Check whether the file is a tar file. */
-                lseek(fd, 257, SEEK_SET);
-                rc = read(fd, tarstring, 5);
-                if (rc == 5)
-                {
-                    tarstring[5] = 0;
-                    if (strcmp(tarstring, "ustar") == 0)
-                    {
-                        printf("Found tar file. Unarchiving...");
-                        lseek(fd, 0, SEEK_SET);
-                        untar(fd);
-                        close(fd);
-                        printf("Removing tar file");
-                        remove(buf);
-                        break;
-                    }
-                }
+        snprintf(buf, sizeof(buf), "%s%s", basedir, entry->d_name);
+        fd = open(buf, O_RDONLY);
+
+        if (fd < 0)
+            continue;
+
+        /* Check whether the file is a rockbox binary. */
+        lseek(fd, 4, SEEK_SET);
+        rc = read(fd, model, 4);
+        if (rc == 4)
+        {
+            model[4] = 0;
+            if (strcmp(model, "gigs") == 0)
+            {
+                printf("Found rockbox binary. Moving...");
                 close(fd);
+                remove("/.rockbox/rockbox.gigabeat");
+                int ret = rename(buf, "/.rockbox/rockbox.gigabeat");
+                printf("returned %d", ret);
+                sleep(HZ);
+                break;
             }
         }
-    }
 
-    if (usb_plugged())
-    {
-        /* Enter USB mode  */
-        struct queue_event ev;
-        queue_init(&usb_wait_queue, true);
-
-        /* Start the USB driver */
-        usb_init();
-        usb_start_monitoring();
-
-        /* Wait for threads to connect or cable is pulled */
-        reset_screen();
-        show_splash(0, "Waiting for USB");
-
-        while (1)
+        /* Check whether the file is a tar file. */
+        lseek(fd, 257, SEEK_SET);
+        rc = read(fd, tarstring, 5);
+        if (rc == 5)
         {
-            queue_wait_w_tmo(&usb_wait_queue, &ev, HZ/2);
-
-            if (ev.id == SYS_USB_CONNECTED)
-                break; /* Hit */
-
-            if (!usb_plugged())
-                break; /* Cable pulled */
+            tarstring[5] = 0;
+            if (strcmp(tarstring, "ustar") == 0)
+            {
+                printf("Found tar file. Unarchiving...");
+                lseek(fd, 0, SEEK_SET);
+                untar(fd);
+                close(fd);
+                printf("Removing tar file");
+                remove(buf);
+                break;
+            }
         }
 
-        if (ev.id == SYS_USB_CONNECTED)
-        {
-            /* Got the message - wait for disconnect */
-            reset_screen();
-            show_splash(0, "Bootloader USB mode");
-
-            usb_acknowledge(SYS_USB_CONNECTED_ACK);
-            usb_wait_for_disconnect(&usb_wait_queue);
-        }
-
-        /* No more monitoring */
-        usb_stop_monitoring();
-
-        reset_screen();
+        close(fd);
     }
-    else
-    {
-        /* Bang on the controller */
-        usb_init_device();
-    }
+}
 
-    unsigned char *loadbuffer = (unsigned char *)0x0;
-    int buffer_size = 31*1024*1024;
+/* Try to load the firmware and run it */
+static void __attribute__((noreturn)) handle_firmware_load(void)
+{
+    int rc = load_firmware(load_buf, "/.rockbox/rockbox.gigabeat",
+                           load_buf_size);
 
-    rc = load_firmware(loadbuffer, "/.rockbox/rockbox.gigabeat", buffer_size);
     if(rc < 0)
         error(EBOOTFILE, rc);
 
+    /* Pause to look at messages */
+    pause_if_button_pressed(false);
+
+    /* Put drivers into a known state */
+    button_close_device();
+    ata_close();
     system_prepare_fw_start();
 
     if (rc == EOK)
     {
         invalidate_icache();
-        asm volatile ("bx %0": : "r"(loadbuffer));
+        asm volatile ("bx %0": : "r"(start_addr));
     }
 
     /* Halt */
@@ -285,3 +322,55 @@
         core_idle();
 }
 
+static void check_battery(void)
+{
+    int batt = battery_adc_voltage();
+    printf("Battery: %d.%03d V", batt / 1000, batt % 1000);
+    /* TODO: warn on low battery or shut down */
+}
+
+void main(void)
+{
+    int rc;
+
+    /* Flush and invalidate all caches (because vectors were written) */
+    invalidate_icache();
+
+    lcd_clear_display();
+    printf("Gigabeat S Rockbox Bootloader");
+    printf("Version %s", version);
+    system_init();
+    kernel_init();
+
+    enable_interrupt(IRQ_FIQ_STATUS);
+
+    /* Initialize KPP so we can poll the button states */
+    button_init_device();
+
+    adc_init();
+
+    check_battery();
+
+    rc = ata_init();
+    if(rc)
+    {
+        reset_screen();
+        error(EATA, rc);
+    }
+
+    disk_init();
+
+    rc = disk_mount_all();
+    if (rc<=0)
+    {
+        error(EDISK,rc);
+    }
+
+    printf("Init complete");
+
+    /* Do USB first since a tar or binary could be added to the MTP directory
+     * at the time and we can untar or move after unplugging. */
+    handle_usb();
+    handle_untar();
+    handle_firmware_load(); /* No return */
+}