URL
https://opencores.org/ocsvn/s80186/s80186/trunk
Subversion Repositories s80186
[/] [s80186/] [trunk/] [bios/] [sd.c] - Rev 2
Compare with Previous | Blame | View Log
// Copyright Jamie Iles, 2017 // // This file is part of s80x86. // // s80x86 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, either version 3 of the License, or // (at your option) any later version. // // s80x86 is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with s80x86. If not, see <http://www.gnu.org/licenses/>. #include "io.h" #include "serial.h" #include "bios.h" #include "utils.h" #define SPI_CONTROL_PORT 0xfff0 #define SPI_TRANSFER_PORT 0xfff2 #define SPI_XFER_BUSY (1 << 8) #define SPI_CS_DEACTIVATE (1 << 9) static char spi_xfer_buf[1280]; static char sd_is_sdhc; static void noinline spi_xfer_buf_set(int offs, unsigned char v) { spi_xfer_buf[offs] = v; } static unsigned char noinline spi_xfer_buf_get(int offs) { return spi_xfer_buf[offs]; } static void spi_wait_idle(void) { while (inw(SPI_TRANSFER_PORT) & SPI_XFER_BUSY) continue; } static void spi_xfer(int len) { int i; for (i = 0; i < len; ++i) { outw(SPI_TRANSFER_PORT, spi_xfer_buf_get(i)); spi_wait_idle(); spi_xfer_buf_set(i, inw(SPI_TRANSFER_PORT) & 0xff); } } static void sd_send_initial_clock(void) { int i; for (i = 0; i < 8; ++i) spi_xfer_buf_set(i, 0xff); // No CS, slow clock outw(SPI_CONTROL_PORT, SPI_CS_DEACTIVATE | 0x1ff); spi_xfer(8); } static void sd_flush_fifo(void) { int i; for (i = 0; i < 128; ++i) spi_xfer_buf_set(i, 0xff); // No CS, slow clock outw(SPI_CONTROL_PORT, 0x1ff); spi_xfer(8); } struct spi_cmd { unsigned char cmd; unsigned char arg[4]; unsigned char crc; unsigned short data_seg; const void *data_addr; unsigned short tx_datalen; unsigned short rx_datalen; }; struct r1_response { unsigned char v; }; #define R1_ERROR_MASK 0xfe #define SD_NCR 8 #define DATA_START_TOKEN 0xfe #define BLOCK_SIZE 512 /* Maximum number of high bytes to read before a data start token. */ #define MAX_DATA_START_OFFS 512 static unsigned short spi_command_len(const struct spi_cmd *cmd) { return 1 + 6 + cmd->tx_datalen + cmd->rx_datalen + SD_NCR; } static void spi_do_command(const struct spi_cmd *cmd) { int m, cmdlen; cmdlen = spi_command_len(cmd); // The command. spi_xfer_buf_set(0, 0xff); spi_xfer_buf_set(1, cmd->cmd); for (m = 0; m < 4; ++m) spi_xfer_buf_set(2 + m, cmd->arg[m]); spi_xfer_buf_set(6, cmd->crc); // Transmit data. memcpy_seg(get_cs(), spi_xfer_buf + 7, cmd->data_seg, cmd->data_addr, cmd->tx_datalen); // Initialize receive buffer so we don't shift out new, garbage data. for (m = 7 + cmd->tx_datalen; m < cmdlen; ++m) spi_xfer_buf_set(m, 0xff); // Fast clock, CS enabled. outw(SPI_CONTROL_PORT, 0x1); spi_xfer(cmdlen); } static int find_r1_response(struct r1_response *r1) { int i; r1->v = 0; for (i = 0; i < sizeof(spi_xfer_buf) / sizeof(spi_xfer_buf[0]); ++i) if (spi_xfer_buf_get(i) != 0xff) { r1->v = spi_xfer_buf_get(i); break; } if (i == sizeof(spi_xfer_buf)) return -1; return i; } static int sd_send_reset(void) { struct spi_cmd cmd = { .cmd = 0x40, .crc = 0x95, .rx_datalen = 1, }; struct r1_response r1; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; return r1.v & R1_ERROR_MASK; } static int sd_send_if_cond(void) { struct spi_cmd cmd = { .cmd = 0x48, .crc = 0x87, .arg = {0x00, 0x00, 0x01, 0xaa}, .rx_datalen = 1, }; struct r1_response r1; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; return r1.v & R1_ERROR_MASK; } static int sd_send_read_ocr(void) { struct spi_cmd cmd = { .cmd = 0x7a, .rx_datalen = 5, }; struct r1_response r1; spi_do_command(&cmd); int r1_offs = find_r1_response(&r1); if (r1_offs < 0) return -1; union { unsigned char u8[4]; unsigned long u32; } converter = { .u8 = { spi_xfer_buf_get(r1_offs + 4), spi_xfer_buf_get(r1_offs + 3), spi_xfer_buf_get(r1_offs + 2), spi_xfer_buf_get(r1_offs + 1), }, }; if (converter.u32 & (1LU << 30)) sd_is_sdhc = 1; else sd_is_sdhc = 0; return r1.v & R1_ERROR_MASK; } static int send_acmd(void) { struct spi_cmd cmd = { .cmd = 0x77, .arg = {0x00, 0x00, 0x00, 0x00}, .rx_datalen = 1, }; struct r1_response r1; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; return r1.v & R1_ERROR_MASK; } static int sd_wait_ready(void) { struct r1_response r1 = {}; do { struct spi_cmd cmd = { .cmd = 0x69, .arg = {0x40, 0x00, 0x00, 0x00}, .rx_datalen = 1, }; int rc = send_acmd(); if (rc) return rc; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; if (r1.v & R1_ERROR_MASK) return r1.v & R1_ERROR_MASK; } while (r1.v & 0x1); return 0; } static int sd_set_blocklen(void) { struct spi_cmd cmd = { .cmd = 0x50, /* BLOCK_SIZE bytes */ .arg = {0x00, 0x00, 0x02, 0x00}, .rx_datalen = 1, }; struct r1_response r1; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; return r1.v & R1_ERROR_MASK; } static int find_data_start(int r1offs) { ++r1offs; while (spi_xfer_buf_get(r1offs) != DATA_START_TOKEN && r1offs < sizeof(spi_xfer_buf)) ++r1offs; return r1offs == sizeof(spi_xfer_buf) ? -1 : r1offs + 1; } static int noinline sd_make_write_request(unsigned long address) { struct spi_cmd cmd = { .cmd = 0x58, .rx_datalen = 1, }; struct r1_response r1; cmd.arg[0] = (address >> 24) & 0xff; cmd.arg[1] = (address >> 16) & 0xff; cmd.arg[2] = (address >> 8) & 0xff; cmd.arg[3] = (address >> 0) & 0xff; spi_do_command(&cmd); if (find_r1_response(&r1) < 0) return -1; return r1.v & R1_ERROR_MASK; } static void noinline sd_write_block(unsigned short sseg, unsigned short saddr) { spi_xfer_buf_set(0, 0xff); // Gap spi_xfer_buf_set(1, 0xfe); // Data start token memcpy_seg(get_cs(), spi_xfer_buf + 2, sseg, (const void *)saddr, 512); spi_xfer_buf_set(2 + 512, 0x0); // CRC1 spi_xfer_buf_set(3 + 512, 0x0); // CRC2 spi_xfer_buf_set(4 + 512, 0xff); // Packet response spi_xfer(5 + 512); } static int noinline write_received(unsigned char packet_response) { return (packet_response & 0x1) && ((packet_response >> 1) & 0x7) == 0x2; } static int noinline wait_for_write_completion(void) { int i = 0; for (i = 0; i < 4096; ++i) { spi_xfer_buf_set(0, 0xff); spi_xfer(1); if (spi_xfer_buf_get(0) != 0) return 0; } return -1; } int write_sector(unsigned short sector, unsigned short sseg, unsigned short saddr) { unsigned long address = sector; if (!sd_is_sdhc) address *= 512; if (sd_make_write_request(address)) return -1; sd_write_block(sseg, saddr); unsigned char packet_response = spi_xfer_buf_get(4 + 512); if (!write_received(packet_response)) return -1; return wait_for_write_completion(); } int read_sector(unsigned short sector, unsigned short dseg, unsigned short daddr) { struct spi_cmd cmd = { .cmd = 0x51, /* r1, start token, data, CRC16 */ .rx_datalen = 1 + 1 + BLOCK_SIZE + 2 + MAX_DATA_START_OFFS, }; struct r1_response r1; int r1offs, data_start; unsigned long address = sector; if (!sd_is_sdhc) address *= 512; cmd.arg[0] = (address >> 24) & 0xff; cmd.arg[1] = (address >> 16) & 0xff; cmd.arg[2] = (address >> 8) & 0xff; cmd.arg[3] = (address >> 0) & 0xff; spi_do_command(&cmd); r1offs = find_r1_response(&r1); if (r1offs < 0) { putstr("Failed to find R1 response\n"); return -1; } if (r1.v & R1_ERROR_MASK) { putstr("Read sector failed\n"); return -1; } data_start = find_data_start(r1offs); if (data_start < 0) { putstr("No data start token\n"); return -1; } memcpy_seg(dseg, (void *)daddr, get_cs(), spi_xfer_buf + data_start, 512); return 0; } void sd_init(void) { sd_send_initial_clock(); sd_flush_fifo(); if (sd_send_reset()) panic("Failed to reset SD card"); if (sd_send_if_cond()) panic("Failed to send interface conditions to SD card"); if (sd_send_read_ocr()) panic("Failed to read OCR for SD card"); if (sd_wait_ready()) panic("Failed to wait for SD card init"); if (sd_send_read_ocr()) panic("Failed to re-read OCR for SD card"); if (sd_set_blocklen()) panic("Failed to wait for SD card init"); putstr("SD card ready\n"); } void sd_boot(void) { if (read_sector(0, 0, 0x7c00)) panic("Failed to read boot sector\n"); putstr("Booting from SD card...\n"); asm volatile( "mov $0x80, %dl\n" "jmp $0x0000, $0x7c00"); }