|
@@ -0,0 +1,761 @@
|
|
|
+/* ----------------------------------------------------------------------- *
|
|
|
+ *
|
|
|
+ * Copyright 2010-2021 H. Peter Anvin - All Rights Reserved
|
|
|
+ *
|
|
|
+ * 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, Inc., 51 Franklin St, Fifth Floor,
|
|
|
+ * Boston MA 02110-1301, USA; either version 2 of the License, or
|
|
|
+ * (at your option) any later version; incorporated herein by reference.
|
|
|
+ *
|
|
|
+ * ----------------------------------------------------------------------- */
|
|
|
+
|
|
|
+/*
|
|
|
+ * sdcard.c
|
|
|
+ *
|
|
|
+ * SD card block driver
|
|
|
+ *
|
|
|
+ * Note: the handling of read operations is tricky, because they pick up
|
|
|
+ * the results from the *previous* transaction. Therefore there are some
|
|
|
+ * serious subtleties, especially with which byte position in the shift
|
|
|
+ * register the result ends up in and what the size flag should be set to.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <stdbool.h>
|
|
|
+#include <stddef.h>
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+#include "fw.h"
|
|
|
+#include "console.h"
|
|
|
+#include "io.h"
|
|
|
+#include "systime.h"
|
|
|
+#include "ff.h"
|
|
|
+#include "diskio.h"
|
|
|
+
|
|
|
+#define SECTOR_SHIFT 9
|
|
|
+#define SECTOR_SIZE (1UL << SECTOR_SHIFT)
|
|
|
+
|
|
|
+/* Command codes, including the leading 01 sequence */
|
|
|
+enum sdcard_cmd {
|
|
|
+ CMD_GO_IDLE_STATE = 0x40, /* a.k.a. reset */
|
|
|
+ CMD_SEND_OP_COND = 0x41,
|
|
|
+ CMD_SWITCH_FUNC = 0x46,
|
|
|
+ CMD_SEND_IF_COND = 0x48,
|
|
|
+ CMD_SEND_CSD = 0x49,
|
|
|
+ CMD_SEND_CID = 0x4a,
|
|
|
+ CMD_STOP_TRANSMISSION = 0x4c,
|
|
|
+ CMD_SEND_STATUS = 0x4d,
|
|
|
+ CMD_SET_BLOCKLEN = 0x50,
|
|
|
+ CMD_READ_SINGLE_BLOCK = 0x51,
|
|
|
+ CMD_READ_MULTIPLE_BLOCK = 0x52,
|
|
|
+ CMD_WRITE_BLOCK = 0x58,
|
|
|
+ CMD_WRITE_MULTIPLE_BLOCK = 0x59,
|
|
|
+ CMD_PROGRAM_CSD = 0x5b,
|
|
|
+ CMD_SET_WRITE_PROT = 0x5c,
|
|
|
+ CMD_CLR_WRITE_PROT = 0x5d,
|
|
|
+ CMD_SEND_WRITE_PROT = 0x5e,
|
|
|
+ CMD_ERASE_WR_BLK_START_ADDR = 0x60,
|
|
|
+ CMD_ERASE_WR_BLK_END_ADDR = 0x61,
|
|
|
+ CMD_ERASE = 0x66,
|
|
|
+ CMD_LOCK_UNLOCK = 0x6a,
|
|
|
+ CMD_APP_CMD = 0x77, /* == ACMD prefix */
|
|
|
+ CMD_GEN_CMD = 0x78,
|
|
|
+ CMD_READ_OCR = 0x7a,
|
|
|
+ CMD_CRC_ON_OFF = 0x7b
|
|
|
+};
|
|
|
+enum sdcard_acmd {
|
|
|
+ ACMD_SD_STATUS = 0x4d,
|
|
|
+ ACMD_SEND_NUM_WR_BLOCKS = 0x56,
|
|
|
+ ACMD_SET_WR_BLK_ERASE_COUNT = 0x57,
|
|
|
+ ACMD_SD_SEND_OP_COND = 0x69,
|
|
|
+ ACMD_SET_CLR_CARD_DETECT = 0x6a,
|
|
|
+ ACMD_SEND_SCR = 0x73
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+struct sdcard_csd {
|
|
|
+ uint32_t raw[4];
|
|
|
+};
|
|
|
+
|
|
|
+struct sdcard_cid {
|
|
|
+ uint32_t raw[4];
|
|
|
+};
|
|
|
+
|
|
|
+struct sdcard_info {
|
|
|
+ DSTATUS status;
|
|
|
+ int8_t card_type;
|
|
|
+ unsigned long lbasize;
|
|
|
+ uint32_t if_cond;
|
|
|
+ uint32_t ocr;
|
|
|
+ struct sdcard_csd csd;
|
|
|
+ struct sdcard_cid cid;
|
|
|
+};
|
|
|
+
|
|
|
+struct sdcard_info sdc = {
|
|
|
+ .status = STA_NOINIT
|
|
|
+};
|
|
|
+
|
|
|
+/* < 0 if it should be left on, 0 for off, > 0 for off after timeout */
|
|
|
+static volatile int8_t sdcard_led;
|
|
|
+
|
|
|
+/*
|
|
|
+ * Called by the timer interrupt
|
|
|
+ */
|
|
|
+void sdcard_timer_tick(void)
|
|
|
+{
|
|
|
+#if 0
|
|
|
+ if (sdcard_led > 0) {
|
|
|
+ if (--sdcard_led == 0)
|
|
|
+ *IO_SYS_LED &= ~0x01;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Enable LED
|
|
|
+ */
|
|
|
+static void sdcard_led_on(void)
|
|
|
+{
|
|
|
+#if 0
|
|
|
+ sdcard_led = -1;
|
|
|
+ *IO_SYS_LED |= 0x01;
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Disable LED after timeout
|
|
|
+ */
|
|
|
+static void sdcard_led_off(void)
|
|
|
+{
|
|
|
+ sdcard_led = 4; /* 4 ticks @ 64 Hz = 62.5 ms */
|
|
|
+}
|
|
|
+
|
|
|
+static int sdcard_send_cmd(uint8_t opcode, uint32_t argument)
|
|
|
+{
|
|
|
+ uint8_t crc7;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!opcode)
|
|
|
+ return 0; /* No command */
|
|
|
+
|
|
|
+ sd_writeb(opcode, SD_GO8|SD_CLEARCRC);
|
|
|
+ sd_writel(argument, SD_BE|SD_GO32);
|
|
|
+ crc7 = sd_crc7_wr();
|
|
|
+ sd_writeb(crc7, SD_GO8);
|
|
|
+
|
|
|
+ /* The spec says a reply within 8 cycles, cut it some slack */
|
|
|
+ for (i = 16; i; i--) {
|
|
|
+ int8_t status = sd_readb(0);
|
|
|
+ if (status >= 0) /* Bit 7 = 0 for a valid reply */
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1; /* Error */
|
|
|
+}
|
|
|
+
|
|
|
+static int sdcard_send_acmd(uint8_t opcode, uint32_t argument)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+
|
|
|
+ /* CMD55 = application command follows */
|
|
|
+ rv = sdcard_send_cmd(CMD_APP_CMD, 0);
|
|
|
+ if (rv & 0x04) /* Unknown command (very old card)? */
|
|
|
+ return rv;
|
|
|
+ return sdcard_send_cmd(opcode, argument);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Read a data block of length len, with a timeout of (timeout)
|
|
|
+ * byte-times. Note that the minimum length is 4 by spec;
|
|
|
+ * this function may fail if this is not the case.
|
|
|
+ *
|
|
|
+ * The input buffer is allowed to be unaligned.
|
|
|
+ */
|
|
|
+union xptr {
|
|
|
+ uint32_t *l;
|
|
|
+ uint16_t *w;
|
|
|
+ uint8_t *b;
|
|
|
+ size_t a;
|
|
|
+};
|
|
|
+union xcptr {
|
|
|
+ const uint32_t *l;
|
|
|
+ const uint16_t *w;
|
|
|
+ const uint8_t *b;
|
|
|
+ size_t a;
|
|
|
+};
|
|
|
+
|
|
|
+static int sdcard_read_block(void *buf, int len, int timeout,
|
|
|
+ uint8_t xcmd, uint32_t xarg)
|
|
|
+{
|
|
|
+ uint8_t tok;
|
|
|
+ uint16_t zcrc;
|
|
|
+ union xptr p;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ p.b = buf;
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ tok = sd_readb(SD_GO8|SD_CLEARCRC);
|
|
|
+ if (tok == 0xfe)
|
|
|
+ break;
|
|
|
+ if (tok < 0xfe) {
|
|
|
+ con_printf("sdcard_read_block: bad token: %02x\n", tok);
|
|
|
+ return -1; /* Bad token */
|
|
|
+ }
|
|
|
+ if (!--timeout) {
|
|
|
+ con_printf("sdcard_read_block: reply timeout\n");
|
|
|
+ return -1; /* Timeout */
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * At this point the first byte after the token is latched into
|
|
|
+ * the input shift register. After dealing with alignment,
|
|
|
+ * use dummy reads to shift in the rest of the first longword.
|
|
|
+ */
|
|
|
+
|
|
|
+ /* Shift in bytes if needed for alignment */
|
|
|
+ if (p.a & 1) {
|
|
|
+ *p.b++ = sd_readb(SD_GO8);
|
|
|
+ len--;
|
|
|
+ }
|
|
|
+ sd_readb(SD_GO8); /* Now total of 2 bytes latched */
|
|
|
+
|
|
|
+ if (p.a & 2) {
|
|
|
+ *p.w++ = sd_readh(SD_GO16);
|
|
|
+ len -= 2;
|
|
|
+ }
|
|
|
+ sd_readh(SD_GO16); /* Now total of 4 bytes latched */
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Are we supposed to send a command in parallel?
|
|
|
+ * This is unsafe for small blocks, but we only need to do this
|
|
|
+ * for full sectors (used to send STOP_TRANSMISSION).
|
|
|
+ */
|
|
|
+ if (xcmd)
|
|
|
+ len -= 6; /* Handle the last 6 bytes specially */
|
|
|
+
|
|
|
+ while (len >= 8) {
|
|
|
+ *p.l++ = sd_readl(SD_GO32);
|
|
|
+ len -= 4;
|
|
|
+ }
|
|
|
+ if (len & 4)
|
|
|
+ *p.l++ = sd_readl(SD_GO16); /* Consume latched lword + shift in CRC */
|
|
|
+ if (len & 2)
|
|
|
+ *p.w++ = sd_readh(SD_GO16);
|
|
|
+ if (len & 1)
|
|
|
+ *p.b++ = sd_readb(SD_GO8 | 1);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we're sending a command in parallel, then we have to
|
|
|
+ * read in the last 6 bytes in parallel with transmitting the
|
|
|
+ * command out. Because pb may be misaligned at this point,
|
|
|
+ * do the latch reads/writes to memory as byte I/O.
|
|
|
+ * We still have 2 bytes of data latched, and should end
|
|
|
+ * with the same (for the CRC).
|
|
|
+ */
|
|
|
+ if (xcmd) {
|
|
|
+ uint8_t crc7;
|
|
|
+
|
|
|
+ *p.b++ = sd_readb(1);
|
|
|
+ *p.b++ = sd_readb(0);
|
|
|
+ sd_writeb(xcmd, SD_GO8|SD_CLEARCRC);
|
|
|
+ *p.b++ = sd_readb(0);
|
|
|
+ sd_writel(xarg, SD_GO32|SD_BE);
|
|
|
+ *p.b++ = sd_readb(3);
|
|
|
+ *p.b++ = sd_readb(2);
|
|
|
+ *p.b++ = sd_readb(1);
|
|
|
+ crc7 = sd_crc7_wr();
|
|
|
+ sd_writeb(crc7, SD_GO8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now the CRC is latched in the shift register, and the CRC
|
|
|
+ * in the CRC generator should be zero. Shift in the first
|
|
|
+ * byte after the CRC for the next round.
|
|
|
+ */
|
|
|
+
|
|
|
+ zcrc = sd_crc16_rd();
|
|
|
+ sd_readb(SD_GO8);
|
|
|
+
|
|
|
+ if (zcrc != 0x0000) {
|
|
|
+ con_printf("sdcard_read_block: CRC error (zcrc = %04x)\n", zcrc);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Read a number of sectors; returns the number of sectors read.
|
|
|
+ * The input buffer may be unaligned.
|
|
|
+ */
|
|
|
+int sdcard_read_sectors(void *buf, uint32_t lba, int count)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+ int okcount = 0;
|
|
|
+ static const uint16_t stop_transmission[3] =
|
|
|
+ { (0x40|CMD_STOP_TRANSMISSION) << 8, 0x0000, 0x0061 };
|
|
|
+ uint8_t resp;
|
|
|
+ uint8_t *p = buf;
|
|
|
+
|
|
|
+ if (!count)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ sdcard_led_on();
|
|
|
+
|
|
|
+ con_printf("sdcard: reading %d sectors at %u to %p\n", count, lba, buf);
|
|
|
+
|
|
|
+ if (sdc.card_type == 1)
|
|
|
+ lba <<= SECTOR_SHIFT; /* Convert to a byte address */
|
|
|
+
|
|
|
+ rv = sdcard_send_cmd(CMD_READ_MULTIPLE_BLOCK, lba);
|
|
|
+ if (rv) {
|
|
|
+ con_printf("sdcard: read_multiple error %02x\n", rv);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (count--) {
|
|
|
+ rv = sdcard_read_block(p, SECTOR_SIZE, 200000,
|
|
|
+ count ? 0 : CMD_STOP_TRANSMISSION, 0);
|
|
|
+ if (rv)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ okcount++;
|
|
|
+ p += SECTOR_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The first byte after the stop command is undefined */
|
|
|
+ sd_readb(SD_GO8);
|
|
|
+
|
|
|
+ /* Wait for the response to the STOP TRANSMISSION command */
|
|
|
+ do {
|
|
|
+ resp = sd_readb(SD_GO8);
|
|
|
+ } while ((int8_t)resp < 0);
|
|
|
+
|
|
|
+ if (resp) {
|
|
|
+ con_printf("sdcard: read_sectors: terminate command error %02x\n",
|
|
|
+ resp);
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ sdcard_led_off();
|
|
|
+ return okcount;
|
|
|
+}
|
|
|
+
|
|
|
+DRESULT disk_read(BYTE drive, BYTE *buffer,
|
|
|
+ LBA_t sectornumber, UINT sectorcount)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+
|
|
|
+ (void)drive;
|
|
|
+
|
|
|
+ rv = sdcard_read_sectors(buffer, sectornumber, sectorcount);
|
|
|
+ return (rv == sectorcount) ? RES_OK : RES_ERROR;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Read CSD/CID
|
|
|
+ */
|
|
|
+static int sdcard_read_reg(uint8_t opcode, void *buf)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+ uint32_t *bp = buf;
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ rv = sdcard_send_cmd(opcode, 0);
|
|
|
+ if (rv)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ rv = sdcard_read_block(buf, 16, 2000, 0, 0);
|
|
|
+ if (rv)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ for (i = 0; i < 4; i++)
|
|
|
+ bp[i] = __builtin_bswap32(bp[i]);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Write a number of sectors; returns the number of sectors written.
|
|
|
+ * The buffer may be unaligned.
|
|
|
+ */
|
|
|
+int sdcard_write_sectors(const void *buf, uint32_t lba, int count)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+ int okcount = 0;
|
|
|
+ uint16_t crc;
|
|
|
+ uint8_t resp;
|
|
|
+ union xcptr p;
|
|
|
+
|
|
|
+ if (!count)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ p.b = buf;
|
|
|
+
|
|
|
+ sdcard_led_on();
|
|
|
+
|
|
|
+ con_printf("sdcard: writing %d sectors at %u from %p\n", count, lba, buf);
|
|
|
+
|
|
|
+ if (sdc.card_type == 1)
|
|
|
+ lba <<= SECTOR_SHIFT; /* Convert to a byte address */
|
|
|
+
|
|
|
+ rv = sdcard_send_cmd(CMD_WRITE_MULTIPLE_BLOCK, lba);
|
|
|
+ if (rv) {
|
|
|
+ con_printf("sdcard: write_multiple error %02x\n", rv);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (count--) {
|
|
|
+ unsigned int podd = p.a & 3;
|
|
|
+ size_t endloop = (p.a + SECTOR_SIZE) & ~3;
|
|
|
+
|
|
|
+ /* Start block token */
|
|
|
+ sd_writeb(0xfc, SD_GO8);
|
|
|
+
|
|
|
+ /* Clear the CRC generator; dummy cycle */
|
|
|
+ sd_writeb(~0, SD_CLEARCRC);
|
|
|
+
|
|
|
+ if (podd & 1)
|
|
|
+ sd_writeb(*p.b++, SD_GO8);
|
|
|
+ if (podd & 2)
|
|
|
+ sd_writeh(*p.w++, SD_GO16);
|
|
|
+
|
|
|
+ while (p.a < endloop)
|
|
|
+ sd_writel(*p.l++, SD_GO32);
|
|
|
+
|
|
|
+ podd = -podd;
|
|
|
+
|
|
|
+ if (podd & 2)
|
|
|
+ sd_writeh(*p.w++, SD_GO16);
|
|
|
+ if (podd & 1)
|
|
|
+ sd_writeb(*p.b++, SD_GO8);
|
|
|
+
|
|
|
+ sd_writeh(sd_crc16_wr(), SD_GO16);
|
|
|
+
|
|
|
+ /* Wait for data response token */
|
|
|
+ do {
|
|
|
+ resp = sd_readb(SD_GO8);
|
|
|
+ } while ((resp & 0x11) != 0x01);
|
|
|
+
|
|
|
+ resp &= ~0xe0;
|
|
|
+ if (resp != 0x05) {
|
|
|
+ /*
|
|
|
+ * Things are confusing here... the spec says
|
|
|
+ * that on error we are supposed to issue a
|
|
|
+ * STOP_TRANSMISSION command, which isn't the normal
|
|
|
+ * thing to do for a write... figure this out later.
|
|
|
+ */
|
|
|
+ break; /* Error */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Wait until the card is ready for the next block */
|
|
|
+ do {
|
|
|
+ resp = sd_readb(SD_GO8);
|
|
|
+ } while (resp == 0x00);
|
|
|
+
|
|
|
+ okcount++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Send stop transmission token */
|
|
|
+ sd_writeb(0xfd, SD_GO8);
|
|
|
+
|
|
|
+ /* Wait for the card to go busy, then unbusy */
|
|
|
+ do {
|
|
|
+ resp = sd_readb(SD_GO8);
|
|
|
+ } while (resp != 0x00);
|
|
|
+ do {
|
|
|
+ resp = sd_readb(SD_GO8);
|
|
|
+ } while (resp == 0x00);
|
|
|
+
|
|
|
+out:
|
|
|
+ sdcard_led_off();
|
|
|
+ return okcount;
|
|
|
+}
|
|
|
+
|
|
|
+DRESULT disk_write(BYTE drive, const BYTE *buffer, LBA_t sectornumber,
|
|
|
+ UINT sectorcount)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+
|
|
|
+ if (drive != 0)
|
|
|
+ return STA_NOINIT;
|
|
|
+
|
|
|
+ rv = sdcard_write_sectors(buffer, sectornumber, sectorcount);
|
|
|
+ return (rv == sectorcount) ? RES_OK : RES_ERROR;
|
|
|
+}
|
|
|
+
|
|
|
+DRESULT disk_ioctl(BYTE drive, BYTE command, void *buffer)
|
|
|
+{
|
|
|
+ if (drive != 0)
|
|
|
+ return STA_NOINIT;
|
|
|
+
|
|
|
+ switch (command) {
|
|
|
+ case CTRL_SYNC:
|
|
|
+ return RES_OK;
|
|
|
+ case GET_SECTOR_SIZE:
|
|
|
+ *(WORD *)buffer = 512;
|
|
|
+ return RES_OK;
|
|
|
+ case GET_SECTOR_COUNT:
|
|
|
+ *(DWORD *)buffer = sdc.lbasize;
|
|
|
+ return RES_OK;
|
|
|
+ case GET_BLOCK_SIZE:
|
|
|
+ *(DWORD *)buffer = 1; /* XXX */
|
|
|
+ return RES_OK;
|
|
|
+ default:
|
|
|
+ return RES_PARERR;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+DWORD get_fattime(void)
|
|
|
+{
|
|
|
+ return SYSCLOCK_DATETIME; /* Already in FAT format */
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned long sdcard_compute_size(struct sdcard_info *sdi)
|
|
|
+{
|
|
|
+ unsigned int c_size;
|
|
|
+ unsigned int c_size_mult;
|
|
|
+ unsigned int read_bl_len;
|
|
|
+ unsigned long lbasize;
|
|
|
+
|
|
|
+ sdi->card_type = (sdi->csd.raw[0] >> 30)+1;
|
|
|
+ switch (sdi->card_type) {
|
|
|
+ case 1: /* Classic SD/MMC card */
|
|
|
+ c_size = ((sdi->csd.raw[2] & 0x3ff) << 2) +
|
|
|
+ (sdi->csd.raw[3] >> 30);
|
|
|
+ c_size_mult = (sdi->csd.raw[2] >> 15) & 7;
|
|
|
+ read_bl_len = (sdi->csd.raw[1] >> 16) & 0xf;
|
|
|
+
|
|
|
+ lbasize = (c_size + 1) << (c_size_mult + read_bl_len + 2 - 9);
|
|
|
+ break;
|
|
|
+ case 2: /* SDHC/SDXC/eMMC card */
|
|
|
+ c_size = ((sdi->csd.raw[1] & 0x3f) << 16) +
|
|
|
+ (sdi->csd.raw[2] >> 16);
|
|
|
+
|
|
|
+ lbasize = c_size << 10;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ sdi->card_type = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return sdi->lbasize = lbasize;
|
|
|
+}
|
|
|
+
|
|
|
+static const char *sdcard_type_name(uint8_t type)
|
|
|
+{
|
|
|
+ static const char * const names[] = {
|
|
|
+ "unknown",
|
|
|
+ "SD/MMC",
|
|
|
+ "SDHC/SDXC/eMMC"
|
|
|
+ };
|
|
|
+
|
|
|
+ if (type >= sizeof names/sizeof names[0])
|
|
|
+ type = 0;
|
|
|
+
|
|
|
+ return names[type];
|
|
|
+}
|
|
|
+
|
|
|
+static void sdcard_try_high_speed(void)
|
|
|
+{
|
|
|
+ int rv;
|
|
|
+ uint8_t swdata[64]; /* Response from CMD6 */
|
|
|
+
|
|
|
+ if (!(sdc.csd.raw[1] & (1 << 30)))
|
|
|
+ return; /* Cmd group 10 = CMD6 not supported */
|
|
|
+
|
|
|
+ /* Try to switch to high speed mode */
|
|
|
+ rv = sdcard_send_cmd(CMD_SWITCH_FUNC, 0x80fffff1);
|
|
|
+ if (rv)
|
|
|
+ return;
|
|
|
+
|
|
|
+ rv = sdcard_read_block(swdata, sizeof swdata, 2000, 0, 0);
|
|
|
+ if (rv)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if ((swdata[47] & 0x0f) != 1)
|
|
|
+ return; /* Failed to switch to high speed mode */
|
|
|
+
|
|
|
+ /* Success, now switch mode! */
|
|
|
+ sd_readl(SD_GO32); /* Issue at least 8 clocks; go for 32 */
|
|
|
+ sd_set_mode(SD_50MHZ, true);
|
|
|
+
|
|
|
+ con_printf("sdcard: switched to high speed\n");
|
|
|
+}
|
|
|
+
|
|
|
+DSTATUS disk_initialize(BYTE drive)
|
|
|
+{
|
|
|
+ uint16_t status;
|
|
|
+ uint32_t try_sdhc;
|
|
|
+ bool is_sd;
|
|
|
+ int i, rv;
|
|
|
+
|
|
|
+ if (drive != 0)
|
|
|
+ return STA_NOINIT;
|
|
|
+
|
|
|
+ memset(&sdc, 0, sizeof sdc);
|
|
|
+
|
|
|
+#if 0
|
|
|
+ status = /* Check card detect if present */
|
|
|
+
|
|
|
+ if (!(status & 0x04)) {
|
|
|
+ con_printf("No memory card installed\n");
|
|
|
+ return sdc.status = STA_NOINIT|STA_NODISK;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ sdcard_led_on();
|
|
|
+
|
|
|
+ /* Generate 256 clock cycles in slow mode, with CS# high */
|
|
|
+ sd_set_mode(SD_SLOW, false);
|
|
|
+ for (i = 0; i < 8; i++)
|
|
|
+ sd_writel(~0, SD_GO32);
|
|
|
+
|
|
|
+ /* Assert CS# and send reset command; if no response assume no disk */
|
|
|
+ sd_set_mode(SD_SLOW, true);
|
|
|
+ rv = sdcard_send_cmd(CMD_GO_IDLE_STATE, 0);
|
|
|
+ if (rv != 0x01) {
|
|
|
+ con_printf("sdcard: CMD0 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT | STA_NODISK;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Switch to 20 MHz */
|
|
|
+ sd_set_mode(SD_20MHZ, true);
|
|
|
+
|
|
|
+ /* Enable command CRC checking (ignore result) */
|
|
|
+ sdcard_send_cmd(CMD_CRC_ON_OFF, 0x0001);
|
|
|
+
|
|
|
+ /* Probe for extended features */
|
|
|
+ /*
|
|
|
+ * Bit 7:0 = check pattern
|
|
|
+ * Bit 11:8 = supply voltage (3.3 V)
|
|
|
+ */
|
|
|
+ rv = sdcard_send_cmd(CMD_SEND_IF_COND, 0x01aa);
|
|
|
+ if ((rv & 0x04) == 0) {
|
|
|
+ /* CMD8 supported */
|
|
|
+ if (rv & ~0x03) {
|
|
|
+ con_printf("sdcard; CMD8 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Shift in additional data bytes */
|
|
|
+ sd_readb(SD_GO8);
|
|
|
+ sd_readh(SD_GO16);
|
|
|
+ sdc.if_cond = sd_readl(SD_GO16|SD_BE);
|
|
|
+
|
|
|
+ con_printf("sdcard: CMD8 returned 0x%08x\n", sdc.if_cond);
|
|
|
+
|
|
|
+ if ((sdc.if_cond & 0x1ff) != 0x1aa) {
|
|
|
+ con_printf("sdcard: CMD8 reports unusable card (0x%x)\n",
|
|
|
+ sdc.if_cond);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+ try_sdhc = 1 << 30;
|
|
|
+ } else {
|
|
|
+ try_sdhc = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Initialize card */
|
|
|
+ do {
|
|
|
+ rv = sdcard_send_acmd(ACMD_SD_SEND_OP_COND, try_sdhc);
|
|
|
+ if (rv & 0x04)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (rv & ~0x01) {
|
|
|
+ con_printf("sdcard: ACMD41 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+ } while (rv);
|
|
|
+
|
|
|
+ if (!rv) {
|
|
|
+ /* ACMD41 successful; this is an SD card: can switch to 25 MHz */
|
|
|
+ is_sd = true;
|
|
|
+
|
|
|
+ sd_set_mode(SD_25MHZ, true);
|
|
|
+ rv = sdcard_send_cmd(CMD_READ_OCR, try_sdhc);
|
|
|
+ if (rv) {
|
|
|
+ con_printf("sdcard: CMD58 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+ /* Shift in additional data bytes */
|
|
|
+ sd_readb(SD_GO8);
|
|
|
+ sd_readh(SD_GO16);
|
|
|
+ sdc.ocr = sd_readl(SD_GO16|SD_BE);
|
|
|
+ } else {
|
|
|
+ /* ACMD41 unsupported, try CMD1 */
|
|
|
+ is_sd = false;
|
|
|
+
|
|
|
+ do {
|
|
|
+ rv = sdcard_send_cmd(CMD_SEND_OP_COND, 0);
|
|
|
+ if (rv & ~0x01) {
|
|
|
+ con_printf("sdcard: CMD1 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+ } while (rv);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set block length -- some cards power up to a larger block size
|
|
|
+ * than 512 bytes even though that violates the spec.
|
|
|
+ */
|
|
|
+ rv = sdcard_send_cmd(CMD_SET_BLOCKLEN, 512);
|
|
|
+ if (rv) {
|
|
|
+ con_printf("sdcard: CMD16 error %02x\n", rv);
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status = STA_NOINIT;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Read CSD and CID
|
|
|
+ */
|
|
|
+ if (sdcard_read_reg(CMD_SEND_CSD, &sdc.csd))
|
|
|
+ memset(&sdc.csd, 0, sizeof sdc.csd);
|
|
|
+
|
|
|
+ con_printf("sdcard: CSD: %08x %08x %08x %08x\n",
|
|
|
+ sdc.csd.raw[0], sdc.csd.raw[1], sdc.csd.raw[2], sdc.csd.raw[3]);
|
|
|
+
|
|
|
+ if (sdcard_read_reg(CMD_SEND_CID, &sdc.cid))
|
|
|
+ memset(&sdc.cid, 0, sizeof sdc.cid);
|
|
|
+
|
|
|
+ con_printf("sdcard: CID: %08x %08x %08x %08x\n",
|
|
|
+ sdc.cid.raw[0], sdc.cid.raw[1], sdc.cid.raw[2], sdc.cid.raw[3]);
|
|
|
+
|
|
|
+ sdcard_compute_size(&sdc);
|
|
|
+
|
|
|
+ con_printf("sdcard: %s card found, capacity %u sectors\n",
|
|
|
+ sdcard_type_name(sdc.card_type), sdc.lbasize);
|
|
|
+
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Try to switch to 50 MHz (optional)
|
|
|
+ */
|
|
|
+ if (is_sd)
|
|
|
+ sdcard_try_high_speed();
|
|
|
+
|
|
|
+ sdc.status = 0;
|
|
|
+
|
|
|
+ sdcard_led_off();
|
|
|
+ return sdc.status;
|
|
|
+}
|
|
|
+
|
|
|
+DSTATUS disk_status(BYTE drive)
|
|
|
+{
|
|
|
+ if (drive != 0)
|
|
|
+ return STA_NOINIT;
|
|
|
+
|
|
|
+ return sdc.status;
|
|
|
+}
|
|
|
+
|
|
|
+static FATFS sd_fs;
|
|
|
+
|
|
|
+int disk_init(void)
|
|
|
+{
|
|
|
+ return f_mount(&sd_fs, "", 1);
|
|
|
+}
|
|
|
+
|