first cut at turnon scripts for EasyTimer v2
[fw/altos] / src / drivers / ao_sdcard.c
index 2174af1e546572ca55a1e08562ed8301426233fa..9d36c397552277a2baab9f0fbdd0b00d8824a997 100644 (file)
@@ -3,7 +3,8 @@
  *
  * 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; version 2 of the License.
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
 #include "ao.h"
 #include "ao_sdcard.h"
 
-#define ao_sdcard_get_slow()           ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz)
-#define ao_sdcard_get()                        ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST)
-#define ao_sdcard_put()                        ao_spi_put(AO_SDCARD_SPI_BUS)
+#if HAS_RADIO
+extern uint8_t ao_radio_mutex;
+#define get_radio()    ao_mutex_get(&ao_radio_mutex)
+#define put_radio()    ao_mutex_put(&ao_radio_mutex)
+#else
+#define get_radio()
+#define put_radio()
+#endif
+
+#define ao_sdcard_get_slow() do { get_radio(); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz); } while (0)
+#define ao_sdcard_get()        do { get_radio(); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST); } while (0)
+#define ao_sdcard_put() do { ao_spi_put(AO_SDCARD_SPI_BUS); put_radio(); } while (0)
 #define ao_sdcard_send_fixed(d,l)      ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS)
 #define ao_sdcard_send(d,l)            ao_spi_send((d), (l), AO_SDCARD_SPI_BUS)
 #define ao_sdcard_recv(d,l)            ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS)
-#define ao_sdcard_select()             ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0)
-#define ao_sdcard_deselect()           ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1)
+#define ao_sdcard_select()             ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,0)
+#define ao_sdcard_deselect()           ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,1)
 
+/* Include SD card commands */
+#ifndef SDCARD_DEBUG
+#define SDCARD_DEBUG   0
+#endif
+
+/* Spew SD tracing */
+#ifndef SDCARD_TRACE
+#define SDCARD_TRACE   0
+#endif
+
+/* Emit error and warning messages */
+#ifndef SDCARD_WARN
+#define SDCARD_WARN    0
+#endif
 
 static uint8_t initialized;
 static uint8_t present;
@@ -36,12 +60,44 @@ static enum ao_sdtype sdtype;
 #define ao_sdcard_lock()       ao_mutex_get(&mutex)
 #define ao_sdcard_unlock()     ao_mutex_put(&mutex)
 
-#if 0
+#if SDCARD_TRACE
 #define DBG(...) printf(__VA_ARGS__)
 #else
-#define DBG(...)
+#define DBG(...) (void) 0
+#endif
+
+#if SDCARD_WARN
+#define WARN(...) printf(__VA_ARGS__)
+#else
+#define WARN(...) (void) 0
 #endif
 
