mirror of
https://github.com/openhwgroup/cvw
synced 2025-01-23 13:04:28 +00:00
256 lines
7.0 KiB
C
256 lines
7.0 KiB
C
///////////////////////////////////////////////////////////////////////
|
|
// sd.c
|
|
//
|
|
// Written: Jaocb Pease jacob.pease@okstate.edu 7/22/2024
|
|
//
|
|
// Purpose: SD Card protocol functions
|
|
//
|
|
//
|
|
//
|
|
// A component of the Wally configurable RISC-V project.
|
|
//
|
|
// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
|
//
|
|
// Licensed under the Solderpad Hardware License v 2.1 (the
|
|
// “License”); you may not use this file except in compliance with the
|
|
// License, or, at your option, the Apache License version 2.0. You
|
|
// may obtain a copy of the License at
|
|
//
|
|
// https://solderpad.org/licenses/SHL-2.1/
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, any work
|
|
// distributed under the License is distributed on an “AS IS” BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
#include "sd.h"
|
|
#include "spi.h"
|
|
#include "uart.h"
|
|
#include "fail.h"
|
|
#include "time.h"
|
|
|
|
// Parallel byte update CRC7-CCITT algorithm.
|
|
// The result is the CRC7 result, left shifted over by 1
|
|
// which is perfect, since we append a 1 at the end anyway
|
|
uint8_t crc7(uint8_t prev, uint8_t in) {
|
|
// CRC polynomial 0x89
|
|
uint8_t remainder = prev ^ in;
|
|
remainder ^= (remainder >> 4) ^ (remainder >> 7);
|
|
remainder = (remainder << 1) ^ (remainder << 4);
|
|
return remainder & 0xff;
|
|
}
|
|
|
|
// Need to check this. This could be wrong as well.
|
|
uint16_t crc16(uint16_t crc, uint8_t data) {
|
|
// CRC polynomial 0x11021
|
|
crc = (uint8_t)(crc >> 8) | (crc << 8);
|
|
crc ^= data;
|
|
crc ^= (uint8_t)(crc >> 4) & 0xf;
|
|
crc ^= crc << 12;
|
|
crc ^= (crc & 0xff) << 5;
|
|
return crc;
|
|
}
|
|
|
|
// sd_cmd ------------------------------------------------------------
|
|
// Sends SD card command using SPI mode.
|
|
// This function:
|
|
// * Chooses the response length based on the input command
|
|
// * Makes use of SPI's full duplex. For every byte sent,
|
|
// a byte is received. Thus for every byte sent as part of
|
|
// a command, a useless byte must be read from the receive
|
|
// FIFO.
|
|
// * Takes advantage of the Sifive SPI peripheral spec's
|
|
// watermark and interrupt features to determine when a
|
|
// transfer is complete. This should save on cycles since
|
|
// no arbitrary delays need to be added.
|
|
|
|
uint64_t sd_cmd(uint8_t cmd, uint32_t arg, uint8_t crc) {
|
|
uint8_t response_len;
|
|
uint8_t i;
|
|
uint8_t shiftAmnt;
|
|
uint64_t r;
|
|
uint8_t rbyte;
|
|
|
|
// Initialize the response with 0's.
|
|
r = 0;
|
|
|
|
// Choose response length based on cmd input.
|
|
// Most commands return an R1 format response.
|
|
switch (cmd) {
|
|
case 8:
|
|
response_len = R7_RESPONSE;
|
|
break;
|
|
case 12:
|
|
response_len = R1B_RESPONSE;
|
|
break;
|
|
default:
|
|
response_len = R1_RESPONSE;
|
|
break;
|
|
}
|
|
|
|
// Make interrupt pending after response fifo receives the correct
|
|
// response length. Probably unecessary so let's wait and see what
|
|
// happens.
|
|
// write_reg(SPI_RXMARK, response_len);
|
|
|
|
// Chip select must remain asserted during transaction
|
|
if (cmd != SD_CMD_STOP_TRANSMISSION) {
|
|
write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_HOLD);
|
|
}
|
|
|
|
// Write all 7 bytes into transfer fifo
|
|
// spi_sendbyte(0xff);
|
|
spi_dummy();
|
|
spi_sendbyte(0x40 | cmd);
|
|
spi_sendbyte(arg >> 24);
|
|
spi_sendbyte(arg >> 16);
|
|
spi_sendbyte(arg >> 8);
|
|
spi_sendbyte(arg);
|
|
spi_sendbyte(crc);
|
|
|
|
// Wait for command to send
|
|
// The Transfer IP bit should go high when the txFIFO is empty
|
|
// while(!(read_reg(SPI_IP) & 1)) {}
|
|
waittx();
|
|
|
|
// Read the dummy rxFIFO entries to move the head back to the tail
|
|
for (i = 0; i < 7; i++) {
|
|
spi_readbyte();
|
|
}
|
|
|
|
// Send "dummy signals". Since SPI is duplex,
|
|
// useless bytes must be transferred
|
|
/* for (i = 0; i < response_len; i++) { */
|
|
/* spi_sendbyte(0xFF); */
|
|
/* } */
|
|
|
|
/* // Wait for transfer fifo again */
|
|
/* waittx(); */
|
|
|
|
// Wait for actual response from SD card
|
|
// All responses start with a 0. Output of SDCIn is high, unless
|
|
// a message is being transferred.
|
|
do {
|
|
rbyte = spi_dummy();
|
|
} while ( (rbyte & 0x80) != 0 );
|
|
|
|
// Note about the compiler. In order to compile as sll instead of
|
|
// sllw, the number to shift has to be a 64 bit number.
|
|
r = ((uint64_t)rbyte) << ((response_len - 1)*8);
|
|
|
|
// Read rxfifo response
|
|
for (i = 1; i < response_len; i++) {
|
|
rbyte = spi_dummy();
|
|
r = r | (((uint64_t)rbyte) << ((response_len - 1 - i)*8));
|
|
}
|
|
|
|
if (cmd != 18) {
|
|
write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_AUTO);
|
|
} else {
|
|
spi_dummy();
|
|
}
|
|
return r;
|
|
} // sd_cmd
|
|
|
|
uint64_t sd_read64(uint16_t * crc) {
|
|
uint64_t r;
|
|
uint8_t rbyte;
|
|
int i;
|
|
|
|
/* for (i = 0; i < 8; i++) { */
|
|
/* spi_sendbyte(0xFF); */
|
|
/* } */
|
|
|
|
/* waittx(); */
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
rbyte = spi_dummy();
|
|
*crc = crc16(*crc, rbyte);
|
|
r = r | ((uint64_t)(rbyte) << ((8 - 1 - i)*8));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
// Utility defines for CMD0, CMD8, CMD55, and ACMD41
|
|
#define CMD0() sd_cmd( 0, 0x00000000, 0x95) // Reset SD card into IDLE state
|
|
#define CMD8() sd_cmd( 8, 0x000001aa, 0x87) //
|
|
#define CMD55() sd_cmd(55, 0x00000000, 0x65) //
|
|
#define ACMD41() sd_cmd(41, 0x40000000, 0x77) //
|
|
|
|
// init_sd: ----------------------------------------------------------
|
|
// This first initializes the SPI peripheral then initializes the SD
|
|
// card itself. We use the uart to display anything that goes wrong.
|
|
int init_sd(uint32_t freq, uint32_t sdclk){
|
|
print_time();
|
|
println("Initializing SPI Controller.");
|
|
spi_init();
|
|
|
|
uint64_t r;
|
|
uint32_t newClockDiv;
|
|
int n;
|
|
|
|
print_time();
|
|
println("Initializing SD Card in SPI mode.");
|
|
// This is necessary. This is the card's pre-init state initialization.
|
|
write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_OFF);
|
|
for (int i = 0; i < 10; i++) {
|
|
spi_txrx(0xff);
|
|
}
|
|
write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_AUTO);
|
|
|
|
// CMD0 --------------------------------------------------------------
|
|
// Reset SD Card command
|
|
// Initializes SD card into SPI mode if CS is asserted '0'
|
|
// We expect to get the R1 response 0x01 which means that the
|
|
// card has been put into the idle state.
|
|
print_time();
|
|
print_uart("CMD0: ");
|
|
n = 0;
|
|
do {
|
|
r = CMD0();
|
|
n++;
|
|
if (n == 1000) {
|
|
fail();
|
|
}
|
|
} while ( r != 0x01 );
|
|
println_with_r1("Success, r = 0x", r & 0xff);
|
|
|
|
// CMD8 -------------------------------------------------------------
|
|
//
|
|
print_time();
|
|
print_uart("CMD8: ");
|
|
r = CMD8();
|
|
if ((r & 0x000000ff0000ffff) != 0x01000001aa) {
|
|
println_with_r7("Failed, 0x", r);
|
|
fail();
|
|
}
|
|
println_with_r7("Success, 0x", r);
|
|
|
|
// ACMD41 -----------------------------------------------------------
|
|
print_time();
|
|
print_uart("ACMD41: ");
|
|
n = 0;
|
|
do {
|
|
CMD55();
|
|
r = ACMD41();
|
|
n++;
|
|
if (n == 1000) {
|
|
fail();
|
|
}
|
|
} while (r == 0x1);
|
|
println_with_r1("Success, r = 0x", r & 0xff);
|
|
|
|
print_time();
|
|
println_with_dec("New clock frequency: ", (uint64_t)sdclk);
|
|
spi_set_clock(freq, sdclk);
|
|
|
|
print_time();
|
|
println("SD card is initialized.");
|
|
}
|
|
|