qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Add device STM32L4x5 GPIO
@ 2024-01-22  9:18 Inès Varhol
  2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Inès Varhol @ 2024-01-22  9:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Arnaud Minier, Philippe Mathieu-Daudé,
	Inès Varhol, Laurent Vivier, Thomas Huth, Samuel Tardieu,
	Paolo Bonzini, Alistair Francis, Peter Maydell

This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

Changes from RFC v1 :
- `stm32l4x5-gpio-test.c` : correct typos, make the test generic,
add a test for bitwise writing in register ODR
- `stm32l4x5_soc.c` : connect gpios to their clock, use an
array of GpioState
- `stm32l4x5_gpio.c` : correct comments in `update_gpio_idr()`,
correct `get_gpio_pins_to_disconnect()`, correct `stm32l4x5_gpio_init()`
and initialize the clock, add a realize function
- add a summary in the commit messages
- update MAINAINERS

Based-on: 20240118091107.87831-1-arnaud.minier@telecom-paris.fr
([PATCH v2 0/7] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>

Inès Varhol (3):
  hw/gpio: Implement STM32L4x5 GPIO
  hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC
  tests/qtest: Add STM32L4x5 GPIO QTest testcase

 MAINTAINERS                        |   1 +
 docs/system/arm/b-l475e-iot01a.rst |   2 +-
 hw/arm/Kconfig                     |   3 +-
 hw/arm/stm32l4x5_soc.c             |  67 +++-
 hw/gpio/Kconfig                    |   3 +
 hw/gpio/meson.build                |   1 +
 hw/gpio/stm32l4x5_gpio.c           | 537 +++++++++++++++++++++++++++++
 hw/gpio/trace-events               |   6 +
 include/hw/arm/stm32l4x5_soc.h     |   2 +
 include/hw/gpio/stm32l4x5_gpio.h   |  80 +++++
 tests/qtest/meson.build            |   3 +-
 tests/qtest/stm32l4x5_gpio-test.c  | 526 ++++++++++++++++++++++++++++
 12 files changed, 1215 insertions(+), 16 deletions(-)
 create mode 100644 hw/gpio/stm32l4x5_gpio.c
 create mode 100644 include/hw/gpio/stm32l4x5_gpio.h
 create mode 100644 tests/qtest/stm32l4x5_gpio-test.c

-- 
2.43.0



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/3] hw/gpio: Implement STM32L4x5 GPIO
  2024-01-22  9:18 [PATCH 0/3] Add device STM32L4x5 GPIO Inès Varhol
