diff options
Diffstat (limited to 'hw/misc')
-rw-r--r-- | hw/misc/meson.build | 2 | ||||
-rw-r--r-- | hw/misc/npcm7xx_mft.c | 540 | ||||
-rw-r--r-- | hw/misc/npcm7xx_pwm.c | 4 | ||||
-rw-r--r-- | hw/misc/trace-events | 8 | ||||
-rw-r--r-- | hw/misc/xlnx-versal-xramc.c | 253 |
5 files changed, 807 insertions, 0 deletions
diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 00356cf12e..7a2b0d031a 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -65,6 +65,7 @@ softmmu_ss.add(when: 'CONFIG_MAINSTONE', if_true: files('mst_fpga.c')) softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files( 'npcm7xx_clk.c', 'npcm7xx_gcr.c', + 'npcm7xx_mft.c', 'npcm7xx_pwm.c', 'npcm7xx_rng.c', )) @@ -85,6 +86,7 @@ softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files( )) softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c')) softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c', 'zynq-xadc.c')) +softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-xramc.c')) softmmu_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c')) softmmu_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c')) softmmu_ss.add(when: 'CONFIG_STM32F4XX_EXTI', if_true: files('stm32f4xx_exti.c')) diff --git a/hw/misc/npcm7xx_mft.c b/hw/misc/npcm7xx_mft.c new file mode 100644 index 0000000000..a30583a1b0 --- /dev/null +++ b/hw/misc/npcm7xx_mft.c @@ -0,0 +1,540 @@ +/* + * Nuvoton NPCM7xx MFT Module + * + * Copyright 2021 Google LLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "hw/misc/npcm7xx_mft.h" +#include "hw/misc/npcm7xx_pwm.h" +#include "hw/registerfields.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "qemu/units.h" +#include "trace.h" + +/* + * Some of the registers can only accessed via 16-bit ops and some can only + * be accessed via 8-bit ops. However we mark all of them using REG16 to + * simplify implementation. npcm7xx_mft_check_mem_op checks the access length + * of memory operations. + */ +REG16(NPCM7XX_MFT_CNT1, 0x00); +REG16(NPCM7XX_MFT_CRA, 0x02); +REG16(NPCM7XX_MFT_CRB, 0x04); +REG16(NPCM7XX_MFT_CNT2, 0x06); +REG16(NPCM7XX_MFT_PRSC, 0x08); +REG16(NPCM7XX_MFT_CKC, 0x0a); +REG16(NPCM7XX_MFT_MCTRL, 0x0c); +REG16(NPCM7XX_MFT_ICTRL, 0x0e); +REG16(NPCM7XX_MFT_ICLR, 0x10); +REG16(NPCM7XX_MFT_IEN, 0x12); +REG16(NPCM7XX_MFT_CPA, 0x14); +REG16(NPCM7XX_MFT_CPB, 0x16); +REG16(NPCM7XX_MFT_CPCFG, 0x18); +REG16(NPCM7XX_MFT_INASEL, 0x1a); +REG16(NPCM7XX_MFT_INBSEL, 0x1c); + +/* Register Fields */ +#define NPCM7XX_MFT_CKC_C2CSEL BIT(3) +#define NPCM7XX_MFT_CKC_C1CSEL BIT(0) + +#define NPCM7XX_MFT_MCTRL_TBEN BIT(6) +#define NPCM7XX_MFT_MCTRL_TAEN BIT(5) +#define NPCM7XX_MFT_MCTRL_TBEDG BIT(4) +#define NPCM7XX_MFT_MCTRL_TAEDG BIT(3) +#define NPCM7XX_MFT_MCTRL_MODE5 BIT(2) + +#define NPCM7XX_MFT_ICTRL_TFPND BIT(5) +#define NPCM7XX_MFT_ICTRL_TEPND BIT(4) +#define NPCM7XX_MFT_ICTRL_TDPND BIT(3) +#define NPCM7XX_MFT_ICTRL_TCPND BIT(2) +#define NPCM7XX_MFT_ICTRL_TBPND BIT(1) +#define NPCM7XX_MFT_ICTRL_TAPND BIT(0) + +#define NPCM7XX_MFT_ICLR_TFCLR BIT(5) +#define NPCM7XX_MFT_ICLR_TECLR BIT(4) +#define NPCM7XX_MFT_ICLR_TDCLR BIT(3) +#define NPCM7XX_MFT_ICLR_TCCLR BIT(2) +#define NPCM7XX_MFT_ICLR_TBCLR BIT(1) +#define NPCM7XX_MFT_ICLR_TACLR BIT(0) + +#define NPCM7XX_MFT_IEN_TFIEN BIT(5) +#define NPCM7XX_MFT_IEN_TEIEN BIT(4) +#define NPCM7XX_MFT_IEN_TDIEN BIT(3) +#define NPCM7XX_MFT_IEN_TCIEN BIT(2) +#define NPCM7XX_MFT_IEN_TBIEN BIT(1) +#define NPCM7XX_MFT_IEN_TAIEN BIT(0) + +#define NPCM7XX_MFT_CPCFG_GET_B(rv) extract8((rv), 4, 4) +#define NPCM7XX_MFT_CPCFG_GET_A(rv) extract8((rv), 0, 4) +#define NPCM7XX_MFT_CPCFG_HIEN BIT(3) +#define NPCM7XX_MFT_CPCFG_EQEN BIT(2) +#define NPCM7XX_MFT_CPCFG_LOEN BIT(1) +#define NPCM7XX_MFT_CPCFG_CPSEL BIT(0) + +#define NPCM7XX_MFT_INASEL_SELA BIT(0) +#define NPCM7XX_MFT_INBSEL_SELB BIT(0) + +/* Max CNT values of the module. The CNT value is a countdown from it. */ +#define NPCM7XX_MFT_MAX_CNT 0xFFFF + +/* Each fan revolution should generated 2 pulses */ +#define NPCM7XX_MFT_PULSE_PER_REVOLUTION 2 + +typedef enum NPCM7xxMFTCaptureState { + /* capture succeeded with a valid CNT value. */ + NPCM7XX_CAPTURE_SUCCEED, + /* capture stopped prematurely due to reaching CPCFG condition. */ + NPCM7XX_CAPTURE_COMPARE_HIT, + /* capture fails since it reaches underflow condition for CNT. */ + NPCM7XX_CAPTURE_UNDERFLOW, +} NPCM7xxMFTCaptureState; + +static void npcm7xx_mft_reset(NPCM7xxMFTState *s) +{ + int i; + + /* Only registers PRSC ~ INBSEL need to be reset. */ + for (i = R_NPCM7XX_MFT_PRSC; i <= R_NPCM7XX_MFT_INBSEL; ++i) { + s->regs[i] = 0; + } +} + +static void npcm7xx_mft_clear_interrupt(NPCM7xxMFTState *s, uint8_t iclr) +{ + /* + * Clear bits in ICTRL where corresponding bits in iclr is 1. + * Both iclr and ictrl are 8-bit regs. (See npcm7xx_mft_check_mem_op) + */ + s->regs[R_NPCM7XX_MFT_ICTRL] &= ~iclr; +} + +/* + * If the CPCFG's condition should be triggered during count down from + * NPCM7XX_MFT_MAX_CNT to src if compared to tgt, return the count when + * the condition is triggered. + * Otherwise return -1. + * Since tgt is uint16_t it must always <= NPCM7XX_MFT_MAX_CNT. + */ +static int npcm7xx_mft_compare(int32_t src, uint16_t tgt, uint8_t cpcfg) +{ + if (cpcfg & NPCM7XX_MFT_CPCFG_HIEN) { + return NPCM7XX_MFT_MAX_CNT; + } + if ((cpcfg & NPCM7XX_MFT_CPCFG_EQEN) && (src <= tgt)) { + return tgt; + } + if ((cpcfg & NPCM7XX_MFT_CPCFG_LOEN) && (tgt > 0) && (src < tgt)) { + return tgt - 1; + } + + return -1; +} + +/* Compute CNT according to corresponding fan's RPM. */ +static NPCM7xxMFTCaptureState npcm7xx_mft_compute_cnt( + Clock *clock, uint32_t max_rpm, uint32_t duty, uint16_t tgt, + uint8_t cpcfg, uint16_t *cnt) +{ + uint32_t rpm = (uint64_t)max_rpm * (uint64_t)duty / NPCM7XX_PWM_MAX_DUTY; + int32_t count; + int stopped; + NPCM7xxMFTCaptureState state; + + if (rpm == 0) { + /* + * If RPM = 0, capture won't happen. CNT will continue count down. + * So it's effective equivalent to have a cnt > NPCM7XX_MFT_MAX_CNT + */ + count = NPCM7XX_MFT_MAX_CNT + 1; + } else { + /* + * RPM = revolution/min. The time for one revlution (in ns) is + * MINUTE_TO_NANOSECOND / RPM. + */ + count = clock_ns_to_ticks(clock, (60 * NANOSECONDS_PER_SECOND) / + (rpm * NPCM7XX_MFT_PULSE_PER_REVOLUTION)); + } + + if (count > NPCM7XX_MFT_MAX_CNT) { + count = -1; + } else { + /* The CNT is a countdown value from NPCM7XX_MFT_MAX_CNT. */ + count = NPCM7XX_MFT_MAX_CNT - count; + } + stopped = npcm7xx_mft_compare(count, tgt, cpcfg); + if (stopped == -1) { + if (count == -1) { + /* Underflow */ + state = NPCM7XX_CAPTURE_UNDERFLOW; + } else { + state = NPCM7XX_CAPTURE_SUCCEED; + } + } else { + count = stopped; + state = NPCM7XX_CAPTURE_COMPARE_HIT; + } + + if (count != -1) { + *cnt = count; + } + trace_npcm7xx_mft_rpm(clock->canonical_path, clock_get_hz(clock), + state, count, rpm, duty); + return state; +} + +/* + * Capture Fan RPM and update CNT and CR registers accordingly. + * Raise IRQ if certain contidions are met in IEN. + */ +static void npcm7xx_mft_capture(NPCM7xxMFTState *s) +{ + int irq_level = 0; + NPCM7xxMFTCaptureState state; + int sel; + uint8_t cpcfg; + + /* + * If not mode 5, the behavior is undefined. We just do nothing in this + * case. + */ + if (!(s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_MODE5)) { + return; + } + + /* Capture input A. */ + if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TAEN && + s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) { + sel = s->regs[R_NPCM7XX_MFT_INASEL] & NPCM7XX_MFT_INASEL_SELA; + cpcfg = NPCM7XX_MFT_CPCFG_GET_A(s->regs[R_NPCM7XX_MFT_CPCFG]); + state = npcm7xx_mft_compute_cnt(s->clock_1, + sel ? s->max_rpm[2] : s->max_rpm[0], + sel ? s->duty[2] : s->duty[0], + s->regs[R_NPCM7XX_MFT_CPA], + cpcfg, + &s->regs[R_NPCM7XX_MFT_CNT1]); + switch (state) { + case NPCM7XX_CAPTURE_SUCCEED: + /* Interrupt on input capture on TAn transition - TAPND */ + s->regs[R_NPCM7XX_MFT_CRA] = s->regs[R_NPCM7XX_MFT_CNT1]; + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TAPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TAIEN) { + irq_level = 1; + } + break; + + case NPCM7XX_CAPTURE_COMPARE_HIT: + /* Compare Hit - TEPND */ + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TEPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TEIEN) { + irq_level = 1; + } + break; + + case NPCM7XX_CAPTURE_UNDERFLOW: + /* Underflow - TCPND */ + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TCPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TCIEN) { + irq_level = 1; + } + break; + + default: + g_assert_not_reached(); + } + } + + /* Capture input B. */ + if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TBEN && + s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) { + sel = s->regs[R_NPCM7XX_MFT_INBSEL] & NPCM7XX_MFT_INBSEL_SELB; + cpcfg = NPCM7XX_MFT_CPCFG_GET_B(s->regs[R_NPCM7XX_MFT_CPCFG]); + state = npcm7xx_mft_compute_cnt(s->clock_2, + sel ? s->max_rpm[3] : s->max_rpm[1], + sel ? s->duty[3] : s->duty[1], + s->regs[R_NPCM7XX_MFT_CPB], + cpcfg, + &s->regs[R_NPCM7XX_MFT_CNT2]); + switch (state) { + case NPCM7XX_CAPTURE_SUCCEED: + /* Interrupt on input capture on TBn transition - TBPND */ + s->regs[R_NPCM7XX_MFT_CRB] = s->regs[R_NPCM7XX_MFT_CNT2]; + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TBPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TBIEN) { + irq_level = 1; + } + break; + + case NPCM7XX_CAPTURE_COMPARE_HIT: + /* Compare Hit - TFPND */ + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TFPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TFIEN) { + irq_level = 1; + } + break; + + case NPCM7XX_CAPTURE_UNDERFLOW: + /* Underflow - TDPND */ + s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TDPND; + if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TDIEN) { + irq_level = 1; + } + break; + + default: + g_assert_not_reached(); + } + } + + trace_npcm7xx_mft_capture(DEVICE(s)->canonical_path, irq_level); + qemu_set_irq(s->irq, irq_level); +} + +/* Update clock for counters. */ +static void npcm7xx_mft_update_clock(void *opaque, ClockEvent event) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); + uint64_t prescaled_clock_period; + + prescaled_clock_period = clock_get(s->clock_in) * + (s->regs[R_NPCM7XX_MFT_PRSC] + 1ULL); + trace_npcm7xx_mft_update_clock(s->clock_in->canonical_path, + s->regs[R_NPCM7XX_MFT_CKC], + clock_get(s->clock_in), + prescaled_clock_period); + /* Update clock 1 */ + if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) { + /* Clock is prescaled. */ + clock_update(s->clock_1, prescaled_clock_period); + } else { + /* Clock stopped. */ + clock_update(s->clock_1, 0); + } + /* Update clock 2 */ + if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) { + /* Clock is prescaled. */ + clock_update(s->clock_2, prescaled_clock_period); + } else { + /* Clock stopped. */ + clock_update(s->clock_2, 0); + } + + npcm7xx_mft_capture(s); +} + +static uint64_t npcm7xx_mft_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); + uint16_t value = 0; + + switch (offset) { + case A_NPCM7XX_MFT_ICLR: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n", + __func__, offset); + break; + + default: + value = s->regs[offset / 2]; + } + + trace_npcm7xx_mft_read(DEVICE(s)->canonical_path, offset, value); + return value; +} + +static void npcm7xx_mft_write(void *opaque, hwaddr offset, + uint64_t v, unsigned size) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); + + trace_npcm7xx_mft_write(DEVICE(s)->canonical_path, offset, v); + switch (offset) { + case A_NPCM7XX_MFT_ICLR: + npcm7xx_mft_clear_interrupt(s, v); + break; + + case A_NPCM7XX_MFT_CKC: + case A_NPCM7XX_MFT_PRSC: + s->regs[offset / 2] = v; + npcm7xx_mft_update_clock(s, ClockUpdate); + break; + + default: + s->regs[offset / 2] = v; + npcm7xx_mft_capture(s); + break; + } +} + +static bool npcm7xx_mft_check_mem_op(void *opaque, hwaddr offset, + unsigned size, bool is_write, + MemTxAttrs attrs) +{ + switch (offset) { + /* 16-bit registers. Must be accessed with 16-bit read/write.*/ + case A_NPCM7XX_MFT_CNT1: + case A_NPCM7XX_MFT_CRA: + case A_NPCM7XX_MFT_CRB: + case A_NPCM7XX_MFT_CNT2: + case A_NPCM7XX_MFT_CPA: + case A_NPCM7XX_MFT_CPB: + return size == 2; + + /* 8-bit registers. Must be accessed with 8-bit read/write.*/ + case A_NPCM7XX_MFT_PRSC: + case A_NPCM7XX_MFT_CKC: + case A_NPCM7XX_MFT_MCTRL: + case A_NPCM7XX_MFT_ICTRL: + case A_NPCM7XX_MFT_ICLR: + case A_NPCM7XX_MFT_IEN: + case A_NPCM7XX_MFT_CPCFG: + case A_NPCM7XX_MFT_INASEL: + case A_NPCM7XX_MFT_INBSEL: + return size == 1; + + default: + /* Invalid registers. */ + return false; + } +} + +static void npcm7xx_mft_get_max_rpm(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + visit_type_uint32(v, name, (uint32_t *)opaque, errp); +} + +static void npcm7xx_mft_set_max_rpm(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(obj); + uint32_t *max_rpm = opaque; + uint32_t value; + + if (!visit_type_uint32(v, name, &value, errp)) { + return; + } + + *max_rpm = value; + npcm7xx_mft_capture(s); +} + +static void npcm7xx_mft_duty_handler(void *opaque, int n, int value) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); + + trace_npcm7xx_mft_set_duty(DEVICE(s)->canonical_path, n, value); + s->duty[n] = value; + npcm7xx_mft_capture(s); +} + +static const struct MemoryRegionOps npcm7xx_mft_ops = { + .read = npcm7xx_mft_read, + .write = npcm7xx_mft_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 2, + .unaligned = false, + .accepts = npcm7xx_mft_check_mem_op, + }, +}; + +static void npcm7xx_mft_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(obj); + + npcm7xx_mft_reset(s); +} + +static void npcm7xx_mft_hold_reset(Object *obj) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(obj); + + qemu_irq_lower(s->irq); +} + +static void npcm7xx_mft_init(Object *obj) +{ + NPCM7xxMFTState *s = NPCM7XX_MFT(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + DeviceState *dev = DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &npcm7xx_mft_ops, s, + TYPE_NPCM7XX_MFT, 4 * KiB); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + s->clock_in = qdev_init_clock_in(dev, "clock-in", npcm7xx_mft_update_clock, + s, ClockUpdate); + s->clock_1 = qdev_init_clock_out(dev, "clock1"); + s->clock_2 = qdev_init_clock_out(dev, "clock2"); + + for (int i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { + object_property_add(obj, "max_rpm[*]", "uint32", + npcm7xx_mft_get_max_rpm, + npcm7xx_mft_set_max_rpm, + NULL, &s->max_rpm[i]); + } + qdev_init_gpio_in_named(dev, npcm7xx_mft_duty_handler, "duty", + NPCM7XX_MFT_FANIN_COUNT); +} + +static const VMStateDescription vmstate_npcm7xx_mft = { + .name = "npcm7xx-mft-module", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock_in, NPCM7xxMFTState), + VMSTATE_CLOCK(clock_1, NPCM7xxMFTState), + VMSTATE_CLOCK(clock_2, NPCM7xxMFTState), + VMSTATE_UINT16_ARRAY(regs, NPCM7xxMFTState, NPCM7XX_MFT_NR_REGS), + VMSTATE_UINT32_ARRAY(max_rpm, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT), + VMSTATE_UINT32_ARRAY(duty, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm7xx_mft_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx MFT Controller"; + dc->vmsd = &vmstate_npcm7xx_mft; + rc->phases.enter = npcm7xx_mft_enter_reset; + rc->phases.hold = npcm7xx_mft_hold_reset; +} + +static const TypeInfo npcm7xx_mft_info = { + .name = TYPE_NPCM7XX_MFT, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxMFTState), + .class_init = npcm7xx_mft_class_init, + .instance_init = npcm7xx_mft_init, +}; + +static void npcm7xx_mft_register_type(void) +{ + type_register_static(&npcm7xx_mft_info); +} +type_init(npcm7xx_mft_register_type); diff --git a/hw/misc/npcm7xx_pwm.c b/hw/misc/npcm7xx_pwm.c index ce192bb274..2be5bd25c6 100644 --- a/hw/misc/npcm7xx_pwm.c +++ b/hw/misc/npcm7xx_pwm.c @@ -139,6 +139,7 @@ static void npcm7xx_pwm_update_duty(NPCM7xxPWM *p) trace_npcm7xx_pwm_update_duty(DEVICE(p->module)->canonical_path, p->index, p->duty, duty); p->duty = duty; + qemu_set_irq(p->module->duty_gpio_out[p->index], p->duty); } } @@ -483,6 +484,7 @@ static void npcm7xx_pwm_init(Object *obj) SysBusDevice *sbd = SYS_BUS_DEVICE(obj); int i; + QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->pwm) != NPCM7XX_PWM_PER_MODULE); for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) { NPCM7xxPWM *p = &s->pwm[i]; p->module = s; @@ -501,6 +503,8 @@ static void npcm7xx_pwm_init(Object *obj) object_property_add_uint32_ptr(obj, "duty[*]", &s->pwm[i].duty, OBJ_PROP_FLAG_READ); } + qdev_init_gpio_out_named(DEVICE(s), s->duty_gpio_out, + "duty-gpio-out", NPCM7XX_PWM_PER_MODULE); } static const VMStateDescription vmstate_npcm7xx_pwm = { diff --git a/hw/misc/trace-events b/hw/misc/trace-events index cae005549e..b87d0b4c90 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -116,6 +116,14 @@ npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " valu npcm7xx_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +# npcm7xx_mft.c +npcm7xx_mft_read(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 +npcm7xx_mft_write(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16 +npcm7xx_mft_rpm(const char *clock, uint32_t clock_hz, int state, int32_t cnt, uint32_t rpm, uint32_t duty) " fan clk: %s clock_hz: %" PRIu32 ", state: %d, cnt: %" PRIi32 ", rpm: %" PRIu32 ", duty: %" PRIu32 +npcm7xx_mft_capture(const char *name, int irq_level) "%s: level: %d" +npcm7xx_mft_update_clock(const char *name, uint16_t sel, uint64_t clock_period, uint64_t prescaled_clock_period) "%s: sel: 0x%02" PRIx16 ", period: %" PRIu64 ", prescaled: %" PRIu64 +npcm7xx_mft_set_duty(const char *name, int n, int value) "%s[%d]: %d" + # npcm7xx_rng.c npcm7xx_rng_read(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" npcm7xx_rng_write(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" diff --git a/hw/misc/xlnx-versal-xramc.c b/hw/misc/xlnx-versal-xramc.c new file mode 100644 index 0000000000..e5b719a0ed --- /dev/null +++ b/hw/misc/xlnx-versal-xramc.c @@ -0,0 +1,253 @@ +/* + * QEMU model of the Xilinx XRAM Controller. + * + * Copyright (c) 2021 Xilinx Inc. + * SPDX-License-Identifier: GPL-2.0-or-later + * Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com> + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/sysbus.h" +#include "hw/register.h" +#include "hw/qdev-properties.h" +#include "hw/irq.h" +#include "hw/misc/xlnx-versal-xramc.h" + +#ifndef XLNX_XRAM_CTRL_ERR_DEBUG +#define XLNX_XRAM_CTRL_ERR_DEBUG 0 +#endif + +static void xram_update_irq(XlnxXramCtrl *s) +{ + bool pending = s->regs[R_XRAM_ISR] & ~s->regs[R_XRAM_IMR]; + qemu_set_irq(s->irq, pending); +} + +static void xram_isr_postw(RegisterInfo *reg, uint64_t val64) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque); + xram_update_irq(s); +} + +static uint64_t xram_ien_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque); + uint32_t val = val64; + + s->regs[R_XRAM_IMR] &= ~val; + xram_update_irq(s); + return 0; +} + +static uint64_t xram_ids_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque); + uint32_t val = val64; + + s->regs[R_XRAM_IMR] |= val; + xram_update_irq(s); + return 0; +} + +static const RegisterAccessInfo xram_ctrl_regs_info[] = { + { .name = "XRAM_ERR_CTRL", .addr = A_XRAM_ERR_CTRL, + .reset = 0xf, + .rsvd = 0xfffffff0, + },{ .name = "XRAM_ISR", .addr = A_XRAM_ISR, + .rsvd = 0xfffff800, + .w1c = 0x7ff, + .post_write = xram_isr_postw, + },{ .name = "XRAM_IMR", .addr = A_XRAM_IMR, + .reset = 0x7ff, + .rsvd = 0xfffff800, + .ro = 0x7ff, + },{ .name = "XRAM_IEN", .addr = A_XRAM_IEN, + .rsvd = 0xfffff800, + .pre_write = xram_ien_prew, + },{ .name = "XRAM_IDS", .addr = A_XRAM_IDS, + .rsvd = 0xfffff800, + .pre_write = xram_ids_prew, + },{ .name = "XRAM_ECC_CNTL", .addr = A_XRAM_ECC_CNTL, + .rsvd = 0xfffffff8, + },{ .name = "XRAM_CLR_EXE", .addr = A_XRAM_CLR_EXE, + .rsvd = 0xffffff00, + },{ .name = "XRAM_CE_FFA", .addr = A_XRAM_CE_FFA, + .rsvd = 0xfff00000, + .ro = 0xfffff, + },{ .name = "XRAM_CE_FFD0", .addr = A_XRAM_CE_FFD0, + .ro = 0xffffffff, + },{ .name = "XRAM_CE_FFD1", .addr = A_XRAM_CE_FFD1, + .ro = 0xffffffff, + },{ .name = "XRAM_CE_FFD2", .addr = A_XRAM_CE_FFD2, + .ro = 0xffffffff, + },{ .name = "XRAM_CE_FFD3", .addr = A_XRAM_CE_FFD3, + .ro = 0xffffffff, + },{ .name = "XRAM_CE_FFE", .addr = A_XRAM_CE_FFE, + .rsvd = 0xffff0000, + .ro = 0xffff, + },{ .name = "XRAM_UE_FFA", .addr = A_XRAM_UE_FFA, + .rsvd = 0xfff00000, + .ro = 0xfffff, + },{ .name = "XRAM_UE_FFD0", .addr = A_XRAM_UE_FFD0, + .ro = 0xffffffff, + },{ .name = "XRAM_UE_FFD1", .addr = A_XRAM_UE_FFD1, + .ro = 0xffffffff, + },{ .name = "XRAM_UE_FFD2", .addr = A_XRAM_UE_FFD2, + .ro = 0xffffffff, + },{ .name = "XRAM_UE_FFD3", .addr = A_XRAM_UE_FFD3, + .ro = 0xffffffff, + },{ .name = "XRAM_UE_FFE", .addr = A_XRAM_UE_FFE, + .rsvd = 0xffff0000, + .ro = 0xffff, + },{ .name = "XRAM_FI_D0", .addr = A_XRAM_FI_D0, + },{ .name = "XRAM_FI_D1", .addr = A_XRAM_FI_D1, + },{ .name = "XRAM_FI_D2", .addr = A_XRAM_FI_D2, + },{ .name = "XRAM_FI_D3", .addr = A_XRAM_FI_D3, + },{ .name = "XRAM_FI_SY", .addr = A_XRAM_FI_SY, + .rsvd = 0xffff0000, + },{ .name = "XRAM_RMW_UE_FFA", .addr = A_XRAM_RMW_UE_FFA, + .rsvd = 0xfff00000, + .ro = 0xfffff, + },{ .name = "XRAM_FI_CNTR", .addr = A_XRAM_FI_CNTR, + .rsvd = 0xff000000, + },{ .name = "XRAM_IMP", .addr = A_XRAM_IMP, + .reset = 0x4, + .rsvd = 0xfffffff0, + .ro = 0xf, + },{ .name = "XRAM_PRDY_DBG", .addr = A_XRAM_PRDY_DBG, + .reset = 0xffff, + .rsvd = 0xffff0000, + .ro = 0xffff, + },{ .name = "XRAM_SAFETY_CHK", .addr = A_XRAM_SAFETY_CHK, + } +}; + +static void xram_ctrl_reset_enter(Object *obj, ResetType type) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { + register_reset(&s->regs_info[i]); + } + + ARRAY_FIELD_DP32(s->regs, XRAM_IMP, SIZE, s->cfg.encoded_size); +} + +static void xram_ctrl_reset_hold(Object *obj) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj); + + xram_update_irq(s); +} + +static const MemoryRegionOps xram_ctrl_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void xram_ctrl_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + XlnxXramCtrl *s = XLNX_XRAM_CTRL(dev); + + switch (s->cfg.size) { + case 64 * KiB: + s->cfg.encoded_size = 0; + break; + case 128 * KiB: + s->cfg.encoded_size = 1; + break; + case 256 * KiB: + s->cfg.encoded_size = 2; + break; + case 512 * KiB: + s->cfg.encoded_size = 3; + break; + case 1 * MiB: + s->cfg.encoded_size = 4; + break; + default: + error_setg(errp, "Unsupported XRAM size %" PRId64, s->cfg.size); + return; + } + + memory_region_init_ram(&s->ram, OBJECT(s), + object_get_canonical_path_component(OBJECT(s)), + s->cfg.size, &error_fatal); + sysbus_init_mmio(sbd, &s->ram); +} + +static void xram_ctrl_init(Object *obj) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = + register_init_block32(DEVICE(obj), xram_ctrl_regs_info, + ARRAY_SIZE(xram_ctrl_regs_info), + s->regs_info, s->regs, + &xram_ctrl_ops, + XLNX_XRAM_CTRL_ERR_DEBUG, + XRAM_CTRL_R_MAX * 4); + sysbus_init_mmio(sbd, &s->reg_array->mem); + sysbus_init_irq(sbd, &s->irq); +} + +static void xram_ctrl_finalize(Object *obj) +{ + XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj); + register_finalize_block(s->reg_array); +} + +static const VMStateDescription vmstate_xram_ctrl = { + .name = TYPE_XLNX_XRAM_CTRL, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, XlnxXramCtrl, XRAM_CTRL_R_MAX), + VMSTATE_END_OF_LIST(), + } +}; + +static Property xram_ctrl_properties[] = { + DEFINE_PROP_UINT64("size", XlnxXramCtrl, cfg.size, 1 * MiB), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xram_ctrl_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = xram_ctrl_realize; + dc->vmsd = &vmstate_xram_ctrl; + device_class_set_props(dc, xram_ctrl_properties); + + rc->phases.enter = xram_ctrl_reset_enter; + rc->phases.hold = xram_ctrl_reset_hold; +} + +static const TypeInfo xram_ctrl_info = { + .name = TYPE_XLNX_XRAM_CTRL, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxXramCtrl), + .class_init = xram_ctrl_class_init, + .instance_init = xram_ctrl_init, + .instance_finalize = xram_ctrl_finalize, +}; + +static void xram_ctrl_register_types(void) +{ + type_register_static(&xram_ctrl_info); +} + +type_init(xram_ctrl_register_types) |