[PATCH v3 0/4] Check clock connection between STM32L4x5 RCC and peripherals

2024-05-23 Thread Inès Varhol
Among implemented STM32L4x5 devices, USART, GPIO and SYSCFG
have a clock source, but none has a corresponding test in QEMU.

This patch makes sure that all 3 devices create a clock correctly,
adds a QOM property to access clocks' periods from QTests,
and adds QTests checking that clock enable in RCC has the
expected results for all 3 devices.

Thank you for the reviews.

Changes from "v1" to v3:
- adding a commit to expose `qtest-clock-period`, a QOM property for
all clocks, only accessible from QTests, and mention it in clock.rst
- adapt QTests so that they use clock period instead of clock frequency
- remove `clock-freq-hz` QOM property in STM32L4x5 USART and SYSCFG
- dropping the commit migrating GPIO clocks as it's already upstream

Changes from v1 to an unfortunate second "v1":
- upgrading `VMStateDescription` to version 2 to account for
`VMSTATE_CLOCK()`
- QTests : consolidating `get_clock_freq_hz()` in a header
and making appropriate changes in stm32l4x5q_*-test.c

Signed-off-by: Inès Varhol 

Inès Varhol (4):
  hw/misc: Create STM32L4x5 SYSCFG clock
  hw/char: Use v2 VMStateDescription for STM32L4x5 USART
  hw/clock: Expose 'qtest-clock-period' QOM property for QTests
  tests/qtest: Check STM32L4x5 clock connections

 docs/devel/clocks.rst   |  3 ++
 include/hw/misc/stm32l4x5_syscfg.h  |  1 +
 tests/qtest/stm32l4x5.h | 43 +
 hw/arm/stm32l4x5_soc.c  |  2 ++
 hw/char/stm32l4x5_usart.c   |  4 +--
 hw/core/clock.c | 16 +++
 hw/misc/stm32l4x5_syscfg.c  | 19 +++--
 tests/qtest/stm32l4x5_gpio-test.c   | 23 +++
 tests/qtest/stm32l4x5_syscfg-test.c | 20 --
 tests/qtest/stm32l4x5_usart-test.c  | 26 +
 10 files changed, 151 insertions(+), 6 deletions(-)
 create mode 100644 tests/qtest/stm32l4x5.h

-- 
2.43.2




[PATCH v3 4/4] tests/qtest: Check STM32L4x5 clock connections

2024-05-23 Thread Inès Varhol
For USART, GPIO and SYSCFG devices, check that clock frequency before
and after enabling the peripheral clock in RCC is correct.

Signed-off-by: Inès Varhol 
---
 tests/qtest/stm32l4x5.h | 43 +
 tests/qtest/stm32l4x5_gpio-test.c   | 23 +++
 tests/qtest/stm32l4x5_syscfg-test.c | 20 --
 tests/qtest/stm32l4x5_usart-test.c  | 26 +
 4 files changed, 110 insertions(+), 2 deletions(-)
 create mode 100644 tests/qtest/stm32l4x5.h

diff --git a/tests/qtest/stm32l4x5.h b/tests/qtest/stm32l4x5.h
new file mode 100644
index 00..cf59aeb019
--- /dev/null
+++ b/tests/qtest/stm32l4x5.h
@@ -0,0 +1,43 @@
+/*
+ * QTest testcase header for STM32L4X5 :
+ * used for consolidating common objects in stm32l4x5_*-test.c
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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.h"
+
+/*
+ * MSI (4 MHz) is used as system clock source after startup
+ * from Reset.
+ * AHB, APB1 and APB2 prescalers are set to 1 at reset.
+ *
+ * A clock period is stored in units of 2^-32 ns :
+ * 10^9 * 2^32 / 400 = 1073741824000
+ */
+#define SYSCLK_PERIOD 1073741824000UL
+#define RCC_AHB2ENR 0x4002104C
+#define RCC_APB1ENR1 0x40021058
+#define RCC_APB1ENR2 0x4002105C
+#define RCC_APB2ENR 0x40021060
+
+
+static inline uint64_t get_clock_period(QTestState *qts, const char *path)
+{
+uint64_t clock_period = 0;
+QDict *r;
+
+r = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments':"
+" { 'path': %s, 'property': 'qtest-clock-period'} }", path);
+g_assert_false(qdict_haskey(r, "error"));
+clock_period = qdict_get_int(r, "return");
+qobject_unref(r);
+return clock_period;
+}
+
+
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 72a7823406..c0686c7b30 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -10,6 +10,7 @@
 
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
+#include "stm32l4x5.h"
 
 #define GPIO_BASE_ADDR 0x4800
 #define GPIO_SIZE  0x400
@@ -505,6 +506,26 @@ static void test_bsrr_brr(const void *data)
 gpio_writel(gpio, ODR, reset(gpio, ODR));
 }
 
+static void test_clock_enable(void)
+{
+/*
+ * For each GPIO, enable its clock in RCC
+ * and check that its clock period changes to SYSCLK_PERIOD
+ */
+unsigned int gpio_id;
+
+for (uint32_t gpio = GPIO_A; gpio <= GPIO_H; gpio += GPIO_B - GPIO_A) {
+gpio_id = get_gpio_id(gpio);
+g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c/clk",
+gpio_id + 'a');
+g_assert_cmpuint(get_clock_period(global_qtest, path), ==, 0);
+/* Enable the gpio clock */
+writel(RCC_AHB2ENR, readl(RCC_AHB2ENR) | (0x1 << gpio_id));
+g_assert_cmpuint(get_clock_period(global_qtest, path), ==,
+ SYSCLK_PERIOD);
+}
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -556,6 +577,8 @@ int main(int argc, char **argv)
 qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr2",
 test_data(GPIO_D, 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();
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index 506ca08bc2..8eaffe43ea 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -10,6 +10,7 @@
 
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
+#include "stm32l4x5.h"
 
 #define SYSCFG_BASE_ADDR 0x4001
 #define SYSCFG_MEMRMP 0x00
@@ -26,7 +27,9 @@
 #define INVALID_ADDR 0x2C
 
 /* SoC forwards GPIOs to SysCfg */
-#define SYSCFG "/machine/soc"
+#define SOC "/machine/soc"
+#define SYSCFG "/machine/soc/syscfg"
+#define SYSCFG_CLK "/machine/soc/syscfg/clk"
 #define EXTI "/machine/soc/exti"
 
 static void syscfg_writel(unsigned int offset, uint32_t value)
@@ -41,7 +44,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
+   qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -301,6 +304,17 @@ static void test_irq_gpio_multiplexer(void)
 syscfg_writel(SYSCFG_EXTICR1, 0x);
 }
 
+static void test_clock_enable(void)
+{
+g_assert_cmpuint(get_clock_period(global_qtest, SYSCFG_CLK), ==, 0);
+
+/* Enable SYSCFG 

[PATCH v3 1/4] hw/misc: Create STM32L4x5 SYSCFG clock

2024-05-23 Thread Inès Varhol
This commit creates a clock in STM32L4x5 SYSCFG and wires it up to the
corresponding clock from STM32L4x5 RCC.

Signed-off-by: Inès Varhol 
---
 include/hw/misc/stm32l4x5_syscfg.h |  1 +
 hw/arm/stm32l4x5_soc.c |  2 ++
 hw/misc/stm32l4x5_syscfg.c | 19 +--
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/include/hw/misc/stm32l4x5_syscfg.h 
b/include/hw/misc/stm32l4x5_syscfg.h
index 23bb564150..c450df2b9e 100644
--- a/include/hw/misc/stm32l4x5_syscfg.h
+++ b/include/hw/misc/stm32l4x5_syscfg.h
@@ -48,6 +48,7 @@ struct Stm32l4x5SyscfgState {
 uint32_t swpr2;
 
 qemu_irq gpio_out[GPIO_NUM_PINS];
+Clock *clk;
 };
 
 #endif
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 38f7a2d5d9..fb2afa6cfe 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -236,6 +236,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>syscfg);
+qdev_connect_clock_in(DEVICE(>syscfg), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), "syscfg-out"));
 if (!sysbus_realize(busdev, errp)) {
 return;
 }
diff --git a/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
index a5a1ce2680..a947a9e036 100644
--- a/hw/misc/stm32l4x5_syscfg.c
+++ b/hw/misc/stm32l4x5_syscfg.c
@@ -26,6 +26,9 @@
 #include "trace.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
+#include "hw/clock.h"
+#include "hw/qdev-clock.h"
+#include "qapi/error.h"
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
 
@@ -225,12 +228,22 @@ static void stm32l4x5_syscfg_init(Object *obj)
 qdev_init_gpio_in(DEVICE(obj), stm32l4x5_syscfg_set_irq,
   GPIO_NUM_PINS * NUM_GPIOS);
 qdev_init_gpio_out(DEVICE(obj), s->gpio_out, GPIO_NUM_PINS);
+s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+}
+
+static void stm32l4x5_syscfg_realize(DeviceState *dev, Error **errp)
+{
+Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(dev);
+if (!clock_has_source(s->clk)) {
+error_setg(errp, "SYSCFG: clk input must be connected");
+return;
+}
 }
 
 static const VMStateDescription vmstate_stm32l4x5_syscfg = {
 .name = TYPE_STM32L4X5_SYSCFG,
-.version_id = 1,
-.minimum_version_id = 1,
+.version_id = 2,
+.minimum_version_id = 2,
 .fields = (VMStateField[]) {
 VMSTATE_UINT32(memrmp, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(cfgr1, Stm32l4x5SyscfgState),
@@ -241,6 +254,7 @@ static const VMStateDescription vmstate_stm32l4x5_syscfg = {
 VMSTATE_UINT32(swpr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(skr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(swpr2, Stm32l4x5SyscfgState),
+VMSTATE_CLOCK(clk, Stm32l4x5SyscfgState),
 VMSTATE_END_OF_LIST()
 }
 };
@@ -251,6 +265,7 @@ static void stm32l4x5_syscfg_class_init(ObjectClass *klass, 
void *data)
 ResettableClass *rc = RESETTABLE_CLASS(klass);
 
 dc->vmsd = _stm32l4x5_syscfg;
+dc->realize = stm32l4x5_syscfg_realize;
 rc->phases.hold = stm32l4x5_syscfg_hold_reset;
 }
 
-- 
2.43.2




[PATCH v3 3/4] hw/clock: Expose 'qtest-clock-period' QOM property for QTests

2024-05-23 Thread Inès Varhol
Expose the clock period via the QOM 'qtest-clock-period' property so it
can be used in QTests. This property is only accessible in QTests (not
via HMP).

Signed-off-by: Philippe Mathieu-Daudé 
Signed-off-by: Inès Varhol 
---
 docs/devel/clocks.rst |  3 +++
 hw/core/clock.c   | 16 
 2 files changed, 19 insertions(+)

diff --git a/docs/devel/clocks.rst b/docs/devel/clocks.rst
index 177ee1c90d..19e67601ec 100644
--- a/docs/devel/clocks.rst
+++ b/docs/devel/clocks.rst
@@ -358,6 +358,9 @@ humans (for instance in debugging), use 
``clock_display_freq()``,
 which returns a prettified string-representation, e.g. "33.3 MHz".
 The caller must free the string with g_free() after use.
 
+It's also possible to retrieve the clock period from a QTest by
+accessing QOM property ``qtest-clock-period`` using a QMP command.
+
 Calculating expiry deadlines
 
 
diff --git a/hw/core/clock.c b/hw/core/clock.c
index e212865307..216b54b8df 100644
--- a/hw/core/clock.c
+++ b/hw/core/clock.c
@@ -13,6 +13,8 @@
 
 #include "qemu/osdep.h"
 #include "qemu/cutils.h"
+#include "qapi/visitor.h"
+#include "sysemu/qtest.h"
 #include "hw/clock.h"
 #include "trace.h"
 
@@ -158,6 +160,15 @@ bool clock_set_mul_div(Clock *clk, uint32_t multiplier, 
uint32_t divider)
 return true;
 }
 
+static void clock_period_prop_get(Object *obj, Visitor *v, const char *name,
+void *opaque, Error **errp)
+{
+Clock *clk = CLOCK(obj);
+uint64_t freq_hz = clock_get(clk);
+visit_type_uint64(v, name, _hz, errp);
+}
+
+
 static void clock_initfn(Object *obj)
 {
 Clock *clk = CLOCK(obj);
@@ -166,6 +177,11 @@ static void clock_initfn(Object *obj)
 clk->divider = 1;
 
 QLIST_INIT(>children);
+
+if (qtest_enabled()) {
+object_property_add(obj, "qtest-clock-period", "uint64",
+clock_period_prop_get, NULL, NULL, NULL);
+}
 }
 
 static void clock_finalizefn(Object *obj)
-- 
2.43.2




[PATCH v3 2/4] hw/char: Use v2 VMStateDescription for STM32L4x5 USART

2024-05-23 Thread Inès Varhol
`vmstate_stm32l4x5_usart_base` namely uses `VMSTATE_CLOCK` so
version needs to be 2.

Signed-off-by: Inès Varhol 
---
 hw/char/stm32l4x5_usart.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c
index 02f666308c..0f16f0917a 100644
--- a/hw/char/stm32l4x5_usart.c
+++ b/hw/char/stm32l4x5_usart.c
@@ -546,8 +546,8 @@ static int stm32l4x5_usart_base_post_load(void *opaque, int 
version_id)
 
 static const VMStateDescription vmstate_stm32l4x5_usart_base = {
 .name = TYPE_STM32L4X5_USART_BASE,
-.version_id = 1,
-.minimum_version_id = 1,
+.version_id = 2,
+.minimum_version_id = 2,
 .post_load = stm32l4x5_usart_base_post_load,
 .fields = (VMStateField[]) {
 VMSTATE_UINT32(cr1, Stm32l4x5UsartBaseState),
-- 
2.43.2




[PATCH v2 3/3] hw/arm: In STM32L4x5 SOC, connect USART devices to EXTI

2024-05-22 Thread Inès Varhol
The USART devices were previously connecting their outbound IRQs
directly to the CPU because the EXTI wasn't handling direct lines
interrupts.
Now the USART connects to the EXTI inbound GPIOs, and the EXTI connects
its IRQs to the CPU.
The existing QTest for the USART (tests/qtest/stm32l4x5_usart-test.c)
checks that USART1_IRQ in the CPU is pending when expected so it
confirms that the connection through the EXTI still works.

Signed-off-by: Inès Varhol 
Reviewed-by: Peter Maydell 
---
 hw/arm/stm32l4x5_soc.c | 24 +++-
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 38f7a2d5d9..fac83d349c 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -81,6 +81,10 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+#define EXTI_USART1_IRQ 26
+#define EXTI_UART4_IRQ 29
+#define EXTI_LPUART1_IRQ 31
+
 static const int exti_or_gates_out[NUM_EXTI_OR_GATES] = {
 23, 40, 63, 1,
 };
@@ -129,10 +133,6 @@ static const hwaddr uart_addr[] = {
 
 #define LPUART_BASE_ADDRESS 0x40008000
 
-static const int usart_irq[] = { 37, 38, 39 };
-static const int uart_irq[] = { 52, 53 };
-#define LPUART_IRQ 70
-
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -297,6 +297,7 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+/* Connect SYSCFG to EXTI */
 for (unsigned i = 0; i < GPIO_NUM_PINS; i++) {
 qdev_connect_gpio_out(DEVICE(>syscfg), i,
   qdev_get_gpio_in(DEVICE(>exti), i));
@@ -322,15 +323,10 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, usart_addr[i]);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_USART1_IRQ + i));
 }
 
-/*
- * TODO: Connect the USARTs, UARTs and LPUART to the EXTI once the EXTI
- * can handle other gpio-in than the gpios. (e.g. Direct Lines for the
- * usarts)
- */
-
 /* UART devices */
 for (int i = 0; i < STM_NUM_UARTS; i++) {
 g_autofree char *name = g_strdup_printf("uart%d-out", STM_NUM_USARTS + 
i + 1);
@@ -343,7 +339,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, uart_addr[i]);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, uart_irq[i]));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_UART4_IRQ + i));
 }
 
 /* LPUART device*/
@@ -356,7 +353,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, LPUART_BASE_ADDRESS);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, LPUART_IRQ));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_LPUART1_IRQ));
 
 /* APB1 BUS */
 create_unimplemented_device("TIM2",  0x4000, 0x400);
-- 
2.43.2




[PATCH v2 0/3] Connect STM32L4x5 USART devices to the EXTI

2024-05-22 Thread Inès Varhol
STM32L4x5 EXTI was handling only configurable interrupts
(such as those coming from STM32L4x5 SYSCFG which was the
only device connected to the EXTI).
This patch adds support for direct line interrupts and
connects the existing STM32L4x5 USART devices to the EXTI.
The patch also corrects the handling of configurable line
interrupts in the EXTI.

Changes from v1 (2nd commit):
- add STM32L4x5 EXTI status fields `irq_levels` to track
configurable irq levels and do edge detection
- use `qemu_set_irq` instead of qemu_irq_raise/lower

Signed-off-by: Inès Varhol 

Inès Varhol (3):
  hw/misc: In STM32L4x5 EXTI, consolidate 2 constants
  hw/misc: In STM32L4x5 EXTI, handle direct and configurable interrupts
  hw/arm: In STM32L4x5 SOC, connect USART devices to EXTI

 include/hw/misc/stm32l4x5_exti.h |  6 --
 hw/arm/stm32l4x5_soc.c   | 24 +++-
 hw/misc/stm32l4x5_exti.c | 31 ++-
 3 files changed, 37 insertions(+), 24 deletions(-)

-- 
2.43.2




[PATCH v2 1/3] hw/misc: In STM32L4x5 EXTI, consolidate 2 constants

2024-05-22 Thread Inès Varhol
Up until now, the EXTI implementation had 16 inbound GPIOs connected to
the 16 outbound GPIOs of STM32L4x5 SYSCFG.
The EXTI actually handles 40 lines (namely 5 from STM32L4x5 USART
devices which are already implemented in QEMU).
In order to connect USART devices to EXTI, this commit consolidates
constants `EXTI_NUM_INTERRUPT_OUT_LINES` (40) and
`EXTI_NUM_GPIO_EVENT_IN_LINES` (16) into `EXTI_NUM_LINES` (40).

Signed-off-by: Inès Varhol 
Reviewed-by: Peter Maydell 
---
 include/hw/misc/stm32l4x5_exti.h | 4 ++--
 hw/misc/stm32l4x5_exti.c | 6 ++
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/include/hw/misc/stm32l4x5_exti.h b/include/hw/misc/stm32l4x5_exti.h
index be961d2f01..82f75a2417 100644
--- a/include/hw/misc/stm32l4x5_exti.h
+++ b/include/hw/misc/stm32l4x5_exti.h
@@ -30,7 +30,7 @@
 #define TYPE_STM32L4X5_EXTI "stm32l4x5-exti"
 OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5ExtiState, STM32L4X5_EXTI)
 
-#define EXTI_NUM_INTERRUPT_OUT_LINES 40
+#define EXTI_NUM_LINES 40
 #define EXTI_NUM_REGISTER 2
 
 struct Stm32l4x5ExtiState {
@@ -45,7 +45,7 @@ struct Stm32l4x5ExtiState {
 uint32_t swier[EXTI_NUM_REGISTER];
 uint32_t pr[EXTI_NUM_REGISTER];
 
-qemu_irq irq[EXTI_NUM_INTERRUPT_OUT_LINES];
+qemu_irq irq[EXTI_NUM_LINES];
 };
 
 #endif
diff --git a/hw/misc/stm32l4x5_exti.c b/hw/misc/stm32l4x5_exti.c
index 495a0004ab..eebefc6cd3 100644
--- a/hw/misc/stm32l4x5_exti.c
+++ b/hw/misc/stm32l4x5_exti.c
@@ -42,7 +42,6 @@
 #define EXTI_SWIER2 0x30
 #define EXTI_PR20x34
 
-#define EXTI_NUM_GPIO_EVENT_IN_LINES 16
 #define EXTI_MAX_IRQ_PER_BANK 32
 #define EXTI_IRQS_BANK0  32
 #define EXTI_IRQS_BANK1  8
@@ -241,7 +240,7 @@ static void stm32l4x5_exti_init(Object *obj)
 {
 Stm32l4x5ExtiState *s = STM32L4X5_EXTI(obj);
 
-for (size_t i = 0; i < EXTI_NUM_INTERRUPT_OUT_LINES; i++) {
+for (size_t i = 0; i < EXTI_NUM_LINES; i++) {
 sysbus_init_irq(SYS_BUS_DEVICE(obj), >irq[i]);
 }
 
@@ -249,8 +248,7 @@ static void stm32l4x5_exti_init(Object *obj)
   TYPE_STM32L4X5_EXTI, 0x400);
 sysbus_init_mmio(SYS_BUS_DEVICE(obj), >mmio);
 
-qdev_init_gpio_in(DEVICE(obj), stm32l4x5_exti_set_irq,
-  EXTI_NUM_GPIO_EVENT_IN_LINES);
+qdev_init_gpio_in(DEVICE(obj), stm32l4x5_exti_set_irq, EXTI_NUM_LINES);
 }
 
 static const VMStateDescription vmstate_stm32l4x5_exti = {
-- 
2.43.2




[PATCH v2 2/3] hw/misc: In STM32L4x5 EXTI, handle direct and configurable interrupts

2024-05-22 Thread Inès Varhol
The previous implementation for EXTI interrupts only handled
"configurable" interrupts, like those originating from STM32L4x5 SYSCFG
(the only device currently connected to the EXTI up until now).

In order to connect STM32L4x5 USART to the EXTI, this commit adds
handling for direct interrupts (interrupts without configurable edge).

The implementation of configurable interrupts (interrupts supporting
edge selection) was incorrectly expecting alternating input levels :
this commits adds a new status field `irq_levels` to actually detect
edges.

Signed-off-by: Inès Varhol 
---
 include/hw/misc/stm32l4x5_exti.h |  2 ++
 hw/misc/stm32l4x5_exti.c | 25 -
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/include/hw/misc/stm32l4x5_exti.h b/include/hw/misc/stm32l4x5_exti.h
index 82f75a2417..62f79362f2 100644
--- a/include/hw/misc/stm32l4x5_exti.h
+++ b/include/hw/misc/stm32l4x5_exti.h
@@ -45,6 +45,8 @@ struct Stm32l4x5ExtiState {
 uint32_t swier[EXTI_NUM_REGISTER];
 uint32_t pr[EXTI_NUM_REGISTER];
 
+/* used for edge detection */
+uint32_t irq_levels[EXTI_NUM_REGISTER];
 qemu_irq irq[EXTI_NUM_LINES];
 };
 
diff --git a/hw/misc/stm32l4x5_exti.c b/hw/misc/stm32l4x5_exti.c
index eebefc6cd3..bdc3dc10d6 100644
--- a/hw/misc/stm32l4x5_exti.c
+++ b/hw/misc/stm32l4x5_exti.c
@@ -87,6 +87,7 @@ static void stm32l4x5_exti_reset_hold(Object *obj, ResetType 
type)
 s->ftsr[bank] = 0x;
 s->swier[bank] = 0x;
 s->pr[bank] = 0x;
+s->irq_levels[bank] = 0x;
 }
 }
 
@@ -106,17 +107,30 @@ static void stm32l4x5_exti_set_irq(void *opaque, int irq, 
int level)
 return;
 }
 
+/* In case of a direct line interrupt */
+if (extract32(exti_romask[bank], irq, 1)) {
+qemu_set_irq(s->irq[oirq], level);
+return;
+}
+
+/* In case of a configurable interrupt */
 if (((1 << irq) & s->rtsr[bank]) && level) {
 /* Rising Edge */
-s->pr[bank] |= 1 << irq;
-qemu_irq_pulse(s->irq[oirq]);
+if (!extract32(s->irq_levels[bank], irq, 1)) {
+s->pr[bank] |= 1 << irq;
+qemu_irq_pulse(s->irq[oirq]);
+}
+s->irq_levels[bank] |= 1 << irq;
 } else if (((1 << irq) & s->ftsr[bank]) && !level) {
 /* Falling Edge */
-s->pr[bank] |= 1 << irq;
-qemu_irq_pulse(s->irq[oirq]);
+if (extract32(s->irq_levels[bank], irq, 1)) {
+s->pr[bank] |= 1 << irq;
+qemu_irq_pulse(s->irq[oirq]);
+}
+s->irq_levels[bank] &= ~(1 << irq);
 }
 /*
- * In the following situations :
+ * In the following situations (for configurable interrupts) :
  * - falling edge but rising trigger selected
  * - rising edge but falling trigger selected
  * - no trigger selected
@@ -262,6 +276,7 @@ static const VMStateDescription vmstate_stm32l4x5_exti = {
 VMSTATE_UINT32_ARRAY(ftsr, Stm32l4x5ExtiState, EXTI_NUM_REGISTER),
 VMSTATE_UINT32_ARRAY(swier, Stm32l4x5ExtiState, EXTI_NUM_REGISTER),
 VMSTATE_UINT32_ARRAY(pr, Stm32l4x5ExtiState, EXTI_NUM_REGISTER),
+VMSTATE_UINT32_ARRAY(irq_levels, Stm32l4x5ExtiState, 
EXTI_NUM_REGISTER),
 VMSTATE_END_OF_LIST()
 }
 };
-- 
2.43.2




[PATCH 0/3] Connect STM32L4x5 USART devices to the EXTI

2024-05-12 Thread Inès Varhol
STM32L4x5 EXTI was handling only configurable interrupts
(such as those coming from STM32L4x5 SYSCFG which was the
only device connected to the EXTI).
This patch adds support for direct line interrupts and
connects the existing STM32L4x5 USART devices to the EXTI.

Signed-off-by: Inès Varhol 

Inès Varhol (3):
  hw/misc: In STM32L4x5 EXTI, consolidate 2 constants
  hw/misc: In STM32L4x5 EXTI, handle direct line interrupts
  hw/arm: In STM32L4x5 SOC, connect USART devices to EXTI

 include/hw/misc/stm32l4x5_exti.h |  4 ++--
 hw/arm/stm32l4x5_soc.c   | 24 +++-
 hw/misc/stm32l4x5_exti.c | 29 -
 3 files changed, 37 insertions(+), 20 deletions(-)

-- 
2.43.2




[PATCH 3/3] hw/arm: In STM32L4x5 SOC, connect USART devices to EXTI

2024-05-12 Thread Inès Varhol
The USART devices were previously connecting their outbound IRQs
directly to the CPU because the EXTI wasn't handling direct lines
interrupts.
Now the USART connects to the EXTI inbound GPIOs, and the EXTI connects
its IRQs to the CPU.
The existing QTest for the USART (tests/qtest/stm32l4x5_usart-test.c)
checks that USART1_IRQ in the CPU is pending when expected so it
confirms that the connection through the EXTI still works.

Signed-off-by: Inès Varhol 
---
 hw/arm/stm32l4x5_soc.c | 24 +++-
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 38f7a2d5d9..fac83d349c 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -81,6 +81,10 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+#define EXTI_USART1_IRQ 26
+#define EXTI_UART4_IRQ 29
+#define EXTI_LPUART1_IRQ 31
+
 static const int exti_or_gates_out[NUM_EXTI_OR_GATES] = {
 23, 40, 63, 1,
 };
@@ -129,10 +133,6 @@ static const hwaddr uart_addr[] = {
 
 #define LPUART_BASE_ADDRESS 0x40008000
 
-static const int usart_irq[] = { 37, 38, 39 };
-static const int uart_irq[] = { 52, 53 };
-#define LPUART_IRQ 70
-
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -297,6 +297,7 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+/* Connect SYSCFG to EXTI */
 for (unsigned i = 0; i < GPIO_NUM_PINS; i++) {
 qdev_connect_gpio_out(DEVICE(>syscfg), i,
   qdev_get_gpio_in(DEVICE(>exti), i));
@@ -322,15 +323,10 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, usart_addr[i]);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_USART1_IRQ + i));
 }
 
-/*
- * TODO: Connect the USARTs, UARTs and LPUART to the EXTI once the EXTI
- * can handle other gpio-in than the gpios. (e.g. Direct Lines for the
- * usarts)
- */
-
 /* UART devices */
 for (int i = 0; i < STM_NUM_UARTS; i++) {
 g_autofree char *name = g_strdup_printf("uart%d-out", STM_NUM_USARTS + 
i + 1);
@@ -343,7 +339,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, uart_addr[i]);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, uart_irq[i]));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_UART4_IRQ + i));
 }
 
 /* LPUART device*/