@ 2024-01-22  9:18 ` Inès Varhol
  2024-01-22 11:32   ` Philippe Mathieu-Daudé
  2024-01-22 11:37   ` Philippe Mathieu-Daudé
  2024-01-22  9:18 ` [PATCH 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
  2024-01-22  9:18 ` [PATCH 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol
  2 siblings, 2 replies; 6+ messages in thread
From: Inès Varhol @ 2024-01-22  9:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Arnaud Minier, Philippe Mathieu-Daudé,
	Inès Varhol, Laurent Vivier, Thomas Huth, Samuel Tardieu,
	Paolo Bonzini, Alistair Francis, Peter Maydell

Features supported :
- the 8 STM32L4x5 GPIOs are initialized with their reset values
    (except IDR, see below)
- input mode : setting a pin in input mode "externally" (using input
    irqs) results in an out irq (transmitted to SYSCFG)
- output mode : setting a bit in ODR sets the corresponding out irq
    (if this line is configured in output mode)
- pull-up, pull-down
- push-pull, open-drain

Difference with the real GPIOs :
- Alternate Function and Analog mode aren't implemented :
    pins in AF/Analog behave like pins in input mode
- floating pins stay at their last value
- register IDR reset values differ from the real one :
    values are coherent with the other registers reset values
    and the fact that AF/Analog modes aren't implemented
- setting I/O output speed isn't supported
- locking port bits isn't supported
- ADC function isn't supported
- GPIOH has 16 pins instead of 2 pins
- writing to registers LCKR, AFRL, AFRH and ASCR is ineffective

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 MAINTAINERS                        |   1 +
 docs/system/arm/b-l475e-iot01a.rst |   2 +-
 hw/gpio/Kconfig                    |   3 +
 hw/gpio/meson.build                |   1 +
 hw/gpio/stm32l4x5_gpio.c           | 537 +++++++++++++++++++++++++++++
 hw/gpio/trace-events               |   6 +
 include/hw/gpio/stm32l4x5_gpio.h   |  80 +++++
 7 files changed, 629 insertions(+), 1 deletion(-)
 create mode 100644 hw/gpio/stm32l4x5_gpio.c
 create mode 100644 include/hw/gpio/stm32l4x5_gpio.h

diff --git a/MAINTAINERS b/MAINTAINERS
index c4085c32a7..269ed96052 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1131,6 +1131,7 @@ F: hw/arm/stm32l4x5_soc.c
 F: hw/misc/stm32l4x5_exti.c
 F: hw/misc/stm32l4x5_syscfg.c
 F: hw/misc/stm32l4x5_rcc.c
+F: hw/gpio/stm32l4x5_gpio.c
 F: include/hw/*/stm32l4x5_*.h
 
 B-L475E-IOT01A IoT Node
diff --git a/docs/system/arm/b-l475e-iot01a.rst b/docs/system/arm/b-l475e-iot01a.rst
index b857a56ca4..0afef8e4f4 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -18,6 +18,7 @@ Currently B-L475E-IOT01A machine's only supports the following devices:
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
+- STM32L4x5 GPIOs (General-purpose I/Os)
 
 Missing devices
 """""""""""""""
@@ -25,7 +26,6 @@ Missing devices
 The B-L475E-IOT01A does *not* support the following devices:
 
 - Serial ports (UART)
-- General-purpose I/Os (GPIO)
 - Analog to Digital Converter (ADC)
 - SPI controller
 - Timer controller (TIMER)
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index d2cf3accc8..712940b8e0 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -16,3 +16,6 @@ config GPIO_PWR
 
 config SIFIVE_GPIO
     bool
+
+config STM32L4X5_GPIO
+    bool
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 066ea96480..8470ca1639 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -9,6 +9,7 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpio.c'))
 system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_gpio.c'))
 system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_gpio.c'))
 system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_gpio.c'))
+system_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_gpio.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_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/stm32l4x5_gpio.c b/hw/gpio/stm32l4x5_gpio.c
new file mode 100644
index 0000000000..35df427363
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1,537 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ * https://www.st.com/en/microcontrollers-microprocessors/stm32l4x5/documentation.html
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define GPIO_MODER 0x00
+#define GPIO_OTYPER 0x04
+#define GPIO_OSPEEDR 0x08
+#define GPIO_PUPDR 0x0C
+#define GPIO_IDR 0x10
+#define GPIO_ODR 0x14
+#define GPIO_BSRR 0x18
+#define GPIO_LCKR 0x1C
+#define GPIO_AFRL 0x20
+#define GPIO_AFRH 0x24
+#define GPIO_BRR 0x28
+#define GPIO_ASCR 0x2C
+/*
+ * DISCONNECTED_PINS isn't actually a GPIO register.
+ * It exists to ensure that :
+ * - push-pull output pins can't be set externally
+ * - open-drain output pins can only be externally set to 0
+ *
+ * This field is accessed for test purposes.
+ */
+#define GPIO_DISCONNECTED_PINS 0x30
+
+/* 0b11111111_11111111_00000000_00000000 */
+#define RESERVED_BITS_MASK 0xFFFF0000
+
+static void update_gpio_idr(Stm32l4x5GpioState *s);
+
+static void stm32l4x5_gpio_reset_hold(Object *obj)
+{
+    Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_GET_CLASS(obj);
+
+    s->moder = sc->moder_reset;
+    s->otyper = 0x00000000;
+    s->ospeedr = sc->ospeedr_reset;
+    s->pupdr = sc->pupdr_reset;
+    s->idr = 0x00000000;
+    s->odr = 0x00000000;
+    s->lckr = 0x00000000;
+    s->afrl = 0x00000000;
+    s->afrh = 0x00000000;
+    s->ascr = 0x00000000;
+
+    s->disconnected_pins = 0xFFFF;
+    s->pins_connected_high = 0x0000;
+    update_gpio_idr(s);
+}
+
+static void stm32l4x5_gpio_set(void *opaque, int line, int level)
+{
+    Stm32l4x5GpioState *s = opaque;
+    /*
+     * The pin isn't set if line is configured in output mode
+     * except if level is 0 and the output is open-drain.
+     * This way there will be no short-circuit prone situations.
+     */
+    if ((extract32(s->moder, 2 * line, 2) == 1) &&
+        !((extract32(s->otyper, line, 1) == 1) &&
+          (level == 0))) {
+        qemu_log_mask(LOG_GUEST_ERROR, "Line %d can't be driven externally\n",
+                      line);
+        return;
+    }
+
+    s->disconnected_pins &= ~(1 << line);
+    if (level) {
+        s->pins_connected_high |= (1 << line);
+    } else {
+        s->pins_connected_high &= ~(1 << line);
+    }
+    trace_stm32l4x5_gpio_pins(s->disconnected_pins,
+                              s->pins_connected_high);
+    update_gpio_idr(s);
+}
+
+
+static void update_gpio_idr(Stm32l4x5GpioState *s)
+{
+    uint32_t new_idr_mask = 0;
+    uint32_t new_idr = s->odr;
+
+    for (int i = 0; i < 16; i++) {
+        /* output mode */
+        if (extract32(s->moder, 2 * i, 2) == 1) {
+            if (extract32(s->otyper, i, 1) == 0) {
+                /* push-pull */
+                new_idr_mask |= (1 << i);
+            } else if (!(s->odr & (1 << i))) {
+                /* open-drain ODR 0 */
+                new_idr_mask |= (1 << i);
+            } else if ((s->disconnected_pins & (1 << i)) &&
+                       (extract32(s->pupdr, 2 * i, 2) == 1)) {
+                /* open-drain pull-up ODR 1 with disconnected pin */
+                new_idr_mask |= (1 << i);
+            } else if ((s->disconnected_pins & (1 << i)) &&
+                       (extract32(s->pupdr, 2 * i, 2) == 2)) {
+                /* open-drain pull-down ODR 1 with disconnected pin */
+                new_idr_mask |= (1 << i);
+                new_idr &= ~(1 << i);
+            } else if (!(s->pins_connected_high & (1 << i))) {
+                /* open-drain ODR 1 with pin connected low */
+                new_idr_mask |= (1 << i);
+                new_idr &= ~(1 << i);
+            }
+            /*
+             * The only case left is for open-drain ODR 1
+             * with disconnected pin without pull-up or pull-down :
+             * the value is floating.
+             */
+        /* input or analog mode with connected pin */
+        } else if (!(s->disconnected_pins & (1 << i))) {
+            if (s->pins_connected_high & (1 << i)) {
+                /* pin high */
+                new_idr_mask |= (1 << i);
+                new_idr |= (1 << i);
+            } else {
+                /* pin low */
+                new_idr_mask |= (1 << i);
+                new_idr &= ~(1 << i);
+            }
+        /* input or analog mode with disconnected pin */
+        } else {
+            if (extract32(s->pupdr, 2 * i, 2) == 1) {
+                /* pull-up */
+                new_idr_mask |= (1 << i);
+                new_idr |= (1 << i);
+            } else if (extract32(s->pupdr, 2 * i, 2) == 2) {
+                /* pull-down */
+                new_idr_mask |= (1 << i);
+                new_idr &= ~(1 << i);
+            }
+            /*
+             * The only case left is for a disconnected pin
+             * without pull-up or pull-down :
+             * the value is floating.
+             */
+        }
+    }
+
+    uint32_t old_idr = s->idr;
+    s->idr = (old_idr & ~new_idr_mask) | (new_idr & new_idr_mask);
+    trace_stm32l4x5_gpio_update_idr(old_idr, s->idr);
+
+    for (int i = 0; i < 16; i++) {
+        if (new_idr_mask & (1 << i)) {
+            if ((new_idr & (1 << i)) > (old_idr & (1 << i))) {
+                qemu_irq_raise(s->pin[i]);
+            } else if ((new_idr & (1 << i)) < (old_idr & (1 << i))) {
+                qemu_irq_lower(s->pin[i]);
+            }
+        }
+    }
+}
+
+/*
+ * Return pins both configured in output mode
+ * and externally driven (except pins in open-drain
+ * mode externally set to 0).
+ */
+static uint32_t get_gpio_pins_to_disconnect(Stm32l4x5GpioState *s)
+{
+    uint32_t pins_to_disconnect = 0;
+    for (int i = 0; i < 16; i++) {
+        /* for each connected pin in output mode */
+        if ((~s->disconnected_pins & (1 << i)) &&
+            (extract32(s->moder, 2 * i, 2) == 1)) {
+            /* if either push-pull or high level */
+            if ((extract32(s->otyper, i, 1) == 0) ||
+                (extract16(s->pins_connected_high, i, 1)) == 1) {
+                pins_to_disconnect |= (1 << i);
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "Line %d can't be driven externally\n",
+                              i);
+            }
+        }
+    }
+    return pins_to_disconnect;
+}
+
+/*
+ * Set field `disconnected_pins` and call `update_gpio_idr()`
+ */
+static void disconnect_gpio_pins(Stm32l4x5GpioState *s, uint16_t lines)
+{
+    s->disconnected_pins |= lines;
+    trace_stm32l4x5_gpio_pins(s->disconnected_pins,
+                              s->pins_connected_high);
+    update_gpio_idr(s);
+}
+
+static void stm32l4x5_gpio_write(void *opaque, hwaddr addr,
+                                 uint64_t val64, unsigned int size)
+{
+    Stm32l4x5GpioState *s = opaque;
+
+    uint32_t value = val64;
+    trace_stm32l4x5_gpio_write(addr, val64);
+
+    switch (addr) {
+    case GPIO_MODER:
+        s->moder = value;
+        disconnect_gpio_pins(s, get_gpio_pins_to_disconnect(s));
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Analog and AF modes aren't supported\n\
+                       Analog and AF mode behave like input mode\n",
+                      __func__);
+        return;
+    case GPIO_OTYPER:
+        s->otyper = value & ~RESERVED_BITS_MASK;
+        disconnect_gpio_pins(s, get_gpio_pins_to_disconnect(s));
+        return;
+    case GPIO_OSPEEDR:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Changing I/O output speed isn't supported\n\
+                       I/O speed is already maximal\n",
+                      __func__);
+        s->ospeedr = value;
+        return;
+    case GPIO_PUPDR:
+        s->pupdr = value;
+        update_gpio_idr(s);
+        return;
+    case GPIO_IDR:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: GPIO->IDR is read-only\n",
+                      __func__);
+        return;
+    case GPIO_ODR:
+        s->odr = value & ~RESERVED_BITS_MASK;
+        update_gpio_idr(s);
+        return;
+    case GPIO_BSRR: {
+        uint32_t bits_to_reset = (value & RESERVED_BITS_MASK) >> 16;
+        uint32_t bits_to_set = value & ~RESERVED_BITS_MASK;
+        /* If both BSx and BRx are set, BSx has priority.*/
+        s->odr &= ~bits_to_reset;
+        s->odr |= bits_to_set;
+        update_gpio_idr(s);
+        return;
+    }
+    case GPIO_LCKR:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Locking port bits configuration isn't supported\n",
+                      __func__);
+        s->lckr = value & ~RESERVED_BITS_MASK;
+        return;
+    case GPIO_AFRL:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Alternate functions aren't supported\n",
+                      __func__);
+        s->afrl = value;
+        return;
+    case GPIO_AFRH:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Alternate functions aren't supported\n",
+                      __func__);
+        s->afrh = value;
+        return;
+    case GPIO_BRR: {
+        uint32_t bits_to_reset = value & ~RESERVED_BITS_MASK;
+        s->odr &= ~bits_to_reset;
+        update_gpio_idr(s);
+        return;
+    }
+    case GPIO_ASCR:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: ADC function isn't supported\n",
+                      __func__);
+        s->ascr = value & ~RESERVED_BITS_MASK;
+        return;
+    /* a tweak to enable the qtest checking disconnected pins */
+    case GPIO_DISCONNECTED_PINS:
+        disconnect_gpio_pins(s, value);
+        return;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
+    }
+}
+
+static uint64_t stm32l4x5_gpio_read(void *opaque, hwaddr addr,
+                                    unsigned int size)
+{
+    Stm32l4x5GpioState *s = opaque;
+
+    trace_stm32l4x5_gpio_read(addr);
+
+    switch (addr) {
+    case GPIO_MODER:
+        return s->moder;
+    case GPIO_OTYPER:
+        return s->otyper;
+    case GPIO_OSPEEDR:
+        return s->ospeedr;
+    case GPIO_PUPDR:
+        return s->pupdr;
+    case GPIO_IDR:
+        return s->idr;
+    case GPIO_ODR:
+        return s->odr;
+    case GPIO_BSRR:
+        return 0;
+    case GPIO_LCKR:
+        return s->lckr;
+    case GPIO_AFRL:
+        return s->afrl;
+    case GPIO_AFRH:
+        return s->afrh;
+    case GPIO_BRR:
+        return 0;
+    case GPIO_ASCR:
+        return s->ascr;
+    /* a tweak to enable the qtest checking disconnected pins */
+    case GPIO_DISCONNECTED_PINS:
+        return s->disconnected_pins;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
+        return 0;
+    }
+}
+
+static const MemoryRegionOps stm32l4x5_gpio_ops = {
+    .read = stm32l4x5_gpio_read,
+    .write = stm32l4x5_gpio_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void stm32l4x5_gpio_init(Object *obj)
+{
+    Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
+
+    memory_region_init_io(&s->mmio, obj, &stm32l4x5_gpio_ops, s,
+                          TYPE_STM32L4X5_GPIO, 0x400);
+
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+    qdev_init_gpio_out(DEVICE(obj), s->pin, GPIO_NUM_PINS);
+    qdev_init_gpio_in(DEVICE(obj), stm32l4x5_gpio_set, GPIO_NUM_PINS);
+
+    s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+}
+
+static void stm32l4x5_gpio_realize(DeviceState *dev, Error **errp)
+{
+    Stm32l4x5GpioState *s = STM32L4X5_GPIO(dev);
+    if (!clock_has_source(s->clk)) {
+        error_setg(errp, "GPIO: clk input must be connected");
+        return;
+    }
+}
+
+static const VMStateDescription vmstate_stm32l4x5_gpio = {
+    .name = TYPE_STM32L4X5_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]){
+        VMSTATE_UINT32(moder, Stm32l4x5GpioState),
+        VMSTATE_UINT32(otyper, Stm32l4x5GpioState),
+        VMSTATE_UINT32(ospeedr, Stm32l4x5GpioState),
+        VMSTATE_UINT32(pupdr, Stm32l4x5GpioState),
+        VMSTATE_UINT32(idr, Stm32l4x5GpioState),
+        VMSTATE_UINT32(odr, Stm32l4x5GpioState),
+        VMSTATE_UINT32(lckr, Stm32l4x5GpioState),
+        VMSTATE_UINT32(afrl, Stm32l4x5GpioState),
+        VMSTATE_UINT32(afrh, Stm32l4x5GpioState),
+        VMSTATE_UINT32(ascr, Stm32l4x5GpioState),
+        VMSTATE_UINT16(disconnected_pins, Stm32l4x5GpioState),
+        VMSTATE_UINT16(pins_connected_high, Stm32l4x5GpioState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void stm32l4x5_gpio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_stm32l4x5_gpio;
+    dc->realize = stm32l4x5_gpio_realize;
+    rc->phases.hold = stm32l4x5_gpio_reset_hold;
+}
+
+static void stm32l4x5_gpio_a_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xABFFFFFF;
+    sc->ospeedr_reset = 0x0C000000;
+    sc->pupdr_reset = 0x64000000;
+}
+
+static void stm32l4x5_gpio_b_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFEBF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000100;
+}
+
+static void stm32l4x5_gpio_c_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFFFF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static void stm32l4x5_gpio_d_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFFFF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static void stm32l4x5_gpio_e_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFFFF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static void stm32l4x5_gpio_f_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFFFF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static void stm32l4x5_gpio_g_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0xFFFFFFFF;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static void stm32l4x5_gpio_h_class_init(ObjectClass *klass, void *data)
+{
+    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
+
+    sc->moder_reset = 0x0000000F;
+    sc->ospeedr_reset = 0x00000000;
+    sc->pupdr_reset = 0x00000000;
+}
+
+static const TypeInfo stm32l4x5_gpio_types[] = {
+    {
+        .name = TYPE_STM32L4X5_GPIO,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(Stm32l4x5GpioState),
+        .instance_init = stm32l4x5_gpio_init,
+        .class_size     = sizeof(Stm32l4x5GpioClass),
+        .class_init = stm32l4x5_gpio_class_init,
+        .abstract = true,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_A,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_a_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_B,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_b_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_C,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_c_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_D,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_d_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_E,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_e_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_F,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_f_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_G,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_g_class_init,
+    }, {
+        .name = TYPE_STM32L4X5_GPIO_H,
+        .parent = TYPE_STM32L4X5_GPIO,
+        .class_init = stm32l4x5_gpio_h_class_init,
+    },
+};
+
+DEFINE_TYPES(stm32l4x5_gpio_types)
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index 9736b362ac..8cbf75a897 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -31,3 +31,9 @@ sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " val
 # aspeed_gpio.c
 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
+
+# stm32l4x5_gpio.c
+stm32l4x5_gpio_read(uint64_t addr) "reg read: addr: 0x%" PRIx64 " "
+stm32l4x5_gpio_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
+stm32l4x5_gpio_update_idr(uint32_t old_idr, uint32_t new_idr) "previous idr: 0x%x new idr: 0x%x"
+stm32l4x5_gpio_pins(uint16_t disconnected, uint16_t high) "disconnected pins: 0x%x levels: 0x%x"
diff --git a/include/hw/gpio/stm32l4x5_gpio.h b/include/hw/gpio/stm32l4x5_gpio.h
new file mode 100644
index 0000000000..40aae52ffb
--- /dev/null
+++ b/include/hw/gpio/stm32l4x5_gpio.h
@@ -0,0 +1,80 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ * https://www.st.com/en/microcontrollers-microprocessors/stm32l4x5/documentation.html
+ */
+
+#ifndef HW_STM32L4X5_GPIO_H
+#define HW_STM32L4X5_GPIO_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_STM32L4X5_GPIO "stm32l4x5-gpio"
+#define TYPE_STM32L4X5_GPIO_A "stm32l4x5-gpio-a"
+#define TYPE_STM32L4X5_GPIO_B "stm32l4x5-gpio-b"
+#define TYPE_STM32L4X5_GPIO_C "stm32l4x5-gpio-c"
+#define TYPE_STM32L4X5_GPIO_D "stm32l4x5-gpio-d"
+#define TYPE_STM32L4X5_GPIO_E "stm32l4x5-gpio-e"
+#define TYPE_STM32L4X5_GPIO_F "stm32l4x5-gpio-f"
+#define TYPE_STM32L4X5_GPIO_G "stm32l4x5-gpio-g"
+#define TYPE_STM32L4X5_GPIO_H "stm32l4x5-gpio-h"
+OBJECT_DECLARE_TYPE(Stm32l4x5GpioState, Stm32l4x5GpioClass, STM32L4X5_GPIO)
+
+#define GPIO_NUM_PINS 16
+
+struct Stm32l4x5GpioState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+
+    /* GPIO registers */
+    uint32_t moder;
+    uint32_t otyper;
+    uint32_t ospeedr;
+    uint32_t pupdr;
+    uint32_t idr;
+    uint32_t odr;
+    uint32_t lckr;
+    uint32_t afrl;
+    uint32_t afrh;
+    uint32_t ascr;
+
+    /*
+     * External driving of pins.
+     * The pins can be set externally through the device
+     * anonymous input GPIOs lines under certain conditions.
+     * The pin must not be in push-pull output mode,
+     * and can't be set high in open-drain mode.
+     * Pins driven externally and configured to
+     * output mode will in general be "disconnected"
+     * (see `get_gpio_pins_to_disconnect()`)
+     */
+    uint16_t disconnected_pins;
+    uint16_t pins_connected_high;
+
+    Clock *clk;
+    qemu_irq pin[GPIO_NUM_PINS];
+};
+
+struct Stm32l4x5GpioClass {
+    SysBusDeviceClass parent_class;
+
+    uint32_t moder_reset;
+    uint32_t ospeedr_reset;
+    uint32_t pupdr_reset;
+};
+
+#endif
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC
  2024-01-22  9:18 [PATCH 0/3] Add device STM32L4x5 GPIO Inès Varhol
  2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
@ 2024-01-22  9:18 ` Inès Varhol
  2024-01-22  9:18 ` [PATCH 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol
  2 siblings, 0 replies; 6+ messages in thread
From: Inès Varhol @ 2024-01-22  9:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Arnaud Minier, Philippe Mathieu-Daudé,
	Inès Varhol, Laurent Vivier, Thomas Huth, Samuel Tardieu,
	Paolo Bonzini, Alistair Francis, Peter Maydell

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 hw/arm/Kconfig                 |  3 +-
 hw/arm/stm32l4x5_soc.c         | 67 +++++++++++++++++++++++++++-------
 include/hw/arm/stm32l4x5_soc.h |  2 +
 3 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 6bd7ba424f..3e49b913f8 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -459,9 +459,10 @@ config STM32L4X5_SOC
     bool
     select ARM_V7M
     select OR_IRQ
-    select STM32L4X5_SYSCFG
     select STM32L4X5_EXTI
+    select STM32L4X5_SYSCFG
     select STM32L4X5_RCC
+    select STM32L4X5_GPIO
 
 config XLNX_ZYNQMP_ARM
     bool
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index bcdad69e92..7857313f84 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -78,6 +78,28 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+static const uint32_t gpio_addr[] = {
+    0x48000000,
+    0x48000400,
+    0x48000800,
+    0x48000C00,
+    0x48001000,
+    0x48001400,
+    0x48001800,
+    0x48001C00,
+};
+
+static const char *gpio_name[] = {
+    "gpioa",
+    "gpiob",
+    "gpioc",
+    "gpiod",
+    "gpioe",
+    "gpiof",
+    "gpiog",
+    "gpioh",
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
     Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -85,6 +107,15 @@ static void stm32l4x5_soc_initfn(Object *obj)
     object_initialize_child(obj, "exti", &s->exti, TYPE_STM32L4X5_EXTI);
     object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32L4X5_SYSCFG);
     object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32L4X5_RCC);
+
+    object_initialize_child(obj, "gpioa", &s->gpio[0], TYPE_STM32L4X5_GPIO_A);
+    object_initialize_child(obj, "gpiob", &s->gpio[1], TYPE_STM32L4X5_GPIO_B);
+    object_initialize_child(obj, "gpioc", &s->gpio[2], TYPE_STM32L4X5_GPIO_C);
+    object_initialize_child(obj, "gpiod", &s->gpio[3], TYPE_STM32L4X5_GPIO_D);
+    object_initialize_child(obj, "gpioe", &s->gpio[4], TYPE_STM32L4X5_GPIO_E);
+    object_initialize_child(obj, "gpiof", &s->gpio[5], TYPE_STM32L4X5_GPIO_F);
+    object_initialize_child(obj, "gpiog", &s->gpio[6], TYPE_STM32L4X5_GPIO_G);
+    object_initialize_child(obj, "gpioh", &s->gpio[7], TYPE_STM32L4X5_GPIO_H);
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -95,6 +126,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
     MemoryRegion *system_memory = get_system_memory();
     DeviceState *armv7m;
     SysBusDevice *busdev;
+    uint32_t pin_index;
+    char clk_name[10];
 
     if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
                                 sc->flash_size, errp)) {
@@ -134,17 +167,33 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
         return;
     }
 
+    /* GPIOs */
+    for (unsigned i = 0; i < NUM_GPIOS; i++) {
+        busdev = SYS_BUS_DEVICE(&s->gpio[i]);
+        sprintf(clk_name, "%s-out", gpio_name[i]);
+        qdev_connect_clock_in(DEVICE(&s->gpio[i]), "clk",
+            qdev_get_clock_out(DEVICE(&(s->rcc)), clk_name));
+        if (!sysbus_realize(busdev, errp)) {
+            return;
+        }
+        sysbus_mmio_map(busdev, 0, gpio_addr[i]);
+    }
+
     /* System configuration controller */
     busdev = SYS_BUS_DEVICE(&s->syscfg);
     if (!sysbus_realize(busdev, errp)) {
         return;
     }
     sysbus_mmio_map(busdev, 0, SYSCFG_ADDR);
-    /*
-     * TODO: when the GPIO device is implemented, connect it
-     * to SYCFG using `qdev_connect_gpio_out`, NUM_GPIOS and
-     * GPIO_NUM_PINS.
-     */
+
+    for (unsigned i = 0; i < NUM_GPIOS; i++) {
+        for (unsigned j = 0; j < GPIO_NUM_PINS; j++) {
+            pin_index = GPIO_NUM_PINS * i + j;
+            qdev_connect_gpio_out(DEVICE(&s->gpio[i]), j,
+                                  qdev_get_gpio_in(DEVICE(&s->syscfg),
+                                  pin_index));
+        }
+    }
 
     /* EXTI device */
     busdev = SYS_BUS_DEVICE(&s->exti);
@@ -241,14 +290,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
     /* RESERVED:    0x40024400, 0x7FDBC00 */
 
     /* AHB2 BUS */
-    create_unimplemented_device("GPIOA",     0x48000000, 0x400);
-    create_unimplemented_device("GPIOB",     0x48000400, 0x400);
-    create_unimplemented_device("GPIOC",     0x48000800, 0x400);
-    create_unimplemented_device("GPIOD",     0x48000C00, 0x400);
-    create_unimplemented_device("GPIOE",     0x48001000, 0x400);
-    create_unimplemented_device("GPIOF",     0x48001400, 0x400);
-    create_unimplemented_device("GPIOG",     0x48001800, 0x400);
-    create_unimplemented_device("GPIOH",     0x48001C00, 0x400);
     /* RESERVED:    0x48002000, 0x7FDBC00 */
     create_unimplemented_device("OTG_FS",    0x50000000, 0x40000);
     create_unimplemented_device("ADC",       0x50040000, 0x400);
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index 1f71298b45..cb4da08629 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -29,6 +29,7 @@
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/misc/stm32l4x5_exti.h"
 #include "hw/misc/stm32l4x5_rcc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
 #include "qom/object.h"
 
 #define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
@@ -45,6 +46,7 @@ struct Stm32l4x5SocState {
     Stm32l4x5ExtiState exti;
     Stm32l4x5SyscfgState syscfg;
     Stm32l4x5RccState rcc;
+    Stm32l4x5GpioState gpio[NUM_GPIOS];
 
     MemoryRegion sram1;
     MemoryRegion sram2;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase
  2024-01-22  9:18 [PATCH 0/3] Add device STM32L4x5 GPIO Inès Varhol
  2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
  2024-01-22  9:18 ` [PATCH 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
@ 2024-01-22  9:18 ` Inès Varhol
  2 siblings, 0 replies; 6+ messages in thread
From: Inès Varhol @ 2024-01-22  9:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Arnaud Minier, Philippe Mathieu-Daudé,
	Inès Varhol, Laurent Vivier, Thomas Huth, Samuel Tardieu,
	Paolo Bonzini, Alistair Francis, Peter Maydell

The testcase contains :
- `test_idr_reset_value()` :
Checks the reset values of MODER, OTYPER, PUPDR, ODR and IDR.
- `test_gpio_output_mode()` :
Checks that writing a bit in register ODR results in the corresponding
pin rising or lowering, if this pin is configured in output mode.
- `test_gpio_input_mode()` :
Checks that a input pin set high or low externally results
in the pin rising and lowering.
- `test_pull_up_pull_down()` :
Checks that a floating pin in pull-up/down mode is actually high/down.
- `test_push_pull()` :
Checks that a pin set externally is disconnected when configured in
push-pull output mode, and can't be set externally while in this mode.
- `test_open_drain()` :
Checks that a pin set externally high is disconnected when configured
in open-drain output mode, and can't be set high while in this mode.
- `test_bsrr_brr()` :
Checks that writing to BSRR and BRR has the desired result in ODR.

Acked-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
---
 tests/qtest/meson.build           |   3 +-
 tests/qtest/stm32l4x5_gpio-test.c | 526 ++++++++++++++++++++++++++++++
 2 files changed, 528 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/stm32l4x5_gpio-test.c

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index b0d9a8c2de..5692da4fc1 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -198,7 +198,8 @@ qtests_aspeed = \
 qtests_stm32l4x5 = \
   ['stm32l4x5_exti-test',
    'stm32l4x5_syscfg-test',
-   'stm32l4x5_rcc-test']
+   'stm32l4x5_rcc-test',
+   'stm32l4x5_gpio-test']
 
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
diff --git a/tests/qtest/stm32l4x5_gpio-test.c b/tests/qtest/stm32l4x5_gpio-test.c
new file mode 100644
index 0000000000..9b234c68ec
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,526 @@
+/*
+ * QTest testcase for STM32L4x5_EXTI
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.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 "libqtest-single.h"
+
+#define GPIO_BASE_ADDR 0x48000000
+#define GPIO_SIZE      0x400
+#define NUM_GPIOS      8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x48000000
+#define GPIO_B 0x48000400
+#define GPIO_C 0x48000800
+#define GPIO_D 0x48000C00
+#define GPIO_E 0x48001000
+#define GPIO_F 0x48001400
+#define GPIO_G 0x48001800
+#define GPIO_H 0x48001C00
+
+#define MODER 0x00
+#define OTYPER 0x04
+#define PUPDR 0x0C
+#define IDR 0x10
+#define ODR 0x14
+#define BSRR 0x18
+#define BRR 0x28
+#define DISCONNECTED_PINS 0x30
+
+#define MODER_INPUT 0
+#define MODER_OUTPUT 1
+
+#define PUPDR_NONE 0
+#define PUPDR_PULLUP 1
+#define PUPDR_PULLDOWN 2
+
+#define OTYPER_PUSH_PULL 0
+#define OTYPER_OPEN_DRAIN 1
+
+const uint32_t moder_reset[NUM_GPIOS] = {
+    0xABFFFFFF,
+    0xFFFFFEBF,
+    0xFFFFFFFF,
+    0xFFFFFFFF,
+    0xFFFFFFFF,
+    0xFFFFFFFF,
+    0xFFFFFFFF,
+    0x0000000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+    0x64000000,
+    0x00000100,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+    0x0000A000,
+    0x00000010,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+    0x00000000
+};
+
+static uint32_t gpio_readl(unsigned int gpio, unsigned int offset)
+{
+    return readl(gpio + offset);
+}
+
+static void gpio_writel(unsigned int gpio, unsigned int offset, uint32_t value)
+{
+    writel(gpio + offset, value);
+}
+
+static void gpio_set_bit(unsigned int gpio, unsigned int reg,
+                         unsigned int pin, uint32_t value)
+{
+    uint32_t mask = 0xFFFFFFFF & ~(0x1 << pin);
+    gpio_writel(gpio, reg, (gpio_readl(gpio, reg) & mask) | value << pin);
+}
+
+static void gpio_set_2bits(unsigned int gpio, unsigned int reg,
+                           unsigned int pin, uint32_t value)
+{
+    uint32_t offset = 2 * pin;
+    uint32_t mask = 0xFFFFFFFF & ~(0x3 << offset);
+    gpio_writel(gpio, reg, (gpio_readl(gpio, reg) & mask) | value << offset);
+}
+
+static void gpio_set_irq(unsigned int gpio, int num, int level)
+{
+    switch (gpio) {
+    case GPIO_A:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpioa",
+                            NULL, num, level);
+        break;
+    case GPIO_B:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpiob",
+                            NULL, num, level);
+        break;
+    case GPIO_C:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpioc",
+                            NULL, num, level);
+        break;
+    case GPIO_D:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpiod",
+                            NULL, num, level);
+        break;
+    case GPIO_E:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpioe",
+                            NULL, num, level);
+        break;
+    case GPIO_F:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpiof",
+                            NULL, num, level);
+        break;
+    case GPIO_G:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpiog",
+                            NULL, num, level);
+        break;
+    case GPIO_H:
+        qtest_set_irq_in(global_qtest, "/machine/soc/gpioh",
+                            NULL, num, level);
+        break;
+    }
+}
+
+static unsigned int get_gpio_id(uint32_t gpio_addr)
+{
+    return (gpio_addr - GPIO_BASE_ADDR) / GPIO_SIZE;
+}
+
+static uint32_t reset(uint32_t gpio, unsigned int offset)
+{
+    switch (offset) {
+    case MODER:
+        return moder_reset[get_gpio_id(gpio)];
+    case PUPDR:
+        return pupdr_reset[get_gpio_id(gpio)];
+    case IDR:
+        return idr_reset[get_gpio_id(gpio)];
+    }
+    return 0x0;
+}
+
+static void test_idr_reset_value(void)
+{
+    /*
+     * Checks that the values in MODER, OTYPER, PUPDR and ODR
+     * at reset are correct, and that the value in IDR is
+     * coherent.
+     * Since AF and analog modes aren't implemented, IDR reset
+     * values aren't the same as with a real board.
+     *
+     * Register IDR contains the actual values of the 16 GPIO pins.
+     * Its value depends on the pins' configuration
+     * (intput/output/analog : register MODER, push-pull/open-drain :
+     * register OTYPER, pull-up/pull-down/none : register PUPDR)
+     * and on the values stored in register ODR
+     * (in case the pin is in output mode).
+     */
+    uint32_t moder = gpio_readl(GPIO_A, MODER);
+    uint32_t odr = gpio_readl(GPIO_A, ODR);
+    uint32_t otyper = gpio_readl(GPIO_A, OTYPER);
+    uint32_t pupdr = gpio_readl(GPIO_A, PUPDR);
+    uint32_t idr = gpio_readl(GPIO_A, IDR);
+    /* 15: AF, 14: AF, 13: AF, 12: Analog ... */
+    /* here AF is the same as Analog and Input mode */
+    g_assert_cmphex(moder, ==, reset(GPIO_A, MODER));
+    g_assert_cmphex(odr, ==, reset(GPIO_A, ODR));
+    g_assert_cmphex(otyper, ==, reset(GPIO_A, OTYPER));
+    /* 15: pull-up, 14: pull-down, 13: pull-up, 12: neither ... */
+    g_assert_cmphex(pupdr, ==, reset(GPIO_A, PUPDR));
+    /* 15 : 1, 14: 0, 13: 1, 12 : reset value ... */
+    g_assert_cmphex(idr, ==, reset(GPIO_A, IDR));
+
+    moder = gpio_readl(GPIO_B, MODER);
+    odr = gpio_readl(GPIO_B, ODR);
+    otyper = gpio_readl(GPIO_B, OTYPER);
+    pupdr = gpio_readl(GPIO_B, PUPDR);
+    idr = gpio_readl(GPIO_B, IDR);
+    /* ... 5: Analog, 4: AF, 3: AF, 2: Analog ... */
+    /* here AF is the same as Analog and Input mode */
+    g_assert_cmphex(moder, ==, reset(GPIO_B, MODER));
+    g_assert_cmphex(odr, ==, reset(GPIO_B, ODR));
+    g_assert_cmphex(otyper, ==, reset(GPIO_B, OTYPER));
+    /* ... 5: neither, 4: pull-up, 3: neither ... */
+    g_assert_cmphex(pupdr, ==, reset(GPIO_B, PUPDR));
+    /* ... 5 : reset value, 4 : 1, 3 : reset value ... */
+    g_assert_cmphex(idr, ==, reset(GPIO_B, IDR));
+
+    moder = gpio_readl(GPIO_C, MODER);
+    odr = gpio_readl(GPIO_C, ODR);
+    otyper = gpio_readl(GPIO_C, OTYPER);
+    pupdr = gpio_readl(GPIO_C, PUPDR);
+    idr = gpio_readl(GPIO_C, IDR);
+    /* Analog, same as Input mode*/
+    g_assert_cmphex(moder, ==, reset(GPIO_C, MODER));
+    g_assert_cmphex(odr, ==, reset(GPIO_C, ODR));
+    g_assert_cmphex(otyper, ==, reset(GPIO_C, OTYPER));
+    /* no pull-up or pull-down */
+    g_assert_cmphex(pupdr, ==, reset(GPIO_C, PUPDR));
+    /* reset value */
+    g_assert_cmphex(idr, ==, reset(GPIO_C, IDR));
+
+    moder = gpio_readl(GPIO_H, MODER);
+    odr = gpio_readl(GPIO_H, ODR);
+    otyper = gpio_readl(GPIO_H, OTYPER);
+    pupdr = gpio_readl(GPIO_H, PUPDR);
+    idr = gpio_readl(GPIO_H, IDR);
+    /* Analog, same as Input mode */
+    g_assert_cmphex(moder, ==, reset(GPIO_H, MODER));
+    g_assert_cmphex(odr, ==, reset(GPIO_H, ODR));
+    g_assert_cmphex(otyper, ==, reset(GPIO_H, OTYPER));
+    /* no pull-up or pull-down */
+    g_assert_cmphex(pupdr, ==, reset(GPIO_H, PUPDR));
+    /* reset value */
+    g_assert_cmphex(idr, ==, reset(GPIO_H, IDR));
+}
+
+static void test_gpio_output_mode(const void *data)
+{
+    /*
+     * Checks that setting a bit in ODR sets the corresponding
+     * GPIO line high : it should set the right bit in IDR
+     * and send an irq to syscfg.
+     * Additionally, it checks that values written to ODR
+     * when not in output mode are stored and not discarded.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+    unsigned int gpio_id = get_gpio_id(gpio);
+
+    qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+
+    /* Set a bit in ODR and check nothing happens */
+    gpio_set_bit(gpio, ODR, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR));
+    g_assert_false(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Configure the relevant line as output and check the pin is high */
+    gpio_set_2bits(gpio, MODER, pin, MODER_OUTPUT);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) | (1 << pin));
+    g_assert_true(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Reset the bit in ODR and check the pin is low */
+    gpio_set_bit(gpio, ODR, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+    g_assert_false(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Clean the test */
+    gpio_writel(gpio, ODR, reset(gpio, ODR));
+    gpio_writel(gpio, MODER, reset(gpio, MODER));
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR));
+    g_assert_false(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+}
+
+static void test_gpio_input_mode(const void *data)
+{
+    /*
+     * Test that setting a line high/low externally sets the
+     * corresponding GPIO line high/low : it should set the
+     * right bit in IDR and send an irq to syscfg.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+    unsigned int gpio_id = get_gpio_id(gpio);
+
+    qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+
+    /* Configure a line as input, raise it, and check that the pin is high */
+    gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) | (1 << pin));
+    g_assert_true(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Lower the line and check that the pin is low */
+    gpio_set_irq(gpio, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+    g_assert_false(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Clean the test */
+    gpio_writel(gpio, MODER, reset(gpio, MODER));
+    gpio_writel(gpio, DISCONNECTED_PINS, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR));
+}
+
+static void test_pull_up_pull_down(const void *data)
+{
+    /*
+     * Test that a floating pin with pull-up sets the pin
+     * high and vice-versa.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+    unsigned int gpio_id = get_gpio_id(gpio);
+
+    qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+
+    /* Configure a line as input with pull-up, check the line is set high */
+    gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
+    gpio_set_2bits(gpio, PUPDR, pin, PUPDR_PULLUP);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) | (1 << pin));
+    g_assert_true(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Configure the line with pull-down, check the line is low */
+    gpio_set_2bits(gpio, PUPDR, pin, PUPDR_PULLDOWN);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+    g_assert_false(get_irq(gpio_id * NUM_GPIO_PINS + pin));
+
+    /* Clean the test */
+    gpio_writel(gpio, MODER, reset(gpio, MODER));
+    gpio_writel(gpio, PUPDR, reset(gpio, PUPDR));
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR));
+}
+
+static void test_push_pull(const void *data)
+{
+    /*
+     * Test that configuring a line in push-pull output mode
+     * disconnects the pin, that the pin can't be set or reset
+     * externally afterwards.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+    uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
+
+    qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+
+    /* Setting a line high externally, configuring it in push-pull output */
+    /* And checking the pin was disconnected */
+    gpio_set_irq(gpio, pin, 1);
+    gpio_set_2bits(gpio, MODER, pin, MODER_OUTPUT);
+    g_assert_cmphex(gpio_readl(gpio, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+
+    /* Setting a line low externally, configuring it in push-pull output */
+    /* And checking the pin was disconnected */
+    gpio_set_irq(gpio2, pin, 0);
+    gpio_set_bit(gpio2, ODR, pin, 1);
+    gpio_set_2bits(gpio2, MODER, pin, MODER_OUTPUT);
+    g_assert_cmphex(gpio_readl(gpio2, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio2, IDR), ==, reset(gpio2, IDR) | (1 << pin));
+
+    /* Trying to set a push-pull output pin, checking it doesn't work */
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+
+    /* Trying to reset a push-pull output pin, checking it doesn't work */
+    gpio_set_irq(gpio2, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio2, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio2, IDR), ==, reset(gpio2, IDR) | (1 << pin));
+
+    /* Clean the test */
+    gpio_writel(gpio, MODER, reset(gpio, MODER));
+    gpio_writel(gpio2, ODR, reset(gpio2, ODR));
+    gpio_writel(gpio2, MODER, reset(gpio2, MODER));
+}
+
+static void test_open_drain(const void *data)
+{
+    /*
+     * Test that configuring a line in open-drain output mode
+     * disconnects a pin set high externally and that the pin
+     * can't be set high externally while configured in open-drain.
+     *
+     * However a pin set low externally shouldn't be disconnected,
+     * and it can be set low externally when in open-drain mode.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+    uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
+
+    qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+
+    /* Setting a line high externally, configuring it in open-drain output */
+    /* And checking the pin was disconnected */
+    gpio_set_irq(gpio, pin, 1);
+    gpio_set_bit(gpio, OTYPER, pin, OTYPER_OPEN_DRAIN);
+    gpio_set_2bits(gpio, MODER, pin, MODER_OUTPUT);
+    g_assert_cmphex(gpio_readl(gpio, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+
+    /* Setting a line low externally, configuring it in open-drain output */
+    /* And checking the pin wasn't disconnected */
+    gpio_set_irq(gpio2, pin, 0);
+    gpio_set_bit(gpio2, ODR, pin, 1);
+    gpio_set_bit(gpio2, OTYPER, pin, OTYPER_OPEN_DRAIN);
+    gpio_set_2bits(gpio2, MODER, pin, MODER_OUTPUT);
+    g_assert_cmphex(gpio_readl(gpio2, DISCONNECTED_PINS), ==,
+                               0xFFFF & ~(1 << pin));
+    g_assert_cmphex(gpio_readl(gpio2, IDR), ==,
+                               reset(gpio2, IDR) & ~(1 << pin));
+
+    /* Trying to set a open-drain output pin, checking it doesn't work */
+    gpio_set_irq(gpio, pin, 1);
+    g_assert_cmphex(gpio_readl(gpio, DISCONNECTED_PINS), ==, 0xFFFF);
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR) & ~(1 << pin));
+
+    /* Trying to reset a open-drain output pin, checking it works */
+    gpio_set_bit(gpio, ODR, pin, 1);
+    gpio_set_irq(gpio, pin, 0);
+    g_assert_cmphex(gpio_readl(gpio2, DISCONNECTED_PINS), ==,
+                               0xFFFF & ~(1 << pin));
+    g_assert_cmphex(gpio_readl(gpio2, IDR), ==,
+                               reset(gpio2, IDR) & ~(1 << pin));
+
+    /* Clean the test */
+    gpio_writel(gpio2, DISCONNECTED_PINS, 0xFFFF);
+    gpio_writel(gpio2, OTYPER, reset(gpio2, OTYPER));
+    gpio_writel(gpio2, ODR, reset(gpio2, ODR));
+    gpio_writel(gpio2, MODER, reset(gpio2, MODER));
+    g_assert_cmphex(gpio_readl(gpio2, IDR), ==, reset(gpio2, IDR));
+    gpio_writel(gpio, DISCONNECTED_PINS, 0xFFFF);
+    gpio_writel(gpio, OTYPER, reset(gpio, OTYPER));
+    gpio_writel(gpio, ODR, reset(gpio, ODR));
+    gpio_writel(gpio, MODER, reset(gpio, MODER));
+    g_assert_cmphex(gpio_readl(gpio, IDR), ==, reset(gpio, IDR));
+}
+
+static void test_bsrr_brr(const void *data)
+{
+    /*
+     * Test that writing a '1' in BSS and BSRR
+     * has the desired effect on ODR.
+     * In BSRR, BSx has priority over BRx.
+     */
+    unsigned int pin = ((uint64_t)data) & 0xF;
+    uint32_t gpio = ((uint64_t)data) >> 32;
+
+    gpio_writel(gpio, BSRR, (1 << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR) | (1 << pin));
+
+    gpio_writel(gpio, BSRR, (1 << (pin + 16)));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR));
+
+    gpio_writel(gpio, BSRR, (1 << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR) | (1 << pin));
+
+    gpio_writel(gpio, BRR, (1 << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR));
+
+    /* BSx should have priority over BRx */
+    gpio_writel(gpio, BSRR, (1 << pin) | (1 << (pin + 16)));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR) | (1 << pin));
+
+    gpio_writel(gpio, BRR, (1 << pin));
+    g_assert_cmphex(gpio_readl(gpio, ODR), ==, reset(gpio, ODR));
+
+    gpio_writel(gpio, ODR, reset(gpio, ODR));
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_set_nonfatal_assertions();
+    qtest_add_func("stm32l4x5/gpio/test_idr_reset_value",
+                   test_idr_reset_value);
+    /*
+     * The inputs for the tests (gpio and pin) can be changed,
+     * but the tests don't work for pins that are high at reset
+     * (GPIOA15, GPIO13 and GPIOB5).
+     * Specifically, rising the pin then checking `get_irq()`
+     * is problematic since the pin was already high.
+     */
+    qtest_add_data_func("stm32l4x5/gpio/test_gpioc5_output_mode",
+                        (void *)((uint64_t)GPIO_C << 32 | 5),
+                        test_gpio_output_mode);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpioh3_output_mode",
+                        (void *)((uint64_t)GPIO_H << 32 | 3),
+                        test_gpio_output_mode);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_input_mode1",
+                        (void *)((uint64_t)GPIO_D << 32 | 6),
+                        test_gpio_input_mode);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_input_mode2",
+                        (void *)((uint64_t)GPIO_C << 32 | 10),
+                        test_gpio_input_mode);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_pull_up_pull_down1",
+                        (void *)((uint64_t)GPIO_B << 32 | 5),
+                        test_pull_up_pull_down);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_pull_up_pull_down2",
+                        (void *)((uint64_t)GPIO_F << 32 | 1),
+                        test_pull_up_pull_down);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_push_pull1",
+                        (void *)((uint64_t)GPIO_G << 32 | 6),
+                        test_push_pull);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_push_pull2",
+                        (void *)((uint64_t)GPIO_H << 32 | 3),
+                        test_push_pull);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_open_drain1",
+                        (void *)((uint64_t)GPIO_C << 32 | 4),
+                        test_open_drain);
+    qtest_add_data_func("stm32l4x5/gpio/test_gpio_open_drain2",
+                        (void *)((uint64_t)GPIO_E << 32 | 11),
+                        test_open_drain);
+    qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr1",
+                        (void *)((uint64_t)GPIO_A << 32 | 12),
+                        test_bsrr_brr);
+    qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr2",
+                        (void *)((uint64_t)GPIO_D << 32 | 0),
+                        test_bsrr_brr);
+
+    qtest_start("-machine b-l475e-iot01a");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/3] hw/gpio: Implement STM32L4x5 GPIO
  2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
@ 2024-01-22 11:32   ` Philippe Mathieu-Daudé
  2024-01-22 11:37   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 6+ messages in thread
From: Philippe Mathieu-Daudé @ 2024-01-22 11:32 UTC (permalink / raw)
  To: Inès Varhol, qemu-devel
  Cc: qemu-arm, Arnaud Minier, Laurent Vivier, Thomas Huth,
	Samuel Tardieu, Paolo Bonzini, Alistair Francis, Peter Maydell

Hello Inès,

On 22/1/24 10:18, Inès Varhol wrote:
> Features supported :
> - the 8 STM32L4x5 GPIOs are initialized with their reset values
>      (except IDR, see below)
> - input mode : setting a pin in input mode "externally" (using input
>      irqs) results in an out irq (transmitted to SYSCFG)
> - output mode : setting a bit in ODR sets the corresponding out irq
>      (if this line is configured in output mode)
> - pull-up, pull-down
> - push-pull, open-drain
> 
> Difference with the real GPIOs :
> - Alternate Function and Analog mode aren't implemented :
>      pins in AF/Analog behave like pins in input mode
> - floating pins stay at their last value
> - register IDR reset values differ from the real one :
>      values are coherent with the other registers reset values
>      and the fact that AF/Analog modes aren't implemented
> - setting I/O output speed isn't supported
> - locking port bits isn't supported
> - ADC function isn't supported
> - GPIOH has 16 pins instead of 2 pins
> - writing to registers LCKR, AFRL, AFRH and ASCR is ineffective
> 
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>   MAINTAINERS                        |   1 +
>   docs/system/arm/b-l475e-iot01a.rst |   2 +-
>   hw/gpio/Kconfig                    |   3 +
>   hw/gpio/meson.build                |   1 +
>   hw/gpio/stm32l4x5_gpio.c           | 537 +++++++++++++++++++++++++++++
>   hw/gpio/trace-events               |   6 +
>   include/hw/gpio/stm32l4x5_gpio.h   |  80 +++++
>   7 files changed, 629 insertions(+), 1 deletion(-)
>   create mode 100644 hw/gpio/stm32l4x5_gpio.c
>   create mode 100644 include/hw/gpio/stm32l4x5_gpio.h


> +static void stm32l4x5_gpio_a_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xABFFFFFF;
> +    sc->ospeedr_reset = 0x0C000000;
> +    sc->pupdr_reset = 0x64000000;
> +}
> +
> +static void stm32l4x5_gpio_b_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFEBF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000100;
> +}
> +
> +static void stm32l4x5_gpio_c_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFFFF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static void stm32l4x5_gpio_d_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFFFF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static void stm32l4x5_gpio_e_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFFFF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static void stm32l4x5_gpio_f_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFFFF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static void stm32l4x5_gpio_g_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0xFFFFFFFF;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static void stm32l4x5_gpio_h_class_init(ObjectClass *klass, void *data)
> +{
> +    Stm32l4x5GpioClass *sc = STM32L4X5_GPIO_CLASS(klass);
> +
> +    sc->moder_reset = 0x0000000F;
> +    sc->ospeedr_reset = 0x00000000;
> +    sc->pupdr_reset = 0x00000000;
> +}
> +
> +static const TypeInfo stm32l4x5_gpio_types[] = {
> +    {
> +        .name = TYPE_STM32L4X5_GPIO,
> +        .parent = TYPE_SYS_BUS_DEVICE,
> +        .instance_size = sizeof(Stm32l4x5GpioState),
> +        .instance_init = stm32l4x5_gpio_init,
> +        .class_size     = sizeof(Stm32l4x5GpioClass),
> +        .class_init = stm32l4x5_gpio_class_init,
> +        .abstract = true,
> +    }, {
> +        .name = TYPE_STM32L4X5_GPIO_A,
> +        .parent = TYPE_STM32L4X5_GPIO,
> +        .class_init = stm32l4x5_gpio_a_class_init,
> +    }, {
> +        .name = TYPE_STM32L4X5_GPIO_B,
> +        .parent = TYPE_STM32L4X5_GPIO,
> +        .class_init = stm32l4x5_gpio_b_class_init,
> +    }, {
> +        .name = TYPE_STM32L4X5_GPIO_C,
> +        .parent = TYPE_STM32L4X5_GPIO,
> +        .class_init = stm32l4x5_gpio_c_class_init,
> +    }, {
> +        .name = TYPE_STM32L4X5_GPIO_D,
> +        .parent = TYPE_STM32L4X5_GPIO,
> +        .class_init = stm32l4x5_gpio_d_class_init,
> +    }, {

My understanding is the same GPIO block is used for the STM32L4*
family, and the reset values are specific to each SoC.

Therefore I'd keep a generic GPIO model in this file, and set the
reset values in stm32l4x5_soc.c, likely stm32l4x5_soc_realize().

Something like:

static const struct {
     uint32_t moder;
     uint32_t ospeedr;
     uint32_t pupdr;
} stm32l4x5_gpio_initval[NUM_GPIOS] = {
     { 0xABFFFFFF, 0x0C000000, 0x64000000 },
     { 0xFFFFFEBF, 0x00000000, 0x00000100 },
     { 0xFFFFFFFF, 0x00000000, 0x00000000 },
     ...
};

   for (unsigned i = 0; i < NUM_GPIOS; i++) {
       DeviceState *dev = DEVICE(&s->gpio[i]);
       g_autofree char *name = g_strdup_printf("%c", 'A' + i);

       qdev_prop_set_string(dev, "name", name);
       qdev_prop_set_uint32(dev, "mode-reset",
                            stm32l4x5_gpio_initval[i].moder);
       qdev_prop_set_uint32(dev, "ospeed-reset",
                            stm32l4x5_gpio_initval[i].ospeedr);
       qdev_prop_set_uint32(dev, "pupd-reset",
                            stm32l4x5_gpio_initval[i].pupdr);
       ...
   }

(Having "mode-reset", "ospeed-reset", "pupd-reset" exposed as
  DEFINE_PROP_UINT32() properties).

Regards,

Phil.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/3] hw/gpio: Implement STM32L4x5 GPIO
  2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
  2024-01-22 11:32   ` Philippe Mathieu-Daudé
@ 2024-01-22 11:37   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 6+ messages in thread
From: Philippe Mathieu-Daudé @ 2024-01-22 11:37 UTC (permalink / raw)
  To: Inès Varhol, qemu-devel, Thomas Huth
  Cc: qemu-arm, Arnaud Minier, Laurent Vivier, Samuel Tardieu,
	Paolo Bonzini, Alistair Francis, Peter Maydell

Hi Inès,

On 22/1/24 10:18, Inès Varhol wrote:
> Features supported :
> - the 8 STM32L4x5 GPIOs are initialized with their reset values
>      (except IDR, see below)
> - input mode : setting a pin in input mode "externally" (using input
>      irqs) results in an out irq (transmitted to SYSCFG)
> - output mode : setting a bit in ODR sets the corresponding out irq
>      (if this line is configured in output mode)
> - pull-up, pull-down
> - push-pull, open-drain
> 
> Difference with the real GPIOs :
> - Alternate Function and Analog mode aren't implemented :
>      pins in AF/Analog behave like pins in input mode
> - floating pins stay at their last value
> - register IDR reset values differ from the real one :
>      values are coherent with the other registers reset values
>      and the fact that AF/Analog modes aren't implemented
> - setting I/O output speed isn't supported
> - locking port bits isn't supported
> - ADC function isn't supported
> - GPIOH has 16 pins instead of 2 pins
> - writing to registers LCKR, AFRL, AFRH and ASCR is ineffective
> 
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> ---
>   MAINTAINERS                        |   1 +
>   docs/system/arm/b-l475e-iot01a.rst |   2 +-
>   hw/gpio/Kconfig                    |   3 +
>   hw/gpio/meson.build                |   1 +
>   hw/gpio/stm32l4x5_gpio.c           | 537 +++++++++++++++++++++++++++++
>   hw/gpio/trace-events               |   6 +
>   include/hw/gpio/stm32l4x5_gpio.h   |  80 +++++
>   7 files changed, 629 insertions(+), 1 deletion(-)
>   create mode 100644 hw/gpio/stm32l4x5_gpio.c
>   create mode 100644 include/hw/gpio/stm32l4x5_gpio.h


> +#define GPIO_MODER 0x00
> +#define GPIO_OTYPER 0x04
> +#define GPIO_OSPEEDR 0x08
> +#define GPIO_PUPDR 0x0C
> +#define GPIO_IDR 0x10
> +#define GPIO_ODR 0x14
> +#define GPIO_BSRR 0x18
> +#define GPIO_LCKR 0x1C
> +#define GPIO_AFRL 0x20
> +#define GPIO_AFRH 0x24
> +#define GPIO_BRR 0x28
> +#define GPIO_ASCR 0x2C
> +/*
> + * DISCONNECTED_PINS isn't actually a GPIO register.
> + * It exists to ensure that :
> + * - push-pull output pins can't be set externally
> + * - open-drain output pins can only be externally set to 0
> + *
> + * This field is accessed for test purposes.
> + */
> +#define GPIO_DISCONNECTED_PINS 0x30

[*]

> +static void stm32l4x5_gpio_write(void *opaque, hwaddr addr,
> +                                 uint64_t val64, unsigned int size)
> +{
> +    Stm32l4x5GpioState *s = opaque;
> +
> +    uint32_t value = val64;
> +    trace_stm32l4x5_gpio_write(addr, val64);
> +
> +    switch (addr) {


> +    /* a tweak to enable the qtest checking disconnected pins */
> +    case GPIO_DISCONNECTED_PINS:
> +        disconnect_gpio_pins(s, value);
> +        return;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
> +    }
> +}
> +
> +static uint64_t stm32l4x5_gpio_read(void *opaque, hwaddr addr,
> +                                    unsigned int size)
> +{
> +    Stm32l4x5GpioState *s = opaque;
> +
> +    trace_stm32l4x5_gpio_read(addr);
> +
> +    switch (addr) {


> +    /* a tweak to enable the qtest checking disconnected pins */
> +    case GPIO_DISCONNECTED_PINS:
> +        return s->disconnected_pins;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
> +        return 0;
> +    }
> +}


> +static void stm32l4x5_gpio_init(Object *obj)
> +{
> +    Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &stm32l4x5_gpio_ops, s,
> +                          TYPE_STM32L4X5_GPIO, 0x400);

This "testing purpose" [*] register is accessible by the guest, so
this isn't a faithful emulation of the device, and could possibly
lead to unexpected guest behavior.
You can store testing fields in the device state, but don't expose
the as register. Expose them as QOM/QDev properties and access them
with the QOM QTest helpers.

Regards,

Phil.

> +    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
> +
> +    qdev_init_gpio_out(DEVICE(obj), s->pin, GPIO_NUM_PINS);
> +    qdev_init_gpio_in(DEVICE(obj), stm32l4x5_gpio_set, GPIO_NUM_PINS);
> +
> +    s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
> +}



^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2024-01-22 11:38 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-22  9:18 [PATCH 0/3] Add device STM32L4x5 GPIO Inès Varhol
2024-01-22  9:18 ` [PATCH 1/3] hw/gpio: Implement " Inès Varhol
2024-01-22 11:32   ` Philippe Mathieu-Daudé
2024-01-22 11:37   ` Philippe Mathieu-Daudé
2024-01-22  9:18 ` [PATCH 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
2024-01-22  9:18 ` [PATCH 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).