// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*
SD Card emulation, SPI interface.
Emulation by R. Belmont
This emulates either an SDHC (SPI_SDCARD) or an SDV2 card (SPI_SDCARDV2). SDHC has a fixed
512 byte block size and the arguments to the read/write commands are block numbers. SDV2
has a variable block size defaulting to 512 and the arguments to the read/write commands
are byte offsets.
The block size set with CMD16 must match the underlying CHD block size if it's not 512.
Adding the native 4-bit-wide SD interface is also possible; this should be broken up into a base
SD Card class with SPI and SD frontends in that case.
Multiple block read/write commands are not supported but would be straightforward to add.
Refrences:
https://www.sdcard.org/downloads/pls/ (Physical Layer Simplified Specification)
http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf
https://embdev.net/attachment/39390/TOSHIBA_SD_Card_Specification.pdf
http://elm-chan.org/docs/mmc/mmc_e.html
*/
#include "emu.h"
#include "spi_sdcard.h"
#include "imagedev/harddriv.h"
#define LOG_GENERAL (1U << 0)
#define LOG_COMMAND (1U << 1)
#define LOG_SPI (1U << 2)
//#define VERBOSE (LOG_COMMAND)
#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"
static constexpr u8 DATA_RESPONSE_OK = 0x05;
static constexpr u8 DATA_RESPONSE_IO_ERROR = 0x0d;
DEFINE_DEVICE_TYPE(SPI_SDCARD, spi_sdcard_sdhc_device, "spi_sdhccard", "SDHC Card (SPI Interface)")
DEFINE_DEVICE_TYPE(SPI_SDCARDV2, spi_sdcard_sdv2_device, "spi_sdv2card", "SDV2 Card (SPI Interface)")
spi_sdcard_device::spi_sdcard_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, type, tag, owner, clock),
write_miso(*this),
m_image(*this, "image"),
m_harddisk(nullptr),
m_in_latch(0), m_out_latch(0), m_cmd_ptr(0), m_state(0), m_out_ptr(0), m_out_count(0), m_ss(0), m_in_bit(0),
m_cur_bit(0), m_write_ptr(0), m_blksize(512), m_bACMD(false)
{
}
spi_sdcard_sdv2_device::spi_sdcard_sdv2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
spi_sdcard_device(mconfig, SPI_SDCARDV2, tag, owner, clock)
{
m_type = SD_TYPE_V2;
}
spi_sdcard_sdhc_device::spi_sdcard_sdhc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
spi_sdcard_device(mconfig, SPI_SDCARD, tag, owner, clock)
{
m_type = SD_TYPE_HC;
}
void spi_sdcard_device::device_start()
{
write_miso.resolve_safe();
save_item(NAME(m_in_latch));
save_item(NAME(m_out_latch));
save_item(NAME(m_cmd_ptr));
save_item(NAME(m_state));
save_item(NAME(m_out_ptr));
save_item(NAME(m_out_count));
save_item(NAME(m_ss));
save_item(NAME(m_in_bit));
save_item(NAME(m_cur_bit));
save_item(NAME(m_write_ptr));
save_item(NAME(m_blksize));
save_item(NAME(m_type));
save_item(NAME(m_cmd));
save_item(NAME(m_data));
save_item(NAME(m_bACMD));
}
void spi_sdcard_device::device_reset()
{
m_harddisk = m_image->get_hard_disk_file();
}
void spi_sdcard_device::device_add_mconfig(machine_config &config)
{
HARDDISK(config, m_image).set_interface("spi_sdcard");
}
void spi_sdcard_device::send_data(int count)
{
m_out_ptr = 0;
m_out_count = count;
}
void spi_sdcard_device::spi_clock_w(int state)
{
// only respond if selected
if (m_ss)
{
// We implmement SPI Mode 3 signalling, in which we latch the data on
// rising clock edges, and shift the data on falling clock edges.
// See http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf for details
// on the 4 SPI signalling modes. SD Cards can work in ether Mode 0 or Mode 3,
// both of which shift on the falling edge and latch on the rising edge but
// have opposite CLK polarity.
if (state)
{
m_in_latch &= ~0x01;
m_in_latch |= m_in_bit;
LOGMASKED(LOG_SPI, "\tsdcard: L %02x (%d) (out %02x)\n", m_in_latch, m_cur_bit, m_out_latch);
m_cur_bit++;
if (m_cur_bit == 8)
{
LOGMASKED(LOG_SPI, "SDCARD: got %02x\n", m_in_latch);
switch (m_state)
{
case SD_STATE_IDLE:
for (int i = 0; i < 5; i++)
{
m_cmd[i] = m_cmd[i + 1];
}
m_cmd[5] = m_in_latch;
if ((((m_cmd[0] & 0xc0) == 0x40) && (m_cmd[5] & 1)) && (m_out_count == 0))
{
do_command();
}
break;
case SD_STATE_WRITE_WAITFE:
if (m_in_latch == 0xfe)
{
m_state = SD_STATE_WRITE_DATA;
m_out_latch = 0xff;
m_write_ptr = 0;
}
break;
case SD_STATE_WRITE_DATA:
m_data[m_write_ptr++] = m_in_latch;
if (m_write_ptr == (m_blksize + 2))
{
u32 blk = (m_cmd[1] << 24) | (m_cmd[2] << 16) | (m_cmd[3] << 8) | m_cmd[4];
if (m_type == SD_TYPE_V2)
{
blk /= m_blksize;
}
LOGMASKED(LOG_GENERAL, "writing LBA %x, data %02x %02x %02x %02x\n", blk, m_data[0], m_data[1], m_data[2], m_data[3]);
if (hard_disk_write(m_harddisk, blk, &m_data[0]))
{
m_data[0] = DATA_RESPONSE_OK;
}
else
{
m_data[0] = DATA_RESPONSE_IO_ERROR;
}
m_data[1] = 0x01;
m_state = SD_STATE_IDLE;
send_data(2);
}
break;
}
}
}
else
{
m_in_latch <<= 1;
m_out_latch <<= 1;
LOGMASKED(LOG_SPI, "\tsdcard: S %02x %02x (%d)\n", m_in_latch, m_out_latch, m_cur_bit);
if (m_cur_bit == 8)
{
m_cur_bit = 0;
}
if (m_cur_bit == 0)
{
if (m_out_count > 0)
{
m_out_latch = m_data[m_out_ptr++];
LOGMASKED(LOG_SPI, "SDCARD: latching %02x (start of shift)\n", m_out_latch);
m_out_count--;
}
}
write_miso(BIT(m_out_latch, 7) ? ASSERT_LINE : CLEAR_LINE);
}
}
}
void spi_sdcard_device::do_command()
{
LOGMASKED(LOG_COMMAND, "SDCARD: cmd %02d %02x %02x %02x %02x %02x\n", m_cmd[0] & 0x3f, m_cmd[1], m_cmd[2], m_cmd[3], m_cmd[4], m_cmd[5]);
switch (m_cmd[0] & 0x3f)
{
case 0: // CMD0 - GO_IDLE_STATE
if (m_harddisk)
{
m_data[0] = 0x01;
}
else
{
m_data[0] = 0x00;
}
send_data(1);
break;
case 8: // CMD8 - SEND_IF_COND (SD v2 only)
m_data[0] = 0x01;
m_data[1] = 0;
m_data[2] = 0;
m_data[3] = 0;
m_data[4] = 0xaa;
send_data(5);
break;
case 10: // CMD10 - SEND_CID
m_data[0] = 0x01; // initial R1 response
m_data[1] = 0x00; // throwaway byte before data transfer
m_data[2] = 0xfe; // data token
m_data[3] = 'M'; // Manufacturer ID - we'll use M for MAME
m_data[4] = 'M'; // OEM ID - MD for MAMEdev
m_data[5] = 'D';
m_data[6] = 'M'; // Product Name - "MCARD"
m_data[7] = 'C';
m_data[8] = 'A';
m_data[9] = 'R';
m_data[10] = 'D';
m_data[11] = 0x10; // Product Revision in BCD (1.0)
{
u32 uSerial = 0x12345678;
m_data[12] = (uSerial>>24) & 0xff; // PSN - Product Serial Number
m_data[13] = (uSerial>>16) & 0xff;
m_data[14] = (uSerial>>8) & 0xff;
m_data[15] = (uSerial & 0xff);
}
m_data[16] = 0x01; // MDT - Manufacturing Date
m_data[17] = 0x59; // 0x15 9 = 2021, September
m_data[18] = 0x00; // CRC7, bit 0 is always 0
{
util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], 16);
m_data[19] = (crc16 >> 8) & 0xff;
m_data[20] = (crc16 & 0xff);
}
send_data(3 + 16 + 2);
break;
case 16: // CMD16 - SET_BLOCKLEN
m_blksize = (m_cmd[3] << 8) | m_cmd[4];
if (hard_disk_set_block_size(m_harddisk, m_blksize))
{
m_data[0] = 0;
}
else
{
m_data[0] = 0xff; // indicate an error
// if false was returned, it means the hard disk is a CHD file, and we can't resize the
// blocks on CHD files.
logerror("spi_sdcard: Couldn't change block size to %d, wrong CHD file?", m_blksize);
}
send_data(1);
break;
case 17: // CMD17 - READ_SINGLE_BLOCK
if (m_harddisk)
{
m_data[0] = 0x00; // initial R1 response
// data token occurs some time after the R1 response. A2SD expects at least 1
// byte of space between R1 and the data packet.
m_data[2] = 0xfe; // data token
u32 blk = (m_cmd[1] << 24) | (m_cmd[2] << 16) | (m_cmd[3] << 8) | m_cmd[4];
if (m_type == SD_TYPE_V2)
{
blk /= m_blksize;
}
LOGMASKED(LOG_GENERAL, "reading LBA %x\n", blk);
hard_disk_read(m_harddisk, blk, &m_data[3]);
{
util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], m_blksize);
m_data[m_blksize + 3] = (crc16 >> 8) & 0xff;
m_data[m_blksize + 4] = (crc16 & 0xff);
}
send_data(3 + m_blksize + 2);
}
else
{
m_data[0] = 0xff; // show an error
send_data(1);
}
break;
case 24: // CMD24 - WRITE_BLOCK
m_data[0] = 0;
send_data(1);
m_state = SD_STATE_WRITE_WAITFE;
break;
case 41:
if (m_bACMD) // ACMD41 - SD_SEND_OP_COND
{
m_data[0] = 0;
}
else // CMD41 - illegal
{
m_data[0] = 0xff;
}
send_data(1);
break;
case 55: // CMD55 - APP_CMD
m_data[0] = 0x01;
send_data(1);
break;
case 58: // CMD58 - READ_OCR
m_data[0] = 0;
if (m_type == SD_TYPE_HC)
{
m_data[1] = 0x40; // indicate SDHC support
}
else
{
m_data[1] = 0;
}
m_data[2] = 0;
m_data[3] = 0;
m_data[4] = 0;
send_data(5);
break;
default:
break;
}
// if this is command 55, that's a prefix indicating the next command is an "app command" or "ACMD"
if ((m_cmd[0] & 0x3f) == 55)
{
m_bACMD = true;
}
else
{
m_bACMD = false;
}
}