diff options
33 files changed, 3049 insertions, 62 deletions
diff --git a/docs/meson.build b/docs/meson.build index 71641b4..fae9849 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -62,6 +62,7 @@ if build_docs 'qemu-img.1': (have_tools ? 'man1' : ''), 'qemu-nbd.8': (have_tools ? 'man8' : ''), 'qemu-pr-helper.8': (have_tools ? 'man8' : ''), + 'qemu-storage-daemon.1': (have_tools ? 'man1' : ''), 'qemu-trace-stap.1': (config_host.has_key('CONFIG_TRACE_SYSTEMTAP') ? 'man1' : ''), 'virtfs-proxy-helper.1': (have_virtfs_proxy_helper ? 'man1' : ''), 'virtiofsd.1': (have_virtiofsd ? 'man1' : ''), diff --git a/docs/system/arm/nuvoton.rst b/docs/system/arm/nuvoton.rst index b00d405..a1786342 100644 --- a/docs/system/arm/nuvoton.rst +++ b/docs/system/arm/nuvoton.rst @@ -41,6 +41,8 @@ Supported devices * Random Number Generator (RNG) * USB host (USBH) * GPIO controller + * Analog to Digital Converter (ADC) + * Pulse Width Modulation (PWM) Missing devices --------------- @@ -58,10 +60,8 @@ Missing devices * USB device (USBD) * SMBus controller (SMBF) * Peripheral SPI controller (PSPI) - * Analog to Digital Converter (ADC) * SD/MMC host * PECI interface - * Pulse Width Modulation (PWM) * Tachometer * PCI and PCIe root complex and bridges * VDM and MCTP support diff --git a/hw/adc/meson.build b/hw/adc/meson.build index 0d62ae9..6ddee23 100644 --- a/hw/adc/meson.build +++ b/hw/adc/meson.build @@ -1 +1,2 @@ softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c')) +softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c')) diff --git a/hw/adc/npcm7xx_adc.c b/hw/adc/npcm7xx_adc.c new file mode 100644 index 0000000..870a6d5 --- /dev/null +++ b/hw/adc/npcm7xx_adc.c @@ -0,0 +1,301 @@ +/* + * Nuvoton NPCM7xx ADC Module + * + * Copyright 2020 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/adc/npcm7xx_adc.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "hw/registerfields.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "qemu/units.h" +#include "trace.h" + +REG32(NPCM7XX_ADC_CON, 0x0) +REG32(NPCM7XX_ADC_DATA, 0x4) + +/* Register field definitions. */ +#define NPCM7XX_ADC_CON_MUX(rv) extract32(rv, 24, 4) +#define NPCM7XX_ADC_CON_INT_EN BIT(21) +#define NPCM7XX_ADC_CON_REFSEL BIT(19) +#define NPCM7XX_ADC_CON_INT BIT(18) +#define NPCM7XX_ADC_CON_EN BIT(17) +#define NPCM7XX_ADC_CON_RST BIT(16) +#define NPCM7XX_ADC_CON_CONV BIT(14) +#define NPCM7XX_ADC_CON_DIV(rv) extract32(rv, 1, 8) + +#define NPCM7XX_ADC_MAX_RESULT 1023 +#define NPCM7XX_ADC_DEFAULT_IREF 2000000 +#define NPCM7XX_ADC_CONV_CYCLES 20 +#define NPCM7XX_ADC_RESET_CYCLES 10 +#define NPCM7XX_ADC_R0_INPUT 500000 +#define NPCM7XX_ADC_R1_INPUT 1500000 + +static void npcm7xx_adc_reset(NPCM7xxADCState *s) +{ + timer_del(&s->conv_timer); + s->con = 0x000c0001; + s->data = 0x00000000; +} + +static uint32_t npcm7xx_adc_convert(uint32_t input, uint32_t ref) +{ + uint32_t result; + + result = input * (NPCM7XX_ADC_MAX_RESULT + 1) / ref; + if (result > NPCM7XX_ADC_MAX_RESULT) { + result = NPCM7XX_ADC_MAX_RESULT; + } + + return result; +} + +static uint32_t npcm7xx_adc_prescaler(NPCM7xxADCState *s) +{ + return 2 * (NPCM7XX_ADC_CON_DIV(s->con) + 1); +} + +static void npcm7xx_adc_start_timer(Clock *clk, QEMUTimer *timer, + uint32_t cycles, uint32_t prescaler) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t ticks = cycles; + int64_t ns; + + ticks *= prescaler; + ns = clock_ticks_to_ns(clk, ticks); + ns += now; + timer_mod(timer, ns); +} + +static void npcm7xx_adc_start_convert(NPCM7xxADCState *s) +{ + uint32_t prescaler = npcm7xx_adc_prescaler(s); + + npcm7xx_adc_start_timer(s->clock, &s->conv_timer, NPCM7XX_ADC_CONV_CYCLES, + prescaler); +} + +static void npcm7xx_adc_convert_done(void *opaque) +{ + NPCM7xxADCState *s = opaque; + uint32_t input = NPCM7XX_ADC_CON_MUX(s->con); + uint32_t ref = (s->con & NPCM7XX_ADC_CON_REFSEL) + ? s->iref : s->vref; + + if (input >= NPCM7XX_ADC_NUM_INPUTS) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid input: %u\n", + __func__, input); + return; + } + s->data = npcm7xx_adc_convert(s->adci[input], ref); + if (s->con & NPCM7XX_ADC_CON_INT_EN) { + s->con |= NPCM7XX_ADC_CON_INT; + qemu_irq_raise(s->irq); + } + s->con &= ~NPCM7XX_ADC_CON_CONV; +} + +static void npcm7xx_adc_calibrate(NPCM7xxADCState *adc) +{ + adc->calibration_r_values[0] = npcm7xx_adc_convert(NPCM7XX_ADC_R0_INPUT, + adc->iref); + adc->calibration_r_values[1] = npcm7xx_adc_convert(NPCM7XX_ADC_R1_INPUT, + adc->iref); +} + +static void npcm7xx_adc_write_con(NPCM7xxADCState *s, uint32_t new_con) +{ + uint32_t old_con = s->con; + + /* Write ADC_INT to 1 to clear it */ + if (new_con & NPCM7XX_ADC_CON_INT) { + new_con &= ~NPCM7XX_ADC_CON_INT; + qemu_irq_lower(s->irq); + } else if (old_con & NPCM7XX_ADC_CON_INT) { + new_con |= NPCM7XX_ADC_CON_INT; + } + + s->con = new_con; + + if (s->con & NPCM7XX_ADC_CON_RST) { + npcm7xx_adc_reset(s); + return; + } + + if ((s->con & NPCM7XX_ADC_CON_EN)) { + if (s->con & NPCM7XX_ADC_CON_CONV) { + if (!(old_con & NPCM7XX_ADC_CON_CONV)) { + npcm7xx_adc_start_convert(s); + } + } else { + timer_del(&s->conv_timer); + } + } +} + +static uint64_t npcm7xx_adc_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t value = 0; + NPCM7xxADCState *s = opaque; + + switch (offset) { + case A_NPCM7XX_ADC_CON: + value = s->con; + break; + + case A_NPCM7XX_ADC_DATA: + value = s->data; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + break; + } + + trace_npcm7xx_adc_read(DEVICE(s)->canonical_path, offset, value); + return value; +} + +static void npcm7xx_adc_write(void *opaque, hwaddr offset, uint64_t v, + unsigned size) +{ + NPCM7xxADCState *s = opaque; + + trace_npcm7xx_adc_write(DEVICE(s)->canonical_path, offset, v); + switch (offset) { + case A_NPCM7XX_ADC_CON: + npcm7xx_adc_write_con(s, v); + break; + + case A_NPCM7XX_ADC_DATA: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", + __func__, offset); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + break; + } + +} + +static const struct MemoryRegionOps npcm7xx_adc_ops = { + .read = npcm7xx_adc_read, + .write = npcm7xx_adc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void npcm7xx_adc_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxADCState *s = NPCM7XX_ADC(obj); + + npcm7xx_adc_reset(s); +} + +static void npcm7xx_adc_hold_reset(Object *obj) +{ + NPCM7xxADCState *s = NPCM7XX_ADC(obj); + + qemu_irq_lower(s->irq); +} + +static void npcm7xx_adc_init(Object *obj) +{ + NPCM7xxADCState *s = NPCM7XX_ADC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + int i; + + sysbus_init_irq(sbd, &s->irq); + + timer_init_ns(&s->conv_timer, QEMU_CLOCK_VIRTUAL, + npcm7xx_adc_convert_done, s); + memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s, + TYPE_NPCM7XX_ADC, 4 * KiB); + sysbus_init_mmio(sbd, &s->iomem); + s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL); + + for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) { + object_property_add_uint32_ptr(obj, "adci[*]", + &s->adci[i], OBJ_PROP_FLAG_WRITE); + } + object_property_add_uint32_ptr(obj, "vref", + &s->vref, OBJ_PROP_FLAG_WRITE); + npcm7xx_adc_calibrate(s); +} + +static const VMStateDescription vmstate_npcm7xx_adc = { + .name = "npcm7xx-adc", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_TIMER(conv_timer, NPCM7xxADCState), + VMSTATE_UINT32(con, NPCM7xxADCState), + VMSTATE_UINT32(data, NPCM7xxADCState), + VMSTATE_CLOCK(clock, NPCM7xxADCState), + VMSTATE_UINT32_ARRAY(adci, NPCM7xxADCState, NPCM7XX_ADC_NUM_INPUTS), + VMSTATE_UINT32(vref, NPCM7xxADCState), + VMSTATE_UINT32(iref, NPCM7xxADCState), + VMSTATE_UINT16_ARRAY(calibration_r_values, NPCM7xxADCState, + NPCM7XX_ADC_NUM_CALIB), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property npcm7xx_timer_properties[] = { + DEFINE_PROP_UINT32("iref", NPCM7xxADCState, iref, NPCM7XX_ADC_DEFAULT_IREF), + DEFINE_PROP_END_OF_LIST(), +}; + +static void npcm7xx_adc_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx ADC Module"; + dc->vmsd = &vmstate_npcm7xx_adc; + rc->phases.enter = npcm7xx_adc_enter_reset; + rc->phases.hold = npcm7xx_adc_hold_reset; + + device_class_set_props(dc, npcm7xx_timer_properties); +} + +static const TypeInfo npcm7xx_adc_info = { + .name = TYPE_NPCM7XX_ADC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxADCState), + .class_init = npcm7xx_adc_class_init, + .instance_init = npcm7xx_adc_init, +}; + +static void npcm7xx_adc_register_types(void) +{ + type_register_static(&npcm7xx_adc_info); +} + +type_init(npcm7xx_adc_register_types); diff --git a/hw/adc/trace-events b/hw/adc/trace-events new file mode 100644 index 0000000..4c3279e --- /dev/null +++ b/hw/adc/trace-events @@ -0,0 +1,5 @@ +# See docs/devel/tracing.txt for syntax documentation. + +# npcm7xx_adc.c +npcm7xx_adc_read(const char *id, uint64_t offset, uint32_t value) " %s offset: 0x%04" PRIx64 " value 0x%04" PRIx32 +npcm7xx_adc_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value 0x%04" PRIx32 diff --git a/hw/adc/trace.h b/hw/adc/trace.h new file mode 100644 index 0000000..b71d5b5 --- /dev/null +++ b/hw/adc/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_adc.h" diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 47e2b6f..72040d4 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -22,6 +22,7 @@ #include "hw/char/serial.h" #include "hw/loader.h" #include "hw/misc/unimp.h" +#include "hw/qdev-clock.h" #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qemu/units.h" @@ -50,6 +51,9 @@ #define NPCM7XX_EHCI_BA (0xf0806000) #define NPCM7XX_OHCI_BA (0xf0807000) +/* ADC Module */ +#define NPCM7XX_ADC_BA (0xf000c000) + /* Internal AHB SRAM */ #define NPCM7XX_RAM3_BA (0xc0008000) #define NPCM7XX_RAM3_SZ (4 * KiB) @@ -60,6 +64,7 @@ #define NPCM7XX_ROM_BA (0xffff0000) #define NPCM7XX_ROM_SZ (64 * KiB) + /* Clock configuration values to be fixed up when bypassing bootloader */ /* Run PLL1 at 1600 MHz */ @@ -72,6 +77,7 @@ * interrupts. */ enum NPCM7xxInterrupt { + NPCM7XX_ADC_IRQ = 0, NPCM7XX_UART0_IRQ = 2, NPCM7XX_UART1_IRQ, NPCM7XX_UART2_IRQ, @@ -96,6 +102,8 @@ enum NPCM7xxInterrupt { NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */ NPCM7XX_EHCI_IRQ = 61, NPCM7XX_OHCI_IRQ = 62, + NPCM7XX_PWM0_IRQ = 93, /* PWM module 0 */ + NPCM7XX_PWM1_IRQ, /* PWM module 1 */ NPCM7XX_GPIO0_IRQ = 116, NPCM7XX_GPIO1_IRQ, NPCM7XX_GPIO2_IRQ, @@ -138,6 +146,12 @@ static const hwaddr npcm7xx_fiu3_flash_addr[] = { 0xb8000000, /* CS3 */ }; +/* Register base address for each PWM Module */ +static const hwaddr npcm7xx_pwm_addr[] = { + 0xf0103000, + 0xf0104000, +}; + static const struct { hwaddr regs_addr; uint32_t unconnected_pins; @@ -295,6 +309,14 @@ static void npcm7xx_init_fuses(NPCM7xxState *s) sizeof(value)); } +static void npcm7xx_write_adc_calibration(NPCM7xxState *s) +{ + /* Both ADC and the fuse array must have realized. */ + QEMU_BUILD_BUG_ON(sizeof(s->adc.calibration_r_values) != 4); + npcm7xx_otp_array_write(&s->fuse_array, s->adc.calibration_r_values, + NPCM7XX_FUSE_ADC_CALIB, sizeof(s->adc.calibration_r_values)); +} + static qemu_irq npcm7xx_irq(NPCM7xxState *s, int n) { return qdev_get_gpio_in(DEVICE(&s->a9mpcore), n); @@ -321,6 +343,7 @@ static void npcm7xx_init(Object *obj) TYPE_NPCM7XX_FUSE_ARRAY); object_initialize_child(obj, "mc", &s->mc, TYPE_NPCM7XX_MC); object_initialize_child(obj, "rng", &s->rng, TYPE_NPCM7XX_RNG); + object_initialize_child(obj, "adc", &s->adc, TYPE_NPCM7XX_ADC); for (i = 0; i < ARRAY_SIZE(s->tim); i++) { object_initialize_child(obj, "tim[*]", &s->tim[i], TYPE_NPCM7XX_TIMER); @@ -338,6 +361,10 @@ static void npcm7xx_init(Object *obj) object_initialize_child(obj, npcm7xx_fiu[i].name, &s->fiu[i], TYPE_NPCM7XX_FIU); } + + for (i = 0; i < ARRAY_SIZE(s->pwm); i++) { + object_initialize_child(obj, "pwm[*]", &s->pwm[i], TYPE_NPCM7XX_PWM); + } } static void npcm7xx_realize(DeviceState *dev, Error **errp) @@ -413,6 +440,15 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) sysbus_realize(SYS_BUS_DEVICE(&s->mc), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->mc), 0, NPCM7XX_MC_BA); + /* ADC Modules. Cannot fail. */ + qdev_connect_clock_in(DEVICE(&s->adc), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "adc-clock")); + sysbus_realize(SYS_BUS_DEVICE(&s->adc), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->adc), 0, NPCM7XX_ADC_BA); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->adc), 0, + npcm7xx_irq(s, NPCM7XX_ADC_IRQ)); + npcm7xx_write_adc_calibration(s); + /* Timer Modules (TIM). Cannot fail. */ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_tim_addr) != ARRAY_SIZE(s->tim)); for (i = 0; i < ARRAY_SIZE(s->tim); i++) { @@ -420,6 +456,10 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) int first_irq; int j; + /* Connect the timer clock. */ + qdev_connect_clock_in(DEVICE(&s->tim[i]), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "timer-clock")); + sysbus_realize(sbd, &error_abort); sysbus_mmio_map(sbd, 0, npcm7xx_tim_addr[i]); @@ -485,6 +525,18 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->ohci), 0, npcm7xx_irq(s, NPCM7XX_OHCI_IRQ)); + /* PWM Modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_pwm_addr) != ARRAY_SIZE(s->pwm)); + for (i = 0; i < ARRAY_SIZE(s->pwm); i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->pwm[i]); + + qdev_connect_clock_in(DEVICE(&s->pwm[i]), "clock", qdev_get_clock_out( + DEVICE(&s->clk), "apb3-clock")); + sysbus_realize(sbd, &error_abort); + sysbus_mmio_map(sbd, 0, npcm7xx_pwm_addr[i]); + sysbus_connect_irq(sbd, i, npcm7xx_irq(s, NPCM7XX_PWM0_IRQ + i)); + } + /* * Flash Interface Unit (FIU). Can fail if incorrect number of chip selects * specified, but this is a programming error. @@ -523,7 +575,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) create_unimplemented_device("npcm7xx.vdmx", 0xe0800000, 4 * KiB); create_unimplemented_device("npcm7xx.pcierc", 0xe1000000, 64 * KiB); create_unimplemented_device("npcm7xx.kcs", 0xf0007000, 4 * KiB); - create_unimplemented_device("npcm7xx.adc", 0xf000c000, 4 * KiB); create_unimplemented_device("npcm7xx.gfxi", 0xf000e000, 4 * KiB); create_unimplemented_device("npcm7xx.gpio[0]", 0xf0010000, 4 * KiB); create_unimplemented_device("npcm7xx.gpio[1]", 0xf0011000, 4 * KiB); @@ -553,8 +604,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) create_unimplemented_device("npcm7xx.peci", 0xf0100000, 4 * KiB); create_unimplemented_device("npcm7xx.siox[1]", 0xf0101000, 4 * KiB); create_unimplemented_device("npcm7xx.siox[2]", 0xf0102000, 4 * KiB); - create_unimplemented_device("npcm7xx.pwm[0]", 0xf0103000, 4 * KiB); - create_unimplemented_device("npcm7xx.pwm[1]", 0xf0104000, 4 * KiB); create_unimplemented_device("npcm7xx.mft[0]", 0xf0180000, 4 * KiB); create_unimplemented_device("npcm7xx.mft[1]", 0xf0181000, 4 * KiB); create_unimplemented_device("npcm7xx.mft[2]", 0xf0182000, 4 * KiB); diff --git a/hw/arm/npcm7xx_boards.c b/hw/arm/npcm7xx_boards.c index 306260f..3fdd5ca 100644 --- a/hw/arm/npcm7xx_boards.c +++ b/hw/arm/npcm7xx_boards.c @@ -82,7 +82,7 @@ static NPCM7xxState *npcm7xx_create_soc(MachineState *machine, uint32_t hw_straps) { NPCM7xxMachineClass *nmc = NPCM7XX_MACHINE_GET_CLASS(machine); - MachineClass *mc = &nmc->parent; + MachineClass *mc = MACHINE_CLASS(nmc); Object *obj; if (strcmp(machine->cpu_type, mc->default_cpu_type) != 0) { diff --git a/hw/mem/npcm7xx_mc.c b/hw/mem/npcm7xx_mc.c index 0435d06..abc5af5 100644 --- a/hw/mem/npcm7xx_mc.c +++ b/hw/mem/npcm7xx_mc.c @@ -62,7 +62,7 @@ static void npcm7xx_mc_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_mc_ops, s, "regs", NPCM7XX_MC_REGS_SIZE); - sysbus_init_mmio(&s->parent, &s->mmio); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio); } static void npcm7xx_mc_class_init(ObjectClass *klass, void *data) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index ce15ffc..607cd38 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -64,6 +64,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_pwm.c', 'npcm7xx_rng.c', )) softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files( diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c index 6732437..0bcae9c 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm7xx_clk.c @@ -18,6 +18,7 @@ #include "hw/misc/npcm7xx_clk.h" #include "hw/timer/npcm7xx_timer.h" +#include "hw/qdev-clock.h" #include "migration/vmstate.h" #include "qemu/error-report.h" #include "qemu/log.h" @@ -27,9 +28,22 @@ #include "trace.h" #include "sysemu/watchdog.h" +/* + * The reference clock hz, and the SECCNT and CNTR25M registers in this module, + * is always 25 MHz. + */ +#define NPCM7XX_CLOCK_REF_HZ (25000000) + +/* Register Field Definitions */ +#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ + #define PLLCON_LOKI BIT(31) #define PLLCON_LOKS BIT(30) #define PLLCON_PWDEN BIT(12) +#define PLLCON_FBDV(con) extract32((con), 16, 12) +#define PLLCON_OTDV2(con) extract32((con), 13, 3) +#define PLLCON_OTDV1(con) extract32((con), 8, 3) +#define PLLCON_INDV(con) extract32((con), 0, 6) enum NPCM7xxCLKRegisters { NPCM7XX_CLK_CLKEN1, @@ -89,12 +103,609 @@ static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = { [NPCM7XX_CLK_AHBCKFI] = 0x000000c8, }; -/* Register Field Definitions */ -#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex A9 Cores */ - /* The number of watchdogs that can trigger a reset. */ #define NPCM7XX_NR_WATCHDOGS (3) +/* Clock converter functions */ + +#define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll" +#define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \ + (obj), TYPE_NPCM7XX_CLOCK_PLL) +#define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel" +#define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \ + (obj), TYPE_NPCM7XX_CLOCK_SEL) +#define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider" +#define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, \ + (obj), TYPE_NPCM7XX_CLOCK_DIVIDER) + +static void npcm7xx_clk_update_pll(void *opaque) +{ + NPCM7xxClockPLLState *s = opaque; + uint32_t con = s->clk->regs[s->reg]; + uint64_t freq; + + /* The PLL is grounded if it is not locked yet. */ + if (con & PLLCON_LOKI) { + freq = clock_get_hz(s->clock_in); + freq *= PLLCON_FBDV(con); + freq /= PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con); + } else { + freq = 0; + } + + clock_update_hz(s->clock_out, freq); +} + +static void npcm7xx_clk_update_sel(void *opaque) +{ + NPCM7xxClockSELState *s = opaque; + uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], s->offset, + s->len); + + if (index >= s->input_size) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: SEL index: %u out of range\n", + __func__, index); + index = 0; + } + clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index])); +} + +static void npcm7xx_clk_update_divider(void *opaque) +{ + NPCM7xxClockDividerState *s = opaque; + uint32_t freq; + + freq = s->divide(s); + clock_update_hz(s->clock_out, freq); +} + +static uint32_t divide_by_constant(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) / s->divisor; +} + +static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) / + (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1); +} + +static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s) +{ + return divide_by_reg_divisor(s) / 2; +} + +static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s) +{ + return clock_get_hz(s->clock_in) >> + extract32(s->clk->regs[s->reg], s->offset, s->len); +} + +static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg) +{ + switch (reg) { + case NPCM7XX_CLK_PLLCON0: + return NPCM7XX_CLOCK_PLL0; + case NPCM7XX_CLK_PLLCON1: + return NPCM7XX_CLOCK_PLL1; + case NPCM7XX_CLK_PLLCON2: + return NPCM7XX_CLOCK_PLL2; + case NPCM7XX_CLK_PLLCONG: + return NPCM7XX_CLOCK_PLLG; + default: + g_assert_not_reached(); + } +} + +static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + npcm7xx_clk_update_pll(&clk->plls[i]); + } +} + +static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + npcm7xx_clk_update_sel(&clk->sels[i]); + } +} + +static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk) +{ + int i; + + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + npcm7xx_clk_update_divider(&clk->dividers[i]); + } +} + +static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk) +{ + clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ); + npcm7xx_clk_update_all_plls(clk); + npcm7xx_clk_update_all_sels(clk); + npcm7xx_clk_update_all_dividers(clk); +} + +/* Types of clock sources. */ +typedef enum ClockSrcType { + CLKSRC_REF, + CLKSRC_PLL, + CLKSRC_SEL, + CLKSRC_DIV, +} ClockSrcType; + +typedef struct PLLInitInfo { + const char *name; + ClockSrcType src_type; + int src_index; + int reg; + const char *public_name; +} PLLInitInfo; + +typedef struct SELInitInfo { + const char *name; + uint8_t input_size; + ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT]; + int src_index[NPCM7XX_CLK_SEL_MAX_INPUT]; + int offset; + int len; + const char *public_name; +} SELInitInfo; + +typedef struct DividerInitInfo { + const char *name; + ClockSrcType src_type; + int src_index; + uint32_t (*divide)(NPCM7xxClockDividerState *s); + int reg; /* not used when type == CONSTANT */ + int offset; /* not used when type == CONSTANT */ + int len; /* not used when type == CONSTANT */ + int divisor; /* used only when type == CONSTANT */ + const char *public_name; +} DividerInitInfo; + +static const PLLInitInfo pll_init_info_list[] = { + [NPCM7XX_CLOCK_PLL0] = { + .name = "pll0", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON0, + }, + [NPCM7XX_CLOCK_PLL1] = { + .name = "pll1", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON1, + }, + [NPCM7XX_CLOCK_PLL2] = { + .name = "pll2", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCON2, + }, + [NPCM7XX_CLOCK_PLLG] = { + .name = "pllg", + .src_type = CLKSRC_REF, + .reg = NPCM7XX_CLK_PLLCONG, + }, +}; + +static const SELInitInfo sel_init_info_list[] = { + [NPCM7XX_CLOCK_PIXCKSEL] = { + .name = "pixcksel", + .input_size = 2, + .src_type = {CLKSRC_PLL, CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLLG, 0}, + .offset = 5, + .len = 1, + .public_name = "pixel-clock", + }, + [NPCM7XX_CLOCK_MCCKSEL] = { + .name = "mccksel", + .input_size = 4, + .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF, + /*MCBPCK, shouldn't be used in normal operation*/ + CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0}, + .offset = 12, + .len = 2, + .public_name = "mc-phy-clock", + }, + [NPCM7XX_CLOCK_CPUCKSEL] = { + .name = "cpucksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, + /*SYSBPCK, shouldn't be used in normal operation*/ + CLKSRC_REF}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0}, + .offset = 0, + .len = 2, + .public_name = "system-clock", + }, + [NPCM7XX_CLOCK_CLKOUTSEL] = { + .name = "clkoutsel", + .input_size = 5, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, + CLKSRC_PLL, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2}, + .offset = 18, + .len = 3, + .public_name = "tock", + }, + [NPCM7XX_CLOCK_UARTCKSEL] = { + .name = "uartcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 8, + .len = 2, + }, + [NPCM7XX_CLOCK_TIMCKSEL] = { + .name = "timcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 14, + .len = 2, + }, + [NPCM7XX_CLOCK_SDCKSEL] = { + .name = "sdcksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 6, + .len = 2, + }, + [NPCM7XX_CLOCK_GFXMSEL] = { + .name = "gfxmksel", + .input_size = 2, + .src_type = {CLKSRC_REF, CLKSRC_PLL}, + .src_index = {0, NPCM7XX_CLOCK_PLL2}, + .offset = 21, + .len = 1, + }, + [NPCM7XX_CLOCK_SUCKSEL] = { + .name = "sucksel", + .input_size = 4, + .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV}, + .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, + NPCM7XX_CLOCK_PLL2D2}, + .offset = 10, + .len = 2, + }, +}; + +static const DividerInitInfo divider_init_info_list[] = { + [NPCM7XX_CLOCK_PLL1D2] = { + .name = "pll1d2", + .src_type = CLKSRC_PLL, + .src_index = NPCM7XX_CLOCK_PLL1, + .divide = divide_by_constant, + .divisor = 2, + }, + [NPCM7XX_CLOCK_PLL2D2] = { + .name = "pll2d2", + .src_type = CLKSRC_PLL, + .src_index = NPCM7XX_CLOCK_PLL2, + .divide = divide_by_constant, + .divisor = 2, + }, + [NPCM7XX_CLOCK_MC_DIVIDER] = { + .name = "mc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_MCCKSEL, + .divide = divide_by_constant, + .divisor = 2, + .public_name = "mc-clock" + }, + [NPCM7XX_CLOCK_AXI_DIVIDER] = { + .name = "axi-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_CPUCKSEL, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 0, + .len = 1, + .public_name = "clk2" + }, + [NPCM7XX_CLOCK_AHB_DIVIDER] = { + .name = "ahb-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AXI_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 26, + .len = 2, + .public_name = "clk4" + }, + [NPCM7XX_CLOCK_AHB3_DIVIDER] = { + .name = "ahb3-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 6, + .len = 5, + .public_name = "ahb3-spi3-clock" + }, + [NPCM7XX_CLOCK_SPI0_DIVIDER] = { + .name = "spi0-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV3, + .offset = 6, + .len = 5, + .public_name = "spi0-clock", + }, + [NPCM7XX_CLOCK_SPIX_DIVIDER] = { + .name = "spix-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV3, + .offset = 1, + .len = 5, + .public_name = "spix-clock", + }, + [NPCM7XX_CLOCK_APB1_DIVIDER] = { + .name = "apb1-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 24, + .len = 2, + .public_name = "apb1-clock", + }, + [NPCM7XX_CLOCK_APB2_DIVIDER] = { + .name = "apb2-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 26, + .len = 2, + .public_name = "apb2-clock", + }, + [NPCM7XX_CLOCK_APB3_DIVIDER] = { + .name = "apb3-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 28, + .len = 2, + .public_name = "apb3-clock", + }, + [NPCM7XX_CLOCK_APB4_DIVIDER] = { + .name = "apb4-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 30, + .len = 2, + .public_name = "apb4-clock", + }, + [NPCM7XX_CLOCK_APB5_DIVIDER] = { + .name = "apb5-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_AHB_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 22, + .len = 2, + .public_name = "apb5-clock", + }, + [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = { + .name = "clkout-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_CLKOUTSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 16, + .len = 5, + .public_name = "clkout", + }, + [NPCM7XX_CLOCK_UART_DIVIDER] = { + .name = "uart-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_UARTCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 16, + .len = 5, + .public_name = "uart-clock", + }, + [NPCM7XX_CLOCK_TIMER_DIVIDER] = { + .name = "timer-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_TIMCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 21, + .len = 5, + .public_name = "timer-clock", + }, + [NPCM7XX_CLOCK_ADC_DIVIDER] = { + .name = "adc-divider", + .src_type = CLKSRC_DIV, + .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER, + .divide = shift_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 28, + .len = 3, + .public_name = "adc-clock", + }, + [NPCM7XX_CLOCK_MMC_DIVIDER] = { + .name = "mmc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SDCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV1, + .offset = 11, + .len = 5, + .public_name = "mmc-clock", + }, + [NPCM7XX_CLOCK_SDHC_DIVIDER] = { + .name = "sdhc-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SDCKSEL, + .divide = divide_by_reg_divisor_times_2, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 0, + .len = 4, + .public_name = "sdhc-clock", + }, + [NPCM7XX_CLOCK_GFXM_DIVIDER] = { + .name = "gfxm-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_GFXMSEL, + .divide = divide_by_constant, + .divisor = 3, + .public_name = "gfxm-clock", + }, + [NPCM7XX_CLOCK_UTMI_DIVIDER] = { + .name = "utmi-divider", + .src_type = CLKSRC_SEL, + .src_index = NPCM7XX_CLOCK_SUCKSEL, + .divide = divide_by_reg_divisor, + .reg = NPCM7XX_CLK_CLKDIV2, + .offset = 8, + .len = 5, + .public_name = "utmi-clock", + }, +}; + +static void npcm7xx_clk_pll_init(Object *obj) +{ + NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj); + + pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in", + npcm7xx_clk_update_pll, pll); + pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out"); +} + +static void npcm7xx_clk_sel_init(Object *obj) +{ + int i; + NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj); + + for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) { + sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel), + g_strdup_printf("clock-in[%d]", i), + npcm7xx_clk_update_sel, sel); + } + sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out"); +} +static void npcm7xx_clk_divider_init(Object *obj) +{ + NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj); + + div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in", + npcm7xx_clk_update_divider, div); + div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out"); +} + +static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll, + NPCM7xxCLKState *clk, const PLLInitInfo *init_info) +{ + pll->name = init_info->name; + pll->clk = clk; + pll->reg = init_info->reg; + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel, + NPCM7xxCLKState *clk, const SELInitInfo *init_info) +{ + int input_size = init_info->input_size; + + sel->name = init_info->name; + sel->clk = clk; + sel->input_size = init_info->input_size; + g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT); + sel->offset = init_info->offset; + sel->len = init_info->len; + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div, + NPCM7xxCLKState *clk, const DividerInitInfo *init_info) +{ + div->name = init_info->name; + div->clk = clk; + + div->divide = init_info->divide; + if (div->divide == divide_by_constant) { + div->divisor = init_info->divisor; + } else { + div->reg = init_info->reg; + div->offset = init_info->offset; + div->len = init_info->len; + } + if (init_info->public_name != NULL) { + qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk), + init_info->public_name); + } +} + +static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type, + int index) +{ + switch (type) { + case CLKSRC_REF: + return clk->clkref; + case CLKSRC_PLL: + return clk->plls[index].clock_out; + case CLKSRC_SEL: + return clk->sels[index].clock_out; + case CLKSRC_DIV: + return clk->dividers[index].clock_out; + default: + g_assert_not_reached(); + } +} + +static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk) +{ + int i, j; + Clock *src; + + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type, + pll_init_info_list[i].src_index); + clock_set_source(clk->plls[i].clock_in, src); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + for (j = 0; j < sel_init_info_list[i].input_size; ++j) { + src = npcm7xx_get_clock(clk, sel_init_info_list[i].src_type[j], + sel_init_info_list[i].src_index[j]); + clock_set_source(clk->sels[i].clock_in[j], src); + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type, + divider_init_info_list[i].src_index); + clock_set_source(clk->dividers[i].clock_in, src); + } +} + static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); @@ -129,7 +740,7 @@ static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) * * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000. */ - value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_TIMER_REF_HZ; + value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_CLOCK_REF_HZ; break; default: @@ -183,6 +794,20 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset, value |= (value & PLLCON_LOKS); } } + /* Only update PLL when it is locked. */ + if (value & PLLCON_LOKI) { + npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]); + } + break; + + case NPCM7XX_CLK_CLKSEL: + npcm7xx_clk_update_all_sels(s); + break; + + case NPCM7XX_CLK_CLKDIV1: + case NPCM7XX_CLK_CLKDIV2: + case NPCM7XX_CLK_CLKDIV3: + npcm7xx_clk_update_all_dividers(s); break; case NPCM7XX_CLK_CNTR25M: @@ -234,6 +859,7 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) case RESET_TYPE_COLD: memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values)); s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + npcm7xx_clk_update_all_clocks(s); return; } @@ -245,28 +871,157 @@ static void npcm7xx_clk_enter_reset(Object *obj, ResetType type) __func__, type); } +static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) +{ + int i; + + s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL); + + /* First pass: init all converter modules */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != NPCM7XX_CLOCK_NR_SELS); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list) + != NPCM7XX_CLOCK_NR_DIVIDERS); + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + object_initialize_child(OBJECT(s), pll_init_info_list[i].name, + &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL); + npcm7xx_init_clock_pll(&s->plls[i], s, + &pll_init_info_list[i]); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + object_initialize_child(OBJECT(s), sel_init_info_list[i].name, + &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL); + npcm7xx_init_clock_sel(&s->sels[i], s, + &sel_init_info_list[i]); + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + object_initialize_child(OBJECT(s), divider_init_info_list[i].name, + &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER); + npcm7xx_init_clock_divider(&s->dividers[i], s, + ÷r_init_info_list[i]); + } + + /* Second pass: connect converter modules */ + npcm7xx_connect_clocks(s); + + clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ); +} + static void npcm7xx_clk_init(Object *obj) { NPCM7xxCLKState *s = NPCM7XX_CLK(obj); memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s, TYPE_NPCM7XX_CLK, 4 * KiB); - sysbus_init_mmio(&s->parent, &s->iomem); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static int npcm7xx_clk_post_load(void *opaque, int version_id) +{ + if (version_id >= 1) { + NPCM7xxCLKState *clk = opaque; + + npcm7xx_clk_update_all_clocks(clk); + } + + return 0; +} + +static void npcm7xx_clk_realize(DeviceState *dev, Error **errp) +{ + int i; + NPCM7xxCLKState *s = NPCM7XX_CLK(dev); + qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); + npcm7xx_clk_init_clock_hierarchy(s); + + /* Realize child devices */ + for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) { + if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) { + return; + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) { + if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) { + return; + } + } + for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) { + if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) { + return; + } + } } -static const VMStateDescription vmstate_npcm7xx_clk = { - .name = "npcm7xx-clk", +static const VMStateDescription vmstate_npcm7xx_clk_pll = { + .name = "npcm7xx-clock-pll", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk_sel = { + .name = "npcm7xx-clock-sel", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState, + NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk_divider = { + .name = "npcm7xx-clock-divider", .version_id = 0, .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_clk = { + .name = "npcm7xx-clk", + .version_id = 1, + .minimum_version_id = 1, + .post_load = npcm7xx_clk_post_load, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS), VMSTATE_INT64(ref_ns, NPCM7xxCLKState), + VMSTATE_CLOCK(clkref, NPCM7xxCLKState), VMSTATE_END_OF_LIST(), }, }; +static void npcm7xx_clk_pll_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock PLL Module"; + dc->vmsd = &vmstate_npcm7xx_clk_pll; +} + +static void npcm7xx_clk_sel_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock SEL Module"; + dc->vmsd = &vmstate_npcm7xx_clk_sel; +} + +static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Clock Divider Module"; + dc->vmsd = &vmstate_npcm7xx_clk_divider; +} + static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) { ResettableClass *rc = RESETTABLE_CLASS(klass); @@ -276,9 +1031,34 @@ static void npcm7xx_clk_class_init(ObjectClass *klass, void *data) dc->desc = "NPCM7xx Clock Control Registers"; dc->vmsd = &vmstate_npcm7xx_clk; + dc->realize = npcm7xx_clk_realize; rc->phases.enter = npcm7xx_clk_enter_reset; } +static const TypeInfo npcm7xx_clk_pll_info = { + .name = TYPE_NPCM7XX_CLOCK_PLL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockPLLState), + .instance_init = npcm7xx_clk_pll_init, + .class_init = npcm7xx_clk_pll_class_init, +}; + +static const TypeInfo npcm7xx_clk_sel_info = { + .name = TYPE_NPCM7XX_CLOCK_SEL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockSELState), + .instance_init = npcm7xx_clk_sel_init, + .class_init = npcm7xx_clk_sel_class_init, +}; + +static const TypeInfo npcm7xx_clk_divider_info = { + .name = TYPE_NPCM7XX_CLOCK_DIVIDER, + .parent = TYPE_DEVICE, + .instance_size = sizeof(NPCM7xxClockDividerState), + .instance_init = npcm7xx_clk_divider_init, + .class_init = npcm7xx_clk_divider_class_init, +}; + static const TypeInfo npcm7xx_clk_info = { .name = TYPE_NPCM7XX_CLK, .parent = TYPE_SYS_BUS_DEVICE, @@ -289,6 +1069,9 @@ static const TypeInfo npcm7xx_clk_info = { static void npcm7xx_clk_register_type(void) { + type_register_static(&npcm7xx_clk_pll_info); + type_register_static(&npcm7xx_clk_sel_info); + type_register_static(&npcm7xx_clk_divider_info); type_register_static(&npcm7xx_clk_info); } type_init(npcm7xx_clk_register_type); diff --git a/hw/misc/npcm7xx_gcr.c b/hw/misc/npcm7xx_gcr.c index 745f690..eace9e1 100644 --- a/hw/misc/npcm7xx_gcr.c +++ b/hw/misc/npcm7xx_gcr.c @@ -220,7 +220,7 @@ static void npcm7xx_gcr_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_gcr_ops, s, TYPE_NPCM7XX_GCR, 4 * KiB); - sysbus_init_mmio(&s->parent, &s->iomem); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); } static const VMStateDescription vmstate_npcm7xx_gcr = { diff --git a/hw/misc/npcm7xx_pwm.c b/hw/misc/npcm7xx_pwm.c new file mode 100644 index 0000000..e99e3cc --- /dev/null +++ b/hw/misc/npcm7xx_pwm.c @@ -0,0 +1,550 @@ +/* + * Nuvoton NPCM7xx PWM Module + * + * Copyright 2020 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_pwm.h" +#include "hw/registerfields.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "trace.h" + +REG32(NPCM7XX_PWM_PPR, 0x00); +REG32(NPCM7XX_PWM_CSR, 0x04); +REG32(NPCM7XX_PWM_PCR, 0x08); +REG32(NPCM7XX_PWM_CNR0, 0x0c); +REG32(NPCM7XX_PWM_CMR0, 0x10); +REG32(NPCM7XX_PWM_PDR0, 0x14); +REG32(NPCM7XX_PWM_CNR1, 0x18); +REG32(NPCM7XX_PWM_CMR1, 0x1c); +REG32(NPCM7XX_PWM_PDR1, 0x20); +REG32(NPCM7XX_PWM_CNR2, 0x24); +REG32(NPCM7XX_PWM_CMR2, 0x28); +REG32(NPCM7XX_PWM_PDR2, 0x2c); +REG32(NPCM7XX_PWM_CNR3, 0x30); +REG32(NPCM7XX_PWM_CMR3, 0x34); +REG32(NPCM7XX_PWM_PDR3, 0x38); +REG32(NPCM7XX_PWM_PIER, 0x3c); +REG32(NPCM7XX_PWM_PIIR, 0x40); +REG32(NPCM7XX_PWM_PWDR0, 0x44); +REG32(NPCM7XX_PWM_PWDR1, 0x48); +REG32(NPCM7XX_PWM_PWDR2, 0x4c); +REG32(NPCM7XX_PWM_PWDR3, 0x50); + +/* Register field definitions. */ +#define NPCM7XX_PPR(rv, index) extract32((rv), npcm7xx_ppr_base[index], 8) +#define NPCM7XX_CSR(rv, index) extract32((rv), npcm7xx_csr_base[index], 3) +#define NPCM7XX_CH(rv, index) extract32((rv), npcm7xx_ch_base[index], 4) +#define NPCM7XX_CH_EN BIT(0) +#define NPCM7XX_CH_INV BIT(2) +#define NPCM7XX_CH_MOD BIT(3) + +/* Offset of each PWM channel's prescaler in the PPR register. */ +static const int npcm7xx_ppr_base[] = { 0, 0, 8, 8 }; +/* Offset of each PWM channel's clock selector in the CSR register. */ +static const int npcm7xx_csr_base[] = { 0, 4, 8, 12 }; +/* Offset of each PWM channel's control variable in the PCR register. */ +static const int npcm7xx_ch_base[] = { 0, 8, 12, 16 }; + +static uint32_t npcm7xx_pwm_calculate_freq(NPCM7xxPWM *p) +{ + uint32_t ppr; + uint32_t csr; + uint32_t freq; + + if (!p->running) { + return 0; + } + + csr = NPCM7XX_CSR(p->module->csr, p->index); + ppr = NPCM7XX_PPR(p->module->ppr, p->index); + freq = clock_get_hz(p->module->clock); + freq /= ppr + 1; + /* csr can only be 0~4 */ + if (csr > 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid csr value %u\n", + __func__, csr); + csr = 4; + } + /* freq won't be changed if csr == 4. */ + if (csr < 4) { + freq >>= csr + 1; + } + + return freq / (p->cnr + 1); +} + +static uint32_t npcm7xx_pwm_calculate_duty(NPCM7xxPWM *p) +{ + uint64_t duty; + + if (p->running) { + if (p->cnr == 0) { + duty = 0; + } else if (p->cmr >= p->cnr) { + duty = NPCM7XX_PWM_MAX_DUTY; + } else { + duty = NPCM7XX_PWM_MAX_DUTY * (p->cmr + 1) / (p->cnr + 1); + } + } else { + duty = 0; + } + + if (p->inverted) { + duty = NPCM7XX_PWM_MAX_DUTY - duty; + } + + return duty; +} + +static void npcm7xx_pwm_update_freq(NPCM7xxPWM *p) +{ + uint32_t freq = npcm7xx_pwm_calculate_freq(p); + + if (freq != p->freq) { + trace_npcm7xx_pwm_update_freq(DEVICE(p->module)->canonical_path, + p->index, p->freq, freq); + p->freq = freq; + } +} + +static void npcm7xx_pwm_update_duty(NPCM7xxPWM *p) +{ + uint32_t duty = npcm7xx_pwm_calculate_duty(p); + + if (duty != p->duty) { + trace_npcm7xx_pwm_update_duty(DEVICE(p->module)->canonical_path, + p->index, p->duty, duty); + p->duty = duty; + } +} + +static void npcm7xx_pwm_update_output(NPCM7xxPWM *p) +{ + npcm7xx_pwm_update_freq(p); + npcm7xx_pwm_update_duty(p); +} + +static void npcm7xx_pwm_write_ppr(NPCM7xxPWMState *s, uint32_t new_ppr) +{ + int i; + uint32_t old_ppr = s->ppr; + + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ppr_base) != NPCM7XX_PWM_PER_MODULE); + s->ppr = new_ppr; + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { + if (NPCM7XX_PPR(old_ppr, i) != NPCM7XX_PPR(new_ppr, i)) { + npcm7xx_pwm_update_freq(&s->pwm[i]); + } + } +} + +static void npcm7xx_pwm_write_csr(NPCM7xxPWMState *s, uint32_t new_csr) +{ + int i; + uint32_t old_csr = s->csr; + + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_csr_base) != NPCM7XX_PWM_PER_MODULE); + s->csr = new_csr; + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { + if (NPCM7XX_CSR(old_csr, i) != NPCM7XX_CSR(new_csr, i)) { + npcm7xx_pwm_update_freq(&s->pwm[i]); + } + } +} + +static void npcm7xx_pwm_write_pcr(NPCM7xxPWMState *s, uint32_t new_pcr) +{ + int i; + bool inverted; + uint32_t pcr; + NPCM7xxPWM *p; + + s->pcr = new_pcr; + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ch_base) != NPCM7XX_PWM_PER_MODULE); + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { + p = &s->pwm[i]; + pcr = NPCM7XX_CH(new_pcr, i); + inverted = pcr & NPCM7XX_CH_INV; + + /* + * We only run a PWM channel with toggle mode. Single-shot mode does not + * generate frequency and duty-cycle values. + */ + if ((pcr & NPCM7XX_CH_EN) && (pcr & NPCM7XX_CH_MOD)) { + if (p->running) { + /* Re-run this PWM channel if inverted changed. */ + if (p->inverted ^ inverted) { + p->inverted = inverted; + npcm7xx_pwm_update_duty(p); + } + } else { + /* Run this PWM channel. */ + p->running = true; + p->inverted = inverted; + npcm7xx_pwm_update_output(p); + } + } else { + /* Clear this PWM channel. */ + p->running = false; + p->inverted = inverted; + npcm7xx_pwm_update_output(p); + } + } + +} + +static hwaddr npcm7xx_cnr_index(hwaddr offset) +{ + switch (offset) { + case A_NPCM7XX_PWM_CNR0: + return 0; + case A_NPCM7XX_PWM_CNR1: + return 1; + case A_NPCM7XX_PWM_CNR2: + return 2; + case A_NPCM7XX_PWM_CNR3: + return 3; + default: + g_assert_not_reached(); + } +} + +static hwaddr npcm7xx_cmr_index(hwaddr offset) +{ + switch (offset) { + case A_NPCM7XX_PWM_CMR0: + return 0; + case A_NPCM7XX_PWM_CMR1: + return 1; + case A_NPCM7XX_PWM_CMR2: + return 2; + case A_NPCM7XX_PWM_CMR3: + return 3; + default: + g_assert_not_reached(); + } +} + +static hwaddr npcm7xx_pdr_index(hwaddr offset) +{ + switch (offset) { + case A_NPCM7XX_PWM_PDR0: + return 0; + case A_NPCM7XX_PWM_PDR1: + return 1; + case A_NPCM7XX_PWM_PDR2: + return 2; + case A_NPCM7XX_PWM_PDR3: + return 3; + default: + g_assert_not_reached(); + } +} + +static hwaddr npcm7xx_pwdr_index(hwaddr offset) +{ + switch (offset) { + case A_NPCM7XX_PWM_PWDR0: + return 0; + case A_NPCM7XX_PWM_PWDR1: + return 1; + case A_NPCM7XX_PWM_PWDR2: + return 2; + case A_NPCM7XX_PWM_PWDR3: + return 3; + default: + g_assert_not_reached(); + } +} + +static uint64_t npcm7xx_pwm_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCM7xxPWMState *s = opaque; + uint64_t value = 0; + + switch (offset) { + case A_NPCM7XX_PWM_CNR0: + case A_NPCM7XX_PWM_CNR1: + case A_NPCM7XX_PWM_CNR2: + case A_NPCM7XX_PWM_CNR3: + value = s->pwm[npcm7xx_cnr_index(offset)].cnr; + break; + + case A_NPCM7XX_PWM_CMR0: + case A_NPCM7XX_PWM_CMR1: + case A_NPCM7XX_PWM_CMR2: + case A_NPCM7XX_PWM_CMR3: + value = s->pwm[npcm7xx_cmr_index(offset)].cmr; + break; + + case A_NPCM7XX_PWM_PDR0: + case A_NPCM7XX_PWM_PDR1: + case A_NPCM7XX_PWM_PDR2: + case A_NPCM7XX_PWM_PDR3: + value = s->pwm[npcm7xx_pdr_index(offset)].pdr; + break; + + case A_NPCM7XX_PWM_PWDR0: + case A_NPCM7XX_PWM_PWDR1: + case A_NPCM7XX_PWM_PWDR2: + case A_NPCM7XX_PWM_PWDR3: + value = s->pwm[npcm7xx_pwdr_index(offset)].pwdr; + break; + + case A_NPCM7XX_PWM_PPR: + value = s->ppr; + break; + + case A_NPCM7XX_PWM_CSR: + value = s->csr; + break; + + case A_NPCM7XX_PWM_PCR: + value = s->pcr; + break; + + case A_NPCM7XX_PWM_PIER: + value = s->pier; + break; + + case A_NPCM7XX_PWM_PIIR: + value = s->piir; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + break; + } + + trace_npcm7xx_pwm_read(DEVICE(s)->canonical_path, offset, value); + return value; +} + +static void npcm7xx_pwm_write(void *opaque, hwaddr offset, + uint64_t v, unsigned size) +{ + NPCM7xxPWMState *s = opaque; + NPCM7xxPWM *p; + uint32_t value = v; + + trace_npcm7xx_pwm_write(DEVICE(s)->canonical_path, offset, value); + switch (offset) { + case A_NPCM7XX_PWM_CNR0: + case A_NPCM7XX_PWM_CNR1: + case A_NPCM7XX_PWM_CNR2: + case A_NPCM7XX_PWM_CNR3: + p = &s->pwm[npcm7xx_cnr_index(offset)]; + p->cnr = value; + npcm7xx_pwm_update_output(p); + break; + + case A_NPCM7XX_PWM_CMR0: + case A_NPCM7XX_PWM_CMR1: + case A_NPCM7XX_PWM_CMR2: + case A_NPCM7XX_PWM_CMR3: + p = &s->pwm[npcm7xx_cmr_index(offset)]; + p->cmr = value; + npcm7xx_pwm_update_output(p); + break; + + case A_NPCM7XX_PWM_PDR0: + case A_NPCM7XX_PWM_PDR1: + case A_NPCM7XX_PWM_PDR2: + case A_NPCM7XX_PWM_PDR3: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", + __func__, offset); + break; + + case A_NPCM7XX_PWM_PWDR0: + case A_NPCM7XX_PWM_PWDR1: + case A_NPCM7XX_PWM_PWDR2: + case A_NPCM7XX_PWM_PWDR3: + qemu_log_mask(LOG_UNIMP, + "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n", + __func__, offset); + break; + + case A_NPCM7XX_PWM_PPR: + npcm7xx_pwm_write_ppr(s, value); + break; + + case A_NPCM7XX_PWM_CSR: + npcm7xx_pwm_write_csr(s, value); + break; + + case A_NPCM7XX_PWM_PCR: + npcm7xx_pwm_write_pcr(s, value); + break; + + case A_NPCM7XX_PWM_PIER: + qemu_log_mask(LOG_UNIMP, + "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n", + __func__, offset); + break; + + case A_NPCM7XX_PWM_PIIR: + qemu_log_mask(LOG_UNIMP, + "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n", + __func__, offset); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid offset 0x%04" HWADDR_PRIx "\n", + __func__, offset); + break; + } +} + +static const struct MemoryRegionOps npcm7xx_pwm_ops = { + .read = npcm7xx_pwm_read, + .write = npcm7xx_pwm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void npcm7xx_pwm_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxPWMState *s = NPCM7XX_PWM(obj); + int i; + + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) { + NPCM7xxPWM *p = &s->pwm[i]; + + p->cnr = 0x00000000; + p->cmr = 0x00000000; + p->pdr = 0x00000000; + p->pwdr = 0x00000000; + } + + s->ppr = 0x00000000; + s->csr = 0x00000000; + s->pcr = 0x00000000; + s->pier = 0x00000000; + s->piir = 0x00000000; +} + +static void npcm7xx_pwm_hold_reset(Object *obj) +{ + NPCM7xxPWMState *s = NPCM7XX_PWM(obj); + int i; + + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) { + qemu_irq_lower(s->pwm[i].irq); + } +} + +static void npcm7xx_pwm_init(Object *obj) +{ + NPCM7xxPWMState *s = NPCM7XX_PWM(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + int i; + + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) { + NPCM7xxPWM *p = &s->pwm[i]; + p->module = s; + p->index = i; + sysbus_init_irq(sbd, &p->irq); + } + + memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s, + TYPE_NPCM7XX_PWM, 4 * KiB); + sysbus_init_mmio(sbd, &s->iomem); + s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL); + + for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { + object_property_add_uint32_ptr(obj, "freq[*]", + &s->pwm[i].freq, OBJ_PROP_FLAG_READ); + object_property_add_uint32_ptr(obj, "duty[*]", + &s->pwm[i].duty, OBJ_PROP_FLAG_READ); + } +} + +static const VMStateDescription vmstate_npcm7xx_pwm = { + .name = "npcm7xx-pwm", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_BOOL(running, NPCM7xxPWM), + VMSTATE_BOOL(inverted, NPCM7xxPWM), + VMSTATE_UINT8(index, NPCM7xxPWM), + VMSTATE_UINT32(cnr, NPCM7xxPWM), + VMSTATE_UINT32(cmr, NPCM7xxPWM), + VMSTATE_UINT32(pdr, NPCM7xxPWM), + VMSTATE_UINT32(pwdr, NPCM7xxPWM), + VMSTATE_UINT32(freq, NPCM7xxPWM), + VMSTATE_UINT32(duty, NPCM7xxPWM), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_pwm_module = { + .name = "npcm7xx-pwm-module", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clock, NPCM7xxPWMState), + VMSTATE_STRUCT_ARRAY(pwm, NPCM7xxPWMState, + NPCM7XX_PWM_PER_MODULE, 0, vmstate_npcm7xx_pwm, + NPCM7xxPWM), + VMSTATE_UINT32(ppr, NPCM7xxPWMState), + VMSTATE_UINT32(csr, NPCM7xxPWMState), + VMSTATE_UINT32(pcr, NPCM7xxPWMState), + VMSTATE_UINT32(pier, NPCM7xxPWMState), + VMSTATE_UINT32(piir, NPCM7xxPWMState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm7xx_pwm_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx PWM Controller"; + dc->vmsd = &vmstate_npcm7xx_pwm_module; + rc->phases.enter = npcm7xx_pwm_enter_reset; + rc->phases.hold = npcm7xx_pwm_hold_reset; +} + +static const TypeInfo npcm7xx_pwm_info = { + .name = TYPE_NPCM7XX_PWM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxPWMState), + .class_init = npcm7xx_pwm_class_init, + .instance_init = npcm7xx_pwm_init, +}; + +static void npcm7xx_pwm_register_type(void) +{ + type_register_static(&npcm7xx_pwm_info); +} +type_init(npcm7xx_pwm_register_type); diff --git a/hw/misc/npcm7xx_rng.c b/hw/misc/npcm7xx_rng.c index f650f34..b01df7c 100644 --- a/hw/misc/npcm7xx_rng.c +++ b/hw/misc/npcm7xx_rng.c @@ -143,7 +143,7 @@ static void npcm7xx_rng_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_rng_ops, s, "regs", NPCM7XX_RNG_REGS_SIZE); - sysbus_init_mmio(&s->parent, &s->iomem); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); } static const VMStateDescription vmstate_npcm7xx_rng = { diff --git a/hw/misc/trace-events b/hw/misc/trace-events index b5118ac..d626b9d 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -120,6 +120,12 @@ npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " valu 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" +# npcm7xx_pwm.c +npcm7xx_pwm_read(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +npcm7xx_pwm_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +npcm7xx_pwm_update_freq(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Freq: old_freq: %u, new_freq: %u" +npcm7xx_pwm_update_duty(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Duty: old_duty: %u, new_duty: %u" + # stm32f4xx_syscfg.c stm32f4xx_syscfg_set_irq(int gpio, int line, int level) "Interupt: GPIO: %d, Line: %d; Level: %d" stm32f4xx_pulse_exti(int irq) "Pulse EXTI: %d" diff --git a/hw/net/lan9118.c b/hw/net/lan9118.c index ab57c02..abc7962 100644 --- a/hw/net/lan9118.c +++ b/hw/net/lan9118.c @@ -40,6 +40,17 @@ do { hw_error("lan9118: error: " fmt , ## __VA_ARGS__);} while (0) do { fprintf(stderr, "lan9118: error: " fmt , ## __VA_ARGS__);} while (0) #endif +/* The tx and rx fifo ports are a range of aliased 32-bit registers */ +#define RX_DATA_FIFO_PORT_FIRST 0x00 +#define RX_DATA_FIFO_PORT_LAST 0x1f +#define TX_DATA_FIFO_PORT_FIRST 0x20 +#define TX_DATA_FIFO_PORT_LAST 0x3f + +#define RX_STATUS_FIFO_PORT 0x40 +#define RX_STATUS_FIFO_PEEK 0x44 +#define TX_STATUS_FIFO_PORT 0x48 +#define TX_STATUS_FIFO_PEEK 0x4c + #define CSR_ID_REV 0x50 #define CSR_IRQ_CFG 0x54 #define CSR_INT_STS 0x58 @@ -1020,7 +1031,8 @@ static void lan9118_writel(void *opaque, hwaddr offset, offset &= 0xff; //DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val); - if (offset >= 0x20 && offset < 0x40) { + if (offset >= TX_DATA_FIFO_PORT_FIRST && + offset <= TX_DATA_FIFO_PORT_LAST) { /* TX FIFO */ tx_fifo_push(s, val); return; @@ -1198,18 +1210,18 @@ static uint64_t lan9118_readl(void *opaque, hwaddr offset, lan9118_state *s = (lan9118_state *)opaque; //DPRINTF("Read reg 0x%02x\n", (int)offset); - if (offset < 0x20) { + if (offset <= RX_DATA_FIFO_PORT_LAST) { /* RX FIFO */ return rx_fifo_pop(s); } switch (offset) { - case 0x40: + case RX_STATUS_FIFO_PORT: return rx_status_fifo_pop(s); - case 0x44: - return s->rx_status_fifo[s->tx_status_fifo_head]; - case 0x48: + case RX_STATUS_FIFO_PEEK: + return s->rx_status_fifo[s->rx_status_fifo_head]; + case TX_STATUS_FIFO_PORT: return tx_status_fifo_pop(s); - case 0x4c: + case TX_STATUS_FIFO_PEEK: return s->tx_status_fifo[s->tx_status_fifo_head]; case CSR_ID_REV: return 0x01180001; diff --git a/hw/nvram/npcm7xx_otp.c b/hw/nvram/npcm7xx_otp.c index b16ca53..c61f2fc 100644 --- a/hw/nvram/npcm7xx_otp.c +++ b/hw/nvram/npcm7xx_otp.c @@ -371,7 +371,7 @@ static void npcm7xx_otp_realize(DeviceState *dev, Error **errp) { NPCM7xxOTPClass *oc = NPCM7XX_OTP_GET_CLASS(dev); NPCM7xxOTPState *s = NPCM7XX_OTP(dev); - SysBusDevice *sbd = &s->parent; + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); memset(s->array, 0, sizeof(s->array)); diff --git a/hw/ssi/npcm7xx_fiu.c b/hw/ssi/npcm7xx_fiu.c index 5040132..4eedb29 100644 --- a/hw/ssi/npcm7xx_fiu.c +++ b/hw/ssi/npcm7xx_fiu.c @@ -498,7 +498,7 @@ static void npcm7xx_fiu_hold_reset(Object *obj) static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp) { NPCM7xxFIUState *s = NPCM7XX_FIU(dev); - SysBusDevice *sbd = &s->parent; + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); int i; if (s->cs_count <= 0) { diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index d24445b..36e2c07 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -17,8 +17,8 @@ #include "qemu/osdep.h" #include "hw/irq.h" +#include "hw/qdev-clock.h" #include "hw/qdev-properties.h" -#include "hw/misc/npcm7xx_clk.h" #include "hw/timer/npcm7xx_timer.h" #include "migration/vmstate.h" #include "qemu/bitops.h" @@ -128,23 +128,18 @@ static uint32_t npcm7xx_tcsr_prescaler(uint32_t tcsr) /* Convert a timer cycle count to a time interval in nanoseconds. */ static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count) { - int64_t ns = count; + int64_t ticks = count; - ns *= NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ; - ns *= npcm7xx_tcsr_prescaler(t->tcsr); + ticks *= npcm7xx_tcsr_prescaler(t->tcsr); - return ns; + return clock_ticks_to_ns(t->ctrl->clock, ticks); } /* Convert a time interval in nanoseconds to a timer cycle count. */ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns) { - int64_t count; - - count = ns / (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ); - count /= npcm7xx_tcsr_prescaler(t->tcsr); - - return count; + return ns / clock_ticks_to_ns(t->ctrl->clock, + npcm7xx_tcsr_prescaler(t->tcsr)); } static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) @@ -166,8 +161,8 @@ static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t, int64_t cycles) { - uint32_t prescaler = npcm7xx_watchdog_timer_prescaler(t); - int64_t ns = (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ) * cycles; + int64_t ticks = cycles * npcm7xx_watchdog_timer_prescaler(t); + int64_t ns = clock_ticks_to_ns(t->ctrl->clock, ticks); /* * The reset function always clears the current timer. The caller of the @@ -176,7 +171,6 @@ static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t, */ npcm7xx_timer_clear(&t->base_timer); - ns *= prescaler; t->base_timer.remaining_ns = ns; } @@ -606,10 +600,11 @@ static void npcm7xx_timer_hold_reset(Object *obj) qemu_irq_lower(s->watchdog_timer.irq); } -static void npcm7xx_timer_realize(DeviceState *dev, Error **errp) +static void npcm7xx_timer_init(Object *obj) { - NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev); - SysBusDevice *sbd = &s->parent; + NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj); + DeviceState *dev = DEVICE(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); int i; NPCM7xxWatchdogTimer *w; @@ -627,11 +622,12 @@ static void npcm7xx_timer_realize(DeviceState *dev, Error **errp) npcm7xx_watchdog_timer_expired, w); sysbus_init_irq(sbd, &w->irq); - memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s, + memory_region_init_io(&s->iomem, obj, &npcm7xx_timer_ops, s, TYPE_NPCM7XX_TIMER, 4 * KiB); sysbus_init_mmio(sbd, &s->iomem); qdev_init_gpio_out_named(dev, &w->reset_signal, NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1); + s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL); } static const VMStateDescription vmstate_npcm7xx_base_timer = { @@ -675,10 +671,11 @@ static const VMStateDescription vmstate_npcm7xx_watchdog_timer = { static const VMStateDescription vmstate_npcm7xx_timer_ctrl = { .name = "npcm7xx-timer-ctrl", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState), + VMSTATE_CLOCK(clock, NPCM7xxTimerCtrlState), VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState, NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer, NPCM7xxTimer), @@ -697,7 +694,6 @@ static void npcm7xx_timer_class_init(ObjectClass *klass, void *data) QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS); dc->desc = "NPCM7xx Timer Controller"; - dc->realize = npcm7xx_timer_realize; dc->vmsd = &vmstate_npcm7xx_timer_ctrl; rc->phases.enter = npcm7xx_timer_enter_reset; rc->phases.hold = npcm7xx_timer_hold_reset; @@ -708,6 +704,7 @@ static const TypeInfo npcm7xx_timer_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(NPCM7xxTimerCtrlState), .class_init = npcm7xx_timer_class_init, + .instance_init = npcm7xx_timer_init, }; static void npcm7xx_timer_register_type(void) diff --git a/include/hw/adc/npcm7xx_adc.h b/include/hw/adc/npcm7xx_adc.h new file mode 100644 index 0000000..7d84421 --- /dev/null +++ b/include/hw/adc/npcm7xx_adc.h @@ -0,0 +1,69 @@ +/* + * Nuvoton NPCM7xx ADC Module + * + * Copyright 2020 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. + */ +#ifndef NPCM7XX_ADC_H +#define NPCM7XX_ADC_H + +#include "hw/clock.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qemu/timer.h" + +#define NPCM7XX_ADC_NUM_INPUTS 8 +/** + * This value should not be changed unless write_adc_calibration function in + * hw/arm/npcm7xx.c is also changed. + */ +#define NPCM7XX_ADC_NUM_CALIB 2 + +/** + * struct NPCM7xxADCState - Analog to Digital Converter Module device state. + * @parent: System bus device. + * @iomem: Memory region through which registers are accessed. + * @conv_timer: The timer counts down remaining cycles for the conversion. + * @irq: GIC interrupt line to fire on expiration (if enabled). + * @con: The Control Register. + * @data: The Data Buffer. + * @clock: The ADC Clock. + * @adci: The input voltage in units of uV. 1uv = 1e-6V. + * @vref: The external reference voltage. + * @iref: The internal reference voltage, initialized at launch time. + * @rv: The calibrated output values of 0.5V and 1.5V for the ADC. + */ +typedef struct { + SysBusDevice parent; + + MemoryRegion iomem; + + QEMUTimer conv_timer; + + qemu_irq irq; + uint32_t con; + uint32_t data; + Clock *clock; + + /* Voltages are in unit of uV. 1V = 1000000uV. */ + uint32_t adci[NPCM7XX_ADC_NUM_INPUTS]; + uint32_t vref; + uint32_t iref; + + uint16_t calibration_r_values[NPCM7XX_ADC_NUM_CALIB]; +} NPCM7xxADCState; + +#define TYPE_NPCM7XX_ADC "npcm7xx-adc" +#define NPCM7XX_ADC(obj) \ + OBJECT_CHECK(NPCM7xxADCState, (obj), TYPE_NPCM7XX_ADC) + +#endif /* NPCM7XX_ADC_H */ diff --git a/include/hw/arm/npcm7xx.h b/include/hw/arm/npcm7xx.h index 5469247..f6227aa 100644 --- a/include/hw/arm/npcm7xx.h +++ b/include/hw/arm/npcm7xx.h @@ -17,11 +17,13 @@ #define NPCM7XX_H #include "hw/boards.h" +#include "hw/adc/npcm7xx_adc.h" #include "hw/cpu/a9mpcore.h" #include "hw/gpio/npcm7xx_gpio.h" #include "hw/mem/npcm7xx_mc.h" #include "hw/misc/npcm7xx_clk.h" #include "hw/misc/npcm7xx_gcr.h" +#include "hw/misc/npcm7xx_pwm.h" #include "hw/misc/npcm7xx_rng.h" #include "hw/nvram/npcm7xx_otp.h" #include "hw/timer/npcm7xx_timer.h" @@ -76,6 +78,8 @@ typedef struct NPCM7xxState { NPCM7xxGCRState gcr; NPCM7xxCLKState clk; NPCM7xxTimerCtrlState tim[3]; + NPCM7xxADCState adc; + NPCM7xxPWMState pwm[2]; NPCM7xxOTPState key_storage; NPCM7xxOTPState fuse_array; NPCM7xxMCState mc; diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h index 2338fbb..d5c8d16 100644 --- a/include/hw/misc/npcm7xx_clk.h +++ b/include/hw/misc/npcm7xx_clk.h @@ -17,15 +17,10 @@ #define NPCM7XX_CLK_H #include "exec/memory.h" +#include "hw/clock.h" #include "hw/sysbus.h" /* - * The reference clock frequency for the timer modules, and the SECCNT and - * CNTR25M registers in this module, is always 25 MHz. - */ -#define NPCM7XX_TIMER_REF_HZ (25000000) - -/* * Number of registers in our device state structure. Don't change this without * incrementing the version_id in the vmstate. */ @@ -33,16 +28,151 @@ #define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in" -typedef struct NPCM7xxCLKState { +/* Maximum amount of clock inputs in a SEL module. */ +#define NPCM7XX_CLK_SEL_MAX_INPUT 5 + +/* PLLs in CLK module. */ +typedef enum NPCM7xxClockPLL { + NPCM7XX_CLOCK_PLL0, + NPCM7XX_CLOCK_PLL1, + NPCM7XX_CLOCK_PLL2, + NPCM7XX_CLOCK_PLLG, + NPCM7XX_CLOCK_NR_PLLS, +} NPCM7xxClockPLL; + +/* SEL/MUX in CLK module. */ +typedef enum NPCM7xxClockSEL { + NPCM7XX_CLOCK_PIXCKSEL, + NPCM7XX_CLOCK_MCCKSEL, + NPCM7XX_CLOCK_CPUCKSEL, + NPCM7XX_CLOCK_CLKOUTSEL, + NPCM7XX_CLOCK_UARTCKSEL, + NPCM7XX_CLOCK_TIMCKSEL, + NPCM7XX_CLOCK_SDCKSEL, + NPCM7XX_CLOCK_GFXMSEL, + NPCM7XX_CLOCK_SUCKSEL, + NPCM7XX_CLOCK_NR_SELS, +} NPCM7xxClockSEL; + +/* Dividers in CLK module. */ +typedef enum NPCM7xxClockDivider { + NPCM7XX_CLOCK_PLL1D2, /* PLL1/2 */ + NPCM7XX_CLOCK_PLL2D2, /* PLL2/2 */ + NPCM7XX_CLOCK_MC_DIVIDER, + NPCM7XX_CLOCK_AXI_DIVIDER, + NPCM7XX_CLOCK_AHB_DIVIDER, + NPCM7XX_CLOCK_AHB3_DIVIDER, + NPCM7XX_CLOCK_SPI0_DIVIDER, + NPCM7XX_CLOCK_SPIX_DIVIDER, + NPCM7XX_CLOCK_APB1_DIVIDER, + NPCM7XX_CLOCK_APB2_DIVIDER, + NPCM7XX_CLOCK_APB3_DIVIDER, + NPCM7XX_CLOCK_APB4_DIVIDER, + NPCM7XX_CLOCK_APB5_DIVIDER, + NPCM7XX_CLOCK_CLKOUT_DIVIDER, + NPCM7XX_CLOCK_UART_DIVIDER, + NPCM7XX_CLOCK_TIMER_DIVIDER, + NPCM7XX_CLOCK_ADC_DIVIDER, + NPCM7XX_CLOCK_MMC_DIVIDER, + NPCM7XX_CLOCK_SDHC_DIVIDER, + NPCM7XX_CLOCK_GFXM_DIVIDER, /* divide by 3 */ + NPCM7XX_CLOCK_UTMI_DIVIDER, + NPCM7XX_CLOCK_NR_DIVIDERS, +} NPCM7xxClockConverter; + +typedef struct NPCM7xxCLKState NPCM7xxCLKState; + +/** + * struct NPCM7xxClockPLLState - A PLL module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @clock_in: The input clock of this module. + * @clock_out: The output clock of this module. + * @reg: The control registers for this PLL module. + */ +typedef struct NPCM7xxClockPLLState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + Clock *clock_in; + Clock *clock_out; + + int reg; +} NPCM7xxClockPLLState; + +/** + * struct NPCM7xxClockSELState - A SEL module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @input_size: The size of inputs of this module. + * @clock_in: The input clocks of this module. + * @clock_out: The output clocks of this module. + * @offset: The offset of this module in the control register. + * @len: The length of this module in the control register. + */ +typedef struct NPCM7xxClockSELState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + uint8_t input_size; + Clock *clock_in[NPCM7XX_CLK_SEL_MAX_INPUT]; + Clock *clock_out; + + int offset; + int len; +} NPCM7xxClockSELState; + +/** + * struct NPCM7xxClockDividerState - A Divider module in CLK module. + * @name: The name of the module. + * @clk: The CLK module that owns this module. + * @clock_in: The input clock of this module. + * @clock_out: The output clock of this module. + * @divide: The function the divider uses to divide the input. + * @reg: The index of the control register that contains the divisor. + * @offset: The offset of the divisor in the control register. + * @len: The length of the divisor in the control register. + * @divisor: The divisor for a constant divisor + */ +typedef struct NPCM7xxClockDividerState { + DeviceState parent; + + const char *name; + NPCM7xxCLKState *clk; + Clock *clock_in; + Clock *clock_out; + + uint32_t (*divide)(struct NPCM7xxClockDividerState *s); + union { + struct { + int reg; + int offset; + int len; + }; + int divisor; + }; +} NPCM7xxClockDividerState; + +struct NPCM7xxCLKState { SysBusDevice parent; MemoryRegion iomem; + /* Clock converters */ + NPCM7xxClockPLLState plls[NPCM7XX_CLOCK_NR_PLLS]; + NPCM7xxClockSELState sels[NPCM7XX_CLOCK_NR_SELS]; + NPCM7xxClockDividerState dividers[NPCM7XX_CLOCK_NR_DIVIDERS]; + uint32_t regs[NPCM7XX_CLK_NR_REGS]; /* Time reference for SECCNT and CNTR25M, initialized by power on reset */ int64_t ref_ns; -} NPCM7xxCLKState; + + /* The incoming reference clock. */ + Clock *clkref; +}; #define TYPE_NPCM7XX_CLK "npcm7xx-clk" #define NPCM7XX_CLK(obj) OBJECT_CHECK(NPCM7xxCLKState, (obj), TYPE_NPCM7XX_CLK) diff --git a/include/hw/misc/npcm7xx_pwm.h b/include/hw/misc/npcm7xx_pwm.h new file mode 100644 index 0000000..5a689d3 --- /dev/null +++ b/include/hw/misc/npcm7xx_pwm.h @@ -0,0 +1,105 @@ +/* + * Nuvoton NPCM7xx PWM Module + * + * Copyright 2020 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. + */ +#ifndef NPCM7XX_PWM_H +#define NPCM7XX_PWM_H + +#include "hw/clock.h" +#include "hw/sysbus.h" +#include "hw/irq.h" + +/* Each PWM module holds 4 PWM channels. */ +#define NPCM7XX_PWM_PER_MODULE 4 + +/* + * Number of registers in one pwm module. Don't change this without increasing + * the version_id in vmstate. + */ +#define NPCM7XX_PWM_NR_REGS (0x54 / sizeof(uint32_t)) + +/* + * The maximum duty values. Each duty unit represents 1/NPCM7XX_PWM_MAX_DUTY + * cycles. For example, if NPCM7XX_PWM_MAX_DUTY=1,000,000 and a PWM has a duty + * value of 100,000 the duty cycle for that PWM is 10%. + */ +#define NPCM7XX_PWM_MAX_DUTY 1000000 + +typedef struct NPCM7xxPWMState NPCM7xxPWMState; + +/** + * struct NPCM7xxPWM - The state of a single PWM channel. + * @module: The PWM module that contains this channel. + * @irq: GIC interrupt line to fire on expiration if enabled. + * @running: Whether this PWM channel is generating output. + * @inverted: Whether this PWM channel is inverted. + * @index: The index of this PWM channel. + * @cnr: The counter register. + * @cmr: The comparator register. + * @pdr: The data register. + * @pwdr: The watchdog register. + * @freq: The frequency of this PWM channel. + * @duty: The duty cycle of this PWM channel. One unit represents + * 1/NPCM7XX_MAX_DUTY cycles. + */ +typedef struct NPCM7xxPWM { + NPCM7xxPWMState *module; + + qemu_irq irq; + + bool running; + bool inverted; + + uint8_t index; + uint32_t cnr; + uint32_t cmr; + uint32_t pdr; + uint32_t pwdr; + + uint32_t freq; + uint32_t duty; +} NPCM7xxPWM; + +/** + * struct NPCM7xxPWMState - Pulse Width Modulation device state. + * @parent: System bus device. + * @iomem: Memory region through which registers are accessed. + * @clock: The PWM clock. + * @pwm: The PWM channels owned by this module. + * @ppr: The prescaler register. + * @csr: The clock selector register. + * @pcr: The control register. + * @pier: The interrupt enable register. + * @piir: The interrupt indication register. + */ +struct NPCM7xxPWMState { + SysBusDevice parent; + + MemoryRegion iomem; + + Clock *clock; + NPCM7xxPWM pwm[NPCM7XX_PWM_PER_MODULE]; + + uint32_t ppr; + uint32_t csr; + uint32_t pcr; + uint32_t pier; + uint32_t piir; +}; + +#define TYPE_NPCM7XX_PWM "npcm7xx-pwm" +#define NPCM7XX_PWM(obj) \ + OBJECT_CHECK(NPCM7xxPWMState, (obj), TYPE_NPCM7XX_PWM) + +#endif /* NPCM7XX_PWM_H */ diff --git a/include/hw/timer/npcm7xx_timer.h b/include/hw/timer/npcm7xx_timer.h index 6993fd7..d45c051 100644 --- a/include/hw/timer/npcm7xx_timer.h +++ b/include/hw/timer/npcm7xx_timer.h @@ -101,6 +101,7 @@ struct NPCM7xxTimerCtrlState { uint32_t tisr; + Clock *clock; NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL]; NPCM7xxWatchdogTimer watchdog_timer; }; diff --git a/meson.build b/meson.build index e4db67c..0ce993a 100644 --- a/meson.build +++ b/meson.build @@ -1687,6 +1687,7 @@ if have_system 'chardev', 'hw/9pfs', 'hw/acpi', + 'hw/adc', 'hw/alpha', 'hw/arm', 'hw/audio', diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 7e6c881..f3bca73 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -931,14 +931,14 @@ struct ARMCPU { uint64_t midr; uint32_t revidr; uint32_t reset_fpsid; - uint32_t ctr; + uint64_t ctr; uint32_t reset_sctlr; uint64_t pmceid0; uint64_t pmceid1; uint32_t id_afr0; uint64_t id_aa64afr0; uint64_t id_aa64afr1; - uint32_t clidr; + uint64_t clidr; uint64_t mp_affinity; /* MP ID without feature bits */ /* The elements of this array are the CCSIDR values for each cache, * in the order L1DCache, L1ICache, L2DCache, L2ICache, etc. @@ -1736,6 +1736,37 @@ FIELD(V7M_FPCCR, ASPEN, 31, 1) /* * System register ID fields. */ +FIELD(CLIDR_EL1, CTYPE1, 0, 3) +FIELD(CLIDR_EL1, CTYPE2, 3, 3) +FIELD(CLIDR_EL1, CTYPE3, 6, 3) +FIELD(CLIDR_EL1, CTYPE4, 9, 3) +FIELD(CLIDR_EL1, CTYPE5, 12, 3) +FIELD(CLIDR_EL1, CTYPE6, 15, 3) +FIELD(CLIDR_EL1, CTYPE7, 18, 3) +FIELD(CLIDR_EL1, LOUIS, 21, 3) +FIELD(CLIDR_EL1, LOC, 24, 3) +FIELD(CLIDR_EL1, LOUU, 27, 3) +FIELD(CLIDR_EL1, ICB, 30, 3) + +/* When FEAT_CCIDX is implemented */ +FIELD(CCSIDR_EL1, CCIDX_LINESIZE, 0, 3) +FIELD(CCSIDR_EL1, CCIDX_ASSOCIATIVITY, 3, 21) +FIELD(CCSIDR_EL1, CCIDX_NUMSETS, 32, 24) + +/* When FEAT_CCIDX is not implemented */ +FIELD(CCSIDR_EL1, LINESIZE, 0, 3) +FIELD(CCSIDR_EL1, ASSOCIATIVITY, 3, 10) +FIELD(CCSIDR_EL1, NUMSETS, 13, 15) + +FIELD(CTR_EL0, IMINLINE, 0, 4) +FIELD(CTR_EL0, L1IP, 14, 2) +FIELD(CTR_EL0, DMINLINE, 16, 4) +FIELD(CTR_EL0, ERG, 20, 4) +FIELD(CTR_EL0, CWG, 24, 4) +FIELD(CTR_EL0, IDC, 28, 1) +FIELD(CTR_EL0, DIC, 29, 1) +FIELD(CTR_EL0, TMINLINE, 32, 6) + FIELD(MIDR_EL1, REVISION, 0, 4) FIELD(MIDR_EL1, PARTNUM, 4, 12) FIELD(MIDR_EL1, ARCHITECTURE, 16, 4) @@ -1799,6 +1830,8 @@ FIELD(ID_ISAR6, DP, 4, 4) FIELD(ID_ISAR6, FHM, 8, 4) FIELD(ID_ISAR6, SB, 12, 4) FIELD(ID_ISAR6, SPECRES, 16, 4) +FIELD(ID_ISAR6, BF16, 20, 4) +FIELD(ID_ISAR6, I8MM, 24, 4) FIELD(ID_MMFR0, VMSA, 0, 4) FIELD(ID_MMFR0, PMSA, 4, 4) @@ -1809,6 +1842,24 @@ FIELD(ID_MMFR0, AUXREG, 20, 4) FIELD(ID_MMFR0, FCSE, 24, 4) FIELD(ID_MMFR0, INNERSHR, 28, 4) +FIELD(ID_MMFR1, L1HVDVA, 0, 4) +FIELD(ID_MMFR1, L1UNIVA, 4, 4) +FIELD(ID_MMFR1, L1HVDSW, 8, 4) +FIELD(ID_MMFR1, L1UNISW, 12, 4) +FIELD(ID_MMFR1, L1HVD, 16, 4) +FIELD(ID_MMFR1, L1UNI, 20, 4) +FIELD(ID_MMFR1, L1TSTCLN, 24, 4) +FIELD(ID_MMFR1, BPRED, 28, 4) + +FIELD(ID_MMFR2, L1HVDFG, 0, 4) +FIELD(ID_MMFR2, L1HVDBG, 4, 4) +FIELD(ID_MMFR2, L1HVDRNG, 8, 4) +FIELD(ID_MMFR2, HVDTLB, 12, 4) +FIELD(ID_MMFR2, UNITLB, 16, 4) +FIELD(ID_MMFR2, MEMBARR, 20, 4) +FIELD(ID_MMFR2, WFISTALL, 24, 4) +FIELD(ID_MMFR2, HWACCFLG, 28, 4) + FIELD(ID_MMFR3, CMAINTVA, 0, 4) FIELD(ID_MMFR3, CMAINTSW, 4, 4) FIELD(ID_MMFR3, BPMAINT, 8, 4) @@ -1827,6 +1878,8 @@ FIELD(ID_MMFR4, LSM, 20, 4) FIELD(ID_MMFR4, CCIDX, 24, 4) FIELD(ID_MMFR4, EVT, 28, 4) +FIELD(ID_MMFR5, ETS, 0, 4) + FIELD(ID_PFR0, STATE0, 0, 4) FIELD(ID_PFR0, STATE1, 4, 4) FIELD(ID_PFR0, STATE2, 8, 4) @@ -1845,6 +1898,10 @@ FIELD(ID_PFR1, SEC_FRAC, 20, 4) FIELD(ID_PFR1, VIRT_FRAC, 24, 4) FIELD(ID_PFR1, GIC, 28, 4) +FIELD(ID_PFR2, CSV3, 0, 4) +FIELD(ID_PFR2, SSBS, 4, 4) +FIELD(ID_PFR2, RAS_FRAC, 8, 4) + FIELD(ID_AA64ISAR0, AES, 4, 4) FIELD(ID_AA64ISAR0, SHA1, 8, 4) FIELD(ID_AA64ISAR0, SHA2, 12, 4) @@ -1871,6 +1928,9 @@ FIELD(ID_AA64ISAR1, GPI, 28, 4) FIELD(ID_AA64ISAR1, FRINTTS, 32, 4) FIELD(ID_AA64ISAR1, SB, 36, 4) FIELD(ID_AA64ISAR1, SPECRES, 40, 4) +FIELD(ID_AA64ISAR1, BF16, 44, 4) +FIELD(ID_AA64ISAR1, DGH, 48, 4) +FIELD(ID_AA64ISAR1, I8MM, 52, 4) FIELD(ID_AA64PFR0, EL0, 0, 4) FIELD(ID_AA64PFR0, EL1, 4, 4) @@ -1881,11 +1941,18 @@ FIELD(ID_AA64PFR0, ADVSIMD, 20, 4) FIELD(ID_AA64PFR0, GIC, 24, 4) FIELD(ID_AA64PFR0, RAS, 28, 4) FIELD(ID_AA64PFR0, SVE, 32, 4) +FIELD(ID_AA64PFR0, SEL2, 36, 4) +FIELD(ID_AA64PFR0, MPAM, 40, 4) +FIELD(ID_AA64PFR0, AMU, 44, 4) +FIELD(ID_AA64PFR0, DIT, 48, 4) +FIELD(ID_AA64PFR0, CSV2, 56, 4) +FIELD(ID_AA64PFR0, CSV3, 60, 4) FIELD(ID_AA64PFR1, BT, 0, 4) -FIELD(ID_AA64PFR1, SBSS, 4, 4) +FIELD(ID_AA64PFR1, SSBS, 4, 4) FIELD(ID_AA64PFR1, MTE, 8, 4) FIELD(ID_AA64PFR1, RAS_FRAC, 12, 4) +FIELD(ID_AA64PFR1, MPAM_FRAC, 16, 4) FIELD(ID_AA64MMFR0, PARANGE, 0, 4) FIELD(ID_AA64MMFR0, ASIDBITS, 4, 4) @@ -1899,6 +1966,8 @@ FIELD(ID_AA64MMFR0, TGRAN16_2, 32, 4) FIELD(ID_AA64MMFR0, TGRAN64_2, 36, 4) FIELD(ID_AA64MMFR0, TGRAN4_2, 40, 4) FIELD(ID_AA64MMFR0, EXS, 44, 4) +FIELD(ID_AA64MMFR0, FGT, 56, 4) +FIELD(ID_AA64MMFR0, ECV, 60, 4) FIELD(ID_AA64MMFR1, HAFDBS, 0, 4) FIELD(ID_AA64MMFR1, VMIDBITS, 4, 4) @@ -1908,6 +1977,8 @@ FIELD(ID_AA64MMFR1, LO, 16, 4) FIELD(ID_AA64MMFR1, PAN, 20, 4) FIELD(ID_AA64MMFR1, SPECSEI, 24, 4) FIELD(ID_AA64MMFR1, XNX, 28, 4) +FIELD(ID_AA64MMFR1, TWED, 32, 4) +FIELD(ID_AA64MMFR1, ETS, 36, 4) FIELD(ID_AA64MMFR2, CNP, 0, 4) FIELD(ID_AA64MMFR2, UAO, 4, 4) @@ -1934,6 +2005,7 @@ FIELD(ID_AA64DFR0, CTX_CMPS, 28, 4) FIELD(ID_AA64DFR0, PMSVER, 32, 4) FIELD(ID_AA64DFR0, DOUBLELOCK, 36, 4) FIELD(ID_AA64DFR0, TRACEFILT, 40, 4) +FIELD(ID_AA64DFR0, MTPMU, 48, 4) FIELD(ID_DFR0, COPDBG, 0, 4) FIELD(ID_DFR0, COPSDBG, 4, 4) @@ -1944,6 +2016,8 @@ FIELD(ID_DFR0, MPROFDBG, 20, 4) FIELD(ID_DFR0, PERFMON, 24, 4) FIELD(ID_DFR0, TRACEFILT, 28, 4) +FIELD(ID_DFR1, MTPMU, 0, 4) + FIELD(DBGDIDR, SE_IMP, 12, 1) FIELD(DBGDIDR, NSUHD_IMP, 14, 1) FIELD(DBGDIDR, VERSION, 16, 4) @@ -3936,6 +4010,11 @@ static inline bool isar_feature_aa64_uao(const ARMISARegisters *id) return FIELD_EX64(id->id_aa64mmfr2, ID_AA64MMFR2, UAO) != 0; } +static inline bool isar_feature_aa64_st(const ARMISARegisters *id) +{ + return FIELD_EX64(id->id_aa64mmfr2, ID_AA64MMFR2, ST) != 0; +} + static inline bool isar_feature_aa64_bti(const ARMISARegisters *id) { return FIELD_EX64(id->id_aa64pfr1, ID_AA64PFR1, BT) != 0; diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index 7cf9fc4..da24f94 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -669,6 +669,7 @@ static void aarch64_max_initfn(Object *obj) t = cpu->isar.id_aa64mmfr2; t = FIELD_DP64(t, ID_AA64MMFR2, UAO, 1); t = FIELD_DP64(t, ID_AA64MMFR2, CNP, 1); /* TTCNP */ + t = FIELD_DP64(t, ID_AA64MMFR2, ST, 1); /* TTST */ cpu->isar.id_aa64mmfr2 = t; /* Replicate the same data to the 32-bit id registers. */ diff --git a/target/arm/helper.c b/target/arm/helper.c index d077dd9..5ab3f5a 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -10842,7 +10842,7 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va, { uint64_t tcr = regime_tcr(env, mmu_idx)->raw_tcr; bool epd, hpd, using16k, using64k; - int select, tsz, tbi; + int select, tsz, tbi, max_tsz; if (!regime_has_2_ranges(mmu_idx)) { select = 0; @@ -10877,7 +10877,14 @@ ARMVAParameters aa64_va_parameters(CPUARMState *env, uint64_t va, hpd = extract64(tcr, 42, 1); } } - tsz = MIN(tsz, 39); /* TODO: ARMv8.4-TTST */ + + if (cpu_isar_feature(aa64_st, env_archcpu(env))) { + max_tsz = 48 - using64k; + } else { + max_tsz = 39; + } + + tsz = MIN(tsz, max_tsz); tsz = MAX(tsz, 16); /* TODO: ARMv8.2-LVA */ /* Present TBI as a composite with TBID. */ @@ -11096,6 +11103,10 @@ static bool get_phys_addr_lpae(CPUARMState *env, uint64_t address, if (!aarch64 || stride == 9) { /* AArch32 or 4KB pages */ startlevel = 2 - sl0; + + if (cpu_isar_feature(aa64_st, cpu)) { + startlevel &= 3; + } } else { /* 16KB or 64KB pages */ startlevel = 3 - sl0; diff --git a/target/arm/translate.c b/target/arm/translate.c index f5acd32..528b93d 100644 --- a/target/arm/translate.c +++ b/target/arm/translate.c @@ -5282,7 +5282,14 @@ static bool valid_cp(DisasContext *s, int cp) * only cp14 and cp15 are valid, and other values aren't considered * to be in the coprocessor-instruction space at all. v8M still * permits coprocessors 0..7. + * For XScale, we must not decode the XScale cp0, cp1 space as + * a standard coprocessor insn, because we want to fall through to + * the legacy disas_xscale_insn() decoder after decodetree is done. */ + if (arm_dc_feature(s, ARM_FEATURE_XSCALE) && (cp == 0 || cp == 1)) { + return false; + } + if (arm_dc_feature(s, ARM_FEATURE_V8) && !arm_dc_feature(s, ARM_FEATURE_M)) { return cp >= 14; diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 6a67c53..0b5467f 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -134,7 +134,9 @@ qtests_sparc64 = \ ['prom-env-test', 'boot-serial-test'] qtests_npcm7xx = \ - ['npcm7xx_gpio-test', + ['npcm7xx_adc-test', + 'npcm7xx_gpio-test', + 'npcm7xx_pwm-test', 'npcm7xx_rng-test', 'npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test'] diff --git a/tests/qtest/npcm7xx_adc-test.c b/tests/qtest/npcm7xx_adc-test.c new file mode 100644 index 0000000..f029706 --- /dev/null +++ b/tests/qtest/npcm7xx_adc-test.c @@ -0,0 +1,377 @@ +/* + * QTests for Nuvoton NPCM7xx ADCModules. + * + * Copyright 2020 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 "qemu/bitops.h" +#include "qemu/timer.h" +#include "libqos/libqtest.h" +#include "qapi/qmp/qdict.h" + +#define REF_HZ (25000000) + +#define CON_OFFSET 0x0 +#define DATA_OFFSET 0x4 + +#define NUM_INPUTS 8 +#define DEFAULT_IREF 2000000 +#define CONV_CYCLES 20 +#define RESET_CYCLES 10 +#define R0_INPUT 500000 +#define R1_INPUT 1500000 +#define MAX_RESULT 1023 + +#define DEFAULT_CLKDIV 5 + +#define FUSE_ARRAY_BA 0xf018a000 +#define FCTL_OFFSET 0x14 +#define FST_OFFSET 0x0 +#define FADDR_OFFSET 0x4 +#define FDATA_OFFSET 0x8 +#define ADC_CALIB_ADDR 24 +#define FUSE_READ 0x2 + +/* Register field definitions. */ +#define CON_MUX(rv) ((rv) << 24) +#define CON_INT_EN BIT(21) +#define CON_REFSEL BIT(19) +#define CON_INT BIT(18) +#define CON_EN BIT(17) +#define CON_RST BIT(16) +#define CON_CONV BIT(14) +#define CON_DIV(rv) extract32(rv, 1, 8) + +#define FST_RDST BIT(1) +#define FDATA_MASK 0xff + +#define MAX_ERROR 10000 +#define MIN_CALIB_INPUT 100000 +#define MAX_CALIB_INPUT 1800000 + +static const uint32_t input_list[] = { + 100000, + 500000, + 1000000, + 1500000, + 1800000, + 2000000, +}; + +static const uint32_t vref_list[] = { + 2000000, + 2200000, + 2500000, +}; + +static const uint32_t iref_list[] = { + 1800000, + 1900000, + 2000000, + 2100000, + 2200000, +}; + +static const uint32_t div_list[] = {0, 1, 3, 7, 15}; + +typedef struct ADC { + int irq; + uint64_t base_addr; +} ADC; + +ADC adc = { + .irq = 0, + .base_addr = 0xf000c000 +}; + +static uint32_t adc_read_con(QTestState *qts, const ADC *adc) +{ + return qtest_readl(qts, adc->base_addr + CON_OFFSET); +} + +static void adc_write_con(QTestState *qts, const ADC *adc, uint32_t value) +{ + qtest_writel(qts, adc->base_addr + CON_OFFSET, value); +} + +static uint32_t adc_read_data(QTestState *qts, const ADC *adc) +{ + return qtest_readl(qts, adc->base_addr + DATA_OFFSET); +} + +static uint32_t adc_calibrate(uint32_t measured, uint32_t *rv) +{ + return R0_INPUT + (R1_INPUT - R0_INPUT) * (int32_t)(measured - rv[0]) + / (int32_t)(rv[1] - rv[0]); +} + +static void adc_qom_set(QTestState *qts, const ADC *adc, + const char *name, uint32_t value) +{ + QDict *response; + const char *path = "/machine/soc/adc"; + + g_test_message("Setting properties %s of %s with value %u", + name, path, value); + response = qtest_qmp(qts, "{ 'execute': 'qom-set'," + " 'arguments': { 'path': %s, 'property': %s, 'value': %u}}", + path, name, value); + /* The qom set message returns successfully. */ + g_assert_true(qdict_haskey(response, "return")); +} + +static void adc_write_input(QTestState *qts, const ADC *adc, + uint32_t index, uint32_t value) +{ + char name[100]; + + sprintf(name, "adci[%u]", index); + adc_qom_set(qts, adc, name, value); +} + +static void adc_write_vref(QTestState *qts, const ADC *adc, uint32_t value) +{ + adc_qom_set(qts, adc, "vref", value); +} + +static uint32_t adc_calculate_output(uint32_t input, uint32_t ref) +{ + uint32_t output; + + g_assert_cmpuint(input, <=, ref); + output = (input * (MAX_RESULT + 1)) / ref; + if (output > MAX_RESULT) { + output = MAX_RESULT; + } + + return output; +} + +static uint32_t adc_prescaler(QTestState *qts, const ADC *adc) +{ + uint32_t div = extract32(adc_read_con(qts, adc), 1, 8); + + return 2 * (div + 1); +} + +static int64_t adc_calculate_steps(uint32_t cycles, uint32_t prescale, + uint32_t clkdiv) +{ + return (NANOSECONDS_PER_SECOND / (REF_HZ >> clkdiv)) * cycles * prescale; +} + +static void adc_wait_conv_finished(QTestState *qts, const ADC *adc, + uint32_t clkdiv) +{ + uint32_t prescaler = adc_prescaler(qts, adc); + + /* + * ADC should takes roughly 20 cycles to convert one sample. So we assert it + * should take 10~30 cycles here. + */ + qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES / 2, prescaler, + clkdiv)); + /* ADC is still converting. */ + g_assert_true(adc_read_con(qts, adc) & CON_CONV); + qtest_clock_step(qts, adc_calculate_steps(CONV_CYCLES, prescaler, clkdiv)); + /* ADC has finished conversion. */ + g_assert_false(adc_read_con(qts, adc) & CON_CONV); +} + +/* Check ADC can be reset to default value. */ +static void test_init(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + + QTestState *qts = qtest_init("-machine quanta-gsj"); + adc_write_con(qts, adc, CON_REFSEL | CON_INT); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_REFSEL); + qtest_quit(qts); +} + +/* Check ADC can convert from an internal reference. */ +static void test_convert_internal(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + for (index = 0; index < NUM_INPUTS; ++index) { + for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { + input = input_list[i]; + expected_output = adc_calculate_output(input, DEFAULT_IREF); + + adc_write_input(qts, adc, index, input); + adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | + CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | + CON_REFSEL | CON_EN); + g_assert_false(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + } + } + + qtest_quit(qts); +} + +/* Check ADC can convert from an external reference. */ +static void test_convert_external(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, vref, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + + for (index = 0; index < NUM_INPUTS; ++index) { + for (size_t i = 0; i < ARRAY_SIZE(input_list); ++i) { + for (size_t j = 0; j < ARRAY_SIZE(vref_list); ++j) { + input = input_list[i]; + vref = vref_list[j]; + expected_output = adc_calculate_output(input, vref); + + adc_write_input(qts, adc, index, input); + adc_write_vref(qts, adc, vref); + adc_write_con(qts, adc, CON_MUX(index) | CON_INT | CON_EN | + CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, + CON_MUX(index) | CON_EN); + g_assert_false(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + } + } + } + + qtest_quit(qts); +} + +/* Check ADC interrupt files if and only if CON_INT_EN is set. */ +static void test_interrupt(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + uint32_t index, input, output, expected_output; + QTestState *qts = qtest_init("-machine quanta-gsj"); + + index = 1; + input = input_list[1]; + expected_output = adc_calculate_output(input, DEFAULT_IREF); + + qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); + adc_write_input(qts, adc, index, input); + g_assert_false(qtest_get_irq(qts, adc->irq)); + adc_write_con(qts, adc, CON_MUX(index) | CON_INT_EN | CON_REFSEL | CON_INT + | CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, CON_MUX(index) | CON_INT_EN + | CON_REFSEL | CON_INT | CON_EN); + g_assert_true(qtest_get_irq(qts, adc->irq)); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + + qtest_quit(qts); +} + +/* Check ADC is reset after setting ADC_RST for 10 ADC cycles. */ +static void test_reset(gconstpointer adc_p) +{ + const ADC *adc = adc_p; + QTestState *qts = qtest_init("-machine quanta-gsj"); + + for (size_t i = 0; i < ARRAY_SIZE(div_list); ++i) { + uint32_t div = div_list[i]; + + adc_write_con(qts, adc, CON_INT | CON_EN | CON_RST | CON_DIV(div)); + qtest_clock_step(qts, adc_calculate_steps(RESET_CYCLES, + adc_prescaler(qts, adc), DEFAULT_CLKDIV)); + g_assert_false(adc_read_con(qts, adc) & CON_EN); + } + qtest_quit(qts); +} + +/* Check ADC Calibration works as desired. */ +static void test_calibrate(gconstpointer adc_p) +{ + int i, j; + const ADC *adc = adc_p; + + for (j = 0; j < ARRAY_SIZE(iref_list); ++j) { + uint32_t iref = iref_list[j]; + uint32_t expected_rv[] = { + adc_calculate_output(R0_INPUT, iref), + adc_calculate_output(R1_INPUT, iref), + }; + char buf[100]; + QTestState *qts; + + sprintf(buf, "-machine quanta-gsj -global npcm7xx-adc.iref=%u", iref); + qts = qtest_init(buf); + + /* Check the converted value is correct using the calibration value. */ + for (i = 0; i < ARRAY_SIZE(input_list); ++i) { + uint32_t input; + uint32_t output; + uint32_t expected_output; + uint32_t calibrated_voltage; + uint32_t index = 0; + + input = input_list[i]; + /* Calibration only works for input range 0.1V ~ 1.8V. */ + if (input < MIN_CALIB_INPUT || input > MAX_CALIB_INPUT) { + continue; + } + expected_output = adc_calculate_output(input, iref); + + adc_write_input(qts, adc, index, input); + adc_write_con(qts, adc, CON_MUX(index) | CON_REFSEL | CON_INT | + CON_EN | CON_CONV); + adc_wait_conv_finished(qts, adc, DEFAULT_CLKDIV); + g_assert_cmphex(adc_read_con(qts, adc), ==, + CON_REFSEL | CON_MUX(index) | CON_EN); + output = adc_read_data(qts, adc); + g_assert_cmpuint(output, ==, expected_output); + + calibrated_voltage = adc_calibrate(output, expected_rv); + g_assert_cmpuint(calibrated_voltage, >, input - MAX_ERROR); + g_assert_cmpuint(calibrated_voltage, <, input + MAX_ERROR); + } + + qtest_quit(qts); + } +} + +static void adc_add_test(const char *name, const ADC* wd, + GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf("npcm7xx_adc/%s", name); + qtest_add_data_func(full_name, wd, fn); +} +#define add_test(name, td) adc_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + add_test(init, &adc); + add_test(convert_internal, &adc); + add_test(convert_external, &adc); + add_test(interrupt, &adc); + add_test(reset, &adc); + add_test(calibrate, &adc); + + return g_test_run(); +} diff --git a/tests/qtest/npcm7xx_pwm-test.c b/tests/qtest/npcm7xx_pwm-test.c new file mode 100644 index 0000000..33fbdf5 --- /dev/null +++ b/tests/qtest/npcm7xx_pwm-test.c @@ -0,0 +1,490 @@ +/* + * QTests for Nuvoton NPCM7xx PWM Modules. + * + * Copyright 2020 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 "qemu/bitops.h" +#include "libqos/libqtest.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qnum.h" + +#define REF_HZ 25000000 + +/* Register field definitions. */ +#define CH_EN BIT(0) +#define CH_INV BIT(2) +#define CH_MOD BIT(3) + +/* Registers shared between all PWMs in a module */ +#define PPR 0x00 +#define CSR 0x04 +#define PCR 0x08 +#define PIER 0x3c +#define PIIR 0x40 + +/* CLK module related */ +#define CLK_BA 0xf0801000 +#define CLKSEL 0x04 +#define CLKDIV1 0x08 +#define CLKDIV2 0x2c +#define PLLCON0 0x0c +#define PLLCON1 0x10 +#define PLL_INDV(rv) extract32((rv), 0, 6) +#define PLL_FBDV(rv) extract32((rv), 16, 12) +#define PLL_OTDV1(rv) extract32((rv), 8, 3) +#define PLL_OTDV2(rv) extract32((rv), 13, 3) +#define APB3CKDIV(rv) extract32((rv), 28, 2) +#define CLK2CKDIV(rv) extract32((rv), 0, 1) +#define CLK4CKDIV(rv) extract32((rv), 26, 2) +#define CPUCKSEL(rv) extract32((rv), 0, 2) + +#define MAX_DUTY 1000000 + +typedef struct PWMModule { + int irq; + uint64_t base_addr; +} PWMModule; + +typedef struct PWM { + uint32_t cnr_offset; + uint32_t cmr_offset; + uint32_t pdr_offset; + uint32_t pwdr_offset; +} PWM; + +typedef struct TestData { + const PWMModule *module; + const PWM *pwm; +} TestData; + +static const PWMModule pwm_module_list[] = { + { + .irq = 93, + .base_addr = 0xf0103000 + }, + { + .irq = 94, + .base_addr = 0xf0104000 + } +}; + +static const PWM pwm_list[] = { + { + .cnr_offset = 0x0c, + .cmr_offset = 0x10, + .pdr_offset = 0x14, + .pwdr_offset = 0x44, + }, + { + .cnr_offset = 0x18, + .cmr_offset = 0x1c, + .pdr_offset = 0x20, + .pwdr_offset = 0x48, + }, + { + .cnr_offset = 0x24, + .cmr_offset = 0x28, + .pdr_offset = 0x2c, + .pwdr_offset = 0x4c, + }, + { + .cnr_offset = 0x30, + .cmr_offset = 0x34, + .pdr_offset = 0x38, + .pwdr_offset = 0x50, + }, +}; + +static const int ppr_base[] = { 0, 0, 8, 8 }; +static const int csr_base[] = { 0, 4, 8, 12 }; +static const int pcr_base[] = { 0, 8, 12, 16 }; + +static const uint32_t ppr_list[] = { + 0, + 1, + 10, + 100, + 255, /* Max possible value. */ +}; + +static const uint32_t csr_list[] = { + 0, + 1, + 2, + 3, + 4, /* Max possible value. */ +}; + +static const uint32_t cnr_list[] = { + 0, + 1, + 50, + 100, + 150, + 200, + 1000, + 10000, + 65535, /* Max possible value. */ +}; + +static const uint32_t cmr_list[] = { + 0, + 1, + 10, + 50, + 100, + 150, + 200, + 1000, + 10000, + 65535, /* Max possible value. */ +}; + +/* Returns the index of the PWM module. */ +static int pwm_module_index(const PWMModule *module) +{ + ptrdiff_t diff = module - pwm_module_list; + + g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list)); + + return diff; +} + +/* Returns the index of the PWM entry. */ +static int pwm_index(const PWM *pwm) +{ + ptrdiff_t diff = pwm - pwm_list; + + g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_list)); + + return diff; +} + +static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name) +{ + QDict *response; + + g_test_message("Getting properties %s from %s", name, path); + response = qtest_qmp(qts, "{ 'execute': 'qom-get'," + " 'arguments': { 'path': %s, 'property': %s}}", + path, name); + /* The qom set message returns successfully. */ + g_assert_true(qdict_haskey(response, "return")); + return qnum_get_uint(qobject_to(QNum, qdict_get(response, "return"))); +} + +static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index) +{ + char path[100]; + char name[100]; + + sprintf(path, "/machine/soc/pwm[%d]", module_index); + sprintf(name, "freq[%d]", pwm_index); + + return pwm_qom_get(qts, path, name); +} + +static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index) +{ + char path[100]; + char name[100]; + + sprintf(path, "/machine/soc/pwm[%d]", module_index); + sprintf(name, "duty[%d]", pwm_index); + + return pwm_qom_get(qts, path, name); +} + +static uint32_t get_pll(uint32_t con) +{ + return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con) + * PLL_OTDV2(con)); +} + +static uint64_t read_pclk(QTestState *qts) +{ + uint64_t freq = REF_HZ; + uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL); + uint32_t pllcon; + uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1); + uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2); + + switch (CPUCKSEL(clksel)) { + case 0: + pllcon = qtest_readl(qts, CLK_BA + PLLCON0); + freq = get_pll(pllcon); + break; + case 1: + pllcon = qtest_readl(qts, CLK_BA + PLLCON1); + freq = get_pll(pllcon); + break; + case 2: + break; + case 3: + break; + default: + g_assert_not_reached(); + } + + freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2)); + + return freq; +} + +static uint32_t pwm_selector(uint32_t csr) +{ + switch (csr) { + case 0: + return 2; + case 1: + return 4; + case 2: + return 8; + case 3: + return 16; + case 4: + return 1; + default: + g_assert_not_reached(); + } +} + +static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr, + uint32_t cnr) +{ + return read_pclk(qts) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1)); +} + +static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted) +{ + uint64_t duty; + + if (cnr == 0) { + /* PWM is stopped. */ + duty = 0; + } else if (cmr >= cnr) { + duty = MAX_DUTY; + } else { + duty = MAX_DUTY * (cmr + 1) / (cnr + 1); + } + + if (inverted) { + duty = MAX_DUTY - duty; + } + + return duty; +} + +static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset) +{ + return qtest_readl(qts, td->module->base_addr + offset); +} + +static void pwm_write(QTestState *qts, const TestData *td, unsigned offset, + uint32_t value) +{ + qtest_writel(qts, td->module->base_addr + offset, value); +} + +static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8); +} + +static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_csr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3); +} + +static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td) +{ + return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4); +} + +static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]); +} + +static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td) +{ + return pwm_read(qts, td, td->pwm->cnr_offset); +} + +static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, td->pwm->cnr_offset, value); +} + +static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td) +{ + return pwm_read(qts, td, td->pwm->cmr_offset); +} + +static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value) +{ + pwm_write(qts, td, td->pwm->cmr_offset, value); +} + +/* Check pwm registers can be reset to default value */ +static void test_init(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); + + qtest_quit(qts); +} + +/* One-shot mode should not change frequency and duty cycle. */ +static void test_oneshot(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + uint32_t ppr, csr, pcr; + int i, j; + + pcr = CH_EN; + for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { + ppr = ppr_list[i]; + pwm_write_ppr(qts, td, ppr); + + for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { + csr = csr_list[j]; + pwm_write_csr(qts, td, csr); + pwm_write_pcr(qts, td, pcr); + + g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); + g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0); + } + } + + qtest_quit(qts); +} + +/* In toggle mode, the PWM generates correct outputs. */ +static void test_toggle(gconstpointer test_data) +{ + const TestData *td = test_data; + QTestState *qts = qtest_init("-machine quanta-gsj"); + int module = pwm_module_index(td->module); + int pwm = pwm_index(td->pwm); + uint32_t ppr, csr, pcr, cnr, cmr; + int i, j, k, l; + uint64_t expected_freq, expected_duty; + + pcr = CH_EN | CH_MOD; + for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) { + ppr = ppr_list[i]; + pwm_write_ppr(qts, td, ppr); + + for (j = 0; j < ARRAY_SIZE(csr_list); ++j) { + csr = csr_list[j]; + pwm_write_csr(qts, td, csr); + + for (k = 0; k < ARRAY_SIZE(cnr_list); ++k) { + cnr = cnr_list[k]; + pwm_write_cnr(qts, td, cnr); + + for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) { + cmr = cmr_list[l]; + pwm_write_cmr(qts, td, cmr); + expected_freq = pwm_compute_freq(qts, ppr, csr, cnr); + expected_duty = pwm_compute_duty(cnr, cmr, false); + + pwm_write_pcr(qts, td, pcr); + g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr); + g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr); + g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr); + g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), + ==, expected_duty); + if (expected_duty != 0 && expected_duty != 100) { + /* Duty cycle with 0 or 100 doesn't need frequency. */ + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), + ==, expected_freq); + } + + /* Test inverted mode */ + expected_duty = pwm_compute_duty(cnr, cmr, true); + pwm_write_pcr(qts, td, pcr | CH_INV); + g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV); + g_assert_cmpuint(pwm_get_duty(qts, module, pwm), + ==, expected_duty); + if (expected_duty != 0 && expected_duty != 100) { + /* Duty cycle with 0 or 100 doesn't need frequency. */ + g_assert_cmpuint(pwm_get_freq(qts, module, pwm), + ==, expected_freq); + } + + } + } + } + } + + qtest_quit(qts); +} + +static void pwm_add_test(const char *name, const TestData* td, + GTestDataFunc fn) +{ + g_autofree char *full_name = g_strdup_printf( + "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module), + pwm_index(td->pwm), name); + qtest_add_data_func(full_name, td, fn); +} +#define add_test(name, td) pwm_add_test(#name, td, test_##name) + +int main(int argc, char **argv) +{ + TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)]; + + g_test_init(&argc, &argv, NULL); + + for (int i = 0; i < ARRAY_SIZE(pwm_module_list); ++i) { + for (int j = 0; j < ARRAY_SIZE(pwm_list); ++j) { + TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j]; + + td->module = &pwm_module_list[i]; + td->pwm = &pwm_list[j]; + + add_test(init, td); + add_test(oneshot, td); + add_test(toggle, td); + } + } + + return g_test_run(); +} @@ -1176,8 +1176,9 @@ QemuCocoaView *cocoaView; - (void) openDocumentation: (NSString *) filename { /* Where to look for local files */ - NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"../docs/"}; + NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; NSString *full_file_path; + NSURL *full_file_url; /* iterate thru the possible paths until the file is found */ int index; @@ -1186,7 +1187,9 @@ QemuCocoaView *cocoaView; full_file_path = [full_file_path stringByDeletingLastPathComponent]; full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, path_array[index], filename]; - if ([[NSWorkspace sharedWorkspace] openFile: full_file_path] == YES) { + full_file_url = [NSURL fileURLWithPath: full_file_path + isDirectory: false]; + if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { return; } } |