#ifndef SDCARD_H
#define SDCARD_H

#include "fw.h"
#include "io.h"
#include "irq.h"
#include "ff.h"
#include "diskio.h"

#define SECTOR_SHIFT	9
#define SECTOR_SIZE	(1UL << SECTOR_SHIFT)

struct sdcard_csd {
    uint32_t raw[4];
};

struct sdcard_cid {
    uint32_t raw[4];
};

struct sdcard_info {
    DSTATUS		status;		/* Physical status */
    DSTATUS		fsstatus;	/* Filesystem visible status */
    int8_t		card_type;
    unsigned long	lbasize;
    uint32_t		if_cond;
    uint32_t		ocr;
    struct sdcard_csd	csd;
    struct sdcard_cid	cid;
};

extern struct sdcard_info sdc;

typedef unsigned int sector_t;

extern int sdcard_read_sectors(void *, sector_t, int);
extern int sdcard_write_sectors(const void *, sector_t, int);
extern DSTATUS sdcard_init(void);
extern DSTATUS sdcard_present_poll(void);

static inline void sd_set_mode(uint8_t divisor, bool cs)
{
    waitfor(SDCARD_IRQ);
    SDCARD_CTL_SPEED = (divisor - 1) | (cs << 7);
}

/* Read/write SD card and start transaction */
enum sd_data_flags {
    SD_B0       = 0x00,		/* Byte offset 0 */
    SD_B1       = 0x01,		/* Byte offset 1 */
    SD_B2       = 0x02,		/* Byte offset 2 */
    SD_B3       = 0x03,		/* Byte offset 3 */
    SD_GO8      = 0x04,		/* Start 8-bit transaction */
    SD_GO16     = 0x08,		/* Start 16-bit transaction */
    SD_GO32     = 0x0c,		/* Start 32-bit transaction */
    SD_BE       = 0x10,		/* Bigendian data */
    SD_DATA	= 0x20,		/* Data register access (assumed) */
    SD_CLEARCRC = 0x40		/* Clear CRC registers */
};

static inline void sd_writeb(uint8_t d, enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    flags ^= (flags & SD_BE) ? 3 : 0;
    *(volatile uint8_t *)IODEVA(SDCARD,0,flags | SD_DATA) = d;
}
static inline void sd_writeh(uint16_t d, enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    flags ^= (flags & SD_BE) ? 2 : 0;
    *(volatile uint16_t *)IODEVA(SDCARD,0,flags | SD_DATA) = d;
}
static inline void sd_writel(uint32_t d, enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    *(volatile uint32_t *)IODEVA(SDCARD,0,flags | SD_DATA) = d;
}
static inline uint8_t sd_readb(enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    flags ^= (flags & SD_BE) ? 0 : 3;
    return *(volatile uint8_t *)IODEVA(SDCARD,0,flags | SD_DATA);
}
static inline uint16_t sd_readh(enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    flags ^= (flags & SD_BE) ? 0 : 2;
    return *(volatile uint16_t *)IODEVA(SDCARD,0,flags | SD_DATA);
}
static inline uint32_t sd_readl(enum sd_data_flags flags)
{
    waitfor(SDCARD_IRQ);
    return *(volatile uint32_t *)IODEVA(SDCARD,0,flags | SD_DATA);
}

static inline uint8_t sd_crc7_rd(void)
{
    waitfor(SDCARD_IRQ);
    return SDCARD_CRC7_RD;
}
static inline uint8_t sd_crc7_wr(void)
{
    waitfor(SDCARD_IRQ);
    return SDCARD_CRC7_WR;
}
static inline uint16_t sd_crc16_rd(void)
{
    waitfor(SDCARD_IRQ);
    return SDCARD_CRC16_RD;
}
static inline uint16_t sd_crc16_wr(void)
{
    waitfor(SDCARD_IRQ);
    return SDCARD_CRC16_WR;
}

#endif /* SDCARD_H */
