725 lines
21 KiB
C
725 lines
21 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* Emulation of the NatSemi PC87309 Super I/O chip.
|
|
*
|
|
* Authors: Miran Grca, <mgrca8@gmail.com>
|
|
*
|
|
* Copyright 2020-2025 Miran Grca.
|
|
*/
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/io.h>
|
|
#include <86box/timer.h>
|
|
#include <86box/device.h>
|
|
#include <86box/lpt.h>
|
|
#include <86box/machine.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/nvr.h>
|
|
#include <86box/pci.h>
|
|
#include <86box/rom.h>
|
|
#include <86box/serial.h>
|
|
#include <86box/fdd.h>
|
|
#include <86box/fdc.h>
|
|
#include <86box/keyboard.h>
|
|
#include <86box/sio.h>
|
|
#include <86box/plat_fallthrough.h>
|
|
#include "cpu.h"
|
|
|
|
typedef struct pc87309_t {
|
|
uint8_t id;
|
|
uint8_t baddr;
|
|
uint8_t pm_idx;
|
|
uint8_t regs[48];
|
|
uint8_t ld_regs[256][208];
|
|
uint8_t pm[8];
|
|
uint16_t superio_base;
|
|
uint16_t pm_base;
|
|
int cur_reg;
|
|
void *kbc;
|
|
fdc_t *fdc;
|
|
serial_t *uart[2];
|
|
} pc87309_t;
|
|
|
|
enum {
|
|
LD_FDC = 0,
|
|
LD_LPT,
|
|
LD_UART2,
|
|
LD_UART1,
|
|
LD_PM,
|
|
LD_MOUSE,
|
|
LD_KBD
|
|
} pc87309_ld_t;
|
|
|
|
#define LD_MIN LD_FDC
|
|
#define LD_MAX LD_MOUSE
|
|
|
|
static void fdc_handler(pc87309_t *dev);
|
|
static void lpt1_handler(pc87309_t *dev);
|
|
static void serial_handler(pc87309_t *dev, int uart);
|
|
static void kbc_handler(pc87309_t *dev);
|
|
static void pc87309_write(uint16_t port, uint8_t val, void *priv);
|
|
static uint8_t pc87309_read(uint16_t port, void *priv);
|
|
|
|
#ifdef ENABLE_PC87309_LOG
|
|
int pc87309_do_log = ENABLE_PC87309_LOG;
|
|
|
|
static void
|
|
pc87309_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (pc87309_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define pc87309_log(fmt, ...)
|
|
#endif
|
|
|
|
static void
|
|
pc87309_pm_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
pc87309_t *dev = (pc87309_t *) priv;
|
|
|
|
if (port & 1)
|
|
dev->pm[dev->pm_idx] = val;
|
|
else {
|
|
dev->pm_idx = val & 0x07;
|
|
|
|
switch (dev->pm_idx) {
|
|
case 0x00:
|
|
fdc_handler(dev);
|
|
lpt1_handler(dev);
|
|
serial_handler(dev, 1);
|
|
serial_handler(dev, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t
|
|
pc87309_pm_read(uint16_t port, void *priv)
|
|
{
|
|
const pc87309_t *dev = (pc87309_t *) priv;
|
|
|
|
if (port & 1)
|
|
return dev->pm[dev->pm_idx];
|
|
else
|
|
return dev->pm_idx;
|
|
}
|
|
|
|
static void
|
|
pc87309_pm_remove(pc87309_t *dev)
|
|
{
|
|
if (dev->pm_base != 0xffff) {
|
|
io_removehandler(dev->pm_base, 0x0008,
|
|
pc87309_pm_read, NULL, NULL, pc87309_pm_write, NULL, NULL, dev);
|
|
dev->pm_base = 0xffff;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pc87309_pm_init(pc87309_t *dev, uint16_t addr)
|
|
{
|
|
dev->pm_base = addr;
|
|
|
|
io_sethandler(dev->pm_base, 0x0008,
|
|
pc87309_pm_read, NULL, NULL, pc87309_pm_write, NULL, NULL, dev);
|
|
}
|
|
|
|
static void
|
|
kbc_handler(pc87309_t *dev)
|
|
{
|
|
uint8_t active = (dev->ld_regs[LD_KBD][0x00] & 0x01) &&
|
|
(dev->pm[0x00] & 0x01);
|
|
uint8_t active_2 = dev->ld_regs[LD_MOUSE][0x00] & 0x01;
|
|
uint8_t irq = (dev->ld_regs[LD_KBD][0x40] & 0x0f);
|
|
uint8_t irq_2 = (dev->ld_regs[LD_MOUSE][0x40] & 0x0f);
|
|
uint16_t addr = (dev->ld_regs[LD_KBD][0x30] << 8) |
|
|
dev->ld_regs[LD_KBD][0x31];
|
|
uint16_t addr_2 = (dev->ld_regs[LD_KBD][0x32] << 8) |
|
|
dev->ld_regs[LD_KBD][0x33];
|
|
|
|
pc87309_log("%02X, %02X, %02X, %02X, %04X, %04X\n",
|
|
active, active_2, irq, irq_2, addr, addr_2);
|
|
|
|
if (addr <= 0xfff8) {
|
|
pc87309_log("Enabling KBC #1 on %04X...\n", addr);
|
|
kbc_at_port_handler(0, active, addr, dev->kbc);
|
|
}
|
|
|
|
if (addr_2 <= 0xfff8) {
|
|
pc87309_log("Enabling KBC #2 on %04X...\n", addr_2);
|
|
kbc_at_port_handler(1, active, addr_2, dev->kbc);
|
|
}
|
|
|
|
kbc_at_set_irq(0, active ? irq : 0xffff, dev->kbc);
|
|
kbc_at_set_irq(1, (active && active_2) ? irq_2 : 0xffff, dev->kbc);
|
|
}
|
|
|
|
static void
|
|
fdc_handler(pc87309_t *dev)
|
|
{
|
|
fdc_remove(dev->fdc);
|
|
|
|
uint8_t active = (dev->ld_regs[LD_FDC][0x00] & 0x01) &&
|
|
(dev->pm[0x00] & 0x08);
|
|
uint8_t irq = (dev->ld_regs[LD_FDC][0x40] & 0x0f);
|
|
uint16_t addr = ((dev->ld_regs[LD_FDC][0x30] << 8) |
|
|
dev->ld_regs[LD_FDC][0x31]) & 0xfff8;
|
|
|
|
if (active && (addr <= 0xfff8)) {
|
|
pc87309_log("Enabling FDC on %04X, IRQ %i...\n", addr, irq);
|
|
fdc_set_base(dev->fdc, addr);
|
|
fdc_set_irq(dev->fdc, irq);
|
|
}
|
|
}
|
|
|
|
static void
|
|
lpt1_handler(pc87309_t *dev)
|
|
{
|
|
uint8_t active = (dev->ld_regs[LD_LPT][0x00] & 0x01) &&
|
|
(dev->pm[0x00] & 0x10);
|
|
uint8_t irq = (dev->ld_regs[LD_LPT][0x40] & 0x0f);
|
|
uint16_t addr = (dev->ld_regs[LD_LPT][0x30] << 8) |
|
|
dev->ld_regs[LD_LPT][0x31];
|
|
|
|
if (active && (addr <= 0xfffc)) {
|
|
pc87309_log("Enabling LPT1 on %04X...\n", addr);
|
|
lpt1_setup(addr);
|
|
} else
|
|
lpt1_setup(0xffff);
|
|
|
|
lpt1_irq(irq);
|
|
}
|
|
|
|
static void
|
|
serial_handler(pc87309_t *dev, int uart)
|
|
{
|
|
serial_remove(dev->uart[uart]);
|
|
|
|
uint8_t active = (dev->ld_regs[LD_UART1 - uart][0x00] & 0x01) &&
|
|
(dev->pm[0x00] & (1 << (6 - uart)));
|
|
uint8_t irq = (dev->ld_regs[LD_UART1 - uart][0x40] & 0x0f);
|
|
uint16_t addr = (dev->ld_regs[LD_UART1 - uart][0x30] << 8) |
|
|
dev->ld_regs[LD_UART1 - uart][0x31];
|
|
|
|
if (active && (addr <= 0xfff8)) {
|
|
pc87309_log("Enabling COM%i on %04X...\n", uart + 1, addr);
|
|
serial_setup(dev->uart[uart], addr, irq);
|
|
} else
|
|
serial_setup(dev->uart[uart], 0x0000, irq);
|
|
}
|
|
|
|
static void
|
|
pm_handler(pc87309_t *dev)
|
|
{
|
|
pc87309_pm_remove(dev);
|
|
|
|
uint8_t active = (dev->ld_regs[LD_PM][0x00] & 0x01);
|
|
uint16_t addr = (dev->ld_regs[LD_PM][0x30] << 8) |
|
|
dev->ld_regs[LD_PM][0x31];
|
|
|
|
if (active) {
|
|
pc87309_log("Enabling power management on %04X...\n", addr);
|
|
pc87309_pm_init(dev, addr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
superio_handler(pc87309_t *dev)
|
|
{
|
|
if (dev->superio_base != 0x0000)
|
|
io_removehandler(dev->superio_base, 0x0002,
|
|
pc87309_read, NULL, NULL,
|
|
pc87309_write, NULL, NULL, dev);
|
|
|
|
switch (dev->regs[0x21] & 0x0b) {
|
|
default:
|
|
dev->superio_base = 0x0000;
|
|
break;
|
|
case 0x02:
|
|
case 0x08: case 0x0a:
|
|
dev->superio_base = 0x015c;
|
|
break;
|
|
case 0x03:
|
|
case 0x09: case 0x0b:
|
|
dev->superio_base = 0x002e;
|
|
break;
|
|
}
|
|
|
|
if (dev->superio_base != 0x0000) {
|
|
pc87309_log("Enabling Super I/O on %04X...\n", dev->superio_base);
|
|
io_sethandler(dev->superio_base, 0x0002,
|
|
pc87309_read, NULL, NULL,
|
|
pc87309_write, NULL, NULL, dev);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pc87309_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
pc87309_t *dev = (pc87309_t *) priv;
|
|
uint8_t ld = dev->regs[0x07];
|
|
uint8_t reg = dev->cur_reg - 0x30;
|
|
uint8_t index = (port & 1) ? 0 : 1;
|
|
uint8_t old = dev->regs[dev->cur_reg];
|
|
|
|
if (index) {
|
|
dev->cur_reg = val;
|
|
return;
|
|
} else {
|
|
#ifdef ENABLE_PC87309_LOG
|
|
if (dev->cur_reg >= 0x30)
|
|
pc87309_log("[%04X:%08X] [W] (%04X) %02X:%02X = %02X\n",
|
|
CS, cpu_state.pc, port, ld, dev->cur_reg, val);
|
|
else
|
|
pc87309_log("[%04X:%08X] [W] (%04X) %02X = %02X\n",
|
|
CS, cpu_state.pc, port, dev->cur_reg, val);
|
|
#endif
|
|
switch (dev->cur_reg) {
|
|
case 0x00:
|
|
case 0x02: case 0x03:
|
|
case 0x06: case 0x07:
|
|
dev->regs[dev->cur_reg] = val;
|
|
break;
|
|
case 0x21:
|
|
dev->regs[dev->cur_reg] = val;
|
|
fdc_toggle_flag(dev->fdc, FDC_FLAG_PS2_MCA, !(val & 0x04));
|
|
superio_handler(dev);
|
|
break;
|
|
case 0x22:
|
|
dev->regs[dev->cur_reg] = val;
|
|
break;
|
|
default:
|
|
if (dev->cur_reg >= 0x30)
|
|
old = dev->ld_regs[ld][reg];
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (dev->cur_reg) {
|
|
case 0x30:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD: case LD_MOUSE:
|
|
dev->ld_regs[ld][reg] = val;
|
|
kbc_handler(dev);
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
fdc_handler(dev);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = val;
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 1);
|
|
break;
|
|
case LD_UART1:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 0);
|
|
break;
|
|
case LD_PM:
|
|
dev->ld_regs[ld][reg] = val;
|
|
pm_handler(dev);
|
|
break;
|
|
}
|
|
break;
|
|
/* I/O Range Check. */
|
|
case 0x31:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_MIN ... LD_MAX:
|
|
if (ld != LD_MOUSE)
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
}
|
|
break;
|
|
/* Base Address 0 MSB. */
|
|
case 0x60:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD:
|
|
dev->ld_regs[ld][reg] = val;
|
|
kbc_handler(dev);
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
fdc_handler(dev);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = (old & 0xfc) | (val & 0x03);
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 1);
|
|
break;
|
|
case LD_UART1:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 0);
|
|
break;
|
|
case LD_PM:
|
|
dev->ld_regs[ld][reg] = val;
|
|
pm_handler(dev);
|
|
break;
|
|
}
|
|
break;
|
|
/* Base Address 0 LSB. */
|
|
case 0x61:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD:
|
|
dev->ld_regs[ld][reg] = (old & 0x04) | (val & 0xfb);
|
|
kbc_handler(dev);
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = (old & 0x07) | (val & 0xf8);
|
|
fdc_handler(dev);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = (old & 0x03) | (val & 0xfc);
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = (old & 0x07) | (val & 0xf8);
|
|
serial_handler(dev, 1);
|
|
break;
|
|
case LD_UART1:
|
|
dev->ld_regs[ld][reg] = (old & 0x07) | (val & 0xf8);
|
|
serial_handler(dev, 0);
|
|
break;
|
|
case LD_PM:
|
|
dev->ld_regs[ld][reg] = (old & 0x01) | (val & 0xfe);
|
|
pm_handler(dev);
|
|
break;
|
|
}
|
|
break;
|
|
/* Base Address 1 MSB (undocumented for Logical Device 7). */
|
|
case 0x62:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD:
|
|
dev->ld_regs[ld][reg] = val;
|
|
kbc_handler(dev);
|
|
break;
|
|
}
|
|
break;
|
|
/* Base Address 1 LSB (undocumented for Logical Device 7). */
|
|
case 0x63:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD:
|
|
dev->ld_regs[ld][reg] = (old & 0x04) | (val & 0xfb);
|
|
kbc_handler(dev);
|
|
break;
|
|
}
|
|
break;
|
|
/* Interrupt Select. */
|
|
case 0x70:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD: case LD_MOUSE:
|
|
dev->ld_regs[ld][reg] = val;
|
|
kbc_handler(dev);
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
fdc_handler(dev);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = val;
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 1);
|
|
break;
|
|
case LD_UART1:
|
|
dev->ld_regs[ld][reg] = val;
|
|
serial_handler(dev, 0);
|
|
break;
|
|
}
|
|
break;
|
|
/* Interrupt Type. */
|
|
case 0x71:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_MIN ... LD_MAX:
|
|
if ((ld == LD_KBD) || (ld == LD_MOUSE))
|
|
dev->ld_regs[ld][reg] = (old & 0xfc) | (val & 0x03);
|
|
else
|
|
dev->ld_regs[ld][reg] = (old & 0xfd) | (val & 0x02);
|
|
break;
|
|
}
|
|
break;
|
|
/* DMA Channel Select 0. */
|
|
case 0x74:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
fdc_handler(dev);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = val;
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
}
|
|
break;
|
|
/* DMA Channel Select 1. */
|
|
case 0x75:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_UART2:
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
}
|
|
break;
|
|
/* Configuration Register 0. */
|
|
case 0xf0:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_KBD:
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
fdc_update_densel_polarity(dev->fdc, (val & 0x20) ? 1 : 0);
|
|
fdc_update_enh_mode(dev->fdc, (val & 0x40) ? 1 : 0);
|
|
break;
|
|
case LD_LPT:
|
|
dev->ld_regs[ld][reg] = val;
|
|
lpt1_handler(dev);
|
|
break;
|
|
case LD_UART2: case LD_UART1:
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
}
|
|
break;
|
|
/* Configuration Register 1. */
|
|
case 0xf1:
|
|
switch (ld) {
|
|
default:
|
|
break;
|
|
case LD_FDC:
|
|
dev->ld_regs[ld][reg] = val;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint8_t
|
|
pc87309_read(uint16_t port, void *priv)
|
|
{
|
|
const pc87309_t *dev = (pc87309_t *) priv;
|
|
uint8_t ld = dev->regs[0x07];
|
|
uint8_t reg = dev->cur_reg - 0x30;
|
|
uint8_t index = (port & 1) ? 0 : 1;
|
|
uint8_t ret = 0xff;
|
|
|
|
if (index)
|
|
ret = dev->cur_reg;
|
|
else {
|
|
if (dev->cur_reg >= 0x30)
|
|
ret = dev->ld_regs[ld][reg];
|
|
/* Write-only registers. */
|
|
else if ((dev->cur_reg == 0x00) ||
|
|
(dev->cur_reg == 0x02) || (dev->cur_reg == 0x03))
|
|
ret = 0x00;
|
|
else
|
|
ret = dev->regs[dev->cur_reg];
|
|
#ifdef ENABLE_PC87309_LOG
|
|
if (dev->cur_reg >= 0x30)
|
|
pc87309_log("[%04X:%08X] [R] (%04X) %02X:%02X = %02X\n",
|
|
CS, cpu_state.pc, port, ld, dev->cur_reg, ret);
|
|
else
|
|
pc87309_log("[%04X:%08X] [R] (%04X) %02X = %02X\n",
|
|
CS, cpu_state.pc, port, dev->cur_reg, ret);
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
pc87309_reset(void *priv)
|
|
{
|
|
pc87309_t *dev = (pc87309_t *) priv;
|
|
|
|
memset(dev->regs, 0x00, 0x30);
|
|
for (uint16_t i = 0; i < 256; i++)
|
|
memset(dev->ld_regs[i], 0x00, 0xd0);
|
|
memset(dev->pm, 0x00, 0x08);
|
|
|
|
dev->regs[0x20] = dev->id;
|
|
dev->regs[0x21] = 0x04 | dev->baddr;
|
|
|
|
dev->ld_regs[LD_KBD ][0x00] = 0x01;
|
|
dev->ld_regs[LD_KBD ][0x31] = 0x60;
|
|
dev->ld_regs[LD_KBD ][0x33] = 0x64;
|
|
dev->ld_regs[LD_KBD ][0x40] = 0x01;
|
|
dev->ld_regs[LD_KBD ][0x41] = 0x02;
|
|
dev->ld_regs[LD_KBD ][0x44] = 0x04;
|
|
dev->ld_regs[LD_KBD ][0x45] = 0x04;
|
|
dev->ld_regs[LD_KBD ][0xc0] = 0x40;
|
|
|
|
dev->ld_regs[LD_MOUSE][0x40] = 0x0c;
|
|
dev->ld_regs[LD_MOUSE][0x41] = 0x02;
|
|
dev->ld_regs[LD_MOUSE][0x44] = 0x04;
|
|
dev->ld_regs[LD_MOUSE][0x45] = 0x04;
|
|
|
|
dev->ld_regs[LD_FDC ][0x01] = 0x01;
|
|
dev->ld_regs[LD_FDC ][0x30] = 0x03;
|
|
dev->ld_regs[LD_FDC ][0x31] = 0xf0;
|
|
dev->ld_regs[LD_FDC ][0x32] = 0x03;
|
|
dev->ld_regs[LD_FDC ][0x33] = 0xf7;
|
|
dev->ld_regs[LD_FDC ][0x40] = 0x06;
|
|
dev->ld_regs[LD_FDC ][0x41] = 0x03;
|
|
dev->ld_regs[LD_FDC ][0x44] = 0x02;
|
|
dev->ld_regs[LD_FDC ][0x45] = 0x04;
|
|
dev->ld_regs[LD_FDC ][0xc0] = 0x02;
|
|
|
|
dev->ld_regs[LD_LPT ][0x30] = 0x02;
|
|
dev->ld_regs[LD_LPT ][0x31] = 0x78;
|
|
dev->ld_regs[LD_LPT ][0x40] = 0x07;
|
|
dev->ld_regs[LD_LPT ][0x44] = 0x04;
|
|
dev->ld_regs[LD_LPT ][0x45] = 0x04;
|
|
dev->ld_regs[LD_LPT ][0xc0] = 0xf2;
|
|
|
|
dev->ld_regs[LD_UART2][0x30] = 0x02;
|
|
dev->ld_regs[LD_UART2][0x31] = 0xf8;
|
|
dev->ld_regs[LD_UART2][0x40] = 0x03;
|
|
dev->ld_regs[LD_UART2][0x41] = 0x03;
|
|
dev->ld_regs[LD_UART2][0x44] = 0x04;
|
|
dev->ld_regs[LD_UART2][0x45] = 0x04;
|
|
dev->ld_regs[LD_UART2][0xc0] = 0x02;
|
|
|
|
dev->ld_regs[LD_UART1][0x30] = 0x03;
|
|
dev->ld_regs[LD_UART1][0x31] = 0xf8;
|
|
dev->ld_regs[LD_UART1][0x40] = 0x04;
|
|
dev->ld_regs[LD_UART1][0x41] = 0x03;
|
|
dev->ld_regs[LD_UART1][0x44] = 0x04;
|
|
dev->ld_regs[LD_UART1][0x45] = 0x04;
|
|
dev->ld_regs[LD_UART1][0xc0] = 0x02;
|
|
|
|
dev->ld_regs[LD_PM ][0x44] = 0x04;
|
|
dev->ld_regs[LD_PM ][0x45] = 0x04;
|
|
|
|
dev->pm[0] = 0x79;
|
|
dev->pm[4] = 0x0e;
|
|
|
|
dev->pm_base = 0xffff;
|
|
|
|
/*
|
|
0 = 360 rpm @ 500 kbps for 3.5"
|
|
1 = Default, 300 rpm @ 500, 300, 250, 1000 kbps for 3.5"
|
|
*/
|
|
fdc_toggle_flag(dev->fdc, FDC_FLAG_PS2_MCA, 0);
|
|
fdc_reset(dev->fdc);
|
|
|
|
kbc_handler(dev);
|
|
fdc_handler(dev);
|
|
lpt1_handler(dev);
|
|
serial_handler(dev, 0);
|
|
serial_handler(dev, 1);
|
|
pm_handler(dev);
|
|
superio_handler(dev);
|
|
}
|
|
|
|
static void
|
|
pc87309_close(void *priv)
|
|
{
|
|
pc87309_t *dev = (pc87309_t *) priv;
|
|
|
|
free(dev);
|
|
}
|
|
|
|
static void *
|
|
pc87309_init(const device_t *info)
|
|
{
|
|
pc87309_t *dev = (pc87309_t *) calloc(1, sizeof(pc87309_t));
|
|
|
|
dev->id = info->local & 0xff;
|
|
|
|
dev->fdc = device_add(&fdc_at_nsc_device);
|
|
|
|
dev->uart[0] = device_add_inst(&ns16550_device, 1);
|
|
dev->uart[1] = device_add_inst(&ns16550_device, 2);
|
|
|
|
switch (info->local & PCX730X_KBC) {
|
|
default:
|
|
case PCX730X_AMI:
|
|
dev->kbc = device_add(&keyboard_ps2_intel_ami_pci_device);
|
|
break;
|
|
/* Optiplex! */
|
|
case PCX730X_PHOENIX_42:
|
|
dev->kbc = device_add(&keyboard_ps2_phoenix_device);
|
|
break;
|
|
case PCX730X_PHOENIX_42I:
|
|
dev->kbc = device_add(&keyboard_ps2_phoenix_pci_device);
|
|
break;
|
|
}
|
|
|
|
if (info->local & PCX730X_15C)
|
|
dev->baddr = 0x0a;
|
|
else
|
|
dev->baddr = 0x0b;
|
|
|
|
pc87309_reset(dev);
|
|
|
|
return dev;
|
|
}
|
|
|
|
const device_t pc87309_device = {
|
|
.name = "National Semiconductor PC87309 Super I/O",
|
|
.internal_name = "pc87309",
|
|
.flags = 0,
|
|
.local = 0,
|
|
.init = pc87309_init,
|
|
.close = pc87309_close,
|
|
.reset = pc87309_reset,
|
|
.available = NULL,
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = NULL
|
|
};
|