//#define USB_SERIAL_PRIVATE_INCLUDE //#include "usb_mass_storage_debug.h" //#include "pauls_ugly_debug.h" #include "core_pins.h" // for delayMicroseconds inline uint32_t media_size(void); void media_init(void); uint8_t media_lock(void); void media_unlock(void); void media_poll(void); static void media_send_begin(uint32_t lba); static void media_send_chunk(uint32_t lba, uint8_t chunk); static void media_send_end(void); static void media_receive_begin(uint32_t lba); static void media_receive_chunk(uint32_t lba, uint8_t chunk); static void media_receive_end(void); /************************************************************************** * * Storage Media Access Functions * **************************************************************************/ #define SPI_PORT PORTB #define SPI_DDR DDRB #define SPI_SS_PIN 0 #define SPI_SCLK_PIN 1 #define SPI_MOSI_PIN 2 #define SPI_MISO_PIN 3 #define SD_CMD_GO_IDLE_STATE 0x4095 #define SD_CMD_SEND_OP_COND 0x41FF #define SD_CMD_SEND_IF_COND 0x4887 #define SD_CMD_SEND_CSD 0x49FF #define SD_CMD_STOP_TRANSMISSION 0x4CFF #define SD_CMD_READ_SINGLE_BLOCK 0x51FF #define SD_CMD_READ_MULTIPLE_BLOCK 0x52FF #define SD_CMD_WRITE_SINGLE_BLOCK 0x58FF #define SD_CMD_WRITE_MULTIPLE_BLOCK 0x59FF #define SD_ACMD_SEND_OP_COND 0x69FF #define SD_CMD_APP_CMD 0x77FF #define SD_CMD_READ_OCR 0x7AFF inline void spi_write(uint8_t val) __attribute__((always_inline)); inline void spi_write(uint8_t val) { SPDR = val; while ((SPSR & (1<<SPIF)) == 0) /*wait*/ ; } inline uint8_t spi_xfer(uint8_t val) __attribute__((always_inline)); inline uint8_t spi_xfer(uint8_t val) { SPDR = val; while ((SPSR & (1<<SPIF)) == 0) /*wait*/ ; return SPDR; } void spi_ignore_bytes(uint8_t count) { do { spi_write(0xFF); } while (--count); } inline void spi_select(void) { SPI_PORT &= ~(1<<SPI_SS_PIN); } inline void sd_deselect(void) { SPI_PORT |= (1<<SPI_SS_PIN); spi_write(0xFF); } uint8_t sd_command(uint16_t cmd, uint32_t parm) { uint8_t n, r; union { struct { unsigned b1:8; unsigned b2:8; unsigned b3:8; unsigned b4:8; } byte; long i; } in; in.i = parm; phex(cmd >> 8); phex(in.byte.b4); phex(in.byte.b3); phex(in.byte.b2); phex(in.byte.b1); phex(cmd); print("\n"); spi_select(); spi_write(cmd >> 8); spi_write(in.byte.b4); spi_write(in.byte.b3); spi_write(in.byte.b2); spi_write(in.byte.b1); SPI_PORT |= (1<<SPI_MISO_PIN); r = spi_xfer(cmd); for (n=0; n < 9; n++) { if (r != 0xFF) break; r = spi_xfer(0xFF); } SPI_PORT &= ~(1<<SPI_MISO_PIN); return r; } uint8_t sd_wait_data(void) { uint16_t count=0; uint8_t r; do { r = spi_xfer(0xFF); //if (r != 0xFF) return r; if (r == 0xFE) return r; } while (count++ < 20000); return r; } #ifndef USBSTATE #define USBSTATE #endif #define READ_ONLY 0 volatile uint8_t media_rdonly USBSTATE; #define MEDIA_PRESENT_MASK 0x10 #define MEDIA_STATE_NOCARD 0x00 #define MEDIA_STATE_INITIALIZING 0x01 #define MEDIA_STATE_READY 0x12 #define MEDIA_STATE_BUSY 0x14 // TODO: implement this!!! #define MEDIA_STATE_CLAIMED 0x80 // media is claimed #define MEDIA_STATE_CLAIMED_STATUS 0x40 // claimed status reported #define MEDIA_STATE_CLAIMED_SENSE 0x20 // claimed scsi sense sent volatile uint8_t media_state USBSTATE; #define MEDIA_TYPE_SDv1 0x00 #define MEDIA_TYPE_SDv2 0x01 #define MEDIA_TYPE_SDHC 0x02 uint8_t media_type USBSTATE; void media_restart(void) { SPI_PORT |= (1<<SPI_SS_PIN); SPI_DDR |= ((1<<SPI_SS_PIN) | (1<<SPI_SCLK_PIN) | (1<<SPI_MOSI_PIN)); SPCR = (1<<SPE) | (1<<MSTR); // 8 Mbit/sec SPSR = (1<<SPI2X); } void media_init(void) { media_restart(); // TODO: initialize to unlocked condition media_state = MEDIA_STATE_NOCARD; media_type = MEDIA_TYPE_SDv1; media_rdonly = READ_ONLY; media_poll(); } void media_claim(void) { if (media_state & MEDIA_STATE_CLAIMED) return; while (!media_lock()) /*wait*/ ; media_state = MEDIA_STATE_CLAIMED; print("Media claimed by user program\n"); media_unlock(); } void media_release(uint8_t read_only_mode) { uint8_t mstate; mstate = media_state; if (!(mstate & MEDIA_STATE_CLAIMED)) return; print("Media release begin\n"); while (mstate != (MEDIA_STATE_CLAIMED|MEDIA_STATE_CLAIMED_STATUS|MEDIA_STATE_CLAIMED_SENSE)) { if (!usb_configuration) break; // TODO: a timeout?? //_delay_ms(100); mstate = media_state; //print("mstate = "); //phex(mstate); //phex(MEDIA_STATE_CLAIMED|MEDIA_STATE_CLAIMED_STATUS|MEDIA_STATE_CLAIMED_SENSE); //print("\n"); } // a brief delay is needed here... but why? delayMicroseconds(12500); print("Media released by user program\n"); media_init(); media_rdonly = read_only_mode; } void media_poll(void) { uint8_t i, r, mstate; mstate = media_state; //print("poll "); //phex(mstate); //print("\n"); if (mstate & MEDIA_STATE_CLAIMED) { // never access the SD card while it's claimed by the user program! return; } else if (mstate == MEDIA_STATE_NOCARD) { media_type = MEDIA_TYPE_SDv1; spi_select(); for (i=0; i<10; i++) { spi_write(0xFF); } r = sd_command(SD_CMD_GO_IDLE_STATE, 0); // CMD0 sd_deselect(); phex(r); print("\n"); if ((r & 0xFE) == 0) { r = sd_command(SD_CMD_SEND_IF_COND, 0x1AA); phex(r); if ((r & 0xFE) == 0) { spi_write(0xFF); spi_write(0xFF); r = spi_xfer(0xFF); i = spi_xfer(0xFF); phex(r); phex(i); print("\n"); if (r == 1 && i == 0xAA) { print("Card is version 2.0\n"); media_type = MEDIA_TYPE_SDv2; } } sd_deselect(); media_state = MEDIA_STATE_INITIALIZING; } } else { //r = sd_command(SD_CMD_SEND_OP_COND, 0); // CMD1 r = sd_command(SD_CMD_SEND_OP_COND, media_type ? 0x40000000 : 0); // CMD1 //sd_command(SD_CMD_APP_CMD, 0); //r = sd_command(SD_ACMD_SEND_OP_COND, media_type ? 0x40000000 : 0); // ACMD41 if (r) { phex(r); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); print("\n"); } sd_deselect(); phex(r); print("\n"); if (r == 0) { if (mstate == MEDIA_STATE_INITIALIZING && media_type) { r = sd_command(SD_CMD_READ_OCR, 0); phex(r); if (r == 0) { r = spi_xfer(0xFF); if (r & 0x40) { print("Card is SDHC\n"); media_type = MEDIA_TYPE_SDHC; } spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); } sd_deselect(); } media_state = MEDIA_STATE_READY; } else if (r & 0x80) { media_state = MEDIA_STATE_NOCARD; } } //return media_state; } inline uint32_t media_size(void) { uint8_t r; if (media_state != MEDIA_STATE_READY) return 1; r = sd_command(SD_CMD_SEND_CSD, 0); if (r) goto error; r = sd_wait_data(); if (r != 0xFE) goto error; r = spi_xfer(0xFF) & 0xC0; if (r == 0) { uint8_t c1, c2, c3, mult; uint16_t c_size; // Version 1.0 (standard capacity, up to 2 gigbytes) spi_ignore_bytes(4); mult = spi_xfer(0xFF); // READ_BL_LEN //phex(mult); mult &= 0x0F; c1 = spi_xfer(0xFF); // C_SIZE[12:11] c2 = spi_xfer(0xFF); // C_SIZE[10:3] c3 = spi_xfer(0xFF); // C_SIZE[2:0] //phex(c1); //phex(c2); //phex(c3); c1 &= 0x03; c_size = ((c1 << 10) | (c2 << 2) | (c3 >> 6)) + 1; c1 = spi_xfer(0xFF); // C_SIZE_MULT[2:1] c2 = spi_xfer(0xFF); // C_SIZE_MULT[0] //phex(c1); //phex(c2); //print("\n"); c1 &= 0x03; c2 &= 0x80; c1 = (c1 << 1) | (c2 >> 7); mult = mult + c1 - 7; spi_ignore_bytes(8); sd_deselect(); return ((uint32_t)c_size << mult); } else if (r == 0x40) { // Version 2.0 (high capacity, more than 2 gigbytes) uint8_t c1, c2, c3; uint32_t size; spi_ignore_bytes(6); c1 = spi_xfer(0xFF); // C_SIZE c2 = spi_xfer(0xFF); // C_SIZE c3 = spi_xfer(0xFF); // C_SIZE spi_ignore_bytes(9); //phex(c1); //phex(c2); //phex(c3); //print("\n"); c1 &= 0x3F; size = (((uint32_t)c1 << 16) | ((uint16_t)c2 << 8) | c3) + 1; size <<= 10; sd_deselect(); return size; } else { goto error; // unknown CSD_STRUCTURE } error: sd_deselect(); media_state = MEDIA_STATE_NOCARD; return 1; } uint8_t media_read_sector(uint32_t lba, uint8_t *buffer) { uint8_t r, i, ret=0; while (!media_lock()) /*wait*/ ; if (media_type != MEDIA_TYPE_SDHC) lba = (lba << 9); r = sd_command(SD_CMD_READ_SINGLE_BLOCK, lba); if (r) { // TODO: check for errors... print("User Read Error, r="); phex(r); print("\n"); goto done; } print("User Read OK, lba="); phex32(lba >> 9); print("\n"); if (sd_wait_data() != 0xFE) { print("User Read Error, bad token\n"); goto done; } for (i=0; i<64; i++) { *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); *buffer++ = spi_xfer(0xFF); } ret = 1; spi_write(0xFF); // ignore CRC spi_write(0xFF); done: sd_deselect(); media_unlock(); return ret; } static void media_send_begin(uint32_t lba) { uint8_t r; if (media_type != MEDIA_TYPE_SDHC) lba = (lba << 9); r = sd_command(SD_CMD_READ_MULTIPLE_BLOCK, lba); if (r) { // TODO: check for errors... print("Read Error, r="); phex(r); print("\n"); } else { print("Read OK\n"); } } static void media_send_chunk(uint32_t lba, uint8_t chunk) { uint8_t i; if (chunk == 0) { i = sd_wait_data(); //phex(i); if (i != 0xFE) { print("Read error, token="); phex(i); print("\n"); } } for (i=0; i<8; i++) { // TODO: asm optimization UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); UEDATX = spi_xfer(0xFF); } UEINTX = 0x3A; if (chunk == 7) { spi_write(0xFF); spi_write(0xFF); } //print("."); } static void media_send_end(void) { uint8_t r; r = sd_command(SD_CMD_STOP_TRANSMISSION, 0); // TODO: proper handling of stop transaction..... // but what is the proper way? Older cards stop instantly, // but newer ones spew 1 or 2 bytes of garbage, then maybe // 0xFF's until the ok (0) response. What a mess! spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); spi_write(0xFF); //print("\rr="); //phex(r); #if 0 if (r) { print("Stop Transmission Error, r="); phex(r); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); print("\n"); // handle errors } else { print("Stop Transmission OK, r="); phex(r); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); phex(spi_xfer(0xFF)); print("\n"); } do { r = spi_xfer(0xFF); //phex(r); } while (r == 0); #endif sd_deselect(); //print("\n"); } uint8_t media_write_sector(uint32_t lba, const uint8_t *buffer) { uint8_t r, i, ret=0; while (!media_lock()) /*wait*/ ; if (media_type != MEDIA_TYPE_SDHC) lba = (lba << 9); r = sd_command(SD_CMD_WRITE_SINGLE_BLOCK, lba); if (r) { // TODO: check for errors... print("User Write Error, r="); phex(r); print("\n"); goto done; } print("User Write OK, lba="); phex32(lba >> 9); print("\n"); spi_write(0xFE); // start block token for (i=0; i<64; i++) { spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); spi_write(*buffer++); } spi_write(0xFF); // CRC spi_write(0xFF); do { r = spi_xfer(0xFF); // wait for busy //phex(r); } while (r != 0xFF); ret = 1; done: sd_deselect(); media_unlock(); return ret; } static void media_receive_begin(uint32_t lba) { uint8_t r; // TODO: check media_rdonly, return error is read only mode if (media_type != MEDIA_TYPE_SDHC) lba = (lba << 9); sd_command(SD_CMD_WRITE_MULTIPLE_BLOCK, lba); if (r) { // TODO: check for errors... } } static void media_receive_chunk(uint32_t lba, uint8_t chunk) { uint8_t i, r; if (chunk == 0) { spi_write(0xFC); } for (i=0; i<8; i++) { // TODO: asm optimization spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); spi_write(UEDATX); } UEINTX = 0x6B; print("."); if (chunk == 7) { spi_write(0xFF); spi_write(0xFF); do { r = spi_xfer(0xFF); phex(r); } while (r != 0xFF); } } static void media_receive_end(void) { uint8_t r; print("stop"); spi_write(0xFD); spi_write(0xFF); do { // TODO: this can wait for a long time... would be // much better to create a busy media state and // return from the ISR, but then need to deal with // it everywhere else! r = spi_xfer(0xFF); phex(r); } while (r != 0xFF); sd_deselect(); print("\n"); }