@@ -17,6 +17,9 @@ config GPIO_PWR
config SIFIVE_GPIO
bool
+config STM32_GPIO
+ bool
+
config STM32L4X5_GPIO
bool
@@ -15,6 +15,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_gpio.c',
'bcm2838_gpio.c'
))
+system_ss.add(when: 'CONFIG_STM32_GPIO', if_true: files('stm32_gpio.c'))
system_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_gpio.c'))
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
new file mode 100644
@@ -0,0 +1,386 @@
+/*
+ * STM32 System-on-Chip general purpose input/output register definition
+ *
+ * Copyright 2024 Román Cárdenas <rcardenas.rod@gmail.com>
+ *
+ * Based on sifive_gpio.c:
+ *
+ * Copyright 2019 AdaCore
+ *
+ * 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/arm/stm32.h"
+#include "hw/gpio/stm32_gpio.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void stm32_gpio_update_state(STM32GPIOState *s)
+{
+ bool prev_id, new_id, od, in, in_mask;
+ uint8_t mode, pupd;
+
+ for (size_t i = 0; i < s->ngpio; i++) {
+ prev_id = extract32(s->idr, i, 1);
+ od = extract32(s->odr, i, 1);
+ in = extract32(s->in, i, 1);
+ in_mask = extract32(s->in_mask, i, 1);
+
+ mode = extract32(s->moder, i * 2, 2);
+ pupd = extract32(s->pupdr, i * 2, 2);
+
+ /* Pin both driven externally and internally */
+ if (mode == STM32_GPIO_MODE_OUTPUT && 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 */
+ new_id = in;
+ } else if (mode == STM32_GPIO_MODE_OUTPUT) {
+ /* The pin is driven by internal circuit */
+ new_id = od;
+ } else {
+ /* Floating? Apply pull-up resistor */
+ new_id = pupd == STM32_GPIO_PULL_UP;
+ }
+
+ /* Update IDR */
+ s->idr = deposit32(s->idr, i, 1, new_id);
+
+ /* If pin is in input mode and IDR has changed, trigger an IRQ */
+ if (new_id != prev_id) {
+ if (mode == STM32_GPIO_MODE_INPUT) {
+ qemu_set_irq(s->input_irq[i], new_id);
+ }
+ }
+ }
+ /* Notify that GPIO has changed its state */
+ qemu_irq_pulse(s->state_irq);
+}
+
+static void stm32_gpio_reset(DeviceState *dev)
+{
+ STM32GPIOState *s = STM32_GPIO(dev);
+
+ /*
+ * Enabled is not affected by reset. It is ruled by RCC IDR is not
+ * directly reset. It is updated at the end by update_state
+ */
+
+ /* By default, we set all the registers to 0 */
+ s->moder = 0;
+ s->otyper = 0;
+ s->ospeedr = 0;
+ s->pupdr = 0;
+ s->odr = 0;
+ s->lckr = 0;
+ s->aflr = 0;
+ s->afhr = 0;
+
+ /* Next, we check model particularities */
+ if (s->family == STM32_F4) {
+ if (s->port == STM32_GPIO_PORT_A) {
+ s->moder = 0xA8000000;
+ s->pupdr = 0x64000000;
+ } else if (s->port == STM32_GPIO_PORT_B) {
+ s->moder = 0x00000280;
+ s->ospeedr = 0x000000C0;
+ s->pupdr = 0x00000100;
+ }
+ }
+
+ stm32_gpio_update_state(s);
+}
+
+static void stm32_gpio_irq_reset(void *opaque, int line, int value)
+{
+ STM32GPIOState *s = STM32_GPIO(opaque);
+
+ trace_stm32_gpio_irq_reset(line, value);
+
+ bool prev_reset = s->reset;
+ s->reset = value != 0;
+ if (prev_reset != s->reset) {
+ if (s->reset) {
+ stm32_gpio_reset(DEVICE(s));
+ } else {
+ stm32_gpio_update_state(s);
+ }
+ }
+}
+
+static void stm32_gpio_irq_enable(void *opaque, int line, int value)
+{
+ STM32GPIOState *s = STM32_GPIO(opaque);
+
+ trace_stm32_gpio_irq_enable(line, value);
+
+ bool prev_enable = s->enable;
+ s->enable = value != 0;
+ if (prev_enable != s->enable) {
+ stm32_gpio_update_state(s);
+ }
+}
+
+static void stm32_gpio_irq_set(void *opaque, int line, int value)
+{
+ STM32GPIOState *s = STM32_GPIO(opaque);
+
+ trace_stm32_gpio_irq_set(line, value);
+
+ assert(line >= 0 && line < s->ngpio);
+
+ s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
+
+ /*
+ * If value < 0, the pin is connected to a load.
+ * If value == 0, the pin is low.
+ * If value > 0, the pin is high.
+ */
+ if (value >= 0) {
+ s->in = deposit32(s->in, line, 1, value != 0);
+ }
+
+ stm32_gpio_update_state(s);
+}
+
+
+static uint64_t stm32_gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ STM32GPIOState *s = STM32_GPIO(opaque);
+ uint64_t r = 0;
+
+ if (!s->enable) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR, "%s: GPIO peripheral is disabled\n", __func__
+ );
+ return 0;
+ }
+
+ switch (offset) {
+ case STM32_GPIO_REG_MODER:
+ r = s->moder;
+ break;
+
+ case STM32_GPIO_REG_OTYPER:
+ r = s->otyper;
+ break;
+
+ case STM32_GPIO_REG_OSPEEDR:
+ r = s->ospeedr;
+ break;
+
+ case STM32_GPIO_REG_PUPDR:
+ r = s->pupdr;
+ break;
+
+ case STM32_GPIO_REG_IDR:
+ r = s->idr;
+ break;
+
+ case STM32_GPIO_REG_ODR:
+ r = s->odr;
+ break;
+
+ case STM32_GPIO_REG_BSRR:
+ break; /* BSRR is write-only */
+
+ case STM32_GPIO_REG_LCKR:
+ r = s->lckr;
+ break;
+
+ case STM32_GPIO_REG_AFRL:
+ r = s->aflr;
+ break;
+
+ case STM32_GPIO_REG_AFRH:
+ r = s->afhr;
+ break;
+
+ case STM32_GPIO_REG_BRR:
+ if (s->family != STM32_F4) {
+ break; /* BRR is write-only */
+ }
+ /* STM32F4xx SoCs do not have this register */
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n", __func__, offset
+ );
+ break;
+
+ default:
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n", __func__, offset
+ );
+ }
+
+ trace_stm32_gpio_read(offset, r);
+
+ return r;
+}
+
+static void stm32_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned int size)
+{
+ STM32GPIOState *s = STM32_GPIO(opaque);
+
+ trace_stm32_gpio_write(offset, value);
+
+ if (!s->enable) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR, "%s: GPIO peripheral is disabled\n", __func__
+ );
+ return;
+ }
+
+ switch (offset) {
+
+ case STM32_GPIO_REG_MODER:
+ s->moder = value;
+ break;
+
+ case STM32_GPIO_REG_OTYPER:
+ s->otyper = value;
+ break;
+
+ case STM32_GPIO_REG_OSPEEDR:
+ s->ospeedr = value;
+ break;
+
+ case STM32_GPIO_REG_PUPDR:
+ s->pupdr = value;
+ break;
+
+ case STM32_GPIO_REG_IDR:
+ break; /* IDR is read-only */
+
+ case STM32_GPIO_REG_ODR:
+ s->odr = value; /* IDR is updated in update_state */
+ break;
+
+ case STM32_GPIO_REG_BSRR:
+ s->odr &= ~((value >> 16) & 0xFFFF);
+ /* set bits have higher priority than reset bits */
+ s->odr |= value & 0xFFFF;
+ break;
+
+ case STM32_GPIO_REG_LCKR:
+ s->lckr = value;
+ break;
+
+ case STM32_GPIO_REG_AFRL:
+ s->aflr = value;
+ break;
+
+ case STM32_GPIO_REG_AFRH:
+ s->afhr = value;
+ break;
+
+ case STM32_GPIO_REG_BRR:
+ if (s->family != STM32_F4) {
+ s->odr &= ~(value & 0xFFFF);
+ break;
+ }
+ /* STM32F4xx SoCs do not have this register */
+ qemu_log_mask(
+ LOG_GUEST_ERROR, "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset
+ );
+ break;
+
+ default:
+ qemu_log_mask(
+ LOG_GUEST_ERROR, "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset
+ );
+ }
+
+ stm32_gpio_update_state(s);
+}
+
+static const MemoryRegionOps stm32_gpio_ops = {
+ .read = stm32_gpio_read,
+ .write = stm32_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_stm32_gpio = {
+ .name = TYPE_STM32_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(moder, STM32GPIOState),
+ VMSTATE_UINT32(otyper, STM32GPIOState),
+ VMSTATE_UINT32(ospeedr, STM32GPIOState),
+ VMSTATE_UINT32(pupdr, STM32GPIOState),
+ VMSTATE_UINT32(idr, STM32GPIOState),
+ VMSTATE_UINT32(odr, STM32GPIOState),
+ VMSTATE_UINT32(lckr, STM32GPIOState),
+ VMSTATE_UINT32(aflr, STM32GPIOState),
+ VMSTATE_UINT32(afhr, STM32GPIOState),
+ VMSTATE_BOOL(reset, STM32GPIOState),
+ VMSTATE_BOOL(enable, STM32GPIOState),
+ VMSTATE_UINT32(in, STM32GPIOState),
+ VMSTATE_UINT32(in_mask, STM32GPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property stm32_gpio_properties[] = {
+ DEFINE_PROP_UINT32("family", STM32GPIOState, family, STM32_F2),
+ DEFINE_PROP_UINT32("port", STM32GPIOState, port, STM32_GPIO_PORT_A),
+ DEFINE_PROP_UINT32("ngpio", STM32GPIOState, ngpio, STM32_GPIO_NPINS),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32_gpio_realize(DeviceState *dev, Error **errp)
+{
+ STM32GPIOState *s = STM32_GPIO(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &stm32_gpio_ops, s,
+ TYPE_STM32_GPIO, STM32_GPIO_PERIPHERAL_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+
+ qdev_init_gpio_in_named(DEVICE(s), stm32_gpio_irq_reset, "reset-in", 1);
+ qdev_init_gpio_in_named(DEVICE(s), stm32_gpio_irq_enable, "enable-in", 1);
+ qdev_init_gpio_in_named(DEVICE(s), stm32_gpio_irq_set,
+ "input-in", STM32_GPIO_NPINS);
+
+ qdev_init_gpio_out_named(DEVICE(s), &s->state_irq, "state-out", 1);
+ qdev_init_gpio_out_named(DEVICE(s), s->input_irq,
+ "input-out", STM32_GPIO_NPINS);
+}
+
+static void stm32_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, stm32_gpio_properties);
+ dc->vmsd = &vmstate_stm32_gpio;
+ dc->realize = stm32_gpio_realize;
+ device_class_set_legacy_reset(dc, stm32_gpio_reset);
+ dc->desc = "STM32 GPIO";
+}
+
+static const TypeInfo stm32_gpio_info = {
+ .name = TYPE_STM32_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32GPIOState),
+ .class_init = stm32_gpio_class_init
+};
+
+static void stm32_gpio_register_types(void)
+{
+ type_register_static(&stm32_gpio_info);
+}
+
+type_init(stm32_gpio_register_types)
@@ -36,6 +36,14 @@ sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " val
aspeed_gpio_read(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " value 0x%" PRIx64
aspeed_gpio_write(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " value 0x%" PRIx64
+# stm32_gpio.c
+stm32_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
+stm32_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
+stm32_gpio_irq_enable(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+stm32_gpio_irq_reset(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+stm32_gpio_irq_set(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+stm32_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " value %" PRIi64
+
# stm32l4x5_gpio.c
stm32l4x5_gpio_read(char *gpio, uint64_t addr) "GPIO%s addr: 0x%" PRIx64 " "
stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
new file mode 100644
@@ -0,0 +1,41 @@
+/*
+ * STM32 chip configuration parameters.
+ * These enums are used to configure STM32 chips, as well as their peripherals.
+ *
+ * Copyright 2024 Román Cárdenas <rcardenas.rod@gmail.com>
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef STM32_H
+#define STM32_H
+
+enum {
+ /* High Performance */
+ STM32_F2,
+ STM32_F4,
+ STM32_H5,
+ STM32_F7,
+ STM32_H7,
+ /* Mainstream */
+ STM32_C0,
+ STM32_F0,
+ STM32_G0,
+ STM32_F1,
+ STM32_F3,
+ STM32_G4,
+ /* Ultra Low Power */
+ STM32_L0,
+ STM32_L4,
+ STM32_L4P,
+ STM32_L5,
+ STM32_U5,
+ /* Wireless */
+ STM32_WL,
+ STM32_WB0,
+ STM32_WB,
+ STM32_WBA,
+};
+
+#endif /* STM32_H */
new file mode 100644
@@ -0,0 +1,109 @@
+/*
+ * STM32 System-on-Chip general purpose input/output register definition.
+ * While this implementation should work for most of STM32 SoCs, there are
+ * a few chips with different GPIO peripheral. For example, STM32F1 series.
+ *
+ * Copyright 2024 Román Cárdenas <rcardenas.rod@gmail.com>
+ *
+ * Based on sifive_gpio.c:
+ *
+ * Copyright 2019 AdaCore
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef STM32_GPIO_H
+#define STM32_GPIO_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_STM32_GPIO "stm32.gpio"
+
+typedef struct STM32GPIOState STM32GPIOState;
+
+DECLARE_INSTANCE_CHECKER(STM32GPIOState, STM32_GPIO, TYPE_STM32_GPIO)
+
+#define STM32_GPIO_REG_MODER 0x000
+#define STM32_GPIO_REG_OTYPER 0x004
+#define STM32_GPIO_REG_OSPEEDR 0x008
+#define STM32_GPIO_REG_PUPDR 0x00C
+#define STM32_GPIO_REG_IDR 0x010
+#define STM32_GPIO_REG_ODR 0x014
+#define STM32_GPIO_REG_BSRR 0x018
+#define STM32_GPIO_REG_LCKR 0x01C
+#define STM32_GPIO_REG_AFRL 0x020
+#define STM32_GPIO_REG_AFRH 0x024
+#define STM32_GPIO_REG_BRR 0x028
+
+#define STM32_GPIO_NPINS 16
+#define STM32_GPIO_PERIPHERAL_SIZE 0x400
+
+struct STM32GPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+
+ /* GPIO registers */
+ uint32_t moder;
+ uint32_t otyper;
+ uint32_t ospeedr;
+ uint32_t pupdr;
+ uint32_t idr; /* Actual value of the pin */
+ uint32_t odr; /* Pin value requested by the user */
+ uint32_t lckr; /* TODO implement locking sequence */
+ uint32_t aflr;
+ uint32_t afhr;
+
+ /* state flags from RCC */
+ bool reset;
+ bool enable;
+
+ /* External input */
+ uint32_t in;
+ /*
+ * If in_mask == 0, the pin is disconnected/connected to a load.
+ * If value == 1, the pin is connected to value in in.
+ */
+ uint32_t in_mask;
+
+ /* IRQ to notify that the GPIO has updated its state */
+ qemu_irq state_irq;
+ /* IRQs to relay each input pin changes to other STM32 peripherals */
+ qemu_irq input_irq[STM32_GPIO_NPINS];
+
+ /* config */
+ uint32_t family; /* e.g. STM32_F4 */
+ uint32_t port; /* e.g. STM32_GPIO_PORT_A */
+ uint32_t ngpio; /* e.g. 16 */
+};
+
+enum STM32GPIOPort {
+ STM32_GPIO_PORT_A = 0,
+ STM32_GPIO_PORT_B = 1,
+ STM32_GPIO_PORT_C = 2,
+ STM32_GPIO_PORT_D = 3,
+ STM32_GPIO_PORT_E = 4,
+ STM32_GPIO_PORT_F = 5,
+ STM32_GPIO_PORT_G = 6,
+ STM32_GPIO_PORT_H = 7,
+ STM32_GPIO_PORT_I = 8,
+ STM32_GPIO_PORT_J = 9,
+ STM32_GPIO_PORT_K = 10,
+};
+
+enum STM32GPIOMode {
+ STM32_GPIO_MODE_INPUT = 0,
+ STM32_GPIO_MODE_OUTPUT = 1,
+ STM32_GPIO_MODE_AF = 2,
+ STM32_GPIO_MODE_ANALOG = 3,
+};
+
+enum STM32GPIOPull {
+ STM32_GPIO_PULL_NONE = 0,
+ STM32_GPIO_PULL_UP = 1,
+ STM32_GPIO_PULL_DOWN = 2,
+};
+
+#endif /* STM32_GPIO_H */
Generic GPIO class for STM32 devices. It can be used for most of STM32 chips. Note that it does not implement configuration locking mechanisms. Signed-off-by: Roman Cardenas Rodriguez <rcardenas.rod@gmail.com> --- hw/gpio/Kconfig | 3 + hw/gpio/meson.build | 1 + hw/gpio/stm32_gpio.c | 386 +++++++++++++++++++++++++++++++++++ hw/gpio/trace-events | 8 + include/hw/arm/stm32.h | 41 ++++ include/hw/gpio/stm32_gpio.h | 109 ++++++++++ 6 files changed, 548 insertions(+) create mode 100644 hw/gpio/stm32_gpio.c create mode 100644 include/hw/arm/stm32.h create mode 100644 include/hw/gpio/stm32_gpio.h