@@ -356,7 +353,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, LPUART_BASE_ADDRESS);
-sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, LPUART_IRQ));
+sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(DEVICE(>exti),
+   EXTI_LPUART1_IRQ));
 
 /* APB1 BUS */
 create_unimplemented_device("TIM2",  0x4000, 0x400);
-- 
2.43.2




[PATCH 2/3] hw/misc: In STM32L4x5 EXTI, handle direct line interrupts

2024-05-12 Thread Inès Varhol
The previous implementation for EXTI interrupts only handled
"configurable" interrupts, like those originating from STM32L4x5 SYSCFG
(the only device currently connected to the EXTI up until now).
In order to connect STM32L4x5 USART to the EXTI, this commit adds
handling for direct interrupts (interrupts without configurable edge),
as well as a comment that will be useful to connect other devices to the
EXTI.

Signed-off-by: Inès Varhol 
---
 hw/misc/stm32l4x5_exti.c | 23 ++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/hw/misc/stm32l4x5_exti.c b/hw/misc/stm32l4x5_exti.c
index eebefc6cd3..1817bbdad2 100644
--- a/hw/misc/stm32l4x5_exti.c
+++ b/hw/misc/stm32l4x5_exti.c
@@ -106,6 +106,27 @@ static void stm32l4x5_exti_set_irq(void *opaque, int irq, 
int level)
 return;
 }
 
+/* In case of a direct line interrupt */
+if (extract32(exti_romask[bank], irq, 1)) {
+if (level) {
+qemu_irq_raise(s->irq[oirq]);
+} else {
+qemu_irq_lower(s->irq[oirq]);
+}
+return;
+}
+
+/*
+ * In case of a configurable interrupt
+ *
+ * Note that while the real EXTI uses edge detection to tell
+ * apart a line rising (the level changes from 0 to 1) and a line
+ * staying high (the level was 1 and is set to 1), the current
+ * implementation relies on the fact that this handler will only
+ * be called when there's a level change. That means that the
+ * devices creating a configurable interrupt (like STM32L4x5 GPIO)
+ * have to set their IRQs only on a change.
+ */
 if (((1 << irq) & s->rtsr[bank]) && level) {
 /* Rising Edge */
 s->pr[bank] |= 1 << irq;
@@ -116,7 +137,7 @@ static void stm32l4x5_exti_set_irq(void *opaque, int irq, 
int level)
 qemu_irq_pulse(s->irq[oirq]);
 }
 /*
- * In the following situations :
+ * In the following situations (for configurable interrupts) :
  * - falling edge but rising trigger selected
  * - rising edge but falling trigger selected
  * - no trigger selected
-- 
2.43.2




[PATCH 1/3] hw/misc: In STM32L4x5 EXTI, consolidate 2 constants

2024-05-12 Thread Inès Varhol
Up until now, the EXTI implementation had 16 inbound GPIOs connected to
the 16 outbound GPIOs of STM32L4x5 SYSCFG.
The EXTI actually handles 40 lines (namely 5 from STM32L4x5 USART
devices which are already implemented in QEMU).
In order to connect USART devices to EXTI, this commit consolidates
constants `EXTI_NUM_INTERRUPT_OUT_LINES` (40) and
`EXTI_NUM_GPIO_EVENT_IN_LINES` (16) into `EXTI_NUM_LINES` (40).

Signed-off-by: Inès Varhol 
---
 include/hw/misc/stm32l4x5_exti.h | 4 ++--
 hw/misc/stm32l4x5_exti.c | 6 ++
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/include/hw/misc/stm32l4x5_exti.h b/include/hw/misc/stm32l4x5_exti.h
index be961d2f01..82f75a2417 100644
--- a/include/hw/misc/stm32l4x5_exti.h
+++ b/include/hw/misc/stm32l4x5_exti.h
@@ -30,7 +30,7 @@
 #define TYPE_STM32L4X5_EXTI "stm32l4x5-exti"
 OBJECT_DECLARE_SIMPLE_TYPE(Stm32l4x5ExtiState, STM32L4X5_EXTI)
 
-#define EXTI_NUM_INTERRUPT_OUT_LINES 40
+#define EXTI_NUM_LINES 40
 #define EXTI_NUM_REGISTER 2
 
 struct Stm32l4x5ExtiState {
@@ -45,7 +45,7 @@ struct Stm32l4x5ExtiState {
 uint32_t swier[EXTI_NUM_REGISTER];
 uint32_t pr[EXTI_NUM_REGISTER];
 
-qemu_irq irq[EXTI_NUM_INTERRUPT_OUT_LINES];
+qemu_irq irq[EXTI_NUM_LINES];
 };
 
 #endif
diff --git a/hw/misc/stm32l4x5_exti.c b/hw/misc/stm32l4x5_exti.c
index 495a0004ab..eebefc6cd3 100644
--- a/hw/misc/stm32l4x5_exti.c
+++ b/hw/misc/stm32l4x5_exti.c
@@ -42,7 +42,6 @@
 #define EXTI_SWIER2 0x30
 #define EXTI_PR20x34
 
-#define EXTI_NUM_GPIO_EVENT_IN_LINES 16
 #define EXTI_MAX_IRQ_PER_BANK 32
 #define EXTI_IRQS_BANK0  32
 #define EXTI_IRQS_BANK1  8
@@ -241,7 +240,7 @@ static void stm32l4x5_exti_init(Object *obj)
 {
 Stm32l4x5ExtiState *s = STM32L4X5_EXTI(obj);
 
-for (size_t i = 0; i < EXTI_NUM_INTERRUPT_OUT_LINES; i++) {
+for (size_t i = 0; i < EXTI_NUM_LINES; i++) {
 sysbus_init_irq(SYS_BUS_DEVICE(obj), >irq[i]);
 }
 
@@ -249,8 +248,7 @@ static void stm32l4x5_exti_init(Object *obj)
   TYPE_STM32L4X5_EXTI, 0x400);
 sysbus_init_mmio(SYS_BUS_DEVICE(obj), >mmio);
 
-qdev_init_gpio_in(DEVICE(obj), stm32l4x5_exti_set_irq,
-  EXTI_NUM_GPIO_EVENT_IN_LINES);
+qdev_init_gpio_in(DEVICE(obj), stm32l4x5_exti_set_irq, EXTI_NUM_LINES);
 }
 
 static const VMStateDescription vmstate_stm32l4x5_exti = {
-- 
2.43.2




[PATCH 3/4] hw/char: Add QOM property for STM32L4x5 USART clock frequency

2024-05-07 Thread Inès Varhol
This QOM property will be used to check the clock frequency from QTests.

Signed-off-by: Inès Varhol 
---
 hw/char/stm32l4x5_usart.c | 16 ++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c
index fc5dcac0c4..5fb3874f35 100644
--- a/hw/char/stm32l4x5_usart.c
+++ b/hw/char/stm32l4x5_usart.c
@@ -26,6 +26,7 @@
 #include "hw/clock.h"
 #include "hw/irq.h"
 #include "hw/qdev-clock.h"
+#include "qapi/visitor.h"
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "hw/registerfields.h"
@@ -523,6 +524,14 @@ static Property stm32l4x5_usart_base_properties[] = {
 DEFINE_PROP_END_OF_LIST(),
 };
 
+static void clock_freq_get(Object *obj, Visitor *v,
+const char *name, void *opaque, Error **errp)
+{
+Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
+uint32_t clock_freq_hz = clock_get_hz(s->clk);
+visit_type_uint32(v, name, _freq_hz, errp);
+}
+
 static void stm32l4x5_usart_base_init(Object *obj)
 {
 Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
@@ -534,6 +543,9 @@ static void stm32l4x5_usart_base_init(Object *obj)
 sysbus_init_mmio(SYS_BUS_DEVICE(obj), >mmio);
 
 s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+
+object_property_add(obj, "clock-freq-hz", "uint32",
+clock_freq_get, NULL, NULL, NULL);
 }
 
 static int stm32l4x5_usart_base_post_load(void *opaque, int version_id)
@@ -546,8 +558,8 @@ static int stm32l4x5_usart_base_post_load(void *opaque, int 
version_id)
 
 static const VMStateDescription vmstate_stm32l4x5_usart_base = {
 .name = TYPE_STM32L4X5_USART_BASE,
-.version_id = 1,
-.minimum_version_id = 1,
+.version_id = 2,
+.minimum_version_id = 2,
 .post_load = stm32l4x5_usart_base_post_load,
 .fields = (VMStateField[]) {
 VMSTATE_UINT32(cr1, Stm32l4x5UsartBaseState),
-- 
2.43.2




[PATCH 4/4] tests/qtest: Check STM32L4x5 clock connections

2024-05-07 Thread Inès Varhol
For USART, GPIO and SYSCFG devices, check that clock frequency before
and after enabling the peripheral clock in RCC is correct.

Signed-off-by: Inès Varhol 
---
 tests/qtest/stm32l4x5.h | 40 +
 tests/qtest/stm32l4x5_gpio-test.c   | 23 +
 tests/qtest/stm32l4x5_syscfg-test.c | 19 --
 tests/qtest/stm32l4x5_usart-test.c  | 26 +++
 4 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 tests/qtest/stm32l4x5.h

diff --git a/tests/qtest/stm32l4x5.h b/tests/qtest/stm32l4x5.h
new file mode 100644
index 00..b8ef6698b2
--- /dev/null
+++ b/tests/qtest/stm32l4x5.h
@@ -0,0 +1,40 @@
+/*
+ * QTest testcase header for STM32L4X5 :
+ * used for consolidating common objects in stm32l4x5_*-test.c
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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.h"
+
+/*
+ * MSI (4 MHz) is used as system clock source after startup
+ * from Reset.
+ * AHB, APB1 and APB2 prescalers are set to 1 at reset.
+ */
+#define SYSCLK_FREQ_HZ 400
+#define RCC_AHB2ENR 0x4002104C
+#define RCC_APB1ENR1 0x40021058
+#define RCC_APB1ENR2 0x4002105C
+#define RCC_APB2ENR 0x40021060
+
+
+static inline uint32_t get_clock_freq_hz(QTestState *qts, const char *path)
+{
+uint32_t clock_freq_hz = 0;
+QDict *r;
+
+r = qtest_qmp(qts, "{ '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;
+}
+
+
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 72a7823406..5c62125736 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -10,6 +10,7 @@
 
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
+#include "stm32l4x5.h"
 
 #define GPIO_BASE_ADDR 0x4800
 #define GPIO_SIZE  0x400
@@ -505,6 +506,26 @@ static void test_bsrr_brr(const void *data)
 gpio_writel(gpio, ODR, reset(gpio, ODR));
 }
 
+static void test_clock_enable(void)
+{
+/*
+ * For each GPIO, enable its clock in RCC
+ * and check that its clock frequency changes to SYSCLK_FREQ_HZ
+ */
+unsigned int gpio_id;
+
+for (uint32_t gpio = GPIO_A; gpio <= GPIO_H; gpio += GPIO_B - GPIO_A) {
+gpio_id = get_gpio_id(gpio);
+g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c",
+gpio_id + 'a');
+g_assert_cmpuint(get_clock_freq_hz(global_qtest, path), ==, 0);
+/* Enable the gpio clock */
+writel(RCC_AHB2ENR, readl(RCC_AHB2ENR) | (0x1 << gpio_id));
+g_assert_cmpuint(get_clock_freq_hz(global_qtest, path), ==,
+ SYSCLK_FREQ_HZ);
+}
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -556,6 +577,8 @@ int main(int argc, char **argv)
 qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr2",
 test_data(GPIO_D, 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();
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index 506ca08bc2..2ff0d5e9e0 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -10,6 +10,7 @@
 
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
+#include "stm32l4x5.h"
 
 #define SYSCFG_BASE_ADDR 0x4001
 #define SYSCFG_MEMRMP 0x00
@@ -26,7 +27,8 @@
 #define INVALID_ADDR 0x2C
 
 /* SoC forwards GPIOs to SysCfg */
-#define SYSCFG "/machine/soc"
+#define SOC "/machine/soc"
+#define SYSCFG "/machine/soc/syscfg"
 #define EXTI "/machine/soc/exti"
 
 static void syscfg_writel(unsigned int offset, uint32_t value)
@@ -41,7 +43,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
+   qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -301,6 +303,17 @@ static void test_irq_gpio_multiplexer(void)
 syscfg_writel(SYSCFG_EXTICR1, 0x);
 }
 
+static void test_clock_enable(void)
+{
+g_assert_cmpuint(get_clock_freq_hz(global_qtest, SYSCFG), ==, 0);
+
+/* Enable SYSCFG clock */
+writel(RCC_APB2ENR, readl(RCC_APB2ENR) | (0x1 << 0));
+
+g_assert_cmpuint(get_clock_freq_hz(global_qtest, SYSCFG), ==,
+  

[PATCH 0/4] Check clock connection between STM32L4x5 RCC and peripherals

2024-05-07 Thread Inès Varhol
Among implemented STM32L4x5 devices, USART, GPIO and SYSCFG
have a clock source, but none has a corresponding test in QEMU.

This patch makes sure that all 3 devices create a clock,
have a QOM property to access the clock frequency,
and adds QTests checking that clock enable in RCC has the
expected results.

Philippe Mathieu-Daudé suggested the following :
".. We could add the clock properties
directly in qdev_init_clock_in(). Seems useful for the QTest
framework."

However Peter Maydell pointed out the following :
"...Mostly "frequency" properties on devices are for the case
where they *don't* have a Clock input and instead have
ad-hoc legacy handling where the board/SoC that creates the
device sets an integer property to define the input frequency
because it doesn't model the clock tree with Clock objects."

You both agree on the fact that replicating the code in the
different devices is a bad idea, what should be the
alternative?

Thank you for the reviews.

Changes from v1:
- upgrading `VMStateDescription` to version 2 to account for
`VMSTATE_CLOCK()`
- QTests : consolidating `get_clock_freq_hz()` in a header
and making appropriate changes in stm32l4x5q_*-test.c

Signed-off-by: Inès Varhol 

Inès Varhol (4):
  hw/misc: Create STM32L4x5 SYSCFG clock
  hw/gpio: Handle clock migration in STM32L4x5 gpios
  hw/char: Add QOM property for STM32L4x5 USART clock frequency
  tests/qtest: Check STM32L4x5 clock connections

 include/hw/misc/stm32l4x5_syscfg.h  |  1 +
 tests/qtest/stm32l4x5.h | 40 +
 hw/arm/stm32l4x5_soc.c  |  2 ++
 hw/char/stm32l4x5_usart.c   | 16 ++--
 hw/gpio/stm32l4x5_gpio.c|  6 +++--
 hw/misc/stm32l4x5_syscfg.c  | 30 --
 tests/qtest/stm32l4x5_gpio-test.c   | 23 +
 tests/qtest/stm32l4x5_syscfg-test.c | 19 --
 tests/qtest/stm32l4x5_usart-test.c  | 26 +++
 9 files changed, 155 insertions(+), 8 deletions(-)
 create mode 100644 tests/qtest/stm32l4x5.h

-- 
2.43.2




[PATCH 2/4] hw/gpio: Handle clock migration in STM32L4x5 gpios

2024-05-07 Thread Inès Varhol
STM32L4x5 GPIO wasn't migrating its clock.

Signed-off-by: Inès Varhol 
---
 hw/gpio/stm32l4x5_gpio.c | 6 --
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/hw/gpio/stm32l4x5_gpio.c b/hw/gpio/stm32l4x5_gpio.c
index 71bf5fddb2..30d8d6cba4 100644
--- a/hw/gpio/stm32l4x5_gpio.c
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -20,6 +20,7 @@
 #include "qemu/log.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
 #include "hw/irq.h"
+#include "hw/clock.h"
 #include "hw/qdev-clock.h"
 #include "hw/qdev-properties.h"
 #include "qapi/visitor.h"
@@ -426,8 +427,8 @@ static void stm32l4x5_gpio_realize(DeviceState *dev, Error 
**errp)
 
 static const VMStateDescription vmstate_stm32l4x5_gpio = {
 .name = TYPE_STM32L4X5_GPIO,
-.version_id = 1,
-.minimum_version_id = 1,
+.version_id = 2,
+.minimum_version_id = 2,
 .fields = (VMStateField[]){
 VMSTATE_UINT32(moder, Stm32l4x5GpioState),
 VMSTATE_UINT32(otyper, Stm32l4x5GpioState),
@@ -441,6 +442,7 @@ static const VMStateDescription vmstate_stm32l4x5_gpio = {
 VMSTATE_UINT32(ascr, Stm32l4x5GpioState),
 VMSTATE_UINT16(disconnected_pins, Stm32l4x5GpioState),
 VMSTATE_UINT16(pins_connected_high, Stm32l4x5GpioState),
+VMSTATE_CLOCK(clk, Stm32l4x5GpioState),
 VMSTATE_END_OF_LIST()
 }
 };
-- 
2.43.2




[PATCH 1/4] hw/misc: Create STM32L4x5 SYSCFG clock

2024-05-07 Thread Inès Varhol
This commit creates a clock in STM32L4x5 SYSCFG and wires it up to the
corresponding clock from STM32L4x5 RCC.
A read-only QOM property allowing to read the clock frequency is added
(it will be used in a QTest).

Signed-off-by: Inès Varhol 
---

Hello,

Several people noticed that replicating the code in the
different devices is a bad idea (cf cover letter).
One proposition is to directly add the clock property
in `qdev_init_clock_in()`.
Would that be acceptable and are there other alternatives
(allowing to the clock frequency from a Qtest)?

Best regards,

Inès Varhol

 include/hw/misc/stm32l4x5_syscfg.h |  1 +
 hw/arm/stm32l4x5_soc.c |  2 ++
 hw/misc/stm32l4x5_syscfg.c | 30 --
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/include/hw/misc/stm32l4x5_syscfg.h 
b/include/hw/misc/stm32l4x5_syscfg.h
index 23bb564150..c450df2b9e 100644
--- a/include/hw/misc/stm32l4x5_syscfg.h
+++ b/include/hw/misc/stm32l4x5_syscfg.h
@@ -48,6 +48,7 @@ struct Stm32l4x5SyscfgState {
 uint32_t swpr2;
 
 qemu_irq gpio_out[GPIO_NUM_PINS];
+Clock *clk;
 };
 
 #endif
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 38f7a2d5d9..fb2afa6cfe 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -236,6 +236,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>syscfg);
+qdev_connect_clock_in(DEVICE(>syscfg), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), "syscfg-out"));
 if (!sysbus_realize(busdev, errp)) {
 return;
 }
diff --git a/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
index a5a1ce2680..7e6125383e 100644
--- a/hw/misc/stm32l4x5_syscfg.c
+++ b/hw/misc/stm32l4x5_syscfg.c
@@ -26,6 +26,10 @@
 #include "trace.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
+#include "hw/clock.h"
+#include "hw/qdev-clock.h"
+#include "qapi/visitor.h"
+#include "qapi/error.h"
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
 
@@ -202,6 +206,14 @@ static void stm32l4x5_syscfg_write(void *opaque, hwaddr 
addr,
 }
 }
 
+static void clock_freq_get(Object *obj, Visitor *v,
+const char *name, void *opaque, Error **errp)
+{
+Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(obj);
+uint32_t clock_freq_hz = clock_get_hz(s->clk);
+visit_type_uint32(v, name, _freq_hz, errp);
+}
+
 static const MemoryRegionOps stm32l4x5_syscfg_ops = {
 .read = stm32l4x5_syscfg_read,
 .write = stm32l4x5_syscfg_write,
@@ -225,12 +237,24 @@ static void stm32l4x5_syscfg_init(Object *obj)
 qdev_init_gpio_in(DEVICE(obj), stm32l4x5_syscfg_set_irq,
   GPIO_NUM_PINS * NUM_GPIOS);
 qdev_init_gpio_out(DEVICE(obj), s->gpio_out, GPIO_NUM_PINS);
+s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+object_property_add(obj, "clock-freq-hz", "uint32", clock_freq_get, NULL,
+NULL, NULL);
+}
+
+static void stm32l4x5_syscfg_realize(DeviceState *dev, Error **errp)
+{
+Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(dev);
+if (!clock_has_source(s->clk)) {
+error_setg(errp, "SYSCFG: clk input must be connected");
+return;
+}
 }
 
 static const VMStateDescription vmstate_stm32l4x5_syscfg = {
 .name = TYPE_STM32L4X5_SYSCFG,
-.version_id = 1,
-.minimum_version_id = 1,
+.version_id = 2,
+.minimum_version_id = 2,
 .fields = (VMStateField[]) {
 VMSTATE_UINT32(memrmp, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(cfgr1, Stm32l4x5SyscfgState),
@@ -241,6 +265,7 @@ static const VMStateDescription vmstate_stm32l4x5_syscfg = {
 VMSTATE_UINT32(swpr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(skr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(swpr2, Stm32l4x5SyscfgState),
+VMSTATE_CLOCK(clk, Stm32l4x5SyscfgState),
 VMSTATE_END_OF_LIST()
 }
 };
@@ -251,6 +276,7 @@ static void stm32l4x5_syscfg_class_init(ObjectClass *klass, 
void *data)
 ResettableClass *rc = RESETTABLE_CLASS(klass);
 
 dc->vmsd = _stm32l4x5_syscfg;
+dc->realize = stm32l4x5_syscfg_realize;
 rc->phases.hold = stm32l4x5_syscfg_hold_reset;
 }
 
-- 
2.43.2




Re: [PATCH 1/4] hw/misc: Create STM32L4x5 SYSCFG clock

2024-05-07 Thread Inès Varhol



- Le 7 Mai 24, à 11:50, peter maydell peter.mayd...@linaro.org a écrit :

> On Sun, 5 May 2024 at 15:06, Inès Varhol  wrote:
>>
>> Signed-off-by: Inès Varhol 
> 
> In general you should try to avoid commits with no commit message.
> Sometimes there really isn't anything to say beyond what the
> subject line is, but that should be the exception rather than
> the usual thing.

Hello,

Understood, I'll add messages.

> 
>> ---
>>  include/hw/misc/stm32l4x5_syscfg.h |  1 +
>>  hw/arm/stm32l4x5_soc.c |  2 ++
>>  hw/misc/stm32l4x5_syscfg.c | 26 ++
>>  3 files changed, 29 insertions(+)
>>
>> diff --git a/include/hw/misc/stm32l4x5_syscfg.h
>> b/include/hw/misc/stm32l4x5_syscfg.h
>> index 23bb564150..c450df2b9e 100644
>> --- a/include/hw/misc/stm32l4x5_syscfg.h
>> +++ b/include/hw/misc/stm32l4x5_syscfg.h
>> @@ -48,6 +48,7 @@ struct Stm32l4x5SyscfgState {
>>  uint32_t swpr2;
>>
>>  qemu_irq gpio_out[GPIO_NUM_PINS];
>> +Clock *clk;
>>  };
>>
>>  #endif
>> diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
>> index 38f7a2d5d9..fb2afa6cfe 100644
>> --- a/hw/arm/stm32l4x5_soc.c
>> +++ b/hw/arm/stm32l4x5_soc.c
>> @@ -236,6 +236,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc,
>> Error **errp)
>>
>>  /* System configuration controller */
>>  busdev = SYS_BUS_DEVICE(>syscfg);
>> +qdev_connect_clock_in(DEVICE(>syscfg), "clk",
>> +qdev_get_clock_out(DEVICE(&(s->rcc)), "syscfg-out"));
>>  if (!sysbus_realize(busdev, errp)) {
>>  return;
>>  }
>> diff --git a/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
>> index a5a1ce2680..a82864c33d 100644
>> --- a/hw/misc/stm32l4x5_syscfg.c
>> +++ b/hw/misc/stm32l4x5_syscfg.c
>> @@ -26,6 +26,10 @@
>>  #include "trace.h"
>>  #include "hw/irq.h"
>>  #include "migration/vmstate.h"
>> +#include "hw/clock.h"
>> +#include "hw/qdev-clock.h"
>> +#include "qapi/visitor.h"
>> +#include "qapi/error.h"
>>  #include "hw/misc/stm32l4x5_syscfg.h"
>>  #include "hw/gpio/stm32l4x5_gpio.h"
>>
>> @@ -202,6 +206,14 @@ static void stm32l4x5_syscfg_write(void *opaque, hwaddr
>> addr,
>>  }
>>  }
>>
>> +static void clock_freq_get(Object *obj, Visitor *v,
>> +const char *name, void *opaque, Error **errp)
>> +{
>> +Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(obj);
>> +uint32_t clock_freq_hz = clock_get_hz(s->clk);
>> +visit_type_uint32(v, name, _freq_hz, errp);
>> +}
>> +
>>  static const MemoryRegionOps stm32l4x5_syscfg_ops = {
>>  .read = stm32l4x5_syscfg_read,
>>  .write = stm32l4x5_syscfg_write,
>> @@ -225,6 +237,18 @@ static void stm32l4x5_syscfg_init(Object *obj)
>>  qdev_init_gpio_in(DEVICE(obj), stm32l4x5_syscfg_set_irq,
>>GPIO_NUM_PINS * NUM_GPIOS);
>>  qdev_init_gpio_out(DEVICE(obj), s->gpio_out, GPIO_NUM_PINS);
>> +s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
>> +object_property_add(obj, "clock-freq-hz", "uint32", clock_freq_get, 
>> NULL,
>> +NULL, NULL);
> 
> Why do we need this property? The clock on this device is an input,
> so the device doesn't control its frequency.

Using a QOM property allows to read the clock frequency from a QTest.
(npcm7xx_pwm-test.c does this, I didn't find other examples of reading a
frequency)

Best regards,

Inès Varhol




Re: [PATCH 4/4] tests/qtest: Check STM32L4x5 clock connections

2024-05-06 Thread Inès Varhol



- Le 6 Mai 24, à 6:16, Thomas Huth th...@redhat.com a écrit :

> On 05/05/2024 16.05, Inès Varhol wrote:
>> For USART, GPIO and SYSCFG devices, check that clock frequency before
>> and after enabling the peripheral clock in RCC is correct.
>> 
>> Signed-off-by: Inès Varhol 
>> ---
>> Hello,
>> 
>> Should these tests be regrouped in stm32l4x5_rcc-test.c ?
> 
>  Hi,
> 
> sounds mostly like a matter of taste at a first glance. Or what would be the
> benefit of putting everything into the *rcc-test.c file? Could you maybe
> consolidate the get_clock_freq_hz() function that way? (maybe that
> get_clock_freq_hz() function could also be consolidated as a inline function
> in a shared header instead?)
> 
>  Thomas

Hello,

I was indeed looking to consolidate the functions get_clock_freq_hz() and
check_clock() from *usart-test.c (along with the definitions for RCC
registers).
Thank you for your suggestion, I'll use a header file.

Inès Varhol




[PATCH] hw/char: Correct STM32L4x5 usart register CR2 field ADD_0 size

2024-05-05 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/char/stm32l4x5_usart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c
index 02f666308c..fc5dcac0c4 100644
--- a/hw/char/stm32l4x5_usart.c
+++ b/hw/char/stm32l4x5_usart.c
@@ -56,7 +56,7 @@ REG32(CR1, 0x00)
 FIELD(CR1, UE, 0, 1) /* USART enable */
 REG32(CR2, 0x04)
 FIELD(CR2, ADD_1, 28, 4)/* ADD[7:4] */
-FIELD(CR2, ADD_0, 24, 1)/* ADD[3:0] */
+FIELD(CR2, ADD_0, 24, 4)/* ADD[3:0] */
 FIELD(CR2, RTOEN, 23, 1)/* Receiver timeout enable */
 FIELD(CR2, ABRMOD, 21, 2)   /* Auto baud rate mode */
 FIELD(CR2, ABREN, 20, 1)/* Auto baud rate enable */
-- 
2.43.2




[PATCH 3/4] hw/char: Add QOM property for STM32L4x5 USART clock frequency

2024-05-05 Thread Inès Varhol
Signed-off-by: Inès Varhol 
---
 hw/char/stm32l4x5_usart.c | 12 
 1 file changed, 12 insertions(+)

diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c
index fc5dcac0c4..ee7727481c 100644
--- a/hw/char/stm32l4x5_usart.c
+++ b/hw/char/stm32l4x5_usart.c
@@ -26,6 +26,7 @@
 #include "hw/clock.h"
 #include "hw/irq.h"
 #include "hw/qdev-clock.h"
+#include "qapi/visitor.h"
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "hw/registerfields.h"
@@ -523,6 +524,14 @@ static Property stm32l4x5_usart_base_properties[] = {
 DEFINE_PROP_END_OF_LIST(),
 };
 
+static void clock_freq_get(Object *obj, Visitor *v,
+const char *name, void *opaque, Error **errp)
+{
+Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
+uint32_t clock_freq_hz = clock_get_hz(s->clk);
+visit_type_uint32(v, name, _freq_hz, errp);
+}
+
 static void stm32l4x5_usart_base_init(Object *obj)
 {
 Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
@@ -534,6 +543,9 @@ static void stm32l4x5_usart_base_init(Object *obj)
 sysbus_init_mmio(SYS_BUS_DEVICE(obj), >mmio);
 
 s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+
+object_property_add(obj, "clock-freq-hz", "uint32",
+clock_freq_get, NULL, NULL, NULL);
 }
 
 static int stm32l4x5_usart_base_post_load(void *opaque, int version_id)
-- 
2.43.2




[PATCH 1/4] hw/misc: Create STM32L4x5 SYSCFG clock

2024-05-05 Thread Inès Varhol
Signed-off-by: Inès Varhol 
---
 include/hw/misc/stm32l4x5_syscfg.h |  1 +
 hw/arm/stm32l4x5_soc.c |  2 ++
 hw/misc/stm32l4x5_syscfg.c | 26 ++
 3 files changed, 29 insertions(+)

diff --git a/include/hw/misc/stm32l4x5_syscfg.h 
b/include/hw/misc/stm32l4x5_syscfg.h
index 23bb564150..c450df2b9e 100644
--- a/include/hw/misc/stm32l4x5_syscfg.h
+++ b/include/hw/misc/stm32l4x5_syscfg.h
@@ -48,6 +48,7 @@ struct Stm32l4x5SyscfgState {
 uint32_t swpr2;
 
 qemu_irq gpio_out[GPIO_NUM_PINS];
+Clock *clk;
 };
 
 #endif
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 38f7a2d5d9..fb2afa6cfe 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -236,6 +236,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>syscfg);
+qdev_connect_clock_in(DEVICE(>syscfg), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), "syscfg-out"));
 if (!sysbus_realize(busdev, errp)) {
 return;
 }