+#define later(x,y)     ((int16_t) ((x) - (y)) >= 0)
+
+/*
+ * Wait while the card is busy. The card will return a stream of 0xff
+ * when it is ready to accept a command
+ */
+
+static uint8_t
+ao_sdcard_wait_busy(void)
+{
+       uint16_t        timeout = ao_time() + SDCARD_BUSY_TIMEOUT;
+       uint8_t         reply;
+       for (;;) {
+               ao_sdcard_recv(&reply, 1);
+               DBG("\t\twait busy %02x\n", reply);
+               if (reply == 0xff)
+                       break;
+               if (later(ao_time(), timeout)) {
+                       WARN("wait busy timeout\n");
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+
 /*
  * Send an SD command and await the status reply
  */
@@ -51,20 +107,17 @@ ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg)
 {
        uint8_t data[6];
        uint8_t reply;
-       int i;
+       uint16_t timeout;
 
        DBG ("\tsend_cmd %d arg %08x\n", cmd, arg);
+
+       /* Wait for the card to not be busy */
        if (cmd != SDCARD_GO_IDLE_STATE) {
-               for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) {
-                       ao_sdcard_recv(&reply, 1);
-                       if (reply == 0xff)
-                               break;
-               }
-               if (i == SDCARD_CMD_TIMEOUT)
+               if (!ao_sdcard_wait_busy())
                        return SDCARD_STATUS_TIMEOUT;
        }
        
-       data[0] = cmd & 0x3f | 0x40;
+       data[0] = (cmd & 0x3f) | 0x40;
        data[1] = arg >> 24;
        data[2] = arg >> 16;
        data[3] = arg >> 8;
@@ -80,13 +133,22 @@ ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg)
        /* The first reply byte will be the status,
         * which must have the high bit clear
         */
-       for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) {
+       timeout = ao_time() + SDCARD_CMD_TIMEOUT;
+       for (;;) {
                ao_sdcard_recv(&reply, 1);
                DBG ("\t\tgot byte %02x\n", reply);
                if ((reply & 0x80) == 0)
-                       return reply;
+                       break;
+               if (later(ao_time(), timeout)) {
+                       WARN("send_cmd %02x timeout\n", cmd);
+                       return SDCARD_STATUS_TIMEOUT;
+               }
        }
-       return SDCARD_STATUS_TIMEOUT;
+#if SDCARD_WARN
+       if (reply != SDCARD_STATUS_READY_STATE && reply != SDCARD_STATUS_IDLE_STATE)
+               WARN("send_cmd %d failed %02x\n", cmd, reply);
+#endif
+       return reply;
 }
 
 /*
@@ -104,21 +166,8 @@ ao_sdcard_recv_reply(uint8_t *reply, int len)
 }
 
 /*
- * Wait while the card is busy. The
- * card will return a stream of 0xff
- * until it isn't busy anymore
+ * Switch to 'idle' state. This is used to get the card into SPI mode
  */
-static void
-ao_sdcard_wait_busy(void)
-{
-       uint8_t v;
-
-       do {
-               ao_sdcard_recv(&v, 1);
-       } while (v != 0xff);
-       ao_sdcard_send_fixed(0xff, 1);
-}
-
 static uint8_t
 ao_sdcard_go_idle_state(void)
 {
@@ -170,6 +219,33 @@ ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4])
        return ret;
 }
 
+/*
+ * _ao_sdcard_send_status
+ *
+ * Get the 2-byte status value.
+ *
+ * Called from other functions with CS held low already,
+ * hence prefixing the name with '_'
+ */
+static uint16_t
+_ao_sdcard_send_status(void)
+{
+       uint8_t ret;
+       uint8_t extra;
+
+       DBG ("send_status\n");
+       ret = ao_sdcard_send_cmd(SDCARD_SEND_STATUS, 0);
+       ao_sdcard_recv_reply(&extra, 1);
+       if (ret != SDCARD_STATUS_READY_STATE)
+               DBG ("\tsend_if_cond failed %02x\n", ret);
+       return ret | (extra << 8);
+}
+
+/*
+ * ao_sdcard_set_blocklen
+ *
+ * Set the block length for future read and write commands
+ */
 static uint8_t
 ao_sdcard_set_blocklen(uint32_t blocklen)
 {
@@ -179,22 +255,28 @@ ao_sdcard_set_blocklen(uint32_t blocklen)
        ao_sdcard_select();
        ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen);
        ao_sdcard_recv_reply(NULL, 0);
+       ao_sdcard_deselect();
        if (ret != SDCARD_STATUS_READY_STATE)
                DBG ("\tsend_if_cond failed %02x\n", ret);
        return ret;
-       
 }
 
+/*
+ * _ao_sdcard_app_cmd
+ *
+ * Send the app command prefix
+ *
+ * Called with the CS held low, hence
+ * the '_' prefix
+ */
 static uint8_t
-ao_sdcard_app_cmd(void)
+_ao_sdcard_app_cmd(void)
 {
        uint8_t ret;
 
        DBG ("app_cmd\n");
-       ao_sdcard_select();
        ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0);
        ao_sdcard_recv_reply(NULL, 0);
-       ao_sdcard_deselect();
        DBG ("\tapp_cmd status %02x\n");
        return ret;
 }
@@ -204,13 +286,14 @@ ao_sdcard_app_send_op_cond(uint32_t arg)
 {
        uint8_t ret;
 
-       ret = ao_sdcard_app_cmd();
-       if (ret != SDCARD_STATUS_IDLE_STATE)
-               return ret;
        DBG("send_op_comd\n");
        ao_sdcard_select();
+       ret = _ao_sdcard_app_cmd();
+       if (ret != SDCARD_STATUS_IDLE_STATE)
+               goto bail;
        ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg);
        ao_sdcard_recv_reply(NULL, 0);
