diff mbox series

[3/4] STM32: new GPIO device

Message ID 20240927150738.57786-3-rcardenas.rod@gmail.com (mailing list archive)
State New, archived
Headers show
Series [1/4] STM32F4: new RCC device | expand

Commit Message

Román Cárdenas Rodríguez Sept. 27, 2024, 3:07 p.m. UTC
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

Comments

Peter Maydell Oct. 4, 2024, 6:08 p.m. UTC | #1
On Fri, 27 Sept 2024 at 16:08, Román Cárdenas Rodríguez
<rcardenas.rod@gmail.com> wrote:
>
> Generic GPIO class for STM32 devices. It can be used for most of STM32 chips.
> Note that it does not implement configuration locking mechanisms.

So we already have an stm32l4x5 GPIO device. How different
is that one from these ones? Should we be sharing implementation,
or are they too different?

When adding a new file, could you please add lines to
the MAINTAINERS file putting it in an appropriate subsection?

> +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;

QEMU's coding style says that variable declarations
should always be at the start of a code block, never
in the middle. (There are some other cases of this here
and in patch 4, I think.)

> +    s->reset = value != 0;
> +    if (prev_reset != s->reset) {
> +        if (s->reset) {
> +            stm32_gpio_reset(DEVICE(s));
> +        } else {
> +            stm32_gpio_update_state(s);
> +        }
> +    }
> +}

> +static Property stm32_gpio_properties[] = {
> +    DEFINE_PROP_UINT32("family", STM32GPIOState, family, STM32_F2),

For this sort of situation where we have a set of devices that
are very similar but have some slight model-to-model variation,
rather than using a device property to tell the device which
variation it is, we generally use a parent class with the
implementation and a set of child classes which tweak fields
controlling the parent class behaviour. For an example, look at
hw/gpio/aspeed_gpio.c -- TYPE_ASPEED_GPIO is marked ".abstract = true",
and it has most of the implementation. Then the various subtypes
inherit from it, and their class init functions set fields in
the class struct to define the behaviour.
hw/char/stm32l4x5_usart.c is another example.

> +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);

Could you use the non-legacy reset instead? It's not much of
a change, instead of calling this you set the ResettableClass
phases.hold, and the reset function has a slightly different
function signature. hw/char/stm32l4x5_usart.c has an example.

thanks
-- PMM
Peter Maydell Oct. 4, 2024, 6:23 p.m. UTC | #2
On Fri, 27 Sept 2024 at 16:08, Román Cárdenas Rodríguez
<rcardenas.rod@gmail.com> wrote:
>
> 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

Oh, I forgot -- could you consider adding some tests for the
new device? We have tests already for stm32l4x5_gpio in
tests/qtest/stm32l4x5_gpio-test.c which you can probably use
as a pattern. You can add the test cases as an extra patch on
the end of the series.

thanks
-- PMM
Román Cárdenas Rodríguez Oct. 9, 2024, 2:32 p.m. UTC | #3
Looks like the stm32l4x5 is quite similar to my implementation. It didn’t exist when I started with my implementation . I will take a closer look and work on improving/extending the stm32l4x5 GPIO with my proposal. Is it OK if I rename it to stm32_gpio? so it is clearer that the implementation is generic and can fit almost any STM32 target.

Sorry about the issues with the patches, I am not familiar with this methodology of working with git.
Could you guide me on how to send with you a revised version of patches 3 and 4?

