Sansav2 Bootloader

Adds read-only SD driver, largely copied from ata-sd-pp.c
Only tested on the embedded SD, on the Clip
First steps to build a Normal firmware

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19045 a1c6a512-1295-4272-9138-f99709370657
diff --git a/bootloader/sansa_as3525.c b/bootloader/sansa_as3525.c
index 6340fbb..1385e47 100644
--- a/bootloader/sansa_as3525.c
+++ b/bootloader/sansa_as3525.c
@@ -31,43 +31,71 @@
 #include "as3525-codec.h"
 #include "common.h"
 #include "storage.h"
+#include "disk.h"
+#include "panic.h"
 
 int show_logo(void);
 void main(void)
 {
-    int i;
-    unsigned char buf[8];
+    unsigned char* loadbuffer;
+    int buffer_size;
+    void(*kernel_entry)(void);
+    int ret;
+    int delay;
 
     system_init();
     kernel_init();
 
     lcd_init();
-
     show_logo();
 
     as3525_codec_init();  /* Required for backlight on e200v2 */
-
     _backlight_on();
 
-    /* show player id to demonstrate communication with codec part */
-    for (i = 0; i < 8; i++) {
-        buf[i] = as3525_codec_read(0x38 + i);
+    delay = 0x3000000;
+    while(delay--); /* show splash screen */
+    reset_screen();
+
+    asm volatile(
+            "mrs r0, cpsr             \n"
+            "bic r0, r0, #0x80        \n" /* enable interrupts */
+            "msr cpsr, r0             \n"
+            : : : "r0" );
+
+    ret = storage_init();
+    if(ret < 0)
+        error(EATA,ret);
+
+    if(!disk_init(IF_MV(0)))
+        panicf("disk_init failed!");
+
+    ret = disk_mount_all();
+
+    if(ret <= 0)
+        error(EDISK, ret);
+
+    printf("Loading firmware");
+
+    loadbuffer = (unsigned char*)0x30000000; /* DRAM */
+    buffer_size = (int)(loadbuffer + (MEM * 0x100000));
+
+    ret = load_firmware(loadbuffer, BOOTFILE, buffer_size);
+    if(ret < 0)
+        error(EBOOTFILE, ret);
+
+    asm volatile(
+            "mrs r0, cpsr             \n"
+            "orr r0, r0, #0x80        \n" /* disable interrupts */
+            "msr cpsr, r0             \n"
+            : : : "r0" );
+
+    if (ret == EOK)
+    {
+        kernel_entry = (void*) loadbuffer;
+        printf("Executing");
+        kernel_entry();
+        printf("ERR: Failed to boot");
     }
-    printf("ID: %02X%02X%02X%02X%02X%02X%02X%02X", buf[7], buf[6], buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]);
-
-    storage_init();
-
-#ifdef SANSA_CLIP
-    /* Use hardware scrolling */
-
-    lcd_write_command(0x26); /* scroll setup */
-    lcd_write_command(0x01); /* columns scrolled per step */
-    lcd_write_command(0x00); /* start page */
-    lcd_write_command(0x00); /* steps freqency */
-    lcd_write_command(0x07); /* end page (including) */
-
-    lcd_write_command(0x2F); /* start horizontal scrolling */
-#endif
 
     /* never returns */
     while(1) ;
diff --git a/firmware/export/config-clip.h b/firmware/export/config-clip.h
index cfeaea7..9bc4a14 100644
--- a/firmware/export/config-clip.h
+++ b/firmware/export/config-clip.h
@@ -6,7 +6,7 @@
 /* For Rolo and boot loader */
 #define MODEL_NUMBER 40
 #define MODEL_NAME   "Sandisk Sansa Clip"
-#define FIRMWARE_OFFSET_FILE_DATA 0
+#define FIRMWARE_OFFSET_FILE_DATA 8
 #define FIRMWARE_OFFSET_FILE_CRC 0
 
 #define HW_SAMPR_CAPS       (SAMPR_CAP_44)
@@ -84,7 +84,11 @@
 #define CODEC_SIZE 0x100000
 
 /* The number of bytes reserved for loadable plugins */
+#if 0 /* The plugin buffer doesn't fit in the 2MB memory */
 #define PLUGIN_BUFFER_SIZE 0x80000
+#else
+#define PLUGIN_BUFFER_SIZE 0
+#endif
 
 #define AB_REPEAT_ENABLE 1
 
@@ -137,8 +141,8 @@
 #define CONFIG_LCD LCD_SSD1303
 
 #ifndef BOOTLOADER
-#define HAVE_MULTIVOLUME
-#define HAVE_HOTSWAP
+
+#if 0 /* disabled since there is no USB driver */
 
 /* USB On-the-go */
 #define CONFIG_USBOTG USBOTG_ARC
@@ -149,6 +153,8 @@
 #define USB_PRODUCT_ID 0x7433
 #endif /* BOOTLOADER */
 
+#endif
+
 /* Virtual LED (icon) */
 #define CONFIG_LED LED_VIRTUAL
 
diff --git a/firmware/export/usb.h b/firmware/export/usb.h
index 08ae279..4275fa9 100644
--- a/firmware/export/usb.h
+++ b/firmware/export/usb.h
@@ -63,6 +63,7 @@
 #define USBPOWER_BTN_IGNORE BUTTON_POWER
 #elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
       (CONFIG_KEYPAD == SANSA_C200_PAD) || \
+      (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \
       (CONFIG_KEYPAD == PHILIPS_SA9200_PAD)
 #define USBPOWER_BUTTON BUTTON_SELECT
 #define USBPOWER_BTN_IGNORE BUTTON_POWER
diff --git a/firmware/target/arm/as3525/app.lds b/firmware/target/arm/as3525/app.lds
new file mode 100644
index 0000000..08b119e
--- /dev/null
+++ b/firmware/target/arm/as3525/app.lds
@@ -0,0 +1,126 @@
+#include "config.h"
+
+ENTRY(start)
+
+OUTPUT_FORMAT(elf32-littlearm)
+OUTPUT_ARCH(arm)
+STARTUP(target/arm/crt0.o)
+
+#define PLUGINSIZE PLUGIN_BUFFER_SIZE
+#define CODECSIZE CODEC_SIZE
+
+#ifdef DEBUG
+#define STUBOFFSET 0x10000
+#else
+#define STUBOFFSET 0
+#endif
+
+#include "cpu.h"
+#define IRAMSIZE 0x50000
+#define DRAMSIZE (MEMORYSIZE * 0x100000) - STUBOFFSET - PLUGINSIZE - CODECSIZE
+
+#define IRAMORIG 0x0
+#define DRAMORIG 0x30000000 + STUBOFFSET
+
+/* End of the audio buffer, where the codec buffer starts */
+#define ENDAUDIOADDR  (DRAMORIG + DRAMSIZE)
+
+/* Where the codec buffer ends, and the plugin buffer starts */
+#define ENDADDR (ENDAUDIOADDR + CODECSIZE)
+
+MEMORY
+{
+    IRAM : ORIGIN = IRAMORIG, LENGTH = IRAMSIZE
+    DRAM : ORIGIN = DRAMORIG, LENGTH = DRAMSIZE
+}
+
+SECTIONS
+{
+    loadaddress = 0x30000000;
+
+    .vectors DRAMORIG :
+    {
+        _vectorstart = .;
+        *(.vectors*);
+        *(.init.text)
+        . = ALIGN(0x4);
+    } > DRAM
+
+    .text :
+    {
+        _textstart = .;
+        *(.text)
+        *(.text*)
+        *(.icode)
+        *(.glue_7)
+        *(.glue_7t)
+        . = ALIGN(0x4);
+    } > DRAM
+
+    .rodata :
+    {
+        *(.rodata)  /* problems without this, dunno why */
+        *(.rodata*)
+        *(.rodata.str1.1)
+        *(.rodata.str1.4)
+        *(.irodata*)
+        . = ALIGN(0x4);
+    } > DRAM
+
+    .data :
+    {
+        *(.data*)
+        *(.idata*)
+        . = ALIGN(0x4);
+    } > DRAM
+
+    /DISCARD/ :
+    {
+        *(.eh_frame)
+    }
+
+    _initdata_end =.;
+
+    .stack (NOLOAD) :
+    {
+       *(.stack)
+       stackbegin = .;
+       . += 0x2000;
+       stackend = .;
+    } > DRAM
+
+    .bss (NOLOAD) :
+    {
+       _edata = .;
+        *(.bss*)
+        *(.ibss*)
+        *(COMMON)
+        . = ALIGN(0x4);
+       _end = .;
+    } > DRAM
+
+    .audiobuf (NOLOAD) :
+    {
+        . = ALIGN(4);
+        _audiobuffer = .;
+        audiobuffer = .;
+    } > DRAM
+
+    .audiobufend ENDAUDIOADDR (NOLOAD) :
+    {
+        audiobufend = .;
+        _audiobufend = .;
+    } > DRAM
+
+    .codec ENDAUDIOADDR  (NOLOAD) :
+    {
+        codecbuf = .;
+        _codecbuf = .;
+    }
+
+    .plugin ENDADDR (NOLOAD) :
+    {
+        _pluginbuf = .;
+        pluginbuf = .;
+    }
+}
diff --git a/firmware/target/arm/as3525/ata_sd_as3525.c b/firmware/target/arm/as3525/ata_sd_as3525.c
index 77d1ec1..18cbb13 100644
--- a/firmware/target/arm/as3525/ata_sd_as3525.c
+++ b/firmware/target/arm/as3525/ata_sd_as3525.c
@@ -7,6 +7,7 @@
  *                     \/            \/     \/    \/            \/
  * $Id$
  *
+ * Copyright (C) 2006 Daniel Ankers
  * Copyright © 2008 Rafaël Carré
  *
  * This program is free software; you can redistribute it and/or
@@ -22,19 +23,31 @@
 /* Driver for the ARM PL180 SD/MMC controller inside AS3525 SoC */
 
 #include "config.h" /* for HAVE_MULTIVOLUME */
-
+#include "fat.h"
+#include "thread.h"
+#include "hotswap.h"
+#include "system.h"
+#include "cpu.h"
+#include <stdlib.h>
 #include "as3525.h"
 #include "pl180.h"
 #include "panic.h"
 #include "stdbool.h"
+#include "ata_idle_notify.h"
 #include "sd.h"
 
-#define     NAND_AS3525 0
-#define     SD_AS3525   1
-static const int pl180_base[2] = { NAND_FLASH_BASE, SD_MCI_BASE };
+#ifdef HAVE_HOTSWAP
+#include "disk.h"
+#endif
+
+/* command flags */
+#define MMC_NO_FLAGS    (0<<0)
+#define MMC_RESP        (1<<0)
+#define MMC_LONG_RESP   (1<<1)
+#define MMC_ARG         (1<<2)
 
 /* ARM PL180 registers */
-#define MMC_POWER(i)       (*(volatile unsigned long *) (pl180_base[i]+0x00))
+#define MMC_POWER(i)       (*(volatile unsigned char *) (pl180_base[i]+0x00))
 #define MMC_CLOCK(i)       (*(volatile unsigned long *) (pl180_base[i]+0x04))
 #define MMC_ARGUMENT(i)    (*(volatile unsigned long *) (pl180_base[i]+0x08))
 #define MMC_COMMAND(i)     (*(volatile unsigned long *) (pl180_base[i]+0x0C))
@@ -43,41 +56,46 @@
 #define MMC_RESP1(i)       (*(volatile unsigned long *) (pl180_base[i]+0x18))
 #define MMC_RESP2(i)       (*(volatile unsigned long *) (pl180_base[i]+0x1C))
 #define MMC_RESP3(i)       (*(volatile unsigned long *) (pl180_base[i]+0x20))
-#define MMC_DATACTRL(i)    (*(volatile unsigned long *) (pl180_base[i]+0x2C))
+#define MMC_DATA_TIMER(i)  (*(volatile unsigned long *) (pl180_base[i]+0x24))
+#define MMC_DATA_LENGTH(i) (*(volatile unsigned short*) (pl180_base[i]+0x28))
+#define MMC_DATA_CTRL(i)   (*(volatile unsigned char *) (pl180_base[i]+0x2C))
+#define MMC_DATA_CNT(i)    (*(volatile unsigned short*) (pl180_base[i]+0x30))
 #define MMC_STATUS(i)      (*(volatile unsigned long *) (pl180_base[i]+0x34))
 #define MMC_CLEAR(i)       (*(volatile unsigned long *) (pl180_base[i]+0x38))
 #define MMC_MASK0(i)       (*(volatile unsigned long *) (pl180_base[i]+0x3C))
 #define MMC_MASK1(i)       (*(volatile unsigned long *) (pl180_base[i]+0x40))
 #define MMC_SELECT(i)      (*(volatile unsigned long *) (pl180_base[i]+0x44))
+#define MMC_FIFO_CNT(i)    (*(volatile unsigned long *) (pl180_base[i]+0x48))
 
+#define MMC_FIFO(i)        ((unsigned long *) (pl180_base[i]+0x80))
+/* volumes */
+#define     NAND_AS3525 0
+#define     SD_AS3525   1
 
-/* SD commands */
-#define GO_IDLE_STATE       0
-#define MMC_CMD_READ_CID    2
-#define SEND_IF_COND        8
-#define SEND_OP_COND        41
-#define APP_CMD             55
-
-/* command flags */
-#define MMC_NO_FLAGS    (0<<0)
-#define MMC_RESP        (1<<0)
-#define MMC_LONG_RESP   (1<<1)
-#define MMC_ARG         (1<<2)
-
-#ifdef BOOTLOADER
-#define DEBUG
-void reset_screen(void);
-void printf(const char *format, ...);
+static const int pl180_base[NUM_VOLUMES] = {
+            NAND_FLASH_BASE
+#ifdef HAVE_MULTIVOLUME
+            , SD_MCI_BASE
 #endif
-
-struct mmc_command
-{
-    int cmd;
-    int arg;
-    int resp[4];
-    int flags;
 };
 
+#define BLOCK_SIZE      512
+#define SECTOR_SIZE     512
+
+static tSDCardInfo card_info[NUM_VOLUMES];
+
+/* for compatibility */
+static long last_disk_activity = -1;
+
+#define MIN_YIELD_PERIOD 1000
+static long next_yield = 0;
+
+/* Shoot for around 75% usage */
+static long sd_stack [(DEFAULT_STACK_SIZE*2 + 0x1c0)/sizeof(long)];
+static const char         sd_thread_name[] = "ata/sd";
+static struct mutex       sd_mtx SHAREDBSS_ATTR;
+static struct event_queue sd_queue;
+
 static inline void mci_delay(void) { int i = 0xffff; while(i--) ; }
 
 static void mci_set_clock_divider(const int drive, int divider)
@@ -106,11 +124,12 @@
     mci_delay();
 }
 
-static int send_cmd(const int drive, struct mmc_command *cmd)
+static bool send_cmd(const int drive, const int cmd, const int arg,
+                     const int flags, int *response)
 {
     int val, status;
 
-    while(MMC_STATUS(drive) & MCI_CMD_ACTIVE); /* useless */
+    while(MMC_STATUS(drive) & MCI_CMD_ACTIVE);
 
     if(MMC_COMMAND(drive) & MCI_COMMAND_ENABLE) /* clears existing command */
     {
@@ -118,152 +137,226 @@
         mci_delay();
     }
 
-    val = cmd->cmd | MCI_COMMAND_ENABLE;
-    if(cmd->flags & MMC_RESP)
+    val = cmd | MCI_COMMAND_ENABLE;
+    if(flags & MMC_RESP)
     {
         val |= MCI_COMMAND_RESPONSE;
-        if(cmd->flags & MMC_LONG_RESP)
+        if(flags & MMC_LONG_RESP)
             val |= MCI_COMMAND_LONG_RESPONSE;
     }
 
     MMC_CLEAR(drive) = 0x7ff;
 
-    MMC_ARGUMENT(drive) = (cmd->flags & MMC_ARG) ? cmd->arg : 0;
+    MMC_ARGUMENT(drive) = (flags & MMC_ARG) ? arg : 0;
     MMC_COMMAND(drive) = val;
 
-    while(MMC_STATUS(drive) & MCI_CMD_ACTIVE);
+    while(MMC_STATUS(drive) & MCI_CMD_ACTIVE);  /* wait for cmd completion */
 
     MMC_COMMAND(drive) = 0;
     MMC_ARGUMENT(drive) = ~0;
 
-    do
-    {
-        status = MMC_STATUS(drive);
-        if(cmd->flags & MMC_RESP)
-        {
-            if(status & MCI_CMD_TIMEOUT)
-            {
-                if(cmd->cmd == SEND_IF_COND)
-                    break; /* SDHC test can fail */
-                panicf("Response timeout");
-            }
-            else if(status & (MCI_CMD_CRC_FAIL|MCI_CMD_RESP_END))
-            {   /* resp received */
-                cmd->resp[0] = MMC_RESP0(drive);
-                if(cmd->flags & MMC_LONG_RESP)
-                {
-                    cmd->resp[1] = MMC_RESP1(drive);
-                    cmd->resp[2] = MMC_RESP2(drive);
-                    cmd->resp[3] = MMC_RESP3(drive);
-                }
-                break;
-            }
-        }
-        else
-            if(status & MCI_CMD_SENT)
-                break;
-
-    } while(1);
-
+    status = MMC_STATUS(drive);
     MMC_CLEAR(drive) = 0x7ff;
-    return status;
+
+    if(flags & MMC_RESP)
+    {
+        if(status & MCI_CMD_TIMEOUT)
+            return false;
+        else if(status & (MCI_CMD_CRC_FAIL /* FIXME? */ | MCI_CMD_RESP_END))
+        {   /* resp received */
+            if(flags & MMC_LONG_RESP)
+            {
+                /* store the response in little endian order for the words */
+                response[0] = MMC_RESP3(drive);
+                response[1] = MMC_RESP2(drive);
+                response[2] = MMC_RESP1(drive);
+                response[3] = MMC_RESP0(drive);
+            }
+            else
+                response[0] = MMC_RESP0(drive);
+            return true;
+        }
+    }
+    else if(status & MCI_CMD_SENT)
+        return true;
+
+    return false;
 }
 
-static void sd_init_card(const int drive)
+static int sd_init_card(const int drive)
 {
-    struct mmc_command cmd_app, cmd_op_cond, cmd_idle, cmd_if_cond;
-    int status;
+    unsigned int  c_size;
+    unsigned long c_mult;
+
+    int response;
+    int max_tries = 100; /* max acmd41 attemps */
     bool sdhc;
 
-#ifdef DEBUG
-    reset_screen();
-    printf("now - powered up");
-#endif
-
-    cmd_idle.cmd = GO_IDLE_STATE;
-    cmd_idle.arg = 0;
-    cmd_idle.flags = MMC_NO_FLAGS;
-    if(send_cmd(drive, &cmd_idle) != MCI_CMD_SENT)
-        panicf("goto idle failed!");
-#ifdef DEBUG
-    else
-        printf("now - idle");
-#endif
+    if(!send_cmd(drive, GO_IDLE_STATE, 0, MMC_NO_FLAGS, NULL))
+        return -1;
 
     mci_delay();
 
-    cmd_if_cond.cmd = SEND_IF_COND;
-    cmd_if_cond.arg = (1 /* 2.7-3.6V */ << 8) | 0xAA /* check pattern */;
-    cmd_if_cond.flags = MMC_RESP | MMC_ARG;
-
-    cmd_app.cmd = APP_CMD;
-    cmd_app.flags = MMC_RESP | MMC_ARG;
-    cmd_app.arg = 0; /* 31:16 RCA (0) , 15:0 stuff bits */
-
-    cmd_op_cond.cmd = SEND_OP_COND;
-    cmd_op_cond.flags = MMC_RESP | MMC_ARG;
-
-#ifdef DEBUG
-    printf("now - card powering up");
-#endif
-
     sdhc = false;
-    status = send_cmd(drive, &cmd_if_cond);
-    if(status & (MCI_CMD_CRC_FAIL|MCI_CMD_RESP_END))
-    {
-        if((cmd_if_cond.resp[0] & 0xFFF) == cmd_if_cond.arg)
+    if(send_cmd(drive, SEND_IF_COND, 0x1AA, MMC_RESP|MMC_ARG, &response))
+        if((response & 0xFFF) == 0x1AA)
             sdhc = true;
-#ifdef DEBUG
-        else
-            printf("Bad resp: %x",cmd_if_cond.arg);
-#endif
-    }
-#ifdef DEBUG
-    else
-        printf("cmd_if_cond stat: 0x%x",status);
 
-    printf("%s Capacity",sdhc?"High":"Normal");
-    mci_delay();
-    mci_delay();
-    mci_delay();
-#endif
-
-#ifdef DEBUG
-    int loop = 0;
-#endif
     do {
         mci_delay();
-        mci_delay();
-#ifdef DEBUG
-        reset_screen();
-        printf("Loop number #%d", ++loop);
-#endif
+
         /* app_cmd */
-        status = send_cmd(drive, &cmd_app);
-        if( !(status & (MCI_CMD_CRC_FAIL|MCI_CMD_RESP_END)) ||
-            !(cmd_app.resp[0] & (1<<5)) )
+        if( !send_cmd(drive, APP_CMD, 0, MMC_RESP|MMC_ARG, &response) ||
+            !(response & (1<<5)) )
         {
-            panicf("app_cmd failed");
+            return -2;
         }
 
-        cmd_op_cond.arg = sdhc ? 0x40FF8000 : (8<<0x14); /* ocr */
-        status = send_cmd(drive, &cmd_op_cond);
-        if(!(status & (MCI_CMD_CRC_FAIL|MCI_CMD_RESP_END)))
-            panicf("cmd_op_cond failed");
+        /* acmd41 */
+        if(!send_cmd(drive, SD_APP_OP_COND, (sdhc ? 0x40FF8000 : (1<<23)),
+                        MMC_RESP|MMC_ARG, &card_info[drive].ocr))
+            return -3;
 
-#ifdef DEBUG
-        printf("OP COND: 0x%.8x", cmd_op_cond.resp[0]);
-#endif
-    } while(!(cmd_op_cond.resp[0] & (1<<31))); /* until card is powered up */
+    } while(!(card_info[drive].ocr & (1<<31)) && max_tries--);
 
-#ifdef DEBUG
-    printf("now - card ready !");
+    if(!max_tries)
+        return -4;
+
+    /* send CID */
+    if(!send_cmd(drive, ALL_SEND_CID, 0, MMC_RESP|MMC_LONG_RESP|MMC_ARG,
+                            card_info[drive].cid))
+        return -5;
+
+    /* send RCA */
+    if(!send_cmd(drive, SEND_RELATIVE_ADDR, 0, MMC_RESP|MMC_ARG,
+                &card_info[drive].rca))
+        return -6;
+
+    /* send CSD */
+    if(!send_cmd(drive, SEND_CSD, card_info[drive].rca,
+                 MMC_RESP|MMC_LONG_RESP|MMC_ARG, card_info[drive].csd))
+        return -7;
+
+    /* These calculations come from the Sandisk SD card product manual */
+    if( (card_info[drive].csd[3]>>30) == 0)
+    {
+        /* CSD version 1.0 */
+        c_size = ((card_info[drive].csd[2] & 0x3ff) << 2) + (card_info[drive].csd[1]>>30) + 1;
+        c_mult = 4 << ((card_info[drive].csd[1] >> 15) & 7);
+        card_info[drive].max_read_bl_len = 1 << ((card_info[drive].csd[2] >> 16) & 15);
+        card_info[drive].block_size = BLOCK_SIZE;     /* Always use 512 byte blocks */
+        card_info[drive].numblocks = c_size * c_mult * (card_info[drive].max_read_bl_len/512);
+        card_info[drive].capacity = card_info[drive].numblocks * card_info[drive].block_size;
+    }
+#ifdef HAVE_MULTIVOLUME
+    else if( (card_info[drive].csd[3]>>30) == 1)
+    {
+        /* CSD version 2.0 */
+        c_size = ((card_info[drive].csd[2] & 0x3f) << 16) + (card_info[drive].csd[1]>>16) + 1;
+        card_info[drive].max_read_bl_len = 1 << ((card_info[drive].csd[2] >> 16) & 0xf);
+        card_info[drive].block_size = BLOCK_SIZE;     /* Always use 512 byte blocks */
+        card_info[drive].numblocks = c_size << 10;
+        card_info[drive].capacity = card_info[drive].numblocks * card_info[drive].block_size;
+    }
 #endif
+
+    if(!send_cmd(drive, SELECT_CARD, card_info[drive].rca, MMC_ARG, NULL))
+        return -9;
+
+    if(!send_cmd(drive, APP_CMD, card_info[drive].rca, MMC_ARG, NULL))
+        return -10;
+
+    if(!send_cmd(drive, SET_BUS_WIDTH, card_info[drive].rca | 2, MMC_ARG, NULL))
+        return -11;
+
+    if(!send_cmd(drive, SET_BLOCKLEN, card_info[drive].block_size, MMC_ARG,
+                 NULL))
+        return -12;
+
+    card_info[drive].initialized = 1;
+
+    mci_set_clock_divider(drive, 1); /* full speed */
+
+    return 0;
 }
 
+static void sd_thread(void) __attribute__((noreturn));
+static void sd_thread(void)
+{
+    struct queue_event ev;
+    bool idle_notified = false;
+
+    while (1)
+    {
+        queue_wait_w_tmo(&sd_queue, &ev, HZ);
+
+        switch ( ev.id )
+        {
+#ifdef HAVE_HOTSWAP
+        case SYS_HOTSWAP_INSERTED:
+        case SYS_HOTSWAP_EXTRACTED:
+            fat_lock();          /* lock-out FAT activity first -
+                                    prevent deadlocking via disk_mount that
+                                    would cause a reverse-order attempt with
+                                    another thread */
+            mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
+                                    into driver that bypass the fat cache */
+
+            /* We now have exclusive control of fat cache and ata */
+
+            disk_unmount(1);     /* release "by force", ensure file
+                                    descriptors aren't leaked and any busy
+                                    ones are invalid if mounting */
+
+            /* Force card init for new card, re-init for re-inserted one or
+             * clear if the last attempt to init failed with an error. */
+            card_info[1].initialized = 0;
+
+            if (ev.id == SYS_HOTSWAP_INSERTED)
+                disk_mount(1);
+
+            queue_broadcast(SYS_FS_CHANGED, 0);
+
+            /* Access is now safe */
+            mutex_unlock(&sd_mtx);
+            fat_unlock();
+            break;
+#endif
+        case SYS_TIMEOUT:
+            if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
+            {
+                idle_notified = false;
+            }
+            else
+            {
+                /* never let a timer wrap confuse us */
+                next_yield = current_tick;
+
+                if (!idle_notified)
+                {
+                    call_storage_idle_notifys(false);
+                    idle_notified = true;
+                }
+            }
+            break;
+#if 0
+        case SYS_USB_CONNECTED:
+            usb_acknowledge(SYS_USB_CONNECTED_ACK);
+            /* Wait until the USB cable is extracted again */
+            usb_wait_for_disconnect(&sd_queue);
+
+            break;
+        case SYS_USB_DISCONNECTED:
+            usb_acknowledge(SYS_USB_DISCONNECTED_ACK);
+            break;
+#endif
+        }
+    }
+}
 static void init_pl180_controller(const int drive)
 {
-    MMC_COMMAND(drive) = MMC_DATACTRL(drive) = 0;
+#ifdef BOOTLOADER
+    MMC_COMMAND(drive) = MMC_DATA_CTRL(drive) = 0;
     MMC_CLEAR(drive) = 0x7ff;
 
     MMC_MASK0(drive) = MMC_MASK1(drive) = 0;  /* disable all interrupts */
@@ -281,10 +374,16 @@
 
     /* set MCLK divider */
     mci_set_clock_divider(drive, 200);
+#else
+    /* controller already initialized by bootloader */
+    (void)drive;
+#endif /* BOOTLOADER */
 }
 
 int sd_init(void)
 {
+    int ret;
+
     CGU_IDE =   (1<<7)  /* AHB interface enable */  |
                 (1<<6)  /* interface enable */      |
                 (2<<2)  /* clock didiver = 2+1 */   |
@@ -299,23 +398,55 @@
     CCU_IO |= 4;
 
     init_pl180_controller(NAND_AS3525);
-    sd_init_card(NAND_AS3525);
+    ret = sd_init_card(NAND_AS3525);
+    if(ret < 0)
+        return ret;
 
 #ifdef HAVE_MULTIVOLUME
     init_pl180_controller(SD_AS3525);
-    sd_init_card(SD_AS3525);
+    ret = sd_init_card(SD_AS3525);
+    if(ret < 0)
+        return ret;
 #endif
 
+    queue_init(&sd_queue, true);
+    create_thread(sd_thread, sd_stack, sizeof(sd_stack), 0,
+            sd_thread_name IF_PRIO(, PRIORITY_USER_INTERFACE) IF_COP(, CPU));
+
     return 0;
 }
 
-int sd_read_sectors(IF_MV2(int drive,) unsigned long start, int count, void* buf)
+#ifdef STORAGE_GET_INFO
+void sd_get_info(IF_MV2(int drive,) struct storage_info *info)
 {
-    (void)start;
-    (void)count;
-    (void)buf;
-    return 0; /* TODO */
+#ifndef HAVE_MULTIVOLUME
+    const int drive=0;
+#endif
+    info->sector_size=card_info[drive].block_size;
+    info->num_sectors=card_info[drive].numblocks;
+    info->vendor="Rockbox";
+    info->product = (drive == 0) ?  "Internal Storage" : "SD Card Slot";
+    info->revision="0.00";
 }
+#endif
+
+#ifdef HAVE_HOTSWAP
+bool sd_removable(IF_MV_NONVOID(int drive))
+{
+#ifndef HAVE_MULTIVOLUME
+    const int drive=0;
+#endif
+    return (drive==1);
+}
+
+bool sd_present(IF_MV_NONVOID(int drive))
+{
+#ifndef HAVE_MULTIVOLUME
+    const int drive=0;
+#endif
+    return (card_info[drive].initialized && card_info[drive].numblocks > 0);
+}
+#endif
 
 int sd_write_sectors(IF_MV2(int drive,) unsigned long start, int count, const void* buf)
 {
@@ -324,3 +455,178 @@
     (void)buf;
     return 0; /* TODO */
 }
+
+static bool sd_poll_status(const int drive, unsigned int trigger, long timeout)
+{
+    long t = current_tick;
+
+    while ((MMC_STATUS(drive) & trigger) == 0)
+    {
+        long time = current_tick;
+
+        if (TIME_AFTER(time, next_yield))
+        {
+            long ty = current_tick;
+            yield();
+            timeout += current_tick - ty;
+            next_yield = ty + MIN_YIELD_PERIOD;
+        }
+
+        if (TIME_AFTER(time, t + timeout))
+            return false;
+    }
+
+    return true;
+}
+
+static int sd_wait_for_state(const int drive, unsigned int state)
+{
+    unsigned int response = 0;
+    unsigned int timeout = 0x80000;
+
+    long t = current_tick;
+
+    while (1)
+    {
+        long us;
+
+        if(!send_cmd(drive, SEND_STATUS, card_info[drive].rca,
+                    MMC_RESP|MMC_ARG, &response))
+            return -1;
+
+        if (((response >> 9) & 0xf) == state)
+            return 0;
+
+        if(TIME_AFTER(current_tick, t + timeout))
+            return -1;
+
+        us = current_tick;
+        if (TIME_AFTER(us, next_yield))
+        {
+            yield();
+            timeout += current_tick - us;
+            next_yield = us + MIN_YIELD_PERIOD;
+        }
+    }
+}
+
+int sd_read_sectors(IF_MV2(int drive,) unsigned long start, int incount,
+                     void* inbuf)
+{
+#ifndef HAVE_MULTIVOLUME
+    const int drive = 0;
+#endif
+    int ret;
+    unsigned char *buf_end, *buf = inbuf;
+    int remaining = incount;
+    const unsigned long *fifo_base = MMC_FIFO(drive);
+
+    start += 20480; /* skip SanDisk OF */
+
+    /* TODO: Add DMA support. */
+
+    mutex_lock(&sd_mtx);
+
+#ifdef HAVE_MULTIVOLUME
+    if (drive != 0 && !card_detect_target())
+    {
+        /* no external sd-card inserted */
+        ret = -88;
+        goto sd_read_error;
+    }
+#endif
+
+    if (card_info[drive].initialized < 0)
+    {
+        ret = card_info[drive].initialized;
+        goto sd_read_error;
+    }
+
+    last_disk_activity = current_tick;
+
+    ret = sd_wait_for_state(drive, TRAN);
+    if (ret < 0)
+        goto sd_read_error;
+
+    while(remaining)
+    {
+        /* 128 * 512 = 2^16, and doesn't fit in the 16 bits of DATA_LENGTH
+         * register, so we have to transfer maximum 127 sectors at a time. */
+        int transfer = (remaining >= 128) ? 127 : remaining; /* sectors */
+
+        if(card_info[drive].ocr & (1<<30) ) /* SDHC */
+            ret = send_cmd(drive, READ_MULTIPLE_BLOCK, start, MMC_ARG, NULL);
+        else
+            ret = send_cmd(drive, READ_MULTIPLE_BLOCK, start * BLOCK_SIZE,
+                    MMC_ARG, NULL);
+
+        if (ret < 0)
+            goto sd_read_error;
+
+        /* TODO: Don't assume BLOCK_SIZE == SECTOR_SIZE */
+
+
+        MMC_DATA_TIMER(drive) = 0x1000000; /* FIXME: arbitrary */
+        MMC_DATA_LENGTH(drive) = transfer * card_info[drive].block_size;
+        MMC_DATA_CTRL(drive) =  (1<<0) /* enable */ |
+                                (1<<1) /* from card to controller */ |
+                                (9<<4) /* 2^9 = 512 */ ;
+
+        buf_end = buf + transfer * card_info[drive].block_size;
+
+        while(buf < buf_end)
+        {
+            /* Wait for the FIFO to be half full */
+            if (!sd_poll_status(drive, ((1<<15)), 100))
+            {
+                ret = -42;
+                goto sd_read_error;
+            }
+
+            asm volatile(
+                "ldmia %2,  {r0-r7} \n" /* load  8 * 4 bytes */
+                "stmia %1!, {r0-r7} \n" /* store 8 * 4 bytes */
+                :"=r"(buf)                /* output */
+                :"r"(buf), "r"(fifo_base) /* input */
+                :"r0","r1","r2","r3","r4","r5","r6","r7","r8" /* clobbers */
+            );
+        }
+
+        remaining -= transfer;
+        start += transfer;
+        last_disk_activity = current_tick;
+
+        if(!send_cmd(drive, STOP_TRANSMISSION, 0, MMC_NO_FLAGS, NULL))
+        {
+            ret = -666;
+            goto sd_read_error;
+        }
+
+        ret = sd_wait_for_state(drive, TRAN);
+        if (ret < 0)
+            goto sd_read_error;
+
+    }
+    while (1)
+    {
+        mutex_unlock(&sd_mtx);
+
+        return ret;
+
+sd_read_error:
+        card_info[drive].initialized = 0;
+    }
+}
+
+void sd_sleep(void)
+{
+}
+
+void sd_spin(void)
+{
+}
+
+void sd_spindown(int seconds)
+{
+    (void)seconds;
+}
diff --git a/firmware/target/arm/as3525/kernel-as3525.c b/firmware/target/arm/as3525/kernel-as3525.c
index 73031b9..c534d5e 100644
--- a/firmware/target/arm/as3525/kernel-as3525.c
+++ b/firmware/target/arm/as3525/kernel-as3525.c
@@ -32,12 +32,9 @@
 
 void tick_start(unsigned int interval_in_ms)
 {
-#ifdef BOOTLOADER
-    (void) interval_in_ms;
-#else
     int phi = 0;                            /* prescaler bits */
     int prescale = 1;
-    int cycles = 64000 * interval_in_ms;    /* pclk is clocked at 64MHz */
+    int cycles = 1000 * interval_in_ms;      /* pclk is clocked at 64MHz */
 
     while(cycles > 0x10000)
     {
@@ -57,5 +54,4 @@
     /* /!\ bit 4 (reserved) must not be modified
      * periodic mode, interrupt enabled, 16 bits counter */
     TIMER2_CONTROL = (TIMER2_CONTROL & (1<<4)) | 0xe0 | (phi<<2);
-#endif
 }
diff --git a/firmware/target/arm/as3525/sansa-clip/system-target.h b/firmware/target/arm/as3525/sansa-clip/system-target.h
index b712d1c1..dc9d77f 100644
--- a/firmware/target/arm/as3525/sansa-clip/system-target.h
+++ b/firmware/target/arm/as3525/sansa-clip/system-target.h
@@ -23,6 +23,8 @@
 
 #include "system-arm.h"
 
-#define CPUFREQ_MAX    250000000
+#define CPUFREQ_MAX     250000000
+#define CPUFREQ_DEFAULT 250000000
+#define CPUFREQ_NORMAL  250000000
 
 #endif /* SYSTEM_TARGET_H */
diff --git a/firmware/target/arm/as3525/system-as3525.c b/firmware/target/arm/as3525/system-as3525.c
index 544371e..240cb63 100644
--- a/firmware/target/arm/as3525/system-as3525.c
+++ b/firmware/target/arm/as3525/system-as3525.c
@@ -23,6 +23,7 @@
 #include "kernel.h"
 #include "system.h"
 #include "panic.h"
+#include "as3525-codec.h"
 
 #define default_interrupt(name) \
   extern __attribute__((weak,alias("UIRQ"))) void name (void)
@@ -123,6 +124,7 @@
     );
 }
 