diff --git a/hw/misc/stm32l4x5_syscfg.c b/hw/misc/stm32l4x5_syscfg.c
index a5a1ce2680..a82864c33d 100644
--- a/hw/misc/stm32l4x5_syscfg.c
+++ b/hw/misc/stm32l4x5_syscfg.c
@@ -26,6 +26,10 @@
 #include "trace.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
+#include "hw/clock.h"
+#include "hw/qdev-clock.h"
+#include "qapi/visitor.h"
+#include "qapi/error.h"
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
 
@@ -202,6 +206,14 @@ static void stm32l4x5_syscfg_write(void *opaque, hwaddr 
addr,
 }
 }
 
+static void clock_freq_get(Object *obj, Visitor *v,
+const char *name, void *opaque, Error **errp)
+{
+Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(obj);
+uint32_t clock_freq_hz = clock_get_hz(s->clk);
+visit_type_uint32(v, name, _freq_hz, errp);
+}
+
 static const MemoryRegionOps stm32l4x5_syscfg_ops = {
 .read = stm32l4x5_syscfg_read,
 .write = stm32l4x5_syscfg_write,
@@ -225,6 +237,18 @@ static void stm32l4x5_syscfg_init(Object *obj)
 qdev_init_gpio_in(DEVICE(obj), stm32l4x5_syscfg_set_irq,
   GPIO_NUM_PINS * NUM_GPIOS);
 qdev_init_gpio_out(DEVICE(obj), s->gpio_out, GPIO_NUM_PINS);
+s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
+object_property_add(obj, "clock-freq-hz", "uint32", clock_freq_get, NULL,
+NULL, NULL);
+}
+
+static void stm32l4x5_syscfg_realize(DeviceState *dev, Error **errp)
+{
+Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(dev);
+if (!clock_has_source(s->clk)) {
+error_setg(errp, "SYSCFG: clk input must be connected");
+return;
+}
 }
 
 static const VMStateDescription vmstate_stm32l4x5_syscfg = {
@@ -241,6 +265,7 @@ static const VMStateDescription vmstate_stm32l4x5_syscfg = {
 VMSTATE_UINT32(swpr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(skr, Stm32l4x5SyscfgState),
 VMSTATE_UINT32(swpr2, Stm32l4x5SyscfgState),
+VMSTATE_CLOCK(clk, Stm32l4x5SyscfgState),
 VMSTATE_END_OF_LIST()
 }
 };
@@ -251,6 +276,7 @@ static void stm32l4x5_syscfg_class_init(ObjectClass *klass, 
void *data)
 ResettableClass *rc = RESETTABLE_CLASS(klass);
 
 dc->vmsd = _stm32l4x5_syscfg;
+dc->realize = stm32l4x5_syscfg_realize;
 rc->phases.hold = stm32l4x5_syscfg_hold_reset;
 }
 
-- 
2.43.2




[PATCH 4/4] tests/qtest: Check STM32L4x5 clock connections

2024-05-05 Thread Inès Varhol
For USART, GPIO and SYSCFG devices, check that clock frequency before
and after enabling the peripheral clock in RCC is correct.

Signed-off-by: Inès Varhol 
---
Hello,

Should these tests be regrouped in stm32l4x5_rcc-test.c ?

Best regards,

Inès Varhol

 tests/qtest/stm32l4x5_gpio-test.c   | 39 +++
 tests/qtest/stm32l4x5_syscfg-test.c | 38 +--
 tests/qtest/stm32l4x5_usart-test.c  | 48 +
 3 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 72a7823406..896c16ad59 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -25,6 +25,14 @@
 #define GPIO_G 0x48001800
 #define GPIO_H 0x48001C00
 
+/*
+ * MSI (4 MHz) is used as system clock source after startup
+ * from Reset.
+ * AHB prescaler is set to 1 at reset.
+ */
+#define SYSCLK_FREQ_HZ 400
+#define RCC_AHB2ENR 0x4002104C
+
 #define MODER 0x00
 #define OTYPER 0x04
 #define PUPDR 0x0C
@@ -168,6 +176,21 @@ static uint32_t reset(uint32_t gpio, unsigned int offset)
 return 0x0;
 }
 
+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 void system_reset(void)
 {
 QDict *r;
@@ -505,6 +528,20 @@ static void test_bsrr_brr(const void *data)
 gpio_writel(gpio, ODR, reset(gpio, ODR));
 }
 
+static void test_clock_enable(void)
+{
+/*
+ * For each GPIO, enable its clock in RCC
+ * and check that its clock frequency changes to SYSCLK_FREQ_HZ
+ */
+for (uint32_t gpio = GPIO_A; gpio <= GPIO_H; gpio += GPIO_B - GPIO_A) {
+g_assert_cmpuint(get_clock_freq_hz(gpio), ==, 0);
+/* Enable the gpio clock */
+writel(RCC_AHB2ENR, readl(RCC_AHB2ENR) | (0x1 << get_gpio_id(gpio)));
+g_assert_cmpuint(get_clock_freq_hz(gpio), ==, SYSCLK_FREQ_HZ);
+}
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -556,6 +593,8 @@ int main(int argc, char **argv)
 qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr2",
 test_data(GPIO_D, 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();
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index 506ca08bc2..616106460d 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -26,9 +26,18 @@
 #define INVALID_ADDR 0x2C
 
 /* SoC forwards GPIOs to SysCfg */
-#define SYSCFG "/machine/soc"
+#define SOC "/machine/soc"
+#define SYSCFG "/machine/soc/syscfg"
 #define EXTI "/machine/soc/exti"
 
+/*
+ * MSI (4 MHz) is used as system clock source after startup
+ * from Reset.
+ * AHB and APB2 prescalers are set to 1 at reset.
+ */
+#define SYSCLK_FREQ_HZ 400
+#define RCC_APB2ENR 0x40021060
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -41,7 +50,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
+   qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -52,6 +61,19 @@ static void system_reset(void)
 qobject_unref(response);
 }
 
+static uint32_t get_clock_freq_hz()
+{
+uint32_t clock_freq_hz = 0;
+QDict *r;
+
+r = qtest_qmp(global_qtest, "{ 'execute': 'qom-get', 'arguments':"
+" { 'path': %s, 'property': 'clock-freq-hz'} }", SYSCFG);
+g_assert_false(qdict_haskey(r, "error"));
+clock_freq_hz = qdict_get_int(r, "return");
+qobject_unref(r);
+return clock_freq_hz;
+}
+
 static void test_reset(void)
 {
 /*
@@ -301,6 +323,16 @@ static void test_irq_gpio_multiplexer(void)
 syscfg_writel(SYSCFG_EXTICR1, 0x);
 }
 
+static void test_clock_enable(void)
+{
+g_assert_cmpuint(get_clock_freq_hz(), ==, 0);
+
+/* Enable SYSCFG clock */
+writel(RCC_APB2ENR, readl(RCC_APB2ENR) | (0x1 << 0));
+
+g_assert_cmpuint(get_clock_freq_hz(), ==, SYSCLK_FREQ_HZ);
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -325,6 +357,8 @@ int main(int argc, char **argv)
test_irq_pin_multiple

[PATCH 2/4] hw/gpio: Handle clock migration in STM32L4x5 gpios

2024-05-05 Thread Inès Varhol
Signed-off-by: Inès Varhol 
---
 hw/gpio/stm32l4x5_gpio.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hw/gpio/stm32l4x5_gpio.c b/hw/gpio/stm32l4x5_gpio.c
index 71bf5fddb2..14e6618d30 100644
--- a/hw/gpio/stm32l4x5_gpio.c
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -20,6 +20,7 @@
 #include "qemu/log.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
 #include "hw/irq.h"
+#include "hw/clock.h"
 #include "hw/qdev-clock.h"
 #include "hw/qdev-properties.h"
 #include "qapi/visitor.h"
@@ -441,6 +442,7 @@ static const VMStateDescription vmstate_stm32l4x5_gpio = {
 VMSTATE_UINT32(ascr, Stm32l4x5GpioState),
 VMSTATE_UINT16(disconnected_pins, Stm32l4x5GpioState),
 VMSTATE_UINT16(pins_connected_high, Stm32l4x5GpioState),
+VMSTATE_CLOCK(clk, Stm32l4x5GpioState),
 VMSTATE_END_OF_LIST()
 }
 };
-- 
2.43.2




[PATCH 0/4] Check clock connection between STM32L4x5 RCC and peripherals

2024-05-05 Thread Inès Varhol
Among implemented STM32L4x5 devices, USART, GPIO and SYSCFG
have a clock source, but none has a corresponding test in QEMU.

This patch makes sure that all 3 devices create a clock,
have a QOM property to access the clock frequency,
and adds QTests checking that clock enable in RCC has the
expected results.

Signed-off-by: Inès Varhol 

Inès Varhol (4):
  hw/misc: Create STM32L4x5 SYSCFG clock
  hw/gpio: Handle clock migration in STM32L4x5 gpios
  hw/char: Add QOM property for STM32L4x5 USART clock frequency
  tests/qtest: Check STM32L4x5 clock connections

 include/hw/misc/stm32l4x5_syscfg.h  |  1 +
 hw/arm/stm32l4x5_soc.c  |  2 ++
 hw/char/stm32l4x5_usart.c   | 12 
 hw/gpio/stm32l4x5_gpio.c|  2 ++
 hw/misc/stm32l4x5_syscfg.c  | 26 
 tests/qtest/stm32l4x5_gpio-test.c   | 39 +++
 tests/qtest/stm32l4x5_syscfg-test.c | 38 +--
 tests/qtest/stm32l4x5_usart-test.c  | 48 +
 8 files changed, 166 insertions(+), 2 deletions(-)

-- 
2.43.2




[PATCH v6 1/5] hw/display : Add device DM163

2024-04-24 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Acked-by: Alistair Francis 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 docs/system/arm/b-l475e-iot01a.rst |   3 +-
 include/hw/display/dm163.h |  59 +
 hw/display/dm163.c | 349 +
 hw/display/Kconfig |   3 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  14 ++
 6 files changed, 428 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c

diff --git a/docs/system/arm/b-l475e-iot01a.rst 
b/docs/system/arm/b-l475e-iot01a.rst
index 0afef8e4f4..91de5e82fc 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -12,13 +12,14 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of 
sensors.
 Supported devices
 """""""""""""""""
 
-Currently B-L475E-IOT01A machine's only supports the following devices:
+Currently B-L475E-IOT01A machines support the following devices:
 
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
 - STM32L4x5 GPIOs (General-purpose I/Os)
+- optional 8x8 led display (based on DM163 driver)
 
 Missing devices
 """""""""""""""
diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h
new file mode 100644
index 00..4377f77bb7
--- /dev/null
+++ b/include/hw/display/dm163.h
@@ -0,0 +1,59 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_DM163_H
+#define HW_DISPLAY_DM163_H
+
+#include "qom/object.h"
+#include "hw/qdev-core.h"
+
+#define TYPE_DM163 "dm163"
+OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163);
+
+#define RGB_MATRIX_NUM_ROWS 8
+#define RGB_MATRIX_NUM_COLS 8
+#define DM163_NUM_LEDS (RGB_MATRIX_NUM_COLS * 3)
+/* The last row is filled with 0 (turned off row) */
+#define COLOR_BUFFER_SIZE (RGB_MATRIX_NUM_ROWS + 1)
+
+typedef struct DM163State {
+DeviceState parent_obj;
+
+/* DM163 driver */
+uint64_t bank0_shift_register[3];
+uint64_t bank1_shift_register[3];
+uint16_t latched_outputs[DM163_NUM_LEDS];
+uint16_t outputs[DM163_NUM_LEDS];
+qemu_irq sout;
+
+uint8_t sin;
+uint8_t dck;
+uint8_t rst_b;
+uint8_t lat_b;
+uint8_t selbk;
+uint8_t en_b;
+
+/* IM120417002 colors shield */
+uint8_t activated_rows;
+
+/* 8x8 RGB matrix */
+QemuConsole *console;
+uint8_t redraw;
+/* Rows currently being displayed on the matrix. */
+/* The last row is filled with 0 (turned off row) */
+uint32_t buffer[COLOR_BUFFER_SIZE][RGB_MATRIX_NUM_COLS];
+uint8_t last_buffer_idx;
+uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS];
+/* Used to simulate retinal persistence of rows */
+uint8_t row_persistence_delay[RGB_MATRIX_NUM_ROWS];
+} DM163State;
+
+#endif /* HW_DISPLAY_DM163_H */
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..a5fbca1a0f
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,349 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTENCE 3
+#define TURNED_OFF_ROW (COLOR_BUFFER_SIZE - 1)
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3

[PATCH v6 2/5] hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC

2024-04-24 Thread Inès Varhol
Exposing SYSCFG inputs to the SoC is practical in order to wire the SoC
to the optional DM163 display from the board code (GPIOs outputs need
to be connected to both SYSCFG inputs and DM163 inputs).

STM32L4x5 SYSCFG in-irq interception needed to be changed accordingly.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/stm32l4x5_soc.c  |  6 --
 tests/qtest/stm32l4x5_gpio-test.c   | 13 -
 tests/qtest/stm32l4x5_syscfg-test.c | 17 ++---
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 40e294f838..0332b67701 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -1,8 +1,8 @@
 /*
  * STM32L4x5 SoC family
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2023-2024 Arnaud Minier 
+ * Copyright (c) 2023-2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -221,6 +221,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+qdev_pass_gpios(DEVICE(>syscfg), dev_soc, NULL);
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 0f6bda54d3..72a7823406 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -43,6 +43,9 @@
 #define OTYPER_PUSH_PULL 0
 #define OTYPER_OPEN_DRAIN 1
 
+/* SoC forwards GPIOs to SysCfg */
+#define SYSCFG "/machine/soc"
+
 const uint32_t moder_reset[NUM_GPIOS] = {
 0xABFF,
 0xFEBF,
@@ -284,7 +287,7 @@ static void test_gpio_output_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Set a bit in ODR and check nothing happens */
 gpio_set_bit(gpio, ODR, pin, 1);
@@ -319,7 +322,7 @@ static void test_gpio_input_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input, raise it, and check that the pin is high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -348,7 +351,7 @@ static void test_pull_up_pull_down(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input with pull-up, check the line is set high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -378,7 +381,7 @@ static void test_push_pull(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in push-pull output */
 /* And checking the pin was disconnected */
@@ -425,7 +428,7 @@ static void test_open_drain(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in open-drain output */
 /* And checking the pin was disconnected */
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index ed4801798d..733b42df55 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -1,8 +1,8 @@
 /*
  * QTest testcase for STM32L4x5_SYSCFG
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -25,6 +25,10 @@
 #define SYSCFG_SWPR2 0x28
 #define INVALID_ADDR 0x2C
 
+/* SoC forwards GPIOs to SysCfg */
+#define SYSCFG "/machine/soc"
+#define EXTI "/machine/soc/exti"
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -37,8 +41,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, "/machine/soc/syscfg",
-NULL, num, level);
+   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -197,7

[PATCH v6 5/5] tests/qtest : Add testcase for DM163

2024-04-24 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are propagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Acked-by: Thomas Huth 
Reviewed-by: Philippe Mathieu-Daudé 
---
 tests/qtest/dm163-test.c | 194 +++
 tests/qtest/meson.build  |   2 +
 2 files changed, 196 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..3161c9208d
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,194 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+enum DM163_INPUTS {
+SIN = 8,
+DCK = 9,
+RST_B = 10,
+LAT_B = 11,
+SELBK = 12,
+EN_B = 13
+};
+
+#define DEVICE_NAME "/machine/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const unsigned bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_g

[PATCH v6 3/5] hw/arm : Create Bl475eMachineState

2024-04-24 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/b-l475e-iot01a.c | 46 -
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index d862aa43fc..970c637ce6 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -2,8 +2,8 @@
  * B-L475E-IOT01A Discovery Kit machine
  * (B-L475E-IOT01A IoT Node)
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2023-2024 Arnaud Minier 
+ * Copyright (c) 2023-2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -32,33 +32,51 @@
 
 /* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
-static void b_l475e_iot01a_init(MachineState *machine)
+#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
+OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
+
+typedef struct Bl475eMachineState {
+MachineState parent_obj;
+
+Stm32l4x5SocState soc;
+} Bl475eMachineState;
+
+static void bl475e_init(MachineState *machine)
 {
+Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
-DeviceState *dev;
 
-dev = qdev_new(TYPE_STM32L4X5XG_SOC);
-object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), _fatal);
+object_initialize_child(OBJECT(machine), "soc", >soc,
+TYPE_STM32L4X5XG_SOC);
+sysbus_realize(SYS_BUS_DEVICE(>soc), _fatal);
 
-sc = STM32L4X5_SOC_GET_CLASS(dev);
-armv7m_load_kernel(ARM_CPU(first_cpu),
-   machine->kernel_filename,
-   0, sc->flash_size);
+sc = STM32L4X5_SOC_GET_CLASS(>soc);
+armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0,
+   sc->flash_size);
 }
 
-static void b_l475e_iot01a_machine_init(MachineClass *mc)
+static void bl475e_machine_init(ObjectClass *oc, void *data)
 {
+MachineClass *mc = MACHINE_CLASS(oc);
 static const char *machine_valid_cpu_types[] = {
 ARM_CPU_TYPE_NAME("cortex-m4"),
 NULL
 };
 mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
-mc->init = b_l475e_iot01a_init;
+mc->init = bl475e_init;
 mc->valid_cpu_types = machine_valid_cpu_types;
 
 /* SRAM pre-allocated as part of the SoC instantiation */
 mc->default_ram_size = 0;
 }
 
-DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
+static const TypeInfo bl475e_machine_type[] = {
+{
+.name   = TYPE_B_L475E_IOT01A,
+.parent = TYPE_MACHINE,
+.instance_size  = sizeof(Bl475eMachineState),
+.class_init = bl475e_machine_init,
+}
+};
+
+DEFINE_TYPES(bl475e_machine_type)
-- 
2.43.2




[PATCH v6 4/5] hw/arm : Connect DM163 to B-L475E-IOT01A

2024-04-24 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/b-l475e-iot01a.c | 59 +++--
 hw/arm/Kconfig  |  1 +
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 970c637ce6..5002a40f06 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -27,10 +27,37 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "qemu/error-report.h"
-#include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
+#include "hw/core/split-irq.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
+
+/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */
 
-/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const unsigned dm163_input[NUM_DM163_INPUTS] = {
+1 * GPIO_NUM_PINS + 2,  /* ROW0  PB2   */
+0 * GPIO_NUM_PINS + 15, /* ROW1  PA15  */
+0 * GPIO_NUM_PINS + 2,  /* ROW2  PA2   */
+0 * GPIO_NUM_PINS + 7,  /* ROW3  PA7   */
+0 * GPIO_NUM_PINS + 6,  /* ROW4  PA6   */
+0 * GPIO_NUM_PINS + 5,  /* ROW5  PA5   */
+1 * GPIO_NUM_PINS + 0,  /* ROW6  PB0   */
+0 * GPIO_NUM_PINS + 3,  /* ROW7  PA3   */
+0 * GPIO_NUM_PINS + 4,  /* SIN (SDA) PA4   */
+1 * GPIO_NUM_PINS + 1,  /* DCK (SCK) PB1   */
+2 * GPIO_NUM_PINS + 3,  /* RST_B (RST) PC3 */
+2 * GPIO_NUM_PINS + 4,  /* LAT_B (LAT) PC4 */
+2 * GPIO_NUM_PINS + 5,  /* SELBK (SB)  PC5 */
+};
 
 #define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
 OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
@@ -39,12 +66,16 @@ typedef struct Bl475eMachineState {
 MachineState parent_obj;
 
 Stm32l4x5SocState soc;
+SplitIRQ gpio_splitters[NUM_DM163_INPUTS];
+DM163State dm163;
 } Bl475eMachineState;
 
 static void bl475e_init(MachineState *machine)
 {
 Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
+DeviceState *dev, *gpio_out_splitter;
+unsigned gpio, pin;
 
 object_initialize_child(OBJECT(machine), "soc", >soc,
 TYPE_STM32L4X5XG_SOC);
@@ -53,6 +84,30 @@ static void bl475e_init(MachineState *machine)
 sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0,
sc->flash_size);
+
+if (object_class_by_name(TYPE_DM163)) {
+object_initialize_child(OBJECT(machine), "dm163",
+>dm163, TYPE_DM163);
+dev = DEVICE(>dm163);
+qdev_realize(dev, NULL, _abort);
+
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]",
+>gpio_splitters[i], TYPE_SPLIT_IRQ);
+gpio_out_splitter = DEVICE(>gpio_splitters[i]);
+qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2);
+qdev_realize(gpio_out_splitter, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_out_splitter, 0,
+qdev_get_gpio_in(DEVICE(>soc), dm163_input[i]));
+qdev_connect_gpio_out(gpio_out_splitter, 1,
+qdev_get_gpio_in(dev, i));
+gpio = dm163_input[i] / GPIO_NUM_PINS;
+pin = dm163_input[i] % GPIO_NUM_PINS;
+qdev_connect_gpio_out(DEVICE(>soc.gpio[gpio]), pin,
+qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0));
+}
+}
 }
 
 static void bl475e_machine_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 893a7bff66..8431384402 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -468,6 +468,7 @@ config B_L475E_IOT01A
 default y
 depends on TCG && ARM
 select STM32L4X5_SOC
+imply DM163
 
 config STM32L4X5_SOC
 bool
-- 
2.43.2




[PATCH v6 0/5] Add device DM163 (led driver, matrix colors shield & display)

2024-04-24 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. This color shield can be plugged
on the Arduino board (or the B-L475E-IOT01A board) to drive an 8x8
RGB led matrix. This RGB led matrix takes advantage of retinal persistance
to seemingly display different colors in each row.

Thank you for the reviews.

Changes from v5 (1st commit) :
- use `extract16` and `deposit32` in `dm163_propagate_outputs` for
more clarity
- similarly, use `extract64` and `deposit32` in `dm163_bank(0|1)`

