* [PATCH v6 0/3] Add device STM32L4x5 GPIO
@ 2024-02-24 10:47 Inès Varhol
2024-02-24 10:47 ` [PATCH v6 1/3] hw/gpio: Implement " Inès Varhol
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Inès Varhol @ 2024-02-24 10:47 UTC (permalink / raw)
To: qemu-devel
Cc: Samuel Tardieu, Peter Maydell, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Inès Varhol,
Arnaud Minier, Laurent Vivier, qemu-arm
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.
Changes from v5 :
- remove duplicate macro constant `GPIO_NUM_PINS` from syscfg.h
(it's defined in gpio.h)
- moving definition of constant `NUM_GPIOS` from syscfg.h to gpio.h
- soc.c : replacing a hardcoded 16 by the correct `GPIO_NUM_PINS`
Changes from v4 :
- gpio.c : use helpers `is_pull_up()`, `is_pull_down()`, `is_output()`
for more clarity
- gpio.c : correct `update_gpio_idr()` in case of open-drain pin
set to 1 in ODR and set to 0 externally
- gpio.c : rename `get_gpio_pins_to_disconnect()` to
`get_gpio_pinmask_to_disconnect()` and associated comments
- gpio.c : correct coding style issues (alignment and declaration)
- soc.c : unite structs `gpio_addr` and `stm32l4x5_gpio_initval`
Changes from v3 :
- replacing occurences of '16' with the correct macro `GPIO_NUM_PINS`
- updating copyright year
- rebasing on latest version of STM32L4x5 RCC
Changes from v2 :
- correct memory leaks caused by re-assigning a `g_autofree`
pointer without freeing it
- gpio-test : test that reset values (and not just initialization
values) are correct, correct `stm32l4x5_gpio_reset()` accordingly
- adding a `clock-freq-hz` object property to test that
enabling GPIO clock in RCC sets the GPIO clocks
Changes from v1 :
- replacing test GPIO register `DISCONNECTED_PINS` with an object
property accessed using `qtest_qmp()` in the qtest (through helpers
`get_disconnected_pins()` and `disconnect_all_pins()`)
- removing GPIO subclasses and storing MODER, OSPEEDR and PUPDR reset
values in properties
- adding a `name` property and using it for more lisible traces
- using `g_strdup_printf()` to facilitate setting irqs in the qtest,
and initializing GPIO children in soc_initfn
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
- update MAINAINERS
Based-on: 20240219200908.49551-1-arnaud.minier@telecom-paris.fr
([PATCH v5 0/8] 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 +-
include/hw/arm/stm32l4x5_soc.h | 2 +
include/hw/gpio/stm32l4x5_gpio.h | 71 ++++
include/hw/misc/stm32l4x5_syscfg.h | 3 +-
hw/arm/stm32l4x5_soc.c | 71 +++-
hw/gpio/stm32l4x5_gpio.c | 477 +++++++++++++++++++++++
hw/misc/stm32l4x5_syscfg.c | 1 +
tests/qtest/stm32l4x5_gpio-test.c | 586 +++++++++++++++++++++++++++++
hw/arm/Kconfig | 3 +-
hw/gpio/Kconfig | 3 +
hw/gpio/meson.build | 1 +
hw/gpio/trace-events | 6 +
tests/qtest/meson.build | 3 +-
14 files changed, 1210 insertions(+), 20 deletions(-)
create mode 100644 include/hw/gpio/stm32l4x5_gpio.h
create mode 100644 hw/gpio/stm32l4x5_gpio.c
create mode 100644 tests/qtest/stm32l4x5_gpio-test.c
--
2.43.2
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v6 1/3] hw/gpio: Implement STM32L4x5 GPIO
2024-02-24 10:47 [PATCH v6 0/3] Add device STM32L4x5 GPIO Inès Varhol
@ 2024-02-24 10:47 ` Inès Varhol
2024-02-26 0:06 ` Alistair Francis
2024-02-24 10:47 ` [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Inès Varhol @ 2024-02-24 10:47 UTC (permalink / raw)
To: qemu-devel
Cc: Samuel Tardieu, Peter Maydell, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Inès Varhol,
Arnaud Minier, Laurent Vivier, qemu-arm
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>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
---
MAINTAINERS | 1 +
docs/system/arm/b-l475e-iot01a.rst | 2 +-
include/hw/gpio/stm32l4x5_gpio.h | 70 +++++
hw/gpio/stm32l4x5_gpio.c | 477 +++++++++++++++++++++++++++++
hw/gpio/Kconfig | 3 +
hw/gpio/meson.build | 1 +
hw/gpio/trace-events | 6 +
7 files changed, 559 insertions(+), 1 deletion(-)
create mode 100644 include/hw/gpio/stm32l4x5_gpio.h
create mode 100644 hw/gpio/stm32l4x5_gpio.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 50ab2982bb..cf49c151f3 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/include/hw/gpio/stm32l4x5_gpio.h b/include/hw/gpio/stm32l4x5_gpio.h
new file mode 100644
index 0000000000..0d361f3410
--- /dev/null
+++ b/include/hw/gpio/stm32l4x5_gpio.h
@@ -0,0 +1,70 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2024 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"
+OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5GpioState, 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;
+
+ /* GPIO registers reset values */
+ uint32_t moder_reset;
+ uint32_t ospeedr_reset;
+ uint32_t pupdr_reset;
+
+ /*
+ * 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_pinmask_to_disconnect()`)
+ */
+ uint16_t disconnected_pins;
+ uint16_t pins_connected_high;
+
+ char *name;
+ Clock *clk;
+ qemu_irq pin[GPIO_NUM_PINS];
+};
+
+#endif
diff --git a/hw/gpio/stm32l4x5_gpio.c b/hw/gpio/stm32l4x5_gpio.c
new file mode 100644
index 0000000000..63b8763e9d
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1,477 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2024 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 "hw/qdev-properties.h"
+#include "qapi/visitor.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
+
+/* 0b11111111_11111111_00000000_00000000 */
+#define RESERVED_BITS_MASK 0xFFFF0000
+
+static void update_gpio_idr(Stm32l4x5GpioState *s);
+
+static bool is_pull_up(Stm32l4x5GpioState *s, unsigned pin)
+{
+ return extract32(s->pupdr, 2 * pin, 2) == 1;
+}
+
+static bool is_pull_down(Stm32l4x5GpioState *s, unsigned pin)
+{
+ return extract32(s->pupdr, 2 * pin, 2) == 2;
+}
+
+static bool is_output(Stm32l4x5GpioState *s, unsigned pin)
+{
+ return extract32(s->moder, 2 * pin, 2) == 1;
+}
+
+static bool is_open_drain(Stm32l4x5GpioState *s, unsigned pin)
+{
+ return extract32(s->otyper, pin, 1) == 1;
+}
+
+static bool is_push_pull(Stm32l4x5GpioState *s, unsigned pin)
+{
+ return extract32(s->otyper, pin, 1) == 0;
+}
+
+static void stm32l4x5_gpio_reset_hold(Object *obj)
+{
+ Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
+
+ s->moder = s->moder_reset;
+ s->otyper = 0x00000000;
+ s->ospeedr = s->ospeedr_reset;
+ s->pupdr = s->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 (is_output(s, line) && !(is_open_drain(s, line) && (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->name, 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;
+ uint32_t old_idr = s->idr;
+ int new_pin_state, old_pin_state;
+
+ for (int i = 0; i < GPIO_NUM_PINS; i++) {
+ if (is_output(s, i)) {
+ if (is_push_pull(s, i)) {
+ new_idr_mask |= (1 << i);
+ } else if (!(s->odr & (1 << i))) {
+ /* open-drain ODR 0 */
+ new_idr_mask |= (1 << i);
+ /* open-drain ODR 1 */
+ } else if (!(s->disconnected_pins & (1 << i)) &&
+ !(s->pins_connected_high & (1 << i))) {
+ /* open-drain ODR 1 with pin connected low */
+ new_idr_mask |= (1 << i);
+ new_idr &= ~(1 << i);
+ /* open-drain ODR 1 with unactive pin */
+ } else if (is_pull_up(s, i)) {
+ new_idr_mask |= (1 << i);
+ } else if (is_pull_down(s, i)) {
+ new_idr_mask |= (1 << i);
+ new_idr &= ~(1 << i);
+ }
+ /*
+ * The only case left is for open-drain ODR 1
+ * with unactive 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 (is_pull_up(s, i)) {
+ /* pull-up */
+ new_idr_mask |= (1 << i);
+ new_idr |= (1 << i);
+ } else if (is_pull_down(s, i)) {
+ /* 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.
+ */
+ }
+ }
+
+ s->idr = (old_idr & ~new_idr_mask) | (new_idr & new_idr_mask);
+ trace_stm32l4x5_gpio_update_idr(s->name, old_idr, s->idr);
+
+ for (int i = 0; i < GPIO_NUM_PINS; i++) {
+ if (new_idr_mask & (1 << i)) {
+ new_pin_state = (new_idr & (1 << i)) > 0;
+ old_pin_state = (old_idr & (1 << i)) > 0;
+ if (new_pin_state > old_pin_state) {
+ qemu_irq_raise(s->pin[i]);
+ } else if (new_pin_state < old_pin_state) {
+ qemu_irq_lower(s->pin[i]);
+ }
+ }
+ }
+}
+
+/*
+ * Return mask of pins that are both configured in output
+ * mode and externally driven (except pins in open-drain
+ * mode externally set to 0).
+ */
+static uint32_t get_gpio_pinmask_to_disconnect(Stm32l4x5GpioState *s)
+{
+ uint32_t pins_to_disconnect = 0;
+ for (int i = 0; i < GPIO_NUM_PINS; i++) {
+ /* for each connected pin in output mode */
+ if (!(s->disconnected_pins & (1 << i)) && is_output(s, i)) {
+ /* if either push-pull or high level */
+ if (is_push_pull(s, i) || s->pins_connected_high & (1 << i)) {
+ 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->name, s->disconnected_pins,
+ s->pins_connected_high);
+ update_gpio_idr(s);
+}
+
+static void disconnected_pins_set(Object *obj, Visitor *v,
+ const char *name, void *opaque, Error **errp)
+{
+ Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+ disconnect_gpio_pins(s, value);
+}
+
+static void disconnected_pins_get(Object *obj, Visitor *v,
+ const char *name, void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void clock_freq_get(Object *obj, Visitor *v,
+ const char *name, void *opaque, Error **errp)
+{
+ Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
+ uint32_t clock_freq_hz = clock_get_hz(s->clk);
+ visit_type_uint32(v, name, &clock_freq_hz, errp);
+}
+
+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(s->name, addr, val64);
+
+ switch (addr) {
+ case GPIO_MODER:
+ s->moder = value;
+ disconnect_gpio_pins(s, get_gpio_pinmask_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_pinmask_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) >> GPIO_NUM_PINS;
+ 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;
+ 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(s->name, 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;
+ 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);
+
+ object_property_add(obj, "disconnected-pins", "uint16",
+ disconnected_pins_get, disconnected_pins_set,
+ NULL, &s->disconnected_pins);
+ object_property_add(obj, "clock-freq-hz", "uint32",
+ clock_freq_get, NULL, NULL, NULL);
+}
+
+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 Property stm32l4x5_gpio_properties[] = {
+ DEFINE_PROP_STRING("name", Stm32l4x5GpioState, name),
+ DEFINE_PROP_UINT32("mode-reset", Stm32l4x5GpioState, moder_reset, 0),
+ DEFINE_PROP_UINT32("ospeed-reset", Stm32l4x5GpioState, ospeedr_reset, 0),
+ DEFINE_PROP_UINT32("pupd-reset", Stm32l4x5GpioState, pupdr_reset, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void stm32l4x5_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ device_class_set_props(dc, stm32l4x5_gpio_properties);
+ dc->vmsd = &vmstate_stm32l4x5_gpio;
+ dc->realize = stm32l4x5_gpio_realize;
+ rc->phases.hold = stm32l4x5_gpio_reset_hold;
+}
+
+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_init = stm32l4x5_gpio_class_init,
+ },
+};
+
+DEFINE_TYPES(stm32l4x5_gpio_types)
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/trace-events b/hw/gpio/trace-events
index 9736b362ac..9331f4289d 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(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 ""
+stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
+stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
--
2.43.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC
2024-02-24 10:47 [PATCH v6 0/3] Add device STM32L4x5 GPIO Inès Varhol
2024-02-24 10:47 ` [PATCH v6 1/3] hw/gpio: Implement " Inès Varhol
@ 2024-02-24 10:47 ` Inès Varhol
2024-02-26 0:08 ` Alistair Francis
2024-02-24 10:47 ` [PATCH v6 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol
2024-03-05 16:10 ` [PATCH v6 0/3] Add device STM32L4x5 GPIO Peter Maydell
3 siblings, 1 reply; 8+ messages in thread
From: Inès Varhol @ 2024-02-24 10:47 UTC (permalink / raw)
To: qemu-devel
Cc: Samuel Tardieu, Peter Maydell, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Inès Varhol,
Arnaud Minier, Laurent Vivier, qemu-arm
Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
---
include/hw/arm/stm32l4x5_soc.h | 2 +
include/hw/gpio/stm32l4x5_gpio.h | 1 +
include/hw/misc/stm32l4x5_syscfg.h | 3 +-
hw/arm/stm32l4x5_soc.c | 71 +++++++++++++++++++++++-------
hw/misc/stm32l4x5_syscfg.c | 1 +
hw/arm/Kconfig | 3 +-
6 files changed, 63 insertions(+), 18 deletions(-)
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;
diff --git a/include/hw/gpio/stm32l4x5_gpio.h b/include/hw/gpio/stm32l4x5_gpio.h
index 0d361f3410..878bd19fc9 100644
--- a/include/hw/gpio/stm32l4x5_gpio.h
+++ b/include/hw/gpio/stm32l4x5_gpio.h
@@ -25,6 +25,7 @@
#define TYPE_STM32L4X5_GPIO "stm32l4x5-gpio"
OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5GpioState, STM32L4X5_GPIO)
+#define NUM_GPIOS 8
#define GPIO_NUM_PINS 16
struct Stm32l4x5GpioState {
diff --git a/include/hw/misc/stm32l4x5_syscfg.h b/include/hw/misc/stm32l4x5_syscfg.h
index 29c3522f9d..23bb564150 100644
--- a/include/hw/misc/stm32l4x5_syscfg.h
+++ b/include/hw/misc/stm32l4x5_syscfg.h
@@ -26,12 +26,11 @@
#include "hw/sysbus.h"
#include "qom/object.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
#define TYPE_STM32L4X5_SYSCFG "stm32l4x5-syscfg"
OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5SyscfgState, STM32L4X5_SYSCFG)
-#define NUM_GPIOS 8
-#define GPIO_NUM_PINS 16
#define SYSCFG_NUM_EXTICR 4
struct Stm32l4x5SyscfgState {
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 347a5377e5..072671bdfb 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -27,6 +27,7 @@
#include "exec/address-spaces.h"
#include "sysemu/sysemu.h"
#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
#include "hw/qdev-clock.h"
#include "hw/misc/unimp.h"
@@ -78,6 +79,22 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
#define RCC_BASE_ADDRESS 0x40021000
#define RCC_IRQ 5
+static const struct {
+ uint32_t addr;
+ uint32_t moder_reset;
+ uint32_t ospeedr_reset;
+ uint32_t pupdr_reset;
+} stm32l4x5_gpio_cfg[NUM_GPIOS] = {
+ { 0x48000000, 0xABFFFFFF, 0x0C000000, 0x64000000 },
+ { 0x48000400, 0xFFFFFEBF, 0x00000000, 0x00000100 },
+ { 0x48000800, 0xFFFFFFFF, 0x00000000, 0x00000000 },
+ { 0x48000C00, 0xFFFFFFFF, 0x00000000, 0x00000000 },
+ { 0x48001000, 0xFFFFFFFF, 0x00000000, 0x00000000 },
+ { 0x48001400, 0xFFFFFFFF, 0x00000000, 0x00000000 },
+ { 0x48001800, 0xFFFFFFFF, 0x00000000, 0x00000000 },
+ { 0x48001C00, 0x0000000F, 0x00000000, 0x00000000 },
+};
+
static void stm32l4x5_soc_initfn(Object *obj)
{
Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -85,6 +102,11 @@ 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);
+
+ for (unsigned i = 0; i < NUM_GPIOS; i++) {
+ g_autofree char *name = g_strdup_printf("gpio%c", 'a' + i);
+ object_initialize_child(obj, name, &s->gpio[i], TYPE_STM32L4X5_GPIO);
+ }
}
static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -93,8 +115,9 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
Stm32l4x5SocState *s = STM32L4X5_SOC(dev_soc);
const Stm32l4x5SocClass *sc = STM32L4X5_SOC_GET_CLASS(dev_soc);
MemoryRegion *system_memory = get_system_memory();
- DeviceState *armv7m;
+ DeviceState *armv7m, *dev;
SysBusDevice *busdev;
+ uint32_t pin_index;
if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
sc->flash_size, errp)) {
@@ -135,17 +158,43 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
return;
}
+ /* GPIOs */
+ for (unsigned i = 0; i < NUM_GPIOS; i++) {
+ g_autofree char *name = g_strdup_printf("%c", 'A' + i);
+ dev = DEVICE(&s->gpio[i]);
+ qdev_prop_set_string(dev, "name", name);
+ qdev_prop_set_uint32(dev, "mode-reset",
+ stm32l4x5_gpio_cfg[i].moder_reset);
+ qdev_prop_set_uint32(dev, "ospeed-reset",
+ stm32l4x5_gpio_cfg[i].ospeedr_reset);
+ qdev_prop_set_uint32(dev, "pupd-reset",
+ stm32l4x5_gpio_cfg[i].pupdr_reset);
+ busdev = SYS_BUS_DEVICE(&s->gpio[i]);
+ g_free(name);
+ name = g_strdup_printf("gpio%c-out", 'a' + i);
+ qdev_connect_clock_in(DEVICE(&s->gpio[i]), "clk",
+ qdev_get_clock_out(DEVICE(&(s->rcc)), name));
+ if (!sysbus_realize(busdev, errp)) {
+ return;
+ }
+ sysbus_mmio_map(busdev, 0, stm32l4x5_gpio_cfg[i].addr);
+ }
+
/* 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);
@@ -157,7 +206,7 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, exti_irq[i]));
}
- for (unsigned i = 0; i < 16; i++) {
+ for (unsigned i = 0; i < GPIO_NUM_PINS; i++) {
qdev_connect_gpio_out(DEVICE(&s->syscfg), i,
qdev_get_gpio_in(DEVICE(&s->exti), i));
}
@@ -242,14 +291,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/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
index fd68cb800b..3dafc00b49 100644
--- a/hw/misc/stm32l4x5_syscfg.c
+++ b/hw/misc/stm32l4x5_syscfg.c
@@ -27,6 +27,7 @@
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "hw/misc/stm32l4x5_syscfg.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
#define SYSCFG_MEMRMP 0x00
#define SYSCFG_CFGR1 0x04
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 92b72d56dc..5776dbb19f 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -463,9 +463,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
--
2.43.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v6 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase
2024-02-24 10:47 [PATCH v6 0/3] Add device STM32L4x5 GPIO Inès Varhol
2024-02-24 10:47 ` [PATCH v6 1/3] hw/gpio: Implement " Inès Varhol
2024-02-24 10:47 ` [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
@ 2024-02-24 10:47 ` Inès Varhol
2024-03-05 16:10 ` [PATCH v6 0/3] Add device STM32L4x5 GPIO Peter Maydell
3 siblings, 0 replies; 8+ messages in thread
From: Inès Varhol @ 2024-02-24 10:47 UTC (permalink / raw)
To: qemu-devel
Cc: Samuel Tardieu, Peter Maydell, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Inès Varhol,
Arnaud Minier, Laurent Vivier, qemu-arm
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.
- `test_clock_enable()` :
Checks that GPIO clock is at the right frequency after enabling it.
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/stm32l4x5_gpio-test.c | 586 ++++++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +-
2 files changed, 588 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/stm32l4x5_gpio-test.c
diff --git a/tests/qtest/stm32l4x5_gpio-test.c b/tests/qtest/stm32l4x5_gpio-test.c
new file mode 100644
index 0000000000..cd4fd9bae2
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,586 @@
+/*
+ * QTest testcase for STM32L4x5_GPIO
+ *
+ * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2024 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
+
+/*
+ * MSI is used as system clock source after startup
+ * from Reset, configured at 4 MHz.
+ */
+#define SYSCLK_FREQ_HZ 4000000
+#define RCC_AHB2ENR 0x4002104C
+
+#define MODER 0x00
+#define OTYPER 0x04
+#define PUPDR 0x0C
+#define IDR 0x10
+#define ODR 0x14
+#define BSRR 0x18
+#define BRR 0x28
+
+#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 unsigned int get_gpio_id(uint32_t gpio_addr)
+{
+ return (gpio_addr - GPIO_BASE_ADDR) / GPIO_SIZE;
+}
+
+static void gpio_set_irq(unsigned int gpio, int num, int level)
+{
+ g_autofree char *name = g_strdup_printf("/machine/soc/gpio%c",
+ get_gpio_id(gpio) + 'a');
+ qtest_set_irq_in(global_qtest, name, NULL, num, level);
+}
+
+static void disconnect_all_pins(unsigned int gpio)
+{
+ g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c",
+ get_gpio_id(gpio) + 'a');
+ QDict *r;
+
+ r = qtest_qmp(global_qtest, "{ 'execute': 'qom-set', 'arguments': "
+ "{ 'path': %s, 'property': 'disconnected-pins', 'value': %d } }",
+ path, 0xFFFF);
+ g_assert_false(qdict_haskey(r, "error"));
+ qobject_unref(r);
+}
+
+static uint32_t get_disconnected_pins(unsigned int gpio)
+{
+ g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c",
+ get_gpio_id(gpio) + 'a');
+ uint32_t disconnected_pins = 0;
+ QDict *r;
+
+ r = qtest_qmp(global_qtest, "{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': 'disconnected-pins'} }", path);
+ g_assert_false(qdict_haskey(r, "error"));
+ disconnected_pins = qdict_get_int(r, "return");
+ qobject_unref(r);
+ return disconnected_pins;
+}
+
+static uint32_t get_clock_freq_hz(unsigned int gpio)
+{
+ g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c",
+ get_gpio_id(gpio) + 'a');
+ uint32_t clock_freq_hz = 0;
+ QDict *r;
+
+ r = qtest_qmp(global_qtest, "{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': 'clock-freq-hz'} }", path);
+ g_assert_false(qdict_haskey(r, "error"));
+ clock_freq_hz = qdict_get_int(r, "return");
+ qobject_unref(r);
+ return clock_freq_hz;
+}
+
+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 system_reset(void)
+{
+ QDict *r;
+ r = qtest_qmp(global_qtest, "{'execute': 'system_reset'}");
+ g_assert_false(qdict_haskey(r, "error"));
+ qobject_unref(r);
+}
+
+static void test_idr_reset_value(void)
+{
+ /*
+ * Checks that the values in MODER, OTYPER, PUPDR and ODR
+ * after 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 all 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).
+ */
+
+ gpio_writel(GPIO_A, MODER, 0xDEADBEEF);
+ gpio_writel(GPIO_A, ODR, 0xDEADBEEF);
+ gpio_writel(GPIO_A, OTYPER, 0xDEADBEEF);
+ gpio_writel(GPIO_A, PUPDR, 0xDEADBEEF);
+
+ gpio_writel(GPIO_B, MODER, 0xDEADBEEF);
+ gpio_writel(GPIO_B, ODR, 0xDEADBEEF);
+ gpio_writel(GPIO_B, OTYPER, 0xDEADBEEF);
+ gpio_writel(GPIO_B, PUPDR, 0xDEADBEEF);
+
+ gpio_writel(GPIO_C, MODER, 0xDEADBEEF);
+ gpio_writel(GPIO_C, ODR, 0xDEADBEEF);
+ gpio_writel(GPIO_C, OTYPER, 0xDEADBEEF);
+ gpio_writel(GPIO_C, PUPDR, 0xDEADBEEF);
+
+ gpio_writel(GPIO_H, MODER, 0xDEADBEEF);
+ gpio_writel(GPIO_H, ODR, 0xDEADBEEF);
+ gpio_writel(GPIO_H, OTYPER, 0xDEADBEEF);
+ gpio_writel(GPIO_H, PUPDR, 0xDEADBEEF);
+
+ system_reset();
+
+ 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_clock_enable(void)
+{
+ g_assert_cmpuint(get_clock_freq_hz(GPIO_B), ==, 0);
+ g_assert_cmpuint(get_clock_freq_hz(GPIO_F), ==, 0);
+
+ writel(RCC_AHB2ENR, readl(RCC_AHB2ENR) | (0x22 << 0));
+
+ g_assert_cmpuint(get_clock_freq_hz(GPIO_B), ==, SYSCLK_FREQ_HZ);
+ g_assert_cmpuint(get_clock_freq_hz(GPIO_F), ==, SYSCLK_FREQ_HZ);
+}
+
+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));
+ disconnect_all_pins(gpio);
+ 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(get_disconnected_pins(gpio), ==, 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(get_disconnected_pins(gpio2), ==, 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(get_disconnected_pins(gpio), ==, 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(get_disconnected_pins(gpio2), ==, 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(get_disconnected_pins(gpio), ==, 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(get_disconnected_pins(gpio2), ==, 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(get_disconnected_pins(gpio), ==, 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(get_disconnected_pins(gpio2), ==, 0xFFFF & ~(1 << pin));
+ g_assert_cmphex(gpio_readl(gpio2, IDR), ==,
+ reset(gpio2, IDR) & ~(1 << pin));
+
+ /* Clean the test */
+ disconnect_all_pins(gpio2);
+ 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));
+ disconnect_all_pins(gpio);
+ 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 + NUM_GPIO_PINS)));
+ 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 + NUM_GPIO_PINS)));
+ 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_add_func("stm32l4x5/gpio/test_clock_enable",
+ test_clock_enable);
+
+ qtest_start("-machine b-l475e-iot01a");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 36f85c8920..2db5b0329e 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -202,7 +202,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'] : []) + \
--
2.43.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/3] hw/gpio: Implement STM32L4x5 GPIO
2024-02-24 10:47 ` [PATCH v6 1/3] hw/gpio: Implement " Inès Varhol
@ 2024-02-26 0:06 ` Alistair Francis
0 siblings, 0 replies; 8+ messages in thread
From: Alistair Francis @ 2024-02-26 0:06 UTC (permalink / raw)
To: Inès Varhol
Cc: qemu-devel, Samuel Tardieu, Peter Maydell,
Philippe Mathieu-Daudé, Thomas Huth, Alistair Francis,
Paolo Bonzini, Arnaud Minier, Laurent Vivier, qemu-arm
On Sat, Feb 24, 2024 at 8:55 PM Inès Varhol
<ines.varhol@telecom-paris.fr> 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>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> MAINTAINERS | 1 +
> docs/system/arm/b-l475e-iot01a.rst | 2 +-
> include/hw/gpio/stm32l4x5_gpio.h | 70 +++++
> hw/gpio/stm32l4x5_gpio.c | 477 +++++++++++++++++++++++++++++
> hw/gpio/Kconfig | 3 +
> hw/gpio/meson.build | 1 +
> hw/gpio/trace-events | 6 +
> 7 files changed, 559 insertions(+), 1 deletion(-)
> create mode 100644 include/hw/gpio/stm32l4x5_gpio.h
> create mode 100644 hw/gpio/stm32l4x5_gpio.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 50ab2982bb..cf49c151f3 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/include/hw/gpio/stm32l4x5_gpio.h b/include/hw/gpio/stm32l4x5_gpio.h
> new file mode 100644
> index 0000000000..0d361f3410
> --- /dev/null
> +++ b/include/hw/gpio/stm32l4x5_gpio.h
> @@ -0,0 +1,70 @@
> +/*
> + * STM32L4x5 GPIO (General Purpose Input/Ouput)
> + *
> + * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2024 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"
> +OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5GpioState, 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;
> +
> + /* GPIO registers reset values */
> + uint32_t moder_reset;
> + uint32_t ospeedr_reset;
> + uint32_t pupdr_reset;
> +
> + /*
> + * 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_pinmask_to_disconnect()`)
> + */
> + uint16_t disconnected_pins;
> + uint16_t pins_connected_high;
> +
> + char *name;
> + Clock *clk;
> + qemu_irq pin[GPIO_NUM_PINS];
> +};
> +
> +#endif
> diff --git a/hw/gpio/stm32l4x5_gpio.c b/hw/gpio/stm32l4x5_gpio.c
> new file mode 100644
> index 0000000000..63b8763e9d
> --- /dev/null
> +++ b/hw/gpio/stm32l4x5_gpio.c
> @@ -0,0 +1,477 @@
> +/*
> + * STM32L4x5 GPIO (General Purpose Input/Ouput)
> + *
> + * Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
> + * Copyright (c) 2024 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 "hw/qdev-properties.h"
> +#include "qapi/visitor.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
> +
> +/* 0b11111111_11111111_00000000_00000000 */
> +#define RESERVED_BITS_MASK 0xFFFF0000
> +
> +static void update_gpio_idr(Stm32l4x5GpioState *s);
> +
> +static bool is_pull_up(Stm32l4x5GpioState *s, unsigned pin)
> +{
> + return extract32(s->pupdr, 2 * pin, 2) == 1;
> +}
> +
> +static bool is_pull_down(Stm32l4x5GpioState *s, unsigned pin)
> +{
> + return extract32(s->pupdr, 2 * pin, 2) == 2;
> +}
> +
> +static bool is_output(Stm32l4x5GpioState *s, unsigned pin)
> +{
> + return extract32(s->moder, 2 * pin, 2) == 1;
> +}
> +
> +static bool is_open_drain(Stm32l4x5GpioState *s, unsigned pin)
> +{
> + return extract32(s->otyper, pin, 1) == 1;
> +}
> +
> +static bool is_push_pull(Stm32l4x5GpioState *s, unsigned pin)
> +{
> + return extract32(s->otyper, pin, 1) == 0;
> +}
> +
> +static void stm32l4x5_gpio_reset_hold(Object *obj)
> +{
> + Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
> +
> + s->moder = s->moder_reset;
> + s->otyper = 0x00000000;
> + s->ospeedr = s->ospeedr_reset;
> + s->pupdr = s->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 (is_output(s, line) && !(is_open_drain(s, line) && (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->name, 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;
> + uint32_t old_idr = s->idr;
> + int new_pin_state, old_pin_state;
> +
> + for (int i = 0; i < GPIO_NUM_PINS; i++) {
> + if (is_output(s, i)) {
> + if (is_push_pull(s, i)) {
> + new_idr_mask |= (1 << i);
> + } else if (!(s->odr & (1 << i))) {
> + /* open-drain ODR 0 */
> + new_idr_mask |= (1 << i);
> + /* open-drain ODR 1 */
> + } else if (!(s->disconnected_pins & (1 << i)) &&
> + !(s->pins_connected_high & (1 << i))) {
> + /* open-drain ODR 1 with pin connected low */
> + new_idr_mask |= (1 << i);
> + new_idr &= ~(1 << i);
> + /* open-drain ODR 1 with unactive pin */
> + } else if (is_pull_up(s, i)) {
> + new_idr_mask |= (1 << i);
> + } else if (is_pull_down(s, i)) {
> + new_idr_mask |= (1 << i);
> + new_idr &= ~(1 << i);
> + }
> + /*
> + * The only case left is for open-drain ODR 1
> + * with unactive 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 (is_pull_up(s, i)) {
> + /* pull-up */
> + new_idr_mask |= (1 << i);
> + new_idr |= (1 << i);
> + } else if (is_pull_down(s, i)) {
> + /* 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.
> + */
> + }
> + }
> +
> + s->idr = (old_idr & ~new_idr_mask) | (new_idr & new_idr_mask);
> + trace_stm32l4x5_gpio_update_idr(s->name, old_idr, s->idr);
> +
> + for (int i = 0; i < GPIO_NUM_PINS; i++) {
> + if (new_idr_mask & (1 << i)) {
> + new_pin_state = (new_idr & (1 << i)) > 0;
> + old_pin_state = (old_idr & (1 << i)) > 0;
> + if (new_pin_state > old_pin_state) {
> + qemu_irq_raise(s->pin[i]);
> + } else if (new_pin_state < old_pin_state) {
> + qemu_irq_lower(s->pin[i]);
> + }
> + }
> + }
> +}
> +
> +/*
> + * Return mask of pins that are both configured in output
> + * mode and externally driven (except pins in open-drain
> + * mode externally set to 0).
> + */
> +static uint32_t get_gpio_pinmask_to_disconnect(Stm32l4x5GpioState *s)
> +{
> + uint32_t pins_to_disconnect = 0;
> + for (int i = 0; i < GPIO_NUM_PINS; i++) {
> + /* for each connected pin in output mode */
> + if (!(s->disconnected_pins & (1 << i)) && is_output(s, i)) {
> + /* if either push-pull or high level */
> + if (is_push_pull(s, i) || s->pins_connected_high & (1 << i)) {
> + 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->name, s->disconnected_pins,
> + s->pins_connected_high);
> + update_gpio_idr(s);
> +}
> +
> +static void disconnected_pins_set(Object *obj, Visitor *v,
> + const char *name, void *opaque, Error **errp)
> +{
> + Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
> + uint16_t value;
> + if (!visit_type_uint16(v, name, &value, errp)) {
> + return;
> + }
> + disconnect_gpio_pins(s, value);
> +}
> +
> +static void disconnected_pins_get(Object *obj, Visitor *v,
> + const char *name, void *opaque, Error **errp)
> +{
> + visit_type_uint16(v, name, (uint16_t *)opaque, errp);
> +}
> +
> +static void clock_freq_get(Object *obj, Visitor *v,
> + const char *name, void *opaque, Error **errp)
> +{
> + Stm32l4x5GpioState *s = STM32L4X5_GPIO(obj);
> + uint32_t clock_freq_hz = clock_get_hz(s->clk);
> + visit_type_uint32(v, name, &clock_freq_hz, errp);
> +}
> +
> +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(s->name, addr, val64);
> +
> + switch (addr) {
> + case GPIO_MODER:
> + s->moder = value;
> + disconnect_gpio_pins(s, get_gpio_pinmask_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_pinmask_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) >> GPIO_NUM_PINS;
> + 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;
> + 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(s->name, 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;
> + 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);
> +
> + object_property_add(obj, "disconnected-pins", "uint16",
> + disconnected_pins_get, disconnected_pins_set,
> + NULL, &s->disconnected_pins);
> + object_property_add(obj, "clock-freq-hz", "uint32",
> + clock_freq_get, NULL, NULL, NULL);
> +}
> +
> +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 Property stm32l4x5_gpio_properties[] = {
> + DEFINE_PROP_STRING("name", Stm32l4x5GpioState, name),
> + DEFINE_PROP_UINT32("mode-reset", Stm32l4x5GpioState, moder_reset, 0),
> + DEFINE_PROP_UINT32("ospeed-reset", Stm32l4x5GpioState, ospeedr_reset, 0),
> + DEFINE_PROP_UINT32("pupd-reset", Stm32l4x5GpioState, pupdr_reset, 0),
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void stm32l4x5_gpio_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> + device_class_set_props(dc, stm32l4x5_gpio_properties);
> + dc->vmsd = &vmstate_stm32l4x5_gpio;
> + dc->realize = stm32l4x5_gpio_realize;
> + rc->phases.hold = stm32l4x5_gpio_reset_hold;
> +}
> +
> +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_init = stm32l4x5_gpio_class_init,
> + },
> +};
> +
> +DEFINE_TYPES(stm32l4x5_gpio_types)
> 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/trace-events b/hw/gpio/trace-events
> index 9736b362ac..9331f4289d 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(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 ""
> +stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
> +stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
> --
> 2.43.2
>
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC
2024-02-24 10:47 ` [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
@ 2024-02-26 0:08 ` Alistair Francis
0 siblings, 0 replies; 8+ messages in thread
From: Alistair Francis @ 2024-02-26 0:08 UTC (permalink / raw)
To: Inès Varhol
Cc: qemu-devel, Samuel Tardieu, Peter Maydell,
Philippe Mathieu-Daudé, Thomas Huth, Alistair Francis,
Paolo Bonzini, Arnaud Minier, Laurent Vivier, qemu-arm
On Sat, Feb 24, 2024 at 8:56 PM Inès Varhol
<ines.varhol@telecom-paris.fr> wrote:
>
> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> include/hw/arm/stm32l4x5_soc.h | 2 +
> include/hw/gpio/stm32l4x5_gpio.h | 1 +
> include/hw/misc/stm32l4x5_syscfg.h | 3 +-
> hw/arm/stm32l4x5_soc.c | 71 +++++++++++++++++++++++-------
> hw/misc/stm32l4x5_syscfg.c | 1 +
> hw/arm/Kconfig | 3 +-
> 6 files changed, 63 insertions(+), 18 deletions(-)
>
> 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;
> diff --git a/include/hw/gpio/stm32l4x5_gpio.h b/include/hw/gpio/stm32l4x5_gpio.h
> index 0d361f3410..878bd19fc9 100644
> --- a/include/hw/gpio/stm32l4x5_gpio.h
> +++ b/include/hw/gpio/stm32l4x5_gpio.h
> @@ -25,6 +25,7 @@
> #define TYPE_STM32L4X5_GPIO "stm32l4x5-gpio"
> OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5GpioState, STM32L4X5_GPIO)
>
> +#define NUM_GPIOS 8
> #define GPIO_NUM_PINS 16
>
> struct Stm32l4x5GpioState {
> diff --git a/include/hw/misc/stm32l4x5_syscfg.h b/include/hw/misc/stm32l4x5_syscfg.h
> index 29c3522f9d..23bb564150 100644
> --- a/include/hw/misc/stm32l4x5_syscfg.h
> +++ b/include/hw/misc/stm32l4x5_syscfg.h
> @@ -26,12 +26,11 @@
>
> #include "hw/sysbus.h"
> #include "qom/object.h"
> +#include "hw/gpio/stm32l4x5_gpio.h"
>
> #define TYPE_STM32L4X5_SYSCFG "stm32l4x5-syscfg"
> OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5SyscfgState, STM32L4X5_SYSCFG)
>
> -#define NUM_GPIOS 8
> -#define GPIO_NUM_PINS 16
> #define SYSCFG_NUM_EXTICR 4
>
> struct Stm32l4x5SyscfgState {
> diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
> index 347a5377e5..072671bdfb 100644
> --- a/hw/arm/stm32l4x5_soc.c
> +++ b/hw/arm/stm32l4x5_soc.c
> @@ -27,6 +27,7 @@
> #include "exec/address-spaces.h"
> #include "sysemu/sysemu.h"
> #include "hw/arm/stm32l4x5_soc.h"
> +#include "hw/gpio/stm32l4x5_gpio.h"
> #include "hw/qdev-clock.h"
> #include "hw/misc/unimp.h"
>
> @@ -78,6 +79,22 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
> #define RCC_BASE_ADDRESS 0x40021000
> #define RCC_IRQ 5
>
> +static const struct {
> + uint32_t addr;
> + uint32_t moder_reset;
> + uint32_t ospeedr_reset;
> + uint32_t pupdr_reset;
> +} stm32l4x5_gpio_cfg[NUM_GPIOS] = {
> + { 0x48000000, 0xABFFFFFF, 0x0C000000, 0x64000000 },
> + { 0x48000400, 0xFFFFFEBF, 0x00000000, 0x00000100 },
> + { 0x48000800, 0xFFFFFFFF, 0x00000000, 0x00000000 },
> + { 0x48000C00, 0xFFFFFFFF, 0x00000000, 0x00000000 },
> + { 0x48001000, 0xFFFFFFFF, 0x00000000, 0x00000000 },
> + { 0x48001400, 0xFFFFFFFF, 0x00000000, 0x00000000 },
> + { 0x48001800, 0xFFFFFFFF, 0x00000000, 0x00000000 },
> + { 0x48001C00, 0x0000000F, 0x00000000, 0x00000000 },
> +};
> +
> static void stm32l4x5_soc_initfn(Object *obj)
> {
> Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
> @@ -85,6 +102,11 @@ 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);
> +
> + for (unsigned i = 0; i < NUM_GPIOS; i++) {
> + g_autofree char *name = g_strdup_printf("gpio%c", 'a' + i);
> + object_initialize_child(obj, name, &s->gpio[i], TYPE_STM32L4X5_GPIO);
> + }
> }
>
> static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
> @@ -93,8 +115,9 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
> Stm32l4x5SocState *s = STM32L4X5_SOC(dev_soc);
> const Stm32l4x5SocClass *sc = STM32L4X5_SOC_GET_CLASS(dev_soc);
> MemoryRegion *system_memory = get_system_memory();
> - DeviceState *armv7m;
> + DeviceState *armv7m, *dev;
> SysBusDevice *busdev;
> + uint32_t pin_index;
>
> if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
> sc->flash_size, errp)) {
> @@ -135,17 +158,43 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
> return;
> }
>
> + /* GPIOs */
> + for (unsigned i = 0; i < NUM_GPIOS; i++) {
> + g_autofree char *name = g_strdup_printf("%c", 'A' + i);
> + dev = DEVICE(&s->gpio[i]);
> + qdev_prop_set_string(dev, "name", name);
> + qdev_prop_set_uint32(dev, "mode-reset",
> + stm32l4x5_gpio_cfg[i].moder_reset);
> + qdev_prop_set_uint32(dev, "ospeed-reset",
> + stm32l4x5_gpio_cfg[i].ospeedr_reset);
> + qdev_prop_set_uint32(dev, "pupd-reset",
> + stm32l4x5_gpio_cfg[i].pupdr_reset);
> + busdev = SYS_BUS_DEVICE(&s->gpio[i]);
> + g_free(name);
> + name = g_strdup_printf("gpio%c-out", 'a' + i);
> + qdev_connect_clock_in(DEVICE(&s->gpio[i]), "clk",
> + qdev_get_clock_out(DEVICE(&(s->rcc)), name));
> + if (!sysbus_realize(busdev, errp)) {
> + return;
> + }
> + sysbus_mmio_map(busdev, 0, stm32l4x5_gpio_cfg[i].addr);
> + }
> +
> /* 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);
> @@ -157,7 +206,7 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
> sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, exti_irq[i]));
> }
>
> - for (unsigned i = 0; i < 16; i++) {
> + for (unsigned i = 0; i < GPIO_NUM_PINS; i++) {
> qdev_connect_gpio_out(DEVICE(&s->syscfg), i,
> qdev_get_gpio_in(DEVICE(&s->exti), i));
> }
> @@ -242,14 +291,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/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
> index fd68cb800b..3dafc00b49 100644
> --- a/hw/misc/stm32l4x5_syscfg.c
> +++ b/hw/misc/stm32l4x5_syscfg.c
> @@ -27,6 +27,7 @@
> #include "hw/irq.h"
> #include "migration/vmstate.h"
> #include "hw/misc/stm32l4x5_syscfg.h"
> +#include "hw/gpio/stm32l4x5_gpio.h"
>
> #define SYSCFG_MEMRMP 0x00
> #define SYSCFG_CFGR1 0x04
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 92b72d56dc..5776dbb19f 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -463,9 +463,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
> --
> 2.43.2
>
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 0/3] Add device STM32L4x5 GPIO
2024-02-24 10:47 [PATCH v6 0/3] Add device STM32L4x5 GPIO Inès Varhol
` (2 preceding siblings ...)
2024-02-24 10:47 ` [PATCH v6 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol
@ 2024-03-05 16:10 ` Peter Maydell
2024-03-05 21:10 ` Inès Varhol
3 siblings, 1 reply; 8+ messages in thread
From: Peter Maydell @ 2024-03-05 16:10 UTC (permalink / raw)
To: Inès Varhol
Cc: qemu-devel, Samuel Tardieu, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Arnaud Minier,
Laurent Vivier, qemu-arm
On Sat, 24 Feb 2024 at 10:54, Inès Varhol <ines.varhol@telecom-paris.fr> wrote:
>
> This patch adds a new device STM32L4x5 GPIO device and is part
> of a series implementing the STM32L4x5 with a few peripherals.
Hi -- I think this patchset is basically good to go, but it
didn't quite apply cleanly on top of the RCC v6 patchset.
If you could rebase it on current head-of-git (where the RCC
device has just landed) and send it out within the next few
days then we should be able to get it into the 9.0 release.
(Softfreeze is 12 Mar, and patches need to be in pullrequests
by then, so the patch needs to get on the list a little before
softfreeze.) Otherwise we'll be able to put it in for 9.1.
thanks
-- PMM
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 0/3] Add device STM32L4x5 GPIO
2024-03-05 16:10 ` [PATCH v6 0/3] Add device STM32L4x5 GPIO Peter Maydell
@ 2024-03-05 21:10 ` Inès Varhol
0 siblings, 0 replies; 8+ messages in thread
From: Inès Varhol @ 2024-03-05 21:10 UTC (permalink / raw)
To: peter maydell
Cc: qemu-devel, Samuel Tardieu, Philippe Mathieu-Daudé,
Thomas Huth, Alistair Francis, Paolo Bonzini, Arnaud Minier,
Laurent Vivier, qemu-arm
Le 5 Mar 24, à 17:10, peter maydell peter.maydell@linaro.org a écrit :
> On Sat, 24 Feb 2024 at 10:54, Inès Varhol <ines.varhol@telecom-paris.fr> wrote:
>>
>> This patch adds a new device STM32L4x5 GPIO device and is part
>> of a series implementing the STM32L4x5 with a few peripherals.
>
> Hi -- I think this patchset is basically good to go, but it
> didn't quite apply cleanly on top of the RCC v6 patchset.
> If you could rebase it on current head-of-git (where the RCC
> device has just landed) and send it out within the next few
> days then we should be able to get it into the 9.0 release.
> (Softfreeze is 12 Mar, and patches need to be in pullrequests
> by then, so the patch needs to get on the list a little before
> softfreeze.) Otherwise we'll be able to put it in for 9.1.
>
> thanks
> -- PMM
Thank you for the information, I just sent a new version based on master.
Ines
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2024-03-05 21:10 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-24 10:47 [PATCH v6 0/3] Add device STM32L4x5 GPIO Inès Varhol
2024-02-24 10:47 ` [PATCH v6 1/3] hw/gpio: Implement " Inès Varhol
2024-02-26 0:06 ` Alistair Francis
2024-02-24 10:47 ` [PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC Inès Varhol
2024-02-26 0:08 ` Alistair Francis
2024-02-24 10:47 ` [PATCH v6 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase Inès Varhol
2024-03-05 16:10 ` [PATCH v6 0/3] Add device STM32L4x5 GPIO Peter Maydell
2024-03-05 21:10 ` 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).