* [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device
@ 2025-10-15 1:18 Coco Li
2025-10-15 1:18 ` [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis Coco Li
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg; +Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd
GPIO series:
Added 32 bits property for ASPEED GPIO with updated qtests.
SGPIO series:
Implemented SGPIO device according for npcm8xx.
Two notable implementations left undone in these patches are:
1. Reading the data from the host controlled SIOX via register IOXDATR
2. On-demand with polling reading node
The reason for this ommitance is that both features are currently unused/umimplemented by the nuvoton driver.
The changes to qobject is used in both sets of patches. The patch series implements indexing gpios with array indices on top of accessing with registers.
The reasons for this is becasue it creates another easier way to access gpio. In our internal tests, we model complex behaviors with a large number of gpios, such as in fault injection, or in networking behaviors.
Indexing multiple gpios at once allows qmp/side band client to no longer hardcode and populate register names and manipulate them faster.
Updates since V0: added more descriptions on qobjects change in cover letter.
Updates since V1:
- added more description to "hw/gpio: Add property for ASPEED GPIO in 32 bits basis" patch
- used bitops.h for bit operations in "hw/gpio/npcm8xx: Implement npcm sgpio device input pin"
Coco Li (3):
hw/arm/npcm8xx.c: Add all IRQ ENUMs
hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input
pin logic
hw/gpio/npcm8xx: Implement npcm sgpio device input pin logic
Felix Wu (2):
hw/gpio: Add property for ASPEED GPIO in 32 bits basis
tests/qtest: Add qtest for for ASPEED GPIO gpio-set property
hw/arm/npcm8xx.c | 66 +++-
hw/gpio/aspeed_gpio.c | 57 ++++
hw/gpio/meson.build | 1 +
hw/gpio/npcm8xx_sgpio.c | 518 +++++++++++++++++++++++++++++++
hw/gpio/trace-events | 4 +
include/hw/arm/npcm8xx.h | 2 +
include/hw/gpio/npcm8xx_sgpio.h | 45 +++
include/qobject/qdict.h | 1 +
qobject/qdict.c | 13 +
tests/qtest/aspeed_gpio-test.c | 105 ++++++-
tests/qtest/meson.build | 3 +-
tests/qtest/npcm8xx_sgpio-test.c | 222 +++++++++++++
12 files changed, 1024 insertions(+), 13 deletions(-)
create mode 100644 hw/gpio/npcm8xx_sgpio.c
create mode 100644 include/hw/gpio/npcm8xx_sgpio.h
create mode 100644 tests/qtest/npcm8xx_sgpio-test.c
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
@ 2025-10-15 1:18 ` Coco Li
2025-10-16 22:56 ` Andrew Jeffery
2025-10-15 1:18 ` [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property Coco Li
` (4 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg; +Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd
From: Felix Wu <flwu@google.com>
Added 32 bits property for ASPEED GPIO. Previously it can only be access in bitwise manner.
The changes to qobject is to index gpios with array indices on top of accessing with registers.
This allows for easier gpio access, especially in tests with complex behaviors that requires large number of gpios at a time, like fault injection and networking behaviors.
Indexing multiple gpios at once allows qmp/side band client to no longer hardcode and populate register names and manipulate them faster.
Signed-off-by: Felix Wu <flwu@google.com>
---
hw/gpio/aspeed_gpio.c | 57 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/hw/gpio/aspeed_gpio.c b/hw/gpio/aspeed_gpio.c
index 609a556908..2d78bf9515 100644
--- a/hw/gpio/aspeed_gpio.c
+++ b/hw/gpio/aspeed_gpio.c
@@ -1308,6 +1308,57 @@ static void aspeed_gpio_2700_write(void *opaque, hwaddr offset,
}
/* Setup functions */
+static void aspeed_gpio_set_set(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ uint32_t set_val = 0;
+ AspeedGPIOState *s = ASPEED_GPIO(obj);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+ int set_idx = 0;
+
+ if (!visit_type_uint32(v, name, &set_val, errp)) {
+ return;
+ }
+
+ if (sscanf(name, "gpio-set[%d]", &set_idx) != 1) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+
+ if (set_idx >= agc->nr_gpio_sets || set_idx < 0) {
+ error_setg(errp, "%s: invalid set_idx %s", __func__, name);
+ return;
+ }
+
+ aspeed_gpio_update(s, &s->sets[set_idx], set_val,
+ ~s->sets[set_idx].direction);
+}
+
+static void aspeed_gpio_get_set(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ uint32_t set_val = 0;
+ AspeedGPIOState *s = ASPEED_GPIO(obj);
+ AspeedGPIOClass *agc = ASPEED_GPIO_GET_CLASS(s);
+ int set_idx = 0;
+
+ if (sscanf(name, "gpio-set[%d]", &set_idx) != 1) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+
+ if (set_idx >= agc->nr_gpio_sets || set_idx < 0) {
+ error_setg(errp, "%s: invalid set_idx %s", __func__, name);
+ return;
+ }
+
+ set_val = s->sets[set_idx].data_value;
+ visit_type_uint32(v, name, &set_val, errp);
+}
+
+/****************** Setup functions ******************/
static const GPIOSetProperties ast2400_set_props[ASPEED_GPIO_MAX_NR_SETS] = {
[0] = {0xffffffff, 0xffffffff, {"A", "B", "C", "D"} },
[1] = {0xffffffff, 0xffffffff, {"E", "F", "G", "H"} },
@@ -1435,6 +1486,12 @@ static void aspeed_gpio_init(Object *obj)
g_free(name);
}
}
+
+ for (int i = 0; i < agc->nr_gpio_sets; i++) {
+ char *name = g_strdup_printf("gpio-set[%d]", i);
+ object_property_add(obj, name, "uint32", aspeed_gpio_get_set,
+ aspeed_gpio_set_set, NULL, NULL);
+ }
}
static const VMStateDescription vmstate_gpio_regs = {
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
2025-10-15 1:18 ` [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis Coco Li
@ 2025-10-15 1:18 ` Coco Li
2025-10-17 5:37 ` Cédric Le Goater
2025-10-15 1:18 ` [PATCH v2 3/5] hw/arm/npcm8xx.c: Add all IRQ ENUMs Coco Li
` (3 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg; +Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd
From: Felix Wu <flwu@google.com>
- Added qtests to test gpio-set property for ASPEED.
- Added function to get uint in qdict.
Signed-off-by: Felix Wu <flwu@google.com>
---
include/qobject/qdict.h | 1 +
qobject/qdict.c | 13 ++++
tests/qtest/aspeed_gpio-test.c | 105 ++++++++++++++++++++++++++++++---
3 files changed, 110 insertions(+), 9 deletions(-)
diff --git a/include/qobject/qdict.h b/include/qobject/qdict.h
index 903e6e5462..861996f08d 100644
--- a/include/qobject/qdict.h
+++ b/include/qobject/qdict.h
@@ -57,6 +57,7 @@ void qdict_put_str(QDict *qdict, const char *key, const char *value);
double qdict_get_double(const QDict *qdict, const char *key);
int64_t qdict_get_int(const QDict *qdict, const char *key);
+uint64_t qdict_get_uint(const QDict *qdict, const char *key);
bool qdict_get_bool(const QDict *qdict, const char *key);
QList *qdict_get_qlist(const QDict *qdict, const char *key);
QDict *qdict_get_qdict(const QDict *qdict, const char *key);
diff --git a/qobject/qdict.c b/qobject/qdict.c
index a90ac9ae2f..0dafe6d421 100644
--- a/qobject/qdict.c
+++ b/qobject/qdict.c
@@ -209,6 +209,19 @@ int64_t qdict_get_int(const QDict *qdict, const char *key)
return qnum_get_int(qobject_to(QNum, qdict_get(qdict, key)));
}
+/**
+ * qdict_get_uint(): Get an unsigned integer mapped by 'key'
+ *
+ * This function assumes that 'key' exists and it stores a
+ * QNum representable as uint.
+ *
+ * Return unsigned integer mapped by 'key'.
+ */
+uint64_t qdict_get_uint(const QDict *qdict, const char *key)
+{
+ return qnum_get_uint(qobject_to(QNum, qdict_get(qdict, key)));
+}
+
/**
* qdict_get_bool(): Get a bool mapped by 'key'
*
diff --git a/tests/qtest/aspeed_gpio-test.c b/tests/qtest/aspeed_gpio-test.c
index 12675d4cbb..c2f9ca2298 100644
--- a/tests/qtest/aspeed_gpio-test.c
+++ b/tests/qtest/aspeed_gpio-test.c
@@ -27,28 +27,115 @@
#include "qemu/timer.h"
#include "qobject/qdict.h"
#include "libqtest-single.h"
+#include "qemu/typedefs.h"
#define AST2600_GPIO_BASE 0x1E780000
#define GPIO_ABCD_DATA_VALUE 0x000
#define GPIO_ABCD_DIRECTION 0x004
+static uint32_t qtest_qom_get_uint32(QTestState *s, const char *path,
+ const char *property)
+{
+ QDict *r;
+
+ uint32_t res;
+ r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
+ "{ 'path': %s, 'property': %s } }", path, property);
+ res = qdict_get_uint(r, "return");
+ qobject_unref(r);
+
+ return res;
+}
+
+static void qtest_qom_set_uint32(QTestState *s, const char *path,
+ const char *property, uint32_t value)
+{
+ QDict *r;
+
+ r = qtest_qmp(s, "{ 'execute': 'qom-set', 'arguments': "
+ "{ 'path': %s, 'property': %s, 'value': %" PRIu32 " } }",
+ path, property, value);
+ qobject_unref(r);
+}
+
+static const char *resp_get_error(QDict *r, const char* error_key)
+{
+ QDict *qdict;
+
+ g_assert(r);
+
+ qdict = qdict_get_qdict(r, "error");
+ if (qdict) {
+ return qdict_get_str(qdict, error_key);
+ }
+
+ return NULL;
+}
+
+static bool qtest_qom_check_error(QTestState *s, const char *path,
+ const char *property, const char *error_msg,
+ const char *error_msg_key)
+{
+ QDict *r;
+ bool b;
+
+ r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
+ "{ 'path': %s, 'property': %s } }", path, property);
+ b = g_str_equal(resp_get_error(r, error_msg_key), error_msg);
+ qobject_unref(r);
+
+ return b;
+}
+
static void test_set_colocated_pins(const void *data)
{
QTestState *s = (QTestState *)data;
-
+ const char path[] = "/machine/soc/gpio";
/*
* gpioV4-7 occupy bits within a single 32-bit value, so we want to make
* sure that modifying one doesn't affect the other.
*/
- qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV4", true);
- qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV5", false);
- qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV6", true);
- qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV7", false);
- g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV4"));
- g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV5"));
- g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV6"));
- g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV7"));
+ qtest_qom_set_bool(s, path, "gpioV4", true);
+ qtest_qom_set_bool(s, path, "gpioV5", false);
+ qtest_qom_set_bool(s, path, "gpioV6", true);
+ qtest_qom_set_bool(s, path, "gpioV7", false);
+ g_assert(qtest_qom_get_bool(s, path, "gpioV4"));
+ g_assert(!qtest_qom_get_bool(s, path, "gpioV5"));
+ g_assert(qtest_qom_get_bool(s, path, "gpioV6"));
+ g_assert(!qtest_qom_get_bool(s, path, "gpioV7"));
+
+ /*
+ * Testing the gpio-set[%d] properties, using individual gpio boolean
+ * properties to do cross check.
+ * We use gpioR4-7 for test, Setting them to be 0b1010.
+ */
+ qtest_qom_set_uint32(s, path, "gpio-set[4]", 0x0);
+ g_assert(qtest_qom_get_uint32(s, path, "gpio-set[4]") == 0x0);
+ qtest_qom_set_uint32(s, path, "gpio-set[4]", 0xa000);
+ g_assert(qtest_qom_get_uint32(s, path, "gpio-set[4]") == 0xa000);
+
+ g_assert(!qtest_qom_get_bool(s, path, "gpioR4"));
+ g_assert(qtest_qom_get_bool(s, path, "gpioR5"));
+ g_assert(!qtest_qom_get_bool(s, path, "gpioR6"));
+ g_assert(qtest_qom_get_bool(s, path, "gpioR7"));
+
+ /*
+ * Testing the invalid indexing, the response info should contain following
+ * info:
+ * {key: "class", value: "GenericError"}
+ *
+ * For pins, it should follow "gpio%2[A-Z]%1d" or "gpio%3[18A-E]%1d" format.
+ */
+ const char error_msg[] = "GenericError";
+ const char error_msg_key[] = "class";
+
+ g_assert(qtest_qom_check_error(s, path, "gpioR+1", error_msg,
+ error_msg_key));
+ g_assert(qtest_qom_check_error(s, path, "gpio-set[99]", error_msg,
+ error_msg_key));
+ g_assert(qtest_qom_check_error(s, path, "gpio-set[-3]", error_msg,
+ error_msg_key));
}
static void test_set_input_pins(const void *data)
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 3/5] hw/arm/npcm8xx.c: Add all IRQ ENUMs
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
2025-10-15 1:18 ` [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis Coco Li
2025-10-15 1:18 ` [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property Coco Li
@ 2025-10-15 1:18 ` Coco Li
2025-10-15 1:18 ` [PATCH v2 4/5] hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input pin logic Coco Li
` (2 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg
Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd, Hao Wu
In the process of implementing serial gpio and adding the corresponding
ENUMs, also complete the list for npcm8xx.
Signed-off-by: Coco Li <lixiaoyan@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
---
hw/arm/npcm8xx.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/hw/arm/npcm8xx.c b/hw/arm/npcm8xx.c
index a276fea698..10887d07fa 100644
--- a/hw/arm/npcm8xx.c
+++ b/hw/arm/npcm8xx.c
@@ -92,8 +92,14 @@ enum NPCM8xxInterrupt {
NPCM8XX_GMAC2_IRQ,
NPCM8XX_GMAC3_IRQ,
NPCM8XX_GMAC4_IRQ,
- NPCM8XX_MMC_IRQ = 26,
+ NPCM8XX_ESPI_IRQ,
+ NPCM8XX_SIOX0_IRQ,
+ NPCM8XX_SIOX1_IRQ,
+ NPCM8XX_MC_IRQ = 25,
+ NPCM8XX_MMC_IRQ,
NPCM8XX_PSPI_IRQ = 28,
+ NPCM8XX_VDMA_IRQ,
+ NPCM8XX_MCTP_IRQ,
NPCM8XX_TIMER0_IRQ = 32, /* Timer Module 0 */
NPCM8XX_TIMER1_IRQ,
NPCM8XX_TIMER2_IRQ,
@@ -116,6 +122,14 @@ enum NPCM8xxInterrupt {
NPCM8XX_OHCI1_IRQ,
NPCM8XX_EHCI2_IRQ,
NPCM8XX_OHCI2_IRQ,
+ NPCM8XX_SPI1_IRQ = 82,
+ NPCM8XX_RNG_IRQ = 84,
+ NPCM8XX_SPI0_IRQ = 85,
+ NPCM8XX_SPI3_IRQ = 87,
+ NPCM8XX_GDMA0_IRQ = 88,
+ NPCM8XX_GDMA1_IRQ,
+ NPCM8XX_GDMA2_IRQ,
+ NPCM8XX_OTP_IRQ = 92,
NPCM8XX_PWM0_IRQ = 93, /* PWM module 0 */
NPCM8XX_PWM1_IRQ, /* PWM module 1 */
NPCM8XX_MFT0_IRQ = 96, /* MFT module 0 */
@@ -128,6 +142,11 @@ enum NPCM8xxInterrupt {
NPCM8XX_MFT7_IRQ, /* MFT module 7 */
NPCM8XX_PCI_MBOX1_IRQ = 105,
NPCM8XX_PCI_MBOX2_IRQ,
+ NPCM8XX_GPIO231_IRQ = 108,
+ NPCM8XX_GPIO233_IRQ,
+ NPCM8XX_GPIO234_IRQ,
+ NPCM8XX_GPIO93_IRQ,
+ NPCM8XX_GPIO94_IRQ,
NPCM8XX_GPIO0_IRQ = 116,
NPCM8XX_GPIO1_IRQ,
NPCM8XX_GPIO2_IRQ,
@@ -163,6 +182,12 @@ enum NPCM8xxInterrupt {
NPCM8XX_SMBUS24_IRQ,
NPCM8XX_SMBUS25_IRQ,
NPCM8XX_SMBUS26_IRQ,
+ NPCM8XX_FLM0_IRQ = 160,
+ NPCM8XX_FLM1_IRQ,
+ NPCM8XX_FLM2_IRQ,
+ NPCM8XX_FLM3_IRQ,
+ NPCM8XX_JMT1_IRQ = 188,
+ NPCM8XX_JMT2_IRQ,
NPCM8XX_UART0_IRQ = 192,
NPCM8XX_UART1_IRQ,
NPCM8XX_UART2_IRQ,
@@ -170,6 +195,22 @@ enum NPCM8xxInterrupt {
NPCM8XX_UART4_IRQ,
NPCM8XX_UART5_IRQ,
NPCM8XX_UART6_IRQ,
+ NPCM8XX_I3C0_IRQ = 224,
+ NPCM8XX_I3C1_IRQ,
+ NPCM8XX_I3C2_IRQ,
+ NPCM8XX_I3C3_IRQ,
+ NPCM8XX_I3C4_IRQ,
+ NPCM8XX_I3C5_IRQ,
+ NPCM8XX_A35INTERR_IRQ = 240,
+ NPCM8XX_A35EXTERR_IRQ,
+ NPCM8XX_PMU0_IRQ,
+ NPCM8XX_PMU1_IRQ,
+ NPCM8XX_PMU2_IRQ,
+ NPCM8XX_PMU3_IRQ,
+ NPCM8XX_CTI0_IRQ,
+ NPCM8XX_CTI1_IRQ,
+ NPCM8XX_CTI2_IRQ,
+ NPCM8XX_CTI3_IRQ,
};
/* Total number of GIC interrupts, including internal Cortex-A35 interrupts. */
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 4/5] hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input pin logic
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
` (2 preceding siblings ...)
2025-10-15 1:18 ` [PATCH v2 3/5] hw/arm/npcm8xx.c: Add all IRQ ENUMs Coco Li
@ 2025-10-15 1:18 ` Coco Li
2025-10-15 1:18 ` [PATCH v2 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device " Coco Li
2025-10-17 5:49 ` [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Cédric Le Goater
5 siblings, 0 replies; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg
Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd, Hao Wu
Signed-off-by: Coco Li <lixiaoyan@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
---
hw/arm/npcm8xx.c | 23 +-
hw/gpio/meson.build | 1 +
hw/gpio/npcm8xx_sgpio.c | 425 +++++++++++++++++++++++++++++++
hw/gpio/trace-events | 4 +
include/hw/arm/npcm8xx.h | 2 +
include/hw/gpio/npcm8xx_sgpio.h | 45 ++++
tests/qtest/meson.build | 3 +-
tests/qtest/npcm8xx_sgpio-test.c | 100 ++++++++
8 files changed, 600 insertions(+), 3 deletions(-)
create mode 100644 hw/gpio/npcm8xx_sgpio.c
create mode 100644 include/hw/gpio/npcm8xx_sgpio.h
create mode 100644 tests/qtest/npcm8xx_sgpio-test.c
diff --git a/hw/arm/npcm8xx.c b/hw/arm/npcm8xx.c
index 10887d07fa..ae72c6d54b 100644
--- a/hw/arm/npcm8xx.c
+++ b/hw/arm/npcm8xx.c
@@ -369,6 +369,11 @@ static const struct {
},
};
+static const hwaddr npcm8xx_sgpio_addr[] = {
+ 0xf0101000,
+ 0xf0102000,
+};
+
static const struct {
const char *name;
hwaddr regs_addr;
@@ -474,6 +479,11 @@ static void npcm8xx_init(Object *obj)
}
+ for (i = 0; i < ARRAY_SIZE(s->sgpio); i++) {
+ object_initialize_child(obj, "sgpio[*]",
+ &s->sgpio[i], TYPE_NPCM8XX_SGPIO);
+ }
+
for (i = 0; i < ARRAY_SIZE(s->smbus); i++) {
object_initialize_child(obj, "smbus[*]", &s->smbus[i],
TYPE_NPCM7XX_SMBUS);
@@ -671,6 +681,17 @@ static void npcm8xx_realize(DeviceState *dev, Error **errp)
npcm8xx_irq(s, NPCM8XX_GPIO0_IRQ + i));
}
+ /* Serial SIOX modules. Cannot fail. */
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_sgpio_addr) != ARRAY_SIZE(s->sgpio));
+ for (i = 0; i < ARRAY_SIZE(s->sgpio); i++) {
+ Object *obj = OBJECT(&s->sgpio[i]);
+
+ sysbus_realize(SYS_BUS_DEVICE(obj), &error_abort);
+ sysbus_mmio_map(SYS_BUS_DEVICE(obj), 0, npcm8xx_sgpio_addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(obj), 0,
+ npcm8xx_irq(s, NPCM8XX_SIOX0_IRQ + i));
+ }
+
/* SMBus modules. Cannot fail. */
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm8xx_smbus_addr) != ARRAY_SIZE(s->smbus));
for (i = 0; i < ARRAY_SIZE(s->smbus); i++) {
@@ -818,8 +839,6 @@ static void npcm8xx_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("npcm8xx.bt", 0xf0030000, 4 * KiB);
create_unimplemented_device("npcm8xx.espi", 0xf009f000, 4 * KiB);
create_unimplemented_device("npcm8xx.peci", 0xf0100000, 4 * KiB);
- create_unimplemented_device("npcm8xx.siox[1]", 0xf0101000, 4 * KiB);
- create_unimplemented_device("npcm8xx.siox[2]", 0xf0102000, 4 * KiB);
create_unimplemented_device("npcm8xx.tmps", 0xf0188000, 4 * KiB);
create_unimplemented_device("npcm8xx.viru1", 0xf0204000, 4 * KiB);
create_unimplemented_device("npcm8xx.viru2", 0xf0205000, 4 * KiB);
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 74840619c0..8a3879983c 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -18,3 +18,4 @@ system_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_gpio.c'))
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c'))
+system_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx_sgpio.c'))
diff --git a/hw/gpio/npcm8xx_sgpio.c b/hw/gpio/npcm8xx_sgpio.c
new file mode 100644
index 0000000000..f7d6bbf672
--- /dev/null
+++ b/hw/gpio/npcm8xx_sgpio.c
@@ -0,0 +1,425 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Nuvoton Serial I/O EXPANSION INTERFACE (SOIX)
+ *
+ * Copyright 2025 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/gpio/npcm8xx_sgpio.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+#define NPCM8XX_SGPIO_RD_MODE_MASK 0x6
+#define NPCM8XX_SGPIO_RD_MODE_PERIODIC 0x4
+#define NPCM8XX_SGPIO_RD_MODE_ON_DEMAND 0x0
+#define NPCM8XX_SGPIO_IOXCTS_IOXIF_EN BIT(7)
+#define NPCM8XX_SGPIO_IOXCTS_WR_PEND BIT(6)
+#define NPCM8XX_SGPIO_IOXCTS_DATA16W BIT(3)
+#define NPCM8XX_SGPIO_REGS_SIZE (4 * KiB)
+
+#define NPCM8XX_SGPIO_IXOEVCFG_FALLING BIT(1)
+#define NPCM8XX_SGPIO_IXOEVCFG_RISING BIT(0)
+#define NPCM8XX_SGPIO_IXOEVCFG_BOTH (NPCM8XX_SGPIO_IXOEVCFG_FALLING | \
+ NPCM8XX_SGPIO_IXOEVCFG_RISING)
+
+#define IXOEVCFG_MASK 0x3
+
+/* 8-bit register indices, with the event config registers being 16-bit */
+enum NPCM8xxSGPIORegister {
+ NPCM8XX_SGPIO_XDOUT0,
+ NPCM8XX_SGPIO_XDOUT1,
+ NPCM8XX_SGPIO_XDOUT2,
+ NPCM8XX_SGPIO_XDOUT3,
+ NPCM8XX_SGPIO_XDOUT4,
+ NPCM8XX_SGPIO_XDOUT5,
+ NPCM8XX_SGPIO_XDOUT6,
+ NPCM8XX_SGPIO_XDOUT7,
+ NPCM8XX_SGPIO_XDIN0,
+ NPCM8XX_SGPIO_XDIN1,
+ NPCM8XX_SGPIO_XDIN2,
+ NPCM8XX_SGPIO_XDIN3,
+ NPCM8XX_SGPIO_XDIN4,
+ NPCM8XX_SGPIO_XDIN5,
+ NPCM8XX_SGPIO_XDIN6,
+ NPCM8XX_SGPIO_XDIN7,
+ NPCM8XX_SGPIO_XEVCFG0 = 0x10,
+ NPCM8XX_SGPIO_XEVCFG1 = 0x12,
+ NPCM8XX_SGPIO_XEVCFG2 = 0x14,
+ NPCM8XX_SGPIO_XEVCFG3 = 0x16,
+ NPCM8XX_SGPIO_XEVCFG4 = 0x18,
+ NPCM8XX_SGPIO_XEVCFG5 = 0x1a,
+ NPCM8XX_SGPIO_XEVCFG6 = 0x1c,
+ NPCM8XX_SGPIO_XEVCFG7 = 0x1e,
+ NPCM8XX_SGPIO_XEVSTS0 = 0x20,
+ NPCM8XX_SGPIO_XEVSTS1,
+ NPCM8XX_SGPIO_XEVSTS2,
+ NPCM8XX_SGPIO_XEVSTS3,
+ NPCM8XX_SGPIO_XEVSTS4,
+ NPCM8XX_SGPIO_XEVSTS5,
+ NPCM8XX_SGPIO_XEVSTS6,
+ NPCM8XX_SGPIO_XEVSTS7,
+ NPCM8XX_SGPIO_IOXCTS,
+ NPCM8XX_SGPIO_IOXINDR,
+ NPCM8XX_SGPIO_IOXCFG1,
+ NPCM8XX_SGPIO_IOXCFG2,
+ NPCM8XX_SGPIO_IOXDATR = 0x2d,
+ NPCM8XX_SGPIO_REGS_END,
+};
+
+static uint8_t npcm8xx_sgpio_get_in_port(NPCM8xxSGPIOState *s)
+{
+ uint8_t p;
+
+ p = s->regs[NPCM8XX_SGPIO_IOXCFG2] & 0xf;
+ if (p > 8) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Trying to set more the allowed input ports %d\n",
+ DEVICE(s)->canonical_path, p);
+ }
+
+ return p;
+}
+
+static uint8_t npcm8xx_sgpio_get_out_port(NPCM8xxSGPIOState *s)
+{
+ uint8_t p;
+
+ p = (s->regs[NPCM8XX_SGPIO_IOXCFG2] >> 4) & 0xf;
+ if (p > 8) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Trying to set more the allowed output ports %d\n",
+ DEVICE(s)->canonical_path, p);
+ }
+
+ return p;
+}
+
+static bool npcm8xx_sgpio_is_16bit(NPCM8xxSGPIOState *s)
+{
+ return s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_DATA16W;
+}
+
+static uint64_t npcm8xx_sgpio_regs_read_with_cfg(NPCM8xxSGPIOState *s,
+ hwaddr reg)
+{
+ bool rd_word = npcm8xx_sgpio_is_16bit(s);
+ uint64_t value;
+
+ if (rd_word) {
+ value = ((uint16_t)s->regs[reg] << 8) | s->regs[reg + 1];
+ } else {
+ value = s->regs[reg];
+ }
+
+ return value;
+}
+
+static void npcm8xx_sgpio_update_event(NPCM8xxSGPIOState *s, uint64_t diff)
+{
+ /* TODO in upcoming patch */
+}
+
+static void npcm8xx_sgpio_update_pins_in(NPCM8xxSGPIOState *s, uint64_t diff)
+{
+ /* TODO in upcoming patch */
+}
+
+static void npcm8xx_sgpio_update_pins_out(NPCM8xxSGPIOState *s, hwaddr reg)
+{
+ uint8_t nout, dout;
+
+ if (~(s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_IOXIF_EN)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Device disabled, transaction out aborted\n",
+ DEVICE(s)->canonical_path);
+ }
+
+ nout = npcm8xx_sgpio_get_out_port(s);
+ dout = reg - NPCM8XX_SGPIO_XDOUT0;
+ if (dout >= nout) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Accessing XDOUT%d when NOUT is %d\n",
+ DEVICE(s)->canonical_path, dout, nout);
+ }
+ s->pin_out_level[dout] = s->regs[NPCM8XX_SGPIO_XDOUT0 + dout];
+ /* unset WR_PEND */
+ s->regs[NPCM8XX_SGPIO_IOXCTS] &= ~0x40;
+}
+
+static uint64_t npcm8xx_sgpio_regs_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ NPCM8xxSGPIOState *s = opaque;
+ uint8_t rd_mode = s->regs[NPCM8XX_SGPIO_IOXCTS] &
+ NPCM8XX_SGPIO_RD_MODE_MASK;
+ hwaddr reg = addr / sizeof(uint8_t);
+ uint8_t nout, nin, din, dout;
+ uint64_t value = 0;
+
+ switch (reg) {
+ case NPCM8XX_SGPIO_XDOUT0 ... NPCM8XX_SGPIO_XDOUT7:
+ nout = npcm8xx_sgpio_get_out_port(s);
+ dout = reg - NPCM8XX_SGPIO_XDOUT0;
+
+ if (dout >= nout) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Accessing XDOUT%d when NOUT is %d\n",
+ DEVICE(s)->canonical_path, dout, nout);
+ break;
+ }
+
+ value = npcm8xx_sgpio_regs_read_with_cfg(s, reg);
+ break;
+
+ case NPCM8XX_SGPIO_XDIN0 ... NPCM8XX_SGPIO_XDIN7:
+ nin = npcm8xx_sgpio_get_in_port(s);
+ din = reg - NPCM8XX_SGPIO_XDIN0;
+
+ if (din >= nin) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Accessing XDIN%d when NIN is %d\n",
+ DEVICE(s)->canonical_path, din, nin);
+ break;
+ }
+
+ switch (rd_mode) {
+ case NPCM8XX_SGPIO_RD_MODE_PERIODIC:
+ /* XDIN are up-to-date from scanning, return directly. */
+ value = npcm8xx_sgpio_regs_read_with_cfg(s, reg);
+ break;
+ case NPCM8XX_SGPIO_RD_MODE_ON_DEMAND:
+ /*
+ * IOX_SCAN write behavior is unimplemented.
+ * Event generation is also umimplemented.
+ */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: On Demand with Polling reading mode is not implemented.\n",
+ __func__);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown read mode\n", __func__);
+ }
+ break;
+
+ case NPCM8XX_SGPIO_XEVCFG0 ... NPCM8XX_SGPIO_XEVCFG7:
+ value = ((uint64_t)s->regs[reg] << 8) | s->regs[reg + 1];
+ break;
+
+ case NPCM8XX_SGPIO_XEVSTS0 ... NPCM8XX_SGPIO_XEVSTS7:
+ value = npcm8xx_sgpio_regs_read_with_cfg(s, reg);
+ break;
+
+ case NPCM8XX_SGPIO_IOXCTS ... NPCM8XX_SGPIO_IOXDATR:
+ value = s->regs[reg];
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, addr);
+ break;
+ }
+
+ trace_npcm8xx_sgpio_read(DEVICE(s)->canonical_path, addr, value);
+
+ return value;
+}
+
+static void npcm8xx_sgpio_regs_write(void *opaque, hwaddr addr, uint64_t v,
+ unsigned int size)
+{
+ hwaddr reg = addr / sizeof(uint8_t);
+ uint8_t hi_val = (uint8_t)(v >> 8);
+ NPCM8xxSGPIOState *s = opaque;
+ uint8_t value = (uint8_t) v;
+ uint8_t diff;
+
+ trace_npcm8xx_sgpio_write(DEVICE(s)->canonical_path, addr, v);
+
+ switch (reg) {
+ case NPCM8XX_SGPIO_XDOUT0 ... NPCM8XX_SGPIO_XDOUT7:
+ /* Set WR_PEND bit */
+ s->regs[NPCM8XX_SGPIO_IOXCTS] |= 0x40;
+ if (npcm8xx_sgpio_is_16bit(s)) {
+ if ((reg - NPCM8XX_SGPIO_XDOUT0) % 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write unaligned 16 bit register @ 0x%"
+ HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, addr);
+ break;
+ }
+ s->regs[reg] = hi_val;
+ s->regs[reg + 1] = value;
+ npcm8xx_sgpio_update_pins_out(s, reg + 1);
+ } else {
+ s->regs[reg] = value;
+ }
+ npcm8xx_sgpio_update_pins_out(s, reg);
+ break;
+
+ /* 2 byte long regs */
+ case NPCM8XX_SGPIO_XEVCFG0 ... NPCM8XX_SGPIO_XEVCFG7:
+ if (~(s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_IOXIF_EN)) {
+ s->regs[reg] = hi_val;
+ s->regs[reg + 1] = value;
+ }
+ break;
+
+ case NPCM8XX_SGPIO_XEVSTS0 ... NPCM8XX_SGPIO_XEVSTS7:
+ if (npcm8xx_sgpio_is_16bit(s)) {
+ if ((reg - NPCM8XX_SGPIO_XEVSTS0) % 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write unaligned 16 bit register @ 0x%"
+ HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, addr);
+ break;
+ }
+ s->regs[reg] ^= hi_val;
+ s->regs[reg + 1] ^= value;
+ npcm8xx_sgpio_update_event(s, 0);
+ } else {
+ s->regs[reg] ^= value;
+ npcm8xx_sgpio_update_event(s, 0);
+ }
+ break;
+
+ case NPCM8XX_SGPIO_IOXCTS:
+ /* Make sure RO bit WR_PEND is not written to */
+ value &= ~NPCM8XX_SGPIO_IOXCTS_WR_PEND;
+ diff = s->regs[reg] ^ value;
+ s->regs[reg] = value;
+ if ((s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_IOXIF_EN) &&
+ (diff & NPCM8XX_SGPIO_RD_MODE_MASK)) {
+ /* reset RD_MODE if attempting to write with IOXIF_EN enabled */
+ s->regs[reg] ^= (diff & NPCM8XX_SGPIO_RD_MODE_MASK);
+ }
+ break;
+
+ case NPCM8XX_SGPIO_IOXINDR:
+ /*
+ * Only relevant to SIOX1.
+ * HSIOX unimplemented for both, set value and do nothing.
+ */
+ s->regs[reg] = value;
+ break;
+
+ case NPCM8XX_SGPIO_IOXCFG1:
+ case NPCM8XX_SGPIO_IOXCFG2:
+ if (~(s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_IOXIF_EN)) {
+ s->regs[reg] = value;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: trying to write to register @ 0x%"
+ HWADDR_PRIx "while IOXIF_EN is enabled\n",
+ DEVICE(s)->canonical_path, addr);
+ }
+ break;
+
+ case NPCM8XX_SGPIO_XDIN0 ... NPCM8XX_SGPIO_XDIN7:
+ case NPCM8XX_SGPIO_IOXDATR:
+ /* IOX_SCAN is unimplemented given no on-demand mode */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only register @ 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, addr);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps npcm8xx_sgpio_regs_ops = {
+ .read = npcm8xx_sgpio_regs_read,
+ .write = npcm8xx_sgpio_regs_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2,
+ .unaligned = false,
+ },
+};
+
+static void npcm8xx_sgpio_enter_reset(Object *obj, ResetType type)
+{
+ NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+
+ memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void npcm8xx_sgpio_hold_reset(Object *obj, ResetType type)
+{
+ NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+
+ npcm8xx_sgpio_update_pins_in(s, -1);
+}
+
+static void npcm8xx_sgpio_init(Object *obj)
+{
+ NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+
+ memory_region_init_io(&s->mmio, obj, &npcm8xx_sgpio_regs_ops, s,
+ "regs", NPCM8XX_SGPIO_REGS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ /* TODO: Add input GPIO pins */
+}
+
+static const VMStateDescription vmstate_npcm8xx_sgpio = {
+ .name = "npcm8xx-sgpio",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(pin_in_level, NPCM8xxSGPIOState,
+ NPCM8XX_SGPIO_NR_PINS / NPCM8XX_SGPIO_MAX_PORTS),
+ VMSTATE_UINT8_ARRAY(pin_out_level, NPCM8xxSGPIOState,
+ NPCM8XX_SGPIO_NR_PINS / NPCM8XX_SGPIO_MAX_PORTS),
+ VMSTATE_UINT8_ARRAY(regs, NPCM8xxSGPIOState, NPCM8XX_SGPIO_NR_REGS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm8xx_sgpio_class_init(ObjectClass *klass, const void *data)
+{
+ ResettableClass *reset = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ QEMU_BUILD_BUG_ON(NPCM8XX_SGPIO_REGS_END > NPCM8XX_SGPIO_NR_REGS);
+
+ dc->desc = "NPCM8xx SIOX Controller";
+ dc->vmsd = &vmstate_npcm8xx_sgpio;
+ reset->phases.enter = npcm8xx_sgpio_enter_reset;
+ reset->phases.hold = npcm8xx_sgpio_hold_reset;
+}
+
+static const TypeInfo npcm8xx_sgpio_types[] = {
+ {
+ .name = TYPE_NPCM8XX_SGPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM8xxSGPIOState),
+ .class_init = npcm8xx_sgpio_class_init,
+ .instance_init = npcm8xx_sgpio_init,
+ },
+};
+DEFINE_TYPES(npcm8xx_sgpio_types);
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index cea896b28f..5a3625cabc 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -12,6 +12,10 @@ npcm7xx_gpio_set_input(const char *id, int32_t line, int32_t level) "%s line: %"
npcm7xx_gpio_set_output(const char *id, int32_t line, int32_t level) "%s line: %" PRIi32 " level: %" PRIi32
npcm7xx_gpio_update_events(const char *id, uint32_t evst, uint32_t even) "%s evst: 0x%08" PRIx32 " even: 0x%08" PRIx32
+# npcm8xx_sgpio.c
+npcm8xx_sgpio_read(const char *id, uint64_t offset, uint64_t value) " %s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
+npcm8xx_sgpio_write(const char *id, uint64_t offset, uint64_t value) " %s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
+
# nrf51_gpio.c
nrf51_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 0x%" PRIx64
nrf51_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/include/hw/arm/npcm8xx.h b/include/hw/arm/npcm8xx.h
index a8377db490..2d177329b8 100644
--- a/include/hw/arm/npcm8xx.h
+++ b/include/hw/arm/npcm8xx.h
@@ -21,6 +21,7 @@
#include "hw/cpu/cluster.h"
#include "hw/gpio/npcm7xx_gpio.h"
#include "hw/i2c/npcm7xx_smbus.h"
+#include "hw/gpio/npcm8xx_sgpio.h"
#include "hw/intc/arm_gic_common.h"
#include "hw/mem/npcm7xx_mc.h"
#include "hw/misc/npcm_clk.h"
@@ -104,6 +105,7 @@ struct NPCM8xxState {
NPCMPCSState pcs;
NPCM7xxSDHCIState mmc;
NPCMPSPIState pspi;
+ NPCM8xxSGPIOState sgpio[2];
};
struct NPCM8xxClass {
diff --git a/include/hw/gpio/npcm8xx_sgpio.h b/include/hw/gpio/npcm8xx_sgpio.h
new file mode 100644
index 0000000000..cce844951e
--- /dev/null
+++ b/include/hw/gpio/npcm8xx_sgpio.h
@@ -0,0 +1,45 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Nuvoton NPCM8xx General Purpose Input / Output (GPIO)
+ *
+ * Copyright 2025 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef NPCM8XX_SGPIO_H
+#define NPCM8XX_SGPIO_H
+
+#include "hw/sysbus.h"
+
+/* Number of pins managed by each controller. */
+#define NPCM8XX_SGPIO_NR_PINS (64)
+
+/*
+ * Number of registers in our device state structure. Don't change this without
+ * incrementing the version_id in the vmstate.
+ */
+#define NPCM8XX_SGPIO_NR_REGS (0x2e)
+#define NPCM8XX_SGPIO_MAX_PORTS 8
+
+typedef struct NPCM8xxSGPIOState {
+ SysBusDevice parent;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+
+ uint8_t pin_in_level[NPCM8XX_SGPIO_MAX_PORTS];
+ uint8_t pin_out_level[NPCM8XX_SGPIO_MAX_PORTS];
+ uint8_t regs[NPCM8XX_SGPIO_NR_REGS];
+} NPCM8xxSGPIOState;
+
+#define TYPE_NPCM8XX_SGPIO "npcm8xx-sgpio"
+OBJECT_DECLARE_SIMPLE_TYPE(NPCM8xxSGPIOState, NPCM8XX_SGPIO)
+
+#endif /* NPCM8XX_SGPIO_H */
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 669d07c06b..12737ad21f 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -212,7 +212,8 @@ qtests_npcm7xx = \
'npcm7xx_watchdog_timer-test'] + \
(slirp.found() ? ['npcm7xx_emc-test'] : [])
qtests_npcm8xx = \
- ['npcm_gmac-test']
+ ['npcm_gmac-test',
+ 'npcm8xx_sgpio-test',]
qtests_aspeed = \
['aspeed_gpio-test',
'aspeed_hace-test',
diff --git a/tests/qtest/npcm8xx_sgpio-test.c b/tests/qtest/npcm8xx_sgpio-test.c
new file mode 100644
index 0000000000..b0b11b3481
--- /dev/null
+++ b/tests/qtest/npcm8xx_sgpio-test.c
@@ -0,0 +1,100 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * QTest testcase for the Nuvoton NPCM8xx I/O EXPANSION INTERFACE (SOIX)
+ * modules.
+ *
+ * Copyright 2025 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define NR_SGPIO_DEVICES 8
+#define SGPIO(x) (0xf0101000 + (x) * 0x1000)
+#define SGPIO_IRQ(x) (19 + (x))
+
+/* SGPIO registers */
+#define GP_N_XDOUT(x) (0x00 + x)
+#define GP_N_XDIN(x) (0x08 + x)
+#define GP_N_XEVCFG(x) (0x10 + (x) * 0x2)
+#define GP_N_XEVSTS(x) (0x20 + x)
+#define GP_N_IOXCTS 0x28
+#define GP_N_IOXINDR 0x29
+#define GP_N_IOXCFG1 0x2a
+#define GP_N_IOXCFG2 0x2b
+#define GP_N_RD_MODE_PERIODIC 0x4
+#define GP_N_IOXIF_EN 0x80
+
+
+/* Restore the SGPIO controller to a sensible default state. */
+static void sgpio_reset(int n)
+{
+ int i;
+
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ writel(SGPIO(n) + GP_N_XDOUT(i), 0x00000000);
+ writel(SGPIO(n) + GP_N_XEVCFG(i), 0x00000000);
+ writel(SGPIO(n) + GP_N_XEVSTS(i), 0x00000000);
+ }
+ writel(SGPIO(n) + GP_N_IOXCTS, 0x00000000);
+ writel(SGPIO(n) + GP_N_IOXINDR, 0x00000000);
+ writel(SGPIO(n) + GP_N_IOXCFG1, 0x00000000);
+ writel(SGPIO(n) + GP_N_IOXCFG2, 0x00000000);
+}
+
+static void test_read_dout_byte(void)
+{
+ int i;
+
+ sgpio_reset(0);
+
+ /* set all 8 output devices */
+ writel(SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ writel(SGPIO(0) + GP_N_XDOUT(i), 0xff);
+ g_assert_cmphex(readb(SGPIO(0) + GP_N_XDOUT(i)), ==, 0xff);
+ }
+}
+
+static void test_read_dout_word(void)
+{
+ int i;
+
+ sgpio_reset(0);
+ /* set all 8 output devices */
+ writel(SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
+ /* set 16 bit aligned access */
+ writel(SGPIO(0) + GP_N_IOXCTS, 1 << 3);
+ for (i = 0; i < NR_SGPIO_DEVICES / 2; ++i) {
+ writel(SGPIO(0) + GP_N_XDOUT(i * 2), 0xf0f0);
+ g_assert_cmphex(readw(SGPIO(0) + GP_N_XDOUT(i * 2)), ==, 0xf0f0);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions();
+
+ qtest_add_func("/npcm8xx_sgpio/read_dout_byte", test_read_dout_byte);
+ qtest_add_func("/npcm8xx_sgpio/read_dout_word", test_read_dout_word);
+
+ qtest_start("-machine npcm845-evb");
+ qtest_irq_intercept_in(global_qtest, "/machine/soc/sgpio");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device input pin logic
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
` (3 preceding siblings ...)
2025-10-15 1:18 ` [PATCH v2 4/5] hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input pin logic Coco Li
@ 2025-10-15 1:18 ` Coco Li
2025-10-17 5:49 ` [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Cédric Le Goater
5 siblings, 0 replies; 10+ messages in thread
From: Coco Li @ 2025-10-15 1:18 UTC (permalink / raw)
To: peter.maydell, clg
Cc: qemu-arm, qemu-devel, lixiaoyan, flwu, andrew, philmd, Hao Wu
Signed-off-by: Coco Li <lixiaoyan@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
---
hw/gpio/npcm8xx_sgpio.c | 119 +++++++++++++++++---
include/hw/gpio/npcm8xx_sgpio.h | 4 +-
tests/qtest/npcm8xx_sgpio-test.c | 180 ++++++++++++++++++++++++++-----
3 files changed, 259 insertions(+), 44 deletions(-)
diff --git a/hw/gpio/npcm8xx_sgpio.c b/hw/gpio/npcm8xx_sgpio.c
index f7d6bbf672..fac24a2d8e 100644
--- a/hw/gpio/npcm8xx_sgpio.c
+++ b/hw/gpio/npcm8xx_sgpio.c
@@ -22,11 +22,14 @@
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
+#include "qemu/bitops.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/units.h"
#include "trace.h"
+#include <limits.h>
+
#define NPCM8XX_SGPIO_RD_MODE_MASK 0x6
#define NPCM8XX_SGPIO_RD_MODE_PERIODIC 0x4
#define NPCM8XX_SGPIO_RD_MODE_ON_DEMAND 0x0
@@ -126,24 +129,67 @@ static uint64_t npcm8xx_sgpio_regs_read_with_cfg(NPCM8xxSGPIOState *s,
if (rd_word) {
value = ((uint16_t)s->regs[reg] << 8) | s->regs[reg + 1];
} else {
- value = s->regs[reg];
+ value = (uint8_t) s->regs[reg];
}
return value;
}
+/*
+ * For each pin, event can be generated from one of 3 conditions.
+ *
+ * | 1 | 0 | event configuration
+ * -----------------------------
+ * | 0 | 0 | disabled
+ * | 0 | 1 | 0-1 transition
+ * | 1 | 0 | 1-0 transition
+ * | 1 | 1 | even by any transition
+ */
+
static void npcm8xx_sgpio_update_event(NPCM8xxSGPIOState *s, uint64_t diff)
{
- /* TODO in upcoming patch */
+ uint8_t *d = (uint8_t *)&(diff);
+ uint8_t *p = (uint8_t *)&s->pin_in_level;
+ uint16_t type;
+ uint8_t sts;
+ int i;
+
+ for (i = 0; i < npcm8xx_sgpio_get_in_port(s); ++i) {
+ type = ((uint16_t)s->regs[NPCM8XX_SGPIO_XEVCFG0 + 2 * i] << 8) |
+ s->regs[NPCM8XX_SGPIO_XEVCFG0 + 2 * i + 1];
+
+ /* 0-1 transitions */
+ sts = p[i] & d[i] & (uint8_t)half_unshuffle32(type);
+ /* 1-0 transitions */
+ sts |= (~p[i]) & (d[i] & (uint8_t)half_unshuffle32(type >> 1));
+
+ s->regs[NPCM8XX_SGPIO_XEVSTS0 + i] = sts;
+
+ /* Generate event if the event status register tells us so */
+ qemu_set_irq(s->irq, !!(s->regs[NPCM8XX_SGPIO_XEVSTS0 + i]));
+ }
}
-static void npcm8xx_sgpio_update_pins_in(NPCM8xxSGPIOState *s, uint64_t diff)
+static void npcm8xx_sgpio_update_pins_in(NPCM8xxSGPIOState *s, uint64_t value)
{
- /* TODO in upcoming patch */
+ uint8_t *nv = (uint8_t *)&value;
+ uint8_t *ov = (uint8_t *)&s->pin_in_level;
+ uint64_t diff = s->pin_in_level ^ value;
+ int i;
+
+ for (i = 0; i < npcm8xx_sgpio_get_in_port(s); ++i) {
+ if (ov[i] == nv[i]) {
+ continue;
+ }
+ s->regs[NPCM8XX_SGPIO_XDIN0 + i] = nv[i];
+ }
+ s->pin_in_level = value;
+ npcm8xx_sgpio_update_event(s, diff);
}
static void npcm8xx_sgpio_update_pins_out(NPCM8xxSGPIOState *s, hwaddr reg)
{
+ uint8_t *p = (uint8_t *)&s->pin_out_level;
uint8_t nout, dout;
if (~(s->regs[NPCM8XX_SGPIO_IOXCTS] & NPCM8XX_SGPIO_IOXCTS_IOXIF_EN)) {
@@ -159,7 +205,7 @@ static void npcm8xx_sgpio_update_pins_out(NPCM8xxSGPIOState *s, hwaddr reg)
"%s: Accessing XDOUT%d when NOUT is %d\n",
DEVICE(s)->canonical_path, dout, nout);
}
- s->pin_out_level[dout] = s->regs[NPCM8XX_SGPIO_XDOUT0 + dout];
+ p[dout] = s->regs[reg];
/* unset WR_PEND */
s->regs[NPCM8XX_SGPIO_IOXCTS] &= ~0x40;
}
@@ -294,10 +340,8 @@ static void npcm8xx_sgpio_regs_write(void *opaque, hwaddr addr, uint64_t v,
}
s->regs[reg] ^= hi_val;
s->regs[reg + 1] ^= value;
- npcm8xx_sgpio_update_event(s, 0);
} else {
s->regs[reg] ^= value;
- npcm8xx_sgpio_update_event(s, 0);
}
break;
@@ -371,19 +415,70 @@ static void npcm8xx_sgpio_hold_reset(Object *obj, ResetType type)
{
NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
- npcm8xx_sgpio_update_pins_in(s, -1);
+ npcm8xx_sgpio_update_pins_in(s, 0);
+}
+
+static void npcm8xx_sgpio_set_input_lo(void *opaque, int line, int level)
+{
+ NPCM8xxSGPIOState *s = opaque;
+
+ g_assert(line >= 0 && line < NPCM8XX_SGPIO_NR_PINS / 2);
+
+ npcm8xx_sgpio_update_pins_in(s, BIT(line) && level);
+}
+
+static void npcm8xx_sgpio_set_input_hi(void *opaque, int line, int level)
+{
+ NPCM8xxSGPIOState *s = opaque;
+ uint64_t line_ull = line;
+
+ g_assert(line >= NPCM8XX_SGPIO_NR_PINS / 2 && line < NPCM8XX_SGPIO_NR_PINS);
+
+ npcm8xx_sgpio_update_pins_in(s, BIT(line_ull << 32) && level);
+}
+
+static void npcm8xx_sgpio_get_pins_in(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+
+ visit_type_uint64(v, name, &s->pin_in_level, errp);
+}
+
+static void npcm8xx_sgpio_set_pins_in(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+ uint64_t new_pins_in;
+
+ if (!visit_type_uint64(v, name, &new_pins_in, errp)) {
+ return;
+ }
+
+ npcm8xx_sgpio_update_pins_in(s, new_pins_in);
}
static void npcm8xx_sgpio_init(Object *obj)
{
NPCM8xxSGPIOState *s = NPCM8XX_SGPIO(obj);
+ DeviceState *dev = DEVICE(obj);
memory_region_init_io(&s->mmio, obj, &npcm8xx_sgpio_regs_ops, s,
"regs", NPCM8XX_SGPIO_REGS_SIZE);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
- /* TODO: Add input GPIO pins */
+ /* There are total 64 input pins that can be set */
+ QEMU_BUILD_BUG_ON(NPCM8XX_SGPIO_NR_PINS >
+ sizeof(s->pin_in_level) * CHAR_BIT);
+ qdev_init_gpio_in(dev, npcm8xx_sgpio_set_input_hi,
+ NPCM8XX_SGPIO_NR_PINS / 2);
+ qdev_init_gpio_in(dev, npcm8xx_sgpio_set_input_lo,
+ NPCM8XX_SGPIO_NR_PINS / 2);
+
+ object_property_add(obj, "sgpio-pins-in", "uint64",
+ npcm8xx_sgpio_get_pins_in, npcm8xx_sgpio_set_pins_in,
+ NULL, NULL);
}
static const VMStateDescription vmstate_npcm8xx_sgpio = {
@@ -391,10 +486,8 @@ static const VMStateDescription vmstate_npcm8xx_sgpio = {
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
- VMSTATE_UINT8_ARRAY(pin_in_level, NPCM8xxSGPIOState,
- NPCM8XX_SGPIO_NR_PINS / NPCM8XX_SGPIO_MAX_PORTS),
- VMSTATE_UINT8_ARRAY(pin_out_level, NPCM8xxSGPIOState,
- NPCM8XX_SGPIO_NR_PINS / NPCM8XX_SGPIO_MAX_PORTS),
+ VMSTATE_UINT64(pin_in_level, NPCM8xxSGPIOState),
+ VMSTATE_UINT64(pin_out_level, NPCM8xxSGPIOState),
VMSTATE_UINT8_ARRAY(regs, NPCM8xxSGPIOState, NPCM8XX_SGPIO_NR_REGS),
VMSTATE_END_OF_LIST(),
},
diff --git a/include/hw/gpio/npcm8xx_sgpio.h b/include/hw/gpio/npcm8xx_sgpio.h
index cce844951e..05dfafcb5e 100644
--- a/include/hw/gpio/npcm8xx_sgpio.h
+++ b/include/hw/gpio/npcm8xx_sgpio.h
@@ -34,8 +34,8 @@ typedef struct NPCM8xxSGPIOState {
MemoryRegion mmio;
qemu_irq irq;
- uint8_t pin_in_level[NPCM8XX_SGPIO_MAX_PORTS];
- uint8_t pin_out_level[NPCM8XX_SGPIO_MAX_PORTS];
+ uint64_t pin_in_level;
+ uint64_t pin_out_level;
uint8_t regs[NPCM8XX_SGPIO_NR_REGS];
} NPCM8xxSGPIOState;
diff --git a/tests/qtest/npcm8xx_sgpio-test.c b/tests/qtest/npcm8xx_sgpio-test.c
index b0b11b3481..b26109600c 100644
--- a/tests/qtest/npcm8xx_sgpio-test.c
+++ b/tests/qtest/npcm8xx_sgpio-test.c
@@ -36,65 +36,187 @@
#define GP_N_IOXIF_EN 0x80
+static void qtest_qom_set_uint64(QTestState *s, const char *path,
+ const char *property, uint64_t value)
+{
+ QDict *r;
+ QDict *qdict;
+
+ r = qtest_qmp(s, "{ 'execute': 'qom-set', 'arguments': "
+ "{ 'path': %s, 'property': %s, 'value': %" PRIu64 " } }",
+ path, property, value);
+
+ qdict = qdict_get_qdict(r, "error");
+ if (qdict) {
+ printf("DEBUG: set error: %s\n", qdict_get_try_str(qdict, "desc"));
+ }
+
+ qobject_unref(r);
+}
+
+
+static uint64_t qtest_qom_get_uint64(QTestState *s, const char *path,
+ const char *property)
+{
+ QDict *r;
+
+ uint64_t res;
+ r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
+ "{ 'path': %s, 'property': %s } }", path, property);
+
+ res = qdict_get_uint(r, "return");
+ qobject_unref(r);
+
+ return res;
+}
+
/* Restore the SGPIO controller to a sensible default state. */
-static void sgpio_reset(int n)
+static void sgpio_reset(QTestState *s, int n)
{
int i;
for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
- writel(SGPIO(n) + GP_N_XDOUT(i), 0x00000000);
- writel(SGPIO(n) + GP_N_XEVCFG(i), 0x00000000);
- writel(SGPIO(n) + GP_N_XEVSTS(i), 0x00000000);
+ qtest_writeq(s, SGPIO(n) + GP_N_XDOUT(i), 0x0);
+ qtest_writeq(s, SGPIO(n) + GP_N_XEVCFG(i), 0x0);
+ qtest_writeq(s, SGPIO(n) + GP_N_XEVSTS(i), 0x0);
}
- writel(SGPIO(n) + GP_N_IOXCTS, 0x00000000);
- writel(SGPIO(n) + GP_N_IOXINDR, 0x00000000);
- writel(SGPIO(n) + GP_N_IOXCFG1, 0x00000000);
- writel(SGPIO(n) + GP_N_IOXCFG2, 0x00000000);
+ qtest_writeq(s, SGPIO(n) + GP_N_IOXCTS, 0x0);
+ qtest_writeq(s, SGPIO(n) + GP_N_IOXINDR, 0x0);
+ qtest_writeq(s, SGPIO(n) + GP_N_IOXCFG1, 0x0);
+ qtest_writeq(s, SGPIO(n) + GP_N_IOXCFG2, 0x0);
}
-static void test_read_dout_byte(void)
+static void test_read_dout_byte(const char *machine)
{
+ QTestState *s = qtest_init(machine);
int i;
- sgpio_reset(0);
+ sgpio_reset(s, 0);
/* set all 8 output devices */
- writel(SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
+ qtest_writeq(s, SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
- writel(SGPIO(0) + GP_N_XDOUT(i), 0xff);
- g_assert_cmphex(readb(SGPIO(0) + GP_N_XDOUT(i)), ==, 0xff);
+ qtest_writeq(s, SGPIO(0) + GP_N_XDOUT(i), 0xff);
+ g_assert_cmphex(qtest_readb(s, SGPIO(0) + GP_N_XDOUT(i)), ==, 0xff);
}
+ qtest_quit(s);
}
-static void test_read_dout_word(void)
+static void test_read_dout_word(const char *machine)
{
+ QTestState *s = qtest_init(machine);
int i;
- sgpio_reset(0);
+ sgpio_reset(s, 0);
/* set all 8 output devices */
- writel(SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
+ qtest_writeq(s, SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES << 4);
/* set 16 bit aligned access */
- writel(SGPIO(0) + GP_N_IOXCTS, 1 << 3);
+ qtest_writeq(s, SGPIO(0) + GP_N_IOXCTS, 1 << 3);
for (i = 0; i < NR_SGPIO_DEVICES / 2; ++i) {
- writel(SGPIO(0) + GP_N_XDOUT(i * 2), 0xf0f0);
- g_assert_cmphex(readw(SGPIO(0) + GP_N_XDOUT(i * 2)), ==, 0xf0f0);
+ qtest_writeq(s, SGPIO(0) + GP_N_XDOUT(i * 2), 0xf0f0);
+ g_assert_cmphex(qtest_readw(s, SGPIO(0) + GP_N_XDOUT(i * 2)),
+ ==, 0xf0f0);
}
+ qtest_quit(s);
}
-int main(int argc, char **argv)
+static void test_events_din_rising_edge(const char *machine)
{
- int ret;
+ QTestState *s = qtest_init(machine);
+ const char path[] = "/machine/soc/sgpio[0]";
+ int i;
+
+ /* clear all inputs */
+ sgpio_reset(s, 0);
+
+ /* set all 8 input devices */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES);
+
+ /* set event detection type to be on the rising edge*/
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ qtest_writel(s, SGPIO(0) + GP_N_XEVCFG(i), 0x5555);
+ }
+ /* Set periodic reading mode, the only accepted mode */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCTS, GP_N_RD_MODE_PERIODIC);
+ /* enable device, set IOXIF_EN */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCTS,
+ GP_N_IOXIF_EN | GP_N_RD_MODE_PERIODIC);
+
+ qtest_irq_intercept_in(s, "/machine/soc/gic");
+
+ /* raise all input pin values */
+ qtest_qom_set_uint64(s, path, "sgpio-pins-in", 0xffffffffffffffff);
+ g_assert(qtest_qom_get_uint64(s, path, "sgpio-pins-in")
+ == 0xffffffffffffffff);
+
+ /* set event status to implicitly change pins */
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ g_assert_cmphex(qtest_readb(s, SGPIO(0) + GP_N_XDIN(i)), ==, 0xff);
+ g_assert_cmphex(qtest_readb(s, SGPIO(0) + GP_N_XEVSTS(i)), ==, 0xff);
+ g_assert_true(qtest_get_irq(s, SGPIO_IRQ(0)));
+ }
+ qtest_quit(s);
+}
+
+static void test_events_din_falling_edge(const char *machine)
+{
+ QTestState *s = qtest_init(machine);
+ const char path[] = "/machine/soc/sgpio[0]";
+ int i;
+
+ /* clear all inputs */
+ sgpio_reset(s, 0);
+
+ /* set all 8 input devices */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCFG2, NR_SGPIO_DEVICES);
+
+ /* set event detection type to be on the falling edge*/
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ qtest_writel(s, SGPIO(0) + GP_N_XEVCFG(i), 0xaaaa);
+ }
+ /* Set periodic reading mode, the only accepted mode */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCTS, GP_N_RD_MODE_PERIODIC);
+ /* enable device, set IOXIF_EN */
+ qtest_writel(s, SGPIO(0) + GP_N_IOXCTS,
+ GP_N_IOXIF_EN | GP_N_RD_MODE_PERIODIC);
+
+ qtest_irq_intercept_in(s, "/machine/soc/gic");
+
+ /* raise all input pin values */
+ qtest_qom_set_uint64(s, path, "sgpio-pins-in", 0xffffffffffffffff);
+ g_assert(qtest_qom_get_uint64(s, path, "sgpio-pins-in")
+ == 0xffffffffffffffff);
+
+ /* reset all input pin values */
+ qtest_qom_set_uint64(s, path, "sgpio-pins-in", 0x0);
+ g_assert(qtest_qom_get_uint64(s, path, "sgpio-pins-in") == 0x0);
+
+ /* set event status to implicitly change pins */
+ for (i = 0; i < NR_SGPIO_DEVICES; ++i) {
+ g_assert_cmphex(qtest_readb(s, SGPIO(0) + GP_N_XDIN(i)), ==, 0x00);
+ g_assert_cmphex(qtest_readb(s, SGPIO(0) + GP_N_XEVSTS(i)), ==, 0xff);
+ g_assert_true(qtest_get_irq(s, SGPIO_IRQ(0)));
+ }
+
+ qtest_quit(s);
+}
+
+
+static void test_npcm8xx(void)
+{
+ test_read_dout_byte("-machine npcm845-evb");
+ test_read_dout_word("-machine npcm845-evb");
+ test_events_din_rising_edge("-machine npcm845-evb");
+ test_events_din_falling_edge("-machine npcm845-evb");
+}
+
+int main(int argc, char **argv)
+{
g_test_init(&argc, &argv, NULL);
g_test_set_nonfatal_assertions();
- qtest_add_func("/npcm8xx_sgpio/read_dout_byte", test_read_dout_byte);
- qtest_add_func("/npcm8xx_sgpio/read_dout_word", test_read_dout_word);
-
- qtest_start("-machine npcm845-evb");
- qtest_irq_intercept_in(global_qtest, "/machine/soc/sgpio");
- ret = g_test_run();
- qtest_end();
+ qtest_add_func("/npcm8xx/sgpio", test_npcm8xx);
- return ret;
+ return g_test_run();
}
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis
2025-10-15 1:18 ` [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis Coco Li
@ 2025-10-16 22:56 ` Andrew Jeffery
2025-10-17 5:31 ` Cédric Le Goater
0 siblings, 1 reply; 10+ messages in thread
From: Andrew Jeffery @ 2025-10-16 22:56 UTC (permalink / raw)
To: Coco Li, peter.maydell, clg; +Cc: qemu-arm, qemu-devel, flwu, philmd
On Wed, 2025-10-15 at 01:18 +0000, Coco Li wrote:
> From: Felix Wu <flwu@google.com>
>
> Added 32 bits property for ASPEED GPIO. Previously it can only be access in bitwise manner.
>
> The changes to qobject is to index gpios with array indices on top of accessing with registers.
> This allows for easier gpio access, especially in tests with complex behaviors that requires large number of gpios at a time, like fault injection and networking behaviors.
>
> Indexing multiple gpios at once allows qmp/side band client to no longer hardcode and populate register names and manipulate them faster.
>
> Signed-off-by: Felix Wu <flwu@google.com>
Thanks for updating the commit message. It should be properly wrapped,
but I expect that can be done when the patch is applied.
Reviewed-by: Andrew Jeffery <andrew@codeconstruct.com.au>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis
2025-10-16 22:56 ` Andrew Jeffery
@ 2025-10-17 5:31 ` Cédric Le Goater
0 siblings, 0 replies; 10+ messages in thread
From: Cédric Le Goater @ 2025-10-17 5:31 UTC (permalink / raw)
To: Andrew Jeffery, Coco Li, peter.maydell; +Cc: qemu-arm, qemu-devel, flwu, philmd
On 10/17/25 00:56, Andrew Jeffery wrote:
> On Wed, 2025-10-15 at 01:18 +0000, Coco Li wrote:
>> From: Felix Wu <flwu@google.com>
>>
>> Added 32 bits property for ASPEED GPIO. Previously it can only be access in bitwise manner.
>>
>> The changes to qobject is to index gpios with array indices on top of accessing with registers.
>> This allows for easier gpio access, especially in tests with complex behaviors that requires large number of gpios at a time, like fault injection and networking behaviors.
>>
>> Indexing multiple gpios at once allows qmp/side band client to no longer hardcode and populate register names and manipulate them faster.
>>
>> Signed-off-by: Felix Wu <flwu@google.com>
>
> Thanks for updating the commit message. It should be properly wrapped,
> but I expect that can be done when the patch is applied.
>
> Reviewed-by: Andrew Jeffery <andrew@codeconstruct.com.au>
yeah. I can do that.
Thanks,
C.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property
2025-10-15 1:18 ` [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property Coco Li
@ 2025-10-17 5:37 ` Cédric Le Goater
0 siblings, 0 replies; 10+ messages in thread
From: Cédric Le Goater @ 2025-10-17 5:37 UTC (permalink / raw)
To: Coco Li, peter.maydell; +Cc: qemu-arm, qemu-devel, flwu, andrew, philmd
On 10/15/25 03:18, Coco Li wrote:
> From: Felix Wu <flwu@google.com>
>
> - Added qtests to test gpio-set property for ASPEED.
> - Added function to get uint in qdict.
>
> Signed-off-by: Felix Wu <flwu@google.com>
> ---
> include/qobject/qdict.h | 1 +
> qobject/qdict.c | 13 ++++
> tests/qtest/aspeed_gpio-test.c | 105 ++++++++++++++++++++++++++++++---
> 3 files changed, 110 insertions(+), 9 deletions(-)
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
>
> diff --git a/include/qobject/qdict.h b/include/qobject/qdict.h
> index 903e6e5462..861996f08d 100644
> --- a/include/qobject/qdict.h
> +++ b/include/qobject/qdict.h
> @@ -57,6 +57,7 @@ void qdict_put_str(QDict *qdict, const char *key, const char *value);
>
> double qdict_get_double(const QDict *qdict, const char *key);
> int64_t qdict_get_int(const QDict *qdict, const char *key);
> +uint64_t qdict_get_uint(const QDict *qdict, const char *key);
> bool qdict_get_bool(const QDict *qdict, const char *key);
> QList *qdict_get_qlist(const QDict *qdict, const char *key);
> QDict *qdict_get_qdict(const QDict *qdict, const char *key);
> diff --git a/qobject/qdict.c b/qobject/qdict.c
> index a90ac9ae2f..0dafe6d421 100644
> --- a/qobject/qdict.c
> +++ b/qobject/qdict.c
> @@ -209,6 +209,19 @@ int64_t qdict_get_int(const QDict *qdict, const char *key)
> return qnum_get_int(qobject_to(QNum, qdict_get(qdict, key)));
> }
>
> +/**
> + * qdict_get_uint(): Get an unsigned integer mapped by 'key'
> + *
> + * This function assumes that 'key' exists and it stores a
> + * QNum representable as uint.
> + *
> + * Return unsigned integer mapped by 'key'.
> + */
> +uint64_t qdict_get_uint(const QDict *qdict, const char *key)
> +{
> + return qnum_get_uint(qobject_to(QNum, qdict_get(qdict, key)));
> +}
> +
> /**
> * qdict_get_bool(): Get a bool mapped by 'key'
> *
> diff --git a/tests/qtest/aspeed_gpio-test.c b/tests/qtest/aspeed_gpio-test.c
> index 12675d4cbb..c2f9ca2298 100644
> --- a/tests/qtest/aspeed_gpio-test.c
> +++ b/tests/qtest/aspeed_gpio-test.c
> @@ -27,28 +27,115 @@
> #include "qemu/timer.h"
> #include "qobject/qdict.h"
> #include "libqtest-single.h"
> +#include "qemu/typedefs.h"
>
> #define AST2600_GPIO_BASE 0x1E780000
>
> #define GPIO_ABCD_DATA_VALUE 0x000
> #define GPIO_ABCD_DIRECTION 0x004
>
> +static uint32_t qtest_qom_get_uint32(QTestState *s, const char *path,
> + const char *property)
> +{
> + QDict *r;
> +
> + uint32_t res;
> + r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
> + "{ 'path': %s, 'property': %s } }", path, property);
> + res = qdict_get_uint(r, "return");
> + qobject_unref(r);
> +
> + return res;
> +}
> +
> +static void qtest_qom_set_uint32(QTestState *s, const char *path,
> + const char *property, uint32_t value)
> +{
> + QDict *r;
> +
> + r = qtest_qmp(s, "{ 'execute': 'qom-set', 'arguments': "
> + "{ 'path': %s, 'property': %s, 'value': %" PRIu32 " } }",
> + path, property, value);
> + qobject_unref(r);
> +}
> +
> +static const char *resp_get_error(QDict *r, const char* error_key)
> +{
> + QDict *qdict;
> +
> + g_assert(r);
> +
> + qdict = qdict_get_qdict(r, "error");
> + if (qdict) {
> + return qdict_get_str(qdict, error_key);
> + }
> +
> + return NULL;
> +}
> +
> +static bool qtest_qom_check_error(QTestState *s, const char *path,
> + const char *property, const char *error_msg,
> + const char *error_msg_key)
> +{
> + QDict *r;
> + bool b;
> +
> + r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
> + "{ 'path': %s, 'property': %s } }", path, property);
> + b = g_str_equal(resp_get_error(r, error_msg_key), error_msg);
> + qobject_unref(r);
> +
> + return b;
> +}
> +
> static void test_set_colocated_pins(const void *data)
> {
> QTestState *s = (QTestState *)data;
> -
> + const char path[] = "/machine/soc/gpio";
> /*
> * gpioV4-7 occupy bits within a single 32-bit value, so we want to make
> * sure that modifying one doesn't affect the other.
> */
> - qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV4", true);
> - qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV5", false);
> - qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV6", true);
> - qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV7", false);
> - g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV4"));
> - g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV5"));
> - g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV6"));
> - g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV7"));
> + qtest_qom_set_bool(s, path, "gpioV4", true);
> + qtest_qom_set_bool(s, path, "gpioV5", false);
> + qtest_qom_set_bool(s, path, "gpioV6", true);
> + qtest_qom_set_bool(s, path, "gpioV7", false);
> + g_assert(qtest_qom_get_bool(s, path, "gpioV4"));
> + g_assert(!qtest_qom_get_bool(s, path, "gpioV5"));
> + g_assert(qtest_qom_get_bool(s, path, "gpioV6"));
> + g_assert(!qtest_qom_get_bool(s, path, "gpioV7"));
> +
> + /*
> + * Testing the gpio-set[%d] properties, using individual gpio boolean
> + * properties to do cross check.
> + * We use gpioR4-7 for test, Setting them to be 0b1010.
> + */
> + qtest_qom_set_uint32(s, path, "gpio-set[4]", 0x0);
> + g_assert(qtest_qom_get_uint32(s, path, "gpio-set[4]") == 0x0);
> + qtest_qom_set_uint32(s, path, "gpio-set[4]", 0xa000);
> + g_assert(qtest_qom_get_uint32(s, path, "gpio-set[4]") == 0xa000);
> +
> + g_assert(!qtest_qom_get_bool(s, path, "gpioR4"));
> + g_assert(qtest_qom_get_bool(s, path, "gpioR5"));
> + g_assert(!qtest_qom_get_bool(s, path, "gpioR6"));
> + g_assert(qtest_qom_get_bool(s, path, "gpioR7"));
> +
> + /*
> + * Testing the invalid indexing, the response info should contain following
> + * info:
> + * {key: "class", value: "GenericError"}
> + *
> + * For pins, it should follow "gpio%2[A-Z]%1d" or "gpio%3[18A-E]%1d" format.
> + */
> + const char error_msg[] = "GenericError";
> + const char error_msg_key[] = "class";
> +
> + g_assert(qtest_qom_check_error(s, path, "gpioR+1", error_msg,
> + error_msg_key));
> + g_assert(qtest_qom_check_error(s, path, "gpio-set[99]", error_msg,
> + error_msg_key));
> + g_assert(qtest_qom_check_error(s, path, "gpio-set[-3]", error_msg,
> + error_msg_key));
> }
>
> static void test_set_input_pins(const void *data)
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
` (4 preceding siblings ...)
2025-10-15 1:18 ` [PATCH v2 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device " Coco Li
@ 2025-10-17 5:49 ` Cédric Le Goater
5 siblings, 0 replies; 10+ messages in thread
From: Cédric Le Goater @ 2025-10-17 5:49 UTC (permalink / raw)
To: Coco Li, peter.maydell; +Cc: qemu-arm, qemu-devel, flwu, andrew, philmd
On 10/15/25 03:18, Coco Li wrote:
> GPIO series:
> Added 32 bits property for ASPEED GPIO with updated qtests.
>
> SGPIO series:
> Implemented SGPIO device according for npcm8xx.
> Two notable implementations left undone in these patches are:
>
> 1. Reading the data from the host controlled SIOX via register IOXDATR
> 2. On-demand with polling reading node
>
> The reason for this ommitance is that both features are currently unused/umimplemented by the nuvoton driver.
>
> The changes to qobject is used in both sets of patches. The patch series implements indexing gpios with array indices on top of accessing with registers.
> The reasons for this is becasue it creates another easier way to access gpio. In our internal tests, we model complex behaviors with a large number of gpios, such as in fault injection, or in networking behaviors.
> Indexing multiple gpios at once allows qmp/side band client to no longer hardcode and populate register names and manipulate them faster.
>
> Updates since V0: added more descriptions on qobjects change in cover letter.
> Updates since V1:
> - added more description to "hw/gpio: Add property for ASPEED GPIO in 32 bits basis" patch
> - used bitops.h for bit operations in "hw/gpio/npcm8xx: Implement npcm sgpio device input pin"
>
> Coco Li (3):
> hw/arm/npcm8xx.c: Add all IRQ ENUMs
> hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input
> pin logic
> hw/gpio/npcm8xx: Implement npcm sgpio device input pin logic
>
> Felix Wu (2):
> hw/gpio: Add property for ASPEED GPIO in 32 bits basis
> tests/qtest: Add qtest for for ASPEED GPIO gpio-set property
>
> hw/arm/npcm8xx.c | 66 +++-
> hw/gpio/aspeed_gpio.c | 57 ++++
> hw/gpio/meson.build | 1 +
> hw/gpio/npcm8xx_sgpio.c | 518 +++++++++++++++++++++++++++++++
> hw/gpio/trace-events | 4 +
> include/hw/arm/npcm8xx.h | 2 +
> include/hw/gpio/npcm8xx_sgpio.h | 45 +++
> include/qobject/qdict.h | 1 +
> qobject/qdict.c | 13 +
> tests/qtest/aspeed_gpio-test.c | 105 ++++++-
> tests/qtest/meson.build | 3 +-
> tests/qtest/npcm8xx_sgpio-test.c | 222 +++++++++++++
> 12 files changed, 1024 insertions(+), 13 deletions(-)
> create mode 100644 hw/gpio/npcm8xx_sgpio.c
> create mode 100644 include/hw/gpio/npcm8xx_sgpio.h
> create mode 100644 tests/qtest/npcm8xx_sgpio-test.c
>
Applied patches 1,2 to aspeed-next.
Thanks,
C.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-10-17 5:51 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-15 1:18 [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Coco Li
2025-10-15 1:18 ` [PATCH v2 1/5] hw/gpio: Add property for ASPEED GPIO in 32 bits basis Coco Li
2025-10-16 22:56 ` Andrew Jeffery
2025-10-17 5:31 ` Cédric Le Goater
2025-10-15 1:18 ` [PATCH v2 2/5] tests/qtest: Add qtest for for ASPEED GPIO gpio-set property Coco Li
2025-10-17 5:37 ` Cédric Le Goater
2025-10-15 1:18 ` [PATCH v2 3/5] hw/arm/npcm8xx.c: Add all IRQ ENUMs Coco Li
2025-10-15 1:18 ` [PATCH v2 4/5] hw/gpio/npcm8xx: Implement SIOX (SPGIO) device for NPCM without input pin logic Coco Li
2025-10-15 1:18 ` [PATCH v2 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device " Coco Li
2025-10-17 5:49 ` [PATCH v2 0/5] Add Aspeed GPIO test and Support Nuvoton Serial GPIO Expansion (SGPIO) device Cédric Le Goater
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).