diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/gpio | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (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/Kconfig | 15 | ||||
-rw-r--r-- | hw/gpio/aspeed_gpio.c | 996 | ||||
-rw-r--r-- | hw/gpio/bcm2835_gpio.c | 344 | ||||
-rw-r--r-- | hw/gpio/gpio_key.c | 109 | ||||
-rw-r--r-- | hw/gpio/gpio_pwr.c | 70 | ||||
-rw-r--r-- | hw/gpio/imx_gpio.c | 355 | ||||
-rw-r--r-- | hw/gpio/max7310.c | 218 | ||||
-rw-r--r-- | hw/gpio/meson.build | 14 | ||||
-rw-r--r-- | hw/gpio/mpc8xxx.c | 224 | ||||
-rw-r--r-- | hw/gpio/npcm7xx_gpio.c | 424 | ||||
-rw-r--r-- | hw/gpio/nrf51_gpio.c | 318 | ||||
-rw-r--r-- | hw/gpio/omap_gpio.c | 813 | ||||
-rw-r--r-- | hw/gpio/pl061.c | 603 | ||||
-rw-r--r-- | hw/gpio/sifive_gpio.c | 397 | ||||
-rw-r--r-- | hw/gpio/trace-events | 29 | ||||
-rw-r--r-- | hw/gpio/trace.h | 1 | ||||
-rw-r--r-- | hw/gpio/zaurus.c | 305 |
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)); +} |