+#ifdef BOOTLOADER
 static void sdram_delay(void)
 {
     int delay = 1024; /* arbitrary */
@@ -192,9 +194,11 @@
 
     MPMC_DYNAMIC_CONFIG_0 |= (1<<19); /* buffer enable */
 }
+#endif
 
 void system_init(void)
 {
+#ifdef BOOTLOADER
 #if 0 /* the GPIO clock is already enabled by the dualboot function */
     CGU_PERI |= CGU_GPIO_CLOCK_ENABLE;
 #endif
@@ -235,6 +239,7 @@
     /* enable VIC */
     CGU_PERI |= CGU_VIC_CLOCK_ENABLE;
     VIC_INT_SELECT = 0; /* only IRQ, no FIQ */
+#endif
 }
 
 void system_reboot(void)
@@ -246,3 +251,15 @@
     (void)newmode;
     return 0;
 }
+
+void power_off(void)
+{
+    int system;
+    system = as3525_codec_read(0x20);
+    system &= ~1; /* clear bit 0 of system register */
+    as3525_codec_write(0x20, system);
+
+    /* TODO : turn off peripherals properly ? */
+
+    while(1);
+}
diff --git a/tools/configure b/tools/configure
index d62f65f..8dbae74 100755
--- a/tools/configure
+++ b/tools/configure
@@ -1688,7 +1688,7 @@
     output="rockbox.sansa"
     bootoutput="bootloader-clip.sansa"
     appextra="recorder:gui"
-    plugins=""
+    plugins="yes"
     swcodec="yes"
     toolset=$scramblebitmaptools
     t_cpu="arm"