+bail:
        ao_sdcard_deselect();
        DBG ("\tapp_send_op_cond status %02x\n", ret);
        return ret;
@@ -236,6 +319,10 @@ ao_sdcard_read_ocr(uint8_t read_ocr_response[4])
        return ret;
 }
 
+/*
+ * Follow the flow-chart defined by the SD group to
+ * initialize the card and figure out what kind it is
+ */
 static void
 ao_sdcard_setup(void)
 {
@@ -251,19 +338,15 @@ ao_sdcard_setup(void)
         */
        ao_sdcard_send_fixed(0xff, 10);
 
-       ao_delay(AO_MS_TO_TICKS(10));
-
        /* Reset the card and get it into SPI mode */
-
-       for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+       for (i = 0; i < SDCARD_IDLE_RETRY; i++) {
                if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE)
                        break;
        }
-       if (i == SDCARD_IDLE_WAIT)
+       if (i == SDCARD_IDLE_RETRY)
                goto bail;
 
        /* Figure out what kind of card we have */
-
        sdtype = ao_sdtype_unknown;
 
        if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) {
@@ -276,14 +359,16 @@ ao_sdcard_setup(void)
                        sdver2 = 1;
                }
 
-               for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+               for (i = 0; i < SDCARD_OP_COND_RETRY; i++) {
+                       ao_delay(AO_MS_TO_TICKS(10));
                        ret = ao_sdcard_app_send_op_cond(arg);
                        if (ret != SDCARD_STATUS_IDLE_STATE)
                                break;
                }
                if (ret != SDCARD_STATUS_READY_STATE) {
                        /* MMC */
-                       for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+                       for (i = 0; i < SDCARD_OP_COND_RETRY; i++) {
+                               ao_delay(AO_MS_TO_TICKS(10));
                                ret = ao_sdcard_send_op_cond();
                                if (ret != SDCARD_STATUS_IDLE_STATE)
                                        break;
@@ -319,18 +404,85 @@ bail:
        ao_sdcard_put();
 }
 
+static uint8_t
+_ao_sdcard_reset(void)
+{
+       int i;
+       uint8_t ret = 0x3f;
+       uint8_t response[10];
+
+       for (i = 0; i < SDCARD_IDLE_RETRY; i++) {
+               if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE)
+                       break;
+       }
+       if (i == SDCARD_IDLE_RETRY) {
+               ret = 0x3f;
+               goto bail;
+       }
+
+       /* Follow the setup path to get the card out of idle state and
+        * up and running again
+        */
+       if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) {
+               uint32_t        arg = 0;
+//             uint8_t         sdver2 = 0;
+
+               /* Check for SD version 2 */
+               if ((response[2] & 0xf) == 1 && response[3] == 0xaa) {
+                       arg = 0x40000000;
+//                     sdver2 = 1;
+               }
+
+               for (i = 0; i < SDCARD_IDLE_RETRY; i++) {
+                       ret = ao_sdcard_app_send_op_cond(arg);
+                       if (ret != SDCARD_STATUS_IDLE_STATE)
+                               break;
+               }
+
+               if (ret != SDCARD_STATUS_READY_STATE) {
+                       /* MMC */
+                       for (i = 0; i < SDCARD_IDLE_RETRY; i++) {
+                               ret = ao_sdcard_send_op_cond();
+                               if (ret != SDCARD_STATUS_IDLE_STATE)
+                                       break;
+                       }
+                       if (ret != SDCARD_STATUS_READY_STATE)
+                               goto bail;
+               }
+
+               /* For everything but SDHC cards, set the block length */
+               if (sdtype != ao_sdtype_sd2block) {
+                       ret = ao_sdcard_set_blocklen(512);
+                       if (ret != SDCARD_STATUS_READY_STATE)
+                               DBG ("set_blocklen failed, ignoring\n");
+               }
+       }
+bail:
+       return ret;
+}
+
+/*
+ * The card will send 0xff until it is ready to send
+ * the data block at which point it will send the START_BLOCK
+ * marker followed by the data. This function waits while
+ * the card is sending 0xff
+ */
 static uint8_t
 ao_sdcard_wait_block_start(void)
 {
-       int     i;
-       uint8_t v;
+       uint8_t         v;
+       uint16_t        timeout = ao_time() + SDCARD_BLOCK_TIMEOUT;
 
        DBG ("\twait_block_start\n");
-       for (i = 0; i < SDCARD_BLOCK_TIMEOUT; i++) {
+       for (;;) {
                ao_sdcard_recv(&v, 1);
                DBG("\t\trecv %02x\n", v);
                if (v != 0xff)
                        break;
+               if (later(ao_time(), timeout)) {
+                       printf ("wait block start timeout\n");
+                       return 0xff;
+               }
        }
        return v;
 }
@@ -341,8 +493,10 @@ ao_sdcard_wait_block_start(void)
 uint8_t
 ao_sdcard_read_block(uint32_t block, uint8_t *data)
 {
-       uint8_t ret;
+       uint8_t ret = 0x3f;
+       uint8_t start_block;
        uint8_t crc[2];
+       int tries;
 
        ao_sdcard_lock();
        if (!initialized) {
@@ -355,26 +509,58 @@ ao_sdcard_read_block(uint32_t block, uint8_t *data)
                ao_sdcard_unlock();
                return 0;
        }
+       DBG("read block %d\n", block);
        if (sdtype != ao_sdtype_sd2block)
                block <<= 9;
+
        ao_sdcard_get();
-       ao_sdcard_select();
-       ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block);
-       ao_sdcard_recv_reply(NULL, 0);
-       if (ret != SDCARD_STATUS_READY_STATE)
-               goto bail;
+       for (tries = 0; tries < 10; tries++) {
+               ao_sdcard_select();
 
-       if (ao_sdcard_wait_block_start() != 0xfe) {
-               ret = 0x3f;
-               goto bail;
-       }
+               ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block);
+               ao_sdcard_recv_reply(NULL, 0);
+               if (ret != SDCARD_STATUS_READY_STATE) {
+                       uint16_t        status;
+                       WARN ("read block command failed %d status %02x\n", block, ret);
+                       status = _ao_sdcard_send_status();
+                       WARN ("\tstatus now %04x\n", status);
+                       (void) status;
+                       goto bail;
+               }
 
-       ao_sdcard_recv(data, 512);
-       ao_sdcard_recv(crc, 2);
-bail:
-       ao_sdcard_deselect();
+               ao_sdcard_send_fixed(0xff, 1);
+
+               /* Wait for the data start block marker */
+               start_block = ao_sdcard_wait_block_start();
+               if (start_block != SDCARD_DATA_START_BLOCK) {
+                       WARN ("wait block start failed %02x\n", start_block);
+                       ret = 0x3f;
+                       goto bail;
+               }
+
+               ao_sdcard_recv(data, 512);
+               ao_sdcard_recv(crc, 2);
+       bail:
+               ao_sdcard_deselect();
+               if (ret == SDCARD_STATUS_READY_STATE)
+                       break;
+               if (ret == SDCARD_STATUS_IDLE_STATE) {
+                       ret = _ao_sdcard_reset();
+                       if (ret != SDCARD_STATUS_READY_STATE)
+                               break;
+               }
+       }
        ao_sdcard_put();
        ao_sdcard_unlock();
