Signed-off-by: Steffen Görtz <cont...@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 <cont...@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 <cont...@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