/*************************************************************************
SMC91C9X ethernet controller implementation
by Aaron Giles
***************************************************************************
Notes:
* only loopback mode really works
**************************************************************************/
#include "emu.h"
#include "smc91c9x.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define LOG_ETHERNET (0)
#define DISPLAY_STATS (0)
/***************************************************************************
CONSTANTS
***************************************************************************/
#define ETHER_BUFFER_SIZE (2048)
#define ETHER_RX_BUFFERS (4)
/* Ethernet registers - bank 0 */
#define EREG_TCR (0*8 + 0)
#define EREG_EPH_STATUS (0*8 + 1)
#define EREG_RCR (0*8 + 2)
#define EREG_COUNTER (0*8 + 3)
#define EREG_MIR (0*8 + 4)
#define EREG_MCR (0*8 + 5)
#define EREG_BANK (0*8 + 7)
/* Ethernet registers - bank 1 */
#define EREG_CONFIG (1*8 + 0)
#define EREG_BASE (1*8 + 1)
#define EREG_IA0_1 (1*8 + 2)
#define EREG_IA2_3 (1*8 + 3)
#define EREG_IA4_5 (1*8 + 4)
#define EREG_GENERAL_PURP (1*8 + 5)
#define EREG_CONTROL (1*8 + 6)
/* Ethernet registers - bank 2 */
#define EREG_MMU_COMMAND (2*8 + 0)
#define EREG_PNR_ARR (2*8 + 1)
#define EREG_FIFO_PORTS (2*8 + 2)
#define EREG_POINTER (2*8 + 3)
#define EREG_DATA_0 (2*8 + 4)
#define EREG_DATA_1 (2*8 + 5)
#define EREG_INTERRUPT (2*8 + 6)
/* Ethernet registers - bank 3 */
#define EREG_MT0_1 (3*8 + 0)
#define EREG_MT2_3 (3*8 + 1)
#define EREG_MT4_5 (3*8 + 2)
#define EREG_MT6_7 (3*8 + 3)
#define EREG_MGMT (3*8 + 4)
#define EREG_REVISION (3*8 + 5)
#define EREG_ERCV (3*8 + 6)
/* Ethernet MMU commands */
#define ECMD_NOP 0
#define ECMD_ALLOCATE 1
#define ECMD_RESET_MMU 2
#define ECMD_REMOVE 3
#define ECMD_REMOVE_RELEASE 4
#define ECMD_RELEASE_PACKET 5
#define ECMD_ENQUEUE_PACKET 6
#define ECMD_RESET_FIFOS 7
/* Ethernet interrupt bits */
#define EINT_RCV 0x01
#define EINT_TX 0x02
#define EINT_TX_EMPTY 0x04
#define EINT_ALLOC 0x08
#define EINT_RX_OVRN 0x10
#define EINT_EPH 0x20
#define EINT_ERCV 0x40
/* Ethernet register names */
static const char *const ethernet_regname[64] =
{
"TCR", "EPH STATUS", "RCR", "COUNTER", "MIR", "MCR", "(0.6)", "BANK",
"CONFIG", "BASE", "IA0-1", "IA2-3", "IA4-5", "GENERAL PURPOSE", "CONTROL", "BANK",
"MMU COMMAND", "PNR ARR", "FIFO PORTS", "POINTER", "DATA", "DATA", "INTERRUPT", "BANK",
"MT0-1", "MT2-3", "MT4-5", "MT6-7", "MGMT", "REVISION", "ERCV", "BANK",
"(4.0)", "(4.1)", "(4.2)", "(4.3)", "(4.4)", "(4.5)", "(4.6)", "BANK",
"(5.0)", "(5.1)", "(5.2)", "(5.3)", "(5.4)", "(5.5)", "(5.6)", "BANK",
"(6.0)", "(6.1)", "(6.2)", "(6.3)", "(6.4)", "(6.5)", "(6.6)", "BANK",
"(7.0)", "(7.1)", "(7.2)", "(7.3)", "(7.4)", "(7.5)", "(7.6)", "BANK"
};
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
typedef struct _smc91c9x_state smc91c9x_state;
struct _smc91c9x_state
{
device_t *device;
smc91c9x_irq_func irq_handler;
/* raw register data and masks */
UINT16 reg[64];
UINT16 regmask[64];
/* IRQ information */
UINT8 irq_state;
/* allocate information */
UINT8 alloc_count;
/* transmit/receive FIFOs */
UINT8 fifo_count;
UINT8 rx[ETHER_BUFFER_SIZE * ETHER_RX_BUFFERS];
UINT8 tx[ETHER_BUFFER_SIZE];
/* counters */
UINT32 sent;
UINT32 recd;
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
static void update_ethernet_irq(smc91c9x_state *smc);
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
get_safe_token - makes sure that the passed
in device is, in fact, an IDE controller
-------------------------------------------------*/
INLINE smc91c9x_state *get_safe_token(device_t *device)
{
assert(device != NULL);
assert(device->type() == SMC91C94 || device->type() == SMC91C96);
return (smc91c9x_state *)downcast<legacy_device_base *>(device)->token();
}
/***************************************************************************
INTERNAL HELPERS
***************************************************************************/
/*-------------------------------------------------
update_ethernet_irq - update the IRQ state
-------------------------------------------------*/
static void update_ethernet_irq(smc91c9x_state *smc)
{
UINT8 mask = smc->reg[EREG_INTERRUPT] >> 8;
UINT8 state = smc->reg[EREG_INTERRUPT] & 0xff;
/* update the IRQ state */
smc->irq_state = ((mask & state) != 0);
if (smc->irq_handler != NULL)
(*smc->irq_handler)(smc->device, smc->irq_state ? ASSERT_LINE : CLEAR_LINE);
}
/*-------------------------------------------------
update_stats - draw statistics
-------------------------------------------------*/
static void update_stats(smc91c9x_state *smc)
{
if (DISPLAY_STATS)
popmessage("Sent:%d Rec'd:%d", smc->sent, smc->recd);
}
/*-------------------------------------------------
finish_enqueue - complete an enqueued packet
-------------------------------------------------*/
static void finish_enqueue(smc91c9x_state *smc, int param)
{
int is_broadcast = (smc->tx[4] == 0xff && smc->tx[5] == 0xff && smc->tx[6] == 0xff &&
smc->tx[7] == 0xff && smc->tx[8] == 0xff && smc->tx[9] == 0xff);
/* update the EPH register and stuff it in the first transmit word */
smc->reg[EREG_EPH_STATUS] = 0x0001;
if (is_broadcast)
smc->reg[EREG_EPH_STATUS] |= 0x0040;
smc->tx[0] = smc->reg[EREG_EPH_STATUS];
smc->tx[1] = smc->reg[EREG_EPH_STATUS] >> 8;
/* signal a transmit interrupt and mark the transmit buffer empty */
smc->reg[EREG_INTERRUPT] |= EINT_TX;
smc->reg[EREG_INTERRUPT] |= EINT_TX_EMPTY;
smc->reg[EREG_FIFO_PORTS] |= 0x0080;
smc->sent++;
update_stats(smc);
/* loopback? */
if (smc->reg[EREG_TCR] & 0x2002)
if (smc->fifo_count < ETHER_RX_BUFFERS)
{
int buffer_len = ((smc->tx[3] << 8) | smc->tx[2]) & 0x7ff;
UINT8 *packet = &smc->rx[smc->fifo_count++ * ETHER_BUFFER_SIZE];
int packet_len;
/* compute the packet length */
packet_len = buffer_len - 6;
if (packet[buffer_len - 1] & 0x20)
packet_len++;
/* build up the packet */
packet[0] = 0x0000;
packet[1] = 0x0000;
packet[2] = buffer_len;
packet[3] = buffer_len >> 8;
memcpy(&packet[4], &smc->tx[4], 6);
memcpy(&packet[10], &smc->tx[10], 6);
memcpy(&packet[16], &smc->tx[16], buffer_len - 16);
/* set the broadcast flag */
if (is_broadcast)
packet[1] |= 0x40;
/* pad? */
if (smc->reg[EREG_TCR & 0x0080])
if (packet_len < 64)
{
memset(&packet[buffer_len], 0, 64+6 - buffer_len);
packet[buffer_len - 1] = 0;
buffer_len = 64+6;
packet[2] = buffer_len;
packet[3] = buffer_len >> 8;
}
/* signal a receive */
smc->reg[EREG_INTERRUPT] |= EINT_RCV;
smc->reg[EREG_FIFO_PORTS] &= ~0x8000;
}
update_ethernet_irq(smc);
}
/*-------------------------------------------------
process_command - handle MMU commands
-------------------------------------------------*/
static void process_command(smc91c9x_state *smc, UINT16 data)
{
switch ((data >> 5) & 7)
{
case ECMD_NOP:
if (LOG_ETHERNET)
logerror(" NOP\n");
break;
case ECMD_ALLOCATE:
if (LOG_ETHERNET)
logerror(" ALLOCATE MEMORY FOR TX (%d)\n", (data & 7));
smc->reg[EREG_PNR_ARR] &= ~0xff00;
smc->reg[EREG_PNR_ARR] |= smc->alloc_count++ << 8;
smc->reg[EREG_INTERRUPT] |= 0x0008;
update_ethernet_irq(smc);
break;
case ECMD_RESET_MMU:
if (LOG_ETHERNET)
logerror(" RESET MMU\n");
break;
case ECMD_REMOVE:
if (LOG_ETHERNET)
logerror(" REMOVE FRAME FROM RX FIFO\n");
break;
case ECMD_REMOVE_RELEASE:
if (LOG_ETHERNET)
logerror(" REMOVE AND RELEASE FRAME FROM RX FIFO\n");
smc->reg[EREG_INTERRUPT] &= ~EINT_RCV;
if (smc->fifo_count > 0)
smc->fifo_count--;
if (smc->fifo_count > 0)
{
memmove(&smc->rx[0], &smc->rx[ETHER_BUFFER_SIZE], smc->fifo_count * ETHER_BUFFER_SIZE);
smc->reg[EREG_INTERRUPT] |= EINT_RCV;
smc->reg[EREG_FIFO_PORTS] &= ~0x8000;
}
else
smc->reg[EREG_FIFO_PORTS] |= 0x8000;
update_ethernet_irq(smc);
smc->recd++;
update_stats(smc);
break;
case ECMD_RELEASE_PACKET:
if (LOG_ETHERNET)
logerror(" RELEASE SPECIFIC PACKET\n");
break;
case ECMD_ENQUEUE_PACKET:
if (LOG_ETHERNET)
logerror(" ENQUEUE TX PACKET\n");
finish_enqueue(smc, 0);
break;
case ECMD_RESET_FIFOS:
if (LOG_ETHERNET)
logerror(" RESET TX FIFOS\n");
break;
}
smc->reg[EREG_MMU_COMMAND] &= ~0x0001;
}
/***************************************************************************
CORE READ/WRITE HANDLERS
***************************************************************************/
/*-------------------------------------------------
smc91c9x_r - handle a read from the device
-------------------------------------------------*/
READ16_DEVICE_HANDLER( smc91c9x_r )
{
smc91c9x_state *smc = get_safe_token(device);
UINT32 result = ~0;
/* determine the effective register */
offset %= 8;
if (offset != EREG_BANK)
offset += 8 * (smc->reg[EREG_BANK] & 7);
result = smc->reg[offset];
switch (offset)
{
case EREG_PNR_ARR:
if (ACCESSING_BITS_8_15)
{
smc->reg[EREG_INTERRUPT] &= ~0x0008;
update_ethernet_irq(smc);
}
break;
case EREG_DATA_0: /* data register */
case EREG_DATA_1: /* data register */
{
UINT8 *buffer = (smc->reg[EREG_POINTER] & 0x8000) ? smc->rx : smc->tx;
int addr = smc->reg[EREG_POINTER] & 0x7ff;
result = buffer[addr++];
if (ACCESSING_BITS_8_15)
result |= buffer[addr++] << 8;
if (smc->reg[EREG_POINTER] & 0x4000)
smc->reg[EREG_POINTER] = (smc->reg[EREG_POINTER] & ~0x7ff) | (addr & 0x7ff);
break;
}
}
if (LOG_ETHERNET && offset != EREG_BANK)
logerror("%s:smc91c9x_r(%s) = %04X & %04X\n", device->machine().describe_context(), ethernet_regname[offset], result, mem_mask);
return result;
}
/*-------------------------------------------------
smc91c9x_w - handle a write to the device
-------------------------------------------------*/
WRITE16_DEVICE_HANDLER( smc91c9x_w )
{
smc91c9x_state *smc = get_safe_token(device);
// UINT16 olddata;
/* determine the effective register */
offset %= 8;
if (offset != EREG_BANK)
offset += 8 * (smc->reg[EREG_BANK] & 7);
/* update the data generically */
// olddata = smc->reg[offset];
mem_mask &= smc->regmask[offset];
COMBINE_DATA(&smc->reg[offset]);
if (LOG_ETHERNET && offset != 7)
logerror("%s:smc91c9x_w(%s) = %04X & %04X\n", device->machine().describe_context(), ethernet_regname[offset], data, mem_mask);
/* handle it */
switch (offset)
{
case EREG_TCR: /* transmit control register */
if (LOG_ETHERNET)
{
if (data & 0x2000) logerror(" EPH LOOP\n");
if (data & 0x1000) logerror(" STP SQET\n");
if (data & 0x0800) logerror(" FDUPLX\n");
if (data & 0x0400) logerror(" MON_CSN\n");
if (data & 0x0100) logerror(" NOCRC\n");
if (data & 0x0080) logerror(" PAD_EN\n");
if (data & 0x0004) logerror(" FORCOL\n");
if (data & 0x0002) logerror(" LOOP\n");
if (data & 0x0001) logerror(" TXENA\n");
}
break;
case EREG_RCR: /* receive control register */
if (LOG_ETHERNET)
{
if (data & 0x8000) device->reset();
if (data & 0x8000) logerror(" SOFT RST\n");
if (data & 0x4000) logerror(" FILT_CAR\n");
if (data & 0x0200) logerror(" STRIP CRC\n");
if (data & 0x0100) logerror(" RXEN\n");
if (data & 0x0004) logerror(" ALMUL\n");
if (data & 0x0002) logerror(" PRMS\n");
if (data & 0x0001) logerror(" RX_ABORT\n");
}
break;
case EREG_CONFIG: /* configuration register */
if (LOG_ETHERNET)
{
if (data & 0x1000) logerror(" NO WAIT\n");
if (data & 0x0400) logerror(" FULL STEP\n");
if (data & 0x0200) logerror(" SET SQLCH\n");
if (data & 0x0100) logerror(" AUI SELECT\n");
if (data & 0x0080) logerror(" 16 BIT\n");
if (data & 0x0040) logerror(" DIS LINK\n");
if (data & 0x0004) logerror(" INT SEL1\n");
if (data & 0x0002) logerror(" INT SEL0\n");
}
break;
case EREG_BASE: /* base address register */
if (LOG_ETHERNET)
{
logerror(" base = $%04X\n", (data & 0xe000) | ((data & 0x1f00) >> 3));
logerror(" romsize = %d\n", ((data & 0xc0) >> 6));
logerror(" romaddr = $%05X\n", ((data & 0x3e) << 13));
}
break;
case EREG_CONTROL: /* control register */
if (LOG_ETHERNET)
{
if (data & 0x4000) logerror(" RCV_BAD\n");
if (data & 0x2000) logerror(" PWRDN\n");
if (data & 0x0800) logerror(" AUTO RELEASE\n");
if (data & 0x0080) logerror(" LE ENABLE\n");
if (data & 0x0040) logerror(" CR ENABLE\n");
if (data & 0x0020) logerror(" TE ENABLE\n");
if (data & 0x0004) logerror(" EEPROM SELECT\n");
if (data & 0x0002) logerror(" RELOAD\n");
if (data & 0x0001) logerror(" STORE\n");
}
break;
case EREG_MMU_COMMAND: /* command register */
process_command(smc, data);
break;
case EREG_DATA_0: /* data register */
case EREG_DATA_1: /* data register */
{
UINT8 *buffer = (smc->reg[EREG_POINTER] & 0x8000) ? smc->rx : smc->tx;
int addr = smc->reg[EREG_POINTER] & 0x7ff;
buffer[addr++] = data;
if (ACCESSING_BITS_8_15)
buffer[addr++] = data >> 8;
if (smc->reg[EREG_POINTER] & 0x4000)
smc->reg[EREG_POINTER] = (smc->reg[EREG_POINTER] & ~0x7ff) | (addr & 0x7ff);
break;
}
case EREG_INTERRUPT:
smc->reg[EREG_INTERRUPT] &= ~(data & 0x56);
update_ethernet_irq(smc);
break;
}
}
/***************************************************************************
DEVICE INTERFACE
***************************************************************************/
/*-------------------------------------------------
device start callback
-------------------------------------------------*/
static DEVICE_START( smc91c9x )
{
const smc91c9x_config *config = (const smc91c9x_config *)downcast<const legacy_device_base *>(device)->inline_config();
smc91c9x_state *smc = get_safe_token(device);
/* validate some basic stuff */
assert(device != NULL);
assert(device->static_config() == NULL);
assert(downcast<const legacy_device_base *>(device)->inline_config() != NULL);
/* store a pointer back to the device */
smc->device = device;
smc->irq_handler = config->interrupt;
/* register ide states */
device->save_item(NAME(smc->reg));
device->save_item(NAME(smc->regmask));
device->save_item(NAME(smc->irq_state));
device->save_item(NAME(smc->alloc_count));
device->save_item(NAME(smc->fifo_count));
device->save_item(NAME(smc->rx));
device->save_item(NAME(smc->tx));
device->save_item(NAME(smc->sent));
device->save_item(NAME(smc->recd));
}
/*-------------------------------------------------
device reset callback
-------------------------------------------------*/
static DEVICE_RESET( smc91c9x )
{
smc91c9x_state *smc = get_safe_token(device);
memset(smc->reg, 0, sizeof(smc->reg));
memset(smc->regmask, 0, sizeof(smc->regmask));
smc->irq_state = 0;
smc->alloc_count = 0;
smc->fifo_count = 0;
smc->sent = 0;
smc->recd = 0;
smc->reg[EREG_TCR] = 0x0000; smc->regmask[EREG_TCR] = 0x3d87;
smc->reg[EREG_EPH_STATUS] = 0x0000; smc->regmask[EREG_EPH_STATUS] = 0x0000;
smc->reg[EREG_RCR] = 0x0000; smc->regmask[EREG_RCR] = 0xc307;
smc->reg[EREG_COUNTER] = 0x0000; smc->regmask[EREG_COUNTER] = 0x0000;
smc->reg[EREG_MIR] = 0x1212; smc->regmask[EREG_MIR] = 0x0000;
smc->reg[EREG_MCR] = 0x3300; smc->regmask[EREG_MCR] = 0x00ff;
smc->reg[EREG_BANK] = 0x3300; smc->regmask[EREG_BANK] = 0x0007;
smc->reg[EREG_CONFIG] = 0x0030; smc->regmask[EREG_CONFIG] = 0x17c6;
smc->reg[EREG_BASE] = 0x1866; smc->regmask[EREG_BASE] = 0xfffe;
smc->reg[EREG_IA0_1] = 0x0000; smc->regmask[EREG_IA0_1] = 0xffff;
smc->reg[EREG_IA2_3] = 0x0000; smc->regmask[EREG_IA2_3] = 0xffff;
smc->reg[EREG_IA4_5] = 0x0000; smc->regmask[EREG_IA4_5] = 0xffff;
smc->reg[EREG_GENERAL_PURP] = 0x0000; smc->regmask[EREG_GENERAL_PURP] = 0xffff;
smc->reg[EREG_CONTROL] = 0x0100; smc->regmask[EREG_CONTROL] = 0x68e7;
smc->reg[EREG_MMU_COMMAND] = 0x0000; smc->regmask[EREG_MMU_COMMAND] = 0x00e7;
smc->reg[EREG_PNR_ARR] = 0x8000; smc->regmask[EREG_PNR_ARR] = 0x00ff;
smc->reg[EREG_FIFO_PORTS] = 0x8080; smc->regmask[EREG_FIFO_PORTS] = 0x0000;
smc->reg[EREG_POINTER] = 0x0000; smc->regmask[EREG_POINTER] = 0xf7ff;
smc->reg[EREG_DATA_0] = 0x0000; smc->regmask[EREG_DATA_0] = 0xffff;
smc->reg[EREG_DATA_1] = 0x0000; smc->regmask[EREG_DATA_1] = 0xffff;
smc->reg[EREG_INTERRUPT] = 0x0004; smc->regmask[EREG_INTERRUPT] = 0x7f00;
smc->reg[EREG_MT0_1] = 0x0000; smc->regmask[EREG_MT0_1] = 0xffff;
smc->reg[EREG_MT2_3] = 0x0000; smc->regmask[EREG_MT2_3] = 0xffff;
smc->reg[EREG_MT4_5] = 0x0000; smc->regmask[EREG_MT4_5] = 0xffff;
smc->reg[EREG_MT6_7] = 0x0000; smc->regmask[EREG_MT6_7] = 0xffff;
smc->reg[EREG_MGMT] = 0x3030; smc->regmask[EREG_MGMT] = 0x0f0f;
smc->reg[EREG_REVISION] = 0x3340; smc->regmask[EREG_REVISION] = 0x0000;
smc->reg[EREG_ERCV] = 0x331f; smc->regmask[EREG_ERCV] = 0x009f;
update_ethernet_irq(smc);
}
/*-------------------------------------------------
device get info callback
-------------------------------------------------*/
static DEVICE_GET_INFO( smc91c9x )
{
switch (state)
{
/* --- the following bits of info are returned as 64-bit signed integers --- */
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(smc91c9x_state); break;
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(smc91c9x_config); break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(smc91c9x); break;
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(smc91c9x);break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: /* provided by subclasses */ break;
case DEVINFO_STR_FAMILY: strcpy(info->s, "SMC91C9X Ethernet Controller");break;
case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break;
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
}
}
DEVICE_GET_INFO( smc91c94 )
{
switch (state)
{
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: strcpy(info->s, "SMC91C94"); break;
default: DEVICE_GET_INFO_CALL(smc91c9x); break;
}
}
DEVICE_GET_INFO( smc91c96 )
{
switch (state)
{
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: strcpy(info->s, "SMC91C96"); break;
default: DEVICE_GET_INFO_CALL(smc91c9x); break;
}
}
DEFINE_LEGACY_DEVICE(SMC91C94, smc91c94);
DEFINE_LEGACY_DEVICE(SMC91C96, smc91c96);