All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Steffen Görtz" <contrib@steffen-goertz.de>
To: qemu-devel@nongnu.org
Cc: "Stefan Hajnoczi" <stefanha@gmail.com>,
	"Joel Stanley" <joel@jms.id.au>,
	"Jim Mussared" <jim@groklearning.com>,
	"Julia Suvorova" <jusual@mail.ru>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Steffen Görtz" <contrib@steffen-goertz.de>
Subject: [Qemu-devel] [RFC 1/2] arm: Add nRF51 GPIO peripheral
Date: Sun, 15 Jul 2018 18:34:03 +0200	[thread overview]
Message-ID: <20180715163404.10077-1-contrib@steffen-goertz.de> (raw)

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
The general purpose i/o implementation supports
tri-state inputs and outputs (HIGH-Z, LOW, HIGH).
SENSE output is not supported and tests will follow in V2.

 hw/gpio/Makefile.objs        |   1 +
 hw/gpio/nrf51_gpio.c         | 302 +++++++++++++++++++++++++++++++++++
 include/hw/gpio/nrf51_gpio.h |  55 +++++++
 3 files changed, 358 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..bb9464cf25
--- /dev/null
+++ b/hw/gpio/nrf51_gpio.c
@@ -0,0 +1,302 @@
+/*
+ * 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 DEBUG_NRF51_GPIO 1
+
+#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
+
+#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 gpio_update_state(Nrf51GPIOState *s)
+{
+    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);
+
+        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);
+
+        DPRINTF("[CON_OUT, DIR, OUT, CON_IN, INPUT] = [%s, %s, %s, %s, %s]\n",
+            connected_out ? "true" : "false",
+            dir ? "true" : "false",
+            out ? "HIGH" : "LOW",
+            connected_in ? "true" : "false",
+            input ? "HIGH" : "LOW"
+        );
+
+        if (connected_out && dir) {
+            DPRINTF("qemu output to %s\n", out ? "HIGH" : "LOW");
+            qemu_set_irq(s->output[i], out);
+        } else {
+            DPRINTF("qemu output to %s\n", "DISCONNECTED");
+            qemu_set_irq(s->output[i], -1);
+        }
+
+        /** Pin both driven externally and internally */
+        if (connected_out && dir && 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 && (!dir || !connected_out))) {
+            uint32_t pull = extract32(s->cnf[i], 2, 2);
+            if (pull == 1) { /* pull down */
+                DPRINTF("pulled-down\n");
+                s->in = deposit32(s->in, i, 1, 0);
+            } else if (pull == 3) { /* pull up */
+                DPRINTF("pulled-up\n");
+                s->in = deposit32(s->in, i, 1, 1);
+            }
+        }
+
+        /** Self stimulation through internal output driver **/
+        if (connected_out && dir && !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->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_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..4e73e8c1e7
--- /dev/null
+++ b/include/hw/gpio/nrf51_gpio.h
@@ -0,0 +1,55 @@
+/*
+ * 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"
+#include "qemu/timer.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];
+
+    qemu_irq output[NRF51_GPIO_PINS];
+} Nrf51GPIOState;
+
+
+#endif
-- 
2.18.0

             reply	other threads:[~2018-07-15 16:34 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-15 16:34 Steffen Görtz [this message]
2018-07-15 16:34 ` [Qemu-devel] [RFC 2/2] arm: Add nRF51 GPIO tests Steffen Görtz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180715163404.10077-1-contrib@steffen-goertz.de \
    --to=contrib@steffen-goertz.de \
    --cc=jim@groklearning.com \
    --cc=joel@jms.id.au \
    --cc=jusual@mail.ru \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.