Changes from v4 :
dm163
- redefine `DM163_NUM_LEDS` for clarity
- change definition of `COLOR_BUFFER_SIZE`
- rename `age_of_row` to `row_persistance_delay`
- remove unnecessary QOM cast macro in GPIO handlers
- remove unnecessary inline of `dm163_bank0` and `dm163_bank1`
- replace occurrences of number 8 by the right define macro
- use unsigned type to print GPIO input `new_stateq`
STM32L4x5 qtests
- add comment to explain GPIO forwarding to SoC
B-L475E-IOT01A
- correct formatting
- use unsigned for gpio pins' indices
DM163 qtest
- use an enum for dm163 inputs
- inline ['dm163-test'] in meson.build (I don't plan on adding qtests)
OTHER
- update copyrights to 2023-2024

Changes from v3 (review of the 1st commit by Peter Maydell) :
- dm163.c : instead of redrawing the entire console each frame,
only redraw the rows that changed using a new variable `redraw`
- reset all the fields in `dm163_reset_hold`
- correcting typos : persistance -> persistence
- b-l475e-iot01a.rst : correcting typo

Changes from v2 : Corrected typo in the Based-on message id

Changes from v1 :
- moving the DM163 from the SoC to the B-L475E-IOT01A machine
(changing config files and tests accordingly)
- restricting DM163 test to ARM & DM163 availability
- using `object_class_by_name()` to check for DM163 presence at run-time
- exporting SYSCFG inputs to the SoC (and adapting tests accordingly)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 

Inès Varhol (5):
  hw/display : Add device DM163
  hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC
  hw/arm : Create Bl475eMachineState
  hw/arm : Connect DM163 to B-L475E-IOT01A
  tests/qtest : Add testcase for DM163

 docs/system/arm/b-l475e-iot01a.rst  |   3 +-
 include/hw/display/dm163.h  |  59 +
 hw/arm/b-l475e-iot01a.c | 105 +++--
 hw/arm/stm32l4x5_soc.c  |   6 +-
 hw/display/dm163.c  | 349 
 tests/qtest/dm163-test.c| 194 
 tests/qtest/stm32l4x5_gpio-test.c   |  13 +-
 tests/qtest/stm32l4x5_syscfg-test.c |  17 +-
 hw/arm/Kconfig  |   1 +
 hw/display/Kconfig  |   3 +
 hw/display/meson.build  |   1 +
 hw/display/trace-events |  14 ++
 tests/qtest/meson.build |   2 +
 13 files changed, 736 insertions(+), 31 deletions(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.2




[PATCH] hw/misc : Correct 5 spaces indents in stm32l4x5_exti

2024-04-21 Thread Inès Varhol
Signed-off-by: Inès Varhol 
---
 hw/misc/stm32l4x5_exti.c | 8 
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/hw/misc/stm32l4x5_exti.c b/hw/misc/stm32l4x5_exti.c
index 9fd859160d..5c55ee4268 100644
--- a/hw/misc/stm32l4x5_exti.c
+++ b/hw/misc/stm32l4x5_exti.c
@@ -59,22 +59,22 @@ static const uint32_t exti_romask[EXTI_NUM_REGISTER] = {
 
 static unsigned regbank_index_by_irq(unsigned irq)
 {
- return irq >= EXTI_MAX_IRQ_PER_BANK ? 1 : 0;
+return irq >= EXTI_MAX_IRQ_PER_BANK ? 1 : 0;
 }
 
 static unsigned regbank_index_by_addr(hwaddr addr)
 {
- return addr >= EXTI_IMR2 ? 1 : 0;
+return addr >= EXTI_IMR2 ? 1 : 0;
 }
 
 static unsigned valid_mask(unsigned bank)
 {
- return MAKE_64BIT_MASK(0, irqs_per_bank[bank]);
+return MAKE_64BIT_MASK(0, irqs_per_bank[bank]);
 }
 
 static unsigned configurable_mask(unsigned bank)
 {
- return valid_mask(bank) & ~exti_romask[bank];
+return valid_mask(bank) & ~exti_romask[bank];
 }
 
 static void stm32l4x5_exti_reset_hold(Object *obj)
-- 
2.43.2




[PATCH v5 5/5] tests/qtest : Add testcase for DM163

2024-04-21 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are propagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Acked-by: Thomas Huth 
Reviewed-by: Philippe Mathieu-Daudé 
---
 tests/qtest/dm163-test.c | 194 +++
 tests/qtest/meson.build  |   2 +
 2 files changed, 196 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..3161c9208d
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,194 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+enum DM163_INPUTS {
+SIN = 8,
+DCK = 9,
+RST_B = 10,
+LAT_B = 11,
+SELBK = 12,
+EN_B = 13
+};
+
+#define DEVICE_NAME "/machine/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const unsigned bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_g

[PATCH v5 1/5] hw/display : Add device DM163

2024-04-21 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Acked-by: Alistair Francis 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 docs/system/arm/b-l475e-iot01a.rst |   3 +-
 include/hw/display/dm163.h |  59 +
 hw/display/dm163.c | 334 +
 hw/display/Kconfig |   3 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  14 ++
 6 files changed, 413 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c

diff --git a/docs/system/arm/b-l475e-iot01a.rst 
b/docs/system/arm/b-l475e-iot01a.rst
index 0afef8e4f4..91de5e82fc 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -12,13 +12,14 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of 
sensors.
 Supported devices
 """""""""""""""""
 
-Currently B-L475E-IOT01A machine's only supports the following devices:
+Currently B-L475E-IOT01A machines support the following devices:
 
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
 - STM32L4x5 GPIOs (General-purpose I/Os)
+- optional 8x8 led display (based on DM163 driver)
 
 Missing devices
 """""""""""""""
diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h
new file mode 100644
index 00..4377f77bb7
--- /dev/null
+++ b/include/hw/display/dm163.h
@@ -0,0 +1,59 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_DM163_H
+#define HW_DISPLAY_DM163_H
+
+#include "qom/object.h"
+#include "hw/qdev-core.h"
+
+#define TYPE_DM163 "dm163"
+OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163);
+
+#define RGB_MATRIX_NUM_ROWS 8
+#define RGB_MATRIX_NUM_COLS 8
+#define DM163_NUM_LEDS (RGB_MATRIX_NUM_COLS * 3)
+/* The last row is filled with 0 (turned off row) */
+#define COLOR_BUFFER_SIZE (RGB_MATRIX_NUM_ROWS + 1)
+
+typedef struct DM163State {
+DeviceState parent_obj;
+
+/* DM163 driver */
+uint64_t bank0_shift_register[3];
+uint64_t bank1_shift_register[3];
+uint16_t latched_outputs[DM163_NUM_LEDS];
+uint16_t outputs[DM163_NUM_LEDS];
+qemu_irq sout;
+
+uint8_t sin;
+uint8_t dck;
+uint8_t rst_b;
+uint8_t lat_b;
+uint8_t selbk;
+uint8_t en_b;
+
+/* IM120417002 colors shield */
+uint8_t activated_rows;
+
+/* 8x8 RGB matrix */
+QemuConsole *console;
+uint8_t redraw;
+/* Rows currently being displayed on the matrix. */
+/* The last row is filled with 0 (turned off row) */
+uint32_t buffer[COLOR_BUFFER_SIZE][RGB_MATRIX_NUM_COLS];
+uint8_t last_buffer_idx;
+uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS];
+/* Used to simulate retinal persistence of rows */
+uint8_t row_persistence_delay[RGB_MATRIX_NUM_ROWS];
+} DM163State;
+
+#endif /* HW_DISPLAY_DM163_H */
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..9079e6604e
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,334 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTENCE 3
+#define TURNED_OFF_ROW (COLOR_BUFFER_SIZE - 1)
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
+VMSTATE_UINT16_ARRAY(latched_ou

[PATCH v5 0/5] Add device DM163 (led driver, matrix colors shield & display)

2024-04-21 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. This color shield can be plugged
on the Arduino board (or the B-L475E-IOT01A board) to drive an 8x8
RGB led matrix. This RGB led matrix takes advantage of retinal persistance
to seemingly display different colors in each row.

Hello,

I'm planning to add a more thorough test of the display functionality
in a later patch, based on the avocado test  `machine_m68k_nextcube.py`.

Thank you for the reviews.

Changes from v4 :
dm163
- redefine `DM163_NUM_LEDS` for clarity
- change definition of `COLOR_BUFFER_SIZE`
- rename `age_of_row` to `row_persistance_delay`
- remove unnecessary QOM cast macro in GPIO handlers
- remove unnecessary inline of `dm163_bank0` and `dm163_bank1`
- replace occurrences of number 8 by the right define macro
- use unsigned type to print GPIO input `new_stateq`
STM32L4x5 qtests
- add comment to explain GPIO forwarding to SoC
B-L475E-IOT01A
- correct formatting
- use unsigned for gpio pins' indices
DM163 qtest
- use an enum for dm163 inputs
- inline ['dm163-test'] in meson.build (I don't plan on adding qtests)
OTHER
- update copyrights to 2023-2024

Changes from v3 (review of the 1st commit by Peter Maydell) :
- dm163.c : instead of redrawing the entire console each frame,
only redraw the rows that changed using a new variable `redraw`
- reset all the fields in `dm163_reset_hold`
- correcting typos : persistance -> persistence
- b-l475e-iot01a.rst : correcting typo

Changes from v2 : Corrected typo in the Based-on message id

Changes from v1 :
- moving the DM163 from the SoC to the B-L475E-IOT01A machine
(changing config files and tests accordingly)
- restricting DM163 test to ARM & DM163 availability
- using `object_class_by_name()` to check for DM163 presence at run-time
- exporting SYSCFG inputs to the SoC (and adapting tests accordingly)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

Inès Varhol (5):
  hw/display : Add device DM163
  hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC
  hw/arm : Create Bl475eMachineState
  hw/arm : Connect DM163 to B-L475E-IOT01A
  tests/qtest : Add testcase for DM163

 docs/system/arm/b-l475e-iot01a.rst  |   3 +-
 include/hw/display/dm163.h  |  59 +
 hw/arm/b-l475e-iot01a.c | 105 +++--
 hw/arm/stm32l4x5_soc.c  |   6 +-
 hw/display/dm163.c  | 334 
 tests/qtest/dm163-test.c| 194 
 tests/qtest/stm32l4x5_gpio-test.c   |  13 +-
 tests/qtest/stm32l4x5_syscfg-test.c |  17 +-
 hw/arm/Kconfig  |   1 +
 hw/display/Kconfig  |   3 +
 hw/display/meson.build  |   1 +
 hw/display/trace-events |  14 ++
 tests/qtest/meson.build |   2 +
 13 files changed, 721 insertions(+), 31 deletions(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.2




[PATCH v5 2/5] hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC

2024-04-21 Thread Inès Varhol
Exposing SYSCFG inputs to the SoC is practical in order to wire the SoC
to the optional DM163 display from the board code (GPIOs outputs need
to be connected to both SYSCFG inputs and DM163 inputs).

STM32L4x5 SYSCFG in-irq interception needed to be changed accordingly.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/stm32l4x5_soc.c  |  6 --
 tests/qtest/stm32l4x5_gpio-test.c   | 13 -
 tests/qtest/stm32l4x5_syscfg-test.c | 17 ++---
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 40e294f838..0332b67701 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -1,8 +1,8 @@
 /*
  * STM32L4x5 SoC family
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2023-2024 Arnaud Minier 
+ * Copyright (c) 2023-2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -221,6 +221,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+qdev_pass_gpios(DEVICE(>syscfg), dev_soc, NULL);
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 0f6bda54d3..72a7823406 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -43,6 +43,9 @@
 #define OTYPER_PUSH_PULL 0
 #define OTYPER_OPEN_DRAIN 1
 
+/* SoC forwards GPIOs to SysCfg */
+#define SYSCFG "/machine/soc"
+
 const uint32_t moder_reset[NUM_GPIOS] = {
 0xABFF,
 0xFEBF,
@@ -284,7 +287,7 @@ static void test_gpio_output_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Set a bit in ODR and check nothing happens */
 gpio_set_bit(gpio, ODR, pin, 1);
@@ -319,7 +322,7 @@ static void test_gpio_input_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input, raise it, and check that the pin is high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -348,7 +351,7 @@ static void test_pull_up_pull_down(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input with pull-up, check the line is set high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -378,7 +381,7 @@ static void test_push_pull(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in push-pull output */
 /* And checking the pin was disconnected */
@@ -425,7 +428,7 @@ static void test_open_drain(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in open-drain output */
 /* And checking the pin was disconnected */
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index ed4801798d..733b42df55 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -1,8 +1,8 @@
 /*
  * QTest testcase for STM32L4x5_SYSCFG
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -25,6 +25,10 @@
 #define SYSCFG_SWPR2 0x28
 #define INVALID_ADDR 0x2C
 
+/* SoC forwards GPIOs to SysCfg */
+#define SYSCFG "/machine/soc"
+#define EXTI "/machine/soc/exti"
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -37,8 +41,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, "/machine/soc/syscfg",
-NULL, num, level);
+   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -197,7

[PATCH v5 3/5] hw/arm : Create Bl475eMachineState

2024-04-21 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/b-l475e-iot01a.c | 46 -
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index d862aa43fc..970c637ce6 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -2,8 +2,8 @@
  * B-L475E-IOT01A Discovery Kit machine
  * (B-L475E-IOT01A IoT Node)
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2023-2024 Arnaud Minier 
+ * Copyright (c) 2023-2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -32,33 +32,51 @@
 
 /* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
-static void b_l475e_iot01a_init(MachineState *machine)
+#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
+OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
+
+typedef struct Bl475eMachineState {
+MachineState parent_obj;
+
+Stm32l4x5SocState soc;
+} Bl475eMachineState;
+
+static void bl475e_init(MachineState *machine)
 {
+Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
-DeviceState *dev;
 
-dev = qdev_new(TYPE_STM32L4X5XG_SOC);
-object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), _fatal);
+object_initialize_child(OBJECT(machine), "soc", >soc,
+TYPE_STM32L4X5XG_SOC);
+sysbus_realize(SYS_BUS_DEVICE(>soc), _fatal);
 
-sc = STM32L4X5_SOC_GET_CLASS(dev);
-armv7m_load_kernel(ARM_CPU(first_cpu),
-   machine->kernel_filename,
-   0, sc->flash_size);
+sc = STM32L4X5_SOC_GET_CLASS(>soc);
+armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0,
+   sc->flash_size);
 }
 
-static void b_l475e_iot01a_machine_init(MachineClass *mc)
+static void bl475e_machine_init(ObjectClass *oc, void *data)
 {
+MachineClass *mc = MACHINE_CLASS(oc);
 static const char *machine_valid_cpu_types[] = {
 ARM_CPU_TYPE_NAME("cortex-m4"),
 NULL
 };
 mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
-mc->init = b_l475e_iot01a_init;
+mc->init = bl475e_init;
 mc->valid_cpu_types = machine_valid_cpu_types;
 
 /* SRAM pre-allocated as part of the SoC instantiation */
 mc->default_ram_size = 0;
 }
 
-DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
+static const TypeInfo bl475e_machine_type[] = {
+{
+.name   = TYPE_B_L475E_IOT01A,
+.parent = TYPE_MACHINE,
+.instance_size  = sizeof(Bl475eMachineState),
+.class_init = bl475e_machine_init,
+}
+};
+
+DEFINE_TYPES(bl475e_machine_type)
-- 
2.43.2




[PATCH v5 4/5] hw/arm : Connect DM163 to B-L475E-IOT01A

2024-04-21 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 hw/arm/b-l475e-iot01a.c | 59 +++--
 hw/arm/Kconfig  |  1 +
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 970c637ce6..5002a40f06 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -27,10 +27,37 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "qemu/error-report.h"
-#include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
+#include "hw/core/split-irq.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
+
+/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */
 
-/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const unsigned dm163_input[NUM_DM163_INPUTS] = {
+1 * GPIO_NUM_PINS + 2,  /* ROW0  PB2   */
+0 * GPIO_NUM_PINS + 15, /* ROW1  PA15  */
+0 * GPIO_NUM_PINS + 2,  /* ROW2  PA2   */
+0 * GPIO_NUM_PINS + 7,  /* ROW3  PA7   */
+0 * GPIO_NUM_PINS + 6,  /* ROW4  PA6   */
+0 * GPIO_NUM_PINS + 5,  /* ROW5  PA5   */
+1 * GPIO_NUM_PINS + 0,  /* ROW6  PB0   */
+0 * GPIO_NUM_PINS + 3,  /* ROW7  PA3   */
+0 * GPIO_NUM_PINS + 4,  /* SIN (SDA) PA4   */
+1 * GPIO_NUM_PINS + 1,  /* DCK (SCK) PB1   */
+2 * GPIO_NUM_PINS + 3,  /* RST_B (RST) PC3 */
+2 * GPIO_NUM_PINS + 4,  /* LAT_B (LAT) PC4 */
+2 * GPIO_NUM_PINS + 5,  /* SELBK (SB)  PC5 */
+};
 
 #define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
 OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
@@ -39,12 +66,16 @@ typedef struct Bl475eMachineState {
 MachineState parent_obj;
 
 Stm32l4x5SocState soc;
+SplitIRQ gpio_splitters[NUM_DM163_INPUTS];
+DM163State dm163;
 } Bl475eMachineState;
 
 static void bl475e_init(MachineState *machine)
 {
 Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
+DeviceState *dev, *gpio_out_splitter;
+unsigned gpio, pin;
 
 object_initialize_child(OBJECT(machine), "soc", >soc,
 TYPE_STM32L4X5XG_SOC);
@@ -53,6 +84,30 @@ static void bl475e_init(MachineState *machine)
 sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0,
sc->flash_size);
+
+if (object_class_by_name(TYPE_DM163)) {
+object_initialize_child(OBJECT(machine), "dm163",
+>dm163, TYPE_DM163);
+dev = DEVICE(>dm163);
+qdev_realize(dev, NULL, _abort);
+
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]",
+>gpio_splitters[i], TYPE_SPLIT_IRQ);
+gpio_out_splitter = DEVICE(>gpio_splitters[i]);
+qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2);
+qdev_realize(gpio_out_splitter, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_out_splitter, 0,
+qdev_get_gpio_in(DEVICE(>soc), dm163_input[i]));
+qdev_connect_gpio_out(gpio_out_splitter, 1,
+qdev_get_gpio_in(dev, i));
+gpio = dm163_input[i] / GPIO_NUM_PINS;
+pin = dm163_input[i] % GPIO_NUM_PINS;
+qdev_connect_gpio_out(DEVICE(>soc.gpio[gpio]), pin,
+qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0));
+}
+}
 }
 
 static void bl475e_machine_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 893a7bff66..8431384402 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -468,6 +468,7 @@ config B_L475E_IOT01A
 default y
 depends on TCG && ARM
 select STM32L4X5_SOC
+imply DM163
 
 config STM32L4X5_SOC
 bool
-- 
2.43.2




[PATCH v2] tests/qtest : Use `g_assert_cmphex` instead of `g_assert_cmpuint`

2024-04-14 Thread Inès Varhol
The messages for assertions using hexadecimal numbers will be
easier to understand with `g_assert_cmphex`.

Cases changed : "cmpuint.*0x", "cmpuint.*<<"

Signed-off-by: Inès Varhol 
---
 tests/qtest/aspeed_fsi-test.c  |  20 ++--
 tests/qtest/cmsdk-apb-dualtimer-test.c |   2 +-
 tests/qtest/cmsdk-apb-watchdog-test.c  |   2 +-
 tests/qtest/erst-test.c|   2 +-
 tests/qtest/ivshmem-test.c |  10 +-
 tests/qtest/libqos/ahci.c  |   4 +-
 tests/qtest/microbit-test.c|  46 -
 tests/qtest/sse-timer-test.c   |   4 +-
 tests/qtest/stm32l4x5_exti-test.c  | 138 -
 tests/qtest/stm32l4x5_syscfg-test.c|  74 ++---
 10 files changed, 151 insertions(+), 151 deletions(-)

diff --git a/tests/qtest/aspeed_fsi-test.c b/tests/qtest/aspeed_fsi-test.c
index b3020dd821..f5ab269972 100644
--- a/tests/qtest/aspeed_fsi-test.c
+++ b/tests/qtest/aspeed_fsi-test.c
@@ -63,22 +63,22 @@ static void test_fsi_setup(QTestState *s, uint32_t 
base_addr)
 /* Unselect FSI1 */
 aspeed_fsi_writel(s, ASPEED_FSI_OPB1_BUS_SELECT, 0x0);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB1_BUS_SELECT);
-g_assert_cmpuint(curval, ==, 0x0);
+g_assert_cmphex(curval, ==, 0x0);
 
 /* Select FSI0 */
 aspeed_fsi_writel(s, ASPEED_FSI_OPB0_BUS_SELECT, 0x1);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB0_BUS_SELECT);
-g_assert_cmpuint(curval, ==, 0x1);
+g_assert_cmphex(curval, ==, 0x1);
 } else if (base_addr == AST2600_OPB_FSI1_BASE_ADDR) {
 /* Unselect FSI0 */
 aspeed_fsi_writel(s, ASPEED_FSI_OPB0_BUS_SELECT, 0x0);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB0_BUS_SELECT);
-g_assert_cmpuint(curval, ==, 0x0);
+g_assert_cmphex(curval, ==, 0x0);
 
 /* Select FSI1 */
 aspeed_fsi_writel(s, ASPEED_FSI_OPB1_BUS_SELECT, 0x1);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB1_BUS_SELECT);
-g_assert_cmpuint(curval, ==, 0x1);
+g_assert_cmphex(curval, ==, 0x1);
 } else {
 g_assert_not_reached();
 }
@@ -145,11 +145,11 @@ static void test_fsi0_getcfam_addr0(const void *data)
 aspeed_fsi_writel(s, ASPEED_FSI_ENGINER_TRIGGER, 0x1);
 
 curval = aspeed_fsi_readl(s, ASPEED_FSI_INTRRUPT_STATUS);
-g_assert_cmpuint(curval, ==, 0x1);
+g_assert_cmphex(curval, ==, 0x1);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB0_BUS_STATUS);
-g_assert_cmpuint(curval, ==, 0x0);
+g_assert_cmphex(curval, ==, 0x0);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB0_READ_DATA);
-g_assert_cmpuint(curval, ==, 0x152d02c0);
+g_assert_cmphex(curval, ==, 0x152d02c0);
 }
 
 static void test_fsi1_getcfam_addr0(const void *data)
@@ -168,11 +168,11 @@ static void test_fsi1_getcfam_addr0(const void *data)
 aspeed_fsi_writel(s, ASPEED_FSI_ENGINER_TRIGGER, 0x1);
 
 curval = aspeed_fsi_readl(s, ASPEED_FSI_INTRRUPT_STATUS);
-g_assert_cmpuint(curval, ==, 0x2);
+g_assert_cmphex(curval, ==, 0x2);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB1_BUS_STATUS);
-g_assert_cmpuint(curval, ==, 0x0);
+g_assert_cmphex(curval, ==, 0x0);
 curval = aspeed_fsi_readl(s, ASPEED_FSI_OPB1_READ_DATA);
-g_assert_cmpuint(curval, ==, 0x152d02c0);
+g_assert_cmphex(curval, ==, 0x152d02c0);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/cmsdk-apb-dualtimer-test.c 
b/tests/qtest/cmsdk-apb-dualtimer-test.c
index ad6a758289..3b89bed97d 100644
--- a/tests/qtest/cmsdk-apb-dualtimer-test.c
+++ b/tests/qtest/cmsdk-apb-dualtimer-test.c
@@ -69,7 +69,7 @@ static void test_dualtimer(void)
  * tick VALUE should have wrapped round to 0x.
  */
 clock_step(40);
-g_assert_cmpuint(readl(TIMER_BASE + TIMER1VALUE), ==, 0x);
+g_assert_cmphex(readl(TIMER_BASE + TIMER1VALUE), ==, 0x);
 
 /* Check that any write to INTCLR clears interrupt */
 writel(TIMER_BASE + TIMER1INTCLR, 1);
diff --git a/tests/qtest/cmsdk-apb-watchdog-test.c 
b/tests/qtest/cmsdk-apb-watchdog-test.c
index 2710cb17b8..00b5dbbc81 100644
--- a/tests/qtest/cmsdk-apb-watchdog-test.c
+++ b/tests/qtest/cmsdk-apb-watchdog-test.c
@@ -88,7 +88,7 @@ static void test_clock_change(void)
 
 /* Rewrite RCC.SYSDIV from 16 to 8, so the clock is now 40ns per tick */
 rcc = readl(SSYS_BASE + RCC);
-g_assert_cmpuint(extract32(rcc, SYSDIV_SHIFT, SYSDIV_LENGTH), ==, 0xf);
+g_assert_cmphex(extract32(rcc, SYSDIV_SHIFT, SYSDIV_LENGTH), ==, 0xf);
 rcc = deposit32(rcc, SYSDIV_SHIFT, SYSDIV_LENGTH, 7);
 writel(SSYS_BASE + RCC, rcc);
 
diff --git a/tests/qtest/erst-test.c b/tests/qtest/erst-test.c
index c45bee7f05..36bbe122ab 100644
--- a/tests/qtest/erst-test.c
+++ b/tests/qtest/erst-test.c
@@ -109,7 +109,7 @@ static void setup_vm_cmd(ERSTState *s, const char *cmd)
 g_assert_cmpuint(s->reg_barsize, ==, 16);
 
 s->mem_bar = qp

Re: [PATCH] tests/qtest : Use `g_assert_cmphex` instead of `g_assert_cmpuint`

2024-04-14 Thread Inès Varhol



- Le 14 Avr 24, à 18:19, Philippe Mathieu-Daudé phi...@linaro.org a écrit :

> Hi Inès,

Hello Philippe !

> 
> On 14/4/24 15:24, Inès Varhol wrote:
>> The messages for STM32L4x5 tests will be easier to understand with
>> `g_assert_cmphex` since the comparisions were made with hexadecimal
> 
> "comparisons"

Ouch thank you I'm fixing it.

> 
>> numbers.
>> 
>> Signed-off-by: Inès Varhol 
>> ---
>>   tests/qtest/stm32l4x5_exti-test.c   | 138 ++--
>>   tests/qtest/stm32l4x5_syscfg-test.c |  74 +++
>>   2 files changed, 106 insertions(+), 106 deletions(-)
> 
> $ git grep g_assert_cmpuint.*,\ 0x tests/qtest/stm32*| wc -l
>  105
> 
> Nice cleanup!
> 
> $ git grep g_assert_cmpuint.*,\ 0x | wc -l
>  148
> 
> Still 33 to go... (not asking you to do it!).

Very cool tool. I can fix it (also with a regex), should I change those with
comparisons to 0x0 and 0x1 though?

Best,

Ines



[PATCH] tests/qtest : Use `g_assert_cmphex` instead of `g_assert_cmpuint`

2024-04-14 Thread Inès Varhol
The messages for STM32L4x5 tests will be easier to understand with
`g_assert_cmphex` since the comparisions were made with hexadecimal
numbers.

Signed-off-by: Inès Varhol 
---
 tests/qtest/stm32l4x5_exti-test.c   | 138 ++--
 tests/qtest/stm32l4x5_syscfg-test.c |  74 +++
 2 files changed, 106 insertions(+), 106 deletions(-)

diff --git a/tests/qtest/stm32l4x5_exti-test.c 
b/tests/qtest/stm32l4x5_exti-test.c
index 81830be8ae..7092860b9b 100644
--- a/tests/qtest/stm32l4x5_exti-test.c
+++ b/tests/qtest/stm32l4x5_exti-test.c
@@ -70,44 +70,44 @@ static void test_reg_write_read(void)
 /* Test that non-reserved bits in xMR and xTSR can be set and cleared */
 
 exti_writel(EXTI_IMR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_IMR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_IMR1), ==, 0x);
 exti_writel(EXTI_IMR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_IMR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_IMR1), ==, 0x);
 
 exti_writel(EXTI_EMR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_EMR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_EMR1), ==, 0x);
 exti_writel(EXTI_EMR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_EMR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_EMR1), ==, 0x);
 
 exti_writel(EXTI_RTSR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_RTSR1), ==, 0x007D);
+g_assert_cmphex(exti_readl(EXTI_RTSR1), ==, 0x007D);
 exti_writel(EXTI_RTSR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_RTSR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_RTSR1), ==, 0x);
 
 exti_writel(EXTI_FTSR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_FTSR1), ==, 0x007D);
+g_assert_cmphex(exti_readl(EXTI_FTSR1), ==, 0x007D);
 exti_writel(EXTI_FTSR1, 0x);
-g_assert_cmpuint(exti_readl(EXTI_FTSR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_FTSR1), ==, 0x);
 
 exti_writel(EXTI_IMR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_IMR2), ==, 0x00FF);
+g_assert_cmphex(exti_readl(EXTI_IMR2), ==, 0x00FF);
 exti_writel(EXTI_IMR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_IMR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_IMR2), ==, 0x);
 
 exti_writel(EXTI_EMR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_EMR2), ==, 0x00FF);
+g_assert_cmphex(exti_readl(EXTI_EMR2), ==, 0x00FF);
 exti_writel(EXTI_EMR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_EMR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_EMR2), ==, 0x);
 
 exti_writel(EXTI_RTSR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_RTSR2), ==, 0x0078);
+g_assert_cmphex(exti_readl(EXTI_RTSR2), ==, 0x0078);
 exti_writel(EXTI_RTSR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_RTSR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_RTSR2), ==, 0x);
 
 exti_writel(EXTI_FTSR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_FTSR2), ==, 0x0078);
+g_assert_cmphex(exti_readl(EXTI_FTSR2), ==, 0x0078);
 exti_writel(EXTI_FTSR2, 0x);
-g_assert_cmpuint(exti_readl(EXTI_FTSR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_FTSR2), ==, 0x);
 }
 
 static void test_direct_lines_write(void)
@@ -115,28 +115,28 @@ static void test_direct_lines_write(void)
 /* Test that direct lines reserved bits are not written to */
 
 exti_writel(EXTI_RTSR1, 0xFF82);
-g_assert_cmpuint(exti_readl(EXTI_RTSR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_RTSR1), ==, 0x);
 
 exti_writel(EXTI_FTSR1, 0xFF82);
-g_assert_cmpuint(exti_readl(EXTI_FTSR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_FTSR1), ==, 0x);
 
 exti_writel(EXTI_SWIER1, 0xFF82);
-g_assert_cmpuint(exti_readl(EXTI_SWIER1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_SWIER1), ==, 0x);
 
 exti_writel(EXTI_PR1, 0xFF82);
-g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_PR1), ==, 0x);
 
 exti_writel(EXTI_RTSR2, 0x0087);
-g_assert_cmpuint(exti_readl(EXTI_RTSR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_RTSR2), ==, 0x);
 
 exti_writel(EXTI_FTSR2, 0x0087);
-g_assert_cmpuint(exti_readl(EXTI_FTSR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_FTSR2), ==, 0x);
 
 exti_writel(EXTI_SWIER2, 0x0087);
-g_assert_cmpuint(exti_readl(EXTI_SWIER2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_SWIER2), ==, 0x);
 
 exti_writel(EXTI_PR2, 0x0087);
-g_assert_cmpuint(exti_readl(EXTI_PR2), ==, 0x);
+g_assert_cmphex(exti_readl(EXTI_PR2), ==, 0x);
 }
 
 static void test_reserved_bits_write(void)
@@ -144,22 +144,22 @@ static void test_reserved_bits_write

[PATCH v4 2/5] hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC

2024-04-14 Thread Inès Varhol
Exposing SYSCFG inputs to the SoC is practical in order to wire the SoC
to the optional DM163 display from the board code (GPIOs outputs need
to be connected to both SYSCFG inputs and DM163 inputs).

