mmc_spi.c
字号:
mmc_spi_send_command(cyg_mmc_spi_disk_info_t* disk, cyg_uint32 command, cyg_uint32 arg)
{
cyg_uint32 reply;
reply = mmc_spi_send_command_start(disk, command, arg);
mmc_spi_end_command(disk);
return reply;
}
// The card will return a data block when reading a disk block, or
// for certain other commands like reading card registers. Each
// data block consists of:
// 1) some number of padding bytes 0xff while the card is still
// processing the command and preparing the data
// 2) a data token byte, usually 0xFE for success
// 3) n bytes of data
// 4) two bytes of crc, which can be ignored.
//
// The data token byte is the only indication of success or failure,
// so that gets returned.
//
// When mounting certain types of card an extra delay may be needed
// before reading the first data block. This is handled by the
// extra_delay argument.
static cyg_uint32
mmc_spi_read_data(cyg_mmc_spi_disk_info_t* disk, cyg_uint8* buf, cyg_uint32 count, cyg_bool extra_delay)
{
cyg_spi_device* dev = disk->mmc_spi_dev;
int i;
cyg_uint8 response[2];
cyg_uint32 retries;
if (extra_delay) {
retries = MMC_SPI_READ_DATA_TOKEN_RETRIES * 100;
} else {
retries = MMC_SPI_READ_DATA_TOKEN_RETRIES;
}
response[0] = 0x00FF;
for (i = 0; (i < retries) && (0x00FF == response[0]); i++) {
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, response, 0);
}
if (MMC_DATA_TOKEN_SUCCESS != response[0]) {
DEBUG1("mmc_spi_read_data(): got error response %02x after %d iterations\n", response[0], i);
return response[0];
}
// Now for the actual data. There is no way of detecting a failure from
// this point on.
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, count, mmc_spi_ff_data, buf, 0);
// And the CRC, which can be ignored
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 2, mmc_spi_ff_data, response, 0);
DEBUG2("mmc_spi_read_data(): got data and CRC %02x %02x\n", response[0], response[1]);
return MMC_DATA_TOKEN_SUCCESS;
}
// Read one of the card registers, e.g. CSD or CID
static Cyg_ErrNo
mmc_spi_read_register(cyg_mmc_spi_disk_info_t* disk, cyg_uint32 command, cyg_uint8* buf, cyg_uint32 count)
{
cyg_uint32 reply;
reply = mmc_spi_send_command_start(disk, command, 0);
if (MMC_REPLY_SUCCESS != reply) {
DEBUG1("mmc_spi_read_register(): unexpected response to command %02x, reply code %02x\n", command, reply);
mmc_spi_end_command(disk);
return (0x00FF == reply) ? -ENODEV : -EIO;
}
reply = mmc_spi_read_data(disk, buf, count, false);
mmc_spi_end_command(disk);
if (MMC_DATA_TOKEN_SUCCESS != reply) {
DEBUG1("mmc_spi_read_register(): unexpected response to command %02x, expected 0x00FE data token, got %02x\n", command, reply);
return -EIO;
}
return ENOERR;
}
// Reading a disk block is just a combination of the above utilities.
// This code is also responsible for translating error codes, since
// higher-level code does not get to see the initial response vs. the
// data token byte.
static Cyg_ErrNo
mmc_spi_read_disk_block(cyg_mmc_spi_disk_info_t* disk, cyg_uint8* buf, cyg_uint32 block, cyg_bool extra_delay)
{
cyg_uint32 reply;
// First the command itself.
DEBUG2("mmc_spi_read_disk_block(%d): sending command\n", block);
reply = mmc_spi_send_command_start(disk, MMC_REQUEST_READ_SINGLE_BLOCK, block * MMC_SPI_BLOCK_SIZE);
if (MMC_REPLY_SUCCESS != reply) {
DEBUG1("mmc_spi_read_disk_block(%d): unexpected response to READ_SINGLE_BLOCK command, code %02x\n", block, reply);
mmc_spi_end_command(disk);
// A byte 0xFF indicates the card has been removed.
if (0x00FF == reply) {
return -ENODEV;
}
// Parameter or address error should not occur, higher-level
// code should have checked the block to ensure that it is
// in range.
if (0 != (reply & (MMC_REPLY_PARAMETER_ERROR | MMC_REPLY_ADDRESS_ERROR))) {
return -EINVAL;
}
// The disk should not be in idle state or in an erase sequence. The
// command is definitely legal and CRCs should be disabled. So everything
// else is an I/O error.
return -EIO;
}
// Now read back the data block. That code can be shared with other read
// operations, e.g. for retrieving registers.
DEBUG2("mmc_spi_read_disk_block(%d): reading data token/data/crc\n", block);
reply = mmc_spi_read_data(disk, buf, MMC_SPI_BLOCK_SIZE, extra_delay);
mmc_spi_end_command(disk);
if (MMC_DATA_TOKEN_SUCCESS != reply) {
DEBUG1("mmc_spi_read_disk_block(%d): failed to retrieve data, error token %02x\n", block, reply);
// Possibilities are password-locked, range error, ECC failure
// if the raw data is corrupt, CC error for an internal card
// error, or some other error. A byte 0xFF indicates the card
// has been removed.
if (0x00FF == reply) {
return -ENODEV;
} else if (0 != (MMC_DATA_ERROR_TOKEN_CARD_LOCKED & reply)) {
// This should have been caught by a mount operation.
return -EPERM;
} else if (0 != (MMC_DATA_ERROR_TOKEN_OUT_OF_RANGE & reply)) {
return -EINVAL;
} else {
return -EIO;
}
}
return ENOERR;
}
// Writing a block involves a bit more work. Some of this could be
// moved into a utility routine if necessary, shared with code for
// e.g. updating the CSD register, but for now that other functionality
// is not needed.
static Cyg_ErrNo
mmc_spi_write_disk_block(cyg_mmc_spi_disk_info_t* disk, const cyg_uint8* buf, cyg_uint32 block)
{
cyg_spi_device* dev = disk->mmc_spi_dev;
cyg_uint32 reply;
cyg_uint8 extra[4];
int i;
// First, send the command itself and get the initial response
DEBUG2("mmc_spi_write_disk_block(), sending command\n");
reply = mmc_spi_send_command_start(disk, MMC_REQUEST_WRITE_BLOCK, block * MMC_SPI_BLOCK_SIZE);
if (MMC_REPLY_SUCCESS != reply) {
DEBUG1("mmc_spi_write_disk_block(): unexpected response to WRITE_BLOCK command, code %02x\n", reply);
mmc_spi_end_command(disk);
if (0x00FF == reply) {
return -ENODEV;
}
// Parameter or address error should not occur, higher-level
// code should have checked the block to ensure that it is
// in range.
if (0 != (reply & (MMC_REPLY_PARAMETER_ERROR | MMC_REPLY_ADDRESS_ERROR))) {
return -EINVAL;
}
// The disk should not be in idle state or in an erase sequence. The
// command is definitely legal and CRCs should be disabled. So everything
// else is an I/O error.
return -EIO;
}
// The card is now expecting a data block. This consists of a single byte
// 0x00FE, then the data itself, and a dummy CRC. The reply from the card
// does not contain any useful information.
DEBUG2("mmc_spi_write_disk_block(): sending data token/data/crc\n");
extra[0] = 0x00FE;
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, extra, (cyg_uint8*)0, 0);
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, MMC_SPI_BLOCK_SIZE, buf, (cyg_uint8*)0, 0);
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 2, mmc_spi_ff_data, (cyg_uint8*)0, 0);
// The card should respond immediately with a data response token.
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, extra, 0);
DEBUG2("mmc_spi_write_disk_block(): got data response token %02x\n", extra[0]);
// The bottom five bits contain the response. 00101 indicates success,
// anything else is a CRC error. Everything else will have been checked
// before the data got transferred.
if (0x05 != (extra[0] & 0x1F)) {
DEBUG1("mmc_spi_write_disk_block(): invalid data response token %02x\n", extra[0]);
mmc_spi_end_command(disk);
if (0x00FF == extra[0]) {
return -ENODEV;
}
return -EIO;
}
#ifdef MMC_SPI_BACKGROUND_WRITES
// Mark the card as writing. The next operation will poll for completion.
disk->mmc_writing = true;
#else
// The card is now busy doing the write and will output a stream of 0's
// while busy. The timeout should really be calculated using the CSD
// register settings.
//
// It should be legal to drop the chip select here, i.e. to end
// the current transaction and start a new one for each poll
// operation. That would allow other SPI devices to be accessed.
// However it appears that this does not work with all MMC cards.
extra[0] = 0x00;
for (i = 0; (i < MMC_SPI_WRITE_BUSY_RETRIES) && (0x00 == extra[0]); i++) {
cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, extra, 0);
DEBUG2("mmc_spi_write_disk_block(), polling for ! busy, got response %02x\n", extra[0]);
}
#endif
// Assume that the loop did in fact terminate.
mmc_spi_end_command(disk);
return ENOERR;
}
// MMC sockets will default to a slow clockrate. During a successful mount
// the SPI device settings will be changed to the fastest supported by the
// card, as per the CSD register. This will need to be undone during an
// unmount, or if the final stages of a mount are unsuccessful.
static void
mmc_spi_restore_baud(cyg_mmc_spi_disk_info_t* disk)
{
cyg_uint32 len = sizeof(cyg_uint32);
(void) cyg_spi_set_config(disk->mmc_spi_dev, CYG_IO_SET_CONFIG_SPI_CLOCKRATE, (void*) &(disk->mmc_saved_baudrate), &len);
}
// check_for_disk() tries to communicate with an MMC card that is not
// currently mounted. It performs the appropriate initialization so
// that read and write operations are possible, checks the disk format,
// distinguishes between read-only and read-write cards, calculates the
// card size, stores the unique id, etc.
//
// The main error conditions are ENODEV (no card), EIO (card not
// responding sensibly to requests), ENOTDIR (wrong format), or EPERM
// (card is password-locked).
static Cyg_ErrNo
mmc_spi_check_for_disk(cyg_mmc_spi_disk_info_t* disk)
{
cyg_spi_device* dev = disk->mmc_spi_dev;
int i;
cyg_uint32 reply;
Cyg_ErrNo code;
mmc_csd_register csd;
#ifdef MMC_SPI_BACKGROUND_WRITES
// If we have unmounted a disk and are remounting it, assume that
// any writes have completed.
disk->mmc_writing = false;
#endif
reply = 0x00ff;
for (i = 0; (i < MMC_SPI_GO_IDLE_RETRIES) && (0x01 != reply); i++) {
// Allow platform HALs to provide additional initialization,
// if the hardware needs it.
#ifdef HAL_MMC_SPI_INIT
HAL_MMC_SPI_INIT(dev, reply);
if (! reply) {
return -ENODEV;
}
#endif
// MMC cards generic initialization. The card may have just
// been plugged in so there is no guarantee that any previous
// init() calls or other traffic will have affected this card.
mmc_spi_send_init(disk);
// Now set the card to idle state. This involves the GO_IDLE_STATE
// command which will be accepted irrespective of whether the card is
// currently in MMC or SPI mode, and will leave the card in SPI mode.
reply = mmc_spi_send_command(disk, MMC_REQUEST_GO_IDLE_STATE, 0);
// The card should reply with 0x01. FF suggests that there is
// no card. Any other response indicates some synchronization
// problem. For example the card might still be responding to
// some request from a previous session which aborted at an
// inconvenient moment. Some dummy traffic is generated in the
// hope that this gets things back in sync.
if (0x01 != reply) {
DEBUG1("mmc_spi_check_for_disk(): loop %d, card did not enter idle state, code %02x\n", i, reply);
if (0x0ff != reply) {
cyg_spi_transfer(dev, cyg_mmc_spi_polled, 128, mmc_spi_ff_data, (cyg_uint8*) 0);
}
}
}
if (0x0ff == reply) {
DEBUG1("mmc_spi_check_for_disk(): unable to get a response from the MMC card: code %02x\n", reply);
// A working card should be returning some data
return -ENODEV;
}
if (0x01 != reply) {
DEBUG1("mmc_spi_check_for_disk(): card did not enter idle state, code %02x\n", reply);
return -EIO;
}
// Next, wait for the card to initialize. This involves repeatedly
// trying the SEND_OP_COND command until we get a reply that is
// not idle
reply = 0x00ff;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -