All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral
@ 2018-07-16 10:47 Steffen Görtz
  2018-07-16 10:47 ` [Qemu-devel] [RFC v2 2/2] arm: Add nRF51 GPIO tests Steffen Görtz
  2018-07-27 12:41 ` [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Stefan Hajnoczi
  0 siblings, 2 replies; 3+ messages in thread
From: Steffen Görtz @ 2018-07-16 10:47 UTC (permalink / raw)
  To: qemu-devel
  Cc: Stefan Hajnoczi, Joel Stanley, Jim Mussared, Julia Suvorova,
	Peter Maydell, Steffen Görtz

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
 Changes in v2:
   - Only call QEMU GPIO update handlers if value changes
   - Code style changes
   - Removed unused includes

 hw/gpio/Makefile.objs        |   1 +
 hw/gpio/nrf51_gpio.c         | 320 +++++++++++++++++++++++++++++++++++
 include/hw/gpio/nrf51_gpio.h |  57 +++++++
 3 files changed, 378 insertions(+)
 create mode 100644 hw/gpio/nrf51_gpio.c
 create mode 100644 include/hw/gpio/nrf51_gpio.h

diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs
index fa0a72e6d0..e5da0cb54f 100644
--- a/hw/gpio/Makefile.objs
+++ b/hw/gpio/Makefile.objs
@@ -8,3 +8,4 @@ common-obj-$(CONFIG_GPIO_KEY) += gpio_key.o
 obj-$(CONFIG_OMAP) += omap_gpio.o
 obj-$(CONFIG_IMX) += imx_gpio.o
 obj-$(CONFIG_RASPI) += bcm2835_gpio.o
+obj-$(CONFIG_NRF51_SOC) += nrf51_gpio.o
diff --git a/hw/gpio/nrf51_gpio.c b/hw/gpio/nrf51_gpio.c
new file mode 100644
index 0000000000..033d013882
--- /dev/null
+++ b/hw/gpio/nrf51_gpio.c
@@ -0,0 +1,320 @@
+/*
+ * nRF51 System-on-Chip general purpose input/output register definition
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ * Product Spec: http://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.1.pdf
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/gpio/nrf51_gpio.h"
+
+#define NRF51_GPIO_SIZE 0x1000
+
+#define NRF51_GPIO_REG_OUT          0x504
+#define NRF51_GPIO_REG_OUTSET       0x508
+#define NRF51_GPIO_REG_OUTCLR       0x50C
+#define NRF51_GPIO_REG_IN           0x510
+#define NRF51_GPIO_REG_DIR          0x514
+#define NRF51_GPIO_REG_DIRSET       0x518
+#define NRF51_GPIO_REG_DIRCLR       0x51C
+#define NRF51_GPIO_REG_CNF_START    0x700
+#define NRF51_GPIO_REG_CNF_END      0x77F
+
+#define GPIO_PULLDOWN 1
+#define GPIO_PULLUP 3
+
+#ifndef DEBUG_NRF51_GPIO
+#define DEBUG_NRF51_GPIO 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+    do { \
+        if (DEBUG_NRF51_GPIO) { \
+            fprintf(stderr, "[%s]%s: " fmt , TYPE_NRF51_GPIO, \
+                                             __func__, ##args); \
+        } \
+    } while (0)
+
+static uint64_t gpio_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    Nrf51GPIOState *s = NRF51_GPIO(opaque);
+    uint64_t r = 0;
+    size_t idx;
+
+    switch (offset) {
+    case NRF51_GPIO_REG_OUT ... NRF51_GPIO_REG_OUTCLR:
+        DPRINTF("read out\n");
+        r = s->out;
+        break;
+    case NRF51_GPIO_REG_IN:
+        DPRINTF("read in\n");
+        r = s->in;
+        break;
+    case NRF51_GPIO_REG_DIR ... NRF51_GPIO_REG_DIRCLR:
+        DPRINTF("read dir\n");
+        r = s->dir;
+        break;
+    case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END:
+        idx = (offset - NRF51_GPIO_REG_CNF_START) / 4;
+        DPRINTF("read config %d\n", (uint32_t)idx);
+        r = s->cnf[idx];
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+
+    return r;
+}
+
+/**
+ * Check if the output driver is connected to the direction switch
+ * given the current configuration and logic level.
+ * It is not differentiated between standard and "high"(-power) drive modes.
+ */
+static bool is_connected(uint32_t config, uint32_t level)
+{
+    bool state;
+    uint32_t drive_config = extract32(config, 8, 3);
+
+    switch (drive_config) {
+    case 0 ... 3:
+        state = true;
+        break;
+    case 4 ... 5:
+        state = level != 0;
+        break;
+    case 6 ... 7:
+        state = level == 0;
+        break;
+    }
+
+    return state;
+}
+
+static void update_output_irq(Nrf51GPIOState *s, size_t i,
+                              bool connected, bool level)
+{
+    bool old_connected = extract32(s->old_out_connected, i, 1);
+    bool old_level = extract32(s->old_out, i, 1);
+
+    if ((old_connected != connected) || (old_level != level)) {
+        if (connected) {
+            DPRINTF("qemu output to %s\n", level ? "HIGH" : "LOW");
+            qemu_set_irq(s->output[i], level);
+        } else {
+            DPRINTF("qemu output to %s\n", "DISCONNECTED");
+            qemu_set_irq(s->output[i], -1);
+        }
+    }
+
+    s->old_out = deposit32(s->old_out, i, 1, level);
+    s->old_out_connected = deposit32(s->old_out_connected, i, 1, connected);
+}
+
+static void gpio_update_state(Nrf51GPIOState *s)
+{
+    uint32_t pull;
+    bool connected_out, dir, connected_in, out, input;
+
+    for (size_t i = 0; i < NRF51_GPIO_PINS; i++) {
+        DPRINTF("=== calc update for state %zu ===\n", i);
+
+        pull = extract32(s->cnf[i], 2, 2);
+        dir = extract32(s->cnf[i], 0, 1);
+        connected_in = extract32(s->in_mask, i, 1);
+        out = extract32(s->out, i, 1);
+        input = !extract32(s->cnf[i], 1, 1);
+        connected_out = is_connected(s->cnf[i], out) && dir;
+
+        DPRINTF("[CON_OUT, OUT, CON_IN, INPUT] = [%s, %s, %s, %s]\n",
+            connected_out ? "true" : "false",
+            out ? "HIGH" : "LOW",
+            connected_in ? "true" : "false",
+            input ? "HIGH" : "LOW"
+        );
+
+        update_output_irq(s, i, connected_out, out);
+
+        /** Pin both driven externally and internally */
+        if (connected_out && connected_in) {
+            qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i);
+        }
+
+        /**
+         * Input buffer disconnected from internal/external drives, so
+         * pull-up/pull-down becomes relevant
+         */
+        if (!input || (input && !connected_in && !connected_out)) {
+            if (pull == GPIO_PULLDOWN) {
+                DPRINTF("pulled-down\n");
+                s->in = deposit32(s->in, i, 1, 0);
+            } else if (pull == GPIO_PULLUP) {
+                DPRINTF("pulled-up\n");
+                s->in = deposit32(s->in, i, 1, 1);
+            }
+        }
+
+        /** Self stimulation through internal output driver **/
+        if (connected_out && !connected_in && input) {
+            DPRINTF("self stimulated %s\n", out ? "HIGH" : "LOW");
+            s->in = deposit32(s->in, i, 1, out);
+        }
+    }
+
+}
+
+static void gpio_write(void *opaque, hwaddr offset,
+                       uint64_t value, unsigned int size)
+{
+    Nrf51GPIOState *s = NRF51_GPIO(opaque);
+    size_t idx;
+
+    switch (offset) {
+    case NRF51_GPIO_REG_OUT:
+        DPRINTF("write out=0x%" PRIx32 "\n", (uint32_t)value);
+        s->out = value;
+        gpio_update_state(s);
+        break;
+    case NRF51_GPIO_REG_OUTSET:
+        DPRINTF("set out=0x%" PRIx32 "\n", (uint32_t)value);
+        s->out |= value;
+        gpio_update_state(s);
+        break;
+    case NRF51_GPIO_REG_OUTCLR:
+        DPRINTF("clr out=0x%" PRIx32 "\n", (uint32_t)value);
+        s->out &= ~value;
+        gpio_update_state(s);
+        break;
+    case NRF51_GPIO_REG_DIR:
+        DPRINTF("write dir=0x%" PRIx32 "\n", (uint32_t)value);
+        s->dir = value;
+        /* direction is exposed in both the DIR register and the DIR bit
+         * of each PINs CNF configuration register. */
+        for (size_t i = 0; i < NRF51_GPIO_PINS; i++) {
+            s->cnf[i] = (s->cnf[i] & ~(1UL)) | ((value >> i) & 0x01);
+        }
+        gpio_update_state(s);
+        break;
+    case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END:
+        idx = (offset - NRF51_GPIO_REG_CNF_START) / 4;
+        DPRINTF("write cnf[%zu]=0x%" PRIx32 "\n", idx, (uint32_t)value);
+        s->cnf[idx] = value;
+        /* direction is exposed in both the DIR register and the DIR bit
+         * of each PINs CNF configuration register. */
+        s->dir = (s->dir & ~(1UL << idx)) | ((value & 0x01) << idx);
+        gpio_update_state(s);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+}
+
+static const MemoryRegionOps gpio_ops = {
+    .read =  gpio_read,
+    .write = gpio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+};
+
+static void nrf51_gpio_set(void *opaque, int line, int value)
+{
+    Nrf51GPIOState *s = NRF51_GPIO(opaque);
+
+    assert(line >= 0 && line < NRF51_GPIO_PINS);
+
+    DPRINTF("line %d to [MASK,VALUE] = [%s, %s]\n", line,
+            (value >= 0) ? "TRUE" : "FALSE", (value > 0) ? "HIGH" : "LOW");
+
+    s->in_mask = deposit32(s->in_mask, line, 1, value >= 0);
+    s->in = deposit32(s->in, line, 1, value > 0);
+
+    gpio_update_state(s);
+}
+
+static void nrf51_gpio_init(Object *obj)
+{
+    Nrf51GPIOState *s = NRF51_GPIO(obj);
+
+    memory_region_init_io(&s->mmio, obj, &gpio_ops, s,
+            TYPE_NRF51_GPIO, NRF51_GPIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+    qdev_init_gpio_in(DEVICE(s), nrf51_gpio_set, NRF51_GPIO_PINS);
+    qdev_init_gpio_out(DEVICE(s), s->output, NRF51_GPIO_PINS);
+}
+
+static void nrf51_gpio_reset(DeviceState *dev)
+{
+    Nrf51GPIOState *s = NRF51_GPIO(dev);
+    size_t i;
+
+    s->out = 0;
+    s->old_out = 0;
+    s->old_out_connected = 0;
+    s->in = 0;
+    s->in_mask = 0;
+    s->dir = 0;
+
+    for (i = 0; i < NRF51_GPIO_PINS; i++) {
+        s->cnf[i] = 0x00000002;
+    }
+}
+
+static const VMStateDescription vmstate_nrf51_gpio = {
+    .name = TYPE_NRF51_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(out, Nrf51GPIOState),
+        VMSTATE_UINT32(in, Nrf51GPIOState),
+        VMSTATE_UINT32(in_mask, Nrf51GPIOState),
+        VMSTATE_UINT32(dir, Nrf51GPIOState),
+        VMSTATE_UINT32_ARRAY(cnf, Nrf51GPIOState, NRF51_GPIO_PINS),
+        VMSTATE_UINT32(old_out, Nrf51GPIOState),
+        VMSTATE_UINT32(old_out_connected, Nrf51GPIOState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property nrf51_gpio_properties[] = {
+
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void nrf51_gpio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->props = nrf51_gpio_properties;
+    dc->vmsd = &vmstate_nrf51_gpio;
+    dc->reset = nrf51_gpio_reset;
+    dc->desc = "NRF51 GPIO";
+}
+
+static const TypeInfo nrf51_gpio_info = {
+    .name = TYPE_NRF51_GPIO,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Nrf51GPIOState),
+    .instance_init = nrf51_gpio_init,
+    .class_init = nrf51_gpio_class_init
+};
+
+static void nrf51_gpio_register_types(void)
+{
+    type_register_static(&nrf51_gpio_info);
+}
+
+type_init(nrf51_gpio_register_types)
diff --git a/include/hw/gpio/nrf51_gpio.h b/include/hw/gpio/nrf51_gpio.h
new file mode 100644
index 0000000000..c5b8501521
--- /dev/null
+++ b/include/hw/gpio/nrf51_gpio.h
@@ -0,0 +1,57 @@
+/*
+ * nRF51 System-on-Chip general purpose input/output register definition
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ * Product Spec: http://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.1.pdf
+ *
+ * QEMU interface:
+ * + sysbus MMIO regions 0: GPIO registers
+ * + Unnamed GPIO inputs 0-31: Set tri-state input level for GPIO pin.
+ *   Level -1: Externally Disconnected/Floating; Pull-up/down will be regarded
+ *   Level 0: Input externally driven LOW
+ *   Level 1: Input externally driven HIGH
+ * + Unnamed GPIO outputs 0-31:
+ *   Level -1: Disconnected/Floating
+ *   Level 0: Driven LOW
+ *   Level 1: Driven HIGH
+ *
+ * Accuracy of the peripheral model:
+ * + The nRF51 GPIO output driver supports two modes, standard and high-current
+ *   mode. These different drive modes are not modeled and handled the same.
+ * + Pin SENSEing is not modeled/implemented.
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ */
+#ifndef NRF51_GPIO_H
+#define NRF51_GPIO_H
+
+#include "hw/sysbus.h"
+#define TYPE_NRF51_GPIO "nrf51_soc.gpio"
+#define NRF51_GPIO(obj) OBJECT_CHECK(Nrf51GPIOState, (obj), TYPE_NRF51_GPIO)
+
+#define NRF51_GPIO_PINS 32
+
+typedef struct Nrf51GPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+
+    uint32_t out;
+    uint32_t in;
+    uint32_t in_mask;
+    uint32_t dir;
+    uint32_t cnf[NRF51_GPIO_PINS];
+
+    uint32_t old_out;
+    uint32_t old_out_connected;
+
+    qemu_irq output[NRF51_GPIO_PINS];
+} Nrf51GPIOState;
+
+
+#endif
-- 
2.18.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [Qemu-devel] [RFC v2 2/2] arm: Add nRF51 GPIO tests
  2018-07-16 10:47 [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Steffen Görtz
@ 2018-07-16 10:47 ` Steffen Görtz
  2018-07-27 12:41 ` [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Stefan Hajnoczi
  1 sibling, 0 replies; 3+ messages in thread
From: Steffen Görtz @ 2018-07-16 10:47 UTC (permalink / raw)
  To: qemu-devel
  Cc: Stefan Hajnoczi, Joel Stanley, Jim Mussared, Julia Suvorova,
	Peter Maydell, Steffen Görtz

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
 tests/microbit-test.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/tests/microbit-test.c b/tests/microbit-test.c
index c502ee3976..a1e2f67855 100644
--- a/tests/microbit-test.c
+++ b/tests/microbit-test.c
@@ -101,6 +101,12 @@ static void test_nrf51_nvmc(void)
     }
 }
 
+static void test_nrf51_gpio(void)
+{
+    /* TODO add tests for GPIO */
+    g_assert_true(false);
+}
+
 int main(int argc, char **argv)
 {
     int ret;
@@ -110,6 +116,7 @@ int main(int argc, char **argv)
     global_qtest = qtest_startf("-machine microbit");
 
     qtest_add_func("/microbit/nrf51/nvmc", test_nrf51_nvmc);
+    qtest_add_func("/microbit/nrf51/gpio", test_nrf51_gpio);
 
     ret = g_test_run();
 
-- 
2.18.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral
  2018-07-16 10:47 [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Steffen Görtz
  2018-07-16 10:47 ` [Qemu-devel] [RFC v2 2/2] arm: Add nRF51 GPIO tests Steffen Görtz
@ 2018-07-27 12:41 ` Stefan Hajnoczi
  1 sibling, 0 replies; 3+ messages in thread
From: Stefan Hajnoczi @ 2018-07-27 12:41 UTC (permalink / raw)
  To: Steffen Görtz
  Cc: qemu-devel, Joel Stanley, Jim Mussared, Julia Suvorova, Peter Maydell

[-- Attachment #1: Type: text/plain, Size: 2156 bytes --]

On Mon, Jul 16, 2018 at 12:47:43PM +0200, Steffen Görtz wrote:
> Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
> ---
>  Changes in v2:
>    - Only call QEMU GPIO update handlers if value changes
>    - Code style changes
>    - Removed unused includes
> 
>  hw/gpio/Makefile.objs        |   1 +
>  hw/gpio/nrf51_gpio.c         | 320 +++++++++++++++++++++++++++++++++++
>  include/hw/gpio/nrf51_gpio.h |  57 +++++++
>  3 files changed, 378 insertions(+)
>  create mode 100644 hw/gpio/nrf51_gpio.c
>  create mode 100644 include/hw/gpio/nrf51_gpio.h

Hi Steffen,
Sorry I didn't see this patch series.  Please email stefanha@redhat.com
in the future.  The way I have GMail set up means qemu-devel threads
with stefanha@gmail.com CCed are not brought to my attention immediately.

I have checked the nRF51 GPIO documentation and your implementation
looks good.

> +static void gpio_write(void *opaque, hwaddr offset,
> +                       uint64_t value, unsigned int size)
> +{
> +    Nrf51GPIOState *s = NRF51_GPIO(opaque);
> +    size_t idx;
> +
> +    switch (offset) {
> +    case NRF51_GPIO_REG_OUT:
> +        DPRINTF("write out=0x%" PRIx32 "\n", (uint32_t)value);
> +        s->out = value;
> +        gpio_update_state(s);
> +        break;
> +    case NRF51_GPIO_REG_OUTSET:
> +        DPRINTF("set out=0x%" PRIx32 "\n", (uint32_t)value);
> +        s->out |= value;
> +        gpio_update_state(s);
> +        break;
> +    case NRF51_GPIO_REG_OUTCLR:
> +        DPRINTF("clr out=0x%" PRIx32 "\n", (uint32_t)value);
> +        s->out &= ~value;
> +        gpio_update_state(s);
> +        break;
> +    case NRF51_GPIO_REG_DIR:
> +        DPRINTF("write dir=0x%" PRIx32 "\n", (uint32_t)value);
> +        s->dir = value;
> +        /* direction is exposed in both the DIR register and the DIR bit
> +         * of each PINs CNF configuration register. */
> +        for (size_t i = 0; i < NRF51_GPIO_PINS; i++) {
> +            s->cnf[i] = (s->cnf[i] & ~(1UL)) | ((value >> i) & 0x01);
> +        }
> +        gpio_update_state(s);
> +        break;

Missing DIRSET and DIRCLR?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2018-07-27 12:41 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-16 10:47 [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Steffen Görtz
2018-07-16 10:47 ` [Qemu-devel] [RFC v2 2/2] arm: Add nRF51 GPIO tests Steffen Görtz
2018-07-27 12:41 ` [Qemu-devel] [RFC v2 1/2] arm: Add nRF51 GPIO peripheral Stefan Hajnoczi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.