STM32L4x5 SYSCFG in-irq interception needed to be changed accordingly.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/stm32l4x5_soc.c  |  6 --
 tests/qtest/stm32l4x5_gpio-test.c   | 12 +++-
 tests/qtest/stm32l4x5_syscfg-test.c | 16 +---
 3 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 40e294f838..c4b45e6956 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -1,8 +1,8 @@
 /*
  * STM32L4x5 SoC family
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -221,6 +221,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+qdev_pass_gpios(DEVICE(>syscfg), dev_soc, NULL);
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index 0f6bda54d3..495a6fc413 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -43,6 +43,8 @@
 #define OTYPER_PUSH_PULL 0
 #define OTYPER_OPEN_DRAIN 1
 
+#define SYSCFG "/machine/soc"
+
 const uint32_t moder_reset[NUM_GPIOS] = {
 0xABFF,
 0xFEBF,
@@ -284,7 +286,7 @@ static void test_gpio_output_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Set a bit in ODR and check nothing happens */
 gpio_set_bit(gpio, ODR, pin, 1);
@@ -319,7 +321,7 @@ static void test_gpio_input_mode(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input, raise it, and check that the pin is high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -348,7 +350,7 @@ static void test_pull_up_pull_down(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input with pull-up, check the line is set high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -378,7 +380,7 @@ static void test_push_pull(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in push-pull output */
 /* And checking the pin was disconnected */
@@ -425,7 +427,7 @@ static void test_open_drain(const void *data)
 uint32_t gpio = test_gpio_addr(data);
 uint32_t gpio2 = GPIO_BASE_ADDR + (GPIO_H - gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in open-drain output */
 /* And checking the pin was disconnected */
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index ed4801798d..eed9d5940b 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -1,8 +1,8 @@
 /*
  * QTest testcase for STM32L4x5_SYSCFG
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -25,6 +25,9 @@
 #define SYSCFG_SWPR2 0x28
 #define INVALID_ADDR 0x2C
 
+#define EXTI "/machine/soc/exti"
+#define SYSCFG "/machine/soc"
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -37,8 +40,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, "/machine/soc/syscfg",
-NULL, num, level);
+   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
 }
 
 static void system_reset(void)
@@ -197,7 +199,7 @@ static void test_interrupt(void)
  * Test that GPIO rising lines result in an irq
  * wi

[PATCH v4 4/5] hw/arm : Connect DM163 to B-L475E-IOT01A

2024-04-14 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 59 +++--
 hw/arm/Kconfig  |  1 +
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 2b570b3e09..6f0bf68ca6 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -27,10 +27,37 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "qemu/error-report.h"
-#include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
+#include "hw/core/split-irq.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
+
+/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */
 
-/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const int dm163_input[NUM_DM163_INPUTS] = {
+1 * GPIO_NUM_PINS + 2,  /* ROW0  PB2   */
+0 * GPIO_NUM_PINS + 15, /* ROW1  PA15  */
+0 * GPIO_NUM_PINS + 2,  /* ROW2  PA2   */
+0 * GPIO_NUM_PINS + 7,  /* ROW3  PA7   */
+0 * GPIO_NUM_PINS + 6,  /* ROW4  PA6   */
+0 * GPIO_NUM_PINS + 5,  /* ROW5  PA5   */
+1 * GPIO_NUM_PINS + 0,  /* ROW6  PB0   */
+0 * GPIO_NUM_PINS + 3,  /* ROW7  PA3   */
+0 * GPIO_NUM_PINS + 4,  /* SIN (SDA) PA4   */
+1 * GPIO_NUM_PINS + 1,  /* DCK (SCK) PB1   */
+2 * GPIO_NUM_PINS + 3,  /* RST_B (RST) PC3 */
+2 * GPIO_NUM_PINS + 4,  /* LAT_B (LAT) PC4 */
+2 * GPIO_NUM_PINS + 5,  /* SELBK (SB)  PC5 */
+};
 
 #define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
 OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
@@ -39,12 +66,16 @@ typedef struct Bl475eMachineState {
 MachineState parent_obj;
 
 Stm32l4x5SocState soc;
+SplitIRQ gpio_splitters[NUM_DM163_INPUTS];
+DM163State dm163;
 } Bl475eMachineState;
 
 static void bl475e_init(MachineState *machine)
 {
 Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
+DeviceState *dev, *gpio_out_splitter;
+int gpio, pin;
 
 object_initialize_child(OBJECT(machine), "soc", >soc,
 TYPE_STM32L4X5XG_SOC);
@@ -53,6 +84,30 @@ static void bl475e_init(MachineState *machine)
 sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
 machine->kernel_filename, 0, sc->flash_size);
+
+if (object_class_by_name("dm163")) {
+object_initialize_child(OBJECT(machine), "dm163",
+>dm163, TYPE_DM163);
+dev = DEVICE(>dm163);
+qdev_realize(dev, NULL, _abort);
+
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]",
+>gpio_splitters[i], TYPE_SPLIT_IRQ);
+gpio_out_splitter = DEVICE(>gpio_splitters[i]);
+qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2);
+qdev_realize(gpio_out_splitter, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_out_splitter, 0,
+qdev_get_gpio_in(DEVICE(>soc), dm163_input[i]));
+qdev_connect_gpio_out(gpio_out_splitter, 1,
+qdev_get_gpio_in(dev, i));
+gpio = dm163_input[i] / GPIO_NUM_PINS;
+pin = dm163_input[i] % GPIO_NUM_PINS;
+qdev_connect_gpio_out(DEVICE(>soc.gpio[gpio]), pin,
+qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0));
+}
+}
 }
 
 static void bl475e_machine_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 893a7bff66..8431384402 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -468,6 +468,7 @@ config B_L475E_IOT01A
 default y
 depends on TCG && ARM
 select STM32L4X5_SOC
+imply DM163
 
 config STM32L4X5_SOC
 bool
-- 
2.43.2




[PATCH v4 0/5] Add device DM163 (led driver, matrix colors shield & display)

2024-04-14 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. This color shield can be plugged
on the Arduino board (or the B-L475E-IOT01A board) to drive an 8x8
RGB led matrix. This RGB led matrix takes advantage of retinal persistance
to seemingly display different colors in each row.

Hello,

I have been very busy this last month, sorry for the delay.
In a later version, I will add a more thorough test like suggested by
Thomas Huth (thank you for the examples, I will look into it).

Following the response of Peter Maydell about the SYSCFG export at
STM32L4x5 level, I'm thinking of moving the STM32L4x5 GPIO output
splitters from the Bl475e machine to the STM32L4x5 SOC code.

In the current code, the GPIO output splitters are created in the
Bl475e machine and the machine has to wire some of these splitted
outputs to SYSCFG inputs. SYSCFG inputs are exported at SOC level
to facilitate this.

By moving the splitters inside the STM32L4x5 SOC, the SOC could
export GPIO I/O instead of SYSCFG I/O which would be closer to
the hardware, and the Bl475e machine would directly connect the
DM163 to GPIO outputs.

Changes from v3 (review of the 1st commit by Peter Maydell) :
- dm163.c : instead of redrawing the entire console each frame,
only redraw the rows that changed using a new variable `redraw`
- reset all the fields in `dm163_reset_hold`
- correcting typos : persistance -> persistence
- b-l475e-iot01a.rst : correcting typo

Changes from v2 : Corrected typo in the Based-on message id

Changes from v1 :
- moving the DM163 from the SoC to the B-L475E-IOT01A machine
(changing config files and tests accordingly)
- restricting DM163 test to ARM & DM163 availability
- using `object_class_by_name()` to check for DM163 presence at run-time
- exporting SYSCFG inputs to the SoC (and adapting tests accordingly)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

Inès Varhol (5):
  hw/display : Add device DM163
  hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC
  hw/arm : Create Bl475eMachineState
  hw/arm : Connect DM163 to B-L475E-IOT01A
  tests/qtest : Add testcase for DM163

 docs/system/arm/b-l475e-iot01a.rst  |   3 +-
 include/hw/display/dm163.h  |  58 +
 hw/arm/b-l475e-iot01a.c | 103 +++--
 hw/arm/stm32l4x5_soc.c  |   6 +-
 hw/display/dm163.c  | 333 
 tests/qtest/dm163-test.c| 192 
 tests/qtest/stm32l4x5_gpio-test.c   |  12 +-
 tests/qtest/stm32l4x5_syscfg-test.c |  16 +-
 hw/arm/Kconfig  |   1 +
 hw/display/Kconfig  |   3 +
 hw/display/meson.build  |   1 +
 hw/display/trace-events |  14 ++
 tests/qtest/meson.build |   5 +
 13 files changed, 717 insertions(+), 30 deletions(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.2




[PATCH v4 5/5] tests/qtest : Add testcase for DM163

2024-04-14 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are propagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/dm163-test.c | 192 +++
 tests/qtest/meson.build  |   5 +
 2 files changed, 197 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..6f88ceef44
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,192 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define SIN 8
+#define DCK 9
+#define RST_B 10
+#define LAT_B 11
+#define SELBK 12
+#define EN_B 13
+
+#define DEVICE_NAME "/machine/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const long bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_gpio_connection(void)
+{
+QTestState *qts = qtest_init("-M b-l475e-iot01a");

[PATCH v4 3/5] hw/arm : Create Bl475eMachineState

2024-04-14 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 44 +
 1 file changed, 31 insertions(+), 13 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index d862aa43fc..2b570b3e09 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -2,8 +2,8 @@
  * B-L475E-IOT01A Discovery Kit machine
  * (B-L475E-IOT01A IoT Node)
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -32,33 +32,51 @@
 
 /* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
-static void b_l475e_iot01a_init(MachineState *machine)
+#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
+OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
+
+typedef struct Bl475eMachineState {
+MachineState parent_obj;
+
+Stm32l4x5SocState soc;
+} Bl475eMachineState;
+
+static void bl475e_init(MachineState *machine)
 {
+Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
-DeviceState *dev;
 
-dev = qdev_new(TYPE_STM32L4X5XG_SOC);
-object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), _fatal);
+object_initialize_child(OBJECT(machine), "soc", >soc,
+TYPE_STM32L4X5XG_SOC);
+sysbus_realize(SYS_BUS_DEVICE(>soc), _fatal);
 
-sc = STM32L4X5_SOC_GET_CLASS(dev);
+sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
-   machine->kernel_filename,
-   0, sc->flash_size);
+machine->kernel_filename, 0, sc->flash_size);
 }
 
-static void b_l475e_iot01a_machine_init(MachineClass *mc)
+static void bl475e_machine_init(ObjectClass *oc, void *data)
 {
+MachineClass *mc = MACHINE_CLASS(oc);
 static const char *machine_valid_cpu_types[] = {
 ARM_CPU_TYPE_NAME("cortex-m4"),
 NULL
 };
 mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
-mc->init = b_l475e_iot01a_init;
+mc->init = bl475e_init;
 mc->valid_cpu_types = machine_valid_cpu_types;
 
 /* SRAM pre-allocated as part of the SoC instantiation */
 mc->default_ram_size = 0;
 }
 
-DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
+static const TypeInfo bl475e_machine_type[] = {
+{
+.name   = TYPE_B_L475E_IOT01A,
+.parent = TYPE_MACHINE,
+.instance_size  = sizeof(Bl475eMachineState),
+.class_init = bl475e_machine_init,
+}
+};
+
+DEFINE_TYPES(bl475e_machine_type)
-- 
2.43.2




[PATCH v4 1/5] hw/display : Add device DM163

2024-04-14 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Acked-by: Alistair Francis 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 docs/system/arm/b-l475e-iot01a.rst |   3 +-
 include/hw/display/dm163.h |  58 +
 hw/display/dm163.c | 333 +
 hw/display/Kconfig |   3 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  14 ++
 6 files changed, 411 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c

diff --git a/docs/system/arm/b-l475e-iot01a.rst 
b/docs/system/arm/b-l475e-iot01a.rst
index 0afef8e4f4..91de5e82fc 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -12,13 +12,14 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of 
sensors.
 Supported devices
 """""""""""""""""
 
-Currently B-L475E-IOT01A machine's only supports the following devices:
+Currently B-L475E-IOT01A machines support the following devices:
 
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
 - STM32L4x5 GPIOs (General-purpose I/Os)
+- optional 8x8 led display (based on DM163 driver)
 
 Missing devices
 """""""""""""""
diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h
new file mode 100644
index 00..00d0504640
--- /dev/null
+++ b/include/hw/display/dm163.h
@@ -0,0 +1,58 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_DM163_H
+#define HW_DISPLAY_DM163_H
+
+#include "qom/object.h"
+#include "hw/qdev-core.h"
+
+#define TYPE_DM163 "dm163"
+OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163);
+
+#define DM163_NUM_LEDS 24
+#define RGB_MATRIX_NUM_ROWS 8
+#define RGB_MATRIX_NUM_COLS (DM163_NUM_LEDS / 3)
+#define COLOR_BUFFER_SIZE RGB_MATRIX_NUM_ROWS
+
+typedef struct DM163State {
+DeviceState parent_obj;
+
+/* DM163 driver */
+uint64_t bank0_shift_register[3];
+uint64_t bank1_shift_register[3];
+uint16_t latched_outputs[DM163_NUM_LEDS];
+uint16_t outputs[DM163_NUM_LEDS];
+qemu_irq sout;
+
+uint8_t sin;
+uint8_t dck;
+uint8_t rst_b;
+uint8_t lat_b;
+uint8_t selbk;
+uint8_t en_b;
+
+/* IM120417002 colors shield */
+uint8_t activated_rows;
+
+/* 8x8 RGB matrix */
+QemuConsole *console;
+uint8_t redraw;
+/* Rows currently being displayed on the matrix. */
+/* The last row is filled with 0 (turned off row) */
+uint32_t buffer[COLOR_BUFFER_SIZE + 1][RGB_MATRIX_NUM_COLS];
+uint8_t last_buffer_idx;
+uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS];
+/* Used to simulate retinal persistence of rows */
+uint8_t age_of_row[RGB_MATRIX_NUM_ROWS];
+} DM163State;
+
+#endif /* HW_DISPLAY_DM163_H */
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..d4e89dd344
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,333 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTENCE 3
+#define TURNED_OFF_ROW COLOR_BUFFER_SIZE
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
+VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS),
+VMSTATE_UINT16_ARRAY(outputs, DM163Stat

Re: [PATCH v6 0/3] Add device STM32L4x5 GPIO

2024-03-05 Thread Inès Varhol
Le 5 Mar 24, à 17:10, peter maydell peter.mayd...@linaro.org a écrit :

> On Sat, 24 Feb 2024 at 10:54, Inès Varhol  
> 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



[PATCH v7 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-03-05 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
Acked-by: Alistair Francis 
---
 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 af67b089ef..ee5f362405 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -30,6 +30,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"
@@ -49,6 +50,7 @@ struct Stm32l4x5SocState {
 OrIRQState exti_or_gates[NUM_EXTI_OR_GATES];
 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 bf9926057b..40e294f838 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -28,6 +28,7 @@
 #include "sysemu/sysemu.h"
 #include "hw/or-irq.h"
 #include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
 #include "hw/qdev-clock.h"
 #include "hw/misc/unimp.h"
 
@@ -99,6 +100,22 @@ static const int 
exti_or_gate1_lines_in[EXTI_OR_GATE1_NUM_LINES_IN] = {
 16, 35, 36, 37, 38,
 };
 
+static const struct {
+uint32_t addr;
+uint32_t moder_reset;
+uint32_t ospeedr_reset;
+uint32_t pupdr_reset;
+} stm32l4x5_gpio_cfg[NUM_GPIOS] = {
+{ 0x4800, 0xABFF, 0x0C00, 0x6400 },
+{ 0x48000400, 0xFEBF, 0x, 0x0100 },
+{ 0x48000800, 0x, 0x, 0x },
+{ 0x48000C00, 0x, 0x, 0x },
+{ 0x48001000, 0x, 0x, 0x },
+{ 0x48001400, 0x, 0x, 0x },
+{ 0x48001800, 0x, 0x, 0x },
+{ 0x48001C00, 0x000F, 0x, 0x },
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -110,6 +127,11 @@ static void stm32l4x5_soc_initfn(Object *obj)
 }
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >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, >gpio[i], TYPE_STM32L4X5_GPIO);
+}
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -118,8 +140,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(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -160,17 +183,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(>gpio[i]);
+qdev_prop_set_string(dev, "name", name);
+qdev_prop_set_uint32(dev, "mode-reset",
+ stm32l4x5_gpio_cfg[i

[PATCH v7 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-03-05 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/stm32l4x5_gpio-test.c | 551 ++
 tests/qtest/meson.build   |   3 +-
 2 files changed, 553 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 00..cc56be2031
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,551 @@
+/*
+ * QTest testcase for STM32L4x5_GPIO
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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, 0x);
+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(gpi

[PATCH v7 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-03-05 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
Acked-by: Alistair Francis 
---
 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 4183f2f3ab..4d96f855de 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1133,6 +1133,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 00..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 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 00..63b8763e9d
--- /dev/null
+++ b/hw/g

[PATCH v7 0/3] Add device STM32L4x5 GPIO

2024-03-05 Thread Inès Varhol
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

Changes from v6 :
- rebasing on main
- removing QTest `clock_enable()` as it isn't actually a GPIO test

Changes from v5 :
- deduplicating macro constant `GPIO_NUM_PINS` that was defined both
in stm32l4x5_syscfg.h and stm32l4x5_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

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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  | 551 +
 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, 1175 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




[PATCH v3 1/5] hw/display : Add device DM163

2024-02-28 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Acked-by: Alistair Francis 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 docs/system/arm/b-l475e-iot01a.rst |   3 +-
 include/hw/display/dm163.h |  57 ++
 hw/display/dm163.c | 308 +
 hw/display/Kconfig |   3 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  13 ++
 6 files changed, 384 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c

diff --git a/docs/system/arm/b-l475e-iot01a.rst 
b/docs/system/arm/b-l475e-iot01a.rst
index 0afef8e4f4..60b9611167 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -12,13 +12,14 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of 
sensors.
 Supported devices
 """""""""""""""""
 
-Currently B-L475E-IOT01A machine's only supports the following devices:
+Currently B-L475E-IOT01A machine's supports the following devices:
 
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
 - STM32L4x5 GPIOs (General-purpose I/Os)
+- optional 8x8 led display (based on DM163 driver)
 
 Missing devices
 """""""""""""""
diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h
new file mode 100644
index 00..aa775e51e1
--- /dev/null
+++ b/include/hw/display/dm163.h
@@ -0,0 +1,57 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_DM163_H
+#define HW_DISPLAY_DM163_H
+
+#include "qom/object.h"
+#include "hw/qdev-core.h"
+
+#define TYPE_DM163 "dm163"
+OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163);
+
+#define DM163_NUM_LEDS 24
+#define RGB_MATRIX_NUM_ROWS 8
+#define RGB_MATRIX_NUM_COLS (DM163_NUM_LEDS / 3)
+#define COLOR_BUFFER_SIZE RGB_MATRIX_NUM_ROWS
+
+typedef struct DM163State {
+DeviceState parent_obj;
+
+/* DM163 driver */
+uint64_t bank0_shift_register[3];
+uint64_t bank1_shift_register[3];
+uint16_t latched_outputs[DM163_NUM_LEDS];
+uint16_t outputs[DM163_NUM_LEDS];
+qemu_irq sout;
+
+uint8_t dck;
+uint8_t en_b;
+uint8_t lat_b;
+uint8_t rst_b;
+uint8_t selbk;
+uint8_t sin;
+
+/* IM120417002 colors shield */
+uint8_t activated_rows;
+
+/* 8x8 RGB matrix */
+QemuConsole *console;
+/* Rows currently being displayed on the matrix. */
+/* The last row is filled with 0 (turned off row) */
+uint32_t buffer[COLOR_BUFFER_SIZE + 1][RGB_MATRIX_NUM_COLS];
+uint8_t last_buffer_idx;
+uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS];
+/* Used to simulate retinal persistance of rows */
+uint8_t age_of_row[RGB_MATRIX_NUM_ROWS];
+} DM163State;
+
+#endif /* HW_DISPLAY_DM163_H */
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..87e886356a
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,308 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTANCE 4
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT8(activated_rows, DM163State),
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
+VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS),
+VMSTATE_UINT16_ARRAY(outputs, DM163Stat

[PATCH v3 3/5] hw/arm : Create Bl475eMachineState

2024-02-28 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 44 +
 1 file changed, 31 insertions(+), 13 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index d862aa43fc..2b570b3e09 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -2,8 +2,8 @@
  * B-L475E-IOT01A Discovery Kit machine
  * (B-L475E-IOT01A IoT Node)
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -32,33 +32,51 @@
 
 /* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
-static void b_l475e_iot01a_init(MachineState *machine)
+#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
+OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
+
+typedef struct Bl475eMachineState {
+MachineState parent_obj;
+
+Stm32l4x5SocState soc;
+} Bl475eMachineState;
+
+static void bl475e_init(MachineState *machine)
 {
+Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
-DeviceState *dev;
 
-dev = qdev_new(TYPE_STM32L4X5XG_SOC);
-object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), _fatal);
+object_initialize_child(OBJECT(machine), "soc", >soc,
+TYPE_STM32L4X5XG_SOC);
+sysbus_realize(SYS_BUS_DEVICE(>soc), _fatal);
 
-sc = STM32L4X5_SOC_GET_CLASS(dev);
+sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
-   machine->kernel_filename,
-   0, sc->flash_size);
+machine->kernel_filename, 0, sc->flash_size);
 }
 
-static void b_l475e_iot01a_machine_init(MachineClass *mc)
+static void bl475e_machine_init(ObjectClass *oc, void *data)
 {
+MachineClass *mc = MACHINE_CLASS(oc);
 static const char *machine_valid_cpu_types[] = {
 ARM_CPU_TYPE_NAME("cortex-m4"),
 NULL
 };
 mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
-mc->init = b_l475e_iot01a_init;
+mc->init = bl475e_init;
 mc->valid_cpu_types = machine_valid_cpu_types;
 
 /* SRAM pre-allocated as part of the SoC instantiation */
 mc->default_ram_size = 0;
 }
 
-DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
+static const TypeInfo bl475e_machine_type[] = {
+{
+.name   = TYPE_B_L475E_IOT01A,
+.parent = TYPE_MACHINE,
+.instance_size  = sizeof(Bl475eMachineState),
+.class_init = bl475e_machine_init,
+}
+};
+
+DEFINE_TYPES(bl475e_machine_type)
-- 
2.43.2




[PATCH v3 5/5] tests/qtest : Add testcase for DM163

2024-02-28 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are prpagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/dm163-test.c | 192 +++
 tests/qtest/meson.build  |   5 +
 2 files changed, 197 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..6f88ceef44
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,192 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define SIN 8
+#define DCK 9
+#define RST_B 10
+#define LAT_B 11
+#define SELBK 12
+#define EN_B 13
+
+#define DEVICE_NAME "/machine/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const long bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_gpio_connection(void)
+{
+QTestState *qts = qtest_init("-M b-l475e-iot01a");
+

[PATCH v3 2/5] hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC

2024-02-28 Thread Inès Varhol
Exposing SYSCFG inputs to the SoC is practical in order to wire the SoC
to the optional DM163 display from the board code (GPIOs outputs need
to be connected to both SYSCFG inputs and DM163 inputs).

STM32L4x5 SYSCFG in-irq interception needed to be changed accordingly.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---

Hello,

If SYSCFG inputs are exposed, should GPIOs be part of the board
rather than the SoC?

Best regards,

Ines

 hw/arm/stm32l4x5_soc.c  |  6 --
 tests/qtest/stm32l4x5_gpio-test.c   | 12 +++-
 tests/qtest/stm32l4x5_syscfg-test.c | 16 +---
 3 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 072671bdfb..8ba0dfc5e7 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -1,8 +1,8 @@
 /*
  * STM32L4x5 SoC family
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -196,6 +196,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+qdev_pass_gpios(DEVICE(>syscfg), dev_soc, NULL);
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index cd4fd9bae2..bec83b3c1d 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -50,6 +50,8 @@
 #define OTYPER_PUSH_PULL 0
 #define OTYPER_OPEN_DRAIN 1
 
+#define SYSCFG "/machine/soc"
+
 const uint32_t moder_reset[NUM_GPIOS] = {
 0xABFF,
 0xFEBF,
@@ -306,7 +308,7 @@ static void test_gpio_output_mode(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Set a bit in ODR and check nothing happens */
 gpio_set_bit(gpio, ODR, pin, 1);
@@ -341,7 +343,7 @@ static void test_gpio_input_mode(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input, raise it, and check that the pin is high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -370,7 +372,7 @@ static void test_pull_up_pull_down(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input with pull-up, check the line is set high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -400,7 +402,7 @@ static void test_push_pull(const void *data)
 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");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in push-pull output */
 /* And checking the pin was disconnected */
@@ -447,7 +449,7 @@ static void test_open_drain(const void *data)
 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");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in open-drain output */
 /* And checking the pin was disconnected */
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index ed4801798d..eed9d5940b 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -1,8 +1,8 @@
 /*
  * QTest testcase for STM32L4x5_SYSCFG
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -25,6 +25,9 @@
 #define SYSCFG_SWPR2 0x28
 #define INVALID_ADDR 0x2C
 
+#define EXTI "/machine/soc/exti"
+#define SYSCFG "/machine/soc"
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -37,8 +40,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, "/machine/soc/syscfg",
-NULL, num, level);
+   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
 }
 
 

[PATCH v3 4/5] hw/arm : Connect DM163 to B-L475E-IOT01A

2024-02-28 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 59 +++--
 hw/arm/Kconfig  |  1 +
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 2b570b3e09..6f0bf68ca6 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -27,10 +27,37 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "qemu/error-report.h"
-#include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
+#include "hw/core/split-irq.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
+
+/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */
 
-/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const int dm163_input[NUM_DM163_INPUTS] = {
+1 * GPIO_NUM_PINS + 2,  /* ROW0  PB2   */
+0 * GPIO_NUM_PINS + 15, /* ROW1  PA15  */
+0 * GPIO_NUM_PINS + 2,  /* ROW2  PA2   */
+0 * GPIO_NUM_PINS + 7,  /* ROW3  PA7   */
+0 * GPIO_NUM_PINS + 6,  /* ROW4  PA6   */
+0 * GPIO_NUM_PINS + 5,  /* ROW5  PA5   */
+1 * GPIO_NUM_PINS + 0,  /* ROW6  PB0   */
+0 * GPIO_NUM_PINS + 3,  /* ROW7  PA3   */
+0 * GPIO_NUM_PINS + 4,  /* SIN (SDA) PA4   */
+1 * GPIO_NUM_PINS + 1,  /* DCK (SCK) PB1   */
+2 * GPIO_NUM_PINS + 3,  /* RST_B (RST) PC3 */
+2 * GPIO_NUM_PINS + 4,  /* LAT_B (LAT) PC4 */
+2 * GPIO_NUM_PINS + 5,  /* SELBK (SB)  PC5 */
+};
 
 #define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
 OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
@@ -39,12 +66,16 @@ typedef struct Bl475eMachineState {
 MachineState parent_obj;
 
 Stm32l4x5SocState soc;
+SplitIRQ gpio_splitters[NUM_DM163_INPUTS];
+DM163State dm163;
 } Bl475eMachineState;
 
 static void bl475e_init(MachineState *machine)
 {
 Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
+DeviceState *dev, *gpio_out_splitter;
+int gpio, pin;
 
 object_initialize_child(OBJECT(machine), "soc", >soc,
 TYPE_STM32L4X5XG_SOC);
@@ -53,6 +84,30 @@ static void bl475e_init(MachineState *machine)
 sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
 machine->kernel_filename, 0, sc->flash_size);
+
+if (object_class_by_name("dm163")) {
+object_initialize_child(OBJECT(machine), "dm163",
+>dm163, TYPE_DM163);
+dev = DEVICE(>dm163);
+qdev_realize(dev, NULL, _abort);
+
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]",
+>gpio_splitters[i], TYPE_SPLIT_IRQ);
+gpio_out_splitter = DEVICE(>gpio_splitters[i]);
+qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2);
+qdev_realize(gpio_out_splitter, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_out_splitter, 0,
+qdev_get_gpio_in(DEVICE(>soc), dm163_input[i]));
+qdev_connect_gpio_out(gpio_out_splitter, 1,
+qdev_get_gpio_in(dev, i));
+gpio = dm163_input[i] / GPIO_NUM_PINS;
+pin = dm163_input[i] % GPIO_NUM_PINS;
+qdev_connect_gpio_out(DEVICE(>soc.gpio[gpio]), pin,
+qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0));
+}
+}
 }
 
 static void bl475e_machine_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5776dbb19f..6c05bac99b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -458,6 +458,7 @@ config B_L475E_IOT01A
 default y
 depends on TCG && ARM
 select STM32L4X5_SOC
