diff options
-rw-r--r-- | default-configs/pci.mak | 1 | ||||
-rw-r--r-- | hw/Makefile.objs | 3 | ||||
-rw-r--r-- | hw/ipack.c | 115 | ||||
-rw-r--r-- | hw/ipack.h | 79 | ||||
-rw-r--r-- | hw/pci/pci_ids.h | 3 | ||||
-rw-r--r-- | hw/tpci200.c | 671 |
6 files changed, 872 insertions, 0 deletions
diff --git a/default-configs/pci.mak b/default-configs/pci.mak index ae9d1eb487..ee2d18d5f2 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -21,3 +21,4 @@ CONFIG_ESP=y CONFIG_ESP_PCI=y CONFIG_SERIAL=y CONFIG_SERIAL_PCI=y +CONFIG_IPACK=y diff --git a/hw/Makefile.objs b/hw/Makefile.objs index aa55ce9873..47d47864fb 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -106,6 +106,9 @@ common-obj-$(CONFIG_XGMAC) += xgmac.o # PCI watchdog devices common-obj-$(CONFIG_PCI) += wdt_i6300esb.o +# IndustryPack +common-obj-$(CONFIG_IPACK) += tpci200.o ipack.o + # PCI network cards common-obj-$(CONFIG_NE2000_PCI) += ne2000.o common-obj-$(CONFIG_EEPRO100_PCI) += eepro100.o diff --git a/hw/ipack.c b/hw/ipack.c new file mode 100644 index 0000000000..e15540d5cd --- /dev/null +++ b/hw/ipack.c @@ -0,0 +1,115 @@ +/* + * QEMU IndustryPack emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia <agarcia@igalia.com> + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#include "ipack.h" + +IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &BUS(bus)->children, sibling) { + DeviceState *qdev = kid->child; + IPackDevice *ip = IPACK_DEVICE(qdev); + if (ip->slot == slot) { + return ip; + } + } + return NULL; +} + +void ipack_bus_new_inplace(IPackBus *bus, DeviceState *parent, + const char *name, uint8_t n_slots, + qemu_irq_handler handler) +{ + qbus_create_inplace(&bus->qbus, TYPE_IPACK_BUS, parent, name); + bus->n_slots = n_slots; + bus->set_irq = handler; +} + +static int ipack_device_dev_init(DeviceState *qdev) +{ + IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(qdev)); + IPackDevice *dev = IPACK_DEVICE(qdev); + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev); + + if (dev->slot < 0) { + dev->slot = bus->free_slot; + } + if (dev->slot >= bus->n_slots) { + return -1; + } + bus->free_slot = dev->slot + 1; + + dev->irq = qemu_allocate_irqs(bus->set_irq, dev, 2); + + return k->init(dev); +} + +static int ipack_device_dev_exit(DeviceState *qdev) +{ + IPackDevice *dev = IPACK_DEVICE(qdev); + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev); + + if (k->exit) { + k->exit(dev); + } + + qemu_free_irqs(dev->irq); + + return 0; +} + +static Property ipack_device_props[] = { + DEFINE_PROP_INT32("slot", IPackDevice, slot, -1), + DEFINE_PROP_END_OF_LIST() +}; + +static void ipack_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_type = TYPE_IPACK_BUS; + k->init = ipack_device_dev_init; + k->exit = ipack_device_dev_exit; + k->props = ipack_device_props; +} + +const VMStateDescription vmstate_ipack_device = { + .name = "ipack_device", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(slot, IPackDevice), + VMSTATE_END_OF_LIST() + } +}; + +static const TypeInfo ipack_device_info = { + .name = TYPE_IPACK_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(IPackDevice), + .class_size = sizeof(IPackDeviceClass), + .class_init = ipack_device_class_init, + .abstract = true, +}; + +static const TypeInfo ipack_bus_info = { + .name = TYPE_IPACK_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(IPackBus), +}; + +static void ipack_register_types(void) +{ + type_register_static(&ipack_device_info); + type_register_static(&ipack_bus_info); +} + +type_init(ipack_register_types) diff --git a/hw/ipack.h b/hw/ipack.h new file mode 100644 index 0000000000..69e26282d3 --- /dev/null +++ b/hw/ipack.h @@ -0,0 +1,79 @@ +/* + * QEMU IndustryPack emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia <agarcia@igalia.com> + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#ifndef QEMU_IPACK_H +#define QEMU_IPACK_H + +#include "qdev.h" + +typedef struct IPackBus IPackBus; + +#define TYPE_IPACK_BUS "IndustryPack" +#define IPACK_BUS(obj) OBJECT_CHECK(IPackBus, (obj), TYPE_IPACK_BUS) + +struct IPackBus { + BusState qbus; + /* All fields are private */ + uint8_t n_slots; + uint8_t free_slot; + qemu_irq_handler set_irq; +}; + +typedef struct IPackDevice IPackDevice; +typedef struct IPackDeviceClass IPackDeviceClass; + +#define TYPE_IPACK_DEVICE "ipack-device" +#define IPACK_DEVICE(obj) \ + OBJECT_CHECK(IPackDevice, (obj), TYPE_IPACK_DEVICE) +#define IPACK_DEVICE_CLASS(klass) \ + OBJECT_CLASS_CHECK(IPackDeviceClass, (klass), TYPE_IPACK_DEVICE) +#define IPACK_DEVICE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(IPackDeviceClass, (obj), TYPE_IPACK_DEVICE) + +struct IPackDeviceClass { + DeviceClass parent_class; + + int (*init)(IPackDevice *dev); + int (*exit)(IPackDevice *dev); + + uint16_t (*io_read)(IPackDevice *dev, uint8_t addr); + void (*io_write)(IPackDevice *dev, uint8_t addr, uint16_t val); + + uint16_t (*id_read)(IPackDevice *dev, uint8_t addr); + void (*id_write)(IPackDevice *dev, uint8_t addr, uint16_t val); + + uint16_t (*int_read)(IPackDevice *dev, uint8_t addr); + void (*int_write)(IPackDevice *dev, uint8_t addr, uint16_t val); + + uint16_t (*mem_read16)(IPackDevice *dev, uint32_t addr); + void (*mem_write16)(IPackDevice *dev, uint32_t addr, uint16_t val); + + uint8_t (*mem_read8)(IPackDevice *dev, uint32_t addr); + void (*mem_write8)(IPackDevice *dev, uint32_t addr, uint8_t val); +}; + +struct IPackDevice { + DeviceState qdev; + int32_t slot; + /* IRQ objects for the IndustryPack INT0# and INT1# */ + qemu_irq *irq; +}; + +extern const VMStateDescription vmstate_ipack_device; + +#define VMSTATE_IPACK_DEVICE(_field, _state) \ + VMSTATE_STRUCT(_field, _state, 1, vmstate_ipack_device, IPackDevice) + +IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot); +void ipack_bus_new_inplace(IPackBus *bus, DeviceState *parent, + const char *name, uint8_t n_slots, + qemu_irq_handler handler); + +#endif diff --git a/hw/pci/pci_ids.h b/hw/pci/pci_ids.h index 271d935bc7..d8dc2f1bf7 100644 --- a/hw/pci/pci_ids.h +++ b/hw/pci/pci_ids.h @@ -148,4 +148,7 @@ #define PCI_VENDOR_ID_NEC 0x1033 #define PCI_DEVICE_ID_NEC_UPD720200 0x0194 +#define PCI_VENDOR_ID_TEWS 0x1498 +#define PCI_DEVICE_ID_TEWS_TPCI200 0x30C8 + #endif diff --git a/hw/tpci200.c b/hw/tpci200.c new file mode 100644 index 0000000000..e082bca965 --- /dev/null +++ b/hw/tpci200.c @@ -0,0 +1,671 @@ +/* + * QEMU TEWS TPCI200 IndustryPack carrier emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia <agarcia@igalia.com> + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#include "ipack.h" +#include "pci/pci.h" +#include "qemu/bitops.h" +#include <stdio.h> + +/* #define DEBUG_TPCI */ + +#ifdef DEBUG_TPCI +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, "TPCI200: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +#define N_MODULES 4 + +#define IP_ID_SPACE 2 +#define IP_INT_SPACE 3 +#define IP_IO_SPACE_ADDR_MASK 0x7F +#define IP_ID_SPACE_ADDR_MASK 0x3F +#define IP_INT_SPACE_ADDR_MASK 0x3F + +#define STATUS_INT(IP, INTNO) BIT((IP) * 2 + (INTNO)) +#define STATUS_TIMEOUT(IP) BIT((IP) + 12) +#define STATUS_ERR_ANY 0xF00 + +#define CTRL_CLKRATE BIT(0) +#define CTRL_RECOVER BIT(1) +#define CTRL_TIME_INT BIT(2) +#define CTRL_ERR_INT BIT(3) +#define CTRL_INT_EDGE(INTNO) BIT(4 + (INTNO)) +#define CTRL_INT(INTNO) BIT(6 + (INTNO)) + +#define REG_REV_ID 0x00 +#define REG_IP_A_CTRL 0x02 +#define REG_IP_B_CTRL 0x04 +#define REG_IP_C_CTRL 0x06 +#define REG_IP_D_CTRL 0x08 +#define REG_RESET 0x0A +#define REG_STATUS 0x0C +#define IP_N_FROM_REG(REG) ((REG) / 2 - 1) + +typedef struct { + PCIDevice dev; + IPackBus bus; + MemoryRegion mmio; + MemoryRegion io; + MemoryRegion las0; + MemoryRegion las1; + MemoryRegion las2; + MemoryRegion las3; + bool big_endian[3]; + uint8_t ctrl[N_MODULES]; + uint16_t status; + uint8_t int_set; +} TPCI200State; + +#define TYPE_TPCI200 "tpci200" + +#define TPCI200(obj) \ + OBJECT_CHECK(TPCI200State, (obj), TYPE_TPCI200) + +static const uint8_t local_config_regs[] = { + 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xFC, 0xFF, 0x0F, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x08, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x60, 0x41, 0xD4, + 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x01, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x08, 0x01, 0x02, + 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x80, 0x02, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x00, 0x52, 0x92, 0x24, 0x02 +}; + +static void adjust_addr(bool big_endian, hwaddr *addr, unsigned size) +{ + /* During 8 bit access in big endian mode, + odd and even addresses are swapped */ + if (big_endian && size == 1) { + *addr ^= 1; + } +} + +static uint64_t adjust_value(bool big_endian, uint64_t *val, unsigned size) +{ + /* Local spaces only support 8/16 bit access, + * so there's no need to care for sizes > 2 */ + if (big_endian && size == 2) { + *val = bswap16(*val); + } + return *val; +} + +static void tpci200_set_irq(void *opaque, int intno, int level) +{ + IPackDevice *ip = opaque; + IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(DEVICE(ip))); + PCIDevice *pcidev = PCI_DEVICE(BUS(bus)->parent); + TPCI200State *dev = TPCI200(pcidev); + unsigned ip_n = ip->slot; + uint16_t prev_status = dev->status; + + assert(ip->slot >= 0 && ip->slot < N_MODULES); + + /* The requested interrupt must be enabled in the IP CONTROL + * register */ + if (!(dev->ctrl[ip_n] & CTRL_INT(intno))) { + return; + } + + /* Update the interrupt status in the IP STATUS register */ + if (level) { + dev->status |= STATUS_INT(ip_n, intno); + } else { + dev->status &= ~STATUS_INT(ip_n, intno); + } + + /* Return if there are no changes */ + if (dev->status == prev_status) { + return; + } + + DPRINTF("IP %u INT%u#: %u\n", ip_n, intno, level); + + /* Check if the interrupt is edge sensitive */ + if (dev->ctrl[ip_n] & CTRL_INT_EDGE(intno)) { + if (level) { + qemu_set_irq(dev->dev.irq[0], !dev->int_set); + qemu_set_irq(dev->dev.irq[0], dev->int_set); + } + } else { + unsigned i, j; + uint16_t level_status = dev->status; + + /* Check if there are any level sensitive interrupts set by + removing the ones that are edge sensitive from the status + register */ + for (i = 0; i < N_MODULES; i++) { + for (j = 0; j < 2; j++) { + if (dev->ctrl[i] & CTRL_INT_EDGE(j)) { + level_status &= ~STATUS_INT(i, j); + } + } + } + + if (level_status && !dev->int_set) { + qemu_irq_raise(dev->dev.irq[0]); + dev->int_set = 1; + } else if (!level_status && dev->int_set) { + qemu_irq_lower(dev->dev.irq[0]); + dev->int_set = 0; + } + } +} + +static uint64_t tpci200_read_cfg(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + uint8_t ret = 0; + if (addr < ARRAY_SIZE(local_config_regs)) { + ret = local_config_regs[addr]; + } + /* Endianness is stored in the first bit of these registers */ + if ((addr == 0x2b && s->big_endian[0]) || + (addr == 0x2f && s->big_endian[1]) || + (addr == 0x33 && s->big_endian[2])) { + ret |= 1; + } + DPRINTF("Read from LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) ret); + return ret; +} + +static void tpci200_write_cfg(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + /* Endianness is stored in the first bit of these registers */ + if (addr == 0x2b || addr == 0x2f || addr == 0x33) { + unsigned las = (addr - 0x2b) / 4; + s->big_endian[las] = val & 1; + DPRINTF("LAS%u big endian mode: %u\n", las, (unsigned) val & 1); + } else { + DPRINTF("Write to LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) val); + } +} + +static uint64_t tpci200_read_las0(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + uint64_t ret = 0; + + switch (addr) { + + case REG_REV_ID: + DPRINTF("Read REVISION ID\n"); /* Current value is 0x00 */ + break; + + case REG_IP_A_CTRL: + case REG_IP_B_CTRL: + case REG_IP_C_CTRL: + case REG_IP_D_CTRL: + { + unsigned ip_n = IP_N_FROM_REG(addr); + ret = s->ctrl[ip_n]; + DPRINTF("Read IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) ret); + } + break; + + case REG_RESET: + DPRINTF("Read RESET\n"); /* Not implemented */ + break; + + case REG_STATUS: + ret = s->status; + DPRINTF("Read STATUS: 0x%x\n", (unsigned) ret); + break; + + /* Reserved */ + default: + DPRINTF("Unsupported read from LAS0 0x%x\n", (unsigned) addr); + break; + } + + return adjust_value(s->big_endian[0], &ret, size); +} + +static void tpci200_write_las0(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + + adjust_value(s->big_endian[0], &val, size); + + switch (addr) { + + case REG_REV_ID: + DPRINTF("Write Revision ID: 0x%x\n", (unsigned) val); /* No effect */ + break; + + case REG_IP_A_CTRL: + case REG_IP_B_CTRL: + case REG_IP_C_CTRL: + case REG_IP_D_CTRL: + { + unsigned ip_n = IP_N_FROM_REG(addr); + s->ctrl[ip_n] = val; + DPRINTF("Write IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) val); + } + break; + + case REG_RESET: + DPRINTF("Write RESET: 0x%x\n", (unsigned) val); /* Not implemented */ + break; + + case REG_STATUS: + { + unsigned i; + + for (i = 0; i < N_MODULES; i++) { + IPackDevice *ip = ipack_device_find(&s->bus, i); + + if (ip != NULL) { + if (val & STATUS_INT(i, 0)) { + DPRINTF("Clear IP %c INT0# status\n", 'A' + i); + qemu_irq_lower(ip->irq[0]); + } + if (val & STATUS_INT(i, 1)) { + DPRINTF("Clear IP %c INT1# status\n", 'A' + i); + qemu_irq_lower(ip->irq[1]); + } + } + + if (val & STATUS_TIMEOUT(i)) { + DPRINTF("Clear IP %c timeout\n", 'A' + i); + s->status &= ~STATUS_TIMEOUT(i); + } + } + + if (val & STATUS_ERR_ANY) { + DPRINTF("Unexpected write to STATUS register: 0x%x\n", + (unsigned) val); + } + } + break; + + /* Reserved */ + default: + DPRINTF("Unsupported write to LAS0 0x%x: 0x%x\n", + (unsigned) addr, (unsigned) val); + break; + } +} + +static uint64_t tpci200_read_las1(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + unsigned ip_n, space; + uint8_t offset; + + adjust_addr(s->big_endian[1], &addr, size); + + /* + * The address is divided into the IP module number (0-4), the IP + * address space (I/O, ID, INT) and the offset within that space. + */ + ip_n = addr >> 8; + space = (addr >> 6) & 3; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS1: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + switch (space) { + + case IP_ID_SPACE: + offset = addr & IP_ID_SPACE_ADDR_MASK; + if (k->id_read) { + ret = k->id_read(ip, offset); + } + break; + + case IP_INT_SPACE: + offset = addr & IP_INT_SPACE_ADDR_MASK; + + /* Read address 0 to ACK IP INT0# and address 2 to ACK IP INT1# */ + if (offset == 0 || offset == 2) { + unsigned intno = offset / 2; + bool int_set = s->status & STATUS_INT(ip_n, intno); + bool int_edge_sensitive = s->ctrl[ip_n] & CTRL_INT_EDGE(intno); + if (int_set && !int_edge_sensitive) { + qemu_irq_lower(ip->irq[intno]); + } + } + + if (k->int_read) { + ret = k->int_read(ip, offset); + } + break; + + default: + offset = addr & IP_IO_SPACE_ADDR_MASK; + if (k->io_read) { + ret = k->io_read(ip, offset); + } + break; + } + } + + return adjust_value(s->big_endian[1], &ret, size); +} + +static void tpci200_write_las1(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + unsigned ip_n, space; + uint8_t offset; + + adjust_addr(s->big_endian[1], &addr, size); + adjust_value(s->big_endian[1], &val, size); + + /* + * The address is divided into the IP module number, the IP + * address space (I/O, ID, INT) and the offset within that space. + */ + ip_n = addr >> 8; + space = (addr >> 6) & 3; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS1: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + switch (space) { + + case IP_ID_SPACE: + offset = addr & IP_ID_SPACE_ADDR_MASK; + if (k->id_write) { + k->id_write(ip, offset, val); + } + break; + + case IP_INT_SPACE: + offset = addr & IP_INT_SPACE_ADDR_MASK; + if (k->int_write) { + k->int_write(ip, offset, val); + } + break; + + default: + offset = addr & IP_IO_SPACE_ADDR_MASK; + if (k->io_write) { + k->io_write(ip, offset, val); + } + break; + } + } +} + +static uint64_t tpci200_read_las2(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + unsigned ip_n; + uint32_t offset; + + adjust_addr(s->big_endian[2], &addr, size); + + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + ip_n = addr >> 23; + offset = addr & 0x7fffff; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS2: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_read16) { + ret = k->mem_read16(ip, offset); + } + } + + return adjust_value(s->big_endian[2], &ret, size); +} + +static void tpci200_write_las2(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + unsigned ip_n; + uint32_t offset; + + adjust_addr(s->big_endian[2], &addr, size); + adjust_value(s->big_endian[2], &val, size); + + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + ip_n = addr >> 23; + offset = addr & 0x7fffff; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS2: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_write16) { + k->mem_write16(ip, offset, val); + } + } +} + +static uint64_t tpci200_read_las3(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + unsigned ip_n = addr >> 22; + uint32_t offset = addr & 0x3fffff; + + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS3: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_read8) { + ret = k->mem_read8(ip, offset); + } + } + + return ret; +} + +static void tpci200_write_las3(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + unsigned ip_n = addr >> 22; + uint32_t offset = addr & 0x3fffff; + + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS3: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_write8) { + k->mem_write8(ip, offset, val); + } + } +} + +static const MemoryRegionOps tpci200_cfg_ops = { + .read = tpci200_read_cfg, + .write = tpci200_write_cfg, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + }, + .impl = { + .min_access_size = 1, + .max_access_size = 1 + } +}; + +static const MemoryRegionOps tpci200_las0_ops = { + .read = tpci200_read_las0, + .write = tpci200_write_las0, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 2, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las1_ops = { + .read = tpci200_read_las1, + .write = tpci200_write_las1, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las2_ops = { + .read = tpci200_read_las2, + .write = tpci200_write_las2, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las3_ops = { + .read = tpci200_read_las3, + .write = tpci200_write_las3, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 1 + } +}; + +static int tpci200_initfn(PCIDevice *pci_dev) +{ + TPCI200State *s = TPCI200(pci_dev); + uint8_t *c = s->dev.config; + + pci_set_word(c + PCI_COMMAND, 0x0003); + pci_set_word(c + PCI_STATUS, 0x0280); + + pci_set_byte(c + PCI_INTERRUPT_PIN, 0x01); /* Interrupt pin A */ + + pci_set_byte(c + PCI_CAPABILITY_LIST, 0x40); + pci_set_long(c + 0x40, 0x48014801); + pci_set_long(c + 0x48, 0x00024C06); + pci_set_long(c + 0x4C, 0x00000003); + + memory_region_init_io(&s->mmio, &tpci200_cfg_ops, + s, "tpci200_mmio", 128); + memory_region_init_io(&s->io, &tpci200_cfg_ops, + s, "tpci200_io", 128); + memory_region_init_io(&s->las0, &tpci200_las0_ops, + s, "tpci200_las0", 256); + memory_region_init_io(&s->las1, &tpci200_las1_ops, + s, "tpci200_las1", 1024); + memory_region_init_io(&s->las2, &tpci200_las2_ops, + s, "tpci200_las2", 1024*1024*32); + memory_region_init_io(&s->las3, &tpci200_las3_ops, + s, "tpci200_las3", 1024*1024*16); + pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio); + pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io); + pci_register_bar(&s->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las0); + pci_register_bar(&s->dev, 3, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las1); + pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las2); + pci_register_bar(&s->dev, 5, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las3); + + ipack_bus_new_inplace(&s->bus, DEVICE(&s->dev), NULL, + N_MODULES, tpci200_set_irq); + + return 0; +} + +static void tpci200_exitfn(PCIDevice *pci_dev) +{ + TPCI200State *s = TPCI200(pci_dev); + + memory_region_destroy(&s->mmio); + memory_region_destroy(&s->io); + memory_region_destroy(&s->las0); + memory_region_destroy(&s->las1); + memory_region_destroy(&s->las2); + memory_region_destroy(&s->las3); +} + +static const VMStateDescription vmstate_tpci200 = { + .name = "tpci200", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, TPCI200State), + VMSTATE_BOOL_ARRAY(big_endian, TPCI200State, 3), + VMSTATE_UINT8_ARRAY(ctrl, TPCI200State, N_MODULES), + VMSTATE_UINT16(status, TPCI200State), + VMSTATE_UINT8(int_set, TPCI200State), + VMSTATE_END_OF_LIST() + } +}; + +static void tpci200_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = tpci200_initfn; + k->exit = tpci200_exitfn; + k->vendor_id = PCI_VENDOR_ID_TEWS; + k->device_id = PCI_DEVICE_ID_TEWS_TPCI200; + k->class_id = PCI_CLASS_BRIDGE_OTHER; + k->subsystem_vendor_id = PCI_VENDOR_ID_TEWS; + k->subsystem_id = 0x300A; + dc->desc = "TEWS TPCI200 IndustryPack carrier"; + dc->vmsd = &vmstate_tpci200; +} + +static const TypeInfo tpci200_info = { + .name = TYPE_TPCI200, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(TPCI200State), + .class_init = tpci200_class_init, +}; + +static void tpci200_register_types(void) +{ + type_register_static(&tpci200_info); +} + +type_init(tpci200_register_types) |