apple2: support for the AppleIISD card [R. Belmont, Florian Reitz]

This commit is contained in:
arbee 2021-09-07 19:51:34 -04:00
parent 87ecae55e3
commit ea8102906e
9 changed files with 697 additions and 5 deletions

View File

@ -2435,6 +2435,8 @@ if (BUSES["A2BUS"]~=null) then
MAME_DIR .. "src/devices/bus/a2bus/a2sam.h",
MAME_DIR .. "src/devices/bus/a2bus/a2scsi.cpp",
MAME_DIR .. "src/devices/bus/a2bus/a2scsi.h",
MAME_DIR .. "src/devices/bus/a2bus/a2sd.cpp",
MAME_DIR .. "src/devices/bus/a2bus/a2sd.h",
MAME_DIR .. "src/devices/bus/a2bus/a2softcard.cpp",
MAME_DIR .. "src/devices/bus/a2bus/a2softcard.h",
MAME_DIR .. "src/devices/bus/a2bus/a2ssc.cpp",

View File

@ -4183,6 +4183,17 @@ if (MACHINES["SMARTMEDIA"]~=null) then
}
end
---------------------------------------------------
--
--@src/devices/machine/spi_sdcard.h,MACHINES["SPISDCARD"] = true
---------------------------------------------------
if (MACHINES["SPISDCARD"]~=null) then
files {
MAME_DIR .. "src/devices/machine/spi_sdcard.cpp",
MAME_DIR .. "src/devices/machine/spi_sdcard.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/appldriv.h,MACHINES["APPLE_DRIVE"] = true

View File

@ -651,6 +651,7 @@ MACHINES["SMC91C9X"] = true
MACHINES["SEGA_SCU"] = true
MACHINES["SMPC"] = true
--MACHINES["SPG2XX"] = true
--MACHINES["SPISDCARD"] = true
MACHINES["STVCD"] = true
--MACHINES["SUN4C_MMU"] = true
MACHINES["SWTPC8212"] = true

View File

@ -692,6 +692,7 @@ MACHINES["SEGA_SCU"] = true
MACHINES["SMPC"] = true
MACHINES["SPG2XX"] = true
MACHINES["SPG290"] = true
MACHINES["SPISDCARD"] = true
MACHINES["STVCD"] = true
MACHINES["SUN4C_MMU"] = true
MACHINES["SWTPC8212"] = true

View File

@ -0,0 +1,280 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*********************************************************************
a2sd.cpp
Implementation of the AppleIISD card by Florian Reitz
https://github.com/freitz85/AppleIISd
AppleIISD has a Xilinx FPGA which implements a minimally hardware
assisted SPI interface, and the SD card is thus driven in SPI
mode rather than SD.
The SPI controller is fixed to SPI Mode 3 only.
(shift on falling CLK edges, shift-then-latch).
Firmware is contained in an Atmel 28C64B parallel EEPROM, which
has a Flash-style command set.
*********************************************************************/
#include "emu.h"
#include "a2sd.h"
#define LOG_GENERAL (1U << 0)
#define LOG_SPI (1U << 1)
//#define VERBOSE (LOG_COMMAND)
#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"
/***************************************************************************
PARAMETERS
***************************************************************************/
static constexpr u8 C0N1_ECE = 0x04; // external clock: toggles between 500 kHz internal clock and 1/2 of the 7M A2 bus clock
static constexpr u8 C0N1_FRX = 0x10; // fast recieve: when enabled, both reads and writes of the SPI data register start a shift cycle
[[unused]] static constexpr u8 C0N1_BSY = 0x20;
static constexpr u8 C0N1_TC = 0x80; // SPI transfer complete
static constexpr u8 C0N3_CD = 0x40; // card detect
static constexpr u8 C0N3_BIT_SS = 7;
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
DEFINE_DEVICE_TYPE(A2BUS_A2SD, a2bus_a2sd_device, "a2sd", "Apple II SD Card")
ROM_START( a2sd )
ROM_REGION(0x2000, "flash", ROMREGION_ERASE00)
ROM_LOAD( "appleiisd.bin", 0x000000, 0x000800, CRC(e82eea8a) SHA1(7e0acef01e622eeed6f8e87893d07c701bbef016) )
ROM_END
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void a2bus_a2sd_device::device_add_mconfig(machine_config &config)
{
AT28C64B(config, m_flash, 0);
SPI_SDCARD(config, m_sdcard, 0);
m_sdcard->spi_miso_callback().set(FUNC(a2bus_a2sd_device::spi_miso_w));
}
//-------------------------------------------------
// rom_region - device-specific ROM region
//-------------------------------------------------
const tiny_rom_entry *a2bus_a2sd_device::device_rom_region() const
{
return ROM_NAME( a2sd );
}
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
a2bus_a2sd_device::a2bus_a2sd_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, type, tag, owner, clock),
device_a2bus_card_interface(mconfig, *this),
m_flash(*this, "flash"),
m_sdcard(*this, "sdcard"),
m_datain(0), m_in_latch(0), m_out_latch(0), m_c0n1(0), m_c0n3(0x80), m_in_bit(0), m_shift_count(0)
{
}
a2bus_a2sd_device::a2bus_a2sd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
a2bus_a2sd_device(mconfig, A2BUS_A2SD, tag, owner, clock)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void a2bus_a2sd_device::device_start()
{
m_shift_timer = timer_alloc(0);
save_item(NAME(m_datain));
save_item(NAME(m_in_latch));
save_item(NAME(m_out_latch));
save_item(NAME(m_c0n1));
save_item(NAME(m_c0n3));
save_item(NAME(m_in_bit));
save_item(NAME(m_shift_count));
}
void a2bus_a2sd_device::device_reset()
{
m_shift_timer->adjust(attotime::never);
m_shift_count = 0;
m_sdcard->spi_clock_w(CLEAR_LINE);
}
void a2bus_a2sd_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
LOGMASKED(LOG_SPI, ">>>>>>> SHIFT %d (%c)\n", m_shift_count, (m_shift_count & 1) ? 'L' : 'S');
if (!(m_shift_count & 1))
{
if (m_shift_count < 16)
{
m_out_latch <<= 1;
}
m_in_latch <<= 1;
m_sdcard->spi_mosi_w(BIT(m_out_latch, 7));
m_sdcard->spi_clock_w(CLEAR_LINE);
}
else
{
m_in_latch &= ~0x01;
m_in_latch |= m_in_bit;
m_sdcard->spi_clock_w(ASSERT_LINE);
if (m_shift_count == 1)
{
m_datain = m_in_latch;
LOGMASKED(LOG_SPI, "SPI: got %02x (in latch %02x)\n", m_datain, m_in_latch);
}
}
m_shift_count--;
if (m_shift_count == 0)
{
m_shift_timer->adjust(attotime::never);
m_c0n1 |= C0N1_TC; // set TC
}
}
/*-------------------------------------------------
read_c0nx - called for reads from this card's c0nx space
-------------------------------------------------*/
u8 a2bus_a2sd_device::read_c0nx(u8 offset)
{
switch (offset)
{
case 0:
m_c0n1 &= ~C0N1_TC; // clear TC
// if FRX is set, both reads and writes trigger a shift cycle
if (m_c0n1 & C0N1_FRX)
{
m_c0n1 &= ~C0N1_TC; // clear TC
m_shift_count = 16;
m_out_latch = 0xff;
if (m_c0n1 & C0N1_ECE)
{
m_shift_timer->adjust(attotime::from_hz(14.318181_MHz_XTAL / 4), 0, attotime::from_hz(14.318181_MHz_XTAL / 4));
}
else
{
m_shift_timer->adjust(attotime::from_hz(500_kHz_XTAL), 0, attotime::from_hz(500_kHz_XTAL));
}
}
return m_datain;
case 1:
return m_c0n1;
case 2:
return 0;
case 3:
m_c0n3 &= ~C0N3_CD;
m_c0n3 |= m_sdcard->get_card_present() ? 0 : C0N3_CD; // bit is set if no card is present
return m_c0n3;
}
return 0xff;
}
/*-------------------------------------------------
write_c0nx - called for writes to this card's c0nx space
-------------------------------------------------*/
void a2bus_a2sd_device::write_c0nx(u8 offset, u8 data)
{
switch (offset)
{
case 0:
if (m_shift_count)
{
return;
}
LOGMASKED(LOG_SPI, "SPI sending %02x\n", data);
m_out_latch = data;
m_sdcard->spi_mosi_w(BIT(m_out_latch, 7));
m_c0n1 &= ~C0N1_TC; // clear TC
m_shift_count = 16;
// if ECE is set, clock is 3.58 MHz from the A2 bus, otherwise internally generated 500 kHz
if (m_c0n1 & C0N1_ECE)
{
m_shift_timer->adjust(attotime::from_hz(14.318181_MHz_XTAL / 4), 0, attotime::from_hz(14.318181_MHz_XTAL/4));
}
else
{
m_shift_timer->adjust(attotime::from_hz(500_kHz_XTAL), 0, attotime::from_hz(500_kHz_XTAL));
}
break;
case 1:
m_c0n1 &= 0xea;
m_c0n1 |= (data & 0x15);
break;
case 2:
break;
case 3:
m_c0n3 &= 0x9f;
m_c0n3 |= (data & 0x91);
m_sdcard->spi_ss_w(BIT(data, C0N3_BIT_SS));
LOGMASKED(LOG_GENERAL, "/SS is %x\n", BIT(data, C0N3_BIT_SS));
break;
default:
logerror("a2sd: write %02x to c0n%x (%s)\n", data, offset, machine().describe_context().c_str());
break;
}
}
/*-------------------------------------------------
read_cnxx - called for reads from this card's cnxx space
-------------------------------------------------*/
u8 a2bus_a2sd_device::read_cnxx(u8 offset)
{
// slot image at 0
return m_flash->read(offset);
}
void a2bus_a2sd_device::write_cnxx(u8 offset, u8 data)
{
m_flash->write(offset, data);
}
/*-------------------------------------------------
read_c800 - called for reads from this card's c800 space
-------------------------------------------------*/
u8 a2bus_a2sd_device::read_c800(u16 offset)
{
return m_flash->read(offset + 0x100);
}
/*-------------------------------------------------
write_c800 - called for writes to this card's c800 space
-------------------------------------------------*/
void a2bus_a2sd_device::write_c800(u16 offset, u8 data)
{
m_flash->write(offset + 0x100, data);
}