+
+#if SDCARD_WARN
+       if (ret != SDCARD_STATUS_READY_STATE)
+               WARN("read failed\n");
+       else if (tries)
+               WARN("took %d tries to read %d\n", tries + 1, block);
+#endif
+
+       DBG("read %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure");
        return ret == SDCARD_STATUS_READY_STATE;
 }
 
@@ -384,15 +570,143 @@ bail:
 uint8_t
 ao_sdcard_write_block(uint32_t block, uint8_t *data)
 {
-       /* Not doing anything until the file system code seems reasonable
-        */
-       return 1;
+       uint8_t ret;
+       uint8_t response[1];
+       uint8_t start_block[8];
+       uint16_t status;
+       int     tries;
+
+       ao_sdcard_lock();
+       if (!initialized) {
+               ao_sdcard_setup();
+               initialized = 1;
+               if (sdtype != ao_sdtype_unknown)
+                       present = 1;
+       }
+       if (!present) {
+               ao_sdcard_unlock();
+               return 0;
+       }
+       DBG("write block %d\n", block);
+       if (sdtype != ao_sdtype_sd2block)
+               block <<= 9;
+
+       ao_sdcard_get();
+
+       for (tries = 0; tries < 10; tries++) {
+               ao_sdcard_select();
+
+               ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block);
+               ao_sdcard_recv_reply(NULL, 0);
+               if (ret != SDCARD_STATUS_READY_STATE)
+                       goto bail;
+
+               /* Write a pad byte followed by the data start block marker */
+               start_block[0] = 0xff;
+               start_block[1] = SDCARD_DATA_START_BLOCK;
+               ao_sdcard_send(start_block, 2);
+
+               /* Send the data */
+               ao_sdcard_send(data, 512);
+
+               /* Fake the CRC */
+               ao_sdcard_send_fixed(0xff, 2);
+
+               /* See if the card liked the data */
+               ao_sdcard_recv(response, sizeof (response));
+               if ((response[0] & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) {
+                       int i;
+                       WARN("Data not accepted, response");
+                       for (i = 0; i < (int) sizeof (response); i++)
+                               WARN(" %02x", response[i]);
+                       WARN("\n");
+                       ret = 0x3f;
+                       goto bail;
+               }
+               
+               /* Wait for the bus to go idle (should be done with an interrupt?) */
+               if (!ao_sdcard_wait_busy()) {
+                       ret = 0x3f;
+                       goto bail;
+               }
+
+               /* Check the current status after the write completes */
+               status = _ao_sdcard_send_status();
+               if ((status & 0xff) != SDCARD_STATUS_READY_STATE) {
+                       WARN ("send status after write %04x\n", status);
+                       ret = status & 0xff;
+                       goto bail;
+               }
+       bail:
+               ao_sdcard_deselect();
+               DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure");
+               if (ret == SDCARD_STATUS_READY_STATE)
+                       break;
+       }
+       ao_sdcard_put();
+       ao_sdcard_unlock();
+       if (tries)
+               WARN("took %d tries to write %d\n", tries + 1, block);
+
+       return ret == SDCARD_STATUS_READY_STATE;
+}
+
+#if SDCARD_DEBUG
+static uint8_t test_data[512];
+
+static void
+ao_sdcard_test_read(void)
+{
+       int i;
+
+       ao_cmd_decimal();
+       if (ao_cmd_status != ao_cmd_success)
+               return;
+       
+       for (i = 0; i < 100; i++) {
+               printf ("."); flush();
+               if (!ao_sdcard_read_block(ao_cmd_lex_u32+i, test_data)) {
+                       printf ("read error %d\n", i);
+                       return;
+               }
+       }
+       printf ("data:");
+       for (i = 0; i < 18; i++)
+               printf (" %02x", test_data[i]);
+       printf ("\n");
 }
 
+static void
+ao_sdcard_test_write(void)
+{
+       int     i;
+       printf ("data:");
+       for (i = 0; i < 16; i++) {
+               test_data[i]++;
+               printf (" %02x", test_data[i]);
+       }
+       printf ("\n");
+       if (!ao_sdcard_write_block(1, test_data)) {
+               printf ("write error\n");
+               return;
+       }
+}
+
+static const struct ao_cmds ao_sdcard_cmds[] = {
+       { ao_sdcard_test_read,  "x\0Test read" },
+       { ao_sdcard_test_write, "y\0Test read" },
+       { 0, NULL },
+};
+#endif
+
 void
 ao_sdcard_init(void)
 {
+       stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_SCK_PIN, STM_PUPDR_PULL_UP);
+       stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MISO_PIN, STM_PUPDR_PULL_UP);
+       stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MOSI_PIN, STM_PUPDR_PULL_UP);
        ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN));
+#if SDCARD_DEBUG
+       ao_cmd_register(&ao_sdcard_cmds[0]);
+#endif
 }
-
-