| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 | /* ----------------------------------------------------------------------- * * *   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 "io.h"#include "systime.h"#include "console.h"#include "ff.h"#include "diskio.h"#ifndef DEBUG# define DEBUG 0#endif#if DEBUG# define dbg_printf con_printf# define dbg_puts   con_puts# define dbg_putc   con_putc#else# define dbg_printf(f,...) ((void)0)# define dbg_puts(x) ((void)0)# define dbg_putc(x) ((void)0)#endif#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){    int status = -1;    int i;    if (!opcode)	return 0;		/* No command */    dbg_printf("sdcard: CMD%02u arg %08x:", opcode ^ 0x40, argument);    sd_writeb(opcode, SD_GO8|SD_CLEARCRC);    sd_writel(argument, SD_BE|SD_GO32);    /* GO16 so we discard the shifted-in byte from during the CRC7 */    sd_writeb(sd_crc7_wr(), SD_GO16);    /* The spec says a reply within 8 cycles, cut it some slack */    for (i = 16; i; i--) {	status = sd_readb(SD_GO8);	dbg_printf(" %02x", status);	if ((int8_t)status >= 0) /* Bit 7 = 0 for a valid reply */	    break;    }    dbg_putc('\n');    return status;}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;    int badtimeout = 8;    p.b = buf;    /*     * 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 */    /*     * Wait for data token     */    dbg_puts("sdcard: read token:");    for (;;) {	tok = sd_readb(SD_GO8|SD_CLEARCRC);	dbg_printf(" %02x", tok);	if (tok == 0xfe)	    break;	if (tok < 0xfe && !--badtimeout) {	    dbg_printf("\nsdcard: read_block: bad token: %02x\n", tok);	    return -1; /* Bad token */	}	if (!--timeout) {	    dbg_printf("\nsdcard: read_block: reply timeout\n");	    return -1; /* Timeout */	}    }    dbg_putc('\n');    /*     * 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;    }    if (len >= 6) {	sd_readh(SD_GO16);	/* Now total of 4 bytes latched */	while (len >= 6) {	    *p.l++ = sd_readl(SD_GO32);	    len -= 4;	}	*p.w++ = sd_readh(2);	/* Consume the two least recent bytes */	len -= 2;    }    /*     * At this point, we are aligned to a 2-byte boundary with 2 bytes     * in the input shift register; we need to maintain those two bytes     * for the CRC.     */    while (len--)	*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).     *     * To keep the logic anything remotely consistent, passively stuff     * the new command into the transmit register before triggering     * active read operations.     *     * Note that the destination buffer may be unaligned here.     */    if (xcmd) {	sd_writeb(xcmd, SD_CLEARCRC);	*p.b++ = sd_readb(SD_GO8 | 1);	sd_writel(xarg, SD_BE);	*p.b++ = sd_readb(SD_GO8 | 1);	*p.b++ = sd_readb(SD_GO8 | 1);	*p.b++ = sd_readb(SD_GO8 | 1);	*p.b++ = sd_readb(SD_GO8 | 1);	sd_writeb(sd_crc7_wr(), 0);	*p.b++ = sd_readb(SD_GO8 | 1);    }    /*     * Now the CRC is latched in the shift register, and the CRC     * in the CRC generator should be zero.     */    zcrc = sd_crc16_rd();    if (zcrc != 0x0000) {	con_printf("sdcard_read_block: CRC error (zcrc = %04x)\n", zcrc);	return -1;    }    /*     * Shift in the first     * byte after the CRC for the next round.     */    sd_readb(SD_GO8);    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 sector%s at %u to %p\n",	       count, (count != 1) ? "s" : "", 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 & ~1) {	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, const char *name){    int rv;    uint32_t *bp = buf;    unsigned int i;    memset(buf, 0, 16);    con_printf("sdcard: %s:", name);    rv = sdcard_send_cmd(opcode, 0);    if (rv & ~1)	goto err;    rv = sdcard_read_block(buf, 16, 2000, 0, 0);    if (rv)	goto err;    for (i = 0; i < 4; i++) {	bp[i] = __builtin_bswap32(bp[i]);	con_printf(" %08x", bp[i]);    }    con_putc('\n');    return 0;err:    con_printf(" failed, err %02x\n", rv);    return rv;}static int sdcard_read_csd(void){    return sdcard_read_reg(CMD_SEND_CSD, &sdc.csd, "CSD");}static int sdcard_read_cid(void){    return sdcard_read_reg(CMD_SEND_CID, &sdc.cid, "CID");}/* * 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 tran_speed;    return;    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 & ~1) {	dbg_printf("sdcard: CMD6 returned %02x\n", rv);	return;    }    if (1) {	/*	 * Despite the spec, this doesn't seem to actually happen	 * in SPI mode?	 */	uint8_t swdata[64];		/* Response from CMD6 */	int i;	for (i = 0; i < 64; i++)	    swdata[i] = sd_readb(SD_GO8);	if ((swdata[47] & 0x0f) != 1) {	    dbg_printf("sdcard: CMD6 reported %X for high speed request\n",		       swdata[47] & 0x0f);	    return;		/* Failed to switch to high speed mode */	}    }    /*     * Success, we should have switched mode.     * This should be refleded in the TRAN_SPEED field in the CSD.     */    sd_readl(SD_GO32);		/* Issue at least 8 clocks; go for 32 */    /* Re-read the CSD */    sdcard_read_csd();    /* TRAN_SPEED should have changed now */    tran_speed = (uint8_t)sdc.csd.raw[0];    if ((tran_speed & 7) < 2 ||	((tran_speed & 7) == 2 && (tran_speed >> 3) < 0xb)) {	dbg_printf("sdcard: speed switch failed, tran_speed %02x\n",		   tran_speed);	return;			/* High speed not available */    }    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, j, 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();    /* This doesn't work for MMC, but speeds up debugging */#undef SD_SLOW#define SD_SLOW SD_20MHZ    /* Allow 4 retries in case the card is in a funky state */    i = 4;    while (1) {	/* Generate 256 clock cycles in slow mode, with CS# high */	sd_set_mode(SD_SLOW, false);	for (j = 0; j < 8; j++)	    sd_writel(~0, SD_GO32);	/* Assert CS# and send reset command */	sd_set_mode(SD_SLOW, true);	sd_writeb(~0, SD_GO8);	/* Dummy byte after CS# assert */	rv = sdcard_send_cmd(CMD_GO_IDLE_STATE, 0);	if (rv == 0x01)	    break;		/* Success! */	if (!--i) {	    con_printf("sdcard: reset failed, assuming no card present\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) {	    dbg_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);	dbg_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 the CSD to figure out what command sets are available...     */    sdcard_read_csd();		/* Read CSD */    /*     * Try to switch to 50 MHz (optional)     */    if (is_sd)	sdcard_try_high_speed();    /*     * Read the CID     */    sdcard_read_cid();    sdcard_compute_size(&sdc);    con_printf("sdcard: %s card found, capacity %u sectors\n",	   sdcard_type_name(sdc.card_type), sdc.lbasize);    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){    FRESULT rv;    char label[128];    uint32_t volid, freeclust;    FATFS *fs;    rv = f_mount(&sd_fs, "", 1);    if (rv != FR_OK) {	con_printf("sdcard: no volume found\n");	return -1;    }    label[0] = '\0';    volid = 0;    f_getlabel("", label, &volid);    con_printf("sdcard: volume found, label \"%s\", volid %08x\n", label, volid);    freeclust = 0;    f_getfree("", &freeclust, &fs);    con_printf("sdcard: %u/%u clusters free, clusters = %u bytes\n",	       freeclust, fs->n_fatent - 2, fs->csize << 9);    return 0;}
 |