View File

@ -0,0 +1,67 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*********************************************************************
a2sd.h
Implementation of the AppleIISD card
*********************************************************************/
#ifndef MAME_BUS_A2BUS_A2SD_H
#define MAME_BUS_A2BUS_A2SD_H
#pragma once
#include "a2bus.h"
#include "machine/at28c64b.h"
#include "machine/spi_sdcard.h"
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
class a2bus_a2sd_device:
public device_t,
public device_a2bus_card_interface
{
public:
// construction/destruction
a2bus_a2sd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
protected:
a2bus_a2sd_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_add_mconfig(machine_config &config) override;
virtual const tiny_rom_entry *device_rom_region() const override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
// overrides of standard a2bus slot functions
virtual u8 read_c0nx(u8 offset) override;
virtual void write_c0nx(u8 offset, u8 data) override;
virtual u8 read_cnxx(u8 offset) override;
virtual void write_cnxx(u8 offset, u8 data) override;
virtual u8 read_c800(uint16_t offset) override;
virtual void write_c800(uint16_t offset, u8 data) override;
// SPI 4-wire interface
WRITE_LINE_MEMBER(spi_miso_w) { m_in_bit = state; }
private:
required_device<at28c64b_device> m_flash;
required_device<spi_sdcard_device> m_sdcard;
u8 m_datain, m_in_latch, m_out_latch;
u8 m_c0n1, m_c0n3;
int m_in_bit;
int m_shift_count;
emu_timer *m_shift_timer;
};
// device type definition
DECLARE_DEVICE_TYPE(A2BUS_A2SD, a2bus_a2sd_device)
#endif // MAME_BUS_A2BUS_A2SD_H