> On 4 Oct 2024, at 20:08, Peter Maydell <peter.maydell@linaro.org> wrote:
> 
> On Fri, 27 Sept 2024 at 16:08, Román Cárdenas Rodríguez
> <rcardenas.rod@gmail.com> wrote:
>> 
>> Generic GPIO class for STM32 devices. It can be used for most of STM32 chips.
>> Note that it does not implement configuration locking mechanisms.
> 
> So we already have an stm32l4x5 GPIO device. How different
> is that one from these ones? Should we be sharing implementation,
> or are they too different?
> 
> When adding a new file, could you please add lines to
> the MAINTAINERS file putting it in an appropriate subsection?
> 
>> +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;
> 
> QEMU's coding style says that variable declarations
> should always be at the start of a code block, never
> in the middle. (There are some other cases of this here
> and in patch 4, I think.)
> 
>> +    s->reset = value != 0;
>> +    if (prev_reset != s->reset) {
>> +        if (s->reset) {
>> +            stm32_gpio_reset(DEVICE(s));
>> +        } else {
>> +            stm32_gpio_update_state(s);
>> +        }
>> +    }
>> +}
> 
>> +static Property stm32_gpio_properties[] = {
>> +    DEFINE_PROP_UINT32("family", STM32GPIOState, family, STM32_F2),
> 
> For this sort of situation where we have a set of devices that
> are very similar but have some slight model-to-model variation,
> rather than using a device property to tell the device which
> variation it is, we generally use a parent class with the
> implementation and a set of child classes which tweak fields
> controlling the parent class behaviour. For an example, look at
> hw/gpio/aspeed_gpio.c -- TYPE_ASPEED_GPIO is marked ".abstract = true",
> and it has most of the implementation. Then the various subtypes
> inherit from it, and their class init functions set fields in
> the class struct to define the behaviour.
> hw/char/stm32l4x5_usart.c is another example.
> 
>> +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);
> 
> Could you use the non-legacy reset instead? It's not much of
> a change, instead of calling this you set the ResettableClass
> phases.hold, and the reset function has a slightly different
> function signature. hw/char/stm32l4x5_usart.c has an example.
> 
> thanks
> -- PMM
Peter Maydell Oct. 10, 2024, 10:20 a.m. UTC | #4
On Wed, 9 Oct 2024 at 15:32, Román Cárdenas Rodríguez
<rcardenas.rod@gmail.com> wrote:
>
> Looks like the stm32l4x5 is quite similar to my implementation. It didn’t exist when I started with my implementation . I will take a closer look and work on improving/extending the stm32l4x5 GPIO with my proposal. Is it OK if I rename it to stm32_gpio? so it is clearer that the implementation is generic and can fit almost any STM32 target.

Yes, renaming would be OK. (Do that in a patch of its own
that does the rename and nothing else.)

> Sorry about the issues with the patches, I am not familiar with this methodology of working with git.
> Could you guide me on how to send with you a revised version of patches 3 and 4?

So I've taken your patches 1 and 2 into my git tree; they
will appear upstream probably either end of this week
or beginning of next week. If that happens before you're
ready to send out a version 2 of this series, you can
rebase your patches on head-of-git and then send
out a new series with patches 3 and 4 in it. If you're
ready to send v2 before the other patches land in upstream
git, just send out v2 with the whole 4-patch set in it.

thanks
-- PMM
diff mbox series

Patch

diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index 19c97cc823..9601b7d1bf 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -17,6 +17,9 @@  config GPIO_PWR
 config SIFIVE_GPIO
     bool
 
+config STM32_GPIO
+    bool
+
 config STM32L4X5_GPIO
     bool
 
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index a7495d196a..81f2a5458e 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -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'))
diff --git a/hw/gpio/stm32_gpio.c b/hw/gpio/stm32_gpio.c
new file mode 100644
index 0000000000..825607b56a
--- /dev/null
+++ b/hw/gpio/stm32_gpio.c
@@ -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)
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index b91cc7e9a4..b469c5dec1 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -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 ""
diff --git a/include/hw/arm/stm32.h b/include/hw/arm/stm32.h
new file mode 100644
index 0000000000..7e8b9a5524
--- /dev/null
+++ b/include/hw/arm/stm32.h
@@ -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 */
diff --git a/include/hw/gpio/stm32_gpio.h b/include/hw/gpio/stm32_gpio.h
new file mode 100644
index 0000000000..373c2fa842
--- /dev/null
+++ b/include/hw/gpio/stm32_gpio.h
@@ -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 */