This commit establish the interrupt logic of Aspeed Serial gpio to
handle four types interrupt trigger, level high, level low, rising edge,
failing edge
Signed-off-by: Yubin Zou <yubinz@google.com>
---
hw/gpio/aspeed_sgpio.c | 171 +++++++++++++++++++++++++++++----------
include/hw/gpio/aspeed_sgpio.h | 30 +++++++
tests/qtest/ast2700-sgpio-test.c | 72 +++++++++++++++--
3 files changed, 222 insertions(+), 51 deletions(-)
diff --git a/hw/gpio/aspeed_sgpio.c b/hw/gpio/aspeed_sgpio.c
index a6f0f32d72aef3c26a143df4e7a49384aa216643..c9aa8e4818673150c4174dc18fe76ab9cbaf4993 100644
--- a/hw/gpio/aspeed_sgpio.c
+++ b/hw/gpio/aspeed_sgpio.c
@@ -12,70 +12,156 @@
#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"
#include "hw/registerfields.h"
-/* AST2700 SGPIO Register Address Offsets */
-REG32(SGPIO_INT_STATUS_0, 0x40)
-REG32(SGPIO_INT_STATUS_1, 0x44)
-REG32(SGPIO_INT_STATUS_2, 0x48)
-REG32(SGPIO_INT_STATUS_3, 0x4C)
-REG32(SGPIO_INT_STATUS_4, 0x50)
-REG32(SGPIO_INT_STATUS_5, 0x54)
-REG32(SGPIO_INT_STATUS_6, 0x58)
-REG32(SGPIO_INT_STATUS_7, 0x5C)
-/* AST2700 SGPIO_0 - SGPIO_255 Control Register */
-REG32(SGPIO_0_CONTROL, 0x80)
- SHARED_FIELD(SGPIO_SERIAL_OUT_VAL, 0, 1)
- SHARED_FIELD(SGPIO_PARALLEL_OUT_VAL, 1, 1)
- SHARED_FIELD(SGPIO_INT_EN, 2, 1)
- SHARED_FIELD(SGPIO_INT_TYPE0, 3, 1)
- SHARED_FIELD(SGPIO_INT_TYPE1, 4, 1)
- SHARED_FIELD(SGPIO_INT_TYPE2, 5, 1)
- SHARED_FIELD(SGPIO_RESET_POLARITY, 6, 1)
- SHARED_FIELD(SGPIO_RESERVED_1, 7, 2)
- SHARED_FIELD(SGPIO_INPUT_MASK, 9, 1)
- SHARED_FIELD(SGPIO_PARALLEL_EN, 10, 1)
- SHARED_FIELD(SGPIO_PARALLEL_IN_MODE, 11, 1)
- SHARED_FIELD(SGPIO_INTERRUPT_STATUS, 12, 1)
- SHARED_FIELD(SGPIO_SERIAL_IN_VAL, 13, 1)
- SHARED_FIELD(SGPIO_PARALLEL_IN_VAL, 14, 1)
- SHARED_FIELD(SGPIO_RESERVED_2, 15, 12)
- SHARED_FIELD(SGPIO_WRITE_PROTECT, 31, 1)
-REG32(SGPIO_255_CONTROL, 0x47C)
+/*
+ * 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 reg)
{
- /* TODO: b/430606659 - Implement aspeed_sgpio_2700_read_int_status_reg */
- return 0;
+ 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)
{
AspeedSGPIOClass *agc = ASPEED_SGPIO_GET_CLASS(s);
- uint32_t pin = reg - R_SGPIO_0_CONTROL;
- if (pin >= agc->nr_sgpio_pin_pairs) {
+ uint32_t idx = reg - R_SGPIO_0_CONTROL;
+ if (idx >= agc->nr_sgpio_pin_pairs) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: pin index: %d, out of bounds\n",
- __func__, pin);
+ __func__, idx);
return 0;
}
- return s->ctrl_regs[pin];
+ return s->ctrl_regs[idx];
}
static void aspeed_sgpio_2700_write_control_reg(AspeedSGPIOState *s,
uint32_t reg, uint64_t data)
{
AspeedSGPIOClass *agc = ASPEED_SGPIO_GET_CLASS(s);
- uint32_t pin = reg - R_SGPIO_0_CONTROL;
- if (pin >= agc->nr_sgpio_pin_pairs) {
+ uint32_t idx = reg - R_SGPIO_0_CONTROL;
+ if (idx >= agc->nr_sgpio_pin_pairs) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: pin index: %d, out of bounds\n",
- __func__, pin);
+ __func__, idx);
return;
}
- s->ctrl_regs[pin] = data;
+ aspeed_sgpio_update(s, idx, data);
}
static uint64_t aspeed_sgpio_2700_read(void *opaque, hwaddr offset,
@@ -89,7 +175,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:
- aspeed_sgpio_2700_read_int_status_reg(s, reg);
+ 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);
@@ -112,8 +198,6 @@ static void aspeed_sgpio_2700_write(void *opaque, hwaddr offset, uint64_t data,
reg = offset >> 2;
switch (reg) {
- case R_SGPIO_INT_STATUS_0 ... R_SGPIO_INT_STATUS_7:
- break;
case R_SGPIO_0_CONTROL ... R_SGPIO_255_CONTROL:
aspeed_sgpio_2700_write_control_reg(s, reg, data);
break;
@@ -156,8 +240,7 @@ static void aspeed_sgpio_set_pin_level(AspeedSGPIOState *s, int pin, bool level)
} else {
value &= ~bit_mask;
}
- s->ctrl_regs[pin >> 1] = value;
- /* TODO: b/430606659 - Implement the SGPIO Interrupt */
+ aspeed_sgpio_update(s, pin >> 1, value);
}
static void aspeed_sgpio_get_pin(Object *obj, Visitor *v, const char *name,
diff --git a/include/hw/gpio/aspeed_sgpio.h b/include/hw/gpio/aspeed_sgpio.h
index a7d4868eeeb2f2caedcdff9858a6047ce5a1fcd8..5dddab80848937417b5f99f1b52b44f62893bda9 100644
--- a/include/hw/gpio/aspeed_sgpio.h
+++ b/include/hw/gpio/aspeed_sgpio.h
@@ -10,6 +10,7 @@
#include "hw/sysbus.h"
#include "qom/object.h"
+#include "hw/registerfields.h"
#define TYPE_ASPEED_SGPIO "aspeed.sgpio"
OBJECT_DECLARE_TYPE(AspeedSGPIOState, AspeedSGPIOClass, ASPEED_SGPIO)
@@ -17,6 +18,33 @@ OBJECT_DECLARE_TYPE(AspeedSGPIOState, AspeedSGPIOClass, ASPEED_SGPIO)
#define ASPEED_SGPIO_MAX_PIN_PAIR 256
#define ASPEED_SGPIO_MAX_INT 8
+/* AST2700 SGPIO Register Address Offsets */
+REG32(SGPIO_INT_STATUS_0, 0x40)
+REG32(SGPIO_INT_STATUS_1, 0x44)
+REG32(SGPIO_INT_STATUS_2, 0x48)
+REG32(SGPIO_INT_STATUS_3, 0x4C)
+REG32(SGPIO_INT_STATUS_4, 0x50)
+REG32(SGPIO_INT_STATUS_5, 0x54)
+REG32(SGPIO_INT_STATUS_6, 0x58)
+REG32(SGPIO_INT_STATUS_7, 0x5C)
+/* AST2700 SGPIO_0 - SGPIO_255 Control Register */
+REG32(SGPIO_0_CONTROL, 0x80)
+ SHARED_FIELD(SGPIO_SERIAL_OUT_VAL, 0, 1)
+ SHARED_FIELD(SGPIO_PARALLEL_OUT_VAL, 1, 1)
+ SHARED_FIELD(SGPIO_INT_EN, 2, 1)
+ SHARED_FIELD(SGPIO_INT_TYPE, 3, 3)
+ SHARED_FIELD(SGPIO_RESET_POLARITY, 6, 1)
+ SHARED_FIELD(SGPIO_RESERVED_1, 7, 2)
+ SHARED_FIELD(SGPIO_INPUT_MASK, 9, 1)
+ SHARED_FIELD(SGPIO_PARALLEL_EN, 10, 1)
+ SHARED_FIELD(SGPIO_PARALLEL_IN_MODE, 11, 1)
+ SHARED_FIELD(SGPIO_INT_STATUS, 12, 1)
+ SHARED_FIELD(SGPIO_SERIAL_IN_VAL, 13, 1)
+ SHARED_FIELD(SGPIO_PARALLEL_IN_VAL, 14, 1)
+ SHARED_FIELD(SGPIO_RESERVED_2, 15, 12)
+ SHARED_FIELD(SGPIO_WRITE_PROTECT, 31, 1)
+REG32(SGPIO_255_CONTROL, 0x47C)
+
struct AspeedSGPIOClass {
SysBusDevice parent_obj;
uint32_t nr_sgpio_pin_pairs;
@@ -30,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/tests/qtest/ast2700-sgpio-test.c b/tests/qtest/ast2700-sgpio-test.c
index a24fa29250d0c47b1ca7564280055de2c53f525b..fc52839c4b149a3010c6a035d8b29f9ad295930a 100644
--- a/tests/qtest/ast2700-sgpio-test.c
+++ b/tests/qtest/ast2700-sgpio-test.c
@@ -12,11 +12,12 @@
#include "qobject/qdict.h"
#include "libqtest-single.h"
#include "qemu/error-report.h"
+#include "hw/registerfields.h"
+#include "hw/gpio/aspeed_sgpio.h"
#define ASPEED_SGPIO_MAX_PIN_PAIR 256
#define AST2700_SGPIO0_BASE 0x14C0C000
#define AST2700_SGPIO1_BASE 0x14C0D000
-#define SGPIO_0_CONTROL 0x80
static void test_output_pins(const char *machine, const uint32_t base, int idx)
{
@@ -29,17 +30,17 @@ static void test_output_pins(const char *machine, const uint32_t base, int idx)
/* Odd index is output port */
sprintf(name, "sgpio%d", i * 2 + 1);
sprintf(qom_path, "/machine/soc/sgpio[%d]", idx);
- offset = base + SGPIO_0_CONTROL + (i * 4);
+ offset = base + (R_SGPIO_0_CONTROL + i) * 4;
/* set serial output */
qtest_writel(s, offset, 0x00000001);
value = qtest_readl(s, offset);
- g_assert_cmphex(value, ==, 0x00000001);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_OUT_VAL), ==, 1);
g_assert_cmphex(qtest_qom_get_bool(s, qom_path, name), ==, true);
/* clear serial output */
qtest_writel(s, offset, 0x00000000);
value = qtest_readl(s, offset);
- g_assert_cmphex(value, ==, 0);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_OUT_VAL), ==, 0);
g_assert_cmphex(qtest_qom_get_bool(s, qom_path, name), ==, false);
}
qtest_quit(s);
@@ -56,22 +57,75 @@ static void test_input_pins(const char *machine, const uint32_t base, int idx)
/* Even index is input port */
sprintf(name, "sgpio%d", i * 2);
sprintf(qom_path, "/machine/soc/sgpio[%d]", idx);
- offset = base + SGPIO_0_CONTROL + (i * 4);
+ offset = base + (R_SGPIO_0_CONTROL + i) * 4;
/* set serial input */
qtest_qom_set_bool(s, qom_path, name, true);
value = qtest_readl(s, offset);
- g_assert_cmphex(value, ==, 0x00002000);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_IN_VAL), ==, 1);
g_assert_cmphex(qtest_qom_get_bool(s, qom_path, name), ==, true);
/* clear serial input */
qtest_qom_set_bool(s, qom_path, name, false);
value = qtest_readl(s, offset);
- g_assert_cmphex(value, ==, 0);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_IN_VAL), ==, 0);
g_assert_cmphex(qtest_qom_get_bool(s, qom_path, name), ==, false);
}
qtest_quit(s);
}
+static void test_irq_level_high(const char *machine,
+ const uint32_t base, int idx)
+{
+ QTestState *s = qtest_init(machine);
+ char name[16];
+ char qom_path[64];
+ uint32_t ctrl_offset = 0;
+ uint32_t int_offset = 0;
+ uint32_t int_reg_idx = 0;
+ uint32_t int_bit_idx = 0;
+ uint32_t value = 0;
+ for (int i = 0; i < ASPEED_SGPIO_MAX_PIN_PAIR; i++) {
+ /* Even index is input port */
+ sprintf(name, "sgpio%d", i * 2);
+ sprintf(qom_path, "/machine/soc/sgpio[%d]", idx);
+ int_reg_idx = i / 32;
+ int_bit_idx = i % 32;
+ int_offset = base + (R_SGPIO_INT_STATUS_0 + int_reg_idx) * 4;
+ ctrl_offset = base + (R_SGPIO_0_CONTROL + i) * 4;
+
+ /* Enable the interrupt */
+ value = SHARED_FIELD_DP32(value, SGPIO_INT_EN, 1);
+ qtest_writel(s, ctrl_offset, value);
+
+ /* Set the interrupt type to level-high trigger */
+ value = SHARED_FIELD_DP32(qtest_readl(s, ctrl_offset),
+ SGPIO_INT_TYPE, 3);
+ qtest_writel(s, ctrl_offset, value);
+
+ /* Set serial input high */
+ qtest_qom_set_bool(s, qom_path, name, true);
+ value = qtest_readl(s, ctrl_offset);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_IN_VAL), ==, 1);
+
+ /* Interrupt status is set */
+ value = qtest_readl(s, int_offset);
+ g_assert_cmphex(extract32(value, int_bit_idx, 1), ==, 1);
+
+ /* Clear Interrupt */
+ value = SHARED_FIELD_DP32(qtest_readl(s, ctrl_offset),
+ SGPIO_INT_STATUS, 1);
+ qtest_writel(s, ctrl_offset, value);
+ value = qtest_readl(s, int_offset);
+ g_assert_cmphex(extract32(value, int_bit_idx, 1), ==, 0);
+
+ /* Clear serial input */
+ qtest_qom_set_bool(s, qom_path, name, false);
+ value = qtest_readl(s, ctrl_offset);
+ g_assert_cmphex(SHARED_FIELD_EX32(value, SGPIO_SERIAL_IN_VAL), ==, 0);
+ }
+ qtest_quit(s);
+}
+
static void test_2700_input_pins(void)
{
test_input_pins("-machine ast2700-evb",
@@ -82,6 +136,10 @@ static void test_2700_input_pins(void)
AST2700_SGPIO0_BASE, 0);
test_output_pins("-machine ast2700-evb",
AST2700_SGPIO1_BASE, 1);
+ test_irq_level_high("-machine ast2700-evb",
+ AST2700_SGPIO0_BASE, 0);
+ test_irq_level_high("-machine ast2700-evb",
+ AST2700_SGPIO1_BASE, 1);
}
int main(int argc, char **argv)
--
2.51.2.1041.gc1ab5b90ca-goog
© 2016 - 2025 Red Hat, Inc.