View File

@ -0,0 +1,273 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*
SD Card emulation, SPI interface only currently
Emulation by R. Belmont
This emulates an SDHC card, which means the block size is fixed at 512 bytes and makes things simpler.
Adapting the code to also handle SD version 2 non-HC cards would be relatively straightforward as well;
the block size defaults to 1 byte in that case but can be overridden with CMD16.
Adding the native 4-bit-wide SD interface is also possible
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_device, "spi_sdcard", "SD 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_bACMD(false)
{
}
spi_sdcard_device::spi_sdcard_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
spi_sdcard_device(mconfig, SPI_SDCARD, tag, owner, clock)
{
}
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_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 == 514)
{
u32 blk = (m_cmd[1] << 24) | (m_cmd[2] << 16) | (m_cmd[3] << 8) | m_cmd[4];
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 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];
LOGMASKED(LOG_GENERAL, "reading LBA %x\n", blk);
hard_disk_read(m_harddisk, blk, &m_data[3]);
send_data(3 + 512 + 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;
m_data[1] = 0x40; // indicate SDHC support
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;
}
}

View File

@ -0,0 +1,55 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
#ifndef MAME_MACHINE_SPI_SDCARD_H
#define MAME_MACHINE_SPI_SDCARD_H
#pragma once
#include "imagedev/harddriv.h"
class spi_sdcard_device : public device_t
{
public:
spi_sdcard_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// SPI 4-wire interface
auto spi_miso_callback() { return write_miso.bind(); }
void spi_clock_w(int state);
void spi_ss_w(int state) { m_ss = state; }
void spi_mosi_w(int state) { m_in_bit = state; }
bool get_card_present() { return !(m_harddisk == nullptr); }
devcb_write_line write_miso;
protected:
spi_sdcard_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_add_mconfig(machine_config &config) override;
required_device<harddisk_image_device> m_image;
private:
enum
{
SD_STATE_IDLE = 0,
SD_STATE_WRITE_WAITFE,
SD_STATE_WRITE_DATA
};
void send_data(int count);
void do_command();
u8 m_data[520], m_cmd[6];
hard_disk_file *m_harddisk;
u8 m_in_latch, m_out_latch;
int m_cmd_ptr, m_state, m_out_ptr, m_out_count, m_ss, m_in_bit, m_cur_bit, m_write_ptr;
bool m_bACMD;
};
DECLARE_DEVICE_TYPE(SPI_SDCARD, spi_sdcard_device)
#endif // MAME_MACHINE_SPI_SDCARD_H

View File

@ -159,6 +159,7 @@ MIG RAM page 2 $CE02 is the speaker/slot bitfield and $CE03 is the paddle/accele
#include "bus/a2bus/a2pic.h"
#include "bus/a2bus/a2sam.h"
#include "bus/a2bus/a2scsi.h"
#include "bus/a2bus/a2sd.h"
#include "bus/a2bus/a2softcard.h"
#include "bus/a2bus/a2ssc.h"
#include "bus/a2bus/a2swyft.h"
@ -4719,6 +4720,7 @@ static void apple2_cards(device_slot_interface &device)
device.option_add("lancegs", A2BUS_LANCEGS); /* ///SHH SYSTEME LANceGS Card */
device.option_add("q68", A2BUS_Q68); /* Stellation Q68 68000 card */
device.option_add("q68plus", A2BUS_Q68PLUS); /* Stellation Q68 Plus 68000 card */
device.option_add("a2sd", A2BUS_A2SD); /* Florian Reitz AppleIISD */
}
static void apple2eaux_cards(device_slot_interface &device)