/* ----------------------------------------------------------------------- *
 *
 *   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 "sdcard.h"
#include "console.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

/* 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_info sdc = {
    .status   = STA_NOINIT,
    .fsstatus = 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 int8_t sdcard_send_cmd(uint8_t opcode, uint32_t argument)
{
    int8_t status = -1;
    int i;

    if (!opcode)
	return 0;		/* No command */

    dbg_printf("sdcard: CMD%u 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 (status >= 0)	/* Bit 7 = 0 for a valid reply */
	    break;
    }
    dbg_putc('\n');
    return status;
}

static int8_t sdcard_send_acmd(uint8_t opcode, uint32_t argument)
{
    int8_t 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.
 */

static int sdcard_read_block(void *buf, int len, int timeout,
			     uint8_t xcmd, uint32_t xarg)
{
    uint8_t tok;
    uint16_t zcrc;
    xptr_t 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, sector_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 || (sdc.status & STA_NOINIT))
	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 < 0) {
	sdc.status = sdc.fsstatus = STA_NOINIT | STA_NODISK;
	con_puts("sdcard: card removed\n");
	goto out;
    } else 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;
}

/*
 * 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, sector_t lba, int count)
{
    int rv;
    int okcount = 0;
    uint16_t crc;
    uint8_t resp;
    xcptr_t p;
    bool error = false;

    if (!count || (sdc.status & STA_NOINIT))
	return 0;

    p.v = 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 < 0) {
	con_puts("sdcard: card removed - WRITE LOST\n");
	sdc.status = sdc.fsstatus = STA_NOINIT | STA_NODISK;
    } else 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; no output */
	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|SD_BE);

	/* Discard byte shifted in during CRC transmission */
	sd_readb(SD_GO8);

	/* Wait for data response token */
	/* XXX: Timeout */
	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; the error flag handles this.
	     */
	    con_printf("sdcard: write error: %02x\n", resp);
	    error = true;
	    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.  The Sandisk
     * documentation says the busy will happen no more than one byte
     * after the stop token if it is going to happen at all; be a bit
     * more cautious and give it up to 8.
     */
    for (int i = 0; i < 8; i++) {
	resp = sd_readb(SD_GO8);
	if (resp == 0)
	    break;
    }
    /* XXX: Timeout */
    while (resp == 0) {
	resp = sd_readb(SD_GO8);
    }

    /*
     * If we got an error, send STOP TRANSMISSION. Not sure why this
     * is necessary after the stop token, but it apparently is.
     */
    if (error)
	sdcard_send_cmd(CMD_STOP_TRANSMISSION, 0);

out:
    sdcard_led_off();
    return okcount;
}

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 int sdcard_switch_func_cmd(uint32_t arg, void *buf)
{
    uint32_t rv = sdcard_send_cmd(CMD_SWITCH_FUNC, arg);
    if (rv & ~1) {
	dbg_printf("sdcard: CMD6 %08x returned %02x\n", arg, rv);
	return -1;
    }
    if (sdcard_read_block(buf, 64, 200000, 0, 0)) {
	dbg_printf("sdcard: no CMD6 %08x query reply\n", arg);
	return -1;
    }
#ifdef DEBUG
    dbg_puts("sdcard: CMD6:");
    for (int i = 0; i < 64; i++)
	dbg_printf(" %02x", ((uint8_t *)buf)[i]);
    dbg_putc('\n');
#endif
    return 0;
}

/* Try to switch to high speed mode (50 MHz) */
static void sdcard_try_high_speed(void)
{
    int rv;
    uint8_t tran_speed;
    uint8_t cmd6data[64];

    /* Verify support for CMD6, part of command group 10 */
    if (!(sdc.csd.raw[1] & (1 << 30))) {
	dbg_printf("sdcard: CMD6 not supported\n");
	return;
    }

    /* Try to switch to access mode 1 */
    if (sdcard_switch_func_cmd(0x80fffff1, cmd6data))
	return;

    /* Did we? */
    if ((cmd6data[16] & 0x0f) != 1) {
	dbg_printf("sdcard: switch to high speed mode rejected\n");
	return;
    }

    /*
     * If we succeeded, we will have switched mode.
     * This should be reflected 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;
    }

    sd_set_mode(SD_50MHZ, true);
    con_printf("sdcard: switched to high speed\n");
}

static DSTATUS sdcard_present_check(void)
{
    /* Send CRC enable command; only care about getting any response at all */
    if (sdc.status & STA_NOINIT)
	return sdc.status;

    if (sdcard_send_cmd(CMD_CRC_ON_OFF, 0x0001) < 0) {
	sdc.fsstatus = sdc.status = STA_NOINIT | STA_NODISK;
	set_led(LED_SDCARD, false);
	con_puts("sdcard: card removed\n");
    }
    return sdc.status;
}

#define SDCARD_PRESENT_POLL (TIMER_HZ/8)	/* 8 times/s */

DSTATUS sdcard_present_poll(void)
{
    static uint32_t last_disk_poll;
    uint32_t now = timer_count();

    if ((now - last_disk_poll) < SDCARD_PRESENT_POLL)
	return sdc.status;

    last_disk_poll = now;
    return sdcard_present_check();
}

#define SDCARD_RETRY (TIMER_HZ*2)		/* Retry max every 2 s */

DSTATUS sdcard_init(void)
{
    uint16_t  status;
    uint32_t try_sdhc;
    bool is_sd;
    int i, j, rv;
    static uint32_t last_disk_retry;
    uint32_t now = timer_count();
    DSTATUS old_sdc_status = sdc.status;

    /* So we can wait for the ready condition */
    SDCARD_CTL_IRQEN = SDCARD_IRQ_READY;

    if (!(old_sdc_status & STA_NOINIT)) {
	return sdcard_present_check();
    }

    if (old_sdc_status & STA_NODISK) {
	if ((now - last_disk_retry) < SDCARD_RETRY)
	    return old_sdc_status;
    }
    last_disk_retry = now;

    memset(&sdc, 0, sizeof sdc);
    sdc.fsstatus = sdc.status = STA_NOINIT | STA_NODISK;

#if 0
    status = /* Check card detect if present */

    if (!(status & 0x04)) {
	con_printf("No memory card installed\n");
	goto out;
    }
#endif

    sdcard_led_on();

    /* 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) {
	    if (!(old_sdc_status & STA_NODISK))
		con_puts("sdcard: no card detected\n");
	    goto out;
	}
    }

    /* 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);
	    goto out;
	}

	/* 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);
	    goto out;
	}
	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);
	    goto out;
	}
    } 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);
	    goto out;
	}
	/* 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);
		goto out;
	    }
	} 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);
	goto out;
    }

    /*
     * 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;

out:
    set_led(LED_SDCARD, !(sdc.status & STA_NOINIT));
    return sdc.status;
}

DSTATUS disk_status(BYTE drive)
{
    if (drive != 0)
	return STA_NOINIT | STA_NODISK;

    sdcard_present_poll();
    sdc.fsstatus |= sdc.status;

    return sdc.fsstatus;
}