+imply DM163
 
 config STM32L4X5_SOC
 bool
-- 
2.43.2




[PATCH v3 0/5] Add device DM163 (led driver, matrix colors shield & display)

2024-02-28 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. This color shield can be plugged
on the Arduino board (or the B-L475E-IOT01A board) to drive an 8x8
RGB led matrix. This RGB led matrix takes advantage of retinal persistance
to seemingly display different colors in each row.



I'm stuck on some issues with this implementation :

1. Tests

TLDR: how can I provide a test or an example?

I've tested the display by running custom executables and
comparing to the result on the real board, but I don't
know how to test it using a QTest.

`qtest_init_internal` sets `-display none`
so there's no way to test the display visually.

There's no `visit_type_*` for arrays so accessing the DM163
buffer to check its content is complicated. I could technically
access all the elements in the array (returning a different element
each time in the getter for example), but that seems sketchy.

2. Frame Rate

It'd be convenient to set the QEMU console's refresh rate
in order to ensure that the delay before turning off rows
(4 frames currently) isn't too short. However
`dpy_ui_info_supported(s->console)` returns false.



Changes from v2 : Corrected typo in the Based-on message id

Changes from v1 :
- moving the DM163 from the SoC to the B-L475E-IOT01A machine
(changing config files and tests accordingly)
- restricting DM163 test to ARM & DM163 availability
- using `object_class_by_name()` to check for DM163 presence at run-time
- exporting SYSCFG inputs to the SoC (and adapting tests accordingly)

Thank you for your review Philippe :)

Based-on: 20240224105417.195674-1-ines.var...@telecom-paris.fr
([PATCH v6 0/3] Add device STM32L4x5 GPIO)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

Inès Varhol (5):
  hw/display : Add device DM163
  hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC
  hw/arm : Create Bl475eMachineState
  hw/arm : Connect DM163 to B-L475E-IOT01A
  tests/qtest : Add testcase for DM163

 docs/system/arm/b-l475e-iot01a.rst  |   3 +-
 include/hw/display/dm163.h  |  57 +
 hw/arm/b-l475e-iot01a.c | 103 --
 hw/arm/stm32l4x5_soc.c  |   6 +-
 hw/display/dm163.c  | 308 
 tests/qtest/dm163-test.c| 192 +
 tests/qtest/stm32l4x5_gpio-test.c   |  12 +-
 tests/qtest/stm32l4x5_syscfg-test.c |  16 +-
 hw/arm/Kconfig  |   1 +
 hw/display/Kconfig  |   3 +
 hw/display/meson.build  |   1 +
 hw/display/trace-events |  13 ++
 tests/qtest/meson.build |   5 +
 13 files changed, 690 insertions(+), 30 deletions(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.2




[PATCH v2 5/5] tests/qtest : Add testcase for DM163

2024-02-28 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are propagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/dm163-test.c | 192 +++
 tests/qtest/meson.build  |   5 +
 2 files changed, 197 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..6f88ceef44
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,192 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define SIN 8
+#define DCK 9
+#define RST_B 10
+#define LAT_B 11
+#define SELBK 12
+#define EN_B 13
+
+#define DEVICE_NAME "/machine/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const long bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_gpio_connection(void)
+{
+QTestState *qts = qtest_init("-M b-l475e-iot01a");

[PATCH v2 2/5] hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC

2024-02-28 Thread Inès Varhol
Exposing SYSCFG inputs to the SoC is practical in order to wire the SoC
to the optional DM163 display from the board code (GPIOs outputs need
to be connected to both SYSCFG inputs and DM163 inputs).

STM32L4x5 SYSCFG in-irq interception needed to be changed accordingly.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---

Hello,

If SYSCFG inputs are exposed, should GPIOs be part of the board
rather than the SoC?

Best regards,

Ines

 hw/arm/stm32l4x5_soc.c  |  6 --
 tests/qtest/stm32l4x5_gpio-test.c   | 12 +++-
 tests/qtest/stm32l4x5_syscfg-test.c | 16 +---
 3 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 072671bdfb..8ba0dfc5e7 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -1,8 +1,8 @@
 /*
  * STM32L4x5 SoC family
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -196,6 +196,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+qdev_pass_gpios(DEVICE(>syscfg), dev_soc, NULL);
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/tests/qtest/stm32l4x5_gpio-test.c 
b/tests/qtest/stm32l4x5_gpio-test.c
index cd4fd9bae2..bec83b3c1d 100644
--- a/tests/qtest/stm32l4x5_gpio-test.c
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -50,6 +50,8 @@
 #define OTYPER_PUSH_PULL 0
 #define OTYPER_OPEN_DRAIN 1
 
+#define SYSCFG "/machine/soc"
+
 const uint32_t moder_reset[NUM_GPIOS] = {
 0xABFF,
 0xFEBF,
@@ -306,7 +308,7 @@ static void test_gpio_output_mode(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Set a bit in ODR and check nothing happens */
 gpio_set_bit(gpio, ODR, pin, 1);
@@ -341,7 +343,7 @@ static void test_gpio_input_mode(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input, raise it, and check that the pin is high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -370,7 +372,7 @@ static void test_pull_up_pull_down(const void *data)
 uint32_t gpio = ((uint64_t)data) >> 32;
 unsigned int gpio_id = get_gpio_id(gpio);
 
-qtest_irq_intercept_in(global_qtest, "/machine/soc/syscfg");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Configure a line as input with pull-up, check the line is set high */
 gpio_set_2bits(gpio, MODER, pin, MODER_INPUT);
@@ -400,7 +402,7 @@ static void test_push_pull(const void *data)
 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");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in push-pull output */
 /* And checking the pin was disconnected */
@@ -447,7 +449,7 @@ static void test_open_drain(const void *data)
 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");
+qtest_irq_intercept_in(global_qtest, SYSCFG);
 
 /* Setting a line high externally, configuring it in open-drain output */
 /* And checking the pin was disconnected */
diff --git a/tests/qtest/stm32l4x5_syscfg-test.c 
b/tests/qtest/stm32l4x5_syscfg-test.c
index ed4801798d..eed9d5940b 100644
--- a/tests/qtest/stm32l4x5_syscfg-test.c
+++ b/tests/qtest/stm32l4x5_syscfg-test.c
@@ -1,8 +1,8 @@
 /*
  * QTest testcase for STM32L4x5_SYSCFG
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
@@ -25,6 +25,9 @@
 #define SYSCFG_SWPR2 0x28
 #define INVALID_ADDR 0x2C
 
+#define EXTI "/machine/soc/exti"
+#define SYSCFG "/machine/soc"
+
 static void syscfg_writel(unsigned int offset, uint32_t value)
 {
 writel(SYSCFG_BASE_ADDR + offset, value);
@@ -37,8 +40,7 @@ static uint32_t syscfg_readl(unsigned int offset)
 
 static void syscfg_set_irq(int num, int level)
 {
-   qtest_set_irq_in(global_qtest, "/machine/soc/syscfg",
-NULL, num, level);
+   qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
 }
 
 

[PATCH v2 4/5] hw/arm : Connect DM163 to B-L475E-IOT01A

2024-02-28 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 59 +++--
 hw/arm/Kconfig  |  1 +
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index 2b570b3e09..6f0bf68ca6 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -27,10 +27,37 @@
 #include "hw/boards.h"
 #include "hw/qdev-properties.h"
 #include "qemu/error-report.h"
-#include "hw/arm/stm32l4x5_soc.h"
 #include "hw/arm/boot.h"
+#include "hw/core/split-irq.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
+
+/* B-L475E-IOT01A implementation is inspired from netduinoplus2 and arduino */
 
-/* B-L475E-IOT01A implementation is derived from netduinoplus2 */
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const int dm163_input[NUM_DM163_INPUTS] = {
+1 * GPIO_NUM_PINS + 2,  /* ROW0  PB2   */
+0 * GPIO_NUM_PINS + 15, /* ROW1  PA15  */
+0 * GPIO_NUM_PINS + 2,  /* ROW2  PA2   */
+0 * GPIO_NUM_PINS + 7,  /* ROW3  PA7   */
+0 * GPIO_NUM_PINS + 6,  /* ROW4  PA6   */
+0 * GPIO_NUM_PINS + 5,  /* ROW5  PA5   */
+1 * GPIO_NUM_PINS + 0,  /* ROW6  PB0   */
+0 * GPIO_NUM_PINS + 3,  /* ROW7  PA3   */
+0 * GPIO_NUM_PINS + 4,  /* SIN (SDA) PA4   */
+1 * GPIO_NUM_PINS + 1,  /* DCK (SCK) PB1   */
+2 * GPIO_NUM_PINS + 3,  /* RST_B (RST) PC3 */
+2 * GPIO_NUM_PINS + 4,  /* LAT_B (LAT) PC4 */
+2 * GPIO_NUM_PINS + 5,  /* SELBK (SB)  PC5 */
+};
 
 #define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
 OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
@@ -39,12 +66,16 @@ typedef struct Bl475eMachineState {
 MachineState parent_obj;
 
 Stm32l4x5SocState soc;
+SplitIRQ gpio_splitters[NUM_DM163_INPUTS];
+DM163State dm163;
 } Bl475eMachineState;
 
 static void bl475e_init(MachineState *machine)
 {
 Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
+DeviceState *dev, *gpio_out_splitter;
+int gpio, pin;
 
 object_initialize_child(OBJECT(machine), "soc", >soc,
 TYPE_STM32L4X5XG_SOC);
@@ -53,6 +84,30 @@ static void bl475e_init(MachineState *machine)
 sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
 machine->kernel_filename, 0, sc->flash_size);
+
+if (object_class_by_name("dm163")) {
+object_initialize_child(OBJECT(machine), "dm163",
+>dm163, TYPE_DM163);
+dev = DEVICE(>dm163);
+qdev_realize(dev, NULL, _abort);
+
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+object_initialize_child(OBJECT(machine), "gpio-out-splitters[*]",
+>gpio_splitters[i], TYPE_SPLIT_IRQ);
+gpio_out_splitter = DEVICE(>gpio_splitters[i]);
+qdev_prop_set_uint32(gpio_out_splitter, "num-lines", 2);
+qdev_realize(gpio_out_splitter, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_out_splitter, 0,
+qdev_get_gpio_in(DEVICE(>soc), dm163_input[i]));
+qdev_connect_gpio_out(gpio_out_splitter, 1,
+qdev_get_gpio_in(dev, i));
+gpio = dm163_input[i] / GPIO_NUM_PINS;
+pin = dm163_input[i] % GPIO_NUM_PINS;
+qdev_connect_gpio_out(DEVICE(>soc.gpio[gpio]), pin,
+qdev_get_gpio_in(DEVICE(gpio_out_splitter), 0));
+}
+}
 }
 
 static void bl475e_machine_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5776dbb19f..6c05bac99b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -458,6 +458,7 @@ config B_L475E_IOT01A
 default y
 depends on TCG && ARM
 select STM32L4X5_SOC
+imply DM163
 
 config STM32L4X5_SOC
 bool
-- 
2.43.2




[PATCH v2 3/5] hw/arm : Create Bl475eMachineState

2024-02-28 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/b-l475e-iot01a.c | 44 +
 1 file changed, 31 insertions(+), 13 deletions(-)

diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
index d862aa43fc..2b570b3e09 100644
--- a/hw/arm/b-l475e-iot01a.c
+++ b/hw/arm/b-l475e-iot01a.c
@@ -2,8 +2,8 @@
  * B-L475E-IOT01A Discovery Kit machine
  * (B-L475E-IOT01A IoT Node)
  *
- * Copyright (c) 2023 Arnaud Minier 
- * Copyright (c) 2023 Inès Varhol 
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  *
@@ -32,33 +32,51 @@
 
 /* B-L475E-IOT01A implementation is derived from netduinoplus2 */
 
-static void b_l475e_iot01a_init(MachineState *machine)
+#define TYPE_B_L475E_IOT01A MACHINE_TYPE_NAME("b-l475e-iot01a")
+OBJECT_DECLARE_SIMPLE_TYPE(Bl475eMachineState, B_L475E_IOT01A)
+
+typedef struct Bl475eMachineState {
+MachineState parent_obj;
+
+Stm32l4x5SocState soc;
+} Bl475eMachineState;
+
+static void bl475e_init(MachineState *machine)
 {
+Bl475eMachineState *s = B_L475E_IOT01A(machine);
 const Stm32l4x5SocClass *sc;
-DeviceState *dev;
 
-dev = qdev_new(TYPE_STM32L4X5XG_SOC);
-object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
-sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), _fatal);
+object_initialize_child(OBJECT(machine), "soc", >soc,
+TYPE_STM32L4X5XG_SOC);
+sysbus_realize(SYS_BUS_DEVICE(>soc), _fatal);
 
-sc = STM32L4X5_SOC_GET_CLASS(dev);
+sc = STM32L4X5_SOC_GET_CLASS(>soc);
 armv7m_load_kernel(ARM_CPU(first_cpu),
-   machine->kernel_filename,
-   0, sc->flash_size);
+machine->kernel_filename, 0, sc->flash_size);
 }
 
-static void b_l475e_iot01a_machine_init(MachineClass *mc)
+static void bl475e_machine_init(ObjectClass *oc, void *data)
 {
+MachineClass *mc = MACHINE_CLASS(oc);
 static const char *machine_valid_cpu_types[] = {
 ARM_CPU_TYPE_NAME("cortex-m4"),
 NULL
 };
 mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
-mc->init = b_l475e_iot01a_init;
+mc->init = bl475e_init;
 mc->valid_cpu_types = machine_valid_cpu_types;
 
 /* SRAM pre-allocated as part of the SoC instantiation */
 mc->default_ram_size = 0;
 }
 
-DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
+static const TypeInfo bl475e_machine_type[] = {
+{
+.name   = TYPE_B_L475E_IOT01A,
+.parent = TYPE_MACHINE,
+.instance_size  = sizeof(Bl475eMachineState),
+.class_init = bl475e_machine_init,
+}
+};
+
+DEFINE_TYPES(bl475e_machine_type)
-- 
2.43.2




[PATCH v2 1/5] hw/display : Add device DM163

2024-02-28 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Acked-by: Alistair Francis 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 docs/system/arm/b-l475e-iot01a.rst |   3 +-
 include/hw/display/dm163.h |  57 ++
 hw/display/dm163.c | 308 +
 hw/display/Kconfig |   3 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  13 ++
 6 files changed, 384 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c

diff --git a/docs/system/arm/b-l475e-iot01a.rst 
b/docs/system/arm/b-l475e-iot01a.rst
index 0afef8e4f4..60b9611167 100644
--- a/docs/system/arm/b-l475e-iot01a.rst
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -12,13 +12,14 @@ USART, I2C, SPI, CAN and USB OTG, as well as a variety of 
sensors.
 Supported devices
 """""""""""""""""
 
-Currently B-L475E-IOT01A machine's only supports the following devices:
+Currently B-L475E-IOT01A machine's supports the following devices:
 
 - Cortex-M4F based STM32L4x5 SoC
 - STM32L4x5 EXTI (Extended interrupts and events controller)
 - STM32L4x5 SYSCFG (System configuration controller)
 - STM32L4x5 RCC (Reset and clock control)
 - STM32L4x5 GPIOs (General-purpose I/Os)
+- optional 8x8 led display (based on DM163 driver)
 
 Missing devices
 """""""""""""""
diff --git a/include/hw/display/dm163.h b/include/hw/display/dm163.h
new file mode 100644
index 00..aa775e51e1
--- /dev/null
+++ b/include/hw/display/dm163.h
@@ -0,0 +1,57 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_DM163_H
+#define HW_DISPLAY_DM163_H
+
+#include "qom/object.h"
+#include "hw/qdev-core.h"
+
+#define TYPE_DM163 "dm163"
+OBJECT_DECLARE_SIMPLE_TYPE(DM163State, DM163);
+
+#define DM163_NUM_LEDS 24
+#define RGB_MATRIX_NUM_ROWS 8
+#define RGB_MATRIX_NUM_COLS (DM163_NUM_LEDS / 3)
+#define COLOR_BUFFER_SIZE RGB_MATRIX_NUM_ROWS
+
+typedef struct DM163State {
+DeviceState parent_obj;
+
+/* DM163 driver */
+uint64_t bank0_shift_register[3];
+uint64_t bank1_shift_register[3];
+uint16_t latched_outputs[DM163_NUM_LEDS];
+uint16_t outputs[DM163_NUM_LEDS];
+qemu_irq sout;
+
+uint8_t dck;
+uint8_t en_b;
+uint8_t lat_b;
+uint8_t rst_b;
+uint8_t selbk;
+uint8_t sin;
+
+/* IM120417002 colors shield */
+uint8_t activated_rows;
+
+/* 8x8 RGB matrix */
+QemuConsole *console;
+/* Rows currently being displayed on the matrix. */
+/* The last row is filled with 0 (turned off row) */
+uint32_t buffer[COLOR_BUFFER_SIZE + 1][RGB_MATRIX_NUM_COLS];
+uint8_t last_buffer_idx;
+uint8_t buffer_idx_of_row[RGB_MATRIX_NUM_ROWS];
+/* Used to simulate retinal persistance of rows */
+uint8_t age_of_row[RGB_MATRIX_NUM_ROWS];
+} DM163State;
+
+#endif /* HW_DISPLAY_DM163_H */
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..87e886356a
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,308 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTANCE 4
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT8(activated_rows, DM163State),
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
+VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS),
+VMSTATE_UINT16_ARRAY(outputs, DM163Stat

[PATCH v2 0/5] Add device DM163 (led driver, matrix colors shield & display)

2024-02-28 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. This color shield can be plugged
on the Arduino board (or the B-L475E-IOT01A board) to drive an 8x8
RGB led matrix. This RGB led matrix takes advantage of retinal persistance
to seemingly display different colors in each row.



I'm stuck on some issues with this implementation :

1. Tests

TLDR: how can I provide a test or an example?

I've tested the display by running custom executables and
comparing to the result on the real board, but I don't
know how to test it using a QTest.

`qtest_init_internal` sets `-display none`
so there's no way to test the display visually.

There's no `visit_type_*` for arrays so accessing the DM163
buffer to check its content is complicated. I could technically
access all the elements in the array (returning a different element
each time in the getter for example), but that seems sketchy.

2. Frame Rate

It'd be convenient to set the QEMU console's refresh rate
in order to ensure that the delay before turning off rows
(4 frames currently) isn't too short. However
`dpy_ui_info_supported(s->console)` returns false.



Changes from v1 :
- moving the DM163 from the SoC to the B-L475E-IOT01A machine
(changing config files and tests accordingly)
- restricting DM163 test to ARM & DM163 availability
- using `object_class_by_name()` to check for DM163 presence at run-time
- exporting SYSCFG inputs to the SoC (and adapting tests accordingly)

Thank you for your review Philippe :)

Based-on: 220240224105417.195674-1-ines.var...@telecom-paris.fr
([PATCH v6 0/3] Add device STM32L4x5 GPIO)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

Inès Varhol (5):
  hw/display : Add device DM163
  hw/arm : Pass STM32L4x5 SYSCFG gpios to STM32L4x5 SoC
  hw/arm : Create Bl475eMachineState
  hw/arm : Connect DM163 to B-L475E-IOT01A
  tests/qtest : Add testcase for DM163

 docs/system/arm/b-l475e-iot01a.rst  |   3 +-
 include/hw/display/dm163.h  |  57 +
 hw/arm/b-l475e-iot01a.c | 103 --
 hw/arm/stm32l4x5_soc.c  |   6 +-
 hw/display/dm163.c  | 308 
 tests/qtest/dm163-test.c| 192 +
 tests/qtest/stm32l4x5_gpio-test.c   |  12 +-
 tests/qtest/stm32l4x5_syscfg-test.c |  16 +-
 hw/arm/Kconfig  |   1 +
 hw/display/Kconfig  |   3 +
 hw/display/meson.build  |   1 +
 hw/display/trace-events |  13 ++
 tests/qtest/meson.build |   5 +
 13 files changed, 690 insertions(+), 30 deletions(-)
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 hw/display/dm163.c
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.2




