aboutsummaryrefslogtreecommitdiffstats
path: root/hw/gpio
diff options
context:
space:
mode:
authorTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
committerTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
commite02cda008591317b1625707ff8e115a4841aa889 (patch)
treeaee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/gpio
parentcc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff)
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback design to work with QEMU and rust-vmm vhost-user backend without require any changes. Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/gpio')
-rw-r--r--hw/gpio/Kconfig15
-rw-r--r--hw/gpio/aspeed_gpio.c996
-rw-r--r--hw/gpio/bcm2835_gpio.c344
-rw-r--r--hw/gpio/gpio_key.c109
-rw-r--r--hw/gpio/gpio_pwr.c70
-rw-r--r--hw/gpio/imx_gpio.c355
-rw-r--r--hw/gpio/max7310.c218
-rw-r--r--hw/gpio/meson.build14
-rw-r--r--hw/gpio/mpc8xxx.c224
-rw-r--r--hw/gpio/npcm7xx_gpio.c424
-rw-r--r--hw/gpio/nrf51_gpio.c318
-rw-r--r--hw/gpio/omap_gpio.c813
-rw-r--r--hw/gpio/pl061.c603
-rw-r--r--hw/gpio/sifive_gpio.c397
-rw-r--r--hw/gpio/trace-events29
-rw-r--r--hw/gpio/trace.h1
-rw-r--r--hw/gpio/zaurus.c305
17 files changed, 5235 insertions, 0 deletions
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
new file mode 100644
index 000000000..f0e7405f6
--- /dev/null
+++ b/hw/gpio/Kconfig
@@ -0,0 +1,15 @@
+config MAX7310
+ bool
+ depends on I2C
+
+config PL061
+ bool
+
+config GPIO_KEY
+ bool
+
+config GPIO_PWR
+ bool
+
+config SIFIVE_GPIO
+ bool
diff --git a/hw/gpio/aspeed_gpio.c b/hw/gpio/aspeed_gpio.c
new file mode 100644
index 000000000..911d21c8c
--- /dev/null
+++ b/hw/gpio/aspeed_gpio.c
@@ -0,0 +1,996 @@
+/*
+ * ASPEED GPIO Controller
+ *
+ * Copyright (C) 2017-2019 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/host-utils.h"
+#include "qemu/log.h"
+#include "hw/gpio/aspeed_gpio.h"
+#include "hw/misc/aspeed_scu.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+
+#define GPIOS_PER_GROUP 8
+
+/* GPIO Source Types */
+#define ASPEED_CMD_SRC_MASK 0x01010101
+#define ASPEED_SOURCE_ARM 0
+#define ASPEED_SOURCE_LPC 1
+#define ASPEED_SOURCE_COPROCESSOR 2
+#define ASPEED_SOURCE_RESERVED 3
+
+/* GPIO Interrupt Triggers */
+/*
+ * For each set of gpios there are three sensitivity registers that control
+ * the interrupt trigger mode.
+ *
+ * | 2 | 1 | 0 | trigger mode
+ * -----------------------------
+ * | 0 | 0 | 0 | falling-edge
+ * | 0 | 0 | 1 | rising-edge
+ * | 0 | 1 | 0 | level-low
+ * | 0 | 1 | 1 | level-high
+ * | 1 | X | X | dual-edge
+ */
+#define ASPEED_FALLING_EDGE 0
+#define ASPEED_RISING_EDGE 1
+#define ASPEED_LEVEL_LOW 2
+#define ASPEED_LEVEL_HIGH 3
+#define ASPEED_DUAL_EDGE 4
+
+/* GPIO Register Address Offsets */
+#define GPIO_ABCD_DATA_VALUE (0x000 >> 2)
+#define GPIO_ABCD_DIRECTION (0x004 >> 2)
+#define GPIO_ABCD_INT_ENABLE (0x008 >> 2)
+#define GPIO_ABCD_INT_SENS_0 (0x00C >> 2)
+#define GPIO_ABCD_INT_SENS_1 (0x010 >> 2)
+#define GPIO_ABCD_INT_SENS_2 (0x014 >> 2)
+#define GPIO_ABCD_INT_STATUS (0x018 >> 2)
+#define GPIO_ABCD_RESET_TOLERANT (0x01C >> 2)
+#define GPIO_EFGH_DATA_VALUE (0x020 >> 2)
+#define GPIO_EFGH_DIRECTION (0x024 >> 2)
+#define GPIO_EFGH_INT_ENABLE (0x028 >> 2)
+#define GPIO_EFGH_INT_SENS_0 (0x02C >> 2)
+#define GPIO_EFGH_INT_SENS_1 (0x030 >> 2)
+#define GPIO_EFGH_INT_SENS_2 (0x034 >> 2)
+#define GPIO_EFGH_INT_STATUS (0x038 >> 2)
+#define GPIO_EFGH_RESET_TOLERANT (0x03C >> 2)
+#define GPIO_ABCD_DEBOUNCE_1 (0x040 >> 2)
+#define GPIO_ABCD_DEBOUNCE_2 (0x044 >> 2)
+#define GPIO_EFGH_DEBOUNCE_1 (0x048 >> 2)
+#define GPIO_EFGH_DEBOUNCE_2 (0x04C >> 2)
+#define GPIO_DEBOUNCE_TIME_1 (0x050 >> 2)
+#define GPIO_DEBOUNCE_TIME_2 (0x054 >> 2)
+#define GPIO_DEBOUNCE_TIME_3 (0x058 >> 2)
+#define GPIO_ABCD_COMMAND_SRC_0 (0x060 >> 2)
+#define GPIO_ABCD_COMMAND_SRC_1 (0x064 >> 2)
+#define GPIO_EFGH_COMMAND_SRC_0 (0x068 >> 2)
+#define GPIO_EFGH_COMMAND_SRC_1 (0x06C >> 2)
+#define GPIO_IJKL_DATA_VALUE (0x070 >> 2)
+#define GPIO_IJKL_DIRECTION (0x074 >> 2)
+#define GPIO_MNOP_DATA_VALUE (0x078 >> 2)
+#define GPIO_MNOP_DIRECTION (0x07C >> 2)
+#define GPIO_QRST_DATA_VALUE (0x080 >> 2)
+#define GPIO_QRST_DIRECTION (0x084 >> 2)
+#define GPIO_UVWX_DATA_VALUE (0x088 >> 2)
+#define GPIO_UVWX_DIRECTION (0x08C >> 2)
+#define GPIO_IJKL_COMMAND_SRC_0 (0x090 >> 2)
+#define GPIO_IJKL_COMMAND_SRC_1 (0x094 >> 2)
+#define GPIO_IJKL_INT_ENABLE (0x098 >> 2)
+#define GPIO_IJKL_INT_SENS_0 (0x09C >> 2)
+#define GPIO_IJKL_INT_SENS_1 (0x0A0 >> 2)
+#define GPIO_IJKL_INT_SENS_2 (0x0A4 >> 2)
+#define GPIO_IJKL_INT_STATUS (0x0A8 >> 2)
+#define GPIO_IJKL_RESET_TOLERANT (0x0AC >> 2)
+#define GPIO_IJKL_DEBOUNCE_1 (0x0B0 >> 2)
+#define GPIO_IJKL_DEBOUNCE_2 (0x0B4 >> 2)
+#define GPIO_IJKL_INPUT_MASK (0x0B8 >> 2)
+#define GPIO_ABCD_DATA_READ (0x0C0 >> 2)
+#define GPIO_EFGH_DATA_READ (0x0C4 >> 2)
+#define GPIO_IJKL_DATA_READ (0x0C8 >> 2)
+#define GPIO_MNOP_DATA_READ (0x0CC >> 2)
+#define GPIO_QRST_DATA_READ (0x0D0 >> 2)
+#define GPIO_UVWX_DATA_READ (0x0D4 >> 2)
+#define GPIO_YZAAAB_DATA_READ (0x0D8 >> 2)
+#define GPIO_AC_DATA_READ (0x0DC >> 2)
+#define GPIO_MNOP_COMMAND_SRC_0 (0x0E0 >> 2)
+#define GPIO_MNOP_COMMAND_SRC_1 (0x0E4 >> 2)
+#define GPIO_MNOP_INT_ENABLE (0x0E8 >> 2)
+#define GPIO_MNOP_INT_SENS_0 (0x0EC >> 2)
+#define GPIO_MNOP_INT_SENS_1 (0x0F0 >> 2)
+#define GPIO_MNOP_INT_SENS_2 (0x0F4 >> 2)
+#define GPIO_MNOP_INT_STATUS (0x0F8 >> 2)
+#define GPIO_MNOP_RESET_TOLERANT (0x0FC >> 2)
+#define GPIO_MNOP_DEBOUNCE_1 (0x100 >> 2)
+#define GPIO_MNOP_DEBOUNCE_2 (0x104 >> 2)
+#define GPIO_MNOP_INPUT_MASK (0x108 >> 2)
+#define GPIO_QRST_COMMAND_SRC_0 (0x110 >> 2)
+#define GPIO_QRST_COMMAND_SRC_1 (0x114 >> 2)
+#define GPIO_QRST_INT_ENABLE (0x118 >> 2)
+#define GPIO_QRST_INT_SENS_0 (0x11C >> 2)
+#define GPIO_QRST_INT_SENS_1 (0x120 >> 2)
+#define GPIO_QRST_INT_SENS_2 (0x124 >> 2)
+#define GPIO_QRST_INT_STATUS (0x128 >> 2)
+#define GPIO_QRST_RESET_TOLERANT (0x12C >> 2)
+#define GPIO_QRST_DEBOUNCE_1 (0x130 >> 2)
+#define GPIO_QRST_DEBOUNCE_2 (0x134 >> 2)
+#define GPIO_QRST_INPUT_MASK (0x138 >> 2)
+#define GPIO_UVWX_COMMAND_SRC_0 (0x140 >> 2)
+#define GPIO_UVWX_COMMAND_SRC_1 (0x144 >> 2)
+#define GPIO_UVWX_INT_ENABLE (0x148 >> 2)
+#define GPIO_UVWX_INT_SENS_0 (0x14C >> 2)
+#define GPIO_UVWX_INT_SENS_1 (0x150 >> 2)
+#define GPIO_UVWX_INT_SENS_2 (0x154 >> 2)
+#define GPIO_UVWX_INT_STATUS (0x158 >> 2)
+#define GPIO_UVWX_RESET_TOLERANT (0x15C >> 2)
+#define GPIO_UVWX_DEBOUNCE_1 (0x160 >> 2)
+#define GPIO_UVWX_DEBOUNCE_2 (0x164 >> 2)
+#define GPIO_UVWX_INPUT_MASK (0x168 >> 2)
+#define GPIO_YZAAAB_COMMAND_SRC_0 (0x170 >> 2)
+#define GPIO_YZAAAB_COMMAND_SRC_1 (0x174 >> 2)
+#define GPIO_YZAAAB_INT_ENABLE (0x178 >> 2)
+#define GPIO_YZAAAB_INT_SENS_0 (0x17C >> 2)
+#define GPIO_YZAAAB_INT_SENS_1 (0x180 >> 2)
+#define GPIO_YZAAAB_INT_SENS_2 (0x184 >> 2)
+#define GPIO_YZAAAB_INT_STATUS (0x188 >> 2)
+#define GPIO_YZAAAB_RESET_TOLERANT (0x18C >> 2)
+#define GPIO_YZAAAB_DEBOUNCE_1 (0x190 >> 2)
+#define GPIO_YZAAAB_DEBOUNCE_2 (0x194 >> 2)
+#define GPIO_YZAAAB_INPUT_MASK (0x198 >> 2)
+#define GPIO_AC_COMMAND_SRC_0 (0x1A0 >> 2)
+#define GPIO_AC_COMMAND_SRC_1 (0x1A4 >> 2)
+#define GPIO_AC_INT_ENABLE (0x1A8 >> 2)
+#define GPIO_AC_INT_SENS_0 (0x1AC >> 2)
+#define GPIO_AC_INT_SENS_1 (0x1B0 >> 2)
+#define GPIO_AC_INT_SENS_2 (0x1B4 >> 2)
+#define GPIO_AC_INT_STATUS (0x1B8 >> 2)
+#define GPIO_AC_RESET_TOLERANT (0x1BC >> 2)
+#define GPIO_AC_DEBOUNCE_1 (0x1C0 >> 2)
+#define GPIO_AC_DEBOUNCE_2 (0x1C4 >> 2)
+#define GPIO_AC_INPUT_MASK (0x1C8 >> 2)
+#define GPIO_ABCD_INPUT_MASK (0x1D0 >> 2)
+#define GPIO_EFGH_INPUT_MASK (0x1D4 >> 2)
+#define GPIO_YZAAAB_DATA_VALUE (0x1E0 >> 2)
+#define GPIO_YZAAAB_DIRECTION (0x1E4 >> 2)
+#define GPIO_AC_DATA_VALUE (0x1E8 >> 2)
+#define GPIO_AC_DIRECTION (0x1EC >> 2)
+#define GPIO_3_3V_MEM_SIZE 0x1F0
+#define GPIO_3_3V_REG_ARRAY_SIZE (GPIO_3_3V_MEM_SIZE >> 2)
+
+/* AST2600 only - 1.8V gpios */
+/*
+ * The AST2600 two copies of the GPIO controller: the same 3.3V gpios as the
+ * AST2400 (memory offsets 0x0-0x198) and a second controller with 1.8V gpios
+ * (memory offsets 0x800-0x9D4).
+ */
+#define GPIO_1_8V_ABCD_DATA_VALUE (0x000 >> 2)
+#define GPIO_1_8V_ABCD_DIRECTION (0x004 >> 2)
+#define GPIO_1_8V_ABCD_INT_ENABLE (0x008 >> 2)
+#define GPIO_1_8V_ABCD_INT_SENS_0 (0x00C >> 2)
+#define GPIO_1_8V_ABCD_INT_SENS_1 (0x010 >> 2)
+#define GPIO_1_8V_ABCD_INT_SENS_2 (0x014 >> 2)
+#define GPIO_1_8V_ABCD_INT_STATUS (0x018 >> 2)
+#define GPIO_1_8V_ABCD_RESET_TOLERANT (0x01C >> 2)
+#define GPIO_1_8V_E_DATA_VALUE (0x020 >> 2)
+#define GPIO_1_8V_E_DIRECTION (0x024 >> 2)
+#define GPIO_1_8V_E_INT_ENABLE (0x028 >> 2)
+#define GPIO_1_8V_E_INT_SENS_0 (0x02C >> 2)
+#define GPIO_1_8V_E_INT_SENS_1 (0x030 >> 2)
+#define GPIO_1_8V_E_INT_SENS_2 (0x034 >> 2)
+#define GPIO_1_8V_E_INT_STATUS (0x038 >> 2)
+#define GPIO_1_8V_E_RESET_TOLERANT (0x03C >> 2)
+#define GPIO_1_8V_ABCD_DEBOUNCE_1 (0x040 >> 2)
+#define GPIO_1_8V_ABCD_DEBOUNCE_2 (0x044 >> 2)
+#define GPIO_1_8V_E_DEBOUNCE_1 (0x048 >> 2)
+#define GPIO_1_8V_E_DEBOUNCE_2 (0x04C >> 2)
+#define GPIO_1_8V_DEBOUNCE_TIME_1 (0x050 >> 2)
+#define GPIO_1_8V_DEBOUNCE_TIME_2 (0x054 >> 2)
+#define GPIO_1_8V_DEBOUNCE_TIME_3 (0x058 >> 2)
+#define GPIO_1_8V_ABCD_COMMAND_SRC_0 (0x060 >> 2)
+#define GPIO_1_8V_ABCD_COMMAND_SRC_1 (0x064 >> 2)
+#define GPIO_1_8V_E_COMMAND_SRC_0 (0x068 >> 2)
+#define GPIO_1_8V_E_COMMAND_SRC_1 (0x06C >> 2)
+#define GPIO_1_8V_ABCD_DATA_READ (0x0C0 >> 2)
+#define GPIO_1_8V_E_DATA_READ (0x0C4 >> 2)
+#define GPIO_1_8V_ABCD_INPUT_MASK (0x1D0 >> 2)
+#define GPIO_1_8V_E_INPUT_MASK (0x1D4 >> 2)
+#define GPIO_1_8V_MEM_SIZE 0x1D8
+#define GPIO_1_8V_REG_ARRAY_SIZE (GPIO_1_8V_MEM_SIZE >> 2)
+
+static int aspeed_evaluate_irq(GPIOSets *regs, int gpio_prev_high, int gpio)
+{
+ uint32_t falling_edge = 0, rising_edge = 0;
+ uint32_t int_trigger = extract32(regs->int_sens_0, gpio, 1)
+ | extract32(regs->int_sens_1, gpio, 1) << 1
+ | extract32(regs->int_sens_2, gpio, 1) << 2;
+ uint32_t gpio_curr_high = extract32(regs->data_value, gpio, 1);
+ uint32_t gpio_int_enabled = extract32(regs->int_enable, gpio, 1);
+
+ if (!gpio_int_enabled) {
+ return 0;
+ }
+
+ /* Detect edges */
+ if (gpio_curr_high && !gpio_prev_high) {
+ rising_edge = 1;
+ } else if (!gpio_curr_high && gpio_prev_high) {
+ falling_edge = 1;
+ }
+
+ if (((int_trigger == ASPEED_FALLING_EDGE) && falling_edge) ||
+ ((int_trigger == ASPEED_RISING_EDGE) && rising_edge) ||
+ ((int_trigger == ASPEED_LEVEL_LOW) && !gpio_curr_high) ||
+ ((int_trigger == ASPEED_LEVEL_HIGH) && gpio_curr_high) ||
+ ((int_trigger >= ASPEED_DUAL_EDGE) && (rising_edge || falling_edge)))
+ {
+ regs->int_status = deposit32(regs->int_status, gpio, 1, 1);
+ return 1;
+ }
+ return 0;
+}
+
+#define nested_struct_index(ta, pa, m, tb, pb) \
+ (pb - ((tb *)(((char *)pa) + offsetof(ta, m))))
+
+static ptrdiff_t aspeed_gpio_set_idx(AspeedGPIOState *s, GPIOSets *regs)
+{
+ return nested_struct_index(AspeedGPIOState, s, sets, GPIOSets, regs);
+}
+
+static void aspeed_gpio_update(AspeedGPIOState *s, GPIOSets *regs,
+ uint32_t value)
+{
+ uint32_t input_mask = regs->input_mask;
+ uint32_t direction = regs->direction;
+ uint32_t old = regs->data_value;
+ uint32_t new = value;
+ uint32_t diff;
+ int gpio;
+
+ diff = old ^ new;
+ if (diff) {
+ for (gpio = 0; gpio < ASPEED_GPIOS_PER_SET; gpio++) {
+ uint32_t mask = 1 << gpio;
+
+ /* If the gpio needs to be updated... */
+ if (!(diff & mask)) {
+ continue;
+ }
+
+ /* ...and we're output or not input-masked... */
+ if (!(direction & mask) && (input_mask & mask)) {
+ continue;
+ }
+
+ /* ...then update the state. */
+ if (mask & new) {
+ regs->data_value |= mask;
+ } else {
+ regs->data_value &= ~mask;
+ }
+
+ /* If the gpio is set to output... */
+ if (direction & mask) {
+ /* ...trigger the line-state IRQ */
+ ptrdiff_t set = aspeed_gpio_set_idx(s, regs);
+ qemu_set_irq(s->gpios[set][gpio], !!(new & mask));
+ } else {
+ /* ...otherwise if we meet the line's current IRQ policy... */
+ if (aspeed_evaluate_irq(regs, old & mask, gpio)) {
+ /* ...trigger the VIC IRQ */
+ s->pending++;
+ }
+ }
+ }
+ }
+ qemu_set_irq(s->irq, !!(s->pending));
+}
+
+static bool aspeed_gpio_get_pin_level(AspeedGPIOState *s, uint32_t set_idx,
+ uint32_t pin)
+{
+ uint32_t reg_val;
+ uint32_t pin_mask = 1 << pin;
+
+ reg_val = s->sets[set_idx].data_value;
+
+ return !!(reg_val & pin_mask);
+}
+
+static void aspeed_gpio_set_pin_level(AspeedGPIOState *s, uint32_t set_idx,
+ uint32_t pin, bool level)
+{
+ uint32_t value = s->sets[set_idx].data_value;
+ uint32_t pin_mask = 1 << pin;
+
+ if (level) {
+ value |= pin_mask;
+ } else {
+ value &= !pin_mask;
+ }
+
+ aspeed_gpio_update(s, &s->sets[set_idx], value);
+}
+
+/*
+ * | src_1 | src_2 | source |
+ * |-----------------------------|
+ * | 0 | 0 | ARM |
+ * | 0 | 1 | LPC |
+ * | 1 | 0 | Coprocessor|
+ * | 1 | 1 | Reserved |
+ *
+ * Once the source of a set is programmed, corresponding bits in the
+ * data_value, direction, interrupt [enable, sens[0-2]], reset_tol and
+ * debounce registers can only be written by the source.
+ *
+ * Source is ARM by default
+ * only bits 24, 16, 8, and 0 can be set
+ *
+ * we don't currently have a model for the LPC or Coprocessor
+ */
+static uint32_t update_value_control_source(GPIOSets *regs, uint32_t old_value,
+ uint32_t value)
+{
+ int i;
+ int cmd_source;
+
+ /* assume the source is always ARM for now */
+ int source = ASPEED_SOURCE_ARM;
+
+ uint32_t new_value = 0;
+
+ /* for each group in set */
+ for (i = 0; i < ASPEED_GPIOS_PER_SET; i += GPIOS_PER_GROUP) {
+ cmd_source = extract32(regs->cmd_source_0, i, 1)
+ | (extract32(regs->cmd_source_1, i, 1) << 1);
+
+ if (source == cmd_source) {
+ new_value |= (0xff << i) & value;
+ } else {
+ new_value |= (0xff << i) & old_value;
+ }
+ }
+ return new_value;
+}
+
+static const AspeedGPIOReg aspeed_3_3v_gpios[GPIO_3_3V_REG_ARRAY_SIZE] = {
+ /* Set ABCD */
+ [GPIO_ABCD_DATA_VALUE] = { 0, gpio_reg_data_value },
+ [GPIO_ABCD_DIRECTION] = { 0, gpio_reg_direction },
+ [GPIO_ABCD_INT_ENABLE] = { 0, gpio_reg_int_enable },
+ [GPIO_ABCD_INT_SENS_0] = { 0, gpio_reg_int_sens_0 },
+ [GPIO_ABCD_INT_SENS_1] = { 0, gpio_reg_int_sens_1 },
+ [GPIO_ABCD_INT_SENS_2] = { 0, gpio_reg_int_sens_2 },
+ [GPIO_ABCD_INT_STATUS] = { 0, gpio_reg_int_status },
+ [GPIO_ABCD_RESET_TOLERANT] = { 0, gpio_reg_reset_tolerant },
+ [GPIO_ABCD_DEBOUNCE_1] = { 0, gpio_reg_debounce_1 },
+ [GPIO_ABCD_DEBOUNCE_2] = { 0, gpio_reg_debounce_2 },
+ [GPIO_ABCD_COMMAND_SRC_0] = { 0, gpio_reg_cmd_source_0 },
+ [GPIO_ABCD_COMMAND_SRC_1] = { 0, gpio_reg_cmd_source_1 },
+ [GPIO_ABCD_DATA_READ] = { 0, gpio_reg_data_read },
+ [GPIO_ABCD_INPUT_MASK] = { 0, gpio_reg_input_mask },
+ /* Set EFGH */
+ [GPIO_EFGH_DATA_VALUE] = { 1, gpio_reg_data_value },
+ [GPIO_EFGH_DIRECTION] = { 1, gpio_reg_direction },
+ [GPIO_EFGH_INT_ENABLE] = { 1, gpio_reg_int_enable },
+ [GPIO_EFGH_INT_SENS_0] = { 1, gpio_reg_int_sens_0 },
+ [GPIO_EFGH_INT_SENS_1] = { 1, gpio_reg_int_sens_1 },
+ [GPIO_EFGH_INT_SENS_2] = { 1, gpio_reg_int_sens_2 },
+ [GPIO_EFGH_INT_STATUS] = { 1, gpio_reg_int_status },
+ [GPIO_EFGH_RESET_TOLERANT] = { 1, gpio_reg_reset_tolerant },
+ [GPIO_EFGH_DEBOUNCE_1] = { 1, gpio_reg_debounce_1 },
+ [GPIO_EFGH_DEBOUNCE_2] = { 1, gpio_reg_debounce_2 },
+ [GPIO_EFGH_COMMAND_SRC_0] = { 1, gpio_reg_cmd_source_0 },
+ [GPIO_EFGH_COMMAND_SRC_1] = { 1, gpio_reg_cmd_source_1 },
+ [GPIO_EFGH_DATA_READ] = { 1, gpio_reg_data_read },
+ [GPIO_EFGH_INPUT_MASK] = { 1, gpio_reg_input_mask },
+ /* Set IJKL */
+ [GPIO_IJKL_DATA_VALUE] = { 2, gpio_reg_data_value },
+ [GPIO_IJKL_DIRECTION] = { 2, gpio_reg_direction },
+ [GPIO_IJKL_INT_ENABLE] = { 2, gpio_reg_int_enable },
+ [GPIO_IJKL_INT_SENS_0] = { 2, gpio_reg_int_sens_0 },
+ [GPIO_IJKL_INT_SENS_1] = { 2, gpio_reg_int_sens_1 },
+ [GPIO_IJKL_INT_SENS_2] = { 2, gpio_reg_int_sens_2 },
+ [GPIO_IJKL_INT_STATUS] = { 2, gpio_reg_int_status },
+ [GPIO_IJKL_RESET_TOLERANT] = { 2, gpio_reg_reset_tolerant },
+ [GPIO_IJKL_DEBOUNCE_1] = { 2, gpio_reg_debounce_1 },
+ [GPIO_IJKL_DEBOUNCE_2] = { 2, gpio_reg_debounce_2 },
+ [GPIO_IJKL_COMMAND_SRC_0] = { 2, gpio_reg_cmd_source_0 },
+ [GPIO_IJKL_COMMAND_SRC_1] = { 2, gpio_reg_cmd_source_1 },
+ [GPIO_IJKL_DATA_READ] = { 2, gpio_reg_data_read },
+ [GPIO_IJKL_INPUT_MASK] = { 2, gpio_reg_input_mask },
+ /* Set MNOP */
+ [GPIO_MNOP_DATA_VALUE] = { 3, gpio_reg_data_value },
+ [GPIO_MNOP_DIRECTION] = { 3, gpio_reg_direction },
+ [GPIO_MNOP_INT_ENABLE] = { 3, gpio_reg_int_enable },
+ [GPIO_MNOP_INT_SENS_0] = { 3, gpio_reg_int_sens_0 },
+ [GPIO_MNOP_INT_SENS_1] = { 3, gpio_reg_int_sens_1 },
+ [GPIO_MNOP_INT_SENS_2] = { 3, gpio_reg_int_sens_2 },
+ [GPIO_MNOP_INT_STATUS] = { 3, gpio_reg_int_status },
+ [GPIO_MNOP_RESET_TOLERANT] = { 3, gpio_reg_reset_tolerant },
+ [GPIO_MNOP_DEBOUNCE_1] = { 3, gpio_reg_debounce_1 },
+ [GPIO_MNOP_DEBOUNCE_2] = { 3, gpio_reg_debounce_2 },
+ [GPIO_MNOP_COMMAND_SRC_0] = { 3, gpio_reg_cmd_source_0 },
+ [GPIO_MNOP_COMMAND_SRC_1] = { 3, gpio_reg_cmd_source_1 },
+ [GPIO_MNOP_DATA_READ] = { 3, gpio_reg_data_read },
+ [GPIO_MNOP_INPUT_MASK] = { 3, gpio_reg_input_mask },
+ /* Set QRST */
+ [GPIO_QRST_DATA_VALUE] = { 4, gpio_reg_data_value },
+ [GPIO_QRST_DIRECTION] = { 4, gpio_reg_direction },
+ [GPIO_QRST_INT_ENABLE] = { 4, gpio_reg_int_enable },
+ [GPIO_QRST_INT_SENS_0] = { 4, gpio_reg_int_sens_0 },
+ [GPIO_QRST_INT_SENS_1] = { 4, gpio_reg_int_sens_1 },
+ [GPIO_QRST_INT_SENS_2] = { 4, gpio_reg_int_sens_2 },
+ [GPIO_QRST_INT_STATUS] = { 4, gpio_reg_int_status },
+ [GPIO_QRST_RESET_TOLERANT] = { 4, gpio_reg_reset_tolerant },
+ [GPIO_QRST_DEBOUNCE_1] = { 4, gpio_reg_debounce_1 },
+ [GPIO_QRST_DEBOUNCE_2] = { 4, gpio_reg_debounce_2 },
+ [GPIO_QRST_COMMAND_SRC_0] = { 4, gpio_reg_cmd_source_0 },
+ [GPIO_QRST_COMMAND_SRC_1] = { 4, gpio_reg_cmd_source_1 },
+ [GPIO_QRST_DATA_READ] = { 4, gpio_reg_data_read },
+ [GPIO_QRST_INPUT_MASK] = { 4, gpio_reg_input_mask },
+ /* Set UVWX */
+ [GPIO_UVWX_DATA_VALUE] = { 5, gpio_reg_data_value },
+ [GPIO_UVWX_DIRECTION] = { 5, gpio_reg_direction },
+ [GPIO_UVWX_INT_ENABLE] = { 5, gpio_reg_int_enable },
+ [GPIO_UVWX_INT_SENS_0] = { 5, gpio_reg_int_sens_0 },
+ [GPIO_UVWX_INT_SENS_1] = { 5, gpio_reg_int_sens_1 },
+ [GPIO_UVWX_INT_SENS_2] = { 5, gpio_reg_int_sens_2 },
+ [GPIO_UVWX_INT_STATUS] = { 5, gpio_reg_int_status },
+ [GPIO_UVWX_RESET_TOLERANT] = { 5, gpio_reg_reset_tolerant },
+ [GPIO_UVWX_DEBOUNCE_1] = { 5, gpio_reg_debounce_1 },
+ [GPIO_UVWX_DEBOUNCE_2] = { 5, gpio_reg_debounce_2 },
+ [GPIO_UVWX_COMMAND_SRC_0] = { 5, gpio_reg_cmd_source_0 },
+ [GPIO_UVWX_COMMAND_SRC_1] = { 5, gpio_reg_cmd_source_1 },
+ [GPIO_UVWX_DATA_READ] = { 5, gpio_reg_data_read },
+ [GPIO_UVWX_INPUT_MASK] = { 5, gpio_reg_input_mask },
+ /* Set YZAAAB */
+ [GPIO_YZAAAB_DATA_VALUE] = { 6, gpio_reg_data_value },
+ [GPIO_YZAAAB_DIRECTION] = { 6, gpio_reg_direction },
+ [GPIO_YZAAAB_INT_ENABLE] = { 6, gpio_reg_int_enable },
+ [GPIO_YZAAAB_INT_SENS_0] = { 6, gpio_reg_int_sens_0 },
+ [GPIO_YZAAAB_INT_SENS_1] = { 6, gpio_reg_int_sens_1 },
+ [GPIO_YZAAAB_INT_SENS_2] = { 6, gpio_reg_int_sens_2 },
+ [GPIO_YZAAAB_INT_STATUS] = { 6, gpio_reg_int_status },
+ [GPIO_YZAAAB_RESET_TOLERANT] = { 6, gpio_reg_reset_tolerant },
+ [GPIO_YZAAAB_DEBOUNCE_1] = { 6, gpio_reg_debounce_1 },
+ [GPIO_YZAAAB_DEBOUNCE_2] = { 6, gpio_reg_debounce_2 },
+ [GPIO_YZAAAB_COMMAND_SRC_0] = { 6, gpio_reg_cmd_source_0 },
+ [GPIO_YZAAAB_COMMAND_SRC_1] = { 6, gpio_reg_cmd_source_1 },
+ [GPIO_YZAAAB_DATA_READ] = { 6, gpio_reg_data_read },
+ [GPIO_YZAAAB_INPUT_MASK] = { 6, gpio_reg_input_mask },
+ /* Set AC (ast2500 only) */
+ [GPIO_AC_DATA_VALUE] = { 7, gpio_reg_data_value },
+ [GPIO_AC_DIRECTION] = { 7, gpio_reg_direction },
+ [GPIO_AC_INT_ENABLE] = { 7, gpio_reg_int_enable },
+ [GPIO_AC_INT_SENS_0] = { 7, gpio_reg_int_sens_0 },
+ [GPIO_AC_INT_SENS_1] = { 7, gpio_reg_int_sens_1 },
+ [GPIO_AC_INT_SENS_2] = { 7, gpio_reg_int_sens_2 },
+ [GPIO_AC_INT_STATUS] = { 7, gpio_reg_int_status },
+ [GPIO_AC_RESET_TOLERANT] = { 7, gpio_reg_reset_tolerant },
+ [GPIO_AC_DEBOUNCE_1] = { 7, gpio_reg_debounce_1 },
+ [GPIO_AC_DEBOUNCE_2] = { 7, gpio_reg_debounce_2 },
+ [GPIO_AC_COMMAND_SRC_0] = { 7, gpio_reg_cmd_source_0 },
+ [GPIO_AC_COMMAND_SRC_1] = { 7, gpio_reg_cmd_source_1 },
+ [GPIO_AC_DATA_READ] = { 7, gpio_reg_data_read },
+ [GPIO_AC_INPUT_MASK] = { 7, gpio_reg_input_mask },
+};
+
+static const AspeedGPIOReg aspeed_1_8v_gpios[GPIO_1_8V_REG_ARRAY_SIZE] = {
+ /* 1.8V Set ABCD */
+ [GPIO_1_8V_ABCD_DATA_VALUE] = {0, gpio_reg_data_value},
+ [GPIO_1_8V_ABCD_DIRECTION] = {0, gpio_reg_direction},
+ [GPIO_1_8V_ABCD_INT_ENABLE] = {0, gpio_reg_int_enable},
+ [GPIO_1_8V_ABCD_INT_SENS_0] = {0, gpio_reg_int_sens_0},
+ [GPIO_1_8V_ABCD_INT_SENS_1] = {0, gpio_reg_int_sens_1},
+ [GPIO_1_8V_ABCD_INT_SENS_2] = {0, gpio_reg_int_sens_2},
+ [GPIO_1_8V_ABCD_INT_STATUS] = {0, gpio_reg_int_status},
+ [GPIO_1_8V_ABCD_RESET_TOLERANT] = {0, gpio_reg_reset_tolerant},
+ [GPIO_1_8V_ABCD_DEBOUNCE_1] = {0, gpio_reg_debounce_1},
+ [GPIO_1_8V_ABCD_DEBOUNCE_2] = {0, gpio_reg_debounce_2},
+ [GPIO_1_8V_ABCD_COMMAND_SRC_0] = {0, gpio_reg_cmd_source_0},
+ [GPIO_1_8V_ABCD_COMMAND_SRC_1] = {0, gpio_reg_cmd_source_1},
+ [GPIO_1_8V_ABCD_DATA_READ] = {0, gpio_reg_data_read},
+ [GPIO_1_8V_ABCD_INPUT_MASK] = {0, gpio_reg_input_mask},
+ /* 1.8V Set E */
+ [GPIO_1_8V_E_DATA_VALUE] = {1, gpio_reg_data_value},
+ [GPIO_1_8V_E_DIRECTION] = {1, gpio_reg_direction},
+ [GPIO_1_8V_E_INT_ENABLE] = {1, gpio_reg_int_enable},
+ [GPIO_1_8V_E_INT_SENS_0] = {1, gpio_reg_int_sens_0},
+ [GPIO_1_8V_E_INT_SENS_1] = {1, gpio_reg_int_sens_1},
+ [GPIO_1_8V_E_INT_SENS_2] = {1, gpio_reg_int_sens_2},
+ [GPIO_1_8V_E_INT_STATUS] = {1, gpio_reg_int_status},
+ [GPIO_1_8V_E_RESET_TOLERANT] = {1, gpio_reg_reset_tolerant},
+ [GPIO_1_8V_E_DEBOUNCE_1] = {1, gpio_reg_debounce_1},
+ [GPIO_1_8V_E_DEBOUNCE_2] = {1, gpio_reg_debounce_2},
+ [GPIO_1_8V_E_COMMAND_SRC_0] = {1, gpio_reg_cmd_source_0},
+ [GPIO_1_8V_E_COMMAND_SRC_1] = {1, gpio_reg_cmd_source_1},
+ [GPIO_1_8V_E_DATA_READ] = {1, gpio_reg_data_read},
+ [GPIO_1_8V_E_INPUT_MASK] = {1, gpio_reg_input_mask},
+};
+
+static uint64_t aspeed_gpio_read(void *opaque, hwaddr offset, uint32_t size)
+{
+ AspeedGPIOState *s = ASPEED_GPIO(opaque);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+ uint64_t idx = -1;
+ const AspeedGPIOReg *reg;
+ GPIOSets *set;
+
+ idx = offset >> 2;
+ if (idx >= GPIO_DEBOUNCE_TIME_1 && idx <= GPIO_DEBOUNCE_TIME_3) {
+ idx -= GPIO_DEBOUNCE_TIME_1;
+ return (uint64_t) s->debounce_regs[idx];
+ }
+
+ reg = &agc->reg_table[idx];
+ if (reg->set_idx >= agc->nr_gpio_sets) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: no getter for offset 0x%"
+ HWADDR_PRIx"\n", __func__, offset);
+ return 0;
+ }
+
+ set = &s->sets[reg->set_idx];
+ switch (reg->type) {
+ case gpio_reg_data_value:
+ return set->data_value;
+ case gpio_reg_direction:
+ return set->direction;
+ case gpio_reg_int_enable:
+ return set->int_enable;
+ case gpio_reg_int_sens_0:
+ return set->int_sens_0;
+ case gpio_reg_int_sens_1:
+ return set->int_sens_1;
+ case gpio_reg_int_sens_2:
+ return set->int_sens_2;
+ case gpio_reg_int_status:
+ return set->int_status;
+ case gpio_reg_reset_tolerant:
+ return set->reset_tol;
+ case gpio_reg_debounce_1:
+ return set->debounce_1;
+ case gpio_reg_debounce_2:
+ return set->debounce_2;
+ case gpio_reg_cmd_source_0:
+ return set->cmd_source_0;
+ case gpio_reg_cmd_source_1:
+ return set->cmd_source_1;
+ case gpio_reg_data_read:
+ return set->data_read;
+ case gpio_reg_input_mask:
+ return set->input_mask;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: no getter for offset 0x%"
+ HWADDR_PRIx"\n", __func__, offset);
+ return 0;
+ };
+}
+
+static void aspeed_gpio_write(void *opaque, hwaddr offset, uint64_t data,
+ uint32_t size)
+{
+ AspeedGPIOState *s = ASPEED_GPIO(opaque);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+ const GPIOSetProperties *props;
+ uint64_t idx = -1;
+ const AspeedGPIOReg *reg;
+ GPIOSets *set;
+ uint32_t cleared;
+
+ idx = offset >> 2;
+ if (idx >= GPIO_DEBOUNCE_TIME_1 && idx <= GPIO_DEBOUNCE_TIME_3) {
+ idx -= GPIO_DEBOUNCE_TIME_1;
+ s->debounce_regs[idx] = (uint32_t) data;
+ return;
+ }
+
+ reg = &agc->reg_table[idx];
+ if (reg->set_idx >= agc->nr_gpio_sets) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: no setter for offset 0x%"
+ HWADDR_PRIx"\n", __func__, offset);
+ return;
+ }
+
+ set = &s->sets[reg->set_idx];
+ props = &agc->props[reg->set_idx];
+
+ switch (reg->type) {
+ case gpio_reg_data_value:
+ data &= props->output;
+ data = update_value_control_source(set, set->data_value, data);
+ set->data_read = data;
+ aspeed_gpio_update(s, set, data);
+ return;
+ case gpio_reg_direction:
+ /*
+ * where data is the value attempted to be written to the pin:
+ * pin type | input mask | output mask | expected value
+ * ------------------------------------------------------------
+ * bidirectional | 1 | 1 | data
+ * input only | 1 | 0 | 0
+ * output only | 0 | 1 | 1
+ * no pin | 0 | 0 | 0
+ *
+ * which is captured by:
+ * data = ( data | ~input) & output;
+ */
+ data = (data | ~props->input) & props->output;
+ set->direction = update_value_control_source(set, set->direction, data);
+ break;
+ case gpio_reg_int_enable:
+ set->int_enable = update_value_control_source(set, set->int_enable,
+ data);
+ break;
+ case gpio_reg_int_sens_0:
+ set->int_sens_0 = update_value_control_source(set, set->int_sens_0,
+ data);
+ break;
+ case gpio_reg_int_sens_1:
+ set->int_sens_1 = update_value_control_source(set, set->int_sens_1,
+ data);
+ break;
+ case gpio_reg_int_sens_2:
+ set->int_sens_2 = update_value_control_source(set, set->int_sens_2,
+ data);
+ break;
+ case gpio_reg_int_status:
+ cleared = ctpop32(data & set->int_status);
+ if (s->pending && cleared) {
+ assert(s->pending >= cleared);
+ s->pending -= cleared;
+ }
+ set->int_status &= ~data;
+ break;
+ case gpio_reg_reset_tolerant:
+ set->reset_tol = update_value_control_source(set, set->reset_tol,
+ data);
+ return;
+ case gpio_reg_debounce_1:
+ set->debounce_1 = update_value_control_source(set, set->debounce_1,
+ data);
+ return;
+ case gpio_reg_debounce_2:
+ set->debounce_2 = update_value_control_source(set, set->debounce_2,
+ data);
+ return;
+ case gpio_reg_cmd_source_0:
+ set->cmd_source_0 = data & ASPEED_CMD_SRC_MASK;
+ return;
+ case gpio_reg_cmd_source_1:
+ set->cmd_source_1 = data & ASPEED_CMD_SRC_MASK;
+ return;
+ case gpio_reg_data_read:
+ /* Read only register */
+ return;
+ case gpio_reg_input_mask:
+ /*
+ * feeds into interrupt generation
+ * 0: read from data value reg will be updated
+ * 1: read from data value reg will not be updated
+ */
+ set->input_mask = data & props->input;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: no setter for offset 0x%"
+ HWADDR_PRIx"\n", __func__, offset);
+ return;
+ }
+ aspeed_gpio_update(s, set, set->data_value);
+ return;
+}
+
+static int get_set_idx(AspeedGPIOState *s, const char *group, int *group_idx)
+{
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+ int set_idx, g_idx;
+
+ for (set_idx = 0; set_idx < agc->nr_gpio_sets; set_idx++) {
+ const GPIOSetProperties *set_props = &agc->props[set_idx];
+ for (g_idx = 0; g_idx < ASPEED_GROUPS_PER_SET; g_idx++) {
+ if (!strncmp(group, set_props->group_label[g_idx], strlen(group))) {
+ *group_idx = g_idx;
+ return set_idx;
+ }
+ }
+ }
+ return -1;
+}
+
+static void aspeed_gpio_get_pin(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ int pin = 0xfff;
+ bool level = true;
+ char group[4];
+ AspeedGPIOState *s = ASPEED_GPIO(obj);
+ int set_idx, group_idx = 0;
+
+ if (sscanf(name, "gpio%2[A-Z]%1d", group, &pin) != 2) {
+ /* 1.8V gpio */
+ if (sscanf(name, "gpio%3[18A-E]%1d", group, &pin) != 2) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+ }
+ set_idx = get_set_idx(s, group, &group_idx);
+ if (set_idx == -1) {
+ error_setg(errp, "%s: invalid group %s", __func__, group);
+ return;
+ }
+ pin = pin + group_idx * GPIOS_PER_GROUP;
+ level = aspeed_gpio_get_pin_level(s, set_idx, pin);
+ visit_type_bool(v, name, &level, errp);
+}
+
+static void aspeed_gpio_set_pin(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ bool level;
+ int pin = 0xfff;
+ char group[4];
+ AspeedGPIOState *s = ASPEED_GPIO(obj);
+ int set_idx, group_idx = 0;
+
+ if (!visit_type_bool(v, name, &level, errp)) {
+ return;
+ }
+ if (sscanf(name, "gpio%2[A-Z]%1d", group, &pin) != 2) {
+ /* 1.8V gpio */
+ if (sscanf(name, "gpio%3[18A-E]%1d", group, &pin) != 2) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+ }
+ set_idx = get_set_idx(s, group, &group_idx);
+ if (set_idx == -1) {
+ error_setg(errp, "%s: invalid group %s", __func__, group);
+ return;
+ }
+ pin = pin + group_idx * GPIOS_PER_GROUP;
+ aspeed_gpio_set_pin_level(s, set_idx, pin, level);
+}
+
+/****************** Setup functions ******************/
+static const GPIOSetProperties ast2400_set_props[ASPEED_GPIO_MAX_NR_SETS] = {
+ [0] = {0xffffffff, 0xffffffff, {"A", "B", "C", "D"} },
+ [1] = {0xffffffff, 0xffffffff, {"E", "F", "G", "H"} },
+ [2] = {0xffffffff, 0xffffffff, {"I", "J", "K", "L"} },
+ [3] = {0xffffffff, 0xffffffff, {"M", "N", "O", "P"} },
+ [4] = {0xffffffff, 0xffffffff, {"Q", "R", "S", "T"} },
+ [5] = {0xffffffff, 0x0000ffff, {"U", "V", "W", "X"} },
+ [6] = {0x0000000f, 0x0fffff0f, {"Y", "Z", "AA", "AB"} },
+};
+
+static const GPIOSetProperties ast2500_set_props[ASPEED_GPIO_MAX_NR_SETS] = {
+ [0] = {0xffffffff, 0xffffffff, {"A", "B", "C", "D"} },
+ [1] = {0xffffffff, 0xffffffff, {"E", "F", "G", "H"} },
+ [2] = {0xffffffff, 0xffffffff, {"I", "J", "K", "L"} },
+ [3] = {0xffffffff, 0xffffffff, {"M", "N", "O", "P"} },
+ [4] = {0xffffffff, 0xffffffff, {"Q", "R", "S", "T"} },
+ [5] = {0xffffffff, 0x0000ffff, {"U", "V", "W", "X"} },
+ [6] = {0x0fffffff, 0x0fffffff, {"Y", "Z", "AA", "AB"} },
+ [7] = {0x000000ff, 0x000000ff, {"AC"} },
+};
+
+static GPIOSetProperties ast2600_3_3v_set_props[ASPEED_GPIO_MAX_NR_SETS] = {
+ [0] = {0xffffffff, 0xffffffff, {"A", "B", "C", "D"} },
+ [1] = {0xffffffff, 0xffffffff, {"E", "F", "G", "H"} },
+ [2] = {0xffffffff, 0xffffffff, {"I", "J", "K", "L"} },
+ [3] = {0xffffffff, 0xffffffff, {"M", "N", "O", "P"} },
+ [4] = {0xffffffff, 0x00ffffff, {"Q", "R", "S", "T"} },
+ [5] = {0xffffffff, 0xffffff00, {"U", "V", "W", "X"} },
+ [6] = {0x0000ffff, 0x0000ffff, {"Y", "Z"} },
+};
+
+static GPIOSetProperties ast2600_1_8v_set_props[ASPEED_GPIO_MAX_NR_SETS] = {
+ [0] = {0xffffffff, 0xffffffff, {"18A", "18B", "18C", "18D"} },
+ [1] = {0x0000000f, 0x0000000f, {"18E"} },
+};
+
+static const MemoryRegionOps aspeed_gpio_ops = {
+ .read = aspeed_gpio_read,
+ .write = aspeed_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void aspeed_gpio_reset(DeviceState *dev)
+{
+ AspeedGPIOState *s = ASPEED_GPIO(dev);
+
+ /* TODO: respect the reset tolerance registers */
+ memset(s->sets, 0, sizeof(s->sets));
+}
+
+static void aspeed_gpio_realize(DeviceState *dev, Error **errp)
+{
+ AspeedGPIOState *s = ASPEED_GPIO(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+
+ /* Interrupt parent line */
+ sysbus_init_irq(sbd, &s->irq);
+
+ /* Individual GPIOs */
+ for (int i = 0; i < ASPEED_GPIO_MAX_NR_SETS; i++) {
+ const GPIOSetProperties *props = &agc->props[i];
+ uint32_t skip = ~(props->input | props->output);
+ for (int j = 0; j < ASPEED_GPIOS_PER_SET; j++) {
+ if (skip >> j & 1) {
+ continue;
+ }
+ sysbus_init_irq(sbd, &s->gpios[i][j]);
+ }
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_gpio_ops, s,
+ TYPE_ASPEED_GPIO, 0x800);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void aspeed_gpio_init(Object *obj)
+{
+ AspeedGPIOState *s = ASPEED_GPIO(obj);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+
+ for (int i = 0; i < ASPEED_GPIO_MAX_NR_SETS; i++) {
+ const GPIOSetProperties *props = &agc->props[i];
+ uint32_t skip = ~(props->input | props->output);
+ for (int j = 0; j < ASPEED_GPIOS_PER_SET; j++) {
+ if (skip >> j & 1) {
+ continue;
+ }
+ int group_idx = j / GPIOS_PER_GROUP;
+ int pin_idx = j % GPIOS_PER_GROUP;
+ const char *group = &props->group_label[group_idx][0];
+ char *name = g_strdup_printf("gpio%s%d", group, pin_idx);
+ object_property_add(obj, name, "bool", aspeed_gpio_get_pin,
+ aspeed_gpio_set_pin, NULL, NULL);
+ g_free(name);
+ }
+ }
+}
+
+static const VMStateDescription vmstate_gpio_regs = {
+ .name = TYPE_ASPEED_GPIO"/regs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(data_value, GPIOSets),
+ VMSTATE_UINT32(data_read, GPIOSets),
+ VMSTATE_UINT32(direction, GPIOSets),
+ VMSTATE_UINT32(int_enable, GPIOSets),
+ VMSTATE_UINT32(int_sens_0, GPIOSets),
+ VMSTATE_UINT32(int_sens_1, GPIOSets),
+ VMSTATE_UINT32(int_sens_2, GPIOSets),
+ VMSTATE_UINT32(int_status, GPIOSets),
+ VMSTATE_UINT32(reset_tol, GPIOSets),
+ VMSTATE_UINT32(cmd_source_0, GPIOSets),
+ VMSTATE_UINT32(cmd_source_1, GPIOSets),
+ VMSTATE_UINT32(debounce_1, GPIOSets),
+ VMSTATE_UINT32(debounce_2, GPIOSets),
+ VMSTATE_UINT32(input_mask, GPIOSets),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static const VMStateDescription vmstate_aspeed_gpio = {
+ .name = TYPE_ASPEED_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(sets, AspeedGPIOState, ASPEED_GPIO_MAX_NR_SETS,
+ 1, vmstate_gpio_regs, GPIOSets),
+ VMSTATE_UINT32_ARRAY(debounce_regs, AspeedGPIOState,
+ ASPEED_GPIO_NR_DEBOUNCE_REGS),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void aspeed_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = aspeed_gpio_realize;
+ dc->reset = aspeed_gpio_reset;
+ dc->desc = "Aspeed GPIO Controller";
+ dc->vmsd = &vmstate_aspeed_gpio;
+}
+
+static void aspeed_gpio_ast2400_class_init(ObjectClass *klass, void *data)
+{
+ AspeedGPIOClass *agc = ASPEED_GPIO_CLASS(klass);
+
+ agc->props = ast2400_set_props;
+ agc->nr_gpio_pins = 216;
+ agc->nr_gpio_sets = 7;
+ agc->reg_table = aspeed_3_3v_gpios;
+}
+
+static void aspeed_gpio_2500_class_init(ObjectClass *klass, void *data)
+{
+ AspeedGPIOClass *agc = ASPEED_GPIO_CLASS(klass);
+
+ agc->props = ast2500_set_props;
+ agc->nr_gpio_pins = 228;
+ agc->nr_gpio_sets = 8;
+ agc->reg_table = aspeed_3_3v_gpios;
+}
+
+static void aspeed_gpio_ast2600_3_3v_class_init(ObjectClass *klass, void *data)
+{
+ AspeedGPIOClass *agc = ASPEED_GPIO_CLASS(klass);
+
+ agc->props = ast2600_3_3v_set_props;
+ agc->nr_gpio_pins = 208;
+ agc->nr_gpio_sets = 7;
+ agc->reg_table = aspeed_3_3v_gpios;
+}
+
+static void aspeed_gpio_ast2600_1_8v_class_init(ObjectClass *klass, void *data)
+{
+ AspeedGPIOClass *agc = ASPEED_GPIO_CLASS(klass);
+
+ agc->props = ast2600_1_8v_set_props;
+ agc->nr_gpio_pins = 36;
+ agc->nr_gpio_sets = 2;
+ agc->reg_table = aspeed_1_8v_gpios;
+}
+
+static const TypeInfo aspeed_gpio_info = {
+ .name = TYPE_ASPEED_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedGPIOState),
+ .class_size = sizeof(AspeedGPIOClass),
+ .class_init = aspeed_gpio_class_init,
+ .abstract = true,
+};
+
+static const TypeInfo aspeed_gpio_ast2400_info = {
+ .name = TYPE_ASPEED_GPIO "-ast2400",
+ .parent = TYPE_ASPEED_GPIO,
+ .class_init = aspeed_gpio_ast2400_class_init,
+ .instance_init = aspeed_gpio_init,
+};
+
+static const TypeInfo aspeed_gpio_ast2500_info = {
+ .name = TYPE_ASPEED_GPIO "-ast2500",
+ .parent = TYPE_ASPEED_GPIO,
+ .class_init = aspeed_gpio_2500_class_init,
+ .instance_init = aspeed_gpio_init,
+};
+
+static const TypeInfo aspeed_gpio_ast2600_3_3v_info = {
+ .name = TYPE_ASPEED_GPIO "-ast2600",
+ .parent = TYPE_ASPEED_GPIO,
+ .class_init = aspeed_gpio_ast2600_3_3v_class_init,
+ .instance_init = aspeed_gpio_init,
+};
+
+static const TypeInfo aspeed_gpio_ast2600_1_8v_info = {
+ .name = TYPE_ASPEED_GPIO "-ast2600-1_8v",
+ .parent = TYPE_ASPEED_GPIO,
+ .class_init = aspeed_gpio_ast2600_1_8v_class_init,
+ .instance_init = aspeed_gpio_init,
+};
+
+static void aspeed_gpio_register_types(void)
+{
+ type_register_static(&aspeed_gpio_info);
+ type_register_static(&aspeed_gpio_ast2400_info);
+ type_register_static(&aspeed_gpio_ast2500_info);
+ type_register_static(&aspeed_gpio_ast2600_3_3v_info);
+ type_register_static(&aspeed_gpio_ast2600_1_8v_info);
+}
+
+type_init(aspeed_gpio_register_types);
diff --git a/hw/gpio/bcm2835_gpio.c b/hw/gpio/bcm2835_gpio.c
new file mode 100644
index 000000000..c995bba1d
--- /dev/null
+++ b/hw/gpio/bcm2835_gpio.c
@@ -0,0 +1,344 @@
+/*
+ * Raspberry Pi (BCM2835) GPIO Controller
+ *
+ * Copyright (c) 2017 Antfield SAS
+ *
+ * Authors:
+ * Clement Deschamps <clement.deschamps@antfield.fr>
+ * Luc Michel <luc.michel@antfield.fr>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/sd/sd.h"
+#include "hw/gpio/bcm2835_gpio.h"
+#include "hw/irq.h"
+
+#define GPFSEL0 0x00
+#define GPFSEL1 0x04
+#define GPFSEL2 0x08
+#define GPFSEL3 0x0C
+#define GPFSEL4 0x10
+#define GPFSEL5 0x14
+#define GPSET0 0x1C
+#define GPSET1 0x20
+#define GPCLR0 0x28
+#define GPCLR1 0x2C
+#define GPLEV0 0x34
+#define GPLEV1 0x38
+#define GPEDS0 0x40
+#define GPEDS1 0x44
+#define GPREN0 0x4C
+#define GPREN1 0x50
+#define GPFEN0 0x58
+#define GPFEN1 0x5C
+#define GPHEN0 0x64
+#define GPHEN1 0x68
+#define GPLEN0 0x70
+#define GPLEN1 0x74
+#define GPAREN0 0x7C
+#define GPAREN1 0x80
+#define GPAFEN0 0x88
+#define GPAFEN1 0x8C
+#define GPPUD 0x94
+#define GPPUDCLK0 0x98
+#define GPPUDCLK1 0x9C
+
+static uint32_t gpfsel_get(BCM2835GpioState *s, uint8_t reg)
+{
+ int i;
+ uint32_t value = 0;
+ for (i = 0; i < 10; i++) {
+ uint32_t index = 10 * reg + i;
+ if (index < sizeof(s->fsel)) {
+ value |= (s->fsel[index] & 0x7) << (3 * i);
+ }
+ }
+ return value;
+}
+
+static void gpfsel_set(BCM2835GpioState *s, uint8_t reg, uint32_t value)
+{
+ int i;
+ for (i = 0; i < 10; i++) {
+ uint32_t index = 10 * reg + i;
+ if (index < sizeof(s->fsel)) {
+ int fsel = (value >> (3 * i)) & 0x7;
+ s->fsel[index] = fsel;
+ }
+ }
+
+ /* SD controller selection (48-53) */
+ if (s->sd_fsel != 0
+ && (s->fsel[48] == 0) /* SD_CLK_R */
+ && (s->fsel[49] == 0) /* SD_CMD_R */
+ && (s->fsel[50] == 0) /* SD_DATA0_R */
+ && (s->fsel[51] == 0) /* SD_DATA1_R */
+ && (s->fsel[52] == 0) /* SD_DATA2_R */
+ && (s->fsel[53] == 0) /* SD_DATA3_R */
+ ) {
+ /* SDHCI controller selected */
+ sdbus_reparent_card(s->sdbus_sdhost, s->sdbus_sdhci);
+ s->sd_fsel = 0;
+ } else if (s->sd_fsel != 4
+ && (s->fsel[48] == 4) /* SD_CLK_R */
+ && (s->fsel[49] == 4) /* SD_CMD_R */
+ && (s->fsel[50] == 4) /* SD_DATA0_R */
+ && (s->fsel[51] == 4) /* SD_DATA1_R */
+ && (s->fsel[52] == 4) /* SD_DATA2_R */
+ && (s->fsel[53] == 4) /* SD_DATA3_R */
+ ) {
+ /* SDHost controller selected */
+ sdbus_reparent_card(s->sdbus_sdhci, s->sdbus_sdhost);
+ s->sd_fsel = 4;
+ }
+}
+
+static int gpfsel_is_out(BCM2835GpioState *s, int index)
+{
+ if (index >= 0 && index < 54) {
+ return s->fsel[index] == 1;
+ }
+ return 0;
+}
+
+static void gpset(BCM2835GpioState *s,
+ uint32_t val, uint8_t start, uint8_t count, uint32_t *lev)
+{
+ uint32_t changes = val & ~*lev;
+ uint32_t cur = 1;
+
+ int i;
+ for (i = 0; i < count; i++) {
+ if ((changes & cur) && (gpfsel_is_out(s, start + i))) {
+ qemu_set_irq(s->out[start + i], 1);
+ }
+ cur <<= 1;
+ }
+
+ *lev |= val;
+}
+
+static void gpclr(BCM2835GpioState *s,
+ uint32_t val, uint8_t start, uint8_t count, uint32_t *lev)
+{
+ uint32_t changes = val & *lev;
+ uint32_t cur = 1;
+
+ int i;
+ for (i = 0; i < count; i++) {
+ if ((changes & cur) && (gpfsel_is_out(s, start + i))) {
+ qemu_set_irq(s->out[start + i], 0);
+ }
+ cur <<= 1;
+ }
+
+ *lev &= ~val;
+}
+
+static uint64_t bcm2835_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835GpioState *s = (BCM2835GpioState *)opaque;
+
+ switch (offset) {
+ case GPFSEL0:
+ case GPFSEL1:
+ case GPFSEL2:
+ case GPFSEL3:
+ case GPFSEL4:
+ case GPFSEL5:
+ return gpfsel_get(s, offset / 4);
+ case GPSET0:
+ case GPSET1:
+ /* Write Only */
+ return 0;
+ case GPCLR0:
+ case GPCLR1:
+ /* Write Only */
+ return 0;
+ case GPLEV0:
+ return s->lev0;
+ case GPLEV1:
+ return s->lev1;
+ case GPEDS0:
+ case GPEDS1:
+ case GPREN0:
+ case GPREN1:
+ case GPFEN0:
+ case GPFEN1:
+ case GPHEN0:
+ case GPHEN1:
+ case GPLEN0:
+ case GPLEN1:
+ case GPAREN0:
+ case GPAREN1:
+ case GPAFEN0:
+ case GPAFEN1:
+ case GPPUD:
+ case GPPUDCLK0:
+ case GPPUDCLK1:
+ /* Not implemented */
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void bcm2835_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835GpioState *s = (BCM2835GpioState *)opaque;
+
+ switch (offset) {
+ case GPFSEL0:
+ case GPFSEL1:
+ case GPFSEL2:
+ case GPFSEL3:
+ case GPFSEL4:
+ case GPFSEL5:
+ gpfsel_set(s, offset / 4, value);
+ break;
+ case GPSET0:
+ gpset(s, value, 0, 32, &s->lev0);
+ break;
+ case GPSET1:
+ gpset(s, value, 32, 22, &s->lev1);
+ break;
+ case GPCLR0:
+ gpclr(s, value, 0, 32, &s->lev0);
+ break;
+ case GPCLR1:
+ gpclr(s, value, 32, 22, &s->lev1);
+ break;
+ case GPLEV0:
+ case GPLEV1:
+ /* Read Only */
+ break;
+ case GPEDS0:
+ case GPEDS1:
+ case GPREN0:
+ case GPREN1:
+ case GPFEN0:
+ case GPFEN1:
+ case GPHEN0:
+ case GPHEN1:
+ case GPLEN0:
+ case GPLEN1:
+ case GPAREN0:
+ case GPAREN1:
+ case GPAFEN0:
+ case GPAFEN1:
+ case GPPUD:
+ case GPPUDCLK0:
+ case GPPUDCLK1:
+ /* Not implemented */
+ break;
+ default:
+ goto err_out;
+ }
+ return;
+
+err_out:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+}
+
+static void bcm2835_gpio_reset(DeviceState *dev)
+{
+ BCM2835GpioState *s = BCM2835_GPIO(dev);
+
+ int i;
+ for (i = 0; i < 6; i++) {
+ gpfsel_set(s, i, 0);
+ }
+
+ s->sd_fsel = 0;
+
+ /* SDHCI is selected by default */
+ sdbus_reparent_card(&s->sdbus, s->sdbus_sdhci);
+
+ s->lev0 = 0;
+ s->lev1 = 0;
+}
+
+static const MemoryRegionOps bcm2835_gpio_ops = {
+ .read = bcm2835_gpio_read,
+ .write = bcm2835_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_bcm2835_gpio = {
+ .name = "bcm2835_gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(fsel, BCM2835GpioState, 54),
+ VMSTATE_UINT32(lev0, BCM2835GpioState),
+ VMSTATE_UINT32(lev1, BCM2835GpioState),
+ VMSTATE_UINT8(sd_fsel, BCM2835GpioState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_gpio_init(Object *obj)
+{
+ BCM2835GpioState *s = BCM2835_GPIO(obj);
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SD_BUS, DEVICE(s), "sd-bus");
+
+ memory_region_init_io(&s->iomem, obj,
+ &bcm2835_gpio_ops, s, "bcm2835_gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ qdev_init_gpio_out(dev, s->out, 54);
+}
+
+static void bcm2835_gpio_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835GpioState *s = BCM2835_GPIO(dev);
+ Object *obj;
+
+ obj = object_property_get_link(OBJECT(dev), "sdbus-sdhci", &error_abort);
+ s->sdbus_sdhci = SD_BUS(obj);
+
+ obj = object_property_get_link(OBJECT(dev), "sdbus-sdhost", &error_abort);
+ s->sdbus_sdhost = SD_BUS(obj);
+}
+
+static void bcm2835_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_bcm2835_gpio;
+ dc->realize = &bcm2835_gpio_realize;
+ dc->reset = &bcm2835_gpio_reset;
+}
+
+static const TypeInfo bcm2835_gpio_info = {
+ .name = TYPE_BCM2835_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835GpioState),
+ .instance_init = bcm2835_gpio_init,
+ .class_init = bcm2835_gpio_class_init,
+};
+
+static void bcm2835_gpio_register_types(void)
+{
+ type_register_static(&bcm2835_gpio_info);
+}
+
+type_init(bcm2835_gpio_register_types)
diff --git a/hw/gpio/gpio_key.c b/hw/gpio/gpio_key.c
new file mode 100644
index 000000000..74f613835
--- /dev/null
+++ b/hw/gpio/gpio_key.c
@@ -0,0 +1,109 @@
+/*
+ * GPIO key
+ *
+ * Copyright (c) 2016 Linaro Limited
+ *
+ * Author: Shannon Zhao <shannon.zhao@linaro.org>
+ *
+ * Emulate a (human) keypress -- when the key is triggered by
+ * setting the incoming gpio line, the outbound irq line is
+ * raised for 100ms before being dropped again.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License; 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+#define TYPE_GPIOKEY "gpio-key"
+OBJECT_DECLARE_SIMPLE_TYPE(GPIOKEYState, GPIOKEY)
+#define GPIO_KEY_LATENCY 100 /* 100ms */
+
+struct GPIOKEYState {
+ SysBusDevice parent_obj;
+
+ QEMUTimer *timer;
+ qemu_irq irq;
+};
+
+static const VMStateDescription vmstate_gpio_key = {
+ .name = "gpio-key",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER_PTR(timer, GPIOKEYState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void gpio_key_reset(DeviceState *dev)
+{
+ GPIOKEYState *s = GPIOKEY(dev);
+
+ timer_del(s->timer);
+}
+
+static void gpio_key_timer_expired(void *opaque)
+{
+ GPIOKEYState *s = (GPIOKEYState *)opaque;
+
+ qemu_set_irq(s->irq, 0);
+ timer_del(s->timer);
+}
+
+static void gpio_key_set_irq(void *opaque, int irq, int level)
+{
+ GPIOKEYState *s = (GPIOKEYState *)opaque;
+
+ qemu_set_irq(s->irq, 1);
+ timer_mod(s->timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + GPIO_KEY_LATENCY);
+}
+
+static void gpio_key_realize(DeviceState *dev, Error **errp)
+{
+ GPIOKEYState *s = GPIOKEY(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, gpio_key_set_irq, 1);
+ s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, gpio_key_timer_expired, s);
+}
+
+static void gpio_key_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = gpio_key_realize;
+ dc->vmsd = &vmstate_gpio_key;
+ dc->reset = &gpio_key_reset;
+}
+
+static const TypeInfo gpio_key_info = {
+ .name = TYPE_GPIOKEY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GPIOKEYState),
+ .class_init = gpio_key_class_init,
+};
+
+static void gpio_key_register_types(void)
+{
+ type_register_static(&gpio_key_info);
+}
+
+type_init(gpio_key_register_types)
diff --git a/hw/gpio/gpio_pwr.c b/hw/gpio/gpio_pwr.c
new file mode 100644
index 000000000..dbaf1c70c
--- /dev/null
+++ b/hw/gpio/gpio_pwr.c
@@ -0,0 +1,70 @@
+/*
+ * GPIO qemu power controller
+ *
+ * Copyright (c) 2020 Linaro Limited
+ *
+ * Author: Maxim Uvarov <maxim.uvarov@linaro.org>
+ *
+ * Virtual gpio driver which can be used on top of pl061
+ * to reboot and shutdown qemu virtual machine. One of use
+ * case is gpio driver for secure world application (ARM
+ * Trusted Firmware.).
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * QEMU interface:
+ * two named input GPIO lines:
+ * 'reset' : when asserted, trigger system reset
+ * 'shutdown' : when asserted, trigger system shutdown
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "sysemu/runstate.h"
+
+#define TYPE_GPIOPWR "gpio-pwr"
+OBJECT_DECLARE_SIMPLE_TYPE(GPIO_PWR_State, GPIOPWR)
+
+struct GPIO_PWR_State {
+ SysBusDevice parent_obj;
+};
+
+static void gpio_pwr_reset(void *opaque, int n, int level)
+{
+ if (level) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+}
+
+static void gpio_pwr_shutdown(void *opaque, int n, int level)
+{
+ if (level) {
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+}
+
+static void gpio_pwr_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+
+ qdev_init_gpio_in_named(dev, gpio_pwr_reset, "reset", 1);
+ qdev_init_gpio_in_named(dev, gpio_pwr_shutdown, "shutdown", 1);
+}
+
+static const TypeInfo gpio_pwr_info = {
+ .name = TYPE_GPIOPWR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GPIO_PWR_State),
+ .instance_init = gpio_pwr_init,
+};
+
+static void gpio_pwr_register_types(void)
+{
+ type_register_static(&gpio_pwr_info);
+}
+
+type_init(gpio_pwr_register_types)
diff --git a/hw/gpio/imx_gpio.c b/hw/gpio/imx_gpio.c
new file mode 100644
index 000000000..7a591804a
--- /dev/null
+++ b/hw/gpio/imx_gpio.c
@@ -0,0 +1,355 @@
+/*
+ * i.MX processors GPIO emulation.
+ *
+ * Copyright (C) 2015 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/imx_gpio.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#ifndef DEBUG_IMX_GPIO
+#define DEBUG_IMX_GPIO 0
+#endif
+
+typedef enum IMXGPIOLevel {
+ IMX_GPIO_LEVEL_LOW = 0,
+ IMX_GPIO_LEVEL_HIGH = 1,
+} IMXGPIOLevel;
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX_GPIO) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_GPIO, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx_gpio_reg_name(uint32_t reg)
+{
+ switch (reg) {
+ case DR_ADDR:
+ return "DR";
+ case GDIR_ADDR:
+ return "GDIR";
+ case PSR_ADDR:
+ return "PSR";
+ case ICR1_ADDR:
+ return "ICR1";
+ case ICR2_ADDR:
+ return "ICR2";
+ case IMR_ADDR:
+ return "IMR";
+ case ISR_ADDR:
+ return "ISR";
+ case EDGE_SEL_ADDR:
+ return "EDGE_SEL";
+ default:
+ return "[?]";
+ }
+}
+
+static void imx_gpio_update_int(IMXGPIOState *s)
+{
+ if (s->has_upper_pin_irq) {
+ qemu_set_irq(s->irq[0], (s->isr & s->imr & 0x0000FFFF) ? 1 : 0);
+ qemu_set_irq(s->irq[1], (s->isr & s->imr & 0xFFFF0000) ? 1 : 0);
+ } else {
+ qemu_set_irq(s->irq[0], (s->isr & s->imr) ? 1 : 0);
+ }
+}
+
+static void imx_gpio_set_int_line(IMXGPIOState *s, int line, IMXGPIOLevel level)
+{
+ /* if this signal isn't configured as an input signal, nothing to do */
+ if (!extract32(s->gdir, line, 1)) {
+ return;
+ }
+
+ /* When set, EDGE_SEL overrides the ICR config */
+ if (extract32(s->edge_sel, line, 1)) {
+ /* we detect interrupt on rising and falling edge */
+ if (extract32(s->psr, line, 1) != level) {
+ /* level changed */
+ s->isr = deposit32(s->isr, line, 1, 1);
+ }
+ } else if (extract64(s->icr, 2*line + 1, 1)) {
+ /* interrupt is edge sensitive */
+ if (extract32(s->psr, line, 1) != level) {
+ /* level changed */
+ if (extract64(s->icr, 2*line, 1) != level) {
+ s->isr = deposit32(s->isr, line, 1, 1);
+ }
+ }
+ } else {
+ /* interrupt is level sensitive */
+ if (extract64(s->icr, 2*line, 1) == level) {
+ s->isr = deposit32(s->isr, line, 1, 1);
+ }
+ }
+}
+
+static void imx_gpio_set(void *opaque, int line, int level)
+{
+ IMXGPIOState *s = IMX_GPIO(opaque);
+ IMXGPIOLevel imx_level = level ? IMX_GPIO_LEVEL_HIGH : IMX_GPIO_LEVEL_LOW;
+
+ imx_gpio_set_int_line(s, line, imx_level);
+
+ /* this is an input signal, so set PSR */
+ s->psr = deposit32(s->psr, line, 1, imx_level);
+
+ imx_gpio_update_int(s);
+}
+
+static void imx_gpio_set_all_int_lines(IMXGPIOState *s)
+{
+ int i;
+
+ for (i = 0; i < IMX_GPIO_PIN_COUNT; i++) {
+ IMXGPIOLevel imx_level = extract32(s->psr, i, 1);
+ imx_gpio_set_int_line(s, i, imx_level);
+ }
+
+ imx_gpio_update_int(s);
+}
+
+static inline void imx_gpio_set_all_output_lines(IMXGPIOState *s)
+{
+ int i;
+
+ for (i = 0; i < IMX_GPIO_PIN_COUNT; i++) {
+ /*
+ * if the line is set as output, then forward the line
+ * level to its user.
+ */
+ if (extract32(s->gdir, i, 1) && s->output[i]) {
+ qemu_set_irq(s->output[i], extract32(s->dr, i, 1));
+ }
+ }
+}
+
+static uint64_t imx_gpio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ IMXGPIOState *s = IMX_GPIO(opaque);
+ uint32_t reg_value = 0;
+
+ switch (offset) {
+ case DR_ADDR:
+ /*
+ * depending on the "line" configuration, the bit values
+ * are coming either from DR or PSR
+ */
+ reg_value = (s->dr & s->gdir) | (s->psr & ~s->gdir);
+ break;
+
+ case GDIR_ADDR:
+ reg_value = s->gdir;
+ break;
+
+ case PSR_ADDR:
+ reg_value = s->psr & ~s->gdir;
+ break;
+
+ case ICR1_ADDR:
+ reg_value = extract64(s->icr, 0, 32);
+ break;
+
+ case ICR2_ADDR:
+ reg_value = extract64(s->icr, 32, 32);
+ break;
+
+ case IMR_ADDR:
+ reg_value = s->imr;
+ break;
+
+ case ISR_ADDR:
+ reg_value = s->isr;
+ break;
+
+ case EDGE_SEL_ADDR:
+ if (s->has_edge_sel) {
+ reg_value = s->edge_sel;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: EDGE_SEL register not "
+ "present on this version of GPIO device\n",
+ TYPE_IMX_GPIO, __func__);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX_GPIO, __func__, offset);
+ break;
+ }
+
+ DPRINTF("(%s) = 0x%" PRIx32 "\n", imx_gpio_reg_name(offset), reg_value);
+
+ return reg_value;
+}
+
+static void imx_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMXGPIOState *s = IMX_GPIO(opaque);
+
+ DPRINTF("(%s, value = 0x%" PRIx32 ")\n", imx_gpio_reg_name(offset),
+ (uint32_t)value);
+
+ switch (offset) {
+ case DR_ADDR:
+ s->dr = value;
+ imx_gpio_set_all_output_lines(s);
+ break;
+
+ case GDIR_ADDR:
+ s->gdir = value;
+ imx_gpio_set_all_output_lines(s);
+ imx_gpio_set_all_int_lines(s);
+ break;
+
+ case ICR1_ADDR:
+ s->icr = deposit64(s->icr, 0, 32, value);
+ imx_gpio_set_all_int_lines(s);
+ break;
+
+ case ICR2_ADDR:
+ s->icr = deposit64(s->icr, 32, 32, value);
+ imx_gpio_set_all_int_lines(s);
+ break;
+
+ case IMR_ADDR:
+ s->imr = value;
+ imx_gpio_update_int(s);
+ break;
+
+ case ISR_ADDR:
+ s->isr &= ~value;
+ imx_gpio_set_all_int_lines(s);
+ break;
+
+ case EDGE_SEL_ADDR:
+ if (s->has_edge_sel) {
+ s->edge_sel = value;
+ imx_gpio_set_all_int_lines(s);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: EDGE_SEL register not "
+ "present on this version of GPIO device\n",
+ TYPE_IMX_GPIO, __func__);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX_GPIO, __func__, offset);
+ break;
+ }
+
+ return;
+}
+
+static const MemoryRegionOps imx_gpio_ops = {
+ .read = imx_gpio_read,
+ .write = imx_gpio_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_imx_gpio = {
+ .name = TYPE_IMX_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dr, IMXGPIOState),
+ VMSTATE_UINT32(gdir, IMXGPIOState),
+ VMSTATE_UINT32(psr, IMXGPIOState),
+ VMSTATE_UINT64(icr, IMXGPIOState),
+ VMSTATE_UINT32(imr, IMXGPIOState),
+ VMSTATE_UINT32(isr, IMXGPIOState),
+ VMSTATE_BOOL(has_edge_sel, IMXGPIOState),
+ VMSTATE_UINT32(edge_sel, IMXGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property imx_gpio_properties[] = {
+ DEFINE_PROP_BOOL("has-edge-sel", IMXGPIOState, has_edge_sel, true),
+ DEFINE_PROP_BOOL("has-upper-pin-irq", IMXGPIOState, has_upper_pin_irq,
+ false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void imx_gpio_reset(DeviceState *dev)
+{
+ IMXGPIOState *s = IMX_GPIO(dev);
+
+ s->dr = 0;
+ s->gdir = 0;
+ s->psr = 0;
+ s->icr = 0;
+ s->imr = 0;
+ s->isr = 0;
+ s->edge_sel = 0;
+
+ imx_gpio_set_all_output_lines(s);
+ imx_gpio_update_int(s);
+}
+
+static void imx_gpio_realize(DeviceState *dev, Error **errp)
+{
+ IMXGPIOState *s = IMX_GPIO(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_gpio_ops, s,
+ TYPE_IMX_GPIO, IMX_GPIO_MEM_SIZE);
+
+ qdev_init_gpio_in(DEVICE(s), imx_gpio_set, IMX_GPIO_PIN_COUNT);
+ qdev_init_gpio_out(DEVICE(s), s->output, IMX_GPIO_PIN_COUNT);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[0]);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[1]);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+}
+
+static void imx_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_gpio_realize;
+ dc->reset = imx_gpio_reset;
+ device_class_set_props(dc, imx_gpio_properties);
+ dc->vmsd = &vmstate_imx_gpio;
+ dc->desc = "i.MX GPIO controller";
+}
+
+static const TypeInfo imx_gpio_info = {
+ .name = TYPE_IMX_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXGPIOState),
+ .class_init = imx_gpio_class_init,
+};
+
+static void imx_gpio_register_types(void)
+{
+ type_register_static(&imx_gpio_info);
+}
+
+type_init(imx_gpio_register_types)
diff --git a/hw/gpio/max7310.c b/hw/gpio/max7310.c
new file mode 100644
index 000000000..db6b5e3d7
--- /dev/null
+++ b/hw/gpio/max7310.c
@@ -0,0 +1,218 @@
+/*
+ * MAX7310 8-port GPIO expansion chip.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This file is licensed under GNU GPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TYPE_MAX7310 "max7310"
+OBJECT_DECLARE_SIMPLE_TYPE(MAX7310State, MAX7310)
+
+struct MAX7310State {
+ I2CSlave parent_obj;
+
+ int i2c_command_byte;
+ int len;
+
+ uint8_t level;
+ uint8_t direction;
+ uint8_t polarity;
+ uint8_t status;
+ uint8_t command;
+ qemu_irq handler[8];
+ qemu_irq *gpio_in;
+};
+
+static void max7310_reset(DeviceState *dev)
+{
+ MAX7310State *s = MAX7310(dev);
+
+ s->level &= s->direction;
+ s->direction = 0xff;
+ s->polarity = 0xf0;
+ s->status = 0x01;
+ s->command = 0x00;
+}
+
+static uint8_t max7310_rx(I2CSlave *i2c)
+{
+ MAX7310State *s = MAX7310(i2c);
+
+ switch (s->command) {
+ case 0x00: /* Input port */
+ return s->level ^ s->polarity;
+
+ case 0x01: /* Output port */
+ return s->level & ~s->direction;
+
+ case 0x02: /* Polarity inversion */
+ return s->polarity;
+
+ case 0x03: /* Configuration */
+ return s->direction;
+
+ case 0x04: /* Timeout */
+ return s->status;
+
+ case 0xff: /* Reserved */
+ return 0xff;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported register 0x02%" PRIx8 "\n",
+ __func__, s->command);
+ break;
+ }
+ return 0xff;
+}
+
+static int max7310_tx(I2CSlave *i2c, uint8_t data)
+{
+ MAX7310State *s = MAX7310(i2c);
+ uint8_t diff;
+ int line;
+
+ if (s->len ++ > 1) {
+#ifdef VERBOSE
+ printf("%s: message too long (%i bytes)\n", __func__, s->len);
+#endif
+ return 1;
+ }
+
+ if (s->i2c_command_byte) {
+ s->command = data;
+ s->i2c_command_byte = 0;
+ return 0;
+ }
+
+ switch (s->command) {
+ case 0x01: /* Output port */
+ for (diff = (data ^ s->level) & ~s->direction; diff;
+ diff &= ~(1 << line)) {
+ line = ctz32(diff);
+ if (s->handler[line])
+ qemu_set_irq(s->handler[line], (data >> line) & 1);
+ }
+ s->level = (s->level & s->direction) | (data & ~s->direction);
+ break;
+
+ case 0x02: /* Polarity inversion */
+ s->polarity = data;
+ break;
+
+ case 0x03: /* Configuration */
+ s->level &= ~(s->direction ^ data);
+ s->direction = data;
+ break;
+
+ case 0x04: /* Timeout */
+ s->status = data;
+ break;
+
+ case 0x00: /* Input port - ignore writes */
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported register 0x02%" PRIx8 "\n",
+ __func__, s->command);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int max7310_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MAX7310State *s = MAX7310(i2c);
+ s->len = 0;
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->i2c_command_byte = 1;
+ break;
+ case I2C_FINISH:
+#ifdef VERBOSE
+ if (s->len == 1)
+ printf("%s: message too short (%i bytes)\n", __func__, s->len);
+#endif
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_max7310 = {
+ .name = "max7310",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(i2c_command_byte, MAX7310State),
+ VMSTATE_INT32(len, MAX7310State),
+ VMSTATE_UINT8(level, MAX7310State),
+ VMSTATE_UINT8(direction, MAX7310State),
+ VMSTATE_UINT8(polarity, MAX7310State),
+ VMSTATE_UINT8(status, MAX7310State),
+ VMSTATE_UINT8(command, MAX7310State),
+ VMSTATE_I2C_SLAVE(parent_obj, MAX7310State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max7310_gpio_set(void *opaque, int line, int level)
+{
+ MAX7310State *s = (MAX7310State *) opaque;
+ assert(line >= 0 && line < ARRAY_SIZE(s->handler));
+
+ if (level)
+ s->level |= s->direction & (1 << line);
+ else
+ s->level &= ~(s->direction & (1 << line));
+}
+
+/* MAX7310 is SMBus-compatible (can be used with only SMBus protocols),
+ * but also accepts sequences that are not SMBus so return an I2C device. */
+static void max7310_realize(DeviceState *dev, Error **errp)
+{
+ I2CSlave *i2c = I2C_SLAVE(dev);
+ MAX7310State *s = MAX7310(dev);
+
+ qdev_init_gpio_in(&i2c->qdev, max7310_gpio_set, 8);
+ qdev_init_gpio_out(&i2c->qdev, s->handler, 8);
+}
+
+static void max7310_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = max7310_realize;
+ k->event = max7310_event;
+ k->recv = max7310_rx;
+ k->send = max7310_tx;
+ dc->reset = max7310_reset;
+ dc->vmsd = &vmstate_max7310;
+}
+
+static const TypeInfo max7310_info = {
+ .name = TYPE_MAX7310,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MAX7310State),
+ .class_init = max7310_class_init,
+};
+
+static void max7310_register_types(void)
+{
+ type_register_static(&max7310_info);
+}
+
+type_init(max7310_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
new file mode 100644
index 000000000..7bd6a5726
--- /dev/null
+++ b/hw/gpio/meson.build
@@ -0,0 +1,14 @@
+softmmu_ss.add(when: 'CONFIG_E500', if_true: files('mpc8xxx.c'))
+softmmu_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c'))
+softmmu_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c'))
+softmmu_ss.add(when: 'CONFIG_MAX7310', if_true: files('max7310.c'))
+softmmu_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.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'))
+softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
diff --git a/hw/gpio/mpc8xxx.c b/hw/gpio/mpc8xxx.c
new file mode 100644
index 000000000..cb42acb6d
--- /dev/null
+++ b/hw/gpio/mpc8xxx.c
@@ -0,0 +1,224 @@
+/*
+ * GPIO Controller for a lot of Freescale SoCs
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TYPE_MPC8XXX_GPIO "mpc8xxx_gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(MPC8XXXGPIOState, MPC8XXX_GPIO)
+
+struct MPC8XXXGPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq out[32];
+
+ uint32_t dir;
+ uint32_t odr;
+ uint32_t dat;
+ uint32_t ier;
+ uint32_t imr;
+ uint32_t icr;
+};
+
+static const VMStateDescription vmstate_mpc8xxx_gpio = {
+ .name = "mpc8xxx_gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dir, MPC8XXXGPIOState),
+ VMSTATE_UINT32(odr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(dat, MPC8XXXGPIOState),
+ VMSTATE_UINT32(ier, MPC8XXXGPIOState),
+ VMSTATE_UINT32(imr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(icr, MPC8XXXGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mpc8xxx_gpio_update(MPC8XXXGPIOState *s)
+{
+ qemu_set_irq(s->irq, !!(s->ier & s->imr));
+}
+
+static uint64_t mpc8xxx_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return 0;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ return s->dir;
+ case 0x4: /* Open Drain */
+ return s->odr;
+ case 0x8: /* Data */
+ return s->dat;
+ case 0xC: /* Interrupt Event */
+ return s->ier;
+ case 0x10: /* Interrupt Mask */
+ return s->imr;
+ case 0x14: /* Interrupt Control */
+ return s->icr;
+ default:
+ return 0;
+ }
+}
+
+static void mpc8xxx_write_data(MPC8XXXGPIOState *s, uint32_t new_data)
+{
+ uint32_t old_data = s->dat;
+ uint32_t diff = old_data ^ new_data;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ uint32_t mask = 0x80000000 >> i;
+ if (!(diff & mask)) {
+ continue;
+ }
+
+ if (s->dir & mask) {
+ /* Output */
+ qemu_set_irq(s->out[i], (new_data & mask) != 0);
+ }
+ }
+
+ s->dat = new_data;
+}
+
+static void mpc8xxx_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ s->dir = value;
+ break;
+ case 0x4: /* Open Drain */
+ s->odr = value;
+ break;
+ case 0x8: /* Data */
+ mpc8xxx_write_data(s, value);
+ break;
+ case 0xC: /* Interrupt Event */
+ s->ier &= ~value;
+ break;
+ case 0x10: /* Interrupt Mask */
+ s->imr = value;
+ break;
+ case 0x14: /* Interrupt Control */
+ s->icr = value;
+ break;
+ }
+
+ mpc8xxx_gpio_update(s);
+}
+
+static void mpc8xxx_gpio_reset(DeviceState *dev)
+{
+ MPC8XXXGPIOState *s = MPC8XXX_GPIO(dev);
+
+ s->dir = 0;
+ s->odr = 0;
+ s->dat = 0;
+ s->ier = 0;
+ s->imr = 0;
+ s->icr = 0;
+}
+
+static void mpc8xxx_gpio_set_irq(void * opaque, int irq, int level)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+ uint32_t mask;
+
+ mask = 0x80000000 >> irq;
+ if ((s->dir & mask) == 0) {
+ uint32_t old_value = s->dat & mask;
+
+ s->dat &= ~mask;
+ if (level)
+ s->dat |= mask;
+
+ if (!(s->icr & irq) || (old_value && !level)) {
+ s->ier |= mask;
+ }
+
+ mpc8xxx_gpio_update(s);
+ }
+}
+
+static const MemoryRegionOps mpc8xxx_gpio_ops = {
+ .read = mpc8xxx_gpio_read,
+ .write = mpc8xxx_gpio_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static void mpc8xxx_gpio_initfn(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ MPC8XXXGPIOState *s = MPC8XXX_GPIO(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &mpc8xxx_gpio_ops,
+ s, "mpc8xxx_gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, mpc8xxx_gpio_set_irq, 32);
+ qdev_init_gpio_out(dev, s->out, 32);
+}
+
+static void mpc8xxx_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_mpc8xxx_gpio;
+ dc->reset = mpc8xxx_gpio_reset;
+}
+
+static const TypeInfo mpc8xxx_gpio_info = {
+ .name = TYPE_MPC8XXX_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPC8XXXGPIOState),
+ .instance_init = mpc8xxx_gpio_initfn,
+ .class_init = mpc8xxx_gpio_class_init,
+};
+
+static void mpc8xxx_gpio_register_types(void)
+{
+ type_register_static(&mpc8xxx_gpio_info);
+}
+
+type_init(mpc8xxx_gpio_register_types)
diff --git a/hw/gpio/npcm7xx_gpio.c b/hw/gpio/npcm7xx_gpio.c
new file mode 100644
index 000000000..3376901ab
--- /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/nrf51_gpio.c b/hw/gpio/nrf51_gpio.c
new file mode 100644
index 000000000..b47fddf4e
--- /dev/null
+++ b/hw/gpio/nrf51_gpio.c
@@ -0,0 +1,318 @@
+/*
+ * nRF51 System-on-Chip general purpose input/output register definition
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ * Product Spec: http://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.1.pdf
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/gpio/nrf51_gpio.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+/*
+ * Check if the output driver is connected to the direction switch
+ * given the current configuration and logic level.
+ * It is not differentiated between standard and "high"(-power) drive modes.
+ */
+static bool is_connected(uint32_t config, uint32_t level)
+{
+ bool state;
+ uint32_t drive_config = extract32(config, 8, 3);
+
+ switch (drive_config) {
+ case 0 ... 3:
+ state = true;
+ break;
+ case 4 ... 5:
+ state = level != 0;
+ break;
+ case 6 ... 7:
+ state = level == 0;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ return state;
+}
+
+static int pull_value(uint32_t config)
+{
+ int pull = extract32(config, 2, 2);
+ if (pull == NRF51_GPIO_PULLDOWN) {
+ return 0;
+ } else if (pull == NRF51_GPIO_PULLUP) {
+ return 1;
+ }
+ return -1;
+}
+
+static void update_output_irq(NRF51GPIOState *s, size_t i,
+ bool connected, bool level)
+{
+ int64_t irq_level = connected ? level : -1;
+ bool old_connected = extract32(s->old_out_connected, i, 1);
+ bool old_level = extract32(s->old_out, i, 1);
+
+ if ((old_connected != connected) || (old_level != level)) {
+ qemu_set_irq(s->output[i], irq_level);
+ trace_nrf51_gpio_update_output_irq(i, irq_level);
+ }
+
+ s->old_out = deposit32(s->old_out, i, 1, level);
+ s->old_out_connected = deposit32(s->old_out_connected, i, 1, connected);
+}
+
+static void update_state(NRF51GPIOState *s)
+{
+ int pull;
+ size_t i;
+ bool connected_out, dir, connected_in, out, in, input;
+
+ for (i = 0; i < NRF51_GPIO_PINS; i++) {
+ pull = pull_value(s->cnf[i]);
+ dir = extract32(s->cnf[i], 0, 1);
+ connected_in = extract32(s->in_mask, i, 1);
+ out = extract32(s->out, i, 1);
+ in = extract32(s->in, i, 1);
+ input = !extract32(s->cnf[i], 1, 1);
+ connected_out = is_connected(s->cnf[i], out) && dir;
+
+ if (!input) {
+ if (pull >= 0) {
+ /* Input buffer disconnected from external drives */
+ s->in = deposit32(s->in, i, 1, pull);
+ }
+ } else {
+ if (connected_out && connected_in && out != in) {
+ /* Pin both driven externally and internally */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPIO pin %zu short circuited\n", i);
+ }
+ if (!connected_in) {
+ /*
+ * Floating input: the output stimulates IN if connected,
+ * otherwise pull-up/pull-down resistors put a value on both
+ * IN and OUT.
+ */
+ if (pull >= 0 && !connected_out) {
+ connected_out = true;
+ out = pull;
+ }
+ if (connected_out) {
+ s->in = deposit32(s->in, i, 1, out);
+ }
+ }
+ }
+ update_output_irq(s, i, connected_out, out);
+ }
+}
+
+/*
+ * Direction is exposed in both the DIR register and the DIR bit
+ * of each PINs CNF configuration register. Reflect bits for pins in DIR
+ * to individual pin configuration registers.
+ */
+static void reflect_dir_bit_in_cnf(NRF51GPIOState *s)
+{
+ size_t i;
+
+ uint32_t value = s->dir;
+
+ for (i = 0; i < NRF51_GPIO_PINS; i++) {
+ s->cnf[i] = (s->cnf[i] & ~(1UL)) | ((value >> i) & 0x01);
+ }
+}
+
+static uint64_t nrf51_gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ NRF51GPIOState *s = NRF51_GPIO(opaque);
+ uint64_t r = 0;
+ size_t idx;
+
+ switch (offset) {
+ case NRF51_GPIO_REG_OUT ... NRF51_GPIO_REG_OUTCLR:
+ r = s->out;
+ break;
+
+ case NRF51_GPIO_REG_IN:
+ r = s->in;
+ break;
+
+ case NRF51_GPIO_REG_DIR ... NRF51_GPIO_REG_DIRCLR:
+ r = s->dir;
+ break;
+
+ case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END:
+ idx = (offset - NRF51_GPIO_REG_CNF_START) / 4;
+ r = s->cnf[idx];
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ trace_nrf51_gpio_read(offset, r);
+
+ return r;
+}
+
+static void nrf51_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned int size)
+{
+ NRF51GPIOState *s = NRF51_GPIO(opaque);
+ size_t idx;
+
+ trace_nrf51_gpio_write(offset, value);
+
+ switch (offset) {
+ case NRF51_GPIO_REG_OUT:
+ s->out = value;
+ break;
+
+ case NRF51_GPIO_REG_OUTSET:
+ s->out |= value;
+ break;
+
+ case NRF51_GPIO_REG_OUTCLR:
+ s->out &= ~value;
+ break;
+
+ case NRF51_GPIO_REG_DIR:
+ s->dir = value;
+ reflect_dir_bit_in_cnf(s);
+ break;
+
+ case NRF51_GPIO_REG_DIRSET:
+ s->dir |= value;
+ reflect_dir_bit_in_cnf(s);
+ break;
+
+ case NRF51_GPIO_REG_DIRCLR:
+ s->dir &= ~value;
+ reflect_dir_bit_in_cnf(s);
+ break;
+
+ case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END:
+ idx = (offset - NRF51_GPIO_REG_CNF_START) / 4;
+ s->cnf[idx] = value;
+ /*
+ * direction is exposed in both the DIR register and the DIR bit
+ * of each PINs CNF configuration register.
+ */
+ s->dir = (s->dir & ~(1UL << idx)) | ((value & 0x01) << idx);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ update_state(s);
+}
+
+static const MemoryRegionOps gpio_ops = {
+ .read = nrf51_gpio_read,
+ .write = nrf51_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static void nrf51_gpio_set(void *opaque, int line, int value)
+{
+ NRF51GPIOState *s = NRF51_GPIO(opaque);
+
+ trace_nrf51_gpio_set(line, value);
+
+ assert(line >= 0 && line < NRF51_GPIO_PINS);
+
+ s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
+ if (value >= 0) {
+ s->in = deposit32(s->in, line, 1, value != 0);
+ }
+
+ update_state(s);
+}
+
+static void nrf51_gpio_reset(DeviceState *dev)
+{
+ NRF51GPIOState *s = NRF51_GPIO(dev);
+ size_t i;
+
+ s->out = 0;
+ s->old_out = 0;
+ s->old_out_connected = 0;
+ s->in = 0;
+ s->in_mask = 0;
+ s->dir = 0;
+
+ for (i = 0; i < NRF51_GPIO_PINS; i++) {
+ s->cnf[i] = 0x00000002;
+ }
+}
+
+static const VMStateDescription vmstate_nrf51_gpio = {
+ .name = TYPE_NRF51_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(out, NRF51GPIOState),
+ VMSTATE_UINT32(in, NRF51GPIOState),
+ VMSTATE_UINT32(in_mask, NRF51GPIOState),
+ VMSTATE_UINT32(dir, NRF51GPIOState),
+ VMSTATE_UINT32_ARRAY(cnf, NRF51GPIOState, NRF51_GPIO_PINS),
+ VMSTATE_UINT32(old_out, NRF51GPIOState),
+ VMSTATE_UINT32(old_out_connected, NRF51GPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void nrf51_gpio_init(Object *obj)
+{
+ NRF51GPIOState *s = NRF51_GPIO(obj);
+
+ memory_region_init_io(&s->mmio, obj, &gpio_ops, s,
+ TYPE_NRF51_GPIO, NRF51_GPIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ qdev_init_gpio_in(DEVICE(s), nrf51_gpio_set, NRF51_GPIO_PINS);
+ qdev_init_gpio_out(DEVICE(s), s->output, NRF51_GPIO_PINS);
+}
+
+static void nrf51_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_nrf51_gpio;
+ dc->reset = nrf51_gpio_reset;
+ dc->desc = "nRF51 GPIO";
+}
+
+static const TypeInfo nrf51_gpio_info = {
+ .name = TYPE_NRF51_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NRF51GPIOState),
+ .instance_init = nrf51_gpio_init,
+ .class_init = nrf51_gpio_class_init
+};
+
+static void nrf51_gpio_register_types(void)
+{
+ type_register_static(&nrf51_gpio_info);
+}
+
+type_init(nrf51_gpio_register_types)
diff --git a/hw/gpio/omap_gpio.c b/hw/gpio/omap_gpio.c
new file mode 100644
index 000000000..e25084b40
--- /dev/null
+++ b/hw/gpio/omap_gpio.c
@@ -0,0 +1,813 @@
+/*
+ * TI OMAP processors GPIO emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2009 Nokia Corporation
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/arm/omap.h"
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+
+struct omap_gpio_s {
+ qemu_irq irq;
+ qemu_irq handler[16];
+
+ uint16_t inputs;
+ uint16_t outputs;
+ uint16_t dir;
+ uint16_t edge;
+ uint16_t mask;
+ uint16_t ints;
+ uint16_t pins;
+};
+
+struct omap_gpif_s {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int mpu_model;
+ void *clk;
+ struct omap_gpio_s omap1;
+};
+
+/* General-Purpose I/O of OMAP1 */
+static void omap_gpio_set(void *opaque, int line, int level)
+{
+ struct omap_gpio_s *s = &((struct omap_gpif_s *) opaque)->omap1;
+ uint16_t prev = s->inputs;
+
+ if (level)
+ s->inputs |= 1 << line;
+ else
+ s->inputs &= ~(1 << line);
+
+ if (((s->edge & s->inputs & ~prev) | (~s->edge & ~s->inputs & prev)) &
+ (1 << line) & s->dir & ~s->mask) {
+ s->ints |= 1 << line;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static uint64_t omap_gpio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpio_s *s = (struct omap_gpio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (offset) {
+ case 0x00: /* DATA_INPUT */
+ return s->inputs & s->pins;
+
+ case 0x04: /* DATA_OUTPUT */
+ return s->outputs;
+
+ case 0x08: /* DIRECTION_CONTROL */
+ return s->dir;
+
+ case 0x0c: /* INTERRUPT_CONTROL */
+ return s->edge;
+
+ case 0x10: /* INTERRUPT_MASK */
+ return s->mask;
+
+ case 0x14: /* INTERRUPT_STATUS */
+ return s->ints;
+
+ case 0x18: /* PIN_CONTROL (not in OMAP310) */
+ OMAP_BAD_REG(addr);
+ return s->pins;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_gpio_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpio_s *s = (struct omap_gpio_s *) opaque;
+ int offset = addr & OMAP_MPUI_REG_MASK;
+ uint16_t diff;
+ int ln;
+
+ if (size != 2) {
+ omap_badwidth_write16(opaque, addr, value);
+ return;
+ }
+
+ switch (offset) {
+ case 0x00: /* DATA_INPUT */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x04: /* DATA_OUTPUT */
+ diff = (s->outputs ^ value) & ~s->dir;
+ s->outputs = value;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x08: /* DIRECTION_CONTROL */
+ diff = s->outputs & (s->dir ^ value);
+ s->dir = value;
+
+ value = s->outputs & ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ if (s->handler[ln])
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+ break;
+
+ case 0x0c: /* INTERRUPT_CONTROL */
+ s->edge = value;
+ break;
+
+ case 0x10: /* INTERRUPT_MASK */
+ s->mask = value;
+ break;
+
+ case 0x14: /* INTERRUPT_STATUS */
+ s->ints &= ~value;
+ if (!s->ints)
+ qemu_irq_lower(s->irq);
+ break;
+
+ case 0x18: /* PIN_CONTROL (not in OMAP310 TRM) */
+ OMAP_BAD_REG(addr);
+ s->pins = value;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+/* *Some* sources say the memory region is 32-bit. */
+static const MemoryRegionOps omap_gpio_ops = {
+ .read = omap_gpio_read,
+ .write = omap_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_gpio_reset(struct omap_gpio_s *s)
+{
+ s->inputs = 0;
+ s->outputs = ~0;
+ s->dir = ~0;
+ s->edge = ~0;
+ s->mask = ~0;
+ s->ints = 0;
+ s->pins = ~0;
+}
+
+struct omap2_gpio_s {
+ qemu_irq irq[2];
+ qemu_irq wkup;
+ qemu_irq *handler;
+ MemoryRegion iomem;
+
+ uint8_t revision;
+ uint8_t config[2];
+ uint32_t inputs;
+ uint32_t outputs;
+ uint32_t dir;
+ uint32_t level[2];
+ uint32_t edge[2];
+ uint32_t mask[2];
+ uint32_t wumask;
+ uint32_t ints[2];
+ uint32_t debounce;
+ uint8_t delay;
+};
+
+struct omap2_gpif_s {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int mpu_model;
+ void *iclk;
+ void *fclk[6];
+ int modulecount;
+ struct omap2_gpio_s *modules;
+ qemu_irq *handler;
+ int autoidle;
+ int gpo;
+};
+
+/* General-Purpose Interface of OMAP2/3 */
+static inline void omap2_gpio_module_int_update(struct omap2_gpio_s *s,
+ int line)
+{
+ qemu_set_irq(s->irq[line], s->ints[line] & s->mask[line]);
+}
+
+static void omap2_gpio_module_wake(struct omap2_gpio_s *s, int line)
+{
+ if (!(s->config[0] & (1 << 2))) /* ENAWAKEUP */
+ return;
+ if (!(s->config[0] & (3 << 3))) /* Force Idle */
+ return;
+ if (!(s->wumask & (1 << line)))
+ return;
+
+ qemu_irq_raise(s->wkup);
+}
+
+static inline void omap2_gpio_module_out_update(struct omap2_gpio_s *s,
+ uint32_t diff)
+{
+ int ln;
+
+ s->outputs ^= diff;
+ diff &= ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ qemu_set_irq(s->handler[ln], (s->outputs >> ln) & 1);
+ diff &= ~(1 << ln);
+ }
+}
+
+static void omap2_gpio_module_level_update(struct omap2_gpio_s *s, int line)
+{
+ s->ints[line] |= s->dir &
+ ((s->inputs & s->level[1]) | (~s->inputs & s->level[0]));
+ omap2_gpio_module_int_update(s, line);
+}
+
+static inline void omap2_gpio_module_int(struct omap2_gpio_s *s, int line)
+{
+ s->ints[0] |= 1 << line;
+ omap2_gpio_module_int_update(s, 0);
+ s->ints[1] |= 1 << line;
+ omap2_gpio_module_int_update(s, 1);
+ omap2_gpio_module_wake(s, line);
+}
+
+static void omap2_gpio_set(void *opaque, int line, int level)
+{
+ struct omap2_gpif_s *p = opaque;
+ struct omap2_gpio_s *s = &p->modules[line >> 5];
+
+ line &= 31;
+ if (level) {
+ if (s->dir & (1 << line) & ((~s->inputs & s->edge[0]) | s->level[1]))
+ omap2_gpio_module_int(s, line);
+ s->inputs |= 1 << line;
+ } else {
+ if (s->dir & (1 << line) & ((s->inputs & s->edge[1]) | s->level[0]))
+ omap2_gpio_module_int(s, line);
+ s->inputs &= ~(1 << line);
+ }
+}
+
+static void omap2_gpio_module_reset(struct omap2_gpio_s *s)
+{
+ s->config[0] = 0;
+ s->config[1] = 2;
+ s->ints[0] = 0;
+ s->ints[1] = 0;
+ s->mask[0] = 0;
+ s->mask[1] = 0;
+ s->wumask = 0;
+ s->dir = ~0;
+ s->level[0] = 0;
+ s->level[1] = 0;
+ s->edge[0] = 0;
+ s->edge[1] = 0;
+ s->debounce = 0;
+ s->delay = 0;
+}
+
+static uint32_t omap2_gpio_module_read(void *opaque, hwaddr addr)
+{
+ struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* GPIO_REVISION */
+ return s->revision;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ return s->config[0];
+
+ case 0x14: /* GPIO_SYSSTATUS */
+ return 0x01;
+
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ return s->ints[0];
+
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ return s->mask[0];
+
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ case 0x80: /* GPIO_CLEARWKUENA */
+ case 0x84: /* GPIO_SETWKUENA */
+ return s->wumask;
+
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ return s->ints[1];
+
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ return s->mask[1];
+
+ case 0x30: /* GPIO_CTRL */
+ return s->config[1];
+
+ case 0x34: /* GPIO_OE */
+ return s->dir;
+
+ case 0x38: /* GPIO_DATAIN */
+ return s->inputs;
+
+ case 0x3c: /* GPIO_DATAOUT */
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ case 0x94: /* GPIO_SETDATAOUT */
+ return s->outputs;
+
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ return s->level[0];
+
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ return s->level[1];
+
+ case 0x48: /* GPIO_RISINGDETECT */
+ return s->edge[0];
+
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ return s->edge[1];
+
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ return s->debounce;
+
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ return s->delay;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap2_gpio_module_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+ uint32_t diff;
+ int ln;
+
+ switch (addr) {
+ case 0x00: /* GPIO_REVISION */
+ case 0x14: /* GPIO_SYSSTATUS */
+ case 0x38: /* GPIO_DATAIN */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ if (((value >> 3) & 3) == 3) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Illegal IDLEMODE value: 3\n", __func__);
+ }
+ if (value & 2)
+ omap2_gpio_module_reset(s);
+ s->config[0] = value & 0x1d;
+ break;
+
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ if (s->ints[0] & value) {
+ s->ints[0] &= ~value;
+ omap2_gpio_module_level_update(s, 0);
+ }
+ break;
+
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ s->mask[0] = value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ s->wumask = value;
+ break;
+
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ if (s->ints[1] & value) {
+ s->ints[1] &= ~value;
+ omap2_gpio_module_level_update(s, 1);
+ }
+ break;
+
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ s->mask[1] = value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x30: /* GPIO_CTRL */
+ s->config[1] = value & 7;
+ break;
+
+ case 0x34: /* GPIO_OE */
+ diff = s->outputs & (s->dir ^ value);
+ s->dir = value;
+
+ value = s->outputs & ~s->dir;
+ while ((ln = ctz32(diff)) != 32) {
+ diff &= ~(1 << ln);
+ qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+ }
+
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x3c: /* GPIO_DATAOUT */
+ omap2_gpio_module_out_update(s, s->outputs ^ value);
+ break;
+
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ s->level[0] = value;
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ s->level[1] = value;
+ omap2_gpio_module_level_update(s, 0);
+ omap2_gpio_module_level_update(s, 1);
+ break;
+
+ case 0x48: /* GPIO_RISINGDETECT */
+ s->edge[0] = value;
+ break;
+
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ s->edge[1] = value;
+ break;
+
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ s->debounce = value;
+ break;
+
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ s->delay = value;
+ break;
+
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ s->mask[0] &= ~value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ s->mask[0] |= value;
+ omap2_gpio_module_int_update(s, 0);
+ break;
+
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ s->mask[1] &= ~value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ s->mask[1] |= value;
+ omap2_gpio_module_int_update(s, 1);
+ break;
+
+ case 0x80: /* GPIO_CLEARWKUENA */
+ s->wumask &= ~value;
+ break;
+
+ case 0x84: /* GPIO_SETWKUENA */
+ s->wumask |= value;
+ break;
+
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ omap2_gpio_module_out_update(s, s->outputs & value);
+ break;
+
+ case 0x94: /* GPIO_SETDATAOUT */
+ omap2_gpio_module_out_update(s, ~s->outputs & value);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static uint64_t omap2_gpio_module_readp(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return omap2_gpio_module_read(opaque, addr & ~3) >> ((addr & 3) << 3);
+}
+
+static void omap2_gpio_module_writep(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ uint32_t cur = 0;
+ uint32_t mask = 0xffff;
+
+ if (size == 4) {
+ omap2_gpio_module_write(opaque, addr, value);
+ return;
+ }
+
+ switch (addr & ~3) {
+ case 0x00: /* GPIO_REVISION */
+ case 0x14: /* GPIO_SYSSTATUS */
+ case 0x38: /* GPIO_DATAIN */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* GPIO_SYSCONFIG */
+ case 0x1c: /* GPIO_IRQENABLE1 */
+ case 0x20: /* GPIO_WAKEUPENABLE */
+ case 0x2c: /* GPIO_IRQENABLE2 */
+ case 0x30: /* GPIO_CTRL */
+ case 0x34: /* GPIO_OE */
+ case 0x3c: /* GPIO_DATAOUT */
+ case 0x40: /* GPIO_LEVELDETECT0 */
+ case 0x44: /* GPIO_LEVELDETECT1 */
+ case 0x48: /* GPIO_RISINGDETECT */
+ case 0x4c: /* GPIO_FALLINGDETECT */
+ case 0x50: /* GPIO_DEBOUNCENABLE */
+ case 0x54: /* GPIO_DEBOUNCINGTIME */
+ cur = omap2_gpio_module_read(opaque, addr & ~3) &
+ ~(mask << ((addr & 3) << 3));
+
+ /* Fall through. */
+ case 0x18: /* GPIO_IRQSTATUS1 */
+ case 0x28: /* GPIO_IRQSTATUS2 */
+ case 0x60: /* GPIO_CLEARIRQENABLE1 */
+ case 0x64: /* GPIO_SETIRQENABLE1 */
+ case 0x70: /* GPIO_CLEARIRQENABLE2 */
+ case 0x74: /* GPIO_SETIREQNEABLE2 */
+ case 0x80: /* GPIO_CLEARWKUENA */
+ case 0x84: /* GPIO_SETWKUENA */
+ case 0x90: /* GPIO_CLEARDATAOUT */
+ case 0x94: /* GPIO_SETDATAOUT */
+ value <<= (addr & 3) << 3;
+ omap2_gpio_module_write(opaque, addr, cur | value);
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap2_gpio_module_ops = {
+ .read = omap2_gpio_module_readp,
+ .write = omap2_gpio_module_writep,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_gpif_reset(DeviceState *dev)
+{
+ struct omap_gpif_s *s = OMAP1_GPIO(dev);
+
+ omap_gpio_reset(&s->omap1);
+}
+
+static void omap2_gpif_reset(DeviceState *dev)
+{
+ struct omap2_gpif_s *s = OMAP2_GPIO(dev);
+ int i;
+
+ for (i = 0; i < s->modulecount; i++) {
+ omap2_gpio_module_reset(&s->modules[i]);
+ }
+ s->autoidle = 0;
+ s->gpo = 0;
+}
+
+static uint64_t omap2_gpif_top_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap2_gpif_s *s = (struct omap2_gpif_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* IPGENERICOCPSPL_REVISION */
+ return 0x18;
+
+ case 0x10: /* IPGENERICOCPSPL_SYSCONFIG */
+ return s->autoidle;
+
+ case 0x14: /* IPGENERICOCPSPL_SYSSTATUS */
+ return 0x01;
+
+ case 0x18: /* IPGENERICOCPSPL_IRQSTATUS */
+ return 0x00;
+
+ case 0x40: /* IPGENERICOCPSPL_GPO */
+ return s->gpo;
+
+ case 0x50: /* IPGENERICOCPSPL_GPI */
+ return 0x00;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap2_gpif_top_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap2_gpif_s *s = (struct omap2_gpif_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* IPGENERICOCPSPL_REVISION */
+ case 0x14: /* IPGENERICOCPSPL_SYSSTATUS */
+ case 0x18: /* IPGENERICOCPSPL_IRQSTATUS */
+ case 0x50: /* IPGENERICOCPSPL_GPI */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* IPGENERICOCPSPL_SYSCONFIG */
+ if (value & (1 << 1)) /* SOFTRESET */
+ omap2_gpif_reset(DEVICE(s));
+ s->autoidle = value & 1;
+ break;
+
+ case 0x40: /* IPGENERICOCPSPL_GPO */
+ s->gpo = value & 1;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap2_gpif_top_ops = {
+ .read = omap2_gpif_top_read,
+ .write = omap2_gpif_top_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_gpio_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ struct omap_gpif_s *s = OMAP1_GPIO(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ qdev_init_gpio_in(dev, omap_gpio_set, 16);
+ qdev_init_gpio_out(dev, s->omap1.handler, 16);
+ sysbus_init_irq(sbd, &s->omap1.irq);
+ memory_region_init_io(&s->iomem, obj, &omap_gpio_ops, &s->omap1,
+ "omap.gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void omap_gpio_realize(DeviceState *dev, Error **errp)
+{
+ struct omap_gpif_s *s = OMAP1_GPIO(dev);
+
+ if (!s->clk) {
+ error_setg(errp, "omap-gpio: clk not connected");
+ }
+}
+
+static void omap2_gpio_realize(DeviceState *dev, Error **errp)
+{
+ struct omap2_gpif_s *s = OMAP2_GPIO(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ int i;
+
+ if (!s->iclk) {
+ error_setg(errp, "omap2-gpio: iclk not connected");
+ return;
+ }
+
+ s->modulecount = s->mpu_model < omap2430 ? 4
+ : s->mpu_model < omap3430 ? 5
+ : 6;
+
+ if (s->mpu_model < omap3430) {
+ memory_region_init_io(&s->iomem, OBJECT(dev), &omap2_gpif_top_ops, s,
+ "omap2.gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ }
+
+ s->modules = g_new0(struct omap2_gpio_s, s->modulecount);
+ s->handler = g_new0(qemu_irq, s->modulecount * 32);
+ qdev_init_gpio_in(dev, omap2_gpio_set, s->modulecount * 32);
+ qdev_init_gpio_out(dev, s->handler, s->modulecount * 32);
+
+ for (i = 0; i < s->modulecount; i++) {
+ struct omap2_gpio_s *m = &s->modules[i];
+
+ if (!s->fclk[i]) {
+ error_setg(errp, "omap2-gpio: fclk%d not connected", i);
+ return;
+ }
+
+ m->revision = (s->mpu_model < omap3430) ? 0x18 : 0x25;
+ m->handler = &s->handler[i * 32];
+ sysbus_init_irq(sbd, &m->irq[0]); /* mpu irq */
+ sysbus_init_irq(sbd, &m->irq[1]); /* dsp irq */
+ sysbus_init_irq(sbd, &m->wkup);
+ memory_region_init_io(&m->iomem, OBJECT(dev), &omap2_gpio_module_ops, m,
+ "omap.gpio-module", 0x1000);
+ sysbus_init_mmio(sbd, &m->iomem);
+ }
+}
+
+void omap_gpio_set_clk(omap_gpif *gpio, omap_clk clk)
+{
+ gpio->clk = clk;
+}
+
+static Property omap_gpio_properties[] = {
+ DEFINE_PROP_INT32("mpu_model", struct omap_gpif_s, mpu_model, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = omap_gpio_realize;
+ dc->reset = omap_gpif_reset;
+ device_class_set_props(dc, omap_gpio_properties);
+ /* Reason: pointer property "clk" */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo omap_gpio_info = {
+ .name = TYPE_OMAP1_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct omap_gpif_s),
+ .instance_init = omap_gpio_init,
+ .class_init = omap_gpio_class_init,
+};
+
+void omap2_gpio_set_iclk(omap2_gpif *gpio, omap_clk clk)
+{
+ gpio->iclk = clk;
+}
+
+void omap2_gpio_set_fclk(omap2_gpif *gpio, uint8_t i, omap_clk clk)
+{
+ assert(i <= 5);
+ gpio->fclk[i] = clk;
+}
+
+static Property omap2_gpio_properties[] = {
+ DEFINE_PROP_INT32("mpu_model", struct omap2_gpif_s, mpu_model, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void omap2_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = omap2_gpio_realize;
+ dc->reset = omap2_gpif_reset;
+ device_class_set_props(dc, omap2_gpio_properties);
+ /* Reason: pointer properties "iclk", "fclk0", ..., "fclk5" */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo omap2_gpio_info = {
+ .name = TYPE_OMAP2_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct omap2_gpif_s),
+ .class_init = omap2_gpio_class_init,
+};
+
+static void omap_gpio_register_types(void)
+{
+ type_register_static(&omap_gpio_info);
+ type_register_static(&omap2_gpio_info);
+}
+
+type_init(omap_gpio_register_types)
diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
new file mode 100644
index 000000000..899be861c
--- /dev/null
+++ b/hw/gpio/pl061.c
@@ -0,0 +1,603 @@
+/*
+ * Arm PrimeCell PL061 General Purpose IO with additional
+ * Luminary Micro Stellaris bits.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ *
+ * QEMU interface:
+ * + sysbus MMIO region 0: the device registers
+ * + sysbus IRQ: the GPIOINTR interrupt line
+ * + unnamed GPIO inputs 0..7: inputs to connect to the emulated GPIO lines
+ * + unnamed GPIO outputs 0..7: the emulated GPIO lines, considered as
+ * outputs
+ * + QOM property "pullups": an integer defining whether non-floating lines
+ * configured as inputs should be pulled up to logical 1 (ie whether in
+ * real hardware they have a pullup resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled high, bit 1 configures line 1, and so on. The default is 0xff,
+ * indicating that all GPIO lines are pulled up to logical 1.
+ * + QOM property "pulldowns": an integer defining whether non-floating lines
+ * configured as inputs should be pulled down to logical 0 (ie whether in
+ * real hardware they have a pulldown resistor on the line out of the PL061).
+ * This should be an 8-bit value, where bit 0 is 1 if GPIO line 0 should
+ * be pulled low, bit 1 configures line 1, and so on. The default is 0x0.
+ * It is an error to set a bit in both "pullups" and "pulldowns". If a bit
+ * is 0 in both, then the line is considered to be floating, and it will
+ * not have qemu_set_irq() called on it when it is configured as an input.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "trace.h"
+
+static const uint8_t pl061_id[12] =
+ { 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+static const uint8_t pl061_id_luminary[12] =
+ { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
+
+#define TYPE_PL061 "pl061"
+OBJECT_DECLARE_SIMPLE_TYPE(PL061State, PL061)
+
+#define N_GPIOS 8
+
+struct PL061State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t locked;
+ uint32_t data;
+ uint32_t old_out_data;
+ uint32_t old_in_data;
+ uint32_t dir;
+ uint32_t isense;
+ uint32_t ibe;
+ uint32_t iev;
+ uint32_t im;
+ uint32_t istate;
+ uint32_t afsel;
+ uint32_t dr2r;
+ uint32_t dr4r;
+ uint32_t dr8r;
+ uint32_t odr;
+ uint32_t pur;
+ uint32_t pdr;
+ uint32_t slr;
+ uint32_t den;
+ uint32_t cr;
+ uint32_t amsel;
+ qemu_irq irq;
+ qemu_irq out[N_GPIOS];
+ const unsigned char *id;
+ /* Properties, for non-Luminary PL061 */
+ uint32_t pullups;
+ uint32_t pulldowns;
+};
+
+static const VMStateDescription vmstate_pl061 = {
+ .name = "pl061",
+ .version_id = 4,
+ .minimum_version_id = 4,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(locked, PL061State),
+ VMSTATE_UINT32(data, PL061State),
+ VMSTATE_UINT32(old_out_data, PL061State),
+ VMSTATE_UINT32(old_in_data, PL061State),
+ VMSTATE_UINT32(dir, PL061State),
+ VMSTATE_UINT32(isense, PL061State),
+ VMSTATE_UINT32(ibe, PL061State),
+ VMSTATE_UINT32(iev, PL061State),
+ VMSTATE_UINT32(im, PL061State),
+ VMSTATE_UINT32(istate, PL061State),
+ VMSTATE_UINT32(afsel, PL061State),
+ VMSTATE_UINT32(dr2r, PL061State),
+ VMSTATE_UINT32(dr4r, PL061State),
+ VMSTATE_UINT32(dr8r, PL061State),
+ VMSTATE_UINT32(odr, PL061State),
+ VMSTATE_UINT32(pur, PL061State),
+ VMSTATE_UINT32(pdr, PL061State),
+ VMSTATE_UINT32(slr, PL061State),
+ VMSTATE_UINT32(den, PL061State),
+ VMSTATE_UINT32(cr, PL061State),
+ VMSTATE_UINT32_V(amsel, PL061State, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint8_t pl061_floating(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are floating (neither pulled up to 1 nor down to 0).
+ */
+ uint8_t floating;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * If both PUR and PDR bits are clear, there is neither a pullup
+ * nor a pulldown in place, and the output truly floats.
+ */
+ floating = ~(s->pur | s->pdr);
+ } else {
+ floating = ~(s->pullups | s->pulldowns);
+ }
+ return floating & ~s->dir;
+}
+
+static uint8_t pl061_pullups(PL061State *s)
+{
+ /*
+ * Return mask of bits which correspond to pins configured as inputs
+ * and which are pulled up to 1.
+ */
+ uint8_t pullups;
+
+ if (s->id == pl061_id_luminary) {
+ /*
+ * The Luminary variant of the PL061 has an extra registers which
+ * the guest can use to configure whether lines should be pullup
+ * or pulldown.
+ */
+ pullups = s->pur;
+ } else {
+ pullups = s->pullups;
+ }
+ return pullups & ~s->dir;
+}
+
+static void pl061_update(PL061State *s)
+{
+ uint8_t changed;
+ uint8_t mask;
+ uint8_t out;
+ int i;
+ uint8_t pullups = pl061_pullups(s);
+ uint8_t floating = pl061_floating(s);
+
+ trace_pl061_update(DEVICE(s)->canonical_path, s->dir, s->data,
+ pullups, floating);
+
+ /*
+ * Pins configured as output are driven from the data register;
+ * otherwise if they're pulled up they're 1, and if they're floating
+ * then we give them the same value they had previously, so we don't
+ * report any change to the other end.
+ */
+ out = (s->data & s->dir) | pullups | (s->old_out_data & floating);
+ changed = s->old_out_data ^ out;
+ if (changed) {
+ s->old_out_data = out;
+ for (i = 0; i < N_GPIOS; i++) {
+ mask = 1 << i;
+ if (changed & mask) {
+ int level = (out & mask) != 0;
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
+ }
+ }
+ }
+
+ /* Inputs */
+ changed = (s->old_in_data ^ s->data) & ~s->dir;
+ if (changed) {
+ s->old_in_data = s->data;
+ for (i = 0; i < N_GPIOS; i++) {
+ mask = 1 << i;
+ if (changed & mask) {
+ trace_pl061_input_change(DEVICE(s)->canonical_path, i,
+ (s->data & mask) != 0);
+
+ if (!(s->isense & mask)) {
+ /* Edge interrupt */
+ if (s->ibe & mask) {
+ /* Any edge triggers the interrupt */
+ s->istate |= mask;
+ } else {
+ /* Edge is selected by IEV */
+ s->istate |= ~(s->data ^ s->iev) & mask;
+ }
+ }
+ }
+ }
+ }
+
+ /* Level interrupt */
+ s->istate |= ~(s->data ^ s->iev) & s->isense;
+
+ trace_pl061_update_istate(DEVICE(s)->canonical_path,
+ s->istate, s->im, (s->istate & s->im) != 0);
+
+ qemu_set_irq(s->irq, (s->istate & s->im) != 0);
+}
+
+static uint64_t pl061_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL061State *s = (PL061State *)opaque;
+ uint64_t r = 0;
+
+ switch (offset) {
+ case 0x0 ... 0x3ff: /* Data */
+ r = s->data & (offset >> 2);
+ break;
+ case 0x400: /* Direction */
+ r = s->dir;
+ break;
+ case 0x404: /* Interrupt sense */
+ r = s->isense;
+ break;
+ case 0x408: /* Interrupt both edges */
+ r = s->ibe;
+ break;
+ case 0x40c: /* Interrupt event */
+ r = s->iev;
+ break;
+ case 0x410: /* Interrupt mask */
+ r = s->im;
+ break;
+ case 0x414: /* Raw interrupt status */
+ r = s->istate;
+ break;
+ case 0x418: /* Masked interrupt status */
+ r = s->istate & s->im;
+ break;
+ case 0x420: /* Alternate function select */
+ r = s->afsel;
+ break;
+ case 0x500: /* 2mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr2r;
+ break;
+ case 0x504: /* 4mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr4r;
+ break;
+ case 0x508: /* 8mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->dr8r;
+ break;
+ case 0x50c: /* Open drain */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->odr;
+ break;
+ case 0x510: /* Pull-up */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pur;
+ break;
+ case 0x514: /* Pull-down */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->pdr;
+ break;
+ case 0x518: /* Slew rate control */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->slr;
+ break;
+ case 0x51c: /* Digital enable */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->den;
+ break;
+ case 0x520: /* Lock */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->locked;
+ break;
+ case 0x524: /* Commit */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->cr;
+ break;
+ case 0x528: /* Analog mode select */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ r = s->amsel;
+ break;
+ case 0xfd0 ... 0xfff: /* ID registers */
+ r = s->id[(offset - 0xfd0) >> 2];
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_read: Bad offset %x\n", (int)offset);
+ break;
+ }
+
+ trace_pl061_read(DEVICE(s)->canonical_path, offset, r);
+ return r;
+}
+
+static void pl061_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL061State *s = (PL061State *)opaque;
+ uint8_t mask;
+
+ trace_pl061_write(DEVICE(s)->canonical_path, offset, value);
+
+ switch (offset) {
+ case 0 ... 0x3ff:
+ mask = (offset >> 2) & s->dir;
+ s->data = (s->data & ~mask) | (value & mask);
+ pl061_update(s);
+ return;
+ case 0x400: /* Direction */
+ s->dir = value & 0xff;
+ break;
+ case 0x404: /* Interrupt sense */
+ s->isense = value & 0xff;
+ break;
+ case 0x408: /* Interrupt both edges */
+ s->ibe = value & 0xff;
+ break;
+ case 0x40c: /* Interrupt event */
+ s->iev = value & 0xff;
+ break;
+ case 0x410: /* Interrupt mask */
+ s->im = value & 0xff;
+ break;
+ case 0x41c: /* Interrupt clear */
+ s->istate &= ~value;
+ break;
+ case 0x420: /* Alternate function select */
+ mask = s->cr;
+ s->afsel = (s->afsel & ~mask) | (value & mask);
+ break;
+ case 0x500: /* 2mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->dr2r = value & 0xff;
+ break;
+ case 0x504: /* 4mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->dr4r = value & 0xff;
+ break;
+ case 0x508: /* 8mA drive */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->dr8r = value & 0xff;
+ break;
+ case 0x50c: /* Open drain */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->odr = value & 0xff;
+ break;
+ case 0x510: /* Pull-up */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->pur = value & 0xff;
+ break;
+ case 0x514: /* Pull-down */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->pdr = value & 0xff;
+ break;
+ case 0x518: /* Slew rate control */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->slr = value & 0xff;
+ break;
+ case 0x51c: /* Digital enable */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->den = value & 0xff;
+ break;
+ case 0x520: /* Lock */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->locked = (value != 0xacce551);
+ break;
+ case 0x524: /* Commit */
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ if (!s->locked)
+ s->cr = value & 0xff;
+ break;
+ case 0x528:
+ if (s->id != pl061_id_luminary) {
+ goto bad_offset;
+ }
+ s->amsel = value & 0xff;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl061_write: Bad offset %x\n", (int)offset);
+ return;
+ }
+ pl061_update(s);
+ return;
+}
+
+static void pl061_enter_reset(Object *obj, ResetType type)
+{
+ PL061State *s = PL061(obj);
+
+ trace_pl061_reset(DEVICE(s)->canonical_path);
+
+ /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
+
+ /*
+ * FIXME: For the LM3S6965, not all of the PL061 instances have the
+ * same reset values for GPIOPUR, GPIOAFSEL and GPIODEN, so in theory
+ * we should allow the board to configure these via properties.
+ * In practice, we don't wire anything up to the affected GPIO lines
+ * (PB7, PC0, PC1, PC2, PC3 -- they're used for JTAG), so we can
+ * get away with this inaccuracy.
+ */
+ s->data = 0;
+ s->old_in_data = 0;
+ s->dir = 0;
+ s->isense = 0;
+ s->ibe = 0;
+ s->iev = 0;
+ s->im = 0;
+ s->istate = 0;
+ s->afsel = 0;
+ s->dr2r = 0xff;
+ s->dr4r = 0;
+ s->dr8r = 0;
+ s->odr = 0;
+ s->pur = 0;
+ s->pdr = 0;
+ s->slr = 0;
+ s->den = 0;
+ s->locked = 1;
+ s->cr = 0xff;
+ s->amsel = 0;
+}
+
+static void pl061_hold_reset(Object *obj)
+{
+ PL061State *s = PL061(obj);
+ int i, level;
+ uint8_t floating = pl061_floating(s);
+ uint8_t pullups = pl061_pullups(s);
+
+ for (i = 0; i < N_GPIOS; i++) {
+ if (extract32(floating, i, 1)) {
+ continue;
+ }
+ level = extract32(pullups, i, 1);
+ trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
+ qemu_set_irq(s->out[i], level);
+ }
+ s->old_out_data = pullups;
+}
+
+static void pl061_set_irq(void * opaque, int irq, int level)
+{
+ PL061State *s = (PL061State *)opaque;
+ uint8_t mask;
+
+ mask = 1 << irq;
+ if ((s->dir & mask) == 0) {
+ s->data &= ~mask;
+ if (level)
+ s->data |= mask;
+ pl061_update(s);
+ }
+}
+
+static const MemoryRegionOps pl061_ops = {
+ .read = pl061_read,
+ .write = pl061_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl061_luminary_init(Object *obj)
+{
+ PL061State *s = PL061(obj);
+
+ s->id = pl061_id_luminary;
+}
+
+static void pl061_init(Object *obj)
+{
+ PL061State *s = PL061(obj);
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->id = pl061_id;
+
+ memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, pl061_set_irq, N_GPIOS);
+ qdev_init_gpio_out(dev, s->out, N_GPIOS);
+}
+
+static void pl061_realize(DeviceState *dev, Error **errp)
+{
+ PL061State *s = PL061(dev);
+
+ if (s->pullups > 0xff) {
+ error_setg(errp, "pullups property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pulldowns > 0xff) {
+ error_setg(errp, "pulldowns property must be between 0 and 0xff");
+ return;
+ }
+ if (s->pullups & s->pulldowns) {
+ error_setg(errp, "no bit may be set both in pullups and pulldowns");
+ return;
+ }
+}
+
+static Property pl061_props[] = {
+ DEFINE_PROP_UINT32("pullups", PL061State, pullups, 0xff),
+ DEFINE_PROP_UINT32("pulldowns", PL061State, pulldowns, 0x0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pl061_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->vmsd = &vmstate_pl061;
+ dc->realize = pl061_realize;
+ device_class_set_props(dc, pl061_props);
+ rc->phases.enter = pl061_enter_reset;
+ rc->phases.hold = pl061_hold_reset;
+}
+
+static const TypeInfo pl061_info = {
+ .name = TYPE_PL061,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL061State),
+ .instance_init = pl061_init,
+ .class_init = pl061_class_init,
+};
+
+static const TypeInfo pl061_luminary_info = {
+ .name = "pl061_luminary",
+ .parent = TYPE_PL061,
+ .instance_init = pl061_luminary_init,
+};
+
+static void pl061_register_types(void)
+{
+ type_register_static(&pl061_info);
+ type_register_static(&pl061_luminary_info);
+}
+
+type_init(pl061_register_types)
diff --git a/hw/gpio/sifive_gpio.c b/hw/gpio/sifive_gpio.c
new file mode 100644
index 000000000..78bf29e99
--- /dev/null
+++ b/hw/gpio/sifive_gpio.c
@@ -0,0 +1,397 @@
+/*
+ * SiFive System-on-Chip general purpose input/output register definition
+ *
+ * Copyright 2019 AdaCore
+ *
+ * Base on nrf51_gpio.c:
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/gpio/sifive_gpio.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void update_output_irq(SIFIVEGPIOState *s)
+{
+ uint32_t pending;
+ uint32_t pin;
+
+ pending = s->high_ip & s->high_ie;
+ pending |= s->low_ip & s->low_ie;
+ pending |= s->rise_ip & s->rise_ie;
+ pending |= s->fall_ip & s->fall_ie;
+
+ for (int i = 0; i < s->ngpio; i++) {
+ pin = 1 << i;
+ qemu_set_irq(s->irq[i], (pending & pin) != 0);
+ trace_sifive_gpio_update_output_irq(i, (pending & pin) != 0);
+ }
+}
+
+static void update_state(SIFIVEGPIOState *s)
+{
+ size_t i;
+ bool prev_ival, in, in_mask, port, out_xor, pull, output_en, input_en,
+ rise_ip, fall_ip, low_ip, high_ip, oval, actual_value, ival;
+
+ for (i = 0; i < s->ngpio; i++) {
+
+ prev_ival = extract32(s->value, i, 1);
+ in = extract32(s->in, i, 1);
+ in_mask = extract32(s->in_mask, i, 1);
+ port = extract32(s->port, i, 1);
+ out_xor = extract32(s->out_xor, i, 1);
+ pull = extract32(s->pue, i, 1);
+ output_en = extract32(s->output_en, i, 1);
+ input_en = extract32(s->input_en, i, 1);
+ rise_ip = extract32(s->rise_ip, i, 1);
+ fall_ip = extract32(s->fall_ip, i, 1);
+ low_ip = extract32(s->low_ip, i, 1);
+ high_ip = extract32(s->high_ip, i, 1);
+
+ /* Output value (IOF not supported) */
+ oval = output_en && (port ^ out_xor);
+
+ /* Pin both driven externally and internally */
+ if (output_en && in_mask) {
+ qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i);
+ }
+
+ if (in_mask) {
+ /* The pin is driven by external device */
+ actual_value = in;
+ } else if (output_en) {
+ /* The pin is driven by internal circuit */
+ actual_value = oval;
+ } else {
+ /* Floating? Apply pull-up resistor */
+ actual_value = pull;
+ }
+
+ if (output_en) {
+ qemu_set_irq(s->output[i], actual_value);
+ }
+
+ /* Input value */
+ ival = input_en && actual_value;
+
+ /* Interrupts */
+ high_ip = high_ip || ival;
+ s->high_ip = deposit32(s->high_ip, i, 1, high_ip);
+
+ low_ip = low_ip || !ival;
+ s->low_ip = deposit32(s->low_ip, i, 1, low_ip);
+
+ rise_ip = rise_ip || (ival && !prev_ival);
+ s->rise_ip = deposit32(s->rise_ip, i, 1, rise_ip);
+
+ fall_ip = fall_ip || (!ival && prev_ival);
+ s->fall_ip = deposit32(s->fall_ip, i, 1, fall_ip);
+
+ /* Update value */
+ s->value = deposit32(s->value, i, 1, ival);
+ }
+ update_output_irq(s);
+}
+
+static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+ uint64_t r = 0;
+
+ switch (offset) {
+ case SIFIVE_GPIO_REG_VALUE:
+ r = s->value;
+ break;
+
+ case SIFIVE_GPIO_REG_INPUT_EN:
+ r = s->input_en;
+ break;
+
+ case SIFIVE_GPIO_REG_OUTPUT_EN:
+ r = s->output_en;
+ break;
+
+ case SIFIVE_GPIO_REG_PORT:
+ r = s->port;
+ break;
+
+ case SIFIVE_GPIO_REG_PUE:
+ r = s->pue;
+ break;
+
+ case SIFIVE_GPIO_REG_DS:
+ r = s->ds;
+ break;
+
+ case SIFIVE_GPIO_REG_RISE_IE:
+ r = s->rise_ie;
+ break;
+
+ case SIFIVE_GPIO_REG_RISE_IP:
+ r = s->rise_ip;
+ break;
+
+ case SIFIVE_GPIO_REG_FALL_IE:
+ r = s->fall_ie;
+ break;
+
+ case SIFIVE_GPIO_REG_FALL_IP:
+ r = s->fall_ip;
+ break;
+
+ case SIFIVE_GPIO_REG_HIGH_IE:
+ r = s->high_ie;
+ break;
+
+ case SIFIVE_GPIO_REG_HIGH_IP:
+ r = s->high_ip;
+ break;
+
+ case SIFIVE_GPIO_REG_LOW_IE:
+ r = s->low_ie;
+ break;
+
+ case SIFIVE_GPIO_REG_LOW_IP:
+ r = s->low_ip;
+ break;
+
+ case SIFIVE_GPIO_REG_IOF_EN:
+ r = s->iof_en;
+ break;
+
+ case SIFIVE_GPIO_REG_IOF_SEL:
+ r = s->iof_sel;
+ break;
+
+ case SIFIVE_GPIO_REG_OUT_XOR:
+ r = s->out_xor;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ trace_sifive_gpio_read(offset, r);
+
+ return r;
+}
+
+static void sifive_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned int size)
+{
+ SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+
+ trace_sifive_gpio_write(offset, value);
+
+ switch (offset) {
+
+ case SIFIVE_GPIO_REG_INPUT_EN:
+ s->input_en = value;
+ break;
+
+ case SIFIVE_GPIO_REG_OUTPUT_EN:
+ s->output_en = value;
+ break;
+
+ case SIFIVE_GPIO_REG_PORT:
+ s->port = value;
+ break;
+
+ case SIFIVE_GPIO_REG_PUE:
+ s->pue = value;
+ break;
+
+ case SIFIVE_GPIO_REG_DS:
+ s->ds = value;
+ break;
+
+ case SIFIVE_GPIO_REG_RISE_IE:
+ s->rise_ie = value;
+ break;
+
+ case SIFIVE_GPIO_REG_RISE_IP:
+ /* Write 1 to clear */
+ s->rise_ip &= ~value;
+ break;
+
+ case SIFIVE_GPIO_REG_FALL_IE:
+ s->fall_ie = value;
+ break;
+
+ case SIFIVE_GPIO_REG_FALL_IP:
+ /* Write 1 to clear */
+ s->fall_ip &= ~value;
+ break;
+
+ case SIFIVE_GPIO_REG_HIGH_IE:
+ s->high_ie = value;
+ break;
+
+ case SIFIVE_GPIO_REG_HIGH_IP:
+ /* Write 1 to clear */
+ s->high_ip &= ~value;
+ break;
+
+ case SIFIVE_GPIO_REG_LOW_IE:
+ s->low_ie = value;
+ break;
+
+ case SIFIVE_GPIO_REG_LOW_IP:
+ /* Write 1 to clear */
+ s->low_ip &= ~value;
+ break;
+
+ case SIFIVE_GPIO_REG_IOF_EN:
+ s->iof_en = value;
+ break;
+
+ case SIFIVE_GPIO_REG_IOF_SEL:
+ s->iof_sel = value;
+ break;
+
+ case SIFIVE_GPIO_REG_OUT_XOR:
+ s->out_xor = value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ update_state(s);
+}
+
+static const MemoryRegionOps gpio_ops = {
+ .read = sifive_gpio_read,
+ .write = sifive_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static void sifive_gpio_set(void *opaque, int line, int value)
+{
+ SIFIVEGPIOState *s = SIFIVE_GPIO(opaque);
+
+ trace_sifive_gpio_set(line, value);
+
+ assert(line >= 0 && line < SIFIVE_GPIO_PINS);
+
+ s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
+ if (value >= 0) {
+ s->in = deposit32(s->in, line, 1, value != 0);
+ }
+
+ update_state(s);
+}
+
+static void sifive_gpio_reset(DeviceState *dev)
+{
+ SIFIVEGPIOState *s = SIFIVE_GPIO(dev);
+
+ s->value = 0;
+ s->input_en = 0;
+ s->output_en = 0;
+ s->port = 0;
+ s->pue = 0;
+ s->ds = 0;
+ s->rise_ie = 0;
+ s->rise_ip = 0;
+ s->fall_ie = 0;
+ s->fall_ip = 0;
+ s->high_ie = 0;
+ s->high_ip = 0;
+ s->low_ie = 0;
+ s->low_ip = 0;
+ s->iof_en = 0;
+ s->iof_sel = 0;
+ s->out_xor = 0;
+ s->in = 0;
+ s->in_mask = 0;
+}
+
+static const VMStateDescription vmstate_sifive_gpio = {
+ .name = TYPE_SIFIVE_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(value, SIFIVEGPIOState),
+ VMSTATE_UINT32(input_en, SIFIVEGPIOState),
+ VMSTATE_UINT32(output_en, SIFIVEGPIOState),
+ VMSTATE_UINT32(port, SIFIVEGPIOState),
+ VMSTATE_UINT32(pue, SIFIVEGPIOState),
+ VMSTATE_UINT32(rise_ie, SIFIVEGPIOState),
+ VMSTATE_UINT32(rise_ip, SIFIVEGPIOState),
+ VMSTATE_UINT32(fall_ie, SIFIVEGPIOState),
+ VMSTATE_UINT32(fall_ip, SIFIVEGPIOState),
+ VMSTATE_UINT32(high_ie, SIFIVEGPIOState),
+ VMSTATE_UINT32(high_ip, SIFIVEGPIOState),
+ VMSTATE_UINT32(low_ie, SIFIVEGPIOState),
+ VMSTATE_UINT32(low_ip, SIFIVEGPIOState),
+ VMSTATE_UINT32(iof_en, SIFIVEGPIOState),
+ VMSTATE_UINT32(iof_sel, SIFIVEGPIOState),
+ VMSTATE_UINT32(out_xor, SIFIVEGPIOState),
+ VMSTATE_UINT32(in, SIFIVEGPIOState),
+ VMSTATE_UINT32(in_mask, SIFIVEGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property sifive_gpio_properties[] = {
+ DEFINE_PROP_UINT32("ngpio", SIFIVEGPIOState, ngpio, SIFIVE_GPIO_PINS),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sifive_gpio_realize(DeviceState *dev, Error **errp)
+{
+ SIFIVEGPIOState *s = SIFIVE_GPIO(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &gpio_ops, s,
+ TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+
+ for (int i = 0; i < s->ngpio; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]);
+ }
+
+ qdev_init_gpio_in(DEVICE(s), sifive_gpio_set, s->ngpio);
+ qdev_init_gpio_out(DEVICE(s), s->output, s->ngpio);
+}
+
+static void sifive_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, sifive_gpio_properties);
+ dc->vmsd = &vmstate_sifive_gpio;
+ dc->realize = sifive_gpio_realize;
+ dc->reset = sifive_gpio_reset;
+ dc->desc = "SiFive GPIO";
+}
+
+static const TypeInfo sifive_gpio_info = {
+ .name = TYPE_SIFIVE_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SIFIVEGPIOState),
+ .class_init = sifive_gpio_class_init
+};
+
+static void sifive_gpio_register_types(void)
+{
+ type_register_static(&sifive_gpio_info);
+}
+
+type_init(sifive_gpio_register_types)
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
new file mode 100644
index 000000000..1dab99c56
--- /dev/null
+++ b/hw/gpio/trace-events
@@ -0,0 +1,29 @@
+# See docs/devel/tracing.rst 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
+nrf51_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+nrf51_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+
+# pl061.c
+pl061_update(const char *id, uint32_t dir, uint32_t data, uint32_t pullups, uint32_t floating) "%s GPIODIR 0x%x GPIODATA 0x%x pullups 0x%x floating 0x%x"
+pl061_set_output(const char *id, int gpio, int level) "%s setting output %d to %d"
+pl061_input_change(const char *id, int gpio, int level) "%s input %d changed to %d"
+pl061_update_istate(const char *id, uint32_t istate, uint32_t im, int level) "%s GPIORIS 0x%x GPIOIE 0x%x interrupt level %d"
+pl061_read(const char *id, uint64_t offset, uint64_t r) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_write(const char *id, uint64_t offset, uint64_t value) "%s offset 0x%" PRIx64 " value 0x%" PRIx64
+pl061_reset(const char *id) "%s reset"
+
+# sifive_gpio.c
+sifive_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
+sifive_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
+sifive_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
diff --git a/hw/gpio/trace.h b/hw/gpio/trace.h
new file mode 100644
index 000000000..8b139071b
--- /dev/null
+++ b/hw/gpio/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_gpio.h"
diff --git a/hw/gpio/zaurus.c b/hw/gpio/zaurus.c
new file mode 100644
index 000000000..7cf52a504
--- /dev/null
+++ b/hw/gpio/zaurus.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2006-2008 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/arm/sharpsl.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+
+/* SCOOP devices */
+
+#define TYPE_SCOOP "scoop"
+OBJECT_DECLARE_SIMPLE_TYPE(ScoopInfo, SCOOP)
+
+struct ScoopInfo {
+ SysBusDevice parent_obj;
+
+ qemu_irq handler[16];
+ MemoryRegion iomem;
+ uint16_t status;
+ uint16_t power;
+ uint32_t gpio_level;
+ uint32_t gpio_dir;
+ uint32_t prev_level;
+
+ uint16_t mcr;
+ uint16_t cdr;
+ uint16_t ccr;
+ uint16_t irr;
+ uint16_t imr;
+ uint16_t isr;
+};
+
+#define SCOOP_MCR 0x00
+#define SCOOP_CDR 0x04
+#define SCOOP_CSR 0x08
+#define SCOOP_CPR 0x0c
+#define SCOOP_CCR 0x10
+#define SCOOP_IRR_IRM 0x14
+#define SCOOP_IMR 0x18
+#define SCOOP_ISR 0x1c
+#define SCOOP_GPCR 0x20
+#define SCOOP_GPWR 0x24
+#define SCOOP_GPRR 0x28
+
+static inline void scoop_gpio_handler_update(ScoopInfo *s) {
+ uint32_t level, diff;
+ int bit;
+ level = s->gpio_level & s->gpio_dir;
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+static uint64_t scoop_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+
+ switch (addr & 0x3f) {
+ case SCOOP_MCR:
+ return s->mcr;
+ case SCOOP_CDR:
+ return s->cdr;
+ case SCOOP_CSR:
+ return s->status;
+ case SCOOP_CPR:
+ return s->power;
+ case SCOOP_CCR:
+ return s->ccr;
+ case SCOOP_IRR_IRM:
+ return s->irr;
+ case SCOOP_IMR:
+ return s->imr;
+ case SCOOP_ISR:
+ return s->isr;
+ case SCOOP_GPCR:
+ return s->gpio_dir;
+ case SCOOP_GPWR:
+ case SCOOP_GPRR:
+ return s->gpio_level;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "scoop_read: bad register offset 0x%02" HWADDR_PRIx "\n",
+ addr);
+ }
+
+ return 0;
+}
+
+static void scoop_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+ value &= 0xffff;
+
+ switch (addr & 0x3f) {
+ case SCOOP_MCR:
+ s->mcr = value;
+ break;
+ case SCOOP_CDR:
+ s->cdr = value;
+ break;
+ case SCOOP_CPR:
+ s->power = value;
+ if (value & 0x80)
+ s->power |= 0x8040;
+ break;
+ case SCOOP_CCR:
+ s->ccr = value;
+ break;
+ case SCOOP_IRR_IRM:
+ s->irr = value;
+ break;
+ case SCOOP_IMR:
+ s->imr = value;
+ break;
+ case SCOOP_ISR:
+ s->isr = value;
+ break;
+ case SCOOP_GPCR:
+ s->gpio_dir = value;
+ scoop_gpio_handler_update(s);
+ break;
+ case SCOOP_GPWR:
+ case SCOOP_GPRR: /* GPRR is probably R/O in real HW */
+ s->gpio_level = value & s->gpio_dir;
+ scoop_gpio_handler_update(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "scoop_write: bad register offset 0x%02" HWADDR_PRIx "\n",
+ addr);
+ }
+}
+
+static const MemoryRegionOps scoop_ops = {
+ .read = scoop_read,
+ .write = scoop_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void scoop_gpio_set(void *opaque, int line, int level)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+
+ if (level)
+ s->gpio_level |= (1 << line);
+ else
+ s->gpio_level &= ~(1 << line);
+}
+
+static void scoop_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ ScoopInfo *s = SCOOP(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->status = 0x02;
+ qdev_init_gpio_out(dev, s->handler, 16);
+ qdev_init_gpio_in(dev, scoop_gpio_set, 16);
+ memory_region_init_io(&s->iomem, obj, &scoop_ops, s, "scoop", 0x1000);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static int scoop_post_load(void *opaque, int version_id)
+{
+ ScoopInfo *s = (ScoopInfo *) opaque;
+ int i;
+ uint32_t level;
+
+ level = s->gpio_level & s->gpio_dir;
+
+ for (i = 0; i < 16; i++) {
+ qemu_set_irq(s->handler[i], (level >> i) & 1);
+ }
+
+ s->prev_level = level;
+
+ return 0;
+}
+
+static bool is_version_0 (void *opaque, int version_id)
+{
+ return version_id == 0;
+}
+
+static bool vmstate_scoop_validate(void *opaque, int version_id)
+{
+ ScoopInfo *s = opaque;
+
+ return !(s->prev_level & 0xffff0000) &&
+ !(s->gpio_level & 0xffff0000) &&
+ !(s->gpio_dir & 0xffff0000);
+}
+
+static const VMStateDescription vmstate_scoop_regs = {
+ .name = "scoop",
+ .version_id = 1,
+ .minimum_version_id = 0,
+ .post_load = scoop_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(status, ScoopInfo),
+ VMSTATE_UINT16(power, ScoopInfo),
+ VMSTATE_UINT32(gpio_level, ScoopInfo),
+ VMSTATE_UINT32(gpio_dir, ScoopInfo),
+ VMSTATE_UINT32(prev_level, ScoopInfo),
+ VMSTATE_VALIDATE("irq levels are 16 bit", vmstate_scoop_validate),
+ VMSTATE_UINT16(mcr, ScoopInfo),
+ VMSTATE_UINT16(cdr, ScoopInfo),
+ VMSTATE_UINT16(ccr, ScoopInfo),
+ VMSTATE_UINT16(irr, ScoopInfo),
+ VMSTATE_UINT16(imr, ScoopInfo),
+ VMSTATE_UINT16(isr, ScoopInfo),
+ VMSTATE_UNUSED_TEST(is_version_0, 2),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void scoop_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Scoop2 Sharp custom ASIC";
+ dc->vmsd = &vmstate_scoop_regs;
+}
+
+static const TypeInfo scoop_sysbus_info = {
+ .name = TYPE_SCOOP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ScoopInfo),
+ .instance_init = scoop_init,
+ .class_init = scoop_sysbus_class_init,
+};
+
+static void scoop_register_types(void)
+{
+ type_register_static(&scoop_sysbus_info);
+}
+
+type_init(scoop_register_types)
+
+/* Write the bootloader parameters memory area. */
+
+#define MAGIC_CHG(a, b, c, d) ((d << 24) | (c << 16) | (b << 8) | a)
+
+static struct QEMU_PACKED sl_param_info {
+ uint32_t comadj_keyword;
+ int32_t comadj;
+
+ uint32_t uuid_keyword;
+ char uuid[16];
+
+ uint32_t touch_keyword;
+ int32_t touch_xp;
+ int32_t touch_yp;
+ int32_t touch_xd;
+ int32_t touch_yd;
+
+ uint32_t adadj_keyword;
+ int32_t adadj;
+
+ uint32_t phad_keyword;
+ int32_t phadadj;
+} zaurus_bootparam = {
+ .comadj_keyword = MAGIC_CHG('C', 'M', 'A', 'D'),
+ .comadj = 125,
+ .uuid_keyword = MAGIC_CHG('U', 'U', 'I', 'D'),
+ .uuid = { -1 },
+ .touch_keyword = MAGIC_CHG('T', 'U', 'C', 'H'),
+ .touch_xp = -1,
+ .adadj_keyword = MAGIC_CHG('B', 'V', 'A', 'D'),
+ .adadj = -1,
+ .phad_keyword = MAGIC_CHG('P', 'H', 'A', 'D'),
+ .phadadj = 0x01,
+};
+
+void sl_bootparam_write(hwaddr ptr)
+{
+ cpu_physical_memory_write(ptr, &zaurus_bootparam,
+ sizeof(struct sl_param_info));
+}