|
@@ -0,0 +1,720 @@ |
|
|
|
|
|
/*!
|
|
|
|
|
|
* \file drv/sd_spi.h
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* SD card driver using SPI interface
|
|
|
|
|
|
*
|
|
|
|
|
|
* \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
|
|
|
|
|
|
*
|
|
|
|
|
|
* <dl class=\"section copyright\"><dt>License</dt><dd>
|
|
|
|
|
|
* The MIT License (MIT)
|
|
|
|
|
|
*
|
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
|
*
|
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
|
* copies or substantial portions of the Software.
|
|
|
|
|
|
*
|
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
|
* SOFTWARE.
|
|
|
|
|
|
* </dd></dl>
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef TBX_DRV_SD_SPI_H_
|
|
|
|
|
|
#define TBX_DRV_SD_SPI_H_
|
|
|
|
|
|
|
|
|
|
|
|
#include <core/core.h>
|
|
|
|
|
|
#include <core/crtp.h>
|
|
|
|
|
|
|
|
|
|
|
|
//#include <drv/diskio.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include <ctime>
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
|
|
namespace tbx {
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
*
|
|
|
|
|
|
* http://elm-chan.org/docs/mmc/mmc_e.html
|
|
|
|
|
|
*
|
|
|
|
|
|
* CRTP requirements
|
|
|
|
|
|
* bool WP_impl (); // write protect, true => write protect
|
|
|
|
|
|
* bool CD_impl (); // check disk present, true => present
|
|
|
|
|
|
* void CS_impl (bool select); // Chip select, true => select
|
|
|
|
|
|
* void PWR_impl(bool state); // SD power, true => power the card
|
|
|
|
|
|
* data_type SPI_rw_impl (data_type); // SPI read-write functionality
|
|
|
|
|
|
* bool SPI_set_clk_impl(uint32_t clk); // SPI set clock functionality
|
|
|
|
|
|
* clock_t clock_impl(); // get system's CPU time
|
|
|
|
|
|
*/
|
|
|
|
|
|
template <typename Impl_t>
|
|
|
|
|
|
class sd_card {
|
|
|
|
|
|
_CRTP_IMPL(Impl_t);
|
|
|
|
|
|
|
|
|
|
|
|
using data_type = uint8_t;
|
|
|
|
|
|
|
|
|
|
|
|
// Driver settings
|
|
|
|
|
|
constexpr static clock_t SD_WaitTimeout = 500; // 500 [CPU time]
|
|
|
|
|
|
constexpr static clock_t SD_PowerTimeout= 250; // 250 [CPU time]
|
|
|
|
|
|
constexpr static clock_t SD_RxTimeout = 100; // 100 [CPU time]
|
|
|
|
|
|
constexpr static clock_t SD_InitTimeout = 2000; // 2000 [CPU time]
|
|
|
|
|
|
constexpr static uint32_t MaxInitClock = 400000; // 400000 [Hz]
|
|
|
|
|
|
|
|
|
|
|
|
// MMC/SDC definitions
|
|
|
|
|
|
constexpr static data_type CMD_MSB = 0x40;
|
|
|
|
|
|
constexpr static data_type CMD_CRC_LSB = 0x01;
|
|
|
|
|
|
|
|
|
|
|
|
constexpr static data_type CMD0 = (CMD_MSB | 0); //!< GO_IDLE_STATE
|
|
|
|
|
|
constexpr static data_type CMD1 = (CMD_MSB | 1); //!< SEND_OP_COND (MMC)
|
|
|
|
|
|
constexpr static data_type CMD8 = (CMD_MSB | 8); //!< SEND_IF_COND
|
|
|
|
|
|
constexpr static data_type CMD9 = (CMD_MSB | 9); //!< SEND_CSD
|
|
|
|
|
|
constexpr static data_type CMD10 = (CMD_MSB | 10); //!< SEND_CID
|
|
|
|
|
|
constexpr static data_type CMD12 = (CMD_MSB | 12); //!< STOP_TRANSMISSION
|
|
|
|
|
|
constexpr static data_type CMD16 = (CMD_MSB | 16); //!< SET_BLOCKLEN
|
|
|
|
|
|
constexpr static data_type CMD17 = (CMD_MSB | 17); //!< READ_SINGLE_BLOCK
|
|
|
|
|
|
constexpr static data_type CMD18 = (CMD_MSB | 18); //!< READ_MULTIPLE_BLOCK
|
|
|
|
|
|
constexpr static data_type CMD23 = (CMD_MSB | 23); //!< SET_BLOCK_COUNT (MMC)
|
|
|
|
|
|
constexpr static data_type CMD24 = (CMD_MSB | 24); //!< WRITE_BLOCK
|
|
|
|
|
|
constexpr static data_type CMD25 = (CMD_MSB | 25); //!< WRITE_MULTIPLE_BLOCK
|
|
|
|
|
|
constexpr static data_type CMD55 = (CMD_MSB | 55); //!< APP_CMD
|
|
|
|
|
|
constexpr static data_type CMD58 = (CMD_MSB | 58); //!< READ_OCR
|
|
|
|
|
|
|
|
|
|
|
|
constexpr static data_type ACMD13 = (0xC0 + 13); //!< SD_STATUS (SDC)
|
|
|
|
|
|
constexpr static data_type ACMD23 = (0xC0 + 23); //!< SET_WR_BLK_ERASE_COUNT (SDC)
|
|
|
|
|
|
constexpr static data_type ACMD41 = (0xC0 + 41); //!< SEND_OP_COND (SDC)
|
|
|
|
|
|
|
|
|
|
|
|
constexpr static data_type R1_READY_STATE = 0x00; //!< status for card in the ready state
|
|
|
|
|
|
constexpr static data_type R1_IDLE_STATE = 0x01; //!< status for card in the idle state
|
|
|
|
|
|
constexpr static data_type R1_ILLEGAL_COMMAND = 0x04; //!< status bit for illegal command
|
|
|
|
|
|
constexpr static data_type DATA_START_BLOCK = 0xFE; //!< start data token for read or write single block
|
|
|
|
|
|
constexpr static data_type STOP_TRAN_TOKEN = 0xFD; //!< stop token for write multiple blocks
|
|
|
|
|
|
constexpr static data_type WRITE_MULTIPLE_TOKEN= 0xFC; //!< start data token for write multiple blocks
|
|
|
|
|
|
constexpr static data_type DATA_RES_MASK = 0x1F; //!< mask for data response tokens after a write block operation
|
|
|
|
|
|
constexpr static data_type DATA_RES_ACCEPTED = 0x05; //!< write data accepted token
|
|
|
|
|
|
|
|
|
|
|
|
// MMC card type flags (MMC_GET_TYPE)
|
|
|
|
|
|
//! \note
|
|
|
|
|
|
//! These types are compatible with FatFS types
|
|
|
|
|
|
constexpr static data_type CT_NONE = 0x00;
|
|
|
|
|
|
constexpr static data_type CT_MMC = 0x01; //!< MMC ver 3
|
|
|
|
|
|
constexpr static data_type CT_SD1 = 0x02; //!< SD ver 1
|
|
|
|
|
|
constexpr static data_type CT_SD2 = 0x04; //!< SD ver 2
|
|
|
|
|
|
constexpr static data_type CT_SDC = (CT_SD1|CT_SD2); //!< SD
|
|
|
|
|
|
constexpr static data_type CT_BLOCK = 0x08; //!< Block addressing
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
|
|
enum status_t : uint8_t {
|
|
|
|
|
|
ST_OK =0,
|
|
|
|
|
|
ST_NOINIT = 1,
|
|
|
|
|
|
ST_NODISK = 2,
|
|
|
|
|
|
ST_WRPROTECT = 3,
|
|
|
|
|
|
ST_ERROR = 4
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
enum ioctl_cmd {
|
|
|
|
|
|
// Fatfs compatibility
|
|
|
|
|
|
IOCTL_SYNC =0, //!< Flush disk cache (for write functions)
|
|
|
|
|
|
IOCTL_GET_SECTOR_COUNT =1, //!< Get media size (for only f_mkfs())
|
|
|
|
|
|
IOCTL_GET_SECTOR_SIZE =2, //!< Get sector size (for multiple sector size (_MAX_SS >= 1024))
|
|
|
|
|
|
IOCTL_GET_BLOCK_SIZE =3, //!< Get erase block size (for only f_mkfs())
|
|
|
|
|
|
IOCTL_ERASE_SECTOR =4, //!< Force erased a block of sectors (for only _USE_ERASE)
|
|
|
|
|
|
|
|
|
|
|
|
// Generics
|
|
|
|
|
|
IOCTL_POWER =5, //!< Get/Set power status
|
|
|
|
|
|
IOCTL_LOCK =6, //!< Lock/Unlock media removal
|
|
|
|
|
|
IOCTL_EJECT =7, //!< Eject media
|
|
|
|
|
|
IOCTL_FORMAT =8, //!< Create physical format on the media
|
|
|
|
|
|
|
|
|
|
|
|
// SD/MMC specific
|
|
|
|
|
|
IOCTL_MMC_GET_TYPE =10, //!< Get card type
|
|
|
|
|
|
IOCTL_MMC_GET_CSD =11, //!< Get CSD
|
|
|
|
|
|
IOCTL_MMC_GET_CID =12, //!< Get CID
|
|
|
|
|
|
IOCTL_MMC_GET_OCR =13, //!< Get OCR
|
|
|
|
|
|
IOCTL_MMC_GET_SDSTAT =14, //!< Get SD status
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
sd_card() :
|
|
|
|
|
|
status{ST_NOINIT} { }
|
|
|
|
|
|
|
|
|
|
|
|
sd_card(const sd_card&) = delete;
|
|
|
|
|
|
sd_card& operator=(const sd_card&) = delete;
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Calculate the maximum data transfer rate per one data line
|
|
|
|
|
|
* from the CSD.
|
|
|
|
|
|
* TRAN_SPEED is the CSD[103..96]
|
|
|
|
|
|
*
|
|
|
|
|
|
* TRAN_SPEED bit code
|
|
|
|
|
|
* ---------------------------------------------------
|
|
|
|
|
|
* 2:0 | transfer rate unit
|
|
|
|
|
|
* | 0=100kbit/s, 1=1Mbit/s, 2=10Mbit/s,
|
|
|
|
|
|
* | 3=100Mbit/s, 4... 7=reserved
|
|
|
|
|
|
* ---------------------------------------------------
|
|
|
|
|
|
* 6:3 | time value
|
|
|
|
|
|
* --------------------------------------------------
|
|
|
|
|
|
* 7 | Reserved
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param csd Pointer to CSD array 128bit.
|
|
|
|
|
|
* \return The maximum spi baud rate.
|
|
|
|
|
|
*/
|
|
|
|
|
|
uint32_t csd2bautrate (data_type *csd) {
|
|
|
|
|
|
data_type brmul = 0;
|
|
|
|
|
|
uint32_t br = 100000; // 100Kbit
|
|
|
|
|
|
|
|
|
|
|
|
// Mask [2..0] bits of TRAN_SPEED
|
|
|
|
|
|
brmul = csd[3] & 0x07;
|
|
|
|
|
|
while (brmul--)
|
|
|
|
|
|
br *= 10;
|
|
|
|
|
|
return br;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void delay (clock_t t) {
|
|
|
|
|
|
clock_t mark = impl().clock_impl();
|
|
|
|
|
|
while (impl().clock_impl() - mark < t)
|
|
|
|
|
|
;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Check if SD Card is present.
|
|
|
|
|
|
* \return The sd card present status
|
|
|
|
|
|
* \arg false Is NOT present
|
|
|
|
|
|
* \arg true Is present.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool is_present () {
|
|
|
|
|
|
return impl().CD_impl();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Check if SD Card is write protected.
|
|
|
|
|
|
* \return The write protect status
|
|
|
|
|
|
* \arg false Is NOT write protected
|
|
|
|
|
|
* \arg true Is write protected.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool is_write_protected () {
|
|
|
|
|
|
return impl().WP_impl();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Powers up or down the SD Card.
|
|
|
|
|
|
* \param on On/Off flag.
|
|
|
|
|
|
* \return The new power state state
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool power (bool on) {
|
|
|
|
|
|
impl().PWR_impl(on);
|
|
|
|
|
|
return pwr_flag = on;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Check if SD Card is powered.
|
|
|
|
|
|
* \return The power status
|
|
|
|
|
|
* \arg false The drive is not powered
|
|
|
|
|
|
* \arg true The drive is powered
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool power () { return pwr_flag; }
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Chip-select control
|
|
|
|
|
|
* \param state True to Select, false to de-select.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void select() {
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
impl().CS_impl(false);
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief De-select SD Card and release SPI bus
|
|
|
|
|
|
* \return None.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void release () {
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
impl().CS_impl(true);
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Transmit a byte to SD/MMC via SPI
|
|
|
|
|
|
* \param data The data to send to the SPI bus.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void spi_tx (data_type data) {
|
|
|
|
|
|
impl().SPI_rw_impl (data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Receive a byte to SD/MMC via SPI.
|
|
|
|
|
|
* \return The data received from SPI bus.
|
|
|
|
|
|
*/
|
|
|
|
|
|
data_type spi_rx () {
|
|
|
|
|
|
return impl().SPI_rw_impl (0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief Keep calling spi_rx until response \c resp.
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param resp the response we wait for
|
|
|
|
|
|
* \param timeout timeout for the operation
|
|
|
|
|
|
* \return
|
|
|
|
|
|
* \arg true Ready
|
|
|
|
|
|
* \arg false NOT ready.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool spi_wait_for (data_type resp, clock_t timeout) {
|
|
|
|
|
|
data_type res;
|
|
|
|
|
|
clock_t mark = impl().clock_impl();
|
|
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
|
res = spi_rx ();
|
|
|
|
|
|
while ((res != resp) && ((impl().clock_impl() - mark) < timeout));
|
|
|
|
|
|
|
|
|
|
|
|
return (res == resp);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool activate (bool state) {
|
|
|
|
|
|
if (state) {
|
|
|
|
|
|
power(true); // power on with delay
|
|
|
|
|
|
delay (SD_PowerTimeout);
|
|
|
|
|
|
impl().CS_impl(1); // make sure CS is high
|
|
|
|
|
|
for (size_t i=0 ; i<10 ; ++i) // 80 dummy clocks with DI high
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
status = ST_NOINIT; // mark the status
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
power(false); // power off
|
|
|
|
|
|
impl().CS_impl(0); // keep CS pin voltage low
|
|
|
|
|
|
status = ST_NOINIT; // mark the status
|
|
|
|
|
|
}
|
|
|
|
|
|
return state;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Receive a data packet from MMC/SD
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param buffer Pointer to data buffer to store received data
|
|
|
|
|
|
* \param n Byte count (must be multiple of 4)
|
|
|
|
|
|
* \return The operation status
|
|
|
|
|
|
* \arg false Fail
|
|
|
|
|
|
* \arg true Success.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool rx_datablock (data_type* buffer, size_t n) {
|
|
|
|
|
|
|
|
|
|
|
|
if (! spi_wait_for(DATA_START_BLOCK, SD_RxTimeout))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* Receive the data block into buffer and make sure
|
|
|
|
|
|
* we receive multiples of 4
|
|
|
|
|
|
*/
|
|
|
|
|
|
n += (n%4) ? 4-(n%4):0;
|
|
|
|
|
|
for ( ; n>0 ; --n)
|
|
|
|
|
|
*buffer++ = spi_rx ();
|
|
|
|
|
|
|
|
|
|
|
|
spi_rx (); // Discard CRC
|
|
|
|
|
|
spi_rx ();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Transmit a data block (512bytes) to MMC/SD
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param buffer Pointer to 512 byte data block to be transmitted
|
|
|
|
|
|
* \param token Data/Stop token
|
|
|
|
|
|
* \return The operation status
|
|
|
|
|
|
* \arg false Fail
|
|
|
|
|
|
* \arg true Success.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool tx_datablock (const data_type* buffer, data_type token) {
|
|
|
|
|
|
if (!spi_wait_for(0xFF, SD_WaitTimeout))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
spi_tx(token); // transmit data token
|
|
|
|
|
|
if (token != STOP_TRAN_TOKEN) {
|
|
|
|
|
|
// if its data token, transmit the 512 byte block
|
|
|
|
|
|
size_t cnt = 512;
|
|
|
|
|
|
do
|
|
|
|
|
|
spi_tx(*buffer++);
|
|
|
|
|
|
while (--cnt);
|
|
|
|
|
|
spi_tx(0xFF); // CRC (Dummy)
|
|
|
|
|
|
spi_tx(0xFF);
|
|
|
|
|
|
data_type r = spi_rx(); // Receive data response
|
|
|
|
|
|
if ((r & DATA_RES_MASK) != DATA_RES_ACCEPTED) // If not accepted, return with error
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Send a command packet to SD/MMC and return the response
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param cmd Command byte
|
|
|
|
|
|
* \param arg Argument
|
|
|
|
|
|
* \return The response as operation status
|
|
|
|
|
|
*/
|
|
|
|
|
|
data_type command (data_type cmd, uint32_t arg) {
|
|
|
|
|
|
data_type n, r;
|
|
|
|
|
|
|
|
|
|
|
|
if (cmd & 0x80) {
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* SD_ACMD<n> is the command sequence of CMD55-SD_CMD<n>
|
|
|
|
|
|
*/
|
|
|
|
|
|
cmd &= 0x7F;
|
|
|
|
|
|
r = command (CMD55, 0);
|
|
|
|
|
|
if (r > 1)
|
|
|
|
|
|
return r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Send command packet
|
|
|
|
|
|
spi_tx (cmd); // Start + Command index
|
|
|
|
|
|
spi_tx ((data_type)(arg>>24)); // Argument [31..24]
|
|
|
|
|
|
spi_tx ((data_type)(arg>>16)); // Argument [23..16]
|
|
|
|
|
|
spi_tx ((data_type)(arg>>8)); // Argument [15..8]
|
|
|
|
|
|
spi_tx ((data_type)arg); // Argument [7..0]
|
|
|
|
|
|
|
|
|
|
|
|
if (cmd == CMD0) n = 0x94; // Valid CRC for CMD0(0)
|
|
|
|
|
|
else if (cmd == CMD8) n = 0x86; // Valid CRC for CMD8(0x1AA)
|
|
|
|
|
|
else n = 0x00;
|
|
|
|
|
|
spi_tx (n | CMD_CRC_LSB);
|
|
|
|
|
|
|
|
|
|
|
|
// Receive command response
|
|
|
|
|
|
if (cmd == CMD12)
|
|
|
|
|
|
spi_rx (); // Skip a stuff byte when stop reading
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for a valid response in timeout of 0xFF attempts
|
|
|
|
|
|
size_t nn = 0xFF;
|
|
|
|
|
|
do
|
|
|
|
|
|
r = spi_rx ();
|
|
|
|
|
|
while ((r & 0x80) && --nn);
|
|
|
|
|
|
|
|
|
|
|
|
return r; // Return with the response value
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool do_command_until (data_type done, data_type cmd, uint32_t arg, clock_t timeout) {
|
|
|
|
|
|
clock_t mark = impl().clock_impl();
|
|
|
|
|
|
data_type ret;
|
|
|
|
|
|
do
|
|
|
|
|
|
ret = command (cmd, arg);
|
|
|
|
|
|
while (ret != done && impl().clock_impl() - mark < timeout);
|
|
|
|
|
|
return ret == done;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
bool get_CSD (data_type* csd) {
|
|
|
|
|
|
bool ret = false;
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
if (command (CMD9, 0) == R1_READY_STATE && rx_datablock (csd, 16)) // READ_CSD
|
|
|
|
|
|
ret = true;
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool get_CID (data_type* cid) {
|
|
|
|
|
|
bool ret = false;
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
if (command (CMD10, 0) == R1_READY_STATE && rx_datablock (cid, 16)) // READ_CID
|
|
|
|
|
|
ret = true;
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool get_OCR (data_type* ocr) {
|
|
|
|
|
|
bool ret = false;
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
// Receive OCR as an R3 response (4 bytes)
|
|
|
|
|
|
if (command (CMD58, 0) == 0) { // READ_OCR
|
|
|
|
|
|
for (size_t n = 0; n < 4; ++n)
|
|
|
|
|
|
*ocr++ = spi_rx ();
|
|
|
|
|
|
ret = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// bool get_SDSTAT (data_type* sdstat) {
|
|
|
|
|
|
// bool ret = false;
|
|
|
|
|
|
// select(); // select card's CS
|
|
|
|
|
|
// if (command (ACMD13, 0) == 0) { // SD_STATUS
|
|
|
|
|
|
// spi_rx ();
|
|
|
|
|
|
// if (rx_datablock (sdstat, 64))
|
|
|
|
|
|
// ret = true;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// release(); // release card's CS
|
|
|
|
|
|
// return ret;
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
bool sync () {
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
bool st = spi_wait_for(0xFF, SD_WaitTimeout); // flush
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return st;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t sector_count() {
|
|
|
|
|
|
size_t ret =0;
|
|
|
|
|
|
data_type csd[16];
|
|
|
|
|
|
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
if (get_CSD(csd)) {
|
|
|
|
|
|
if ((csd[0] >> 6) == 1) {
|
|
|
|
|
|
// SDC version 2.00
|
|
|
|
|
|
size_t csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
|
|
|
|
|
|
ret = csize << 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// SDC version 1.XX or MMC
|
|
|
|
|
|
uint8_t n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
|
|
|
|
|
|
size_t csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
|
|
|
|
|
|
ret = csize << (n - 9);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t sector_size() const { return 512; }
|
|
|
|
|
|
|
|
|
|
|
|
size_t block_size() {
|
|
|
|
|
|
size_t ret =0;
|
|
|
|
|
|
data_type csd[16];
|
|
|
|
|
|
|
|
|
|
|
|
select(); // select card's CS
|
|
|
|
|
|
if (card_type & CT_SD2) {
|
|
|
|
|
|
// SDC version 2.00
|
|
|
|
|
|
if (command (ACMD13, 0) == R1_READY_STATE) {
|
|
|
|
|
|
spi_rx (); // Read SD status
|
|
|
|
|
|
if (rx_datablock (csd, 16)) { // Read partial block
|
|
|
|
|
|
for (size_t n = 64 - 16; n; n--) // Purge trailing data
|
|
|
|
|
|
spi_rx ();
|
|
|
|
|
|
ret = 16UL << (csd[10] >> 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// SDC version 1.XX or MMC
|
|
|
|
|
|
if (get_CSD(csd)) { // Read CSD
|
|
|
|
|
|
if (card_type & CT_SD1) // SDC version 1.XX
|
|
|
|
|
|
ret = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
|
|
|
|
|
|
else // MMC
|
|
|
|
|
|
ret = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
release(); // release card's CS
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* De-Initialize SD Drive.
|
|
|
|
|
|
* \return None
|
|
|
|
|
|
*/
|
|
|
|
|
|
void deinit () {
|
|
|
|
|
|
card_type = data_type{};
|
|
|
|
|
|
status = status_t{};
|
|
|
|
|
|
activate (0); // finally power off the card
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Initialize SD Drive.
|
|
|
|
|
|
*
|
|
|
|
|
|
* \return The status of the operation
|
|
|
|
|
|
* \arg false On error.
|
|
|
|
|
|
* \arg true On success.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool init () {
|
|
|
|
|
|
uint32_t clk;
|
|
|
|
|
|
data_type ocr[4], csd[16];
|
|
|
|
|
|
|
|
|
|
|
|
clk = 400000; // Start at lower clk
|
|
|
|
|
|
impl().SPI_set_clk_impl(clk);
|
|
|
|
|
|
|
|
|
|
|
|
activate (0); // Initially power off the card
|
|
|
|
|
|
if (!is_present()) { // check for presence
|
|
|
|
|
|
status = ST_NODISK;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
activate (1); // activate and wait for PowerTimeout delay
|
|
|
|
|
|
|
|
|
|
|
|
select(); // select card
|
|
|
|
|
|
data_type type = CT_NONE;
|
|
|
|
|
|
|
|
|
|
|
|
if (command (CMD0, 0) == R1_IDLE_STATE) { // Command to enter Idle state
|
|
|
|
|
|
if (command (CMD8, 0x1AA) == 1) { // check SD version
|
|
|
|
|
|
// SDHC
|
|
|
|
|
|
for (size_t n=0 ; n<4 ; ++n) // Get trailing return value of R7 response
|
|
|
|
|
|
ocr[n] = spi_rx ();
|
|
|
|
|
|
if (ocr[2] == 0x01 && ocr[3] == 0xAA) {
|
|
|
|
|
|
// Wait for leaving idle state (ACMD41 with HCS bit)
|
|
|
|
|
|
bool st = do_command_until(R1_READY_STATE, ACMD41, 1UL << 30, SD_InitTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
if (st && get_OCR(ocr))
|
|
|
|
|
|
type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data_type cmd;
|
|
|
|
|
|
// SDSC or MMC
|
|
|
|
|
|
if (command (ACMD41, 0) <= 1) { // SDSC
|
|
|
|
|
|
type = CT_SD1; cmd = ACMD41;
|
|
|
|
|
|
} else { // MMC
|
|
|
|
|
|
type = CT_MMC; cmd = CMD1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Wait for leaving idle state (ACMD41 || CMD1)
|
|
|
|
|
|
bool st = do_command_until(R1_READY_STATE, cmd, 0, SD_InitTimeout);
|
|
|
|
|
|
// On failure, set R/W block length to 512 (For FAT compatibility)
|
|
|
|
|
|
if (!st || command (CMD16, 512) != R1_READY_STATE)
|
|
|
|
|
|
type = CT_NONE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
card_type = type;
|
|
|
|
|
|
release (); // Initialization ended
|
|
|
|
|
|
|
|
|
|
|
|
if (type != CT_NONE) {
|
|
|
|
|
|
// Success
|
|
|
|
|
|
get_CSD(csd);
|
|
|
|
|
|
clk = csd2bautrate(csd);
|
|
|
|
|
|
impl().SPI_set_clk_impl(clk);
|
|
|
|
|
|
status = ST_OK;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
activate(0);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
status_t get_status () const { return status; }
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Read Sector(s)
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param sector Start sector number (LBA)
|
|
|
|
|
|
* \param buf Pointer to the data buffer to store read data
|
|
|
|
|
|
* \param count Sector (512 bytes) count (1..255)
|
|
|
|
|
|
* \return The status of the operation
|
|
|
|
|
|
* \arg false On error.
|
|
|
|
|
|
* \arg true On success.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool read (size_t sector, data_type *buf, size_t count) {
|
|
|
|
|
|
|
|
|
|
|
|
if (status != ST_OK) return false;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(card_type & CT_BLOCK)) // Convert to byte address if needed
|
|
|
|
|
|
sector *= 512;
|
|
|
|
|
|
select();
|
|
|
|
|
|
if (count == 1) { //Single block read
|
|
|
|
|
|
if (command (CMD17, sector) == 0)
|
|
|
|
|
|
if (rx_datablock (buf, 512))
|
|
|
|
|
|
count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else { // Multiple block read
|
|
|
|
|
|
if (command (CMD18, sector) == 0) {
|
|
|
|
|
|
do {
|
|
|
|
|
|
if (!rx_datablock (buf, 512))
|
|
|
|
|
|
break;
|
|
|
|
|
|
buf += 512;
|
|
|
|
|
|
} while (--count);
|
|
|
|
|
|
command (CMD12, 0); // STOP_TRANSMISSION
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
release ();
|
|
|
|
|
|
return (count == 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
|
* \brief
|
|
|
|
|
|
* Write Sector(s)
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param sector Start sector number (LBA)
|
|
|
|
|
|
* \param buf Pointer to the data to be written
|
|
|
|
|
|
* \param count Sector(512 bytes) count (1..255)
|
|
|
|
|
|
* \return The status of the operation
|
|
|
|
|
|
* \arg false On error.
|
|
|
|
|
|
* \arg true On success.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool write (size_t sector, const data_type *buf, size_t count) {
|
|
|
|
|
|
|
|
|
|
|
|
if (!count) return false;
|
|
|
|
|
|
if (status != ST_OK) return false;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(card_type & CT_BLOCK)) // Convert to byte address if needed
|
|
|
|
|
|
sector *= 512;
|
|
|
|
|
|
|
|
|
|
|
|
select();
|
|
|
|
|
|
if (count == 1) { // Single block write
|
|
|
|
|
|
if ((command (CMD24, sector) == 0) && tx_datablock (buf, 0xFE))
|
|
|
|
|
|
count = 0;
|
|
|
|
|
|
} else { // Multiple block write
|
|
|
|
|
|
if (card_type & CT_SDC)
|
|
|
|
|
|
command (ACMD23, count);
|
|
|
|
|
|
if (command (CMD25, sector) == 0) {
|
|
|
|
|
|
do {
|
|
|
|
|
|
if (!tx_datablock (buf, WRITE_MULTIPLE_TOKEN))
|
|
|
|
|
|
break;
|
|
|
|
|
|
buf += 512;
|
|
|
|
|
|
} while (--count);
|
|
|
|
|
|
if (!tx_datablock (0, STOP_TRAN_TOKEN)) // STOP token
|
|
|
|
|
|
count = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
release ();
|
|
|
|
|
|
return (count == 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ioctl (ioctl_cmd cmd, void* buffer) {
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
|
// SD/MMC specific
|
|
|
|
|
|
case IOCTL_MMC_GET_TYPE: *(data_type*)buffer = card_type; return true;
|
|
|
|
|
|
case IOCTL_MMC_GET_CSD: return get_CSD ((data_type*)buffer);
|
|
|
|
|
|
case IOCTL_MMC_GET_CID: return get_CID ((data_type*)buffer);
|
|
|
|
|
|
case IOCTL_MMC_GET_OCR: return get_OCR ((data_type*)buffer);
|
|
|
|
|
|
case IOCTL_MMC_GET_SDSTAT: return false;
|
|
|
|
|
|
|
|
|
|
|
|
// Generic
|
|
|
|
|
|
case IOCTL_POWER:
|
|
|
|
|
|
switch (*(data_type*)buffer) {
|
|
|
|
|
|
case 0: *((data_type*)buffer+1) = (data_type)activate(0); return true;
|
|
|
|
|
|
case 1: *((data_type*)buffer+1) = (data_type)activate(0); return true;
|
|
|
|
|
|
case 2: *((data_type*)buffer+1) = (data_type)power(); return true;
|
|
|
|
|
|
default: return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case IOCTL_LOCK:
|
|
|
|
|
|
case IOCTL_EJECT:
|
|
|
|
|
|
case IOCTL_FORMAT:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// FatFS compatibility
|
|
|
|
|
|
case IOCTL_SYNC: return sync();
|
|
|
|
|
|
case IOCTL_GET_SECTOR_COUNT:return (*(size_t*) buffer = sector_count() != 0);
|
|
|
|
|
|
case IOCTL_GET_SECTOR_SIZE: return (*(size_t*) buffer = sector_size() != 0);
|
|
|
|
|
|
case IOCTL_GET_BLOCK_SIZE: return (*(size_t*) buffer = block_size() != 0);
|
|
|
|
|
|
case IOCTL_ERASE_SECTOR: return false;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
data_type card_type{};
|
|
|
|
|
|
status_t status{};
|
|
|
|
|
|
bool pwr_flag{};
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace tbx
|
|
|
|
|
|
|
|
|
|
|
|
#endif /* TBX_DRV_SD_SPI_H_ */
|