From patchwork Sun Jul 15 16:34:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Steffen_G=C3=B6rtz?= X-Patchwork-Id: 10525103 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id D509B602B3 for ; Sun, 15 Jul 2018 16:35:21 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C384328429 for ; Sun, 15 Jul 2018 16:35:21 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B7B1B28926; Sun, 15 Jul 2018 16:35:21 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id DD46428429 for ; Sun, 15 Jul 2018 16:35:20 +0000 (UTC) Received: from localhost ([::1]:46175 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fejzU-0001XL-2Y for patchwork-qemu-devel@patchwork.kernel.org; Sun, 15 Jul 2018 12:35:20 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:53670) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fejyP-0000oI-7H for qemu-devel@nongnu.org; Sun, 15 Jul 2018 12:34:15 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fejyM-0007Pj-2r for qemu-devel@nongnu.org; Sun, 15 Jul 2018 12:34:13 -0400 Received: from steffen-goertz.de ([88.198.119.201]:56794) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fejyL-0007MA-O8 for qemu-devel@nongnu.org; Sun, 15 Jul 2018 12:34:10 -0400 Received: from localhost.localdomain (p508EFB2D.dip0.t-ipconnect.de [80.142.251.45]) by steffen-goertz.de (Postfix) with ESMTPSA id 891724A4DB; Sun, 15 Jul 2018 18:31:59 +0200 (CEST) From: =?UTF-8?q?Steffen=20G=C3=B6rtz?= To: qemu-devel@nongnu.org Date: Sun, 15 Jul 2018 18:34:03 +0200 Message-Id: <20180715163404.10077-1-contrib@steffen-goertz.de> X-Mailer: git-send-email 2.18.0 MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 88.198.119.201 Subject: [Qemu-devel] [RFC 1/2] arm: Add nRF51 GPIO peripheral X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Peter Maydell , Jim Mussared , Stefan Hajnoczi , =?UTF-8?q?Steffen=20G=C3=B6rtz?= , Joel Stanley , Julia Suvorova Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Steffen Görtz --- 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 + * + * 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 + * + * 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