diff --git a/MAINTAINERS b/MAINTAINERS index 6e7890ce8222..ee879492349c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -964,6 +964,14 @@ F: include/hw/*/nrf51*.h F: include/hw/*/microbit*.h F: tests/qtest/microbit-test.c +TI Hercules +M: Benjamin Kamath +M: Andrey Smirnov +L: qemu-arm@nongnu.org +S: Maintained +F: hw/*/hercules_* +F: include/hw/*/hercules_* + CRIS Machines ------------- Axis Dev88 diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index 8fc09a4a5103..9a31947b2f6e 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -19,6 +19,7 @@ CONFIG_SX1=y CONFIG_NSERIES=y CONFIG_STELLARIS=y CONFIG_REALVIEW=y +CONFIG_HERCULES=y CONFIG_VERSATILE=y CONFIG_VEXPRESS=y CONFIG_ZYNQ=y diff --git a/hw/adc/Makefile.objs b/hw/adc/Makefile.objs index 2b9dc36c7f94..7756439bdd4d 100644 --- a/hw/adc/Makefile.objs +++ b/hw/adc/Makefile.objs @@ -1 +1,2 @@ common-obj-$(CONFIG_STM32F2XX_ADC) += stm32f2xx_adc.o +common-obj-$(CONFIG_HERCULES) += hercules_mibadc.o diff --git a/hw/adc/hercules_mibadc.c b/hw/adc/hercules_mibadc.c new file mode 100644 index 000000000000..08cd96d361b1 --- /dev/null +++ b/hw/adc/hercules_mibadc.c @@ -0,0 +1,396 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/ptimer.h" +#include "qemu/main-loop.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" + +#include "hw/adc/hercules_mibadc.h" +#include "hw/arm/hercules.h" + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesMibAdcRegisters { + ADOPMODECR = 0x004, + _10_12_BIT = BIT(31), + COS = BIT(24), + ADEVINTFLG = 0x34, + ADG1INTFLG = 0x38, + ADG2INTFLG = 0x3C, + ADGxINTFLG_END = BIT(3), + ADG1THRINTCR = 0x44, + ADG2THRINTCR = 0x48, + ADBNDCR = 0x58, + ADBNDEND = 0x5C, + ADEVSR = 0x6C, + ADG1SR = 0x70, + ADG2SR = 0x74, + ADGxSR_END = BIT(0), + ADEVSEL = 0x78, + ADG1SEL = 0x7C, + ADG2SEL = 0x80, + ADG1BUFFER0 = 0xB0, + ADG1BUFFER7 = 0xCC, + ADG2BUFFER0 = 0xD0, + ADG2BUFFER7 = 0xEC, + + ADPARCR = 0x180, + TEST = BIT(8), + ADPARADDR = 0x184, +}; + +enum { + HERCULES_MIBADC_CONTAINER_SIZE = 8 * 1024, + HERCULES_MIBADC_RAM_OFFSET = 0, + HERCULES_MIBADC_ECC_OFFSET = HERCULES_MIBADC_CONTAINER_SIZE / 2, + + HERCULES_MIBADC_REGS_SIZE = 512, +}; + +#define Gx_EMPTY(adopmodecr) ((adopmodecr & _10_12_BIT) ? BIT(31) : BIT(15)) +static bool hercules_mibadc_group_invalid(HerculesMibAdcGroup *group) +{ + if (group->start >= group->end) { + return true; + } + + return false; +} + +static void hercules_mibadc_group_reset(HerculesMibAdcGroup *group) +{ + group->rdidx = group->wridx = group->start; +} + +static void hercules_mibadc_push_result(HerculesMibAdcState *s, + HerculesMibAdcGroup *group, + uint32_t chid, + uint32_t result) +{ + unsigned int start; + + if (hercules_mibadc_group_invalid(group)) { + return; + } + + if (group->wridx == group->end) { + /* + * Results RAM is full. Ingore new result + */ + return; + } + + start = (s->adopmodecr & _10_12_BIT) ? 16 : 10; + s->results[group->wridx++] = deposit32(result, start, 5, chid); +} + +static uint32_t hercules_mibadc_pop_result(HerculesMibAdcState *s, + HerculesMibAdcGroup *group) +{ + const uint32_t empty = Gx_EMPTY(s->adopmodecr); + uint32_t result; + + if (hercules_mibadc_group_invalid(group)) { + return empty; + } + + if (group->wridx == group->start) { + /* + * Results RAM is empty + */ + return empty; + } + + result = s->results[group->rdidx]; + s->results[group->rdidx++] = empty; + + if (group->rdidx == group->wridx) { + /* + * All results were read out we can rewind results RAM indices + */ + hercules_mibadc_group_reset(group); + } + + return result; +} + +static void hercules_mibadc_do_conversion(HerculesMibAdcState *s, + HerculesMibAdcGroup *group) +{ + unsigned long sel = group->sel; + uint32_t chid; + + for_each_set_bit(chid, &sel, ARRAY_SIZE(s->channel)) { + hercules_mibadc_push_result(s, group, chid, s->channel[chid]); + } + + group->sr |= ADGxSR_END; + group->intflg |= ADGxINTFLG_END; +} + +#define IDX(o, s) (((o) - (s)) / sizeof(uint32_t)) + +static void hercules_mibadc_ram_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + HerculesMibAdcState *s = opaque; + + s->results[IDX(offset, 0)] = val; +} + +static uint64_t hercules_mibadc_ram_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibAdcState *s = opaque; + unsigned int idx = IDX(offset, 0); + + if (s->ecc[idx]) { + /* + * TODO this isn't how real HW would do it, but its enough to + * pass our ADC error signalling functionality test + */ + s->adparaddr = offset; + qemu_irq_raise(s->parity_error); + } + + return s->results[idx]; +} + +static void hercules_mibadc_ecc_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + HerculesMibAdcState *s = opaque; + + if (s->adparcr & TEST) { + s->ecc[IDX(offset, 0)] = val; + } +} + +static uint64_t hercules_mibadc_ecc_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibAdcState *s = opaque; + + if (s->adparcr & TEST) { + return s->ecc[IDX(offset, 0)]; + } + + return 0; +} + +static uint64_t hercules_mibadc_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibAdcState *s = opaque; + + switch (offset) { + case ADOPMODECR: + return s->adopmodecr | COS; + case ADEVINTFLG ... ADG2INTFLG: + return s->adg[IDX(offset, ADEVINTFLG)].intflg; + case ADEVSR ... ADG2SR: + return s->adg[IDX(offset, ADEVSR)].sr; + case ADG1BUFFER0 ... ADG1BUFFER7: + return hercules_mibadc_pop_result(s, &s->adg[1]); + case ADG2BUFFER0 ... ADG2BUFFER7: + return hercules_mibadc_pop_result(s, &s->adg[2]); + case ADPARCR: + return s->adparcr; + case ADBNDCR: + case ADBNDEND: + case ADPARADDR: + break; + case ADG1THRINTCR ... ADG2THRINTCR: + return 0; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_mibadc_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesMibAdcState *s = opaque; + const uint32_t val = val64; + HerculesMibAdcGroup *group; + + switch (offset) { + case ADG1SEL: + case ADG2SEL: + group = &s->adg[IDX(offset, ADEVSEL)]; + group->sel = val; + hercules_mibadc_do_conversion(s, group); + break; + case ADOPMODECR: + s->adopmodecr = val; + break; + case ADBNDCR: + /* + * We don't support more than 64 word buffer, so we only + * extract 6 bits + */ + s->adg[0].end = s->adg[1].start = 2 * extract32(val, 16, 6); + s->adg[1].end = s->adg[2].start = 2 * extract32(val, 0, 6); + + hercules_mibadc_group_reset(&s->adg[1]); + hercules_mibadc_group_reset(&s->adg[2]); + break; + case ADBNDEND: + s->adg[2].end = 16 << MIN(extract32(val, 0, 2), 2); + break; + case ADEVSR ... ADG2SR: + s->adg[IDX(offset, ADEVSR)].sr &= ~(val & ADGxSR_END); + break; + case ADEVINTFLG ... ADG2INTFLG: + s->adg[IDX(offset, ADEVINTFLG)].intflg &= ~(val & ADGxINTFLG_END); + break; + case ADPARCR: + s->adparcr = val; + break; + case ADPARADDR: + case ADG1BUFFER0 ... ADG2BUFFER7: + return; + default: + qemu_log_bad_offset(offset); + } +} + +#undef IDX + +static void hercules_mibadc_realize(DeviceState *dev, Error **errp) +{ + HerculesMibAdcState *s = HERCULES_MIBADC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_mibadc_ecc_ops = { + .read = hercules_mibadc_ecc_read, + .write = hercules_mibadc_ecc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_mibadc_ram_ops = { + .read = hercules_mibadc_ram_read, + .write = hercules_mibadc_ram_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_mibadc_ops = { + .read = hercules_mibadc_read, + .write = hercules_mibadc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_mibadc_ecc_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_mibadc_ram_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_mibadc_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->regs, OBJECT(dev), &hercules_mibadc_ops, s, + TYPE_HERCULES_MIBADC ".regs", + HERCULES_MIBADC_REGS_SIZE); + sysbus_init_mmio(sbd, &s->regs); + + memory_region_init_io(&s->io.ram, OBJECT(dev), &hercules_mibadc_ram_ops, + s, TYPE_HERCULES_MIBADC ".io.ram", + sizeof(s->results)); + + memory_region_init_io(&s->io.ecc, OBJECT(dev), &hercules_mibadc_ecc_ops, + s, TYPE_HERCULES_MIBADC ".io.ecc", + sizeof(s->ecc)); + + memory_region_init(&s->io.container, OBJECT(dev), + TYPE_HERCULES_MIBADC ".io.container", + HERCULES_MIBADC_CONTAINER_SIZE); + + memory_region_add_subregion(&s->io.container, HERCULES_MIBADC_RAM_OFFSET, + &s->io.ram); + memory_region_add_subregion(&s->io.container, HERCULES_MIBADC_ECC_OFFSET, + &s->io.ecc); + sysbus_init_mmio(sbd, &s->io.container); + + sysbus_init_irq(sbd, &s->parity_error); +} + +static void hercules_mibadc_reset(DeviceState *dev) +{ + HerculesMibAdcState *s = HERCULES_MIBADC(dev); + + s->adopmodecr = 0; + + memset(s->adg, 0, sizeof(s->adg)); + memset(s->results, 0, sizeof(s->results)); + + qemu_irq_lower(s->parity_error); +} + +static void hercules_mibadc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_mibadc_reset; + dc->realize = hercules_mibadc_realize; +} + +static const TypeInfo hercules_mibadc_info = { + .name = TYPE_HERCULES_MIBADC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesMibAdcState), + .class_init = hercules_mibadc_class_init, +}; + +static void hercules_mibadc_register_types(void) +{ + type_register_static(&hercules_mibadc_info); +} + +type_init(hercules_mibadc_register_types) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 9afa6eee7991..9ee183541690 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -456,6 +456,9 @@ config MSF2 select SSI select UNIMP +config HERCULES + bool + config ZAURUS bool select NAND diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 534a6a119e5d..d7d6a7a4283b 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -54,3 +54,4 @@ obj-$(CONFIG_FSL_IMX7) += fsl-imx7.o mcimx7d-sabre.o obj-$(CONFIG_ARM_SMMUV3) += smmu-common.o smmuv3.o obj-$(CONFIG_FSL_IMX6UL) += fsl-imx6ul.o mcimx6ul-evk.o obj-$(CONFIG_NRF51_SOC) += nrf51_soc.o +obj-$(CONFIG_HERCULES) += hercules.o diff --git a/hw/arm/hercules.c b/hw/arm/hercules.c new file mode 100644 index 000000000000..ce83b13039ad --- /dev/null +++ b/hw/arm/hercules.c @@ -0,0 +1,643 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu-common.h" +#include "chardev/char-fe.h" +#include "cpu.h" +#include "elf.h" +#include "exec/address-spaces.h" +#include "hw/arm/boot.h" +#include "hw/arm/hercules.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "hw/sysbus.h" +#include "net/net.h" +#include "qemu/error-report.h" +#include "sysemu/reset.h" +#include "sysemu/sysemu.h" +#include "sysemu/block-backend.h" +#include "sysemu/qtest.h" + + +#ifdef HOST_WORDS_BIGENDIAN +#pragma message "Hercules emulation not tested on Big Endian hosts" +#endif + +static const int +HERCULES_MIBSPIn_DMAREQ[HERCULES_NUM_MIBSPIS][HERCULES_SPI_NUM_DMAREQS] = { + { + [0] = 1, [1] = 0, [2] = 4, [3] = 5, + [4] = 8, [5] = 9, [6] = 12, [7] = 13, + [8] = 16, [9] = 17, [10] = 22, [11] = 23, + [12] = 26, [13] = 27, [14] = 30, [15] = 31, + }, + { + [0] = 3, [1] = 2, [2] = 32, [3] = 33, + [4] = 34, [5] = 35, [6] = 36, [7] = 37, + [8] = 38, [9] = 39, [10] = 40, [11] = 41, + [12] = 42, [13] = 43, [14] = 44, [15] = 45, + }, + { + [0] = 15, [1] = 14, [2] = 4, [3] = 5, + [4] = 8, [5] = 9, [6] = 12, [7] = 13, + [8] = 16, [9] = 17, [10] = 22, [11] = 23, + [12] = 26, [13] = 27, [14] = 30, [15] = 31, + }, + { + [0] = 25, [1] = 24, [2] = 32, [3] = 33, + [4] = 34, [5] = 35, [6] = 36, [7] = 37, + [8] = 38, [9] = 39, [10] = 40, [11] = 41, + [12] = 42, [13] = 43, [14] = 44, [15] = 45, + }, + { + [0] = 31, [1] = 30, [2] = 6, [3] = 7, + [4] = 10, [5] = 11, [6] = 14, [7] = 15, + [8] = 18, [9] = 19, [10] = 22, [11] = 23, + [12] = 24, [13] = 25, [14] = 28, [15] = 29, + }, +}; + +static void hercules_initfn(Object *obj) +{ + HerculesState *s = HERCULES_SOC(obj); + Object *cpu_obj = object_new(ARM_CPU_TYPE_NAME("cortex-r5f")); + int i; + + s->cpu = ARM_CPU(cpu_obj); + s->cpu->ctr = 0x1d192992; /* 32K icache 32K dcache */ + set_feature(&s->cpu->env, ARM_FEATURE_DUMMY_C15_REGS); + + if (s->is_tms570) { + object_property_set_bool(cpu_obj, true, "cfgend", &error_fatal); + object_property_set_bool(cpu_obj, true, "cfgend-instr", &error_fatal); + } + + object_property_add_child(obj, "cpu", cpu_obj); + + sysbus_init_child_obj(obj, "l2ramw", &s->l2ramw, sizeof(s->l2ramw), + TYPE_HERCULES_L2RAMW); + + sysbus_init_child_obj(obj, "rtp", &s->rtp, sizeof(s->rtp), + TYPE_HERCULES_RTP); + + sysbus_init_child_obj(obj, "vim", &s->vim, sizeof(s->vim), + TYPE_HERCULES_VIM); + + sysbus_init_child_obj(obj, "system", &s->system, sizeof(s->system), + TYPE_HERCULES_SYSTEM); + + sysbus_init_child_obj(obj, "gio", &s->gio, sizeof(s->gio), + TYPE_HERCULES_GIO); + + for (i = 0; i < HERCULES_NUM_N2HETS; i++) { + sysbus_init_child_obj(obj, "n2het[*]", &s->n2het[i], + sizeof(s->n2het[i]), TYPE_HERCULES_N2HET); + } + s->n2het[0].gpio.bank = 2; + s->n2het[1].gpio.bank = 3; + + for (i = 0; i < HERCULES_NUM_MIBADCS; i++) { + sysbus_init_child_obj(obj, "mibadc[*]", &s->mibadc[i], + sizeof(s->mibadc[i]), TYPE_HERCULES_MIBADC); + } + + sysbus_init_child_obj(obj, "rti", &s->rti, sizeof(s->rti), + TYPE_HERCULES_RTI); + + sysbus_init_child_obj(obj, "emac", &s->emac, sizeof(s->emac), + TYPE_HERCULES_EMAC); + + sysbus_init_child_obj(obj, "dma", &s->dma, sizeof(s->dma), + TYPE_HERCULES_DMA); + + for (i = 0; i < HERCULES_NUM_MIBSPIS; i++) { + sysbus_init_child_obj(obj, "mibspi[*]", &s->mibspi[i], + sizeof(s->mibspi), TYPE_HERCULES_SPI); + } + + sysbus_init_child_obj(obj, "scm", &s->scm, sizeof(s->scm), + TYPE_HERCULES_SCM); + sysbus_init_child_obj(obj, "esm", &s->esm, sizeof(s->esm), + TYPE_HERCULES_ESM); + sysbus_init_child_obj(obj, "efuse", &s->efuse, sizeof(s->efuse), + TYPE_HERCULES_EFUSE); + sysbus_init_child_obj(obj, "pmm", &s->pmm, sizeof(s->pmm), + TYPE_HERCULES_PMM); + + for (i = 0; i < HERCULES_NUM_ECAPS; i++) { + sysbus_init_child_obj(obj, "ecap[*]", &s->ecap[i], + sizeof(s->ecap), TYPE_HERCULES_ECAP); + } + + sysbus_init_child_obj(obj, "stc", &s->stc, sizeof(s->stc), + TYPE_HERCULES_STC); + sysbus_init_child_obj(obj, "pbist", &s->pbist, sizeof(s->pbist), + TYPE_HERCULES_PBIST); + sysbus_init_child_obj(obj, "ccm", &s->ccm, sizeof(s->ccm), + TYPE_HERCULES_CCM); + sysbus_init_child_obj(obj, "l2fmc", &s->l2fmc, sizeof(s->l2fmc), + TYPE_HERCULES_L2FMC); +} + +static void hercules_cpu_reset(void *opaque) +{ + ARMCPU *cpu = opaque; + + cpu_reset(CPU(cpu)); +} + +static void hercules_realize(DeviceState *dev, Error **errp) +{ + HerculesState *s = HERCULES_SOC(dev); + MemoryRegion *system_memory = get_system_memory(); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *eeprom = g_new(MemoryRegion, 1); + MemoryRegion *otp_bank1 = g_new(MemoryRegion, 1); + SysBusDevice *sbd; + DeviceState *vim, *dma, *esm; + qemu_irq irq, error; + int i; + + object_property_set_bool(OBJECT(s->cpu), true, "realized", &error_fatal); + qemu_register_reset(hercules_cpu_reset, ARM_CPU(s->cpu)); + + memory_region_init_rom(flash, OBJECT(dev), "hercules.flash", + HERCULES_FLASH_SIZE, &error_fatal); + memory_region_add_subregion(system_memory, HERCULES_FLASH_ADDR, flash); + + memory_region_init_rom(eeprom, OBJECT(dev), "hercules.eeprom", + HERCULES_EEPROM_SIZE, &error_fatal); + memory_region_add_subregion(system_memory, HERCULES_EEPROM_ADDR, eeprom); + + if (s->blk_eeprom) { + int64_t size; + + size = MIN(blk_getlength(s->blk_eeprom), HERCULES_EEPROM_SIZE); + if (size <= 0) { + error_setg(errp, "failed to get flash size"); + return; + } + + if (blk_pread(s->blk_eeprom, 0, memory_region_get_ram_ptr(eeprom), + size) < 0) { + error_setg(errp, "failed to read EEPROM content"); + return; + } + } + + qdev_prop_set_chr(DEVICE(&s->rtp), "chardev", serial_hd(0)); + object_property_set_bool(OBJECT(&s->rtp), true, "realized", &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtp), 0, HERCULES_RTP_ADDR); + + /* + * ARM Debug peripherals + */ + create_unimplemented_device("debug-rom", HERCULES_DEBUG_ROM_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("debug", HERCULES_DEBUG_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("etm", HERCULES_ETM_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("tpiu", HERCULES_TPIU_ADDR , + HERCULES_DEBUG_SIZE); + create_unimplemented_device("pom", HERCULES_POM_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("cti1", HERCULES_CTI1_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("cti2", HERCULES_CTI2_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("cti3", HERCULES_CTI3_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("cti4", HERCULES_CTI4_ADDR, + HERCULES_DEBUG_SIZE); + create_unimplemented_device("ctsf", HERCULES_CTSF_ADDR, + HERCULES_DEBUG_SIZE); + + /* + * VIM + */ + object_property_set_bool(OBJECT(&s->vim), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->vim); + irq = qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ); + sysbus_connect_irq(sbd, 0, irq); + irq = qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ); + sysbus_connect_irq(sbd, 1, irq); + sysbus_mmio_map(sbd, 0, HERCULES_VIM_ECC_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_VIM_CONTROL_ADDR); + sysbus_mmio_map(sbd, 2, HERCULES_VIM_RAM_ADDR); + + vim = DEVICE(&s->vim); + + object_property_set_bool(OBJECT(&s->esm), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->esm); + sysbus_mmio_map(sbd, 0, HERCULES_ESM_ADDR); + irq = qdev_get_gpio_in(vim, HERCULES_ESM_HIGH_LEVEL_IRQ); + sysbus_connect_irq(sbd, 0, irq); + irq = qdev_get_gpio_in(vim, HERCULES_ESM_LOW_LEVEL_IRQ); + sysbus_connect_irq(sbd, 1, irq); + + esm = DEVICE(&s->esm); + + object_property_set_bool(OBJECT(&s->l2ramw), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->l2ramw); + sysbus_mmio_map(sbd, 0, HERCULES_RAM_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_L2RAMW_ADDR); + error = qdev_get_gpio_in(esm, HERCULES_L2RAMW_TYPE_B_UNCORRECTABLE_ERROR); + sysbus_connect_irq(sbd, 0, error); + + object_property_set_bool(OBJECT(&s->system), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->system); + sysbus_mmio_map(sbd, 0, HERCULES_SYS_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_SYS2_ADDR); + sysbus_mmio_map(sbd, 2, HERCULES_PCR_ADDR); + sysbus_mmio_map(sbd, 3, HERCULES_PCR2_ADDR); + sysbus_mmio_map(sbd, 4, HERCULES_PCR3_ADDR); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(vim, HERCULES_SSI_IRQ)); + error = qdev_get_gpio_in(esm, HERCULES_PLL1_SLIP_ERROR); + sysbus_connect_irq(sbd, 1, error); + error = qdev_get_gpio_in(esm, HERCULES_PLL2_SLIP_ERROR); + sysbus_connect_irq(sbd, 2, error); + + create_unimplemented_device("pinmux", HERCULES_PINMUX_ADDR, + HERCULES_PINMUX_SIZE); + + object_property_set_bool(OBJECT(&s->l2fmc), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->l2fmc); + sysbus_mmio_map(sbd, 0, HERCULES_L2FMC_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_EPC_ADDR); + error = qdev_get_gpio_in(esm, HERCULES_L2FMC_UNCORRECTABLE_ERROR); + sysbus_connect_irq(sbd, 0, error); + error = qdev_get_gpio_in(esm, HERCULES_CR5F_FATAL_BUS_ERROR); + sysbus_connect_irq(sbd, 1, error); + error = qdev_get_gpio_in(esm, HERCULES_EPC_CORRECTABLE_ERROR); + sysbus_connect_irq(sbd, 2, error); + + memory_region_init_rom(otp_bank1, OBJECT(dev), + TYPE_HERCULES_SOC ".otp.bank1", + HERCULES_OTP_BANK1_SIZE, + &error_fatal); + memory_region_add_subregion(system_memory, + HERCULES_OTP_BANK1_ADDR, otp_bank1); + + create_unimplemented_device("emif", HERCULES_EMIF_ADDR, + HERCULES_EMIF_SIZE); + + + object_property_set_bool(OBJECT(&s->gio), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->gio); + sysbus_mmio_map(sbd, 0, HERCULES_GIO_ADDR); + + for (i = 0; i < HERCULES_NUM_N2HETS; i++) { + static const hwaddr HERCULES_N2HETn_ADDR[HERCULES_NUM_N2HETS] = { + HERCULES_N2HET1_ADDR, + HERCULES_N2HET2_ADDR, + }; + + static const hwaddr HERCULES_N2HETn_RAM_ADDR[HERCULES_NUM_N2HETS] = { + HERCULES_N2HET1_RAM_ADDR, + HERCULES_N2HET2_RAM_ADDR, + }; + + object_property_set_bool(OBJECT(&s->n2het[i]), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->n2het[i]); + sysbus_mmio_map(sbd, 0, HERCULES_N2HETn_ADDR[i]); + sysbus_mmio_map(sbd, 1, HERCULES_N2HETn_RAM_ADDR[i]); + } + + create_unimplemented_device("lin1", HERCULES_LIN1_ADDR, + HERCULES_LIN1_SIZE); + + for (i = 0; i < HERCULES_NUM_MIBADCS; i++) { + static const hwaddr HERCULES_MIBADCn_ADDR[HERCULES_NUM_MIBADCS] = { + HERCULES_MIBADC1_ADDR, + HERCULES_MIBADC2_ADDR, + }; + static const hwaddr HERCULES_MIBADCn_RAM_ADDR[HERCULES_NUM_MIBADCS] = { + HERCULES_MIBADC1_RAM_ADDR, + HERCULES_MIBADC2_RAM_ADDR, + }; + static const int + HERCULES_MIBADCn_PARITY_ERRROR[HERCULES_NUM_MIBADCS] = { + HERCULES_MIBADC1_PARITY_ERROR, + HERCULES_MIBADC2_PARITY_ERROR, + }; + + object_property_set_bool(OBJECT(&s->mibadc[i]), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->mibadc[i]); + sysbus_mmio_map(sbd, 0, HERCULES_MIBADCn_ADDR[i]); + sysbus_mmio_map(sbd, 1, HERCULES_MIBADCn_RAM_ADDR[i]); + error = qdev_get_gpio_in(esm, HERCULES_MIBADCn_PARITY_ERRROR[i]); + sysbus_connect_irq(sbd, 0, error); + } + + /* + * RTI + */ + object_property_set_bool(OBJECT(&s->rti), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->rti); + + irq = qdev_get_gpio_in(vim, HERCULES_RTI_COMPARE0_IRQ); + sysbus_connect_irq(sbd, HERCULES_RTI_INT_COMPARE0, irq); + irq = qdev_get_gpio_in(vim, HERCULES_RTI_COMPARE1_IRQ); + sysbus_connect_irq(sbd, HERCULES_RTI_INT_COMPARE1, irq); + irq = qdev_get_gpio_in(vim, HERCULES_RTI_COMPARE2_IRQ); + sysbus_connect_irq(sbd, HERCULES_RTI_INT_COMPARE2, irq); + irq = qdev_get_gpio_in(vim, HERCULES_RTI_COMPARE3_IRQ); + sysbus_connect_irq(sbd, HERCULES_RTI_INT_COMPARE3, irq); + + sysbus_mmio_map(sbd, 0, HERCULES_RTI_ADDR); + + qdev_set_nic_properties(DEVICE(&s->emac), &nd_table[0]); + object_property_set_bool(OBJECT(&s->emac), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->emac); + sysbus_mmio_map(sbd, 0, HERCULES_EMAC_MODULE_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_EMAC_CTRL_ADDR); + sysbus_mmio_map(sbd, 2, HERCULES_EMAC_MDIO_ADDR); + sysbus_mmio_map(sbd, 3, HERCULES_EMAC_CPPI_ADDR); + + create_unimplemented_device("nmpu", HERCULES_NMPU_ADDR, + HERCULES_NMPU_SIZE); + + create_unimplemented_device("dcc1", HERCULES_DCC1_ADDR, + HERCULES_DCC1_SIZE); + + object_property_set_bool(OBJECT(&s->dma), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->dma); + sysbus_mmio_map(sbd, 0, HERCULES_DMA_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_DMA_RAM_ADDR); + + dma = DEVICE(&s->dma); + + for (i = 0; i < HERCULES_NUM_MIBSPIS; i++) { + static const hwaddr + HERCULES_MIBSPIn_RAM_ADDR[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_RAM_ADDR, + HERCULES_MIBSPI2_RAM_ADDR, + HERCULES_MIBSPI3_RAM_ADDR, + HERCULES_MIBSPI4_RAM_ADDR, + HERCULES_MIBSPI5_RAM_ADDR, + }; + static const hwaddr + HERCULES_MIBSPIn_CTRL_ADDR[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_CTRL_ADDR, + HERCULES_MIBSPI2_CTRL_ADDR, + HERCULES_MIBSPI3_CTRL_ADDR, + HERCULES_MIBSPI4_CTRL_ADDR, + HERCULES_MIBSPI5_CTRL_ADDR, + }; + static const int HERCULES_MIBSPIn_L0_IRQ[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_L0_IRQ, + HERCULES_MIBSPI2_L0_IRQ, + HERCULES_MIBSPI3_L0_IRQ, + HERCULES_MIBSPI4_L0_IRQ, + HERCULES_MIBSPI5_L0_IRQ, + }; + static const int HERCULES_MIBSPIn_L1_IRQ[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_L1_IRQ, + HERCULES_MIBSPI2_L1_IRQ, + HERCULES_MIBSPI3_L1_IRQ, + HERCULES_MIBSPI4_L1_IRQ, + HERCULES_MIBSPI5_L1_IRQ, + }; + static const int + HERCULES_MIBSPIn_SINGLE_BIT_ERROR[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_SINGLE_BIT_ERROR, + HERCULES_MIBSPI2_SINGLE_BIT_ERROR, + HERCULES_MIBSPI3_SINGLE_BIT_ERROR, + HERCULES_MIBSPI4_SINGLE_BIT_ERROR, + HERCULES_MIBSPI5_SINGLE_BIT_ERROR, + }; + static const int + HERCULES_MIBSPIn_UNCORRECTABLE_ERROR[HERCULES_NUM_MIBSPIS] = { + HERCULES_MIBSPI1_UNCORRECTABLE_ERROR, + HERCULES_MIBSPI2_UNCORRECTABLE_ERROR, + HERCULES_MIBSPI3_UNCORRECTABLE_ERROR, + HERCULES_MIBSPI4_UNCORRECTABLE_ERROR, + HERCULES_MIBSPI5_UNCORRECTABLE_ERROR, + }; + int irq_nr = 0; + int j; + + object_property_set_bool(OBJECT(&s->mibspi[i]), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->mibspi[i]); + sysbus_mmio_map(sbd, 0, HERCULES_MIBSPIn_CTRL_ADDR[i]); + sysbus_mmio_map(sbd, 1, HERCULES_MIBSPIn_RAM_ADDR[i]); + irq = qdev_get_gpio_in(vim, HERCULES_MIBSPIn_L0_IRQ[i]); + sysbus_connect_irq(sbd, irq_nr++, irq); + irq = qdev_get_gpio_in(vim, HERCULES_MIBSPIn_L1_IRQ[i]); + sysbus_connect_irq(sbd, irq_nr++, irq); + + irq_nr += HERCULES_SPI_NUM_CS_LINES; + + for (j = 0; j < HERCULES_SPI_NUM_DMAREQS; j++) { + irq = qdev_get_gpio_in(dma, HERCULES_MIBSPIn_DMAREQ[i][j]); + sysbus_connect_irq(sbd, irq_nr++, irq); + } + + error = qdev_get_gpio_in(esm, HERCULES_MIBSPIn_SINGLE_BIT_ERROR[i]); + sysbus_connect_irq(sbd, irq_nr++, error); + error = qdev_get_gpio_in(esm, HERCULES_MIBSPIn_UNCORRECTABLE_ERROR[i]); + sysbus_connect_irq(sbd, irq_nr++, error); + } + + object_property_set_bool(OBJECT(&s->scm), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->scm); + sysbus_mmio_map(sbd, 0, HERCULES_SCM_ADDR); + sysbus_mmio_map(sbd, 1, HERCULES_SDR_MMR_ADDR); + irq = qdev_get_gpio_in(DEVICE(&s->system), HERCULES_SYSTEM_ICRST); + sysbus_connect_irq(sbd, 0, irq); + + object_property_set_bool(OBJECT(&s->efuse), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->efuse); + sysbus_mmio_map(sbd, 0, HERCULES_EFUSE_ADDR); + error = qdev_get_gpio_in(esm, HERCULES_EFUSE_AUTOLOAD_ERROR); + sysbus_connect_irq(sbd, 0, error); + error = qdev_get_gpio_in(esm, HERCULES_EFUSE_SELF_TEST_ERROR); + sysbus_connect_irq(sbd, 1, error); + error = qdev_get_gpio_in(esm, HERCULES_EFUSE_SINGLE_BIT_ERROR); + sysbus_connect_irq(sbd, 2, error); + + object_property_set_bool(OBJECT(&s->pmm), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->pmm); + sysbus_mmio_map(sbd, 0, HERCULES_PMM_ADDR); + error = qdev_get_gpio_in(esm, HERCULES_PMM_COMPARE_ERROR); + sysbus_connect_irq(sbd, 0, error); + error = qdev_get_gpio_in(esm, HERCULES_PMM_SELF_TEST_ERROR); + sysbus_connect_irq(sbd, 1, error); + + for (i = 0; i < HERCULES_NUM_ECAPS; i++) { + static const hwaddr HERCULES_ECAPn_ADDR[HERCULES_NUM_ECAPS] = { + HERCULES_ECAP1_ADDR, + HERCULES_ECAP2_ADDR, + HERCULES_ECAP3_ADDR, + HERCULES_ECAP4_ADDR, + HERCULES_ECAP5_ADDR, + HERCULES_ECAP6_ADDR, + }; + + object_property_set_bool(OBJECT(&s->ecap[i]), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->ecap[i]); + sysbus_mmio_map(sbd, 0, HERCULES_ECAPn_ADDR[i]); + } + + object_property_set_bool(OBJECT(&s->stc), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->stc); + sysbus_mmio_map(sbd, 0, HERCULES_STC1_ADDR); + irq = qdev_get_gpio_in(DEVICE(&s->system), HERCULES_SYSTEM_CPURST); + sysbus_connect_irq(sbd, 0, irq); + + object_property_set_bool(OBJECT(&s->pbist), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->pbist); + sysbus_mmio_map(sbd, 0, HERCULES_PBIST_ADDR); + irq = qdev_get_gpio_in(DEVICE(&s->system), HERCULES_SYSTEM_MSTDONE); + sysbus_connect_irq(sbd, 0, irq); + + object_property_set_bool(OBJECT(&s->ccm), true, "realized", + &error_abort); + sbd = SYS_BUS_DEVICE(&s->ccm); + sysbus_mmio_map(sbd, 0, HERCULES_CCM_ADDR); + error = qdev_get_gpio_in(esm, HERCULES_CCMR5F_CPU_COMPARE_ERROR); + sysbus_connect_irq(sbd, 0, error); + error = qdev_get_gpio_in(esm, HERCULES_CCMR5F_VIM_COMPARE_ERROR); + sysbus_connect_irq(sbd, 1, error); + error = qdev_get_gpio_in(esm, HERCULES_CPU1_AXIM_BUS_MONITOR_ERROR); + sysbus_connect_irq(sbd, 2, error); + error = qdev_get_gpio_in(esm, HERCULES_CCMR5F_SELF_TEST_ERROR); + sysbus_connect_irq(sbd, 3, error); + +#ifdef HOST_WORDS_BIGENDIAN + error_setg(errp, "failed realize on big endian host"); +#endif +} + +static Property hercules_properties[] = { + DEFINE_PROP_DRIVE("eeprom", HerculesState, blk_eeprom), + DEFINE_PROP_BOOL("tms570", HerculesState, is_tms570, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void hercules_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = hercules_realize; + dc->desc = "TI Hercules"; + device_class_set_props(dc, hercules_properties); + + /* Reason: Uses serial_hds in realize() directly */ + dc->user_creatable = false; +} + +static const TypeInfo hercules_type_info = { + .name = TYPE_HERCULES_SOC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(HerculesState), + .instance_init = hercules_initfn, + .class_init = hercules_class_init, +}; + +static void hercules_register_types(void) +{ + type_register_static(&hercules_type_info); +} +type_init(hercules_register_types) + +static void hercules_xx57_init(MachineState *machine, bool is_tms570) +{ + Object *dev; + MemoryRegion *sdram = g_new(MemoryRegion, 1); + DriveInfo *eeprom = drive_get(IF_MTD, 0, 0); + const char *file; + + dev = object_new_with_props(TYPE_HERCULES_SOC, + object_get_objects_root(), + "hercules0", + &error_fatal, + "tms570", is_tms570, + NULL); + qdev_prop_set_drive(DEVICE(dev), "eeprom", + eeprom ? blk_by_legacy_dinfo(eeprom) : NULL, + &error_abort); + object_property_set_bool(dev, true, "realized", &error_fatal); + + memory_region_init_ram(sdram, OBJECT(dev), "hercules.sdram", + 0x00800000, &error_fatal); + memory_region_add_subregion(get_system_memory(), HERCULES_EMIF_CS1_ADDR, + sdram); + + if (qtest_enabled()) { + return; + } + + if (machine->kernel_filename) { + uint64_t e, l; + + file = machine->kernel_filename; + if (load_elf(file, NULL, NULL, NULL, &e, &l, NULL, NULL, + 1, EM_ARM, 1, 0) < 0) { + goto exit; + } + } else if (machine->firmware) { + file = machine->firmware; + if (load_image_targphys(file, HERCULES_FLASH_ADDR, + HERCULES_FLASH_SIZE) < 0) { + goto exit; + } + } + + return; +exit: + error_report("Could not load '%s'", file); + exit(1); +} + +static void tms570lc43_init(MachineState *machine) +{ + hercules_xx57_init(machine, true); +} + +static void rm57l843_init(MachineState *machine) +{ + hercules_xx57_init(machine, false); +} + +static void tms570lc43_machine_init(MachineClass *mc) +{ + mc->desc = "Texas Instruments Hercules TMS570LC43"; + mc->init = tms570lc43_init; + mc->max_cpus = 1; +} + +static void rm57l843_machine_init(MachineClass *mc) +{ + mc->desc = "Texas Instruments Hercules RM57L843"; + mc->init = rm57l843_init; + mc->max_cpus = 1; +} + +DEFINE_MACHINE("tms570lc43", tms570lc43_machine_init) +DEFINE_MACHINE("rm57l843", rm57l843_machine_init) diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs index 9e9a6c1affb6..67751dd3ffe2 100644 --- a/hw/char/Makefile.objs +++ b/hw/char/Makefile.objs @@ -30,6 +30,7 @@ common-obj-$(CONFIG_LM32) += lm32_juart.o common-obj-$(CONFIG_LM32) += lm32_uart.o common-obj-$(CONFIG_MILKYMIST) += milkymist-uart.o common-obj-$(CONFIG_SCLPCONSOLE) += sclpconsole.o sclpconsole-lm.o +common-obj-$(CONFIG_HERCULES) += hercules_rtp.o obj-$(CONFIG_VIRTIO) += virtio-serial-bus.o obj-$(CONFIG_PSERIES) += spapr_vty.o diff --git a/hw/char/hercules_rtp.c b/hw/char/hercules_rtp.c new file mode 100644 index 000000000000..38fa525aeb0e --- /dev/null +++ b/hw/char/hercules_rtp.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" +#include "hw/char/hercules_rtp.h" +#include "chardev/char-fe.h" +#include "qemu/log.h" + +#define RTPDDMW 0x2c + +static uint64_t hercules_rtp_read(void *opaque, hwaddr offset, unsigned size) +{ + return 0; +} + +static void hercules_rtp_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + HerculesRTPState *s = HERCULES_RTP(opaque); + uint8_t ch = value; + + switch (offset) { + case RTPDDMW: + qemu_chr_fe_write_all(&s->chr, &ch, sizeof(ch)); + break; + } +} + +static const MemoryRegionOps hercules_rtp_ops = { + .read = hercules_rtp_read, + .write = hercules_rtp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void hercules_rtp_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + HerculesRTPState *s = HERCULES_RTP(obj); + + memory_region_init_io(&s->iomem, obj, &hercules_rtp_ops, s, + TYPE_HERCULES_RTP ".io", HERCULES_RTP_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static void hercules_rtp_realize(DeviceState *dev, Error **errp) +{ + HerculesRTPState *s = HERCULES_RTP(dev); + + qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL, s, NULL, true); +} + +static Property hercules_rtp_properties[] = { + DEFINE_PROP_CHR("chardev", HerculesRTPState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void hercules_rtp_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = hercules_rtp_realize; + device_class_set_props(dc, hercules_rtp_properties); +} + +static const TypeInfo hercules_rtp_info = { + .name = TYPE_HERCULES_RTP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesRTPState), + .instance_init = hercules_rtp_init, + .class_init = hercules_rtp_class_init, +}; + +static void hercules_rtp_register_types(void) +{ + type_register_static(&hercules_rtp_info); +} + +type_init(hercules_rtp_register_types) diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs index f4b1cfe26da0..bd43608900d0 100644 --- a/hw/dma/Makefile.objs +++ b/hw/dma/Makefile.objs @@ -14,3 +14,4 @@ common-obj-$(CONFIG_XLNX_ZYNQMP_ARM) += xlnx-zdma.o common-obj-$(CONFIG_OMAP) += omap_dma.o soc_dma.o common-obj-$(CONFIG_PXA2XX) += pxa2xx_dma.o common-obj-$(CONFIG_RASPI) += bcm2835_dma.o +common-obj-$(CONFIG_HERCULES) += hercules_dma.o diff --git a/hw/dma/hercules_dma.c b/hw/dma/hercules_dma.c new file mode 100644 index 000000000000..8506abb5ebba --- /dev/null +++ b/hw/dma/hercules_dma.c @@ -0,0 +1,447 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "hw/core/cpu.h" +#include "sysemu/dma.h" +#include "trace.h" + +#include "hw/dma/hercules_dma.h" +#include "hw/arm/hercules.h" + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesDMARegisters { + GCTRL = 0x000, + HWCHENAS = 0x014, + HWCHENAR = 0x01C, + DREQASI0 = 0x054, + DREQASI7 = 0x070, + PAR0 = 0x094, + PAR3 = 0x0A0, + BTCFLAG = 0x13C, + PTCRL = 0x178, + TTYPE = BIT(8), + + ISADDR = 0x00, + IDADDR = 0x04, + ITCOUNT = 0x08, + CHCTRL = 0x10, + ADDM_POST_INCREMENT = 1, + ADDM_INDEXED = 2, + + EIOFF = 0x14, + FIOFF = 0x18, + + CSADDR = 0x00, + CDADDR = 0x04, + CTCOUNT = 0x08, +}; + +enum { + HERCULES_DMA_SIZE = 1024, + HERCULES_DMA_RAM_SIZE = 4 * 1024, + + HERCULES_DMA_PCP_OFFSET = 0x000, + HERCULES_DMA_PCP_SIZE = 32, + + HERCULES_DMA_WCP_OFFSET = 0x800, + HERCULES_DMA_WCP_SIZE = 32, +}; + +#define CHCTRL_ADDMR(w) extract32(w, 3, 2) +#define CHCTRL_ADDMW(w) extract32(w, 1, 2) +#define CHCTRL_RES(w) extract32(w, 14, 2) +#define CHCTRL_WES(w) extract32(w, 12, 2) + +static void hercules_dma_adjust_addr(uint32_t *caddr, unsigned int addm, + unsigned int es, unsigned int eidx, + unsigned int fidx, int cetcount) +{ + switch (addm) { + case ADDM_INDEXED: + *caddr += es * ((cetcount == 1) ? fidx : eidx); + break; + case ADDM_POST_INCREMENT: + *caddr += es; + break; + default: + break; + } +} + +static void hercules_dma_set_request(void *opaque, int req, int level) +{ + HerculesDMAState *s = opaque; + const uint32_t enabled = s->reqmap[req] & s->hwchena; + + if (level && enabled) { + const unsigned int channel = ctzl(enabled); + HerculesDMAChannel *ch = &s->channel[channel]; + const uint8_t addmr = CHCTRL_ADDMR(ch->pcp.chctrl); + const uint8_t addmw = CHCTRL_ADDMW(ch->pcp.chctrl); + const unsigned int res = CHCTRL_RES(ch->pcp.chctrl) + 1; + const unsigned int wes = CHCTRL_WES(ch->pcp.chctrl) + 1; + + while (ch->wcp.cftcount) { + uint64_t buffer = 0; + int i; + + for (i = ch->wcp.cetcount; i > 0; i--) { + trace_hercules_dma_transfer(channel, + (int)ch->wcp.cftcount, + (int)ch->wcp.cetcount, + ch->wcp.csaddr, res, + ch->wcp.cdaddr, wes); + + dma_memory_read(&address_space_memory, ch->wcp.csaddr, + &buffer, res); + hercules_dma_adjust_addr(&ch->wcp.csaddr, addmr, res, + ch->pcp.eidxs, ch->pcp.fidxs, i); + + dma_memory_write(&address_space_memory, ch->wcp.cdaddr, + &buffer, wes); + hercules_dma_adjust_addr(&ch->wcp.cdaddr, addmw, wes, + ch->pcp.eidxd, ch->pcp.fidxd, i); + } + + if (!--ch->wcp.cftcount) { + s->btcflag |= BIT(channel); + } + + if (!(ch->pcp.chctrl & TTYPE)) { + /* + * Exit this loop early if we are configured for + * frame transfer + */ + break; + } + } + } +} + +#define IDX(o, s) (((o) - (s)) / sizeof(uint32_t)) + +static uint64_t hercules_dma_read(void *opaque, hwaddr offset, unsigned size) +{ + HerculesDMAState *s = opaque; + + switch (offset) { + case GCTRL: + return s->gctrl; + case HWCHENAS: + case HWCHENAR: + return s->hwchena; + case BTCFLAG: + return s->btcflag; + case DREQASI0 ... DREQASI7: + return s->dreqasi[IDX(offset, DREQASI0)]; + case PTCRL: + case PAR0 ... PAR3: + break; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_dma_update_reqmap(HerculesDMAState *s) +{ + int i, channel; + uint8_t req; + + memset(s->reqmap, 0, sizeof(s->reqmap)); + + for (i = 0, channel = 0; i < ARRAY_SIZE(s->dreqasi); i++) { + req = extract32(s->dreqasi[i], 24, 6); + s->reqmap[req] |= BIT(channel++); + req = extract32(s->dreqasi[i], 16, 6); + s->reqmap[req] |= BIT(channel++); + req = extract32(s->dreqasi[i], 8, 6); + s->reqmap[req] |= BIT(channel++); + req = extract32(s->dreqasi[i], 0, 6); + s->reqmap[req] |= BIT(channel++); + } +} + +static void hercules_dma_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesDMAState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case GCTRL: + s->gctrl = val; + break; + case HWCHENAS: + s->hwchena |= val; + break; + case HWCHENAR: + s->hwchena &= ~val; + break; + case BTCFLAG: + s->btcflag &= ~val; + break; + case DREQASI0 ... DREQASI7: + s->dreqasi[IDX(offset, DREQASI0)] = val; + hercules_dma_update_reqmap(s); + break; + case PTCRL: + case PAR0 ... PAR3: + break; + default: + qemu_log_bad_offset(offset); + } +} + +#undef IDX + +static void hercules_dma_ram_pcp_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesDMAChannel *ch = opaque; + const uint32_t val = val64; + + switch (offset) { + case ISADDR: + ch->wcp.csaddr = ch->pcp.isaddr = val; + break; + case IDADDR: + ch->wcp.cdaddr = ch->pcp.idaddr = val; + break; + case ITCOUNT: + ch->wcp.cetcount = ch->pcp.ietcount = extract32(val, 0, 16); + ch->wcp.cftcount = ch->pcp.iftcount = extract32(val, 16, 16); + break; + case CHCTRL: + ch->pcp.chctrl = val; + break; + case EIOFF: + ch->pcp.eidxs = extract32(val, 0, 16); + ch->pcp.eidxd = extract32(val, 16, 16); + break; + case FIOFF: + ch->pcp.fidxs = extract32(val, 0, 16); + ch->pcp.fidxd = extract32(val, 16, 16); + break; + default: + qemu_log_bad_offset(offset); + break; + }; +} + +static uint64_t hercules_dma_ram_pcp_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesDMAChannel *ch = opaque; + + switch (offset) { + case ISADDR: + return ch->pcp.isaddr; + case IDADDR: + return ch->pcp.idaddr; + case ITCOUNT: + return deposit32(ch->pcp.ietcount, 16, 16, ch->pcp.iftcount); + case CHCTRL: + return ch->pcp.chctrl; + case EIOFF: + return deposit32(ch->pcp.eidxs, 16, 16, ch->pcp.eidxd); + case FIOFF: + return deposit32(ch->pcp.fidxs, 16, 16, ch->pcp.fidxd); + default: + qemu_log_bad_offset(offset); + }; + + return 0; +} + +static uint64_t hercules_dma_ram_wcp_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesDMAChannel *ch = opaque; + + switch (offset) { + case CSADDR: + return ch->wcp.csaddr; + case CDADDR: + return ch->wcp.cdaddr; + case CTCOUNT: + return deposit32(ch->wcp.cetcount, 16, 16, ch->wcp.cftcount); + default: + qemu_log_bad_offset(offset); + break; + }; + + return 0; +} + +static void hercules_dma_reset(DeviceState *d) +{ + HerculesDMAState *s = HERCULES_DMA(d); + int i; + + for (i = 0; i < HERCULES_DMA_CHANNEL_NUM; i++) { + HerculesDMAChannel *ch = &s->channel[i]; + + ch->pcp.isaddr = 0; + ch->pcp.idaddr = 0; + ch->pcp.iftcount = 0; + ch->pcp.ietcount = 0; + ch->pcp.chctrl = 0; + ch->pcp.eidxd = 0; + ch->pcp.eidxs = 0; + ch->pcp.fidxd = 0; + ch->pcp.fidxs = 0; + + ch->wcp.csaddr = 0; + ch->wcp.cdaddr = 0; + ch->wcp.cftcount = 0; + ch->wcp.cetcount = 0; + } + + s->hwchena = 0; + s->btcflag = 0; + s->gctrl = 0; + + for (i = 0; i < ARRAY_SIZE(s->dreqasi); i++) { + s->dreqasi[i] = 0x00010203 + i * 0x04040404; + } + hercules_dma_update_reqmap(s); +} + +static void hercules_dma_initfn(Object *obj) +{ +} + +static void hercules_dma_realize(DeviceState *dev, Error **errp) +{ + HerculesDMAState *s = HERCULES_DMA(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + int i; + + static MemoryRegionOps hercules_dma_ops = { + .read = hercules_dma_read, + .write = hercules_dma_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_dma_ram_pcp_ops = { + .read = hercules_dma_ram_pcp_read, + .write = hercules_dma_ram_pcp_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_dma_ram_wcp_ops = { + .read = hercules_dma_ram_wcp_read, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_dma_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_dma_ram_pcp_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_dma_ram_wcp_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_dma_ops, + s, TYPE_HERCULES_DMA ".io", HERCULES_DMA_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + memory_region_init(&s->ram, OBJECT(dev), + TYPE_HERCULES_DMA ".ram", + HERCULES_DMA_RAM_SIZE); + + for (i = 0; i < HERCULES_DMA_CHANNEL_NUM; i++) { + MemoryRegion *io, *parent = &s->ram; + hwaddr offset; + + io = &s->channel[i].pcp.io; + memory_region_init_io(io, OBJECT(dev), &hercules_dma_ram_pcp_ops, + &s->channel[i], + TYPE_HERCULES_DMA ".pcp.io", + HERCULES_DMA_PCP_SIZE); + offset = HERCULES_DMA_PCP_OFFSET + i * HERCULES_DMA_PCP_SIZE; + memory_region_add_subregion(parent, offset, io); + + io = &s->channel[i].wcp.io; + memory_region_init_io(io, OBJECT(dev), &hercules_dma_ram_wcp_ops, + &s->channel[i], + TYPE_HERCULES_DMA ".wcp.io", + HERCULES_DMA_WCP_SIZE); + offset = HERCULES_DMA_WCP_OFFSET + i * HERCULES_DMA_WCP_SIZE; + memory_region_add_subregion(parent, offset, io); + } + + sysbus_init_mmio(sbd, &s->ram); + + qdev_init_gpio_in(dev, hercules_dma_set_request, HERCULES_DMA_REQUEST_NUM); +} + +static void hercules_dma_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_dma_reset; + dc->realize = hercules_dma_realize; +} + +static const TypeInfo hercules_dma_info = { + .name = TYPE_HERCULES_DMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesDMAState), + .instance_init = hercules_dma_initfn, + .class_init = hercules_dma_class_init, +}; + +static void hercules_dma_register_types(void) +{ + type_register_static(&hercules_dma_info); +} + +type_init(hercules_dma_register_types) diff --git a/hw/dma/trace-events b/hw/dma/trace-events index 44893995f631..b77003eaa801 100644 --- a/hw/dma/trace-events +++ b/hw/dma/trace-events @@ -44,3 +44,6 @@ pl330_debug_exec_stall(void) "stall of debug instruction not implemented" pl330_iomem_write(uint32_t offset, uint32_t value) "addr: 0x%08"PRIx32" data: 0x%08"PRIx32 pl330_iomem_write_clr(int i) "event interrupt lowered %d" pl330_iomem_read(uint32_t addr, uint32_t data) "addr: 0x%08"PRIx32" data: 0x%08"PRIx32 + +# hercules_dma.c +hercules_dma_transfer(int channel, int cftcount, int cetcount, uint32_t saddr, unsigned int res, uint32_t daddr, unsigned int wes) "DMA (%d): %d,%d 0x%x(%u) -> 0x%x(%u)" diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs index 3cfc261f9b74..1094bae042e4 100644 --- a/hw/gpio/Makefile.objs +++ b/hw/gpio/Makefile.objs @@ -10,3 +10,4 @@ common-obj-$(CONFIG_IMX) += imx_gpio.o common-obj-$(CONFIG_RASPI) += bcm2835_gpio.o common-obj-$(CONFIG_NRF51_SOC) += nrf51_gpio.o common-obj-$(CONFIG_ASPEED_SOC) += aspeed_gpio.o +common-obj-$(CONFIG_HERCULES) += hercules_gpio.o diff --git a/hw/gpio/hercules_gpio.c b/hw/gpio/hercules_gpio.c new file mode 100644 index 000000000000..9caa8de61adc --- /dev/null +++ b/hw/gpio/hercules_gpio.c @@ -0,0 +1,491 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" + +#include "hw/gpio/hercules_gpio.h" +#include "hw/arm/hercules.h" + +#include "trace.h" + +enum { + HERCULES_GIO_REGS_SIZE = 0x34, + HERCULES_GIO_GIO_SIZE = 0x20, + HERCULES_GIO_REGS_OFFSET = 0x00, + HERCULES_GIO_GIOA_OFFSET = 0x34, + HERCULES_GIO_GIOB_OFFSET = 0x54, +}; + +enum HerculesGioRegRegisters { + GIOGCR0 = 0x00, + GIOINTDET = 0x08, + GIOPOL = 0x0C, + GIOENASET = 0x10, + GIOENACLR = 0x14, + GIOLVLSET = 0x18, + GIOLVLCLR = 0x1C, + GIOFLG = 0x20, + GIOOFF1 = 0x24, + GIOOFF2 = 0x28, + GIOEMU1 = 0x2C, + GIOEMU2 = 0x30, +}; + +enum HerculesGioGioRegisters { + GIODIR = 0x00, + GIODIN = 0x04, + GIODOUT = 0x08, + GIODSET = 0x0C, + GIODCLR = 0x10, + GIOPDR = 0x14, + GIOPULDIS = 0x18, + GIOPSL = 0x1C, +}; + +enum HerculesHetRegisters { + HETDIR = 0x4C, + HETDIN = 0x50, + HETDOUT = 0x54, + HETDSET = 0x58, + HETDCLR = 0x5c, + HETLBPSEL = 0x8C, + HETLBPDIR = 0x90, + HETPINDIS = 0x94, +}; + +#define HETLBPDIR_LBPTSTENA(v) extract32(v, 16, 4) + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +static uint64_t hercules_gio_gio_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesGpio *gpio = opaque; + + switch (offset) { + case GIODIR: + return gpio->dir; + case GIODIN: + return gpio->din; + case GIODSET: + case GIODCLR: + case GIODOUT: + return gpio->dout; + case GIOPDR: + return gpio->pdr; + case GIOPULDIS: + return gpio->puldis; + case GIOPSL: + return gpio->psl; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_gio_update_din(HerculesGpio *gpio) +{ + trace_hercules_gio_update(gpio->bank, + gpio->din, gpio->dir, gpio->dout); + + gpio->din &= ~gpio->dir; + gpio->din |= gpio->dout & gpio->dir; + + trace_hercules_gio_update(gpio->bank, + gpio->din, gpio->dir, gpio->dout); +} + +static void hercules_gio_gio_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesGpio *gpio = opaque; + const uint32_t val = val64; + + switch (offset) { + case GIODIR: + gpio->dir = val; + hercules_gio_update_din(gpio); + break; + case GIODIN: + break; + case GIODOUT: + gpio->dout = val; + hercules_gio_update_din(gpio); + break; + case GIODSET: + gpio->dout |= val; + hercules_gio_update_din(gpio); + break; + case GIODCLR: + gpio->dout &= ~val; + hercules_gio_update_din(gpio); + break; + case GIOPDR: + gpio->pdr = val; + break; + case GIOPULDIS: + gpio->puldis = val; + break; + case GIOPSL: + gpio->psl = val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static uint64_t hercules_gio_reg_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesGioState *s = opaque; + + switch (offset) { + case GIOENACLR: + return s->gioena; + case GIOLVLSET: + case GIOLVLCLR: + return s->giolvl; + case GIOFLG: + return s->gioflg; + case GIOGCR0: + case GIOINTDET: + case GIOPOL: + case GIOENASET: + case GIOOFF1: + case GIOOFF2: + case GIOEMU1: + case GIOEMU2: + break; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_gio_reg_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesGioState *s = opaque; + uint32_t val = val64; + + switch (offset) { + case GIOENASET: + s->gioena |= val; + break; + case GIOENACLR: + s->gioena &= ~val; + break; + case GIOLVLSET: + s->giolvl |= val; + break; + case GIOLVLCLR: + s->giolvl &= ~val; + break; + case GIOFLG: + s->gioflg = val; + break; + case GIOGCR0: + case GIOINTDET: + case GIOPOL: + case GIOOFF1: + case GIOOFF2: + case GIOEMU1: + case GIOEMU2: + break; + default: + qemu_log_bad_offset(offset); + } +} + +static uint64_t hercules_n2het_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesN2HetState *s = opaque; + + switch (offset) { + case HETDIN: + return s->gpio.din; + case HETDIR: + return s->gpio.dir; + case HETDSET: + case HETDCLR: + case HETDOUT: + return s->gpio.dout; + case HETLBPDIR: + return s->hetlbpdir; + case HETLBPSEL: + case HETPINDIS: + /* always zero */ + break; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_n2het_update_gpios(HerculesN2HetState *s) +{ + const bool loopback = HETLBPDIR_LBPTSTENA(s->hetlbpdir) == 0xA; + + if (unlikely(loopback)) { + int i, n; + + s->gpio.din = s->gpio.dout; + + for (n = 0, i = 0; i < 16; i++, n += 2) { + const uint32_t bits = extract32(s->gpio.dout, n, 2); + const uint8_t pattern = 0b00111100; + unsigned int shift; + + if (bits == 0b00 || bits == 0b11) { + /* Nothing to do both bits are equal */ + continue; + } + + shift = bits & ~BIT(0); /* 0b01 -> 0, 0b10 -> 2 */ + + /* + * false: [n + 1] -> [n] + * true: [n] -> [n + 1] + */ + if (BIT(i) & s->hetlbpdir) { + shift += 4; + } + + s->gpio.din = deposit32(s->gpio.din, n, 2, pattern >> shift); + } + } else { + hercules_gio_update_din(&s->gpio); + } +} + +static void hercules_n2het_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesN2HetState *s = opaque; + uint32_t val = val64; + + switch (offset) { + case HETLBPSEL: + case HETPINDIS: + case HETDIN: + break; + case HETDIR: + s->gpio.dir = val; + hercules_n2het_update_gpios(s); + break; + case HETDOUT: + s->gpio.dout = val; + hercules_n2het_update_gpios(s); + break; + case HETDSET: + s->gpio.dout |= val; + hercules_n2het_update_gpios(s); + break; + case HETDCLR: + s->gpio.dout &= ~val; + hercules_n2het_update_gpios(s); + break; + case HETLBPDIR: + s->hetlbpdir = val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_gio_realize(DeviceState *dev, Error **errp) +{ + HerculesGioState *s = HERCULES_GIO(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_gio_gio_ops = { + .read = hercules_gio_gio_read, + .write = hercules_gio_gio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_gio_regs_ops = { + .read = hercules_gio_reg_read, + .write = hercules_gio_reg_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_gio_gio_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_gio_regs_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->io.regs, OBJECT(dev), &hercules_gio_regs_ops, + s, TYPE_HERCULES_GIO ".io.regs", + HERCULES_GIO_REGS_SIZE); + memory_region_init_io(&s->io.gioa, OBJECT(dev), &hercules_gio_gio_ops, + &s->gpio[0], TYPE_HERCULES_GIO ".io.gioa", + HERCULES_GIO_GIO_SIZE); + memory_region_init_io(&s->io.giob, OBJECT(dev), &hercules_gio_gio_ops, + &s->gpio[1], TYPE_HERCULES_GIO ".io.giob", + HERCULES_GIO_GIO_SIZE); + + memory_region_init(&s->io.container, OBJECT(dev), + TYPE_HERCULES_GIO ".io", + HERCULES_GIO_REGS_SIZE + + HERCULES_GIO_GIO_SIZE + + HERCULES_GIO_GIO_SIZE); + + memory_region_add_subregion(&s->io.container, HERCULES_GIO_REGS_OFFSET, + &s->io.regs); + memory_region_add_subregion(&s->io.container, HERCULES_GIO_GIOA_OFFSET, + &s->io.gioa); + memory_region_add_subregion(&s->io.container, HERCULES_GIO_GIOB_OFFSET, + &s->io.giob); + + sysbus_init_mmio(sbd, &s->io.container); +} + +static void hercules_gio_reset(DeviceState *d) +{ + HerculesGioState *s = HERCULES_GIO(d); + + memset(s->gpio, 0, sizeof(s->gpio)); + + s->gpio[0].bank = 0; + s->gpio[1].bank = 1; +} + +static void hercules_gio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_gio_reset; + dc->realize = hercules_gio_realize; +} + +static void hercules_n2het_initfn(Object *obj) +{ + HerculesN2HetState *s = HERCULES_N2HET(obj); + + sysbus_init_child_obj(obj, "n2het-ram", &s->ram, + sizeof(UnimplementedDeviceState), + TYPE_UNIMPLEMENTED_DEVICE); +} + +static void hercules_n2het_realize(DeviceState *dev, Error **errp) +{ + HerculesN2HetState *s = HERCULES_N2HET(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_n2het_ops = { + .read = hercules_n2het_read, + .write = hercules_n2het_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_n2het_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_n2het_ops, s, + TYPE_HERCULES_N2HET ".io", HERCULES_N2HET_REG_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + qdev_prop_set_string(DEVICE(&s->ram), "name", "n2het-ram"); + qdev_prop_set_uint64(DEVICE(&s->ram), "size", HERCULES_N2HET_RAM_SIZE); + object_property_set_bool(OBJECT(&s->ram), true, "realized", + &error_fatal); + + sysbus_init_mmio(sbd, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->ram), 0)); +} + +static void hercules_n2het_reset(DeviceState *d) +{ + HerculesN2HetState *s = HERCULES_N2HET(d); + + s->hetlbpdir = 0x00050000; + + uint32_t bank = s->gpio.bank; + memset(&s->gpio, 0, sizeof(s->gpio)); + s->gpio.bank = bank; +} + +static void hercules_n2het_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_n2het_reset; + dc->realize = hercules_n2het_realize; +} + +static const TypeInfo hercules_gio_info = { + .name = TYPE_HERCULES_GIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesGioState), + .class_init = hercules_gio_class_init, +}; + +static const TypeInfo hercules_n2het_info = { + .name = TYPE_HERCULES_N2HET, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesN2HetState), + .instance_init = hercules_n2het_initfn, + .class_init = hercules_n2het_class_init, +}; + +static void hercules_gpio_register_types(void) +{ + type_register_static(&hercules_gio_info); + type_register_static(&hercules_n2het_info); +} + +type_init(hercules_gpio_register_types) diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events index c1271fdfb27b..3a237b053eaf 100644 --- a/hw/gpio/trace-events +++ b/hw/gpio/trace-events @@ -5,3 +5,7 @@ nrf51_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PR nrf51_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64 nrf51_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64 nrf51_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64 + +# hercules_gpio.c + +hercules_gio_update(uint32_t bank, uint32_t din, uint32_t dir, uint32_t dout) "bank %" PRId32 " din 0x%" PRIx32 " dir 0x%" PRIx32 " dout 0x%" PRIx32 diff --git a/hw/intc/Makefile.objs b/hw/intc/Makefile.objs index f726d8753276..365b78b1e541 100644 --- a/hw/intc/Makefile.objs +++ b/hw/intc/Makefile.objs @@ -49,3 +49,4 @@ obj-$(CONFIG_ARM_GIC) += arm_gicv3_cpuif.o obj-$(CONFIG_MIPS_CPS) += mips_gic.o obj-$(CONFIG_NIOS2) += nios2_iic.o obj-$(CONFIG_OMPIC) += ompic.o +obj-$(CONFIG_HERCULES) += hercules_vim.o diff --git a/hw/intc/hercules_vim.c b/hw/intc/hercules_vim.c new file mode 100644 index 000000000000..3e74752993cc --- /dev/null +++ b/hw/intc/hercules_vim.c @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "cpu.h" + +#include "hw/intc/hercules_vim.h" +#include "hw/arm/hercules.h" + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesVimRegisters { + IRQINDEX = 0x00, + FIQINDEX = 0x04, + FIRQPR0 = 0x10, + FIRQPR1 = 0x14, + FIRQPR2 = 0x18, + FIRQPR3 = 0x1C, + REQENASET0 = 0x30, + REQENASET1 = 0x34, + REQENASET2 = 0x38, + REQENASET3 = 0x3C, + REQENACLR0 = 0x40, + REQENACLR1 = 0x44, + REQENACLR2 = 0x48, + REQENACLR3 = 0x4C, + IRQVECREG = 0x70, + FIQVECREG = 0x74, + CHANCTRL0 = 0x80, + CHANCTRL31 = 0xFC, +}; + +static void hercules_vim_update_line(HerculesVimState *s, + unsigned long *mask, + qemu_irq irq) +{ + int group; + + for (group = 0; group < HERCULES_NUM_IRQ_GROUP; group++) { + if (s->intreq[group] & s->reqena[group] & mask[group]) { + qemu_irq_raise(irq); + return; + } + } + + qemu_irq_lower(irq); +} + +/* Update interrupts. */ +static void hercules_vim_update(HerculesVimState *s) +{ + hercules_vim_update_line(s, s->rpqrif, s->irq); + hercules_vim_update_line(s, s->firqpr, s->fiq); +} + +static void hercules_vim_set_irq(void *opaque, int irq, int level) +{ + HerculesVimState *s = opaque; + int group, index; + unsigned long bit; + /* + * Map physical IRQ line to a channel + */ + irq = s->chanctrl[irq]; + + group = irq / HERCULES_IRQ_GROUP_WIDTH; + index = irq % HERCULES_IRQ_GROUP_WIDTH; + bit = BIT(index); + + if (level) { + s->intreq[group] |= bit; + } else { + s->intreq[group] &= ~bit; + } + + if (unlikely(s->firqpr[group] & bit)) { + hercules_vim_update_line(s, s->firqpr, s->fiq); + } else { + hercules_vim_update_line(s, s->rpqrif, s->irq); + } +} + +static uint32_t hercules_vim_line_index(HerculesVimState *s, + unsigned long *mask) +{ + int group; + + for (group = 0; group < HERCULES_NUM_IRQ_GROUP; group++) { + const unsigned long active = s->intreq[group] & mask[group]; + if (active) { + return HERCULES_IRQ_GROUP_WIDTH * group + ctzl(active) + 1; + } + } + + return 0; +} + +static uint32_t hercules_vim_irq_index(HerculesVimState *s) +{ + return hercules_vim_line_index(s, s->rpqrif); +} + +static uint32_t hercules_vim_fiq_index(HerculesVimState *s) +{ + return hercules_vim_line_index(s, s->firqpr); +} + +static uint32_t hercules_vim_read_vector(HerculesVimState *s, int idx) +{ + /* + * FIXME: Is this the best way to deal with endianness RM57 vs + * TMS570 + */ + const bool big_endian = s->iomem.ops->endianness == DEVICE_BIG_ENDIAN; + const uint32_t *vector = &s->vectors[idx]; + + return big_endian ? ldl_be_p(vector) : ldl_le_p(vector); +} + +static uint64_t hercules_vim_read(void *opaque, hwaddr offset, unsigned size) +{ + HerculesVimState *s = opaque; + + switch (offset) { + case IRQINDEX: + return hercules_vim_irq_index(s); + case FIQINDEX: + return hercules_vim_fiq_index(s); + case FIRQPR0: + return s->firqpr0; + case FIRQPR1: + return s->firqpr1; + case FIRQPR2: + return s->firqpr2; + case FIRQPR3: + return s->firqpr3; + case IRQVECREG: + return hercules_vim_read_vector(s, hercules_vim_irq_index(s)); + case FIQVECREG: + return hercules_vim_read_vector(s, hercules_vim_fiq_index(s)); + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_vim_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesVimState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case IRQINDEX: + case IRQVECREG: + case FIQVECREG: + case FIQINDEX: + /* no-op */ + break; + case FIRQPR0: + s->firqpr0 = val; + s->rpqrif0 = ~val; + break; + case FIRQPR1: + s->firqpr1 = val; + s->rpqrif1 = ~val; + break; + case FIRQPR2: + s->firqpr2 = val; + s->rpqrif2 = ~val; + break; + case FIRQPR3: + s->firqpr3 = val; + s->rpqrif3 = ~val; + break; + case REQENASET0: + s->reqena0 |= val; + hercules_vim_update(s); + break; + case REQENACLR0: + s->reqena0 &= ~val; + hercules_vim_update(s); + break; + case REQENASET1: + s->reqena1 |= val; + hercules_vim_update(s); + break; + case REQENACLR1: + s->reqena1 &= ~val; + hercules_vim_update(s); + break; + case REQENASET2: + s->reqena2 |= val; + hercules_vim_update(s); + break; + case REQENACLR2: + s->reqena2 &= ~val; + hercules_vim_update(s); + break; + case REQENASET3: + s->reqena3 |= val; + hercules_vim_update(s); + break; + case REQENACLR3: + s->reqena3 &= ~val; + hercules_vim_update(s); + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_vim_reset(DeviceState *d) +{ + HerculesVimState *s = HERCULES_VIM(d); + int i; + + memset(s->vectors, 0, sizeof(s->vectors)); + + for (i = 0; i < HERCULES_NUM_IRQ_GROUP; i++) { + s->intreq[i] = 0; + s->reqena[i] = 0; + s->firqpr[i] = 0; + s->rpqrif[i] = ~s->firqpr[i]; + } + + s->firqpr[0] = 0b11; + s->rpqrif[0] = ~s->firqpr[0]; + + for (i = 0; i < HERCULES_NUM_IRQ; i++) { + s->chanctrl[i] = i; + } + + hercules_vim_update(s); +} + +static void hercules_vim_initfn(Object *obj) +{ + HerculesVimState *s = HERCULES_VIM(obj); + + sysbus_init_child_obj(obj, "ecc-regs", &s->ecc, + sizeof(UnimplementedDeviceState), + TYPE_UNIMPLEMENTED_DEVICE); +} + +static void hercules_vim_realize(DeviceState *dev, Error **errp) +{ + HerculesVimState *s = HERCULES_VIM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_vim_ops = { + .read = hercules_vim_read, + .write = hercules_vim_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_vim_ops.endianness = DEVICE_BIG_ENDIAN; + } + + qdev_prop_set_string(DEVICE(&s->ecc), "name", "ecc-regs"); + qdev_prop_set_uint64(DEVICE(&s->ecc), "size", 256); + object_property_set_bool(OBJECT(&s->ecc), true, "realized", &error_fatal); + sysbus_init_mmio(sbd, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->ecc), 0)); + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_vim_ops, + s, "hercules.vim", 256); + sysbus_init_mmio(sbd, &s->iomem); + + memory_region_init_ram_ptr(&s->ram, OBJECT(dev), TYPE_HERCULES_VIM ".ram", + sizeof(s->vectors), s->vectors); + sysbus_init_mmio(sbd, &s->ram); + + qdev_init_gpio_in(dev, hercules_vim_set_irq, HERCULES_NUM_IRQ); + + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->fiq); +} + +static void hercules_vim_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_vim_reset; + dc->realize = hercules_vim_realize; +} + +static const TypeInfo hercules_vim_info = { + .name = TYPE_HERCULES_VIM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesVimState), + .instance_init = hercules_vim_initfn, + .class_init = hercules_vim_class_init, +}; + +static void hercules_vim_register_types(void) +{ + type_register_static(&hercules_vim_info); +} + +type_init(hercules_vim_register_types) diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index b25181b7113b..3bafdbf92133 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -86,6 +86,12 @@ common-obj-$(CONFIG_ASPEED_SOC) += aspeed_xdma.o common-obj-$(CONFIG_ASPEED_SOC) += aspeed_scu.o aspeed_sdmc.o common-obj-$(CONFIG_MSF2) += msf2-sysreg.o common-obj-$(CONFIG_NRF51_SOC) += nrf51_rng.o +common-obj-$(CONFIG_HERCULES) += hercules_system.o hercules_scm.o \ + hercules_esm.o hercules_efuse.o \ + hercules_l2ramw.o hercules_pmm.o \ + hercules_stc.o hercules_pbist.o \ + hercules_ccm.o hercules_l2fmc.o \ + hercules_ecap.o obj-$(CONFIG_MAC_VIA) += mac_via.o common-obj-$(CONFIG_GRLIB) += grlib_ahb_apb_pnp.o diff --git a/hw/misc/hercules_ccm.c b/hw/misc/hercules_ccm.c new file mode 100644 index 000000000000..8464427b0fa3 --- /dev/null +++ b/hw/misc/hercules_ccm.c @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_ccm.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_CCM_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesCCMRegisters { + CCMSR1 = 0x00, + CCMKEYR1 = 0x04, + CCMSR2 = 0x08, + CCMKEYR2 = 0x0C, + CCMSR3 = 0x10, + CCMKEYR3 = 0x14, + MKEYn_SELF_TEST = 0x6, + MKEYn_ERROR_FORCING = 0x9, + MKEYn_SELF_TEST_ERROR_FORCING = 0xF, + STCn = BIT(8), +}; + +static uint64_t hercules_ccm_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesCCMState *s = opaque; + + switch (offset) { + + case CCMSR1: + return s->ccmsr[0]; + case CCMSR2: + return s->ccmsr[1]; + case CCMSR3: + return s->ccmsr[2]; + case CCMKEYR1: + case CCMKEYR2: + case CCMKEYR3: + break; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_ccm_test(HerculesCCMState *s, unsigned int idx, + uint32_t val) +{ + switch (val) { + case MKEYn_SELF_TEST: + s->ccmsr[idx] |= STCn; + break; + case MKEYn_ERROR_FORCING: + qemu_irq_raise(s->error[idx]); + qemu_irq_raise(s->error_self_test); + break; + case MKEYn_SELF_TEST_ERROR_FORCING: + qemu_irq_raise(s->error_self_test); + break; + } +} + +static void hercules_ccm_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesCCMState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case CCMSR1: + s->ccmsr[0] &= ~val; + break; + case CCMSR2: + s->ccmsr[1] &= ~val; + break; + case CCMSR3: + s->ccmsr[2] &= ~val; + break; + case CCMKEYR1: + hercules_ccm_test(s, 0, val); + break; + case CCMKEYR2: + hercules_ccm_test(s, 1, val); + break; + case CCMKEYR3: + hercules_ccm_test(s, 2, val); + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_ccm_realize(DeviceState *dev, Error **errp) +{ + HerculesCCMState *s = HERCULES_CCM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_ccm_ops = { + .read = hercules_ccm_read, + .write = hercules_ccm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_ccm_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, obj, &hercules_ccm_ops, + s, TYPE_HERCULES_CCM ".io", HERCULES_CCM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->error[0]); + sysbus_init_irq(sbd, &s->error[1]); + sysbus_init_irq(sbd, &s->error[2]); + sysbus_init_irq(sbd, &s->error_self_test); +} + +static void hercules_ccm_reset(DeviceState *d) +{ + HerculesCCMState *s = HERCULES_CCM(d); + + memset(s->ccmsr, 0, sizeof(s->ccmsr)); +} + +static void hercules_ccm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_ccm_reset; + dc->realize = hercules_ccm_realize; +} + +static const TypeInfo hercules_ccm_info = { + .name = TYPE_HERCULES_CCM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesCCMState), + .class_init = hercules_ccm_class_init, +}; + +static void hercules_ccm_register_types(void) +{ + type_register_static(&hercules_ccm_info); +} +type_init(hercules_ccm_register_types) diff --git a/hw/misc/hercules_ecap.c b/hw/misc/hercules_ecap.c new file mode 100644 index 000000000000..d0fceea16d47 --- /dev/null +++ b/hw/misc/hercules_ecap.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_ecap.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_ECAP_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesECAPRegisters { + TSCTR = 0x00, + CTRPHS = 0x04, + CAP1 = 0x08, + CAP2 = 0x0C, + CAP3 = 0x10, + CAP4 = 0x14, + + ECCTL2 = 0x28, + ECCTL1 = 0x2A, + ECFLG = 0x2C, + ECEINT = 0x2E, + ECFRC = 0x30, + ECCLR = 0x32, +}; + +static uint64_t hercules_ecap_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesECAPState *s = opaque; + + switch (size) { + case sizeof(uint16_t): + switch (offset) { + case ECCTL1: + case ECCTL2: + case ECEINT: + case ECFRC: + case ECCLR: + return 0; + case ECFLG: + return s->ecflg; + } + break; + case sizeof(uint32_t): + switch (offset) { + case TSCTR: + return 0; + case CAP1 ... CAP4: + return s->cap[(offset - CAP1) / sizeof(uint32_t)]; + } + break; + } + + qemu_log_bad_offset(offset); + return 0; +} + +static void hercules_ecap_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesECAPState *s = opaque; + const uint32_t val = val64; + + switch (size) { + case sizeof(uint16_t): + switch (offset) { + case ECCTL1: + case ECCTL2: + case ECEINT: + case ECFRC: + break; + case ECCLR: + /* + * Currently a no-op on purpose. Once a given capture + * register and corresponding bit in ECFLG is set by an + * external entity, we want it to remain set in order to + * make guest think that we are constantly capturing a + * waveform of given frequency + */ + break; + } + break; + case sizeof(uint32_t): + switch (offset) { + case TSCTR: + break; + case CAP1 ... CAP4: + s->cap[(offset - CAP1) / sizeof(uint32_t)] = val; + break; + default: + qemu_log_bad_offset(offset); + } + break; + + } +} + +static void hercules_ecap_realize(DeviceState *dev, Error **errp) +{ + HerculesECAPState *s = HERCULES_ECAP(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_ecap_ops = { + .read = hercules_ecap_read, + .write = hercules_ecap_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 2, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_ecap_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, obj, &hercules_ecap_ops, + s, TYPE_HERCULES_ECAP ".io", HERCULES_ECAP_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static void hercules_ecap_reset(DeviceState *d) +{ + HerculesECAPState *s = HERCULES_ECAP(d); + + memset(s->cap, 0, sizeof(s->cap)); + s->ecflg = 0; +} + +static void hercules_ecap_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_ecap_reset; + dc->realize = hercules_ecap_realize; +} + +static const TypeInfo hercules_ecap_info = { + .name = TYPE_HERCULES_ECAP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesECAPState), + .class_init = hercules_ecap_class_init, +}; + +static void hercules_ecap_register_types(void) +{ + type_register_static(&hercules_ecap_info); +} +type_init(hercules_ecap_register_types) diff --git a/hw/misc/hercules_efuse.c b/hw/misc/hercules_efuse.c new file mode 100644 index 000000000000..ae768ec609be --- /dev/null +++ b/hw/misc/hercules_efuse.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_efuse.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_EFUSE_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesEFuseRegisters { + EFCBOUND = 0x1C, + EFCBOUND_SELF_TEST_ERR = BIT(21), + EFCBOUND_SINGLE_BIT_ERR = BIT(20), + EFCBOUND_INSTR_ERR = BIT(19), + EFCBOUND_AUTOLOAD_ERR = BIT(18), + EFCBOUND_OUTPUT_EN = (0xf << 14), + EFCBOUND_INPUT_EN = (0xf << 0), + EFCBOUND_SELF_TEST_EN = BIT(13), + + EFCPINS = 0x2C, + EFCPINS_SELF_TEST_DONE = BIT(15), + EFCPINS_SELF_TEST_ERR = BIT(14), + EFCPINS_SINGLE_BIT_ERR = BIT(12), + EFCPINS_INSTR_ERR = BIT(11), + EFCPINS_AUTOLOAD_ERR = BIT(10), + EFCERRSTAT = 0x3C, + + EFCSTCY = 0x48, + EFCSTSIG = 0x4C, +}; + +static uint64_t hercules_efuse_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesEFuseState *s = opaque; + + switch (offset) { + case EFCPINS: + return s->efcpins; + case EFCBOUND: + case EFCERRSTAT: + break; + case EFCSTCY: + return s->efcstcy; + case EFCSTSIG: + return s->efcstsig; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_efuse_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesEFuseState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case EFCBOUND: + if ((val & EFCBOUND_INPUT_EN) == EFCBOUND_INPUT_EN) { + if (val & EFCBOUND_SELF_TEST_EN && + s->efcstcy == 0x00000258 && + s->efcstsig == 0x5362F97F) { + s->efcpins = EFCPINS_SELF_TEST_DONE; + } + } + + if ((val & EFCBOUND_OUTPUT_EN) == EFCBOUND_OUTPUT_EN) { + if (val & EFCBOUND_AUTOLOAD_ERR) { + s->efcpins |= EFCPINS_AUTOLOAD_ERR; + qemu_irq_raise(s->autoload_error); + } + + if (val & EFCBOUND_INSTR_ERR) { + s->efcpins |= EFCPINS_INSTR_ERR; + } + + if (val & EFCBOUND_SINGLE_BIT_ERR) { + s->efcpins |= EFCPINS_SINGLE_BIT_ERR; + } + + if (val & EFCBOUND_SELF_TEST_ERR) { + s->efcpins |= EFCPINS_SELF_TEST_ERR; + qemu_irq_raise(s->self_test_error); + } + } + case EFCPINS: + case EFCERRSTAT: + break; + case EFCSTCY: + s->efcstcy = val; + break; + case EFCSTSIG: + s->efcstsig = val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_efuse_realize(DeviceState *dev, Error **errp) +{ + HerculesEFuseState *s = HERCULES_EFUSE(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_efuse_ops = { + .read = hercules_efuse_read, + .write = hercules_efuse_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_efuse_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_efuse_ops, + s, TYPE_HERCULES_EFUSE ".io", HERCULES_EFUSE_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->autoload_error); + sysbus_init_irq(sbd, &s->self_test_error); + sysbus_init_irq(sbd, &s->single_bit_error); +} + +static void hercules_efuse_reset(DeviceState *d) +{ + HerculesEFuseState *s = HERCULES_EFUSE(d); + + s->efcpins = 0; +} + +static void hercules_efuse_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_efuse_reset; + dc->realize = hercules_efuse_realize; +} + +static const TypeInfo hercules_efuse_info = { + .name = TYPE_HERCULES_EFUSE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesEFuseState), + .class_init = hercules_efuse_class_init, +}; + +static void hercules_efuse_register_types(void) +{ + type_register_static(&hercules_efuse_info); +} + +type_init(hercules_efuse_register_types) diff --git a/hw/misc/hercules_esm.c b/hw/misc/hercules_esm.c new file mode 100644 index 000000000000..12e01b654595 --- /dev/null +++ b/hw/misc/hercules_esm.c @@ -0,0 +1,422 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_esm.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_ESM_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesESMRegisters { + ESMDEPAPR1 = 0x00, + ESMEEPAPR1 = 0x04, + ESMIESR1 = 0x08, + ESMIECR1 = 0x0C, + ESMILSR1 = 0x10, + ESMILCR1 = 0x14, + ESMSR1 = 0x18, + ESMSR2 = 0x1C, + ESMSR3 = 0x20, + ESMEPSR = 0x24, + ESMIOFFHR = 0x28, + ESMIOFFLR = 0x2C, + + ESMLTCPR = 0x34, + ESMEKR = 0x38, + ESMSSR2 = 0x3C, + + ESMIEPSR4 = 0x40, + ESMIEPCR4 = 0x44, + ESMIESR4 = 0x48, + ESMIECR4 = 0x4C, + ESMILSR4 = 0x50, + ESMILCR4 = 0x54, + ESMSR4 = 0x58, + + ESMIEPSR7 = 0x80, + ESMIEPSC7 = 0x84, + ESMIESR7 = 0x88, + ESMIECR7 = 0x8C, + ESMILSR7 = 0x90, + ESMILCR7 = 0x94, + ESMSR7 = 0x98, +}; + +enum { + ESM_R1, + ESM_R4, + ESM_R7, + ESM_R2, + ESM_R3, +}; + +static void hercules_esm_irq_lower(HerculesESMState *s, int n) +{ + const unsigned int bit = BIT(n); + + if (s->irq_state & bit) { + qemu_irq_lower(s->irq[n]); + s->irq_state &= ~bit; + } +} + +static void hercules_esm_irq_raise(HerculesESMState *s, int n) +{ + const unsigned int bit = BIT(n); + + if (s->irq_state & bit) { + return; + } + + s->irq_state |= bit; + qemu_irq_raise(s->irq[n]); +} + +static void hercules_esm_set_error(void *opaque, int error, int level) +{ + HerculesESMState *s = opaque; + unsigned int idx; + unsigned int bit; + + if (level) { + switch (error) { + case 0 ... 95: + idx = error / 32; + bit = BIT(error % 32); + + s->esmsr[idx] |= bit; + + if (s->esmie[idx] & bit) { + hercules_esm_irq_raise(s, (s->esmil[idx] & bit) ? + HERCULES_ESM_IRQ_HIGH : + HERCULES_ESM_IRQ_LOW); + } + break; + case 96 ... 127: + /* group 2 */ + s->esmsr[ESM_R2] |= BIT(error - 96); + hercules_esm_irq_raise(s, HERCULES_ESM_IRQ_HIGH); + break; + case 128 ... 159: + s->esmsr[ESM_R3] |= BIT(error - 128); + /* FIXME: Need to abort here */ + /* fall through */ + default: + return; + } + } +} + +enum HerculesESMIOffMask { + ESMIOFF_HIGH = 0x00000000, + ESMIOFF_LOW = 0xFFFFFFFF, +}; + +static unsigned int hercules_esm_interrupt_offset_high(HerculesESMState *s) +{ + const uint32_t pending = s->esmie[ESM_R2] & s->esmsr[ESM_R2]; + + if (pending) { + return 0x21 + ctzl(pending); + } + + return 0; +} + +static unsigned int +hercules_esm_interrupt_offset_low(HerculesESMState *s, + enum HerculesESMIOffMask mask) +{ + uint32_t pending; + + pending = s->esmie[ESM_R1] & (s->esmsr[ESM_R1] ^ mask); + if (pending) { + return 0x01 + ctzl(pending); + } + + pending = s->esmie[ESM_R4] & (s->esmsr[ESM_R4] ^ mask); + if (pending) { + return 0x41 + ctzl(pending); + } + + pending = s->esmie[ESM_R7] & (s->esmsr[ESM_R7] ^ mask); + if (pending) { + return 0x81 + ctzl(pending); + } + + return 0; +} + +static uint64_t hercules_esm_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesESMState *s = opaque; + int irq; + + switch (offset) { + case ESMIESR1: + case ESMIECR1: + return s->esmie[ESM_R1]; + case ESMILSR1: + case ESMILCR1: + return s->esmil[ESM_R1]; + case ESMSR1: + return s->esmsr[ESM_R1]; + case ESMSR2: + return s->esmsr[ESM_R2]; + case ESMSR3: + return s->esmsr[ESM_R3]; + case ESMIOFFHR: + irq = hercules_esm_interrupt_offset_high(s); + if (irq) { + return irq; + } + return hercules_esm_interrupt_offset_low(s, ESMIOFF_HIGH); + case ESMIOFFLR: + return hercules_esm_interrupt_offset_low(s, ESMIOFF_LOW); + case ESMIEPSR4: + case ESMIEPCR4: + return s->esmiep[ESM_R4]; + case ESMIESR4: + case ESMIECR4: + return s->esmie[ESM_R4]; + case ESMILSR4: + case ESMILCR4: + return s->esmil[ESM_R4]; + case ESMSR4: + return s->esmsr[ESM_R4]; + case ESMIEPSR7: + case ESMIEPSC7: + return s->esmiep[ESM_R7]; + case ESMIESR7: + case ESMIECR7: + return s->esmie[ESM_R7]; + case ESMILSR7: + case ESMILCR7: + return s->esmil[ESM_R7]; + case ESMSR7: + return s->esmsr[ESM_R7]; + case ESMDEPAPR1: + case ESMEEPAPR1: + case ESMEPSR: + return 0; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_esm_update_irq_high(HerculesESMState *s) +{ + if (s->esmsr[ESM_R2] || + s->esmsr[ESM_R1] & s->esmie[ESM_R1] & s->esmil[ESM_R1] || + s->esmsr[ESM_R4] & s->esmie[ESM_R4] & s->esmil[ESM_R4] || + s->esmsr[ESM_R7] & s->esmie[ESM_R7] & s->esmil[ESM_R7]) { + return; + } + + hercules_esm_irq_lower(s, HERCULES_ESM_IRQ_HIGH); +} + +static void hercules_esm_update_irq_low(HerculesESMState *s) +{ + if (s->esmsr[ESM_R1] & s->esmie[ESM_R1] & ~s->esmil[ESM_R1] || + s->esmsr[ESM_R4] & s->esmie[ESM_R4] & ~s->esmil[ESM_R4] || + s->esmsr[ESM_R7] & s->esmie[ESM_R7] & ~s->esmil[ESM_R7]) { + return; + } + + hercules_esm_irq_lower(s, HERCULES_ESM_IRQ_LOW); +} + + +static void hercules_esm_update_irqs(HerculesESMState *s) +{ + hercules_esm_update_irq_high(s); + hercules_esm_update_irq_low(s); +} + +static void hercules_esm_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesESMState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case ESMIESR1: + s->esmie[ESM_R1] |= val; + break; + case ESMIECR1: + s->esmie[ESM_R1] &= ~val; + break; + case ESMILSR1: + s->esmil[ESM_R1] |= val; + break; + case ESMILCR1: + s->esmil[ESM_R1] &= ~val; + break; + case ESMSR1: + s->esmsr[ESM_R1] &= ~val; + hercules_esm_update_irqs(s); + break; + case ESMSR2: + s->esmsr[ESM_R2] &= ~val; + hercules_esm_update_irq_high(s); + break; + case ESMSR3: + s->esmsr[ESM_R3] &= ~val; + break; + case ESMIEPSR4: + s->esmiep[ESM_R4] |= val; + break; + case ESMIEPCR4: + s->esmiep[ESM_R4] &= ~val; + break; + case ESMIESR4: + s->esmie[ESM_R4] |= val; + hercules_esm_update_irqs(s); + break; + case ESMIECR4: + s->esmie[ESM_R4] &= ~val; + hercules_esm_update_irqs(s); + break; + case ESMILSR4: + s->esmil[ESM_R4] |= val; + break; + case ESMILCR4: + s->esmil[ESM_R4] &= ~val; + break; + case ESMSR4: + s->esmsr[ESM_R4] &= ~val; + hercules_esm_update_irqs(s); + break; + case ESMIEPSR7: + s->esmiep[ESM_R7] |= val; + break; + case ESMIEPSC7: + s->esmiep[ESM_R7] &= ~val; + break; + case ESMIESR7: + s->esmie[ESM_R7] |= val; + hercules_esm_update_irqs(s); + break; + case ESMIECR7: + s->esmie[ESM_R7] &= ~val; + hercules_esm_update_irqs(s); + break; + case ESMILSR7: + s->esmil[ESM_R7] |= val; + break; + case ESMILCR7: + s->esmil[ESM_R7] &= ~val; + break; + case ESMSR7: + s->esmsr[ESM_R7] &= ~val; + hercules_esm_update_irqs(s); + break; + case ESMIOFFHR: + case ESMIOFFLR: + case ESMDEPAPR1: + case ESMEEPAPR1: + case ESMLTCPR: + case ESMEKR: + case ESMSSR2: + /* No op */ + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_esm_realize(DeviceState *dev, Error **errp) +{ + HerculesESMState *s = HERCULES_ESM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_esm_ops = { + .read = hercules_esm_read, + .write = hercules_esm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_esm_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_esm_ops, + s, TYPE_HERCULES_ESM ".io", HERCULES_ESM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + qdev_init_gpio_in(dev, hercules_esm_set_error, HERCULES_NUM_ESM_CHANNELS); + + sysbus_init_irq(sbd, &s->irq[HERCULES_ESM_IRQ_HIGH]); + sysbus_init_irq(sbd, &s->irq[HERCULES_ESM_IRQ_LOW]); +} + +static void hercules_esm_reset(DeviceState *d) +{ + HerculesESMState *s = HERCULES_ESM(d); + + memset(s->esmsr, 0, sizeof(s->esmsr)); + memset(s->esmil, 0, sizeof(s->esmil)); + memset(s->esmie, 0, sizeof(s->esmie)); + memset(s->esmiep, 0, sizeof(s->esmiep)); + + hercules_esm_irq_lower(s, HERCULES_ESM_IRQ_LOW); + hercules_esm_irq_lower(s, HERCULES_ESM_IRQ_HIGH); +} + +static void hercules_esm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_esm_reset; + dc->realize = hercules_esm_realize; +} + +static const TypeInfo hercules_esm_info = { + .name = TYPE_HERCULES_ESM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesESMState), + .class_init = hercules_esm_class_init, +}; + +static void hercules_esm_register_types(void) +{ + type_register_static(&hercules_esm_info); +} + +type_init(hercules_esm_register_types) diff --git a/hw/misc/hercules_l2fmc.c b/hw/misc/hercules_l2fmc.c new file mode 100644 index 000000000000..4468a07fd510 --- /dev/null +++ b/hw/misc/hercules_l2fmc.c @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_l2fmc.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_L2FMC_SIZE = 4 * 1024, + HERCULES_EPC_SIZE = 1024, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesL2FMCRegisters { + FRDCNTL = 0x0000, + EE_FEDACCTRL1 = 0x0008, + FEDAC_PASTATUS = 0x0014, + FEDAC_PBSTATUS = 0x0018, + ADD_PAR_ERR = BIT(10), + ADD_TAG_ERR = BIT(11), + FEDAC_GBLSTATUS = 0x001C, + FEDACSDIS = 0x0024, + FPRIM_ADD_TAG = 0x0028, + FDUP_ADD_TAG = 0x002C, + FBPROT = 0x0030, + FBSE = 0x0034, + FBBUSY = 0x0038, + FBAC = 0x003C, + FBPWRMODE = 0x0040, + FBPRDY = 0x0044, + FPAC1 = 0x0048, + FMAC = 0x0050, + FMSTAT = 0x0054, + FEMU_DMSW = 0x0058, + FEMU_DLSW = 0x005C, + FEMU_ECC = 0x0060, + FLOCK = 0x0064, + FDIAGCTRL = 0x006C, + DIAG_TRIG = BIT(24), + FRAW_ADDR = 0x0074, + FPAR_OVR = 0x007C, + RCR_VALID = 0x00B4, + ACC_THRESHOLD = 0x00B8, + FEDACSDIS2 = 0x00C0, + RCR_VALUE0 = 0x00D0, + RCR_VALUE1 = 0x00D4, + FSM_WR_ENA = 0x0288, + EEPROM_CONFIG = 0x02B8, + FSM_SECTOR1 = 0x02C0, + FSM_SECTOR2 = 0x02C4, + FCFG_BANK = 0x02B8, +}; + +#define DIAG_EN_KEY(w) extract32(w, 16, 4) +#define DIAG_BUF_SEL(w) extract32(w, 8, 3) +#define DIAGMODE(w) extract32(w, 0, 3) +#define DIAGMODE_ADDR 0x5 +#define DIAGMODE_ECC 0x7 + +enum HerculesEPCRegisters { + EPCREVnID = 0x0000, + EPCCNTRL = 0x0004, + UERRSTAT = 0x0008, + EPCERRSTAT = 0x000C, + FIFOFULLSTAT = 0x0010, + OVRFLWSTAT = 0x0014, + CAMAVAILSTAT = 0x0018, +}; + +#define UERRADDR(n) (0x0020 + (n) * sizeof(uint32_t)) +#define CAM_CONTENT(n) (0x00A0 + (n) * sizeof(uint32_t)) +#define CAM_INDEX(n) (0x0200 + (n) * sizeof(uint32_t)) + +static uint64_t hercules_epc_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesL2FMCState *s = opaque; + + switch (offset) { + case CAMAVAILSTAT: + if (s->camavailstat) { + return ctzl(s->camavailstat) + 1; + } + break; + case CAM_CONTENT(0) ... CAM_CONTENT(31): + return s->cam_content[(offset - CAM_CONTENT(0)) / sizeof(uint32_t)]; + case CAM_INDEX(0) ... CAM_INDEX(7): + return s->cam_index[(offset - CAM_INDEX(0)) / sizeof(uint32_t)]; + } + + return 0; +} + +static void hercules_epc_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesL2FMCState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case CAMAVAILSTAT: + break; + case CAM_CONTENT(0) ... CAM_CONTENT(31): + s->cam_content[(offset - CAM_CONTENT(0)) / sizeof(uint32_t)] = val; + break; + case CAM_INDEX(0) ... CAM_INDEX(7): + s->cam_index[(offset - CAM_INDEX(0)) / sizeof(uint32_t)] = val; + s->camavailstat = 0; + break; + } +} + +static uint64_t hercules_l2fmc_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesL2FMCState *s = opaque; + + switch (offset) { + case FDIAGCTRL: + return s->fdiagctrl; + case FRAW_ADDR: + return s->fraw_addr; + case FPRIM_ADD_TAG: + return s->fprim_add_tag; + case FDUP_ADD_TAG: + return s->fdup_add_tag; + case FEDAC_PASTATUS: + return s->fedac_pastatus; + case FEDAC_PBSTATUS: + return s->fedac_pbstatus; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_l2fmc_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesL2FMCState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case FDIAGCTRL: + s->fdiagctrl = val; + + if (s->fdiagctrl & DIAG_TRIG && + DIAG_EN_KEY(s->fdiagctrl) == 0x5) { + uint32_t err_bit; + qemu_irq error; + + if (DIAGMODE(s->fdiagctrl) == DIAGMODE_ADDR) { + err_bit = ADD_TAG_ERR; + error = s->uncorrectable_error; + } else { + err_bit = ADD_PAR_ERR; + /* + * FIXME, we should calculate those value against + * reading all zeros or all Fs + */ + if (s->femu_ecc == 0xCE) { + error = s->correctable_error; + s->camavailstat |= BIT(0); + s->cam_content[0] = s->ecc_1bit_address; + } else { + error = s->bus_error; + } + } + + switch (DIAG_BUF_SEL(s->fdiagctrl)) { + case 2: + case 3: + return; + case 0: + case 1: + s->fedac_pastatus |= err_bit; + break; + case 4: + case 5: + case 6: + case 7: + s->fedac_pbstatus |= err_bit; + break; + } + + s->fdiagctrl &= ~DIAG_TRIG; + qemu_irq_raise(error); + } + break; + case FRAW_ADDR: + s->fraw_addr = val; + break; + case FPRIM_ADD_TAG: + s->fprim_add_tag = val; + break; + case FDUP_ADD_TAG: + s->fdup_add_tag = val; + break; + case FEDAC_PASTATUS: + s->fedac_pastatus &= ~val; + break; + case FEDAC_PBSTATUS: + s->fedac_pbstatus &= ~val; + break; + case FEMU_ECC: + s->femu_ecc = val; + break; + case FEMU_DMSW: + case FEMU_DLSW: + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_l2fmc_realize(DeviceState *dev, Error **errp) +{ + HerculesL2FMCState *s = HERCULES_L2FMC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_epc_ops = { + .read = hercules_epc_read, + .write = hercules_epc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_l2fmc_ops = { + .read = hercules_l2fmc_read, + .write = hercules_l2fmc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_epc_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_l2fmc_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, obj, &hercules_l2fmc_ops, + s, TYPE_HERCULES_L2FMC ".io", HERCULES_L2FMC_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->uncorrectable_error); + sysbus_init_irq(sbd, &s->bus_error); + sysbus_init_irq(sbd, &s->correctable_error); + + /* + * Technically EPC is a separate IP block, but our only use-case + * for it involves flash controller so dealing with it here + * simplifies things + */ + memory_region_init_io(&s->epc, obj, &hercules_epc_ops, + s, TYPE_HERCULES_L2FMC ".epc", HERCULES_EPC_SIZE); + sysbus_init_mmio(sbd, &s->epc); + + s->ecc_1bit_address = 0x00000008; + s->ecc_1bit_femu_ecc = 0xCE; +} + +static void hercules_l2fmc_reset(DeviceState *d) +{ + HerculesL2FMCState *s = HERCULES_L2FMC(d); + + s->fdiagctrl = 0; + s->fraw_addr = 0; + s->fprim_add_tag = 0; + s->fdup_add_tag = 0; + s->fedac_pastatus = 0; + s->fedac_pbstatus = 0; + s->femu_ecc = 0; +} + +static void hercules_l2fmc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_l2fmc_reset; + dc->realize = hercules_l2fmc_realize; +} + +static const TypeInfo hercules_l2fmc_info = { + .name = TYPE_HERCULES_L2FMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesL2FMCState), + .class_init = hercules_l2fmc_class_init, +}; + +static void hercules_l2fmc_register_types(void) +{ + type_register_static(&hercules_l2fmc_info); +} +type_init(hercules_l2fmc_register_types) diff --git a/hw/misc/hercules_l2ramw.c b/hw/misc/hercules_l2ramw.c new file mode 100644 index 000000000000..2d6dde21c575 --- /dev/null +++ b/hw/misc/hercules_l2ramw.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" + +#include "hw/misc/hercules_l2ramw.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_L2RAMW_CONTAINER_SIZE = 8 * 1024 * 1024, + HERCULES_L2RAMW_SRAM_SIZE = 512 * 1024, + HERCULES_L2RAMW_SRAM_OFFSET = 0, + HERCULES_L2RAMW_ECC_SIZE = HERCULES_L2RAMW_SRAM_SIZE, + HERCULES_L2RAMW_ECC_OFFSET = HERCULES_L2RAMW_CONTAINER_SIZE / 2, + HERCULES_L2RAMW_SIZE = 256, +}; + +enum HerculesL2RAMWRegisters { + RAMCTRL = 0x0000, + RAMERRSTATUS = 0x0010, + DRDE = BIT(22), + DRSE = BIT(21), + DWDE = BIT(20), + DWSE = BIT(19), + ADDE = BIT(4), + ADE = BIT(2), + DIAG_DATA_VECTOR_H = 0x0024, + DIAG_DATA_VECTOR_L = 0x0028, + DIAG_ECC = 0x002C, + RAMTEST = 0x0030, + TRIGGER = BIT(8), + RAMADDRDEC_VECT = 0x0038, + MEMINIT_DOMAIN = 0x003C, + BANK_DOMAIN_MAP0 = 0x0044, + BANK_DOMAIN_MAP1 = 0x0048, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +#define TEST_ENABLE(w) extract32(w, 0, 4) +#define TEST_MODE(w) extract32(w, 6, 2) + +static void hercules_l2ramw_write(void *opaque, hwaddr offset, uint64_t val64, + unsigned size) +{ + HerculesL2RamwState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case RAMCTRL: + s->ramctrl = val; + break; + case RAMTEST: + s->ramtest = val; + if (s->ramtest & TRIGGER) { + s->ramtest &= ~TRIGGER; + + switch (s->diag_ecc) { + case 0x03: + s->ramerrstatus |= DRDE | DWDE; + qemu_irq_raise(s->uncorrectable_error); + break; + case 0xCE: + s->ramerrstatus |= DRSE | DWSE; + qemu_irq_raise(s->uncorrectable_error); + break; + default: + break; + } + } + break; + case RAMERRSTATUS: + s->ramerrstatus &= ~val; + break; + case DIAG_ECC: + s->diag_ecc = val; + break; + case DIAG_DATA_VECTOR_H: + case DIAG_DATA_VECTOR_L: + case RAMADDRDEC_VECT: + case MEMINIT_DOMAIN: + case BANK_DOMAIN_MAP0: + case BANK_DOMAIN_MAP1: + break; + default: + qemu_log_bad_offset(offset); + } +} + +static uint64_t hercules_l2ramw_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesL2RamwState *s = opaque; + + switch (offset) { + case RAMCTRL: + return s->ramctrl; + case RAMTEST: + return s->ramtest; + case RAMERRSTATUS: + return s->ramerrstatus; + case DIAG_ECC: + return s->diag_ecc; + case DIAG_DATA_VECTOR_H: + case DIAG_DATA_VECTOR_L: + case RAMADDRDEC_VECT: + case MEMINIT_DOMAIN: + case BANK_DOMAIN_MAP0: + case BANK_DOMAIN_MAP1: + break; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_l2ramw_ecc_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + qemu_log_bad_offset(offset); +} + +static uint64_t hercules_l2ramw_ecc_read(void *opaque, hwaddr offset, + unsigned size) +{ + qemu_log_bad_offset(offset); + return 0; +} + +static void hercules_l2ramw_realize(DeviceState *dev, Error **errp) +{ + HerculesL2RamwState *s = HERCULES_L2RAMW(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_l2ramw_ops = { + .read = hercules_l2ramw_read, + .write = hercules_l2ramw_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_l2ramw_ecc_ops = { + .read = hercules_l2ramw_ecc_read, + .write = hercules_l2ramw_ecc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_l2ramw_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_l2ramw_ecc_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->io.ecc, obj, &hercules_l2ramw_ecc_ops, s, + TYPE_HERCULES_L2RAMW ".io.ecc", + HERCULES_L2RAMW_ECC_SIZE); + + memory_region_init_ram(&s->io.sram, obj, + TYPE_HERCULES_L2RAMW ".io.sram", + HERCULES_L2RAMW_SRAM_SIZE, &error_fatal); + + memory_region_init(&s->io.container, obj, + TYPE_HERCULES_L2RAMW ".io", + HERCULES_L2RAMW_CONTAINER_SIZE); + + memory_region_add_subregion(&s->io.container, HERCULES_L2RAMW_SRAM_OFFSET, + &s->io.sram); + memory_region_add_subregion(&s->io.container, HERCULES_L2RAMW_ECC_OFFSET, + &s->io.ecc); + + sysbus_init_mmio(sbd, &s->io.container); + + memory_region_init_io(&s->io.regs, obj, &hercules_l2ramw_ops, s, + TYPE_HERCULES_L2RAMW ".io.regs", + HERCULES_L2RAMW_SIZE); + + sysbus_init_mmio(sbd, &s->io.regs); + sysbus_init_irq(sbd, &s->uncorrectable_error); +} + +static void hercules_l2ramw_reset(DeviceState *dev) +{ + HerculesL2RamwState *s = HERCULES_L2RAMW(dev); + + s->ramctrl = 0; + s->ramtest = 0; + s->ramerrstatus = 0; + s->diag_ecc = 0; +} + +static void hercules_l2ramw_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_l2ramw_reset; + dc->realize = hercules_l2ramw_realize; + + dc->desc = "Hercules Level II RAM Module"; +} + +static const TypeInfo hercules_l2ramw_info = { + .name = TYPE_HERCULES_L2RAMW, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesL2RamwState), + .class_init = hercules_l2ramw_class_init, +}; + +static void hercules_l2ramw_register_types(void) +{ + type_register_static(&hercules_l2ramw_info); +} + +type_init(hercules_l2ramw_register_types) diff --git a/hw/misc/hercules_pbist.c b/hw/misc/hercules_pbist.c new file mode 100644 index 000000000000..6e58d6ef540c --- /dev/null +++ b/hw/misc/hercules_pbist.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_pbist.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_PBIST_SIZE = 512, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesPBISTRegisters { + RAMT = 0x160, + DLR = 0x164, + DLR2 = BIT(2), + STC = 0x16C, + PACT = 0x180, + FSRF0 = 0x190, + FSRF1 = 0x194, + FSRFx = BIT(0), + OVER = 0x188, + ROM = 0x1C0, + ALGO = 0x1C4, + RINFOL = 0x1C8, + RINFOU = 0x1CC, +}; + +static uint64_t hercules_pbist_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesPBISTState *s = opaque; + + switch (offset) { + case PACT: + return s->pact; + case FSRF0: + return s->fsrf[0]; + case FSRF1: + return s->fsrf[1]; + case OVER: + return s->over; + case ROM: + return s->rom; + case ALGO: + return s->algo; + case RINFOL: + return s->rinfol; + case RINFOU: + return s->rinfou; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_pbist_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesPBISTState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + /* + * Magic undocumented registers used by PBIST code + */ + case 0x00 ... 0x18: + case 0x40 ... 0x58: + break; + case RAMT: + case STC: + break; + case PACT: + s->pact = val; + break; + case DLR: + /* + * Not how HW works, but good enough to get things running + */ + if (val & DLR2) { + s->fsrf[0] = 0; + s->fsrf[1] = 0; + } else { + s->fsrf[0] = FSRFx; + s->fsrf[1] = FSRFx; + } + qemu_irq_raise(s->mstdone); + break; + case OVER: + s->over = val; + break; + case ROM: + s->rom = val; + break; + case ALGO: + s->algo = val; + break; + case RINFOL: + s->rinfol = val; + break; + case RINFOU: + s->rinfou = val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_pbist_realize(DeviceState *dev, Error **errp) +{ + HerculesPBISTState *s = HERCULES_PBIST(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_pbist_ops = { + .read = hercules_pbist_read, + .write = hercules_pbist_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_pbist_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_pbist_ops, + s, TYPE_HERCULES_PBIST ".io", HERCULES_PBIST_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->mstdone); +} + +static void hercules_pbist_reset(DeviceState *d) +{ + HerculesPBISTState *s = HERCULES_PBIST(d); + + s->pact = 0; +} + +static void hercules_pbist_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_pbist_reset; + dc->realize = hercules_pbist_realize; +} + +static const TypeInfo hercules_pbist_info = { + .name = TYPE_HERCULES_PBIST, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesPBISTState), + .class_init = hercules_pbist_class_init, +}; + +static void hercules_pbist_register_types(void) +{ + type_register_static(&hercules_pbist_info); +} + +type_init(hercules_pbist_register_types) diff --git a/hw/misc/hercules_pmm.c b/hw/misc/hercules_pmm.c new file mode 100644 index 000000000000..c194ddfe589c --- /dev/null +++ b/hw/misc/hercules_pmm.c @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_pmm.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_PMM_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesPMMRegisters { + PRCKEYREG = 0xAC, + MKEY_LOCK_STEP = 0x0, + MKEY_SELF_TEST = 0x6, + MKEY_ERROR_FORCING = 0x9, + MKEY_SELF_TEST_ERROR_FORCING = 0xF, + LPDDCSTAT1 = 0xB0, + LPDDCSTAT2 = 0xB4, + + LPDDCSTAT1_LCMPE_ALL = BIT(20) | BIT(19) | BIT(18) | BIT(17) | BIT(16), + LPDDCSTAT1_LSTC_ALL = BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0), +}; + +static uint64_t hercules_pmm_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesPMMState *s = opaque; + + switch (offset) { + case PRCKEYREG: + return s->prckeyreg; + case LPDDCSTAT1: + return s->lpddcstat1; + case LPDDCSTAT2: + return s->lpddcstat2; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_pmm_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesPMMState *s = opaque; + uint32_t val = val64; + qemu_irq error = NULL; + + switch (offset) { + case PRCKEYREG: + s->prckeyreg = val & 0xF; + + switch (s->prckeyreg) { + case MKEY_ERROR_FORCING: + error = s->compare_error; + break; + case MKEY_SELF_TEST_ERROR_FORCING: + error = s->self_test_error; + break; + case MKEY_SELF_TEST: + break; + default: + return; + } + + s->lpddcstat1 |= LPDDCSTAT1_LSTC_ALL; + + if (error) { + qemu_irq_raise(error); + } + + break; + case LPDDCSTAT1: + val &= LPDDCSTAT1_LCMPE_ALL; + s->lpddcstat1 &= ~val; + case LPDDCSTAT2: + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_pmm_realize(DeviceState *dev, Error **errp) +{ + HerculesPMMState *s = HERCULES_PMM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_pmm_ops = { + .read = hercules_pmm_read, + .write = hercules_pmm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_pmm_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_pmm_ops, + s, TYPE_HERCULES_PMM ".io", HERCULES_PMM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + sysbus_init_irq(sbd, &s->compare_error); + sysbus_init_irq(sbd, &s->self_test_error); +} + +static void hercules_pmm_reset(DeviceState *d) +{ + HerculesPMMState *s = HERCULES_PMM(d); + + s->prckeyreg = 0; + s->lpddcstat1 = 0; + s->lpddcstat2 = 0; + + qemu_irq_lower(s->compare_error); + qemu_irq_lower(s->self_test_error); +} + +static void hercules_pmm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_pmm_reset; + dc->realize = hercules_pmm_realize; +} + +static const TypeInfo hercules_pmm_info = { + .name = TYPE_HERCULES_PMM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesPMMState), + .class_init = hercules_pmm_class_init, +}; + +static void hercules_pmm_register_types(void) +{ + type_register_static(&hercules_pmm_info); +} + +type_init(hercules_pmm_register_types) diff --git a/hw/misc/hercules_scm.c b/hw/misc/hercules_scm.c new file mode 100644 index 000000000000..cf3fe8cf70a4 --- /dev/null +++ b/hw/misc/hercules_scm.c @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_scm.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_SCM_SIZE = 256, + HERCULES_SDR_MMR_SIZE = 16 * 1024 * 1024, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesSCMRegisters { + SCMCNTRL = 0x04, +}; + +#define DTC_SOFT_RESET(w) extract32(w, 8, 4) + +enum HerculesSDCRegisters { + SDC_STATUS = 0x00, + NT_OK = BIT(3), + PT_OK = BIT(1), +}; + +static void hercules_scm_self_test(void *opaque) +{ + HerculesSCMState *s = opaque; + CPUState *cpu = qemu_get_cpu(0); + + if (cpu->halted) { + s->sdc_status |= NT_OK | PT_OK; + qemu_irq_raise(s->icrst); + return; + } + + qemu_bh_schedule(s->self_test); +} + +static uint64_t hercules_scm_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesSCMState *s = opaque; + + switch (offset) { + case SCMCNTRL: + return s->scmcntrl; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_scm_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesSCMState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case SCMCNTRL: + if (DTC_SOFT_RESET(val) == 0xA) { + qemu_bh_schedule(s->self_test); + } + break; + default: + qemu_log_bad_offset(offset); + } +} + +static uint64_t hercules_sdr_mmr_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesSCMState *s = opaque; + + switch (offset) { + case SDC_STATUS: + return s->sdc_status; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_sdr_mmr_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + switch (offset) { + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_scm_realize(DeviceState *dev, Error **errp) +{ + HerculesSCMState *s = HERCULES_SCM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_scm_ops = { + .read = hercules_scm_read, + .write = hercules_scm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_sdr_mmr_ops = { + .read = hercules_sdr_mmr_read, + .write = hercules_sdr_mmr_write, + /* + * This is not BE on TMS570 as per Device#51 errata + */ + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_scm_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->io.scm, OBJECT(dev), &hercules_scm_ops, + s, TYPE_HERCULES_SCM ".io.scm", HERCULES_SCM_SIZE); + sysbus_init_mmio(sbd, &s->io.scm); + + memory_region_init_io(&s->io.sdr_mmr, OBJECT(dev), &hercules_sdr_mmr_ops, + s, TYPE_HERCULES_SCM ".io.sdr-mmr", + HERCULES_SDR_MMR_SIZE); + sysbus_init_mmio(sbd, &s->io.sdr_mmr); + + s->self_test = qemu_bh_new(hercules_scm_self_test, s); + + sysbus_init_irq(sbd, &s->icrst); +} + +static void hercules_scm_reset(DeviceState *d) +{ + HerculesSCMState *s = HERCULES_SCM(d); + + s->scmcntrl = 0x05050505; + + /* + * s->sdc_status is left alone on purpose + */ + + qemu_bh_cancel(s->self_test); +} + +static void hercules_scm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_scm_reset; + dc->realize = hercules_scm_realize; +} + +static const TypeInfo hercules_scm_info = { + .name = TYPE_HERCULES_SCM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesSCMState), + .class_init = hercules_scm_class_init, +}; + +static void hercules_scm_register_types(void) +{ + type_register_static(&hercules_scm_info); +} + +type_init(hercules_scm_register_types) diff --git a/hw/misc/hercules_stc.c b/hw/misc/hercules_stc.c new file mode 100644 index 000000000000..c8efbc6e2d03 --- /dev/null +++ b/hw/misc/hercules_stc.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" + +#include "hw/misc/hercules_stc.h" +#include "hw/arm/hercules.h" + +enum { + HERCULES_STC_SIZE = 256, +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesSTCRegisters { + STCGCR0 = 0x0000, + STCGCR1 = 0x0004, + STCTPR = 0x0008, + STCGSTAT = 0x0014, + TEST_DONE = BIT(0), + TEST_FAIL = BIT(1), + STCFSTAT = 0x0018, + STCSCSCR = 0x003C, + FAULT_INS = BIT(4), + STCCLKDIV = 0x0044, +}; + +#define STC_ENA(w) extract32(w, 0, 4) + +static uint64_t hercules_stc_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesSTCState *s = opaque; + + switch (offset) { + case STCGCR0: + return s->stcgcr[0]; + case STCGCR1: + return s->stcgcr[1]; + case STCTPR: + return s->stctpr; + case STCFSTAT: + break; + case STCGSTAT: + return s->stcgstat; + case STCSCSCR: + return s->stcscscr; + case STCCLKDIV: + return s->stcclkdiv; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_stc_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesSTCState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case STCGCR0: + s->stcgcr[0] = val; + break; + case STCGCR1: + s->stcgcr[1] = val; + + if (STC_ENA(val) == 0xA) { + qemu_bh_schedule(s->self_test); + } + break; + case STCTPR: + s->stctpr = val; + break; + case STCGSTAT: + s->stcgstat &= ~val; + break; + case STCFSTAT: + break; + case STCSCSCR: + s->stcscscr = val; + break; + case STCCLKDIV: + s->stcclkdiv = val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_stc_self_test(void *opaque) +{ + HerculesSTCState *s = opaque; + CPUState *cpu = qemu_get_cpu(0); + + if (cpu->halted) { + s->stcgstat |= TEST_DONE; + if (s->stcscscr & FAULT_INS) { + s->stcgstat |= TEST_FAIL; + } + + qemu_irq_raise(s->cpurst); + return; + } + + qemu_bh_schedule(s->self_test); +} + +static void hercules_stc_realize(DeviceState *dev, Error **errp) +{ + HerculesSTCState *s = HERCULES_STC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + static MemoryRegionOps hercules_stc_ops = { + .read = hercules_stc_read, + .write = hercules_stc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_stc_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, OBJECT(dev), &hercules_stc_ops, + s, TYPE_HERCULES_STC ".io", HERCULES_STC_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + s->self_test = qemu_bh_new(hercules_stc_self_test, s); + sysbus_init_irq(sbd, &s->cpurst); +} + +static void hercules_stc_reset(DeviceState *d) +{ + HerculesSTCState *s = HERCULES_STC(d); + (void)s; +} + +static void hercules_stc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_stc_reset; + dc->realize = hercules_stc_realize; +} + +static const TypeInfo hercules_stc_info = { + .name = TYPE_HERCULES_STC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesSTCState), + .class_init = hercules_stc_class_init, +}; + +static void hercules_stc_register_types(void) +{ + type_register_static(&hercules_stc_info); +} + +type_init(hercules_stc_register_types) diff --git a/hw/misc/hercules_system.c b/hw/misc/hercules_system.c new file mode 100644 index 000000000000..fe44e8f22436 --- /dev/null +++ b/hw/misc/hercules_system.c @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "sysemu/runstate.h" +#include "sysemu/sysemu.h" +#include "hw/arm/hercules.h" +#include "hw/misc/hercules_system.h" + +#define NAME_SIZE 20 + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesSysRegisters { + CSDIS = 0x30, + CSDISSET = 0x34, + CSDISCLR = 0x38, + GHVSRC = 0x48, + CSVSTAT = 0x54, + MSTGCR = 0x58, + MINITGCR = 0x5C, + MSINENA = 0x60, + MSTCGSTAT = 0x68, + MSTDONE = BIT(0), + MINIDONE = BIT(8), + MINISTAT = 0x6C, + PLLCTL1 = 0x70, + ROS = BIT(31), + SSIR1 = 0xB0, + SSIR2 = 0xB4, + SSIR3 = 0xB8, + SSIR4 = 0xBC, + SYSECR = 0xE0, + SYSESR = 0xE4, + PORST = BIT(15), + DBGRST = BIT(11), + ICRST = BIT(7), + CPURST = BIT(5), + SWRST = BIT(4), + EXTRST = BIT(3), + GLBSTAT = 0xEC, + RFSLIP = BIT(8), + SSIVEC = 0xF4, +}; + +#define SYSECR_RESET(v) extract32(v, 14, 2) + +enum HerculesSys2Registers { + PLLCTL3 = 0x00, +}; + +static uint64_t hercules_sys_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesSystemState *s = opaque; + + switch (offset) { + case CSDIS: + case CSDISSET: + return s->csdis; + case GHVSRC: + return s->ghvsrc; + case CSVSTAT: + return (~s->csdis) & 0xff; + case MSTGCR: + return s->mstgcr; + case MINITGCR: + return s->minitgcr; + case MSINENA: + return s->msinena; + case MINISTAT: + return s->ministat; + case PLLCTL1: + return s->pllctl1; + case MSTCGSTAT: + return s->mstcgstat; + case SSIR1: + case SSIR2: + case SSIR3: + case SSIR4: + return 0; + case SYSESR: + return s->sysesr; + case GLBSTAT: + return s->glbstat; + case SSIVEC: + qemu_irq_lower(s->irq); + return 0; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_sys_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesSystemState *s = opaque; + const uint32_t val = val64; + unsigned int reset; + + switch (offset) { + case CSDIS: + s->csdis = val; + break; + case CSDISSET: + s->csdis |= val; + break; + case CSDISCLR: + s->csdis &= ~val; + break; + case GHVSRC: + s->ghvsrc = val; + break; + case MSTGCR: + s->mstgcr = val; + break; + case MINITGCR: + s->minitgcr = val; + break; + case MSINENA: + s->msinena = val; + if (val & 0x1 && s->minitgcr & 0xA) { + s->ministat = 0x100; + } + break; + case MINISTAT: + s->ministat &= ~val; + break; + case PLLCTL1: + s->pllctl1 = val; + + if (!s->ghvsrc && !(s->pllctl1 & ROS)) { + s->glbstat |= RFSLIP; + qemu_irq_raise(s->pll1_slip_error); + } + break; + case MSTCGSTAT: + s->mstcgstat &= ~val; + break; + case SSIR1: + case SSIR2: + case SSIR3: + case SSIR4: + /* + * TODO: We might want to emulate the fact that writes to this + * register are actually keyed + */ + qemu_irq_raise(s->irq); + break; + case SYSECR: + reset = SYSECR_RESET(val); + if (reset != 0x1) { + s->sysesr |= SWRST; + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + } + break; + case SYSESR: + s->sysesr &= ~val; + break; + case GLBSTAT: + s->glbstat &= ~val; + break; + default: + qemu_log_bad_offset(offset); + } +} + +static uint64_t hercules_sys2_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesSystemState *s = opaque; + + switch (offset) { + case PLLCTL3: + return s->pllctl3; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static void hercules_sys2_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesSystemState *s = opaque; + const uint32_t val = val64; + + switch (offset) { + case PLLCTL3: + s->pllctl3 = val; + + if (!s->ghvsrc) { + s->glbstat |= RFSLIP; + qemu_irq_raise(s->pll2_slip_error); + } + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_system_set_signal(void *opaque, int sig, int level) +{ + HerculesSystemState *s = opaque; + + CPUState *cpu = qemu_get_cpu(0); + + switch (sig) { + case HERCULES_SYSTEM_ICRST: + s->sysesr |= ICRST; + cpu_reset(cpu); + break; + case HERCULES_SYSTEM_CPURST: + s->sysesr |= CPURST; + cpu_reset(cpu); + break; + case HERCULES_SYSTEM_MSTDONE: + s->mstcgstat |= MSTDONE; + break; + } +} + +static void hercules_system_initfn(Object *obj) +{ + HerculesSystemState *s = HERCULES_SYSTEM(obj); + + int i; + + for (i = 0; i < HERCULES_SYSTEM_NUM_PCRS; i++) { + sysbus_init_child_obj(obj, "pcr[*]", &s->pcr[i], + sizeof(UnimplementedDeviceState), + TYPE_UNIMPLEMENTED_DEVICE); + } + + s->sysesr |= PORST; +} + +static void hercules_system_realize(DeviceState *dev, Error **errp) +{ + HerculesSystemState *s = HERCULES_SYSTEM(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + char name[NAME_SIZE]; + DeviceState *d; + int i; + + static MemoryRegionOps hercules_system_sys_ops = { + .read = hercules_sys_read, + .write = hercules_sys_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_system_sys2_ops = { + .read = hercules_sys2_read, + .write = hercules_sys2_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_system_sys_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_system_sys2_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->sys, obj, &hercules_system_sys_ops, + s, TYPE_HERCULES_SYSTEM ".io.sys", + HERCULES_SYSTEM_SYS_SIZE); + sysbus_init_mmio(sbd, &s->sys); + + memory_region_init_io(&s->sys2, obj, &hercules_system_sys2_ops, + s, TYPE_HERCULES_SYSTEM ".io.sys2", + HERCULES_SYSTEM_SYS2_SIZE); + sysbus_init_mmio(sbd, &s->sys2); + + for (i = 0; i < HERCULES_SYSTEM_NUM_PCRS; i++) { + d = DEVICE(&s->pcr[i]); + snprintf(name, NAME_SIZE, "pcr%d", i); + qdev_prop_set_string(d, "name", name); + qdev_prop_set_uint64(d, "size", HERCULES_SYSTEM_PCR_SIZE); + object_property_set_bool(OBJECT(d), true, "realized", &error_fatal); + + sysbus_init_mmio(sbd, sysbus_mmio_get_region(SYS_BUS_DEVICE(d), 0)); + } + + sysbus_init_irq(sbd, &s->irq); + + qdev_init_gpio_in(dev, hercules_system_set_signal, + HERCULES_SYSTEM_NUM_SIGNALS); + + sysbus_init_irq(sbd, &s->pll1_slip_error); + sysbus_init_irq(sbd, &s->pll2_slip_error); +} + +static void hercules_system_reset(DeviceState *d) +{ + HerculesSystemState *s = HERCULES_SYSTEM(d); + + /* + * If this wasn't a SW reset or POR reset, set DBGRST for now + */ + if (s->sysesr == 0) { + s->sysesr |= DBGRST; + } + + s->minitgcr = 0x5; + s->msinena = 0; + s->ministat = 0; + s->csdis = 0b11001110; + s->mstcgstat = MINIDONE; + s->mstgcr = 0x5; + + qemu_irq_lower(s->irq); +} + +static void hercules_system_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_system_reset; + dc->realize = hercules_system_realize; +} + +static const TypeInfo hercules_system_info = { + .name = TYPE_HERCULES_SYSTEM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesSystemState), + .instance_init = hercules_system_initfn, + .class_init = hercules_system_class_init, +}; + +static void hercules_system_register_types(void) +{ + type_register_static(&hercules_system_info); +} + +type_init(hercules_system_register_types) diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index f2b73983eeca..a8fd43365c0e 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -56,3 +56,4 @@ obj-$(call lnot,$(CONFIG_ROCKER)) += rocker/qmp-norocker.o common-obj-$(CONFIG_CAN_BUS) += can/ common-obj-$(CONFIG_MSF2) += msf2-emac.o +common-obj-$(CONFIG_HERCULES) += hercules_emac.o diff --git a/hw/net/hercules_emac.c b/hw/net/hercules_emac.c new file mode 100644 index 000000000000..978688b36223 --- /dev/null +++ b/hw/net/hercules_emac.c @@ -0,0 +1,662 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "chardev/char-fe.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "net/eth.h" +#include "sysemu/dma.h" + +#include "hw/net/hercules_emac.h" +#include "hw/arm/hercules.h" + +typedef struct QEMU_PACKED HerculesCppiDescriptor { + uint32_t next; + + uint32_t buffer_pointer; + uint16_t buffer_length; + uint16_t buffer_offset; + + uint16_t packet_length; + uint16_t flags; +} HerculesCppiDescriptor; + +#define HERCULES_CPPI_RAM_SIZE (8 * 1024) + +enum HerculesCppiDescriptorFlags { + SOP = BIT(31 - 16), + EOP = BIT(30 - 16), + OWNER = BIT(29 - 16), + EOQ = BIT(28 - 16), +}; + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + +enum HerculesEmacControlRegisters { + SOFTRESET = 0x04, + RESET = BIT(0), +}; + +enum HerculesEmacModuleRegister { + TXCONTROL = 0x004, + RXCONTROL = 0x014, + RXEN = BIT(0), + RXMBPENABLE = 0x100, + RXMULTEN = BIT(5), + RXBROADEN = BIT(13), + RXNOCHAIN = BIT(28), + RXUNICASTSET = 0x104, + RXUNICASTCLEAR = 0x108, + VALID = BIT(20), + MATCHFILT = BIT(19), + RXBUFFEROFFSET = 0x110, + MACCONTROL = 0x160, + MACHASH1 = 0x1D8, + MACHASH2 = 0x1DC, + MACADDRLO = 0x500, + MACADDRHI = 0x504, + MACINDEX = 0x508, + TX0HDP = 0x600, + TX7HDP = 0x61C, + RX0HDP = 0x620, + RX7HDP = 0x63C, + TX0CP = 0x640, + TX7CP = 0x65C, + RX0CP = 0x660, + RX7CP = 0x67C, +}; + +#define MACADDRLO_CHANNEL(s, idx) extract32(s->mac_lo[idx], 16, 3) +#define RXMPBENABLE_RXMULTCH(s) extract32(s->rxmbpenable, 0, 3) +#define RXMPBENABLE_BROADCH(s) extract32(s->rxmbpenable, 8, 3) + +static uint64_t hercules_emac_control_read(void *opaque, hwaddr offset, + unsigned size) +{ + qemu_log_bad_offset(offset); + return 0; +} + +static void hercules_emac_control_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + switch (offset) { + case SOFTRESET: + if (val & RESET) { + device_cold_reset(opaque); + } + break; + default: + qemu_log_bad_offset(offset); + break; + } +} + +static void hercules_emac_unmap_iov(QEMUIOVector *qiov) +{ + int i; + + for (i = 0; i < qiov->niov; i++) { + dma_memory_unmap(&address_space_memory, + qiov->iov[i].iov_base, + qiov->iov[i].iov_len, + DMA_DIRECTION_TO_DEVICE, + qiov->iov[i].iov_len); + }; +} + +static void hercules_emac_channel_process_tx(HerculesEmacState *s, int idx) +{ + NetClientState *nc = qemu_get_queue(s->nic); + QEMUIOVector qiov; + + qemu_iovec_init(&qiov, 1); + + while (s->txhdp[idx]) { + HerculesCppiDescriptor txd; + dma_addr_t addr, len; + void *chunk; + + dma_memory_read(&address_space_memory, s->txhdp[idx], &txd, + sizeof(txd)); + + addr = le32toh(txd.buffer_pointer); + len = le16toh(txd.buffer_length); + if (txd.flags & SOP) { + addr += le16toh(txd.buffer_offset); + } + + chunk = dma_memory_map(&address_space_memory, + addr, &len, DMA_DIRECTION_TO_DEVICE); + + qemu_iovec_add(&qiov, chunk, (int)len); + + if (le16toh(txd.flags) & EOP) { + qemu_sendv_packet(nc, qiov.iov, qiov.niov); + hercules_emac_unmap_iov(&qiov); + qemu_iovec_reset(&qiov); + } + /* + * We cheat here and clear ownership flag early + */ + txd.flags &= htole16(~OWNER); + if (!txd.next) { + txd.flags |= htole16(EOQ); + } + + dma_memory_write(&address_space_memory, s->txhdp[idx], &txd, + sizeof(txd)); + + s->txhdp[idx] = le32toh(txd.next); + } + /* + * Should normally be a no-op. Would be necessary for malformed + * descriptor chain (lacking EOP marker). + */ + hercules_emac_unmap_iov(&qiov); + qemu_iovec_destroy(&qiov); +} + +static uint64_t hercules_emac_module_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesEmacState *s = opaque; + NetClientState *nc = qemu_get_queue(s->nic); + unsigned int base; + uint32_t *reg; + + switch (offset) { + case TXCONTROL: + return s->txcontrol; + case RXCONTROL: + return s->rxcontrol; + case RXMBPENABLE: + return s->rxmbpenable; + case RXUNICASTSET: + /* fall-through */ + case RXUNICASTCLEAR: + return s->rxunicast; + case RXBUFFEROFFSET: + return s->rxbufferoffset; + case MACCONTROL: + return s->maccontrol; + case MACHASH1: + return s->machash[0]; + case MACHASH2: + return s->machash[1]; + case MACADDRLO: + return s->mac_lo[s->macindex]; + case MACADDRHI: + return s->mac_hi; + case MACINDEX: + return s->macindex; + default: + qemu_log_bad_offset(offset); + return 0; + /* + * Indexed register handling below + */ + case TX0HDP ... TX7HDP: + base = TX0HDP; + reg = s->txhdp; + break; + case RX0HDP ... RX7HDP: + base = RX0HDP; + reg = s->rxhdp; + break; + case TX0CP ... TX7CP: + base = TX0CP; + reg = s->txcp; + break; + case RX0CP ... RX7CP: + base = RX0CP; + reg = s->rxcp; + break; + } + + qemu_flush_queued_packets(nc); + return reg[(offset - base) / sizeof(uint32_t)]; +} + +static void hercules_emac_update_active_channels(HerculesEmacState *s) +{ + int i; + s->active_channels = 0; + + for (i = 0; i < HERCULES_EMAC_NUM_CHANNELS; i++) { + int channel = MACADDRLO_CHANNEL(s, i); + + if (s->mac_lo[i] & VALID && s->mac_lo[i] & MATCHFILT && + s->rxunicast & BIT(channel)) { + s->active_channels |= BIT(i); + } + } +} + +static void hercules_emac_module_write(void *opaque, hwaddr offset, + uint64_t val64, unsigned size) +{ + HerculesEmacState *s = opaque; + unsigned int idx, base; + const uint32_t val = val64; + uint32_t *reg; + + switch (offset) { + case TXCONTROL: + s->txcontrol = val; + return; + case RXCONTROL: + s->rxcontrol = val; + return; + case RXMBPENABLE: + s->rxmbpenable = val; + return; + case RXUNICASTSET: + s->rxunicast |= val; + hercules_emac_update_active_channels(s); + return; + case RXUNICASTCLEAR: + s->rxunicast &= ~val; + hercules_emac_update_active_channels(s); + return; + case RXBUFFEROFFSET: + s->rxbufferoffset = val; + return; + case MACCONTROL: + s->maccontrol = val; + return; + case MACHASH1: + s->machash[0] = val; + return; + case MACHASH2: + s->machash[1] = val; + return; + case MACADDRLO: + s->mac_lo[s->macindex] = val; + hercules_emac_update_active_channels(s); + return; + case MACADDRHI: + s->mac_hi = val; + return; + case MACINDEX: + s->macindex = val & 0b111; + return; + default: + qemu_log_bad_offset(offset); + return; + /* + * Indexed register handling below + */ + case TX0HDP ... TX7HDP: + base = TX0HDP; + reg = s->txhdp; + break; + case RX0HDP ... RX7HDP: + base = RX0HDP; + reg = s->rxhdp; + break; + case TX0CP ... TX7CP: + base = TX0CP; + reg = s->txcp; + break; + case RX0CP ... RX7CP: + base = RX0CP; + reg = s->rxcp; + break; + } + + idx = (offset - base) / sizeof(uint32_t); + reg[idx] = val; + if (base == TX0HDP) { + hercules_emac_channel_process_tx(s, idx); + } +} + +static bool emac_can_receive(NetClientState *nc) +{ + HerculesEmacState *s = qemu_get_nic_opaque(nc); + + return s->rxcontrol & RXEN; +} + +static ssize_t hercules_emac_channel_process_rx(HerculesEmacState *s, int idx, + const uint8_t *buf, + size_t size) +{ + HerculesCppiDescriptor rxd; + size_t residue = size; + size_t chunk; + uint16_t available; + uint32_t addr; + + while (s->rxhdp[idx] && residue) { + dma_memory_read(&address_space_memory, s->rxhdp[idx], &rxd, + sizeof(rxd)); + + /* + * If this is the first buffer we are processing mark it with SOP + */ + if (residue == size) { + rxd.flags |= le16toh(SOP); + rxd.packet_length = le16toh(size); + rxd.buffer_offset = s->rxbufferoffset; + } + + available = le16toh(rxd.buffer_length) - le16toh(rxd.buffer_offset); + chunk = MIN(size, available); + + if (s->rxmbpenable & RXNOCHAIN) { + /* + * If RXNOCHAIN is set we only process as much data as + * would fit in a single buffer and drop the reset, so we + * adjust 'residue' accordingly + */ + residue = chunk; + } + + addr = le32toh(rxd.buffer_pointer) + le16toh(rxd.buffer_offset); + dma_memory_write(&address_space_memory, + addr, + buf, chunk); + + rxd.buffer_length = htole16(chunk); + rxd.flags &= htole16(~OWNER); + + residue -= chunk; + buf += chunk; + /* + * If there's no more packet data to DMA, mark this buffer + * with EOP + */ + if (!residue) { + rxd.flags |= htole16(EOP); + } + /* + * If this is the last descriptor we can process, set EOQ to + * indicate that + */ + if (!rxd.next) { + rxd.flags |= htole16(EOQ); + } + + dma_memory_write(&address_space_memory, s->rxhdp[idx], &rxd, + sizeof(rxd)); + + s->rxcp[idx] = s->rxhdp[idx]; + s->rxhdp[idx] = le32toh(rxd.next); + } + + return size; +} + +static bool hercules_emac_machash_filter(HerculesEmacState *s, + const struct eth_header *ethpacket) +{ + /* + * Taken from 32.5.37 MAC Hash Address Register 1 (MACHASH1) + */ + const uint64_t hash_fun[] = { + BIT(0) | BIT(6) | BIT(12) | BIT(18) | + BIT(24) | BIT(30) | BIT(36) | BIT(42), + + BIT(1) | BIT(7) | BIT(13) | BIT(19) | + BIT(25) | BIT(31) | BIT(37) | BIT(43), + + BIT(2) | BIT(8) | BIT(14) | BIT(20) | + BIT(26) | BIT(32) | BIT(38) | BIT(44), + + BIT(3) | BIT(9) | BIT(15) | BIT(21) | + BIT(27) | BIT(33) | BIT(39) | BIT(45), + + BIT(4) | BIT(10) | BIT(16) | BIT(22) | + BIT(28) | BIT(34) | BIT(40) | BIT(46), + + BIT(5) | BIT(11) | BIT(17) | BIT(23) | + BIT(29) | BIT(35) | BIT(41) | BIT(47), + }; + const uint64_t da = be64toh(*(const uint64_t *)ethpacket->h_dest) >> 16; + unsigned idx = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(hash_fun); i++) { + /* + * Result of XOR is going to be 1 is the number of 1's is odd + * and 0 if number of 1's is even + */ + idx |= ctpop64(da & hash_fun[i]) % 2; + idx <<= 1; + } + + return s->machash[idx / 32] & BIT(idx % 32); +} + +static ssize_t hercules_emac_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + HerculesEmacState *s = qemu_get_nic_opaque(nc); + const struct eth_header *ethpacket = (const struct eth_header *)buf; + const uint32_t *h_dest_hi = (const uint32_t *)ethpacket->h_dest; + const uint16_t *h_dest_lo = + (const uint16_t *)(ethpacket->h_dest + sizeof(uint32_t)); + unsigned long active_channels = s->active_channels; + uint32_t channel; + int i; + + /* Broadcast */ + if (s->rxmbpenable & RXBROADEN && + is_broadcast_ether_addr(ethpacket->h_dest)) { + + channel = RXMPBENABLE_BROADCH(s); + return hercules_emac_channel_process_rx(s, channel, buf, size); + } + + if (s->rxmbpenable & RXMULTEN && + is_multicast_ether_addr(ethpacket->h_dest)) { + + /* Returns true if packet should be filtered */ + if (hercules_emac_machash_filter(s, ethpacket)) { + return size; + } + + channel = RXMPBENABLE_RXMULTCH(s); + return hercules_emac_channel_process_rx(s, channel, buf, size); + } + + /* + * Both MACADDRHI stores MAC address as follows: + * + * 31-24 MACADDR2 MAC source address bits 23-16 (byte 2). + * 23-16 MACADDR3 MAC source address bits 31-24 (byte 3). + * 15-8 MACADDR4 MAC source address bits 39-32 (byte 4). + * 7-0 MACADDR5 MAC source address bits 47-40 (byte 5). + * + * so we intentionally read h_dest_hi as LE + */ + if (s->mac_hi == le32toh(*h_dest_hi)) { + for_each_set_bit(i, &active_channels, HERCULES_EMAC_NUM_CHANNELS) { + /* + * h_dest_lo is the same as h_dest_hi (see comment above) + */ + if ((s->mac_lo[i] & 0xffff) == le16toh(*h_dest_lo)) { + channel = MACADDRLO_CHANNEL(s, i); + return hercules_emac_channel_process_rx(s, channel, buf, size); + } + } + } + + return size; +} + +static void hercules_emac_set_link_status(NetClientState *nc) +{ + /* We don't do anything for now */ +} + +static void hercules_emac_initfn(Object *obj) +{ + HerculesEmacState *s = HERCULES_EMAC(obj); + + sysbus_init_child_obj(obj, "mdio", &s->mdio, + sizeof(UnimplementedDeviceState), + TYPE_UNIMPLEMENTED_DEVICE); +} + +static void emac_reset(DeviceState *d) +{ + HerculesEmacState *s = HERCULES_EMAC(d); + NetClientState *const nc = qemu_get_queue(s->nic); + + qemu_purge_queued_packets(nc); + + s->txcontrol = 0; + s->rxcontrol = 0; + s->maccontrol = 0; + s->rxbufferoffset = 0; + s->mac_hi = 0; + s->macindex = 0; + s->rxmbpenable = 0; + s->rxunicast = 0; + s->active_channels = 0; + + memset(s->mac_lo, 0, sizeof(s->mac_lo)); + memset(s->machash, 0, sizeof(s->machash)); + memset(s->txhdp, 0, sizeof(s->txhdp)); + memset(s->rxhdp, 0, sizeof(s->rxhdp)); + memset(s->txcp, 0, sizeof(s->txcp)); + memset(s->rxcp, 0, sizeof(s->rxcp)); +} + +static NetClientInfo net_emac_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = emac_can_receive, + .receive = hercules_emac_receive, + .link_status_changed = hercules_emac_set_link_status, +}; + +static void hercules_emac_realize(DeviceState *dev, Error **errp) +{ + HerculesEmacState *s = HERCULES_EMAC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + MemoryRegion *io; + + static MemoryRegionOps hercules_emac_module_ops = { + .read = hercules_emac_module_read, + .write = hercules_emac_module_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + static MemoryRegionOps hercules_emac_control_ops = { + .read = hercules_emac_control_read, + .write = hercules_emac_control_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_emac_module_ops.endianness = DEVICE_BIG_ENDIAN; + hercules_emac_control_ops.endianness = DEVICE_BIG_ENDIAN; + } + + /* + * Init controller mmap'd interface + * 0x000 - 0x800 : emac + * 0x800 - 0x900 : ctrl + * 0x900 - 0xA00 : mdio + */ + memory_region_init_io(&s->module, obj, &hercules_emac_module_ops, + s, TYPE_HERCULES_EMAC ".io.module", + HERCULES_EMAC_MODULE_SIZE); + sysbus_init_mmio(sbd, &s->module); + + memory_region_init_io(&s->control, obj, &hercules_emac_control_ops, + s, TYPE_HERCULES_EMAC ".io.control", + HERCULES_EMAC_CONTROL_SIZE); + sysbus_init_mmio(sbd, &s->control); + + qdev_prop_set_string(DEVICE(&s->mdio), "name", + TYPE_HERCULES_EMAC ".io.mdio"); + qdev_prop_set_uint64(DEVICE(&s->mdio), "size", + HERCULES_EMAC_MDIO_SIZE); + object_property_set_bool(OBJECT(&s->mdio), true, "realized", &error_fatal); + io = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->mdio), 0); + sysbus_init_mmio(sbd, io); + + memory_region_init_ram(&s->ram, obj, + TYPE_HERCULES_EMAC ".cppi-ram", + HERCULES_CPPI_RAM_SIZE, &error_fatal); + sysbus_init_mmio(sbd, &s->ram); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + s->nic = qemu_new_nic(&net_emac_info, &s->conf, + object_get_typename(obj), + dev->id, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static Property hercules_emac_properties[] = { + DEFINE_NIC_PROPERTIES(HerculesEmacState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void hercules_emac_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, hercules_emac_properties); + dc->reset = emac_reset; + dc->realize = hercules_emac_realize; + + dc->desc = "Hercules EMAC Controller"; + + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +} + +static const TypeInfo hercules_emac_info = { + .name = TYPE_HERCULES_EMAC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesEmacState), + .instance_init = hercules_emac_initfn, + .class_init = hercules_emac_class_init, +}; + +static void hercules_emac_register_types(void) +{ + type_register_static(&hercules_emac_info); +} + +type_init(hercules_emac_register_types) diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs index 07a85f1967a8..6b492bcb1b69 100644 --- a/hw/ssi/Makefile.objs +++ b/hw/ssi/Makefile.objs @@ -8,3 +8,4 @@ common-obj-$(CONFIG_MSF2) += mss-spi.o common-obj-$(CONFIG_OMAP) += omap_spi.o common-obj-$(CONFIG_IMX) += imx_spi.o +common-obj-$(CONFIG_HERCULES) += hercules_spi.o diff --git a/hw/ssi/hercules_spi.c b/hw/ssi/hercules_spi.c new file mode 100644 index 000000000000..38f4ce01fc23 --- /dev/null +++ b/hw/ssi/hercules_spi.c @@ -0,0 +1,734 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "sysemu/dma.h" + +#include "hw/ssi/hercules_spi.h" +#include "hw/arm/hercules.h" + +#define qemu_log_bad_offset(offset) \ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx "\n", \ + __func__, offset); + + +/* + * Current assumptions in this MIBSPI implementation + * + * - Only MIBSPI mode is implemented, no compatability mode + * + * - For MIBSPI mode, we are assuming that the first buffer in a + * transfer group has the same control settings as the rest (number + * of bits, shift direction, etc) + * + * - We only implement the SW trigger one-shot mode + * + * - We only are only using a single chip select per device + * + * - Only support word length of 8 / 16 + * + */ + +#define HERCULES_KEY_ENABLE 0xA +#define HERCULES_KEY_DISABLE 0x5 + +enum HerculesMibSpiRegisters { + SPIGCR0 = 0x000, + SPIGCR1 = 0x004, + SPIEN = BIT(24), + SPIINT0 = 0x008, + DMAREQEN = BIT(16), + SPIFLG = 0x010, + SPIFLG_W1C_MASK = 0x15f, + SPIPC0 = 0x014, + ENAFUN = BIT(8), + SPIPC1 = 0x018, + SPIPC8 = 0x034, + SPIDAT0 = 0x038, + SPIDAT1 = 0x03C, + SPIBUF = 0x040, + SPIFMT0 = 0x050, + SPIFMT1 = 0x054, + SPIFMT2 = 0x058, + SPIFMT3 = 0x05C, + MIBSPIE = 0x070, + RXRAM_ACCESS = BIT(16), + TGINTFLAG = 0x084, + LTGPEND = 0x094, + TG0CTRL = 0x098, + TG14CTRL = 0x0D0, + TG15CTRL = 0x0D4, + DMA0CTRL = 0x0DF, + RXDMAENA = BIT(15), + TXDMAENA = BIT(14), + DMA8CTRL = 0x0F4, + DMA0COUNT = 0x0F8, + DMA8COUNT = 0x112, + DMACNTLEN = 0x118, + TGENA = BIT(31), + ONESHOTx = BIT(30), + PAR_ECC_CTRL = 0x120, + PAR_ECC_STAT = 0x124, + UERR_FLG0 = BIT(0), + UERR_FLG1 = BIT(1), + SBE_FLG0 = BIT(8), + SBE_FLG1 = BIT(9), + UERRADDR1 = 0x128, + UERRADDR0 = 0x12C, + IOLPBKTSTCR = 0x134, + IOLPBKSTENA = 0x0A, + ECCDIAG_CTRL = 0x140, + ECCDIAG_STAT = 0x144, + SEFLG0 = BIT(0), + SEFLG1 = BIT(1), + DEFLG0 = BIT(16), + DEFLG1 = BIT(17), + SBERRADDR1 = 0x148, + SBERRADDR0 = 0x14C, +}; + +#define INTFLGRDY(n) BIT((n) + 16) + +#define TXRAM_CSNR(w) extract32(w, 16, 8) +#define TXRAM_DFSEL(w) extract32(w, 24, 2) +#define TXRAM_TXDATA(w, l) extract32(w, 0, l) +#define TXRAM_CSHOLD BIT(28) + +#define SPIFMT_CHARLEN(w) extract32(w, 0, 5) + +#define TGxCTRL_PSTART(w) extract32(w, 8, 8) + +#define IOLPBKTSTCR_IOLPBKSTENA(w) extract32(w, 8, 4) + +#define TXDMA_MAPx(w) extract32(w, 16, 4) +#define RXDMA_MAPx(w) extract32(w, 20, 4) + +#define SBE_EVT_EN(w) extract32(w, 24, 4) + +static uint16_t hercules_spi_tx_single(HerculesMibSpiState *s, uint32_t txword) +{ + const uint8_t dfsel = TXRAM_DFSEL(txword); + const uint32_t spifmt = s->spifmt[dfsel]; + const uint8_t charlen = SPIFMT_CHARLEN(spifmt); + const uint16_t txdata = TXRAM_TXDATA(txword, charlen); + + /* + * FIXME: Should probably cache this as a pivate boolean flag + */ + if (unlikely(IOLPBKTSTCR_IOLPBKSTENA(s->iolpbktstcr) == IOLPBKSTENA)) { + return txdata; + } + + return ssi_transfer(s->ssi, txdata); +} + +static bool hercules_spi_ram_big_endian(HerculesMibSpiState *s) +{ + return s->io.regs.ops->endianness == DEVICE_BIG_ENDIAN; +} + +static uint32_t hercules_spi_ram_read(HerculesMibSpiState *s, uint32_t *word) +{ + return hercules_spi_ram_big_endian(s) ? ldl_be_p(word) : ldl_le_p(word); +} + +static void hercules_spi_ram_write(HerculesMibSpiState *s, uint32_t *word, + uint32_t value) +{ + if (hercules_spi_ram_big_endian(s)) { + stl_be_p(word, value); + } else { + stl_le_p(word, value); + } +} + +static void hercules_spi_assert_cs(HerculesMibSpiState *s, + unsigned long csnr, int val) +{ + int i; + + for_each_set_bit(i, &csnr, HERCULES_SPI_NUM_CS_LINES) { + qemu_set_irq(s->cs_lines[i], val); + } +} + +static void hercules_ecc_error_raise(HerculesMibSpiState *s, + unsigned int idx, + unsigned int err_idx, + uint32_t uerr_flg, uint32_t sbe_flg) +{ + if (s->par_ecc_stat & uerr_flg && + s->uerraddr[err_idx] == idx * sizeof(uint32_t)) { + qemu_irq_raise(s->uncorrectable_error); + } + + if (s->par_ecc_stat & sbe_flg && + s->sberraddr[err_idx] == idx * sizeof(uint32_t)) { + qemu_irq_raise(s->single_bit_error); + } +} + +static void ___hercules_spi_process_tg(HerculesMibSpiState *s, unsigned int n, + unsigned int end) +{ + const bool tgena = s->tgxctrl[n] & TGENA; + const bool oneshot = s->tgxctrl[n] & ONESHOTx; + + if (tgena && oneshot) { + const unsigned int start = TGxCTRL_PSTART(s->tgxctrl[n]); + unsigned int i; + + + for (i = start; i <= end; i++) { + uint32_t txword; + uint32_t rxword; + uint8_t csnr; + + hercules_ecc_error_raise(s, i, 0, UERR_FLG0, SBE_FLG0); + + /* + * FIXME: Handle CS assertion here + */ + txword = hercules_spi_ram_read(s, &s->txram[i]); + csnr = ~TXRAM_CSNR(txword); + + hercules_spi_assert_cs(s, csnr, 0); + + rxword = hercules_spi_tx_single(s, txword); + hercules_spi_ram_write(s, &s->rxram[i], rxword); + + if (txword & TXRAM_CSHOLD) { + continue; + } + + hercules_spi_assert_cs(s, csnr, 1); + } + + s->tgintflag |= INTFLGRDY(n); + } +} + +static void __hercules_spi_process_tg(HerculesMibSpiState *s, unsigned int n, + unsigned int end) +{ + if (s->spiena != SPIENA_IDLE) { + s->pending_tg.n = n; + s->pending_tg.end = end; + s->spiena = SPIENA_PENDING_TG; + return; + } + + ___hercules_spi_process_tg(s, n, end); +} + +static void hercules_spi_process_tg(HerculesMibSpiState *s, + unsigned int n) +{ + const unsigned int end = TGxCTRL_PSTART(s->tgxctrl[n + 1]) - 1; + + __hercules_spi_process_tg(s, n, end); +} + +static void hercules_spi_process_tg_last(HerculesMibSpiState *s) +{ + + const unsigned int end = TGxCTRL_PSTART(s->ltgpend); + + __hercules_spi_process_tg(s, 15, end); +} + +static uint64_t hercules_spi_read32(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibSpiState *s = opaque; + unsigned int idx; + uint32_t erraddr; + + switch (offset) { + case SPIGCR0: + return s->spigcr0; + case SPIGCR1: + return s->spigcr1; + case SPIINT0: + return s->spiint0; + case SPIFLG: + return s->spiflg; + case SPIPC0: + return s->spipc[0]; + case SPIPC1 ... SPIPC8: + return 0; + case SPIFMT0 ... SPIFMT3: + idx = (offset - SPIFMT0) / sizeof(uint32_t); + return s->spifmt[idx]; + case MIBSPIE: + return s->mibspie; + case TGINTFLAG: + return s->tgintflag; + case LTGPEND: + return s->ltgpend; + case TG0CTRL ... TG15CTRL: + idx = (offset - TG0CTRL) / sizeof(uint32_t); + return s->tgxctrl[idx]; + case PAR_ECC_CTRL: + return s->par_ecc_ctrl; + case PAR_ECC_STAT: + return s->par_ecc_stat; + case IOLPBKTSTCR: + return s->iolpbktstcr; + case ECCDIAG_CTRL: + return s->eccdiag_ctrl; + case ECCDIAG_STAT: + return s->eccdiag_stat; + case SBERRADDR1: + erraddr = s->sberraddr[1] + sizeof(s->txram); + s->sberraddr[1] = 0; + return erraddr; + case SBERRADDR0: + erraddr = s->sberraddr[0]; + s->sberraddr[0] = 0; + return erraddr; + case UERRADDR1: + erraddr = s->uerraddr[1] + sizeof(s->txram); + s->uerraddr[1] = 0; + return erraddr; + case UERRADDR0: + erraddr = s->uerraddr[0]; + s->uerraddr[0] = 0; + return erraddr; + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static uint64_t hercules_spi_read16(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibSpiState *s = opaque; + + /* FIXME: This assumes BE */ + switch (offset) { + case SPIBUF: + return extract32(s->spibuf, 16, 16); + case SPIBUF + sizeof(uint16_t): + return extract32(s->spibuf, 0, 16); + default: + qemu_log_bad_offset(offset); + } + + return 0; +} + +static uint64_t hercules_spi_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (size) { + case sizeof(uint16_t): + return hercules_spi_read16(opaque, offset, size); + case sizeof(uint32_t): + return hercules_spi_read32(opaque, offset, size); + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad size %u\n", + __func__, size); + break; + } + + return 0; +} + +static bool hercules_spi_compatibility_dma_enabled(HerculesMibSpiState *s) +{ + return s->spiint0 & DMAREQEN && s->spigcr1 & SPIEN; +} + +static void hercules_spi_assert_dmareq(void *opaque) +{ + HerculesMibSpiState *s = opaque; + + if (s->spiint0 & DMAREQEN) { + qemu_irq_raise(s->dmareq[0]); + } +} + +static void hercules_spi_process_compatibility_dma(HerculesMibSpiState *s) +{ + if (hercules_spi_compatibility_dma_enabled(s)) { + qemu_irq_raise(s->dmareq[0]); + } else { + qemu_bh_cancel(s->compatibility_dma_req); + } +} + +static void __hercules_spi_process_spidata(HerculesMibSpiState *s) +{ + s->spibuf = hercules_spi_tx_single(s, s->spidat1); + + if (hercules_spi_compatibility_dma_enabled(s)) { + qemu_irq_raise(s->dmareq[1]); + qemu_bh_schedule(s->compatibility_dma_req); + } +} + +static void hercules_spi_process_spidata(HerculesMibSpiState *s) +{ + if (s->spiena != SPIENA_IDLE) { + s->spiena = SPIENA_PENDING_SPIDATA; + return; + } + + __hercules_spi_process_spidata(s); +} + +static void hercules_spi_write16(void *opaque, hwaddr offset, uint64_t val64, + unsigned size) +{ + HerculesMibSpiState *s = opaque; + const uint16_t val = val64; + + /* FIXME: This assumes BE */ + switch (offset) { + case SPIDAT1: + s->spidat1 = deposit32(s->spidat1, 16, 16, val); + break; + case SPIDAT1 + sizeof(uint16_t): + s->spidat1 = deposit32(s->spidat1, 0, 16, val); + hercules_spi_process_spidata(s); + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_spi_write32(void *opaque, hwaddr offset, uint64_t val64, + unsigned size) +{ + HerculesMibSpiState *s = opaque; + const uint32_t val = val64; + unsigned int idx; + + switch (offset) { + case SPIGCR0: + s->spigcr0 = val; + break; + case SPIGCR1: + s->spigcr1 = val; + hercules_spi_process_compatibility_dma(s); + break; + case SPIINT0: + s->spiint0 = val; + hercules_spi_process_compatibility_dma(s); + break; + case SPIFLG: + s->spiflg &= ~(val & SPIFLG_W1C_MASK); + break; + case SPIPC0: + s->spipc[0] = val; + break; + case SPIPC1 ... SPIPC8: + break; + case SPIDAT1: + s->spidat1 = val; + break; + case SPIFMT0 ... SPIFMT3: + idx = (offset - SPIFMT0) / sizeof(uint32_t); + s->spifmt[idx] = val; + break; + case MIBSPIE: + s->mibspie = val; + break; + case TGINTFLAG: + s->tgintflag &= ~val; + break; + case LTGPEND: + s->ltgpend = val; + break; + case TG0CTRL ... TG14CTRL: + idx = (offset - TG0CTRL) / sizeof(uint32_t); + s->tgxctrl[idx] = val; + hercules_spi_process_tg(s, idx); + break; + case TG15CTRL: + s->tgxctrl[15] = val; + hercules_spi_process_tg_last(s); + break; + case PAR_ECC_CTRL: + s->par_ecc_ctrl = val; + break; + case PAR_ECC_STAT: + s->par_ecc_stat &= ~val; + break; + case IOLPBKTSTCR: + s->iolpbktstcr = val; + break; + case ECCDIAG_CTRL: + s->eccdiag_ctrl = val; + break; + case ECCDIAG_STAT: + s->eccdiag_stat &= ~val; + break; + case SBERRADDR1: + case SBERRADDR0: + case UERRADDR1: + case UERRADDR0: + break; + default: + qemu_log_bad_offset(offset); + } +} + +static void hercules_spi_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + switch (size) { + case sizeof(uint16_t): + hercules_spi_write16(opaque, offset, val, size); + break; + case sizeof(uint32_t): + hercules_spi_write32(opaque, offset, val, size); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad size %u\n", + __func__, size); + break; + } +} + +static void hercules_spi_rxram_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + HerculesMibSpiState *s = opaque; + + if (s->mibspie & RXRAM_ACCESS) { + if (s->eccdiag_ctrl == 0x5) { + switch (ctpop32(val)) { + default: + s->par_ecc_stat |= UERR_FLG1; + s->eccdiag_stat |= DEFLG1; + s->uerraddr[1] = offset; + break; + case 1: + s->par_ecc_stat |= SBE_FLG1; + s->eccdiag_stat |= SEFLG1; + s->sberraddr[1] = offset; + case 0: + break; + } + } + + switch (size) { + case 1: + *(uint8_t *)((void *)s->rxram + offset) = (uint8_t)val; + break; + case 2: + *(uint16_t *)((void *)s->rxram + offset) = (uint16_t)val; + break; + case 4: + *(uint32_t *)((void *)s->rxram + offset) = (uint32_t)val; + break; + } + } +} + +static uint64_t hercules_spi_rxram_read(void *opaque, hwaddr offset, + unsigned size) +{ + HerculesMibSpiState *s = opaque; + const unsigned int idx = offset / sizeof(uint32_t); + uint64_t data = (uint64_t)~0; + + hercules_ecc_error_raise(s, idx, 1, UERR_FLG1, SBE_FLG1); + + switch (size) { + case 1: + data = *(uint8_t *)((void *)s->rxram + offset); + break; + case 2: + data = *(uint16_t *)((void *)s->rxram + offset); + break; + case 4: + data = *(uint32_t *)((void *)s->rxram + offset); + break; + } + + return data; +} + +static void hercules_spi_set_spiena(void *opaque, int req, int level) +{ + HerculesMibSpiState *s = opaque; + const bool asserted = !level; + + if (likely(s->spipc[0] & ENAFUN)) { + switch (s->spiena) { + case SPIENA_PENDING_TG: + if (asserted) { + ___hercules_spi_process_tg(s, s->pending_tg.n, + s->pending_tg.end); + s->spiena = SPIENA_IDLE; + } + break; + case SPIENA_PENDING_SPIDATA: + if (asserted) { + __hercules_spi_process_spidata(s); + s->spiena = SPIENA_IDLE; + } + break; + case SPIENA_IDLE: + if (level) { + s->spiena = SPIENA_BUSY; + } + break; + case SPIENA_BUSY: + if (asserted) { + s->spiena = SPIENA_IDLE; + } + } + } +} + +static void hercules_spi_realize(DeviceState *dev, Error **errp) +{ + HerculesMibSpiState *s = HERCULES_SPI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + + int i; + + static MemoryRegionOps hercules_spi_rxram_ops = { + .read = hercules_spi_rxram_read, + .write = hercules_spi_rxram_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + }; + + static MemoryRegionOps hercules_spi_ops = { + .read = hercules_spi_read, + .write = hercules_spi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 2, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_spi_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->io.regs, obj, &hercules_spi_ops, s, + TYPE_HERCULES_SPI ".io", HERCULES_SPI_SIZE); + sysbus_init_mmio(sbd, &s->io.regs); + + memory_region_init_ram_ptr(&s->io.txram, obj, + TYPE_HERCULES_SPI ".ram.tx", + sizeof(s->txram), s->txram); + + memory_region_init_io(&s->io.rxram, obj, + &hercules_spi_rxram_ops, + s, TYPE_HERCULES_SPI ".ram.rx", + sizeof(s->rxram)); + + memory_region_init(&s->io.ram, obj, + TYPE_HERCULES_SPI ".ram", + sizeof(s->txram) + + sizeof(s->rxram)); + + memory_region_add_subregion(&s->io.ram, + 0x0, + &s->io.txram); + memory_region_add_subregion(&s->io.ram, + sizeof(s->txram), + &s->io.rxram); + + sysbus_init_mmio(sbd, &s->io.ram); + + sysbus_init_irq(sbd, &s->irq[0]); + sysbus_init_irq(sbd, &s->irq[1]); + + s->ssi = ssi_create_bus(dev, "ssi"); + ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->ssi); + + for (i = 0; i < ARRAY_SIZE(s->cs_lines); ++i) { + sysbus_init_irq(sbd, &s->cs_lines[i]); + } + + for (i = 0; i < ARRAY_SIZE(s->dmareq); i++) { + sysbus_init_irq(sbd, &s->dmareq[i]); + } + + s->compatibility_dma_req = qemu_bh_new(hercules_spi_assert_dmareq, s); + + qdev_init_gpio_in_named(dev, hercules_spi_set_spiena, + HERCULES_SPI_SPIENA, 1); + + sysbus_init_irq(sbd, &s->single_bit_error); + sysbus_init_irq(sbd, &s->uncorrectable_error); +} + +static void hercules_spi_reset(DeviceState *d) +{ + HerculesMibSpiState *s = HERCULES_SPI(d); + + s->mibspie = 5 << 8; + s->spigcr0 = 0; + s->spigcr1 = 0; + s->spiint0 = 0; + s->spiflg = 0; + s->spipc[0] = 0; + s->tgintflag = 0; + s->iolpbktstcr = 0; + s->ltgpend = 0; + s->spiena = SPIENA_IDLE; + + memset(s->spifmt, 0, sizeof(s->spifmt)); + memset(s->tgxctrl, 0, sizeof(s->tgxctrl)); + memset(s->txram, 0, sizeof(s->txram)); + memset(s->rxram, 0, sizeof(s->rxram)); +} + +static void hercules_spi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_spi_reset; + dc->realize = hercules_spi_realize; + + dc->desc = "Hercules MiBSPI Controller"; +} + +static const TypeInfo hercules_spi_info = { + .name = TYPE_HERCULES_SPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesMibSpiState), + .class_init = hercules_spi_class_init, +}; + +static void hercules_spi_register_types(void) +{ + type_register_static(&hercules_spi_info); +} + +type_init(hercules_spi_register_types) diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index dece235fd72e..f1e38b9bc6a3 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -35,3 +35,4 @@ common-obj-$(CONFIG_CMSDK_APB_TIMER) += cmsdk-apb-timer.o common-obj-$(CONFIG_CMSDK_APB_DUALTIMER) += cmsdk-apb-dualtimer.o common-obj-$(CONFIG_MSF2) += mss-timer.o common-obj-$(CONFIG_RASPI) += bcm2835_systmr.o +common-obj-$(CONFIG_HERCULES) += hercules_rti.o diff --git a/hw/timer/hercules_rti.c b/hw/timer/hercules_rti.c new file mode 100644 index 000000000000..d75e2450ec69 --- /dev/null +++ b/hw/timer/hercules_rti.c @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/ptimer.h" +#include "qemu/main-loop.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" + +#include "hw/timer/hercules_rti.h" +#include "hw/arm/hercules.h" + +enum HerculesRtiRegisters { + RTIGCTRL = 0x00, + RTICOMPCTRL = 0x0C, + RTIFRC0 = 0x10, + RTICPUC0 = 0x18, + RTICPUC1 = 0x38, + COMPSEL0 = BIT(0), + COMPSEL1 = BIT(4), + COMPSEL2 = BIT(8), + COMPSEL3 = BIT(12), + COMPSEL_ALL = COMPSEL0 | COMPSEL1 | COMPSEL2 | COMPSEL3, + RTICOMP0 = 0x50, + RTIUDCP0 = 0x54, + RTISETINTENA = 0x80, + RTICLEARINTENA = 0x84, + RTIINTFLAG = 0x88, +}; + +#define CNTnEN(n) BIT(n) + +static void hercules_rti_update_irq(HerculesRtiState *s, + unsigned long changed) +{ + const uint32_t masked = s->intflag & s->intena; + int i; + + for_each_set_bit(i, &changed, 32) { + const int group = i / HERCULES_RTI_INT_PER_GROUP; + const int line = i % HERCULES_RTI_INT_PER_GROUP; + + qemu_set_irq(s->irq[group][line], masked & BIT(i)); + } +} + +static bool hercules_rti_frc_is_enabled(const HerculesRtiFrc *frc) +{ + return frc->enabled && frc->gctrl_en & frc->rti->gctrl; +} + +static void hercules_rti_set_frc(HerculesRtiFrc *frc, uint32_t value) +{ + frc->timestamp = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + frc->counter = value; +} + +static void __hercules_rti_compare_event(HerculesRtiCompareModule *c, + bool update_timer) +{ + c->rti->intflag |= c->mask; + + if (c->udcp) { + const int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + hercules_rti_set_frc(c->frc, c->comp); + c->comp += c->udcp; + + if (update_timer) { + timer_mod(c->timer, now + c->udcp_ns); + } + } + + hercules_rti_update_irq(c->rti, c->mask); +} + +static void hercules_rti_compare_event(void *opaque) +{ + __hercules_rti_compare_event(opaque, true); +} + +static uint32_t hercules_rti_get_frc(HerculesRtiFrc *frc) +{ + if (hercules_rti_frc_is_enabled(frc)) { + const int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + const int64_t delta_ns = now - frc->timestamp; + const uint32_t result = frc->counter + delta_ns / frc->period; + + hercules_rti_set_frc(frc, result); + + return result; + } + + return frc->counter; +} + +static uint64_t hercules_rti_read(void *opaque, hwaddr offset, unsigned size) +{ + HerculesRtiState *s = opaque; + HerculesRtiCompareModule *c; + uint32_t frc; + + switch (offset) { + case RTIGCTRL: + return s->gctrl; + case RTIFRC0: + frc = hercules_rti_get_frc(&s->frc[0]); + return frc; + case RTICPUC0: + return s->frc[0].cpuc; + case RTICOMP0: + c = &s->compare[0]; + return c->comp; + case RTIUDCP0: + c = &s->compare[0]; + return c->udcp; + case RTISETINTENA: + case RTICLEARINTENA: + return s->intena; + case RTIINTFLAG: + return s->intflag; + default: + return 0; + } +} + +static void __hercules_rti_update_capture(HerculesRtiState *s, + unsigned long compctrl) +{ + int i; + + /* + * We go through all of the capture and compare channels specified + * by @compctrl and and either enable or disable corresponding + * timer (based on the value passed in @on) + */ + for_each_set_bit(i, &compctrl, 32) { + /* + * COMPSEL1,2,3,4 are bits 0, 4, 8 and 12, so we need to + * divide bit number by 4 to get corresponding compare module + */ + HerculesRtiCompareModule *c = &s->compare[i / 4]; + const uint32_t scale = c->frc->cpuc * c->frc->period; + + if (c->udcp) { + /* + * If RTIUDCPn is specified, let's cache its value in + * nanoseconds for future use. + */ + c->udcp_ns = c->udcp * scale; + } + + if (hercules_rti_frc_is_enabled(c->frc)) { + const int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + uint32_t counter = hercules_rti_get_frc(c->frc); + uint32_t delta; + + if (c->comp <= counter) { + delta = UINT32_MAX - counter + c->comp; + /* + * TODO: Emulate overflow interrupt + */ + } else { + delta = c->comp - counter; + } + + timer_mod(c->timer, now + delta * scale); + } else { + timer_del(c->timer); + } + } +} + +static uint32_t hercules_rti_compctrl(HerculesRtiState *s, unsigned int idx) +{ + /* + * All compare channels that are running off of free-running + * counter 0 will have their COMPSELn bits set to 0, so we need to + * invert COMPCTRL before we mask it by compsel to flip those + * zeros to ones + */ + return idx ? s->compctrl : ~s->compctrl; +} + +static void +hercules_rti_update_capture_cnt(HerculesRtiState *s, unsigned int idx, + uint32_t compsel) +{ + __hercules_rti_update_capture(s, hercules_rti_compctrl(s, idx) & compsel); +} + +static void hercules_rti_update_capture(HerculesRtiState *s, uint32_t gctrl, + uint32_t compsel) +{ + if (gctrl & CNTnEN(0)) { + hercules_rti_update_capture_cnt(s, 0, compsel); + } + + if (gctrl & CNTnEN(1)) { + hercules_rti_update_capture_cnt(s, 1, compsel); + } +} + +static void hercules_rti_write(void *opaque, hwaddr offset, uint64_t val64, + unsigned size) +{ + HerculesRtiState *s = opaque; + HerculesRtiCompareModule *c; + const uint32_t val = val64; + uint32_t gctrl; + uint32_t intflag; + uint32_t intena; + + switch (offset) { + case RTIGCTRL: + /* + * We only update timer settings for free-running counter + * whose state was changed by this write. "on" -> "on" and + * "off" -> "off" transitions should be a no-op + */ + gctrl = s->gctrl ^ val; + s->gctrl = val; + hercules_rti_update_capture(s, gctrl, COMPSEL_ALL); + break; + case RTIFRC0: + hercules_rti_set_frc(&s->frc[0], val); + break; + case RTICPUC0: + hercules_rti_get_frc(&s->frc[0]); + s->frc[0].cpuc = val; + hercules_rti_update_capture_cnt(s, 0, COMPSEL_ALL); + break; + case RTICPUC1: + s->frc[1].cpuc = val; + hercules_rti_update_capture_cnt(s, 1, COMPSEL_ALL); + break; + case RTICOMP0: + c = &s->compare[0]; + c->comp = val; + hercules_rti_update_capture(s, s->gctrl, COMPSEL0); + break; + case RTIUDCP0: + c = &s->compare[0]; + c->udcp = val; + hercules_rti_update_capture(s, s->gctrl, COMPSEL0); + break; + case RTISETINTENA: + intena = s->intena; + s->intena |= val; + hercules_rti_update_irq(s, intena ^ s->intena); + break; + case RTICLEARINTENA: + intena = s->intena; + s->intena &= ~val; + hercules_rti_update_irq(s, intena ^ s->intena); + break; + case RTIINTFLAG: + intflag = s->intflag; + s->intflag &= ~val; + hercules_rti_update_irq(s, intflag ^ s->intflag); + break; + default: + return; + } +} + +static void hercules_rti_init_irq_group(HerculesRtiState *s, int group, + int line_num) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(s); + int i; + + s->irq[group] = g_new(qemu_irq, line_num); + for (i = 0; i < line_num; i++) { + sysbus_init_irq(sbd, &s->irq[group][i]); + } +} + +static void hercules_rti_reset_irq_group(HerculesRtiState *s, int group, + int line_num) +{ + int i; + for (i = 0; i < line_num; i++) { + qemu_irq_lower(s->irq[group][i]); + } +} + +static void hercules_rti_realize(DeviceState *dev, Error **errp) +{ + HerculesRtiState *s = HERCULES_RTI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Object *obj = OBJECT(dev); + HerculesState *parent = HERCULES_SOC(obj->parent); + int i; + + static MemoryRegionOps hercules_rti_ops = { + .read = hercules_rti_read, + .write = hercules_rti_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + }; + + if (parent->is_tms570) + { + hercules_rti_ops.endianness = DEVICE_BIG_ENDIAN; + } + + memory_region_init_io(&s->iomem, obj, &hercules_rti_ops, s, + TYPE_HERCULES_RTI ".io", + HERCULES_RTI_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + for (i = 0; i < HERCULES_RTI_INT_LINE_COMPARE_NUM; i++) { + HerculesRtiCompareModule *c = &s->compare[i]; + + c->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + hercules_rti_compare_event, + c); + c->mask = BIT(i); + c->rti = s; + } + + for (i = 0; i < HERCULES_RTI_FRC_NUM; i++) { + HerculesRtiFrc *frc = &s->frc[i]; + /* + * Hardcode to 75 Mhz clock ~13ns per tick + */ + frc->period = 13; + frc->rti = s; + frc->gctrl_en = CNTnEN(i); + } + + hercules_rti_init_irq_group(s, HERCULES_RTI_INT_GROUP_COMPARE, + HERCULES_RTI_INT_LINE_COMPARE_NUM); + + hercules_rti_init_irq_group(s, HERCULES_RTI_INT_GROUP_DMA, + HERCULES_RTI_INT_LINE_DMA_NUM); + + hercules_rti_init_irq_group(s, HERCULES_RTI_INT_GROUP_TBOVL, + HERCULES_RTI_INT_LINE_TBOVL_NUM); +} + +static void hercules_compare_adjust_frc(HerculesRtiState *s) +{ + int i; + + for (i = 0; i < HERCULES_RTI_INT_LINE_COMPARE_NUM; i++) { + s->compare[i].frc = &s->frc[!!(s->compctrl & BIT(4 * i))]; + } +} + +static void hercules_rti_reset(DeviceState *d) +{ + HerculesRtiState *s = HERCULES_RTI(d); + int i; + + s->gctrl = 0; + s->intflag = 0; + s->intena = 0; + s->compctrl = 0; + + for (i = 0; i < HERCULES_RTI_FRC_NUM; i++) { + HerculesRtiFrc *frc = &s->frc[i]; + + frc->counter = 0; + frc->cpuc = 0; + frc->timestamp = 0; + + frc->enabled = true; + } + + hercules_compare_adjust_frc(s); + + for (i = 0; i < HERCULES_RTI_INT_LINE_COMPARE_NUM; i++) { + HerculesRtiCompareModule *c = &s->compare[i]; + + timer_del(c->timer); + c->comp = 0; + c->udcp = 0; + c->udcp_ns = 0; + } + + hercules_rti_reset_irq_group(s, HERCULES_RTI_INT_GROUP_COMPARE, + HERCULES_RTI_INT_LINE_COMPARE_NUM); + + hercules_rti_reset_irq_group(s, HERCULES_RTI_INT_GROUP_DMA, + HERCULES_RTI_INT_LINE_DMA_NUM); + + hercules_rti_reset_irq_group(s, HERCULES_RTI_INT_GROUP_TBOVL, + HERCULES_RTI_INT_LINE_TBOVL_NUM); +} + +static void hercules_rti_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = hercules_rti_reset; + dc->realize = hercules_rti_realize; +} + +static const TypeInfo hercules_rti_info = { + .name = TYPE_HERCULES_RTI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HerculesRtiState), + .class_init = hercules_rti_class_init, +}; + +static void hercules_rti_register_types(void) +{ + type_register_static(&hercules_rti_info); +} + +type_init(hercules_rti_register_types) + +void hercules_rti_counter_enable(HerculesRtiState *s, uint32_t idx, + bool enable) +{ + if (s->frc[idx].enabled != enable) { + uint32_t compctrl = hercules_rti_compctrl(s, idx) & COMPSEL_ALL; + s->frc[idx].enabled = enable; + __hercules_rti_update_capture(s, compctrl); + } +} + +static HerculesRtiCompareModule * +hercules_rti_next_active_compare(HerculesRtiState *s, unsigned long compctrl, + uint32_t now, uint32_t dest) +{ + HerculesRtiCompareModule *active = NULL; + uint32_t deadline = dest; + int i; + + for_each_set_bit(i, &compctrl, 32) { + HerculesRtiCompareModule *c = &s->compare[i / 4]; + + if (c->comp < now) { + /* + * skip compare channels that are in the "past" + */ + continue; + } + + if (c->comp < deadline) { + /* + * for the ones that aren't - find the soonest one to + * expire + */ + deadline = c->comp; + active = c; + } + } + + return active; +} + +void hercules_rti_counter_advance(HerculesRtiState *s, uint32_t idx, + uint32_t delta) +{ + HerculesRtiCompareModule *c; + bool needs_disabling = s->frc[idx].enabled; + uint32_t now = s->frc[idx].counter; + uint32_t dest = now + delta; + uint32_t compctrl = hercules_rti_compctrl(s, idx) & COMPSEL_ALL; + + if (needs_disabling) { + hercules_rti_counter_enable(s, idx, false); + } + + while ((c = hercules_rti_next_active_compare(s, compctrl, now, dest))) { + now = c->comp; + __hercules_rti_compare_event(c, false); + } + + s->frc[idx].counter = dest; + + if (needs_disabling) { + hercules_rti_counter_enable(s, idx, true); + } +} diff --git a/include/hw/adc/hercules_mibadc.h b/include/hw/adc/hercules_mibadc.h new file mode 100644 index 000000000000..2efe62ce587c --- /dev/null +++ b/include/hw/adc/hercules_mibadc.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_MIBADC_H +#define HERCULES_MIBADC_H + +#include "hw/sysbus.h" + +typedef struct HerculesMibAdcGroup { + uint32_t sr; + uint32_t sel; + uint32_t intflg; + + uint8_t start; + uint8_t end; + uint8_t wridx; + uint8_t rdidx; +} HerculesMibAdcGroup; + +typedef struct HerculesMibAdcState { + SysBusDevice parent_obj; + + MemoryRegion regs; + struct { + MemoryRegion container; + MemoryRegion ram; + MemoryRegion ecc; + } io; + + uint32_t adopmodecr; + uint32_t adparcr; + uint32_t adparaddr; + HerculesMibAdcGroup adg[3]; + + uint32_t results[64]; + uint32_t ecc[64]; + uint16_t channel[32]; + + qemu_irq parity_error; +} HerculesMibAdcState; + +#define TYPE_HERCULES_MIBADC "ti-hercules-mibadc" +#define HERCULES_MIBADC(obj) OBJECT_CHECK(HerculesMibAdcState, (obj), \ + TYPE_HERCULES_MIBADC) + +#endif diff --git a/include/hw/arm/hercules.h b/include/hw/arm/hercules.h new file mode 100644 index 000000000000..9edc62d4d36e --- /dev/null +++ b/include/hw/arm/hercules.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_H +#define HERCULES_H + +#include "qemu/osdep.h" +#include "hw/arm/boot.h" +#include "hw/misc/hercules_l2ramw.h" +#include "hw/char/hercules_rtp.h" +#include "hw/intc/hercules_vim.h" +#include "hw/misc/hercules_system.h" +#include "hw/gpio/hercules_gpio.h" +#include "hw/adc/hercules_mibadc.h" +#include "hw/timer/hercules_rti.h" +#include "hw/net/hercules_emac.h" +#include "hw/ssi/hercules_spi.h" +#include "hw/dma/hercules_dma.h" +#include "hw/misc/hercules_scm.h" +#include "hw/misc/hercules_esm.h" +#include "hw/misc/hercules_efuse.h" +#include "hw/misc/hercules_pmm.h" +#include "hw/misc/hercules_stc.h" +#include "hw/misc/hercules_pbist.h" +#include "hw/misc/hercules_ccm.h" +#include "hw/misc/hercules_l2fmc.h" +#include "hw/misc/hercules_ecap.h" + +#define TYPE_HERCULES_SOC "ti-hercules" + +enum HerculesConfiguration { + HERCULES_NUM_N2HETS = 2, + HERCULES_NUM_MIBADCS = 2, + HERCULES_NUM_MIBSPIS = 5, + HERCULES_NUM_ECAPS = 6, +}; + +struct ARMCPU; + +typedef struct HerculesState { + /*< private >*/ + DeviceState parent_obj; + /*< public >*/ + + /* properties */ + BlockBackend *blk_eeprom; + bool is_tms570; + /* end properties */ + + struct ARMCPU* cpu; + HerculesL2RamwState l2ramw; + HerculesRTPState rtp; + + HerculesVimState vim; + HerculesSystemState system; + HerculesGioState gio; + HerculesN2HetState n2het[HERCULES_NUM_N2HETS]; + HerculesMibAdcState mibadc[HERCULES_NUM_MIBADCS]; + HerculesRtiState rti; + HerculesEmacState emac; + HerculesDMAState dma; + HerculesMibSpiState mibspi[HERCULES_NUM_MIBSPIS]; + HerculesSCMState scm; + HerculesESMState esm; + HerculesEFuseState efuse; + HerculesPMMState pmm; + HerculesSTCState stc; + HerculesPBISTState pbist; + HerculesCCMState ccm; + HerculesL2FMCState l2fmc; + HerculesECAPState ecap[HERCULES_NUM_ECAPS]; +} HerculesState; + +#define HERCULES_SOC(obj) OBJECT_CHECK(HerculesState, (obj), TYPE_HERCULES_SOC) + +enum HerculesMemoryMap { + HERCULES_FLASH_ADDR = 0x00000000, + HERCULES_FLASH_SIZE = 4 * 1024 * 1024, + + HERCULES_RAM_ADDR = 0x08000000, + + HERCULES_EMIF_CS1_ADDR = 0x80000000, + + HERCULES_OTP_BANK1_ADDR = 0xF0080000, + HERCULES_OTP_BANK1_SIZE = 8 * 1024, + + HERCULES_EEPROM_ADDR = 0xF0200000, + HERCULES_EEPROM_SIZE = 128 * 1024, + + HERCULES_SDR_MMR_ADDR = 0xFA000000, + + HERCULES_EMAC_CPPI_ADDR = 0xFC520000, + + HERCULES_EMAC_MODULE_ADDR = 0xFCF78000, + HERCULES_EMAC_CTRL_ADDR = 0xFCF78800, + HERCULES_EMAC_MDIO_ADDR = 0xFCF78900, + + HERCULES_ECAP1_ADDR = 0xFCF79300, + HERCULES_ECAP2_ADDR = 0xFCF79400, + HERCULES_ECAP3_ADDR = 0xFCF79500, + HERCULES_ECAP4_ADDR = 0xFCF79600, + HERCULES_ECAP5_ADDR = 0xFCF79700, + HERCULES_ECAP6_ADDR = 0xFCF79800, + + HERCULES_NMPU_ADDR = 0xFCFF1800, + HERCULES_NMPU_SIZE = 512, + + HERCULES_PCR2_ADDR = 0xFCFF1000, + + HERCULES_EMIF_ADDR = 0xFCFFE800, + HERCULES_EMIF_SIZE = 256, + + HERCULES_MIBSPI4_RAM_ADDR = 0xFF060000, + + HERCULES_MIBSPI2_RAM_ADDR = 0xFF080000, + + HERCULES_MIBSPI5_RAM_ADDR = 0xFF0A0000, + + HERCULES_MIBSPI3_RAM_ADDR = 0xFF0C0000, + + HERCULES_MIBSPI1_RAM_ADDR = 0xFF0E0000, + + HERCULES_MIBADC2_RAM_ADDR = 0xFF3A0000, + + HERCULES_MIBADC1_RAM_ADDR = 0xFF3E0000, + + HERCULES_N2HET2_RAM_ADDR = 0xFF440000, + + HERCULES_N2HET1_RAM_ADDR = 0xFF460000, + + HERCULES_DEBUG_ROM_ADDR = 0xFFA00000, + HERCULES_DEBUG_ADDR = 0xFFA01000, + HERCULES_ETM_ADDR = 0xFFA02000, + HERCULES_TPIU_ADDR = 0xFFA03000, + HERCULES_POM_ADDR = 0xFFA04000, + HERCULES_CTI1_ADDR = 0xFFA07000, + HERCULES_CTI2_ADDR = 0xFFA07000, + HERCULES_CTI3_ADDR = 0xFFA07000, + HERCULES_CTI4_ADDR = 0xFFA07000, + HERCULES_CTSF_ADDR = 0xFFA07000, + HERCULES_DEBUG_SIZE = 4 * 1024, + + HERCULES_PCR_ADDR = 0xFFFF1000, + + HERCULES_PINMUX_ADDR = 0xFFFF1C00, + HERCULES_PINMUX_SIZE = 1024, + + HERCULES_PCR3_ADDR = 0xFFF78000, + + HERCULES_N2HET1_ADDR = 0xFFF7B800, + + HERCULES_N2HET2_ADDR = 0xFFF7B900, + + HERCULES_GIO_ADDR = 0xFFF7BC00, + + HERCULES_MIBADC1_ADDR = 0xFFF7C000, + + HERCULES_MIBADC2_ADDR = 0xFFF7C200, + + HERCULES_LIN1_ADDR = 0xFFF7E400, + HERCULES_LIN1_SIZE = 256, + + HERCULES_MIBSPI1_CTRL_ADDR = 0xFFF7F400, + + HERCULES_MIBSPI2_CTRL_ADDR = 0xFFF7F600, + + HERCULES_MIBSPI3_CTRL_ADDR = 0xFFF7F800, + + HERCULES_MIBSPI4_CTRL_ADDR = 0xFFF7FA00, + + HERCULES_MIBSPI5_CTRL_ADDR = 0xFFF7FC00, + + HERCULES_DMA_RAM_ADDR = 0xFFF80000, + + HERCULES_VIM_RAM_ADDR = 0xFFF82000, + + HERCULES_L2FMC_ADDR = 0xFFF87000, + + HERCULES_EFUSE_ADDR = 0xFFF8C000, + + HERCULES_PMM_ADDR = 0xFFFF0000, + + HERCULES_SCM_ADDR = 0xFFFF0A00, + + HERCULES_EPC_ADDR = 0xFFFF0C00, + + HERCULES_RTP_ADDR = 0xFFFFFA00, + + HERCULES_DMA_ADDR = 0xFFFFF000, + + HERCULES_SYS2_ADDR = 0xFFFFE100, + + HERCULES_PBIST_ADDR = 0xFFFFE400, + + HERCULES_STC1_ADDR = 0xFFFFE600, + + HERCULES_DCC1_ADDR = 0xFFFFEC00, + HERCULES_DCC1_SIZE = 256, + + HERCULES_ESM_ADDR = 0xFFFFF500, + + HERCULES_CCM_ADDR = 0xFFFFF600, + + HERCULES_L2RAMW_ADDR = 0xFFFFF900, + + HERCULES_RTI_ADDR = 0xFFFFFC00, + + HERCULES_VIM_ECC_ADDR = 0xFFFFFD00, + + HERCULES_VIM_CONTROL_ADDR = 0xFFFFFE00, + + HERCULES_SYS_ADDR = 0xFFFFFF00, +}; + +#endif diff --git a/include/hw/char/hercules_rtp.h b/include/hw/char/hercules_rtp.h new file mode 100644 index 000000000000..8dd6b8a9407e --- /dev/null +++ b/include/hw/char/hercules_rtp.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HW_HERCULES_RTP_H +#define HW_HERCULES_RTP_H + +#include "hw/sysbus.h" +#include "chardev/char-fe.h" + +#define TYPE_HERCULES_RTP "hercules-rtp" +#define HERCULES_RTP(obj) OBJECT_CHECK(HerculesRTPState, (obj), \ + TYPE_HERCULES_RTP) + +/* This shares the same struct (and cast macro) as the base pl011 device */ + +typedef struct HerculesRTPState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + CharBackend chr; +} HerculesRTPState; + +#define HERCULES_RTP_SIZE 256 + +#endif diff --git a/include/hw/dma/hercules_dma.h b/include/hw/dma/hercules_dma.h new file mode 100644 index 000000000000..8f330fd503af --- /dev/null +++ b/include/hw/dma/hercules_dma.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_DMA_H +#define HERCULES_DMA_H + +enum { + HERCULES_DMA_CHANNEL_NUM = 32, + HERCULES_DMA_REQUEST_NUM = 48, +}; + +typedef struct HerculesDMAChannel { + struct { + MemoryRegion io; + + uint32_t isaddr; + uint32_t idaddr; + uint16_t iftcount; + uint16_t ietcount; + uint32_t chctrl; + uint16_t eidxd; + uint16_t eidxs; + uint16_t fidxd; + uint16_t fidxs; + } pcp; + + struct { + MemoryRegion io; + + uint32_t csaddr; + uint32_t cdaddr; + uint16_t cftcount; + uint16_t cetcount; + } wcp; +} HerculesDMAChannel; + +typedef struct HerculesDMAState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion ram; + + uint32_t hwchena; + uint32_t dreqasi[8]; + uint32_t btcflag; + uint32_t gctrl; + + uint32_t reqmap[HERCULES_DMA_REQUEST_NUM]; + HerculesDMAChannel channel[HERCULES_DMA_CHANNEL_NUM]; +} HerculesDMAState; + +#define TYPE_HERCULES_DMA "ti-hercules-dma" +#define HERCULES_DMA(obj) OBJECT_CHECK(HerculesDMAState, (obj), \ + TYPE_HERCULES_DMA) +#endif diff --git a/include/hw/gpio/hercules_gpio.h b/include/hw/gpio/hercules_gpio.h new file mode 100644 index 000000000000..9af4d64aaa1c --- /dev/null +++ b/include/hw/gpio/hercules_gpio.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_GPIO_H +#define HERCULES_GPIO_H + +#include "hw/misc/unimp.h" + +typedef struct HerculesGpio { + uint32_t dir; + uint32_t din; + uint32_t dout; + uint32_t dset; + uint32_t dclr; + uint32_t pdr; + uint32_t puldis; + uint32_t psl; + + unsigned int bank; +} HerculesGpio; + +typedef struct HerculesGioState { + SysBusDevice parent_obj; + + /* CTRL registers */ + struct { + MemoryRegion gioa; + MemoryRegion giob; + MemoryRegion regs; + MemoryRegion container; + } io; + + HerculesGpio gpio[2]; + + uint32_t gioena; + uint32_t giolvl; + uint32_t gioflg; + +} HerculesGioState; + +enum { + HERCULES_N2HET_REG_SIZE = 256, + HERCULES_N2HET_RAM_SIZE = 128 * 1024, +}; + +typedef struct HerculesN2HetState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t hetlbpdir; + HerculesGpio gpio; + + UnimplementedDeviceState ram; +} HerculesN2HetState; + +#define TYPE_HERCULES_GIO "ti-hercules-gio" +#define HERCULES_GIO(obj) OBJECT_CHECK(HerculesGioState, (obj), \ + TYPE_HERCULES_GIO) + +#define TYPE_HERCULES_N2HET "ti-hercules-n2het" +#define HERCULES_N2HET(obj) OBJECT_CHECK(HerculesN2HetState, (obj), \ + TYPE_HERCULES_N2HET) + +#endif diff --git a/include/hw/intc/hercules_vim.h b/include/hw/intc/hercules_vim.h new file mode 100644 index 000000000000..66ec8dae9a58 --- /dev/null +++ b/include/hw/intc/hercules_vim.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_VIM_H +#define HERCULES_VIM_H + +#include "hw/misc/unimp.h" + +enum HerculesVimInterruprs { + HERCULES_ESM_HIGH_LEVEL_IRQ = 0, + HERCULES_RTI_COMPARE0_IRQ = 2, + HERCULES_RTI_COMPARE1_IRQ = 3, + HERCULES_RTI_COMPARE2_IRQ = 4, + HERCULES_RTI_COMPARE3_IRQ = 5, + HERCULES_RTI_OVERFLOW0_IRQ = 6, + HERCULES_RTI_OVERFLOW1_IRQ = 7, + HERCULES_RTI_TIME_BASE_IRQ = 8, + + HERCULES_ESM_LOW_LEVEL_IRQ = 20, + HERCULES_SSI_IRQ = 21, + HERCULES_MIBSPI1_L0_IRQ = 12, + HERCULES_MIBSPI1_L1_IRQ = 26, + HERCULES_MIBSPI2_L0_IRQ = 17, + HERCULES_MIBSPI2_L1_IRQ = 30, + HERCULES_MIBSPI3_L0_IRQ = 37, + HERCULES_MIBSPI3_L1_IRQ = 38, + HERCULES_MIBSPI4_L0_IRQ = 49, + HERCULES_MIBSPI4_L1_IRQ = 54, + HERCULES_MIBSPI5_L0_IRQ = 53, + HERCULES_MIBSPI5_L1_IRQ = 56, + + HERCULES_NUM_IRQ = 128, + + HERCULES_IRQ_GROUP_WIDTH = BITS_PER_LONG, + HERCULES_NUM_IRQ_GROUP = HERCULES_NUM_IRQ / BITS_PER_LONG, +}; + +#define HERCULES_VIM_BITFIELD(_name) \ + union { \ + unsigned long _name[HERCULES_NUM_IRQ_GROUP]; \ + struct QEMU_PACKED { \ + uint32_t _name##0; \ + uint32_t _name##1; \ + uint32_t _name##2; \ + uint32_t _name##3; \ + }; \ + } + +typedef struct HerculesVimState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion ram; + UnimplementedDeviceState ecc; + uint32_t vectors[HERCULES_NUM_IRQ]; + + HERCULES_VIM_BITFIELD(intreq); + HERCULES_VIM_BITFIELD(reqena); + HERCULES_VIM_BITFIELD(firqpr); + /* + * firqpr inverted + */ + HERCULES_VIM_BITFIELD(rpqrif); + + uint8_t chanctrl[HERCULES_NUM_IRQ]; + + qemu_irq irq; + qemu_irq fiq; +} HerculesVimState; + +#define TYPE_HERCULES_VIM "ti-hercules-vim" +#define HERCULES_VIM(obj) OBJECT_CHECK(HerculesVimState, (obj), \ + TYPE_HERCULES_VIM) +#endif diff --git a/include/hw/misc/hercules_ccm.h b/include/hw/misc/hercules_ccm.h new file mode 100644 index 000000000000..86827f80de8d --- /dev/null +++ b/include/hw/misc/hercules_ccm.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_CCM_H +#define HERCULES_CCM_H + +#include "hercules_system.h" + +typedef struct HerculesCCMState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t ccmsr[3]; + + qemu_irq error[3]; + qemu_irq error_self_test; +} HerculesCCMState; + +#define TYPE_HERCULES_CCM "ti-hercules-ccm" +#define HERCULES_CCM(obj) OBJECT_CHECK(HerculesCCMState, (obj), \ + TYPE_HERCULES_CCM) +#endif diff --git a/include/hw/misc/hercules_ecap.h b/include/hw/misc/hercules_ecap.h new file mode 100644 index 000000000000..5bfdded5b38e --- /dev/null +++ b/include/hw/misc/hercules_ecap.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_ECAP_H +#define HERCULES_ECAP_H + +#include "hercules_system.h" + +enum { + HERCULES_ECAP_NUM_CAPS = 4, +}; + +typedef struct HerculesECAPState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t cap[HERCULES_ECAP_NUM_CAPS]; + uint16_t ecflg; + +} HerculesECAPState; + +#define TYPE_HERCULES_ECAP "ti-hercules-ecap" +#define HERCULES_ECAP(obj) OBJECT_CHECK(HerculesECAPState, (obj), \ + TYPE_HERCULES_ECAP) +#endif diff --git a/include/hw/misc/hercules_efuse.h b/include/hw/misc/hercules_efuse.h new file mode 100644 index 000000000000..eb27f7d52daa --- /dev/null +++ b/include/hw/misc/hercules_efuse.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_EFUSE_H +#define HERCULES_EFUSE_H + +typedef struct HerculesEFuseState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t efcpins; + uint32_t efcstcy; + uint32_t efcstsig; + + qemu_irq autoload_error; + qemu_irq self_test_error; + qemu_irq single_bit_error; +} HerculesEFuseState; + +#define TYPE_HERCULES_EFUSE "ti-hercules-efuse" +#define HERCULES_EFUSE(obj) OBJECT_CHECK(HerculesEFuseState, (obj), \ + TYPE_HERCULES_EFUSE) +#endif diff --git a/include/hw/misc/hercules_esm.h b/include/hw/misc/hercules_esm.h new file mode 100644 index 000000000000..929c48033cb9 --- /dev/null +++ b/include/hw/misc/hercules_esm.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_ESM_H +#define HERCULES_ESM_H + +enum { + HERCULES_ESM_IRQ_HIGH = 0, + HERCULES_ESM_IRQ_LOW = 1, + HERCULES_ESM_NUM_IRQS = 2, + HERCULES_ESM_NUM_GROUP1 = 96, + HERCULES_ESM_NUM_GROUP2 = 32, + + HERCULES_NUM_ESM_CHANNELS = 160, +}; + +#define HERCULES_ESM_GROUP1(n) (n) +#define HERCULES_ESM_GROUP2(n) (HERCULES_ESM_NUM_GROUP1 + (n)) +#define HERCULES_ESM_GROUP3(n) (HERCULES_ESM_NUM_GROUP2 + \ + HERCULES_ESM_GROUP2(n)) + +enum HerculesESMChannels { + HERCULES_MIBADC2_PARITY_ERROR = HERCULES_ESM_GROUP1(1), + HERCULES_EPC_CORRECTABLE_ERROR = HERCULES_ESM_GROUP1(4), + HERCULES_PLL1_SLIP_ERROR = HERCULES_ESM_GROUP1(10), + HERCULES_MIBADC1_PARITY_ERROR = HERCULES_ESM_GROUP1(19), + + HERCULES_CCMR5F_SELF_TEST_ERROR = HERCULES_ESM_GROUP1(31), + HERCULES_PMM_COMPARE_ERROR = HERCULES_ESM_GROUP1(38), + HERCULES_PMM_SELF_TEST_ERROR = HERCULES_ESM_GROUP1(39), + HERCULES_EFUSE_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(40), + HERCULES_EFUSE_SELF_TEST_ERROR = HERCULES_ESM_GROUP1(41), + HERCULES_PLL2_SLIP_ERROR = HERCULES_ESM_GROUP1(42), + + HERCULES_MIBSPI1_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(77), + HERCULES_MIBSPI2_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(78), + HERCULES_MIBSPI3_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(79), + HERCULES_MIBSPI4_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(80), + HERCULES_MIBSPI5_SINGLE_BIT_ERROR = HERCULES_ESM_GROUP1(81), + + HERCULES_MIBSPI1_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP1(17), + HERCULES_MIBSPI2_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP1(49), + HERCULES_MIBSPI3_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP1(18), + HERCULES_MIBSPI4_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP1(50), + HERCULES_MIBSPI5_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP1(24), + + HERCULES_CCMR5F_CPU_COMPARE_ERROR = HERCULES_ESM_GROUP2(2), + HERCULES_CR5F_FATAL_BUS_ERROR = HERCULES_ESM_GROUP2(3), + HERCULES_L2RAMW_TYPE_B_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP2(7), + HERCULES_CCMR5F_VIM_COMPARE_ERROR = HERCULES_ESM_GROUP2(25), + HERCULES_CPU1_AXIM_BUS_MONITOR_ERROR = HERCULES_ESM_GROUP2(26), + + HERCULES_EFUSE_AUTOLOAD_ERROR = HERCULES_ESM_GROUP3(1), + + HERCULES_L2FMC_UNCORRECTABLE_ERROR = HERCULES_ESM_GROUP3(13), +}; + +typedef struct HerculesESMState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t esmsr[5]; + uint32_t esmil[5]; + uint32_t esmie[5]; + uint32_t esmiep[5]; + + unsigned int irq_state; + qemu_irq irq[HERCULES_ESM_NUM_IRQS]; +} HerculesESMState; + +#define TYPE_HERCULES_ESM "ti-hercules-esm" +#define HERCULES_ESM(obj) OBJECT_CHECK(HerculesESMState, (obj), \ + TYPE_HERCULES_ESM) +#endif diff --git a/include/hw/misc/hercules_l2fmc.h b/include/hw/misc/hercules_l2fmc.h new file mode 100644 index 000000000000..645b346ed729 --- /dev/null +++ b/include/hw/misc/hercules_l2fmc.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_L2FMC_H +#define HERCULES_L2FMC_H + +typedef struct HerculesL2FMCState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegion epc; + + uint32_t fdiagctrl; + uint32_t fraw_addr; + uint32_t fprim_add_tag; + uint32_t fdup_add_tag; + uint32_t fedac_pastatus; + uint32_t fedac_pbstatus; + uint32_t femu_ecc; + + uint32_t camavailstat; + uint32_t cam_index[7]; + uint32_t cam_content[32]; + + uint32_t ecc_1bit_address; + uint32_t ecc_1bit_femu_ecc; + + qemu_irq uncorrectable_error; + qemu_irq bus_error; + qemu_irq correctable_error; +} HerculesL2FMCState; + +#define TYPE_HERCULES_L2FMC "ti-hercules-l2fmc" +#define HERCULES_L2FMC(obj) OBJECT_CHECK(HerculesL2FMCState, (obj), \ + TYPE_HERCULES_L2FMC) +#endif diff --git a/include/hw/misc/hercules_l2ramw.h b/include/hw/misc/hercules_l2ramw.h new file mode 100644 index 000000000000..39b71e529919 --- /dev/null +++ b/include/hw/misc/hercules_l2ramw.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_L2RAMW_H +#define HERCULES_L2RAMW_H + +#include "hw/sysbus.h" + +typedef struct HerculesL2RamwState { + SysBusDevice parent_obj; + + struct { + MemoryRegion ecc; + MemoryRegion sram; + MemoryRegion container; + + MemoryRegion regs; + } io; + + uint32_t ramctrl; + uint32_t ramtest; + uint32_t ramerrstatus; + uint32_t diag_ecc; + + qemu_irq uncorrectable_error; +} HerculesL2RamwState; + +#define TYPE_HERCULES_L2RAMW "ti-hercules-l2ramw" +#define HERCULES_L2RAMW(obj) OBJECT_CHECK(HerculesL2RamwState, (obj), \ + TYPE_HERCULES_L2RAMW) +#endif diff --git a/include/hw/misc/hercules_pbist.h b/include/hw/misc/hercules_pbist.h new file mode 100644 index 000000000000..dac799ab5d42 --- /dev/null +++ b/include/hw/misc/hercules_pbist.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_PBIST_H +#define HERCULES_PBIST_H + +typedef struct HerculesPBISTState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t pact; + uint32_t fsrf[2]; + uint32_t over; + uint32_t rom; + uint32_t algo; + uint32_t rinfol; + uint32_t rinfou; + + qemu_irq mstdone; +} HerculesPBISTState; + +#define TYPE_HERCULES_PBIST "ti-hercules-pbist" +#define HERCULES_PBIST(obj) OBJECT_CHECK(HerculesPBISTState, (obj), \ + TYPE_HERCULES_PBIST) +#endif diff --git a/include/hw/misc/hercules_pmm.h b/include/hw/misc/hercules_pmm.h new file mode 100644 index 000000000000..d080af60087b --- /dev/null +++ b/include/hw/misc/hercules_pmm.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_PMM_H +#define HERCULES_PMM_H + +typedef struct HerculesPMMState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t prckeyreg; + uint32_t lpddcstat1; + uint32_t lpddcstat2; + + qemu_irq compare_error; + qemu_irq self_test_error; +} HerculesPMMState; + +#define TYPE_HERCULES_PMM "ti-hercules-pmm" +#define HERCULES_PMM(obj) OBJECT_CHECK(HerculesPMMState, (obj), \ + TYPE_HERCULES_PMM) +#endif diff --git a/include/hw/misc/hercules_scm.h b/include/hw/misc/hercules_scm.h new file mode 100644 index 000000000000..d42b982a5b4c --- /dev/null +++ b/include/hw/misc/hercules_scm.h @@ -0,0 +1,25 @@ +#ifndef HERCULES_SCM_H +#define HERCULES_SCM_H + +#include "hercules_system.h" + +typedef struct HerculesSCMState { + SysBusDevice parent_obj; + + struct { + MemoryRegion scm; + MemoryRegion sdr_mmr; + } io; + + uint32_t scmcntrl; + + uint32_t sdc_status; + + QEMUBH *self_test; + qemu_irq icrst; +} HerculesSCMState; + +#define TYPE_HERCULES_SCM "ti-hercules-scm" +#define HERCULES_SCM(obj) OBJECT_CHECK(HerculesSCMState, (obj), \ + TYPE_HERCULES_SCM) +#endif diff --git a/include/hw/misc/hercules_stc.h b/include/hw/misc/hercules_stc.h new file mode 100644 index 000000000000..7d2f349c45d6 --- /dev/null +++ b/include/hw/misc/hercules_stc.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_STC_H +#define HERCULES_STC_H + +typedef struct HerculesSTCState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + uint32_t stcgcr[2]; + uint32_t stctpr; + uint32_t stcscscr; + uint32_t stcclkdiv; + uint32_t stcgstat; + + QEMUBH *self_test; + qemu_irq cpurst; +} HerculesSTCState; + +#define TYPE_HERCULES_STC "ti-hercules-stc" +#define HERCULES_STC(obj) OBJECT_CHECK(HerculesSTCState, (obj), \ + TYPE_HERCULES_STC) +#endif diff --git a/include/hw/misc/hercules_system.h b/include/hw/misc/hercules_system.h new file mode 100644 index 000000000000..3fc046f42dad --- /dev/null +++ b/include/hw/misc/hercules_system.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_SYSTEM_H +#define HERCULES_SYSTEM_H + +#include "hw/misc/unimp.h" + +enum { + HERCULES_SYSTEM_SYS_SIZE = 256, + HERCULES_SYSTEM_SYS2_SIZE = 256, + HERCULES_SYSTEM_PCR_SIZE = 2 * 1024, + HERCULES_SYSTEM_NUM_PCRS = 3, +}; + +enum HerculesSystemSignals { + HERCULES_SYSTEM_ICRST, + HERCULES_SYSTEM_CPURST, + HERCULES_SYSTEM_MSTDONE, + HERCULES_SYSTEM_NUM_SIGNALS, +}; + +typedef struct HerculesSystemState { + SysBusDevice parent_obj; + + uint32_t csdis; + uint32_t minitgcr; + uint32_t msinena; + uint32_t ministat; + uint32_t sysesr; + uint32_t mstcgstat; + uint32_t mstgcr; + + uint32_t ghvsrc; + uint32_t glbstat; + uint32_t pllctl1; + uint32_t pllctl3; + + MemoryRegion sys; + MemoryRegion sys2; + UnimplementedDeviceState pcr[HERCULES_SYSTEM_NUM_PCRS]; + + qemu_irq irq; + qemu_irq pll1_slip_error; + qemu_irq pll2_slip_error; +} HerculesSystemState; + +#define TYPE_HERCULES_SYSTEM "ti-hercules-system" +#define HERCULES_SYSTEM(obj) OBJECT_CHECK(HerculesSystemState, (obj), \ + TYPE_HERCULES_SYSTEM) +#endif diff --git a/include/hw/net/hercules_emac.h b/include/hw/net/hercules_emac.h new file mode 100644 index 000000000000..08dd36be63e1 --- /dev/null +++ b/include/hw/net/hercules_emac.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_EMAC_H +#define HERCULES_EMAC_H + +#include "net/net.h" +#include "hw/misc/unimp.h" + +enum { + HERCULES_EMAC_NUM_CHANNELS = 8, + HERCULES_EMAC_MODULE_SIZE = 2 * 1024, + HERCULES_EMAC_CONTROL_SIZE = 256, + HERCULES_EMAC_MDIO_SIZE = 256, +}; + +typedef struct HerculesEmacState { + SysBusDevice parent_obj; + + MemoryRegion module; + MemoryRegion control; + UnimplementedDeviceState mdio; + MemoryRegion ram; + + NICState *nic; + NICConf conf; + + uint32_t mac_lo[HERCULES_EMAC_NUM_CHANNELS]; + + uint32_t txcontrol; + uint32_t rxcontrol; + uint32_t maccontrol; + uint16_t rxbufferoffset; + uint32_t machash[2]; + + uint32_t mac_hi; + uint32_t macindex; + uint32_t rxmbpenable; + uint32_t rxunicast; + + uint32_t txhdp[HERCULES_EMAC_NUM_CHANNELS]; + uint32_t rxhdp[HERCULES_EMAC_NUM_CHANNELS]; + uint32_t txcp[HERCULES_EMAC_NUM_CHANNELS]; + uint32_t rxcp[HERCULES_EMAC_NUM_CHANNELS]; + + uint32_t active_channels; +} HerculesEmacState; + +#define TYPE_HERCULES_EMAC "ti-hercules-emac" +#define HERCULES_EMAC(obj) OBJECT_CHECK(HerculesEmacState, (obj), \ + TYPE_HERCULES_EMAC) + +#endif diff --git a/include/hw/ssi/hercules_spi.h b/include/hw/ssi/hercules_spi.h new file mode 100644 index 000000000000..f82a09a09453 --- /dev/null +++ b/include/hw/ssi/hercules_spi.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_SPI_H +#define HERCULES_SPI_H + +#include "hw/ssi/ssi.h" + +enum { + HERCULES_SPI_RAM_SIZE = 128, + HERCULES_SPI_SIZE = 512, + HERCULES_SPI_NUM_IRQ_LINES = 2, + HERCULES_SPI_NUM_CS_LINES = 8, + HERCULES_SPI_NUM_DMAREQS = 16, +}; + +typedef struct HerculesMibSpiState { + SysBusDevice parent_obj; + + struct { + MemoryRegion regs; + MemoryRegion txram; + MemoryRegion rxram; + MemoryRegion ram; + } io; + + uint32_t txram[HERCULES_SPI_RAM_SIZE]; + uint32_t rxram[HERCULES_SPI_RAM_SIZE]; + + uint32_t spigcr0; + uint32_t spigcr1; + uint32_t spiint0; + uint32_t spiflg; + uint32_t spipc[1]; + uint32_t spidat0; + uint32_t spidat1; + uint32_t spibuf; + uint32_t spifmt[4]; + uint32_t mibspie; + uint32_t tgintflag; + uint32_t tgxctrl[16]; + uint32_t iolpbktstcr; + uint32_t ltgpend; + uint32_t par_ecc_ctrl; + uint32_t par_ecc_stat; + uint32_t uerraddr[2]; + uint32_t eccdiag_ctrl; + uint32_t eccdiag_stat; + uint32_t sberraddr[2]; + qemu_irq irq[HERCULES_SPI_NUM_IRQ_LINES]; + + SSIBus *ssi; + qemu_irq cs_lines[HERCULES_SPI_NUM_CS_LINES]; + + qemu_irq dmareq[HERCULES_SPI_NUM_DMAREQS]; + + qemu_irq single_bit_error; + qemu_irq uncorrectable_error; + + enum { + SPIENA_IDLE, + SPIENA_BUSY, + SPIENA_PENDING_TG, + SPIENA_PENDING_SPIDATA, + } spiena; + + struct { + unsigned int n; + unsigned int end; + } pending_tg; + + QEMUBH *compatibility_dma_req; +} HerculesMibSpiState; + +#define TYPE_HERCULES_SPI "ti-hercules-spi" +#define HERCULES_SPI(obj) OBJECT_CHECK(HerculesMibSpiState, (obj), \ + TYPE_HERCULES_SPI) + +#define HERCULES_SPI_SPIENA TYPE_HERCULES_SPI "-spiena" + +#endif diff --git a/include/hw/timer/hercules_rti.h b/include/hw/timer/hercules_rti.h new file mode 100644 index 000000000000..a21d9bd0b3ae --- /dev/null +++ b/include/hw/timer/hercules_rti.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2019, Blue Origin. + * + * Authors: Andrey Smirnov + * Benjamin Kamath + */ +#ifndef HERCULES_RTI_H +#define HERCULES_RTI_H + +#include "hw/ptimer.h" + +enum HerculesRtiInterruptGroup { + HERCULES_RTI_INT_GROUP_COMPARE = 0, + HERCULES_RTI_INT_GROUP_DMA, + HERCULES_RTI_INT_GROUP_RESERVED, + HERCULES_RTI_INT_GROUP_TBOVL, + HERCULES_RTI_INT_GROUP_NUM, + HERCULES_RTI_INT_PER_GROUP = 4, +}; + +enum HerculesRtiInterruptLine { + HERCULES_RTI_INT_LINE_COMPARE0 = 0, + HERCULES_RTI_INT_LINE_COMPARE1, + HERCULES_RTI_INT_LINE_COMPARE2, + HERCULES_RTI_INT_LINE_COMPARE3, + HERCULES_RTI_INT_LINE_COMPARE_NUM, + + HERCULES_RTI_INT_LINE_DMA0 = 0, + HERCULES_RTI_INT_LINE_DMA1, + HERCULES_RTI_INT_LINE_DMA2, + HERCULES_RTI_INT_LINE_DMA3, + HERCULES_RTI_INT_LINE_DMA_NUM, + + HERCULES_RTI_INT_LINE_TB = 0, + HERCULES_RTI_INT_LINE_OVL0, + HERCULES_RTI_INT_LINE_OVL1, + HERCULES_RTI_INT_LINE_TBOVL_NUM, +}; + +enum HerculesRtiInterrupt { + HERCULES_RTI_INT_COMPARE0 = 0, + HERCULES_RTI_INT_COMPARE1, + HERCULES_RTI_INT_COMPARE2, + HERCULES_RTI_INT_COMPARE3, + HERCULES_RTI_INT_DMA0, + HERCULES_RTI_INT_DMA1, + HERCULES_RTI_INT_DMA2, + HERCULES_RTI_INT_DMA3, + HERCULES_RTI_INT_TIMEBASE, + HERCULES_RTI_INT_OVERFLOW0, + HERCULES_RTI_INT_OVERFLOW1, + HERCULES_RTI_INT_NUM +}; + +enum { + HERCULES_RTI_FRC_NUM = 2, + HERCULES_RTI_SIZE = 256, +}; + +struct HerculesRtiState; +typedef struct HerculesRtiState HerculesRtiState; + +typedef struct HerculesRtiFrc { + HerculesRtiState *rti; + uint32_t counter; + uint32_t cpuc; + uint32_t period; + int64_t timestamp; + uint32_t gctrl_en; + bool enabled; +} HerculesRtiFrc; + +typedef struct HerculesRtiCompareModule { + HerculesRtiState *rti; + HerculesRtiFrc *frc; + QEMUTimer *timer; + uint32_t comp; + uint32_t udcp; + uint32_t mask; + int64_t udcp_ns; +} HerculesRtiCompareModule; + +struct HerculesRtiState { + SysBusDevice parent_obj; + + qemu_irq *irq[HERCULES_RTI_INT_GROUP_NUM]; + MemoryRegion iomem; + + uint32_t gctrl; + uint32_t intflag; + uint32_t intena; + uint32_t compctrl; + + HerculesRtiFrc frc[HERCULES_RTI_FRC_NUM]; + HerculesRtiCompareModule compare[HERCULES_RTI_INT_LINE_COMPARE_NUM]; +}; + +#define TYPE_HERCULES_RTI "ti-hercules-rti" +#define HERCULES_RTI(obj) OBJECT_CHECK(HerculesRtiState, (obj), \ + TYPE_HERCULES_RTI) + + +void hercules_rti_counter_enable(HerculesRtiState *s, uint32_t idx, + bool enable); +void hercules_rti_counter_advance(HerculesRtiState *s, uint32_t idx, + uint32_t delta); +#endif diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h index f55ce8b320b2..4d1914c2993a 100644 --- a/include/qemu/bitops.h +++ b/include/qemu/bitops.h @@ -580,4 +580,10 @@ static inline uint64_t half_unshuffle64(uint64_t x) return x; } +#define for_each_set_bit(bit, addr, size) \ + for ((bit) = find_first_bit((addr), (size)); \ + (bit) < (size); \ + (bit) = find_next_bit((addr), (size), (bit) + 1)) + + #endif diff --git a/target/arm/arm_ldst.h b/target/arm/arm_ldst.h index 45edb108f6aa..d34eca498764 100644 --- a/target/arm/arm_ldst.h +++ b/target/arm/arm_ldst.h @@ -24,24 +24,22 @@ #include "qemu/bswap.h" /* Load an instruction and return it in the standard little-endian order */ -static inline uint32_t arm_ldl_code(CPUARMState *env, target_ulong addr, - bool sctlr_b) +static inline uint32_t arm_ldl_code(CPUARMState *env, target_ulong addr) { - return translator_ldl_swap(env, addr, bswap_code(sctlr_b)); + return translator_ldl_swap(env, addr, bswap_code(env)); } /* Ditto, for a halfword (Thumb) instruction */ -static inline uint16_t arm_lduw_code(CPUARMState *env, target_ulong addr, - bool sctlr_b) +static inline uint16_t arm_lduw_code(CPUARMState *env, target_ulong addr) { #ifndef CONFIG_USER_ONLY /* In big-endian (BE32) mode, adjacent Thumb instructions have been swapped within each word. Undo that now. */ - if (sctlr_b) { + if (arm_sctlr_b(env)) { addr ^= 2; } #endif - return translator_lduw_swap(env, addr, bswap_code(sctlr_b)); + return translator_lduw_swap(env, addr, bswap_code(env)); } #endif diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 32bec156f2d4..81544ed0b98c 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -247,6 +247,10 @@ static void arm_cpu_reset(DeviceState *dev) } env->daif = PSTATE_D | PSTATE_A | PSTATE_I | PSTATE_F; + if (cpu->cfgend_instr) { + env->uncached_cpsr |= CPSR_E; + } + if (arm_feature(env, ARM_FEATURE_M)) { uint32_t initial_msp; /* Loaded from 0x0 */ uint32_t initial_pc; /* Loaded from 0x4 */ @@ -709,7 +713,6 @@ static void arm_disas_set_info(CPUState *cpu, disassemble_info *info) { ARMCPU *ac = ARM_CPU(cpu); CPUARMState *env = &ac->env; - bool sctlr_b; if (is_a64(env)) { /* We might not be compiled with the A64 disassembler @@ -745,8 +748,7 @@ static void arm_disas_set_info(CPUState *cpu, disassemble_info *info) info->cap_mode = cap_mode; } - sctlr_b = arm_sctlr_b(env); - if (bswap_code(sctlr_b)) { + if (bswap_code(env)) { #ifdef TARGET_WORDS_BIGENDIAN info->endian = BFD_ENDIAN_LITTLE; #else @@ -755,7 +757,7 @@ static void arm_disas_set_info(CPUState *cpu, disassemble_info *info) } info->flags &= ~INSN_ARM_BE32; #ifndef CONFIG_USER_ONLY - if (sctlr_b) { + if (arm_sctlr_b(env)) { info->flags |= INSN_ARM_BE32; } #endif @@ -1083,6 +1085,9 @@ static Property arm_cpu_has_neon_property = static Property arm_cpu_has_dsp_property = DEFINE_PROP_BOOL("dsp", ARMCPU, has_dsp, true); +static Property arm_cpu_cfgend_instr_property = + DEFINE_PROP_BOOL("cfgend-instr", ARMCPU, cfgend_instr, false); + static Property arm_cpu_has_mpu_property = DEFINE_PROP_BOOL("has-mpu", ARMCPU, has_mpu, true); @@ -1242,6 +1247,8 @@ void arm_cpu_post_init(Object *obj) qdev_property_add_static(DEVICE(obj), &arm_cpu_cfgend_property); + qdev_property_add_static(DEVICE(obj), &arm_cpu_cfgend_instr_property); + if (arm_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER)) { qdev_property_add_static(DEVICE(cpu), &arm_cpu_gt_cntfrq_property); } @@ -1623,6 +1630,10 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) } } + if (cpu->cfgend_instr) { + cpu->reset_sctlr |= SCTLR_IE; + } + if (!cpu->has_el3) { /* If the has_el3 CPU property is disabled then we need to disable the * feature. diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 677584e5da04..d1a1ce6cd1b8 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -944,6 +944,7 @@ struct ARMCPU { * architecture version. */ bool cfgend; + bool cfgend_instr; QLIST_HEAD(, ARMELChangeHook) pre_el_change_hooks; QLIST_HEAD(, ARMELChangeHook) el_change_hooks; @@ -1181,6 +1182,8 @@ void pmu_init(ARMCPU *cpu); #define SCTLR_ATA0 (1ULL << 42) /* v8.5-MemTag */ #define SCTLR_ATA (1ULL << 43) /* v8.5-MemTag */ #define SCTLR_DSSBS (1ULL << 44) /* v8.5 */ +#define SCTLR_IE (1U << 31) + #define CPTR_TCPAC (1U << 31) #define CPTR_TTA (1U << 20) @@ -3155,6 +3158,11 @@ static inline bool arm_cpu_data_is_big_endian_a64(int el, uint64_t sctlr) return sctlr & (el ? SCTLR_EE : SCTLR_E0E); } +static inline bool arm_sctlr_ie(CPUARMState *env) +{ + return env->cp15.sctlr_el[1] & SCTLR_IE; +} + /* Return true if the processor is in big-endian mode. */ static inline bool arm_cpu_data_is_big_endian(CPUARMState *env) { @@ -3267,7 +3275,7 @@ static inline int cpu_mmu_index(CPUARMState *env, bool ifetch) return FIELD_EX32(env->hflags, TBFLAG_ANY, MMUIDX); } -static inline bool bswap_code(bool sctlr_b) +static inline bool bswap_code(CPUARMState *env) { #ifdef CONFIG_USER_ONLY /* BE8 (SCTLR.B = 0, TARGET_WORDS_BIGENDIAN = 1) is mixed endian. @@ -3278,12 +3286,12 @@ static inline bool bswap_code(bool sctlr_b) #ifdef TARGET_WORDS_BIGENDIAN 1 ^ #endif - sctlr_b; + arm_sctlr_b(env); #else - /* All code access in ARM is little endian, and there are no loaders - * doing swaps that need to be reversed + /* + * ARMv7-R profile speicifes IE bit to enable instruction swapping */ - return 0; + return arm_sctlr_ie(env); #endif } diff --git a/target/arm/gdbstub.c b/target/arm/gdbstub.c index ecfa88f8e605..b319b20e7a16 100644 --- a/target/arm/gdbstub.c +++ b/target/arm/gdbstub.c @@ -27,6 +27,11 @@ typedef struct RegisterSysregXmlParam { int n; } RegisterSysregXmlParam; +static uint32_t arm_gdb_get_reg32(CPUARMState *env, uint32_t reg) +{ + return arm_sctlr_ie(env) ? bswap32(reg) : reg; +} + /* Old gdb always expect FPA registers. Newer (xml-aware) gdb only expect whatever the target description contains. Due to a historical mishap the FPA registers appear in between core integer regs and the CPSR. @@ -40,7 +45,8 @@ int arm_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) if (n < 16) { /* Core integer register. */ - return gdb_get_reg32(mem_buf, env->regs[n]); + return gdb_get_reg32(mem_buf, + arm_gdb_get_reg32(env, env->regs[n])); } if (n < 24) { /* FPA registers. */ @@ -59,9 +65,10 @@ int arm_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) case 25: /* CPSR, or XPSR for M-profile */ if (arm_feature(env, ARM_FEATURE_M)) { - return gdb_get_reg32(mem_buf, xpsr_read(env)); - } else { return gdb_get_reg32(mem_buf, cpsr_read(env)); + } else { + return gdb_get_reg32(mem_buf, + arm_gdb_get_reg32(env, cpsr_read(env))); } } /* Unknown register. */ @@ -74,7 +81,7 @@ int arm_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) CPUARMState *env = &cpu->env; uint32_t tmp; - tmp = ldl_p(mem_buf); + tmp = arm_gdb_get_reg32(env, ldl_p(mem_buf)); /* Mask out low bit of PC to workaround gdb bugs. This will probably cause problems if we ever implement the Jazelle DBX extensions. */ diff --git a/target/arm/translate.c b/target/arm/translate.c index c8296116d4b3..cc470d319f56 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -10676,7 +10676,7 @@ static bool insn_crosses_page(CPUARMState *env, DisasContext *s) * boundary, so we cross the page if the first 16 bits indicate * that this is a 32 bit insn. */ - uint16_t insn = arm_lduw_code(env, s->base.pc_next, s->sctlr_b); + uint16_t insn = arm_lduw_code(env, s->base.pc_next); return !thumb_insn_is_16bit(s, s->base.pc_next, insn); } @@ -10915,7 +10915,8 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) } dc->pc_curr = dc->base.pc_next; - insn = arm_ldl_code(env, dc->base.pc_next, dc->sctlr_b); + insn = arm_ldl_code(env, dc->base.pc_next); + dc->insn = insn; dc->base.pc_next += 4; disas_arm_insn(dc, insn); @@ -10984,11 +10985,11 @@ static void thumb_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) } dc->pc_curr = dc->base.pc_next; - insn = arm_lduw_code(env, dc->base.pc_next, dc->sctlr_b); + insn = arm_lduw_code(env, dc->base.pc_next); is_16bit = thumb_insn_is_16bit(dc, dc->base.pc_next, insn); dc->base.pc_next += 2; if (!is_16bit) { - uint32_t insn2 = arm_lduw_code(env, dc->base.pc_next, dc->sctlr_b); + uint32_t insn2 = arm_lduw_code(env, dc->base.pc_next); insn = insn << 16 | insn2; dc->base.pc_next += 2;