diff options
Diffstat (limited to 'hw')
-rw-r--r-- | hw/arm/Kconfig | 1 | ||||
-rw-r--r-- | hw/arm/bcm2835_peripherals.c | 15 | ||||
-rw-r--r-- | hw/arm/bcm2836.c | 184 | ||||
-rw-r--r-- | hw/arm/highbank.c | 2 | ||||
-rw-r--r-- | hw/arm/npcm7xx.c | 126 | ||||
-rw-r--r-- | hw/arm/raspi.c | 41 | ||||
-rw-r--r-- | hw/arm/sbsa-ref.c | 23 | ||||
-rw-r--r-- | hw/arm/smmuv3.c | 1 | ||||
-rw-r--r-- | hw/arm/trace-events | 2 | ||||
-rw-r--r-- | hw/arm/xlnx-versal-virt.c | 1 | ||||
-rw-r--r-- | hw/char/pl011.c | 45 | ||||
-rw-r--r-- | hw/char/trace-events | 1 | ||||
-rw-r--r-- | hw/core/clock.c | 6 | ||||
-rw-r--r-- | hw/core/ptimer.c | 4 | ||||
-rw-r--r-- | hw/core/trace-events | 4 | ||||
-rw-r--r-- | hw/gpio/meson.build | 1 | ||||
-rw-r--r-- | hw/gpio/npcm7xx_gpio.c | 424 | ||||
-rw-r--r-- | hw/gpio/trace-events | 7 | ||||
-rw-r--r-- | hw/misc/bcm2835_cprman.c | 808 | ||||
-rw-r--r-- | hw/misc/meson.build | 2 | ||||
-rw-r--r-- | hw/misc/npcm7xx_clk.c | 28 | ||||
-rw-r--r-- | hw/misc/npcm7xx_rng.c | 180 | ||||
-rw-r--r-- | hw/misc/trace-events | 9 | ||||
-rw-r--r-- | hw/timer/armv7m_systick.c | 124 | ||||
-rw-r--r-- | hw/timer/npcm7xx_timer.c | 270 | ||||
-rw-r--r-- | hw/usb/hcd-ehci-sysbus.c | 19 | ||||
-rw-r--r-- | hw/usb/hcd-ehci.h | 1 | ||||
-rw-r--r-- | hw/watchdog/Kconfig | 3 | ||||
-rw-r--r-- | hw/watchdog/meson.build | 1 | ||||
-rw-r--r-- | hw/watchdog/sbsa_gwdt.c | 293 |
30 files changed, 2432 insertions, 194 deletions
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 3c893e0..fdf4464 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -212,6 +212,7 @@ config SBSA_REF select PL031 # RTC select PL061 # GPIO select USB_EHCI_SYSBUS + select WDT_SBSA config SABRELITE bool diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c index 48909a4..dcff134 100644 --- a/hw/arm/bcm2835_peripherals.c +++ b/hw/arm/bcm2835_peripherals.c @@ -121,6 +121,9 @@ static void bcm2835_peripherals_init(Object *obj) /* DWC2 */ object_initialize_child(obj, "dwc2", &s->dwc2, TYPE_DWC2_USB); + /* CPRMAN clock manager */ + object_initialize_child(obj, "cprman", &s->cprman, TYPE_BCM2835_CPRMAN); + object_property_add_const_link(OBJECT(&s->dwc2), "dma-mr", OBJECT(&s->gpu_bus_mr)); } @@ -160,6 +163,15 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp) return; } + /* CPRMAN clock manager */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->cprman), errp)) { + return; + } + memory_region_add_subregion(&s->peri_mr, CPRMAN_OFFSET, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->cprman), 0)); + qdev_connect_clock_in(DEVICE(&s->uart0), "clk", + qdev_get_clock_out(DEVICE(&s->cprman), "uart-out")); + memory_region_add_subregion(&s->peri_mr, ARMCTRL_IC_OFFSET, sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->ic), 0)); sysbus_pass_irq(SYS_BUS_DEVICE(s), SYS_BUS_DEVICE(&s->ic)); @@ -354,8 +366,7 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp) create_unimp(s, &s->txp, "bcm2835-txp", TXP_OFFSET, 0x1000); create_unimp(s, &s->armtmr, "bcm2835-sp804", ARMCTRL_TIMER0_1_OFFSET, 0x40); - create_unimp(s, &s->cprman, "bcm2835-cprman", CPRMAN_OFFSET, 0x1000); - create_unimp(s, &s->a2w, "bcm2835-a2w", A2W_OFFSET, 0x1000); + create_unimp(s, &s->powermgt, "bcm2835-powermgt", PM_OFFSET, 0x114); create_unimp(s, &s->i2s, "bcm2835-i2s", I2S_OFFSET, 0x100); create_unimp(s, &s->smi, "bcm2835-smi", SMI_OFFSET, 0x100); create_unimp(s, &s->spi[0], "bcm2835-spi0", SPI0_OFFSET, 0x20); diff --git a/hw/arm/bcm2836.c b/hw/arm/bcm2836.c index f15cc3b..de7ade2 100644 --- a/hw/arm/bcm2836.c +++ b/hw/arm/bcm2836.c @@ -17,46 +17,45 @@ #include "hw/arm/raspi_platform.h" #include "hw/sysbus.h" -struct BCM283XInfo { +typedef struct BCM283XClass { + /*< private >*/ + DeviceClass parent_class; + /*< public >*/ const char *name; const char *cpu_type; + unsigned core_count; hwaddr peri_base; /* Peripheral base address seen by the CPU */ hwaddr ctrl_base; /* Interrupt controller and mailboxes etc. */ int clusterid; -}; +} BCM283XClass; -static const BCM283XInfo bcm283x_socs[] = { - { - .name = TYPE_BCM2836, - .cpu_type = ARM_CPU_TYPE_NAME("cortex-a7"), - .peri_base = 0x3f000000, - .ctrl_base = 0x40000000, - .clusterid = 0xf, - }, -#ifdef TARGET_AARCH64 - { - .name = TYPE_BCM2837, - .cpu_type = ARM_CPU_TYPE_NAME("cortex-a53"), - .peri_base = 0x3f000000, - .ctrl_base = 0x40000000, - .clusterid = 0x0, - }, -#endif -}; +#define BCM283X_CLASS(klass) \ + OBJECT_CLASS_CHECK(BCM283XClass, (klass), TYPE_BCM283X) +#define BCM283X_GET_CLASS(obj) \ + OBJECT_GET_CLASS(BCM283XClass, (obj), TYPE_BCM283X) + +static Property bcm2836_enabled_cores_property = + DEFINE_PROP_UINT32("enabled-cpus", BCM283XState, enabled_cpus, 0); static void bcm2836_init(Object *obj) { BCM283XState *s = BCM283X(obj); BCM283XClass *bc = BCM283X_GET_CLASS(obj); - const BCM283XInfo *info = bc->info; int n; - for (n = 0; n < BCM283X_NCPUS; n++) { + for (n = 0; n < bc->core_count; n++) { object_initialize_child(obj, "cpu[*]", &s->cpu[n].core, - info->cpu_type); + bc->cpu_type); + } + if (bc->core_count > 1) { + qdev_property_add_static(DEVICE(obj), &bcm2836_enabled_cores_property); + qdev_prop_set_uint32(DEVICE(obj), "enabled-cpus", bc->core_count); } - object_initialize_child(obj, "control", &s->control, TYPE_BCM2836_CONTROL); + if (bc->ctrl_base) { + object_initialize_child(obj, "control", &s->control, + TYPE_BCM2836_CONTROL); + } object_initialize_child(obj, "peripherals", &s->peripherals, TYPE_BCM2835_PERIPHERALS); @@ -66,13 +65,11 @@ static void bcm2836_init(Object *obj) "vcram-size"); } -static void bcm2836_realize(DeviceState *dev, Error **errp) +static bool bcm283x_common_realize(DeviceState *dev, Error **errp) { BCM283XState *s = BCM283X(dev); BCM283XClass *bc = BCM283X_GET_CLASS(dev); - const BCM283XInfo *info = bc->info; Object *obj; - int n; /* common peripherals from bcm2835 */ @@ -81,21 +78,52 @@ static void bcm2836_realize(DeviceState *dev, Error **errp) object_property_add_const_link(OBJECT(&s->peripherals), "ram", obj); if (!sysbus_realize(SYS_BUS_DEVICE(&s->peripherals), errp)) { - return; + return false; } object_property_add_alias(OBJECT(s), "sd-bus", OBJECT(&s->peripherals), "sd-bus"); sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->peripherals), 0, - info->peri_base, 1); + bc->peri_base, 1); + return true; +} + +static void bcm2835_realize(DeviceState *dev, Error **errp) +{ + BCM283XState *s = BCM283X(dev); + + if (!bcm283x_common_realize(dev, errp)) { + return; + } + + if (!qdev_realize(DEVICE(&s->cpu[0].core), NULL, errp)) { + return; + } + + /* Connect irq/fiq outputs from the interrupt controller. */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 0, + qdev_get_gpio_in(DEVICE(&s->cpu[0].core), ARM_CPU_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 1, + qdev_get_gpio_in(DEVICE(&s->cpu[0].core), ARM_CPU_FIQ)); +} + +static void bcm2836_realize(DeviceState *dev, Error **errp) +{ + BCM283XState *s = BCM283X(dev); + BCM283XClass *bc = BCM283X_GET_CLASS(dev); + int n; + + if (!bcm283x_common_realize(dev, errp)) { + return; + } /* bcm2836 interrupt controller (and mailboxes, etc.) */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->control), errp)) { return; } - sysbus_mmio_map(SYS_BUS_DEVICE(&s->control), 0, info->ctrl_base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->control), 0, bc->ctrl_base); sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 0, qdev_get_gpio_in_named(DEVICE(&s->control), "gpu-irq", 0)); @@ -104,11 +132,11 @@ static void bcm2836_realize(DeviceState *dev, Error **errp) for (n = 0; n < BCM283X_NCPUS; n++) { /* TODO: this should be converted to a property of ARM_CPU */ - s->cpu[n].core.mp_affinity = (info->clusterid << 8) | n; + s->cpu[n].core.mp_affinity = (bc->clusterid << 8) | n; /* set periphbase/CBAR value for CPU-local registers */ if (!object_property_set_int(OBJECT(&s->cpu[n].core), "reset-cbar", - info->peri_base, errp)) { + bc->peri_base, errp)) { return; } @@ -142,47 +170,77 @@ static void bcm2836_realize(DeviceState *dev, Error **errp) } } -static Property bcm2836_props[] = { - DEFINE_PROP_UINT32("enabled-cpus", BCM283XState, enabled_cpus, - BCM283X_NCPUS), - DEFINE_PROP_END_OF_LIST() -}; - static void bcm283x_class_init(ObjectClass *oc, void *data) { DeviceClass *dc = DEVICE_CLASS(oc); - BCM283XClass *bc = BCM283X_CLASS(oc); - bc->info = data; - dc->realize = bcm2836_realize; - device_class_set_props(dc, bcm2836_props); /* Reason: Must be wired up in code (see raspi_init() function) */ dc->user_creatable = false; } -static const TypeInfo bcm283x_type_info = { - .name = TYPE_BCM283X, - .parent = TYPE_DEVICE, - .instance_size = sizeof(BCM283XState), - .instance_init = bcm2836_init, - .class_size = sizeof(BCM283XClass), - .abstract = true, +static void bcm2835_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + BCM283XClass *bc = BCM283X_CLASS(oc); + + bc->cpu_type = ARM_CPU_TYPE_NAME("arm1176"); + bc->core_count = 1; + bc->peri_base = 0x20000000; + dc->realize = bcm2835_realize; +}; + +static void bcm2836_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + BCM283XClass *bc = BCM283X_CLASS(oc); + + bc->cpu_type = ARM_CPU_TYPE_NAME("cortex-a7"); + bc->core_count = BCM283X_NCPUS; + bc->peri_base = 0x3f000000; + bc->ctrl_base = 0x40000000; + bc->clusterid = 0xf; + dc->realize = bcm2836_realize; }; -static void bcm2836_register_types(void) +#ifdef TARGET_AARCH64 +static void bcm2837_class_init(ObjectClass *oc, void *data) { - int i; - - type_register_static(&bcm283x_type_info); - for (i = 0; i < ARRAY_SIZE(bcm283x_socs); i++) { - TypeInfo ti = { - .name = bcm283x_socs[i].name, - .parent = TYPE_BCM283X, - .class_init = bcm283x_class_init, - .class_data = (void *) &bcm283x_socs[i], - }; - type_register(&ti); + DeviceClass *dc = DEVICE_CLASS(oc); + BCM283XClass *bc = BCM283X_CLASS(oc); + + bc->cpu_type = ARM_CPU_TYPE_NAME("cortex-a53"); + bc->core_count = BCM283X_NCPUS; + bc->peri_base = 0x3f000000; + bc->ctrl_base = 0x40000000; + bc->clusterid = 0x0; + dc->realize = bcm2836_realize; +}; +#endif + +static const TypeInfo bcm283x_types[] = { + { + .name = TYPE_BCM2835, + .parent = TYPE_BCM283X, + .class_init = bcm2835_class_init, + }, { + .name = TYPE_BCM2836, + .parent = TYPE_BCM283X, + .class_init = bcm2836_class_init, +#ifdef TARGET_AARCH64 + }, { + .name = TYPE_BCM2837, + .parent = TYPE_BCM283X, + .class_init = bcm2837_class_init, +#endif + }, { + .name = TYPE_BCM283X, + .parent = TYPE_DEVICE, + .instance_size = sizeof(BCM283XState), + .instance_init = bcm2836_init, + .class_size = sizeof(BCM283XClass), + .class_init = bcm283x_class_init, + .abstract = true, } -} +}; -type_init(bcm2836_register_types) +DEFINE_TYPES(bcm283x_types) diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c index da0510d..f710878 100644 --- a/hw/arm/highbank.c +++ b/hw/arm/highbank.c @@ -92,10 +92,12 @@ static void hb_reset_secondary(ARMCPU *cpu, const struct arm_boot_info *info) address_space_stl_notdirty(&address_space_memory, SMP_BOOT_REG + 0x30, 0, MEMTXATTRS_UNSPECIFIED, NULL); + /* fallthrough */ case 3: address_space_stl_notdirty(&address_space_memory, SMP_BOOT_REG + 0x20, 0, MEMTXATTRS_UNSPECIFIED, NULL); + /* fallthrough */ case 2: address_space_stl_notdirty(&address_space_memory, SMP_BOOT_REG + 0x10, 0, diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 037f3a2..47e2b6f 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -44,6 +44,11 @@ #define NPCM7XX_GCR_BA (0xf0800000) #define NPCM7XX_CLK_BA (0xf0801000) #define NPCM7XX_MC_BA (0xf0824000) +#define NPCM7XX_RNG_BA (0xf000b000) + +/* USB Host modules */ +#define NPCM7XX_EHCI_BA (0xf0806000) +#define NPCM7XX_OHCI_BA (0xf0807000) /* Internal AHB SRAM */ #define NPCM7XX_RAM3_BA (0xc0008000) @@ -86,6 +91,19 @@ enum NPCM7xxInterrupt { NPCM7XX_TIMER12_IRQ, NPCM7XX_TIMER13_IRQ, NPCM7XX_TIMER14_IRQ, + NPCM7XX_WDG0_IRQ = 47, /* Timer Module 0 Watchdog */ + NPCM7XX_WDG1_IRQ, /* Timer Module 1 Watchdog */ + NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */ + NPCM7XX_EHCI_IRQ = 61, + NPCM7XX_OHCI_IRQ = 62, + NPCM7XX_GPIO0_IRQ = 116, + NPCM7XX_GPIO1_IRQ, + NPCM7XX_GPIO2_IRQ, + NPCM7XX_GPIO3_IRQ, + NPCM7XX_GPIO4_IRQ, + NPCM7XX_GPIO5_IRQ, + NPCM7XX_GPIO6_IRQ, + NPCM7XX_GPIO7_IRQ, }; /* Total number of GIC interrupts, including internal Cortex-A9 interrupts. */ @@ -121,6 +139,55 @@ static const hwaddr npcm7xx_fiu3_flash_addr[] = { }; static const struct { + hwaddr regs_addr; + uint32_t unconnected_pins; + uint32_t reset_pu; + uint32_t reset_pd; + uint32_t reset_osrc; + uint32_t reset_odsc; +} npcm7xx_gpio[] = { + { + .regs_addr = 0xf0010000, + .reset_pu = 0xff03ffff, + .reset_pd = 0x00fc0000, + }, { + .regs_addr = 0xf0011000, + .unconnected_pins = 0x0000001e, + .reset_pu = 0xfefffe07, + .reset_pd = 0x010001e0, + }, { + .regs_addr = 0xf0012000, + .reset_pu = 0x780fffff, + .reset_pd = 0x07f00000, + .reset_odsc = 0x00700000, + }, { + .regs_addr = 0xf0013000, + .reset_pu = 0x00fc0000, + .reset_pd = 0xff000000, + }, { + .regs_addr = 0xf0014000, + .reset_pu = 0xffffffff, + }, { + .regs_addr = 0xf0015000, + .reset_pu = 0xbf83f801, + .reset_pd = 0x007c0000, + .reset_osrc = 0x000000f1, + .reset_odsc = 0x3f9f80f1, + }, { + .regs_addr = 0xf0016000, + .reset_pu = 0xfc00f801, + .reset_pd = 0x000007fe, + .reset_odsc = 0x00000800, + }, { + .regs_addr = 0xf0017000, + .unconnected_pins = 0xffffff00, + .reset_pu = 0x0000007f, + .reset_osrc = 0x0000007f, + .reset_odsc = 0x0000007f, + }, +}; + +static const struct { const char *name; hwaddr regs_addr; int cs_count; @@ -253,11 +320,19 @@ static void npcm7xx_init(Object *obj) object_initialize_child(obj, "otp2", &s->fuse_array, TYPE_NPCM7XX_FUSE_ARRAY); object_initialize_child(obj, "mc", &s->mc, TYPE_NPCM7XX_MC); + object_initialize_child(obj, "rng", &s->rng, TYPE_NPCM7XX_RNG); for (i = 0; i < ARRAY_SIZE(s->tim); i++) { object_initialize_child(obj, "tim[*]", &s->tim[i], TYPE_NPCM7XX_TIMER); } + for (i = 0; i < ARRAY_SIZE(s->gpio); i++) { + object_initialize_child(obj, "gpio[*]", &s->gpio[i], TYPE_NPCM7XX_GPIO); + } + + object_initialize_child(obj, "ehci", &s->ehci, TYPE_NPCM7XX_EHCI); + object_initialize_child(obj, "ohci", &s->ohci, TYPE_SYSBUS_OHCI); + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu)); for (i = 0; i < ARRAY_SIZE(s->fiu); i++) { object_initialize_child(obj, npcm7xx_fiu[i].name, &s->fiu[i], @@ -353,6 +428,15 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) qemu_irq irq = npcm7xx_irq(s, first_irq + j); sysbus_connect_irq(sbd, j, irq); } + + /* IRQ for watchdogs */ + sysbus_connect_irq(sbd, NPCM7XX_TIMERS_PER_CTRL, + npcm7xx_irq(s, NPCM7XX_WDG0_IRQ + i)); + /* GPIO that connects clk module with watchdog */ + qdev_connect_gpio_out_named(DEVICE(&s->tim[i]), + NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 0, + qdev_get_gpio_in_named(DEVICE(&s->clk), + NPCM7XX_WATCHDOG_RESET_GPIO_IN, i)); } /* UART0..3 (16550 compatible) */ @@ -362,6 +446,45 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) serial_hd(i), DEVICE_LITTLE_ENDIAN); } + /* Random Number Generator. Cannot fail. */ + sysbus_realize(SYS_BUS_DEVICE(&s->rng), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rng), 0, NPCM7XX_RNG_BA); + + /* GPIO modules. Cannot fail. */ + QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_gpio) != ARRAY_SIZE(s->gpio)); + for (i = 0; i < ARRAY_SIZE(s->gpio); i++) { + Object *obj = OBJECT(&s->gpio[i]); + + object_property_set_uint(obj, "reset-pullup", + npcm7xx_gpio[i].reset_pu, &error_abort); + object_property_set_uint(obj, "reset-pulldown", + npcm7xx_gpio[i].reset_pd, &error_abort); + object_property_set_uint(obj, "reset-osrc", + npcm7xx_gpio[i].reset_osrc, &error_abort); + object_property_set_uint(obj, "reset-odsc", + npcm7xx_gpio[i].reset_odsc, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(obj), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(obj), 0, npcm7xx_gpio[i].regs_addr); + sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0, + npcm7xx_irq(s, NPCM7XX_GPIO0_IRQ + i)); + } + + /* USB Host */ + object_property_set_bool(OBJECT(&s->ehci), "companion-enable", true, + &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->ehci), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ehci), 0, NPCM7XX_EHCI_BA); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ehci), 0, + npcm7xx_irq(s, NPCM7XX_EHCI_IRQ)); + + object_property_set_str(OBJECT(&s->ohci), "masterbus", "usb-bus.0", + &error_abort); + object_property_set_uint(OBJECT(&s->ohci), "num-ports", 1, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->ohci), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ohci), 0, NPCM7XX_OHCI_BA); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->ohci), 0, + npcm7xx_irq(s, NPCM7XX_OHCI_IRQ)); + /* * Flash Interface Unit (FIU). Can fail if incorrect number of chip selects * specified, but this is a programming error. @@ -400,7 +523,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.rng", 0xf000b000, 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); @@ -447,8 +569,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp) create_unimplemented_device("npcm7xx.mcphy", 0xf05f0000, 64 * KiB); create_unimplemented_device("npcm7xx.gmac1", 0xf0802000, 8 * KiB); create_unimplemented_device("npcm7xx.gmac2", 0xf0804000, 8 * KiB); - create_unimplemented_device("npcm7xx.ehci", 0xf0806000, 4 * KiB); - create_unimplemented_device("npcm7xx.ohci", 0xf0807000, 4 * KiB); create_unimplemented_device("npcm7xx.vcd", 0xf0810000, 64 * KiB); create_unimplemented_device("npcm7xx.ece", 0xf0820000, 8 * KiB); create_unimplemented_device("npcm7xx.vdma", 0xf0822000, 8 * KiB); diff --git a/hw/arm/raspi.c b/hw/arm/raspi.c index b5b30f0..990509d 100644 --- a/hw/arm/raspi.c +++ b/hw/arm/raspi.c @@ -70,6 +70,7 @@ FIELD(REV_CODE, MEMORY_SIZE, 20, 3); FIELD(REV_CODE, STYLE, 23, 1); typedef enum RaspiProcessorId { + PROCESSOR_ID_BCM2835 = 0, PROCESSOR_ID_BCM2836 = 1, PROCESSOR_ID_BCM2837 = 2, } RaspiProcessorId; @@ -78,6 +79,7 @@ static const struct { const char *type; int cores_count; } soc_property[] = { + [PROCESSOR_ID_BCM2835] = {TYPE_BCM2835, 1}, [PROCESSOR_ID_BCM2836] = {TYPE_BCM2836, BCM283X_NCPUS}, [PROCESSOR_ID_BCM2837] = {TYPE_BCM2837, BCM283X_NCPUS}, }; @@ -317,6 +319,24 @@ static void raspi_machine_class_common_init(MachineClass *mc, mc->default_ram_id = "ram"; }; +static void raspi0_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + RaspiMachineClass *rmc = RASPI_MACHINE_CLASS(oc); + + rmc->board_rev = 0x920092; /* Revision 1.2 */ + raspi_machine_class_common_init(mc, rmc->board_rev); +}; + +static void raspi1ap_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + RaspiMachineClass *rmc = RASPI_MACHINE_CLASS(oc); + + rmc->board_rev = 0x900021; /* Revision 1.1 */ + raspi_machine_class_common_init(mc, rmc->board_rev); +}; + static void raspi2b_machine_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -328,6 +348,15 @@ static void raspi2b_machine_class_init(ObjectClass *oc, void *data) }; #ifdef TARGET_AARCH64 +static void raspi3ap_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + RaspiMachineClass *rmc = RASPI_MACHINE_CLASS(oc); + + rmc->board_rev = 0x9020e0; /* Revision 1.0 */ + raspi_machine_class_common_init(mc, rmc->board_rev); +}; + static void raspi3b_machine_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -341,11 +370,23 @@ static void raspi3b_machine_class_init(ObjectClass *oc, void *data) static const TypeInfo raspi_machine_types[] = { { + .name = MACHINE_TYPE_NAME("raspi0"), + .parent = TYPE_RASPI_MACHINE, + .class_init = raspi0_machine_class_init, + }, { + .name = MACHINE_TYPE_NAME("raspi1ap"), + .parent = TYPE_RASPI_MACHINE, + .class_init = raspi1ap_machine_class_init, + }, { .name = MACHINE_TYPE_NAME("raspi2b"), .parent = TYPE_RASPI_MACHINE, .class_init = raspi2b_machine_class_init, #ifdef TARGET_AARCH64 }, { + .name = MACHINE_TYPE_NAME("raspi3ap"), + .parent = TYPE_RASPI_MACHINE, + .class_init = raspi3ap_machine_class_init, + }, { .name = MACHINE_TYPE_NAME("raspi3b"), .parent = TYPE_RASPI_MACHINE, .class_init = raspi3b_machine_class_init, diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c index 0186351..7d9e180 100644 --- a/hw/arm/sbsa-ref.c +++ b/hw/arm/sbsa-ref.c @@ -40,6 +40,7 @@ #include "hw/qdev-properties.h" #include "hw/usb.h" #include "hw/char/pl011.h" +#include "hw/watchdog/sbsa_gwdt.h" #include "net/net.h" #include "qom/object.h" @@ -64,6 +65,9 @@ enum { SBSA_GIC_DIST, SBSA_GIC_REDIST, SBSA_SECURE_EC, + SBSA_GWDT, + SBSA_GWDT_REFRESH, + SBSA_GWDT_CONTROL, SBSA_SMMU, SBSA_UART, SBSA_RTC, @@ -104,6 +108,8 @@ static const MemMapEntry sbsa_ref_memmap[] = { [SBSA_GIC_DIST] = { 0x40060000, 0x00010000 }, [SBSA_GIC_REDIST] = { 0x40080000, 0x04000000 }, [SBSA_SECURE_EC] = { 0x50000000, 0x00001000 }, + [SBSA_GWDT_REFRESH] = { 0x50010000, 0x00001000 }, + [SBSA_GWDT_CONTROL] = { 0x50011000, 0x00001000 }, [SBSA_UART] = { 0x60000000, 0x00001000 }, [SBSA_RTC] = { 0x60010000, 0x00001000 }, [SBSA_GPIO] = { 0x60020000, 0x00001000 }, @@ -134,6 +140,7 @@ static const int sbsa_ref_irqmap[] = { [SBSA_AHCI] = 10, [SBSA_EHCI] = 11, [SBSA_SMMU] = 12, /* ... to 15 */ + [SBSA_GWDT] = 16, }; static uint64_t sbsa_ref_cpu_mp_affinity(SBSAMachineState *sms, int idx) @@ -448,6 +455,20 @@ static void create_rtc(const SBSAMachineState *sms) sysbus_create_simple("pl031", base, qdev_get_gpio_in(sms->gic, irq)); } +static void create_wdt(const SBSAMachineState *sms) +{ + hwaddr rbase = sbsa_ref_memmap[SBSA_GWDT_REFRESH].base; + hwaddr cbase = sbsa_ref_memmap[SBSA_GWDT_CONTROL].base; + DeviceState *dev = qdev_new(TYPE_WDT_SBSA); + SysBusDevice *s = SYS_BUS_DEVICE(dev); + int irq = sbsa_ref_irqmap[SBSA_GWDT]; + + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, rbase); + sysbus_mmio_map(s, 1, cbase); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(sms->gic, irq)); +} + static DeviceState *gpio_key_dev; static void sbsa_ref_powerdown_req(Notifier *n, void *opaque) { @@ -731,6 +752,8 @@ static void sbsa_ref_init(MachineState *machine) create_rtc(sms); + create_wdt(sms); + create_gpio(sms); create_ahci(sms); diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c index 0122700..2017ba7 100644 --- a/hw/arm/smmuv3.c +++ b/hw/arm/smmuv3.c @@ -1440,6 +1440,7 @@ static const VMStateDescription vmstate_smmuv3 = { .name = "smmuv3", .version_id = 1, .minimum_version_id = 1, + .priority = MIG_PRI_IOMMU, .fields = (VMStateField[]) { VMSTATE_UINT32(features, SMMUv3State), VMSTATE_UINT8(sid_size, SMMUv3State), diff --git a/hw/arm/trace-events b/hw/arm/trace-events index c8a4d80..a335ee8 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -41,7 +41,7 @@ smmuv3_get_cd(uint64_t addr) "CD addr: 0x%"PRIx64 smmuv3_decode_cd(uint32_t oas) "oas=%d" smmuv3_decode_cd_tt(int i, uint32_t tsz, uint64_t ttb, uint32_t granule_sz, bool had) "TT[%d]:tsz:%d ttb:0x%"PRIx64" granule_sz:%d had:%d" smmuv3_cmdq_cfgi_ste(int streamid) "streamid =%d" -smmuv3_cmdq_cfgi_ste_range(int start, int end) "start=0x%d - end=0x%d" +smmuv3_cmdq_cfgi_ste_range(int start, int end) "start=0x%x - end=0x%x" smmuv3_cmdq_cfgi_cd(uint32_t sid) "streamid = %d" smmuv3_config_cache_hit(uint32_t sid, uint32_t hits, uint32_t misses, uint32_t perc) "Config cache HIT for sid %d (hits=%d, misses=%d, hit rate=%d)" smmuv3_config_cache_miss(uint32_t sid, uint32_t hits, uint32_t misses, uint32_t perc) "Config cache MISS for sid %d (hits=%d, misses=%d, hit rate=%d)" diff --git a/hw/arm/xlnx-versal-virt.c b/hw/arm/xlnx-versal-virt.c index 03e2320..ee12822 100644 --- a/hw/arm/xlnx-versal-virt.c +++ b/hw/arm/xlnx-versal-virt.c @@ -561,6 +561,7 @@ static void versal_virt_machine_class_init(ObjectClass *oc, void *data) mc->desc = "Xilinx Versal Virtual development board"; mc->init = versal_virt_init; + mc->min_cpus = XLNX_VERSAL_NR_ACPUS; mc->max_cpus = XLNX_VERSAL_NR_ACPUS; mc->default_cpus = XLNX_VERSAL_NR_ACPUS; mc->no_cdrom = true; diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 13e784f..ede16c7 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -22,6 +22,7 @@ #include "hw/char/pl011.h" #include "hw/irq.h" #include "hw/sysbus.h" +#include "hw/qdev-clock.h" #include "migration/vmstate.h" #include "chardev/char-fe.h" #include "qemu/log.h" @@ -169,6 +170,25 @@ static void pl011_set_read_trigger(PL011State *s) s->read_trigger = 1; } +static unsigned int pl011_get_baudrate(const PL011State *s) +{ + uint64_t clk; + + if (s->fbrd == 0) { + return 0; + } + + clk = clock_get_hz(s->clk); + return (clk / ((s->ibrd << 6) + s->fbrd)) << 2; +} + +static void pl011_trace_baudrate_change(const PL011State *s) +{ + trace_pl011_baudrate_change(pl011_get_baudrate(s), + clock_get_hz(s->clk), + s->ibrd, s->fbrd); +} + static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { @@ -198,9 +218,11 @@ static void pl011_write(void *opaque, hwaddr offset, break; case 9: /* UARTIBRD */ s->ibrd = value; + pl011_trace_baudrate_change(s); break; case 10: /* UARTFBRD */ s->fbrd = value; + pl011_trace_baudrate_change(s); break; case 11: /* UARTLCR_H */ /* Reset the FIFO state on FIFO enable or disable */ @@ -286,12 +308,29 @@ static void pl011_event(void *opaque, QEMUChrEvent event) pl011_put_fifo(opaque, 0x400); } +static void pl011_clock_update(void *opaque) +{ + PL011State *s = PL011(opaque); + + pl011_trace_baudrate_change(s); +} + static const MemoryRegionOps pl011_ops = { .read = pl011_read, .write = pl011_write, .endianness = DEVICE_NATIVE_ENDIAN, }; +static const VMStateDescription vmstate_pl011_clock = { + .name = "pl011/clock", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clk, PL011State), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_pl011 = { .name = "pl011", .version_id = 2, @@ -314,6 +353,10 @@ static const VMStateDescription vmstate_pl011 = { VMSTATE_INT32(read_count, PL011State), VMSTATE_INT32(read_trigger, PL011State), VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * []) { + &vmstate_pl011_clock, + NULL } }; @@ -334,6 +377,8 @@ static void pl011_init(Object *obj) sysbus_init_irq(sbd, &s->irq[i]); } + s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s); + s->read_trigger = 1; s->ifl = 0x12; s->cr = 0x300; diff --git a/hw/char/trace-events b/hw/char/trace-events index 609df10..81026f6 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -65,6 +65,7 @@ pl011_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x" pl011_can_receive(uint32_t lcr, int read_count, int r) "LCR 0x%08x read_count %d returning %d" pl011_put_fifo(uint32_t c, int read_count) "new char 0x%x read_count now %d" pl011_put_fifo_full(void) "FIFO now full, RXFF set" +pl011_baudrate_change(unsigned int baudrate, uint64_t clock, uint32_t ibrd, uint32_t fbrd) "new baudrate %u (clk: %" PRIu64 "hz, ibrd: %" PRIu32 ", fbrd: %" PRIu32 ")" # cmsdk-apb-uart.c cmsdk_apb_uart_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" diff --git a/hw/core/clock.c b/hw/core/clock.c index f866717..8c6af22 100644 --- a/hw/core/clock.c +++ b/hw/core/clock.c @@ -54,8 +54,8 @@ bool clock_set(Clock *clk, uint64_t period) if (clk->period == period) { return false; } - trace_clock_set(CLOCK_PATH(clk), CLOCK_PERIOD_TO_NS(clk->period), - CLOCK_PERIOD_TO_NS(period)); + trace_clock_set(CLOCK_PATH(clk), CLOCK_PERIOD_TO_HZ(clk->period), + CLOCK_PERIOD_TO_HZ(period)); clk->period = period; return true; @@ -69,7 +69,7 @@ static void clock_propagate_period(Clock *clk, bool call_callbacks) if (child->period != clk->period) { child->period = clk->period; trace_clock_update(CLOCK_PATH(child), CLOCK_PATH(clk), - CLOCK_PERIOD_TO_NS(clk->period), + CLOCK_PERIOD_TO_HZ(clk->period), call_callbacks); if (call_callbacks && child->callback) { child->callback(child->callback_opaque); diff --git a/hw/core/ptimer.c b/hw/core/ptimer.c index c6d2beb..2aa97cb 100644 --- a/hw/core/ptimer.c +++ b/hw/core/ptimer.c @@ -117,6 +117,10 @@ static void ptimer_reload(ptimer_state *s, int delta_adjust) } if (delta == 0) { + if (s->enabled == 0) { + /* trigger callback disabled the timer already */ + return; + } if (!qtest_enabled()) { fprintf(stderr, "Timer with delta zero, disabling\n"); } diff --git a/hw/core/trace-events b/hw/core/trace-events index 1ac60ed..360ddeb 100644 --- a/hw/core/trace-events +++ b/hw/core/trace-events @@ -31,6 +31,6 @@ resettable_transitional_function(void *obj, const char *objtype) "obj=%p(%s)" # clock.c clock_set_source(const char *clk, const char *src) "'%s', src='%s'" clock_disconnect(const char *clk) "'%s'" -clock_set(const char *clk, uint64_t old, uint64_t new) "'%s', ns=%"PRIu64"->%"PRIu64 +clock_set(const char *clk, uint64_t old, uint64_t new) "'%s', %"PRIu64"Hz->%"PRIu64"Hz" clock_propagate(const char *clk) "'%s'" -clock_update(const char *clk, const char *src, uint64_t val, int cb) "'%s', src='%s', ns=%"PRIu64", cb=%d" +clock_update(const char *clk, const char *src, uint64_t hz, int cb) "'%s', src='%s', val=%"PRIu64"Hz cb=%d" diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 86cae9a..5c0a7d7 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build @@ -6,6 +6,7 @@ softmmu_ss.add(when: 'CONFIG_PUV3', if_true: files('puv3_gpio.c')) softmmu_ss.add(when: 'CONFIG_ZAURUS', if_true: files('zaurus.c')) softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpio.c')) +softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_gpio.c')) softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_gpio.c')) softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_gpio.c')) softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_gpio.c')) diff --git a/hw/gpio/npcm7xx_gpio.c b/hw/gpio/npcm7xx_gpio.c new file mode 100644 index 0000000..3376901a --- /dev/null +++ b/hw/gpio/npcm7xx_gpio.c @@ -0,0 +1,424 @@ +/* + * Nuvoton NPCM7xx General Purpose Input / Output (GPIO) + * + * 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 + * version 2 as published by the Free Software Foundation. + * + * 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/gpio/npcm7xx_gpio.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "trace.h" + +/* 32-bit register indices. */ +enum NPCM7xxGPIORegister { + NPCM7XX_GPIO_TLOCK1, + NPCM7XX_GPIO_DIN, + NPCM7XX_GPIO_POL, + NPCM7XX_GPIO_DOUT, + NPCM7XX_GPIO_OE, + NPCM7XX_GPIO_OTYP, + NPCM7XX_GPIO_MP, + NPCM7XX_GPIO_PU, + NPCM7XX_GPIO_PD, + NPCM7XX_GPIO_DBNC, + NPCM7XX_GPIO_EVTYP, + NPCM7XX_GPIO_EVBE, + NPCM7XX_GPIO_OBL0, + NPCM7XX_GPIO_OBL1, + NPCM7XX_GPIO_OBL2, + NPCM7XX_GPIO_OBL3, + NPCM7XX_GPIO_EVEN, + NPCM7XX_GPIO_EVENS, + NPCM7XX_GPIO_EVENC, + NPCM7XX_GPIO_EVST, + NPCM7XX_GPIO_SPLCK, + NPCM7XX_GPIO_MPLCK, + NPCM7XX_GPIO_IEM, + NPCM7XX_GPIO_OSRC, + NPCM7XX_GPIO_ODSC, + NPCM7XX_GPIO_DOS = 0x68 / sizeof(uint32_t), + NPCM7XX_GPIO_DOC, + NPCM7XX_GPIO_OES, + NPCM7XX_GPIO_OEC, + NPCM7XX_GPIO_TLOCK2 = 0x7c / sizeof(uint32_t), + NPCM7XX_GPIO_REGS_END, +}; + +#define NPCM7XX_GPIO_REGS_SIZE (4 * KiB) + +#define NPCM7XX_GPIO_LOCK_MAGIC1 (0xc0defa73) +#define NPCM7XX_GPIO_LOCK_MAGIC2 (0xc0de1248) + +static void npcm7xx_gpio_update_events(NPCM7xxGPIOState *s, uint32_t din_diff) +{ + uint32_t din_new = s->regs[NPCM7XX_GPIO_DIN]; + + /* Trigger on high level */ + s->regs[NPCM7XX_GPIO_EVST] |= din_new & ~s->regs[NPCM7XX_GPIO_EVTYP]; + /* Trigger on both edges */ + s->regs[NPCM7XX_GPIO_EVST] |= (din_diff & s->regs[NPCM7XX_GPIO_EVTYP] + & s->regs[NPCM7XX_GPIO_EVBE]); + /* Trigger on rising edge */ + s->regs[NPCM7XX_GPIO_EVST] |= (din_diff & din_new + & s->regs[NPCM7XX_GPIO_EVTYP]); + + trace_npcm7xx_gpio_update_events(DEVICE(s)->canonical_path, + s->regs[NPCM7XX_GPIO_EVST], + s->regs[NPCM7XX_GPIO_EVEN]); + qemu_set_irq(s->irq, !!(s->regs[NPCM7XX_GPIO_EVST] + & s->regs[NPCM7XX_GPIO_EVEN])); +} + +static void npcm7xx_gpio_update_pins(NPCM7xxGPIOState *s, uint32_t diff) +{ + uint32_t drive_en; + uint32_t drive_lvl; + uint32_t not_driven; + uint32_t undefined; + uint32_t pin_diff; + uint32_t din_old; + + /* Calculate level of each pin driven by GPIO controller. */ + drive_lvl = s->regs[NPCM7XX_GPIO_DOUT] ^ s->regs[NPCM7XX_GPIO_POL]; + /* If OTYP=1, only drive low (open drain) */ + drive_en = s->regs[NPCM7XX_GPIO_OE] & ~(s->regs[NPCM7XX_GPIO_OTYP] + & drive_lvl); + /* + * If a pin is driven to opposite levels by the GPIO controller and the + * external driver, the result is undefined. + */ + undefined = drive_en & s->ext_driven & (drive_lvl ^ s->ext_level); + if (undefined) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: pins have multiple drivers: 0x%" PRIx32 "\n", + DEVICE(s)->canonical_path, undefined); + } + + not_driven = ~(drive_en | s->ext_driven); + pin_diff = s->pin_level; + + /* Set pins to externally driven level. */ + s->pin_level = s->ext_level & s->ext_driven; + /* Set internally driven pins, ignoring any conflicts. */ + s->pin_level |= drive_lvl & drive_en; + /* Pull up undriven pins with internal pull-up enabled. */ + s->pin_level |= not_driven & s->regs[NPCM7XX_GPIO_PU]; + /* Pins not driven, pulled up or pulled down are undefined */ + undefined |= not_driven & ~(s->regs[NPCM7XX_GPIO_PU] + | s->regs[NPCM7XX_GPIO_PD]); + + /* If any pins changed state, update the outgoing GPIOs. */ + pin_diff ^= s->pin_level; + pin_diff |= undefined & diff; + if (pin_diff) { + int i; + + for (i = 0; i < NPCM7XX_GPIO_NR_PINS; i++) { + uint32_t mask = BIT(i); + if (pin_diff & mask) { + int level = (undefined & mask) ? -1 : !!(s->pin_level & mask); + trace_npcm7xx_gpio_set_output(DEVICE(s)->canonical_path, + i, level); + qemu_set_irq(s->output[i], level); + } + } + } + + /* Calculate new value of DIN after masking and polarity setting. */ + din_old = s->regs[NPCM7XX_GPIO_DIN]; + s->regs[NPCM7XX_GPIO_DIN] = ((s->pin_level & s->regs[NPCM7XX_GPIO_IEM]) + ^ s->regs[NPCM7XX_GPIO_POL]); + + /* See if any new events triggered because of all this. */ + npcm7xx_gpio_update_events(s, din_old ^ s->regs[NPCM7XX_GPIO_DIN]); +} + +static bool npcm7xx_gpio_is_locked(NPCM7xxGPIOState *s) +{ + return s->regs[NPCM7XX_GPIO_TLOCK1] == 1; +} + +static uint64_t npcm7xx_gpio_regs_read(void *opaque, hwaddr addr, + unsigned int size) +{ + hwaddr reg = addr / sizeof(uint32_t); + NPCM7xxGPIOState *s = opaque; + uint64_t value = 0; + + switch (reg) { + case NPCM7XX_GPIO_TLOCK1 ... NPCM7XX_GPIO_EVEN: + case NPCM7XX_GPIO_EVST ... NPCM7XX_GPIO_ODSC: + value = s->regs[reg]; + break; + + case NPCM7XX_GPIO_EVENS ... NPCM7XX_GPIO_EVENC: + case NPCM7XX_GPIO_DOS ... NPCM7XX_GPIO_TLOCK2: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from write-only register 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + break; + } + + trace_npcm7xx_gpio_read(DEVICE(s)->canonical_path, addr, value); + + return value; +} + +static void npcm7xx_gpio_regs_write(void *opaque, hwaddr addr, uint64_t v, + unsigned int size) +{ + hwaddr reg = addr / sizeof(uint32_t); + NPCM7xxGPIOState *s = opaque; + uint32_t value = v; + uint32_t diff; + + trace_npcm7xx_gpio_write(DEVICE(s)->canonical_path, addr, v); + + if (npcm7xx_gpio_is_locked(s)) { + switch (reg) { + case NPCM7XX_GPIO_TLOCK1: + if (s->regs[NPCM7XX_GPIO_TLOCK2] == NPCM7XX_GPIO_LOCK_MAGIC2 && + value == NPCM7XX_GPIO_LOCK_MAGIC1) { + s->regs[NPCM7XX_GPIO_TLOCK1] = 0; + s->regs[NPCM7XX_GPIO_TLOCK2] = 0; + } + break; + + case NPCM7XX_GPIO_TLOCK2: + s->regs[reg] = value; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to locked register @ 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + break; + } + + return; + } + + diff = s->regs[reg] ^ value; + + switch (reg) { + case NPCM7XX_GPIO_TLOCK1: + case NPCM7XX_GPIO_TLOCK2: + s->regs[NPCM7XX_GPIO_TLOCK1] = 1; + s->regs[NPCM7XX_GPIO_TLOCK2] = 0; + break; + + case NPCM7XX_GPIO_DIN: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only register @ 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + break; + + case NPCM7XX_GPIO_POL: + case NPCM7XX_GPIO_DOUT: + case NPCM7XX_GPIO_OE: + case NPCM7XX_GPIO_OTYP: + case NPCM7XX_GPIO_PU: + case NPCM7XX_GPIO_PD: + case NPCM7XX_GPIO_IEM: + s->regs[reg] = value; + npcm7xx_gpio_update_pins(s, diff); + break; + + case NPCM7XX_GPIO_DOS: + s->regs[NPCM7XX_GPIO_DOUT] |= value; + npcm7xx_gpio_update_pins(s, value); + break; + case NPCM7XX_GPIO_DOC: + s->regs[NPCM7XX_GPIO_DOUT] &= ~value; + npcm7xx_gpio_update_pins(s, value); + break; + case NPCM7XX_GPIO_OES: + s->regs[NPCM7XX_GPIO_OE] |= value; + npcm7xx_gpio_update_pins(s, value); + break; + case NPCM7XX_GPIO_OEC: + s->regs[NPCM7XX_GPIO_OE] &= ~value; + npcm7xx_gpio_update_pins(s, value); + break; + + case NPCM7XX_GPIO_EVTYP: + case NPCM7XX_GPIO_EVBE: + case NPCM7XX_GPIO_EVEN: + s->regs[reg] = value; + npcm7xx_gpio_update_events(s, 0); + break; + + case NPCM7XX_GPIO_EVENS: + s->regs[NPCM7XX_GPIO_EVEN] |= value; + npcm7xx_gpio_update_events(s, 0); + break; + case NPCM7XX_GPIO_EVENC: + s->regs[NPCM7XX_GPIO_EVEN] &= ~value; + npcm7xx_gpio_update_events(s, 0); + break; + + case NPCM7XX_GPIO_EVST: + s->regs[reg] &= ~value; + npcm7xx_gpio_update_events(s, 0); + break; + + case NPCM7XX_GPIO_MP: + case NPCM7XX_GPIO_DBNC: + case NPCM7XX_GPIO_OSRC: + case NPCM7XX_GPIO_ODSC: + /* Nothing to do; just store the value. */ + s->regs[reg] = value; + break; + + case NPCM7XX_GPIO_OBL0: + case NPCM7XX_GPIO_OBL1: + case NPCM7XX_GPIO_OBL2: + case NPCM7XX_GPIO_OBL3: + s->regs[reg] = value; + qemu_log_mask(LOG_UNIMP, "%s: Blinking is not implemented\n", + __func__); + break; + + case NPCM7XX_GPIO_SPLCK: + case NPCM7XX_GPIO_MPLCK: + qemu_log_mask(LOG_UNIMP, "%s: Per-pin lock is not implemented\n", + __func__); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, addr); + break; + } +} + +static const MemoryRegionOps npcm7xx_gpio_regs_ops = { + .read = npcm7xx_gpio_regs_read, + .write = npcm7xx_gpio_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void npcm7xx_gpio_set_input(void *opaque, int line, int level) +{ + NPCM7xxGPIOState *s = opaque; + + trace_npcm7xx_gpio_set_input(DEVICE(s)->canonical_path, line, level); + + g_assert(line >= 0 && line < NPCM7XX_GPIO_NR_PINS); + + s->ext_driven = deposit32(s->ext_driven, line, 1, level >= 0); + s->ext_level = deposit32(s->ext_level, line, 1, level > 0); + + npcm7xx_gpio_update_pins(s, BIT(line)); +} + +static void npcm7xx_gpio_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxGPIOState *s = NPCM7XX_GPIO(obj); + + memset(s->regs, 0, sizeof(s->regs)); + + s->regs[NPCM7XX_GPIO_PU] = s->reset_pu; + s->regs[NPCM7XX_GPIO_PD] = s->reset_pd; + s->regs[NPCM7XX_GPIO_OSRC] = s->reset_osrc; + s->regs[NPCM7XX_GPIO_ODSC] = s->reset_odsc; +} + +static void npcm7xx_gpio_hold_reset(Object *obj) +{ + NPCM7xxGPIOState *s = NPCM7XX_GPIO(obj); + + npcm7xx_gpio_update_pins(s, -1); +} + +static void npcm7xx_gpio_init(Object *obj) +{ + NPCM7xxGPIOState *s = NPCM7XX_GPIO(obj); + DeviceState *dev = DEVICE(obj); + + memory_region_init_io(&s->mmio, obj, &npcm7xx_gpio_regs_ops, s, + "regs", NPCM7XX_GPIO_REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + + qdev_init_gpio_in(dev, npcm7xx_gpio_set_input, NPCM7XX_GPIO_NR_PINS); + qdev_init_gpio_out(dev, s->output, NPCM7XX_GPIO_NR_PINS); +} + +static const VMStateDescription vmstate_npcm7xx_gpio = { + .name = "npcm7xx-gpio", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(pin_level, NPCM7xxGPIOState), + VMSTATE_UINT32(ext_level, NPCM7xxGPIOState), + VMSTATE_UINT32(ext_driven, NPCM7xxGPIOState), + VMSTATE_UINT32_ARRAY(regs, NPCM7xxGPIOState, NPCM7XX_GPIO_NR_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property npcm7xx_gpio_properties[] = { + /* Bit n set => pin n has pullup enabled by default. */ + DEFINE_PROP_UINT32("reset-pullup", NPCM7xxGPIOState, reset_pu, 0), + /* Bit n set => pin n has pulldown enabled by default. */ + DEFINE_PROP_UINT32("reset-pulldown", NPCM7xxGPIOState, reset_pd, 0), + /* Bit n set => pin n has high slew rate by default. */ + DEFINE_PROP_UINT32("reset-osrc", NPCM7xxGPIOState, reset_osrc, 0), + /* Bit n set => pin n has high drive strength by default. */ + DEFINE_PROP_UINT32("reset-odsc", NPCM7xxGPIOState, reset_odsc, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void npcm7xx_gpio_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *reset = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + QEMU_BUILD_BUG_ON(NPCM7XX_GPIO_REGS_END > NPCM7XX_GPIO_NR_REGS); + + dc->desc = "NPCM7xx GPIO Controller"; + dc->vmsd = &vmstate_npcm7xx_gpio; + reset->phases.enter = npcm7xx_gpio_enter_reset; + reset->phases.hold = npcm7xx_gpio_hold_reset; + device_class_set_props(dc, npcm7xx_gpio_properties); +} + +static const TypeInfo npcm7xx_gpio_types[] = { + { + .name = TYPE_NPCM7XX_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxGPIOState), + .class_init = npcm7xx_gpio_class_init, + .instance_init = npcm7xx_gpio_init, + }, +}; +DEFINE_TYPES(npcm7xx_gpio_types); diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events index 6e3f048..46ab932 100644 --- a/hw/gpio/trace-events +++ b/hw/gpio/trace-events @@ -1,5 +1,12 @@ # See docs/devel/tracing.txt for syntax documentation. +# npcm7xx_gpio.c +npcm7xx_gpio_read(const char *id, uint64_t offset, uint64_t value) " %s offset: 0x%04" PRIx64 " value 0x%08" PRIx64 +npcm7xx_gpio_write(const char *id, uint64_t offset, uint64_t value) "%s offset: 0x%04" PRIx64 " value 0x%08" PRIx64 +npcm7xx_gpio_set_input(const char *id, int32_t line, int32_t level) "%s line: %" PRIi32 " level: %" PRIi32 +npcm7xx_gpio_set_output(const char *id, int32_t line, int32_t level) "%s line: %" PRIi32 " level: %" PRIi32 +npcm7xx_gpio_update_events(const char *id, uint32_t evst, uint32_t even) "%s evst: 0x%08" PRIx32 " even: 0x%08" PRIx32 + # nrf51_gpio.c nrf51_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64 nrf51_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64 diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c new file mode 100644 index 0000000..7e415a0 --- /dev/null +++ b/hw/misc/bcm2835_cprman.c @@ -0,0 +1,808 @@ +/* + * BCM2835 CPRMAN clock manager + * + * Copyright (c) 2020 Luc Michel <luc@lmichel.fr> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * This peripheral is roughly divided into 3 main parts: + * - the PLLs + * - the PLL channels + * - the clock muxes + * + * A main oscillator (xosc) feeds all the PLLs. Each PLLs has one or more + * channels. Those channel are then connected to the clock muxes. Each mux has + * multiples sources (usually the xosc, some of the PLL channels and some "test + * debug" clocks). A mux is configured to select a given source through its + * control register. Each mux has one output clock that also goes out of the + * CPRMAN. This output clock usually connects to another peripheral in the SoC + * (so a given mux is dedicated to a peripheral). + * + * At each level (PLL, channel and mux), the clock can be altered through + * dividers (and multipliers in case of the PLLs), and can be disabled (in this + * case, the next levels see no clock). + * + * This can be sum-up as follows (this is an example and not the actual BCM2835 + * clock tree): + * + * /-->[PLL]-|->[PLL channel]--... [mux]--> to peripherals + * | |->[PLL channel] muxes takes [mux] + * | \->[PLL channel] inputs from [mux] + * | some channels [mux] + * [xosc]---|-->[PLL]-|->[PLL channel] and other srcs [mux] + * | \->[PLL channel] ...-->[mux] + * | [mux] + * \-->[PLL]--->[PLL channel] [mux] + * + * The page at https://elinux.org/The_Undocumented_Pi gives the actual clock + * tree configuration. + * + * The CPRMAN exposes clock outputs with the name of the clock mux suffixed + * with "-out" (e.g. "uart-out", "h264-out", ...). + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/misc/bcm2835_cprman.h" +#include "hw/misc/bcm2835_cprman_internals.h" +#include "trace.h" + +/* PLL */ + +static void pll_reset(DeviceState *dev) +{ + CprmanPllState *s = CPRMAN_PLL(dev); + const PLLResetInfo *info = &PLL_RESET_INFO[s->id]; + + *s->reg_cm = info->cm; + *s->reg_a2w_ctrl = info->a2w_ctrl; + memcpy(s->reg_a2w_ana, info->a2w_ana, sizeof(info->a2w_ana)); + *s->reg_a2w_frac = info->a2w_frac; +} + +static bool pll_is_locked(const CprmanPllState *pll) +{ + return !FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PWRDN) + && !FIELD_EX32(*pll->reg_cm, CM_PLLx, ANARST); +} + +static void pll_update(CprmanPllState *pll) +{ + uint64_t freq, ndiv, fdiv, pdiv; + + if (!pll_is_locked(pll)) { + clock_update(pll->out, 0); + return; + } + + pdiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PDIV); + + if (!pdiv) { + clock_update(pll->out, 0); + return; + } + + ndiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, NDIV); + fdiv = FIELD_EX32(*pll->reg_a2w_frac, A2W_PLLx_FRAC, FRAC); + + if (pll->reg_a2w_ana[1] & pll->prediv_mask) { + /* The prescaler doubles the parent frequency */ + ndiv *= 2; + fdiv *= 2; + } + + /* + * We have a multiplier with an integer part (ndiv) and a fractional part + * (fdiv), and a divider (pdiv). + */ + freq = clock_get_hz(pll->xosc_in) * + ((ndiv << R_A2W_PLLx_FRAC_FRAC_LENGTH) + fdiv); + freq /= pdiv; + freq >>= R_A2W_PLLx_FRAC_FRAC_LENGTH; + + clock_update_hz(pll->out, freq); +} + +static void pll_xosc_update(void *opaque) +{ + pll_update(CPRMAN_PLL(opaque)); +} + +static void pll_init(Object *obj) +{ + CprmanPllState *s = CPRMAN_PLL(obj); + + s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, s); + s->out = qdev_init_clock_out(DEVICE(s), "out"); +} + +static const VMStateDescription pll_vmstate = { + .name = TYPE_CPRMAN_PLL, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(xosc_in, CprmanPllState), + VMSTATE_END_OF_LIST() + } +}; + +static void pll_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = pll_reset; + dc->vmsd = &pll_vmstate; +} + +static const TypeInfo cprman_pll_info = { + .name = TYPE_CPRMAN_PLL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CprmanPllState), + .class_init = pll_class_init, + .instance_init = pll_init, +}; + + +/* PLL channel */ + +static void pll_channel_reset(DeviceState *dev) +{ + CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(dev); + const PLLChannelResetInfo *info = &PLL_CHANNEL_RESET_INFO[s->id]; + + *s->reg_a2w_ctrl = info->a2w_ctrl; +} + +static bool pll_channel_is_enabled(CprmanPllChannelState *channel) +{ + /* + * XXX I'm not sure of the purpose of the LOAD field. The Linux driver does + * not set it when enabling the channel, but does clear it when disabling + * it. + */ + return !FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DISABLE) + && !(*channel->reg_cm & channel->hold_mask); +} + +static void pll_channel_update(CprmanPllChannelState *channel) +{ + uint64_t freq, div; + + if (!pll_channel_is_enabled(channel)) { + clock_update(channel->out, 0); + return; + } + + div = FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DIV); + + if (!div) { + /* + * It seems that when the divider value is 0, it is considered as + * being maximum by the hardware (see the Linux driver). + */ + div = R_A2W_PLLx_CHANNELy_DIV_MASK; + } + + /* Some channels have an additional fixed divider */ + freq = clock_get_hz(channel->pll_in) / (div * channel->fixed_divider); + + clock_update_hz(channel->out, freq); +} + +/* Update a PLL and all its channels */ +static void pll_update_all_channels(BCM2835CprmanState *s, + CprmanPllState *pll) +{ + size_t i; + + pll_update(pll); + + for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { + CprmanPllChannelState *channel = &s->channels[i]; + if (channel->parent == pll->id) { + pll_channel_update(channel); + } + } +} + +static void pll_channel_pll_in_update(void *opaque) +{ + pll_channel_update(CPRMAN_PLL_CHANNEL(opaque)); +} + +static void pll_channel_init(Object *obj) +{ + CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj); + + s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in", + pll_channel_pll_in_update, s); + s->out = qdev_init_clock_out(DEVICE(s), "out"); +} + +static const VMStateDescription pll_channel_vmstate = { + .name = TYPE_CPRMAN_PLL_CHANNEL, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(pll_in, CprmanPllChannelState), + VMSTATE_END_OF_LIST() + } +}; + +static void pll_channel_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = pll_channel_reset; + dc->vmsd = &pll_channel_vmstate; +} + +static const TypeInfo cprman_pll_channel_info = { + .name = TYPE_CPRMAN_PLL_CHANNEL, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CprmanPllChannelState), + .class_init = pll_channel_class_init, + .instance_init = pll_channel_init, +}; + + +/* clock mux */ + +static bool clock_mux_is_enabled(CprmanClockMuxState *mux) +{ + return FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, ENABLE); +} + +static void clock_mux_update(CprmanClockMuxState *mux) +{ + uint64_t freq; + uint32_t div, src = FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, SRC); + bool enabled = clock_mux_is_enabled(mux); + + *mux->reg_ctl = FIELD_DP32(*mux->reg_ctl, CM_CLOCKx_CTL, BUSY, enabled); + + if (!enabled) { + clock_update(mux->out, 0); + return; + } + + freq = clock_get_hz(mux->srcs[src]); + + if (mux->int_bits == 0 && mux->frac_bits == 0) { + clock_update_hz(mux->out, freq); + return; + } + + /* + * The divider has an integer and a fractional part. The size of each part + * varies with the muxes (int_bits and frac_bits). Both parts are + * concatenated, with the integer part always starting at bit 12. + * + * 31 12 11 0 + * ------------------------------ + * CM_DIV | | int | frac | | + * ------------------------------ + * <-----> <------> + * int_bits frac_bits + */ + div = extract32(*mux->reg_div, + R_CM_CLOCKx_DIV_FRAC_LENGTH - mux->frac_bits, + mux->int_bits + mux->frac_bits); + + if (!div) { + clock_update(mux->out, 0); + return; + } + + freq = muldiv64(freq, 1 << mux->frac_bits, div); + + clock_update_hz(mux->out, freq); +} + +static void clock_mux_src_update(void *opaque) +{ + CprmanClockMuxState **backref = opaque; + CprmanClockMuxState *s = *backref; + CprmanClockMuxSource src = backref - s->backref; + + if (FIELD_EX32(*s->reg_ctl, CM_CLOCKx_CTL, SRC) != src) { + return; + } + + clock_mux_update(s); +} + +static void clock_mux_reset(DeviceState *dev) +{ + CprmanClockMuxState *clock = CPRMAN_CLOCK_MUX(dev); + const ClockMuxResetInfo *info = &CLOCK_MUX_RESET_INFO[clock->id]; + + *clock->reg_ctl = info->cm_ctl; + *clock->reg_div = info->cm_div; +} + +static void clock_mux_init(Object *obj) +{ + CprmanClockMuxState *s = CPRMAN_CLOCK_MUX(obj); + size_t i; + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) { + char *name = g_strdup_printf("srcs[%zu]", i); + s->backref[i] = s; + s->srcs[i] = qdev_init_clock_in(DEVICE(s), name, + clock_mux_src_update, + &s->backref[i]); + g_free(name); + } + + s->out = qdev_init_clock_out(DEVICE(s), "out"); +} + +static const VMStateDescription clock_mux_vmstate = { + .name = TYPE_CPRMAN_CLOCK_MUX, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_ARRAY_CLOCK(srcs, CprmanClockMuxState, + CPRMAN_NUM_CLOCK_MUX_SRC), + VMSTATE_END_OF_LIST() + } +}; + +static void clock_mux_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = clock_mux_reset; + dc->vmsd = &clock_mux_vmstate; +} + +static const TypeInfo cprman_clock_mux_info = { + .name = TYPE_CPRMAN_CLOCK_MUX, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CprmanClockMuxState), + .class_init = clock_mux_class_init, + .instance_init = clock_mux_init, +}; + + +/* DSI0HSCK mux */ + +static void dsi0hsck_mux_update(CprmanDsi0HsckMuxState *s) +{ + bool src_is_plld = FIELD_EX32(*s->reg_cm, CM_DSI0HSCK, SELPLLD); + Clock *src = src_is_plld ? s->plld_in : s->plla_in; + + clock_update(s->out, clock_get(src)); +} + +static void dsi0hsck_mux_in_update(void *opaque) +{ + dsi0hsck_mux_update(CPRMAN_DSI0HSCK_MUX(opaque)); +} + +static void dsi0hsck_mux_init(Object *obj) +{ + CprmanDsi0HsckMuxState *s = CPRMAN_DSI0HSCK_MUX(obj); + DeviceState *dev = DEVICE(obj); + + s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update, s); + s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update, s); + s->out = qdev_init_clock_out(DEVICE(s), "out"); +} + +static const VMStateDescription dsi0hsck_mux_vmstate = { + .name = TYPE_CPRMAN_DSI0HSCK_MUX, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(plla_in, CprmanDsi0HsckMuxState), + VMSTATE_CLOCK(plld_in, CprmanDsi0HsckMuxState), + VMSTATE_END_OF_LIST() + } +}; + +static void dsi0hsck_mux_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &dsi0hsck_mux_vmstate; +} + +static const TypeInfo cprman_dsi0hsck_mux_info = { + .name = TYPE_CPRMAN_DSI0HSCK_MUX, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CprmanDsi0HsckMuxState), + .class_init = dsi0hsck_mux_class_init, + .instance_init = dsi0hsck_mux_init, +}; + + +/* CPRMAN "top level" model */ + +static uint32_t get_cm_lock(const BCM2835CprmanState *s) +{ + static const int CM_LOCK_MAPPING[CPRMAN_NUM_PLL] = { + [CPRMAN_PLLA] = R_CM_LOCK_FLOCKA_SHIFT, + [CPRMAN_PLLC] = R_CM_LOCK_FLOCKC_SHIFT, + [CPRMAN_PLLD] = R_CM_LOCK_FLOCKD_SHIFT, + [CPRMAN_PLLH] = R_CM_LOCK_FLOCKH_SHIFT, + [CPRMAN_PLLB] = R_CM_LOCK_FLOCKB_SHIFT, + }; + + uint32_t r = 0; + size_t i; + + for (i = 0; i < CPRMAN_NUM_PLL; i++) { + r |= pll_is_locked(&s->plls[i]) << CM_LOCK_MAPPING[i]; + } + + return r; +} + +static uint64_t cprman_read(void *opaque, hwaddr offset, + unsigned size) +{ + BCM2835CprmanState *s = CPRMAN(opaque); + uint64_t r = 0; + size_t idx = offset / sizeof(uint32_t); + + switch (idx) { + case R_CM_LOCK: + r = get_cm_lock(s); + break; + + default: + r = s->regs[idx]; + } + + trace_bcm2835_cprman_read(offset, r); + return r; +} + +static inline void update_pll_and_channels_from_cm(BCM2835CprmanState *s, + size_t idx) +{ + size_t i; + + for (i = 0; i < CPRMAN_NUM_PLL; i++) { + if (PLL_INIT_INFO[i].cm_offset == idx) { + pll_update_all_channels(s, &s->plls[i]); + return; + } + } +} + +static inline void update_channel_from_a2w(BCM2835CprmanState *s, size_t idx) +{ + size_t i; + + for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { + if (PLL_CHANNEL_INIT_INFO[i].a2w_ctrl_offset == idx) { + pll_channel_update(&s->channels[i]); + return; + } + } +} + +static inline void update_mux_from_cm(BCM2835CprmanState *s, size_t idx) +{ + size_t i; + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { + if ((CLOCK_MUX_INIT_INFO[i].cm_offset == idx) || + (CLOCK_MUX_INIT_INFO[i].cm_offset + 4 == idx)) { + /* matches CM_CTL or CM_DIV mux register */ + clock_mux_update(&s->clock_muxes[i]); + return; + } + } +} + +#define CASE_PLL_A2W_REGS(pll_) \ + case R_A2W_ ## pll_ ## _CTRL: \ + case R_A2W_ ## pll_ ## _ANA0: \ + case R_A2W_ ## pll_ ## _ANA1: \ + case R_A2W_ ## pll_ ## _ANA2: \ + case R_A2W_ ## pll_ ## _ANA3: \ + case R_A2W_ ## pll_ ## _FRAC + +static void cprman_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + BCM2835CprmanState *s = CPRMAN(opaque); + size_t idx = offset / sizeof(uint32_t); + + if (FIELD_EX32(value, CPRMAN, PASSWORD) != CPRMAN_PASSWORD) { + trace_bcm2835_cprman_write_invalid_magic(offset, value); + return; + } + + value &= ~R_CPRMAN_PASSWORD_MASK; + + trace_bcm2835_cprman_write(offset, value); + s->regs[idx] = value; + + switch (idx) { + case R_CM_PLLA ... R_CM_PLLH: + case R_CM_PLLB: + /* + * A given CM_PLLx register is shared by both the PLL and the channels + * of this PLL. + */ + update_pll_and_channels_from_cm(s, idx); + break; + + CASE_PLL_A2W_REGS(PLLA) : + pll_update(&s->plls[CPRMAN_PLLA]); + break; + + CASE_PLL_A2W_REGS(PLLC) : + pll_update(&s->plls[CPRMAN_PLLC]); + break; + + CASE_PLL_A2W_REGS(PLLD) : + pll_update(&s->plls[CPRMAN_PLLD]); + break; + + CASE_PLL_A2W_REGS(PLLH) : + pll_update(&s->plls[CPRMAN_PLLH]); + break; + + CASE_PLL_A2W_REGS(PLLB) : + pll_update(&s->plls[CPRMAN_PLLB]); + break; + + case R_A2W_PLLA_DSI0: + case R_A2W_PLLA_CORE: + case R_A2W_PLLA_PER: + case R_A2W_PLLA_CCP2: + case R_A2W_PLLC_CORE2: + case R_A2W_PLLC_CORE1: + case R_A2W_PLLC_PER: + case R_A2W_PLLC_CORE0: + case R_A2W_PLLD_DSI0: + case R_A2W_PLLD_CORE: + case R_A2W_PLLD_PER: + case R_A2W_PLLD_DSI1: + case R_A2W_PLLH_AUX: + case R_A2W_PLLH_RCAL: + case R_A2W_PLLH_PIX: + case R_A2W_PLLB_ARM: + update_channel_from_a2w(s, idx); + break; + + case R_CM_GNRICCTL ... R_CM_SMIDIV: + case R_CM_TCNTCNT ... R_CM_VECDIV: + case R_CM_PULSECTL ... R_CM_PULSEDIV: + case R_CM_SDCCTL ... R_CM_ARMCTL: + case R_CM_AVEOCTL ... R_CM_EMMCDIV: + case R_CM_EMMC2CTL ... R_CM_EMMC2DIV: + update_mux_from_cm(s, idx); + break; + + case R_CM_DSI0HSCK: + dsi0hsck_mux_update(&s->dsi0hsck_mux); + break; + } +} + +#undef CASE_PLL_A2W_REGS + +static const MemoryRegionOps cprman_ops = { + .read = cprman_read, + .write = cprman_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + /* + * Although this hasn't been checked against real hardware, nor the + * information can be found in a datasheet, it seems reasonable because + * of the "PASSWORD" magic value found in every registers. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, + .impl = { + .max_access_size = 4, + }, +}; + +static void cprman_reset(DeviceState *dev) +{ + BCM2835CprmanState *s = CPRMAN(dev); + size_t i; + + memset(s->regs, 0, sizeof(s->regs)); + + for (i = 0; i < CPRMAN_NUM_PLL; i++) { + device_cold_reset(DEVICE(&s->plls[i])); + } + + for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { + device_cold_reset(DEVICE(&s->channels[i])); + } + + device_cold_reset(DEVICE(&s->dsi0hsck_mux)); + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { + device_cold_reset(DEVICE(&s->clock_muxes[i])); + } + + clock_update_hz(s->xosc, s->xosc_freq); +} + +static void cprman_init(Object *obj) +{ + BCM2835CprmanState *s = CPRMAN(obj); + size_t i; + + for (i = 0; i < CPRMAN_NUM_PLL; i++) { + object_initialize_child(obj, PLL_INIT_INFO[i].name, + &s->plls[i], TYPE_CPRMAN_PLL); + set_pll_init_info(s, &s->plls[i], i); + } + + for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { + object_initialize_child(obj, PLL_CHANNEL_INIT_INFO[i].name, + &s->channels[i], + TYPE_CPRMAN_PLL_CHANNEL); + set_pll_channel_init_info(s, &s->channels[i], i); + } + + object_initialize_child(obj, "dsi0hsck-mux", + &s->dsi0hsck_mux, TYPE_CPRMAN_DSI0HSCK_MUX); + s->dsi0hsck_mux.reg_cm = &s->regs[R_CM_DSI0HSCK]; + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { + char *alias; + + object_initialize_child(obj, CLOCK_MUX_INIT_INFO[i].name, + &s->clock_muxes[i], + TYPE_CPRMAN_CLOCK_MUX); + set_clock_mux_init_info(s, &s->clock_muxes[i], i); + + /* Expose muxes output as CPRMAN outputs */ + alias = g_strdup_printf("%s-out", CLOCK_MUX_INIT_INFO[i].name); + qdev_alias_clock(DEVICE(&s->clock_muxes[i]), "out", DEVICE(obj), alias); + g_free(alias); + } + + s->xosc = clock_new(obj, "xosc"); + s->gnd = clock_new(obj, "gnd"); + + clock_set(s->gnd, 0); + + memory_region_init_io(&s->iomem, obj, &cprman_ops, + s, "bcm2835-cprman", 0x2000); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static void connect_mux_sources(BCM2835CprmanState *s, + CprmanClockMuxState *mux, + const CprmanPllChannel *clk_mapping) +{ + size_t i; + Clock *td0 = s->clock_muxes[CPRMAN_CLOCK_TD0].out; + Clock *td1 = s->clock_muxes[CPRMAN_CLOCK_TD1].out; + + /* For sources from 0 to 3. Source 4 to 9 are mux specific */ + Clock * const CLK_SRC_MAPPING[] = { + [CPRMAN_CLOCK_SRC_GND] = s->gnd, + [CPRMAN_CLOCK_SRC_XOSC] = s->xosc, + [CPRMAN_CLOCK_SRC_TD0] = td0, + [CPRMAN_CLOCK_SRC_TD1] = td1, + }; + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) { + CprmanPllChannel mapping = clk_mapping[i]; + Clock *src; + + if (mapping == CPRMAN_CLOCK_SRC_FORCE_GROUND) { + src = s->gnd; + } else if (mapping == CPRMAN_CLOCK_SRC_DSI0HSCK) { + src = s->dsi0hsck_mux.out; + } else if (i < CPRMAN_CLOCK_SRC_PLLA) { + src = CLK_SRC_MAPPING[i]; + } else { + src = s->channels[mapping].out; + } + + clock_set_source(mux->srcs[i], src); + } +} + +static void cprman_realize(DeviceState *dev, Error **errp) +{ + BCM2835CprmanState *s = CPRMAN(dev); + size_t i; + + for (i = 0; i < CPRMAN_NUM_PLL; i++) { + CprmanPllState *pll = &s->plls[i]; + + clock_set_source(pll->xosc_in, s->xosc); + + if (!qdev_realize(DEVICE(pll), NULL, errp)) { + return; + } + } + + for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { + CprmanPllChannelState *channel = &s->channels[i]; + CprmanPll parent = PLL_CHANNEL_INIT_INFO[i].parent; + Clock *parent_clk = s->plls[parent].out; + + clock_set_source(channel->pll_in, parent_clk); + + if (!qdev_realize(DEVICE(channel), NULL, errp)) { + return; + } + } + + clock_set_source(s->dsi0hsck_mux.plla_in, + s->channels[CPRMAN_PLLA_CHANNEL_DSI0].out); + clock_set_source(s->dsi0hsck_mux.plld_in, + s->channels[CPRMAN_PLLD_CHANNEL_DSI0].out); + + if (!qdev_realize(DEVICE(&s->dsi0hsck_mux), NULL, errp)) { + return; + } + + for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { + CprmanClockMuxState *clock_mux = &s->clock_muxes[i]; + + connect_mux_sources(s, clock_mux, CLOCK_MUX_INIT_INFO[i].src_mapping); + + if (!qdev_realize(DEVICE(clock_mux), NULL, errp)) { + return; + } + } +} + +static const VMStateDescription cprman_vmstate = { + .name = TYPE_BCM2835_CPRMAN, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, BCM2835CprmanState, CPRMAN_NUM_REGS), + VMSTATE_END_OF_LIST() + } +}; + +static Property cprman_properties[] = { + DEFINE_PROP_UINT32("xosc-freq-hz", BCM2835CprmanState, xosc_freq, 19200000), + DEFINE_PROP_END_OF_LIST() +}; + +static void cprman_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = cprman_realize; + dc->reset = cprman_reset; + dc->vmsd = &cprman_vmstate; + device_class_set_props(dc, cprman_properties); +} + +static const TypeInfo cprman_info = { + .name = TYPE_BCM2835_CPRMAN, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(BCM2835CprmanState), + .class_init = cprman_class_init, + .instance_init = cprman_init, +}; + +static void cprman_register_types(void) +{ + type_register_static(&cprman_info); + type_register_static(&cprman_pll_info); + type_register_static(&cprman_pll_channel_info); + type_register_static(&cprman_clock_mux_info); + type_register_static(&cprman_dsi0hsck_mux_info); +} + +type_init(cprman_register_types); diff --git a/hw/misc/meson.build b/hw/misc/meson.build index aa8ec3b..4a06cba 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -60,6 +60,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_rng.c', )) softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files( 'omap_clk.c', @@ -74,6 +75,7 @@ softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files( 'bcm2835_property.c', 'bcm2835_rng.c', 'bcm2835_thermal.c', + 'bcm2835_cprman.c', )) softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c')) softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c', 'zynq-xadc.c')) diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c index 21ab420..6732437 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm7xx_clk.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "hw/misc/npcm7xx_clk.h" +#include "hw/timer/npcm7xx_timer.h" #include "migration/vmstate.h" #include "qemu/error-report.h" #include "qemu/log.h" @@ -24,6 +25,7 @@ #include "qemu/timer.h" #include "qemu/units.h" #include "trace.h" +#include "sysemu/watchdog.h" #define PLLCON_LOKI BIT(31) #define PLLCON_LOKS BIT(30) @@ -87,6 +89,12 @@ 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) + static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size) { uint32_t reg = offset / sizeof(uint32_t); @@ -187,6 +195,24 @@ static void npcm7xx_clk_write(void *opaque, hwaddr offset, s->regs[reg] = value; } +/* Perform reset action triggered by a watchdog */ +static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n, + int level) +{ + NPCM7xxCLKState *clk = NPCM7XX_CLK(opaque); + uint32_t rcr; + + g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS); + rcr = clk->regs[NPCM7XX_CLK_WD0RCR + n]; + if (rcr & NPCM7XX_CLK_WDRCR_CA9C) { + watchdog_perform_action(); + } else { + qemu_log_mask(LOG_UNIMP, + "%s: only CPU reset is implemented. (requested 0x%" PRIx32")\n", + __func__, rcr); + } +} + static const struct MemoryRegionOps npcm7xx_clk_ops = { .read = npcm7xx_clk_read, .write = npcm7xx_clk_write, @@ -226,6 +252,8 @@ static void npcm7xx_clk_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s, TYPE_NPCM7XX_CLK, 4 * KiB); sysbus_init_mmio(&s->parent, &s->iomem); + qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset, + NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS); } static const VMStateDescription vmstate_npcm7xx_clk = { diff --git a/hw/misc/npcm7xx_rng.c b/hw/misc/npcm7xx_rng.c new file mode 100644 index 0000000..f650f34 --- /dev/null +++ b/hw/misc/npcm7xx_rng.c @@ -0,0 +1,180 @@ +/* + * Nuvoton NPCM7xx Random Number Generator. + * + * 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/misc/npcm7xx_rng.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/guest-random.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#include "trace.h" + +#define NPCM7XX_RNG_REGS_SIZE (4 * KiB) + +#define NPCM7XX_RNGCS (0x00) +#define NPCM7XX_RNGCS_CLKP(rv) extract32(rv, 2, 4) +#define NPCM7XX_RNGCS_DVALID BIT(1) +#define NPCM7XX_RNGCS_RNGE BIT(0) + +#define NPCM7XX_RNGD (0x04) +#define NPCM7XX_RNGMODE (0x08) +#define NPCM7XX_RNGMODE_NORMAL (0x02) + +static bool npcm7xx_rng_is_enabled(NPCM7xxRNGState *s) +{ + return (s->rngcs & NPCM7XX_RNGCS_RNGE) && + (s->rngmode == NPCM7XX_RNGMODE_NORMAL); +} + +static uint64_t npcm7xx_rng_read(void *opaque, hwaddr offset, unsigned size) +{ + NPCM7xxRNGState *s = opaque; + uint64_t value = 0; + + switch (offset) { + case NPCM7XX_RNGCS: + /* + * If the RNG is enabled, but we don't have any valid random data, try + * obtaining some and update the DVALID bit accordingly. + */ + if (!npcm7xx_rng_is_enabled(s)) { + s->rngcs &= ~NPCM7XX_RNGCS_DVALID; + } else if (!(s->rngcs & NPCM7XX_RNGCS_DVALID)) { + uint8_t byte = 0; + + if (qemu_guest_getrandom(&byte, sizeof(byte), NULL) == 0) { + s->rngd = byte; + s->rngcs |= NPCM7XX_RNGCS_DVALID; + } + } + value = s->rngcs; + break; + case NPCM7XX_RNGD: + if (npcm7xx_rng_is_enabled(s) && s->rngcs & NPCM7XX_RNGCS_DVALID) { + s->rngcs &= ~NPCM7XX_RNGCS_DVALID; + value = s->rngd; + s->rngd = 0; + } + break; + case NPCM7XX_RNGMODE: + value = s->rngmode; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: read from invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } + + trace_npcm7xx_rng_read(offset, value, size); + + return value; +} + +static void npcm7xx_rng_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + NPCM7xxRNGState *s = opaque; + + trace_npcm7xx_rng_write(offset, value, size); + + switch (offset) { + case NPCM7XX_RNGCS: + s->rngcs &= NPCM7XX_RNGCS_DVALID; + s->rngcs |= value & ~NPCM7XX_RNGCS_DVALID; + break; + case NPCM7XX_RNGD: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only register @ 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + case NPCM7XX_RNGMODE: + s->rngmode = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to invalid offset 0x%" HWADDR_PRIx "\n", + DEVICE(s)->canonical_path, offset); + break; + } +} + +static const MemoryRegionOps npcm7xx_rng_ops = { + .read = npcm7xx_rng_read, + .write = npcm7xx_rng_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void npcm7xx_rng_enter_reset(Object *obj, ResetType type) +{ + NPCM7xxRNGState *s = NPCM7XX_RNG(obj); + + s->rngcs = 0; + s->rngd = 0; + s->rngmode = 0; +} + +static void npcm7xx_rng_init(Object *obj) +{ + NPCM7xxRNGState *s = NPCM7XX_RNG(obj); + + memory_region_init_io(&s->iomem, obj, &npcm7xx_rng_ops, s, "regs", + NPCM7XX_RNG_REGS_SIZE); + sysbus_init_mmio(&s->parent, &s->iomem); +} + +static const VMStateDescription vmstate_npcm7xx_rng = { + .name = "npcm7xx-rng", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(rngcs, NPCM7xxRNGState), + VMSTATE_UINT8(rngd, NPCM7xxRNGState), + VMSTATE_UINT8(rngmode, NPCM7xxRNGState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void npcm7xx_rng_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NPCM7xx Random Number Generator"; + dc->vmsd = &vmstate_npcm7xx_rng; + rc->phases.enter = npcm7xx_rng_enter_reset; +} + +static const TypeInfo npcm7xx_rng_types[] = { + { + .name = TYPE_NPCM7XX_RNG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NPCM7xxRNGState), + .class_init = npcm7xx_rng_class_init, + .instance_init = npcm7xx_rng_init, + }, +}; +DEFINE_TYPES(npcm7xx_rng_types); diff --git a/hw/misc/trace-events b/hw/misc/trace-events index c68811b..b5118ac 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -116,6 +116,10 @@ npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " valu npcm7xx_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32 +# npcm7xx_rng.c +npcm7xx_rng_read(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" +npcm7xx_rng_write(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u" + # 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" @@ -228,3 +232,8 @@ led_change_intensity(const char *color, const char *desc, uint8_t old_intensity_ # pca9552.c pca955x_gpio_status(const char *description, const char *buf) "%s GPIOs 0-15 [%s]" pca955x_gpio_change(const char *description, unsigned id, unsigned prev_state, unsigned current_state) "%s GPIO id:%u status: %u -> %u" + +# bcm2835_cprman.c +bcm2835_cprman_read(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64 +bcm2835_cprman_write(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64 +bcm2835_cprman_write_invalid_magic(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64 diff --git a/hw/timer/armv7m_systick.c b/hw/timer/armv7m_systick.c index a8cec7e..2f19201 100644 --- a/hw/timer/armv7m_systick.c +++ b/hw/timer/armv7m_systick.c @@ -39,26 +39,6 @@ static inline int64_t systick_scale(SysTickState *s) } } -static void systick_reload(SysTickState *s, int reset) -{ - /* The Cortex-M3 Devices Generic User Guide says that "When the - * ENABLE bit is set to 1, the counter loads the RELOAD value from the - * SYST RVR register and then counts down". So, we need to check the - * ENABLE bit before reloading the value. - */ - trace_systick_reload(); - - if ((s->control & SYSTICK_ENABLE) == 0) { - return; - } - - if (reset) { - s->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - } - s->tick += (s->reload + 1) * systick_scale(s); - timer_mod(s->timer, s->tick); -} - static void systick_timer_tick(void *opaque) { SysTickState *s = (SysTickState *)opaque; @@ -70,10 +50,12 @@ static void systick_timer_tick(void *opaque) /* Tell the NVIC to pend the SysTick exception */ qemu_irq_pulse(s->irq); } - if (s->reload == 0) { - s->control &= ~SYSTICK_ENABLE; - } else { - systick_reload(s, 0); + if (ptimer_get_limit(s->ptimer) == 0) { + /* + * Timer expiry with SYST_RVR zero disables the timer + * (but doesn't clear SYST_CSR.ENABLE) + */ + ptimer_stop(s->ptimer); } } @@ -94,30 +76,11 @@ static MemTxResult systick_read(void *opaque, hwaddr addr, uint64_t *data, s->control &= ~SYSTICK_COUNTFLAG; break; case 0x4: /* SysTick Reload Value. */ - val = s->reload; + val = ptimer_get_limit(s->ptimer); break; case 0x8: /* SysTick Current Value. */ - { - int64_t t; - - if ((s->control & SYSTICK_ENABLE) == 0) { - val = 0; - break; - } - t = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - if (t >= s->tick) { - val = 0; - break; - } - val = ((s->tick - (t + 1)) / systick_scale(s)) + 1; - /* The interrupt in triggered when the timer reaches zero. - However the counter is not reloaded until the next clock - tick. This is a hack to return zero during the first tick. */ - if (val > s->reload) { - val = 0; - } + val = ptimer_get_count(s->ptimer); break; - } case 0xc: /* SysTick Calibration Value. */ val = 10000; break; @@ -149,39 +112,50 @@ static MemTxResult systick_write(void *opaque, hwaddr addr, switch (addr) { case 0x0: /* SysTick Control and Status. */ { - uint32_t oldval = s->control; + uint32_t oldval; + ptimer_transaction_begin(s->ptimer); + oldval = s->control; s->control &= 0xfffffff8; s->control |= value & 7; + if ((oldval ^ value) & SYSTICK_ENABLE) { - int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); if (value & SYSTICK_ENABLE) { - if (s->tick) { - s->tick += now; - timer_mod(s->timer, s->tick); - } else { - systick_reload(s, 1); - } + /* + * Always reload the period in case board code has + * changed system_clock_scale. If we ever replace that + * global with a more sensible API then we might be able + * to set the period only when it actually changes. + */ + ptimer_set_period(s->ptimer, systick_scale(s)); + ptimer_run(s->ptimer, 0); } else { - timer_del(s->timer); - s->tick -= now; - if (s->tick < 0) { - s->tick = 0; - } + ptimer_stop(s->ptimer); } } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) { - /* This is a hack. Force the timer to be reloaded - when the reference clock is changed. */ - systick_reload(s, 1); + ptimer_set_period(s->ptimer, systick_scale(s)); } + ptimer_transaction_commit(s->ptimer); break; } case 0x4: /* SysTick Reload Value. */ - s->reload = value; + ptimer_transaction_begin(s->ptimer); + ptimer_set_limit(s->ptimer, value & 0xffffff, 0); + ptimer_transaction_commit(s->ptimer); break; - case 0x8: /* SysTick Current Value. Writes reload the timer. */ - systick_reload(s, 1); + case 0x8: /* SysTick Current Value. */ + /* + * Writing any value clears SYST_CVR to zero and clears + * SYST_CSR.COUNTFLAG. The counter will then reload from SYST_RVR + * on the next clock edge unless SYST_RVR is zero. + */ + ptimer_transaction_begin(s->ptimer); + if (ptimer_get_limit(s->ptimer) == 0) { + ptimer_stop(s->ptimer); + } + ptimer_set_count(s->ptimer, 0); s->control &= ~SYSTICK_COUNTFLAG; + ptimer_transaction_commit(s->ptimer); break; default: qemu_log_mask(LOG_GUEST_ERROR, @@ -210,10 +184,13 @@ static void systick_reset(DeviceState *dev) */ assert(system_clock_scale != 0); + ptimer_transaction_begin(s->ptimer); s->control = 0; - s->reload = 0; - s->tick = 0; - timer_del(s->timer); + ptimer_stop(s->ptimer); + ptimer_set_count(s->ptimer, 0); + ptimer_set_limit(s->ptimer, 0, 0); + ptimer_set_period(s->ptimer, systick_scale(s)); + ptimer_transaction_commit(s->ptimer); } static void systick_instance_init(Object *obj) @@ -229,18 +206,21 @@ static void systick_instance_init(Object *obj) static void systick_realize(DeviceState *dev, Error **errp) { SysTickState *s = SYSTICK(dev); - s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, systick_timer_tick, s); + s->ptimer = ptimer_init(systick_timer_tick, s, + PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD | + PTIMER_POLICY_NO_COUNTER_ROUND_DOWN | + PTIMER_POLICY_NO_IMMEDIATE_RELOAD | + PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT); } static const VMStateDescription vmstate_systick = { .name = "armv7m_systick", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT32(control, SysTickState), - VMSTATE_UINT32(reload, SysTickState), VMSTATE_INT64(tick, SysTickState), - VMSTATE_TIMER_PTR(timer, SysTickState), + VMSTATE_PTIMER(ptimer, SysTickState), VMSTATE_END_OF_LIST() } }; diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index 5703e43..d24445b 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "hw/irq.h" +#include "hw/qdev-properties.h" #include "hw/misc/npcm7xx_clk.h" #include "hw/timer/npcm7xx_timer.h" #include "migration/vmstate.h" @@ -60,6 +61,50 @@ enum NPCM7xxTimerRegisters { #define NPCM7XX_TCSR_PRESCALE_START 0 #define NPCM7XX_TCSR_PRESCALE_LEN 8 +#define NPCM7XX_WTCR_WTCLK(rv) extract32(rv, 10, 2) +#define NPCM7XX_WTCR_FREEZE_EN BIT(9) +#define NPCM7XX_WTCR_WTE BIT(7) +#define NPCM7XX_WTCR_WTIE BIT(6) +#define NPCM7XX_WTCR_WTIS(rv) extract32(rv, 4, 2) +#define NPCM7XX_WTCR_WTIF BIT(3) +#define NPCM7XX_WTCR_WTRF BIT(2) +#define NPCM7XX_WTCR_WTRE BIT(1) +#define NPCM7XX_WTCR_WTR BIT(0) + +/* + * The number of clock cycles between interrupt and reset in watchdog, used + * by the software to handle the interrupt before system is reset. + */ +#define NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES 1024 + +/* Start or resume the timer. */ +static void npcm7xx_timer_start(NPCM7xxBaseTimer *t) +{ + int64_t now; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + t->expires_ns = now + t->remaining_ns; + timer_mod(&t->qtimer, t->expires_ns); +} + +/* Stop counting. Record the time remaining so we can continue later. */ +static void npcm7xx_timer_pause(NPCM7xxBaseTimer *t) +{ + int64_t now; + + timer_del(&t->qtimer); + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + t->remaining_ns = t->expires_ns - now; +} + +/* Delete the timer and reset it to default state. */ +static void npcm7xx_timer_clear(NPCM7xxBaseTimer *t) +{ + timer_del(&t->qtimer); + t->expires_ns = 0; + t->remaining_ns = 0; +} + /* * Returns the index of timer in the tc->timer array. This can be used to * locate the registers that belong to this timer. @@ -102,6 +147,52 @@ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns) return count; } +static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) +{ + switch (NPCM7XX_WTCR_WTCLK(t->wtcr)) { + case 0: + return 1; + case 1: + return 256; + case 2: + return 2048; + case 3: + return 65536; + default: + g_assert_not_reached(); + } +} + +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; + + /* + * The reset function always clears the current timer. The caller of the + * this needs to decide whether to start the watchdog timer based on + * specific flag in WTCR. + */ + npcm7xx_timer_clear(&t->base_timer); + + ns *= prescaler; + t->base_timer.remaining_ns = ns; +} + +static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t) +{ + int64_t cycles = 1; + uint32_t s = NPCM7XX_WTCR_WTIS(t->wtcr); + + g_assert(s <= 3); + + cycles <<= NPCM7XX_WATCHDOG_BASETIME_SHIFT; + cycles <<= 2 * s; + + npcm7xx_watchdog_timer_reset_cycles(t, cycles); +} + /* * Raise the interrupt line if there's a pending interrupt and interrupts are * enabled for this timer. If not, lower it. @@ -116,16 +207,6 @@ static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t) trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending); } -/* Start or resume the timer. */ -static void npcm7xx_timer_start(NPCM7xxTimer *t) -{ - int64_t now; - - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - t->expires_ns = now + t->remaining_ns; - timer_mod(&t->qtimer, t->expires_ns); -} - /* * Called when the counter reaches zero. Sets the interrupt flag, and either * restarts or disables the timer. @@ -138,9 +219,9 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t) tc->tisr |= BIT(index); if (t->tcsr & NPCM7XX_TCSR_PERIODIC) { - t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); + t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); if (t->tcsr & NPCM7XX_TCSR_CEN) { - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } } else { t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT); @@ -149,18 +230,6 @@ static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t) npcm7xx_timer_check_interrupt(t); } -/* Stop counting. Record the time remaining so we can continue later. */ -static void npcm7xx_timer_pause(NPCM7xxTimer *t) -{ - int64_t now; - - timer_del(&t->qtimer); - now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - t->remaining_ns = t->expires_ns - now; - if (t->remaining_ns <= 0) { - npcm7xx_timer_reached_zero(t); - } -} /* * Restart the timer from its initial value. If the timer was enabled and stays @@ -170,10 +239,10 @@ static void npcm7xx_timer_pause(NPCM7xxTimer *t) */ static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr) { - t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); + t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) { - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } } @@ -184,10 +253,10 @@ static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t) if (t->tcsr & NPCM7XX_TCSR_CEN) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - return npcm7xx_timer_ns_to_count(t, t->expires_ns - now); + return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now); } - return npcm7xx_timer_ns_to_count(t, t->remaining_ns); + return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns); } static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) @@ -219,9 +288,9 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) { /* Recalculate time remaining based on the current TDR value. */ - t->remaining_ns = npcm7xx_timer_count_to_ns(t, tdr); + t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, tdr); if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) { - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } } @@ -235,10 +304,13 @@ static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) { if (new_tcsr & NPCM7XX_TCSR_CEN) { t->tcsr |= NPCM7XX_TCSR_CACT; - npcm7xx_timer_start(t); + npcm7xx_timer_start(&t->base_timer); } else { t->tcsr &= ~NPCM7XX_TCSR_CACT; - npcm7xx_timer_pause(t); + npcm7xx_timer_pause(&t->base_timer); + if (t->base_timer.remaining_ns <= 0) { + npcm7xx_timer_reached_zero(t); + } } } } @@ -259,9 +331,47 @@ static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value) if (value & (1U << i)) { npcm7xx_timer_check_interrupt(&s->timer[i]); } + } } +static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new_wtcr) +{ + uint32_t old_wtcr = t->wtcr; + + /* + * WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits + * unchanged. + */ + if (new_wtcr & NPCM7XX_WTCR_WTIF) { + new_wtcr &= ~NPCM7XX_WTCR_WTIF; + } else if (old_wtcr & NPCM7XX_WTCR_WTIF) { + new_wtcr |= NPCM7XX_WTCR_WTIF; + } + if (new_wtcr & NPCM7XX_WTCR_WTRF) { + new_wtcr &= ~NPCM7XX_WTCR_WTRF; + } else if (old_wtcr & NPCM7XX_WTCR_WTRF) { + new_wtcr |= NPCM7XX_WTCR_WTRF; + } + + t->wtcr = new_wtcr; + + if (new_wtcr & NPCM7XX_WTCR_WTR) { + t->wtcr &= ~NPCM7XX_WTCR_WTR; + npcm7xx_watchdog_timer_reset(t); + if (new_wtcr & NPCM7XX_WTCR_WTE) { + npcm7xx_timer_start(&t->base_timer); + } + } else if ((old_wtcr ^ new_wtcr) & NPCM7XX_WTCR_WTE) { + if (new_wtcr & NPCM7XX_WTCR_WTE) { + npcm7xx_timer_start(&t->base_timer); + } else { + npcm7xx_timer_pause(&t->base_timer); + } + } + +} + static hwaddr npcm7xx_tcsr_index(hwaddr reg) { switch (reg) { @@ -353,7 +463,7 @@ static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size) break; case NPCM7XX_TIMER_WTCR: - value = s->wtcr; + value = s->watchdog_timer.wtcr; break; default: @@ -409,8 +519,7 @@ static void npcm7xx_timer_write(void *opaque, hwaddr offset, return; case NPCM7XX_TIMER_WTCR: - qemu_log_mask(LOG_UNIMP, "%s: WTCR write not implemented: 0x%08x\n", - __func__, value); + npcm7xx_timer_write_wtcr(&s->watchdog_timer, value); return; } @@ -448,15 +557,42 @@ static void npcm7xx_timer_enter_reset(Object *obj, ResetType type) for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { NPCM7xxTimer *t = &s->timer[i]; - timer_del(&t->qtimer); - t->expires_ns = 0; - t->remaining_ns = 0; + npcm7xx_timer_clear(&t->base_timer); t->tcsr = 0x00000005; t->ticr = 0x00000000; } s->tisr = 0x00000000; - s->wtcr = 0x00000400; + /* + * Set WTCLK to 1(default) and reset all flags except WTRF. + * WTRF is not reset during a core domain reset. + */ + s->watchdog_timer.wtcr = 0x00000400 | (s->watchdog_timer.wtcr & + NPCM7XX_WTCR_WTRF); +} + +static void npcm7xx_watchdog_timer_expired(void *opaque) +{ + NPCM7xxWatchdogTimer *t = opaque; + + if (t->wtcr & NPCM7XX_WTCR_WTE) { + if (t->wtcr & NPCM7XX_WTCR_WTIF) { + if (t->wtcr & NPCM7XX_WTCR_WTRE) { + t->wtcr |= NPCM7XX_WTCR_WTRF; + /* send reset signal to CLK module*/ + qemu_irq_raise(t->reset_signal); + } + } else { + t->wtcr |= NPCM7XX_WTCR_WTIF; + if (t->wtcr & NPCM7XX_WTCR_WTIE) { + /* send interrupt */ + qemu_irq_raise(t->irq); + } + npcm7xx_watchdog_timer_reset_cycles(t, + NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES); + npcm7xx_timer_start(&t->base_timer); + } + } } static void npcm7xx_timer_hold_reset(Object *obj) @@ -467,6 +603,7 @@ static void npcm7xx_timer_hold_reset(Object *obj) for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { qemu_irq_lower(s->timer[i].irq); } + qemu_irq_lower(s->watchdog_timer.irq); } static void npcm7xx_timer_realize(DeviceState *dev, Error **errp) @@ -474,43 +611,80 @@ static void npcm7xx_timer_realize(DeviceState *dev, Error **errp) NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev); SysBusDevice *sbd = &s->parent; int i; + NPCM7xxWatchdogTimer *w; for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { NPCM7xxTimer *t = &s->timer[i]; t->ctrl = s; - timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expired, t); + timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL, + npcm7xx_timer_expired, t); sysbus_init_irq(sbd, &t->irq); } + w = &s->watchdog_timer; + w->ctrl = s; + timer_init_ns(&w->base_timer.qtimer, QEMU_CLOCK_VIRTUAL, + npcm7xx_watchdog_timer_expired, w); + sysbus_init_irq(sbd, &w->irq); + memory_region_init_io(&s->iomem, OBJECT(s), &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); } -static const VMStateDescription vmstate_npcm7xx_timer = { - .name = "npcm7xx-timer", +static const VMStateDescription vmstate_npcm7xx_base_timer = { + .name = "npcm7xx-base-timer", .version_id = 0, .minimum_version_id = 0, .fields = (VMStateField[]) { - VMSTATE_TIMER(qtimer, NPCM7xxTimer), - VMSTATE_INT64(expires_ns, NPCM7xxTimer), - VMSTATE_INT64(remaining_ns, NPCM7xxTimer), + VMSTATE_TIMER(qtimer, NPCM7xxBaseTimer), + VMSTATE_INT64(expires_ns, NPCM7xxBaseTimer), + VMSTATE_INT64(remaining_ns, NPCM7xxBaseTimer), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_timer = { + .name = "npcm7xx-timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(base_timer, NPCM7xxTimer, + 0, vmstate_npcm7xx_base_timer, + NPCM7xxBaseTimer), VMSTATE_UINT32(tcsr, NPCM7xxTimer), VMSTATE_UINT32(ticr, NPCM7xxTimer), VMSTATE_END_OF_LIST(), }, }; -static const VMStateDescription vmstate_npcm7xx_timer_ctrl = { - .name = "npcm7xx-timer-ctrl", +static const VMStateDescription vmstate_npcm7xx_watchdog_timer = { + .name = "npcm7xx-watchdog-timer", .version_id = 0, .minimum_version_id = 0, .fields = (VMStateField[]) { + VMSTATE_STRUCT(base_timer, NPCM7xxWatchdogTimer, + 0, vmstate_npcm7xx_base_timer, + NPCM7xxBaseTimer), + VMSTATE_UINT32(wtcr, NPCM7xxWatchdogTimer), + VMSTATE_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_npcm7xx_timer_ctrl = { + .name = "npcm7xx-timer-ctrl", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState), - VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState), VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState, NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer, NPCM7xxTimer), + VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState, + 0, vmstate_npcm7xx_watchdog_timer, + NPCM7xxWatchdogTimer), VMSTATE_END_OF_LIST(), }, }; diff --git a/hw/usb/hcd-ehci-sysbus.c b/hw/usb/hcd-ehci-sysbus.c index 3730736..e3758db 100644 --- a/hw/usb/hcd-ehci-sysbus.c +++ b/hw/usb/hcd-ehci-sysbus.c @@ -147,6 +147,24 @@ static const TypeInfo ehci_aw_h3_type_info = { .class_init = ehci_aw_h3_class_init, }; +static void ehci_npcm7xx_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + sec->portscbase = 0x44; + sec->portnr = 1; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_npcm7xx_type_info = { + .name = TYPE_NPCM7XX_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_npcm7xx_class_init, +}; + static void ehci_tegra2_class_init(ObjectClass *oc, void *data) { SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); @@ -269,6 +287,7 @@ static void ehci_sysbus_register_types(void) type_register_static(&ehci_platform_type_info); type_register_static(&ehci_exynos4210_type_info); type_register_static(&ehci_aw_h3_type_info); + type_register_static(&ehci_npcm7xx_type_info); type_register_static(&ehci_tegra2_type_info); type_register_static(&ehci_ppc4xx_type_info); type_register_static(&ehci_fusbh200_type_info); diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h index fd122dd..a173707 100644 --- a/hw/usb/hcd-ehci.h +++ b/hw/usb/hcd-ehci.h @@ -344,6 +344,7 @@ struct EHCIPCIState { #define TYPE_PLATFORM_EHCI "platform-ehci-usb" #define TYPE_EXYNOS4210_EHCI "exynos4210-ehci-usb" #define TYPE_AW_H3_EHCI "aw-h3-ehci-usb" +#define TYPE_NPCM7XX_EHCI "npcm7xx-ehci-usb" #define TYPE_TEGRA2_EHCI "tegra2-ehci-usb" #define TYPE_PPC4xx_EHCI "ppc4xx-ehci-usb" #define TYPE_FUSBH200_EHCI "fusbh200-ehci-usb" diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig index 293209b..66e1d02 100644 --- a/hw/watchdog/Kconfig +++ b/hw/watchdog/Kconfig @@ -17,3 +17,6 @@ config WDT_DIAG288 config WDT_IMX2 bool + +config WDT_SBSA + bool diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build index 9b8725e..054c403 100644 --- a/hw/watchdog/meson.build +++ b/hw/watchdog/meson.build @@ -5,3 +5,4 @@ softmmu_ss.add(when: 'CONFIG_WDT_IB700', if_true: files('wdt_ib700.c')) softmmu_ss.add(when: 'CONFIG_WDT_DIAG288', if_true: files('wdt_diag288.c')) softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('wdt_aspeed.c')) softmmu_ss.add(when: 'CONFIG_WDT_IMX2', if_true: files('wdt_imx2.c')) +softmmu_ss.add(when: 'CONFIG_WDT_SBSA', if_true: files('sbsa_gwdt.c')) diff --git a/hw/watchdog/sbsa_gwdt.c b/hw/watchdog/sbsa_gwdt.c new file mode 100644 index 0000000..d0998f8 --- /dev/null +++ b/hw/watchdog/sbsa_gwdt.c @@ -0,0 +1,293 @@ +/* + * Generic watchdog device model for SBSA + * + * The watchdog device has been implemented as revision 1 variant of + * the ARM SBSA specification v6.0 + * (https://developer.arm.com/documentation/den0029/d?lang=en) + * + * Copyright Linaro.org 2020 + * + * Authors: + * Shashi Mallela <shashi.mallela@linaro.org> + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at your + * option) any later version. See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "sysemu/reset.h" +#include "sysemu/watchdog.h" +#include "hw/watchdog/sbsa_gwdt.h" +#include "qemu/timer.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" + +static WatchdogTimerModel model = { + .wdt_name = TYPE_WDT_SBSA, + .wdt_description = "SBSA-compliant generic watchdog device", +}; + +static const VMStateDescription vmstate_sbsa_gwdt = { + .name = "sbsa-gwdt", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_TIMER_PTR(timer, SBSA_GWDTState), + VMSTATE_UINT32(wcs, SBSA_GWDTState), + VMSTATE_UINT32(worl, SBSA_GWDTState), + VMSTATE_UINT32(woru, SBSA_GWDTState), + VMSTATE_UINT32(wcvl, SBSA_GWDTState), + VMSTATE_UINT32(wcvu, SBSA_GWDTState), + VMSTATE_END_OF_LIST() + } +}; + +typedef enum WdtRefreshType { + EXPLICIT_REFRESH = 0, + TIMEOUT_REFRESH = 1, +} WdtRefreshType; + +static uint64_t sbsa_gwdt_rread(void *opaque, hwaddr addr, unsigned int size) +{ + SBSA_GWDTState *s = SBSA_GWDT(opaque); + uint32_t ret = 0; + + switch (addr) { + case SBSA_GWDT_WRR: + /* watch refresh read has no effect and returns 0 */ + ret = 0; + break; + case SBSA_GWDT_W_IIDR: + ret = s->id; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame read :" + " 0x%x\n", (int)addr); + } + return ret; +} + +static uint64_t sbsa_gwdt_read(void *opaque, hwaddr addr, unsigned int size) +{ + SBSA_GWDTState *s = SBSA_GWDT(opaque); + uint32_t ret = 0; + + switch (addr) { + case SBSA_GWDT_WCS: + ret = s->wcs; + break; + case SBSA_GWDT_WOR: + ret = s->worl; + break; + case SBSA_GWDT_WORU: + ret = s->woru; + break; + case SBSA_GWDT_WCV: + ret = s->wcvl; + break; + case SBSA_GWDT_WCVU: + ret = s->wcvu; + break; + case SBSA_GWDT_W_IIDR: + ret = s->id; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame read :" + " 0x%x\n", (int)addr); + } + return ret; +} + +static void sbsa_gwdt_update_timer(SBSA_GWDTState *s, WdtRefreshType rtype) +{ + uint64_t timeout = 0; + + timer_del(s->timer); + + if (s->wcs & SBSA_GWDT_WCS_EN) { + /* + * Extract the upper 16 bits from woru & 32 bits from worl + * registers to construct the 48 bit offset value + */ + timeout = s->woru; + timeout <<= 32; + timeout |= s->worl; + timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, SBSA_TIMER_FREQ); + timeout += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + if ((rtype == EXPLICIT_REFRESH) || ((rtype == TIMEOUT_REFRESH) && + (!(s->wcs & SBSA_GWDT_WCS_WS0)))) { + /* store the current timeout value into compare registers */ + s->wcvu = timeout >> 32; + s->wcvl = timeout; + } + timer_mod(s->timer, timeout); + } +} + +static void sbsa_gwdt_rwrite(void *opaque, hwaddr offset, uint64_t data, + unsigned size) { + SBSA_GWDTState *s = SBSA_GWDT(opaque); + + if (offset == SBSA_GWDT_WRR) { + s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1); + + sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame write :" + " 0x%x\n", (int)offset); + } +} + +static void sbsa_gwdt_write(void *opaque, hwaddr offset, uint64_t data, + unsigned size) { + SBSA_GWDTState *s = SBSA_GWDT(opaque); + + switch (offset) { + case SBSA_GWDT_WCS: + s->wcs = data & SBSA_GWDT_WCS_EN; + qemu_set_irq(s->irq, 0); + sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH); + break; + + case SBSA_GWDT_WOR: + s->worl = data; + s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1); + qemu_set_irq(s->irq, 0); + sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH); + break; + + case SBSA_GWDT_WORU: + s->woru = data & SBSA_GWDT_WOR_MASK; + s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1); + qemu_set_irq(s->irq, 0); + sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH); + break; + + case SBSA_GWDT_WCV: + s->wcvl = data; + break; + + case SBSA_GWDT_WCVU: + s->wcvu = data; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame write :" + " 0x%x\n", (int)offset); + } + return; +} + +static void wdt_sbsa_gwdt_reset(DeviceState *dev) +{ + SBSA_GWDTState *s = SBSA_GWDT(dev); + + timer_del(s->timer); + + s->wcs = 0; + s->wcvl = 0; + s->wcvu = 0; + s->worl = 0; + s->woru = 0; + s->id = SBSA_GWDT_ID; +} + +static void sbsa_gwdt_timer_sysinterrupt(void *opaque) +{ + SBSA_GWDTState *s = SBSA_GWDT(opaque); + + if (!(s->wcs & SBSA_GWDT_WCS_WS0)) { + s->wcs |= SBSA_GWDT_WCS_WS0; + sbsa_gwdt_update_timer(s, TIMEOUT_REFRESH); + qemu_set_irq(s->irq, 1); + } else { + s->wcs |= SBSA_GWDT_WCS_WS1; + qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n"); + /* + * Reset the watchdog only if the guest gets notified about + * expiry. watchdog_perform_action() may temporarily relinquish + * the BQL; reset before triggering the action to avoid races with + * sbsa_gwdt instructions. + */ + switch (get_watchdog_action()) { + case WATCHDOG_ACTION_DEBUG: + case WATCHDOG_ACTION_NONE: + case WATCHDOG_ACTION_PAUSE: + break; + default: + wdt_sbsa_gwdt_reset(DEVICE(s)); + } + watchdog_perform_action(); + } +} + +static const MemoryRegionOps sbsa_gwdt_rops = { + .read = sbsa_gwdt_rread, + .write = sbsa_gwdt_rwrite, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static const MemoryRegionOps sbsa_gwdt_ops = { + .read = sbsa_gwdt_read, + .write = sbsa_gwdt_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp) +{ + SBSA_GWDTState *s = SBSA_GWDT(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->rmmio, OBJECT(dev), + &sbsa_gwdt_rops, s, + "sbsa_gwdt.refresh", + SBSA_GWDT_RMMIO_SIZE); + + memory_region_init_io(&s->cmmio, OBJECT(dev), + &sbsa_gwdt_ops, s, + "sbsa_gwdt.control", + SBSA_GWDT_CMMIO_SIZE); + + sysbus_init_mmio(sbd, &s->rmmio); + sysbus_init_mmio(sbd, &s->cmmio); + + sysbus_init_irq(sbd, &s->irq); + + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sbsa_gwdt_timer_sysinterrupt, + dev); +} + +static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = wdt_sbsa_gwdt_realize; + dc->reset = wdt_sbsa_gwdt_reset; + dc->hotpluggable = false; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->vmsd = &vmstate_sbsa_gwdt; +} + +static const TypeInfo wdt_sbsa_gwdt_info = { + .class_init = wdt_sbsa_gwdt_class_init, + .parent = TYPE_SYS_BUS_DEVICE, + .name = TYPE_WDT_SBSA, + .instance_size = sizeof(SBSA_GWDTState), +}; + +static void wdt_sbsa_gwdt_register_types(void) +{ + watchdog_add_model(&model); + type_register_static(&wdt_sbsa_gwdt_info); +} + +type_init(wdt_sbsa_gwdt_register_types) |