[PATCH 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device input pin logic

Coco Li posted 5 patches 2 days, 17 hours ago
Maintainers: Tyrone Ting <kfting@nuvoton.com>, Hao Wu <wuhaotsh@google.com>, Peter Maydell <peter.maydell@linaro.org>, "Cédric Le Goater" <clg@kaod.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>, Markus Armbruster <armbru@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
[PATCH 5/5] hw/gpio/npcm8xx: Implement npcm sgpio device input pin logic
Posted by Coco Li 2 days, 17 hours ago
Signed-off-by: Coco Li <lixiaoyan@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
---
 hw/gpio/npcm8xx_sgpio.c          | 134 ++++++++++++++++++++---
 include/hw/gpio/npcm8xx_sgpio.h  |   4 +-
 tests/qtest/npcm8xx_sgpio-test.c | 180 ++++++++++++++++++++++++++-----
 3 files changed, 274 insertions(+), 44 deletions(-)

diff --git a/hw/gpio/npcm8xx_sgpio.c b/hw/gpio/npcm8xx_sgpio.c
index f7d6bbf672..3b626a44e5 100644
--- a/hw/gpio/npcm8xx_sgpio.c
+++ b/hw/gpio/npcm8xx_sgpio.c
@@ -27,6 +27,8 @@
 #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 +128,83 @@ 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;
 }
 
+static uint8_t get_even_bits(uint16_t n)
+{
+    n &= 0x5555;
+
+    n = (n | (n >> 1)) & 0x3333;
+    n = (n | (n >> 2)) & 0x0F0F;
+    n = (n | (n >> 4)) & 0x00FF;
+
+    return (uint8_t)n;
+}
+
+static uint8_t get_odd_bits(uint16_t n)
+{
+    return get_even_bits(n >> 1);
+}
+
+/*
+ *  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] & get_even_bits(type);
+        /* 1-0 transitions */
+        sts |= (~p[i]) & (d[i] & get_odd_bits(type));
+
+        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 +220,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 +355,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 +430,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 +501,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.338.gd7d06c2dae-goog