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
© 2016 - 2025 Red Hat, Inc.