The SGPIO controller can generate interrupts based on various pin state
changes, such as rising/falling edges or high/low levels. This change
adds the necessary logic to detect these events, update the interrupt
status registers, and signal the interrupt to the SoC.
Signed-off-by: Yubin Zou <yubinz@google.com>
---
include/hw/gpio/aspeed_sgpio.h | 2 +
hw/gpio/aspeed_sgpio.c | 127 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 127 insertions(+), 2 deletions(-)
diff --git a/include/hw/gpio/aspeed_sgpio.h b/include/hw/gpio/aspeed_sgpio.h
index 60279a597c722f94fba406d60cb30a52ef9544bc..8a11a9998c013cb2e4be99690ecd7bcd9dcb5815 100644
--- a/include/hw/gpio/aspeed_sgpio.h
+++ b/include/hw/gpio/aspeed_sgpio.h
@@ -58,7 +58,9 @@ struct AspeedSGPIOState {
/*< public >*/
MemoryRegion iomem;
+ int pending;
qemu_irq irq;
+ qemu_irq sgpios[ASPEED_SGPIO_MAX_PIN_PAIR];
uint32_t ctrl_regs[ASPEED_SGPIO_MAX_PIN_PAIR];
uint32_t int_regs[ASPEED_SGPIO_MAX_INT];
};
diff --git a/hw/gpio/aspeed_sgpio.c b/hw/gpio/aspeed_sgpio.c
index dc2df137db247c178adc05807bd0595fc0cb5c52..c6e6b3f52a9a982171a03cda820a4573674ab67d 100644
--- a/hw/gpio/aspeed_sgpio.c
+++ b/hw/gpio/aspeed_sgpio.c
@@ -12,9 +12,131 @@
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
+#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "hw/gpio/aspeed_sgpio.h"
+/*
+ * For each set of gpios there are three sensitivity registers that control
+ * the interrupt trigger mode.
+ *
+ * | 2 | 1 | 0 | trigger mode
+ * -----------------------------
+ * | 0 | 0 | 0 | falling-edge
+ * | 0 | 0 | 1 | rising-edge
+ * | 0 | 1 | 0 | level-low
+ * | 0 | 1 | 1 | level-high
+ * | 1 | X | X | dual-edge
+ */
+
+/* GPIO Interrupt Triggers */
+#define ASPEED_FALLING_EDGE 0
+#define ASPEED_RISING_EDGE 1
+#define ASPEED_LEVEL_LOW 2
+#define ASPEED_LEVEL_HIGH 3
+#define ASPEED_DUAL_EDGE 4
+
+static void aspeed_clear_irq(AspeedSGPIOState *s, int idx)
+{
+ uint32_t reg_index = idx / 32;
+ uint32_t bit_index = idx % 32;
+ uint32_t pending = extract32(s->int_regs[reg_index], bit_index, 1);
+
+ assert(s->pending >= pending);
+
+ /* No change to s->pending if pending is 0 */
+ s->pending -= pending;
+
+ /*
+ * The write acknowledged the interrupt regardless of whether it
+ * was pending or not. The post-condition is that it mustn't be
+ * pending. Unconditionally clear the status bit.
+ */
+ s->int_regs[reg_index] = deposit32(s->int_regs[reg_index], bit_index, 1, 0);
+}
+
+static void aspeed_evaluate_irq(AspeedSGPIOState *s, int sgpio_prev_high,
+ int sgpio_curr_high, int idx)
+{
+ uint32_t ctrl = s->ctrl_regs[idx];
+ uint32_t falling_edge = 0, rising_edge = 0;
+ uint32_t int_trigger = SHARED_FIELD_EX32(ctrl, SGPIO_INT_TYPE);
+ uint32_t int_enabled = SHARED_FIELD_EX32(ctrl, SGPIO_INT_EN);
+ uint32_t reg_index = idx / 32;
+ uint32_t bit_index = idx % 32;
+
+ if (!int_enabled) {
+ return;
+ }
+
+ /* Detect edges */
+ if (sgpio_curr_high && !sgpio_prev_high) {
+ rising_edge = 1;
+ } else if (!sgpio_curr_high && sgpio_prev_high) {
+ falling_edge = 1;
+ }
+
+ if (((int_trigger == ASPEED_FALLING_EDGE) && falling_edge) ||
+ ((int_trigger == ASPEED_RISING_EDGE) && rising_edge) ||
+ ((int_trigger == ASPEED_LEVEL_LOW) && !sgpio_curr_high) ||
+ ((int_trigger == ASPEED_LEVEL_HIGH) && sgpio_curr_high) ||
+ ((int_trigger >= ASPEED_DUAL_EDGE) && (rising_edge || falling_edge)))
+ {
+ s->int_regs[reg_index] = deposit32(s->int_regs[reg_index],
+ bit_index, 1, 1);
+ /* Trigger the VIC IRQ */
+ s->pending++;
+ }
+}
+
+static void aspeed_sgpio_update(AspeedSGPIOState *s, uint32_t idx,
+ uint32_t value)
+{
+ uint32_t old = s->ctrl_regs[idx];
+ uint32_t new = value;
+ uint32_t diff = (old ^ new);
+ if (diff) {
+ /* If the interrupt clear bit is set */
+ if (SHARED_FIELD_EX32(new, SGPIO_INT_STATUS)) {
+ aspeed_clear_irq(s, idx);
+ /* Clear the interrupt clear bit */
+ new &= ~SGPIO_INT_STATUS_MASK;
+ }
+
+ /* Uppdate the control register. */
+ s->ctrl_regs[idx] = new;
+
+ /* If the output value is changed */
+ if (SHARED_FIELD_EX32(diff, SGPIO_SERIAL_OUT_VAL)) {
+ /* ...trigger the line-state IRQ */
+ qemu_set_irq(s->sgpios[idx], 1);
+ }
+
+ /* If the input value is changed */
+ if (SHARED_FIELD_EX32(diff, SGPIO_SERIAL_IN_VAL)) {
+ aspeed_evaluate_irq(s,
+ SHARED_FIELD_EX32(old, SGPIO_SERIAL_IN_VAL),
+ SHARED_FIELD_EX32(new, SGPIO_SERIAL_IN_VAL),
+ idx);
+ }
+ }
+ qemu_set_irq(s->irq, !!(s->pending));
+}
+
+static uint64_t aspeed_sgpio_2700_read_int_status_reg(AspeedSGPIOState *s,
+ uint32_t reg)
+{
+ uint32_t idx = reg - R_SGPIO_INT_STATUS_0;
+ if (idx >= ASPEED_SGPIO_MAX_INT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: interrupt status index: %d, out of bounds\n",
+ __func__, idx);
+ return 0;
+ }
+ return s->int_regs[idx];
+}
+
+
static uint64_t aspeed_sgpio_2700_read_control_reg(AspeedSGPIOState *s,
uint32_t reg)
{
@@ -38,7 +160,7 @@ static void aspeed_sgpio_2700_write_control_reg(AspeedSGPIOState *s,
__func__, idx);
return;
}
- s->ctrl_regs[idx] = data;
+ aspeed_sgpio_update(s, idx, data);
}
static uint64_t aspeed_sgpio_2700_read(void *opaque, hwaddr offset,
@@ -52,6 +174,7 @@ static uint64_t aspeed_sgpio_2700_read(void *opaque, hwaddr offset,
switch (reg) {
case R_SGPIO_INT_STATUS_0 ... R_SGPIO_INT_STATUS_7:
+ value = aspeed_sgpio_2700_read_int_status_reg(s, reg);
break;
case R_SGPIO_0_CONTROL ... R_SGPIO_255_CONTROL:
value = aspeed_sgpio_2700_read_control_reg(s, reg);
@@ -116,7 +239,7 @@ static void aspeed_sgpio_set_pin_level(AspeedSGPIOState *s, int pin, bool level)
} else {
value &= ~bit_mask;
}
- s->ctrl_regs[pin >> 1] = value;
+ aspeed_sgpio_update(s, pin >> 1, value);
}
static void aspeed_sgpio_get_pin(Object *obj, Visitor *v, const char *name,
--
2.52.0.305.g3fc767764a-goog
> -----Original Message-----
> From: Yubin Zou <yubinz@google.com>
> Sent: Wednesday, December 17, 2025 8:22 AM
> To: qemu-devel@nongnu.org
> Cc: Cédric Le Goater <clg@kaod.org>; Peter Maydell
> <peter.maydell@linaro.org>; Steven Lee <steven_lee@aspeedtech.com>; Troy
> Lee <leetroy@gmail.com>; Jamin Lin <jamin_lin@aspeedtech.com>; Andrew
> Jeffery <andrew@codeconstruct.com.au>; Joel Stanley <joel@jms.id.au>;
> Fabiano Rosas <farosas@suse.de>; Laurent Vivier <lvivier@redhat.com>;
> Paolo Bonzini <pbonzini@redhat.com>; Kane Chen
> <kane_chen@aspeedtech.com>; Nabih Estefan <nabihestefan@google.com>;
> qemu-arm@nongnu.org; Yubin Zou <yubinz@google.com>
> Subject: [PATCH v4 3/6] hw/gpio/aspeed_sgpio: Implement SGPIO interrupt
> handling
>
> The SGPIO controller can generate interrupts based on various pin state
> changes, such as rising/falling edges or high/low levels. This change adds the
> necessary logic to detect these events, update the interrupt status registers,
> and signal the interrupt to the SoC.
>
> Signed-off-by: Yubin Zou <yubinz@google.com>
> ---
> include/hw/gpio/aspeed_sgpio.h | 2 +
> hw/gpio/aspeed_sgpio.c | 127
> ++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 127 insertions(+), 2 deletions(-)
>
> diff --git a/include/hw/gpio/aspeed_sgpio.h
> b/include/hw/gpio/aspeed_sgpio.h index
> 60279a597c722f94fba406d60cb30a52ef9544bc..8a11a9998c013cb2e4be996
> 90ecd7bcd9dcb5815 100644
> --- a/include/hw/gpio/aspeed_sgpio.h
> +++ b/include/hw/gpio/aspeed_sgpio.h
> @@ -58,7 +58,9 @@ struct AspeedSGPIOState {
>
> /*< public >*/
> MemoryRegion iomem;
> + int pending;
> qemu_irq irq;
> + qemu_irq sgpios[ASPEED_SGPIO_MAX_PIN_PAIR];
> uint32_t ctrl_regs[ASPEED_SGPIO_MAX_PIN_PAIR];
> uint32_t int_regs[ASPEED_SGPIO_MAX_INT]; }; diff --git
> a/hw/gpio/aspeed_sgpio.c b/hw/gpio/aspeed_sgpio.c index
> dc2df137db247c178adc05807bd0595fc0cb5c52..c6e6b3f52a9a982171a03cda
> 820a4573674ab67d 100644
> --- a/hw/gpio/aspeed_sgpio.c
> +++ b/hw/gpio/aspeed_sgpio.c
> @@ -12,9 +12,131 @@
> #include "qemu/error-report.h"
> #include "qapi/error.h"
> #include "qapi/visitor.h"
> +#include "hw/irq.h"
> #include "hw/qdev-properties.h"
> #include "hw/gpio/aspeed_sgpio.h"
>
> +/*
> + * For each set of gpios there are three sensitivity registers that
> +control
> + * the interrupt trigger mode.
> + *
> + * | 2 | 1 | 0 | trigger mode
> + * -----------------------------
> + * | 0 | 0 | 0 | falling-edge
> + * | 0 | 0 | 1 | rising-edge
> + * | 0 | 1 | 0 | level-low
> + * | 0 | 1 | 1 | level-high
> + * | 1 | X | X | dual-edge
> + */
> +
> +/* GPIO Interrupt Triggers */
> +#define ASPEED_FALLING_EDGE 0
> +#define ASPEED_RISING_EDGE 1
> +#define ASPEED_LEVEL_LOW 2
> +#define ASPEED_LEVEL_HIGH 3
> +#define ASPEED_DUAL_EDGE 4
> +
> +static void aspeed_clear_irq(AspeedSGPIOState *s, int idx) {
> + uint32_t reg_index = idx / 32;
> + uint32_t bit_index = idx % 32;
> + uint32_t pending = extract32(s->int_regs[reg_index], bit_index, 1);
> +
> + assert(s->pending >= pending);
> +
> + /* No change to s->pending if pending is 0 */
> + s->pending -= pending;
> +
> + /*
> + * The write acknowledged the interrupt regardless of whether it
> + * was pending or not. The post-condition is that it mustn't be
> + * pending. Unconditionally clear the status bit.
> + */
> + s->int_regs[reg_index] = deposit32(s->int_regs[reg_index],
> +bit_index, 1, 0); }
> +
> +static void aspeed_evaluate_irq(AspeedSGPIOState *s, int sgpio_prev_high,
> + int sgpio_curr_high, int idx) {
> + uint32_t ctrl = s->ctrl_regs[idx];
> + uint32_t falling_edge = 0, rising_edge = 0;
> + uint32_t int_trigger = SHARED_FIELD_EX32(ctrl, SGPIO_INT_TYPE);
> + uint32_t int_enabled = SHARED_FIELD_EX32(ctrl, SGPIO_INT_EN);
> + uint32_t reg_index = idx / 32;
> + uint32_t bit_index = idx % 32;
> +
> + if (!int_enabled) {
> + return;
> + }
> +
> + /* Detect edges */
> + if (sgpio_curr_high && !sgpio_prev_high) {
> + rising_edge = 1;
> + } else if (!sgpio_curr_high && sgpio_prev_high) {
> + falling_edge = 1;
> + }
> +
> + if (((int_trigger == ASPEED_FALLING_EDGE) && falling_edge) ||
> + ((int_trigger == ASPEED_RISING_EDGE) && rising_edge) ||
> + ((int_trigger == ASPEED_LEVEL_LOW) && !sgpio_curr_high) ||
> + ((int_trigger == ASPEED_LEVEL_HIGH) && sgpio_curr_high) ||
> + ((int_trigger >= ASPEED_DUAL_EDGE) && (rising_edge ||
> falling_edge)))
> + {
> + s->int_regs[reg_index] = deposit32(s->int_regs[reg_index],
> + bit_index, 1, 1);
> + /* Trigger the VIC IRQ */
> + s->pending++;
> + }
> +}
> +
> +static void aspeed_sgpio_update(AspeedSGPIOState *s, uint32_t idx,
> + uint32_t value) {
> + uint32_t old = s->ctrl_regs[idx];
> + uint32_t new = value;
> + uint32_t diff = (old ^ new);
> + if (diff) {
> + /* If the interrupt clear bit is set */
> + if (SHARED_FIELD_EX32(new, SGPIO_INT_STATUS)) {
> + aspeed_clear_irq(s, idx);
> + /* Clear the interrupt clear bit */
> + new &= ~SGPIO_INT_STATUS_MASK;
> + }
> +
> + /* Uppdate the control register. */
There is a small typo here ("Uppdate"). Please correct it.
> + s->ctrl_regs[idx] = new;
> +
> + /* If the output value is changed */
> + if (SHARED_FIELD_EX32(diff, SGPIO_SERIAL_OUT_VAL)) {
> + /* ...trigger the line-state IRQ */
> + qemu_set_irq(s->sgpios[idx], 1);
> + }
> +
> + /* If the input value is changed */
> + if (SHARED_FIELD_EX32(diff, SGPIO_SERIAL_IN_VAL)) {
> + aspeed_evaluate_irq(s,
> + SHARED_FIELD_EX32(old,
> SGPIO_SERIAL_IN_VAL),
> + SHARED_FIELD_EX32(new,
> SGPIO_SERIAL_IN_VAL),
> + idx);
> + }
> + }
> + qemu_set_irq(s->irq, !!(s->pending)); }
> +
> +static uint64_t aspeed_sgpio_2700_read_int_status_reg(AspeedSGPIOState
> *s,
> + uint32_t
> reg) {
> + uint32_t idx = reg - R_SGPIO_INT_STATUS_0;
> + if (idx >= ASPEED_SGPIO_MAX_INT) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: interrupt status index: %d, out of
> bounds\n",
> + __func__, idx);
> + return 0;
> + }
> + return s->int_regs[idx];
> +}
> +
> +
There seems to be an extra empty line here. It can be removed.
> static uint64_t aspeed_sgpio_2700_read_control_reg(AspeedSGPIOState *s,
> uint32_t reg) { @@ -38,7 +160,7
> @@ static void aspeed_sgpio_2700_write_control_reg(AspeedSGPIOState *s,
> __func__, idx);
> return;
> }
> - s->ctrl_regs[idx] = data;
> + aspeed_sgpio_update(s, idx, data);
> }
>
> static uint64_t aspeed_sgpio_2700_read(void *opaque, hwaddr offset, @@
> -52,6 +174,7 @@ static uint64_t aspeed_sgpio_2700_read(void *opaque,
> hwaddr offset,
>
> switch (reg) {
> case R_SGPIO_INT_STATUS_0 ... R_SGPIO_INT_STATUS_7:
> + value = aspeed_sgpio_2700_read_int_status_reg(s, reg);
> break;
> case R_SGPIO_0_CONTROL ... R_SGPIO_255_CONTROL:
> value = aspeed_sgpio_2700_read_control_reg(s, reg); @@ -116,7
> +239,7 @@ static void aspeed_sgpio_set_pin_level(AspeedSGPIOState *s, int
> pin, bool level)
> } else {
> value &= ~bit_mask;
> }
> - s->ctrl_regs[pin >> 1] = value;
> + aspeed_sgpio_update(s, pin >> 1, value);
> }
>
> static void aspeed_sgpio_get_pin(Object *obj, Visitor *v, const char *name,
>
> --
> 2.52.0.305.g3fc767764a-goog
Best Regards,
Kane
© 2016 - 2026 Red Hat, Inc.