[PATCH v6 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-02-24 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 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 00..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 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 00..63b8763e9d
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1

[PATCH v6 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-02-24 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 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] = {
+{ 0x4800, 0xABFF, 0x0C00, 0x6400 },
+{ 0x48000400, 0xFEBF, 0x, 0x0100 },
+{ 0x48000800, 0x, 0x, 0x },
+{ 0x48000C00, 0x, 0x, 0x },
+{ 0x48001000, 0x, 0x, 0x },
+{ 0x48001400, 0x, 0x, 0x },
+{ 0x48001800, 0x, 0x, 0x },
+{ 0x48001C00, 0x000F, 0x, 0x },
+};
+
 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", >exti, TYPE_STM32L4X5_EXTI);
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >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, >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(>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(>gpio[i]);
+qdev_prop_set_string(dev, "name", name);
+qdev_prop_set_uint32(dev, "mode-reset",
+  

[PATCH v6 0/3] Add device STM32L4x5 GPIO

2024-02-24 Thread Inès Varhol
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.min...@telecom-paris.fr
([PATCH v5 0/8] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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




[PATCH v6 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-02-24 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 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 00..cd4fd9bae2
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,586 @@
+/*
+ * QTest testcase for STM32L4x5_GPIO
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 400
+#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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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, 0x);
+g_assert_false(qdict_haskey(r, "error"));
+qobject_unref(r);
+}
+
+static uint32_t

[PATCH v5 0/3] Add device STM32L4x5 GPIO

2024-02-23 Thread Inès Varhol
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

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.min...@telecom-paris.fr
([PATCH v5 0/8] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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   |  70 
 hw/arm/stm32l4x5_soc.c |  68 +++-
 hw/gpio/stm32l4x5_gpio.c   | 477 +++
 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 +-
 12 files changed, 1205 insertions(+), 17 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




[PATCH v5 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-02-23 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 include/hw/arm/stm32l4x5_soc.h |  2 +
 hw/arm/stm32l4x5_soc.c | 68 +++---
 hw/arm/Kconfig |  3 +-
 3 files changed, 58 insertions(+), 15 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/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 347a5377e5..be0f11dc1b 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -78,6 +78,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] = {
+{ 0x4800, 0xABFF, 0x0C00, 0x6400 },
+{ 0x48000400, 0xFEBF, 0x, 0x0100 },
+{ 0x48000800, 0x, 0x, 0x },
+{ 0x48000C00, 0x, 0x, 0x },
+{ 0x48001000, 0x, 0x, 0x },
+{ 0x48001400, 0x, 0x, 0x },
+{ 0x48001800, 0x, 0x, 0x },
+{ 0x48001C00, 0x000F, 0x, 0x },
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -85,6 +101,11 @@ static void stm32l4x5_soc_initfn(Object *obj)
 object_initialize_child(obj, "exti", >exti, TYPE_STM32L4X5_EXTI);
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >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, >gpio[i], TYPE_STM32L4X5_GPIO);
+}
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -93,8 +114,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(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -135,17 +157,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(>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(>gpio[i]);
+g_free(name);
+name = g_strdup_printf("gpio%c-out", 'a' + i);
+qdev_connect_clock_in(DEVICE(>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(>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(>gpio[i]), j,
+  qdev_get_gpio_in(DEVICE(>syscfg),
+  

[PATCH v5 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-02-23 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
Reviewed-by: Philippe Mathieu-Daudé 
---
 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 00..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 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 00..63b8763e9d
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1

[PATCH v5 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-02-23 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 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 00..cd4fd9bae2
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,586 @@
+/*
+ * QTest testcase for STM32L4x5_GPIO
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 400
+#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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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, 0x);
+g_assert_false(qdict_haskey(r, "error"));
+qobject_unref(r);
+}
+
+static uint32_t

[PATCH v2 1/2] hw/arm: Use TYPE_OR_IRQ when connecting STM32L4x5 EXTI fan-in IRQs

2024-02-20 Thread Inès Varhol
Fixes: 52671f69f7a4 ("[PATCH v8 0/3] Add device STM32L4x5 EXTI")
Signed-off-by: Inès Varhol 
---
 include/hw/arm/stm32l4x5_soc.h |  4 ++
 hw/arm/stm32l4x5_soc.c | 80 +-
 2 files changed, 74 insertions(+), 10 deletions(-)

diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index baf70410b5..4f314b7a93 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -26,6 +26,7 @@
 
 #include "exec/memory.h"
 #include "hw/arm/armv7m.h"
+#include "hw/or-irq.h"
 #include "hw/misc/stm32l4x5_syscfg.h"
 #include "hw/misc/stm32l4x5_exti.h"
 #include "qom/object.h"
@@ -36,12 +37,15 @@
 #define TYPE_STM32L4X5XG_SOC "stm32l4x5xg-soc"
 OBJECT_DECLARE_TYPE(Stm32l4x5SocState, Stm32l4x5SocClass, STM32L4X5_SOC)
 
+#define NUM_EXTI_OR_GATES 4
+
 struct Stm32l4x5SocState {
 SysBusDevice parent_obj;
 
 ARMv7MState armv7m;
 
 Stm32l4x5ExtiState exti;
+OrIRQState exti_or_gates[NUM_EXTI_OR_GATES];
 Stm32l4x5SyscfgState syscfg;
 
 MemoryRegion sram1;
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index f470ff74ec..d1786e0da1 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -26,6 +26,7 @@
 #include "qapi/error.h"
 #include "exec/address-spaces.h"
 #include "sysemu/sysemu.h"
+#include "hw/or-irq.h"
 #include "hw/arm/stm32l4x5_soc.h"
 #include "hw/qdev-clock.h"
 #include "hw/misc/unimp.h"
@@ -42,21 +43,24 @@
 #define NUM_EXTI_IRQ 40
 /* Match exti line connections with their CPU IRQ number */
 /* See Vector Table (Reference Manual p.396) */
+/*
+ * Some IRQs are connected to the same CPU IRQ (denoted by -1)
+ * and require an intermediary OR gate to function correctly.
+ */
 static const int exti_irq[NUM_EXTI_IRQ] = {
 6,  /* GPIO[0] */
 7,  /* GPIO[1] */
 8,  /* GPIO[2] */
 9,  /* GPIO[3] */
 10, /* GPIO[4] */
-23, 23, 23, 23, 23, /* GPIO[5..9]  */
-40, 40, 40, 40, 40, 40, /* GPIO[10..15]*/
-1,  /* PVD */
+-1, -1, -1, -1, -1, /* GPIO[5..9] OR gate 23   */
+-1, -1, -1, -1, -1, -1, /* GPIO[10..15] OR gate 40 */
+-1, /* PVD OR gate 1   */
 67, /* OTG_FS_WKUP, Direct */
 41, /* RTC_ALARM   */
 2,  /* RTC_TAMP_STAMP2/CSS_LSE */
 3,  /* RTC wakeup timer*/
-63, /* COMP1   */
-63, /* COMP2   */
+-1, -1, /* COMP[1..2] OR gate 63   */
 31, /* I2C1 wakeup, Direct */
 33, /* I2C2 wakeup, Direct */
 72, /* I2C3 wakeup, Direct */
@@ -69,18 +73,39 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 65, /* LPTIM1, Direct  */
 66, /* LPTIM2, Direct  */
 76, /* SWPMI1 wakeup, Direct   */
-1,  /* PVM1 wakeup */
-1,  /* PVM2 wakeup */
-1,  /* PVM3 wakeup */
-1,  /* PVM4 wakeup */
+-1, -1, -1, -1, /* PVM[1..4] OR gate 1 */
 78  /* LCD wakeup, Direct  */
 };
 
+static const int exti_or_gates_out[NUM_EXTI_OR_GATES] = {
+23, 40, 63, 1,
+};
+
+static const int exti_or_gates_num_lines_in[NUM_EXTI_OR_GATES] = {
+5, 6, 2, 5,
+};
+
+/* 3 OR gates with consecutive inputs */
+#define NUM_EXTI_SIMPLE_OR_GATES 3
+static const int exti_or_gates_first_line_in[NUM_EXTI_SIMPLE_OR_GATES] = {
+5, 10, 21,
+};
+
+/* 1 OR gate with non-consecutive inputs */
+#define EXTI_OR_GATE1_NUM_LINES_IN 5
+static const int exti_or_gate1_lines_in[EXTI_OR_GATE1_NUM_LINES_IN] = {
+16, 35, 36, 37, 38,
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
 
 object_initialize_child(obj, "exti", >exti, TYPE_STM32L4X5_EXTI);
+for (unsigned i = 0; i < NUM_EXTI_OR_GATES; i++) {
+object_initialize_child(obj, "exti_or_gates[*]", >exti_or_gates[i],
+TYPE_OR_IRQ);
+}
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 
 s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
@@ -175,8 +200,43 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(bus

[PATCH v2 0/2] hw/arm: Fix STM32L4x5 EXTI to CPU irq fan-in connections

2024-02-20 Thread Inès Varhol
The original code was connecting several outbounds qemu_irqs to the
same qemu_irq without using a TYPE_OR_IRQ.

This patch fixes the issue by using OR gates when necessary (1st commit).

I attempted to check that the problem is fixed by using a QTest (2nd commit)
but actually the test is passing even before the fix :
when any fan-in input line is raised, the output is raised too.

Changes from v1 :
- using SoC State fields for EXTI OR gates
- correcting length of array `exti_or_gates_num_lines_in`
- using a for loop in the test for more clarity
- correcting typo in test comment

Fixes: 52671f69f7a4 ("[PATCH v8 0/3] Add device STM32L4x5 EXTI")
Signed-off-by: Inès Varhol 

Inès Varhol (2):
  hw/arm: Use TYPE_OR_IRQ when connecting STM32L4x5 EXTI fan-in IRQs
  tests/qtest: Check that EXTI fan-in irqs are correctly connected

 include/hw/arm/stm32l4x5_soc.h|  4 ++
 hw/arm/stm32l4x5_soc.c| 80 +++
 tests/qtest/stm32l4x5_exti-test.c | 37 ++
 3 files changed, 111 insertions(+), 10 deletions(-)

-- 
2.43.2




[PATCH v2 2/2] tests/qtest: Check that EXTI fan-in irqs are correctly connected

2024-02-20 Thread Inès Varhol
This commit adds a QTest that verifies each input line of a specific
EXTI OR gate can influence the output line.

Signed-off-by: Inès Varhol 
Reviewed-by: Peter Maydell 
---

Hello,

I expected this test to fail after switching the two patch commits,
but it didn't.
I'm mentionning it in case it reveals a problem with the test I didn't notice.


 tests/qtest/stm32l4x5_exti-test.c | 37 +++
 1 file changed, 37 insertions(+)

diff --git a/tests/qtest/stm32l4x5_exti-test.c 
b/tests/qtest/stm32l4x5_exti-test.c
index c390077713..81830be8ae 100644
--- a/tests/qtest/stm32l4x5_exti-test.c
+++ b/tests/qtest/stm32l4x5_exti-test.c
@@ -31,6 +31,7 @@
 
 #define EXTI0_IRQ 6
 #define EXTI1_IRQ 7
+#define EXTI5_9_IRQ 23
 #define EXTI35_IRQ 1
 
 static void enable_nvic_irq(unsigned int n)
@@ -499,6 +500,40 @@ static void test_interrupt(void)
 g_assert_false(check_nvic_pending(EXTI1_IRQ));
 }
 
+static void test_orred_interrupts(void)
+{
+/*
+ * For lines EXTI5..9 (fanned-in to NVIC irq 23),
+ * test that raising the line pends interrupt
+ * 23 in NVIC.
+ */
+enable_nvic_irq(EXTI5_9_IRQ);
+/* Check that there are no interrupts already pending in PR */
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+/* Check that this specific interrupt isn't pending in NVIC */
+g_assert_false(check_nvic_pending(EXTI5_9_IRQ));
+
+/* Enable interrupt lines EXTI[5..9] */
+exti_writel(EXTI_IMR1, (0x1F << 5));
+
+/* Configure interrupt on rising edge */
+exti_writel(EXTI_RTSR1, (0x1F << 5));
+
+/* Raise GPIO line i, check that the interrupt is pending */
+for (unsigned i = 5; i < 10; i++) {
+exti_set_irq(i, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << i);
+g_assert_true(check_nvic_pending(EXTI5_9_IRQ));
+
+exti_writel(EXTI_PR1, 1 << i);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI5_9_IRQ));
+
+unpend_nvic_irq(EXTI5_9_IRQ);
+g_assert_false(check_nvic_pending(EXTI5_9_IRQ));
+}
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -515,6 +550,8 @@ int main(int argc, char **argv)
 qtest_add_func("stm32l4x5/exti/masked_interrupt", test_masked_interrupt);
 qtest_add_func("stm32l4x5/exti/interrupt", test_interrupt);
 qtest_add_func("stm32l4x5/exti/test_edge_selector", test_edge_selector);
+qtest_add_func("stm32l4x5/exti/test_orred_interrupts",
+   test_orred_interrupts);
 
 qtest_start("-machine b-l475e-iot01a");
 ret = g_test_run();
-- 
2.43.2




[PATCH 1/2] hw/arm: Use TYPE_OR_IRQ when connecting STM32L4x5 EXTI fan-in IRQs

2024-02-12 Thread Inès Varhol
Fixes: 52671f69f7a4 ("[PATCH v8 0/3] Add device STM32L4x5 EXTI")
Signed-off-by: Inès Varhol 
---
 hw/arm/stm32l4x5_soc.c | 69 --
 1 file changed, 59 insertions(+), 10 deletions(-)

diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index f470ff74ec..df5bb02315 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -26,6 +26,7 @@
 #include "qapi/error.h"
 #include "exec/address-spaces.h"
 #include "sysemu/sysemu.h"
+#include "hw/or-irq.h"
 #include "hw/arm/stm32l4x5_soc.h"
 #include "hw/qdev-clock.h"
 #include "hw/misc/unimp.h"
@@ -48,15 +49,14 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 8,  /* GPIO[2] */
 9,  /* GPIO[3] */
 10, /* GPIO[4] */
-23, 23, 23, 23, 23, /* GPIO[5..9]  */
-40, 40, 40, 40, 40, 40, /* GPIO[10..15]*/
-1,  /* PVD */
+-1, -1, -1, -1, -1, /* GPIO[5..9] OR gate 23   */
+-1, -1, -1, -1, -1, -1, /* GPIO[10..15] OR gate 40 */
+-1, /* PVD OR gate 1   */
 67, /* OTG_FS_WKUP, Direct */
 41, /* RTC_ALARM   */
 2,  /* RTC_TAMP_STAMP2/CSS_LSE */
 3,  /* RTC wakeup timer*/
-63, /* COMP1   */
-63, /* COMP2   */
+-1, -1, /* COMP[1..2] OR gate 63   */
 31, /* I2C1 wakeup, Direct */
 33, /* I2C2 wakeup, Direct */
 72, /* I2C3 wakeup, Direct */
@@ -69,13 +69,29 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 65, /* LPTIM1, Direct  */
 66, /* LPTIM2, Direct  */
 76, /* SWPMI1 wakeup, Direct   */
-1,  /* PVM1 wakeup */
-1,  /* PVM2 wakeup */
-1,  /* PVM3 wakeup */
-1,  /* PVM4 wakeup */
+-1, -1, -1, -1, /* PVM[1..4] OR gate 1 */
 78  /* LCD wakeup, Direct  */
 };
 
+#define NUM_EXTI_OR_GATES 4
+static const int exti_or_gates_out[NUM_EXTI_OR_GATES] = {
+23, 40, 63, 1,
+};
+
+#define NUM_EXTI_SIMPLE_FANIN_IRQ 3
+static const int exti_or_gates_num_lines_in[NUM_EXTI_SIMPLE_FANIN_IRQ] = {
+5, 6, 2,
+};
+
+static const int exti_or_gates_first_line_in[NUM_EXTI_SIMPLE_FANIN_IRQ] = {
+5, 10, 21,
+};
+
+#define NUM_EXTI_OR_GATE1_NUM_LINES_IN 5
+static const int exti_or_gate1_lines_in[NUM_EXTI_OR_GATE1_NUM_LINES_IN] = {
+16, 35, 36, 37, 38,
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -175,8 +191,41 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 sysbus_mmio_map(busdev, 0, EXTI_ADDR);
+
+/* IRQs with fan-in that require an OR gate */
+for (unsigned i = 0; i < NUM_EXTI_OR_GATES; i++) {
+Object *orgate = object_new(TYPE_OR_IRQ);
+object_property_set_int(orgate, "num-lines",
+exti_or_gates_num_lines_in[i], _fatal);
+/* Should unref be used? */
+qdev_realize(DEVICE(orgate), NULL, _fatal);
+
+qdev_connect_gpio_out(DEVICE(orgate), 0,
+qdev_get_gpio_in(armv7m, exti_or_gates_out[i]));
+
+/* consecutive inputs for OR gates 23, 40, 63 */
+if (i < NUM_EXTI_SIMPLE_FANIN_IRQ) {
+for (unsigned j = 0; j < exti_or_gates_num_lines_in[i]; j++) {
+sysbus_connect_irq(SYS_BUS_DEVICE(>exti),
+exti_or_gates_first_line_in[i] + j,
+qdev_get_gpio_in(DEVICE(orgate), j));
+}
+/* non-consecutive inputs for OR gate 1 */
+} else {
+for (unsigned j = 0; j < NUM_EXTI_OR_GATE1_NUM_LINES_IN; j++) {
+sysbus_connect_irq(SYS_BUS_DEVICE(>exti),
+exti_or_gate1_lines_in[j],
+qdev_get_gpio_in(DEVICE(orgate), j));
+}
+}
+}
+
+/* IRQs that don't require fan-in */
 for (unsigned i = 0; i < NUM_EXTI_IRQ; i++) {
-sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, exti_irq[i]));
+if (exti_irq[i] != -1) {
+sysbus_connect_irq(busdev, i,
+   qdev_get_gpio_in(armv7m, exti_irq[i]));
+}
 }
 
 for (unsigned i = 0; i < 16; i++) {
-- 
2.43.0




[PATCH 2/2] tests/qtest: Check that EXTI fan-in irqs are correctly connected

2024-02-12 Thread Inès Varhol
This commit adds a QTest that verifies each input line of a specific
EXTI OR gate can influence the output line.

Signed-off-by: Inès Varhol 
---
 tests/qtest/stm32l4x5_exti-test.c | 97 +++
 1 file changed, 97 insertions(+)

diff --git a/tests/qtest/stm32l4x5_exti-test.c 
b/tests/qtest/stm32l4x5_exti-test.c
index c390077713..276b7adc7a 100644
--- a/tests/qtest/stm32l4x5_exti-test.c
+++ b/tests/qtest/stm32l4x5_exti-test.c
@@ -31,6 +31,11 @@
 
 #define EXTI0_IRQ 6
 #define EXTI1_IRQ 7
+#define EXTI5_IRQ 23
+#define EXTI6_IRQ 23
+#define EXTI7_IRQ 23
+#define EXTI8_IRQ 23
+#define EXTI9_IRQ 23
 #define EXTI35_IRQ 1
 
 static void enable_nvic_irq(unsigned int n)
@@ -499,6 +504,96 @@ static void test_interrupt(void)
 g_assert_false(check_nvic_pending(EXTI1_IRQ));
 }
 
+static void test_orred_interrupts(void)
+{
+/*
+ * For lines EXTI5..9 (fanned-in to NVIC irq 23),
+ * test that rising the line pends interrupt
+ * 23 in NVIC.
+ */
+enable_nvic_irq(EXTI5_IRQ);
+/* Check that there are no interrupts already pending in PR */
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+/* Check that this specific interrupt isn't pending in NVIC */
+g_assert_false(check_nvic_pending(EXTI5_IRQ));
+
+/* Enable interrupt lines EXTI[5..9] */
+exti_writel(EXTI_IMR1, (0x1F << 5));
+
+/* Configure interrupt on rising edge */
+exti_writel(EXTI_RTSR1, (0x1F << 5));
+
+/* Simulate rising edge from GPIO line 7 */
+exti_set_irq(7, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << 7);
+g_assert_true(check_nvic_pending(EXTI7_IRQ));
+
+/* Clear the pending bit in PR */
+exti_writel(EXTI_PR1, 1 << 7);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI7_IRQ));
+
+/* Clean NVIC */
+unpend_nvic_irq(EXTI7_IRQ);
+g_assert_false(check_nvic_pending(EXTI7_IRQ));
+
+/* Simulate rising edge from GPIO line 6 */
+exti_set_irq(6, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << 6);
+g_assert_true(check_nvic_pending(EXTI6_IRQ));
+
+/* Clear the pending bit in PR */
+exti_writel(EXTI_PR1, 1 << 6);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI6_IRQ));
+
+/* Clean NVIC */
+unpend_nvic_irq(EXTI6_IRQ);
+g_assert_false(check_nvic_pending(EXTI6_IRQ));
+
+/* Simulate rising edge from GPIO line 5 */
+exti_set_irq(5, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << 5);
+g_assert_true(check_nvic_pending(EXTI5_IRQ));
+
+/* Clear the pending bit in PR */
+exti_writel(EXTI_PR1, 1 << 5);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI5_IRQ));
+
+/* Clean NVIC */
+unpend_nvic_irq(EXTI5_IRQ);
+g_assert_false(check_nvic_pending(EXTI5_IRQ));
+
+/* Simulate rising edge from GPIO line 8 */
+exti_set_irq(8, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << 8);
+g_assert_true(check_nvic_pending(EXTI8_IRQ));
+
+/* Clear the pending bit in PR */
+exti_writel(EXTI_PR1, 1 << 8);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI8_IRQ));
+
+/* Clean NVIC */
+unpend_nvic_irq(EXTI8_IRQ);
+g_assert_false(check_nvic_pending(EXTI8_IRQ));
+
+/* Simulate rising edge from GPIO line 9 */
+exti_set_irq(9, 1);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 1 << 9);
+g_assert_true(check_nvic_pending(EXTI9_IRQ));
+
+/* Clear the pending bit in PR */
+exti_writel(EXTI_PR1, 1 << 9);
+g_assert_cmpuint(exti_readl(EXTI_PR1), ==, 0x);
+g_assert_true(check_nvic_pending(EXTI9_IRQ));
+
+/* Clean NVIC */
+unpend_nvic_irq(EXTI9_IRQ);
+g_assert_false(check_nvic_pending(EXTI9_IRQ));
+}
+
 int main(int argc, char **argv)
 {
 int ret;
@@ -515,6 +610,8 @@ int main(int argc, char **argv)
 qtest_add_func("stm32l4x5/exti/masked_interrupt", test_masked_interrupt);
 qtest_add_func("stm32l4x5/exti/interrupt", test_interrupt);
 qtest_add_func("stm32l4x5/exti/test_edge_selector", test_edge_selector);
+qtest_add_func("stm32l4x5/exti/test_orred_interrupts",
+   test_orred_interrupts);
 
 qtest_start("-machine b-l475e-iot01a");
 ret = g_test_run();
-- 
2.43.0




[PATCH 0/2] hw/arm: Fix STM32L4x5 EXTI to CPU irq fan-in connections

2024-02-12 Thread Inès Varhol
The original code was connecting several outbounds qemu_irqs to the
same qemu_irq without using a TYPE_OR_IRQ.

This patch fixes the issue by using OR gates when necessary (1st commit).

I attempted to check that the problem is fixed by using a QTest (2nd commit)
but actually the test is passing even before the fix :
when any fan-in input line is raised, the output is raised too.

Fixes: 52671f69f7a4 ("[PATCH v8 0/3] Add device STM32L4x5 EXTI")
Signed-off-by: Inès Varhol 

Inès Varhol (2):
  hw/arm: Use TYPE_OR_IRQ when connecting STM32L4x5 EXTI fan-in IRQs
  tests/qtest: Check that EXTI fan-in irqs are correctly connected

 hw/arm/stm32l4x5_soc.c| 69 ++
 tests/qtest/stm32l4x5_exti-test.c | 97 +++
 2 files changed, 156 insertions(+), 10 deletions(-)

-- 
2.43.0




Re: [PATCH v8 2/3] hw/arm: Connect STM32L4x5 EXTI to STM32L4x5 SoC

2024-02-08 Thread Inès Varhol

 
 
Hi,

 
> De: Philippe  
> Envoyé: mercredi 7 février 2024 23:02 CET 
>  
> Hi Inès, 
>  
> (this is now commit 52671f69f7). 
>  
> On 9/1/24 17:06, Inès Varhol wrote: 
> > Tested-by: Philippe Mathieu-Daudé  
> > Reviewed-by: Philippe Mathieu-Daudé  
> > Reviewed-by: Alistair Francis  
> > Signed-off-by: Arnaud Minier  
> > Signed-off-by: Inès Varhol  
> > --- 
> > hw/arm/Kconfig | 1 + 
> > hw/arm/stm32l4x5_soc.c | 52 +- 
> > include/hw/arm/stm32l4x5_soc.h | 3 ++ 
> > 3 files changed, 55 insertions(+), 1 deletion(-) 
>  
>  
> > +#define NUM_EXTI_IRQ 40 
> > +/* Match exti line connections with their CPU IRQ number */ 
> > +/* See Vector Table (Reference Manual p.396) */ 
> > +static const int exti_irq[NUM_EXTI_IRQ] = { 
> > + 6, /* GPIO[0] */ 
> > + 7, /* GPIO[1] */ 
> > + 8, /* GPIO[2] */ 
> > + 9, /* GPIO[3] */ 
> > + 10, /* GPIO[4] */ 
> > + 23, 23, 23, 23, 23, /* GPIO[5..9] */ 
> > + 40, 40, 40, 40, 40, 40, /* GPIO[10..15] */ 
>  
> I'm sorry because I missed that earlier, and I'm surprised 
> you aren't chasing weird bugs. Due to how QEMU IRQs are 
> implemented, we can not wire multiple input lines to the same 
> output without using an intermediate "OR gate". We model it 
> as TYPE_OR_IRQ. See the comment in "hw/qdev-core.h" added in 
> commit cd07d7f9f5 ("qdev: Document GPIO related functions"): 
  
Better fixing it now than later :)
I must admit I didn't pay attention to the particularity of EXTI5 to 15. 
Current exti tests don't even use these lines, a testcase will have 
to be added. Otherwise we mostly ran executables using GPIOs as output, 
so no weird bugs encountered. 
  
Thank you for noticing ! 
Ines 
  
>  
> * It is not valid to try to connect one outbound GPIO to multiple 
> * qemu_irqs at once, or to connect multiple outbound GPIOs to the 
> * same qemu_irq. (Warning: there is no assertion or other guard to 
> * catch this error: the model will just not do the right thing.) 
> * Instead, for fan-out you can use the TYPE_SPLIT_IRQ device: connect 
> * a device's outbound GPIO to the splitter's input, and connect each 
> * of the splitter's outputs to a different device. For fan-in you 
> * can use the TYPE_OR_IRQ device, which is a model of a logical OR 
> * gate with multiple inputs and one output. 
>  
> So for example for the GPIO[10..15] you need to create a 6-line 
> OR gate as (totally untested): 
>  
> /* 6-line OR IRQ gate */ 
> Object *orgate40 = object_new(TYPE_OR_IRQ); 
> object_property_set_int(orgate40, "num-lines", 6, _fatal); 
> qdev_realize(DEVICE(orgate), NULL, _fatal); 
>  
> /* OR gate -> IRQ #40 */ 
> qdev_connect_gpio_out(DEVICE(orgate40), 0, 
> qdev_get_gpio_in(armv7m, 40)); 
>  
> /* EXTI GPIO[10..15] -> OR gate */ 
> for (unsigned i = 0; i < 6; i++) { 
> sysbus_connect_irq(SYS_BUS_DEVICE(>exti), 10 + i, 
> qdev_get_gpio_in(DEVICE(orgate40), i)); 
> } 
>  
> > + 1, /* PVD */ 
> > + 67, /* OTG_FS_WKUP, Direct */ 
> > + 41, /* RTC_ALARM */ 
> > + 2, /* RTC_TAMP_STAMP2/CSS_LSE */ 
> > + 3, /* RTC wakeup timer */ 
> > + 63, /* COMP1 */ 
> > + 63, /* COMP2 */ 
> > + 31, /* I2C1 wakeup, Direct */ 
> > + 33, /* I2C2 wakeup, Direct */ 
> > + 72, /* I2C3 wakeup, Direct */ 
> > + 37, /* USART1 wakeup, Direct */ 
> > + 38, /* USART2 wakeup, Direct */ 
> > + 39, /* USART3 wakeup, Direct */ 
> > + 52, /* UART4 wakeup, Direct */ 
> > + 53, /* UART4 wakeup, Direct */ 
> > + 70, /* LPUART1 wakeup, Direct */ 
> > + 65, /* LPTIM1, Direct */ 
> > + 66, /* LPTIM2, Direct */ 
> > + 76, /* SWPMI1 wakeup, Direct */ 
> > + 1, /* PVM1 wakeup */ 
> > + 1, /* PVM2 wakeup */ 
> > + 1, /* PVM3 wakeup */ 
> > + 1, /* PVM4 wakeup */ 
> > + 78 /* LCD wakeup, Direct */ 
> > +}; 
>  
> > + busdev = SYS_BUS_DEVICE(>exti); 
> > + if (!sysbus_realize(busdev, errp)) { 
> > + return; 
> > + } 
> > + sysbus_mmio_map(busdev, 0, EXTI_ADDR); 
> > + for (unsigned i = 0; i < NUM_EXTI_IRQ; i++) { 
> > + sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, exti_irq[i])); 
>  
> ^^ 
> > + } 
> Regards, 
>  
> Phil.   


Re: [PATCH 0/3] Add device DM163 (led driver, matrix colors shield & display)

2024-02-07 Thread Inès Varhol
Hello,

> De: "Philippe Mathieu-Daudé" 
> Envoyé: Lundi 5 Février 2024 15:03:59
> 
> Hi Inès,
> 
> On 26/1/24 20:31, Inès Varhol wrote:
> > This device implements the IM120417002 colors shield v1.1 for Arduino
> > (which relies on the DM163 8x3-channel led driving logic) and features
> > a simple display of an 8x8 RGB matrix.
> > 
> > This color shield can be plugged on the Arduino board (or the
> > B-L475E-IOT01A board) to drive an 8x8 RGB led matrix.
> 
> Nice. Do you have an example? Or better, a test :)
> 

Actually I don't know how to test the display with QTest :/
(I've tested it by running custom executables)

I've seen that `qtest_init_internal` sets `-display none`
so I imagine there's no way to test the display visually.

It seems to me that I can't use a qdev property (to access
the DM163 buffer and check its content) either since there's
no `visit_type_*` for arrays.

I could technically access all the elements in the array 
(returning a different element each time in the getter for
example), but that seems sketchy.


In short, how can I provide a test or an example?

Best regards,

Ines



Re: [PATCH 2/3] hw/arm : Connect DM163 to STM32L4x5

2024-02-07 Thread Inès Varhol
Hello !

> De: "Philippe Mathieu-Daudé" 
> Envoyé: Lundi 5 Février 2024 14:46:58
>
> Hi Inès,
> 
> On 26/1/24 20:31, Inès Varhol wrote:
> > Signed-off-by: Arnaud Minier 
> > Signed-off-by: Inès Varhol 
> > ---
> >   hw/arm/Kconfig |  1 +
> >   hw/arm/stm32l4x5_soc.c | 55 +-
> >   include/hw/arm/stm32l4x5_soc.h |  3 ++
> >   3 files changed, 58 insertions(+), 1 deletion(-)
> > 
> > diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> > index 3e49b913f8..818aa2f1a2 100644
> > --- a/hw/arm/Kconfig
> > +++ b/hw/arm/Kconfig
> > @@ -463,6 +463,7 @@ config STM32L4X5_SOC
> >   select STM32L4X5_SYSCFG
> >   select STM32L4X5_RCC
> >   select STM32L4X5_GPIO
> > +select DM163
> 
> 
> > +/*
> > + * There are actually 14 input pins in the DM163 device.
> > + * Here the DM163 input pin EN isn't connected to the STM32L4x5
> > + * GPIOs as the IM120417002 colors shield doesn't actually use
> > + * this pin to drive the RGB matrix.
> > + */
> > +#define NUM_DM163_INPUTS 13
> > +
> > +static const int dm163_input[NUM_DM163_INPUTS] = {
> > +1 * 16 + 2,  /* ROW0  PB2   */
> > +0 * 16 + 15, /* ROW1  PA15  */
> > +0 * 16 + 2,  /* ROW2  PA2   */
> > +0 * 16 + 7,  /* ROW3  PA7   */
> > +0 * 16 + 6,  /* ROW4  PA6   */
> > +0 * 16 + 5,  /* ROW5  PA5   */
> > +1 * 16 + 0,  /* ROW6  PB0   */
> > +0 * 16 + 3,  /* ROW7  PA3   */
> > +0 * 16 + 4,  /* SIN (SDA) PA4   */
> > +1 * 16 + 1,  /* DCK (SCK) PB1   */
> > +2 * 16 + 3,  /* RST_B (RST) PC3 */
> > +2 * 16 + 4,  /* LAT_B (LAT) PC4 */
> > +2 * 16 + 5,  /* SELBK (SB)  PC5 */
> > +};
> > +
> > +
> >   static const uint32_t gpio_addr[] = {
> >   0x4800,
> >   0x48000400,
> > @@ -116,6 +143,8 @@ static void stm32l4x5_soc_initfn(Object *obj)
> >   g_autofree char *name = g_strdup_printf("gpio%c", 'a' + i);
> >   object_initialize_child(obj, name, >gpio[i], 
> > TYPE_STM32L4X5_GPIO);
> >   }
> > +
> > +object_initialize_child(obj, "dm163", >dm163, TYPE_DM163);
> 
> The DM163 is another chip, not a component part of the SoC;
> it belongs to the machine and should be created/wired in
> b_l475e_iot01a_init(). Similarly to the IRQ splitters.
> 
> Keeping board component states in a Bl475eMachineState structure
> could help organizing your model. You can find an example on how
> extend the MachineState in hw/avr/arduino.c.
> 

Yes thank you ! that's done :)

> You might call qdev_pass_gpios() to exposes the SysCfg lines out
> of the SoC.

I was wondering what's the reason to expose Syscfg lines and not Gpio lines?
(Should GPIOs also be moved to the machine ?)

Best regards,

Ines



