/* * 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 "qemu/module.h" #include "hw/gpio/nrf51_gpio.h" #include "hw/irq.h" #include "migration/vmstate.h" #include "trace.h" /* * 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; default: g_assert_not_reached(); } return state; } static int pull_value(uint32_t config) { int pull = extract32(config, 2, 2); if (pull == NRF51_GPIO_PULLDOWN) { return 0; } else if (pull == NRF51_GPIO_PULLUP) { return 1; } return -1; } static void update_output_irq(NRF51GPIOState *s, size_t i, bool connected, bool level) { int64_t irq_level = connected ? level : -1; 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)) { qemu_set_irq(s->output[i], irq_level); trace_nrf51_gpio_update_output_irq(i, irq_level); } s->old_out = deposit32(s->old_out, i, 1, level); s->old_out_connected = deposit32(s->old_out_connected, i, 1, connected); } static void update_state(NRF51GPIOState *s) { int pull; size_t i; bool connected_out, dir, connected_in, out, in, input; bool assert_detect = false; for (i = 0; i < NRF51_GPIO_PINS; i++) { pull = pull_value(s->cnf[i]); dir = extract32(s->cnf[i], 0, 1); connected_in = extract32(s->in_mask, i, 1); out = extract32(s->out, i, 1); in = extract32(s->in, i, 1); input = !extract32(s->cnf[i], 1, 1); connected_out = is_connected(s->cnf[i], out) && dir; if (!input) { if (pull >= 0) { /* Input buffer disconnected from external drives */ s->in = deposit32(s->in, i, 1, pull); } } else { if (connected_out && connected_in && out != in) { /* Pin both driven externally and internally */ qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i); } if (connected_in) { uint32_t detect_config = extract32(s->cnf[i], 16, 2); if ((detect_config == 2) && (in == 1)) { assert_detect = true; } if ((detect_config == 3) && (in == 0)) { assert_detect = true; } } else { /* * Floating input: the output stimulates IN if connected, * otherwise pull-up/pull-down resistors put a value on both * IN and OUT. */ if (pull >= 0 && !connected_out) { connected_out = true; out = pull; } if (connected_out) { s->in = deposit32(s->in, i, 1, out); } } } update_output_irq(s, i, connected_out, out); } qemu_set_irq(s->detect, assert_detect); } /* * Direction is exposed in both the DIR register and the DIR bit * of each PINs CNF configuration register. Reflect bits for pins in DIR * to individual pin configuration registers. */ static void reflect_dir_bit_in_cnf(NRF51GPIOState *s) { size_t i; uint32_t value = s->dir; for (i = 0; i < NRF51_GPIO_PINS; i++) { s->cnf[i] = (s->cnf[i] & ~(1UL)) | ((value >> i) & 0x01); } } static uint64_t nrf51_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: r = s->out; break; case NRF51_GPIO_REG_IN: r = s->in; break; case NRF51_GPIO_REG_DIR ... NRF51_GPIO_REG_DIRCLR: r = s->dir; break; case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END: idx = (offset - NRF51_GPIO_REG_CNF_START) / 4; r = s->cnf[idx]; break; default: qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read offset 0x%" HWADDR_PRIx "\n", __func__, offset); } trace_nrf51_gpio_read(offset, r); return r; } static void nrf51_gpio_write(void *opaque, hwaddr offset, uint64_t value, unsigned int size) { NRF51GPIOState *s = NRF51_GPIO(opaque); size_t idx; trace_nrf51_gpio_write(offset, value); switch (offset) { case NRF51_GPIO_REG_OUT: s->out = value; break; case NRF51_GPIO_REG_OUTSET: s->out |= value; break; case NRF51_GPIO_REG_OUTCLR: s->out &= ~value; break; case NRF51_GPIO_REG_DIR: s->dir = value; reflect_dir_bit_in_cnf(s); break; case NRF51_GPIO_REG_DIRSET: s->dir |= value; reflect_dir_bit_in_cnf(s); break; case NRF51_GPIO_REG_DIRCLR: s->dir &= ~value; reflect_dir_bit_in_cnf(s); break; case NRF51_GPIO_REG_CNF_START ... NRF51_GPIO_REG_CNF_END: idx = (offset - NRF51_GPIO_REG_CNF_START) / 4; 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); break; default: qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write offset 0x%" HWADDR_PRIx "\n", __func__, offset); } update_state(s); } static const MemoryRegionOps gpio_ops = { .read = nrf51_gpio_read, .write = nrf51_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); trace_nrf51_gpio_set(line, value); assert(line >= 0 && line < NRF51_GPIO_PINS); s->in_mask = deposit32(s->in_mask, line, 1, value >= 0); if (value >= 0) { s->in = deposit32(s->in, line, 1, value != 0); } update_state(s); } 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, .fields = (const 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 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); qdev_init_gpio_out_named(DEVICE(s), &s->detect, "detect", 1); } static void nrf51_gpio_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->vmsd = &vmstate_nrf51_gpio; device_class_set_legacy_reset(dc, 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)