From: rob-ng15 <58272847+rob-ng15@users.noreply.github.com> Date: Tue, 17 Mar 2020 09:51:11 +0000 (+0000) Subject: SPI hardware bitbanging from SD CARD X-Git-Tag: 24jan2021_ls180~553^2 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=27720409ce429aa4b6c214dadf4b542393bf90e5;p=litex.git SPI hardware bitbanging from SD CARD --- diff --git a/litex/soc/software/libbase/Makefile b/litex/soc/software/libbase/Makefile index 5cc81600..7ccb4d0b 100755 --- a/litex/soc/software/libbase/Makefile +++ b/litex/soc/software/libbase/Makefile @@ -2,7 +2,7 @@ include ../include/generated/variables.mak include $(SOC_DIRECTORY)/software/common.mak OBJECTS=exception.o libc.o errno.o crc16.o crc32.o console.o \ - system.o id.o uart.o time.o qsort.o strtod.o spiflash.o strcasecmp.o mdio.o + system.o id.o uart.o time.o qsort.o strtod.o spiflash.o spi.o strcasecmp.o mdio.o all: crt0-$(CPU)-ctr.o crt0-$(CPU)-xip.o libbase.a libbase-nofloat.a diff --git a/litex/soc/software/libbase/spi.c b/litex/soc/software/libbase/spi.c new file mode 100644 index 00000000..b602860a --- /dev/null +++ b/litex/soc/software/libbase/spi.c @@ -0,0 +1,585 @@ +// SD CARD bitbanging code for loading files from a FAT16 forrmatted partition into memory +// +// Code is known to work on a de10nano with MiSTer SDRAM and IO Boards - IO Board has a secondary SD CARD interface connected to GPIO pins +// SPI signals CLK, CS and MOSI are configured as GPIO output pins, and MISO is configued as GPIO input pins +// +// Protocol details developed from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ +// +// FAT16 details developed from https://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/ and https://codeandlife.com/2012/04/07/simple-fat-and-sd-tutorial-part-2/ + +// Import LiteX SoC details that are generated each time the SoC is compiled for the FPGA +// csr defines the SPI Control registers +// soc defines the clock CONFIG_CLOCK_FREQUENCY (50MHz for the VexRiscV processor on the MiSTer FPGA +// mem defines the addresses for the SDRAM MAIN_RAM_BASE and MAIN_RAM_SIZE +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CSR_SPI_BASE +// Import prototypes for the functions +#include + +// SPI +// cs line - high to indicate DESELECT +// - low to indicate SELECT +#define CS_HIGH 0x00 +#define CS_LOW 0x01 + +// control register values +// onebyte to indicate 1 byte being transferred +// spi_start to indicate START of transfer +// spi_done to indicate transfer DONE +#define ONEBYTE 0x0800 +#define SPI_START 0x01 +#define SPI_DONE 0x01 + +// Return values +#define SUCCESS 0x01 +#define FAILURE 0x00 + +// spi_write_byte +// Send a BYTE (8bits) to the SD CARD +// Seqeunce +// Set MOSI +// Set START bit and LENGTH=8 +// Await DONE +// +// No return values +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "SD Commands" +void spi_write_byte(unsigned char char_to_send); +void spi_write_byte(unsigned char char_to_send) +{ + // Place data into MOSI register + // Pulse the START bit and set LENGTH=8 + spi_mosi_write(char_to_send); + spi_control_write(ONEBYTE | SPI_START); + + // Wait for DONE + while( (spi_status_read() != SPI_DONE)) {} + + // Signal end of transfer + spi_control_write( 0x00 ); +} + + +// spi_read_rbyte +// Read a command response from the SD CARD - Equivalent to and R1 response or first byte of an R7 response +// Sequence +// Read MISO +// If MSB != 0 then send dsummy byte and re-read MISO +// +// Return value is the response from the SD CARD +// If the MSB is not 0, this would represent an ERROR +// Calling function to determine if the correct response has been received +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "SD Commands" +unsigned char spi_read_rbyte(void); +unsigned char spi_read_rbyte(void) +{ + int timeout=32; + unsigned char r=0; + + // Check if MISO is 0x0xxxxxxx as MSB=0 indicates valid response + r = spi_miso_read(); + while( ((r&0x80)!=0) && timeout>0) { + spi_mosi_write( 0xff ); + spi_control_write(ONEBYTE | SPI_START); + while( (spi_status_read() != SPI_DONE)) {} + r = spi_miso_read(); + spi_control_write( 0x00 ); + timeout--; + } + +// printf("Done\n"); + return r; +} + +// spi_read_byte +// Sequence +// Send dummy byte +// Read MISO +// +// Read subsequenct bytes from the SD CARD - MSB first +// NOTE different from the spi_read_rbyte as no need to await an intial 0 bit as card is already responsing +// Used to read additional response bytes, or data bytes from the SD CARD +// +// Return value is the byte read +// NOTE no error status as assumed bytes are read via CLK pulses +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "SD Commands" +unsigned char spi_read_byte(void); +unsigned char spi_read_byte(void) +{ + unsigned char r=0; + + spi_write_byte( 0xff ); + r = spi_miso_read(); + + return r; +} + +// SETSPIMODE +// Signal the SD CARD to switch to SPI mode +// Pulse the CLK line HIGH/LOW repeatedly with MOSI and CS_N HIGH +// Drop CS_N LOW and pulse the CLK +// Check MISO for HIGH +// Return 0 success, 1 failure +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "Initializing the SD Card" +unsigned char spi_setspimode(void); +unsigned char spi_setspimode(void) +{ + unsigned int i, r, timeout=32; + + // Initialise SPI mode + // set CS to HIGH + // Send pulses + do { + // set CS HIGH and send pulses + spi_cs_write(CS_HIGH); + for (i=0; i<10; i++) { + spi_write_byte( 0xff ); + } + + // set CS LOW and send pulses + spi_cs_write(CS_LOW); + r = spi_read_rbyte(); + + timeout--; + } while ( (timeout>0) && (r==0) ); + + if(timeout==0) return FAILURE; + + return SUCCESS; +} + +// SPI_SDCARD_GOIDLE +// Function exposed to BIOS to initialise SPI mode +// +// Sequence +// Set 100KHz timer mode +// Send CLK pulses to set SD CARD to SPI mode +// Send CMD0 - Software RESET - force SD CARD IDLE +// Send CMD8 - Check SD CARD type +// Send CMD55+ACMD41 - Force SD CARD READY +// Send CMD58 - Read SD CARD OCR (status register) +// Send CMD16 - Set SD CARD block size to 512 - Sector Size for the SD CARD +// NOTE - Each command is prefixed with a dummy set of CLK pulses to prepare SD CARD to receive a command +// Return 0 success, 1 failure +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "Initializing the SD Card" +unsigned char spi_sdcard_goidle(void) +{ + unsigned char r; // Response from SD CARD + int i, timeout; // TIMEOUT loop to send CMD55+ACMD41 repeatedly + + r = spi_setspimode(); // Set SD CARD to SPI mode + if( r != 0x01 ) return FAILURE; + + // CMD0 - Software reset - SD CARD IDLE + // Command Sequence is DUMMY=0xff CMD0=0x40 0x00 0x00 0x00 0x00 CRC=0x95 + // Expected R1 response is 0x01 indicating SD CARD is IDLE + spi_write_byte( 0xff ); spi_write_byte( 0x40 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x95 ); + r = spi_read_rbyte(); + if(r!=0x01) return FAILURE; + + // CMD8 - Check SD CARD type + // Command sequence is DUMMY=0xff CMD8=0x48 0x00 0x00 0x01 0xaa CRC=0x87 + // Expected R7 response is 0x01 followed by 0x00 0x00 0x01 0xaa (these trailing 4 bytes not currently checked) + spi_write_byte( 0xff ); spi_write_byte( 0x48 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x01 ); spi_write_byte( 0xaa ); spi_write_byte( 0x87 ); + r = spi_read_rbyte(); + if(r!=0x01) return FAILURE; + // Receive the trailing 4 bytes for R7 response - FIXME should check for 0x00 0x00 0x01 0xaa + for(i=0; i<4; i++) + r=spi_read_byte(); + + // CMD55+ACMD41 - Force SD CARD READY - prepare card for reading/writing + // Command sequence is CMD55 followed by ACMD41 + // Send commands repeatedly until SD CARD indicates READY 0x00 + // CMD55 Sequence is DUMMY=0xff CMD55=0x77 0x00 0x00 0x00 0x00 CRC=0x00 + // ACMD41 Sequence is DUMMY=0xff ACMD41=0x69 0x40 0x00 0x00 0x00 CRC=0x00 + // Expected R1 response is 0x00 indicating SD CARD is READY + timeout=32; + do { + spi_write_byte( 0xff ); spi_write_byte( 0x77 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); + r = spi_read_rbyte(); + + spi_write_byte( 0xff ); spi_write_byte( 0x69 ); spi_write_byte( 0x40 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); + r = spi_read_rbyte(); + timeout--; + } while ((r != 0x00) && (timeout>0)); + if(r!=0x00) return FAILURE; + + // CMD58 - Read SD CARD OCR (status register) + // FIXME - Find details on expected response from CMD58 to allow accurate checking of SD CARD R3 response + // Command sequence is DUMMY=0xff CMD58=0x7a 0x00 0x00 0x01 0xaa CRC=0xff + // Expected R3 response is 0x00 OR 0x01 followed by 4 (unchecked) trailing bytes + spi_write_byte( 0xff ); spi_write_byte( 0x7a ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0xff ); + r = spi_read_rbyte(); + if(r>0x01) return FAILURE; + // // Receive the trailing 4 bytes for R3 response + for(i=0; i<4; i++) + r=spi_read_byte(); + + // CMD16 - Set SD CARD block size to 512 - Sector Size for the SD CARD + // Command Sequence is DUMMY=0xff (512 as unsigned long = 0x00000200) 0x00 0x00 0x02 0x00 CRC=0xff + // Expected R1 response is 0x00 indicating SD CARD is READY + spi_write_byte( 0xff ); spi_write_byte( 0x50 ); spi_write_byte( 0x00 ); spi_write_byte( 0x00 ); spi_write_byte( 0x02 ); spi_write_byte( 0x00 ); spi_write_byte( 0xff ); + r=spi_read_rbyte(); + if(r!=0x00) return FAILURE; + + return SUCCESS; +} + +// READSECTOR +// Read a 512 byte sector from the SD CARD +// Given SECTORNUMBER and memory STORAGE +// +// Sequence +// Send CMD17 - Read Block +// Command Sequence is DUMMY=0xff CMD17=0x51 SECTORNUMBER (32bit UNSIGNED as bits 32-25,24-17, 16-9, 8-1) CRC=0xff +// Wait for SD CARD to send 0x00 indicating SD CARD is processing +// Wait for SD CARD to send 0xfe indicating SD CARD BLOCK START +// Read 512 bytes +// Read 8 DUMMY bytes +// Return 0 success, 1 failure +// +// Details from https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/ section "Read/Write SD Card" +unsigned char readSector(unsigned int sectorNumber, unsigned char *storage); +unsigned char readSector(unsigned int sectorNumber, unsigned char *storage) +{ + unsigned int n,timeout; // Number of bytes loop, timeout loop awaiting response bytes + unsigned char r; // Response bytes from SD CARD + + // CMD17 - Read Block + // Command Sequence is DUMMY=0xff CMD17=0x51 SECTORNUMBER (32bit UNSIGNED as bits 32-25,24-17, 16-9, 8-1) CRC=0xff + // Expected R1 response is 0x00 indicating SD CARD is processing + spi_write_byte( 0xff ); spi_write_byte( 0x51 ); spi_write_byte( (sectorNumber>>24)&0xff ); spi_write_byte( (sectorNumber>>16)&0xff ); spi_write_byte( (sectorNumber>>8)&0xff ); spi_write_byte( (sectorNumber)&0xff ); spi_write_byte( 0xff ); + r=spi_read_rbyte(); + if( r!=0x00 ) return FAILURE; + + // Await 0xfe to indicate BLOCK START + r=spi_read_byte(); + timeout=16384; + while( (r!=0xfe) && (timeout>0) ) { + r=spi_read_byte(); + timeout--; + } + if( r!=0xfe ) return FAILURE; + + // Read 512 bytes into storage + for(n=0; n<512; n++) + storage[n]=spi_read_byte(); + + // Read 8 dummy bytes + for(n=0; n<8; n++) + r=spi_read_byte(); + + return SUCCESS; +} + + +// FAT16 Specific code starts here +// Details from https://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/ + +// Structure to store SD CARD partition table +typedef struct { + unsigned char first_byte; + unsigned char start_chs[3]; + unsigned char partition_type; + unsigned char end_chs[3]; + unsigned long start_sector; + unsigned long length_sectors; +} __attribute((packed)) PartitionTable; + +PartitionTable sdCardPartition; + +// Structure to store SD CARD FAT16 Boot Sector (boot code is ignored, provides layout of the FAT16 partition on the SD CARD) +typedef struct { + unsigned char jmp[3]; + char oem[8]; + unsigned short sector_size; + unsigned char sectors_per_cluster; + unsigned short reserved_sectors; + unsigned char number_of_fats; + unsigned short root_dir_entries; + unsigned short total_sectors_short; // if zero, later field is used + unsigned char media_descriptor; + unsigned short fat_size_sectors; + unsigned short sectors_per_track; + unsigned short number_of_heads; + unsigned long hidden_sectors; + unsigned long total_sectors_long; + + unsigned char drive_number; + unsigned char current_head; + unsigned char boot_signature; + unsigned long volume_id; + char volume_label[11]; + char fs_type[8]; + char boot_code[448]; + unsigned short boot_sector_signature; +} __attribute((packed)) Fat16BootSector; + +Fat16BootSector sdCardFatBootSector; + +// Structure to store SD CARD FAT16 Root Directory Entries +// Allocated to MAIN RAM - hence pointer +typedef struct { + unsigned char filename[8]; + unsigned char ext[3]; + unsigned char attributes; + unsigned char reserved[10]; + unsigned short modify_time; + unsigned short modify_date; + unsigned short starting_cluster; + unsigned long file_size; +} __attribute((packed)) Fat16Entry; + +Fat16Entry *sdCardFat16RootDir; + +// Structure to store SD CARD FAT16 Entries +// Array of UNSIGNED SHORTS (16bit integers) +unsigned short *sdCardFatTable; + +// Calculated sector numbers on the SD CARD for the FAT16 Entries and ROOT DIRECTORY +unsigned int fatSectorStart, rootDirSectorStart; + +// Storage for SECTOR read from SD CARD +unsigned char sdCardSector[512]; + +// SPI_SDCARD_READMBR +// Function exposed to BIOS to retrieve FAT16 partition details, FAT16 Entry Table, FAT16 Root Directory +// MBR = Master Boot Record - Sector 0x00000000 on SD CARD - Contains Partition 1 details at 0x1be +// +// FIXME only checks partition 1 out of 4 +// +// Return 0 success, 1 failure +// +// Details from https://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/ +unsigned char spi_sdcard_readMBR(void) +{ + int i, n; + + // Read Sector 0x00000000 + printf("Reading MBR\n"); + if( readSector(0x00000000, sdCardSector)==SUCCESS ) { + // Copy Partition 1 Entry from byte 0x1be + // FIXME should check 0x55 0xaa at end of sector + memcpy(&sdCardPartition, &sdCardSector[0x1be], sizeof(PartitionTable)); + + // Check Partition 1 is valid, FIRST_BYTE=0x00 or 0x80 + // Check Partition 1 has type 4, 6 or 14 (FAT16 of various sizes) + printf("Partition 1 Information: Active=0x%02x, Type=0x%02x, LBAStart=0x%08x\n", sdCardPartition.first_byte, sdCardPartition.partition_type, sdCardPartition.start_sector); + if( (sdCardPartition.first_byte!=0x80) && (sdCardPartition.first_byte!=0x00) ) { + printf("Partition 1 Not Valid\n"); + return FAILURE; + } + if( (sdCardPartition.partition_type==4) || (sdCardPartition.partition_type==6) || (sdCardPartition.partition_type==14) ) { + printf("Partition 1 is FAT16\n"); + } + else { + printf("Partition 1 Not FAT16\n"); + return FAILURE; + } + } + else { + printf("Failed to read MBR\n"); + return FAILURE; + } + + // Read Parition 1 Boot Sector - Found from Partion Table + printf("\nRead FAT16 Boot Sector\n"); + if( readSector(sdCardPartition.start_sector, sdCardSector)==SUCCESS ) { + memcpy(&sdCardFatBootSector, &sdCardSector, sizeof(Fat16BootSector)); + } + else { + printf("Failed to read FAT16 Boot Sector\n"); + return FAILURE; + } + + // Print details of Parition 1 + printf(" Jump Code: 0x%02x 0x%02x 0x%02x\n",sdCardFatBootSector.jmp[0],sdCardFatBootSector.jmp[1],sdCardFatBootSector.jmp[2]); + printf(" OEM Code: ["); + for(n=0; n<8; n++) + printf("%c",sdCardFatBootSector.oem[n]); + printf("]\n"); + printf(" Sector Size: %d\n",sdCardFatBootSector.sector_size); + printf(" Sectors Per Cluster: %d\n",sdCardFatBootSector.sectors_per_cluster); + printf(" Reserved Sectors: %d\n",sdCardFatBootSector.reserved_sectors); + printf(" Number of Fats: %d\n",sdCardFatBootSector.number_of_fats); + printf(" Root Dir Entries: %d\n",sdCardFatBootSector.root_dir_entries); + printf(" Total Sectors Short: %d\n",sdCardFatBootSector.total_sectors_short); + printf(" Media Descriptor: 0x%02x\n",sdCardFatBootSector.media_descriptor); + printf(" Fat Size Sectors: %d\n",sdCardFatBootSector.fat_size_sectors); + printf(" Sectors Per Track: %d\n",sdCardFatBootSector.sectors_per_track); + printf(" Number of Heads: %d\n",sdCardFatBootSector.number_of_heads); + printf(" Hidden Sectors: %d\n",sdCardFatBootSector.hidden_sectors); + printf(" Total Sectors Long: %d\n",sdCardFatBootSector.total_sectors_long); + printf(" Drive Number: 0x%02x\n",sdCardFatBootSector.drive_number); + printf(" Current Head: 0x%02x\n",sdCardFatBootSector.current_head); + printf(" Boot Signature: 0x%02x\n",sdCardFatBootSector.boot_signature); + printf(" Volume ID: 0x%08x\n",sdCardFatBootSector.volume_id); + printf(" Volume Label: ["); + for(n=0; n<11; n++) + printf("%c",sdCardFatBootSector.volume_label[n]); + printf("]\n"); + printf(" Volume Label: ["); + for(n=0; n<8; n++) + printf("%c",sdCardFatBootSector.fs_type[n]); + printf("]\n"); + printf(" Boot Sector Signature: 0x%04x\n\n",sdCardFatBootSector.boot_sector_signature); + + // Check Partition 1 is valid, not 0 length + if(sdCardFatBootSector.total_sectors_long==0) { + printf("Error reading FAT16 Boot Sector\n"); + return FAILURE; + } + + // Read in FAT16 File Allocation Table, array of 16bit unsinged integers + // Calculate Storage from TOP of MAIN RAM + sdCardFatTable = (unsigned short *)(MAIN_RAM_BASE+MAIN_RAM_SIZE-sdCardFatBootSector.sector_size*sdCardFatBootSector.fat_size_sectors); + printf("sdCardFatTable = 0x%08x Reading Fat16 Table (%d Sectors Long)\n\n",sdCardFatTable,sdCardFatBootSector.fat_size_sectors); + + // Calculate Start of FAT16 File Allocation Table (start of partition plus reserved sectors) + fatSectorStart=sdCardPartition.start_sector+sdCardFatBootSector.reserved_sectors; + for(n=0; n0)) { + printf(" File %d [",n); + for( i=0; i<8; i++) { + if( (sdCardFat16RootDir[n].filename[i]>31) && (sdCardFat16RootDir[n].filename[i]<127) ) + printf("%c",sdCardFat16RootDir[n].filename[i]); + else + printf(" "); + } + printf("."); + for( i=0; i<3; i++) { + if( (sdCardFat16RootDir[n].ext[i]>31) && (sdCardFat16RootDir[n].ext[i]<127) ) + printf("%c",sdCardFat16RootDir[n].ext[i]); + else + printf(" "); + } + printf("] @ Cluster %d for %d bytes\n",sdCardFat16RootDir[n].starting_cluster,sdCardFat16RootDir[n].file_size); + } + } + + printf("\n"); + return SUCCESS; +} + +// SPI_SDCARD_READFILE +// Function exposed to BIOS to retrieve FILENAME+EXT into ADDRESS +// +// FIXME only checks UPPERCASE 8+3 filenames +// +// Return 0 success, 1 failure +// +// Details from https://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/ +unsigned char spi_sdcard_readFile(char *filename, char *ext, unsigned long address) +{ + int i, n, sector; + unsigned short fileClusterStart; + unsigned long fileLength, bytesRemaining, clusterSectorStart; + unsigned short nameMatch; + printf("Reading File [%s.%s] into 0x%08x : ",filename, ext, address); + + // Find FILENAME+EXT in Root Directory + // Indicate FILE found by setting the starting cluster number + fileClusterStart=0; n=0; + while( (fileClusterStart==0) && (n1) + for(i=0; i<1+((fileLength/sdCardFatBootSector.sectors_per_cluster)/sdCardFatBootSector.sector_size); i++) { + printf("%d ",fileClusterStart); + + // Locate start of cluster on SD CARD and read appropraite number of sectors + clusterSectorStart=rootDirSectorStart+(fileClusterStart-1)*sdCardFatBootSector.sectors_per_cluster; + for(sector=0; sectorsdCardFatBootSector.sector_size) { + if( readSector(clusterSectorStart+sector,(unsigned char *)address) == FAILURE ) { + printf("Read Error\n"); + return FAILURE; + } + bytesRemaining=bytesRemaining-sdCardFatBootSector.sector_size; + address=address+sdCardFatBootSector.sector_size; + } else { + if( readSector(clusterSectorStart+sector,sdCardSector) == FAILURE ) { + printf("Read Error\n"); + return FAILURE; + } + memcpy((unsigned char *)address, sdCardSector, bytesRemaining); + bytesRemaining=0; + } + } + + // Move to next cluster + fileClusterStart=sdCardFatTable[fileClusterStart]; + } + printf("\n\n"); + return SUCCESS; +} +#endif