[PATCH v4 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-02-07 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 include/hw/arm/stm32l4x5_soc.h |  2 +
 hw/arm/stm32l4x5_soc.c | 78 --
 hw/arm/Kconfig |  3 +-
 3 files changed, 68 insertions(+), 15 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/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 347a5377e5..ff38585dd6 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -78,6 +78,32 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+static const uint32_t gpio_addr[] = {
+0x4800,
+0x48000400,
+0x48000800,
+0x48000C00,
+0x48001000,
+0x48001400,
+0x48001800,
+0x48001C00,
+};
+
+static const struct {
+uint32_t moder;
+uint32_t ospeedr;
+uint32_t pupdr;
+} stm32l4x5_gpio_initval[NUM_GPIOS] = {
+{ 0xABFF, 0x0C00, 0x6400 },
+{ 0xFEBF, 0x, 0x0100 },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x000F, 0x, 0x },
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -85,6 +111,11 @@ static void stm32l4x5_soc_initfn(Object *obj)
 object_initialize_child(obj, "exti", >exti, TYPE_STM32L4X5_EXTI);
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >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, >gpio[i], TYPE_STM32L4X5_GPIO);
+}
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -93,8 +124,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(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -135,17 +167,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(>gpio[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);
+busdev = SYS_BUS_DEVICE(>gpio[i]);
+g_free(name);
+name = g_strdup_printf("gpio%c-out", 'a' + i);
+qdev_connect_clock_in(DEVICE(>gpio[i]), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), name));
+if (!sysbus_realize(busdev, errp)) {
+return;
+}
+sysbus_mmio_map(busdev, 0, gpio_addr[i]);
+}
+
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>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(>gpio[i]), j,
+  qdev_get_gpio_in(DEVICE(>syscfg),
+  

[PATCH v4 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-02-07 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
---
 MAINTAINERS|   1 +
 docs/system/arm/b-l475e-iot01a.rst |   2 +-
 include/hw/gpio/stm32l4x5_gpio.h   |  70 +
 hw/gpio/stm32l4x5_gpio.c   | 456 +
 hw/gpio/Kconfig|   3 +
 hw/gpio/meson.build|   1 +
 hw/gpio/trace-events   |   6 +
 7 files changed, 538 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 00..4eabde064b
--- /dev/null
+++ b/include/hw/gpio/stm32l4x5_gpio.h
@@ -0,0 +1,70 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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_pins_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 00..ca7d48f922
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1,456 @@
+/*
+ * STM32L4x5 GPIO (Ge

[PATCH v4 0/3] Add device STM32L4x5 GPIO

2024-02-07 Thread Inès Varhol
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

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: 20240130160656.113112-1-arnaud.min...@telecom-paris.fr
([PATCH v4 0/8] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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   |  70 
 hw/arm/stm32l4x5_soc.c |  78 +++-
 hw/gpio/stm32l4x5_gpio.c   | 456 ++
 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 +-
 12 files changed, 1194 insertions(+), 17 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.0




[PATCH v4 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-02-07 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 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 00..cd4fd9bae2
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,586 @@
+/*
+ * QTest testcase for STM32L4x5_GPIO
+ *
+ * Copyright (c) 2024 Arnaud Minier 
+ * Copyright (c) 2024 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 400
+#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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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, 0x);
+g_assert_false(qdict_haskey(r, "error"));
+qobject_unref(r);
+}
+
+static uint32_t

[PATCH 1/3] hw/display : Add device DM163

2024-01-26 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix. The columns of the matrix are
driven by the DM163 and the rows are driven externally.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/display/Kconfig |   3 +
 hw/display/dm163.c | 307 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  13 ++
 include/hw/display/dm163.h |  57 +++
 5 files changed, 381 insertions(+)
 create mode 100644 hw/display/dm163.c
 create mode 100644 include/hw/display/dm163.h

diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index 1aafe1923d..4dbfc6e7af 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -139,3 +139,6 @@ config XLNX_DISPLAYPORT
 bool
 # defaults to "N", enabled by specific boards
 depends on PIXMAN
+
+config DM163
+bool
diff --git a/hw/display/dm163.c b/hw/display/dm163.c
new file mode 100644
index 00..565fc84ddf
--- /dev/null
+++ b/hw/display/dm163.c
@@ -0,0 +1,307 @@
+/*
+ * QEMU DM163 8x3-channel constant current led driver
+ * driving columns of associated 8x8 RGB matrix.
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The reference used for the DM163 is the following :
+ * http://www.siti.com.tw/product/spec/LED/DM163.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/dm163.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#define LED_SQUARE_SIZE 100
+/* Number of frames a row stays visible after being turned off. */
+#define ROW_PERSISTANCE 2
+
+static const VMStateDescription vmstate_dm163 = {
+.name = TYPE_DM163,
+.version_id = 1,
+.minimum_version_id = 1,
+.fields = (const VMStateField[]) {
+VMSTATE_UINT8(activated_rows, DM163State),
+VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
+VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
+VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS),
+VMSTATE_UINT16_ARRAY(outputs, DM163State, DM163_NUM_LEDS),
+VMSTATE_UINT8(dck, DM163State),
+VMSTATE_UINT8(en_b, DM163State),
+VMSTATE_UINT8(lat_b, DM163State),
+VMSTATE_UINT8(rst_b, DM163State),
+VMSTATE_UINT8(selbk, DM163State),
+VMSTATE_UINT8(sin, DM163State),
+VMSTATE_UINT32_2DARRAY(buffer, DM163State,
+COLOR_BUFFER_SIZE + 1, RGB_MATRIX_NUM_COLS),
+VMSTATE_UINT8(last_buffer_idx, DM163State),
+VMSTATE_UINT8_ARRAY(buffer_idx_of_row, DM163State, 
RGB_MATRIX_NUM_ROWS),
+VMSTATE_UINT8_ARRAY(age_of_row, DM163State, RGB_MATRIX_NUM_ROWS),
+VMSTATE_END_OF_LIST()
+}
+};
+
+static void dm163_reset_hold(Object *obj)
+{
+DM163State *s = DM163(obj);
+
+/* Reset only stops the PWM. */
+memset(s->outputs, 0, sizeof(s->outputs));
+
+/* The last row of the buffer stores a turned off row */
+memset(s->buffer[COLOR_BUFFER_SIZE], 0, sizeof(s->buffer[0]));
+}
+
+static void dm163_dck_gpio_handler(void *opaque, int line, int new_state)
+{
+DM163State *s = DM163(opaque);
+
+if (new_state && !s->dck) {
+/*
+ * On raising dck, sample selbk to get the bank to use, and
+ * sample sin for the bit to enter into the bank shift buffer.
+ */
+uint64_t *sb =
+s->selbk ? s->bank1_shift_register : s->bank0_shift_register;
+/* Output the outgoing bit on sout */
+const bool sout = (s->selbk ? sb[2] & MAKE_64BIT_MASK(63, 1) :
+   sb[2] & MAKE_64BIT_MASK(15, 1)) != 0;
+qemu_set_irq(s->sout, sout);
+/* Enter sin into the shift buffer */
+sb[2] = (sb[2] << 1) | ((sb[1] >> 63) & 1);
+sb[1] = (sb[1] << 1) | ((sb[0] >> 63) & 1);
+sb[0] = (sb[0] << 1) | s->sin;
+}
+
+s->dck = new_state;
+trace_dm163_dck(new_state);
+}
+
+static void dm163_propagate_outputs(DM163State *s)
+{
+s->last_buffer_idx = (s->last_buffer_idx + 1) % COLOR_BUFFER_SIZE;
+/* Values are output when reset and enable are both high. */
+if (s->rst_b && !s->en_b) {
+memcpy(s->outputs, s->latched_outputs, sizeof(s->outputs));
+} else {
+memset(s->outputs, 0, sizeof(s->outputs));
+}
+for (unsigned x = 0; x < RGB_MATRIX_NUM_COLS; x++) {
+trace_dm163_channels(3 * x, (uint8_t)(s->outputs[3 * x] >> 6));
+trace_dm163_channels(3 * x + 1, (uint8_t)(s->outputs[3 * x + 1

[PATCH 3/3] tests/qtest : Add testcase for DM163

2024-01-26 Thread Inès Varhol
`test_dm163_bank()`
Checks that the pin "sout" of the DM163 led driver outputs the values
received on pin "sin" with the expected latency (depending on the bank).

`test_dm163_gpio_connection()`
Check that changes to relevant STM32L4x5 GPIO pins are prpagated to the
DM163 device.

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/dm163-test.c | 192 +++
 tests/qtest/meson.build  |   1 +
 2 files changed, 193 insertions(+)
 create mode 100644 tests/qtest/dm163-test.c

diff --git a/tests/qtest/dm163-test.c b/tests/qtest/dm163-test.c
new file mode 100644
index 00..7691ce1af0
--- /dev/null
+++ b/tests/qtest/dm163-test.c
@@ -0,0 +1,192 @@
+/*
+ * QTest testcase for DM163
+ *
+ * Copyright (C) 2024 Samuel Tardieu 
+ * Copyright (C) 2024 Arnaud Minier 
+ * Copyright (C) 2024 Inès Varhol 
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define SIN 8
+#define DCK 9
+#define RST_B 10
+#define LAT_B 11
+#define SELBK 12
+#define EN_B 13
+
+#define DEVICE_NAME "/machine/soc/dm163"
+#define GPIO_OUT(name, value) qtest_set_irq_in(qts, DEVICE_NAME, NULL, name,   
\
+   value)
+#define GPIO_PULSE(name)   
\
+  do { 
\
+GPIO_OUT(name, 1); 
\
+GPIO_OUT(name, 0); 
\
+  } while (0)
+
+
+static void rise_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 1 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x0002);
+}
+
+static void lower_gpio_pin_dck(QTestState *qts)
+{
+/* Configure output mode for pin PB1 */
+qtest_writel(qts, 0x48000400, 0xFEB7);
+/* Write 0 in ODR for PB1 */
+qtest_writel(qts, 0x48000414, 0x);
+}
+
+static void rise_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 1 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x0020);
+}
+
+static void lower_gpio_pin_selbk(QTestState *qts)
+{
+/* Configure output mode for pin PC5 */
+qtest_writel(qts, 0x48000800, 0xF7FF);
+/* Write 0 in ODR for PC5 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 1 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x0010);
+}
+
+static void lower_gpio_pin_lat_b(QTestState *qts)
+{
+/* Configure output mode for pin PC4 */
+qtest_writel(qts, 0x48000800, 0xFDFF);
+/* Write 0 in ODR for PC4 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 1 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x0008);
+}
+
+static void lower_gpio_pin_rst_b(QTestState *qts)
+{
+/* Configure output mode for pin PC3 */
+qtest_writel(qts, 0x48000800, 0xFF7F);
+/* Write 0 in ODR for PC3 */
+qtest_writel(qts, 0x48000814, 0x);
+}
+
+static void rise_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 1 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x0010);
+}
+
+static void lower_gpio_pin_sin(QTestState *qts)
+{
+/* Configure output mode for pin PA4 */
+qtest_writel(qts, 0x4800, 0xFDFF);
+/* Write 0 in ODR for PA4 */
+qtest_writel(qts, 0x4814, 0x);
+}
+
+static void test_dm163_bank(const void *opaque)
+{
+const long bank = (uintptr_t) opaque;
+const int width = bank ? 192 : 144;
+
+QTestState *qts = qtest_initf("-M b-l475e-iot01a");
+qtest_irq_intercept_out_named(qts, DEVICE_NAME, "sout");
+GPIO_OUT(RST_B, 1);
+GPIO_OUT(EN_B, 0);
+GPIO_OUT(DCK, 0);
+GPIO_OUT(SELBK, bank);
+GPIO_OUT(LAT_B, 1);
+
+/* Fill bank with zeroes */
+GPIO_OUT(SIN, 0);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+}
+/* Fill bank with ones, check that we get the previous zeroes */
+GPIO_OUT(SIN, 1);
+for (int i = 0; i < width; i++) {
+GPIO_PULSE(DCK);
+g_assert(!qtest_get_irq(qts, 0));
+}
+
+/* Pulse one more bit in the bank, check that we get a one */
+GPIO_PULSE(DCK);
+g_assert(qtest_get_irq(qts, 0));
+
+qtest_quit(qts);
+}
+
+static void test_dm163_gpio_connection(void)
+{
+QTestState *qts = qtest_init("-M b-l475e-iot01a

[PATCH 0/3] Add device DM163 (led driver, matrix colors shield & display)

2024-01-26 Thread Inès Varhol
This device implements the IM120417002 colors shield v1.1 for Arduino
(which relies on the DM163 8x3-channel led driving logic) and features
a simple display of an 8x8 RGB matrix.

This color shield can be plugged on the Arduino board (or the
B-L475E-IOT01A board) to drive an 8x8 RGB led matrix.
This RGB led matrix takes advantage of retinal persistance to
seemingly display different colors in each row.

It'd be convenient to set the QEMU console's refresh rate
in order to ensure that the delay before turning off rows
(2 frames currently) isn't too short. However
`dpy_ui_info_supported(s->console)` can't be used.

I saw that Kconfig configurable components aren't visible in C files,
does that mean it's impossible to make the DM163 device optional when
using the B-L475E-IOT01A board?

Based-on: 20240123122505.516393-1-ines.var...@telecom-paris.fr
([PATCH v3 0/3] Add device STM32L4x5 GPIO)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

Inès Varhol (3):
  hw/display : Add device DM163
  hw/arm : Connect DM163 to STM32L4x5
  tests/qtest : Add testcase for DM163

 hw/arm/Kconfig |   1 +
 hw/arm/stm32l4x5_soc.c |  55 +-
 hw/display/Kconfig |   3 +
 hw/display/dm163.c | 307 +
 hw/display/meson.build |   1 +
 hw/display/trace-events|  13 ++
 include/hw/arm/stm32l4x5_soc.h |   3 +
 include/hw/display/dm163.h |  57 ++
 tests/qtest/dm163-test.c   | 192 +
 tests/qtest/meson.build|   1 +
 10 files changed, 632 insertions(+), 1 deletion(-)
 create mode 100644 hw/display/dm163.c
 create mode 100644 include/hw/display/dm163.h
 create mode 100644 tests/qtest/dm163-test.c

-- 
2.43.0




[PATCH 2/3] hw/arm : Connect DM163 to STM32L4x5

2024-01-26 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/Kconfig |  1 +
 hw/arm/stm32l4x5_soc.c | 55 +-
 include/hw/arm/stm32l4x5_soc.h |  3 ++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 3e49b913f8..818aa2f1a2 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -463,6 +463,7 @@ config STM32L4X5_SOC
 select STM32L4X5_SYSCFG
 select STM32L4X5_RCC
 select STM32L4X5_GPIO
+select DM163
 
 config XLNX_ZYNQMP_ARM
 bool
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
index 478c6ba056..8663546901 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -26,7 +26,9 @@
 #include "qapi/error.h"
 #include "exec/address-spaces.h"
 #include "sysemu/sysemu.h"
+#include "hw/core/split-irq.h"
 #include "hw/arm/stm32l4x5_soc.h"
+#include "hw/display/dm163.h"
 #include "hw/qdev-clock.h"
 #include "hw/misc/unimp.h"
 
@@ -78,6 +80,31 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+/*
+ * There are actually 14 input pins in the DM163 device.
+ * Here the DM163 input pin EN isn't connected to the STM32L4x5
+ * GPIOs as the IM120417002 colors shield doesn't actually use
+ * this pin to drive the RGB matrix.
+ */
+#define NUM_DM163_INPUTS 13
+
+static const int dm163_input[NUM_DM163_INPUTS] = {
+1 * 16 + 2,  /* ROW0  PB2   */
+0 * 16 + 15, /* ROW1  PA15  */
+0 * 16 + 2,  /* ROW2  PA2   */
+0 * 16 + 7,  /* ROW3  PA7   */
+0 * 16 + 6,  /* ROW4  PA6   */
+0 * 16 + 5,  /* ROW5  PA5   */
+1 * 16 + 0,  /* ROW6  PB0   */
+0 * 16 + 3,  /* ROW7  PA3   */
+0 * 16 + 4,  /* SIN (SDA) PA4   */
+1 * 16 + 1,  /* DCK (SCK) PB1   */
+2 * 16 + 3,  /* RST_B (RST) PC3 */
+2 * 16 + 4,  /* LAT_B (LAT) PC4 */
+2 * 16 + 5,  /* SELBK (SB)  PC5 */
+};
+
+
 static const uint32_t gpio_addr[] = {
 0x4800,
 0x48000400,
@@ -116,6 +143,8 @@ static void stm32l4x5_soc_initfn(Object *obj)
 g_autofree char *name = g_strdup_printf("gpio%c", 'a' + i);
 object_initialize_child(obj, name, >gpio[i], TYPE_STM32L4X5_GPIO);
 }
+
+object_initialize_child(obj, "dm163", >dm163, TYPE_DM163);
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -124,9 +153,10 @@ 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, *dev;
+DeviceState *armv7m, *dev, *gpio_output_fork;
 SysBusDevice *busdev;
 uint32_t pin_index;
+int gpio, pin;
 
 if (!memory_region_init_rom(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -166,6 +196,12 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 
+/* DM163 */
+dev = DEVICE(>dm163);
+if (!qdev_realize(dev, NULL, errp)) {
+return;
+}
+
 /* GPIOs */
 for (unsigned i = 0; i < NUM_GPIOS; i++) {
 g_autofree char *name = g_strdup_printf("%c", 'A' + i);
@@ -204,6 +240,23 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 }
 }
 
+for (unsigned i = 0; i < NUM_DM163_INPUTS; i++) {
+gpio_output_fork = qdev_new(TYPE_SPLIT_IRQ);
+qdev_prop_set_uint32(gpio_output_fork, "num-lines", 2);
+qdev_realize_and_unref(gpio_output_fork, NULL, _fatal);
+
+qdev_connect_gpio_out(gpio_output_fork, 0,
+  qdev_get_gpio_in(DEVICE(>syscfg),
+   dm163_input[i]));
+qdev_connect_gpio_out(gpio_output_fork, 1,
+  qdev_get_gpio_in(DEVICE(>dm163),
+   i));
+gpio = dm163_input[i] / 16;
+pin = dm163_input[i] % 16;
+qdev_connect_gpio_out(DEVICE(>gpio[gpio]), pin,
+  qdev_get_gpio_in(DEVICE(gpio_output_fork), 0));
+}
+
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
 if (!sysbus_realize(busdev, errp)) {
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
index cb4da08629..60b31d430e 100644
--- a/include/hw/arm/stm32l4x5_soc.h
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -30,6 +30,7 @@
 #include "hw/misc/stm32l4x5_exti.h"
 #include "hw/misc/stm32l4x5_rcc.h"
 #include "hw/gpio/stm32l4x5_gpio.h"
+#include "hw/display/dm163.h"
 #include "qom/object.h"
 
 #define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
@@ -48,6 +49,8 @@ struct Stm32l4x5SocState {
 Stm32l4x5RccState rcc;
 Stm32l4x5GpioState gpio[NUM_GPIOS];
 
+DM163State dm163;
+
 MemoryRegion sram1;
 MemoryRegion sram2;
 MemoryRegion flash;
-- 
2.43.0




[PATCH v3 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-01-23 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/Kconfig |  3 +-
 hw/arm/stm32l4x5_soc.c | 78 --
 include/hw/arm/stm32l4x5_soc.h |  2 +
 3 files changed, 68 insertions(+), 15 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..478c6ba056 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -78,6 +78,32 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+static const uint32_t gpio_addr[] = {
+0x4800,
+0x48000400,
+0x48000800,
+0x48000C00,
+0x48001000,
+0x48001400,
+0x48001800,
+0x48001C00,
+};
+
+static const struct {
+uint32_t moder;
+uint32_t ospeedr;
+uint32_t pupdr;
+} stm32l4x5_gpio_initval[NUM_GPIOS] = {
+{ 0xABFF, 0x0C00, 0x6400 },
+{ 0xFEBF, 0x, 0x0100 },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x000F, 0x, 0x },
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
@@ -85,6 +111,11 @@ static void stm32l4x5_soc_initfn(Object *obj)
 object_initialize_child(obj, "exti", >exti, TYPE_STM32L4X5_EXTI);
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >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, >gpio[i], TYPE_STM32L4X5_GPIO);
+}
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -93,8 +124,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(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -134,17 +166,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(>gpio[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);
+busdev = SYS_BUS_DEVICE(>gpio[i]);
+g_free(name);
+name = g_strdup_printf("gpio%c-out", 'a' + i);
+qdev_connect_clock_in(DEVICE(>gpio[i]), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), name));
+if (!sysbus_realize(busdev, errp)) {
+return;
+}
+sysbus_mmio_map(busdev, 0, gpio_addr[i]);
+}
+
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>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(>gpio[i]), j,
+  qdev_get_gpio_in(DEVICE(>syscfg),
+  pin_index));
+}
+}
 
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
@@ -241,14 +299,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 /* RESERVED:0x40024400, 0x7FDBC00 */
 
 /* AHB2 BUS */
-create_unimplemented_device("GPIOA", 0x4800, 0x400);

[PATCH v3 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-01-23 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
---
 MAINTAINERS|   1 +
 docs/system/arm/b-l475e-iot01a.rst |   2 +-
 hw/gpio/Kconfig|   3 +
 hw/gpio/meson.build|   1 +
 hw/gpio/stm32l4x5_gpio.c   | 456 +
 hw/gpio/trace-events   |   6 +
 include/hw/gpio/stm32l4x5_gpio.h   |  70 +
 7 files changed, 538 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 00..7995201558
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1,456 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2023 Arnaud Minier 
+ * Copyright (c) 2023 Inès Varhol 
+ *
+ * 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
+#def

[PATCH v3 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-01-23 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/meson.build   |   3 +-
 tests/qtest/stm32l4x5_gpio-test.c | 586 ++
 2 files changed, 588 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 00..4b775ed983
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,586 @@
+/*
+ * QTest testcase for STM32L4x5_EXTI
+ *
+ * Copyright (c) 2023 Arnaud Minier 
+ * Copyright (c) 2023 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 400
+#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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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

[PATCH v3 0/3] Add device STM32L4x5 GPIO

2024-01-23 Thread Inès Varhol
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

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: 20240118091107.87831-1-arnaud.min...@telecom-paris.fr
([PATCH v2 0/7] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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 |  78 +++-
 hw/gpio/Kconfig|   3 +
 hw/gpio/meson.build|   1 +
 hw/gpio/stm32l4x5_gpio.c   | 456 ++
 hw/gpio/trace-events   |   6 +
 include/hw/arm/stm32l4x5_soc.h |   2 +
 include/hw/gpio/stm32l4x5_gpio.h   |  70 
 tests/qtest/meson.build|   3 +-
 tests/qtest/stm32l4x5_gpio-test.c  | 586 +
 12 files changed, 1194 insertions(+), 17 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




[PATCH v2 1/3] hw/gpio: Implement STM32L4x5 GPIO

2024-01-22 Thread Inès Varhol
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 
Signed-off-by: Inès Varhol 
---
 MAINTAINERS|   1 +
 docs/system/arm/b-l475e-iot01a.rst |   2 +-
 hw/gpio/Kconfig|   3 +
 hw/gpio/meson.build|   1 +
 hw/gpio/stm32l4x5_gpio.c   | 447 +
 hw/gpio/trace-events   |   6 +
 include/hw/gpio/stm32l4x5_gpio.h   |  65 +
 7 files changed, 524 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 00..58b321a299
--- /dev/null
+++ b/hw/gpio/stm32l4x5_gpio.c
@@ -0,0 +1,447 @@
+/*
+ * STM32L4x5 GPIO (General Purpose Input/Ouput)
+ *
+ * Copyright (c) 2023 Arnaud Minier 
+ * Copyright (c) 2023 Inès Varhol 
+ *
+ * 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
+#def

[PATCH v2 2/3] hw/arm: Connect STM32L4x5 GPIO to STM32L4x5 SoC

2024-01-22 Thread Inès Varhol
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 hw/arm/Kconfig |  3 +-
 hw/arm/stm32l4x5_soc.c | 79 --
 include/hw/arm/stm32l4x5_soc.h |  2 +
 3 files changed, 69 insertions(+), 15 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..0333cbb81a 100644
--- a/hw/arm/stm32l4x5_soc.c
+++ b/hw/arm/stm32l4x5_soc.c
@@ -78,13 +78,45 @@ static const int exti_irq[NUM_EXTI_IRQ] = {
 #define RCC_BASE_ADDRESS 0x40021000
 #define RCC_IRQ 5
 
+static const uint32_t gpio_addr[] = {
+0x4800,
+0x48000400,
+0x48000800,
+0x48000C00,
+0x48001000,
+0x48001400,
+0x48001800,
+0x48001C00,
+};
+
+static const struct {
+uint32_t moder;
+uint32_t ospeedr;
+uint32_t pupdr;
+} stm32l4x5_gpio_initval[NUM_GPIOS] = {
+{ 0xABFF, 0x0C00, 0x6400 },
+{ 0xFEBF, 0x, 0x0100 },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x, 0x, 0x },
+{ 0x000F, 0x, 0x },
+};
+
 static void stm32l4x5_soc_initfn(Object *obj)
 {
 Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
+g_autofree char *name = NULL;
 
 object_initialize_child(obj, "exti", >exti, TYPE_STM32L4X5_EXTI);
 object_initialize_child(obj, "syscfg", >syscfg, TYPE_STM32L4X5_SYSCFG);
 object_initialize_child(obj, "rcc", >rcc, TYPE_STM32L4X5_RCC);
+
+for (unsigned i = 0; i < NUM_GPIOS; i++) {
+name = g_strdup_printf("gpio%c", 'a' + i);
+object_initialize_child(obj, name, >gpio[i], TYPE_STM32L4X5_GPIO);
+}
 }
 
 static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
@@ -93,8 +125,10 @@ 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;
+g_autofree char *name = NULL;
 
 if (!memory_region_init_rom(>flash, OBJECT(dev_soc), "flash",
 sc->flash_size, errp)) {
@@ -134,17 +168,42 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 return;
 }
 
+/* GPIOs */
+for (unsigned i = 0; i < NUM_GPIOS; i++) {
+dev = DEVICE(>gpio[i]);
+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);
+busdev = SYS_BUS_DEVICE(>gpio[i]);
+name = g_strdup_printf("gpio%c-out", 'a' + i);
+qdev_connect_clock_in(DEVICE(>gpio[i]), "clk",
+qdev_get_clock_out(DEVICE(&(s->rcc)), name));
+if (!sysbus_realize(busdev, errp)) {
+return;
+}
+sysbus_mmio_map(busdev, 0, gpio_addr[i]);
+}
+
 /* System configuration controller */
 busdev = SYS_BUS_DEVICE(>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(>gpio[i]), j,
+  qdev_get_gpio_in(DEVICE(>syscfg),
+  pin_index));
+}
+}
 
 /* EXTI device */
 busdev = SYS_BUS_DEVICE(>exti);
@@ -241,14 +300,6 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, 
Error **errp)
 /* RESERVED:0x40024400, 0x7FDBC00 */
 
 /* AHB2 BUS */
-create_unimplemented_device("GPIOA", 0x4800, 0x400);
-create_unimplemented_device("GPIOB"

[PATCH v2 0/3] Add device STM32L4x5 GPIO

2024-01-22 Thread Inès Varhol
This patch adds a new device STM32L4x5 GPIO device and is part
of a series implementing the STM32L4x5 with a few peripherals.

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: 20240118091107.87831-1-arnaud.min...@telecom-paris.fr
([PATCH v2 0/7] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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 |  79 -
 hw/gpio/Kconfig|   3 +
 hw/gpio/meson.build|   1 +
 hw/gpio/stm32l4x5_gpio.c   | 447 +
 hw/gpio/trace-events   |   6 +
 include/hw/arm/stm32l4x5_soc.h |   2 +
 include/hw/gpio/stm32l4x5_gpio.h   |  65 
 tests/qtest/meson.build|   3 +-
 tests/qtest/stm32l4x5_gpio-test.c  | 520 +
 12 files changed, 1115 insertions(+), 17 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




[PATCH v2 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-01-22 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 tests/qtest/meson.build   |   3 +-
 tests/qtest/stm32l4x5_gpio-test.c | 520 ++
 2 files changed, 522 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 00..3803687a6a
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,520 @@
+/*
+ * QTest testcase for STM32L4x5_EXTI
+ *
+ * Copyright (c) 2023 Arnaud Minier 
+ * Copyright (c) 2023 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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 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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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': 

[PATCH 0/3] Add device STM32L4x5 GPIO

2024-01-22 Thread Inès Varhol
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.min...@telecom-paris.fr
([PATCH v2 0/7] Add device STM32L4x5 RCC)

Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 

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




[PATCH 3/3] tests/qtest: Add STM32L4x5 GPIO QTest testcase

2024-01-22 Thread Inès Varhol
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 
Signed-off-by: Arnaud Minier 
Signed-off-by: Inès Varhol 
---
 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 00..9b234c68ec
--- /dev/null
+++ b/tests/qtest/stm32l4x5_gpio-test.c
@@ -0,0 +1,526 @@
+/*
+ * QTest testcase for STM32L4x5_EXTI
+ *
+ * Copyright (c) 2023 Arnaud Minier 
+ * Copyright (c) 2023 Inès Varhol 
+ *
+ * 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 0x4800
+#define GPIO_SIZE  0x400
+#define NUM_GPIOS  8
+#define NUM_GPIO_PINS  16
+
+#define GPIO_A 0x4800
+#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] = {
+0xABFF,
+0xFEBF,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x000F
+};
+
+const uint32_t pupdr_reset[NUM_GPIOS] = {
+0x6400,
+0x0100,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+const uint32_t idr_reset[NUM_GPIOS] = {
+0xA000,
+0x0010,
+0x,
+0x,
+0x,
+0x,
+0x,
+0x
+};
+
+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 = 0x & ~(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 = 0x & ~(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, l

  1   2   >